commit 0455f5d6e2a9b42a4199c40fe981f641d35574c0 Author: a1batross Date: Mon Aug 31 01:15:53 2020 +0300 1.51 Aug30 2020 https://hlfx.ru/forum/showthread.php?s=&postid=196728#post196728 diff --git a/backup.bat b/backup.bat new file mode 100644 index 0000000..0b8470c --- /dev/null +++ b/backup.bat @@ -0,0 +1,31 @@ +@echo off +color 4F +echo XashXT Group 2006 (C) +echo Prepare source for backup +echo. + +if exist backup.log del /f /q backup.log +if not exist D:\Paranoia2\!backup/ mkdir D:\Paranoia2\!backup\ +echo Prepare OK! +echo Please wait: backup in progress +C:\Progra~1\WinRar\rar a -agMMMYYYY-DD D:\Paranoia2\!backup\.rar -dh -m5 @backup.lst >>backup.log +if errorlevel 1 goto error +if errorlevel 0 goto ok +:ok +cls +echo Source was sucessfully backuped +echo and stored in folder "backup" +echo Press any key for exit. :-) +if exist backup.log del /f /q backup.log +exit +:error +echo ****************************** +echo ***********Error!************* +echo ****************************** +echo **See backup.log for details** +echo ****************************** +echo ****************************** +echo. +echo press any key for exit :-( +pause>nul +exit diff --git a/backup.lst b/backup.lst new file mode 100644 index 0000000..7b58e47 --- /dev/null +++ b/backup.lst @@ -0,0 +1,45 @@ +//======================================================================= +// Copyright XashXT Group 2007 © +// list with backup directories +//======================================================================= + +// global stuff +paranoia_sdk.dsw +debug.bat +backup.lst +backup.bat +release.bat + +cl_dll\ +cl_dll\hl\ +cl_dll\render\ +common\ +dlls\ +engine\ +game_shared\ +mainui\ +mainui\legacy +p2_launch\ +pm_shared\ +utils\ +utils\bsp31migrate\ +utils\common\ +utils\decal2tga\ +utils\hlmv\ +utils\hlsv\ +utils\mxtk\ +utils\makefont\ +utils\maketex\ +utils\makewad\ +utils\p2bsp\ +utils\p2csg\ +utils\p2rad\ +utils\p2vis\ +utils\reqtest\ +utils\squish\ +utils\stalker2tga\ +utils\spritegen\ +utils\studiomdl\ +utils\vgui\ +utils\vgui\include\ +utils\vgui\lib\win32_vc6\ \ No newline at end of file diff --git a/bspextra.h b/bspextra.h new file mode 100644 index 0000000..d85989c --- /dev/null +++ b/bspextra.h @@ -0,0 +1,124 @@ +/* +bspfile.h - BSP format included q1, hl1 support +Copyright (C) 2010 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef BSPEXTRAFILE_H +#define BSPEXTRAFILE_H + +/* +============================================================================== + +EXTRA BSP INFO + +============================================================================== +*/ + +#define IDEXTRAHEADER (('H'<<24)+('S'<<16)+('A'<<8)+'X') // little-endian "XASH" +#define EXTRA_VERSION 3 // ver. 1 was occupied by old versions of XashXT, ver. 2 was occupied by old vesrions of P2:savior + +#define MAX_MAP_CUBEMAPS 1024 +#define MAX_MAP_LANDSCAPES 8192 // can be increased but not needs +#define MAX_MAP_LEAFLIGHTS 0x40000 // can be increased but not needs +#define MAX_MAP_WORLDLIGHTS 65535 // including a light surfaces too + +#define LUMP_VERTNORMALS 0 // phong shaded vertex normals +#define LUMP_LIGHTVECS 1 // deluxemap data +#define LUMP_CUBEMAPS 2 // cubemap description +#define LUMP_LANDSCAPES 3 // landscape and lightmap resolution info +#define LUMP_LEAF_LIGHTING 4 // contain compressed light cubes per empty leafs +#define LUMP_WORLDLIGHTS 5 // list of all the virtual and real lights (used to relight models in-game) +#define LUMP_COLLISION 6 // physics engine collision hull dump +#define LUMP_AINODEGRAPH 7 // node graph that stored into the bsp +#define EXTRA_LUMPS 8 // count of the extra lumps + +#define LM_ENVIRONMENT_STYLE 20 // light_environment always handle into separate style, so we can ignore it + +typedef struct +{ + int id; // must be little endian XASH + int version; + dlump_t lumps[EXTRA_LUMPS]; +} dextrahdr_t; + +// MODIFIED: int flags -> short flags, short landscape (total struct size not changed!) +typedef struct +{ + float vecs[2][4]; // texmatrix [s/t][xyz offset] + int miptex; + short flags; + short landscape; +} dtexinfo_t; + +//============================================================================ +typedef struct +{ + float normal[3]; +} dnormal_t; + +typedef struct +{ + short origin[3]; // position of light snapped to the nearest integer + short size; // cubemap side size +} dcubemap_t; + +typedef struct +{ + byte color[6][3]; // 6 sides 1x1 (single pixel per side) +} dsample_t; + +typedef struct +{ + dsample_t cube; + short origin[3]; + short leafnum; // leaf that contain this sample +} dleafambient_t; + +typedef struct +{ + char landname[16]; // name of decsription in mapname_land.txt + word texture_step; // default is 16, pixels\luxels ratio + word max_extent; // default is 16, subdivision step ((texture_step * max_extent) - texture_step) + short groupid; // to determine equal landscapes from various groups +} dlandscape_t; + +typedef enum +{ + emit_surface, + emit_point, + emit_spotlight, + emit_skylight +} emittype_t; + +#define VERTEXNORMAL_CONE_INNER_ANGLE DEG2RAD( 7.275 ) +#define DWL_FLAGS_INAMBIENTCUBE 0x0001 // This says that the light was put into the per-leaf ambient cubes. + +typedef struct +{ + byte emittype; + byte style; + byte flags; // will be set in ComputeLeafAmbientLighting + short origin[3]; // light abs origin + float intensity[3]; // RGB + float normal[3]; // for surfaces and spotlights + float stopdot; // for spotlights + float stopdot2; // for spotlights + float fade; // falloff scaling for linear and inverse square falloff 1.0 = normal, 0.5 = farther, 2.0 = shorter etc + float radius; // light radius + short leafnum; // light linked into this leaf + byte falloff; // falloff style 0 = default (inverse square), 1 = inverse falloff, 2 = inverse square (arghrad compat) + word facenum; // face number for emit_surface + short modelnumber; // g-cont. we can't link lights with entities by entity number so we link it by bmodel number +} dworldlight_t; + +#endif//BSPFILE_H \ No newline at end of file diff --git a/cl_dll/ammo.cpp b/cl_dll/ammo.cpp new file mode 100644 index 0000000..1c32c94 --- /dev/null +++ b/cl_dll/ammo.cpp @@ -0,0 +1,1247 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Ammo.cpp +// +// implementation of CHudAmmo class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" +#include "pm_shared.h" +#include "stringlib.h" +#include +#include + +#include "ammo.h" +#include "ammohistory.h" +#include "vgui_TeamFortressViewport.h" + +WEAPON *gpActiveSel; // NULL means off, 1 means just the menu bar, otherwise + // this points to the active weapon menu item +//WEAPON *gpLastSel; // Last weapon menu selection buz: íàõ + +client_sprite_t *GetSpriteList(client_sprite_t *pList, const char *psz, int iRes, int iCount); + +WeaponsResource gWR; +extern int g_iGunMode; // buz + +int g_ammoAdded = 0; // buz + +int g_weaponselect = 0; + +void WeaponsResource :: LoadAllWeaponSprites( void ) +{ + for (int i = 0; i < MAX_WEAPONS; i++) + { + if ( rgWeapons[i].iId ) + LoadWeaponSprites( &rgWeapons[i] ); + } +} + +int WeaponsResource :: CountAmmo( int iId ) +{ + if ( iId < 0 ) + return 0; + + return riAmmo[iId]; +} + +int WeaponsResource :: HasAmmo( WEAPON *p ) +{ + if ( !p ) + return FALSE; + + // weapons with no max ammo can always be selected + if ( p->iMax1 == -1 ) + return TRUE; + + return (p->iAmmoType == -1) || p->iClip > 0 || CountAmmo(p->iAmmoType) + || CountAmmo(p->iAmmo2Type) || ( p->iFlags & WEAPON_FLAGS_SELECTONEMPTY ); +} + + +void WeaponsResource :: LoadWeaponSprites( WEAPON *pWeapon ) +{ + int i, iRes; + + if (ScreenWidth < 640) + iRes = 320; + else + iRes = 640; + + char sz[128]; + + if ( !pWeapon ) + return; + + memset( &pWeapon->rcActive, 0, sizeof(wrect_t) ); + memset( &pWeapon->rcNoAmmo, 0, sizeof(wrect_t) ); // buz + memset( &pWeapon->rcInactive, 0, sizeof(wrect_t) ); + memset( &pWeapon->rcAmmo, 0, sizeof(wrect_t) ); + memset( &pWeapon->rcAmmo2, 0, sizeof(wrect_t) ); + pWeapon->hInactive = 0; + pWeapon->hNoAmmo = 0; // buz + pWeapon->hActive = 0; + pWeapon->hAmmo = 0; + pWeapon->hAmmo2 = 0; + + sprintf(sz, "scripts/weapons/%s.txt", pWeapon->szName); + client_sprite_t *pList = SPR2_GetList(sz, &i); + + if (!pList) + return; + + client_sprite_t *p; + + p = GetSpriteList( pList, "crosshair", iRes, i ); + if (p) + { + pWeapon->hCrosshair = SPR_Load( p->szSprite ); + pWeapon->rcCrosshair = p->rc; + } + else + pWeapon->hCrosshair = NULL; + + p = GetSpriteList(pList, "autoaim", iRes, i); + if (p) + { + pWeapon->hAutoaim = SPR_Load( p->szSprite ); + pWeapon->rcAutoaim = p->rc; + } + else + pWeapon->hAutoaim = 0; + + p = GetSpriteList( pList, "zoom", iRes, i ); + if (p) + { + pWeapon->hZoomedCrosshair = SPR_Load( p->szSprite ); + pWeapon->rcZoomedCrosshair = p->rc; + } + else + { + pWeapon->hZoomedCrosshair = pWeapon->hCrosshair; //default to non-zoomed crosshair + pWeapon->rcZoomedCrosshair = pWeapon->rcCrosshair; + } + + p = GetSpriteList(pList, "zoom_autoaim", iRes, i); + if (p) + { + pWeapon->hZoomedAutoaim = SPR_Load( p->szSprite ); + pWeapon->rcZoomedAutoaim = p->rc; + } + else + { + pWeapon->hZoomedAutoaim = pWeapon->hZoomedCrosshair; //default to zoomed crosshair + pWeapon->rcZoomedAutoaim = pWeapon->rcZoomedCrosshair; + } + + p = GetSpriteList(pList, "weapon", iRes, i); + if (p) + { + pWeapon->hInactive = SPR_Load( p->szSprite ); + pWeapon->rcInactive = p->rc; + + gHR.iHistoryGap = max( gHR.iHistoryGap, pWeapon->rcActive.bottom - pWeapon->rcActive.top ); + } + else + pWeapon->hInactive = 0; + + // buz + p = GetSpriteList(pList, "noammo", iRes, i); + if (p) + { + pWeapon->hNoAmmo = SPR_Load( p->szSprite ); + pWeapon->rcNoAmmo = p->rc; + } + else + pWeapon->hNoAmmo = 0; + + p = GetSpriteList(pList, "weapon_s", iRes, i); + if (p) + { + pWeapon->hActive = SPR_Load( p->szSprite ); + pWeapon->rcActive = p->rc; + } + else + pWeapon->hActive = 0; + + p = GetSpriteList(pList, "ammo", iRes, i); + if (p) + { + pWeapon->hAmmo = SPR_Load( p->szSprite ); + pWeapon->rcAmmo = p->rc; + + gHR.iHistoryGap = max( gHR.iHistoryGap, pWeapon->rcActive.bottom - pWeapon->rcActive.top ); + } + else + pWeapon->hAmmo = 0; + + p = GetSpriteList(pList, "ammo2", iRes, i); + if (p) + { + pWeapon->hAmmo2 = SPR_Load( p->szSprite ); + pWeapon->rcAmmo2 = p->rc; + + gHR.iHistoryGap = max( gHR.iHistoryGap, pWeapon->rcActive.bottom - pWeapon->rcActive.top ); + } + else + pWeapon->hAmmo2 = 0; + + + delete [] pList; +} + +// Returns the first weapon for a given slot. +WEAPON *WeaponsResource :: GetFirstPos( int iSlot ) +{ + WEAPON *pret = NULL; + + for (int i = 0; i < MAX_WEAPON_POSITIONS; i++) + { + if ( rgSlots[iSlot][i] && HasAmmo( rgSlots[iSlot][i] ) ) + { + pret = rgSlots[iSlot][i]; + break; + } + } + + return pret; +} + +// buz +WEAPON *WeaponsResource :: GetLastPos( int iSlot ) +{ + WEAPON *pret = NULL; + + for (int i = MAX_WEAPON_POSITIONS - 1; i >= 0; i--) + { + if ( rgSlots[iSlot][i] && HasAmmo( rgSlots[iSlot][i] ) ) + { + pret = rgSlots[iSlot][i]; + break; + } + } + + return pret; +} + + +WEAPON* WeaponsResource :: GetNextActivePos( int iSlot, int iSlotPos ) +{ + if ( iSlotPos >= MAX_WEAPON_POSITIONS || iSlot >= MAX_WEAPON_SLOTS ) + return NULL; + + WEAPON *p = gWR.rgSlots[ iSlot ][ iSlotPos+1 ]; + + if ( !p || !gWR.HasAmmo(p) ) + return GetNextActivePos( iSlot, iSlotPos + 1 ); + + return p; +} + + +WEAPON* WeaponsResource :: GetPrevActivePos( int iSlot, int iSlotPos ) // buz +{ + if ( iSlotPos < 1 || iSlot < 0 ) + return NULL; + + WEAPON *p = gWR.rgSlots[ iSlot ][ iSlotPos - 1 ]; + + if ( !p || !gWR.HasAmmo(p) ) + return GetPrevActivePos( iSlot, iSlotPos - 1 ); + + return p; +} + + +int giBucketHeight, giBucketWidth, giABHeight, giABWidth; // Ammo Bar width and height + +HSPRITE ghsprBuckets; // Sprite for top row of weapons menu + +DECLARE_MESSAGE(m_Ammo, CurWeapon ); // Current weapon and clip +DECLARE_MESSAGE(m_Ammo, WeaponList); // new weapon type +DECLARE_MESSAGE(m_Ammo, AmmoX); // update known ammo type's count +DECLARE_MESSAGE(m_Ammo, AmmoPickup); // flashes an ammo pickup record +DECLARE_MESSAGE(m_Ammo, WeapPickup); // flashes a weapon pickup record +DECLARE_MESSAGE(m_Ammo, HideWeapon); // hides the weapon, ammo, and crosshair displays temporarily +DECLARE_MESSAGE(m_Ammo, ItemPickup); + +DECLARE_COMMAND(m_Ammo, Slot1); +DECLARE_COMMAND(m_Ammo, Slot2); +DECLARE_COMMAND(m_Ammo, Slot3); +DECLARE_COMMAND(m_Ammo, Slot4); +DECLARE_COMMAND(m_Ammo, Slot5); +DECLARE_COMMAND(m_Ammo, Slot6); +DECLARE_COMMAND(m_Ammo, Slot7); +DECLARE_COMMAND(m_Ammo, Slot8); +DECLARE_COMMAND(m_Ammo, Slot9); +DECLARE_COMMAND(m_Ammo, Slot10); +DECLARE_COMMAND(m_Ammo, Close); +DECLARE_COMMAND(m_Ammo, NextWeapon); +DECLARE_COMMAND(m_Ammo, PrevWeapon); + +// width of ammo fonts +#define AMMO_SMALL_WIDTH 10 +#define AMMO_LARGE_WIDTH 20 + +#define HISTORY_DRAW_TIME "5" + +int CHudAmmo::Init(void) +{ + gHUD.AddHudElem(this); + + HOOK_MESSAGE(CurWeapon); + HOOK_MESSAGE(WeaponList); + HOOK_MESSAGE(AmmoPickup); + HOOK_MESSAGE(WeapPickup); + HOOK_MESSAGE(ItemPickup); + HOOK_MESSAGE(HideWeapon); + HOOK_MESSAGE(AmmoX); + + HOOK_COMMAND("slot1", Slot1); + HOOK_COMMAND("slot2", Slot2); + HOOK_COMMAND("slot3", Slot3); + HOOK_COMMAND("slot4", Slot4); + HOOK_COMMAND("slot5", Slot5); + HOOK_COMMAND("slot6", Slot6); + HOOK_COMMAND("slot7", Slot7); + HOOK_COMMAND("slot8", Slot8); + HOOK_COMMAND("slot9", Slot9); + HOOK_COMMAND("slot10", Slot10); + HOOK_COMMAND("cancelselect", Close); + HOOK_COMMAND("invnext", NextWeapon); + HOOK_COMMAND("invprev", PrevWeapon); + + Reset(); + + CVAR_REGISTER( "hud_drawhistory_time", HISTORY_DRAW_TIME, 0 ); + CVAR_REGISTER( "hud_fastswitch", "0", FCVAR_ARCHIVE ); // controls whether or not weapons can be selected in one keypress + + m_iFlags |= HUD_ACTIVE; //!!! + + gWR.Init(); + gHR.Init(); + + return 1; +}; + + +// buz: loads menu settings from menu_settings.txt +void CHudAmmo::LoadMenuSettings( void ) +{ + char token[64]; + char *pfile; + char *pf2; + + m_menuSizeX = XRES(170); + m_menuSizeY = YRES(45); + m_menuScale = 0.4; + m_menuMinAlpha = 0.35; + m_menuAddAlpha = 0.8; + m_menuSpeed = 5; + m_menuRenderMode = kRenderTransAlpha; + m_menuOfsX = XRES(10); + m_menuOfsY = YRES(10); + m_menuSpaceX = XRES(5); + m_menuSpaceY = 0; + m_menuNumberSizeX = XRES(12); + m_menuNumberSizeY = YRES(12); + + pfile = (char *)gEngfuncs.COM_LoadFile( "scripts/menu_settings.txt", 5, NULL); + pf2 = pfile; + if (!pfile) + { + gEngfuncs.Con_Printf("Error: couldn't load menu_settings.txt, using default\n"); + m_menuAddAlpha = m_menuAddAlpha - m_menuMinAlpha; + return; + } + + while (true) + { + pfile = COM_ParseFile(pfile, token); + if (!pfile) + break; + + if (!stricmp(token, "SizeX")) + { + pfile = COM_ParseFile(pfile, token); + m_menuSizeX = XRES(atoi(token)); + } + else if (!stricmp(token, "SizeY")) + { + pfile = COM_ParseFile(pfile, token); + m_menuSizeY = YRES(atoi(token)); + } + else if (!stricmp(token, "NumberSizeX")) + { + pfile = COM_ParseFile(pfile, token); + m_menuNumberSizeX = XRES(atoi(token)); + } + else if (!stricmp(token, "NumberSizeY")) + { + pfile = COM_ParseFile(pfile, token); + m_menuNumberSizeY = YRES(atoi(token)); + } + else if (!stricmp(token, "OfsX")) + { + pfile = COM_ParseFile(pfile, token); + m_menuOfsX = XRES(atoi(token)); + } + else if (!stricmp(token, "OfsY")) + { + pfile = COM_ParseFile(pfile, token); + m_menuOfsY = YRES(atoi(token)); + } + else if (!stricmp(token, "SpaceX")) + { + pfile = COM_ParseFile(pfile, token); + m_menuSpaceX = XRES(atoi(token)); + } + else if (!stricmp(token, "SpaceY")) + { + pfile = COM_ParseFile(pfile, token); + m_menuSpaceY = YRES(atoi(token)); + } + else if (!stricmp(token, "Scale")) + { + pfile = COM_ParseFile(pfile, token); + m_menuScale = atof(token); + } + else if (!stricmp(token, "MinAlpha")) + { + pfile = COM_ParseFile(pfile, token); + m_menuMinAlpha = atof(token); + } + else if (!stricmp(token, "MaxAlpha")) + { + pfile = COM_ParseFile(pfile, token); + m_menuAddAlpha = atof(token); + } + else if (!stricmp(token, "Speed")) + { + pfile = COM_ParseFile(pfile, token); + m_menuSpeed = atof(token); + } + else if (!stricmp(token, "RenderMode")) + { + pfile = COM_ParseFile(pfile, token); + m_menuRenderMode = atoi(token); + } + else + gEngfuncs.Con_Printf("Error: unknown token %s in file menu_settings.txt\n", token); + } + + m_menuAddAlpha = m_menuAddAlpha - m_menuMinAlpha; + +/* gEngfuncs.Con_Printf("m_menuSizeX %d\n",m_menuSizeX); + gEngfuncs.Con_Printf("m_menuSizeY %d\n",m_menuSizeY); + gEngfuncs.Con_Printf("m_menuScale %f\n",m_menuScale); + gEngfuncs.Con_Printf("m_menuMinAlpha %f\n",m_menuMinAlpha); + gEngfuncs.Con_Printf("m_menuAddAlpha %f\n",m_menuAddAlpha); + gEngfuncs.Con_Printf("m_menuSpeed %f\n",m_menuSpeed); + gEngfuncs.Con_Printf("m_menuRenderMode %d\n",m_menuRenderMode);*/ + + gEngfuncs.COM_FreeFile( pf2 ); +} + + +void CHudAmmo::Reset(void) +{ + m_fFade = 0; + m_iFlags |= HUD_ACTIVE; //!!! + + gpActiveSel = NULL; + gHUD.m_iHideHUDDisplay = 0; + + gWR.Reset(); + gHR.Reset(); + + // VidInit(); + +} + +int CHudAmmo::VidInit(void) +{ + // Load sprites for buckets (top row of weapon menu) + m_HUD_bucket0 = gHUD.GetSpriteIndex( "bucket1" ); + m_HUD_selection = gHUD.GetSpriteIndex( "selection" ); + m_mach = gHUD.GetSpriteIndex( "machinegun" ); + + ghsprBuckets = gHUD.GetSprite(m_HUD_bucket0); + giBucketWidth = gHUD.GetSpriteRect(m_HUD_bucket0).right - gHUD.GetSpriteRect(m_HUD_bucket0).left; + giBucketHeight = gHUD.GetSpriteRect(m_HUD_bucket0).bottom - gHUD.GetSpriteRect(m_HUD_bucket0).top; + + gHR.iHistoryGap = max( gHR.iHistoryGap, gHUD.GetSpriteRect(m_HUD_bucket0).bottom - gHUD.GetSpriteRect(m_HUD_bucket0).top); + + // If we've already loaded weapons, let's get new sprites + gWR.LoadAllWeaponSprites(); + + if (ScreenWidth >= 640) + { + giABWidth = 20; + giABHeight = 4; + } + else + { + giABWidth = 10; + giABHeight = 2; + } + + LoadMenuSettings(); // buz + + return 1; +} + +// +// Think: +// Used for selection of weapon menu item. +// +void CHudAmmo::Think(void) +{ + if ( gHUD.m_fPlayerDead ) + return; + + if ( gHUD.m_iWeaponBits != gWR.iOldWeaponBits ) + { + gWR.iOldWeaponBits = gHUD.m_iWeaponBits; + + for (int i = MAX_WEAPONS-1; i > 0; i-- ) + { + WEAPON *p = gWR.GetWeapon(i); + + if ( p ) + { + if ( gHUD.m_iWeaponBits & ( 1 << p->iId ) ) + gWR.PickupWeapon( p ); + else + gWR.DropWeapon( p ); + } + } + } + +// if (!gpActiveSel) + if (gWR.m_iSelectedColumn == -1) // buz + return; + + // has the player selected one? + if (gHUD.m_iKeyBits & IN_ATTACK) + { + if (gpActiveSel) + { + ServerCmd(gpActiveSel->szName); + g_weaponselect = gpActiveSel->iId; + } + + // gpLastSel = gpActiveSel; + gpActiveSel = NULL; + gHUD.m_iKeyBits &= ~IN_ATTACK; + gWR.m_iSelectedColumn = -1; + + PlaySound("common/wpn_select.wav", 1); + } + +} + +// +// Helper function to return a Ammo pointer from id +// + +HSPRITE* WeaponsResource :: GetAmmoPicFromWeapon( int iAmmoId, wrect_t& rect ) +{ + for ( int i = 0; i < MAX_WEAPONS; i++ ) + { + if ( rgWeapons[i].iAmmoType == iAmmoId ) + { + rect = rgWeapons[i].rcAmmo; + return &rgWeapons[i].hAmmo; + } + else if ( rgWeapons[i].iAmmo2Type == iAmmoId ) + { + rect = rgWeapons[i].rcAmmo2; + return &rgWeapons[i].hAmmo2; + } + } + + return NULL; +} + + +// Menu Selection Code + +void WeaponsResource :: SelectSlot( int iSlot, int fAdvance, int iDirection ) +{ + if ( gHUD.m_Menu.m_fMenuDisplayed && (fAdvance == FALSE) && (iDirection == 1) ) + { // menu is overriding slot use commands + gHUD.m_Menu.SelectMenuItem( iSlot + 1 ); // slots are one off the key numbers + return; + } + + if ( iSlot > MAX_WEAPON_SLOTS ) + return; + + if ( gHUD.m_fPlayerDead || gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL ) ) + return; + + if ( !FBitSet( gHUD.m_iHideHUDDisplay, ITEM_SUIT )) + return; // no suit? + + if ( !gHUD.m_iWeaponBits ) + return; // no weapons? + + WEAPON *p = NULL; + bool fastSwitch = CVAR_GET_FLOAT( "hud_fastswitch" ) != 0; + + if ( m_iSelectedColumn == -1 ) + { + m_iSelectedColumn = iSlot; + memset(m_rgColumnSizes, 0, sizeof m_rgColumnSizes); + m_rgColumnSizes[iSlot] = 1; + gpActiveSel = GetFirstPos( iSlot ); + + // Wargon: hud_fastswitch äëÿ íîâîãî HUD'à. + if (gpActiveSel && fastSwitch && !(GetNextActivePos(gpActiveSel->iSlot, gpActiveSel->iSlotPos)) && (iSlot != 0)) + { + ServerCmd(gpActiveSel->szName); + g_weaponselect = gpActiveSel->iId; + gpActiveSel = NULL; + gWR.m_iSelectedColumn = -1; + return; + } + + PlaySound( "common/wpn_hudon.wav", 1 ); + return; + } + else + { + if (iSlot == m_iSelectedColumn) + { + if (!gpActiveSel) + return; + PlaySound("common/wpn_moveselect.wav", 1); + gpActiveSel = GetNextActivePos( gpActiveSel->iSlot, gpActiveSel->iSlotPos ); + if (!gpActiveSel) + gpActiveSel = GetFirstPos( iSlot ); + return; + } + else + { + m_iSelectedColumn = iSlot; + gpActiveSel = GetFirstPos( iSlot ); + + // Wargon: hud_fastswitch äëÿ íîâîãî HUD'à. + if (gpActiveSel && fastSwitch && !(GetNextActivePos(gpActiveSel->iSlot, gpActiveSel->iSlotPos)) && (iSlot != 0)) + { + ServerCmd(gpActiveSel->szName); + g_weaponselect = gpActiveSel->iId; + gpActiveSel = NULL; + gWR.m_iSelectedColumn = -1; + return; + } + + PlaySound( "common/wpn_hudon.wav", 1 ); + return; + } + } +} + +//------------------------------------------------------------------------ +// Message Handlers +//------------------------------------------------------------------------ + +// +// AmmoX -- Update the count of a known type of ammo +// +int CHudAmmo::MsgFunc_AmmoX(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + int iIndex = READ_BYTE(); + int iCount = READ_BYTE(); + + gWR.SetAmmo( iIndex, abs(iCount) ); + + return 1; +} + +int CHudAmmo::MsgFunc_AmmoPickup( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + int iIndex = READ_BYTE(); + int iCount = READ_BYTE(); + const char *ammoname = READ_STRING(); + + // Add ammo to the history +// gHR.AddToHistory( HISTSLOT_AMMO, iIndex, abs(iCount) ); + // buz: use new text-based vgui ammo history + if (ammoname) + { + if (!strcmp(ammoname, "Hand Grenade")) + ammoname = "handgrenade"; // prevent spaces in ammo name.. + + char msgname[256]; + sprintf(msgname, "!%s", ammoname); // dont show message if not found in titles.txt + g_ammoAdded = iCount; + gHUD.m_Message.MessageAdd( msgname, gEngfuncs.GetClientTime() ); + g_ammoAdded = 0; + // CONPRINT("[%s] - %d\n", msgname, iCount); + } + + return 1; +} + +int CHudAmmo::MsgFunc_WeapPickup( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + int iIndex = READ_BYTE(); + + // Add the weapon to the history +// gHR.AddToHistory( HISTSLOT_WEAP, iIndex ); + + // buz: use new text-based vgui ammo history + WEAPON *weap = gWR.GetWeapon( iIndex ); + if (weap) + { + char msgname[256]; + sprintf(msgname, "!%s", weap->szName); // dont show message if not found in titles.txt + gHUD.m_Message.MessageAdd( msgname, gEngfuncs.GetClientTime() ); + // CONPRINT("[%s]\n", msgname); + } + + return 1; +} + +int CHudAmmo::MsgFunc_ItemPickup( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + const char *szName = READ_STRING(); + + // Add the weapon to the history +// gHR.AddToHistory( HISTSLOT_ITEM, szName ); + // buz: use new text-based vgui ammo history + if (szName) + { + char msgname[256]; + sprintf(msgname, "!%s", szName); // dont show message if not found in titles.txt + gHUD.m_Message.MessageAdd( msgname, gEngfuncs.GetClientTime() ); + // CONPRINT("[%s]\n", msgname); + } + + return 1; +} + +int CHudAmmo :: MsgFunc_HideWeapon( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + gHUD.m_iHideHUDDisplay = READ_BYTE(); + + // buz: dont draw fucking crosshairs! + static wrect_t nullrc; + SetCrosshair( 0, nullrc, 0, 0, 0 ); + + return 1; +} + +// +// CurWeapon: Update hud state with the current weapon and clip count. Ammo +// counts are updated with AmmoX. Server assures that the Weapon ammo type +// numbers match a real ammo type. +// +int CHudAmmo::MsgFunc_CurWeapon(const char *pszName, int iSize, void *pbuf ) +{ + static wrect_t nullrc; + int fOnTarget = FALSE; + + BEGIN_READ( pbuf, iSize ); + + int iState = READ_BYTE(); + int iId = READ_CHAR(); + int iClip = READ_CHAR(); + + // detect if we're also on target + if ( iState > 1 ) + { + fOnTarget = TRUE; + } + + if ( iId < 1 ) + { + SetCrosshair(0, nullrc, 0, 0, 0); + m_pWeapon = NULL; //LRC + return 0; + } + + if ( g_iUser1 != OBS_IN_EYE ) + { + // Is player dead??? + if ((iId == -1) && (iClip == -1)) + { + gHUD.m_fPlayerDead = TRUE; + gpActiveSel = NULL; + return 1; + } + gHUD.m_fPlayerDead = FALSE; + } + + WEAPON *pWeapon = gWR.GetWeapon( iId ); + + if ( !pWeapon ) + return 0; + + if ( iClip < -1 ) + pWeapon->iClip = abs(iClip); + else + pWeapon->iClip = iClip; + + + if ( iState == 0 ) // we're not the current weapon, so update no more + return 1; + + m_pWeapon = pWeapon; + + // buz: dont draw fucking crosshairs! + SetCrosshair( 0, nullrc, 0, 0, 0 ); + + m_fFade = 200.0f; //!!! + m_iFlags |= HUD_ACTIVE; + + return 1; +} + +// +// WeaponList -- Tells the hud about a new weapon type. +// +int CHudAmmo::MsgFunc_WeaponList(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + WEAPON Weapon; + + strcpy( Weapon.szName, READ_STRING() ); + Weapon.iAmmoType = (int)READ_CHAR(); + + Weapon.iMax1 = READ_BYTE(); + if (Weapon.iMax1 == 255) + Weapon.iMax1 = -1; + + Weapon.iAmmo2Type = READ_CHAR(); + Weapon.iMax2 = READ_BYTE(); + if (Weapon.iMax2 == 255) + Weapon.iMax2 = -1; + + Weapon.iSlot = READ_CHAR(); + Weapon.iSlotPos = READ_CHAR(); + Weapon.iId = READ_CHAR(); + Weapon.iFlags = READ_SHORT(); + Weapon.iClip = 0; + + gWR.AddWeapon( &Weapon ); + + return 1; + +} + +//------------------------------------------------------------------------ +// Command Handlers +//------------------------------------------------------------------------ +// Slot button pressed +void CHudAmmo::SlotInput( int iSlot ) +{ + // Let the Viewport use it first, for menus + if ( gViewPort && gViewPort->SlotInput( iSlot ) ) + return; + + gWR.SelectSlot(iSlot, FALSE, 1); +} + +void CHudAmmo::UserCmd_Slot1(void) +{ + SlotInput( 0 ); +} + +void CHudAmmo::UserCmd_Slot2(void) +{ + SlotInput( 1 ); +} + +void CHudAmmo::UserCmd_Slot3(void) +{ + SlotInput( 2 ); +} + +void CHudAmmo::UserCmd_Slot4(void) +{ + SlotInput( 3 ); +} + +void CHudAmmo::UserCmd_Slot5(void) +{ + SlotInput( 4 ); +} + +void CHudAmmo::UserCmd_Slot6(void) +{ + SlotInput( 5 ); +} + +void CHudAmmo::UserCmd_Slot7(void) +{ + SlotInput( 6 ); +} + +void CHudAmmo::UserCmd_Slot8(void) +{ + SlotInput( 7 ); +} + +void CHudAmmo::UserCmd_Slot9(void) +{ + SlotInput( 8 ); +} + +void CHudAmmo::UserCmd_Slot10(void) +{ + SlotInput( 9 ); +} + +void CHudAmmo::UserCmd_Close(void) +{ + if (gWR.m_iSelectedColumn > -1) + { +// gpLastSel = gpActiveSel; + gpActiveSel = NULL; + gWR.m_iSelectedColumn = -1; // buz + PlaySound("common/wpn_hudoff.wav", 1); + } + else + ClientCmd("escape"); +} + + +// Selects the next item in the weapon menu +// buz - rewritten +void CHudAmmo::UserCmd_NextWeapon(void) +{ + if ( gHUD.m_fPlayerDead || (gHUD.m_iHideHUDDisplay & (HIDEHUD_WEAPONS | HIDEHUD_ALL)) ) + return; + + if (gWR.m_iSelectedColumn == -1) + { + if (m_pWeapon) + { + gWR.SelectSlot(m_pWeapon->iSlot, FALSE, 1); + } + return; + } + + if (gpActiveSel) + gpActiveSel = gWR.GetNextActivePos( gpActiveSel->iSlot, gpActiveSel->iSlotPos ); + + if (!gpActiveSel) + { + for (int i = 1; i <= MAX_WEAPON_SLOTS; i++) + { + int check = gWR.m_iSelectedColumn + i; + if (check >= MAX_WEAPON_SLOTS) + check -= MAX_WEAPON_SLOTS; + + gpActiveSel = gWR.GetFirstPos( check ); + if (gpActiveSel) + { + gWR.m_iSelectedColumn = gpActiveSel->iSlot; + break; + } + } + } + + if (gpActiveSel) + PlaySound("common/wpn_moveselect.wav", 1); +} + +// Selects the previous item in the menu +// buz - rewritten +void CHudAmmo::UserCmd_PrevWeapon(void) +{ + if ( gHUD.m_fPlayerDead || (gHUD.m_iHideHUDDisplay & (HIDEHUD_WEAPONS | HIDEHUD_ALL)) ) + return; + + if (gWR.m_iSelectedColumn == -1) + { + if (m_pWeapon) + { + gWR.SelectSlot(m_pWeapon->iSlot, FALSE, 1); + } + return; + } + + if (gpActiveSel) + gpActiveSel = gWR.GetPrevActivePos( gpActiveSel->iSlot, gpActiveSel->iSlotPos ); + + if (!gpActiveSel) + { + for (int i = 1; i <= MAX_WEAPON_SLOTS; i++) + { + int check = gWR.m_iSelectedColumn - i; + if (check < 0) + check = MAX_WEAPON_SLOTS + check; + + gpActiveSel = gWR.GetLastPos( check ); + if (gpActiveSel) + { + gWR.m_iSelectedColumn = gpActiveSel->iSlot; + break; + } + } + } + + if (gpActiveSel) + PlaySound("common/wpn_moveselect.wav", 1); +} + + + +//------------------------------------------------------------------------- +// Drawing code +//------------------------------------------------------------------------- + +int CHudAmmo::Draw(float flTime) +{ + if (!FBitSet( gHUD.m_iHideHUDDisplay, ITEM_SUIT )) + return 1; + + if ( (gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL ))) + return 1; + + // Draw Weapon Menu + DrawWList(flTime); + + // Draw ammo pickup history + gHR.DrawAmmoHistory( flTime ); + + if (!(m_iFlags & HUD_ACTIVE)) + return 0; + + if (!m_pWeapon) + { + return 0; + } + + WEAPON *pw = m_pWeapon; // shorthand + + // SPR_Draw Ammo + if ((pw->iAmmoType < 0) && (pw->iAmmo2Type < 0)) + return 0; + + // buz: draw crosshair... + if ( g_iGunMode == 3 ) + { + SPR_Set(m_pWeapon->hZoomedCrosshair, 255, 255, 255); + SPR_DrawHoles(0, + (ScreenWidth - SPR_Width(m_pWeapon->hZoomedCrosshair, 0))/2, + (ScreenHeight - SPR_Height(m_pWeapon->hZoomedCrosshair, 0))/2, + &m_pWeapon->rcZoomedCrosshair); + } + + return 1; +} + + +// +// Draws the ammo bar on the hud +// +int DrawBar(int x, int y, int width, int height, float f) +{ + int r, g, b; + + if (f < 0) + f = 0; + if (f > 1) + f = 1; + + if (f) + { + int w = f * width; + + // Always show at least one pixel if we have ammo. + if (w <= 0) + w = 1; + UnpackRGB(r, g, b, RGB_GREENISH); + FillRGBA(x, y, w, height, r, g, b, 255); + x += w; + width -= w; + } + + UnpackRGB(r, g, b, gHUD.m_iHUDColor); + + FillRGBA(x, y, width, height, r, g, b, 128); + + return (x + width); +} + + + +void DrawAmmoBar(WEAPON *p, int x, int y, int width, int height) +{ + if ( !p ) + return; + + if (p->iAmmoType != -1) + { + if (!gWR.CountAmmo(p->iAmmoType)) + return; + + float f = (float)gWR.CountAmmo(p->iAmmoType)/(float)p->iMax1; + + x = DrawBar(x, y, width, height, f); + + + // Do we have secondary ammo too? + + if (p->iAmmo2Type != -1) + { + f = (float)gWR.CountAmmo(p->iAmmo2Type)/(float)p->iMax2; + + x += 5; //!!! + + DrawBar(x, y, width, height, f); + } + } +} + + +// buz: use triapi to draw sprites on screen +void DrawSpriteAsPoly( HSPRITE hspr, wrect_t *rect, wrect_t *screenpos, int mode, float r, float g, float b, float a); + +// +// Draw Weapon Menu +// +int CHudAmmo::DrawWList(float flTime) +{ + int x,y,i; + float a; + wrect_t screenrect; + +// if ( !gpActiveSel ) + if ( gWR.m_iSelectedColumn == -1 ) // buz + return 0; + + // buz: correct column sizes + if (gWR.m_rgColumnSizes[gWR.m_iSelectedColumn] >= 1) + { + // dont need correction + memset(gWR.m_rgColumnSizes, 0, sizeof gWR.m_rgColumnSizes); + gWR.m_rgColumnSizes[gWR.m_iSelectedColumn] = 1; + } + else + { + gWR.m_rgColumnSizes[gWR.m_iSelectedColumn] += gHUD.m_flTimeDelta * m_menuSpeed; + float spaceleft = 1 - gWR.m_rgColumnSizes[gWR.m_iSelectedColumn]; + if (spaceleft < 0) + { + memset(gWR.m_rgColumnSizes, 0, sizeof gWR.m_rgColumnSizes); + gWR.m_rgColumnSizes[gWR.m_iSelectedColumn] = 1; + } + else + { + float scalefactor = 0; + for (i = 0; i < MAX_WEAPON_SLOTS; i++) + { + if (i != gWR.m_iSelectedColumn) + scalefactor += gWR.m_rgColumnSizes[i]; // compute, how much space get rest of columns + } + scalefactor = spaceleft / scalefactor; + for (i = 0; i < MAX_WEAPON_SLOTS; i++) + { + if (i != gWR.m_iSelectedColumn) + gWR.m_rgColumnSizes[i] *= scalefactor; + } + } + } + + x = m_menuOfsX; + for ( i = 0; i < MAX_WEAPON_SLOTS; i++ ) + { + // if ( gWR.m_iSelectedColumn == i ) + // a = 0.8; + // else + // a = 0.3; + a = m_menuMinAlpha + (m_menuAddAlpha * gWR.m_rgColumnSizes[i]); + + y = m_menuOfsY; + screenrect.top = y; + screenrect.bottom = y + m_menuNumberSizeY; + screenrect.left = x; + screenrect.right = x + m_menuNumberSizeX; + DrawSpriteAsPoly( gHUD.GetSprite(m_HUD_bucket0 + i), &gHUD.GetSpriteRect(m_HUD_bucket0 + i), &screenrect, m_menuRenderMode, 1.0, 1.0, 1.0, m_menuMinAlpha + m_menuAddAlpha); + y = screenrect.bottom; + + float scale = m_menuScale + ((1-m_menuScale) * gWR.m_rgColumnSizes[i]); + for ( int pos = 0; pos < MAX_WEAPON_POSITIONS; pos++ ) + { + if (gWR.rgSlots[i][pos]) + { + screenrect.top = y; + screenrect.bottom = y + (m_menuSizeY * scale); + screenrect.left = x; + screenrect.right = x + (m_menuSizeX * scale); + + if (gWR.rgSlots[i][pos] == gpActiveSel) + DrawSpriteAsPoly( gWR.rgSlots[i][pos]->hActive, &gWR.rgSlots[i][pos]->rcActive, &screenrect, m_menuRenderMode, 1.0, 1.0, 1.0, a); + else + { + if ( gWR.HasAmmo(gWR.rgSlots[i][pos]) || (gWR.rgSlots[i][pos]->hNoAmmo == 0)) + DrawSpriteAsPoly( gWR.rgSlots[i][pos]->hInactive, &gWR.rgSlots[i][pos]->rcInactive, &screenrect, m_menuRenderMode, 1.0, 1.0, 1.0, a); + else + DrawSpriteAsPoly( gWR.rgSlots[i][pos]->hNoAmmo, &gWR.rgSlots[i][pos]->rcNoAmmo, &screenrect, m_menuRenderMode, 1.0, 1.0, 1.0, a); + } + + y = screenrect.bottom + m_menuSpaceY; + } + } + x += m_menuSizeX * scale; + x += m_menuSpaceX; + } + + return 1; +} + + +/* ================================= + GetSpriteList + +Finds and returns the matching +sprite name 'psz' and resolution 'iRes' +in the given sprite list 'pList' +iCount is the number of items in the pList +================================= */ +client_sprite_t *GetSpriteList(client_sprite_t *pList, const char *psz, int iRes, int iCount) +{ + if (!pList) + return NULL; + + int i = iCount; + client_sprite_t *p = pList; + + while(i--) + { + if ((!Q_stricmp(psz, p->szName)) && (p->iRes == iRes)) + return p; + p++; + } + + return NULL; +} diff --git a/cl_dll/ammo.h b/cl_dll/ammo.h new file mode 100644 index 0000000..de772f3 --- /dev/null +++ b/cl_dll/ammo.h @@ -0,0 +1,64 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef __AMMO_H__ +#define __AMMO_H__ + +#define MAX_WEAPON_NAME 128 + + +#define WEAPON_FLAGS_SELECTONEMPTY 1 + +#define WEAPON_IS_ONTARGET 0x40 + +struct WEAPON +{ + char szName[MAX_WEAPON_NAME]; + int iAmmoType; + int iAmmo2Type; + int iMax1; + int iMax2; + int iSlot; + int iSlotPos; + int iFlags; + int iId; + int iClip; + + int iCount; // # of itesm in plist + + HSPRITE hActive; + wrect_t rcActive; + HSPRITE hInactive; + wrect_t rcInactive; + HSPRITE hNoAmmo; // buz + wrect_t rcNoAmmo; // buz + HSPRITE hAmmo; + wrect_t rcAmmo; + HSPRITE hAmmo2; + wrect_t rcAmmo2; + HSPRITE hCrosshair; + wrect_t rcCrosshair; + HSPRITE hAutoaim; + wrect_t rcAutoaim; + HSPRITE hZoomedCrosshair; + wrect_t rcZoomedCrosshair; + HSPRITE hZoomedAutoaim; + wrect_t rcZoomedAutoaim; +}; + +typedef int AMMO; + + +#endif \ No newline at end of file diff --git a/cl_dll/ammo_secondary.cpp b/cl_dll/ammo_secondary.cpp new file mode 100644 index 0000000..7ecdc0a --- /dev/null +++ b/cl_dll/ammo_secondary.cpp @@ -0,0 +1,159 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// ammo_secondary.cpp +// +// implementation of CHudAmmoSecondary class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE( m_AmmoSecondary, SecAmmoVal ); +DECLARE_MESSAGE( m_AmmoSecondary, SecAmmoIcon ); + +int CHudAmmoSecondary :: Init( void ) +{ + HOOK_MESSAGE( SecAmmoVal ); + HOOK_MESSAGE( SecAmmoIcon ); + + gHUD.AddHudElem(this); + m_HUD_ammoicon = 0; + + for ( int i = 0; i < MAX_SEC_AMMO_VALUES; i++ ) + m_iAmmoAmounts[i] = -1; // -1 means don't draw this value + + Reset(); + + return 1; +} + +void CHudAmmoSecondary :: Reset( void ) +{ + m_fFade = 0; +} + +int CHudAmmoSecondary :: VidInit( void ) +{ + return 1; +} + +int CHudAmmoSecondary :: Draw(float flTime) +{ + if ( (gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL )) ) + return 1; + + // draw secondary ammo icons above normal ammo readout + int a, x, y, r, g, b, AmmoWidth; + UnpackRGB( r, g, b, gHUD.m_iHUDColor ); //LRC + a = (int) max( MIN_ALPHA, m_fFade ); + if (m_fFade > 0) + m_fFade -= (gHUD.m_flTimeDelta * 20); // slowly lower alpha to fade out icons + ScaleColors( r, g, b, a ); + + AmmoWidth = gHUD.GetSpriteRect(gHUD.m_HUD_number_0).right - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).left; + + y = ScreenHeight - (gHUD.m_iFontHeight*4); // this is one font height higher than the weapon ammo values + x = ScreenWidth - AmmoWidth; + + if ( m_HUD_ammoicon ) + { + // Draw the ammo icon + x -= (gHUD.GetSpriteRect(m_HUD_ammoicon).right - gHUD.GetSpriteRect(m_HUD_ammoicon).left); + y -= (gHUD.GetSpriteRect(m_HUD_ammoicon).top - gHUD.GetSpriteRect(m_HUD_ammoicon).bottom); + + SPR_Set( gHUD.GetSprite(m_HUD_ammoicon), r, g, b ); + SPR_DrawAdditive( 0, x, y, &gHUD.GetSpriteRect(m_HUD_ammoicon) ); + } + else + { // move the cursor by the '0' char instead, since we don't have an icon to work with + x -= AmmoWidth; + y -= (gHUD.GetSpriteRect(gHUD.m_HUD_number_0).top - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).bottom); + } + + // draw the ammo counts, in reverse order, from right to left + for ( int i = MAX_SEC_AMMO_VALUES-1; i >= 0; i-- ) + { + if ( m_iAmmoAmounts[i] < 0 ) + continue; // negative ammo amounts imply that they shouldn't be drawn + + // half a char gap between the ammo number and the previous pic + x -= (AmmoWidth / 2); + + // draw the number, right-aligned + x -= (gHUD.GetNumWidth( m_iAmmoAmounts[i], DHN_DRAWZERO ) * AmmoWidth); + gHUD.DrawHudNumber( x, y, DHN_DRAWZERO, m_iAmmoAmounts[i], r, g, b ); + + if ( i != 0 ) + { + // draw the divider bar + x -= (AmmoWidth / 2); + FillRGBA(x, y, (AmmoWidth/10), gHUD.m_iFontHeight, r, g, b, a); + } + } + + return 1; +} + +// Message handler for Secondary Ammo Value +// accepts one value: +// string: sprite name +int CHudAmmoSecondary :: MsgFunc_SecAmmoIcon( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + m_HUD_ammoicon = gHUD.GetSpriteIndex( READ_STRING() ); + + return 1; +} + +// Message handler for Secondary Ammo Icon +// Sets an ammo value +// takes two values: +// byte: ammo index +// byte: ammo value +int CHudAmmoSecondary :: MsgFunc_SecAmmoVal( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int index = READ_BYTE(); + if ( index < 0 || index >= MAX_SEC_AMMO_VALUES ) + return 1; + + m_iAmmoAmounts[index] = READ_BYTE(); + m_iFlags |= HUD_ACTIVE; + + // check to see if there is anything left to draw + int count = 0; + for ( int i = 0; i < MAX_SEC_AMMO_VALUES; i++ ) + { + count += max( 0, m_iAmmoAmounts[i] ); + } + + if ( count == 0 ) + { // the ammo fields are all empty, so turn off this hud area + m_iFlags &= ~HUD_ACTIVE; + return 1; + } + + // make the icons light up + m_fFade = 200.0f; + + return 1; +} + + diff --git a/cl_dll/ammohistory.cpp b/cl_dll/ammohistory.cpp new file mode 100644 index 0000000..1f45444 --- /dev/null +++ b/cl_dll/ammohistory.cpp @@ -0,0 +1,190 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// ammohistory.cpp +// + + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +#include "ammohistory.h" + +HistoryResource gHR; + +#define AMMO_PICKUP_GAP (gHR.iHistoryGap+5) +#define AMMO_PICKUP_PICK_HEIGHT (32 + (gHR.iHistoryGap * 2)) +#define AMMO_PICKUP_HEIGHT_MAX (ScreenHeight - 100) + +#define MAX_ITEM_NAME 32 +int HISTORY_DRAW_TIME = 5; + +// keep a list of items +struct ITEM_INFO +{ + char szName[MAX_ITEM_NAME]; + HSPRITE spr; + wrect_t rect; +}; + +void HistoryResource :: AddToHistory( int iType, int iId, int iCount ) +{ + if ( iType == HISTSLOT_AMMO && !iCount ) + return; // no amount, so don't add + + if ( (((AMMO_PICKUP_GAP * iCurrentHistorySlot) + AMMO_PICKUP_PICK_HEIGHT) > AMMO_PICKUP_HEIGHT_MAX) || (iCurrentHistorySlot >= MAX_HISTORY) ) + { // the pic would have to be drawn too high + // so start from the bottom + iCurrentHistorySlot = 0; + } + + HIST_ITEM *freeslot = &rgAmmoHistory[iCurrentHistorySlot++]; // default to just writing to the first slot + HISTORY_DRAW_TIME = CVAR_GET_FLOAT( "hud_drawhistory_time" ); + + freeslot->type = iType; + freeslot->iId = iId; + freeslot->iCount = iCount; + freeslot->DisplayTime = gHUD.m_flTime + HISTORY_DRAW_TIME; +} + +void HistoryResource :: AddToHistory( int iType, const char *szName, int iCount ) +{ + if ( iType != HISTSLOT_ITEM ) + return; + + if ( (((AMMO_PICKUP_GAP * iCurrentHistorySlot) + AMMO_PICKUP_PICK_HEIGHT) > AMMO_PICKUP_HEIGHT_MAX) || (iCurrentHistorySlot >= MAX_HISTORY) ) + { // the pic would have to be drawn too high + // so start from the bottom + iCurrentHistorySlot = 0; + } + + HIST_ITEM *freeslot = &rgAmmoHistory[iCurrentHistorySlot++]; // default to just writing to the first slot + + // I am really unhappy with all the code in this file + + int i = gHUD.GetSpriteIndex( szName ); + if ( i == -1 ) + return; // unknown sprite name, don't add it to history + + freeslot->iId = i; + freeslot->type = iType; + freeslot->iCount = iCount; + + HISTORY_DRAW_TIME = CVAR_GET_FLOAT( "hud_drawhistory_time" ); + freeslot->DisplayTime = gHUD.m_flTime + HISTORY_DRAW_TIME; +} + + +void HistoryResource :: CheckClearHistory( void ) +{ + for ( int i = 0; i < MAX_HISTORY; i++ ) + { + if ( rgAmmoHistory[i].type ) + return; + } + + iCurrentHistorySlot = 0; +} + +// +// Draw Ammo pickup history +// +int HistoryResource :: DrawAmmoHistory( float flTime ) +{ + for ( int i = 0; i < MAX_HISTORY; i++ ) + { + if ( rgAmmoHistory[i].type ) + { + rgAmmoHistory[i].DisplayTime = min( rgAmmoHistory[i].DisplayTime, gHUD.m_flTime + HISTORY_DRAW_TIME ); + + if ( rgAmmoHistory[i].DisplayTime <= flTime ) + { // pic drawing time has expired + memset( &rgAmmoHistory[i], 0, sizeof(HIST_ITEM) ); + CheckClearHistory(); + } + else if ( rgAmmoHistory[i].type == HISTSLOT_AMMO ) + { + wrect_t rcPic; + HSPRITE *spr = gWR.GetAmmoPicFromWeapon( rgAmmoHistory[i].iId, rcPic ); + + int r, g, b; + UnpackRGB(r,g,b, gHUD.m_iHUDColor); + float scale = (rgAmmoHistory[i].DisplayTime - flTime) * 80; + ScaleColors(r, g, b, min(scale, 255) ); + + // Draw the pic + int ypos = ScreenHeight - (AMMO_PICKUP_PICK_HEIGHT + (AMMO_PICKUP_GAP * i)); + int xpos = ScreenWidth - 24; + if ( spr && *spr ) // weapon isn't loaded yet so just don't draw the pic + { // the dll has to make sure it has sent info the weapons you need + SPR_Set( *spr, r, g, b ); + SPR_DrawAdditive( 0, xpos, ypos, &rcPic ); + } + + // Draw the number + gHUD.DrawHudNumberString( xpos - 10, ypos, xpos - 100, rgAmmoHistory[i].iCount, r, g, b ); + } + else if ( rgAmmoHistory[i].type == HISTSLOT_WEAP ) + { + WEAPON *weap = gWR.GetWeapon( rgAmmoHistory[i].iId ); + + if ( !weap ) + return 1; // we don't know about the weapon yet, so don't draw anything + + int r, g, b; + UnpackRGB(r,g,b, gHUD.m_iHUDColor); + + if ( !gWR.HasAmmo( weap ) ) + UnpackRGB(r,g,b, RGB_REDISH); // if the weapon doesn't have ammo, display it as red + + float scale = (rgAmmoHistory[i].DisplayTime - flTime) * 80; + ScaleColors(r, g, b, min(scale, 255) ); + + int ypos = ScreenHeight - (AMMO_PICKUP_PICK_HEIGHT + (AMMO_PICKUP_GAP * i)); + int xpos = ScreenWidth - (weap->rcInactive.right - weap->rcInactive.left); + SPR_Set( weap->hInactive, r, g, b ); + SPR_DrawAdditive( 0, xpos, ypos, &weap->rcInactive ); + } + else if ( rgAmmoHistory[i].type == HISTSLOT_ITEM ) + { + int r, g, b; + + if ( !rgAmmoHistory[i].iId ) + continue; // sprite not loaded + + wrect_t rect = gHUD.GetSpriteRect( rgAmmoHistory[i].iId ); + + UnpackRGB(r,g,b, gHUD.m_iHUDColor); + float scale = (rgAmmoHistory[i].DisplayTime - flTime) * 80; + ScaleColors(r, g, b, min(scale, 255) ); + + int ypos = ScreenHeight - (AMMO_PICKUP_PICK_HEIGHT + (AMMO_PICKUP_GAP * i)); + int xpos = ScreenWidth - (rect.right - rect.left) - 10; + + SPR_Set( gHUD.GetSprite( rgAmmoHistory[i].iId ), r, g, b ); + SPR_DrawAdditive( 0, xpos, ypos, &rect ); + } + } + } + + + return 1; +} + + diff --git a/cl_dll/ammohistory.h b/cl_dll/ammohistory.h new file mode 100644 index 0000000..ebd2677 --- /dev/null +++ b/cl_dll/ammohistory.h @@ -0,0 +1,154 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// ammohistory.h +// + +// this is the max number of items in each bucket +#define MAX_WEAPON_POSITIONS MAX_WEAPON_SLOTS +extern WEAPON *gpActiveSel; + +class WeaponsResource +{ +public: // buz: made public to access from drawing code (valves idea about WR is suxx) + // Information about weapons & ammo + WEAPON rgWeapons[MAX_WEAPONS]; // Weapons Array + + // counts of weapons * ammo + WEAPON* rgSlots[MAX_WEAPON_SLOTS+1][MAX_WEAPON_POSITIONS+1]; // The slots currently in use by weapons. The value is a pointer to the weapon; if it's NULL, no weapon is there + int riAmmo[MAX_AMMO_SLOTS]; // count of each ammo type + +public: + void Init( void ) + { + memset( rgWeapons, 0, sizeof rgWeapons ); + Reset(); + } + + void Reset( void ) + { + iOldWeaponBits = 0; + m_iSelectedColumn = -1; + memset( rgSlots, 0, sizeof rgSlots ); + memset( riAmmo, 0, sizeof riAmmo ); + } + +///// WEAPON ///// + int iOldWeaponBits; + + WEAPON *GetWeapon( int iId ) { return &rgWeapons[iId]; } + void AddWeapon( WEAPON *wp ) + { + rgWeapons[ wp->iId ] = *wp; + LoadWeaponSprites( &rgWeapons[ wp->iId ] ); + } + + void PickupWeapon( WEAPON *wp ) + { + rgSlots[ wp->iSlot ][ wp->iSlotPos ] = wp; + if ((m_iSelectedColumn == wp->iSlot) && !gpActiveSel) // buz: menu active, but no weapons in this category + gpActiveSel = wp; + } + + void DropWeapon( WEAPON *wp ) + { + rgSlots[ wp->iSlot ][ wp->iSlotPos ] = NULL; + if (wp == gpActiveSel) // buz: removed current weapon + gpActiveSel = GetFirstPos( wp->iSlot ); + } + + void DropAllWeapons( void ) + { + for ( int i = 0; i < MAX_WEAPONS; i++ ) + { + if ( rgWeapons[i].iId ) + DropWeapon( &rgWeapons[i] ); + } + } + + WEAPON* GetWeaponSlot( int slot, int pos ) { return rgSlots[slot][pos]; } + + void LoadWeaponSprites( WEAPON* wp ); + void LoadAllWeaponSprites( void ); + WEAPON* GetFirstPos( int iSlot ); + WEAPON* GetLastPos( int iSlot ); // buz + void SelectSlot( int iSlot, int fAdvance, int iDirection ); + WEAPON* GetNextActivePos( int iSlot, int iSlotPos ); + WEAPON* GetPrevActivePos( int iSlot, int iSlotPos ); // buz + + int HasAmmo( WEAPON *p ); + +///// AMMO ///// + AMMO GetAmmo( int iId ) { return iId; } + + void SetAmmo( int iId, int iCount ) { riAmmo[ iId ] = iCount; } + + int CountAmmo( int iId ); + + HSPRITE* GetAmmoPicFromWeapon( int iAmmoId, wrect_t& rect ); + + int m_iSelectedColumn; // buz: -1 means menu inactive + float m_rgColumnSizes[MAX_WEAPON_SLOTS]; // buz +}; + +extern WeaponsResource gWR; + + +#define MAX_HISTORY 12 +enum { + HISTSLOT_EMPTY, + HISTSLOT_AMMO, + HISTSLOT_WEAP, + HISTSLOT_ITEM, +}; + +class HistoryResource +{ +private: + struct HIST_ITEM { + int type; + float DisplayTime; // the time at which this item should be removed from the history + int iCount; + int iId; + }; + + HIST_ITEM rgAmmoHistory[MAX_HISTORY]; + +public: + + void Init( void ) + { + Reset(); + } + + void Reset( void ) + { + memset( rgAmmoHistory, 0, sizeof rgAmmoHistory ); + } + + int iHistoryGap; + int iCurrentHistorySlot; + + void AddToHistory( int iType, int iId, int iCount = 0 ); + void AddToHistory( int iType, const char *szName, int iCount = 0 ); + + void CheckClearHistory( void ); + int DrawAmmoHistory( float flTime ); +}; + +extern HistoryResource gHR; + + + diff --git a/cl_dll/battery.cpp b/cl_dll/battery.cpp new file mode 100644 index 0000000..d1d25f3 --- /dev/null +++ b/cl_dll/battery.cpp @@ -0,0 +1,145 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// battery.cpp +// +// implementation of CHudBattery class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +#include "vgui_TeamFortressViewport.h" // buz +#include "vgui_hud.h" // buz + +DECLARE_MESSAGE(m_Battery, Battery) + +int CHudBattery::Init(void) +{ + m_iBat = 0; + m_fFade = 0; + m_iFlags = 0; + + HOOK_MESSAGE(Battery); + + gHUD.AddHudElem(this); + + return 1; +}; + + +int CHudBattery::VidInit(void) +{ + int HUD_suit_empty = gHUD.GetSpriteIndex( "suit_empty" ); + int HUD_suit_full = gHUD.GetSpriteIndex( "suit_full" ); + + m_hSprite1 = m_hSprite2 = 0; // delaying get sprite handles until we know the sprites are loaded + m_prc1 = &gHUD.GetSpriteRect( HUD_suit_empty ); + m_prc2 = &gHUD.GetSpriteRect( HUD_suit_full ); + m_iHeight = m_prc2->bottom - m_prc1->top; + m_fFade = 0; + return 1; +}; + +int CHudBattery:: MsgFunc_Battery(const char *pszName, int iSize, void *pbuf ) +{ + m_iFlags |= HUD_ACTIVE; + + + BEGIN_READ( pbuf, iSize ); + int x = READ_SHORT(); + + if( x != m_iBat ) + { + m_fFade = FADE_TIME; + m_iBat = x; + } + + gViewPort->m_pHud2->UpdateArmor(x); // buz + + return 1; +} + + +int CHudBattery::Draw(float flTime) +{ + return 1; // buz: dont draw battery.. + + if ( gHUD.m_iHideHUDDisplay & HIDEHUD_HEALTH ) + return 1; + + int r, g, b, x, y, a; + wrect_t rc; + + rc = *m_prc2; + rc.top += m_iHeight * ((float)(100-(min(100,m_iBat))) * 0.01); // battery can go from 0 to 100 so * 0.01 goes from 0 to 1 + + UnpackRGB(r,g,b, gHUD.m_iHUDColor); + + if (!FBitSet( gHUD.m_iHideHUDDisplay, ITEM_SUIT )) + return 1; + + // Has health changed? Flash the health # + if (m_fFade) + { + if (m_fFade > FADE_TIME) + m_fFade = FADE_TIME; + + m_fFade -= (gHUD.m_flTimeDelta * 20); + if (m_fFade <= 0) + { + a = 128; + m_fFade = 0; + } + + // Fade the health number back to dim + + a = MIN_ALPHA + (m_fFade/FADE_TIME) * 128; + + } + else + a = MIN_ALPHA; + + ScaleColors(r, g, b, a ); + + int iOffset = (m_prc1->bottom - m_prc1->top)/6; + + y = ScreenHeight - gHUD.m_iFontHeight - gHUD.m_iFontHeight / 2; + x = ScreenWidth/5; + + // make sure we have the right sprite handles + if ( !m_hSprite1 ) + m_hSprite1 = gHUD.GetSprite( gHUD.GetSpriteIndex( "suit_empty" ) ); + if ( !m_hSprite2 ) + m_hSprite2 = gHUD.GetSprite( gHUD.GetSpriteIndex( "suit_full" ) ); + + SPR_Set(m_hSprite1, r, g, b ); + SPR_DrawAdditive( 0, x, y - iOffset, m_prc1); + + if (rc.bottom > rc.top) + { + SPR_Set(m_hSprite2, r, g, b ); + SPR_DrawAdditive( 0, x, y - iOffset + (rc.top - m_prc2->top), &rc); + } + + x += (m_prc1->right - m_prc1->left); + x = gHUD.DrawHudNumber(x, y, DHN_3DIGITS | DHN_DRAWZERO, m_iBat, r, g, b); + + return 1; +} diff --git a/cl_dll/camera.h b/cl_dll/camera.h new file mode 100644 index 0000000..af1591d --- /dev/null +++ b/cl_dll/camera.h @@ -0,0 +1,24 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// Camera.h -- defines and such for a 3rd person camera +// NOTE: must include quakedef.h first + +#ifndef _CAMERA_H_ +#define _CAMEA_H_ + +// pitch, yaw, dist +extern vec3_t cam_ofs; +// Using third person camera +extern int cam_thirdperson; + +void CAM_Init( void ); +void CAM_ClearStates( void ); +void CAM_StartMouseMove(void); +void CAM_EndMouseMove(void); + +#endif // _CAMERA_H_ diff --git a/cl_dll/cdll_int.cpp b/cl_dll/cdll_int.cpp new file mode 100644 index 0000000..5388150 --- /dev/null +++ b/cl_dll/cdll_int.cpp @@ -0,0 +1,344 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cdll_int.c +// +// this implementation handles the linking of the engine to the DLL +// + +#include "hud.h" +#include "cl_util.h" +#include "netadr.h" +#include "vgui_schememanager.h" + +#include "pm_shared.h" +#include "pm_defs.h" + +#include +#include "hud_servers.h" +#include "vgui_int.h" + +int developer_level; +int g_iXashEngineBuildNumber; +BOOL g_fRenderInitialized = FALSE; +BOOL g_fRenderInterfaceValid = FALSE; +BOOL g_fXashEngine = FALSE; +cl_enginefunc_t gEngfuncs; +render_api_t gRenderfuncs; +CHud gHUD; +TeamFortressViewport *gViewPort = NULL; + +void InitInput( void ); +void ShutdownInput( void ); +void EV_HookEvents( void ); +void IN_Commands( void ); + +/* +========================== + Initialize + +Called when the DLL is first loaded. +========================== +*/ +extern "C" +{ +int DLLEXPORT Initialize( cl_enginefunc_t *pEnginefuncs, int iVersion ); +int DLLEXPORT HUD_VidInit( void ); +void DLLEXPORT HUD_Init( void ); +void DLLEXPORT HUD_Shutdown( void ); +int DLLEXPORT HUD_Redraw( float flTime, int intermission ); +int DLLEXPORT HUD_UpdateClientData( client_data_t *cdata, float flTime ); +void DLLEXPORT HUD_Reset ( void ); +void DLLEXPORT HUD_PlayerMove( struct playermove_s *ppmove, int server ); +void DLLEXPORT HUD_PlayerMoveInit( struct playermove_s *ppmove ); +char DLLEXPORT HUD_PlayerMoveTexture( char *name ); +int DLLEXPORT HUD_ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ); +void DLLEXPORT HUD_PostRunCmd( local_state_t *from, local_state_t *to, struct usercmd_s *cmd, int runfuncs, double time, unsigned int seed ); +int DLLEXPORT HUD_GetHullBounds( int hullnumber, float *mins, float *maxs ); +void DLLEXPORT HUD_Frame( double time ); +void DLLEXPORT HUD_VoiceStatus(int entindex, qboolean bTalking); +void DLLEXPORT HUD_DirectorMessage( int iSize, void *pbuf ); +void DLLEXPORT HUD_ClipMoveToEntity( physent_t *pe, const float *start, float *mins, float *maxs, const float *end, pmtrace_t *tr ); +} + +/* +================================ +HUD_GetHullBounds + + Engine calls this to enumerate player collision hulls, for prediction. Return 0 if the hullnumber doesn't exist. +================================ +*/ +int DLLEXPORT HUD_GetHullBounds( int hullnumber, float *mins, float *maxs ) +{ + int iret = 0; + + switch ( hullnumber ) + { + case 0: // Normal player + mins = Vector(-16, -16, -36); + maxs = Vector(16, 16, 36); + iret = 1; + break; + case 1: // Crouched player + mins = Vector(-16, -16, -18 ); + maxs = Vector(16, 16, 18 ); + iret = 1; + break; + case 2: // Point based hull + mins = Vector( 0, 0, 0 ); + maxs = Vector( 0, 0, 0 ); + iret = 1; + break; + } + + return iret; +} + +/* +================================ +HUD_ConnectionlessPacket + + Return 1 if the packet is valid. Set response_buffer_size if you want to send a response packet. Incoming, it holds the max + size of the response_buffer, so you must zero it out if you choose not to respond. +================================ +*/ +int DLLEXPORT HUD_ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ) +{ + // Parse stuff from args + int max_buffer_size = *response_buffer_size; + + // Zero it out since we aren't going to respond. + // If we wanted to response, we'd write data into response_buffer + *response_buffer_size = 0; + + // Since we don't listen for anything here, just respond that it's a bogus message + // If we didn't reject the message, we'd return 1 for success instead. + return 0; +} + +void DLLEXPORT HUD_PlayerMoveInit( struct playermove_s *ppmove ) +{ + PM_Init( ppmove ); +} + +char DLLEXPORT HUD_PlayerMoveTexture( char *name ) +{ + return (char)0; +} + +void DLLEXPORT HUD_PlayerMove( struct playermove_s *ppmove, int server ) +{ + PM_Move( ppmove, server ); +} + +int DLLEXPORT Initialize( cl_enginefunc_t *pEnginefuncs, int iVersion ) +{ + gEngfuncs = *pEnginefuncs; + + if( iVersion != CLDLL_INTERFACE_VERSION ) + return 0; + + memcpy( &gEngfuncs, pEnginefuncs, sizeof( cl_enginefunc_t )); + + // get developer level + developer_level = (int)CVAR_GET_FLOAT( "developer" ); + + if( CVAR_GET_POINTER( "host_clientloaded" ) != NULL ) + g_fXashEngine = TRUE; + + g_iXashEngineBuildNumber = (int)CVAR_GET_FLOAT( "build" ); // 0 for old builds or GoldSrc + if( g_iXashEngineBuildNumber <= 0 ) + g_iXashEngineBuildNumber = (int)CVAR_GET_FLOAT( "buildnum" ); + + EV_HookEvents(); + + return 1; +} + + +/* +========================== + HUD_VidInit + +Called when the game initializes +and whenever the vid_mode is changed +so the HUD can reinitialize itself. +========================== +*/ +int DLLEXPORT HUD_VidInit( void ) +{ + gHUD.VidInit(); + + VGui_Startup(); + + if( g_fXashEngine && g_fRenderInitialized ) + R_VidInit(); + + return 1; +} + +/* +========================== + HUD_Init + +Called whenever the client connects +to a server. Reinitializes all +the hud variables. +========================== +*/ +void DLLEXPORT HUD_Init( void ) +{ + InitInput(); + + if( g_fXashEngine && g_fRenderInitialized ) + GL_Init(); + + gHUD.Init(); + Scheme_Init(); +} + +void DLLEXPORT HUD_Shutdown( void ) +{ + ShutdownInput(); + + if( g_fXashEngine && g_fRenderInitialized ) + GL_Shutdown(); +} + +/* +========================== + HUD_Redraw + +called every screen frame to +redraw the HUD. +=========================== +*/ +int DLLEXPORT HUD_Redraw( float time, int intermission ) +{ + return gHUD.Redraw( time, intermission ); +} + + +/* +========================== + HUD_UpdateClientData + +called every time shared client +dll/engine data gets changed, +and gives the cdll a chance +to modify the data. + +returns 1 if anything has been changed, 0 otherwise. +========================== +*/ +int DLLEXPORT HUD_UpdateClientData(client_data_t *pcldata, float flTime ) +{ + IN_Commands(); + + return gHUD.UpdateClientData(pcldata, flTime ); +} + +/* +========================== + HUD_Reset + +Called at start and end of demos to restore to "non"HUD state. +========================== +*/ +void DLLEXPORT HUD_Reset( void ) +{ + gHUD.VidInit(); +} + +/* +========================== +HUD_Frame + +Called by engine every frame that client .dll is loaded +========================== +*/ +void DLLEXPORT HUD_Frame( double time ) +{ + ServersThink( time ); + + GetClientVoiceMgr()->Frame(time); +} + + +/* +========================== +HUD_VoiceStatus + +Called when a player starts or stops talking. +========================== +*/ +void DLLEXPORT HUD_VoiceStatus( int entindex, qboolean bTalking ) +{ + GetClientVoiceMgr()->UpdateSpeakerStatus( entindex, bTalking ); +} + +/* +========================== +HUD_DirectorEvent + +Called when a director event message was received +========================== +*/ +void DLLEXPORT HUD_DirectorMessage( int iSize, void *pbuf ) +{ + gHUD.m_Spectator.DirectorMessage( iSize, pbuf ); +} + +void DLLEXPORT HUD_PostRunCmd( local_state_t *from, local_state_t *to, struct usercmd_s *cmd, int runfuncs, double time, unsigned int seed ) +{ + to->client.fov = 0;//g_lastFOV; buz +} + +/* +========================== +HUD_ClipMoveToEntity + +This called only for non-local clients (multiplayer) +========================== +*/ +void DLLEXPORT HUD_ClipMoveToEntity( physent_t *pe, const float *start, float *mins, float *maxs, const float *end, pmtrace_t *tr ) +{ + // convert physent_t to cl_entity_t + cl_entity_t *pTouch = gEngfuncs.GetEntityByIndex( pe->info ); + trace_t trace; + + if( !pTouch ) + { + // removed entity? + tr->allsolid = false; + return; + } + + // make trace default + memset( &trace, 0, sizeof( trace )); + trace.allsolid = true; + trace.fraction = 1.0f; + trace.endpos = end; + + Physic_SweepTest( pTouch, start, mins, maxs, end, &trace ); + + // convert trace_t into pmtrace_t + memcpy( tr, &trace, 48 ); + tr->surf = trace.surf; + + if( trace.ent != NULL && PM_GetPlayerMove( )) + tr->ent = pe - PM_GetPlayerMove()->physents; + else tr->ent = -1; +} \ No newline at end of file diff --git a/cl_dll/cl_dll.dsp b/cl_dll/cl_dll.dsp new file mode 100644 index 0000000..779a0b8 --- /dev/null +++ b/cl_dll/cl_dll.dsp @@ -0,0 +1,929 @@ +# Microsoft Developer Studio Project File - Name="cl_dll" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=cl_dll - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "cl_dll.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "cl_dll.mak" CFG="cl_dll - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "cl_dll - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "cl_dll - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/GoldSrc/cl_dll", HGEBAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "cl_dll - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\temp\client\!release" +# PROP Intermediate_Dir "..\temp\client\!release" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /MD /W3 /GX /O2 /I "..\utils\vgui\include" /I "..\engine" /I "..\common" /I "..\pm_shared" /I ".\render" /I ".\\" /I "..\game_shared" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "CLIENT_DLL" /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386 +# ADD LINK32 msvcrt.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib winmm.lib ../utils/vgui/lib/win32_vc6/vgui.lib wsock32.lib glu32.lib /nologo /subsystem:windows /dll /pdb:none /machine:I386 /nodefaultlib:"libc.lib" /out:"..\temp\client\!release\client.dll" +# SUBTRACT LINK32 /map /debug +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\client\!release +InputPath=\Paranoia2\src_main\temp\client\!release\client.dll +SOURCE="$(InputPath)" + +"D:\Paranoia2\base\bin\client.dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\client.dll "D:\Paranoia2\base\bin\client.dll" + +# End Custom Build + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "cl_dll___Win32_Debug" +# PROP BASE Intermediate_Dir "cl_dll___Win32_Debug" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\temp\client\!debug" +# PROP Intermediate_Dir "..\temp\client\!debug" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /Zi /O2 /I "..\utils\vgui\include" /I "..\engine" /I "..\common" /I "..\pm_shared" /I "..\dlls" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "CLIENT_DLL" /Fr /YX /FD /c +# ADD CPP /nologo /MDd /W3 /Gi /GX /ZI /Od /I "..\utils\vgui\include" /I "..\engine" /I "..\common" /I "..\pm_shared" /I ".\render" /I ".\\" /I "..\game_shared" /D "WIN32" /D "DEBUG" /D "_DEBUG" /D "_WINDOWS" /D "CLIENT_DLL" /FAs /FR /FD /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /D "DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" /d "DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib winmm.lib ../utils/vgui/lib/win32_vc6/vgui.lib wsock32.lib opengl32.lib cg.lib cgGL.lib /nologo /subsystem:windows /dll /map /debug /machine:I386 /out:".\Release\client.dll" +# ADD LINK32 msvcrtd.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib winmm.lib ../utils/vgui/lib/win32_vc6/vgui.lib wsock32.lib /nologo /subsystem:windows /dll /incremental:yes /debug /machine:I386 /nodefaultlib:"libcd.lib" /out:"..\temp\client\!debug\client.dll" +# SUBTRACT LINK32 /map +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\client\!debug +InputPath=\Paranoia2\src_main\temp\client\!debug\client.dll +SOURCE="$(InputPath)" + +"D:\Paranoia2\base\bin\client.dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\client.dll "D:\Paranoia2\base\bin\client.dll" + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "cl_dll - Win32 Release" +# Name "cl_dll - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Group "hl" + +# PROP Default_Filter "*.CPP" +# Begin Source File + +SOURCE=..\game_shared\bone_setup.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\common.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_cubemaps.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_deferred.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_dlight.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_framebuffer.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_frustum.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_grass.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_lightmap.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_occlusion.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_primitive.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_scene.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_shader.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_shadowmap.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_slight.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_studio_draw.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_studio_init.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_studiodecal_new.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_studiovbo.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_subview.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_world_new.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\ikcontext.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\jigglebones.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\material.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\mathlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\matrix.cpp +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\meshdesc.cpp +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_shared.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\procbones.cpp +# End Source File +# Begin Source File + +SOURCE=.\stamina.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\stringlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\trace.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\vgui_checkbutton2.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\vgui_grid.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\vgui_helpers.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\vgui_listbox.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\vgui_loadtga.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\vgui_scrollbar2.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\vgui_slider2.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\voice_banmgr.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\voice_status.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\voice_vgui_tweakdlg.cpp +# End Source File +# End Group +# Begin Group "render" + +# PROP Default_Filter "*.CPP" +# Begin Source File + +SOURCE=.\render\gl_aurora.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_backend.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_cull.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_debug.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_decals.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_export.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_movie.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_postprocess.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_rmain.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_rmisc.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_rpart.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_rsurf.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_shadows.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_sky.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\gl_sprite.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\rain.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\tri.cpp +# End Source File +# Begin Source File + +SOURCE=.\render\view.cpp +# End Source File +# End Group +# Begin Source File + +SOURCE=.\ammo.cpp +# End Source File +# Begin Source File + +SOURCE=.\ammo_secondary.cpp +# End Source File +# Begin Source File + +SOURCE=.\ammohistory.cpp +# End Source File +# Begin Source File + +SOURCE=.\battery.cpp +# End Source File +# Begin Source File + +SOURCE=.\cdll_int.cpp +# End Source File +# Begin Source File + +SOURCE=.\death.cpp +# End Source File +# Begin Source File + +SOURCE=.\demo.cpp +# End Source File +# Begin Source File + +SOURCE=.\entity.cpp +# End Source File +# Begin Source File + +SOURCE=.\ev_common.cpp +# End Source File +# Begin Source File + +SOURCE=.\ev_files.cpp +# End Source File +# Begin Source File + +SOURCE=.\ev_hldm.cpp +# End Source File +# Begin Source File + +SOURCE=.\events.cpp +# End Source File +# Begin Source File + +SOURCE=.\flashlight.cpp +# End Source File +# Begin Source File + +SOURCE=.\geiger.cpp +# End Source File +# Begin Source File + +SOURCE=.\health.cpp +# End Source File +# Begin Source File + +SOURCE=.\hud.cpp +# End Source File +# Begin Source File + +SOURCE=.\hud_msg.cpp +# End Source File +# Begin Source File + +SOURCE=.\hud_redraw.cpp +# End Source File +# Begin Source File + +SOURCE=.\hud_servers.cpp +# End Source File +# Begin Source File + +SOURCE=.\hud_spectator.cpp +# End Source File +# Begin Source File + +SOURCE=.\hud_update.cpp +# End Source File +# Begin Source File + +SOURCE=.\in_camera.cpp +# End Source File +# Begin Source File + +SOURCE=.\input.cpp +# End Source File +# Begin Source File + +SOURCE=.\inputw32.cpp +# End Source File +# Begin Source File + +SOURCE=.\lensflare.cpp +# End Source File +# Begin Source File + +SOURCE=.\menu.cpp +# End Source File +# Begin Source File + +SOURCE=.\message.cpp +# End Source File +# Begin Source File + +SOURCE=.\parsemsg.cpp +# End Source File +# Begin Source File + +SOURCE=.\saytext.cpp +# End Source File +# Begin Source File + +SOURCE=.\status_icons.cpp +# End Source File +# Begin Source File + +SOURCE=.\statusbar.cpp +# End Source File +# Begin Source File + +SOURCE=.\text_message.cpp +# End Source File +# Begin Source File + +SOURCE=.\train.cpp +# End Source File +# Begin Source File + +SOURCE=.\util.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_ClassMenu.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_ConsolePanel.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_ControlConfigPanel.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_CustomObjects.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_gamma.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_hud.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_int.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_MOTDWindow.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_paranoiatext.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_radio.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_SchemeManager.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_ScorePanel.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_ServerBrowser.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_SpectatorPanel.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_subtitles.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_tabpanel.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_TeamFortressViewport.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_teammenu.cpp +# End Source File +# Begin Source File + +SOURCE=.\vgui_tips.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=.\ammo.h +# End Source File +# Begin Source File + +SOURCE=.\ammohistory.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\bitvec.h +# End Source File +# Begin Source File + +SOURCE=.\camera.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\cdll_dll.h +# End Source File +# Begin Source File + +SOURCE=.\cl_dll.h +# End Source File +# Begin Source File + +SOURCE=.\cl_util.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\clipfile.h +# End Source File +# Begin Source File + +SOURCE=.\com_weapons.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\cubemap.h +# End Source File +# Begin Source File + +SOURCE=.\custom_alloc.h +# End Source File +# Begin Source File + +SOURCE=.\demo.h +# End Source File +# Begin Source File + +SOURCE=.\enginecallback.h +# End Source File +# Begin Source File + +SOURCE=.\ev_hldm.h +# End Source File +# Begin Source File + +SOURCE=.\eventscripts.h +# End Source File +# Begin Source File + +SOURCE=.\fmod_errors.h +# End Source File +# Begin Source File + +SOURCE=.\getfont.h +# End Source File +# Begin Source File + +SOURCE=.\render\gl_aurora.h +# End Source File +# Begin Source File + +SOURCE=.\render\gl_decals.h +# End Source File +# Begin Source File + +SOURCE=.\render\gl_export.h +# End Source File +# Begin Source File + +SOURCE=.\render\gl_framebuffer.h +# End Source File +# Begin Source File + +SOURCE=.\render\gl_frustum.h +# End Source File +# Begin Source File + +SOURCE=.\render\gl_grass.h +# End Source File +# Begin Source File + +SOURCE=.\render\gl_local.h +# End Source File +# Begin Source File + +SOURCE=.\render\gl_occlusion.h +# End Source File +# Begin Source File + +SOURCE=.\render\gl_rpart.h +# End Source File +# Begin Source File + +SOURCE=.\render\gl_shader.h +# End Source File +# Begin Source File + +SOURCE=.\render\gl_sprite.h +# End Source File +# Begin Source File + +SOURCE=.\render\gl_studio.h +# End Source File +# Begin Source File + +SOURCE=.\health.h +# End Source File +# Begin Source File + +SOURCE=.\hud.h +# End Source File +# Begin Source File + +SOURCE=.\hud_iface.h +# End Source File +# Begin Source File + +SOURCE=.\hud_servers.h +# End Source File +# Begin Source File + +SOURCE=.\hud_servers_priv.h +# End Source File +# Begin Source File + +SOURCE=.\hud_spectator.h +# End Source File +# Begin Source File + +SOURCE=.\in_defs.h +# End Source File +# Begin Source File + +SOURCE=.\kbutton.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\material.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\matrix.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\meshdesc.h +# End Source File +# Begin Source File + +SOURCE=.\overview.h +# End Source File +# Begin Source File + +SOURCE=.\parsemsg.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_debug.h +# End Source File +# Begin Source File + +SOURCE=.\render\rain.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\stringlib.h +# End Source File +# Begin Source File + +SOURCE=.\render\texture.h +# End Source File +# Begin Source File + +SOURCE=.\tf_defs.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\trace.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\tri_stripper.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\utlarray.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\utlblockmemory.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\utllinkedlist.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\utlmemory.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\vector.h +# End Source File +# Begin Source File + +SOURCE=.\render\vertex_fmt.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\vgui_checkbutton2.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_ConsolePanel.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_ControlConfigPanel.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\vgui_defaultinputsignal.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_gamma.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\vgui_grid.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\vgui_helpers.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_hud.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_int.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\vgui_listbox.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\vgui_loadtga.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_paranoiatext.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_pickup.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_radio.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_SchemeManager.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_ScorePanel.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_screenmsg.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\vgui_scrollbar2.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_ServerBrowser.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_shadowtext.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\vgui_slider2.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_SpectatorPanel.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_subtitles.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_tabpanel.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_TeamFortressViewport.h +# End Source File +# Begin Source File + +SOURCE=.\vgui_tips.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\voice_banmgr.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\voice_common.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\voice_gamemgr.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\voice_status.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\voice_vgui_tweakdlg.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/cl_dll/cl_dll.h b/cl_dll/cl_dll.h new file mode 100644 index 0000000..261b98c --- /dev/null +++ b/cl_dll/cl_dll.h @@ -0,0 +1,49 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cl_dll.h +// + +// 4-23-98 JOHN + +// +// This DLL is linked by the client when they first initialize. +// This DLL is responsible for the following tasks: +// - Loading the HUD graphics upon initialization +// - Drawing the HUD graphics every frame +// - Handling the custum HUD-update packets +// +typedef unsigned char byte; +typedef unsigned short word; +typedef int (*pfnUserMsgHook)(const char *pszName, int iSize, void *pbuf); + +#include +#define EXPORT _declspec( dllexport ) + +#include "../engine/cdll_int.h" +#include "cdll_dll.h" + +extern cl_enginefunc_t gEngfuncs; + +#define CONPRINT (gEngfuncs.Con_Printf) //LRC - I can't live without printf! + +// +// gl_export.cpp +// +bool GL_Init( void ); +void GL_MapChanged( void ); +void GL_Shutdown( void ); +bool GL_Support( int r_ext ); +void R_VidInit( void ); \ No newline at end of file diff --git a/cl_dll/cl_util.h b/cl_dll/cl_util.h new file mode 100644 index 0000000..f723d53 --- /dev/null +++ b/cl_dll/cl_util.h @@ -0,0 +1,319 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cl_util.h +// + +#include "cvardef.h" + +#define EXPORT _declspec( dllexport ) +#define DLLEXPORT __declspec( dllexport ) + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif + +extern int developer_level; +extern int r_currentMessageNum; + +extern int g_iXashEngineBuildNumber; +extern BOOL g_fRenderInitialized; +extern BOOL g_fRenderInterfaceValid; +extern BOOL g_fXashEngine; + +enum +{ + DEV_NONE = 0, + DEV_NORMAL, + DEV_EXTENDED +}; + +typedef HMODULE dllhandle_t; + +typedef struct dllfunc_s +{ + const char *name; + void **func; +} dllfunc_t; + +// misc cvars +extern cvar_t *r_test; // just cvar for testify new effects +extern cvar_t *r_stencilbits; +extern cvar_t *r_drawentities; +extern cvar_t *gl_extensions; +extern cvar_t *cv_dynamiclight; +extern cvar_t *r_detailtextures; +extern cvar_t *r_lighting_ambient; +extern cvar_t *r_lighting_modulate; +extern cvar_t *r_lightstyle_lerping; +extern cvar_t *r_lighting_extended; +extern cvar_t *r_occlusion_culling; +extern cvar_t *r_show_lightprobes; +extern cvar_t *r_show_cubemaps; +extern cvar_t *r_show_viewleaf; +extern cvar_t *cv_crosshair; +extern cvar_t *r_shadows; +extern cvar_t *r_fullbright; +extern cvar_t *r_draw_beams; +extern cvar_t *r_overview; +extern cvar_t *r_novis; +extern cvar_t *r_nocull; +extern cvar_t *r_lockpvs; +extern cvar_t *r_dof; +extern cvar_t *r_dof_hold_time; +extern cvar_t *r_dof_change_time; +extern cvar_t *r_dof_focal_length; +extern cvar_t *r_dof_fstop; +extern cvar_t *r_dof_debug; +extern cvar_t *r_allow_mirrors; +extern cvar_t *cv_renderer; +extern cvar_t *cv_brdf; +extern cvar_t *cv_bump; +extern cvar_t *cv_specular; +extern cvar_t *cv_parallax; +extern cvar_t *cv_decals; +extern cvar_t *cv_realtime_puddles; +extern cvar_t *cv_shadow_offset; +extern cvar_t *cv_cubemaps; +extern cvar_t *cv_deferred; +extern cvar_t *cv_deferred_full; +extern cvar_t *cv_deferred_maxlights; +extern cvar_t *cv_deferred_tracebmodels; +extern cvar_t *cv_cube_lod_bias; +extern cvar_t *cv_gamma; +extern cvar_t *cv_brightness; +extern cvar_t *cv_water; +extern cvar_t *cv_decalsdebug; +extern cvar_t *cv_show_tbn; +extern cvar_t *cv_nosort; +extern cvar_t *r_lightmap; +extern cvar_t *r_speeds; +extern cvar_t *r_decals; +extern cvar_t *r_studio_decals; +extern cvar_t *r_hand; +extern cvar_t *r_sunshadows; +extern cvar_t *r_sun_allowed; +extern cvar_t *r_shadow_split_weight; +extern cvar_t *r_wireframe; +extern cvar_t *r_lightstyles; +extern cvar_t *r_polyoffset; +extern cvar_t *r_dynamic; +extern cvar_t *r_finish; +extern cvar_t *r_clear; +extern cvar_t *r_grass; +extern cvar_t *r_grass_alpha; +extern cvar_t *r_grass_lighting; +extern cvar_t *r_grass_shadows; +extern cvar_t *r_grass_fade_start; +extern cvar_t *r_grass_fade_dist; +extern cvar_t *r_scissor_glass_debug; +extern cvar_t *r_scissor_light_debug; +extern cvar_t *r_showlightmaps; +extern cvar_t *r_recursion_depth; +extern cvar_t *r_shadowmap_size; +extern cvar_t *r_pssm_show_split; +extern cvar_t *v_sunshafts; +extern cvar_t *v_glows; + +extern "C" void DLLEXPORT HUD_StudioEvent( const struct mstudioevent_s *event, const struct cl_entity_s *entity ); + +// Macros to hook function calls into the HUD object +#define HOOK_MESSAGE(x) gEngfuncs.pfnHookUserMsg(#x, __MsgFunc_##x ); + +#define DECLARE_MESSAGE(y, x) int __MsgFunc_##x(const char *pszName, int iSize, void *pbuf) \ + { \ + return gHUD.##y.MsgFunc_##x(pszName, iSize, pbuf ); \ + } + + +#define HOOK_COMMAND(x, y) gEngfuncs.pfnAddCommand( x, __CmdFunc_##y ); +#define DECLARE_COMMAND(y, x) void __CmdFunc_##x( void ) \ + { \ + gHUD.##y.UserCmd_##x( ); \ + } + +#define SPR_Set (*gEngfuncs.pfnSPR_Set) +#define SPR_Frames (*gEngfuncs.pfnSPR_Frames) +client_sprite_t *SPR2_GetList( char *psz, int *piCount ); // new version + +// SPR_Draw draws a the current sprite as solid +#define SPR_Draw (*gEngfuncs.pfnSPR_Draw) +// SPR_DrawHoles draws the current sprites, with color index255 not drawn (transparent) +#define SPR_DrawHoles (*gEngfuncs.pfnSPR_DrawHoles) +// SPR_DrawAdditive adds the sprites RGB values to the background (additive transulency) +#define SPR_DrawAdditive (*gEngfuncs.pfnSPR_DrawAdditive) + +// SPR_EnableScissor sets a clipping rect for HUD sprites. (0,0) is the top-left hand corner of the screen. +#define SPR_EnableScissor (*gEngfuncs.pfnSPR_EnableScissor) +// SPR_DisableScissor disables the clipping rect +#define SPR_DisableScissor (*gEngfuncs.pfnSPR_DisableScissor) +// +#define FillRGBA (*gEngfuncs.pfnFillRGBA) + + +// ScreenHeight returns the height of the screen, in pixels +#define ScreenHeight (gHUD.m_scrinfo.iHeight) +// ScreenWidth returns the width of the screen, in pixels +#define ScreenWidth (gHUD.m_scrinfo.iWidth) + +// Use this to set any co-ords in 640x480 space +#define XRES(x) ((int)(float(x) * ((float)ScreenWidth / 640.0f) + 0.5f)) +#define YRES(y) ((int)(float(y) * ((float)ScreenHeight / 480.0f) + 0.5f)) + +// use this to project world coordinates to screen coordinates +#define XPROJECT(x) ( (1.0f+(x))*ScreenWidth*0.5f ) +#define YPROJECT(y) ( (1.0f-(y))*ScreenHeight*0.5f ) + +#define GetScreenInfo (*gEngfuncs.pfnGetScreenInfo) +#define ServerCmd (*gEngfuncs.pfnServerCmd) +#define ClientCmd (*gEngfuncs.pfnClientCmd) +#define SetCrosshair (*gEngfuncs.pfnSetCrosshair) +#define AngleVectors (*gEngfuncs.pfnAngleVectors) + +inline int ConsoleStringLen( const char *string ) +{ + int _width, _height; + GetConsoleStringSize( string, &_width, &_height ); + return _width; +} + +// returns the players name of entity no. +#define GetPlayerInfo (*gEngfuncs.pfnGetPlayerInfo) + +#define max(a, b) (((a) > (b)) ? (a) : (b)) +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#define fabs(x) ((x) > 0 ? (x) : 0 - (x)) + +void ScaleColors( int &r, int &g, int &b, int a ); + +#define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2]) +#define VectorSubtract(a,b,c) {(c)[0]=(a)[0]-(b)[0];(c)[1]=(a)[1]-(b)[1];(c)[2]=(a)[2]-(b)[2];} +#define VectorAdd(a,b,c) {(c)[0]=(a)[0]+(b)[0];(c)[1]=(a)[1]+(b)[1];(c)[2]=(a)[2]+(b)[2];} +#define VectorCopy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2]) +inline void VectorClear(float *a) { a[0]=0.0;a[1]=0.0;a[2]=0.0;} +float Length(const float *v); +void VectorMA (const float *veca, float scale, const float *vecb, float *vecc); +void VectorScale (const float *in, float scale, float *out); +float VectorNormalize (float *v); + +extern vec3_t vec3_origin; +extern struct ref_params_s *g_pViewParams; + +// disable 'possible loss of data converting float to int' warning message +#pragma warning( disable: 4244 ) +// disable 'truncation from 'const double' to 'float' warning message +#pragma warning( disable: 4305 ) + +inline void UnpackRGB(int &r, int &g, int &b, unsigned long ulRGB)\ +{\ + r = (ulRGB & 0xFF0000) >>16;\ + g = (ulRGB & 0xFF00) >> 8;\ + b = ulRGB & 0xFF;\ +} + +inline unsigned int PackRGBA( int r, int g, int b, int a ) +{ + r = bound( 0, r, 255 ); + g = bound( 0, g, 255 ); + b = bound( 0, b, 255 ); + a = bound( 0, a, 255 ); + + return ((a)<<24|(r)<<16|(g)<<8|(b)); +} + +inline void UnpackRGBA( int &r, int &g, int &b, int &a, unsigned int ulRGBA ) +{ + a = (ulRGBA & 0xFF000000) >> 24; + r = (ulRGBA & 0xFF0000) >> 16; + g = (ulRGBA & 0xFF00) >> 8; + b = (ulRGBA & 0xFF) >> 0; +} + +float PackColor( const color24 &color ); +color24 UnpackColor( float pack ); + +HSPRITE LoadSprite(const char *pszName); + +#define CHECKVISBIT( vis, b ) ((b) >= 0 ? (byte)((vis)[(b) >> 3] & (1 << ((b) & 7))) : (byte)false ) +#define SETVISBIT( vis, b )( void ) ((b) >= 0 ? (byte)((vis)[(b) >> 3] |= (1 << ((b) & 7))) : (byte)false ) +#define CLEARVISBIT( vis, b )( void ) ((b) >= 0 ? (byte)((vis)[(b) >> 3] &= ~(1 << ((b) & 7))) : (byte)false ) + +typedef struct leaflist_s +{ + int count; + int maxcount; + bool overflowed; + short *list; + Vector mins, maxs; + struct mnode_s *headnode; // for overflows where each leaf can't be stored individually +} leaflist_t; + +struct mleaf_s *Mod_PointInLeaf( const Vector &p, struct mnode_s *node ); +byte *Mod_LeafPVS( struct mleaf_s *leaf, struct model_s *model ); +byte *Mod_GetCurrentVis( void ); +bool Mod_BoxVisible( const Vector &mins, const Vector &maxs, const byte *visbits ); +bool Mod_CheckEntityPVS( cl_entity_t *ent ); +bool Mod_CheckTempEntityPVS( struct tempent_s *pTemp ); +bool Mod_CheckEntityLeafPVS( const Vector &absmin, const Vector &absmax, struct mleaf_s *leaf ); +bool Mod_CheckBoxVisible( const Vector &absmin, const Vector &absmax ); +void Mod_GetFrames( int modelIndex, int &numFrames ); +struct model_s *Mod_Handle( int modelIndex ); +bool Mod_PointInSolid( const Vector &p ); +int Mod_GetType( int modelIndex ); +extern void ParseRain( void ); +extern int CL_IsDead( void ); +void SetDLightVis( struct mworldlight_s *wl, int leafnum ); +void MergeDLightVis( struct mworldlight_s *wl, int leafnum ); + +bool UTIL_IsPlayer( int idx ); +bool UTIL_IsLocal( int idx ); +void UTIL_WeaponAnimation( int iAnim, float framerate ); +void UTIL_StudioDecal( const char *pDecalName, struct pmtrace_s *pTrace, const Vector &vecSrc ); +bool R_ScissorForAABB( const Vector &absmin, const Vector &absmax, float *x, float *y, float *w, float *h ); +bool R_AABBToScreen( const Vector &absmin, const Vector &absmax, Vector2D &scrmin, Vector2D &scrmax, wrect_t *rect = NULL ); +bool R_ScissorForFrustum( class CFrustum *frustum, float *x, float *y, float *w, float *h ); +void R_DrawScissorRectangle( float x, float y, float w, float h ); +int WorldToScreen( const Vector &world, Vector &screen ); +void R_TransformWorldToDevice( const Vector &world, Vector &ndc ); +void R_TransformDeviceToScreen( const Vector &ndc, Vector &screen ); +float ComputePixelWidthOfSphere( const Vector& vecOrigin, float flRadius ); + +bool R_SkyIsVisible( void ); + +bool R_ClipPolygon( int numPoints, Vector *points, const struct mplane_s *plane, int *numClipped, Vector *clipped ); +void R_SplitPolygon( int numPoints, Vector *points, const struct mplane_s *plane, int *numFront, Vector *front, int *numBack, Vector *back ); + +// dll managment +bool Sys_LoadLibrary( const char *dllname, dllhandle_t *handle, const dllfunc_t *fcts = NULL ); +void *Sys_GetProcAddress( dllhandle_t handle, const char *name ); +void Sys_FreeLibrary( dllhandle_t *handle ); +bool Sys_RemoveFile( const char *path ); + +void UTIL_CreateAurora( cl_entity_t *ent, const char *file, int attachment, float lifetime = 0.0f ); +void UTIL_RemoveAurora( cl_entity_t *ent ); + +extern void AngleMatrix (const float *angles, float (*matrix)[4] ); +extern void VectorTransform (const float *in1, float in2[3][4], float *out); +extern void SetPoint( float x, float y, float z, float (*matrix)[4]); +extern void CreateDecal( const Vector &p, const Vector &n, float ang, const char *sz, int flags = 0, int eIdx = 0, int mIdx = 0, bool source = true ); +extern void CreateDecal( struct pmtrace_s *tr, const char *name, float angle, bool visent = false ); + +extern void Physic_SweepTest( struct cl_entity_s *pTouch, const Vector &start, const Vector &mins, const Vector &maxs, const Vector &end, trace_t *tr ); + +extern void BuildGammaTable( void ); +extern float TextureToLinear( int c ); +extern int LinearToTexture( float f ); +extern void GL_GpuMemUsage_f( void ); diff --git a/cl_dll/custom_alloc.h b/cl_dll/custom_alloc.h new file mode 100644 index 0000000..3613af7 --- /dev/null +++ b/cl_dll/custom_alloc.h @@ -0,0 +1,108 @@ +/************************************ + simple memory allocator + by BUzer +************************************/ + +template +class MemBlock +{ + typedef struct chunk_s { + int next; + T data; + } chunk_t; + +public: + MemBlock(int numElements) + { + // ýëåìåíò 0 èñïîëüçóåòñÿ â êà÷åñòâå íà÷àëà ñïèñêà çàíÿòûõ ÿ÷ååê + m_iArraySize = numElements + 1; + m_pArray = new chunk_t[m_iArraySize]; + + if (!m_pArray) + { + m_iArraySize = 1; + m_iFirstFree = m_iArraySize; + return; + } + + Clear(); + } + + ~MemBlock() + { + delete[] m_pArray; + m_pArray = NULL; + } + + void Clear( void ) + { + if (m_iArraySize > 1) + { + m_pArray[0].next = 0; // åñëè îí ññûëàåòñÿ ñàì íà ñåáÿ, çíà÷èò ñïèñîê çàíÿòûõ ïóñò + m_iFirstFree = 1; + + for (int i = 1; i < m_iArraySize; ++i) + m_pArray[i].next = i + 1; + } + } + + T* Allocate( void ) + { + if (m_iFirstFree != m_iArraySize) + { + int savedFirstFree = m_pArray[m_iFirstFree].next; + m_pArray[m_iFirstFree].next = m_pArray[0].next; // äîáàâëÿåì ñâîáîäíóþ ÿ÷åéêó â + m_pArray[0].next = m_iFirstFree; // ñïèñîê çàíÿòûõ + m_iFirstFree = savedFirstFree; // èñêëþ÷àåì ÿ÷åéêó èç ñïèñêà ñâîáîäíûõ + return &(m_pArray[m_pArray[0].next].data); + } + else + return NULL; + } + + bool IsClear( void ) + { + return m_pArray[0].next ? false : true; + } + + bool StartPass( void ) + { + m_iCurrent = 0; // íà÷èíàåì îáõîä ñ íóëåâîãî ýëåìåíòà + + if (m_iArraySize > 1) + return true; + else + return false; + } + + T* GetCurrent( void ) + { + int retindex = m_pArray[m_iCurrent].next; + if (!retindex) + return NULL; + + return &(m_pArray[retindex].data); + } + + void MoveNext( void ) + { + m_iCurrent = m_pArray[m_iCurrent].next; + } + + void DeleteCurrent( void ) + { + int delindex = m_pArray[m_iCurrent].next; + m_pArray[m_iCurrent].next = m_pArray[delindex].next; // âûáðàñûâàåì ýëåìåíò èç öåïè çàíÿòûõ + m_pArray[delindex].next = m_iFirstFree; + m_iFirstFree = delindex; // âêëþ÷àåì ýëåìåíò â íà÷àëî öåïè ñâîáîäíûõ + } + +private: + chunk_t *m_pArray; + int m_iArraySize; + int m_iCurrent; // äëÿ ïðîõîæäåíèÿ ÷åðåç ìàññèâ + + int m_iFirstFree; // íà÷àëî ñïèñêà ñâîáîäíûõ ýëåìåíòîâ +}; + + diff --git a/cl_dll/death.cpp b/cl_dll/death.cpp new file mode 100644 index 0000000..4fec049 --- /dev/null +++ b/cl_dll/death.cpp @@ -0,0 +1,297 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// death notice +// +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +#include "vgui_TeamFortressViewport.h" + +DECLARE_MESSAGE( m_DeathNotice, DeathMsg ); + +struct DeathNoticeItem { + char szKiller[MAX_PLAYER_NAME_LENGTH*2]; + char szVictim[MAX_PLAYER_NAME_LENGTH*2]; + int iId; // the index number of the associated sprite + int iSuicide; + int iTeamKill; + int iNonPlayerKill; + float flDisplayTime; + float *KillerColor; + float *VictimColor; +}; + +#define MAX_DEATHNOTICES 4 +static int DEATHNOTICE_DISPLAY_TIME = 6; + +#define DEATHNOTICE_TOP 32 + +DeathNoticeItem rgDeathNoticeList[ MAX_DEATHNOTICES + 1 ]; + +float g_ColorBlue[3] = { 0.6, 0.8, 1.0 }; +float g_ColorRed[3] = { 1.0, 0.25, 0.25 }; +float g_ColorGreen[3] = { 0.6, 1.0, 0.6 }; +float g_ColorYellow[3] = { 1.0, 0.7, 0.0 }; +float g_ColorGrey[3] = { 0.8, 0.8, 0.8 }; + +float *GetClientColor( int clientIndex ) +{ + switch ( g_PlayerExtraInfo[clientIndex].teamnumber ) + { + case 1: return g_ColorBlue; + case 2: return g_ColorRed; + case 3: return g_ColorYellow; + case 4: return g_ColorGreen; + case 0: return g_ColorYellow; + + default : return g_ColorGrey; + } + + return NULL; +} + +int CHudDeathNotice :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( DeathMsg ); + + CVAR_REGISTER( "hud_deathnotice_time", "6", 0 ); + + return 1; +} + + +void CHudDeathNotice :: InitHUDData( void ) +{ + memset( rgDeathNoticeList, 0, sizeof(rgDeathNoticeList) ); +} + + +int CHudDeathNotice :: VidInit( void ) +{ + m_HUD_d_skull = gHUD.GetSpriteIndex( "d_skull" ); + + return 1; +} + +int CHudDeathNotice :: Draw( float flTime ) +{ + int x, y, r, g, b; + + for ( int i = 0; i < MAX_DEATHNOTICES; i++ ) + { + if ( rgDeathNoticeList[i].iId == 0 ) + break; // we've gone through them all + + if ( rgDeathNoticeList[i].flDisplayTime < flTime ) + { // display time has expired + // remove the current item from the list + memmove( &rgDeathNoticeList[i], &rgDeathNoticeList[i+1], sizeof(DeathNoticeItem) * (MAX_DEATHNOTICES - i) ); + i--; // continue on the next item; stop the counter getting incremented + continue; + } + + rgDeathNoticeList[i].flDisplayTime = min( rgDeathNoticeList[i].flDisplayTime, gHUD.m_flTime + DEATHNOTICE_DISPLAY_TIME ); + + // Only draw if the viewport will let me + if ( gViewPort && gViewPort->AllowedToPrintText() ) + { + // Draw the death notice + y = YRES(DEATHNOTICE_TOP) + 2 + (20 * i); //!!! + + int id = (rgDeathNoticeList[i].iId == -1) ? m_HUD_d_skull : rgDeathNoticeList[i].iId; + x = ScreenWidth - ConsoleStringLen(rgDeathNoticeList[i].szVictim) - (gHUD.GetSpriteRect(id).right - gHUD.GetSpriteRect(id).left); + + if ( !rgDeathNoticeList[i].iSuicide ) + { + x -= (5 + ConsoleStringLen( rgDeathNoticeList[i].szKiller ) ); + + // Draw killers name + if ( rgDeathNoticeList[i].KillerColor ) + gEngfuncs.pfnDrawSetTextColor( rgDeathNoticeList[i].KillerColor[0], rgDeathNoticeList[i].KillerColor[1], rgDeathNoticeList[i].KillerColor[2] ); + x = 5 + DrawConsoleString( x, y, rgDeathNoticeList[i].szKiller ); + } + + r = 255; g = 80; b = 0; + if ( rgDeathNoticeList[i].iTeamKill ) + { + r = 10; g = 240; b = 10; // display it in sickly green + } + + // Draw death weapon + SPR_Set( gHUD.GetSprite(id), r, g, b ); + SPR_DrawAdditive( 0, x, y, &gHUD.GetSpriteRect(id) ); + + x += (gHUD.GetSpriteRect(id).right - gHUD.GetSpriteRect(id).left); + + // Draw victims name (if it was a player that was killed) + if (rgDeathNoticeList[i].iNonPlayerKill == FALSE) + { + if ( rgDeathNoticeList[i].VictimColor ) + gEngfuncs.pfnDrawSetTextColor( rgDeathNoticeList[i].VictimColor[0], rgDeathNoticeList[i].VictimColor[1], rgDeathNoticeList[i].VictimColor[2] ); + x = DrawConsoleString( x, y, rgDeathNoticeList[i].szVictim ); + } + } + } + + return 1; +} + +// This message handler may be better off elsewhere +int CHudDeathNotice :: MsgFunc_DeathMsg( const char *pszName, int iSize, void *pbuf ) +{ + m_iFlags |= HUD_ACTIVE; + + BEGIN_READ( pbuf, iSize ); + + int killer = READ_BYTE(); + int victim = READ_BYTE(); + + char killedwith[32]; + strcpy( killedwith, "d_" ); + strncat( killedwith, READ_STRING(), 32 ); + + if (gViewPort) + gViewPort->DeathMsg( killer, victim ); + + gHUD.m_Spectator.DeathMessage(victim); + + for ( int i = 0; i < MAX_DEATHNOTICES; i++ ) + { + if ( rgDeathNoticeList[i].iId == 0 ) + break; + } + if ( i == MAX_DEATHNOTICES ) + { // move the rest of the list forward to make room for this item + memmove( rgDeathNoticeList, rgDeathNoticeList+1, sizeof(DeathNoticeItem) * MAX_DEATHNOTICES ); + i = MAX_DEATHNOTICES - 1; + } + + if (gViewPort) + gViewPort->GetAllPlayersInfo(); + + // Get the Killer's name + char *killer_name = g_PlayerInfoList[ killer ].name; + if ( !killer_name ) + { + killer_name = ""; + rgDeathNoticeList[i].szKiller[0] = 0; + } + else + { + rgDeathNoticeList[i].KillerColor = GetClientColor( killer ); + strncpy( rgDeathNoticeList[i].szKiller, killer_name, MAX_PLAYER_NAME_LENGTH ); + rgDeathNoticeList[i].szKiller[MAX_PLAYER_NAME_LENGTH-1] = 0; + } + + // Get the Victim's name + char *victim_name = NULL; + // If victim is -1, the killer killed a specific, non-player object (like a sentrygun) + if ( ((char)victim) != -1 ) + victim_name = g_PlayerInfoList[ victim ].name; + if ( !victim_name ) + { + victim_name = ""; + rgDeathNoticeList[i].szVictim[0] = 0; + } + else + { + rgDeathNoticeList[i].VictimColor = GetClientColor( victim ); + strncpy( rgDeathNoticeList[i].szVictim, victim_name, MAX_PLAYER_NAME_LENGTH ); + rgDeathNoticeList[i].szVictim[MAX_PLAYER_NAME_LENGTH-1] = 0; + } + + // Is it a non-player object kill? + if ( ((char)victim) == -1 ) + { + rgDeathNoticeList[i].iNonPlayerKill = TRUE; + + // Store the object's name in the Victim slot (skip the d_ bit) + strcpy( rgDeathNoticeList[i].szVictim, killedwith+2 ); + } + else + { + if ( killer == victim || killer == 0 ) + rgDeathNoticeList[i].iSuicide = TRUE; + + if ( !strcmp( killedwith, "d_teammate" ) ) + rgDeathNoticeList[i].iTeamKill = TRUE; + } + + // Find the sprite in the list + int spr = gHUD.GetSpriteIndex( killedwith ); + + rgDeathNoticeList[i].iId = spr; + + DEATHNOTICE_DISPLAY_TIME = CVAR_GET_FLOAT( "hud_deathnotice_time" ); + rgDeathNoticeList[i].flDisplayTime = gHUD.m_flTime + DEATHNOTICE_DISPLAY_TIME; + + if (rgDeathNoticeList[i].iNonPlayerKill) + { + ConsolePrint( rgDeathNoticeList[i].szKiller ); + ConsolePrint( " killed a " ); + ConsolePrint( rgDeathNoticeList[i].szVictim ); + ConsolePrint( "\n" ); + } + else + { + // record the death notice in the console + if ( rgDeathNoticeList[i].iSuicide ) + { + ConsolePrint( rgDeathNoticeList[i].szVictim ); + + if ( !strcmp( killedwith, "d_world" ) ) + { + ConsolePrint( " died" ); + } + else + { + ConsolePrint( " killed self" ); + } + } + else if ( rgDeathNoticeList[i].iTeamKill ) + { + ConsolePrint( rgDeathNoticeList[i].szKiller ); + ConsolePrint( " killed his teammate " ); + ConsolePrint( rgDeathNoticeList[i].szVictim ); + } + else + { + ConsolePrint( rgDeathNoticeList[i].szKiller ); + ConsolePrint( " killed " ); + ConsolePrint( rgDeathNoticeList[i].szVictim ); + } + + if ( killedwith && *killedwith && (*killedwith > 13 ) && strcmp( killedwith, "d_world" ) && !rgDeathNoticeList[i].iTeamKill ) + { + ConsolePrint( " with " ); + ConsolePrint( killedwith+2 ); // skip over the "d_" part + } + + ConsolePrint( "\n" ); + } + + return 1; +} + + + + diff --git a/cl_dll/demo.cpp b/cl_dll/demo.cpp new file mode 100644 index 0000000..6968b38 --- /dev/null +++ b/cl_dll/demo.cpp @@ -0,0 +1,103 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "hud.h" +#include "cl_util.h" +#include "demo.h" +#include "demo_api.h" +#include + +#define DLLEXPORT __declspec( dllexport ) + +int g_demosniper = 0; +int g_demosniperdamage = 0; +float g_demosniperorg[3]; +float g_demosniperangles[3]; +float g_demozoom; + +// FIXME: There should be buffer helper functions to avoid all of the *(int *)& crap. + +extern "C" +{ + void DLLEXPORT Demo_ReadBuffer( int size, unsigned char *buffer ); +} + +/* +===================== +Demo_WriteBuffer + +Write some data to the demo stream +===================== +*/ +void Demo_WriteBuffer( int type, int size, unsigned char *buffer ) +{ + int pos = 0; + unsigned char buf[ 32 * 1024 ]; + *( int * )&buf[pos] = type; + pos+=sizeof( int ); + + memcpy( &buf[pos], buffer, size ); + + // Write full buffer out + gEngfuncs.pDemoAPI->WriteBuffer( size + sizeof( int ), buf ); +} + +/* +===================== +Demo_ReadBuffer + +Engine wants us to parse some data from the demo stream +===================== +*/ +void DLLEXPORT Demo_ReadBuffer( int size, unsigned char *buffer ) +{ + int type; + int i = 0; + + type = *( int * )buffer; + i += sizeof( int ); + switch ( type ) + { + case TYPE_SNIPERDOT: + g_demosniper = *(int * )&buffer[ i ]; + i += sizeof( int ); + + if ( g_demosniper ) + { + g_demosniperdamage = *( int * )&buffer[ i ]; + i += sizeof( int ); + + g_demosniperangles[ 0 ] = *(float *)&buffer[i]; + i += sizeof( float ); + g_demosniperangles[ 1 ] = *(float *)&buffer[i]; + i += sizeof( float ); + g_demosniperangles[ 2 ] = *(float *)&buffer[i]; + i += sizeof( float ); + g_demosniperorg[ 0 ] = *(float *)&buffer[i]; + i += sizeof( float ); + g_demosniperorg[ 1 ] = *(float *)&buffer[i]; + i += sizeof( float ); + g_demosniperorg[ 2 ] = *(float *)&buffer[i]; + i += sizeof( float ); + } + break; + case TYPE_ZOOM: + g_demozoom = *(float * )&buffer[ i ]; + i += sizeof( float ); + break; + default: + gEngfuncs.Con_DPrintf( "Unknown demo buffer type, skipping.\n" ); + break; + } +} \ No newline at end of file diff --git a/cl_dll/demo.h b/cl_dll/demo.h new file mode 100644 index 0000000..c5a5057 --- /dev/null +++ b/cl_dll/demo.h @@ -0,0 +1,27 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( DEMOH ) +#define DEMOH +#pragma once + +// Types of demo messages we can write/parse +enum +{ + TYPE_SNIPERDOT = 0, + TYPE_ZOOM +}; + +void Demo_WriteBuffer( int type, int size, unsigned char *buffer ); + +extern int g_demosniper; +extern int g_demosniperdamage; +extern float g_demosniperorg[3]; +extern float g_demosniperangles[3]; +extern float g_demozoom; + +#endif \ No newline at end of file diff --git a/cl_dll/enginecallback.h b/cl_dll/enginecallback.h new file mode 100644 index 0000000..03ffba3 --- /dev/null +++ b/cl_dll/enginecallback.h @@ -0,0 +1,173 @@ +/* +enginecallback.h - actual engine callbacks +Copyright (C) 2011 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef ENGINECALLBACK_H +#define ENGINECALLBACK_H + +extern cl_enginefunc_t gEngfuncs; +extern render_api_t gRenderfuncs; + +#define GET_CLIENT_TIME (*gEngfuncs.GetClientTime) +#define GET_CLIENT_OLDTIME (*gEngfuncs.GetClientOldTime) +#define CVAR_REGISTER (*gEngfuncs.pfnRegisterVariable) +#define CVAR_GET_FLOAT (*gEngfuncs.pfnGetCvarFloat) +#define CVAR_GET_STRING (*gEngfuncs.pfnGetCvarString) +#define CVAR_SET_FLOAT (*gEngfuncs.Cvar_SetValue) +//#define CVAR_SET_STRING (*gEngfuncs.pfnCVarSetString) // not implemented +#define CVAR_GET_POINTER (*gEngfuncs.pfnGetCvarPointer) +#define ADD_COMMAND (*gEngfuncs.pfnAddCommand) +#define CMD_ARGC (*gEngfuncs.Cmd_Argc) +#define CMD_ARGV (*gEngfuncs.Cmd_Argv) +#define Msg (*gEngfuncs.Con_Printf) + +#define GET_LOCAL_PLAYER (*gEngfuncs.GetLocalPlayer) +#define GET_VIEWMODEL (*gEngfuncs.GetViewModel) +#define GET_ENTITY (*gEngfuncs.GetEntityByIndex) + +#define POINT_CONTENTS( p ) (*gEngfuncs.PM_PointContents)( p, NULL ) +#define WATER_ENTITY (*gEngfuncs.PM_WaterEntity) +#define RANDOM_LONG (*gEngfuncs.pfnRandomLong) +#define RANDOM_FLOAT (*gEngfuncs.pfnRandomFloat) + +#define GetScreenInfo (*gEngfuncs.pfnGetScreenInfo) +#define ServerCmd (*gEngfuncs.pfnServerCmd) +#define ClientCmd (*gEngfuncs.pfnClientCmd) +#define SetCrosshair (*gEngfuncs.pfnSetCrosshair) +#define AngleVectors (*gEngfuncs.pfnAngleVectors) +#define GetPlayerInfo (*gEngfuncs.pfnGetPlayerInfo) + +#define SPR_Load (*gEngfuncs.pfnSPR_Load) +#define SPR_Set (*gEngfuncs.pfnSPR_Set) +#define SPR_Frames (*gEngfuncs.pfnSPR_Frames) +#define SPR_Draw (*gEngfuncs.pfnSPR_Draw) +#define SPR_DrawHoles (*gEngfuncs.pfnSPR_DrawHoles) +#define SPR_DrawAdditive (*gEngfuncs.pfnSPR_DrawAdditive) +#define SPR_EnableScissor (*gEngfuncs.pfnSPR_EnableScissor) +#define SPR_DisableScissor (*gEngfuncs.pfnSPR_DisableScissor) +#define FillRGBA (*gEngfuncs.pfnFillRGBA) +#define SPR_Height (*gEngfuncs.pfnSPR_Height) +#define SPR_Width (*gEngfuncs.pfnSPR_Width) +#define SPR_GetList (*gEngfuncs.pfnSPR_GetList) +#define SPR_LoadEx (*gRenderfuncs.SPR_LoadExt) + +#define ConsolePrint (*gEngfuncs.pfnConsolePrint) +#define CenterPrint (*gEngfuncs.pfnCenterPrint) +#define TextMessageGet (*gEngfuncs.pfnTextMessageGet) +#define TextMessageDrawChar (*gEngfuncs.pfnDrawCharacter) +#define DrawConsoleString (*gEngfuncs.pfnDrawConsoleString) +#define GetConsoleStringSize (*gEngfuncs.pfnDrawConsoleStringLen) +#define DrawSetTextColor (*gEngfuncs.pfnDrawSetTextColor) + +#define LOAD_FILE( x, y ) (*gEngfuncs.COM_LoadFile)( x, 5, y ) +#define FREE_FILE (*gEngfuncs.COM_FreeFile) +#define SAVE_FILE (*gRenderfuncs.pfnSaveFile) + +#define Sys_DoubleTime (*gRenderfuncs.pfnTime) + +// sound functions (we can't use macroses - this names is collide with standard windows methods) +inline void PlaySound( char *szSound, float vol ) { gEngfuncs.pfnPlaySoundByName( szSound, vol ); } +inline void PlaySound( int iSound, float vol ) { gEngfuncs.pfnPlaySoundByIndex( iSound, vol ); } + +// render api callbacks +#define RENDER_GET_PARM (*gRenderfuncs.RenderGetParm) +#define SET_CURRENT_ENTITY (*gRenderfuncs.R_SetCurrentEntity) +#define SET_CURRENT_MODEL (*gRenderfuncs.R_SetCurrentModel) +#define ENGINE_SET_PVS (*gRenderfuncs.R_FatPVS) +#define HOST_ERROR (*gRenderfuncs.Host_Error) +#define GET_LIGHTSTYLE (*gRenderfuncs.GetLightStyle) +#define GET_DYNAMIC_LIGHT (*gRenderfuncs.GetDynamicLight) +#define GET_ENTITY_LIGHT (*gRenderfuncs.GetEntityLight) +#define TEXTURE_TO_TEXGAMMA (*gRenderfuncs.LightToTexGamma) +#define GET_FRAMETIME (*gRenderfuncs.GetFrameTime) +#define DRAW_SINGLE_DECAL (*gRenderfuncs.DrawSingleDecal) +#define DECAL_SETUP_VERTS (*gRenderfuncs.R_DecalSetupVerts) +#define GET_DETAIL_SCALE (*gRenderfuncs.GetDetailScaleForTexture) +#define GET_EXTRA_PARAMS (*gRenderfuncs.GetExtraParmsForTexture) +#define CREATE_TEXTURE (*gRenderfuncs.GL_CreateTexture) +#define FIND_TEXTURE (*gRenderfuncs.GL_FindTexture) +#define FREE_TEXTURE (*gRenderfuncs.GL_FreeTexture) +#define CREATE_TEXTURE_ARRAY (*gRenderfuncs.GL_CreateTextureArray) +#define STORE_EFRAGS (*gRenderfuncs.R_StoreEfrags) +#define INIT_BEAMCHAINS (*gRenderfuncs.GetBeamChains) +#define DRAW_PARTICLES (*gRenderfuncs.GL_DrawParticles) +#define SET_ENGINE_WORLDVIEW_MATRIX (*gRenderfuncs.GL_SetWorldviewProjectionMatrix) +#define GET_FOG_PARAMS (*gRenderfuncs.GetExtraParmsForTexture) +#define GET_TEXTURE_NAME (*gRenderfuncs.GL_TextureName) +#define GET_TEXTURE_DATA (*gRenderfuncs.GL_TextureData) +#define COMPARE_FILE_TIME (*gRenderfuncs.COM_CompareFileTime) +#define REMOVE_BSP_DECALS (*gRenderfuncs.R_EntityRemoveDecals) +#define STUDIO_GET_TEXTURE (*gRenderfuncs.StudioGetTexture) +#define GET_OVERVIEW_PARMS (*gRenderfuncs.GetOverviewParms) +#define FS_SEARCH (*gRenderfuncs.pfnGetFilesList) +#define ENV_SHOT (*gRenderfuncs.EnvShot) + +#define LOAD_TEXTURE (*gRenderfuncs.GL_LoadTexture) +#define LOAD_TEXTURE_ARRAY (*gRenderfuncs.GL_LoadTextureArray) + +// AVIKit interface +#define OPEN_CINEMATIC (*gRenderfuncs.AVI_LoadVideo) +#define FREE_CINEMATIC (*gRenderfuncs.AVI_FreeVideo) +#define CIN_IS_ACTIVE (*gRenderfuncs.AVI_IsActive) +#define CIN_GET_VIDEO_INFO (*gRenderfuncs.AVI_GetVideoInfo) +#define CIN_GET_FRAME_NUMBER (*gRenderfuncs.AVI_GetVideoFrameNumber) +#define CIN_GET_FRAMEDATA (*gRenderfuncs.AVI_GetVideoFrame) +#define CIN_UPLOAD_FRAME (*gRenderfuncs.AVI_UploadRawFrame) +#define CIN_UPDATE_SOUND (*gRenderfuncs.AVI_StreamSound) + +// glcommands +#define GL_BindTexture (*gRenderfuncs.GL_Bind) +#define GL_SelectTexture (*gRenderfuncs.GL_SelectTexture) +#define GL_TexGen (*gRenderfuncs.GL_TexGen) +#define GL_LoadTextureMatrix (*gRenderfuncs.GL_LoadTextureMatrix) +#define GL_LoadIdentityTexMatrix (*gRenderfuncs.GL_TexMatrixIdentity) +#define GL_CleanUpTextureUnits (*gRenderfuncs.GL_CleanUpTextureUnits) +#define GL_TexCoordArrayMode (*gRenderfuncs.GL_TexCoordArrayMode) +#define GL_TextureTarget (*gRenderfuncs.GL_TextureTarget) +#define GL_UpdateTexSize (*gRenderfuncs.GL_UpdateTexSize) + +#define RANDOM_SEED (*gRenderfuncs.SetRandomSeed) +#define MUSIC_FADE_VOLUME (*gRenderfuncs.S_FadeMusicVolume) + +#define GL_GetProcAddress (*gRenderfuncs.GL_GetProcAddress) + +#define MODEL_HANDLE (*gRenderfuncs.pfnGetModel) + +// built-in memory manager +#define Mem_Alloc( x ) (*gRenderfuncs.pfnMemAlloc)( x, __FILE__, __LINE__ ) +#define Mem_Free( x ) (*gRenderfuncs.pfnMemFree)( x, __FILE__, __LINE__ ) +#define _Mem_Alloc (*gRenderfuncs.pfnMemAlloc) +#define _Mem_Free (*gRenderfuncs.pfnMemFree) + +#define ASSERT( exp ) if(!( exp )) HOST_ERROR( "assert failed at %s:%i\n", __FILE__, __LINE__ ) + +#define IMAGE_EXISTS( path ) ( FILE_EXISTS( va( "%s.tga", path )) || FILE_EXISTS( va( "%s.dds", path ))) + +extern void ALERT( ALERT_TYPE level, char *szFmt, ... ); + +inline bool FILE_EXISTS( const char *filename ) +{ + int iCompare; + + // verify file exists + // g-cont. idea! use COMPARE_FILE_TIME instead of COM_LoadFile + if( COMPARE_FILE_TIME( filename, filename, &iCompare )) + return true; + return false; +} + +#define FILE_CRC32 (*gRenderfuncs.pfnFileBufferCRC32) +#define GET_MAX_CLIENTS (*gEngfuncs.GetMaxClients) + +#endif//ENGINECALLBACK_H \ No newline at end of file diff --git a/cl_dll/entity.cpp b/cl_dll/entity.cpp new file mode 100644 index 0000000..b307cd2 --- /dev/null +++ b/cl_dll/entity.cpp @@ -0,0 +1,937 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// Client side entity management functions + +#include + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_types.h" +#include "r_efx.h" +#include "event_api.h" +#include "pm_defs.h" +#include "pmtrace.h" +#include "pm_shared.h" +#include "eventscripts.h" // buz +#include "gl_local.h" +#include "gl_studio.h" + +#define DLLEXPORT __declspec( dllexport ) + +void Game_AddObjects( void ); + +extern vec3_t v_origin; +extern vec3_t g_vSpread; +extern int g_iGunMode; + +int g_iAlive = 1; +int g_flashlight; // buz +int r_currentMessageNum = 0; +int GlowFilterEntities ( int type, struct cl_entity_s *ent, const char *modelname ); // buz + +extern "C" +{ + int DLLEXPORT HUD_AddEntity( int type, struct cl_entity_s *ent, const char *modelname ); + void DLLEXPORT HUD_CreateEntities( void ); + void DLLEXPORT HUD_StudioEvent( const struct mstudioevent_s *event, const struct cl_entity_s *entity ); + void DLLEXPORT HUD_TxferLocalOverrides( struct entity_state_s *state, const struct clientdata_s *client ); + void DLLEXPORT HUD_ProcessPlayerState( struct entity_state_s *dst, const struct entity_state_s *src ); + void DLLEXPORT HUD_TxferPredictionData ( struct entity_state_s *ps, const struct entity_state_s *pps, struct clientdata_s *pcd, const struct clientdata_s *ppcd, struct weapon_data_s *wd, const struct weapon_data_s *pwd ); + void DLLEXPORT HUD_TempEntUpdate( double frametime, double client_time, double cl_gravity, struct tempent_s **ppTempEntFree, struct tempent_s **ppTempEntActive, int ( *Callback_AddVisibleEntity )( struct cl_entity_s *pEntity ), void ( *Callback_TempEntPlaySound )( struct tempent_s *pTemp, float damp ) ); + struct cl_entity_s DLLEXPORT *HUD_GetUserEntity( int index ); +} + +/* +======================== +HUD_AddEntity + Return 0 to filter entity from visible list for rendering +======================== +*/ +int DLLEXPORT HUD_AddEntity( int type, struct cl_entity_s *ent, const char *modelname ) +{ + if( ent->curstate.rendermode == kRenderTransAlpha && ent->model && ent->model->type == mod_brush ) + { + // fix invisible grates on fallback renderer + ent->curstate.renderamt = 255; + } + + if( g_fXashEngine && g_fRenderInitialized ) + { + // use engine renderer + if( cv_renderer->value == 0 ) + return 1; + + if( type == ET_BEAM ) + return 1; // let the engine draw beams + + R_AddEntity( ent, type ); + + return 0; + } + + // each frame every entity passes this function, so the overview hooks it to filter the overview entities + // in spectator mode: + // each frame every entity passes this function, so the overview hooks + // it to filter the overview entities + + if ( g_iUser1 ) + { + gHUD.m_Spectator.AddOverviewEntity( type, ent, modelname ); + + if ( ( g_iUser1 == OBS_IN_EYE || gHUD.m_Spectator.m_pip->value == INSET_IN_EYE ) && + ent->index == g_iUser2 ) + return 0; // don't draw the player we are following in eye + + } + + return 1; +} + +/* +========================= +HUD_TxferLocalOverrides + +The server sends us our origin with extra precision as part of the clientdata structure, not during the normal +playerstate update in entity_state_t. In order for these overrides to eventually get to the appropriate playerstate +structure, we need to copy them into the state structure at this point. +========================= +*/ +void DLLEXPORT HUD_TxferLocalOverrides( struct entity_state_s *state, const struct clientdata_s *client ) +{ + state->origin = client->origin; + state->velocity = client->velocity; + + gHUD.m_iViewModelIndex = client->viewmodel; + + // Spectator + state->iuser1 = client->iuser1; + state->iuser2 = client->iuser2; + + // Duck prevention + state->iuser3 = client->iuser3; + + // Fire prevention + state->iuser4 = client->iuser4; + + // always have valid PVS message + r_currentMessageNum = state->messagenum; + + // IMPORTANT: this data doesn't present in entity_state_t + // but only in clientdata_t for local player (gun params, spectator mode etc) + g_iUser1 = client->iuser1; + g_iUser2 = client->iuser2; + g_iUser3 = client->iuser3; + + // buz + g_vSpread = client->vuser1; + g_iGunMode = client->iuser4; +} + +/* +========================= +HUD_ProcessPlayerState + +We have received entity_state_t for this player over the network. We need to copy appropriate fields to the +playerstate structure +========================= +*/ +void DLLEXPORT HUD_ProcessPlayerState( struct entity_state_s *dst, const struct entity_state_s *src ) +{ + // Copy in network data + dst->origin = src->origin; + dst->angles = src->angles; + + dst->velocity = src->velocity; + dst->basevelocity = src->basevelocity; + + dst->frame = src->frame; + dst->modelindex = src->modelindex; + dst->skin = src->skin; + dst->effects = src->effects; + dst->weaponmodel = src->weaponmodel; + dst->movetype = src->movetype; + dst->sequence = src->sequence; + dst->animtime = src->animtime; + + dst->solid = src->solid; + + dst->rendermode = src->rendermode; + dst->renderamt = src->renderamt; + dst->rendercolor.r = src->rendercolor.r; + dst->rendercolor.g = src->rendercolor.g; + dst->rendercolor.b = src->rendercolor.b; + dst->renderfx = src->renderfx; + + dst->framerate = src->framerate; + dst->body = src->body; + + dst->friction = src->friction; + dst->gravity = src->gravity; + dst->gaitsequence = src->gaitsequence; + dst->usehull = src->usehull; + dst->playerclass = src->playerclass; + dst->team = src->team; + dst->colormap = src->colormap; + + dst->fuser1 = src->fuser1; + dst->fuser2 = src->fuser2; + dst->fuser3 = src->fuser3; + dst->fuser4 = src->fuser4; + + dst->vuser1 = src->vuser1; + dst->vuser2 = src->vuser2; + dst->vuser3 = src->vuser3; + dst->vuser4 = src->vuser4; + + memcpy( &dst->controller[0], &src->controller[0], 4 * sizeof( byte )); + memcpy( &dst->blending[0], &src->blending[0], 2 * sizeof( byte )); + + // Save off some data so other areas of the Client DLL can get to it + cl_entity_t *player = gEngfuncs.GetLocalPlayer(); // Get the local player's index + if ( dst->number == player->index ) + { + g_iPlayerClass = dst->playerclass; + g_iTeamNumber = dst->team; + } + + // buz: get flashlight status + if (dst->effects & EF_DIMLIGHT) + g_flashlight = 1; + else g_flashlight = 0; +} + +/* +========================= +HUD_TxferPredictionData + +Because we can predict an arbitrary number of frames before the server responds with an update, we need to be able to copy client side prediction data in + from the state that the server ack'd receiving, which can be anywhere along the predicted frame path ( i.e., we could predict 20 frames into the future and the server ack's + up through 10 of those frames, so we need to copy persistent client-side only state from the 10th predicted frame to the slot the server + update is occupying. +========================= +*/ +void DLLEXPORT HUD_TxferPredictionData ( struct entity_state_s *ps, const struct entity_state_s *pps, struct clientdata_s *pcd, const struct clientdata_s *ppcd, struct weapon_data_s *wd, const struct weapon_data_s *pwd ) +{ + ps->oldbuttons = pps->oldbuttons; + ps->flFallVelocity = pps->flFallVelocity; + ps->iStepLeft = pps->iStepLeft; + ps->playerclass = pps->playerclass; + + pcd->viewmodel = ppcd->viewmodel; + pcd->m_iId = ppcd->m_iId; + pcd->ammo_shells = ppcd->ammo_shells; + pcd->ammo_nails = ppcd->ammo_nails; + pcd->ammo_cells = ppcd->ammo_cells; + pcd->ammo_rockets = ppcd->ammo_rockets; + pcd->m_flNextAttack = ppcd->m_flNextAttack; + pcd->fov = ppcd->fov; + pcd->weaponanim = ppcd->weaponanim; + pcd->tfstate = ppcd->tfstate; + pcd->maxspeed = ppcd->maxspeed; + + pcd->deadflag = ppcd->deadflag; + + // Spectating or not dead == get control over view angles. + g_iAlive = ( ppcd->iuser1 || ( pcd->deadflag == DEAD_NO ) ) ? 1 : 0; + + // Spectator + pcd->iuser1 = ppcd->iuser1; + pcd->iuser2 = ppcd->iuser2; + + // Duck prevention + pcd->iuser3 = ppcd->iuser3; + + if ( gEngfuncs.IsSpectateOnly() ) + { + // in specator mode we tell the engine who we want to spectate and how + // iuser3 is not used for duck prevention (since the spectator can't duck at all) + pcd->iuser1 = g_iUser1; // observer mode + pcd->iuser2 = g_iUser2; // first target + pcd->iuser3 = g_iUser3; // second target + + } + + // Fire prevention + pcd->iuser4 = ppcd->iuser4; + + pcd->fuser1 = ppcd->fuser1; + pcd->fuser2 = ppcd->fuser2; + pcd->fuser3 = ppcd->fuser3; + pcd->fuser4 = ppcd->fuser4; + + pcd->vuser1 = ppcd->vuser1; + pcd->vuser2 = ppcd->vuser2; + pcd->vuser3 = ppcd->vuser3; + pcd->vuser4 = ppcd->vuser4; + + memcpy( wd, pwd, 32 * sizeof( weapon_data_t ) ); +} + +/* +========================= +HUD_CreateEntities + +Gives us a chance to add additional entities to the render this frame +========================= +*/ +void DLLEXPORT HUD_CreateEntities( void ) +{ + // e.g., create a persistent cl_entity_t somewhere. + // Load an appropriate model into it ( gEngfuncs.CL_LoadModel ) + // Call gEngfuncs.CL_CreateVisibleEntity to add it to the visedicts list + + GetClientVoiceMgr()->CreateEntities(); + + // used to draw legs + HUD_AddEntity( ET_PLAYER, GET_LOCAL_PLAYER(), GET_LOCAL_PLAYER()->model->name ); +} + +void DlightFlash( const Vector &origin, int index ) +{ + dlight_t *dl = gEngfuncs.pEfxAPI->CL_AllocDlight( index ); + dl->origin = origin; + dl->radius = 128; + dl->color.r = 180; + dl->color.g = 160; + dl->color.b = 120; + dl->die = GET_CLIENT_TIME() + 0.06f; + + CDynLight *pl = CL_AllocDlight( index ); + + R_SetupLightParams( pl, origin, g_vecZero, 128.0f, 0.0f, LIGHT_OMNI, DLF_NOSHADOWS ); + pl->color = Vector( 0.7f, 0.6f, 0.5f ); + pl->die = GET_CLIENT_TIME() + 0.06f; +} + +/* +============== +CL_MuzzleFlash + +Do muzzleflash +============== +*/ +void HUD_MuzzleFlash( const cl_entity_t *e, const Vector &pos, const Vector &fwd, int type, float mul ) +{ + TEMPENTITY *pTemp; + int body, modelIndex, frameCount; + Vector flash_angles; + int flags = 0; + float scale; + + if( RP_NORMALPASS( )) + { + if( e == gEngfuncs.GetViewModel( )) + flags |= EF_NOREFLECT|EF_NODEPTHTEST; + else if( e->player && RP_LOCALCLIENT( e )) + flags |= EF_REFLECTONLY; + } + else + { + if( e->player && RP_LOCALCLIENT( e )) + flags |= EF_REFLECTONLY; + } + + body = bound( 0, type % 5, 4 ); + scale = (type / 5) * 0.2f; + if( scale == 0.0f ) scale = 0.5f; + + modelIndex = gEngfuncs.pEventAPI->EV_FindModelIndex ("models/m_flash1.mdl"); + if( !modelIndex ) return; + + Mod_GetFrames( modelIndex, frameCount ); + + if( body > ( frameCount - 1 )) + body = frameCount - 1; + + // must set position for right culling on render + if( !( pTemp = gEngfuncs.pEfxAPI->CL_TempEntAllocHigh((float *)&pos, Mod_Handle( modelIndex )))) + return; + + VectorAngles( -fwd, flash_angles ); + scale *= mul; + + pTemp->entity.curstate.rendermode = kRenderGlow; + pTemp->entity.curstate.renderamt = 255; + pTemp->entity.curstate.framerate = 10; + pTemp->entity.curstate.renderfx = 0; + pTemp->entity.angles = flash_angles; + pTemp->die = tr.time + 0.015f; // die at next frame + pTemp->entity.curstate.body = body; +// pTemp->flags |= FTENT_MDLANIMATE|FTENT_MDLANIMATELOOP; + pTemp->entity.angles[2] = RANDOM_LONG( 0, 359 ); + pTemp->entity.curstate.scale = scale; + pTemp->frameMax = frameCount - 1; + + gEngfuncs.CL_CreateVisibleEntity( ET_TEMPENTITY, &pTemp->entity ); + pTemp->entity.curstate.effects |= EF_FULLBRIGHT|flags; // CL_CreateVisibleEntity clears 'effect' field, so we need add it here +} + +/* +========================= +HUD_StudioEvent + +The entity's studio model description indicated an event was +fired during this frame, handle the event by it's tag ( e.g., muzzleflash, sound ) +========================= +*/ +void DLLEXPORT HUD_StudioEvent( const struct mstudioevent_s *event, const struct cl_entity_s *entity ) +{ + float rnd2 = gEngfuncs.pfnRandomFloat( -0.03, 0.03 ); + Vector pos, dir; + float mul = 2.0f; + int shell; + +// ALERT( at_console, "Play event: %i, options %s, framecount %i\n", event->event, event->options, tr.realframecount ); + + if( entity == GET_VIEWMODEL( )) + mul = 8.0f; + + switch( event->event ) + { + case 5001: + R_StudioAttachmentPosDir( entity, 0, &pos, &dir ); + HUD_MuzzleFlash( entity, pos, dir, atoi( event->options), mul ); + DlightFlash((float *)&entity->attachment[0], entity->index ); + EV_GunSmoke( entity->attachment[0] ); + break; + case 5007: + EV_GunSmoke( entity->attachment[0] ); + break; + case 5008: + EV_GunSmoke( entity->attachment[1] ); + break; + case 5009: // custom shell ejection + shell = gEngfuncs.pEventAPI->EV_FindModelIndex( event->options ); + R_StudioAttachmentPosDir( entity, 2, &pos, &dir ); + EV_EjectBrass( pos, dir, 0, shell, TE_BOUNCE_SHELL ); + break; + case 5010: // custom shell ejection, no velocity + shell = gEngfuncs.pEventAPI->EV_FindModelIndex( event->options ); + R_StudioAttachmentPosDir( entity, 2, &pos, &dir ); + EV_EjectBrass( pos, (float *)&g_vecZero, 0, shell, TE_BOUNCE_SHELL ); + break; + case 5011: + R_StudioAttachmentPosDir( entity, 1, &pos, &dir ); + HUD_MuzzleFlash( entity, pos, dir, atoi( event->options), mul ); + DlightFlash((float *)&entity->attachment[1], entity->index ); + EV_GunSmoke( entity->attachment[1] ); + break; + case 5021: + R_StudioAttachmentPosDir( entity, 2, &pos, &dir ); + HUD_MuzzleFlash( entity, pos, dir, atoi( event->options), mul ); + DlightFlash((float *)&entity->attachment[2], entity->index ); + EV_GunSmoke( entity->attachment[2] ); + break; + case 5031: + R_StudioAttachmentPosDir( entity, 3, &pos, &dir ); + HUD_MuzzleFlash( entity, pos, dir, atoi( event->options), mul ); + DlightFlash((float *)&entity->attachment[3], entity->index ); + EV_GunSmoke( entity->attachment[3] ); + break; + case 5002: + gEngfuncs.pEfxAPI->R_SparkEffect( (float *)&entity->attachment[0], atoi( event->options), -100, 100 ); + break; + // Client side sound + case 5004: + gEngfuncs.pfnPlaySoundByNameAtLocation( (char *)event->options, 1.0, (float *)&entity->attachment[0] ); + break; + case 5005: // buz: left foot step (attach 3) + { + int contents = gEngfuncs.PM_PointContents( (float *)&entity->attachment[3], NULL ); + if (contents == CONTENTS_WATER) // leg is in the water + { + int waterEntity = gEngfuncs.PM_WaterEntity( (float *)&entity->attachment[3] ); + if ( waterEntity > 0 ) // water should be func_water entity + { + cl_entity_t *pwater = gEngfuncs.GetEntityByIndex( waterEntity ); + if ( pwater && ( pwater->model != NULL ) ) + { + if ((pwater->curstate.maxs[2] - entity->attachment[3][2]) < 16) + { + vec3_t vecNull(0, 0, 0); + vec3_t vecSrc((float *)&entity->attachment[3]); + vecSrc.z += 25; + int iPuff = gEngfuncs.pEventAPI->EV_FindModelIndex("sprites/wsplash_x.spr"); + TEMPENTITY *wp = gEngfuncs.pEfxAPI->R_TempSprite(vecSrc, vecNull, 0.5, iPuff, kRenderTransAdd, kRenderFxNone, 1, 5, FTENT_SPRANIMATE); + wp->entity.curstate.framerate = 20; + //wp->entity.curstate.rendercolor.r = entity->cvFloorColor.r; + //wp->entity.curstate.rendercolor.g = entity->cvFloorColor.g; + //wp->entity.curstate.rendercolor.b = entity->cvFloorColor.b; + } + } + } + } + break; + } + case 5015: // buz: right foot step (attach 2) + { + int contents = gEngfuncs.PM_PointContents( (float *)&entity->attachment[2], NULL ); + if (contents == CONTENTS_WATER) // leg is in the water + { + int waterEntity = gEngfuncs.PM_WaterEntity( (float *)&entity->attachment[2] ); + if ( waterEntity > 0 ) // water should be func_water entity + { + cl_entity_t *pwater = gEngfuncs.GetEntityByIndex( waterEntity ); + if ( pwater && ( pwater->model != NULL ) ) + { + if ((pwater->curstate.maxs[2] - entity->attachment[2][2]) < 16) + { + vec3_t vecNull(0, 0, 0); + vec3_t vecSrc((float *)&entity->attachment[2]); + vecSrc.z += 25; + int iPuff = gEngfuncs.pEventAPI->EV_FindModelIndex("sprites/wsplash_x.spr"); + TEMPENTITY *wp = gEngfuncs.pEfxAPI->R_TempSprite(vecSrc, vecNull, 0.5, iPuff, kRenderTransAdd, kRenderFxNone, 1, 5, FTENT_SPRANIMATE); + wp->entity.curstate.framerate = 20; + //wp->entity.curstate.rendercolor.r = entity->cvFloorColor.r; + //wp->entity.curstate.rendercolor.g = entity->cvFloorColor.g; + //wp->entity.curstate.rendercolor.b = entity->cvFloorColor.b; + } + } + } + } + break; + } + case 5006: // buz: shell at 2nd attachment flying to 3rd + { + int shell = gEngfuncs.pEventAPI->EV_FindModelIndex ("models/shell.mdl"); + vec3_t VecDir = entity->attachment[2] - entity->attachment[1]; + VecDir = VecDir * 10; + EV_EjectBrass ( (float *)&entity->attachment[1], VecDir, 0, shell, TE_BOUNCE_SHELL ); + break; + } + case 5040: + // make aurora for origin + UTIL_CreateAurora((cl_entity_t *)entity, event->options, 0, 0.0f ); + break; + case 5041: + // make aurora for attachment #1 + UTIL_CreateAurora((cl_entity_t *)entity, event->options, 1, 0.0f ); + break; + case 5042: + // make aurora for attachment #2 + UTIL_CreateAurora((cl_entity_t *)entity, event->options, 2, 0.0f ); + break; + case 5043: + // make aurora for attachment #3 + UTIL_CreateAurora((cl_entity_t *)entity, event->options, 3, 0.0f ); + break; + case 5044: + // make aurora for attachment #4 + UTIL_CreateAurora((cl_entity_t *)entity, event->options, 4, 0.0f ); + break; + default: + break; + } +} + +/* +================= +CL_UpdateTEnts + +Simulation and cleanup of temporary entities +================= +*/ +void DLLEXPORT HUD_TempEntUpdate ( + double frametime, // Simulation time + double client_time, // Absolute time on client + double cl_gravity, // True gravity on client + TEMPENTITY **ppTempEntFree, // List of freed temporary ents + TEMPENTITY **ppTempEntActive, // List + int (*Callback_AddVisibleEntity)( cl_entity_t *pEntity ), + void (*Callback_TempEntPlaySound)( TEMPENTITY *pTemp, float damp ) ) +{ + static int gTempEntFrame = 0; + TEMPENTITY *pTemp, *pnext, *pprev; + float freq, gravity, gravitySlow, life, fastFreq; + + // Nothing to simulate + if( !*ppTempEntActive ) return; + + // in order to have tents collide with players, we have to run the player prediction code so + // that the client has the player list. We run this code once when we detect any COLLIDEALL + // tent, then set this BOOL to true so the code doesn't get run again if there's more than + // one COLLIDEALL ent for this update. (often are). + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true ); + + // Store off the old count + gEngfuncs.pEventAPI->EV_PushPMStates(); + + // Now add in all of the players. + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( -1 ); + + // !!!BUGBUG -- This needs to be time based + gTempEntFrame = (gTempEntFrame+1) & 31; + + pTemp = *ppTempEntActive; + + // !!! Don't simulate while paused.... This is sort of a hack, revisit. + if( frametime <= 0 ) + { + while( pTemp ) + { + if( !(pTemp->flags & FTENT_NOMODEL )) + { + Callback_AddVisibleEntity( &pTemp->entity ); + } + pTemp = pTemp->next; + } + goto finish; + } + + pprev = NULL; + freq = client_time * 0.01; + fastFreq = client_time * 5.5; + gravity = -frametime * cl_gravity; + gravitySlow = gravity * 0.5; + + while ( pTemp ) + { + int active = 1; + + life = pTemp->die - client_time; + pnext = pTemp->next; + + if( life < 0 ) + { + if ( pTemp->flags & FTENT_FADEOUT ) + { + if (pTemp->entity.curstate.rendermode == kRenderNormal) + pTemp->entity.curstate.rendermode = kRenderTransTexture; + pTemp->entity.curstate.renderamt = pTemp->entity.baseline.renderamt * ( 1 + life * pTemp->fadeSpeed ); + + if ( pTemp->entity.curstate.renderamt <= 0 ) + active = 0; + + } + else active = 0; + } + + if( !active ) // Kill it + { + pTemp->next = *ppTempEntFree; + *ppTempEntFree = pTemp; + + if( !pprev ) // deleting at head of list + *ppTempEntActive = pnext; + else + pprev->next = pnext; + } + else + { + pprev = pTemp; + + VectorCopy( pTemp->entity.origin, pTemp->entity.prevstate.origin ); + + if ( pTemp->flags & FTENT_SPARKSHOWER ) + { + // Adjust speed if it's time + // Scale is next think time + if ( client_time > pTemp->entity.baseline.scale ) + { + // Show Sparks + gEngfuncs.pEfxAPI->R_SparkEffect( pTemp->entity.origin, 8, -200, 200 ); + + // Reduce life + pTemp->entity.baseline.framerate -= 0.1; + + if ( pTemp->entity.baseline.framerate <= 0.0 ) + { + pTemp->die = client_time; + } + else + { + // So it will die no matter what + pTemp->die = client_time + 0.5; + + // Next think + pTemp->entity.baseline.scale = client_time + 0.1; + } + } + } + else if ( pTemp->flags & FTENT_PLYRATTACHMENT ) + { + cl_entity_t *pClient; + + pClient = gEngfuncs.GetEntityByIndex( pTemp->clientIndex ); + + VectorAdd( pClient->origin, pTemp->tentOffset, pTemp->entity.origin ); + } + else if ( pTemp->flags & FTENT_SINEWAVE ) + { + pTemp->x += pTemp->entity.baseline.origin[0] * frametime; + pTemp->y += pTemp->entity.baseline.origin[1] * frametime; + + pTemp->entity.origin[0] = pTemp->x + sin( pTemp->entity.baseline.origin[2] + client_time * pTemp->entity.prevstate.frame ) * (10*pTemp->entity.curstate.framerate); + pTemp->entity.origin[1] = pTemp->y + sin( pTemp->entity.baseline.origin[2] + fastFreq + 0.7 ) * (8*pTemp->entity.curstate.framerate); + pTemp->entity.origin[2] += pTemp->entity.baseline.origin[2] * frametime; + } + else if ( pTemp->flags & FTENT_SPIRAL ) + { + float s, c; + s = sin( pTemp->entity.baseline.origin[2] + fastFreq ); + c = cos( pTemp->entity.baseline.origin[2] + fastFreq ); + + pTemp->entity.origin[0] += pTemp->entity.baseline.origin[0] * frametime + 8 * sin( client_time * 20 + (int)pTemp ); + pTemp->entity.origin[1] += pTemp->entity.baseline.origin[1] * frametime + 4 * sin( client_time * 30 + (int)pTemp ); + pTemp->entity.origin[2] += pTemp->entity.baseline.origin[2] * frametime; + } + + else + { + for ( int i = 0; i < 3; i++ ) + pTemp->entity.origin[i] += pTemp->entity.baseline.origin[i] * frametime; + } + + if ( pTemp->flags & FTENT_SPRANIMATE ) + { + pTemp->entity.curstate.frame += frametime * pTemp->entity.curstate.framerate; + if ( pTemp->entity.curstate.frame >= pTemp->frameMax ) + { + pTemp->entity.curstate.frame = pTemp->entity.curstate.frame - (int)(pTemp->entity.curstate.frame); + + if ( !(pTemp->flags & FTENT_SPRANIMATELOOP) ) + { + // this animating sprite isn't set to loop, so destroy it. + pTemp->die = client_time; + pTemp = pnext; + continue; + } + } + } + else if ( pTemp->flags & FTENT_MDLANIMATE ) + { + pTemp->entity.curstate.body += frametime * pTemp->entity.curstate.framerate; + if ( pTemp->entity.curstate.body >= pTemp->frameMax ) + { + pTemp->entity.curstate.body = pTemp->frameMax; + + if( !( pTemp->flags & FTENT_MDLANIMATELOOP )) + { + // this animating sprite isn't set to loop, so destroy it. + pTemp->die = client_time; + pTemp = pnext; + continue; + } + } + } + else if ( pTemp->flags & FTENT_SPRCYCLE ) + { + pTemp->entity.curstate.frame += frametime * 10; + if ( pTemp->entity.curstate.frame >= pTemp->frameMax ) + { + pTemp->entity.curstate.frame = pTemp->entity.curstate.frame - (int)(pTemp->entity.curstate.frame); + } + } +// Experiment +#if 0 + if ( pTemp->flags & FTENT_SCALE ) + pTemp->entity.curstate.framerate += 20.0 * (frametime / pTemp->entity.curstate.framerate); +#endif + + if ( pTemp->flags & FTENT_ROTATE ) + { + pTemp->entity.angles[0] += pTemp->entity.baseline.angles[0] * frametime; + pTemp->entity.angles[1] += pTemp->entity.baseline.angles[1] * frametime; + pTemp->entity.angles[2] += pTemp->entity.baseline.angles[2] * frametime; + + VectorCopy( pTemp->entity.angles, pTemp->entity.latched.prevangles ); + } + + if ( pTemp->flags & (FTENT_COLLIDEALL | FTENT_COLLIDEWORLD) ) + { + vec3_t traceNormal; + float traceFraction = 1; + + if ( pTemp->flags & FTENT_COLLIDEALL ) + { + pmtrace_t pmtrace; + physent_t *pe; + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + + gEngfuncs.pEventAPI->EV_PlayerTrace( pTemp->entity.prevstate.origin, pTemp->entity.origin, PM_STUDIO_BOX, -1, &pmtrace ); + + + if ( pmtrace.fraction != 1 ) + { + pe = gEngfuncs.pEventAPI->EV_GetPhysent( pmtrace.ent ); + + if ( !pmtrace.ent || ( pe->info != pTemp->clientIndex ) ) + { + traceFraction = pmtrace.fraction; + VectorCopy( pmtrace.plane.normal, traceNormal ); + + if ( pTemp->hitcallback ) + { + (*pTemp->hitcallback)( pTemp, &pmtrace ); + } + } + } + } + else if ( pTemp->flags & FTENT_COLLIDEWORLD ) + { + pmtrace_t pmtrace; + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + + gEngfuncs.pEventAPI->EV_PlayerTrace( pTemp->entity.prevstate.origin, pTemp->entity.origin, PM_STUDIO_BOX | PM_WORLD_ONLY, -1, &pmtrace ); + + if ( pmtrace.fraction != 1 ) + { + traceFraction = pmtrace.fraction; + VectorCopy( pmtrace.plane.normal, traceNormal ); + + if ( pTemp->flags & FTENT_SPARKSHOWER ) + { + // Chop spark speeds a bit more + // + VectorScale( pTemp->entity.baseline.origin, 0.6, pTemp->entity.baseline.origin ); + + if ( Length( pTemp->entity.baseline.origin ) < 10 ) + { + pTemp->entity.baseline.framerate = 0.0; + } + } + + if ( pTemp->hitcallback ) + { + (*pTemp->hitcallback)( pTemp, &pmtrace ); + } + } + } + + if ( traceFraction != 1 ) // Decent collision now, and damping works + { + float proj, damp; + + // Place at contact point + VectorMA( pTemp->entity.prevstate.origin, traceFraction*frametime, pTemp->entity.baseline.origin, pTemp->entity.origin ); + // Damp velocity + damp = pTemp->bounceFactor; + if ( pTemp->flags & (FTENT_GRAVITY|FTENT_SLOWGRAVITY) ) + { + damp *= 0.5; + if ( traceNormal[2] > 0.9 ) // Hit floor? + { + if ( pTemp->entity.baseline.origin[2] <= 0 && pTemp->entity.baseline.origin[2] >= gravity*3 ) + { + damp = 0; // Stop + pTemp->flags &= ~(FTENT_ROTATE|FTENT_GRAVITY|FTENT_SLOWGRAVITY|FTENT_COLLIDEWORLD|FTENT_SMOKETRAIL); + pTemp->entity.angles[0] = 0; + pTemp->entity.angles[2] = 0; + } + } + } + + if (pTemp->hitSound) + { + Callback_TempEntPlaySound(pTemp, damp); + } + + if (pTemp->flags & FTENT_COLLIDEKILL) + { + // die on impact + pTemp->flags &= ~FTENT_FADEOUT; + pTemp->die = client_time; + } + else + { + // Reflect velocity + if ( damp != 0 ) + { + proj = DotProduct( pTemp->entity.baseline.origin, traceNormal ); + VectorMA( pTemp->entity.baseline.origin, -proj*2, traceNormal, pTemp->entity.baseline.origin ); + // Reflect rotation (fake) + + pTemp->entity.angles[1] = -pTemp->entity.angles[1]; + } + + if ( damp != 1 ) + { + + VectorScale( pTemp->entity.baseline.origin, damp, pTemp->entity.baseline.origin ); + VectorScale( pTemp->entity.angles, 0.9, pTemp->entity.angles ); + } + } + } + } + + + if ( (pTemp->flags & FTENT_FLICKER) && gTempEntFrame == pTemp->entity.curstate.effects ) + { + dlight_t *dl = gEngfuncs.pEfxAPI->CL_AllocDlight (0); + VectorCopy (pTemp->entity.origin, dl->origin); + dl->radius = 60; + dl->color.r = 255; + dl->color.g = 120; + dl->color.b = 0; + dl->die = client_time + 0.01; + } + + if ( pTemp->flags & FTENT_SMOKETRAIL ) + { + gEngfuncs.pEfxAPI->R_RocketTrail (pTemp->entity.prevstate.origin, pTemp->entity.origin, 1); + } + + if ( pTemp->flags & FTENT_GRAVITY ) + pTemp->entity.baseline.origin[2] += gravity; + else if ( pTemp->flags & FTENT_SLOWGRAVITY ) + pTemp->entity.baseline.origin[2] += gravitySlow; + + if ( pTemp->flags & FTENT_CLIENTCUSTOM ) + { + if ( pTemp->callback ) + { + ( *pTemp->callback )( pTemp, frametime, client_time ); + } + } + + // Cull to PVS (not frustum cull, just PVS) + if ( !(pTemp->flags & FTENT_NOMODEL ) ) + { + if( g_fRenderInitialized ) + { + Callback_AddVisibleEntity( &pTemp->entity ); + } + else + { + if ( !Callback_AddVisibleEntity( &pTemp->entity ) ) + { + if ( !(pTemp->flags & FTENT_PERSIST) ) + { + pTemp->die = client_time; // If we can't draw it this frame, just dump it. + pTemp->flags &= ~FTENT_FADEOUT; // Don't fade out, just die + } + } + } + } + } + pTemp = pnext; + } + +finish: + // Restore state info + gEngfuncs.pEventAPI->EV_PopPMStates(); +} + +/* +================= +HUD_GetUserEntity + +If you specify negative numbers for beam start and end point entities, then + the engine will call back into this function requesting a pointer to a cl_entity_t + object that describes the entity to attach the beam onto. + +Indices must start at 1, not zero. +================= +*/ +cl_entity_t DLLEXPORT *HUD_GetUserEntity( int index ) +{ + return NULL; +} \ No newline at end of file diff --git a/cl_dll/ev_common.cpp b/cl_dll/ev_common.cpp new file mode 100644 index 0000000..94bfad0 --- /dev/null +++ b/cl_dll/ev_common.cpp @@ -0,0 +1,205 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// shared event functions +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" + +#include "r_efx.h" + +#include "eventscripts.h" +#include "event_api.h" +#include "pm_shared.h" + +#define IS_FIRSTPERSON_SPEC ( g_iUser1 == OBS_IN_EYE || (g_iUser1 && (gHUD.m_Spectator.m_pip->value == INSET_IN_EYE)) ) +/* +================= +GetEntity + +Return's the requested cl_entity_t +================= +*/ +struct cl_entity_s *GetEntity( int idx ) +{ + return gEngfuncs.GetEntityByIndex( idx ); +} + +/* +================= +GetViewEntity + +Return's the current weapon/view model +================= +*/ +struct cl_entity_s *GetViewEntity( void ) +{ + return gEngfuncs.GetViewModel(); +} + +/* +================= +EV_CreateTracer + +Creates a tracer effect +================= +*/ +void EV_CreateTracer( float *start, float *end ) +{ + gEngfuncs.pEfxAPI->R_TracerEffect( start, end ); +} + +/* +================= +EV_IsPlayer + +Is the entity's index in the player range? +================= +*/ +qboolean EV_IsPlayer( int idx ) +{ + if ( idx >= 1 && idx <= gEngfuncs.GetMaxClients() ) + return true; + + return false; +} + +/* +================= +EV_IsLocal + +Is the entity == the local player +================= +*/ +qboolean EV_IsLocal( int idx ) +{ + // check if we are in some way in first person spec mode + if ( IS_FIRSTPERSON_SPEC ) + return (g_iUser2 == idx); + else + return gEngfuncs.pEventAPI->EV_IsLocal( idx - 1 ) ? true : false; +} + +/* +================= +EV_GetGunPosition + +Figure out the height of the gun +================= +*/ +void EV_GetGunPosition( event_args_t *args, float *pos, float *origin ) +{ + int idx; + vec3_t view_ofs; + + idx = args->entindex; + + VectorClear( view_ofs ); + view_ofs[2] = DEFAULT_VIEWHEIGHT; + + if ( EV_IsPlayer( idx ) ) + { + // in spec mode use entity viewheigh, not own + if ( EV_IsLocal( idx ) && !IS_FIRSTPERSON_SPEC ) + { + // Grab predicted result for local player + gEngfuncs.pEventAPI->EV_LocalPlayerViewheight( view_ofs ); + } + else if ( args->ducking == 1 ) + { + view_ofs[2] = VEC_DUCK_VIEW; + } + } + + VectorAdd( origin, view_ofs, pos ); +} + +/* +================= +EV_EjectBrass + +Bullet shell casings +================= +*/ +void EV_EjectBrass( float *origin, float *velocity, float rotation, int model, int soundtype ) +{ + Vector endpos = Vector( 0.0f, rotation, 0.0f ); + gEngfuncs.pEfxAPI->R_TempModel( origin, velocity, endpos, RANDOM_FLOAT( 1.5f, 3.0f ), model, soundtype ); +} + +/* +================= +EV_GetDefaultShellInfo + +Determine where to eject shells from +================= +*/ +void EV_GetDefaultShellInfo( event_args_t *args, float *origin, float *velocity, float *ShellVelocity, float *ShellOrigin, float *forward, float *right, float *up, float forwardScale, float upScale, float rightScale ) +{ + int i; + vec3_t view_ofs; + float fR, fU; + + int idx; + + idx = args->entindex; + + VectorClear( view_ofs ); + view_ofs[2] = DEFAULT_VIEWHEIGHT; + + if ( EV_IsPlayer( idx ) ) + { + if ( EV_IsLocal( idx ) ) + { + gEngfuncs.pEventAPI->EV_LocalPlayerViewheight( view_ofs ); + } + else if ( args->ducking == 1 ) + { + view_ofs[2] = VEC_DUCK_VIEW; + } + } + +// fR = gEngfuncs.pfnRandomFloat( 50, 70 ); +// fU = gEngfuncs.pfnRandomFloat( 100, 150 ); + fR = gEngfuncs.pfnRandomFloat( 80, 120 ); + fU = gEngfuncs.pfnRandomFloat( 10, 30 ); // buz + + for ( i = 0; i < 3; i++ ) + { + ShellVelocity[i] = velocity[i] + right[i] * fR + up[i] * fU + forward[i] * 25; + ShellOrigin[i] = origin[i] + view_ofs[i] + up[i] * upScale + forward[i] * forwardScale + right[i] * rightScale; + } +} + +/* +================= +EV_MuzzleFlash + +Flag weapon/view model for muzzle flash +================= +*/ +void EV_MuzzleFlash( void ) +{ + // Add muzzle flash to current weapon model + cl_entity_t *ent = GetViewEntity(); + if ( !ent ) + { + return; + } + + // Or in the muzzle flash + ent->curstate.effects |= EF_MUZZLEFLASH; +} \ No newline at end of file diff --git a/cl_dll/ev_files.cpp b/cl_dll/ev_files.cpp new file mode 100644 index 0000000..275c56f --- /dev/null +++ b/cl_dll/ev_files.cpp @@ -0,0 +1,44 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "../hud.h" +#include "../cl_util.h" +#include "event_api.h" + +extern "C" +{ +void EV_FireGeneric( struct event_args_s *args ); +void EV_TrainPitchAdjust( struct event_args_s *args ); +void EV_SmokePuff( struct event_args_s *args ); +} + +/* +====================== +Game_HookEvents + +Associate script file name with callback functions. Callback's must be extern "C" so + the engine doesn't get confused about name mangling stuff. Note that the format is + always the same. Of course, a clever mod team could actually embed parameters, behavior + into the actual .sc files and create a .sc file parser and hook their functionality through + that.. i.e., a scripting system. + +That was what we were going to do, but we ran out of time...oh well. +====================== +*/ +void Game_HookEvents( void ) +{ + gEngfuncs.pfnHookEvent( "evTrainSound", EV_TrainPitchAdjust ); + gEngfuncs.pfnHookEvent( "evSmokePuff", EV_SmokePuff ); + gEngfuncs.pfnHookEvent( "evFireGeneric", EV_FireGeneric ); +} \ No newline at end of file diff --git a/cl_dll/ev_hldm.cpp b/cl_dll/ev_hldm.cpp new file mode 100644 index 0000000..815a59f --- /dev/null +++ b/cl_dll/ev_hldm.cpp @@ -0,0 +1,418 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "entity_types.h" +#include "usercmd.h" +#include "pm_defs.h" + +#include "eventscripts.h" +#include "ev_hldm.h" + +#include "r_efx.h" +#include "event_api.h" +#include "event_args.h" +#include "in_defs.h" +#include "gl_rpart.h" +#include +#include "material.h" +#include "r_studioint.h" +#include "com_model.h" +#include "studio.h" + +extern engine_studio_api_t IEngineStudio; + +static int tracerCount[ 32 ]; + +void V_PunchAxis( int axis, float punch ); +void VectorAngles( const float *forward, float *angles ); + +extern cvar_t *cl_lw; + +extern "C" +{ +void EV_FireGeneric( struct event_args_s *args ); +void EV_TrainPitchAdjust( struct event_args_s *args ); +void EV_SmokePuff( struct event_args_s *args ); +} + +// play a strike sound based on the texture that was hit by the attack traceline. VecSrc/VecEnd are the +// original traceline endpoints used by the attacker, iBulletType is the type of bullet that hit the texture. +// returns volume of strike instrument (crowbar) to play +void EV_HLDM_PlayTextureSound( pmtrace_t *ptr, const Vector &vecSrc, const Vector &vecEnd ) +{ + // hit the world, try to play sound based on texture material type + char *rgsz[MAX_MAT_SOUNDS]; + float fattn = ATTN_NORM; + int cnt = 0; + + physent_t *pe = gEngfuncs.pEventAPI->EV_GetPhysent( ptr->ent ); + + matdef_t *pMat = NULL; + + if( pe ) + { + if( pe->solid == SOLID_BSP || pe->movetype == MOVETYPE_PUSHSTEP ) + pMat = COM_MatDefFromSurface( gEngfuncs.pEventAPI->EV_TraceSurface( ptr->ent, (float *)&vecSrc, (float *)&vecEnd ), ptr->endpos ); + else if( pe->solid == SOLID_CUSTOM && ptr->surf ) + pMat = ptr->surf->effects; + + if( !pMat ) return; + + // fire effects + for( cnt = 0; pMat->impact_parts[cnt] != NULL; cnt++ ) + g_pParticles.CreateEffect( pMat->impact_parts[cnt], ptr->endpos, ptr->plane.normal ); + + // count sounds + for( cnt = 0; pMat->impact_sounds[cnt] != NULL; cnt++ ) + rgsz[cnt] = (char *)pMat->impact_sounds[cnt]; + } + + if( !cnt ) return; + + gEngfuncs.pEventAPI->EV_PlaySound( 0, ptr->endpos, CHAN_STATIC, rgsz[RANDOM_LONG( 0, cnt - 1 )], 0.9, fattn, 0, 96 + RANDOM_LONG( 0, 0xf )); +} + +void EV_HLDM_GunshotDecalTrace( pmtrace_t *pTrace, const Vector &vecSrc, const Vector &vecEnd ) +{ + if( pTrace->inwater ) return; + + int iRand = RANDOM_LONG( 0, 0x7FFF ); + + if( iRand < ( 0x7fff / 2 )) // not every bullet makes a sound. + { + switch( iRand % 5) + { + case 0: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "weapons/ric1.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "weapons/ric2.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 2: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "weapons/ric3.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "weapons/ric4.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + case 4: gEngfuncs.pEventAPI->EV_PlaySound( -1, pTrace->endpos, 0, "weapons/ric5.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); break; + } + } + + physent_t *pe = gEngfuncs.pEventAPI->EV_GetPhysent( pTrace->ent ); + + matdef_t *pMat = NULL; + + if( pe ) + { + if( pe->solid == SOLID_BSP || pe->movetype == MOVETYPE_PUSHSTEP ) + { + pMat = COM_MatDefFromSurface( gEngfuncs.pEventAPI->EV_TraceSurface( pTrace->ent, (float *)&vecSrc, (float *)&vecEnd ), pTrace->endpos ); + if ( pMat ) CreateDecal( pTrace, pMat->impact_decal, RANDOM_FLOAT( 0.0f, 360.0f )); + } + else if( pe->solid == SOLID_CUSTOM && pTrace->surf ) + { + pMat = pTrace->surf->effects; + if ( pMat ) UTIL_StudioDecal( pMat->impact_decal, pTrace, vecSrc ); + } + } +} + +/* +================ +FireBullets + +Go to the trouble of combining multiple pellets into a single damage call. +================ +*/ +void EV_HLDM_FireBullets( int idx, float *forward, float *right, float *up, int cShots, float *vecSrc, float *vecDirShooting, float flDistance, int iBulletType, int iTracerFreq, int *tracerCount, float flSpreadX, float flSpreadY ) +{ + int i; + pmtrace_t tr; + int iShot; + + for ( iShot = 1; iShot <= cShots; iShot++ ) + { + Vector vecDir, vecEnd; + + float x, y, z; + + // We randomize for the Shotgun. + if ( iBulletType == BULLET_BUCKSHOT ) + { + do { + x = RANDOM_FLOAT( -0.5f, 0.5f ) + RANDOM_FLOAT( -0.5f, 0.5f ); + y = RANDOM_FLOAT( -0.5f, 0.5f ) + RANDOM_FLOAT( -0.5f, 0.5f ); + z = x * x + y * y; + } while( z > 1.0f ); + + for ( i = 0 ; i < 3; i++ ) + { + vecDir[i] = vecDirShooting[i] + x * flSpreadX * right[i] + y * flSpreadY * up[i]; + vecEnd[i] = vecSrc[i] + flDistance * vecDir[i]; + } + } + else + { + // but other guns already have their spread randomized in the synched spread. + for ( i = 0 ; i < 3; i++ ) + { + vecDir[i] = vecDirShooting[i] + flSpreadX * right[i] + flSpreadY * up[i]; + vecEnd[i] = vecSrc[i] + flDistance * vecDir[i]; + } + } + + gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true ); + + // Store off the old count + gEngfuncs.pEventAPI->EV_PushPMStates(); + + // Now add in all of the players. + gEngfuncs.pEventAPI->EV_SetSolidPlayers ( idx - 1 ); + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PlayerTrace( vecSrc, vecEnd, 0, -1, &tr ); + + // do damage, paint decals + if ( tr.fraction != 1.0 && !tr.inwater ) // buz: dont smoke and particle if shoot in sky + { + EV_HLDM_PlayTextureSound( &tr, vecSrc, vecEnd ); + EV_HLDM_GunshotDecalTrace( &tr, vecSrc, vecEnd ); + } + + gEngfuncs.pEventAPI->EV_PopPMStates(); + } +} + +//====================== +// WEAPON GENERIC START +//====================== +void EV_FireGeneric( struct event_args_s *args ) +{ + int idx; + Vector origin; + Vector angles; + Vector velocity; + + Vector ShellVelocity; + Vector ShellOrigin; + Vector vecSrc, vecAiming; + Vector up, right, forward; + float flSpread = 0.01; + + idx = args->entindex; + VectorCopy( args->origin, origin ); + VectorCopy( args->angles, angles ); + VectorCopy( args->velocity, velocity ); + + AngleVectors( angles, forward, right, up ); + + int shell = args->bparam1; // brass shell + EV_GetDefaultShellInfo( args, origin, velocity, ShellVelocity, ShellOrigin, forward, right, up, 20, -12, 4 ); + + if( EV_IsLocal( idx )) + { + // add muzzle flash to current weapon model + EV_MuzzleFlash(); + + UTIL_WeaponAnimation(( args->iparam1 & 0xFFF ), 1.0f ); // FIXME: how to send framerate? + + V_PunchAxis( 0, RANDOM_FLOAT( ((float)args->iparam2 / 255.0f ) * -3.0f, 0.0f )); + + cl_entity_t *ent = GetViewEntity(); + if( ent ) ShellOrigin = ent->attachment[1]; + } + + int numShots = ((args->iparam1 >> 12) & 0xF); + int soundType = TE_BOUNCE_SHELL; + int bulletType = BULLET_NORMAL; + float flDist = 8192.0f; + + // shotgun case + if( numShots > 1 ) + { + bulletType = BULLET_BUCKSHOT; + soundType = TE_BOUNCE_SHOTSHELL; + flDist = 2048.0f; + } + + EV_EjectBrass( ShellOrigin, ShellVelocity , RANDOM_FLOAT( -angles[YAW], angles[YAW] ), shell, soundType ); + + if( args->bparam2 ) + { + const char *fireSound = gEngfuncs.pEventAPI->EV_SoundForIndex( args->bparam2 ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, fireSound, 1, ATTN_NORM, 0, 94 + RANDOM_LONG( 0, 0xf )); + } + + EV_GetGunPosition( args, vecSrc, origin ); + + VectorCopy( forward, vecAiming ); + + EV_HLDM_FireBullets( idx, forward, right, up, numShots, vecSrc, vecAiming, flDist, bulletType, 2, &tracerCount[idx-1], args->fparam1, args->fparam2 ); +} +//====================== +// WEAPON GENERIC END +//====================== +#define SND_CHANGE_PITCH (1<<7) // duplicated in protocol.h change sound pitch + +void EV_TrainPitchAdjust( event_args_t *args ) +{ + int idx; + Vector origin; + + unsigned short us_params; + int noise; + float m_flVolume; + int pitch; + int stop; + + char sz[ 256 ]; + + idx = args->entindex; + + VectorCopy( args->origin, origin ); + + us_params = (unsigned short)args->iparam1; + stop = args->bparam1; + + m_flVolume = (float)(us_params & 0x003f)/40.0; + noise = (int)(((us_params) >> 12 ) & 0x0007); + pitch = (int)( 10.0 * (float)( ( us_params >> 6 ) & 0x003f ) ); + + switch ( noise ) + { + case 1: strcpy( sz, "plats/ttrain1.wav"); break; + case 2: strcpy( sz, "plats/ttrain2.wav"); break; + case 3: strcpy( sz, "plats/ttrain3.wav"); break; + case 4: strcpy( sz, "plats/ttrain4.wav"); break; + case 5: strcpy( sz, "plats/ttrain6.wav"); break; + case 6: strcpy( sz, "plats/ttrain7.wav"); break; + default: + // no sound + strcpy( sz, "" ); + return; + } + + if ( stop ) + { + gEngfuncs.pEventAPI->EV_StopSound( idx, CHAN_STATIC, sz ); + } + else + { + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_STATIC, sz, m_flVolume, ATTN_NORM, SND_CHANGE_PITCH, pitch ); + } +} + +int EV_TFC_IsAllyTeam( int iTeam1, int iTeam2 ) +{ + return 0; +} + +// buz: smokepuff event - for invoke puffs from server, when monsters firing, etc.. +void EV_SmokePuff( struct event_args_s *args ) +{ + VectorNormalize(args->angles); // there is surface normal actually + g_pParticles.BulletParticles( args->origin, args->angles ); +} + +void EV_GunSmoke( const Vector &pos ) +{ + g_pParticles.GunSmoke( pos, 2 ); +} + +void EV_ExplodeSmoke( const Vector &pos ) +{ + g_pParticles.SmokeParticles( pos, 10 ); +} + +void EV_HLDM_WaterSplash( float x, float y, float z, float ScaleSplash1, float ScaleSplash2 ) +{ + int iWaterSplash = gEngfuncs.pEventAPI->EV_FindModelIndex ("sprites/splash1.spr"); + TEMPENTITY *pTemp = gEngfuncs.pEfxAPI->R_TempSprite( Vector( x, y, z + 15 ), + Vector( 0, 0, 0 ), + /*ScaleSplash1*/0.2, iWaterSplash, kRenderTransAdd, kRenderFxNone, 1.0, 0.5, FTENT_SPRANIMATE | FTENT_FADEOUT | FTENT_COLLIDEKILL ); + + if(pTemp) + { + pTemp->fadeSpeed = 90.0; + pTemp->entity.curstate.framerate = 70.0; + pTemp->entity.curstate.renderamt = 155; + pTemp->entity.curstate.rendercolor.r = 255; + pTemp->entity.curstate.rendercolor.g = 255; + pTemp->entity.curstate.rendercolor.b = 255; + } + + iWaterSplash = gEngfuncs.pEventAPI->EV_FindModelIndex ("sprites/splash2.spr"); + pTemp = gEngfuncs.pEfxAPI->R_TempSprite( Vector( x, y, z ), + Vector( 0, 0, 0 ), + /*ScaleSplash2*/0.08, iWaterSplash, kRenderTransAdd, kRenderFxNone, 1.0, 0.5, FTENT_SPRANIMATE | FTENT_FADEOUT | FTENT_COLLIDEKILL ); + + if(pTemp) + { + pTemp->fadeSpeed = 60.0; + pTemp->entity.curstate.framerate = 60.0; + pTemp->entity.curstate.renderamt = 150; + pTemp->entity.curstate.rendercolor.r = 255; + pTemp->entity.curstate.rendercolor.g = 255; + pTemp->entity.curstate.rendercolor.b = 255; + pTemp->entity.angles = Vector( 90, 0, 0 ); + } +} + +void EV_HLDM_NewExplode( float x, float y, float z, float ScaleExplode1 ) +{ + float rnd = gEngfuncs.pfnRandomFloat( -0.03, 0.03 ); + int iNewExplode = gEngfuncs.pEventAPI->EV_FindModelIndex ("sprites/dexplo.spr"); + TEMPENTITY *pTemp = gEngfuncs.pEfxAPI->R_TempSprite( Vector( x, y, z + 15 ), Vector( 0, 0, 0 ), + ScaleExplode1, iNewExplode, kRenderTransAdd, kRenderFxNone, 1.0, 0.5, FTENT_SPRANIMATE | FTENT_FADEOUT ); + + if( pTemp ) + { + pTemp->fadeSpeed = 90.0; + pTemp->entity.curstate.framerate = 37.0; + pTemp->entity.curstate.renderamt = 155; + pTemp->entity.curstate.rendercolor.r = 255; + pTemp->entity.curstate.rendercolor.g = 255; + pTemp->entity.curstate.rendercolor.b = 255; + } + + iNewExplode = gEngfuncs.pEventAPI->EV_FindModelIndex ("sprites/fexplo.spr"); + pTemp = gEngfuncs.pEfxAPI->R_TempSprite( Vector( x, y, z + 15), Vector( 0, 0, 0 ), + ScaleExplode1, iNewExplode, kRenderTransAdd, kRenderFxNone, 1.0, 0.5, FTENT_SPRANIMATE | FTENT_FADEOUT ); + + if( pTemp ) + { + pTemp->fadeSpeed = 100.0; + pTemp->entity.curstate.framerate = 35.0; + pTemp->entity.curstate.renderamt = 150; + pTemp->entity.curstate.rendercolor.r = 255; + pTemp->entity.curstate.rendercolor.g = 255; + pTemp->entity.curstate.rendercolor.b = 255; + pTemp->entity.angles = Vector( 90, 0, 0 ); + } + + iNewExplode = gEngfuncs.pEventAPI->EV_FindModelIndex ("sprites/smokeball.spr"); + pTemp = gEngfuncs.pEfxAPI->R_TempSprite( Vector( x, y, z + 16), Vector( 0, 0, 0 ), + ScaleExplode1, iNewExplode, kRenderTransAdd, kRenderFxNone, 1.0, 0.5, FTENT_SPRANIMATE | FTENT_FADEOUT ); + + if( pTemp ) + { + pTemp->fadeSpeed = 50.0; + pTemp->entity.curstate.framerate = 22.0; + pTemp->entity.curstate.renderamt = 120; + pTemp->entity.curstate.rendercolor.r = 255; + pTemp->entity.curstate.rendercolor.g = 255; + pTemp->entity.curstate.rendercolor.b = 255; + pTemp->entity.angles = Vector( 90, 0, 0 ); + } + + EV_ExplodeSmoke( Vector( x, y, z + 4.0f )); +} \ No newline at end of file diff --git a/cl_dll/ev_hldm.h b/cl_dll/ev_hldm.h new file mode 100644 index 0000000..2946010 --- /dev/null +++ b/cl_dll/ev_hldm.h @@ -0,0 +1,17 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined ( EV_HLDMH ) +#define EV_HLDMH + +#include + +void EV_HLDM_GunshotDecalTrace( pmtrace_t *pTrace, const Vector &vecSrc, const Vector &vecEnd ); +int EV_HLDM_CheckTracer( int idx, float *vecSrc, float *end, float *forward, float *right, int iBulletType, int iTracerFreq, int *tracerCount ); +void EV_HLDM_FireBullets( int idx, float *forward, float *right, float *up, int cShots, float *vecSrc, float *vecDirShooting, float flDistance, int iBulletType, int iTracerFreq, int *tracerCount, float flSpreadX, float flSpreadY ); + +#endif // EV_HLDMH \ No newline at end of file diff --git a/cl_dll/events.cpp b/cl_dll/events.cpp new file mode 100644 index 0000000..65c842b --- /dev/null +++ b/cl_dll/events.cpp @@ -0,0 +1,23 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "hud.h" +#include "cl_util.h" + +void Game_HookEvents( void ); + +/* +=================== +EV_HookEvents + +See if game specific code wants to hook any events. +=================== +*/ +void EV_HookEvents( void ) +{ + Game_HookEvents(); +} \ No newline at end of file diff --git a/cl_dll/eventscripts.h b/cl_dll/eventscripts.h new file mode 100644 index 0000000..c9323a4 --- /dev/null +++ b/cl_dll/eventscripts.h @@ -0,0 +1,74 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// eventscripts.h +#if !defined ( EVENTSCRIPTSH ) +#define EVENTSCRIPTSH + +// defaults for clientinfo messages +#define DEFAULT_VIEWHEIGHT 28 +#define VEC_DUCK_VIEW 12 + +#define FTENT_FADEOUT 0x00000080 + +#define DMG_GENERIC 0 // generic damage was done +#define DMG_CRUSH (1 << 0) // crushed by falling or moving object +#define DMG_BULLET (1 << 1) // shot +#define DMG_SLASH (1 << 2) // cut, clawed, stabbed +#define DMG_BURN (1 << 3) // heat burned +#define DMG_FREEZE (1 << 4) // frozen +#define DMG_FALL (1 << 5) // fell too far +#define DMG_BLAST (1 << 6) // explosive blast damage +#define DMG_CLUB (1 << 7) // crowbar, punch, headbutt +#define DMG_SHOCK (1 << 8) // electric shock +#define DMG_SONIC (1 << 9) // sound pulse shockwave +#define DMG_ENERGYBEAM (1 << 10) // laser or other high energy beam +#define DMG_NEVERGIB (1 << 12) // with this bit OR'd in, no damage type will be able to gib victims upon death +#define DMG_ALWAYSGIB (1 << 13) // with this bit OR'd in, any damage type can be made to gib victims upon death. + +// time-based damage +//mask off TF-specific stuff too +#define DMG_TIMEBASED (~(0xff003fff)) // mask for time-based damage + +#define DMG_DROWN (1 << 14) // Drowning +#define DMG_FIRSTTIMEBASED DMG_DROWN + +#define DMG_PARALYZE (1 << 15) // slows affected creature down +#define DMG_NERVEGAS (1 << 16) // nerve toxins, very bad +#define DMG_POISON (1 << 17) // blood poisioning +#define DMG_RADIATION (1 << 18) // radiation exposure +#define DMG_DROWNRECOVER (1 << 19) // drowning recovery +#define DMG_ACID (1 << 20) // toxic chemicals or acid burns +#define DMG_SLOWBURN (1 << 21) // in an oven +#define DMG_SLOWFREEZE (1 << 22) // in a subzero freezer +#define DMG_MORTAR (1 << 23) // Hit by air raid (done to distinguish grenade from mortar) + +//TF ADDITIONS +#define DMG_IGNITE (1 << 24) // Players hit by this begin to burn +#define DMG_RADIUS_MAX (1 << 25) // Radius damage with this flag doesn't decrease over distance +#define DMG_RADIUS_QUAKE (1 << 26) // Radius damage is done like Quake. 1/2 damage at 1/2 radius. +#define DMG_IGNOREARMOR (1 << 27) // Damage ignores target's armor +#define DMG_AIMED (1 << 28) // Does Hit location damage +#define DMG_WALLPIERCING (1 << 29) // Blast Damages ents through walls + +#define DMG_CALTROP (1<<30) +#define DMG_HALLUC (1<<31) + +// Some of these are HL/TFC specific? +void EV_EjectBrass( float *origin, float *velocity, float rotation, int model, int soundtype ); +void EV_GetGunPosition( struct event_args_s *args, float *pos, float *origin ); +void EV_GetDefaultShellInfo( struct event_args_s *args, float *origin, float *velocity, float *ShellVelocity, float *ShellOrigin, float *forward, float *right, float *up, float forwardScale, float upScale, float rightScale ); +qboolean EV_IsLocal( int idx ); +qboolean EV_IsPlayer( int idx ); +void EV_CreateTracer( float *start, float *end ); + +struct cl_entity_s *GetEntity( int idx ); +struct cl_entity_s *GetViewEntity( void ); +void EV_GunSmoke( const Vector &pos ); +void EV_MuzzleFlash( void ); + +#endif // EVENTSCRIPTSH diff --git a/cl_dll/flashlight.cpp b/cl_dll/flashlight.cpp new file mode 100644 index 0000000..4d22058 --- /dev/null +++ b/cl_dll/flashlight.cpp @@ -0,0 +1,151 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// flashlight.cpp +// +// implementation of CHudFlashlight class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + + + +DECLARE_MESSAGE(m_Flash, FlashBat) +DECLARE_MESSAGE(m_Flash, Flashlight) + +#define BAT_NAME "sprites/%d_Flashlight.spr" + +int CHudFlashlight::Init(void) +{ + m_fFade = 0; + m_fOn = 0; + + HOOK_MESSAGE(Flashlight); + HOOK_MESSAGE(FlashBat); + + m_iFlags |= HUD_ACTIVE; + + gHUD.AddHudElem(this); + + return 1; +}; + +void CHudFlashlight::Reset(void) +{ + m_fFade = 0; + m_fOn = 0; +} + +int CHudFlashlight::VidInit(void) +{ + int HUD_flash_empty = gHUD.GetSpriteIndex( "flash_empty" ); + int HUD_flash_full = gHUD.GetSpriteIndex( "flash_full" ); + int HUD_flash_beam = gHUD.GetSpriteIndex( "flash_beam" ); + + m_hSprite1 = gHUD.GetSprite(HUD_flash_empty); + m_hSprite2 = gHUD.GetSprite(HUD_flash_full); + m_hBeam = gHUD.GetSprite(HUD_flash_beam); + m_prc1 = &gHUD.GetSpriteRect(HUD_flash_empty); + m_prc2 = &gHUD.GetSpriteRect(HUD_flash_full); + m_prcBeam = &gHUD.GetSpriteRect(HUD_flash_beam); + m_iWidth = m_prc2->right - m_prc2->left; + + return 1; +}; + +int CHudFlashlight:: MsgFunc_FlashBat(const char *pszName, int iSize, void *pbuf ) +{ + + + BEGIN_READ( pbuf, iSize ); + int x = READ_BYTE(); + m_iBat = x; + m_flBat = ((float)x)/100.0; + + return 1; +} + +int CHudFlashlight:: MsgFunc_Flashlight(const char *pszName, int iSize, void *pbuf ) +{ + + BEGIN_READ( pbuf, iSize ); + m_fOn = READ_BYTE(); + int x = READ_BYTE(); + m_iBat = x; + m_flBat = ((float)x)/100.0; + + return 1; +} + +int CHudFlashlight::Draw(float flTime) +{ + return 1; // buz: no flashlight + + if ( gHUD.m_iHideHUDDisplay & ( HIDEHUD_FLASHLIGHT | HIDEHUD_ALL ) ) + return 1; + + int r, g, b, x, y, a; + wrect_t rc; + + if (!FBitSet( gHUD.m_iHideHUDDisplay, ITEM_SUIT )) + return 1; + + if (m_fOn) + a = 225; + else + a = MIN_ALPHA; + + if (m_flBat < 0.20) + UnpackRGB(r,g,b, RGB_REDISH); + else + UnpackRGB(r,g,b, gHUD.m_iHUDColor); + + ScaleColors(r, g, b, a); + + y = (m_prc1->bottom - m_prc2->top)/2; + x = ScreenWidth - m_iWidth - m_iWidth/2 ; + + // Draw the flashlight casing + SPR_Set(m_hSprite1, r, g, b ); + SPR_DrawAdditive( 0, x, y, m_prc1); + + if ( m_fOn ) + { // draw the flashlight beam + x = ScreenWidth - m_iWidth/2; + + SPR_Set( m_hBeam, r, g, b ); + SPR_DrawAdditive( 0, x, y, m_prcBeam ); + } + + // draw the flashlight energy level + x = ScreenWidth - m_iWidth - m_iWidth/2 ; + int iOffset = m_iWidth * (1.0 - m_flBat); + if (iOffset < m_iWidth) + { + rc = *m_prc2; + rc.left += iOffset; + + SPR_Set(m_hSprite2, r, g, b ); + SPR_DrawAdditive( 0, x + iOffset, y, &rc); + } + + + return 1; +} diff --git a/cl_dll/geiger.cpp b/cl_dll/geiger.cpp new file mode 100644 index 0000000..6f5f984 --- /dev/null +++ b/cl_dll/geiger.cpp @@ -0,0 +1,184 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Geiger.cpp +// +// implementation of CHudAmmo class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include + +#include "parsemsg.h" + +DECLARE_MESSAGE(m_Geiger, Geiger ) + +int CHudGeiger::Init(void) +{ + HOOK_MESSAGE( Geiger ); + + m_iGeigerRange = 0; + m_iFlags = 0; + + gHUD.AddHudElem(this); + + srand( (unsigned)time( NULL ) ); + + return 1; +}; + +int CHudGeiger::VidInit(void) +{ + return 1; +}; + +int CHudGeiger::MsgFunc_Geiger(const char *pszName, int iSize, void *pbuf) +{ + + BEGIN_READ( pbuf, iSize ); + + // update geiger data + m_iGeigerRange = READ_BYTE(); + m_iGeigerRange = m_iGeigerRange << 2; + + m_iFlags |= HUD_ACTIVE; + + return 1; +} + +int CHudGeiger::Draw (float flTime) +{ + int pct; + float flvol; + int rg[3]; + int i; + + if (m_iGeigerRange < 1000 && m_iGeigerRange > 0) + { + // peicewise linear is better than continuous formula for this + if (m_iGeigerRange > 800) + { + pct = 0; //Con_Printf ( "range > 800\n"); + } + else if (m_iGeigerRange > 600) + { + pct = 2; + flvol = 0.4; //Con_Printf ( "range > 600\n"); + rg[0] = 1; + rg[1] = 1; + i = 2; + } + else if (m_iGeigerRange > 500) + { + pct = 4; + flvol = 0.5; //Con_Printf ( "range > 500\n"); + rg[0] = 1; + rg[1] = 2; + i = 2; + } + else if (m_iGeigerRange > 400) + { + pct = 8; + flvol = 0.6; //Con_Printf ( "range > 400\n"); + rg[0] = 1; + rg[1] = 2; + rg[2] = 3; + i = 3; + } + else if (m_iGeigerRange > 300) + { + pct = 8; + flvol = 0.7; //Con_Printf ( "range > 300\n"); + rg[0] = 2; + rg[1] = 3; + rg[2] = 4; + i = 3; + } + else if (m_iGeigerRange > 200) + { + pct = 28; + flvol = 0.78; //Con_Printf ( "range > 200\n"); + rg[0] = 2; + rg[1] = 3; + rg[2] = 4; + i = 3; + } + else if (m_iGeigerRange > 150) + { + pct = 40; + flvol = 0.80; //Con_Printf ( "range > 150\n"); + rg[0] = 3; + rg[1] = 4; + rg[2] = 5; + i = 3; + } + else if (m_iGeigerRange > 100) + { + pct = 60; + flvol = 0.85; //Con_Printf ( "range > 100\n"); + rg[0] = 3; + rg[1] = 4; + rg[2] = 5; + i = 3; + } + else if (m_iGeigerRange > 75) + { + pct = 80; + flvol = 0.9; //Con_Printf ( "range > 75\n"); + //gflGeigerDelay = cl.time + GEIGERDELAY * 0.75; + rg[0] = 4; + rg[1] = 5; + rg[2] = 6; + i = 3; + } + else if (m_iGeigerRange > 50) + { + pct = 90; + flvol = 0.95; //Con_Printf ( "range > 50\n"); + rg[0] = 5; + rg[1] = 6; + i = 2; + } + else + { + pct = 95; + flvol = 1.0; //Con_Printf ( "range < 50\n"); + rg[0] = 5; + rg[1] = 6; + i = 2; + } + + flvol = (flvol * ((rand() & 127)) / 255) + 0.25; // UTIL_RandomFloat(0.25, 0.5); + + if ((rand() & 127) < pct || (rand() & 127) < pct) + { + //S_StartDynamicSound (-1, 0, rgsfx[rand() % i], r_origin, flvol, 1.0, 0, 100); + char sz[256]; + + int j = rand() & 1; + if (i > 2) + j += rand() & 1; + + sprintf(sz, "player/geiger%d.wav", j + 1); + PlaySound(sz, flvol); + + } + } + + return 1; +} diff --git a/cl_dll/getfont.h b/cl_dll/getfont.h new file mode 100644 index 0000000..31d9c72 --- /dev/null +++ b/cl_dll/getfont.h @@ -0,0 +1,11 @@ +// ============================ +// getfont - function, returning font pointer from text message +// written by BUzer +// ============================ + +#ifndef _GETFONT_H +#define _GETFONT_H + +Font* FontFromMessage(const char* &ptext); + +#endif // _GETFONT_H \ No newline at end of file diff --git a/cl_dll/health.cpp b/cl_dll/health.cpp new file mode 100644 index 0000000..e3e3ca9 --- /dev/null +++ b/cl_dll/health.cpp @@ -0,0 +1,508 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Health.cpp +// +// implementation of CHudHealth class +// + +#include "STDIO.H" +#include "STDLIB.H" +#include "MATH.H" + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" +#include + +#include "vgui_TeamFortressViewport.h" // buz +#include "vgui_hud.h" // buz + + +DECLARE_MESSAGE(m_Health, Health ) +DECLARE_MESSAGE(m_Health, Damage ) + +#define PAIN_NAME "sprites/%d_pain.spr" +#define DAMAGE_NAME "sprites/%d_dmg.spr" + +int giDmgHeight, giDmgWidth; + +int giDmgFlags[NUM_DMG_TYPES] = +{ + DMG_POISON, + DMG_ACID, + DMG_FREEZE|DMG_SLOWFREEZE, + DMG_DROWN, + DMG_BURN|DMG_SLOWBURN, + DMG_NERVEGAS, + DMG_RADIATION, + DMG_SHOCK, + DMG_CALTROP, + DMG_TRANQ, + DMG_CONCUSS, + DMG_HALLUC +}; + +int CHudHealth::Init(void) +{ + HOOK_MESSAGE(Health); + HOOK_MESSAGE(Damage); + m_iHealth = 100; + m_fFade = 0; + m_iFlags = 0; + m_bitsDamage = 0; + m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 0; + giDmgHeight = 0; + giDmgWidth = 0; + + memset(m_dmg, 0, sizeof(DAMAGE_IMAGE) * NUM_DMG_TYPES); + + + gHUD.AddHudElem(this); + return 1; +} + +void CHudHealth::Reset( void ) +{ + // make sure the pain compass is cleared when the player respawns + m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 0; + + + // force all the flashing damage icons to expire + m_bitsDamage = 0; + for ( int i = 0; i < NUM_DMG_TYPES; i++ ) + { + m_dmg[i].fExpire = 0; + } +} + +int CHudHealth::VidInit(void) +{ + m_hSprite = 0; +// m_hDecoration = gHUD.GetSpriteIndex( "health_decoration" ); // buz + + m_HUD_dmg_bio = gHUD.GetSpriteIndex( "dmg_bio" ) + 1; + m_HUD_cross = gHUD.GetSpriteIndex( "cross" ); + + giDmgHeight = gHUD.GetSpriteRect(m_HUD_dmg_bio).right - gHUD.GetSpriteRect(m_HUD_dmg_bio).left; + giDmgWidth = gHUD.GetSpriteRect(m_HUD_dmg_bio).bottom - gHUD.GetSpriteRect(m_HUD_dmg_bio).top; + return 1; +} + +int CHudHealth:: MsgFunc_Health(const char *pszName, int iSize, void *pbuf ) +{ + // TODO: update local health data + BEGIN_READ( pbuf, iSize ); + int x = READ_BYTE(); + + m_iFlags |= HUD_ACTIVE; + + // Only update the fade if we've changed health + if (x != m_iHealth) + { + m_fFade = FADE_TIME; + m_iHealth = x; + } + + gViewPort->m_pHud2->UpdateHealth(x); // buz + + return 1; +} + +int CHudHealth:: MsgFunc_Damage(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int armor = READ_BYTE(); // armor + int damageTaken = READ_BYTE(); // health + long bitsDamage = READ_LONG(); // damage bits + + Vector vecFrom; + + for ( int i = 0 ; i < 3 ; i++) + vecFrom[i] = READ_COORD(); + + UpdateTiles(gHUD.m_flTime, bitsDamage); + + // Actually took damage? + if( damageTaken > 0 || armor > 0) + { + CalcDamageDirection( vecFrom ); + } + + return 1; +} + + +// Returns back a color from the +// Green <-> Yellow <-> Red ramp +void CHudHealth::GetPainColor( int &r, int &g, int &b ) +{ + int iHealth = m_iHealth; + + if (iHealth > 25) + iHealth -= 25; + else if ( iHealth < 0 ) + iHealth = 0; +#if 0 + g = iHealth * 255 / 100; + r = 255 - g; + b = 0; +#else + if (m_iHealth > 25) + { + UnpackRGB(r,g,b, gHUD.m_iHUDColor); + } + else + { + r = 250; + g = 0; + b = 0; + } +#endif +} + +int CHudHealth::Draw(float flTime) +{ +// gEngfuncs.Con_Printf("drawing health. decindex is %d\n", m_hDecoration); + + if ( (gHUD.m_iHideHUDDisplay & HIDEHUD_HEALTH) || gEngfuncs.IsSpectateOnly() ) + return 1; + + if ( !m_hSprite ) + m_hSprite = LoadSprite(PAIN_NAME); + +#if (0) // buz: dont draw health! + int r, g, b; + int a = 0, x, y; + int HealthWidth; + + // Has health changed? Flash the health # // buz - comment from here + if (m_fFade) + { + m_fFade -= (gHUD.m_flTimeDelta * 20); + if (m_fFade <= 0) + { + a = MIN_ALPHA; + m_fFade = 0; + } + + // Fade the health number back to dim + + a = MIN_ALPHA + (m_fFade/FADE_TIME) * 128; + + } + else + a = MIN_ALPHA; + + // If health is getting low, make it bright red + if (m_iHealth <= 15) + a = 255; + + GetPainColor( r, g, b ); + ScaleColors(r, g, b, a ); // buz - to here + +// r = 255; g = 255; b = 255; //buz + + // Only draw health if we have the suit. + if (FBitSet( gHUD.m_iHideHUDDisplay, ITEM_SUIT )) + { + // buz: draw decoration + // SPR_Set(gHUD.GetSprite(m_hDecoration), 255, 255, 255 ); + // SPR_DrawHoles(0, 0, ScreenHeight - (gHUD.GetSpriteRect(m_hDecoration).bottom - gHUD.GetSpriteRect(m_hDecoration).top), &gHUD.GetSpriteRect(m_hDecoration)); + +// buz- comment from here + HealthWidth = gHUD.GetSpriteRect(gHUD.m_HUD_number_0).right - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).left; + int CrossWidth = gHUD.GetSpriteRect(m_HUD_cross).right - gHUD.GetSpriteRect(m_HUD_cross).left; + + y = ScreenHeight - gHUD.m_iFontHeight - gHUD.m_iFontHeight / 2; + x = CrossWidth /2; + + SPR_Set(gHUD.GetSprite(m_HUD_cross), r, g, b); + SPR_DrawAdditive(0, x, y, &gHUD.GetSpriteRect(m_HUD_cross)); + + x = CrossWidth + HealthWidth / 2; + + x = gHUD.DrawHudNumber(x, y, DHN_3DIGITS | DHN_DRAWZERO, m_iHealth, r, g, b); + + x += HealthWidth/2; + + int iHeight = gHUD.m_iFontHeight; + int iWidth = HealthWidth/10; + + UnpackRGB(r,g,b, gHUD.m_iHUDColor); //LRC + //LRC FillRGBA(x, y, iWidth, iHeight, 255, 160, 0, a); + FillRGBA(x, y, iWidth, iHeight, r, g, b, a); //LRC buz - to here +/* if (ScreenWidth < 640) + { + x = 57; + y = ScreenHeight - 15; + } + else + { + x = 112; + y = ScreenHeight - 29; + }*/ // buz + +// gEngfuncs.Con_Printf("Health amount: %d\n", m_iHealth); +// x = gHUD.DrawHudNumber(x, y, DHN_3DIGITS | DHN_DRAWZERO, m_iHealth, r, g, b); + } +#endif // buz + + DrawDamage(flTime); + return DrawPain(flTime); +} + +void CHudHealth::CalcDamageDirection(Vector vecFrom) +{ + Vector forward, right, up; + float side, front; + Vector vecOrigin, vecAngles; + + if (!vecFrom[0] && !vecFrom[1] && !vecFrom[2]) + { + m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 0; + return; + } + + + memcpy(vecOrigin, gHUD.m_vecOrigin, sizeof(Vector)); + memcpy(vecAngles, gHUD.m_vecAngles, sizeof(Vector)); + + + VectorSubtract (vecFrom, vecOrigin, vecFrom); + + float flDistToTarget = vecFrom.Length(); + + vecFrom = vecFrom.Normalize(); + AngleVectors (vecAngles, forward, right, up); + + front = DotProduct (vecFrom, right); + side = DotProduct (vecFrom, forward); + + if (flDistToTarget <= 50) + { + m_fAttackFront = m_fAttackRear = m_fAttackRight = m_fAttackLeft = 1; + } + else + { + if (side > 0) + { + if (side > 0.3) + m_fAttackFront = max(m_fAttackFront, side); + } + else + { + float f = fabs(side); + if (f > 0.3) + m_fAttackRear = max(m_fAttackRear, f); + } + + if (front > 0) + { + if (front > 0.3) + m_fAttackRight = max(m_fAttackRight, front); + } + else + { + float f = fabs(front); + if (f > 0.3) + m_fAttackLeft = max(m_fAttackLeft, f); + } + } +} + +int CHudHealth::DrawPain(float flTime) +{ + if (!(m_fAttackFront || m_fAttackRear || m_fAttackLeft || m_fAttackRight)) + return 1; + + int r, g, b; + int x, y, a, shade; + + // TODO: get the shift value of the health + a = 255; // max brightness until then + + float fFade = gHUD.m_flTimeDelta * 2; + + // SPR_Draw top + if (m_fAttackFront > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackFront, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 - SPR_Width(m_hSprite, 0)/2; + y = ScreenHeight/2 - SPR_Height(m_hSprite,0) * 3; + SPR_DrawAdditive(0, x, y, NULL); + m_fAttackFront = max( 0, m_fAttackFront - fFade ); + } else + m_fAttackFront = 0; + + if (m_fAttackRight > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackRight, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 + SPR_Width(m_hSprite, 1) * 2; + y = ScreenHeight/2 - SPR_Height(m_hSprite,1)/2; + SPR_DrawAdditive(1, x, y, NULL); + m_fAttackRight = max( 0, m_fAttackRight - fFade ); + } else + m_fAttackRight = 0; + + if (m_fAttackRear > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackRear, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 - SPR_Width(m_hSprite, 2)/2; + y = ScreenHeight/2 + SPR_Height(m_hSprite,2) * 2; + SPR_DrawAdditive(2, x, y, NULL); + m_fAttackRear = max( 0, m_fAttackRear - fFade ); + } else + m_fAttackRear = 0; + + if (m_fAttackLeft > 0.4) + { + GetPainColor(r,g,b); + shade = a * max( m_fAttackLeft, 0.5 ); + ScaleColors(r, g, b, shade); + SPR_Set(m_hSprite, r, g, b ); + + x = ScreenWidth/2 - SPR_Width(m_hSprite, 3) * 3; + y = ScreenHeight/2 - SPR_Height(m_hSprite,3)/2; + SPR_DrawAdditive(3, x, y, NULL); + + m_fAttackLeft = max( 0, m_fAttackLeft - fFade ); + } else + m_fAttackLeft = 0; + + return 1; +} + +int CHudHealth::DrawDamage(float flTime) +{ + int r, g, b, a; + DAMAGE_IMAGE *pdmg; + + if (!m_bitsDamage) + return 1; + + UnpackRGB(r,g,b, gHUD.m_iHUDColor); + + a = (int)( fabs(sin(flTime*2)) * 256.0); + + ScaleColors(r, g, b, a); + + // Draw all the items + for (int i = 0; i < NUM_DMG_TYPES; i++) + { + if (m_bitsDamage & giDmgFlags[i]) + { + pdmg = &m_dmg[i]; + HSPRITE hCurrent = gHUD.GetSprite( m_HUD_dmg_bio + i ); + if( !hCurrent ) continue; // sprite was missed + SPR_Set( hCurrent, r, g, b ); + SPR_DrawAdditive(0, pdmg->x, pdmg->y, &gHUD.GetSpriteRect(m_HUD_dmg_bio + i)); + } + } + + + // check for bits that should be expired + for ( i = 0; i < NUM_DMG_TYPES; i++ ) + { + DAMAGE_IMAGE *pdmg = &m_dmg[i]; + + if ( m_bitsDamage & giDmgFlags[i] ) + { + pdmg->fExpire = min( flTime + DMG_IMAGE_LIFE, pdmg->fExpire ); + + if ( pdmg->fExpire <= flTime // when the time has expired + && a < 40 ) // and the flash is at the low point of the cycle + { + pdmg->fExpire = 0; + + int y = pdmg->y; + pdmg->x = pdmg->y = 0; + + // move everyone above down + for (int j = 0; j < NUM_DMG_TYPES; j++) + { + pdmg = &m_dmg[j]; + if ((pdmg->y) && (pdmg->y < y)) + pdmg->y += giDmgHeight; + + } + + m_bitsDamage &= ~giDmgFlags[i]; // clear the bits + } + } + } + + return 1; +} + + +void CHudHealth::UpdateTiles(float flTime, long bitsDamage) +{ + DAMAGE_IMAGE *pdmg; + + // Which types are new? + long bitsOn = ~m_bitsDamage & bitsDamage; + + for (int i = 0; i < NUM_DMG_TYPES; i++) + { + pdmg = &m_dmg[i]; + + // Is this one already on? + if (m_bitsDamage & giDmgFlags[i]) + { + pdmg->fExpire = flTime + DMG_IMAGE_LIFE; // extend the duration + if (!pdmg->fBaseline) + pdmg->fBaseline = flTime; + } + + // Are we just turning it on? + if (bitsOn & giDmgFlags[i]) + { + // put this one at the bottom + pdmg->x = giDmgWidth/8; + pdmg->y = ScreenHeight - giDmgHeight * 2; + pdmg->fExpire=flTime + DMG_IMAGE_LIFE; + + // move everyone else up + for (int j = 0; j < NUM_DMG_TYPES; j++) + { + if (j == i) + continue; + + pdmg = &m_dmg[j]; + if (pdmg->y) + pdmg->y -= giDmgHeight; + + } + pdmg = &m_dmg[i]; + } + } + + // damage bits are only turned on here; they are turned off when the draw time has expired (in DrawDamage()) + m_bitsDamage |= bitsDamage; +} diff --git a/cl_dll/health.h b/cl_dll/health.h new file mode 100644 index 0000000..e84fdde --- /dev/null +++ b/cl_dll/health.h @@ -0,0 +1,128 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#define DMG_IMAGE_LIFE 2 // seconds that image is up + +#define DMG_IMAGE_POISON 0 +#define DMG_IMAGE_ACID 1 +#define DMG_IMAGE_COLD 2 +#define DMG_IMAGE_DROWN 3 +#define DMG_IMAGE_BURN 4 +#define DMG_IMAGE_NERVE 5 +#define DMG_IMAGE_RAD 6 +#define DMG_IMAGE_SHOCK 7 +//tf defines +#define DMG_IMAGE_CALTROP 8 +#define DMG_IMAGE_TRANQ 9 +#define DMG_IMAGE_CONCUSS 10 +#define DMG_IMAGE_HALLUC 11 +#define NUM_DMG_TYPES 12 +// instant damage + +#define DMG_GENERIC 0 // generic damage was done +#define DMG_CRUSH (1 << 0) // crushed by falling or moving object +#define DMG_BULLET (1 << 1) // shot +#define DMG_SLASH (1 << 2) // cut, clawed, stabbed +#define DMG_BURN (1 << 3) // heat burned +#define DMG_FREEZE (1 << 4) // frozen +#define DMG_FALL (1 << 5) // fell too far +#define DMG_BLAST (1 << 6) // explosive blast damage +#define DMG_CLUB (1 << 7) // crowbar, punch, headbutt +#define DMG_SHOCK (1 << 8) // electric shock +#define DMG_SONIC (1 << 9) // sound pulse shockwave +#define DMG_ENERGYBEAM (1 << 10) // laser or other high energy beam +#define DMG_NEVERGIB (1 << 12) // with this bit OR'd in, no damage type will be able to gib victims upon death +#define DMG_ALWAYSGIB (1 << 13) // with this bit OR'd in, any damage type can be made to gib victims upon death. + + +// time-based damage +//mask off TF-specific stuff too +#define DMG_TIMEBASED (~(0xff003fff)) // mask for time-based damage + + +#define DMG_DROWN (1 << 14) // Drowning +#define DMG_FIRSTTIMEBASED DMG_DROWN + +#define DMG_PARALYZE (1 << 15) // slows affected creature down +#define DMG_NERVEGAS (1 << 16) // nerve toxins, very bad +#define DMG_POISON (1 << 17) // blood poisioning +#define DMG_RADIATION (1 << 18) // radiation exposure +#define DMG_DROWNRECOVER (1 << 19) // drowning recovery +#define DMG_ACID (1 << 20) // toxic chemicals or acid burns +#define DMG_SLOWBURN (1 << 21) // in an oven +#define DMG_SLOWFREEZE (1 << 22) // in a subzero freezer +#define DMG_MORTAR (1 << 23) // Hit by air raid (done to distinguish grenade from mortar) + +//TF ADDITIONS +#define DMG_IGNITE (1 << 24) // Players hit by this begin to burn +#define DMG_RADIUS_MAX (1 << 25) // Radius damage with this flag doesn't decrease over distance +#define DMG_RADIUS_QUAKE (1 << 26) // Radius damage is done like Quake. 1/2 damage at 1/2 radius. +#define DMG_IGNOREARMOR (1 << 27) // Damage ignores target's armor +#define DMG_AIMED (1 << 28) // Does Hit location damage +#define DMG_WALLPIERCING (1 << 29) // Blast Damages ents through walls + +#define DMG_CALTROP (1<<30) +#define DMG_HALLUC (1<<31) + +// TF Healing Additions for TakeHealth +#define DMG_IGNORE_MAXHEALTH DMG_IGNITE +// TF Redefines since we never use the originals +#define DMG_NAIL DMG_SLASH +#define DMG_NOT_SELF DMG_FREEZE + + +#define DMG_TRANQ DMG_MORTAR +#define DMG_CONCUSS DMG_SONIC + + + +typedef struct +{ + float fExpire; + float fBaseline; + int x, y; +} DAMAGE_IMAGE; + +// +//----------------------------------------------------- +// +class CHudHealth: public CHudBase +{ +public: + virtual int Init( void ); + virtual int VidInit( void ); + virtual int Draw(float fTime); + virtual void Reset( void ); + int MsgFunc_Health(const char *pszName, int iSize, void *pbuf); + int MsgFunc_Damage(const char *pszName, int iSize, void *pbuf); + int m_iHealth; + int m_HUD_dmg_bio; + int m_HUD_cross; + float m_fAttackFront, m_fAttackRear, m_fAttackLeft, m_fAttackRight; + void GetPainColor( int &r, int &g, int &b ); + float m_fFade; + +private: + HSPRITE m_hSprite; + HSPRITE m_hDamage; + int m_hDecoration; // buz + + DAMAGE_IMAGE m_dmg[NUM_DMG_TYPES]; + int m_bitsDamage; + int DrawPain(float fTime); + int DrawDamage(float fTime); + void CalcDamageDirection(vec3_t vecFrom); + void UpdateTiles(float fTime, long bits); +}; diff --git a/cl_dll/hud.cpp b/cl_dll/hud.cpp new file mode 100644 index 0000000..f752a3a --- /dev/null +++ b/cl_dll/hud.cpp @@ -0,0 +1,889 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud.cpp +// +// implementation of CHud class +// + +//LRC - define to help track what calls are made on changelevel, save/restore, etc +#define ENGINE_DEBUG + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" +#include "hud_servers.h" +#include "vgui_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_subtitles.h" // buz +#include "vgui_radio.h" // buz +#include "vgui_hud.h" // buz +#include "vgui_tabpanel.h" // buz +#include "studio.h" +#include "demo.h" +#include "demo_api.h" +#include "vgui_scorepanel.h" +#include "gl_local.h" // buz +#include "r_studioint.h" + +cvar_t *cl_rollspeed; +cvar_t *cl_rollangle; + +class CHLVoiceStatusHelper : public IVoiceStatusHelper +{ +public: + virtual void GetPlayerTextColor(int entindex, int color[3]) + { + color[0] = color[1] = color[2] = 255; + + if( entindex >= 0 && entindex < sizeof(g_PlayerExtraInfo)/sizeof(g_PlayerExtraInfo[0]) ) + { + int iTeam = g_PlayerExtraInfo[entindex].teamnumber; + + if ( iTeam < 0 ) + { + iTeam = 0; + } + + iTeam = iTeam % iNumberOfTeamColors; + + color[0] = iTeamColors[iTeam][0]; + color[1] = iTeamColors[iTeam][1]; + color[2] = iTeamColors[iTeam][2]; + } + } + + virtual void UpdateCursorState() + { + gViewPort->UpdateCursorState(); + } + + virtual int GetAckIconHeight() + { + return ScreenHeight - gHUD.m_iFontHeight*3 - 6; + } + + virtual bool CanShowSpeakerLabels() + { + if( gViewPort && gViewPort->m_pScoreBoard ) + return !gViewPort->m_pScoreBoard->isVisible(); + else + return false; + } +}; +static CHLVoiceStatusHelper g_VoiceStatusHelper; + + +extern client_sprite_t *GetSpriteList(client_sprite_t *pList, const char *psz, int iRes, int iCount); +extern cvar_t *sensitivity; +cvar_t *cl_lw = NULL; + +void ShutdownInput (void); + +//DECLARE_MESSAGE(m_Logo, Logo) +int __MsgFunc_Logo(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_Logo(pszName, iSize, pbuf ); +} + +//DECLARE_MESSAGE(m_Logo, Logo) +//LRC +int __MsgFunc_HUDColor(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_HUDColor(pszName, iSize, pbuf ); +} + +//LRC +int __MsgFunc_SetFog(const char *pszName, int iSize, void *pbuf) +{ + gHUD.MsgFunc_SetFog( pszName, iSize, pbuf ); + return 1; +} + +//LRC +int __MsgFunc_KeyedDLight(const char *pszName, int iSize, void *pbuf) +{ + gHUD.MsgFunc_KeyedDLight( pszName, iSize, pbuf ); + return 1; +} + +//LRC +int __MsgFunc_SetSky(const char *pszName, int iSize, void *pbuf) +{ + gHUD.MsgFunc_SetSky( pszName, iSize, pbuf ); + return 1; +} + +int __MsgFunc_ResetHUD(const char *pszName, int iSize, void *pbuf) +{ +#ifdef ENGINE_DEBUG +// CONPRINT("## ResetHUD\n"); +#endif + return gHUD.MsgFunc_ResetHUD(pszName, iSize, pbuf ); +} + +int __MsgFunc_InitHUD(const char *pszName, int iSize, void *pbuf) +{ +#ifdef ENGINE_DEBUG +// CONPRINT("## InitHUD\n"); +#endif + gHUD.MsgFunc_InitHUD( pszName, iSize, pbuf ); + return 1; +} + +int __MsgFunc_ViewMode(const char *pszName, int iSize, void *pbuf) +{ + gHUD.MsgFunc_ViewMode( pszName, iSize, pbuf ); + return 1; +} + +int __MsgFunc_SetFOV(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_SetFOV( pszName, iSize, pbuf ); +} + +int __MsgFunc_Concuss(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_Concuss( pszName, iSize, pbuf ); +} + +int __MsgFunc_GameMode( const char *pszName, int iSize, void *pbuf ) +{ + return gHUD.MsgFunc_GameMode( pszName, iSize, pbuf ); +} + +int __MsgFunc_WaterSplash( const char *pszName, int iSize, void *pbuf ) +{ + gHUD.MsgFunc_WaterSplash( pszName, iSize, pbuf ); + return 1; +} + +int __MsgFunc_NewExplode( const char *pszName, int iSize, void *pbuf ) +{ + gHUD.MsgFunc_NewExplode( pszName, iSize, pbuf ); + return 1; +} + +// buz +int __MsgFunc_GasMask(const char *pszName, int iSize, void *pbuf ) +{ + return gHUD.MsgFunc_GasMask( pszName, iSize, pbuf ); +} + +// buz +int __MsgFunc_SpecTank(const char *pszName, int iSize, void *pbuf ) +{ + return gHUD.MsgFunc_SpecTank( pszName, iSize, pbuf ); +} + +int __MsgFunc_HeadShield(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_HeadShield( pszName, iSize, pbuf ); +} + +int __MsgFunc_Particle(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_Particle( pszName, iSize, pbuf ); +} + +int __MsgFunc_DelParticle(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_DelParticle( pszName, iSize, pbuf ); +} + +int __MsgFunc_WeaponAnim(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_WeaponAnim( pszName, iSize, pbuf ); +} + +int __MsgFunc_WeaponBody(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_WeaponBody( pszName, iSize, pbuf ); +} + +int __MsgFunc_WeaponSkin(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_WeaponSkin( pszName, iSize, pbuf ); +} + +int __MsgFunc_SkyMarker(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_SkyMarker( pszName, iSize, pbuf ); +} + +int __MsgFunc_WorldMarker(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_WorldMarker( pszName, iSize, pbuf ); +} + +int __MsgFunc_CustomDecal(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_CustomDecal( pszName, iSize, pbuf ); +} + +int __MsgFunc_StudioDecal(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_StudioDecal( pszName, iSize, pbuf ); +} + +int __MsgFunc_PartEffect(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_PartEffect( pszName, iSize, pbuf ); +} + +int __MsgFunc_LevelTime(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_LevelTime( pszName, iSize, pbuf ); +} + +int __MsgFunc_BlurEffect(const char *pszName, int iSize, void *pbuf) +{ + gHUD.MsgFunc_BlurEffect( pszName, iSize, pbuf ); + return 1; +} + +// TFFree Command Menu +void __CmdFunc_OpenCommandMenu(void) +{ + if ( gViewPort ) + { + gViewPort->ShowCommandMenu( gViewPort->m_StandardMenu ); + } +} + +// TFC "special" command +void __CmdFunc_InputPlayerSpecial(void) +{ + if ( gViewPort ) + { + gViewPort->InputPlayerSpecial(); + } +} + +void __CmdFunc_CloseCommandMenu(void) +{ + if ( gViewPort ) + { + gViewPort->InputSignalHideCommandMenu(); + } +} + +void __CmdFunc_ForceCloseCommandMenu( void ) +{ + if ( gViewPort ) + { + gViewPort->HideCommandMenu(); + } +} + +void __CmdFunc_ToggleServerBrowser( void ) +{ + if ( gViewPort ) + { + gViewPort->ToggleServerBrowser(); + } +} + +// TFFree Command Menu Message Handlers +int __MsgFunc_ValClass(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_ValClass( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_TeamNames(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_TeamNames( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_Feign(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_Feign( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_Detpack(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_Detpack( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_VGUIMenu(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_VGUIMenu( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_MOTD(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_MOTD( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_BuildSt(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_BuildSt( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_RandomPC(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_RandomPC( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_ServerName(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_ServerName( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_ScoreInfo(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_ScoreInfo( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_TeamScore(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_TeamScore( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_TeamInfo(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_TeamInfo( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_Spectator(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_Spectator( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_AllowSpec(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_AllowSpec( pszName, iSize, pbuf ); + return 0; +} + +int __MsgFunc_MusicFade(const char *pszName, int iSize, void *pbuf ) +{ + return gHUD.MsgFunc_MusicFade( pszName, iSize, pbuf ); +} + +// buz +int __MsgFunc_TextWindow(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort) + return gViewPort->MsgFunc_ShowTextWindow( pszName, iSize, pbuf ); + return 0; +} + +// buz +int __MsgFunc_RainData(const char *pszName, int iSize, void *pbuf) +{ + return gHUD.MsgFunc_RainData( pszName, iSize, pbuf ); +} + +int MsgCustomDlight(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + vec3_t pos; + pos.x = READ_COORD(); + pos.y = READ_COORD(); + pos.z = READ_COORD(); + float radius = (float)READ_BYTE() * 10; + float life = (float)READ_BYTE() / 10; + float decay = (float)READ_BYTE() * 10; + + CDynLight *dl = CL_AllocDlight (0); + + R_SetupLightParams( dl, pos, g_vecZero, radius, 0.0f, LIGHT_OMNI ); + + dl->color = Vector( 0.7f, 0.6f, 0.5f ); + dl->die = GET_CLIENT_TIME() + life; + dl->decay = decay; + + return 1; +} + +// debug command +// usage: makelight [R G B] [radius] [life]\n" ); +void MakeLight( void ) +{ + CDynLight *dl = CL_AllocDlight( 0 ); + + if( CMD_ARGC() >= 4 ) + { + dl->color[0] = Q_atof(CMD_ARGV( 1 )); + dl->color[1] = Q_atof(CMD_ARGV( 2 )); + dl->color[2] = Q_atof(CMD_ARGV( 3 )); + } + else + { + dl->color = Vector( 0.7f, 0.6f, 0.5f ); + } + + float radius; + + if( CMD_ARGC() >= 5 ) + radius = Q_atof(CMD_ARGV( 4 )); + else radius = 128.0f; + + R_SetupLightParams( dl, GetVieworg(), g_vecZero, radius, 0.0f, LIGHT_OMNI ); + + if( CMD_ARGC() >= 6 ) + dl->die = GET_CLIENT_TIME() + Q_atof(CMD_ARGV( 5 )); + else dl->die = GET_CLIENT_TIME() + 30.0f; +} + +void GammaGraphInit(); + +void CanUseInit( void ); // Wargon: Èêîíêà þçà. + +// This is called every time the DLL is loaded +void CHud :: Init( void ) +{ + static cl_entity_t head_shield; + + GammaGraphInit(); + RadioIconInit(); // buz + Hud2Init(); // buz + SubtitleInit(); // buz + TabPanelInit(); // buz + + CanUseInit(); // Wargon: Èêîíêà þçà. + + // pointer to headshield entity + m_pHeadShieldEnt = &head_shield; + + HOOK_MESSAGE( Logo ); + HOOK_MESSAGE( ResetHUD ); + HOOK_MESSAGE( GameMode ); + HOOK_MESSAGE( InitHUD ); + HOOK_MESSAGE( ViewMode ); + HOOK_MESSAGE( SetFOV ); + HOOK_MESSAGE( Concuss ); + HOOK_MESSAGE( HUDColor ); //LRC + HOOK_MESSAGE( SetFog ); //LRC + HOOK_MESSAGE( KeyedDLight ); //LRC + HOOK_MESSAGE( SetSky ); //LRC + HOOK_MESSAGE( GasMask ); //buz + HOOK_MESSAGE( SpecTank ); //buz + HOOK_MESSAGE( TextWindow ); // buz + HOOK_MESSAGE( RainData );// buz + HOOK_MESSAGE( Particle );// buz + HOOK_MESSAGE( DelParticle );// buz + gEngfuncs.pfnHookUserMsg( "mydlight", MsgCustomDlight ); + + // TFFree CommandMenu + HOOK_COMMAND( "+commandmenu", OpenCommandMenu ); + HOOK_COMMAND( "-commandmenu", CloseCommandMenu ); + HOOK_COMMAND( "ForceCloseCommandMenu", ForceCloseCommandMenu ); + HOOK_COMMAND( "special", InputPlayerSpecial ); + HOOK_COMMAND( "togglebrowser", ToggleServerBrowser ); + gEngfuncs.pfnAddCommand( "makelight", MakeLight ); + + HOOK_MESSAGE( ValClass ); + HOOK_MESSAGE( TeamNames ); + HOOK_MESSAGE( Feign ); + HOOK_MESSAGE( Detpack ); + HOOK_MESSAGE( MOTD ); + HOOK_MESSAGE( BuildSt ); + HOOK_MESSAGE( RandomPC ); + HOOK_MESSAGE( ServerName ); + HOOK_MESSAGE( ScoreInfo ); + HOOK_MESSAGE( TeamScore ); + HOOK_MESSAGE( TeamInfo ); + + HOOK_MESSAGE( Spectator ); + HOOK_MESSAGE( AllowSpec ); + HOOK_MESSAGE( WaterSplash ); + HOOK_MESSAGE( NewExplode ); + HOOK_MESSAGE( HeadShield ); + + HOOK_MESSAGE( WeaponAnim ); + HOOK_MESSAGE( WeaponBody ); + HOOK_MESSAGE( WeaponSkin ); + + HOOK_MESSAGE( SkyMarker ); + HOOK_MESSAGE( WorldMarker ); + + HOOK_MESSAGE( StudioDecal ); + HOOK_MESSAGE( CustomDecal ); + + HOOK_MESSAGE( PartEffect ); + HOOK_MESSAGE( LevelTime ); + + HOOK_MESSAGE( BlurEffect ); + + // VGUI Menus + HOOK_MESSAGE( VGUIMenu ); + + m_pZoomSpeed = gEngfuncs.pfnRegisterVariable( "cl_zoomspeed","100", 0 ); + CVAR_REGISTER( "hud_classautokill", "1", FCVAR_ARCHIVE | FCVAR_USERINFO ); // controls whether or not to suicide immediately on TF class switch + CVAR_REGISTER( "hud_takesshots", "0", FCVAR_ARCHIVE );// controls whether or not to automatically take screenshots at the end of a round + + HOOK_MESSAGE( MusicFade ); + + m_iLogo = 0; + m_iFOV = 90; // buz - make 90, not 0 + m_iHUDColor = 0x00FFA000; //255,160,0 -- LRC + + CVAR_REGISTER( "zoom_sensitivity_ratio", "1.2", 0 ); + default_fov = CVAR_REGISTER( "default_fov", "75", FCVAR_ARCHIVE );// buz: turn off + m_pCvarStealMouse = CVAR_REGISTER( "hud_capturemouse", "1", FCVAR_ARCHIVE ); + m_pCvarDraw = CVAR_REGISTER( "hud_draw", "1", FCVAR_ARCHIVE ); + cl_lw = gEngfuncs.pfnGetCvarPointer( "cl_lw" ); + + cl_rollangle = gEngfuncs.pfnRegisterVariable ( "cl_rollangle", "0.65", FCVAR_CLIENTDLL|FCVAR_ARCHIVE ); + cl_rollspeed = gEngfuncs.pfnRegisterVariable ( "cl_rollspeed", "300", FCVAR_CLIENTDLL|FCVAR_ARCHIVE ); + + m_pSpriteList = NULL; + + // Clear any old HUD list + if ( m_pHudList ) + { + HUDLIST *pList; + while ( m_pHudList ) + { + pList = m_pHudList; + m_pHudList = m_pHudList->pNext; + free( pList ); + } + m_pHudList = NULL; + } + + // In case we get messages before the first update -- time will be valid + m_flTime = 1.0; + + m_Ammo.Init(); + m_HudStamina.Init(); + m_Health.Init(); + m_SayText.Init(); + m_Spectator.Init(); + m_Geiger.Init(); + m_Train.Init(); + m_Battery.Init(); + m_Flash.Init(); + m_Message.Init(); + m_StatusBar.Init(); + m_DeathNotice.Init(); + m_AmmoSecondary.Init(); + m_TextMessage.Init(); + m_StatusIcons.Init(); + m_Lensflare.Init(); + GetClientVoiceMgr()->Init(&g_VoiceStatusHelper, (vgui::Panel**)&gViewPort); + + m_Menu.Init(); + + ServersInit(); + + MsgFunc_ResetHUD(0, 0, NULL ); +} + +// CHud destructor +// cleans up memory allocated for m_rg* arrays +CHud :: ~CHud() +{ + delete [] m_rghSprites; + delete [] m_rgrcRects; + delete [] m_rgszSpriteNames; + + if ( m_pHudList ) + { + HUDLIST *pList; + while ( m_pHudList ) + { + pList = m_pHudList; + m_pHudList = m_pHudList->pNext; + free( pList ); + } + m_pHudList = NULL; + } + + ServersShutdown(); +} + +// GetSpriteIndex() +// searches through the sprite list loaded from hud.txt for a name matching SpriteName +// returns an index into the gHUD.m_rghSprites[] array +// returns 0 if sprite not found +int CHud :: GetSpriteIndex( const char *SpriteName ) +{ + // look through the loaded sprite name list for SpriteName + for ( int i = 0; i < m_iSpriteCount; i++ ) + { + if ( strncmp( SpriteName, m_rgszSpriteNames + (i * MAX_SPRITE_NAME_LENGTH), MAX_SPRITE_NAME_LENGTH ) == 0 ) + return i; + } + + return -1; // invalid sprite +} + +void CHud :: VidInit( void ) +{ + m_flDeadTime = 0; // buz + m_SpecTank_on = 0; // buz + + memset( m_pHeadShieldEnt, 0, sizeof(cl_entity_t)); + m_pHeadShieldEnt->modelhandle = INVALID_HANDLE; + m_pHeadShieldEnt->curstate.framerate = 1.0f; + m_iHeadShieldState = SHIELD_OFF; + m_flHeadShieldSwitchTime = 0.0f; + CVAR_SET_FLOAT( "hud_draw", 1.0f ); + + m_flFOV = -1; // buz + + m_scrinfo.iSize = sizeof(m_scrinfo); + GetScreenInfo(&m_scrinfo); + + // ---------- + // Load Sprites + // --------- +// m_hsprFont = LoadSprite("sprites/%d_font.spr"); + + m_hsprLogo = 0; + m_hsprCursor = 0; + + if (ScreenWidth < 640) + m_iRes = 320; + else + m_iRes = 640; + + // Only load this once + if ( !m_pSpriteList ) + { + // we need to load the hud.txt, and all sprites within + m_pSpriteList = SPR_GetList("scripts/weapons/hud.txt", &m_iSpriteCountAllRes); + + if (m_pSpriteList) + { + // count the number of sprites of the appropriate res + m_iSpriteCount = 0; + client_sprite_t *p = m_pSpriteList; + for ( int j = 0; j < m_iSpriteCountAllRes; j++ ) + { + if ( p->iRes == m_iRes ) + m_iSpriteCount++; + p++; + } + + // allocated memory for sprite handle arrays + m_rghSprites = new HSPRITE[m_iSpriteCount]; + m_rgrcRects = new wrect_t[m_iSpriteCount]; + m_rgszSpriteNames = new char[m_iSpriteCount * MAX_SPRITE_NAME_LENGTH]; + + p = m_pSpriteList; + int index = 0; + for ( j = 0; j < m_iSpriteCountAllRes; j++ ) + { + if ( p->iRes == m_iRes ) + { + char sz[256]; + sprintf(sz, "sprites/%s.spr", p->szSprite); + m_rghSprites[index] = SPR_Load(sz); + m_rgrcRects[index] = p->rc; + strncpy( &m_rgszSpriteNames[index * MAX_SPRITE_NAME_LENGTH], p->szName, MAX_SPRITE_NAME_LENGTH ); + + index++; + } + + p++; + } + } + } + else + { + // we have already have loaded the sprite reference from hud.txt, but + // we need to make sure all the sprites have been loaded (we've gone through a transition, or loaded a save game) + client_sprite_t *p = m_pSpriteList; + int index = 0; + for ( int j = 0; j < m_iSpriteCountAllRes; j++ ) + { + if ( p->iRes == m_iRes ) + { + char sz[256]; + sprintf( sz, "sprites/%s.spr", p->szSprite ); + m_rghSprites[index] = SPR_Load(sz); + index++; + } + + p++; + } + } + + // assumption: number_1, number_2, etc, are all listed and loaded sequentially + m_HUD_number_0 = GetSpriteIndex( "number_0" ); + + m_iFontHeight = m_rgrcRects[m_HUD_number_0].bottom - m_rgrcRects[m_HUD_number_0].top; + + m_Ammo.VidInit(); + m_HudStamina.VidInit(); + m_Health.VidInit(); + m_Spectator.VidInit(); + m_Geiger.VidInit(); + m_Train.VidInit(); + m_Battery.VidInit(); + m_Flash.VidInit(); + m_Message.VidInit(); + m_StatusBar.VidInit(); + m_DeathNotice.VidInit(); + m_SayText.VidInit(); + m_Menu.VidInit(); + m_AmmoSecondary.VidInit(); + m_TextMessage.VidInit(); + m_StatusIcons.VidInit(); + m_Lensflare.VidInit(); + GetClientVoiceMgr()->VidInit(); +} + +int CHud::MsgFunc_Logo(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + // update Train data + m_iLogo = READ_BYTE(); + + return 1; +} + +//LRC +int CHud::MsgFunc_HUDColor(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + m_iHUDColor = READ_LONG(); + + return 1; +} + +//float g_lastFOV = 0.0; buz + +/* +================= +HUD_IsGame + +================= +*/ +int HUD_IsGame( const char *game ) +{ + const char *gamedir; + char gd[ 1024 ]; + + gamedir = gEngfuncs.pfnGetGameDirectory(); + if ( gamedir && gamedir[0] ) + { + COM_FileBase( gamedir, gd ); + if ( !stricmp( gd, game ) ) + return 1; + } + return 0; +} + +/* +===================== +HUD_GetFOV + +Returns last FOV +===================== +*/ +#if 0 // buz +float HUD_GetFOV( void ) +{ + if ( gEngfuncs.pDemoAPI->IsRecording() ) + { + // Write it + int i = 0; + unsigned char buf[ 100 ]; + + // Active + *( float * )&buf[ i ] = g_lastFOV; + i += sizeof( float ); + + Demo_WriteBuffer( TYPE_ZOOM, i, buf ); + } + + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + { + g_lastFOV = g_demozoom; + } + return g_lastFOV; +} +#endif + +int CHud::MsgFunc_SetFOV(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + int newfov = READ_BYTE(); + + return 1; +} + +void CHud::AddHudElem(CHudBase *phudelem) +{ + HUDLIST *pdl, *ptemp; + +//phudelem->Think(); + + if (!phudelem) + return; + + pdl = (HUDLIST *)malloc(sizeof(HUDLIST)); + if (!pdl) + return; + + memset(pdl, 0, sizeof(HUDLIST)); + pdl->p = phudelem; + + if (!m_pHudList) + { + m_pHudList = pdl; + return; + } + + ptemp = m_pHudList; + + while (ptemp->pNext) + ptemp = ptemp->pNext; + + ptemp->pNext = pdl; +} + +float CHud::GetSensitivity( void ) +{ + return m_flMouseSensitivity; +} \ No newline at end of file diff --git a/cl_dll/hud.h b/cl_dll/hud.h new file mode 100644 index 0000000..de53498 --- /dev/null +++ b/cl_dll/hud.h @@ -0,0 +1,832 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud.h +// +// class CHud declaration +// +// CHud handles the message, calculation, and drawing the HUD +// + +#pragma warning (disable:4018) + +#define DO_TIMING + +#define FOG_LIMIT 30000 +#define RGB_YELLOWISH 0x00FFA000 //255,160,0 +#define RGB_REDISH 0x00FF1010 //255,160,0 +#define RGB_GREENISH 0x0000A000 //0,160,0 + +#include "wrect.h" +#include "windows.h" +#include "cl_dll.h" +#include "render_api.h" +#include "enginecallback.h" +#include "ammo.h" +#include "mathlib.h" +#include "timing.h" + +#define DHN_DRAWZERO 1 +#define DHN_2DIGITS 2 +#define DHN_3DIGITS 4 +#define MIN_ALPHA 170 + +// headshield states +#define SHIELD_ON 0 +#define SHIELD_OFF 1 +#define SHIELD_TURNING_ON 2 +#define SHIELD_TURNING_OFF 3 + +// headshield anims +#define SHIELDANIM_IDLE 0 +#define SHIELDANIM_DRAW 1 +#define SHIELDANIM_HOLSTER 2 + +#define HUDELEM_ACTIVE 1 + +typedef struct { + int x, y; +} POSITION; + +enum +{ + MAX_PLAYERS = 64, + MAX_TEAMS = 64, + MAX_TEAM_NAME = 16, +}; + +typedef struct { + unsigned char r,g,b,a; +} RGBA; + +typedef struct cvar_s cvar_t; + + +#define HUD_ACTIVE 1 +#define HUD_INTERMISSION 2 + +#define MAX_PLAYER_NAME_LENGTH 32 + +#define MAX_MOTD_LENGTH 1536 + +// +//----------------------------------------------------- +// +class CHudBase +{ +public: + POSITION m_pos; + int m_type; + int m_iFlags; // active, moving, + virtual ~CHudBase() {} + virtual int Init( void ) {return 0;} + virtual int VidInit( void ) {return 0;} + virtual int Draw(float flTime) {return 0;} + virtual void Think(void) {return;} + virtual void Reset(void) {return;} + virtual void InitHUDData( void ) {} // called every time a server is connected to + +}; + +struct HUDLIST { + CHudBase *p; + HUDLIST *pNext; +}; + + + +// +//----------------------------------------------------- +// +#include "..\game_shared\voice_status.h" +#include "hud_spectator.h" + + +// +//----------------------------------------------------- +// +class CHudAmmo: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + void Think(void); + void Reset(void); + void UpdateCrosshair(); // buz + int DrawWList(float flTime); + int MsgFunc_CurWeapon(const char *pszName, int iSize, void *pbuf); + int MsgFunc_WeaponList(const char *pszName, int iSize, void *pbuf); + int MsgFunc_AmmoX(const char *pszName, int iSize, void *pbuf); + int MsgFunc_AmmoPickup( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_WeapPickup( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_ItemPickup( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_HideWeapon( const char *pszName, int iSize, void *pbuf ); + + void SlotInput( int iSlot ); + void _cdecl UserCmd_Slot1( void ); + void _cdecl UserCmd_Slot2( void ); + void _cdecl UserCmd_Slot3( void ); + void _cdecl UserCmd_Slot4( void ); + void _cdecl UserCmd_Slot5( void ); + void _cdecl UserCmd_Slot6( void ); + void _cdecl UserCmd_Slot7( void ); + void _cdecl UserCmd_Slot8( void ); + void _cdecl UserCmd_Slot9( void ); + void _cdecl UserCmd_Slot10( void ); + void _cdecl UserCmd_Close( void ); + void _cdecl UserCmd_NextWeapon( void ); + void _cdecl UserCmd_PrevWeapon( void ); + + void LoadMenuSettings( void ); + + // buz: these variables loaded from file menu_settings.txt + int m_menuSizeX; + int m_menuSizeY; + float m_menuScale; + float m_menuMinAlpha; + float m_menuAddAlpha; + float m_menuSpeed; + int m_menuRenderMode; + int m_menuOfsX; + int m_menuOfsY; + int m_menuSpaceX; + int m_menuSpaceY; + int m_menuNumberSizeX; + int m_menuNumberSizeY; + +//private: + float m_fFade; + RGBA m_rgba; + WEAPON *m_pWeapon; + int m_HUD_bucket0; + int m_HUD_selection; + int m_mach; // buz +}; + +// +//----------------------------------------------------- +// + +class CHudAmmoSecondary: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + void Reset( void ); + int Draw(float flTime); + + int MsgFunc_SecAmmoVal( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_SecAmmoIcon( const char *pszName, int iSize, void *pbuf ); + +private: + enum { + MAX_SEC_AMMO_VALUES = 4 + }; + + int m_HUD_ammoicon; // sprite indices + int m_iAmmoAmounts[MAX_SEC_AMMO_VALUES]; + float m_fFade; +}; + + +#include "health.h" + + +#define FADE_TIME 100 + + +// +//----------------------------------------------------- +// +class CHudGeiger: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + int MsgFunc_Geiger(const char *pszName, int iSize, void *pbuf); + +private: + int m_iGeigerRange; + +}; + +// +//----------------------------------------------------- +// +class CHudTrain: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + int MsgFunc_Train(const char *pszName, int iSize, void *pbuf); + +private: + HSPRITE m_hSprite; + int m_iPos; + +}; + +// +//----------------------------------------------------- +// +class CHudLensflare: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + int DrawFlare( const Vector &forward, const Vector &lightdir, const Vector &lightorg ); +private: + float flPlayerBlend; + float flPlayerBlend2; + float flPlayerBlend3; + float flPlayerBlend4; + float flPlayerBlend5; + float flPlayerBlend6; + + float Screenmx; + float Screenmy; + + float multi[10]; + + int scale[10]; + + int red[10]; + int green[10]; + int blue[10]; + + char text[10]; + float Lensx[10]; + float Lensy[10]; + + float Suncoordx; + float Suncoordy; + + float Sundistx; + float Sundisty; + + cvar_t *m_pCvarDraw; +}; + +// +//----------------------------------------------------- +// +// REMOVED: Vgui has replaced this. +// +/* +class CHudMOTD : public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw( float flTime ); + void Reset( void ); + + int MsgFunc_MOTD( const char *pszName, int iSize, void *pbuf ); + +protected: + static int MOTD_DISPLAY_TIME; + char m_szMOTD[ MAX_MOTD_LENGTH ]; + float m_flActiveRemaining; + int m_iLines; +}; +*/ + +// +//----------------------------------------------------- +// +class CHudStatusBar : public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw( float flTime ); + void Reset( void ); + void ParseStatusString( int line_num ); + + int MsgFunc_StatusText( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_StatusValue( const char *pszName, int iSize, void *pbuf ); + +protected: + enum { + MAX_STATUSTEXT_LENGTH = 128, + MAX_STATUSBAR_VALUES = 8, + MAX_STATUSBAR_LINES = 2, + }; + + char m_szStatusText[MAX_STATUSBAR_LINES][MAX_STATUSTEXT_LENGTH]; // a text string describing how the status bar is to be drawn + char m_szStatusBar[MAX_STATUSBAR_LINES][MAX_STATUSTEXT_LENGTH]; // the constructed bar that is drawn + int m_iStatusValues[MAX_STATUSBAR_VALUES]; // an array of values for use in the status bar + + int m_bReparseString; // set to TRUE whenever the m_szStatusBar needs to be recalculated + + // an array of colors...one color for each line + float *m_pflNameColors[MAX_STATUSBAR_LINES]; +}; + +// +//----------------------------------------------------- +// +// REMOVED: Vgui has replaced this. +// +/* +class CHudScoreboard: public CHudBase +{ +public: + int Init( void ); + void InitHUDData( void ); + int VidInit( void ); + int Draw( float flTime ); + int DrawPlayers( int xoffset, float listslot, int nameoffset = 0, char *team = NULL ); // returns the ypos where it finishes drawing + void UserCmd_ShowScores( void ); + void UserCmd_HideScores( void ); + int MsgFunc_ScoreInfo( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_TeamInfo( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_TeamScore( const char *pszName, int iSize, void *pbuf ); + void DeathMsg( int killer, int victim ); + + int m_iNumTeams; + + int m_iLastKilledBy; + int m_fLastKillTime; + int m_iPlayerNum; + int m_iShowscoresHeld; + + void GetAllPlayersInfo( void ); +private: + struct cvar_s *cl_showpacketloss; + +}; +*/ + +struct extra_player_info_t +{ + short frags; + short deaths; + short playerclass; + short teamnumber; + char teamname[MAX_TEAM_NAME]; +}; + +struct team_info_t +{ + char name[MAX_TEAM_NAME]; + short frags; + short deaths; + short ping; + short packetloss; + short ownteam; + short players; + int already_drawn; + int scores_overriden; + int teamnumber; +}; + +extern hud_player_info_t g_PlayerInfoList[MAX_PLAYERS+1]; // player info from the engine +extern extra_player_info_t g_PlayerExtraInfo[MAX_PLAYERS+1]; // additional player info sent directly to the client dll +extern team_info_t g_TeamInfo[MAX_TEAMS+1]; +extern int g_IsSpectator[MAX_PLAYERS+1]; + + +// +//----------------------------------------------------- +// +class CHudDeathNotice : public CHudBase +{ +public: + int Init( void ); + void InitHUDData( void ); + int VidInit( void ); + int Draw( float flTime ); + int MsgFunc_DeathMsg( const char *pszName, int iSize, void *pbuf ); + +private: + int m_HUD_d_skull; // sprite index of skull icon +}; + +// +//----------------------------------------------------- +// +class CHudMenu : public CHudBase +{ +public: + int Init( void ); + void InitHUDData( void ); + int VidInit( void ); + void Reset( void ); + int Draw( float flTime ); + int MsgFunc_ShowMenu( const char *pszName, int iSize, void *pbuf ); + + void SelectMenuItem( int menu_item ); + + int m_fMenuDisplayed; + int m_bitsValidSlots; + float m_flShutoffTime; + int m_fWaitingForMore; +}; + +// +//----------------------------------------------------- +// +class CHudSayText : public CHudBase +{ +public: + int Init( void ); + void InitHUDData( void ); + int VidInit( void ); + int Draw( float flTime ); + int MsgFunc_SayText( const char *pszName, int iSize, void *pbuf ); + void SayTextPrint( const char *pszBuf, int iBufSize, int clientIndex = -1 ); + void EnsureTextFitsInOneLineAndWrapIfHaveTo( int line ); +friend class CHudSpectator; + +private: + + struct cvar_s * m_HUD_saytext; + struct cvar_s * m_HUD_saytext_time; +}; + +// +//----------------------------------------------------- +// +class CHudBattery: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + int MsgFunc_Battery(const char *pszName, int iSize, void *pbuf ); + +private: + HSPRITE m_hSprite1; + HSPRITE m_hSprite2; + wrect_t *m_prc1; + wrect_t *m_prc2; + int m_iBat; + float m_fFade; + int m_iHeight; // width of the battery innards +}; + +class CHudStamina: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + int MsgFunc_Stamina(const char *pszName, int iSize, void *pbuf ); + +private: + HSPRITE m_hSprite1; + HSPRITE m_hSprite2; + wrect_t *m_prc1; + wrect_t *m_prc2; + int m_iStam; + float m_fFade; + int m_iHeight; +}; + +// +//----------------------------------------------------- +// +class CHudFlashlight: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + void Reset( void ); + int MsgFunc_Flashlight(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_FlashBat(const char *pszName, int iSize, void *pbuf ); + +private: + HSPRITE m_hSprite1; + HSPRITE m_hSprite2; + HSPRITE m_hBeam; + wrect_t *m_prc1; + wrect_t *m_prc2; + wrect_t *m_prcBeam; + float m_flBat; + int m_iBat; + int m_fOn; + float m_fFade; + int m_iWidth; // width of the battery innards +}; + +// +//----------------------------------------------------- +// +const int maxHUDMessages = 16; +struct message_parms_t +{ + client_textmessage_t *pMessage; + float time; + int x, y; + int totalWidth, totalHeight; + int width; + int lines; + int lineLength; + int length; + int r, g, b; + int text; + int fadeBlend; + float charTime; + float fadeTime; +}; + +// +//----------------------------------------------------- +// + +class CHudTextMessage: public CHudBase +{ +public: + int Init( void ); + static char *LocaliseTextString( const char *msg, char *dst_buffer, int buffer_size ); + static char *BufferedLocaliseTextString( const char *msg ); + char *LookupString( const char *msg_name, int *msg_dest = NULL ); + int MsgFunc_TextMsg(const char *pszName, int iSize, void *pbuf); +}; + +// +//----------------------------------------------------- +// + +class CHudMessage: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + int MsgFunc_HudText(const char *pszName, int iSize, void *pbuf); + int MsgFunc_GameTitle(const char *pszName, int iSize, void *pbuf); + + float FadeBlend( float fadein, float fadeout, float hold, float localTime ); + int XPosition( float x, int width, int lineWidth ); + int YPosition( float y, int height ); + + void MessageAdd( const char *pName, float time ); + void MessageAdd(client_textmessage_t * newMessage ); + void MessageDrawScan( client_textmessage_t *pMessage, float time ); + void MessageScanStart( void ); + void MessageScanNextChar( void ); + void Reset( void ); + +private: + client_textmessage_t *m_pMessages[maxHUDMessages]; + float m_startTime[maxHUDMessages]; + message_parms_t m_parms; + float m_gameTitleTime; + client_textmessage_t *m_pGameTitle; + + int m_HUD_title_life; + int m_HUD_title_half; +}; + +// +//----------------------------------------------------- +// +#define MAX_SPRITE_NAME_LENGTH 24 + +class CHudStatusIcons: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + void Reset( void ); + int Draw(float flTime); + int MsgFunc_StatusIcon(const char *pszName, int iSize, void *pbuf); + + enum { + MAX_ICONSPRITENAME_LENGTH = MAX_SPRITE_NAME_LENGTH, + MAX_ICONSPRITES = 4, + }; + + + //had to make these public so CHud could access them (to enable concussion icon) + //could use a friend declaration instead... + void EnableIcon( char *pszIconName, unsigned char red, unsigned char green, unsigned char blue ); + void DisableIcon( char *pszIconName ); + +private: + + typedef struct + { + char szSpriteName[MAX_ICONSPRITENAME_LENGTH]; + HSPRITE spr; + wrect_t rc; + unsigned char r, g, b; + } icon_sprite_t; + + icon_sprite_t m_IconList[MAX_ICONSPRITES]; + +}; + +// +//----------------------------------------------------- +// + +//LRC - for the moment, skymode has only two settings +#define SKY_OFF 0 +#define SKY_ON 1 + +class CHud +{ +private: + HUDLIST *m_pHudList; + int m_iLogo; + client_sprite_t *m_pSpriteList; + int m_iSpriteCount; + int m_iSpriteCountAllRes; + float m_flMouseSensitivity; + int m_iConcussionEffect; + +public: + HSPRITE m_hsprCursor; + HSPRITE m_hsprLogo; // buz: make public + float m_flTime; // the current client time + float m_fOldTime; // the time at which the HUD was last redrawn + double m_flTimeDelta; // the difference between flTime and fOldTime + Vector m_vecOrigin; + Vector m_vecAngles; + + int m_iKeyBits; + int m_iHideHUDDisplay; + int m_iFOV; + float m_flFOV; // buz - that's my FOV! + int m_Teamplay; + int m_iRes; + cvar_t *m_pCvarStealMouse; + cvar_t *m_pCvarDraw; + cvar_t *m_pZoomSpeed; // buz + + cl_entity_t *m_pHeadShieldEnt; + int m_iHeadShieldState; + float m_flHeadShieldSwitchTime; + + int m_iViewModelIndex; + + Vector m_vecSkyPos; //LRC + int m_iSkyMode; //LRC + + // buz: spec tank variables; + int m_SpecTank_on; + Vector m_SpecTank_point; + float m_SpecTank_defYaw; + float m_SpecTank_coneHor; + float m_SpecTank_coneVer; + float m_SpecTank_distFwd; + float m_SpecTank_distUp; + int m_SpecTank_Ammo; + + float m_flBlurAmount; + + // buz: die time + float m_flDeadTime; + + float m_flLevelTime; + + int m_iFontHeight; + int DrawHudNumber(int x, int y, int iFlags, int iNumber, int r, int g, int b ); + int DrawHudString(int x, int y, int iMaxX, char *szString, int r, int g, int b ); + int DrawHudStringReverse( int xpos, int ypos, int iMinX, char *szString, int r, int g, int b ); + int DrawHudNumberString( int xpos, int ypos, int iMinX, int iNumber, int r, int g, int b ); + int GetNumWidth(int iNumber, int iFlags); + + int m_iHUDColor; //LRC + +private: + // the memory for these arrays are allocated in the first call to CHud::VidInit(), when the hud.txt and associated sprites are loaded. + // freed in ~CHud() + HSPRITE *m_rghSprites; /*[HUD_SPRITE_COUNT]*/ // the sprites loaded from hud.txt + wrect_t *m_rgrcRects; /*[HUD_SPRITE_COUNT]*/ + char *m_rgszSpriteNames; /*[HUD_SPRITE_COUNT][MAX_SPRITE_NAME_LENGTH]*/ + + struct cvar_s *default_fov; // buz: turn off +public: + HSPRITE GetSprite( int index ) + { + return (index < 0) ? 0 : m_rghSprites[index]; + } + + wrect_t& GetSpriteRect( int index ) + { + return m_rgrcRects[index]; + } + + + int GetSpriteIndex( const char *SpriteName ); // gets a sprite index, for use in the m_rghSprites[] array + + CHudAmmo m_Ammo; + CHudStamina m_HudStamina; + CHudHealth m_Health; + CHudSpectator m_Spectator; + CHudGeiger m_Geiger; + CHudBattery m_Battery; + CHudTrain m_Train; + CHudFlashlight m_Flash; + CHudMessage m_Message; + CHudStatusBar m_StatusBar; + CHudDeathNotice m_DeathNotice; + CHudSayText m_SayText; + CHudMenu m_Menu; + CHudAmmoSecondary m_AmmoSecondary; + CHudTextMessage m_TextMessage; + CHudStatusIcons m_StatusIcons; + CHudLensflare m_Lensflare; + + + void Init( void ); + void VidInit( void ); + void Think(void); + int Redraw( float flTime, int intermission ); + int UpdateClientData( client_data_t *cdata, float time ); + + CHud() : m_iSpriteCount(0), m_pHudList(NULL) {} + ~CHud(); // destructor, frees allocated memory + + // user messages + int _cdecl MsgFunc_Damage(const char *pszName, int iSize, void *pbuf ); + int _cdecl MsgFunc_GameMode(const char *pszName, int iSize, void *pbuf ); + int _cdecl MsgFunc_Logo(const char *pszName, int iSize, void *pbuf); + int _cdecl MsgFunc_ResetHUD(const char *pszName, int iSize, void *pbuf); + void _cdecl MsgFunc_InitHUD( const char *pszName, int iSize, void *pbuf ); + void _cdecl MsgFunc_ViewMode( const char *pszName, int iSize, void *pbuf ); + int _cdecl MsgFunc_SetFOV(const char *pszName, int iSize, void *pbuf); + int _cdecl MsgFunc_Concuss( const char *pszName, int iSize, void *pbuf ); + int _cdecl MsgFunc_BlurEffect( const char *pszName, int iSize, void *pbuf ); + + int _cdecl MsgFunc_HUDColor(const char *pszName, int iSize, void *pbuf); //LRC + void _cdecl MsgFunc_SetFog( const char *pszName, int iSize, void *pbuf ); //LRC + void _cdecl MsgFunc_KeyedDLight( const char *pszName, int iSize, void *pbuf ); //LRC + void _cdecl MsgFunc_SetSky( const char *pszName, int iSize, void *pbuf ); //LRC + + int _cdecl MsgFunc_GasMask( const char *pszName, int iSize, void *pbuf ); // buz + int _cdecl MsgFunc_SpecTank( const char *pszName, int iSize, void *pbuf ); // buz + int _cdecl MsgFunc_MusicFade( const char *pszName, int iSize, void *pbuf ); + int _cdecl MsgFunc_RainData( const char *pszName, int iSize, void *pbuf ); // buz + int _cdecl MsgFunc_WaterSplash( const char *pszName, int iSize, void *pbuf ); + int _cdecl MsgFunc_NewExplode( const char *pszName, int iSize, void *pbuf ); + int _cdecl MsgFunc_HeadShield( const char *pszName, int iSize, void *pbuf ); + + int _cdecl MsgFunc_Particle( const char *pszName, int iSize, void *pbuf ); + int _cdecl MsgFunc_DelParticle( const char *pszName, int iSize, void *pbuf ); + + // viewmodel messages + int _cdecl MsgFunc_WeaponAnim( const char *pszName, int iSize, void *pbuf ); + int _cdecl MsgFunc_WeaponBody( const char *pszName, int iSize, void *pbuf ); + int _cdecl MsgFunc_WeaponSkin( const char *pszName, int iSize, void *pbuf ); + + // sky messages + int _cdecl MsgFunc_SkyMarker( const char *pszName, int iSize, void *pbuf ); + int _cdecl MsgFunc_WorldMarker( const char *pszName, int iSize, void *pbuf ); + + // decal messages + int _cdecl MsgFunc_CustomDecal( const char *pszName, int iSize, void *pbuf ); + int _cdecl MsgFunc_StudioDecal( const char *pszName, int iSize, void *pbuf ); + + // effect messages + int _cdecl MsgFunc_PartEffect(const char *pszName, int iSize, void *pbuf); + + int _cdecl MsgFunc_LevelTime(const char *pszName, int iSize, void *pbuf); + + // Screen information + SCREENINFO m_scrinfo; + + int m_iWeaponBits; + int m_fPlayerDead; + int m_iIntermission; + + // sprite indexes + int m_HUD_number_0; + + + void AddHudElem(CHudBase *p); + + float GetSensitivity(); +}; + +class TeamFortressViewport; + +extern CHud gHUD; +extern TeamFortressViewport *gViewPort; + +extern int g_iPlayerClass; +extern int g_iTeamNumber; +extern int g_iUser1; +extern int g_iUser2; +extern int g_iUser3; diff --git a/cl_dll/hud_iface.h b/cl_dll/hud_iface.h new file mode 100644 index 0000000..36d4092 --- /dev/null +++ b/cl_dll/hud_iface.h @@ -0,0 +1,18 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( HUD_IFACEH ) +#define HUD_IFACEH +#pragma once + +#define EXPORT _declspec( dllexport ) +#define _DLLEXPORT __declspec( dllexport ) + +#include "../engine/cdll_int.h" +extern cl_enginefunc_t gEngfuncs; + +#endif \ No newline at end of file diff --git a/cl_dll/hud_msg.cpp b/cl_dll/hud_msg.cpp new file mode 100644 index 0000000..7a1e26c --- /dev/null +++ b/cl_dll/hud_msg.cpp @@ -0,0 +1,613 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud_msg.cpp +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" +#include "r_efx.h" +#include "studio.h" +#include "gl_local.h" +#include "gl_studio.h" +#include "stringlib.h" +#include "gl_rpart.h" + +//LRC - the fogging fog +float g_fFogColor[3]; +float g_fStartDist; +float g_fEndDist; +//int g_iFinalStartDist; //for fading +int g_iFinalEndDist; //for fading +float g_fFadeDuration; //negative = fading out +extern engine_studio_api_t IEngineStudio; +extern float v_idlescale; +extern int g_iGunMode; + +#define MAX_CLIENTS 32 + +void EV_HLDM_WaterSplash( float x, float y, float z, float ScaleSplash1, float ScaleSplash2 ); + +int CHud :: MsgFunc_WaterSplash( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + float X, Y, Z, ScaleSplash1, ScaleSplash2; + + X = READ_COORD(); + Y = READ_COORD(); + Z = READ_COORD(); + ScaleSplash1 = READ_COORD(); + ScaleSplash2 = READ_COORD(); + + EV_HLDM_WaterSplash( X, Y, Z, ScaleSplash1, ScaleSplash2 ); + return 1; +} + +void EV_HLDM_NewExplode( float x, float y, float z, float ScaleExplode1 ); + +int CHud :: MsgFunc_NewExplode( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + float X, Y, Z, ScaleExplode1; + + X = READ_COORD(); + Y = READ_COORD(); + Z = READ_COORD(); + ScaleExplode1 = READ_COORD(); + + EV_HLDM_NewExplode( X, Y, Z, ScaleExplode1 ); + return 1; +} + +/// USER-DEFINED SERVER MESSAGE HANDLERS + +int CHud :: MsgFunc_ResetHUD(const char *pszName, int iSize, void *pbuf ) +{ + // clear all hud data + HUDLIST *pList = m_pHudList; + + while ( pList ) + { + if ( pList->p ) + pList->p->Reset(); + pList = pList->pNext; + } + + // reset sensitivity + m_flMouseSensitivity = 0; + + // reset concussion effect + m_iConcussionEffect = 0; + + m_flLevelTime = -1.0f; + + //LRC - reset fog + m_flBlurAmount = 0; + g_fStartDist = 0; + g_fEndDist = 0; + g_iGunMode = 0; + + return 1; +} + +void CAM_ToFirstPerson(void); + +void CHud :: MsgFunc_ViewMode( const char *pszName, int iSize, void *pbuf ) +{ + CAM_ToFirstPerson(); +} + +void CHud :: MsgFunc_InitHUD( const char *pszName, int iSize, void *pbuf ) +{ + m_flLevelTime = -1.0f; + + //LRC - clear the fog + g_fStartDist = 0; + g_fEndDist = 0; + m_iSkyMode = SKY_OFF; //LRC + + // prepare all hud data + HUDLIST *pList = m_pHudList; + + while (pList) + { + if ( pList->p ) + pList->p->InitHUDData(); + pList = pList->pNext; + } + + g_iGunMode = 0; +} + +//LRC +void CHud :: MsgFunc_SetFog( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + for ( int i = 0; i < 3; i++ ) + g_fFogColor[ i ] = READ_BYTE(); + + g_fFadeDuration = READ_SHORT(); + g_fStartDist = READ_SHORT(); + + if (g_fFadeDuration > 0) + { +// // fading in +// g_fStartDist = READ_SHORT(); + g_iFinalEndDist = READ_SHORT(); +// g_fStartDist = FOG_LIMIT; + g_fEndDist = FOG_LIMIT; + } + else if (g_fFadeDuration < 0) + { +// // fading out +// g_iFinalStartDist = + g_iFinalEndDist = g_fEndDist = READ_SHORT(); + } + else + { +// g_fStartDist = READ_SHORT(); + g_fEndDist = READ_SHORT(); + } +} + +//LRC +void CHud :: MsgFunc_KeyedDLight( const char *pszName, int iSize, void *pbuf ) +{ +// CONPRINT("MSG:KeyedDLight"); + BEGIN_READ( pbuf, iSize ); + +// as-yet unused: +// float decay; // drop this each second +// float minlight; // don't add when contributing less +// qboolean dark; // subtracts light instead of adding (doesn't seem to do anything?) + + int iKey = READ_BYTE(); + dlight_t *dl = gEngfuncs.pEfxAPI->CL_AllocDlight( iKey ); + + int bActive = READ_BYTE(); + if (!bActive) + { + // die instantly + dl->die = gEngfuncs.GetClientTime(); + } + else + { + // never die + dl->die = gEngfuncs.GetClientTime() + 1E6; + + dl->origin[0] = READ_COORD(); + dl->origin[1] = READ_COORD(); + dl->origin[2] = READ_COORD(); + dl->radius = READ_BYTE(); + dl->color.r = READ_BYTE(); + dl->color.g = READ_BYTE(); + dl->color.b = READ_BYTE(); + } +} + +//LRC +void CHud :: MsgFunc_SetSky( const char *pszName, int iSize, void *pbuf ) +{ +// CONPRINT("MSG:SetSky"); + BEGIN_READ( pbuf, iSize ); + + m_iSkyMode = READ_BYTE(); + m_vecSkyPos.x = READ_COORD(); + m_vecSkyPos.y = READ_COORD(); + m_vecSkyPos.z = READ_COORD(); +} + +int CHud :: MsgFunc_GameMode(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + m_Teamplay = READ_BYTE(); + + return 1; +} + + +int CHud :: MsgFunc_Damage(const char *pszName, int iSize, void *pbuf ) +{ + int armor, blood; + Vector from; + int i; + float count; + + BEGIN_READ( pbuf, iSize ); + armor = READ_BYTE(); + blood = READ_BYTE(); + + for (i=0 ; i<3 ; i++) + from[i] = READ_COORD(); + + count = (blood * 0.5) + (armor * 0.5); + + if (count < 10) + count = 10; + + // TODO: kick viewangles, show damage visually + + return 1; +} + +int CHud :: MsgFunc_Concuss( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + m_iConcussionEffect = READ_BYTE(); + if (m_iConcussionEffect) + this->m_StatusIcons.EnableIcon("dmg_concuss",255,160,0); + else + this->m_StatusIcons.DisableIcon("dmg_concuss"); + return 1; +} + +// buz: gasmask message +int CHud :: MsgFunc_GasMask( const char *pszName, int iSize, void *pbuf ) +{ + studiohdr_t *pStudioHeader; + mstudioseqdesc_t *pseq; + + BEGIN_READ( pbuf, iSize ); + + gHUD.m_pHeadShieldEnt->model = IEngineStudio.Mod_ForName( "models/v_gasmask.mdl", true ); + if( g_fRenderInitialized && RENDER_GET_PARM( PARM_WIDESCREEN, 0 )) + gHUD.m_pHeadShieldEnt->curstate.fuser2 = 4.2f; // offset + else gHUD.m_pHeadShieldEnt->curstate.fuser2 = 4.8f; // offset + + // 0 is OFF; 1 is ON; 2 is fast switch to ON + switch( READ_BYTE( )) + { + case 0: + m_iHeadShieldState = SHIELD_TURNING_OFF; + m_pHeadShieldEnt->curstate.animtime = gEngfuncs.GetClientTime(); + m_pHeadShieldEnt->curstate.sequence = SHIELDANIM_HOLSTER; + + // get animation length in seconds + pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( gHUD.m_pHeadShieldEnt->model ); + pseq = (mstudioseqdesc_t *)((byte *)pStudioHeader + pStudioHeader->seqindex) + SHIELDANIM_HOLSTER; + m_flHeadShieldSwitchTime = gEngfuncs.GetClientTime() + (pseq->numframes / pseq->fps); + break; + case 1: + m_iHeadShieldState = SHIELD_TURNING_ON; + m_pHeadShieldEnt->curstate.animtime = gEngfuncs.GetClientTime(); + m_pHeadShieldEnt->curstate.sequence = SHIELDANIM_DRAW; + + // get animation length in seconds + pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( gHUD.m_pHeadShieldEnt->model ); + pseq = (mstudioseqdesc_t *)((byte *)pStudioHeader + pStudioHeader->seqindex) + SHIELDANIM_DRAW; + m_flHeadShieldSwitchTime = gEngfuncs.GetClientTime() + (pseq->numframes / pseq->fps); + break; + case 2: + m_iHeadShieldState = SHIELD_ON; + m_pHeadShieldEnt->curstate.animtime = gEngfuncs.GetClientTime(); + m_pHeadShieldEnt->curstate.sequence = SHIELDANIM_IDLE; + } + + return 1; +} + +int CHud::MsgFunc_HeadShield( const char *pszName, int iSize, void *pbuf ) +{ + studiohdr_t *pStudioHeader; + mstudioseqdesc_t *pseq; + + BEGIN_READ( pbuf, iSize ); + + gHUD.m_pHeadShieldEnt->model = IEngineStudio.Mod_ForName( "models/v_headshield.mdl", true ); + + if( g_fRenderInitialized && RENDER_GET_PARM( PARM_WIDESCREEN, 0 )) + gHUD.m_pHeadShieldEnt->curstate.fuser2 = 5.0f; // offset + else gHUD.m_pHeadShieldEnt->curstate.fuser2 = 16.0f; // offset + + // 0 is OFF; 1 is ON; 2 is fast switch to ON + switch( READ_BYTE( )) + { + case 0: + m_iHeadShieldState = SHIELD_TURNING_OFF; + m_pHeadShieldEnt->curstate.animtime = gEngfuncs.GetClientTime(); + m_pHeadShieldEnt->curstate.sequence = SHIELDANIM_HOLSTER; + + // get animation length in seconds + pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( gHUD.m_pHeadShieldEnt->model ); + pseq = (mstudioseqdesc_t *)((byte *)pStudioHeader + pStudioHeader->seqindex) + SHIELDANIM_HOLSTER; + m_flHeadShieldSwitchTime = gEngfuncs.GetClientTime() + (pseq->numframes / pseq->fps); + break; + case 1: + m_iHeadShieldState = SHIELD_TURNING_ON; + m_pHeadShieldEnt->curstate.animtime = gEngfuncs.GetClientTime(); + m_pHeadShieldEnt->curstate.sequence = SHIELDANIM_DRAW; + + // get animation length in seconds + pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( gHUD.m_pHeadShieldEnt->model ); + pseq = (mstudioseqdesc_t *)((byte *)pStudioHeader + pStudioHeader->seqindex) + SHIELDANIM_DRAW; + m_flHeadShieldSwitchTime = gEngfuncs.GetClientTime() + (pseq->numframes / pseq->fps); + break; + case 2: + m_iHeadShieldState = SHIELD_ON; + m_pHeadShieldEnt->curstate.animtime = gEngfuncs.GetClientTime(); + m_pHeadShieldEnt->curstate.sequence = SHIELDANIM_IDLE; + } + + return 1; +} + +// buz: special tank message +int CHud :: MsgFunc_SpecTank( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_SpecTank_on = READ_BYTE(); + if (m_SpecTank_on == 0) // turn off + return 1; + else if (m_SpecTank_on == 2) // only ammo update + { + m_SpecTank_Ammo = READ_LONG(); + m_Ammo.m_fFade = 200.0f; + } + else // turn on + { + m_SpecTank_point.x = READ_COORD(); + m_SpecTank_point.y = READ_COORD(); + m_SpecTank_point.z = READ_COORD(); + m_SpecTank_defYaw = READ_COORD(); + m_SpecTank_coneHor = READ_COORD(); + m_SpecTank_coneVer = READ_COORD(); + m_SpecTank_distFwd = READ_COORD(); + m_SpecTank_distUp = READ_COORD(); + m_SpecTank_Ammo = READ_LONG(); + } + return 1; +} + +int CHud :: MsgFunc_MusicFade( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + MUSIC_FADE_VOLUME( (float)READ_SHORT() / 100.0f ); + + return 1; +} + +int CHud :: MsgFunc_Particle( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + int entindex = READ_SHORT(); + char *sz = READ_STRING(); + int attachment = READ_BYTE(); + + UTIL_CreateAurora( GET_ENTITY( entindex ), sz, attachment ); + + return 1; +} + +int CHud :: MsgFunc_DelParticle( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + int entindex = READ_SHORT(); + + UTIL_RemoveAurora( GET_ENTITY( entindex )); + + return 1; +} + +int CHud :: MsgFunc_RainData( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + ParseRain(); + + return 1; +} + +int CHud :: MsgFunc_WeaponAnim( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int sequence = READ_BYTE(); + float framerate = READ_BYTE() * 0.125f; + + UTIL_WeaponAnimation( sequence, framerate ); + + return 1; +} + +int CHud :: MsgFunc_WeaponBody( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + GET_VIEWMODEL()->curstate.body = READ_BYTE(); + + return 1; +} + +int CHud :: MsgFunc_WeaponSkin( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + GET_VIEWMODEL()->curstate.skin = READ_BYTE(); + + return 1; +} + +// 3d skybox +int CHud :: MsgFunc_SkyMarker( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + tr.sky_origin.x = READ_COORD(); + tr.sky_origin.y = READ_COORD(); + tr.sky_origin.z = READ_COORD(); + + return 1; +} + +// 3d skybox +int CHud :: MsgFunc_WorldMarker( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + tr.sky_world_origin.x = READ_COORD(); + tr.sky_world_origin.y = READ_COORD(); + tr.sky_world_origin.z = READ_COORD(); + tr.sky_speed = READ_COORD(); +Msg( "sky_speed: %g\n", tr.sky_speed ); + return 1; +} + +int CHud :: MsgFunc_CustomDecal( const char *pszName, int iSize, void *pbuf ) +{ + char name[80]; + + BEGIN_READ( pbuf, iSize ); + + Vector pos, normal; + pos.x = READ_COORD(); + pos.y = READ_COORD(); + pos.z = READ_COORD(); + normal.x = READ_COORD() / 8192.0f; + normal.y = READ_COORD() / 8192.0f; + normal.z = READ_COORD() / 8192.0f; + int entityIndex = READ_SHORT(); + int modelIndex = READ_SHORT(); + Q_strncpy( name, READ_STRING(), sizeof( name )); + int flags = READ_BYTE(); + float angle = READ_ANGLE(); + + CreateDecal( pos, normal, angle, name, flags, entityIndex, modelIndex ); + + return 1; +} + +int CHud :: MsgFunc_StudioDecal( const char *pszName, int iSize, void *pbuf ) +{ + Vector vecEnd, vecNormal, vecScale = g_vecZero; + char name[80]; + + BEGIN_READ( pbuf, iSize ); + + vecEnd.x = READ_COORD(); + vecEnd.y = READ_COORD(); + vecEnd.z = READ_COORD(); + vecNormal.x = READ_COORD() * 0.001f; + vecNormal.y = READ_COORD() * 0.001f; + vecNormal.z = READ_COORD() * 0.001f; + int entityIndex = READ_SHORT(); + int modelIndex = READ_SHORT(); + Q_strncpy( name, READ_STRING(), sizeof( name )); + int flags = READ_BYTE(); + + modelstate_t state; + state.sequence = READ_SHORT(); + state.frame = READ_SHORT(); + state.blending[0] = READ_BYTE(); + state.blending[1] = READ_BYTE(); + state.controller[0] = READ_BYTE(); + state.controller[1] = READ_BYTE(); + state.controller[2] = READ_BYTE(); + state.controller[3] = READ_BYTE(); + state.body = READ_BYTE(); + state.skin = READ_BYTE(); + int cacheID = READ_SHORT(); + + if( REMAIN_BYTES( )) + { + vecScale.x = READ_COORD() * 0.001f; + vecScale.y = READ_COORD() * 0.001f; + vecScale.z = READ_COORD() * 0.001f; + } + + cl_entity_t *ent = GET_ENTITY( entityIndex ); + + if( !ent ) + { + // something very bad happens... + ALERT( at_error, "StudioDecal: ent == NULL\n" ); + return 1; + } + + g_StudioRenderer.PushEntityState( ent ); + g_StudioRenderer.ModelStateToEntity( ent, &state ); + + // restore model in case decalmessage was delivered early than delta-update + if( !ent->model && modelIndex != 0 ) + ent->model = IEngineStudio.GetModelByIndex( modelIndex ); + + if( cacheID ) + { + // tell the code about vertex lighting + SetBits( ent->curstate.iuser1, CF_STATIC_ENTITY ); + ent->curstate.colormap = cacheID; + } + + if( !RENDER_GET_PARM( PARM_CLIENT_ACTIVE, 0 ) && FBitSet( ent->curstate.iuser1, CF_STATIC_ENTITY )) + ent->curstate.startpos = vecScale; // restore scale here + + if( !ent->model || ent->model->type != mod_studio ) + return 1; + + g_StudioRenderer.StudioDecalShoot( vecNormal, vecEnd, name, ent, flags, &state ); + g_StudioRenderer.PopEntityState( ent ); + + return 1; +} + +int CHud :: MsgFunc_PartEffect( const char *pszName, int iSize, void *pbuf ) +{ + char name[80]; + + BEGIN_READ( pbuf, iSize ); + + Vector pos, normal; + pos.x = READ_COORD(); + pos.y = READ_COORD(); + pos.z = READ_COORD(); + normal.x = READ_COORD() / 8192.0f; + normal.y = READ_COORD() / 8192.0f; + normal.z = READ_COORD() / 8192.0f; + Q_strncpy( name, READ_STRING(), sizeof( name )); + + g_pParticles.CreateEffect( name, pos, normal ); + + return 1; +} + +int CHud :: MsgFunc_LevelTime( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_flLevelTime = READ_FLOAT(); + + return 1; +} + +int CHud :: MsgFunc_BlurEffect( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_flBlurAmount = (float)READ_SHORT() / 10000.0f; + + v_idlescale = m_flBlurAmount * 100.0f; + + // reset blur on a next level + if( !RENDER_GET_PARM( PARM_CLIENT_ACTIVE, 0 )) + { + m_flBlurAmount = 0.0f; + v_idlescale = 0.0f; + } + + return 1; +} \ No newline at end of file diff --git a/cl_dll/hud_redraw.cpp b/cl_dll/hud_redraw.cpp new file mode 100644 index 0000000..62b0206 --- /dev/null +++ b/cl_dll/hud_redraw.cpp @@ -0,0 +1,521 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud_redraw.cpp +// +#include +#include "hud.h" +#include "cl_util.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_hud.h" +#include "triangleapi.h" + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +// buz: for IsHardware check +#include "r_studioint.h" +#include "com_model.h" +extern engine_studio_api_t IEngineStudio; + +#include "vgui_TeamFortressViewport.h" +#include "vgui_paranoiatext.h"// buz +#include "gl_local.h" +#include "gl_decals.h" + +#define MAX_LOGO_FRAMES 56 + +// buz: spread value +vec3_t g_vSpread; +int g_iGunMode = 0; + +void DrawBlurTest(float frametime); // buz +extern int g_iVisibleMouse; + +//float HUD_GetFOV( void ); buz + +extern cvar_t *sensitivity; + +// Think +void CHud::Think(void) +{ + // buz: calc dead time + if (gHUD.m_Health.m_iHealth <= 0 && !m_flDeadTime) + { + m_flDeadTime = gEngfuncs.GetClientTime(); + } + + // Wargon: Èñïðàâëåíèå áàãà ýôôåêòà grayscale, îñòàâàâøåãîñÿ ïîñëå çàãðóçêè èç ìåðòâîãî ñîñòîÿíèÿ. + if (gHUD.m_Health.m_iHealth > 0) + { + m_flDeadTime = 0; + } + + float targetFOV; + HUDLIST *pList = m_pHudList; + static float lasttime = 0; + + while (pList) + { + if (pList->p->m_iFlags & HUD_ACTIVE) + pList->p->Think(); + pList = pList->pNext; + } + + if( g_iGunMode == 3 ) + targetFOV = 30; + else if( g_iGunMode == 2 ) + targetFOV = 60; + else targetFOV = default_fov->value; + + static float lastFixedFov = 0; + + if( m_flFOV < 0 ) + { + m_flFOV = targetFOV; + lasttime = gEngfuncs.GetClientTime(); + lastFixedFov = m_flFOV; + } + else + { + float curtime = gEngfuncs.GetClientTime(); + float mod = targetFOV - m_flFOV; + if( mod < 0 ) mod *= -1; + if( mod < 30 ) mod = 30; + + if( g_iGunMode == 3 || lastFixedFov == 30 ) + mod *= 2; // õàêàìè õàëôà ïîëíèòñÿ (c) + mod /= 30; + + if( m_flFOV < targetFOV ) + { + m_flFOV += (curtime - lasttime) * m_pZoomSpeed->value * mod; + if( m_flFOV > targetFOV ) + { + m_flFOV = targetFOV; + lastFixedFov = m_flFOV; + } + } + else if( m_flFOV > targetFOV ) + { + m_flFOV -= (curtime - lasttime) * m_pZoomSpeed->value * mod; + + if( m_flFOV < targetFOV ) + { + m_flFOV = targetFOV; + lastFixedFov = m_flFOV; + } + } + lasttime = curtime; + } + + m_iFOV = m_flFOV; + + // Set a new sensitivity + if ( m_iFOV == default_fov->value )// buz: turn off + { + // reset to saved sensitivity + m_flMouseSensitivity = 0; + } + else + { + // set a new sensitivity that is proportional to the change from the FOV default + m_flMouseSensitivity = sensitivity->value * (m_flFOV / (float)90) * CVAR_GET_FLOAT("zoom_sensitivity_ratio"); + } +} + +//LRC - fog fading values +extern float g_fFadeDuration; +extern float g_fStartDist; +extern float g_fEndDist; +//extern int g_iFinalStartDist; +extern int g_iFinalEndDist; + +void OrthoScope(void); // buz +void OrthoVGUI(void); // buz +extern vec3_t g_CrosshairAngle; // buz + +#define CROSS_LENGTH 18.0f + +// Redraw +// step through the local data, placing the appropriate graphics & text as appropriate +// returns 1 if they've changed, 0 otherwise +int CHud :: Redraw( float flTime, int intermission ) +{ + m_fOldTime = m_flTime; // save time of previous redraw + m_flTime = flTime; + m_flTimeDelta = (double)m_flTime - m_fOldTime; + static m_flShotTime = 0; + +#if 0 + // g-cont. disabled for users request + if( g_fRenderInitialized ) + DrawBlurTest( m_flTimeDelta ); +#endif + //LRC - handle fog fading effects. (is this the right place for it?) + if (g_fFadeDuration) + { + // Nicer might be to use some kind of logarithmic fade-in? + double fFraction = m_flTimeDelta/g_fFadeDuration; +// g_fStartDist -= (FOG_LIMIT - g_iFinalStartDist)*fFraction; + g_fEndDist -= (FOG_LIMIT - g_iFinalEndDist)*fFraction; + +// CONPRINT("FogFading: %f - %f, frac %f, time %f, final %d\n", g_fStartDist, g_fEndDist, fFraction, flTime, g_iFinalEndDist); + + // cap it +// if (g_fStartDist > FOG_LIMIT) g_fStartDist = FOG_LIMIT; + if (g_fEndDist > FOG_LIMIT) g_fEndDist = FOG_LIMIT; +// if (g_fStartDist < g_iFinalStartDist) g_fStartDist = g_iFinalStartDist; + if (g_fEndDist < g_iFinalEndDist) g_fEndDist = g_iFinalEndDist; + } + + // Clock was reset, reset delta + if ( m_flTimeDelta < 0 ) + m_flTimeDelta = 0; + + if (g_iGunMode == 3) // buz: special sniper scope + { + if (IEngineStudio.IsHardware()) + OrthoScope(); + } + + if (!IEngineStudio.IsHardware() && m_iHeadShieldState != 1 ) + DrawHudString(XRES(10), YRES(350), XRES(600), "Using Head Shield", 180, 180, 180); + + if( r_stats.debug_surface != NULL ) + gEngfuncs.pfnDrawCharacter( XRES( 320 ), YRES( 240 ), '+', 128, 255, 92 ); + + OrthoVGUI(); // buz: panels background + + // Bring up the scoreboard during intermission + if (gViewPort) + { + if ( m_iIntermission && !intermission ) + { + // Have to do this here so the scoreboard goes away + m_iIntermission = intermission; + gViewPort->HideCommandMenu(); + gViewPort->HideScoreBoard(); +// gViewPort->UpdateSpectatorPanel(); + } + else if ( !m_iIntermission && intermission ) + { + m_iIntermission = intermission; + gViewPort->HideCommandMenu(); + gViewPort->HideVGUIMenu(); + gViewPort->ShowScoreBoard(); +// gViewPort->UpdateSpectatorPanel(); + + // Take a screenshot if the client's got the cvar set + if ( CVAR_GET_FLOAT( "hud_takesshots" ) != 0 ) + m_flShotTime = flTime + 1.0; // Take a screenshot in a second + } + } + + if (m_flShotTime && m_flShotTime < flTime) + { + gEngfuncs.pfnClientCmd("snapshot\n"); + m_flShotTime = 0; + } + + m_iIntermission = intermission; + + // if no redrawing is necessary + // return 0; + + if ( m_pCvarDraw->value ) + { + HUDLIST *pList = m_pHudList; + + while (pList) + { + if ( !intermission ) + { + if ( (pList->p->m_iFlags & HUD_ACTIVE) && !(m_iHideHUDDisplay & HIDEHUD_ALL) ) + pList->p->Draw(flTime); + } + else + { // it's an intermission, so only draw hud elements that are set to draw during intermissions + if ( pList->p->m_iFlags & HUD_INTERMISSION ) + pList->p->Draw( flTime ); + } + + pList = pList->pNext; + } + } + + // buz: draw crosshair + // Wargon: Ïðèöåë ðèñóåòñÿ òîëüêî åñëè hud_draw = 1. + if(( g_vSpread[0] && g_iGunMode == 1 && m_SpecTank_on == 0 ) && gHUD.m_pCvarDraw->value && cv_crosshair->value ) + { + if( gViewPort && gViewPort->m_pParanoiaText && gViewPort->m_pParanoiaText->isVisible( )) + return 1; + + int barsize = XRES(g_iGunMode == 1 ? 9 : 6); + int hW = ScreenWidth / 2; + int hH = ScreenHeight / 2; + float mod = (1/(tan(M_PI/180*(m_iFOV/2)))); + int dir = ((g_vSpread[0] * hW) / 500) * mod; + // gEngfuncs.Con_Printf("mod is %f, %d\n", mod, m_iFOV); + + if (g_CrosshairAngle[0] != 0 || g_CrosshairAngle[1] != 0) + { + // adjust for autoaim + hW -= g_CrosshairAngle[1] * (ScreenWidth / m_iFOV); + hH -= g_CrosshairAngle[0] * (ScreenWidth / m_iFOV); + } + + // g_vSpread[2] - is redish [0..500] + // gEngfuncs.Con_Printf("received spread: %f\n", g_vSpread[2]); + int c = 255 - (g_vSpread[2] * 0.5); + + if( cv_crosshair->value > 1.0f ) + { + // old Paranoia-style crosshair + FillRGBA(hW - dir - barsize, hH, barsize, 1, 255, c, c, 200); + FillRGBA(hW + dir, hH, barsize, 1, 255, c, c, 200); + FillRGBA(hW, hH - dir - barsize, 1, barsize, 255, c, c, 200); + FillRGBA(hW, hH + dir, 1, barsize, 255, c, c, 200); + } + else + { + // new crysis-style crosshair + float flColor = 255.0f - (g_vSpread[2] * 0.5f); + flColor = bound( 0.0f, flColor * (1.0f / 255.0f), 1.0f ); + + GL_TextureTarget( GL_NONE ); + + gEngfuncs.pTriAPI->RenderMode( kRenderTransColor ); + gEngfuncs.pTriAPI->Color4f( 1.0f, flColor, flColor, 0.8f ); + + gEngfuncs.pTriAPI->Begin( TRI_LINES ); + gEngfuncs.pTriAPI->Vertex3f( hW, hH - dir, 0.0f ); + gEngfuncs.pTriAPI->Vertex3f( hW, hH - ( CROSS_LENGTH * 1.2f ) - dir, 0.0f ); + gEngfuncs.pTriAPI->End(); + + gEngfuncs.pTriAPI->Begin( TRI_LINES ); + gEngfuncs.pTriAPI->Vertex3f( hW + dir, hH + dir, 0.0f ); + gEngfuncs.pTriAPI->Vertex3f( hW + CROSS_LENGTH + dir, hH + CROSS_LENGTH + dir, 0.0f ); + gEngfuncs.pTriAPI->End(); + + gEngfuncs.pTriAPI->Begin( TRI_LINES ); + gEngfuncs.pTriAPI->Vertex3f( hW - dir, hH + dir, 0.0f ); + gEngfuncs.pTriAPI->Vertex3f( hW - CROSS_LENGTH - dir, hH + CROSS_LENGTH + dir, 0.0f ); + gEngfuncs.pTriAPI->End(); + + GL_TextureTarget( GL_TEXTURE_2D ); + } + } + + return 1; +} + +void ScaleColors( int &r, int &g, int &b, int a ) +{ + float x = (float)a / 255; + r = (int)(r * x); + g = (int)(g * x); + b = (int)(b * x); +} + +int CHud :: DrawHudString(int xpos, int ypos, int iMaxX, char *szIt, int r, int g, int b ) +{ + // draw the string until we hit the null character or a newline character + for ( ; *szIt != 0 && *szIt != '\n'; szIt++ ) + { + int next = xpos + gHUD.m_scrinfo.charWidths[ *szIt ]; // variable-width fonts look cool + if ( next > iMaxX ) + return xpos; + + TextMessageDrawChar( xpos, ypos, *szIt, r, g, b ); + xpos = next; + } + + return xpos; +} + +int CHud :: DrawHudNumberString( int xpos, int ypos, int iMinX, int iNumber, int r, int g, int b ) +{ + char szString[32]; + sprintf( szString, "%d", iNumber ); + return DrawHudStringReverse( xpos, ypos, iMinX, szString, r, g, b ); + +} + +// draws a string from right to left (right-aligned) +int CHud :: DrawHudStringReverse( int xpos, int ypos, int iMinX, char *szString, int r, int g, int b ) +{ + // find the end of the string + for ( char *szIt = szString; *szIt != 0; szIt++ ) + { // we should count the length? + } + + // iterate throug the string in reverse + for ( szIt--; szIt != (szString-1); szIt-- ) + { + int next = xpos - gHUD.m_scrinfo.charWidths[ *szIt ]; // variable-width fonts look cool + if ( next < iMinX ) + return xpos; + xpos = next; + + TextMessageDrawChar( xpos, ypos, *szIt, r, g, b ); + } + + return xpos; +} + +int CHud :: DrawHudNumber( int x, int y, int iFlags, int iNumber, int r, int g, int b) +{ + int iWidth = GetSpriteRect(m_HUD_number_0).right - GetSpriteRect(m_HUD_number_0).left; + int k; + + if (iNumber > 0) + { + // SPR_Draw 100's + if (iNumber >= 100) + { + k = iNumber/100; + SPR_Set(GetSprite(m_HUD_number_0 + k), r, g, b ); + SPR_DrawAdditive( 0, x, y, &GetSpriteRect(m_HUD_number_0 + k)); + x += iWidth; + } + else if (iFlags & (DHN_3DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + // SPR_Draw 10's + if (iNumber >= 10) + { + k = (iNumber % 100)/10; + SPR_Set(GetSprite(m_HUD_number_0 + k), r, g, b ); + SPR_DrawAdditive( 0, x, y, &GetSpriteRect(m_HUD_number_0 + k)); + x += iWidth; + } + else if (iFlags & (DHN_3DIGITS | DHN_2DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + // SPR_Draw ones + k = iNumber % 10; + SPR_Set(GetSprite(m_HUD_number_0 + k), r, g, b ); + SPR_DrawAdditive(0, x, y, &GetSpriteRect(m_HUD_number_0 + k)); + x += iWidth; + } + else if (iFlags & DHN_DRAWZERO) + { + SPR_Set(GetSprite(m_HUD_number_0), r, g, b ); + + // SPR_Draw 100's + if (iFlags & (DHN_3DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + if (iFlags & (DHN_3DIGITS | DHN_2DIGITS)) + { + //SPR_DrawAdditive( 0, x, y, &rc ); + x += iWidth; + } + + // SPR_Draw ones + + SPR_DrawAdditive( 0, x, y, &GetSpriteRect(m_HUD_number_0)); + x += iWidth; + } + + return x; +} + + +int CHud::GetNumWidth( int iNumber, int iFlags ) +{ + if (iFlags & (DHN_3DIGITS)) + return 3; + + if (iFlags & (DHN_2DIGITS)) + return 2; + + if (iNumber <= 0) + { + if (iFlags & (DHN_DRAWZERO)) + return 1; + else + return 0; + } + + if (iNumber < 10) + return 1; + + if (iNumber < 100) + return 2; + + return 3; + +} + +// buz +void DrawBlurTest( float frametime ) +{ + static int blurstate = false; + + if( gHUD.m_flBlurAmount > 0.0f && frametime ) + { + if( !g_fRenderInitialized ) + { + ALERT( at_error, "GL effects are not allowed\n" ); + gHUD.m_flBlurAmount = 0.0f; + return; + } + + GLint val_r, val_g, val_b; + pglGetIntegerv( GL_ACCUM_RED_BITS, &val_r ); + pglGetIntegerv( GL_ACCUM_GREEN_BITS, &val_g ); + pglGetIntegerv( GL_ACCUM_BLUE_BITS, &val_b ); + + if( !val_r || !val_g || !val_b ) + { + ALERT( at_error, "Accumulation buffer is not present\n" ); + gHUD.m_flBlurAmount = 0.0f; + return; + } + + if( !blurstate ) + { + // load entire screen first time + pglAccum( GL_LOAD, 1.0f ); + } + else + { + float blur = ( 51.0f - ( gHUD.m_flBlurAmount * 50.0f )); + blur = bound( 0.0f, blur * frametime, 1.0f ); + pglReadBuffer( GL_BACK ); + pglAccum( GL_MULT, 1.0f - blur ); // scale contents of accumulation buffer + pglAccum( GL_ACCUM, blur ); // add screen contents + pglAccum( GL_RETURN, 1 ); // read result back + } + + blurstate = true; + } + else + { + blurstate = false; + } +} \ No newline at end of file diff --git a/cl_dll/hud_servers.cpp b/cl_dll/hud_servers.cpp new file mode 100644 index 0000000..8bd42d3 --- /dev/null +++ b/cl_dll/hud_servers.cpp @@ -0,0 +1,1230 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// hud_servers.cpp +#include "hud.h" +#include "cl_util.h" +#include "hud_servers_priv.h" +#include "hud_servers.h" +#include "net_api.h" +#include +#include + +static int context_id; + +// Default master server address in case we can't read any from woncomm.lst file +#define VALVE_MASTER_ADDRESS "half-life.east.won.net" +#define PORT_MASTER 27010 +#define PORT_SERVER 27015 + +// File where we really should look for master servers +#define MASTER_PARSE_FILE "woncomm.lst" + +#define MAX_QUERIES 20 + +#define NET_API gEngfuncs.pNetAPI + +static CHudServers *g_pServers = NULL; + +/* +=================== +ListResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK ListResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->ListResponse( response ); + } +} + +/* +=================== +ServerResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK ServerResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->ServerResponse( response ); + } +} + +/* +=================== +PingResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK PingResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->PingResponse( response ); + } +} + +/* +=================== +RulesResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK RulesResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->RulesResponse( response ); + } +} +/* +=================== +PlayersResponse + +Callback from engine +=================== +*/ +void NET_CALLBACK PlayersResponse( struct net_response_s *response ) +{ + if ( g_pServers ) + { + g_pServers->PlayersResponse( response ); + } +} +/* +=================== +ListResponse + +=================== +*/ +void CHudServers::ListResponse( struct net_response_s *response ) +{ + request_t *list; + request_t *p; + int c = 0; + + if ( !( response->error == NET_SUCCESS ) ) + return; + + if ( response->type != NETAPI_REQUEST_SERVERLIST ) + return; + + if ( response->response ) + { + list = ( request_t * ) response->response; + while ( list ) + { + c++; + + //if ( c < 40 ) + { + // Copy from parsed stuff + p = new request_t; + p->context = -1; + p->remote_address = list->remote_address; + p->next = m_pServerList; + m_pServerList = p; + } + + // Move on + list = list->next; + } + } + + gEngfuncs.Con_Printf( "got list\n" ); + + m_nQuerying = 1; + m_nActiveQueries = 0; +} + +/* +=================== +ServerResponse + +=================== +*/ +void CHudServers::ServerResponse( struct net_response_s *response ) +{ + char *szresponse; + request_t *p; + server_t *browser; + int len; + char sz[ 32 ]; + + // Remove from active list + p = FindRequest( response->context, m_pActiveList ); + if ( p ) + { + RemoveServerFromList( &m_pActiveList, p ); + m_nActiveQueries--; + } + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_DETAILS: + if ( response->response ) + { + szresponse = (char *)response->response; + len = strlen( szresponse ) + 100 + 1; + sprintf( sz, "%i", (int)( 1000.0 * response->ping ) ); + + browser = new server_t; + browser->remote_address = response->remote_address; + browser->info = new char[ len ]; + browser->ping = (int)( 1000.0 * response->ping ); + strcpy( browser->info, szresponse ); + + NET_API->SetValueForKey( browser->info, "address", gEngfuncs.pNetAPI->AdrToString( &response->remote_address ), len ); + NET_API->SetValueForKey( browser->info, "ping", sz, len ); + + AddServer( &m_pServers, browser ); + } + break; + default: + break; + } +} + +/* +=================== +PingResponse + +=================== +*/ +void CHudServers::PingResponse( struct net_response_s *response ) +{ + char sz[ 32 ]; + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_PING: + sprintf( sz, "%.2f", 1000.0 * response->ping ); + + gEngfuncs.Con_Printf( "ping == %s\n", sz ); + break; + default: + break; + } +} + +/* +=================== +RulesResponse + +=================== +*/ +void CHudServers::RulesResponse( struct net_response_s *response ) +{ + char *szresponse; + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_RULES: + if ( response->response ) + { + szresponse = (char *)response->response; + + gEngfuncs.Con_Printf( "rules %s\n", szresponse ); + } + break; + default: + break; + } +} + +/* +=================== +PlayersResponse + +=================== +*/ +void CHudServers::PlayersResponse( struct net_response_s *response ) +{ + char *szresponse; + + if ( response->error != NET_SUCCESS ) + return; + + switch ( response->type ) + { + case NETAPI_REQUEST_PLAYERS: + if ( response->response ) + { + szresponse = (char *)response->response; + + gEngfuncs.Con_Printf( "players %s\n", szresponse ); + } + break; + default: + break; + } +} + +/* +=================== +CompareServers + +Return 1 if p1 is "less than" p2, 0 otherwise +=================== +*/ +int CHudServers::CompareServers( server_t *p1, server_t *p2 ) +{ + const char *n1, *n2; + + if ( p1->ping < p2->ping ) + return 1; + + if ( p1->ping == p2->ping ) + { + // Pings equal, sort by second key: hostname + if ( p1->info && p2->info ) + { + n1 = NET_API->ValueForKey( p1->info, "hostname" ); + n2 = NET_API->ValueForKey( p2->info, "hostname" ); + + if ( n1 && n2 ) + { + if ( stricmp( n1, n2 ) < 0 ) + return 1; + } + } + } + + return 0; +} + +/* +=================== +AddServer + +=================== +*/ +void CHudServers::AddServer( server_t **ppList, server_t *p ) +{ +server_t *list; + + if ( !ppList || ! p ) + return; + + m_nServerCount++; + + // What sort key? Ping? + list = *ppList; + + // Head of list? + if ( !list ) + { + p->next = NULL; + *ppList = p; + return; + } + + // Put on head of list + if ( CompareServers( p, list ) ) + { + p->next = *ppList; + *ppList = p; + } + else + { + while ( list->next ) + { + // Insert before list next + if ( CompareServers( p, list->next ) ) + { + p->next = list->next->next; + list->next = p; + return; + } + + list = list->next; + } + + // Just add at end + p->next = NULL; + list->next = p; + } +} + +/* +=================== +Think + +=================== +*/ +void CHudServers::Think( double time ) +{ + m_fElapsed += time; + + if ( !m_nRequesting ) + return; + + if ( !m_nQuerying ) + return; + + QueryThink(); + + if ( ServerListSize() > 0 ) + return; + + m_dStarted = 0.0; + m_nRequesting = 0; + m_nDone = 0; + m_nQuerying = 0; + m_nActiveQueries = 0; +} + +/* +=================== +QueryThink + +=================== +*/ +void CHudServers::QueryThink( void ) +{ + request_t *p; + + if ( !m_nRequesting || m_nDone ) + return; + + if ( !m_nQuerying ) + return; + + if ( m_nActiveQueries > MAX_QUERIES ) + return; + + // Nothing left + if ( !m_pServerList ) + return; + + while ( 1 ) + { + p = m_pServerList; + + // No more in list? + if ( !p ) + break; + + // Move to next + m_pServerList = m_pServerList->next; + + // Setup context_id + p->context = context_id; + + // Start up query on this one + NET_API->SendRequest( context_id++, NETAPI_REQUEST_DETAILS, 0, 2.0, &p->remote_address, ::ServerResponse ); + + // Increment active list + m_nActiveQueries++; + + // Add to active list + p->next = m_pActiveList; + m_pActiveList = p; + + // Too many active? + if ( m_nActiveQueries > MAX_QUERIES ) + break; + } +} + +/* +================== +ServerListSize + +# of servers in active query and in pending to be queried lists +================== +*/ +int CHudServers::ServerListSize( void ) +{ + int c = 0; + request_t *p; + + p = m_pServerList; + while ( p ) + { + c++; + p = p->next; + } + + p = m_pActiveList; + while ( p ) + { + c++; + p = p->next; + } + + return c; +} + +/* +=================== +FindRequest + +Look up a request by context id +=================== +*/ +CHudServers::request_t *CHudServers::FindRequest( int context, request_t *pList ) +{ + request_t *p; + p = pList; + while ( p ) + { + if ( context == p->context ) + return p; + + p = p->next; + } + return NULL; +} + +/* +=================== +RemoveServerFromList + +Remote, but don't delete, item from *ppList +=================== +*/ +void CHudServers::RemoveServerFromList( request_t **ppList, request_t *item ) +{ + request_t *p, *n; + request_t *newlist = NULL; + + if ( !ppList ) + return; + + p = *ppList; + while ( p ) + { + n = p->next; + if ( p != item ) + { + p->next = newlist; + newlist = p; + } + p = n; + } + *ppList = newlist; +} + +/* +=================== +ClearRequestList + +=================== +*/ +void CHudServers::ClearRequestList( request_t **ppList ) +{ + request_t *p, *n; + + if ( !ppList ) + return; + + p = *ppList; + while ( p ) + { + n = p->next; + delete p; + p = n; + } + *ppList = NULL; +} + +/* +=================== +ClearServerList + +=================== +*/ +void CHudServers::ClearServerList( server_t **ppList ) +{ + server_t *p, *n; + + if ( !ppList ) + return; + + p = *ppList; + while ( p ) + { + n = p->next; + delete[] p->info; + delete p; + p = n; + } + *ppList = NULL; +} + +int CompareField( CHudServers::server_t *p1, CHudServers::server_t *p2, const char *fieldname, int iSortOrder ) +{ + const char *sz1, *sz2; + float fv1, fv2; + + sz1 = NET_API->ValueForKey( p1->info, fieldname ); + sz2 = NET_API->ValueForKey( p2->info, fieldname ); + + fv1 = atof( sz1 ); + fv2 = atof( sz2 ); + + if ( fv1 && fv2 ) + { + if ( fv1 > fv2 ) + return iSortOrder; + else if ( fv1 < fv2 ) + return -iSortOrder; + else + return 0; + } + + // String compare + return stricmp( sz1, sz2 ); +} + +int CALLBACK ServerListCompareFunc( CHudServers::server_t *p1, CHudServers::server_t *p2, const char *fieldname ) +{ + if (!p1 || !p2) // No meaningful comparison + return 0; + + int iSortOrder = 1; + + int retval = 0; + + retval = CompareField( p1, p2, fieldname, iSortOrder ); + + return retval; +} + +static char g_fieldname[ 256 ]; +int __cdecl FnServerCompare(const void *elem1, const void *elem2 ) +{ + CHudServers::server_t *list1, *list2; + + list1 = *(CHudServers::server_t **)elem1; + list2 = *(CHudServers::server_t **)elem2; + + return ServerListCompareFunc( list1, list2, g_fieldname ); +} + +void CHudServers::SortServers( const char *fieldname ) +{ + server_t *p; + // Create a list + if ( !m_pServers ) + return; + + strcpy( g_fieldname, fieldname ); + + int i; + int c = 0; + + p = m_pServers; + while ( p ) + { + c++; + p = p->next; + } + + server_t **pSortArray; + + pSortArray = new server_t *[ c ]; + memset( pSortArray, 0, c * sizeof( server_t * ) ); + + // Now copy the list into the pSortArray: + p = m_pServers; + i = 0; + while ( p ) + { + pSortArray[ i++ ] = p; + p = p->next; + } + + // Now do that actual sorting. + size_t nCount = c; + size_t nSize = sizeof( server_t * ); + + qsort( + pSortArray, + (size_t)nCount, + (size_t)nSize, + FnServerCompare + ); + + // Now rebuild the list. + m_pServers = pSortArray[0]; + for ( i = 0; i < c - 1; i++ ) + { + pSortArray[ i ]->next = pSortArray[ i + 1 ]; + } + pSortArray[ c - 1 ]->next = NULL; + + // Clean Up. + delete[] pSortArray; +} + +/* +=================== +GetServer + +Return particular server +=================== +*/ +CHudServers::server_t *CHudServers::GetServer( int server ) +{ + int c = 0; + server_t *p; + + p = m_pServers; + while ( p ) + { + if ( c == server ) + return p; + + c++; + p = p->next; + } + return NULL; +} + +/* +=================== +GetServerInfo + +Return info ( key/value ) string for particular server +=================== +*/ +char *CHudServers::GetServerInfo( int server ) +{ + server_t *p = GetServer( server ); + if ( p ) + { + return p->info; + } + return NULL; +} + +/* +=================== +CancelRequest + +Kill all pending requests in engine +=================== +*/ +void CHudServers::CancelRequest( void ) +{ + m_nRequesting = 0; + m_nQuerying = 0; + m_nDone = 1; + + NET_API->CancelAllRequests(); +} + +/* +================== +LoadMasterAddresses + +Loads the master server addresses from file and into the passed in array +================== +*/ +int CHudServers::LoadMasterAddresses( int maxservers, int *count, netadr_t *padr ) +{ + int i; + char szMaster[ 256 ]; + char szMasterFile[256]; + char *pbuffer = NULL; + char *pstart = NULL ; + netadr_t adr; + char szAdr[64]; + int nPort; + int nCount = 0; + bool bIgnore; + int nDefaultPort; + + // Assume default master and master file + strcpy( szMaster, VALVE_MASTER_ADDRESS ); // IP:PORT string + strcpy( szMasterFile, MASTER_PARSE_FILE ); + + // See if there is a command line override + i = gEngfuncs.CheckParm( "-comm", &pstart ); + if ( i && pstart ) + { + strcpy (szMasterFile, pstart ); + } + + // Read them in from proper file + pbuffer = (char *)gEngfuncs.COM_LoadFile( szMasterFile, 5, NULL ); // Use malloc + if ( !pbuffer ) + { + goto finish_master; + } + + pstart = pbuffer; + + while ( nCount < maxservers ) + { + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if ( strlen(m_szToken) <= 0) + break; + + bIgnore = true; + + if ( !stricmp( m_szToken, "Master" ) ) + { + nDefaultPort = PORT_MASTER; + bIgnore = FALSE; + } + + // Now parse all addresses between { } + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + if ( strlen(m_szToken) <= 0 ) + break; + + if ( stricmp ( m_szToken, "{" ) ) + break; + + // Parse addresses until we get to "}" + while ( nCount < maxservers ) + { + char base[256]; + + // Now parse all addresses between { } + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if (strlen(m_szToken) <= 0) + break; + + if ( !stricmp ( m_szToken, "}" ) ) + break; + + sprintf( base, "%s", m_szToken ); + + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if (strlen(m_szToken) <= 0) + break; + + if ( stricmp( m_szToken, ":" ) ) + break; + + pstart = gEngfuncs.COM_ParseFile( pstart, m_szToken ); + + if (strlen(m_szToken) <= 0) + break; + + nPort = atoi ( m_szToken ); + if ( !nPort ) + nPort = nDefaultPort; + + sprintf( szAdr, "%s:%i", base, nPort ); + + // Can we resolve it any better + if ( !NET_API->StringToAdr( szAdr, &adr ) ) + bIgnore = true; + + if ( !bIgnore ) + { + padr[ nCount++ ] = adr; + } + } + } + +finish_master: + if ( !nCount ) + { + sprintf( szMaster, VALVE_MASTER_ADDRESS ); // IP:PORT string + + // Convert to netadr_t + if ( NET_API->StringToAdr ( szMaster, &adr ) ) + { + + padr[ nCount++ ] = adr; + } + } + + *count = nCount; + + if ( pbuffer ) + { + gEngfuncs.COM_FreeFile( pbuffer ); + } + + return ( nCount > 0 ) ? 1 : 0; +} + +/* +=================== +RequestList + +Request list of game servers from master +=================== +*/ +void CHudServers::RequestList( void ) +{ + m_nRequesting = 1; + m_nDone = 0; + m_dStarted = m_fElapsed; + + int count = 0; + netadr_t adr; + + if ( !LoadMasterAddresses( 1, &count, &adr ) ) + { + gEngfuncs.Con_DPrintf( "SendRequest: Unable to read master server addresses\n" ); + return; + } + + ClearRequestList( &m_pActiveList ); + ClearRequestList( &m_pServerList ); + ClearServerList( &m_pServers ); + + m_nServerCount = 0; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Kill off left overs if any + NET_API->CancelAllRequests(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_SERVERLIST, 0, 5.0, &adr, ::ListResponse ); +} + +void CHudServers::RequestBroadcastList( int clearpending ) +{ + m_nRequesting = 1; + m_nDone = 0; + m_dStarted = m_fElapsed; + + netadr_t adr; + memset( &adr, 0, sizeof( adr ) ); + + if ( clearpending ) + { + ClearRequestList( &m_pActiveList ); + ClearRequestList( &m_pServerList ); + ClearServerList( &m_pServers ); + + m_nServerCount = 0; + } + + // Make sure to byte swap server if necessary ( using "host" to "net" conversion + adr.port = htons( PORT_SERVER ); + + // Make sure networking system has started. + NET_API->InitNetworking(); + + if ( clearpending ) + { + // Kill off left overs if any + NET_API->CancelAllRequests(); + } + + adr.type = NA_BROADCAST; + + // Request Servers from LAN via IP + NET_API->SendRequest( context_id++, NETAPI_REQUEST_DETAILS, FNETAPI_MULTIPLE_RESPONSE, 5.0, &adr, ::ServerResponse ); + + adr.type = NA_BROADCAST_IPX; + + // Request Servers from LAN via IPX ( if supported ) + NET_API->SendRequest( context_id++, NETAPI_REQUEST_DETAILS, FNETAPI_MULTIPLE_RESPONSE, 5.0, &adr, ::ServerResponse ); +} + +void CHudServers::ServerPing( int server ) +{ + server_t *p; + + p = GetServer( server ); + if ( !p ) + return; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_PING, 0, 5.0, &p->remote_address, ::PingResponse ); +} + +void CHudServers::ServerRules( int server ) +{ + server_t *p; + + p = GetServer( server ); + if ( !p ) + return; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_RULES, 0, 5.0, &p->remote_address, ::RulesResponse ); +} + +void CHudServers::ServerPlayers( int server ) +{ + server_t *p; + + p = GetServer( server ); + if ( !p ) + return; + + // Make sure networking system has started. + NET_API->InitNetworking(); + + // Request Server List from master + NET_API->SendRequest( context_id++, NETAPI_REQUEST_PLAYERS, 0, 5.0, &p->remote_address, ::PlayersResponse ); +} + +int CHudServers::isQuerying() +{ + return m_nRequesting ? 1 : 0; +} + + +/* +=================== +GetServerCount + +Return number of servers in browser list +=================== +*/ +int CHudServers::GetServerCount( void ) +{ + return m_nServerCount; +} + +/* +=================== +CHudServers + +=================== +*/ +CHudServers::CHudServers( void ) +{ + m_nRequesting = 0; + m_dStarted = 0.0; + m_nDone = 0; + m_pServerList = NULL; + m_pServers = NULL; + m_pActiveList = NULL; + m_nQuerying = 0; + m_nActiveQueries = 0; + + m_fElapsed = 0.0; + + + m_pPingRequest = NULL; + m_pRulesRequest = NULL; + m_pPlayersRequest = NULL; +} + +/* +=================== +~CHudServers + +=================== +*/ +CHudServers::~CHudServers( void ) +{ + ClearRequestList( &m_pActiveList ); + ClearRequestList( &m_pServerList ); + ClearServerList( &m_pServers ); + + m_nServerCount = 0; + + if ( m_pPingRequest ) + { + delete m_pPingRequest; + m_pPingRequest = NULL; + + } + + if ( m_pRulesRequest ) + { + delete m_pRulesRequest; + m_pRulesRequest = NULL; + } + + if ( m_pPlayersRequest ) + { + delete m_pPlayersRequest; + m_pPlayersRequest = NULL; + } +} + +/////////////////////////////// +// +// PUBLIC APIs +// +/////////////////////////////// + +/* +=================== +ServersGetCount + +=================== +*/ +int ServersGetCount( void ) +{ + if ( g_pServers ) + { + return g_pServers->GetServerCount(); + } + return 0; +} + +int ServersIsQuerying( void ) +{ + if ( g_pServers ) + { + return g_pServers->isQuerying(); + } + return 0; +} + +/* +=================== +ServersGetInfo + +=================== +*/ +const char *ServersGetInfo( int server ) +{ + if ( g_pServers ) + { + return g_pServers->GetServerInfo( server ); + } + + return NULL; +} + +void SortServers( const char *fieldname ) +{ + if ( g_pServers ) + { + g_pServers->SortServers( fieldname ); + } +} + +/* +=================== +ServersShutdown + +=================== +*/ +void ServersShutdown( void ) +{ + if ( g_pServers ) + { + delete g_pServers; + g_pServers = NULL; + } +} + +/* +=================== +ServersInit + +=================== +*/ +void ServersInit( void ) +{ + // Kill any previous instance + ServersShutdown(); + + g_pServers = new CHudServers(); +} + +/* +=================== +ServersThink + +=================== +*/ +void ServersThink( double time ) +{ + if ( g_pServers ) + { + g_pServers->Think( time ); + } +} + +/* +=================== +ServersCancel + +=================== +*/ +void ServersCancel( void ) +{ + if ( g_pServers ) + { + g_pServers->CancelRequest(); + } +} + +// Requests +/* +=================== +ServersList + +=================== +*/ +void ServersList( void ) +{ + if ( g_pServers ) + { + g_pServers->RequestList(); + } +} + +void BroadcastServersList( int clearpending ) +{ + if ( g_pServers ) + { + g_pServers->RequestBroadcastList( clearpending ); + } +} + +void ServerPing( int server ) +{ + if ( g_pServers ) + { + g_pServers->ServerPing( server ); + } +} + +void ServerRules( int server ) +{ + if ( g_pServers ) + { + g_pServers->ServerRules( server ); + } +} + +void ServerPlayers( int server ) +{ + if ( g_pServers ) + { + g_pServers->ServerPlayers( server ); + } +} \ No newline at end of file diff --git a/cl_dll/hud_servers.h b/cl_dll/hud_servers.h new file mode 100644 index 0000000..02bc855 --- /dev/null +++ b/cl_dll/hud_servers.h @@ -0,0 +1,41 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( HUD_SERVERSH ) +#define HUD_SERVERSH +#pragma once + +#define NET_CALLBACK /* */ + +// Dispatchers +void NET_CALLBACK ListResponse( struct net_response_s *response ); +void NET_CALLBACK ServerResponse( struct net_response_s *response ); +void NET_CALLBACK PingResponse( struct net_response_s *response ); +void NET_CALLBACK RulesResponse( struct net_response_s *response ); +void NET_CALLBACK PlayersResponse( struct net_response_s *response ); + +void ServersInit( void ); +void ServersShutdown( void ); +void ServersThink( double time ); +void ServersCancel( void ); + +// Get list and get server info from each +void ServersList( void ); + +// Query for IP / IPX LAN servers +void BroadcastServersList( int clearpending ); + +void ServerPing( int server ); +void ServerRules( int server ); +void ServerPlayers( int server ); + +int ServersGetCount( void ); +const char *ServersGetInfo( int server ); +int ServersIsQuerying( void ); +void SortServers( const char *fieldname ); + +#endif // HUD_SERVERSH \ No newline at end of file diff --git a/cl_dll/hud_servers_priv.h b/cl_dll/hud_servers_priv.h new file mode 100644 index 0000000..1919a0a --- /dev/null +++ b/cl_dll/hud_servers_priv.h @@ -0,0 +1,98 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( HUD_SERVERS_PRIVH ) +#define HUD_SERVERS_PRIVH +#pragma once + +#include "netadr.h" + +class CHudServers +{ +public: + typedef struct request_s + { + struct request_s *next; + netadr_t remote_address; + int context; + } request_t; + + typedef struct server_s + { + struct server_s *next; + netadr_t remote_address; + char *info; + int ping; + } server_t; + + CHudServers(); + ~CHudServers(); + + void Think( double time ); + void QueryThink( void ); + int isQuerying( void ); + + int LoadMasterAddresses( int maxservers, int *count, netadr_t *padr ); + + void RequestList( void ); + void RequestBroadcastList( int clearpending ); + + void ServerPing( int server ); + void ServerRules( int server ); + void ServerPlayers( int server ); + + void CancelRequest( void ); + + int CompareServers( server_t *p1, server_t *p2 ); + + void ClearServerList( server_t **ppList ); + void ClearRequestList( request_t **ppList ); + + void AddServer( server_t **ppList, server_t *p ); + + void RemoveServerFromList( request_t **ppList, request_t *item ); + + request_t *FindRequest( int context, request_t *pList ); + + int ServerListSize( void ); + char *GetServerInfo( int server ); + int GetServerCount( void ); + void SortServers( const char *fieldname ); + + void ListResponse( struct net_response_s *response ); + void ServerResponse( struct net_response_s *response ); + void PingResponse( struct net_response_s *response ); + void RulesResponse( struct net_response_s *response ); + void PlayersResponse( struct net_response_s *response ); +private: + + server_t *GetServer( int server ); + + // + char m_szToken[ 1024 ]; + int m_nRequesting; + int m_nDone; + + double m_dStarted; + + request_t *m_pServerList; + request_t *m_pActiveList; + + server_t *m_pServers; + + int m_nServerCount; + + int m_nActiveQueries; + int m_nQuerying; + double m_fElapsed; + + request_t *m_pPingRequest; + request_t *m_pRulesRequest; + request_t *m_pPlayersRequest; +}; + +#endif // HUD_SERVERS_PRIVH \ No newline at end of file diff --git a/cl_dll/hud_spectator.cpp b/cl_dll/hud_spectator.cpp new file mode 100644 index 0000000..f7ea703 --- /dev/null +++ b/cl_dll/hud_spectator.cpp @@ -0,0 +1,1626 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "hud.h" +#include "cl_util.h" +#include "cl_entity.h" +#include "triangleapi.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_SpectatorPanel.h" +#include "hltv.h" + +#include "pm_shared.h" +#include "pm_defs.h" +#include "pmtrace.h" +#include "parsemsg.h" +#include "entity_types.h" + +// these are included for the math functions +#include "com_model.h" +#include "demo_api.h" +#include "event_api.h" +#include "screenfade.h" + + +#pragma warning(disable: 4244) + +extern int iJumpSpectator; +extern float vJumpOrigin[3]; +extern float vJumpAngles[3]; + + +extern void V_GetInEyePos(int entity, float * origin, float * angles ); +extern void V_ResetChaseCam(); +extern void V_GetChasePos(int target, float * cl_angles, float * origin, float * angles); +extern void VectorAngles( const float *forward, float *angles ); +extern "C" void NormalizeAngles( float *angles ); +extern float * GetClientColor( int clientIndex ); + +extern vec3_t v_origin; // last view origin +extern vec3_t v_angles; // last view angle +extern vec3_t v_cl_angles; // last client/mouse angle +extern vec3_t v_sim_org; // last sim origin + +void SpectatorMode(void) +{ + + + if ( gEngfuncs.Cmd_Argc() <= 1 ) + { + gEngfuncs.Con_Printf( "usage: spec_mode
[]\n" ); + return; + } + + // SetModes() will decide if command is executed on server or local + if ( gEngfuncs.Cmd_Argc() == 2 ) + gHUD.m_Spectator.SetModes( atoi( gEngfuncs.Cmd_Argv(1) ), -1 ); + else if ( gEngfuncs.Cmd_Argc() == 3 ) + gHUD.m_Spectator.SetModes( atoi( gEngfuncs.Cmd_Argv(1) ), atoi( gEngfuncs.Cmd_Argv(2) ) ); +} + +void SpectatorSpray(void) +{ + vec3_t forward; + char string[128]; + + if ( !gEngfuncs.IsSpectateOnly() ) + return; + + AngleVectors(v_angles,forward,NULL,NULL); + VectorScale(forward, 128, forward); + VectorAdd(forward, v_origin, forward); + pmtrace_t * trace = gEngfuncs.PM_TraceLine( v_origin, forward, PM_TRACELINE_PHYSENTSONLY, 2, -1 ); + if ( trace->fraction != 1.0 ) + { + sprintf(string, "drc_spray %.2f %.2f %.2f %i", + trace->endpos[0], trace->endpos[1], trace->endpos[2], trace->ent ); + gEngfuncs.pfnServerCmd(string); + } + +} +void SpectatorHelp(void) +{ + if ( gViewPort ) + { + gViewPort->ShowVGUIMenu( MENU_SPECHELP ); + } + else + { + char *text = CHudTextMessage::BufferedLocaliseTextString( "#Spec_Help_Text" ); + + if ( text ) + { + while ( *text ) + { + if ( *text != 13 ) + gEngfuncs.Con_Printf( "%c", *text ); + text++; + } + } + } +} + +void SpectatorMenu( void ) +{ + if ( gEngfuncs.Cmd_Argc() <= 1 ) + { + gEngfuncs.Con_Printf( "usage: spec_menu <0|1>\n" ); + return; + } + +// gViewPort->m_pSpectatorPanel->ShowMenu( atoi( gEngfuncs.Cmd_Argv(1))!=0 ); +} + +void ToggleScores( void ) +{ + if ( gViewPort ) + { + if (gViewPort->IsScoreBoardVisible() ) + { + gViewPort->HideScoreBoard(); + } + else + { + gViewPort->ShowScoreBoard(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CHudSpectator::Init() +{ + gHUD.AddHudElem(this); + + m_iFlags |= HUD_ACTIVE; + m_flNextObserverInput = 0.0f; + m_zoomDelta = 0.0f; + m_moveDelta = 0.0f; + m_chatEnabled = (gHUD.m_SayText.m_HUD_saytext->value!=0); + iJumpSpectator = 0; + + memset( &m_OverviewData, 0, sizeof(m_OverviewData)); + memset( &m_OverviewEntities, 0, sizeof(m_OverviewEntities)); + m_lastPrimaryObject = m_lastSecondaryObject = 0; + + gEngfuncs.pfnAddCommand ("spec_mode", SpectatorMode ); + gEngfuncs.pfnAddCommand ("spec_decal", SpectatorSpray ); + gEngfuncs.pfnAddCommand ("spec_help", SpectatorHelp ); + gEngfuncs.pfnAddCommand ("spec_menu", SpectatorMenu ); + gEngfuncs.pfnAddCommand ("togglescores", ToggleScores ); + + m_drawnames = gEngfuncs.pfnRegisterVariable("spec_drawnames","1",0); + m_drawcone = gEngfuncs.pfnRegisterVariable("spec_drawcone","1",0); + m_drawstatus = gEngfuncs.pfnRegisterVariable("spec_drawstatus","1",0); + m_autoDirector = gEngfuncs.pfnRegisterVariable("spec_autodirector","1",0); + m_pip = gEngfuncs.pfnRegisterVariable("spec_pip","1",0); + + if ( !m_drawnames || !m_drawcone || !m_drawstatus || !m_autoDirector || !m_pip) + { + gEngfuncs.Con_Printf("ERROR! Couldn't register all spectator variables.\n"); + return 0; + } + + return 1; +} + + +//----------------------------------------------------------------------------- +// UTIL_StringToVector originally from ..\dlls\util.cpp, slightly changed +//----------------------------------------------------------------------------- + +void UTIL_StringToVector( float * pVector, const char *pString ) +{ + char *pstr, *pfront, tempString[128]; + int j; + + strcpy( tempString, pString ); + pstr = pfront = tempString; + + for ( j = 0; j < 3; j++ ) + { + pVector[j] = atof( pfront ); + + while ( *pstr && *pstr != ' ' ) + pstr++; + if (!*pstr) + break; + pstr++; + pfront = pstr; + } + + if (j < 2) + { + for (j = j+1;j < 3; j++) + pVector[j] = 0; + } +} + +int UTIL_FindEntityInMap(char * name, float * origin, float * angle) +{ + int n,found = 0; + char keyname[256]; + char token[1024]; + + cl_entity_t * pEnt = gEngfuncs.GetEntityByIndex( 0 ); // get world model + + if ( !pEnt ) return 0; + + if ( !pEnt->model ) return 0; + + char * data = pEnt->model->entities; + + while (data) + { + data = gEngfuncs.COM_ParseFile(data, token); + + if ( (token[0] == '}') || (token[0]==0) ) + break; + + if (!data) + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: EOF without closing brace\n"); + return 0; + } + + if (token[0] != '{') + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: expected {\n"); + return 0; + } + + // we parse the first { now parse entities properties + + while ( 1 ) + { + // parse key + data = gEngfuncs.COM_ParseFile(data, token); + if (token[0] == '}') + break; // finish parsing this entity + + if (!data) + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: EOF without closing brace\n"); + return 0; + }; + + strcpy (keyname, token); + + // another hack to fix keynames with trailing spaces + n = strlen(keyname); + while (n && keyname[n-1] == ' ') + { + keyname[n-1] = 0; + n--; + } + + // parse value + data = gEngfuncs.COM_ParseFile(data, token); + if (!data) + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: EOF without closing brace\n"); + return 0; + }; + + if (token[0] == '}') + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: closing brace without data"); + return 0; + } + + if (!strcmp(keyname,"classname")) + { + if (!strcmp(token, name )) + { + found = 1; // thats our entity + } + }; + + if( !strcmp( keyname, "angle" ) ) + { + float y = atof( token ); + + if (y >= 0) + { + angle[0] = 0.0f; + angle[1] = y; + } + else if ((int)y == -1) + { + angle[0] = -90.0f; + angle[1] = 0.0f;; + } + else + { + angle[0] = 90.0f; + angle[1] = 0.0f; + } + + angle[2] = 0.0f; + } + + if( !strcmp( keyname, "angles" ) ) + { + UTIL_StringToVector(angle, token); + } + + if (!strcmp(keyname,"origin")) + { + UTIL_StringToVector(origin, token); + + }; + + } // while (1) + + if (found) + return 1; + + } + + return 0; // we search all entities, but didn't found the correct + +} + +//----------------------------------------------------------------------------- +// SetSpectatorStartPosition(): +// Get valid map position and 'beam' spectator to this position +//----------------------------------------------------------------------------- + +void CHudSpectator::SetSpectatorStartPosition() +{ + // search for info_player start + if ( UTIL_FindEntityInMap( "trigger_camera", m_cameraOrigin, m_cameraAngles ) ) + iJumpSpectator = 1; + + else if ( UTIL_FindEntityInMap( "info_player_start", m_cameraOrigin, m_cameraAngles ) ) + iJumpSpectator = 1; + + else if ( UTIL_FindEntityInMap( "info_player_deathmatch", m_cameraOrigin, m_cameraAngles ) ) + iJumpSpectator = 1; + + else if ( UTIL_FindEntityInMap( "info_player_coop", m_cameraOrigin, m_cameraAngles ) ) + iJumpSpectator = 1; + else + { + // jump to 0,0,0 if no better position was found + VectorCopy(vec3_origin, m_cameraOrigin); + VectorCopy(vec3_origin, m_cameraAngles); + } + + VectorCopy(m_cameraOrigin, vJumpOrigin); + VectorCopy(m_cameraAngles, vJumpAngles); + + iJumpSpectator = 1; // jump anyway +} + +//----------------------------------------------------------------------------- +// Purpose: Loads new icons +//----------------------------------------------------------------------------- +int CHudSpectator::VidInit() +{ + m_hsprPlayer = SPR_Load("sprites/iplayer.spr"); + m_hsprPlayerBlue = SPR_Load("sprites/iplayerblue.spr"); + m_hsprPlayerRed = SPR_Load("sprites/iplayerred.spr"); + m_hsprPlayerDead = SPR_Load("sprites/iplayerdead.spr"); + m_hsprUnkownMap = SPR_Load("sprites/tile.spr"); + m_hsprBeam = SPR_Load("sprites/laserbeam.spr"); + m_hsprCamera = SPR_Load("sprites/camera.spr"); + m_hCrosshair = SPR_Load("sprites/null.spr"); + + return 1; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flTime - +// intermission - +//----------------------------------------------------------------------------- +int CHudSpectator::Draw(float flTime) +{ + int lx; + + char string[256]; + float * color; + + // draw only in spectator mode + if ( !g_iUser1 ) + return 0; + + // if user pressed zoom, aplly changes + if ( (m_zoomDelta != 0.0f) && ( g_iUser1 == OBS_MAP_FREE ) ) + { + m_mapZoom += m_zoomDelta; + + if ( m_mapZoom > 3.0f ) + m_mapZoom = 3.0f; + + if ( m_mapZoom < 0.5f ) + m_mapZoom = 0.5f; + } + + // if user moves in map mode, change map origin + if ( (m_moveDelta != 0.0f) && (g_iUser1 != OBS_ROAMING) ) + { + vec3_t right; + AngleVectors(v_angles, NULL, right, NULL); + VectorNormalize(right); + VectorScale(right, m_moveDelta, right ); + + VectorAdd( m_mapOrigin, right, m_mapOrigin ) + + } + + // Only draw the icon names only if map mode is in Main Mode + if ( g_iUser1 < OBS_MAP_FREE ) + return 1; + + if ( !m_drawnames->value ) + return 1; + + // make sure we have player info + gViewPort->GetAllPlayersInfo(); + + + // loop through all the players and draw additional infos to their sprites on the map + for (int i = 0; i < MAX_PLAYERS; i++) + { + + if ( m_vPlayerPos[i][2]<0 ) // marked as invisible ? + continue; + + // check if name would be in inset window + if ( m_pip->value != INSET_OFF ) + { + if ( m_vPlayerPos[i][0] > XRES( m_OverviewData.insetWindowX ) && + m_vPlayerPos[i][1] > YRES( m_OverviewData.insetWindowY ) && + m_vPlayerPos[i][0] < XRES( m_OverviewData.insetWindowX + m_OverviewData.insetWindowWidth ) && + m_vPlayerPos[i][1] < YRES( m_OverviewData.insetWindowY + m_OverviewData.insetWindowHeight) + ) continue; + } + + color = GetClientColor( i+1 ); + + // draw the players name and health underneath + sprintf(string, "%s", g_PlayerInfoList[i+1].name ); + + lx = strlen(string)*3; // 3 is avg. character length :) + + gEngfuncs.pfnDrawSetTextColor( color[0], color[1], color[2] ); + DrawConsoleString( m_vPlayerPos[i][0]-lx,m_vPlayerPos[i][1], string); + + } + + return 1; +} + + +void CHudSpectator::DirectorMessage( int iSize, void *pbuf ) +{ + float value; + char * string; + + BEGIN_READ( pbuf, iSize ); + + int cmd = READ_BYTE(); + + switch ( cmd ) // director command byte + { + case DRC_CMD_START : + // now we have to do some things clientside, since the proxy doesn't know our mod + g_iPlayerClass = 0; + g_iTeamNumber = 0; + + // fake a InitHUD & ResetHUD message + gHUD.MsgFunc_InitHUD(NULL,0, NULL); + gHUD.MsgFunc_ResetHUD(NULL, 0, NULL); + + break; + + case DRC_CMD_EVENT : + m_lastPrimaryObject = READ_WORD(); + m_lastSecondaryObject = READ_WORD(); + m_iObserverFlags = READ_LONG(); + + if ( m_autoDirector->value ) + { + if ( (g_iUser2 != m_lastPrimaryObject) || (g_iUser3 != m_lastSecondaryObject) ) + V_ResetChaseCam(); + + g_iUser2 = m_lastPrimaryObject; + g_iUser3 = m_lastSecondaryObject; + } + + // gEngfuncs.Con_Printf("Director Camera: %i %i\n", firstObject, secondObject); + break; + + case DRC_CMD_MODE : + if ( m_autoDirector->value ) + { + SetModes( READ_BYTE(), -1 ); + } + break; + + case DRC_CMD_CAMERA : + if ( m_autoDirector->value ) + { + vJumpOrigin[0] = READ_COORD(); // position + vJumpOrigin[1] = READ_COORD(); + vJumpOrigin[2] = READ_COORD(); + + vJumpAngles[0] = READ_COORD(); // view angle + vJumpAngles[1] = READ_COORD(); + vJumpAngles[2] = READ_COORD(); + + gEngfuncs.SetViewAngles( vJumpAngles ); + + iJumpSpectator = 1; + } + break; + + case DRC_CMD_MESSAGE: + { + client_textmessage_t * msg = &m_HUDMessages[m_lastHudMessage]; + + msg->effect = READ_BYTE(); // effect + + UnpackRGB( (int&)msg->r1, (int&)msg->g1, (int&)msg->b1, READ_LONG() ); // color + msg->r2 = msg->r1; + msg->g2 = msg->g1; + msg->b2 = msg->b1; + msg->a2 = msg->a1 = 0xFF; // not transparent + + msg->x = READ_FLOAT(); // x pos + msg->y = READ_FLOAT(); // y pos + + msg->fadein = READ_FLOAT(); // fadein + msg->fadeout = READ_FLOAT(); // fadeout + msg->holdtime = READ_FLOAT(); // holdtime + msg->fxtime = READ_FLOAT(); // fxtime; + + strncpy( m_HUDMessageText[m_lastHudMessage], READ_STRING(), 128 ); + m_HUDMessageText[m_lastHudMessage][127]=0; // text + + msg->pMessage = m_HUDMessageText[m_lastHudMessage]; + msg->pName = "HUD_MESSAGE"; + + gHUD.m_Message.MessageAdd( msg ); + + m_lastHudMessage++; + m_lastHudMessage %= MAX_SPEC_HUD_MESSAGES; + + } + + break; + + case DRC_CMD_SOUND : + string = READ_STRING(); + value = READ_FLOAT(); + + // gEngfuncs.Con_Printf("DRC_CMD_FX_SOUND: %s %.2f\n", string, value ); + gEngfuncs.pEventAPI->EV_PlaySound(0, v_origin, CHAN_BODY, string, value, ATTN_NORM, 0, PITCH_NORM ); + + break; + + case DRC_CMD_TIMESCALE : + value = READ_FLOAT(); + break; + + + + case DRC_CMD_STATUS: + READ_LONG(); // total number of spectator slots + m_iSpectatorNumber = READ_LONG(); // total number of spectator + READ_WORD(); // total number of relay proxies + +// gViewPort->UpdateSpectatorPanel(); + break; + + case DRC_CMD_BANNER: + // gEngfuncs.Con_DPrintf("GUI: Banner %s\n",READ_STRING() ); // name of banner tga eg gfx/temp/7454562234563475.tga +// gViewPort->m_pSpectatorPanel->m_TopBanner->LoadImage( READ_STRING() ); +// gViewPort->UpdateSpectatorPanel(); + break; + + case DRC_CMD_FADE: + break; + + case DRC_CMD_STUFFTEXT: + ClientCmd( READ_STRING() ); + break; + + default : gEngfuncs.Con_DPrintf("CHudSpectator::DirectorMessage: unknown command %i.\n", cmd ); + } +} + +void CHudSpectator::FindNextPlayer(bool bReverse) +{ + // MOD AUTHORS: Modify the logic of this function if you want to restrict the observer to watching + // only a subset of the players. e.g. Make it check the target's team. + + int iStart; + cl_entity_t * pEnt = NULL; + + // if we are NOT in HLTV mode, spectator targets are set on server + if ( !gEngfuncs.IsSpectateOnly() ) + { + char cmdstring[32]; + // forward command to server + sprintf(cmdstring,"follownext %i",bReverse?1:0); + gEngfuncs.pfnServerCmd(cmdstring); + return; + } + + if ( g_iUser2 ) + iStart = g_iUser2; + else + iStart = 1; + + g_iUser2 = 0; + + int iCurrent = iStart; + + int iDir = bReverse ? -1 : 1; + + // make sure we have player info + gViewPort->GetAllPlayersInfo(); + + + do + { + iCurrent += iDir; + + // Loop through the clients + if (iCurrent > MAX_PLAYERS) + iCurrent = 1; + if (iCurrent < 1) + iCurrent = MAX_PLAYERS; + + pEnt = gEngfuncs.GetEntityByIndex( iCurrent ); + + if ( !IsActivePlayer( pEnt ) ) + continue; + + // MOD AUTHORS: Add checks on target here. + + g_iUser2 = iCurrent; + break; + + } while ( iCurrent != iStart ); + + // Did we find a target? + if ( !g_iUser2 ) + { + gEngfuncs.Con_DPrintf( "No observer targets.\n" ); + // take save camera position + VectorCopy(m_cameraOrigin, vJumpOrigin); + VectorCopy(m_cameraAngles, vJumpAngles); + } + else + { + // use new entity position for roaming + VectorCopy ( pEnt->origin, vJumpOrigin ); + VectorCopy ( pEnt->angles, vJumpAngles ); + } + iJumpSpectator = 1; +} + +void CHudSpectator::HandleButtonsDown( int ButtonPressed ) +{ + double time = gEngfuncs.GetClientTime(); + + int newMainMode = g_iUser1; + int newInsetMode = m_pip->value; + + // gEngfuncs.Con_Printf(" HandleButtons:%i\n", ButtonPressed ); + if ( !gViewPort ) + return; + + //Not in intermission. + if ( gHUD.m_iIntermission ) + return; + + if ( !g_iUser1 ) + return; // dont do anything if not in spectator mode + + // don't handle buttons during normal demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() && !gEngfuncs.IsSpectateOnly() ) + return; + // Slow down mouse clicks. + if ( m_flNextObserverInput > time ) + return; + + // enable spectator screen +// if ( ButtonPressed & IN_DUCK ) +// gViewPort->m_pSpectatorPanel->ShowMenu(!gViewPort->m_pSpectatorPanel->m_menuVisible); + + // 'Use' changes inset window mode + if ( ButtonPressed & IN_USE ) + { + newInsetMode = ToggleInset(true); + } + + // if not in HLTV mode, buttons are handled server side + if ( gEngfuncs.IsSpectateOnly() ) + { + // changing target or chase mode not in overviewmode without inset window + + // Jump changes main window modes + if ( ButtonPressed & IN_JUMP ) + { + if ( g_iUser1 == OBS_CHASE_LOCKED ) + newMainMode = OBS_CHASE_FREE; + + else if ( g_iUser1 == OBS_CHASE_FREE ) + newMainMode = OBS_IN_EYE; + + else if ( g_iUser1 == OBS_IN_EYE ) + newMainMode = OBS_ROAMING; + + else if ( g_iUser1 == OBS_ROAMING ) + newMainMode = OBS_MAP_FREE; + + else if ( g_iUser1 == OBS_MAP_FREE ) + newMainMode = OBS_MAP_CHASE; + + else + newMainMode = OBS_CHASE_FREE; // don't use OBS_CHASE_LOCKED anymore + } + + // Attack moves to the next player + if ( ButtonPressed & (IN_ATTACK | IN_ATTACK2) ) + { + FindNextPlayer( (ButtonPressed & IN_ATTACK2) ? true:false ); + + if ( g_iUser1 == OBS_ROAMING ) + { + gEngfuncs.SetViewAngles( vJumpAngles ); + iJumpSpectator = 1; + + } + // lease directed mode if player want to see another player + m_autoDirector->value = 0.0f; + } + } + + SetModes(newMainMode, newInsetMode); + + if ( g_iUser1 == OBS_MAP_FREE ) + { + if ( ButtonPressed & IN_FORWARD ) + m_zoomDelta = 0.01f; + + if ( ButtonPressed & IN_BACK ) + m_zoomDelta = -0.01f; + + if ( ButtonPressed & IN_MOVELEFT ) + m_moveDelta = -12.0f; + + if ( ButtonPressed & IN_MOVERIGHT ) + m_moveDelta = 12.0f; + } + + m_flNextObserverInput = time + 0.2; +} + +void CHudSpectator::HandleButtonsUp( int ButtonPressed ) +{ + if ( !gViewPort ) + return; + +// if ( !gViewPort->m_pSpectatorPanel->isVisible() ) + return; // dont do anything if not in spectator mode + + if ( ButtonPressed & (IN_FORWARD | IN_BACK) ) + m_zoomDelta = 0.0f; + + if ( ButtonPressed & (IN_MOVELEFT | IN_MOVERIGHT) ) + m_moveDelta = 0.0f; +} + +void CHudSpectator::SetModes(int iNewMainMode, int iNewInsetMode) +{ + // if value == -1 keep old value + if ( iNewMainMode == -1 ) + iNewMainMode = g_iUser1; + + if ( iNewInsetMode == -1 ) + iNewInsetMode = m_pip->value; + + // inset mode is handled only clients side + m_pip->value = iNewInsetMode; + + if ( iNewMainMode < OBS_CHASE_LOCKED || iNewMainMode > OBS_MAP_CHASE ) + { + gEngfuncs.Con_Printf("Invalid spectator mode.\n"); + return; + } + + // main modes ettings will override inset window settings + if ( iNewMainMode != g_iUser1 ) + { + // if we are NOT in HLTV mode, main spectator mode is set on server + if ( !gEngfuncs.IsSpectateOnly() ) + { + return; + } + + if ( !g_iUser2 && (iNewMainMode !=OBS_ROAMING ) ) // make sure we have a target + { + // choose last Director object if still available + if ( IsActivePlayer( gEngfuncs.GetEntityByIndex( m_lastPrimaryObject ) ) ) + { + g_iUser2 = m_lastPrimaryObject; + g_iUser3 = m_lastSecondaryObject; + } + else + FindNextPlayer(false); // find any target + } + + switch ( iNewMainMode ) + { + case OBS_CHASE_LOCKED: g_iUser1 = OBS_CHASE_LOCKED; + break; + + case OBS_CHASE_FREE : g_iUser1 = OBS_CHASE_FREE; + break; + + case OBS_ROAMING : // jump to current vJumpOrigin/angle + g_iUser1 = OBS_ROAMING; + if ( g_iUser2 ) + { + V_GetChasePos( g_iUser2, v_cl_angles, vJumpOrigin, vJumpAngles ); + gEngfuncs.SetViewAngles( vJumpAngles ); + iJumpSpectator = 1; + } + break; + + case OBS_IN_EYE : g_iUser1 = OBS_IN_EYE; + break; + + case OBS_MAP_FREE : g_iUser1 = OBS_MAP_FREE; + // reset user values + m_mapZoom = m_OverviewData.zoom; + m_mapOrigin = m_OverviewData.origin; + break; + + case OBS_MAP_CHASE : g_iUser1 = OBS_MAP_CHASE; + // reset user values + m_mapZoom = m_OverviewData.zoom; + m_mapOrigin = m_OverviewData.origin; + break; + } + + if ( (g_iUser1 == OBS_IN_EYE) || (g_iUser1 == OBS_ROAMING) ) + { + m_crosshairRect.left = 24; + m_crosshairRect.top = 0; + m_crosshairRect.right = 48; + m_crosshairRect.bottom = 24; + + SetCrosshair( m_hCrosshair, m_crosshairRect, 255, 255, 255 ); + } + else + { + memset( &m_crosshairRect,0,sizeof(m_crosshairRect) ); + SetCrosshair( 0, m_crosshairRect, 0, 0, 0 ); + } + + char string[128]; + sprintf(string, "#Spec_Mode%d", g_iUser1 ); + sprintf(string, "%c%s", HUD_PRINTCENTER, CHudTextMessage::BufferedLocaliseTextString( string )); + gHUD.m_TextMessage.MsgFunc_TextMsg(NULL, strlen(string)+1, string ); + } + +// gViewPort->UpdateSpectatorPanel(); + +} + +bool CHudSpectator::IsActivePlayer(cl_entity_t * ent) +{ + return ( ent && + ent->player && + ent->curstate.solid != SOLID_NOT && + ent != gEngfuncs.GetLocalPlayer() && + g_PlayerInfoList[ent->index].name != NULL + ); +} + + +bool CHudSpectator::ParseOverviewFile( ) +{ + char filename[255]; + char levelname[255]; + char token[1024]; + float height; + + char *pfile = NULL; + + memset( &m_OverviewData, 0, sizeof(m_OverviewData)); + + // fill in standrd values + m_OverviewData.insetWindowX = 4; // upper left corner + m_OverviewData.insetWindowY = 4; + m_OverviewData.insetWindowHeight = 180; + m_OverviewData.insetWindowWidth = 240; + m_OverviewData.origin[0] = 0.0f; + m_OverviewData.origin[1] = 0.0f; + m_OverviewData.origin[2] = 0.0f; + m_OverviewData.zoom = 1.0f; + m_OverviewData.layers = 0; + m_OverviewData.layersHeights[0] = 0.0f; + strcpy( m_OverviewData.map, gEngfuncs.pfnGetLevelName() ); + + if ( strlen( m_OverviewData.map ) == 0 ) + return false; // not active yet + + strcpy(levelname, m_OverviewData.map + 5); + levelname[strlen(levelname)-4] = 0; + + sprintf(filename, "overviews/%s.txt", levelname ); + + pfile = (char *)gEngfuncs.COM_LoadFile( filename, 5, NULL); + + if (!pfile) + { + ALERT( at_aiconsole, "Couldn't open file %s. Using default values for overiew mode.\n", filename ); + return false; + } + + + while (true) + { + pfile = gEngfuncs.COM_ParseFile(pfile, token); + + if (!pfile) + break; + + if ( !stricmp( token, "global" ) ) + { + // parse the global data + pfile = gEngfuncs.COM_ParseFile(pfile, token); + if ( stricmp( token, "{" ) ) + { + gEngfuncs.Con_Printf("Error parsing overview file %s. (expected { )\n", filename ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); + + while (stricmp( token, "}") ) + { + if ( !stricmp( token, "zoom" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.zoom = atof( token ); + } + else if ( !stricmp( token, "origin" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile, token); + m_OverviewData.origin[0] = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.origin[1] = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile, token); + m_OverviewData.origin[2] = atof( token ); + } + else if ( !stricmp( token, "rotated" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.rotated = atoi( token ); + } + else if ( !stricmp( token, "inset" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.insetWindowX = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.insetWindowY = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.insetWindowWidth = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.insetWindowHeight = atof( token ); + + } + else + { + gEngfuncs.Con_Printf("Error parsing overview file %s. (%s unkown)\n", filename, token ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); // parse next token + + } + } + else if ( !stricmp( token, "layer" ) ) + { + // parse a layer data + + if ( m_OverviewData.layers == OVERVIEW_MAX_LAYERS ) + { + gEngfuncs.Con_Printf("Error parsing overview file %s. ( too many layers )\n", filename ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); + + + if ( stricmp( token, "{" ) ) + { + gEngfuncs.Con_Printf("Error parsing overview file %s. (expected { )\n", filename ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); + + while (stricmp( token, "}") ) + { + if ( !stricmp( token, "image" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + strcpy(m_OverviewData.layersImages[ m_OverviewData.layers ], token); + + + } + else if ( !stricmp( token, "height" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + height = atof(token); + m_OverviewData.layersHeights[ m_OverviewData.layers ] = height; + } + else + { + gEngfuncs.Con_Printf("Error parsing overview file %s. (%s unkown)\n", filename, token ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); // parse next token + } + + m_OverviewData.layers++; + + } + } + + gEngfuncs.COM_FreeFile( pfile ); + + m_mapZoom = m_OverviewData.zoom; + m_mapOrigin = m_OverviewData.origin; + + return true; + +} + +void CHudSpectator::LoadMapSprites() +{ + // right now only support for one map layer + if (m_OverviewData.layers > 0 ) + { + m_MapSprite = gEngfuncs.LoadMapSprite( m_OverviewData.layersImages[0] ); + } + else + m_MapSprite = NULL; // the standard "unkown map" sprite will be used instead +} + +void CHudSpectator::DrawOverviewLayer() +{ + float screenaspect, xs, ys, xStep, yStep, x,y,z; + int ix,iy,i,xTiles,yTiles,frame; + + qboolean hasMapImage = m_MapSprite?TRUE:FALSE; + model_t * dummySprite = (struct model_s *)gEngfuncs.GetSpritePointer( m_hsprUnkownMap); + + if ( hasMapImage) + { + i = m_MapSprite->numframes / (4*3); + i = sqrt(i); + xTiles = i*4; + yTiles = i*3; + } + else + { + xTiles = 8; + yTiles = 6; + } + + + screenaspect = 4.0f/3.0f; + + + xs = m_OverviewData.origin[0]; + ys = m_OverviewData.origin[1]; + z = ( 90.0f - v_angles[0] ) / 90.0f; + z *= m_OverviewData.layersHeights[0]; // gOverviewData.z_min - 32; + + // i = r_overviewTexture + ( layer*OVERVIEW_X_TILES*OVERVIEW_Y_TILES ); + + gEngfuncs.pTriAPI->RenderMode( kRenderTransTexture ); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); + gEngfuncs.pTriAPI->Color4f( 1.0, 1.0, 1.0, 1.0 ); + + frame = 0; + + + // rotated view ? + if ( m_OverviewData.rotated ) + { + xStep = (2*4096.0f / m_OverviewData.zoom ) / xTiles; + yStep = -(2*4096.0f / (m_OverviewData.zoom* screenaspect) ) / yTiles; + + y = ys + (4096.0f / (m_OverviewData.zoom * screenaspect)); + + for (iy = 0; iy < yTiles; iy++) + { + x = xs - (4096.0f / (m_OverviewData.zoom)); + + for (ix = 0; ix < xTiles; ix++) + { + if (hasMapImage) + gEngfuncs.pTriAPI->SpriteTexture( m_MapSprite, frame ); + else + gEngfuncs.pTriAPI->SpriteTexture( dummySprite, 0 ); + + gEngfuncs.pTriAPI->Begin( TRI_QUADS ); + gEngfuncs.pTriAPI->TexCoord2f( 0, 0 ); + gEngfuncs.pTriAPI->Vertex3f (x, y, z); + + gEngfuncs.pTriAPI->TexCoord2f( 1, 0 ); + gEngfuncs.pTriAPI->Vertex3f (x+xStep ,y, z); + + gEngfuncs.pTriAPI->TexCoord2f( 1, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x+xStep, y+yStep, z); + + gEngfuncs.pTriAPI->TexCoord2f( 0, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x, y+yStep, z); + gEngfuncs.pTriAPI->End(); + + frame++; + x+= xStep; + } + + y+=yStep; + } + } + else + { + xStep = -(2*4096.0f / m_OverviewData.zoom ) / xTiles; + yStep = -(2*4096.0f / (m_OverviewData.zoom* screenaspect) ) / yTiles; + + + x = xs + (4096.0f / (m_OverviewData.zoom * screenaspect )); + + + + for (ix = 0; ix < yTiles; ix++) + { + + y = ys + (4096.0f / (m_OverviewData.zoom)); + + for (iy = 0; iy < xTiles; iy++) + { + if (hasMapImage) + gEngfuncs.pTriAPI->SpriteTexture( m_MapSprite, frame ); + else + gEngfuncs.pTriAPI->SpriteTexture( dummySprite, 0 ); + + gEngfuncs.pTriAPI->Begin( TRI_QUADS ); + gEngfuncs.pTriAPI->TexCoord2f( 0, 0 ); + gEngfuncs.pTriAPI->Vertex3f (x, y, z); + + gEngfuncs.pTriAPI->TexCoord2f( 0, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x+xStep ,y, z); + + gEngfuncs.pTriAPI->TexCoord2f( 1, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x+xStep, y+yStep, z); + + gEngfuncs.pTriAPI->TexCoord2f( 1, 0 ); + gEngfuncs.pTriAPI->Vertex3f (x, y+yStep, z); + gEngfuncs.pTriAPI->End(); + + frame++; + + y+=yStep; + } + + x+= xStep; + + } + } + + gEngfuncs.pTriAPI->CullFace( TRI_FRONT ); +} + +void CHudSpectator::DrawOverviewEntities() +{ + int i,ir,ig,ib; + struct model_s *hSpriteModel; + vec3_t origin, angles, point, forward, right, left, up, world, screen, offset; + float x,y,z, r,g,b, sizeScale = 4.0f; + cl_entity_t * ent; + float rmatrix[3][4]; // transformation matrix + + float zScale = (90.0f - v_angles[0] ) / 90.0f; + + + z = m_OverviewData.layersHeights[0] * zScale; + // get yellow/brown HUD color + UnpackRGB(ir,ig,ib, RGB_YELLOWISH); + r = (float)ir/255.0f; + g = (float)ig/255.0f; + b = (float)ib/255.0f; + + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); + + for (i=0; i < MAX_PLAYERS; i++ ) + m_vPlayerPos[i][2] = -1; // mark as invisible + + // draw all players + for (i=0 ; i < MAX_OVERVIEW_ENTITIES ; i++) + { + if ( !m_OverviewEntities[i].hSprite ) + continue; + + hSpriteModel = (struct model_s *)gEngfuncs.GetSpritePointer( m_OverviewEntities[i].hSprite ); + ent = m_OverviewEntities[i].entity; + + gEngfuncs.pTriAPI->SpriteTexture( hSpriteModel, 0 ); + gEngfuncs.pTriAPI->RenderMode( kRenderTransTexture ); + + // see R_DrawSpriteModel + // draws players sprite + + AngleVectors(ent->angles, right, up, NULL ); + + VectorCopy(ent->origin,origin); + + gEngfuncs.pTriAPI->Begin( TRI_QUADS ); + + gEngfuncs.pTriAPI->Color4f( 1.0, 1.0, 1.0, 1.0 ); + + gEngfuncs.pTriAPI->TexCoord2f (1, 0); + VectorMA (origin, 16.0f * sizeScale, up, point); + VectorMA (point, 16.0f * sizeScale, right, point); + point[2] *= zScale; + gEngfuncs.pTriAPI->Vertex3fv (point); + + gEngfuncs.pTriAPI->TexCoord2f (0, 0); + + VectorMA (origin, 16.0f * sizeScale, up, point); + VectorMA (point, -16.0f * sizeScale, right, point); + point[2] *= zScale; + gEngfuncs.pTriAPI->Vertex3fv (point); + + gEngfuncs.pTriAPI->TexCoord2f (0,1); + VectorMA (origin, -16.0f * sizeScale, up, point); + VectorMA (point, -16.0f * sizeScale, right, point); + point[2] *= zScale; + gEngfuncs.pTriAPI->Vertex3fv (point); + + gEngfuncs.pTriAPI->TexCoord2f (1,1); + VectorMA (origin, -16.0f * sizeScale, up, point); + VectorMA (point, 16.0f * sizeScale, right, point); + point[2] *= zScale; + gEngfuncs.pTriAPI->Vertex3fv (point); + + gEngfuncs.pTriAPI->End (); + + + if ( !ent->player) + continue; + // draw line under player icons + origin[2] *= zScale; + + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + + hSpriteModel = (struct model_s *)gEngfuncs.GetSpritePointer( m_hsprBeam ); + gEngfuncs.pTriAPI->SpriteTexture( hSpriteModel, 0 ); + + gEngfuncs.pTriAPI->Color4f(r, g, b, 0.3); + + gEngfuncs.pTriAPI->Begin ( TRI_QUADS ); + gEngfuncs.pTriAPI->TexCoord2f (1, 0); + gEngfuncs.pTriAPI->Vertex3f (origin[0]+4, origin[1]+4, origin[2]-zScale); + gEngfuncs.pTriAPI->TexCoord2f (0, 0); + gEngfuncs.pTriAPI->Vertex3f (origin[0]-4, origin[1]-4, origin[2]-zScale); + gEngfuncs.pTriAPI->TexCoord2f (0, 1); + gEngfuncs.pTriAPI->Vertex3f (origin[0]-4, origin[1]-4,z); + gEngfuncs.pTriAPI->TexCoord2f (1, 1); + gEngfuncs.pTriAPI->Vertex3f (origin[0]+4, origin[1]+4,z); + gEngfuncs.pTriAPI->End (); + + gEngfuncs.pTriAPI->Begin ( TRI_QUADS ); + gEngfuncs.pTriAPI->TexCoord2f (1, 0); + gEngfuncs.pTriAPI->Vertex3f (origin[0]-4, origin[1]+4, origin[2]-zScale); + gEngfuncs.pTriAPI->TexCoord2f (0, 0); + gEngfuncs.pTriAPI->Vertex3f (origin[0]+4, origin[1]-4, origin[2]-zScale); + gEngfuncs.pTriAPI->TexCoord2f (0, 1); + gEngfuncs.pTriAPI->Vertex3f (origin[0]+4, origin[1]-4,z); + gEngfuncs.pTriAPI->TexCoord2f (1, 1); + gEngfuncs.pTriAPI->Vertex3f (origin[0]-4, origin[1]+4,z); + gEngfuncs.pTriAPI->End (); + + // calculate screen position for name and infromation in hud::draw() + if ( gEngfuncs.pTriAPI->WorldToScreen(origin,screen) ) + continue; // object is behind viewer + + screen[0] = XPROJECT(screen[0]); + screen[1] = YPROJECT(screen[1]); + screen[2] = 0.0f; + + // calculate some offset under the icon + origin[0]+=32.0f; + origin[1]+=32.0f; + + gEngfuncs.pTriAPI->WorldToScreen(origin,offset); + + offset[0] = XPROJECT(offset[0]); + offset[1] = YPROJECT(offset[1]); + offset[2] = 0.0f; + + VectorSubtract(offset, screen, offset ); + + int playerNum = ent->index - 1; + + m_vPlayerPos[playerNum][0] = screen[0]; + m_vPlayerPos[playerNum][1] = screen[1] + Length(offset); + m_vPlayerPos[playerNum][2] = 1; // mark player as visible + } + + gEngfuncs.pTriAPI->CullFace( TRI_FRONT ); + + if ( !m_pip->value || !m_drawcone->value ) + return; + + // get current camera position and angle + + if ( m_pip->value == INSET_IN_EYE || g_iUser1 == OBS_IN_EYE ) + { + V_GetInEyePos( g_iUser2, origin, angles ); + } + else if ( m_pip->value == INSET_CHASE_FREE || g_iUser1 == OBS_CHASE_FREE ) + { + V_GetChasePos( g_iUser2, v_cl_angles, origin, angles ); + } + else if ( g_iUser1 == OBS_ROAMING ) + { + VectorCopy( v_sim_org, origin ); + VectorCopy( v_cl_angles, angles ); + } + else + V_GetChasePos( g_iUser2, NULL, origin, angles ); + + + // draw camera sprite + + x = origin[0]; + y = origin[1]; + z = origin[2]; + + angles[0] = 0; // always show horizontal camera sprite + + hSpriteModel = (struct model_s *)gEngfuncs.GetSpritePointer( m_hsprCamera ); + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + gEngfuncs.pTriAPI->SpriteTexture( hSpriteModel, 0 ); + + + gEngfuncs.pTriAPI->Color4f( r, g, b, 1.0 ); + + AngleVectors(angles, forward, NULL, NULL ); + VectorScale (forward, 512.0f, forward); + + offset[0] = 0.0f; + offset[1] = 45.0f; + offset[2] = 0.0f; + + AngleMatrix(offset, rmatrix ); + VectorTransform(forward, rmatrix , right ); + + offset[1]= -45.0f; + AngleMatrix(offset, rmatrix ); + VectorTransform(forward, rmatrix , left ); + + gEngfuncs.pTriAPI->Begin (TRI_TRIANGLES); + gEngfuncs.pTriAPI->TexCoord2f( 0, 0 ); + gEngfuncs.pTriAPI->Vertex3f (x+right[0], y+right[1], (z+right[2]) * zScale); + + gEngfuncs.pTriAPI->TexCoord2f( 0, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x, y, z * zScale); + + gEngfuncs.pTriAPI->TexCoord2f( 1, 1 ); + gEngfuncs.pTriAPI->Vertex3f (x+left[0], y+left[1], (z+left[2]) * zScale); + gEngfuncs.pTriAPI->End (); + +} + + + +void CHudSpectator::DrawOverview() +{ + // draw only in sepctator mode + if ( !g_iUser1 ) + return; + + // Only draw the overview if Map Mode is selected for this view + if ( m_iDrawCycle == 0 && ( (g_iUser1 != OBS_MAP_FREE) && (g_iUser1 != OBS_MAP_CHASE) ) ) + return; + + if ( m_iDrawCycle == 1 && m_pip->value < INSET_MAP_FREE ) + return; + + DrawOverviewLayer(); + DrawOverviewEntities(); + CheckOverviewEntities(); +} +void CHudSpectator::CheckOverviewEntities() +{ + double time = gEngfuncs.GetClientTime(); + + // removes old entities from list + for ( int i = 0; i< MAX_OVERVIEW_ENTITIES; i++ ) + { + // remove entity from list if it is too old + if ( m_OverviewEntities[i].killTime < time ) + { + memset( &m_OverviewEntities[i], 0, sizeof (overviewEntity_t) ); + } + } +} + +bool CHudSpectator::AddOverviewEntity( int type, struct cl_entity_s *ent, const char *modelname) +{ + HSPRITE hSprite = 0; + double duration = -1.0f; // duration -1 means show it only this frame; + + if ( !ent ) + return false; + + if ( type == ET_PLAYER ) + { + if ( ent->curstate.solid != SOLID_NOT) + { + switch ( g_PlayerExtraInfo[ent->index].teamnumber ) + { + // blue and red teams are swapped in CS and TFC + case 1 : hSprite = m_hsprPlayerBlue; break; + case 2 : hSprite = m_hsprPlayerRed; break; + default : hSprite = m_hsprPlayer; break; + } + } + else + return false; // it's an spectator + } + else if (type == ET_NORMAL) + { + return false; + } + else + return false; + + return AddOverviewEntityToList(hSprite, ent, gEngfuncs.GetClientTime() + duration ); +} + +void CHudSpectator::DeathMessage(int victim) +{ + // find out where the victim is + cl_entity_t *pl = gEngfuncs.GetEntityByIndex(victim); + + if (pl && pl->player) + AddOverviewEntityToList(m_hsprPlayerDead, pl, gEngfuncs.GetClientTime() + 2.0f ); +} + +bool CHudSpectator::AddOverviewEntityToList(HSPRITE sprite, cl_entity_t *ent, double killTime) +{ + for ( int i = 0; i< MAX_OVERVIEW_ENTITIES; i++ ) + { + // find empty entity slot + if ( m_OverviewEntities[i].entity == NULL) + { + m_OverviewEntities[i].entity = ent; + m_OverviewEntities[i].hSprite = sprite; + m_OverviewEntities[i].killTime = killTime; + return true; + } + } + + return false; // maximum overview entities reached +} +void CHudSpectator::CheckSettings() +{ + // disallow same inset mode as main mode: + + m_pip->value = (int)m_pip->value; + + if ( ( g_iUser1 < OBS_MAP_FREE ) && ( m_pip->value == INSET_CHASE_FREE || m_pip->value == INSET_IN_EYE ) ) + { + // otherwise both would show in World picures + m_pip->value = INSET_MAP_FREE; + } + + if ( ( g_iUser1 >= OBS_MAP_FREE ) && ( m_pip->value >= INSET_MAP_FREE ) ) + { + // both would show map views + m_pip->value = INSET_CHASE_FREE; + } + + // disble in intermission screen + if ( gHUD.m_iIntermission ) + m_pip->value = INSET_OFF; + + // check chat mode + if ( m_chatEnabled != (gHUD.m_SayText.m_HUD_saytext->value!=0) ) + { + // hud_saytext changed + m_chatEnabled = (gHUD.m_SayText.m_HUD_saytext->value!=0); + + if ( gEngfuncs.IsSpectateOnly() ) + { + // tell proxy our new chat mode + char chatcmd[32]; + sprintf(chatcmd, "ignoremsg %i", m_chatEnabled?0:1 ); + gEngfuncs.pfnServerCmd(chatcmd); + } + } + + // HL/TFC has no oberserver corsshair, so set it client side + if ( (g_iUser1 == OBS_IN_EYE) || (g_iUser1 == OBS_ROAMING) ) + { + m_crosshairRect.left = 24; + m_crosshairRect.top = 0; + m_crosshairRect.right = 48; + m_crosshairRect.bottom = 24; + + SetCrosshair( m_hCrosshair, m_crosshairRect, 255, 255, 255 ); + } + else + { + memset( &m_crosshairRect,0,sizeof(m_crosshairRect) ); + SetCrosshair( 0, m_crosshairRect, 0, 0, 0 ); + } + + + + // if we are a real player on server don't allow inset window + // in First Person mode since this is our resticted forcecamera mode 2 + // team number 3 = SPECTATOR see player.h + + if ( ( (g_iTeamNumber == 1) || (g_iTeamNumber == 2)) && (g_iUser1 == OBS_IN_EYE) ) + m_pip->value = INSET_OFF; + + // draw small border around inset view, adjust upper black bar +// gViewPort->m_pSpectatorPanel->EnableInsetView( m_pip->value != INSET_OFF ); +} + +int CHudSpectator::ToggleInset(bool allowOff) +{ + int newInsetMode = (int)m_pip->value + 1; + + if ( g_iUser1 < OBS_MAP_FREE ) + { + if ( newInsetMode > INSET_MAP_CHASE ) + { + if (allowOff) + newInsetMode = INSET_OFF; + else + newInsetMode = INSET_MAP_FREE; + } + + if ( newInsetMode == INSET_CHASE_FREE ) + newInsetMode = INSET_MAP_FREE; + } + else + { + if ( newInsetMode > INSET_IN_EYE ) + { + if (allowOff) + newInsetMode = INSET_OFF; + else + newInsetMode = INSET_CHASE_FREE; + } + } + + return newInsetMode; +} +void CHudSpectator::Reset() +{ + // Reset HUD + if ( strcmp( m_OverviewData.map, gEngfuncs.pfnGetLevelName() ) ) + { + // update level overview if level changed + ParseOverviewFile(); + LoadMapSprites(); + } + + memset( &m_OverviewEntities, 0, sizeof(m_OverviewEntities)); + + SetSpectatorStartPosition(); +} + +void CHudSpectator::InitHUDData() +{ + m_lastPrimaryObject = m_lastSecondaryObject = 0; + m_flNextObserverInput = 0.0f; + m_lastHudMessage = 0; + m_iSpectatorNumber = 0; + iJumpSpectator = 0; + g_iUser1 = g_iUser2 = 0; + + memset( &m_OverviewData, 0, sizeof(m_OverviewData)); + memset( &m_OverviewEntities, 0, sizeof(m_OverviewEntities)); + + if ( gEngfuncs.IsSpectateOnly() || gEngfuncs.pDemoAPI->IsPlayingback() ) + m_autoDirector->value = 1.0f; + else + m_autoDirector->value = 0.0f; + + Reset(); + + SetModes( OBS_CHASE_FREE, INSET_OFF ); + + g_iUser2 = 0; // fake not target until first camera command + + // reset HUD FOV + gHUD.m_iFOV = CVAR_GET_FLOAT("default_fov"); // buz: turn off +} + diff --git a/cl_dll/hud_spectator.h b/cl_dll/hud_spectator.h new file mode 100644 index 0000000..7f1a361 --- /dev/null +++ b/cl_dll/hud_spectator.h @@ -0,0 +1,132 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef SPECTATOR_H +#define SPECTATOR_H +#pragma once + +#include "cl_entity.h" + + + +#define INSET_OFF 0 +#define INSET_CHASE_FREE 1 +#define INSET_IN_EYE 2 +#define INSET_MAP_FREE 3 +#define INSET_MAP_CHASE 4 + +#define MAX_SPEC_HUD_MESSAGES 8 + + + +#define OVERVIEW_TILE_SIZE 128 // don't change this +#define OVERVIEW_MAX_LAYERS 1 + +//----------------------------------------------------------------------------- +// Purpose: Handles the drawing of the spectator stuff (camera & top-down map and all the things on it ) +//----------------------------------------------------------------------------- + +typedef struct overviewInfo_s { + char map[64]; // cl.levelname or empty + vec3_t origin; // center of map + float zoom; // zoom of map images + int layers; // how may layers do we have + float layersHeights[OVERVIEW_MAX_LAYERS]; + char layersImages[OVERVIEW_MAX_LAYERS][255]; + qboolean rotated; // are map images rotated (90 degrees) ? + + int insetWindowX; + int insetWindowY; + int insetWindowHeight; + int insetWindowWidth; +} overviewInfo_t; + +typedef struct overviewEntity_s { + + HSPRITE hSprite; + struct cl_entity_s * entity; + double killTime; +} overviewEntity_t; + +#define MAX_OVERVIEW_ENTITIES 128 + +class CHudSpectator : public CHudBase +{ +public: + void Reset(); + int ToggleInset(bool allowOff); + void CheckSettings(); + void InitHUDData( void ); + bool AddOverviewEntityToList( HSPRITE sprite, cl_entity_t * ent, double killTime); + void DeathMessage(int victim); + bool AddOverviewEntity( int type, struct cl_entity_s *ent, const char *modelname ); + void CheckOverviewEntities(); + void DrawOverview(); + void DrawOverviewEntities(); + void GetMapPosition( float * returnvec ); + void DrawOverviewLayer(); + void LoadMapSprites(); + bool ParseOverviewFile(); + bool IsActivePlayer(cl_entity_t * ent); + void SetModes(int iMainMode, int iInsetMode); + void HandleButtonsDown(int ButtonPressed); + void HandleButtonsUp(int ButtonPressed); + void FindNextPlayer( bool bReverse ); + void DirectorMessage( int iSize, void *pbuf ); + void SetSpectatorStartPosition(); + int Init(); + int VidInit(); + + int Draw(float flTime); + + int m_iDrawCycle; + client_textmessage_t m_HUDMessages[MAX_SPEC_HUD_MESSAGES]; + char m_HUDMessageText[MAX_SPEC_HUD_MESSAGES][128]; + int m_lastHudMessage; + overviewInfo_t m_OverviewData; + overviewEntity_t m_OverviewEntities[MAX_OVERVIEW_ENTITIES]; + int m_iObserverFlags; + int m_iSpectatorNumber; + + float m_mapZoom; // zoom the user currently uses + vec3_t m_mapOrigin; // origin where user rotates around + cvar_t * m_drawnames; + cvar_t * m_drawcone; + cvar_t * m_drawstatus; + cvar_t * m_autoDirector; + cvar_t * m_pip; + + + qboolean m_chatEnabled; + + vec3_t m_cameraOrigin; // a help camera + vec3_t m_cameraAngles; // and it's angles + + +private: + vec3_t m_vPlayerPos[MAX_PLAYERS]; + HSPRITE m_hsprPlayerBlue; + HSPRITE m_hsprPlayerRed; + HSPRITE m_hsprPlayer; + HSPRITE m_hsprCamera; + HSPRITE m_hsprPlayerDead; + HSPRITE m_hsprViewcone; + HSPRITE m_hsprUnkownMap; + HSPRITE m_hsprBeam; + HSPRITE m_hCrosshair; + + wrect_t m_crosshairRect; + + struct model_s * m_MapSprite; // each layer image is saved in one sprite, where each tile is a sprite frame + float m_flNextObserverInput; + float m_zoomDelta; + float m_moveDelta; + int m_lastPrimaryObject; + int m_lastSecondaryObject; +}; + +#endif // SPECTATOR_H diff --git a/cl_dll/hud_update.cpp b/cl_dll/hud_update.cpp new file mode 100644 index 0000000..01b864f --- /dev/null +++ b/cl_dll/hud_update.cpp @@ -0,0 +1,54 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// hud_update.cpp +// + +#include +#include "hud.h" +#include "cl_util.h" +#include +#include + +int CL_ButtonBits( int ); +void CL_ResetButtonBits( int bits ); + +extern float v_idlescale; +float in_fov; +extern void HUD_SetCmdBits( int bits ); + +int CHud::UpdateClientData(client_data_t *cdata, float time) +{ + memcpy(m_vecOrigin, cdata->origin, sizeof(vec3_t)); + memcpy(m_vecAngles, cdata->viewangles, sizeof(vec3_t)); + + m_iKeyBits = CL_ButtonBits( 0 ); + m_iWeaponBits = cdata->iWeaponBits; + + in_fov = cdata->fov; + + Think(); + + cdata->fov = m_flFOV;//m_iFOV; buz + +// v_idlescale = m_iConcussionEffect; + + CL_ResetButtonBits( m_iKeyBits ); + + // return 1 if in anything in the client_data struct has been changed, 0 otherwise + return 1; +} + + diff --git a/cl_dll/in_camera.cpp b/cl_dll/in_camera.cpp new file mode 100644 index 0000000..0dfefe4 --- /dev/null +++ b/cl_dll/in_camera.cpp @@ -0,0 +1,621 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "gl_local.h" +#include "windows.h" + +float CL_KeyState (kbutton_t *key); + +extern "C" +{ + void DLLEXPORT CAM_Think( void ); + int DLLEXPORT CL_IsThirdPerson( void ); + void DLLEXPORT CL_CameraOffset( float *ofs ); +} + +extern cl_enginefunc_t gEngfuncs; + +//-------------------------------------------------- Constants + +#define CAM_DIST_DELTA 1.0 +#define CAM_ANGLE_DELTA 2.5 +#define CAM_ANGLE_SPEED 2.5 +#define CAM_MIN_DIST 30.0 +#define CAM_ANGLE_MOVE .5 +#define MAX_ANGLE_DIFF 10.0 +#define PITCH_MAX 90.0 +#define PITCH_MIN 0 +#define YAW_MAX 135.0 +#define YAW_MIN -135.0 + +enum ECAM_Command +{ + CAM_COMMAND_NONE = 0, + CAM_COMMAND_TOTHIRDPERSON = 1, + CAM_COMMAND_TOFIRSTPERSON = 2 +}; + +//-------------------------------------------------- Global Variables + +cvar_t *cam_command; +cvar_t *cam_snapto; +cvar_t *cam_idealyaw; +cvar_t *cam_idealpitch; +cvar_t *cam_idealdist; +cvar_t *cam_contain; + +cvar_t *c_maxpitch; +cvar_t *c_minpitch; +cvar_t *c_maxyaw; +cvar_t *c_minyaw; +cvar_t *c_maxdistance; +cvar_t *c_mindistance; + +// pitch, yaw, dist +vec3_t cam_ofs; + + +// In third person +int cam_thirdperson; +int cam_mousemove; //true if we are moving the cam with the mouse, False if not +int iMouseInUse=0; +int cam_distancemove; +extern int mouse_x, mouse_y; //used to determine what the current x and y values are +int cam_old_mouse_x, cam_old_mouse_y; //holds the last ticks mouse movement +POINT cam_mouse; +//-------------------------------------------------- Local Variables + +static kbutton_t cam_pitchup, cam_pitchdown, cam_yawleft, cam_yawright; +static kbutton_t cam_in, cam_out, cam_move; + +//-------------------------------------------------- Prototypes + +void CAM_ToThirdPerson(void); +void CAM_ToFirstPerson(void); +void CAM_StartDistance(void); +void CAM_EndDistance(void); + + +//-------------------------------------------------- Local Functions + +float MoveToward( float cur, float goal, float maxspeed ) +{ + if( cur != goal ) + { + if( abs( cur - goal ) > 180.0 ) + { + if( cur < goal ) + cur += 360.0; + else + cur -= 360.0; + } + + if( cur < goal ) + { + if( cur < goal - 1.0 ) + cur += ( goal - cur ) / 4.0; + else + cur = goal; + } + else + { + if( cur > goal + 1.0 ) + cur -= ( cur - goal ) / 4.0; + else + cur = goal; + } + } + + + // bring cur back into range + if( cur < 0 ) + cur += 360.0; + else if( cur >= 360 ) + cur -= 360; + + return cur; +} + + +//-------------------------------------------------- Gobal Functions + +typedef struct +{ + vec3_t boxmins, boxmaxs;// enclose the test object along entire move + float *mins, *maxs; // size of the moving object + vec3_t mins2, maxs2; // size when clipping against mosnters + float *start, *end; + trace_t trace; + int type; + edict_t *passedict; + qboolean monsterclip; +} moveclip_t; + +extern trace_t SV_ClipMoveToEntity (edict_t *ent, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end); + +void DLLEXPORT CAM_Think( void ) +{ + vec3_t origin; + vec3_t ext, pnt, camForward, camRight, camUp; + moveclip_t clip; + float dist; + vec3_t camAngles; + float flSensitivity; +#ifdef LATER + int i; +#endif + vec3_t viewangles; + + switch( (int) cam_command->value ) + { + case CAM_COMMAND_TOTHIRDPERSON: + CAM_ToThirdPerson(); + break; + + case CAM_COMMAND_TOFIRSTPERSON: + CAM_ToFirstPerson(); + break; + + case CAM_COMMAND_NONE: + default: + break; + } + + if( !cam_thirdperson ) + return; + +#ifdef LATER + if ( cam_contain->value ) + { + gEngfuncs.GetClientOrigin( origin ); + ext[0] = ext[1] = ext[2] = 0.0; + } +#endif + + camAngles[ PITCH ] = cam_idealpitch->value; + camAngles[ YAW ] = cam_idealyaw->value; + dist = cam_idealdist->value; + // + //movement of the camera with the mouse + // + if (cam_mousemove) + { + //get windows cursor position + GetCursorPos (&cam_mouse); + //check for X delta values and adjust accordingly + //eventually adjust YAW based on amount of movement + //don't do any movement of the cam using YAW/PITCH if we are zooming in/out the camera + if (!cam_distancemove) + { + + //keep the camera within certain limits around the player (ie avoid certain bad viewing angles) + if (cam_mouse.x>gEngfuncs.GetWindowCenterX()) + { + //if ((camAngles[YAW]>=225.0)||(camAngles[YAW]<135.0)) + if (camAngles[YAW]value) + { + camAngles[ YAW ] += (CAM_ANGLE_MOVE)*((cam_mouse.x-gEngfuncs.GetWindowCenterX())/2); + } + if (camAngles[YAW]>c_maxyaw->value) + { + + camAngles[YAW]=c_maxyaw->value; + } + } + else if (cam_mouse.x225.0)) + if (camAngles[YAW]>c_minyaw->value) + { + camAngles[ YAW ] -= (CAM_ANGLE_MOVE)* ((gEngfuncs.GetWindowCenterX()-cam_mouse.x)/2); + + } + if (camAngles[YAW]value) + { + camAngles[YAW]=c_minyaw->value; + + } + } + + //check for y delta values and adjust accordingly + //eventually adjust PITCH based on amount of movement + //also make sure camera is within bounds + if (cam_mouse.y>gEngfuncs.GetWindowCenterY()) + { + if(camAngles[PITCH]value) + { + camAngles[PITCH] +=(CAM_ANGLE_MOVE)* ((cam_mouse.y-gEngfuncs.GetWindowCenterY())/2); + } + if (camAngles[PITCH]>c_maxpitch->value) + { + camAngles[PITCH]=c_maxpitch->value; + } + } + else if (cam_mouse.yc_minpitch->value) + { + camAngles[PITCH] -= (CAM_ANGLE_MOVE)*((gEngfuncs.GetWindowCenterY()-cam_mouse.y)/2); + } + if (camAngles[PITCH]value) + { + camAngles[PITCH]=c_minpitch->value; + } + } + + //set old mouse coordinates to current mouse coordinates + //since we are done with the mouse + + if ( ( flSensitivity = gHUD.GetSensitivity() ) != 0 ) + { + cam_old_mouse_x=cam_mouse.x*flSensitivity; + cam_old_mouse_y=cam_mouse.y*flSensitivity; + } + else + { + cam_old_mouse_x=cam_mouse.x; + cam_old_mouse_y=cam_mouse.y; + } + SetCursorPos (gEngfuncs.GetWindowCenterX(), gEngfuncs.GetWindowCenterY()); + } + } + + //Nathan code here + if( CL_KeyState( &cam_pitchup ) ) + camAngles[ PITCH ] += CAM_ANGLE_DELTA; + else if( CL_KeyState( &cam_pitchdown ) ) + camAngles[ PITCH ] -= CAM_ANGLE_DELTA; + + if( CL_KeyState( &cam_yawleft ) ) + camAngles[ YAW ] -= CAM_ANGLE_DELTA; + else if( CL_KeyState( &cam_yawright ) ) + camAngles[ YAW ] += CAM_ANGLE_DELTA; + + if( CL_KeyState( &cam_in ) ) + { + dist -= CAM_DIST_DELTA; + if( dist < CAM_MIN_DIST ) + { + // If we go back into first person, reset the angle + camAngles[ PITCH ] = 0; + camAngles[ YAW ] = 0; + dist = CAM_MIN_DIST; + } + + } + else if( CL_KeyState( &cam_out ) ) + dist += CAM_DIST_DELTA; + + if (cam_distancemove) + { + if (cam_mouse.y>gEngfuncs.GetWindowCenterY()) + { + if(distvalue) + { + dist +=CAM_DIST_DELTA * ((cam_mouse.y-gEngfuncs.GetWindowCenterY())/2); + } + if (dist>c_maxdistance->value) + { + dist=c_maxdistance->value; + } + } + else if (cam_mouse.yc_mindistance->value) + { + dist -= (CAM_DIST_DELTA)*((gEngfuncs.GetWindowCenterY()-cam_mouse.y)/2); + } + if (distvalue) + { + dist=c_mindistance->value; + } + } + //set old mouse coordinates to current mouse coordinates + //since we are done with the mouse + cam_old_mouse_x=cam_mouse.x*gHUD.GetSensitivity(); + cam_old_mouse_y=cam_mouse.y*gHUD.GetSensitivity(); + SetCursorPos (gEngfuncs.GetWindowCenterX(), gEngfuncs.GetWindowCenterY()); + } +#ifdef LATER + if( cam_contain->value ) + { + // check new ideal + VectorCopy( origin, pnt ); + AngleVectors( camAngles, camForward, camRight, camUp ); + for (i=0 ; i<3 ; i++) + pnt[i] += -dist*camForward[i]; + + // check line from r_refdef.vieworg to pnt + memset ( &clip, 0, sizeof ( moveclip_t ) ); + clip.trace = SV_ClipMoveToEntity( sv.edicts, r_refdef.vieworg, ext, ext, pnt ); + if( clip.trace.fraction == 1.0 ) + { + // update ideal + cam_idealpitch->value = camAngles[ PITCH ]; + cam_idealyaw->value = camAngles[ YAW ]; + cam_idealdist->value = dist; + } + } + else +#endif + { + // update ideal + cam_idealpitch->value = camAngles[ PITCH ]; + cam_idealyaw->value = camAngles[ YAW ]; + cam_idealdist->value = dist; + } + + // Move towards ideal + VectorCopy( cam_ofs, camAngles ); + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + if( cam_snapto->value ) + { + camAngles[ YAW ] = cam_idealyaw->value + viewangles[ YAW ]; + camAngles[ PITCH ] = cam_idealpitch->value + viewangles[ PITCH ]; + camAngles[ 2 ] = cam_idealdist->value; + } + else + { + if( camAngles[ YAW ] - viewangles[ YAW ] != cam_idealyaw->value ) + camAngles[ YAW ] = MoveToward( camAngles[ YAW ], cam_idealyaw->value + viewangles[ YAW ], CAM_ANGLE_SPEED ); + + if( camAngles[ PITCH ] - viewangles[ PITCH ] != cam_idealpitch->value ) + camAngles[ PITCH ] = MoveToward( camAngles[ PITCH ], cam_idealpitch->value + viewangles[ PITCH ], CAM_ANGLE_SPEED ); + + if( abs( camAngles[ 2 ] - cam_idealdist->value ) < 2.0 ) + camAngles[ 2 ] = cam_idealdist->value; + else + camAngles[ 2 ] += ( cam_idealdist->value - camAngles[ 2 ] ) / 4.0; + } +#ifdef LATER + if( cam_contain->value ) + { + // Test new position + dist = camAngles[ ROLL ]; + camAngles[ ROLL ] = 0; + + VectorCopy( origin, pnt ); + AngleVectors( camAngles, camForward, camRight, camUp ); + for (i=0 ; i<3 ; i++) + pnt[i] += -dist*camForward[i]; + + // check line from r_refdef.vieworg to pnt + memset ( &clip, 0, sizeof ( moveclip_t ) ); + ext[0] = ext[1] = ext[2] = 0.0; + clip.trace = SV_ClipMoveToEntity( sv.edicts, r_refdef.vieworg, ext, ext, pnt ); + if( clip.trace.fraction != 1.0 ) + return; + } +#endif + cam_ofs[ 0 ] = camAngles[ 0 ]; + cam_ofs[ 1 ] = camAngles[ 1 ]; + cam_ofs[ 2 ] = dist; +} + +extern void KeyDown (kbutton_t *b); // HACK +extern void KeyUp (kbutton_t *b); // HACK + +void CAM_PitchUpDown(void) { KeyDown( &cam_pitchup ); } +void CAM_PitchUpUp(void) { KeyUp( &cam_pitchup ); } +void CAM_PitchDownDown(void) { KeyDown( &cam_pitchdown ); } +void CAM_PitchDownUp(void) { KeyUp( &cam_pitchdown ); } +void CAM_YawLeftDown(void) { KeyDown( &cam_yawleft ); } +void CAM_YawLeftUp(void) { KeyUp( &cam_yawleft ); } +void CAM_YawRightDown(void) { KeyDown( &cam_yawright ); } +void CAM_YawRightUp(void) { KeyUp( &cam_yawright ); } +void CAM_InDown(void) { KeyDown( &cam_in ); } +void CAM_InUp(void) { KeyUp( &cam_in ); } +void CAM_OutDown(void) { KeyDown( &cam_out ); } +void CAM_OutUp(void) { KeyUp( &cam_out ); } + +void CAM_ToThirdPerson(void) +{ + vec3_t viewangles; + +#if !defined( _DEBUG ) + if ( gEngfuncs.GetMaxClients() > 1 ) + { + // no thirdperson in multiplayer. + return; + } +#endif + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + if( !cam_thirdperson ) + { + cam_thirdperson = 1; + + cam_ofs[ YAW ] = viewangles[ YAW ]; + cam_ofs[ PITCH ] = viewangles[ PITCH ]; + cam_ofs[ 2 ] = CAM_MIN_DIST; + } + + gEngfuncs.Cvar_SetValue( "cam_command", 0 ); +} + +void CAM_ToFirstPerson(void) +{ + cam_thirdperson = 0; + + gEngfuncs.Cvar_SetValue( "cam_command", 0 ); +} + +void CAM_ToggleSnapto( void ) +{ + cam_snapto->value = !cam_snapto->value; +} + +void CAM_Init( void ) +{ + gEngfuncs.pfnAddCommand( "+campitchup", CAM_PitchUpDown ); + gEngfuncs.pfnAddCommand( "-campitchup", CAM_PitchUpUp ); + gEngfuncs.pfnAddCommand( "+campitchdown", CAM_PitchDownDown ); + gEngfuncs.pfnAddCommand( "-campitchdown", CAM_PitchDownUp ); + gEngfuncs.pfnAddCommand( "+camyawleft", CAM_YawLeftDown ); + gEngfuncs.pfnAddCommand( "-camyawleft", CAM_YawLeftUp ); + gEngfuncs.pfnAddCommand( "+camyawright", CAM_YawRightDown ); + gEngfuncs.pfnAddCommand( "-camyawright", CAM_YawRightUp ); + gEngfuncs.pfnAddCommand( "+camin", CAM_InDown ); + gEngfuncs.pfnAddCommand( "-camin", CAM_InUp ); + gEngfuncs.pfnAddCommand( "+camout", CAM_OutDown ); + gEngfuncs.pfnAddCommand( "-camout", CAM_OutUp ); + gEngfuncs.pfnAddCommand( "thirdperson", CAM_ToThirdPerson ); + gEngfuncs.pfnAddCommand( "firstperson", CAM_ToFirstPerson ); + gEngfuncs.pfnAddCommand( "+cammousemove",CAM_StartMouseMove); + gEngfuncs.pfnAddCommand( "-cammousemove",CAM_EndMouseMove); + gEngfuncs.pfnAddCommand( "+camdistance", CAM_StartDistance ); + gEngfuncs.pfnAddCommand( "-camdistance", CAM_EndDistance ); + gEngfuncs.pfnAddCommand( "snapto", CAM_ToggleSnapto ); + + cam_command = gEngfuncs.pfnRegisterVariable ( "cam_command", "0", 0 ); // tells camera to go to thirdperson + cam_snapto = gEngfuncs.pfnRegisterVariable ( "cam_snapto", "0", 0 ); // snap to thirdperson view + cam_idealyaw = gEngfuncs.pfnRegisterVariable ( "cam_idealyaw", "90", 0 ); // thirdperson yaw + cam_idealpitch = gEngfuncs.pfnRegisterVariable ( "cam_idealpitch", "0", 0 ); // thirperson pitch + cam_idealdist = gEngfuncs.pfnRegisterVariable ( "cam_idealdist", "64", 0 ); // thirdperson distance + cam_contain = gEngfuncs.pfnRegisterVariable ( "cam_contain", "0", 0 ); // contain camera to world + + c_maxpitch = gEngfuncs.pfnRegisterVariable ( "c_maxpitch", "90.0", 0 ); + c_minpitch = gEngfuncs.pfnRegisterVariable ( "c_minpitch", "0.0", 0 ); + c_maxyaw = gEngfuncs.pfnRegisterVariable ( "c_maxyaw", "135.0", 0 ); + c_minyaw = gEngfuncs.pfnRegisterVariable ( "c_minyaw", "-135.0", 0 ); + c_maxdistance = gEngfuncs.pfnRegisterVariable ( "c_maxdistance", "200.0", 0 ); + c_mindistance = gEngfuncs.pfnRegisterVariable ( "c_mindistance", "30.0", 0 ); +} + +void CAM_ClearStates( void ) +{ + vec3_t viewangles; + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + cam_pitchup.state = 0; + cam_pitchdown.state = 0; + cam_yawleft.state = 0; + cam_yawright.state = 0; + cam_in.state = 0; + cam_out.state = 0; + + cam_thirdperson = 0; + cam_command->value = 0; + cam_mousemove=0; + + cam_snapto->value = 0; + cam_distancemove = 0; + + cam_ofs[ 0 ] = 0.0; + cam_ofs[ 1 ] = 0.0; + cam_ofs[ 2 ] = CAM_MIN_DIST; + + cam_idealpitch->value = viewangles[ PITCH ]; + cam_idealyaw->value = viewangles[ YAW ]; + cam_idealdist->value = CAM_MIN_DIST; +} + +void CAM_StartMouseMove(void) +{ + float flSensitivity; + + //only move the cam with mouse if we are in third person. + if (cam_thirdperson) + { + //set appropriate flags and initialize the old mouse position + //variables for mouse camera movement + if (!cam_mousemove) + { + cam_mousemove=1; + iMouseInUse=1; + GetCursorPos (&cam_mouse); + + if ( ( flSensitivity = gHUD.GetSensitivity() ) != 0 ) + { + cam_old_mouse_x=cam_mouse.x*flSensitivity; + cam_old_mouse_y=cam_mouse.y*flSensitivity; + } + else + { + cam_old_mouse_x=cam_mouse.x; + cam_old_mouse_y=cam_mouse.y; + } + } + } + //we are not in 3rd person view..therefore do not allow camera movement + else + { + cam_mousemove=0; + iMouseInUse=0; + } +} + +//the key has been released for camera movement +//tell the engine that mouse camera movement is off +void CAM_EndMouseMove(void) +{ + cam_mousemove=0; + iMouseInUse=0; +} + + +//---------------------------------------------------------- +//routines to start the process of moving the cam in or out +//using the mouse +//---------------------------------------------------------- +void CAM_StartDistance(void) +{ + //only move the cam with mouse if we are in third person. + if (cam_thirdperson) + { + //set appropriate flags and initialize the old mouse position + //variables for mouse camera movement + if (!cam_distancemove) + { + cam_distancemove=1; + cam_mousemove=1; + iMouseInUse=1; + GetCursorPos (&cam_mouse); + cam_old_mouse_x=cam_mouse.x*gHUD.GetSensitivity(); + cam_old_mouse_y=cam_mouse.y*gHUD.GetSensitivity(); + } + } + //we are not in 3rd person view..therefore do not allow camera movement + else + { + cam_distancemove=0; + cam_mousemove=0; + iMouseInUse=0; + } +} + +//the key has been released for camera movement +//tell the engine that mouse camera movement is off +void CAM_EndDistance(void) +{ + cam_distancemove=0; + cam_mousemove=0; + iMouseInUse=0; +} + +int DLLEXPORT CL_IsThirdPerson( void ) +{ + return (cam_thirdperson ? 1 : 0) || (g_iUser1 && (g_iUser2 == gEngfuncs.GetLocalPlayer()->index) ); +} + +void DLLEXPORT CL_CameraOffset( float *ofs ) +{ + VectorCopy( cam_ofs, ofs ); +} \ No newline at end of file diff --git a/cl_dll/in_defs.h b/cl_dll/in_defs.h new file mode 100644 index 0000000..4e48f22 --- /dev/null +++ b/cl_dll/in_defs.h @@ -0,0 +1,24 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( IN_DEFSH ) +#define IN_DEFSH +#pragma once + +// up / down +#define PITCH 0 +// left / right +#define YAW 1 +// fall over +#define ROLL 2 + +#define DLLEXPORT __declspec( dllexport ) + +void V_StartPitchDrift( void ); +void V_StopPitchDrift( void ); + +#endif \ No newline at end of file diff --git a/cl_dll/input.cpp b/cl_dll/input.cpp new file mode 100644 index 0000000..a4b2ddc --- /dev/null +++ b/cl_dll/input.cpp @@ -0,0 +1,1051 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// cl.input.c -- builds an intended movement command to send to the server + +//xxxxxx Move bob and pitch drifting code here and other stuff from view if needed + +// Quake is a trademark of Id Software, Inc., (c) 1996 Id Software, Inc. All +// rights reserved. +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include +#include + +#include "vgui_TeamFortressViewport.h" + + +extern "C" +{ + struct kbutton_s DLLEXPORT *KB_Find( const char *name ); + void DLLEXPORT CL_CreateMove ( float frametime, struct usercmd_s *cmd, int active ); + int DLLEXPORT HUD_Key_Event( int eventcode, int keynum, const char *pszCurrentBinding ); +} + +extern int g_iAlive; + +extern int g_weaponselect; +extern cl_enginefunc_t gEngfuncs; + +void IN_Init (void); +void IN_Move ( float frametime, usercmd_t *cmd); +void IN_Shutdown( void ); +void V_Init( void ); +void VectorAngles( const float *forward, float *angles ); +int CL_ButtonBits( int ); + +// xxx need client dll function to get and clear impuse +extern cvar_t *in_joystick; + +int in_impulse = 0; +int in_cancel = 0; + +cvar_t *m_pitch; +cvar_t *m_yaw; +cvar_t *m_forward; +cvar_t *m_side; + +cvar_t *lookstrafe; +cvar_t *lookspring; +cvar_t *cl_pitchup; +cvar_t *cl_pitchdown; +cvar_t *cl_upspeed; +cvar_t *cl_forwardspeed; +cvar_t *cl_backspeed; +cvar_t *cl_sidespeed; +cvar_t *cl_movespeedkey; +cvar_t *cl_yawspeed; +cvar_t *cl_pitchspeed; +cvar_t *cl_anglespeedkey; +cvar_t *cl_vsmoothing; +/* +=============================================================================== + +KEY BUTTONS + +Continuous button event tracking is complicated by the fact that two different +input sources (say, mouse button 1 and the control key) can both press the +same button, but the button should only be released when both of the +pressing key have been released. + +When a key event issues a button command (+forward, +attack, etc), it appends +its key number as a parameter to the command so it can be matched up with +the release. + +state bit 0 is the current state of the key +state bit 1 is edge triggered on the up to down transition +state bit 2 is edge triggered on the down to up transition + +=============================================================================== +*/ + + +kbutton_t in_mlook; +kbutton_t in_klook; +kbutton_t in_jlook; +kbutton_t in_left; +kbutton_t in_right; +kbutton_t in_forward; +kbutton_t in_back; +kbutton_t in_lookup; +kbutton_t in_lookdown; +kbutton_t in_moveleft; +kbutton_t in_moveright; +kbutton_t in_strafe; +kbutton_t in_speed; +kbutton_t in_use; +kbutton_t in_jump; +kbutton_t in_attack; +kbutton_t in_attack2; +kbutton_t in_up; +kbutton_t in_down; +kbutton_t in_duck; +kbutton_t in_reload; +kbutton_t in_alt1; +kbutton_t in_score; +kbutton_t in_break; +kbutton_t in_graph; // Display the netgraph +kbutton_t in_sprint; + +typedef struct kblist_s +{ + struct kblist_s *next; + kbutton_t *pkey; + char name[32]; +} kblist_t; + +kblist_t *g_kbkeys = NULL; + +/* +============ +KB_ConvertString + +Removes references to +use and replaces them with the keyname in the output string. If + a binding is unfound, then the original text is retained. +NOTE: Only works for text with +word in it. +============ +*/ +int KB_ConvertString( char *in, char **ppout ) +{ + char sz[ 4096 ]; + char binding[ 64 ]; + char *p; + char *pOut; + char *pEnd; + const char *pBinding; + + if ( !ppout ) + return 0; + + *ppout = NULL; + p = in; + pOut = sz; + while ( *p ) + { + if ( *p == '+' ) + { + pEnd = binding; + while ( *p && ( isalnum( *p ) || ( pEnd == binding ) ) && ( ( pEnd - binding ) < 63 ) ) + { + *pEnd++ = *p++; + } + + *pEnd = '\0'; + + pBinding = NULL; + if ( strlen( binding + 1 ) > 0 ) + { + // See if there is a binding for binding? + pBinding = gEngfuncs.Key_LookupBinding( binding + 1 ); + } + + if ( pBinding ) + { + *pOut++ = '['; + pEnd = (char *)pBinding; + } + else + { + pEnd = binding; + } + + while ( *pEnd ) + { + *pOut++ = *pEnd++; + } + + if ( pBinding ) + { + *pOut++ = ']'; + } + } + else + { + *pOut++ = *p++; + } + } + + *pOut = '\0'; + + pOut = ( char * )malloc( strlen( sz ) + 1 ); + strcpy( pOut, sz ); + *ppout = pOut; + + return 1; +} + +/* +============ +KB_Find + +Allows the engine to get a kbutton_t directly ( so it can check +mlook state, etc ) for saving out to .cfg files +============ +*/ +struct kbutton_s DLLEXPORT *KB_Find( const char *name ) +{ + kblist_t *p; + p = g_kbkeys; + while ( p ) + { + if ( !stricmp( name, p->name ) ) + return p->pkey; + + p = p->next; + } + return NULL; +} + +/* +============ +KB_Add + +Add a kbutton_t * to the list of pointers the engine can retrieve via KB_Find +============ +*/ +void KB_Add( const char *name, kbutton_t *pkb ) +{ + kblist_t *p; + kbutton_t *kb; + + kb = KB_Find( name ); + + if ( kb ) + return; + + p = ( kblist_t * )malloc( sizeof( kblist_t ) ); + memset( p, 0, sizeof( *p ) ); + + strcpy( p->name, name ); + p->pkey = pkb; + + p->next = g_kbkeys; + g_kbkeys = p; +} + +/* +============ +KB_Init + +Add kbutton_t definitions that the engine can query if needed +============ +*/ +void KB_Init( void ) +{ + g_kbkeys = NULL; + + KB_Add( "in_graph", &in_graph ); + KB_Add( "in_mlook", &in_mlook ); + KB_Add( "in_jlook", &in_jlook ); +} + +/* +============ +KB_Shutdown + +Clear kblist +============ +*/ +void KB_Shutdown( void ) +{ + kblist_t *p, *n; + p = g_kbkeys; + while ( p ) + { + n = p->next; + free( p ); + p = n; + } + g_kbkeys = NULL; +} + +/* +============ +KeyDown +============ +*/ +void KeyDown (kbutton_t *b) +{ + int k; + char *c; + + c = gEngfuncs.Cmd_Argv(1); + if (c[0]) + k = atoi(c); + else + k = -1; // typed manually at the console for continuous down + + if (k == b->down[0] || k == b->down[1]) + return; // repeating key + + if (!b->down[0]) + b->down[0] = k; + else if (!b->down[1]) + b->down[1] = k; + else + { + gEngfuncs.Con_DPrintf ("Three keys down for a button '%c' '%c' '%c'!\n", b->down[0], b->down[1], c); + return; + } + + if (b->state & 1) + return; // still down + b->state |= 1 + 2; // down + impulse down +} + +/* +============ +KeyUp +============ +*/ +void KeyUp (kbutton_t *b) +{ + int k; + char *c; + + c = gEngfuncs.Cmd_Argv(1); + if (c[0]) + k = atoi(c); + else + { // typed manually at the console, assume for unsticking, so clear all + b->down[0] = b->down[1] = 0; + b->state = 4; // impulse up + return; + } + + if (b->down[0] == k) + b->down[0] = 0; + else if (b->down[1] == k) + b->down[1] = 0; + else + return; // key up without coresponding down (menu pass through) + if (b->down[0] || b->down[1]) + { + //Con_Printf ("Keys down for button: '%c' '%c' '%c' (%d,%d,%d)!\n", b->down[0], b->down[1], c, b->down[0], b->down[1], c); + return; // some other key is still holding it down + } + + if (!(b->state & 1)) + return; // still up (this should not happen) + + b->state &= ~1; // now up + b->state |= 4; // impulse up +} + +/* +============ +HUD_Key_Event + +Return 1 to allow engine to process the key, otherwise, act on it as needed +============ +*/ +int DLLEXPORT HUD_Key_Event( int down, int keynum, const char *pszCurrentBinding ) +{ + if (gViewPort) + return gViewPort->KeyInput(down, keynum, pszCurrentBinding); + + return 1; +} + +void IN_BreakDown( void ) { KeyDown( &in_break );}; +void IN_BreakUp( void ) { KeyUp( &in_break ); }; +void IN_KLookDown (void) {KeyDown(&in_klook);} +void IN_KLookUp (void) {KeyUp(&in_klook);} +void IN_JLookDown (void) {KeyDown(&in_jlook);} +void IN_JLookUp (void) {KeyUp(&in_jlook);} +void IN_MLookDown (void) {KeyDown(&in_mlook);} +void IN_UpDown(void) {KeyDown(&in_up);} +void IN_UpUp(void) {KeyUp(&in_up);} +void IN_DownDown(void) {KeyDown(&in_down);} +void IN_DownUp(void) {KeyUp(&in_down);} +void IN_LeftDown(void) {KeyDown(&in_left);} +void IN_LeftUp(void) {KeyUp(&in_left);} +void IN_RightDown(void) {KeyDown(&in_right);} +void IN_RightUp(void) {KeyUp(&in_right);} + +void IN_ForwardDown(void) +{ + KeyDown(&in_forward); + gHUD.m_Spectator.HandleButtonsDown( IN_FORWARD ); +} + +void IN_ForwardUp(void) +{ + KeyUp(&in_forward); + gHUD.m_Spectator.HandleButtonsUp( IN_FORWARD ); +} + +void IN_BackDown(void) +{ + KeyDown(&in_back); + gHUD.m_Spectator.HandleButtonsDown( IN_BACK ); +} + +void IN_BackUp(void) +{ + KeyUp(&in_back); + gHUD.m_Spectator.HandleButtonsUp( IN_BACK ); +} +void IN_LookupDown(void) {KeyDown(&in_lookup);} +void IN_LookupUp(void) {KeyUp(&in_lookup);} +void IN_LookdownDown(void) {KeyDown(&in_lookdown);} +void IN_LookdownUp(void) {KeyUp(&in_lookdown);} +void IN_MoveleftDown(void) +{ + KeyDown(&in_moveleft); + gHUD.m_Spectator.HandleButtonsDown( IN_MOVELEFT ); +} + +void IN_MoveleftUp(void) +{ + KeyUp(&in_moveleft); + gHUD.m_Spectator.HandleButtonsUp( IN_MOVELEFT ); +} + +void IN_MoverightDown(void) +{ + KeyDown(&in_moveright); + gHUD.m_Spectator.HandleButtonsDown( IN_MOVERIGHT ); +} + +void IN_MoverightUp(void) +{ + KeyUp(&in_moveright); + gHUD.m_Spectator.HandleButtonsUp( IN_MOVERIGHT ); +} +void IN_SpeedDown(void) {KeyDown(&in_speed);} +void IN_SpeedUp(void) {KeyUp(&in_speed);} + +void IN_SprintDown(void) {KeyDown(&in_sprint);} +void IN_SprintUp(void) {KeyUp(&in_sprint);} + +void IN_StrafeDown(void) {KeyDown(&in_strafe);} +void IN_StrafeUp(void) {KeyUp(&in_strafe);} + +// needs capture by hud/vgui also +extern void __CmdFunc_InputPlayerSpecial(void); + +void IN_Attack2Down(void) +{ + KeyDown(&in_attack2); + + gHUD.m_Spectator.HandleButtonsDown( IN_ATTACK2 ); +} + +void IN_Attack2Up(void) {KeyUp(&in_attack2);} +void IN_UseDown (void) +{ + KeyDown(&in_use); + gHUD.m_Spectator.HandleButtonsDown( IN_USE ); +} +void IN_UseUp (void) {KeyUp(&in_use);} +void IN_JumpDown (void) +{ + KeyDown(&in_jump); + gHUD.m_Spectator.HandleButtonsDown( IN_JUMP ); + +} +void IN_JumpUp (void) {KeyUp(&in_jump);} +void IN_DuckDown(void) +{ + KeyDown(&in_duck); + gHUD.m_Spectator.HandleButtonsDown( IN_DUCK ); + +} +void IN_DuckUp(void) {KeyUp(&in_duck);} +void IN_ReloadDown(void) {KeyDown(&in_reload);} +void IN_ReloadUp(void) {KeyUp(&in_reload);} +void IN_Alt1Down(void) {KeyDown(&in_alt1);} +void IN_Alt1Up(void) {KeyUp(&in_alt1);} +void IN_GraphDown(void) {KeyDown(&in_graph);} +void IN_GraphUp(void) {KeyUp(&in_graph);} + +void IN_AttackDown(void) +{ + KeyDown( &in_attack ); + gHUD.m_Spectator.HandleButtonsDown( IN_ATTACK ); +} + +void IN_AttackUp(void) +{ + KeyUp( &in_attack ); + in_cancel = 0; +} + +// Special handling +void IN_Cancel(void) +{ + in_cancel = 1; +} + +void IN_Impulse (void) +{ + in_impulse = atoi( gEngfuncs.Cmd_Argv(1) ); +} + +void IN_ScoreDown(void) +{ + KeyDown(&in_score); + if ( gViewPort ) + { + gViewPort->ShowScoreBoard(); + } +} + +void IN_ScoreUp(void) +{ + KeyUp(&in_score); + if ( gViewPort ) + { + gViewPort->HideScoreBoard(); + } +} + +void IN_MLookUp (void) +{ + KeyUp( &in_mlook ); + if ( !( in_mlook.state & 1 ) && lookspring->value ) + { + V_StartPitchDrift(); + } +} + +/* +=============== +CL_KeyState + +Returns 0.25 if a key was pressed and released during the frame, +0.5 if it was pressed and held +0 if held then released, and +1.0 if held for the entire time +=============== +*/ +float CL_KeyState (kbutton_t *key) +{ + float val = 0.0; + int impulsedown, impulseup, down; + + impulsedown = key->state & 2; + impulseup = key->state & 4; + down = key->state & 1; + + if ( impulsedown && !impulseup ) + { + // pressed and held this frame? + val = down ? 0.5 : 0.0; + } + + if ( impulseup && !impulsedown ) + { + // released this frame? + val = down ? 0.0 : 0.0; + } + + if ( !impulsedown && !impulseup ) + { + // held the entire frame? + val = down ? 1.0 : 0.0; + } + + if ( impulsedown && impulseup ) + { + if ( down ) + { + // released and re-pressed this frame + val = 0.75; + } + else + { + // pressed and released this frame + val = 0.25; + } + } + + // clear impulses + key->state &= 1; + return val; +} + +/* +================ +CL_AdjustAngles + +Moves the local angle positions +================ +*/ +void CL_AdjustAngles ( float frametime, float *viewangles ) +{ + float speed; + float up, down; + + if (in_speed.state & 1) + { + speed = frametime * cl_anglespeedkey->value; + } + else + { + speed = frametime; + } + + if (!(in_strafe.state & 1)) + { + viewangles[YAW] -= speed*cl_yawspeed->value*CL_KeyState (&in_right); + viewangles[YAW] += speed*cl_yawspeed->value*CL_KeyState (&in_left); + viewangles[YAW] = anglemod(viewangles[YAW]); + } + if (in_klook.state & 1) + { + V_StopPitchDrift (); + viewangles[PITCH] -= speed*cl_pitchspeed->value * CL_KeyState (&in_forward); + viewangles[PITCH] += speed*cl_pitchspeed->value * CL_KeyState (&in_back); + } + + up = CL_KeyState (&in_lookup); + down = CL_KeyState(&in_lookdown); + + viewangles[PITCH] -= speed*cl_pitchspeed->value * up; + viewangles[PITCH] += speed*cl_pitchspeed->value * down; + + if (up || down) + V_StopPitchDrift (); + + if (viewangles[PITCH] > cl_pitchdown->value) + viewangles[PITCH] = cl_pitchdown->value; + if (viewangles[PITCH] < -cl_pitchup->value) + viewangles[PITCH] = -cl_pitchup->value; + + if (viewangles[ROLL] > 50) + viewangles[ROLL] = 50; + if (viewangles[ROLL] < -50) + viewangles[ROLL] = -50; +} + +/* +================ +CL_CreateMove + +Send the intended movement message to the server +if active == 1 then we are 1) not playing back demos ( where our commands are ignored ) and +2 ) we have finished signing on to server +================ +*/ +void DLLEXPORT CL_CreateMove ( float frametime, struct usercmd_s *cmd, int active ) +{ + vec3_t viewangles; + static vec3_t oldangles; + + if ( active ) + { + //memset( viewangles, 0, sizeof( vec3_t ) ); + //viewangles[ 0 ] = viewangles[ 1 ] = viewangles[ 2 ] = 0.0; + gEngfuncs.GetViewAngles( (float *)viewangles ); + + CL_AdjustAngles ( frametime, viewangles ); + + memset (cmd, 0, sizeof(*cmd)); + + gEngfuncs.SetViewAngles( (float *)viewangles ); + + if ( in_strafe.state & 1 ) + { + cmd->sidemove += cl_sidespeed->value * CL_KeyState (&in_right); + cmd->sidemove -= cl_sidespeed->value * CL_KeyState (&in_left); + } + + cmd->sidemove += cl_sidespeed->value * CL_KeyState (&in_moveright); + cmd->sidemove -= cl_sidespeed->value * CL_KeyState (&in_moveleft); + + cmd->upmove += cl_upspeed->value * CL_KeyState (&in_up); + cmd->upmove -= cl_upspeed->value * CL_KeyState (&in_down); + + if ( !(in_klook.state & 1 ) ) + { + cmd->forwardmove += cl_forwardspeed->value * CL_KeyState (&in_forward); + cmd->forwardmove -= cl_backspeed->value * CL_KeyState (&in_back); + } + + // adjust for speed key + if ( in_speed.state & 1 ) + { + cmd->forwardmove *= cl_movespeedkey->value; + cmd->sidemove *= cl_movespeedkey->value; + cmd->upmove *= cl_movespeedkey->value; + } + + // clip to maxspeed + // buz: little fix - dont need to do it here, pm code will handle it + /* float spd = gEngfuncs.GetClientMaxspeed(); + if ( spd != 0.0 ) + { + // scale the 3 speeds so that the total velocity is not > cl.maxspeed + float fmov = sqrt( (cmd->forwardmove*cmd->forwardmove) + (cmd->sidemove*cmd->sidemove) + (cmd->upmove*cmd->upmove) ); + + if ( fmov > spd ) + { + float fratio = spd / fmov; + cmd->forwardmove *= fratio; + cmd->sidemove *= fratio; + cmd->upmove *= fratio; + } + }*/ + + // Allow mice and other controllers to add their inputs + IN_Move ( frametime, cmd ); + } + + cmd->impulse = in_impulse; + in_impulse = 0; + + cmd->weaponselect = g_weaponselect; + g_weaponselect = 0; + // + // set button and flag bits + // + cmd->buttons = CL_ButtonBits( 1 ); + + // If they're in a modal dialog, ignore the attack button. + if(GetClientVoiceMgr()->IsInSquelchMode()) + cmd->buttons &= ~IN_ATTACK; + + // Using joystick? + if ( in_joystick->value ) + { + if ( cmd->forwardmove > 0 ) + { + cmd->buttons |= IN_FORWARD; + } + else if ( cmd->forwardmove < 0 ) + { + cmd->buttons |= IN_BACK; + } + } + + gEngfuncs.GetViewAngles( (float *)viewangles ); + // Set current view angles. + +// buz: in spec tank mode check for bounds +// gEngfuncs.Con_Printf("pitch: %f, yaw: %f\n", viewangles[PITCH], viewangles[YAW]); + if (gHUD.m_SpecTank_on) + { + // check yaw + float ofs = viewangles[YAW] - gHUD.m_SpecTank_defYaw; + if (ofs > 180) ofs -= 360; + else if (ofs < -180) ofs += 360; + + if (ofs < -gHUD.m_SpecTank_coneHor) + viewangles[YAW] = gHUD.m_SpecTank_defYaw - gHUD.m_SpecTank_coneHor; + else if (ofs > gHUD.m_SpecTank_coneHor) + viewangles[YAW] = gHUD.m_SpecTank_defYaw + gHUD.m_SpecTank_coneHor; + + // check pitch + if (viewangles[PITCH] > gHUD.m_SpecTank_coneVer) + viewangles[PITCH] = gHUD.m_SpecTank_coneVer; + else if (viewangles[PITCH] < -gHUD.m_SpecTank_coneVer) + viewangles[PITCH] = -gHUD.m_SpecTank_coneVer; + + // âñåì ñïàñèáî, âñå ñâîáîäíû + gEngfuncs.SetViewAngles( (float *)viewangles ); + } + + if ( g_iAlive ) + { + VectorCopy( viewangles, cmd->viewangles ); + VectorCopy( viewangles, oldangles ); + } + else + { + VectorCopy( oldangles, cmd->viewangles ); + } +} + +/* +============ +CL_IsDead + +Returns 1 if health is <= 0 +============ +*/ +int CL_IsDead( void ) +{ + return ( gHUD.m_Health.m_iHealth <= 0 ) ? 1 : 0; +} + +/* +============ +CL_ButtonBits + +Returns appropriate button info for keyboard and mouse state +Set bResetState to 1 to clear old state info +============ +*/ +int CL_ButtonBits( int bResetState ) +{ + int bits = 0; + + if ( in_attack.state & 3 ) + { + bits |= IN_ATTACK; + } + + if (in_duck.state & 3) + { + bits |= IN_DUCK; + } + + if (in_jump.state & 3) + { + bits |= IN_JUMP; + } + + if ( in_forward.state & 3 ) + { + bits |= IN_FORWARD; + } + + if (in_back.state & 3) + { + bits |= IN_BACK; + } + + if (in_use.state & 3) + { + bits |= IN_USE; + } + + if (in_cancel) + { + bits |= IN_CANCEL; + } + + if ( in_left.state & 3 ) + { + bits |= IN_LEFT; + } + + if (in_right.state & 3) + { + bits |= IN_RIGHT; + } + + if ( in_moveleft.state & 3 ) + { + bits |= IN_MOVELEFT; + } + + if (in_moveright.state & 3) + { + bits |= IN_MOVERIGHT; + } + + if (in_attack2.state & 3) + { + bits |= IN_ATTACK2; + } + + if (in_reload.state & 3) + { + bits |= IN_RELOAD; + } + + if (in_alt1.state & 3) + { + bits |= IN_ALT1; + } + + if ( in_score.state & 3 ) + { + bits |= IN_SCORE; + } + + if ( in_sprint.state & 3 ) + { + bits |= IN_RUN; + } + + // Dead or in intermission? Shore scoreboard, too + if ( CL_IsDead() || gHUD.m_iIntermission ) + { + bits |= IN_SCORE; + } + + if ( bResetState ) + { + in_attack.state &= ~2; + in_duck.state &= ~2; + in_jump.state &= ~2; + in_forward.state &= ~2; + in_back.state &= ~2; + in_use.state &= ~2; + in_left.state &= ~2; + in_right.state &= ~2; + in_moveleft.state &= ~2; + in_moveright.state &= ~2; + in_attack2.state &= ~2; + in_reload.state &= ~2; + in_alt1.state &= ~2; + in_score.state &= ~2; + in_sprint.state &= ~2; + } + + return bits; +} + +/* +============ +CL_ResetButtonBits + +============ +*/ +void CL_ResetButtonBits( int bits ) +{ + int bitsNew = CL_ButtonBits( 0 ) ^ bits; + + // Has the attack button been changed + if ( bitsNew & IN_ATTACK ) + { + // Was it pressed? or let go? + if ( bits & IN_ATTACK ) + { + KeyDown( &in_attack ); + } + else + { + // totally clear state + in_attack.state &= ~7; + } + } +} + +/* +============ +InitInput +============ +*/ +void InitInput (void) +{ + gEngfuncs.pfnAddCommand ("+moveup",IN_UpDown); + gEngfuncs.pfnAddCommand ("-moveup",IN_UpUp); + gEngfuncs.pfnAddCommand ("+movedown",IN_DownDown); + gEngfuncs.pfnAddCommand ("-movedown",IN_DownUp); + gEngfuncs.pfnAddCommand ("+left",IN_LeftDown); + gEngfuncs.pfnAddCommand ("-left",IN_LeftUp); + gEngfuncs.pfnAddCommand ("+right",IN_RightDown); + gEngfuncs.pfnAddCommand ("-right",IN_RightUp); + gEngfuncs.pfnAddCommand ("+forward",IN_ForwardDown); + gEngfuncs.pfnAddCommand ("-forward",IN_ForwardUp); + gEngfuncs.pfnAddCommand ("+back",IN_BackDown); + gEngfuncs.pfnAddCommand ("-back",IN_BackUp); + gEngfuncs.pfnAddCommand ("+lookup", IN_LookupDown); + gEngfuncs.pfnAddCommand ("-lookup", IN_LookupUp); + gEngfuncs.pfnAddCommand ("+lookdown", IN_LookdownDown); + gEngfuncs.pfnAddCommand ("-lookdown", IN_LookdownUp); + gEngfuncs.pfnAddCommand ("+strafe", IN_StrafeDown); + gEngfuncs.pfnAddCommand ("-strafe", IN_StrafeUp); + gEngfuncs.pfnAddCommand ("+moveleft", IN_MoveleftDown); + gEngfuncs.pfnAddCommand ("-moveleft", IN_MoveleftUp); + gEngfuncs.pfnAddCommand ("+moveright", IN_MoverightDown); + gEngfuncs.pfnAddCommand ("-moveright", IN_MoverightUp); + gEngfuncs.pfnAddCommand ("+speed", IN_SpeedDown); + gEngfuncs.pfnAddCommand ("-speed", IN_SpeedUp); + gEngfuncs.pfnAddCommand ("+attack", IN_AttackDown); + gEngfuncs.pfnAddCommand ("-attack", IN_AttackUp); + gEngfuncs.pfnAddCommand ("+attack2", IN_Attack2Down); + gEngfuncs.pfnAddCommand ("-attack2", IN_Attack2Up); + gEngfuncs.pfnAddCommand ("+use", IN_UseDown); + gEngfuncs.pfnAddCommand ("-use", IN_UseUp); + gEngfuncs.pfnAddCommand ("+jump", IN_JumpDown); + gEngfuncs.pfnAddCommand ("-jump", IN_JumpUp); + gEngfuncs.pfnAddCommand ("impulse", IN_Impulse); + gEngfuncs.pfnAddCommand ("+klook", IN_KLookDown); + gEngfuncs.pfnAddCommand ("-klook", IN_KLookUp); + gEngfuncs.pfnAddCommand ("+mlook", IN_MLookDown); + gEngfuncs.pfnAddCommand ("-mlook", IN_MLookUp); + gEngfuncs.pfnAddCommand ("+jlook", IN_JLookDown); + gEngfuncs.pfnAddCommand ("-jlook", IN_JLookUp); + gEngfuncs.pfnAddCommand ("+duck", IN_DuckDown); + gEngfuncs.pfnAddCommand ("-duck", IN_DuckUp); + gEngfuncs.pfnAddCommand ("+reload", IN_ReloadDown); + gEngfuncs.pfnAddCommand ("-reload", IN_ReloadUp); + gEngfuncs.pfnAddCommand ("+alt1", IN_Alt1Down); + gEngfuncs.pfnAddCommand ("-alt1", IN_Alt1Up); + gEngfuncs.pfnAddCommand ("+score", IN_ScoreDown); + gEngfuncs.pfnAddCommand ("-score", IN_ScoreUp); + gEngfuncs.pfnAddCommand ("-sprint", IN_SprintUp); + gEngfuncs.pfnAddCommand ("+sprint", IN_SprintDown); + gEngfuncs.pfnAddCommand ("+showscores", IN_ScoreDown); + gEngfuncs.pfnAddCommand ("-showscores", IN_ScoreUp); + gEngfuncs.pfnAddCommand ("+graph", IN_GraphDown); + gEngfuncs.pfnAddCommand ("-graph", IN_GraphUp); + gEngfuncs.pfnAddCommand ("+break",IN_BreakDown); + gEngfuncs.pfnAddCommand ("-break",IN_BreakUp); + + lookstrafe = gEngfuncs.pfnRegisterVariable ( "lookstrafe", "0", FCVAR_ARCHIVE ); + lookspring = gEngfuncs.pfnRegisterVariable ( "lookspring", "0", FCVAR_ARCHIVE ); + cl_anglespeedkey = gEngfuncs.pfnRegisterVariable ( "cl_anglespeedkey", "0.67", 0 ); + cl_yawspeed = gEngfuncs.pfnRegisterVariable ( "cl_yawspeed", "210", 0 ); + cl_pitchspeed = gEngfuncs.pfnRegisterVariable ( "cl_pitchspeed", "225", 0 ); + cl_upspeed = gEngfuncs.pfnRegisterVariable ( "cl_upspeed", "320", FCVAR_ARCHIVE ); + cl_forwardspeed = gEngfuncs.pfnRegisterVariable ( "cl_forwardspeed", "400", FCVAR_ARCHIVE ); + cl_backspeed = gEngfuncs.pfnRegisterVariable ( "cl_backspeed", "400", FCVAR_ARCHIVE ); + cl_sidespeed = gEngfuncs.pfnRegisterVariable ( "cl_sidespeed", "400", FCVAR_ARCHIVE ); + cl_movespeedkey = gEngfuncs.pfnRegisterVariable ( "cl_movespeedkey", "0.3", 0 ); + cl_pitchup = gEngfuncs.pfnRegisterVariable ( "cl_pitchup", "89", 0 ); + cl_pitchdown = gEngfuncs.pfnRegisterVariable ( "cl_pitchdown", "89", 0 ); + + cl_vsmoothing = gEngfuncs.pfnRegisterVariable ( "cl_vsmoothing", "0.05", FCVAR_ARCHIVE ); + + m_pitch = gEngfuncs.pfnRegisterVariable ( "m_pitch","0.022", FCVAR_ARCHIVE ); + m_yaw = gEngfuncs.pfnRegisterVariable ( "m_yaw","0.022", FCVAR_ARCHIVE ); + m_forward = gEngfuncs.pfnRegisterVariable ( "m_forward","1", FCVAR_ARCHIVE ); + m_side = gEngfuncs.pfnRegisterVariable ( "m_side","0.8", FCVAR_ARCHIVE ); + + // Initialize third person camera controls. + CAM_Init(); + // Initialize inputs + IN_Init(); + // Initialize keyboard + KB_Init(); + // Initialize view system + V_Init(); +} + +/* +============ +ShutdownInput +============ +*/ +void ShutdownInput( void ) +{ + IN_Shutdown(); + KB_Shutdown(); +} \ No newline at end of file diff --git a/cl_dll/inputw32.cpp b/cl_dll/inputw32.cpp new file mode 100644 index 0000000..f9dda8d --- /dev/null +++ b/cl_dll/inputw32.cpp @@ -0,0 +1,963 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// in_win.c -- windows 95 mouse and joystick code +// 02/21/97 JCB Added extended DirectInput code to support external controllers. + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "../engine/keydefs.h" +#include "windows.h" + +#define MOUSE_BUTTON_COUNT 5 + +// Set this to 1 to show mouse cursor. Experimental +int g_iVisibleMouse = 0; + +extern "C" +{ + void DLLEXPORT IN_ActivateMouse( void ); + void DLLEXPORT IN_DeactivateMouse( void ); + void DLLEXPORT IN_MouseEvent (int mstate); + void DLLEXPORT IN_Accumulate (void); + void DLLEXPORT IN_ClearStates (void); +} + +extern cl_enginefunc_t gEngfuncs; + +extern int iMouseInUse; + +extern kbutton_t in_strafe; +extern kbutton_t in_mlook; +extern kbutton_t in_speed; +extern kbutton_t in_jlook; + +extern cvar_t *m_pitch; +extern cvar_t *m_yaw; +extern cvar_t *m_forward; +extern cvar_t *m_side; + +extern cvar_t *lookstrafe; +extern cvar_t *lookspring; +extern cvar_t *cl_pitchdown; +extern cvar_t *cl_pitchup; +extern cvar_t *cl_yawspeed; +extern cvar_t *cl_sidespeed; +extern cvar_t *cl_forwardspeed; +extern cvar_t *cl_pitchspeed; +extern cvar_t *cl_movespeedkey; + +// mouse variables +cvar_t *m_filter; +cvar_t *sensitivity; + +int CL_IsDead( void ); // buz + +int mouse_buttons; +int mouse_oldbuttonstate; +POINT current_pos; +int mouse_x, mouse_y, old_mouse_x, old_mouse_y, mx_accum, my_accum; + +static int restore_spi; +static int originalmouseparms[3], newmouseparms[3] = {0, 0, 1}; +static int mouseactive; +int mouseinitialized; +static int mouseparmsvalid; +static int mouseshowtoggle = 1; + +// joystick defines and variables +// where should defines be moved? +#define JOY_ABSOLUTE_AXIS 0x00000000 // control like a joystick +#define JOY_RELATIVE_AXIS 0x00000010 // control like a mouse, spinner, trackball +#define JOY_MAX_AXES 6 // X, Y, Z, R, U, V +#define JOY_AXIS_X 0 +#define JOY_AXIS_Y 1 +#define JOY_AXIS_Z 2 +#define JOY_AXIS_R 3 +#define JOY_AXIS_U 4 +#define JOY_AXIS_V 5 + +enum _ControlList +{ + AxisNada = 0, + AxisForward, + AxisLook, + AxisSide, + AxisTurn +}; + +DWORD dwAxisFlags[JOY_MAX_AXES] = +{ + JOY_RETURNX, + JOY_RETURNY, + JOY_RETURNZ, + JOY_RETURNR, + JOY_RETURNU, + JOY_RETURNV +}; + +DWORD dwAxisMap[ JOY_MAX_AXES ]; +DWORD dwControlMap[ JOY_MAX_AXES ]; +PDWORD pdwRawValue[ JOY_MAX_AXES ]; + +// none of these cvars are saved over a session +// this means that advanced controller configuration needs to be executed +// each time. this avoids any problems with getting back to a default usage +// or when changing from one controller to another. this way at least something +// works. +cvar_t *in_joystick; +cvar_t *joy_name; +cvar_t *joy_advanced; +cvar_t *joy_advaxisx; +cvar_t *joy_advaxisy; +cvar_t *joy_advaxisz; +cvar_t *joy_advaxisr; +cvar_t *joy_advaxisu; +cvar_t *joy_advaxisv; +cvar_t *joy_forwardthreshold; +cvar_t *joy_sidethreshold; +cvar_t *joy_pitchthreshold; +cvar_t *joy_yawthreshold; +cvar_t *joy_forwardsensitivity; +cvar_t *joy_sidesensitivity; +cvar_t *joy_pitchsensitivity; +cvar_t *joy_yawsensitivity; +cvar_t *joy_wwhack1; +cvar_t *joy_wwhack2; + +int joy_avail, joy_advancedinit, joy_haspov; +DWORD joy_oldbuttonstate, joy_oldpovstate; + +int joy_id; +DWORD joy_flags; +DWORD joy_numbuttons; + +static JOYINFOEX ji; + +/* +=========== +Force_CenterView_f +=========== +*/ +void Force_CenterView_f (void) +{ + vec3_t viewangles; + + if (!iMouseInUse) + { + gEngfuncs.GetViewAngles( (float *)viewangles ); + viewangles[PITCH] = 0; + gEngfuncs.SetViewAngles( (float *)viewangles ); + } +} + +/* +=========== +IN_ActivateMouse +=========== +*/ +void DLLEXPORT IN_ActivateMouse (void) +{ + if (mouseinitialized) + { + if (mouseparmsvalid) + restore_spi = SystemParametersInfo (SPI_SETMOUSE, 0, newmouseparms, 0); + mouseactive = 1; + } +} + +/* +=========== +IN_DeactivateMouse +=========== +*/ +void DLLEXPORT IN_DeactivateMouse (void) +{ + if (mouseinitialized) + { + if (restore_spi) + SystemParametersInfo (SPI_SETMOUSE, 0, originalmouseparms, 0); + + mouseactive = 0; + } +} + +/* +=========== +IN_StartupMouse +=========== +*/ +void IN_StartupMouse (void) +{ + if ( gEngfuncs.CheckParm ("-nomouse", NULL ) ) + return; + + mouseinitialized = 1; + mouseparmsvalid = SystemParametersInfo (SPI_GETMOUSE, 0, originalmouseparms, 0); + + if (mouseparmsvalid) + { + if ( gEngfuncs.CheckParm ("-noforcemspd", NULL ) ) + newmouseparms[2] = originalmouseparms[2]; + + if ( gEngfuncs.CheckParm ("-noforcemaccel", NULL ) ) + { + newmouseparms[0] = originalmouseparms[0]; + newmouseparms[1] = originalmouseparms[1]; + } + + if ( gEngfuncs.CheckParm ("-noforcemparms", NULL ) ) + { + newmouseparms[0] = originalmouseparms[0]; + newmouseparms[1] = originalmouseparms[1]; + newmouseparms[2] = originalmouseparms[2]; + } + } + + mouse_buttons = MOUSE_BUTTON_COUNT; +} + +/* +=========== +IN_Shutdown +=========== +*/ +void IN_Shutdown (void) +{ + IN_DeactivateMouse (); +} + +/* +=========== +IN_GetMousePos + +Ask for mouse position from engine +=========== +*/ +void IN_GetMousePos( int *mx, int *my ) +{ + gEngfuncs.GetMousePosition( mx, my ); +} + +/* +=========== +IN_ResetMouse + +FIXME: Call through to engine? +=========== +*/ +void IN_ResetMouse( void ) +{ + SetCursorPos ( gEngfuncs.GetWindowCenterX(), gEngfuncs.GetWindowCenterY() ); +} + +/* +=========== +IN_MouseEvent +=========== +*/ +void DLLEXPORT IN_MouseEvent (int mstate) +{ + int i; + + if ( iMouseInUse || g_iVisibleMouse ) + return; + + // perform button actions + for (i=0 ; ivalue) + { + mouse_x = (mx + old_mouse_x) * 0.5; + mouse_y = (my + old_mouse_y) * 0.5; + } + else + { + mouse_x = mx; + mouse_y = my; + } + + old_mouse_x = mx; + old_mouse_y = my; + + if ( gHUD.GetSensitivity() != 0 ) + { + mouse_x *= gHUD.GetSensitivity(); + mouse_y *= gHUD.GetSensitivity(); + } + else + { + mouse_x *= sensitivity->value; + mouse_y *= sensitivity->value; + } + + // buz: slower turning when in sniper mode + if (gHUD.m_iFOV < 90) + { + mouse_x *= 0.5; + mouse_y *= 0.5; + } + + // buz: remove turning if DEAD! + if (CL_IsDead()) + { + mouse_x = 0; + mouse_y = 0; + // CONPRINT("DEAD!\n"); + } + + // add mouse X/Y movement to cmd + if ( (in_strafe.state & 1) || (lookstrafe->value && (in_mlook.state & 1) )) + cmd->sidemove += m_side->value * mouse_x; + else + viewangles[YAW] -= m_yaw->value * mouse_x; + + if ( (in_mlook.state & 1) && !(in_strafe.state & 1)) + { + viewangles[PITCH] += m_pitch->value * mouse_y; + if (viewangles[PITCH] > cl_pitchdown->value) + viewangles[PITCH] = cl_pitchdown->value; + if (viewangles[PITCH] < -cl_pitchup->value) + viewangles[PITCH] = -cl_pitchup->value; + } + else + { + if ((in_strafe.state & 1) && gEngfuncs.IsNoClipping() ) + { + cmd->upmove -= m_forward->value * mouse_y; + } + else + { + cmd->forwardmove -= m_forward->value * mouse_y; + } + } + + // if the mouse has moved, force it to the center, so there's room to move + if ( mx || my ) + { + IN_ResetMouse(); + } + } + + gEngfuncs.SetViewAngles( (float *)viewangles ); + +/* +//#define TRACE_TEST +#if defined( TRACE_TEST ) + { + int mx, my; + void V_Move( int mx, int my ); + IN_GetMousePos( &mx, &my ); + V_Move( mx, my ); + } +#endif +*/ +} + +/* +=========== +IN_Accumulate +=========== +*/ +void DLLEXPORT IN_Accumulate (void) +{ + //only accumulate mouse if we are not moving the camera with the mouse + if ( !iMouseInUse && !g_iVisibleMouse ) + { + if (mouseactive) + { + GetCursorPos (¤t_pos); + + mx_accum += current_pos.x - gEngfuncs.GetWindowCenterX(); + my_accum += current_pos.y - gEngfuncs.GetWindowCenterY(); + + // force the mouse to the center, so there's room to move + IN_ResetMouse(); + } + } + +} + +/* +=================== +IN_ClearStates +=================== +*/ +void DLLEXPORT IN_ClearStates (void) +{ + if ( !mouseactive ) + return; + + mx_accum = 0; + my_accum = 0; + mouse_oldbuttonstate = 0; +} + +/* +=============== +IN_StartupJoystick +=============== +*/ +void IN_StartupJoystick (void) +{ + int numdevs; + JOYCAPS jc; + MMRESULT mmr; + + // assume no joystick + joy_avail = 0; + + // abort startup if user requests no joystick + if ( gEngfuncs.CheckParm ("-nojoy", NULL ) ) + return; + + // verify joystick driver is present + if ((numdevs = joyGetNumDevs ()) == 0) + { + gEngfuncs.Con_DPrintf ("joystick not found -- driver not present\n\n"); + return; + } + + // cycle through the joystick ids for the first valid one + for (joy_id=0 ; joy_idvalue == 0.0) + { + // default joystick initialization + // 2 axes only with joystick control + dwAxisMap[JOY_AXIS_X] = AxisTurn; + // dwControlMap[JOY_AXIS_X] = JOY_ABSOLUTE_AXIS; + dwAxisMap[JOY_AXIS_Y] = AxisForward; + // dwControlMap[JOY_AXIS_Y] = JOY_ABSOLUTE_AXIS; + } + else + { + if ( strcmp ( joy_name->string, "joystick") != 0 ) + { + // notify user of advanced controller + gEngfuncs.Con_Printf ("\n%s configured\n\n", joy_name->string); + } + + // advanced initialization here + // data supplied by user via joy_axisn cvars + dwTemp = (DWORD) joy_advaxisx->value; + dwAxisMap[JOY_AXIS_X] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_X] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisy->value; + dwAxisMap[JOY_AXIS_Y] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_Y] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisz->value; + dwAxisMap[JOY_AXIS_Z] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_Z] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisr->value; + dwAxisMap[JOY_AXIS_R] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_R] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisu->value; + dwAxisMap[JOY_AXIS_U] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_U] = dwTemp & JOY_RELATIVE_AXIS; + dwTemp = (DWORD) joy_advaxisv->value; + dwAxisMap[JOY_AXIS_V] = dwTemp & 0x0000000f; + dwControlMap[JOY_AXIS_V] = dwTemp & JOY_RELATIVE_AXIS; + } + + // compute the axes to collect from DirectInput + joy_flags = JOY_RETURNCENTERED | JOY_RETURNBUTTONS | JOY_RETURNPOV; + for (i = 0; i < JOY_MAX_AXES; i++) + { + if (dwAxisMap[i] != AxisNada) + { + joy_flags |= dwAxisFlags[i]; + } + } +} + + +/* +=========== +IN_Commands +=========== +*/ +void IN_Commands (void) +{ + int i, key_index; + DWORD buttonstate, povstate; + + if (!joy_avail) + { + return; + } + + + // loop through the joystick buttons + // key a joystick event or auxillary event for higher number buttons for each state change + buttonstate = ji.dwButtons; + for (i=0 ; i < (int)joy_numbuttons ; i++) + { + if ( (buttonstate & (1<value != 0.0) + { + ji.dwUpos += 100; + } + return 1; + } + else + { + // read error occurred + // turning off the joystick seems too harsh for 1 read error,\ + // but what should be done? + // Con_Printf ("IN_ReadJoystick: no response\n"); + // joy_avail = 0; + return 0; + } +} + + +/* +=========== +IN_JoyMove +=========== +*/ +void IN_JoyMove ( float frametime, usercmd_t *cmd ) +{ + float speed, aspeed; + float fAxisValue, fTemp; + int i; + vec3_t viewangles; + + gEngfuncs.GetViewAngles( (float *)viewangles ); + + + // complete initialization if first time in + // this is needed as cvars are not available at initialization time + if( joy_advancedinit != 1 ) + { + Joy_AdvancedUpdate_f(); + joy_advancedinit = 1; + } + + // verify joystick is available and that the user wants to use it + if (!joy_avail || !in_joystick->value) + { + return; + } + + // collect the joystick data, if possible + if (IN_ReadJoystick () != 1) + { + return; + } + + if (in_speed.state & 1) + speed = cl_movespeedkey->value; + else + speed = 1; + + aspeed = speed * frametime; + + // loop through the axes + for (i = 0; i < JOY_MAX_AXES; i++) + { + // get the floating point zero-centered, potentially-inverted data for the current axis + fAxisValue = (float) *pdwRawValue[i]; + // move centerpoint to zero + fAxisValue -= 32768.0; + + if (joy_wwhack2->value != 0.0) + { + if (dwAxisMap[i] == AxisTurn) + { + // this is a special formula for the Logitech WingMan Warrior + // y=ax^b; where a = 300 and b = 1.3 + // also x values are in increments of 800 (so this is factored out) + // then bounds check result to level out excessively high spin rates + fTemp = 300.0 * pow(abs(fAxisValue) / 800.0, 1.3); + if (fTemp > 14000.0) + fTemp = 14000.0; + // restore direction information + fAxisValue = (fAxisValue > 0.0) ? fTemp : -fTemp; + } + } + + // convert range from -32768..32767 to -1..1 + fAxisValue /= 32768.0; + + switch (dwAxisMap[i]) + { + case AxisForward: + if ((joy_advanced->value == 0.0) && (in_jlook.state & 1)) + { + // user wants forward control to become look control + if (fabs(fAxisValue) > joy_pitchthreshold->value) + { + // if mouse invert is on, invert the joystick pitch value + // only absolute control support here (joy_advanced is 0) + if (m_pitch->value < 0.0) + { + viewangles[PITCH] -= (fAxisValue * joy_pitchsensitivity->value) * aspeed * cl_pitchspeed->value; + } + else + { + viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity->value) * aspeed * cl_pitchspeed->value; + } + V_StopPitchDrift(); + } + else + { + // no pitch movement + // disable pitch return-to-center unless requested by user + // *** this code can be removed when the lookspring bug is fixed + // *** the bug always has the lookspring feature on + if(lookspring->value == 0.0) + { + V_StopPitchDrift(); + } + } + } + else + { + // user wants forward control to be forward control + if (fabs(fAxisValue) > joy_forwardthreshold->value) + { + cmd->forwardmove += (fAxisValue * joy_forwardsensitivity->value) * speed * cl_forwardspeed->value; + } + } + break; + + case AxisSide: + if (fabs(fAxisValue) > joy_sidethreshold->value) + { + cmd->sidemove += (fAxisValue * joy_sidesensitivity->value) * speed * cl_sidespeed->value; + } + break; + + case AxisTurn: + if ((in_strafe.state & 1) || (lookstrafe->value && (in_jlook.state & 1))) + { + // user wants turn control to become side control + if (fabs(fAxisValue) > joy_sidethreshold->value) + { + cmd->sidemove -= (fAxisValue * joy_sidesensitivity->value) * speed * cl_sidespeed->value; + } + } + else + { + // user wants turn control to be turn control + if (fabs(fAxisValue) > joy_yawthreshold->value) + { + if(dwControlMap[i] == JOY_ABSOLUTE_AXIS) + { + viewangles[YAW] += (fAxisValue * joy_yawsensitivity->value) * aspeed * cl_yawspeed->value; + } + else + { + viewangles[YAW] += (fAxisValue * joy_yawsensitivity->value) * speed * 180.0; + } + + } + } + break; + + case AxisLook: + if (in_jlook.state & 1) + { + if (fabs(fAxisValue) > joy_pitchthreshold->value) + { + // pitch movement detected and pitch movement desired by user + if(dwControlMap[i] == JOY_ABSOLUTE_AXIS) + { + viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity->value) * aspeed * cl_pitchspeed->value; + } + else + { + viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity->value) * speed * 180.0; + } + V_StopPitchDrift(); + } + else + { + // no pitch movement + // disable pitch return-to-center unless requested by user + // *** this code can be removed when the lookspring bug is fixed + // *** the bug always has the lookspring feature on + if( lookspring->value == 0.0 ) + { + V_StopPitchDrift(); + } + } + } + break; + + default: + break; + } + } + + // bounds check pitch + if (viewangles[PITCH] > cl_pitchdown->value) + viewangles[PITCH] = cl_pitchdown->value; + if (viewangles[PITCH] < -cl_pitchup->value) + viewangles[PITCH] = -cl_pitchup->value; + + gEngfuncs.SetViewAngles( (float *)viewangles ); + +} + +/* +=========== +IN_Move +=========== +*/ +void IN_Move ( float frametime, usercmd_t *cmd) +{ + if ( !iMouseInUse && mouseactive ) + { + IN_MouseMove ( frametime, cmd); + } + + IN_JoyMove ( frametime, cmd); +} + +/* +=========== +IN_Init +=========== +*/ +void IN_Init (void) +{ + m_filter = gEngfuncs.pfnRegisterVariable ( "m_filter","0", FCVAR_ARCHIVE ); + sensitivity = gEngfuncs.pfnRegisterVariable ( "sensitivity","3", FCVAR_ARCHIVE ); // user mouse sensitivity setting. + + in_joystick = gEngfuncs.pfnRegisterVariable ( "joystick","0", FCVAR_ARCHIVE ); + joy_name = gEngfuncs.pfnRegisterVariable ( "joyname", "joystick", 0 ); + joy_advanced = gEngfuncs.pfnRegisterVariable ( "joyadvanced", "0", 0 ); + joy_advaxisx = gEngfuncs.pfnRegisterVariable ( "joyadvaxisx", "0", 0 ); + joy_advaxisy = gEngfuncs.pfnRegisterVariable ( "joyadvaxisy", "0", 0 ); + joy_advaxisz = gEngfuncs.pfnRegisterVariable ( "joyadvaxisz", "0", 0 ); + joy_advaxisr = gEngfuncs.pfnRegisterVariable ( "joyadvaxisr", "0", 0 ); + joy_advaxisu = gEngfuncs.pfnRegisterVariable ( "joyadvaxisu", "0", 0 ); + joy_advaxisv = gEngfuncs.pfnRegisterVariable ( "joyadvaxisv", "0", 0 ); + joy_forwardthreshold = gEngfuncs.pfnRegisterVariable ( "joyforwardthreshold", "0.15", 0 ); + joy_sidethreshold = gEngfuncs.pfnRegisterVariable ( "joysidethreshold", "0.15", 0 ); + joy_pitchthreshold = gEngfuncs.pfnRegisterVariable ( "joypitchthreshold", "0.15", 0 ); + joy_yawthreshold = gEngfuncs.pfnRegisterVariable ( "joyyawthreshold", "0.15", 0 ); + joy_forwardsensitivity = gEngfuncs.pfnRegisterVariable ( "joyforwardsensitivity", "-1.0", 0 ); + joy_sidesensitivity = gEngfuncs.pfnRegisterVariable ( "joysidesensitivity", "-1.0", 0 ); + joy_pitchsensitivity = gEngfuncs.pfnRegisterVariable ( "joypitchsensitivity", "1.0", 0 ); + joy_yawsensitivity = gEngfuncs.pfnRegisterVariable ( "joyyawsensitivity", "-1.0", 0 ); + joy_wwhack1 = gEngfuncs.pfnRegisterVariable ( "joywwhack1", "0.0", 0 ); + joy_wwhack2 = gEngfuncs.pfnRegisterVariable ( "joywwhack2", "0.0", 0 ); + + gEngfuncs.pfnAddCommand ("force_centerview", Force_CenterView_f); + gEngfuncs.pfnAddCommand ("joyadvancedupdate", Joy_AdvancedUpdate_f); + + IN_StartupMouse (); + IN_StartupJoystick (); +} \ No newline at end of file diff --git a/cl_dll/kbutton.h b/cl_dll/kbutton.h new file mode 100644 index 0000000..a772b3d --- /dev/null +++ b/cl_dll/kbutton.h @@ -0,0 +1,18 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( KBUTTONH ) +#define KBUTTONH +#pragma once + +typedef struct kbutton_s +{ + int down[2]; // key nums holding it down + int state; // low bit is down state +} kbutton_t; + +#endif // !KBUTTONH \ No newline at end of file diff --git a/cl_dll/lensflare.cpp b/cl_dll/lensflare.cpp new file mode 100644 index 0000000..a59a099 --- /dev/null +++ b/cl_dll/lensflare.cpp @@ -0,0 +1,364 @@ +#include "hud.h" +#include "cl_util.h" +#include +#include +#include +#include "parsemsg.h" +#include "vgui_TeamFortressViewport.h" +#include "triangleapi.h" +#include "ref_params.h" +#include "event_api.h" +#include "pm_defs.h" +#include "pm_movevars.h" +#include "gl_local.h" + +int CHudLensflare :: Init( void ) +{ + m_iFlags |= HUD_ACTIVE; + + m_pCvarDraw = CVAR_REGISTER( "cl_lensflare", "1", FCVAR_ARCHIVE ); + + gHUD.AddHudElem( this ); + + return 1; +} + +int CHudLensflare :: VidInit( void ) +{ + text[0] = SPR_Load("sprites/lens/lens1.spr"); + red[0] = green[0] = blue[0] = 1.0; + scale[0] = 45; + multi[0] = -0.45; + + text[1] = SPR_Load("sprites/lens/lens2.spr"); + red[1] = green[0] = blue[0] = 1.0; + scale[1] = 25; + multi[1] = 0.2; + + text[2] = SPR_Load("sprites/lens/glow1.spr"); + red[2] = 132/255; + green[2] = 1.0; + blue[2] = 153/255; + scale[2] = 35; + multi[2] = 0.3; + + text[3] = SPR_Load("sprites/lens/glow2.spr"); + red[3] = 1.0; + green[3] = 164/255; + blue[3] = 164/255; + scale[3] = 40; + multi[3] = 0.46; + + text[4] = SPR_Load("sprites/lens/lens3.spr"); + red[4] = 1.0; + green[4] = 164/255; + blue[4] = 164/255; + scale[4] = 52; + multi[4] = 0.5; + + text[5] = SPR_Load("sprites/lens/lens2.spr"); + red[5] = green[5] = blue[5] = 1.0; + scale[5] = 31; + multi[5] = 0.54; + + text[6] = SPR_Load("sprites/lens/lens2.spr"); + red[6] = 0.6; + green[6] = 1.0; + blue[6] = 0.6; + scale[6] = 26; + multi[6] = 0.64; + + text[7] = SPR_Load("sprites/lens/glow1.spr"); + red[7] = 0.5; + green[7] = 1.0; + blue[7] = 0.5; + scale[7] = 20; + multi[7] = 0.77; + + text[8] = SPR_Load("sprites/lens/lens2.spr"); + + text[9] = SPR_Load("sprites/lens/lens1.spr"); + + flPlayerBlend = 0.0; + flPlayerBlend2 = 0.0; + + return 1; +} + +int CHudLensflare :: DrawFlare( const Vector &forward, const Vector &lightdir, const Vector &lightorg ) +{ + flPlayerBlend = max( DotProduct( forward, lightdir ) - 0.85, 0.0 ) * 6.8; + if( flPlayerBlend > 1.0 ) flPlayerBlend = 1.0; + + flPlayerBlend4 = max( DotProduct( forward, lightdir ) - 0.90, 0.0 ) * 6.6; + if( flPlayerBlend4 > 1.0 ) flPlayerBlend4 = 1.0; + + flPlayerBlend6 = max( DotProduct( forward, lightdir ) - 0.80, 0.0 ) * 6.7; + if( flPlayerBlend6 > 1.0 ) flPlayerBlend6 = 1.0; + + flPlayerBlend2 = flPlayerBlend6 * 140.0 ; + flPlayerBlend3 = flPlayerBlend * 190.0 ; + flPlayerBlend5 = flPlayerBlend4 * 222.0 ; + + Vector normal, point, screen; + + if( cv_renderer->value ) R_WorldToScreen( lightorg, screen ); + else gEngfuncs.pTriAPI->WorldToScreen( (float *)&lightorg, screen ); + + Suncoordx = XPROJECT( screen[0] ); + Suncoordy = YPROJECT( screen[1] ); + + Screenmx = ScreenWidth / 2; + Screenmy = ScreenHeight / 2; + + Sundistx = Screenmx - Suncoordx; + Sundisty = Screenmy - Suncoordy; + + gEngfuncs.pTriAPI->RenderMode(kRenderTransAdd); //additive + gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *) gEngfuncs.GetSpritePointer(SPR_Load("sprites/lens/lensflare2.spr")) , 0); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); //no culling + gEngfuncs.pTriAPI->Color4f( 0.97f, 0.6f, 0.02f, 1.0f ); + gEngfuncs.pTriAPI->Brightness( 0.3f ); + gEngfuncs.pTriAPI->Begin(TRI_QUADS); //start our quad + gEngfuncs.pTriAPI->TexCoord2f(0.0f, 1.0f);gEngfuncs.pTriAPI->Vertex3f(Suncoordx + 190, Suncoordy + 190, 0); //top left + gEngfuncs.pTriAPI->TexCoord2f(0.0f, 0.0f);gEngfuncs.pTriAPI->Vertex3f(Suncoordx + 190, Suncoordy - 190, 0); //bottom left + gEngfuncs.pTriAPI->TexCoord2f(1.0f, 0.0f);gEngfuncs.pTriAPI->Vertex3f(Suncoordx - 190, Suncoordy - 190, 0); //bottom right + gEngfuncs.pTriAPI->TexCoord2f(1.0f, 1.0f);gEngfuncs.pTriAPI->Vertex3f(Suncoordx - 190, Suncoordy + 190, 0); //top right + gEngfuncs.pTriAPI->End(); //end our list of vertexes + gEngfuncs.pTriAPI->RenderMode(kRenderNormal); + + gEngfuncs.pTriAPI->RenderMode(kRenderTransAdd); //additive + gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *) gEngfuncs.GetSpritePointer(SPR_Load("sprites/lens/glow2.spr")) , 0); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); //no culling + gEngfuncs.pTriAPI->Color4f(1.0, 1.0 , 1.0, flPlayerBlend3/255.0); + gEngfuncs.pTriAPI->Brightness(flPlayerBlend3/255.0); + gEngfuncs.pTriAPI->Begin(TRI_QUADS); //start our quad + gEngfuncs.pTriAPI->TexCoord2f(0.0f, 1.0f);gEngfuncs.pTriAPI->Vertex3f(Suncoordx + 160, Suncoordy + 160, 0); //top left + gEngfuncs.pTriAPI->TexCoord2f(0.0f, 0.0f);gEngfuncs.pTriAPI->Vertex3f(Suncoordx + 160, Suncoordy - 160, 0); //bottom left + gEngfuncs.pTriAPI->TexCoord2f(1.0f, 0.0f);gEngfuncs.pTriAPI->Vertex3f(Suncoordx - 160, Suncoordy - 160, 0); //bottom right + gEngfuncs.pTriAPI->TexCoord2f(1.0f, 1.0f);gEngfuncs.pTriAPI->Vertex3f(Suncoordx - 160, Suncoordy + 160, 0); //top right + gEngfuncs.pTriAPI->End(); //end our list of vertexes + gEngfuncs.pTriAPI->RenderMode(kRenderNormal); + + gEngfuncs.pTriAPI->RenderMode(kRenderTransAdd); //additive + gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *) gEngfuncs.GetSpritePointer(SPR_Load("sprites/lens/glow3.spr")) , 0); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); //no culling + gEngfuncs.pTriAPI->Color4f(1.0, 1.0 , 1.0, flPlayerBlend5/255.0); + gEngfuncs.pTriAPI->Brightness(flPlayerBlend5/255.0); + gEngfuncs.pTriAPI->Begin(TRI_QUADS); //start our quad + gEngfuncs.pTriAPI->TexCoord2f(0.0f, 1.0f);gEngfuncs.pTriAPI->Vertex3f(0, 0, 0); //top left + gEngfuncs.pTriAPI->TexCoord2f(0.0f, 0.0f);gEngfuncs.pTriAPI->Vertex3f(0, ScreenHeight, 0); //bottom left + gEngfuncs.pTriAPI->TexCoord2f(1.0f, 0.0f);gEngfuncs.pTriAPI->Vertex3f(ScreenWidth, ScreenHeight, 0); //bottom right + gEngfuncs.pTriAPI->TexCoord2f(1.0f, 1.0f);gEngfuncs.pTriAPI->Vertex3f(ScreenWidth, 0, 0); //top right + gEngfuncs.pTriAPI->End(); //end our list of vertexes + gEngfuncs.pTriAPI->RenderMode(kRenderNormal); + + int i = 1; + Lensx[i] = (Suncoordx + (Sundistx * multi[i])); + Lensy[i] = (Suncoordy + (Sundisty * multi[i])); + gEngfuncs.pTriAPI->RenderMode(kRenderTransAdd); //additive + gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *) gEngfuncs.GetSpritePointer(text[i]) , 0); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); //no culling + gEngfuncs.pTriAPI->Color4f(red[i], green[i] , green[i], flPlayerBlend2/255.0); + gEngfuncs.pTriAPI->Brightness(flPlayerBlend2/255.0); + gEngfuncs.pTriAPI->Begin(TRI_QUADS); //start our quad + gEngfuncs.pTriAPI->TexCoord2f(0.0f, 1.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] + scale[i], Lensy[i] + scale[i], 0); //top left + gEngfuncs.pTriAPI->TexCoord2f(0.0f, 0.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] + scale[i], Lensy[i] - scale[i], 0); //bottom left + gEngfuncs.pTriAPI->TexCoord2f(1.0f, 0.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] - scale[i], Lensy[i] - scale[i], 0); //bottom right + gEngfuncs.pTriAPI->TexCoord2f(1.0f, 1.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] - scale[i], Lensy[i] + scale[i], 0); //top right + gEngfuncs.pTriAPI->End(); //end our list of vertexes + gEngfuncs.pTriAPI->RenderMode(kRenderNormal); + + i++; + Lensx[i] = (Suncoordx + (Sundistx * multi[i])); + Lensy[i] = (Suncoordy + (Sundisty * multi[i])); + gEngfuncs.pTriAPI->RenderMode(kRenderTransAdd); //additive + gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *) gEngfuncs.GetSpritePointer(text[i]) , 0); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); //no culling + gEngfuncs.pTriAPI->Color4f(red[i], green[i] , green[i], flPlayerBlend2/255.0); + gEngfuncs.pTriAPI->Brightness(flPlayerBlend2/255.0); + gEngfuncs.pTriAPI->Begin(TRI_QUADS); //start our quad + gEngfuncs.pTriAPI->TexCoord2f(0.0f, 1.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] + scale[i], Lensy[i] + scale[i], 0); //top left + gEngfuncs.pTriAPI->TexCoord2f(0.0f, 0.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] + scale[i], Lensy[i] - scale[i], 0); //bottom left + gEngfuncs.pTriAPI->TexCoord2f(1.0f, 0.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] - scale[i], Lensy[i] - scale[i], 0); //bottom right + gEngfuncs.pTriAPI->TexCoord2f(1.0f, 1.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] - scale[i], Lensy[i] + scale[i], 0); //top right + gEngfuncs.pTriAPI->End(); //end our list of vertexes + gEngfuncs.pTriAPI->RenderMode(kRenderNormal); + + i++; + Lensx[i] = (Suncoordx + (Sundistx * multi[i])); + Lensy[i] = (Suncoordy + (Sundisty * multi[i])); + gEngfuncs.pTriAPI->RenderMode(kRenderTransAdd); //additive + gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *) gEngfuncs.GetSpritePointer(text[i]) , 0); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); //no culling + gEngfuncs.pTriAPI->Color4f(red[i], green[i] , green[i], flPlayerBlend2/255.0); + gEngfuncs.pTriAPI->Brightness(flPlayerBlend2/255.0); + gEngfuncs.pTriAPI->Begin(TRI_QUADS); //start our quad + gEngfuncs.pTriAPI->TexCoord2f(0.0f, 1.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] + scale[i], Lensy[i] + scale[i], 0); //top left + gEngfuncs.pTriAPI->TexCoord2f(0.0f, 0.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] + scale[i], Lensy[i] - scale[i], 0); //bottom left + gEngfuncs.pTriAPI->TexCoord2f(1.0f, 0.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] - scale[i], Lensy[i] - scale[i], 0); //bottom right + gEngfuncs.pTriAPI->TexCoord2f(1.0f, 1.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] - scale[i], Lensy[i] + scale[i], 0); //top right + gEngfuncs.pTriAPI->End(); //end our list of vertexes + gEngfuncs.pTriAPI->RenderMode(kRenderNormal); + + i++; + Lensx[i] = (Suncoordx + (Sundistx * multi[i])); + Lensy[i] = (Suncoordy + (Sundisty * multi[i])); + gEngfuncs.pTriAPI->RenderMode(kRenderTransAdd); //additive + gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *) gEngfuncs.GetSpritePointer(text[i]) , 0); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); //no culling + gEngfuncs.pTriAPI->Color4f(red[i], green[i] , green[i], flPlayerBlend2/255.0); + gEngfuncs.pTriAPI->Brightness(flPlayerBlend2/255.0); + gEngfuncs.pTriAPI->Begin(TRI_QUADS); //start our quad + gEngfuncs.pTriAPI->TexCoord2f(0.0f, 1.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] + scale[i], Lensy[i] + scale[i], 0); //top left + gEngfuncs.pTriAPI->TexCoord2f(0.0f, 0.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] + scale[i], Lensy[i] - scale[i], 0); //bottom left + gEngfuncs.pTriAPI->TexCoord2f(1.0f, 0.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] - scale[i], Lensy[i] - scale[i], 0); //bottom right + gEngfuncs.pTriAPI->TexCoord2f(1.0f, 1.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] - scale[i], Lensy[i] + scale[i], 0); //top right + gEngfuncs.pTriAPI->End(); //end our list of vertexes + gEngfuncs.pTriAPI->RenderMode(kRenderNormal); + + i++; + Lensx[i] = (Suncoordx + (Sundistx * multi[i])); + Lensy[i] = (Suncoordy + (Sundisty * multi[i])); + gEngfuncs.pTriAPI->RenderMode(kRenderTransAdd); //additive + gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *) gEngfuncs.GetSpritePointer(text[i]) , 0); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); //no culling + gEngfuncs.pTriAPI->Color4f(red[i], green[i] , green[i], flPlayerBlend2/255.0); + gEngfuncs.pTriAPI->Brightness(flPlayerBlend2/255.0); + gEngfuncs.pTriAPI->Begin(TRI_QUADS); //start our quad + gEngfuncs.pTriAPI->TexCoord2f(0.0f, 1.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] + scale[i], Lensy[i] + scale[i], 0); //top left + gEngfuncs.pTriAPI->TexCoord2f(0.0f, 0.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] + scale[i], Lensy[i] - scale[i], 0); //bottom left + gEngfuncs.pTriAPI->TexCoord2f(1.0f, 0.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] - scale[i], Lensy[i] - scale[i], 0); //bottom right + gEngfuncs.pTriAPI->TexCoord2f(1.0f, 1.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] - scale[i], Lensy[i] + scale[i], 0); //top right + gEngfuncs.pTriAPI->End(); //end our list of vertexes + gEngfuncs.pTriAPI->RenderMode(kRenderNormal); + + i++; + Lensx[i] = (Suncoordx + (Sundistx * multi[i])); + Lensy[i] = (Suncoordy + (Sundisty * multi[i])); + gEngfuncs.pTriAPI->RenderMode(kRenderTransAdd); //additive + gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *) gEngfuncs.GetSpritePointer(text[i]) , 0); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); //no culling + gEngfuncs.pTriAPI->Color4f(red[i], green[i] , green[i], flPlayerBlend2/255.0); + gEngfuncs.pTriAPI->Brightness(flPlayerBlend2/255.0); + gEngfuncs.pTriAPI->Begin(TRI_QUADS); //start our quad + gEngfuncs.pTriAPI->TexCoord2f(0.0f, 1.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] + scale[i], Lensy[i] + scale[i], 0); //top left + gEngfuncs.pTriAPI->TexCoord2f(0.0f, 0.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] + scale[i], Lensy[i] - scale[i], 0); //bottom left + gEngfuncs.pTriAPI->TexCoord2f(1.0f, 0.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] - scale[i], Lensy[i] - scale[i], 0); //bottom right + gEngfuncs.pTriAPI->TexCoord2f(1.0f, 1.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] - scale[i], Lensy[i] + scale[i], 0); //top right + gEngfuncs.pTriAPI->End(); //end our list of vertexes + gEngfuncs.pTriAPI->RenderMode(kRenderNormal); + + i++; + Lensx[i] = (Suncoordx + (Sundistx * multi[i])); + Lensy[i] = (Suncoordy + (Sundisty * multi[i])); + gEngfuncs.pTriAPI->RenderMode(kRenderTransAdd); //additive + gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *) gEngfuncs.GetSpritePointer(text[i]) , 0); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); //no culling + gEngfuncs.pTriAPI->Color4f(red[i], green[i] , green[i], flPlayerBlend2/255.0); + gEngfuncs.pTriAPI->Brightness(flPlayerBlend2/255.0); + gEngfuncs.pTriAPI->Begin(TRI_QUADS); //start our quad + gEngfuncs.pTriAPI->TexCoord2f(0.0f, 1.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] + scale[i], Lensy[i] + scale[i], 0); //top left + gEngfuncs.pTriAPI->TexCoord2f(0.0f, 0.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] + scale[i], Lensy[i] - scale[i], 0); //bottom left + gEngfuncs.pTriAPI->TexCoord2f(1.0f, 0.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] - scale[i], Lensy[i] - scale[i], 0); //bottom right + gEngfuncs.pTriAPI->TexCoord2f(1.0f, 1.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx[i] - scale[i], Lensy[i] + scale[i], 0); //top right + gEngfuncs.pTriAPI->End(); //end our list of vertexes + gEngfuncs.pTriAPI->RenderMode(kRenderNormal); + + i++; + int scale1 = 32; + int Lensx1,Lensy1 = 0; + Lensx1 = (Suncoordx + (Sundistx * 0.88)); + Lensy1 = (Suncoordy + (Sundisty * 0.88)); + gEngfuncs.pTriAPI->RenderMode(kRenderTransAdd); //additive + gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *) gEngfuncs.GetSpritePointer(text[i]) , 0); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); //no culling + gEngfuncs.pTriAPI->Color4f(0.9, 0.9 , 0.9, flPlayerBlend2/255.0); + gEngfuncs.pTriAPI->Brightness(flPlayerBlend2/255.0); + gEngfuncs.pTriAPI->Begin(TRI_QUADS); //start our quad + gEngfuncs.pTriAPI->TexCoord2f(0.0f, 1.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx1 + scale1, Lensy1 + scale1, 0); //top left + gEngfuncs.pTriAPI->TexCoord2f(0.0f, 0.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx1 + scale1, Lensy1 - scale1, 0); //bottom left + gEngfuncs.pTriAPI->TexCoord2f(1.0f, 0.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx1 - scale1, Lensy1 - scale1, 0); //bottom right + gEngfuncs.pTriAPI->TexCoord2f(1.0f, 1.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx1 - scale1, Lensy1 + scale1, 0); //top right + gEngfuncs.pTriAPI->End(); //end our list of vertexes + gEngfuncs.pTriAPI->RenderMode(kRenderNormal); + + i++; + scale1 = 140; + Lensx1 = (Suncoordx + (Sundistx * 1.1)); + Lensy1 = (Suncoordy + (Sundisty * 1.1)); + gEngfuncs.pTriAPI->RenderMode(kRenderTransAdd); //additive + gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *) gEngfuncs.GetSpritePointer(text[i]) , 0); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); //no culling + gEngfuncs.pTriAPI->Color4f(0.9, 0.9 , 0.9, flPlayerBlend2/255.0); + gEngfuncs.pTriAPI->Brightness(flPlayerBlend2/255.0); + gEngfuncs.pTriAPI->Begin(TRI_QUADS); //start our quad + gEngfuncs.pTriAPI->TexCoord2f(0.0f, 1.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx1 + scale1, Lensy1 + scale1, 0); //top left + gEngfuncs.pTriAPI->TexCoord2f(0.0f, 0.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx1 + scale1, Lensy1 - scale1, 0); //bottom left + gEngfuncs.pTriAPI->TexCoord2f(1.0f, 0.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx1 - scale1, Lensy1 - scale1, 0); //bottom right + gEngfuncs.pTriAPI->TexCoord2f(1.0f, 1.0f);gEngfuncs.pTriAPI->Vertex3f(Lensx1 - scale1, Lensy1 + scale1, 0); //top right + gEngfuncs.pTriAPI->End(); //end our list of vertexes + gEngfuncs.pTriAPI->RenderMode(kRenderNormal); + + gEngfuncs.pTriAPI->CullFace( TRI_FRONT ); + + return 1; +} + +int CHudLensflare :: Draw( float flTime ) +{ + Vector forward, sundir, suntarget; + pmtrace_t ptr; + + if( m_pCvarDraw->value <= 0.0f ) + return 0; + + forward = g_pViewParams->forward; + + // draw flares for dynlights + for( int i = 0; i < MAX_DLIGHTS; i++ ) + { + CDynLight *pl = &tr.dlights[i]; + + if( pl->die < flTime || !pl->radius || !FBitSet( pl->flags, DLF_LENSFLARE )) + continue; + + if( pl->type == LIGHT_SPOT ) + sundir = pl->frustum.GetPlane( FRUSTUM_FAR )->normal; + else sundir = ( pl->origin - g_pViewParams->vieworg ).Normalize(); + + suntarget = pl->origin; + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PlayerTrace( g_pViewParams->vieworg, suntarget, PM_GLASS_IGNORE, -1, &ptr ); + + if( DotProduct( forward, sundir ) >= 0.68f && !pl->frustum.CullSphere( GetVieworg(), 72.0f ) && ptr.fraction == 1.0f ) + { + DrawFlare( forward, sundir, suntarget ); + } + } + + if( CVAR_TO_BOOL( v_sunshafts )) + return 1; // don't mixing sunshafts and lensflares because this looks ugly + + if( !tr.fogEnabled ) + { + Vector skyVec = tr.sky_normal; + + if( skyVec == g_vecZero ) + return 1; // no light_environment on a map + + sundir = -skyVec.Normalize(); + suntarget = tr.cached_vieworigin + sundir * 32768.0f; + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PlayerTrace( tr.cached_vieworigin, suntarget, PM_GLASS_IGNORE, -1, &ptr ); + + if( DotProduct( forward, sundir ) >= 0.68f && R_SkyIsVisible() && gEngfuncs.PM_PointContents( ptr.endpos, null ) == CONTENTS_SKY ) + { + DrawFlare( forward, sundir, suntarget ); + } + } + + return 1; +} \ No newline at end of file diff --git a/cl_dll/menu.cpp b/cl_dll/menu.cpp new file mode 100644 index 0000000..d9e1f91 --- /dev/null +++ b/cl_dll/menu.cpp @@ -0,0 +1,187 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// menu.cpp +// +// generic menu handler +// +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +#include "vgui_TeamFortressViewport.h" + +#define MAX_MENU_STRING 512 +char g_szMenuString[MAX_MENU_STRING]; +char g_szPrelocalisedMenuString[MAX_MENU_STRING]; + +int KB_ConvertString( char *in, char **ppout ); + +DECLARE_MESSAGE( m_Menu, ShowMenu ); + +int CHudMenu :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( ShowMenu ); + + InitHUDData(); + + return 1; +} + +void CHudMenu :: InitHUDData( void ) +{ + m_fMenuDisplayed = 0; + m_bitsValidSlots = 0; + Reset(); +} + +void CHudMenu :: Reset( void ) +{ + g_szPrelocalisedMenuString[0] = 0; + m_fWaitingForMore = FALSE; +} + +int CHudMenu :: VidInit( void ) +{ + return 1; +} + +int CHudMenu :: Draw( float flTime ) +{ + // check for if menu is set to disappear + if ( m_flShutoffTime > 0 ) + { + if ( m_flShutoffTime <= gHUD.m_flTime ) + { // times up, shutoff + m_fMenuDisplayed = 0; + m_iFlags &= ~HUD_ACTIVE; + return 1; + } + } + + // don't draw the menu if the scoreboard is being shown + if ( gViewPort && gViewPort->IsScoreBoardVisible() ) + return 1; + + // draw the menu, along the left-hand side of the screen + + // count the number of newlines + int nlc = 0; + for ( int i = 0; i < MAX_MENU_STRING && g_szMenuString[i] != '\0'; i++ ) + { + if ( g_szMenuString[i] == '\n' ) + nlc++; + } + + // center it + int y = (ScreenHeight/2) - ((nlc/2)*12) - 40; // make sure it is above the say text + int x = 20; + + i = 0; + while ( i < MAX_MENU_STRING && g_szMenuString[i] != '\0' ) + { + gHUD.DrawHudString( x, y, 320, g_szMenuString + i, 255, 255, 255 ); + y += 12; + + while ( i < MAX_MENU_STRING && g_szMenuString[i] != '\0' && g_szMenuString[i] != '\n' ) + i++; + if ( g_szMenuString[i] == '\n' ) + i++; + } + + return 1; +} + +// selects an item from the menu +void CHudMenu :: SelectMenuItem( int menu_item ) +{ + // if menu_item is in a valid slot, send a menuselect command to the server + if ( (menu_item > 0) && (m_bitsValidSlots & (1 << (menu_item-1))) ) + { + char szbuf[32]; + sprintf( szbuf, "menuselect %d\n", menu_item ); + ClientCmd( szbuf ); + + // remove the menu + m_fMenuDisplayed = 0; + m_iFlags &= ~HUD_ACTIVE; + } +} + + +// Message handler for ShowMenu message +// takes four values: +// short: a bitfield of keys that are valid input +// char : the duration, in seconds, the menu should stay up. -1 means is stays until something is chosen. +// byte : a boolean, TRUE if there is more string yet to be received before displaying the menu, FALSE if it's the last string +// string: menu string to display +// if this message is never received, then scores will simply be the combined totals of the players. +int CHudMenu :: MsgFunc_ShowMenu( const char *pszName, int iSize, void *pbuf ) +{ + char *temp = NULL; + + BEGIN_READ( pbuf, iSize ); + + m_bitsValidSlots = READ_SHORT(); + int DisplayTime = READ_CHAR(); + int NeedMore = READ_BYTE(); + + if ( DisplayTime > 0 ) + m_flShutoffTime = DisplayTime + gHUD.m_flTime; + else + m_flShutoffTime = -1; + + if ( m_bitsValidSlots ) + { + if ( !m_fWaitingForMore ) // this is the start of a new menu + { + strncpy( g_szPrelocalisedMenuString, READ_STRING(), MAX_MENU_STRING ); + } + else + { // append to the current menu string + strncat( g_szPrelocalisedMenuString, READ_STRING(), MAX_MENU_STRING - strlen(g_szPrelocalisedMenuString) ); + } + g_szPrelocalisedMenuString[MAX_MENU_STRING-1] = 0; // ensure null termination (strncat/strncpy does not) + + if ( !NeedMore ) + { // we have the whole string, so we can localise it now + strcpy( g_szMenuString, gHUD.m_TextMessage.BufferedLocaliseTextString( g_szPrelocalisedMenuString ) ); + + // Swap in characters + if ( KB_ConvertString( g_szMenuString, &temp ) ) + { + strcpy( g_szMenuString, temp ); + free( temp ); + } + } + + m_fMenuDisplayed = 1; + m_iFlags |= HUD_ACTIVE; + } + else + { + m_fMenuDisplayed = 0; // no valid slots means that the menu should be turned off + m_iFlags &= ~HUD_ACTIVE; + } + + m_fWaitingForMore = NeedMore; + + return 1; +} diff --git a/cl_dll/message.cpp b/cl_dll/message.cpp new file mode 100644 index 0000000..dac86d0 --- /dev/null +++ b/cl_dll/message.cpp @@ -0,0 +1,539 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Message.cpp +// +// implementation of CHudMessage class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +void SubtitleMessageAdd( client_textmessage_t *tempMessage ); // buz +void ShowTip( client_textmessage_t *tempMessage ); // buz +void VGuiAddScreenMessage( client_textmessage_t *msg ); // buz +void VGuiAddPickupMessage( client_textmessage_t *msg ); // buz + +// Wargon: Ñêðîëëÿùèéñÿ òåêñò. +void VGuiAddScrollingMessage( client_textmessage_t *msg ); + +DECLARE_MESSAGE( m_Message, HudText ) +DECLARE_MESSAGE( m_Message, GameTitle ) + +// 1 Global client_textmessage_t for custom messages that aren't in the titles.txt +client_textmessage_t g_pCustomMessage; +char *g_pCustomName = "Custom"; +char g_pCustomText[1024]; + +int CHudMessage::Init(void) +{ + HOOK_MESSAGE( HudText ); + HOOK_MESSAGE( GameTitle ); + + gHUD.AddHudElem(this); + + Reset(); + + return 1; +}; + +int CHudMessage::VidInit( void ) +{ + return 1; +}; + + +void CHudMessage::Reset( void ) +{ + memset( m_pMessages, 0, sizeof( m_pMessages[0] ) * maxHUDMessages ); + memset( m_startTime, 0, sizeof( m_startTime[0] ) * maxHUDMessages ); + + m_gameTitleTime = 0; + m_pGameTitle = NULL; +} + + +float CHudMessage::FadeBlend( float fadein, float fadeout, float hold, float localTime ) +{ + float fadeTime = fadein + hold; + float fadeBlend; + + if ( localTime < 0 ) + return 0; + + if ( localTime < fadein ) + { + fadeBlend = 1 - ((fadein - localTime) / fadein); + } + else if ( localTime > fadeTime ) + { + if ( fadeout > 0 ) + fadeBlend = 1 - ((localTime - fadeTime) / fadeout); + else + fadeBlend = 0; + } + else + fadeBlend = 1; + + return fadeBlend; +} + + +int CHudMessage::XPosition( float x, int width, int totalWidth ) +{ + int xPos; + + if ( x == -1 ) + { + xPos = (ScreenWidth - width) / 2; + } + else + { + if ( x < 0 ) + xPos = (1.0 + x) * ScreenWidth - totalWidth; // Alight right + else + xPos = x * ScreenWidth; + } + + if ( xPos + width > ScreenWidth ) + xPos = ScreenWidth - width; + else if ( xPos < 0 ) + xPos = 0; + + return xPos; +} + + +int CHudMessage::YPosition( float y, int height ) +{ + int yPos; + + if ( y == -1 ) // Centered? + yPos = (ScreenHeight - height) * 0.5; + else + { + // Alight bottom? + if ( y < 0 ) + yPos = (1.0 + y) * ScreenHeight - height; // Alight bottom + else // align top + yPos = y * ScreenHeight; + } + + if ( yPos + height > ScreenHeight ) + yPos = ScreenHeight - height; + else if ( yPos < 0 ) + yPos = 0; + + return yPos; +} + + +void CHudMessage::MessageScanNextChar( void ) +{ + int srcRed, srcGreen, srcBlue, destRed, destGreen, destBlue; + int blend; + + srcRed = m_parms.pMessage->r1; + srcGreen = m_parms.pMessage->g1; + srcBlue = m_parms.pMessage->b1; + blend = 0; // Pure source + + switch( m_parms.pMessage->effect ) + { + // Fade-in / Fade-out + case 0: + case 1: + destRed = destGreen = destBlue = 0; + blend = m_parms.fadeBlend; + break; + + case 2: + m_parms.charTime += m_parms.pMessage->fadein; + if ( m_parms.charTime > m_parms.time ) + { + srcRed = srcGreen = srcBlue = 0; + blend = 0; // pure source + } + else + { + float deltaTime = m_parms.time - m_parms.charTime; + + destRed = destGreen = destBlue = 0; + if ( m_parms.time > m_parms.fadeTime ) + { + blend = m_parms.fadeBlend; + } + else if ( deltaTime > m_parms.pMessage->fxtime ) + blend = 0; // pure dest + else + { + destRed = m_parms.pMessage->r2; + destGreen = m_parms.pMessage->g2; + destBlue = m_parms.pMessage->b2; + blend = 255 - (deltaTime * (1.0/m_parms.pMessage->fxtime) * 255.0 + 0.5); + } + } + break; + } + if ( blend > 255 ) + blend = 255; + else if ( blend < 0 ) + blend = 0; + + m_parms.r = ((srcRed * (255-blend)) + (destRed * blend)) >> 8; + m_parms.g = ((srcGreen * (255-blend)) + (destGreen * blend)) >> 8; + m_parms.b = ((srcBlue * (255-blend)) + (destBlue * blend)) >> 8; + + if ( m_parms.pMessage->effect == 1 && m_parms.charTime != 0 ) + { + if ( m_parms.x >= 0 && m_parms.y >= 0 && (m_parms.x + gHUD.m_scrinfo.charWidths[ m_parms.text ]) <= ScreenWidth ) + TextMessageDrawChar( m_parms.x, m_parms.y, m_parms.text, m_parms.pMessage->r2, m_parms.pMessage->g2, m_parms.pMessage->b2 ); + } +} + + +void CHudMessage::MessageScanStart( void ) +{ + switch( m_parms.pMessage->effect ) + { + // Fade-in / out with flicker + case 1: + case 0: + m_parms.fadeTime = m_parms.pMessage->fadein + m_parms.pMessage->holdtime; + + + if ( m_parms.time < m_parms.pMessage->fadein ) + { + m_parms.fadeBlend = ((m_parms.pMessage->fadein - m_parms.time) * (1.0/m_parms.pMessage->fadein) * 255); + } + else if ( m_parms.time > m_parms.fadeTime ) + { + if ( m_parms.pMessage->fadeout > 0 ) + m_parms.fadeBlend = (((m_parms.time - m_parms.fadeTime) / m_parms.pMessage->fadeout) * 255); + else + m_parms.fadeBlend = 255; // Pure dest (off) + } + else + m_parms.fadeBlend = 0; // Pure source (on) + m_parms.charTime = 0; + + if ( m_parms.pMessage->effect == 1 && (rand()%100) < 10 ) + m_parms.charTime = 1; + break; + + case 2: + m_parms.fadeTime = (m_parms.pMessage->fadein * m_parms.length) + m_parms.pMessage->holdtime; + + if ( m_parms.time > m_parms.fadeTime && m_parms.pMessage->fadeout > 0 ) + m_parms.fadeBlend = (((m_parms.time - m_parms.fadeTime) / m_parms.pMessage->fadeout) * 255); + else + m_parms.fadeBlend = 0; + break; + } +} + + +void CHudMessage::MessageDrawScan( client_textmessage_t *pMessage, float time ) +{ + int i, j, length, width; + const char *pText; + unsigned char line[80]; + + pText = pMessage->pMessage; + // Count lines + m_parms.lines = 1; + m_parms.time = time; + m_parms.pMessage = pMessage; + length = 0; + width = 0; + m_parms.totalWidth = 0; + while ( *pText ) + { + if ( *pText == '\n' ) + { + m_parms.lines++; + if ( width > m_parms.totalWidth ) + m_parms.totalWidth = width; + width = 0; + } + else + width += gHUD.m_scrinfo.charWidths[*pText]; + pText++; + length++; + } + m_parms.length = length; + m_parms.totalHeight = (m_parms.lines * gHUD.m_scrinfo.iCharHeight); + + + m_parms.y = YPosition( pMessage->y, m_parms.totalHeight ); + pText = pMessage->pMessage; + + m_parms.charTime = 0; + + MessageScanStart(); + + for ( i = 0; i < m_parms.lines; i++ ) + { + m_parms.lineLength = 0; + m_parms.width = 0; + while ( *pText && *pText != '\n' ) + { + unsigned char c = *pText; + line[m_parms.lineLength] = c; + m_parms.width += gHUD.m_scrinfo.charWidths[c]; + m_parms.lineLength++; + pText++; + } + pText++; // Skip LF + line[m_parms.lineLength] = 0; + + m_parms.x = XPosition( pMessage->x, m_parms.width, m_parms.totalWidth ); + + for ( j = 0; j < m_parms.lineLength; j++ ) + { + m_parms.text = line[j]; + int next = m_parms.x + gHUD.m_scrinfo.charWidths[ m_parms.text ]; + MessageScanNextChar(); + + if ( m_parms.x >= 0 && m_parms.y >= 0 && next <= ScreenWidth ) + TextMessageDrawChar( m_parms.x, m_parms.y, m_parms.text, m_parms.r, m_parms.g, m_parms.b ); + m_parms.x = next; + } + + m_parms.y += gHUD.m_scrinfo.iCharHeight; + } +} + + +int CHudMessage::Draw( float fTime ) +{ + int i, drawn; + client_textmessage_t *pMessage; + float endTime; + + drawn = 0; + + // Fixup level transitions + for ( i = 0; i < maxHUDMessages; i++ ) + { + // Assume m_parms.time contains last time + if ( m_pMessages[i] ) + { + pMessage = m_pMessages[i]; + if ( m_startTime[i] > gHUD.m_flTime ) + m_startTime[i] = gHUD.m_flTime + m_parms.time - m_startTime[i] + 0.2; // Server takes 0.2 seconds to spawn, adjust for this + } + } + + for ( i = 0; i < maxHUDMessages; i++ ) + { + if ( m_pMessages[i] ) + { + pMessage = m_pMessages[i]; + + // This is when the message is over + switch( pMessage->effect ) + { + case 0: + case 1: + endTime = m_startTime[i] + pMessage->fadein + pMessage->fadeout + pMessage->holdtime; + break; + + // Fade in is per character in scanning messages + case 2: + endTime = m_startTime[i] + (pMessage->fadein * strlen( pMessage->pMessage )) + pMessage->fadeout + pMessage->holdtime; + break; + } + + if ( fTime <= endTime ) + { + float messageTime = fTime - m_startTime[i]; + + // Draw the message + // effect 0 is fade in/fade out + // effect 1 is flickery credits + // effect 2 is write out (training room) + MessageDrawScan( pMessage, messageTime ); + + drawn++; + } + else + { + // The message is over + m_pMessages[i] = NULL; + } + } + } + + // Remember the time -- to fix up level transitions + m_parms.time = gHUD.m_flTime; + // Don't call until we get another message + if ( !drawn ) + m_iFlags &= ~HUD_ACTIVE; + + return 1; +} + + +void CHudMessage::MessageAdd( const char *pName, float time ) +{ + int i,j; + client_textmessage_t *tempMessage; + + for ( i = 0; i < maxHUDMessages; i++ ) + { + if ( !m_pMessages[i] ) + { + // Trim off a leading # if it's there + if ( pName[0] == '#' ) + pName++; + + tempMessage = TextMessageGet( pName ); + + // If we couldnt find it in the titles.txt, just create it + if ( !tempMessage ) + { + // buz: !-messages are only sentences.txt + if (pName[0] == '!') + return; + + g_pCustomMessage.effect = 2; + g_pCustomMessage.r1 = g_pCustomMessage.g1 = g_pCustomMessage.b1 = g_pCustomMessage.a1 = 100; + g_pCustomMessage.r2 = 240; + g_pCustomMessage.g2 = 110; + g_pCustomMessage.b2 = 0; + g_pCustomMessage.a2 = 0; + g_pCustomMessage.x = -1; // Centered + g_pCustomMessage.y = 0.7; + g_pCustomMessage.fadein = 0.01; + g_pCustomMessage.fadeout = 1.5; + g_pCustomMessage.fxtime = 0.25; + g_pCustomMessage.holdtime = 5; + g_pCustomMessage.pName = g_pCustomName; + strcpy( g_pCustomText, pName ); + g_pCustomMessage.pMessage = g_pCustomText; + + tempMessage = &g_pCustomMessage; + } + else if (tempMessage->effect == 3) // buz: link into subtitle system + { + SubtitleMessageAdd( tempMessage ); + return; + } + else if (tempMessage->effect == 4) // buz: link into vgui screen messages system + { + VGuiAddScreenMessage( tempMessage ); + return; + } + else if (tempMessage->effect == 5) // buz: link into vgui pickup messages system + { + VGuiAddPickupMessage( tempMessage ); + return; + } + else if (tempMessage->effect == 6) // Wargon: Ñêðîëëÿùèéñÿ òåêñò. + { + VGuiAddScrollingMessage( tempMessage ); + return; + } + else if (tempMessage->effect >= 20 && tempMessage->effect < 30) // buz: link into tips system + { + ShowTip( tempMessage ); + return; + } + + for ( j = 0; j < maxHUDMessages; j++ ) + { + if ( m_pMessages[j] ) + { + // is this message already in the list + if ( !strcmp( tempMessage->pMessage, m_pMessages[j]->pMessage ) ) + { + return; + } + + // get rid of any other messages in same location (only one displays at a time) + if ( fabs( tempMessage->y - m_pMessages[j]->y ) < 0.0001 ) + { + if ( fabs( tempMessage->x - m_pMessages[j]->x ) < 0.0001 ) + { + m_pMessages[j] = NULL; + } + } + } + } + + m_pMessages[i] = tempMessage; + m_startTime[i] = time; + return; + } + } +} + + +int CHudMessage::MsgFunc_HudText( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + char *pString = READ_STRING(); + + MessageAdd( pString, gHUD.m_flTime ); + // Remember the time -- to fix up level transitions + m_parms.time = gHUD.m_flTime; + + // Turn on drawing + if ( !(m_iFlags & HUD_ACTIVE) ) + m_iFlags |= HUD_ACTIVE; + + return 1; +} + + +int CHudMessage::MsgFunc_GameTitle( const char *pszName, int iSize, void *pbuf ) +{ + m_pGameTitle = TextMessageGet( "GAMETITLE" ); + if ( m_pGameTitle != NULL ) + { + m_gameTitleTime = gHUD.m_flTime; + + // Turn on drawing + if ( !(m_iFlags & HUD_ACTIVE) ) + m_iFlags |= HUD_ACTIVE; + } + + return 1; +} + +void CHudMessage::MessageAdd(client_textmessage_t * newMessage ) +{ + m_parms.time = gHUD.m_flTime; + + // Turn on drawing + if ( !(m_iFlags & HUD_ACTIVE) ) + m_iFlags |= HUD_ACTIVE; + + for ( int i = 0; i < maxHUDMessages; i++ ) + { + if ( !m_pMessages[i] ) + { + m_pMessages[i] = newMessage; + m_startTime[i] = gHUD.m_flTime; + return; + } + } + +} diff --git a/cl_dll/overview.h b/cl_dll/overview.h new file mode 100644 index 0000000..7f0502e --- /dev/null +++ b/cl_dll/overview.h @@ -0,0 +1,31 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef OVERVIEW_H +#define OVERVIEW_H +#pragma once + + +//----------------------------------------------------------------------------- +// Purpose: Handles the drawing of the top-down map and all the things on it +//----------------------------------------------------------------------------- +class CHudOverview : public CHudBase +{ +public: + int Init(); + int VidInit(); + + int Draw(float flTime); + void InitHUDData( void ); + +private: + HSPRITE m_hsprPlayer; + HSPRITE m_hsprViewcone; +}; + + +#endif // OVERVIEW_H diff --git a/cl_dll/parsemsg.cpp b/cl_dll/parsemsg.cpp new file mode 100644 index 0000000..7f73079 --- /dev/null +++ b/cl_dll/parsemsg.cpp @@ -0,0 +1,178 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// parsemsg.cpp +// +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "com_model.h" +#include "gl_local.h" + +typedef unsigned char byte; +#define true 1 + +static byte *gpBuf; +static int giSize; +static int giRead; +static int giBadRead; + +void BEGIN_READ( void *buf, int size ) +{ + giRead = 0; + giBadRead = 0; + giSize = size; + gpBuf = (byte*)buf; +} + + +int READ_CHAR( void ) +{ + int c; + + if (giRead + 1 > giSize) + { + giBadRead = true; + return -1; + } + + c = (signed char)gpBuf[giRead]; + giRead++; + + return c; +} + +int READ_BYTE( void ) +{ + int c; + + if (giRead+1 > giSize) + { + giBadRead = true; + return -1; + } + + c = (unsigned char)gpBuf[giRead]; + giRead++; + + return c; +} + +int READ_SHORT( void ) +{ + int c; + + if (giRead+2 > giSize) + { + giBadRead = true; + return -1; + } + + c = (short)( gpBuf[giRead] + ( gpBuf[giRead+1] << 8 ) ); + + giRead += 2; + + return c; +} + +int READ_WORD( void ) +{ + return READ_SHORT(); +} + + +int READ_LONG( void ) +{ + int c; + + if (giRead+4 > giSize) + { + giBadRead = true; + return -1; + } + + c = gpBuf[giRead] + (gpBuf[giRead + 1] << 8) + (gpBuf[giRead + 2] << 16) + (gpBuf[giRead + 3] << 24); + + giRead += 4; + + return c; +} + +float READ_FLOAT( void ) +{ + union + { + byte b[4]; + float f; + int l; + } dat; + + dat.b[0] = gpBuf[giRead]; + dat.b[1] = gpBuf[giRead+1]; + dat.b[2] = gpBuf[giRead+2]; + dat.b[3] = gpBuf[giRead+3]; + giRead += 4; + +// dat.l = LittleLong (dat.l); + + return dat.f; +} + +char* READ_STRING( void ) +{ + static char string[2048]; + int l,c; + + string[0] = 0; + + l = 0; + do + { + if ( giRead+1 > giSize ) + break; // no more characters + + c = READ_CHAR(); + if (c == 0) break; + string[l] = c; + l++; + } while (l < sizeof(string)-1); + + string[l] = 0; + + return string; +} + +float READ_COORD( void ) +{ + // g-cont. we loose precision here but keep old size of coord variable! + if( g_fRenderInitialized && RENDER_GET_PARM( PARM_FEATURES, 0 ) & ENGINE_WRITE_LARGE_COORD ) + return (float)(READ_SHORT() * 1.0f); + return (float)(READ_SHORT() * (1.0f / 8.0f)); +} + +float READ_ANGLE( void ) +{ + return (float)(READ_CHAR() * (360.0/256)); +} + +float READ_HIRESANGLE( void ) +{ + return (float)(READ_SHORT() * (360.0/65536)); +} + +BOOL REMAIN_BYTES( void ) +{ + return giRead < giSize; +} \ No newline at end of file diff --git a/cl_dll/parsemsg.h b/cl_dll/parsemsg.h new file mode 100644 index 0000000..d4cf86b --- /dev/null +++ b/cl_dll/parsemsg.h @@ -0,0 +1,38 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// parsemsg.h +// + +void BEGIN_READ( void *buf, int size ); +int READ_CHAR( void ); +int READ_BYTE( void ); +int READ_SHORT( void ); +int READ_WORD( void ); +int READ_LONG( void ); +float READ_FLOAT( void ); +char* READ_STRING( void ); +float READ_COORD( void ); +float READ_ANGLE( void ); +float READ_HIRESANGLE( void ); +BOOL REMAIN_BYTES( void ); + + + + + + + + diff --git a/cl_dll/render/cl_dlight.h b/cl_dll/render/cl_dlight.h new file mode 100644 index 0000000..4865b8a --- /dev/null +++ b/cl_dll/render/cl_dlight.h @@ -0,0 +1,88 @@ +/* +cl_dlight.h - dynamic lighting description +this code written for Paranoia 2: Savior modification +Copyright (C) 2014 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef CL_DLIGHT_H +#define CL_DLIGHT_H + +#define NUM_SHADOW_SPLITS 3 // four splits +#define MAX_SHADOWMAPS (NUM_SHADOW_SPLITS + 1) + +// dlight flags +#define DLF_NOSHADOWS BIT( 0 ) +#define DLF_NOBUMP BIT( 1 ) +#define DLF_LENSFLARE BIT( 2 ) +#define DLF_CULLED BIT( 3 ) // light culled by scissor +#define DLF_ASPECT3X4 BIT( 4 ) +#define DLF_ASPECT4X3 BIT( 5 ) +#define DLF_FLIPTEXTURE BIT( 6 ) + +class CDynLight +{ +public: + Vector origin; + Vector angles; + float radius; + Vector color; // ignored for spotlights, they have a texture + float die; // stop lighting after this time + float decay; // drop this each second + int key; + int type; // light type + bool update; // light needs update + + matrix4x4 viewMatrix; + matrix4x4 projectionMatrix; // light projection matrix + matrix4x4 modelviewMatrix; // light modelview + matrix4x4 lightviewProjMatrix; // lightview projection + matrix4x4 textureMatrix[MAX_SHADOWMAPS]; // result texture matrix + matrix4x4 shadowMatrix[MAX_SHADOWMAPS]; // result texture matrix + GLfloat gl_shadowMatrix[MAX_SHADOWMAPS][16]; // cached matrices + + Vector mins, maxs; // local bounds + Vector absmin, absmax; // world bounds + CFrustum frustum; // normal frustum + CFrustum splitFrustum[MAX_SHADOWMAPS]; + + // scissor data + float x, y, w, h; + + // spotlight specific: + int spotlightTexture; // spotlights only + int shadowTexture[MAX_SHADOWMAPS]; // shadowmap for this light + int cinTexturenum; // not gltexturenum! + int lastframe; // cinematic lastframe + int flags; + float fov; + + bool Expired( void ) + { + if( die < GET_CLIENT_TIME( )) + return true; + if( radius <= 0.0f ) + return true; + return false; + } + + bool Active( void ) + { + if( Expired( )) + return false; + if( FBitSet( flags, DLF_CULLED )) + return false; + return true; + } +}; + +#endif//CL_DLIGHT_H \ No newline at end of file diff --git a/cl_dll/render/gl_aurora.cpp b/cl_dll/render/gl_aurora.cpp new file mode 100644 index 0000000..485c191 --- /dev/null +++ b/cl_dll/render/gl_aurora.cpp @@ -0,0 +1,1170 @@ +// 02/08/02 November235: Particle System +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include +#include "entity_state.h" +#include "event_api.h" +#include "cl_entity.h" +#include "triangleapi.h" +#include "com_model.h" +#include "pmtrace.h" // for contents and traceline +#include "pm_defs.h" +#include "gl_local.h" +#include "gl_studio.h" +#include "gl_aurora.h" + +CParticleSystemManager g_pParticleSystems; // buz - static single object + +void UTIL_CreateAurora( cl_entity_t *ent, const char *file, int attachment, float lifetime ) +{ + if( !g_fXashEngine || !g_fRenderInitialized ) + return; + + int iCompare; + + // verify file exists + // g-cont. idea! use COMPARE_FILE_TIME instead of LOAD_FILE + if( COMPARE_FILE_TIME( file, file, &iCompare )) + { + CParticleSystem *pSystem = new CParticleSystem( ent, file, attachment, lifetime ); + g_pParticleSystems.AddSystem( pSystem ); + } + else + { + ALERT( at_error, "CreateAurora: couldn't load %s\n", file ); + } +} + +void UTIL_RemoveAurora( cl_entity_t *ent ) +{ + if( !g_fXashEngine || !g_fRenderInitialized ) + return; + + CParticleSystem *pSystem = g_pParticleSystems.FindSystem( NULL, ent ); + + // find all the partsystems that attached with this entity + while( pSystem != NULL ) + { + g_pParticleSystems.MarkSystemForDeletion( pSystem ); + pSystem = g_pParticleSystems.FindSystem( pSystem, ent ); + } +} + +CParticleSystemManager :: CParticleSystemManager( void ) +{ + m_pFirstSystem = NULL; +} + +CParticleSystemManager :: ~CParticleSystemManager( void ) +{ + ClearSystems(); +} + +void CParticleSystemManager :: AddSystem( CParticleSystem *pNewSystem ) +{ + pNewSystem->m_pNextSystem = m_pFirstSystem; + m_pFirstSystem = pNewSystem; +} + +CParticleSystem *CParticleSystemManager :: FindSystem( CParticleSystem *pFirstSystem, cl_entity_t *pEntity ) +{ + CParticleSystem *pSys; + + if( pFirstSystem != NULL ) + pSys = pFirstSystem->m_pNextSystem; + else pSys = m_pFirstSystem; + + while( pSys != NULL ) + { + if( pEntity == pSys->m_pEntity ) + return pSys; + pSys = pSys->m_pNextSystem; + } + + return NULL; +} + +void CParticleSystemManager :: MarkSystemForDeletion( CParticleSystem *pSys ) +{ + // parent entity is removed from server. + pSys->MarkForDeletion(); + pSys->m_pEntity = NULL; +} + +// blended particles don't use the z-buffer, so we need to sort them before drawing. +// for efficiency, only the systems are sorted - individual particles just get drawn in order of creation. +// (this should actually make things look better - no ugly popping when one particle passes through another.) +void CParticleSystemManager :: SortSystems( void ) +{ + CParticleSystem *pSystem, *pLast; + CParticleSystem *pBeforeCompare, *pCompare; + + if( !m_pFirstSystem ) return; + + // calculate how far away each system is from the viewer + for( pSystem = m_pFirstSystem; pSystem; pSystem = pSystem->m_pNextSystem ) + pSystem->CalculateDistance(); + + // do an insertion sort on the systems + pLast = m_pFirstSystem; + pSystem = pLast->m_pNextSystem; + + while( pSystem ) + { + if( pLast->m_fViewerDist < pSystem->m_fViewerDist ) + { + // pSystem is in the wrong place! First, let's unlink it from the list + pLast->m_pNextSystem = pSystem->m_pNextSystem; + + // then find somewhere to insert it + if( m_pFirstSystem == pLast || m_pFirstSystem->m_fViewerDist < pSystem->m_fViewerDist ) + { + // pSystem comes before the first system, insert it there + pSystem->m_pNextSystem = m_pFirstSystem; + m_pFirstSystem = pSystem; + } + else + { + // insert pSystem somewhere within the sorted part of the list + pBeforeCompare = m_pFirstSystem; + pCompare = pBeforeCompare->m_pNextSystem; + + while( pCompare != pLast ) + { + if( pCompare->m_fViewerDist < pSystem->m_fViewerDist ) + { + // pSystem comes before pCompare. We've found where it belongs. + break; + } + + pBeforeCompare = pCompare; + pCompare = pBeforeCompare->m_pNextSystem; + } + + // we've found where pSystem belongs. Insert it between pBeforeCompare and pCompare. + pBeforeCompare->m_pNextSystem = pSystem; + pSystem->m_pNextSystem = pCompare; + } + } + else + { + // pSystem is in the right place, move on + pLast = pSystem; + } + + pSystem = pLast->m_pNextSystem; + } +} + +void CParticleSystemManager :: UpdateSystems( void ) +{ + AURSTATE state; + + if( FBitSet( RI->params, ( RP_ENVVIEW|RP_SKYVIEW ))) + return; + + CParticleSystem *pSystem; + CParticleSystem *pLast = NULL; + pSystem = m_pFirstSystem; + + while( pSystem ) + { + state = pSystem->UpdateSystem( tr.frametime ); + + if( state != AURORA_REMOVE ) + { + if( state == AURORA_DRAW ) + pSystem->DrawSystem(); + + pLast = pSystem; + pSystem = pSystem->m_pNextSystem; + } + else + { + // delete this system + if( pLast ) + { + pLast->m_pNextSystem = pSystem->m_pNextSystem; + delete pSystem; + pSystem = pLast->m_pNextSystem; + } + else + { + // deleting the first system + m_pFirstSystem = pSystem->m_pNextSystem; + delete pSystem; + pSystem = m_pFirstSystem; + } + } + } + + gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); +} + +void CParticleSystemManager :: ClearSystems( void ) +{ + CParticleSystem *pSystem = m_pFirstSystem; + CParticleSystem *pTemp; + + while( pSystem ) + { + pTemp = pSystem->m_pNextSystem; + delete pSystem; + pSystem = pTemp; + } + + m_pFirstSystem = NULL; +} + +CParticleType :: CParticleType( CParticleType *pNext ) +{ + m_pSprayType = m_pOverlayType = NULL; + m_StartAngle = RandomRange( 45.0f ); + m_pNext = pNext; + m_szName[0] = 0; + m_hSprite = 0; + + m_StartRed = m_StartGreen = m_StartBlue = m_StartAlpha = RandomRange( 1.0f ); + m_EndRed = m_EndGreen = m_EndBlue = m_EndAlpha = RandomRange( 1.0f ); + + m_iRenderMode = kRenderTransAdd; + m_iDrawCond = CONTENTS_NONE; + m_bIsDefined = false; + m_bEndFrame = false; + m_bBouncing = false; +} + +CParticle* CParticleType :: CreateParticle( CParticleSystem *pSys ) +{ + if( !pSys ) return NULL; + + CParticle *pPart = pSys->ActivateParticle(); + if( !pPart ) return NULL; + + pPart->age = 0.0f; + pPart->age_death = m_Life.GetInstance(); + + InitParticle( pPart, pSys ); + + return pPart; +} + +void CParticleType :: InitParticle( CParticle *pPart, CParticleSystem *pSys ) +{ + float fLifeRecip; + + if( pPart->age_death > 0.0f ) + fLifeRecip = 1.0f / pPart->age_death; + else fLifeRecip = 1.0f; + + pPart->pType = this; + + pPart->velocity = pPart->origin = g_vecZero; + pPart->accel.x = pPart->accel.y = 0.0f; + pPart->accel.z = m_Gravity.GetInstance(); + pPart->m_pEntity = NULL; + + CParticle *pOverlay = NULL; + + if( m_pOverlayType ) + { + // create an overlay for this particle + pOverlay = pSys->ActivateParticle(); + + if( pOverlay ) + { + pOverlay->age = pPart->age; + pOverlay->age_death = pPart->age_death; + m_pOverlayType->InitParticle( pOverlay, pSys ); + } + } + + pPart->m_pOverlay = pOverlay; + + if( m_pSprayType ) + pPart->age_spray = 1.0f / m_SprayRate.GetInstance(); + else pPart->age_spray = 0.0f; + + pPart->m_fSize = m_StartSize.GetInstance(); + + if( m_EndSize.IsDefined( )) + pPart->m_fSizeStep = m_EndSize.GetOffset( pPart->m_fSize ) * fLifeRecip; + else pPart->m_fSizeStep = m_SizeDelta.GetInstance(); + + pPart->frame = m_StartFrame.GetInstance(); + + if( m_EndFrame.IsDefined( )) + pPart->m_fFrameStep = m_EndFrame.GetOffset( pPart->frame ) * fLifeRecip; + else pPart->m_fFrameStep = m_FrameRate.GetInstance(); + + pPart->m_fAlpha = m_StartAlpha.GetInstance(); + pPart->m_fAlphaStep = m_EndAlpha.GetOffset( pPart->m_fAlpha ) * fLifeRecip; + pPart->m_fRed = m_StartRed.GetInstance(); + pPart->m_fRedStep = m_EndRed.GetOffset( pPart->m_fRed ) * fLifeRecip; + pPart->m_fGreen = m_StartGreen.GetInstance(); + pPart->m_fGreenStep = m_EndGreen.GetOffset( pPart->m_fGreen ) * fLifeRecip; + pPart->m_fBlue = m_StartBlue.GetInstance(); + pPart->m_fBlueStep = m_EndBlue.GetOffset( pPart->m_fBlue ) * fLifeRecip; + + pPart->m_fAngle = m_StartAngle.GetInstance(); + pPart->m_fAngleStep = m_AngleDelta.GetInstance(); + + pPart->m_fDrag = m_Drag.GetInstance(); + + float fWindStrength = m_WindStrength.GetInstance(); + float fWindYaw = m_WindYaw.GetInstance(); + + if( fWindStrength != 0 ) + { + if( fWindYaw == 0 ) + { + // angle = 0, sin 0, cos 1 + pPart->m_vecWind.x = 1.0f; + pPart->m_vecWind.y = 0.0f; + pPart->m_vecWind.z = 0.0f; + } + else + { + float fSinWindYaw = CParticleSystem :: CosLookup( fWindYaw ); + float fCosWindYaw = CParticleSystem :: SinLookup( fWindYaw ); + + pPart->m_vecWind.x = fCosWindYaw; + pPart->m_vecWind.y = fSinWindYaw; + pPart->m_vecWind.z = 0; + } + + // rotate wind vector into world space + pPart->m_vecWind = pSys->entityMatrix.VectorRotate( pPart->m_vecWind ) * fWindStrength; + } + else + { + pPart->m_vecWind = g_vecZero; + } +} + +float CParticleSystem :: c_fCosTable[360 + 90]; +bool CParticleSystem :: c_bCosTableInit = false; + +//============================================ +CParticleSystem :: CParticleSystem( cl_entity_t *ent, const char *szFilename, int attachment, float lifetime ) +{ + int iParticles = 100; // default + m_iKillCondition = CONTENTS_NONE; + m_iEntAttachment = attachment; + m_pActiveParticle = NULL; + m_pMainParticle = NULL; + m_fLifeTime = lifetime; + m_pNextSystem = NULL; + m_iLightingModel = 0; + m_fViewerDist = 0.0f; + m_pFirstType = NULL; + m_pEntity = ent; + enable = true; + + entityMatrix.Identity(); + + if( !c_bCosTableInit ) + { + for( int i = 0; i < 360 + 90; i++ ) + c_fCosTable[i] = cos( i * M_PI / 180.0f ); + c_bCosTableInit = true; + } + + char *afile = (char *)gEngfuncs.COM_LoadFile( (char *)szFilename, 5, NULL ); + char szToken[1024]; + char *pfile = afile; + + if( !afile ) + { + ALERT( at_error, "couldn't load %s.\n", szFilename ); + return; + } + else + { + pfile = COM_ParseFile( pfile, szToken ); + + while( pfile ) + { + if( !Q_stricmp( szToken, "particles" )) + { + pfile = COM_ParseFile( pfile, szToken ); + iParticles = Q_atoi( szToken ); + } + else if( !Q_stricmp( szToken, "maintype" )) + { + pfile = COM_ParseFile( pfile, szToken ); + m_pMainType = AddPlaceholderType( szToken ); + } + else if( !Q_stricmp( szToken, "attachment" )) + { + pfile = COM_ParseFile( pfile, szToken ); + m_iEntAttachment = Q_atoi( szToken ); + } + else if( !Q_stricmp( szToken, "lightmodel" )) + { + pfile = COM_ParseFile( pfile, szToken ); + m_iLightingModel = Q_atoi( szToken ); + } + else if( !Q_stricmp( szToken, "lifetime" )) + { + pfile = COM_ParseFile( pfile, szToken ); + m_fLifeTime = tr.time + Q_atof( szToken ); + } + else if( !Q_stricmp( szToken, "killcondition" )) + { + pfile = COM_ParseFile( pfile, szToken ); + + if( !Q_stricmp( szToken, "empty" )) + { + m_iKillCondition = CONTENTS_EMPTY; + } + else if( !Q_stricmp( szToken, "water" )) + { + m_iKillCondition = CONTENTS_WATER; + } + else if( !Q_stricmp( szToken, "solid" )) + { + m_iKillCondition = CONTENTS_SOLID; + } + } + else if( !Q_stricmp( szToken, "{" )) + { + // parse new type + this->ParseType( pfile ); + } + + pfile = COM_ParseFile( pfile, szToken ); + } + } + + gEngfuncs.COM_FreeFile( afile ); + AllocateParticles( iParticles ); +} + +void CParticleSystem :: AllocateParticles( int iParticles ) +{ + m_pAllParticles = new CParticle[iParticles]; + memset( m_pAllParticles, 0, sizeof( CParticle ) * iParticles ); + m_pFreeParticle = m_pAllParticles; + m_pActiveParticle = NULL; + m_pMainParticle = NULL; + + // initialise the linked list + CParticle *pLast = m_pAllParticles; + CParticle *pParticle = pLast + 1; + + for( int i = 1; i < iParticles; i++ ) + { + pLast->nextpart = pParticle; + pLast = pParticle; + pParticle++; + } + + pLast->nextpart = NULL; +} + +CParticleSystem :: ~CParticleSystem( void ) +{ + delete[] m_pAllParticles; + + CParticleType *pType = m_pFirstType; + CParticleType *pNext; + + while( pType ) + { + pNext = pType->m_pNext; + delete pType; + pType = pNext; + } +} + +// returns the CParticleType with the given name, if there is one +CParticleType *CParticleSystem :: GetType( const char *szName ) +{ + for( CParticleType *pType = m_pFirstType; pType != NULL; pType = pType->m_pNext ) + { + if( !Q_stricmp( pType->m_szName, szName )) + return pType; + } + return NULL; +} + +CParticleType *CParticleSystem :: AddPlaceholderType( const char *szName ) +{ + m_pFirstType = new CParticleType( m_pFirstType ); + Q_strncpy( m_pFirstType->m_szName, szName, sizeof( m_pFirstType->m_szName )); + + return m_pFirstType; +} + +// creates a new particletype from the given file +// NB: this changes the value of szFile. +CParticleType *CParticleSystem :: ParseType( char *&pfile ) +{ + CParticleType *pType = new CParticleType(); + + // parse the .aur file + char szToken[1024]; + + pfile = COM_ParseFile( pfile, szToken ); + + while( Q_stricmp( szToken, "}" )) + { + if( !pfile ) break; + + if( !Q_stricmp( szToken, "name" )) + { + pfile = COM_ParseFile( pfile, szToken ); + Q_strncpy( pType->m_szName, szToken, sizeof( pType->m_szName )); + + CParticleType *pTemp = GetType( szToken ); + + if( pTemp ) + { + // there's already a type with this name + if( pTemp->m_bIsDefined ) + ALERT( at_warning, "Particle type %s is defined more than once!\n", szToken ); + + // copy all our data into the existing type, throw away the type we were making + *pTemp = *pType; + delete pType; + pType = pTemp; + pType->m_bIsDefined = true; // record the fact that it's defined, so we won't need to add it to the list + } + } + else if( !Q_stricmp( szToken, "gravity" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_Gravity = RandomRange( szToken ); + } + else if( !Q_stricmp( szToken, "windyaw" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_WindYaw = RandomRange( szToken ); + } + else if( !Q_stricmp( szToken, "windstrength" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_WindStrength = RandomRange( szToken ); + } + else if( !Q_stricmp( szToken, "sprite" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_hSprite = SPR_Load( szToken ); + } + else if( !Q_stricmp( szToken, "startalpha" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_StartAlpha = RandomRange( szToken ); + } + else if( !Q_stricmp( szToken, "endalpha" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_EndAlpha = RandomRange( szToken ); + } + else if( !Q_stricmp( szToken, "startred" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_StartRed = RandomRange( szToken ); + } + else if( !Q_stricmp( szToken, "endred" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_EndRed = RandomRange( szToken ); + } + else if( !Q_stricmp( szToken, "startgreen" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_StartGreen = RandomRange( szToken ); + } + else if( !Q_stricmp( szToken, "endgreen" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_EndGreen = RandomRange( szToken ); + } + else if( !Q_stricmp( szToken, "startblue" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_StartBlue = RandomRange( szToken ); + } + else if( !Q_stricmp( szToken, "endblue" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_EndBlue = RandomRange( szToken ); + } + else if( !Q_stricmp( szToken, "startsize" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_StartSize = RandomRange( szToken ); + } + else if( !Q_stricmp( szToken, "sizedelta" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_SizeDelta = RandomRange( szToken ); + } + else if( !Q_stricmp( szToken, "endsize" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_EndSize = RandomRange( szToken ); + } + else if( !Q_stricmp( szToken, "startangle" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_StartAngle = RandomRange( szToken ); + } + else if( !Q_stricmp( szToken, "angledelta" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_AngleDelta = RandomRange( szToken ); + } + else if( !Q_stricmp( szToken, "startframe" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_StartFrame = RandomRange( szToken ); + } + else if( !Q_stricmp( szToken, "endframe" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_EndFrame = RandomRange( szToken ); + pType->m_bEndFrame = true; + } + else if( !Q_stricmp( szToken, "framerate" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_FrameRate = RandomRange( szToken ); + } + else if( !Q_stricmp( szToken, "lifetime" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_Life = RandomRange( szToken ); + } + else if( !Q_stricmp( szToken, "spraytype" )) + { + pfile = COM_ParseFile( pfile, szToken ); + CParticleType *pTemp = GetType( szToken ); + + if( pTemp ) + pType->m_pSprayType = pTemp; + else + pType->m_pSprayType = AddPlaceholderType( szToken ); + } + else if( !Q_stricmp( szToken, "overlaytype" )) + { + pfile = COM_ParseFile( pfile, szToken ); + CParticleType *pTemp = GetType( szToken ); + + if( pTemp ) + pType->m_pOverlayType = pTemp; + else + pType->m_pOverlayType = AddPlaceholderType( szToken ); + } + else if( !Q_stricmp( szToken, "sprayrate" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_SprayRate = RandomRange( szToken ); + } + else if( !Q_stricmp( szToken, "sprayforce" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_SprayForce = RandomRange( szToken ); + } + else if( !Q_stricmp( szToken, "spraypitch" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_SprayPitch = RandomRange( szToken ); + } + else if( !Q_stricmp( szToken, "sprayyaw" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_SprayYaw = RandomRange( szToken ); + } + else if( !Q_stricmp( szToken, "drag" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_Drag = RandomRange( szToken ); + } + else if( !Q_stricmp( szToken, "bounce" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_Bounce = RandomRange( szToken ); + + if( pType->m_Bounce.m_flMin != 0 || pType->m_Bounce.m_flMax != 0 ) + pType->m_bBouncing = true; + } + else if( !Q_stricmp( szToken, "bouncefriction" )) + { + pfile = COM_ParseFile( pfile, szToken ); + pType->m_BounceFriction = RandomRange( szToken ); + } + else if( !Q_stricmp( szToken, "rendermode" )) + { + pfile = COM_ParseFile( pfile, szToken ); + + if( !Q_stricmp( szToken, "additive" )) + { + pType->m_iRenderMode = kRenderTransAdd; + } + else if( !Q_stricmp( szToken, "solid" )) + { + pType->m_iRenderMode = kRenderTransAlpha; + } + else if( !Q_stricmp( szToken, "texture" )) + { + pType->m_iRenderMode = kRenderTransTexture; + } + else if( !Q_stricmp( szToken, "color" )) + { + pType->m_iRenderMode = kRenderTransColor; + } + } + else if( !Q_stricmp( szToken, "drawcondition" )) + { + pfile = COM_ParseFile( pfile, szToken ); + + if( !Q_stricmp( szToken, "empty" )) + { + pType->m_iDrawCond = CONTENTS_EMPTY; + } + else if( !Q_stricmp( szToken, "water" )) + { + pType->m_iDrawCond = CONTENTS_WATER; + } + else if( !Q_stricmp( szToken, "solid" )) + { + pType->m_iDrawCond = CONTENTS_SOLID; + } + else if( !Q_stricmp( szToken, "special" ) || !Q_stricmp( szToken, "special1" )) + { + pType->m_iDrawCond = CONTENTS_SPECIAL1; + } + else if( !Q_stricmp( szToken, "special2" )) + { + pType->m_iDrawCond = CONTENTS_SPECIAL2; + } + else if( !Q_stricmp( szToken, "special3" )) + { + pType->m_iDrawCond = CONTENTS_SPECIAL3; + } + else if( !Q_stricmp( szToken, "spotlight" )) + { + pType->m_iDrawCond = CONTENTS_SPOTLIGHT; + } + } + + // get the next token + pfile = COM_ParseFile( pfile, szToken ); + } + + if( !pType->m_bIsDefined ) + { + // if this is a newly-defined type, we need to add it to the list + pType->m_pNext = m_pFirstType; + m_pFirstType = pType; + pType->m_bIsDefined = true; + } + + return pType; +} + +CParticle *CParticleSystem :: ActivateParticle( void ) +{ + CParticle *pActivated = m_pFreeParticle; + + if( pActivated ) + { + m_pFreeParticle = pActivated->nextpart; + pActivated->nextpart = m_pActiveParticle; + m_pActiveParticle = pActivated; + } + + return pActivated; +} + +void CParticleSystem :: MarkForDeletion( void ) +{ + if( m_pMainParticle ) + { + m_pMainParticle->age_death = 0.0f; // die now + m_pMainParticle = NULL; + } + + // completely remove for time-based systems + if( m_fLifeTime != 0.0f ) m_pEntity = NULL; +} + +void CParticleSystem :: CalculateDistance( void ) +{ + if( !m_pActiveParticle ) + return; + + Vector offset = GetVieworg() - m_pActiveParticle->origin; // just pick one + m_fViewerDist = DotProduct( offset, offset ); +} + +AURSTATE CParticleSystem :: UpdateSystem( float frametime ) +{ + if( m_pEntity != NULL ) + { + // don't update if the system is outside the player's PVS. + if( m_pEntity->curstate.messagenum != r_currentMessageNum ) + return AURORA_INVISIBLE; + + // time-based particle system + if( m_fLifeTime != 0.0f ) + { + enable = (m_fLifeTime >= tr.time) ? true : false; + } + else + { + enable = (m_pEntity->curstate.body) ? true : false; + } + + // check for contents to remove + if( m_iKillCondition == POINT_CONTENTS( m_pEntity->origin )) + { + m_pEntity = NULL; + enable = false; + } + } + else + { + enable = false; + } + + if( m_pEntity != NULL ) + { + Vector angles = m_pEntity->angles; + + // stupid quake bug + if( m_pEntity == GET_VIEWMODEL( )) + angles.x = -angles.x; + + // get the system entity matrix + if( m_iEntAttachment && m_pEntity->model->type == mod_studio ) + angles = R_StudioAttachmentAng( m_pEntity, m_iEntAttachment - 1 ); + entityMatrix = matrix3x3( angles ); + } + + if( m_pMainParticle == NULL ) + { + if( enable ) + { + CParticleType *pType = m_pMainType; + + if( pType ) + { + m_pMainParticle = pType->CreateParticle( this ); + + if( m_pMainParticle ) + { + // first origin initialize + if( m_iEntAttachment && m_pEntity->model->type == mod_studio ) + m_pMainParticle->origin = R_StudioAttachmentPos( m_pEntity, m_iEntAttachment - 1 ); + else m_pMainParticle->origin = m_pEntity->origin; + + m_pMainParticle->m_pEntity = m_pEntity; + m_pMainParticle->age_death = -1.0f; // never die + } + } + } + } + else if( !enable ) + { + MarkForDeletion(); + } + + // last particle is died, allow to remove partsystem + if( !m_pEntity && !m_pActiveParticle ) + return AURORA_REMOVE; + + CParticle *pParticle = m_pActiveParticle; + CParticle *pLast = NULL; + + while( pParticle ) + { + if( UpdateParticle( pParticle, frametime )) + { + pLast = pParticle; + pParticle = pParticle->nextpart; + } + else + { + // deactivate it + if( pLast ) + { + pLast->nextpart = pParticle->nextpart; + pParticle->nextpart = m_pFreeParticle; + m_pFreeParticle = pParticle; + pParticle = pLast->nextpart; + } + else + { + // deactivate the first CParticle in the list + m_pActiveParticle = pParticle->nextpart; + pParticle->nextpart = m_pFreeParticle; + m_pFreeParticle = pParticle; + pParticle = m_pActiveParticle; + } + } + } + + return AURORA_DRAW; +} + +void CParticleSystem :: DrawSystem( void ) +{ + CParticle *pParticle = m_pActiveParticle; + + if( m_pEntity != NULL ) + { + // don't draw if the system is outside the player's PVS. + if( m_pEntity->curstate.messagenum != r_currentMessageNum ) + return; + } + + m_fHasProjectionLighting = HasDynamicLights(); + + for( pParticle = m_pActiveParticle; pParticle; pParticle = pParticle->nextpart ) + { + if( pParticle->m_fSize <= 0 || !ParticleIsVisible( pParticle )) + continue; + DrawParticle( pParticle, GetVLeft(), GetVUp( )); + } +} + +bool CParticleSystem :: ParticleIsVisible( CParticle *part ) +{ + if( R_CullSphere( part->origin, part->m_fSize + 1 )) + return false; + return true; +} + +bool CParticleSystem :: UpdateParticle( CParticle *part, float frametime ) +{ + if( frametime == 0 ) + return true; + + part->age += frametime; + + // is this particle bound to an entity? + if( part->m_pEntity ) + { + if( enable ) + { + if( m_iEntAttachment && m_pEntity->model->type == mod_studio ) + { + float flSpeed = (R_StudioAttachmentPos( m_pEntity, m_iEntAttachment - 1 ) - part->origin).Length() * frametime; + part->velocity = entityMatrix.GetForward() * flSpeed; + part->origin = R_StudioAttachmentPos( m_pEntity, m_iEntAttachment - 1 ); + } + else + { + float flSpeed = (m_pEntity->origin - part->origin).Length() * frametime; + part->velocity = entityMatrix.GetForward() * flSpeed; + part->origin = m_pEntity->origin; + } + } + else + { + // entity is switched off, die + return false; + } + } + else + { + // not tied to an entity, check whether it's time to die + if( part->age_death >= 0.0f && part->age > part->age_death ) + return false; + + // apply acceleration and velocity + if( part->m_fDrag ) + part->velocity += (part->velocity - part->m_vecWind) * (-part->m_fDrag * frametime); + + part->velocity += part->accel * frametime; + part->origin += part->velocity * frametime; + + if( part->pType->m_bBouncing ) + { + Vector vecTarget = part->origin + part->velocity * frametime; + pmtrace_t *tr = gEngfuncs.PM_TraceLine( part->origin, vecTarget, PM_TRACELINE_PHYSENTSONLY, 2, -1 ); + + if( tr->fraction < 1.0f ) + { + part->origin = tr->endpos; + float bounceforce = DotProduct( tr->plane.normal, part->velocity ); + float newspeed = (1.0f - part->pType->m_BounceFriction.GetInstance( )); + part->velocity = part->velocity * newspeed; + part->velocity += tr->plane.normal * (-bounceforce * (newspeed + part->pType->m_Bounce.GetInstance())); + } + } + } + + // spray children + if( part->age_spray && part->age > part->age_spray ) + { + part->age_spray = part->age + 1.0f / part->pType->m_SprayRate.GetInstance(); + + if( part->pType->m_pSprayType ) + { + CParticle *pChild = part->pType->m_pSprayType->CreateParticle( this ); + + if( pChild ) + { + pChild->origin = part->origin; + pChild->velocity = part->velocity; + float fSprayForce = part->pType->m_SprayForce.GetInstance(); + + if( fSprayForce ) + { + Vector vecLocalAngles, vecSprayDir; + vecLocalAngles.x = part->pType->m_SprayPitch.GetInstance(); + vecLocalAngles.y = part->pType->m_SprayYaw.GetInstance(); + vecLocalAngles.z = 0.0f; + + // setup particle local direction + if( vecLocalAngles != g_vecZero ) + AngleVectors( vecLocalAngles, vecSprayDir, NULL, NULL ); + else vecSprayDir = Vector( 1, 0, 0 ); // fast case + pChild->velocity += entityMatrix.VectorRotate( vecSprayDir ) * fSprayForce; + } + } + } + } + + part->m_fSize += part->m_fSizeStep * frametime; + part->m_fAlpha += part->m_fAlphaStep * frametime; + part->m_fRed += part->m_fRedStep * frametime; + part->m_fGreen += part->m_fGreenStep * frametime; + part->m_fBlue += part->m_fBlueStep * frametime; + part->frame += part->m_fFrameStep * frametime; + + if( part->m_fAngleStep ) + { + part->m_fAngle += part->m_fAngleStep * frametime; + while( part->m_fAngle < 0 ) part->m_fAngle += 360; + while( part->m_fAngle > 360 ) part->m_fAngle -= 360; + } + + return true; +} + +void CParticleSystem :: DrawParticle( CParticle *part, vec3_t &right, vec3_t &up ) +{ + float fSize = part->m_fSize; + + Vector point[4]; + Vector origin = part->origin; + Vector absmin, absmax; + Vector4D partColor; + + float fCosSize = CosLookup( part->m_fAngle ) * fSize; + float fSinSize = SinLookup( part->m_fAngle ) * fSize; + + // calculate the four corners of the sprite + point[0] = origin + up * -fCosSize + right * -fSinSize; + point[1] = origin + up * fSinSize + right * -fCosSize; + point[2] = origin + up * fCosSize + right * fSinSize; + point[3] = origin + up * -fSinSize + right * fCosSize; + + ClearBounds( absmin, absmax ); + for( int i = 0; i < 4; i++ ) + AddPointToBounds( point[i], absmin, absmax ); + + int iContents = CONTENTS_NONE; + model_t *pModel; + + for( CParticle *pDraw = part; pDraw; pDraw = pDraw->m_pOverlay ) + { + if( !pDraw->pType->m_hSprite ) + continue; + + if( pDraw->pType->m_iDrawCond ) + { + if( pDraw->pType->m_iDrawCond == CONTENTS_SPOTLIGHT ) + { + if( !m_fHasProjectionLighting ) + continue; // fast reject + + for( int i = 0; i < MAX_DLIGHTS; i++ ) + { + CDynLight *pl = &tr.dlights[i]; + + if( !pl->Active( )) continue; + + if( !pl->frustum.CullSphere( part->origin, part->m_fSize + 1 )) + break; // cone intersected with particle + + } + + if( i == MAX_DLIGHTS ) + continue; // no intersection + } + else + { + if( iContents == CONTENTS_NONE ) + iContents = POINT_CONTENTS( origin ); + + if( iContents != pDraw->pType->m_iDrawCond ) + continue; + } + } + + pModel = (model_t *)gEngfuncs.GetSpritePointer( pDraw->pType->m_hSprite ); + + // if we've reached the end of the sprite's frames, loop back + while( pDraw->frame > pModel->numframes ) + pDraw->frame -= pModel->numframes; + + while( pDraw->frame < 0 ) + pDraw->frame += pModel->numframes; + + if( m_iLightingModel >= 1 ) + { + Vector lightColor; + + gEngfuncs.pTriAPI->LightAtPoint( part->origin, (float *)&lightColor ); + lightColor *= (1.0f / 255.0f); + partColor.x = pDraw->m_fRed * lightColor.x; + partColor.y = pDraw->m_fGreen * lightColor.y; + partColor.z = pDraw->m_fBlue * lightColor.z; + partColor.w = pDraw->m_fAlpha; + } + else + { + partColor = Vector4D( pDraw->m_fRed, pDraw->m_fGreen, pDraw->m_fBlue, pDraw->m_fAlpha ); + } +#if 0 + if( !gEngfuncs.pTriAPI->SpriteTexture( pModel, (int)pDraw->frame )) + continue; + + gEngfuncs.pTriAPI->RenderMode( pDraw->pType->m_iRenderMode ); + pglColor4f( partColor.x, partColor.y, partColor.z, partColor.w ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + + pglBegin( GL_QUADS ); + pglTexCoord2f( 0.0f, 1.0f ); + pglVertex3fv( point[0] ); + + pglTexCoord2f( 0.0f, 0.0f ); + pglVertex3fv( point[1] ); + + pglTexCoord2f( 1.0f, 0.0f ); + pglVertex3fv( point[2] ); + + pglTexCoord2f( 1.0f, 1.0f ); + pglVertex3fv( point[3] ); + pglEnd(); +#else + int hTexture; + + if(( hTexture = R_GetSpriteTexture( pModel, pDraw->frame )) == 0 ) + continue; + + CTransEntry entry; + entry.SetRenderPrimitive( point, partColor, hTexture, pDraw->pType->m_iRenderMode ); + entry.ComputeViewDistance( absmin, absmax ); + RI->frame.trans_list.AddToTail( entry ); +#endif + } +} \ No newline at end of file diff --git a/cl_dll/render/gl_aurora.h b/cl_dll/render/gl_aurora.h new file mode 100644 index 0000000..bb91dd3 --- /dev/null +++ b/cl_dll/render/gl_aurora.h @@ -0,0 +1,211 @@ +/* +r_particle.h - Laurie Cheers Aurora Particle System +First implementation of 02/08/02 November235 +Copyright (C) 2011 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef GL_PARTICLE_H +#define GL_PARTICLE_H + +class CParticleType; +class CParticleSystem; + +#include "randomrange.h" + +struct CParticle +{ + CParticle *nextpart; + CParticle *m_pOverlay; // for making multi-layered particles + CParticleType *pType; + + Vector origin; + Vector velocity; + Vector accel; + Vector m_vecWind; + + cl_entity_t *m_pEntity; // if not null, this particle is tied to the given entity + + float m_fRed; + float m_fGreen; + float m_fBlue; + float m_fRedStep; + float m_fGreenStep; + float m_fBlueStep; + + float m_fAlpha; + float m_fAlphaStep; + + float frame; + float m_fFrameStep; + + float m_fAngle; + float m_fAngleStep; + + float m_fSize; + float m_fSizeStep; + + float m_fDrag; + + float age; + float age_death; + float age_spray; +}; + +class CParticleType +{ +public: + CParticleType( CParticleType *pNext = NULL ); + + // here is a particle system. Add a (set of) particles according to this type, and initialise their values. + CParticle *CreateParticle( CParticleSystem *pSys ); + + // initialise this particle. Does not define velocity or age. + void InitParticle( CParticle *pPart, CParticleSystem *pSys ); + + bool m_bIsDefined; // is this CParticleType just a placeholder? + + int m_iRenderMode; + int m_iDrawCond; + RandomRange m_Bounce; + RandomRange m_BounceFriction; + bool m_bBouncing; + + RandomRange m_Life; + + RandomRange m_StartAlpha; + RandomRange m_EndAlpha; + RandomRange m_StartRed; + RandomRange m_EndRed; + RandomRange m_StartGreen; + RandomRange m_EndGreen; + RandomRange m_StartBlue; + RandomRange m_EndBlue; + + RandomRange m_StartSize; + RandomRange m_SizeDelta; + RandomRange m_EndSize; + + RandomRange m_StartFrame; + RandomRange m_EndFrame; + RandomRange m_FrameRate; // incompatible with EndFrame + bool m_bEndFrame; + + RandomRange m_StartAngle; + RandomRange m_AngleDelta; + + RandomRange m_SprayRate; + RandomRange m_SprayForce; + RandomRange m_SprayPitch; + RandomRange m_SprayYaw; + RandomRange m_SprayRoll; + + CParticleType *m_pSprayType; + + RandomRange m_Gravity; + RandomRange m_WindStrength; + RandomRange m_WindYaw; + + HSPRITE m_hSprite; + CParticleType *m_pOverlayType; + + RandomRange m_Drag; + + CParticleType *m_pNext; + char m_szName[32]; +}; + +typedef enum +{ + AURORA_REMOVE = 0, + AURORA_INVISIBLE, + AURORA_DRAW, +} AURSTATE; + +class CParticleSystem +{ +public: + CParticleSystem( cl_entity_t *ent, const char *szFilename, int attachment = 0, float lifetime = 0.0f ); + ~CParticleSystem( void ); + + void AllocateParticles( int iParticles ); + void CalculateDistance( void ); + CParticleType *GetType( const char *szName ); + CParticleType *AddPlaceholderType( const char *szName ); + CParticleType *ParseType( char *&szFile ); + + cl_entity_t *GetEntity() { return m_pEntity; } + + // General functions + AURSTATE UpdateSystem( float frametime ); // If this function returns false, the manager deletes the system + void DrawSystem( void ); + CParticle *ActivateParticle( void ); // adds one of the free particles to the active list, and returns it for initialisation. + // MUST CHECK WHETHER THIS RESULT IS NULL! + // returns false if the particle has died + bool UpdateParticle( CParticle *part, float frametime ); + void DrawParticle( CParticle *part, Vector &right, Vector &up ); + + // Utility functions that have to be public + bool ParticleIsVisible( CParticle* part ); + + void MarkForDeletion( void ); + + static float CosLookup( int angle ) { return angle < 0 ? c_fCosTable[angle+360] : c_fCosTable[angle]; } + static float SinLookup( int angle ) { return angle < -90 ? c_fCosTable[angle+450] : c_fCosTable[angle+90]; } + + // Pointer to next system for linked list structure + CParticleSystem *m_pNextSystem; + + CParticle *m_pActiveParticle; + float m_fViewerDist; + cl_entity_t *m_pEntity; + int m_iEntAttachment; + int m_iKillCondition; + int m_iLightingModel; + matrix3x3 entityMatrix; + int m_fHasProjectionLighting; + float m_fLifeTime; // for auto-removed particles + bool enable; + +private: + static float c_fCosTable[360 + 90]; + static bool c_bCosTableInit; + + // the block of allocated particles + CParticle *m_pAllParticles; + + // First particles in the linked list for the active particles and the dead particles + CParticle *m_pFreeParticle; + CParticle *m_pMainParticle; // the "source" particle. + + CParticleType *m_pFirstType; + CParticleType *m_pMainType; +}; + +class CParticleSystemManager +{ +public: + CParticleSystemManager( void ); + ~CParticleSystemManager( void ); + void AddSystem( CParticleSystem* ); + CParticleSystem *FindSystem( CParticleSystem *pFirstSystem, cl_entity_t *pEntity ); + void MarkSystemForDeletion( CParticleSystem *pSys ); + void UpdateSystems( void ); + void ClearSystems( void ); + void SortSystems( void ); +private: + CParticleSystem *m_pFirstSystem; +}; + +extern CParticleSystemManager g_pParticleSystems; // buz + +#endif//GL_PARTICLE_H \ No newline at end of file diff --git a/cl_dll/render/gl_backend.cpp b/cl_dll/render/gl_backend.cpp new file mode 100644 index 0000000..d2aad8a --- /dev/null +++ b/cl_dll/render/gl_backend.cpp @@ -0,0 +1,1055 @@ +/* +gl_backend.cpp - renderer backend routines +Copyright (C) 2013 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "com_model.h" +#include +#include "r_studioint.h" +#include +#include +#include "ref_params.h" +#include "pmtrace.h" +#include "event_api.h" +#include "gl_local.h" +#include "gl_studio.h" +#include "gl_sprite.h" +#include "gl_world.h" +#include "gl_grass.h" +#include "gl_shader.h" +#include "screenfade.h" +#include "shake.h" + +#define SKY_FOG_FACTOR 16.0f // experimentally determined value (chislo s potolka) + +/* +============== +R_Speeds_Printf + +helper to print into r_speeds message +============== +*/ +void R_Speeds_Printf( const char *msg, ... ) +{ + va_list argptr; + char text[2048]; + + va_start( argptr, msg ); + Q_vsprintf( text, msg, argptr ); + va_end( argptr ); + + Q_strncat( r_speeds_msg, text, sizeof( r_speeds_msg )); +} + +/* +============== +GL_PrintStats + +display renderer stats +============== +*/ +void GL_PrintStats( int params ) +{ + GLint cur_avail_mem_kb = 0; + GLint total_mem_kb = 0; + + if( r_speeds->value <= 0.0f || !FBitSet( params, RP_DRAW_WORLD )) + return; + + msurface_t *surf = r_stats.debug_surface; + + switch( (int)r_speeds->value ) + { + case 1: + R_Speeds_Printf( "%3i solid faces\n%3i solid meshes\n", RI->frame.solid_faces.Count(), RI->frame.solid_meshes.Count() ); + R_Speeds_Printf( "%3i trans entries\n%3i grass faces\n", RI->frame.trans_list.Count(), RI->frame.grass_list.Count() ); + R_Speeds_Printf( "%3i mirror faces\n%3i tess verts\n", RI->frame.num_subview_faces, RI->frame.primverts.Count() ); + break; + case 2: + R_Speeds_Printf( "DIP count %3i\nShader bind %3i\n", r_stats.num_flushes, r_stats.num_shader_binds ); + R_Speeds_Printf( "Frame total tris %3i\n", r_stats.c_total_tris ); + R_Speeds_Printf( "Total GLSL shaders %3i", num_glsl_programs - 1 ); + break; + case 3: + Q_snprintf( r_speeds_msg, sizeof( r_speeds_msg ), "%3i mirrors\n%3i shadow passes\n%3i screencopy\n%3i occluded", + r_stats.c_subview_passes, r_stats.c_shadow_passes, r_stats.c_screen_copy, r_stats.c_occlusion_culled ); + break; + case 4: + if( glConfig.hardware_type == GLHW_NVIDIA ) + { + pglGetIntegerv( GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX, &total_mem_kb ); + pglGetIntegerv( GL_GPU_MEM_INFO_CURRENT_AVAILABLE_MEM_NVX, &cur_avail_mem_kb ); + } + R_Speeds_Printf( "TEX used memory %s\n", Q_memprint( RENDER_GET_PARM( PARM_TEX_MEMORY, 0 ))); + R_Speeds_Printf( "VBO used memory %s\n", Q_memprint( tr.total_vbo_memory )); + R_Speeds_Printf( "GPU used memory %s\n", Q_memprint(( total_mem_kb - cur_avail_mem_kb ) * 1024 )); + break; + case 5: + // draw hierarchy map of recursion calls + R_Speeds_Printf( "total %3i subview passes\n", r_stats.c_subview_passes ); + R_Speeds_Printf( "%s", r_depth_msg ); + break; + case 6: + if( r_stats.debug_surface != NULL ) + { + glsl_program_t *cur = surf->info->forwardScene[0].GetShader(); + R_Speeds_Printf( "Surf: ^1%s^7\n", surf->texinfo->texture->name ); + R_Speeds_Printf( "Shader: ^3#%i %s^7\n", surf->info->forwardScene[0].GetHandle(), cur->name ); + R_Speeds_Printf( "List Options:\n" ); + R_Speeds_Printf( "%s\n", GL_PretifyListOptions( cur->options, true )); + } + break; + case 7: + R_Speeds_Printf( "%3i world lights (from %d lights)\n", r_stats.c_worldlights, world->numworldlights ); + break; + } +} + +/* +============== +GL_ComputeScreenRays +============== +*/ +void GL_ComputeScreenRays( void ) +{ + vec3_t axis[3]; + float tx, ty; + int i; + + // compute the world-space rays to the far plane corners + tx = tan( DEG2RAD( RI->view.fov_x * 0.5f )); + ty = tan( DEG2RAD( RI->view.fov_y * 0.5f )); + + for( i = 0; i < 3; i++ ) + { + axis[0][i] = RI->view.matrix[0][i]; + axis[1][i] = RI->view.matrix[1][i] * tx; + axis[2][i] = RI->view.matrix[2][i] * ty; + + // counter-clockwise order + tr.screen_normals[0][i] = axis[0][i] + axis[1][i] + axis[2][i]; // top left + tr.screen_normals[1][i] = axis[0][i] + axis[1][i] - axis[2][i]; // bottom left + tr.screen_normals[2][i] = axis[0][i] - axis[1][i] - axis[2][i]; // bottom right + tr.screen_normals[3][i] = axis[0][i] - axis[1][i] + axis[2][i]; // top right + } +} + +/* +============== +GL_ComputeSunParams +============== +*/ +void GL_ComputeSunParams( const Vector &skyVector ) +{ + float sunPos = -skyVector.z; + float RefractionFactor = ( 1.0f - sqrt( Q_max( sunPos, 0.0f ))); + Vector SunColor = Vector( 1.0f, 1.0f, 1.0f ) - Vector( 0.0f, 0.5f, 1.0f ) * RefractionFactor; + + // calculate ambient and diffuse light color + Vector DiffuseColor = Vector( 1.0f, 1.0f, 1.0f ) - Vector( 0.0f, 0.25f, 0.5f ) * RefractionFactor; + float AmbientIntensity = 0.0025f + 0.1875f * Q_min( 1.0f, Q_max( 0.0f, ( 0.375f + sunPos ) / 0.25f )); + float DiffuseIntensity = 0.75f * Q_min( 1.0f, Q_max( 0.0f, ( 0.03125f + sunPos ) / 0.0625f )); + + float ambientFactor = RemapVal( tr.ambientFactor, AMBIENT_EPSILON, r_lighting_modulate->value, 0.0f, 1.0f ); + tr.sun_ambient = AmbientIntensity * ambientFactor * 2.0f; + tr.sun_ambient = bound( 0.0f, tr.sun_ambient, tr.ambientFactor ); + tr.sun_diffuse = DiffuseColor * DiffuseIntensity; + tr.sun_refract = RefractionFactor; + tr.sky_normal = skyVector; +} + +/* +============== +GL_BackendStartFrame +============== +*/ +bool GL_BackendStartFrame( ref_viewpass_t *rvp, int params ) +{ + bool allow_dynamic_sun = false; + static float cached_lighting = 0.0f; + static float shadowmap_size = 0.0f; + static int waterlevel_old; + + r_speeds_msg[0] = r_depth_msg[0] = '\0'; + tr.fCustomRendering = false; + + tr.fGamePaused = RENDER_GET_PARM( PARAM_GAMEPAUSED, 0 ); + memset( &r_stats, 0, sizeof( r_stats )); + tr.sunlight = NULL; + + if( r_buildstats.total_buildtime > 0.0 && RENDER_GET_PARM( PARM_CLIENT_ACTIVE, 0 )) + { + // display time to building some stuff: lighting, VBO, shaders etc + if( r_buildstats.compile_shader > 0.0 ) + ALERT( at_aiconsole, "shader compiled in %g secs\n", r_buildstats.compile_shader ); + if( r_buildstats.create_buffer_object > 0.0 ) + ALERT( at_aiconsole, "creating VBO in %g secs\n", r_buildstats.create_buffer_object - r_buildstats.compile_shader ); + if( r_buildstats.create_light_cache > 0.0 ) + ALERT( at_aiconsole, "creating VL cache in %g secs\n", r_buildstats.create_light_cache ); + if( r_buildstats.create_buffer_object > 0.0 || r_buildstats.create_light_cache > 0.0 ) + ALERT( at_aiconsole, "total building time %g\n", r_buildstats.total_buildtime ); + memset( &r_buildstats, 0, sizeof( r_buildstats )); + } + + if( !g_fRenderInitialized ) + return 0; + + if( !FBitSet( params, RP_DRAW_WORLD )) + GL_Setup3D(); + + if( CVAR_TO_BOOL( r_finish ) && FBitSet( params, RP_DRAW_WORLD )) + pglFinish(); + + // setup light factors + tr.ambientFactor = bound( AMBIENT_EPSILON, r_lighting_ambient->value, r_lighting_modulate->value ); + float cachedFactor = bound( tr.ambientFactor, r_lighting_modulate->value, 1.0f ); + + if( cachedFactor != tr.diffuseFactor ) + { + tr.diffuseFactor = cachedFactor; + R_StudioClearLightCache(); + } + + if( cached_lighting != r_lighting_extended->value ) + { + cached_lighting = r_lighting_extended->value; + R_StudioClearLightCache(); + } + + if( shadowmap_size != r_shadowmap_size->value ) + { + shadowmap_size = r_shadowmap_size->value; + R_InitShadowTextures(); + } + + // FIXME: allow player overview for custom renderer + if( !FBitSet( params, RP_DRAW_WORLD )) + return 0; + + // use engine renderer + if( cv_renderer->value == 0 ) + { + glState.depthmin = -1.0f; + glState.depthmax = -1.0f; + glState.depthmask = -1; + return 0; + } + + // keep world in actual state + GET_ENTITY( 0 )->curstate.messagenum = r_currentMessageNum; + + if( gHUD.m_flLevelTime != -1.0f && CVAR_TO_BOOL( r_sun_allowed )) + { + Vector skyVector; + matrix4x4 a; + + float time = gHUD.m_flLevelTime; + float timeAng = ((( time + 12.0f ) / 24.0f ) * M_PI * 2.0f ); + float longitude = RAD2DEG( 0.5f * M_PI ) - 90.0f; + + a.CreateRotate( RAD2DEG( timeAng ) - 180.0f, 1.0, 0.5f, 0.0f ); + a.ConcatRotate( longitude, -0.5f, 1.0f, 0.0f ); + skyVector = a.VectorRotate( Vector( 0.0f, 0.0f, 1.0f )); + + gEngfuncs.Con_NPrintf( 7, "time %i:%02.f\n", (int)time, ( time - (int)time ) * 59.0f ); + skyVector = skyVector.Normalize(); + GL_ComputeSunParams( skyVector ); + allow_dynamic_sun = true; + } + else + { + Vector skyVector; + + skyVector.x = tr.movevars->skyvec_x; + skyVector.y = tr.movevars->skyvec_y; + skyVector.z = tr.movevars->skyvec_z; + skyVector = skyVector.Normalize(); + + GL_ComputeSunParams( skyVector ); + + // hidden test mode + if( r_sun_allowed->value == 2.0 ) + allow_dynamic_sun = true; + } + + tr.sky_ambient.x = tr.movevars->skycolor_r; + tr.sky_ambient.y = tr.movevars->skycolor_g; + tr.sky_ambient.z = tr.movevars->skycolor_b; + + // NOTE: sunlight must be added first in list + if( FBitSet( world->features, WORLD_HAS_SKYBOX ) && allow_dynamic_sun ) + { + tr.sunlight = CL_AllocDlight( SUNLIGHT_KEY ); // alloc sun source + R_SetupLightParams( tr.sunlight, g_vecZero, g_vecZero, 32768.0f, 90.0f, LIGHT_DIRECTIONAL ); + R_SetupLightTexture( tr.sunlight, tr.whiteTexture ); + tr.sunlight->die = GET_CLIENT_TIME(); + tr.sunlight->color = tr.sun_diffuse; + tr.sun_light_enabled = true; + } + + tr.gravity = tr.movevars->gravity; + tr.farclip = tr.movevars->zmax; + + if( tr.farclip == 0.0f || worldmodel == NULL ) + tr.farclip = 4096.0f; + + tr.cached_vieworigin = rvp->vieworigin; + tr.cached_viewangles = rvp->viewangles; + tr.waterlevel = g_pViewParams->waterlevel; + + if(( tr.waterlevel >= 3 ) != ( waterlevel_old >= 3 )) + { + waterlevel_old = tr.waterlevel; + tr.glsl_valid_sequence++; // now all uber-shaders are invalidate and possible need for recompile + tr.params_changed = true; + } + + if( tr.waterlevel >= 3 ) + { + int waterent = WATER_ENTITY( g_pViewParams->vieworg ); + + // FIXME: how to allow fog on a world water? + if( waterent > 0 && waterent < g_pViewParams->max_entities ) + tr.waterentity = GET_ENTITY( waterent ); + else tr.waterentity = NULL; + } + else tr.waterentity = NULL; + + R_GrassSetupFrame(); + + // check for fog + if( tr.waterentity ) + { + entity_state_t *state = &tr.waterentity->curstate; + + if( state->rendercolor.r || state->rendercolor.g || state->rendercolor.b ) + { + // enable global exponential color fog + tr.fogColor[0] = (state->rendercolor.r) / 255.0f; + tr.fogColor[1] = (state->rendercolor.g) / 255.0f; + tr.fogColor[2] = (state->rendercolor.b) / 255.0f; + tr.fogDensity = (state->renderamt) * 0.000025f; + tr.fogSkyDensity = tr.fogDensity * SKY_FOG_FACTOR; + tr.fogEnabled = true; + } + } + else if( tr.movevars->fog_settings != 0 ) + { + // enable global exponential color fog + tr.fogColor[0] = (float)TEXTURE_TO_TEXGAMMA((tr.movevars->fog_settings & 0xFF000000) >> 24) / 255.0f; + tr.fogColor[1] = (float)TEXTURE_TO_TEXGAMMA((tr.movevars->fog_settings & 0xFF0000) >> 16) / 255.0f; + tr.fogColor[2] = (float)TEXTURE_TO_TEXGAMMA((tr.movevars->fog_settings & 0xFF00) >> 8) / 255.0f; + tr.fogDensity = (tr.movevars->fog_settings & 0xFF) * 0.000025f; + tr.fogSkyDensity = tr.fogDensity * SKY_FOG_FACTOR; + tr.fogEnabled = true; + } + else + { + tr.fogColor[0] = 0.0f; + tr.fogColor[1] = 0.0f; + tr.fogColor[2] = 0.0f; + tr.fogDensity = 0.0f; + tr.fogSkyDensity = 0.0f; + tr.fogEnabled = false; + } + + // apply the underwater warp + if( tr.waterlevel >= 3 ) + { + float f = sin( tr.time * 0.4f * ( M_PI * 1.7f )); + rvp->fov_x += f; + rvp->fov_y -= f; + } + + // apply the blur effect + if( gHUD.m_flBlurAmount > 0.0f ) + { + float f = sin( tr.time * 0.4 * ( M_PI * 1.7f )) * gHUD.m_flBlurAmount * 20.0f; + rvp->fov_x += f; + rvp->fov_y += f; + + screenfade_t fade; + + f = ( sin( tr.time * 0.5f * ( M_PI * 1.7f )) * 127 ) + 128; + fade.fadeFlags = FFADE_STAYOUT; + fade.fader = fade.fadeg = fade.fadeb = 0; + fade.fadeReset = tr.time + 0.1f; + fade.fadeEnd = tr.time + 0.1f; + fade.fadealpha = bound( 0, (byte)f, gHUD.m_flBlurAmount * 255 ); + + gEngfuncs.pfnSetScreenFade( &fade ); + } + + Mod_ResortFaces(); + GL_LoadAndRebuildCubemaps( params ); + tr.fCustomRendering = true; + r_stats.debug_surface = NULL; + + if( r_speeds->value == 6.0f ) + { + Vector vecSrc = RI->view.origin; + Vector vecEnd = vecSrc + GetVForward() * RI->view.farClip; + pmtrace_t trace; + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PlayerTrace( (float *)&vecSrc, (float *)&vecEnd, PM_NORMAL, -1, &trace ); + r_stats.debug_surface = gEngfuncs.pEventAPI->EV_TraceSurface( trace.ent, (float *)&vecSrc, (float *)&vecEnd ); + } + + // setup light animation tables + R_AnimateLight(); + + return 1; +} + +/* +============== +GL_BackendEndFrame +============== +*/ +void GL_BackendEndFrame( ref_viewpass_t *rvp, int params ) +{ + mstudiolight_t light; + + tr.frametime = tr.saved_frametime; + + // go into 2D mode (in case we draw PlayerSetup between two 2d calls) + if( !FBitSet( params, RP_DRAW_WORLD )) + GL_Setup2D(); + + if( tr.show_uniforms_peak ) + { + ALERT( at_aiconsole, "peak used uniforms: %i\n", glConfig.peak_used_uniforms ); + tr.show_uniforms_peak = false; + } + + DrawLightProbes(); // 3D + + DrawCubeMaps(); // 3D + + DrawViewLeaf(); // 3D + + DrawWireFrame(); // 3D + + DrawTangentSpaces(); // 3D + + DrawWirePoly( r_stats.debug_surface ); // 3D + + DBG_DrawLightFrustum(); // 3D + + R_PushRefState(); + RI->params = params; + RI->view.fov_x = rvp->fov_x; + RI->view.fov_y = rvp->fov_y; + + if( !CVAR_TO_BOOL( cv_deferred )) + R_DrawViewModel(); // 3D + + RenderSunShafts(); // 2D + RenderDOF(); // 2D + + RenderNerveGasBlur(); // 2D + RenderUnderwaterBlur(); // 2D + + if( !CVAR_TO_BOOL( cv_deferred )) + R_DrawHeadShield(); // 3D + R_RenderDebugStudioList( true ); // 3D + + RenderMonochrome(); // 2D + R_ShowLightMaps(); // 2D + + if( g_iGunMode == 3 ) + { + // used for lighting scope + R_LightVec( rvp->vieworigin, &light, true ); + tr.ambientLight = light.diffuse; + } + + GL_CleanupDrawState(); + R_PopRefState(); + + // fill the r_speeds message + GL_PrintStats( params ); + + R_UnloadFarGrass(); + + tr.params_changed = false; + tr.realframecount++; + +} + +/* +=============== +R_ScreenToWorld + +transform point from 3D position to ortho +=============== +*/ +bool R_WorldToScreen( const Vector &point, Vector &screen ) +{ + matrix4x4 worldToScreen; + bool behind; + float w; + + worldToScreen = RI->view.worldProjectionMatrix; + + screen[0] = worldToScreen[0][0] * point[0] + worldToScreen[1][0] * point[1] + worldToScreen[2][0] * point[2] + worldToScreen[3][0]; + screen[1] = worldToScreen[0][1] * point[0] + worldToScreen[1][1] * point[1] + worldToScreen[2][1] * point[2] + worldToScreen[3][1]; + w = worldToScreen[0][3] * point[0] + worldToScreen[1][3] * point[1] + worldToScreen[2][3] * point[2] + worldToScreen[3][3]; + screen[2] = 0.0f; // just so we have something valid here + + if( w < 0.001f ) + { + behind = true; + screen[0] *= 100000; + screen[1] *= 100000; + } + else + { + float invw = 1.0f / w; + behind = false; + screen[0] *= invw; + screen[1] *= invw; + } + + return behind; +} + +/* +=============== +R_ScreenToWorld + +transform point from ortho to 3D position +=============== +*/ +void R_ScreenToWorld( const Vector &screen, Vector &point ) +{ + matrix4x4 screenToWorld; + Vector temp; + float w; + + screenToWorld = RI->view.worldProjectionMatrix.InvertFull(); + + temp[0] = 2.0f * (screen[0] - RI->view.port[0]) / RI->view.port[2] - 1; + temp[1] = -2.0f * (screen[1] - RI->view.port[1]) / RI->view.port[3] + 1; + temp[2] = 0.0f; // just so we have something valid here + + point[0] = temp[0] * screenToWorld[0][0] + temp[1] * screenToWorld[0][1] + temp[2] * screenToWorld[0][2] + screenToWorld[0][3]; + point[1] = temp[0] * screenToWorld[1][0] + temp[1] * screenToWorld[1][1] + temp[2] * screenToWorld[1][2] + screenToWorld[1][3]; + point[2] = temp[0] * screenToWorld[2][0] + temp[1] * screenToWorld[2][1] + temp[2] * screenToWorld[2][2] + screenToWorld[2][3]; + w = temp[0] * screenToWorld[3][0] + temp[1] * screenToWorld[3][1] + temp[2] * screenToWorld[3][2] + screenToWorld[3][3]; + if( w ) point *= ( 1.0f / w ); +} + +/* +============= +R_GetSpriteTexture + +============= +*/ +int R_GetSpriteTexture( const model_t *m_pSpriteModel, int frame ) +{ + if( !m_pSpriteModel || m_pSpriteModel->type != mod_sprite || !m_pSpriteModel->cache.data ) + return 0; + + return R_GetSpriteFrame( m_pSpriteModel, frame )->gl_texturenum; +} + +void R_RenderQuadPrimitive( CSolidEntry *entry ) +{ + if( entry->m_bDrawType != DRAWTYPE_QUAD ) + return; + + GL_CleanupDrawState(); + + // select properly rendermode + switch( entry->m_iRenderMode ) + { + case kRenderTransAlpha: + GL_AlphaTest( GL_TRUE ); + pglAlphaFunc( GL_GREATER, 0.0f ); + case kRenderTransColor: + case kRenderTransTexture: + GL_Blend( GL_TRUE ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + GL_DepthMask( GL_FALSE ); + break; + case kRenderGlow: + pglDisable( GL_DEPTH_TEST ); + case kRenderTransAdd: + GL_Blend( GL_TRUE ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE ); + GL_DepthMask( GL_FALSE ); + break; + case kRenderNormal: + default: + GL_DepthMask( GL_TRUE ); + GL_Blend( GL_FALSE ); + break; + } + + int r, g, b, a; + + // draw the particle + GL_BindTexture( GL_TEXTURE0, entry->m_hTexture ); + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + UnpackRGBA( r, g, b, a, entry->m_iColor ); + pglColor4ub( r, g, b, a ); + + pglBegin( GL_QUADS ); + pglTexCoord2f( 0.0f, 1.0f ); + pglVertex3fv( RI->frame.primverts[entry->m_iStartVertex+0] ); + + pglTexCoord2f( 0.0f, 0.0f ); + pglVertex3fv( RI->frame.primverts[entry->m_iStartVertex+1] ); + + pglTexCoord2f( 1.0f, 0.0f ); + pglVertex3fv( RI->frame.primverts[entry->m_iStartVertex+2] ); + + pglTexCoord2f( 1.0f, 1.0f ); + pglVertex3fv( RI->frame.primverts[entry->m_iStartVertex+3] ); + pglEnd(); + + if( entry->m_iRenderMode == kRenderGlow ) + pglEnable( GL_DEPTH_TEST ); + + if( entry->m_iRenderMode == kRenderTransAlpha ) + GL_AlphaTest( GL_FALSE ); +} + +int R_AllocFrameBuffer( int viewport[4] ) +{ + int i = tr.num_framebuffers; + + if( i >= MAX_FRAMEBUFFERS ) + { + ALERT( at_error, "R_AllocateFrameBuffer: FBO limit exceeded!\n" ); + return -1; // disable + } + + gl_fbo_t *fbo = &tr.frame_buffers[i]; + tr.num_framebuffers++; + + if( fbo->init ) + { + ALERT( at_warning, "R_AllocFrameBuffer: buffer %i already initialized\n", i ); + return i; + } + + // create a depth-buffer + pglGenRenderbuffers( 1, &fbo->renderbuffer ); + pglBindRenderbuffer( GL_RENDERBUFFER_EXT, fbo->renderbuffer ); + pglRenderbufferStorage( GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, viewport[2], viewport[3] ); + pglBindRenderbuffer( GL_RENDERBUFFER_EXT, 0 ); + + // create frame-buffer + pglGenFramebuffers( 1, &fbo->framebuffer ); + pglBindFramebuffer( GL_FRAMEBUFFER_EXT, fbo->framebuffer ); + pglFramebufferRenderbuffer( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fbo->renderbuffer ); + pglDrawBuffer( GL_COLOR_ATTACHMENT0_EXT ); + pglReadBuffer( GL_COLOR_ATTACHMENT0_EXT ); + pglBindFramebuffer( GL_FRAMEBUFFER_EXT, 0 ); + fbo->init = true; + + return i; +} + +void R_FreeFrameBuffer( int buffer ) +{ + if( buffer < 0 || buffer >= MAX_FRAMEBUFFERS ) + { + ALERT( at_error, "R_FreeFrameBuffer: invalid buffer enum %i\n", buffer ); + return; + } + + gl_fbo_t *fbo = &tr.frame_buffers[buffer]; + + pglDeleteRenderbuffers( 1, &fbo->renderbuffer ); + pglDeleteFramebuffers( 1, &fbo->framebuffer ); + memset( fbo, 0, sizeof( *fbo )); +} + +void GL_BindFrameBuffer( int buffer, int texture ) +{ + gl_fbo_t *fbo = NULL; + + if( buffer >= 0 && buffer < MAX_FRAMEBUFFERS ) + fbo = &tr.frame_buffers[buffer]; + + if( !fbo ) + { + pglBindFramebuffer( GL_FRAMEBUFFER_EXT, 0 ); + glState.frameBuffer = buffer; + return; + } + + // at this point FBO index is always positive + if((unsigned int)glState.frameBuffer != fbo->framebuffer ) + { + pglBindFramebuffer( GL_FRAMEBUFFER_EXT, fbo->framebuffer ); + glState.frameBuffer = fbo->framebuffer; + } + + if( fbo->texture != texture ) + { + // change texture attachment + GLuint texnum = RENDER_GET_PARM( PARM_TEX_TEXNUM, texture ); + pglFramebufferTexture2D( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, texnum, 0 ); + fbo->texture = texture; + } +} + +/* +============== +GL_BindFBO +============== +*/ +void GL_BindFBO( GLuint buffer ) +{ + if( !GL_Support( R_FRAMEBUFFER_OBJECT )) + return; + + if( glState.frameBuffer == buffer ) + return; + + pglBindFramebuffer( GL_FRAMEBUFFER_EXT, buffer ); + glState.frameBuffer = buffer; +} + +/* +================== +GL_BindDrawbuffer +================== +*/ +void GL_BindDrawbuffer( gl_drawbuffer_t *drawbuffer ) +{ + if( !GL_Support( R_FRAMEBUFFER_OBJECT )) + return; + + if( !drawbuffer ) + { + if( !glState.frameBuffer ) + return; + + pglBindFramebuffer( GL_FRAMEBUFFER_EXT, 0 ); + glState.frameBuffer = 0; + return; + } + + if( glState.frameBuffer == drawbuffer->id ) + return; + + pglBindFramebuffer( GL_FRAMEBUFFER_EXT, drawbuffer->id ); + glState.frameBuffer = drawbuffer->id; +} + +/* +============== +GL_DepthRange +============== +*/ +void GL_DepthRange( GLfloat depthmin, GLfloat depthmax ) +{ + if( glState.depthmin == depthmin && glState.depthmax == depthmax ) + return; + + pglDepthRange( depthmin, depthmax ); + glState.depthmin = depthmin; + glState.depthmax = depthmax; +} + +/* +============== +GL_DepthMask +============== +*/ +void GL_DepthMask( GLint enable ) +{ +// it's won't work anyway +// if( glState.depthmask == enable ) +// return; + + glState.depthmask = enable; + pglDepthMask( enable ); +} + +/* +============== +GL_AlphaTest +============== +*/ +void GL_AlphaTest( GLint enable ) +{ + if( pglIsEnabled( GL_ALPHA_TEST ) == enable ) + return; + + if( enable ) pglEnable( GL_ALPHA_TEST ); + else pglDisable( GL_ALPHA_TEST ); +} + +/* +============== +GL_DepthTest +============== +*/ +void GL_DepthTest( GLint enable ) +{ + if( pglIsEnabled( GL_DEPTH_TEST ) == enable ) + return; + + if( enable ) pglEnable( GL_DEPTH_TEST ); + else pglDisable( GL_DEPTH_TEST ); +} + +/* +============== +GL_Blend +============== +*/ +void GL_Blend( GLint enable ) +{ + if( pglIsEnabled( GL_BLEND ) == enable ) + return; + + if( enable ) pglEnable( GL_BLEND ); + else pglDisable( GL_BLEND ); +} + +/* +============== +GL_CleanupAllTextureUnits +============== +*/ +void GL_CleanupAllTextureUnits( void ) +{ + // force to cleanup all the units + GL_SelectTexture( glConfig.max_texture_units - 1 ); + GL_CleanUpTextureUnits( 0 ); +} + +/* +============== +GL_CleanupDrawState +============== +*/ +void GL_CleanupDrawState( void ) +{ + GL_CleanupAllTextureUnits(); + pglBindVertexArray( GL_FALSE ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); + GL_BindShader( NULL ); +} + +/* +============== +GL_CheckVertexArrayBinding +============== +*/ +void GL_CheckVertexArrayBinding( void ) +{ + int current_vao = 0; + + pglGetIntegerv( GL_VERTEX_ARRAY_BINDING, (int *)¤t_vao ); + if( !current_vao ) return; + + ALERT( at_error, "detected active VAO %d while uploading vertex buffer!\n", current_vao ); + pglBindVertexArray( GL_FALSE ); +} + +/* +============== +GL_DisableAllTexGens +============== +*/ +void GL_DisableAllTexGens( void ) +{ + GL_TexGen( GL_S, 0 ); + GL_TexGen( GL_T, 0 ); + GL_TexGen( GL_R, 0 ); + GL_TexGen( GL_Q, 0 ); +} + +/* +================= +GL_ClipPlane +================= +*/ +void GL_ClipPlane( bool enable ) +{ + // if cliplane was not set - do nothing + if( !FBitSet( RI->params, RP_CLIPPLANE )) + return; + + if( glConfig.hardware_type == GLHW_RADEON ) + return; // ATI doesn't need clip planes + + if( enable ) + { + GLdouble clip[4]; + mplane_t *p = &RI->clipPlane; + + clip[0] = p->normal[0]; + clip[1] = p->normal[1]; + clip[2] = p->normal[2]; + clip[3] = -p->dist; + + pglClipPlane( GL_CLIP_PLANE0, clip ); + pglEnable( GL_CLIP_PLANE0 ); + } + else + { + pglDisable( GL_CLIP_PLANE0 ); + } +} + + +/* +================= +GL_Cull +================= +*/ +void GL_Cull( GLenum cull ) +{ + if( !cull ) + { + pglDisable( GL_CULL_FACE ); + glState.faceCull = 0; + return; + } + + pglEnable( GL_CULL_FACE ); + pglCullFace( cull ); + glState.faceCull = cull; +} + +/* +================= +GL_FrontFace +================= +*/ +void GL_FrontFace( GLenum front ) +{ + pglFrontFace( front ? GL_CW : GL_CCW ); + glState.frontFace = front; +} + +/* +================= +GL_LoadTexMatrix +================= +*/ +void GL_LoadTexMatrix( const matrix4x4 &source ) +{ + GLfloat dest[16]; + + source.CopyToArray( dest ); + GL_LoadTextureMatrix( dest ); +} + +/* +================= +GL_LoadMatrix +================= +*/ +void GL_LoadMatrix( const matrix4x4 &source ) +{ + GLfloat dest[16]; + + source.CopyToArray( dest ); + pglLoadMatrixf( dest ); +} + +/* +================= +GL_Setup2D +================= +*/ +void GL_Setup2D( void ) +{ + // set up full screen workspace + pglMatrixMode( GL_PROJECTION ); + pglLoadIdentity(); + + pglOrtho( 0, glState.width, glState.height, 0, -99999, 99999 ); + + pglMatrixMode( GL_MODELVIEW ); + pglLoadIdentity(); + + pglDisable( GL_DEPTH_TEST ); + GL_AlphaTest( GL_FALSE ); + GL_DepthMask( GL_FALSE ); + GL_Blend( GL_FALSE ); + + GL_Cull( GL_FRONT ); + pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + pglViewport( 0, 0, glState.width, glState.height ); +} + +/* +================= +GL_Setup3D +================= +*/ +void GL_Setup3D( void ) +{ + pglViewport( RI->glstate.viewport[0], RI->glstate.viewport[1], RI->glstate.viewport[2], RI->glstate.viewport[3] ); + + pglMatrixMode( GL_PROJECTION ); + GL_LoadMatrix( RI->glstate.projectionMatrix ); + + pglMatrixMode( GL_MODELVIEW ); + GL_LoadMatrix( RI->glstate.modelviewMatrix ); + + pglEnable( GL_DEPTH_TEST ); + GL_DepthMask( GL_TRUE ); + GL_Cull( GL_FRONT ); +} + +void CompressNormalizedVector( char outVec[3], const Vector &inVec ) +{ + const byte *bestFitTexture = GET_TEXTURE_DATA( tr.normalsFitting ); + Vector vNorm = inVec.Normalize(); // should be normalized + + if( bestFitTexture != NULL ) + { + Vector uNorm = vNorm.Abs(); // get unsigned normal + float maxNAbs = VectorMax( uNorm ); // get the main axis for cubemap lookup + + // get texture dimensions (probably always equal 512) + uint width = RENDER_GET_PARM( PARM_TEX_SRC_WIDTH, tr.normalsFitting ); + uint height = RENDER_GET_PARM( PARM_TEX_SRC_HEIGHT, tr.normalsFitting ); + + // get texture coordinates in a collapsed cubemap + float u = (uNorm.z < maxNAbs ? (uNorm.y < maxNAbs ? uNorm.y : uNorm.x) : uNorm.x); + float v = (uNorm.z < maxNAbs ? (uNorm.y < maxNAbs ? uNorm.z : uNorm.z) : uNorm.y); + vNorm /= maxNAbs; // fit normal into the edge of unit cube + + if( u < v ) swap( v, u ); + if( u != 0.0f ) v /= u; + + uint x = uint( u * (float)width ); + uint y = uint( v * (float)height ); + + // look-up fitting length and scale the normal to get the best fit + float fFittingScale = (float)bestFitTexture[(y * width + x)] * (1.0f / 255.0f); + // scale the normal to get the best fit + vNorm *= fFittingScale; + } + + outVec[0] = FloatToChar( vNorm.x ); + outVec[1] = FloatToChar( vNorm.y ); + outVec[2] = FloatToChar( vNorm.z ); +} \ No newline at end of file diff --git a/cl_dll/render/gl_cubemaps.cpp b/cl_dll/render/gl_cubemaps.cpp new file mode 100644 index 0000000..820af1e --- /dev/null +++ b/cl_dll/render/gl_cubemaps.cpp @@ -0,0 +1,228 @@ +/* +gl_cubemaps.cpp - tools for cubemaps search & handling +Copyright (C) 2016 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "com_model.h" +#include "ref_params.h" +#include "gl_local.h" +#include "gl_decals.h" +#include +#include "gl_world.h" + +/* +================= +CL_FindNearestCubeMap + +find the nearest cubemap for a given point +================= +*/ +void CL_FindNearestCubeMap( const Vector &pos, mcubemap_t **result ) +{ + if( !result ) return; + + float maxDist = 99999.0f; + *result = NULL; + + for( int i = 0; i < world->num_cubemaps; i++ ) + { + mcubemap_t *check = &world->cubemaps[i]; + float dist = VectorDistance( check->origin, pos ); + + if( dist < maxDist ) + { + *result = check; + maxDist = dist; + } + } + + if( !*result ) + { + // this may happens if map + // doesn't have any cubemaps + *result = &world->defaultCubemap; + } +} + +/* +================= +CL_FindNearestCubeMapForSurface + +find the nearest cubemap on front of plane +================= +*/ +void CL_FindNearestCubeMapForSurface( const Vector &pos, const msurface_t *surf, mcubemap_t **result ) +{ + if( !result ) return; + + float maxDist = 99999.0f; + mplane_t plane; + *result = NULL; + + plane = *surf->plane; + + if( FBitSet( surf->flags, SURF_PLANEBACK )) + { + plane.normal = -plane.normal; + plane.dist = -plane.dist; + } + + for( int i = 0; i < world->num_cubemaps; i++ ) + { + mcubemap_t *check = &world->cubemaps[i]; + float dist = VectorDistance( check->origin, pos ); + + if( dist < maxDist && PlaneDiff( check->origin, &plane ) >= 0.0f ) + { + *result = check; + maxDist = dist; + } + } + + if( *result ) return; + + // fallback to default method + CL_FindNearestCubeMap( pos, result ); +} + +/* +================= +CL_FindTwoNearestCubeMap + +find the two nearest cubemaps for a given point +================= +*/ +void CL_FindTwoNearestCubeMap( const Vector &pos, mcubemap_t **result1, mcubemap_t **result2 ) +{ + if( !result1 || !result2 ) + return; + + float maxDist1 = 99999.0f; + float maxDist2 = 99999.0f; + *result1 = *result2 = NULL; + + for( int i = 0; i < world->num_cubemaps; i++ ) + { + mcubemap_t *check = &world->cubemaps[i]; + float dist = VectorDistance( check->origin, pos ); + + if( dist < maxDist1 ) + { + *result1 = check; + maxDist1 = dist; + } + else if( dist < maxDist2 && dist > maxDist1 ) + { + *result2 = check; + maxDist2 = dist; + } + } + + if( !*result1 ) + { + // this may happens if map + // doesn't have any cubemaps + *result1 = &world->defaultCubemap; + } + + if( !*result2 ) + { + // this may happens if map + // doesn't have any cubemaps + *result2 = *result1; + } +} + +/* +================= +CL_FindTwoNearestCubeMapForSurface + +find the two nearest cubemaps on front of plane +================= +*/ +void CL_FindTwoNearestCubeMapForSurface( const Vector &pos, const msurface_t *surf, mcubemap_t **result1, mcubemap_t **result2 ) +{ + if( !result1 || !result2 ) return; + + float maxDist1 = 99999.0f; + float maxDist2 = 99999.0f; + mplane_t plane; + *result1 = NULL; + *result2 = NULL; + + plane = *surf->plane; + + if( FBitSet( surf->flags, SURF_PLANEBACK )) + { + plane.normal = -plane.normal; + plane.dist = -plane.dist; + } + + for( int i = 0; i < world->num_cubemaps; i++ ) + { + mcubemap_t *check = &world->cubemaps[i]; + float dist = VectorDistance( check->origin, pos ); + + if( dist < maxDist1 && PlaneDiff( check->origin, &plane ) >= 0.0f ) + { + *result1 = check; + maxDist1 = dist; + } + else if( dist < maxDist2 && dist > maxDist1 ) + { + *result2 = check; + maxDist2 = dist; + } + } + + if( *result1 ) + { + if( !*result2 ) + *result2 = *result1; + return; + } + + // fallback to default method + CL_FindTwoNearestCubeMap( pos, result1, result2 ); +} + +/* +================= +CL_BuildCubemaps_f + +force to rebuilds all the cubemaps +in the scene +================= +*/ +void CL_BuildCubemaps_f( void ) +{ + mcubemap_t *m = &world->defaultCubemap; + FREE_TEXTURE( m->texture ); + m->valid = m->texture = false; + + for( int i = 0; i < world->num_cubemaps; i++ ) + { + mcubemap_t *m = &world->cubemaps[i]; + FREE_TEXTURE( m->texture ); + m->valid = m->texture = false; + } + + if( FBitSet( world->features, WORLD_HAS_SKYBOX )) + world->build_default_cubemap = true; + + world->rebuilding_cubemaps = CMREBUILD_CHECKING; + world->loading_cubemaps = true; +} \ No newline at end of file diff --git a/cl_dll/render/gl_cull.cpp b/cl_dll/render/gl_cull.cpp new file mode 100644 index 0000000..a05de52 --- /dev/null +++ b/cl_dll/render/gl_cull.cpp @@ -0,0 +1,253 @@ +/* +gl_cull.cpp - render culling routines +Copyright (C) 2011 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include "cl_util.h" +#include "gl_local.h" +#include "entity_types.h" +#include "mathlib.h" +#include "gl_world.h" +#include "gl_grass.h" + +/* +============= +R_CullModel +============= +*/ +bool R_CullModel( cl_entity_t *e, const Vector &absmin, const Vector &absmax ) +{ + if( e == GET_VIEWMODEL( ) || e == gHUD.m_pHeadShieldEnt ) + { + if( RI->params & RP_NONVIEWERREF ) + return true; + return false; + } + + // don't reflect this entity in mirrors + if( FBitSet( e->curstate.effects, EF_NOREFLECT ) && FBitSet( RI->params, RP_MIRRORVIEW )) + return true; + + // draw only in mirrors + if( FBitSet( e->curstate.effects, EF_REFLECTONLY ) && !FBitSet( RI->params, RP_MIRRORVIEW )) + return true; + + // never draw playermodel for himself flashlight while shadowpass is active + if( FBitSet( RI->params, RP_SHADOWVIEW ) && RI->currentlight != NULL ) + { + if( UTIL_IsLocal( e->index ) && RI->currentlight->key == FLASHLIGHT_KEY ) + return true; + } +#if 0 + // don't cull local player because we draw legs instead + if( RP_LOCALCLIENT( e ) && !FBitSet( RI->params, RP_THIRDPERSON ) && UTIL_IsLocal( RI->view.entity )) + { + // player can view himself from the portal camera + if( !FBitSet( RI->params, RP_MIRRORVIEW|RP_SHADOWVIEW )) + return true; + } +#endif + return R_CullBox( absmin, absmax ); +} + +/* +================ +R_CullBrushModel + +Cull brush model by bbox +================ +*/ +bool R_CullBrushModel( cl_entity_t *e ) +{ + gl_state_t *glm = GL_GetCache( e->hCachedMatrix ); + model_t *clmodel = e->model; + Vector absmin, absmax; + + if( !e || !e->model ) + return true; + + // skybox entity + if( e->curstate.renderfx == SKYBOX_ENTITY ) + { + if( FBitSet( RI->params, RP_SHADOWVIEW )) + return true; + + Vector trans = GetVieworg() - tr.sky_origin; + + if( tr.sky_speed ) + { + trans = trans - (GetVieworg() - tr.sky_world_origin) / tr.sky_speed; + Vector skypos = tr.sky_origin + (GetVieworg() - tr.sky_world_origin) / tr.sky_speed; + tr.modelorg = skypos - e->origin; + } + else tr.modelorg = tr.sky_origin - e->origin; + + absmin = e->origin + trans + clmodel->mins; + absmax = e->origin + trans + clmodel->maxs; + } + else + { + if( e->angles != g_vecZero ) + { + absmin = e->origin - clmodel->radius; + absmax = e->origin + clmodel->radius; + } + else + { + absmin = e->origin + clmodel->mins; + absmax = e->origin + clmodel->maxs; + } + tr.modelorg = glm->GetModelOrigin(); + } + + return R_CullModel( e, absmin, absmax ); +} + +static bool R_SunLightShadow( void ) +{ + if( RI->currentlight && RI->currentlight->type == LIGHT_DIRECTIONAL && FBitSet( RI->params, RP_SHADOWVIEW )) + return true; + return false; +} + +/* +================= +R_AllowFacePlaneCulling + +allow to culling backfaces +================= +*/ +static bool R_AllowFacePlaneCulling( msurface_t *surf ) +{ + if( !glState.faceCull ) + return false; + + if( FBitSet( surf->flags, SURF_TWOSIDE )) + return false; + + if( !FBitSet( RI->params, RP_SHADOWVIEW ) && RI->currentlight && RI->currentlight->type == LIGHT_DIRECTIONAL ) + return false; + + // don't cull transparent surfaces because we should be draw decals on them + if( FBitSet( surf->flags, SURF_HAS_DECALS ) && !R_OpaqueEntity( RI->currententity )) + return false; + + return true; +} + +/* +================= +R_CullSurface + +cull invisible surfaces +================= +*/ +int R_CullSurface( msurface_t *surf, const Vector &vieworg, CFrustum *frustum, int clipFlags ) +{ + cl_entity_t *e = RI->currententity; + + if( !surf || !surf->texinfo || !surf->texinfo->texture ) + return CULL_OTHER; + + if( FBitSet( RI->params, RP_WATERPASS ) && R_WaterEntity( e->model )) + return CULL_OTHER; // don't render water from waterplane reflection + + if( CVAR_TO_BOOL( r_nocull )) + return CULL_VISIBLE; + + if( R_SunLightShadow( )) + return CULL_VISIBLE; + + // because light or shadow passes required both sides for right self-shadowing + if( R_AllowFacePlaneCulling( surf )) + { + float dist; + + // can use normal.z for world (optimisation) + if( FBitSet( RI->params, RP_DRAW_OVERVIEW )) + { + Vector orthonormal; + + if( e == GET_ENTITY( 0 ) || R_StaticEntity( e )) + { + orthonormal.z = surf->plane->normal.z; + } + else + { + matrix4x4 m = matrix4x4( g_vecZero, e->angles ); + orthonormal = m.VectorRotate( surf->plane->normal ); + } + + dist = orthonormal.z; + } + else dist = PlaneDiff( vieworg, surf->plane ); + + if( glState.faceCull == GL_FRONT || ( RI->params & RP_MIRRORVIEW )) + { + if( surf->flags & SURF_PLANEBACK ) + { + if( dist >= -BACKFACE_EPSILON ) + return CULL_BACKSIDE; // wrong side + } + else + { + if( dist <= BACKFACE_EPSILON ) + return CULL_BACKSIDE; // wrong side + } + } + else if( glState.faceCull == GL_BACK ) + { + if( surf->flags & SURF_PLANEBACK ) + { + if( dist <= BACKFACE_EPSILON ) + return CULL_BACKSIDE; // wrong side + } + else + { + if( dist >= -BACKFACE_EPSILON ) + return CULL_BACKSIDE; // wrong side + } + } + } + + if( frustum && frustum->CullBox( surf->info->mins, surf->info->maxs, clipFlags )) + return CULL_FRUSTUM; + return CULL_VISIBLE; +} + +/* +================ +R_CullNodeTopView + +cull node by user rectangle (simple scissor) +================ +*/ +bool R_CullNodeTopView( mnode_t *node ) +{ + Vector2D delta, size; + Vector center, half, mins, maxs; + + // build the node center and half-diagonal + mins = node->minmaxs, maxs = node->minmaxs + 3; + center = (mins + maxs) * 0.5f; + half = maxs - center; + + // cull against the screen frustum or the appropriate area's frustum. + delta.x = center.x - world->orthocenter.x; + delta.y = center.y - world->orthocenter.y; + size.x = half.x + world->orthohalf.x; + size.y = half.y + world->orthohalf.y; + + return ( fabs( delta.x ) > size.x ) || ( fabs( delta.y ) > size.y ); +} diff --git a/cl_dll/render/gl_debug.cpp b/cl_dll/render/gl_debug.cpp new file mode 100644 index 0000000..1e3c9ad --- /dev/null +++ b/cl_dll/render/gl_debug.cpp @@ -0,0 +1,576 @@ +// +// written by BUzer for HL: Paranoia modification +// +// 2006 + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "com_model.h" +#include "r_studioint.h" +#include "ref_params.h" +#include "r_efx.h" +#include "event_api.h" +#include "pm_defs.h" +#include "pmtrace.h" +#include "triangleapi.h" +#include "gl_local.h" +#include "stringlib.h" +#include "gl_world.h" +#include "gl_shader.h" +#include "vertex_fmt.h" + +void GL_GpuMemUsage_f( void ) +{ + GLint cur_avail_mem_kb = 0; + GLint total_mem_kb = 0; + + if( glConfig.hardware_type == GLHW_NVIDIA ) + { + pglGetIntegerv( GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX, &total_mem_kb ); + pglGetIntegerv( GL_GPU_MEM_INFO_CURRENT_AVAILABLE_MEM_NVX, &cur_avail_mem_kb ); + } + + Msg( "TEX used memory %s\n", Q_memprint( RENDER_GET_PARM( PARM_TEX_MEMORY, 0 ))); + Msg( "VBO used memory %s\n", Q_memprint( tr.total_vbo_memory )); + Msg( "GPU used memory %s\n", Q_memprint(( total_mem_kb - cur_avail_mem_kb ) * 1024 )); +} + +void DBG_PrintVertexVBOSizes( void ) +{ + if( developer_level < at_aiconsole ) + return; + + ALERT( at_console, "sizeof( bvert_v0_gl21_t ) == %d bytes\n", sizeof( bvert_v0_gl21_t )); + ALERT( at_console, "sizeof( bvert_v0_gl30_t ) == %d bytes\n", sizeof( bvert_v0_gl30_t )); + ALERT( at_console, "sizeof( gvert_v0_gl21_t ) == %d bytes\n", sizeof( gvert_v0_gl21_t )); + ALERT( at_console, "sizeof( gvert_v0_gl30_t ) == %d bytes\n", sizeof( gvert_v0_gl30_t )); + ALERT( at_console, "sizeof( gvert_v1_gl21_t ) == %d bytes\n", sizeof( gvert_v1_gl21_t )); + ALERT( at_console, "sizeof( gvert_v1_gl30_t ) == %d bytes\n", sizeof( gvert_v1_gl30_t )); + ALERT( at_console, "sizeof( svert_v0_gl21_t ) == %d bytes\n", sizeof( svert_v0_gl21_t )); + ALERT( at_console, "sizeof( svert_v0_gl30_t ) == %d bytes\n", sizeof( svert_v0_gl30_t )); + ALERT( at_console, "sizeof( svert_v1_gl21_t ) == %d bytes\n", sizeof( svert_v1_gl21_t )); + ALERT( at_console, "sizeof( svert_v1_gl30_t ) == %d bytes\n", sizeof( svert_v1_gl30_t )); + ALERT( at_console, "sizeof( svert_v2_gl21_t ) == %d bytes\n", sizeof( svert_v2_gl21_t )); + ALERT( at_console, "sizeof( svert_v2_gl30_t ) == %d bytes\n", sizeof( svert_v2_gl30_t )); + ALERT( at_console, "sizeof( svert_v3_gl21_t ) == %d bytes\n", sizeof( svert_v3_gl21_t )); + ALERT( at_console, "sizeof( svert_v3_gl30_t ) == %d bytes\n", sizeof( svert_v3_gl30_t )); + ALERT( at_console, "sizeof( svert_v4_gl21_t ) == %d bytes\n", sizeof( svert_v4_gl21_t )); + ALERT( at_console, "sizeof( svert_v4_gl30_t ) == %d bytes\n", sizeof( svert_v4_gl30_t )); + ALERT( at_console, "sizeof( svert_v5_gl21_t ) == %d bytes\n", sizeof( svert_v5_gl21_t )); + ALERT( at_console, "sizeof( svert_v5_gl30_t ) == %d bytes\n", sizeof( svert_v5_gl30_t )); + ALERT( at_console, "sizeof( svert_v6_gl21_t ) == %d bytes\n", sizeof( svert_v6_gl21_t )); + ALERT( at_console, "sizeof( svert_v6_gl30_t ) == %d bytes\n", sizeof( svert_v6_gl30_t )); + ALERT( at_console, "sizeof( svert_v7_gl21_t ) == %d bytes\n", sizeof( svert_v7_gl21_t )); + ALERT( at_console, "sizeof( svert_v7_gl30_t ) == %d bytes\n", sizeof( svert_v7_gl30_t )); + ALERT( at_console, "sizeof( svert_v8_gl21_t ) == %d bytes\n", sizeof( svert_v8_gl21_t )); + ALERT( at_console, "sizeof( svert_v8_gl30_t ) == %d bytes\n", sizeof( svert_v8_gl30_t )); +} + +// some simple helpers to draw a cube in the special way the ambient visualization wants +static float *CubeSide( const vec3_t pos, float size, int vert ) +{ + static vec3_t side; + + VectorCopy( pos, side ); + side[0] += (vert & 1) ? -size : size; + side[1] += (vert & 2) ? -size : size; + side[2] += (vert & 4) ? -size : size; + + return side; +} + +static void CubeFace( const vec3_t org, int v0, int v1, int v2, int v3, float size, const byte *color ) +{ + uint scale = tr.lightstyle[0]; + uint unclamped[3]; + int col[3]; + + unclamped[0] = TEXTURE_TO_TEXGAMMA( color[0] ) * scale; + unclamped[1] = TEXTURE_TO_TEXGAMMA( color[1] ) * scale; + unclamped[2] = TEXTURE_TO_TEXGAMMA( color[2] ) * scale; + + col[0] = min((unclamped[0] >> 7), 255 ); + col[1] = min((unclamped[1] >> 7), 255 ); + col[2] = min((unclamped[2] >> 7), 255 ); + +// pglColor3ub( col[0], col[1], col[2] ); + pglColor3ub( color[0], color[1], color[2] ); + pglVertex3fv( CubeSide( org, size, v0 )); + pglVertex3fv( CubeSide( org, size, v1 )); + pglVertex3fv( CubeSide( org, size, v2 )); + pglVertex3fv( CubeSide( org, size, v3 )); +} + +void R_RenderLightProbe( mlightprobe_t *probe ) +{ + float rad = 4.0f; + + pglBegin( GL_QUADS ); + + CubeFace( probe->origin, 4, 6, 2, 0, rad, probe->cube.color[0] ); + CubeFace( probe->origin, 7, 5, 1, 3, rad, probe->cube.color[1] ); + CubeFace( probe->origin, 0, 1, 5, 4, rad, probe->cube.color[2] ); + CubeFace( probe->origin, 3, 2, 6, 7, rad, probe->cube.color[3] ); + CubeFace( probe->origin, 2, 3, 1, 0, rad, probe->cube.color[4] ); + CubeFace( probe->origin, 4, 5, 7, 6, rad, probe->cube.color[5] ); + + pglEnd (); +} + +void R_RenderCubemap( mcubemap_t *cube ) +{ + float rad = (float)cube->size * 0.1f; + byte color[3] = { 127, 127, 127 }; + + pglBegin( GL_QUADS ); + + CubeFace( cube->origin, 4, 6, 2, 0, rad, color ); + CubeFace( cube->origin, 7, 5, 1, 3, rad, color ); + CubeFace( cube->origin, 0, 1, 5, 4, rad, color ); + CubeFace( cube->origin, 3, 2, 6, 7, rad, color ); + CubeFace( cube->origin, 2, 3, 1, 0, rad, color ); + CubeFace( cube->origin, 4, 5, 7, 6, rad, color ); + + pglEnd (); +} + +void R_RenderLightProbeInternal( const Vector &origin, const Vector lightCube[] ) +{ + mlightprobe_t probe; + + for( int i = 0; i < 6; i++ ) + { + probe.cube.color[i][0] = lightCube[i].x * 255; + probe.cube.color[i][1] = lightCube[i].y * 255; + probe.cube.color[i][2] = lightCube[i].z * 255; + } + probe.origin = origin; + + R_RenderLightProbe( &probe ); +} + +/* +=============== +R_DrawAABB + +=============== +*/ +static void R_DrawAABB( const Vector &absmin, const Vector &absmax, int contents ) +{ + vec3_t bbox[8]; + int i; + + // compute a full bounding box + for( i = 0; i < 8; i++ ) + { + bbox[i][0] = ( i & 1 ) ? absmin[0] : absmax[0]; + bbox[i][1] = ( i & 2 ) ? absmin[1] : absmax[1]; + bbox[i][2] = ( i & 4 ) ? absmin[2] : absmax[2]; + } + + GL_BindTexture( GL_TEXTURE0, tr.whiteTexture ); + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + pglDisable( GL_DEPTH_TEST ); + + switch( contents ) + { + case CONTENTS_EMPTY: + pglColor4f( 0.5f, 1.0f, 0.0f, 1.0f ); // green for empty + break; + case CONTENTS_SOLID: + pglColor4f( 1.0f, 0.0f, 0.0f, 1.0f ); // red for solid + break; + case CONTENTS_WATER: + pglColor4f( 0.0f, 0.5f, 1.0f, 1.0f ); // blue for water + break; + default: + pglColor4f( 0.5f, 0.5f, 0.5f, 1.0f ); // gray as default + break; + } + pglBegin( GL_LINES ); + + for( i = 0; i < 2; i += 1 ) + { + pglVertex3fv( bbox[i+0] ); + pglVertex3fv( bbox[i+2] ); + pglVertex3fv( bbox[i+4] ); + pglVertex3fv( bbox[i+6] ); + pglVertex3fv( bbox[i+0] ); + pglVertex3fv( bbox[i+4] ); + pglVertex3fv( bbox[i+2] ); + pglVertex3fv( bbox[i+6] ); + pglVertex3fv( bbox[i*2+0] ); + pglVertex3fv( bbox[i*2+1] ); + pglVertex3fv( bbox[i*2+4] ); + pglVertex3fv( bbox[i*2+5] ); + } + + pglEnd(); + pglEnable( GL_DEPTH_TEST ); +} + +/* +=============== +R_RenderVisibleLeafs + +=============== +*/ +static void R_RenderVisibleLeafs( void ) +{ + mleaf_t *leaf; + int i; + + // always skip the leaf 0, because is outside leaf + for( i = 1, leaf = &worldmodel->leafs[1]; i < worldmodel->numleafs + 1; i++, leaf++ ) + { + mextraleaf_t *eleaf = LEAF_INFO( leaf, worldmodel ); + + if( CHECKVISBIT( RI->view.pvsarray, leaf->cluster ) && ( leaf->efrags || leaf->nummarksurfaces )) + { + R_DrawAABB( eleaf->mins, eleaf->maxs, CONTENTS_WATER ); + R_DrawAABB( Vector( leaf->minmaxs ), Vector( leaf->minmaxs + 3 ), leaf->contents ); + } + } +} + +/* +============= +DrawViewLeaf +============= +*/ +void DrawViewLeaf( void ) +{ + if( !CVAR_TO_BOOL( r_show_viewleaf )) + return; + + GL_Blend( GL_FALSE ); + R_RenderVisibleLeafs(); +} + +/* +============= +DrawLightProbes +============= +*/ +void DrawLightProbes( void ) +{ + int i, j; + mlightprobe_t *probe; + mleaf_t *leaf; + mextraleaf_t *info; + + if( !CVAR_TO_BOOL( r_show_lightprobes )) + return; + + GL_Blend( GL_FALSE ); + GL_BindTexture( GL_TEXTURE0, tr.whiteTexture ); + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + + // draw the all visible probes + for( i = 1; i < world->numleafs; i++ ) + { + leaf = (mleaf_t *)&worldmodel->leafs[i]; + info = LEAF_INFO( leaf, worldmodel ); + + if( !CHECKVISBIT( RI->view.pvsarray, leaf->cluster )) + continue; // not visible from player + + for( j = 0; j < info->num_lightprobes; j++ ) + { + probe = info->ambient_light + j; + R_RenderLightProbe( probe ); + } + } + + pglColor3f( 1.0f, 1.0f, 1.0f ); +} + +/* +============= +DrawCubeMaps +============= +*/ +void DrawCubeMaps( void ) +{ + mcubemap_t *cm; + + if( !CVAR_TO_BOOL( r_show_cubemaps )) + return; + + GL_Blend( GL_FALSE ); + GL_BindTexture( GL_TEXTURE0, tr.whiteTexture ); + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + + // draw the all cubemaps + for( int i = 0; i < world->num_cubemaps; i++ ) + { + cm = &world->cubemaps[i]; + R_RenderCubemap( cm ); + } + + pglColor3f( 1.0f, 1.0f, 1.0f ); +} + +void DBG_DrawBBox( const Vector &mins, const Vector &maxs ) +{ + Vector bbox[8]; + int i; + + for( i = 0; i < 8; i++ ) + { + bbox[i][0] = ( i & 1 ) ? mins[0] : maxs[0]; + bbox[i][1] = ( i & 2 ) ? mins[1] : maxs[1]; + bbox[i][2] = ( i & 4 ) ? mins[2] : maxs[2]; + } + + pglColor4f( 1.0f, 0.0f, 1.0f, 1.0f ); // yellow bboxes for frustum + GL_BindTexture( GL_TEXTURE0, tr.whiteTexture ); + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + pglBegin( GL_LINES ); + + for( i = 0; i < 2; i += 1 ) + { + pglVertex3fv( bbox[i+0] ); + pglVertex3fv( bbox[i+2] ); + pglVertex3fv( bbox[i+4] ); + pglVertex3fv( bbox[i+6] ); + pglVertex3fv( bbox[i+0] ); + pglVertex3fv( bbox[i+4] ); + pglVertex3fv( bbox[i+2] ); + pglVertex3fv( bbox[i+6] ); + pglVertex3fv( bbox[i*2+0] ); + pglVertex3fv( bbox[i*2+1] ); + pglVertex3fv( bbox[i*2+4] ); + pglVertex3fv( bbox[i*2+5] ); + } + pglEnd(); +} + +void DBG_DrawLightFrustum( void ) +{ + if( CVAR_TO_BOOL( r_scissor_light_debug ) && RP_NORMALPASS( )) + { + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + + for( int i = 0; i < MAX_DLIGHTS; i++ ) + { + CDynLight *pl = &tr.dlights[i]; + + if( !pl->Active( )) continue; + + if( r_scissor_light_debug->value == 1.0f ) + { + R_DrawScissorRectangle( pl->x, pl->y, pl->w, pl->h ); + } + else if( r_scissor_light_debug->value == 2.0f ) + { + if( pl->type == LIGHT_DIRECTIONAL ) + { + for( int j = 0; j < NUM_SHADOW_SPLITS + 1; j++ ) + pl->splitFrustum[j].DrawFrustumDebug(); + } + else pl->frustum.DrawFrustumDebug(); + } + else + { + DBG_DrawBBox( pl->absmin, pl->absmax ); + } + } + } +} + +void DBG_DrawGlassScissors( void ) +{ + if( CVAR_TO_BOOL( r_scissor_glass_debug ) && RP_NORMALPASS( )) + { + // debug scissor code + GL_BindShader( NULL ); + + // debug scissor code + for( int k = 0; k < RI->frame.trans_list.Count(); k++ ) + { + CTransEntry *entry = &RI->frame.trans_list[k]; + entry->RenderScissorDebug(); + } + } +} + +/* +================ +DrawTangentSpaces + +Draws vertex tangent spaces for debugging +================ +*/ +void DrawTangentSpaces( void ) +{ + float temp[3]; + float vecLen = 4.0f; + + if( !CVAR_TO_BOOL( cv_show_tbn )) + return; + + GL_BindTexture( GL_TEXTURE0, tr.whiteTexture ); + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + pglDisable( GL_DEPTH_TEST ); + GL_Blend( GL_FALSE ); + pglBegin( GL_LINES ); + + for( int i = 0; i < worldmodel->nummodelsurfaces; i++ ) + { + msurface_t *surf = &worldmodel->surfaces[i]; + mextrasurf_t *esrf = surf->info; + + if( FBitSet( surf->flags, SURF_DRAWTURB|SURF_DRAWSKY )) + continue; + + bvert_t *mv = &world->vertexes[esrf->firstvertex]; + + for( int j = 0; j < esrf->numverts; j++, mv++ ) + { + pglColor3f( 1.0f, 0.0f, 0.0f ); + pglVertex3fv( mv->vertex ); + VectorMA( mv->vertex, vecLen, Vector( mv->tangent ), temp ); + pglVertex3fv( temp ); + + pglColor3f( 0.0f, 1.0f, 0.0f ); + pglVertex3fv( mv->vertex ); + VectorMA( mv->vertex, vecLen, Vector( mv->binormal ), temp ); + pglVertex3fv( temp ); + + pglColor3f( 0.0f, 0.0f, 1.0f ); + pglVertex3fv( mv->vertex ); + VectorMA( mv->vertex, vecLen, Vector( mv->normal ), temp ); + pglVertex3fv( temp ); + } + } + + pglEnd(); + pglEnable( GL_DEPTH_TEST ); +} + +void DrawWireFrame( void ) +{ + int i; + + if( !CVAR_TO_BOOL( r_wireframe )) + return; + + pglEnable( GL_BLEND ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + pglPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + pglColor4f( 1.0f, 1.0f, 1.0f, 0.99f ); + + pglDisable( GL_DEPTH_TEST ); + pglEnable( GL_LINE_SMOOTH ); + GL_BindTexture( GL_TEXTURE0, tr.whiteTexture ); + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + pglEnable( GL_POLYGON_SMOOTH ); + pglHint( GL_LINE_SMOOTH_HINT, GL_NICEST ); + pglHint( GL_POLYGON_SMOOTH_HINT, GL_NICEST ); + + for( i = 0; i < RI->frame.solid_faces.Count(); i++ ) + { + if( RI->frame.solid_faces[i].m_bDrawType != DRAWTYPE_SURFACE ) + continue; + + msurface_t *surf = RI->frame.solid_faces[i].m_pSurf; + mextrasurf_t *es = surf->info; + + pglBegin( GL_POLYGON ); + for( int j = 0; j < es->numverts; j++ ) + { + bvert_t *v = &world->vertexes[es->firstvertex + j]; + pglVertex3fv( v->vertex + Vector( v->normal ) * 0.1f ); + } + pglEnd(); + } + + pglColor4f( 0.0f, 1.0f, 0.0f, 0.99f ); + for( i = 0; i < RI->frame.trans_list.Count(); i++ ) + { + if( RI->frame.trans_list[i].m_bDrawType != DRAWTYPE_SURFACE ) + continue; + + msurface_t *surf = RI->frame.trans_list[i].m_pSurf; + mextrasurf_t *es = surf->info; + + pglBegin( GL_POLYGON ); + for( int j = 0; j < es->numverts; j++ ) + { + bvert_t *v = &world->vertexes[es->firstvertex + j]; + pglVertex3fv( v->vertex + Vector( v->normal ) * 0.1f ); + } + pglEnd(); + } + + pglPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + pglDisable( GL_POLYGON_SMOOTH ); + pglDisable( GL_LINE_SMOOTH ); + pglEnable( GL_DEPTH_TEST ); + pglDisable( GL_BLEND ); +} + +void DrawWirePoly( msurface_t *surf ) +{ + if( !surf ) return; + + pglEnable( GL_BLEND ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + pglPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + pglColor4f( 0.5f, 1.0f, 0.36f, 0.99f ); + pglLineWidth( 4.0f ); + + pglDisable( GL_DEPTH_TEST ); + pglEnable( GL_LINE_SMOOTH ); + pglEnable( GL_POLYGON_SMOOTH ); + pglHint( GL_LINE_SMOOTH_HINT, GL_NICEST ); + pglHint( GL_POLYGON_SMOOTH_HINT, GL_NICEST ); + + mextrasurf_t *es = surf->info; + + pglBegin( GL_POLYGON ); + for( int j = 0; j < es->numverts; j++ ) + { + bvert_t *v = &world->vertexes[es->firstvertex + j]; + pglVertex3fv( v->vertex + Vector( v->normal ) * 0.1f ); + } + pglEnd(); + + pglPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + pglDisable( GL_POLYGON_SMOOTH ); + pglDisable( GL_LINE_SMOOTH ); + pglEnable( GL_DEPTH_TEST ); + pglDisable( GL_BLEND ); + pglLineWidth( 1.0f ); +} + +void R_ShowLightMaps( void ) +{ + int index = 0; + + if( !CVAR_TO_BOOL( r_showlightmaps )) + return; + + index = r_showlightmaps->value - 1.0f; + if( tr.lightmaps[index].state == LM_FREE ) + return; + + GL_BindTexture( GL_TEXTURE0, tr.lightmaps[index].lightmap ); + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + GL_Setup2D(); + + pglBegin( GL_QUADS ); + pglTexCoord2f( 0.0f, 0.0f ); + pglVertex2f( 0.0f, 0.0f ); + pglTexCoord2f( 1.0f, 0.0f ); + pglVertex2f( glState.width, 0.0f ); + pglTexCoord2f( 1.0f, 1.0f ); + pglVertex2f( glState.width, glState.height ); + pglTexCoord2f( 0.0f, 1.0f ); + pglVertex2f( 0.0f, glState.height ); + pglEnd(); + + GL_Setup3D(); +} \ No newline at end of file diff --git a/cl_dll/render/gl_decals.cpp b/cl_dll/render/gl_decals.cpp new file mode 100644 index 0000000..b9e2c4f --- /dev/null +++ b/cl_dll/render/gl_decals.cpp @@ -0,0 +1,1973 @@ +// +// written by BUzer for HL: Paranoia modification +// +// 2006 + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "cdll_int.h" +#include "com_model.h" +#include "entity_types.h" +#include "r_efx.h" +#include "event_api.h" +#include "pm_defs.h" +#include "pmtrace.h" +#include +#include "gl_local.h" +#include "gl_decals.h" +#include +#include "gl_shader.h" +#include "gl_world.h" +#include "gl_studio.h" +#include "gl_occlusion.h" + +#define MAX_CLIPVERTS 64 // don't change this +#define MAX_GROUPENTRIES 512 +#define MAX_BRUSH_DECALS 4096 +#define MAX_DECAL_VERTICES 32 // per one fragment, enough in most cases +#define MAX_DECAL_INDICES (MAX_DECAL_VERTICES * 3) +#define DECAL_VERTICES_HASH_SIZE (MAX_DECAL_VERTICES >> 2) + +#define MAX_DECAL_VERTS (MAX_DECAL_VERTICES * MAX_BRUSH_DECALS) +#define MAX_DECAL_ELEMS (MAX_DECAL_INDICES * MAX_BRUSH_DECALS) + +typedef CUtlArray CIntVector; + +typedef struct decalVertex_s +{ + Vector point; + int index; + decalVertex_s* nextHash; +} decalVertex_t; + +// used for build new decals +typedef struct +{ + // decal baseinfo + short entityIndex; + const DecalGroupEntry *decalDesc; + model_t *model; + byte flags; + float angle; + brushdecal_t *current; + Vector origin; + Vector axis[3]; // up, right, normal + + // decal clipinfo + Vector mins, maxs; + + mplane_t planes[6]; + mplane_t splitPlanes[2]; + + Vector textureVecs[2]; + + word indices[MAX_DECAL_INDICES]; + word numIndices; + + // clipped vertices + decalVertex_t vertices[MAX_DECAL_VERTICES]; + decalVertex_t *verticesHashTable[DECAL_VERTICES_HASH_SIZE]; + byte numVertices; +} decalClip_t; + +void DecalGroupEntry :: PreloadTextures( void ) +{ + char path[256]; + char name[64]; + + if( m_init ) return; + + if( m_DecalName[0] == '#' ) + Q_strncpy( name, m_DecalName + 1, sizeof( name )); + else Q_strncpy( name, m_DecalName, sizeof( name )); + Q_snprintf( path, sizeof( path ), "gfx/decals/%s", name ); + int id = LOAD_TEXTURE( path, NULL, 0, TF_CLAMP ); + if( !id ) + { + // decal was completely missed + m_init = true; + return; + } + + gl_diffuse_id = id; + + if( FBitSet( RENDER_GET_PARM( PARM_TEX_FLAGS, gl_diffuse_id ), TF_HAS_ALPHA )) + opaque = true; + + Q_snprintf( path, sizeof( path ), "gfx/decals/%s_norm", name ); + if( IMAGE_EXISTS( path )) + gl_normalmap_id = LOAD_TEXTURE( path, NULL, 0, TF_NORMALMAP ); + else gl_normalmap_id = tr.normalmapTexture; + + Q_snprintf( path, sizeof( path ), "gfx/decals/%s_gloss", name ); + if( IMAGE_EXISTS( path )) + gl_specular_id = LOAD_TEXTURE( path, NULL, 0, TF_CLAMP ); + else gl_specular_id = tr.blackTexture; + + Q_snprintf( path, sizeof( path ), "gfx/decals/%s_height", name ); + if( IMAGE_EXISTS( path )) + gl_heightmap_id = LOAD_TEXTURE( path, NULL, 0, TF_CLAMP ); + else gl_heightmap_id = tr.whiteTexture; + + m_init = true; +} + +DecalGroup *pDecalGroupList = NULL; + +DecalGroup :: DecalGroup( const char *name, int numelems, DecalGroupEntry *source ) +{ + Q_strncpy( m_chGroupName, name, sizeof( m_chGroupName )); + + pEntryArray = new DecalGroupEntry[numelems]; + memcpy( pEntryArray, source, sizeof( DecalGroupEntry ) * numelems ); + size = numelems; + + // setup upcast + for( int i = 0; i < size; i++ ) + pEntryArray[i].group = this; + + pnext = pDecalGroupList; + pDecalGroupList = this; +} + +DecalGroup :: ~DecalGroup( void ) +{ + for( int i = 0; i < size; i++ ) + { + if( pEntryArray[i].gl_diffuse_id != 0 ) + FREE_TEXTURE( pEntryArray[i].gl_diffuse_id ); + if( pEntryArray[i].gl_normalmap_id != tr.normalmapTexture ) + FREE_TEXTURE( pEntryArray[i].gl_normalmap_id ); + if( pEntryArray[i].gl_specular_id != tr.blackTexture ) + FREE_TEXTURE( pEntryArray[i].gl_specular_id ); + if( pEntryArray[i].gl_heightmap_id != tr.whiteTexture ) + FREE_TEXTURE( pEntryArray[i].gl_heightmap_id ); + } + + delete[] pEntryArray; +} + +DecalGroupEntry *DecalGroup :: GetEntry( int num ) +{ + if( num < 0 || num >= size ) + return NULL; + return &pEntryArray[num]; +} + +DecalGroupEntry *DecalGroup :: GetEntry( const char *name, int flags ) +{ + if( FBitSet( flags, FDECAL_NORANDOM ) && Q_strchr( name, '@' )) + { + // NOTE: restored decal contain name same as 'group@name' + // so we need separate them before search group + char *sep = Q_strchr( name, '@' ); + if( sep != NULL ) *sep = '\0'; + char *decalname = sep + 1; + + DecalGroup *groupDesc = FindGroup( name ); + if( !groupDesc ) + { + ALERT( at_warning, "RestoreDecal: group %s is not exist\n", name ); + return NULL; + } + + return groupDesc->FindEntry( decalname ); + } + else + { + DecalGroup *groupDesc = DecalGroup::FindGroup( name ); + if( !groupDesc ) + { + ALERT( at_warning, "CreateDecal: group %s is not exist\n", name ); + return NULL; + } + + return groupDesc->GetRandomDecal(); + } +} + +DecalGroupEntry *DecalGroup :: FindEntry( const char *name ) +{ + for( int i = 0; i < size; i++ ) + { + if( !Q_strcmp( pEntryArray[i].m_DecalName, name )) + return &pEntryArray[i]; + } + + return NULL; +} + +DecalGroupEntry *DecalGroup :: GetRandomDecal( void ) +{ + return &pEntryArray[RANDOM_LONG( 0, size - 1 )]; +} + +DecalGroup *DecalGroup :: FindGroup( const char *name ) +{ + DecalGroup *plist = pDecalGroupList; + + while( plist ) + { + if( !Q_strcmp( plist->m_chGroupName, name )) + return plist; + plist = plist->pnext; + } + + return NULL; // nothing found +} + +static dvert_t g_decalVertexCache[MAX_DECAL_VERTS]; // 4.00 mbytes here if max decals count is 4096 +static word g_decalIndexCache[MAX_DECAL_ELEMS]; // 1.5 mbytes here if max decals count is 4096 +static unsigned int g_decalVertUsed; +static unsigned int g_decalElemUsed; + +static brushdecal_t gDecalPool[MAX_BRUSH_DECALS]; +static int gDecalCycle; +static int gDecalCount; + +// =========================== +// Decals creation +// =========================== +// unlink pdecal from any surface it's attached to +static void R_UnlinkDecal( brushdecal_t *pdecal ) +{ + brushdecal_t *tmp; + + if( pdecal->surface ) + { + mextrasurf_t *es = pdecal->surface; + + ASSERT( es != NULL ); + + if( es->pdecals == pdecal ) + { + es->pdecals = pdecal->pnext; + } + else + { + tmp = es->pdecals; + if( !tmp ) HOST_ERROR( "R_UnlinkDecal: bad decal list\n" ); + + while( tmp->pnext ) + { + if( tmp->pnext == pdecal ) + { + tmp->pnext = pdecal->pnext; + break; + } + tmp = tmp->pnext; + } + } + + if( FBitSet( es->surf->flags, SURF_REFLECT_PUDDLE )) + { + int puddleCount = 0; + + tmp = es->pdecals; + while( tmp ) + { + if( FBitSet( tmp->flags, FDECAL_PUDDLE )) + puddleCount++; + tmp = tmp->pnext; + } + + if( !puddleCount ) + { + ClearBits( es->surf->flags, SURF_REFLECT_PUDDLE ); + GL_DeleteOcclusionQuery( es->surf ); + } + } + + if( !es->pdecals ) + ClearBits( es->surf->flags, SURF_HAS_DECALS ); + } + + // decals are not used dynamic memory, just clear it + pdecal->surface = NULL; +} + +static brushdecal_t *R_AllocDecal( brushdecal_t *pdecal = NULL ) +{ + int limit = MAX_BRUSH_DECALS; + + if( r_decals->value < limit ) + limit = r_decals->value; + + if( !limit ) return NULL; + + if( !pdecal ) + { + int count = 0; + + // check for the odd possiblity of infinte loop + do + { + if( gDecalCycle >= limit ) + gDecalCycle = 0; + + pdecal = &gDecalPool[gDecalCycle]; // reuse next decal + gDecalCycle++; + count++; + } while( FBitSet( pdecal->flags, FDECAL_PERMANENT ) && count < limit ); + + // decal allocated + if( gDecalCount < limit ) + gDecalCount++; + } + + // if decal is already linked to a surface, unlink it. + R_UnlinkDecal( pdecal ); + + return pdecal; +} + +/* +=============== +R_ShaderDecalForward + +Select the program for surface (diffuse\puddle) +=============== +*/ +static word R_ShaderDecalForward( brushdecal_t *decal ) +{ + char glname[64]; + char options[MAX_OPTIONS_LENGTH]; + const DecalGroupEntry *texinfo = decal->texinfo; + mextrasurf_t *es= decal->surface; + bool mirrorSurface = false; + bool have_parallax = false; + bool have_bumpmap = false; + msurface_t *s = es->surf; + + ASSERT( worldmodel != NULL ); + + if( decal->forwardScene.IsValid( )) + return decal->forwardScene.GetHandle(); // valid + + Q_strncpy( glname, "forward/decal_bmodel", sizeof( glname )); + memset( options, 0, sizeof( options )); + + if( FBitSet( decal->flags, FDECAL_PUDDLE )) + GL_AddShaderDirective( options, "DECAL_PUDDLE" ); + + if( texinfo->gl_heightmap_id != tr.whiteTexture && texinfo->matdesc->reliefScale > 0.0f ) + { + if( cv_parallax->value == 1.0f ) + GL_AddShaderDirective( options, "PARALLAX_SIMPLE" ); + else if( cv_parallax->value >= 2.0f ) + GL_AddShaderDirective( options, "PARALLAX_OCCLUSION" ); + have_parallax = true; + } + + material_t *mat = R_TextureAnimation( s )->material; + + // process lightstyles + for( int i = 0; i < MAXLIGHTMAPS && s->styles[i] != LS_NONE; i++ ) + { + if( tr.sun_light_enabled && s->styles[i] == LS_SKY ) + continue; // skip the sunlight due realtime sun is enabled + GL_AddShaderDirective( options, va( "APPLY_STYLE%i", i )); + } + + if( CVAR_TO_BOOL( cv_brdf )) + GL_AddShaderDirective( options, "APPLY_PBS" ); + + // NOTE: deluxemap and normalmap are separate because some modes may using + // normalmap directly e.g. for mirror distorsion + if( es->normals ) GL_AddShaderDirective( options, "HAS_DELUXEMAP" ); + + if( CVAR_TO_BOOL( cv_specular ) && ( texinfo->gl_specular_id != tr.blackTexture )) + GL_AddShaderDirective( options, "HAS_GLOSSMAP" ); + + if( !texinfo->opaque ) + { + // GL_DST_COLOR, GL_SRC_COLOR + GL_AddShaderDirective( options, "APPLY_COLORBLEND" ); + } + + if(( texinfo->gl_normalmap_id != tr.normalmapTexture ) && CVAR_TO_BOOL( cv_bump )) + { + GL_AddShaderDirective( options, "HAS_NORMALMAP" ); + GL_EncodeNormal( options, texinfo->gl_normalmap_id ); + + if( FBitSet( decal->flags, FDECAL_PUDDLE )) + GL_AddShaderDirective( options, "APPLY_REFRACTION" ); + have_bumpmap = true; + } + + if( have_parallax || have_bumpmap ) + GL_AddShaderDirective( options, "COMPUTE_TBN" ); + + if( FBitSet( mat->flags, BRUSH_TRANSPARENT )) + GL_AddShaderDirective( options, "ALPHA_TEST" ); + + if( FBitSet( decal->flags, FDECAL_PUDDLE )) + { + if( CVAR_TO_BOOL( cv_realtime_puddles ) && CVAR_TO_BOOL( r_allow_mirrors )) + GL_AddShaderDirective( options, "PLANAR_REFLECTION" ); + else GL_AddShaderDirective( options, "REFLECTION_CUBEMAP" ); + } + + if( tr.fogEnabled ) + GL_AddShaderDirective( options, "APPLY_FOG_EXP" ); + + word shaderNum = GL_FindUberShader( glname, options ); + if( !shaderNum ) return 0; // something bad happens + + decal->forwardScene.SetShader( shaderNum ); + + return shaderNum; +} + +/* +=============== +R_ShaderDecalDeferred + +Select the program for surface (diffuse\puddle) +=============== +*/ +static word R_ShaderDecalDeferred( brushdecal_t *decal ) +{ + char glname[64]; + char options[MAX_OPTIONS_LENGTH]; + const DecalGroupEntry *texinfo = decal->texinfo; + mextrasurf_t *es= decal->surface; + bool mirrorSurface = false; + bool have_parallax = false; + bool have_bumpmap = false; + msurface_t *s = es->surf; + + ASSERT( worldmodel != NULL ); + + if( decal->deferredScene.IsValid( )) + return decal->deferredScene.GetHandle(); // valid + + Q_strncpy( glname, "deferred/scene_decal_bmodel", sizeof( glname )); + memset( options, 0, sizeof( options )); + + if( FBitSet( decal->flags, FDECAL_PUDDLE )) + GL_AddShaderDirective( options, "DECAL_PUDDLE" ); + + if( texinfo->gl_heightmap_id != tr.whiteTexture && texinfo->matdesc->reliefScale > 0.0f ) + { + if( cv_parallax->value == 1.0f ) + GL_AddShaderDirective( options, "PARALLAX_SIMPLE" ); + else if( cv_parallax->value >= 2.0f ) + GL_AddShaderDirective( options, "PARALLAX_OCCLUSION" ); + have_parallax = true; + } + + material_t *mat = R_TextureAnimation( s )->material; + + if( CVAR_TO_BOOL( cv_specular ) && ( texinfo->gl_specular_id != tr.blackTexture )) + GL_AddShaderDirective( options, "HAS_GLOSSMAP" ); + + if( !texinfo->opaque ) + { + // GL_DST_COLOR, GL_SRC_COLOR + GL_AddShaderDirective( options, "APPLY_COLORBLEND" ); + } + + if(( texinfo->gl_normalmap_id != tr.normalmapTexture ) && CVAR_TO_BOOL( cv_bump )) + { + GL_AddShaderDirective( options, "HAS_NORMALMAP" ); + GL_EncodeNormal( options, texinfo->gl_normalmap_id ); + + if( FBitSet( decal->flags, FDECAL_PUDDLE )) + GL_AddShaderDirective( options, "APPLY_REFRACTION" ); + have_bumpmap = true; + } + + if( have_parallax || have_bumpmap ) + GL_AddShaderDirective( options, "COMPUTE_TBN" ); + + if( FBitSet( mat->flags, BRUSH_TRANSPARENT )) + GL_AddShaderDirective( options, "ALPHA_TEST" ); + + if( FBitSet( decal->flags, FDECAL_PUDDLE )) + GL_AddShaderDirective( options, "REFLECTION_CUBEMAP" ); + + word shaderNum = GL_FindUberShader( glname, options ); + if( !shaderNum ) return 0; // something bad happens + + decal->deferredScene.SetShader( shaderNum ); + + return shaderNum; +} + +/* +============================================================================== + + DECAL CLIPPING + +============================================================================== +*/ +/* +================== +R_DecalPointHashKey +================== +*/ +static uint R_DecalPointHashKey( const Vector &point, uint hashSize ) +{ + uint hashKey = 0; + + hashKey ^= int( fabs( point.x )); + hashKey ^= int( fabs( point.y )); + hashKey ^= int( fabs( point.z )); + + hashKey &= (hashSize - 1); + + return hashKey; +} + +/* +================== +R_DecalPointCull +================== +*/ +static void R_DecalPointCull( const mplane_t *planes, int numVertices, const bvert_t *vertices, byte *cullBits ) +{ + float d0, d1, d2, d3, d4, d5; + int bits; + + for( int i = 0; i < numVertices; i++ ) + { + bits = 0; + + d0 = PlaneDiff( vertices[i].vertex, &planes[0] ); + d1 = PlaneDiff( vertices[i].vertex, &planes[1] ); + d2 = PlaneDiff( vertices[i].vertex, &planes[2] ); + d3 = PlaneDiff( vertices[i].vertex, &planes[3] ); + d4 = PlaneDiff( vertices[i].vertex, &planes[4] ); + d5 = PlaneDiff( vertices[i].vertex, &planes[5] ); + + bits |= FLOATSIGNBITSET( d0 ) << 0; + bits |= FLOATSIGNBITSET( d1 ) << 1; + bits |= FLOATSIGNBITSET( d2 ) << 2; + bits |= FLOATSIGNBITSET( d3 ) << 3; + bits |= FLOATSIGNBITSET( d4 ) << 4; + bits |= FLOATSIGNBITSET( d5 ) << 5; + + cullBits[i] = bits; + } +} + +/* +================== +R_ClearDecalClip +================== +*/ +static void R_ClearDecalClip( decalClip_t *clip ) +{ + if( !clip->numIndices && !clip->numVertices ) + return; + + // clear the hash table + memset( clip->verticesHashTable, 0, sizeof( clip->verticesHashTable )); + + // clear the arrays + clip->numVertices = clip->numIndices = 0; +} + +/* +================== +R_DecalIntersect + +check if this decal is intersected with others +================== +*/ +static brushdecal_t *R_DecalIntersect( decalClip_t *clip, msurface_t *surf ) +{ + float texc_orig_x = DotProduct( clip->textureVecs[0], clip->origin ); + float texc_orig_y = DotProduct( clip->textureVecs[1], clip->origin ); + float xsize = (float)clip->decalDesc->ysize; + float ysize = (float)clip->decalDesc->xsize; + brushdecal_t *pDecal = surf->info->pdecals; + float overlay = clip->decalDesc->overlay; + + while( pDecal ) + { + if( !FBitSet( pDecal->flags, FDECAL_PERMANENT )) + { + if(( pDecal->texinfo->xsize == xsize ) && ( pDecal->texinfo->ysize == ysize )) + { + float texc_x = fabs( DotProduct( pDecal->position, clip->textureVecs[0] ) - texc_orig_x ); + float texc_y = fabs( DotProduct( pDecal->position, clip->textureVecs[1] ) - texc_orig_y ); + + // replace existed decal + if( texc_x < ( xsize * overlay ) && texc_y < ( ysize * overlay )) + return pDecal; + } + } + + pDecal = pDecal->pnext; + } + + return NULL; +} + +static void R_DecalComputeTBN( brushdecal_t *decal ) +{ + if( decal->texinfo->gl_normalmap_id == tr.normalmapTexture && decal->texinfo->gl_heightmap_id == tr.whiteTexture ) + return; // not needs + + // build a map from vertex to a list of triangles that share the vert. + CUtlArray vertToTriMap; + + vertToTriMap.AddMultipleToTail( decal->numVerts ); + int triID; + + for( triID = 0; triID < (decal->numElems / 3); triID++ ) + { + vertToTriMap[decal->elems[triID*3+0]].AddToTail( triID ); + vertToTriMap[decal->elems[triID*3+1]].AddToTail( triID ); + vertToTriMap[decal->elems[triID*3+2]].AddToTail( triID ); + } + + // calculate the tangent space for each triangle. + CUtlArray triSVect, triTVect; + triSVect.AddMultipleToTail( (decal->numElems / 3) ); + triTVect.AddMultipleToTail( (decal->numElems / 3) ); + + float *v[3], *tc[3]; + + for( triID = 0; triID < (decal->numElems / 3); triID++ ) + { + for( int i = 0; i < 3; i++ ) + { + v[i] = (float *)&decal->verts[decal->elems[triID*3+i]].vertex; + tc[i] = (float *)&decal->verts[decal->elems[triID*3+i]].stcoord0; + } + + CalcTBN( v[0], v[1], v[2], tc[0], tc[1], tc[2], triSVect[triID], triTVect[triID] ); + } + + // calculate an average tangent space for each vertex. + for( int vertID = 0; vertID < decal->numVerts; vertID++ ) + { + dvert_t *v = &decal->verts[vertID]; + const Vector &normal = v->normal; + Vector &finalSVect = v->tangent; + Vector &finalTVect = v->binormal; + Vector sVect, tVect; + + sVect = tVect = g_vecZero; + + for( triID = 0; triID < vertToTriMap[vertID].Size(); triID++ ) + { + sVect += triSVect[vertToTriMap[vertID][triID]]; + tVect += triTVect[vertToTriMap[vertID][triID]]; + } + + Vector tmpVect = CrossProduct( sVect, tVect ); + bool leftHanded = DotProduct( tmpVect, normal ) < 0.0f; + + if( !leftHanded ) + { + tVect = CrossProduct( normal, sVect ); + sVect = CrossProduct( tVect, normal ); + } + else + { + tVect = CrossProduct( sVect, normal ); + sVect = CrossProduct( normal, tVect ); + } + + finalSVect = -sVect.Normalize(); + finalTVect = -tVect.Normalize(); + } +} + +/* +================== +R_AddDecal +================== +*/ +static void R_AddDecal( decalClip_t *clip, msurface_t *surf ) +{ + if( !clip->numIndices || !clip->numVertices ) + return; + + DecalGroupEntry *entry = (DecalGroupEntry *)clip->decalDesc; + + entry->PreloadTextures(); // time to cache decal textures + + if( !entry->gl_diffuse_id ) + { + // decal texture was missed? + R_ClearDecalClip( clip ); + return; + } + + brushdecal_t *newdecal = NULL; + brushdecal_t *olddecal = NULL; + + if( FBitSet( clip->flags, FDECAL_PERMANENT )) + { + newdecal = R_AllocDecal(); + } + else + { + if( !clip->current ) // don't intersect with self-fragments! + olddecal = R_DecalIntersect( clip, surf ); + newdecal = R_AllocDecal( olddecal ); + } + + if( !newdecal ) + { + ALERT( at_error, "MAX_BRUSH_DECALS limit exceeded!\n" ); + R_ClearDecalClip( clip ); + return; + } + + // puddles required reflections + if( FBitSet( clip->flags, FDECAL_PUDDLE ) && clip->decalDesc->matdesc->reflectScale > 0.0f ) + { + SetBits( surf->flags, SURF_REFLECT_PUDDLE ); + GL_AllocOcclusionQuery( surf ); + } + + // now our surface has decals + SetBits( surf->flags, SURF_HAS_DECALS ); + + newdecal->pnext = NULL; + olddecal = surf->info->pdecals; + + // place decal at tail of list + if( olddecal ) + { + while( olddecal->pnext ) + olddecal = olddecal->pnext; + olddecal->pnext = newdecal; + } + else + { + // first decal in chain + surf->info->pdecals = newdecal; + } + + // tag surface + newdecal->surface = surf->info; + newdecal->position = clip->origin; + newdecal->impactPlaneNormal = clip->axis[2]; + newdecal->model = clip->model; + newdecal->entityIndex = clip->entityIndex; + newdecal->texinfo = clip->decalDesc; + newdecal->flags = clip->flags; + newdecal->angle = clip->angle; + + // NOTE: any decal may potentially fragmented to multiple parts + // don't save all the fragments because we don't need them to restore this right + // only once (any) fragment needs to be saved to full restore all the fragments + if( clip->current ) + SetBits( newdecal->flags, FDECAL_DONTSAVE ); + else clip->current = newdecal; + + // init vertex cache + if( newdecal->numVerts < clip->numVertices ) + { + // FIXME: cache realloc provoked 'leaks' in cache array + if( newdecal->numVerts ) + ALERT( at_aiconsole, "R_AddDecal: vertex cache was reallocated (%i < %i)\n", newdecal->numVerts, clip->numVertices ); + newdecal->verts = &g_decalVertexCache[g_decalVertUsed]; + g_decalVertUsed += clip->numVertices; + newdecal->numVerts = clip->numVertices; + } + + // init index cache + if( newdecal->numElems < clip->numIndices ) + { + // FIXME: cache realloc provoked 'leaks' in cache array + if( newdecal->numElems ) + ALERT( at_aiconsole, "R_AddDecal: index cache was reallocated (%i < %i)\n", newdecal->numElems, clip->numIndices ); + newdecal->elems = &g_decalIndexCache[g_decalElemUsed]; + g_decalElemUsed += clip->numIndices; + newdecal->numElems = clip->numIndices; + } + + // Copy the indices + memcpy( newdecal->elems, clip->indices, clip->numIndices * sizeof( word )); + + mtexinfo_t *tex = surf->texinfo; + + // set up the vertices + for( int i = 0; i < clip->numVertices; i++ ) + { + dvert_t *v = &newdecal->verts[i]; + + Vector point = clip->vertices[i].point; + Vector delta = point - clip->origin; + + v->stcoord0[0] = DotProduct( clip->textureVecs[0], delta ) + 0.5f; + v->stcoord0[1] = DotProduct( clip->textureVecs[1], delta ) + 0.5f; + v->stcoord0[2] = (( DotProduct( point, tex->vecs[0] ) + tex->vecs[0][3] ) / tex->texture->width ); + v->stcoord0[3] = (( DotProduct( point, tex->vecs[1] ) + tex->vecs[1][3] ) / tex->texture->height ); + R_LightmapCoords( surf, point, newdecal->verts[i].lmcoord0, 0 ); // styles 0-1 + R_LightmapCoords( surf, point, newdecal->verts[i].lmcoord1, 2 ); // styles 2-3 + // NOTE: i can to place styles is outside but i'm leave it here to keep vertex aligned + memcpy( v->styles, surf->styles, sizeof( surf->styles )); + v->vertex = point; + if( FBitSet( surf->flags, SURF_PLANEBACK )) + v->normal = -surf->plane->normal; + else v->normal = surf->plane->normal; + } + + R_DecalComputeTBN( newdecal ); + R_ClearDecalClip( clip ); +} + +/* +================== +R_AddDecalFragment +================== +*/ +static void R_AddDecalFragment( decalClip_t *clip, msurface_t *fa, const bvert_t *verts, int numPoints, const Vector *points ) +{ + int index, indices[3]; + decalVertex_t *vertex; + uint hashKey; + + // for each triangle in the fragment + for( int i = 0; i < numPoints - 2; i++ ) + { + indices[0] = 0; + indices[1] = i + 1; + indices[2] = i + 2; + + // If we're going to overflow, add all the previous triangles to a separate decal + if(( clip->numIndices + 3 ) > MAX_DECAL_INDICES || ( clip->numVertices + 3 ) > MAX_DECAL_VERTICES ) + R_AddDecal( clip, fa ); + + // add the triangle + for( int j = 0; j < 3; j++ ) + { + index = indices[j]; + + // Check if this vertex already exists + hashKey = R_DecalPointHashKey( points[index], DECAL_VERTICES_HASH_SIZE ); + + for( vertex = clip->verticesHashTable[hashKey]; vertex; vertex = vertex->nextHash ) + { + if( vertex->point.Compare( points[index], 0.01f )) + break; + } + + // reuse an existing vertex or add a new one + if( !vertex ) + { + clip->indices[clip->numIndices++] = clip->numVertices; + + // add a new vertex + clip->vertices[clip->numVertices].point = points[index]; + clip->vertices[clip->numVertices].index = clip->numVertices; + + clip->vertices[clip->numVertices].nextHash = clip->verticesHashTable[hashKey]; + clip->verticesHashTable[hashKey] = &clip->vertices[clip->numVertices]; + + clip->numVertices++; + } + else clip->indices[clip->numIndices++] = vertex->index; + } + } +} + +/* +================== +R_ClipTriangleToDecal +================== +*/ +static void R_ClipTriangleToDecal( decalClip_t *clip, msurface_t *fa, int v0, int v1, int v2, const bvert_t *verts, int planeBits ) +{ + Vector points[2][MAX_CLIPVERTS]; + Vector front[MAX_CLIPVERTS]; + int numFront, pingPong = 0; + int numPoints; + + // clip the triangle to the decal + numPoints = 3; + + points[pingPong][0] = verts[v0].vertex; + points[pingPong][1] = verts[v1].vertex; + points[pingPong][2] = verts[v2].vertex; + + for( int i = 0; i < 6; i++ ) + { + if( !FBitSet( planeBits, BIT( i ))) + continue; + + if( !R_ClipPolygon( numPoints, points[pingPong], clip->planes + i, &numPoints, points[!pingPong] )) + return; + + pingPong ^= 1; + } + + if( numPoints < 3 ) return; + + // add the fragment at the front of the first split plane + R_SplitPolygon( numPoints, points[pingPong], &clip->splitPlanes[0], &numFront, front, &numPoints, points[!pingPong] ); + + R_AddDecalFragment( clip, fa, verts, numFront, front ); + + // add the fragment at the front of the second split plane + R_SplitPolygon( numPoints, points[!pingPong], &clip->splitPlanes[1], &numFront, front, &numPoints, points[pingPong] ); + + R_AddDecalFragment( clip, fa, verts, numFront, front ); + + // add the fragment at the back of both split planes + R_AddDecalFragment( clip, fa, verts, numPoints, points[pingPong] ); +} + +/* +================== +R_ClipSurfaceToDecal +================== +*/ +static void R_ClipSurfaceToDecal( decalClip_t *clip, msurface_t *fa ) +{ + mextrasurf_t *esrf = fa->info; + bvert_t *verts = &world->vertexes[esrf->firstvertex]; + byte cullBits[MAX_CLIPVERTS]; + int v0, v1, v2; + + ASSERT( esrf->numverts < MAX_CLIPVERTS ); + + if( FBitSet( fa->flags, SURF_PLANEBACK )) + { + if( DotProduct( clip->axis[2], fa->plane->normal ) > 0.0f ) + return; // facing away + } + else + { + if( DotProduct( clip->axis[2], fa->plane->normal ) < 0.0f ) + return; // facing away + } + + // Categorize all points by the planes + R_DecalPointCull( clip->planes, esrf->numverts, verts, cullBits ); + + // clip the surface + for( int i = 0; i < esrf->numverts - 2; i++ ) + { + v0 = 0; + v1 = i + 1; + v2 = i + 2; + + if( cullBits[v0] & cullBits[v1] & cullBits[v2] ) + continue; // completely off one side + + // calculate two mostly perpendicular edge directions + Vector dir1 = verts[v0].vertex - verts[v1].vertex; + Vector dir2 = verts[v2].vertex - verts[v1].vertex; + + // we have two edge directions, we can calculate a third vector from + // them, which is the direction of the triangle normal + Vector snorm = CrossProduct( dir1, dir2 ).Normalize(); + + // we multiply 0.5 by length of snorm to avoid normalizing + if( DotProduct( clip->axis[2], snorm ) < 0.0 ) + continue; // greater than 90 degrees + + // clip the triangle to the decal + R_ClipTriangleToDecal( clip, fa, v0, v1, v2, verts, cullBits[v0]|cullBits[v1]|cullBits[v2] ); + } + + // add a new decal if needed + R_AddDecal( clip, fa ); +} + +static void R_DecalNodeSurfaces( mnode_t *node, decalClip_t *clip ) +{ + // iterate over all surfaces in the node + msurface_t *surf = worldmodel->surfaces + node->firstsurface; + + for( int i = 0; i < node->numsurfaces; i++, surf++ ) + { + mextrasurf_t *esrf = surf->info; + + // never apply decals on the water or sky surfaces + if( FBitSet( surf->flags, ( SURF_DRAWTURB|SURF_DRAWSKY|SURF_CONVEYOR|SURF_DRAWTILED ))) + continue; + + // no puddles on transparent surfaces or mirrors + if( FBitSet( clip->flags, FDECAL_PUDDLE ) && FBitSet( surf->flags, ( SURF_TRANSPARENT|SURF_REFLECT ))) + continue; + + if( !BoundsIntersect( esrf->mins, esrf->maxs, clip->mins, clip->maxs )) + continue; + + R_ClipSurfaceToDecal( clip, surf ); + } +} + +static void R_DecalNode( mnode_t *node, decalClip_t *clip ) +{ + ASSERT( node != NULL ); + + // hit a leaf + if( node->contents < 0 ) + return; + + int s = BOX_ON_PLANE_SIDE( clip->mins, clip->maxs, node->plane ); + + if( s == 3 ) R_DecalNodeSurfaces( node, clip ); + if( s & 1 ) R_DecalNode( node->children[0], clip ); + if( s & 2 ) R_DecalNode( node->children[1], clip ); +} + +void CreateDecal( const Vector &vecEndPos, const Vector &vecPlaneNormal, float angle, const char *name, int flags, int entityIndex, int modelIndex, bool source ) +{ + int srcFlags = flags; + + if( !pDecalGroupList ) + return; + + // probably this never happens + if(( g_decalVertUsed >= MAX_DECAL_VERTS ) || ( g_decalElemUsed >= MAX_DECAL_ELEMS )) + { + ALERT( at_error, "CreateDecal: cache decal is overflow!\n" ); + return; + } + + decalClip_t decalClip; // intermediate struct that used only for build new decals + cl_entity_t *ent = NULL; + + decalClip.decalDesc = DecalGroup::GetEntry( name, flags ); + if( !decalClip.decalDesc ) return; + + // g-cont. allow more groups that starts from 'puddle' + if( !Q_strnicmp( name, "puddle", 6 )) + SetBits( flags, FDECAL_PUDDLE ); + + // puddles allowed only at floor surfaces + if( FBitSet( flags, FDECAL_PUDDLE ) && vecPlaneNormal != Vector( 0.0f, 0.0f, 1.0f )) + return; + + decalClip.model = NULL; + + if( entityIndex > 0 ) + { + ent = GET_ENTITY( entityIndex ); + + if( modelIndex > 0 ) + decalClip.model = MODEL_HANDLE( modelIndex ); + else if( ent != NULL ) + decalClip.model = MODEL_HANDLE( ent->curstate.modelindex ); + else return; + } + else if( modelIndex > 0 ) + decalClip.model = MODEL_HANDLE( modelIndex ); + else decalClip.model = worldmodel; + + if( !decalClip.model ) return; + + if( decalClip.model->type != mod_brush ) + { + ALERT( at_error, "Decals must hit mod_brush!\n" ); + return; + } + + if( ent && !FBitSet( flags, FDECAL_LOCAL_SPACE )) + { + // transform decal position in local bmodel space + if( ent->angles != g_vecZero ) + { + matrix4x4 matrix( ent->origin, ent->angles ); + + // transfrom decal position into local space + decalClip.origin = matrix.VectorITransform( vecEndPos ); + decalClip.axis[2] = matrix.VectorIRotate( vecPlaneNormal ); + } + else + { + decalClip.origin = vecEndPos - ent->origin; + decalClip.axis[2] = vecPlaneNormal; + } + + flags |= FDECAL_LOCAL_SPACE; // decal position moved into local space + } + else + { + // pass position in global + decalClip.origin = vecEndPos; + decalClip.axis[2] = vecPlaneNormal; + } + + // this decal must use landmark for correct transition + // a models with origin brush just use local space + if( !FBitSet( decalClip.model->flags, MODEL_HAS_ORIGIN )) + flags |= FDECAL_USE_LANDMARK; + + // just for consistency + RI->currententity = GET_ENTITY( entityIndex ); + RI->currentmodel = RI->currententity->model; + + // don't allow random decal select on a next save\restore + SetBits( flags, FDECAL_NORANDOM ); + + int xsize = decalClip.decalDesc->xsize; + int ysize = decalClip.decalDesc->ysize; + float s, c, depth = 1.0f; + matrix3x4 transform; + Vector up, right; + + // Compute orientation + SinCos( DEG2RAD( anglemod( angle )), &s, &c ); + VectorMatrix( decalClip.axis[2], right, up ); + right = -right; + + decalClip.axis[0] = (up * c) + (right * s); + decalClip.axis[1] = (right * c) - (up * s); + + decalClip.mins.x = -xsize; + decalClip.mins.y = -ysize; + decalClip.mins.z = -depth; + decalClip.maxs.x = xsize; + decalClip.maxs.y = ysize; + decalClip.maxs.z = depth; + decalClip.current = NULL; + decalClip.angle = angle; + + // need to properly transform decal bbox + transform.SetForward( decalClip.axis[0] ); + transform.SetRight( decalClip.axis[1] ); + transform.SetUp( decalClip.axis[2] ); + transform.SetOrigin( decalClip.origin ); + TransformAABB( transform, decalClip.mins, decalClip.maxs, decalClip.mins, decalClip.maxs ); + + // set up the clip planes + SetPlane( &decalClip.planes[0], decalClip.axis[0], DotProduct( decalClip.origin, decalClip.axis[0] ) - xsize ); + SetPlane( &decalClip.planes[1],-decalClip.axis[0],-DotProduct( decalClip.origin, decalClip.axis[0] ) - xsize ); + SetPlane( &decalClip.planes[2], decalClip.axis[1], DotProduct( decalClip.origin, decalClip.axis[1] ) - ysize ); + SetPlane( &decalClip.planes[3],-decalClip.axis[1],-DotProduct( decalClip.origin, decalClip.axis[1] ) - ysize ); + SetPlane( &decalClip.planes[4], decalClip.axis[2], DotProduct( decalClip.origin, decalClip.axis[2] ) - depth ); + SetPlane( &decalClip.planes[5],-decalClip.axis[2],-DotProduct( decalClip.origin, decalClip.axis[2] ) - depth ); + + // set up the split planes + SetPlane( &decalClip.splitPlanes[0], decalClip.axis[2], DotProduct( decalClip.origin, decalClip.axis[2] ) + ( depth * 0.5f )); + SetPlane( &decalClip.splitPlanes[1],-decalClip.axis[2], -DotProduct( decalClip.origin, decalClip.axis[2] ) - ( depth * 0.5f ) + depth ); + + // compute the texture vectors + decalClip.textureVecs[0] = decalClip.axis[1] * ( 0.5f / ysize ); + decalClip.textureVecs[1] = decalClip.axis[0] * ( 0.5f / xsize ); + + // Clear the arrays + decalClip.numIndices = 0; + decalClip.numVertices = 0; + decalClip.entityIndex = entityIndex; + decalClip.flags = flags; + + // clear the hash table + memset( decalClip.verticesHashTable, 0, sizeof( decalClip.verticesHashTable )); + + // g-cont. now using walking on bsp-tree instead of stupid linear search + R_DecalNode( &decalClip.model->nodes[decalClip.model->hulls[0].firstclipnode], &decalClip ); + if( !source ) return; // to avoid recursion + + // trying to place decals on contacted submodels too + // FIXME: this is doesn't working + for( int i = 0; i < 1024; i++ ) + { + model_t *mod = MODEL_HANDLE( i ); + + if( !mod || mod->type != mod_brush || mod == decalClip.model ) + continue; + + if( mod->firstmodelsurface >= worldmodel->numsurfaces || !mod->nummodelsurfaces ) + continue; // skip weird models + + int entityIndex = 0; + + if( mod->name[0] == '*' ) + { + msurface_t *surf = mod->surfaces + mod->firstmodelsurface; + cl_entity_t *e = surf->info->parent; + if( !e ) continue; // entity invisible on the client + + gl_state_t *glm = GL_GetCache( e->hCachedMatrix ); + Vector absmin, absmax; + + TransformAABB( glm->transform, mod->mins, mod->maxs, absmin, absmax ); + + if( !BoundsIntersect( absmin, absmax, vecEndPos, vecEndPos )) + continue; // no intersection with this model + entityIndex = e->index; + } + + // trying to place decal on neighbored bmodel + CreateDecal( vecEndPos, vecPlaneNormal, angle, name, srcFlags, entityIndex, 0, false ); + } +} + +void CreateDecal( pmtrace_t *tr, const char *name, float angle, bool visent ) +{ + if( !g_fRenderInitialized ) + return; + + if( tr->allsolid || tr->fraction == 1.0 || tr->ent < 0 ) + return; + + physent_t *pe = NULL; + + if( visent ) pe = gEngfuncs.pEventAPI->EV_GetVisent( tr->ent ); + else pe = gEngfuncs.pEventAPI->EV_GetPhysent( tr->ent ); + if( !pe ) return; + + int entityIndex = pe->info; + int modelIndex = 0; + + // modelindex is needs for properly save\restore + cl_entity_t *ent = GET_ENTITY( entityIndex ); + if( ent ) modelIndex = ent->curstate.modelindex; + + CreateDecal( tr->endpos, tr->plane.normal, angle, name, 0, entityIndex, modelIndex ); +} + +// debugging feature +void PasteViewDecal( void ) +{ + const char *name = CMD_ARGV( 1 ); + float angle = 0.0f; + + if( CMD_ARGC() <= 1 ) + { + Msg( "usage: pastedecal \n" ); + return; + } + + pmtrace_t *tr = gEngfuncs.pEventAPI->EV_VisTraceLine( GetVieworg(), (GetVieworg() + (GetVForward() * 1024.0f)), PM_NORMAL ); + if( fabs( tr->plane.normal.z ) > 0.7f ) + angle = RI->view.angles.y + 90.0f; + physent_t *pe = gEngfuncs.pEventAPI->EV_GetVisent( tr->ent ); + + // now handle both types: studio and brushes + if( pe && pe->studiomodel ) + g_StudioRenderer.StudioDecalShoot( tr, name, true ); + else CreateDecal( tr, name, angle, true ); +} + +int SaveDecalList( decallist_t *pBaseList, int count ) +{ + int maxBrushDecals = MAX_BRUSH_DECALS + (MAX_BRUSH_DECALS - count); + decallist_t *pList = pBaseList + count; // shift list to first free slot + brushdecal_t *pdecal, *pdecals; + int total = 0, depth; + + for( int i = 0; i < gDecalCount; i++ ) + { + pdecal = &gDecalPool[i]; + const DecalGroupEntry *tex = pdecal->texinfo; + + if( !pdecal->surface || !tex || !tex->group ) + continue; // unused + + if( FBitSet( pdecal->flags, FDECAL_DONTSAVE )) + continue; + + // compute depth + depth = 0; + pdecals = pdecal->surface->pdecals; + + while( pdecals && pdecals != pdecal ) + { + depth++; + pdecals = pdecals->pnext; + } + + pList[total].depth = depth; + pList[total].flags = pdecal->flags; + pList[total].entityIndex = pdecal->entityIndex; + pList[total].position = pdecal->position; + pList[total].impactPlaneNormal = pdecal->impactPlaneNormal; + pList[total].scale = pdecal->angle; + Q_snprintf( pList[total].name, sizeof( pList[total].name ), "%s@%s", tex->group->GetName(), tex->m_DecalName ); + total++; + + // check for list overflow + if( total >= maxBrushDecals ) + { + ALERT( at_error, "SaveDecalList: too many brush decals on save\restore\n" ); + goto end_serialize; + } + } +end_serialize: + return total; +} + +inline void R_DecalSetupVertex( dvert_t *vert ) +{ + pglVertexAttrib4fvARB( ATTR_INDEX_TANGENT, vert->tangent ); + pglVertexAttrib4fvARB( ATTR_INDEX_BINORMAL, vert->binormal ); + pglVertexAttrib4fvARB( ATTR_INDEX_NORMAL, vert->normal ); + pglVertexAttrib4fvARB( ATTR_INDEX_TEXCOORD0, vert->stcoord0 ); + pglVertexAttrib4fvARB( ATTR_INDEX_TEXCOORD1, vert->lmcoord0 ); + pglVertexAttrib4fvARB( ATTR_INDEX_TEXCOORD2, vert->lmcoord1 ); + pglVertexAttrib4ubvARB( ATTR_INDEX_LIGHT_STYLES, vert->styles ); + pglVertexAttrib3fvARB( ATTR_INDEX_POSITION, vert->vertex ); +} + +/* +================ +R_SetDecalUniforms + +================ +*/ +void R_SetDecalUniforms( brushdecal_t *decal ) +{ + mextrasurf_t *es = decal->surface; + cl_entity_t *e = es->parent; + msurface_t *s = es->surf; + int map, width, height; + Vector4D lightstyles; + + // begin draw the sorted list + if( FBitSet( RI->params, RP_DEFERREDSCENE )) + { + if( RI->currentshader != decal->deferredScene.GetShader() ) + { + // force to bind new shader + GL_BindShader( decal->deferredScene.GetShader() ); + } + } + else + { + if( RI->currentshader != decal->forwardScene.GetShader() ) + { + // force to bind new shader + GL_BindShader( decal->forwardScene.GetShader() ); + } + } + + material_t *mat = R_TextureAnimation( es->surf )->material; + gl_state_t *glm = GL_GetCache( e->hCachedMatrix ); + glsl_program_t *shader = RI->currentshader; + matdesc_t *desc = decal->texinfo->matdesc; + GLfloat viewMatrix[16]; + + tr.modelorg = glm->GetModelOrigin(); + + // setup specified uniforms (and texture bindings) + for( int i = 0; i < shader->numUniforms; i++ ) + { + uniform_t *u = &shader->uniforms[i]; + + switch( u->type ) + { + case UT_COLORMAP: + if( Surf_CheckSubview( es, true ) && FBitSet( decal->flags, FDECAL_PUDDLE )) + u->SetValue( Surf_GetSubview( es )->texturenum ); + else u->SetValue( mat->gl_diffuse_id ); + break; + case UT_NORMALMAP: + u->SetValue( decal->texinfo->gl_normalmap_id ); + break; + case UT_GLOSSMAP: + u->SetValue( decal->texinfo->gl_specular_id ); + break; + case UT_LIGHTMAP: + if( R_FullBright( )) u->SetValue( tr.deluxemapTexture ); + else u->SetValue( tr.lightmaps[es->lightmaptexturenum].lightmap ); + break; + case UT_DELUXEMAP: + if( R_FullBright( )) u->SetValue( tr.grayTexture ); + else u->SetValue( tr.lightmaps[es->lightmaptexturenum].deluxmap ); + break; + case UT_DECALMAP: + u->SetValue( decal->texinfo->gl_diffuse_id ); + break; + case UT_SCREENMAP: + u->SetValue( tr.screen_color ); + break; + case UT_DEPTHMAP: + u->SetValue( tr.screen_depth ); + break; + case UT_HEIGHTMAP: + u->SetValue( decal->texinfo->gl_heightmap_id ); + break; + case UT_ENVMAP0: + case UT_ENVMAP: + if( es->cubemap[0] != NULL ) + u->SetValue( es->cubemap[0]->texture ); + else u->SetValue( tr.whiteCubeTexture ); + break; + case UT_ENVMAP1: + if( es->cubemap[1] != NULL ) + u->SetValue( es->cubemap[1]->texture ); + else u->SetValue( tr.whiteCubeTexture ); + break; + case UT_FITNORMALMAP: + u->SetValue( tr.normalsFitting ); + break; + case UT_FRAGDATA0: + u->SetValue( tr.defscene_fbo->colortarget[0] ); + break; + case UT_FRAGDATA1: + u->SetValue( tr.defscene_fbo->colortarget[1] ); + break; + case UT_FRAGDATA2: + u->SetValue( tr.defscene_fbo->colortarget[2] ); + break; + case UT_MODELMATRIX: + u->SetValue( &glm->modelMatrix[0] ); + break; + case UT_REFLECTMATRIX: + if( Surf_CheckSubview( es, true )) + Surf_GetSubview( es )->matrix.CopyToArray( viewMatrix ); + else memcpy( viewMatrix, glState.identityMatrix, sizeof( float ) * 16 ); + u->SetValue( &viewMatrix[0] ); + break; + case UT_SCREENSIZEINV: + u->SetValue( 1.0f / (float)glState.width, 1.0f / (float)glState.height ); + break; + case UT_ZFAR: + u->SetValue( -tr.farclip * 1.74f ); + break; + case UT_LIGHTSTYLES: + for( map = 0; map < MAXLIGHTMAPS; map++ ) + { + if( s->styles[map] != 255 ) + lightstyles[map] = tr.lightstyle[s->styles[map]]; + else lightstyles[map] = 0.0f; + } + u->SetValue( lightstyles.x, lightstyles.y, lightstyles.z, lightstyles.w ); + break; + case UT_LIGHTSTYLEVALUES: + u->SetValue( &tr.lightstyle[0], MAX_LIGHTSTYLES ); + break; + case UT_REALTIME: + u->SetValue( (float)tr.time ); + break; + case UT_FOGPARAMS: + if( e->curstate.renderfx == SKYBOX_ENTITY ) + u->SetValue( tr.fogColor[0], tr.fogColor[1], tr.fogColor[2], tr.fogSkyDensity ); + else u->SetValue( tr.fogColor[0], tr.fogColor[1], tr.fogColor[2], tr.fogDensity ); + break; + case UT_TEXOFFSET: + u->SetValue( es->texofs[0], es->texofs[1] ); + break; + case UT_VIEWORIGIN: + u->SetValue( tr.modelorg.x, tr.modelorg.y, tr.modelorg.z, e->hCachedMatrix ? 1.0f : 0.0f ); + break; + case UT_SMOOTHNESS: + u->SetValue( desc->smoothness ); + break; + case UT_DIFFUSEFACTOR: + u->SetValue( tr.diffuseFactor ); + break; + case UT_AMBIENTFACTOR: + u->SetValue( tr.ambientFactor ); + break; + case UT_REFRACTSCALE: + u->SetValue( bound( 0.0f, desc->refractScale, 1.0f )); + break; + case UT_REFLECTSCALE: + u->SetValue( bound( 0.0f, desc->reflectScale, 1.0f )); + break; + case UT_ABERRATIONSCALE: + u->SetValue( bound( 0.0f, desc->aberrationScale, 1.0f )); + break; + case UT_LERPFACTOR: + u->SetValue( es->lerpFactor ); + break; + case UT_BOXMINS: + if( world->num_cubemaps > 0 ) + { + Vector mins[2]; + mins[0] = es->cubemap[0]->mins; + mins[1] = es->cubemap[1]->mins; + u->SetValue( &mins[0][0], 2 ); + } + break; + case UT_BOXMAXS: + if( world->num_cubemaps > 0 ) + { + Vector maxs[2]; + maxs[0] = es->cubemap[0]->maxs; + maxs[1] = es->cubemap[1]->maxs; + u->SetValue( &maxs[0][0], 2 ); + } + break; + case UT_CUBEORIGIN: + if( world->num_cubemaps > 0 ) + { + Vector origin[2]; + origin[0] = es->cubemap[0]->origin; + origin[1] = es->cubemap[1]->origin; + u->SetValue( &origin[0][0], 2 ); + } + break; + case UT_CUBEMIPCOUNT: + if( world->num_cubemaps > 0 ) + { + float r = Q_max( 1, es->cubemap[0]->numMips - cv_cube_lod_bias->value ); + float g = Q_max( 1, es->cubemap[1]->numMips - cv_cube_lod_bias->value ); + u->SetValue( r, g ); + } + break; + case UT_LIGHTNUMS0: + u->SetValue( (float)es->lights[0], (float)es->lights[1], (float)es->lights[2], (float)es->lights[3] ); + break; + case UT_LIGHTNUMS1: + u->SetValue( (float)es->lights[4], (float)es->lights[5], (float)es->lights[6], (float)es->lights[7] ); + break; + case UT_RELIEFPARAMS: + width = RENDER_GET_PARM( PARM_TEX_WIDTH, decal->texinfo->gl_heightmap_id ); + height = RENDER_GET_PARM( PARM_TEX_HEIGHT, decal->texinfo->gl_heightmap_id ); + u->SetValue( (float)width, (float)height, desc->reliefScale, cv_shadow_offset->value ); + break; + default: + ALERT( at_error, "%s: unhandled uniform %s\n", RI->currentshader->name, u->name ); + break; + } + } +} + +static void DrawWireDecal( brushdecal_t *decal ) +{ + mextrasurf_t *es = decal->surface; + cl_entity_t *e = es->parent; + + pglEnable( GL_BLEND ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + pglPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + if( FBitSet( decal->flags, FDECAL_DONTSAVE )) + pglColor4f( 1.0f, 0.5f, 0.36f, 0.99f ); + else pglColor4f( 0.5f, 1.0f, 0.36f, 0.99f ); + pglLineWidth( 4.0f ); + + pglDisable( GL_DEPTH_TEST ); + pglEnable( GL_LINE_SMOOTH ); + pglEnable( GL_POLYGON_SMOOTH ); + pglHint( GL_LINE_SMOOTH_HINT, GL_NICEST ); + pglHint( GL_POLYGON_SMOOTH_HINT, GL_NICEST ); + + gl_state_t *glm = GL_GetCache( e->hCachedMatrix ); + + // transform decal verts to actual position + for( int k = 0; k < decal->numElems; k += 3 ) + { + pglBegin( GL_TRIANGLES ); + pglVertex3fv( glm->transform.VectorTransform( decal->verts[decal->elems[k+0]].vertex )); + pglVertex3fv( glm->transform.VectorTransform( decal->verts[decal->elems[k+1]].vertex )); + pglVertex3fv( glm->transform.VectorTransform( decal->verts[decal->elems[k+2]].vertex )); + pglEnd(); + } + + pglPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + pglDisable( GL_POLYGON_SMOOTH ); + pglDisable( GL_LINE_SMOOTH ); + pglEnable( GL_DEPTH_TEST ); + pglDisable( GL_BLEND ); + pglLineWidth( 1.0f ); +} + +static void R_DrawSurfaceDecalsDebug( msurface_t *surf, drawlist_t drawlist_type ) +{ + mextrasurf_t *fa = surf->info; + brushdecal_t *p; + + for( p = fa->pdecals; p; p = p->pnext ) + { + if( !p->surface || !p->texinfo ) + continue; // bad decal? + + if( drawlist_type == DRAWLIST_SOLID && !p->texinfo->opaque ) + continue; + + if( drawlist_type == DRAWLIST_TRANS && p->texinfo->opaque ) + continue; + + DrawWireDecal( p ); + } +} + +static void R_RenderSurfaceDecals( msurface_t *surf, drawlist_t drawlist_type ) +{ + mextrasurf_t *fa = surf->info; + brushdecal_t *p; + + for( p = fa->pdecals; p; p = p->pnext ) + { + if( !p->surface || !p->texinfo ) + continue; // bad decal? + + if( drawlist_type == DRAWLIST_SOLID && !p->texinfo->opaque ) + continue; + + if( drawlist_type == DRAWLIST_TRANS && p->texinfo->opaque ) + continue; + + // initialize decal shader + if( FBitSet( RI->params, RP_DEFERREDSCENE )) + { + if( !R_ShaderDecalDeferred( p )) + continue; + } + else + { + if( !R_ShaderDecalForward( p )) + continue; + } + R_SetDecalUniforms( p ); + + if( !FBitSet( RI->params, RP_DEFERREDSCENE )) + { + if( p->texinfo->opaque ) + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + else pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR ); + } + r_stats.c_total_tris += (p->numElems / 3); + + // draw decal from cache + for( int k = 0; k < p->numElems; k += 3 ) + { + pglBegin( GL_TRIANGLES ); + R_DecalSetupVertex( &p->verts[p->elems[k+0]] ); + R_DecalSetupVertex( &p->verts[p->elems[k+1]] ); + R_DecalSetupVertex( &p->verts[p->elems[k+2]] ); + pglEnd(); + } + } +} + +static void R_RenderSolidListDecalsDebug( drawlist_t drawlist_type ) +{ + GL_AlphaTest( GL_FALSE ); + GL_DepthMask( GL_FALSE ); + GL_BindShader( NULL ); + GL_Blend( GL_FALSE ); + + for( int i = 0; i < RI->frame.solid_faces.Count(); i++ ) + { + CSolidEntry *entry = &RI->frame.solid_faces[i]; + + if( entry->m_bDrawType != DRAWTYPE_SURFACE ) + continue; + + msurface_t *s = entry->m_pSurf; + + if( !FBitSet( s->flags, SURF_HAS_DECALS )) + continue; + + R_DrawSurfaceDecalsDebug( s, drawlist_type ); + } +} + +static void R_RenderTransListDecalsDebug( drawlist_t drawlist_type ) +{ + GL_AlphaTest( GL_FALSE ); + GL_DepthMask( GL_FALSE ); + GL_BindShader( NULL ); + GL_Blend( GL_FALSE ); + + for( int i = 0; i < RI->frame.trans_list.Count(); i++ ) + { + CTransEntry *entry = &RI->frame.trans_list[i]; + + if( entry->m_bDrawType != DRAWTYPE_SURFACE ) + continue; + + msurface_t *s = entry->m_pSurf; + + if( !FBitSet( s->flags, SURF_HAS_DECALS )) + continue; + + R_DrawSurfaceDecalsDebug( s, drawlist_type ); + } +} + +void R_RenderDecalsSolidList( drawlist_t drawlist_type ) +{ + if( FBitSet( RI->params, ( RP_ENVVIEW|RP_SKYVIEW ))) + return; + + if( !gDecalCount || !CVAR_TO_BOOL( cv_decals )) + return; + + if( !RI->frame.solid_faces.Count() ) + return; + + if( CVAR_TO_BOOL( cv_decalsdebug )) + { + R_RenderSolidListDecalsDebug( drawlist_type ); + return; + } + + if( GL_Support( R_SEAMLESS_CUBEMAP )) + pglEnable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); + + if( FBitSet( RI->params, RP_DEFERREDSCENE )) + { + pglColorMaski( 3, GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE ); + pglColorMaski( 4, GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE ); + } + + if( FBitSet( RI->params, RP_DEFERREDSCENE )) + GL_Blend( GL_FALSE ); + else GL_Blend( GL_TRUE ); + GL_AlphaTest( GL_FALSE ); + GL_DepthMask( GL_FALSE ); + + if( CVAR_TO_BOOL( r_polyoffset )) + { + pglEnable( GL_POLYGON_OFFSET_FILL ); + pglPolygonOffset( -1.0f, -r_polyoffset->value ); + } + + for( int i = 0; i < RI->frame.solid_faces.Count(); i++ ) + { + CSolidEntry *entry = &RI->frame.solid_faces[i]; + + if( entry->m_bDrawType != DRAWTYPE_SURFACE ) + continue; + + msurface_t *s = entry->m_pSurf; + + if( !FBitSet( s->flags, SURF_HAS_DECALS )) + continue; + + R_RenderSurfaceDecals( s, drawlist_type ); + } + + if( FBitSet( RI->params, RP_DEFERREDSCENE )) + { + pglColorMaski( 3, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE ); + pglColorMaski( 4, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE ); + } + + if( GL_Support( R_SEAMLESS_CUBEMAP )) + pglDisable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); + if( CVAR_TO_BOOL( r_polyoffset )) + pglDisable( GL_POLYGON_OFFSET_FILL ); + GL_CleanupAllTextureUnits(); + GL_BindShader( NULL ); + GL_Cull( GL_FRONT ); +} + +void R_RenderDecalsTransList( drawlist_t drawlist_type ) +{ + if( FBitSet( RI->params, ( RP_ENVVIEW|RP_SKYVIEW ))) + return; + + if( !gDecalCount || !CVAR_TO_BOOL( cv_decals )) + return; + + if( !RI->frame.trans_list.Count() ) + return; + + if( CVAR_TO_BOOL( cv_decalsdebug )) + { + R_RenderTransListDecalsDebug( drawlist_type ); + return; + } + + GL_Blend( GL_TRUE ); + GL_AlphaTest( GL_FALSE ); + GL_DepthMask( GL_FALSE ); + GL_Cull( GL_NONE ); + + pglPolygonOffset( -1, -1 ); + pglEnable( GL_POLYGON_OFFSET_FILL ); + + for( int i = 0; i < RI->frame.trans_list.Count(); i++ ) + { + CTransEntry *entry = &RI->frame.trans_list[i]; + + if( entry->m_bDrawType != DRAWTYPE_SURFACE ) + continue; + + msurface_t *s = entry->m_pSurf; + + if( !FBitSet( s->flags, SURF_HAS_DECALS )) + continue; + + R_RenderSurfaceDecals( s, drawlist_type ); + } + + pglDisable( GL_POLYGON_OFFSET_FILL ); + GL_CleanupAllTextureUnits(); + GL_BindShader( NULL ); + GL_Cull( GL_FRONT ); +} + +void R_RenderDecalsTransEntry( CTransEntry *entry, drawlist_t drawlist_type ) +{ + if( FBitSet( RI->params, ( RP_ENVVIEW|RP_SKYVIEW ))) + return; + + if( !gDecalCount || !CVAR_TO_BOOL( cv_decals )) + return; + + if( entry->m_bDrawType != DRAWTYPE_SURFACE ) + return; + + if( !FBitSet( entry->m_pSurf->flags, SURF_HAS_DECALS )) + return; + + if( CVAR_TO_BOOL( cv_decalsdebug )) + { + R_DrawSurfaceDecalsDebug( entry->m_pSurf, drawlist_type ); + return; + } + + GL_Blend( GL_TRUE ); + GL_AlphaTest( GL_FALSE ); + GL_DepthMask( GL_FALSE ); + GL_Cull( GL_NONE ); + + pglPolygonOffset( -1, -1 ); + pglEnable( GL_POLYGON_OFFSET_FILL ); + + R_RenderSurfaceDecals( entry->m_pSurf, drawlist_type ); + + pglDisable( GL_POLYGON_OFFSET_FILL ); + GL_CleanupAllTextureUnits(); + GL_Cull( GL_FRONT ); +} + +void ClearDecals( void ) +{ + for( int i = 0; i < worldmodel->numsurfaces; i++ ) + { + msurface_t *surf = &worldmodel->surfaces[i]; + ClearBits( surf->flags, SURF_HAS_DECALS|SURF_REFLECT_PUDDLE ); + surf->info->pdecals = NULL; + } + + memset( gDecalPool, 0, sizeof( gDecalPool )); + g_decalVertUsed = g_decalElemUsed = 0; + gDecalCount = gDecalCycle = 0; +} + +// =========================== +// Decals loading +// =========================== +void DecalsInit( void ) +{ + ADD_COMMAND( "pastedecal", PasteViewDecal ); + ADD_COMMAND( "cleardecals", ClearDecals ); + + ALERT( at_aiconsole, "Loading decals\n" ); + + char *pfile = (char *)gEngfuncs.COM_LoadFile( "gfx/decals/decalinfo.txt", 5, NULL ); + + if( !pfile ) + { + ALERT( at_error, "Cannot open file \"gfx/decals/decalinfo.txt\"\n" ); + return; + } + + ALERT( at_aiconsole, "Decal vertex cache %s, index cache %s\n", Q_memprint( sizeof( g_decalVertexCache )), Q_memprint( sizeof( g_decalIndexCache ))); + + char *ptext = pfile; + int counter = 0; + + while( 1 ) + { + // store position where group names recorded + char *groupnames = ptext; + + // loop until we'll find decal names + char path[256], token[256]; + int numgroups = 0; + + while( 1 ) + { + ptext = COM_ParseFile( ptext, token ); + if( !ptext ) goto getout; + + if( token[0] == '{' && !token[1] ) + break; + numgroups++; + } + + DecalGroupEntry tempentries[MAX_GROUPENTRIES]; + int numtemp = 0; + + while( 1 ) + { + char sz_xsize[64]; + char sz_ysize[64]; + char sz_overlay[64]; + + ptext = COM_ParseFile( ptext, token ); + if( !ptext ) goto getout; + if( token[0] == '}' ) + break; + + if( numtemp >= MAX_GROUPENTRIES ) + { + ALERT( at_error, "Too many decals in group (%d max) - skipping %s\n", MAX_GROUPENTRIES, token ); + continue; + } + + ptext = COM_ParseFile( ptext, sz_xsize ); + if( !ptext ) goto getout; + ptext = COM_ParseFile( ptext, sz_ysize ); + if( !ptext ) goto getout; + ptext = COM_ParseFile( ptext, sz_overlay ); + if( !ptext ) goto getout; + + if( Q_strlen( token ) > 16 ) + { + ALERT( at_error, "%s - got too large token when parsing decal info file\n", token ); + continue; + } + + if( token[0] == '$' ) + { + Q_strncpy( token, token + 1, sizeof( token )); + tempentries[numtemp].opaque = true;// force solid + } + else tempentries[numtemp].opaque = false; + Q_strncpy( tempentries[numtemp].m_DecalName, token, sizeof( tempentries[numtemp].m_DecalName )); + tempentries[numtemp].xsize = Q_atof( sz_xsize ) / 2.0f; + tempentries[numtemp].ysize = Q_atof( sz_ysize ) / 2.0f; + tempentries[numtemp].overlay = Q_atof( sz_overlay ) * 2.0f; + Q_snprintf( path, sizeof( path ), "decals/%s", token ); + tempentries[numtemp].matdesc = CL_FindMaterial( path ); // get description for decal texture + tempentries[numtemp].m_init = false; // need to preload + tempentries[numtemp].gl_normalmap_id = tr.normalmapTexture; + tempentries[numtemp].gl_specular_id = tr.blackTexture; + tempentries[numtemp].gl_heightmap_id = tr.whiteTexture; + tempentries[numtemp].gl_diffuse_id = 0; // assume no texture + numtemp++; + } + + // get back to group names + for( int i = 0; i < numgroups; i++ ) + { + groupnames = COM_ParseFile( groupnames, token ); + if( !numtemp ) + { + ALERT( at_warning, "got empty decal group: %s\n", token ); + continue; + } + + new DecalGroup( token, numtemp, tempentries ); + counter++; + } + } + +getout: + gEngfuncs.COM_FreeFile( pfile ); + ALERT( at_console, "%d decal groups created\n", counter ); +} + +void DecalsShutdown( void ) +{ + if( !pDecalGroupList ) + return; + + DecalGroup **prev = &pDecalGroupList; + DecalGroup *item; + + while( 1 ) + { + item = *prev; + if( !item ) break; + + *prev = item->GetNext(); + delete item; + } +} \ No newline at end of file diff --git a/cl_dll/render/gl_decals.h b/cl_dll/render/gl_decals.h new file mode 100644 index 0000000..c482c5e --- /dev/null +++ b/cl_dll/render/gl_decals.h @@ -0,0 +1,105 @@ +/* +gl_decals.h - decal project & rendering +this code written for Paranoia 2: Savior modification +Copyright (C) 2013 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef GL_DECALS_H +#define GL_DECALS_H + +class DecalGroup; +class DecalGroupEntry +{ +public: + char m_DecalName[64]; + unsigned short gl_diffuse_id; + unsigned short gl_normalmap_id; + unsigned short gl_heightmap_id; + unsigned short gl_specular_id; // specular + int xsize, ysize; + matdesc_t *matdesc; // pointer to settings + float overlay; + bool opaque; // solid decal doesn't use blend + const DecalGroup *group; // get group name + bool m_init; + + void PreloadTextures( void ); +}; + +class DecalGroup +{ +public: + DecalGroup( const char *name, int numelems, DecalGroupEntry *source ); + ~DecalGroup(); + + DecalGroupEntry *GetRandomDecal( void ); + DecalGroup *GetNext( void ) { return pnext; } + const char *GetName( void ) { return m_chGroupName; } + const char *GetName( void ) const { return m_chGroupName; } + static DecalGroup *FindGroup( const char *name ); + DecalGroupEntry *FindEntry( const char *name ); + DecalGroupEntry *GetEntry( int num ); + static DecalGroupEntry *GetEntry( const char *name, int flags ); +private: + char m_chGroupName[16]; + DecalGroupEntry *pEntryArray; + DecalGroup *pnext; + int size; +}; + +typedef struct dvert_s +{ + Vector vertex; // position + Vector tangent; // tangent + Vector binormal; // binormal + Vector normal; // normal + float stcoord0[4]; // ST texture coords + Decal coords + float lmcoord0[4]; // LM texture coords for styles 0-1 + float lmcoord1[4]; // LM texture coords for styles 2-3 + byte styles[4]; // lightstyles +} dvert_t; + +// decal entry +typedef struct brushdecal_s +{ + // this part is goes to savelist + byte flags; + short entityIndex; + Vector position; + Vector impactPlaneNormal; + float angle; // goes into scale + const DecalGroupEntry *texinfo; + + // verts & elems + dvert_t *verts; // pointer to cache array + word *elems; // pointer to index array + byte numVerts; + byte numElems; + + // shader cache + shader_t forwardScene; + shader_t deferredScene; + mextrasurf_t *surface; + struct brushdecal_s *pnext; // linked list for each surface + model_t *model; +} brushdecal_t; + +void DecalsInit( void ); +void ClearDecals( void ); +void DecalsShutdown( void ); +void R_RenderDecalsSolidList( drawlist_t drawlist_type ); +void R_RenderDecalsTransList( drawlist_t drawlist_type ); +void R_RenderDecalsTransEntry( CTransEntry *entry, drawlist_t drawlist_type ); +int SaveDecalList( decallist_t *pBaseList, int count ); + +#endif//GL_DECALS_H \ No newline at end of file diff --git a/cl_dll/render/gl_deferred.cpp b/cl_dll/render/gl_deferred.cpp new file mode 100644 index 0000000..a2b92f6 --- /dev/null +++ b/cl_dll/render/gl_deferred.cpp @@ -0,0 +1,554 @@ +/* +gl_deferred.cpp - deferred rendering +Copyright (C) 2018 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include "cl_util.h" +#include "gl_local.h" +#include "gl_shader.h" +#include "gl_world.h" +#include "gl_grass.h" + +enum +{ + SCENE_PASS = 0, + LIGHT_PASS, +}; + +enum +{ + BILATERAL_PASS0 = 0, + BILATERAL_PASS1, +}; + +void GL_InitDefSceneFBO( void ) +{ + GLenum MRTBuffers[5] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_COLOR_ATTACHMENT2_EXT, GL_COLOR_ATTACHMENT3_EXT, GL_COLOR_ATTACHMENT4_EXT }; + int albedo_buffer; + int normal_buffer; + int smooth_buffer; + int light0_buffer; + int light1_buffer; + int depth_buffer; + + if( !GL_Support( R_FRAMEBUFFER_OBJECT ) || !GL_Support( R_DRAW_BUFFERS_EXT )) + return; + + // Create and set up the framebuffer + tr.defscene_fbo = GL_AllocDrawbuffer( "*defscene", glState.width, glState.height, 1 ); + + // create textures + albedo_buffer = CREATE_TEXTURE( "*albedo_rt", glState.width, glState.height, NULL, TF_RT_COLOR|TF_HAS_ALPHA ); + normal_buffer = CREATE_TEXTURE( "*normal_rt", glState.width, glState.height, NULL, TF_RT_NORMAL|TF_HAS_ALPHA ); + smooth_buffer = CREATE_TEXTURE( "*smooth_rt", glState.width, glState.height, NULL, TF_RT_COLOR|TF_HAS_ALPHA ); + light0_buffer = CREATE_TEXTURE( "*light0_rt", glState.width, glState.height, NULL, TF_RT_COLOR|TF_HAS_ALPHA ); + light1_buffer = CREATE_TEXTURE( "*light1_rt", glState.width, glState.height, NULL, TF_RT_COLOR|TF_HAS_ALPHA ); + depth_buffer = CREATE_TEXTURE( "*depth_rt", glState.width, glState.height, NULL, TF_RT_DEPTH ); + + GL_AttachColorTextureToFBO( tr.defscene_fbo, albedo_buffer, 0 ); + GL_AttachColorTextureToFBO( tr.defscene_fbo, normal_buffer, 1 ); + GL_AttachColorTextureToFBO( tr.defscene_fbo, smooth_buffer, 2 ); + GL_AttachColorTextureToFBO( tr.defscene_fbo, light0_buffer, 3 ); + GL_AttachColorTextureToFBO( tr.defscene_fbo, light1_buffer, 4 ); + GL_AttachDepthTextureToFBO( tr.defscene_fbo, depth_buffer ); + + pglDrawBuffersARB( ARRAYSIZE( MRTBuffers ), MRTBuffers ); + pglReadBuffer( GL_NONE ); + + // check the framebuffer status + GL_CheckFBOStatus( tr.defscene_fbo ); +} + +void GL_VidInitDefSceneFBO( void ) +{ + // resize attached textures + GL_ResizeDrawbuffer( tr.defscene_fbo, glState.width, glState.height, 1 ); + GL_AttachColorTextureToFBO( tr.defscene_fbo, tr.defscene_fbo->colortarget[0], 0 ); + GL_AttachColorTextureToFBO( tr.defscene_fbo, tr.defscene_fbo->colortarget[1], 1 ); + GL_AttachColorTextureToFBO( tr.defscene_fbo, tr.defscene_fbo->colortarget[2], 2 ); + GL_AttachColorTextureToFBO( tr.defscene_fbo, tr.defscene_fbo->colortarget[3], 3 ); + GL_AttachColorTextureToFBO( tr.defscene_fbo, tr.defscene_fbo->colortarget[4], 4 ); + GL_AttachDepthTextureToFBO( tr.defscene_fbo, tr.defscene_fbo->depthtarget ); +} + +void GL_InitDefLightFBO( void ) +{ + GLenum MRTBuffers[3] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_COLOR_ATTACHMENT2_EXT }; + int normal_buffer; + int light0_buffer; + int light1_buffer; + int depth_buffer; + + if( !GL_Support( R_FRAMEBUFFER_OBJECT ) || !GL_Support( R_DRAW_BUFFERS_EXT )) + return; + + // Create and set up the framebuffer + tr.deflight_fbo = GL_AllocDrawbuffer( "*deflight", glState.defWidth, glState.defHeight, 1 ); + + // create textures + normal_buffer = CREATE_TEXTURE( "*normal_rs", glState.defWidth, glState.defHeight, NULL, TF_RT_NORMAL|TF_HAS_ALPHA ); + light0_buffer = CREATE_TEXTURE( "*light0_rs", glState.defWidth, glState.defHeight, NULL, TF_RT_COLOR|TF_HAS_ALPHA ); + light1_buffer = CREATE_TEXTURE( "*light1_rs", glState.defWidth, glState.defHeight, NULL, TF_RT_COLOR|TF_HAS_ALPHA ); + depth_buffer = CREATE_TEXTURE( "*depth_rs", glState.defWidth, glState.defHeight, NULL, TF_RT_DEPTH ); + + GL_AttachColorTextureToFBO( tr.deflight_fbo, normal_buffer, 0 ); + GL_AttachColorTextureToFBO( tr.deflight_fbo, light0_buffer, 1 ); + GL_AttachColorTextureToFBO( tr.deflight_fbo, light1_buffer, 2 ); + GL_AttachDepthTextureToFBO( tr.deflight_fbo, depth_buffer ); + + pglDrawBuffersARB( ARRAYSIZE( MRTBuffers ), MRTBuffers ); + pglReadBuffer( GL_NONE ); + + // check the framebuffer status + GL_CheckFBOStatus( tr.deflight_fbo ); +} + +void GL_VidInitDefLightFBO( void ) +{ + // resize attached textures + GL_ResizeDrawbuffer( tr.deflight_fbo, glState.defWidth, glState.defHeight, 1 ); + GL_AttachColorTextureToFBO( tr.deflight_fbo, tr.deflight_fbo->colortarget[0], 0 ); + GL_AttachColorTextureToFBO( tr.deflight_fbo, tr.deflight_fbo->colortarget[1], 1 ); + GL_AttachColorTextureToFBO( tr.deflight_fbo, tr.deflight_fbo->colortarget[2], 2 ); + GL_AttachDepthTextureToFBO( tr.deflight_fbo, tr.deflight_fbo->depthtarget ); +} + +void GL_VidInitDrawBuffers( void ) +{ + if( tr.defscene_fbo == NULL ) + GL_InitDefSceneFBO(); + else GL_VidInitDefSceneFBO(); + + if( tr.deflight_fbo == NULL ) + GL_InitDefLightFBO(); + else GL_VidInitDefLightFBO(); + + // unbind the framebuffer + GL_BindDrawbuffer( NULL ); +} + +void GL_SetupGBuffer( void ) +{ + // bind geometry fullscreen buffer + if( FBitSet( RI->params, RP_DEFERREDSCENE )) + GL_BindDrawbuffer( tr.defscene_fbo ); + + // bind light-pass downscaled buffer + if( FBitSet( RI->params, RP_DEFERREDLIGHT )) + GL_BindDrawbuffer( tr.deflight_fbo ); + + R_Clear( ~0 ); +} + +void GL_ResetGBuffer( void ) +{ + GL_BindDrawbuffer( NULL ); +} + +void GL_DrawScreenSpaceQuad( const vec3_t normals[4] ) +{ + pglBegin( GL_QUADS ); + pglTexCoord2f( 0.0f, 1.0f ); + pglNormal3fv( normals[0] ); + pglVertex2f( 0.0f, 0.0f ); + pglTexCoord2f( 1.0f, 1.0f ); + pglNormal3fv( normals[3] ); + pglVertex2f( RI->view.port[2], 0.0f ); + pglTexCoord2f( 1.0f, 0.0f ); + pglNormal3fv( normals[2] ); + pglVertex2f( RI->view.port[2], RI->view.port[3] ); + pglTexCoord2f( 0.0f, 0.0f ); + pglNormal3fv( normals[1] ); + pglVertex2f( 0.0f, RI->view.port[3] ); + pglEnd(); +} + +/* +================= +GL_Setup2D +================= +*/ +void GL_SetupDeferred( void ) +{ + // set up full screen workspace + pglMatrixMode( GL_PROJECTION ); + pglLoadIdentity(); + + pglOrtho( 0, RI->view.port[2], RI->view.port[3], 0, -99999, 99999 ); + + pglMatrixMode( GL_MODELVIEW ); + pglLoadIdentity(); + + GL_AlphaTest( GL_FALSE ); + GL_DepthMask( GL_FALSE ); + pglDisable( GL_DEPTH_TEST ); // older dirvers has issues with this + + if( RI->currentlight != NULL ) + { + GL_Blend( GL_TRUE ); + pglBlendFunc( GL_ONE, GL_ONE ); + } + else + { + GL_Blend( GL_FALSE ); + } + + GL_Cull( GL_FRONT ); + pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + pglViewport( 0, 0, RI->view.port[2], RI->view.port[3] ); +} + +static void GL_DrawDeferred( word hProgram, int pass ) +{ + if( hProgram <= 0 ) + { + GL_BindShader( NULL ); + return; // bad shader? + } + + // prepare deferred pass + GL_SetupDeferred(); + + if( RI->currentshader != &glsl_programs[hProgram] ) + { + // force to bind new shader + GL_BindShader( &glsl_programs[hProgram] ); + } + + glsl_program_t *shader = RI->currentshader; + CDynLight *pl = RI->currentlight; // may be NULL + Vector4D lightdir; + float *v; + + // setup specified uniforms (and texture bindings) + for( int i = 0; i < shader->numUniforms; i++ ) + { + uniform_t *u = &shader->uniforms[i]; + + switch( u->type ) + { + case UT_COLORMAP: + u->SetValue( tr.defscene_fbo->colortarget[0] ); + break; + case UT_NORMALMAP: + if( pass == LIGHT_PASS ) + u->SetValue( tr.deflight_fbo->colortarget[0] ); + else u->SetValue( tr.defscene_fbo->colortarget[1] ); + break; + case UT_GLOSSMAP: + u->SetValue( tr.defscene_fbo->colortarget[2] ); + break; + case UT_VISLIGHTMAP0: + if( pass == LIGHT_PASS ) + u->SetValue( tr.deflight_fbo->colortarget[1] ); + else u->SetValue( tr.defscene_fbo->colortarget[3] ); + break; + case UT_VISLIGHTMAP1: + if( pass == LIGHT_PASS ) + u->SetValue( tr.deflight_fbo->colortarget[2] ); + else u->SetValue( tr.defscene_fbo->colortarget[4] ); + break; + case UT_DEPTHMAP: + if( pass == LIGHT_PASS ) + u->SetValue( tr.deflight_fbo->depthtarget ); + else u->SetValue( tr.defscene_fbo->depthtarget ); + break; + case UT_BSPPLANESMAP: + u->SetValue( tr.packed_planes_texture ); + break; + case UT_BSPNODESMAP: + u->SetValue( tr.packed_nodes_texture ); + break; + case UT_BSPLIGHTSMAP: + u->SetValue( tr.packed_lights_texture ); + break; + case UT_BSPMODELSMAP: + u->SetValue( tr.packed_models_texture ); + break; + case UT_SHADOWMAP: + if( pl ) u->SetValue( pl->shadowTexture[0] ); + else u->SetValue( tr.fbo_light.GetTexture() ); + break; + case UT_SHADOWMAP0: + if( pl ) u->SetValue( pl->shadowTexture[0] ); + else u->SetValue( tr.depthTexture ); + break; + case UT_SHADOWMAP1: + if( pl ) u->SetValue( pl->shadowTexture[1] ); + else u->SetValue( tr.depthTexture ); + break; + case UT_SHADOWMAP2: + if( pl ) u->SetValue( pl->shadowTexture[2] ); + else u->SetValue( tr.depthTexture ); + break; + case UT_SHADOWMAP3: + if( pl ) u->SetValue( pl->shadowTexture[3] ); + else u->SetValue( tr.depthTexture ); + break; + case UT_PROJECTMAP: + if( pl && pl->type == LIGHT_SPOT ) + u->SetValue( pl->spotlightTexture ); + else u->SetValue( tr.whiteTexture ); + break; + case UT_VIEWORIGIN: + u->SetValue( RI->view.matrix[3][0], RI->view.matrix[3][1], RI->view.matrix[3][2] ); + break; + case UT_LIGHTSTYLEVALUES: + u->SetValue( &tr.lightstyle[0], MAX_LIGHTSTYLES ); + break; + case UT_GAMMATABLE: + u->SetValue( &tr.gamma_table[0][0], 64 ); + break; + case UT_LIGHTTHRESHOLD: + u->SetValue( tr.light_threshold ); + break; + case UT_LIGHTSCALE: + u->SetValue( tr.direct_scale ); + break; + case UT_LIGHTGAMMA: + u->SetValue( tr.light_gamma ); + break; + case UT_ZFAR: + u->SetValue( RI->view.farClip ); + break; + case UT_SCREENSIZEINV: + u->SetValue( 1.0f / (float)glState.width, 1.0f / (float)glState.height ); + break; + case UT_DIFFUSEFACTOR: + u->SetValue( tr.diffuseFactor ); + break; + case UT_AMBIENTFACTOR: + if( pl && pl->type == LIGHT_DIRECTIONAL ) + u->SetValue( tr.sun_ambient ); + else u->SetValue( tr.ambientFactor ); + break; + case UT_SUNREFRACT: + u->SetValue( tr.sun_refract ); + break; + case UT_REALTIME: + u->SetValue( (float)tr.time ); + break; + case UT_FOGPARAMS: + u->SetValue( tr.fogColor[0], tr.fogColor[1], tr.fogColor[2], tr.fogDensity ); + break; + case UT_SHADOWPARMS: + if( pl != NULL ) + { + float shadowWidth = 1.0f / (float)RENDER_GET_PARM( PARM_TEX_WIDTH, pl->shadowTexture[0] ); + float shadowHeight = 1.0f / (float)RENDER_GET_PARM( PARM_TEX_HEIGHT, pl->shadowTexture[0] ); + // depth scale and bias and shadowmap resolution + u->SetValue( shadowWidth, shadowHeight, -pl->projectionMatrix[2][2], pl->projectionMatrix[3][2] ); + } + else u->SetValue( 0.0f, 0.0f, 0.0f, 0.0f ); + break; + case UT_SHADOWMATRIX: + if( pl ) u->SetValue( &pl->gl_shadowMatrix[0][0], MAX_SHADOWMAPS ); + break; + case UT_SHADOWSPLITDIST: + v = RI->view.parallelSplitDistances; + u->SetValue( v[0], v[1], v[2], v[3] ); + break; + case UT_TEXELSIZE: + u->SetValue( 1.0f / (float)sunSize[0], 1.0f / (float)sunSize[1], 1.0f / (float)sunSize[2], 1.0f / (float)sunSize[3] ); + break; + case UT_LIGHTDIR: + if( pl ) + { + if( pl->type == LIGHT_DIRECTIONAL ) lightdir = -tr.sky_normal; + else lightdir = pl->frustum.GetPlane( FRUSTUM_FAR )->normal; + u->SetValue( lightdir.x, lightdir.y, lightdir.z, pl->fov ); + } + break; + case UT_LIGHTDIFFUSE: + if( pl ) u->SetValue( pl->color.x, pl->color.y, pl->color.z ); + break; + case UT_LIGHTORIGIN: + if( pl ) u->SetValue( pl->origin.x, pl->origin.y, pl->origin.z, ( 1.0f / pl->radius )); + break; + case UT_LIGHTVIEWPROJMATRIX: + if( pl ) + { + GLfloat gl_lightViewProjMatrix[16]; + pl->lightviewProjMatrix.CopyToArray( gl_lightViewProjMatrix ); + u->SetValue( &gl_lightViewProjMatrix[0] ); + } + break; + case UT_NUMVISIBLEMODELS: + u->SetValue( world->num_visible_models ); + break; + default: + ALERT( at_error, "%s: unhandled uniform %s\n", RI->currentshader->name, u->name ); + break; + } + } + + GL_DrawScreenSpaceQuad( tr.screen_normals ); +} + +static void GL_DrawBilateralFilter( word hProgram, int pass ) +{ + if( hProgram <= 0 ) + { + GL_BindShader( NULL ); + return; // bad shader? + } + + if( RI->currentshader != &glsl_programs[hProgram] ) + { + // force to bind new shader + GL_BindShader( &glsl_programs[hProgram] ); + } + + glsl_program_t *shader = RI->currentshader; + + // setup specified uniforms (and texture bindings) + for( int i = 0; i < shader->numUniforms; i++ ) + { + uniform_t *u = &shader->uniforms[i]; + + switch( u->type ) + { + case UT_COLORMAP: + if( pass == BILATERAL_PASS0 ) + u->SetValue( tr.fbo_light.GetTexture() ); + else if( pass = BILATERAL_PASS1 ) + u->SetValue( tr.fbo_filter.GetTexture() ); + else u->SetValue( tr.defscene_fbo->colortarget[0] ); + break; + case UT_DEPTHMAP: + u->SetValue( tr.defscene_fbo->depthtarget ); + break; + case UT_ZFAR: + u->SetValue( RI->view.farClip ); + break; + case UT_SCREENSIZEINV: + if( pass == BILATERAL_PASS0 ) + u->SetValue( 1.0f / (float)glState.width, 0.0f ); + else if( pass = BILATERAL_PASS1 ) + u->SetValue( 0.0f, 1.0f / (float)glState.height ); + else u->SetValue( 1.0f / (float)glState.width, 1.0f / (float)glState.height ); + break; + case UT_SHADOWPARMS: + u->SetValue( RI->view.projectionMatrix[3][2], RI->view.projectionMatrix[2][2] ); + break; + default: + ALERT( at_error, "%s: unhandled uniform %s\n", RI->currentshader->name, u->name ); + break; + } + } + + RenderFSQ( glState.width, glState.height ); +} + +void GL_SetupWorldLightPass( void ) +{ + tr.fbo_shadow.Bind(); // <- render to shadow texture + + // FIXME! modelview isn't be changed anyway until current pass!!! +// pglMatrixMode( GL_MODELVIEW ); +// pglLoadMatrixf( RI->glstate.modelviewMatrix ); + + GL_DrawDeferred( tr.defLightShader, LIGHT_PASS ); + GL_CleanupAllTextureUnits(); + + GL_Setup2D(); + tr.fbo_light.Bind(); // <- copy to fbo_light result of works complex light shader + GL_BindShader( NULL ); + GL_BindTexture( GL_TEXTURE0, tr.fbo_shadow.GetTexture() ); + RenderFSQ( glState.width, glState.height ); + + if( tr.bilateralShader ) + { + // apply bilateral filter + tr.fbo_filter.Bind(); // <- bilateral pass0, filtering by width, store to fbo_filter + GL_DrawBilateralFilter( tr.bilateralShader, BILATERAL_PASS0 ); + + tr.fbo_light.Bind(); // <- bilateral pass1, filtering by height, store back to fbo_light + GL_DrawBilateralFilter( tr.bilateralShader, BILATERAL_PASS1 ); + } +} + +void GL_SetupDynamicPass( void ) +{ + if( !FBitSet( RI->view.flags, RF_HASDYNLIGHTS )) + return; + + pglEnable( GL_SCISSOR_TEST ); + CDynLight *pl = tr.dlights; + + for( int i = 0; i < MAX_DLIGHTS; i++, pl++ ) + { + // deferred path are ignored sunlights + if( pl->Expired( ) || pl->type == LIGHT_DIRECTIONAL ) + continue; + + if( !pl->Active( )) continue; + + if( !Mod_CheckBoxVisible( pl->absmin, pl->absmax )) + continue; + + if( R_CullFrustum( &pl->frustum )) + continue; + + float y2 = (float)RI->view.port[3] - pl->h - pl->y; + pglScissor( pl->x, y2, pl->w, pl->h ); + RI->currentlight = pl; + + GL_DrawDeferred( tr.defDynLightShader[pl->type], SCENE_PASS ); + } + + pglDisable( GL_SCISSOR_TEST ); + RI->currentlight = NULL; +} + +void GL_SetupWorldScenePass( void ) +{ + mworldlight_t *wl; + int i; + + // debug + for( i = 0, wl = world->worldlights; i < world->numworldlights; i++, wl++ ) + { + if( wl->emittype == emit_ignored ) + continue; + + if( !CHECKVISBIT( RI->view.vislight, i )) + continue; + r_stats.c_worldlights++; + } + + int type = CVAR_TO_BOOL( cv_deferred_full ) ? 1 : 0; + GL_DrawDeferred( tr.defSceneShader[type], SCENE_PASS ); + + // also render a standard dynamic lights + GL_SetupDynamicPass(); +} + +void GL_DrawDeferredPass( void ) +{ + if( !tr.defSceneShader || !tr.defLightShader ) + return; // oops! + + GL_ComputeScreenRays(); + + if( FBitSet( RI->params, RP_DEFERREDSCENE )) + GL_SetupWorldScenePass(); + + if( FBitSet( RI->params, RP_DEFERREDLIGHT )) + GL_SetupWorldLightPass(); + + GL_CleanupDrawState(); + GL_BindFBO( FBO_MAIN ); + GL_Setup3D(); +} \ No newline at end of file diff --git a/cl_dll/render/gl_dlight.cpp b/cl_dll/render/gl_dlight.cpp new file mode 100644 index 0000000..6d94968 --- /dev/null +++ b/cl_dll/render/gl_dlight.cpp @@ -0,0 +1,779 @@ +/* +gl_dlight.cpp - dynamic lighting +Copyright (C) 2014 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include +#include "cl_util.h" +#include "pm_defs.h" +#include "event_api.h" +#include "gl_local.h" +#include "gl_studio.h" +#include "gl_world.h" +#include "gl_grass.h" + +/* +============================================================================= + +PROJECTED LIGHTS + +============================================================================= +*/ +/* +=============== +CL_ClearDlights +=============== +*/ +void CL_ClearDlights( void ) +{ + memset( tr.dlights, 0, sizeof( tr.dlights )); +} + +/* +=============== +CL_AllocDlight +=============== +*/ +CDynLight *CL_AllocDlight( int key ) +{ + CDynLight *dl; + + // first look for an exact key match + if( key ) + { + dl = tr.dlights; + for( int i = 0; i < MAX_DLIGHTS; i++, dl++ ) + { + if( dl->key == key ) + { + // reuse this light + return dl; + } + } + } + + float time = GET_CLIENT_TIME(); + + // then look for anything else + dl = tr.dlights; + for( int i = 0; i < MAX_DLIGHTS; i++, dl++ ) + { + if( dl->die < time && dl->key == 0 ) + { + memset( dl, 0, sizeof( *dl )); + dl->key = key; + return dl; + } + } + + dl = &tr.dlights[0]; + memset( dl, 0, sizeof( *dl )); + dl->key = key; + + return dl; +} + +/* +=============== +CL_DecayLights + +=============== +*/ +void CL_DecayLights( void ) +{ + float time = GET_CLIENT_TIME(); + CDynLight *pl = tr.dlights; + + for( int i = 0; i < MAX_DLIGHTS; i++, pl++ ) + { + if( !pl->radius ) continue; + + pl->radius -= tr.frametime * pl->decay; + if( pl->radius < 0.0f ) pl->radius = 0.0f; + + if( pl->Expired( )) + memset( pl, 0, sizeof( *pl )); + } +} + +//===================================== +// HasDynamicLights +// +// Return count dynamic lights for current frame +//===================================== +int HasDynamicLights( void ) +{ + int numDlights = 0; + + if( !worldmodel->lightdata || !CVAR_TO_BOOL( cv_dynamiclight )) + return numDlights; + + for( int i = 0; i < MAX_DLIGHTS; i++ ) + { + CDynLight *pl = &tr.dlights[i]; + if( pl->Expired( )) continue; + numDlights++; + } + + return numDlights; +} + +//===================================== +// HasStaticLights +// +// Return count static lights for current frame +//===================================== +int HasStaticLights( void ) +{ + int i, numLights = 0; + mworldlight_t *wl; + + for( i = 0, wl = world->worldlights; i < world->numworldlights; i++, wl++ ) + { + if( wl->emittype == emit_ignored ) + continue; + + if( !CHECKVISBIT( RI->view.vislight, i )) + continue; + numLights++; + } + + return numLights; +} + +/* +============================================================================= + +DYNAMIC LIGHTS + +============================================================================= +*/ +/* +================== +R_AnimateLight + +================== +*/ +void R_AnimateLight( void ) +{ + int i, k, flight, clight; + float l, lerpfrac, backlerp; + float scale = 1.0f; + lightstyle_t *ls; + + scale = tr.diffuseFactor; + + if( tr.realframecount != 1 && !CVAR_TO_BOOL( r_lightstyles )) + return; + + // light animations + // 'm' is normal light, 'a' is no light, 'z' is double bright + for( i = 0; i < MAX_LIGHTSTYLES; i++ ) + { + ls = GET_LIGHTSTYLE( i ); + + if( !worldmodel->lightdata ) + { + tr.lightstyle[i] = 256.0f * scale; + continue; + } + + flight = (int)Q_floor( ls->time * 10.0f ); + clight = (int)Q_ceil( ls->time * 10.0f ); + lerpfrac = ( ls->time * 10 ) - flight; + backlerp = 1.0f - lerpfrac; + + if( !ls->length ) + { + tr.lightstyle[i] = 256.0f * scale; + continue; + } + else if( ls->length == 1 ) + { + float value = ls->map[0]; + + // turn off the baked sunlight + if( tr.sun_light_enabled && i == LS_SKY ) + value = (float)('a' - 'a'); + + // single length style so don't bother interpolating + tr.lightstyle[i] = value * 22.0f * scale; + continue; + } + else if( !ls->interp || !CVAR_TO_BOOL( r_lightstyle_lerping )) + { + tr.lightstyle[i] = ls->map[flight%ls->length] * 22.0f * scale; + continue; + } + + // interpolate animating light + // frame just gone + k = ls->map[flight % ls->length]; + l = (float)( k * 22.0f ) * backlerp; + + // upcoming frame + k = ls->map[clight % ls->length]; + l += (float)( k * 22.0f ) * lerpfrac; + + tr.lightstyle[i] = l * scale; + } +} + +/* +======================================================================= + + GATHER DYNAMIC LIGHTING + +======================================================================= +*/ +/* +================= +R_LightsForPoint +================= +*/ +Vector R_LightsForPoint( const Vector &point, float radius ) +{ + Vector lightColor; + + if( radius <= 0.0f ) + radius = 1.0f; + + lightColor = g_vecZero; + + for( int lnum = 0; lnum < MAX_DLIGHTS; lnum++ ) + { + CDynLight *dl = &tr.dlights[lnum]; + float atten = 1.0f; + + if( dl->type == LIGHT_DIRECTIONAL ) + continue; + + if( dl->die < GET_CLIENT_TIME() || !dl->radius ) + continue; + + Vector dir = (dl->origin - point); + float dist = dir.Length(); + + if( !dist || dist > ( dl->radius + radius )) + continue; + + if( dl->frustum.CullSphere( point, radius )) + continue; + + atten = 1.0 - saturate( pow( dist * ( 1.0f / dl->radius ), 2.2f )); + if( atten <= 0.0 ) continue; // fast reject + + if( dl->type == LIGHT_SPOT ) + { + Vector lightDir = dl->frustum.GetPlane( FRUSTUM_FAR )->normal.Normalize(); + float fov_x, fov_y; + + // BUGBUG: we use 5:4 aspect not an 4:3 + if( dl->flags & DLF_ASPECT3X4 ) + fov_y = dl->fov * (5.0f / 4.0f); + else if( dl->flags & DLF_ASPECT4X3 ) + fov_y = dl->fov * (4.0f / 5.0f); + else fov_y = dl->fov; + fov_x = dl->fov; + + // spot attenuation + float spotDot = DotProduct( dir.Normalize(), lightDir ); + fov_x = DEG2RAD( fov_x * 0.25f ); + fov_y = DEG2RAD( fov_y * 0.25f ); + float spotCos = cos( fov_x + fov_y ); + if( spotDot < spotCos ) continue; + } + + lightColor += (dl->color * 0.5f * atten); + } + + return lightColor; +} + +/* +================ +R_GetLightVectors + +Get light vectors for entity +================ +*/ +void R_GetLightVectors( cl_entity_t *pEnt, Vector &origin, Vector &angles ) +{ + // fill default case + origin = pEnt->origin; + angles = pEnt->angles; + + // try to grab position from attachment + if( pEnt->curstate.aiment > 0 && pEnt->curstate.movetype == MOVETYPE_FOLLOW ) + { + cl_entity_t *pParent = GET_ENTITY( pEnt->curstate.aiment ); + studiohdr_t *pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( pParent->model ); + + if( pParent && pParent->model && pStudioHeader != NULL ) + { + // make sure what model really has attachements + if( pEnt->curstate.body > 0 && ( pStudioHeader && pStudioHeader->numattachments > 0 )) + { + int num = bound( 1, pEnt->curstate.body, MAXSTUDIOATTACHMENTS ); + R_StudioAttachmentPosAng( pParent, num - 1, &origin, &angles ); + angles[PITCH] = -angles[PITCH]; // stupid quake bug + } + else if( pParent->curstate.movetype == MOVETYPE_STEP ) + { + origin = pParent->origin; + angles = pParent->angles; + + // add the eye position for monster + if( pParent->curstate.eflags & EFLAG_SLERP ) + origin += pStudioHeader->eyeposition; + } + else + { + origin = pParent->origin; + angles = pParent->angles; + } + } + } + // all other parent types will be attached on the server +} + +/* +================ +R_SetupLightTexture + +Must be called after R_SetupLightParams +================ +*/ +void R_SetupLightTexture( CDynLight *pl, int texture ) +{ + ASSERT( pl != NULL ); + + pl->spotlightTexture = texture; +} + +/* +================ +R_SetupLightParams +================ +*/ +void R_SetupLightParams( CDynLight *pl, const Vector &origin, const Vector &angles, float radius, float fov, int type, int flags ) +{ + pl->type = bound( LIGHT_SPOT, type, LIGHT_DIRECTIONAL ); + + if( pl->type == LIGHT_OMNI ) + { + for( int i = 0; i < MAX_SHADOWMAPS; i++ ) + pl->shadowTexture[i] = tr.depthCubemap; // stub + + if( !GL_Support( R_TEXTURECUBEMAP_EXT )) + flags |= DLF_NOSHADOWS; + } + else + { + for( int i = 0; i < MAX_SHADOWMAPS; i++ ) + pl->shadowTexture[i] = tr.depthTexture; // stub + } + + if( pl->origin != origin || pl->angles != angles || pl->fov != fov || pl->radius != radius || pl->flags != flags ) + { + pl->origin = origin; + pl->angles = angles; + pl->radius = radius; + pl->flags = flags; + pl->update = true; + pl->fov = fov; + } +} + +/* +================ +R_SetupLightProjection + +General setup light projections. +Calling only once per frame +================ +*/ +void R_SetupLightProjection( CDynLight *pl ) +{ + if( !pl->update ) + { + if( pl->type != LIGHT_DIRECTIONAL ) + { + if( !R_ScissorForFrustum( &pl->frustum, &pl->x, &pl->y, &pl->w, &pl->h )) + SetBits( pl->flags, DLF_CULLED ); + else ClearBits( pl->flags, DLF_CULLED ); + } + return; + } + + if( pl->type == LIGHT_SPOT ) + { + float fov_x, fov_y; + + // BUGBUG: we use 5:4 aspect not an 4:3 + if( pl->flags & DLF_ASPECT3X4 ) + fov_y = pl->fov * (5.0f / 4.0f); + else if( pl->flags & DLF_ASPECT4X3 ) + fov_y = pl->fov * (4.0f / 5.0f); + else fov_y = pl->fov; + + // e.g. for fake cinema projectors + if( FBitSet( pl->flags, DLF_FLIPTEXTURE )) + fov_x = -pl->fov; + else fov_x = pl->fov; + + pl->projectionMatrix.CreateProjection( fov_x, fov_y, Z_NEAR_LIGHT, pl->radius ); + pl->modelviewMatrix.CreateModelview(); // init quake world orientation + pl->viewMatrix = matrix4x4( pl->origin, pl->angles ); + + // transform projector by position and angles + pl->modelviewMatrix.ConcatRotate( -pl->angles.z, 1, 0, 0 ); + pl->modelviewMatrix.ConcatRotate( -pl->angles.x, 0, 1, 0 ); + pl->modelviewMatrix.ConcatRotate( -pl->angles.y, 0, 0, 1 ); + pl->modelviewMatrix.ConcatTranslate( -pl->origin.x, -pl->origin.y, -pl->origin.z ); + + pl->frustum.InitProjection( pl->viewMatrix, Z_NEAR_LIGHT, pl->radius, pl->fov, fov_y ); + pl->frustum.ComputeFrustumBounds( pl->absmin, pl->absmax ); + pl->frustum.DisablePlane( FRUSTUM_FAR ); // only use plane.normal + } + else if( pl->type == LIGHT_OMNI ) + { + pl->modelviewMatrix.CreateModelview(); + pl->viewMatrix = matrix4x4( pl->origin, g_vecZero ); + pl->projectionMatrix.CreateProjection( 90.0f, 90.0f, Z_NEAR_LIGHT, pl->radius ); + + // transform omnilight by position + pl->modelviewMatrix.ConcatTranslate( -pl->origin.x, -pl->origin.y, -pl->origin.z ); + + pl->frustum.InitBoxFrustum( pl->origin, pl->radius ); + pl->frustum.ComputeFrustumBounds( pl->absmin, pl->absmax ); + } + else if( pl->type == LIGHT_DIRECTIONAL ) + { + Vector skyDir, angles, up; + + skyDir = tr.sky_normal.Normalize(); + VectorAngles2( skyDir, angles ); + AngleVectors( angles, NULL, NULL, up ); + + pl->viewMatrix.LookAt( tr.cached_vieworigin, skyDir, up ); + pl->modelviewMatrix = pl->viewMatrix; + + ClearBounds( pl->absmin, pl->absmax ); + } + else + { + HOST_ERROR( "R_SetupLightProjection: unknown light type %i\n", pl->type ); + } + + if( pl->type != LIGHT_DIRECTIONAL ) + { + matrix4x4 projectionView, s1; + + projectionView = pl->projectionMatrix.Concat( pl->modelviewMatrix ); + pl->lightviewProjMatrix = projectionView; + + s1.CreateTranslate( 0.5f, 0.5f, 0.5f ); + s1.ConcatScale( 0.5f, 0.5f, 0.5f ); + + pl->textureMatrix[0] = projectionView; + pl->shadowMatrix[0] = s1.Concat( projectionView ); + + if( !R_ScissorForFrustum( &pl->frustum, &pl->x, &pl->y, &pl->w, &pl->h )) + SetBits( pl->flags, DLF_CULLED ); + else ClearBits( pl->flags, DLF_CULLED ); + } + + pl->update = false; +} + +/* +================ +R_SetupDynamicLights +================ +*/ +void R_SetupDynamicLights( void ) +{ + CDynLight *pl; + dlight_t *dl; + + for( int lnum = 0; lnum < MAX_ENGINE_DLIGHTS; lnum++ ) + { + dl = GET_DYNAMIC_LIGHT( lnum ); + pl = &tr.dlights[MAX_ENGINE_DLIGHTS+lnum]; + + // NOTE: here we copies dlight settings 'as-is' + // without reallocating by key because key may + // be set indirectly without call CL_AllocDlight + if( dl->die < GET_CLIENT_TIME() || !dl->radius ) + { + // light is expired. Clear it + memset( pl, 0, sizeof( *pl )); + continue; + } + + pl->key = dl->key; + pl->die = dl->die + tr.frametime; + pl->decay = dl->decay; + pl->color.x = dl->color.r * (1.0 / 255.0f); + pl->color.y = dl->color.g * (1.0 / 255.0f); + pl->color.z = dl->color.b * (1.0 / 255.0f); + pl->origin = dl->origin; + pl->radius = dl->radius; + pl->type = LIGHT_OMNI; + pl->update = true; + + R_SetupLightProjection( pl ); + } + + pl = tr.dlights; + + for( int i = 0; i < MAX_DLIGHTS; i++, pl++ ) + { + if( pl->Expired( )) continue; + R_SetupLightProjection( pl ); + } +} + +/* +======================================================================= + + WORLDLIGHTS PROCESSING + +======================================================================= +*/ +static vec_t LightDistanceFalloff( const mworldlight_t *wl, const Vector &direction ) +{ + Vector delta = direction; + float dist = VectorNormalize( delta ); + float dot = 1.0f; // assume dot is 1.0f + + dist = Q_max( dist, 1.0 ); + + switch( wl->emittype ) + { + case emit_surface: + return dot / (dist * dist); + case emit_skylight: + return dot; + break; + case emit_spotlight: // directional & positional + case emit_point: + // cull out stuff that's too far + if( dist > wl->radius ) + return 0.0f; + return dot / (dist * dist); + break; + } + + return 1.0f; +} + +//----------------------------------------------------------------------------- +// This method returns the effective intensity of a light as seen from +// a particular point. PVS is used to speed up the task. +//----------------------------------------------------------------------------- +static float LightIntensityAtPoint( mworldlight_t *wl, const Vector &mid, bool skipZ ) +{ + lightzbuffer_t *pZBuf = world->shadowzbuffers; + Vector direction; + + // special case lights + if( wl->emittype == emit_skylight ) + { + // check to see if you can hit the sky texture + Vector end = mid + wl->normal * -BOGUS_RANGE; // max_range * sqrt(3) + msurface_t *surf = gEngfuncs.pEventAPI->EV_TraceSurface( 0, (float *)&mid, (float *)end ); + + // here, we didn't hit the sky, so we must be in shadow + if( !surf || !FBitSet( surf->flags, SURF_DRAWSKY )) + return 0.0f; + return 1.0f; + } + + // all other lights + + // check distance + direction = wl->origin - mid; + float ratio = LightDistanceFalloff( wl, direction ); + + // Early out for really low-intensity lights + // That way we don't need to ray-cast or normalize + float intensity = VectorMax( wl->intensity ); + + if( intensity * ratio < 4e-3 ) + return 0.0f; + + if( skipZ ) return ratio; + float dist = VectorNormalize( direction ); + + float flTraceDistance = dist; + + // check if we are so close to the light that we shouldn't use our coarse z buf + if( dist < 8 * SHADOW_ZBUF_RES ) + pZBuf = NULL; + + LightShadowZBufferSample_t *pSample = NULL; + Vector epnt = mid; + + if( pZBuf ) + { + pSample = &( pZBuf->GetSample( direction )); + + if(( pSample->m_flHitDistance < pSample->m_flTraceDistance ) || ( pSample->m_flTraceDistance >= dist )) + { + // hit! + if( dist > pSample->m_flHitDistance + 8 ) // shadow hit + return 0; + return ratio; + } + + // cache miss + flTraceDistance = Q_max( 100.0f, 2.0f * dist ); // trace a little further for better caching + epnt += direction * ( dist - flTraceDistance ); + } + + pmtrace_t pm; + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PlayerTrace( wl->origin, epnt, PM_WORLD_ONLY, -1, &pm ); +// pm.fraction = 1.0f - pm.fraction; + + float flHitDistance = ( pm.startsolid ) ? FLT_EPSILON : ( pm.fraction ) * flTraceDistance; + + if( pSample ) + { + pSample->m_flTraceDistance = flTraceDistance; + pSample->m_flHitDistance = ( pm.fraction >= 1.0 ) ? 1.0e23 : flHitDistance; + } + + if( dist > flHitDistance + 8 ) + return 0.0f; + + return ratio; +} + +static float LightIntensityInBox( mworldlight_t *wl, const Vector &mid, const Vector &mins, const Vector &maxs, bool skipZ ) +{ + float sphereRadius; + float angle, sinAngle; + float dist, distSqr; + + // Choose the point closest on the box to the light to get max intensity + // within the box.... + switch( wl->emittype ) + { + case emit_spotlight: // directional & positional + sphereRadius = (maxs - mid).Length(); + // first do a sphere/sphere check + dist = (wl->origin - mid).Length(); + if( dist > (sphereRadius + wl->radius )) + return 0; + // PERFORMANCE: precalc this and store in the light? + angle = acos( wl->stopdot2 ); + sinAngle = sin( angle ); + if( !IsSphereIntersectingCone( mid, sphereRadius, wl->origin, wl->normal, sinAngle, wl->stopdot2 )) + return 0; + // NOTE: fall through to radius check in point case + case emit_point: + distSqr = CalcSqrDistanceToAABB( mins, maxs, wl->origin ); + if( distSqr > wl->radius * wl->radius ) + return 0; + break; + case emit_surface: // directional & positional, fixed cone size + sphereRadius = (maxs - mid).Length(); + // first do a sphere/sphere check + dist = (wl->origin - mid).Length(); + if( dist > ( sphereRadius + wl->radius )) + return 0; + // PERFORMANCE: precalc this and store in the light? + if( !IsSphereIntersectingCone( mid, sphereRadius, wl->origin, wl->normal, 1.0f, 0.0f )) + return 0; + break; + } + + return LightIntensityAtPoint( wl, mid, skipZ ); +} + +/* +================= +R_FindWorldLights + +search for lights that potentially can lit bbox +================= +*/ +void R_FindWorldLights( const Vector &origin, const Vector &mins, const Vector &maxs, byte lights[MAXDYNLIGHTS], bool skipZ ) +{ + mworldlight_t *wl = world->worldlights; + Vector absmin = origin + mins; + Vector absmax = origin + maxs; + vec2_t indexes[32]; + int count = 0; + + for( int i = 0; i < world->numworldlights; i++, wl++ ) + { + if( !Mod_BoxVisible( absmin, absmax, wl->pvs )) + continue; + + float ratio = LightIntensityInBox( wl, origin, mins, maxs, skipZ ); + + // no light contribution? + if( ratio <= 0.0f ) continue; + + Vector add = wl->intensity * ratio; + float illum = VectorMax( add ); +//Msg( "#%i, type %d, illum %.5f, intensity %g %g %g\n", i, wl->emittype, illum, wl->intensity[0], wl->intensity[1], wl->intensity[2] ); + if( illum <= 4e-3 ) + continue; + + if( count >= ARRAYSIZE( indexes ) / 2 ) + break; + + indexes[count][0] = i; + indexes[count][1] = illum; + count++; + } + + memset( lights, 255, sizeof( byte ) * MAXDYNLIGHTS ); +get_next_light: + float maxIllum = 0.0; + int ignored = -1; + int light = 255; + + for( i = 0; i < count; i++ ) + { + if( indexes[i][0] == -1.0f ) + continue; + + // skylight has a maximum priority + if( indexes[i][1] > maxIllum ) + { + maxIllum = indexes[i][1]; + light = indexes[i][0]; + ignored = i; + } + } + + if( ignored == -1 ) + return; + + for( i = 0; i < (int)cv_deferred_maxlights->value && lights[i] != 255; i++ ); + if( i < (int)cv_deferred_maxlights->value ) + lights[i] = light; // nearest light for surf + indexes[ignored][0] = -1; // this light is handled + +// if( count > (int)cv_deferred_maxlights->value && i == (int)cv_deferred_maxlights->value ) +// Msg( "skipped light %i intensity %g, type %d\n", light, maxIllum, world->worldlights[light].emittype ); + goto get_next_light; +} \ No newline at end of file diff --git a/cl_dll/render/gl_export.cpp b/cl_dll/render/gl_export.cpp new file mode 100644 index 0000000..acb38ae --- /dev/null +++ b/cl_dll/render/gl_export.cpp @@ -0,0 +1,1029 @@ +/* +gl_export.cpp - OpenGL dynamically linkage +Copyright (C) 2010 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#define EXTERN + +#include "hud.h" +#include "cl_util.h" +#include "gl_local.h" +#include +#include "gl_world.h" +#include "gl_decals.h" +#include "gl_studio.h" +#include "gl_grass.h" +#include "material.h" +#include "gl_occlusion.h" + +#define MAX_RESERVED_UNIFORMS 22 // while MAX_LIGHTSTYLES 64 +#define PROJ_SIZE 64 + +glState_t glState; +glConfig_t glConfig; + +static dllfunc_t opengl_110funcs[] = +{ +{ "glClearColor" , (void **)&pglClearColor }, +{ "glClear" , (void **)&pglClear }, +{ "glAlphaFunc" , (void **)&pglAlphaFunc }, +{ "glBlendFunc" , (void **)&pglBlendFunc }, +{ "glCullFace" , (void **)&pglCullFace }, +{ "glDrawBuffer" , (void **)&pglDrawBuffer }, +{ "glReadBuffer" , (void **)&pglReadBuffer }, +{ "glAccum" , (void **)&pglAccum }, +{ "glEnable" , (void **)&pglEnable }, +{ "glDisable" , (void **)&pglDisable }, +{ "glEnableClientState" , (void **)&pglEnableClientState }, +{ "glDisableClientState" , (void **)&pglDisableClientState }, +{ "glGetBooleanv" , (void **)&pglGetBooleanv }, +{ "glGetDoublev" , (void **)&pglGetDoublev }, +{ "glGetFloatv" , (void **)&pglGetFloatv }, +{ "glGetIntegerv" , (void **)&pglGetIntegerv }, +{ "glGetError" , (void **)&pglGetError }, +{ "glGetString" , (void **)&pglGetString }, +{ "glFinish" , (void **)&pglFinish }, +{ "glFlush" , (void **)&pglFlush }, +{ "glClearDepth" , (void **)&pglClearDepth }, +{ "glDepthFunc" , (void **)&pglDepthFunc }, +{ "glDepthMask" , (void **)&pglDepthMask }, +{ "glDepthRange" , (void **)&pglDepthRange }, +{ "glFrontFace" , (void **)&pglFrontFace }, +{ "glDrawElements" , (void **)&pglDrawElements }, +{ "glDrawArrays" , (void **)&pglDrawArrays }, +{ "glColorMask" , (void **)&pglColorMask }, +{ "glIndexPointer" , (void **)&pglIndexPointer }, +{ "glVertexPointer" , (void **)&pglVertexPointer }, +{ "glNormalPointer" , (void **)&pglNormalPointer }, +{ "glColorPointer" , (void **)&pglColorPointer }, +{ "glTexCoordPointer" , (void **)&pglTexCoordPointer }, +{ "glArrayElement" , (void **)&pglArrayElement }, +{ "glColor3f" , (void **)&pglColor3f }, +{ "glColor3fv" , (void **)&pglColor3fv }, +{ "glColor4f" , (void **)&pglColor4f }, +{ "glColor4fv" , (void **)&pglColor4fv }, +{ "glColor3ub" , (void **)&pglColor3ub }, +{ "glColor4ub" , (void **)&pglColor4ub }, +{ "glColor4ubv" , (void **)&pglColor4ubv }, +{ "glTexCoord1f" , (void **)&pglTexCoord1f }, +{ "glTexCoord2f" , (void **)&pglTexCoord2f }, +{ "glTexCoord3f" , (void **)&pglTexCoord3f }, +{ "glTexCoord4f" , (void **)&pglTexCoord4f }, +{ "glTexCoord1fv" , (void **)&pglTexCoord1fv }, +{ "glTexCoord2fv" , (void **)&pglTexCoord2fv }, +{ "glTexCoord3fv" , (void **)&pglTexCoord3fv }, +{ "glTexCoord4fv" , (void **)&pglTexCoord4fv }, +{ "glTexGenf" , (void **)&pglTexGenf }, +{ "glTexGenfv" , (void **)&pglTexGenfv }, +{ "glTexGeni" , (void **)&pglTexGeni }, +{ "glVertex2f" , (void **)&pglVertex2f }, +{ "glVertex3f" , (void **)&pglVertex3f }, +{ "glVertex3fv" , (void **)&pglVertex3fv }, +{ "glNormal3f" , (void **)&pglNormal3f }, +{ "glNormal3fv" , (void **)&pglNormal3fv }, +{ "glBegin" , (void **)&pglBegin }, +{ "glEnd" , (void **)&pglEnd }, +{ "glLineWidth" , (void**)&pglLineWidth }, +{ "glPointSize" , (void**)&pglPointSize }, +{ "glMatrixMode" , (void **)&pglMatrixMode }, +{ "glOrtho" , (void **)&pglOrtho }, +{ "glRasterPos2f" , (void **) &pglRasterPos2f }, +{ "glFrustum" , (void **)&pglFrustum }, +{ "glViewport" , (void **)&pglViewport }, +{ "glPushMatrix" , (void **)&pglPushMatrix }, +{ "glPopMatrix" , (void **)&pglPopMatrix }, +{ "glPushAttrib" , (void **)&pglPushAttrib }, +{ "glPopAttrib" , (void **)&pglPopAttrib }, +{ "glLoadIdentity" , (void **)&pglLoadIdentity }, +{ "glLoadMatrixd" , (void **)&pglLoadMatrixd }, +{ "glLoadMatrixf" , (void **)&pglLoadMatrixf }, +{ "glMultMatrixd" , (void **)&pglMultMatrixd }, +{ "glMultMatrixf" , (void **)&pglMultMatrixf }, +{ "glRotated" , (void **)&pglRotated }, +{ "glRotatef" , (void **)&pglRotatef }, +{ "glScaled" , (void **)&pglScaled }, +{ "glScalef" , (void **)&pglScalef }, +{ "glTranslated" , (void **)&pglTranslated }, +{ "glTranslatef" , (void **)&pglTranslatef }, +{ "glReadPixels" , (void **)&pglReadPixels }, +{ "glDrawPixels" , (void **)&pglDrawPixels }, +{ "glStencilFunc" , (void **)&pglStencilFunc }, +{ "glStencilMask" , (void **)&pglStencilMask }, +{ "glStencilOp" , (void **)&pglStencilOp }, +{ "glClearStencil" , (void **)&pglClearStencil }, +{ "glIsEnabled" , (void **)&pglIsEnabled }, +{ "glIsList" , (void **)&pglIsList }, +{ "glIsTexture" , (void **)&pglIsTexture }, +{ "glTexEnvf" , (void **)&pglTexEnvf }, +{ "glTexEnvfv" , (void **)&pglTexEnvfv }, +{ "glTexEnvi" , (void **)&pglTexEnvi }, +{ "glTexParameterf" , (void **)&pglTexParameterf }, +{ "glTexParameterfv" , (void **)&pglTexParameterfv }, +{ "glTexParameteri" , (void **)&pglTexParameteri }, +{ "glHint" , (void **)&pglHint }, +{ "glPixelStoref" , (void **)&pglPixelStoref }, +{ "glPixelStorei" , (void **)&pglPixelStorei }, +{ "glGenTextures" , (void **)&pglGenTextures }, +{ "glDeleteTextures" , (void **)&pglDeleteTextures }, +{ "glBindTexture" , (void **)&pglBindTexture }, +{ "glTexImage1D" , (void **)&pglTexImage1D }, +{ "glTexImage2D" , (void **)&pglTexImage2D }, +{ "glTexSubImage1D" , (void **)&pglTexSubImage1D }, +{ "glTexSubImage2D" , (void **)&pglTexSubImage2D }, +{ "glCopyTexImage1D" , (void **)&pglCopyTexImage1D }, +{ "glCopyTexImage2D" , (void **)&pglCopyTexImage2D }, +{ "glCopyTexSubImage1D" , (void **)&pglCopyTexSubImage1D }, +{ "glCopyTexSubImage2D" , (void **)&pglCopyTexSubImage2D }, +{ "glScissor" , (void **)&pglScissor }, +{ "glGetTexImage" , (void **)&pglGetTexImage }, +{ "glGetTexEnviv" , (void **)&pglGetTexEnviv }, +{ "glPolygonOffset" , (void **)&pglPolygonOffset }, +{ "glPolygonMode" , (void **)&pglPolygonMode }, +{ "glPolygonStipple" , (void **)&pglPolygonStipple }, +{ "glClipPlane" , (void **)&pglClipPlane }, +{ "glGetClipPlane" , (void **)&pglGetClipPlane }, +{ "glShadeModel" , (void **)&pglShadeModel }, +{ "glGetTexLevelParameteriv" , (void **)&pglGetTexLevelParameteriv }, +{ "glGetTexLevelParameterfv" , (void **)&pglGetTexLevelParameterfv }, +{ "glFogfv" , (void **)&pglFogfv }, +{ "glFogf" , (void **)&pglFogf }, +{ "glFogi" , (void **)&pglFogi }, +{ NULL, NULL } +}; + +static dllfunc_t drawrangeelementsfuncs[] = +{ +{ "glDrawRangeElements" , (void **)&pglDrawRangeElements }, +{ NULL, NULL } +}; + +static dllfunc_t drawrangeelementsextfuncs[] = +{ +{ "glDrawRangeElementsEXT" , (void **)&pglDrawRangeElementsEXT }, +{ NULL, NULL } +}; + +static dllfunc_t debugoutputfuncs[] = +{ +{ "glDebugMessageControlARB" , (void **)&pglDebugMessageControlARB }, +{ "glDebugMessageInsertARB" , (void **)&pglDebugMessageInsertARB }, +{ "glDebugMessageCallbackARB" , (void **)&pglDebugMessageCallbackARB }, +{ "glGetDebugMessageLogARB" , (void **)&pglGetDebugMessageLogARB }, +{ NULL, NULL } +}; + +static dllfunc_t multitexturefuncs[] = +{ +{ "glMultiTexCoord1fARB" , (void **)&pglMultiTexCoord1f }, +{ "glMultiTexCoord2fARB" , (void **)&pglMultiTexCoord2f }, +{ "glMultiTexCoord3fARB" , (void **)&pglMultiTexCoord3f }, +{ "glMultiTexCoord4fARB" , (void **)&pglMultiTexCoord4f }, +{ "glActiveTextureARB" , (void **)&pglActiveTexture }, +{ "glActiveTextureARB" , (void **)&pglActiveTextureARB }, +{ "glClientActiveTextureARB" , (void **)&pglClientActiveTexture }, +{ "glClientActiveTextureARB" , (void **)&pglClientActiveTextureARB }, +{ NULL, NULL } +}; + +static dllfunc_t texture3dextfuncs[] = +{ +{ "glTexImage3DEXT" , (void **)&pglTexImage3D }, +{ "glTexSubImage3DEXT" , (void **)&pglTexSubImage3D }, +{ "glCopyTexSubImage3DEXT" , (void **)&pglCopyTexSubImage3D }, +{ NULL, NULL } +}; + +static dllfunc_t blendseparatefunc[] = +{ +{ "glBlendFuncSeparateEXT", (void **)&pglBlendFuncSeparate }, +{ NULL, NULL } +}; + +static dllfunc_t shaderobjectsfuncs[] = +{ +{ "glDeleteObjectARB" , (void **)&pglDeleteObjectARB }, +{ "glGetHandleARB" , (void **)&pglGetHandleARB }, +{ "glDetachObjectARB" , (void **)&pglDetachObjectARB }, +{ "glCreateShaderObjectARB" , (void **)&pglCreateShaderObjectARB }, +{ "glShaderSourceARB" , (void **)&pglShaderSourceARB }, +{ "glCompileShaderARB" , (void **)&pglCompileShaderARB }, +{ "glCreateProgramObjectARB" , (void **)&pglCreateProgramObjectARB }, +{ "glAttachObjectARB" , (void **)&pglAttachObjectARB }, +{ "glLinkProgramARB" , (void **)&pglLinkProgramARB }, +{ "glUseProgramObjectARB" , (void **)&pglUseProgramObjectARB }, +{ "glValidateProgramARB" , (void **)&pglValidateProgramARB }, +{ "glUniform1fARB" , (void **)&pglUniform1fARB }, +{ "glUniform2fARB" , (void **)&pglUniform2fARB }, +{ "glUniform3fARB" , (void **)&pglUniform3fARB }, +{ "glUniform4fARB" , (void **)&pglUniform4fARB }, +{ "glUniform1iARB" , (void **)&pglUniform1iARB }, +{ "glUniform2iARB" , (void **)&pglUniform2iARB }, +{ "glUniform3iARB" , (void **)&pglUniform3iARB }, +{ "glUniform4iARB" , (void **)&pglUniform4iARB }, +{ "glUniform1fvARB" , (void **)&pglUniform1fvARB }, +{ "glUniform2fvARB" , (void **)&pglUniform2fvARB }, +{ "glUniform3fvARB" , (void **)&pglUniform3fvARB }, +{ "glUniform4fvARB" , (void **)&pglUniform4fvARB }, +{ "glUniform1ivARB" , (void **)&pglUniform1ivARB }, +{ "glUniform2ivARB" , (void **)&pglUniform2ivARB }, +{ "glUniform3ivARB" , (void **)&pglUniform3ivARB }, +{ "glUniform4ivARB" , (void **)&pglUniform4ivARB }, +{ "glUniformMatrix2fvARB" , (void **)&pglUniformMatrix2fvARB }, +{ "glUniformMatrix3fvARB" , (void **)&pglUniformMatrix3fvARB }, +{ "glUniformMatrix4fvARB" , (void **)&pglUniformMatrix4fvARB }, +{ "glGetObjectParameterfvARB" , (void **)&pglGetObjectParameterfvARB }, +{ "glGetObjectParameterivARB" , (void **)&pglGetObjectParameterivARB }, +{ "glGetInfoLogARB" , (void **)&pglGetInfoLogARB }, +{ "glGetAttachedObjectsARB" , (void **)&pglGetAttachedObjectsARB }, +{ "glGetUniformLocationARB" , (void **)&pglGetUniformLocationARB }, +{ "glGetActiveUniformARB" , (void **)&pglGetActiveUniformARB }, +{ "glGetUniformfvARB" , (void **)&pglGetUniformfvARB }, +{ "glGetUniformivARB" , (void **)&pglGetUniformivARB }, +{ "glGetShaderSourceARB" , (void **)&pglGetShaderSourceARB }, +{ "glVertexAttribPointerARB" , (void **)&pglVertexAttribPointerARB }, +{ "glEnableVertexAttribArrayARB" , (void **)&pglEnableVertexAttribArrayARB }, +{ "glDisableVertexAttribArrayARB" , (void **)&pglDisableVertexAttribArrayARB }, +{ "glBindAttribLocationARB" , (void **)&pglBindAttribLocationARB }, +{ "glGetActiveAttribARB" , (void **)&pglGetActiveAttribARB }, +{ "glGetAttribLocationARB" , (void **)&pglGetAttribLocationARB }, +{ "glVertexAttrib2f" , (void **)&pglVertexAttrib2fARB }, +{ "glVertexAttrib2fv" , (void **)&pglVertexAttrib2fvARB }, +{ "glVertexAttrib3fv" , (void **)&pglVertexAttrib3fvARB }, +{ "glVertexAttrib4fv" , (void **)&pglVertexAttrib4fvARB }, +{ "glVertexAttrib4ubv" , (void **)&pglVertexAttrib4ubvARB }, +{ NULL, NULL } +}; + +static dllfunc_t vertexshaderfuncs[] = +{ +{ "glVertexAttribPointerARB" , (void **)&pglVertexAttribPointerARB }, +{ "glEnableVertexAttribArrayARB" , (void **)&pglEnableVertexAttribArrayARB }, +{ "glDisableVertexAttribArrayARB" , (void **)&pglDisableVertexAttribArrayARB }, +{ "glBindAttribLocationARB" , (void **)&pglBindAttribLocationARB }, +{ "glGetActiveAttribARB" , (void **)&pglGetActiveAttribARB }, +{ "glGetAttribLocationARB" , (void **)&pglGetAttribLocationARB }, +{ NULL, NULL } +}; + +static dllfunc_t binaryshaderfuncs[] = +{ +{ "glProgramBinary" , (void **)&pglProgramBinary }, +{ "glGetProgramBinary" , (void **)&pglGetProgramBinary }, +{ "glProgramParameteri" , (void **)&pglProgramParameteri }, +{ "glGetProgramiv" , (void **)&pglGetProgramivARB }, +{ NULL, NULL } +}; + +static dllfunc_t vbofuncs[] = +{ +{ "glBindBufferARB" , (void **)&pglBindBufferARB }, +{ "glDeleteBuffersARB" , (void **)&pglDeleteBuffersARB }, +{ "glGenBuffersARB" , (void **)&pglGenBuffersARB }, +{ "glIsBufferARB" , (void **)&pglIsBufferARB }, +{ "glMapBufferARB" , (void **)&pglMapBufferARB }, +{ "glUnmapBufferARB" , (void **)&pglUnmapBufferARB }, +{ "glBufferDataARB" , (void **)&pglBufferDataARB }, +{ "glBufferSubDataARB" , (void **)&pglBufferSubDataARB }, +{ NULL, NULL } +}; + +static dllfunc_t vaofuncs[] = +{ +{ "glBindVertexArray" , (void **)&pglBindVertexArray }, +{ "glDeleteVertexArrays" , (void **)&pglDeleteVertexArrays }, +{ "glGenVertexArrays" , (void **)&pglGenVertexArrays }, +{ "glIsVertexArray" , (void **)&pglIsVertexArray }, +{ NULL, NULL } +}; + +static dllfunc_t fbofuncs[] = +{ +{ "glIsRenderbuffer" , (void **)&pglIsRenderbuffer }, +{ "glBindRenderbuffer" , (void **)&pglBindRenderbuffer }, +{ "glDeleteRenderbuffers" , (void **)&pglDeleteRenderbuffers }, +{ "glGenRenderbuffers" , (void **)&pglGenRenderbuffers }, +{ "glRenderbufferStorage" , (void **)&pglRenderbufferStorage }, +{ "glGetRenderbufferParameteriv" , (void **)&pglGetRenderbufferParameteriv }, +{ "glIsFramebuffer" , (void **)&pglIsFramebuffer }, +{ "glBindFramebuffer" , (void **)&pglBindFramebuffer }, +{ "glDeleteFramebuffers" , (void **)&pglDeleteFramebuffers }, +{ "glGenFramebuffers" , (void **)&pglGenFramebuffers }, +{ "glCheckFramebufferStatus" , (void **)&pglCheckFramebufferStatus }, +{ "glFramebufferTexture1D" , (void **)&pglFramebufferTexture1D }, +{ "glFramebufferTexture2D" , (void **)&pglFramebufferTexture2D }, +{ "glFramebufferTexture3D" , (void **)&pglFramebufferTexture3D }, +{ "glFramebufferRenderbuffer" , (void **)&pglFramebufferRenderbuffer }, +{ "glGetFramebufferAttachmentParameteriv" , (void **)&pglGetFramebufferAttachmentParameteriv }, +{ "glGenerateMipmap" , (void **)&pglGenerateMipmap }, +{ "glColorMaski" , (void **)&pglColorMaski }, +{ NULL, NULL} +}; + +static dllfunc_t occlusionfunc[] = +{ +{ "glGenQueriesARB" , (void **)&pglGenQueriesARB }, +{ "glDeleteQueriesARB" , (void **)&pglDeleteQueriesARB }, +{ "glIsQueryARB" , (void **)&pglIsQueryARB }, +{ "glBeginQueryARB" , (void **)&pglBeginQueryARB }, +{ "glEndQueryARB" , (void **)&pglEndQueryARB }, +{ "glGetQueryivARB" , (void **)&pglGetQueryivARB }, +{ "glGetQueryObjectivARB" , (void **)&pglGetQueryObjectivARB }, +{ "glGetQueryObjectuivARB" , (void **)&pglGetQueryObjectuivARB }, +{ NULL, NULL } +}; + +static dllfunc_t drawbuffersfuncs[] = +{ +{ "glDrawBuffersARB" , (void **)&pglDrawBuffersARB }, +{ NULL, NULL } +}; + +/* +======================== +DebugCallback + +For ARB_debug_output +======================== +*/ +static void CALLBACK GL_DebugOutput( GLuint source, GLuint type, GLuint id, GLuint severity, GLint length, const GLcharARB *message, GLvoid *userParam ) +{ + static char string[8192]; + char *msg; + + string[0] = '\0'; + + switch( type ) + { + case GL_DEBUG_TYPE_ERROR_ARB: + string[0] = 3; // no extra refresh + msg = va( "^1OpenGL Error:^7 %s\n", message ); + Q_strncat( string, msg, sizeof( string )); + break; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB: + string[0] = 3; // no extra refresh + msg = va( "^3OpenGL Warning:^7 %s\n", message ); + Q_strncat( string, msg, sizeof( string )); + break; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB: + string[0] = 3; // no extra refresh + msg = va( "^3OpenGL Warning:^7 %s\n", message ); + Q_strncat( string, msg, sizeof( string )); + break; + case GL_DEBUG_TYPE_PORTABILITY_ARB: + if( developer_level < DEV_EXTENDED ) + return; + string[0] = 3; // no extra refresh + msg = va( "^3OpenGL Warning:^7 %s\n", message ); + Q_strncat( string, msg, sizeof( string )); + break; + case GL_DEBUG_TYPE_PERFORMANCE_ARB: + if( developer_level < DEV_EXTENDED ) + return; + string[0] = 3; // no extra refresh + msg = va( "OpenGL Notify: %s\n", message ); + Q_strncat( string, msg, sizeof( string )); + break; + case GL_DEBUG_TYPE_OTHER_ARB: + default: + // ignore spam about detailed infos + if( !Q_strnicmp( message, "Buffer detailed info", 20 )) + return; + if( !Q_strnicmp( message, "Framebuffer detailed info", 25 )) + return; + string[0] = 3; // no extra refresh + msg = va( "OpenGL: %s\n", message ); + Q_strncat( string, msg, sizeof( string )); + break; + } + + if( !string[0] ) return; + gEngfuncs.Con_Printf( string ); +} + +/* +================= +GL_SetExtension +================= +*/ +void GL_SetExtension( int r_ext, int enable ) +{ + if( r_ext >= 0 && r_ext < R_EXTCOUNT ) + glConfig.extension[r_ext] = enable ? GL_TRUE : GL_FALSE; + else ALERT( at_error, "GL_SetExtension: invalid extension %d\n", r_ext ); +} + +/* +================= +GL_Support +================= +*/ +bool GL_Support( int r_ext ) +{ + if( r_ext >= 0 && r_ext < R_EXTCOUNT ) + return glConfig.extension[r_ext] ? true : false; + ALERT( at_error, "GL_Support: invalid extension %d\n", r_ext ); + return false; +} + +/* +================= +GL_CheckExtension +================= +*/ +void GL_CheckExtension( const char *name, const dllfunc_t *funcs, const char *cvarname, int r_ext, bool cvar_from_engine = false ) +{ + const dllfunc_t *func; + cvar_t *parm; + + ALERT( at_aiconsole, "GL_CheckExtension: %s ", name ); + + // ugly hack for p1 opengl32.dll + if(( name[0] == 'P' || name[2] == '_' || name[3] == '_' ) && !Q_strstr( glConfig.extensions_string, name )) + { + GL_SetExtension( r_ext, false ); // update render info + ALERT( at_aiconsole, "- ^1failed\n" ); + return; + } + + if( cvarname ) + { + // NOTE: engine will be ignore cvar value if variable already exitsts (e.g. created on exec opengl.cfg) + // so this call just update variable description (because Host_WriteOpenGLConfig won't archive cvars without it) + if( cvar_from_engine ) parm = CVAR_GET_POINTER( cvarname ); + else parm = CVAR_REGISTER( (char *)cvarname, "1", FCVAR_GLCONFIG ); + + if( !CVAR_TO_BOOL( parm ) || ( !CVAR_TO_BOOL( gl_extensions ) && r_ext != R_OPENGL_110 )) + { + ALERT( at_aiconsole, "- disabled\n" ); + GL_SetExtension( r_ext, false ); + return; // nothing to process at + } + GL_SetExtension( r_ext, true ); + } + + // clear exports + for( func = funcs; func && func->name; func++ ) + *func->func = NULL; + + GL_SetExtension( r_ext, true ); // predict extension state + for( func = funcs; func && func->name != NULL; func++ ) + { + // functions are cleared before all the extensions are evaluated + if(!(*func->func = (void *)GL_GetProcAddress( func->name ))) + GL_SetExtension( r_ext, false ); // one or more functions are invalid, extension will be disabled + } + + if( GL_Support( r_ext )) + ALERT( at_aiconsole, "- ^2enabled\n" ); + else ALERT( at_aiconsole, "- ^1failed\n" ); +} + +static void GL_InitExtensions( void ) +{ + // initialize gl extensions + GL_CheckExtension( "OpenGL 1.1.0", opengl_110funcs, NULL, R_OPENGL_110 ); + + if( !GL_Support( R_OPENGL_110 )) + { + ALERT( at_error, "OpenGL 1.0 can't be installed. Custom renderer disabled\n" ); + g_fRenderInterfaceValid = FALSE; + g_fRenderInitialized = FALSE; + return; + } + + // get our various GL strings + glConfig.vendor_string = pglGetString( GL_VENDOR ); + glConfig.renderer_string = pglGetString( GL_RENDERER ); + glConfig.version_string = pglGetString( GL_VERSION ); + glConfig.extensions_string = pglGetString( GL_EXTENSIONS ); + + if( Q_stristr( glConfig.renderer_string, "geforce" )) + glConfig.hardware_type = GLHW_NVIDIA; + else if( Q_stristr( glConfig.renderer_string, "quadro fx" )) + glConfig.hardware_type = GLHW_NVIDIA; + else if( Q_stristr(glConfig.renderer_string, "rv770" )) + glConfig.hardware_type = GLHW_RADEON; + else if( Q_stristr(glConfig.renderer_string, "radeon hd" )) + glConfig.hardware_type = GLHW_RADEON; + else if( Q_stristr( glConfig.renderer_string, "eah4850" ) || Q_stristr( glConfig.renderer_string, "eah4870" )) + glConfig.hardware_type = GLHW_RADEON; + else if( Q_stristr( glConfig.renderer_string, "radeon" )) + glConfig.hardware_type = GLHW_RADEON; + else glConfig.hardware_type = GLHW_GENERIC; + + glConfig.version = Q_atof( glConfig.version_string ); + + Msg( "GL_VERSION: %g\n", glConfig.version ); + + GL_CheckExtension( "glDrawRangeElements", drawrangeelementsfuncs, "gl_drawrangeelments", R_DRAW_RANGEELEMENTS_EXT ); + + if( !GL_Support( R_DRAW_RANGEELEMENTS_EXT )) + GL_CheckExtension( "GL_EXT_draw_range_elements", drawrangeelementsextfuncs, "gl_drawrangeelments", R_DRAW_RANGEELEMENTS_EXT ); + + // we don't care if it's an extension or not, they are identical functions, so keep it simple in the rendering code + if( pglDrawRangeElementsEXT == NULL ) pglDrawRangeElementsEXT = pglDrawRangeElements; + + if( !GL_Support( R_DRAW_RANGEELEMENTS_EXT )) + { + ALERT( at_error, "GL_EXT_draw_range_elements not support. Custom renderer disabled\n" ); + g_fRenderInitialized = FALSE; + return; + } + + // multitexture + glConfig.max_texture_units = 1; + GL_CheckExtension( "GL_ARB_multitexture", multitexturefuncs, "gl_arb_multitexture", R_ARB_MULTITEXTURE, true ); + + if( GL_Support( R_ARB_MULTITEXTURE )) + pglGetIntegerv( GL_MAX_TEXTURE_UNITS_ARB, &glConfig.max_texture_units ); + + if( glConfig.max_texture_units == 1 ) + GL_SetExtension( R_ARB_MULTITEXTURE, false ); + + if( !GL_Support( R_ARB_MULTITEXTURE )) + { + ALERT( at_error, "GL_ARB_multitexture not support. Custom renderer disabled\n" ); + g_fRenderInitialized = FALSE; + return; + } + + // 3d texture support + GL_CheckExtension( "GL_EXT_texture3D", texture3dextfuncs, "gl_texture_3d", R_TEXTURE_3D_EXT, true ); + + if( GL_Support( R_TEXTURE_3D_EXT )) + { + pglGetIntegerv( GL_MAX_3D_TEXTURE_SIZE, &glConfig.max_3d_texture_size ); + + if( glConfig.max_3d_texture_size < 32 ) + { + GL_SetExtension( R_TEXTURE_3D_EXT, false ); + ALERT( at_error, "GL_EXT_texture3D reported bogus GL_MAX_3D_TEXTURE_SIZE, disabled\n" ); + } + } + + // 2d texture array support + GL_CheckExtension( "GL_EXT_texture_array", texture3dextfuncs, "gl_texture_2d_array", R_TEXTURE_ARRAY_EXT, true ); + + if( !GL_Support( R_TEXTURE_ARRAY_EXT )) + ALERT( at_warning, "GL_EXT_texture_array not support. Landscapes will be unavailable\n" ); + + // occlusion queries + GL_CheckExtension( "GL_ARB_occlusion_query", occlusionfunc, "gl_occlusion_queries", R_OCCLUSION_QUERIES_EXT ); + + // separate blend + GL_CheckExtension( "GL_EXT_blend_func_separate", blendseparatefunc, "gl_separate_blend", R_SEPARATE_BLENDFUNC_EXT ); + + // hardware cubemaps + GL_CheckExtension( "GL_ARB_texture_cube_map", NULL, "gl_texture_cubemap", R_TEXTURECUBEMAP_EXT, true ); + + if( GL_Support( R_TEXTURECUBEMAP_EXT )) + pglGetIntegerv( GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB, &glConfig.max_cubemap_size ); + else ALERT( at_warning, "GL_ARB_texture_cube_map not support. Cubemap reflections and omni shadows will be disabled\n" ); + + GL_CheckExtension( "GL_ARB_texture_non_power_of_two", NULL, "gl_texture_npot", R_ARB_TEXTURE_NPOT_EXT, true ); + + GL_CheckExtension( "GL_ARB_draw_buffers", drawbuffersfuncs, "gl_draw_buffers", R_DRAW_BUFFERS_EXT ); + + GL_CheckExtension( "GL_ARB_vertex_buffer_object", vbofuncs, "gl_vertex_buffer_object", R_ARB_VERTEX_BUFFER_OBJECT_EXT ); + + if( !GL_Support( R_ARB_VERTEX_BUFFER_OBJECT_EXT )) + { + ALERT( at_error, "GL_ARB_vertex_buffer_object not support. Custom renderer disabled\n" ); + g_fRenderInitialized = FALSE; + return; + } + + GL_CheckExtension( "GL_ARB_vertex_array_object", vaofuncs, "gl_vertex_array_object", R_ARB_VERTEX_ARRAY_OBJECT_EXT ); + + if( !GL_Support( R_ARB_VERTEX_ARRAY_OBJECT_EXT )) + { + ALERT( at_error, "GL_ARB_vertex_array_object not support. Custom renderer disabled\n" ); + g_fRenderInitialized = FALSE; + return; + } + + GL_CheckExtension( "GL_EXT_gpu_shader4", NULL, "gl_ext_gpu_shader4", R_EXT_GPU_SHADER4 ); + + if( !GL_Support( R_EXT_GPU_SHADER4 )) + ALERT( at_warning, "GL_EXT_gpu_shader4 not support. Shadows from omni lights will be disabled\n" ); + + GL_CheckExtension( "GL_ARB_debug_output", debugoutputfuncs, "gl_debug_output", R_DEBUG_OUTPUT, true ); + + // vp and fp shaders + GL_CheckExtension( "GL_ARB_shader_objects", shaderobjectsfuncs, "gl_shaderobjects", R_SHADER_OBJECTS_EXT ); + + if( !GL_Support( R_SHADER_OBJECTS_EXT )) + { + ALERT( at_error, "GL_ARB_shader_objects not support. Custom renderer disabled\n" ); + g_fRenderInitialized = FALSE; + return; + } + + GL_CheckExtension( "GL_ARB_shading_language_100", NULL, "gl_shading_language", R_SHADER_GLSL100_EXT ); + + if( !GL_Support( R_SHADER_GLSL100_EXT )) + { + ALERT( at_error, "GL_ARB_shading_language_100 not support. Custom renderer disabled\n" ); + g_fRenderInitialized = FALSE; + return; + } + + GL_CheckExtension( "GL_ARB_vertex_shader", vertexshaderfuncs, "gl_vertexshader", R_VERTEX_SHADER_EXT ); + + if( !GL_Support( R_VERTEX_SHADER_EXT )) + { + ALERT( at_error, "GL_ARB_vertex_shader not support. Custom renderer disabled\n" ); + g_fRenderInitialized = FALSE; + return; + } + + GL_CheckExtension( "GL_ARB_fragment_shader", NULL, "gl_pixelshader", R_FRAGMENT_SHADER_EXT ); + + if( !GL_Support( R_FRAGMENT_SHADER_EXT )) + { + ALERT( at_error, "GL_ARB_fragment_shader not support. Custom renderer disabled\n" ); + g_fRenderInitialized = FALSE; + return; + } + + GL_CheckExtension( "GL_ARB_get_program_binary", binaryshaderfuncs, "gl_binaryshader", R_BINARY_SHADER_EXT ); + GL_CheckExtension( "GL_ARB_depth_texture", NULL, "gl_depthtexture", R_DEPTH_TEXTURE ); + GL_CheckExtension( "GL_ARB_shadow", NULL, "gl_arb_shadow", R_SHADOW_EXT ); + GL_CheckExtension( "GL_ARB_texture_rectangle", NULL, "gl_texture_rectangle", R_TEXTURE_2D_RECT_EXT, true ); + + if( GL_Support( R_BINARY_SHADER_EXT )) + { + pglGetIntegerv( GL_NUM_PROGRAM_BINARY_FORMATS, &glConfig.num_formats ); + pglGetIntegerv( GL_PROGRAM_BINARY_FORMATS, &glConfig.binary_formats ); + } + + if( !GL_Support( R_DEPTH_TEXTURE ) || !GL_Support( R_SHADOW_EXT )) + { + ALERT( at_warning, "GL_ARB_depth_texture or GL_ARB_shadow not support. Dynamic shadows disabled\n" ); + tr.shadows_notsupport = true; + } + + glConfig.max_2d_texture_size = 0; + pglGetIntegerv( GL_MAX_TEXTURE_SIZE, &glConfig.max_2d_texture_size ); + if( glConfig.max_2d_texture_size <= 0 ) glConfig.max_2d_texture_size = 256; + + if( GL_Support( R_TEXTURE_ARRAY_EXT )) + pglGetIntegerv( GL_MAX_ARRAY_TEXTURE_LAYERS_EXT, &glConfig.max_2d_texture_layers ); + + // FBO support + GL_CheckExtension( "GL_ARB_framebuffer_object", fbofuncs, "gl_framebuffers", R_FRAMEBUFFER_OBJECT ); + + // Paranoia OpenGL32.dll may be eliminate shadows. Run special check for it + GL_CheckExtension( "PARANOIA_HACKS_V1", NULL, NULL, R_PARANOIA_EXT ); + + GL_CheckExtension( "GL_ARB_seamless_cube_map", NULL, "gl_seamless_cubemap", R_SEAMLESS_CUBEMAP ); + + // check for hardware skinning + pglGetIntegerv( GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB, &glConfig.max_vertex_uniforms ); + pglGetIntegerv( GL_MAX_VERTEX_ATTRIBS_ARB, &glConfig.max_vertex_attribs ); + pglGetIntegerv( GL_MAX_VARYING_FLOATS_ARB, &glConfig.max_varying_floats ); + + if( glConfig.hardware_type == GLHW_RADEON && glConfig.max_vertex_uniforms > 512 ) + glConfig.max_vertex_uniforms /= 4; // only radion returns count of floats other returns count of vec4 + + glConfig.max_skinning_bones = bound( 0, ( Q_max( glConfig.max_vertex_uniforms - MAX_RESERVED_UNIFORMS, 0 ) / 7 ), MAXSTUDIOBONES ); + + if( glConfig.max_skinning_bones < 32 ) + { + ALERT( at_error, "Hardware Skinning not support. Custom renderer disabled\n" ); + g_fRenderInitialized = FALSE; + return; + } + else if( glConfig.max_skinning_bones < MAXSTUDIOBONES ) + ALERT( at_warning, "Hardware Skinning has a limitation (max %i bones)\n", glConfig.max_skinning_bones ); + + ALERT( at_aiconsole, "GL_InitExtensions: max vertex uniforms %i\n", glConfig.max_vertex_uniforms ); + ALERT( at_aiconsole, "GL_InitExtensions: max varying floats %i\n", glConfig.max_varying_floats ); + ALERT( at_aiconsole, "GL_InitExtensions: MaxSkinned bones %i\n", glConfig.max_skinning_bones ); + + glConfig.max_texture_units = RENDER_GET_PARM( PARM_MAX_IMAGE_UNITS, 0 ); + + if( GL_Support( R_DEBUG_OUTPUT )) + { + if( developer_level >= 2 ) + { + pglDebugMessageCallbackARB( GL_DebugOutput, NULL ); + } + + if( developer_level >= 3 ) + { + // force everything to happen in the main thread instead of in a separate driver thread + pglEnable( GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB ); + } + + if( developer_level >= 4 ) + { + // enable all the low priority messages + pglDebugMessageControlARB( GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_LOW_ARB, 0, NULL, true ); + } + } +} + +/* +=============== +GL_SetDefaultState +=============== +*/ +void GL_SetDefaultState( void ) +{ + glState.depthmin = glState.depthmax = -1.0f; + glState.depthmask = -1; + + GL_CleanupAllTextureUnits(); + pglBindVertexArray( GL_FALSE ); // should be first! + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); + pglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 ); + pglDisable( GL_SCISSOR_TEST ); + pglShadeModel( GL_SMOOTH ); + GL_BindFBO( FBO_MAIN ); + GL_BindShader( NULL ); +} + +void R_CreateSpotLightTexture( void ) +{ + if( tr.defaultProjTexture ) return; + + byte data[PROJ_SIZE*PROJ_SIZE*4]; + byte *p = data; + + for( int i = 0; i < PROJ_SIZE; i++ ) + { + float dy = (PROJ_SIZE * 0.5f - i + 0.5f) / (PROJ_SIZE * 0.5f); + + for( int j = 0; j < PROJ_SIZE; j++ ) + { + float dx = (PROJ_SIZE * 0.5f - j + 0.5f) / (PROJ_SIZE * 0.5f); + float r = cos( M_PI / 2.0f * sqrt(dx * dx + dy * dy)); + float c; + + r = (r < 0) ? 0 : r * r; + c = 0xFF * r; + p[0] = (c <= 0xFF) ? c : 0xFF; + p[1] = (c <= 0xFF) ? c : 0xFF; + p[2] = (c <= 0xFF) ? c : 0xFF; + p[3] = (c <= 0xff) ? c : 0xFF; + p += 4; + } + } + + tr.defaultProjTexture = CREATE_TEXTURE( "*spotlight", PROJ_SIZE, PROJ_SIZE, data, TF_SPOTLIGHT ); +} + +/* +================== +R_InitBlankBumpTexture +================== +*/ +static void R_InitBlankBumpTexture( void ) +{ + byte data2D[256*4]; + + // default normalmap texture + for( int i = 0; i < 256; i++ ) + { + data2D[i*4+0] = 127; + data2D[i*4+1] = 127; + data2D[i*4+2] = 255; + } + + tr.normalmapTexture = CREATE_TEXTURE( "*blankbump", 16, 16, data2D, TF_NORMALMAP ); +} + +/* +================== +R_InitVSDCTCubemap + +Virtual Shadow Depth Cube Texture +================== +*/ +static void R_InitVSDCTCubemap( void ) +{ + if( !GL_Support( R_TEXTURECUBEMAP_EXT )) + return; + + // maps to a 2x3 texture rectangle with normalized coordinates + // +- + // XX + // YY + // ZZ + // stores abs(dir.xy), offset.xy/2.5 + static byte data[4*6] = + { + 0xFF, 0x00, 0x33, 0x33, // +X: <1, 0>, <0.5, 0.5> + 0xFF, 0x00, 0x99, 0x33, // -X: <1, 0>, <1.5, 0.5> + 0x00, 0xFF, 0x33, 0x99, // +Y: <0, 1>, <0.5, 1.5> + 0x00, 0xFF, 0x99, 0x99, // -Y: <0, 1>, <1.5, 1.5> + 0x00, 0x00, 0x33, 0xFF, // +Z: <0, 0>, <0.5, 2.5> + 0x00, 0x00, 0x99, 0xFF, // -Z: <0, 0>, <1.5, 2.5> + }; + + tr.vsdctCubeTexture = CREATE_TEXTURE( "*vsdct", 1, 1, data, TF_NEAREST|TF_HAS_ALPHA|TF_CUBEMAP|TF_CLAMP ); +} + +/* +================== +R_InitWhiteCubemap +================== +*/ +static void R_InitWhiteCubemap( void ) +{ + byte dataCM[4*4*6*4]; + int size = 4; + + if( !GL_Support( R_TEXTURECUBEMAP_EXT )) + return; + + // white cubemap - just stub for pointlights + memset( dataCM, 0xFF, sizeof( dataCM )); + + tr.whiteCubeTexture = CREATE_TEXTURE( "*whiteCube", size, size, dataCM, TF_NOMIPMAP|TF_CUBEMAP|TF_CLAMP ); +} + +/* +============= +R_LoadIdentity +============= +*/ +static void R_LoadIdentity( void ) +{ + glState.identityMatrix[ 0] = 1.0f; + glState.identityMatrix[ 1] = 0.0f; + glState.identityMatrix[ 2] = 0.0f; + glState.identityMatrix[ 3] = 0.0f; + glState.identityMatrix[ 4] = 0.0f; + glState.identityMatrix[ 5] = 1.0f; + glState.identityMatrix[ 6] = 0.0f; + glState.identityMatrix[ 7] = 0.0f; + glState.identityMatrix[ 8] = 0.0f; + glState.identityMatrix[ 9] = 0.0f; + glState.identityMatrix[10] = 1.0f; + glState.identityMatrix[11] = 0.0f; + glState.identityMatrix[12] = 0.0f; + glState.identityMatrix[13] = 0.0f; + glState.identityMatrix[14] = 0.0f; + glState.identityMatrix[15] = 1.0f; +} + +void R_InitDefaultLights( void ) +{ + // this used to precache uber-shaders + memset( &tr.defaultlightSpot, 0, sizeof( tr.defaultlightSpot )); + memset( &tr.defaultlightOmni, 0, sizeof( tr.defaultlightOmni )); + memset( &tr.defaultlightProj, 0, sizeof( tr.defaultlightProj )); + + tr.defaultlightSpot.type = LIGHT_SPOT; + tr.defaultlightOmni.type = LIGHT_OMNI; + tr.defaultlightProj.type = LIGHT_DIRECTIONAL; +} + +static void GL_InitTextures( void ) +{ + // just get it from engine + tr.defaultTexture = FIND_TEXTURE( "*default" ); // use for bad textures + tr.deluxemapTexture = FIND_TEXTURE( "*gray" ); // like Vector( 0, 0, 0 ) + tr.whiteTexture = FIND_TEXTURE( "*white" ); + tr.grayTexture = FIND_TEXTURE( "*gray" ); + tr.blackTexture = FIND_TEXTURE( "*black" ); + tr.depthTexture = CREATE_TEXTURE( "*depth", 8, 8, NULL, TF_SHADOW ); + + R_InitWhiteCubemap(); + + R_InitBlankBumpTexture(); + + R_InitVSDCTCubemap(); + + if( GL_Support( R_EXT_GPU_SHADER4 )) + tr.depthCubemap = CREATE_TEXTURE( "depthCube", 8, 8, NULL, TF_SHADOW_CUBEMAP ); + + // best fit normals + tr.normalsFitting = LOAD_TEXTURE( "gfx/NormalsFitting.dds", NULL, 0, TF_KEEP_SOURCE|TF_CLAMP|TF_NEAREST ); + if( !tr.normalsFitting ) tr.normalsFitting = tr.whiteTexture; // fallback + + // load water animation + for( int i = 0; i < WATER_TEXTURES; i++ ) + { + char path[256]; + Q_snprintf( path, sizeof( path ), "gfx/water/water_normal_%i", i ); + tr.waterTextures[i] = LOAD_TEXTURE( path, NULL, 0, TF_NORMALMAP ); + } + + R_CreateSpotLightTexture (); + + // initialize spotlights + if( !tr.spotlightTexture[0] ) + { + char path[256]; + + tr.spotlightTexture[0] = tr.defaultProjTexture; // always present + tr.flashlightTexture = LOAD_TEXTURE( "gfx/flashlight", NULL, 0, TF_SPOTLIGHT ); + if( !tr.flashlightTexture ) tr.flashlightTexture = tr.defaultProjTexture; + + // 7 custom textures allowed + for( int i = 1; i < 8; i++ ) + { + Q_snprintf( path, sizeof( path ), "gfx/spotlight%i", i ); + + if( IMAGE_EXISTS( path )) + tr.spotlightTexture[i] = LOAD_TEXTURE( path, NULL, 0, TF_SPOTLIGHT ); + + if( !tr.spotlightTexture[i] ) + tr.spotlightTexture[i] = tr.defaultProjTexture; // make default if missed + } + } +} + +/* +================== +GL_Init +================== +*/ +bool GL_Init( void ) +{ + R_InitRefState(); + GL_InitExtensions(); + BuildGammaTable(); + R_LoadIdentity(); + + if( !g_fRenderInitialized ) + { + CVAR_SET_FLOAT( "gl_renderer", 0 ); + GL_Shutdown(); + return false; + } + + DBG_PrintVertexVBOSizes(); + GL_InitGPUShaders(); + InitPostEffects(); + GL_InitTextures(); + COM_InitMatdef(); + CL_InitMaterials(); + GL_SetDefaultState(); + GL_InitRandomTable(); + + pglPixelStorei( GL_UNPACK_ALIGNMENT, 1 ); + pglPixelStorei( GL_PACK_ALIGNMENT, 1 ); + pglPointSize( 10.0f ); + pglLineWidth( 1.0f ); + + InitRain(); // rain + DecalsInit(); + R_GrassInit(); + + return true; +} + +/* +=============== +GL_Shutdown +=============== +*/ +void GL_Shutdown( void ) +{ + int i; + + g_StudioRenderer.DestroyAllModelInstances(); + g_StudioRenderer.FreeStudioCacheVL(); + g_StudioRenderer.FreeStudioCacheFL(); + + if( tr.materials ) + Mem_Free( tr.materials ); + + for( i = 0; i < tr.num_framebuffers; i++ ) + { + if( !tr.frame_buffers[i].init ) + break; + R_FreeFrameBuffer( i ); + } + + tr.fbo_shadow2D.Free(); + tr.fbo_shadowCM.Free(); + + for( i = 0; i < MAX_SHADOWMAPS; i++ ) + { + tr.sunShadowFBO[i].Free(); + } + + R_FreeCinematics(); + DecalsShutdown(); + R_GrassShutdown(); + GL_FreeGPUShaders(); + GL_FreeDrawbuffers(); + + // now all extensions are disabled + memset( glConfig.extension, 0, sizeof( glConfig.extension[0] ) * R_EXTCOUNT ); +} \ No newline at end of file diff --git a/cl_dll/render/gl_export.h b/cl_dll/render/gl_export.h new file mode 100644 index 0000000..f4bd546 --- /dev/null +++ b/cl_dll/render/gl_export.h @@ -0,0 +1,1366 @@ +/* +gl_export.h - OpenGL definition +Copyright (C) 2011 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef GL_EXPORT_H +#define GL_EXPORT_H + +#include + +#ifndef APIENTRY +#define APIENTRY +#endif + +#ifndef EXTERN +#define EXTERN extern +#endif + +typedef unsigned int GLenum; +typedef unsigned char GLboolean; +typedef unsigned int GLbitfield; +typedef void GLvoid; +typedef signed char GLbyte; +typedef short GLshort; +typedef int GLint; +typedef unsigned char GLubyte; +typedef unsigned short GLushort; +typedef unsigned int GLuint; +typedef int GLsizei; +typedef float GLfloat; +typedef float GLclampf; +typedef double GLdouble; +typedef double GLclampd; +typedef int GLintptrARB; +typedef int GLsizeiptrARB; +typedef char GLcharARB; +typedef unsigned int GLhandleARB; + +#define GL_MODELVIEW 0x1700 +#define GL_PROJECTION 0x1701 +#define GL_TEXTURE 0x1702 +#define GL_MATRIX_MODE 0x0BA0 +#define GL_MODELVIEW_MATRIX 0x0BA6 +#define GL_PROJECTION_MATRIX 0x0BA7 +#define GL_TEXTURE_MATRIX 0x0BA8 +#define GL_VIEWPORT 0x0BA2 + +#define GL_DONT_CARE 0x1100 +#define GL_FASTEST 0x1101 +#define GL_NICEST 0x1102 + +#define GL_ACCUM 0x0100 +#define GL_LOAD 0x0101 +#define GL_RETURN 0x0102 +#define GL_MULT 0x0103 +#define GL_ADD 0x0104 + +#define GL_DEPTH_TEST 0x0B71 + +#define GL_CULL_FACE 0x0B44 +#define GL_CW 0x0900 +#define GL_CCW 0x0901 +#define GL_BLEND 0x0BE2 +#define GL_ALPHA_TEST 0x0BC0 +#define GL_ALPHA_TEST_FUNC 0x0BC1 +#define GL_ALPHA_TEST_REF 0x0BC2 + +// shading model +#define GL_FLAT 0x1D00 +#define GL_SMOOTH 0x1D01 + +#define GL_ZERO 0 +#define GL_ONE 1 +#define GL_RED 0x1903 +#define GL_GREEN 0x1904 +#define GL_BLUE 0x1905 +#define GL_ALPHA 0x1906 +#define GL_SRC_COLOR 0x0300 +#define GL_ONE_MINUS_SRC_COLOR 0x0301 +#define GL_DST_COLOR 0x0306 +#define GL_ONE_MINUS_DST_COLOR 0x0307 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_DST_ALPHA 0x0304 +#define GL_ONE_MINUS_DST_ALPHA 0x0305 +#define GL_SRC_ALPHA_SATURATE 0x0308 +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 + +#define GL_TEXTURE_WIDTH 0x1000 +#define GL_TEXTURE_HEIGHT 0x1001 +#define GL_TEXTURE_INTERNAL_FORMAT 0x1003 +#define GL_TEXTURE_BORDER_COLOR 0x1004 +#define GL_TEXTURE_BORDER 0x1005 +#define GL_RED_BITS 0x0D52 +#define GL_GREEN_BITS 0x0D53 +#define GL_BLUE_BITS 0x0D54 +#define GL_ALPHA_BITS 0x0D55 +#define GL_DEPTH_BITS 0x0D56 +#define GL_STENCIL_BITS 0x0D57 +#define GL_ACCUM_RED_BITS 0x0D58 +#define GL_ACCUM_GREEN_BITS 0x0D59 +#define GL_ACCUM_BLUE_BITS 0x0D5A +#define GL_ACCUM_ALPHA_BITS 0x0D5B +#define GL_TEXTURE_ENV 0x2300 +#define GL_TEXTURE_ENV_MODE 0x2200 +#define GL_TEXTURE_ENV_COLOR 0x2201 +#define GL_TEXTURE_1D 0x0DE0 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_TEXTURE_BORDER_COLOR 0x1004 +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_PACK_ALIGNMENT 0x0D05 +#define GL_UNPACK_ALIGNMENT 0x0CF5 +#define GL_TEXTURE_BINDING_1D 0x8068 +#define GL_TEXTURE_BINDING_2D 0x8069 +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_NEAREST 0x2600 +#define GL_LINEAR 0x2601 +#define GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 + +#define GL_LINE 0x1B01 +#define GL_FILL 0x1B02 + +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF + +#define GL_MAX_TEXTURE_LOD_BIAS_EXT 0x84FD +#define GL_TEXTURE_FILTER_CONTROL_EXT 0x8500 +#define GL_TEXTURE_LOD_BIAS_EXT 0x8501 + +#define GL_CLAMP_TO_BORDER_ARB 0x812D + +#define GL_ADD 0x0104 +#define GL_DECAL 0x2101 +#define GL_MODULATE 0x2100 + +#define GL_REPEAT 0x2901 +#define GL_CLAMP 0x2900 + +#define GL_POINTS 0x0000 +#define GL_LINES 0x0001 +#define GL_LINE_LOOP 0x0002 +#define GL_LINE_STRIP 0x0003 +#define GL_TRIANGLES 0x0004 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_TRIANGLE_FAN 0x0006 +#define GL_QUADS 0x0007 +#define GL_QUAD_STRIP 0x0008 +#define GL_POLYGON 0x0009 + +#define GL_FALSE 0 +#define GL_TRUE 1 + +#define GL_BYTE 0x1400 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_SHORT 0x1402 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_INT 0x1404 +#define GL_UNSIGNED_INT 0x1405 +#define GL_FLOAT 0x1406 +#define GL_DOUBLE 0x140A +#define GL_2_BYTES 0x1407 +#define GL_3_BYTES 0x1408 +#define GL_4_BYTES 0x1409 + +#define GL_VERTEX_ARRAY 0x8074 +#define GL_NORMAL_ARRAY 0x8075 +#define GL_COLOR_ARRAY 0x8076 +#define GL_INDEX_ARRAY 0x8077 +#define GL_TEXTURE_COORD_ARRAY 0x8078 +#define GL_EDGE_FLAG_ARRAY 0x8079 + +#define GL_NONE 0 +#define GL_FRONT_LEFT 0x0400 +#define GL_FRONT_RIGHT 0x0401 +#define GL_BACK_LEFT 0x0402 +#define GL_BACK_RIGHT 0x0403 +#define GL_FRONT 0x0404 +#define GL_BACK 0x0405 +#define GL_LEFT 0x0406 +#define GL_RIGHT 0x0407 +#define GL_FRONT_AND_BACK 0x0408 +#define GL_AUX0 0x0409 +#define GL_AUX1 0x040A +#define GL_AUX2 0x040B +#define GL_AUX3 0x040C + +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 + +#define GL_NO_ERROR 0 +#define GL_INVALID_VALUE 0x0501 +#define GL_INVALID_ENUM 0x0500 +#define GL_INVALID_OPERATION 0x0502 +#define GL_STACK_OVERFLOW 0x0503 +#define GL_STACK_UNDERFLOW 0x0504 +#define GL_OUT_OF_MEMORY 0x0505 + +#define GL_DITHER 0x0BD0 +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 +#define GL_BGR 0x80E0 +#define GL_BGRA 0x80E1 +#define GL_ALPHA4 0x803B +#define GL_ALPHA8 0x803C +#define GL_ALPHA12 0x803D +#define GL_ALPHA16 0x803E +#define GL_LUMINANCE4 0x803F +#define GL_LUMINANCE8 0x8040 +#define GL_LUMINANCE12 0x8041 +#define GL_LUMINANCE16 0x8042 +#define GL_LUMINANCE4_ALPHA4 0x8043 +#define GL_LUMINANCE6_ALPHA2 0x8044 +#define GL_LUMINANCE8_ALPHA8 0x8045 +#define GL_LUMINANCE12_ALPHA4 0x8046 +#define GL_LUMINANCE12_ALPHA12 0x8047 +#define GL_LUMINANCE16_ALPHA16 0x8048 +#define GL_LUMINANCE 0x1909 +#define GL_LUMINANCE_ALPHA 0x190A +#define GL_DEPTH_COMPONENT 0x1902 +#define GL_INTENSITY 0x8049 +#define GL_INTENSITY4 0x804A +#define GL_INTENSITY8 0x804B +#define GL_INTENSITY12 0x804C +#define GL_INTENSITY16 0x804D +#define GL_R3_G3_B2 0x2A10 +#define GL_RGB4 0x804F +#define GL_RGB5 0x8050 +#define GL_RGB8 0x8051 +#define GL_RGB10 0x8052 +#define GL_RGB12 0x8053 +#define GL_RGB16 0x8054 +#define GL_RGBA2 0x8055 +#define GL_RGBA4 0x8056 +#define GL_RGB5_A1 0x8057 +#define GL_RGBA8 0x8058 +#define GL_RGB10_A2 0x8059 +#define GL_RGBA12 0x805A +#define GL_RGBA16 0x805B +#define GL_TEXTURE_RED_SIZE 0x805C +#define GL_TEXTURE_GREEN_SIZE 0x805D +#define GL_TEXTURE_BLUE_SIZE 0x805E +#define GL_TEXTURE_ALPHA_SIZE 0x805F +#define GL_TEXTURE_LUMINANCE_SIZE 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE 0x8061 +#define GL_PROXY_TEXTURE_1D 0x8063 +#define GL_PROXY_TEXTURE_2D 0x8064 +#define GL_MAX_TEXTURE_SIZE 0x0D33 + +// texture coord name +#define GL_S 0x2000 +#define GL_T 0x2001 +#define GL_R 0x2002 +#define GL_Q 0x2003 + +// texture gen mode +#define GL_EYE_LINEAR 0x2400 +#define GL_OBJECT_LINEAR 0x2401 +#define GL_SPHERE_MAP 0x2402 + +// texture gen parameter +#define GL_TEXTURE_GEN_MODE 0x2500 +#define GL_OBJECT_PLANE 0x2501 +#define GL_EYE_PLANE 0x2502 +#define GL_FOG_HINT 0x0C54 +#define GL_TEXTURE_GEN_S 0x0C60 +#define GL_TEXTURE_GEN_T 0x0C61 +#define GL_TEXTURE_GEN_R 0x0C62 +#define GL_TEXTURE_GEN_Q 0x0C63 + +#define GL_SCISSOR_BOX 0x0C10 +#define GL_SCISSOR_TEST 0x0C11 + +#define GL_NEVER 0x0200 +#define GL_LESS 0x0201 +#define GL_EQUAL 0x0202 +#define GL_LEQUAL 0x0203 +#define GL_GREATER 0x0204 +#define GL_NOTEQUAL 0x0205 +#define GL_GEQUAL 0x0206 +#define GL_ALWAYS 0x0207 +#define GL_DEPTH_TEST 0x0B71 + +#define GL_RED_SCALE 0x0D14 +#define GL_GREEN_SCALE 0x0D18 +#define GL_BLUE_SCALE 0x0D1A +#define GL_ALPHA_SCALE 0x0D1C + +#define GL_POINT_SMOOTH_HINT 0x0C51 +#define GL_LINE_SMOOTH_HINT 0x0C52 +#define GL_POLYGON_SMOOTH_HINT 0x0C53 + +/* AttribMask */ +#define GL_CURRENT_BIT 0x00000001 +#define GL_POINT_BIT 0x00000002 +#define GL_LINE_BIT 0x00000004 +#define GL_POLYGON_BIT 0x00000008 +#define GL_POLYGON_STIPPLE_BIT 0x00000010 +#define GL_PIXEL_MODE_BIT 0x00000020 +#define GL_LIGHTING_BIT 0x00000040 +#define GL_FOG_BIT 0x00000080 +#define GL_DEPTH_BUFFER_BIT 0x00000100 +#define GL_ACCUM_BUFFER_BIT 0x00000200 +#define GL_STENCIL_BUFFER_BIT 0x00000400 +#define GL_VIEWPORT_BIT 0x00000800 +#define GL_TRANSFORM_BIT 0x00001000 +#define GL_ENABLE_BIT 0x00002000 +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_HINT_BIT 0x00008000 +#define GL_EVAL_BIT 0x00010000 +#define GL_LIST_BIT 0x00020000 +#define GL_TEXTURE_BIT 0x00040000 +#define GL_SCISSOR_BIT 0x00080000 +#define GL_ALL_ATTRIB_BITS 0x000fffff + +#define GL_STENCIL_TEST 0x0B90 +#define GL_KEEP 0x1E00 +#define GL_REPLACE 0x1E01 +#define GL_INCR 0x1E02 +#define GL_DECR 0x1E03 + +// fog stuff +#define GL_FOG 0x0B60 +#define GL_FOG_INDEX 0x0B61 +#define GL_FOG_DENSITY 0x0B62 +#define GL_FOG_START 0x0B63 +#define GL_FOG_END 0x0B64 +#define GL_FOG_MODE 0x0B65 +#define GL_FOG_COLOR 0x0B66 +#define GL_EXP 0x0800 +#define GL_EXP2 0x0801 + +#define GL_POLYGON_OFFSET_FACTOR 0x8038 +#define GL_POLYGON_OFFSET_UNITS 0x2A00 +#define GL_POLYGON_OFFSET_POINT 0x2A01 +#define GL_POLYGON_OFFSET_LINE 0x2A02 +#define GL_POLYGON_OFFSET_FILL 0x8037 + +#define GL_POINT_SMOOTH 0x0B10 +#define GL_LINE_SMOOTH 0x0B20 +#define GL_POLYGON_SMOOTH 0x0B41 +#define GL_POLYGON_STIPPLE 0x0B42 +#define GL_CLIP_PLANE0 0x3000 +#define GL_CLIP_PLANE1 0x3001 +#define GL_CLIP_PLANE2 0x3002 +#define GL_CLIP_PLANE3 0x3003 +#define GL_CLIP_PLANE4 0x3004 +#define GL_CLIP_PLANE5 0x3005 +#define GL_POINT_SIZE_MIN_EXT 0x8126 +#define GL_POINT_SIZE_MAX_EXT 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_EXT 0x8128 +#define GL_DISTANCE_ATTENUATION_EXT 0x8129 +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_TEXTURE_UNITS_ARB 0x84E2 +#define GL_TEXTURE0_ARB 0x84C0 +#define GL_TEXTURE1_ARB 0x84C1 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE3_ARB 0x84C3 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE0_SGIS 0x835E +#define GL_TEXTURE1_SGIS 0x835F +#define GL_GENERATE_MIPMAP_SGIS 0x8191 +#define GL_GENERATE_MIPMAP_HINT_SGIS 0x8192 +#define GL_TEXTURE_RECTANGLE_NV 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_NV 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_NV 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_NV 0x84F8 +#define GL_TEXTURE_RECTANGLE_EXT 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_EXT 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_EXT 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_EXT 0x84F8 +#define GL_MAX_TEXTURE_UNITS 0x84E2 +#define GL_MAX_TEXTURE_UNITS_ARB 0x84E2 + +#define GL_REGISTER_COMBINERS_NV 0x8522 +#define GL_VARIABLE_A_NV 0x8523 +#define GL_VARIABLE_B_NV 0x8524 +#define GL_VARIABLE_C_NV 0x8525 +#define GL_VARIABLE_D_NV 0x8526 +#define GL_VARIABLE_E_NV 0x8527 +#define GL_VARIABLE_F_NV 0x8528 +#define GL_VARIABLE_G_NV 0x8529 +#define GL_CONSTANT_COLOR0_NV 0x852A +#define GL_CONSTANT_COLOR1_NV 0x852B +#define GL_PRIMARY_COLOR_NV 0x852C +#define GL_SECONDARY_COLOR_NV 0x852D +#define GL_SPARE0_NV 0x852E +#define GL_SPARE1_NV 0x852F +#define GL_DISCARD_NV 0x8530 +#define GL_E_TIMES_F_NV 0x8531 +#define GL_SPARE0_PLUS_SECONDARY_COLOR_NV 0x8532 +#define GL_UNSIGNED_IDENTITY_NV 0x8536 +#define GL_UNSIGNED_INVERT_NV 0x8537 +#define GL_EXPAND_NORMAL_NV 0x8538 +#define GL_EXPAND_NEGATE_NV 0x8539 +#define GL_HALF_BIAS_NORMAL_NV 0x853A +#define GL_HALF_BIAS_NEGATE_NV 0x853B +#define GL_SIGNED_IDENTITY_NV 0x853C +#define GL_SIGNED_NEGATE_NV 0x853D +#define GL_SCALE_BY_TWO_NV 0x853E +#define GL_SCALE_BY_FOUR_NV 0x853F +#define GL_SCALE_BY_ONE_HALF_NV 0x8540 +#define GL_BIAS_BY_NEGATIVE_ONE_HALF_NV 0x8541 +#define GL_COMBINER_INPUT_NV 0x8542 +#define GL_COMBINER_MAPPING_NV 0x8543 +#define GL_COMBINER_COMPONENT_USAGE_NV 0x8544 +#define GL_COMBINER_AB_DOT_PRODUCT_NV 0x8545 +#define GL_COMBINER_CD_DOT_PRODUCT_NV 0x8546 +#define GL_COMBINER_MUX_SUM_NV 0x8547 +#define GL_COMBINER_SCALE_NV 0x8548 +#define GL_COMBINER_BIAS_NV 0x8549 +#define GL_COMBINER_AB_OUTPUT_NV 0x854A +#define GL_COMBINER_CD_OUTPUT_NV 0x854B +#define GL_COMBINER_SUM_OUTPUT_NV 0x854C +#define GL_MAX_GENERAL_COMBINERS_NV 0x854D +#define GL_NUM_GENERAL_COMBINERS_NV 0x854E +#define GL_COLOR_SUM_CLAMP_NV 0x854F +#define GL_COMBINER0_NV 0x8550 +#define GL_COMBINER1_NV 0x8551 +#define GL_COMBINER2_NV 0x8552 +#define GL_COMBINER3_NV 0x8553 +#define GL_COMBINER4_NV 0x8554 +#define GL_COMBINER5_NV 0x8555 +#define GL_COMBINER6_NV 0x8556 +#define GL_COMBINER7_NV 0x8557 +#define GL_DSDT8_NV 0x8709 +#define GL_DSDT_NV 0x86F5 + +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#define GL_COMPRESSED_RED_GREEN_RGTC2_EXT 0x8DBD +#define GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI 0x8837 +#define GL_COMPRESSED_ALPHA_ARB 0x84E9 +#define GL_COMPRESSED_LUMINANCE_ARB 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA_ARB 0x84EB +#define GL_COMPRESSED_INTENSITY_ARB 0x84EC +#define GL_COMPRESSED_RGB_ARB 0x84ED +#define GL_COMPRESSED_RGBA_ARB 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT_ARB 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB 0x86A0 +#define GL_TEXTURE_COMPRESSED_ARB 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A3 +#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 +#define GL_TEXTURE_MAX_LEVEL 0x813D +#define GL_GENERATE_MIPMAP 0x8191 +#define GL_ADD_SIGNED 0x8574 +#define GL_ATI_texture_compression_3dc 0x8837 + +#define GL_PROGRAM_OBJECT_ARB 0x8B40 +#define GL_OBJECT_TYPE_ARB 0x8B4E +#define GL_OBJECT_SUBTYPE_ARB 0x8B4F +#define GL_OBJECT_DELETE_STATUS_ARB 0x8B80 +#define GL_OBJECT_COMPILE_STATUS_ARB 0x8B81 +#define GL_OBJECT_LINK_STATUS_ARB 0x8B82 +#define GL_OBJECT_VALIDATE_STATUS_ARB 0x8B83 +#define GL_OBJECT_INFO_LOG_LENGTH_ARB 0x8B84 +#define GL_OBJECT_ATTACHED_OBJECTS_ARB 0x8B85 +#define GL_OBJECT_ACTIVE_UNIFORMS_ARB 0x8B86 +#define GL_OBJECT_ACTIVE_UNIFORM_MAX_LENGTH_ARB 0x8B87 +#define GL_OBJECT_SHADER_SOURCE_LENGTH_ARB 0x8B88 +#define GL_SHADER_OBJECT_ARB 0x8B48 +#define GL_PROGRAM_BINARY_RETRIEVABLE_HINT 0x8257 +#define GL_PROGRAM_BINARY_LENGTH 0x8741 +#define GL_NUM_PROGRAM_BINARY_FORMATS 0x87FE +#define GL_PROGRAM_BINARY_FORMATS 0x87FF +#define GL_FLOAT_VEC2_ARB 0x8B50 +#define GL_FLOAT_VEC3_ARB 0x8B51 +#define GL_FLOAT_VEC4_ARB 0x8B52 +#define GL_INT_VEC2_ARB 0x8B53 +#define GL_INT_VEC3_ARB 0x8B54 +#define GL_INT_VEC4_ARB 0x8B55 +#define GL_BOOL_ARB 0x8B56 +#define GL_BOOL_VEC2_ARB 0x8B57 +#define GL_BOOL_VEC3_ARB 0x8B58 +#define GL_BOOL_VEC4_ARB 0x8B59 +#define GL_FLOAT_MAT2_ARB 0x8B5A +#define GL_FLOAT_MAT3_ARB 0x8B5B +#define GL_FLOAT_MAT4_ARB 0x8B5C +#define GL_SAMPLER_1D_ARB 0x8B5D +#define GL_SAMPLER_2D_ARB 0x8B5E +#define GL_SAMPLER_3D_ARB 0x8B5F +#define GL_SAMPLER_CUBE_ARB 0x8B60 +#define GL_SAMPLER_1D_SHADOW_ARB 0x8B61 +#define GL_SAMPLER_2D_SHADOW_ARB 0x8B62 +#define GL_SAMPLER_2D_RECT_ARB 0x8B63 +#define GL_SAMPLER_2D_RECT_SHADOW_ARB 0x8B64 +#define GL_SAMPLER_1D_ARRAY_EXT 0x8DC0 +#define GL_SAMPLER_2D_ARRAY_EXT 0x8DC1 +#define GL_SAMPLER_BUFFER_EXT 0x8DC2 +#define GL_SAMPLER_1D_ARRAY_SHADOW_EXT 0x8DC3 +#define GL_SAMPLER_2D_ARRAY_SHADOW_EXT 0x8DC4 +#define GL_SAMPLER_CUBE_SHADOW_EXT 0x8DC5 + +#define GL_PACK_SKIP_IMAGES 0x806B +#define GL_PACK_IMAGE_HEIGHT 0x806C +#define GL_UNPACK_SKIP_IMAGES 0x806D +#define GL_UNPACK_IMAGE_HEIGHT 0x806E +#define GL_TEXTURE_3D 0x806F +#define GL_PROXY_TEXTURE_3D 0x8070 +#define GL_TEXTURE_DEPTH 0x8071 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE 0x8073 +#define GL_TEXTURE_BINDING_3D 0x806A +#define GL_TEXTURE_CUBE_MAP_SEAMLESS 0x884F +#define GL_STENCIL_TEST_TWO_SIDE_EXT 0x8910 +#define GL_ACTIVE_STENCIL_FACE_EXT 0x8911 +#define GL_STENCIL_BACK_FUNC 0x8800 +#define GL_STENCIL_BACK_FAIL 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS 0x8803 + +#define GL_DEPTH_TEXTURE_MODE_ARB 0x884B +#define GL_TEXTURE_COMPARE_MODE_ARB 0x884C +#define GL_TEXTURE_COMPARE_FUNC_ARB 0x884D +#define GL_COMPARE_R_TO_TEXTURE_ARB 0x884E +#define GL_TEXTURE_COMPARE_FAIL_VALUE_ARB 0x80BF + +#define GL_QUERY_COUNTER_BITS_ARB 0x8864 +#define GL_CURRENT_QUERY_ARB 0x8865 +#define GL_QUERY_RESULT_ARB 0x8866 +#define GL_QUERY_RESULT_AVAILABLE_ARB 0x8867 +#define GL_SAMPLES_PASSED_ARB 0x8914 + +#define GL_FUNC_ADD_EXT 0x8006 +#define GL_FUNC_SUBTRACT_EXT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT_EXT 0x800B +#define GL_MIN_EXT 0x8007 +#define GL_MAX_EXT 0x8008 +#define GL_BLEND_EQUATION_EXT 0x8009 + +#define GL_DEPTH_COMPONENT16 0x81A5 +#define GL_DEPTH_COMPONENT24 0x81A6 +#define GL_DEPTH_COMPONENT32 0x81A7 +#define GL_VERTEX_SHADER_ARB 0x8B31 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB 0x8B4A +#define GL_MAX_VARYING_FLOATS_ARB 0x8B4B +#define GL_MAX_VERTEX_ATTRIBS_ARB 0x8869 +#define GL_MAX_TEXTURE_IMAGE_UNITS_ARB 0x8872 +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB 0x8B4D +#define GL_MAX_TEXTURE_COORDS_ARB 0x8871 +#define GL_VERTEX_PROGRAM_POINT_SIZE_ARB 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_ARB 0x8643 +#define GL_OBJECT_ACTIVE_ATTRIBUTES_ARB 0x8B89 +#define GL_OBJECT_ACTIVE_ATTRIBUTE_MAX_LENGTH_ARB 0x8B8A +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED_ARB 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE_ARB 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE_ARB 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE_ARB 0x8625 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED_ARB 0x886A +#define GL_CURRENT_VERTEX_ATTRIB_ARB 0x8626 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER_ARB 0x8645 +#define GL_FLOAT_VEC2_ARB 0x8B50 +#define GL_FLOAT_VEC3_ARB 0x8B51 +#define GL_FLOAT_VEC4_ARB 0x8B52 +#define GL_FLOAT_MAT2_ARB 0x8B5A +#define GL_FLOAT_MAT3_ARB 0x8B5B +#define GL_FLOAT_MAT4_ARB 0x8B5C + +#define GL_HALF_FLOAT_ARB 0x140B + +#define GL_RGBA_FLOAT_MODE_ARB 0x8820 +#define GL_CLAMP_VERTEX_COLOR_ARB 0x891A +#define GL_CLAMP_FRAGMENT_COLOR_ARB 0x891B +#define GL_CLAMP_READ_COLOR_ARB 0x891C +#define GL_FIXED_ONLY_ARB 0x891D + +#define GL_FRAGMENT_SHADER_ARB 0x8B30 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB 0x8B49 +#define GL_MAX_TEXTURE_COORDS_ARB 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_ARB 0x8872 +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT_ARB 0x8B8B + +//GL_ARB_vertex_buffer_object +#define GL_ARRAY_BUFFER_ARB 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER_ARB 0x8893 +#define GL_ARRAY_BUFFER_BINDING_ARB 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING_ARB 0x8895 +#define GL_VERTEX_ARRAY_BUFFER_BINDING_ARB 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING_ARB 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING_ARB 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING_ARB 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING_ARB 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING_ARB 0x889B +#define GL_WEIGHT_ARRAY_BUFFER_BINDING_ARB 0x889E +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB 0x889F +#define GL_STREAM_DRAW_ARB 0x88E0 +#define GL_STREAM_READ_ARB 0x88E1 +#define GL_STREAM_COPY_ARB 0x88E2 +#define GL_STATIC_DRAW_ARB 0x88E4 +#define GL_STATIC_READ_ARB 0x88E5 +#define GL_STATIC_COPY_ARB 0x88E6 +#define GL_DYNAMIC_DRAW_ARB 0x88E8 +#define GL_DYNAMIC_READ_ARB 0x88E9 +#define GL_DYNAMIC_COPY_ARB 0x88EA +#define GL_READ_ONLY_ARB 0x88B8 +#define GL_WRITE_ONLY_ARB 0x88B9 +#define GL_READ_WRITE_ARB 0x88BA +#define GL_BUFFER_SIZE_ARB 0x8764 +#define GL_BUFFER_USAGE_ARB 0x8765 +#define GL_BUFFER_ACCESS_ARB 0x88BB +#define GL_BUFFER_MAPPED_ARB 0x88BC +#define GL_BUFFER_MAP_POINTER_ARB 0x88BD +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING_ARB 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING_ARB 0x889D + +#define GL_NORMAL_MAP_ARB 0x8511 +#define GL_REFLECTION_MAP_ARB 0x8512 +#define GL_TEXTURE_CUBE_MAP_ARB 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARB 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_ARB 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB 0x851C + +#define GL_COMBINE_ARB 0x8570 +#define GL_COMBINE_RGB_ARB 0x8571 +#define GL_COMBINE_ALPHA_ARB 0x8572 +#define GL_SOURCE0_RGB_ARB 0x8580 +#define GL_SOURCE1_RGB_ARB 0x8581 +#define GL_SOURCE2_RGB_ARB 0x8582 +#define GL_SOURCE0_ALPHA_ARB 0x8588 +#define GL_SOURCE1_ALPHA_ARB 0x8589 +#define GL_SOURCE2_ALPHA_ARB 0x858A +#define GL_OPERAND0_RGB_ARB 0x8590 +#define GL_OPERAND1_RGB_ARB 0x8591 +#define GL_OPERAND2_RGB_ARB 0x8592 +#define GL_OPERAND0_ALPHA_ARB 0x8598 +#define GL_OPERAND1_ALPHA_ARB 0x8599 +#define GL_OPERAND2_ALPHA_ARB 0x859A +#define GL_RGB_SCALE_ARB 0x8573 +#define GL_ADD_SIGNED_ARB 0x8574 +#define GL_INTERPOLATE_ARB 0x8575 +#define GL_SUBTRACT_ARB 0x84E7 +#define GL_CONSTANT_ARB 0x8576 +#define GL_PRIMARY_COLOR_ARB 0x8577 +#define GL_PREVIOUS_ARB 0x8578 + +#define GL_DOT3_RGB_ARB 0x86AE +#define GL_DOT3_RGBA_ARB 0x86AF + +#define GL_TEXTURE_1D_ARRAY_EXT 0x8C18 +#define GL_PROXY_TEXTURE_1D_ARRAY_EXT 0x8C19 +#define GL_TEXTURE_2D_ARRAY_EXT 0x8C1A +#define GL_PROXY_TEXTURE_2D_ARRAY_EXT 0x8C1B +#define GL_TEXTURE_BINDING_1D_ARRAY_EXT 0x8C1C +#define GL_TEXTURE_BINDING_2D_ARRAY_EXT 0x8C1D +#define GL_MAX_ARRAY_TEXTURE_LAYERS_EXT 0x88FF +#define GL_COMPARE_REF_DEPTH_TO_TEXTURE_EXT 0x884E + +#define GL_MULTISAMPLE_ARB 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE_ARB 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_ARB 0x809F +#define GL_SAMPLE_COVERAGE_ARB 0x80A0 +#define GL_SAMPLE_BUFFERS_ARB 0x80A8 +#define GL_SAMPLES_ARB 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE_ARB 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT_ARB 0x80AB +#define GL_MULTISAMPLE_BIT_ARB 0x20000000 + +#define GL_COLOR_SUM_ARB 0x8458 +#define GL_VERTEX_PROGRAM_ARB 0x8620 +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED_ARB 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE_ARB 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE_ARB 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE_ARB 0x8625 +#define GL_CURRENT_VERTEX_ATTRIB_ARB 0x8626 +#define GL_PROGRAM_LENGTH_ARB 0x8627 +#define GL_PROGRAM_STRING_ARB 0x8628 +#define GL_MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB 0x862E +#define GL_MAX_PROGRAM_MATRICES_ARB 0x862F +#define GL_CURRENT_MATRIX_STACK_DEPTH_ARB 0x8640 +#define GL_CURRENT_MATRIX_ARB 0x8641 +#define GL_VERTEX_PROGRAM_POINT_SIZE_ARB 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_ARB 0x8643 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER_ARB 0x8645 +#define GL_PROGRAM_ERROR_POSITION_ARB 0x864B +#define GL_PROGRAM_BINDING_ARB 0x8677 +#define GL_MAX_VERTEX_ATTRIBS_ARB 0x8869 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED_ARB 0x886A +#define GL_PROGRAM_ERROR_STRING_ARB 0x8874 +#define GL_PROGRAM_FORMAT_ASCII_ARB 0x8875 +#define GL_PROGRAM_FORMAT_ARB 0x8876 +#define GL_PROGRAM_INSTRUCTIONS_ARB 0x88A0 +#define GL_MAX_PROGRAM_INSTRUCTIONS_ARB 0x88A1 +#define GL_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A2 +#define GL_MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A3 +#define GL_PROGRAM_TEMPORARIES_ARB 0x88A4 +#define GL_MAX_PROGRAM_TEMPORARIES_ARB 0x88A5 +#define GL_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A6 +#define GL_MAX_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A7 +#define GL_PROGRAM_PARAMETERS_ARB 0x88A8 +#define GL_MAX_PROGRAM_PARAMETERS_ARB 0x88A9 +#define GL_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AA +#define GL_MAX_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AB +#define GL_PROGRAM_ATTRIBS_ARB 0x88AC +#define GL_MAX_PROGRAM_ATTRIBS_ARB 0x88AD +#define GL_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AE +#define GL_MAX_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AF +#define GL_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B0 +#define GL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B1 +#define GL_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B2 +#define GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B3 +#define GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB 0x88B4 +#define GL_MAX_PROGRAM_ENV_PARAMETERS_ARB 0x88B5 +#define GL_PROGRAM_UNDER_NATIVE_LIMITS_ARB 0x88B6 +#define GL_TRANSPOSE_CURRENT_MATRIX_ARB 0x88B7 +#define GL_MATRIX0_ARB 0x88C0 +#define GL_MATRIX1_ARB 0x88C1 +#define GL_MATRIX2_ARB 0x88C2 +#define GL_MATRIX3_ARB 0x88C3 +#define GL_MATRIX4_ARB 0x88C4 +#define GL_MATRIX5_ARB 0x88C5 +#define GL_MATRIX6_ARB 0x88C6 +#define GL_MATRIX7_ARB 0x88C7 +#define GL_MATRIX8_ARB 0x88C8 +#define GL_MATRIX9_ARB 0x88C9 +#define GL_MATRIX10_ARB 0x88CA +#define GL_MATRIX11_ARB 0x88CB +#define GL_MATRIX12_ARB 0x88CC +#define GL_MATRIX13_ARB 0x88CD +#define GL_MATRIX14_ARB 0x88CE +#define GL_MATRIX15_ARB 0x88CF +#define GL_MATRIX16_ARB 0x88D0 +#define GL_MATRIX17_ARB 0x88D1 +#define GL_MATRIX18_ARB 0x88D2 +#define GL_MATRIX19_ARB 0x88D3 +#define GL_MATRIX20_ARB 0x88D4 +#define GL_MATRIX21_ARB 0x88D5 +#define GL_MATRIX22_ARB 0x88D6 +#define GL_MATRIX23_ARB 0x88D7 +#define GL_MATRIX24_ARB 0x88D8 +#define GL_MATRIX25_ARB 0x88D9 +#define GL_MATRIX26_ARB 0x88DA +#define GL_MATRIX27_ARB 0x88DB +#define GL_MATRIX28_ARB 0x88DC +#define GL_MATRIX29_ARB 0x88DD +#define GL_MATRIX30_ARB 0x88DE +#define GL_MATRIX31_ARB 0x88DF +#define GL_FRAGMENT_PROGRAM_ARB 0x8804 +#define GL_PROGRAM_ALU_INSTRUCTIONS_ARB 0x8805 +#define GL_PROGRAM_TEX_INSTRUCTIONS_ARB 0x8806 +#define GL_PROGRAM_TEX_INDIRECTIONS_ARB 0x8807 +#define GL_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x8808 +#define GL_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x8809 +#define GL_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x880A +#define GL_MAX_PROGRAM_ALU_INSTRUCTIONS_ARB 0x880B +#define GL_MAX_PROGRAM_TEX_INSTRUCTIONS_ARB 0x880C +#define GL_MAX_PROGRAM_TEX_INDIRECTIONS_ARB 0x880D +#define GL_MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x880E +#define GL_MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x880F +#define GL_MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x8810 +#define GL_MAX_TEXTURE_COORDS_ARB 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_ARB 0x8872 + +#define GL_INVALID_FRAMEBUFFER_OPERATION_EXT 0x0506 +#define GL_MAX_RENDERBUFFER_SIZE_EXT 0x84E8 +#define GL_FRAMEBUFFER_BINDING_EXT 0x8CA6 +#define GL_RENDERBUFFER_BINDING_EXT 0x8CA7 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_EXT 0x8CD0 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT 0x8CD1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL_EXT 0x8CD2 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE_EXT 0x8CD3 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_3D_ZOFFSET_EXT 0x8CD4 +#define GL_FRAMEBUFFER_COMPLETE_EXT 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT 0x8CD9 +#define GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT 0x8CDA +#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT 0x8CDB +#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT 0x8CDC +#define GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT 0x8D56 +#define GL_FRAMEBUFFER_UNSUPPORTED_EXT 0x8CDD +#define GL_FRAMEBUFFER_UNDEFINED_EXT 0x8219 +#define GL_MAX_COLOR_ATTACHMENTS_EXT 0x8CDF +#define GL_COLOR_ATTACHMENT0_EXT 0x8CE0 +#define GL_COLOR_ATTACHMENT1_EXT 0x8CE1 +#define GL_COLOR_ATTACHMENT2_EXT 0x8CE2 +#define GL_COLOR_ATTACHMENT3_EXT 0x8CE3 +#define GL_COLOR_ATTACHMENT4_EXT 0x8CE4 +#define GL_COLOR_ATTACHMENT5_EXT 0x8CE5 +#define GL_COLOR_ATTACHMENT6_EXT 0x8CE6 +#define GL_COLOR_ATTACHMENT7_EXT 0x8CE7 +#define GL_COLOR_ATTACHMENT8_EXT 0x8CE8 +#define GL_COLOR_ATTACHMENT9_EXT 0x8CE9 +#define GL_COLOR_ATTACHMENT10_EXT 0x8CEA +#define GL_COLOR_ATTACHMENT11_EXT 0x8CEB +#define GL_COLOR_ATTACHMENT12_EXT 0x8CEC +#define GL_COLOR_ATTACHMENT13_EXT 0x8CED +#define GL_COLOR_ATTACHMENT14_EXT 0x8CEE +#define GL_COLOR_ATTACHMENT15_EXT 0x8CEF +#define GL_DEPTH_ATTACHMENT_EXT 0x8D00 +#define GL_STENCIL_ATTACHMENT_EXT 0x8D20 +#define GL_FRAMEBUFFER_EXT 0x8D40 +#define GL_RENDERBUFFER_EXT 0x8D41 +#define GL_RENDERBUFFER_WIDTH_EXT 0x8D42 +#define GL_RENDERBUFFER_HEIGHT_EXT 0x8D43 +#define GL_RENDERBUFFER_INTERNAL_FORMAT_EXT 0x8D44 +#define GL_STENCIL_INDEX1_EXT 0x8D46 +#define GL_STENCIL_INDEX4_EXT 0x8D47 +#define GL_STENCIL_INDEX8_EXT 0x8D48 +#define GL_STENCIL_INDEX16_EXT 0x8D49 +#define GL_RENDERBUFFER_RED_SIZE_EXT 0x8D50 +#define GL_RENDERBUFFER_GREEN_SIZE_EXT 0x8D51 +#define GL_RENDERBUFFER_BLUE_SIZE_EXT 0x8D52 +#define GL_RENDERBUFFER_ALPHA_SIZE_EXT 0x8D53 +#define GL_RENDERBUFFER_DEPTH_SIZE_EXT 0x8D54 +#define GL_RENDERBUFFER_STENCIL_SIZE_EXT 0x8D55 +#define GL_VERTEX_ARRAY_BINDING 0x85B5 + +#define GL_GPU_MEM_INFO_TOTAL_AVAILABLE_MEM_NVX 0x9048 +#define GL_GPU_MEM_INFO_CURRENT_AVAILABLE_MEM_NVX 0x9049 + +#define GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB 0x8242 +#define GL_MAX_DEBUG_MESSAGE_LENGTH_ARB 0x9143 +#define GL_MAX_DEBUG_LOGGED_MESSAGES_ARB 0x9144 +#define GL_DEBUG_LOGGED_MESSAGES_ARB 0x9145 +#define GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_ARB 0x8243 +#define GL_DEBUG_CALLBACK_FUNCTION_ARB 0x8244 +#define GL_DEBUG_CALLBACK_USER_PARAM_ARB 0x8245 +#define GL_DEBUG_SOURCE_API_ARB 0x8246 +#define GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB 0x8247 +#define GL_DEBUG_SOURCE_SHADER_COMPILER_ARB 0x8248 +#define GL_DEBUG_SOURCE_THIRD_PARTY_ARB 0x8249 +#define GL_DEBUG_SOURCE_APPLICATION_ARB 0x824A +#define GL_DEBUG_SOURCE_OTHER_ARB 0x824B +#define GL_DEBUG_TYPE_ERROR_ARB 0x824C +#define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB 0x824D +#define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB 0x824E +#define GL_DEBUG_TYPE_PORTABILITY_ARB 0x824F +#define GL_DEBUG_TYPE_PERFORMANCE_ARB 0x8250 +#define GL_DEBUG_TYPE_OTHER_ARB 0x8251 +#define GL_DEBUG_SEVERITY_HIGH_ARB 0x9146 +#define GL_DEBUG_SEVERITY_MEDIUM_ARB 0x9147 +#define GL_DEBUG_SEVERITY_LOW_ARB 0x9148 + +// helper opengl functions +EXTERN GLenum ( APIENTRY *pglGetError )(void); +EXTERN const GLcharARB * ( APIENTRY *pglGetString )(GLenum name); + +// base gl functions +EXTERN void ( APIENTRY *pglAccum )(GLenum op, GLfloat value); +EXTERN void ( APIENTRY *pglAlphaFunc )(GLenum func, GLclampf ref); +EXTERN void ( APIENTRY *pglBegin )(GLenum mode); +EXTERN void ( APIENTRY *pglBindTexture )(GLenum target, GLuint texture); +EXTERN void ( APIENTRY *pglBitmap )(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); +EXTERN void ( APIENTRY *pglBlendFunc )(GLenum sfactor, GLenum dfactor); +EXTERN void ( APIENTRY *pglCallList )(GLuint list); +EXTERN void ( APIENTRY *pglCallLists )(GLsizei n, GLenum type, const GLvoid *lists); +EXTERN void ( APIENTRY *pglClear )(GLbitfield mask); +EXTERN void ( APIENTRY *pglClearAccum )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +EXTERN void ( APIENTRY *pglClearColor )(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +EXTERN void ( APIENTRY *pglClearDepth )(GLclampd depth); +EXTERN void ( APIENTRY *pglClearIndex )(GLfloat c); +EXTERN void ( APIENTRY *pglClearStencil )(GLint s); +EXTERN GLboolean ( APIENTRY *pglIsEnabled )( GLenum cap ); +EXTERN GLboolean ( APIENTRY *pglIsList )( GLuint list ); +EXTERN GLboolean ( APIENTRY *pglIsTexture )( GLuint texture ); +EXTERN void ( APIENTRY *pglClipPlane )(GLenum plane, const GLdouble *equation); +EXTERN void ( APIENTRY *pglColor3b )(GLbyte red, GLbyte green, GLbyte blue); +EXTERN void ( APIENTRY *pglColor3bv )(const GLbyte *v); +EXTERN void ( APIENTRY *pglColor3d )(GLdouble red, GLdouble green, GLdouble blue); +EXTERN void ( APIENTRY *pglColor3dv )(const GLdouble *v); +EXTERN void ( APIENTRY *pglColor3f )(GLfloat red, GLfloat green, GLfloat blue); +EXTERN void ( APIENTRY *pglColor3fv )(const GLfloat *v); +EXTERN void ( APIENTRY *pglColor3i )(GLint red, GLint green, GLint blue); +EXTERN void ( APIENTRY *pglColor3iv )(const GLint *v); +EXTERN void ( APIENTRY *pglColor3s )(GLshort red, GLshort green, GLshort blue); +EXTERN void ( APIENTRY *pglColor3sv )(const GLshort *v); +EXTERN void ( APIENTRY *pglColor3ub )(GLubyte red, GLubyte green, GLubyte blue); +EXTERN void ( APIENTRY *pglColor3ubv )(const GLubyte *v); +EXTERN void ( APIENTRY *pglColor3ui )(GLuint red, GLuint green, GLuint blue); +EXTERN void ( APIENTRY *pglColor3uiv )(const GLuint *v); +EXTERN void ( APIENTRY *pglColor3us )(GLushort red, GLushort green, GLushort blue); +EXTERN void ( APIENTRY *pglColor3usv )(const GLushort *v); +EXTERN void ( APIENTRY *pglColor4b )(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +EXTERN void ( APIENTRY *pglColor4bv )(const GLbyte *v); +EXTERN void ( APIENTRY *pglColor4d )(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +EXTERN void ( APIENTRY *pglColor4dv )(const GLdouble *v); +EXTERN void ( APIENTRY *pglColor4f )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +EXTERN void ( APIENTRY *pglColor4fv )(const GLfloat *v); +EXTERN void ( APIENTRY *pglColor4i )(GLint red, GLint green, GLint blue, GLint alpha); +EXTERN void ( APIENTRY *pglColor4iv )(const GLint *v); +EXTERN void ( APIENTRY *pglColor4s )(GLshort red, GLshort green, GLshort blue, GLshort alpha); +EXTERN void ( APIENTRY *pglColor4sv )(const GLshort *v); +EXTERN void ( APIENTRY *pglColor4ub )(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +EXTERN void ( APIENTRY *pglColor4ubv )(const GLubyte *v); +EXTERN void ( APIENTRY *pglColor4ui )(GLuint red, GLuint green, GLuint blue, GLuint alpha); +EXTERN void ( APIENTRY *pglColor4uiv )(const GLuint *v); +EXTERN void ( APIENTRY *pglColor4us )(GLushort red, GLushort green, GLushort blue, GLushort alpha); +EXTERN void ( APIENTRY *pglColor4usv )(const GLushort *v); +EXTERN void ( APIENTRY *pglColorMask )(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +EXTERN void ( APIENTRY *pglColorMaterial )(GLenum face, GLenum mode); +EXTERN void ( APIENTRY *pglCopyPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +EXTERN void ( APIENTRY *pglCopyTexImage1D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); +EXTERN void ( APIENTRY *pglCopyTexImage2D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +EXTERN void ( APIENTRY *pglCopyTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +EXTERN void ( APIENTRY *pglCopyTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +EXTERN void ( APIENTRY *pglCullFace )(GLenum mode); +EXTERN void ( APIENTRY *pglDeleteLists )(GLuint list, GLsizei range); +EXTERN void ( APIENTRY *pglDeleteTextures )(GLsizei n, const GLuint *textures); +EXTERN void ( APIENTRY *pglDepthFunc )(GLenum func); +EXTERN void ( APIENTRY *pglDepthMask )(GLboolean flag); +EXTERN void ( APIENTRY *pglDepthRange )(GLclampd zNear, GLclampd zFar); +EXTERN void ( APIENTRY *pglDisable )(GLenum cap); +EXTERN void ( APIENTRY *pglDisableClientState )(GLenum array); +EXTERN void ( APIENTRY *pglDrawArrays )(GLenum mode, GLint first, GLsizei count); +EXTERN void ( APIENTRY *pglDrawBuffer )(GLenum mode); +EXTERN void ( APIENTRY *pglDrawPixels )(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +EXTERN void ( APIENTRY *pglEdgeFlag )(GLboolean flag); +EXTERN void ( APIENTRY *pglEdgeFlagPointer )(GLsizei stride, const GLvoid *pointer); +EXTERN void ( APIENTRY *pglEdgeFlagv )(const GLboolean *flag); +EXTERN void ( APIENTRY *pglEnable )(GLenum cap); +EXTERN void ( APIENTRY *pglEnableClientState )(GLenum array); +EXTERN void ( APIENTRY *pglEnd )(void); +EXTERN void ( APIENTRY *pglEndList )(void); +EXTERN void ( APIENTRY *pglEvalCoord1d )(GLdouble u); +EXTERN void ( APIENTRY *pglEvalCoord1dv )(const GLdouble *u); +EXTERN void ( APIENTRY *pglEvalCoord1f )(GLfloat u); +EXTERN void ( APIENTRY *pglEvalCoord1fv )(const GLfloat *u); +EXTERN void ( APIENTRY *pglEvalCoord2d )(GLdouble u, GLdouble v); +EXTERN void ( APIENTRY *pglEvalCoord2dv )(const GLdouble *u); +EXTERN void ( APIENTRY *pglEvalCoord2f )(GLfloat u, GLfloat v); +EXTERN void ( APIENTRY *pglEvalCoord2fv )(const GLfloat *u); +EXTERN void ( APIENTRY *pglEvalMesh1 )(GLenum mode, GLint i1, GLint i2); +EXTERN void ( APIENTRY *pglEvalMesh2 )(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +EXTERN void ( APIENTRY *pglEvalPoint1 )(GLint i); +EXTERN void ( APIENTRY *pglEvalPoint2 )(GLint i, GLint j); +EXTERN void ( APIENTRY *pglFeedbackBuffer )(GLsizei size, GLenum type, GLfloat *buffer); +EXTERN void ( APIENTRY *pglFinish )(void); +EXTERN void ( APIENTRY *pglFlush )(void); +EXTERN void ( APIENTRY *pglFogf )(GLenum pname, GLfloat param); +EXTERN void ( APIENTRY *pglFogfv )(GLenum pname, const GLfloat *params); +EXTERN void ( APIENTRY *pglFogi )(GLenum pname, GLint param); +EXTERN void ( APIENTRY *pglFogiv )(GLenum pname, const GLint *params); +EXTERN void ( APIENTRY *pglFrontFace )(GLenum mode); +EXTERN void ( APIENTRY *pglFrustum )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +EXTERN void ( APIENTRY *pglGenTextures )(GLsizei n, GLuint *textures); +EXTERN void ( APIENTRY *pglGetBooleanv )(GLenum pname, GLboolean *params); +EXTERN void ( APIENTRY *pglGetClipPlane )(GLenum plane, GLdouble *equation); +EXTERN void ( APIENTRY *pglGetDoublev )(GLenum pname, GLdouble *params); +EXTERN void ( APIENTRY *pglGetFloatv )(GLenum pname, GLfloat *params); +EXTERN void ( APIENTRY *pglGetIntegerv )(GLenum pname, GLint *params); +EXTERN void ( APIENTRY *pglGetLightfv )(GLenum light, GLenum pname, GLfloat *params); +EXTERN void ( APIENTRY *pglGetLightiv )(GLenum light, GLenum pname, GLint *params); +EXTERN void ( APIENTRY *pglGetMapdv )(GLenum target, GLenum query, GLdouble *v); +EXTERN void ( APIENTRY *pglGetMapfv )(GLenum target, GLenum query, GLfloat *v); +EXTERN void ( APIENTRY *pglGetMapiv )(GLenum target, GLenum query, GLint *v); +EXTERN void ( APIENTRY *pglGetMaterialfv )(GLenum face, GLenum pname, GLfloat *params); +EXTERN void ( APIENTRY *pglGetMaterialiv )(GLenum face, GLenum pname, GLint *params); +EXTERN void ( APIENTRY *pglGetPixelMapfv )(GLenum map, GLfloat *values); +EXTERN void ( APIENTRY *pglGetPixelMapuiv )(GLenum map, GLuint *values); +EXTERN void ( APIENTRY *pglGetPixelMapusv )(GLenum map, GLushort *values); +EXTERN void ( APIENTRY *pglGetPointerv )(GLenum pname, GLvoid* *params); +EXTERN void ( APIENTRY *pglGetPolygonStipple )(GLubyte *mask); +EXTERN void ( APIENTRY *pglGetTexEnvfv )(GLenum target, GLenum pname, GLfloat *params); +EXTERN void ( APIENTRY *pglGetTexEnviv )(GLenum target, GLenum pname, GLint *params); +EXTERN void ( APIENTRY *pglGetTexGendv )(GLenum coord, GLenum pname, GLdouble *params); +EXTERN void ( APIENTRY *pglGetTexGenfv )(GLenum coord, GLenum pname, GLfloat *params); +EXTERN void ( APIENTRY *pglGetTexGeniv )(GLenum coord, GLenum pname, GLint *params); +EXTERN void ( APIENTRY *pglGetTexImage )(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +EXTERN void ( APIENTRY *pglGetTexLevelParameterfv )(GLenum target, GLint level, GLenum pname, GLfloat *params); +EXTERN void ( APIENTRY *pglGetTexLevelParameteriv )(GLenum target, GLint level, GLenum pname, GLint *params); +EXTERN void ( APIENTRY *pglGetTexParameterfv )(GLenum target, GLenum pname, GLfloat *params); +EXTERN void ( APIENTRY *pglGetTexParameteriv )(GLenum target, GLenum pname, GLint *params); +EXTERN void ( APIENTRY *pglHint )(GLenum target, GLenum mode); +EXTERN void ( APIENTRY *pglIndexMask )(GLuint mask); +EXTERN void ( APIENTRY *pglIndexPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +EXTERN void ( APIENTRY *pglIndexd )(GLdouble c); +EXTERN void ( APIENTRY *pglIndexdv )(const GLdouble *c); +EXTERN void ( APIENTRY *pglIndexf )(GLfloat c); +EXTERN void ( APIENTRY *pglIndexfv )(const GLfloat *c); +EXTERN void ( APIENTRY *pglIndexi )(GLint c); +EXTERN void ( APIENTRY *pglIndexiv )(const GLint *c); +EXTERN void ( APIENTRY *pglIndexs )(GLshort c); +EXTERN void ( APIENTRY *pglIndexsv )(const GLshort *c); +EXTERN void ( APIENTRY *pglIndexub )(GLubyte c); +EXTERN void ( APIENTRY *pglIndexubv )(const GLubyte *c); +EXTERN void ( APIENTRY *pglInitNames )(void); +EXTERN void ( APIENTRY *pglInterleavedArrays )(GLenum format, GLsizei stride, const GLvoid *pointer); +EXTERN void ( APIENTRY *pglLightModelf )(GLenum pname, GLfloat param); +EXTERN void ( APIENTRY *pglLightModelfv )(GLenum pname, const GLfloat *params); +EXTERN void ( APIENTRY *pglLightModeli )(GLenum pname, GLint param); +EXTERN void ( APIENTRY *pglLightModeliv )(GLenum pname, const GLint *params); +EXTERN void ( APIENTRY *pglLightf )(GLenum light, GLenum pname, GLfloat param); +EXTERN void ( APIENTRY *pglLightfv )(GLenum light, GLenum pname, const GLfloat *params); +EXTERN void ( APIENTRY *pglLighti )(GLenum light, GLenum pname, GLint param); +EXTERN void ( APIENTRY *pglLightiv )(GLenum light, GLenum pname, const GLint *params); +EXTERN void ( APIENTRY *pglLineStipple )(GLint factor, GLushort pattern); +EXTERN void ( APIENTRY *pglLineWidth )(GLfloat width); +EXTERN void ( APIENTRY *pglListBase )(GLuint base); +EXTERN void ( APIENTRY *pglLoadIdentity )(void); +EXTERN void ( APIENTRY *pglLoadMatrixd )(const GLdouble *m); +EXTERN void ( APIENTRY *pglLoadMatrixf )(const GLfloat *m); +EXTERN void ( APIENTRY *pglLoadName )(GLuint name); +EXTERN void ( APIENTRY *pglLogicOp )(GLenum opcode); +EXTERN void ( APIENTRY *pglMap1d )(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +EXTERN void ( APIENTRY *pglMap1f )(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +EXTERN void ( APIENTRY *pglMap2d )(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +EXTERN void ( APIENTRY *pglMap2f )(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +EXTERN void ( APIENTRY *pglMapGrid1d )(GLint un, GLdouble u1, GLdouble u2); +EXTERN void ( APIENTRY *pglMapGrid1f )(GLint un, GLfloat u1, GLfloat u2); +EXTERN void ( APIENTRY *pglMapGrid2d )(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +EXTERN void ( APIENTRY *pglMapGrid2f )(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +EXTERN void ( APIENTRY *pglMaterialf )(GLenum face, GLenum pname, GLfloat param); +EXTERN void ( APIENTRY *pglMaterialfv )(GLenum face, GLenum pname, const GLfloat *params); +EXTERN void ( APIENTRY *pglMateriali )(GLenum face, GLenum pname, GLint param); +EXTERN void ( APIENTRY *pglMaterialiv )(GLenum face, GLenum pname, const GLint *params); +EXTERN void ( APIENTRY *pglMatrixMode )(GLenum mode); +EXTERN void ( APIENTRY *pglMultMatrixd )(const GLdouble *m); +EXTERN void ( APIENTRY *pglMultMatrixf )(const GLfloat *m); +EXTERN void ( APIENTRY *pglNewList )(GLuint list, GLenum mode); +EXTERN void ( APIENTRY *pglNormal3b )(GLbyte nx, GLbyte ny, GLbyte nz); +EXTERN void ( APIENTRY *pglNormal3bv )(const GLbyte *v); +EXTERN void ( APIENTRY *pglNormal3d )(GLdouble nx, GLdouble ny, GLdouble nz); +EXTERN void ( APIENTRY *pglNormal3dv )(const GLdouble *v); +EXTERN void ( APIENTRY *pglNormal3f )(GLfloat nx, GLfloat ny, GLfloat nz); +EXTERN void ( APIENTRY *pglNormal3fv )(const GLfloat *v); +EXTERN void ( APIENTRY *pglNormal3i )(GLint nx, GLint ny, GLint nz); +EXTERN void ( APIENTRY *pglNormal3iv )(const GLint *v); +EXTERN void ( APIENTRY *pglNormal3s )(GLshort nx, GLshort ny, GLshort nz); +EXTERN void ( APIENTRY *pglNormal3sv )(const GLshort *v); +EXTERN void ( APIENTRY *pglOrtho )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +EXTERN void ( APIENTRY *pglPassThrough )(GLfloat token); +EXTERN void ( APIENTRY *pglPixelMapfv )(GLenum map, GLsizei mapsize, const GLfloat *values); +EXTERN void ( APIENTRY *pglPixelMapuiv )(GLenum map, GLsizei mapsize, const GLuint *values); +EXTERN void ( APIENTRY *pglPixelMapusv )(GLenum map, GLsizei mapsize, const GLushort *values); +EXTERN void ( APIENTRY *pglPixelStoref )(GLenum pname, GLfloat param); +EXTERN void ( APIENTRY *pglPixelStorei )(GLenum pname, GLint param); +EXTERN void ( APIENTRY *pglPixelTransferf )(GLenum pname, GLfloat param); +EXTERN void ( APIENTRY *pglPixelTransferi )(GLenum pname, GLint param); +EXTERN void ( APIENTRY *pglPixelZoom )(GLfloat xfactor, GLfloat yfactor); +EXTERN void ( APIENTRY *pglPointSize )(GLfloat size); +EXTERN void ( APIENTRY *pglPolygonMode )(GLenum face, GLenum mode); +EXTERN void ( APIENTRY *pglPolygonOffset )(GLfloat factor, GLfloat units); +EXTERN void ( APIENTRY *pglPolygonStipple )(const GLubyte *mask); +EXTERN void ( APIENTRY *pglPopAttrib )(void); +EXTERN void ( APIENTRY *pglPopClientAttrib )(void); +EXTERN void ( APIENTRY *pglPopMatrix )(void); +EXTERN void ( APIENTRY *pglPopName )(void); +EXTERN void ( APIENTRY *pglPushAttrib )(GLbitfield mask); +EXTERN void ( APIENTRY *pglPushClientAttrib )(GLbitfield mask); +EXTERN void ( APIENTRY *pglPushMatrix )(void); +EXTERN void ( APIENTRY *pglPushName )(GLuint name); +EXTERN void ( APIENTRY *pglRasterPos2d )(GLdouble x, GLdouble y); +EXTERN void ( APIENTRY *pglRasterPos2dv )(const GLdouble *v); +EXTERN void ( APIENTRY *pglRasterPos2f )(GLfloat x, GLfloat y); +EXTERN void ( APIENTRY *pglRasterPos2fv )(const GLfloat *v); +EXTERN void ( APIENTRY *pglRasterPos2i )(GLint x, GLint y); +EXTERN void ( APIENTRY *pglRasterPos2iv )(const GLint *v); +EXTERN void ( APIENTRY *pglRasterPos2s )(GLshort x, GLshort y); +EXTERN void ( APIENTRY *pglRasterPos2sv )(const GLshort *v); +EXTERN void ( APIENTRY *pglRasterPos3d )(GLdouble x, GLdouble y, GLdouble z); +EXTERN void ( APIENTRY *pglRasterPos3dv )(const GLdouble *v); +EXTERN void ( APIENTRY *pglRasterPos3f )(GLfloat x, GLfloat y, GLfloat z); +EXTERN void ( APIENTRY *pglRasterPos3fv )(const GLfloat *v); +EXTERN void ( APIENTRY *pglRasterPos3i )(GLint x, GLint y, GLint z); +EXTERN void ( APIENTRY *pglRasterPos3iv )(const GLint *v); +EXTERN void ( APIENTRY *pglRasterPos3s )(GLshort x, GLshort y, GLshort z); +EXTERN void ( APIENTRY *pglRasterPos3sv )(const GLshort *v); +EXTERN void ( APIENTRY *pglRasterPos4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +EXTERN void ( APIENTRY *pglRasterPos4dv )(const GLdouble *v); +EXTERN void ( APIENTRY *pglRasterPos4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +EXTERN void ( APIENTRY *pglRasterPos4fv )(const GLfloat *v); +EXTERN void ( APIENTRY *pglRasterPos4i )(GLint x, GLint y, GLint z, GLint w); +EXTERN void ( APIENTRY *pglRasterPos4iv )(const GLint *v); +EXTERN void ( APIENTRY *pglRasterPos4s )(GLshort x, GLshort y, GLshort z, GLshort w); +EXTERN void ( APIENTRY *pglRasterPos4sv )(const GLshort *v); +EXTERN void ( APIENTRY *pglReadBuffer )(GLenum mode); +EXTERN void ( APIENTRY *pglReadPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); +EXTERN void ( APIENTRY *pglRectd )(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +EXTERN void ( APIENTRY *pglRectdv )(const GLdouble *v1, const GLdouble *v2); +EXTERN void ( APIENTRY *pglRectf )(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +EXTERN void ( APIENTRY *pglRectfv )(const GLfloat *v1, const GLfloat *v2); +EXTERN void ( APIENTRY *pglRecti )(GLint x1, GLint y1, GLint x2, GLint y2); +EXTERN void ( APIENTRY *pglRectiv )(const GLint *v1, const GLint *v2); +EXTERN void ( APIENTRY *pglRects )(GLshort x1, GLshort y1, GLshort x2, GLshort y2); +EXTERN void ( APIENTRY *pglRectsv )(const GLshort *v1, const GLshort *v2); +EXTERN void ( APIENTRY *pglRotated )(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +EXTERN void ( APIENTRY *pglRotatef )(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +EXTERN void ( APIENTRY *pglScaled )(GLdouble x, GLdouble y, GLdouble z); +EXTERN void ( APIENTRY *pglScalef )(GLfloat x, GLfloat y, GLfloat z); +EXTERN void ( APIENTRY *pglScissor )(GLint x, GLint y, GLsizei width, GLsizei height); +EXTERN void ( APIENTRY *pglSelectBuffer )(GLsizei size, GLuint *buffer); +EXTERN void ( APIENTRY *pglShadeModel )(GLenum mode); +EXTERN void ( APIENTRY *pglStencilFunc )(GLenum func, GLint ref, GLuint mask); +EXTERN void ( APIENTRY *pglStencilMask )(GLuint mask); +EXTERN void ( APIENTRY *pglStencilOp )(GLenum fail, GLenum zfail, GLenum zpass); +EXTERN void ( APIENTRY *pglTexCoord1d )(GLdouble s); +EXTERN void ( APIENTRY *pglTexCoord1dv )(const GLdouble *v); +EXTERN void ( APIENTRY *pglTexCoord1f )(GLfloat s); +EXTERN void ( APIENTRY *pglTexCoord1fv )(const GLfloat *v); +EXTERN void ( APIENTRY *pglTexCoord1i )(GLint s); +EXTERN void ( APIENTRY *pglTexCoord1iv )(const GLint *v); +EXTERN void ( APIENTRY *pglTexCoord1s )(GLshort s); +EXTERN void ( APIENTRY *pglTexCoord1sv )(const GLshort *v); +EXTERN void ( APIENTRY *pglTexCoord2d )(GLdouble s, GLdouble t); +EXTERN void ( APIENTRY *pglTexCoord2dv )(const GLdouble *v); +EXTERN void ( APIENTRY *pglTexCoord2f )(GLfloat s, GLfloat t); +EXTERN void ( APIENTRY *pglTexCoord2fv )(const GLfloat *v); +EXTERN void ( APIENTRY *pglTexCoord2i )(GLint s, GLint t); +EXTERN void ( APIENTRY *pglTexCoord2iv )(const GLint *v); +EXTERN void ( APIENTRY *pglTexCoord2s )(GLshort s, GLshort t); +EXTERN void ( APIENTRY *pglTexCoord2sv )(const GLshort *v); +EXTERN void ( APIENTRY *pglTexCoord3d )(GLdouble s, GLdouble t, GLdouble r); +EXTERN void ( APIENTRY *pglTexCoord3dv )(const GLdouble *v); +EXTERN void ( APIENTRY *pglTexCoord3f )(GLfloat s, GLfloat t, GLfloat r); +EXTERN void ( APIENTRY *pglTexCoord3fv )(const GLfloat *v); +EXTERN void ( APIENTRY *pglTexCoord3i )(GLint s, GLint t, GLint r); +EXTERN void ( APIENTRY *pglTexCoord3iv )(const GLint *v); +EXTERN void ( APIENTRY *pglTexCoord3s )(GLshort s, GLshort t, GLshort r); +EXTERN void ( APIENTRY *pglTexCoord3sv )(const GLshort *v); +EXTERN void ( APIENTRY *pglTexCoord4d )(GLdouble s, GLdouble t, GLdouble r, GLdouble q); +EXTERN void ( APIENTRY *pglTexCoord4dv )(const GLdouble *v); +EXTERN void ( APIENTRY *pglTexCoord4f )(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +EXTERN void ( APIENTRY *pglTexCoord4fv )(const GLfloat *v); +EXTERN void ( APIENTRY *pglTexCoord4i )(GLint s, GLint t, GLint r, GLint q); +EXTERN void ( APIENTRY *pglTexCoord4iv )(const GLint *v); +EXTERN void ( APIENTRY *pglTexCoord4s )(GLshort s, GLshort t, GLshort r, GLshort q); +EXTERN void ( APIENTRY *pglTexCoord4sv )(const GLshort *v); +EXTERN void ( APIENTRY *pglTexEnvf )(GLenum target, GLenum pname, GLfloat param); +EXTERN void ( APIENTRY *pglTexEnvfv )(GLenum target, GLenum pname, const GLfloat *params); +EXTERN void ( APIENTRY *pglTexEnvi )(GLenum target, GLenum pname, GLint param); +EXTERN void ( APIENTRY *pglTexEnviv )(GLenum target, GLenum pname, const GLint *params); +EXTERN void ( APIENTRY *pglTexGend )(GLenum coord, GLenum pname, GLdouble param); +EXTERN void ( APIENTRY *pglTexGendv )(GLenum coord, GLenum pname, const GLdouble *params); +EXTERN void ( APIENTRY *pglTexGenf )(GLenum coord, GLenum pname, GLfloat param); +EXTERN void ( APIENTRY *pglTexGenfv )(GLenum coord, GLenum pname, const GLfloat *params); +EXTERN void ( APIENTRY *pglTexGeni )(GLenum coord, GLenum pname, GLint param); +EXTERN void ( APIENTRY *pglTexGeniv )(GLenum coord, GLenum pname, const GLint *params); +EXTERN void ( APIENTRY *pglTexImage1D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +EXTERN void ( APIENTRY *pglTexImage2D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +EXTERN void ( APIENTRY *pglTexParameterf )(GLenum target, GLenum pname, GLfloat param); +EXTERN void ( APIENTRY *pglTexParameterfv )(GLenum target, GLenum pname, const GLfloat *params); +EXTERN void ( APIENTRY *pglTexParameteri )(GLenum target, GLenum pname, GLint param); +EXTERN void ( APIENTRY *pglTexParameteriv )(GLenum target, GLenum pname, const GLint *params); +EXTERN void ( APIENTRY *pglTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +EXTERN void ( APIENTRY *pglTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +EXTERN void ( APIENTRY *pglTranslated )(GLdouble x, GLdouble y, GLdouble z); +EXTERN void ( APIENTRY *pglTranslatef )(GLfloat x, GLfloat y, GLfloat z); +EXTERN void ( APIENTRY *pglVertex2d )(GLdouble x, GLdouble y); +EXTERN void ( APIENTRY *pglVertex2dv )(const GLdouble *v); +EXTERN void ( APIENTRY *pglVertex2f )(GLfloat x, GLfloat y); +EXTERN void ( APIENTRY *pglVertex2fv )(const GLfloat *v); +EXTERN void ( APIENTRY *pglVertex2i )(GLint x, GLint y); +EXTERN void ( APIENTRY *pglVertex2iv )(const GLint *v); +EXTERN void ( APIENTRY *pglVertex2s )(GLshort x, GLshort y); +EXTERN void ( APIENTRY *pglVertex2sv )(const GLshort *v); +EXTERN void ( APIENTRY *pglVertex3d )(GLdouble x, GLdouble y, GLdouble z); +EXTERN void ( APIENTRY *pglVertex3dv )(const GLdouble *v); +EXTERN void ( APIENTRY *pglVertex3f )(GLfloat x, GLfloat y, GLfloat z); +EXTERN void ( APIENTRY *pglVertex3fv )(const GLfloat *v); +EXTERN void ( APIENTRY *pglVertex3i )(GLint x, GLint y, GLint z); +EXTERN void ( APIENTRY *pglVertex3iv )(const GLint *v); +EXTERN void ( APIENTRY *pglVertex3s )(GLshort x, GLshort y, GLshort z); +EXTERN void ( APIENTRY *pglVertex3sv )(const GLshort *v); +EXTERN void ( APIENTRY *pglVertex4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +EXTERN void ( APIENTRY *pglVertex4dv )(const GLdouble *v); +EXTERN void ( APIENTRY *pglVertex4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +EXTERN void ( APIENTRY *pglVertex4fv )(const GLfloat *v); +EXTERN void ( APIENTRY *pglVertex4i )(GLint x, GLint y, GLint z, GLint w); +EXTERN void ( APIENTRY *pglVertex4iv )(const GLint *v); +EXTERN void ( APIENTRY *pglVertex4s )(GLshort x, GLshort y, GLshort z, GLshort w); +EXTERN void ( APIENTRY *pglVertex4sv )(const GLshort *v); +EXTERN void ( APIENTRY *pglViewport )(GLint x, GLint y, GLsizei width, GLsizei height); +EXTERN void ( APIENTRY *pglPointParameterfEXT)( GLenum param, GLfloat value ); +EXTERN void ( APIENTRY *pglPointParameterfvEXT)( GLenum param, const GLfloat *value ); +EXTERN void ( APIENTRY *pglLockArraysEXT) (int , int); +EXTERN void ( APIENTRY *pglUnlockArraysEXT) (void); +EXTERN void ( APIENTRY *pglActiveTextureARB)( GLenum ); +EXTERN void ( APIENTRY *pglClientActiveTextureARB)( GLenum ); +EXTERN void ( APIENTRY *pglGetCompressedTexImage)( GLenum target, GLint lod, const void* data ); +EXTERN void ( APIENTRY *pglDrawRangeElements)( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices ); +EXTERN void ( APIENTRY *pglDrawRangeElementsEXT)( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices ); +EXTERN void ( APIENTRY *pglDrawElements)(GLenum mode, GLsizei count, GLenum type, const void *indices); +EXTERN void ( APIENTRY *pglVertexPointer)(GLint size, GLenum type, GLsizei stride, const void *ptr); +EXTERN void ( APIENTRY *pglNormalPointer)(GLenum type, GLsizei stride, const void *ptr); +EXTERN void ( APIENTRY *pglColorPointer)(GLint size, GLenum type, GLsizei stride, const void *ptr); +EXTERN void ( APIENTRY *pglTexCoordPointer)(GLint size, GLenum type, GLsizei stride, const void *ptr); +EXTERN void ( APIENTRY *pglArrayElement)(GLint i); +EXTERN void ( APIENTRY *pglMultiTexCoord1f) (GLenum, GLfloat); +EXTERN void ( APIENTRY *pglMultiTexCoord2f) (GLenum, GLfloat, GLfloat); +EXTERN void ( APIENTRY *pglMultiTexCoord3f) (GLenum, GLfloat, GLfloat, GLfloat); +EXTERN void ( APIENTRY *pglMultiTexCoord4f) (GLenum, GLfloat, GLfloat, GLfloat, GLfloat); +EXTERN void ( APIENTRY *pglActiveTexture) (GLenum); +EXTERN void ( APIENTRY *pglClientActiveTexture) (GLenum); +EXTERN void ( APIENTRY *pglCompressedTexImage3DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); +EXTERN void ( APIENTRY *pglCompressedTexImage2DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); +EXTERN void ( APIENTRY *pglCompressedTexImage1DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data); +EXTERN void ( APIENTRY *pglCompressedTexSubImage3DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); +EXTERN void ( APIENTRY *pglCompressedTexSubImage2DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); +EXTERN void ( APIENTRY *pglCompressedTexSubImage1DARB)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); +EXTERN void ( APIENTRY *pglDeleteObjectARB)(GLhandleARB obj); +EXTERN GLhandleARB ( APIENTRY *pglGetHandleARB)(GLenum pname); +EXTERN void ( APIENTRY *pglDetachObjectARB)(GLhandleARB containerObj, GLhandleARB attachedObj); +EXTERN GLhandleARB ( APIENTRY *pglCreateShaderObjectARB)(GLenum shaderType); +EXTERN void ( APIENTRY *pglShaderSourceARB)(GLhandleARB shaderObj, GLsizei count, const GLcharARB **string, const GLint *length); +EXTERN void ( APIENTRY *pglCompileShaderARB)(GLhandleARB shaderObj); +EXTERN GLhandleARB ( APIENTRY *pglCreateProgramObjectARB)(void); +EXTERN void ( APIENTRY *pglAttachObjectARB)(GLhandleARB containerObj, GLhandleARB obj); +EXTERN void ( APIENTRY *pglLinkProgramARB)(GLhandleARB programObj); +EXTERN void ( APIENTRY *pglUseProgramObjectARB)(GLhandleARB programObj); +EXTERN void ( APIENTRY *pglValidateProgramARB)(GLhandleARB programObj); +EXTERN void ( APIENTRY *pglBindProgramARB)(GLenum target, GLuint program); +EXTERN void ( APIENTRY *pglDeleteProgramsARB)(GLsizei n, const GLuint *programs); +EXTERN void ( APIENTRY *pglGenProgramsARB)(GLsizei n, GLuint *programs); +EXTERN void ( APIENTRY *pglProgramStringARB)(GLenum target, GLenum format, GLsizei len, const void *string); +EXTERN void ( APIENTRY *pglProgramEnvParameter4fARB)(GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +EXTERN void ( APIENTRY *pglProgramLocalParameter4fARB)(GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +EXTERN void ( APIENTRY *pglGetProgramivARB)( GLenum target, GLenum pname, GLint *params ); +EXTERN void ( APIENTRY *pglUniform1fARB)(GLint location, GLfloat v0); +EXTERN void ( APIENTRY *pglUniform2fARB)(GLint location, GLfloat v0, GLfloat v1); +EXTERN void ( APIENTRY *pglUniform3fARB)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +EXTERN void ( APIENTRY *pglUniform4fARB)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +EXTERN void ( APIENTRY *pglUniform1iARB)(GLint location, GLint v0); +EXTERN void ( APIENTRY *pglUniform2iARB)(GLint location, GLint v0, GLint v1); +EXTERN void ( APIENTRY *pglUniform3iARB)(GLint location, GLint v0, GLint v1, GLint v2); +EXTERN void ( APIENTRY *pglUniform4iARB)(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +EXTERN void ( APIENTRY *pglUniform1fvARB)(GLint location, GLsizei count, const GLfloat *value); +EXTERN void ( APIENTRY *pglUniform2fvARB)(GLint location, GLsizei count, const GLfloat *value); +EXTERN void ( APIENTRY *pglUniform3fvARB)(GLint location, GLsizei count, const GLfloat *value); +EXTERN void ( APIENTRY *pglUniform4fvARB)(GLint location, GLsizei count, const GLfloat *value); +EXTERN void ( APIENTRY *pglUniform1ivARB)(GLint location, GLsizei count, const GLint *value); +EXTERN void ( APIENTRY *pglUniform2ivARB)(GLint location, GLsizei count, const GLint *value); +EXTERN void ( APIENTRY *pglUniform3ivARB)(GLint location, GLsizei count, const GLint *value); +EXTERN void ( APIENTRY *pglUniform4ivARB)(GLint location, GLsizei count, const GLint *value); +EXTERN void ( APIENTRY *pglUniformMatrix2fvARB)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +EXTERN void ( APIENTRY *pglUniformMatrix3fvARB)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +EXTERN void ( APIENTRY *pglUniformMatrix4fvARB)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +EXTERN void ( APIENTRY *pglGetObjectParameterfvARB)(GLhandleARB obj, GLenum pname, GLfloat *params); +EXTERN void ( APIENTRY *pglGetObjectParameterivARB)(GLhandleARB obj, GLenum pname, GLint *params); +EXTERN void ( APIENTRY *pglGetInfoLogARB)(GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *infoLog); +EXTERN void ( APIENTRY *pglGetAttachedObjectsARB)(GLhandleARB containerObj, GLsizei maxCount, GLsizei *count, GLhandleARB *obj); +EXTERN GLint ( APIENTRY *pglGetUniformLocationARB)(GLhandleARB programObj, const GLcharARB *name); +EXTERN void ( APIENTRY *pglGetActiveUniformARB)(GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +EXTERN void ( APIENTRY *pglGetUniformfvARB)(GLhandleARB programObj, GLint location, GLfloat *params); +EXTERN void ( APIENTRY *pglGetUniformivARB)(GLhandleARB programObj, GLint location, GLint *params); +EXTERN void ( APIENTRY *pglGetShaderSourceARB)(GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *source); +EXTERN void ( APIENTRY *pglGetProgramBinary)( GLuint program, GLsizei bufferSize, GLsizei *length, GLenum *binaryFormat, GLvoid *binary ); +EXTERN void ( APIENTRY *pglProgramBinary)(GLuint program, GLenum binaryFormat, const GLvoid *binary, GLsizei length); +EXTERN void ( APIENTRY *pglProgramParameteri)(GLuint program, GLenum pname, GLint value); +EXTERN void ( APIENTRY *pglTexImage3D)( GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels ); +EXTERN void ( APIENTRY *pglTexSubImage3D)( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels ); +EXTERN void ( APIENTRY *pglCopyTexSubImage3D)( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height ); +EXTERN void ( APIENTRY *pglBlendEquationEXT)(GLenum); +EXTERN void ( APIENTRY *pglStencilOpSeparate)(GLenum, GLenum, GLenum, GLenum); +EXTERN void ( APIENTRY *pglStencilFuncSeparate)(GLenum, GLenum, GLint, GLuint); +EXTERN void ( APIENTRY *pglActiveStencilFaceEXT)(GLenum); +EXTERN void ( APIENTRY *pglBlendFuncSeparate)( GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha ); +EXTERN void ( APIENTRY *pglVertexAttribPointerARB)(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +EXTERN void ( APIENTRY *pglEnableVertexAttribArrayARB)(GLuint index); +EXTERN void ( APIENTRY *pglDisableVertexAttribArrayARB)(GLuint index); +EXTERN void ( APIENTRY *pglBindAttribLocationARB)(GLhandleARB programObj, GLuint index, const GLcharARB *name); +EXTERN void ( APIENTRY *pglGetActiveAttribARB)(GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +EXTERN GLint ( APIENTRY *pglGetAttribLocationARB)(GLhandleARB programObj, const GLcharARB *name); +EXTERN void ( APIENTRY *pglBindFragDataLocation)(GLuint programObj, GLuint index, const GLcharARB *name); +EXTERN void ( APIENTRY *pglVertexAttrib2fARB)( GLuint index, GLfloat x, GLfloat y ); +EXTERN void ( APIENTRY *pglVertexAttrib2fvARB)( GLuint index, const GLfloat *v ); +EXTERN void ( APIENTRY *pglVertexAttrib3fvARB)( GLuint index, const GLfloat *v ); +EXTERN void ( APIENTRY *pglVertexAttrib4fvARB)( GLuint index, const GLfloat *v ); +EXTERN void ( APIENTRY *pglVertexAttrib4ubvARB)( GLuint index, const GLubyte *b ); +EXTERN void ( APIENTRY *pglBindBufferARB) (GLenum target, GLuint buffer); +EXTERN void ( APIENTRY *pglDeleteBuffersARB) (GLsizei n, const GLuint *buffers); +EXTERN void ( APIENTRY *pglGenBuffersARB) (GLsizei n, GLuint *buffers); +EXTERN GLboolean ( APIENTRY *pglIsBufferARB) (GLuint buffer); +EXTERN void* ( APIENTRY *pglMapBufferARB) (GLenum target, GLenum access); +EXTERN GLboolean ( APIENTRY *pglUnmapBufferARB) (GLenum target); +EXTERN void ( APIENTRY *pglBufferDataARB) (GLenum target, GLsizeiptrARB size, const void *data, GLenum usage); +EXTERN void ( APIENTRY *pglBufferSubDataARB) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const void *data); +EXTERN void ( APIENTRY *pglGenQueriesARB) (GLsizei n, GLuint *ids); +EXTERN void ( APIENTRY *pglDeleteQueriesARB) (GLsizei n, const GLuint *ids); +EXTERN GLboolean ( APIENTRY *pglIsQueryARB) (GLuint id); +EXTERN void ( APIENTRY *pglBeginQueryARB) (GLenum target, GLuint id); +EXTERN void ( APIENTRY *pglEndQueryARB) (GLenum target); +EXTERN void ( APIENTRY *pglGetQueryivARB) (GLenum target, GLenum pname, GLint *params); +EXTERN void ( APIENTRY *pglGetQueryObjectivARB) (GLuint id, GLenum pname, GLint *params); +EXTERN void ( APIENTRY *pglGetQueryObjectuivARB) (GLuint id, GLenum pname, GLuint *params); +EXTERN void ( APIENTRY * pglSelectTextureSGIS )( GLenum ); +EXTERN void ( APIENTRY * pglMTexCoord2fSGIS )( GLenum, GLfloat, GLfloat ); +EXTERN void ( APIENTRY * pglSwapInterval )( int interval ); +EXTERN GLboolean ( APIENTRY *pglIsRenderbuffer )(GLuint renderbuffer); +EXTERN void ( APIENTRY *pglBindRenderbuffer )(GLenum target, GLuint renderbuffer); +EXTERN void ( APIENTRY *pglDeleteRenderbuffers )(GLsizei n, const GLuint *renderbuffers); +EXTERN void ( APIENTRY *pglGenRenderbuffers )(GLsizei n, GLuint *renderbuffers); +EXTERN void ( APIENTRY *pglRenderbufferStorage )(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +EXTERN void ( APIENTRY *pglRenderbufferStorageMultisample )(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +EXTERN void ( APIENTRY *pglGetRenderbufferParameteriv )(GLenum target, GLenum pname, GLint *params); +EXTERN void ( APIENTRY *pglClampColor)(GLenum target, GLenum clamp); +EXTERN GLvoid ( APIENTRY *pglColorMaski)( GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a ); +EXTERN GLboolean (APIENTRY *pglIsFramebuffer )(GLuint framebuffer); +EXTERN void ( APIENTRY *pglBindFramebuffer )(GLenum target, GLuint framebuffer); +EXTERN void ( APIENTRY *pglDeleteFramebuffers )(GLsizei n, const GLuint *framebuffers); +EXTERN void ( APIENTRY *pglGenFramebuffers )(GLsizei n, GLuint *framebuffers); +EXTERN GLenum ( APIENTRY *pglCheckFramebufferStatus )(GLenum target); +EXTERN void ( APIENTRY *pglFramebufferTexture1D )(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +EXTERN void ( APIENTRY *pglFramebufferTexture2D )(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +EXTERN void ( APIENTRY *pglFramebufferTexture3D )(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint layer); +EXTERN void ( APIENTRY *pglFramebufferTextureLayer )(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +EXTERN void ( APIENTRY *pglFramebufferRenderbuffer )(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +EXTERN void ( APIENTRY *pglGetFramebufferAttachmentParameteriv )(GLenum target, GLenum attachment, GLenum pname, GLint *params); +EXTERN void ( APIENTRY *pglBlitFramebuffer )(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +EXTERN void ( APIENTRY *pglCopyImageSubData )( GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth ); +EXTERN void ( APIENTRY *pglCombinerParameterfvNV )( GLenum, const GLfloat * ); +EXTERN void ( APIENTRY *pglCombinerParameterfNV )( GLenum, GLfloat ); +EXTERN void ( APIENTRY *pglCombinerParameterivNV )(GLenum, const GLint * ); +EXTERN void ( APIENTRY *pglCombinerParameteriNV )( GLenum, GLint ); +EXTERN void ( APIENTRY *pglCombinerInputNV )( GLenum, GLenum, GLenum, GLenum, GLenum, GLenum ); +EXTERN void ( APIENTRY *pglCombinerOutputNV )( GLenum, GLenum, GLenum, GLenum, GLenum, GLenum, GLenum, GLboolean, GLboolean, GLboolean ); +EXTERN void ( APIENTRY *pglFinalCombinerInputNV )( GLenum, GLenum, GLenum, GLenum ); +EXTERN void ( APIENTRY *pglGetCombinerInputParameterfvNV )( GLenum, GLenum, GLenum, GLenum, GLfloat * ); +EXTERN void ( APIENTRY *pglGetCombinerInputParameterivNV )( GLenum, GLenum, GLenum, GLenum, GLint * ); +EXTERN void ( APIENTRY *pglGetCombinerOutputParameterfvNV )( GLenum, GLenum, GLenum, GLfloat * ); +EXTERN void ( APIENTRY *pglGetCombinerOutputParameterivNV )( GLenum, GLenum, GLenum, GLint * ); +EXTERN void ( APIENTRY *pglGetFinalCombinerInputParameterfvNV )( GLenum, GLenum, GLfloat * ); +EXTERN void ( APIENTRY *pglGetFinalCombinerInputParameterivNV )( GLenum, GLenum, GLint * ); +EXTERN void ( APIENTRY *pglGenerateMipmap )( GLenum target ); +EXTERN void ( APIENTRY *pglDrawBuffersARB)( GLsizei n, const GLenum *bufs ); +EXTERN PROC ( WINAPI *pwglGetProcAddress)( const char * ); + +EXTERN void ( APIENTRY *pglBindVertexArray )( GLuint array ); +EXTERN void ( APIENTRY *pglDeleteVertexArrays )( GLsizei n, const GLuint *arrays ); +EXTERN void ( APIENTRY *pglGenVertexArrays )( GLsizei n, const GLuint *arrays ); +EXTERN GLboolean ( APIENTRY *pglIsVertexArray )( GLuint array ); + +typedef void ( APIENTRY *pglDebugProcARB)( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLcharARB* message, GLvoid* userParam ); +EXTERN void ( APIENTRY *pglDebugMessageControlARB)( GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint* ids, GLboolean enabled ); +EXTERN void ( APIENTRY *pglDebugMessageInsertARB)( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const char* buf ); +EXTERN void ( APIENTRY *pglDebugMessageCallbackARB)( pglDebugProcARB callback, void* userParam ); +EXTERN GLuint ( APIENTRY *pglGetDebugMessageLogARB)( GLuint count, GLsizei bufsize, GLenum* sources, GLenum* types, GLuint* ids, GLuint* severities, GLsizei* lengths, char* messageLog ); + +#endif//GL_EXPORT_H \ No newline at end of file diff --git a/cl_dll/render/gl_framebuffer.cpp b/cl_dll/render/gl_framebuffer.cpp new file mode 100644 index 0000000..2fe05a3 --- /dev/null +++ b/cl_dll/render/gl_framebuffer.cpp @@ -0,0 +1,628 @@ +/* +gl_framebuffer.cpp - framebuffer implementation class +Copyright (C) 2014 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include "cl_util.h" +#include "gl_local.h" +#include +#include + +static gl_drawbuffer_t gl_drawbuffers[MAX_FRAMEBUFFERS]; +static int gl_num_drawbuffers; + +/* +================== +GL_AllocDrawbuffer +================== +*/ +gl_drawbuffer_t *GL_AllocDrawbuffer( const char *name, int width, int height, int depth ) +{ + gl_drawbuffer_t *fbo; + int i; + + if( !GL_Support( R_FRAMEBUFFER_OBJECT )) + return NULL; + + // find a free FBO slot + for( i = 0, fbo = gl_drawbuffers; i < gl_num_drawbuffers; i++, fbo++ ) + if( !fbo->name[0] ) break; + + if( i == gl_num_drawbuffers ) + { + if( gl_num_drawbuffers == MAX_FRAMEBUFFERS ) + HOST_ERROR( "GL_AllocDrawBuffer: MAX_FRAMEBUFFERS limit exceeds\n" ); + gl_num_drawbuffers++; + } + + if( !GL_Support( R_ARB_TEXTURE_NPOT_EXT )) + { + width = NearestPOW( width, true ); + height = NearestPOW( height, true ); + } + + // fill it in + Q_strncpy( fbo->name, name, sizeof( fbo->name )); + + // make sure it's fit in limits + fbo->width = Q_min( glConfig.max_2d_texture_size, width ); + fbo->height = Q_min( glConfig.max_2d_texture_size, height ); + fbo->depth = depth; + + // generate the drawbuffer + pglGenFramebuffers( 1, &fbo->id ); + + return fbo; +} + +/* +================== +GL_ResizeDrawbuffer +================== +*/ +void GL_ResizeDrawbuffer( gl_drawbuffer_t *fbo, int width, int height, int depth ) +{ + ASSERT( fbo != NULL ); + + // fill it in + fbo->width = width; + fbo->height = height; + fbo->depth = depth; +} + +/* +================== +GL_AttachColorTextureToFBO +================== +*/ +void GL_AttachColorTextureToFBO( gl_drawbuffer_t *fbo, int texture, int colorIndex, int index ) +{ + GLuint target = RENDER_GET_PARM( PARM_TEX_TARGET, texture ); + GLuint format = RENDER_GET_PARM( PARM_TEX_GLFORMAT, texture ); + GLint width = RENDER_GET_PARM( PARM_TEX_WIDTH, texture ); + GLint height = RENDER_GET_PARM( PARM_TEX_HEIGHT, texture ); + GLint depth = RENDER_GET_PARM( PARM_TEX_DEPTH, texture ); + + if( target == GL_TEXTURE_2D ) + { + // set the texture + fbo->colortarget[colorIndex] = texture; + + // if the drawbuffer has been resized + if( width != fbo->width || height != fbo->height ) + { + // check the dimensions + if( fbo->width > glConfig.max_2d_texture_size || fbo->height > glConfig.max_2d_texture_size ) + HOST_ERROR( "GL_AttachColorTextureToFBO: size exceeds hardware limits (%i > %i or %i > %i)\n", + fbo->width, glConfig.max_2d_texture_size, fbo->height, glConfig.max_2d_texture_size ); + + // reallocate the texture + GL_UpdateTexSize( texture, fbo->width, fbo->height, fbo->depth ); + GL_BindTexture( GL_KEEP_UNIT, texture ); + + // need to refresh real FBO size with possible hardware limitations + fbo->width = RENDER_GET_PARM( PARM_TEX_WIDTH, texture ); + fbo->height = RENDER_GET_PARM( PARM_TEX_HEIGHT, texture ); + + pglTexImage2D( target, 0, format, fbo->width, fbo->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL ); + } + + // Bind the drawbuffer + GL_BindDrawbuffer( fbo ); + + // Set up the color attachment + pglFramebufferTexture2D( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + colorIndex, GL_TEXTURE_2D, texture, 0 ); + } + else if( target == GL_TEXTURE_CUBE_MAP_ARB ) + { + // set the texture + fbo->colortarget[colorIndex] = texture; + + // if the drawbuffer has been resized + if( width != fbo->width || height != fbo->height ) + { + // check the dimensions + if( fbo->width > glConfig.max_cubemap_size || fbo->height > glConfig.max_cubemap_size ) + HOST_ERROR( "GL_AttachColorTextureToFBO: size exceeds hardware limits (%i > %i or %i > %i)\n", + fbo->width, glConfig.max_cubemap_size, fbo->height, glConfig.max_cubemap_size ); + + // reallocate the texture + GL_UpdateTexSize( texture, fbo->width, fbo->height, fbo->depth ); + GL_BindTexture( GL_KEEP_UNIT, texture ); + + // need to refresh real FBO size with possible hardware limitations + fbo->width = RENDER_GET_PARM( PARM_TEX_WIDTH, texture ); + fbo->height = RENDER_GET_PARM( PARM_TEX_HEIGHT, texture ); + + pglTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB, 0, format, fbo->width, fbo->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL ); + pglTexImage2D( GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB, 0, format, fbo->width, fbo->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL ); + pglTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB, 0, format, fbo->width, fbo->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL ); + pglTexImage2D( GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB, 0, format, fbo->width, fbo->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL ); + pglTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB, 0, format, fbo->width, fbo->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL ); + pglTexImage2D( GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB, 0, format, fbo->width, fbo->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL ); + } + + // bind the drawbuffer + GL_BindDrawbuffer( fbo ); + + // set up the color attachment + pglFramebufferTexture2D( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + colorIndex, GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB + index, texture, 0 ); + } + else if( target == GL_TEXTURE_2D_ARRAY_EXT ) + { + // Set the texture + fbo->colortarget[colorIndex] = texture; + + // if the drawbuffer has been resized + if( width != fbo->width || height != fbo->height || depth != fbo->depth ) + { + // check the dimensions + if( fbo->width > glConfig.max_2d_texture_size || fbo->height > glConfig.max_2d_texture_size || fbo->depth > glConfig.max_2d_texture_layers ) + HOST_ERROR( "GL_AttachColorTextureToFBO: size exceeds hardware limits (%i > %i or %i > %i or %i > %i)\n", + fbo->width, glConfig.max_2d_texture_size, fbo->height, glConfig.max_2d_texture_size, fbo->depth, glConfig.max_2d_texture_layers ); + + // reallocate the texture + GL_UpdateTexSize( texture, fbo->width, fbo->height, fbo->depth ); + GL_BindTexture( GL_KEEP_UNIT, texture ); + + // need to refresh real FBO size with possible hardware limitations + fbo->width = RENDER_GET_PARM( PARM_TEX_WIDTH, texture ); + fbo->height = RENDER_GET_PARM( PARM_TEX_HEIGHT, texture ); + fbo->depth = RENDER_GET_PARM( PARM_TEX_DEPTH, texture ); + + pglTexImage3D( target, 0, format, fbo->width, fbo->height, fbo->depth, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL ); + } + + // bind the drawbuffer + GL_BindDrawbuffer( fbo ); + + // Set up the color attachment + pglFramebufferTextureLayer( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + colorIndex, texture, 0, index ); + } + else if( target == GL_TEXTURE_3D ) + { + // Set the texture + fbo->colortarget[colorIndex] = texture; + + // if the drawbuffer has been resized + if( width != fbo->width || height != fbo->height || depth != fbo->depth ) + { + // check the dimensions + if( fbo->width > glConfig.max_3d_texture_size || fbo->height > glConfig.max_3d_texture_size || fbo->depth > glConfig.max_3d_texture_size ) + HOST_ERROR( "GL_AttachColorTextureToFBO: size exceeds hardware limits (%i > %i or %i > %i or %i > %i)\n", + fbo->width, glConfig.max_3d_texture_size, fbo->height, glConfig.max_3d_texture_size, fbo->depth, glConfig.max_3d_texture_size ); + + // reallocate the texture + GL_UpdateTexSize( texture, fbo->width, fbo->height, fbo->depth ); + GL_BindTexture( GL_KEEP_UNIT, texture ); + + // need to refresh real FBO size with possible hardware limitations + fbo->width = RENDER_GET_PARM( PARM_TEX_WIDTH, texture ); + fbo->height = RENDER_GET_PARM( PARM_TEX_HEIGHT, texture ); + fbo->depth = RENDER_GET_PARM( PARM_TEX_DEPTH, texture ); + + pglTexImage3D( target, 0, format, fbo->width, fbo->height, fbo->depth, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL ); + } + + // bind the drawbuffer + GL_BindDrawbuffer( fbo ); + + // Set up the color attachment + pglFramebufferTexture3D( GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT + colorIndex, GL_TEXTURE_3D, texture, 0, index ); + } + else + { + HOST_ERROR( "GL_AttachColorTextureToFBO: bad texture target (%i)\n", target ); + } +} + +/* +================== +GL_AttachDepthTextureToFBO +================== +*/ +void GL_AttachDepthTextureToFBO( gl_drawbuffer_t *fbo, int texture, int index ) +{ + GLuint target = RENDER_GET_PARM( PARM_TEX_TARGET, texture ); + GLuint format = RENDER_GET_PARM( PARM_TEX_GLFORMAT, texture ); + GLint width = RENDER_GET_PARM( PARM_TEX_WIDTH, texture ); + GLint height = RENDER_GET_PARM( PARM_TEX_HEIGHT, texture ); + GLint depth = RENDER_GET_PARM( PARM_TEX_DEPTH, texture ); + + if( target == GL_TEXTURE_2D ) + { + // set the texture + fbo->depthtarget = texture; + + // if the drawbuffer has been resized + if( width != fbo->width || height != fbo->height ) + { + // check the dimensions + if( fbo->width > glConfig.max_2d_texture_size || fbo->height > glConfig.max_2d_texture_size ) + HOST_ERROR( "GL_AttachDepthTextureToFBO: size exceeds hardware limits (%i > %i or %i > %i)\n", + fbo->width, glConfig.max_2d_texture_size, fbo->height, glConfig.max_2d_texture_size ); + + // reallocate the texture + GL_UpdateTexSize( texture, fbo->width, fbo->height, fbo->depth ); + GL_BindTexture( GL_KEEP_UNIT, texture ); + + // need to refresh real FBO size with possible hardware limitations + fbo->width = RENDER_GET_PARM( PARM_TEX_WIDTH, texture ); + fbo->height = RENDER_GET_PARM( PARM_TEX_HEIGHT, texture ); + + pglTexImage2D( target, 0, format, fbo->width, fbo->height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL ); + } + + // Bind the drawbuffer + GL_BindDrawbuffer( fbo ); + + // Set up the color attachment + pglFramebufferTexture2D( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, texture, 0 ); + } + else if( target == GL_TEXTURE_CUBE_MAP_ARB ) + { + // set the texture + fbo->depthtarget = texture; + + // if the drawbuffer has been resized + if( width != fbo->width || height != fbo->height ) + { + // check the dimensions + if( fbo->width > glConfig.max_cubemap_size || fbo->height > glConfig.max_cubemap_size ) + HOST_ERROR( "GL_AttachDepthTextureToFBO: size exceeds hardware limits (%i > %i or %i > %i)\n", + fbo->width, glConfig.max_cubemap_size, fbo->height, glConfig.max_cubemap_size ); + + // reallocate the texture + GL_UpdateTexSize( texture, fbo->width, fbo->height, fbo->depth ); + GL_BindTexture( GL_KEEP_UNIT, texture ); + + // need to refresh real FBO size with possible hardware limitations + fbo->width = RENDER_GET_PARM( PARM_TEX_WIDTH, texture ); + fbo->height = RENDER_GET_PARM( PARM_TEX_HEIGHT, texture ); + + pglTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB, 0, format, fbo->width, fbo->height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL ); + pglTexImage2D( GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB, 0, format, fbo->width, fbo->height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL ); + pglTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB, 0, format, fbo->width, fbo->height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL ); + pglTexImage2D( GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB, 0, format, fbo->width, fbo->height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL ); + pglTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB, 0, format, fbo->width, fbo->height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL ); + pglTexImage2D( GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB, 0, format, fbo->width, fbo->height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL ); + } + + // bind the drawbuffer + GL_BindDrawbuffer( fbo ); + + // set up the color attachment + pglFramebufferTexture2D( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB + index, texture, 0 ); + } + else if( target == GL_TEXTURE_2D_ARRAY_EXT ) + { + // Set the texture + fbo->depthtarget = texture; + + // if the drawbuffer has been resized + if( width != fbo->width || height != fbo->height || depth != fbo->depth ) + { + // check the dimensions + if( fbo->width > glConfig.max_2d_texture_size || fbo->height > glConfig.max_2d_texture_size || fbo->depth > glConfig.max_2d_texture_layers ) + HOST_ERROR( "GL_AttachDepthTextureToFBO: size exceeds hardware limits (%i > %i or %i > %i or %i > %i)\n", + fbo->width, glConfig.max_2d_texture_size, fbo->height, glConfig.max_2d_texture_size, fbo->depth, glConfig.max_2d_texture_layers ); + + // reallocate the texture + GL_UpdateTexSize( texture, fbo->width, fbo->height, fbo->depth ); + GL_BindTexture( GL_KEEP_UNIT, texture ); + + // need to refresh real FBO size with possible hardware limitations + fbo->width = RENDER_GET_PARM( PARM_TEX_WIDTH, texture ); + fbo->height = RENDER_GET_PARM( PARM_TEX_HEIGHT, texture ); + fbo->depth = RENDER_GET_PARM( PARM_TEX_DEPTH, texture ); + + pglTexImage3D( target, 0, format, fbo->width, fbo->height, fbo->depth, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL ); + } + + // bind the drawbuffer + GL_BindDrawbuffer( fbo ); + + // Set up the color attachment + pglFramebufferTextureLayer( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, texture, 0, index ); + } + else + { + HOST_ERROR( "GL_AttachDepthTextureToFBO: bad texture target (%i)\n", target ); + } +} + +/* +================== +GL_CheckFBOStatus +================== +*/ +void GL_CheckFBOStatus( gl_drawbuffer_t *fbo ) +{ + const char *string; + int status; + + // bind the drawbuffer + GL_BindDrawbuffer( fbo ); + + // check the framebuffer status + status = pglCheckFramebufferStatus( GL_FRAMEBUFFER_EXT ); + + if( status == GL_FRAMEBUFFER_COMPLETE_EXT ) + return; + + switch( status ) + { + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: + string = "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT"; + break; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: + string = "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT"; + break; + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: + string = "GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER"; + break; + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: + string = "GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER"; + break; + case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT: + string = "GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE"; + break; + case GL_FRAMEBUFFER_UNSUPPORTED_EXT: + string = "GL_FRAMEBUFFER_UNSUPPORTED"; + break; + case GL_FRAMEBUFFER_UNDEFINED_EXT: + string = "GL_FRAMEBUFFER_UNDEFINED"; + break; + default: + string = "UNKNOWN STATUS"; + break; + } + + HOST_ERROR( "GL_CheckFBOStatus: %s for '%s'\n", string, fbo->name ); +} + +/* +================== +GL_FreeDrawbuffers +================== +*/ +void GL_FreeDrawbuffers( void ) +{ + gl_drawbuffer_t *fbo; + + // unbind the drawbuffer + GL_BindDrawbuffer( NULL ); + + // delete all the drawbuffers + for( int i = 0; i < gl_num_drawbuffers; i++ ) + { + fbo = &gl_drawbuffers[i]; + pglDeleteFramebuffers( 1, &fbo->id ); + } + + // NOTE: let the engine release attached textures + memset( gl_drawbuffers, 0, sizeof( gl_drawbuffers )); + gl_num_drawbuffers = 0; +} + +CFrameBuffer :: CFrameBuffer( void ) +{ + m_iFrameWidth = m_iFrameHeight = 0; + m_iFrameBuffer = m_iDepthBuffer = 0; + m_iTexture = m_iAttachment = 0; + + m_bAllowFBO = false; +} + +CFrameBuffer :: ~CFrameBuffer( void ) +{ + // NOTE: static case will be failed + Free(); +} + +int CFrameBuffer :: m_iBufferNum = 0; + +bool CFrameBuffer :: Init( FBO_TYPE type, GLuint width, GLuint height, GLuint flags ) +{ + Free(); // release old buffer + + m_iFlags = flags; + + if( !GL_Support( R_ARB_TEXTURE_NPOT_EXT )) + SetBits( m_iFlags, FBO_MAKEPOW ); + + if( FBitSet( m_iFlags, FBO_MAKEPOW )) + { + width = NearestPOW( width, true ); + height = NearestPOW( height, true ); + } + + // clamp size to hardware limits + if( type == FBO_CUBE ) + { + m_iFrameWidth = bound( 0, width, glConfig.max_cubemap_size ); + m_iFrameHeight = bound( 0, height, glConfig.max_cubemap_size ); + } + else + { + m_iFrameWidth = bound( 0, width, glConfig.max_2d_texture_size ); + m_iFrameHeight = bound( 0, height, glConfig.max_2d_texture_size ); + } + + if( !m_iFrameWidth || !m_iFrameHeight ) + { + ALERT( at_error, "CFrameBuffer( %i x %i ) invalid size\n", m_iFrameWidth, m_iFrameHeight ); + return false; + } + + // create FBO texture + if( !FBitSet( m_iFlags, FBO_NOTEXTURE )) + { + int texFlags = (TF_NOMIPMAP|TF_CLAMP); + + if( type == FBO_CUBE ) + SetBits( texFlags, TF_CUBEMAP ); + else if( type == FBO_DEPTH ) + SetBits( texFlags, TF_DEPTHMAP ); + + if( !FBitSet( m_iFlags, FBO_LINEAR )) + SetBits( texFlags, TF_NEAREST ); + + if( FBitSet( m_iFlags, FBO_FLOAT )) + SetBits( texFlags, TF_ARB_FLOAT ); + + if( FBitSet( m_iFlags, FBO_RECTANGLE )) + SetBits( texFlags, TF_RECTANGLE ); + + if( FBitSet( m_iFlags, FBO_LUMINANCE )) + SetBits( texFlags, TF_LUMINANCE ); + else if( type == FBO_COLOR ) + SetBits( texFlags, TF_HAS_ALPHA ); + + m_iTexture = CREATE_TEXTURE( va( "*framebuffer#%i", m_iBufferNum++ ), m_iFrameWidth, m_iFrameHeight, NULL, texFlags ); + } + + m_bAllowFBO = (GL_Support( R_FRAMEBUFFER_OBJECT )) ? true : false; + + if( m_bAllowFBO ) + { + // frame buffer + pglGenFramebuffers( 1, &m_iFrameBuffer ); + pglBindFramebuffer( GL_FRAMEBUFFER_EXT, m_iFrameBuffer ); + + // depth buffer + pglGenRenderbuffers( 1, &m_iDepthBuffer ); + pglBindRenderbuffer( GL_RENDERBUFFER_EXT, m_iDepthBuffer ); + pglRenderbufferStorage( GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, m_iFrameWidth, m_iFrameHeight ); + + // attach depthbuffer to framebuffer + pglFramebufferRenderbuffer( GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, m_iDepthBuffer ); + + // attach the framebuffer to our texture, which may be a depth texture + if( type == FBO_DEPTH ) + { + m_iAttachment = GL_DEPTH_ATTACHMENT_EXT; + pglDrawBuffer( GL_NONE ); + pglReadBuffer( GL_NONE ); + } + else + { + m_iAttachment = GL_COLOR_ATTACHMENT0_EXT; + pglDrawBuffer( m_iAttachment ); + pglReadBuffer( m_iAttachment ); + } + + if( m_iTexture != 0 ) + { + GLuint target = RENDER_GET_PARM( PARM_TEX_TARGET, m_iTexture ); + GLuint texnum = RENDER_GET_PARM( PARM_TEX_TEXNUM, m_iTexture ); + + if( target == GL_TEXTURE_CUBE_MAP_ARB ) + { + target = GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB; + for( int i = 0; i < 6; i++ ) + pglFramebufferTexture2D( GL_FRAMEBUFFER_EXT, m_iAttachment, target + i, texnum, 0 ); + } + else pglFramebufferTexture2D( GL_FRAMEBUFFER_EXT, m_iAttachment, target, texnum, 0 ); + } + + m_bAllowFBO = ValidateFBO(); + pglBindFramebuffer( GL_FRAMEBUFFER_EXT, 0 ); + } + + return true; +} + +void CFrameBuffer :: Free( void ) +{ + if( !m_bAllowFBO ) return; + + if( !FBitSet( m_iFlags, FBO_NOTEXTURE ) && m_iTexture != 0 ) + FREE_TEXTURE( m_iTexture ); + + pglDeleteRenderbuffers( 1, &m_iDepthBuffer ); + pglDeleteFramebuffers( 1, &m_iFrameBuffer ); + + m_iFrameWidth = m_iFrameHeight = 0; + m_iFrameBuffer = m_iDepthBuffer = 0; + m_iTexture = m_iAttachment = 0; + + m_bAllowFBO = false; +} + +bool CFrameBuffer :: ValidateFBO( void ) +{ + if( !GL_Support( R_FRAMEBUFFER_OBJECT )) + return false; + + // check FBO status + GLenum status = pglCheckFramebufferStatus( GL_FRAMEBUFFER_EXT ); + + switch( status ) + { + case GL_FRAMEBUFFER_COMPLETE_EXT: + return true; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: + ALERT( at_error, "CFrameBuffer: attachment is NOT complete\n" ); + return false; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: + ALERT( at_error, "CFrameBuffer: no image is attached to FBO\n" ); + return false; + case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: + ALERT( at_error, "CFrameBuffer: attached images have different dimensions\n" ); + return false; + case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: + ALERT( at_error, "CFrameBuffer: color attached images have different internal formats\n" ); + return false; + case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: + ALERT( at_error, "CFrameBuffer: draw buffer incomplete\n" ); + return false; + case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: + ALERT( at_error, "CFrameBuffere: read buffer incomplete\n" ); + return false; + case GL_FRAMEBUFFER_UNSUPPORTED_EXT: + ALERT( at_error, "CFrameBuffer: unsupported by current FBO implementation\n" ); + return false; + default: + ALERT( at_error, "CFrameBuffer: unknown error\n" ); + return false; + } +} + +void CFrameBuffer :: Bind( GLuint texture, GLuint side ) +{ + if( !m_bAllowFBO ) return; + + if( glState.frameBuffer != m_iFrameBuffer ) + { + pglBindFramebuffer( GL_FRAMEBUFFER_EXT, m_iFrameBuffer ); + glState.frameBuffer = m_iFrameBuffer; + } + + // change texture if needs + if( FBitSet( m_iFlags, FBO_NOTEXTURE ) && texture != 0 ) + { + m_iTexture = texture; + + GLuint target = RENDER_GET_PARM( PARM_TEX_TARGET, m_iTexture ); + GLuint texnum = RENDER_GET_PARM( PARM_TEX_TEXNUM, m_iTexture ); + + if( target == GL_TEXTURE_CUBE_MAP_ARB ) + target = GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB + side; + + pglFramebufferTexture2D( GL_FRAMEBUFFER_EXT, m_iAttachment, target, texnum, 0 ); + } + + ValidateFBO(); +} \ No newline at end of file diff --git a/cl_dll/render/gl_framebuffer.h b/cl_dll/render/gl_framebuffer.h new file mode 100644 index 0000000..ee75f69 --- /dev/null +++ b/cl_dll/render/gl_framebuffer.h @@ -0,0 +1,63 @@ +/* +gl_framebuffer.h - framebuffer implementation class +this code written for Paranoia 2: Savior modification +Copyright (C) 2014 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef GL_FRAMEBUFFER_H +#define GL_FRAMEBUFFER_H + +typedef enum +{ + FBO_COLOR = 0, // only color texture is used + FBO_DEPTH, // only depth texture is used + FBO_CUBE, // only color texture as cubemap is side +} FBO_TYPE; + +#define FBO_MAKEPOW (1<<0) // round buffer size to nearest pow +#define FBO_NOTEXTURE (1<<1) // don't create texture on initialization +#define FBO_FLOAT (1<<2) // use float texture +#define FBO_RECTANGLE (1<<3) // use rectangle texture +#define FBO_LINEAR (1<<4) // use linear filtering +#define FBO_LUMINANCE (1<<5) // force to luminance texture + +class CFrameBuffer +{ +public: + CFrameBuffer(); + ~CFrameBuffer(); + + bool Init( FBO_TYPE type, GLuint width, GLuint height, GLuint flags = 0 ); + void Bind( GLuint texture = 0, GLuint side = 0 ); + bool ValidateFBO( void ); + void Free( void ); + + unsigned int GetWidth( void ) const { return m_iFrameWidth; } + unsigned int GetHeight( void ) const { return m_iFrameHeight; } + int GetTexture( void ) const { return m_iTexture; } + bool Active( void ) const { return m_bAllowFBO; } +protected: + static int m_iBufferNum; // single object for all instances +private: + GLuint m_iFrameWidth; + GLuint m_iFrameHeight; + GLint m_iTexture; + + GLuint m_iFrameBuffer; + GLuint m_iDepthBuffer; + GLenum m_iAttachment; // attachment type + bool m_bAllowFBO; // FBO is valid + GLuint m_iFlags; // member FBO flags +}; + +#endif//GL_FRAMEBUFFER_H \ No newline at end of file diff --git a/cl_dll/render/gl_frustum.cpp b/cl_dll/render/gl_frustum.cpp new file mode 100644 index 0000000..9b56b9c --- /dev/null +++ b/cl_dll/render/gl_frustum.cpp @@ -0,0 +1,374 @@ +/* +gl_frustum.cpp - frustum test implementation class +Copyright (C) 2014 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "ref_params.h" +#include "gl_local.h" +#include +#include + +void CFrustum :: ClearFrustum( void ) +{ + memset( planes, 0, sizeof( planes )); + clipFlags = 0; +} + +void CFrustum :: EnablePlane( int side ) +{ + ASSERT( side >= 0 && side < FRUSTUM_PLANES ); + + // make sure what plane is ready + if( planes[side].normal != g_vecZero ) + SetBits( clipFlags, BIT( side )); +} + +void CFrustum :: DisablePlane( int side ) +{ + ASSERT( side >= 0 && side < FRUSTUM_PLANES ); + ClearBits( clipFlags, BIT( side )); +} + +void CFrustum :: InitProjection( const matrix4x4 &view, float flZNear, float flZFar, float flFovX, float flFovY ) +{ + float xs, xc; + Vector normal; + + // horizontal fov used for left and right planes + SinCos( DEG2RAD( flFovX ) * 0.5f, &xs, &xc ); + + // setup left plane + normal = view.GetForward() * xs + view.GetRight() * -xc; + SetPlane( FRUSTUM_LEFT, normal, DotProduct( view.GetOrigin(), normal )); + + // setup right plane + normal = view.GetForward() * xs + view.GetRight() * xc; + SetPlane( FRUSTUM_RIGHT, normal, DotProduct( view.GetOrigin(), normal )); + + // vertical fov used for top and bottom planes + SinCos( DEG2RAD( flFovY ) * 0.5f, &xs, &xc ); + + // setup bottom plane + normal = view.GetForward() * xs + view.GetUp() * -xc; + SetPlane( FRUSTUM_BOTTOM, normal, DotProduct( view.GetOrigin(), normal )); + + // setup top plane + normal = view.GetForward() * xs + view.GetUp() * xc; + SetPlane( FRUSTUM_TOP, normal, DotProduct( view.GetOrigin(), normal )); + + // setup far plane + SetPlane( FRUSTUM_FAR, -view.GetForward(), DotProduct( -view.GetForward(), ( view.GetOrigin() + view.GetForward() * flZFar ))); + + // no need to setup backplane for general view. It's only used for portals and mirrors + if( flZNear == 0.0f ) return; + + // setup near plane + SetPlane( FRUSTUM_NEAR, view.GetForward(), DotProduct( view.GetForward(), ( view.GetOrigin() + view.GetForward() * flZNear ))); +} + +void CFrustum :: InitOrthogonal( const matrix4x4 &view, float xLeft, float xRight, float yBottom, float yTop, float flZNear, float flZFar ) +{ + // setup the near and far planes + float orgOffset = DotProduct( view.GetOrigin(), view.GetForward() ); + + SetPlane( FRUSTUM_FAR, -view.GetForward(), -flZNear - orgOffset ); + SetPlane( FRUSTUM_NEAR, view.GetForward(), flZFar + orgOffset ); + + // setup left and right planes + orgOffset = DotProduct( view.GetOrigin(), view.GetRight() ); + + SetPlane( FRUSTUM_LEFT, view.GetRight(), xLeft + orgOffset ); + SetPlane( FRUSTUM_RIGHT, -view.GetRight(), -xRight - orgOffset ); + + // setup top and buttom planes + orgOffset = DotProduct( view.GetOrigin(), view.GetUp() ); + + SetPlane( FRUSTUM_TOP, view.GetUp(), yTop + orgOffset ); + SetPlane( FRUSTUM_BOTTOM, -view.GetUp(), -yBottom - orgOffset ); +} + +void CFrustum :: InitBoxFrustum( const Vector &org, float radius ) +{ + for( int i = 0; i < 6; i++ ) + { + // setup normal for each direction + Vector normal = g_vecZero; + normal[((i >> 1) + 1) % 3] = (i & 1) ? 1.0f : -1.0f; + SetPlane( i, normal, DotProduct( org, normal ) - radius ); + } +} + +void CFrustum :: InitProjectionFromMatrix( const matrix4x4 &projection ) +{ + // left + planes[FRUSTUM_LEFT].normal[0] = projection[0][3] + projection[0][0]; + planes[FRUSTUM_LEFT].normal[1] = projection[1][3] + projection[1][0]; + planes[FRUSTUM_LEFT].normal[2] = projection[2][3] + projection[2][0]; + planes[FRUSTUM_LEFT].dist = -(projection[3][3] + projection[3][0]); + + // right + planes[FRUSTUM_RIGHT].normal[0] = projection[0][3] - projection[0][0]; + planes[FRUSTUM_RIGHT].normal[1] = projection[1][3] - projection[1][0]; + planes[FRUSTUM_RIGHT].normal[2] = projection[2][3] - projection[2][0]; + planes[FRUSTUM_RIGHT].dist = -(projection[3][3] - projection[3][0]); + + // bottom + planes[FRUSTUM_BOTTOM].normal[0] = projection[0][3] + projection[0][1]; + planes[FRUSTUM_BOTTOM].normal[1] = projection[1][3] + projection[1][1]; + planes[FRUSTUM_BOTTOM].normal[2] = projection[2][3] + projection[2][1]; + planes[FRUSTUM_BOTTOM].dist = -(projection[3][3] + projection[3][1]); + + // top + planes[FRUSTUM_TOP].normal[0] = projection[0][3] - projection[0][1]; + planes[FRUSTUM_TOP].normal[1] = projection[1][3] - projection[1][1]; + planes[FRUSTUM_TOP].normal[2] = projection[2][3] - projection[2][1]; + planes[FRUSTUM_TOP].dist = -(projection[3][3] - projection[3][1]); + + // near + planes[FRUSTUM_NEAR].normal[0] = projection[0][3] + projection[0][2]; + planes[FRUSTUM_NEAR].normal[1] = projection[1][3] + projection[1][2]; + planes[FRUSTUM_NEAR].normal[2] = projection[2][3] + projection[2][2]; + planes[FRUSTUM_NEAR].dist = -(projection[3][3] + projection[3][2]); + + // far + planes[FRUSTUM_FAR].normal[0] = projection[0][3] - projection[0][2]; + planes[FRUSTUM_FAR].normal[1] = projection[1][3] - projection[1][2]; + planes[FRUSTUM_FAR].normal[2] = projection[2][3] - projection[2][2]; + planes[FRUSTUM_FAR].dist = -(projection[3][3] - projection[3][2]); + + for( int i = 0; i < FRUSTUM_PLANES; i++ ) + { + NormalizePlane( i ); + } +} + +void CFrustum :: SetPlane( int side, const Vector &vecNormal, float flDist ) +{ + ASSERT( side >= 0 && side < FRUSTUM_PLANES ); + + planes[side].type = PlaneTypeForNormal( vecNormal ); + planes[side].signbits = SignbitsForPlane( vecNormal ); + planes[side].normal = vecNormal; + planes[side].dist = flDist; + + clipFlags |= BIT( side ); +} + +void CFrustum :: NormalizePlane( int side ) +{ + ASSERT( side >= 0 && side < FRUSTUM_PLANES ); + + // normalize + float length = planes[side].normal.Length(); + + if( length ) + { + float ilength = (1.0f / length); + planes[side].normal.x *= ilength; + planes[side].normal.y *= ilength; + planes[side].normal.z *= ilength; + planes[side].dist *= ilength; + } + + planes[side].type = PlaneTypeForNormal( planes[side].normal ); + planes[side].signbits = SignbitsForPlane( planes[side].normal ); + + clipFlags |= BIT( side ); +} + +void CFrustum :: ComputeFrustumCorners( Vector corners[8] ) +{ + memset( corners, 0, sizeof( Vector ) * 8 ); + + PlanesGetIntersectionPoint( &planes[FRUSTUM_LEFT], &planes[FRUSTUM_TOP], &planes[FRUSTUM_FAR], corners[0] ); + PlanesGetIntersectionPoint( &planes[FRUSTUM_RIGHT], &planes[FRUSTUM_TOP], &planes[FRUSTUM_FAR], corners[1] ); + PlanesGetIntersectionPoint( &planes[FRUSTUM_LEFT], &planes[FRUSTUM_BOTTOM], &planes[FRUSTUM_FAR], corners[2] ); + PlanesGetIntersectionPoint( &planes[FRUSTUM_RIGHT], &planes[FRUSTUM_BOTTOM], &planes[FRUSTUM_FAR], corners[3] ); + + if( FBitSet( clipFlags, BIT( FRUSTUM_NEAR ))) + { + PlanesGetIntersectionPoint( &planes[FRUSTUM_LEFT], &planes[FRUSTUM_TOP], &planes[FRUSTUM_NEAR], corners[4] ); + PlanesGetIntersectionPoint( &planes[FRUSTUM_RIGHT], &planes[FRUSTUM_TOP], &planes[FRUSTUM_NEAR], corners[5] ); + PlanesGetIntersectionPoint( &planes[FRUSTUM_LEFT], &planes[FRUSTUM_BOTTOM], &planes[FRUSTUM_NEAR], corners[6] ); + PlanesGetIntersectionPoint( &planes[FRUSTUM_RIGHT], &planes[FRUSTUM_BOTTOM], &planes[FRUSTUM_NEAR], corners[7] ); + } + else + { + PlanesGetIntersectionPoint( &planes[FRUSTUM_LEFT], &planes[FRUSTUM_RIGHT], &planes[FRUSTUM_TOP], corners[4] ); + corners[7] = corners[6] = corners[5] = corners[4]; + } +} + +void CFrustum :: ComputeFrustumBounds( Vector &mins, Vector &maxs ) +{ + Vector corners[8]; + + ComputeFrustumCorners( corners ); + + ClearBounds( mins, maxs ); + + for( int i = 0; i < 8; i++ ) + AddPointToBounds( corners[i], mins, maxs ); +} + +void CFrustum :: DrawFrustumDebug( void ) +{ + Vector bbox[8]; + + ComputeFrustumCorners( bbox ); + + // g-cont. frustum must be yellow :-) + pglColor4f( 1.0f, 1.0f, 0.0f, 1.0f ); + GL_BindTexture( GL_TEXTURE0, tr.whiteTexture ); + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + pglBegin( GL_LINES ); + + for( int i = 0; i < 2; i += 1 ) + { + pglVertex3fv( bbox[i+0] ); + pglVertex3fv( bbox[i+2] ); + pglVertex3fv( bbox[i+4] ); + pglVertex3fv( bbox[i+6] ); + pglVertex3fv( bbox[i+0] ); + pglVertex3fv( bbox[i+4] ); + pglVertex3fv( bbox[i+2] ); + pglVertex3fv( bbox[i+6] ); + pglVertex3fv( bbox[i*2+0] ); + pglVertex3fv( bbox[i*2+1] ); + pglVertex3fv( bbox[i*2+4] ); + pglVertex3fv( bbox[i*2+5] ); + } + + pglEnd(); +} + +bool CFrustum :: CullBox( const Vector &mins, const Vector &maxs, int userClipFlags ) +{ + int iClipFlags; + + if( CVAR_TO_BOOL( r_nocull )) + return false; + + if( userClipFlags != 0 ) + iClipFlags = userClipFlags; + else iClipFlags = clipFlags; + + for( int i = 0; i < FRUSTUM_PLANES; i++ ) + { + if( !FBitSet( iClipFlags, BIT( i ))) + continue; + + const mplane_t *p = &planes[i]; + + switch( p->signbits ) + { + case 0: + if( p->normal.x * maxs.x + p->normal.y * maxs.y + p->normal.z * maxs.z < p->dist ) + return true; + break; + case 1: + if( p->normal.x * mins.x + p->normal.y * maxs.y + p->normal.z * maxs.z < p->dist ) + return true; + break; + case 2: + if( p->normal.x * maxs.x + p->normal.y * mins.y + p->normal.z * maxs.z < p->dist ) + return true; + break; + case 3: + if( p->normal.x * mins.x + p->normal.y * mins.y + p->normal.z * maxs.z < p->dist ) + return true; + break; + case 4: + if( p->normal.x * maxs.x + p->normal.y * maxs.y + p->normal.z * mins.z < p->dist ) + return true; + break; + case 5: + if( p->normal.x * mins.x + p->normal.y * maxs.y + p->normal.z * mins.z < p->dist ) + return true; + break; + case 6: + if( p->normal.x * maxs.x + p->normal.y * mins.y + p->normal.z * mins.z < p->dist ) + return true; + break; + case 7: + if( p->normal.x * mins.x + p->normal.y * mins.y + p->normal.z * mins.z < p->dist ) + return true; + break; + default: + return false; + } + } + + return false; +} + +bool CFrustum :: CullSphere( const Vector ¢re, float radius, int userClipFlags ) +{ + int iClipFlags; + + if( CVAR_TO_BOOL( r_nocull )) + return false; + + if( userClipFlags != 0 ) + iClipFlags = userClipFlags; + else iClipFlags = clipFlags; + + for( int i = 0; i < FRUSTUM_PLANES; i++ ) + { + if( !FBitSet( iClipFlags, BIT( i ))) + continue; + + const mplane_t *p = &planes[i]; + + if( DotProduct( centre, p->normal ) - p->dist <= -radius ) + return true; + } + + return false; +} + +// FIXME: could be optimize? +bool CFrustum :: CullFrustum( CFrustum *frustum, int userClipFlags ) +{ + int iClipFlags; + Vector bbox[8]; + + if( CVAR_TO_BOOL( r_nocull )) + return false; + + if( userClipFlags != 0 ) + iClipFlags = userClipFlags; + else iClipFlags = clipFlags; + + frustum->ComputeFrustumCorners( bbox ); + + for( int i = 0; i < FRUSTUM_PLANES; i++ ) + { + if( !FBitSet( iClipFlags, BIT( i ))) + continue; + + const mplane_t *p = &planes[i]; + + for( int j = 0; j < 8; j++ ) + { + // at least one point of other frustum intersect with our frustum + if( DotProduct( bbox[j], p->normal ) - p->dist >= ON_EPSILON ) + return false; + } + } + + return true; +} \ No newline at end of file diff --git a/cl_dll/render/gl_frustum.h b/cl_dll/render/gl_frustum.h new file mode 100644 index 0000000..d21727c --- /dev/null +++ b/cl_dll/render/gl_frustum.h @@ -0,0 +1,60 @@ +/* +gl_frustum.h - frustum test implementation class +this code written for Paranoia 2: Savior modification +Copyright (C) 2014 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef GL_FRUSTUM_H +#define GL_FRUSTUM_H + +// don't change this order +#define FRUSTUM_LEFT 0 +#define FRUSTUM_RIGHT 1 +#define FRUSTUM_BOTTOM 2 +#define FRUSTUM_TOP 3 +#define FRUSTUM_FAR 4 +#define FRUSTUM_NEAR 5 +#define FRUSTUM_PLANES 6 + +class CFrustum +{ +public: + void InitProjection( const matrix4x4 &view, float flZNear, float flZFar, float flFovX, float flFovY ); + void InitOrthogonal( const matrix4x4 &view, float xLeft, float xRight, float yBottom, float yTop, float flZNear, float flZFar ); + void InitBoxFrustum( const Vector &org, float radius ); // used for pointlights + void InitProjectionFromMatrix( const matrix4x4 &projection ); + void SetPlane( int side, const mplane_t *plane ) { planes[side] = *plane; } + void SetPlane( int side, const Vector &vecNormal, float flDist ); + void NormalizePlane( int side ); + const mplane_t *GetPlane( int side ) const { return &planes[side]; } + const mplane_t *GetPlanes( void ) const { return &planes[0]; } + unsigned int GetClipFlags( void ) const { return clipFlags; } + void ComputeFrustumBounds( Vector &mins, Vector &maxs ); + void ComputeFrustumCorners( Vector bbox[8] ); + void DrawFrustumDebug( void ); + void ClearFrustum( void ); + + // cull methods + bool CullBox( const Vector &mins, const Vector &maxs, int userClipFlags = 0 ); + bool CullSphere( const Vector ¢re, float radius, int userClipFlags = 0 ); + bool CullFrustum( CFrustum *frustum, int userClipFlags = 0 ); + + // plane manipulating + void EnablePlane( int side ); + void DisablePlane( int side ); +private: + mplane_t planes[FRUSTUM_PLANES]; + unsigned int clipFlags; +}; + +#endif//GL_FRUSTUM_H \ No newline at end of file diff --git a/cl_dll/render/gl_grass.cpp b/cl_dll/render/gl_grass.cpp new file mode 100644 index 0000000..985790e --- /dev/null +++ b/cl_dll/render/gl_grass.cpp @@ -0,0 +1,1775 @@ +/* +gl_grass.cpp - auto grass system +Copyright (C) 2014 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include "cl_util.h" +#include "gl_local.h" +#include "gl_world.h" +#include "gl_grass.h" +#include "gl_shader.h" +#include +#include +#include "vertex_fmt.h" + +#define LEAF_MAX_EXPAND 48.0f +#define DENSITY_FACTOR 0.0001f + +grasstexture_t grasstexs[GRASS_TEXTURES]; +CUtlArray grassInfo; + +// intermediate arrays used for creating VBO's +static gvert_t m_arraygvert[MAX_GRASS_VERTS]; +static word m_indexarray[MAX_GRASS_ELEMS]; +static int m_iNumVertex, m_iNumIndex, m_iVertexState; +static int m_iTotalVerts = 0; +static int m_iTextureWidth; +static int m_iTextureHeight; +static float m_flGrassFadeStart; +static float m_flGrassFadeDist; +static float m_flGrassFadeEnd; + +static void CreateBufferBaseGL21( grass_t *pOut, gvert_t *arrayxvert ) +{ + static gvert_v0_gl21_t arraygvert[MAX_GRASS_VERTS]; + + // convert to GLSL-compacted array + for( int i = 0; i < pOut->numVerts; i++ ) + { + arraygvert[i].center[0] = arrayxvert[i].center[0]; + arraygvert[i].center[1] = arrayxvert[i].center[1]; + arraygvert[i].center[2] = arrayxvert[i].center[2]; + arraygvert[i].center[3] = arrayxvert[i].center[3]; + arraygvert[i].light[0] = arrayxvert[i].light[0]; + arraygvert[i].light[1] = arrayxvert[i].light[1]; + arraygvert[i].light[2] = arrayxvert[i].light[2]; + arraygvert[i].light[3] = arrayxvert[i].light[3]; + arraygvert[i].normal[0] = arrayxvert[i].normal[0]; + arraygvert[i].normal[1] = arrayxvert[i].normal[1]; + arraygvert[i].normal[2] = arrayxvert[i].normal[2]; + arraygvert[i].normal[3] = arrayxvert[i].normal[3]; + } + + pOut->cacheSize = pOut->numVerts * sizeof( gvert_v0_gl21_t ); + + // create grass static buffer + pglGenBuffersARB( 1, &pOut->vbo ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, pOut->cacheSize, &arraygvert[0], GL_STATIC_DRAW_ARB ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); +} + +static void BindBufferBaseGL21( grass_t *pOut, int attrFlags ) +{ + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + + pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 4, GL_FLOAT, GL_FALSE, sizeof( gvert_v0_gl21_t ), (void *)offsetof( gvert_v0_gl21_t, center )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION ); + + pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 4, GL_FLOAT, GL_FALSE, sizeof( gvert_v0_gl21_t ), (void *)offsetof( gvert_v0_gl21_t, normal )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL ); + + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_COLOR, 4, GL_FLOAT, GL_FALSE, sizeof( gvert_v0_gl21_t ), (void *)offsetof( gvert_v0_gl21_t, light )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_COLOR ); + + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_STYLES, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof( gvert_v0_gl21_t ), (void *)offsetof( gvert_v0_gl21_t, styles )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_STYLES ); +} + +static void CreateBufferBaseGL30( grass_t *pOut, gvert_t *arrayxvert ) +{ + static gvert_v0_gl30_t arraygvert[MAX_GRASS_VERTS]; + + // convert to GLSL-compacted array + for( int i = 0; i < pOut->numVerts; i++ ) + { + arraygvert[i].center[0] = FloatToHalf( arrayxvert[i].center[0] ); + arraygvert[i].center[1] = FloatToHalf( arrayxvert[i].center[1] ); + arraygvert[i].center[2] = FloatToHalf( arrayxvert[i].center[2] ); + arraygvert[i].center[3] = FloatToHalf( arrayxvert[i].center[3] ); + arraygvert[i].light[0] = arrayxvert[i].light[0]; + arraygvert[i].light[1] = arrayxvert[i].light[1]; + arraygvert[i].light[2] = arrayxvert[i].light[2]; + arraygvert[i].light[3] = arrayxvert[i].light[3]; + CompressNormalizedVector( arraygvert[i].normal, arrayxvert[i].normal ); + arraygvert[i].normal[3] = arrayxvert[i].normal[3]; + } + + pOut->cacheSize = pOut->numVerts * sizeof( gvert_v0_gl30_t ); + + // create grass static buffer + pglGenBuffersARB( 1, &pOut->vbo ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, pOut->cacheSize, &arraygvert[0], GL_STATIC_DRAW_ARB ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); +} + +static void BindBufferBaseGL30( grass_t *pOut, int attrFlags ) +{ + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + + pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 4, GL_HALF_FLOAT_ARB, GL_FALSE, sizeof( gvert_v0_gl30_t ), (void *)offsetof( gvert_v0_gl30_t, center )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION ); + + pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 4, GL_BYTE, GL_FALSE, sizeof( gvert_v0_gl30_t ), (void *)offsetof( gvert_v0_gl30_t, normal )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL ); + + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_COLOR, 4, GL_FLOAT, GL_FALSE, sizeof( gvert_v0_gl30_t ), (void *)offsetof( gvert_v0_gl30_t, light )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_COLOR ); + + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_STYLES, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof( gvert_v0_gl30_t ), (void *)offsetof( gvert_v0_gl30_t, styles )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_STYLES ); +} + +static void CreateBufferBaseBumpGL21( grass_t *pOut, gvert_t *arrayxvert ) +{ + static gvert_v1_gl21_t arraygvert[MAX_GRASS_VERTS]; + + // convert to GLSL-compacted array + for( int i = 0; i < pOut->numVerts; i++ ) + { + arraygvert[i].center[0] = arrayxvert[i].center[0]; + arraygvert[i].center[1] = arrayxvert[i].center[1]; + arraygvert[i].center[2] = arrayxvert[i].center[2]; + arraygvert[i].center[3] = arrayxvert[i].center[3]; + arraygvert[i].light[0] = arrayxvert[i].light[0]; + arraygvert[i].light[1] = arrayxvert[i].light[1]; + arraygvert[i].light[2] = arrayxvert[i].light[2]; + arraygvert[i].light[3] = arrayxvert[i].light[3]; + arraygvert[i].delux[0] = arrayxvert[i].delux[0]; + arraygvert[i].delux[1] = arrayxvert[i].delux[1]; + arraygvert[i].delux[2] = arrayxvert[i].delux[2]; + arraygvert[i].delux[3] = arrayxvert[i].delux[3]; + arraygvert[i].normal[0] = arrayxvert[i].normal[0]; + arraygvert[i].normal[1] = arrayxvert[i].normal[1]; + arraygvert[i].normal[2] = arrayxvert[i].normal[2]; + arraygvert[i].normal[3] = arrayxvert[i].normal[3]; + } + + pOut->cacheSize = pOut->numVerts * sizeof( gvert_v1_gl21_t ); + + // create grass static buffer + pglGenBuffersARB( 1, &pOut->vbo ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, pOut->cacheSize, &arraygvert[0], GL_STATIC_DRAW_ARB ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); +} + +static void BindBufferBaseBumpGL21( grass_t *pOut, int attrFlags ) +{ + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + + pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 4, GL_FLOAT, GL_FALSE, sizeof( gvert_v1_gl21_t ), (void *)offsetof( gvert_v1_gl21_t, center )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION ); + + pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 4, GL_FLOAT, GL_FALSE, sizeof( gvert_v1_gl21_t ), (void *)offsetof( gvert_v1_gl21_t, normal )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL ); + + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_COLOR, 4, GL_FLOAT, GL_FALSE, sizeof( gvert_v1_gl21_t ), (void *)offsetof( gvert_v1_gl21_t, light )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_COLOR ); + + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_VECS, 4, GL_FLOAT, GL_FALSE, sizeof( gvert_v1_gl21_t ), (void *)offsetof( gvert_v1_gl21_t, delux )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_VECS ); + + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_STYLES, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof( gvert_v1_gl21_t ), (void *)offsetof( gvert_v1_gl21_t, styles )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_STYLES ); +} + +static void CreateBufferBaseBumpGL30( grass_t *pOut, gvert_t *arrayxvert ) +{ + static gvert_v1_gl30_t arraygvert[MAX_GRASS_VERTS]; + + // convert to GLSL-compacted array + for( int i = 0; i < pOut->numVerts; i++ ) + { + arraygvert[i].center[0] = FloatToHalf( arrayxvert[i].center[0] ); + arraygvert[i].center[1] = FloatToHalf( arrayxvert[i].center[1] ); + arraygvert[i].center[2] = FloatToHalf( arrayxvert[i].center[2] ); + arraygvert[i].center[3] = FloatToHalf( arrayxvert[i].center[3] ); + arraygvert[i].light[0] = arrayxvert[i].light[0]; + arraygvert[i].light[1] = arrayxvert[i].light[1]; + arraygvert[i].light[2] = arrayxvert[i].light[2]; + arraygvert[i].light[3] = arrayxvert[i].light[3]; + arraygvert[i].delux[0] = arrayxvert[i].delux[0]; + arraygvert[i].delux[1] = arrayxvert[i].delux[1]; + arraygvert[i].delux[2] = arrayxvert[i].delux[2]; + arraygvert[i].delux[3] = arrayxvert[i].delux[3]; + CompressNormalizedVector( arraygvert[i].normal, arrayxvert[i].normal ); + arraygvert[i].normal[3] = arrayxvert[i].normal[3]; + } + + pOut->cacheSize = pOut->numVerts * sizeof( gvert_v1_gl30_t ); + + // create grass static buffer + pglGenBuffersARB( 1, &pOut->vbo ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, pOut->numVerts * sizeof( gvert_v1_gl30_t ), &arraygvert[0], GL_STATIC_DRAW_ARB ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); +} + +static void BindBufferBaseBumpGL30( grass_t *pOut, int attrFlags ) +{ + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + + pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 4, GL_HALF_FLOAT_ARB, GL_FALSE, sizeof( gvert_v1_gl30_t ), (void *)offsetof( gvert_v1_gl30_t, center )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION ); + + pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 4, GL_BYTE, GL_FALSE, sizeof( gvert_v1_gl30_t ), (void *)offsetof( gvert_v1_gl30_t, normal )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL ); + + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_COLOR, 4, GL_FLOAT, GL_FALSE, sizeof( gvert_v1_gl30_t ), (void *)offsetof( gvert_v1_gl30_t, light )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_COLOR ); + + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_VECS, 4, GL_FLOAT, GL_FALSE, sizeof( gvert_v1_gl30_t ), (void *)offsetof( gvert_v1_gl30_t, delux )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_VECS ); + + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_STYLES, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof( gvert_v1_gl30_t ), (void *)offsetof( gvert_v1_gl30_t, styles )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_STYLES ); +} + +static void CreateIndexBuffer( grass_t *pOut, unsigned short *arrayelems ) +{ + uint cacheSize = pOut->numElems * sizeof( word ); + + pglGenBuffersARB( 1, &pOut->ibo ); + pglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, pOut->ibo ); + pglBufferDataARB( GL_ELEMENT_ARRAY_BUFFER_ARB, cacheSize, &arrayelems[0], GL_STATIC_DRAW_ARB ); + pglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 ); + + // update total buffer size + pOut->cacheSize += cacheSize; +} + +static void BindIndexBuffer( grass_t *pOut ) +{ + pglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, pOut->ibo ); +} + +unsigned int SelectGrassLoader( void ) +{ + if( FBitSet( world->features, WORLD_HAS_DELUXEMAP ) && r_grass->value > 1.0f ) + return GRASSLOADER_BASEBUMP; + return GRASSLOADER_BASE; +} + +static grass_loader_t pfnMeshLoaderGL21[GRASSLOADER_COUNT] = +{ +{ CreateBufferBaseGL21, BindBufferBaseGL21, "BaseBuffer" }, +{ CreateBufferBaseBumpGL21, BindBufferBaseBumpGL21, "BumpBaseBuffer" }, +}; + +static grass_loader_t pfnMeshLoaderGL30[GRASSLOADER_COUNT] = +{ +{ CreateBufferBaseGL30, BindBufferBaseGL30, "BaseBuffer" }, +{ CreateBufferBaseBumpGL30, BindBufferBaseBumpGL30, "BumpBaseBuffer" }, +}; + +/* +================ +R_GetPointForBush + +compute 16 points for single bush +================ +*/ +static const Vector R_GetPointForBush( int vertexNum, const Vector &pos, float scale ) +{ + float s1 = ( m_iTextureWidth * 0.075f ) * scale; + float s2 = ( m_iTextureWidth * 0.1f ) * scale; + float s3 = ( m_iTextureHeight * 0.1f ) * scale; + + switch(( vertexNum & 15 )) + { + case 0: return Vector( pos.x - s2, pos.y, pos.z ); + case 1: return Vector( pos.x - s2, pos.y, pos.z + s3 ); + case 2: return Vector( pos.x + s2, pos.y, pos.z + s3 ); + case 3: return Vector( pos.x + s2, pos.y, pos.z ); + case 4: return Vector( pos.x, pos.y - s2, pos.z ); + case 5: return Vector( pos.x, pos.y - s2, pos.z + s3 ); + case 6: return Vector( pos.x, pos.y + s2, pos.z + s3 ); + case 7: return Vector( pos.x, pos.y + s2, pos.z ); + case 8: return Vector( pos.x - s1, pos.y - s1, pos.z ); + case 9: return Vector( pos.x - s1, pos.y - s1, pos.z + s3 ); + case 10: return Vector( pos.x + s1, pos.y + s1, pos.z + s3 ); + case 11: return Vector( pos.x + s1, pos.y + s1, pos.z ); + case 12: return Vector( pos.x - s1, pos.y + s1, pos.z ); + case 13: return Vector( pos.x - s1, pos.y + s1, pos.z + s3 ); + case 14: return Vector( pos.x + s1, pos.y - s1, pos.z + s3 ); + case 15: return Vector( pos.x + s1, pos.y - s1, pos.z ); + } + + return g_vecZero; // error +} +/* +================ +R_GetTexCoordForBush + +get fixed texcoord for bush +================ +*/ +static const Vector2D R_GetTexCoordForBush( int vertexNum ) +{ + switch(( vertexNum & 7 )) + { + case 0: return Vector2D( 0.0f, 1.0f ); + case 1: return Vector2D( 0.0f, 0.0f ); + case 2: return Vector2D( 1.0f, 0.0f ); + case 3: return Vector2D( 1.0f, 1.0f ); + case 4: return Vector2D( 1.0f, 1.0f ); + case 5: return Vector2D( 1.0f, 0.0f ); + case 6: return Vector2D( 0.0f, 0.0f ); + case 7: return Vector2D( 0.0f, 1.0f ); + } + + return Vector2D( 0.0f, 0.0f ); // error +} + +/* +================ +R_GetNormalForBush + +get fixed normals for single bush +================ +*/ +static const Vector R_GetNormalForBush( int vertexNum ) +{ + switch(( vertexNum & 15 )) + { + case 0: return Vector( 0.0f, 1.0f, 0.0f ); + case 1: return Vector( 0.0f, 1.0f, 0.0f ); + case 2: return Vector( 0.0f, 1.0f, 0.0f ); + case 3: return Vector( 0.0f, 1.0f, 0.0f ); + case 4: return Vector( -1.0f, 0.0f, 0.0f ); + case 5: return Vector( -1.0f, 0.0f, 0.0f ); + case 6: return Vector( -1.0f, 0.0f, 0.0f ); + case 7: return Vector( -1.0f, 0.0f, 0.0f ); + case 8: return Vector( -0.707107f, 0.707107f, 0.0f ); + case 9: return Vector( -0.707107f, 0.707107f, 0.0f ); + case 10: return Vector( -0.707107f, 0.707107f, 0.0f ); + case 11: return Vector( -0.707107f, 0.707107f, 0.0f ); + case 12: return Vector( 0.707107f, 0.707107f, 0.0f ); + case 13: return Vector( 0.707107f, 0.707107f, 0.0f ); + case 14: return Vector( 0.707107f, 0.707107f, 0.0f ); + case 15: return Vector( 0.707107f, 0.707107f, 0.0f ); + } + + return g_vecZero; // error +} + +/* +================ +R_AdvanceVertex + +routine to build quad sequences +================ +*/ +bool R_GrassAdvanceVertex( void ) +{ + if((( m_iNumIndex + 6 ) >= MAX_GRASS_ELEMS ) || (( m_iNumVertex + 4 ) >= MAX_GRASS_VERTS )) + return false; + + if( m_iVertexState++ < 3 ) + { + m_indexarray[m_iNumIndex++] = m_iNumVertex; + } + else + { + // we've already done triangle (0, 1, 2), now draw (2, 3, 0) + m_indexarray[m_iNumIndex++] = m_iNumVertex - 1; + m_indexarray[m_iNumIndex++] = m_iNumVertex; + m_indexarray[m_iNumIndex++] = m_iNumVertex - 3; + m_iVertexState = 0; + } + m_iNumVertex++; + + return true; +} + +/* +================ +R_GrassLightForVertex + +We already have a valid spot on texture +Just find lightmap point and update grass color +================ +*/ +void R_GrassLightForVertex( msurface_t *fa, mextrasurf_t *es, const Vector &vertex, float posz, float light[MAXLIGHTMAPS], float delux[MAXLIGHTMAPS] ) +{ + if( !worldmodel->lightdata || !fa->samples ) + return; + + float sample_size = Mod_SampleSizeForFace( fa ); + Vector pos = Vector( vertex.x, vertex.y, posz ); // drop to floor + float s = DotProduct( pos, fa->info->lmvecs[0] ) + fa->info->lmvecs[0][3] - fa->info->lightmapmins[0]; + float t = DotProduct( pos, fa->info->lmvecs[1] ) + fa->info->lmvecs[1][3] - fa->info->lightmapmins[1]; + int smax = (fa->info->lightextents[0] / sample_size) + 1; + int tmax = (fa->info->lightextents[1] / sample_size) + 1; + int size = smax * tmax; + int map; + + // some bushes may be out of current poly + s = bound( 0, s, fa->info->lightextents[0] ); + t = bound( 0, t, fa->info->lightextents[1] ); + s /= sample_size; + t /= sample_size; + + color24 *lm = fa->samples + Q_rint( t ) * smax + Q_rint( s ); + + for( map = 0; map < MAXLIGHTMAPS && fa->styles[map] != 255; map++ ) + { + color24 out; + out.r = TEXTURE_TO_TEXGAMMA( lm->r ); + out.g = TEXTURE_TO_TEXGAMMA( lm->g ); + out.b = TEXTURE_TO_TEXGAMMA( lm->b ); + light[map] = PackColor( out ); + lm += size; // skip to next lightmap + } + + if( !es->normals ) return; + + color24 *dm = es->normals + Q_rint( t ) * smax + Q_rint( s ); + Vector vec_x, vec_y, vec_z; + matrix3x3 mat; + + // flat TBN for better results + vec_x = Vector( fa->info->lmvecs[0] ); + vec_y = Vector( fa->info->lmvecs[1] ); + if( FBitSet( fa->flags, SURF_PLANEBACK )) + vec_z = -fa->plane->normal; + else vec_z = fa->plane->normal; + + // create tangent space rotational matrix + mat.SetForward( vec_x.Normalize( )); + mat.SetRight( -vec_y.Normalize( )); + mat.SetUp( vec_z.Normalize( )); + + for( map = 0; map < MAXLIGHTMAPS && fa->styles[map] != 255; map++ ) + { + float f = (1.0f / 128.0f); + Vector normal = Vector(((float)dm->r - 128.0f) * f, ((float)dm->g - 128.0f) * f, ((float)dm->b - 128.0f) * f); + delux[map] = NormalToFloat( mat.VectorRotate( normal )); + dm += size; // skip to next lightmap + } +} + +/* +================ +R_CreateSingleBush + +create a bush with specified pos +================ +*/ +bool R_CreateSingleBush( msurface_t *surf, mextrasurf_t *es, grasshdr_t *hdr, const Vector &pos, float size ) +{ + for( int i = 0; i < 16; i++ ) + { + gvert_t *entry = &m_arraygvert[m_iNumVertex]; + Vector vertex = R_GetPointForBush( i, pos, size ); + memcpy( entry->styles, surf->styles, sizeof( entry->styles )); + R_GrassLightForVertex( surf, es, vertex, pos.z, entry->light, entry->delux ); + AddPointToBounds( vertex, hdr->mins, hdr->maxs ); // build bbox for grass + Vector dir = ( vertex - pos ).Normalize(); + float scale = ( vertex - pos ).Length(); + + entry->center[0] = pos.x; + entry->center[1] = pos.y; + entry->center[2] = pos.z; + entry->center[3] = scale; + entry->normal[0] = dir.x; + entry->normal[1] = dir.y; + entry->normal[2] = dir.z; + entry->normal[3] = i; + + // generate indices + if( !R_GrassAdvanceVertex( )) + { + // vertexes is out + return false; + } + } + + return true; +} + +void R_CreateSurfaceVBO( grass_t *pOut ) +{ + if( !m_iNumVertex ) return; // empty mesh? + + GL_CheckVertexArrayBinding(); + + // determine optimal mesh loader + uint attribs = 0; // unused + uint type = SelectGrassLoader(); + + // move data to video memory + if( glConfig.version < ACTUAL_GL_VERSION ) + pfnMeshLoaderGL21[type].CreateBuffer( pOut, m_arraygvert ); + else pfnMeshLoaderGL30[type].CreateBuffer( pOut, m_arraygvert ); + CreateIndexBuffer( pOut, m_indexarray ); + + // link it with vertex array object + pglGenVertexArrays( 1, &pOut->vao ); + pglBindVertexArray( pOut->vao ); + if( glConfig.version < ACTUAL_GL_VERSION ) + pfnMeshLoaderGL21[type].BindBuffer( pOut, attribs ); + else pfnMeshLoaderGL30[type].BindBuffer( pOut, attribs ); + BindIndexBuffer( pOut ); + pglBindVertexArray( GL_FALSE ); + + // update stats + tr.total_vbo_memory += pOut->cacheSize; +} + +void R_DeleteSurfaceVBO( grass_t *pOut ) +{ + if( pOut->vao ) pglDeleteVertexArrays( 1, &pOut->vao ); + if( pOut->vbo ) pglDeleteBuffersARB( 1, &pOut->vbo ); + if( pOut->ibo ) pglDeleteBuffersARB( 1, &pOut->ibo ); + tr.total_vbo_memory -= pOut->cacheSize; + pOut->cacheSize = 0; +} + +/* +================ +R_BuildGrassMesh + +build mesh with single texture +================ +*/ +bool R_BuildGrassMesh( msurface_t *surf, mextrasurf_t *es, grassentry_t *entry, grasshdr_t *hdr, grass_t *out ) +{ + mfaceinfo_t *land = surf->texinfo->faceinfo; + bvert_t *v0, *v1, *v2; + + // update random set to get predictable positions for grass 'random' placement + RANDOM_SEED(( surf - worldmodel->surfaces ) * entry->seed ); + + m_iNumVertex = m_iNumIndex = m_iVertexState = 0; + + m_iTextureWidth = RENDER_GET_PARM( PARM_TEX_WIDTH, grasstexs[entry->texture].gl_texturenum ); + m_iTextureHeight = RENDER_GET_PARM( PARM_TEX_HEIGHT, grasstexs[entry->texture].gl_texturenum ); + + // turn the face into a bunch of polygons, and compute the area of each + v0 = &world->vertexes[es->firstvertex]; + + for( int i = 1; i < es->numverts - 1; i++ ) + { + v1 = &world->vertexes[es->firstvertex+i+0]; + v2 = &world->vertexes[es->firstvertex+i+1]; + + // compute two triangle edges + Vector e1 = v1->vertex - v0->vertex; + Vector e2 = v2->vertex - v0->vertex; + + // compute the triangle area + Vector areaVec = CrossProduct( e1, e2 ); + float normalLength = areaVec.Length(); + float area = 0.5f * normalLength; + + // compute the number of samples to take + int numSamples = area * entry->density * DENSITY_FACTOR; + + // now take a sample, and randomly place an object there + for( int j = 0; j < numSamples; j++ ) + { + // Create a random sample... + float u = RANDOM_FLOAT( 0.0f, 1.0f ); + float v = RANDOM_FLOAT( 0.0f, 1.0f ); + + if( v > ( 1.0f - u )) + { + u = 1.0f - u; + v = 1.0f - v; + } + + float size = RANDOM_FLOAT( entry->min, entry->max ); + + Vector pos = v0->vertex + e1 * u + e2 * v; + + if( !Mod_CheckLayerNameForPixel( land, pos, entry->name )) + continue; // rejected by heightmap + + if( !R_CreateSingleBush( surf, es, hdr, pos, size )) + goto build_mesh; // vertices is out (more than 2048 bushes per surface created) + } + } + + // nothing to added? + if( !m_iNumVertex ) return false; + +build_mesh: + // give lightnums from surface + memcpy( out->lights, es->lights, sizeof( byte ) * MAXDYNLIGHTS ); + out->texture = entry->texture; + out->numVerts = m_iNumVertex; + out->numElems = m_iNumIndex; + R_CreateSurfaceVBO( out ); + + return true; +} + +/* +================ +R_ConstructGrassForSurface + +compile all the grassdata with +specified texture into single VBO +================ +*/ +void R_ConstructGrassForSurface( msurface_t *surf, mextrasurf_t *es ) +{ + // already init or not specified? + if( es->grass || !es->grasscount ) + return; + + size_t grasshdr_size = sizeof( grasshdr_t ) + sizeof( grass_t ) * ( es->grasscount - 1 ); + grasshdr_t *hdr = es->grass = (grasshdr_t *)IEngineStudio.Mem_Calloc( 1, grasshdr_size ); + hdr->mins = es->mins; + hdr->maxs = es->maxs; + hdr->count = 0; + + for( int i = 0; i < grassInfo.Count(); i++ ) + { + grassentry_t *entry = &grassInfo[i]; + + if( !Mod_CheckLayerNameForSurf( surf, entry->name )) + continue; + + // create a single mesh for all the bushes that have same texture + if( !R_BuildGrassMesh( surf, es, entry, hdr, &hdr->g[hdr->count] )) + continue; // failed to build for some reasons + + hdr->count++; + } + + ClearBits( surf->flags, SURF_GRASS_UPDATE ); + + // bah! failed to create + if( !hdr->count ) + { + Mem_Free( es->grass ); + es->grasscount = 0; + es->grass = NULL; + } + + // restore normal randomization + RANDOM_SEED( 0 ); +} + +void R_RemoveGrassForSurface( mextrasurf_t *es ) +{ + ClearBits( es->surf->flags, SURF_GRASS_UPDATE ); + + // not specified? + if( !es->grass ) return; + + grasshdr_t *hdr = es->grass; + + for( int i = 0; i < hdr->count; i++ ) + R_DeleteSurfaceVBO( &hdr->g[i] ); + + es->grass = NULL; + Mem_Free( hdr ); +} + +void R_DrawGrassMeshFromBuffer( const grass_t *mesh ) +{ + pglBindVertexArray( mesh->vao ); + + if( GL_Support( R_DRAW_RANGEELEMENTS_EXT )) + pglDrawRangeElementsEXT( GL_TRIANGLES, 0, mesh->numVerts - 1, mesh->numElems, GL_UNSIGNED_SHORT, 0 ); + else pglDrawElements( GL_TRIANGLES, mesh->numElems, GL_UNSIGNED_SHORT, 0 ); + + r_stats.c_total_tris += (mesh->numVerts / 2); + r_stats.num_flushes++; +} + +/* +================ +R_SetSurfaceUniforms + +================ +*/ +void R_SetGrassUniforms( word hProgram, grass_t *grass ) +{ + Vector lightdir; + float *v; + + if( RI->currentshader != &glsl_programs[hProgram] ) + GL_BindShader( &glsl_programs[hProgram] ); + + glsl_program_t *shader = RI->currentshader; + gl_state_t *glm = GL_GetCache( grass->hCachedMatrix ); + CDynLight *pl = RI->currentlight; // may be NULL + + // sometime we can't set the uniforms + if( !shader || !shader->numUniforms || !shader->uniforms ) + return; + + // setup specified uniforms (and texture bindings) + for( int i = 0; i < shader->numUniforms; i++ ) + { + uniform_t *u = &shader->uniforms[i]; + + switch( u->type ) + { + case UT_COLORMAP: + u->SetValue( grasstexs[grass->texture].gl_texturenum ); + break; + case UT_PROJECTMAP: + if( pl && pl->type == LIGHT_SPOT ) + u->SetValue( pl->spotlightTexture ); + else u->SetValue( tr.whiteTexture ); + break; + case UT_SHADOWMAP: + case UT_SHADOWMAP0: + if( pl ) u->SetValue( pl->shadowTexture[0] ); + else u->SetValue( tr.depthTexture ); + break; + case UT_SHADOWMAP1: + if( pl ) u->SetValue( pl->shadowTexture[1] ); + else u->SetValue( tr.depthTexture ); + break; + case UT_SHADOWMAP2: + if( pl ) u->SetValue( pl->shadowTexture[2] ); + else u->SetValue( tr.depthTexture ); + break; + case UT_SHADOWMAP3: + if( pl ) u->SetValue( pl->shadowTexture[3] ); + else u->SetValue( tr.depthTexture ); + break; + case UT_LIGHTMAP: + case UT_DELUXEMAP: + case UT_DECALMAP: + // unacceptable for grass + u->SetValue( tr.whiteTexture ); + break; + case UT_SCREENMAP: + u->SetValue( tr.screen_color ); + break; + case UT_DEPTHMAP: + u->SetValue( tr.screen_depth ); + break; + case UT_FITNORMALMAP: + u->SetValue( tr.normalsFitting ); + break; + case UT_MODELMATRIX: + u->SetValue( &glm->modelMatrix[0] ); + break; + case UT_SCREENSIZEINV: + u->SetValue( 1.0f / (float)glState.width, 1.0f / (float)glState.height ); + break; + case UT_ZFAR: + u->SetValue( -tr.farclip * 1.74f ); + break; + case UT_LIGHTSTYLEVALUES: + u->SetValue( &tr.lightstyle[0], MAX_LIGHTSTYLES ); + break; + case UT_REALTIME: + u->SetValue( (float)tr.time ); + break; + case UT_FOGPARAMS: + u->SetValue( tr.fogColor[0], tr.fogColor[1], tr.fogColor[2], tr.fogDensity ); + break; + case UT_SHADOWPARMS: + if( pl != NULL ) + { + float shadowWidth = 1.0f / (float)RENDER_GET_PARM( PARM_TEX_WIDTH, pl->shadowTexture[0] ); + float shadowHeight = 1.0f / (float)RENDER_GET_PARM( PARM_TEX_HEIGHT, pl->shadowTexture[0] ); + // depth scale and bias and shadowmap resolution + u->SetValue( shadowWidth, shadowHeight, -pl->projectionMatrix[2][2], pl->projectionMatrix[3][2] ); + } + else u->SetValue( 0.0f, 0.0f, 0.0f, 0.0f ); + break; + case UT_VIEWORIGIN: + u->SetValue( tr.cached_vieworigin.x, tr.cached_vieworigin.y, tr.cached_vieworigin.z ); + break; + case UT_SHADOWMATRIX: + if( pl ) u->SetValue( &pl->gl_shadowMatrix[0][0], MAX_SHADOWMAPS ); + break; + case UT_SHADOWSPLITDIST: + v = RI->view.parallelSplitDistances; + u->SetValue( v[0], v[1], v[2], v[3] ); + break; + case UT_TEXELSIZE: + u->SetValue( 1.0f / (float)sunSize[0], 1.0f / (float)sunSize[1], 1.0f / (float)sunSize[2], 1.0f / (float)sunSize[3] ); + break; + case UT_GAMMATABLE: + u->SetValue( &tr.gamma_table[0][0], 64 ); + break; + case UT_LIGHTDIR: + if( pl ) + { + if( pl->type == LIGHT_DIRECTIONAL ) lightdir = -tr.sky_normal; + else lightdir = pl->frustum.GetPlane( FRUSTUM_FAR )->normal; + u->SetValue( lightdir.x, lightdir.y, lightdir.z, pl->fov ); + } + break; + case UT_LIGHTDIFFUSE: + if( pl ) u->SetValue( pl->color.x, pl->color.y, pl->color.z ); + break; + case UT_LIGHTORIGIN: + if( pl ) u->SetValue( pl->origin.x, pl->origin.y, pl->origin.z, ( 1.0f / pl->radius )); + break; + case UT_LIGHTVIEWPROJMATRIX: + if( pl ) + { + GLfloat gl_lightViewProjMatrix[16]; + pl->lightviewProjMatrix.CopyToArray( gl_lightViewProjMatrix ); + u->SetValue( &gl_lightViewProjMatrix[0] ); + } + break; + case UT_DIFFUSEFACTOR: + u->SetValue( tr.diffuseFactor ); + break; + case UT_AMBIENTFACTOR: + if( pl && pl->type == LIGHT_DIRECTIONAL ) + u->SetValue( tr.sun_ambient ); + else u->SetValue( tr.ambientFactor ); + break; + case UT_SUNREFRACT: + u->SetValue( tr.sun_refract ); + break; + case UT_LIGHTNUMS0: + u->SetValue( (float)grass->lights[0], (float)grass->lights[1], (float)grass->lights[2], (float)grass->lights[3] ); + break; + case UT_LIGHTNUMS1: + u->SetValue( (float)grass->lights[4], (float)grass->lights[5], (float)grass->lights[6], (float)grass->lights[7] ); + break; + case UT_GRASSPARAMS: + u->SetValue( m_flGrassFadeStart, m_flGrassFadeDist, m_flGrassFadeEnd ); + break; + default: + ALERT( at_error, "%s: unhandled uniform %s\n", RI->currentshader->name, u->name ); + break; + } + } +} + +/* +================= +R_SortGrassMeshes + +sort by texture +================= +*/ +static int R_SortGrassMeshes( struct grass_s *const *pa, struct grass_s *const *pb ) +{ + grass_t *a = (grass_t *)*pa; + grass_t *b = (grass_t *)*pb; + + if( a->texture > b->texture ) + return -1; + if( a->texture < b->texture ) + return 1; + + return 0; +} + +/* +================ +R_RenderGrassOnList + +rendering the grass +================ +*/ +void R_RenderGrassOnList( void ) +{ + word hCachedMatrix = -1; + word hLastShader = -1; + word hCurrentShader; + byte hLastTexture = -1; + byte cached_lights[MAXDYNLIGHTS] = { 255 }; + bool update_params = false; + int parms_updated = 0; + + if( !RI->frame.grass_list.Count()) + return; // don't waste time + + if( !FBitSet( RI->params, RP_DEFERREDLIGHT )) + GL_AlphaTest( GL_TRUE ); + else GL_AlphaTest( GL_FALSE ); + pglAlphaFunc( GL_GREATER, r_grass_alpha->value ); + GL_Cull( GL_NONE ); // grass is double-sided poly + GL_DepthMask( GL_TRUE ); + GL_Blend( GL_FALSE ); + + // sorting list to reduce shader switches + if( !CVAR_TO_BOOL( cv_nosort )) + RI->frame.grass_list.Sort( R_SortGrassMeshes ); + + for( int i = 0; i < RI->frame.grass_list.Count(); i++ ) + { + grass_t *g = RI->frame.grass_list[i]; + + if( FBitSet( RI->params, RP_DEFERREDSCENE )) + hCurrentShader = g->deferredScene.GetHandle(); + else if( FBitSet( RI->params, RP_DEFERREDLIGHT )) + hCurrentShader = g->deferredLight.GetHandle(); + else hCurrentShader = g->forwardScene.GetHandle(); + + if( !hCurrentShader ) continue; + + if( FBitSet( RI->params, RP_DEFERREDSCENE|RP_DEFERREDLIGHT ) && memcmp( g->lights, cached_lights, MAXDYNLIGHTS )) + { + memcpy( cached_lights, g->lights, MAXDYNLIGHTS ); + update_params = true; + } + + if( hCachedMatrix != g->hCachedMatrix ) + { + hCachedMatrix = g->hCachedMatrix; + update_params = true; + } + + if( FBitSet( g->flags, FRGASS_SKYENTITY )) + { + GL_DepthRange( 0.8f, 0.9f ); + GL_ClipPlane( false ); + } + else + { + GL_DepthRange( gldepthmin, gldepthmax ); + GL_ClipPlane( true ); + } + + if( hLastShader != hCurrentShader ) + { + hLastShader = hCurrentShader; + update_params = true; + } + + if( hLastTexture != g->texture ) + { + hLastTexture = g->texture; + update_params = true; + } + + if( update_params ) + { + R_SetGrassUniforms( hCurrentShader, g ); + update_params = false; + } + + R_DrawGrassMeshFromBuffer( g ); + } + + // restore old binding + GL_DepthRange( gldepthmin, gldepthmax ); + GL_ClipPlane( true ); + GL_Cull( GL_FRONT ); +} + +/* +================ +R_DrawLightForGrass + +draw lighting for grass +================ +*/ +void R_DrawLightForGrass( CDynLight *pl ) +{ + word hCachedMatrix = -1; + word hLastShader = -1; + byte hLastTexture = -1; + bool update_params = false; + word hLightShader; + + if( !RI->frame.light_grass.Count()) + return; // don't waste time + + GL_AlphaTest( GL_TRUE ); + pglAlphaFunc( GL_GREATER, r_grass_alpha->value ); + GL_Cull( GL_NONE ); + + // sorting list to reduce shader switches + if( !CVAR_TO_BOOL( cv_nosort )) + RI->frame.light_grass.Sort( R_SortGrassMeshes ); + + for( int i = 0; i < RI->frame.light_grass.Count(); i++ ) + { + grass_t *g = RI->frame.light_grass[i]; + + switch( pl->type ) + { + case LIGHT_SPOT: + hLightShader = g->forwardLightSpot.GetHandle(); + break; + case LIGHT_OMNI: + hLightShader = g->forwardLightOmni.GetHandle(); + break; + case LIGHT_DIRECTIONAL: + hLightShader = g->forwardLightProj.GetHandle(); + break; + default: + hLightShader = 0; + break; + } + + if( !hLightShader ) continue; + + if( hCachedMatrix != g->hCachedMatrix ) + { + hCachedMatrix = g->hCachedMatrix; + update_params = true; + } + + if( hLastShader != hLightShader ) + { + hLastShader = hLightShader; + update_params = true; + } + + if( hLastTexture != g->texture ) + { + hLastTexture = g->texture; + update_params = true; + } + + if( update_params ) + { + R_SetGrassUniforms( hLightShader, g ); + update_params = false; + } + + R_DrawGrassMeshFromBuffer( g ); + } + + GL_Cull( GL_FRONT ); +} + +/* +================ +R_RenderShadowGrassOnList + +depth pass +================ +*/ +void R_RenderShadowGrassOnList( void ) +{ + word hCachedMatrix = -1; + word hLastShader = -1; + byte hLastTexture = -1; + bool update_params = false; + + if( !RI->frame.grass_list.Count()) + return; // don't waste time + + GL_AlphaTest( GL_TRUE ); + pglAlphaFunc( GL_GREATER, r_grass_alpha->value ); + GL_Cull( GL_NONE ); // grass is double-sided poly + + // sorting list to reduce shader switches + if( !CVAR_TO_BOOL( cv_nosort )) + RI->frame.grass_list.Sort( R_SortGrassMeshes ); + + for( int i = 0; i < RI->frame.grass_list.Count(); i++ ) + { + grass_t *g = RI->frame.grass_list[i]; + + if( hCachedMatrix != g->hCachedMatrix ) + { + hCachedMatrix = g->hCachedMatrix; + update_params = true; + } + + if( hLastShader != g->forwardDepth.GetHandle() ) + { + hLastShader = g->forwardDepth.GetHandle(); + update_params = true; + } + + if( hLastTexture != g->texture ) + { + hLastTexture = g->texture; + update_params = true; + } + + if( update_params ) + { + R_SetGrassUniforms( g->forwardDepth.GetHandle(), g ); + update_params = false; + } + + R_DrawGrassMeshFromBuffer( g ); + } + + // restore old binding + GL_Cull( GL_FRONT ); +} + +/* +================ +R_GrassTextureForName + +find or add unique texture for grass +================ +*/ +byte R_GrassTextureForName( const char *name ) +{ + for( byte i = 0; i < GRASS_TEXTURES && grasstexs[i].name[0]; i++ ) + { + if( !Q_stricmp( grasstexs[i].name, name )) + return i; // found + } + + // allocate a new one + Q_strncpy( grasstexs[i].name, name, sizeof( grasstexs[i].name )); + grasstexs[i].gl_texturenum = LOAD_TEXTURE( name, NULL, 0, TF_GRASS ); + + if( !grasstexs[i].gl_texturenum ) + { + ALERT( at_warning, "couldn't load %s\n", name ); + grasstexs[i].gl_texturenum = tr.defaultTexture; + } + + return i; +} + +/* +================ +R_GrassInitForSurface + +lookup all the descriptions +because this system allow many +sorts of grass per one surface +================ +*/ +void R_GrassInitForSurface( msurface_t *surf ) +{ + if( !surf || !surf->texinfo || !surf->texinfo->texture ) + return; // some bad polygons + + mextrasurf_t *es = surf->info; + bvert_t *v0, *v1, *v2; + + for( int i = 0; i < grassInfo.Count(); i++ ) + { + grassentry_t *entry = &grassInfo[i]; + + if( !Mod_CheckLayerNameForSurf( surf, entry->name )) + continue; + + if( !FBitSet( surf->flags, SURF_PLANEBACK ) && surf->plane->normal.z < 0.40f ) + continue; // vertical too much + + if( FBitSet( surf->flags, SURF_PLANEBACK ) && surf->plane->normal.z > -0.40f ) + continue; // vertical too much + + // turn the face into a bunch of polygons, and compute the area of each + v0 = &world->vertexes[es->firstvertex]; + int count = 0; + + for( int j = 1; j < es->numverts - 1; j++ ) + { + v1 = &world->vertexes[es->firstvertex+j]; + v2 = &world->vertexes[es->firstvertex+j+1]; + + // compute two triangle edges + Vector e1 = v1->vertex - v0->vertex; + Vector e2 = v2->vertex - v0->vertex; + + // compute the triangle area + Vector areaVec = CrossProduct( e1, e2 ); + float normalLength = areaVec.Length(); + float area = 0.5f * normalLength; + + // compute the number of samples to take + count += area * entry->density * DENSITY_FACTOR; + } + + if( count ) es->grasscount++; // mesh added + + if( es->grasscount > 0 ) // at least one bush was detected + SetBits( world->features, WORLD_HAS_GRASS ); + } +} + +/* +================ +R_GrassSetupFrame + +clear the chains +================ +*/ +void R_GrassSetupFrame( void ) +{ + m_flGrassFadeStart = r_grass_fade_start->value; + + if( m_flGrassFadeStart < GRASS_ANIM_DIST ) + m_flGrassFadeStart = GRASS_ANIM_DIST; + m_flGrassFadeDist = max( 0.0f, r_grass_fade_dist->value ); + m_flGrassFadeEnd = m_flGrassFadeStart + m_flGrassFadeDist; +} + +/* +================ +R_GrassShaderSceneForward + +compute albedo with static lighting +================ +*/ +static word R_GrassShaderSceneForward( msurface_t *s, grass_t *g ) +{ + char glname[64]; + char options[MAX_OPTIONS_LENGTH]; + mextrasurf_t *es = s->info; + + if( g->forwardScene.IsValid( )) + return g->forwardScene.GetHandle(); // valid + + ASSERT( RI->currententity != NULL ); + + Q_strncpy( glname, "forward/scene_grass", sizeof( glname )); + memset( options, 0, sizeof( options )); + + material_t *mat = R_TextureAnimation( s )->material; + + if( FBitSet( mat->flags, BRUSH_FULLBRIGHT ) || R_FullBright( )) + { + GL_AddShaderDirective( options, "LIGHTING_FULLBRIGHT" ); + } + else + { + if( r_lightmap->value > 0.0f && r_lightmap->value <= 2.0f ) + { + if( r_lightmap->value == 1.0f && worldmodel->lightdata ) + GL_AddShaderDirective( options, "LIGHTMAP_DEBUG" ); + else if( r_lightmap->value == 2.0f && FBitSet( world->features, WORLD_HAS_DELUXEMAP )) + GL_AddShaderDirective( options, "LIGHTVEC_DEBUG" ); + } + + if( es->normals != NULL && r_grass->value > 1.0f ) + GL_AddShaderDirective( options, "HAS_DELUXEMAP" ); + + // process lightstyles + for( int i = 0; i < MAXLIGHTMAPS && s->styles[i] != LS_NONE; i++ ) + GL_AddShaderDirective( options, va( "APPLY_STYLE%i", i )); + } + + if( RI->currententity->curstate.renderfx != SKYBOX_ENTITY ) + GL_AddShaderDirective( options, "APPLY_FADE_DIST" ); + + if( tr.fogEnabled ) + GL_AddShaderDirective( options, "APPLY_FOG_EXP" ); + + word shaderNum = GL_FindUberShader( glname, options ); + if( !shaderNum ) + { + SetBits( g->flags, FGRASS_NODRAW ); + return 0; // something bad happens + } + + g->forwardScene.SetShader( shaderNum ); + ClearBits( g->flags, FGRASS_NODRAW ); + + return shaderNum; +} + +/* +================ +R_GrassShaderLightForward + +compute dynamic lighting +================ +*/ +static word R_GrassShaderLightForward( CDynLight *dl, grass_t *g ) +{ + char glname[64]; + char options[MAX_OPTIONS_LENGTH]; + + switch( dl->type ) + { + case LIGHT_SPOT: + if( g->forwardLightSpot.IsValid( )) + return g->forwardLightSpot.GetHandle(); // valid + break; + case LIGHT_OMNI: + if( g->forwardLightOmni.IsValid( )) + return g->forwardLightOmni.GetHandle(); // valid + break; + case LIGHT_DIRECTIONAL: + if( g->forwardLightProj.IsValid( )) + return g->forwardLightProj.GetHandle(); // valid + break; + } + + Q_strncpy( glname, "forward/light_grass", sizeof( glname )); + memset( options, 0, sizeof( options )); + + switch( dl->type ) + { + case LIGHT_SPOT: + GL_AddShaderDirective( options, "LIGHT_SPOT" ); + break; + case LIGHT_OMNI: + GL_AddShaderDirective( options, "LIGHT_OMNI" ); + break; + case LIGHT_DIRECTIONAL: + GL_AddShaderDirective( options, "LIGHT_PROJ" ); + break; + } + + if( RI->currententity->curstate.renderfx != SKYBOX_ENTITY ) + GL_AddShaderDirective( options, "APPLY_FADE_DIST" ); + + if( CVAR_TO_BOOL( r_shadows ) && !FBitSet( dl->flags, DLF_NOSHADOWS )) + { + // shadow cubemaps only support if GL_EXT_gpu_shader4 is support + if( dl->type == LIGHT_DIRECTIONAL && CVAR_TO_BOOL( r_sunshadows )) + GL_AddShaderDirective( options, "APPLY_SHADOW" ); + else if( dl->type == LIGHT_SPOT || GL_Support( R_EXT_GPU_SHADER4 )) + GL_AddShaderDirective( options, "APPLY_SHADOW" ); + } + + word shaderNum = GL_FindUberShader( glname, options ); + + if( !shaderNum ) + { + if( dl->type == LIGHT_DIRECTIONAL ) + SetBits( g->flags, FGRASS_NOSUNLIGHT ); + else SetBits( g->flags, FGRASS_NODLIGHT ); + return 0; // something bad happens + } + + // done + switch( dl->type ) + { + case LIGHT_SPOT: + g->forwardLightSpot.SetShader( shaderNum ); + ClearBits( g->flags, FGRASS_NODLIGHT ); + break; + case LIGHT_OMNI: + g->forwardLightOmni.SetShader( shaderNum ); + ClearBits( g->flags, FGRASS_NODLIGHT ); + break; + case LIGHT_DIRECTIONAL: + g->forwardLightProj.SetShader( shaderNum ); + ClearBits( g->flags, FGRASS_NOSUNLIGHT ); + break; + } + + return shaderNum; +} + +/* +================ +R_GrassShaderSceneDeferred + +grass scene deferred +================ +*/ +word R_GrassShaderSceneDeferred( msurface_t *s, grass_t *g ) +{ + char glname[64]; + char options[MAX_OPTIONS_LENGTH]; + mextrasurf_t *es = s->info; + + if( g->deferredScene.IsValid( )) + return g->deferredScene.GetHandle(); // valid + + Q_strncpy( glname, "deferred/scene_grass", sizeof( glname )); + memset( options, 0, sizeof( options )); + + material_t *mat = s->texinfo->texture->material; + + if( RI->currententity->curstate.renderfx != SKYBOX_ENTITY ) + GL_AddShaderDirective( options, "APPLY_FADE_DIST" ); + + word shaderNum = GL_FindUberShader( glname, options ); + if( !shaderNum ) + { + SetBits( g->flags, FGRASS_NODRAW ); + return 0; // something bad happens + } + + ClearBits( g->flags, FGRASS_NODRAW ); + g->deferredScene.SetShader( shaderNum ); + + return shaderNum; +} + +/* +================ +R_GrassShaderLightDeferred + +grass light deferred +================ +*/ +word R_GrassShaderLightDeferred( msurface_t *s, grass_t *g ) +{ + char glname[64]; + char options[MAX_OPTIONS_LENGTH]; + mextrasurf_t *es = s->info; + + if( g->deferredLight.IsValid( )) + return g->deferredLight.GetHandle(); // valid + + Q_strncpy( glname, "deferred/light_grass", sizeof( glname )); + memset( options, 0, sizeof( options )); + + material_t *mat = s->texinfo->texture->material; + + if( RI->currententity->curstate.renderfx != SKYBOX_ENTITY ) + GL_AddShaderDirective( options, "APPLY_FADE_DIST" ); + + word shaderNum = GL_FindUberShader( glname, options ); + if( !shaderNum ) + { + SetBits( g->flags, FGRASS_NODRAW ); + return 0; // something bad happens + } + + g->deferredLight.SetShader( shaderNum ); + ClearBits( g->flags, FGRASS_NODRAW ); + + return shaderNum; +} + +/* +================ +R_GrassShaderSceneDepth + +return grass depth-shader +================ +*/ +static word R_GrassShaderSceneDepth( msurface_t *s, grass_t *g ) +{ + char glname[64]; + char options[MAX_OPTIONS_LENGTH]; + mextrasurf_t *es = s->info; + + if( g->forwardDepth.IsValid( )) + return g->forwardDepth.GetHandle(); // valid + + ASSERT( RI->currententity != NULL ); + + Q_strncpy( glname, "forward/depth_grass", sizeof( glname )); + memset( options, 0, sizeof( options )); + + if( RI->currententity->curstate.renderfx != SKYBOX_ENTITY ) + GL_AddShaderDirective( options, "APPLY_FADE_DIST" ); + + word shaderNum = GL_FindUberShader( glname, options ); + if( !shaderNum ) return 0; // something bad happens + + g->forwardDepth.SetShader( shaderNum ); + + return shaderNum; +} + +/* +================ +R_PrecacheGrass + +precache grass in the world +================ +*/ +void R_PrecacheGrass( msurface_t *s, mextraleaf_t *leaf ) +{ + mextrasurf_t *es = s->info; + grasshdr_t *hdr = es->grass; + cl_entity_t *e = GET_ENTITY( 0 ); + + if( !es->grasscount ) return; // no grass for this face + + if( hdr ) + { + if( FBitSet( s->flags, SURF_GRASS_UPDATE )) + { + // rebuild mesh with new gamma + R_RemoveGrassForSurface( es ); + } + else + { + // already created? + return; + } + } + + float curdist = VectorDistance( tr.cached_vieworigin, es->origin ); + + // but grass that attached to sky entities will be ignoring distance + if( curdist > m_flGrassFadeEnd && ( e->curstate.renderfx != SKYBOX_ENTITY )) + return; // too far + + // initialize grass for surface + R_ConstructGrassForSurface( s, es ); + hdr = es->grass; // refresh the pointer + + if( hdr && leaf ) + { + // prevent to expand leafs too much + AddPointToBounds( hdr->mins, leaf->mins, leaf->maxs, LEAF_MAX_EXPAND ); + AddPointToBounds( hdr->maxs, leaf->mins, leaf->maxs, LEAF_MAX_EXPAND ); + } +} + +/* +================ +R_AddGrassToDrawList + +build the visible grass list +================ +*/ +void R_AddGrassToDrawList( msurface_t *s, drawlist_t drawlist_type ) +{ + if( !FBitSet( world->features, WORLD_HAS_GRASS )) + return; // completely has no grass + + if( !CVAR_TO_BOOL( r_grass ) || FBitSet( RI->params, RP_NOGRASS )) + return; // disabled by request + + if( drawlist_type == DRAWLIST_SHADOW && !CVAR_TO_BOOL( r_grass_shadows )) + return;// don't cast shadows from grass + + if( drawlist_type == DRAWLIST_LIGHT && !CVAR_TO_BOOL( r_grass_lighting )) + return;// don't cast shadows from grass + + mextrasurf_t *es = s->info; + cl_entity_t *e = es->parent; + Vector absmin, absmax; + + // do simple culling by distance + if( es->grasscount <= 0 ) return; // surface doesn't contain grass + float curdist = VectorDistance( es->origin, tr.cached_vieworigin ); + + // but grass that attached to sky entities will be ignoring distance + if( curdist > m_flGrassFadeEnd && ( e->curstate.renderfx != SKYBOX_ENTITY )) + return; // too far + + // rebuild mesh with new gamma + if( es->grass && FBitSet( s->flags, SURF_GRASS_UPDATE )) + R_RemoveGrassForSurface( es ); + + if( !es->grass && RP_NORMALPASS( )) + R_ConstructGrassForSurface( s, es ); + + grasshdr_t *hdr = es->grass; + if( !hdr ) return; // face completely missed grass or creation was failed + + gl_state_t *glm = GL_GetCache( e->hCachedMatrix ); + + if( e->hCachedMatrix == WORLD_MATRIX ) + { + // default case + absmin = hdr->mins; + absmax = hdr->maxs; + } + else + { + // moving entity + TransformAABB( glm->transform, hdr->mins, hdr->maxs, absmin, absmax ); + } + + CFrustum *frustum = NULL; + + if( RI->currentlight != NULL ) + frustum = &RI->currentlight->frustum; + else frustum = &RI->view.frustum; + + if( frustum && frustum->CullBox( absmin, absmax )) + return; + + // NOTE: at this point we have surface that passed visibility and frustum tests + + // each mesh should be added individually + for( int i = 0; i < hdr->count; i++ ) + { + grass_t *grass = &hdr->g[i]; + + if( e->curstate.renderfx == SKYBOX_ENTITY ) + SetBits( grass->flags, FRGASS_SKYENTITY ); + else ClearBits( grass->flags, FRGASS_SKYENTITY ); + grass->hCachedMatrix = e->hCachedMatrix; + + switch( drawlist_type ) + { + case DRAWLIST_SOLID: + if( FBitSet( grass->flags, FGRASS_NODRAW )) + continue; + if( FBitSet( RI->params, RP_DEFERREDSCENE|RP_DEFERREDLIGHT )) + { + // precache shaders + R_GrassShaderSceneDeferred( s, grass ); + R_GrassShaderLightDeferred( s, grass ); + } + else R_GrassShaderSceneForward( s, grass ); + RI->frame.grass_list.AddToTail( grass ); + break; + case DRAWLIST_SHADOW: + if( FBitSet( grass->flags, FGRASS_NODRAW )) + continue; + R_GrassShaderSceneDepth( s, grass ); + RI->frame.grass_list.AddToTail( grass ); + break; + case DRAWLIST_LIGHT: + if( RI->currentlight->type == LIGHT_DIRECTIONAL ) + { + if( FBitSet( grass->flags, FGRASS_NOSUNLIGHT )) + continue; + } + else + { + if( FBitSet( grass->flags, FGRASS_NODLIGHT )) + continue; + } + R_GrassShaderLightForward( RI->currentlight, grass ); + RI->frame.light_grass.AddToTail( grass ); + break; + default: // invalid mode + return; + } + } +} + +/* +================ +R_UnloadFarGrass + +release far VBO's +================ +*/ +void R_UnloadFarGrass( void ) +{ + if( !FBitSet( world->features, WORLD_HAS_GRASS )) + return; // don't waste time + + if( ++tr.grassunloadframe < 300 ) + return; // run every three seconds + + // check surfaces + for( int i = 0; i < worldmodel->numsurfaces; i++ ) + { + msurface_t *surf = &worldmodel->surfaces[i]; + mextrasurf_t *es = surf->info; + + if( !es->grasscount ) continue; // surface doesn't contain grass + float curdist = VectorDistance( tr.cached_vieworigin, es->origin ); + + if( curdist > m_flGrassFadeEnd ) + { + if( curdist > ( m_flGrassFadeEnd * 2.0f ) && es->grass ) + R_RemoveGrassForSurface( es ); + } + } + + tr.grassunloadframe = 0; +} + +/* +================ +R_GrassInit + +parse grass definitions and load textures +================ +*/ +void R_GrassInit( void ) +{ + static int random_seed = 1; // starts from 1 + + char *afile = (char *)gEngfuncs.COM_LoadFile( "gfx/grass/grassinfo.txt", 5, NULL ); + if( !afile ) ALERT( at_error, "couldn't load grassinfo.txt\n" ); + + // remove grass description from the pervious map + grassInfo.Purge(); + + memset( grasstexs, 0, sizeof( grasstexs )); + + if( afile ) + { + grassentry_t entry; + char *pfile = afile; + int parse_line = 1; + char token[1024]; + + while(( pfile = COM_ParseFile( pfile, token )) != NULL ) + { + Q_strncpy( entry.name, token, sizeof( entry.name )); + + pfile = COM_ParseLine( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "R_GrassInit: missed grass texture path at line %i\n", parse_line ); + parse_line++; + continue; + } + + entry.texture = R_GrassTextureForName( token ); + + pfile = COM_ParseLine( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "R_GrassInit: missed grass density at line %i\n", parse_line ); + parse_line++; + continue; + } + entry.density = Q_atof( token ); + entry.density = bound( 0.1f, entry.density, 512.0f ); + + pfile = COM_ParseLine( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "R_GrassInit: missed grass min scale at line %i\n", parse_line ); + parse_line++; + continue; + } + entry.min = Q_atof( token ); + entry.min = bound( 0.01f, entry.min, 100.0f ); + + pfile = COM_ParseLine( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "R_GrassInit: missed grass max scale at line %i\n", parse_line ); + parse_line++; + continue; + } + entry.max = Q_atof( token ); + entry.max = bound( entry.min, entry.max, 100.0f ); + if( entry.min > entry.max ) entry.min = entry.max; + + pfile = COM_ParseLine( pfile, token ); + if( pfile ) + { + // seed is optional + entry.seed = Q_atoi( token ); + entry.seed = max( 1, entry.seed ); + } + else entry.seed = random_seed++; + + grassInfo.AddToTail( entry ); + parse_line++; + } + + gEngfuncs.COM_FreeFile( afile ); + } +} + +/* +================ +R_GrassShutdown + +prepare grass system to shutdown +================ +*/ +void R_GrassShutdown( void ) +{ + // release all grass textures + for( int i = 0; i < GRASS_TEXTURES; i++ ) + { + if( !grasstexs[i].gl_texturenum ) + continue; + + FREE_TEXTURE( grasstexs[i].gl_texturenum ); + } +} \ No newline at end of file diff --git a/cl_dll/render/gl_grass.h b/cl_dll/render/gl_grass.h new file mode 100644 index 0000000..8d66e0c --- /dev/null +++ b/cl_dll/render/gl_grass.h @@ -0,0 +1,116 @@ +/* +gl_grass.h - grass construct & rendering +this code written for Paranoia 2: Savior modification +Copyright (C) 2014 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef GL_GRASS_H +#define GL_GRASS_H + +#define GRASS_TEXTURES 256 // unique textures for grass (a byte limit, don't change) +#define GRASS_ANIM_DIST 512.0f // if this is changed it must be changed in glsl too! +#define MAX_GRASS_ELEMS 65536 // unsigned short limit +#define MAX_GRASS_VERTS ( MAX_GRASS_ELEMS / 2 ) // ( numelems / 1.5 ) actually +#define MAX_GRASS_BUSHES ( MAX_GRASS_VERTS / 16 ) // one bush contain 4 poly, so we have 2048 bushes max per one surface +#define GRASS_SKY_DIST BOGUS_RANGE // in-world grass never reach this value + +typedef struct grassentry_s +{ + char name[16]; // name of level texture + byte texture; // number in array of grass textures + float density; // grass density (0 - 100) + float min; // min grass scale + float max; // max grass scale + int seed; // seed for predictable random (auto-filled) +} grassentry_t; + +typedef struct grasstexture_s +{ + char name[256]; // path to grass texture + int gl_texturenum; // gl-texture +} grasstexture_t; + +typedef struct gvert_s +{ + float center[4]; // used for rescale + float normal[4]; // center + vertex[2] * vertex[3]; + float light[MAXLIGHTMAPS]; // packed color + unused entry + float delux[MAXLIGHTMAPS]; // packed lightdir + unused entry + byte styles[MAXLIGHTMAPS]; // styles on surface +} gvert_t; + +#define FGRASS_NODRAW BIT( 0 ) // grass shader is failed to build +#define FGRASS_NODLIGHT BIT( 1 ) // grass dlight shader is failed to build +#define FGRASS_NOSUNLIGHT BIT( 2 ) // grass dlight shader is failed to build +#define FRGASS_SKYENTITY BIT( 3 ) // it's sky grass + +// all the grassdata for one polygon and specified texture +// stored into single vbo +typedef struct grass_s +{ + // shader cache + shader_t forwardScene; + shader_t forwardLightSpot; + shader_t forwardLightOmni; + shader_t forwardLightProj; + shader_t deferredScene; + shader_t deferredLight; + shader_t forwardDepth; + + byte texture; // not a real texture just index into array + byte flags; // state flags + unsigned short numVerts; // for glDrawRangeElementsEXT + unsigned short numElems; // for glDrawElements + unsigned int vbo, vao, ibo; // buffer objects + unsigned short hCachedMatrix; // HACKHACK: get matrices + byte lights[MAXDYNLIGHTS];/// light numbers + unsigned int cacheSize; // debug info: uploaded cache size for this buffer +} grass_t; + +typedef void (*pfnCreateGrassBuffer)( grass_t *pOut, gvert_t *arrayxvert ); +typedef void (*pfnBindGrassBuffer)( grass_t *pOut, int attrFlags ); + +enum +{ + GRASSLOADER_BASE = 0, + GRASSLOADER_BASEBUMP, + GRASSLOADER_COUNT, +}; + +typedef struct +{ + pfnCreateGrassBuffer CreateBuffer; + pfnBindGrassBuffer BindBuffer; + const char* BufferName; // debug +} grass_loader_t; + +typedef struct grasshdr_s +{ + Vector mins, maxs; // per-poly culling + int count; // total bush count for this poly + grass_t g[1]; // variable sized +} grasshdr_t; + +extern void R_GrassInit( void ); +extern void R_GrassShutdown( void ); +extern void R_GrassInitForSurface( msurface_t *surf ); +extern void R_RenderGrassOnList( void ); +extern void R_GrassSetupFrame( void ); +extern void R_RenderShadowGrassOnList( void ); +extern void R_DrawLightForGrass( CDynLight *pl ); +extern void R_AddGrassToDrawList( msurface_t *s, drawlist_t type ); +extern void R_PrecacheGrass( msurface_t *s, mextraleaf_t *leaf ); +extern void R_RemoveGrassForSurface( mextrasurf_t *es ); +extern void R_UnloadFarGrass( void ); + +#endif//GL_GRASS_H \ No newline at end of file diff --git a/cl_dll/render/gl_lightmap.cpp b/cl_dll/render/gl_lightmap.cpp new file mode 100644 index 0000000..c7e53ed --- /dev/null +++ b/cl_dll/render/gl_lightmap.cpp @@ -0,0 +1,757 @@ +/* +gl_lightmap.cpp - generate lightmaps and uploading them +Copyright (C) 2016 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "studio.h" +#include "com_model.h" +#include "ref_params.h" +#include "gl_local.h" +#include +#include "gl_shader.h" +#include "gl_world.h" + +/* +============================================================================= + + LIGHTMAP ALLOCATION + +============================================================================= +*/ +static int LM_AllocBlock( unsigned short w, unsigned short h, unsigned short *x, unsigned short *y ) +{ + gl_lightmap_t *lms = &tr.lightmaps[tr.current_lightmap_texture]; + unsigned short i, j, best, best2; + + best = BLOCK_SIZE; + + for( i = 0; i < BLOCK_SIZE - w; i++ ) + { + best2 = 0; + + for( j = 0; j < w; j++ ) + { + if( lms->allocated[i+j] >= best ) + break; + if( lms->allocated[i+j] > best2 ) + best2 = lms->allocated[i+j]; + } + + if( j == w ) + { + // this is a valid spot + *x = i; + *y = best = best2; + } + } + + if( best + h > BLOCK_SIZE ) + { + // current lightmap is full + lms->state = LM_DONE; + return false; + } + + for( i = 0; i < w; i++ ) + lms->allocated[*x + i] = best + h; + lms->state = LM_USED; // lightmap in use + + return true; +} + +static void LM_InitBlock( void ) +{ + word dummy; + gl_lightmap_t *lms = &tr.lightmaps[tr.current_lightmap_texture]; + memset( lms->allocated, 0, sizeof( lms->allocated )); + + // first block at pos 0,0 used as black lightmap for studiomodel + LM_AllocBlock( 1, 1, &dummy, &dummy ); +} + +static void LM_UploadPages( bool lightmap, bool deluxmap ) +{ + char lmName[16]; + byte lightBuf[4]; + byte deluxBuf[4]; + int i; + + lightBuf[0] = 0; + lightBuf[1] = 0; + lightBuf[2] = 0; + lightBuf[3] = 0; + deluxBuf[0] = 127; + deluxBuf[1] = 127; + deluxBuf[2] = 255; + deluxBuf[3] = 0; + + for( i = 0; i < MAX_LIGHTMAPS && tr.lightmaps[i].state != LM_FREE; i++ ) + { + gl_lightmap_t *lms = &tr.lightmaps[i]; + + if( lightmap && !lms->lightmap ) + { + Q_snprintf( lmName, sizeof( lmName ), "*diffuse%i", i ); + lms->lightmap = CREATE_TEXTURE( lmName, BLOCK_SIZE, BLOCK_SIZE, NULL, TF_LIGHTMAP ); + + // also loading dummy blackpixel + GL_BindTexture( GL_TEXTURE0, lms->lightmap ); + pglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, lightBuf ); + } + + if( deluxmap && !lms->deluxmap ) + { + Q_snprintf( lmName, sizeof( lmName ), "*normals%i", i ); + lms->deluxmap = CREATE_TEXTURE( lmName, BLOCK_SIZE, BLOCK_SIZE, NULL, TF_DELUXMAP ); + + // also loading dummy blackpixel + GL_BindTexture( GL_TEXTURE0, lms->deluxmap ); + pglTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, deluxBuf ); + } + } +} + +static void LM_GoToNextPage( void ) +{ + gl_lightmap_t *lms = &tr.lightmaps[tr.current_lightmap_texture]; + + if( lms->state != LM_DONE ) return; // current atlas not completed + + if( ++tr.current_lightmap_texture == MAX_LIGHTMAPS ) + HOST_ERROR( "MAX_LIGHTMAPS limit exceded\n" ); +} + +/* +================== +GL_BeginBuildingLightmaps + +================== +*/ +void GL_BeginBuildingLightmaps( void ) +{ + int i; + + // release old lightmaps first + for( i = 0; i < MAX_LIGHTMAPS && tr.lightmaps[i].state != LM_FREE; i++ ) + { + FREE_TEXTURE( tr.lightmaps[i].lightmap ); + FREE_TEXTURE( tr.lightmaps[i].deluxmap ); + } + + memset( tr.lightmaps, 0, sizeof( tr.lightmaps )); + tr.current_lightmap_texture = 0; + LM_InitBlock(); +} + +/* +================= +Mod_AllocLightmapForFace + +NOTE: we don't loading lightmap here. +just create lmcoords and set lmnum +================= +*/ +void GL_AllocLightmapForFace( msurface_t *surf ) +{ + mextrasurf_t *esrf = surf->info; + word smax, tmax; + int map; + + // always reject the tiled faces + if( FBitSet( surf->flags, SURF_DRAWSKY )) + return; + + // no lightdata and no deluxdata + if( !surf->samples && !esrf->normals ) + return; + + int sample_size = Mod_SampleSizeForFace( surf ); + smax = ( surf->info->lightextents[0] / sample_size ) + 1; + tmax = ( surf->info->lightextents[1] / sample_size ) + 1; +new_page: + // alloc blocks for all the styles on current page + for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != LS_NONE; map++ ) + { + if( !LM_AllocBlock( smax, tmax, &esrf->light_s[map], &esrf->light_t[map] )) + { + // current page is not enough room for next 1-4 blocks + LM_GoToNextPage(); + LM_InitBlock(); + goto new_page; + } + } + + // lightmap will be uploaded as far as player can see it + esrf->lightmaptexturenum = tr.current_lightmap_texture; + SetBits( surf->flags, SURF_LM_UPDATE|SURF_DM_UPDATE ); +} + +/* +================= +Mod_AllocLightmapForFace + +NOTE: we don't loading lightmap here. +just create lmcoords and set lmnum +================= +*/ +bool GL_AllocLightmapForFace( mstudiosurface_t *surf ) +{ + word smax, tmax; + int map; + + smax = surf->lightextents[0] + 1; + tmax = surf->lightextents[1] + 1; + + // alloc blocks for all the styles on current page + for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != LS_NONE; map++ ) + { + if( !LM_AllocBlock( smax, tmax, &surf->light_s[map], &surf->light_t[map] )) + { + // current page is not enough room for next 1-4 blocks + LM_GoToNextPage(); + LM_InitBlock(); + return false; + } + } + + // lightmap will be uploaded as far as player can see it + surf->lightmaptexturenum = tr.current_lightmap_texture; + SetBits( surf->flags, SURF_LM_UPDATE|SURF_DM_UPDATE ); + return true; +} + +/* +======================= +GL_EndBuildingLightmaps +======================= +*/ +void GL_EndBuildingLightmaps( bool lightmap, bool deluxmap ) +{ + LM_UploadPages( lightmap, deluxmap ); +} + +/* +================= +R_BuildLightMapForStyle + +write lightmap into page for a given style +================= +*/ +static void R_BuildLightMapForStyle( msurface_t *surf, byte *dest, int style ) +{ + mextrasurf_t *esrf = surf->info; + int stride, size; + int smax, tmax; + int s, t; + color24 *lm; + byte *sm; + + ASSERT( style >= 0 && style < MAXLIGHTMAPS ); + + // always reject the sky faces + if( FBitSet( surf->flags, SURF_DRAWSKY )) + return; + + // no lightdata or style missed + if( !surf->samples || surf->styles[style] == LS_NONE ) + return; + + int sample_size = Mod_SampleSizeForFace( surf ); + smax = ( surf->info->lightextents[0] / sample_size ) + 1; + tmax = ( surf->info->lightextents[1] / sample_size ) + 1; + size = smax * tmax; + + // jump to specified style + lm = surf->samples + size * style; + sm = esrf->shadows + size * style; + + // put into texture format + stride = (smax * 4) - (smax << 2); + + for( t = 0; t < tmax; t++, dest += stride ) + { + for( s = 0; s < smax; s++ ) + { + dest[0] = TEXTURE_TO_TEXGAMMA( lm->r ); + dest[1] = TEXTURE_TO_TEXGAMMA( lm->g ); + dest[2] = TEXTURE_TO_TEXGAMMA( lm->b ); + + if( esrf->shadows != NULL ) + dest[3] = *sm++; + else dest[3] = 255; + + dest += 4; + lm++; + } + } +} + +/* +================= +R_BuildLightMapForStyle + +write lightmap into page for a given style +================= +*/ +static void R_BuildLightMapForStyle( mstudiosurface_t *surf, byte *dest, int style ) +{ + int stride, size; + int smax, tmax; + int s, t; + color24 *lm; + byte *sm; + + ASSERT( style >= 0 && style < MAXLIGHTMAPS ); + + // no lightdata or style missed + if( !surf->samples || surf->styles[style] == LS_NONE ) + return; + + smax = surf->lightextents[0] + 1; + tmax = surf->lightextents[1] + 1; + size = smax * tmax; + + // jump to specified style + lm = surf->samples + size * style; + sm = surf->shadows + size * style; + + // put into texture format + stride = (smax * 4) - (smax << 2); + + for( t = 0; t < tmax; t++, dest += stride ) + { + for( s = 0; s < smax; s++ ) + { + dest[0] = TEXTURE_TO_TEXGAMMA( lm->r ); + dest[1] = TEXTURE_TO_TEXGAMMA( lm->g ); + dest[2] = TEXTURE_TO_TEXGAMMA( lm->b ); + + if( surf->shadows != NULL ) + dest[3] = *sm++; + else dest[3] = 255; + + dest += 4; + lm++; + } + } +} + +/* +================= +R_BuildDeluxMapForStyle + +write deluxmap into page for a given style +================= +*/ +static void R_BuildDeluxMapForStyle( msurface_t *surf, byte *dest, int style ) +{ + mextrasurf_t *esrf = surf->info; + int stride, size; + int smax, tmax; + int s, t; + color24 *dm; + + ASSERT( style >= 0 && style < MAXLIGHTMAPS ); + + // always reject the sky faces + if( FBitSet( surf->flags, SURF_DRAWSKY )) + return; + + // no lightdata or style missed + if( !esrf->normals || surf->styles[style] == LS_NONE ) + return; + + int sample_size = Mod_SampleSizeForFace( surf ); + smax = ( surf->info->lightextents[0] / sample_size ) + 1; + tmax = ( surf->info->lightextents[1] / sample_size ) + 1; + size = smax * tmax; + + // jump to specified style + dm = esrf->normals + size * style; + + // put into texture format + stride = (smax * 4) - (smax << 2); + + for( t = 0; t < tmax; t++, dest += stride ) + { + for( s = 0; s < smax; s++ ) + { + dest[0] = dm->r; + dest[1] = dm->g; + dest[2] = dm->b; + dest[3] = 255; + + dest += 4; + dm++; + } + } +} + +/* +================= +R_BuildDeluxMapForStyle + +write deluxmap into page for a given style +================= +*/ +static void R_BuildDeluxMapForStyle( mstudiosurface_t *surf, byte *dest, int style ) +{ + int stride, size; + int smax, tmax; + int s, t; + color24 *dm; + + ASSERT( style >= 0 && style < MAXLIGHTMAPS ); + + // no lightdata or style missed + if( !surf->normals || surf->styles[style] == LS_NONE ) + return; + + smax = surf->lightextents[0] + 1; + tmax = surf->lightextents[1] + 1; + size = smax * tmax; + + // jump to specified style + dm = surf->normals + size * style; + + // put into texture format + stride = (smax * 4) - (smax << 2); + + for( t = 0; t < tmax; t++, dest += stride ) + { + for( s = 0; s < smax; s++ ) + { + dest[0] = dm->r; + dest[1] = dm->g; + dest[2] = dm->b; + dest[3] = 255; + + dest += 4; + dm++; + } + } +} + +/* +================= +R_UpdateLightMap + +Combine and scale multiple lightmaps into the floating +format in r_blocklights +================= +*/ +static void R_UpdateLightMap( msurface_t *surf ) +{ + mextrasurf_t *esrf = surf->info; + static byte buf[132*132*4]; + int map, smax, tmax; + + // always reject the sky faces + if( FBitSet( surf->flags, SURF_DRAWSKY )) + return; + + int sample_size = Mod_SampleSizeForFace( surf ); + smax = ( surf->info->lightextents[0] / sample_size ) + 1; + tmax = ( surf->info->lightextents[1] / sample_size ) + 1; + + // upload the lightmap + if( surf->samples != NULL && FBitSet( surf->flags, SURF_LM_UPDATE )) + { + GL_BindTexture( GL_TEXTURE0, tr.lightmaps[esrf->lightmaptexturenum].lightmap ); + + // write lightmaps into page + for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != LS_NONE; map++ ) + { + R_BuildLightMapForStyle( surf, buf, map ); + pglTexSubImage2D( GL_TEXTURE_2D, 0, esrf->light_s[map], esrf->light_t[map], smax, tmax, GL_RGBA, GL_UNSIGNED_BYTE, buf ); + } + } + + ClearBits( surf->flags, SURF_LM_UPDATE ); + + // upload the deluxemap + if( esrf->normals != NULL && FBitSet( surf->flags, SURF_DM_UPDATE )) + { + GL_BindTexture( GL_TEXTURE0, tr.lightmaps[esrf->lightmaptexturenum].deluxmap ); + + // write lightmaps into page + for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != LS_NONE; map++ ) + { + R_BuildDeluxMapForStyle( surf, buf, map ); + pglTexSubImage2D( GL_TEXTURE_2D, 0, esrf->light_s[map], esrf->light_t[map], smax, tmax, GL_RGBA, GL_UNSIGNED_BYTE, buf ); + } + } + + ClearBits( surf->flags, SURF_DM_UPDATE ); +} + +/* +================= +R_UpdateLightMap + +Combine and scale multiple lightmaps into the floating +format in r_blocklights +================= +*/ +static void R_UpdateLightMap( mstudiosurface_t *surf ) +{ + static byte buf[512*512*4]; + int map, smax, tmax; + + smax = surf->lightextents[0] + 1; + tmax = surf->lightextents[1] + 1; + + // upload the lightmap + if( surf->samples != NULL && FBitSet( surf->flags, SURF_LM_UPDATE )) + { + GL_BindTexture( GL_TEXTURE0, tr.lightmaps[surf->lightmaptexturenum].lightmap ); + + // write lightmaps into page + for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != LS_NONE; map++ ) + { + R_BuildLightMapForStyle( surf, buf, map ); + pglTexSubImage2D( GL_TEXTURE_2D, 0, surf->light_s[map], surf->light_t[map], smax, tmax, GL_RGBA, GL_UNSIGNED_BYTE, buf ); + } + } + + ClearBits( surf->flags, SURF_LM_UPDATE ); + + // upload the deluxemap + if( surf->normals != NULL && FBitSet( surf->flags, SURF_DM_UPDATE )) + { + GL_BindTexture( GL_TEXTURE0, tr.lightmaps[surf->lightmaptexturenum].deluxmap ); + + // write lightmaps into page + for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != LS_NONE; map++ ) + { + R_BuildDeluxMapForStyle( surf, buf, map ); + pglTexSubImage2D( GL_TEXTURE_2D, 0, surf->light_s[map], surf->light_t[map], smax, tmax, GL_RGBA, GL_UNSIGNED_BYTE, buf ); + } + } + + ClearBits( surf->flags, SURF_DM_UPDATE ); +} + +/* +======================== +R_TextureCoords + +fill vec2_t with texture coords +======================== +*/ +void R_TextureCoords( msurface_t *surf, const Vector &vec, float *out ) +{ + float s, t; + + s = DotProduct( vec, surf->texinfo->vecs[0] ) + surf->texinfo->vecs[0][3]; + s /= surf->texinfo->texture->width; + + t = DotProduct( vec, surf->texinfo->vecs[1] ) + surf->texinfo->vecs[1][3]; + t /= surf->texinfo->texture->height; + + out[0] = s; + out[1] = t; +} + +/* +======================== +R_GlobalCoords + +fill vec2_t with global coords +======================== +*/ +void R_GlobalCoords( msurface_t *surf, const Vector &point, float *out ) +{ + mfaceinfo_t *land = surf->texinfo->faceinfo; + terrain_t *terra; + Vector size; + indexMap_t *im; + + if( !land ) return; + + terra = land->terrain; + if( !terra ) return; + + im = &terra->indexmap; + + for( int i = 0; i < 3; i++ ) + size[i] = land->maxs[i] - land->mins[i]; + + out[2] = ( point[0] - land->mins[0] ) / size[0]; + out[3] = ( land->maxs[1] - point[1] ) / size[1]; +} + +/* +======================== +R_GlobalCoords + +fill vec2_t with global coords +======================== +*/ +void R_GlobalCoords( msurface_t *surf, const Vector &point, const Vector &absmin, const Vector &absmax, float scale, float *out ) +{ + Vector size; + + for( int i = 0; i < 3; i++ ) + size[i] = absmax[i] - absmin[i]; + + out[2] = (( point[0] - absmin[0] ) / size[0]) * scale; + out[3] = (( absmax[1] - point[1] ) / size[1]) * scale; +} + +/* +======================== +R_LightmapCoords + +fill Vector4D with lightstyle coords (two styles per array) +======================== +*/ +void R_LightmapCoords( msurface_t *surf, const Vector &vec, float *coords, int style ) +{ + mextrasurf_t *esrf = surf->info; + float sample_size = Mod_SampleSizeForFace( surf ); + float s, t; + + for( int i = 0; i < 2; i++ ) + { + if( surf->styles[style+i] == LS_NONE ) + return; // end of styles + + s = DotProduct( vec, surf->info->lmvecs[0] ) + surf->info->lmvecs[0][3]; + s -= surf->info->lightmapmins[0]; + s += esrf->light_s[style+i] * sample_size; + s += sample_size * 0.5f; + s /= BLOCK_SIZE * sample_size; + + t = DotProduct( vec, surf->info->lmvecs[1] ) + surf->info->lmvecs[1][3]; + t -= surf->info->lightmapmins[1]; + t += esrf->light_t[style+i] * sample_size; + t += sample_size * 0.5f; + t /= BLOCK_SIZE * sample_size; + + coords[i*2+0] = s; + coords[i*2+1] = t; + } +} + +/* +======================== +R_LightmapCoords + +fill Vector4D with lightstyle coords (two styles per array) +======================== +*/ +void R_LightmapCoords( mstudiosurface_t *surf, const Vector &vec, const Vector lmvecs[2], float *coords, int style ) +{ + float s, t; + + for( int i = 0; i < 2; i++ ) + { + if( surf->styles[style+i] == LS_NONE ) + return; // end of styles + + s = DotProduct( vec, lmvecs[0] ) + surf->light_s[style+i] + 0.5f; + t = DotProduct( vec, lmvecs[1] ) + surf->light_t[style+i] + 0.5f; + s /= (float)BLOCK_SIZE; + t /= (float)BLOCK_SIZE; + + coords[i*2+0] = s; + coords[i*2+1] = t; + } +} + +/* +================= +R_UpdateSurfaceParams + +update some surface params +if this was changed +================= +*/ +void R_UpdateSurfaceParams( msurface_t *surf ) +{ + mextrasurf_t *esrf = surf->info; + cl_entity_t *e = RI->currententity; + model_t *clmodel = e->model; + + // check for lightmap modification + if( FBitSet( surf->flags, SURF_LM_UPDATE|SURF_DM_UPDATE )) + R_UpdateLightMap( surf ); + + if( FBitSet( surf->flags, SURF_MOVIE )) + R_UpdateCinematic( surf ); + + // handle conveyor movement + if( FBitSet( clmodel->flags, BIT( 0 )) && FBitSet( surf->flags, SURF_CONVEYOR )) + { + float flRate, flAngle; + float flWidth, flConveyorSpeed; + float sOffset, sy; + float tOffset, cy; + + flConveyorSpeed = (e->curstate.rendercolor.g<<8|e->curstate.rendercolor.b) / 16.0f; + if( e->curstate.rendercolor.r ) flConveyorSpeed = -flConveyorSpeed; + flWidth = (float)RENDER_GET_PARM( PARM_TEX_SRC_WIDTH, surf->texinfo->texture->gl_texturenum ); + + if( flWidth != 0.0f ) + { + flRate = abs( flConveyorSpeed ) / flWidth; + flAngle = ( flConveyorSpeed >= 0.0f ) ? 180.0f : 0.0f; + + SinCos( DEG2RAD( flAngle ), &sy, &cy ); + sOffset = tr.time * cy * flRate; + tOffset = tr.time * sy * flRate; + + // make sure that we are positive + if( sOffset < 0.0f ) sOffset += 1.0f + -(int)sOffset; + if( tOffset < 0.0f ) tOffset += 1.0f + -(int)tOffset; + + // make sure that we are in a [0,1] range + sOffset = sOffset - (int)sOffset; + tOffset = tOffset - (int)tOffset; + + esrf->texofs[0] = sOffset; + esrf->texofs[1] = tOffset; + } + else + { + // no conveyor + esrf->texofs[0] = 0.0f; + esrf->texofs[1] = 0.0f; + } + } + else + { + // no conveyor + esrf->texofs[0] = 0.0f; + esrf->texofs[1] = 0.0f; + } +} + +/* +================= +R_UpdateSurfaceParams + +update some surface params +if this was changed +================= +*/ +void R_UpdateSurfaceParams( mstudiosurface_t *surf ) +{ + // check for lightmap modification + if( FBitSet( surf->flags, SURF_LM_UPDATE|SURF_DM_UPDATE )) + R_UpdateLightMap( surf ); +} \ No newline at end of file diff --git a/cl_dll/render/gl_local.h b/cl_dll/render/gl_local.h new file mode 100644 index 0000000..9f0bf8d --- /dev/null +++ b/cl_dll/render/gl_local.h @@ -0,0 +1,998 @@ +/* +gl_local.h - renderer local definitions +this code written for Paranoia 2: Savior modification +Copyright (C) 2013 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef GL_LOCAL_H +#define GL_LOCAL_H + +#include "gl_export.h" +#include "ref_params.h" +#include "com_model.h" +#include "r_studioint.h" +#include "gl_framebuffer.h" +#include "gl_frustum.h" +#include "gl_primitive.h" +#include "cl_dlight.h" +#include "features.h" +#include +#include + +#define ACTUAL_GL_VERSION 30.0f + +// limits +#define MAX_REF_STACK 8 // pass depth +#define MAX_VISIBLE_ENTS 4096 // total pack of frame ents +#define MAX_SORTED_FACES 32768 // bmodels only +#define MAX_SUBVIEW_FACES 1024 // mirrors, portals, monitors, water, puddles. NOTE: multipass faces can merge view passes +#define MAX_OCCLUDED_FACES 1024 // mirrors + water +#define MAX_SORTED_MESHES 2048 // studio only +#define MAX_MOVIES 16 // max various movies per level +#define MAX_MOVIE_TEXTURES 64 // max # of unique video textures per level +#define MAX_LIGHTSTYLES 64 // a byte limit, don't modify +#define MAX_LIGHTMAPS 256 // Xash3D supports up to 256 lightmaps +#define MAX_DLIGHTS 64 // per one frame. unsigned int limit +#define MAX_ENGINE_DLIGHTS 32 +#define MAX_LIGHTCACHE 2048 // unique models with instanced vertex lighting +#define MAX_SHADOWS MAX_DLIGHTS +#define MAX_SUBVIEW_TEXTURES 64 // total depth +#define MAX_FRAMEBUFFERS MAX_SUBVIEW_TEXTURES +#define MAX_FBO_ATTACHMENTS 8 // color attachments per FBO +#define DEFAULT_CUBEMAP_SIZE 32 // same as in Source +#define AMBIENT_EPSILON 0.001f // to avoid division by zero +#define STAIR_INTERP_TIME 100.0f +#define LIGHT_PROBES 10 // eight OBB corners, center and one reserved slot (NOTE: not all the probes will be used) +#define LIGHT_SAMPLES 8 // GPU limitation for local arrays (very slowly if more than eight elements) +#define MOD_FRAMES 20 + +#define WATER_TEXTURES 29 +#define WATER_ANIMTIME 20.0f + +#define INVALID_HANDLE 0xFFFF // studio cache + +#define FLASHLIGHT_KEY -666 +#define SUNLIGHT_KEY -777 +#define SKYBOX_ENTITY 70 + +#define LM_SAMPLE_SIZE 16 +#define LM_SAMPLE_EXTRASIZE 8 + +#define BLOCK_SIZE glConfig.block_size // lightmap blocksize +#define BLOCK_SIZE_DEFAULT 128 // for keep backward compatibility +#define BLOCK_SIZE_MAX 2048 // must match with engine const!!! +#define SHADOW_SIZE 4096 // atlas size + +#define WORLD_MATRIX 0 // must be 0 always +#define REFPVS_RADIUS 2.0f // PVS radius for rendering +#define Z_NEAR 4.0f +#define Z_NEAR_LIGHT 0.1f +#define BACKFACE_EPSILON 0.01f + +#define CVAR_TO_BOOL( x ) ((x) && ((x)->value != 0.0f) ? true : false ) + +// VBO offsets +#define OFFSET( type, var ) ((const void *)&(((type *)NULL)->var)) +#define R_OpaqueEntity( e ) (( (e)->curstate.rendermode == kRenderNormal ) || ( (e)->curstate.rendermode == kRenderTransAlpha )) +#define R_ModelOpaque( rm ) (( rm == kRenderNormal ) || ( rm == kRenderTransAlpha )) +#define R_StaticEntity( e ) ( (e)->origin == g_vecZero && (e)->angles == g_vecZero && (e)->curstate.renderfx != SKYBOX_ENTITY ) +#define R_FullBright() ( CVAR_TO_BOOL( r_fullbright ) || !worldmodel->lightdata ) +#define RP_OUTSIDE( leaf ) (((( leaf ) - worldmodel->leafs ) - 1 ) == -1 ) +#define R_WaterEntity( m ) ( FBitSet( m->flags, BIT( 2 ))) + +#define ScreenCopyRequired( x ) ((x) && FBitSet( (x)->status, SHADER_USE_SCREENCOPY )) +#define IsReflectShader( x ) ((x) && FBitSet( (x)->status, SHADER_USE_CUBEMAPS )) + +// refparams +#define RP_NONE 0 +#define RP_THIRDPERSON BIT( 0 ) +#define RP_DRAW_WORLD BIT( 1 ) // otherwise it's player customization window +#define RP_DRAW_OVERVIEW BIT( 2 ) // dev_overview is active +#define RP_CLIPPLANE BIT( 3 ) // mirrors used +#define RP_MERGE_PVS BIT( 4 ) // merge PVS with previous pass +#define RP_MIRRORVIEW BIT( 5 ) // lock pvs at vieworg +#define RP_ENVVIEW BIT( 6 ) // used for cubemapshot +#define RP_SHADOWVIEW BIT( 7 ) // view through light +#define RP_NOSHADOWS BIT( 8 ) // disable shadows for this pass +#define RP_SKYVIEW BIT( 9 ) // render skyonly +#define RP_WATERPASS BIT( 10 ) // it's mirorring plane for water surface +#define RP_NOGRASS BIT( 11 ) // don't draw grass +#define RP_DEFERREDSCENE BIT( 12 ) // render scene into geometry buffer +#define RP_DEFERREDLIGHT BIT( 13 ) // render scene into low-res lightmap + +// RI->view.changed +#define RC_ORIGIN_CHANGED BIT( 0 ) // origin is changed from the previous frame +#define RC_ANGLES_CHANGED BIT( 1 ) // angles is changed from the previous frame +#define RC_VIEWLEAF_CHANGED BIT( 2 ) // viewleaf is changed +#define RC_FOV_CHANGED BIT( 3 ) // FOV is changed +#define RC_PVS_CHANGED BIT( 4 ) // now our PVS was potentially changed :-) +#define RC_FRUSTUM_CHANGED BIT( 5 ) // now our frustum was potentially changed :-) +#define RC_FORCE_UPDATE BIT( 6 ) // some cvar manipulations invoke updates of visible list + +// RI->view.flags +#define RF_SKYVISIBLE BIT( 0 ) // sky is visible for this frame +#define RF_HASDYNLIGHTS BIT( 1 ) // pass have dynlights + +#define RP_NONVIEWERREF (RP_MIRRORVIEW|RP_ENVVIEW|RP_SHADOWVIEW|RP_SKYVIEW) +#define RP_LOCALCLIENT( e ) (gEngfuncs.GetLocalPlayer() && ((e)->index == gEngfuncs.GetLocalPlayer()->index && e->curstate.entityType == ET_PLAYER )) +#define RP_NORMALPASS() ( FBitSet( RI->params, RP_NONVIEWERREF ) == 0 ) +#define RP_CUBEPASS() ( FBitSet( RI->params, RP_SKYVIEW|RP_ENVVIEW )) + +#define TF_LIGHTMAP (TF_NOMIPMAP|TF_CLAMP|TF_ATLAS_PAGE|TF_HAS_ALPHA) +#define TF_DELUXMAP (TF_CLAMP|TF_NOMIPMAP|TF_NORMALMAP|TF_ATLAS_PAGE) +#define TF_IMAGE (TF_NOMIPMAP|TF_CLAMP) +#define TF_SCREEN (TF_NOMIPMAP|TF_CLAMP) +#define TF_SPOTLIGHT (TF_NOMIPMAP|TF_BORDER) +#define TF_SHADOW (TF_NOMIPMAP|TF_CLAMP|TF_DEPTHMAP|TF_LUMINANCE) +#define TF_SHADOW_CUBEMAP (TF_NOMIPMAP|TF_CLAMP|TF_CUBEMAP|TF_DEPTHMAP|TF_LUMINANCE) +#define TF_RECTANGLE_SCREEN (TF_RECTANGLE|TF_NOMIPMAP|TF_CLAMP) +#define TF_DEPTH (TF_NOMIPMAP|TF_CLAMP|TF_NEAREST|TF_DEPTHMAP|TF_LUMINANCE|TF_NOCOMPARE) +#define TF_GRASS (TF_CLAMP) +#define TF_DEPTHBUFF (TF_DEPTHMAP|TF_LUMINANCE|TF_NOCOMPARE) +#define TF_STORAGE (TF_NEAREST|TF_RECTANGLE|TF_ARB_FLOAT|TF_HAS_ALPHA|TF_CLAMP|TF_NOMIPMAP) + +#define TF_DEPTHBUFFER (TF_SCREEN|TF_DEPTHMAP|TF_NEAREST|TF_NOCOMPARE) +#define TF_COLORBUFFER (TF_SCREEN|TF_NEAREST) + +#define CULL_VISIBLE 0 // not culled +#define CULL_BACKSIDE 1 // backside of transparent wall +#define CULL_FRUSTUM 2 // culled by frustum +#define CULL_OTHER 3 // culled by other reason + +#define TF_RT_COLOR (TF_NEAREST|TF_CLAMP|TF_NOMIPMAP) +#define TF_RT_NORMAL (TF_NEAREST|TF_CLAMP|TF_NOMIPMAP|TF_NORMALMAP) +#define TF_RT_DEPTH (TF_NEAREST|TF_CLAMP|TF_NOMIPMAP|TF_DEPTHMAP|TF_NOCOMPARE) + +#define MAT_ALL_EFFECTS (BRUSH_HAS_BUMP|BRUSH_HAS_SPECULAR|BRUSH_REFLECT|BRUSH_FULLBRIGHT) + +#define FBO_MAIN 0 + +// light types +#define LIGHT_SPOT 0 // standard projection light +#define LIGHT_OMNI 1 // omnidirectional light +#define LIGHT_DIRECTIONAL 2 // parallel light (sun light) + +// helpers +#define GetVForward() Vector( RI->view.matrix[0] ) +#define GetVRight() Vector( RI->view.matrix[1] ) +#define GetVLeft() -Vector(RI->view.matrix[1] ) +#define GetVUp() Vector( RI->view.matrix[2] ) +#define GetVieworg() RI->view.origin + +typedef enum +{ + DRAWLIST_ALL = 0, // special case for decals + DRAWLIST_SOLID, + DRAWLIST_TRANS, + DRAWLIST_LIGHT, + DRAWLIST_SUBVIEW, + DRAWLIST_SHADOW, +} drawlist_t; + +enum +{ + BUMP_BASELIGHT_STYLE = 61, + BUMP_ADDLIGHT_STYLE = 62, + BUMP_LIGHTVECS_STYLE = 63, +}; + +class DecalGroupEntry; +typedef int (*cmpfunc)( const void *a, const void *b ); +typedef void (*pfnShaderCallback)( struct glsl_prog_s *shader ); + +// multiple output draw buffers +typedef struct +{ + char name[32]; + int width; + int height; + int depth; + int colortarget[MAX_FBO_ATTACHMENTS]; + int depthtarget; + uint id; +} gl_drawbuffer_t; + +typedef struct gl_fbo_s +{ + GLboolean init; + GLuint framebuffer; + GLuint renderbuffer; + int texture; +} gl_fbo_t; + +typedef struct gl_movie_s +{ + char name[32]; + void *state; + float length; // total cinematic length + long xres, yres; // size of cinematic +} gl_movie_t; + +typedef struct gl_texbuffer_s +{ + int framebuffer; + int texturenum; + int texframe; // this frame texture was used + matrix4x4 matrix; // texture matrix +} gl_texbuffer_t; + +class gl_state_t +{ +public: + GLfloat modelMatrix[16]; // matrix4x4( origin, angles, scale ) + matrix4x4 transform; // entity transformation matrix + bool m_bSkyEntity; + const Vector GetModelOrigin( void ); +}; + +typedef enum +{ + LM_FREE = 0, // lightmap is clear + LM_USED, // partially used, has free space + LM_DONE, // completely full +} lmstate_t; + +typedef struct +{ + lmstate_t state; + unsigned short allocated[BLOCK_SIZE_MAX]; + int lightmap; + int deluxmap; +} gl_lightmap_t; + +typedef struct +{ + unsigned short allocated[SHADOW_SIZE]; + CFrameBuffer shadowmap; +} gl_shadowmap_t; + +typedef struct +{ + Vector diffuse; // direct light color + Vector normal; // direct light normal + int ambientlight; // clip at 128 + int shadelight; // clip at 192 - ambientlight + Vector ambient[6]; // cubemap 1x1 (single pixel per side) + bool nointerp; // flickering light force nointerp +} mstudiolight_t; + +// NOTE: if this is changed it must be changed in studio.h and com_model.h too!!! +typedef struct +{ + float smoothness; // smoothness factor + float detailScale[2]; // detail texture scales x, y + float reflectScale; // reflection scale for translucent water + float refractScale; // refraction scale for mirrors, windows, water + float aberrationScale; // chromatic abberation + float reliefScale; // relief-mapping + struct matdef_s *effects; // hit, impact, particle effects etc + + char name[64]; + unsigned short dt_texturenum; // detail texture load directly from material specific + + char diffusemap[64]; + char normalmap[64]; + char glossmap[64]; +} matdesc_t; + +typedef struct +{ + char name[64]; + char diffuse[64]; + unsigned short gl_diffuse_id; // diffuse texture + unsigned short gl_heightmap_id; + unsigned short width, height; + byte maxHeight; + byte numLayers; // layers that specified on heightmap + byte *pixels; // pixels are immediately goes here +} indexMap_t; + +typedef struct +{ + char pathes[MAX_LANDSCAPE_LAYERS][64]; // path to texture (may include extension etc) + char names[MAX_LANDSCAPE_LAYERS][64]; // basenames + matdesc_t *material[MAX_LANDSCAPE_LAYERS]; // layer settings + float smoothness[MAX_LANDSCAPE_LAYERS]; // shader params + unsigned short gl_diffuse_id; // diffuse texture array + unsigned short gl_detail_id; // detail texture + unsigned short gl_normalmap_id; // normalmap array + unsigned short gl_specular_id; // specular array +} layerMap_t; + +typedef struct terrain_s +{ + char name[16]; + indexMap_t indexmap; + layerMap_t layermap; + int numLayers; // count of array textures + int tessSize; + float texScale; // global texture scale + bool valid; // if heightmap was actual +} terrain_t; + +typedef struct +{ + int changed; // whats changed from last frame + int flags; // some info about current frame (sets by renderer) + + int entity; // playernum or camera edict + Vector origin; + Vector angles; + Vector pvspoint; + ref_overview_t over; // cached overview + + int client_frame; // cached client frame + bool novis_cached; // last value of r_novis variable + bool lockpvs_cached; // last value of r_lockpvs variable + + float fov_x; // actual fov_x + float fov_y; // actual fov_y + float farClip; + float planedist; // for sort translucent surfaces + float lodScale; + + int port[4]; // cached view.port + + CFrustum frustum; // view frustum + CFrustum splitFrustum[MAX_SHADOWMAPS]; + float parallelSplitDistances[MAX_SHADOWMAPS]; // distances in camera space + + matrix4x4 matrix; // untransformed viewmatrix + matrix4x4 worldMatrix; // modelview for world + matrix4x4 projectionMatrix; // gl frustum + matrix4x4 worldProjectionMatrix; // worldviewMatrix * projectionMatrix + + mleaf_t *leaf; // leaf where are vieworg located + + vec3_t visMins; // visMins used for compute precision farclip + vec3_t visMaxs; + + float skyMins[2][6]; // sky texcoords + float skyMaxs[2][6]; // sky texcoords + + byte pvsarray[(MAX_MAP_LEAFS+7)/8]; // actual PVS for current frame + byte visfaces[(MAX_MAP_FACES+7)/8]; // actual visible faces for current frame (world only) + byte vislight[(MAX_MAP_WORLDLIGHTS+7)/8]; // visible lights per current frame +} ref_viewcache_t; + +typedef struct +{ + // forward rendering lists + CUtlArray solid_faces; + CUtlArray solid_meshes; + CUtlArray trans_list; + CUtlArray grass_list; + CUtlArray primverts; // primitive vertexes + + // forward lighting lists + CUtlArray light_faces; + CUtlArray light_meshes; + CUtlArray light_grass; + + msurface_t *subview_faces[MAX_SUBVIEW_FACES]; // 6 kb + int num_subview_faces; +} ref_drawlist_t; + +// contain gl-friendly data that may keep from previous frame +// to avoid to recompute it again +typedef struct +{ + int viewport[4]; // in OpenGL space + + GLfloat modelviewMatrix[16]; // worldviewMatrix + GLfloat projectionMatrix[16]; // projection matrix + GLfloat modelviewProjectionMatrix[16];// send to engine +} ref_glstate_t; + +typedef struct +{ + int params; // rendering parameters + + // NEW STATE + ref_viewcache_t view; // cached view + ref_glstate_t glstate; // cached glstate + ref_drawlist_t frame; // frame lists + + // GLOBAL STATE + mplane_t clipPlane; + cl_entity_t *currententity; + model_t *currentmodel; + CDynLight *currentlight; + struct glsl_prog_s *currentshader; + msurface_t *reject_face; // avoid recursion to himself +} ref_instance_t; + +/* +======================================================================= + + GL STATE MACHINE + +======================================================================= +*/ +enum +{ + R_OPENGL_110 = 0, // base + R_WGL_PROCADDRESS, + R_ARB_VERTEX_BUFFER_OBJECT_EXT, + R_ARB_VERTEX_ARRAY_OBJECT_EXT, + R_TEXTURE_ARRAY_EXT, // shaders only + R_EXT_GPU_SHADER4, // shaders only + R_DRAW_BUFFERS_EXT, + R_ARB_MULTITEXTURE, + R_TEXTURECUBEMAP_EXT, + R_SHADER_GLSL100_EXT, + R_DRAW_RANGEELEMENTS_EXT, + R_TEXTURE_3D_EXT, + R_SHADER_OBJECTS_EXT, + R_VERTEX_SHADER_EXT, // glsl vertex program + R_FRAGMENT_SHADER_EXT, // glsl fragment program + R_ARB_TEXTURE_NPOT_EXT, + R_TEXTURE_2D_RECT_EXT, + R_DEPTH_TEXTURE, + R_SHADOW_EXT, + R_FRAMEBUFFER_OBJECT, + R_SEPARATE_BLENDFUNC_EXT, + R_OCCLUSION_QUERIES_EXT, + R_SEAMLESS_CUBEMAP, + R_BINARY_SHADER_EXT, + R_PARANOIA_EXT, // custom OpenGL32.dll with hacked function glDepthRange + R_DEBUG_OUTPUT, + R_EXTCOUNT, // must be last +}; + +enum +{ + GL_KEEP_UNIT = -1, // alternative way - change the unit by GL_SelectTexture + GL_TEXTURE0 = 0, + GL_TEXTURE1, + GL_TEXTURE2, + GL_TEXTURE3, + GL_TEXTURE4, + GL_TEXTURE5, + GL_TEXTURE6, + GL_TEXTURE7, + GL_TEXTURE8, + GL_TEXTURE9, + GL_TEXTURE10, + GL_TEXTURE11, + MAX_TEXTURE_UNITS +}; + +typedef struct +{ + bool fCustomRendering; + bool fClearScreen; // force clear if world shaders failed to build + int fGamePaused; + + double time; // cl.time + double oldtime; // cl.oldtime + double frametime; // special frametime for multipass rendering (will set to 0 on a nextview) + double saved_frametime; // push\pop + + cl_entity_t *draw_entities[MAX_VISIBLE_ENTS]; // list of all pending entities for current frame + int num_draw_entities; // count for actual rendering frame + + int defaultTexture; // use for bad textures + int skyboxTextures[6]; // skybox sides + int normalmapTexture; // default normalmap + int deluxemapTexture; // default deluxemap + int vsdctCubeTexture; // Virtual Shadow Depth Cubemap Texture + int whiteCubeTexture; // stub + int depthTexture; // stub + int depthCubemap; // stub + int normalsFitting; // best fit normals + + int defaultProjTexture; // fallback for missed textures + int flashlightTexture; // flashlight projection texture + int spotlightTexture[8];// reserve for eight textures + int cinTextures[MAX_MOVIE_TEXTURES]; + gl_texbuffer_t subviewTextures[MAX_SUBVIEW_TEXTURES]; + int shadowTextures[MAX_SHADOWS]; + int shadowCubemaps[MAX_SHADOWS]; + int waterTextures[WATER_TEXTURES]; + int num_2D_shadows_used; // used shadow textures per full frame + int num_CM_shadows_used; // used shadow textures per full frame + int num_subview_used; // used mirror textures per full frame + int num_cin_used; // used movie textures per full frame + + int screen_color; + int screen_depth; + + int grayTexture; + int whiteTexture; + int blackTexture; + int screenTexture; + + CUtlArray cached_state; + + gl_lightmap_t lightmaps[MAX_LIGHTMAPS]; + byte current_lightmap_texture; + int packed_lights_texture; + int packed_planes_texture; + int packed_nodes_texture; + int packed_models_texture; + + gl_shadowmap_t shadowmap; // single atlas + + // framebuffers + CFrameBuffer fbo_shadow2D; // used for projection shadowmapping + CFrameBuffer fbo_shadowCM; // used for omnidirectional shadowmapping + CFrameBuffer sunShadowFBO[MAX_SHADOWMAPS]; // extra-large shadowmap for sun rendering + CFrameBuffer fbo_light; // store lightmap + CFrameBuffer fbo_filter; // store filtered lightmap + CFrameBuffer fbo_shadow; // store shadowflags + + gl_drawbuffer_t *defscene_fbo; + gl_drawbuffer_t *deflight_fbo; + word defSceneShader[2]; // geometry pass + word defLightShader; // light pass + word defDynLightShader[2];// dynamic light pass + word bilateralShader; // upscale filter + + // skybox shaders + word skyboxEnv[2]; // skybox & sun + word defSceneSky; // skybox & sun + word defLightSky; // skybox & sun + + // framebuffers + gl_fbo_t frame_buffers[MAX_FRAMEBUFFERS]; + int num_framebuffers; + + int realframecount; // not including passes + int grassunloadframe; // unload too far grass to save video memory + + Vector ambientLight; // at vieworg + int waterlevel; // player waterlevel + cl_entity_t *waterentity; // player inside + + // fog params + bool fogEnabled; + Vector fogColor; + float fogDensity; + float fogSkyDensity; + + // sky params + Vector sky_origin; + Vector sky_world_origin; + float sky_speed; + + Vector sky_normal; // sky vector + Vector sky_ambient; // sky ambient color + + // global ambient\direct factors + float ambientFactor; + float diffuseFactor; + + float sun_refract; + float sun_ambient; + Vector sun_diffuse; + + CDynLight *sunlight; // sun is active if not a NULL + + Vector screen_normals[4]; // helper to transform world->screen space + + float farclip; // max viewable distance + float gravity; // particles used + + float lightstyle[MAX_LIGHTSTYLES]; // value 0 - 65536 + gl_movie_t cinematics[MAX_MOVIES]; // precached cinematics + + struct movevars_s *movevars; + + // generic light that used to cache shaders + CDynLight defaultlightSpot; + CDynLight defaultlightOmni; + CDynLight defaultlightProj; + + matdesc_t *materials; + unsigned int matcount; + + + bool params_changed; // some cvars are toggled, shaders needs to recompile and resort + bool local_client_added; // indicate what a local client already been added into renderlist + bool sun_light_enabled; // map have a light_environment with valid direction + bool lighting_changed; // r_lighting_modulate was changed + bool shadows_notsupport; // no shadow textures + bool show_uniforms_peak; // print the maxcount of used uniforms + int glsl_valid_sequence; // reloas shaders while some render cvars was changed + int total_vbo_memory; // statistics + Vector4D gamma_table[64]; + + CDynLight dlights[MAX_DLIGHTS]; + + vec3_t ambient_color; + float direct_scale; + float light_gamma; + float light_threshold; + float smoothing_threshold; + + // original player vieworg and angles + Vector cached_vieworigin; + Vector cached_viewangles; + + struct mvbocache_s *vertex_light_cache[MAX_LIGHTCACHE]; // FIXME: make growable + struct mvbocache_s *surface_light_cache[MAX_LIGHTCACHE]; + + // cull info + Vector modelorg; // relative to viewpoint +} ref_globals_t; + +typedef struct +{ + unsigned int c_world_leafs; + unsigned int c_world_nodes; // walking by BSP tree + + unsigned int c_culled_entities; + unsigned int c_total_tris; // triangle count + + unsigned int c_subview_passes; + unsigned int c_shadow_passes; + + unsigned int c_worldlights; + unsigned int c_occlusion_culled; // culled by occlusion query + + unsigned int c_screen_copy; // how many times screen was copied + + unsigned int num_shader_binds; + unsigned int num_flushes; + + msurface_t *debug_surface; +} ref_stats_t; + +typedef struct +{ + double compile_shader; + double create_light_cache; + double create_buffer_object; + double total_buildtime; +} ref_buildstats_t; + +typedef enum +{ + GLHW_GENERIC, // where everthing works the way it should + GLHW_RADEON, // where you don't have proper GLSL support + GLHW_NVIDIA // Geforce 8/9 class DX10 hardware +} glHWType_t; + +typedef struct +{ + int width, height; + int defWidth, defHeight; + qboolean fullScreen; + qboolean wideScreen; + + int faceCull; + int frontFace; + int frameBuffer; + + GLfloat depthmin; + GLfloat depthmax; + GLint depthmask; + GLfloat identityMatrix[16]; + + ref_instance_t stack[MAX_REF_STACK]; + GLuint stack_position; +} glState_t; + +typedef struct +{ + const char *renderer_string; // ptrs to OpenGL32.dll, use with caution + const char *version_string; + const char *vendor_string; + + glHWType_t hardware_type; + float version; + + // list of supported extensions + const char *extensions_string; + bool extension[R_EXTCOUNT]; + + int block_size; // lightmap blocksize + + int max_texture_units; + GLint max_2d_texture_size; + GLint max_3d_texture_size; + GLint max_2d_texture_layers; + GLint max_cubemap_size; + GLint binary_formats; + GLint num_formats; + + int max_vertex_uniforms; + int max_vertex_attribs; + int max_varying_floats; + int max_skinning_bones; // total bones that can be transformed with GLSL + int peak_used_uniforms; +} glConfig_t; + +extern glState_t glState; +extern glConfig_t glConfig; +extern engine_studio_api_t IEngineStudio; +extern float gldepthmin, gldepthmax; +extern int sunSize[MAX_SHADOWMAPS]; +extern char r_speeds_msg[2048]; +extern char r_depth_msg[2048]; +extern model_t *worldmodel; +extern int g_iGunMode; +extern ref_stats_t r_stats; +extern ref_buildstats_t r_buildstats; +extern ref_instance_t *RI; +extern ref_globals_t tr; + +// +// gl_backend.cpp +// +void R_InitRefState( void ); +void R_PushRefState( void ); +void R_PopRefState( void ); +void R_ResetRefState( void ); +ref_instance_t *R_GetPrevInstance( void ); +void CompressNormalizedVector( char outVec[3], const Vector &inVec ); +bool GL_BackendStartFrame( ref_viewpass_t *rvp, int params ); +void GL_BackendEndFrame( ref_viewpass_t *rvp, int params ); +int R_GetSpriteTexture( const model_t *m_pSpriteModel, int frame ); +void GL_BindDrawbuffer( gl_drawbuffer_t *framebuffer ); +void GL_DepthRange( GLfloat depthmin, GLfloat depthmax ); +void R_RenderQuadPrimitive( CSolidEntry *entry ); +void GL_LoadMatrix( const matrix4x4 &source ); +void GL_LoadTexMatrix( const matrix4x4 &source ); +void GL_BindFrameBuffer( int buffer, int texture ); +void R_Speeds_Printf( const char *msg, ... ); +int R_AllocFrameBuffer( int viewport[4] ); +void GL_CheckVertexArrayBinding( void ); +void R_FreeFrameBuffer( int buffer ); +void GL_CleanupAllTextureUnits( void ); +void GL_ComputeScreenRays( void ); +void GL_DisableAllTexGens( void ); +void GL_DepthMask( GLint enable ); +void GL_FrontFace( GLenum front ); +void GL_ClipPlane( bool enable ); +void GL_BindFBO( GLuint buffer ); +void GL_AlphaTest( GLint enable ); +void GL_DepthTest( GLint enable ); +void GL_CleanupDrawState( void ); +void GL_SetDefaultState( void ); +void GL_Blend( GLint enable ); +void GL_Cull( GLenum cull ); +void GL_Setup2D( void ); +void GL_Setup3D( void ); + +// +// gl_cubemaps.cpp +// +void CL_FindNearestCubeMap( const Vector &pos, mcubemap_t **result ); +void CL_FindTwoNearestCubeMap( const Vector &pos, mcubemap_t **result1, mcubemap_t **result2 ); +void CL_FindNearestCubeMapForSurface( const Vector &pos, const msurface_t *surf, mcubemap_t **result ); +void CL_FindTwoNearestCubeMapForSurface( const Vector &pos, const msurface_t *surf, mcubemap_t **result1, mcubemap_t **result2 ); +void CL_BuildCubemaps_f( void ); + +// +// gl_cull.cpp +// +bool R_CullModel( cl_entity_t *e, const Vector &mins, const Vector &maxs ); +int R_CullSurface( msurface_t *surf, const Vector &vieworg, CFrustum *frustum, int clipFlags = 0 ); +bool R_CullBrushModel( cl_entity_t *e ); +bool R_CullNodeTopView( mnode_t *node ); + +#define R_CullBox( mins, maxs ) ( RI->view.frustum.CullBox( mins, maxs )) +#define R_CullSphere( centre, radius ) ( RI->view.frustum.CullSphere( centre, radius )) +#define R_CullFrustum( otherFrustum ) ( RI->view.frustum.CullFrustum( otherFrustum )) + +// +// gl_debug.cpp +// +void DBG_PrintVertexVBOSizes( void ); +void DBG_DrawLightFrustum( void ); +void DBG_DrawGlassScissors( void ); +void DrawLightProbes( void ); +void R_ShowLightMaps( void ); +void R_RenderLightProbeInternal( const Vector &origin, const Vector lightCube[] ); +void DBG_DrawBBox( const Vector &mins, const Vector &maxs ); +void DrawWirePoly( msurface_t *surf ); +void DrawTangentSpaces( void ); +void DrawWireFrame( void ); +void DrawViewLeaf( void ); +void DrawCubeMaps( void ); + +// +// gl_deferred.cpp +// +void GL_SetupGBuffer( void ); +void GL_ResetGBuffer( void ); +void GL_DrawDeferredPass( void ); + +// +// gl_framebuffer.c +// +gl_drawbuffer_t *GL_AllocDrawbuffer( const char *name, int width, int height, int depth = 1 ); +void GL_ResizeDrawbuffer( gl_drawbuffer_t *fbo, int width, int height, int depth = 1 ); +void GL_AttachColorTextureToFBO( gl_drawbuffer_t *fbo, int textrue, int colorIndex, int side = 0 ); +void GL_AttachDepthTextureToFBO( gl_drawbuffer_t *fbo, int texture, int side = 0 ); +void GL_CheckFBOStatus( gl_drawbuffer_t *fbo ); +void GL_VidInitDrawBuffers( void ); +void GL_FreeDrawbuffers( void ); + +// +// gl_lightmap.cpp +// +void R_UpdateSurfaceParams( msurface_t *surf ); +void R_UpdateSurfaceParams( struct mstudiosurface_s *surf ); +void GL_BeginBuildingLightmaps( void ); +void GL_AllocLightmapForFace( msurface_t *surf ); +bool GL_AllocLightmapForFace( struct mstudiosurface_s *surf ); +void GL_EndBuildingLightmaps( bool lightmap, bool deluxmap ); +void R_TextureCoords( msurface_t *surf, const Vector &vec, float *out ); +void R_GlobalCoords( msurface_t *surf, const Vector &point, float *out ); +void R_GlobalCoords( msurface_t *surf, const Vector &point, const Vector &absmin, const Vector &absmax, float scale, float *out ); +void R_LightmapCoords( msurface_t *surf, const Vector &vec, float *coords, int style ); +void R_LightmapCoords( struct mstudiosurface_s *surf, const Vector &vec, const Vector lmvecs[2], float *coords, int style ); + +// +// gl_rlight.cpp +// +CDynLight *CL_AllocDlight( int key ); +void R_GetLightVectors( cl_entity_t *pEnt, Vector &origin, Vector &angles ); +void R_SetupLightParams( CDynLight *pl, const Vector &origin, const Vector &angles, float radius, float fov, int type, int flags = 0 ); +void R_FindWorldLights( const Vector &origin, const Vector &mins, const Vector &maxs, byte lights[MAXDYNLIGHTS], bool skipZ = false ); +void R_LightForStudio( const Vector &point, mstudiolight_t *light, bool ambient ); +void R_PointAmbientFromLeaf( const Vector &point, mstudiolight_t *light ); +void R_LightForSky( const Vector &point, mstudiolight_t *light ); +void R_LightVec( const Vector &point, mstudiolight_t *light, bool ambient ); +Vector R_LightsForPoint( const Vector &point, float radius ); +void R_SetupLightTexture( CDynLight *pl, int texture ); +void R_SetupDynamicLights( void ); +void CL_ClearDlights( void ); +void R_AnimateLight( void ); +int HasDynamicLights( void ); +int HasStaticLights( void ); +void CL_DecayLights( void ); + +// +// gl_rmain.cpp +// +void R_ClearScene( void ); +int R_ComputeFxBlend( cl_entity_t *e ); +void R_RenderScene( const ref_viewpass_t *rvp, int params ); +qboolean R_AddEntity( struct cl_entity_s *clent, int entityType ); +bool R_WorldToScreen( const Vector &point, Vector &screen ); +void R_ScreenToWorld( const Vector &screen, Vector &point ); +void R_SetupProjectionMatrix( float fov_x, float fov_y, matrix4x4 &m ); +unsigned short GL_CacheState( const Vector &origin, const Vector &angles, bool skyentity = false ); +void R_MarkWorldVisibleFaces( model_t *model ); +gl_state_t *GL_GetCache( word hCachedMatrix ); +void R_DrawParticles( qboolean trans ); +void R_SetupGLstate( void ); +void R_RenderTransList( void ); +void R_SetupFrustum( void ); +void R_Clear( int bitMask ); + +// +// gl_rmisc.cpp +// +void R_NewMap( void ); +void R_VidInit( void ); +void CL_InitMaterials( void ); +matdesc_t *CL_FindMaterial( const char *name ); +void R_LoadLandscapes( const char *filename ); +terrain_t *R_FindTerrain( const char *texname ); +void R_InitDynLightShaders( void ); +void R_InitShadowTextures( void ); +void R_FreeLandscapes( void ); + +// +// gl_rsurf.cpp +// +texture_t *R_TextureAnimation( msurface_t *s ); +void GL_InitRandomTable( void ); + +// +// gl_shader.cpp +// +const char *GL_PretifyListOptions( const char *options, bool newlines = false ); +word GL_FindUberShader( const char *glname, const char *options = "" ); +word GL_FindShader( const char *glname, const char *vpname, const char *fpname, const char *options = "" ); +void GL_SetShaderDirective( char *options, const char *directive ); +void GL_AddShaderDirective( char *options, const char *directive ); +void GL_AddShaderFeature( word shaderNum, int feature ); +void GL_CheckTextureAlpha( char *options, int texturenum ); +void GL_EncodeNormal( char *options, int texturenum ); +void GL_BindShader( struct glsl_prog_s *shader ); +void GL_FreeUberShaders( void ); +void GL_InitGPUShaders( void ); +void GL_FreeGPUShaders( void ); + +// +// gl_shadows.cpp +// +void R_RenderShadowmaps( void ); +void R_RenderDeferredShadows( void ); + +// +// gl_movie.cpp +// +void R_InitCinematics( void ); +void R_FreeCinematics( void ); +int R_PrecacheCinematic( const char *cinname ); +int R_AllocateCinematicTexture( unsigned int txFlags ); +void R_UpdateCinematic( const msurface_t *surf ); +void R_UpdateCinSound( cl_entity_t *e ); + +// +// gl_mirror.cpp +// +void R_RenderSubview( void ); + +// +// gl_scene.cpp +// +void R_CheckChanges( void ); +void R_InitDefaultLights( void ); + +// +// gl_sky.cpp +// +void R_AddSkyBoxSurface( msurface_t *fa ); +void R_DrawSkyBox( void ); + +// +// gl_postprocess.cpp +// +void InitPostTextures( void ); +void InitPostEffects( void ); +void RenderDOF( void ); +void RenderUnderwaterBlur( void ); +void RenderNerveGasBlur( void ); +void RenderMonochrome( void ); +void RenderSunShafts( void ); +void RenderFSQ( int wide, int tall ); + +// +// gl_world_new.cpp +// +void Mod_ThrowModelInstances( void ); +void Mod_PrepareModelInstances( void ); +void GL_LoadAndRebuildCubemaps( int refParams ); +void Mod_SetOrthoBounds( const float *mins, const float *maxs ); +bool Mod_CheckLayerNameForSurf( msurface_t *surf, const char *checkName ); +bool Mod_CheckLayerNameForPixel( mfaceinfo_t *land, const Vector &point, const char *checkName ); +int Mod_FatPVS( model_t *model, const vec3_t org, float radius, byte *visbuffer, int visbytes, bool merge, bool fullvis ); +void Mod_FindStaticLights( byte *vislight, byte lights[MAXDYNLIGHTS], const Vector &origin ); +void R_ProcessWorldData( model_t *mod, qboolean create, const byte *buffer ); +bool R_AddSurfaceToDrawList( msurface_t *surf, drawlist_t type ); +void R_MarkVisibleLights( byte lights[MAXDYNLIGHTS] ); +gl_texbuffer_t *Surf_GetSubview( mextrasurf_t *es ); +void R_RenderTransSurface( CTransEntry *entry ); +int Mod_SampleSizeForFace( msurface_t *surf ); +bool Surf_CheckSubview( mextrasurf_t *es, bool puddle = false ); +void R_RenderDynLightList( bool solid ); +void R_MarkSubmodelVisibleFaces( void ); +void Mod_InitBSPModelsTexture( void ); +void R_UpdateSubmodelParams( void ); +void Mod_ResortFaces( void ); + +// +// gl_world.cpp +// +void R_RenderDeferredBrushList( void ); +void R_RenderSolidBrushList( void ); +void R_RenderShadowBrushList( void ); +void R_RenderSurfOcclusionList( void ); + +// +// rain.cpp +// +void R_DrawWeather( void ); +void ParseRain( void ); +void ResetRain( void ); +void InitRain( void ); + +#endif//GL_LOCAL_H \ No newline at end of file diff --git a/cl_dll/render/gl_material.h b/cl_dll/render/gl_material.h new file mode 100644 index 0000000..33a0f76 --- /dev/null +++ b/cl_dll/render/gl_material.h @@ -0,0 +1,37 @@ +/* +gl_material.h - visible material settings +this code written for Paranoia 2: Savior modification +Copyright (C) 2016 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef GL_MATERIAL_H +#define GL_MATERIAL_H + +typedef enum +{ + PHYSMODEL_BRDF = 0, // BRDF as default + PHYSMODEL_DOOM3, // obsolete lighting model like Doom3 + PHYSMODEL_FOLIAGE, // two-side rendering, vegetation lighting model + PHYSMODEL_GLASS, // translucent surface + PHYSMODEL_SKIN, // human skin lighting model + PHYSMODEL_HAIR, // anisotropic lighting model for hairs + PHYSMODEL_EYES, // eyes physical model +} physmodel_t; + +typedef struct +{ + physmodel_t physModel; + struct matdef_s *effects; // material common effects (decals, particles, etc) +} gl_material_t; + +#endif//GL_MATERIAL_H \ No newline at end of file diff --git a/cl_dll/render/gl_movie.cpp b/cl_dll/render/gl_movie.cpp new file mode 100644 index 0000000..31dc395 --- /dev/null +++ b/cl_dll/render/gl_movie.cpp @@ -0,0 +1,230 @@ +/* +gl_movie.cpp - draw screen movie surfaces +Copyright (C) 2011 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include "cl_util.h" +#include "gl_local.h" +#include "gl_world.h" +#include "mathlib.h" +#include "event_api.h" +#include + +int R_PrecacheCinematic( const char *cinname ) +{ + int load_sound = 0; + + if( !cinname || !*cinname ) + return -1; + + if( *cinname == '*' ) + { + if( g_iXashEngineBuildNumber >= 4256 ) + load_sound = 1; + cinname++; + } + + // not AVI file + if( Q_stricmp( UTIL_FileExtension( cinname ), "avi" )) + return -1; + + // first check for co-existing + for( int i = 0; i < MAX_MOVIES; i++ ) + { + if( !Q_stricmp( tr.cinematics[i].name, cinname )) + { + // already existed + return i; + } + } + + // found an empty slot + for( i = 0; i < MAX_MOVIES; i++ ) + { + if( !tr.cinematics[i].name[0] ) + break; + } + + if( i == MAX_MOVIES ) + { + ALERT( at_error, "R_PrecacheCinematic: cinematic list limit exceeded\n" ); + return -1; + } + + // register new cinematic + Q_strncpy( tr.cinematics[i].name, cinname, sizeof( tr.cinematics[0].name )); + if( tr.cinematics[i].state ) + { + ALERT( at_warning, "Reused cin state %i with %s\n", i, tr.cinematics[i].name ); + FREE_CINEMATIC( tr.cinematics[i].state ); + } + + ALERT( at_console, "Loading cinematic %s [%s]\n", cinname, load_sound ? "sound" : "muted" ); + tr.cinematics[i].state = OPEN_CINEMATIC( tr.cinematics[i].name, load_sound ); + + // grab info about movie + if( tr.cinematics[i].state != NULL ) + CIN_GET_VIDEO_INFO( tr.cinematics[i].state, &tr.cinematics[i].xres, &tr.cinematics[i].yres, &tr.cinematics[i].length ); + + return i; +} + +void R_InitCinematics( void ) +{ + const char *name, *ext; + + // make sure what we have texture to draw cinematics + if( !FBitSet( world->features, WORLD_HAS_MOVIES )) + return; + + for( int i = 1; i < 1024; i++ ) + { + name = gRenderfuncs.GetFileByIndex( i ); + + if( !name || !*name ) break; // end of files array + + ext = UTIL_FileExtension( name ); + if( Q_stricmp( ext, "avi" )) continue; // not AVI + + if( R_PrecacheCinematic( name ) == -1 ) + break; // full + } +} + +void R_FreeCinematics( void ) +{ + for( int i = 0; i < MAX_MOVIES; i++ ) + { + if( tr.cinematics[i].state ) + { + ALERT( at_notice, "release cinematic %s\n", tr.cinematics[i].name ); + FREE_CINEMATIC( tr.cinematics[i].state ); + } + } + + memset( tr.cinematics, 0, sizeof( tr.cinematics )); + + for( i = 0; i < MAX_MOVIE_TEXTURES; i++ ) + { + if( !tr.cinTextures[i] ) break; + FREE_TEXTURE( tr.cinTextures[i] ); + } + + memset( tr.cinTextures, 0, sizeof( tr.cinTextures )); +} + +int R_AllocateCinematicTexture( unsigned int txFlags ) +{ + int i = tr.num_cin_used; + + if( i >= MAX_MOVIE_TEXTURES ) + { + ALERT( at_error, "R_AllocateCinematicTexture: cine textures limit exceeded!\n" ); + return 0; // disable + } + tr.num_cin_used++; + + if( !tr.cinTextures[i] ) + { + char txName[16]; + + Q_snprintf( txName, sizeof( txName ), "*cinematic%i", i ); + + // create new cinematic texture + // NOTE: dimension of texture is no matter because CIN_UPLOAD_FRAME will be rescale texture + tr.cinTextures[i] = CREATE_TEXTURE( txName, 256, 256, NULL, txFlags ); + } + + return (i+1); +} + +void R_UpdateCinematic( const msurface_t *surf ) +{ + if( !RI->currententity->curstate.body ) + return; // just disabled + + // draw the cinematic + mextrasurf_t *es = surf->info; + + // found the corresponding cinstate + const char *cinname = gRenderfuncs.GetFileByIndex( RI->currententity->curstate.sequence ); + int cinhandle = R_PrecacheCinematic( cinname ); + + if( cinhandle >= 0 && es->cintexturenum <= 0 ) + es->cintexturenum = R_AllocateCinematicTexture( TF_NOMIPMAP ); + + if( cinhandle == -1 || es->cintexturenum <= 0 || CIN_IS_ACTIVE( tr.cinematics[cinhandle].state ) == false ) + { + // cinematic textures limit exceeded, so remove SURF_MOVIE flag + ((msurface_t *)surf)->flags &= ~SURF_MOVIE; + return; + } + + gl_movie_t *cin = &tr.cinematics[cinhandle]; + float cin_time; + + if( FBitSet( RI->currententity->curstate.iuser1, CF_LOOPED_MOVIE )) + { + // advances cinematic time + cin_time = fmod( RI->currententity->curstate.fuser2, cin->length ); + } + else + { + cin_time = RI->currententity->curstate.fuser2; + } + + // read the next frame + int cin_frame = CIN_GET_FRAME_NUMBER( cin->state, cin_time ); + + // upload the new frame + if( cin_frame != es->checkcount ) + { + GL_SelectTexture( GL_TEXTURE0 ); // doesn't matter. select 0-th unit just as default + byte *raw = CIN_GET_FRAMEDATA( cin->state, cin_frame ); + CIN_UPLOAD_FRAME( tr.cinTextures[es->cintexturenum-1], cin->xres, cin->yres, cin->xres, cin->yres, raw ); + es->checkcount = cin_frame; + } +} + +void R_UpdateCinSound( cl_entity_t *e ) +{ + if( g_iXashEngineBuildNumber < 4256 ) + return; // too old for this feature + + if( !e->curstate.body || !FBitSet( e->curstate.iuser1, CF_MOVIE_SOUND )) + return; // just disabled + + // found the corresponding cinstate + const char *cinname = gRenderfuncs.GetFileByIndex( e->curstate.sequence ); + int cinhandle = R_PrecacheCinematic( cinname ); + + if( cinhandle == -1 || CIN_IS_ACTIVE( tr.cinematics[cinhandle].state ) == false ) + return; + + gl_movie_t *cin = &tr.cinematics[cinhandle]; + float cin_time; + + if( FBitSet( e->curstate.iuser1, CF_LOOPED_MOVIE )) + { + // advances cinematic time + cin_time = fmod( e->curstate.fuser2, cin->length ); + } + else + { + cin_time = e->curstate.fuser2; + } + + // stream avi sound + CIN_UPDATE_SOUND( cin->state, e->index, VOL_NORM, ATTN_IDLE, cin_time ); +} \ No newline at end of file diff --git a/cl_dll/render/gl_occlusion.cpp b/cl_dll/render/gl_occlusion.cpp new file mode 100644 index 0000000..9aa3e4d --- /dev/null +++ b/cl_dll/render/gl_occlusion.cpp @@ -0,0 +1,231 @@ +/* +gl_occlusion.cpp - occlusion query implementation class +this code written for Paranoia 2: Savior modification +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "gl_local.h" +#include +#include "gl_occlusion.h" +#include "gl_world.h" + +/* +=============== +GL_AllocOcclusionQuery + +=============== +*/ +void GL_AllocOcclusionQuery( msurface_t *surf ) +{ + if( !GL_Support( R_OCCLUSION_QUERIES_EXT ) || surf->info->query ) + return; + + pglGenQueriesARB( 1, &surf->info->query ); +} + +/* +=============== +GL_DeleteOcclusionQuery + +=============== +*/ +void GL_DeleteOcclusionQuery( msurface_t *surf ) +{ + if( !GL_Support( R_OCCLUSION_QUERIES_EXT ) || !surf->info->query ) + return; + + pglDeleteQueriesARB( 1, &surf->info->query ); +} + +/* +=============== +GL_DrawOcclusionCube + +=============== +*/ +static void GL_DrawOcclusionCube( const Vector &absmin, const Vector &absmax ) +{ + vec3_t bbox[8]; + int i; + + // compute a full bounding box + for( i = 0; i < 8; i++ ) + { + bbox[i][0] = ( i & 1 ) ? absmin[0] : absmax[0]; + bbox[i][1] = ( i & 2 ) ? absmin[1] : absmax[1]; + bbox[i][2] = ( i & 4 ) ? absmin[2] : absmax[2]; + } + + pglBegin( GL_QUADS ); + + for( i = 0; i < 6; i++ ) + { + pglVertex3fv( bbox[g_boxpnt[i][0]] ); + pglVertex3fv( bbox[g_boxpnt[i][1]] ); + pglVertex3fv( bbox[g_boxpnt[i][2]] ); + pglVertex3fv( bbox[g_boxpnt[i][3]] ); + } + pglEnd(); +} + +/* +=============== +GL_TestSurfaceOcclusion + +=============== +*/ +void GL_TestSurfaceOcclusion( msurface_t *surf ) +{ + mextrasurf_t *es = surf->info; + Vector absmin, absmax; + word cached_matrix; + Vector normal; + + if( !es->query || FBitSet( surf->flags, SURF_QUEUED )) + return; // we already have the query + + if( !es->parent ) cached_matrix = WORLD_MATRIX; + else cached_matrix = es->parent->hCachedMatrix; + gl_state_t *glm = GL_GetCache( cached_matrix ); + + if( FBitSet( surf->flags, SURF_PLANEBACK )) + normal = -surf->plane->normal; + else normal = surf->plane->normal; + + // place above surface + absmin = es->mins + normal * 5.0f; + absmax = es->maxs + normal * 5.0f; + ExpandBounds( absmin, absmax, 2.0f ); + + if( cached_matrix != WORLD_MATRIX ) + TransformAABB( glm->transform, es->mins, es->maxs, absmin, absmax ); + pglBeginQueryARB( GL_SAMPLES_PASSED_ARB, es->query ); + GL_DrawOcclusionCube( absmin, absmax ); + pglEndQueryARB( GL_SAMPLES_PASSED_ARB ); + + // now we have a valid query + SetBits( surf->flags, SURF_QUEUED ); +} + +/* +=============== +GL_TestSurfaceOcclusion + +=============== +*/ +void GL_DebugSurfaceOcclusion( msurface_t *surf ) +{ + mextrasurf_t *es = surf->info; + Vector absmin, absmax; + word cached_matrix; + Vector normal; + + if( !FBitSet( surf->flags, SURF_QUEUED )) + return; // draw only queue + + if( !es->parent ) cached_matrix = WORLD_MATRIX; + else cached_matrix = es->parent->hCachedMatrix; + gl_state_t *glm = GL_GetCache( cached_matrix ); + + if( FBitSet( surf->flags, SURF_PLANEBACK )) + normal = -surf->plane->normal; + else normal = surf->plane->normal; + + // place above surface + absmin = es->mins + normal * 5.0f; + absmax = es->maxs + normal * 5.0f; + ExpandBounds( absmin, absmax, 2.0f ); + + if( cached_matrix != WORLD_MATRIX ) + TransformAABB( glm->transform, es->mins, es->maxs, absmin, absmax ); + GL_DrawOcclusionCube( absmin, absmax ); +} + +/* +================ +R_RenderOcclusionList +================ +*/ +void R_RenderSurfOcclusionList( void ) +{ + int i; + + if( !RP_NORMALPASS() || !CVAR_TO_BOOL( r_occlusion_culling )) + return; + + if( !RI->frame.num_subview_faces ) + return; + + pglColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE ); + GL_DepthMask( GL_FALSE ); + GL_AlphaTest( GL_FALSE ); + GL_BindShader( NULL ); + + for( i = 0; i < RI->frame.num_subview_faces; i++ ) + GL_TestSurfaceOcclusion( RI->frame.subview_faces[i] ); + + pglColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE ); + GL_DepthMask( GL_TRUE ); + pglFlush(); + + if( r_occlusion_culling->value < 2.0f ) + return; + + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + GL_Blend( GL_FALSE ); + + for( i = 0; i < RI->frame.num_subview_faces; i++ ) + GL_DebugSurfaceOcclusion( RI->frame.subview_faces[i] ); +} + +/* +================ +GL_SurfaceOccluded +================ +*/ +bool GL_SurfaceOccluded( msurface_t *surf ) +{ + mextrasurf_t *es = surf->info; + GLuint sampleCount = 0; + GLint available = false; + + if( !RP_NORMALPASS() || !CVAR_TO_BOOL( r_occlusion_culling )) + return false; + + if( !es->query ) return false; + + if( !FBitSet( surf->flags, SURF_QUEUED )) + { + // occlusion is no more actual + ClearBits( surf->flags, SURF_OCCLUDED ); + return false; + } + + // i hope results will be arrived on a next frame... + pglGetQueryObjectivARB( es->query, GL_QUERY_RESULT_AVAILABLE_ARB, &available ); + + // NOTE: if we can't get actual information about query results + // assume that object was visible and cull him with default methods: frustum, pvs etc + if( !available ) return false; + + pglGetQueryObjectuivARB( es->query, GL_QUERY_RESULT_ARB, &sampleCount ); + ClearBits( surf->flags, SURF_QUEUED ); // we catch the results, so query is outdated + if( sampleCount == 0 ) SetBits( surf->flags, SURF_OCCLUDED ); + else ClearBits( surf->flags, SURF_OCCLUDED ); + if( !sampleCount ) r_stats.c_occlusion_culled++; + + return (FBitSet( surf->flags, SURF_OCCLUDED ) != 0); +} \ No newline at end of file diff --git a/cl_dll/render/gl_occlusion.h b/cl_dll/render/gl_occlusion.h new file mode 100644 index 0000000..4c6cc76 --- /dev/null +++ b/cl_dll/render/gl_occlusion.h @@ -0,0 +1,25 @@ +/* +gl_occlusion.h - occlusion query implementation +this code written for Paranoia 2: Savior modification +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef GL_OCCLUSION_H +#define GL_OCCLUSION_H + +void GL_AllocOcclusionQuery( msurface_t *surf ); +void GL_DeleteOcclusionQuery( msurface_t *surf ); +bool GL_SurfaceOccluded( msurface_t *surf ); +void GL_TestSurfaceOcclusion( msurface_t *surf ); + +#endif//GL_OCCLUSION_H \ No newline at end of file diff --git a/cl_dll/render/gl_postprocess.cpp b/cl_dll/render/gl_postprocess.cpp new file mode 100644 index 0000000..f1f364b --- /dev/null +++ b/cl_dll/render/gl_postprocess.cpp @@ -0,0 +1,600 @@ +// +// written by BUzer for HL: Paranoia modification +// +// 2006 + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "gl_local.h" +#include "mathlib.h" +#include "gl_shader.h" +#include "stringlib.h" +#include "gl_world.h" + +extern int g_iGunMode; + +#define DEAD_GRAYSCALE_TIME 5.0f +#define TARGET_SIZE 256 + +cvar_t *v_posteffects; +cvar_t *v_grayscale; +cvar_t *v_sunshafts; + +// post-process shaders +class CBasePostEffects +{ +public: + word blurShader[2]; // e.g. underwater blur + word dofShader; // iron sight with dof + word monoShader; // monochrome effect + word genSunShafts; // sunshafts effect + word drawSunShafts; // sunshafts effect + int target_rgb[2]; + float grayScaleFactor; + float blurFactor[2]; + bool m_bUseTarget; + + // DOF parameters + float m_flCachedDepth; + float m_flLastDepth; + float m_flStartDepth; + float m_flOffsetDepth; + float m_flStartTime; + float m_flDelayTime; + int g_iGunLastMode; + float m_flStartLength; + float m_flOffsetLength; + float m_flLastLength; + float m_flDOFStartTime; + + // sunshafts variables + Vector m_vecSunLightColor; + Vector m_vecSunPosition; + + void InitScreenColor( void ); + void InitScreenDepth( void ); + void InitTargetColor( int slot ); + void RequestScreenColor( void ); + void RequestScreenDepth( void ); + void RequestTargetCopy( int slot ); + bool ProcessDepthOfField( void ); + bool ProcessSunShafts( void ); + void InitDepthOfField( void ); + void SetNormalViewport( void ); + void SetTargetViewport( void ); + bool Begin( void ); + void End( void ); +}; + +void CBasePostEffects :: InitScreenColor( void ) +{ + if( tr.screen_color ) + { + FREE_TEXTURE( tr.screen_color ); + tr.screen_color = 0; + } + + tr.screen_color = CREATE_TEXTURE( "*screencolor", glState.width, glState.height, NULL, TF_COLORBUFFER ); +} + +void CBasePostEffects :: InitScreenDepth( void ) +{ + if( tr.screen_depth ) + { + FREE_TEXTURE( tr.screen_depth ); + tr.screen_depth = 0; + } + + tr.screen_depth = CREATE_TEXTURE( "*screendepth", glState.width, glState.height, NULL, TF_DEPTHBUFFER ); +} + +void CBasePostEffects :: InitTargetColor( int slot ) +{ + if( target_rgb[slot] ) + { + FREE_TEXTURE( target_rgb[slot] ); + target_rgb[slot] = 0; + } + + target_rgb[slot] = CREATE_TEXTURE( va( "*target%i", slot ), TARGET_SIZE, TARGET_SIZE, NULL, TF_IMAGE ); +} + +void CBasePostEffects :: RequestScreenColor( void ) +{ + GL_BindTexture( GL_TEXTURE0, tr.screen_color ); + pglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, glState.width, glState.height ); +} + +void CBasePostEffects :: RequestScreenDepth( void ) +{ + GL_BindTexture( GL_TEXTURE0, tr.screen_depth ); + pglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, glState.width, glState.height ); +} + +void CBasePostEffects :: RequestTargetCopy( int slot ) +{ + GL_BindTexture( GL_TEXTURE0, target_rgb[slot] ); + pglCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, TARGET_SIZE, TARGET_SIZE ); +} + +void CBasePostEffects :: SetNormalViewport( void ) +{ + pglViewport( RI->glstate.viewport[0], RI->glstate.viewport[1], RI->glstate.viewport[2], RI->glstate.viewport[3] ); +} + +void CBasePostEffects :: SetTargetViewport( void ) +{ + pglViewport( 0, 0, TARGET_SIZE, TARGET_SIZE ); +} + +void CBasePostEffects :: InitDepthOfField( void ) +{ + g_iGunLastMode = 1; +} + +bool CBasePostEffects :: ProcessDepthOfField( void ) +{ + if( !CVAR_TO_BOOL( r_dof ) || g_iGunMode == 0 ) + return false; // disabled or unitialized + + if( g_iGunMode != g_iGunLastMode ) + { + if( g_iGunMode == 1 ) + { + // disable iron sight + m_flStartLength = m_flLastLength; + m_flOffsetLength = -m_flStartLength; + m_flDOFStartTime = tr.time; + } + else + { + // enable iron sight + m_flStartLength = m_flLastLength; + m_flOffsetLength = r_dof_focal_length->value; + m_flDOFStartTime = tr.time; + } + + +// ALERT( at_console, "Iron sight changed( %i )\n", g_iGunMode ); + g_iGunLastMode = g_iGunMode; + } + + if( g_iGunLastMode == 1 && m_flDOFStartTime == 0.0f ) + return false; // iron sight disabled + + if( !Begin( )) return false; + + if( m_flDOFStartTime != 0.0f ) + { + float flDegree = (tr.time - m_flDOFStartTime) / 0.3f; + + if( flDegree >= 1.0f ) + { + // all done. holds the final value + m_flLastLength = m_flStartLength + m_flOffsetLength; + m_flDOFStartTime = 0.0f; // done + } + else + { + // evaluate focal length + m_flLastLength = m_flStartLength + m_flOffsetLength * flDegree; + } + } + + float zNear = Z_NEAR; // fixed + float zFar = RI->view.farClip; + float depthValue = 0.0f; + + // get current depth value + pglReadPixels( glState.width >> 1, glState.height >> 1, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depthValue ); + depthValue = RemapVal( depthValue, 0.0, 0.8, 0.0, 1.0 ); + depthValue = -zFar * zNear / ( depthValue * ( zFar - zNear ) - zFar ); // linearize it + float holdTime = bound( 0.01f, r_dof_hold_time->value, 0.5f ); + float changeTime = bound( 0.1f, r_dof_change_time->value, 2.0f ); + + if( Q_round( m_flCachedDepth, 10 ) != Q_round( depthValue, 10 )) + { + m_flStartTime = 0.0f; // cancelling changes + m_flDelayTime = tr.time; // make sure what focal point is not changed more than 0.5 secs + m_flStartDepth = m_flLastDepth; // get last valid depth + m_flOffsetDepth = depthValue - m_flStartDepth; + m_flCachedDepth = depthValue; + } + + if(( tr.time - m_flDelayTime ) > holdTime && m_flStartTime == 0.0f && m_flDelayTime != 0.0f ) + { + // begin the change depth + m_flStartTime = tr.time; + } + + if( m_flStartTime != 0.0f ) + { + float flDegree = (tr.time - m_flStartTime) / changeTime; + + if( flDegree >= 1.0f ) + { + // all done. holds the final value + m_flLastDepth = m_flStartDepth + m_flOffsetDepth; + m_flStartTime = m_flDelayTime = 0.0f; + } + else + { + // evaluate focal depth + m_flLastDepth = m_flStartDepth + m_flOffsetDepth * flDegree; + } + } + + return true; +} + +bool CBasePostEffects :: ProcessSunShafts( void ) +{ + if( !CVAR_TO_BOOL( v_sunshafts )) + return false; + + if( !FBitSet( world->features, WORLD_HAS_SKYBOX )) + return false; + + if( tr.sky_normal == g_vecZero ) + return false; + + // update blur params + blurFactor[0] = 0.15f; + blurFactor[1] = 0.15f; + + if( tr.sun_light_enabled ) m_vecSunLightColor = tr.sun_diffuse; + else m_vecSunLightColor = tr.sky_ambient * (1.0f/128.0f) * tr.diffuseFactor; + Vector sunPos = tr.cached_vieworigin + tr.sky_normal * 1000.0; + ColorNormalize( m_vecSunLightColor, m_vecSunLightColor ); + + Vector ndc, view; + + // project sunpos to screen + R_TransformWorldToDevice( sunPos, ndc ); + R_TransformDeviceToScreen( ndc, m_vecSunPosition ); + m_vecSunPosition.z = DotProduct( -tr.sky_normal, GetVForward( )); + + if( m_vecSunPosition.z < 0.01f ) + return false; // fade out + + // convert to screen pixels + m_vecSunPosition.x = m_vecSunPosition.x / glState.width; + m_vecSunPosition.y = m_vecSunPosition.y / glState.height; + + return Begin(); +} + +bool CBasePostEffects :: Begin( void ) +{ + // we are in cubemap rendering mode + if( !RP_NORMALPASS( )) + return false; + + if( !CVAR_TO_BOOL( v_posteffects )) + return false; + + GL_Setup2D(); + + return true; +} + +void CBasePostEffects :: End( void ) +{ + GL_CleanUpTextureUnits( 0 ); + GL_BindShader( NULL ); + GL_Setup3D(); +} + +static CBasePostEffects post; + +void InitPostEffects( void ) +{ + char options[MAX_OPTIONS_LENGTH]; + + v_posteffects = CVAR_REGISTER( "gl_posteffects", "1", FCVAR_ARCHIVE ); + v_sunshafts = CVAR_REGISTER( "gl_sunshafts", "1", FCVAR_ARCHIVE ); + v_grayscale = CVAR_REGISTER( "gl_grayscale", "0", 0 ); + + memset( &post, 0, sizeof( post )); + + // monochrome effect + post.monoShader = GL_FindShader( "postfx/monochrome", "postfx/generic", "postfx/monochrome" ); + + // gaussian blur for X + GL_SetShaderDirective( options, "BLUR_X" ); + post.blurShader[0] = GL_FindShader( "postfx/gaussblur", "postfx/generic", "postfx/gaussblur", options ); + + // gaussian blur for Y + GL_SetShaderDirective( options, "BLUR_Y" ); + post.blurShader[1] = GL_FindShader( "postfx/gaussblur", "postfx/generic", "postfx/gaussblur", options ); + + // DOF with bokeh + post.dofShader = GL_FindShader( "postfx/dofbokeh", "postfx/generic", "postfx/dofbokeh" ); + + // prepare sunshafts + post.genSunShafts = GL_FindShader( "postfx/genshafts", "postfx/generic", "postfx/genshafts" ); + + // render sunshafts + post.drawSunShafts = GL_FindShader( "postfx/drawshafts", "postfx/generic", "postfx/drawshafts" ); +} + +void InitPostTextures( void ) +{ + post.InitScreenColor(); + post.InitScreenDepth(); + post.InitTargetColor( 0 ); + post.InitDepthOfField(); +} + +static float GetGrayscaleFactor( void ) +{ + float grayscale = v_grayscale->value; + + if( gHUD.m_flDeadTime ) + { + float fact = (tr.time - gHUD.m_flDeadTime) / DEAD_GRAYSCALE_TIME; + + fact = Q_min( fact, 1.0f ); + grayscale = Q_max( fact, grayscale ); + } + + return grayscale; +} + +// rectangle version +void RenderFSQ( void ) +{ + float screenWidth = (float)glState.width; + float screenHeight = (float)glState.height; + + pglBegin( GL_QUADS ); + pglTexCoord2f( 0.0f, screenHeight ); + pglVertex2f( 0.0f, 0.0f ); + pglTexCoord2f( screenWidth, screenHeight ); + pglVertex2f( screenWidth, 0.0f ); + pglTexCoord2f( screenWidth, 0.0f ); + pglVertex2f( screenWidth, screenHeight ); + pglTexCoord2f( 0.0f, 0.0f ); + pglVertex2f( 0.0f, screenHeight ); + pglEnd(); +} + +void RenderFSQ( int wide, int tall ) +{ + float screenWidth = (float)wide; + float screenHeight = (float)tall; + + pglBegin( GL_QUADS ); + pglTexCoord2f( 0.0f, 1.0f ); + pglNormal3fv( tr.screen_normals[0] ); + pglVertex2f( 0.0f, 0.0f ); + pglTexCoord2f( 1.0f, 1.0f ); + pglNormal3fv( tr.screen_normals[1] ); + pglVertex2f( screenWidth, 0.0f ); + pglTexCoord2f( 1.0f, 0.0f ); + pglNormal3fv( tr.screen_normals[2] ); + pglVertex2f( screenWidth, screenHeight ); + pglTexCoord2f( 0.0f, 0.0f ); + pglNormal3fv( tr.screen_normals[3] ); + pglVertex2f( 0.0f, screenHeight ); + pglEnd(); +} + +void GL_DrawScreenSpaceQuad( void ) +{ + pglBegin( GL_QUADS ); + pglTexCoord2f( 0.0f, 1.0f ); + pglNormal3fv( tr.screen_normals[0] ); + pglVertex3f( 0.0f, 0.0f, 0.0f ); + pglNormal3fv( tr.screen_normals[1] ); + pglTexCoord2f( 0.0f, 0.0f ); + pglVertex3f( 0.0f, glState.height, 0.0f ); + pglNormal3fv( tr.screen_normals[2] ); + pglTexCoord2f( 1.0f, 0.0f ); + pglVertex3f( glState.width, glState.height, 0.0f ); + pglNormal3fv( tr.screen_normals[3] ); + pglTexCoord2f( 1.0f, 1.0f ); + pglVertex3f( glState.width, 0.0f, 0.0f ); + pglEnd(); +} + +void V_RenderPostEffect( word hProgram ) +{ + if( hProgram <= 0 ) + { + GL_BindShader( NULL ); + return; // bad shader? + } + + if( RI->currentshader != &glsl_programs[hProgram] ) + { + // force to bind new shader + GL_BindShader( &glsl_programs[hProgram] ); + } + + glsl_program_t *shader = RI->currentshader; + + // setup specified uniforms (and texture bindings) + for( int i = 0; i < shader->numUniforms; i++ ) + { + uniform_t *u = &shader->uniforms[i]; + + switch( u->type ) + { + case UT_SCREENMAP: + if( post.m_bUseTarget ) // HACKHACK + u->SetValue( post.target_rgb[0] ); + else u->SetValue( tr.screen_color ); + break; + case UT_DEPTHMAP: + u->SetValue( tr.screen_depth ); + break; + case UT_COLORMAP: + u->SetValue( post.target_rgb[0] ); + break; + case UT_GRAYSCALE: + u->SetValue( post.grayScaleFactor ); + break; + case UT_BLURFACTOR: + u->SetValue( post.blurFactor[0], post.blurFactor[1] ); + break; + case UT_SCREENSIZEINV: + u->SetValue( 1.0f / (float)glState.width, 1.0f / (float)glState.height ); + break; + case UT_SCREENWIDTH: + u->SetValue( (float)glState.width ); + break; + case UT_SCREENHEIGHT: + u->SetValue( (float)glState.height ); + break; + case UT_FOCALDEPTH: + u->SetValue( post.m_flLastDepth ); + break; + case UT_FOCALLENGTH: + u->SetValue( post.m_flLastLength ); + break; + case UT_DOFDEBUG: + u->SetValue( CVAR_TO_BOOL( r_dof_debug )); + break; + case UT_FSTOP: + u->SetValue( r_dof_fstop->value ); + break; + case UT_ZFAR: + u->SetValue( RI->view.farClip ); + break; + case UT_GAMMATABLE: + u->SetValue( &tr.gamma_table[0][0], 64 ); + break; + case UT_DIFFUSEFACTOR: + u->SetValue( tr.diffuseFactor ); + break; + case UT_AMBIENTFACTOR: + u->SetValue( tr.ambientFactor ); + break; + case UT_SUNREFRACT: + u->SetValue( tr.sun_refract ); + break; + case UT_REALTIME: + u->SetValue( (float)tr.time ); + break; + case UT_LIGHTDIFFUSE: + u->SetValue( post.m_vecSunLightColor.x, post.m_vecSunLightColor.y, post.m_vecSunLightColor.z ); + break; + case UT_LIGHTORIGIN: + u->SetValue( post.m_vecSunPosition.x, post.m_vecSunPosition.y, post.m_vecSunPosition.z ); + break; + case UT_FOGPARAMS: + u->SetValue( tr.fogColor[0], tr.fogColor[1], tr.fogColor[2], tr.fogDensity ); + break; + default: + ALERT( at_error, "%s: unhandled uniform %s\n", RI->currentshader->name, u->name ); + break; + } + } + + // render a fullscreen quad + RenderFSQ( glState.width, glState.height ); +} + +void RenderBlur( float blurX, float blurY ) +{ + if( !blurX && !blurY ) + return; + + // update blur params + post.blurFactor[0] = blurX; + post.blurFactor[1] = blurY; + + if( !post.Begin( )) return; + + // do vertical blur + post.RequestScreenColor(); + V_RenderPostEffect( post.blurShader[0] ); + + // do horizontal blur + post.RequestScreenColor(); + V_RenderPostEffect( post.blurShader[1] ); + + post.End(); +} + +void RenderMonochrome( void ) +{ + post.grayScaleFactor = GetGrayscaleFactor(); + if( post.grayScaleFactor <= 0.0f ) return; + + if( !post.Begin( )) return; + + // apply monochromatic + post.RequestScreenColor(); + V_RenderPostEffect( post.monoShader ); + + post.End(); +} + +void RenderUnderwaterBlur( void ) +{ + if( !CVAR_TO_BOOL( cv_water ) || tr.waterlevel < 3 ) + return; + + float factor = sin( tr.time * 0.1f * ( M_PI * 2.7f )); + float blurX = RemapVal( factor, -1.0f, 1.0f, 0.18f, 0.23f ); + float blurY = RemapVal( factor, -1.0f, 1.0f, 0.15f, 0.24f ); + + RenderBlur( blurX, blurY ); +} + +void RenderNerveGasBlur( void ) +{ + if( gHUD.m_flBlurAmount <= 0.0f ) + return; + + float factor = sin( tr.time * 0.4f * ( M_PI * 1.7f )); + float blurX = RemapVal( factor, -1.0f, 1.0f, 0.0f, 0.3f ); + float blurY = RemapVal( factor, -1.0f, 1.0f, 0.0f, 0.3f ); + + blurX = bound( 0.0f, blurX, gHUD.m_flBlurAmount ); + blurY = bound( 0.0f, blurY, gHUD.m_flBlurAmount ); + + RenderBlur( blurX, blurY ); +} + +void RenderDOF( void ) +{ + if( !post.ProcessDepthOfField( )) + return; + + post.RequestScreenColor(); + post.RequestScreenDepth(); + V_RenderPostEffect( post.dofShader ); + + post.End(); +} + +void RenderSunShafts( void ) +{ + if( !post.ProcessSunShafts( )) + return; + + post.RequestScreenColor(); + post.RequestScreenDepth(); + + // we operate in small window to increase speedup + post.SetTargetViewport(); + V_RenderPostEffect( post.genSunShafts ); + post.RequestTargetCopy( 0 ); + + post.m_bUseTarget = true; + V_RenderPostEffect( post.blurShader[0] ); + post.RequestTargetCopy( 0 ); + V_RenderPostEffect( post.blurShader[1] ); + post.RequestTargetCopy( 0 ); + post.m_bUseTarget = false; + + // back to normal size + post.SetNormalViewport(); + V_RenderPostEffect( post.drawSunShafts ); + + post.End(); +} \ No newline at end of file diff --git a/cl_dll/render/gl_primitive.cpp b/cl_dll/render/gl_primitive.cpp new file mode 100644 index 0000000..5e7ac78 --- /dev/null +++ b/cl_dll/render/gl_primitive.cpp @@ -0,0 +1,99 @@ +/* +gl_primitive.cpp - rendering primitives +Copyright (C) 2018 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include +#include "cl_util.h" +#include "pm_defs.h" +#include "event_api.h" +#include "gl_local.h" +#include "gl_studio.h" +#include "gl_world.h" +#include "gl_grass.h" + +void CSolidEntry :: SetRenderPrimitive( const Vector verts[4], const Vector4D &color, int texture, int rendermode ) +{ + m_bDrawType = DRAWTYPE_QUAD; + m_iStartVertex = RI->frame.primverts.Count(); + + for( int i = 0; i < 4; i++ ) + RI->frame.primverts.AddToTail( verts[i] ); + m_iColor = PackRGBA( color.x * 255, color.y * 255, color.z * 255, color.w * 255 ); + m_iRenderMode = rendermode; + m_hTexture = texture; +} + +void CSolidEntry :: SetRenderSurface( msurface_t *surface, word hProgram ) +{ + m_bDrawType = DRAWTYPE_SURFACE; + m_pSurf = surface; + m_pParentEntity = RI->currententity; + m_pRenderModel = RI->currentmodel; + m_hProgram = hProgram; +} + +void CSolidEntry :: SetRenderMesh( vbomesh_t *mesh, word hProgram ) +{ + m_bDrawType = DRAWTYPE_MESH; + m_pMesh = mesh; + m_pParentEntity = RI->currententity; + m_pRenderModel = RI->currentmodel; + m_hProgram = hProgram; +} + +void CTransEntry :: ComputeViewDistance( const Vector &absmin, const Vector &absmax ) +{ +#if 1 + m_flViewDist = CalcSqrDistanceToAABB( absmin, absmax, GetVieworg( )); +#else + Vector origin = (absmin + absmax) * 0.5f; + m_flViewDist = DotProduct( origin, GetVForward() ) - RI->view.planedist; +// m_flViewDist = VectorDistance2( origin, GetVieworg( )); +#endif +} + +void CTransEntry :: ComputeScissor( const Vector &absmin, const Vector &absmax ) +{ + ComputeViewDistance( absmin, absmax ); + + if( R_ScissorForAABB( absmin, absmax, &m_vecRect.x, &m_vecRect.y, &m_vecRect.z, &m_vecRect.w )) + m_bScissorReady = true; + else m_bScissorReady = false; +} + +void CTransEntry :: RequestScreenColor( void ) +{ + if( !m_bScissorReady ) return; + + float y2 = (float)RI->view.port[3] - m_vecRect.w - m_vecRect.y; + GL_BindTexture( GL_TEXTURE0, tr.screen_color ); + pglCopyTexSubImage2D( GL_TEXTURE_2D, 0, m_vecRect.x, y2, m_vecRect.x, y2, m_vecRect.z, m_vecRect.w ); +} + +void CTransEntry :: RequestScreenDepth( void ) +{ + if( !m_bScissorReady ) return; + + float y2 = (float)RI->view.port[3] - m_vecRect.w - m_vecRect.y; + GL_BindTexture( GL_TEXTURE0, tr.screen_depth ); + pglCopyTexSubImage2D( GL_TEXTURE_2D, 0, m_vecRect.x, y2, m_vecRect.x, y2, m_vecRect.z, m_vecRect.w ); +} + +void CTransEntry :: RenderScissorDebug( void ) +{ + if( !m_bScissorReady ) return; + + R_DrawScissorRectangle( m_vecRect.x, m_vecRect.y, m_vecRect.z, m_vecRect.w ); +} \ No newline at end of file diff --git a/cl_dll/render/gl_primitive.h b/cl_dll/render/gl_primitive.h new file mode 100644 index 0000000..d5d37b8 --- /dev/null +++ b/cl_dll/render/gl_primitive.h @@ -0,0 +1,80 @@ +/* +gl_primitive.h - render primitives +this code written for Paranoia 2: Savior modification +Copyright (C) 2013 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef GL_PRIMITIVE_H +#define GL_PRIMITIVE_H + +#define DRAWTYPE_UNKNOWN 0 +#define DRAWTYPE_SURFACE 1 +#define DRAWTYPE_MESH 2 +#define DRAWTYPE_QUAD 3 + +//#pragma pack(1) + +class CSolidEntry +{ +public: + void SetRenderPrimitive( const Vector verts[4], const Vector4D &color, int texture, int rendermode ); + void SetRenderSurface( msurface_t *surface, word hProgram ); + void SetRenderMesh( struct vbomesh_s *mesh, word hProgram ); + virtual bool IsTranslucent( void ) { return false; } + int GetType( void ) { return m_bDrawType; } + + byte m_bDrawType; // type of entry + + union + { + cl_entity_t *m_pParentEntity; // pointer to parent entity + int m_iRenderMode; // rendermode for primitive + }; + + union + { + model_t *m_pRenderModel; // render model + int m_iStartVertex; // offset in global heap + }; + + union + { + unsigned short m_hProgram; // handle to glsl program (may be 0) + unsigned short m_hTexture; // texture for primitive + }; + + union + { + struct vbomesh_s *m_pMesh; // NULL or mesh + msurface_t *m_pSurf; // NULL or surface + int m_iColor; // primitive color + }; +}; + +class CTransEntry : public CSolidEntry +{ +public: + void ComputeViewDistance( const Vector &absmin, const Vector &absmax ); + void ComputeScissor( const Vector &absmin, const Vector &absmax ); + virtual bool IsTranslucent( void ) { return true; } + void RequestScreenColor( void ); + void RequestScreenDepth( void ); + void RenderScissorDebug( void ); + + float m_flViewDist; + Vector4D m_vecRect; + bool m_bScissorReady : 1; +}; +//#pragma pack() + +#endif//GL_PRIMITIVE_H \ No newline at end of file diff --git a/cl_dll/render/gl_rmain.cpp b/cl_dll/render/gl_rmain.cpp new file mode 100644 index 0000000..d8655ee --- /dev/null +++ b/cl_dll/render/gl_rmain.cpp @@ -0,0 +1,1218 @@ +/* +gl_rmain.cpp - renderer main loop +Copyright (C) 2013 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "entity_types.h" +#include "gl_local.h" +#include +#include "gl_aurora.h" +#include "gl_rpart.h" +#include "gl_studio.h" +#include "gl_sprite.h" +#include "event_api.h" +#include "gl_world.h" +#include "gl_grass.h" + +extern "C" int DLLEXPORT HUD_GetRenderInterface( int version, render_api_t *renderfuncs, render_interface_t *callback ); + +ref_globals_t tr; +ref_instance_t *RI = NULL; +ref_stats_t r_stats; +ref_buildstats_t r_buildstats; +char r_speeds_msg[2048]; +char r_depth_msg[2048]; +model_t *worldmodel = NULL; +float gldepthmin, gldepthmax; +int sunSize[MAX_SHADOWMAPS] = { 1024, 1024, 1024, 1024 }; + +bool R_SkyIsVisible( void ) +{ + return FBitSet( RI->view.flags, RF_SKYVISIBLE ) ? true : false; +} + +void R_InitRefState( void ) +{ + glState.stack_position = 0; + RI = &glState.stack[glState.stack_position]; +} + +const char *R_GetNameForView( void ) +{ + static char passName[256]; + + passName[0] = '\0'; + + if( FBitSet( RI->params, RP_MIRRORVIEW )) + Q_strncat( passName, "mirror ", sizeof( passName )); + if( FBitSet( RI->params, RP_ENVVIEW )) + Q_strncat( passName, "cubemap ", sizeof( passName )); + if( FBitSet( RI->params, RP_SKYVIEW )) + Q_strncat( passName, "skybox ", sizeof( passName )); + if( FBitSet( RI->params, RP_SHADOWVIEW )) + Q_strncat( passName, "shadow ", sizeof( passName )); + if( RP_NORMALPASS( )) + Q_strncat( passName, "general ", sizeof( passName )); + + return passName; +} + +/* +=============== +R_BuildViewPassHierarchy + +debug thing +=============== +*/ +void R_BuildViewPassHierarchy( void ) +{ + char empty[MAX_REF_STACK]; + + if( (int)r_speeds->value == 8 ) + { + int num_faces = 0; + + if( glState.stack_position > 0 ) + num_faces = R_GetPrevInstance()->frame.num_subview_faces; + for( unsigned int i = 0; i < glState.stack_position; i++ ) + empty[i] = ' '; + empty[i] = '\0'; + + // build pass hierarchy + const char *string = va( "%s->%d %s (%d subview)\n", empty, glState.stack_position, R_GetNameForView( ), num_faces ); + Q_strncat( r_depth_msg, string, sizeof( r_depth_msg )); + } +} + +void R_ClearFrameLists( void ) +{ + RI->frame.solid_faces.Purge(); + RI->frame.solid_meshes.Purge(); + RI->frame.grass_list.Purge(); + RI->frame.trans_list.Purge(); + RI->frame.light_meshes.Purge(); + RI->frame.light_faces.Purge(); + RI->frame.light_grass.Purge(); + RI->frame.primverts.Purge(); + RI->frame.num_subview_faces = 0; +} + +void R_ResetRefState( void ) +{ + ref_instance_t *prevRI; + + ASSERT( glState.stack_position > 0 ); + + prevRI = &glState.stack[glState.stack_position - 1]; + RI = &glState.stack[glState.stack_position]; + + // copy params from old refresh + RI->params = 0; + memcpy( &RI->view, &prevRI->view, sizeof( ref_viewcache_t )); + memcpy( &RI->glstate, &prevRI->glstate, sizeof( ref_glstate_t )); + + RI->view.client_frame = 0; + R_ClearFrameLists(); + + // send info about visible lights + if( FBitSet( prevRI->view.flags, RF_HASDYNLIGHTS )) + SetBits( RI->view.flags, RF_HASDYNLIGHTS ); + + RI->currententity = NULL; + RI->currentmodel = NULL; + RI->currentlight = NULL; + GL_BindShader( NULL ); +} + +void R_PushRefState( void ) +{ + if( ++glState.stack_position >= MAX_REF_STACK ) + HOST_ERROR( "Refresh stack overflow\n" ); + + RI = &glState.stack[glState.stack_position]; + R_ResetRefState(); +} + +void R_PopRefState( void ) +{ + if( --glState.stack_position < 0 ) + HOST_ERROR( "Refresh stack underflow\n" ); + RI = &glState.stack[glState.stack_position]; + + // frametime is valid only for normal pass + if( RP_NORMALPASS( )) + tr.frametime = tr.saved_frametime; + else tr.frametime = 0.0; + + // need to rebuild scissors + R_SetupDynamicLights(); +} + +ref_instance_t *R_GetPrevInstance( void ) +{ + ASSERT( glState.stack_position > 0 ); + + return &glState.stack[glState.stack_position - 1]; +} + +const Vector gl_state_t :: GetModelOrigin( void ) +{ + if( m_bSkyEntity ) + { + if( tr.sky_speed ) + { + Vector trans = RI->view.origin - tr.sky_origin; + trans = trans - (RI->view.origin - tr.sky_world_origin) / tr.sky_speed; + Vector skypos = tr.sky_origin + (RI->view.origin - tr.sky_world_origin) / tr.sky_speed; + return skypos - transform.GetOrigin(); + } + + return tr.sky_origin - transform.GetOrigin(); + } + + return transform.VectorITransform( RI->view.origin ); +} + +/* +=============== +GL_CacheState + +build matrix pack or find already existed +=============== +*/ +unsigned short GL_CacheState( const Vector &origin, const Vector &angles, bool skyentity ) +{ + gl_state_t state; + int i; + + state.transform = matrix4x4( origin, angles, 1.0f ); + state.transform.CopyToArray( state.modelMatrix ); + state.m_bSkyEntity = skyentity; + + for( i = 0; i < tr.cached_state.Count(); i++ ) + { + // NOTE: (MVP == MV == M). So no reason to compare all the matrices + if( !memcmp( tr.cached_state[i].modelMatrix, state.modelMatrix, sizeof( state.modelMatrix ))) + { + if( tr.cached_state[i].m_bSkyEntity == skyentity ) + return i; + } + } + + // store results + tr.cached_state.AddToTail( state ); + + return tr.cached_state.Count() - 1; +} + +/* +=============== +GL_CacheState + +build matrix pack or find already existed +=============== +*/ +gl_state_t *GL_GetCache( word hCachedMatrix ) +{ + static gl_state_t skycache; + gl_state_t *glm; + + if( hCachedMatrix >= tr.cached_state.Count( )) + { + ASSERT( tr.cached_state.Count() > 0 ); + ALERT( at_aiconsole, "request invalid cachenum %d\n", hCachedMatrix ); + return &tr.cached_state[WORLD_MATRIX]; + } + + glm = &tr.cached_state[hCachedMatrix]; + + // sky entities can't be cached properly... + if( glm->m_bSkyEntity ) + { + Vector origin = glm->transform.GetOrigin(); + Vector trans = GetVieworg() - tr.sky_origin; + skycache.transform = matrix4x4( origin + trans, g_vecZero, 1.0f ); + skycache.transform.CopyToArray( skycache.modelMatrix ); + skycache.m_bSkyEntity = glm->m_bSkyEntity; + + return &skycache; + } + + return glm; +} + +/* +=============== +R_MarkWorldVisibleFaces + +instead of RecursiveWorldNode +=============== +*/ +void R_MarkWorldVisibleFaces( model_t *model ) +{ + float maxdist = 0.0f; + msurface_t **mark; + mleaf_t *leaf; + int i, j; + + memset( RI->view.visfaces, 0x00, (worldmodel->numsurfaces + 7) >> 3 ); + memset( RI->view.vislight, 0x00, (world->numworldlights + 7) >> 3 ); + ClearBounds( RI->view.visMins, RI->view.visMaxs ); + + if( !model ) return; // just clear visibility and out + + // always skip the leaf 0, because is outside leaf + for( i = 1, leaf = &model->leafs[1]; i < model->numleafs + 1; i++, leaf++ ) + { + mextraleaf_t *eleaf = LEAF_INFO( leaf, model ); // named like personal vaporisers manufacturer he-he + + if( CHECKVISBIT( RI->view.pvsarray, leaf->cluster ) && ( leaf->efrags || leaf->nummarksurfaces )) + { + if( RI->view.frustum.CullBox( eleaf->mins, eleaf->maxs )) + continue; + + // do additional culling in dev_overview mode + if( FBitSet( RI->params, RP_DRAW_OVERVIEW ) && R_CullNodeTopView( (mnode_t *)leaf )) + continue; + + // deal with model fragments in this leaf + if( leaf->efrags && RP_NORMALPASS( )) + STORE_EFRAGS( &leaf->efrags, tr.realframecount ); + + if( leaf->contents == CONTENTS_EMPTY ) + { + // unrolled for speedup reasons + RI->view.visMins[0] = Q_min( RI->view.visMins[0], eleaf->mins[0] ); + RI->view.visMaxs[0] = Q_max( RI->view.visMaxs[0], eleaf->maxs[0] ); + RI->view.visMins[1] = Q_min( RI->view.visMins[1], eleaf->mins[1] ); + RI->view.visMaxs[1] = Q_max( RI->view.visMaxs[1], eleaf->maxs[1] ); + RI->view.visMins[2] = Q_min( RI->view.visMins[2], eleaf->mins[2] ); + RI->view.visMaxs[2] = Q_max( RI->view.visMaxs[2], eleaf->maxs[2] ); + } + + r_stats.c_world_leafs++; + + if( leaf->nummarksurfaces ) + { + for( j = 0, mark = leaf->firstmarksurface; j < leaf->nummarksurfaces; j++, mark++ ) + { + msurface_t *surf = *mark; + + // construct grass and update leaf bounds + if( surf->info->grasscount && RP_NORMALPASS( )) + R_PrecacheGrass( surf, eleaf ); + SETVISBIT( RI->view.visfaces, *mark - model->surfaces ); + } + } + } + } + + // now we have actual vismins\vismaxs and can calc farplane distance + for( i = 0; i < 8; i++ ) + { + Vector v, dir; + float dist; + + v[0] = ( i & 1 ) ? RI->view.visMins[0] : RI->view.visMaxs[0]; + v[1] = ( i & 2 ) ? RI->view.visMins[1] : RI->view.visMaxs[1]; + v[2] = ( i & 4 ) ? RI->view.visMins[2] : RI->view.visMaxs[2]; + + dir = v - RI->view.origin; + dist = DotProduct( dir, dir ); + maxdist = Q_max( dist, maxdist ); + } + +// RI->view.farClip = sqrt( maxdist ) + 64.0f; // add some bias +} + +/* +=============== +R_SetupViewCache + +check for changes and build visibility +=============== +*/ +static void R_SetupViewCache( const ref_viewpass_t *rvp ) +{ + const ref_overview_t *ov = GET_OVERVIEW_PARMS(); + model_t *model = worldmodel; + + RI->view.changed = 0; // always clearing changes at start of frame + + if( !model && FBitSet( RI->params, RP_DRAW_WORLD )) + HOST_ERROR( "R_SetupViewCache: NULL worldmodel\n" ); + + // client frame was changed: refresh the draw lists + // FIXME: get rid of this when renderer will be finalized + if( RI->view.client_frame != tr.realframecount ) + SetBits( RI->view.changed, RC_FORCE_UPDATE ); + + if( tr.params_changed ) + SetBits( RI->view.changed, RC_FORCE_UPDATE ); + + // viewentity was changed + if( RI->view.entity != rvp->viewentity ) + { + SetBits( RI->view.changed, RC_FORCE_UPDATE ); + RI->view.entity = rvp->viewentity; + } + + // viewport was changed, recompute + if( memcmp( RI->view.port, rvp->viewport, sizeof( RI->view.port ))) + { + memcpy( RI->view.port, rvp->viewport, sizeof( RI->view.port )); + + if( RP_NORMALPASS( ) && !FBitSet( RI->params, RP_DEFERREDLIGHT )) + { + int x, x2, y, y2; + + // set up viewport (main, playersetup) + x = floor( RI->view.port[0] * glState.width / glState.width ); + x2 = ceil(( RI->view.port[0] + RI->view.port[2] ) * glState.width / glState.width ); + y = floor( glState.height - RI->view.port[1] * glState.height / glState.height ); + y2 = ceil( glState.height - ( RI->view.port[1] + RI->view.port[3] ) * glState.height / glState.height ); + + RI->glstate.viewport[0] = x; + RI->glstate.viewport[1] = y2; + RI->glstate.viewport[2] = x2 - x; + RI->glstate.viewport[3] = y - y2; + } + else memcpy( RI->glstate.viewport, RI->view.port, sizeof( RI->glstate.viewport )); // additional passes + } + + // vieworigin was changed + if( RI->view.origin != rvp->vieworigin ) + { + if( !FBitSet( RI->params, RP_MERGE_PVS )) + RI->view.pvspoint = rvp->vieworigin; + SetBits( RI->view.changed, RC_ORIGIN_CHANGED ); + RI->view.origin = rvp->vieworigin; + } + + if( RI->view.angles != rvp->viewangles ) + { + SetBits( RI->view.changed, RC_ANGLES_CHANGED ); + RI->view.angles = rvp->viewangles; + } + + // check if overview settings was changed + if( FBitSet( RI->params, RP_DRAW_OVERVIEW ) && memcmp( &RI->view.over, ov, sizeof( RI->view.over ))) + { + SetBits( RI->view.changed, RC_ORIGIN_CHANGED|RC_ANGLES_CHANGED|RC_FOV_CHANGED ); + memcpy( &RI->view.over, ov, sizeof( RI->view.over )); + } + + if( FBitSet( RI->view.changed, RC_ORIGIN_CHANGED ) && ( model != NULL )) + { + mleaf_t *leaf = Mod_PointInLeaf( RI->view.pvspoint, model->nodes ); + + if(( RI->view.leaf == NULL ) || ( RI->view.leaf != leaf )) + { + SetBits( RI->view.changed, RC_VIEWLEAF_CHANGED ); + RI->view.leaf = leaf; + } + } + + // development cvars invoke to recompute PVS + if(( RI->view.novis_cached != CVAR_TO_BOOL( r_novis )) || ( RI->view.lockpvs_cached != CVAR_TO_BOOL( r_lockpvs ))) + { + SetBits( RI->view.changed, RC_VIEWLEAF_CHANGED ); + RI->view.novis_cached = CVAR_TO_BOOL( r_novis ); + RI->view.lockpvs_cached = CVAR_TO_BOOL( r_lockpvs ); + } + + // now setup the frame visibility + if( FBitSet( RI->view.changed, RC_VIEWLEAF_CHANGED )) + { + bool mergevis = FBitSet( RI->params, RP_MERGE_PVS ) ? true : false; + bool fullvis = false; + + if( CVAR_TO_BOOL( r_novis ) || FBitSet( RI->params, RP_DRAW_OVERVIEW ) || ( !RI->view.leaf )) + fullvis = true; + + ENGINE_SET_PVS( RI->view.pvspoint, REFPVS_RADIUS, RI->view.pvsarray, mergevis, fullvis ); + + // add skyorigin in each pass in case it was specified + if( tr.sky_origin != g_vecZero && !fullvis ) + ENGINE_SET_PVS( tr.sky_origin, REFPVS_RADIUS, RI->view.pvsarray, true, fullvis ); + SetBits( RI->view.changed, RC_PVS_CHANGED ); + } + + // check FOV for changes + if(( RI->view.fov_x != rvp->fov_x ) || ( RI->view.fov_y != rvp->fov_y )) + { + SetBits( RI->view.changed, RC_FOV_CHANGED ); + RI->view.fov_x = rvp->fov_x; + RI->view.fov_y = rvp->fov_y; + + if( RI->view.fov_x <= 0.0f || RI->view.fov_y <= 0.0f ) + HOST_ERROR( "R_SetupViewCache: bad fov!\n" ); + + if( IS_NAN( RI->view.fov_x ) || IS_NAN( RI->view.fov_y )) + HOST_ERROR( "R_SetupViewCache: NAN fov!\n" ); + + RI->view.lodScale = tan( DEG2RAD( RI->view.fov_x ) * 0.5f ); + } + + if( FBitSet( RI->view.changed, RC_ORIGIN_CHANGED|RC_ANGLES_CHANGED )) + { + // build the transformation matrix for the given view angles and view origin + RI->view.matrix = matrix4x4( RI->view.origin, RI->view.angles, 1.0f ); + RI->view.planedist = DotProduct( GetVieworg(), GetVForward() ); + + // create modelview + RI->view.worldMatrix.CreateModelview(); // init quake world orientation + RI->view.worldMatrix.ConcatRotate( -RI->view.angles[2], 1, 0, 0 ); + RI->view.worldMatrix.ConcatRotate( -RI->view.angles[0], 0, 1, 0 ); + RI->view.worldMatrix.ConcatRotate( -RI->view.angles[1], 0, 0, 1 ); + RI->view.worldMatrix.ConcatTranslate( -RI->view.origin[0], -RI->view.origin[1], -RI->view.origin[2] ); + RI->view.worldMatrix.CopyToArray( RI->glstate.modelviewMatrix ); + } + + // first calculation frustum ignores precision farclip, and will be recomputed with actual farclip + if( FBitSet( RI->view.changed, RC_ORIGIN_CHANGED|RC_ANGLES_CHANGED|RC_FOV_CHANGED )) + { + // compute initial farclip + RI->view.farClip = tr.farclip * 1.74f; + + if( FBitSet( RI->params, RP_DRAW_OVERVIEW )) + { + RI->view.frustum.InitOrthogonal( RI->view.matrix, ov->xLeft, ov->xRight, ov->yBottom, ov->yTop, ov->zNear, ov->zFar ); + RI->view.projectionMatrix.CreateOrtho( ov->xLeft, ov->xRight, ov->yTop, ov->yBottom, ov->zNear, ov->zFar ); + RI->view.projectionMatrix.CopyToArray( RI->glstate.projectionMatrix ); + } + else RI->view.frustum.InitProjection( RI->view.matrix, 0.0f, RI->view.farClip, RI->view.fov_x, RI->view.fov_y ); + SetBits( RI->view.changed, RC_FRUSTUM_CHANGED ); + } + + // time to determine visible leafs and recalc farclip + if( FBitSet( RI->view.changed, RC_PVS_CHANGED|RC_FRUSTUM_CHANGED|RC_FORCE_UPDATE )) + { + CFrustum *frustum = &RI->view.frustum; + float maxdist = 0.0f; + msurface_t *surf; + mextrasurf_t *esrf; + int i, j; + + if( FBitSet( RI->params, RP_SKYVIEW )) + { + for( i = 0; i < 6; i++ ) + { + RI->view.skyMins[0][i] = RI->view.skyMins[1][i] = -1.0f; + RI->view.skyMaxs[0][i] = RI->view.skyMaxs[1][i] = 1.0f; + } + + // force to draw skybox (used for create default cubemap) + SetBits( RI->view.flags, RF_SKYVISIBLE ); + + // force to ignore draw the world + model = NULL; + } + else + { + for( i = 0; i < 6; i++ ) + { + RI->view.skyMins[0][i] = RI->view.skyMins[1][i] = 9999999.0f; + RI->view.skyMaxs[0][i] = RI->view.skyMaxs[1][i] = -9999999.0f; + } + + ClearBits( RI->view.flags, RF_SKYVISIBLE ); // now sky is invisible + } + + // we have dynamic lights for this frame + if( HasDynamicLights( )) SetBits( RI->view.flags, RF_HASDYNLIGHTS ); + else ClearBits( RI->view.flags, RF_HASDYNLIGHTS ); + + R_MarkWorldVisibleFaces( model ); + + // update the frustum + if( !FBitSet( RI->params, RP_DRAW_OVERVIEW )) + { + RI->view.frustum.InitProjection( RI->view.matrix, 0.0f, RI->view.farClip, RI->view.fov_x, RI->view.fov_y ); + RI->view.projectionMatrix.CreateProjection( RI->view.fov_x, RI->view.fov_y, Z_NEAR, RI->view.farClip ); + RI->view.projectionMatrix.CopyToArray( RI->glstate.projectionMatrix ); + SetBits( RI->view.changed, RC_FRUSTUM_CHANGED ); + } + + // recompute worldview projection + RI->view.worldProjectionMatrix = RI->view.projectionMatrix.Concat( RI->view.worldMatrix ); + RI->view.worldProjectionMatrix.CopyToArray( RI->glstate.modelviewProjectionMatrix ); + R_ClearFrameLists(); + + // update the split frustum + if( model != NULL && RP_NORMALPASS() && !FBitSet( RI->params, RP_DRAW_OVERVIEW ) && FBitSet( RI->view.changed, RC_FRUSTUM_CHANGED )) + { + float farClip = RI->view.farClip; + float lambda = bound( 0.1f, r_shadow_split_weight->value, 1.0f ); + float ratio = farClip / Z_NEAR; + Vector planeOrigin; + float zNear, zFar; + int i; + + // initialize all the frustums + for( i = 0; i < MAX_SHADOWMAPS; i++ ) + RI->view.splitFrustum[i] = RI->view.frustum; + + for( i = 1; i < MAX_SHADOWMAPS; i++ ) + { + float si = i / (float)(MAX_SHADOWMAPS); + + zNear = lambda * (Z_NEAR * powf( ratio, si )) + (1.0f - lambda) * (Z_NEAR + (farClip - Z_NEAR) * si); + zFar = zNear * 1.005f; + + planeOrigin = GetVieworg() + GetVForward() * zNear; + RI->view.splitFrustum[i].SetPlane( FRUSTUM_NEAR, GetVForward(), DotProduct( planeOrigin, GetVForward())); + + planeOrigin = GetVieworg() + GetVForward() * zFar; + RI->view.splitFrustum[i-1].SetPlane( FRUSTUM_FAR, -GetVForward(), DotProduct( planeOrigin, -GetVForward())); + + RI->view.parallelSplitDistances[i - 1] = zFar; + } + + planeOrigin = GetVieworg() + GetVForward() * farClip; + RI->view.splitFrustum[NUM_SHADOW_SPLITS].SetPlane( FRUSTUM_FAR, -GetVForward(), DotProduct( planeOrigin, -GetVForward())); + RI->view.parallelSplitDistances[NUM_SHADOW_SPLITS] = farClip; + } + + // don't draw the entities while render skybox or cubemap + if( !FBitSet( RI->params, ( RP_ENVVIEW|RP_SKYVIEW ))) + { + // before process of tr.draw_entities + // we can add muzzleflashes here + R_RunViewmodelEvents(); + + // brush faces not added here! + // only marks as visible in RI->view.visfaces array + for( i = 0; i < tr.num_draw_entities; i++ ) + { + RI->currententity = tr.draw_entities[i]; + RI->currentmodel = RI->currententity->model; + + switch( RI->currentmodel->type ) + { + case mod_brush: + R_MarkSubmodelVisibleFaces(); + break; + case mod_studio: + R_AddStudioToDrawList( RI->currententity ); + break; + case mod_sprite: + R_AddSpriteToDrawList( RI->currententity ); + break; + default: +// HOST_ERROR( "R_SetupViewCache: mod_bad\n" ); + break; + } + } + + // add particles to deferred list + g_pParticleSystems.UpdateSystems(); + g_pParticles.Update(); + } + + // create drawlist for faces, do additional culling for world faces + for( i = 0; model != NULL && i < world->numsortedfaces; i++ ) + { + ASSERT( world->sortedfaces != NULL ); + + j = world->sortedfaces[i]; + + ASSERT( j >= 0 && j < model->numsurfaces ); + + if( CHECKVISBIT( RI->view.visfaces, j )) + { + surf = model->surfaces + j; + esrf = surf->info; + + // submodel faces already passed through this + // operation but world is not + if( FBitSet( surf->flags, SURF_OF_SUBMODEL )) + { + RI->currententity = esrf->parent; + RI->currentmodel = RI->currententity->model; + + R_AddGrassToDrawList( surf, DRAWLIST_SOLID ); + } + else + { + RI->currententity = GET_ENTITY( 0 ); + RI->currentmodel = RI->currententity->model; + + esrf->parent = RI->currententity; // setup dynamic upcast + + R_AddGrassToDrawList( surf, DRAWLIST_SOLID ); + + if( R_CullSurface( surf, GetVieworg(), frustum )) + { + CLEARVISBIT( RI->view.visfaces, j ); // not visible + continue; + } + + // surface has passed all visibility checks + // and can be update some data (lightmaps, mirror matrix, etc) + R_UpdateSurfaceParams( surf ); + } + + // store world translucent watery (if transparent water is support) + if( FBitSet( surf->flags, SURF_DRAWTURB ) && !FBitSet( surf->flags, SURF_OF_SUBMODEL )) + { + if( FBitSet( world->features, WORLD_WATERALPHA )) + R_AddSurfaceToDrawList( surf, DRAWLIST_TRANS ); + else R_AddSurfaceToDrawList( surf, DRAWLIST_SOLID ); + } + else if( FBitSet( surf->flags, SURF_DRAWSKY )) + { + SetBits( RI->view.flags, RF_SKYVISIBLE ); + R_AddSkyBoxSurface( surf ); + } + else if( R_OpaqueEntity( RI->currententity )) + { + R_AddSurfaceToDrawList( surf, DRAWLIST_SOLID ); + } + else + { + R_AddSurfaceToDrawList( surf, DRAWLIST_TRANS ); + } + + // and store faces that required additional pass from another point into separate list + if( FBitSet( surf->flags, SURF_REFLECT|SURF_REFLECT_PUDDLE ) && CVAR_TO_BOOL( r_allow_mirrors )) + { + if( !FBitSet( RI->currentmodel->flags, BIT( 2 )) || tr.waterlevel < 3 ) + R_AddSurfaceToDrawList( surf, DRAWLIST_SUBVIEW ); + } + } + } + } + else if( !FBitSet( RI->params, ( RP_ENVVIEW|RP_SKYVIEW ))) + { + // before process of tr.draw_entities + // we can add muzzleflashes here + R_RunViewmodelEvents(); + + // update entity params for every frame + for( int i = 0; i < tr.num_draw_entities; i++ ) + { + RI->currententity = tr.draw_entities[i]; + RI->currentmodel = RI->currententity->model; + + switch( RI->currentmodel->type ) + { + case mod_brush: + R_UpdateSubmodelParams(); + break; + case mod_studio: + R_AddStudioToDrawList( RI->currententity, true ); + break; + case mod_sprite: + R_AddSpriteToDrawList( RI->currententity, true ); + break; + default: HOST_ERROR( "R_SetupViewCache: mod_bad\n" ); + } + } + } + + // setup dynamic lights + if( !FBitSet( RI->params, RP_DEFERREDLIGHT )) + { + Mod_InitBSPModelsTexture(); + R_SetupDynamicLights(); + } + + // cache client frame + RI->view.client_frame = tr.realframecount; +} + +/* +=============== +R_SetupGLstate + +shared our matrices with gl-machine +=============== +*/ +void R_SetupGLstate( void ) +{ + pglViewport( RI->glstate.viewport[0], RI->glstate.viewport[1], RI->glstate.viewport[2], RI->glstate.viewport[3] ); + + if( RP_NORMALPASS() && tr.sunlight != NULL && CVAR_TO_BOOL( r_pssm_show_split )) + { + GLfloat debugSplitProjection[16], debugSplitModelview[16]; + int split = bound( 0, (int)r_pssm_show_split->value - 1, NUM_SHADOW_SPLITS ); + tr.sunlight->textureMatrix[split].CopyToArray( debugSplitProjection ); + tr.sunlight->modelviewMatrix.CopyToArray( debugSplitModelview ); + + pglMatrixMode( GL_PROJECTION ); + pglLoadMatrixf( debugSplitProjection ); + + pglMatrixMode( GL_MODELVIEW ); + pglLoadMatrixf( debugSplitModelview ); + } + else + { + pglMatrixMode( GL_PROJECTION ); + pglLoadMatrixf( RI->glstate.projectionMatrix ); + + pglMatrixMode( GL_MODELVIEW ); + pglLoadMatrixf( RI->glstate.modelviewMatrix ); + } + + if( FBitSet( RI->params, RP_CLIPPLANE )) + GL_ClipPlane( true ); +} + +/* +=============== +R_ResetGLstate + +disable frame specifics +=============== +*/ +void R_ResetGLstate( void ) +{ + if( FBitSet( RI->params, RP_CLIPPLANE )) + GL_ClipPlane( false ); + + GL_CleanupDrawState(); + GL_AlphaTest( GL_FALSE ); + GL_DepthMask( GL_TRUE ); + GL_Blend( GL_FALSE ); +} + +/* +============= +R_Clear +============= +*/ +void R_Clear( int bitMask ) +{ + int bits = GL_DEPTH_BUFFER_BIT; + + // NOTE: mask should be enabled to properly clear buffer + GL_DepthMask( GL_TRUE ); + + if( FBitSet( RI->params, RP_DRAW_OVERVIEW )) + pglClearColor( 0.0f, 1.0f, 0.0f, 1.0f ); // green background (Valve rules) + else pglClearColor( 0.5f, 0.5f, 0.5f, 1.0f ); + + // force to clearing screen to avoid ugly blur + if( tr.fClearScreen && !CVAR_TO_BOOL( r_clear )) + bits |= GL_COLOR_BUFFER_BIT; + + bits &= bitMask; + + pglClear( bits ); + pglDepthFunc( GL_LEQUAL ); + GL_Cull( GL_FRONT ); + + // change ordering for overview + if( FBitSet( RI->params, RP_DRAW_OVERVIEW )) + { + gldepthmin = 0.8f; + gldepthmax = 0.0f; + } + else + { + gldepthmin = 0.0f; + gldepthmax = 0.8f; + } + + GL_DepthRange( gldepthmin, gldepthmax ); +} + +/* +============= +R_SetupProjectionMatrix +============= +*/ +void R_SetupProjectionMatrix( float fov_x, float fov_y, matrix4x4 &m ) +{ + GLdouble xMax, yMax, zFar; + + zFar = Q_max( 256.0, RI->view.farClip ); + xMax = Z_NEAR * tan( fov_x * M_PI / 360.0 ); + yMax = Z_NEAR * tan( fov_y * M_PI / 360.0 ); + + m.CreateProjection( xMax, -xMax, yMax, -yMax, Z_NEAR, zFar ); +} + +/* +============= +R_DrawParticles + +NOTE: particles are drawing with engine methods +============= +*/ +void R_DrawParticles( qboolean trans ) +{ + ref_viewpass_t rvp; + + if( FBitSet( RI->params, ( RP_ENVVIEW|RP_SKYVIEW ))) + return; + + rvp.viewport[0] = RI->view.port[0]; + rvp.viewport[1] = RI->view.port[1]; + rvp.viewport[2] = RI->view.port[2]; + rvp.viewport[3] = RI->view.port[3]; + + rvp.viewangles = RI->view.angles; + rvp.vieworigin = RI->view.origin; + rvp.fov_x = RI->view.fov_x; + rvp.fov_y = RI->view.fov_y; + rvp.flags = 0; + + if( FBitSet( RI->params, RP_DRAW_WORLD )) + SetBits( rvp.flags, RF_DRAW_WORLD ); + if( FBitSet( RI->params, RP_ENVVIEW )) + SetBits( rvp.flags, RF_DRAW_CUBEMAP ); + if( FBitSet( RI->params, RP_DRAW_OVERVIEW )) + SetBits( rvp.flags, RF_DRAW_OVERVIEW ); + + DRAW_PARTICLES( &rvp, trans, tr.frametime ); +} + +/* +================= +R_SortTransMeshes + +compare translucent meshes +================= +*/ +static int R_SortTransMeshes( const CTransEntry *a, const CTransEntry *b ) +{ + if( a->m_flViewDist > b->m_flViewDist ) + return -1; + if( a->m_flViewDist < b->m_flViewDist ) + return 1; + + return 0; +} + +/* +=============== +R_RenderTransList +=============== +*/ +void R_RenderTransList( void ) +{ + if( !RI->frame.trans_list.Count() ) + return; + + GL_Blend( GL_FALSE ); // mixing screencopy with diffuse so we don't need blend + GL_AlphaTest( GL_FALSE ); + // yes, we rendering translucent objects with enabdled depthwrite + GL_DepthMask( GL_TRUE ); + + if( GL_Support( R_SEAMLESS_CUBEMAP )) + pglEnable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); + + // sorting by distance + RI->frame.trans_list.Sort( R_SortTransMeshes ); + + for( int i = 0; i < RI->frame.trans_list.Count(); i++ ) + { + CTransEntry *entry = &RI->frame.trans_list[i]; + + switch( entry->m_bDrawType ) + { + case DRAWTYPE_SURFACE: + R_RenderTransSurface( entry ); + break; + case DRAWTYPE_MESH: + R_RenderTransMesh( entry ); + break; + case DRAWTYPE_QUAD: + R_RenderQuadPrimitive( entry ); + break; + } + } + + R_RenderDecalsTransList( DRAWLIST_SOLID ); + + R_RenderDynLightList( false ); + R_RenderLightForTransMeshes(); + + R_RenderDecalsTransList( DRAWLIST_TRANS ); + + if( GL_Support( R_SEAMLESS_CUBEMAP )) + pglDisable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); + GL_DepthRange( gldepthmin, gldepthmax ); + GL_CleanupDrawState(); + GL_ClipPlane( true ); + GL_Cull( GL_FRONT ); + + DBG_DrawGlassScissors(); +} + +/* +=============== +R_RenderScene +=============== +*/ +void R_RenderScene( const ref_viewpass_t *rvp, int params ) +{ + int err; + + // now we know about pass specific + RI->params = params; + + // frametime is valid only for normal pass + if( RP_NORMALPASS( )) + tr.frametime = tr.saved_frametime; + else tr.frametime = 0.0; + + R_BuildViewPassHierarchy(); + R_SetupViewCache( rvp ); + + // prepare subview frames + R_RenderSubview(); + + // draw all the shadowmaps + R_RenderShadowmaps(); + + R_SetupGLstate(); + R_Clear( ~0 ); + + R_DrawSkyBox(); + R_RenderSolidBrushList(); + R_RenderSolidStudioList(); + R_RenderSurfOcclusionList(); + R_DrawParticles( false ); + R_RenderDebugStudioList( false ); + + if(( err = pglGetError( )) != GL_NO_ERROR ) + ALERT( at_error, "OpenGL: error %x while render solid objects\n", err ); + + // restore right depthrange here + GL_DepthRange( gldepthmin, gldepthmax ); + + R_RenderTransList(); + R_DrawParticles( true ); + R_DrawWeather(); + + if(( err = pglGetError( )) != GL_NO_ERROR ) + ALERT( at_error, "OpenGL: error %x while render trans objects\n", err ); + + GL_BindShader( NULL ); + R_ResetGLstate(); +} + +/* +=============== +R_RenderDeferredScene +=============== +*/ +void R_RenderDeferredScene( const ref_viewpass_t *rvp, int params ) +{ + int err; + + // now we know about pass specific + RI->params = params; + + R_SetupViewCache( rvp ); + + // draw all the shadowmaps + if( FBitSet( RI->params, RP_DEFERREDSCENE )) + R_RenderShadowmaps(); + + GL_SetupGBuffer(); + R_SetupGLstate(); + R_Clear( ~0 ); + + R_DrawSkyBox(); + R_RenderDeferredBrushList(); + R_RenderDeferredStudioList(); + R_PushRefState(); + RI->params = params; + R_DrawViewModel(); + R_PopRefState(); + GL_CleanupDrawState(); + GL_ResetGBuffer(); + + if(( err = pglGetError( )) != GL_NO_ERROR ) + ALERT( at_error, "OpenGL: error %x while render solid objects\n", err ); + + GL_DrawDeferredPass(); + + R_ResetGLstate(); +} + +/* +=============== +HUD_RenderFrame + +A callback that replaces RenderFrame +engine function. Return 1 if you +override ALL the rendering and return 0 +if we don't want draw from +the client (e.g. playersetup preview) +=============== +*/ +int HUD_RenderFrame( const struct ref_viewpass_s *rvp ) +{ + int refParams = RP_NONE; + ref_viewpass_t defVP = *rvp; + + // setup some renderer flags + if( !FBitSet( rvp->flags, RF_DRAW_CUBEMAP )) + { + if( FBitSet( rvp->flags, RF_DRAW_OVERVIEW )) + SetBits( refParams, RP_DRAW_OVERVIEW ); + + if( cam_thirdperson ) + SetBits( refParams, RP_THIRDPERSON ); + } + else + { + if( world->build_default_cubemap ) + SetBits( refParams, RP_SKYVIEW ); + else SetBits( refParams, RP_ENVVIEW ); + tr.fClearScreen = true; + + // now all uber-shaders are invalidate + // and possible need for recompile + tr.params_changed = true; + tr.glsl_valid_sequence++; + } + + if( FBitSet( rvp->flags, RF_DRAW_WORLD )) + SetBits( refParams, RP_DRAW_WORLD ); + + if( !GL_BackendStartFrame( &defVP, refParams )) + return 0; + + if( CVAR_TO_BOOL( cv_deferred )) + { + if( !CVAR_TO_BOOL( cv_deferred_full )) + { + defVP.viewport[2] = glState.defWidth; + defVP.viewport[3] = glState.defHeight; + R_RenderDeferredScene( &defVP, RP_DEFERREDLIGHT ); + defVP = *rvp; + } + R_RenderDeferredScene( &defVP, RP_DEFERREDSCENE ); + } + else + { + R_RenderScene( &defVP, refParams ); + } + + defVP = *rvp; + + GL_BackendEndFrame( &defVP, refParams ); + + return 1; +} + +void HUD_ProcessModelData( model_t *mod, qboolean create, const byte *buffer ) +{ + if( !g_fRenderInitialized ) + { + // we needs CRC anyway + if( mod->type == mod_studio && create ) + { + // compute model CRC to verify vertexlighting data + // NOTE: source buffer is not equal to Mod_Extradata! + studiohdr_t *src = (studiohdr_t *)buffer; + mod->modelCRC = FILE_CRC32( buffer, src->length ); + } + return; + } + // g-cont. probably this is redundant :-) + if( RENDER_GET_PARM( PARM_DEDICATED_SERVER, 0 )) + return; + + if( FBitSet( mod->flags, MODEL_WORLD )) + R_ProcessWorldData( mod, create, buffer ); + else R_ProcessStudioData( mod, create, buffer ); +} + +BOOL HUD_SpeedsMessage( char *out, size_t size ) +{ + if( !g_fRenderInitialized || !CVAR_TO_BOOL( cv_renderer )) + return false; // let the engine use built-in counters + + if( r_speeds->value <= 0 ) return false; + if( !out || !size ) return false; + + Q_strncpy( out, r_speeds_msg, size ); + + return true; +} + +void HUD_ProcessEntData( qboolean allocate ) +{ + if( allocate ) Mod_PrepareModelInstances(); + else Mod_ThrowModelInstances(); +} + +void HUD_BuildLightmaps( void ) +{ + int i; + + if( !g_fRenderInitialized ) return; + + // put the gamma into GLSL-friendly array + for( i = 0; i < 256; i++ ) + tr.gamma_table[i/4][i%4] = (float)TEXTURE_TO_TEXGAMMA( i ) / 255.0f; + + for( i = 0; i < worldmodel->numsurfaces; i++ ) + SetBits( worldmodel->surfaces[i].flags, SURF_LM_UPDATE|SURF_GRASS_UPDATE ); + + R_StudioClearLightCache(); +} + +// +// Xash3D render interface +// +static render_interface_t gRenderInterface = +{ + CL_RENDER_INTERFACE_VERSION, + HUD_RenderFrame, + HUD_BuildLightmaps, + Mod_SetOrthoBounds, + R_CreateStudioDecalList, + R_ClearStudioDecals, + HUD_SpeedsMessage, + HUD_ProcessModelData, + HUD_ProcessEntData, + Mod_GetCurrentVis, + GL_MapChanged, + R_ClearScene, + R_UpdateLatchedVars, +}; + +int HUD_GetRenderInterface( int version, render_api_t *renderfuncs, render_interface_t *callback ) +{ + if ( !callback || !renderfuncs || version != CL_RENDER_INTERFACE_VERSION ) + { + return FALSE; + } + + size_t iImportSize = sizeof( render_interface_t ); + + // copy new physics interface + memcpy( &gRenderfuncs, renderfuncs, sizeof( render_api_t )); + + // fill engine callbacks + memcpy( callback, &gRenderInterface, iImportSize ); + + // get pointer to movevars + tr.movevars = gEngfuncs.pEventAPI->EV_GetMovevars(); + + g_fRenderInterfaceValid = TRUE; + g_fRenderInitialized = TRUE; + + return TRUE; +} \ No newline at end of file diff --git a/cl_dll/render/gl_rmisc.cpp b/cl_dll/render/gl_rmisc.cpp new file mode 100644 index 0000000..68828e9 --- /dev/null +++ b/cl_dll/render/gl_rmisc.cpp @@ -0,0 +1,1202 @@ +/* +gl_rmisc.cpp - renderer miscellaneous +Copyright (C) 2013 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include "cl_util.h" +#include "gl_local.h" +#include "gl_studio.h" +#include "gl_aurora.h" +#include "gl_rpart.h" +#include "material.h" +#include "gl_occlusion.h" +#include "gl_world.h" +#include "gl_grass.h" +#include "gl_shader.h" + +#define DEFAULT_SMOOTHNESS 0.35f +#define FILTER_SIZE 2 + +/* +================== +CL_LoadMaterials + +parse material from a given file +================== +*/ +void CL_LoadMaterials( const char *path ) +{ + ALERT( at_aiconsole, "loading %s\n", path ); + + char *afile = (char *)gEngfuncs.COM_LoadFile( (char *)path, 5, NULL ); + + if( !afile ) + { + ALERT( at_error, "Cannot open file \"%s\"\n", path ); + return; + } + + matdesc_t *oldmaterials = tr.materials; + int oldcount = tr.matcount; + char *pfile = afile; + char token[256]; + int depth = 0; + float flValue; + + // count materials + while( pfile != NULL ) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + + if( Q_strlen( token ) > 1 ) + continue; + + if( token[0] == '{' ) + { + depth++; + } + else if( token[0] == '}' ) + { + tr.matcount++; + depth--; + } + } + + if( depth > 0 ) ALERT( at_warning, "%s: EOF reached without closing brace\n", path ); + if( depth < 0 ) ALERT( at_warning, "%s: EOF reached without opening brace\n", path ); + + tr.materials = (matdesc_t *)Mem_Alloc( sizeof( matdesc_t ) * tr.matcount ); + memcpy( tr.materials, oldmaterials, oldcount * sizeof( matdesc_t )); + Mem_Free( oldmaterials ); + pfile = afile; // start real parsing + + int current = oldcount; // starts from + + while( pfile != NULL ) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + + if( current >= tr.matcount ) + { + ALERT ( at_error, "material parse is overrun %d > %d\n", current, tr.matcount ); + break; + } + + matdesc_t *mat = &tr.materials[current]; + + // read the material name + Q_strncpy( mat->name, token, sizeof( mat->name )); + COM_StripExtension( mat->name ); + + for( int i = 0; i < current; i++ ) + { + if( !Q_stricmp( mat->name, tr.materials[i].name )) + { + ALERT( at_warning, "mat %s was duplicated, second definition will be ignored\n", mat->name ); + break; + } + } + + // set defaults + mat->dt_texturenum = tr.grayTexture; + mat->detailScale[0] = 10.0f; + mat->detailScale[1] = 10.0f; + mat->smoothness = DEFAULT_SMOOTHNESS; + + // read opening brace + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + + if( token[0] != '{' ) + { + ALERT( at_error, "found %s when expecting {\n", token ); + break; + } + + while( pfile != NULL ) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "EOF without closing brace\n" ); + goto getout; + } + + // description end goto next material + if( token[0] == '}' ) + { + current++; + break; + } + else if( !Q_stricmp( token, "glossExp" )) // for backward compatibility + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'glossExp'\n" ); + goto getout; + } + + flValue = Q_atof( token ); + flValue = bound( 0.0f, flValue, 256.0f ); + mat->smoothness = sqrt( flValue / 256.0f ); + } + else if( !Q_stricmp( token, "smoothness" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'smoothness'\n" ); + goto getout; + } + + mat->smoothness = Q_atof( token ); + mat->smoothness = bound( 0.0f, mat->smoothness, 1.0f ); + } + else if( !Q_stricmp( token, "detailScale" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'detailScale'\n" ); + goto getout; + } + + Q_atov( mat->detailScale, token, 2 ); + mat->detailScale[0] = bound( 0.01f, mat->detailScale[0], 100.0f ); + mat->detailScale[1] = bound( 0.01f, mat->detailScale[0], 100.0f ); + } + else if( !Q_stricmp( token, "detailmap" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'detailmap'\n" ); + goto getout; + } + + COM_FixSlashes( token ); + mat->dt_texturenum = LOAD_TEXTURE( token, NULL, 0, TF_FORCE_COLOR ); + } + else if( !Q_stricmp( token, "diffuseMap" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'diffusemap'\n" ); + goto getout; + } + + COM_FixSlashes( token ); + Q_strncpy( mat->diffusemap, token, sizeof( mat->diffusemap )); + } + else if( !Q_stricmp( token, "normalMap" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'normalmap'\n" ); + goto getout; + } + + COM_FixSlashes( token ); + Q_strncpy( mat->normalmap, token, sizeof( mat->normalmap )); + } + else if( !Q_stricmp( token, "glossMap" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'glossmap'\n" ); + goto getout; + } + + COM_FixSlashes( token ); + Q_strncpy( mat->glossmap, token, sizeof( mat->glossmap )); + } + else if( !Q_stricmp( token, "AberrationScale" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'AberrationScale'\n" ); + goto getout; + } + + mat->aberrationScale = Q_atof( token ) * 0.1f; + mat->aberrationScale = bound( 0.0f, mat->aberrationScale, 0.1f ); + } + else if( !Q_stricmp( token, "ReflectScale" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'ReflectScale'\n" ); + goto getout; + } + + mat->reflectScale = Q_atof( token ); + mat->reflectScale = bound( 0.0f, mat->reflectScale, 1.0f ); + } + else if( !Q_stricmp( token, "RefractScale" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'RefractScale'\n" ); + goto getout; + } + + mat->refractScale = Q_atof( token ); + mat->refractScale = bound( 0.0f, mat->refractScale, 1.0f ); + } + else if( !Q_stricmp( token, "ReliefScale" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'ReliefScale'\n" ); + goto getout; + } + + mat->reliefScale = Q_atof( token ); + mat->reliefScale = bound( 0.0f, mat->reliefScale, 1.0f ); + } + else if( !Q_stricmp( token, "material" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'material'\n" ); + goto getout; + } + + mat->effects = COM_FindMatdef( token ); + } + else ALERT( at_warning, "Unknown material token %s\n", token ); + } + + // apply default values + if( !mat->effects ) mat->effects = COM_DefaultMatdef(); + + if( mat->dt_texturenum == tr.grayTexture && mat->effects->detailName[0] ) + { + mat->dt_texturenum = LOAD_TEXTURE( mat->effects->detailName, NULL, 0, TF_FORCE_COLOR ); + mat->detailScale[0] = mat->effects->detailScale[0]; + mat->detailScale[1] = mat->effects->detailScale[0]; + } + + // if detail texture was missed + if( mat->dt_texturenum == 0 ) + mat->dt_texturenum = tr.grayTexture; + } +getout: + gEngfuncs.COM_FreeFile( afile ); + ALERT( at_aiconsole, "%d materials parsed\n", current ); +} + +/* +================== +CL_InitMaterials + +parse all files with .mat extension +================== +*/ +void CL_InitMaterials( void ) +{ + int count = 0; + char **filenames = FS_SEARCH( "scripts/*.mat", &count, 0 ); + + tr.matcount = 0; + + // sequentially load materials + for( int i = 0; i < count; i++ ) + CL_LoadMaterials( filenames[i] ); + +} + +/* +================== +CL_FindMaterial + +This function never failed +================== +*/ +matdesc_t *CL_FindMaterial( const char *name ) +{ + static matdesc_t defmat; + + if( !defmat.name[0] ) + { + // initialize default material + Q_strncpy( defmat.name, "*default", sizeof( defmat.name )); + defmat.dt_texturenum = tr.grayTexture; + defmat.detailScale[0] = 10.0f; + defmat.detailScale[1] = 10.0f; + defmat.aberrationScale = 0.0f; + defmat.reflectScale = 0.0f; + defmat.refractScale = 0.0f; + defmat.reliefScale = 0.0f; + defmat.smoothness = DEFAULT_SMOOTHNESS; // same as glossExp 32.0 + defmat.effects = COM_DefaultMatdef(); + } + + for( int i = 0; i < tr.matcount; i++ ) + { + if( !Q_stricmp( name, tr.materials[i].name )) + return &tr.materials[i]; + } + + return &defmat; +} + +/* +======================== +LoadHeightMap + +parse heightmap pixels and remap it +to real layer count +======================== +*/ +bool LoadHeightMap( indexMap_t *im, int numLayers ) +{ + unsigned int *src; + int i, tex; + int depth = 1; + + if( !GL_Support( R_TEXTURE_ARRAY_EXT ) || numLayers <= 0 ) + return false; + + // loading heightmap and keep the source pixels + if( !( tex = LOAD_TEXTURE( im->name, NULL, 0, TF_KEEP_SOURCE|TF_EXPAND_SOURCE ))) + return false; + + if(( src = (unsigned int *)GET_TEXTURE_DATA( tex )) == NULL ) + { + ALERT( at_error, "LoadHeightMap: couldn't get source pixels for %s\n", im->name ); + FREE_TEXTURE( tex ); + return false; + } + + im->gl_diffuse_id = LOAD_TEXTURE( im->diffuse, NULL, 0, 0 ); + + int width = RENDER_GET_PARM( PARM_TEX_SRC_WIDTH, tex ); + int height = RENDER_GET_PARM( PARM_TEX_SRC_HEIGHT, tex ); + + im->pixels = (byte *)Mem_Alloc( width * height ); + im->numLayers = bound( 1, numLayers, 255 ); + im->height = height; + im->width = width; + + for( i = 0; i < ( im->width * im->height ); i++ ) + { + byte rawHeight = ( src[i] & 0xFF ); + im->maxHeight = Q_max(( 16 * (int)ceil( rawHeight / 16 )), im->maxHeight ); + } + + // merge layers count + im->numLayers = (im->maxHeight / 16) + 1; + depth = Q_max((int)Q_ceil((float)im->numLayers / 4.0f ), 1 ); + + // clamp to layers count + for( i = 0; i < ( im->width * im->height ); i++ ) + im->pixels[i] = (( src[i] & 0xFF ) * ( im->numLayers - 1 )) / im->maxHeight; + + size_t lay_size = im->width * im->height * 4; + size_t img_size = lay_size * depth; + byte *layers = (byte *)Mem_Alloc( img_size ); + byte *pixels = (byte *)src; + + for( int x = 0; x < im->width; x++ ) + { + for( int y = 0; y < im->height; y++ ) + { + float weights[MAX_LANDSCAPE_LAYERS]; + + memset( weights, 0, sizeof( weights )); + + for( int pos_x = 0; pos_x < FILTER_SIZE; pos_x++ ) + { + for( int pos_y = 0; pos_y < FILTER_SIZE; pos_y++ ) + { + int img_x = (x - (FILTER_SIZE / 2) + pos_x + im->width) % im->width; + int img_y = (y - (FILTER_SIZE / 2) + pos_y + im->height) % im->height; + + float rawHeight = (float)( src[img_y * im->width + img_x] & 0xFF ); + float curLayer = ( rawHeight * ( im->numLayers - 1 )) / (float)im->maxHeight; + + if( curLayer != (int)curLayer ) + { + byte layer0 = (int)floor( curLayer ); + byte layer1 = (int)ceil( curLayer ); + float factor = curLayer - (int)curLayer; + weights[layer0] += (1.0 - factor) * (1.0 / (FILTER_SIZE * FILTER_SIZE)); + weights[layer1] += (factor ) * (1.0 / (FILTER_SIZE * FILTER_SIZE)); + } + else + { + weights[(int)curLayer] += (1.0 / (FILTER_SIZE * FILTER_SIZE)); + } + } + } + + // encode layers into RGBA channels + layers[lay_size * 0 + (y * im->width + x)*4+0] = weights[0] * 255; + layers[lay_size * 0 + (y * im->width + x)*4+1] = weights[1] * 255; + layers[lay_size * 0 + (y * im->width + x)*4+2] = weights[2] * 255; + layers[lay_size * 0 + (y * im->width + x)*4+3] = weights[3] * 255; + + if( im->numLayers <= 4 ) continue; + + layers[lay_size * 1 + ((y * im->width + x)*4+0)] = weights[4] * 255; + layers[lay_size * 1 + ((y * im->width + x)*4+1)] = weights[5] * 255; + layers[lay_size * 1 + ((y * im->width + x)*4+2)] = weights[6] * 255; + layers[lay_size * 1 + ((y * im->width + x)*4+3)] = weights[7] * 255; + + if( im->numLayers <= 8 ) continue; + + layers[lay_size * 2 + ((y * im->width + x)*4+0)] = weights[8] * 255; + layers[lay_size * 2 + ((y * im->width + x)*4+1)] = weights[9] * 255; + layers[lay_size * 2 + ((y * im->width + x)*4+2)] = weights[10] * 255; + layers[lay_size * 2 + ((y * im->width + x)*4+3)] = weights[11] * 255; + + if( im->numLayers <= 12 ) continue; + + layers[lay_size * 3 + ((y * im->width + x)*4+0)] = weights[12] * 255; + layers[lay_size * 3 + ((y * im->width + x)*4+1)] = weights[13] * 255; + layers[lay_size * 3 + ((y * im->width + x)*4+2)] = weights[14] * 255; + layers[lay_size * 3 + ((y * im->width + x)*4+3)] = weights[15] * 255; + } + } + + // release source texture + FREE_TEXTURE( tex ); + + tex = CREATE_TEXTURE_ARRAY( im->name, im->width, im->height, depth, layers, TF_CLAMP|TF_HAS_ALPHA ); + Mem_Free( layers ); + + im->gl_heightmap_id = tex; + + return true; +} + +/* +======================== +LoadTerrainLayers + +loading all the landscape layers +into texture arrays +======================== +*/ +bool LoadTerrainLayers( layerMap_t *lm, int numLayers ) +{ + char *texnames[MAX_LANDSCAPE_LAYERS]; + char *ptr, buffer[1024], tmpname[64]; + size_t nameLen = 64; + int i; + + memset( buffer, 0, sizeof( buffer )); // list must be null terminated + + // initialize names array + for( i = 0, ptr = buffer; i < MAX_LANDSCAPE_LAYERS; i++, ptr += nameLen ) + texnames[i] = ptr; + + // process diffuse textures + for( i = 0; i < numLayers; i++ ) + { + Q_snprintf( texnames[i], nameLen, "textures/%s", lm->pathes[i] ); + lm->material[i] = CL_FindMaterial( lm->names[i] ); + lm->smoothness[i] = lm->material[i]->smoothness; + } + + if(( lm->gl_diffuse_id = LOAD_TEXTURE_ARRAY( (const char **)texnames, 0 )) == 0 ) + return false; + + memset( buffer, 0, sizeof( buffer )); // list must be null terminated + + // process normalmaps + for( i = 0; i < numLayers; i++ ) + { + const char *ext = COM_FileExtension( lm->pathes[i] ); + COM_FileBase( lm->pathes[i], tmpname ); + + if( !Q_stricmp( ext, "" )) + { + Q_snprintf( texnames[i], nameLen, "textures/%s_norm", tmpname ); + if( IMAGE_EXISTS( texnames[i] )) + continue; + + ALERT( at_warning, "layer normalmap %s is missed\n", texnames[i] ); + break; + } + else + { + Q_snprintf( texnames[i], nameLen, "textures/%s_norm.%s", tmpname, ext ); + if( FILE_EXISTS( texnames[i] )) + continue; + + ALERT( at_warning, "layer normalmap %s is missed\n", texnames[i] ); + break; + } + } + + if( i == numLayers ) lm->gl_normalmap_id = LOAD_TEXTURE_ARRAY( (const char **)texnames, TF_NORMALMAP ); + if( !lm->gl_normalmap_id ) lm->gl_normalmap_id = tr.normalmapTexture; + + memset( buffer, 0, sizeof( buffer )); // list must be null terminated + + // process glossmaps + for( i = 0; i < numLayers; i++ ) + { + const char *ext = COM_FileExtension( lm->pathes[i] ); + COM_FileBase( lm->pathes[i], tmpname ); + + if( !Q_stricmp( ext, "" )) + { + Q_snprintf( texnames[i], nameLen, "textures/%s_gloss", tmpname ); + if( IMAGE_EXISTS( texnames[i] )) + continue; + + ALERT( at_warning, "layer glossmap %s is missed\n", texnames[i] ); + break; + } + else + { + Q_snprintf( texnames[i], nameLen, "textures/%s_gloss.%s", tmpname, ext ); + if( FILE_EXISTS( texnames[i] )) + continue; + + ALERT( at_warning, "layer glossmap %s is missed\n", texnames[i] ); + break; + } + } + + if( i == numLayers ) lm->gl_specular_id = LOAD_TEXTURE_ARRAY( (const char **)texnames, 0 ); + if( !lm->gl_specular_id ) lm->gl_specular_id = tr.blackTexture; + + return true; +} + +/* +======================== +R_FreeLandscapes + +free the landscape definitions +======================== +*/ +void R_FreeLandscapes( void ) +{ + for( int i = 0; i < world->num_terrains; i++ ) + { + terrain_t *terra = &world->terrains[i]; + indexMap_t *im = &terra->indexmap; + if( im->pixels ) Mem_Free( im->pixels ); + layerMap_t *lm = &terra->layermap; + + if( lm->gl_diffuse_id ) + FREE_TEXTURE( lm->gl_diffuse_id ); + + if( lm->gl_normalmap_id != tr.normalmapTexture ) + FREE_TEXTURE( lm->gl_normalmap_id ); + + if( lm->gl_specular_id != tr.blackTexture ) + FREE_TEXTURE( lm->gl_specular_id ); + + if( im->gl_diffuse_id != 0 ) + FREE_TEXTURE( im->gl_diffuse_id ); + + FREE_TEXTURE( im->gl_heightmap_id ); + } + + if( world->terrains ) Mem_Free( world->terrains ); + + world->num_terrains = 0; + world->terrains = NULL; +} + +/* +======================== +R_LoadLandscapes + +load the landscape definitions +======================== +*/ +void R_LoadLandscapes( const char *filename ) +{ + char filepath[256]; + + Q_snprintf( filepath, sizeof( filepath ), "maps/%s_land.txt", filename ); + + char *afile = (char *)gEngfuncs.COM_LoadFile( filepath, 5, NULL ); + if( !afile ) return; + + ALERT( at_aiconsole, "loading %s\n", filepath ); + + char *pfile = afile; + char token[256]; + int depth = 0; + + // count materials + while( pfile != NULL ) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + + if( Q_strlen( token ) > 1 ) + continue; + + if( token[0] == '{' ) + { + depth++; + } + else if( token[0] == '}' ) + { + world->num_terrains++; + depth--; + } + } + + if( depth > 0 ) ALERT( at_warning, "%s: EOF reached without closing brace\n", filepath ); + if( depth < 0 ) ALERT( at_warning, "%s: EOF reached without opening brace\n", filepath ); + + world->terrains = (terrain_t *)Mem_Alloc( sizeof( terrain_t ) * world->num_terrains ); + pfile = afile; // start real parsing + + int current = 0; + + while( pfile != NULL ) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + + if( current >= world->num_terrains ) + { + ALERT ( at_error, "landscape parse is overrun %d > %d\n", current, world->num_terrains ); + break; + } + + terrain_t *terra = &world->terrains[current]; + + // read the landscape name + Q_strncpy( terra->name, token, sizeof( terra->name )); + terra->texScale = 1.0f; + + // read opening brace + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + + if( token[0] != '{' ) + { + ALERT( at_error, "found %s when expecting {\n", token ); + break; + } + + while( pfile != NULL ) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "EOF without closing brace\n" ); + goto land_getout; + } + + // description end goto next material + if( token[0] == '}' ) + { + current++; + break; + } + else if( !Q_stricmp( token, "indexMap" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'indexMap'\n" ); + goto land_getout; + } + + Q_strncpy( terra->indexmap.name, token, sizeof( terra->indexmap.name )); + } + else if( !Q_stricmp( token, "diffuseMap" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'diffuseMap'\n" ); + goto land_getout; + } + + Q_strncpy( terra->indexmap.diffuse, token, sizeof( terra->indexmap.diffuse )); + } + else if( !Q_strnicmp( token, "layer", 5 )) + { + int layerNum = Q_atoi( token + 5 ); + + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'layer'\n" ); + goto land_getout; + } + + if( layerNum < 0 || layerNum > ( MAX_LANDSCAPE_LAYERS - 1 )) + { + ALERT( at_error, "%s is out of range. Ignored\n", token ); + } + else + { + Q_strncpy( terra->layermap.pathes[layerNum], token, sizeof( terra->layermap.pathes[0] )); + COM_FileBase( terra->layermap.pathes[layerNum], terra->layermap.names[layerNum] ); + } + + terra->numLayers = Q_max( terra->numLayers, layerNum + 1 ); + } + else if( !Q_stricmp( token, "tessSize" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'tessSize'\n" ); + goto land_getout; + } + + terra->tessSize = Q_atoi( token ); + terra->tessSize = bound( 8, terra->tessSize, 256 ); + } + else if( !Q_stricmp( token, "texScale" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'texScale'\n" ); + goto land_getout; + } + + terra->texScale = Q_atof( token ); + terra->texScale = 1.0 / (bound( 0.000001f, terra->texScale, 16.0f )); + } + else ALERT( at_warning, "Unknown landscape token %s\n", token ); + } + + if( LoadHeightMap( &terra->indexmap, terra->numLayers )) + { + if( LoadTerrainLayers( &terra->layermap, terra->numLayers )) + terra->valid = true; // all done + } + } + +land_getout: + gEngfuncs.COM_FreeFile( afile ); + ALERT( at_console, "%d landscapes parsed\n", current ); +} + +/* +======================== +R_FindTerrain + +find the terrain description +======================== +*/ +terrain_t *R_FindTerrain( const char *texname ) +{ + for( int i = 0; i < world->num_terrains; i++ ) + { + if( !Q_stricmp( texname, world->terrains[i].name ) && world->terrains[i].valid ) + return &world->terrains[i]; + } + + return NULL; +} + +/* +================== +R_InitShadowTextures + +Re-create shadow textures +if them size was changed +================== +*/ +void R_InitShadowTextures( void ) +{ + int i; + + for( i = 0; i < MAX_SHADOWS; i++ ) + { + if( !tr.shadowTextures[i] ) break; + FREE_TEXTURE( tr.shadowTextures[i] ); + } + + for( i = 0; i < MAX_SHADOWS; i++ ) + { + if( !tr.shadowCubemaps[i] ) break; + FREE_TEXTURE( tr.shadowCubemaps[i] ); + } + + memset( tr.shadowTextures, 0, sizeof( tr.shadowTextures )); + memset( tr.shadowCubemaps, 0, sizeof( tr.shadowCubemaps )); + + int base_shadow_size = bound( 128, r_shadowmap_size->value, 2048 ); + + int shadow_size2D = bound( 128, NearestPOW( base_shadow_size, true ), 2048 ); + tr.fbo_shadow2D.Init( FBO_DEPTH, shadow_size2D, shadow_size2D, FBO_NOTEXTURE ); + + int shadow_sizeCM = bound( 128, NearestPOW( base_shadow_size * 0.5f, true ), 1024 ); + tr.fbo_shadowCM.Init( FBO_DEPTH, shadow_sizeCM, shadow_sizeCM, FBO_NOTEXTURE ); +} + +/* +================== +R_InitCommonTextures + +Init common textures for renderer that size +based on screen size +================== +*/ +void R_InitCommonTextures( void ) +{ + int i; + + GL_BindFBO( 0 ); + R_InitShadowTextures(); + + // release old subview textures + for( i = 0; i < MAX_SUBVIEW_TEXTURES; i++ ) + { + if( !tr.subviewTextures[i].texturenum ) break; + FREE_TEXTURE( tr.subviewTextures[i].texturenum ); + } + + memset( tr.subviewTextures, 0, sizeof( tr.subviewTextures )); + + for( i = 0; i < tr.num_framebuffers; i++ ) + { + if( !tr.frame_buffers[i].init ) + break; + R_FreeFrameBuffer( i ); + } + + for( i = 0; i < MAX_SHADOWMAPS; i++ ) + { + tr.sunShadowFBO[i].Init( FBO_DEPTH, sunSize[i], sunSize[i] ); + } + + tr.fbo_light.Init( FBO_COLOR, glState.width, glState.height, FBO_LINEAR ); + tr.fbo_filter.Init( FBO_COLOR, glState.width, glState.height, FBO_LINEAR ); + tr.fbo_shadow.Init( FBO_COLOR, glState.defWidth, glState.defHeight, FBO_LINEAR ); + + // setup the skybox sides + for( i = 0; i < 6; i++ ) + tr.skyboxTextures[i] = RENDER_GET_PARM( PARM_TEX_SKYBOX, i ); + + tr.num_framebuffers = 0; + tr.num_subview_used = 0; + tr.glsl_valid_sequence++; // refresh shader cache + + InitPostTextures(); +} + +/* +==================== +ED_ParseEdict + +Parses an edict out of the given string, returning the new position +ed should be a properly initialized empty edict. +==================== +*/ +void ED_ParseEdict( char **pfile ) +{ + int vertex_light_cache = -1; + int surface_light_cache = -1; + char modelname[64]; + char token[2048]; + + // go through all the dictionary pairs + while( 1 ) + { + char keyname[256]; + + // parse key + if(( *pfile = COM_ParseFile( *pfile, token )) == NULL ) + HOST_ERROR( "ED_ParseEdict: EOF without closing brace\n" ); + + if( token[0] == '}' ) break; // end of desc + + Q_strncpy( keyname, token, sizeof( keyname )); + + // parse value + if(( *pfile = COM_ParseFile( *pfile, token )) == NULL ) + HOST_ERROR( "ED_ParseEdict: EOF without closing brace\n" ); + + if( token[0] == '}' ) + HOST_ERROR( "ED_ParseEdict: closing brace without data\n" ); + + // ignore attempts to set key "" + if( !keyname[0] ) continue; + + // "wad" field is completely ignored in XashXT + if( !Q_strcmp( keyname, "wad" )) + continue; + + // ignore attempts to set value "" + if( !token[0] ) continue; + + // also grab light settings from p2rad + if( !Q_strcmp( keyname, "_lightgamma" )) + tr.light_gamma = Q_atof( token ); + if( !Q_strcmp( keyname, "_dscale" )) + tr.direct_scale = Q_atof( token ); + if( !Q_strcmp( keyname, "_maxlight" )) + tr.light_threshold = Q_atof( token ); + if( !Q_strcmp( keyname, "_ambient" )) + Q_atov( tr.ambient_color, token, 3 ); + if( !Q_strcmp( keyname, "_smooth" )) + tr.smoothing_threshold = cos( DEG2RAD( Q_atof( token ))); + + // only two fields that we needed + if( !Q_strcmp( keyname, "model" )) + Q_strncpy( modelname, token, sizeof( modelname )); + + if( !Q_strcmp( keyname, "vlight_cache" )) + vertex_light_cache = atoi( token ); + if( !Q_strcmp( keyname, "flight_cache" )) + surface_light_cache = atoi( token ); + } + + // deal with light cache + double start = Sys_DoubleTime(); + if( vertex_light_cache > 0 && vertex_light_cache < MAX_LIGHTCACHE ) + g_StudioRenderer.CreateStudioCacheVL( modelname, vertex_light_cache - 1 ); + if( surface_light_cache > 0 && surface_light_cache < MAX_LIGHTCACHE ) + g_StudioRenderer.CreateStudioCacheFL( modelname, surface_light_cache - 1 ); + double end = Sys_DoubleTime(); + + r_buildstats.create_light_cache += (end - start); + r_buildstats.total_buildtime += (end - start); +} + +/* +================== +GL_InitModelLightCache + +create VBO cache for vertex-lit and lightmapped studio models +================== +*/ +void GL_InitModelLightCache( void ) +{ + char *entities = worldmodel->entities; + static char worldname[64]; + char token[2048]; + + if( !Q_stricmp( world->name, worldname )) + return; // just a restart + + Q_strncpy( worldname, world->name, sizeof( worldname )); + RI->currententity = GET_ENTITY( 0 ); // to have something valid here + + // parse ents to find vertex light cache + while(( entities = COM_ParseFile( entities, token )) != NULL ) + { + if( token[0] != '{' ) + HOST_ERROR( "ED_LoadFromFile: found %s when expecting {\n", token ); + + ED_ParseEdict( &entities ); + } + + // create lightmap pages right after studiomodel alloc their lightmaps (empty at this moment) + GL_EndBuildingLightmaps( (worldmodel->lightdata != NULL), FBitSet( world->features, WORLD_HAS_DELUXEMAP ) ? true : false ); +} + +/* +================== +R_NewMap + +Called always when map is changed or restarted +================== +*/ +void R_NewMap( void ) +{ + // setup special flags + for( int i = 0; i < worldmodel->numsurfaces; i++ ) + { + msurface_t *surf = &worldmodel->surfaces[i]; + mextrasurf_t *info = surf->info; + + // clear the in-game set flags + ClearBits( surf->flags, SURF_NODRAW|SURF_NODLIGHT|SURF_NOSUNLIGHT|SURF_REFLECT_PUDDLE ); + + // clear occlusion queries results + ClearBits( surf->flags, SURF_QUEUED|SURF_OCCLUDED ); + + memset( info->subtexture, 0, sizeof( info->subtexture )); + info->cubemap[0] = &world->defaultCubemap; + info->cubemap[1] = &world->defaultCubemap; + info->checkcount = -1; + } + + // we need to reapply cubemaps to surfaces after restart level + if( world->num_cubemaps > 0 ) + world->loading_cubemaps = true; + + // reset sky settings + tr.sky_origin = tr.sky_world_origin = g_vecZero; + tr.sky_speed = 0; + + // reset light settings + tr.light_gamma = 0.5; + tr.direct_scale = 2.0; + tr.light_threshold = 196; + tr.ambient_color = g_vecZero; + tr.smoothing_threshold = 0.642788f; // 50 degrees +} + +/* +================== +R_InitDynLightShader + +changed dynamic lighting shaders +================== +*/ +void R_InitDynLightShader( int type ) +{ + char options[MAX_OPTIONS_LENGTH]; + + switch( type ) + { + case LIGHT_SPOT: + GL_SetShaderDirective( options, "LIGHT_SPOT" ); + break; + case LIGHT_OMNI: + GL_SetShaderDirective( options, "LIGHT_OMNI" ); + break; + default: return; + break; + } + + if( CVAR_TO_BOOL( cv_brdf )) + GL_AddShaderDirective( options, "APPLY_PBS" ); + + if( CVAR_TO_BOOL( cv_specular )) + GL_AddShaderDirective( options, "HAS_GLOSSMAP" ); + + if( CVAR_TO_BOOL( r_shadows )) + { + // shadow cubemaps only support if GL_EXT_gpu_shader4 is support + if( type == LIGHT_SPOT || GL_Support( R_EXT_GPU_SHADER4 )) + { + GL_AddShaderDirective( options, "APPLY_SHADOW" ); + + if( r_shadows->value == 2.0f ) + GL_AddShaderDirective( options, "SHADOW_PCF2X2" ); + else if( r_shadows->value >= 3.0f ) + GL_AddShaderDirective( options, "SHADOW_PCF3X3" ); + } + } + + tr.defDynLightShader[type] = GL_FindShader( "deferred/dynlight", "deferred/generic", "deferred/dynlight", options ); +} + +/* +================== +R_InitDynLightShaders + +changed dynamic lighting shaders +================== +*/ +void R_InitDynLightShaders( void ) +{ + char options[MAX_OPTIONS_LENGTH]; + + GL_CleanupDrawState(); + + R_InitDynLightShader( LIGHT_SPOT ); + R_InitDynLightShader( LIGHT_OMNI ); + + options[0] = '\0'; + + if( CVAR_TO_BOOL( cv_deferred_tracebmodels )) + GL_AddShaderDirective( options, "BSPTRACE_BMODELS" ); + + // init deferred shaders + tr.defSceneShader[0] = GL_FindShader( "deferred/scene_sep", "deferred/generic", "deferred/scene_sep", options ); + tr.defSceneShader[1] = GL_FindShader( "deferred/scene_all", "deferred/generic", "deferred/scene_all", options ); + tr.defLightShader = GL_FindShader( "deferred/light", "deferred/generic", "deferred/light", options ); + tr.bilateralShader = GL_FindUberShader( "deferred/bilateral" ); +} + +/* +================== +R_VidInit + +Called always when "vid_mode" or "fullscreen" cvars is changed +================== +*/ +void R_VidInit( void ) +{ + // get the actual screen size + glState.width = RENDER_GET_PARM( PARM_SCREEN_WIDTH, 0 ); + glState.height = RENDER_GET_PARM( PARM_SCREEN_HEIGHT, 0 ); + + // TESTTEST + glState.defWidth = 256; + glState.defHeight = 192; + + R_InitCommonTextures(); + GL_VidInitDrawBuffers(); +} + +/* +================== +GL_MapChanged + +Called always when map is changed or restarted +================== +*/ +void GL_MapChanged( void ) +{ + if( !g_fRenderInitialized ) + return; + + R_InitRefState(); + + tr.glsl_valid_sequence = 1; + tr.grassunloadframe = 0; + tr.fClearScreen = false; + tr.realframecount = 1; + tr.num_cin_used = 0; + + // catch changes of screen + R_VidInit (); + + g_pParticleSystems.ClearSystems(); // buz + + g_pParticles.Clear(); + + CL_ClearDlights(); + + ClearDecals(); + + ResetRain(); + + // don't flush shaders for each map - save time to recompile + if( num_glsl_programs >= ( MAX_GLSL_PROGRAMS * 0.9f )) + GL_FreeUberShaders(); + + R_NewMap (); // tell the renderer what a new map started + + g_StudioRenderer.VidInit(); + + GL_InitModelLightCache(); +} \ No newline at end of file diff --git a/cl_dll/render/gl_rpart.cpp b/cl_dll/render/gl_rpart.cpp new file mode 100644 index 0000000..43073ba --- /dev/null +++ b/cl_dll/render/gl_rpart.cpp @@ -0,0 +1,1181 @@ +/* +gl_rpart.cpp - quake-like particle system +Copyright (C) 2014 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include +#include "gl_local.h" +#include "com_model.h" +#include "r_studioint.h" +#include "gl_rpart.h" +#include "pm_defs.h" +#include "event_api.h" +#include "triangleapi.h" +#include "gl_sprite.h" + +CQuakePartSystem g_pParticles; + +bool CQuakePart :: Evaluate( float gravity ) +{ + Vector org, org2, org3, vel; + + float time = ( tr.time - m_flTime ); + float time2 = time * time; + + float curAlpha = m_flAlpha + m_flAlphaVelocity * time; + float curRadius = m_flRadius + m_flRadiusVelocity * time; + float curLength = m_flLength + m_flLengthVelocity * time; + + if( curAlpha <= 0.0f || curRadius <= 0.0f || curLength <= 0.0f ) + { + // faded out + return false; + } + + Vector curColor = m_vecColor + m_vecColorVelocity * time; + + org.x = m_vecOrigin.x + m_vecVelocity.x * time + m_vecAccel.x * time2; + org.y = m_vecOrigin.y + m_vecVelocity.y * time + m_vecAccel.y * time2; + org.z = m_vecOrigin.z + m_vecVelocity.z * time + m_vecAccel.z * time2 * gravity; + + if( FBitSet( m_iFlags, FPART_UNDERWATER )) + { + // underwater particle + org2 = Vector( org.x, org.y, org.z + curRadius ); + + int contents = POINT_CONTENTS( org ); + + if( contents != CONTENTS_WATER && contents != CONTENTS_SLIME && contents != CONTENTS_LAVA ) + { + // not underwater + return false; + } + } + + if( FBitSet( m_iFlags, FPART_FRICTION )) + { + // water friction affected particle + int contents = POINT_CONTENTS( org ); + + if( contents <= CONTENTS_WATER && contents >= CONTENTS_LAVA ) + { + // add friction + switch( contents ) + { + case CONTENTS_WATER: + m_vecVelocity *= 0.25f; + m_vecAccel *= 0.25f; + break; + case CONTENTS_SLIME: + m_vecVelocity *= 0.20f; + m_vecAccel *= 0.20f; + break; + case CONTENTS_LAVA: + m_vecVelocity *= 0.10f; + m_vecAccel *= 0.10f; + break; + } + + // don't add friction again + m_iFlags &= ~FPART_FRICTION; + curLength = 1.0f; + + // reset + m_flTime = tr.time; + m_vecColor = curColor; + m_flAlpha = curAlpha; + m_flRadius = curRadius; + m_vecOrigin = org; + + // don't stretch + m_iFlags &= ~FPART_STRETCH; + m_flLengthVelocity = 0.0f; + m_flLength = curLength; + } + } + + if( FBitSet( m_iFlags, FPART_BOUNCE )) + { + // bouncy particle + pmtrace_t pmtrace; + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PlayerTrace( m_vecLastOrg, org, PM_STUDIO_IGNORE, -1, &pmtrace ); + + if( pmtrace.fraction != 1.0f ) + { + // reflect velocity + time = tr.time - (tr.frametime + tr.frametime * pmtrace.fraction); + time = (time - m_flTime); + + vel.x = m_vecVelocity.x; + vel.y = m_vecVelocity.y; + vel.z = m_vecVelocity.z + m_vecAccel.z * gravity * time; + + float d = DotProduct( vel, pmtrace.plane.normal ) * 2.0f; + m_vecVelocity = vel - pmtrace.plane.normal * d; + m_vecVelocity *= bound( 0.0f, m_flBounceFactor, 1.0f ); + + // check for stop or slide along the plane + if( pmtrace.plane.normal.z > 0.0f && m_vecVelocity.z < 1.0f ) + { + if( pmtrace.plane.normal.z >= 0.7f ) + { + m_vecVelocity = g_vecZero; + m_vecAccel = g_vecZero; + m_iFlags &= ~FPART_BOUNCE; + } + else + { + // FIXME: check for new plane or free fall + float dot = DotProduct( m_vecVelocity, pmtrace.plane.normal ); + m_vecVelocity += ( pmtrace.plane.normal * -dot ); + + dot = DotProduct( m_vecAccel, pmtrace.plane.normal ); + m_vecAccel += ( pmtrace.plane.normal * -dot ); + } + } + + org = pmtrace.endpos; + curLength = 1.0f; + + // reset + m_flTime = tr.time; + m_vecColor = curColor; + m_flAlpha = curAlpha; + m_flRadius = curRadius; + m_vecOrigin = org; + + // don't stretch + m_iFlags &= ~FPART_STRETCH; + m_flLengthVelocity = 0.0f; + m_flLength = curLength; + } + } + + // save current origin if needed + if( FBitSet( m_iFlags, ( FPART_BOUNCE|FPART_STRETCH ))) + { + org2 = m_vecLastOrg; + m_vecLastOrg = org; + } + + // vertex lit particle + if( FBitSet( m_iFlags, FPART_VERTEXLIGHT )) + { + Vector light; + // gather static lighting + gEngfuncs.pTriAPI->LightAtPoint( org, light ); + light *= (1.0f/255.0f); + + // gather dynamic lighting + light += R_LightsForPoint( org, curRadius ); + + // renormalize lighting + float f = Q_max( Q_max( light.x, light.y ), light.z ); + if( f > 1.0f ) light *= ( 1.0f / f ); + curColor *= light; // multiply to diffuse + } + + if( FBitSet( m_iFlags, FPART_INSTANT )) + { + // instant particle + m_flAlphaVelocity = 0.0f; + m_flAlpha = 0.0f; + } + + if( curRadius == 1.0f ) + { + float scale = 0.0f; + + // hack a scale up to keep quake particles from disapearing + scale += (org.x - GetVieworg().x) * GetVForward().x; + scale += (org.y - GetVieworg().y) * GetVForward().y; + scale += (org.z - GetVieworg().z) * GetVForward().z; + if( scale >= 20.0f ) curRadius = 1.0f + scale * 0.004f; + } + + Vector axis[3], verts[4]; + Vector absmin, absmax; + + // prepare to draw + if( curLength != 1.0f ) + { + // find orientation vectors + axis[0] = GetVieworg() - org; + axis[1] = org2 - org; + axis[2] = CrossProduct( axis[0], axis[1] ); + + axis[1] = axis[1].Normalize(); + axis[2] = axis[2].Normalize(); + + // find normal + axis[0] = CrossProduct( axis[1], axis[2] ); + axis[0] = axis[0].Normalize(); + + org3 = org + ( axis[1] * -curLength ); + axis[2] *= m_flRadius; + + // setup vertexes + verts[0] = org3 - axis[2]; + verts[1] = org3 + axis[2]; + verts[2] = org + axis[2]; + verts[3] = org - axis[2]; + } + else + { + if( m_flRotation ) + { + // Rotate it around its normal + RotatePointAroundVector( axis[1], GetVForward(), GetVLeft(), m_flRotation ); + axis[2] = CrossProduct( GetVForward(), axis[1] ); + + // the normal should point at the viewer + axis[0] = -GetVForward(); + + // Scale the axes by radius + axis[1] *= curRadius; + axis[2] *= curRadius; + } + else + { + // the normal should point at the viewer + axis[0] = -GetVForward(); + + // scale the axes by radius + axis[1] = GetVLeft() * curRadius; + axis[2] = GetVUp() * curRadius; + } + + verts[0] = org + axis[1] - axis[2]; + verts[1] = org + axis[1] + axis[2]; + verts[2] = org - axis[1] + axis[2]; + verts[3] = org - axis[1] - axis[2]; + } + + ClearBounds( absmin, absmax ); + for( int i = 0; i < 4; i++ ) + AddPointToBounds( verts[i], absmin, absmax ); +#if 0 + GL_Blend( GL_TRUE ); + + if( FBitSet( m_iFlags, FPART_ADDITIVE )) + pglBlendFunc( GL_SRC_ALPHA, GL_ONE ); + else pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + + // draw the particle + GL_BindTexture( GL_TEXTURE0, m_hTexture ); + + pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + + if( FBitSet( m_iFlags, FPART_ADDITIVE )) + pglColor4f( 1.0f, 1.0f, 1.0f, curAlpha ); + else pglColor4f( curColor.x, curColor.y, curColor.z, curAlpha ); + + pglBegin( GL_QUADS ); + pglTexCoord2f( 0.0f, 1.0f ); + pglVertex3fv( verts[0] ); + + pglTexCoord2f( 0.0f, 0.0f ); + pglVertex3fv( verts[1] ); + + pglTexCoord2f( 1.0f, 0.0f ); + pglVertex3fv( verts[2] ); + + pglTexCoord2f( 1.0f, 1.0f ); + pglVertex3fv( verts[3] ); + pglEnd(); +#else + CTransEntry entry; + Vector4D partColor; + int rendermode; + + if( FBitSet( m_iFlags, FPART_ADDITIVE )) + { + partColor = Vector4D( 1.0f, 1.0f, 1.0f, curAlpha ); + rendermode = kRenderTransAdd; + } + else + { + partColor = Vector4D( curColor.x, curColor.y, curColor.z, curAlpha ); + rendermode = kRenderTransTexture; + } + + entry.SetRenderPrimitive( verts, partColor, m_hTexture, rendermode ); + entry.ComputeViewDistance( absmin, absmax ); + RI->frame.trans_list.AddToTail( entry ); +#endif + return true; +} + +CQuakePartSystem :: CQuakePartSystem( void ) +{ + memset( m_pParticles, 0, sizeof( CQuakePart ) * MAX_PARTICLES ); + + m_pFreeParticles = m_pParticles; + m_pActiveParticles = NULL; +} + +CQuakePartSystem :: ~CQuakePartSystem( void ) +{ +} + +void CQuakePartSystem :: Clear( void ) +{ + m_pFreeParticles = m_pParticles; + m_pActiveParticles = NULL; + + for( int i = 0; i < MAX_PARTICLES; i++ ) + m_pParticles[i].pNext = &m_pParticles[i+1]; + + m_pParticles[MAX_PARTICLES-1].pNext = NULL; + + m_pAllowParticles = CVAR_REGISTER( "cl_particles", "1", FCVAR_ARCHIVE ); + m_pParticleLod = CVAR_REGISTER( "cl_particle_lod", "0", FCVAR_ARCHIVE ); + + // loading TE shaders + m_hDefaultParticle = FIND_TEXTURE( "*particle" ); // quake particle + m_hSparks = LOAD_TEXTURE( "gfx/particles/spark", NULL, 0, TF_CLAMP ); + m_hSmoke = LOAD_TEXTURE( "gfx/particles/smoke", NULL, 0, TF_CLAMP ); + m_hWaterSplash = LOAD_TEXTURE( "gfx/particles/splash1", NULL, 0, TF_CLAMP ); + + ParsePartInfos( "gfx/particles/effects.txt" ); +} + +void CQuakePartSystem :: ParsePartInfos( const char *filename ) +{ + // we parse our effects each call of VidInit because we need to keep textures and sprites an actual + + ALERT( at_aiconsole, "loading %s\n", filename ); + + char *afile = (char *)gEngfuncs.COM_LoadFile( (char *)filename, 5, NULL ); + + if( !afile ) + { + ALERT( at_error, "Cannot open file %s\n", filename ); + return; + } + + char *pfile = afile; + char token[256]; + m_iNumPartInfo = 0; + + memset( &m_pPartInfo, 0, sizeof( m_pPartInfo )); + + while( pfile != NULL ) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + + if( m_iNumPartInfo >= MAX_PARTINFOS ) + { + ALERT ( at_error, "particle effects info limit exceeded %d > %d\n", m_iNumPartInfo, MAX_PARTINFOS ); + break; + } + + CQuakePartInfo *pCur = &m_pPartInfo[m_iNumPartInfo]; + + // read the effect name + Q_strncpy( pCur->m_szName, token, sizeof( pCur->m_szName )); + + // read opening brace + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + + if( token[0] != '{' ) + { + ALERT( at_error, "found %s when expecting {\n", token ); + break; + } + + if( !ParsePartInfo( pCur, pfile )) + break; // something bad happens + + } + + gEngfuncs.COM_FreeFile( afile ); + ALERT( at_aiconsole, "%d effects parsed\n", m_iNumPartInfo ); +} + +CQuakePartInfo *CQuakePartSystem :: FindPartInfo( const char *name ) +{ + for( int i = 0; i < m_iNumPartInfo; i++ ) + { + if( !Q_stricmp( m_pPartInfo[i].m_szName, name )) + return &m_pPartInfo[i]; + } + + return NULL; +} + +void CQuakePartSystem :: CreateEffect( const char *name, const Vector &origin, const Vector &normal ) +{ + if( !g_fRenderInitialized || !m_pAllowParticles->value ) + return; + + if( !name || !*name ) + return; + + CQuakePartInfo *pInfo = FindPartInfo( name ); + int m_hTexture = m_hDefaultParticle; + CQuakePart src; + + if( !pInfo ) + { + ALERT( at_warning, "QuakeParticle: couldn't find effect '%s'\n", name ); + return; + } + + // sparks + int flags = pInfo->flags; + + if( FBitSet( flags, FPART_NOTWATER ) && POINT_CONTENTS( (float *)&origin ) == CONTENTS_WATER ) + return; + + int count = bound( 1, pInfo->count.Random(), 1024 ); // don't alloc too many particles + + for( int i = 0; i < count; i++ ) + { + if( pInfo->m_pSprite ) + { + int frame = bound( 0, pInfo->frame.Random(), pInfo->m_pSprite->numframes - 1 ); + mspriteframe_t *pframe = R_GetSpriteFrame( pInfo->m_pSprite, frame ); + if( pframe ) m_hTexture = pframe->gl_texturenum; + } + else m_hTexture = pInfo->m_hTexture; + + if( pInfo->normal == NORMAL_OFFSET || pInfo->normal == NORMAL_OFS_DIR ) + { + src.m_vecOrigin.x = origin.x + pInfo->offset[0].Random() * normal.x; + src.m_vecOrigin.y = origin.y + pInfo->offset[1].Random() * normal.y; + src.m_vecOrigin.z = origin.z + pInfo->offset[2].Random() * normal.z; + } + else + { + src.m_vecOrigin.x = origin.x + pInfo->offset[0].Random(); + src.m_vecOrigin.y = origin.y + pInfo->offset[1].Random(); + src.m_vecOrigin.z = origin.z + pInfo->offset[2].Random(); + } + + if( pInfo->normal == NORMAL_DIRECTION || pInfo->normal == NORMAL_OFS_DIR ) + { + src.m_vecVelocity.x = normal.x * pInfo->velocity[0].Random(); + src.m_vecVelocity.y = normal.y * pInfo->velocity[1].Random(); + src.m_vecVelocity.z = normal.z * pInfo->velocity[2].Random(); + } + else + { + src.m_vecVelocity.x = pInfo->velocity[0].Random(); + src.m_vecVelocity.y = pInfo->velocity[1].Random(); + src.m_vecVelocity.z = pInfo->velocity[2].Random(); + } + + src.m_vecAccel.x = pInfo->accel[0].Random(); + src.m_vecAccel.y = pInfo->accel[1].Random(); + src.m_vecAccel.z = pInfo->accel[2].Random(); + src.m_vecColor.x = pInfo->color[0].Random(); + src.m_vecColor.y = pInfo->color[1].Random(); + src.m_vecColor.z = pInfo->color[2].Random(); + src.m_vecColorVelocity.x = pInfo->colorVel[0].Random(); + src.m_vecColorVelocity.y = pInfo->colorVel[1].Random(); + src.m_vecColorVelocity.z = pInfo->colorVel[2].Random(); + src.m_flAlpha = pInfo->alpha.Random(); + src.m_flAlphaVelocity = pInfo->alphaVel.Random(); + src.m_flRadius = pInfo->radius.Random(); + src.m_flRadiusVelocity = pInfo->radiusVel.Random(); + src.m_flLength = pInfo->length.Random(); + src.m_flLengthVelocity = pInfo->lengthVel.Random(); + src.m_flRotation = pInfo->rotation.Random(); + src.m_flBounceFactor = pInfo->bounce.Random(); + + if( !AddParticle( &src, m_hTexture, flags )) + return; // out of particles? + } +} + +bool CQuakePartSystem :: ParseRandomVector( char *&pfile, RandomRange out[3] ) +{ + char token[256]; + + for( int i = 0; i < 3 && pfile != NULL; i++ ) + { + pfile = COM_ParseLine( pfile, token ); + out[i] = RandomRange( token ); + } + + return (i == 3) ? true : false; +} + +int CQuakePartSystem :: ParseParticleFlags( char *pfile ) +{ + char token[256]; + int iFlags = 0; + + if( !pfile || !*pfile ) + return iFlags; + + while( pfile != NULL ) + { + pfile = COM_ParseLine( pfile, token ); + + if( !Q_stricmp( token, "Bounce" )) + iFlags |= FPART_BOUNCE; + else if( !Q_stricmp( token, "Friction" )) + iFlags |= FPART_FRICTION; + else if( !Q_stricmp( token, "Light" )) + iFlags |= FPART_VERTEXLIGHT; + else if( !Q_stricmp( token, "Stretch" )) + iFlags |= FPART_STRETCH; + else if( !Q_stricmp( token, "Underwater" )) + iFlags |= FPART_UNDERWATER; + else if( !Q_stricmp( token, "Instant" )) + iFlags |= FPART_INSTANT; + else if( !Q_stricmp( token, "Additive" )) + iFlags |= FPART_ADDITIVE; + else if( !Q_stricmp( token, "NotInWater" )) + iFlags |= FPART_NOTWATER; + else if( pfile && token[0] != '|' ) + ALERT( at_warning, "unknown value %s for 'flags'\n", token ); + } + + return iFlags; +} + +bool CQuakePartSystem :: ParsePartInfo( CQuakePartInfo *info, char *&pfile ) +{ + char token[256]; + + while( pfile != NULL ) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "EOF without closing brace\n" ); + return false; + } + + // description end goto next material + if( token[0] == '}' ) + { + m_iNumPartInfo++; + return true; + } + else if( !Q_stricmp( token, "texture" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'texture'\n" ); + break; + } + + const char *ext = COM_FileExtension( token ); + + if( !Q_stricmp( ext, "tga" ) || !Q_stricmp( ext, "dds" )) + info->m_hTexture = LOAD_TEXTURE( token, NULL, 0, TF_CLAMP ); + else if( !Q_stricmp( ext, "spr" )) + info->m_pSprite = (model_t *)gEngfuncs.GetSpritePointer( SPR_Load( token )); + + if( !info->m_hTexture && !info->m_pSprite ) + { + ALERT( at_error, "couldn't load texture for effect %s. using default particle\n", info->m_szName ); + info->m_hTexture = m_hDefaultParticle; + } + } + else if( !Q_stricmp( token, "offset" )) + { + if( !ParseRandomVector( pfile, info->offset )) + { + ALERT( at_error, "hit EOF while parsing 'offset'\n" ); + break; + } + } + else if( !Q_stricmp( token, "velocity" )) + { + if( !ParseRandomVector( pfile, info->velocity )) + { + ALERT( at_error, "hit EOF while parsing 'velocity'\n" ); + break; + } + } + else if( !Q_stricmp( token, "accel" )) + { + if( !ParseRandomVector( pfile, info->accel )) + { + ALERT( at_error, "hit EOF while parsing 'accel'\n" ); + break; + } + } + else if( !Q_stricmp( token, "color" )) + { + if( !ParseRandomVector( pfile, info->color )) + { + ALERT( at_error, "hit EOF while parsing 'color'\n" ); + break; + } + } + else if( !Q_stricmp( token, "colorVelocity" )) + { + if( !ParseRandomVector( pfile, info->colorVel )) + { + ALERT( at_error, "hit EOF while parsing 'colorVelocity'\n" ); + break; + } + } + else if( !Q_stricmp( token, "alpha" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'alpha'\n" ); + break; + } + + info->alpha = RandomRange( token ); + } + else if( !Q_stricmp( token, "alphaVelocity" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'alphaVelocity'\n" ); + break; + } + + info->alphaVel = RandomRange( token ); + } + else if( !Q_stricmp( token, "radius" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'radius'\n" ); + break; + } + + info->radius = RandomRange( token ); + } + else if( !Q_stricmp( token, "radiusVelocity" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'radiusVelocity'\n" ); + break; + } + + info->radiusVel = RandomRange( token ); + } + else if( !Q_stricmp( token, "length" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'length'\n" ); + break; + } + + info->length = RandomRange( token ); + } + else if( !Q_stricmp( token, "lengthVelocity" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'lengthVelocity'\n" ); + break; + } + + info->lengthVel = RandomRange( token ); + } + else if( !Q_stricmp( token, "rotation" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'rotation'\n" ); + break; + } + + info->rotation = RandomRange( token ); + } + else if( !Q_stricmp( token, "bounceFactor" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'bounceFactor'\n" ); + break; + } + + info->bounce = RandomRange( token ); + } + else if( !Q_stricmp( token, "frame" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'frame'\n" ); + break; + } + + info->frame = RandomRange( token ); + } + else if( !Q_stricmp( token, "count" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'count'\n" ); + break; + } + + info->count = RandomRange( token ); + } + else if( !Q_stricmp( token, "flags" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'flags'\n" ); + break; + } + + info->flags = ParseParticleFlags( token ); + } + else if( !Q_stricmp( token, "useNormal" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'useNormal'\n" ); + break; + } + + if( !Q_stricmp( token, "ignore" )) + info->normal = NORMAL_IGNORE; + else if( !Q_stricmp( token, "ofs" )) + info->normal = NORMAL_OFFSET; + else if( !Q_stricmp( token, "dir" )) + info->normal = NORMAL_DIRECTION; + else if( !Q_stricmp( token, "ofs+dir" )) + info->normal = NORMAL_OFS_DIR; + else if( !Q_stricmp( token, "dir+ofs" )) + info->normal = NORMAL_OFS_DIR; + else ALERT( at_warning, "Unknown 'useNormal' key '%s'\n", token ); + } + else ALERT( at_warning, "Unknown effects token %s\n", token ); + } + + return true; +} + +void CQuakePartSystem :: FreeParticle( CQuakePart *pCur ) +{ + pCur->pNext = m_pFreeParticles; + m_pFreeParticles = pCur; +} + +CQuakePart *CQuakePartSystem :: AllocParticle( void ) +{ + CQuakePart *pCur; + + if( !m_pFreeParticles ) + { + ALERT( at_console, "Overflow %d particles\n", MAX_PARTICLES ); + return NULL; + } + + if( m_pParticleLod->value > 1.0f ) + { + if( !( RANDOM_LONG( 0, 1 ) % (int)m_pParticleLod->value )) + return NULL; + } + + pCur = m_pFreeParticles; + m_pFreeParticles = pCur->pNext; + pCur->pNext = m_pActiveParticles; + m_pActiveParticles = pCur; + + return pCur; +} + +void CQuakePartSystem :: Update( void ) +{ + CQuakePart *pCur, *pNext; + CQuakePart *pActive = NULL, *pTail = NULL; + + if( !m_pAllowParticles->value ) + return; + + if( FBitSet( RI->params, ( RP_ENVVIEW|RP_SKYVIEW ))) + return; + + GL_DepthMask( GL_FALSE ); + + float gravity = tr.frametime * tr.gravity; + + for( pCur = m_pActiveParticles; pCur != NULL; pCur = pNext ) + { + // grab next now, so if the particle is freed we still have it + pNext = pCur->pNext; + + if( !pCur->Evaluate( gravity )) + { + FreeParticle( pCur ); + continue; + } + + pCur->pNext = NULL; + + if( !pTail ) + { + pActive = pTail = pCur; + } + else + { + pTail->pNext = pCur; + pTail = pCur; + } + } + + m_pActiveParticles = pActive; +} + +bool CQuakePartSystem :: AddParticle( CQuakePart *src, int texture, int flags ) +{ + if( !src ) return false; + + CQuakePart *dst = AllocParticle(); + + if( !dst ) return false; + + if( texture ) dst->m_hTexture = texture; + else dst->m_hTexture = m_hDefaultParticle; + dst->m_flTime = tr.time; + dst->m_iFlags = flags; + + dst->m_vecOrigin = src->m_vecOrigin; + dst->m_vecVelocity = src->m_vecVelocity; + dst->m_vecAccel = src->m_vecAccel; + dst->m_vecColor = src->m_vecColor; + dst->m_vecColorVelocity = src->m_vecColorVelocity; + dst->m_flAlpha = src->m_flAlpha; + + dst->m_flRadius = src->m_flRadius; + dst->m_flLength = src->m_flLength; + dst->m_flRotation = src->m_flRotation; + dst->m_flAlphaVelocity = src->m_flAlphaVelocity; + dst->m_flRadiusVelocity = src->m_flRadiusVelocity; + dst->m_flLengthVelocity = src->m_flLengthVelocity; + dst->m_flBounceFactor = src->m_flBounceFactor; + + // needs to save old origin + if( FBitSet( flags, ( FPART_BOUNCE|FPART_FRICTION ))) + dst->m_vecLastOrg = dst->m_vecOrigin; + + return true; +} + +/* +================= +CL_ExplosionParticles +================= +*/ +void CQuakePartSystem :: ExplosionParticles( const Vector &pos ) +{ + CQuakePart src; + int flags; + + if( !g_fRenderInitialized || !m_pAllowParticles->value ) + return; + + flags = (FPART_STRETCH|FPART_BOUNCE|FPART_FRICTION); + + for( int i = 0; i < 384; i++ ) + { + src.m_vecOrigin.x = pos.x + RANDOM_LONG( -16, 16 ); + src.m_vecOrigin.y = pos.y + RANDOM_LONG( -16, 16 ); + src.m_vecOrigin.z = pos.z + RANDOM_LONG( -16, 16 ); + src.m_vecVelocity.x = RANDOM_LONG( -256, 256 ); + src.m_vecVelocity.y = RANDOM_LONG( -256, 256 ); + src.m_vecVelocity.z = RANDOM_LONG( -256, 256 ); + src.m_vecAccel.x = src.m_vecAccel.y = 0; + src.m_vecAccel.z = -60 + RANDOM_FLOAT( -30, 30 ); + src.m_vecColor = Vector( 1, 1, 1 ); + src.m_vecColorVelocity = Vector( 0, 0, 0 ); + src.m_flAlpha = 1.0; + src.m_flAlphaVelocity = -3.0; + src.m_flRadius = 0.5 + RANDOM_FLOAT( -0.2, 0.2 ); + src.m_flRadiusVelocity = 0; + src.m_flLength = 8 + RANDOM_FLOAT( -4, 4 ); + src.m_flLengthVelocity = 8 + RANDOM_FLOAT( -4, 4 ); + src.m_flRotation = 0; + src.m_flBounceFactor = 0.2; + + if( !AddParticle( &src, m_hSparks, flags )) + return; + } + + // smoke + flags = FPART_VERTEXLIGHT; + + for( i = 0; i < 5; i++ ) + { + src.m_vecOrigin.x = pos.x + RANDOM_FLOAT( -10, 10 ); + src.m_vecOrigin.y = pos.y + RANDOM_FLOAT( -10, 10 ); + src.m_vecOrigin.z = pos.z + RANDOM_FLOAT( -10, 10 ); + src.m_vecVelocity.x = RANDOM_FLOAT( -10, 10 ); + src.m_vecVelocity.y = RANDOM_FLOAT( -10, 10 ); + src.m_vecVelocity.z = RANDOM_FLOAT( -10, 10 ) + RANDOM_FLOAT( -5, 5 ) + 25; + src.m_vecAccel = Vector( 0, 0, 0 ); + src.m_vecColor = Vector( 0, 0, 0 ); + src.m_vecColorVelocity = Vector( 0.75, 0.75, 0.75 ); + src.m_flAlpha = 0.5; + src.m_flAlphaVelocity = RANDOM_FLOAT( -0.1, -0.2 ); + src.m_flRadius = 30 + RANDOM_FLOAT( -15, 15 ); + src.m_flRadiusVelocity = 15 + RANDOM_FLOAT( -7.5, 7.5 ); + src.m_flLength = 1; + src.m_flLengthVelocity = 0; + src.m_flRotation = RANDOM_LONG( 0, 360 ); + + if( !AddParticle( &src, m_hSmoke, flags )) + return; + } +} + +/* +================= +CL_BulletParticles +================= +*/ +void CQuakePartSystem :: SparkParticles( const Vector &org, const Vector &dir ) +{ + CQuakePart src; + + if( !g_fRenderInitialized || !m_pAllowParticles->value ) + return; + + // sparks + int flags = (FPART_STRETCH|FPART_BOUNCE|FPART_FRICTION); + + for( int i = 0; i < 16; i++ ) + { + src.m_vecOrigin.x = org[0] + dir[0] * 2 + RANDOM_FLOAT( -1, 1 ); + src.m_vecOrigin.y = org[1] + dir[1] * 2 + RANDOM_FLOAT( -1, 1 ); + src.m_vecOrigin.z = org[2] + dir[2] * 2 + RANDOM_FLOAT( -1, 1 ); + src.m_vecVelocity.x = dir[0] * 180 + RANDOM_FLOAT( -60, 60 ); + src.m_vecVelocity.y = dir[1] * 180 + RANDOM_FLOAT( -60, 60 ); + src.m_vecVelocity.z = dir[2] * 180 + RANDOM_FLOAT( -60, 60 ); + src.m_vecAccel.x = src.m_vecAccel.y = 0; + src.m_vecAccel.z = -120 + RANDOM_FLOAT( -60, 60 ); + src.m_vecColor = Vector( 1.0, 1.0f, 1.0f ); + src.m_vecColorVelocity = Vector( 0, 0, 0 ); + src.m_flAlpha = 1.0; + src.m_flAlphaVelocity = -8.0; + src.m_flRadius = 0.4 + RANDOM_FLOAT( -0.2, 0.2 ); + src.m_flRadiusVelocity = 0; + src.m_flLength = 8 + RANDOM_FLOAT( -4, 4 ); + src.m_flLengthVelocity = 8 + RANDOM_FLOAT( -4, 4 ); + src.m_flRotation = 0; + src.m_flBounceFactor = 0.2; + + if( !AddParticle( &src, m_hSparks, flags )) + return; + } +} + +/* +================= +CL_RicochetSparks +================= +*/ +void CQuakePartSystem :: RicochetSparks( const Vector &org, float scale ) +{ + CQuakePart src; + + if( !g_fRenderInitialized || !m_pAllowParticles->value ) + return; + + // sparks + int flags = (FPART_STRETCH|FPART_BOUNCE|FPART_FRICTION); + + for( int i = 0; i < 16; i++ ) + { + src.m_vecOrigin.x = org[0] + RANDOM_FLOAT( -1, 1 ); + src.m_vecOrigin.y = org[1] + RANDOM_FLOAT( -1, 1 ); + src.m_vecOrigin.z = org[2] + RANDOM_FLOAT( -1, 1 ); + src.m_vecVelocity.x = RANDOM_FLOAT( -60, 60 ); + src.m_vecVelocity.y = RANDOM_FLOAT( -60, 60 ); + src.m_vecVelocity.z = RANDOM_FLOAT( -60, 60 ); + src.m_vecAccel.x = src.m_vecAccel.y = 0; + src.m_vecAccel.z = -120 + RANDOM_FLOAT( -60, 60 ); + src.m_vecColor = Vector( 1.0, 1.0f, 1.0f ); + src.m_vecColorVelocity = Vector( 0, 0, 0 ); + src.m_flAlpha = 1.0; + src.m_flAlphaVelocity = -8.0; + src.m_flRadius = scale + RANDOM_FLOAT( -0.2, 0.2 ); + src.m_flRadiusVelocity = 0; + src.m_flLength = scale + RANDOM_FLOAT( -0.2, 0.2 ); + src.m_flLengthVelocity = scale + RANDOM_FLOAT( -0.2, 0.2 ); + src.m_flRotation = 0; + src.m_flBounceFactor = 0.2; + + if( !AddParticle( &src, m_hSparks, flags )) + return; + } +} + +void CQuakePartSystem :: SmokeParticles( const Vector &pos, int count ) +{ + CQuakePart src; + + if( !g_fRenderInitialized || !m_pAllowParticles->value ) + return; + + // smoke + int flags = FPART_VERTEXLIGHT; + + for( int i = 0; i < count; i++ ) + { + src.m_vecOrigin.x = pos.x + RANDOM_FLOAT( -10, 10 ); + src.m_vecOrigin.y = pos.y + RANDOM_FLOAT( -10, 10 ); + src.m_vecOrigin.z = pos.z + RANDOM_FLOAT( -10, 10 ); + src.m_vecVelocity.x = RANDOM_FLOAT( -10, 10 ); + src.m_vecVelocity.y = RANDOM_FLOAT( -10, 10 ); + src.m_vecVelocity.z = RANDOM_FLOAT( -10, 10 ) + RANDOM_FLOAT( -5, 5 ) + 25; + src.m_vecAccel = Vector( 0, 0, 0 ); + src.m_vecColor = Vector( 0, 0, 0 ); + src.m_vecColorVelocity = Vector( 0.75, 0.75, 0.75 ); + src.m_flAlpha = 0.5; + src.m_flAlphaVelocity = RANDOM_FLOAT( -0.1, -0.15 ); + src.m_flRadius = 30 + RANDOM_FLOAT( -15, 15 ); + src.m_flRadiusVelocity = 15 + RANDOM_FLOAT( -7.5, 7.5 ); + src.m_flLength = 1; + src.m_flLengthVelocity = 0; + src.m_flRotation = RANDOM_LONG( 0, 360 ); + + if( !AddParticle( &src, m_hSmoke, flags )) + return; + } +} + +void CQuakePartSystem :: GunSmoke( const Vector &pos, int count ) +{ + CQuakePart src; + + if( !g_fRenderInitialized || !m_pAllowParticles->value ) + return; + + // smoke + int flags = FPART_VERTEXLIGHT; + + for( int i = 0; i < count; i++ ) + { + src.m_vecOrigin.x = pos.x + RANDOM_FLOAT( -0.1f, 0.1f ); + src.m_vecOrigin.y = pos.y + RANDOM_FLOAT( -0.1f, 0.1f ); + src.m_vecOrigin.z = pos.z + RANDOM_FLOAT( -0.1f, 0.1f ); + src.m_vecVelocity.x = RANDOM_FLOAT( -5.1f, 5.1f ); + src.m_vecVelocity.y = RANDOM_FLOAT( -5.1f, 5.1f ); + src.m_vecVelocity.z = RANDOM_FLOAT( -5.1f, 5.1f ); + src.m_vecAccel = Vector( 0, 0, 0 ); + src.m_vecColor = Vector( 1.0f, 1.0f, 1.0f ); + src.m_vecColorVelocity = g_vecZero; + src.m_flAlpha = 0.5; + src.m_flAlphaVelocity = RANDOM_FLOAT( -0.2f, -0.4f ); + src.m_flRadius = RANDOM_FLOAT( 4.0f, 6.0f ); + src.m_flRadiusVelocity = 2.0f + RANDOM_FLOAT( -0.5, 0.5 ); + src.m_flLength = 1; + src.m_flLengthVelocity = 0; + src.m_flRotation = RANDOM_LONG( 0, 360 ); + + if( !AddParticle( &src, m_hSmoke, flags )) + return; + } +} + +/* +================= +CL_BulletParticles +================= +*/ +void CQuakePartSystem :: BulletParticles( const Vector &org, const Vector &dir ) +{ + CQuakePart src; + int cnt, count; + + if( !g_fRenderInitialized || !m_pAllowParticles->value ) + return; + + count = RANDOM_LONG( 3, 8 ); + cnt = POINT_CONTENTS( (float *)&org ); + + if( cnt == CONTENTS_WATER ) + return; + + // sparks + int flags = (FPART_STRETCH|FPART_BOUNCE|FPART_FRICTION|FPART_ADDITIVE); + + for( int i = 0; i < count; i++ ) + { + src.m_vecOrigin.x = org[0] + dir[0] * 2 + RANDOM_FLOAT( -1, 1 ); + src.m_vecOrigin.y = org[1] + dir[1] * 2 + RANDOM_FLOAT( -1, 1 ); + src.m_vecOrigin.z = org[2] + dir[2] * 2 + RANDOM_FLOAT( -1, 1 ); + src.m_vecVelocity.x = dir[0] * 180 + RANDOM_FLOAT( -60, 60 ); + src.m_vecVelocity.y = dir[1] * 180 + RANDOM_FLOAT( -60, 60 ); + src.m_vecVelocity.z = dir[2] * 180 + RANDOM_FLOAT( -60, 60 ); + src.m_vecAccel.x = src.m_vecAccel.y = 0; + src.m_vecAccel.z = -120 + RANDOM_FLOAT( -60, 60 ); + src.m_vecColor = Vector( 1.0, 1.0f, 1.0f ); + src.m_vecColorVelocity = Vector( 0, 0, 0 ); + src.m_flAlpha = 1.0; + src.m_flAlphaVelocity = -8.0; + src.m_flRadius = 0.4 + RANDOM_FLOAT( -0.2, 0.2 ); + src.m_flRadiusVelocity = 0; + src.m_flLength = 8 + RANDOM_FLOAT( -4, 4 ); + src.m_flLengthVelocity = 8 + RANDOM_FLOAT( -4, 4 ); + src.m_flRotation = 0; + src.m_flBounceFactor = 0.2; + + if( !AddParticle( &src, m_hSparks, flags )) + return; + } + + // smoke + flags = FPART_VERTEXLIGHT; + + for( i = 0; i < 3; i++ ) + { + src.m_vecOrigin.x = org[0] + dir[0] * 5 + RANDOM_FLOAT( -1, 1 ); + src.m_vecOrigin.y = org[1] + dir[1] * 5 + RANDOM_FLOAT( -1, 1 ); + src.m_vecOrigin.z = org[2] + dir[2] * 5 + RANDOM_FLOAT( -1, 1 ); + src.m_vecVelocity.x = RANDOM_FLOAT( -2.5, 2.5 ); + src.m_vecVelocity.y = RANDOM_FLOAT( -2.5, 2.5 ); + src.m_vecVelocity.z = RANDOM_FLOAT( -2.5, 2.5 ) + (25 + RANDOM_FLOAT( -5, 5 )); + src.m_vecAccel = Vector( 0, 0, 0 ); + src.m_vecColor = Vector( 0.4, 0.4, 0.4 ); + src.m_vecColorVelocity = Vector( 0.2, 0.2, 0.2 ); + src.m_flAlpha = 0.5; + src.m_flAlphaVelocity = -(0.4 + RANDOM_FLOAT( 0, 0.2 )); + src.m_flRadius = 3 + RANDOM_FLOAT( -1.5, 1.5 ); + src.m_flRadiusVelocity = 5 + RANDOM_FLOAT( -2.5, 2.5 ); + src.m_flLength = 1; + src.m_flLengthVelocity = 0; + src.m_flRotation = RANDOM_LONG( 0, 360 ); + + if( !AddParticle( &src, m_hSmoke, flags )) + return; + } +} \ No newline at end of file diff --git a/cl_dll/render/gl_rpart.h b/cl_dll/render/gl_rpart.h new file mode 100644 index 0000000..6fd32ef --- /dev/null +++ b/cl_dll/render/gl_rpart.h @@ -0,0 +1,144 @@ +/* +gl_rpart.h - quake-like particles +this code written for Paranoia 2: Savior modification +Copyright (C) 2013 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef GL_RPART_H +#define GL_RPART_H + +#include "randomrange.h" + +#define MAX_PARTICLES 8192 +#define MAX_PARTINFOS 256 // various types of part-system + +// built-in particle-system flags +#define FPART_BOUNCE (1<<0) // makes a bouncy particle +#define FPART_FRICTION (1<<1) +#define FPART_VERTEXLIGHT (1<<2) // give some ambient light for it +#define FPART_STRETCH (1<<3) +#define FPART_UNDERWATER (1<<4) +#define FPART_INSTANT (1<<5) +#define FPART_ADDITIVE (1<<6) +#define FPART_NOTWATER (1<<7) // don't spawn in water + +class CQuakePart +{ +public: + Vector m_vecOrigin; // position for current frame + Vector m_vecLastOrg; // position from previous frame + + Vector m_vecVelocity; // linear velocity + Vector m_vecAccel; + Vector m_vecColor; + Vector m_vecColorVelocity; + float m_flAlpha; + float m_flAlphaVelocity; + float m_flRadius; + float m_flRadiusVelocity; + float m_flLength; + float m_flLengthVelocity; + float m_flRotation; // texture ROLL angle + float m_flBounceFactor; + + CQuakePart *pNext; // linked list + int m_hTexture; + + float m_flTime; + int m_iFlags; + + bool Evaluate( float gravity ); +}; + +typedef enum +{ + NORMAL_IGNORE = 0, + NORMAL_OFFSET, + NORMAL_DIRECTION, + NORMAL_OFS_DIR, +}; + +class CQuakePartInfo +{ +public: + char m_szName[32]; // effect name + + struct model_s *m_pSprite; // sprite + int m_hTexture; // tga texture + + RandomRange offset[3]; + RandomRange velocity[3]; + RandomRange accel[3]; + RandomRange color[3]; + RandomRange colorVel[3]; + RandomRange alpha; + RandomRange alphaVel; + RandomRange radius; + RandomRange radiusVel; + RandomRange length; + RandomRange lengthVel; + RandomRange rotation; + RandomRange bounce; + RandomRange frame; + RandomRange count; // particle count + + int normal; // how to use normal + int flags; // particle flags +}; + +class CQuakePartSystem +{ + CQuakePart *m_pActiveParticles; + CQuakePart *m_pFreeParticles; + CQuakePart m_pParticles[MAX_PARTICLES]; + + CQuakePartInfo m_pPartInfo[MAX_PARTINFOS]; + int m_iNumPartInfo; + + // private partsystem shaders + int m_hDefaultParticle; + int m_hSparks; + int m_hSmoke; + int m_hWaterSplash; + + cvar_t *m_pAllowParticles; + cvar_t *m_pParticleLod; +public: + CQuakePartSystem( void ); + virtual ~CQuakePartSystem( void ); + + void Clear( void ); + void Update( void ); + void FreeParticle( CQuakePart *pCur ); + CQuakePart *AllocParticle( void ); + bool AddParticle( CQuakePart *src, int texture = 0, int flags = 0 ); + void ParsePartInfos( const char *filename ); + bool ParsePartInfo( CQuakePartInfo *info, char *&pfile ); + bool ParseRandomVector( char *&pfile, RandomRange out[3] ); + int ParseParticleFlags( char *pfile ); + CQuakePartInfo *FindPartInfo( const char *name ); + void CreateEffect( const char *name, const Vector &origin, const Vector &normal ); + + // example presets + void ExplosionParticles( const Vector &pos ); + void BulletParticles( const Vector &org, const Vector &dir ); + void BubbleParticles( const Vector &org, int count, float magnitude ); + void SparkParticles( const Vector &org, const Vector &dir ); + void RicochetSparks( const Vector &org, float scale ); + void SmokeParticles( const Vector &pos, int count ); + void GunSmoke( const Vector &pos, int count ); +}; + +extern CQuakePartSystem g_pParticles; + +#endif//GL_RPART_H \ No newline at end of file diff --git a/cl_dll/render/gl_rsurf.cpp b/cl_dll/render/gl_rsurf.cpp new file mode 100644 index 0000000..c3495ac --- /dev/null +++ b/cl_dll/render/gl_rsurf.cpp @@ -0,0 +1,99 @@ +/* +gl_rsurf.cpp - surface-related code +Copyright (C) 2013 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include "cl_util.h" +#include "gl_local.h" + +static int rtable[MOD_FRAMES][MOD_FRAMES]; + +/* +=============== +R_TextureAnimation + +Returns the proper texture for a given time and base texture +=============== +*/ +texture_t *R_TextureAnimation( msurface_t *s ) +{ + texture_t *base = s->texinfo->texture; + int count, reletive = 0; + + if(( RI->currententity != NULL ) && ( RI->currententity->curstate.frame != 0.0f )) + { + if( base->alternate_anims ) + base = base->alternate_anims; + } + + if( !base->anim_total ) + return base; + + if( base->name[0] == '-' ) + { + int tx = (int)((s->texturemins[0] + (base->width << 16)) / base->width) % MOD_FRAMES; + int ty = (int)((s->texturemins[1] + (base->height << 16)) / base->height) % MOD_FRAMES; + reletive = rtable[tx][ty] % base->anim_total; + } + else + { + reletive = (int)(tr.time * 20) % base->anim_total; + } + + count = 0; + + while( base->anim_min > reletive || base->anim_max <= reletive ) + { + base = base->anim_next; + + if( !base ) + { + ALERT( at_error, "R_TextureAnimation: broken loop\n" ); + return s->texinfo->texture; + } + + if( ++count > MOD_FRAMES ) + { + ALERT( at_error, "R_TextureAnimation: infinite loop\n" ); + return s->texinfo->texture; + } + } + + return base; +} + +/* +============================================================= + + WORLD MODEL + +============================================================= +*/ +void GL_InitRandomTable( void ) +{ + int tu, tv; + + // make random predictable + RANDOM_SEED( 255 ); + + for( tu = 0; tu < MOD_FRAMES; tu++ ) + { + for( tv = 0; tv < MOD_FRAMES; tv++ ) + { + rtable[tu][tv] = RANDOM_LONG( 0, 0x7FFF ); + } + } + + RANDOM_SEED( 0 ); +} \ No newline at end of file diff --git a/cl_dll/render/gl_scene.cpp b/cl_dll/render/gl_scene.cpp new file mode 100644 index 0000000..328fdf0 --- /dev/null +++ b/cl_dll/render/gl_scene.cpp @@ -0,0 +1,548 @@ +/* +gl_scene.cpp - scene management +Copyright (C) 2016 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "entity_types.h" +#include "gl_local.h" +#include +#include "gl_aurora.h" +#include "gl_rpart.h" +#include "gl_studio.h" +#include "gl_sprite.h" +#include "event_api.h" +#include "gl_world.h" +#include "gl_grass.h" +#include "screenfade.h" +#include "shake.h" + +/* +=============== +R_CheckChanges +=============== +*/ +void R_CheckChanges( void ) +{ + static bool fog_enabled_old; + bool settings_changed = false; + + if( FBitSet( r_showlightmaps->flags, FCVAR_CHANGED )) + { + float lightmap = bound( 0.0f, r_showlightmaps->value, MAX_LIGHTMAPS ); + CVAR_SET_FLOAT( "r_showlightmaps", lightmap ); + ClearBits( r_showlightmaps->flags, FCVAR_CHANGED ); + } + + if( FBitSet( r_studio_decals->flags, FCVAR_CHANGED )) + { + float maxStudioDecals = bound( 10.0f, r_studio_decals->value, 256.0f ); + CVAR_SET_FLOAT( "r_studio_decals", maxStudioDecals ); + ClearBits( r_studio_decals->flags, FCVAR_CHANGED ); + } + + if( FBitSet( cv_deferred_maxlights->flags, FCVAR_CHANGED )) + { + float maxDeferredLights = bound( 1, cv_deferred_maxlights->value, MAXDYNLIGHTS ); + if( maxDeferredLights != cv_deferred_maxlights->value ) + CVAR_SET_FLOAT( "gl_deferred_maxlights", maxDeferredLights ); + ClearBits( cv_deferred_maxlights->flags, FCVAR_CHANGED ); + } + + if( FBitSet( cv_deferred_tracebmodels->flags, FCVAR_CHANGED )) + { + ClearBits( cv_deferred_tracebmodels->flags, FCVAR_CHANGED ); + tr.params_changed = true; + } + + if( FBitSet( r_recursion_depth->flags, FCVAR_CHANGED )) + { + float depth = bound( 0.0f, r_recursion_depth->value, MAX_REF_STACK - 2 ); + CVAR_SET_FLOAT( "gl_recursion_depth", depth ); + ClearBits( r_recursion_depth->flags, FCVAR_CHANGED ); + } + + if( FBitSet( r_drawentities->flags, FCVAR_CHANGED )) + { + ClearBits( r_drawentities->flags, FCVAR_CHANGED ); + tr.params_changed = true; + } + + if( FBitSet( r_lightstyles->flags, FCVAR_CHANGED )) + { + ClearBits( r_lightstyles->flags, FCVAR_CHANGED ); + settings_changed = true; + } + + if( FBitSet( cv_cubemaps->flags, FCVAR_CHANGED )) + { + ClearBits( cv_cubemaps->flags, FCVAR_CHANGED ); + settings_changed = true; + } + + if( FBitSet( cv_deferred->flags, FCVAR_CHANGED )) + { + ClearBits( cv_deferred->flags, FCVAR_CHANGED ); + settings_changed = true; + } + + if( FBitSet( r_lightmap->flags, FCVAR_CHANGED )) + { + ClearBits( r_lightmap->flags, FCVAR_CHANGED ); + settings_changed = true; + } + + if( FBitSet( r_allow_mirrors->flags, FCVAR_CHANGED )) + { + ClearBits( r_allow_mirrors->flags, FCVAR_CHANGED ); + settings_changed = true; + } + + if( FBitSet( cv_realtime_puddles->flags, FCVAR_CHANGED )) + { + ClearBits( cv_realtime_puddles->flags, FCVAR_CHANGED ); + settings_changed = true; + } + + if( FBitSet( r_detailtextures->flags, FCVAR_CHANGED )) + { + ClearBits( r_detailtextures->flags, FCVAR_CHANGED ); + settings_changed = true; + } + + if( FBitSet( r_fullbright->flags, FCVAR_CHANGED )) + { + ClearBits( r_fullbright->flags, FCVAR_CHANGED ); + settings_changed = true; + } + + if( FBitSet( r_sunshadows->flags, FCVAR_CHANGED )) + { + ClearBits( r_sunshadows->flags, FCVAR_CHANGED ); + settings_changed = true; + } + + if( FBitSet( r_sun_allowed->flags, FCVAR_CHANGED )) + { + ClearBits( r_sun_allowed->flags, FCVAR_CHANGED ); + settings_changed = true; + } + + if( FBitSet( cv_parallax->flags, FCVAR_CHANGED )) + { + ClearBits( cv_parallax->flags, FCVAR_CHANGED ); + settings_changed = true; + } + + if( FBitSet( cv_specular->flags, FCVAR_CHANGED )) + { + ClearBits( cv_specular->flags, FCVAR_CHANGED ); + settings_changed = true; + } + + if( FBitSet( r_grass_shadows->flags, FCVAR_CHANGED )) + { + ClearBits( r_grass_shadows->flags, FCVAR_CHANGED ); + settings_changed = true; + } + + if( FBitSet( r_shadows->flags, FCVAR_CHANGED )) + { + ClearBits( r_shadows->flags, FCVAR_CHANGED ); + settings_changed = true; + } + + if( FBitSet( cv_bump->flags, FCVAR_CHANGED )) + { + ClearBits( cv_bump->flags, FCVAR_CHANGED ); + settings_changed = true; + } + + if( FBitSet( cv_brdf->flags, FCVAR_CHANGED )) + { + ClearBits( cv_brdf->flags, FCVAR_CHANGED ); + settings_changed = true; + } + + if( FBitSet( r_test->flags, FCVAR_CHANGED )) + { + ClearBits( r_test->flags, FCVAR_CHANGED ); + R_StudioClearLightCache(); + settings_changed = true; + } + + if( FBitSet( r_grass->flags, FCVAR_CHANGED )) + { + if( worldmodel != NULL ) + { + for( int i = 0; i < worldmodel->numsurfaces; i++ ) + SetBits( worldmodel->surfaces[i].flags, SURF_GRASS_UPDATE ); + } + ClearBits( r_grass->flags, FCVAR_CHANGED ); + settings_changed = true; + } + + if( FBitSet( cv_gamma->flags, FCVAR_CHANGED ) || FBitSet( cv_brightness->flags, FCVAR_CHANGED )) + { + if( worldmodel != NULL ) + { + for( int i = 0; i < worldmodel->numsurfaces; i++ ) + SetBits( worldmodel->surfaces[i].flags, SURF_LM_UPDATE|SURF_GRASS_UPDATE ); + } + R_StudioClearLightCache(); + } + + if( tr.fogEnabled != fog_enabled_old ) + { + fog_enabled_old = tr.fogEnabled; + settings_changed = true; + } + + if( settings_changed ) + { + tr.glsl_valid_sequence++; // now all uber-shaders are invalidate and possible need for recompile + tr.params_changed = true; + } +} + +/* +=============== +R_ClearScene +=============== +*/ +void R_ClearScene( void ) +{ + if( !g_fRenderInitialized ) return; + + CL_DecayLights(); + + tr.time = GET_CLIENT_TIME(); + tr.oldtime = GET_CLIENT_OLDTIME(); + tr.frametime = tr.time - tr.oldtime; + tr.saved_frametime = tr.frametime; // backup + + memset( &r_stats, 0, sizeof( r_stats )); + + tr.local_client_added = false; + tr.num_draw_entities = 0; + tr.cached_state.Purge(); // invalidate cache + GET_ENTITY( 0 )->hCachedMatrix = GL_CacheState( g_vecZero, g_vecZero ); + + tr.num_2D_shadows_used = tr.num_CM_shadows_used = 0; + tr.sun_light_enabled = false; + + if( r_sunshadows->value > 2.0f ) + CVAR_SET_FLOAT( "gl_sun_shadows", 2.0f ); + else if( r_sunshadows->value < 0.0f ) + CVAR_SET_FLOAT( "gl_sun_shadows", 0.0f ); + + if( tr.shadows_notsupport ) + CVAR_SET_FLOAT( "r_shadows", 0.0f ); + + R_CheckChanges(); + + if( tr.params_changed ) + R_InitDynLightShaders(); +} + +/* +=============== +R_ComputeFxBlend +=============== +*/ +int R_ComputeFxBlend( cl_entity_t *e ) +{ + int blend = 0, renderAmt; + float offset, dist; + Vector tmp; + + offset = ((int)e->index ) * 363.0f; // Use ent index to de-sync these fx + renderAmt = e->curstate.renderamt; + + switch( e->curstate.renderfx ) + { + case kRenderFxPulseSlowWide: + blend = renderAmt + 0x40 * sin( tr.time * 2 + offset ); + break; + case kRenderFxPulseFastWide: + blend = renderAmt + 0x40 * sin( tr.time * 8 + offset ); + break; + case kRenderFxPulseSlow: + blend = renderAmt + 0x10 * sin( tr.time * 2 + offset ); + break; + case kRenderFxPulseFast: + blend = renderAmt + 0x10 * sin( tr.time * 8 + offset ); + break; + // JAY: HACK for now -- not time based + case kRenderFxFadeSlow: + if( renderAmt > 0 ) + renderAmt -= 1; + else renderAmt = 0; + blend = renderAmt; + break; + case kRenderFxFadeFast: + if( renderAmt > 3 ) + renderAmt -= 4; + else renderAmt = 0; + blend = renderAmt; + break; + case kRenderFxSolidSlow: + if( renderAmt < 255 ) + renderAmt += 1; + else renderAmt = 255; + blend = renderAmt; + break; + case kRenderFxSolidFast: + if( renderAmt < 252 ) + renderAmt += 4; + else renderAmt = 255; + blend = renderAmt; + break; + case kRenderFxStrobeSlow: + blend = 20 * sin( tr.time * 4 + offset ); + if( blend < 0 ) blend = 0; + else blend = renderAmt; + break; + case kRenderFxStrobeFast: + blend = 20 * sin( tr.time * 16 + offset ); + if( blend < 0 ) blend = 0; + else blend = renderAmt; + break; + case kRenderFxStrobeFaster: + blend = 20 * sin( tr.time * 36 + offset ); + if( blend < 0 ) blend = 0; + else blend = renderAmt; + break; + case kRenderFxFlickerSlow: + blend = 20 * (sin( tr.time * 2 ) + sin( tr.time * 17 + offset )); + if( blend < 0 ) blend = 0; + else blend = renderAmt; + break; + case kRenderFxFlickerFast: + blend = 20 * (sin( tr.time * 16 ) + sin( tr.time * 23 + offset )); + if( blend < 0 ) blend = 0; + else blend = renderAmt; + break; + case kRenderFxHologram: + case kRenderFxDistort: + tmp = e->origin - GetVieworg(); + dist = DotProduct( tmp, GetVForward( )); + + // Turn off distance fade + if( e->curstate.renderfx == kRenderFxDistort ) + dist = 1; + + if( dist <= 0 ) + { + blend = 0; + } + else + { + renderAmt = 180; + if( dist <= 100 ) blend = renderAmt; + else blend = (int) ((1.0f - ( dist - 100 ) * ( 1.0f / 400.0f )) * renderAmt ); + blend += RANDOM_LONG( -32, 31 ); + } + break; + case kRenderFxGlowShell: // safe current renderamt because it's shell scale! + case kRenderFxDeadPlayer: // safe current renderamt because it's player index! + blend = renderAmt; + break; + case kRenderFxNone: + case kRenderFxClampMinScale: + default: + if( e->curstate.rendermode == kRenderNormal ) + blend = 255; + else blend = renderAmt; + break; + } + + if( e->model->type != mod_brush || R_WaterEntity( e->model )) + { + // NOTE: never pass sprites with rendercolor '0 0 0' it's a stupid Valve Hammer Editor bug + if( !e->curstate.rendercolor.r && !e->curstate.rendercolor.g && !e->curstate.rendercolor.b ) + e->curstate.rendercolor.r = e->curstate.rendercolor.g = e->curstate.rendercolor.b = 255; + } + + // apply scale to studiomodels and sprites only + if( e->model && e->model->type != mod_brush && !e->curstate.scale ) + e->curstate.scale = 1.0f; + + blend = bound( 0, blend, 255 ); + + return blend; +} + +/* +=============== +R_AddEntity +=============== +*/ +qboolean R_AddEntity( struct cl_entity_s *clent, int entityType ) +{ + if( !CVAR_TO_BOOL( r_drawentities )) + return false; // not allow to drawing + + if( !clent || !clent->model ) + return false; // if set to invisible, skip + + if( clent->curstate.effects & EF_NODRAW ) + return false; // done + + if( entityType == ET_PLAYER && RP_LOCALCLIENT( clent )) + { + if( tr.local_client_added ) + return false; // already present in list + tr.local_client_added = true; + } + + if( clent->curstate.renderfx == 71 ) // dynamic light + { + CDynLight *dl = CL_AllocDlight( clent->curstate.number ); + + float radius = clent->curstate.renderamt * 8.0f; + float fov = clent->curstate.scale; + int tex = 0, flags = 0, type; + Vector origin, angles; + + if( clent->curstate.scale ) // spotlight + { + int i = bound( 0, clent->curstate.rendermode, 7 ); + tex = tr.spotlightTexture[i]; + type = LIGHT_SPOT; + } + else type = LIGHT_OMNI; + + if( clent->curstate.effects & EF_NOSHADOW ) + flags |= DLF_NOSHADOWS; + + if( clent->curstate.effects & EF_NOBUMP ) + flags |= DLF_NOBUMP; + + if( clent->curstate.effects & EF_LENSFLARE ) + flags |= DLF_LENSFLARE; + + R_GetLightVectors( clent, origin, angles ); + R_SetupLightParams( dl, origin, angles, radius, clent->curstate.scale, type, flags ); + R_SetupLightTexture( dl, tex ); + + dl->color[0] = (float)clent->curstate.rendercolor.r / 128; + dl->color[1] = (float)clent->curstate.rendercolor.g / 128; + dl->color[2] = (float)clent->curstate.rendercolor.b / 128; + dl->die = tr.time + 0.05f; + + return true; // no reason to drawing this entity + } + else if( clent->curstate.renderfx == 72 ) // dynamic light with avi file + { + if( !clent->curstate.sequence ) + return true; // bad avi file + + CDynLight *dl = CL_AllocDlight( clent->curstate.number ); + + if( dl->spotlightTexture == tr.spotlightTexture[1] ) + return true; // bad avi file + + float radius = clent->curstate.renderamt * 8.0f; + float fov = clent->curstate.scale; + Vector origin, angles; + int flags = DLF_ASPECT3X4; // fit to film01.avi aspect + + // found the corresponding cinstate + const char *cinname = gRenderfuncs.GetFileByIndex( clent->curstate.sequence ); + int hCin = R_PrecacheCinematic( cinname ); + + if( hCin >= 0 && !dl->cinTexturenum ) + dl->cinTexturenum = R_AllocateCinematicTexture( TF_SPOTLIGHT ); + + if( hCin == -1 || dl->cinTexturenum <= 0 || !CIN_IS_ACTIVE( tr.cinematics[hCin].state )) + { + // cinematic textures limit exceeded or movie not found + dl->spotlightTexture = tr.spotlightTexture[1]; + return true; + } + + gl_movie_t *cin = &tr.cinematics[hCin]; + float cin_time; + + // advances cinematic time + cin_time = fmod( clent->curstate.fuser2, cin->length ); + + // read the next frame + int cin_frame = CIN_GET_FRAME_NUMBER( cin->state, cin_time ); + + if( cin_frame != dl->lastframe ) + { + // upload the new frame + byte *raw = CIN_GET_FRAMEDATA( cin->state, cin_frame ); + CIN_UPLOAD_FRAME( tr.cinTextures[dl->cinTexturenum-1], cin->xres, cin->yres, cin->xres, cin->yres, raw ); + dl->lastframe = cin_frame; + } + + if( clent->curstate.effects & EF_NOSHADOW ) + flags |= DLF_NOSHADOWS; + + if( clent->curstate.effects & EF_NOBUMP ) + flags |= DLF_NOBUMP; + + R_GetLightVectors( clent, origin, angles ); + R_SetupLightParams( dl, origin, angles, radius, clent->curstate.scale, LIGHT_SPOT, flags ); + R_SetupLightTexture( dl, tr.cinTextures[dl->cinTexturenum-1] ); + + dl->color[0] = (float)clent->curstate.rendercolor.r / 128; + dl->color[1] = (float)clent->curstate.rendercolor.g / 128; + dl->color[2] = (float)clent->curstate.rendercolor.b / 128; + dl->die = GET_CLIENT_TIME() + 0.05f; + + return true; // no reason to drawing this entity + } + + if( clent->curstate.effects & EF_SCREENMOVIE ) + { + // update cin sound properly + R_UpdateCinSound( clent ); + } + + clent->curstate.renderamt = R_ComputeFxBlend( clent ); + + if( !R_OpaqueEntity( clent )) + { + if( clent->curstate.renderamt <= 0.0f ) + return true; // invisible + } +#if 0 + if( clent->model->type == mod_brush && CVAR_TO_BOOL( r_test )) + return true; +#endif + if( clent->model->type == mod_brush ) + clent->hCachedMatrix = GL_CacheState( clent->origin, clent->angles, ( clent->curstate.renderfx == SKYBOX_ENTITY )); + clent->curstate.entityType = entityType; + + // mark static entity as visible + if( entityType == ET_FRAGMENTED ) + { + // non-solid statics wants a new lighting too :-) + SetBits( clent->curstate.iuser1, CF_STATIC_ENTITY ); + clent->visframe = tr.realframecount; + } + + if( tr.num_draw_entities < MAX_VISIBLE_ENTS ) + { + tr.draw_entities[tr.num_draw_entities] = clent; + tr.num_draw_entities++; + } + + return true; +} \ No newline at end of file diff --git a/cl_dll/render/gl_shader.cpp b/cl_dll/render/gl_shader.cpp new file mode 100644 index 0000000..30288f6 --- /dev/null +++ b/cl_dll/render/gl_shader.cpp @@ -0,0 +1,1357 @@ +/* +gl_shader.cpp - glsl shaders +Copyright (C) 2014 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "ref_params.h" +#include "gl_local.h" +#include +#include +#include "gl_shader.h" +#include "virtualfs.h" +#include "gl_world.h" +#include "gl_decals.h" +#include "studio.h" +#include "gl_grass.h" + +//#define _DEBUG_UNIFORMS +#define SHADERS_HASH_SIZE (MAX_GLSL_PROGRAMS >> 2) +#define MAX_FILE_STACK 64 + +static char filenames_stack[MAX_FILE_STACK][256]; +glsl_program_t glsl_programs[MAX_GLSL_PROGRAMS]; +glsl_program_t *glsl_programsHashTable[SHADERS_HASH_SIZE]; +int num_glsl_programs; +static int file_stack_pos; +static bool cache_needs_update = false; + +typedef struct +{ + const char *name; + uniformType_t type; + int flags; // hints +} uniformTable_t; + +glsl_program_t *shader_t :: GetShader( void ) +{ + return &glsl_programs[shadernum]; +} + +void shader_t :: SetShader( unsigned short hand ) +{ + sequence = tr.glsl_valid_sequence; + shadernum = hand; +} + +bool shader_t :: IsValid( void ) +{ + return (shadernum > 0) && (sequence == tr.glsl_valid_sequence); +} + +int uniform_t :: GetSizeInBytes( void ) +{ + switch( format ) + { + case GL_SAMPLER_1D_ARB: + case GL_SAMPLER_2D_ARB: + case GL_SAMPLER_3D_ARB: + case GL_SAMPLER_CUBE_ARB: + case GL_SAMPLER_1D_SHADOW_ARB: + case GL_SAMPLER_2D_SHADOW_ARB: + case GL_SAMPLER_2D_RECT_ARB: + case GL_SAMPLER_2D_RECT_SHADOW_ARB: + case GL_SAMPLER_CUBE_SHADOW_EXT: + return 4; + case GL_FLOAT: + case GL_INT: + case GL_UNSIGNED_INT: + return 4; + case GL_FLOAT_VEC2_ARB: + case GL_INT_VEC2_ARB: + return 8; + case GL_FLOAT_VEC3_ARB: + case GL_INT_VEC3_ARB: + return 12; + case GL_FLOAT_VEC4_ARB: + case GL_INT_VEC4_ARB: + return 16; + case GL_FLOAT_MAT2_ARB: + return 16; + case GL_FLOAT_MAT3_ARB: + return 36; + case GL_FLOAT_MAT4_ARB: + return 64; + } + + ALERT( at_error, "uniform %s has unspecified size\n", name ); + return 0; // assume error +} + +void uniform_t :: SetValue( const void *pdata, int count ) +{ + unicache_t *check = (unicache_t *)pdata; + + // set texture unit + if( FBitSet( flags, UFL_TEXTURE_UNIT ) && unit >= 0 ) + { + GL_BindTexture( unit, check->iValue[0] ); + } + else if( size > 1 ) + { + // handle arrays + if( count == -1 ) + count = size; + + switch( format ) + { + case GL_FLOAT: + pglUniform1fvARB( location, count, (const float *)pdata ); + break; + case GL_FLOAT_VEC2_ARB: + pglUniform2fvARB( location, count, (const float *)pdata ); + break; + case GL_FLOAT_VEC3_ARB: + pglUniform3fvARB( location, count, (const float *)pdata ); + break; + case GL_FLOAT_VEC4_ARB: + pglUniform4fvARB( location, count, (const float *)pdata ); + break; + case GL_FLOAT_MAT2_ARB: + pglUniformMatrix2fvARB( location, count, GL_FALSE, (const float *)pdata ); + break; + case GL_FLOAT_MAT3_ARB: + pglUniformMatrix3fvARB( location, count, GL_FALSE, (const float *)pdata ); + break; + case GL_FLOAT_MAT4_ARB: + pglUniformMatrix4fvARB( location, count, GL_FALSE, (const float *)pdata ); + break; + } + } + else + { + int testSize = GetSizeInBytes(); + + // some values could be cached + if( testSize <= 16 && !memcmp( &cache, check, testSize )) + return; + + // single cached values + switch( format ) + { + case GL_FLOAT: + pglUniform1fARB( location, check->fValue[0] ); + cache.fValue[0] = check->fValue[0]; + break; + case GL_FLOAT_VEC2_ARB: + pglUniform2fARB( location, check->fValue[0], check->fValue[1] ); + cache.fValue[0] = check->fValue[0]; + cache.fValue[1] = check->fValue[1]; + break; + case GL_FLOAT_VEC3_ARB: + pglUniform3fARB( location, check->fValue[0], check->fValue[1], check->fValue[2] ); + cache.fValue[0] = check->fValue[0]; + cache.fValue[1] = check->fValue[1]; + cache.fValue[2] = check->fValue[2]; + break; + case GL_FLOAT_VEC4_ARB: + pglUniform4fARB( location, check->fValue[0], check->fValue[1], check->fValue[2], check->fValue[3] ); + cache.fValue[0] = check->fValue[0]; + cache.fValue[1] = check->fValue[1]; + cache.fValue[2] = check->fValue[2]; + cache.fValue[3] = check->fValue[3]; + break; + case GL_INT: + pglUniform1iARB( location, check->iValue[0] ); + cache.iValue[0] = check->iValue[0]; + break; + case GL_INT_VEC2_ARB: + pglUniform2iARB( location, check->iValue[0], check->iValue[1] ); + cache.iValue[0] = check->iValue[0]; + cache.iValue[1] = check->iValue[1]; + break; + case GL_INT_VEC3_ARB: + pglUniform3iARB( location, check->iValue[0], check->iValue[1], check->iValue[2] ); + cache.iValue[0] = check->iValue[0]; + cache.iValue[1] = check->iValue[1]; + cache.iValue[2] = check->iValue[2]; + break; + case GL_INT_VEC4_ARB: + pglUniform4iARB( location, check->iValue[0], check->iValue[1], check->iValue[2], check->iValue[3] ); + cache.iValue[0] = check->iValue[0]; + cache.iValue[1] = check->iValue[1]; + cache.iValue[2] = check->iValue[2]; + cache.iValue[3] = check->iValue[3]; + break; + case GL_FLOAT_MAT2_ARB: + pglUniformMatrix2fvARB( location, 1, GL_FALSE, (const float *)pdata ); + cache.fValue[0] = check->fValue[0]; + cache.fValue[1] = check->fValue[1]; + cache.fValue[2] = check->fValue[2]; + cache.fValue[3] = check->fValue[3]; + break; + case GL_FLOAT_MAT3_ARB: + pglUniformMatrix3fvARB( location, 1, GL_FALSE, (const float *)pdata ); + break; + case GL_FLOAT_MAT4_ARB: + pglUniformMatrix4fvARB( location, 1, GL_FALSE, (const float *)pdata ); + break; + } + } +} + +// all known engine uniforms +static uniformTable_t glsl_uniformTable[] = +{ +{ "u_ColorMap", UT_COLORMAP, UFL_TEXTURE_UNIT }, +{ "u_DepthMap", UT_DEPTHMAP, UFL_TEXTURE_UNIT }, +{ "u_NormalMap", UT_NORMALMAP, UFL_TEXTURE_UNIT }, +{ "u_GlossMap", UT_GLOSSMAP, UFL_TEXTURE_UNIT }, +{ "u_DetailMap", UT_DETAILMAP, UFL_TEXTURE_UNIT }, +{ "u_ProjectMap", UT_PROJECTMAP, UFL_TEXTURE_UNIT }, +{ "u_ShadowMap0", UT_SHADOWMAP0, UFL_TEXTURE_UNIT }, +{ "u_ShadowMap1", UT_SHADOWMAP1, UFL_TEXTURE_UNIT }, +{ "u_ShadowMap2", UT_SHADOWMAP2, UFL_TEXTURE_UNIT }, +{ "u_ShadowMap3", UT_SHADOWMAP3, UFL_TEXTURE_UNIT }, +{ "u_ShadowMap", UT_SHADOWMAP, UFL_TEXTURE_UNIT }, +{ "u_LightMap", UT_LIGHTMAP, UFL_TEXTURE_UNIT }, +{ "u_DeluxeMap", UT_DELUXEMAP, UFL_TEXTURE_UNIT }, +{ "u_DecalMap", UT_DECALMAP, UFL_TEXTURE_UNIT }, +{ "u_ScreenMap", UT_SCREENMAP, UFL_TEXTURE_UNIT }, +{ "u_VisLightMap0", UT_VISLIGHTMAP0, UFL_TEXTURE_UNIT }, +{ "u_VisLightMap1", UT_VISLIGHTMAP1, UFL_TEXTURE_UNIT }, +{ "u_EnvMap0", UT_ENVMAP0, UFL_TEXTURE_UNIT }, +{ "u_EnvMap1", UT_ENVMAP1, UFL_TEXTURE_UNIT }, +{ "u_EnvMap", UT_ENVMAP, UFL_TEXTURE_UNIT }, +{ "u_GlowMap", UT_GLOWMAP, UFL_TEXTURE_UNIT }, +{ "u_HeightMap", UT_HEIGHTMAP, UFL_TEXTURE_UNIT }, +{ "u_LayerMap", UT_LAYERMAP, UFL_TEXTURE_UNIT }, +{ "u_FragData0", UT_FRAGDATA0, UFL_TEXTURE_UNIT }, +{ "u_FragData1", UT_FRAGDATA1, UFL_TEXTURE_UNIT }, +{ "u_FragData2", UT_FRAGDATA2, UFL_TEXTURE_UNIT }, +{ "u_BspPlanesMap", UT_BSPPLANESMAP, UFL_TEXTURE_UNIT }, +{ "u_BspModelsMap", UT_BSPMODELSMAP, UFL_TEXTURE_UNIT }, +{ "u_BspNodesMap", UT_BSPNODESMAP, UFL_TEXTURE_UNIT }, +{ "u_BspLightsMap", UT_BSPLIGHTSMAP, UFL_TEXTURE_UNIT }, +{ "u_FitNormalMap", UT_FITNORMALMAP, UFL_TEXTURE_UNIT }, +{ "u_ModelMatrix", UT_MODELMATRIX, 0 }, +{ "u_ReflectMatrix", UT_REFLECTMATRIX, 0 }, +{ "u_BonesArray", UT_BONESARRAY, 0 }, +{ "u_BoneQuaternion", UT_BONEQUATERNION, 0 }, +{ "u_BonePosition", UT_BONEPOSITION, 0 }, +{ "u_ScreenSizeInv", UT_SCREENSIZEINV, UFL_GLOBAL_PARM }, +{ "u_zFar", UT_ZFAR, UFL_GLOBAL_PARM }, +{ "u_LightStyleValues", UT_LIGHTSTYLEVALUES, UFL_GLOBAL_PARM }, +{ "u_LightStyles", UT_LIGHTSTYLES, 0 }, +{ "u_RealTime", UT_REALTIME, UFL_GLOBAL_PARM }, +{ "u_DetailScale", UT_DETAILSCALE, 0 }, +{ "u_FogParams", UT_FOGPARAMS, UFL_GLOBAL_PARM }, +{ "u_ShadowParams", UT_SHADOWPARMS, 0 }, +{ "u_TexOffset", UT_TEXOFFSET, 0 }, +{ "u_ViewOrigin", UT_VIEWORIGIN, 0 }, // not in a global because it's transformed into modelspace +{ "u_ViewRight", UT_VIEWRIGHT, 0 }, +{ "u_RenderColor", UT_RENDERCOLOR, 0 }, +{ "u_RenderAlpha", UT_RENDERALPHA, 0 }, +{ "u_Smoothness", UT_SMOOTHNESS, 0 }, +{ "u_ShadowMatrix", UT_SHADOWMATRIX, 0 }, +{ "u_ShadowSplitDist", UT_SHADOWSPLITDIST, UFL_GLOBAL_PARM }, +{ "u_TexelSize", UT_TEXELSIZE, UFL_GLOBAL_PARM }, +{ "u_GammaTable", UT_GAMMATABLE, UFL_GLOBAL_PARM }, +{ "u_LightDir", UT_LIGHTDIR, 0 }, +{ "u_LightDiffuse", UT_LIGHTDIFFUSE, 0 }, +{ "u_LightShade", UT_LIGHTSHADE, 0 }, +{ "u_LightOrigin", UT_LIGHTORIGIN, 0 }, +{ "u_LightViewProjMatrix", UT_LIGHTVIEWPROJMATRIX, 0 }, +{ "u_DiffuseFactor", UT_DIFFUSEFACTOR, UFL_GLOBAL_PARM }, +{ "u_AmbientFactor", UT_AMBIENTFACTOR, UFL_GLOBAL_PARM }, +{ "u_AmbientCube", UT_AMBIENTCUBE, 0 }, +{ "u_SunRefract", UT_SUNREFRACT, UFL_GLOBAL_PARM }, +{ "u_LerpFactor", UT_LERPFACTOR, 0 }, +{ "u_RefractScale", UT_REFRACTSCALE, 0 }, +{ "u_ReflectScale", UT_REFLECTSCALE, 0 }, +{ "u_AberrationScale", UT_ABERRATIONSCALE, 0 }, +{ "u_BoxMins", UT_BOXMINS, 0 }, +{ "u_BoxMaxs", UT_BOXMAXS, 0 }, +{ "u_CubeOrigin", UT_CUBEORIGIN, 0 }, +{ "u_CubeMipCount", UT_CUBEMIPCOUNT, 0 }, +{ "u_LightNums0", UT_LIGHTNUMS0, 0 }, +{ "u_LightNums1", UT_LIGHTNUMS1, 0 }, +{ "u_GrassParams", UT_GRASSPARAMS, 0 }, +{ "u_ReliefParams", UT_RELIEFPARAMS, 0 }, +{ "u_BlurFactor", UT_BLURFACTOR, 0 }, +{ "u_ScreenWidth", UT_SCREENWIDTH, 0 }, +{ "u_ScreenHeight", UT_SCREENHEIGHT, 0 }, +{ "u_FocalDepth", UT_FOCALDEPTH, 0 }, +{ "u_FocalLength", UT_FOCALLENGTH, 0 }, +{ "u_DofDebug", UT_DOFDEBUG, 0 }, +{ "u_FStop", UT_FSTOP, 0 }, +{ "u_GrayScale", UT_GRAYSCALE, 0 }, +{ "u_LightGamma", UT_LIGHTGAMMA, UFL_GLOBAL_PARM }, +{ "u_LightScale", UT_LIGHTSCALE, UFL_GLOBAL_PARM }, +{ "u_LightThreshold", UT_LIGHTTHRESHOLD, UFL_GLOBAL_PARM }, +{ "u_NumVisibleModels", UT_NUMVISIBLEMODELS, UFL_GLOBAL_PARM }, +{ "u_Undefined", UT_UNDEFINED, 0 }, +}; + +static char *GL_PrintInfoLog( GLhandleARB object ) +{ + static char msg[32768]; + int maxLength = 0; + + pglGetObjectParameterivARB( object, GL_OBJECT_INFO_LOG_LENGTH_ARB, &maxLength ); + + if( maxLength >= sizeof( msg )) + { + ALERT( at_warning, "GL_PrintInfoLog: message exceeds %i symbols\n", sizeof( msg )); + maxLength = sizeof( msg ) - 1; + } + + pglGetInfoLogARB( object, maxLength, &maxLength, msg ); + + return msg; +} + +static char *GL_PrintShaderSource( GLhandleARB object ) +{ + static char msg[8192]; + int maxLength = 0; + + pglGetObjectParameterivARB( object, GL_OBJECT_SHADER_SOURCE_LENGTH_ARB, &maxLength ); + + if( maxLength >= sizeof( msg )) + { + ALERT( at_warning, "GL_PrintShaderSource: message exceeds %i symbols\n", sizeof( msg )); + maxLength = sizeof( msg ) - 1; + } + + pglGetShaderSourceARB( object, maxLength, &maxLength, msg ); + + return msg; +} + +static bool GL_PushFileStack( const char *filename ) +{ + if( file_stack_pos < MAX_FILE_STACK ) + { + Q_strncpy( filenames_stack[file_stack_pos], filename, sizeof( filenames_stack[0] )); + file_stack_pos++; + return true; + } + + ALERT( at_error, "GL_PushFileStack: stack overflow\n" ); + return false; +} + +static bool GL_PopFileStack( void ) +{ + file_stack_pos--; + + if( file_stack_pos < 0 ) + { + ALERT( at_error, "GL_PushFileStack: stack underflow\n" ); + return false; + } + + return true; +} + +static bool GL_CheckFileStack( const char *filename ) +{ + for( int i = 0; i < file_stack_pos; i++ ) + { + if( !Q_stricmp( filenames_stack[i], filename )) + return true; + } + return false; +} + +static bool GL_TestSource( const char *szFilename, const char *szCacheName, CVirtualFS *file ) +{ + int iCompare; + + if( GL_CheckFileStack( szFilename )) + return false; + + // check vertex shader source or include-file + if( COMPARE_FILE_TIME( szFilename, szCacheName, &iCompare )) + { + // glsl file is newer. + if( iCompare > 0 ) + { + Msg( "%s was changed, %s will be updated\n", szFilename, szCacheName ); + cache_needs_update = true; + } + } + else + { + Msg( "%s will be created\n", szCacheName ); + cache_needs_update = true; + } + + int size; + char *source = (char *)gEngfuncs.COM_LoadFile((char *)szFilename, 5, &size ); + if( !source ) return false; + + GL_PushFileStack( szFilename ); + file->Write( source, size ); + file->Seek( 0, SEEK_SET ); // rewind + + gEngfuncs.COM_FreeFile( source ); + + return true; +} + +static void GL_TestFile( const char *szFilename, const char *szCacheName, CVirtualFS *file ) +{ + char *pfile, token[256]; + char line[2048]; + int ret; + + do + { + ret = file->Gets( line, sizeof( line )); + pfile = line; + + // NOTE: if first keyword it's not an '#include' just ignore it + pfile = COM_ParseFile( pfile, token ); + if( !Q_strcmp( token, "#include" )) + { + CVirtualFS incfile; + char incname[256]; + + pfile = COM_ParseLine( pfile, token ); + Q_snprintf( incname, sizeof( incname ), "glsl/%s", token ); + if( !GL_TestSource( incname, szCacheName, &incfile )) + continue; + + if( cache_needs_update ) + break; // no reason to seek more + GL_TestFile( incname, szCacheName, &incfile ); + } + } while( ret != EOF ); + + GL_PopFileStack(); +} + +static bool GL_TestShader( const char *szFilename, const char *szCacheName ) +{ + CVirtualFS file; + + cache_needs_update = false; + file_stack_pos = 0; + + if( !GL_TestSource( szFilename, szCacheName, &file )) + return false; + GL_TestFile( szFilename, szCacheName, &file ); + + return cache_needs_update; +} + +static bool GL_LoadSource( const char *filename, CVirtualFS *file ) +{ + if( GL_CheckFileStack( filename )) + { + ALERT( at_error, "recursive include for %s\n", filename ); + return false; + } + + int size; + char *source = (char *)gEngfuncs.COM_LoadFile((char *)filename, 5, &size ); + if( !source ) + { + ALERT( at_error, "couldn't load %s\n", filename ); + return false; + } + + GL_PushFileStack( filename ); + file->Write( source, size ); + file->Seek( 0, SEEK_SET ); // rewind + + gEngfuncs.COM_FreeFile( source ); + + return true; +} + +static void GL_ParseFile( const char *filename, CVirtualFS *file, CVirtualFS *out ) +{ + char *pfile, token[256]; + int ret, fileline = 1; + char line[2048]; + +// out->Printf( "#file %s\n", filename ); // OpenGL doesn't support #file :-( + out->Printf( "#line 0\n" ); + + do + { + ret = file->Gets( line, sizeof( line )); + pfile = line; + + // NOTE: if first keyword it's not an '#include' just ignore it + pfile = COM_ParseFile( pfile, token ); + if( !Q_strcmp( token, "#include" )) + { + CVirtualFS incfile; + char incname[256]; + + pfile = COM_ParseLine( pfile, token ); + Q_snprintf( incname, sizeof( incname ), "glsl/%s", token ); + if( !GL_LoadSource( incname, &incfile )) + { + fileline++; + continue; + } + GL_ParseFile( incname, &incfile, out ); +// out->Printf( "#file %s\n", filename ); // OpenGL doesn't support #file :-( + out->Printf( "#line %i\n", fileline ); + } + else out->Printf( "%s\n", line ); + fileline++; + } while( ret != EOF ); + + GL_PopFileStack(); +} + +static bool GL_ProcessShader( const char *filename, CVirtualFS *out, const char *defines = NULL ) +{ + CVirtualFS file; + + file_stack_pos = 0; + + if( !GL_LoadSource( filename, &file )) + return false; + + // add internal defines + out->Printf( "#version 120\n" ); // OpenGL 2.1 required (because 'flat' modifier support only starts from this version) + out->Printf( "#ifndef M_PI\n#define M_PI 3.14159265358979323846\n#endif\n" ); + out->Printf( "#ifndef M_PI2\n#define M_PI2 6.28318530717958647692\n#endif\n" ); + if( GL_Support( R_EXT_GPU_SHADER4 )) + { + out->Printf( "#extension GL_EXT_gpu_shader4 : require\n" ); // support bitwise ops + out->Printf( "#define GLSL_gpu_shader4\n" ); + if( GL_Support( R_TEXTURE_ARRAY_EXT )) + out->Printf( "#define GLSL_ALLOW_TEXTURE_ARRAY\n" ); + } + else if( GL_Support( R_TEXTURE_ARRAY_EXT )) + { + out->Printf( "#extension GL_EXT_texture_array : require\n" ); // support texture arrays + out->Printf( "#define GLSL_ALLOW_TEXTURE_ARRAY\n" ); + } + + if( GL_Support( R_TEXTURE_2D_RECT_EXT )) + { + out->Printf( "#extension GL_ARB_texture_rectangle : enable\n" ); // support texture rectangle + } + + if( defines ) out->Print( defines ); + + // user may override this constants + out->Printf( "#ifndef MAXSTUDIOBONES\n#define MAXSTUDIOBONES %i\n#endif\n", glConfig.max_skinning_bones ); + out->Printf( "#ifndef MAX_SHADOWMAPS\n#define MAX_SHADOWMAPS %i\n#endif\n", MAX_SHADOWMAPS ); + out->Printf( "#ifndef NUM_SHADOW_SPLITS\n#define NUM_SHADOW_SPLITS %i\n#endif\n", NUM_SHADOW_SPLITS ); + out->Printf( "#ifndef LIGHT_SAMPLES\n#define LIGHT_SAMPLES %i\n#endif\n", LIGHT_SAMPLES ); + out->Printf( "#ifndef MAX_LIGHTSTYLES\n#define MAX_LIGHTSTYLES %i\n#endif\n", MAX_LIGHTSTYLES ); + out->Printf( "#ifndef MAXLIGHTMAPS\n#define MAXLIGHTMAPS %i\n#endif\n", MAXLIGHTMAPS ); + out->Printf( "#ifndef MAXDYNLIGHTS\n#define MAXDYNLIGHTS %i\n#endif\n", (int)cv_deferred_maxlights->value ); + out->Printf( "#ifndef GRASS_ANIM_DIST\n#define GRASS_ANIM_DIST %f\n#endif\n", GRASS_ANIM_DIST ); + + GL_ParseFile( filename, &file, out ); + out->Write( "", 1 ); // terminator + + return true; +} + +static void GL_LoadGPUShader( glsl_program_t *shader, const char *name, GLenum shaderType, const char *defines = NULL ) +{ + char filename[256]; + GLhandleARB object; + CVirtualFS source; + GLint compiled; + + ASSERT( shader != NULL ); + + switch( shaderType ) + { + case GL_VERTEX_SHADER_ARB: + Q_snprintf( filename, sizeof( filename ), "glsl/%s_vp.glsl", name ); + break; + case GL_FRAGMENT_SHADER_ARB: + Q_snprintf( filename, sizeof( filename ), "glsl/%s_fp.glsl", name ); + break; + default: + ALERT( at_error, "GL_LoadGPUShader: unknown shader type %p\n", shaderType ); + return; + } + + // load includes, add some directives + if( !GL_ProcessShader( filename, &source, defines )) + return; + + GLcharARB *buffer = (GLcharARB *)source.GetBuffer(); + int bufferSize = source.GetSize(); + + ALERT( at_aiconsole, "loading '%s'\n", filename ); + object = pglCreateShaderObjectARB( shaderType ); + pglShaderSourceARB( object, GL_TRUE, (const GLcharARB **)&buffer, &bufferSize ); + + // compile shader + pglCompileShaderARB( object ); + + // check if shader compiled + pglGetObjectParameterivARB( object, GL_OBJECT_COMPILE_STATUS_ARB, &compiled ); + + if( !compiled ) + { + if( developer_level ) Msg( "%s", GL_PrintInfoLog( object )); + if( developer_level ) Msg( "Shader options:%s\n", GL_PretifyListOptions( defines )); + ALERT( at_error, "Couldn't compile %s\n", filename ); + return; + } + + if( shaderType == GL_VERTEX_SHADER_ARB ) + shader->status |= SHADER_VERTEX_COMPILED; + else shader->status |= SHADER_FRAGMENT_COMPILED; + + // attach shader to program + pglAttachObjectARB( shader->handle, object ); + + // delete shader, no longer needed + pglDeleteObjectARB( object ); +} + +static bool GL_LoadGPUBinaryShader( glsl_program_t *shader, const char *vpname, const char *fpname, uint checksum ) +{ + char szFilename[MAX_PATH]; + char szVpSource[MAX_PATH]; + char szFpSource[MAX_PATH]; + GLint linked = 0; + int length; + + if( !GL_Support( R_BINARY_SHADER_EXT )) + return false; + + Q_snprintf( szFilename, sizeof( szFilename ), "cache/glsl/%p.bin", checksum ); + Q_snprintf( szVpSource, sizeof( szVpSource ), "glsl/%s_vp.glsl", vpname ); + Q_snprintf( szFpSource, sizeof( szFpSource ), "glsl/%s_fp.glsl", fpname ); + + // check vertex shader source + if( GL_TestShader( szVpSource, szFilename )) + return false; + + // check fragment shader source + if( GL_TestShader( szFpSource, szFilename )) + return false; + + byte *aMemFile = LOAD_FILE( szFilename, &length ); + if( !aMemFile ) return false; + + pglProgramBinary( shader->handle, glConfig.binary_formats, aMemFile, length ); + pglGetObjectParameterivARB( shader->handle, GL_OBJECT_LINK_STATUS_ARB, &linked ); + SetBits( shader->status, SHADER_FRAGMENT_COMPILED|SHADER_VERTEX_COMPILED ); + FREE_FILE( aMemFile ); + + if( linked ) + { + ALERT( at_aiconsole, "loading %s\n", szFilename ); + SetBits( shader->status, SHADER_PROGRAM_LINKED ); + return true; + } + + return false; +} + +static bool GL_SaveGPUBinaryShader( glsl_program_t *shader, uint checksum ) +{ + char szFilename[MAX_PATH]; + int length, result; + GLenum outFormat; + byte *binary; + + if( !GL_Support( R_BINARY_SHADER_EXT )) + return false; + + Q_snprintf( szFilename, sizeof( szFilename ), "cache/glsl/%p.bin", checksum ); + pglGetObjectParameterivARB( shader->handle, GL_PROGRAM_BINARY_LENGTH, &length ); + if( length <= 0 ) return false; + + binary = (byte *)malloc( length ); + pglGetProgramBinary( shader->handle, length, &length, &outFormat, binary ); + + result = SAVE_FILE( szFilename, binary, length ); + ALERT( at_aiconsole, "write glsl cache: %s\n", szFilename ); + free( binary ); + + return (result != 0 ); +} + +static void GL_LinkProgram( glsl_program_t *shader ) +{ + GLint linked = 0; + + if( !shader ) return; + + if( GL_Support( R_BINARY_SHADER_EXT )) + pglProgramParameteri( shader->handle, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE ); + + pglLinkProgramARB( shader->handle ); + + pglGetObjectParameterivARB( shader->handle, GL_OBJECT_LINK_STATUS_ARB, &linked ); + if( !linked ) + { + ALERT( at_error, "%s\n%s shader failed to link\n", GL_PrintInfoLog( shader->handle ), shader->name ); + if( developer_level ) Msg( "Shader options:%s\n", GL_PretifyListOptions( shader->options )); + } + else shader->status |= SHADER_PROGRAM_LINKED; +} + +static void GL_ValidateProgram( glsl_program_t *shader ) +{ + GLint validated = 0; + + if( !shader ) return; + + pglValidateProgramARB( shader->handle ); + + pglGetObjectParameterivARB( shader->handle, GL_OBJECT_VALIDATE_STATUS_ARB, &validated ); + if( !validated ) ALERT( at_error, "%s\n%s shader failed to validate\n", GL_PrintInfoLog( shader->handle ), shader->name ); +} + +int GL_UniformTypeToDwordCount( GLuint type, bool align = false ) +{ + switch( type ) + { + case GL_INT: + case GL_UNSIGNED_INT: + case GL_SAMPLER_1D_ARB: + case GL_SAMPLER_2D_ARB: + case GL_SAMPLER_3D_ARB: + case GL_SAMPLER_CUBE_ARB: + case GL_SAMPLER_1D_SHADOW_ARB: + case GL_SAMPLER_2D_SHADOW_ARB: + case GL_SAMPLER_2D_RECT_ARB: + case GL_SAMPLER_2D_RECT_SHADOW_ARB: + case GL_SAMPLER_CUBE_SHADOW_EXT: + return align ? 4 : 1; // int[1] + case GL_FLOAT: + return align ? 4 : 1; // float[1] + case GL_FLOAT_VEC2_ARB: + return align ? 4 : 2; // float[2] + case GL_FLOAT_VEC3_ARB: + return align ? 4 : 3; // float[3] + case GL_FLOAT_VEC4_ARB: + return 4; // float[4] + case GL_FLOAT_MAT2_ARB: + return 4; // float[2][2] + case GL_FLOAT_MAT3_ARB: + return align ? 12 : 9; // float[3][3] + case GL_FLOAT_MAT4_ARB: + return 16;// float[4][4] + default: return align ? 4 : 1; // assume error + } +} + +const char *GL_UniformTypeToName( GLuint type ) +{ + switch( type ) + { + case GL_INT: + return "int"; + case GL_UNSIGNED_INT: + return "uint"; + case GL_FLOAT: + return "float"; + case GL_FLOAT_VEC2_ARB: + return "vec2"; + case GL_FLOAT_VEC3_ARB: + return "vec3"; + case GL_FLOAT_VEC4_ARB: + return "vec4"; + case GL_FLOAT_MAT2_ARB: + return "mat2"; + case GL_FLOAT_MAT3_ARB: + return "mat3"; + case GL_FLOAT_MAT4_ARB: + return "mat4"; + case GL_SAMPLER_1D_ARB: + return "sampler1D"; + case GL_SAMPLER_2D_ARB: + return "sampler2D"; + case GL_SAMPLER_3D_ARB: + return "sampler3D"; + case GL_SAMPLER_CUBE_ARB: + return "samplerCube"; + case GL_SAMPLER_1D_ARRAY_EXT: + return "sampler1DArray"; + case GL_SAMPLER_2D_ARRAY_EXT: + return "sampler2DArray"; + case GL_SAMPLER_1D_SHADOW_ARB: + return "sampler1DShadow"; + case GL_SAMPLER_2D_SHADOW_ARB: + return "sampler2DShadow"; + case GL_SAMPLER_2D_RECT_ARB: + return "sampler2DRect"; + case GL_SAMPLER_2D_RECT_SHADOW_ARB: + return "sampler2DRectShadow"; + case GL_SAMPLER_CUBE_SHADOW_EXT: + return "samplerCubeShadow"; + default: return "???"; + } +} + +void GL_ShowProgramUniforms( glsl_program_t *shader ) +{ + int count, size; + char uniformName[256]; + int total_uniforms_used = 0; + GLuint type; + + if( !shader || developer_level < DEV_EXTENDED ) + return; + + // install the executables in the program object as part of current state. + pglUseProgramObjectARB( shader->handle ); + + // query the number of active uniforms + pglGetObjectParameterivARB( shader->handle, GL_OBJECT_ACTIVE_UNIFORMS_ARB, &count ); + + // Loop over each of the active uniforms, and set their value + for( int i = 0; i < count; i++ ) + { + pglGetActiveUniformARB( shader->handle, i, sizeof( uniformName ), NULL, &size, &type, uniformName ); + if( developer_level >= DEV_EXTENDED ) + { + if( size != 1 ) + { + char *end = Q_strchr( uniformName, '[' ); + if( end ) *end = '\0'; // cutoff [0] + } + + if( size == 1 ) ALERT( at_aiconsole, "uniform %s %s;\n", GL_UniformTypeToName( type ), uniformName ); + else ALERT( at_aiconsole, "uniform %s %s[%i];\n", GL_UniformTypeToName( type ), uniformName, size ); + } + total_uniforms_used += GL_UniformTypeToDwordCount( type ) * size; + } + + if( total_uniforms_used >= glConfig.max_vertex_uniforms ) + ALERT( at_error, "used uniforms %i is overflowed max count %i\n", total_uniforms_used, glConfig.max_vertex_uniforms ); + else ALERT( at_aiconsole, "used uniforms %i from %i\n", total_uniforms_used, glConfig.max_vertex_uniforms ); + + int max_shader_uniforms = total_uniforms_used; + + if( max_shader_uniforms > ( glConfig.max_skinning_bones * 12 )) + max_shader_uniforms -= ( glConfig.max_skinning_bones * 12 ); + + ALERT( at_aiconsole, "%s used %i uniforms\n", shader->name, max_shader_uniforms ); + + if( max_shader_uniforms > glConfig.peak_used_uniforms ) + { + glConfig.peak_used_uniforms = max_shader_uniforms; + tr.show_uniforms_peak = true; + } + + pglUseProgramObjectARB( GL_NONE ); +} + +static void GL_ParseProgramUniforms( glsl_program_t *shader ) +{ + int count, size; + char uniformName[256]; + int total_uniforms_used = 0; + char cleanName[256]; + int enumUnits = 0; + GLuint format; + + // query the number of active uniforms + pglGetObjectParameterivARB( shader->handle, GL_OBJECT_ACTIVE_UNIFORMS_ARB, &count ); + pglUseProgramObjectARB( shader->handle ); + + shader->uniforms = (uniform_t *)Mem_Alloc( count * sizeof( uniform_t )); + shader->numUniforms = 0; +#ifdef _DEBUG_UNIFORMS + Msg( "%c%s%s\n", 3, shader->name, GL_PretifyListOptions( shader->options )); +#endif + // Loop over each of the active uniforms, and set their value + for( int i = 0; i < count; i++ ) + { + uniformTable_t *desc; + uniform_t *uniform; + int location; + + pglGetActiveUniformARB( shader->handle, i, sizeof( uniformName ), NULL, &size, &format, uniformName ); + + if(( location = pglGetUniformLocationARB( shader->handle, uniformName )) == -1 ) + continue; // ignore built-in uniforms + + // remove array size from name + Q_strncpy( cleanName, uniformName, sizeof( cleanName )); + char *end = Q_strchr( cleanName, '[' ); + if( end ) *end = '\0'; // cutoff [0] + + // check for description + for( int j = 0; j < ARRAYSIZE( glsl_uniformTable ); j++ ) + { + desc = &glsl_uniformTable[j]; + if( !Q_strcmp( desc->name, cleanName )) + break; + } + + if( desc->type == UT_UNDEFINED ) + { + ALERT( at_error, "%cUnhandled uniform %s. Ignoring\n", 3, uniformName ); + continue; + } + + // fill next uniform + uniform = &shader->uniforms[shader->numUniforms++]; + Q_strncpy( uniform->name, cleanName, sizeof( uniform->name )); + uniform->location = location; + uniform->flags = desc->flags; + uniform->type = desc->type; + uniform->format = format; + uniform->size = size; + + if( FBitSet( uniform->flags, UFL_TEXTURE_UNIT )) + { + uniform->unit = enumUnits++; + pglUniform1iARB( uniform->location, uniform->unit ); + } + else uniform->unit = -1; // not a texture + + if( enumUnits >= glConfig.max_texture_units ) + ALERT( at_warning, "%c%s [%d] exceeded GL_MAX_IMAGE_UNITS\n", 3, cleanName, enumUnits ); +#ifdef _DEBUG_UNIFORMS + if( uniform->unit != -1 ) Msg( "%c%s %s : %d;\n", 3, GL_UniformTypeToName( format ), cleanName, uniform->unit ); + else if( size == 1 ) Msg( "%cuniform %s %s;\n", 3, GL_UniformTypeToName( format ), cleanName ); + else Msg( "%cuniform %s %s[%i];\n", 3, GL_UniformTypeToName( format ), cleanName, size ); +#endif + total_uniforms_used += GL_UniformTypeToDwordCount( format ) * size; + } + + if( total_uniforms_used >= glConfig.max_vertex_uniforms ) + ALERT( at_error, "%cused uniforms %i is overflowed max count %i\n", 3, total_uniforms_used, glConfig.max_vertex_uniforms ); +#ifdef _DEBUG_UNIFORMS + ALERT( at_aiconsole, "%c%s used %i uniforms\n", 3, shader->name, total_uniforms_used ); +#endif + if( total_uniforms_used > glConfig.peak_used_uniforms ) + { + glConfig.peak_used_uniforms = total_uniforms_used; + tr.show_uniforms_peak = true; + } + + pglUseProgramObjectARB( GL_NONE ); +} + +static void GL_SetDefaultVertexAttribs( glsl_program_t *shader ) +{ + pglBindAttribLocationARB( shader->handle, ATTR_INDEX_POSITION, "attr_Position" ); + pglBindAttribLocationARB( shader->handle, ATTR_INDEX_TEXCOORD0, "attr_TexCoord0" ); + pglBindAttribLocationARB( shader->handle, ATTR_INDEX_TEXCOORD1, "attr_TexCoord1" ); + pglBindAttribLocationARB( shader->handle, ATTR_INDEX_TEXCOORD2, "attr_TexCoord2" ); + pglBindAttribLocationARB( shader->handle, ATTR_INDEX_TANGENT, "attr_Tangent" ); + pglBindAttribLocationARB( shader->handle, ATTR_INDEX_BINORMAL, "attr_Binormal" ); + pglBindAttribLocationARB( shader->handle, ATTR_INDEX_NORMAL, "attr_Normal" ); + pglBindAttribLocationARB( shader->handle, ATTR_INDEX_BONE_INDEXES, "attr_BoneIndexes" ); + pglBindAttribLocationARB( shader->handle, ATTR_INDEX_BONE_WEIGHTS, "attr_BoneWeights" ); + pglBindAttribLocationARB( shader->handle, ATTR_INDEX_LIGHT_STYLES, "attr_LightStyles" ); + pglBindAttribLocationARB( shader->handle, ATTR_INDEX_LIGHT_COLOR, "attr_LightColor" ); + pglBindAttribLocationARB( shader->handle, ATTR_INDEX_LIGHT_VECS, "attr_LightVecs" ); + pglBindAttribLocationARB( shader->handle, ATTR_INDEX_LIGHT_NUMS0, "attr_LightNums0" ); + pglBindAttribLocationARB( shader->handle, ATTR_INDEX_LIGHT_NUMS1, "attr_LightNums1" ); +} + +static void GL_ParseProgramVertexAttribs( glsl_program_t *shader ) +{ + if( pglGetAttribLocationARB( shader->handle, "attr_Position" ) == ATTR_INDEX_POSITION ) + SetBits( shader->attribs, FATTR_POSITION ); + if( pglGetAttribLocationARB( shader->handle, "attr_TexCoord0" ) == ATTR_INDEX_TEXCOORD0 ) + SetBits( shader->attribs, FATTR_TEXCOORD0 ); + if( pglGetAttribLocationARB( shader->handle, "attr_TexCoord1" ) == ATTR_INDEX_TEXCOORD1 ) + SetBits( shader->attribs, FATTR_TEXCOORD1 ); + if( pglGetAttribLocationARB( shader->handle, "attr_TexCoord2" ) == ATTR_INDEX_TEXCOORD2 ) + SetBits( shader->attribs, FATTR_TEXCOORD2 ); + if( pglGetAttribLocationARB( shader->handle, "attr_Tangent" ) == ATTR_INDEX_TANGENT ) + SetBits( shader->attribs, FATTR_TANGENT ); + if( pglGetAttribLocationARB( shader->handle, "attr_Binormal" ) == ATTR_INDEX_BINORMAL ) + SetBits( shader->attribs, FATTR_BINORMAL ); + if( pglGetAttribLocationARB( shader->handle, "attr_Normal" ) == ATTR_INDEX_NORMAL ) + SetBits( shader->attribs, FATTR_NORMAL ); + if( pglGetAttribLocationARB( shader->handle, "attr_BoneIndexes" ) == ATTR_INDEX_BONE_INDEXES ) + SetBits( shader->attribs, FATTR_BONE_INDEXES ); + if( pglGetAttribLocationARB( shader->handle, "attr_BoneWeights" ) == ATTR_INDEX_BONE_WEIGHTS ) + SetBits( shader->attribs, FATTR_BONE_WEIGHTS ); + if( pglGetAttribLocationARB( shader->handle, "attr_LightStyles" ) == ATTR_INDEX_LIGHT_STYLES ) + SetBits( shader->attribs, FATTR_LIGHT_STYLES ); + if( pglGetAttribLocationARB( shader->handle, "attr_LightColor" ) == ATTR_INDEX_LIGHT_COLOR ) + SetBits( shader->attribs, FATTR_LIGHT_COLOR ); + if( pglGetAttribLocationARB( shader->handle, "attr_LightVecs" ) == ATTR_INDEX_LIGHT_VECS ) + SetBits( shader->attribs, FATTR_LIGHT_VECS ); + if( pglGetAttribLocationARB( shader->handle, "attr_LightNums0" ) == ATTR_INDEX_LIGHT_NUMS0 ) + SetBits( shader->attribs, FATTR_LIGHT_NUMS0 ); + if( pglGetAttribLocationARB( shader->handle, "attr_LightNums1" ) == ATTR_INDEX_LIGHT_NUMS1 ) + SetBits( shader->attribs, FATTR_LIGHT_NUMS1 ); +} + +void GL_BindShader( glsl_program_t *shader ) +{ + if( !shader && RI->currentshader ) + { + pglUseProgramObjectARB( GL_NONE ); + RI->currentshader = NULL; + } + else if( shader != RI->currentshader && shader != NULL && shader->handle ) + { + pglUseProgramObjectARB( shader->handle ); + r_stats.num_shader_binds++; + RI->currentshader = shader; + } +} + +static void GL_FreeGPUShader( glsl_program_t *shader ) +{ + if( shader && shader->handle ) + { + uint hash; + glsl_program_t *cur; + glsl_program_t **prev; + const char *find; + + find = va( "%s %s", shader->name, shader->options ); + if( shader->uniforms != NULL ) + { + Mem_Free( shader->uniforms ); + shader->uniforms = NULL; + } + + // remove from hash table + hash = COM_HashKey( find, SHADERS_HASH_SIZE ); + prev = &glsl_programsHashTable[hash]; + + while( 1 ) + { + cur = *prev; + if( !cur ) break; + + if( cur == shader ) + { + *prev = cur->nextHash; + break; + } + prev = &cur->nextHash; + } + + pglDeleteObjectARB( shader->handle ); + memset( shader, 0, sizeof( *shader )); + } +} + +static glsl_program_t *GL_CreateUberShader( GLint slot, const char *glname, const char *vpname, const char *fpname, const char *options, uint checksum ) +{ + if( !GL_Support( R_SHADER_GLSL100_EXT )) + return NULL; + + if( num_glsl_programs >= MAX_GLSL_PROGRAMS ) + { + ALERT( at_error, "GL_CreateUberShader: GLSL shaders limit exceeded (%i max)\n", MAX_GLSL_PROGRAMS ); + return NULL; + } + + // alloc new shader + glsl_program_t *shader = &glsl_programs[slot]; + + shader->handle = pglCreateProgramObjectARB(); + if( !shader->handle ) return NULL; // some bad happens + + Q_strncpy( shader->name, glname, sizeof( shader->name )); + Q_strncpy( shader->options, options, sizeof( shader->options )); + + if( !GL_LoadGPUBinaryShader( shader, vpname, fpname, checksum )) + { + if( vpname ) GL_LoadGPUShader( shader, vpname, GL_VERTEX_SHADER_ARB, options ); + else SetBits( shader->status, SHADER_VERTEX_COMPILED ); + if( fpname ) GL_LoadGPUShader( shader, fpname, GL_FRAGMENT_SHADER_ARB, options ); + else SetBits( shader->status, SHADER_FRAGMENT_COMPILED ); + + if( vpname && FBitSet( shader->status, SHADER_VERTEX_COMPILED )) + GL_SetDefaultVertexAttribs( shader ); + + GL_LinkProgram( shader ); + + if( FBitSet( shader->status, SHADER_PROGRAM_LINKED )) + GL_SaveGPUBinaryShader( shader, checksum ); + } + + // dynamic ubershaders has the identically name of fragment and vertex program + if(( glname == vpname ) && ( glname == fpname )) + SetBits( shader->status, SHADER_UBERSHADER ); // it's UberShader! + + if( FBitSet( shader->status, SHADER_PROGRAM_LINKED )) + { + // register shader uniforms + GL_ParseProgramVertexAttribs( shader ); + GL_ParseProgramUniforms( shader ); + GL_ValidateProgram( shader ); + } + + // rewind generated errors if shader compile was failed + // to avoid show them later e.g. on textures loading + while( pglGetError() != GL_NO_ERROR ); +#ifdef _DEBUG_UNIFORMS + if( FBitSet( shader->status, SHADER_UBERSHADER )) + ALERT( at_aiconsole, "CompileUberShader #%i: %s\n%s\n", slot, glname, options ); + else ALERT( at_aiconsole, "CompileShader #%i: %s\n%s\n", slot, glname, options ); +#endif + if( slot == num_glsl_programs ) + { + if( num_glsl_programs == MAX_GLSL_PROGRAMS ) + { + ALERT( at_error, "GL_CreateUberShader: GLSL shaders limit exceeded (%i max)\n", MAX_GLSL_PROGRAMS ); + GL_FreeGPUShader( shader ); + return NULL; + } + num_glsl_programs++; + } + + // all done, program is loaded + + return shader; +} + +word GL_FindUberShader( const char *glname, const char *options ) +{ + glsl_program_t *prog; + + if( !GL_Support( R_SHADER_GLSL100_EXT )) + return 0; + + ASSERT( glname != NULL ); + + const char *find = va( "%s %s", glname, options ); + uint hash = COM_HashKey( find, SHADERS_HASH_SIZE ); + + // check for coexist + for( prog = glsl_programsHashTable[hash]; prog != NULL; prog = prog->nextHash ) + { + if( !Q_strcmp( prog->name, glname ) && !Q_strcmp( prog->options, options )) + return (word)(prog - glsl_programs); + } + + // find free spot + for( int i = 1; i < num_glsl_programs; i++ ) + if( !glsl_programs[i].name[0] ) + break; + + double start = Sys_DoubleTime(); + uint checksum = FILE_CRC32( find, Q_strlen( find )); + prog = GL_CreateUberShader( i, glname, glname, glname, options, checksum ); + double end = Sys_DoubleTime(); + r_buildstats.compile_shader += (end - start); + if( RENDER_GET_PARM( PARM_CLIENT_ACTIVE, 0 )) + r_buildstats.total_buildtime += (end - start); + + if( prog != NULL ) + { + // add to hash table + prog->nextHash = glsl_programsHashTable[hash]; + glsl_programsHashTable[hash] = prog; + } + + return (word)(prog - glsl_programs); +} + +word GL_FindShader( const char *glname, const char *vpname, const char *fpname, const char *options ) +{ + glsl_program_t *prog; + + if( !GL_Support( R_SHADER_GLSL100_EXT )) + return 0; + + ASSERT( glname != NULL ); + + const char *find = va( "%s %s", glname, options ); + uint hash = COM_HashKey( find, SHADERS_HASH_SIZE ); + + // check for coexist + for( prog = glsl_programsHashTable[hash]; prog != NULL; prog = prog->nextHash ) + { + if( !Q_strcmp( prog->name, glname ) && !Q_strcmp( prog->options, options )) + return (word)(prog - glsl_programs); + } + + // find free spot + for( int i = 1; i < num_glsl_programs; i++ ) + if( !glsl_programs[i].name[0] ) + break; + + double start = Sys_DoubleTime(); + uint checksum = FILE_CRC32( find, Q_strlen( find )); + prog = GL_CreateUberShader( i, glname, vpname, fpname, options, checksum ); + double end = Sys_DoubleTime(); + r_buildstats.compile_shader += (end - start); + if( RENDER_GET_PARM( PARM_CLIENT_ACTIVE, 0 )) + r_buildstats.total_buildtime += (end - start); + + if( prog != NULL ) + { + // add to hash table + prog->nextHash = glsl_programsHashTable[hash]; + glsl_programsHashTable[hash] = prog; + } + + return (word)(prog - glsl_programs); +} + +void GL_AddShaderFeature( word shaderNum, int feature ) +{ + if( shaderNum <= 0 || shaderNum >= MAX_GLSL_PROGRAMS ) + return; + + glsl_program_t *shader = &glsl_programs[shaderNum]; + SetBits( shader->status, feature ); +} + +void GL_SetShaderDirective( char *options, const char *directive ) +{ + options[0] = '\0'; + Q_strncat( options, va( "#define %s\n", directive ), MAX_OPTIONS_LENGTH ); +} + +void GL_AddShaderDirective( char *options, const char *directive ) +{ + Q_strncat( options, va( "#define %s\n", directive ), MAX_OPTIONS_LENGTH ); +} + +const char *GL_PretifyListOptions( const char *options, bool newlines ) +{ + static char output[MAX_OPTIONS_LENGTH]; + const char *pstart = options; + const char *pend = options + Q_strlen( options ); + char *pout = output; + + *pout = '\0'; + + while( pstart < pend ) + { + const char *pfind = Q_strstr( pstart, "#define" ); + if( !pfind ) break; + + pstart = pfind + Q_strlen( "#define" ); + + for( ; *pstart != '\n'; pstart++, pout++ ) + *pout = *pstart; + if( newlines ) + *pout++ = *pstart++; + else pstart++; // skip '\n' + } + + if( pout == output ) + return ""; // nothing found + + *pout++ = ' '; + *pout = '\0'; + + return output; +} + +void GL_CheckTextureAlpha( char *options, int texturenum ) +{ + if( RENDER_GET_PARM( PARM_TEX_ENCODE, texturenum ) == DXT_ENCODE_ALPHA_SDF ) + GL_AddShaderDirective( options, "SIGNED_DISTANCE_FIELD" ); +} + +void GL_EncodeNormal( char *options, int texturenum ) +{ + if( RENDER_GET_PARM( PARM_TEX_GLFORMAT, texturenum ) == GL_COMPRESSED_RED_GREEN_RGTC2_EXT ) + { + GL_AddShaderDirective( options, "NORMAL_RG_PARABOLOID" ); + } + else if( RENDER_GET_PARM( PARM_TEX_GLFORMAT, texturenum ) == GL_COMPRESSED_LUMINANCE_ALPHA_3DC_ATI ) + { + GL_AddShaderDirective( options, "NORMAL_3DC_PARABOLOID" ); + } + else if( RENDER_GET_PARM( PARM_TEX_ENCODE, texturenum ) == DXT_ENCODE_NORMAL_AG_PARABOLOID ) + { + GL_AddShaderDirective( options, "NORMAL_AG_PARABOLOID" ); + } + else if( RENDER_GET_PARM( PARM_TEX_GLFORMAT, texturenum ) == GL_COMPRESSED_RGBA_S3TC_DXT5_EXT ) + { + // implicit DXT5NM format (Paranoia2 v 1.2 old stuff) + if( FBitSet( RENDER_GET_PARM( PARM_TEX_FLAGS, texturenum ), TF_HAS_ALPHA )) + GL_AddShaderDirective( options, "NORMAL_AG_PARABOLOID" ); + } +} + +void GL_ListGPUShaders( void ) +{ + int count = 0; + + for( uint i = 1; i < num_glsl_programs; i++ ) + { + glsl_program_t *cur = &glsl_programs[i]; + if( !cur->name[0] ) continue; + + const char *options = GL_PretifyListOptions( cur->options ); + + if( Q_stricmp( options, "" )) + Msg( "#%i %s [%s]\n", i, cur->name, options ); + else Msg( "#%i %s\n", i, cur->name ); + count++; + } + + Msg( "total %i shaders\n", count ); +} + +void GL_InitGPUShaders( void ) +{ + char options[MAX_OPTIONS_LENGTH]; + + memset( &glsl_programs, 0, sizeof( glsl_programs )); + num_glsl_programs = 1; // entry #0 isn't used + + if( !GL_Support( R_SHADER_GLSL100_EXT )) + return; + + ADD_COMMAND( "shaderlist", GL_ListGPUShaders ); + + // init sky shaders + GL_SetShaderDirective( options, "SKYBOX_DAYTIME" ); + tr.skyboxEnv[0] = GL_FindShader( "forward/skybox", "forward/generic", "forward/skybox" ); + tr.skyboxEnv[1] = GL_FindShader( "forward/skybox", "forward/generic", "forward/skybox", options ); + tr.defSceneSky = GL_FindShader( "deferred/skybox", "deferred/generic", "deferred/sky_scene" ); + tr.defLightSky = GL_FindShader( "deferred/skybox", "deferred/generic", "deferred/sky_light" ); +} + +void GL_FreeUberShaders( void ) +{ + if( !GL_Support( R_SHADER_GLSL100_EXT )) + return; + + for( uint i = 1; i < num_glsl_programs; i++ ) + { + if( FBitSet( glsl_programs[i].status, SHADER_UBERSHADER )) + GL_FreeGPUShader( &glsl_programs[i] ); + } + + GL_BindShader( GL_NONE ); +} + +void GL_FreeGPUShaders( void ) +{ + if( !GL_Support( R_SHADER_GLSL100_EXT )) + return; + + for( uint i = 1; i < num_glsl_programs; i++ ) + GL_FreeGPUShader( &glsl_programs[i] ); + + GL_BindShader( GL_NONE ); +} \ No newline at end of file diff --git a/cl_dll/render/gl_shader.h b/cl_dll/render/gl_shader.h new file mode 100644 index 0000000..118c677 --- /dev/null +++ b/cl_dll/render/gl_shader.h @@ -0,0 +1,262 @@ +/* +gl_shader.h - shader parsing and handling +this code written for Paranoia 2: Savior modification +Copyright (C) 2013 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef GL_SHADER_H +#define GL_SHADER_H + +#define MAX_OPTIONS_LENGTH 512 +#define MAX_GLSL_PROGRAMS 4096 + +#define SHADER_VERTEX_COMPILED BIT( 0 ) +#define SHADER_FRAGMENT_COMPILED BIT( 1 ) +#define SHADER_PROGRAM_LINKED BIT( 2 ) +#define SHADER_UBERSHADER BIT( 3 ) +#define SHADER_TRANSLUCENT BIT( 4 ) +#define SHADER_USE_CUBEMAPS BIT( 5 ) +#define SHADER_USE_SCREENCOPY BIT( 6 ) +#define SHADER_ADDITIVE BIT( 7 ) + +#define SHADER_STATUS_OK ( SHADER_PROGRAM_LINKED|SHADER_VERTEX_COMPILED|SHADER_FRAGMENT_COMPILED ) +#define CheckShader( shader ) ( shader && shader->status == SHADER_STATUS_OK ) + +enum +{ + ATTR_INDEX_POSITION = 0, + ATTR_INDEX_TANGENT, + ATTR_INDEX_BINORMAL, + ATTR_INDEX_NORMAL, + ATTR_INDEX_TEXCOORD0, // texture coord + ATTR_INDEX_TEXCOORD1, // lightmap coord (styles0-1) + ATTR_INDEX_TEXCOORD2, // lightmap coord (styles2-3) + ATTR_INDEX_BONE_INDEXES, // studiomodels only + ATTR_INDEX_BONE_WEIGHTS, // studiomodels only + ATTR_INDEX_LIGHT_STYLES, // brushmodels only + ATTR_INDEX_LIGHT_COLOR, // studio & grass + ATTR_INDEX_LIGHT_VECS, // studio & grass + ATTR_INDEX_LIGHT_NUMS0, // brushmodels only + ATTR_INDEX_LIGHT_NUMS1, // brushmodels only +}; + +// shader->attribs +#define FATTR_POSITION BIT( 0 ) +#define FATTR_TANGENT BIT( 1 ) +#define FATTR_BINORMAL BIT( 2 ) +#define FATTR_NORMAL BIT( 3 ) +#define FATTR_TEXCOORD0 BIT( 4 ) +#define FATTR_TEXCOORD1 BIT( 5 ) +#define FATTR_TEXCOORD2 BIT( 6 ) +#define FATTR_BONE_INDEXES BIT( 7 ) +#define FATTR_BONE_WEIGHTS BIT( 8 ) +#define FATTR_LIGHT_STYLES BIT( 9 ) +#define FATTR_LIGHT_COLOR BIT( 10 ) +#define FATTR_LIGHT_VECS BIT( 11 ) +#define FATTR_LIGHT_NUMS0 BIT( 12 ) +#define FATTR_LIGHT_NUMS1 BIT( 13 ) + +// uniform->flags +#define UFL_GLOBAL_PARM BIT( 0 ) +#define UFL_TEXTURE_UNIT BIT( 1 ) + +// uniform->type +typedef enum +{ + UT_COLORMAP = 0, + UT_DEPTHMAP, + UT_NORMALMAP, + UT_GLOSSMAP, + UT_DETAILMAP, + UT_PROJECTMAP, // spotlight texture + UT_SHADOWMAP0, + UT_SHADOWMAP1, + UT_SHADOWMAP2, + UT_SHADOWMAP3, + UT_SHADOWMAP, + UT_LIGHTMAP, + UT_DELUXEMAP, + UT_DECALMAP, + UT_SCREENMAP, + UT_VISLIGHTMAP0, + UT_VISLIGHTMAP1, + UT_ENVMAP0, + UT_ENVMAP1, + UT_ENVMAP, + UT_GLOWMAP, + UT_HEIGHTMAP, + UT_LAYERMAP, + UT_FRAGDATA0, + UT_FRAGDATA1, + UT_FRAGDATA2, + UT_BSPPLANESMAP, + UT_BSPNODESMAP, + UT_BSPLIGHTSMAP, + UT_BSPMODELSMAP, + UT_FITNORMALMAP, + UT_MODELMATRIX, + UT_REFLECTMATRIX, + UT_BONESARRAY, + UT_BONEQUATERNION, + UT_BONEPOSITION, + UT_SCREENSIZEINV, + UT_ZFAR, + UT_LIGHTSTYLEVALUES, + UT_LIGHTSTYLES, + UT_REALTIME, + UT_DETAILSCALE, + UT_FOGPARAMS, + UT_SHADOWPARMS, + UT_TEXOFFSET, + UT_VIEWORIGIN, + UT_VIEWRIGHT, + UT_RENDERCOLOR, + UT_RENDERALPHA, + UT_SMOOTHNESS, + UT_SHADOWMATRIX, + UT_SHADOWSPLITDIST, + UT_TEXELSIZE, + UT_GAMMATABLE, + UT_LIGHTDIR, + UT_LIGHTDIFFUSE, + UT_LIGHTSHADE, + UT_LIGHTORIGIN, + UT_LIGHTVIEWPROJMATRIX, + UT_DIFFUSEFACTOR, + UT_AMBIENTFACTOR, + UT_AMBIENTCUBE, + UT_SUNREFRACT, + UT_LERPFACTOR, + UT_REFRACTSCALE, + UT_REFLECTSCALE, + UT_ABERRATIONSCALE, + UT_BOXMINS, + UT_BOXMAXS, + UT_CUBEORIGIN, + UT_CUBEMIPCOUNT, + UT_LIGHTNUMS0, + UT_LIGHTNUMS1, + UT_GRASSPARAMS, + UT_RELIEFPARAMS, + UT_BLURFACTOR, + UT_SCREENWIDTH, + UT_SCREENHEIGHT, + UT_FOCALDEPTH, + UT_FOCALLENGTH, + UT_DOFDEBUG, + UT_FSTOP, + UT_GRAYSCALE, + UT_LIGHTGAMMA, + UT_LIGHTSCALE, + UT_LIGHTTHRESHOLD, + UT_NUMVISIBLEMODELS, + UT_UNDEFINED, +} uniformType_t; + +union unicache_t +{ + unicache_t( int v0 ) { iValue[0] = v0; iValue[1] = iValue[2] = iValue[3] = 0; } + unicache_t( int v0, int v1 ) { iValue[0] = v0; iValue[1] = v1; iValue[2] = iValue[3] = 0; } + unicache_t( int v0, int v1, int v2 ) { iValue[0] = v0; iValue[1] = v1; iValue[2] = v2; iValue[3] = 0; } + unicache_t( int v0, int v1, int v2, int v3 ) { iValue[0] = v0; iValue[1] = v1; iValue[2] = v2; iValue[3] = v3; } + unicache_t( float v0 ) { fValue[0] = v0; fValue[1] = fValue[2] = fValue[3] = 0.0f; } + unicache_t( float v0, float v1 ) { fValue[0] = v0; fValue[1] = v1; fValue[2] = fValue[3] = 0.0f; } + unicache_t( float v0, float v1, float v2 ) { fValue[0] = v0; fValue[1] = v1; fValue[2] = v2; fValue[3] = 0.0f; } + unicache_t( float v0, float v1, float v2, float v3 ) { fValue[0] = v0; fValue[1] = v1; fValue[2] = v2; fValue[3] = v3; } + + float fValue[4]; + int iValue[4]; +}; + +class uniform_t +{ +public: + char name[MAX_QPATH]; + uniformType_t type; + int size; + uint format; + int location; + int unit; // texture unit + int flags; // hints + unicache_t cache; + + // helpers + void SetValue( float v0 ) + { + unicache_t pack( v0 ); + SetValue( &pack ); + } + + void SetValue( float v0, float v1 ) + { + unicache_t pack( v0, v1 ); + SetValue( &pack ); + } + + void SetValue( float v0, float v1, float v2 ) + { + unicache_t pack( v0, v1, v2 ); + SetValue( &pack ); + } + + void SetValue( float v0, float v1, float v2, float v3 ) + { + unicache_t pack( v0, v1, v2, v3 ); + SetValue( &pack ); + } + + void SetValue( int v0 ) + { + unicache_t pack( v0 ); + SetValue( &pack ); + } + + void SetValue( int v0, int v1 ) + { + unicache_t pack( v0, v1 ); + SetValue( &pack ); + } + + void SetValue( int v0, int v1, int v2 ) + { + unicache_t pack( v0, v1, v2 ); + SetValue( &pack ); + } + + void SetValue( int v0, int v1, int v2, GLint v3 ) + { + unicache_t pack( v0, v1, v2, v3 ); + SetValue( &pack ); + } + + // passed any data here + void SetValue( const void *pdata, int count = -1 ); + int GetSizeInBytes( void ); +}; + +typedef struct glsl_prog_s +{ + char name[64]; + char options[MAX_OPTIONS_LENGTH]; // UberShader preprocess agrs + GLhandleARB handle; + unsigned short status; + struct glsl_prog_s *nextHash; + unsigned short attribs; + uniform_t *uniforms; + int numUniforms; +} glsl_program_t; + +extern glsl_program_t glsl_programs[MAX_GLSL_PROGRAMS]; +extern int num_glsl_programs; + +#endif//GL_SHADER_H \ No newline at end of file diff --git a/cl_dll/render/gl_shadowmap.cpp b/cl_dll/render/gl_shadowmap.cpp new file mode 100644 index 0000000..e92471c --- /dev/null +++ b/cl_dll/render/gl_shadowmap.cpp @@ -0,0 +1,138 @@ +/* +gl_shadowmap.cpp - render shadowmaps for directional lights +Copyright (C) 2018 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "gl_local.h" +#include +#include +#include "gl_studio.h" +#include "gl_sprite.h" +#include "gl_world.h" +#include "gl_grass.h" +#include "pm_defs.h" + +/* +============================================================================= + + SHADOWMAP ALLOCATION + +============================================================================= +*/ +static void SM_InitBlock( void ) +{ + gl_shadowmap_t *sms = &tr.shadowmap; + memset( sms->allocated, 0, sizeof( sms->allocated )); + sms->shadowmap.Init( FBO_DEPTH, SHADOW_SIZE, SHADOW_SIZE ); +} + +static int SM_AllocBlock( unsigned short w, unsigned short h, unsigned short *x, unsigned short *y ) +{ + gl_shadowmap_t *sms = &tr.shadowmap; + unsigned short i, j, best, best2; + + best = SHADOW_SIZE; + + for( i = 0; i < SHADOW_SIZE - w; i++ ) + { + best2 = 0; + + for( j = 0; j < w; j++ ) + { + if( sms->allocated[i+j] >= best ) + break; + if( sms->allocated[i+j] > best2 ) + best2 = sms->allocated[i+j]; + } + + if( j == w ) + { + // this is a valid spot + *x = i; + *y = best = best2; + } + } + + // atlas is full + if( best + h > SHADOW_SIZE ) + return false; + + for( i = 0; i < w; i++ ) + sms->allocated[*x + i] = best + h; + + return true; +} + +/* +================ +R_RenderShadowScene + +fast version of R_RenderScene: no colors, no texcords etc +================ +*/ +void R_RenderShadow( mworldlight_t *wl ) +{ + if( !wl->shadow_w || !wl->shadow_h ) + { + wl->shadow_w = 256; + wl->shadow_h = 256; + SM_AllocBlock( wl->shadow_w, wl->shadow_h, &wl->shadow_x, &wl->shadow_y ); + } +} + +void R_RenderDeferredShadows( void ) +{ + unsigned int oldFBO; + mworldlight_t *wl; + int i; + + if( R_FullBright() || !CVAR_TO_BOOL( r_shadows ) || tr.fGamePaused ) + return; + + if( FBitSet( RI->params, ( RP_NOSHADOWS|RP_ENVVIEW|RP_SKYVIEW ))) + return; + + // check for static lights + if( !HasStaticLights( )) return; + + R_PushRefState(); // make refinst backup + oldFBO = glState.frameBuffer; + + for( i = 0, wl = world->worldlights; i < world->numworldlights; i++, wl++ ) + { + if( wl->emittype == emit_ignored ) + continue; + + // single visible check + if( !CHECKVISBIT( RI->view.vislight, i )) + continue; + + switch( wl->emittype ) + { + case emit_surface: + R_RenderShadow( wl ); + break; + } + + R_ResetRefState(); // restore ref instance + } + + R_PopRefState(); // restore ref instance + + // restore FBO state + GL_BindFBO( oldFBO ); + GL_BindShader( NULL ); +} \ No newline at end of file diff --git a/cl_dll/render/gl_shadows.cpp b/cl_dll/render/gl_shadows.cpp new file mode 100644 index 0000000..1a4e38e --- /dev/null +++ b/cl_dll/render/gl_shadows.cpp @@ -0,0 +1,658 @@ +/* +gl_shadows.cpp - render shadowmaps for directional lights +Copyright (C) 2012 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "gl_local.h" +#include +#include +#include "gl_studio.h" +#include "gl_sprite.h" +#include "gl_world.h" +#include "gl_grass.h" +#include "pm_defs.h" + +static Vector light_sides[] = +{ +Vector( 0.0f, 0.0f, 90.0f ), // GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB +Vector( 0.0f, 180.0f, -90.0f ), // GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB +Vector( 0.0f, 90.0f, 0.0f ), // GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB +Vector( 0.0f, 270.0f, 180.0f ), // GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB +Vector(-90.0f, 180.0f, -90.0f ), // GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB +Vector( 90.0f, 0.0f, 90.0f ), // GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB +}; + +/* +============================================================= + + SHADOW RENDERING + +============================================================= +*/ +int R_AllocateShadowTexture( bool copyToImage = true ) +{ + int i = tr.num_2D_shadows_used; + + if( i >= MAX_SHADOWS ) + { + ALERT( at_error, "R_AllocateShadowTexture: shadow textures limit exceeded!\n" ); + return 0; // disable + } + + int texture = tr.shadowTextures[i]; + tr.num_2D_shadows_used++; + + if( !tr.shadowTextures[i] ) + { + char txName[16]; + + Q_snprintf( txName, sizeof( txName ), "*shadow2D%i", i ); + + tr.shadowTextures[i] = CREATE_TEXTURE( txName, RI->view.port[2], RI->view.port[3], NULL, TF_SHADOW ); + texture = tr.shadowTextures[i]; + } + + if( copyToImage ) + { + GL_BindTexture( GL_TEXTURE0, texture ); + pglCopyTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, RI->view.port[0], RI->view.port[1], RI->view.port[2], RI->view.port[3], 0 ); + } + + return texture; +} + +int R_AllocateShadowCubemap( int side, bool copyToImage = true ) +{ + int texture = 0; + + if( side != 0 ) + { + int i = (tr.num_CM_shadows_used - 1); + + if( i >= MAX_SHADOWS ) + { + ALERT( at_error, "R_AllocateShadowCubemap: shadow cubemaps limit exceeded!\n" ); + return 0; // disable + } + + texture = tr.shadowCubemaps[i]; + + if( !tr.shadowCubemaps[i] ) + { + ALERT( at_error, "R_AllocateShadowCubemap: cubemap not initialized!\n" ); + return 0; // disable + } + } + else + { + int i = tr.num_CM_shadows_used; + + if( i >= MAX_SHADOWS ) + { + ALERT( at_error, "R_AllocateShadowCubemap: shadow cubemaps limit exceeded!\n" ); + return 0; // disable + } + + texture = tr.shadowCubemaps[i]; + tr.num_CM_shadows_used++; + + if( !tr.shadowCubemaps[i] ) + { + char txName[16]; + + Q_snprintf( txName, sizeof( txName ), "*shadowCM%i", i ); + + tr.shadowCubemaps[i] = CREATE_TEXTURE( txName, RI->view.port[2], RI->view.port[3], NULL, TF_SHADOW_CUBEMAP ); + texture = tr.shadowCubemaps[i]; + } + } + + if( copyToImage ) + { + GL_BindTexture( GL_TEXTURE0, texture ); + pglCopyTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB + side, 0, GL_DEPTH_COMPONENT, RI->view.port[0], RI->view.port[1], RI->view.port[2], RI->view.port[3], 0 ); + } + + return texture; +} + +static int R_ComputeCropBounds( const matrix4x4 &lightViewProjection, Vector bounds[2] ) +{ + Vector worldBounds[2]; + int numCasters = 0; + ref_instance_t *prevRI = R_GetPrevInstance(); + CFrustum frustum; + + ClearBounds( bounds[0], bounds[1] ); + + frustum.InitProjectionFromMatrix( lightViewProjection ); + + // FIXME: nearplane culled studiomodels incorrectly. disabled for now + frustum.DisablePlane( FRUSTUM_NEAR ); + frustum.DisablePlane( FRUSTUM_FAR ); + + for( int i = 0; i < prevRI->frame.solid_faces.Count(); i++ ) + { + CSolidEntry *entry = &prevRI->frame.solid_faces[i]; + + if( entry->m_bDrawType != DRAWTYPE_SURFACE ) + continue; + + mextrasurf_t *es = entry->m_pSurf->info; + RI->currentmodel = es->parent->model; + RI->currententity = es->parent; + msurface_t *s = entry->m_pSurf; + + bool worldpos = R_StaticEntity( RI->currententity ); + if( !worldpos ) continue; // world polys only + + if( es->grass && CVAR_TO_BOOL( r_grass )) + { + // already included surface minmax + worldBounds[0] = es->grass->mins; + worldBounds[1] = es->grass->maxs; + } + else + { + worldBounds[0] = es->mins; + worldBounds[1] = es->maxs; + } + + if( frustum.CullBox( worldBounds[0], worldBounds[1] )) + continue; + + for( int j = 0; j < 8; j++ ) + { + Vector4D point; + point.x = worldBounds[(j >> 0) & 1].x; + point.y = worldBounds[(j >> 1) & 1].y; + point.z = worldBounds[(j >> 2) & 1].z; + point.w = 1.0f; + + Vector4D transf = lightViewProjection.VectorTransform( point ); + + transf.x /= transf.w; + transf.y /= transf.w; + transf.z /= transf.w; + + AddPointToBounds( transf, bounds[0], bounds[1] ); + } + numCasters++; + } + + // add studio models too + for( i = 0; i < prevRI->frame.solid_meshes.Count(); i++ ) + { + if( !R_StudioGetBounds( &prevRI->frame.solid_meshes[i], worldBounds )) + continue; + + if( frustum.CullBox( worldBounds[0], worldBounds[1] )) + continue; + + for( int j = 0; j < 8; j++ ) + { + Vector4D point; + point.x = worldBounds[(j >> 0) & 1].x; + point.y = worldBounds[(j >> 1) & 1].y; + point.z = worldBounds[(j >> 2) & 1].z; + point.w = 1.0f; + + Vector4D transf = lightViewProjection.VectorTransform( point ); + transf.x /= transf.w; + transf.y /= transf.w; + transf.z /= transf.w; + + AddPointToBounds( transf, bounds[0], bounds[1] ); + } + numCasters++; + } + + return numCasters; +} + +/* +=============== +R_SetupLightDirectional +=============== +*/ +void R_SetupLightDirectional( CDynLight *pl, int split ) +{ + matrix4x4 projectionMatrix, cropMatrix, s1; + Vector splitFrustumCorners[8]; + Vector splitFrustumBounds[2]; + Vector splitFrustumClipBounds[2]; + Vector casterBounds[2]; + Vector cropBounds[2]; + int i; + + RI->view.splitFrustum[split].ComputeFrustumCorners( splitFrustumCorners ); + + ClearBounds( splitFrustumBounds[0], splitFrustumBounds[1] ); + + for( i = 0; i < 8; i++ ) + AddPointToBounds( splitFrustumCorners[i], splitFrustumBounds[0], splitFrustumBounds[1] ); + + // find the bounding box of the current split in the light's view space + ClearBounds( cropBounds[0], cropBounds[1] ); + + for( i = 0; i < 8; i++ ) + { + Vector4D point( splitFrustumCorners[i] ); + Vector4D transf = pl->viewMatrix.VectorTransform( point ); + + transf.x /= transf.w; + transf.y /= transf.w; + transf.z /= transf.w; + + AddPointToBounds( transf, cropBounds[0], cropBounds[1] ); + } + + projectionMatrix.CreateOrthoRH( cropBounds[0].x, cropBounds[1].x, cropBounds[0].y, cropBounds[1].y, -cropBounds[1].z, -cropBounds[0].z ); + + matrix4x4 viewProjectionMatrix = projectionMatrix.Concat( pl->viewMatrix ); + + int numCasters = R_ComputeCropBounds( viewProjectionMatrix, casterBounds ); + + // find the bounding box of the current split in the light's clip space + ClearBounds( splitFrustumClipBounds[0], splitFrustumClipBounds[1] ); + + for( i = 0; i < 8; i++ ) + { + Vector4D point( splitFrustumCorners[i] ); + Vector4D transf = viewProjectionMatrix.VectorTransform( point ); + + transf.x /= transf.w; + transf.y /= transf.w; + transf.z /= transf.w; + + AddPointToBounds( transf, splitFrustumClipBounds[0], splitFrustumClipBounds[1] ); + } + + // scene-dependent bounding volume + cropBounds[0].x = Q_max( casterBounds[0].x, splitFrustumClipBounds[0].x ); + cropBounds[0].y = Q_max( casterBounds[0].y, splitFrustumClipBounds[0].y ); + cropBounds[0].z = Q_min( casterBounds[0].z, splitFrustumClipBounds[0].z ); + cropBounds[1].x = Q_min( casterBounds[1].x, splitFrustumClipBounds[1].x ); + cropBounds[1].y = Q_min( casterBounds[1].y, splitFrustumClipBounds[1].y ); + cropBounds[1].z = Q_max( casterBounds[1].z, splitFrustumClipBounds[1].z ); + + if( numCasters == 0 ) + { + cropBounds[0] = splitFrustumClipBounds[0]; + cropBounds[1] = splitFrustumClipBounds[1]; + } + + cropMatrix.Crop( cropBounds[0], cropBounds[1] ); + pl->projectionMatrix = cropMatrix.Concat( projectionMatrix ); + + s1.CreateTranslate( 0.5f, 0.5f, 0.5f ); + s1.ConcatScale( 0.5f, 0.5f, 0.5f ); + + viewProjectionMatrix = pl->projectionMatrix.Concat( pl->modelviewMatrix ); + + // NOTE: texture matrix is not used. Save it for pssm show split debug tool + pl->textureMatrix[split] = pl->projectionMatrix; + + // build shadow matrices for each split + pl->shadowMatrix[split] = s1.Concat( viewProjectionMatrix ); + pl->shadowMatrix[split].CopyToArray( pl->gl_shadowMatrix[split] ); + + RI->view.frustum.InitProjectionFromMatrix( viewProjectionMatrix ); +} + +/* +=============== +R_ShadowPassSetupViewCache +=============== +*/ +static void R_ShadowPassSetupViewCache( CDynLight *pl, int split = 0 ) +{ + memcpy( RI->glstate.viewport, RI->view.port, sizeof( RI->glstate.viewport )); + + RI->view.farClip = pl->radius; + RI->view.origin = pl->origin; + + // setup the screen FOV + RI->view.fov_x = pl->fov; + RI->view.fov_y = pl->fov; + + // setup frustum + if( pl->type == LIGHT_DIRECTIONAL ) + { + pl->splitFrustum[split] = RI->view.splitFrustum[split]; + RI->view.matrix = pl->viewMatrix; + R_SetupLightDirectional( pl, split ); + } + else if( pl->type == LIGHT_OMNI ) + { + RI->view.angles = light_sides[split]; // this is cube side of course + RI->view.matrix = matrix4x4( RI->view.origin, RI->view.angles ); + RI->view.frustum.InitProjection( RI->view.matrix, 0.1f, pl->radius, 90.0f, 90.0f ); + } + else + { + RI->view.matrix = pl->viewMatrix; + RI->view.frustum = pl->frustum; + } + + if( pl->type == LIGHT_OMNI ) + { + RI->view.worldMatrix.CreateModelview(); + RI->view.worldMatrix.ConcatRotate( -light_sides[split].z, 1, 0, 0 ); + RI->view.worldMatrix.ConcatRotate( -light_sides[split].x, 0, 1, 0 ); + RI->view.worldMatrix.ConcatRotate( -light_sides[split].y, 0, 0, 1 ); + RI->view.worldMatrix.ConcatTranslate( -pl->origin.x, -pl->origin.y, -pl->origin.z ); + RI->view.projectionMatrix = pl->projectionMatrix; + } + else + { + // matrices already computed + RI->view.worldMatrix = pl->modelviewMatrix; + RI->view.projectionMatrix = pl->projectionMatrix; + } + + RI->view.worldProjectionMatrix = RI->view.projectionMatrix.Concat( RI->view.worldMatrix ); + RI->view.worldProjectionMatrix.CopyToArray( RI->glstate.modelviewProjectionMatrix ); + RI->view.projectionMatrix.CopyToArray( RI->glstate.projectionMatrix ); + RI->view.worldMatrix.CopyToArray( RI->glstate.modelviewMatrix ); + + RI->currentlight = pl; + + R_MarkWorldVisibleFaces( worldmodel ); + + msurface_t *surf; + mextrasurf_t *esrf; + int i, j; + + // add all studio models, mark visible bmodels + for( i = 0; i < tr.num_draw_entities; i++ ) + { + RI->currententity = tr.draw_entities[i]; + RI->currentmodel = RI->currententity->model; + + switch( RI->currentmodel->type ) + { + case mod_studio: + R_AddStudioToDrawList( RI->currententity ); + break; + case mod_brush: + R_MarkSubmodelVisibleFaces(); + break; + } + } + + // create drawlist for faces, do additional culling for world faces + for( i = 0; i < world->numsortedfaces; i++ ) + { + ASSERT( world->sortedfaces != NULL ); + + j = world->sortedfaces[i]; + + ASSERT( j >= 0 && j < worldmodel->numsurfaces ); + + if( CHECKVISBIT( RI->view.visfaces, j )) + { + surf = worldmodel->surfaces + j; + esrf = surf->info; + + // submodel faces already passed through this + // operation but world is not + if( FBitSet( surf->flags, SURF_OF_SUBMODEL )) + { + RI->currententity = esrf->parent; + RI->currentmodel = RI->currententity->model; + + R_AddGrassToDrawList( surf, DRAWLIST_SHADOW ); + } + else + { + RI->currententity = GET_ENTITY( 0 ); + RI->currentmodel = RI->currententity->model; + + esrf->parent = RI->currententity; // setup dynamic upcast + + R_AddGrassToDrawList( surf, DRAWLIST_SHADOW ); + + if( R_CullSurface( surf, GetVieworg(), &RI->view.frustum )) + { + CLEARVISBIT( RI->view.visfaces, j ); // not visible + continue; + } + } + + // only opaque faces interesting us + if( R_OpaqueEntity( RI->currententity )) + { + R_AddSurfaceToDrawList( surf, DRAWLIST_SHADOW ); + } + } + } +} + +/* +============= +R_ShadowPassSetupGL +============= +*/ +static void R_ShadowPassSetupGL( const CDynLight *pl ) +{ + R_SetupGLstate(); + + pglEnable( GL_POLYGON_OFFSET_FILL ); + + if( RI->currentlight->type == LIGHT_DIRECTIONAL ) + { + if( r_shadows->value > 2.0f ) + pglPolygonOffset( 3.0f, 0.0f ); + else pglPolygonOffset( 2.0f, 0.0f ); + GL_Cull( GL_NONE ); + } + else + { + if( r_shadows->value > 2.0f ) + pglPolygonOffset( 2.0f, 0.0f ); + else pglPolygonOffset( 1.0f, 0.0f ); + GL_Cull( GL_FRONT ); + } + + // HACKHACK to ignore paranoia opengl32.dll + GL_DepthRange( 0.0001f, 1.0f ); + pglEnable( GL_DEPTH_TEST ); + GL_AlphaTest( GL_FALSE ); + GL_DepthMask( GL_TRUE ); + GL_Blend( GL_FALSE ); +} + +/* +============= +R_ShadowPassEndGL +============= +*/ +static void R_ShadowPassEndGL( void ) +{ + pglDisable( GL_POLYGON_OFFSET_FILL ); + GL_DepthRange( gldepthmin, gldepthmax ); + pglPolygonOffset( -1, -2 ); + r_stats.c_shadow_passes++; + GL_Cull( GL_FRONT ); +} + +/* +================ +R_RenderShadowScene + +fast version of R_RenderScene: no colors, no texcords etc +================ +*/ +void R_RenderShadowScene( CDynLight *pl, int split = 0 ) +{ + RI->params = RP_SHADOWVIEW; + bool using_fbo = false; + + if( pl->type == LIGHT_DIRECTIONAL ) + { + if( tr.sunShadowFBO[split].Active( )) + { + RI->view.port[2] = RI->view.port[3] = sunSize[split]; + + pl->shadowTexture[split] = tr.sunShadowFBO[split].GetTexture(); + tr.sunShadowFBO[split].Bind(); + using_fbo = true; + } + else RI->view.port[2] = RI->view.port[3] = 512; // simple size if FBO was missed + } + else + { + if( tr.fbo_shadow2D.Active( )) + { + RI->view.port[2] = tr.fbo_shadow2D.GetWidth(); + RI->view.port[3] = tr.fbo_shadow2D.GetHeight(); + + pl->shadowTexture[0] = R_AllocateShadowTexture( false ); + tr.fbo_shadow2D.Bind( pl->shadowTexture[0] ); + using_fbo = true; + } + else RI->view.port[2] = RI->view.port[3] = 512; + } + + R_ShadowPassSetupViewCache( pl, split ); + R_ShadowPassSetupGL( pl ); + + pglClear( GL_DEPTH_BUFFER_BIT ); + + R_RenderShadowBrushList(); + R_RenderShadowStudioList(); + R_DrawParticles( false ); + R_ShadowPassEndGL(); + + if( !using_fbo ) + pl->shadowTexture[split] = R_AllocateShadowTexture(); +} + +/* +================ +R_RenderShadowCubeSide + +fast version of R_RenderScene: no colors, no texcords etc +================ +*/ +void R_RenderShadowCubeSide( CDynLight *pl, int side ) +{ + RI->params = RP_SHADOWVIEW; + bool using_fbo = false; + + if( tr.fbo_shadowCM.Active( )) + { + RI->view.port[2] = tr.fbo_shadowCM.GetWidth(); + RI->view.port[3] = tr.fbo_shadowCM.GetHeight(); + + pl->shadowTexture[0] = R_AllocateShadowCubemap( side, false ); + tr.fbo_shadowCM.Bind( pl->shadowTexture[0], side ); + using_fbo = true; + } + else + { + // same size if FBO was missed + RI->view.port[2] = RI->view.port[3] = 512; + using_fbo = false; + } + + R_ShadowPassSetupViewCache( pl, side ); + R_ShadowPassSetupGL( pl ); + + pglClear( GL_DEPTH_BUFFER_BIT ); + + R_RenderShadowBrushList(); + R_RenderShadowStudioList(); + R_DrawParticles( false ); + R_ShadowPassEndGL(); + + if( !using_fbo ) + pl->shadowTexture[0] = R_AllocateShadowCubemap( side ); +} + +void R_RenderShadowmaps( void ) +{ + unsigned int oldFBO; + + if( R_FullBright() || !CVAR_TO_BOOL( r_shadows ) || tr.fGamePaused ) + return; + + if( FBitSet( RI->params, ( RP_NOSHADOWS|RP_ENVVIEW|RP_SKYVIEW ))) + return; + + // check for dynamic lights + if( !HasDynamicLights( )) return; + + R_PushRefState(); // make refinst backup + oldFBO = glState.frameBuffer; + + for( int i = 0; i < MAX_DLIGHTS; i++ ) + { + CDynLight *pl = &tr.dlights[i]; + + if( !pl->Active() || FBitSet( pl->flags, DLF_NOSHADOWS )) + continue; + + if( pl->type == LIGHT_OMNI ) + { + // need GL_EXT_gpu_shader4 for cubemap shadows + if( !GL_Support( R_TEXTURECUBEMAP_EXT ) || !GL_Support( R_EXT_GPU_SHADER4 )) + continue; + + if( !Mod_CheckBoxVisible( pl->absmin, pl->absmax )) + continue; + + if( R_CullBox( pl->absmin, pl->absmax )) + continue; + + for( int j = 0; j < 6; j++ ) + { + R_RenderShadowCubeSide( pl, j ); + R_ResetRefState(); // restore ref instance + } + } + else if( pl->type == LIGHT_SPOT ) + { + if( !Mod_CheckBoxVisible( pl->absmin, pl->absmax )) + continue; + + if( R_CullBox( pl->absmin, pl->absmax )) + continue; + + R_RenderShadowScene( pl ); + R_ResetRefState(); // restore ref instance + } + else if( pl->type == LIGHT_DIRECTIONAL ) + { + if( !CVAR_TO_BOOL( r_sunshadows ) || tr.sky_normal.z >= 0.0f ) + continue; // shadows are invisible + + for( int j = 0; j <= NUM_SHADOW_SPLITS; j++ ) + { + // PSSM: draw all the splits + R_RenderShadowScene( pl, j ); + R_ResetRefState(); // restore ref instance + } + } + } + + R_PopRefState(); // restore ref instance + // restore FBO state + GL_BindFBO( oldFBO ); + GL_BindShader( NULL ); + RI->currentlight = NULL; +} \ No newline at end of file diff --git a/cl_dll/render/gl_shadows.new b/cl_dll/render/gl_shadows.new new file mode 100644 index 0000000..bffc1f8 --- /dev/null +++ b/cl_dll/render/gl_shadows.new @@ -0,0 +1,1272 @@ +/* +gl_shadows.cpp - render shadowmaps for directional lights +Copyright (C) 2012 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "gl_local.h" +#include +#include +#include "gl_studio.h" +#include "gl_sprite.h" +#include "gl_world.h" +#include "gl_grass.h" +#include "pm_defs.h" + +static Vector light_sides[] = +{ +Vector( 0.0f, 0.0f, 90.0f ), // GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB +Vector( 0.0f, 180.0f, -90.0f ), // GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB +Vector( 0.0f, 90.0f, 0.0f ), // GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB +Vector( 0.0f, 270.0f, 180.0f ), // GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB +Vector(-90.0f, 180.0f, -90.0f ), // GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB +Vector( 90.0f, 0.0f, 90.0f ), // GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB +}; + +/* +============================================================= + + SHADOW RENDERING + +============================================================= +*/ +int R_AllocateShadowTexture( bool copyToImage = true ) +{ + int i = tr.num_2D_shadows_used; + + if( i >= MAX_SHADOWS ) + { + ALERT( at_error, "R_AllocateShadowTexture: shadow textures limit exceeded!\n" ); + return 0; // disable + } + + int texture = tr.shadowTextures[i]; + tr.num_2D_shadows_used++; + + if( !tr.shadowTextures[i] ) + { + char txName[16]; + + Q_snprintf( txName, sizeof( txName ), "*shadow2D%i", i ); + + tr.shadowTextures[i] = CREATE_TEXTURE( txName, RI->view.port[2], RI->view.port[3], NULL, TF_SHADOW ); + texture = tr.shadowTextures[i]; + } + + if( copyToImage ) + { + GL_BindTexture( GL_TEXTURE0, texture ); + pglCopyTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, RI->view.port[0], RI->view.port[1], RI->view.port[2], RI->view.port[3], 0 ); + } + + return texture; +} + +int R_AllocateShadowCubemap( int side, bool copyToImage = true ) +{ + int texture = 0; + + if( side != 0 ) + { + int i = (tr.num_CM_shadows_used - 1); + + if( i >= MAX_SHADOWS ) + { + ALERT( at_error, "R_AllocateShadowCubemap: shadow cubemaps limit exceeded!\n" ); + return 0; // disable + } + + texture = tr.shadowCubemaps[i]; + + if( !tr.shadowCubemaps[i] ) + { + ALERT( at_error, "R_AllocateShadowCubemap: cubemap not initialized!\n" ); + return 0; // disable + } + } + else + { + int i = tr.num_CM_shadows_used; + + if( i >= MAX_SHADOWS ) + { + ALERT( at_error, "R_AllocateShadowCubemap: shadow cubemaps limit exceeded!\n" ); + return 0; // disable + } + + texture = tr.shadowCubemaps[i]; + tr.num_CM_shadows_used++; + + if( !tr.shadowCubemaps[i] ) + { + char txName[16]; + + Q_snprintf( txName, sizeof( txName ), "*shadowCM%i", i ); + + tr.shadowCubemaps[i] = CREATE_TEXTURE( txName, RI->view.port[2], RI->view.port[3], NULL, TF_SHADOW_CUBEMAP ); + texture = tr.shadowCubemaps[i]; + } + } + + if( copyToImage ) + { + GL_BindTexture( GL_TEXTURE0, texture ); + pglCopyTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB + side, 0, GL_DEPTH_COMPONENT, RI->view.port[0], RI->view.port[1], RI->view.port[2], RI->view.port[3], 0 ); + } + + return texture; +} + +static int R_ComputeCropBounds( const matrix4x4 &lightViewProjection, model_t *model, Vector bounds[2] ) +{ + Vector worldBounds[2]; + int numCasters = 0; + msurface_t *surf, **mark; + mextrasurf_t *esrf; + mleaf_t *leaf; + int i, j; + + if( r_sunshadows->value < 2.0f ) + model = NULL; // don't add the world + + ClearBounds( bounds[0], bounds[1] ); + + if( model != NULL ) + { + RI->currententity = GET_ENTITY( 0 ); + RI->currentmodel = RI->currententity->model; + R_GrassPrepareFrame(); + } + + // orthogonal frustum for selected slice + // NOTE: combined PVS is already set + RI->view.frustum.InitProjectionFromMatrix( lightViewProjection ); + memset( RI->view.visfaces, 0x00, (model->numsurfaces + 7) >> 3 ); + + if( model != NULL ) + { + // always skip the leaf 0, because is outside leaf + // tehnically it's equal of job R_MarkLeaves + for( i = 1, leaf = &model->leafs[1]; i < model->numleafs + 1; i++, leaf++ ) + { + mextraleaf_t *eleaf = LEAF_INFO( leaf, model ); // named like personal vaporisers manufacturer he-he + + if( CHECKVISBIT( RI->view.pvsarray, leaf->cluster ) && ( leaf->efrags || leaf->nummarksurfaces )) + { + if( RI->view.frustum.CullBox( eleaf->mins, eleaf->maxs )) + continue; + + // deal with model fragments in this leaf + if( leaf->efrags ) + STORE_EFRAGS( &leaf->efrags, tr.realframecount ); + r_stats.c_world_leafs++; + + if( leaf->nummarksurfaces ) + { + for( j = 0, mark = leaf->firstmarksurface; j < leaf->nummarksurfaces; j++, mark++ ) + SETVISBIT( RI->view.visfaces, *mark - model->surfaces ); + } + } + } + } + + // brush faces not added here! + // only marks as visible in RI->view.visfaces array + for( i = 0; i < tr.num_draw_entities; i++ ) + { + RI->currententity = tr.draw_entities[i]; + RI->currentmodel = RI->currententity->model; + + switch( RI->currentmodel->type ) + { + case mod_brush: + R_MarkSubmodelVisibleFaces(); + break; + case mod_studio: + R_AddStudioToDrawList( RI->currententity ); + break; + } + } + + // create drawlist for faces, do additional culling for world faces + for( i = 0; model != NULL && i < world->numsortedfaces; i++ ) + { + ASSERT( world->sortedfaces != NULL ); + + j = world->sortedfaces[i]; + + ASSERT( j >= 0 && j < model->numsurfaces ); + + if( CHECKVISBIT( RI->view.visfaces, j )) + { + surf = model->surfaces + j; + esrf = surf->info; + + // submodel faces already passed through this + // operation but world is not + if( FBitSet( surf->flags, SURF_OF_SUBMODEL )) + { + RI->currententity = esrf->parent; + RI->currentmodel = RI->currententity->model; + } + else + { + RI->currententity = GET_ENTITY( 0 ); + RI->currentmodel = RI->currententity->model; + + esrf->parent = RI->currententity; // setup dynamic upcast + + bool force = R_AddGrassToChain( NULL, surf, RI->currententity, &RI->view.frustum, 0 ); + + if( !force && R_CullSurface( surf, GetVieworg(), &RI->view.frustum )) + { + CLEARVISBIT( RI->view.visfaces, j ); // not visible + continue; + } + } + + if( R_OpaqueEntity( RI->currententity )) + R_AddSurfaceToDrawList( surf, DRAWLIST_SOLID ); + } + } + + for( i = 0; i < RI->frame.num_solid_faces; i++ ) + { + gl_bmodelface_t *entry = &RI->frame.solid_faces[i]; + mextrasurf_t *es = entry->surface->info; + RI->currentmodel = es->parent->model; + RI->currententity = es->parent; + msurface_t *s = entry->surface; + + bool worldpos = ( RI->currententity->origin == g_vecZero && RI->currententity->angles == g_vecZero ) ? true : false; + if( !worldpos ) continue; // world polys only + + if( es->grass && r_grass->value ) + { + // already included surface minmax + worldBounds[0] = es->grass->mins; + worldBounds[1] = es->grass->maxs; + } + else + { + worldBounds[0] = es->mins; + worldBounds[1] = es->maxs; + } + +// if( RI->view.frustum.CullBox( worldBounds[0], worldBounds[1] )) +// continue; + + for( int j = 0; j < 8; j++ ) + { + Vector4D point; + point.x = worldBounds[(j >> 0) & 1].x; + point.y = worldBounds[(j >> 1) & 1].y; + point.z = worldBounds[(j >> 2) & 1].z; + point.w = 1.0f; + + Vector4D transf = lightViewProjection.VectorTransform( point ); + + transf.x /= transf.w; + transf.y /= transf.w; + transf.z /= transf.w; + + AddPointToBounds( transf, bounds[0], bounds[1] ); + } + numCasters++; + } + + // FIXME: nearplane culled studiomodels incorrectly. disabled for now +// RI->view.frustum.DisablePlane( FRUSTUM_NEAR ); + + // add studio models too + for( i = 0; i < RI->frame.num_solid_meshes; i++ ) + { + if( !R_StudioGetBounds( &RI->frame.solid_meshes[i], worldBounds )) + continue; + +// if( RI->view.frustum.CullBox( worldBounds[0], worldBounds[1] )) +// continue; + + for( int j = 0; j < 8; j++ ) + { + Vector4D point; + point.x = worldBounds[(j >> 0) & 1].x; + point.y = worldBounds[(j >> 1) & 1].y; + point.z = worldBounds[(j >> 2) & 1].z; + point.w = 1.0f; + + Vector4D transf = lightViewProjection.VectorTransform( point ); + transf.x /= transf.w; + transf.y /= transf.w; + transf.z /= transf.w; + + AddPointToBounds( transf, bounds[0], bounds[1] ); + } + numCasters++; + } + + return numCasters; +} + +/* +=============== +R_SetupLightDirectional +=============== +*/ +void R_SetupLightDirectional( DynamicLight *pl, int split ) +{ + matrix4x4 projectionMatrix, cropMatrix, s1; + Vector splitFrustumCorners[8]; + Vector splitFrustumBounds[2]; + Vector splitFrustumClipBounds[2]; + Vector casterBounds[2]; + Vector cropBounds[2]; + int i; + + RI->view.splitFrustum[split].ComputeFrustumCorners( splitFrustumCorners ); + + ClearBounds( splitFrustumBounds[0], splitFrustumBounds[1] ); + + for( i = 0; i < 8; i++ ) + AddPointToBounds( splitFrustumCorners[i], splitFrustumBounds[0], splitFrustumBounds[1] ); + + // find the bounding box of the current split in the light's view space + ClearBounds( cropBounds[0], cropBounds[1] ); + + for( i = 0; i < 8; i++ ) + { + Vector4D point( splitFrustumCorners[i] ); + Vector4D transf = pl->viewMatrix.VectorTransform( point ); + + transf.x /= transf.w; + transf.y /= transf.w; + transf.z /= transf.w; + + AddPointToBounds( transf, cropBounds[0], cropBounds[1] ); + } + + projectionMatrix.CreateOrthoRH( cropBounds[0].x, cropBounds[1].x, cropBounds[0].y, cropBounds[1].y, -cropBounds[1].z, -cropBounds[0].z ); + + matrix4x4 viewProjectionMatrix = projectionMatrix.Concat( pl->viewMatrix ); + + int numCasters = R_ComputeCropBounds( viewProjectionMatrix, NULL, casterBounds ); + + // find the bounding box of the current split in the light's clip space + ClearBounds( splitFrustumClipBounds[0], splitFrustumClipBounds[1] ); + + for( i = 0; i < 8; i++ ) + { + Vector4D point( splitFrustumCorners[i] ); + Vector4D transf = viewProjectionMatrix.VectorTransform( point ); + + transf.x /= transf.w; + transf.y /= transf.w; + transf.z /= transf.w; + + AddPointToBounds( transf, splitFrustumClipBounds[0], splitFrustumClipBounds[1] ); + } + + // scene-dependent bounding volume + cropBounds[0].x = Q_max( casterBounds[0].x, splitFrustumClipBounds[0].x ); + cropBounds[0].y = Q_max( casterBounds[0].y, splitFrustumClipBounds[0].y ); + cropBounds[0].z = Q_min( casterBounds[0].z, splitFrustumClipBounds[0].z ); + cropBounds[1].x = Q_min( casterBounds[1].x, splitFrustumClipBounds[1].x ); + cropBounds[1].y = Q_min( casterBounds[1].y, splitFrustumClipBounds[1].y ); + cropBounds[1].z = Q_max( casterBounds[1].z, splitFrustumClipBounds[1].z ); + + if( numCasters == 0 ) + { + cropBounds[0] = splitFrustumClipBounds[0]; + cropBounds[1] = splitFrustumClipBounds[1]; + } + + cropMatrix.Crop( cropBounds[0], cropBounds[1] ); + pl->projectionMatrix = cropMatrix.Concat( projectionMatrix ); + + s1.CreateTranslate( 0.5f, 0.5f, 0.5f ); + s1.ConcatScale( 0.5f, 0.5f, 0.5f ); + + viewProjectionMatrix = pl->projectionMatrix.Concat( pl->modelviewMatrix ); + + // NOTE: texture matrix is not used. Save it for pssm show split debug tool + pl->textureMatrix[split] = pl->projectionMatrix; + + // build shadow matrices for each split + pl->shadowMatrix[split] = s1.Concat( viewProjectionMatrix ); + + RI->view.frustum.InitProjectionFromMatrix( viewProjectionMatrix ); +} + +/* +=============== +R_ShadowPassSetupFrame +=============== +*/ +static void R_ShadowPassSetupFrame( DynamicLight *pl, int split = 0 ) +{ + matrix3x3 viewRot; + + if( !FBitSet( RI->params, RP_OLDVIEWLEAF )) + { + r_oldviewleaf = r_viewleaf; + r_oldviewleaf2 = r_viewleaf2; + r_viewleaf = Mod_PointInLeaf( pl->viewMatrix.GetOrigin(), worldmodel->nodes ); // light pvs + r_viewleaf2 = Mod_PointInLeaf( RI->view.origin, worldmodel->nodes ); // client pvs + } + + RI->view.farClip = pl->radius; + RI->view.origin = pl->origin; + + R_GrassPrepareFrame(); + + // setup the screen FOV + RI->view.fov_x = pl->fov; + RI->view.fov_y = pl->fov; + + tr.framecount++; + + // setup frustum + if( pl->type == LIGHT_DIRECTIONAL ) + { + pl->splitFrustum[split] = RI->view.splitFrustum[split]; + RI->view.matrix = pl->viewMatrix; + } + else if( pl->type == LIGHT_OMNI ) + { + RI->view.angles = light_sides[split]; + RI->view.matrix = matrix4x4( RI->view.origin, RI->view.angles ); + RI->view.frustum.InitProjection( RI->view.matrix, 0.1f, pl->radius, 90.0f, 90.0f ); + } + else + { + RI->view.matrix = pl->viewMatrix; + RI->view.frustum = pl->frustum; + } + + if( pl->type == LIGHT_OMNI ) + { + RI->view.worldMatrix.CreateModelview(); + RI->view.worldMatrix.ConcatRotate( -light_sides[split].z, 1, 0, 0 ); + RI->view.worldMatrix.ConcatRotate( -light_sides[split].x, 0, 1, 0 ); + RI->view.worldMatrix.ConcatRotate( -light_sides[split].y, 0, 0, 1 ); + RI->view.worldMatrix.ConcatTranslate( -pl->origin.x, -pl->origin.y, -pl->origin.z ); + RI->view.projectionMatrix = pl->projectionMatrix; + } + else + { + // matrices already computed + RI->view.worldMatrix = pl->modelviewMatrix; + RI->view.projectionMatrix = pl->projectionMatrix; + } + + RI->currentlight = pl; +} + +/* +============= +R_ShadowPassSetupGL +============= +*/ +static void R_ShadowPassSetupGL( const DynamicLight *pl ) +{ + RI->view.worldProjectionMatrix = RI->view.projectionMatrix.Concat( RI->view.worldMatrix ); + RI->view.worldMatrix.CopyToArray( RI->glstate.modelviewMatrix ); + + RI->view.worldProjectionMatrix.CopyToArray( RI->glstate.modelviewProjectionMatrix ); + pglViewport( RI->view.port[0], RI->view.port[1], RI->view.port[2], RI->view.port[3] ); + + pglMatrixMode( GL_PROJECTION ); + GL_LoadMatrix( RI->view.projectionMatrix ); + + pglMatrixMode( GL_MODELVIEW ); + GL_LoadMatrix( RI->view.worldMatrix ); + + GL_Cull( GL_FRONT ); + + GL_DepthRange( 0.0001f, 1.0f ); // ignore paranoia opengl32.dll + pglEnable( GL_POLYGON_OFFSET_FILL ); + GL_DepthMask( GL_TRUE ); + pglPolygonOffset( 1.0f, 2.0f ); + pglEnable( GL_DEPTH_TEST ); + GL_AlphaTest( GL_FALSE ); + GL_Blend( GL_FALSE ); + + pglClear( GL_DEPTH_BUFFER_BIT ); +} + +/* +============= +R_ShadowPassEndGL +============= +*/ +static void R_ShadowPassEndGL( void ) +{ + pglDisable( GL_POLYGON_OFFSET_FILL ); + pglPolygonOffset( -1, -2 ); + GL_DepthRange( gldepthmin, gldepthmax ); + r_stats.c_shadow_passes++; + GL_Cull( GL_FRONT ); +} + +/* +================ +R_RecursiveShadowNode +================ +*/ +void R_RecursiveShadowNode( mnode_t *node, CFrustum *frustum, unsigned int clipflags ) +{ + msurface_t *surf, **mark; + mleaf_t *pleaf; + int c, side; + float dot; + + if( node->contents == CONTENTS_SOLID ) + return; // hit a solid leaf + + if( node->visframe != tr.visframecount ) + return; + + if( clipflags ) + { + for( int i = 0; i < 6; i++ ) + { + const mplane_t *p = frustum->GetPlane( i ); + + if( !FBitSet( clipflags, BIT( i ))) + continue; + + int clipped = BoxOnPlaneSide( node->minmaxs, node->minmaxs + 3, p ); + if( clipped == 2 ) return; + if( clipped == 1 ) ClearBits( clipflags, BIT( i )); + } + } + + // if a leaf node, draw stuff + if( node->contents < 0 ) + { + pleaf = (mleaf_t *)node; + + mark = pleaf->firstmarksurface; + c = pleaf->nummarksurfaces; + + if( c ) + { + do + { + (*mark)->visframe = tr.framecount; + mark++; + } while( --c ); + } + + // deal with model fragments in this leaf + if( pleaf->efrags ) + STORE_EFRAGS( &pleaf->efrags, tr.realframecount ); + return; + } + + // node is just a decision point, so go down the apropriate sides + + // find which side of the node we are on + dot = PlaneDiff( tr.modelorg, node->plane ); + side = (dot >= 0) ? 0 : 1; + + // recurse down the children, front side first + R_RecursiveShadowNode( node->children[side], frustum, clipflags ); + + // draw stuff + for( c = node->numsurfaces, surf = worldmodel->surfaces + node->firstsurface; c; c--, surf++ ) + { + if( RI->currentlight->type != LIGHT_DIRECTIONAL || r_sunshadows->value > 2.0f ) + R_AddGrassToChain( NULL, surf, RI->currententity, frustum, clipflags ); + + if( R_CullSurface( surf, tr.modelorg, frustum, clipflags )) + continue; + + R_AddSurfaceToDrawList( surf, true, true ); + } + + // recurse down the back side + R_RecursiveShadowNode( node->children[!side], frustum, clipflags ); +} + +/* +============= +R_ShadowPassDrawWorld +============= +*/ +static void R_ShadowPassDrawWorld( DynamicLight *pl ) +{ + int i; + + // restore worldmodel + RI->currententity = GET_ENTITY( 0 ); + RI->currentmodel = RI->currententity->model; + tr.num_light_surfaces = 0; + tr.num_light_meshes = 0; + tr.modelorg = GetVieworg(); + + R_LoadIdentity(); + + // register worldviewProjectionMatrix at zero entry (~80% hits) + RI->currententity->hCachedMatrix = GL_CacheState( g_vecZero, g_vecZero ); + + if( pl->type != LIGHT_DIRECTIONAL || r_sunshadows->value > 1.0f ) + R_RecursiveShadowNode( worldmodel->nodes, &RI->view.frustum, RI->view.frustum.GetClipFlags( )); + + // add all studio models + for( i = 0; i < tr.num_draw_entities; i++ ) + { + RI->currententity = tr.draw_entities[i]; + RI->currentmodel = RI->currententity->model; + + switch( RI->currentmodel->type ) + { + case mod_studio: + R_AddStudioToDrawList( RI->currententity ); + break; + case mod_brush: + if( pl->type != LIGHT_DIRECTIONAL || r_sunshadows->value > 1.0f ) + R_AddBmodelToDrawList( RI->currententity, R_OpaqueEntity( RI->currententity )); + break; + } + } +} + +void R_ShadowPassDrawSolidEntities( void ) +{ + // draw solid entities only. + glState.drawTrans = false; + + R_RenderShadowBrushList(); + + R_RenderShadowStudioList(); + + // g-cont. probably always empty + R_RenderShadowSpriteList(); + + // may be solid cables + R_DrawParticles( false ); +} + +/* +=============== +R_SetupViewCache + +simply version of setup view cache +especially for parallel shadow pass +=============== +*/ +static void R_SetupViewCacheForParallelLightShadow( const int viewport[4], DynamicLight *pl, int split = 0 ) +{ + model_t *model = worldmodel; + + RI->view.changed = 0; // always clearing changes at start of frame + + if( !model && FBitSet( RI->params, RP_DRAW_WORLD )) + HOST_ERROR( "R_SetupViewCacheShadow: NULL worldmodel\n" ); + + memcpy( RI->view.port, viewport, sizeof( RI->view.port )); + memcpy( RI->glstate.viewport, RI->view.port, sizeof( RI->glstate.viewport )); + + RI->view.pvspoint = pl->viewMatrix.GetOrigin(); + RI->view.origin = pl->origin; + glState.drawTrans = false; + RI->currentlight = pl; + + RI->view.leaf = Mod_PointInLeaf( RI->view.pvspoint, model->nodes ); + + // merge PVS with previous pass + ENGINE_SET_PVS( RI->view.pvspoint, REFPVS_RADIUS, RI->view.pvsarray, true, false ); + + RI->view.farClip = pl->radius; + RI->view.fov_x = pl->fov; + RI->view.fov_y = pl->fov; + + if( RI->view.fov_x <= 0.0f || RI->view.fov_y <= 0.0f ) + HOST_ERROR( "R_SetupViewCacheForParallelLightShadow: bad fov!\n" ); + + if( IS_NAN( RI->view.fov_x ) || IS_NAN( RI->view.fov_y )) + HOST_ERROR( "R_SetupViewCacheForParallelLightShadow: NAN fov!\n" ); + + RI->view.frustum = RI->view.splitFrustum[split]; + RI->view.worldMatrix = pl->modelviewMatrix; + RI->view.matrix = pl->viewMatrix; + + matrix4x4 projectionMatrix, cropMatrix, s1; + Vector splitFrustumCorners[8]; + Vector splitFrustumBounds[2]; + Vector splitFrustumClipBounds[2]; + Vector casterBounds[2]; + Vector cropMins, cropMaxs; + int i; + + s1.CreateTexture(); + + RI->view.splitFrustum[split].ComputeFrustumCorners( splitFrustumCorners ); + ClearBounds( splitFrustumBounds[0], splitFrustumBounds[1] ); + + for( i = 0; i < 8; i++ ) + AddPointToBounds( splitFrustumCorners[i], splitFrustumBounds[0], splitFrustumBounds[1] ); + + // find the bounding box of the current split in the light's view space + ClearBounds( cropMins, cropMaxs ); + + for( i = 0; i < 8; i++ ) + { + Vector4D point( splitFrustumCorners[i] ); + Vector4D transf = pl->viewMatrix.VectorTransform( point ); + + transf.x /= transf.w; + transf.y /= transf.w; + transf.z /= transf.w; + + AddPointToBounds( transf, cropMins, cropMaxs ); + } + + projectionMatrix.CreateOrthoRH( cropMins.x, cropMaxs.x, cropMins.y, cropMaxs.y, -cropMaxs.z, -cropMins.z ); + + matrix4x4 viewProjectionMatrix = projectionMatrix.Concat( pl->viewMatrix ); + + int numCasters = R_ComputeCropBounds( viewProjectionMatrix, model, casterBounds ); + + // find the bounding box of the current split in the light's clip space + ClearBounds( splitFrustumClipBounds[0], splitFrustumClipBounds[1] ); + + for( i = 0; i < 8; i++ ) + { + Vector4D point( splitFrustumCorners[i] ); + Vector4D transf = viewProjectionMatrix.VectorTransform( point ); + + transf.x /= transf.w; + transf.y /= transf.w; + transf.z /= transf.w; + + AddPointToBounds( transf, splitFrustumClipBounds[0], splitFrustumClipBounds[1] ); + } + + // scene-dependent bounding volume + cropMins.x = Q_max( casterBounds[0].x, splitFrustumClipBounds[0].x ); + cropMins.y = Q_max( casterBounds[0].y, splitFrustumClipBounds[0].y ); + cropMins.z = Q_min( casterBounds[0].z, splitFrustumClipBounds[0].z ); + cropMaxs.x = Q_min( casterBounds[1].x, splitFrustumClipBounds[1].x ); + cropMaxs.y = Q_min( casterBounds[1].y, splitFrustumClipBounds[1].y ); + cropMaxs.z = Q_max( casterBounds[1].z, splitFrustumClipBounds[1].z ); + + if( numCasters == 0 ) + { + cropMins = splitFrustumClipBounds[0]; + cropMaxs = splitFrustumClipBounds[1]; + } + + cropMatrix.Crop( cropMins, cropMaxs ); + pl->projectionMatrix = cropMatrix.Concat( projectionMatrix ); + viewProjectionMatrix = pl->projectionMatrix.Concat( pl->modelviewMatrix ); + + // NOTE: texture matrix is not used. Save it for pssm show split debug tool + pl->textureMatrix[split] = projectionMatrix; + + // build shadow matrices for each split + pl->shadowMatrix[split] = s1.Concat( viewProjectionMatrix ); + + RI->view.frustum.InitProjectionFromMatrix( viewProjectionMatrix ); + RI->view.projectionMatrix = pl->projectionMatrix; +} + +/* +=============== +R_SetupViewCache + +simply version of setup view cache +especially for shadow pass +=============== +*/ +static void R_SetupViewCacheShadow( const int viewport[4], DynamicLight *pl, int split = 0 ) +{ + model_t *model = worldmodel; + + RI->view.changed = 0; // always clearing changes at start of frame + + if( !model && FBitSet( RI->params, RP_DRAW_WORLD )) + HOST_ERROR( "R_SetupViewCacheShadow: NULL worldmodel\n" ); + + memcpy( RI->view.port, viewport, sizeof( RI->view.port )); + memcpy( RI->glstate.viewport, RI->view.port, sizeof( RI->glstate.viewport )); + + RI->view.pvspoint = pl->viewMatrix.GetOrigin(); + RI->view.origin = pl->origin; + glState.drawTrans = false; + RI->currentlight = pl; + + RI->view.leaf = Mod_PointInLeaf( RI->view.pvspoint, model->nodes ); + + ENGINE_SET_PVS( RI->view.pvspoint, REFPVS_RADIUS, RI->view.pvsarray, true, false ); + + RI->view.farClip = pl->radius; + RI->view.fov_x = pl->fov ? pl->fov : 90.0f; + RI->view.fov_y = pl->fov ? pl->fov : 90.0f; + + if( RI->view.fov_x <= 0.0f || RI->view.fov_y <= 0.0f ) + HOST_ERROR( "R_SetupViewCacheShadow: bad fov!\n" ); + + if( IS_NAN( RI->view.fov_x ) || IS_NAN( RI->view.fov_y )) + HOST_ERROR( "R_SetupViewCacheShadow: NAN fov!\n" ); + + // setup frustum + switch( pl->type ) + { + case LIGHT_DIRECTIONAL: + RI->view.frustum = RI->view.splitFrustum[split]; + RI->view.worldMatrix = pl->modelviewMatrix; + RI->view.matrix = pl->viewMatrix; + break; + case LIGHT_OMNI: + RI->view.angles = light_sides[split]; + RI->view.matrix = matrix4x4( RI->view.origin, RI->view.angles ); + RI->view.frustum.InitProjection( RI->view.matrix, Z_NEAR_LIGHT, pl->radius, pl->fov, pl->fov ); + RI->view.worldMatrix.CreateModelview(); + RI->view.worldMatrix.ConcatRotate( -light_sides[split].z, 1, 0, 0 ); + RI->view.worldMatrix.ConcatRotate( -light_sides[split].x, 0, 1, 0 ); + RI->view.worldMatrix.ConcatRotate( -light_sides[split].y, 0, 0, 1 ); + RI->view.worldMatrix.ConcatTranslate( -pl->origin.x, -pl->origin.y, -pl->origin.z ); + break; + case LIGHT_PROJECTION: + RI->view.worldMatrix = pl->modelviewMatrix; + RI->view.matrix = pl->viewMatrix; + RI->view.frustum = pl->frustum; + break; + default: + HOST_ERROR( "R_SetupViewCacheShadow: invalid light type\n" ); + break; + } + + CFrustum *frustum = &RI->view.frustum; + float maxdist = 0.0f; + msurface_t *surf, **mark; + mextrasurf_t *esrf; + mleaf_t *leaf; + int i, j; + + if( model != NULL ) + { + memset( RI->view.visfaces, 0x00, (model->numsurfaces + 7) >> 3 ); + ClearBounds( RI->view.visMins, RI->view.visMaxs ); + + // always skip the leaf 0, because is outside leaf + for( i = 1, leaf = &model->leafs[1]; i < model->numleafs + 1; i++, leaf++ ) + { + mextraleaf_t *eleaf = LEAF_INFO( leaf, model ); // named like personal vaporisers manufacturer he-he + + if( CHECKVISBIT( RI->view.pvsarray, leaf->cluster ) && ( leaf->efrags || leaf->nummarksurfaces )) + { + if( RI->view.frustum.CullBox( eleaf->mins, eleaf->maxs )) + continue; + + // deal with model fragments in this leaf + if( leaf->efrags ) + STORE_EFRAGS( &leaf->efrags, tr.realframecount ); + + if( leaf->contents == CONTENTS_EMPTY ) + { + // unrolled for speedup reasons + RI->view.visMins[0] = Q_min( RI->view.visMins[0], eleaf->mins[0] ); + RI->view.visMaxs[0] = Q_max( RI->view.visMaxs[0], eleaf->maxs[0] ); + RI->view.visMins[1] = Q_min( RI->view.visMins[1], eleaf->mins[1] ); + RI->view.visMaxs[1] = Q_max( RI->view.visMaxs[1], eleaf->maxs[1] ); + RI->view.visMins[2] = Q_min( RI->view.visMins[2], eleaf->mins[2] ); + RI->view.visMaxs[2] = Q_max( RI->view.visMaxs[2], eleaf->maxs[2] ); + } + + r_stats.c_world_leafs++; + + if( leaf->nummarksurfaces ) + { + for( j = 0, mark = leaf->firstmarksurface; j < leaf->nummarksurfaces; j++, mark++ ) + SETVISBIT( RI->view.visfaces, *mark - model->surfaces ); + } + } + } + + // now we have actual vismins\vismaxs and can calc farplane distance + for( i = 0; i < 8; i++ ) + { + Vector v, dir; + float dist; + + v[0] = ( i & 1 ) ? RI->view.visMins[0] : RI->view.visMaxs[0]; + v[1] = ( i & 2 ) ? RI->view.visMins[1] : RI->view.visMaxs[1]; + v[2] = ( i & 4 ) ? RI->view.visMins[2] : RI->view.visMaxs[2]; + + dir = v - RI->view.origin; + dist = DotProduct( dir, dir ); + maxdist = Q_max( dist, maxdist ); + } + + RI->view.farClip = sqrt( maxdist ) + 64.0f; // add some bias + + if( pl->type == LIGHT_DIRECTIONAL ) + { + matrix4x4 projectionMatrix, cropMatrix, s1; + Vector splitFrustumCorners[8]; + Vector splitFrustumBounds[2]; + Vector splitFrustumClipBounds[2]; + Vector casterBounds[2]; + Vector cropMins, cropMaxs; + + s1.CreateTexture(); + + RI->view.splitFrustum[split].ComputeFrustumCorners( splitFrustumCorners ); + + ClearBounds( splitFrustumBounds[0], splitFrustumBounds[1] ); + + for( i = 0; i < 8; i++ ) + AddPointToBounds( splitFrustumCorners[i], splitFrustumBounds[0], splitFrustumBounds[1] ); + + // find the bounding box of the current split in the light's view space + ClearBounds( cropMins, cropMaxs ); + + for( i = 0; i < 8; i++ ) + { + Vector4D point( splitFrustumCorners[i] ); + Vector4D transf = pl->viewMatrix.VectorTransform( point ); + + transf.x /= transf.w; + transf.y /= transf.w; + transf.z /= transf.w; + + AddPointToBounds( transf, cropMins, cropMaxs ); + } + + projectionMatrix.CreateOrthoRH( cropMins.x, cropMaxs.x, cropMins.y, cropMaxs.y, -cropMaxs.z, -cropMins.z ); + + matrix4x4 viewProjectionMatrix = projectionMatrix.Concat( pl->viewMatrix ); + + int numCasters = R_ComputeCropBounds( viewProjectionMatrix, NULL, casterBounds ); + + // find the bounding box of the current split in the light's clip space + ClearBounds( splitFrustumClipBounds[0], splitFrustumClipBounds[1] ); + + for( i = 0; i < 8; i++ ) + { + Vector4D point( splitFrustumCorners[i] ); + Vector4D transf = viewProjectionMatrix.VectorTransform( point ); + + transf.x /= transf.w; + transf.y /= transf.w; + transf.z /= transf.w; + + AddPointToBounds( transf, splitFrustumClipBounds[0], splitFrustumClipBounds[1] ); + } + + // scene-dependent bounding volume + cropMins.x = Q_max( casterBounds[0].x, splitFrustumClipBounds[0].x ); + cropMins.y = Q_max( casterBounds[0].y, splitFrustumClipBounds[0].y ); + cropMins.z = Q_min( casterBounds[0].z, splitFrustumClipBounds[0].z ); + cropMaxs.x = Q_min( casterBounds[1].x, splitFrustumClipBounds[1].x ); + cropMaxs.y = Q_min( casterBounds[1].y, splitFrustumClipBounds[1].y ); + cropMaxs.z = Q_max( casterBounds[1].z, splitFrustumClipBounds[1].z ); + + if( numCasters == 0 ) + { + cropMins = splitFrustumClipBounds[0]; + cropMaxs = splitFrustumClipBounds[1]; + } + + cropMatrix.Crop( cropMins, cropMaxs ); + pl->projectionMatrix = cropMatrix.Concat( projectionMatrix ); + viewProjectionMatrix = pl->projectionMatrix.Concat( pl->modelviewMatrix ); + + // NOTE: texture matrix is not used. Save it for pssm show split debug tool + pl->textureMatrix[split] = projectionMatrix; + + // build shadow matrices for each split + pl->shadowMatrix[split] = s1.Concat( viewProjectionMatrix ); + + RI->view.frustum.InitProjectionFromMatrix( viewProjectionMatrix ); + RI->view.projectionMatrix = pl->projectionMatrix; + } + else + { + // farclip was changed so we need to recompute frustum and projection again + RI->view.frustum.InitProjection( RI->view.matrix, 0.0f, RI->view.farClip, RI->view.fov_x, RI->view.fov_y ); + RI->view.projectionMatrix.CreateProjection( RI->view.fov_x, RI->view.fov_y, Z_NEAR, RI->view.farClip ); + } + + RI->frame.num_solid_faces = 0; + RI->frame.num_trans_faces = 0; + RI->frame.num_mpass_faces = 0; + RI->frame.num_solid_meshes = 0; + RI->frame.num_trans_meshes = 0; + RI->frame.num_solid_sprites = 0; + RI->frame.num_trans_sprites = 0; + + if( model != NULL ) + { + RI->currententity = GET_ENTITY( 0 ); + RI->currentmodel = RI->currententity->model; + R_GrassPrepareFrame(); + } + + // brush faces not added here! + // only marks as visible in RI->view.visfaces array + for( i = 0; i < tr.num_draw_entities; i++ ) + { + RI->currententity = tr.draw_entities[i]; + RI->currentmodel = RI->currententity->model; + + switch( RI->currentmodel->type ) + { + case mod_brush: + R_MarkSubmodelVisibleFaces(); + break; + case mod_studio: + R_AddStudioToDrawList( RI->currententity ); + break; + } + } + + // create drawlist for faces, do additional culling for world faces + for( i = 0; model != NULL && i < world->numsortedfaces; i++ ) + { + ASSERT( world->sortedfaces != NULL ); + + j = world->sortedfaces[i]; + + ASSERT( j >= 0 && j < model->numsurfaces ); + + if( CHECKVISBIT( RI->view.visfaces, j )) + { + surf = model->surfaces + j; + esrf = surf->info; + + // submodel faces already passed through this + // operation but world is not + if( FBitSet( surf->flags, SURF_OF_SUBMODEL )) + { + RI->currententity = esrf->parent; + RI->currentmodel = RI->currententity->model; + } + else + { + RI->currententity = GET_ENTITY( 0 ); + RI->currentmodel = RI->currententity->model; + + esrf->parent = RI->currententity; // setup dynamic upcast + + bool force = R_AddGrassToChain( NULL, surf, RI->currententity, &RI->view.frustum, 0 ); + + if( !force && R_CullSurface( surf, GetVieworg(), frustum )) + { + CLEARVISBIT( RI->view.visfaces, j ); // not visible + continue; + } + + // surface has passed all visibility checks + // and can be update some data (lightmaps, mirror matrix, etc) + R_UpdateSurfaceParams( surf ); + } + + if( R_OpaqueEntity( RI->currententity )) + R_AddSurfaceToDrawList( surf, DRAWLIST_SOLID ); + } + } + } +} + +/* +================ +R_RenderShadowScene + +fast version of R_RenderScene: no colors, no texcords etc +================ +*/ +void R_RenderShadowScene( DynamicLight *pl, int split = 0 ) +{ + int viewport[4]; + + RI->params = RP_SHADOWVIEW|RP_MERGEVISIBILITY|RP_DRAW_WORLD; + viewport[0] = viewport[1] = 0; + bool using_fbo = false; + + if( pl->type == LIGHT_DIRECTIONAL ) + { + if( tr.sunShadowFBO[split].Active( )) + { + pl->shadowTexture[split] = tr.sunShadowFBO[split].GetTexture(); + viewport[2] = viewport[3] = sunShadowSize[split]; + tr.sunShadowFBO[split].Bind(); + using_fbo = true; + } + else viewport[2] = viewport[3] = 512; // simple size if FBO was missed + } + else + { + if( tr.fbo_shadow2D.Active( )) + { + pl->shadowTexture[0] = R_AllocateShadowTexture( false ); + tr.fbo_shadow2D.Bind( pl->shadowTexture[0] ); + viewport[2] = tr.fbo_shadow2D.GetWidth(); + viewport[3] = tr.fbo_shadow2D.GetHeight(); + using_fbo = true; + } + else viewport[2] = viewport[3] = 512; // simple size if FBO was missed + } + + // set the worldmodel + worldmodel = GET_ENTITY( 0 )->model; + + if( !worldmodel ) + { + ALERT( at_error, "R_RenderShadowView: NULL worldmodel\n" ); + return; + } + + if( pl->type == LIGHT_DIRECTIONAL ) + R_SetupViewCacheForParallelLightShadow( viewport, pl, split ); + else R_SetupViewCacheShadow( viewport, pl, split ); + R_ShadowPassSetupGL( pl ); + R_ShadowPassDrawSolidEntities(); + + R_ShadowPassEndGL(); + + if( !using_fbo ) + pl->shadowTexture[split] = R_AllocateShadowTexture(); +} + +/* +================ +R_RenderShadowCubeSide + +fast version of R_RenderScene: no colors, no texcords etc +================ +*/ +void R_RenderShadowCubeSide( DynamicLight *pl, int side ) +{ + RI->params = RP_SHADOWVIEW|RP_MERGEVISIBILITY|RP_DRAW_WORLD; + bool using_fbo = false; + + if( tr.fbo_shadowCM.Active( )) + { + RI->view.port[2] = tr.fbo_shadowCM.GetWidth(); + RI->view.port[3] = tr.fbo_shadowCM.GetHeight(); + + pl->shadowTexture[0] = R_AllocateShadowCubemap( side, false ); + tr.fbo_shadowCM.Bind( pl->shadowTexture[0], side ); + using_fbo = true; + } + else + { + // same size if FBO was missed + RI->view.port[2] = RI->view.port[3] = 512; + using_fbo = false; + } + + // set the worldmodel + worldmodel = GET_ENTITY( 0 )->model; + + if( !worldmodel ) + { + ALERT( at_error, "R_RenderShadowCubeSide: NULL worldmodel\n" ); + return; + } + + R_ShadowPassSetupFrame( pl, side ); + R_ShadowPassSetupGL( pl ); + + R_MarkLeaves(); + + R_ShadowPassDrawWorld( pl ); + + R_ShadowPassDrawSolidEntities(); + + R_ShadowPassEndGL(); + + if( !using_fbo ) + pl->shadowTexture[0] = R_AllocateShadowCubemap( side ); +} + +void R_RenderShadowmaps( void ) +{ + unsigned int oldFBO; + + if( R_FullBright() || !CVAR_TO_BOOL( r_shadows ) || tr.fGamePaused ) + return; + + if( FBitSet( RI->params, ( RP_NOSHADOWS|RP_ENVVIEW|RP_SKYVIEW ))) + return; + + // check for dynamic lights + if( !HasDynamicLights( )) return; + + R_PushRefState(); // make refinst backup + oldFBO = glState.frameBuffer; + + for( int i = 0; i < MAX_DLIGHTS; i++ ) + { + DynamicLight *pl = &cl_dlights[i]; + + if( pl->die < GET_CLIENT_TIME() || !pl->radius || FBitSet( pl->flags, DLF_NOSHADOWS )) + continue; + + RI->currentlight = pl; + + if( pl->type == LIGHT_OMNI ) + { + // need GL_EXT_gpu_shader4 for cubemap shadows + if( !GL_Support( R_TEXTURECUBEMAP_EXT ) || !GL_Support( R_EXT_GPU_SHADER4 )) + continue; + + if( !Mod_CheckBoxVisible( pl->absmin, pl->absmax )) + continue; + + if( R_CullBox( pl->absmin, pl->absmax )) + continue; + + for( int j = 0; j < 6; j++ ) + { + R_RenderShadowCubeSide( pl, j ); + R_ResetRefState(); // restore ref instance + } + } + else if( pl->type == LIGHT_PROJECTION ) + { + if( !Mod_CheckBoxVisible( pl->absmin, pl->absmax )) + continue; + + if( R_CullBox( pl->absmin, pl->absmax )) + continue; + + R_RenderShadowScene( pl ); + R_ResetRefState(); // restore ref instance + } + else if( pl->type == LIGHT_DIRECTIONAL ) + { + if( !CVAR_TO_BOOL( r_sunshadows ) || tr.sky_normal.z >= 0.0f ) + continue; // shadows are invisible + + for( int j = 0; j <= NUM_SHADOW_SPLITS; j++ ) + { + // PSSM: draw all the splits + R_RenderShadowScene( pl, j ); + R_ResetRefState(); // restore ref instance + } + } + } + + R_PopRefState(); // restore ref instance + // restore FBO state + GL_BindFBO( oldFBO ); + GL_BindShader( NULL ); + RI->currentlight = NULL; +} \ No newline at end of file diff --git a/cl_dll/render/gl_sky.cpp b/cl_dll/render/gl_sky.cpp new file mode 100644 index 0000000..cd24792 --- /dev/null +++ b/cl_dll/render/gl_sky.cpp @@ -0,0 +1,373 @@ +// +// written by BUzer for HL: Paranoia modification +// +// 2006 + +#include "hud.h" +#include "cl_util.h" +#include "gl_local.h" +#include "gl_shader.h" + +#define MAX_CLIP_VERTS 128 // skybox clip vertices + +static const int r_skyTexOrder[6] = { 0, 2, 1, 3, 4, 5 }; + +static const Vector skyclip[6] = +{ +Vector( 1, 1, 0 ), +Vector( 1, -1, 0 ), +Vector( 0, -1, 1 ), +Vector( 0, 1, 1 ), +Vector( 1, 0, 1 ), +Vector( -1, 0, 1 ) +}; + +// 1 = s, 2 = t, 3 = 2048 +static const int st_to_vec[6][3] = +{ +{ 3, -1, 2 }, +{ -3, 1, 2 }, +{ 1, 3, 2 }, +{ -1, -3, 2 }, +{ -2, -1, 3 }, // 0 degrees yaw, look straight up +{ 2, -1, -3 } // look straight down +}; + +// s = [0]/[2], t = [1]/[2] +static const int vec_to_st[6][3] = +{ +{ -2, 3, 1 }, +{ 2, 3, -1 }, +{ 1, 3, 2 }, +{ -1, 3, -2 }, +{ -2, -1, 3 }, +{ -2, 1, -3 } +}; + +static void DrawSkyPolygon( int nump, Vector vecs[] ) +{ + int i, j, axis; + float s, t, dv; + Vector v, av; + + // decide which face it maps to + v = g_vecZero; + + for( i = 0; i < nump; i++ ) + v += vecs[i]; + + av[0] = fabs( v[0] ); + av[1] = fabs( v[1] ); + av[2] = fabs( v[2] ); + + if( av[0] > av[1] && av[0] > av[2] ) + axis = (v[0] < 0) ? 1 : 0; + else if( av[1] > av[2] && av[1] > av[0] ) + axis = (v[1] < 0) ? 3 : 2; + else axis = (v[2] < 0) ? 5 : 4; + + // project new texture coords + for( i = 0; i < nump; i++ ) + { + j = vec_to_st[axis][2]; + dv = (j > 0) ? (vecs[i])[j-1] : -(vecs[i])[-j-1]; + + j = vec_to_st[axis][0]; + s = (j < 0) ? -vecs[i][-j-1] / dv : (vecs[i])[j-1] / dv; + + j = vec_to_st[axis][1]; + t = (j < 0) ? -(vecs[i])[-j-1] / dv : (vecs[i])[j-1] / dv; + + if( s < RI->view.skyMins[0][axis] ) RI->view.skyMins[0][axis] = s; + if( t < RI->view.skyMins[1][axis] ) RI->view.skyMins[1][axis] = t; + if( s > RI->view.skyMaxs[0][axis] ) RI->view.skyMaxs[0][axis] = s; + if( t > RI->view.skyMaxs[1][axis] ) RI->view.skyMaxs[1][axis] = t; + } +} + +/* +============== +ClipSkyPolygon +============== +*/ +static void ClipSkyPolygon( int nump, Vector vecs[], int stage ) +{ + bool front, back; + float dists[MAX_CLIP_VERTS + 1]; + int sides[MAX_CLIP_VERTS + 1]; + Vector newv[2][MAX_CLIP_VERTS + 1]; + int newc[2]; + float d, e; + int i; + + if( nump > MAX_CLIP_VERTS ) + HOST_ERROR( "ClipSkyPolygon: MAX_CLIP_VERTS\n" ); +loc1: + if( stage == 6 ) + { + // fully clipped, so draw it + DrawSkyPolygon( nump, vecs ); + return; + } + + front = back = false; + const Vector &norm = skyclip[stage]; + + for( i = 0; i < nump; i++ ) + { + d = DotProduct( vecs[i], norm ); + if( d > ON_EPSILON ) + { + front = true; + sides[i] = SIDE_FRONT; + } + else if( d < -ON_EPSILON ) + { + back = true; + sides[i] = SIDE_BACK; + } + else + { + sides[i] = SIDE_ON; + } + dists[i] = d; + } + + if( !front || !back ) + { + // not clipped + stage++; + goto loc1; + } + + // clip it + sides[i] = sides[0]; + dists[i] = dists[0]; + vecs[i] = vecs[0]; + newc[0] = newc[1] = 0; + + for( i = 0; i < nump; i++ ) + { + switch( sides[i] ) + { + case SIDE_FRONT: + newv[0][newc[0]] = vecs[i]; + newc[0]++; + break; + case SIDE_BACK: + newv[1][newc[1]] = vecs[i]; + newc[1]++; + break; + case SIDE_ON: + newv[0][newc[0]] = vecs[i]; + newc[0]++; + newv[1][newc[1]] = vecs[i]; + newc[1]++; + break; + } + + if( sides[i] == SIDE_ON || sides[i+1] == SIDE_ON || sides[i+1] == sides[i] ) + continue; + + d = dists[i] / ( dists[i] - dists[i+1] ); + + for( int j = 0; j < 3; j++ ) + { + e = (vecs[i])[j] + d * ( (vecs[i+1])[j] - (vecs[i])[j] ); + newv[0][newc[0]][j] = e; + newv[1][newc[1]][j] = e; + } + newc[0]++; + newc[1]++; + } + + // continue + ClipSkyPolygon( newc[0], newv[0], stage + 1 ); + ClipSkyPolygon( newc[1], newv[1], stage + 1 ); +} + +static void MakeSkyVec( float s, float t, int axis ) +{ + int j, k, farclip; + Vector v, b; + + farclip = RI->view.farClip; + + b[0] = s * (farclip >> 1); + b[1] = t * (farclip >> 1); + b[2] = (farclip >> 1); + + for( j = 0; j < 3; j++ ) + { + k = st_to_vec[axis][j]; + v[j] = (k < 0) ? -b[-k-1] : b[k-1]; + v[j] += GetVieworg()[j]; + } + + // avoid bilerp seam + s = (s + 1.0f) * 0.5f; + t = (t + 1.0f) * 0.5f; + + if( s < ( 1.0f / 512.0f )) + s = (1.0f / 512.0f); + else if( s > ( 511.0f / 512.0f )) + s = (511.0f / 512.0f); + if( t < ( 1.0f / 512.0f )) + t = (1.0f / 512.0f); + else if( t > ( 511.0f / 512.0f )) + t = (511.0f / 512.0f); + + t = 1.0f - t; + + pglTexCoord2f( s, t ); + pglVertex3fv( v ); +} + +/* +================= +R_AddSkyBoxSurface +================= +*/ +void R_AddSkyBoxSurface( msurface_t *fa ) +{ + Vector verts[MAX_CLIP_VERTS]; + + // calculate vertex values for sky box + for( glpoly_t *p = fa->polys; p; p = p->next ) + { + if( p->numverts >= MAX_CLIP_VERTS ) + HOST_ERROR( "R_AddSkyBoxSurface: numverts >= MAX_CLIP_VERTS\n" ); + + for( int i = 0; i < p->numverts; i++ ) + { + verts[i][0] = p->verts[i][0] - GetVieworg().x; + verts[i][1] = p->verts[i][1] - GetVieworg().y; + verts[i][2] = p->verts[i][2] - GetVieworg().z; + } + + ClipSkyPolygon( p->numverts, verts, 0 ); + } +} + +static void GL_DrawSkySide( int skyside ) +{ + pglBegin( GL_QUADS ); + MakeSkyVec( RI->view.skyMins[0][skyside], RI->view.skyMins[1][skyside], skyside ); + MakeSkyVec( RI->view.skyMins[0][skyside], RI->view.skyMaxs[1][skyside], skyside ); + MakeSkyVec( RI->view.skyMaxs[0][skyside], RI->view.skyMaxs[1][skyside], skyside ); + MakeSkyVec( RI->view.skyMaxs[0][skyside], RI->view.skyMins[1][skyside], skyside ); + pglEnd(); +} + +static void GL_DrawSkySide( word hProgram, int skyside ) +{ + if( hProgram <= 0 ) + { + GL_BindShader( NULL ); + GL_BindTexture( GL_TEXTURE0, tr.skyboxTextures[r_skyTexOrder[skyside]] ); + GL_DrawSkySide( skyside ); + return; // old method + } + + if( RI->currentshader != &glsl_programs[hProgram] ) + { + // force to bind new shader + GL_BindShader( &glsl_programs[hProgram] ); + } + + Vector sky_color = (tr.sun_light_enabled) ? tr.sun_diffuse : tr.sky_ambient * (1.0f/128.0f) * tr.diffuseFactor; + Vector sky_vec = tr.sky_normal.Normalize(); + glsl_program_t *shader = RI->currentshader; + + ColorNormalize( sky_color, sky_color ); + + // setup specified uniforms (and texture bindings) + for( int i = 0; i < shader->numUniforms; i++ ) + { + uniform_t *u = &shader->uniforms[i]; + + switch( u->type ) + { + case UT_COLORMAP: + u->SetValue( tr.skyboxTextures[r_skyTexOrder[skyside]] ); + break; + case UT_LIGHTDIR: + u->SetValue( sky_vec.x, sky_vec.y, sky_vec.z ); + break; + case UT_LIGHTDIFFUSE: + u->SetValue( sky_color.x, sky_color.y, sky_color.z ); + break; + case UT_VIEWORIGIN: + u->SetValue( GetVieworg().x, GetVieworg().y, GetVieworg().z ); + break; + case UT_FOGPARAMS: + u->SetValue( tr.fogColor[0], tr.fogColor[1], tr.fogColor[2], tr.fogDensity * 0.5f ); + break; + case UT_ZFAR: + u->SetValue( RI->view.farClip ); + break; + default: + ALERT( at_error, "%s: unhandled uniform %s\n", RI->currentshader->name, u->name ); + break; + } + } + + GL_DrawSkySide( skyside ); +} + +/* +============== +R_DrawSkybox +============== +*/ +void R_DrawSkyBox( void ) +{ + bool drawSun = true; + float fogDenstity = tr.fogDensity; + word hSkyShader = 0; + int i; + + if( !FBitSet( RI->view.flags, RF_SKYVISIBLE )) + return; + + GL_DepthRange( 0.9f, 1.0f ); + GL_DepthMask( GL_FALSE ); + GL_Blend( GL_FALSE ); + GL_AlphaTest( GL_FALSE ); + + // clipplane cut the sky if drawing through mirror. Disable it + GL_ClipPlane( false ); + + int type = tr.sun_light_enabled ? 1 : 0; + + if( FBitSet( RI->params, RP_SKYVIEW ) || ( FBitSet( RI->params, RP_WATERPASS ) && CVAR_TO_BOOL( cv_specular ))) + drawSun = false; + + // disable sky fogging while underwater + if( tr.waterlevel >= 3 || FBitSet( RI->params, RP_SKYVIEW )) + fogDenstity = 0.0f; + + // make sure what light_environment is present + if( tr.sky_normal != g_vecZero && drawSun ) + { + if( FBitSet( RI->params, RP_DEFERREDSCENE )) + hSkyShader = tr.defSceneSky; + else if( FBitSet( RI->params, RP_DEFERREDLIGHT )) + hSkyShader = tr.defLightSky; + else hSkyShader = tr.skyboxEnv[type]; + } + + for( i = 0; i < 6; i++ ) + { + if( RI->view.skyMins[0][i] >= RI->view.skyMaxs[0][i] || RI->view.skyMins[1][i] >= RI->view.skyMaxs[1][i] ) + continue; + + GL_DrawSkySide( hSkyShader, i ); + } + + GL_ClipPlane( true ); + GL_DepthMask( GL_TRUE ); + + // back to normal depth range + GL_DepthRange( gldepthmin, gldepthmax ); +} \ No newline at end of file diff --git a/cl_dll/render/gl_slight.cpp b/cl_dll/render/gl_slight.cpp new file mode 100644 index 0000000..5d7800e --- /dev/null +++ b/cl_dll/render/gl_slight.cpp @@ -0,0 +1,733 @@ +/* +gl_slight.cpp - static lighting +Copyright (C) 2019 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include +#include "cl_util.h" +#include "pm_defs.h" +#include "event_api.h" +#include "gl_local.h" +#include "gl_studio.h" +#include "gl_world.h" + +#define NUMVERTEXNORMALS 162 +#define AMBIENT_CUBE_SCALE 1.0f + +// lightpoint flags +#define LIGHTINFO_HITSKY (1<<0) +#define LIGHTINFO_AVERAGE (1<<1) // compute average color from lightmap +#define LIGHTINFO_STYLES (1<<2) + +typedef struct +{ + vec3_t diffuse; + vec3_t average; + colorVec lightmap; + vec3_t normal; + msurface_t *surf; // may be NULL + float fraction; // hit fraction + vec3_t origin; + int status; +} lightpoint_t; + +static vec_t g_anorms[NUMVERTEXNORMALS][3] = +{ +#include "anorms.h" +}; + +static Vector g_BoxDirections[6] = +{ +Vector( 1.0f, 0.0f, 0.0f ), +Vector(-1.0f, 0.0f, 0.0f ), +Vector( 0.0f, 1.0f, 0.0f ), +Vector( 0.0f, -1.0f, 0.0f ), +Vector( 0.0f, 0.0f, 1.0f ), +Vector( 0.0f, 0.0f, -1.0f ), +}; + +/* +======================================================================= + + AMBIENT & DIFFUSE LIGHTING + +======================================================================= +*/ +/* +================= +R_FindAmbientSkyLight +================= +*/ +static mworldlight_t *R_FindAmbientSkyLight( void ) +{ + // find any ambient lights + for( int i = 0; i < world->numworldlights; i++ ) + { + if( world->worldlights[i].emittype == emit_skylight ) + return &world->worldlights[i]; + } + + return NULL; +} + +/* +================= +R_LightTraceFilter +================= +*/ +static int R_LightTraceFilter( physent_t *pe ) +{ + if( !pe || !pe->model ) + return 1; + return 0; +} + +/* +================= +R_GetDirectLightFromSurface +================= +*/ +static bool R_GetDirectLightFromSurface( msurface_t *surf, const Vector &point, lightpoint_t *info ) +{ + mextrasurf_t *es = surf->info; + float ds, dt, s, t; + color24 *lm, *dm; + mtexinfo_t *tex; + int map, size; + + tex = surf->texinfo; + + if( FBitSet( surf->flags, SURF_DRAWSKY )) + { + info->status |= LIGHTINFO_HITSKY; + return false; // no lightmaps + } + + if( FBitSet( surf->flags, SURF_DRAWTILED )) + return false; // no lightmaps + + s = DotProduct( point, es->lmvecs[0] ) + es->lmvecs[0][3]; + t = DotProduct( point, es->lmvecs[1] ) + es->lmvecs[1][3]; + + if( s < es->lightmapmins[0] || t < es->lightmapmins[1] ) + return false; + + ds = s - es->lightmapmins[0]; + dt = t - es->lightmapmins[1]; + + if( ds > es->lightextents[0] || dt > es->lightextents[1] ) + return false; + + if( !surf->samples ) + return true; + + int sample_size = Mod_SampleSizeForFace( surf ); + int smax = (es->lightextents[0] / sample_size) + 1; + int tmax = (es->lightextents[1] / sample_size) + 1; + ds /= sample_size; + dt /= sample_size; + + lm = surf->samples + Q_rint( dt ) * smax + Q_rint( ds ); + colorVec localDiffuse = { 0, 0, 0, 0 }; + size = smax * tmax; + + if( es->normals ) + { + Vector vec_x, vec_y, vec_z; + matrix3x3 mat; + + dm = es->normals + Q_rint( dt ) * smax + Q_rint( ds ); + + // flat TBN for better results + vec_x = Vector( surf->info->lmvecs[0] ); + vec_y = Vector( surf->info->lmvecs[1] ); + if( FBitSet( surf->flags, SURF_PLANEBACK )) + vec_z = -surf->plane->normal; + else vec_z = surf->plane->normal; + + // create tangent space rotational matrix + mat.SetForward( vec_x.Normalize( )); + mat.SetRight( -vec_y.Normalize( )); + mat.SetUp( vec_z.Normalize( )); + + for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255; map++ ) + { + float f = (1.0f / 128.0f); + Vector normal = Vector(((float)dm->r - 128.0f) * f, ((float)dm->g - 128.0f) * f, ((float)dm->b - 128.0f) * f); + uint scale = tr.lightstyle[surf->styles[map]]; // style modifier + mworldlight_t *pSkyLight = NULL; + Vector localDir = g_vecZero; + Vector sky_color = g_vecZero; + + // rotate from tangent to model space + localDir = mat.VectorRotate( normal ); + + // add local dir into main + info->normal += localDir * (float)scale; // direction factor + + // compute diffuse color + localDiffuse.r += TEXTURE_TO_TEXGAMMA( lm->r ) * scale; + localDiffuse.g += TEXTURE_TO_TEXGAMMA( lm->g ) * scale; + localDiffuse.b += TEXTURE_TO_TEXGAMMA( lm->b ) * scale; + + lm += size; // skip to next lightmap + dm += size; // skip to next deluxemap + } + + info->lightmap.r = Q_min(( localDiffuse.r >> 7 ), 255 ); + info->lightmap.g = Q_min(( localDiffuse.g >> 7 ), 255 ); + info->lightmap.b = Q_min(( localDiffuse.b >> 7 ), 255 ); + info->diffuse.x = (float)info->lightmap.r * (1.0f / 255.0f); + info->diffuse.y = (float)info->lightmap.g * (1.0f / 255.0f); + info->diffuse.z = (float)info->lightmap.b * (1.0f / 255.0f); + if( info->normal == g_vecZero ) info->normal = vec_z; // fixup some cases + info->normal = info->normal.Normalize(); + } + else + { + for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255; map++ ) + { + uint scale = tr.lightstyle[surf->styles[map]]; + + // compute diffuse color + localDiffuse.r += TEXTURE_TO_TEXGAMMA( lm->r ) * scale; + localDiffuse.g += TEXTURE_TO_TEXGAMMA( lm->g ) * scale; + localDiffuse.b += TEXTURE_TO_TEXGAMMA( lm->b ) * scale; + lm += size; // skip to next lightmap + } + + info->lightmap.r = Q_min(( localDiffuse.r >> 7 ), 255 ); + info->lightmap.g = Q_min(( localDiffuse.g >> 7 ), 255 ); + info->lightmap.b = Q_min(( localDiffuse.b >> 7 ), 255 ); + info->diffuse.x = (float)info->lightmap.r * (1.0f / 255.0f); + info->diffuse.y = (float)info->lightmap.g * (1.0f / 255.0f); + info->diffuse.z = (float)info->lightmap.b * (1.0f / 255.0f); + } + + if(( surf->styles[0] != 0 && surf->styles[0] != LS_SKY ) || surf->styles[1] != 255 ) + SetBits( info->status, LIGHTINFO_STYLES ); + + // also collect the average value + if( FBitSet( info->status, LIGHTINFO_AVERAGE )) + { + Vector localAverage = g_vecZero; + lm = surf->samples; + + for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255; map++ ) + { + float scale = tr.lightstyle[surf->styles[map]]; + + for( int i = 0; i < size; i++, lm++ ) + { + localAverage.x += (float)TEXTURE_TO_TEXGAMMA( lm->r ) * scale; + localAverage.y += (float)TEXTURE_TO_TEXGAMMA( lm->g ) * scale; + localAverage.z += (float)TEXTURE_TO_TEXGAMMA( lm->b ) * scale; + } + + localAverage *= (1.0f / (float)size ); + } + + info->average.x = Q_min( localAverage.x * (1.0f / 128.0f), 255.0f ) * (1.0f / 255.0f); + info->average.y = Q_min( localAverage.y * (1.0f / 128.0f), 255.0f ) * (1.0f / 255.0f); + info->average.z = Q_min( localAverage.z * (1.0f / 128.0f), 255.0f ) * (1.0f / 255.0f); + } + + info->surf = surf; + + return true; +} + +/* +================= +R_RecursiveLightPoint +================= +*/ +static bool R_RecursiveLightPoint( model_t *model, mnode_t *node, float p1f, float p2f, const Vector &start, const Vector &end, lightpoint_t *info ) +{ + float front, back; + float frac, midf; + int side; + msurface_t *surf; + Vector mid; + + // didn't hit anything + if( !node || node->contents < 0 ) + return false; + + // calculate mid point + front = PlaneDiff( start, node->plane ); + back = PlaneDiff( end, node->plane ); + + side = front < 0.0f; + if(( back < 0.0f ) == side ) + return R_RecursiveLightPoint( model, node->children[side], p1f, p2f, start, end, info ); + + frac = front / ( front - back ); + + midf = p1f + ( p2f - p1f ) * frac; + VectorLerp( start, frac, end, mid ); + + // co down front side + if( R_RecursiveLightPoint( model, node->children[side], p1f, midf, start, mid, info )) + return true; // hit something + + if(( back < 0.0f ) == side ) + return false;// didn't hit anything + + // check for impact on this node + surf = model->surfaces + node->firstsurface; + + for( int i = 0; i < node->numsurfaces; i++, surf++ ) + { + if( R_GetDirectLightFromSurface( surf, mid, info )) + { + info->fraction = midf; + return true; + } + } + + // go down back side + return R_RecursiveLightPoint( model, node->children[!side], midf, p2f, mid, end, info ); +} + +/* +================= +R_LightPoint +================= +*/ +static bool R_LightPoint( model_t *model, mnode_t *node, const Vector &start, const Vector &end, mstudiolight_t *light, bool ambient, float *fraction ) +{ + float factor = (ambient) ? 0.6f : 0.9f; + lightpoint_t info; + + memset( &info, 0, sizeof( lightpoint_t )); + info.fraction = *fraction = 1.0f; + info.origin = start; + + if( R_RecursiveLightPoint( model, node, 0.0f, 1.0f, start, end, &info )) + { + float total = Q_max( Q_max( info.lightmap.r, info.lightmap.g ), info.lightmap.b ); + if( total == 0.0f ) total = 1.0f; + + info.normal *= (total * factor); + light->shadelight = info.normal.Length(); + light->ambientlight = total - light->shadelight; + + if( total > 0.0f ) + { + light->diffuse.x = (float)info.lightmap.r * ( 1.0f / total ); + light->diffuse.y = (float)info.lightmap.g * ( 1.0f / total ); + light->diffuse.z = (float)info.lightmap.b * ( 1.0f / total ); + } + else light->diffuse = Vector( 1.0f, 1.0f, 1.0f ); + + if( light->ambientlight > 128 ) + light->ambientlight = 128; + + if( light->ambientlight + light->shadelight > 255 ) + light->shadelight = 255 - light->ambientlight; + light->normal = info.normal.Normalize(); + *fraction = info.fraction; + light->nointerp = FBitSet( info.status, LIGHTINFO_STYLES ) ? true : false; + + return true; + } + + return false; +} + +/* +================= +ComputeLightmapColorFromPoint +================= +*/ +static void ComputeLightmapColorFromPoint( lightpoint_t *info, mworldlight_t* pSkylight, float scale, Vector &radcolor, bool average ) +{ + if( !info->surf && FBitSet( info->status, LIGHTINFO_HITSKY )) + { + if( pSkylight ) + { + radcolor += pSkylight->intensity * scale * 0.5f * (1.0f / 255.0f); + } + else if( world->numworldlights <= 0 ) // old bsp format? + { + Vector skyAmbient = tr.sky_ambient * (1.0f / 128.0f) * tr.diffuseFactor; + radcolor += skyAmbient * scale * 0.5f * (1.0f / 255.0f); + } + return; + } + + if( info->surf != NULL ) + { + Vector reflectivity; + byte rgba[4]; + Vector color; + + GET_EXTRA_PARAMS( info->surf->texinfo->texture->gl_texturenum, &rgba[0], &rgba[1], &rgba[2], &rgba[3] ); + reflectivity.x = TextureToLinear( rgba[0] ); + reflectivity.y = TextureToLinear( rgba[1] ); + reflectivity.z = TextureToLinear( rgba[2] ); + + if( average ) color = info->average * scale; + else color = info->diffuse * scale; +#if 0 + if( reflectivity != g_vecZero ) + color *= reflectivity; +#endif + radcolor += color; + } +} + +//----------------------------------------------------------------------------- +// Computes ambient lighting along a specified ray. +// Ray represents a cone, tanTheta is the tan of the inner cone angle +//----------------------------------------------------------------------------- +static void R_CalcRayAmbientLighting( const Vector &vStart, const Vector &vEnd, mworldlight_t *pSkyLight, float tanTheta, Vector &color ) +{ + cl_entity_t *ent = NULL; + model_t *model = worldmodel; + lightpoint_t info; + pmtrace_t tr0; + + memset( &info, 0, sizeof( lightpoint_t )); + SetBits( info.status, LIGHTINFO_AVERAGE ); + info.fraction = 1.0f; + info.origin = vStart; + color = g_vecZero; + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PlayerTraceExt( (float *)&vStart, (float *)&vEnd, PM_WORLD_ONLY, R_LightTraceFilter, &tr0 ); + + if( tr0.allsolid || tr0.startsolid || tr0.fraction == 1.0f ) + return; // doesn't hit anything + + if( gEngfuncs.pEventAPI->EV_IndexFromTrace( &tr0 ) != -1 ) + ent = GET_ENTITY( gEngfuncs.pEventAPI->EV_IndexFromTrace( &tr0 )); + + if( ent ) model = ent->model; + + // Now that we've got a ray, see what surface we've hit + R_RecursiveLightPoint( model, &model->nodes[model->hulls[0].firstclipnode], 0.0f, 1.0f, vStart, vEnd, &info ); + + Vector delta = vEnd - vStart; + + // compute the approximate radius of a circle centered around the intersection point + float dist = delta.Length() * tanTheta * info.fraction; + + // until 20" we use the point sample, then blend in the average until we're covering 40" + // This is attempting to model the ray as a cone - in the ideal case we'd simply sample all + // luxels in the intersection of the cone with the surface. Since we don't have surface + // neighbor information computed we'll just approximate that sampling with a blend between + // a point sample and the face average. + // This yields results that are similar in that aliasing is reduced at distance while + // point samples provide accuracy for intersections with near geometry + float scaleAvg = RemapValClamped( dist, 20, 40, 0.0f, 1.0f ); + + // don't have luxel UV, so just use average sample + if( !info.surf ) scaleAvg = 1.0f; + + float scaleSample = 1.0f - scaleAvg; + + if( scaleAvg != 0.0f ) + { + ComputeLightmapColorFromPoint( &info, pSkyLight, scaleAvg, color, true ); + } + + if( scaleSample != 0.0f ) + { + ComputeLightmapColorFromPoint( &info, pSkyLight, scaleSample, color, false ); + } +} + +/* +================= +R_PointAmbientFromAxisAlignedSamples + +fast ambient comptation +================= +*/ +static bool R_PointAmbientFromAxisAlignedSamples( const Vector &p1, mstudiolight_t *light ) +{ + // use lightprobes instead + if( world->numleaflights && world->leafs ) + return false; + + mworldlight_t *pSkyLight = R_FindAmbientSkyLight(); + Vector radcolor[NUMVERTEXNORMALS]; + Vector lightBoxColor[6], p2; + lightpoint_t info; + + // sample world only along cardinal axes + for( int i = 0; i < 6; i++ ) + { + memset( &info, 0, sizeof( lightpoint_t )); + SetBits( info.status, LIGHTINFO_AVERAGE ); + info.fraction = 1.0f; + info.origin = p1; + + VectorMA( p1, 65536.0f * 1.74f, g_BoxDirections[i], p2 ); + + // now that we've got a ray, see what surface we've hit + if( !R_RecursiveLightPoint( worldmodel, worldmodel->nodes, 0.0f, 1.0f, p1, p2, &info )) + continue; + + ComputeLightmapColorFromPoint( &info, pSkyLight, 1.0f, light->ambient[i], true ); + light->ambient[i] *= AMBIENT_CUBE_SCALE; + } + + return true; +} + +/* +================= +R_PointAmbientFromSphericalSamples + +reconstruct the ambient lighting for a leaf +at the given position in worldspace +================= +*/ +static bool R_PointAmbientFromSphericalSamples( const Vector &p1, mstudiolight_t *light ) +{ + // use lightprobes instead + if( world->numleaflights && world->leafs ) + return false; + + // Figure out the color that rays hit when shot out from this position. + float tanTheta = tan( VERTEXNORMAL_CONE_INNER_ANGLE ); + mworldlight_t *pSkylight = R_FindAmbientSkyLight(); + Vector radcolor[NUMVERTEXNORMALS]; + Vector lightBoxColor[6], p2; + + // sample world by casting N rays distributed across a sphere + for( int i = 0; i < NUMVERTEXNORMALS; i++ ) + { + // FIXME: a good optimization would be to scale this per leaf + VectorMA( p1, 65536.0f * 1.74f, g_anorms[i], p2 ); + + R_CalcRayAmbientLighting( p1, p2, pSkylight, tanTheta, radcolor[i] ); + } + + // accumulate samples into radiant box + for( int j = 0; j < 6; j++ ) + { + float t = 0.0f; + + VectorClear( light->ambient[j] ); + + for( int i = 0; i < NUMVERTEXNORMALS; i++ ) + { + float c = DotProduct( g_anorms[i], g_BoxDirections[j] ); + + if( c > 0.0f ) + { + VectorMA( light->ambient[j], c, radcolor[i], light->ambient[j] ); + t += c; + } + } + + VectorScale( light->ambient[j], ( 1.0 / t ) * AMBIENT_CUBE_SCALE, light->ambient[j] ); + } + + return true; +} + +/* +================= +R_PointAmbientFromLeaf + +reconstruct the ambient lighting for a leaf +at the given position in worldspace +================= +*/ +void R_PointAmbientFromLeaf( const Vector &point, mstudiolight_t *light ) +{ + memset( light->ambient, 0, sizeof( light->ambient )); + + if( r_lighting_extended->value > 1.0f ) + { + if( R_PointAmbientFromSphericalSamples( point, light )) + return; + } + else + { + if( R_PointAmbientFromAxisAlignedSamples( point, light )) + return; + } + + mleaf_t *leaf = Mod_PointInLeaf( point, worldmodel->nodes ); + mextraleaf_t *info = LEAF_INFO( leaf, worldmodel ); + mlightprobe_t *pAmbientProbe = info->ambient_light; + float totalFactor = 0.0f; + int i; + + if( info->num_lightprobes > 0 ) + { + for( i = 0; i < info->num_lightprobes; i++, pAmbientProbe++ ) + { + // do an inverse squared distance weighted average of the samples + // to reconstruct the original function + float dist = (pAmbientProbe->origin - point).LengthSqr(); + float factor = 1.0f / (dist + 1.0f); + totalFactor += factor; + + for( int j = 0; j < 6; j++ ) + { + Vector v; + + v.x = (float)pAmbientProbe->cube.color[j][0] * (1.0f / 255.0f); + v.y = (float)pAmbientProbe->cube.color[j][1] * (1.0f / 255.0f); + v.z = (float)pAmbientProbe->cube.color[j][2] * (1.0f / 255.0f); + + light->ambient[j] += v * factor; + } + } + + for( i = 0; i < 6; i++ ) + { + light->ambient[i] *= (1.0f / totalFactor) * AMBIENT_CUBE_SCALE; + } + } +} + +/* +================= +R_LightIdentity +================= +*/ +static void R_LightIdentity( mstudiolight_t *light ) +{ + // get default values + light->diffuse.x = (float)TEXTURE_TO_TEXGAMMA( 255 ) / 255.0f; + light->diffuse.y = (float)TEXTURE_TO_TEXGAMMA( 255 ) / 255.0f; + light->diffuse.z = (float)TEXTURE_TO_TEXGAMMA( 255 ) / 255.0f; + light->normal = Vector( 0.0f, 0.0f, 0.0f ); +} + +/* +================= +R_LightMin +================= +*/ +static void R_LightMin( mstudiolight_t *light ) +{ + // get minlight values + light->diffuse.x = (float)TEXTURE_TO_TEXGAMMA( 10 ) / 255.0f; + light->diffuse.y = (float)TEXTURE_TO_TEXGAMMA( 10 ) / 255.0f; + light->diffuse.z = (float)TEXTURE_TO_TEXGAMMA( 10 ) / 255.0f; + light->normal = Vector( 0.0f, 0.0f, 0.0f ); +} + +/* +================= +R_LightVec + +check bspmodels to get light from +================= +*/ +void R_LightVec( const Vector &point, mstudiolight_t *light, bool ambient ) +{ + memset( light, 0 , sizeof( *light )); + + if( worldmodel && worldmodel->lightdata ) + { + Vector p0 = point + Vector( 0.0f, 0.0f, 8.0f ); + Vector p1 = point + Vector( 0.0f, 0.0f, -512.0f ); + float last_fraction = 1.0f, fraction; + Vector start = point; + mstudiolight_t probe; + + for( int i = 0; i < MAX_PHYSENTS; i++ ) + { + physent_t *pe = gEngfuncs.pEventAPI->EV_GetPhysent( i ); + Vector offset, start_l, end_l; + matrix3x4 matrix; + mnode_t *pnodes; + + if( !pe || !pe->model || pe->model->type != mod_brush ) + continue; // skip non-bsp models + + pnodes = &pe->model->nodes[pe->model->hulls[0].firstclipnode]; + // rotate start and end into the models frame of reference + if( pe->angles != g_vecZero ) + { + matrix = matrix3x4( pe->origin, pe->angles ); + start_l = matrix.VectorITransform( p0 ); + end_l = matrix.VectorITransform( p1 ); + } + else + { + start_l = p0 - pe->origin; + end_l = p1 - pe->origin; + } + + memset( &probe, 0 , sizeof( probe )); + if( !R_LightPoint( pe->model, pnodes, start_l, end_l, &probe, ambient, &fraction )) + continue; // didn't hit anything + + if( fraction < last_fraction ) + { + last_fraction = fraction; + *light = probe; + + if( light->diffuse != g_vecZero ) + return; // we get light now + } + + // get light from bmodels too + if( !CVAR_TO_BOOL( r_lighting_extended )) + return; // get light from world and out + } + + R_LightMin( light ); + } + else + { + R_LightIdentity( light ); + } +} + +/* +================= +R_LightForSky +================= +*/ +void R_LightForSky( const Vector &point, mstudiolight_t *light ) +{ + if( tr.sun_light_enabled ) + { + light->diffuse = tr.sun_diffuse; + } + else + { + light->diffuse = tr.sky_ambient * ( 1.0f / 128.0f ) * tr.diffuseFactor; + } + + // FIXME: this is correct? + light->normal = -tr.sky_normal; + light->ambientlight = 128; + light->shadelight = 192; +} + +/* +================= +R_LightForStudio + +given light for a studiomodel +================= +*/ +void R_LightForStudio( const Vector &point, mstudiolight_t *light, bool ambient ) +{ + R_LightVec( point, light, ambient ); + R_PointAmbientFromLeaf( point, light ); +} \ No newline at end of file diff --git a/cl_dll/render/gl_sprite.cpp b/cl_dll/render/gl_sprite.cpp new file mode 100644 index 0000000..ad5fc22 --- /dev/null +++ b/cl_dll/render/gl_sprite.cpp @@ -0,0 +1,595 @@ +/* +gl_sprite.cpp - sprite model rendering +Copyright (C) 2011 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include "cl_util.h" +#include "gl_local.h" +#include +#include "gl_sprite.h" +#include "gl_studio.h" +#include "event_api.h" +#include "pm_defs.h" +#include "studio.h" +#include "triangleapi.h" + +CSpriteModelRenderer g_SpriteRenderer; + +/* +==================== +Init + +==================== +*/ +void CSpriteModelRenderer :: Init( void ) +{ + // Set up some variables shared with engine + m_pCvarLerping = IEngineStudio.GetCvar( "r_sprite_lerping" ); + m_pCvarLighting = IEngineStudio.GetCvar( "r_sprite_lighting" ); +} + +/* +==================== +CSpriteModelRenderer + +==================== +*/ +CSpriteModelRenderer :: CSpriteModelRenderer( void ) +{ + m_pCurrentEntity = NULL; + m_pCvarLerping = NULL; + m_pCvarLighting = NULL; + m_pSpriteHeader = NULL; + m_pRenderModel = NULL; +} + +CSpriteModelRenderer :: ~CSpriteModelRenderer( void ) +{ +} + +/* +================ +GetSpriteFrame + +================ +*/ +mspriteframe_t *CSpriteModelRenderer :: GetSpriteFrame( int frame, float yaw ) +{ + mspritegroup_t *pspritegroup; + mspriteframe_t *pspriteframe = NULL; + + if( frame < 0 ) + { + frame = 0; + } + else if( frame >= m_pSpriteHeader->numframes ) + { + ALERT( at_warning, "R_GetSpriteFrame: no such frame %d (%s)\n", frame, m_pRenderModel->name ); + frame = m_pSpriteHeader->numframes - 1; + } + + if( m_pSpriteHeader->frames[frame].type == SPR_SINGLE ) + { + pspriteframe = (mspriteframe_t *)m_pSpriteHeader->frames[frame].frameptr; + } + else if( m_pSpriteHeader->frames[frame].type == SPR_GROUP ) + { + pspritegroup = (mspritegroup_t *)m_pSpriteHeader->frames[frame].frameptr; + + float *pintervals = pspritegroup->intervals; + int numframes = pspritegroup->numframes; + float fullinterval = pintervals[numframes-1]; + + // when loading in Mod_LoadSpriteGroup, we guaranteed all interval values + // are positive, so we don't have to worry about division by zero + float targettime = m_clTime - ((int)( m_clTime / fullinterval )) * fullinterval; + + for( int i = 0; i < (numframes - 1); i++ ) + { + if( pintervals[i] > targettime ) + break; + } + pspriteframe = pspritegroup->frames[i]; + } + else if( m_pSpriteHeader->frames[frame].type == FRAME_ANGLED ) + { + int angleframe = (int)(Q_rint(( RI->view.angles[YAW] - yaw + 45.0f ) / 360 * 8 ) - 4) & 7; + + // doom-style sprite monsters + pspritegroup = (mspritegroup_t *)m_pSpriteHeader->frames[frame].frameptr; + pspriteframe = pspritegroup->frames[angleframe]; + } + + return pspriteframe; +} + +/* +================ +CSpriteModelRenderer + +Compute renderer origin (include parent movement, sky movement, etc) +================ +*/ +void CSpriteModelRenderer :: SpriteComputeOrigin( cl_entity_t *e ) +{ + sprite_origin = e->origin; // set render origin + + // link sprite with parent (if present) + if( e->curstate.aiment > 0 && e->curstate.movetype == MOVETYPE_FOLLOW ) + { + cl_entity_t *parent = GET_ENTITY( e->curstate.aiment ); + + if( parent && parent->model ) + { + if( parent->model->type == mod_studio && e->curstate.body > 0 ) + { + int num = bound( 1, e->curstate.body, MAXSTUDIOATTACHMENTS ); + sprite_origin = R_StudioAttachmentPos( parent, num - 1 ); + } + else + { + sprite_origin = parent->origin; + } + } + } + + if( e->curstate.renderfx == SKYBOX_ENTITY ) + { + Vector trans = GetVieworg() - tr.sky_origin; + + if( tr.sky_speed ) + { + trans = trans - (GetVieworg() - tr.sky_world_origin) / tr.sky_speed; + Vector skypos = tr.sky_origin + (GetVieworg() - tr.sky_world_origin) / tr.sky_speed; + tr.modelorg = skypos - sprite_origin; + } + else + { + tr.modelorg = tr.sky_origin - sprite_origin; + } + + sprite_origin += trans; // move to the sky position + } + else + { + tr.modelorg = sprite_origin; + } +} + +/* +================ +SspriteComputeBBox + +Compute a full bounding box for reference +================ +*/ +void CSpriteModelRenderer :: SpriteComputeBBox( cl_entity_t *e, Vector bbox[8] ) +{ + // always compute origin first + if( bbox != NULL ) SpriteComputeOrigin( e ); + + // copy original bbox (no rotation for sprites) + sprite_absmin = e->model->mins; + sprite_absmax = e->model->maxs; + + float scale = 1.0f; + + if( e->curstate.scale > 0.0f ) + scale = e->curstate.scale; + + sprite_absmin *= scale; + sprite_absmax *= scale; + + sprite_absmin += sprite_origin; + sprite_absmax += sprite_origin; + + // compute a full bounding box + for( int i = 0; bbox != NULL && i < 8; i++ ) + { + bbox[i][0] = ( i & 1 ) ? sprite_absmin[0] : sprite_absmax[0]; + bbox[i][1] = ( i & 2 ) ? sprite_absmin[1] : sprite_absmax[1]; + bbox[i][2] = ( i & 4 ) ? sprite_absmin[2] : sprite_absmax[2]; + } +} + +/* +================ +CullSpriteModel + +Cull sprite model by bbox +================ +*/ +bool CSpriteModelRenderer :: CullSpriteModel( void ) +{ + if( !m_pSpriteHeader ) + return true; + + SpriteComputeBBox( m_pCurrentEntity, NULL ); + + return R_CullModel( m_pCurrentEntity, sprite_absmin, sprite_absmax ); +} + +/* +================ +GlowSightDistance + +Calc sight distance for glow-sprites +================ +*/ +float CSpriteModelRenderer :: GlowSightDistance( void ) +{ + pmtrace_t tr; + + float dist = (sprite_origin - GetVieworg( )).Length(); + + if( !FBitSet( RI->params, RP_MIRRORVIEW )) + { + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PlayerTrace( GetVieworg(), sprite_origin, PM_GLASS_IGNORE, -1, &tr ); + + if((( 1.0f - tr.fraction ) * dist ) > 8.0f ) + return -1; + } + + return dist; +} + +/* +================ +R_GlowSightDistance + +Set sprite brightness factor +================ +*/ +float CSpriteModelRenderer :: SpriteGlowBlend( int rendermode, int renderfx, int alpha, float &scale ) +{ + float dist = GlowSightDistance(); + float brightness; + + if( dist <= 0 ) return 0.0f; // occluded + + if( renderfx == kRenderFxNoDissipation ) + return (float)alpha * (1.0f / 255.0f); + + scale = 0.0f; // variable sized glow + + brightness = 19000.0 / ( dist * dist ); + brightness = bound( 0.05f, brightness, 1.0f ); + + // make the glow fixed size in screen space, taking into consideration the scale setting. + if( scale == 0.0f ) scale = 1.0f; + scale *= dist * ( 1.0f / 200.0f ); + + return brightness; +} + +/* +================ +SpriteOccluded + +Do occlusion test for glow-sprites +================ +*/ +int CSpriteModelRenderer :: SpriteOccluded( int &alpha, float &pscale ) +{ + // always compute origin first + SpriteComputeOrigin( m_pCurrentEntity ); + + if( m_pCurrentEntity->curstate.rendermode == kRenderGlow ) + { + // don't reflect this entity in mirrors + if( FBitSet( m_pCurrentEntity->curstate.effects, EF_NOREFLECT ) && FBitSet( RI->params, RP_MIRRORVIEW )) + return true; + + // draw only in mirrors + if( FBitSet( m_pCurrentEntity->curstate.effects, EF_REFLECTONLY ) && !FBitSet( RI->params, RP_MIRRORVIEW )) + return true; + + // original glow occlusion code by BUzer from Paranoia + if( m_pCurrentEntity->curstate.renderfx == kRenderFxNoDissipation && CVAR_TO_BOOL( v_glows )) + { + mspriteframe_t *frame = GetSpriteFrame( m_pCurrentEntity->curstate.frame, m_pCurrentEntity->angles[YAW] ); + Vector left = Vector( 0.0f, ( frame->left * pscale ) / 5.0f, 0.0f ); + Vector right = Vector( 0.0f, ( frame->right * pscale ) / 5.0f, 0.0f ); + matrix4x4 sprite_transform = RI->view.matrix; + + sprite_transform.SetOrigin( sprite_origin ); + Vector aleft = sprite_transform.VectorTransform( left ); + Vector aright = sprite_transform.VectorTransform( right ); + Vector dist = aright - aleft; + + float dst = DotProduct( GetVForward(), sprite_origin - GetVieworg() ); + dst = bound( 0.0f, dst, 64.0f ); + dst = dst / 64.0f; + + int numtraces = GLOW_NUM_TRACES; + if( numtraces < 1 ) numtraces = 1; + Vector step = dist * (1.0f / ( numtraces + 1 )); + float frac = 1.0f / numtraces; + float totalfrac = 0.0f; + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + + for( int j = 0; j < numtraces; j++ ) + { + Vector start = aleft + step * (j+1); + + pmtrace_t pmtrace; + gEngfuncs.pEventAPI->EV_PlayerTrace( GetVieworg(), start, PM_GLASS_IGNORE|PM_STUDIO_IGNORE, -1, &pmtrace ); + + if( pmtrace.fraction == 1.0f ) + totalfrac += frac; + } + + float targetalpha = totalfrac; + float blend; + + if( m_pCurrentEntity->latched.sequencetime > targetalpha ) + { + m_pCurrentEntity->latched.sequencetime -= tr.frametime * GLOW_INTERP_SPEED; + if( m_pCurrentEntity->latched.sequencetime <= targetalpha ) + m_pCurrentEntity->latched.sequencetime = targetalpha; + } + else if( m_pCurrentEntity->latched.sequencetime < targetalpha ) + { + m_pCurrentEntity->latched.sequencetime += tr.frametime * GLOW_INTERP_SPEED; + if( m_pCurrentEntity->latched.sequencetime >= targetalpha ) + m_pCurrentEntity->latched.sequencetime = targetalpha; + } + + blend = m_pCurrentEntity->latched.sequencetime * dst; + alpha *= blend; + + if( blend <= 0.01f ) + return true; // faded + } + else + { + Vector screenVec; + float blend; + + WorldToScreen( sprite_origin, screenVec ); + + if( screenVec[0] < RI->view.port[0] || screenVec[0] > RI->view.port[0] + RI->view.port[2] ) + return true; // out of screen + if( screenVec[1] < RI->view.port[1] || screenVec[1] > RI->view.port[1] + RI->view.port[3] ) + return true; // out of screen + + blend = SpriteGlowBlend( m_pCurrentEntity->curstate.rendermode, m_pCurrentEntity->curstate.renderfx, alpha, pscale ); + alpha *= blend; + + if( blend <= 0.01f ) + return true; // faded + } + } + else + { + if( CullSpriteModel( )) + return true; + } + return false; +} + +/* +================= +DrawSpriteQuad + +================= +*/ +void CSpriteModelRenderer :: DrawSpriteQuad( mspriteframe_t *frame, const Vector &org, const Vector &right, const Vector &up, float scale ) +{ + Vector point; + + pglBegin( GL_QUADS ); + pglTexCoord2f( 0.0f, 1.0f ); + point = org + up * (frame->down * scale); + point = point + right * (frame->left * scale); + pglVertex3fv( point ); + pglTexCoord2f( 0.0f, 0.0f ); + point = org + up * (frame->up * scale); + point = point + right * (frame->left * scale); + pglVertex3fv( point ); + pglTexCoord2f( 1.0f, 0.0f ); + point = org + up * (frame->up * scale); + point = point + right * (frame->right * scale); + pglVertex3fv( point ); + pglTexCoord2f( 1.0f, 1.0f ); + point = org + up * (frame->down * scale); + point = point + right * (frame->right * scale); + pglVertex3fv( point ); + pglEnd(); +} + +int CSpriteModelRenderer :: SpriteHasLightmap( int texFormat ) +{ + if( m_pCvarLighting->value == 0 ) + return false; + + if( texFormat != SPR_ALPHTEST ) + return false; + + if( FBitSet( m_pCurrentEntity->curstate.effects, EF_FULLBRIGHT )) + return false; + + if( m_pCurrentEntity->curstate.renderamt <= 127 ) + return false; + + switch( m_pCurrentEntity->curstate.rendermode ) + { + case kRenderNormal: + case kRenderTransAlpha: + case kRenderTransTexture: + break; + default: + return false; + } + + return true; +} + +/* +================= +R_DrawSpriteModel +================= +*/ +void CSpriteModelRenderer :: AddSpriteModelToDrawList( cl_entity_t *e, bool update ) +{ + // obviously we can't update sprites... + if( update ) return; + + m_pCurrentEntity = RI->currententity; + + IEngineStudio.GetTimes( &m_nFrameCount, &m_clTime, &m_clOldTime ); + + m_pRenderModel = m_pCurrentEntity->model; + m_pSpriteHeader = (msprite_t *)Mod_Extradata( m_pRenderModel ); + + if( !m_pSpriteHeader ) return; + + int alpha = m_pCurrentEntity->curstate.renderamt; + float scale = m_pCurrentEntity->curstate.scale; + int rendermode = m_pCurrentEntity->curstate.rendermode; + + if( SpriteOccluded( alpha, scale )) + return; // sprite culled + + if( m_pSpriteHeader->texFormat == SPR_ALPHTEST ) + { + if( rendermode != kRenderGlow && rendermode != kRenderTransAdd ) + rendermode = kRenderTransAlpha; + } + + Vector color, color2; + + // add basecolor (any rendermode can have colored sprite) + color[0] = (float)m_pCurrentEntity->curstate.rendercolor.r * ( 1.0f / 255.0f ); + color[1] = (float)m_pCurrentEntity->curstate.rendercolor.g * ( 1.0f / 255.0f ); + color[2] = (float)m_pCurrentEntity->curstate.rendercolor.b * ( 1.0f / 255.0f ); + + if( SpriteHasLightmap( m_pSpriteHeader->texFormat )) + { + gEngfuncs.pTriAPI->LightAtPoint( sprite_origin, (float *)&color2 ); + color2 *= (1.0f / 255.0f); + color *= color2; + } + + mspriteframe_t *frame = GetSpriteFrame( m_pCurrentEntity->curstate.frame, m_pCurrentEntity->angles[YAW] ); + + int type = m_pSpriteHeader->type; + + // automatically roll parallel sprites if requested + if( m_pCurrentEntity->angles[ROLL] != 0.0f && type == SPR_FWD_PARALLEL ) + type = SPR_FWD_PARALLEL_ORIENTED; + + Vector v_forward, v_right, v_up; + + switch( type ) + { + case SPR_ORIENTED: + { + AngleVectors( m_pCurrentEntity->angles, v_forward, v_right, v_up ); + sprite_origin = sprite_origin - v_forward * 0.01f; // to avoid z-fighting + } + break; + case SPR_FACING_UPRIGHT: + { + v_right.x = sprite_origin.y - GetVieworg().y; + v_right.y = -(sprite_origin.x - GetVieworg().x); + v_right.z = 0.0f; + v_up.x = v_up.y = 0.0f; + v_up.z = 1.0f; + v_right = v_right.Normalize(); + } + break; + case SPR_FWD_PARALLEL_UPRIGHT: + { + float dot = GetVForward().z; + if(( dot > 0.999848f ) || ( dot < -0.999848f )) // cos(1 degree) = 0.999848 + return; // invisible + + v_right.x = GetVForward().y; + v_right.y = -GetVForward().x; + v_right.z = 0.0f; + v_up.x = v_up.y = 0.0f; + v_up.z = 1.0f; + v_right = v_right.Normalize(); + } + break; + case SPR_FWD_PARALLEL_ORIENTED: + { + float angle = m_pCurrentEntity->angles[ROLL] * (M_PI * 2.0f / 360.0f); + float sr, cr; + + SinCos( angle, &sr, &cr ); + + for( int i = 0; i < 3; i++ ) + { + v_right[i] = (GetVLeft()[i] * cr + GetVUp()[i] * sr); + v_up[i] = GetVLeft()[i] * -sr + GetVUp()[i] * cr; + } + } + break; + case SPR_FWD_PARALLEL: // normal sprite + default: + { + v_right = GetVLeft(); + v_up = GetVUp(); + } + break; + } + + float flAlpha = (float)alpha * ( 1.0f / 255.0f ); + + if( m_pCurrentEntity->curstate.rendermode == kRenderGlow && m_pCurrentEntity->curstate.renderfx == kRenderFxNoDissipation ) + { + if( CVAR_TO_BOOL( v_glows )) + { + flAlpha = m_pCurrentEntity->curstate.renderamt * ( 1.0f/ 255.0f ); + color *= (float)alpha * ( 1.0f/ 255.0f ); + } + } + + Vector point[4]; + Vector4D spriteColor; + Vector absmin, absmax; + + spriteColor = Vector4D( color[0], color[1], color[2], flAlpha ); + point[0] = sprite_origin + v_up * (frame->down * scale); + point[0] = point[0] + v_right * (frame->left * scale); + point[1] = sprite_origin + v_up * (frame->up * scale); + point[1] = point[1] + v_right * (frame->left * scale); + point[2] = sprite_origin + v_up * (frame->up * scale); + point[2] = point[2] + v_right * (frame->right * scale); + point[3] = sprite_origin + v_up * (frame->down * scale); + point[3] = point[3] + v_right * (frame->right * scale); + + // more precision bounds than sprite_absmin\absmax + ClearBounds( absmin, absmax ); + for( int i = 0; i < 4; i++ ) + AddPointToBounds( point[i], absmin, absmax ); + + CTransEntry entry; + entry.SetRenderPrimitive( point, spriteColor, frame->gl_texturenum, rendermode ); + entry.ComputeViewDistance( absmin, absmax ); + RI->frame.trans_list.AddToTail( entry ); +} + +mspriteframe_t *CSpriteModelRenderer :: GetSpriteFrame( const model_t *m_pSpriteModel, int frame ) +{ + if(( m_pSpriteHeader = (msprite_t *)Mod_Extradata( (model_t *)m_pSpriteModel )) == NULL ) + return NULL; + + m_pCurrentEntity = NULL; + return GetSpriteFrame( frame, 0.0f ); +} \ No newline at end of file diff --git a/cl_dll/render/gl_sprite.h b/cl_dll/render/gl_sprite.h new file mode 100644 index 0000000..38937a3 --- /dev/null +++ b/cl_dll/render/gl_sprite.h @@ -0,0 +1,112 @@ +/* +gl_sprite.h - sprite model rendering +Copyright (C) 2011 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef GL_SPRITE_H +#define GL_SPRITE_H + +#include "sprite.h" + +#define GLOW_NUM_TRACES 5 +#define GLOW_INTERP_SPEED 2.0f // time to fade glows + +/* +==================== +CSpriteModelRenderer + +==================== +*/ +class CSpriteModelRenderer +{ +public: + // Construction/Destruction + CSpriteModelRenderer( void ); + virtual ~CSpriteModelRenderer( void ); + + // Initialization + virtual void Init( void ); +private: + // Get Sprite description for frame + virtual mspriteframe_t *GetSpriteFrame( int frame, float yaw ); + + virtual void SpriteComputeOrigin( cl_entity_t *e ); + + virtual void SpriteComputeBBox( cl_entity_t *e, Vector bbox[8] ); + + virtual bool CullSpriteModel( void ); + + virtual float GlowSightDistance( void ); + + virtual float SpriteGlowBlend( int rendermode, int renderfx, int alpha, float &scale ); + + virtual int SpriteOccluded( int &alpha, float &pscale ); + + virtual void DrawSpriteQuad( mspriteframe_t *frame, const Vector &org, const Vector &right, const Vector &up, float scale ); + + virtual int SpriteHasLightmap( int texFormat ); + + inline void *Mod_Extradata( model_t *mod ) + { + if( mod && mod->type == mod_sprite ) + return mod->cache.data; + return NULL; + } + + Vector sprite_origin; + Vector sprite_absmin, sprite_absmax; + + // Client clock + double m_clTime; + // Old Client clock + double m_clOldTime; + // Current render frame # + int m_nFrameCount; + + // Cvars that sprite model code needs to reference + cvar_t *m_pCvarLerping; // Use lerping for animation? + cvar_t *m_pCvarLighting; // lighting mode + + // The entity which we are currently rendering. + cl_entity_t *m_pCurrentEntity; + + // The model for the entity being rendered + model_t *m_pRenderModel; + + // Current model rendermode + int m_iRenderMode; + + // Pointer to header block for sprite model data + msprite_t *m_pSpriteHeader; + + // engine stuff (backend) +public: + void AddSpriteModelToDrawList( cl_entity_t *e, bool update = false ); + + // Draw generic spritemodel + mspriteframe_t *GetSpriteFrame( const model_t *m_pSpriteModel, int frame ); +}; + +extern CSpriteModelRenderer g_SpriteRenderer; + +inline mspriteframe_t *R_GetSpriteFrame( const model_t *m_pSpriteModel, int frame ) +{ + return g_SpriteRenderer.GetSpriteFrame( m_pSpriteModel, frame ); +} + +inline void R_AddSpriteToDrawList( cl_entity_t *e, bool update = false ) +{ + g_SpriteRenderer.AddSpriteModelToDrawList( e, update ); +} + +#endif// GL_SPRITE_H \ No newline at end of file diff --git a/cl_dll/render/gl_studio.h b/cl_dll/render/gl_studio.h new file mode 100644 index 0000000..e108a9a --- /dev/null +++ b/cl_dll/render/gl_studio.h @@ -0,0 +1,744 @@ +/* +gl_studio.h - studio model rendering +this code written for Paranoia 2: Savior modification +Copyright (C) 2014 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef GL_STUDIO_H +#define GL_STUDIO_H + +#include "pmtrace.h" +#include "studio.h" +#include +#include +#include +#include "gl_decals.h" +#include "gl_studiodecal.h" +#include "trace.h" +#include "bs_defs.h" +#include "ikcontext.h" +#include "jigglebones.h" +#include "tbnfile.h" + +#define EVENT_CLIENT 5000 // less than this value it's a server-side studio events +#define MAX_MODEL_MESHES (MAXSTUDIOBODYPARTS * MAXSTUDIOMODELS) +#define SHADE_LAMBERT 1.495f +#define MAXARRAYVERTS 65536 // max vertices per studio submodel + +#define MAX_SEQBLENDS 8 // must be power of two +#define MASK_SEQBLENDS (MAX_SEQBLENDS - 1) + +#define MF_STATIC_BOUNDS BIT( 0 ) // this model is a env_static. don't recalc bounds every frame +#define MF_CUSTOM_LIGHTGRID BIT( 1 ) // model has special attahaments that was accepted as light probe start points +#define MF_VERTEX_LIGHTING BIT( 2 ) // model has the custom vertex lighting (loading from level) +#define MF_SURFACE_LIGHTING BIT( 3 ) // model has the custom surface lighting (loading from level) +#define MF_VL_BAD_CACHE BIT( 4 ) // for some reasons this model can't be instanced with a vertex lighting (bad crc, mismatch vertexcount etc) +#define MF_INIT_SMOOTHSTAIRS BIT( 5 ) +#define MF_ATTACHMENTS_DONE BIT( 6 ) +#define MF_POSITION_CHANGED BIT( 7 ) + +// studiorenderer modes +#define DRAWSTUDIO_NORMAL 0 // as default +#define DRAWSTUDIO_VIEWMODEL 1 // rendeging viewmodel +#define DRAWSTUDIO_HEADSHIELD 2 // headshield or gasmask +#define DRAWSTUDIO_RUNEVENTS 3 // run events of studiomodel + +#define LIGHTSTATIC_NONE 0 +#define LIGHTSTATIC_VERTEX 1 +#define LIGHTSTATIC_SURFACE 2 + +#define TBNSTATE_INACTIVE 0 +#define TBNSTATE_LOADING 1 +#define TBNSTATE_GENERATE 2 + +// holds temporary data +typedef struct +{ + int firstvertex; + int numvertices; + int firstindex; + int numindices; + int lightmapnum; +} StudioMesh_t; + +// new blending sequence system +typedef struct +{ + float blendtime; // time to blend between current and previous sequence + int sequence; // previous sequence number + float cycle; // cycle where sequence was changed + float fadeout; + bool gaitseq; +} mstudioblendseq_t; + +typedef struct +{ + float frame; + int sequence; + float gaitframe; + int gaitsequence; + + // for smooth stair climbing + float stairtime; + float stairoldz; +} mstudiolerp_t; + +// source vertex data (106 bytes here) +typedef struct xvert_s +{ + Vector vertex; // position + Vector normal; // normal + Vector tangent; // tangent + Vector binormal; // binormal + float stcoord[4]; // ST texture coords + float lmcoord0[4]; // LM texture coords + float lmcoord1[4]; // LM texture coords + char boneid[4]; // control bones + byte weight[4]; // boneweights + float light[MAXLIGHTMAPS]; // packed color + float deluxe[MAXLIGHTMAPS]; // packed lightdir + byte styles[MAXLIGHTMAPS]; // lightstyles + word m_MeshVertexIndex; // index into the mesh's vertex list (decals only) +} svert_t; + +typedef void (*pfnCreateStudioBuffer)( vbomesh_t *pOut, svert_t *arrayxvert ); +typedef void (*pfnBindStudioBuffer)( vbomesh_t *pOut, int attrFlags ); + +typedef struct +{ + pfnCreateStudioBuffer CreateBuffer; + pfnBindStudioBuffer BindBuffer; + const char* BufferName; // debug +} mesh_loader_t; + +class CBaseBoneSetup : public CStudioBoneSetup +{ +public: + virtual void debugMsg( char *szFmt, ... ); + virtual mstudioanim_t *GetAnimSourceData( mstudioseqdesc_t *pseqdesc ); + virtual void debugLine( const Vector& origin, const Vector& dest, int r, int g, int b, bool noDepthTest = false, float duration = 0.0f ); +}; + +/* +==================== +CStudioModelRenderer + +==================== +*/ +class CStudioModelRenderer +{ +public: + // Construction/Destruction + CStudioModelRenderer( void ); + virtual ~CStudioModelRenderer( void ); + + // Initialization + void Init( void ); + + void VidInit( void ); + + // Look up animation data for sequence + mstudioanim_t *StudioGetAnim ( model_t *pModel, mstudioseqdesc_t *pseqdesc ); + + // precache vertexlit mesh + void CreateStudioCacheVL( const char *modelname, int cacheID ); + + // precache lightmapped mesh + void CreateStudioCacheFL( const char *modelname, int cacheID ); + + // throw all the meshes when the engine is shutting down + void FreeStudioCacheVL( void ); + void FreeStudioCacheFL( void ); +private: + // Local interfaces + + // Extract bbox from current sequence + int StudioExtractBbox ( studiohdr_t *phdr, int sequence, Vector &mins, Vector &maxs ); + + // Compute a full bounding box for current sequence + int StudioComputeBBox ( void ); + + float CalcStairSmoothValue( float oldz, float newz, float smoothtime, float smoothvalue ); + + const Vector StudioGetOrigin( void ); + + // Interpolate model position and angles and set up matrices + void StudioSetUpTransform( void ); + + // Set up model bone positions + void StudioSetupBones( void ); + + // Find final attachment points + void StudioCalcAttachments( matrix3x4 bones[] ); + + void AddBlendSequence( int oldseq, int newseq, float prevframe, bool gaitseq = false ); + + void BlendSequence( Vector pos[], Vector4D q[], mstudioblendseq_t *pseqblend ); + + void UpdateIKLocks( CIKContext *pIK ); + + void CalculateIKLocks( CIKContext *pIK ); + + // Merge cached bones with current bones for model + void StudioMergeBones( matrix3x4 &transform, matrix3x4 bones[], matrix3x4 cached_bones[], model_t *pModel, model_t *pParentModel ); + + // Determine interpolation fraction + float StudioEstimateInterpolant( void ); + + // Determine current gaitframe for rendering + float StudioEstimateGaitFrame ( mstudioseqdesc_t *pseqdesc ); + + // Determine current frame for rendering + float StudioEstimateFrame ( mstudioseqdesc_t *pseqdesc ); + + void StudioInterpolateControllers( cl_entity_t *e, float dadt ); + + void StudioInterpolatePoseParams( cl_entity_t *e, float dadt ); + + // Apply special effects to transform matrix + void StudioFxTransform( cl_entity_t *ent, matrix3x4 &transform ); + + void ComputeSkinMatrix( mstudioboneweight_t *boneweights, const matrix3x4 worldtransform[], matrix3x4 &result ); + + void ComputeSkinMatrix( svert_t *vertex, const matrix3x4 worldtransform[], matrix3x4 &result ); + + int StudioCheckLOD( void ); + + //calc bodies and get pointers to him + int StudioSetupModel ( int bodypart, mstudiomodel_t **ppsubmodel, msubmodel_t **ppvbomodel ); + + void DrawMeshFromBuffer( const vbomesh_t *mesh ); + + void DeleteVBOMesh( vbomesh_t *mesh ); + + // Process studio client events + void StudioClientEvents( void ); + + void StudioLighting( float *lv, int bone, int flags, const Vector &normal ); + + void StudioStaticLight( cl_entity_t *ent, mstudiolight_t *light ); + void CacheVertexLight( cl_entity_t *ent ); + void CacheSurfaceLight( cl_entity_t *ent ); + void StudioFormatAttachment( Vector &point ); + + word ChooseStudioProgram( studiohdr_t *phdr, mstudiomaterial_t *mat, bool lightpass ); + + void AddMeshToDrawList( studiohdr_t *phdr, vbomesh_t *mesh, bool lightpass ); + + void AddBodyPartToDrawList( studiohdr_t *phdr, mbodypart_t *bodyparts, int bodypart, bool lightpass ); + + bool CheckBoneCache( float f ); + + word ShaderSceneForward( mstudiomaterial_t *mat, int lightmode, bool bone_weighting, int numbones ); + word ShaderLightForward( CDynLight *dl, mstudiomaterial_t *mat, bool bone_weighting, int numbones ); + word ShaderSceneDeferred( mstudiomaterial_t *mat, bool bone_weighting, int numbones ); + word ShaderLightDeferred( mstudiomaterial_t *mat, bool bone_weighting, int numbones ); + word ShaderSceneDepth( mstudiomaterial_t *mat, bool bone_weighting, int numbones ); + word ShaderDecalForward( studiodecal_t *pDecal, bool has_vertexlight ); + + // Debug drawing + void StudioDrawDebug( cl_entity_t *e ); + + void StudioDrawHulls( int iHitbox = -1 ); + + void StudioDrawAbsBBox( void ); + + void StudioDrawBones( void ); + + void StudioDrawAttachments( bool bCustomFov ); + + int HeadShieldThink( void ); + + // intermediate structure. Used only for build unique submodels + struct TmpModel_t + { + char name[64]; + mstudiomodel_t *pmodel; + msubmodel_t *pout; + }; + + struct BoneCache_t + { + float frame; // product of StudioEstimateFrame, not a curstate.frame! + short sequence; + byte blending[2]; + byte controller[4]; + byte mouthopen; + matrix3x4 transform; // cached transform because ent->angles\ent->origin doesn't contains interpolation info + float poseparam[MAXSTUDIOPOSEPARAM]; + + // special fields for player + short gaitsequence; + float gaitframe; + }; + + struct StudioAttachment_t + { + char name[MAXSTUDIONAME]; + matrix3x4 local; // local position + Vector origin; // attachment pos + Vector angles; // VectorAngles + Vector dir; // old method + }; + + typedef struct + { + int m_VertCount; // Number of used vertices + int m_Indices[2][7]; // Indices into the clip verts array of the used vertices + bool m_Pass; // Helps us avoid copying the m_Indices array by using double-buffering + int m_ClipVertCount; // Add vertices we've started with and had to generate due to clipping + svert_t m_ClipVerts[16]; + int m_ClipFlags[16]; // Union of the decal triangle clip flags above for each vert + } DecalClipState_t; + + typedef CUtlArray StudioDecalList_t; + typedef CUtlArray CIntVector; + + enum + { + MESHLOADER_BASE = 0, + MESHLOADER_BASEBUMP, + MESHLOADER_VLIGHT, + MESHLOADER_VLIGHTBUMP, + MESHLOADER_WEIGHT, + MESHLOADER_WEIGHTBUMP, + MESHLOADER_LIGHTMAP, + MESHLOADER_LIGHTMAPBUMP, + MESHLOADER_GENERIC, + MESHLOADER_COUNT, + }; + + struct ModelInstance_t + { + cl_entity_t *m_pEntity; + + // Need to store off the model. When it changes, we lose all instance data.. + model_t *m_pModel; + StudioDecalList_t m_DecalList; // new decal list for each instance + int m_DecalCount; // just used as timestamp for calculate decal depth + int info_flags; + + // NOTE: each decal applied while model has some pose + // keep it in this array for each model and dump into + // savefile to properly restored decal positions + CUtlArray pose_stamps; + + mstudiocache_t *m_VlCache; // valid only for env_statics that have vertexlight data + mstudiocache_t *m_FlCache; // valid only for env_statics that have surfacelight data + byte styles[MAXLIGHTMAPS];// actual only if MF_VERTEX_LIGHTING|MF_SURFACE_LIGHTING bit is set + + // bounds info + Vector absmin; + Vector absmax; + float radius; + Vector origin; // transformed origin + + BoneCache_t bonecache; // just compare to avoid recalc bones every frame + + // attachments + StudioAttachment_t attachment[MAXSTUDIOATTACHMENTS]; + int numattachments; + + byte m_controller[MAXSTUDIOCONTROLLERS]; + float m_poseparameter[MAXSTUDIOPOSEPARAM]; // blends for traditional studio models + float m_oldposeparams[MAXSTUDIOPOSEPARAM]; // latched values + mstudioblendseq_t m_seqblend[MAX_SEQBLENDS]; + int m_current_seqblend; + + // sequence blends stuff + mstudiolerp_t lerp; + + CJiggleBones *m_pJiggleBones; + CIKContext m_ik; + // attached cubemaps + mcubemap_t *cubemap[2]; + float lerpFactor; + + mstudiomaterial_t *materials; // shaders cache + + // light interpolation + mstudiolight_t oldlight; + mstudiolight_t newlight; + mstudiolight_t light; // cached light values + + float lighttimecheck; + bool light_update; + byte lights[MAXDYNLIGHTS]; // dynamic lights cache + + matrix3x4 m_protationmatrix; + matrix3x4 m_pbones[MAXSTUDIOBONES]; // bone to pose + matrix3x4 m_pwpnbones[MAXSTUDIOBONES]; + Vector4D m_glstudiobones[MAXSTUDIOBONES*3]; // GLSL-friendly compacted matrix4x3 + Vector4D m_glweaponbones[MAXSTUDIOBONES*3]; // GLSL-friendly compacted matrix4x3 + + // GLSL cached arrays + Vector4D m_studioquat[MAXSTUDIOBONES]; + Vector m_studiopos[MAXSTUDIOBONES]; + Vector4D m_weaponquat[MAXSTUDIOBONES]; + Vector m_weaponpos[MAXSTUDIOBONES]; + GLfloat m_glmatrix[16]; + + unsigned int cached_frame; // to avoid compute bones more than once per frame + unsigned int visframe; // model is visible this frame + }; + + struct DecalBuildInfo_t + { + // this part is constant all time while decal is build + const DecalGroupEntry* m_pTexInfo; + modelstate_t* modelState; + dmodelvertlight_t* modelLight; + Vector vecLocalNormal; + Vector vecLocalEnd; + Vector2D vecDecalScale; + bool m_UseClipVert; + int decalDepth; + byte decalFlags; + int poseState; + studiodecal_t *current; + float m_Radius; + + // this part is changed for each mesh + vbomesh_t* m_pModelMesh; + StudioMesh_t* m_pDecalMesh; + dvertlight_t* m_pVertexLight; + DecalVertexInfo_t* m_pVertexInfo; + CUtlArray m_Vertices; + CUtlArray m_Indices; + }; + + // keep model instances for each entity + CUtlLinkedList< ModelInstance_t, word > m_ModelInstances; + + // decal stuff + bool ComputePoseToDecal( const Vector &vecStart, const Vector &vecEnd ); + void AddDecalToModel( DecalBuildInfo_t& buildInfo ); + void AddDecalToMesh( DecalBuildInfo_t& buildInfo ); + void AllocDecalForMesh( DecalBuildInfo_t& build ); + void ProjectDecalOntoMesh( DecalBuildInfo_t& build ); + bool IsFrontFacing( const svert_t *vert ); + void DecalCreateBuffer( DecalBuildInfo_t& build, studiodecal_t *pDecal ); + bool TransformToDecalSpace( DecalBuildInfo_t& build, const svert_t *vert, Vector2D& uv ); + matrix3x3 GetDecalRotateTransform( byte vertexBone ); + void AddTriangleToDecal( DecalBuildInfo_t& build, int i1, int i2, int i3 ); + int ComputeClipFlags( Vector2D const& uv ); + bool ClipDecal( DecalBuildInfo_t& build, int i1, int i2, int i3, int *pClipFlags ); + void ConvertMeshVertexToDecalVertex( DecalBuildInfo_t& build, int vertIndex, svert_t *out ); + void ClipTriangleAgainstPlane( DecalClipState_t& state, int normalInd, int flag, float val ); + int IntersectPlane( DecalClipState_t& state, int start, int end, int normalInd, float val ); + word AddVertexToDecal( DecalBuildInfo_t& build, int vertIndex ); + word AddVertexToDecal( DecalBuildInfo_t& build, svert_t *vert ); + void AddClippedDecalToTriangle( DecalBuildInfo_t& build, DecalClipState_t& clipState ); + void ComputeDecalTBN( DecalBuildInfo_t& build ); + void PurgeDecals( ModelInstance_t *inst ); + void PurgeDecals( cl_entity_t *pEnt ); + bool StudioSetEntity( cl_entity_t *pEnt ); + bool StudioSetEntity( CSolidEntry *entry ); + bool IsModelInstanceValid( ModelInstance_t *inst ); + bool StudioSetupInstance( void ); + void DestroyInstance( word handle ); + void SetDecalUniforms( studiodecal_t *pDecal ); + void DrawDecal( CSolidEntry *entry, GLenum cull = GL_FRONT ); + + mstudiocache_t *CreateStudioCache( void *dml = NULL, int lightmode = LIGHTSTATIC_NONE ); + void DeleteStudioCache( mstudiocache_t **ppcache ); + void DestroyMeshCache( void ); + + void CreateStudioCacheVL( dmodelvertlight_t *dml, int cacheID ); + void CreateStudioCacheFL( dmodelfacelight_t *dml, int cacheID ); + void PrecacheStudioShaders( void ); + void LoadStudioMaterials( void ); + void FreeStudioMaterials( void ); + + void UpdateInstanceMaterials( void ); + void ClearInstanceData( bool create ); + + // set uniforms data for specified shader + void DrawSingleMesh( CSolidEntry *mesh, bool force ); + + void SetupSubmodelVerts( const mstudiomodel_t *pSubModel, const matrix3x4 bones[], void *dml, int lightmode ); + void MeshCreateBuffer( vbomesh_t *pDst, const mstudiomesh_t *pSrc, const StudioMesh_t *pMeshInfo, int lightmode ); + void AllocLightmapsForMesh( StudioMesh_t *pCurMesh, const dmodelfacelight_t *dfl ); + bool CalcLightmapAxis( mstudiosurface_t *surf, const dfacelight_t *fl, const dmodelfacelight_t *dfl ); + static void CreateBufferBaseGL21( vbomesh_t *pOut, svert_t *arrayxvert ); + static void CreateBufferBaseGL30( vbomesh_t *pOut, svert_t *arrayxvert ); + static void CreateBufferBaseBumpGL21( vbomesh_t *pOut, svert_t *arrayxvert ); + static void CreateBufferBaseBumpGL30( vbomesh_t *pOut, svert_t *arrayxvert ); + static void CreateBufferVLightGL21( vbomesh_t *pOut, svert_t *arrayxvert ); + static void CreateBufferVLightGL30( vbomesh_t *pOut, svert_t *arrayxvert ); + static void CreateBufferVLightBumpGL21( vbomesh_t *pOut, svert_t *arrayxvert ); + static void CreateBufferVLightBumpGL30( vbomesh_t *pOut, svert_t *arrayxvert ); + static void CreateBufferWeightGL21( vbomesh_t *pOut, svert_t *arrayxvert ); + static void CreateBufferWeightGL30( vbomesh_t *pOut, svert_t *arrayxvert ); + static void CreateBufferWeightBumpGL21( vbomesh_t *pOut, svert_t *arrayxvert ); + static void CreateBufferWeightBumpGL30( vbomesh_t *pOut, svert_t *arrayxvert ); + static void CreateBufferLightMapGL21( vbomesh_t *pOut, svert_t *arrayxvert ); + static void CreateBufferLightMapGL30( vbomesh_t *pOut, svert_t *arrayxvert ); + static void CreateBufferLightMapBumpGL21( vbomesh_t *pOut, svert_t *arrayxvert ); + static void CreateBufferLightMapBumpGL30( vbomesh_t *pOut, svert_t *arrayxvert ); + static void CreateBufferGenericGL21( vbomesh_t *pOut, svert_t *arrayxvert ); + static void CreateBufferGenericGL30( vbomesh_t *pOut, svert_t *arrayxvert ); + + static void BindBufferBaseGL21( vbomesh_t *pOut, int attrFlags ); + static void BindBufferBaseGL30( vbomesh_t *pOut, int attrFlags ); + static void BindBufferBaseBumpGL21( vbomesh_t *pOut, int attrFlags ); + static void BindBufferBaseBumpGL30( vbomesh_t *pOut, int attrFlags ); + static void BindBufferVLightGL21( vbomesh_t *pOut, int attrFlags ); + static void BindBufferVLightGL30( vbomesh_t *pOut, int attrFlags ); + static void BindBufferVLightBumpGL21( vbomesh_t *pOut, int attrFlags ); + static void BindBufferVLightBumpGL30( vbomesh_t *pOut, int attrFlags ); + static void BindBufferWeightGL21( vbomesh_t *pOut, int attrFlags ); + static void BindBufferWeightGL30( vbomesh_t *pOut, int attrFlags ); + static void BindBufferWeightBumpGL21( vbomesh_t *pOut, int attrFlags ); + static void BindBufferWeightBumpGL30( vbomesh_t *pOut, int attrFlags ); + static void BindBufferLightMapGL21( vbomesh_t *pOut, int attrFlags ); + static void BindBufferLightMapGL30( vbomesh_t *pOut, int attrFlags ); + static void BindBufferLightMapBumpGL21( vbomesh_t *pOut, int attrFlags ); + static void BindBufferLightMapBumpGL30( vbomesh_t *pOut, int attrFlags ); + static void BindBufferGenericGL21( vbomesh_t *pOut, int attrFlags ); + static void BindBufferGenericGL30( vbomesh_t *pOut, int attrFlags ); + + static void CreateIndexBuffer( vbomesh_t *pOut, unsigned int *arrayelems ); + static void BindIndexBuffer( vbomesh_t *pOut ); + + static int SortSolidMeshes( const CSolidEntry *a, const CSolidEntry *b ); + + unsigned int ComputeAttribFlags( int numbones, bool has_bumpmap, bool has_boneweights, bool has_vertexlight, bool has_lightmap ); + unsigned int SelectMeshLoader( int numbones, bool has_bumpmap, bool has_boneweights, bool has_vertexlight, bool has_lightmap ); + + static mesh_loader_t m_pfnMeshLoaderGL21[MESHLOADER_COUNT]; + static mesh_loader_t m_pfnMeshLoaderGL30[MESHLOADER_COUNT]; + + bool m_fShootDecal; // disable all interpolations and bonecache + float m_flViewmodelFov; // custom fov for viewmodel + int m_iDrawModelType; // various modes of rendering + + // Cvars that studio model code needs to reference + cvar_t *m_pCvarHiModels; // Use high quality models? + cvar_t *m_pCvarDrawViewModel; + cvar_t *m_pCvarHand; // handness + cvar_t *m_pCvarViewmodelFov; + cvar_t *m_pCvarHeadShieldFov; + cvar_t *m_pCvarLegsOffset; + cvar_t *m_pCvarDrawLegs; + cvar_t *m_pCvarCompatible; + cvar_t *m_pCvarLodScale; + cvar_t *m_pCvarLodBias; + + CBaseBoneSetup m_boneSetup; + + // current mesh material + mstudiomaterial_t *m_pCurrentMaterial; + + ModelInstance_t *m_pModelInstance; + + // Pointer to header block for studio model data + studiohdr_t *m_pStudioHeader; + mstudiocache_t *m_pStudioCache; // only valid while mesh is build + + msubmodel_t *m_pVboModel; + mstudiomodel_t *m_pSubModel; + StudioMesh_t m_pTempMesh[MAXSTUDIOSKINS]; // temp structure + + // used by PushEntity\PopEntity + entity_state_t m_savestate; + Vector m_saveorigin; + Vector m_saveangles; + + Vector m_bonelightvecs[MAXSTUDIOBONES]; // debug used this + Vector m_arrayverts[MAXARRAYVERTS]; + svert_t m_arrayxvert[MAXARRAYVERTS]; + dmodeltbn_t *m_tbnverts; // variable sized + unsigned int m_arrayelems[MAXARRAYVERTS*3]; + unsigned int m_nNumArrayVerts; + unsigned int m_nNumArrayElems; + unsigned int m_nNumLightVerts; + unsigned int m_nNumLightFaces; + unsigned int m_nNumTBNVerts; + unsigned int m_nNumTempVerts; // used to conversion to fan sequence + + // decal building stuff + matrix3x4 m_pdecaltransform[MAXSTUDIOBONES]; // decal->world + matrix3x4 m_pworldtransform[MAXSTUDIOBONES]; // world->decal + + // firstperson legs stuff + model_t *m_pPlayerLegsModel; + int m_iTBNState; +public: + void DestroyAllModelInstances( void ); + + int StudioGetBounds( cl_entity_t *e, Vector bounds[2] ); + int StudioGetBounds( CSolidEntry *entry, Vector bounds[2] ); + bool StudioLoadTBN( void ); + bool StudioSaveTBN( void ); + + void PushEntityState( cl_entity_t *ent ); + void PopEntityState( cl_entity_t *ent ); + void EntityToModelState( modelstate_t *state, const cl_entity_t *ent ); + void ModelStateToEntity( cl_entity_t *ent, const modelstate_t *state ); + void StudioDecalShoot( const Vector &vecNorm, const Vector &vecEnd, const char *name, cl_entity_t *ent, int flags, modelstate_t *state ); + void StudioDecalShoot( struct pmtrace_s *tr, const char *name, bool visent = false ); + int StudioDecalList( decallist_t *pList, int count ); + void StudioClearDecals( void ); + void RemoveAllDecals( int entityIndex ); + + void UpdateLatchedVars( cl_entity_t *e, qboolean reset ); + + void ProcessUserData( model_t *mod, qboolean create, const byte *buffer ); + + void LoadLocalMatrix( int bone, mstudioboneinfo_t *boneinfo ); + + void AddStudioModelToDrawList( cl_entity_t *e, bool update = false ); + + void StudioGetAttachment( const cl_entity_t *ent, int iAttachment, Vector *pos, Vector *ang, Vector *dir ); + + // Process viewmodel events (at start the frame so muzzleflashes will be correct added) + void RunViewModelEvents( void ); + + bool ComputeCustomFov( matrix4x4 &projMatrix, matrix4x4 &worldViewProjMatrix ); + + void RestoreNormalFov( matrix4x4 &projMatrix, matrix4x4 &worldViewProjMatrix ); + + // Draw view model (at end the frame) + void DrawViewModel( void ); + + // draw head shield (after viewmodel) + void DrawHeadShield( void ); + + void RenderDeferredStudioList( void ); + + void RenderSolidStudioList( void ); + + void RenderShadowStudioList( void ); + + void RenderDebugStudioList( bool bViewModel ); + + void RenderDynLightList( bool solid ); + + void RenderTransMesh( CTransEntry *entry ); + + void BuildMeshListForLight( CDynLight *pl, bool solid ); + + void DrawLightForMeshList( CDynLight *pl ); + + int CacheCount( void ) { return m_ModelInstances.Count(); } + + void ClearLightCache( void ); +}; + +extern CStudioModelRenderer g_StudioRenderer; + +// implementation of drawing funcs +inline void R_RunViewmodelEvents( void ) { g_StudioRenderer.RunViewModelEvents(); } +inline void R_DrawViewModel( void ) { g_StudioRenderer.DrawViewModel(); } +inline void R_DrawHeadShield( void ) { g_StudioRenderer.DrawHeadShield(); } +inline void R_ProcessStudioData( model_t *mod, qboolean create, const byte *buffer ) +{ + if( mod->type == mod_studio ) + g_StudioRenderer.ProcessUserData( mod, create, buffer ); +} + +inline int R_CreateStudioDecalList( decallist_t *pList, int count ) +{ + return g_StudioRenderer.StudioDecalList( pList, count ); +} + +inline void R_ClearStudioDecals( void ) +{ + g_StudioRenderer.StudioClearDecals(); +} + +inline int R_StudioGetBounds( cl_entity_t *e, Vector bounds[2] ) +{ + return g_StudioRenderer.StudioGetBounds( e, bounds ); +} + +inline int R_StudioGetBounds( CSolidEntry *entry, Vector bounds[2] ) +{ + return g_StudioRenderer.StudioGetBounds( entry, bounds ); +} + +inline void R_RenderDeferredStudioList( void ) +{ + g_StudioRenderer.RenderDeferredStudioList(); +} + +inline void R_RenderSolidStudioList( void ) +{ + g_StudioRenderer.RenderSolidStudioList(); +} + +inline void R_RenderTransMesh( CTransEntry *entry ) +{ + g_StudioRenderer.RenderTransMesh( entry ); +} + +inline void R_RenderLightForTransMeshes( void ) +{ + g_StudioRenderer.RenderDynLightList( false ); +} + +inline void R_RenderShadowStudioList( void ) +{ + g_StudioRenderer.RenderShadowStudioList(); +} + +inline void R_RenderDebugStudioList( bool bViewModel ) +{ + g_StudioRenderer.RenderDebugStudioList( bViewModel ); +} + +inline void R_AddStudioToDrawList( cl_entity_t *e, bool update = false ) +{ + g_StudioRenderer.AddStudioModelToDrawList( e, update ); +} + +inline void R_StudioClearLightCache( void ) +{ + g_StudioRenderer.ClearLightCache(); +} + +inline void R_StudioAttachmentPosAng( const cl_entity_t *ent, int num, Vector *pos, Vector *ang ) +{ + g_StudioRenderer.StudioGetAttachment( ent, num, pos, ang, NULL ); +} + +inline void R_StudioAttachmentPosDir( const cl_entity_t *ent, int num, Vector *pos, Vector *dir ) +{ + g_StudioRenderer.StudioGetAttachment( ent, num, pos, NULL, dir ); +} + +inline Vector R_StudioAttachmentPos( const cl_entity_t *ent, int num ) +{ + Vector pos = g_vecZero; + + g_StudioRenderer.StudioGetAttachment( ent, num, &pos, NULL, NULL ); + + return pos; +} + +inline Vector R_StudioAttachmentAng( const cl_entity_t *ent, int num ) +{ + Vector ang = g_vecZero; + + g_StudioRenderer.StudioGetAttachment( ent, num, NULL, &ang, NULL ); + + return ang; +} + +inline void R_UpdateLatchedVars( cl_entity_t *e, qboolean reset ) +{ + g_StudioRenderer.UpdateLatchedVars( e, reset ); +} + +#endif// GL_STUDIO_H \ No newline at end of file diff --git a/cl_dll/render/gl_studio.old b/cl_dll/render/gl_studio.old new file mode 100644 index 0000000..c3fe9c9 --- /dev/null +++ b/cl_dll/render/gl_studio.old @@ -0,0 +1,5448 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// studio_model.cpp +// routines for setting up to draw 3DStudio models + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" // buz +#include "const.h" +#include "com_model.h" +#include "studio.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "dlight.h" +#include "triangleapi.h" +#include "entity_types.h" +#include "stringlib.h" +#include "pm_defs.h" +#include +#include +#include +#include +#include "mathlib.h" +#include "vertex_fmt.h" +#include "r_studioint.h" +#include +#include "gl_local.h" +#include "gl_studio.h" +#include "gl_sprite.h" +#include "event_api.h" +#include "gl_shader.h" +#include "gl_world.h" + +// Global engine <-> studio model rendering code interface +engine_studio_api_t IEngineStudio; + +// the renderer object, created on the stack. +CStudioModelRenderer g_StudioRenderer; + +//================================================================================================ +// HUD_GetStudioModelInterface +// Export this function for the engine to use the studio renderer class to render objects. +//================================================================================================ +extern "C" int DLLEXPORT HUD_GetStudioModelInterface( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio ) +{ + if( version != STUDIO_INTERFACE_VERSION ) + return 0; + + // Copy in engine helper functions + memcpy( &IEngineStudio, pstudio, sizeof( IEngineStudio )); + + if( g_fRenderInitialized ) + { + // Initialize local variables, etc. + g_StudioRenderer.Init(); + + g_SpriteRenderer.Init(); + } + + // Success + return 1; +} + +//================================================================================================ +// +// Implementation of bone setup class +// +//================================================================================================ +void CBaseBoneSetup :: debugMsg( char *szFmt, ... ) +{ + char buffer[2048]; // must support > 1k messages + va_list args; + + if( developer_level <= DEV_NONE ) + return; + + va_start( args, szFmt ); + Q_vsnprintf( buffer, 2048, szFmt, args ); + va_end( args ); + + gEngfuncs.Con_Printf( buffer ); +} + +mstudioanim_t *CBaseBoneSetup :: GetAnimSourceData( mstudioseqdesc_t *pseqdesc ) +{ + return g_StudioRenderer.StudioGetAnim( RI->currentmodel, pseqdesc ); +} + +void CBaseBoneSetup :: debugLine( const Vector& origin, const Vector& dest, int r, int g, int b, bool noDepthTest, float duration ) +{ + if( noDepthTest ) + pglDisable( GL_DEPTH_TEST ); + + pglColor3ub( r, g, b ); + + pglBegin( GL_LINES ); + pglVertex3fv( origin ); + pglVertex3fv( dest ); + pglEnd(); +} + +/* +================= +R_SortSolidMeshes + +sort by shaders +================= +*/ +static int R_SortSolidMeshes( const CSolidEntry *a, const CSolidEntry *b ) +{ + if( a->m_hProgram > b->m_hProgram ) + return -1; + if( a->m_hProgram < b->m_hProgram ) + return 1; + + return 0; +} + +/* +==================== +Init + +==================== +*/ +void CStudioModelRenderer :: Init( void ) +{ + // Set up some variables shared with engine + m_pCvarHiModels = IEngineStudio.GetCvar( "cl_himodels" ); + m_pCvarDrawViewModel = IEngineStudio.GetCvar( "r_drawviewmodel" ); + m_pCvarHand = CVAR_REGISTER( "cl_righthand", "0", FCVAR_ARCHIVE ); + m_pCvarViewmodelFov = CVAR_REGISTER( "cl_viewmodel_fov", "60", FCVAR_ARCHIVE ); + m_pCvarHeadShieldFov = CVAR_REGISTER( "cl_headshield_fov", "63", FCVAR_ARCHIVE ); + m_pCvarLegsOffset = CVAR_REGISTER( "legs_offset", "16", FCVAR_ARCHIVE ); + m_pCvarDrawLegs = CVAR_REGISTER( "r_drawlegs", "1", FCVAR_ARCHIVE ); + m_pCvarCompatible = CVAR_REGISTER( "r_studio_compatible", "1", FCVAR_ARCHIVE ); + m_pCvarLodScale = CVAR_REGISTER( "cl_lod_scale", "5.0", FCVAR_ARCHIVE ); + m_pCvarLodBias = CVAR_REGISTER( "cl_lod_bias", "0", FCVAR_ARCHIVE ); +} + +/* +==================== +Init + +==================== +*/ +void CStudioModelRenderer :: VidInit( void ) +{ + // tell the engine what models is used + m_pPlayerLegsModel = IEngineStudio.Mod_ForName( "models/player_legs.mdl", false ); +} + +/* +==================== +CStudioModelRenderer + +==================== +*/ +CStudioModelRenderer :: CStudioModelRenderer( void ) +{ + m_iDrawModelType = DRAWSTUDIO_NORMAL; + m_pCurrentMaterial = NULL; + m_pCvarHiModels = NULL; + m_pCvarDrawViewModel= NULL; + m_pCvarHand = NULL; + m_pStudioHeader = NULL; + m_pVboModel = NULL; + m_pSubModel = NULL; + m_pPlayerInfo = NULL; + m_pModelInstance = NULL; +} + +/* +==================== +~CStudioModelRenderer + +==================== +*/ +CStudioModelRenderer :: ~CStudioModelRenderer( void ) +{ +} + +/* +==================== +Prepare all the pointers for +working with current entity + +==================== +*/ +bool CStudioModelRenderer :: StudioSetEntity( cl_entity_t *pEnt ) +{ + if( !pEnt || !pEnt->model || pEnt->model->type != mod_studio ) + return false; + + RI->currententity = pEnt; + SET_CURRENT_ENTITY( RI->currententity ); + m_pPlayerInfo = NULL; + + if( m_iDrawModelType == DRAWSTUDIO_NORMAL && ( RI->currententity->player || RI->currententity->curstate.renderfx == kRenderFxDeadPlayer )) + { + int iPlayerIndex; + + if( RI->currententity->curstate.renderfx == kRenderFxDeadPlayer ) + iPlayerIndex = RI->currententity->curstate.renderamt - 1; + else iPlayerIndex = RI->currententity->curstate.number - 1; + + if( iPlayerIndex < 0 || iPlayerIndex >= GET_MAX_CLIENTS( )) + return false; + + if( RP_NORMALPASS() && RP_LOCALCLIENT( RI->currententity ) && !FBitSet( RI->params, RP_THIRDPERSON )) + { + if( !CVAR_TO_BOOL( m_pCvarDrawLegs ) || !m_pPlayerLegsModel ) + return false; + + // do simple cull for player legs + if( FBitSet( gHUD.m_iKeyBits, IN_DUCK ) && RI->view.angles[PITCH] <= 30.0f ) + return false; + else if( !FBitSet( gHUD.m_iKeyBits, IN_DUCK ) && RI->view.angles[PITCH] <= 50.0f ) + return false; + + RI->currentmodel = m_pPlayerLegsModel; + } + else + { + RI->currentmodel = IEngineStudio.SetupPlayerModel( iPlayerIndex ); + + // show highest resolution multiplayer model + if( CVAR_TO_BOOL( m_pCvarHiModels ) && RI->currentmodel != RI->currententity->model ) + RI->currententity->curstate.body = 255; + + if( !( !developer_level && GET_MAX_CLIENTS() == 1 ) && ( RI->currentmodel == RI->currententity->model )) + RI->currententity->curstate.body = 1; // force helmet + } + + // setup the playerinfo + if( RI->currententity->player ) + m_pPlayerInfo = IEngineStudio.PlayerInfo( iPlayerIndex ); + } + else + { + RI->currentmodel = RI->currententity->model; + } + + if( RI->currentmodel == NULL ) + return false; + + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( RI->currentmodel ); + + // downloading in-progress ? + if( m_pStudioHeader == NULL ) + return false; + + // tell the engine about model + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + IEngineStudio.SetRenderModel( RI->currentmodel ); + + if( !StudioSetupInstance( )) + { + ALERT( at_error, "Couldn't create instance for entity %d\n", pEnt->index ); + return false; // out of memory ? + } + + // all done + return true; +} + +//----------------------------------------------------------------------------- +// Fast version without data reconstruction or changing +//----------------------------------------------------------------------------- +bool CStudioModelRenderer :: StudioSetEntity( CSolidEntry *entry ) +{ + studiohdr_t *phdr; + + if( !entry || entry->m_bDrawType != DRAWTYPE_MESH ) + return false; + + if( !entry->m_pParentEntity || !entry->m_pRenderModel ) + return false; // bad entry? + + if( entry->m_pParentEntity->modelhandle == INVALID_HANDLE ) + return false; // not initialized? + + if(( phdr = (studiohdr_t *)IEngineStudio.Mod_Extradata( entry->m_pRenderModel )) == NULL ) + return false; // no model? + + RI->currentmodel = entry->m_pRenderModel; + RI->currententity = entry->m_pParentEntity; + m_pModelInstance = &m_ModelInstances[entry->m_pParentEntity->modelhandle]; + m_pStudioHeader = phdr; + m_pPlayerInfo = NULL; + + return true; +} + +bool CStudioModelRenderer :: StudioSetupInstance( void ) +{ + // first call ? + if( RI->currententity->modelhandle == INVALID_HANDLE ) + { + RI->currententity->modelhandle = m_ModelInstances.AddToTail(); + + if( RI->currententity->modelhandle == INVALID_HANDLE ) + return false; // out of memory ? + + m_pModelInstance = &m_ModelInstances[RI->currententity->modelhandle]; + ClearInstanceData( true ); + } + else + { + m_pModelInstance = &m_ModelInstances[RI->currententity->modelhandle]; + + // model has been changed or something like + if( !IsModelInstanceValid( m_pModelInstance )) + ClearInstanceData( false ); + } + + m_boneSetup.SetStudioPointers( m_pStudioHeader, m_pModelInstance->m_poseparameter ); + + return true; +} + +//----------------------------------------------------------------------------- +// It's not valid if the model index changed + we have non-zero instance data +//----------------------------------------------------------------------------- +bool CStudioModelRenderer :: IsModelInstanceValid( ModelInstance_t *inst ) +{ + const model_t *pModel; + + if( m_iDrawModelType == DRAWSTUDIO_NORMAL && ( inst->m_pEntity->player || RI->currententity->curstate.renderfx == kRenderFxDeadPlayer )) + { + if( FBitSet( RI->params, RP_SHADOWVIEW )) + return true; // never change playermodel until shadowview + + if( RP_NORMALPASS() && RP_LOCALCLIENT( RI->currententity ) && !FBitSet( RI->params, RP_THIRDPERSON )) + pModel = m_pPlayerLegsModel; + else if( RI->currententity->curstate.renderfx == kRenderFxDeadPlayer ) + pModel = IEngineStudio.SetupPlayerModel( inst->m_pEntity->curstate.renderamt - 1 ); + else pModel = IEngineStudio.SetupPlayerModel( inst->m_pEntity->curstate.number - 1 ); + } + else + { + pModel = inst->m_pEntity->model; + } + + return inst->m_pModel == pModel; +} + +void CStudioModelRenderer :: DestroyInstance( word handle ) +{ + if( !m_ModelInstances.IsValidIndex( handle )) + return; + + ModelInstance_t *inst = &m_ModelInstances[handle]; + + inst->m_BodyMesh.FreeMesh(); + PurgeDecals( inst ); + + if( inst->materials != NULL ) + Mem_Free( inst->materials ); + inst->materials = NULL; + + if( inst->m_pJiggleBones != NULL ) + delete inst->m_pJiggleBones; + inst->m_pJiggleBones = NULL; + + m_ModelInstances.Remove( handle ); +} + +void CStudioModelRenderer :: UpdateInstanceMaterials( void ) +{ + ASSERT( m_pStudioHeader != NULL ); + ASSERT( m_pModelInstance != NULL ); + + // model was changed, so we need to realloc materials + if( m_pModelInstance->materials != NULL ) + Mem_Free( m_pModelInstance->materials ); + + // create a local copy of all the model material for cache uber-shaders + m_pModelInstance->materials = (mstudiomaterial_t *)Mem_Alloc( sizeof( mstudiomaterial_t ) * m_pStudioHeader->numtextures ); + memcpy( m_pModelInstance->materials, RI->currentmodel->materials, sizeof( mstudiomaterial_t ) * m_pStudioHeader->numtextures ); + + // invalidate sequences when a new instance was created + for( int i = 0; i < m_pStudioHeader->numtextures; i++ ) + { + m_pModelInstance->materials[i].forwardScene.Invalidate(); + m_pModelInstance->materials[i].forwardLightSpot.Invalidate(); + m_pModelInstance->materials[i].forwardLightOmni.Invalidate(); + m_pModelInstance->materials[i].forwardLightProj.Invalidate(); + } +} + +void CStudioModelRenderer :: ClearInstanceData( bool create ) +{ + if( create ) + { + m_pModelInstance->m_DecalList.Purge(); + m_pModelInstance->m_pJiggleBones = NULL; + m_pModelInstance->materials = NULL; + } + else + { + if( m_pModelInstance->m_pJiggleBones != NULL ) + delete m_pModelInstance->m_pJiggleBones; + m_pModelInstance->m_pJiggleBones = NULL; + PurgeDecals( m_pModelInstance ); + } + + m_pModelInstance->m_pEntity = RI->currententity; + m_pModelInstance->m_pModel = RI->currentmodel; + m_pModelInstance->m_VlCache = NULL; + m_pModelInstance->m_DecalCount = 0; + m_pModelInstance->cached_frame = -1; + m_pModelInstance->visframe = -1; + m_pModelInstance->radius = 0.0f; + m_pModelInstance->info_flags = 0; + m_pModelInstance->lerpFactor = 0.0f; + m_pModelInstance->cubemap[0] = &world->defaultCubemap; + m_pModelInstance->cubemap[1] = &world->defaultCubemap; + + ClearBounds( m_pModelInstance->absmin, m_pModelInstance->absmax ); + memset( &m_pModelInstance->bonecache, 0, sizeof( BoneCache_t )); + memset( m_pModelInstance->m_protationmatrix, 0, sizeof( matrix3x4 )); + memset( m_pModelInstance->m_pbones, 0, sizeof( matrix3x4 ) * MAXSTUDIOBONES ); + memset( m_pModelInstance->m_pwpnbones, 0, sizeof( matrix3x4 ) * MAXSTUDIOBONES ); + memset( m_pModelInstance->attachment, 0, sizeof( StudioAttachment_t ) * MAXSTUDIOATTACHMENTS ); + memset( m_pModelInstance->m_glstudiobones, 0, sizeof( Vector4D ) * MAXSTUDIOBONES * 3 ); + memset( m_pModelInstance->m_glweaponbones, 0, sizeof( Vector4D ) * MAXSTUDIOBONES * 3 ); + memset( m_pModelInstance->m_studioquat, 0, sizeof( Vector4D ) * MAXSTUDIOBONES ); + memset( m_pModelInstance->m_studiopos, 0, sizeof( Vector ) * MAXSTUDIOBONES ); + memset( m_pModelInstance->m_weaponquat, 0, sizeof( Vector4D ) * MAXSTUDIOBONES ); + memset( m_pModelInstance->m_weaponpos, 0, sizeof( Vector ) * MAXSTUDIOBONES ); + memset( &m_pModelInstance->lighting, 0, sizeof( mstudiolight_t )); + memset( &m_pModelInstance->lerp, 0, sizeof( mstudiolerp_t )); + memset( &m_pModelInstance->light, 0, sizeof( lightinfo_t )); + memset( &m_pModelInstance->lights, 255, sizeof( byte[MAXDYNLIGHTS] )); + memset( &m_pModelInstance->m_controller, 0, sizeof( m_pModelInstance->m_controller )); + memset( &m_pModelInstance->m_seqblend, 0, sizeof( m_pModelInstance->m_seqblend )); + m_pModelInstance->lerp.stairoldz = RI->currententity->origin[2]; + m_pModelInstance->lerp.stairtime = tr.time; + m_pModelInstance->m_current_seqblend = 0; + m_pModelInstance->numLightPoints = 0; + + m_boneSetup.SetStudioPointers( m_pStudioHeader, m_pModelInstance->m_poseparameter ); + + // set poseparam sliders to their default values + m_boneSetup.CalcDefaultPoseParameters( m_pModelInstance->m_poseparameter ); + + // refresh the materials list + UpdateInstanceMaterials(); + + // copy attachments names + mstudioattachment_t *pattachment = (mstudioattachment_t *)((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex); + StudioAttachment_t *att = m_pModelInstance->attachment; + + // setup attachment names + for( int i = 0; i < Q_min( MAXSTUDIOATTACHMENTS, m_pStudioHeader->numattachments ); i++ ) + { + Q_strncpy( att[i].name, pattachment[i].name, sizeof( att[0].name )); + + att[i].local.Identity(); + att[i].local.SetOrigin( pattachment[i].org ); + + if( !Q_strnicmp( att[i].name, "LightProbe.", 11 )) + SetBits( m_pModelInstance->info_flags, MF_CUSTOM_LIGHTGRID ); + } + m_pModelInstance->numattachments = m_pStudioHeader->numattachments; + + for( int map = 0; map < MAXLIGHTMAPS; map++ ) + m_pModelInstance->styles[map] = 255; +} + +void CStudioModelRenderer :: ProcessUserData( model_t *mod, qboolean create, const byte *buffer ) +{ + // to get something valid here + RI->currententity = GET_ENTITY( 0 ); + RI->currentmodel = mod; + + if( !( m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( RI->currentmodel ))) + return; + + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + IEngineStudio.SetRenderModel( RI->currentmodel ); + + if( create ) + { + // compute model CRC to verify vertexlighting data + // NOTE: source buffer is not equal to Mod_Extradata! + studiohdr_t *src = (studiohdr_t *)buffer; + RI->currentmodel->modelCRC = FILE_CRC32( buffer, src->length ); + double start = Sys_DoubleTime(); + RI->currentmodel->studiocache = CreateMeshCache(); + double end = Sys_DoubleTime(); + r_buildstats.create_buffer_object += (end - start); + r_buildstats.total_buildtime += (end - start); + } + else + { + DestroyMeshCache(); + } +} + +bool CStudioModelRenderer :: CheckBoneCache( float f ) +{ + if( !m_pModelInstance ) + return false; + + ModelInstance_t *inst = m_pModelInstance; + BoneCache_t *cache = &inst->bonecache; + cl_entity_t *e = RI->currententity; + + bool pos_valid = (cache->transform == inst->m_protationmatrix) ? true : false; + + // make sure what all cached values are unchanged + if( cache->frame == f && cache->sequence == e->curstate.sequence && pos_valid && !memcmp( cache->blending, e->curstate.blending, 2 ) + && !memcmp( cache->controller, e->curstate.controller, 4 ) && cache->mouthopen == e->mouth.mouthopen ) + { + if( m_pPlayerInfo ) + { + if( cache->gaitsequence == m_pPlayerInfo->gaitsequence && cache->gaitframe == m_pPlayerInfo->gaitframe ) + return true; + } + else + { + // cache are valid + return true; + } + } + + // time to check cubemaps + if( !pos_valid ) + { + // search for center of bbox + Vector pos = ( inst->absmin + inst->absmax ) * 0.5f; + CL_FindTwoNearestCubeMap( pos, &inst->cubemap[0], &inst->cubemap[1] ); + + float dist0 = ( inst->cubemap[0]->origin - pos ).Length(); + float dist1 = ( inst->cubemap[1]->origin - pos ).Length(); + inst->lerpFactor = dist0 / (dist0 + dist1); + } + + // save rotationmatrix to GL-style array + inst->m_protationmatrix.CopyToArray( inst->m_glmatrix ); + + // update bonecache + cache->frame = f; + cache->mouthopen = e->mouth.mouthopen; + cache->sequence = e->curstate.sequence; + cache->transform = inst->m_protationmatrix; + memcpy( cache->blending, e->curstate.blending, 2 ); + memcpy( cache->controller, e->curstate.controller, 4 ); + + if( m_pPlayerInfo ) + { + cache->gaitsequence = m_pPlayerInfo->gaitsequence; + cache->gaitframe = m_pPlayerInfo->gaitframe; + } + + return false; +} + +void CStudioModelRenderer :: LoadLocalMatrix( int bone, mstudioboneinfo_t *boneinfo ) +{ + mposetobone_t *m = RI->currentmodel->poseToBone; + + // transform Valve matrix to Xash matrix + m->posetobone[bone][0][0] = boneinfo->poseToBone[0][0]; + m->posetobone[bone][0][1] = boneinfo->poseToBone[1][0]; + m->posetobone[bone][0][2] = boneinfo->poseToBone[2][0]; + + m->posetobone[bone][1][0] = boneinfo->poseToBone[0][1]; + m->posetobone[bone][1][1] = boneinfo->poseToBone[1][1]; + m->posetobone[bone][1][2] = boneinfo->poseToBone[2][1]; + + m->posetobone[bone][2][0] = boneinfo->poseToBone[0][2]; + m->posetobone[bone][2][1] = boneinfo->poseToBone[1][2]; + m->posetobone[bone][2][2] = boneinfo->poseToBone[2][2]; + + m->posetobone[bone][3][0] = boneinfo->poseToBone[0][3]; + m->posetobone[bone][3][1] = boneinfo->poseToBone[1][3]; + m->posetobone[bone][3][2] = boneinfo->poseToBone[2][3]; +} + +void CStudioModelRenderer :: ComputeSkinMatrix( mstudioboneweight_t *boneweights, const matrix3x4 worldtransform[], matrix3x4 &result ) +{ + float flWeight0, flWeight1, flWeight2, flWeight3; + int numbones = 0; + float flTotal; + + for( int i = 0; i < MAXSTUDIOBONEWEIGHTS; i++ ) + { + if( boneweights->bone[i] != -1 ) + numbones++; + } + + if( numbones == 4 ) + { + const matrix3x4 &boneMat0 = worldtransform[boneweights->bone[0]]; + const matrix3x4 &boneMat1 = worldtransform[boneweights->bone[1]]; + const matrix3x4 &boneMat2 = worldtransform[boneweights->bone[2]]; + const matrix3x4 &boneMat3 = worldtransform[boneweights->bone[3]]; + flWeight0 = boneweights->weight[0] / 255.0f; + flWeight1 = boneweights->weight[1] / 255.0f; + flWeight2 = boneweights->weight[2] / 255.0f; + flWeight3 = boneweights->weight[3] / 255.0f; + flTotal = flWeight0 + flWeight1 + flWeight2 + flWeight3; + + if( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal; // compensate rounding error + + result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2 + boneMat3[0][0] * flWeight3; + result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2 + boneMat3[0][1] * flWeight3; + result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2 + boneMat3[0][2] * flWeight3; + result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2 + boneMat3[1][0] * flWeight3; + result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2 + boneMat3[1][1] * flWeight3; + result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2 + boneMat3[1][2] * flWeight3; + result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2 + boneMat3[2][0] * flWeight3; + result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2 + boneMat3[2][1] * flWeight3; + result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2 + boneMat3[2][2] * flWeight3; + result[3][0] = boneMat0[3][0] * flWeight0 + boneMat1[3][0] * flWeight1 + boneMat2[3][0] * flWeight2 + boneMat3[3][0] * flWeight3; + result[3][1] = boneMat0[3][1] * flWeight0 + boneMat1[3][1] * flWeight1 + boneMat2[3][1] * flWeight2 + boneMat3[3][1] * flWeight3; + result[3][2] = boneMat0[3][2] * flWeight0 + boneMat1[3][2] * flWeight1 + boneMat2[3][2] * flWeight2 + boneMat3[3][2] * flWeight3; + } + else if( numbones == 3 ) + { + const matrix3x4 &boneMat0 = worldtransform[boneweights->bone[0]]; + const matrix3x4 &boneMat1 = worldtransform[boneweights->bone[1]]; + const matrix3x4 &boneMat2 = worldtransform[boneweights->bone[2]]; + flWeight0 = boneweights->weight[0] / 255.0f; + flWeight1 = boneweights->weight[1] / 255.0f; + flWeight2 = boneweights->weight[2] / 255.0f; + flTotal = flWeight0 + flWeight1 + flWeight2; + + if( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal; // compensate rounding error + + result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2; + result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2; + result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2; + result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2; + result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2; + result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2; + result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2; + result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2; + result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2; + result[3][0] = boneMat0[3][0] * flWeight0 + boneMat1[3][0] * flWeight1 + boneMat2[3][0] * flWeight2; + result[3][1] = boneMat0[3][1] * flWeight0 + boneMat1[3][1] * flWeight1 + boneMat2[3][1] * flWeight2; + result[3][2] = boneMat0[3][2] * flWeight0 + boneMat1[3][2] * flWeight1 + boneMat2[3][2] * flWeight2; + } + else if( numbones == 2 ) + { + const matrix3x4 &boneMat0 = worldtransform[boneweights->bone[0]]; + const matrix3x4 &boneMat1 = worldtransform[boneweights->bone[1]]; + flWeight0 = boneweights->weight[0] / 255.0f; + flWeight1 = boneweights->weight[1] / 255.0f; + flTotal = flWeight0 + flWeight1; + + if( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal; // compensate rounding error + + // NOTE: Inlining here seems to make a fair amount of difference + result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1; + result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1; + result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1; + result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1; + result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1; + result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1; + result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1; + result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1; + result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1; + result[3][0] = boneMat0[3][0] * flWeight0 + boneMat1[3][0] * flWeight1; + result[3][1] = boneMat0[3][1] * flWeight0 + boneMat1[3][1] * flWeight1; + result[3][2] = boneMat0[3][2] * flWeight0 + boneMat1[3][2] * flWeight1; + } + else + { + result = worldtransform[boneweights->bone[0]]; + } +} + +void CStudioModelRenderer :: ComputeSkinMatrix( svert_t *vertex, const matrix3x4 worldtransform[], matrix3x4 &result ) +{ + float flWeight0, flWeight1, flWeight2, flWeight3; + int numbones = 0; + float flTotal; + + for( int i = 0; i < MAXSTUDIOBONEWEIGHTS; i++ ) + { + if( vertex->boneid[i] != -1 ) + numbones++; + } + + if( numbones == 4 ) + { + const matrix3x4 &boneMat0 = worldtransform[vertex->boneid[0]]; + const matrix3x4 &boneMat1 = worldtransform[vertex->boneid[1]]; + const matrix3x4 &boneMat2 = worldtransform[vertex->boneid[2]]; + const matrix3x4 &boneMat3 = worldtransform[vertex->boneid[3]]; + flWeight0 = vertex->weight[0] / 255.0f; + flWeight1 = vertex->weight[1] / 255.0f; + flWeight2 = vertex->weight[2] / 255.0f; + flWeight3 = vertex->weight[3] / 255.0f; + flTotal = flWeight0 + flWeight1 + flWeight2 + flWeight3; + + if( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal; // compensate rounding error + + result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2 + boneMat3[0][0] * flWeight3; + result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2 + boneMat3[0][1] * flWeight3; + result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2 + boneMat3[0][2] * flWeight3; + result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2 + boneMat3[1][0] * flWeight3; + result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2 + boneMat3[1][1] * flWeight3; + result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2 + boneMat3[1][2] * flWeight3; + result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2 + boneMat3[2][0] * flWeight3; + result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2 + boneMat3[2][1] * flWeight3; + result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2 + boneMat3[2][2] * flWeight3; + result[3][0] = boneMat0[3][0] * flWeight0 + boneMat1[3][0] * flWeight1 + boneMat2[3][0] * flWeight2 + boneMat3[3][0] * flWeight3; + result[3][1] = boneMat0[3][1] * flWeight0 + boneMat1[3][1] * flWeight1 + boneMat2[3][1] * flWeight2 + boneMat3[3][1] * flWeight3; + result[3][2] = boneMat0[3][2] * flWeight0 + boneMat1[3][2] * flWeight1 + boneMat2[3][2] * flWeight2 + boneMat3[3][2] * flWeight3; + } + else if( numbones == 3 ) + { + const matrix3x4 &boneMat0 = worldtransform[vertex->boneid[0]]; + const matrix3x4 &boneMat1 = worldtransform[vertex->boneid[1]]; + const matrix3x4 &boneMat2 = worldtransform[vertex->boneid[2]]; + flWeight0 = vertex->weight[0] / 255.0f; + flWeight1 = vertex->weight[1] / 255.0f; + flWeight2 = vertex->weight[2] / 255.0f; + flTotal = flWeight0 + flWeight1 + flWeight2; + + if( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal; // compensate rounding error + + result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2; + result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2; + result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2; + result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2; + result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2; + result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2; + result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2; + result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2; + result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2; + result[3][0] = boneMat0[3][0] * flWeight0 + boneMat1[3][0] * flWeight1 + boneMat2[3][0] * flWeight2; + result[3][1] = boneMat0[3][1] * flWeight0 + boneMat1[3][1] * flWeight1 + boneMat2[3][1] * flWeight2; + result[3][2] = boneMat0[3][2] * flWeight0 + boneMat1[3][2] * flWeight1 + boneMat2[3][2] * flWeight2; + } + else if( numbones == 2 ) + { + const matrix3x4 &boneMat0 = worldtransform[vertex->boneid[0]]; + const matrix3x4 &boneMat1 = worldtransform[vertex->boneid[1]]; + flWeight0 = vertex->weight[0] / 255.0f; + flWeight1 = vertex->weight[1] / 255.0f; + flTotal = flWeight0 + flWeight1; + + if( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal; // compensate rounding error + + // NOTE: Inlining here seems to make a fair amount of difference + result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1; + result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1; + result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1; + result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1; + result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1; + result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1; + result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1; + result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1; + result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1; + result[3][0] = boneMat0[3][0] * flWeight0 + boneMat1[3][0] * flWeight1; + result[3][1] = boneMat0[3][1] * flWeight0 + boneMat1[3][1] * flWeight1; + result[3][2] = boneMat0[3][2] * flWeight0 + boneMat1[3][2] * flWeight1; + } + else + { + result = worldtransform[vertex->boneid[0]]; + } +} + +void CStudioModelRenderer :: MeshCreateBuffer( vbomesh_t *pOut, const mstudiomesh_t *pMesh, const mstudiomodel_t *pSubModel, const matrix3x4 bones[], dmodellight_t *dml ) +{ + // FIXME: if various skinfamilies has different sizes then our texcoords probably will be invalid for pev->skin != 0 + short *pskinref = (short *)((byte *)m_pStudioHeader + m_pStudioHeader->skinindex); // setup skinref for skin == 0 + mstudiotexture_t *ptexture = (mstudiotexture_t *)((byte *)m_pStudioHeader + m_pStudioHeader->textureindex); + mstudiomaterial_t *pmaterial = (mstudiomaterial_t *)RI->currentmodel->materials; + ptexture = &ptexture[pskinref[pMesh->skinref]]; + pmaterial = &pmaterial[pskinref[pMesh->skinref]]; + + mstudiobone_t *pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + short *ptricmds = (short *)((byte *)m_pStudioHeader + pMesh->triindex); + Vector *pstudioverts = (Vector *)((byte *)m_pStudioHeader + pSubModel->vertindex); + Vector *pstudionorms = (Vector *)((byte *)m_pStudioHeader + pSubModel->normindex); + byte *pvertbone = ((byte *)m_pStudioHeader + pSubModel->vertinfoindex); + byte *pnormbone = ((byte *)m_pStudioHeader + pSubModel->norminfoindex); + + // if weights was missed their offsets just equal to 0 + mstudioboneweight_t *pvertweight = (mstudioboneweight_t *)((byte *)m_pStudioHeader + pSubModel->blendvertinfoindex); + mstudioboneweight_t *pnormweight = (mstudioboneweight_t *)((byte *)m_pStudioHeader + pSubModel->blendnorminfoindex); + bool has_bumpmap = FBitSet( pmaterial->flags, STUDIO_NF_NORMALMAP ) ? true : false; + bool has_boneweights = ( RI->currentmodel->poseToBone != NULL ) ? true : false; + bool has_vertexlight = ( dml != NULL && dml->numverts > 0 ) ? true : false; + static Vector localverts[MAXARRAYVERTS]; + static svert_t arrayxvert[MAXARRAYVERTS]; + Vector mins, maxs; + matrix3x4 skinMat; + int i; + + float s = 1.0f / (float)ptexture->width; + float t = 1.0f / (float)ptexture->height; + + pOut->skinref = pMesh->skinref; + pOut->parentbone = 0xFF; + + // init temporare arrays + m_nNumArrayVerts = m_nNumArrayElems = 0; + + ClearBounds( mins, maxs ); + + if( RI->currentmodel->poseToBone ) + { + // compute weighted vertexes + for( i = 0; i < pSubModel->numverts; i++ ) + { + ComputeSkinMatrix( &pvertweight[i], bones, skinMat ); + localverts[i] = skinMat.VectorTransform( pstudioverts[i] ); + } + } + else + { + // compute unweighted vertexes + for( i = 0; i < pSubModel->numverts; i++ ) + localverts[i] = bones[pvertbone[i]].VectorTransform( pstudioverts[i] ); + } + + // first create trifan array from studiomodel mesh + while( i = *( ptricmds++ )) + { + bool strip = ( i < 0 ) ? false : true; + int vertexState = 0; + + if( i < 0 ) i = -i; + + for( ; i > 0; i--, ptricmds += 4 ) + { + if( vertexState++ < 3 ) + { + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts; + } + else if( strip ) + { + // flip triangles between clockwise and counter clockwise + if( vertexState & 1 ) + { + // draw triangle [n-2 n-1 n] + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - 2; + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - 1; + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts; + } + else + { + // draw triangle [n-1 n-2 n] + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - 1; + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - 2; + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts; + } + } + else + { + // draw triangle fan [0 n-1 n] + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - ( vertexState - 1 ); + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - 1; + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts; + } + + // don't concat by matrix here - it's should be done on GPU + arrayxvert[m_nNumArrayVerts].vertex = pstudioverts[ptricmds[0]]; + arrayxvert[m_nNumArrayVerts].normal = pstudionorms[ptricmds[1]]; + + // transformed vertices to build TBN + m_arrayverts[m_nNumArrayVerts] = localverts[ptricmds[0]]; + + if( dml != NULL && dml->numverts > 0 ) + { + dvertlight_t *vl = &dml->verts[m_nNumLightVerts++]; + + // now setup light and deluxe vector + for( int map = 0; map < MAXLIGHTMAPS; map++ ) + { + byte r = vl->light[map][0], g = vl->light[map][1], b = vl->light[map][2]; + float packDirect = (float)((double)((r << 16) | (g << 8) | b) / (double)(1 << 24)); + arrayxvert[m_nNumArrayVerts].light[map] = packDirect; + + byte x = vl->deluxe[map][0], y = vl->deluxe[map][1], z = vl->deluxe[map][2]; + float packDeluxe = (float)((double)((x << 16) | (y << 8) | z) / (double)(1 << 24)); + arrayxvert[m_nNumArrayVerts].deluxe[map] = packDeluxe; + } + } + + AddPointToBounds( pstudioverts[ptricmds[0]], mins, maxs ); + + if( FBitSet( ptexture->flags, STUDIO_NF_CHROME )) + { + // probably always equal 64 (see studiomdl.c for details) + arrayxvert[m_nNumArrayVerts].stcoord[0] = s; + arrayxvert[m_nNumArrayVerts].stcoord[1] = t; + } + else if( FBitSet( ptexture->flags, STUDIO_NF_UV_COORDS )) + { + arrayxvert[m_nNumArrayVerts].stcoord[0] = HalfToFloat( ptricmds[2] ); + arrayxvert[m_nNumArrayVerts].stcoord[1] = HalfToFloat( ptricmds[3] ); + } + else + { + arrayxvert[m_nNumArrayVerts].stcoord[0] = ptricmds[2] * s; + arrayxvert[m_nNumArrayVerts].stcoord[1] = ptricmds[3] * t; + } + + int boneid = pvertbone[ptricmds[0]]; + + if( pOut->parentbone == 0xFF ) + pOut->parentbone = boneid; + + // update bone if it was parent of current bone + if( pOut->parentbone != boneid ) + { + for( int k = pOut->parentbone; k != -1; k = pbones[k].parent ) + { + if( k == boneid ) + { + pOut->parentbone = boneid; + break; + } + } + } + + if( RI->currentmodel->poseToBone != NULL ) + { + mstudioboneweight_t *pCurWeight = &pvertweight[ptricmds[0]]; + + arrayxvert[m_nNumArrayVerts].boneid[0] = pCurWeight->bone[0]; + arrayxvert[m_nNumArrayVerts].boneid[1] = pCurWeight->bone[1]; + arrayxvert[m_nNumArrayVerts].boneid[2] = pCurWeight->bone[2]; + arrayxvert[m_nNumArrayVerts].boneid[3] = pCurWeight->bone[3]; + arrayxvert[m_nNumArrayVerts].weight[0] = pCurWeight->weight[0]; + arrayxvert[m_nNumArrayVerts].weight[1] = pCurWeight->weight[1]; + arrayxvert[m_nNumArrayVerts].weight[2] = pCurWeight->weight[2]; + arrayxvert[m_nNumArrayVerts].weight[3] = pCurWeight->weight[3]; + } + else + { + arrayxvert[m_nNumArrayVerts].boneid[0] = pvertbone[ptricmds[0]]; + arrayxvert[m_nNumArrayVerts].boneid[1] = -1; + arrayxvert[m_nNumArrayVerts].boneid[2] = -1; + arrayxvert[m_nNumArrayVerts].boneid[3] = -1; + arrayxvert[m_nNumArrayVerts].weight[0] = 255; + arrayxvert[m_nNumArrayVerts].weight[1] = 0; + arrayxvert[m_nNumArrayVerts].weight[2] = 0; + arrayxvert[m_nNumArrayVerts].weight[3] = 0; + } + + m_nNumArrayVerts++; + } + } + +// SetBits( ptexture->flags, STUDIO_NF_SMOOTH ); +#if 0 + // TESTTEST: this is only used by fully dynamic deferred lighting + if( has_vertexlight && FBitSet( ptexture->flags, STUDIO_NF_SMOOTH )) + { + Vector *normals = (Vector *)Mem_Alloc( m_nNumArrayVerts * sizeof( Vector )); + + // smooth the normals + for( i = 0; i < m_nNumArrayVerts; i++ ) + { + svert_t *sv0 = &arrayxvert[i]; + + for( int j = 0; j < m_nNumArrayVerts; j++ ) + { + svert_t *sv1 = &arrayxvert[j]; + + if( !VectorCompareEpsilon( sv0->vertex, sv1->vertex, ON_EPSILON )) + continue; + + if( DotProduct( sv0->normal, sv1->normal ) >= tr.smoothing_threshold ) + normals[i] += sv1->normal; + } + } + + // copy smoothed normals back + for( i = 0; i < m_nNumArrayVerts; i++ ) + arrayxvert[i].normal = normals[i]; + Mem_Free( normals ); + } +#endif + pOut->mins = mins; + pOut->maxs = maxs; + + // compute tangent space + if( FBitSet( pmaterial->flags, STUDIO_NF_NORMALMAP )) + { + // build a map from vertex to a list of triangles that share the vert. + CUtlArray vertToTriMap; + + vertToTriMap.AddMultipleToTail( m_nNumArrayVerts ); + int triID; + + for( triID = 0; triID < (m_nNumArrayElems / 3); triID++ ) + { + vertToTriMap[m_arrayelems[triID*3+0]].AddToTail( triID ); + vertToTriMap[m_arrayelems[triID*3+1]].AddToTail( triID ); + vertToTriMap[m_arrayelems[triID*3+2]].AddToTail( triID ); + } + + // calculate the tangent space for each triangle. + CUtlArray triSVect, triTVect; + triSVect.AddMultipleToTail( (m_nNumArrayElems / 3) ); + triTVect.AddMultipleToTail( (m_nNumArrayElems / 3) ); + + float *v[3], *tc[3]; + + for( triID = 0; triID < (m_nNumArrayElems / 3); triID++ ) + { + for( int i = 0; i < 3; i++ ) + { + v[i] = (float *)&m_arrayverts[m_arrayelems[triID*3+i]]; // transformed to global pose to avoid seams + tc[i] = (float *)&arrayxvert[m_arrayelems[triID*3+i]].stcoord; + } + + CalcTBN( v[0], v[1], v[2], tc[0], tc[1], tc[2], triSVect[triID], triTVect[triID] ); + } + + // calculate an average tangent space for each vertex. + for( int vertID = 0; vertID < m_nNumArrayVerts; vertID++ ) + { + svert_t *v = &arrayxvert[vertID]; + const Vector &normal = v->normal; + Vector &finalSVect = v->tangent; + Vector &finalTVect = v->binormal; + Vector sVect, tVect; + + sVect = tVect = g_vecZero; + + for( triID = 0; triID < vertToTriMap[vertID].Size(); triID++ ) + { + sVect += triSVect[vertToTriMap[vertID][triID]]; + tVect += triTVect[vertToTriMap[vertID][triID]]; + } + + if( FBitSet( ptexture->flags, STUDIO_NF_SMOOTH )) + { + // smooth TBN + Vector vertPos1 = m_arrayverts[vertID]; // transformed to global pose to avoid seams + + for( int vertID2 = 0; vertID2 < m_nNumArrayVerts; vertID2++ ) + { + if( vertID2 == vertID ) + continue; + + Vector vertPos2 = m_arrayverts[vertID2]; // transformed to global pose to avoid seams + + if( vertPos1 == vertPos2 ) + { + for( int triID2 = 0; triID2 < vertToTriMap[vertID2].Size(); triID2++ ) + { + sVect += triSVect[vertToTriMap[vertID2][triID2]]; + tVect += triTVect[vertToTriMap[vertID2][triID2]]; + } + } + } + } + + // rotate tangent and binormal back to bone space + ComputeSkinMatrix( &arrayxvert[vertID], bones, skinMat ); + + sVect = skinMat.VectorIRotate( sVect ); + tVect = skinMat.VectorIRotate( tVect ); + + if( !FBitSet( ptexture->flags, STUDIO_NF_SMOOTH )) + { + Vector tmpVect = CrossProduct( sVect, tVect ); + bool leftHanded = DotProduct( tmpVect, normal ) < 0.0f; + + if( !leftHanded ) + { + tVect = CrossProduct( normal, sVect ); + sVect = CrossProduct( tVect, normal ); + } + else + { + tVect = CrossProduct( sVect, normal ); + sVect = CrossProduct( normal, tVect ); + } + } + + finalSVect = sVect.Normalize(); + finalTVect = tVect.Normalize(); + } + } + + pOut->numVerts = m_nNumArrayVerts; + pOut->numElems = m_nNumArrayElems; + + GL_CheckVertexArrayBinding(); + + // determine optimal mesh loader + uint attribs = ComputeAttribFlags( m_pStudioHeader->numbones, has_bumpmap, has_boneweights, has_vertexlight ); + uint type = SelectMeshLoader( m_pStudioHeader->numbones, has_bumpmap, has_boneweights, has_vertexlight ); + +// Msg( "loading %s->%s as %s\n", RI->currentmodel->name, pSubModel->name, m_pfnMeshLoader[type].BufferName ); + + // move data to video memory + if( glConfig.version < ACTUAL_GL_VERSION ) + m_pfnMeshLoaderGL21[type].CreateBuffer( pOut, arrayxvert ); + else m_pfnMeshLoaderGL30[type].CreateBuffer( pOut, arrayxvert ); + CreateIndexBuffer( pOut, m_arrayelems ); + + // link it with vertex array object + pglGenVertexArrays( 1, &pOut->vao ); + pglBindVertexArray( pOut->vao ); + if( glConfig.version < ACTUAL_GL_VERSION ) + m_pfnMeshLoaderGL21[type].BindBuffer( pOut, attribs ); + else m_pfnMeshLoaderGL30[type].BindBuffer( pOut, attribs ); + BindIndexBuffer( pOut ); + pglBindVertexArray( GL_FALSE ); + + // update stats + tr.total_vbo_memory += pOut->cacheSize; +} + +mvbocache_t *CStudioModelRenderer :: CreateMeshCache( dmodellight_t *dml ) +{ + bool unique_model = (dml == NULL); // just for more readable code + float poseparams[MAXSTUDIOPOSEPARAM]; + TmpModel_t submodel[MAXSTUDIOMODELS]; // list of unique models + static matrix3x4 bones[MAXSTUDIOBONES]; + static Vector pos[MAXSTUDIOBONES]; + static Vector4D q[MAXSTUDIOBONES]; + int i, j, k, bufSize = 0; + int num_submodels = 0; + byte *buffer, *bufend; // simple bounds check + mvbocache_t *studiocache; + mstudiobodyparts_t *pbodypart; + mstudiomodel_t *psubmodel; + msubmodel_t *pModel; + mstudiobone_t *pbones; + + // materials goes first to determine bump + if( unique_model ) LoadStudioMaterials (); + else PrecacheStudioShaders (); + + // build default pose to build seamless TBN-space + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + m_boneSetup.SetStudioPointers( m_pStudioHeader, poseparams ); + + // setup local bone matrices + if( unique_model && FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEINFO )) + { + // NOTE: extended boneinfo goes immediately after bones + mstudioboneinfo_t *boneinfo = (mstudioboneinfo_t *)((byte *)pbones + m_pStudioHeader->numbones * sizeof( mstudiobone_t )); + + // alloc storage for bone array + RI->currentmodel->poseToBone = (mposetobone_t *)Mem_Alloc( sizeof( mposetobone_t )); + + for( j = 0; j < m_pStudioHeader->numbones; j++ ) + LoadLocalMatrix( j, &boneinfo[j] ); + } + + // compute default pose with no anim + m_boneSetup.InitPose( pos, q ); + + for( i = 0; i < m_pStudioHeader->numbones; i++ ) + { + if( pbones[i].parent == -1 ) bones[i] = matrix3x4( pos[i], q[i] ); + else bones[i] = bones[pbones[i].parent].ConcatTransforms( matrix3x4( pos[i], q[i] )); + } + + if( RI->currentmodel->poseToBone != NULL ) + { + // convert bones into worldtransform + for( i = 0; i < m_pStudioHeader->numbones; i++ ) + bones[i] = bones[i].ConcatTransforms( RI->currentmodel->poseToBone->posetobone[i] ); + } + + memset( submodel, 0, sizeof( submodel )); + word meshUniqueID = 0; + num_submodels = 0; + + // build list of unique submodels (by name) + for( i = 0; i < m_pStudioHeader->numbodyparts; i++ ) + { + pbodypart = (mstudiobodyparts_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bodypartindex) + i; + + for( j = 0; j < pbodypart->nummodels; j++ ) + { + psubmodel = (mstudiomodel_t *)((byte *)m_pStudioHeader + pbodypart->modelindex) + j; + if( !psubmodel->nummesh ) continue; // blank submodel, ignore it + + for( k = 0; k < num_submodels; k++ ) + { + if( !Q_stricmp( submodel[k].name, psubmodel->name )) + break; + } + + // add new one + if( k == num_submodels ) + { + Q_strncpy( submodel[k].name, psubmodel->name, sizeof( submodel[k].name )); + submodel[k].pmodel = psubmodel; + num_submodels++; + } + } + } + + // compute cache size (include individual meshes) + bufSize = sizeof( mvbocache_t ) + sizeof( mbodypart_t ) * m_pStudioHeader->numbodyparts; + + for( i = 0; i < num_submodels; i++ ) + bufSize += sizeof( msubmodel_t ) + sizeof( vbomesh_t ) * submodel[i].pmodel->nummesh; + + buffer = (byte *)Mem_Alloc( bufSize ); + bufend = buffer + bufSize; + + // setup pointers + studiocache = (mvbocache_t *)buffer; + buffer += sizeof( mvbocache_t ); + studiocache->bodyparts = (mbodypart_t *)buffer; + buffer += sizeof( mbodypart_t ) * m_pStudioHeader->numbodyparts; + studiocache->numbodyparts = m_pStudioHeader->numbodyparts; + + // begin to building submodels + for( i = 0; i < num_submodels; i++ ) + { + psubmodel = submodel[i].pmodel; + pModel = (msubmodel_t *)buffer; + buffer += sizeof( msubmodel_t ); + pModel->nummesh = psubmodel->nummesh; + + // setup meshes + pModel->meshes = (vbomesh_t *)buffer; + buffer += sizeof( vbomesh_t ) * psubmodel->nummesh; + + // sanity check + if( dml != NULL && dml->numverts > 0 ) + { + // search for submodel offset + int offset = (byte *)psubmodel - (byte *)m_pStudioHeader; + + for( j = 0; j < MAXSTUDIOMODELS; j++ ) + { + if( dml->submodels[j].submodel_offset == offset ) + break; + } + + ASSERT( j != MAXSTUDIOMODELS ); + ASSERT( m_nNumLightVerts == dml->submodels[j].vertex_offset ); + } + + for( j = 0; j < psubmodel->nummesh; j++ ) + { + mstudiomesh_t *pSrc = (mstudiomesh_t *)((byte *)m_pStudioHeader + psubmodel->meshindex) + j; + vbomesh_t *pDst = &pModel->meshes[j]; + + MeshCreateBuffer( pDst, pSrc, psubmodel, bones, dml ); + pDst->uniqueID = meshUniqueID++; + } + submodel[i].pout = pModel; // store unique submodel + } + + // and finally setup bodyparts + for( i = 0; i < m_pStudioHeader->numbodyparts; i++ ) + { + pbodypart = (mstudiobodyparts_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bodypartindex) + i; + mbodypart_t *pBodyPart = &studiocache->bodyparts[i]; + + pBodyPart->base = pbodypart->base; + pBodyPart->nummodels = pbodypart->nummodels; + + // setup pointers to unique models + for( j = 0; j < pBodyPart->nummodels; j++ ) + { + psubmodel = (mstudiomodel_t *)((byte *)m_pStudioHeader + pbodypart->modelindex) + j; + if( !psubmodel->nummesh ) continue; // blank submodel, leave null pointer + + // find supposed model + for( k = 0; k < num_submodels; k++ ) + { + if( !Q_stricmp( submodel[k].name, psubmodel->name )) + { + pBodyPart->models[j] = submodel[k].pout; + break; + } + } + + if( k == num_submodels ) + ALERT( at_error, "Couldn't find submodel %s for bodypart %i\n", psubmodel->name, i ); + } + } + + // bounds checking + if( buffer != bufend ) + { + if( buffer > bufend ) + ALERT( at_error, "CreateMeshCache: memory buffer overrun\n" ); + else ALERT( at_error, "CreateMeshCache: memory buffer underrun\n" ); + } + + return studiocache; +} + +//----------------------------------------------------------------------------- +// all the caches should be build before starting the new map +//----------------------------------------------------------------------------- +void CStudioModelRenderer :: CreateMeshCacheVL( const char *modelname, int cacheID ) +{ + dvlightlump_t *vl = world->vertex_lighting; + + // first we need throw previous mesh + ReleaseVBOCache( &tr.vertex_light_cache[cacheID] ); + + if( world->vertex_lighting == NULL ) + return; // for some reasons we missed this lump + + RI->currentmodel = IEngineStudio.Mod_ForName( modelname, false ); + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( RI->currentmodel ); + + if( !RI->currentmodel || !m_pStudioHeader ) + return; // download in progress? + + // first initialization + if( cacheID < vl->nummodels && vl->dataofs[cacheID] != -1 ) + { + dmodellight_t *dml = (dmodellight_t *)((byte *)vl + vl->dataofs[cacheID]); + + m_nNumLightVerts = 0; + + if( RI->currentmodel->modelCRC == dml->modelCRC ) + { + // now create mesh per entity with instanced vertex lighting + tr.vertex_light_cache[cacheID] = CreateMeshCache( dml ); + } + + if( dml->numverts == m_nNumLightVerts ) + ALERT( at_aiconsole, "%s vertexlit instance created, model verts %i, total verts %i\n", modelname, dml->numverts, m_nNumLightVerts ); + else if( RI->currentmodel->modelCRC != dml->modelCRC ) + ALERT( at_error, "%s failed to create vertex lighting: model CRC %p != %p\n", modelname, RI->currentmodel->modelCRC, dml->modelCRC ); + else ALERT( at_error, "%s failed to create vertex lighting: model verts %i != total verts %i\n", modelname, dml->numverts, m_nNumLightVerts ); + m_nNumLightVerts = 0; + } +} + +//----------------------------------------------------------------------------- +// all the caches should be build before starting the new map +//----------------------------------------------------------------------------- +void CStudioModelRenderer :: FreeMeshCacheVL( void ) +{ + for( int i = 0; i < MAX_LIGHTCACHE; i++ ) + ReleaseVBOCache( &tr.vertex_light_cache[i] ); +} + +void CStudioModelRenderer :: CreateMeshCacheVL( dmodellight_t *dml, int cacheID ) +{ + if( RI->currentmodel->modelCRC == dml->modelCRC ) + { + // get lighting cache + m_pModelInstance->m_VlCache = tr.vertex_light_cache[cacheID]; + } + + if( m_pModelInstance->m_VlCache != NULL ) + { + SetBits( m_pModelInstance->info_flags, MF_VERTEX_LIGHTING ); + + for( int map = 0; map < MAXLIGHTMAPS; map++ ) + m_pModelInstance->styles[map] = dml->styles[map]; + + for( int i = 0; i < m_pStudioHeader->numtextures; i++ ) + { + mstudiomaterial_t *mat = &m_pModelInstance->materials[i]; + mat->forwardScene.Invalidate(); // refresh shaders + } + } + else + { + ALERT( at_warning, "failed to create vertex lighting for %s\n", RI->currentmodel->name ); + SetBits( m_pModelInstance->info_flags, MF_VL_BAD_CACHE ); + } +} + +void CStudioModelRenderer :: DeleteVBOMesh( vbomesh_t *pMesh ) +{ + // purge all GPU data + if( pMesh->vao ) pglDeleteVertexArrays( 1, &pMesh->vao ); + if( pMesh->vbo ) pglDeleteBuffersARB( 1, &pMesh->vbo ); + if( pMesh->ibo ) pglDeleteBuffersARB( 1, &pMesh->ibo ); + tr.total_vbo_memory -= pMesh->cacheSize; + pMesh->cacheSize = 0; +} + +void CStudioModelRenderer :: ReleaseVBOCache( mvbocache_t **ppvbocache ) +{ + ASSERT( ppvbocache != NULL ); + + mvbocache_t *pvbocache = *ppvbocache; + if( !pvbocache ) return; + + for( int i = 0; i < pvbocache->numbodyparts; i++ ) + { + mbodypart_t *pBodyPart = &pvbocache->bodyparts[i]; + + for( int j = 0; j < pBodyPart->nummodels; j++ ) + { + msubmodel_t *pSubModel = pBodyPart->models[j]; + + if( !pSubModel || pSubModel->nummesh <= 0 ) + continue; // blank submodel + + for( int k = 0; k < pSubModel->nummesh; k++ ) + { + vbomesh_t *pMesh = &pSubModel->meshes[k]; + + DeleteVBOMesh( pMesh ); + } + } + } + + if( pvbocache != NULL ) + Mem_Free( pvbocache ); + *ppvbocache = NULL; +} + +void CStudioModelRenderer :: DestroyMeshCache( void ) +{ + FreeStudioMaterials (); + + ReleaseVBOCache( &RI->currentmodel->studiocache ); + + if( RI->currentmodel->poseToBone != NULL ) + Mem_Free( RI->currentmodel->poseToBone ); + RI->currentmodel->poseToBone = NULL; +} + +void CStudioModelRenderer :: PrecacheStudioShaders( void ) +{ + bool bone_weights = FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS ) ? true : false; + mstudiomaterial_t *pmaterial = (mstudiomaterial_t *)RI->currentmodel->materials; + + // this function is called when loading vertexlight cache, so we guessed what vertex light is active + for( int i = 0; i < m_pStudioHeader->numtextures; i++, pmaterial++ ) + { + ShaderSceneForward( pmaterial, &tr.vertexLightInfo, true, bone_weights, m_pStudioHeader->numbones ); + } +} + +void CStudioModelRenderer :: LoadStudioMaterials( void ) +{ + // first we need alloc copy of all the materials to prevent modify mstudiotexture_t + RI->currentmodel->materials = (mstudiomaterial_t *)Mem_Alloc( sizeof( mstudiomaterial_t ) * m_pStudioHeader->numtextures ); + + bool bone_weights = FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS ) ? true : false; + mstudiotexture_t *ptexture = (mstudiotexture_t *)((byte *)m_pStudioHeader + m_pStudioHeader->textureindex); + char diffuse[128], bumpmap[128], glossmap[128], glowmap[128], heightmap[128]; + mstudiomaterial_t *pmaterial = (mstudiomaterial_t *)RI->currentmodel->materials; + char texname[128], matname[64], mdlname[64]; + + COM_FileBase( RI->currentmodel->name, mdlname ); + + // loading studio materials from studio textures + for( int i = 0; i < m_pStudioHeader->numtextures; i++, ptexture++, pmaterial++ ) + { + COM_FileBase( ptexture->name, texname ); + + // build material names + Q_snprintf( matname, sizeof( matname ), "%s/%s", mdlname, texname ); // material description + Q_snprintf( diffuse, sizeof( diffuse ), "textures/%s/%s", mdlname, texname ); + Q_snprintf( bumpmap, sizeof( bumpmap ), "textures/%s/%s_norm", mdlname, texname ); + Q_snprintf( glossmap, sizeof( glossmap ), "textures/%s/%s_gloss", mdlname, texname ); + Q_snprintf( glowmap, sizeof( glowmap ), "textures/%s/%s_luma", mdlname, texname ); + Q_snprintf( heightmap, sizeof( heightmap ), "textures/%s/%s_hmap", mdlname, texname ); + + pmaterial->pSource = ptexture; + pmaterial->flags = ptexture->flags; + + if( IMAGE_EXISTS( diffuse )) + { + pmaterial->gl_diffuse_id = LOAD_TEXTURE( diffuse, NULL, 0, 0 ); + + // semi-transparent textures must have additive flag to invoke renderer insert supposed mesh into translist + if( FBitSet( pmaterial->flags, STUDIO_NF_ADDITIVE )) + { + if( RENDER_GET_PARM( PARM_TEX_FLAGS, pmaterial->gl_diffuse_id ) & TF_HAS_ALPHA ) + SetBits( pmaterial->flags, STUDIO_NF_HAS_ALPHA ); + } + + if( FBitSet( pmaterial->flags, STUDIO_NF_MASKED )) + SetBits( pmaterial->flags, STUDIO_NF_HAS_ALPHA ); + } + + if( pmaterial->gl_diffuse_id != 0 ) + { + // so engine can be draw HQ image for gl_renderer 0 + if( ptexture->index != tr.defaultTexture ) + FREE_TEXTURE( ptexture->index ); + ptexture->index = pmaterial->gl_diffuse_id; + } + else + { + // reuse original texture + pmaterial->gl_diffuse_id = ptexture->index; + } + + if( IMAGE_EXISTS( bumpmap )) + { + pmaterial->gl_normalmap_id = LOAD_TEXTURE( bumpmap, NULL, 0, TF_NORMALMAP ); + if( pmaterial->gl_normalmap_id > 0 ) + SetBits( pmaterial->flags, STUDIO_NF_NORMALMAP ); + } + else + { + // try alternate suffix + Q_snprintf( bumpmap, sizeof( bumpmap ), "textures/%s/%s_local", mdlname, texname ); + if( IMAGE_EXISTS( bumpmap )) + pmaterial->gl_normalmap_id = LOAD_TEXTURE( bumpmap, NULL, 0, TF_NORMALMAP ); + else pmaterial->gl_normalmap_id = tr.normalmapTexture; // blank bumpy + } + + if( IMAGE_EXISTS( glossmap )) + { + pmaterial->gl_specular_id = LOAD_TEXTURE( glossmap, NULL, 0, 0 ); + } + else + { + // try alternate suffix + Q_snprintf( glossmap, sizeof( glossmap ), "textures/%s/%s_gloss", mdlname, texname ); + if( IMAGE_EXISTS( glossmap )) + pmaterial->gl_specular_id = LOAD_TEXTURE( glossmap, NULL, 0, 0 ); + else pmaterial->gl_specular_id = tr.blackTexture; + } + + if( IMAGE_EXISTS( glowmap )) + pmaterial->gl_glowmap_id = LOAD_TEXTURE( glowmap, NULL, 0, 0 ); + else pmaterial->gl_glowmap_id = tr.blackTexture; + + if( IMAGE_EXISTS( heightmap )) + { + pmaterial->gl_heightmap_id = LOAD_TEXTURE( heightmap, NULL, 0, 0 ); + } + else + { + // try alternate suffix + Q_snprintf( heightmap, sizeof( heightmap ), "textures/%s/%s_bump", mdlname, texname ); + if( IMAGE_EXISTS( heightmap )) + pmaterial->gl_heightmap_id = LOAD_TEXTURE( heightmap, NULL, 0, 0 ); + else pmaterial->gl_heightmap_id = tr.blackTexture; + } + + // current model has bumpmapping effect + if( pmaterial->gl_normalmap_id > 0 && pmaterial->gl_normalmap_id != tr.normalmapTexture ) + SetBits( m_pStudioHeader->flags, STUDIO_HAS_BUMP ); + + if( pmaterial->gl_specular_id != tr.blackTexture ) + SetBits( pmaterial->flags, STUDIO_NF_GLOSSMAP ); + + if( pmaterial->gl_glowmap_id != tr.blackTexture ) + SetBits( pmaterial->flags, STUDIO_NF_LUMA ); + + if( pmaterial->gl_heightmap_id != tr.blackTexture ) + SetBits( pmaterial->flags, STUDIO_NF_HEIGHTMAP ); + + // setup material constants + matdesc_t *desc = CL_FindMaterial( matname ); + + pmaterial->gl_detailmap_id = desc->dt_texturenum; + pmaterial->smoothness = desc->smoothness; + pmaterial->detailScale[0] = desc->detailScale[0]; + pmaterial->detailScale[1] = desc->detailScale[1]; + pmaterial->reflectScale = desc->reflectScale; + pmaterial->refractScale = desc->refractScale; + pmaterial->aberrationScale = desc->aberrationScale; + pmaterial->reliefScale = desc->reliefScale; + pmaterial->effects = desc->effects; + + if( pmaterial->smoothness <= 0.0f ) // don't waste time + ClearBits( pmaterial->flags, STUDIO_NF_GLOSSMAP ); + + if( pmaterial->gl_detailmap_id > 0 && pmaterial->gl_detailmap_id != tr.grayTexture ) + SetBits( pmaterial->flags, STUDIO_NF_HAS_DETAIL ); + + // time to precache shaders + ShaderSceneForward( pmaterial, &tr.defaultLightInfo, false, bone_weights, m_pStudioHeader->numbones ); + ShaderLightForward( &tr.defaultlightSpot, pmaterial, bone_weights, m_pStudioHeader->numbones ); + ShaderLightForward( &tr.defaultlightOmni, pmaterial, bone_weights, m_pStudioHeader->numbones ); + ShaderLightForward( &tr.defaultlightProj, pmaterial, bone_weights, m_pStudioHeader->numbones ); + pmaterial->forwardScene.Invalidate(); // don't keep shadernum + } +} + +void CStudioModelRenderer :: FreeStudioMaterials( void ) +{ + if( !RI->currentmodel->materials ) return; + + mstudiomaterial_t *pmaterial = (mstudiomaterial_t *)RI->currentmodel->materials; + + // release textures for current model + for( int i = 0; i < m_pStudioHeader->numtextures; i++, pmaterial++ ) + { + if( pmaterial->pSource->index != pmaterial->gl_diffuse_id ) + FREE_TEXTURE( pmaterial->gl_diffuse_id ); + + if( pmaterial->gl_normalmap_id != tr.normalmapTexture ) + FREE_TEXTURE( pmaterial->gl_normalmap_id ); + + if( pmaterial->gl_specular_id != tr.blackTexture ) + FREE_TEXTURE( pmaterial->gl_specular_id ); + + if( pmaterial->gl_glowmap_id != tr.blackTexture ) + FREE_TEXTURE( pmaterial->gl_glowmap_id ); + } + + Mem_Free( RI->currentmodel->materials ); + RI->currentmodel->materials = NULL; +} + +void CStudioModelRenderer :: DestroyAllModelInstances( void ) +{ + // if caused by Host_Error during draw the viewmodel or gasmask + m_iDrawModelType = DRAWSTUDIO_NORMAL; + m_fShootDecal = false; + + // NOTE: should destroy in reverse-order because it's linked list not array! + for( int i = m_ModelInstances.Count(); --i >= 0; ) + DestroyInstance( i ); +} + +int CStudioModelRenderer :: HeadShieldThink( void ) +{ + switch( gHUD.m_iHeadShieldState ) + { + case SHIELD_ON: + return 1; + case SHIELD_TURNING_ON: + if( tr.time > gHUD.m_flHeadShieldSwitchTime ) + { + gHUD.m_iHeadShieldState = SHIELD_ON; + gHUD.m_pHeadShieldEnt->curstate.animtime = tr.time; + gHUD.m_pHeadShieldEnt->curstate.sequence = SHIELDANIM_IDLE; + } + return 1; + case SHIELD_TURNING_OFF: + if( tr.time > gHUD.m_flHeadShieldSwitchTime ) + { + gHUD.m_iHeadShieldState = SHIELD_OFF; + return 0; + } + else + { + return 1; + } + case SHIELD_OFF: + default: + return 0; + } +} + +/* +================ +StudioGetBounds + +Get bounds for a current sequence +================ +*/ +CMeshDesc *CStudioModelRenderer :: StudioGetMeshDesc( cl_entity_t *e ) +{ + if( !e || e->modelhandle == INVALID_HANDLE ) + return NULL; + + ModelInstance_t *inst = &m_ModelInstances[e->modelhandle]; + + return &inst->m_BodyMesh; +} + +/* +================ +StudioExtractBbox + +Extract bbox from current sequence +================ +*/ +int CStudioModelRenderer :: StudioExtractBbox( studiohdr_t *phdr, int sequence, Vector &mins, Vector &maxs ) +{ + if( !phdr || sequence < 0 || sequence >= phdr->numseq ) + return 0; + + mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)phdr + phdr->seqindex); + mins = pseqdesc[sequence].bbmin; + maxs = pseqdesc[sequence].bbmax; + + return 1; +} + +/* +================ +StudioComputeBBox + +Compute a full bounding box for current sequence +================ +*/ +int CStudioModelRenderer :: StudioComputeBBox( void ) +{ + Vector p1, p2, scale = Vector( 1.0f, 1.0f, 1.0f ); + Vector origin, mins, maxs; + + if( FBitSet( m_pModelInstance->info_flags, MF_STATIC_BOUNDS )) + return true; // bounds already computed + + if( !StudioExtractBbox( m_pStudioHeader, RI->currententity->curstate.sequence, mins, maxs )) + return false; + + // prevent to compute env_static bounds every frame + if( RI->currententity->curstate.iuser1 & CF_STATIC_ENTITY && RI->currententity->curstate.renderfx != SKYBOX_ENTITY ) + SetBits( m_pModelInstance->info_flags, MF_STATIC_BOUNDS ); + + if( FBitSet( RI->currententity->curstate.iuser1, CF_STATIC_ENTITY )) + { + if( RI->currententity->curstate.vuser1 != g_vecZero ) + scale = RI->currententity->curstate.vuser1; + } + else if( RI->currententity->curstate.scale > 0.0f && RI->currententity->curstate.scale <= 16.0f ) + { + // apply studiomodel scale (clamp scale to prevent too big sizes on some HL maps) + scale = Vector( RI->currententity->curstate.scale ); + } + + Vector angles = RI->currententity->angles; + angles[PITCH] = -angles[PITCH]; // stupid quakebug + + // don't rotate player model, only aim + if( RI->currententity->player ) angles[PITCH] = 0; + + if( RI->currententity->curstate.renderfx == SKYBOX_ENTITY ) + { + // calc skybox origin to avoid do it in StudioSetupTransform + Vector trans = GetVieworg() - tr.sky_origin; + if( tr.sky_speed ) trans -= (GetVieworg() - tr.sky_world_origin) / tr.sky_speed; + origin = RI->currententity->origin + trans; + } + else origin = RI->currententity->origin; + + matrix3x4 transform = matrix3x4( g_vecZero, angles, scale ); + + if( FBitSet( RI->currententity->curstate.iuser1, CF_STATIC_ENTITY ) && RI->currententity->curstate.renderfx != SKYBOX_ENTITY ) + { + BoundsTest_t lightbox[MAX_LIGHTBOX_TESTS]; // for effectively testing bbox individually per each entity + const float expand_text[MAX_LIGHTBOX_TESTS] = { 0.1f, -0.1f, 1.0f, -1.0f, 4.0f, -4.0f, 8.0f, -8.0f }; + matrix3x4 itransform = transform.Transpose(); + int i, bestBox = -1, maxValid = 0; + + memset( lightbox, 0, sizeof( lightbox )); + + for( i = 0; i < MAX_LIGHTBOX_TESTS; i++ ) + { + lightbox[i].expand = expand_text[i]; + lightbox[i].numValid = StudioComputeLightBBox( mins, maxs, itransform, origin, m_pModelInstance->bbox, lightbox[i].expand ); + if( lightbox[i].numValid == 8 ) break; // all the corners are valid + } + + // failed case + if( i == MAX_LIGHTBOX_TESTS ) + { + // search to state with max valid corners + for( i = 0; i < MAX_LIGHTBOX_TESTS; i++ ) + { + if( lightbox[i].numValid > maxValid ) + { + maxValid = lightbox[i].numValid; + bestBox = i; + } + } + + // select optimal box from the list + StudioComputeLightBBox( mins, maxs, itransform, origin, m_pModelInstance->bbox, lightbox[bestBox].expand ); + m_pModelInstance->numLightPoints = lightbox[bestBox].numValid; + } + else m_pModelInstance->numLightPoints = lightbox[i].numValid; + } + + // rotate and scale bbox for env_static + TransformAABB( transform, mins, maxs, mins, maxs ); + + if( m_pModelInstance->origin != origin ) + SetBits( m_pModelInstance->info_flags, MF_POSITION_CHANGED ); + + // compute abs box + m_pModelInstance->absmin = mins + origin; + m_pModelInstance->absmax = maxs + origin; + m_pModelInstance->radius = RadiusFromBounds( mins, maxs ); + m_pModelInstance->origin = origin; + + return true; +} + +/* +================ +StudioGetBounds + +Get bounds for a current sequence +================ +*/ +int CStudioModelRenderer :: StudioGetBounds( cl_entity_t *e, Vector bounds[2] ) +{ + if( !e || e->modelhandle == INVALID_HANDLE ) + return 0; + + ModelInstance_t *inst = &m_ModelInstances[e->modelhandle]; + bounds[0] = inst->absmin; + bounds[1] = inst->absmax; + + return 1; +} + +int CStudioModelRenderer :: StudioGetBounds( CSolidEntry *entry, Vector bounds[2] ) +{ + if( !entry || entry->m_bDrawType != DRAWTYPE_MESH ) + return 0; + + if( !entry->m_pParentEntity || entry->m_pParentEntity->modelhandle == INVALID_HANDLE ) + return 0; + + vbomesh_t *vbo = entry->m_pMesh; + + if( !vbo || vbo->parentbone == 0xFF ) + return 0; + + ModelInstance_t *inst = &m_ModelInstances[entry->m_pParentEntity->modelhandle]; + TransformAABB( inst->m_pbones[vbo->parentbone], vbo->mins, vbo->maxs, bounds[0], bounds[1] ); + + return 1; +} + +void CStudioModelRenderer :: StudioComputeDrawBBox( Vector bbox[8] ) +{ + ASSERT( m_pModelInstance != NULL && bbox != NULL ); + + // compute a full bounding box + for( int i = 0; i < 8; i++ ) + { + bbox[i][0] = ( i & 1 ) ? m_pModelInstance->absmin[0] : m_pModelInstance->absmax[0]; + bbox[i][1] = ( i & 2 ) ? m_pModelInstance->absmin[1] : m_pModelInstance->absmax[1]; + bbox[i][2] = ( i & 4 ) ? m_pModelInstance->absmin[2] : m_pModelInstance->absmax[2]; + } +} + +int CStudioModelRenderer :: StudioComputeLightBBox( const Vector &mins, const Vector &maxs, const matrix3x4 &itransform, const Vector &origin, Vector bbox[8], float expand ) +{ + Vector lightmins = mins; + Vector lightmaxs = maxs; + int validCorners = 0; + Vector p1, p2; + + // adjust bbox size + ExpandBounds( lightmins, lightmaxs, expand ); + + // if model too small we potentialy give backward min\max + if( BoundsIsCleared( lightmins, lightmaxs )) + return 0; + + // compute a full bounding box and transform to world space + for( int i = 0; i < 8; i++ ) + { + p1.x = ( i & 1 ) ? lightmins.x : lightmaxs.x; + p1.y = ( i & 2 ) ? lightmins.y : lightmaxs.y; + p1.z = ( i & 4 ) ? lightmins.z : lightmaxs.z; + + // rotate & translate bbox + p2.x = DotProduct( p1, itransform[0] ) + origin.x; + p2.y = DotProduct( p1, itransform[1] ) + origin.y; + p2.z = DotProduct( p1, itransform[2] ) + origin.z; + + if( !Mod_PointInSolid( p2 )) validCorners++; + + bbox[i] = p2; + } + + return validCorners; +} + +/* +==================== +StudioPlayerBlend + +==================== +*/ +void CStudioModelRenderer :: StudioPlayerBlend( mstudioseqdesc_t *pseqdesc, int &pBlend, float &pPitch ) +{ + pBlend = (pPitch * -3.0f); + + if( pBlend < pseqdesc->blendstart[0] ) + { + pPitch -= pseqdesc->blendstart[0] / 3.0f; + pBlend = 0; + } + else if( pBlend > pseqdesc->blendend[0] ) + { + pPitch -= pseqdesc->blendend[0] / 3.0f; + pBlend = 255; + } + else + { + if( pseqdesc->blendend[0] - pseqdesc->blendstart[0] < 0.1f ) // catch qc error + pBlend = 127; + else pBlend = 255 * (pBlend - pseqdesc->blendstart[0]) / (pseqdesc->blendend[0] - pseqdesc->blendstart[0]); + + pPitch = 0; + } +} + +void CStudioModelRenderer :: AddBlendSequence( int oldseq, int newseq, float prevframe, bool gaitseq ) +{ + mstudioseqdesc_t *poldseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + oldseq; + mstudioseqdesc_t *pnewseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + newseq; + + // sequence has changed, hold the previous sequence info + if( oldseq != newseq && !FBitSet( pnewseqdesc->flags, STUDIO_SNAP )) + { + mstudioblendseq_t *pseqblending; + + // move current sequence into circular buffer + m_pModelInstance->m_current_seqblend = (m_pModelInstance->m_current_seqblend + 1) & MASK_SEQBLENDS; + + pseqblending = &m_pModelInstance->m_seqblend[m_pModelInstance->m_current_seqblend]; + + pseqblending->blendtime = tr.time; + pseqblending->sequence = oldseq; + pseqblending->cycle = prevframe / m_boneSetup.LocalMaxFrame( oldseq ); + pseqblending->gaitseq = gaitseq; + pseqblending->fadeout = Q_min( poldseqdesc->fadeouttime / 100.0f, pnewseqdesc->fadeintime / 100.0f ); + if( pseqblending->fadeout <= 0.0f ) + pseqblending->fadeout = 0.2f; // force to default + } +} + +float CStudioModelRenderer :: CalcStairSmoothValue( float oldz, float newz, float smoothtime, float smoothvalue ) +{ + if( oldz < newz ) + return bound( newz - tr.movevars->stepsize, oldz + smoothtime * smoothvalue, newz ); + if( oldz > newz ) + return bound( newz, oldz - smoothtime * smoothvalue, newz + tr.movevars->stepsize ); + return 0.0f; +} + +int CStudioModelRenderer :: StudioCheckLOD( void ) +{ + mstudiobodyparts_t *m_pBodyPart; + + for( int i = 0; i < m_pStudioHeader->numbodyparts; i++ ) + { + m_pBodyPart = (mstudiobodyparts_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bodypartindex) + i; + + if( !Q_stricmp( m_pBodyPart->name, "studioLOD" )) + return m_pBodyPart->nummodels; + } + + return 0; // no lod-levels for this model +} + +/* +==================== +StudioSetUpTransform + +==================== +*/ +void CStudioModelRenderer :: StudioSetUpTransform( void ) +{ + cl_entity_t *e = RI->currententity; + bool disable_smooth = false; + float step, smoothtime; + + if( !m_fShootDecal && RI->currententity->curstate.renderfx != kRenderFxDeadPlayer ) + { + // calculate how much time has passed since the last V_CalcRefdef + smoothtime = bound( 0.0f, tr.time - m_pModelInstance->lerp.stairtime, 0.1f ); + m_pModelInstance->lerp.stairtime = tr.time; + + if( e->curstate.onground == -1 || FBitSet( e->curstate.effects, EF_NOINTERP )) + disable_smooth = true; + + if( e->curstate.movetype != MOVETYPE_STEP && e->curstate.movetype != MOVETYPE_WALK ) + disable_smooth = true; + + if( !FBitSet( m_pModelInstance->info_flags, MF_INIT_SMOOTHSTAIRS )) + { + SetBits( m_pModelInstance->info_flags, MF_INIT_SMOOTHSTAIRS ); + disable_smooth = true; + } + + if( disable_smooth ) + { + m_pModelInstance->lerp.stairoldz = e->origin[2]; + } + else + { + step = CalcStairSmoothValue( m_pModelInstance->lerp.stairoldz, e->origin[2], smoothtime, STAIR_INTERP_TIME ); + if( step ) m_pModelInstance->lerp.stairoldz = e->origin[2] = step; + } + } + + Vector origin = RI->currententity->origin; + Vector angles = RI->currententity->angles; + Vector scale = Vector( 1.0f, 1.0f, 1.0f ); + + float lodDist = (origin - RI->view.origin).Length() * RI->view.lodScale; + float radius = Q_max( m_pModelInstance->radius, 1.0f ); // to avoid division by zero + int lodnum = (int)( lodDist / radius ); + int numLods; + + if( CVAR_TO_BOOL( m_pCvarLodScale )) + lodnum /= (int)fabs( m_pCvarLodScale->value ); + if( CVAR_TO_BOOL( m_pCvarLodBias )) + lodnum += (int)fabs( m_pCvarLodBias->value ); + + // apply lodnum to model + if(( numLods = StudioCheckLOD( )) != 0 ) + { + // set derived LOD + e->curstate.body = Q_min( lodnum, numLods - 1 ); + } + + angles[PITCH] = -angles[PITCH]; // stupid quake bug! + + if( m_pPlayerInfo ) + { + int iBlend, m_iGaitSequence = 0; + mstudioseqdesc_t *pseqdesc; + + if( RI->currententity->curstate.renderfx != kRenderFxDeadPlayer ) + m_pPlayerInfo->gaitsequence = IEngineStudio.GetPlayerState( e->index - 1 )->gaitsequence; + else m_pPlayerInfo->gaitsequence = 0; + + if( m_iGaitSequence ) + { + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence; + // calc blend (FXIME: move to the server) + StudioPlayerBlend( pseqdesc, iBlend, angles[PITCH] ); + RI->currententity->curstate.blending[0] = iBlend; + RI->currententity->latched.prevblending[0] = iBlend; + } + + // don't rotate clients, only aim + angles[PITCH] = 0.0f; + + if( m_pPlayerInfo->gaitsequence != m_pModelInstance->lerp.gaitsequence ) + { + AddBlendSequence( m_pModelInstance->lerp.gaitsequence, m_pPlayerInfo->gaitsequence, m_pModelInstance->lerp.gaitframe, true ); + m_pModelInstance->lerp.gaitsequence = m_pPlayerInfo->gaitsequence; + } + m_pModelInstance->lerp.gaitframe = m_pPlayerInfo->gaitframe; + } + + if( FBitSet( RI->currententity->curstate.effects, EF_NOINTERP ) || tr.realframecount < 3 ) + { + m_pModelInstance->lerp.sequence = RI->currententity->curstate.sequence; + } + else if( RI->currententity->curstate.sequence != m_pModelInstance->lerp.sequence ) + { + AddBlendSequence( m_pModelInstance->lerp.sequence, RI->currententity->curstate.sequence, m_pModelInstance->lerp.frame ); + m_pModelInstance->lerp.sequence = RI->currententity->curstate.sequence; + } + + // don't blend sequences for a dead player or a viewmodel, faceprotect + if( m_iDrawModelType > DRAWSTUDIO_NORMAL || RI->currententity->curstate.renderfx == kRenderFxDeadPlayer ) + memset( &m_pModelInstance->m_seqblend, 0, sizeof( m_pModelInstance->m_seqblend )); + + if( RI->currententity->curstate.iuser1 & CF_STATIC_ENTITY ) + { + if( RI->currententity->curstate.vuser1 != g_vecZero ) + scale = RI->currententity->curstate.vuser1; + } + else if( RI->currententity->curstate.scale > 0.0f && RI->currententity->curstate.scale <= 16.0f ) + { + // apply studiomodel scale (clamp scale to prevent too big sizes on some HL maps) + scale = Vector( RI->currententity->curstate.scale, RI->currententity->curstate.scale, RI->currententity->curstate.scale ); + } + + if( RI->currententity->curstate.renderfx == SKYBOX_ENTITY ) + { + // calc skybox origin + Vector trans = GetVieworg() - tr.sky_origin; + if( tr.sky_speed ) trans -= (GetVieworg() - tr.sky_world_origin) / tr.sky_speed; + origin += trans; + } + + if( RP_LOCALCLIENT( RI->currententity ) && !FBitSet( RI->params, RP_THIRDPERSON )) + { + // offset only for legs or water reflection + if(( RI->currentmodel == m_pPlayerLegsModel ) || FBitSet( RI->params, RP_SHADOWVIEW|RP_MIRRORVIEW ) || ( GetVForward().z == 1.0f )) + { + Vector ang, forward; + ang = tr.cached_viewangles; + ang[PITCH] = ang[ROLL] = 0; // yaw only + AngleVectors( ang, forward, NULL, NULL ); + origin += forward * -m_pCvarLegsOffset->value; + } + } + + // build the rotation matrix + m_pModelInstance->m_protationmatrix = matrix3x4( origin, angles, scale ); + + if( RI->currententity == GET_VIEWMODEL() && CVAR_TO_BOOL( m_pCvarHand )) + { + // inverse the right vector + m_pModelInstance->m_protationmatrix.SetRight( -m_pModelInstance->m_protationmatrix.GetRight() ); + } + + StudioFxTransform( e, m_pModelInstance->m_protationmatrix ); +} + +/* +==================== +StudioEstimateFrame + +==================== +*/ +float CStudioModelRenderer :: StudioEstimateFrame( mstudioseqdesc_t *pseqdesc ) +{ + double dfdt = 0, f = 0; + + if( !m_fShootDecal && tr.time >= RI->currententity->curstate.animtime ) + dfdt = (tr.time - RI->currententity->curstate.animtime) * RI->currententity->curstate.framerate * pseqdesc->fps; + + if( pseqdesc->numframes > 1 ) + f = (RI->currententity->curstate.frame * (pseqdesc->numframes - 1)) / 256.0; + + f += dfdt; + + if( pseqdesc->flags & STUDIO_LOOPING ) + { + if( pseqdesc->numframes > 1 ) + { + f -= (int)(f / (pseqdesc->numframes - 1)) * (pseqdesc->numframes - 1); + } + + if( f < 0.0 ) + { + f += (pseqdesc->numframes - 1); + } + } + else + { + if( f >= pseqdesc->numframes - 1.001 ) + { + f = pseqdesc->numframes - 1.001; + } + + if( f < 0.0 ) + { + f = 0.0; + } + } + + return f; +} + +/* +==================== +StudioEstimateFrame + +==================== +*/ +float CStudioModelRenderer :: StudioEstimateGaitFrame( mstudioseqdesc_t *pseqdesc ) +{ + double dfdt = 0, f = 0; + + if( !m_fShootDecal && tr.time >= RI->currententity->curstate.animtime ) + dfdt = (tr.time - RI->currententity->curstate.animtime) / 0.1f; + + if( pseqdesc->numframes > 1 ) + f = RI->currententity->curstate.fuser1; + + f += dfdt; + + if( pseqdesc->flags & STUDIO_LOOPING ) + { + if( pseqdesc->numframes > 1 ) + { + f -= (int)(f / (pseqdesc->numframes - 1)) * (pseqdesc->numframes - 1); + } + + if( f < 0.0 ) + { + f += (pseqdesc->numframes - 1); + } + } + else + { + if( f >= pseqdesc->numframes - 1.001 ) + { + f = pseqdesc->numframes - 1.001; + } + + if( f < 0.0 ) + { + f = 0.0; + } + } + + return f; +} + +/* +==================== +StudioEstimateInterpolant + +==================== +*/ +float CStudioModelRenderer :: StudioEstimateInterpolant( void ) +{ + float dadt = 1.0f; + + if( !m_fShootDecal && ( RI->currententity->curstate.animtime >= RI->currententity->latched.prevanimtime + 0.01f )) + { + dadt = (tr.time - RI->currententity->curstate.animtime) / 0.1f; + + if( dadt > 2.0f ) + { + dadt = 2.0f; + } + } + + return dadt; +} + +/* +==================== +StudioInterpolateBlends + +==================== +*/ +void CStudioModelRenderer :: StudioInterpolateBlends( cl_entity_t *e, float dadt ) +{ + mstudiobonecontroller_t *pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bonecontrollerindex); + + if( !m_boneSetup.CountPoseParameters( )) + { + // interpolate blends + m_pModelInstance->m_poseparameter[0] = (e->curstate.blending[0] * dadt + e->latched.prevblending[0] * (1.0f - dadt)) / 255.0f; + m_pModelInstance->m_poseparameter[1] = (e->curstate.blending[1] * dadt + e->latched.prevblending[1] * (1.0f - dadt)) / 255.0f; + } + else + { + // interpolate pose parameters here... + } + + // buz: õàê, ïîçâîëÿþùèé íå èíòåðïîëèðîâàòü êîíòðîëëåðû äëÿ ñòàöèîíàðíîãî ïóëåìåòà + if( RI->currententity->curstate.renderfx == 51 ) + dadt = 1.0f; + + // interpolate controllers + for( int j = 0; j < m_pStudioHeader->numbonecontrollers; j++ ) + { + int i = pbonecontroller[j].index; + float value; + + if( i <= 3 ) + { + // check for 360% wrapping + if( FBitSet( pbonecontroller[j].type, STUDIO_RLOOP )) + { + if( abs( e->curstate.controller[i] - e->latched.prevcontroller[i] ) > 128 ) + { + int a = (e->curstate.controller[j] + 128) % 256; + int b = (e->latched.prevcontroller[j] + 128) % 256; + value = ((a * dadt) + (b * (1.0f - dadt)) - 128); + } + else + { + value = ((e->curstate.controller[i] * dadt + (e->latched.prevcontroller[i]) * (1.0f - dadt))); + } + } + else + { + value = (e->curstate.controller[i] * dadt + e->latched.prevcontroller[i] * (1.0 - dadt)); + } + m_pModelInstance->m_controller[i] = bound( 0, Q_rint( value ), 255 ); + } + } +} + +/* +==================== +StudioGetAnim + +==================== +*/ +mstudioanim_t *CStudioModelRenderer :: StudioGetAnim( model_t *m_pSubModel, mstudioseqdesc_t *pseqdesc ) +{ + mstudioseqgroup_t *pseqgroup; + cache_user_t *paSequences; + + pseqgroup = (mstudioseqgroup_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqgroupindex) + pseqdesc->seqgroup; + + if( pseqdesc->seqgroup == 0 ) + return (mstudioanim_t *)((byte *)m_pStudioHeader + pseqgroup->data + pseqdesc->animindex); + + paSequences = (cache_user_t *)m_pSubModel->submodels; + + if( paSequences == NULL ) + { + paSequences = (cache_user_t *)IEngineStudio.Mem_Calloc( MAXSTUDIOGROUPS, sizeof( cache_user_t )); + m_pSubModel->submodels = (dmodel_t *)paSequences; + } + + // check for already loaded + if( !IEngineStudio.Cache_Check(( struct cache_user_s *)&(paSequences[pseqdesc->seqgroup] ))) + { + char filepath[128], modelpath[128], modelname[64]; + + COM_FileBase( m_pSubModel->name, modelname ); + COM_ExtractFilePath( m_pSubModel->name, modelpath ); + + // NOTE: here we build real sub-animation filename because stupid user may rename model without recompile + Q_snprintf( filepath, sizeof( filepath ), "%s/%s%i%i.mdl", modelpath, modelname, pseqdesc->seqgroup / 10, pseqdesc->seqgroup % 10 ); + + ALERT( at_console, "loading: %s\n", filepath ); + IEngineStudio.LoadCacheFile( filepath, (struct cache_user_s *)&paSequences[pseqdesc->seqgroup] ); + } + + return (mstudioanim_t *)((byte *)paSequences[pseqdesc->seqgroup].data + pseqdesc->animindex); +} + +/* +==================== +Studio_FxTransform + +==================== +*/ +void CStudioModelRenderer :: StudioFxTransform( cl_entity_t *ent, matrix3x4 &transform ) +{ + switch( ent->curstate.renderfx ) + { + case kRenderFxDistort: + case kRenderFxHologram: + if( RANDOM_LONG( 0, 49 ) == 0 ) + { + // choose between x & z + switch( RANDOM_LONG( 0, 1 )) + { + case 0: + transform.SetForward( transform.GetForward() * RANDOM_FLOAT( 1.0f, 1.484f )); + break; + case 1: + transform.SetUp( transform.GetUp() * RANDOM_FLOAT( 1.0f, 1.484f )); + break; + } + } + else if( RANDOM_LONG( 0, 49 ) == 0 ) + { + transform[3][RANDOM_LONG( 0, 2 )] += RANDOM_FLOAT( -10.0f, 10.0f ); + } + break; + case kRenderFxExplode: + { + float scale = 1.0f + ( tr.time - ent->curstate.animtime ) * 10.0f; + if( scale > 2 ) scale = 2; // don't blow up more than 200% + transform.SetRight( transform.GetRight() * scale ); + } + break; + } +} + +void CStudioModelRenderer :: BlendSequence( Vector pos[], Vector4D q[], mstudioblendseq_t *pseqblend ) +{ + CIKContext *pIK = NULL; + + // to prevent division by zero + if( pseqblend->fadeout <= 0.0f ) + pseqblend->fadeout = 0.2f; + + if( m_boneSetup.GetNumIKChains( )) + pIK = &m_pModelInstance->m_ik; + + if( pseqblend->blendtime && ( pseqblend->blendtime + pseqblend->fadeout > tr.time ) && ( pseqblend->sequence < m_pStudioHeader->numseq )) + { + float s = 1.0f - (tr.time - pseqblend->blendtime) / pseqblend->fadeout; + + if( s > 0 && s <= 1.0 ) + { + // do a nice spline curve + s = 3.0f * s * s - 2.0f * s * s * s; + } + else if( s > 1.0f ) + { + // Shouldn't happen, but maybe curtime is behind animtime? + s = 1.0f; + } + + if( pseqblend->gaitseq ) + { + mstudiobone_t *pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + float m_flGaitBoneWeights[MAXSTUDIOBONES]; + bool copy = true; + + for( int i = 0; i < m_pStudioHeader->numbones; i++) + { + if( !Q_strcmp( pbones[i].name, "Bip01 Spine" )) + copy = false; + else if( !Q_strcmp( pbones[pbones[i].parent].name, "Bip01 Pelvis" )) + copy = true; + m_flGaitBoneWeights[i] = (copy) ? 1.0f : 0.0f; + } + + m_boneSetup.SetBoneWeights( m_flGaitBoneWeights ); // install weightlist for gait sequence + } + + m_boneSetup.AccumulatePose( pIK, pos, q, pseqblend->sequence, pseqblend->cycle, s ); + m_boneSetup.SetBoneWeights( NULL ); // back to default rules + } +} + +//----------------------------------------------------------------------------- +// Purpose: update latched IK contacts if they're in a moving reference frame. +//----------------------------------------------------------------------------- +void CStudioModelRenderer :: UpdateIKLocks( CIKContext *pIK ) +{ + if( !pIK ) return; + + int targetCount = pIK->m_target.Count(); + + if( targetCount == 0 ) + return; + + for( int i = 0; i < targetCount; i++ ) + { + CIKTarget *pTarget = &pIK->m_target[i]; + + if( !pTarget->IsActive( )) + continue; + + if( pTarget->GetOwner() != -1 ) + { + cl_entity_t *pOwner = GET_ENTITY( pTarget->GetOwner() ); + + if( pOwner != NULL ) + { + pTarget->UpdateOwner( pOwner->index, pOwner->origin, pOwner->angles ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Find the ground or external attachment points needed by IK rules +//----------------------------------------------------------------------------- +void CStudioModelRenderer :: CalculateIKLocks( CIKContext *pIK ) +{ + if( !pIK ) return; + + int targetCount = pIK->m_target.Count(); + + if( targetCount == 0 ) + return; + + // FIXME: trace based on gravity or trace based on angles? + Vector up; + AngleVectors( RI->currententity->angles, NULL, NULL, (float *)&up ); + + // FIXME: check number of slots? + float minHeight = FLT_MAX; + float maxHeight = -FLT_MAX; + + for( int i = 0, j = 0; i < targetCount; i++ ) + { + pmtrace_t *trace; + CIKTarget *pTarget = &pIK->m_target[i]; + float flDist = pTarget->est.radius; + + if( !pTarget->IsActive( )) + continue; + + switch( pTarget->type ) + { + case IK_GROUND: + { + Vector estGround; + Vector p1, p2; + + // adjust ground to original ground position + estGround = (pTarget->est.pos - RI->currententity->origin); + estGround = estGround - (estGround * up) * up; + estGround = RI->currententity->origin + estGround + pTarget->est.floor * up; + + p1 = estGround + up * pTarget->est.height; + p2 = estGround - up * pTarget->est.height; + float r = Q_max( pTarget->est.radius, 1 ); + + Vector mins = Vector( -r, -r, 0.0f ); + Vector maxs = Vector( r, r, r * 2.0f ); + + // don't IK to other characters + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PushTraceBounds( 2, mins, maxs ); + trace = gEngfuncs.pEventAPI->EV_VisTraceLine( p1, p2, PM_STUDIO_IGNORE ); + physent_t *ve = gEngfuncs.pEventAPI->EV_GetVisent( trace->ent ); + cl_entity_t *m_pGround = (ve) ? GET_ENTITY( ve->info ) : NULL; + gEngfuncs.pEventAPI->EV_PopTraceBounds(); + + if( m_pGround != NULL && m_pGround->curstate.movetype == MOVETYPE_PUSH ) + { + pTarget->SetOwner( m_pGround->index, m_pGround->origin, m_pGround->angles ); + } + else + { + pTarget->ClearOwner(); + } + + if( trace->startsolid ) + { + // trace from back towards hip + Vector tmp = (estGround - pTarget->trace.closest).Normalize(); + + p1 = estGround - tmp * pTarget->est.height; + p2 = estGround; + mins = Vector( -r, -r, 0.0f ); + maxs = Vector( r, r, 1.0f ); + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PushTraceBounds( 2, mins, maxs ); + trace = gEngfuncs.pEventAPI->EV_VisTraceLine( p1, p2, PM_STUDIO_IGNORE ); + ve = gEngfuncs.pEventAPI->EV_GetVisent( trace->ent ); + m_pGround = (ve) ? GET_ENTITY( ve->info ) : NULL; + gEngfuncs.pEventAPI->EV_PopTraceBounds(); + + if( !trace->startsolid ) + { + p1 = trace->endpos; + p2 = p1 - up * pTarget->est.height; + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + trace = gEngfuncs.pEventAPI->EV_VisTraceLine( p1, p2, PM_STUDIO_IGNORE ); + ve = gEngfuncs.pEventAPI->EV_GetVisent( trace->ent ); + m_pGround = (ve) ? GET_ENTITY( ve->info ) : NULL; + } + } + + if( !trace->startsolid ) + { + if( m_pGround == GET_ENTITY( 0 )) + { + // clamp normal to 33 degrees + const float limit = 0.832; + float dot = DotProduct( trace->plane.normal, up ); + + if( dot < limit ) + { + ASSERT( dot >= 0 ); + // subtract out up component + Vector diff = trace->plane.normal - up * dot; + // scale remainder such that it and the up vector are a unit vector + float d = sqrt(( 1.0f - limit * limit ) / DotProduct( diff, diff ) ); + trace->plane.normal = up * limit + d * diff; + } + + // FIXME: this is wrong with respect to contact position and actual ankle offset + pTarget->SetPosWithNormalOffset( trace->endpos, trace->plane.normal ); + pTarget->SetNormal( trace->plane.normal ); + pTarget->SetOnWorld( true ); + + // only do this on forward tracking or commited IK ground rules + if( pTarget->est.release < 0.1f ) + { + // keep track of ground height + float offset = DotProduct( pTarget->est.pos, up ); + + if( minHeight > offset ) + minHeight = offset; + if( maxHeight < offset ) + maxHeight = offset; + } + // FIXME: if we don't drop legs, running down hills looks horrible + /* + if (DotProduct( pTarget->est.pos, up ) < DotProduct( estGround, up )) + { + pTarget->est.pos = estGround; + } + */ + } + else if( m_pGround != NULL ) + { + pTarget->SetPos( trace->endpos ); + pTarget->SetAngles( RI->currententity->angles ); + + // only do this on forward tracking or commited IK ground rules + if( pTarget->est.release < 0.1f ) + { + float offset = DotProduct( pTarget->est.pos, up ); + + if( minHeight > offset ) + minHeight = offset; + + if( maxHeight < offset ) + maxHeight = offset; + } + // FIXME: if we don't drop legs, running down hills looks horrible + /* + if (DotProduct( pTarget->est.pos, up ) < DotProduct( estGround, up )) + { + pTarget->est.pos = estGround; + } + */ + } + else + { + pTarget->IKFailed(); + } + } + else + { + if( m_pGround != GET_ENTITY( 0 )) + { + pTarget->IKFailed( ); + } + else + { + pTarget->SetPos( trace->endpos ); + pTarget->SetAngles( RI->currententity->angles ); + pTarget->SetOnWorld( true ); + } + } + } + break; + case IK_ATTACHMENT: + flDist = pTarget->est.radius; + for( j = 1; j < RENDER_GET_PARM( PARM_MAX_ENTITIES, 0 ); j++ ) + { + cl_entity_t *m_pEntity = GET_ENTITY( j ); + float flRadius = 4096.0f; // (64.0f * 64.0f) + + if( !m_pEntity || m_pEntity->modelhandle == INVALID_HANDLE ) + continue; // not a studiomodel or not in PVS + + ModelInstance_t *inst = &m_ModelInstances[m_pEntity->modelhandle]; + float distSquared = 0.0f, eorg; + + for( int k = 0; k < 3 && distSquared <= flRadius; k++ ) + { + if( pTarget->est.pos[j] < inst->absmin[j] ) + eorg = pTarget->est.pos[j] - inst->absmin[j]; + else if( pTarget->est.pos[j] > inst->absmax[j] ) + eorg = pTarget->est.pos[j] - inst->absmax[j]; + else eorg = 0.0f; + + distSquared += eorg * eorg; + } + + if( distSquared >= flRadius ) + continue; // not in radius + + // Extract the bone index from the name + if( pTarget->offset.attachmentIndex >= inst->numattachments ) + continue; + + // FIXME: how to validate a index? + Vector origin = inst->attachment[pTarget->offset.attachmentIndex].origin; + Vector angles = inst->attachment[pTarget->offset.attachmentIndex].angles; + float d = (pTarget->est.pos - origin).Length(); + + if( d >= flDist ) + continue; + flDist = d; + pTarget->SetPos( origin ); + pTarget->SetAngles( angles ); + } + + if( flDist >= pTarget->est.radius ) + { + // no solution, disable ik rule + pTarget->IKFailed( ); + } + break; + } + } +} + +/* +==================== +StudioSetupBones + +==================== +*/ +void CStudioModelRenderer :: StudioSetupBones( void ) +{ + float adj[MAXSTUDIOCONTROLLERS]; + cl_entity_t *e = RI->currententity; // for more readability + CIKContext *pIK = NULL; + mstudioboneinfo_t *pboneinfo; + mstudioseqdesc_t *pseqdesc; + matrix3x4 bonematrix; + mstudiobone_t *pbones; + int i; + + static Vector pos[MAXSTUDIOBONES]; + static Vector4D q[MAXSTUDIOBONES]; + + if( e->curstate.sequence < 0 || e->curstate.sequence >= m_pStudioHeader->numseq ) + { + int sequence = (short)e->curstate.sequence; + ALERT( at_warning, "StudioSetupBones: sequence %i/%i out of range for model %s\n", sequence, m_pStudioHeader->numseq, RI->currentmodel->name ); + e->curstate.sequence = 0; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence; + float f = StudioEstimateFrame( pseqdesc ); + if( CheckBoneCache( f )) return; // using a cached bones no need transformations + + if( m_boneSetup.GetNumIKChains( )) + { + if( FBitSet( e->curstate.effects, EF_NOINTERP )) + m_pModelInstance->m_ik.ClearTargets(); + m_pModelInstance->m_ik.Init( &m_boneSetup, e->angles, e->origin, tr.time, tr.realframecount ); + pIK = &m_pModelInstance->m_ik; + } + + float dadt = StudioEstimateInterpolant(); + float cycle = f / m_boneSetup.LocalMaxFrame( e->curstate.sequence ); + + StudioInterpolateBlends( e, dadt ); + + m_boneSetup.InitPose( pos, q ); + m_boneSetup.UpdateRealTime( tr.time ); + if( CVAR_TO_BOOL( m_pCvarCompatible )) + m_boneSetup.CalcBoneAdj( adj, m_pModelInstance->m_controller, e->mouth.mouthopen ); + m_boneSetup.AccumulatePose( pIK, pos, q, e->curstate.sequence, cycle, 1.0 ); + m_pModelInstance->lerp.frame = f; + + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + pboneinfo = (mstudioboneinfo_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex + m_pStudioHeader->numbones * sizeof( mstudiobone_t )); + + if( m_pPlayerInfo && ( m_pPlayerInfo->gaitsequence < 0 || m_pPlayerInfo->gaitsequence >= m_pStudioHeader->numseq )) + m_pPlayerInfo->gaitsequence = 0; + + // calc gait animation + if( m_pPlayerInfo && m_pPlayerInfo->gaitsequence != 0 ) + { + float m_flGaitBoneWeights[MAXSTUDIOBONES]; + bool copy = true; + + for( int i = 0; i < m_pStudioHeader->numbones; i++) + { + if( !Q_strcmp( pbones[i].name, "Bip01 Spine" )) + copy = false; + else if( !Q_strcmp( pbones[pbones[i].parent].name, "Bip01 Pelvis" )) + copy = true; + m_flGaitBoneWeights[i] = (copy) ? 1.0f : 0.0f; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pPlayerInfo->gaitsequence; + f = StudioEstimateGaitFrame( pseqdesc ); + + // convert gaitframe to cycle + cycle = f / m_boneSetup.LocalMaxFrame( m_pPlayerInfo->gaitsequence ); + + m_boneSetup.SetBoneWeights( m_flGaitBoneWeights ); // install weightlist for gait sequence + m_boneSetup.AccumulatePose( pIK, pos, q, m_pPlayerInfo->gaitsequence, cycle, 1.0 ); + m_boneSetup.SetBoneWeights( NULL ); // back to default rules + m_pPlayerInfo->gaitframe = f; + } + + // blends from previous sequences + for( i = 0; i < MAX_SEQBLENDS; i++ ) + BlendSequence( pos, q, &m_pModelInstance->m_seqblend[i] ); + + CIKContext auto_ik; + auto_ik.Init( &m_boneSetup, e->angles, e->origin, 0.0f, 0 ); + m_boneSetup.CalcAutoplaySequences( &auto_ik, pos, q ); + if( !CVAR_TO_BOOL( m_pCvarCompatible )) + m_boneSetup.CalcBoneAdj( pos, q, m_pModelInstance->m_controller, e->mouth.mouthopen ); + + byte boneComputed[MAXSTUDIOBONES]; + + memset( boneComputed, 0, sizeof( boneComputed )); + + // don't calculate IK on ragdolls + if( pIK != NULL ) + { + UpdateIKLocks( pIK ); + pIK->UpdateTargets( pos, q, m_pModelInstance->m_pbones, boneComputed ); + CalculateIKLocks( pIK ); + pIK->SolveDependencies( pos, q, m_pModelInstance->m_pbones, boneComputed ); + } + + for( i = 0; i < m_pStudioHeader->numbones; i++ ) + { + // animate all non-simulated bones + if( CalcProceduralBone( m_pStudioHeader, i, m_pModelInstance->m_pbones )) + continue; + + // initialize bonematrix + bonematrix = matrix3x4( pos[i], q[i] ); + + if( FBitSet( pbones[i].flags, BONE_JIGGLE_PROCEDURAL ) && FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEINFO )) + { + // Physics-based "jiggle" bone + // Bone is assumed to be along the Z axis + // Pitch around X, yaw around Y + + // compute desired bone orientation + matrix3x4 goalMX; + + if( pbones[i].parent == -1 ) goalMX = m_pModelInstance->m_protationmatrix.ConcatTransforms( bonematrix ); + else goalMX = m_pModelInstance->m_pbones[pbones[i].parent].ConcatTransforms( bonematrix ); + + // get jiggle properties from QC data + mstudiojigglebone_t *jiggleInfo = (mstudiojigglebone_t *)((byte *)m_pStudioHeader + pboneinfo[i].procindex); + if( !m_pModelInstance->m_pJiggleBones ) m_pModelInstance->m_pJiggleBones = new CJiggleBones; + + // do jiggle physics + if( pboneinfo[i].proctype == STUDIO_PROC_JIGGLE ) + m_pModelInstance->m_pJiggleBones->BuildJiggleTransformations( i, tr.time, jiggleInfo, goalMX, m_pModelInstance->m_pbones[i] ); + else m_pModelInstance->m_pbones[i] = goalMX; // fallback + } + else + { + if( pbones[i].parent == -1 ) m_pModelInstance->m_pbones[i] = m_pModelInstance->m_protationmatrix.ConcatTransforms( bonematrix ); + else m_pModelInstance->m_pbones[i] = m_pModelInstance->m_pbones[pbones[i].parent].ConcatTransforms( bonematrix ); + } + } + + mposetobone_t *m = m_pModelInstance->m_pModel->poseToBone; + + // convert bones into compacted GLSL array + if( m != NULL ) + { + for( int i = 0; i < m_pStudioHeader->numbones; i++ ) + { + matrix3x4 out = m_pModelInstance->m_pbones[i].ConcatTransforms( m->posetobone[i] ); + out.CopyToArray4x3( &m_pModelInstance->m_glstudiobones[i*3] ); + m_pModelInstance->m_studioquat[i] = out.GetQuaternion(); + m_pModelInstance->m_studiopos[i] = out.GetOrigin(); + } + } + else + { + for( int i = 0; i < m_pStudioHeader->numbones; i++ ) + { + m_pModelInstance->m_pbones[i].CopyToArray4x3( &m_pModelInstance->m_glstudiobones[i*3] ); + m_pModelInstance->m_studioquat[i] = m_pModelInstance->m_pbones[i].GetQuaternion(); + m_pModelInstance->m_studiopos[i] = m_pModelInstance->m_pbones[i].GetOrigin(); + } + } +} + +/* +==================== +StudioMergeBones + +==================== +*/ +void CStudioModelRenderer :: StudioMergeBones( matrix3x4 &transform, matrix3x4 bones[], matrix3x4 cached_bones[], model_t *pModel, model_t *pParentModel ) +{ + matrix3x4 bonematrix; + static Vector pos[MAXSTUDIOBONES]; + static Vector4D q[MAXSTUDIOBONES]; + float poseparams[MAXSTUDIOPOSEPARAM]; + int sequence = RI->currententity->curstate.sequence; + model_t *oldmodel = RI->currentmodel; + studiohdr_t *oldheader = m_pStudioHeader; + + ASSERT( pModel != NULL && pModel->type == mod_studio ); + ASSERT( pParentModel != NULL && pParentModel->type == mod_studio ); + + RI->currentmodel = pModel; + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( RI->currentmodel ); + + // tell the bonesetup about current model + m_boneSetup.SetStudioPointers( m_pStudioHeader, poseparams ); // don't touch original parameters + + if( sequence < 0 || sequence >= m_pStudioHeader->numseq ) + sequence = 0; + + mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + sequence; + float f = StudioEstimateFrame( pseqdesc ); + float cycle = f / m_boneSetup.LocalMaxFrame( sequence ); + + m_boneSetup.InitPose( pos, q ); + m_boneSetup.AccumulatePose( NULL, pos, q, sequence, cycle, 1.0 ); + + studiohdr_t *m_pParentHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( pParentModel ); + + ASSERT( m_pParentHeader != NULL ); + + mstudiobone_t *pchildbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + mstudiobone_t *pparentbones = (mstudiobone_t *)((byte *)m_pParentHeader + m_pParentHeader->boneindex); + + for( int i = 0; i < m_pStudioHeader->numbones; i++ ) + { + for( int j = 0; j < m_pParentHeader->numbones; j++ ) + { + if( !Q_stricmp( pchildbones[i].name, pparentbones[j].name )) + { + bones[i] = cached_bones[j]; + break; + } + } + + if( j >= m_pParentHeader->numbones ) + { + // initialize bonematrix + bonematrix = matrix3x4( pos[i], q[i] ); + if( pchildbones[i].parent == -1 ) bones[i] = transform.ConcatTransforms( bonematrix ); + else bones[i] = bones[pchildbones[i].parent].ConcatTransforms( bonematrix ); + } + } + + RI->currentmodel = oldmodel; + m_pStudioHeader = oldheader; + + // restore the bonesetup pointers + m_boneSetup.SetStudioPointers( m_pStudioHeader, m_pModelInstance->m_poseparameter ); +} + +/* +==================== +StudioCalcAttachments + +==================== +*/ +void CStudioModelRenderer :: StudioCalcAttachments( matrix3x4 bones[] ) +{ + if( RI->currententity->modelhandle == INVALID_HANDLE || !m_pModelInstance ) + return; // too early ? + + if( FBitSet( m_pModelInstance->info_flags, MF_ATTACHMENTS_DONE )) + return; // already computed + + StudioAttachment_t *att = m_pModelInstance->attachment; + mstudioattachment_t *pattachment; + cl_entity_t *e = RI->currententity; + + // prevent to compute env_static bounds every frame + if( FBitSet( e->curstate.iuser1, CF_STATIC_ENTITY )) + SetBits( m_pModelInstance->info_flags, MF_ATTACHMENTS_DONE ); + + if( m_pStudioHeader->numattachments <= 0 ) + { + // clear attachments + for( int i = 0; i < MAXSTUDIOATTACHMENTS; i++ ) + { + if( i < 4 ) e->attachment[i] = e->origin; + att[i].angles = e->angles; + att[i].origin = e->origin; + } + return; + } + else if( m_pStudioHeader->numattachments > MAXSTUDIOATTACHMENTS ) + { + m_pStudioHeader->numattachments = MAXSTUDIOATTACHMENTS; // reduce it + ALERT( at_error, "Too many attachments on %s\n", e->model->name ); + } + + // calculate attachment points + pattachment = (mstudioattachment_t *)((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex); + + for( int i = 0; i < m_pStudioHeader->numattachments; i++ ) + { + matrix3x4 world = bones[pattachment[i].bone].ConcatTransforms( att[i].local ); + Vector p1 = bones[pattachment[i].bone].GetOrigin(); + + att[i].origin = world.GetOrigin(); + att[i].angles = world.GetAngles(); + + // merge attachments position for viewmodel + if( m_iDrawModelType == DRAWSTUDIO_VIEWMODEL || m_iDrawModelType == DRAWSTUDIO_RUNEVENTS ) + { + StudioFormatAttachment( att[i].origin ); + StudioFormatAttachment( p1 ); + } + + if( i < 4 ) e->attachment[i] = att[i].origin; + att[i].dir = (att[i].origin - p1).Normalize(); // forward vec + } +} + +/* +==================== +StudioGetAttachment + +==================== +*/ +void CStudioModelRenderer :: StudioGetAttachment( const cl_entity_t *ent, int iAttachment, Vector *pos, Vector *ang, Vector *dir ) +{ + if( !ent || !ent->model || ( !pos && !ang )) + return; + + studiohdr_t *phdr = (studiohdr_t *)IEngineStudio.Mod_Extradata( ent->model ); + if( !phdr ) return; + + if( ent->modelhandle == INVALID_HANDLE ) + return; // too early ? + + ModelInstance_t *inst = &m_ModelInstances[ent->modelhandle]; + + // make sure we not overflow + iAttachment = bound( 0, iAttachment, phdr->numattachments - 1 ); + + if( pos ) *pos = inst->attachment[iAttachment].origin; + if( ang ) *ang = inst->attachment[iAttachment].angles; + if( dir ) *dir = inst->attachment[iAttachment].dir; +} + +/* +==================== +StudioSetupModel + +used only by decals code +==================== +*/ +int CStudioModelRenderer :: StudioSetupModel( int bodypart, mstudiomodel_t **ppsubmodel, msubmodel_t **ppvbomodel ) +{ + mstudiobodyparts_t *pbodypart; + mstudiomodel_t *psubmodel; + mbodypart_t *pCachedParts; + msubmodel_t *pvbomodel; + int index; + + if( bodypart > m_pStudioHeader->numbodyparts ) + bodypart = 0; + + if( m_pModelInstance->m_VlCache != NULL ) + pCachedParts = &m_pModelInstance->m_VlCache->bodyparts[bodypart]; + else pCachedParts = &RI->currentmodel->studiocache->bodyparts[bodypart]; + pbodypart = (mstudiobodyparts_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bodypartindex) + bodypart; + + index = RI->currententity->curstate.body / pbodypart->base; + index = index % pbodypart->nummodels; + + psubmodel = (mstudiomodel_t *)((byte *)m_pStudioHeader + pbodypart->modelindex) + index; + pvbomodel = pCachedParts->models[index]; + + *ppsubmodel = psubmodel; + *ppvbomodel = pvbomodel; + + return index; +} + +/* +=============== +StudioLighting + +used for lighting decals +=============== +*/ +void CStudioModelRenderer :: StudioLighting( float *lv, int bone, int flags, const Vector &normal ) +{ + mstudiolight_t *light = &m_pModelInstance->lighting; + + if( FBitSet( flags, STUDIO_NF_FULLBRIGHT ) || FBitSet( RI->currententity->curstate.effects, EF_FULLBRIGHT ) || R_FullBright( )) + { + *lv = 1.0f; + return; + } + + float illum = light->ambientlight; + + if( FBitSet( flags, STUDIO_NF_FLATSHADE )) + { + illum += light->shadelight * 0.8f; + } + else + { + float lightcos; + + if( bone != -1 ) lightcos = DotProduct( normal, m_bonelightvecs[bone] ); + else lightcos = DotProduct( normal, light->plightvec ); // -1 colinear, 1 opposite + if( lightcos > 1.0f ) lightcos = 1.0f; + + illum += light->shadelight; + + // do modified hemispherical lighting + lightcos = (lightcos + ( SHADE_LAMBERT - 1.0f )) / SHADE_LAMBERT; + if( lightcos > 0.0f ) + illum -= light->shadelight * lightcos; + illum = Q_max( illum, 0.0f ); + } + + illum = Q_min( illum, 255.0f ); + *lv = illum * (1.0f / 255.0f); +} + +/* +=============== +CacheVertexLight + +=============== +*/ +void CStudioModelRenderer :: CacheVertexLight( cl_entity_t *ent, lightinfo_t *light ) +{ + if( FBitSet( ent->curstate.iuser1, CF_STATIC_ENTITY ) && ( ent->curstate.colormap > 0 ) && world->vertex_lighting != NULL ) + { + Vector org = m_pModelInstance->m_protationmatrix[3]; // model origin + int cacheID = ent->curstate.colormap - 1; + dvlightlump_t *vl = world->vertex_lighting; + + if( FBitSet( m_pModelInstance->info_flags, MF_VERTEX_LIGHTING )) + { +#if 1 + Vector fwd = m_pModelInstance->m_protationmatrix[0]; // model facing + + // TODO: compute ambient cube here + // solid and non-solid static entities + if( m_pModelInstance->numLightPoints > 5 ) R_LightForOBB( m_pModelInstance->bbox, fwd, org, light ); + else R_LightForAABB( m_pModelInstance->absmin, m_pModelInstance->absmax, fwd, org, light ); +#endif + } + else if( !FBitSet( m_pModelInstance->info_flags, MF_VL_BAD_CACHE )) + { + // first initialization + if( cacheID < vl->nummodels && vl->dataofs[cacheID] != -1 ) + { + dmodellight_t *dml = (dmodellight_t *)((byte *)world->vertex_lighting + vl->dataofs[cacheID]); + byte *vislight = ((byte *)world->vertex_lighting + vl->dataofs[cacheID]); + + // we have ID of vertex light cache and cache is present + CreateMeshCacheVL( dml, cacheID ); + + vislight += sizeof( *dml ) - ( sizeof( dvertlight_t ) * 3 ) + sizeof( dvertlight_t ) * dml->numverts; + Mod_FindStaticLights( vislight, m_pModelInstance->lights, org ); + } + } + } +} + +/* +=============== +StudioStaticLight + +=============== +*/ +void CStudioModelRenderer :: StudioStaticLight( cl_entity_t *ent, lightinfo_t *light ) +{ + if( !light ) return; + + if( m_iDrawModelType == DRAWSTUDIO_VIEWMODEL ) + { + static lightinfo_t lightprev, lightcur; + static float lerptime; + + cl_entity_t *player = GET_ENTITY( ent->index ); + + // changelevel issues + if( lerptime > tr.time + 1.0f ) + lerptime = tr.time + 0.1f; + + if( player->curstate.velocity.Length() != 0.0f ) + { + float frac = 1.0f - ((lerptime - tr.time) * 10.0f); + InterpolateOrigin( lightprev.data.diffuse[0], lightcur.data.diffuse[0], light->data.diffuse[0], frac ); + InterpolateOrigin( lightprev.data.normal[0], lightcur.data.normal[0], light->data.normal[0], frac ); + InterpolateOrigin( lightprev.data.origin[0], lightcur.data.origin[0], light->data.origin[0], frac ); + + for( int i = 0; i < 6; i++ ) + InterpolateOrigin( lightprev.ambient[i], lightcur.ambient[i], light->ambient[i], frac ); + + if(( lerptime - tr.time ) < 0.0f ) + { + lightprev = lightcur; // shuffle states + R_LightForPoint( ent->origin, &lightcur, false ); + lerptime = tr.time + 0.1f; + } + } + else + { + R_LightForPoint( ent->origin, light, false ); + lightprev = lightcur = *light; + lerptime = tr.time; + } + + R_CompressLightSamples( light ); + + // find worldlights for dynamic lighting + if( FBitSet( m_pModelInstance->info_flags, MF_POSITION_CHANGED )) + { + Vector origin = (m_pModelInstance->absmin + m_pModelInstance->absmax) * 0.5f; + Vector mins = m_pModelInstance->absmin - origin; + Vector maxs = m_pModelInstance->absmax - origin; + // search for worldlights that affected to model bbox + R_FindWorldLights( origin, mins, maxs, m_pModelInstance->lights ); + ClearBits( m_pModelInstance->info_flags, MF_POSITION_CHANGED ); + } + } + else + { + CacheVertexLight( ent, light ); + + // model has vertex lighting + if( FBitSet( m_pModelInstance->info_flags, MF_VERTEX_LIGHTING )) + return; + + float dynamic = r_dynamic->value; + alight_t lighting; + Vector dir; + + lighting.plightvec = dir; + + // setup classic Half-Life lighting + r_dynamic->value = 0.0f; // ignore dlights + IEngineStudio.StudioDynamicLight( ent, &lighting ); + r_dynamic->value = dynamic; + + m_pModelInstance->lighting.ambientlight = lighting.ambientlight; + m_pModelInstance->lighting.shadelight = lighting.shadelight; + m_pModelInstance->lighting.color = lighting.color; + m_pModelInstance->lighting.plightvec = -Vector( lighting.plightvec ); + + if( ent->curstate.renderfx == SKYBOX_ENTITY ) + { + // skyentity always get settings from light_environment entity + light->data.numSamples = 0; + R_LightForSky( ent->origin, light ); + return; + } + + bool invLight = (ent->curstate.effects & EF_INVLIGHT) ? true : false; + bool skipZCheck = true; + + if( ent->curstate.movetype == MOVETYPE_FLY ) + invLight = true; // flying entities should get lighting info from the ceiling + + if( FBitSet( ent->curstate.iuser1, CF_STATIC_ENTITY ) && ent->curstate.renderfx != SKYBOX_ENTITY ) + { + Vector fwd = m_pModelInstance->m_protationmatrix[0]; // model facing + Vector org = m_pModelInstance->m_protationmatrix[3]; // model origin + + // initialize custom lightgrid + if( FBitSet( m_pModelInstance->info_flags, MF_CUSTOM_LIGHTGRID ) && light->user.numProbes <= 0 ) + { + StudioAttachment_t *att = m_pModelInstance->attachment; + light->user.numProbes = 0; + + for( int i = 0; i < Q_min( MAXSTUDIOATTACHMENTS, m_pStudioHeader->numattachments ); i++ ) + { + if( !Q_strnicmp( att[i].name, "LightProbe.", 11 ) && light->user.numProbes < LIGHT_PROBES ) + { + light->user.probe[light->user.numProbes] = att[i].origin; + light->user.numProbes++; + } + } + } + + // solid and non-solid static entities + if( m_pModelInstance->numLightPoints > 5 ) R_LightForOBB( m_pModelInstance->bbox, fwd, org, light ); + else R_LightForAABB( m_pModelInstance->absmin, m_pModelInstance->absmax, fwd, org, light ); + } + else if( ent->curstate.movetype == MOVETYPE_STEP ) + { + // scripted (env_model) and normal NPC's + R_LightForHull( m_pModelInstance->absmin, m_pModelInstance->absmax, light ); + skipZCheck = false; + } + else + { + Vector mid = ( m_pModelInstance->absmin + m_pModelInstance->absmax ) * 0.5f; + R_LightForPoint( mid, light, invLight ); // all remaining cases + } + + // find worldlights for dynamic lighting + if( FBitSet( m_pModelInstance->info_flags, MF_POSITION_CHANGED )) + { + Vector origin = (m_pModelInstance->absmin + m_pModelInstance->absmax) * 0.5f; + Vector mins = m_pModelInstance->absmin - origin; + Vector maxs = m_pModelInstance->absmax - origin; + // search for worldlights that affected to model bbox + R_FindWorldLights( origin, mins, maxs, m_pModelInstance->lights, skipZCheck ); + ClearBits( m_pModelInstance->info_flags, MF_POSITION_CHANGED ); + } + + // use inverted light vector for head shield (hack) + if( m_iDrawModelType == DRAWSTUDIO_HEADSHIELD ) + { + for( int i = 0; i < light->data.numSamples; i++ ) + light->data.normal[i] = -light->data.normal[i]; + } + } +} + +/* +=============== +StudioClientEvents + +=============== +*/ +void CStudioModelRenderer :: StudioClientEvents( void ) +{ + mstudioseqdesc_t *pseqdesc; + mstudioevent_t *pevent; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + RI->currententity->curstate.sequence; + pevent = (mstudioevent_t *)((byte *)m_pStudioHeader + pseqdesc->eventindex); + + // no events for this animation or gamepaused + if( pseqdesc->numevents == 0 || tr.time == tr.oldtime ) + return; + + float f = StudioEstimateFrame( pseqdesc ) + 0.01f; // get start offset + float start = f - RI->currententity->curstate.framerate * (tr.time - tr.oldtime) * pseqdesc->fps; + + for( int i = 0; i < pseqdesc->numevents; i++ ) + { + // ignore all non-client-side events + if( pevent[i].event < EVENT_CLIENT ) + continue; + + if( (float)pevent[i].frame > start && f >= (float)pevent[i].frame ) + HUD_StudioEvent( &pevent[i], RI->currententity ); + } +} + +/* +=============== +StudioDrawHulls + +=============== +*/ +void CStudioModelRenderer :: StudioDrawHulls( int iHitbox ) +{ + float alpha, lv; + int i, j; + + if( r_drawentities->value == 4 ) + alpha = 0.5f; + else alpha = 1.0f; + + // setup bone lighting + for( i = 0; i < m_pStudioHeader->numbones; i++ ) + m_bonelightvecs[i] = m_pModelInstance->m_pbones[i].VectorIRotate( -m_pModelInstance->lighting.plightvec ); + + GL_BindTexture( GL_TEXTURE0, tr.whiteTexture ); + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + + for( i = 0; i < m_pStudioHeader->numhitboxes; i++ ) + { + mstudiobbox_t *pbbox = (mstudiobbox_t *)((byte *)m_pStudioHeader + m_pStudioHeader->hitboxindex); + vec3_t tmp, p[8]; + + if( iHitbox >= 0 && iHitbox != i ) + continue; + + for( j = 0; j < 8; j++ ) + { + tmp[0] = (j & 1) ? pbbox[i].bbmin[0] : pbbox[i].bbmax[0]; + tmp[1] = (j & 2) ? pbbox[i].bbmin[1] : pbbox[i].bbmax[1]; + tmp[2] = (j & 4) ? pbbox[i].bbmin[2] : pbbox[i].bbmax[2]; + p[j] = m_pModelInstance->m_pbones[pbbox[i].bone].VectorTransform( tmp ); + } + + j = (pbbox[i].group % ARRAYSIZE( g_hullcolor )); + + gEngfuncs.pTriAPI->Begin( TRI_QUADS ); + gEngfuncs.pTriAPI->Color4f( g_hullcolor[j][0], g_hullcolor[j][1], g_hullcolor[j][2], alpha ); + + for( j = 0; j < 6; j++ ) + { + tmp = g_vecZero; + tmp[j % 3] = (j < 3) ? 1.0f : -1.0f; + StudioLighting( &lv, pbbox[i].bone, 0, tmp ); + + gEngfuncs.pTriAPI->Brightness( lv ); + gEngfuncs.pTriAPI->Vertex3fv( p[g_boxpnt[j][0]] ); + gEngfuncs.pTriAPI->Vertex3fv( p[g_boxpnt[j][1]] ); + gEngfuncs.pTriAPI->Vertex3fv( p[g_boxpnt[j][2]] ); + gEngfuncs.pTriAPI->Vertex3fv( p[g_boxpnt[j][3]] ); + } + gEngfuncs.pTriAPI->End(); + } +} + +/* +=============== +StudioDrawAbsBBox + +=============== +*/ +void CStudioModelRenderer :: StudioDrawAbsBBox( void ) +{ + Vector p[8], tmp; + float lv; + int i; + + // looks ugly, skip + if( RI->currententity == GET_VIEWMODEL( )) + return; + + // compute a full bounding box + for( i = 0; i < 8; i++ ) + { + p[i][0] = ( i & 1 ) ? m_pModelInstance->absmin[0] : m_pModelInstance->absmax[0]; + p[i][1] = ( i & 2 ) ? m_pModelInstance->absmin[1] : m_pModelInstance->absmax[1]; + p[i][2] = ( i & 4 ) ? m_pModelInstance->absmin[2] : m_pModelInstance->absmax[2]; + } + + GL_BindTexture( GL_TEXTURE0, tr.whiteTexture ); + gEngfuncs.pTriAPI->Color4f( 0.5f, 0.5f, 1.0f, 0.5f ); + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + + gEngfuncs.pTriAPI->Begin( TRI_QUADS ); + + for( i = 0; i < 6; i++ ) + { + tmp = g_vecZero; + tmp[i % 3] = (i < 3) ? 1.0f : -1.0f; + StudioLighting( &lv, -1, 0, tmp ); + + gEngfuncs.pTriAPI->Brightness( lv ); + gEngfuncs.pTriAPI->Vertex3fv( p[g_boxpnt[i][0]] ); + gEngfuncs.pTriAPI->Vertex3fv( p[g_boxpnt[i][1]] ); + gEngfuncs.pTriAPI->Vertex3fv( p[g_boxpnt[i][2]] ); + gEngfuncs.pTriAPI->Vertex3fv( p[g_boxpnt[i][3]] ); + } + + gEngfuncs.pTriAPI->End(); +} + +/* +=============== +StudioDrawBones + +=============== +*/ +void CStudioModelRenderer :: StudioDrawBones( void ) +{ + mstudiobone_t *pbones = (mstudiobone_t *) ((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + Vector point; + + pglDisable( GL_TEXTURE_2D ); + + for( int i = 0; i < m_pStudioHeader->numbones; i++ ) + { + if( pbones[i].parent >= 0 ) + { + pglPointSize( 3.0f ); + pglColor3f( 1, 0.7f, 0 ); + pglBegin( GL_LINES ); + + m_pModelInstance->m_pbones[pbones[i].parent].GetOrigin( point ); + pglVertex3fv( point ); + + m_pModelInstance->m_pbones[i].GetOrigin( point ); + pglVertex3fv( point ); + + pglEnd(); + + pglColor3f( 0, 0, 0.8f ); + pglBegin( GL_POINTS ); + + if( pbones[pbones[i].parent].parent != -1 ) + { + m_pModelInstance->m_pbones[pbones[i].parent].GetOrigin( point ); + pglVertex3fv( point ); + } + + m_pModelInstance->m_pbones[i].GetOrigin( point ); + pglVertex3fv( point ); + pglEnd(); + } + else + { + // draw parent bone node + pglPointSize( 5.0f ); + pglColor3f( 0.8f, 0, 0 ); + pglBegin( GL_POINTS ); + + m_pModelInstance->m_pbones[i].GetOrigin( point ); + pglVertex3fv( point ); + pglEnd(); + } + } + + pglPointSize( 1.0f ); + pglEnable( GL_TEXTURE_2D ); +} + +void CStudioModelRenderer :: StudioDrawAttachments( bool bCustomFov ) +{ + pglDisable( GL_TEXTURE_2D ); + pglDisable( GL_DEPTH_TEST ); + + for( int i = 0; i < m_pStudioHeader->numattachments; i++ ) + { + mstudioattachment_t *pattachments; + Vector v[4]; + + pattachments = (mstudioattachment_t *) ((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex); + + v[0] = m_pModelInstance->m_pbones[pattachments[i].bone].VectorTransform( pattachments[i].org ); + v[1] = m_pModelInstance->m_pbones[pattachments[i].bone].VectorTransform( g_vecZero ); + v[2] = m_pModelInstance->m_pbones[pattachments[i].bone].VectorTransform( g_vecZero ); + v[3] = m_pModelInstance->m_pbones[pattachments[i].bone].VectorTransform( g_vecZero ); + + for( int j = 0; j < 4 && bCustomFov; j++ ) + StudioFormatAttachment( v[j] ); + + pglBegin( GL_LINES ); + pglColor3f( 1, 0, 0 ); + pglVertex3fv( v[0] ); + pglColor3f( 1, 1, 1 ); + pglVertex3fv( v[1] ); + pglColor3f( 1, 0, 0 ); + pglVertex3fv( v[0] ); + pglColor3f( 1, 1, 1 ); + pglVertex3fv (v[2] ); + pglColor3f( 1, 0, 0 ); + pglVertex3fv( v[0] ); + pglColor3f( 1, 1, 1 ); + pglVertex3fv( v[3] ); + pglEnd(); + + pglPointSize( 5.0f ); + pglColor3f( 0, 1, 0 ); + pglBegin( GL_POINTS ); + pglVertex3fv( v[0] ); + pglEnd(); + pglPointSize( 1.0f ); + } + + pglEnable( GL_TEXTURE_2D ); + pglEnable( GL_DEPTH_TEST ); +} + +void CStudioModelRenderer :: StudioDrawLightGrid( void ) +{ + lightinfo_t *light = &m_pModelInstance->light; + + pglDisable( GL_TEXTURE_2D ); + pglDisable( GL_DEPTH_TEST ); + + for( int i = 0; i < light->grid.numProbes; i++ ) + { + Vector hit, vec; + + VectorLerp( light->grid.p0[i], light->grid.fraction[i], light->grid.p1[i], hit ); + + vec = light->grid.p0[i] + light->data.normal[i].Normalize() * 4.0f; + + pglBegin( GL_LINES ); + pglColor3f( 1, 0.5, 0 ); + pglVertex3fv( light->grid.p0[i] ); + pglColor3f( 1, 0.5, 1 ); + pglVertex3fv( hit ); + pglColor3f( 0, 1, 0 ); + pglVertex3fv( light->grid.p0[i] ); + pglColor3f( 0, 1, 0 ); + pglVertex3fv( vec ); + pglEnd(); + + pglPointSize( 5.0f ); + pglColor3f( 1, 0, 0 ); + pglBegin( GL_POINTS ); + pglVertex3fv( light->grid.p0[i] ); + pglColor3f( 0, 1, 0 ); + pglVertex3fv( hit ); + pglEnd(); + pglPointSize( 1.0f ); + } + + pglEnable( GL_DEPTH_TEST ); + + if( light->cubeOrigin != g_vecZero ) + { + // also draw the light cube + R_RenderLightProbeInternal( light->cubeOrigin, light->ambient ); + } + + pglEnable( GL_TEXTURE_2D ); +} + +void CStudioModelRenderer :: StudioDrawDebug( cl_entity_t *e ) +{ + matrix4x4 projMatrix, worldViewProjMatrix; + bool bCustomFov = false; + + // don't reflect this entity in mirrors + if( e->curstate.effects & EF_NOREFLECT && FBitSet( RI->params, RP_MIRRORVIEW )) + return; + + // draw only in mirrors + if( e->curstate.effects & EF_REFLECTONLY && !FBitSet( RI->params, RP_MIRRORVIEW )) + return; + + // ignore in thirdperson, camera view or client is died + if( e == GET_VIEWMODEL() || e == gHUD.m_pHeadShieldEnt ) + { + if( FBitSet( RI->params, RP_THIRDPERSON ) || CL_IsDead() || !UTIL_IsLocal( RI->view.entity )) + return; + + if( !RP_NORMALPASS( )) + return; + + bCustomFov = ComputeCustomFov( projMatrix, worldViewProjMatrix ); + } + + if( !StudioSetEntity( e )) + return; + + GL_Blend( GL_FALSE ); + GL_AlphaTest( GL_FALSE ); + GL_BindShader( NULL ); + + switch( (int)r_drawentities->value ) + { + case 2: + StudioDrawBones(); + break; + case 3: + StudioDrawHulls (); + break; + case 4: + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + StudioDrawHulls (); + gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); + break; + case 5: + StudioDrawAbsBBox(); + break; + case 6: + StudioDrawAttachments( bCustomFov ); + break; + case 7: + case 8: + StudioDrawLightGrid(); + break; + } + + if( bCustomFov ) RestoreNormalFov( projMatrix, worldViewProjMatrix ); +} + +/* +==================== +StudioFormatAttachment + +==================== +*/ +void CStudioModelRenderer :: StudioFormatAttachment( Vector &point ) +{ + float worldx = tan( (float)RI->view.fov_x * M_PI / 360.0 ); + float viewx = tan( m_flViewmodelFov * M_PI / 360.0 ); + + // BUGBUG: workaround + if( viewx == 0.0f ) viewx = 1.0f; + + // aspect ratio cancels out, so only need one factor + // the difference between the screen coordinates of the 2 systems is the ratio + // of the coefficients of the projection matrices (tan (fov/2) is that coefficient) + float factor = worldx / viewx; + + // get the coordinates in the viewer's space. + Vector tmp = point - GetVieworg(); + Vector vTransformed; + + vTransformed.x = DotProduct( GetVLeft(), tmp ); + vTransformed.y = DotProduct( GetVUp(), tmp ); + vTransformed.z = DotProduct( GetVForward(), tmp ); + vTransformed.x *= factor; + vTransformed.y *= factor; + + // Transform back to world space. + Vector vOut = (GetVLeft() * vTransformed.x) + (GetVUp() * vTransformed.y) + (GetVForward() * vTransformed.z); + point = GetVieworg() + vOut; +} + +/* +=============== +ChooseStudioProgram + +Select the program for mesh (diffuse\bump\debug) +=============== +*/ +word CStudioModelRenderer :: ChooseStudioProgram( studiohdr_t *phdr, mstudiomaterial_t *mat, bool lightpass ) +{ + bool vertex_lighting = FBitSet( m_pModelInstance->info_flags, MF_VERTEX_LIGHTING ) ? true : false; + bool bone_weights = FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS ) ? true : false; + + if( FBitSet( RI->params, RP_SHADOWVIEW )) + return ShaderSceneDepth( mat, bone_weights, phdr->numbones ); + + if( lightpass ) + { + return ShaderLightForward( RI->currentlight, mat, bone_weights, phdr->numbones ); + } + else + { + if( FBitSet( RI->params, RP_DEFERREDSCENE|RP_DEFERREDLIGHT )) + { + ShaderSceneDeferred( mat, bone_weights, phdr->numbones ); + return ShaderLightDeferred( mat, bone_weights, phdr->numbones ); + } + + return ShaderSceneForward( mat, &m_pModelInstance->light, vertex_lighting, bone_weights, phdr->numbones ); + } +} + +/* +==================== +AddMeshToDrawList + +==================== +*/ +void CStudioModelRenderer :: AddMeshToDrawList( studiohdr_t *phdr, vbomesh_t *mesh, bool lightpass ) +{ + int m_skinnum = bound( 0, RI->currententity->curstate.skin, phdr->numskinfamilies - 1 ); + short *pskinref = (short *)((byte *)phdr + phdr->skinindex); + if( m_skinnum != 0 && m_skinnum < phdr->numskinfamilies ) + pskinref += (m_skinnum * phdr->numskinref); + + mstudiomaterial_t *mat = NULL; + if( FBitSet( RI->params, RP_SHADOWVIEW )) + mat = &RI->currentmodel->materials[pskinref[mesh->skinref]]; + else mat = &m_pModelInstance->materials[pskinref[mesh->skinref]]; // NOTE: use local copy for right cache shadernums + bool solid = ( R_OpaqueEntity( RI->currententity ) && !FBitSet( mat->flags, STUDIO_NF_ADDITIVE )) ? true : false; + + // goes into regular arrays + if( FBitSet( RI->params, RP_SHADOWVIEW )) + { + if( FBitSet( mat->flags, STUDIO_NF_ADDITIVE ) && FBitSet( mat->flags, STUDIO_NF_HAS_ALPHA )) + solid = true; // add alpha-glasses to shadowlist + lightpass = false; + } + + if( lightpass ) + { + if( RI->currententity->curstate.renderfx == SKYBOX_ENTITY ) + return; // skybox entities can't lighting by dynlights + if( FBitSet( mat->flags, STUDIO_NF_FULLBRIGHT )) + return; // can't light fullbrights + + if( RI->currentlight->type == LIGHT_DIRECTIONAL ) + { + if( FBitSet( mat->flags, STUDIO_NF_NOSUNLIGHT )) + return; // shader was failed to compile + } + else + { + if( FBitSet( mat->flags, STUDIO_NF_NODLIGHT )) + return; // shader was failed to compile + } + } + else + { + if( FBitSet( mat->flags, STUDIO_NF_NODRAW )) + return; // shader was failed to compile + } + + word hProgram = 0; + + if( !( hProgram = ChooseStudioProgram( phdr, mat, lightpass ))) + return; // failed to build shader, don't draw this surface + glsl_program_t *shader = &glsl_programs[hProgram]; + + if( lightpass ) + { + CSolidEntry entry; + entry.SetRenderMesh( mesh, hProgram ); + RI->frame.light_meshes.AddToTail( entry ); + } + else if( solid ) + { + CSolidEntry entry; + entry.SetRenderMesh( mesh, hProgram ); + RI->frame.solid_meshes.AddToTail( entry ); + } + else + { + CTransEntry entry; + + entry.SetRenderMesh( mesh, hProgram ); + if( ScreenCopyRequired( shader ) && mesh->parentbone != 0xFF ) + { + Vector mins, maxs; + TransformAABB( m_pModelInstance->m_pbones[mesh->parentbone], mesh->mins, mesh->maxs, mins, maxs ); + // create sentinel border for refractions + ExpandBounds( mins, maxs, 2.0f ); + entry.ComputeScissor( mins, maxs ); + } + RI->frame.trans_list.AddToTail( entry ); + } +} + +/* +==================== +AddBodyPartToDrawList + +==================== +*/ +void CStudioModelRenderer :: AddBodyPartToDrawList( studiohdr_t *phdr, mbodypart_s *bodyparts, int bodypart, bool lightpass ) +{ + if( !bodyparts ) bodyparts = RI->currentmodel->studiocache->bodyparts; + if( !bodyparts ) HOST_ERROR( "%s missed cache\n", RI->currententity->model->name ); + + bodypart = bound( 0, bodypart, phdr->numbodyparts ); + mbodypart_t *pBodyPart = &bodyparts[bodypart]; + int index = RI->currententity->curstate.body / pBodyPart->base; + index = index % pBodyPart->nummodels; + + msubmodel_t *pSubModel = pBodyPart->models[index]; + if( !pSubModel ) return; // blank submodel, just ignore + + for( int i = 0; i < pSubModel->nummesh; i++ ) + AddMeshToDrawList( phdr, &pSubModel->meshes[i], lightpass ); +} + +/* +================= +AddStudioModelToDrawList + +do culling, compute bones, add meshes to list +================= +*/ +void CStudioModelRenderer :: AddStudioModelToDrawList( cl_entity_t *e, bool update ) +{ + // no shadows for skybox ents + if( FBitSet( RI->params, RP_SHADOWVIEW ) && e->curstate.renderfx == SKYBOX_ENTITY ) + return; + + if( !StudioSetEntity( e )) + return; + + if( !StudioComputeBBox( )) + return; // invalid sequence + + if( !Mod_CheckBoxVisible( m_pModelInstance->absmin, m_pModelInstance->absmax )) + { + r_stats.c_culled_entities++; + return; + } + + if( R_CullModel( RI->currententity, m_pModelInstance->absmin, m_pModelInstance->absmax )) + { + r_stats.c_culled_entities++; + return; // culled + } + + m_pModelInstance->visframe = tr.realframecount; // visible + + if( m_pModelInstance->cached_frame != tr.realframecount ) + { + StudioSetUpTransform( ); + + if( RI->currententity->curstate.movetype == MOVETYPE_FOLLOW && RI->currententity->curstate.aiment > 0 ) + { + cl_entity_t *parent = gEngfuncs.GetEntityByIndex( RI->currententity->curstate.aiment ); + if( parent != NULL && parent->modelhandle != INVALID_HANDLE ) + { + ModelInstance_t *inst = &m_ModelInstances[parent->modelhandle]; + StudioMergeBones( m_pModelInstance->m_protationmatrix, m_pModelInstance->m_pbones, inst->m_pbones, RI->currentmodel, parent->model ); + + mposetobone_t *m = m_pModelInstance->m_pModel->poseToBone; + + // convert bones into compacted GLSL array + if( m != NULL ) + { + for( int i = 0; i < m_pStudioHeader->numbones; i++ ) + { + matrix3x4 out = m_pModelInstance->m_pbones[i].ConcatTransforms( m->posetobone[i] ); + out.CopyToArray4x3( &m_pModelInstance->m_glstudiobones[i*3] ); + m_pModelInstance->m_studioquat[i] = out.GetQuaternion(); + m_pModelInstance->m_studiopos[i] = out.GetOrigin(); + } + } + else + { + for( int i = 0; i < m_pStudioHeader->numbones; i++ ) + { + m_pModelInstance->m_pbones[i].CopyToArray4x3( &m_pModelInstance->m_glstudiobones[i*3] ); + m_pModelInstance->m_studioquat[i] = m_pModelInstance->m_pbones[i].GetQuaternion(); + m_pModelInstance->m_studiopos[i] = m_pModelInstance->m_pbones[i].GetOrigin(); + } + } + } + else + { + ALERT( at_error, "FollowEntity: %i with model %s has missed parent!\n", + RI->currententity->index, RI->currentmodel->name ); + return; + } + } + else StudioSetupBones( ); + + // calc attachments only once per frame + StudioCalcAttachments( m_pModelInstance->m_pbones ); + StudioClientEvents( ); + + if( RI->currententity->index > 0 ) + { + // because RI->currententity may be not equal his index e.g. for viewmodel + cl_entity_t *ent = GET_ENTITY( RI->currententity->index ); + memcpy( ent->attachment, RI->currententity->attachment, sizeof( Vector ) * 4 ); + } + + m_pPlayerInfo = NULL; + + // grab the static lighting from world + StudioStaticLight( RI->currententity, &m_pModelInstance->light ); + + model_t *pweaponmodel = NULL; + + if( RI->currententity->curstate.weaponmodel ) + pweaponmodel = IEngineStudio.GetModelByIndex( RI->currententity->curstate.weaponmodel ); + + // don't draw p_model for firstperson legs + if( pweaponmodel && ( RI->currentmodel != m_pPlayerLegsModel )) + { + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( pweaponmodel ); + + StudioMergeBones( m_pModelInstance->m_protationmatrix, m_pModelInstance->m_pwpnbones, m_pModelInstance->m_pbones, pweaponmodel, RI->currentmodel ); + + mposetobone_t *m = pweaponmodel->poseToBone; + + // convert bones into compacted GLSL array + if( m != NULL ) + { + for( int i = 0; i < m_pStudioHeader->numbones; i++ ) + { + matrix3x4 out = m_pModelInstance->m_pwpnbones[i].ConcatTransforms( m->posetobone[i] ); + out.CopyToArray4x3( &m_pModelInstance->m_glweaponbones[i*3] ); + m_pModelInstance->m_weaponquat[i] = out.GetQuaternion(); + m_pModelInstance->m_weaponpos[i] = out.GetOrigin(); + } + } + else + { + for( int i = 0; i < m_pStudioHeader->numbones; i++ ) + { + m_pModelInstance->m_pwpnbones[i].CopyToArray4x3( &m_pModelInstance->m_glweaponbones[i*3] ); + m_pModelInstance->m_weaponquat[i] = m_pModelInstance->m_pwpnbones[i].GetQuaternion(); + m_pModelInstance->m_weaponpos[i] = m_pModelInstance->m_pwpnbones[i].GetOrigin(); + } + } + + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( RI->currentmodel ); + } + + // add visible lights to vislight matrix + R_MarkVisibleLights( m_pModelInstance->lights ); + + // now this frame cached + m_pModelInstance->cached_frame = tr.realframecount; + } + + if(( m_iDrawModelType == DRAWSTUDIO_RUNEVENTS ) || r_drawentities->value == 2.0f || r_drawentities->value == 8.0f ) + return; + + model_t *pweaponmodel = NULL; + mbodypart_t *pbodyparts = NULL; + + if( update ) + { + if( RI->currententity->curstate.weaponmodel ) + pweaponmodel = IEngineStudio.GetModelByIndex( RI->currententity->curstate.weaponmodel ); + m_pModelInstance = NULL; + return; + } + + // change shared model with instanced model for this entity (it has personal vertex light cache) + if( m_pModelInstance->m_VlCache != NULL ) + pbodyparts = m_pModelInstance->m_VlCache->bodyparts; + + for( int i = 0 ; i < m_pStudioHeader->numbodyparts; i++ ) + AddBodyPartToDrawList( m_pStudioHeader, pbodyparts, i, ( RI->currentlight != NULL )); + + if( RI->currententity->curstate.weaponmodel ) + pweaponmodel = IEngineStudio.GetModelByIndex( RI->currententity->curstate.weaponmodel ); + + // don't draw p_model for firstperson legs + if( pweaponmodel && ( RI->currentmodel != m_pPlayerLegsModel )) + { + RI->currentmodel = pweaponmodel; + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( RI->currentmodel ); + + // add weaponmodel parts + for( int i = 0 ; i < m_pStudioHeader->numbodyparts; i++ ) + AddBodyPartToDrawList( m_pStudioHeader, NULL, i, ( RI->currentlight != NULL )); + } + + m_pModelInstance = NULL; +} + +/* +================= +RunViewModelEvents + +================= +*/ +void CStudioModelRenderer :: RunViewModelEvents( void ) +{ + if( !CVAR_TO_BOOL( m_pCvarDrawViewModel )) + return; + + // ignore in thirdperson, camera view or client is died + if( FBitSet( RI->params, RP_THIRDPERSON ) || CL_IsDead() || !UTIL_IsLocal( RI->view.entity )) + return; + + if( !RP_NORMALPASS( )) return; + + if( !IEngineStudio.Mod_Extradata( GET_VIEWMODEL()->model )) + return; + + RI->currententity = GET_VIEWMODEL(); + RI->currentmodel = RI->currententity->model; + if( !RI->currentmodel ) return; + + RI->currententity->curstate.renderamt = R_ComputeFxBlend( RI->currententity ); + + // tell the particle system about visibility + RI->currententity->curstate.messagenum = r_currentMessageNum; + + m_iDrawModelType = DRAWSTUDIO_RUNEVENTS; + + SET_CURRENT_ENTITY( RI->currententity ); + AddStudioModelToDrawList( RI->currententity ); + SET_CURRENT_ENTITY( NULL ); + + m_iDrawModelType = DRAWSTUDIO_NORMAL; + RI->currententity = NULL; + RI->currentmodel = NULL; +} + +bool CStudioModelRenderer :: ComputeCustomFov( matrix4x4 &projMatrix, matrix4x4 &worldViewProjMatrix ) +{ + float flDesiredFOV = 90.0f; + + // bound FOV values + if( m_pCvarViewmodelFov->value < 50 ) + gEngfuncs.Cvar_SetValue( "cl_viewmodel_fov", 50 ); + else if( m_pCvarViewmodelFov->value > 120 ) + gEngfuncs.Cvar_SetValue( "cl_viewmodel_fov", 120 ); + + if( m_pCvarHeadShieldFov->value < 50 ) + gEngfuncs.Cvar_SetValue( "cl_headshield_fov", 50 ); + else if( m_pCvarHeadShieldFov->value > 120 ) + gEngfuncs.Cvar_SetValue( "cl_headshield_fov", 120 ); + + // Find the offset our current FOV is from the default value + float flFOVOffset = 90.0f - (float)RI->view.fov_x; + + switch( m_iDrawModelType ) + { + case DRAWSTUDIO_VIEWMODEL: + // Adjust the viewmodel's FOV to move with any FOV offsets on the viewer's end + m_flViewmodelFov = flDesiredFOV = m_pCvarViewmodelFov->value - flFOVOffset; + break; + case DRAWSTUDIO_HEADSHIELD: + // Adjust the headshield's FOV to move with any FOV offsets on the viewer's end + flDesiredFOV = m_pCvarHeadShieldFov->value - flFOVOffset; + break; + default: // failed case, unchanged + flDesiredFOV = RI->view.fov_x; + break; + } + + // calc local FOV + float fov_x = flDesiredFOV; + float fov_y = V_CalcFov( fov_x, ScreenWidth, ScreenHeight ); + + // don't adjust FOV for viewmodel, faceprotect only + if( m_iDrawModelType == DRAWSTUDIO_HEADSHIELD && RENDER_GET_PARM( PARM_WIDESCREEN, 0 )) + V_AdjustFov( fov_x, fov_y, ScreenWidth, ScreenHeight, false ); + + if( fov_x != RI->view.fov_x ) + { + projMatrix = RI->view.projectionMatrix; + worldViewProjMatrix = RI->view.worldProjectionMatrix; + R_SetupProjectionMatrix( fov_x, fov_y, RI->view.projectionMatrix ); + RI->view.worldProjectionMatrix = RI->view.projectionMatrix.Concat( RI->view.worldMatrix ); + RI->view.worldProjectionMatrix.CopyToArray( RI->glstate.modelviewProjectionMatrix ); + RI->view.projectionMatrix.CopyToArray( RI->glstate.projectionMatrix ); + + pglMatrixMode( GL_PROJECTION ); + pglLoadMatrixf( RI->glstate.projectionMatrix ); + + return true; + } + + return false; +} + +void CStudioModelRenderer :: RestoreNormalFov( matrix4x4 &projMatrix, matrix4x4 &worldViewProjMatrix ) +{ + // restore original matrix + RI->view.projectionMatrix = projMatrix; + RI->view.worldProjectionMatrix = worldViewProjMatrix; + RI->view.worldProjectionMatrix.CopyToArray( RI->glstate.modelviewProjectionMatrix ); + RI->view.projectionMatrix.CopyToArray( RI->glstate.projectionMatrix ); + + pglMatrixMode( GL_PROJECTION ); + pglLoadMatrixf( RI->glstate.projectionMatrix ); +} + +/* +================= +DrawViewModel + +================= +*/ +void CStudioModelRenderer :: DrawViewModel( void ) +{ + cl_entity_t *view = GET_VIEWMODEL(); + + if( m_pCvarDrawViewModel->value == 0 ) + return; + + // ignore in thirdperson, camera view or client is died + if( FBitSet( RI->params, RP_THIRDPERSON ) || CL_IsDead() || !UTIL_IsLocal( RI->view.entity )) + return; + + if( !RP_NORMALPASS( )) return; + + // tell the particle system about visibility + view->curstate.messagenum = r_currentMessageNum; + + // hack the depth range to prevent view model from poking into walls + GL_DepthRange( gldepthmin, gldepthmin + 0.3f * ( gldepthmax - gldepthmin )); + + // backface culling for left-handed weapons + if( CVAR_TO_BOOL( m_pCvarHand )) + GL_FrontFace( !glState.frontFace ); + + m_iDrawModelType = DRAWSTUDIO_VIEWMODEL; + RI->frame.solid_meshes.Purge(); + RI->frame.trans_list.Purge(); + view->curstate.weaponmodel = 0; + + matrix4x4 projMatrix, worldViewProjMatrix; + + bool bCustom = ComputeCustomFov( projMatrix, worldViewProjMatrix ); + + // prevent ugly blinking when weapon is changed + view->model = MOD_HANDLE( gHUD.m_iViewModelIndex ); + + // copy viewhands modelindx into weaponmodel + if( view->index > 0 ) + { + cl_entity_t *ent = GET_ENTITY( view->index ); + view->curstate.weaponmodel = ent->curstate.iuser3; + if( ent->curstate.iuser4 == 3 ) + view->model = NULL; // Zoom + } + + // we can't draw head shield and viewmodel for once call + // because water blur separates them + AddStudioModelToDrawList( view ); + RenderSolidStudioList(); + R_RenderTransList(); + + if( bCustom ) RestoreNormalFov( projMatrix, worldViewProjMatrix ); + + // restore depth range + GL_DepthRange( gldepthmin, gldepthmax ); + + // backface culling for left-handed weapons + if( CVAR_TO_BOOL( m_pCvarHand )) + GL_FrontFace( !glState.frontFace ); + + m_iDrawModelType = DRAWSTUDIO_NORMAL; + GL_BindShader( NULL ); +} + +/* +================= +DrawHeadShield + +a some copy and paste +from viewmodel code +================= +*/ +void CStudioModelRenderer :: DrawHeadShield( void ) +{ + // g-cont. head shield must thinking always + if( !HeadShieldThink( )) return; + + // ignore in thirdperson, camera view or some special passes + if( FBitSet( RI->params, RP_THIRDPERSON ) || !UTIL_IsLocal( RI->view.entity )) + return; + + if( !RP_NORMALPASS( )) return; + + // load shield once only + if( !IEngineStudio.Mod_Extradata( gHUD.m_pHeadShieldEnt->model )) + return; + + RI->currententity = gHUD.m_pHeadShieldEnt; + RI->currentmodel = gHUD.m_pHeadShieldEnt->model; + if( !RI->currentmodel ) return; + + SET_CURRENT_ENTITY( RI->currententity ); + RI->currententity->curstate.renderamt = R_ComputeFxBlend( RI->currententity ); + + // g-cont. current offset for headshield is match with original paranoia code + gHUD.m_pHeadShieldEnt->origin = gHUD.m_pHeadShieldEnt->curstate.origin = GetVieworg() + GetVForward() * gHUD.m_pHeadShieldEnt->curstate.fuser1 + GetVUp() * 1.8f; + gHUD.m_pHeadShieldEnt->angles = gHUD.m_pHeadShieldEnt->curstate.angles = RI->view.angles; + + // rotate face to player + gHUD.m_pHeadShieldEnt->angles[PITCH] *= -1; + gHUD.m_pHeadShieldEnt->curstate.angles[PITCH] *= -1; + + m_iDrawModelType = DRAWSTUDIO_HEADSHIELD; + RI->frame.solid_meshes.Purge(); + RI->frame.trans_list.Purge(); + + // clearing depth buffer to draw headshield always on a top + // like nextView = 1 in original code + pglClear( GL_DEPTH_BUFFER_BIT ); + pglDisable( GL_DEPTH_TEST ); + + matrix4x4 projMatrix, worldViewProjMatrix; + + bool bCustom = ComputeCustomFov( projMatrix, worldViewProjMatrix ); + + // we can't draw head shield and viewmodel for once call + // because water blur separates them + AddStudioModelToDrawList( RI->currententity ); + RenderSolidStudioList(); + R_RenderTransList(); + + if( bCustom ) RestoreNormalFov( projMatrix, worldViewProjMatrix ); + + m_iDrawModelType = DRAWSTUDIO_NORMAL; + + pglEnable( GL_DEPTH_TEST ); + SET_CURRENT_ENTITY( NULL ); + RI->currententity = NULL; + RI->currentmodel = NULL; + GL_BindShader( NULL ); +} + +void CStudioModelRenderer :: DrawMeshFromBuffer( const vbomesh_t *mesh ) +{ + pglBindVertexArray( mesh->vao ); + + if( GL_Support( R_DRAW_RANGEELEMENTS_EXT )) + pglDrawRangeElementsEXT( GL_TRIANGLES, 0, mesh->numVerts - 1, mesh->numElems, GL_UNSIGNED_INT, 0 ); + else pglDrawElements( GL_TRIANGLES, mesh->numElems, GL_UNSIGNED_INT, 0 ); + + r_stats.c_total_tris += (mesh->numElems / 3); + r_stats.num_flushes++; +} + +void CStudioModelRenderer :: BuildMeshListForLight( CDynLight *pl, bool solid ) +{ + RI->frame.light_meshes.Purge(); + Vector bounds[2]; + + if( solid ) + { + // check solid meshes for light intersection + for( int i = 0; i < RI->frame.solid_meshes.Count(); i++ ) + { + CSolidEntry *entry = &RI->frame.solid_meshes[i]; + StudioGetBounds( entry, bounds ); + + if( pl->frustum.CullBox( bounds[0], bounds[1] )) + continue; // no interaction + + // setup the global pointers + if( !StudioSetEntity( entry )) + continue; + + if( RI->currententity->curstate.renderfx == SKYBOX_ENTITY ) + continue; // fast reject + + if( FBitSet( RI->currententity->curstate.effects, EF_FULLBRIGHT )) + continue; + + AddMeshToDrawList( m_pStudioHeader, entry->m_pMesh, true ); + } + } + else + { + // check trans meshes for light intersection + for( int i = 0; i < RI->frame.trans_list.Count(); i++ ) + { + CTransEntry *entry = &RI->frame.trans_list[i]; + + if( entry->m_bDrawType != DRAWTYPE_MESH ) + continue; + + StudioGetBounds( entry, bounds ); + if( pl->frustum.CullBox( bounds[0], bounds[1] )) + continue; // no interaction + + // setup the global pointers + if( !StudioSetEntity( entry )) + continue; + + if( RI->currententity->curstate.renderfx == SKYBOX_ENTITY ) + continue; // fast reject + + if( FBitSet( RI->currententity->curstate.effects, EF_FULLBRIGHT )) + continue; + + AddMeshToDrawList( m_pStudioHeader, entry->m_pMesh, true ); + } + } +} + +void CStudioModelRenderer :: DrawLightForMeshList( CDynLight *pl ) +{ + pglBlendFunc( GL_ONE, GL_ONE ); + float y2 = (float)RI->view.port[3] - pl->h - pl->y; + pglScissor( pl->x, y2, pl->w, pl->h ); + + // sorting list to reduce shader switches + if( !CVAR_TO_BOOL( cv_nosort )) + RI->frame.light_meshes.Sort( R_SortSolidMeshes ); + + pglAlphaFunc( GL_GEQUAL, 0.5f ); + RI->currententity = NULL; + RI->currentmodel = NULL; + m_pCurrentMaterial = NULL; + + for( int i = 0; i < RI->frame.light_meshes.Count(); i++ ) + { + CSolidEntry *entry = &RI->frame.light_meshes[i]; + DrawSingleMesh( entry, ( i == 0 )); + } + + GL_Cull( GL_FRONT ); +} + +void CStudioModelRenderer :: RenderDynLightList( bool solid ) +{ + if( FBitSet( RI->params, RP_ENVVIEW|RP_SKYVIEW )) + return; + + if( !FBitSet( RI->params, RP_HASDYNLIGHTS )) + return; + + if( R_FullBright( )) return; + + GL_Blend( GL_TRUE ); + GL_AlphaTest( GL_FALSE ); + GL_DepthMask( GL_FALSE ); + + CDynLight *pl = tr.dlights; + + for( int i = 0; i < MAX_DLIGHTS; i++, pl++ ) + { + if( pl->Expired( )) continue; + + if( pl->type == LIGHT_PROJECTION || pl->type == LIGHT_OMNI ) + { + if( !pl->Active( )) continue; + + if( !Mod_CheckBoxVisible( pl->absmin, pl->absmax )) + continue; + + if( R_CullFrustum( &pl->frustum )) + continue; + + pglEnable( GL_SCISSOR_TEST ); + } + else + { + // couldn't use scissor for sunlight + pglDisable( GL_SCISSOR_TEST ); + } + + RI->currentlight = pl; + + // draw world from light position + BuildMeshListForLight( pl, solid ); + + if( !RI->frame.light_meshes.Count( )) + continue; // no interaction with this light? + + DrawLightForMeshList( pl ); + } + + GL_SelectTexture( glConfig.max_texture_units - 1 ); // force to cleanup all the units + pglDisable( GL_SCISSOR_TEST ); + GL_CleanUpTextureUnits( 0 ); + RI->currentlight = NULL; +} + +void CStudioModelRenderer :: RenderDeferredStudioList( void ) +{ + if( !RI->frame.solid_meshes.Count() ) + return; + + pglAlphaFunc( GL_GEQUAL, 0.5f ); + GL_Blend( GL_FALSE ); + GL_AlphaTest( GL_FALSE ); + GL_DepthMask( GL_TRUE ); + + if( GL_Support( R_SEAMLESS_CUBEMAP )) + pglEnable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); + + // sorting list to reduce shader switches + if( !CVAR_TO_BOOL( cv_nosort )) + RI->frame.solid_meshes.Sort( R_SortSolidMeshes ); + + RI->currententity = NULL; + RI->currentmodel = NULL; + m_pCurrentMaterial = NULL; + + for( int i = 0; i < RI->frame.solid_meshes.Count(); i++ ) + { + CSolidEntry *entry = &RI->frame.solid_meshes[i]; + + if( entry->m_bDrawType != DRAWTYPE_MESH ) + continue; + + if( m_iDrawModelType == DRAWSTUDIO_NORMAL ) + { + if( entry->m_pParentEntity->curstate.renderfx == SKYBOX_ENTITY ) + { + GL_DepthRange( 0.8f, 0.9f ); + GL_ClipPlane( false ); + } + else + { + GL_DepthRange( gldepthmin, gldepthmax ); + GL_ClipPlane( true ); + } + } + + DrawSingleMesh( entry, ( i == 0 )); + } + + if( GL_Support( R_SEAMLESS_CUBEMAP )) + pglDisable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); + + if( m_iDrawModelType == DRAWSTUDIO_NORMAL ) + GL_DepthRange( gldepthmin, gldepthmax ); + GL_CleanupDrawState(); + GL_AlphaTest( GL_FALSE ); + GL_ClipPlane( true ); + GL_Cull( GL_FRONT ); + + // now draw studio decals + for( i = 0; i < RI->frame.solid_meshes.Count(); i++ ) + { +// DrawDecal( &RI->frame.solid_meshes[i] ); + } +} + +word CStudioModelRenderer :: ShaderSceneForward( mstudiomaterial_t *mat, lightinfo_t *light, bool vertex_lighting, bool bone_weighting, int numbones ) +{ + char glname[64]; + char options[MAX_OPTIONS_LENGTH]; + bool shader_translucent = false; + bool change_rendermode = false; + bool using_screenrect = false; + bool using_cubemaps = false; + + if( mat->lastRenderMode != RI->currententity->curstate.rendermode ) + change_rendermode = true; + + if( mat->forwardScene.IsValid() && mat->lightstatus == light->status && !change_rendermode ) + return mat->forwardScene.GetHandle(); // valid + + Q_strncpy( glname, "forward/scene_studio", sizeof( glname )); + memset( options, 0, sizeof( options )); + + if( numbones == 1 ) + GL_AddShaderDirective( options, "MAXSTUDIOBONES 1" ); + + if( bone_weighting ) + GL_AddShaderDirective( options, "APPLY_BONE_WEIGHTING" ); + + if( FBitSet( mat->flags, STUDIO_NF_CHROME )) + GL_AddShaderDirective( options, "HAS_CHROME" ); + + GL_CheckTextureAlpha( options, mat->gl_diffuse_id ); + + if( RI->currententity->curstate.rendermode == kRenderTransAdd || RI->currententity->curstate.rendermode == kRenderGlow ) + { + // additive and glow is always fullbright + GL_AddShaderDirective( options, "LIGHTING_FULLBRIGHT" ); + + if( RI->currententity->curstate.rendermode == kRenderTransAdd ) + shader_translucent = true; + } + else if( FBitSet( mat->flags, STUDIO_NF_FULLBRIGHT ) || R_FullBright() || FBitSet( RI->currententity->curstate.effects, EF_FULLBRIGHT )) + { + GL_AddShaderDirective( options, "LIGHTING_FULLBRIGHT" ); + } + else + { + if( CVAR_TO_BOOL( cv_brdf )) + GL_AddShaderDirective( options, "APPLY_PBS" ); + + if( vertex_lighting ) + GL_AddShaderDirective( options, "VERTEX_LIGHTING" ); + else if( light->data.numSamples > 1 ) + GL_AddShaderDirective( options, "LIGHTING_MULTISAMPLE" ); + else if( FBitSet( mat->flags, STUDIO_NF_FLATSHADE )) + GL_AddShaderDirective( options, "LIGHTING_FLATSHADE" ); + + // debug visualization + if( r_lightmap->value > 0.0f && r_lightmap->value <= 2.0f ) + { + if( r_lightmap->value == 1.0f && worldmodel->lightdata ) + GL_AddShaderDirective( options, "LIGHTMAP_DEBUG" ); + else if( r_lightmap->value == 2.0f && FBitSet( world->features, WORLD_HAS_DELUXEMAP )) + GL_AddShaderDirective( options, "LIGHTVEC_DEBUG" ); + } + + // deluxemap required + if( CVAR_TO_BOOL( cv_bump ) && FBitSet( light->status, LIGHTINFO_DIRECTION ) && FBitSet( mat->flags, STUDIO_NF_NORMALMAP )) + { + GL_AddShaderDirective( options, "HAS_NORMALMAP" ); + GL_EncodeNormal( options, mat->gl_normalmap_id ); + GL_AddShaderDirective( options, "COMPUTE_TBN" ); + } + + // deluxemap required + if( CVAR_TO_BOOL( cv_specular ) && FBitSet( light->status, LIGHTINFO_DIRECTION ) && FBitSet( mat->flags, STUDIO_NF_GLOSSMAP )) + GL_AddShaderDirective( options, "HAS_GLOSSMAP" ); + + if( FBitSet( mat->flags, STUDIO_NF_LUMA )) + GL_AddShaderDirective( options, "HAS_LUMA" ); + } + + if( mat->aberrationScale > 0.0f && Q_stristr( options, "HAS_NORMALMAP" )) + GL_AddShaderDirective( options, "APPLY_ABERRATION" ); + + if( mat->refractScale > 0.0f && Q_stristr( options, "HAS_NORMALMAP" )) + GL_AddShaderDirective( options, "APPLY_REFRACTION" ); + + if(( world->num_cubemaps > 0 ) && CVAR_TO_BOOL( cv_cubemaps ) && (mat->reflectScale > 0.0f) && !FBitSet( RI->params, RP_ENVVIEW )) + { + GL_AddShaderDirective( options, "REFLECTION_CUBEMAP" ); + using_cubemaps = true; + } + + if( tr.fogEnabled ) + GL_AddShaderDirective( options, "APPLY_FOG_EXP" ); + + // mixed mode: solid & transparent controlled by alpha-channel + if( FBitSet( mat->flags, STUDIO_NF_ADDITIVE ) && RI->currententity->curstate.rendermode != kRenderGlow ) + { + if( FBitSet( mat->flags, STUDIO_NF_HAS_ALPHA )) + GL_AddShaderDirective( options, "ALPHA_GLASS" ); + shader_translucent = true; + } + + if( RI->currententity->curstate.rendermode == kRenderTransColor || RI->currententity->curstate.rendermode == kRenderTransTexture ) + shader_translucent = true; + + if( shader_translucent ) + GL_AddShaderDirective( options, "TRANSLUCENT" ); + + if( FBitSet( mat->flags, STUDIO_NF_HAS_DETAIL ) && CVAR_TO_BOOL( r_detailtextures )) + GL_AddShaderDirective( options, "HAS_DETAIL" ); + + word shaderNum = GL_FindUberShader( glname, options ); + if( !shaderNum ) + { + SetBits( mat->flags, STUDIO_NF_NODRAW ); + return 0; // something bad happens + } + + if( shader_translucent ) + GL_AddShaderFeature( shaderNum, SHADER_TRANSLUCENT|SHADER_USE_SCREENCOPY ); + + if( using_cubemaps ) + GL_AddShaderFeature( shaderNum, SHADER_USE_CUBEMAPS ); + + // done + mat->lastRenderMode = RI->currententity->curstate.rendermode; + ClearBits( mat->flags, STUDIO_NF_NODRAW ); + mat->forwardScene.SetShader( shaderNum ); + mat->lightstatus = light->status; + + return shaderNum; +} + +word CStudioModelRenderer :: ShaderLightForward( CDynLight *dl, mstudiomaterial_t *mat, bool bone_weighting, int numbones ) +{ + char glname[64]; + char options[MAX_OPTIONS_LENGTH]; + + switch( dl->type ) + { + case LIGHT_PROJECTION: + if( mat->forwardLightSpot.IsValid( )) + return mat->forwardLightSpot.GetHandle(); // valid + break; + case LIGHT_OMNI: + if( mat->forwardLightOmni.IsValid( )) + return mat->forwardLightOmni.GetHandle(); // valid + break; + case LIGHT_DIRECTIONAL: + if( mat->forwardLightProj.IsValid( )) + return mat->forwardLightProj.GetHandle(); // valid + break; + } + + Q_strncpy( glname, "forward/light_studio", sizeof( glname )); + memset( options, 0, sizeof( options )); + + switch( dl->type ) + { + case LIGHT_PROJECTION: + GL_AddShaderDirective( options, "LIGHT_SPOT" ); + break; + case LIGHT_OMNI: + GL_AddShaderDirective( options, "LIGHT_OMNI" ); + break; + case LIGHT_DIRECTIONAL: + GL_AddShaderDirective( options, "LIGHT_PROJ" ); + break; + } + + if( numbones == 1 ) + GL_AddShaderDirective( options, "MAXSTUDIOBONES 1" ); + + if( bone_weighting ) + GL_AddShaderDirective( options, "APPLY_BONE_WEIGHTING" ); + + if( FBitSet( mat->flags, STUDIO_NF_CHROME )) + GL_AddShaderDirective( options, "HAS_CHROME" ); + + if( CVAR_TO_BOOL( cv_brdf )) + GL_AddShaderDirective( options, "APPLY_PBS" ); + + if( FBitSet( mat->flags, STUDIO_NF_HAS_DETAIL ) && CVAR_TO_BOOL( r_detailtextures )) + GL_AddShaderDirective( options, "HAS_DETAIL" ); + + GL_CheckTextureAlpha( options, mat->gl_diffuse_id ); + + if( CVAR_TO_BOOL( cv_bump ) && FBitSet( mat->flags, STUDIO_NF_NORMALMAP ) && !FBitSet( dl->flags, DLF_NOBUMP )) + { + GL_AddShaderDirective( options, "HAS_NORMALMAP" ); + GL_EncodeNormal( options, mat->gl_normalmap_id ); + GL_AddShaderDirective( options, "COMPUTE_TBN" ); + } + + if( CVAR_TO_BOOL( cv_specular ) && FBitSet( mat->flags, STUDIO_NF_GLOSSMAP )) + GL_AddShaderDirective( options, "HAS_GLOSSMAP" ); + + if( CVAR_TO_BOOL( r_shadows ) && !FBitSet( dl->flags, DLF_NOSHADOWS )) + { + // shadow cubemaps only support if GL_EXT_gpu_shader4 is support + if( dl->type == LIGHT_DIRECTIONAL && CVAR_TO_BOOL( r_sunshadows )) + { + GL_AddShaderDirective( options, "APPLY_SHADOW" ); + } + else if( dl->type == LIGHT_PROJECTION || GL_Support( R_EXT_GPU_SHADER4 )) + { + GL_AddShaderDirective( options, "APPLY_SHADOW" ); + + if( r_shadows->value == 2.0f ) + GL_AddShaderDirective( options, "SHADOW_PCF2X2" ); + else if( r_shadows->value >= 3.0f ) + GL_AddShaderDirective( options, "SHADOW_PCF3X3" ); + } + } + + word shaderNum = GL_FindUberShader( glname, options ); + + if( !shaderNum ) + { + if( dl->type == LIGHT_DIRECTIONAL ) + SetBits( mat->flags, STUDIO_NF_NOSUNLIGHT ); + else SetBits( mat->flags, STUDIO_NF_NODLIGHT ); + + return 0; // something bad happens + } + + // done + switch( dl->type ) + { + case LIGHT_PROJECTION: + mat->forwardLightSpot.SetShader( shaderNum ); + ClearBits( mat->flags, STUDIO_NF_NODLIGHT ); + break; + case LIGHT_OMNI: + mat->forwardLightOmni.SetShader( shaderNum ); + ClearBits( mat->flags, STUDIO_NF_NODLIGHT ); + break; + case LIGHT_DIRECTIONAL: + mat->forwardLightProj.SetShader( shaderNum ); + ClearBits( mat->flags, STUDIO_NF_NOSUNLIGHT ); + break; + } + + return shaderNum; +} + +word CStudioModelRenderer :: ShaderSceneDeferred( mstudiomaterial_t *mat, bool bone_weighting, int numbones ) +{ + char glname[64]; + char options[MAX_OPTIONS_LENGTH]; + bool using_cubemaps = false; + + if( mat->deferredScene.IsValid( )) + return mat->deferredScene.GetHandle(); // valid + + Q_strncpy( glname, "deferred/scene_studio", sizeof( glname )); + memset( options, 0, sizeof( options )); + + if( numbones == 1 ) + GL_AddShaderDirective( options, "MAXSTUDIOBONES 1" ); + + if( bone_weighting ) + GL_AddShaderDirective( options, "APPLY_BONE_WEIGHTING" ); + + if( FBitSet( mat->flags, STUDIO_NF_CHROME )) + GL_AddShaderDirective( options, "HAS_CHROME" ); + + if( FBitSet( mat->flags, STUDIO_NF_FULLBRIGHT ) || R_FullBright( )) + GL_AddShaderDirective( options, "LIGHTING_FULLBRIGHT" ); + + if( FBitSet( mat->flags, STUDIO_NF_FLATSHADE )) + GL_AddShaderDirective( options, "LIGHTING_FLATSHADE" ); + + if( FBitSet( mat->flags, STUDIO_NF_LUMA )) + GL_AddShaderDirective( options, "HAS_LUMA" ); + + if( FBitSet( mat->flags, STUDIO_NF_HAS_DETAIL ) && CVAR_TO_BOOL( r_detailtextures )) + GL_AddShaderDirective( options, "HAS_DETAIL" ); + + if( FBitSet( mat->flags, STUDIO_NF_NORMALMAP ) && CVAR_TO_BOOL( cv_bump )) + { + GL_AddShaderDirective( options, "HAS_NORMALMAP" ); + GL_EncodeNormal( options, mat->gl_normalmap_id ); + GL_AddShaderDirective( options, "COMPUTE_TBN" ); + } + + if( CVAR_TO_BOOL( cv_specular ) && FBitSet( mat->flags, STUDIO_NF_GLOSSMAP )) + GL_AddShaderDirective( options, "HAS_GLOSSMAP" ); + + if(( world->num_cubemaps > 0 ) && CVAR_TO_BOOL( cv_cubemaps ) && (mat->reflectScale > 0.0f) && !FBitSet( RI->params, RP_ENVVIEW )) + { + GL_AddShaderDirective( options, "REFLECTION_CUBEMAP" ); + using_cubemaps = true; + } + + word shaderNum = GL_FindUberShader( glname, options ); + if( !shaderNum ) + { + SetBits( mat->flags, STUDIO_NF_NODRAW ); + return 0; // something bad happens + } + + if( using_cubemaps ) + GL_AddShaderFeature( shaderNum, SHADER_USE_CUBEMAPS ); + + // done + ClearBits( mat->flags, STUDIO_NF_NODRAW ); + mat->deferredScene.SetShader( shaderNum ); + + return shaderNum; +} + +word CStudioModelRenderer :: ShaderLightDeferred( mstudiomaterial_t *mat, bool bone_weighting, int numbones ) +{ + char glname[64]; + char options[MAX_OPTIONS_LENGTH]; + + if( mat->deferredLight.IsValid( )) + return mat->deferredLight.GetHandle(); // valid + + Q_strncpy( glname, "deferred/light_studio", sizeof( glname )); + memset( options, 0, sizeof( options )); + + if( numbones == 1 ) + GL_AddShaderDirective( options, "MAXSTUDIOBONES 1" ); + + if( bone_weighting ) + GL_AddShaderDirective( options, "APPLY_BONE_WEIGHTING" ); + + if( FBitSet( mat->flags, STUDIO_NF_FULLBRIGHT ) || R_FullBright( )) + GL_AddShaderDirective( options, "LIGHTING_FULLBRIGHT" ); + + if( FBitSet( mat->flags, STUDIO_NF_FLATSHADE )) + GL_AddShaderDirective( options, "LIGHTING_FLATSHADE" ); + + if( FBitSet( mat->flags, STUDIO_NF_LUMA )) + GL_AddShaderDirective( options, "HAS_LUMA" ); + + word shaderNum = GL_FindUberShader( glname, options ); + if( !shaderNum ) + { + SetBits( mat->flags, STUDIO_NF_NODRAW ); + return 0; // something bad happens + } + + // done + ClearBits( mat->flags, STUDIO_NF_NODRAW ); + mat->deferredLight.SetShader( shaderNum ); + + return shaderNum; +} + +word CStudioModelRenderer :: ShaderSceneDepth( mstudiomaterial_t *mat, bool bone_weighting, int numbones ) +{ + char glname[64]; + char options[MAX_OPTIONS_LENGTH]; + + if( mat->forwardDepth.IsValid( )) + return mat->forwardDepth.GetHandle(); // valid + + Q_strncpy( glname, "forward/depth_studio", sizeof( glname )); + memset( options, 0, sizeof( options )); + + if( numbones == 1 ) + GL_AddShaderDirective( options, "MAXSTUDIOBONES 1" ); + + if( bone_weighting ) + GL_AddShaderDirective( options, "APPLY_BONE_WEIGHTING" ); + + word shaderNum = GL_FindUberShader( glname, options ); + if( !shaderNum ) return 0; // something bad happens + + mat->forwardDepth.SetShader( shaderNum ); + + return shaderNum; +} + +void CStudioModelRenderer :: DrawSingleMesh( CSolidEntry *entry, bool force ) +{ + bool cache_has_changed = false; + Vector4D lightstyles, lightdir; + bool weapon_model = false; + float r, g, b, a, *v; + int map; + + if( entry->m_bDrawType != DRAWTYPE_MESH ) + return; + + if( RI->currentmodel != entry->m_pRenderModel ) + cache_has_changed = true; + + if( RI->currententity != entry->m_pParentEntity ) + cache_has_changed = true; + + RI->currentmodel = entry->m_pRenderModel; + cl_entity_t *e = RI->currententity = entry->m_pParentEntity; + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( RI->currentmodel ); + int m_skinnum = bound( 0, RI->currententity->curstate.skin, MAXSTUDIOSKINS - 1 ); + + ASSERT( RI->currententity->modelhandle != INVALID_HANDLE ); + + ModelInstance_t *inst = m_pModelInstance = &m_ModelInstances[e->modelhandle]; + int num_bones = Q_min( m_pStudioHeader->numbones, glConfig.max_skinning_bones ); + vbomesh_t *pMesh = entry->m_pMesh; + mstudiomaterial_t *mat; + + short *pskinref = (short *)((byte *)m_pStudioHeader + m_pStudioHeader->skinindex); + if( m_skinnum != 0 && m_skinnum < m_pStudioHeader->numskinfamilies ) + pskinref += (m_skinnum * m_pStudioHeader->numskinref); + + if( FBitSet( RI->params, RP_SHADOWVIEW )) + mat = &RI->currentmodel->materials[pskinref[pMesh->skinref]]; + else mat = &m_pModelInstance->materials[pskinref[pMesh->skinref]]; // NOTE: use local copy for right cache shadernums + lightinfo_t *light = &inst->light; + + if( mat != m_pCurrentMaterial ) + cache_has_changed = true; + m_pCurrentMaterial = mat; + + if( FBitSet( RI->params, RP_DEFERREDLIGHT|RP_DEFERREDSCENE )) + { + if( FBitSet( RI->params, RP_DEFERREDSCENE )) + entry->m_hProgram = mat->deferredScene.GetHandle(); + else if( FBitSet( RI->params, RP_DEFERREDLIGHT )) + entry->m_hProgram = mat->deferredLight.GetHandle(); + else entry->m_hProgram = 0; + + if( !entry->m_hProgram ) return; + } + + if( force || ( RI->currentshader != &glsl_programs[entry->m_hProgram] )) + { + // force to bind new shader + GL_BindShader( &glsl_programs[entry->m_hProgram] ); + cache_has_changed = true; + } + + if( FBitSet( RI->params, RP_SHADOWVIEW|RP_DEFERREDSCENE )) + { + if( FBitSet( mat->flags, STUDIO_NF_MASKED )) + { + pglAlphaFunc( GL_GEQUAL, 0.5f ); + GL_AlphaTest( GL_TRUE ); + } + else if( FBitSet( mat->flags, STUDIO_NF_HAS_ALPHA )) + { + pglAlphaFunc( GL_GEQUAL, 0.999f ); + GL_AlphaTest( GL_TRUE ); + } + else GL_AlphaTest( GL_FALSE ); + } + else + { + if( FBitSet( mat->flags, STUDIO_NF_MASKED )) + GL_AlphaTest( GL_TRUE ); + else GL_AlphaTest( GL_FALSE ); + } + + if( !FBitSet( RI->params, RP_SHADOWVIEW )) + { + if( FBitSet( mat->flags, STUDIO_NF_TWOSIDE )) + GL_Cull( GL_NONE ); + else GL_Cull( GL_FRONT ); + } + + // each transparent surfaces reqiured an actual screencopy + if( ScreenCopyRequired( RI->currentshader ) && entry->IsTranslucent( )) + { + CTransEntry *trans = (CTransEntry *)entry; + + if( trans->m_bScissorReady ) + { + trans->RequestScreenColor(); + cache_has_changed = true; // force to refresh uniforms + r_stats.c_screen_copy++; + } + } + + glsl_program_t *shader = RI->currentshader; + CDynLight *pl = RI->currentlight; // may be NULL + + // sometime we can't set the uniforms + if( !cache_has_changed || !shader || !shader->numUniforms || !shader->uniforms ) + { + // just draw mesh and out + DrawMeshFromBuffer( pMesh ); + return; + } + + if( entry->m_pRenderModel == IEngineStudio.GetModelByIndex( e->curstate.weaponmodel )) + weapon_model = true; + + // setup specified uniforms (and texture bindings) + for( int i = 0; i < shader->numUniforms; i++ ) + { + uniform_t *u = &shader->uniforms[i]; + + switch( u->type ) + { + case UT_COLORMAP: + u->SetValue( mat->gl_diffuse_id ); + break; + case UT_NORMALMAP: + u->SetValue( mat->gl_normalmap_id ); + break; + case UT_GLOSSMAP: + u->SetValue( mat->gl_specular_id ); + break; + case UT_DETAILMAP: + u->SetValue( mat->gl_detailmap_id ); + break; + case UT_PROJECTMAP: + if( pl && pl->type == LIGHT_PROJECTION ) + u->SetValue( pl->spotlightTexture ); + else u->SetValue( tr.whiteTexture ); + break; + case UT_SHADOWMAP: + case UT_SHADOWMAP0: + if( pl ) u->SetValue( pl->shadowTexture[0] ); + else u->SetValue( tr.depthTexture ); + break; + case UT_SHADOWMAP1: + if( pl ) u->SetValue( pl->shadowTexture[1] ); + else u->SetValue( tr.depthTexture ); + break; + case UT_SHADOWMAP2: + if( pl ) u->SetValue( pl->shadowTexture[2] ); + else u->SetValue( tr.depthTexture ); + break; + case UT_SHADOWMAP3: + if( pl ) u->SetValue( pl->shadowTexture[3] ); + else u->SetValue( tr.depthTexture ); + break; + case UT_LIGHTMAP: + case UT_DELUXEMAP: + case UT_DECALMAP: + // unacceptable for studiomodels + u->SetValue( tr.whiteTexture ); + break; + case UT_SCREENMAP: + u->SetValue( tr.screen_color ); + break; + case UT_DEPTHMAP: + u->SetValue( tr.screen_depth ); + break; + case UT_ENVMAP0: + case UT_ENVMAP: + if( inst->cubemap[0] != NULL ) + u->SetValue( inst->cubemap[0]->texture ); + else u->SetValue( tr.whiteCubeTexture ); + break; + case UT_ENVMAP1: + if( inst->cubemap[1] != NULL ) + u->SetValue( inst->cubemap[1]->texture ); + else u->SetValue( tr.whiteCubeTexture ); + break; + case UT_GLOWMAP: + u->SetValue( mat->gl_glowmap_id ); + break; + case UT_HEIGHTMAP: + u->SetValue( mat->gl_heightmap_id ); + break; + case UT_BSPPLANESMAP: + u->SetValue( tr.packed_planes_texture ); + break; + case UT_BSPNODESMAP: + u->SetValue( tr.packed_nodes_texture ); + break; + case UT_BSPLIGHTSMAP: + u->SetValue( tr.packed_lights_texture ); + break; + case UT_MODELMATRIX: + u->SetValue( &inst->m_glmatrix[0] ); + break; + case UT_BONESARRAY: + if( weapon_model ) + u->SetValue( &inst->m_glweaponbones[0][0], num_bones * 3 ); + else u->SetValue( &inst->m_glstudiobones[0][0], num_bones * 3 ); + break; + case UT_BONEQUATERNION: + if( weapon_model ) + u->SetValue( &inst->m_weaponquat[0][0], num_bones ); + else u->SetValue( &inst->m_studioquat[0][0], num_bones ); + break; + case UT_BONEPOSITION: + if( weapon_model ) + u->SetValue( &inst->m_weaponpos[0][0], num_bones ); + else u->SetValue( &inst->m_studiopos[0][0], num_bones ); + break; + case UT_SCREENSIZEINV: + u->SetValue( 1.0f / (float)glState.width, 1.0f / (float)glState.height ); + break; + case UT_ZFAR: + u->SetValue( -tr.farclip * 1.74f ); + break; + case UT_LIGHTSTYLES: + for( map = 0; map < MAXLIGHTMAPS; map++ ) + { + if( inst->styles[map] != 255 ) + lightstyles[map] = tr.lightstyle[inst->styles[map]]; + else lightstyles[map] = 0.0f; + } + u->SetValue( lightstyles.x, lightstyles.y, lightstyles.z, lightstyles.w ); + break; + case UT_REALTIME: + u->SetValue( (float)tr.time ); + break; + case UT_DETAILSCALE: + u->SetValue( mat->detailScale[0], mat->detailScale[1] ); + break; + case UT_FOGPARAMS: + u->SetValue( tr.fogColor[0], tr.fogColor[1], tr.fogColor[2], tr.fogDensity ); + break; + case UT_SHADOWPARMS: + if( pl != NULL ) + { + float shadowWidth = 1.0f / (float)RENDER_GET_PARM( PARM_TEX_WIDTH, pl->shadowTexture[0] ); + float shadowHeight = 1.0f / (float)RENDER_GET_PARM( PARM_TEX_HEIGHT, pl->shadowTexture[0] ); + // depth scale and bias and shadowmap resolution + u->SetValue( shadowWidth, shadowHeight, -pl->projectionMatrix[2][2], pl->projectionMatrix[3][2] ); + } + else u->SetValue( 0.0f, 0.0f, 0.0f, 0.0f ); + break; + case UT_TEXOFFSET: // FIXME: implement conveyors? + u->SetValue( 0.0f, 0.0f ); + break; + case UT_VIEWORIGIN: + u->SetValue( GetVieworg().x, GetVieworg().y, GetVieworg().z ); + break; + case UT_VIEWRIGHT: + u->SetValue( GetVRight().x, GetVRight().y, GetVRight().z ); + break; + case UT_RENDERCOLOR: + if( e->curstate.rendermode == kRenderNormal ) + { + r = g = b = a = 1.0f; + } + else + { + int sum = (e->curstate.rendercolor.r + e->curstate.rendercolor.g + e->curstate.rendercolor.b); + + if( sum > 0 ) + { + r = e->curstate.rendercolor.r / 255.0f; + g = e->curstate.rendercolor.g / 255.0f; + b = e->curstate.rendercolor.b / 255.0f; + } + else + { + r = g = b = 1.0f; + } + + if( e->curstate.rendermode != kRenderTransAlpha ) + a = e->curstate.renderamt / 255.0f; + else a = 1.0f; + } + + if( FBitSet( mat->flags, STUDIO_NF_ADDITIVE )) + a = 0.65f; // FIXME: 0.5 looks ugly + u->SetValue( r, g, b, a ); + break; + case UT_SMOOTHNESS: + u->SetValue( mat->smoothness ); + break; + case UT_SHADOWMATRIX: + if( pl ) u->SetValue( &pl->gl_shadowMatrix[0][0], MAX_SHADOWMAPS ); + break; + case UT_SHADOWSPLITDIST: + v = RI->view.parallelSplitDistances; + u->SetValue( v[0], v[1], v[2], v[3] ); + break; + case UT_TEXELSIZE: + u->SetValue( 1.0f / (float)sunSize[0], 1.0f / (float)sunSize[1], 1.0f / (float)sunSize[2], 1.0f / (float)sunSize[3] ); + break; + case UT_GAMMATABLE: + u->SetValue( &tr.gamma_table[0][0], 64 ); + break; + case UT_LIGHTDIR: + if( pl ) + { + if( pl->type == LIGHT_DIRECTIONAL ) lightdir = -tr.sky_normal; + else lightdir = pl->frustum.GetPlane( FRUSTUM_FAR )->normal; + u->SetValue( lightdir.x, lightdir.y, lightdir.z, pl->fov ); + } + else u->SetValue( &light->data.normal[0][0], light->data.numSamples ); + break; + case UT_LIGHTDIFFUSE: + if( pl ) u->SetValue( pl->color.x, pl->color.y, pl->color.z ); + else u->SetValue( &light->data.diffuse[0][0], light->data.numSamples ); + break; + case UT_LIGHTSAMPLES: + u->SetValue( light->data.numSamples ); + break; + case UT_LIGHTORIGIN: + if( pl ) u->SetValue( pl->origin.x, pl->origin.y, pl->origin.z, ( 1.0f / pl->radius )); + else u->SetValue( &light->data.origin[0][0], light->data.numSamples ); + break; + case UT_LIGHTVIEWPROJMATRIX: + if( pl ) + { + GLfloat gl_lightViewProjMatrix[16]; + pl->lightviewProjMatrix.CopyToArray( gl_lightViewProjMatrix ); + u->SetValue( &gl_lightViewProjMatrix[0] ); + } + break; + case UT_DIFFUSEFACTOR: + u->SetValue( tr.diffuseFactor ); + break; + case UT_AMBIENTFACTOR: + if( pl && pl->type == LIGHT_DIRECTIONAL ) + u->SetValue( tr.sun_ambient ); + else u->SetValue( tr.ambientFactor ); + break; + case UT_SUNREFRACT: + u->SetValue( tr.sun_refract ); + break; + case UT_AMBIENTCUBE: + u->SetValue( &light->ambient[0][0], 6 ); + break; + case UT_LERPFACTOR: + u->SetValue( inst->lerpFactor ); + break; + case UT_REFRACTSCALE: + u->SetValue( bound( 0.0f, mat->refractScale, 1.0f )); + break; + case UT_REFLECTSCALE: + u->SetValue( bound( 0.0f, mat->reflectScale, 1.0f )); + break; + case UT_ABERRATIONSCALE: + u->SetValue( bound( 0.0f, mat->aberrationScale, 1.0f )); + break; + case UT_BOXMINS: + if( world->num_cubemaps > 0 ) + { + Vector mins[2]; + mins[0] = inst->cubemap[0]->mins; + mins[1] = inst->cubemap[1]->mins; + u->SetValue( &mins[0][0], 2 ); + } + break; + case UT_BOXMAXS: + if( world->num_cubemaps > 0 ) + { + Vector maxs[2]; + maxs[0] = inst->cubemap[0]->maxs; + maxs[1] = inst->cubemap[1]->maxs; + u->SetValue( &maxs[0][0], 2 ); + } + break; + case UT_CUBEORIGIN: + if( world->num_cubemaps > 0 ) + { + Vector origin[2]; + origin[0] = inst->cubemap[0]->origin; + origin[1] = inst->cubemap[1]->origin; + u->SetValue( &origin[0][0], 2 ); + } + break; + case UT_CUBEMIPCOUNT: + if( world->num_cubemaps > 0 ) + { + r = Q_max( 1, inst->cubemap[0]->numMips - cv_cube_lod_bias->value ); + g = Q_max( 1, inst->cubemap[1]->numMips - cv_cube_lod_bias->value ); + u->SetValue( r, g ); + } + break; + case UT_LIGHTNUMS0: + u->SetValue( (float)inst->lights[0], (float)inst->lights[1], (float)inst->lights[2], (float)inst->lights[3] ); + break; + case UT_LIGHTNUMS1: + u->SetValue( (float)inst->lights[4], (float)inst->lights[5], (float)inst->lights[6], (float)inst->lights[7] ); + break; + default: + ALERT( at_error, "%s: unhandled uniform %s\n", RI->currentshader->name, u->name ); + break; + } + } + + DrawMeshFromBuffer( pMesh ); +} + +void CStudioModelRenderer :: RenderSolidStudioList( void ) +{ + if( !RI->frame.solid_meshes.Count() ) + return; + + pglAlphaFunc( GL_GEQUAL, 0.5f ); + GL_Blend( GL_FALSE ); + GL_AlphaTest( GL_FALSE ); + GL_DepthMask( GL_TRUE ); + + if( GL_Support( R_SEAMLESS_CUBEMAP )) + pglEnable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); + + // sorting list to reduce shader switches + if( !CVAR_TO_BOOL( cv_nosort )) + RI->frame.solid_meshes.Sort( R_SortSolidMeshes ); + + RI->currententity = NULL; + RI->currentmodel = NULL; + m_pCurrentMaterial = NULL; + + for( int i = 0; i < RI->frame.solid_meshes.Count(); i++ ) + { + CSolidEntry *entry = &RI->frame.solid_meshes[i]; + + if( entry->m_bDrawType != DRAWTYPE_MESH ) + continue; + + if( m_iDrawModelType == DRAWSTUDIO_NORMAL ) + { + if( entry->m_pParentEntity->curstate.renderfx == SKYBOX_ENTITY ) + { + GL_DepthRange( 0.8f, 0.9f ); + GL_ClipPlane( false ); + } + else + { + GL_DepthRange( gldepthmin, gldepthmax ); + GL_ClipPlane( true ); + } + } + + DrawSingleMesh( entry, ( i == 0 )); + } + + if( GL_Support( R_SEAMLESS_CUBEMAP )) + pglDisable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); + + if( m_iDrawModelType == DRAWSTUDIO_NORMAL ) + GL_DepthRange( gldepthmin, gldepthmax ); + GL_AlphaTest( GL_FALSE ); + GL_ClipPlane( true ); + GL_Cull( GL_FRONT ); + + RenderDynLightList( true ); + + GL_CleanupDrawState(); + + // now draw studio decals + for( i = 0; i < RI->frame.solid_meshes.Count(); i++ ) + { + DrawDecal( &RI->frame.solid_meshes[i] ); + } +} + +void CStudioModelRenderer :: RenderTransMesh( CTransEntry *entry ) +{ + if( entry->m_bDrawType != DRAWTYPE_MESH ) + return; + + pglAlphaFunc( GL_GEQUAL, 0.5f ); + GL_DepthMask( GL_TRUE ); + + if( m_iDrawModelType == DRAWSTUDIO_NORMAL ) + { + if( entry->m_pParentEntity->curstate.renderfx == SKYBOX_ENTITY ) + { + GL_DepthRange( 0.8f, 0.9f ); + GL_ClipPlane( false ); + } + else + { + GL_DepthRange( gldepthmin, gldepthmax ); + GL_ClipPlane( true ); + } + } + + if( entry->m_pParentEntity->curstate.rendermode == kRenderGlow ) + { + pglBlendFunc( GL_ONE, GL_ONE ); + pglDisable( GL_DEPTH_TEST ); + GL_Blend( GL_TRUE ); + } + else + { + pglEnable( GL_DEPTH_TEST ); + GL_Blend( GL_FALSE ); + } + + DrawSingleMesh( entry, true ); + + // trans meshes reqiured draw decals separately + DrawDecal( entry ); + + pglEnable( GL_DEPTH_TEST ); + GL_Blend( GL_FALSE ); + GL_ClipPlane( true ); +} + +void CStudioModelRenderer :: RenderTransStudioList( void ) +{ + if( !RI->frame.trans_list.Count() ) + return; + + pglAlphaFunc( GL_GEQUAL, 0.5f ); + GL_Blend( GL_FALSE ); // mixing screencopy with diffuse so we don't need blend + GL_AlphaTest( GL_FALSE ); + GL_DepthMask( GL_TRUE ); + + if( GL_Support( R_SEAMLESS_CUBEMAP )) + pglEnable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); + + RI->currententity = NULL; + RI->currentmodel = NULL; + m_pCurrentMaterial = NULL; + + for( int i = 0; i < RI->frame.trans_list.Count(); i++ ) + { + CTransEntry *entry = &RI->frame.trans_list[i]; + + if( entry->m_bDrawType != DRAWTYPE_MESH ) + continue; + + if( m_iDrawModelType == DRAWSTUDIO_NORMAL ) + { + if( entry->m_pParentEntity->curstate.renderfx == SKYBOX_ENTITY ) + { + GL_DepthRange( 0.8f, 0.9f ); + GL_ClipPlane( false ); + } + else + { + GL_DepthRange( gldepthmin, gldepthmax ); + GL_ClipPlane( true ); + } + } + + if( entry->m_pParentEntity->curstate.rendermode == kRenderGlow ) + { + pglBlendFunc( GL_ONE, GL_ONE ); + pglDisable( GL_DEPTH_TEST ); + GL_Blend( GL_TRUE ); + } + else + { + pglEnable( GL_DEPTH_TEST ); + GL_Blend( GL_FALSE ); + } + + DrawSingleMesh( entry, ( i == 0 )); + + // trans meshes reqiured draw decals separately + DrawDecal( entry ); + } + + if( GL_Support( R_SEAMLESS_CUBEMAP )) + pglDisable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); + + if( m_iDrawModelType == DRAWSTUDIO_NORMAL ) + GL_DepthRange( gldepthmin, gldepthmax ); + GL_Cull( GL_FRONT ); + + RenderDynLightList( false ); + + GL_CleanupDrawState(); + pglEnable( GL_DEPTH_TEST ); + GL_DepthMask( GL_TRUE ); + GL_ClipPlane( true ); +} + +void CStudioModelRenderer :: RenderShadowStudioList( void ) +{ + if( !RI->frame.solid_meshes.Count() ) + return; + + RI->currententity = NULL; + RI->currentmodel = NULL; + m_pCurrentMaterial = NULL; + + for( int i = 0; i < RI->frame.solid_meshes.Count(); i++ ) + { + CSolidEntry *entry = &RI->frame.solid_meshes[i]; + DrawSingleMesh( entry, ( i == 0 )); + } + + GL_AlphaTest( GL_FALSE ); + GL_CleanupDrawState(); +} + +void CStudioModelRenderer :: RenderDebugStudioList( bool bViewModel ) +{ + int i; + + if( r_drawentities->value <= 1.0f ) return; + + if( FBitSet( RI->params, RP_SHADOWVIEW|RP_ENVVIEW|RP_SKYVIEW )) + return; + + if( bViewModel ) + { + StudioDrawDebug( GET_VIEWMODEL( )); + StudioDrawDebug( gHUD.m_pHeadShieldEnt ); + } + else + { + // render debug lines + for( i = 0; i < tr.num_draw_entities; i++ ) + StudioDrawDebug( tr.draw_entities[i] ); + } +} + +void CStudioModelRenderer :: ClearLightCache( void ) +{ + // force to recalc static light again + for( int i = m_ModelInstances.Count(); --i >= 0; ) + { + ModelInstance_t *inst = &m_ModelInstances[i]; + inst->light.status &= ~LIGHTINFO_STATIC; + inst->light.status &= ~LIGHTINFO_GRID_INIT; + inst->light.center = g_vecZero; + } +} \ No newline at end of file diff --git a/cl_dll/render/gl_studio_draw.cpp b/cl_dll/render/gl_studio_draw.cpp new file mode 100644 index 0000000..362f6fc --- /dev/null +++ b/cl_dll/render/gl_studio_draw.cpp @@ -0,0 +1,3885 @@ +/* +gl_studio_draw.cpp - rendering studio models +Copyright (C) 2019 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include "cl_util.h" +#include "gl_local.h" +#include "com_model.h" +#include "r_studioint.h" +#include "pm_movevars.h" +#include "gl_studio.h" +#include "gl_sprite.h" +#include "event_api.h" +#include +#include "pm_defs.h" +#include "stringlib.h" +#include "triangleapi.h" +#include "entity_types.h" +#include "gl_shader.h" +#include "gl_world.h" + +#define LIGHT_INTERP_UPDATE 0.1f +#define LIGHT_INTERP_FACTOR (1.0f / LIGHT_INTERP_UPDATE) + +/* +================= +SortSolidMeshes + +sort by shaders to reduce state switches +================= +*/ +int CStudioModelRenderer :: SortSolidMeshes( const CSolidEntry *a, const CSolidEntry *b ) +{ + if( a->m_hProgram > b->m_hProgram ) + return -1; + if( a->m_hProgram < b->m_hProgram ) + return 1; + + return 0; +} + +/* +================ +HeadShieldThink + +process client-side animations +================ +*/ +int CStudioModelRenderer :: HeadShieldThink( void ) +{ + switch( gHUD.m_iHeadShieldState ) + { + case SHIELD_ON: + return 1; + case SHIELD_TURNING_ON: + if( tr.time > gHUD.m_flHeadShieldSwitchTime ) + { + gHUD.m_iHeadShieldState = SHIELD_ON; + gHUD.m_pHeadShieldEnt->curstate.animtime = tr.time; + gHUD.m_pHeadShieldEnt->curstate.sequence = SHIELDANIM_IDLE; + } + return 1; + case SHIELD_TURNING_OFF: + if( tr.time > gHUD.m_flHeadShieldSwitchTime ) + { + gHUD.m_iHeadShieldState = SHIELD_OFF; + return 0; + } + else + { + return 1; + } + case SHIELD_OFF: + return 0; + default: + ALERT( at_error, "HeadShield: invalid state %d\n", gHUD.m_iHeadShieldState ); + return 0; + } +} + +/* +================ +StudioExtractBbox + +Extract bbox from current sequence +================ +*/ +int CStudioModelRenderer :: StudioExtractBbox( studiohdr_t *phdr, int sequence, Vector &mins, Vector &maxs ) +{ + if( !phdr || sequence < 0 || sequence >= phdr->numseq ) + return 0; + + mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)phdr + phdr->seqindex) + sequence; + mins = pseqdesc->bbmin; + maxs = pseqdesc->bbmax; + + return 1; +} + +/* +================ +StudioGetBounds + +Get bounds for a current sequence +================ +*/ +int CStudioModelRenderer :: StudioGetBounds( cl_entity_t *e, Vector bounds[2] ) +{ + if( !e || e->modelhandle == INVALID_HANDLE ) + return 0; + + ModelInstance_t *inst = &m_ModelInstances[e->modelhandle]; + bounds[0] = inst->absmin; + bounds[1] = inst->absmax; + + return 1; +} + +/* +================ +StudioGetBounds + +Get bounds for a given mesh +================ +*/ +int CStudioModelRenderer :: StudioGetBounds( CSolidEntry *entry, Vector bounds[2] ) +{ + if( !entry || entry->m_bDrawType != DRAWTYPE_MESH ) + return 0; + + if( !entry->m_pParentEntity || entry->m_pParentEntity->modelhandle == INVALID_HANDLE ) + return 0; + + vbomesh_t *vbo = entry->m_pMesh; + + if( !vbo || vbo->parentbone == 0xFF ) + return 0; + + ModelInstance_t *inst = &m_ModelInstances[entry->m_pParentEntity->modelhandle]; + TransformAABB( inst->m_pbones[vbo->parentbone], vbo->mins, vbo->maxs, bounds[0], bounds[1] ); + + return 1; +} + +/* +================ +UpdateLatchedVars + +lerp custom values +================ +*/ +void CStudioModelRenderer :: UpdateLatchedVars( cl_entity_t *e, qboolean reset ) +{ + if( !g_fRenderInitialized ) return; + + if( !StudioSetEntity( e )) return; + + // store old values to right lerping from + m_pModelInstance->m_oldposeparams[0] = e->prevstate.vuser1[0]; + m_pModelInstance->m_oldposeparams[1] = e->prevstate.vuser1[1]; + m_pModelInstance->m_oldposeparams[2] = e->prevstate.vuser1[2]; + + m_pModelInstance->m_oldposeparams[3] = e->prevstate.vuser2[0]; + m_pModelInstance->m_oldposeparams[4] = e->prevstate.vuser2[1]; + m_pModelInstance->m_oldposeparams[5] = e->prevstate.vuser2[2]; + + m_pModelInstance->m_oldposeparams[6] = e->prevstate.vuser3[0]; + m_pModelInstance->m_oldposeparams[7] = e->prevstate.vuser3[1]; + m_pModelInstance->m_oldposeparams[8] = e->prevstate.vuser3[2]; + + m_pModelInstance->m_oldposeparams[ 9] = e->prevstate.vuser4[0]; + m_pModelInstance->m_oldposeparams[10] = e->prevstate.vuser4[1]; + m_pModelInstance->m_oldposeparams[11] = e->prevstate.vuser4[2]; +} + +/* +================ +StudioComputeBBox + +Compute a full bounding box for current sequence +================ +*/ +int CStudioModelRenderer :: StudioComputeBBox( void ) +{ + Vector p1, p2, scale = Vector( 1.0f, 1.0f, 1.0f ); + int sequence = RI->currententity->curstate.sequence; + Vector origin, mins, maxs; + + if( FBitSet( m_pModelInstance->info_flags, MF_STATIC_BOUNDS )) + return true; // bounds already computed + + if( !StudioExtractBbox( m_pStudioHeader, sequence, mins, maxs )) + return false; + + // FIXME: some problems in studiomdl compiler? + if( BoundsIsCleared( mins, maxs ) || BoundsIsNull( mins, maxs )) + { + mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + sequence; + ALERT( at_aiconsole, "%s: sequence: %s has invalid bbox\n", RI->currentmodel->name, pseqdesc->label ); + // some seqeunces can have invalid bbox size, so we need to start from default size + AddPointToBounds( RI->currentmodel->mins, mins, maxs ); + AddPointToBounds( RI->currentmodel->maxs, mins, maxs ); + } + + // prevent to compute env_static bounds every frame + if( FBitSet( RI->currententity->curstate.iuser1, CF_STATIC_ENTITY ) && RI->currententity->curstate.renderfx != SKYBOX_ENTITY ) + SetBits( m_pModelInstance->info_flags, MF_STATIC_BOUNDS ); + + if( FBitSet( RI->currententity->curstate.iuser1, CF_STATIC_ENTITY )) + { + if( RI->currententity->curstate.startpos != g_vecZero ) + scale = RI->currententity->curstate.startpos; + } + else if( RI->currententity->curstate.scale > 0.0f && RI->currententity->curstate.scale <= 16.0f ) + { + // apply studiomodel scale (clamp scale to prevent too big sizes on some HL maps) + scale = Vector( RI->currententity->curstate.scale ); + } + + Vector angles = RI->currententity->angles; + angles[PITCH] = -angles[PITCH]; // stupid quakebug + + // don't rotate player model, only aim + if( RI->currententity->player ) angles[PITCH] = 0; + + origin = StudioGetOrigin(); + + matrix3x4 transform = matrix3x4( g_vecZero, angles, scale ); + + // rotate and scale bbox for env_static + TransformAABB( transform, mins, maxs, mins, maxs ); + + if( m_pModelInstance->origin != origin ) + SetBits( m_pModelInstance->info_flags, MF_POSITION_CHANGED ); + + // compute abs box + m_pModelInstance->absmin = mins + origin; + m_pModelInstance->absmax = maxs + origin; + m_pModelInstance->radius = RadiusFromBounds( mins, maxs ); + m_pModelInstance->origin = origin; + + return true; +} + +/* +==================== +AddBlendSequence + +multiple blends +==================== +*/ +void CStudioModelRenderer :: AddBlendSequence( int oldseq, int newseq, float prevframe, bool gaitseq ) +{ + mstudioseqdesc_t *poldseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + oldseq; + mstudioseqdesc_t *pnewseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + newseq; + + // sequence has changed, hold the previous sequence info + if( oldseq != newseq && !FBitSet( pnewseqdesc->flags, STUDIO_SNAP )) + { + mstudioblendseq_t *pseqblending; + + // move current sequence into circular buffer + m_pModelInstance->m_current_seqblend = (m_pModelInstance->m_current_seqblend + 1) & MASK_SEQBLENDS; + + pseqblending = &m_pModelInstance->m_seqblend[m_pModelInstance->m_current_seqblend]; + + pseqblending->blendtime = tr.time; + pseqblending->sequence = oldseq; + pseqblending->cycle = prevframe / m_boneSetup.LocalMaxFrame( oldseq ); + pseqblending->gaitseq = gaitseq; + pseqblending->fadeout = Q_min( poldseqdesc->fadeouttime / 100.0f, pnewseqdesc->fadeintime / 100.0f ); + if( pseqblending->fadeout <= 0.0f ) + pseqblending->fadeout = 0.2f; // force to default + } +} + +/* +==================== +CalcStairSmoothValue + +smoothing stepsize height +==================== +*/ +float CStudioModelRenderer :: CalcStairSmoothValue( float oldz, float newz, float smoothtime, float smoothvalue ) +{ + if( oldz < newz ) + return bound( newz - tr.movevars->stepsize, oldz + smoothtime * smoothvalue, newz ); + if( oldz > newz ) + return bound( newz, oldz - smoothtime * smoothvalue, newz + tr.movevars->stepsize ); + return 0.0f; +} + +/* +==================== +StudioCheckLOD + +check special bodygroup +==================== +*/ +int CStudioModelRenderer :: StudioCheckLOD( void ) +{ + mstudiobodyparts_t *m_pBodyPart; + + for( int i = 0; i < m_pStudioHeader->numbodyparts; i++ ) + { + m_pBodyPart = (mstudiobodyparts_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bodypartindex) + i; + + if( !Q_stricmp( m_pBodyPart->name, "studioLOD" )) + return m_pBodyPart->nummodels; + } + + return 0; // no lod-levels for this model +} + +const Vector CStudioModelRenderer :: StudioGetOrigin( void ) +{ + Vector origin = RI->currententity->origin; + + if( RI->currententity->curstate.renderfx == SKYBOX_ENTITY ) + { + // calc skybox origin + Vector trans = GetVieworg() - tr.sky_origin; + if( tr.sky_speed ) trans -= (GetVieworg() - tr.sky_world_origin) / tr.sky_speed; + origin += trans; + } + + return origin; +} + +/* +==================== +StudioSetUpTransform + +==================== +*/ +void CStudioModelRenderer :: StudioSetUpTransform( void ) +{ + cl_entity_t *e = RI->currententity; + bool disable_smooth = false; + float step, smoothtime; + int numLods; + + if( !m_fShootDecal && RI->currententity->curstate.renderfx != kRenderFxDeadPlayer ) + { + if( m_iDrawModelType == DRAWSTUDIO_HEADSHIELD || m_iDrawModelType == DRAWSTUDIO_RUNEVENTS ) + e = gEngfuncs.GetLocalPlayer(); + + // calculate how much time has passed since the last V_CalcRefdef + smoothtime = bound( 0.0f, tr.time - m_pModelInstance->lerp.stairtime, 0.1f ); + m_pModelInstance->lerp.stairtime = tr.time; + + if( e->curstate.onground == -1 || FBitSet( e->curstate.effects, EF_NOINTERP )) + disable_smooth = true; + + if( e->curstate.movetype != MOVETYPE_STEP && e->curstate.movetype != MOVETYPE_WALK ) + disable_smooth = true; + + if( !FBitSet( m_pModelInstance->info_flags, MF_INIT_SMOOTHSTAIRS )) + { + SetBits( m_pModelInstance->info_flags, MF_INIT_SMOOTHSTAIRS ); + disable_smooth = true; + } + + if( disable_smooth ) + { + m_pModelInstance->lerp.stairoldz = e->origin[2]; + } + else + { + step = CalcStairSmoothValue( m_pModelInstance->lerp.stairoldz, e->origin[2], smoothtime, STAIR_INTERP_TIME ); + if( step ) m_pModelInstance->lerp.stairoldz = e->origin[2] = step; + } + e = RI->currententity; + } + + Vector origin = StudioGetOrigin(); + Vector angles = RI->currententity->angles; + Vector scale = Vector( 1.0f, 1.0f, 1.0f ); + + // apply lodnum to model + if(( numLods = StudioCheckLOD( )) != 0 ) + { + float radius = Q_max( m_pModelInstance->radius, 1.0f ); // to avoid division by zero +#if 1 + float screenSize = ComputePixelWidthOfSphere( origin, 0.5f ); + float lodMetric = screenSize != 0.0f ? (100.0f / screenSize) : 0.0f; + int lodnum = (int)( lodMetric / radius * 2.0f ); +#else + float lodDist = (origin - RI->view.origin).Length() * RI->view.lodScale; + int lodnum = (int)( lodDist / radius ); + if( CVAR_TO_BOOL( m_pCvarLodScale )) + lodnum /= (int)fabs( m_pCvarLodScale->value ); +#endif + if( CVAR_TO_BOOL( m_pCvarLodBias )) + lodnum += (int)fabs( m_pCvarLodBias->value ); + + // set derived LOD + e->curstate.body = Q_min( lodnum, numLods - 1 ); + } + + angles[PITCH] = -angles[PITCH]; // stupid quake bug! + + if( e->curstate.renderfx != kRenderFxDeadPlayer ) + { + int gaitsequence = 0; + + // don't rotate clients, only aim + if( e->player ) angles[PITCH] = 0.0f; + + if( e->curstate.gaitsequence != m_pModelInstance->lerp.gaitsequence ) + { + AddBlendSequence( m_pModelInstance->lerp.gaitsequence, e->curstate.gaitsequence, m_pModelInstance->lerp.gaitframe, true ); + m_pModelInstance->lerp.gaitsequence = e->curstate.gaitsequence; + } + } + + if( FBitSet( RI->currententity->curstate.effects, EF_NOINTERP ) || ( tr.realframecount - m_pModelInstance->cached_frame ) > 1 ) + { + m_pModelInstance->lerp.sequence = RI->currententity->curstate.sequence; + } + else if( RI->currententity->curstate.sequence != m_pModelInstance->lerp.sequence ) + { + AddBlendSequence( m_pModelInstance->lerp.sequence, RI->currententity->curstate.sequence, m_pModelInstance->lerp.frame ); + m_pModelInstance->lerp.sequence = RI->currententity->curstate.sequence; + } + + // store prevseqblending manually, engine doesn't it + e->latched.prevseqblending[0] = e->curstate.blending[0]; + e->latched.prevseqblending[1] = e->curstate.blending[1]; + + // don't blend sequences for a dead player or a viewmodel, faceprotect + if( m_iDrawModelType > DRAWSTUDIO_NORMAL || RI->currententity->curstate.renderfx == kRenderFxDeadPlayer ) + memset( &m_pModelInstance->m_seqblend, 0, sizeof( m_pModelInstance->m_seqblend )); + + if( RI->currententity->curstate.iuser1 & CF_STATIC_ENTITY ) + { + if( RI->currententity->curstate.startpos != g_vecZero ) + scale = RI->currententity->curstate.startpos; + } + else if( RI->currententity->curstate.scale > 0.0f && RI->currententity->curstate.scale <= 16.0f ) + { + // apply studiomodel scale (clamp scale to prevent too big sizes on some HL maps) + scale = Vector( RI->currententity->curstate.scale, RI->currententity->curstate.scale, RI->currententity->curstate.scale ); + } + + if( RP_LOCALCLIENT( RI->currententity ) && !FBitSet( RI->params, RP_THIRDPERSON )) + { + // offset only for legs or water reflection + if(( RI->currentmodel == m_pPlayerLegsModel ) || FBitSet( RI->params, RP_SHADOWVIEW|RP_MIRRORVIEW ) || ( GetVForward().z == 1.0f )) + { + Vector ang, forward; + ang = tr.cached_viewangles; + ang[PITCH] = ang[ROLL] = 0; // yaw only + AngleVectors( ang, forward, NULL, NULL ); + origin += forward * -m_pCvarLegsOffset->value; + } + } + + // build the rotation matrix + m_pModelInstance->m_protationmatrix = matrix3x4( origin, angles, scale ); + + if( RI->currententity == GET_VIEWMODEL() && CVAR_TO_BOOL( m_pCvarHand )) + { + // inverse the right vector + m_pModelInstance->m_protationmatrix.SetRight( -m_pModelInstance->m_protationmatrix.GetRight() ); + } + + StudioFxTransform( e, m_pModelInstance->m_protationmatrix ); +} + +/* +==================== +StudioEstimateFrame + +==================== +*/ +float CStudioModelRenderer :: StudioEstimateFrame( mstudioseqdesc_t *pseqdesc ) +{ + int numframes = m_boneSetup.LocalMaxFrame( RI->currententity->curstate.sequence ); + double fps = m_boneSetup.LocalFPS( RI->currententity->curstate.sequence ); + double dfdt = 0, f = 0; + + if( !m_fShootDecal && tr.time >= RI->currententity->curstate.animtime ) + dfdt = (tr.time - RI->currententity->curstate.animtime) * RI->currententity->curstate.framerate * fps; + + if( numframes > 1 ) + f = (RI->currententity->curstate.frame * numframes) / 256.0; + + f += dfdt; + + if( pseqdesc->flags & STUDIO_LOOPING ) + { + if( numframes > 1 ) + { + f -= (int)(f / numframes) * numframes; + } + + if( f < 0.0 ) + { + f += numframes; + } + } + else + { + if( f >= numframes ) + { + f = numframes; + } + + if( f < 0.0 ) + { + f = 0.0; + } + } + + return f; +} + +/* +==================== +StudioEstimateFrame + +==================== +*/ +float CStudioModelRenderer :: StudioEstimateGaitFrame( mstudioseqdesc_t *pseqdesc ) +{ + cl_entity_t *e = RI->currententity; + int numframes = m_boneSetup.LocalMaxFrame( e->curstate.gaitsequence ); + double dfdt = 0, f = 0; + + // FIXME: gaitinterp is broken + if( !m_fShootDecal && tr.time >= RI->currententity->curstate.animtime ) + dfdt = (tr.time - RI->currententity->curstate.animtime) * tr.frametime; + + if( numframes > 1 ) + f = RI->currententity->curstate.fuser1; + f += dfdt; + + if( pseqdesc->flags & STUDIO_LOOPING ) + { + if( numframes > 1 ) + { + f -= (int)(f / numframes) * numframes; + } + + if( f < 0.0 ) + { + f += numframes; + } + } + else + { + if( f >= numframes - 1.001 ) + { + f = numframes - 1.001; + } + + if( f < 0.0 ) + { + f = 0.0; + } + } + + return f; +} + +/* +==================== +StudioEstimateInterpolant + +==================== +*/ +float CStudioModelRenderer :: StudioEstimateInterpolant( void ) +{ + cl_entity_t *e = RI->currententity; + double rate = 0.1; // monster think + float dadt = 1.0f; + + if( e->player ) + { + rate = atof( gEngfuncs.PlayerInfo_ValueForKey( e->index, "cl_updaterate" )); + if( rate != 0.0 ) rate = 1.0 / rate; + if( gEngfuncs.GetMaxClients() == 1 ) + rate = 1.0f; // it's single player and has unlimited update rate + } + + if( !m_fShootDecal && ( e->curstate.animtime >= e->latched.prevanimtime + 0.01f )) + { + dadt = (tr.time - e->curstate.animtime) / (e->player) ? 0.01f : 0.1f; // think interval + if( dadt > 1.0f ) dadt = 1.0f; + } + + return dadt; +} + +/* +==================== +StudioInterpolatePoseParams + +==================== +*/ +void CStudioModelRenderer :: StudioInterpolatePoseParams( cl_entity_t *e, float dadt ) +{ + if( !m_boneSetup.CountPoseParameters( )) + { + // interpolate blends + m_pModelInstance->m_poseparameter[0] = (e->curstate.blending[0] * dadt + e->latched.prevblending[0] * (1.0f - dadt)) / 255.0f; + m_pModelInstance->m_poseparameter[1] = (e->curstate.blending[1] * dadt + e->latched.prevblending[1] * (1.0f - dadt)) / 255.0f; + } + else + { + m_pModelInstance->m_poseparameter[0] = (e->curstate.vuser1[0] * dadt + m_pModelInstance->m_oldposeparams[0] * (1.0f - dadt)); + m_pModelInstance->m_poseparameter[1] = (e->curstate.vuser1[1] * dadt + m_pModelInstance->m_oldposeparams[1] * (1.0f - dadt)); + m_pModelInstance->m_poseparameter[2] = (e->curstate.vuser1[2] * dadt + m_pModelInstance->m_oldposeparams[2] * (1.0f - dadt)); + + m_pModelInstance->m_poseparameter[3] = (e->curstate.vuser2[0] * dadt + m_pModelInstance->m_oldposeparams[3] * (1.0f - dadt)); + m_pModelInstance->m_poseparameter[4] = (e->curstate.vuser2[1] * dadt + m_pModelInstance->m_oldposeparams[4] * (1.0f - dadt)); + m_pModelInstance->m_poseparameter[5] = (e->curstate.vuser2[2] * dadt + m_pModelInstance->m_oldposeparams[5] * (1.0f - dadt)); + + m_pModelInstance->m_poseparameter[6] = (e->curstate.vuser3[0] * dadt + m_pModelInstance->m_oldposeparams[6] * (1.0f - dadt)); + m_pModelInstance->m_poseparameter[7] = (e->curstate.vuser3[1] * dadt + m_pModelInstance->m_oldposeparams[7] * (1.0f - dadt)); + m_pModelInstance->m_poseparameter[8] = (e->curstate.vuser3[2] * dadt + m_pModelInstance->m_oldposeparams[8] * (1.0f - dadt)); + + m_pModelInstance->m_poseparameter[ 9] = (e->curstate.vuser4[0] * dadt + m_pModelInstance->m_oldposeparams[ 9] * (1.0f - dadt)); + m_pModelInstance->m_poseparameter[10] = (e->curstate.vuser4[1] * dadt + m_pModelInstance->m_oldposeparams[10] * (1.0f - dadt)); + m_pModelInstance->m_poseparameter[11] = (e->curstate.vuser4[2] * dadt + m_pModelInstance->m_oldposeparams[11] * (1.0f - dadt)); + } +} + +/* +==================== +StudioInterpolateControllers + +==================== +*/ +void CStudioModelRenderer :: StudioInterpolateControllers( cl_entity_t *e, float dadt ) +{ + // buz: õàê, ïîçâîëÿþùèé íå èíòåðïîëèðîâàòü êîíòðîëëåðû äëÿ ñòàöèîíàðíîãî ïóëåìåòà + if( RI->currententity->curstate.renderfx == 51 ) + dadt = 1.0f; + + mstudiobonecontroller_t *pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bonecontrollerindex); + + // interpolate controllers + for( int j = 0; j < m_pStudioHeader->numbonecontrollers; j++ ) + { + int i = pbonecontroller[j].index; + float value; + + if( i <= 3 ) + { + // check for 360% wrapping + if( FBitSet( pbonecontroller[j].type, STUDIO_RLOOP )) + { + if( abs( e->curstate.controller[i] - e->latched.prevcontroller[i] ) > 128 ) + { + int a = (e->curstate.controller[j] + 128) % 256; + int b = (e->latched.prevcontroller[j] + 128) % 256; + value = ((a * dadt) + (b * (1.0f - dadt)) - 128); + } + else + { + value = ((e->curstate.controller[i] * dadt + (e->latched.prevcontroller[i]) * (1.0f - dadt))); + } + } + else + { + value = (e->curstate.controller[i] * dadt + e->latched.prevcontroller[i] * (1.0 - dadt)); + } + m_pModelInstance->m_controller[i] = bound( 0, Q_rint( value ), 255 ); + } + } +} + +/* +==================== +StudioGetAnim + +==================== +*/ +mstudioanim_t *CStudioModelRenderer :: StudioGetAnim( model_t *pModel, mstudioseqdesc_t *pseqdesc ) +{ + mstudioseqgroup_t *pseqgroup; + cache_user_t *paSequences; + + pseqgroup = (mstudioseqgroup_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqgroupindex) + pseqdesc->seqgroup; + + if( pseqdesc->seqgroup == 0 ) + return (mstudioanim_t *)((byte *)m_pStudioHeader + pseqgroup->data + pseqdesc->animindex); + + paSequences = (cache_user_t *)pModel->submodels; + + if( paSequences == NULL ) + { + paSequences = (cache_user_t *)IEngineStudio.Mem_Calloc( MAXSTUDIOGROUPS, sizeof( cache_user_t )); + pModel->submodels = (dmodel_t *)paSequences; + } + + // check for already loaded + if( !IEngineStudio.Cache_Check(( struct cache_user_s *)&(paSequences[pseqdesc->seqgroup] ))) + { + char filepath[128], modelpath[128], modelname[64]; + + COM_FileBase( pModel->name, modelname ); + COM_ExtractFilePath( pModel->name, modelpath ); + + // NOTE: here we build real sub-animation filename because stupid user may rename model without recompile + Q_snprintf( filepath, sizeof( filepath ), "%s/%s%i%i.mdl", modelpath, modelname, pseqdesc->seqgroup / 10, pseqdesc->seqgroup % 10 ); + + ALERT( at_console, "loading: %s\n", filepath ); + IEngineStudio.LoadCacheFile( filepath, (struct cache_user_s *)&paSequences[pseqdesc->seqgroup] ); + } + + return (mstudioanim_t *)((byte *)paSequences[pseqdesc->seqgroup].data + pseqdesc->animindex); +} + +/* +==================== +Studio_FxTransform + +==================== +*/ +void CStudioModelRenderer :: StudioFxTransform( cl_entity_t *ent, matrix3x4 &transform ) +{ + switch( ent->curstate.renderfx ) + { + case kRenderFxDistort: + case kRenderFxHologram: + if( RANDOM_LONG( 0, 49 ) == 0 ) + { + // choose between x & z + switch( RANDOM_LONG( 0, 1 )) + { + case 0: + transform.SetForward( transform.GetForward() * RANDOM_FLOAT( 1.0f, 1.484f )); + break; + case 1: + transform.SetUp( transform.GetUp() * RANDOM_FLOAT( 1.0f, 1.484f )); + break; + } + } + else if( RANDOM_LONG( 0, 49 ) == 0 ) + { + transform[3][RANDOM_LONG( 0, 2 )] += RANDOM_FLOAT( -10.0f, 10.0f ); + } + break; + case kRenderFxExplode: + { + float scale = 1.0f + ( tr.time - ent->curstate.animtime ) * 10.0f; + if( scale > 2 ) scale = 2; // don't blow up more than 200% + transform.SetRight( transform.GetRight() * scale ); + } + break; + } +} + +void CStudioModelRenderer :: BlendSequence( Vector pos[], Vector4D q[], mstudioblendseq_t *pseqblend ) +{ + float m_flGaitBoneWeights[MAXSTUDIOBONES]; + CIKContext *pIK = NULL; + + // to prevent division by zero + if( pseqblend->fadeout <= 0.0f ) + pseqblend->fadeout = 0.2f; + + if( m_boneSetup.GetNumIKChains( )) + pIK = &m_pModelInstance->m_ik; + + if( pseqblend->blendtime && ( pseqblend->blendtime + pseqblend->fadeout > tr.time ) && ( pseqblend->sequence < m_pStudioHeader->numseq )) + { + float s = 1.0f - (tr.time - pseqblend->blendtime) / pseqblend->fadeout; + + if( s > 0 && s <= 1.0 ) + { + // do a nice spline curve + s = 3.0f * s * s - 2.0f * s * s * s; + } + else if( s > 1.0f ) + { + // Shouldn't happen, but maybe curtime is behind animtime? + s = 1.0f; + } + + if( pseqblend->gaitseq ) + { + mstudiobone_t *pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + bool copy = true; + + for( int i = 0; i < m_pStudioHeader->numbones; i++) + { + if( !Q_strcmp( pbones[i].name, "Bip01 Spine" )) + copy = false; + else if( !Q_strcmp( pbones[pbones[i].parent].name, "Bip01 Pelvis" )) + copy = true; + m_flGaitBoneWeights[i] = (copy) ? 1.0f : 0.0f; + } + + m_boneSetup.SetBoneWeights( m_flGaitBoneWeights ); // install weightlist for gait sequence + } + + m_boneSetup.AccumulatePose( pIK, pos, q, pseqblend->sequence, pseqblend->cycle, s ); + m_boneSetup.SetBoneWeights( NULL ); // back to default rules + } +} + +//----------------------------------------------------------------------------- +// Purpose: update latched IK contacts if they're in a moving reference frame. +//----------------------------------------------------------------------------- +void CStudioModelRenderer :: UpdateIKLocks( CIKContext *pIK ) +{ + if( !pIK ) return; + + int targetCount = pIK->m_target.Count(); + + if( targetCount == 0 ) + return; + + for( int i = 0; i < targetCount; i++ ) + { + CIKTarget *pTarget = &pIK->m_target[i]; + + if( !pTarget->IsActive( )) + continue; + + if( pTarget->GetOwner() != -1 ) + { + cl_entity_t *pOwner = GET_ENTITY( pTarget->GetOwner() ); + + if( pOwner != NULL ) + { + pTarget->UpdateOwner( pOwner->index, pOwner->origin, pOwner->angles ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Find the ground or external attachment points needed by IK rules +//----------------------------------------------------------------------------- +void CStudioModelRenderer :: CalculateIKLocks( CIKContext *pIK ) +{ + if( !pIK ) return; + + int targetCount = pIK->m_target.Count(); + + if( targetCount == 0 ) + return; + + Vector up = Vector( 0.0f, 0.0f, 1.0f ); + // FIXME: check number of slots? + float minHeight = FLT_MAX; + float maxHeight = -FLT_MAX; + + for( int i = 0, j = 0; i < targetCount; i++ ) + { + pmtrace_t *trace; + CIKTarget *pTarget = &pIK->m_target[i]; + float flDist = pTarget->est.radius; + + if( !pTarget->IsActive( )) + continue; + + switch( pTarget->type ) + { + case IK_GROUND: + { + Vector estGround; + Vector p1, p2; + + // adjust ground to original ground position + estGround = (pTarget->est.pos - RI->currententity->origin); + estGround = estGround - (estGround * up) * up; + estGround = RI->currententity->origin + estGround + pTarget->est.floor * up; + + p1 = estGround + up * pTarget->est.height; + p2 = estGround - up * pTarget->est.height; + float r = Q_max( pTarget->est.radius, 1 ); + + Vector mins = Vector( -r, -r, 0.0f ); + Vector maxs = Vector( r, r, r * 2.0f ); + + // don't IK to other characters + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PushTraceBounds( 2, mins, maxs ); + trace = gEngfuncs.pEventAPI->EV_VisTraceLine( p1, p2, PM_STUDIO_IGNORE ); + physent_t *ve = gEngfuncs.pEventAPI->EV_GetVisent( trace->ent ); + cl_entity_t *m_pGround = (ve) ? GET_ENTITY( ve->info ) : NULL; + gEngfuncs.pEventAPI->EV_PopTraceBounds(); + + if( m_pGround != NULL && m_pGround->curstate.movetype == MOVETYPE_PUSH ) + { + pTarget->SetOwner( m_pGround->index, m_pGround->origin, m_pGround->angles ); + } + else + { + pTarget->ClearOwner(); + } + + if( trace->startsolid ) + { + // trace from back towards hip + Vector tmp = (estGround - pTarget->trace.closest).Normalize(); + + p1 = estGround - tmp * pTarget->est.height; + p2 = estGround; + mins = Vector( -r, -r, 0.0f ); + maxs = Vector( r, r, 1.0f ); + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PushTraceBounds( 2, mins, maxs ); + trace = gEngfuncs.pEventAPI->EV_VisTraceLine( p1, p2, PM_STUDIO_IGNORE ); + ve = gEngfuncs.pEventAPI->EV_GetVisent( trace->ent ); + m_pGround = (ve) ? GET_ENTITY( ve->info ) : NULL; + gEngfuncs.pEventAPI->EV_PopTraceBounds(); + + if( !trace->startsolid ) + { + p1 = trace->endpos; + p2 = p1 - up * pTarget->est.height; + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + trace = gEngfuncs.pEventAPI->EV_VisTraceLine( p1, p2, PM_STUDIO_IGNORE ); + ve = gEngfuncs.pEventAPI->EV_GetVisent( trace->ent ); + m_pGround = (ve) ? GET_ENTITY( ve->info ) : NULL; + } + } + + if( !trace->startsolid ) + { + if( m_pGround == GET_ENTITY( 0 )) + { + // clamp normal to 33 degrees + const float limit = 0.832; + float dot = DotProduct( trace->plane.normal, up ); + + if( dot < limit ) + { + ASSERT( dot >= 0 ); + // subtract out up component + Vector diff = trace->plane.normal - up * dot; + // scale remainder such that it and the up vector are a unit vector + float d = sqrt(( 1.0f - limit * limit ) / DotProduct( diff, diff ) ); + trace->plane.normal = up * limit + d * diff; + } + + // FIXME: this is wrong with respect to contact position and actual ankle offset + pTarget->SetPosWithNormalOffset( trace->endpos, trace->plane.normal ); + pTarget->SetNormal( trace->plane.normal ); + pTarget->SetOnWorld( true ); + + // only do this on forward tracking or commited IK ground rules + if( pTarget->est.release < 0.1f ) + { + // keep track of ground height + float offset = DotProduct( pTarget->est.pos, up ); + + if( minHeight > offset ) + minHeight = offset; + if( maxHeight < offset ) + maxHeight = offset; + } + // FIXME: if we don't drop legs, running down hills looks horrible + /* + if (DotProduct( pTarget->est.pos, up ) < DotProduct( estGround, up )) + { + pTarget->est.pos = estGround; + } + */ + } + else if( m_pGround != NULL ) + { + pTarget->SetPos( trace->endpos ); + pTarget->SetAngles( RI->currententity->angles ); + + // only do this on forward tracking or commited IK ground rules + if( pTarget->est.release < 0.1f ) + { + float offset = DotProduct( pTarget->est.pos, up ); + + if( minHeight > offset ) + minHeight = offset; + + if( maxHeight < offset ) + maxHeight = offset; + } + // FIXME: if we don't drop legs, running down hills looks horrible + /* + if (DotProduct( pTarget->est.pos, up ) < DotProduct( estGround, up )) + { + pTarget->est.pos = estGround; + } + */ + } + else + { + pTarget->IKFailed(); + } + } + else + { + if( m_pGround != GET_ENTITY( 0 )) + { + pTarget->IKFailed( ); + } + else + { + pTarget->SetPos( trace->endpos ); + pTarget->SetAngles( RI->currententity->angles ); + pTarget->SetOnWorld( true ); + } + } + } + break; + case IK_ATTACHMENT: + flDist = pTarget->est.radius; + for( j = 1; j < RENDER_GET_PARM( PARM_MAX_ENTITIES, 0 ); j++ ) + { + cl_entity_t *m_pEntity = GET_ENTITY( j ); + float flRadius = 4096.0f; // (64.0f * 64.0f) + + if( !m_pEntity || m_pEntity->modelhandle == INVALID_HANDLE ) + continue; // not a studiomodel or not in PVS + + ModelInstance_t *inst = &m_ModelInstances[m_pEntity->modelhandle]; + float distSquared = 0.0f, eorg; + + for( int k = 0; k < 3 && distSquared <= flRadius; k++ ) + { + if( pTarget->est.pos[j] < inst->absmin[j] ) + eorg = pTarget->est.pos[j] - inst->absmin[j]; + else if( pTarget->est.pos[j] > inst->absmax[j] ) + eorg = pTarget->est.pos[j] - inst->absmax[j]; + else eorg = 0.0f; + + distSquared += eorg * eorg; + } + + if( distSquared >= flRadius ) + continue; // not in radius + + // Extract the bone index from the name + if( pTarget->offset.attachmentIndex >= inst->numattachments ) + continue; + + // FIXME: how to validate a index? + Vector origin = inst->attachment[pTarget->offset.attachmentIndex].origin; + Vector angles = inst->attachment[pTarget->offset.attachmentIndex].angles; + float d = (pTarget->est.pos - origin).Length(); + + if( d >= flDist ) + continue; + flDist = d; + pTarget->SetPos( origin ); + pTarget->SetAngles( angles ); + } + + if( flDist >= pTarget->est.radius ) + { + // no solution, disable ik rule + pTarget->IKFailed( ); + } + break; + } + } +} + +/* +================ +CheckBoneCache + +validate cache +================ +*/ +bool CStudioModelRenderer :: CheckBoneCache( float f ) +{ + if( !m_pModelInstance ) + return false; + + ModelInstance_t *inst = m_pModelInstance; + BoneCache_t *cache = &inst->bonecache; + cl_entity_t *e = RI->currententity; + + if(( tr.time - m_pModelInstance->lighttimecheck ) > LIGHT_INTERP_UPDATE ) + { + // shuffle states + m_pModelInstance->oldlight = m_pModelInstance->newlight; + m_pModelInstance->lighttimecheck = tr.time; + m_pModelInstance->light_update = true; + } + + // no cache for local player + if( RP_LOCALCLIENT( RI->currententity ) && !FBitSet( RI->params, RP_THIRDPERSON )) + return false; + + bool pos_valid = (cache->transform == inst->m_protationmatrix) ? true : false; + bool param_valid = !memcmp( cache->poseparam, m_pModelInstance->m_poseparameter, sizeof( float ) * MAXSTUDIOPOSEPARAM ); + + // make sure what all cached values are unchanged + if( cache->frame == f && cache->sequence == e->curstate.sequence && pos_valid && !memcmp( cache->blending, e->curstate.blending, 2 ) + && !memcmp( cache->controller, e->curstate.controller, 4 ) && cache->mouthopen == e->mouth.mouthopen && param_valid ) + { + if( cache->gaitsequence == e->curstate.gaitsequence && cache->gaitframe == e->curstate.fuser1 ) + return true; + } + + // time to check cubemaps + if( cache->transform.GetOrigin() != inst->m_protationmatrix.GetOrigin() ) + { + // search for center of bbox + Vector pos = ( inst->absmin + inst->absmax ) * 0.5f; + CL_FindTwoNearestCubeMap( pos, &inst->cubemap[0], &inst->cubemap[1] ); + + float dist0 = ( inst->cubemap[0]->origin - pos ).Length(); + float dist1 = ( inst->cubemap[1]->origin - pos ).Length(); + inst->lerpFactor = dist0 / (dist0 + dist1); + } + + // save rotationmatrix to GL-style array + inst->m_protationmatrix.CopyToArray( inst->m_glmatrix ); + + // update bonecache + cache->frame = f; + cache->mouthopen = e->mouth.mouthopen; + cache->sequence = e->curstate.sequence; + cache->transform = inst->m_protationmatrix; + memcpy( cache->blending, e->curstate.blending, 2 ); + memcpy( cache->controller, e->curstate.controller, 4 ); + memcpy( cache->poseparam, m_pModelInstance->m_poseparameter, sizeof( float ) * MAXSTUDIOPOSEPARAM ); + cache->gaitsequence = e->curstate.gaitsequence; + cache->gaitframe = e->curstate.fuser1; + + return false; +} + +/* +==================== +StudioSetupBones + +==================== +*/ +void CStudioModelRenderer :: StudioSetupBones( void ) +{ + float adj[MAXSTUDIOCONTROLLERS]; + cl_entity_t *e = RI->currententity; // for more readability + CIKContext *pIK = NULL; + mstudioboneinfo_t *pboneinfo; + mstudioseqdesc_t *pseqdesc; + matrix3x4 bonematrix; + mstudiobone_t *pbones; + int i; + + static Vector pos[MAXSTUDIOBONES]; + static Vector4D q[MAXSTUDIOBONES]; + + if( e->curstate.sequence < 0 || e->curstate.sequence >= m_pStudioHeader->numseq ) + { + int sequence = (short)e->curstate.sequence; + ALERT( at_aiconsole, "StudioSetupBones: sequence %i/%i out of range for model %s\n", + sequence, m_pStudioHeader->numseq, RI->currentmodel->name ); + e->curstate.sequence = 0; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence; + float f = StudioEstimateFrame( pseqdesc ); + float dadt = StudioEstimateInterpolant(); + + StudioInterpolatePoseParams( e, dadt ); + + if( CheckBoneCache( f )) return; // using a cached bones no need transformations + + if( m_boneSetup.GetNumIKChains( )) + { + if( FBitSet( e->curstate.effects, EF_NOINTERP )) + m_pModelInstance->m_ik.ClearTargets(); + m_pModelInstance->m_ik.Init( &m_boneSetup, e->angles, e->origin, tr.time, tr.realframecount ); + pIK = &m_pModelInstance->m_ik; + } + + float cycle = f / m_boneSetup.LocalMaxFrame( e->curstate.sequence ); + StudioInterpolateControllers( e, dadt ); + + m_boneSetup.InitPose( pos, q ); + m_boneSetup.UpdateRealTime( tr.time ); + if( CVAR_TO_BOOL( m_pCvarCompatible )) + m_boneSetup.CalcBoneAdj( adj, m_pModelInstance->m_controller, e->mouth.mouthopen ); + m_boneSetup.AccumulatePose( pIK, pos, q, e->curstate.sequence, cycle, 1.0 ); + m_pModelInstance->lerp.frame = f; + + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + pboneinfo = (mstudioboneinfo_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex + m_pStudioHeader->numbones * sizeof( mstudiobone_t )); + + if( e->curstate.gaitsequence < 0 || e->curstate.gaitsequence >= m_pStudioHeader->numseq ) + e->curstate.gaitsequence = 0; + + // calc gait animation + if( e->curstate.gaitsequence != 0 ) + { + float m_flGaitBoneWeights[MAXSTUDIOBONES]; + bool copy = true; + + for( int i = 0; i < m_pStudioHeader->numbones; i++) + { + if( !Q_strcmp( pbones[i].name, "Bip01 Spine" )) + copy = false; + else if( !Q_strcmp( pbones[pbones[i].parent].name, "Bip01 Pelvis" )) + copy = true; + m_flGaitBoneWeights[i] = (copy) ? 1.0f : 0.0f; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.gaitsequence; + f = StudioEstimateGaitFrame( pseqdesc ); + + // convert gaitframe to cycle + cycle = f / m_boneSetup.LocalMaxFrame( e->curstate.gaitsequence ); + + m_boneSetup.SetBoneWeights( m_flGaitBoneWeights ); // install weightlist for gait sequence + m_boneSetup.AccumulatePose( pIK, pos, q, e->curstate.gaitsequence, cycle, 1.0 ); + m_boneSetup.SetBoneWeights( NULL ); // back to default rules + m_pModelInstance->lerp.gaitframe = f; + } + + // run blends from previous sequences + for( i = 0; i < MAX_SEQBLENDS; i++ ) + BlendSequence( pos, q, &m_pModelInstance->m_seqblend[i] ); + + CIKContext auto_ik; + auto_ik.Init( &m_boneSetup, e->angles, e->origin, 0.0f, 0 ); + m_boneSetup.CalcAutoplaySequences( &auto_ik, pos, q ); + if( !CVAR_TO_BOOL( m_pCvarCompatible )) + m_boneSetup.CalcBoneAdj( pos, q, m_pModelInstance->m_controller, e->mouth.mouthopen ); + + byte boneComputed[MAXSTUDIOBONES]; + + memset( boneComputed, 0, sizeof( boneComputed )); + + // don't calculate IK on ragdolls + if( pIK != NULL ) + { + UpdateIKLocks( pIK ); + pIK->UpdateTargets( pos, q, m_pModelInstance->m_pbones, boneComputed ); + CalculateIKLocks( pIK ); + pIK->SolveDependencies( pos, q, m_pModelInstance->m_pbones, boneComputed ); + } + + for( i = 0; i < m_pStudioHeader->numbones; i++ ) + { + // animate all non-simulated bones + if( CalcProceduralBone( m_pStudioHeader, i, m_pModelInstance->m_pbones )) + continue; + + // initialize bonematrix + bonematrix = matrix3x4( pos[i], q[i] ); + + if( FBitSet( pbones[i].flags, BONE_JIGGLE_PROCEDURAL ) && FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEINFO )) + { + // Physics-based "jiggle" bone + // Bone is assumed to be along the Z axis + // Pitch around X, yaw around Y + + // compute desired bone orientation + matrix3x4 goalMX; + + if( pbones[i].parent == -1 ) goalMX = m_pModelInstance->m_protationmatrix.ConcatTransforms( bonematrix ); + else goalMX = m_pModelInstance->m_pbones[pbones[i].parent].ConcatTransforms( bonematrix ); + + // get jiggle properties from QC data + mstudiojigglebone_t *jiggleInfo = (mstudiojigglebone_t *)((byte *)m_pStudioHeader + pboneinfo[i].procindex); + if( !m_pModelInstance->m_pJiggleBones ) m_pModelInstance->m_pJiggleBones = new CJiggleBones; + + // do jiggle physics + if( pboneinfo[i].proctype == STUDIO_PROC_JIGGLE ) + m_pModelInstance->m_pJiggleBones->BuildJiggleTransformations( i, tr.time, jiggleInfo, goalMX, m_pModelInstance->m_pbones[i] ); + else m_pModelInstance->m_pbones[i] = goalMX; // fallback + } + else + { + if( pbones[i].parent == -1 ) m_pModelInstance->m_pbones[i] = m_pModelInstance->m_protationmatrix.ConcatTransforms( bonematrix ); + else m_pModelInstance->m_pbones[i] = m_pModelInstance->m_pbones[pbones[i].parent].ConcatTransforms( bonematrix ); + } + } + + mposetobone_t *m = m_pModelInstance->m_pModel->poseToBone; + + // convert bones into compacted GLSL array + if( m != NULL ) + { + for( int i = 0; i < m_pStudioHeader->numbones; i++ ) + { + matrix3x4 out = m_pModelInstance->m_pbones[i].ConcatTransforms( m->posetobone[i] ); + out.CopyToArray4x3( &m_pModelInstance->m_glstudiobones[i*3] ); + m_pModelInstance->m_studioquat[i] = out.GetQuaternion(); + m_pModelInstance->m_studiopos[i] = out.GetOrigin(); + } + } + else + { + for( int i = 0; i < m_pStudioHeader->numbones; i++ ) + { + m_pModelInstance->m_pbones[i].CopyToArray4x3( &m_pModelInstance->m_glstudiobones[i*3] ); + m_pModelInstance->m_studioquat[i] = m_pModelInstance->m_pbones[i].GetQuaternion(); + m_pModelInstance->m_studiopos[i] = m_pModelInstance->m_pbones[i].GetOrigin(); + } + } +} + +/* +==================== +StudioMergeBones + +==================== +*/ +void CStudioModelRenderer :: StudioMergeBones( matrix3x4 &transform, matrix3x4 bones[], matrix3x4 cached_bones[], model_t *pModel, model_t *pParentModel ) +{ + matrix3x4 bonematrix; + static Vector pos[MAXSTUDIOBONES]; + static Vector4D q[MAXSTUDIOBONES]; + float poseparams[MAXSTUDIOPOSEPARAM]; + int sequence = RI->currententity->curstate.sequence; + model_t *oldmodel = RI->currentmodel; + studiohdr_t *oldheader = m_pStudioHeader; + bool weapon_model = false; + + ASSERT( pModel != NULL && pModel->type == mod_studio ); + ASSERT( pParentModel != NULL && pParentModel->type == mod_studio ); + + RI->currentmodel = pModel; + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( RI->currentmodel ); + + // tell the bonesetup about current model + m_boneSetup.SetStudioPointers( m_pStudioHeader, poseparams ); // don't touch original parameters + m_boneSetup.CalcDefaultPoseParameters( poseparams ); // just to have something valid here + + if( pModel == IEngineStudio.GetModelByIndex( RI->currententity->curstate.weaponmodel )) + weapon_model = true; + + // FIXME: how to specify custom sequence for p_model? + if( weapon_model ) sequence = 0; + + mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + sequence; + float f = StudioEstimateFrame( pseqdesc ); + float cycle = f / m_boneSetup.LocalMaxFrame( sequence ); + + m_boneSetup.InitPose( pos, q ); + m_boneSetup.AccumulatePose( NULL, pos, q, sequence, cycle, 1.0 ); + + studiohdr_t *m_pParentHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( pParentModel ); + + ASSERT( m_pParentHeader != NULL ); + + mstudiobone_t *pchildbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + mstudiobone_t *pparentbones = (mstudiobone_t *)((byte *)m_pParentHeader + m_pParentHeader->boneindex); + + for( int i = 0; i < m_pStudioHeader->numbones; i++ ) + { + for( int j = 0; j < m_pParentHeader->numbones; j++ ) + { + if( !Q_stricmp( pchildbones[i].name, pparentbones[j].name )) + { + bones[i] = cached_bones[j]; + break; + } + } + + if( j >= m_pParentHeader->numbones ) + { + // initialize bonematrix + bonematrix = matrix3x4( pos[i], q[i] ); + if( pchildbones[i].parent == -1 ) bones[i] = transform.ConcatTransforms( bonematrix ); + else bones[i] = bones[pchildbones[i].parent].ConcatTransforms( bonematrix ); + } + } + + RI->currentmodel = oldmodel; + m_pStudioHeader = oldheader; + + // restore the bonesetup pointers + m_boneSetup.SetStudioPointers( m_pStudioHeader, m_pModelInstance->m_poseparameter ); +} + +/* +==================== +StudioCalcAttachments + +==================== +*/ +void CStudioModelRenderer :: StudioCalcAttachments( matrix3x4 bones[] ) +{ + if( RI->currententity->modelhandle == INVALID_HANDLE || !m_pModelInstance ) + return; // too early ? + + if( FBitSet( m_pModelInstance->info_flags, MF_ATTACHMENTS_DONE )) + return; // already computed + + StudioAttachment_t *att = m_pModelInstance->attachment; + mstudioattachment_t *pattachment; + cl_entity_t *e = RI->currententity; + + // prevent to compute env_static bounds every frame + if( FBitSet( e->curstate.iuser1, CF_STATIC_ENTITY )) + SetBits( m_pModelInstance->info_flags, MF_ATTACHMENTS_DONE ); + + if( m_pStudioHeader->numattachments <= 0 ) + { + // clear attachments + for( int i = 0; i < MAXSTUDIOATTACHMENTS; i++ ) + { + if( i < 4 ) e->attachment[i] = e->origin; + att[i].angles = e->angles; + att[i].origin = e->origin; + } + return; + } + else if( m_pStudioHeader->numattachments > MAXSTUDIOATTACHMENTS ) + { + m_pStudioHeader->numattachments = MAXSTUDIOATTACHMENTS; // reduce it + ALERT( at_error, "Too many attachments on %s\n", e->model->name ); + } + + // calculate attachment points + pattachment = (mstudioattachment_t *)((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex); + + for( int i = 0; i < m_pStudioHeader->numattachments; i++ ) + { + matrix3x4 world = bones[pattachment[i].bone].ConcatTransforms( att[i].local ); + Vector p1 = bones[pattachment[i].bone].GetOrigin(); + + att[i].origin = world.GetOrigin(); + att[i].angles = world.GetAngles(); + + // merge attachments position for viewmodel + if( m_iDrawModelType == DRAWSTUDIO_VIEWMODEL || m_iDrawModelType == DRAWSTUDIO_RUNEVENTS ) + { + StudioFormatAttachment( att[i].origin ); + StudioFormatAttachment( p1 ); + } + + if( i < 4 ) e->attachment[i] = att[i].origin; + att[i].dir = (att[i].origin - p1).Normalize(); // forward vec + } +} + +/* +==================== +StudioGetAttachment + +==================== +*/ +void CStudioModelRenderer :: StudioGetAttachment( const cl_entity_t *ent, int iAttachment, Vector *pos, Vector *ang, Vector *dir ) +{ + if( !ent || !ent->model || ( !pos && !ang )) + return; + + studiohdr_t *phdr = (studiohdr_t *)IEngineStudio.Mod_Extradata( ent->model ); + if( !phdr ) return; + + if( ent->modelhandle == INVALID_HANDLE ) + return; // too early ? + + ModelInstance_t *inst = &m_ModelInstances[ent->modelhandle]; + + // make sure we not overflow + iAttachment = bound( 0, iAttachment, phdr->numattachments - 1 ); + + if( pos ) *pos = inst->attachment[iAttachment].origin; + if( ang ) *ang = inst->attachment[iAttachment].angles; + if( dir ) *dir = inst->attachment[iAttachment].dir; +} + +/* +==================== +StudioSetupModel + +used only by decals code +==================== +*/ +int CStudioModelRenderer :: StudioSetupModel( int bodypart, mstudiomodel_t **ppsubmodel, msubmodel_t **ppvbomodel ) +{ + mstudiobodyparts_t *pbodypart; + mstudiomodel_t *psubmodel; + mbodypart_t *pCachedParts; + msubmodel_t *pvbomodel; + int index; + + if( bodypart > m_pStudioHeader->numbodyparts ) + bodypart = 0; + + if( m_pModelInstance->m_VlCache != NULL ) + pCachedParts = &m_pModelInstance->m_VlCache->bodyparts[bodypart]; + else pCachedParts = &RI->currentmodel->studiocache->bodyparts[bodypart]; + pbodypart = (mstudiobodyparts_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bodypartindex) + bodypart; + + index = RI->currententity->curstate.body / pbodypart->base; + index = index % pbodypart->nummodels; + + psubmodel = (mstudiomodel_t *)((byte *)m_pStudioHeader + pbodypart->modelindex) + index; + pvbomodel = pCachedParts->models[index]; + + *ppsubmodel = psubmodel; + *ppvbomodel = pvbomodel; + + return index; +} + +/* +=============== +StudioLighting + +used for lighting decals +=============== +*/ +void CStudioModelRenderer :: StudioLighting( float *lv, int bone, int flags, const Vector &normal ) +{ + mstudiolight_t *light = &m_pModelInstance->light; + + if( FBitSet( flags, STUDIO_NF_FULLBRIGHT ) || FBitSet( RI->currententity->curstate.effects, EF_FULLBRIGHT ) || R_FullBright( )) + { + *lv = 1.0f; + return; + } + + float illum = light->ambientlight; + + if( FBitSet( flags, STUDIO_NF_FLATSHADE )) + { + illum += light->shadelight * 0.8f; + } + else + { + float lightcos; + + if( bone != -1 ) lightcos = DotProduct( normal, m_bonelightvecs[bone] ); + else lightcos = DotProduct( normal, -light->normal ); // -1 colinear, 1 opposite + if( lightcos > 1.0f ) lightcos = 1.0f; + + illum += light->shadelight; + + // do modified hemispherical lighting + lightcos = (lightcos + ( SHADE_LAMBERT - 1.0f )) / SHADE_LAMBERT; + if( lightcos > 0.0f ) + illum -= light->shadelight * lightcos; + illum = Q_max( illum, 0.0f ); + } + + illum = Q_min( illum, 255.0f ); + *lv = illum * (1.0f / 255.0f); +} + +/* +=============== +CacheVertexLight + +=============== +*/ +void CStudioModelRenderer :: CacheVertexLight( cl_entity_t *ent ) +{ + if( FBitSet( ent->curstate.iuser1, CF_STATIC_ENTITY ) && ( ent->curstate.colormap > 0 ) && world->vertex_lighting != NULL ) + { + Vector org = m_pModelInstance->m_protationmatrix[3]; // model origin + int cacheID = ent->curstate.colormap - 1; + dvlightlump_t *vl = world->vertex_lighting; + + if( FBitSet( m_pModelInstance->info_flags, MF_VERTEX_LIGHTING )) + { + // does nothing + } + else if( !FBitSet( m_pModelInstance->info_flags, MF_VL_BAD_CACHE )) + { + // first initialization + if( cacheID < vl->nummodels && vl->dataofs[cacheID] != -1 ) + { + dmodelvertlight_t *dml = (dmodelvertlight_t *)((byte *)world->vertex_lighting + vl->dataofs[cacheID]); + byte *vislight = ((byte *)world->vertex_lighting + vl->dataofs[cacheID]); + + // we have ID of vertex light cache and cache is present + CreateStudioCacheVL( dml, cacheID ); + + vislight += sizeof( *dml ) - ( sizeof( dvertlight_t ) * 3 ) + sizeof( dvertlight_t ) * dml->numverts; + Mod_FindStaticLights( vislight, m_pModelInstance->lights, org ); + } + } + } +} + +/* +=============== +CacheSurfaceLight + +=============== +*/ +void CStudioModelRenderer :: CacheSurfaceLight( cl_entity_t *ent ) +{ + if( FBitSet( ent->curstate.iuser1, CF_STATIC_ENTITY ) && ( ent->curstate.colormap > 0 ) && world->surface_lighting != NULL ) + { + Vector org = m_pModelInstance->m_protationmatrix[3]; // model origin + int cacheID = ent->curstate.colormap - 1; + dvlightlump_t *vl = world->surface_lighting; + + if( FBitSet( m_pModelInstance->info_flags, MF_SURFACE_LIGHTING )) + { + if( m_pModelInstance->m_FlCache && m_pModelInstance->m_FlCache->update_light ) + { + for( int j = 0; j < m_pModelInstance->m_FlCache->numsurfaces; j++ ) + { + mstudiosurface_t *surf = &m_pModelInstance->m_FlCache->surfaces[j]; + R_UpdateSurfaceParams( surf ); + } + m_pModelInstance->m_FlCache->update_light = false; + } + } + else if( !FBitSet( m_pModelInstance->info_flags, MF_VL_BAD_CACHE )) + { + // first initialization + if( cacheID < vl->nummodels && vl->dataofs[cacheID] != -1 ) + { + dmodelfacelight_t *dml = (dmodelfacelight_t *)((byte *)world->surface_lighting + vl->dataofs[cacheID]); + byte *vislight = ((byte *)world->surface_lighting + vl->dataofs[cacheID]); + + // we have ID of vertex light cache and cache is present + CreateStudioCacheFL( dml, cacheID ); + + vislight += sizeof( *dml ) - ( sizeof( dfacelight_t ) * 3 ) + sizeof( dfacelight_t ) * dml->numfaces; + Mod_FindStaticLights( vislight, m_pModelInstance->lights, org ); + } + } + } +} + +/* +=============== +StudioStaticLight + +=============== +*/ +void CStudioModelRenderer :: StudioStaticLight( cl_entity_t *ent, mstudiolight_t *light ) +{ + bool ambient_light = false; + bool skipZCheck = true; + + if( FBitSet( ent->model->flags, STUDIO_AMBIENT_LIGHT )) + ambient_light = true; +#if 0 + if( ent->curstate.movetype == MOVETYPE_STEP ) + skipZCheck = false; +#endif + + if( FBitSet( ent->curstate.iuser1, CF_STATIC_LIGHTMAPPED )) + CacheSurfaceLight( ent ); + else CacheVertexLight( ent ); + + if( m_pModelInstance->oldlight.nointerp ) + { + m_pModelInstance->light_update = true; + m_pModelInstance->lighttimecheck = 0.0f; + } + + // refresh lighting every 0.1 secs + if( m_pModelInstance->light_update ) + { + if( ent->curstate.renderfx == SKYBOX_ENTITY ) + { + R_LightForSky( ent->origin, &m_pModelInstance->newlight ); + } + else if( FBitSet( ent->curstate.iuser1, CF_STATIC_ENTITY ) && FBitSet( m_pModelInstance->info_flags, MF_VL_BAD_CACHE )) + { + float dynamic = r_dynamic->value; + alight_t lighting; + Vector dir; + + lighting.plightvec = dir; + + // setup classic Half-Life lighting + r_dynamic->value = 0.0f; // ignore dlights + IEngineStudio.StudioDynamicLight( ent, &lighting ); + r_dynamic->value = dynamic; + + m_pModelInstance->newlight.ambientlight = lighting.ambientlight; + m_pModelInstance->newlight.shadelight = lighting.shadelight; + m_pModelInstance->newlight.diffuse = lighting.color; + m_pModelInstance->newlight.normal = -Vector( lighting.plightvec ); + R_PointAmbientFromLeaf( ent->origin, &m_pModelInstance->newlight ); + } + else + { + R_LightForStudio( ent->origin, &m_pModelInstance->newlight, ambient_light ); + } + m_pModelInstance->light_update = false; + + // init state if was not in frustum + if( abs( m_pModelInstance->cached_frame - tr.realframecount ) > 2 ) + m_pModelInstance->oldlight = m_pModelInstance->newlight; + } + + float frac = (tr.time - m_pModelInstance->lighttimecheck) * LIGHT_INTERP_FACTOR; + frac = bound( 0.0f, frac, 1.0f ); + + memset( light, 0, sizeof( *light )); + InterpolateOrigin( m_pModelInstance->oldlight.diffuse, m_pModelInstance->newlight.diffuse, light->diffuse, frac ); + InterpolateOrigin( m_pModelInstance->oldlight.normal, m_pModelInstance->newlight.normal, light->normal, frac ); + light->ambientlight = Lerp( frac, m_pModelInstance->oldlight.ambientlight, m_pModelInstance->newlight.ambientlight ); + light->shadelight = Lerp( frac, m_pModelInstance->oldlight.shadelight, m_pModelInstance->newlight.shadelight ); + + for( int i = 0; i < 6; i++ ) + InterpolateOrigin( m_pModelInstance->oldlight.ambient[i], m_pModelInstance->newlight.ambient[i], light->ambient[i], frac ); + + // find worldlights for dynamic lighting + if( FBitSet( m_pModelInstance->info_flags, MF_POSITION_CHANGED ) && !FBitSet( ent->curstate.iuser1, CF_STATIC_ENTITY )) + { + Vector origin = (m_pModelInstance->absmin + m_pModelInstance->absmax) * 0.5f; + Vector mins = m_pModelInstance->absmin - origin; + Vector maxs = m_pModelInstance->absmax - origin; + // search for worldlights that affected to model bbox + R_FindWorldLights( origin, mins, maxs, m_pModelInstance->lights, skipZCheck ); + ClearBits( m_pModelInstance->info_flags, MF_POSITION_CHANGED ); + } + + // use inverted light vector for head shield (hack) + if( m_iDrawModelType == DRAWSTUDIO_HEADSHIELD ) + light->normal = -light->normal; +} + +/* +=============== +StudioClientEvents + +=============== +*/ +void CStudioModelRenderer :: StudioClientEvents( void ) +{ + mstudioseqdesc_t *pseqdesc; + mstudioevent_t *pevent; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + RI->currententity->curstate.sequence; + pevent = (mstudioevent_t *)((byte *)m_pStudioHeader + pseqdesc->eventindex); + + // no events for this animation or gamepaused + if( pseqdesc->numevents == 0 || tr.frametime == 0 ) + return; + + // don't playing events from thirdperson model + if( RP_LOCALCLIENT( RI->currententity ) && !FBitSet( RI->params, RP_THIRDPERSON )) + return; + + float frame = StudioEstimateFrame( pseqdesc ); // get start offset + float start = frame - RI->currententity->curstate.framerate * tr.frametime * pseqdesc->fps; + float end = frame; + + for( int i = 0; i < pseqdesc->numevents; i++ ) + { + // ignore all non-client-side events + if( pevent[i].event < EVENT_CLIENT ) + continue; + + if( (float)pevent[i].frame > start && (float)pevent[i].frame <= end ) + HUD_StudioEvent( &pevent[i], RI->currententity ); + } +} + +/* +=============== +StudioDrawBones + +=============== +*/ +void CStudioModelRenderer :: StudioDrawBones( void ) +{ + mstudiobone_t *pbones = (mstudiobone_t *) ((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + Vector point; + + GL_BindTexture( GL_TEXTURE0, tr.whiteTexture ); + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + + for( int i = 0; i < m_pStudioHeader->numbones; i++ ) + { + if( pbones[i].parent >= 0 ) + { + pglPointSize( 3.0f ); + pglColor3f( 1, 0.7f, 0 ); + pglBegin( GL_LINES ); + + m_pModelInstance->m_pbones[pbones[i].parent].GetOrigin( point ); + pglVertex3fv( point ); + + m_pModelInstance->m_pbones[i].GetOrigin( point ); + pglVertex3fv( point ); + + pglEnd(); + + pglColor3f( 0, 0, 0.8f ); + pglBegin( GL_POINTS ); + + if( pbones[pbones[i].parent].parent != -1 ) + { + m_pModelInstance->m_pbones[pbones[i].parent].GetOrigin( point ); + pglVertex3fv( point ); + } + + m_pModelInstance->m_pbones[i].GetOrigin( point ); + pglVertex3fv( point ); + pglEnd(); + } + else + { + // draw parent bone node + pglPointSize( 5.0f ); + pglColor3f( 0.8f, 0, 0 ); + pglBegin( GL_POINTS ); + + m_pModelInstance->m_pbones[i].GetOrigin( point ); + pglVertex3fv( point ); + pglEnd(); + } + } + + pglPointSize( 1.0f ); +} + +/* +=============== +StudioDrawHulls + +=============== +*/ +void CStudioModelRenderer :: StudioDrawHulls( int iHitbox ) +{ + float alpha, lv; + int i, j; + + if( r_drawentities->value == 4 ) + alpha = 0.5f; + else alpha = 1.0f; + + // setup bone lighting + for( i = 0; i < m_pStudioHeader->numbones; i++ ) + m_bonelightvecs[i] = m_pModelInstance->m_pbones[i].VectorIRotate( -m_pModelInstance->light.normal ); + + GL_BindTexture( GL_TEXTURE0, tr.whiteTexture ); + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + + for( i = 0; i < m_pStudioHeader->numhitboxes; i++ ) + { + mstudiobbox_t *pbbox = (mstudiobbox_t *)((byte *)m_pStudioHeader + m_pStudioHeader->hitboxindex); + vec3_t tmp, p[8]; + + if( iHitbox >= 0 && iHitbox != i ) + continue; + + for( j = 0; j < 8; j++ ) + { + tmp[0] = (j & 1) ? pbbox[i].bbmin[0] : pbbox[i].bbmax[0]; + tmp[1] = (j & 2) ? pbbox[i].bbmin[1] : pbbox[i].bbmax[1]; + tmp[2] = (j & 4) ? pbbox[i].bbmin[2] : pbbox[i].bbmax[2]; + p[j] = m_pModelInstance->m_pbones[pbbox[i].bone].VectorTransform( tmp ); + } + + j = (pbbox[i].group % ARRAYSIZE( g_hullcolor )); + + gEngfuncs.pTriAPI->Begin( TRI_QUADS ); + gEngfuncs.pTriAPI->Color4f( g_hullcolor[j][0], g_hullcolor[j][1], g_hullcolor[j][2], alpha ); + + for( j = 0; j < 6; j++ ) + { + tmp = g_vecZero; + tmp[j % 3] = (j < 3) ? 1.0f : -1.0f; + StudioLighting( &lv, pbbox[i].bone, 0, tmp ); + + gEngfuncs.pTriAPI->Brightness( Q_max( lv, tr.ambientFactor )); + gEngfuncs.pTriAPI->Vertex3fv( p[g_boxpnt[j][0]] ); + gEngfuncs.pTriAPI->Vertex3fv( p[g_boxpnt[j][1]] ); + gEngfuncs.pTriAPI->Vertex3fv( p[g_boxpnt[j][2]] ); + gEngfuncs.pTriAPI->Vertex3fv( p[g_boxpnt[j][3]] ); + } + gEngfuncs.pTriAPI->End(); + } +} + +/* +=============== +StudioDrawAbsBBox + +=============== +*/ +void CStudioModelRenderer :: StudioDrawAbsBBox( void ) +{ + Vector p[8], tmp; + float lv; + int i; + + // looks ugly, skip + if( RI->currententity == GET_VIEWMODEL( )) + return; + + // compute a full bounding box + for( i = 0; i < 8; i++ ) + { + p[i][0] = ( i & 1 ) ? m_pModelInstance->absmin[0] : m_pModelInstance->absmax[0]; + p[i][1] = ( i & 2 ) ? m_pModelInstance->absmin[1] : m_pModelInstance->absmax[1]; + p[i][2] = ( i & 4 ) ? m_pModelInstance->absmin[2] : m_pModelInstance->absmax[2]; + } + + GL_BindTexture( GL_TEXTURE0, tr.whiteTexture ); + gEngfuncs.pTriAPI->Color4f( 0.5f, 0.5f, 1.0f, 0.5f ); + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + + gEngfuncs.pTriAPI->Begin( TRI_QUADS ); + + for( i = 0; i < 6; i++ ) + { + tmp = g_vecZero; + tmp[i % 3] = (i < 3) ? 1.0f : -1.0f; + StudioLighting( &lv, -1, 0, tmp ); + + gEngfuncs.pTriAPI->Brightness( Q_max( lv, tr.ambientFactor )); + gEngfuncs.pTriAPI->Vertex3fv( p[g_boxpnt[i][0]] ); + gEngfuncs.pTriAPI->Vertex3fv( p[g_boxpnt[i][1]] ); + gEngfuncs.pTriAPI->Vertex3fv( p[g_boxpnt[i][2]] ); + gEngfuncs.pTriAPI->Vertex3fv( p[g_boxpnt[i][3]] ); + } + + gEngfuncs.pTriAPI->End(); +} + +void CStudioModelRenderer :: StudioDrawAttachments( bool bCustomFov ) +{ + GL_BindTexture( GL_TEXTURE0, tr.whiteTexture ); + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + pglDisable( GL_DEPTH_TEST ); + + for( int i = 0; i < m_pStudioHeader->numattachments; i++ ) + { + mstudioattachment_t *pattachments; + Vector v[4]; + + pattachments = (mstudioattachment_t *) ((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex); + + v[0] = m_pModelInstance->m_pbones[pattachments[i].bone].VectorTransform( pattachments[i].org ); + v[1] = m_pModelInstance->m_pbones[pattachments[i].bone].VectorTransform( g_vecZero ); + v[2] = m_pModelInstance->m_pbones[pattachments[i].bone].VectorTransform( g_vecZero ); + v[3] = m_pModelInstance->m_pbones[pattachments[i].bone].VectorTransform( g_vecZero ); + + for( int j = 0; j < 4 && bCustomFov; j++ ) + StudioFormatAttachment( v[j] ); + + pglBegin( GL_LINES ); + pglColor3f( 1, 0, 0 ); + pglVertex3fv( v[0] ); + pglColor3f( 1, 1, 1 ); + pglVertex3fv( v[1] ); + pglColor3f( 1, 0, 0 ); + pglVertex3fv( v[0] ); + pglColor3f( 1, 1, 1 ); + pglVertex3fv (v[2] ); + pglColor3f( 1, 0, 0 ); + pglVertex3fv( v[0] ); + pglColor3f( 1, 1, 1 ); + pglVertex3fv( v[3] ); + pglEnd(); + + pglPointSize( 5.0f ); + pglColor3f( 0, 1, 0 ); + pglBegin( GL_POINTS ); + pglVertex3fv( v[0] ); + pglEnd(); + pglPointSize( 1.0f ); + } + + pglEnable( GL_DEPTH_TEST ); +} + +void CStudioModelRenderer :: StudioDrawDebug( cl_entity_t *e ) +{ + matrix4x4 projMatrix, worldViewProjMatrix; + bool bCustomFov = false; + + // don't reflect this entity in mirrors + if( e->curstate.effects & EF_NOREFLECT && FBitSet( RI->params, RP_MIRRORVIEW )) + return; + + // draw only in mirrors + if( e->curstate.effects & EF_REFLECTONLY && !FBitSet( RI->params, RP_MIRRORVIEW )) + return; + + // ignore in thirdperson, camera view or client is died + if( e == GET_VIEWMODEL() || e == gHUD.m_pHeadShieldEnt ) + { + if( FBitSet( RI->params, RP_THIRDPERSON ) || CL_IsDead() || !UTIL_IsLocal( RI->view.entity )) + return; + + if( !RP_NORMALPASS( )) + return; + + bCustomFov = ComputeCustomFov( projMatrix, worldViewProjMatrix ); + } + + if( !StudioSetEntity( e )) + return; + + GL_Blend( GL_FALSE ); + GL_AlphaTest( GL_FALSE ); + GL_BindShader( NULL ); + + switch( (int)r_drawentities->value ) + { + case 2: + StudioDrawBones(); + break; + case 3: + StudioDrawHulls (); + break; + case 4: + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + StudioDrawHulls (); + gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); + break; + case 5: + StudioDrawAbsBBox(); + break; + case 6: + StudioDrawAttachments( bCustomFov ); + break; + } + + if( bCustomFov ) RestoreNormalFov( projMatrix, worldViewProjMatrix ); +} + +/* +==================== +StudioFormatAttachment + +==================== +*/ +void CStudioModelRenderer :: StudioFormatAttachment( Vector &point ) +{ + float worldx = tan( (float)RI->view.fov_x * M_PI / 360.0 ); + float viewx = tan( m_flViewmodelFov * M_PI / 360.0 ); + + // BUGBUG: workaround + if( viewx == 0.0f ) viewx = 1.0f; + + // aspect ratio cancels out, so only need one factor + // the difference between the screen coordinates of the 2 systems is the ratio + // of the coefficients of the projection matrices (tan (fov/2) is that coefficient) + float factor = worldx / viewx; + + // get the coordinates in the viewer's space. + Vector tmp = point - GetVieworg(); + Vector vTransformed; + + vTransformed.x = DotProduct( GetVLeft(), tmp ); + vTransformed.y = DotProduct( GetVUp(), tmp ); + vTransformed.z = DotProduct( GetVForward(), tmp ); + vTransformed.x *= factor; + vTransformed.y *= factor; + + // Transform back to world space. + Vector vOut = (GetVLeft() * vTransformed.x) + (GetVUp() * vTransformed.y) + (GetVForward() * vTransformed.z); + point = GetVieworg() + vOut; +} + +/* +=============== +ChooseStudioProgram + +Select the program for mesh (diffuse\bump\debug) +=============== +*/ +word CStudioModelRenderer :: ChooseStudioProgram( studiohdr_t *phdr, mstudiomaterial_t *mat, bool lightpass ) +{ + bool bone_weights = FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS ) ? true : false; + int lightmode = LIGHTSTATIC_NONE; + + if( FBitSet( m_pModelInstance->info_flags, MF_SURFACE_LIGHTING )) + lightmode = LIGHTSTATIC_SURFACE; + else if( FBitSet( m_pModelInstance->info_flags, MF_VERTEX_LIGHTING )) + lightmode = LIGHTSTATIC_VERTEX; + + if( FBitSet( RI->params, RP_SHADOWVIEW )) + return ShaderSceneDepth( mat, bone_weights, phdr->numbones ); + + if( lightpass ) + { + return ShaderLightForward( RI->currentlight, mat, bone_weights, phdr->numbones ); + } + else + { + if( FBitSet( RI->params, RP_DEFERREDSCENE|RP_DEFERREDLIGHT )) + { + ShaderSceneDeferred( mat, bone_weights, phdr->numbones ); + return ShaderLightDeferred( mat, bone_weights, phdr->numbones ); + } + + return ShaderSceneForward( mat, lightmode, bone_weights, phdr->numbones ); + } +} + +/* +==================== +AddMeshToDrawList + +==================== +*/ +void CStudioModelRenderer :: AddMeshToDrawList( studiohdr_t *phdr, vbomesh_t *mesh, bool lightpass ) +{ + // static entities allows to cull each part individually + if( FBitSet( RI->currententity->curstate.iuser1, CF_STATIC_ENTITY ) && RI->currententity->curstate.renderfx != SKYBOX_ENTITY ) + { + Vector absmin, absmax; + + TransformAABB( m_pModelInstance->m_pbones[mesh->parentbone], mesh->mins, mesh->maxs, absmin, absmax ); + + if( !Mod_CheckBoxVisible( absmin, absmax )) + return; // occulded + + if( R_CullBox( absmin, absmax )) + return; // culled + } + + int m_skinnum = bound( 0, RI->currententity->curstate.skin, phdr->numskinfamilies - 1 ); + short *pskinref = (short *)((byte *)phdr + phdr->skinindex); + if( m_skinnum != 0 && m_skinnum < phdr->numskinfamilies ) + pskinref += (m_skinnum * phdr->numskinref); + + mstudiomaterial_t *mat = NULL; + bool cache_from_model = false; + + if( RI->currentmodel == m_pPlayerLegsModel ) + cache_from_model = true; + if( RI->currentmodel == IEngineStudio.GetModelByIndex( RI->currententity->curstate.weaponmodel )) + cache_from_model = true; + + if( cache_from_model ) + mat = &RI->currentmodel->materials[pskinref[mesh->skinref]]; + else mat = &m_pModelInstance->materials[pskinref[mesh->skinref]]; // NOTE: use local copy for right cache shadernums + bool solid = ( R_OpaqueEntity( RI->currententity ) && !FBitSet( mat->flags, STUDIO_NF_ADDITIVE )) ? true : false; + + // goes into regular arrays + if( FBitSet( RI->params, RP_SHADOWVIEW )) + { + if( FBitSet( mat->flags, STUDIO_NF_ADDITIVE ) && FBitSet( mat->flags, STUDIO_NF_HAS_ALPHA )) + solid = true; // add alpha-glasses to shadowlist + lightpass = false; + } + + if( lightpass ) + { + if( RI->currententity->curstate.renderfx == SKYBOX_ENTITY ) + return; // skybox entities can't lighting by dynlights + if( FBitSet( mat->flags, STUDIO_NF_FULLBRIGHT )) + return; // can't light fullbrights + + if( RI->currentlight->type == LIGHT_DIRECTIONAL ) + { + if( FBitSet( mat->flags, STUDIO_NF_NOSUNLIGHT )) + return; // shader was failed to compile + } + else + { + if( FBitSet( mat->flags, STUDIO_NF_NODLIGHT )) + return; // shader was failed to compile + } + } + else + { + if( FBitSet( mat->flags, STUDIO_NF_NODRAW )) + return; // shader was failed to compile + } + + word hProgram = 0; + + if( !( hProgram = ChooseStudioProgram( phdr, mat, lightpass ))) + return; // failed to build shader, don't draw this surface + glsl_program_t *shader = &glsl_programs[hProgram]; + + if( lightpass ) + { + CSolidEntry entry; + entry.SetRenderMesh( mesh, hProgram ); + RI->frame.light_meshes.AddToTail( entry ); + } + else if( solid ) + { + CSolidEntry entry; + entry.SetRenderMesh( mesh, hProgram ); + RI->frame.solid_meshes.AddToTail( entry ); + } + else + { + CTransEntry entry; + + entry.SetRenderMesh( mesh, hProgram ); + if( ScreenCopyRequired( shader ) && mesh->parentbone != 0xFF ) + { + Vector mins, maxs; + TransformAABB( m_pModelInstance->m_pbones[mesh->parentbone], mesh->mins, mesh->maxs, mins, maxs ); + // create sentinel border for refractions + ExpandBounds( mins, maxs, 0.5f ); + entry.ComputeScissor( mins, maxs ); + } + RI->frame.trans_list.AddToTail( entry ); + } +} + +/* +==================== +AddBodyPartToDrawList + +==================== +*/ +void CStudioModelRenderer :: AddBodyPartToDrawList( studiohdr_t *phdr, mbodypart_s *bodyparts, int bodypart, bool lightpass ) +{ + if( !bodyparts ) bodyparts = RI->currentmodel->studiocache->bodyparts; + if( !bodyparts ) HOST_ERROR( "%s missed cache\n", RI->currententity->model->name ); + + bodypart = bound( 0, bodypart, phdr->numbodyparts ); + mbodypart_t *pBodyPart = &bodyparts[bodypart]; + int index = RI->currententity->curstate.body / pBodyPart->base; + index = index % pBodyPart->nummodels; + + msubmodel_t *pSubModel = pBodyPart->models[index]; + if( !pSubModel ) return; // blank submodel, just ignore + + for( int i = 0; i < pSubModel->nummesh; i++ ) + AddMeshToDrawList( phdr, &pSubModel->meshes[i], lightpass ); +} + +/* +================= +AddStudioModelToDrawList + +do culling, compute bones, add meshes to list +================= +*/ +void CStudioModelRenderer :: AddStudioModelToDrawList( cl_entity_t *e, bool update ) +{ + // no shadows for skybox ents + if( FBitSet( RI->params, RP_SHADOWVIEW ) && e->curstate.renderfx == SKYBOX_ENTITY ) + return; + + if( !StudioSetEntity( e )) + return; + + if( !StudioComputeBBox( )) + return; // invalid sequence + + if( !Mod_CheckBoxVisible( m_pModelInstance->absmin, m_pModelInstance->absmax )) + { + r_stats.c_culled_entities++; + return; + } + + if( R_CullModel( RI->currententity, m_pModelInstance->absmin, m_pModelInstance->absmax )) + { + r_stats.c_culled_entities++; + return; // culled + } + + // no cache for local player in firstperson + if( RP_LOCALCLIENT( RI->currententity ) && !FBitSet( RI->params, RP_THIRDPERSON )) + m_pModelInstance->cached_frame = -1; + + m_pModelInstance->visframe = tr.realframecount; // visible + + if( m_pModelInstance->cached_frame != tr.realframecount ) + { + StudioSetUpTransform( ); + + if( RI->currententity->curstate.movetype == MOVETYPE_FOLLOW && RI->currententity->curstate.aiment > 0 ) + { + cl_entity_t *parent = gEngfuncs.GetEntityByIndex( RI->currententity->curstate.aiment ); + if( parent != NULL && parent->modelhandle != INVALID_HANDLE ) + { + ModelInstance_t *inst = &m_ModelInstances[parent->modelhandle]; + StudioMergeBones( m_pModelInstance->m_protationmatrix, m_pModelInstance->m_pbones, inst->m_pbones, RI->currentmodel, parent->model ); + + mposetobone_t *m = m_pModelInstance->m_pModel->poseToBone; + + // convert bones into compacted GLSL array + if( m != NULL ) + { + for( int i = 0; i < m_pStudioHeader->numbones; i++ ) + { + matrix3x4 out = m_pModelInstance->m_pbones[i].ConcatTransforms( m->posetobone[i] ); + out.CopyToArray4x3( &m_pModelInstance->m_glstudiobones[i*3] ); + m_pModelInstance->m_studioquat[i] = out.GetQuaternion(); + m_pModelInstance->m_studiopos[i] = out.GetOrigin(); + } + } + else + { + for( int i = 0; i < m_pStudioHeader->numbones; i++ ) + { + m_pModelInstance->m_pbones[i].CopyToArray4x3( &m_pModelInstance->m_glstudiobones[i*3] ); + m_pModelInstance->m_studioquat[i] = m_pModelInstance->m_pbones[i].GetQuaternion(); + m_pModelInstance->m_studiopos[i] = m_pModelInstance->m_pbones[i].GetOrigin(); + } + } + } + else + { + ALERT( at_error, "FollowEntity: %i with model %s has missed parent!\n", + RI->currententity->index, RI->currentmodel->name ); + return; + } + } + else StudioSetupBones( ); + + // calc attachments only once per frame + StudioCalcAttachments( m_pModelInstance->m_pbones ); + StudioClientEvents( ); + + if( RI->currententity->index > 0 ) + { + // because RI->currententity may be not equal his index e.g. for viewmodel + cl_entity_t *ent = GET_ENTITY( RI->currententity->index ); + memcpy( ent->attachment, RI->currententity->attachment, sizeof( Vector ) * 4 ); + } + + // grab the static lighting from world + StudioStaticLight( RI->currententity, &m_pModelInstance->light ); + + model_t *pweaponmodel = NULL; + + if( RI->currententity->curstate.weaponmodel ) + pweaponmodel = IEngineStudio.GetModelByIndex( RI->currententity->curstate.weaponmodel ); + + // don't draw p_model for firstperson legs + if( pweaponmodel && ( RI->currentmodel != m_pPlayerLegsModel )) + { + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( pweaponmodel ); + + StudioMergeBones( m_pModelInstance->m_protationmatrix, m_pModelInstance->m_pwpnbones, m_pModelInstance->m_pbones, pweaponmodel, RI->currentmodel ); + + mposetobone_t *m = pweaponmodel->poseToBone; + + // convert bones into compacted GLSL array + if( m != NULL ) + { + for( int i = 0; i < m_pStudioHeader->numbones; i++ ) + { + matrix3x4 out = m_pModelInstance->m_pwpnbones[i].ConcatTransforms( m->posetobone[i] ); + out.CopyToArray4x3( &m_pModelInstance->m_glweaponbones[i*3] ); + m_pModelInstance->m_weaponquat[i] = out.GetQuaternion(); + m_pModelInstance->m_weaponpos[i] = out.GetOrigin(); + } + } + else + { + for( int i = 0; i < m_pStudioHeader->numbones; i++ ) + { + m_pModelInstance->m_pwpnbones[i].CopyToArray4x3( &m_pModelInstance->m_glweaponbones[i*3] ); + m_pModelInstance->m_weaponquat[i] = m_pModelInstance->m_pwpnbones[i].GetQuaternion(); + m_pModelInstance->m_weaponpos[i] = m_pModelInstance->m_pwpnbones[i].GetOrigin(); + } + } + + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( RI->currentmodel ); + } + + // add visible lights to vislight matrix + R_MarkVisibleLights( m_pModelInstance->lights ); + + // now this frame cached + m_pModelInstance->cached_frame = tr.realframecount; + } + + if(( m_iDrawModelType == DRAWSTUDIO_RUNEVENTS ) || r_drawentities->value == 2.0f ) + return; + + model_t *pweaponmodel = NULL; + mbodypart_t *pbodyparts = NULL; + + if( update ) + { + if( RI->currententity->curstate.weaponmodel ) + pweaponmodel = IEngineStudio.GetModelByIndex( RI->currententity->curstate.weaponmodel ); + m_pModelInstance = NULL; + return; + } + + // change shared model with instanced model for this entity (it has personal vertex light cache) + if( m_pModelInstance->m_FlCache != NULL ) + pbodyparts = m_pModelInstance->m_FlCache->bodyparts; + else if( m_pModelInstance->m_VlCache != NULL ) + pbodyparts = m_pModelInstance->m_VlCache->bodyparts; + + for( int i = 0 ; i < m_pStudioHeader->numbodyparts; i++ ) + AddBodyPartToDrawList( m_pStudioHeader, pbodyparts, i, ( RI->currentlight != NULL )); + + if( RI->currententity->curstate.weaponmodel ) + pweaponmodel = IEngineStudio.GetModelByIndex( RI->currententity->curstate.weaponmodel ); + + // don't draw p_model for firstperson legs + if( pweaponmodel && ( RI->currentmodel != m_pPlayerLegsModel )) + { + RI->currentmodel = pweaponmodel; + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( RI->currentmodel ); + + // add weaponmodel parts + for( int i = 0 ; i < m_pStudioHeader->numbodyparts; i++ ) + AddBodyPartToDrawList( m_pStudioHeader, NULL, i, ( RI->currentlight != NULL )); + } + + m_pModelInstance = NULL; +} + +/* +================= +RunViewModelEvents + +================= +*/ +void CStudioModelRenderer :: RunViewModelEvents( void ) +{ + if( !CVAR_TO_BOOL( m_pCvarDrawViewModel )) + return; + + // ignore in thirdperson, camera view or client is died + if( FBitSet( RI->params, RP_THIRDPERSON ) || CL_IsDead() || !UTIL_IsLocal( RI->view.entity )) + return; + + if( !RP_NORMALPASS( )) return; + + if( !IEngineStudio.Mod_Extradata( GET_VIEWMODEL()->model )) + return; + + RI->currententity = GET_VIEWMODEL(); + RI->currentmodel = RI->currententity->model; + if( !RI->currentmodel ) return; + + RI->currententity->curstate.renderamt = R_ComputeFxBlend( RI->currententity ); + + // tell the particle system about visibility + RI->currententity->curstate.messagenum = r_currentMessageNum; + + m_iDrawModelType = DRAWSTUDIO_RUNEVENTS; + + SET_CURRENT_ENTITY( RI->currententity ); + AddStudioModelToDrawList( RI->currententity ); + SET_CURRENT_ENTITY( NULL ); + + m_iDrawModelType = DRAWSTUDIO_NORMAL; + RI->currententity = NULL; + RI->currentmodel = NULL; +} + +bool CStudioModelRenderer :: ComputeCustomFov( matrix4x4 &projMatrix, matrix4x4 &worldViewProjMatrix ) +{ + float flDesiredFOV = 90.0f; + + // bound FOV values + if( m_pCvarViewmodelFov->value < 50 ) + gEngfuncs.Cvar_SetValue( "cl_viewmodel_fov", 50 ); + else if( m_pCvarViewmodelFov->value > 120 ) + gEngfuncs.Cvar_SetValue( "cl_viewmodel_fov", 120 ); + + if( m_pCvarHeadShieldFov->value < 50 ) + gEngfuncs.Cvar_SetValue( "cl_headshield_fov", 50 ); + else if( m_pCvarHeadShieldFov->value > 120 ) + gEngfuncs.Cvar_SetValue( "cl_headshield_fov", 120 ); + + // Find the offset our current FOV is from the default value + float flFOVOffset = 90.0f - (float)RI->view.fov_x; + + switch( m_iDrawModelType ) + { + case DRAWSTUDIO_VIEWMODEL: + // Adjust the viewmodel's FOV to move with any FOV offsets on the viewer's end + m_flViewmodelFov = flDesiredFOV = m_pCvarViewmodelFov->value - flFOVOffset; + break; + case DRAWSTUDIO_HEADSHIELD: + // Adjust the headshield's FOV to move with any FOV offsets on the viewer's end + flDesiredFOV = m_pCvarHeadShieldFov->value - flFOVOffset; + break; + default: // failed case, unchanged + flDesiredFOV = RI->view.fov_x; + break; + } + + // calc local FOV + float fov_x = flDesiredFOV; + float fov_y = V_CalcFov( fov_x, ScreenWidth, ScreenHeight ); + + // don't adjust FOV for viewmodel, faceprotect only + if( m_iDrawModelType == DRAWSTUDIO_HEADSHIELD && RENDER_GET_PARM( PARM_WIDESCREEN, 0 )) + V_AdjustFov( fov_x, fov_y, ScreenWidth, ScreenHeight, false ); + + if( fov_x != RI->view.fov_x ) + { + projMatrix = RI->view.projectionMatrix; + worldViewProjMatrix = RI->view.worldProjectionMatrix; + R_SetupProjectionMatrix( fov_x, fov_y, RI->view.projectionMatrix ); + RI->view.worldProjectionMatrix = RI->view.projectionMatrix.Concat( RI->view.worldMatrix ); + RI->view.worldProjectionMatrix.CopyToArray( RI->glstate.modelviewProjectionMatrix ); + RI->view.projectionMatrix.CopyToArray( RI->glstate.projectionMatrix ); + + pglMatrixMode( GL_PROJECTION ); + pglLoadMatrixf( RI->glstate.projectionMatrix ); + + return true; + } + + return false; +} + +void CStudioModelRenderer :: RestoreNormalFov( matrix4x4 &projMatrix, matrix4x4 &worldViewProjMatrix ) +{ + // restore original matrix + RI->view.projectionMatrix = projMatrix; + RI->view.worldProjectionMatrix = worldViewProjMatrix; + RI->view.worldProjectionMatrix.CopyToArray( RI->glstate.modelviewProjectionMatrix ); + RI->view.projectionMatrix.CopyToArray( RI->glstate.projectionMatrix ); + + pglMatrixMode( GL_PROJECTION ); + pglLoadMatrixf( RI->glstate.projectionMatrix ); +} + +/* +================= +DrawViewModel + +================= +*/ +void CStudioModelRenderer :: DrawViewModel( void ) +{ + cl_entity_t *view = GET_VIEWMODEL(); + + if( !CVAR_TO_BOOL( m_pCvarDrawViewModel )) + return; + + // ignore in thirdperson, camera view or client is died + if( FBitSet( RI->params, RP_THIRDPERSON ) || CL_IsDead() || !UTIL_IsLocal( RI->view.entity )) + return; + + if( !RP_NORMALPASS( ) || g_iGunMode == 3 ) + return; + + // tell the particle system about visibility + view->curstate.messagenum = r_currentMessageNum; + + // hack the depth range to prevent view model from poking into walls + GL_DepthRange( gldepthmin, gldepthmin + 0.3f * ( gldepthmax - gldepthmin )); + + // backface culling for left-handed weapons + if( CVAR_TO_BOOL( m_pCvarHand )) + GL_FrontFace( !glState.frontFace ); + + m_iDrawModelType = DRAWSTUDIO_VIEWMODEL; + RI->frame.solid_meshes.Purge(); + RI->frame.trans_list.Purge(); + view->curstate.weaponmodel = 0; + + matrix4x4 projMatrix, worldViewProjMatrix; + + bool bCustom = ComputeCustomFov( projMatrix, worldViewProjMatrix ); + + // prevent ugly blinking when weapon is changed + view->model = MODEL_HANDLE( gHUD.m_iViewModelIndex ); + + // copy viewhands modelindx into weaponmodel + if( view->index > 0 ) + { + cl_entity_t *ent = GET_ENTITY( view->index ); + view->curstate.weaponmodel = ent->curstate.iuser3; + } + + // we can't draw head shield and viewmodel for once call + // because water blur separates them + AddStudioModelToDrawList( view ); + + if( FBitSet( RI->params, RP_DEFERREDSCENE|RP_DEFERREDLIGHT )) + { + RenderDeferredStudioList(); + } + else + { + RenderSolidStudioList(); + R_RenderTransList(); + } + + if( bCustom ) RestoreNormalFov( projMatrix, worldViewProjMatrix ); + + // restore depth range + GL_DepthRange( gldepthmin, gldepthmax ); + + // backface culling for left-handed weapons + if( CVAR_TO_BOOL( m_pCvarHand )) + GL_FrontFace( !glState.frontFace ); + + m_iDrawModelType = DRAWSTUDIO_NORMAL; + GL_BindShader( NULL ); +} + +/* +================= +DrawHeadShield + +a some copy and paste +from viewmodel code +================= +*/ +void CStudioModelRenderer :: DrawHeadShield( void ) +{ + // g-cont. head shield must thinking always + if( !HeadShieldThink( )) return; + + // ignore in thirdperson, camera view or some special passes + if( FBitSet( RI->params, RP_THIRDPERSON ) || !UTIL_IsLocal( RI->view.entity )) + return; + + if( !RP_NORMALPASS( )) return; + + // load shield once only + if( !IEngineStudio.Mod_Extradata( gHUD.m_pHeadShieldEnt->model )) + return; + + RI->currententity = gHUD.m_pHeadShieldEnt; + RI->currentmodel = gHUD.m_pHeadShieldEnt->model; + if( !RI->currentmodel ) return; + + SET_CURRENT_ENTITY( RI->currententity ); + RI->currententity->curstate.renderamt = R_ComputeFxBlend( RI->currententity ); + + // g-cont. current offset for headshield is match with original paranoia code + gHUD.m_pHeadShieldEnt->origin = gHUD.m_pHeadShieldEnt->curstate.origin = GetVieworg() + GetVForward() * gHUD.m_pHeadShieldEnt->curstate.fuser2 + GetVUp() * 1.8f; + gHUD.m_pHeadShieldEnt->angles = gHUD.m_pHeadShieldEnt->curstate.angles = RI->view.angles; + + // rotate face to player + gHUD.m_pHeadShieldEnt->angles[PITCH] *= -1; + gHUD.m_pHeadShieldEnt->curstate.angles[PITCH] *= -1; + + m_iDrawModelType = DRAWSTUDIO_HEADSHIELD; + + // don't affect headshield by any dynamic lights! + ClearBits( RI->view.flags, RF_HASDYNLIGHTS ); + RI->frame.solid_meshes.Purge(); + RI->frame.trans_list.Purge(); + + // clearing depth buffer to draw headshield always on a top + // like nextView = 1 in original code + pglClear( GL_DEPTH_BUFFER_BIT ); + pglDisable( GL_DEPTH_TEST ); + + matrix4x4 projMatrix, worldViewProjMatrix; + + bool bCustom = ComputeCustomFov( projMatrix, worldViewProjMatrix ); + + // we can't draw head shield and viewmodel for once call + // because water blur separates them + AddStudioModelToDrawList( RI->currententity ); + + if( FBitSet( RI->params, RP_DEFERREDSCENE|RP_DEFERREDLIGHT )) + { + RenderDeferredStudioList(); + } + else + { + RenderSolidStudioList(); + R_RenderTransList(); + } + + if( bCustom ) RestoreNormalFov( projMatrix, worldViewProjMatrix ); + + m_iDrawModelType = DRAWSTUDIO_NORMAL; + + pglEnable( GL_DEPTH_TEST ); + SET_CURRENT_ENTITY( NULL ); + RI->currententity = NULL; + RI->currentmodel = NULL; + GL_BindShader( NULL ); +} + +void CStudioModelRenderer :: DrawMeshFromBuffer( const vbomesh_t *mesh ) +{ + pglBindVertexArray( mesh->vao ); + + if( GL_Support( R_DRAW_RANGEELEMENTS_EXT )) + pglDrawRangeElementsEXT( GL_TRIANGLES, 0, mesh->numVerts - 1, mesh->numElems, GL_UNSIGNED_INT, 0 ); + else pglDrawElements( GL_TRIANGLES, mesh->numElems, GL_UNSIGNED_INT, 0 ); + + r_stats.c_total_tris += (mesh->numElems / 3); + r_stats.num_flushes++; +} + +void CStudioModelRenderer :: BuildMeshListForLight( CDynLight *pl, bool solid ) +{ + RI->frame.light_meshes.Purge(); + Vector bounds[2]; + + if( solid ) + { + // check solid meshes for light intersection + for( int i = 0; i < RI->frame.solid_meshes.Count(); i++ ) + { + CSolidEntry *entry = &RI->frame.solid_meshes[i]; + StudioGetBounds( entry, bounds ); + + if( pl->frustum.CullBox( bounds[0], bounds[1] )) + continue; // no interaction + + // setup the global pointers + if( !StudioSetEntity( entry )) + continue; + + if( RI->currententity->curstate.renderfx == SKYBOX_ENTITY ) + continue; // fast reject + + if( FBitSet( RI->currententity->curstate.effects, EF_FULLBRIGHT )) + continue; + + AddMeshToDrawList( m_pStudioHeader, entry->m_pMesh, true ); + } + } + else + { + // check trans meshes for light intersection + for( int i = 0; i < RI->frame.trans_list.Count(); i++ ) + { + CTransEntry *entry = &RI->frame.trans_list[i]; + + if( entry->m_bDrawType != DRAWTYPE_MESH ) + continue; + + StudioGetBounds( entry, bounds ); + if( pl->frustum.CullBox( bounds[0], bounds[1] )) + continue; // no interaction + + // setup the global pointers + if( !StudioSetEntity( entry )) + continue; + + if( RI->currententity->curstate.renderfx == SKYBOX_ENTITY ) + continue; // fast reject + + if( FBitSet( RI->currententity->curstate.effects, EF_FULLBRIGHT )) + continue; + + AddMeshToDrawList( m_pStudioHeader, entry->m_pMesh, true ); + } + } +} + +void CStudioModelRenderer :: DrawLightForMeshList( CDynLight *pl ) +{ + pglBlendFunc( GL_ONE, GL_ONE ); + float y2 = (float)RI->view.port[3] - pl->h - pl->y; + pglScissor( pl->x, y2, pl->w, pl->h ); + + // sorting list to reduce shader switches + if( !CVAR_TO_BOOL( cv_nosort )) + RI->frame.light_meshes.Sort( SortSolidMeshes ); + + pglAlphaFunc( GL_GEQUAL, 0.5f ); + RI->currententity = NULL; + RI->currentmodel = NULL; + m_pCurrentMaterial = NULL; + + for( int i = 0; i < RI->frame.light_meshes.Count(); i++ ) + { + CSolidEntry *entry = &RI->frame.light_meshes[i]; + DrawSingleMesh( entry, ( i == 0 )); + } + + GL_Cull( GL_FRONT ); +} + +void CStudioModelRenderer :: RenderDynLightList( bool solid ) +{ + if( FBitSet( RI->params, RP_ENVVIEW|RP_SKYVIEW )) + return; + + if( !FBitSet( RI->view.flags, RF_HASDYNLIGHTS )) + return; + + if( R_FullBright( )) return; + + GL_Blend( GL_TRUE ); + GL_AlphaTest( GL_FALSE ); + GL_DepthMask( GL_FALSE ); + + CDynLight *pl = tr.dlights; + + for( int i = 0; i < MAX_DLIGHTS; i++, pl++ ) + { + if( pl->Expired( )) continue; + + if( pl->type == LIGHT_SPOT || pl->type == LIGHT_OMNI ) + { + if( !pl->Active( )) continue; + + if( !Mod_CheckBoxVisible( pl->absmin, pl->absmax )) + continue; + + if( R_CullFrustum( &pl->frustum )) + continue; + + pglEnable( GL_SCISSOR_TEST ); + } + else + { + // couldn't use scissor for sunlight + pglDisable( GL_SCISSOR_TEST ); + } + + RI->currentlight = pl; + + // draw world from light position + BuildMeshListForLight( pl, solid ); + + if( !RI->frame.light_meshes.Count( )) + continue; // no interaction with this light? + + DrawLightForMeshList( pl ); + } + + GL_SelectTexture( glConfig.max_texture_units - 1 ); // force to cleanup all the units + pglDisable( GL_SCISSOR_TEST ); + GL_CleanUpTextureUnits( 0 ); + RI->currentlight = NULL; +} + +void CStudioModelRenderer :: RenderDeferredStudioList( void ) +{ + if( !RI->frame.solid_meshes.Count() ) + return; + + pglAlphaFunc( GL_GEQUAL, 0.5f ); + GL_Blend( GL_FALSE ); + GL_AlphaTest( GL_FALSE ); + GL_DepthMask( GL_TRUE ); + + if( GL_Support( R_SEAMLESS_CUBEMAP )) + pglEnable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); + + // sorting list to reduce shader switches + if( !CVAR_TO_BOOL( cv_nosort )) + RI->frame.solid_meshes.Sort( SortSolidMeshes ); + + RI->currententity = NULL; + RI->currentmodel = NULL; + m_pCurrentMaterial = NULL; + + for( int i = 0; i < RI->frame.solid_meshes.Count(); i++ ) + { + CSolidEntry *entry = &RI->frame.solid_meshes[i]; + + if( entry->m_bDrawType != DRAWTYPE_MESH ) + continue; + + if( m_iDrawModelType == DRAWSTUDIO_NORMAL ) + { + if( entry->m_pParentEntity->curstate.renderfx == SKYBOX_ENTITY ) + { + GL_DepthRange( 0.8f, 0.9f ); + GL_ClipPlane( false ); + } + else + { + GL_DepthRange( gldepthmin, gldepthmax ); + GL_ClipPlane( true ); + } + } + + DrawSingleMesh( entry, ( i == 0 )); + } + + if( GL_Support( R_SEAMLESS_CUBEMAP )) + pglDisable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); + + if( m_iDrawModelType == DRAWSTUDIO_NORMAL ) + GL_DepthRange( gldepthmin, gldepthmax ); + GL_CleanupDrawState(); + GL_AlphaTest( GL_FALSE ); + GL_ClipPlane( true ); + GL_Cull( GL_FRONT ); + + // now draw studio decals + for( i = 0; i < RI->frame.solid_meshes.Count(); i++ ) + { +// DrawDecal( &RI->frame.solid_meshes[i] ); + } +} + +word CStudioModelRenderer :: ShaderSceneForward( mstudiomaterial_t *mat, int lightmode, bool bone_weighting, int numbones ) +{ + char glname[64]; + char options[MAX_OPTIONS_LENGTH]; + bool shader_translucent = false; + bool using_screenrect = false; + bool using_cubemaps = false; + + if( mat->forwardScene.IsValid() && mat->lastRenderMode == RI->currententity->curstate.rendermode ) + return mat->forwardScene.GetHandle(); // valid + + Q_strncpy( glname, "forward/scene_studio", sizeof( glname )); + memset( options, 0, sizeof( options )); + + if( numbones == 1 ) + GL_AddShaderDirective( options, "MAXSTUDIOBONES 1" ); + + if( bone_weighting ) + GL_AddShaderDirective( options, "APPLY_BONE_WEIGHTING" ); + + if( FBitSet( mat->flags, STUDIO_NF_CHROME )) + GL_AddShaderDirective( options, "HAS_CHROME" ); + + GL_CheckTextureAlpha( options, mat->gl_diffuse_id ); + + if( RI->currententity->curstate.rendermode == kRenderTransAdd || RI->currententity->curstate.rendermode == kRenderGlow ) + { + // additive and glow is always fullbright + GL_AddShaderDirective( options, "LIGHTING_FULLBRIGHT" ); + + if( RI->currententity->curstate.rendermode == kRenderTransAdd ) + shader_translucent = true; + } + else if( FBitSet( mat->flags, STUDIO_NF_FULLBRIGHT ) || R_FullBright() || FBitSet( RI->currententity->curstate.effects, EF_FULLBRIGHT )) + { + GL_AddShaderDirective( options, "LIGHTING_FULLBRIGHT" ); + } + else + { + if( CVAR_TO_BOOL( cv_brdf )) + GL_AddShaderDirective( options, "APPLY_PBS" ); + + if( lightmode == LIGHTSTATIC_VERTEX ) + GL_AddShaderDirective( options, "VERTEX_LIGHTING" ); + else if( lightmode == LIGHTSTATIC_SURFACE ) + GL_AddShaderDirective( options, "SURFACE_LIGHTING" ); + else if( FBitSet( mat->flags, STUDIO_NF_FLATSHADE )) + GL_AddShaderDirective( options, "LIGHTING_FLATSHADE" ); + + if( lightmode == LIGHTSTATIC_SURFACE ) + { + // process lightstyles + for( int i = 0; i < MAXLIGHTMAPS && m_pModelInstance->styles[i] != LS_NONE; i++ ) + { + if( tr.sun_light_enabled && m_pModelInstance->styles[i] == LS_SKY ) + continue; // skip the sunlight due realtime sun is enabled + GL_AddShaderDirective( options, va( "APPLY_STYLE%i", i )); + } + + if( FBitSet( world->features, WORLD_HAS_DELUXEMAP )) + GL_AddShaderDirective( options, "HAS_DELUXEMAP" ); + } + + // debug visualization + if( r_lightmap->value > 0.0f && r_lightmap->value <= 2.0f ) + { + if( r_lightmap->value == 1.0f && worldmodel->lightdata ) + GL_AddShaderDirective( options, "LIGHTMAP_DEBUG" ); + else if( r_lightmap->value == 2.0f && FBitSet( world->features, WORLD_HAS_DELUXEMAP )) + GL_AddShaderDirective( options, "LIGHTVEC_DEBUG" ); + } + + // deluxemap required + if( !RP_CUBEPASS() && ( CVAR_TO_BOOL( cv_bump ) && FBitSet( world->features, WORLD_HAS_DELUXEMAP ) && FBitSet( mat->flags, STUDIO_NF_NORMALMAP ))) + { + GL_AddShaderDirective( options, "HAS_NORMALMAP" ); + GL_EncodeNormal( options, mat->gl_normalmap_id ); + GL_AddShaderDirective( options, "COMPUTE_TBN" ); + } + + // deluxemap required + if( !RP_CUBEPASS() && ( CVAR_TO_BOOL( cv_specular ) && FBitSet( world->features, WORLD_HAS_DELUXEMAP ) && FBitSet( mat->flags, STUDIO_NF_GLOSSMAP ))) + GL_AddShaderDirective( options, "HAS_GLOSSMAP" ); + + if( FBitSet( mat->flags, STUDIO_NF_LUMA )) + GL_AddShaderDirective( options, "HAS_LUMA" ); + } + + if( mat->aberrationScale > 0.0f && Q_stristr( options, "HAS_NORMALMAP" )) + GL_AddShaderDirective( options, "APPLY_ABERRATION" ); + + if( mat->refractScale > 0.0f && Q_stristr( options, "HAS_NORMALMAP" )) + GL_AddShaderDirective( options, "APPLY_REFRACTION" ); + + if(( world->num_cubemaps > 0 ) && CVAR_TO_BOOL( cv_cubemaps ) && (mat->reflectScale > 0.0f) && !RP_CUBEPASS( )) + { + GL_AddShaderDirective( options, "REFLECTION_CUBEMAP" ); + using_cubemaps = true; + } + + if( tr.fogEnabled ) + GL_AddShaderDirective( options, "APPLY_FOG_EXP" ); + + // mixed mode: solid & transparent controlled by alpha-channel + if( FBitSet( mat->flags, STUDIO_NF_ADDITIVE ) && RI->currententity->curstate.rendermode != kRenderGlow ) + { + if( FBitSet( mat->flags, STUDIO_NF_HAS_ALPHA )) + GL_AddShaderDirective( options, "ALPHA_GLASS" ); + shader_translucent = true; + } + + if( RI->currententity->curstate.rendermode == kRenderTransColor || RI->currententity->curstate.rendermode == kRenderTransTexture ) + shader_translucent = true; + + if( shader_translucent ) + GL_AddShaderDirective( options, "TRANSLUCENT" ); + + if( FBitSet( mat->flags, STUDIO_NF_HAS_DETAIL ) && CVAR_TO_BOOL( r_detailtextures )) + GL_AddShaderDirective( options, "HAS_DETAIL" ); + + word shaderNum = GL_FindUberShader( glname, options ); + if( !shaderNum ) + { + SetBits( mat->flags, STUDIO_NF_NODRAW ); + return 0; // something bad happens + } + + if( shader_translucent ) + GL_AddShaderFeature( shaderNum, SHADER_TRANSLUCENT|SHADER_USE_SCREENCOPY ); + + if( using_cubemaps ) + GL_AddShaderFeature( shaderNum, SHADER_USE_CUBEMAPS ); + + // done + mat->lastRenderMode = RI->currententity->curstate.rendermode; + ClearBits( mat->flags, STUDIO_NF_NODRAW ); + mat->forwardScene.SetShader( shaderNum ); + + return shaderNum; +} + +word CStudioModelRenderer :: ShaderLightForward( CDynLight *dl, mstudiomaterial_t *mat, bool bone_weighting, int numbones ) +{ + char glname[64]; + char options[MAX_OPTIONS_LENGTH]; + + switch( dl->type ) + { + case LIGHT_SPOT: + if( mat->forwardLightSpot.IsValid( )) + return mat->forwardLightSpot.GetHandle(); // valid + break; + case LIGHT_OMNI: + if( mat->forwardLightOmni.IsValid( )) + return mat->forwardLightOmni.GetHandle(); // valid + break; + case LIGHT_DIRECTIONAL: + if( mat->forwardLightProj.IsValid( )) + return mat->forwardLightProj.GetHandle(); // valid + break; + } + + Q_strncpy( glname, "forward/light_studio", sizeof( glname )); + memset( options, 0, sizeof( options )); + + switch( dl->type ) + { + case LIGHT_SPOT: + GL_AddShaderDirective( options, "LIGHT_SPOT" ); + break; + case LIGHT_OMNI: + GL_AddShaderDirective( options, "LIGHT_OMNI" ); + break; + case LIGHT_DIRECTIONAL: + GL_AddShaderDirective( options, "LIGHT_PROJ" ); + break; + } + + if( numbones == 1 ) + GL_AddShaderDirective( options, "MAXSTUDIOBONES 1" ); + + if( bone_weighting ) + GL_AddShaderDirective( options, "APPLY_BONE_WEIGHTING" ); + + if( FBitSet( mat->flags, STUDIO_NF_CHROME )) + GL_AddShaderDirective( options, "HAS_CHROME" ); + + if( CVAR_TO_BOOL( cv_brdf )) + GL_AddShaderDirective( options, "APPLY_PBS" ); + + if( FBitSet( mat->flags, STUDIO_NF_HAS_DETAIL ) && CVAR_TO_BOOL( r_detailtextures )) + GL_AddShaderDirective( options, "HAS_DETAIL" ); + + GL_CheckTextureAlpha( options, mat->gl_diffuse_id ); + + if( CVAR_TO_BOOL( cv_bump ) && FBitSet( mat->flags, STUDIO_NF_NORMALMAP ) && !FBitSet( dl->flags, DLF_NOBUMP )) + { + GL_AddShaderDirective( options, "HAS_NORMALMAP" ); + GL_EncodeNormal( options, mat->gl_normalmap_id ); + GL_AddShaderDirective( options, "COMPUTE_TBN" ); + } + + if( CVAR_TO_BOOL( cv_specular ) && FBitSet( mat->flags, STUDIO_NF_GLOSSMAP )) + GL_AddShaderDirective( options, "HAS_GLOSSMAP" ); + + if( CVAR_TO_BOOL( r_shadows ) && !FBitSet( dl->flags, DLF_NOSHADOWS )) + { + // shadow cubemaps only support if GL_EXT_gpu_shader4 is support + if( dl->type == LIGHT_DIRECTIONAL && CVAR_TO_BOOL( r_sunshadows )) + { + GL_AddShaderDirective( options, "APPLY_SHADOW" ); + } + else if( dl->type == LIGHT_SPOT || GL_Support( R_EXT_GPU_SHADER4 )) + { + GL_AddShaderDirective( options, "APPLY_SHADOW" ); + + if( r_shadows->value == 2.0f ) + GL_AddShaderDirective( options, "SHADOW_PCF2X2" ); + else if( r_shadows->value >= 3.0f ) + GL_AddShaderDirective( options, "SHADOW_PCF3X3" ); + } + } + + word shaderNum = GL_FindUberShader( glname, options ); + + if( !shaderNum ) + { + if( dl->type == LIGHT_DIRECTIONAL ) + SetBits( mat->flags, STUDIO_NF_NOSUNLIGHT ); + else SetBits( mat->flags, STUDIO_NF_NODLIGHT ); + + return 0; // something bad happens + } + + // done + switch( dl->type ) + { + case LIGHT_SPOT: + mat->forwardLightSpot.SetShader( shaderNum ); + ClearBits( mat->flags, STUDIO_NF_NODLIGHT ); + break; + case LIGHT_OMNI: + mat->forwardLightOmni.SetShader( shaderNum ); + ClearBits( mat->flags, STUDIO_NF_NODLIGHT ); + break; + case LIGHT_DIRECTIONAL: + mat->forwardLightProj.SetShader( shaderNum ); + ClearBits( mat->flags, STUDIO_NF_NOSUNLIGHT ); + break; + } + + return shaderNum; +} + +word CStudioModelRenderer :: ShaderSceneDeferred( mstudiomaterial_t *mat, bool bone_weighting, int numbones ) +{ + char glname[64]; + char options[MAX_OPTIONS_LENGTH]; + bool using_cubemaps = false; + + if( mat->deferredScene.IsValid( )) + return mat->deferredScene.GetHandle(); // valid + + Q_strncpy( glname, "deferred/scene_studio", sizeof( glname )); + memset( options, 0, sizeof( options )); + + if( numbones == 1 ) + GL_AddShaderDirective( options, "MAXSTUDIOBONES 1" ); + + if( bone_weighting ) + GL_AddShaderDirective( options, "APPLY_BONE_WEIGHTING" ); + + if( FBitSet( mat->flags, STUDIO_NF_CHROME )) + GL_AddShaderDirective( options, "HAS_CHROME" ); + + if( FBitSet( mat->flags, STUDIO_NF_FULLBRIGHT ) || R_FullBright( )) + GL_AddShaderDirective( options, "LIGHTING_FULLBRIGHT" ); + + if( FBitSet( mat->flags, STUDIO_NF_FLATSHADE )) + GL_AddShaderDirective( options, "LIGHTING_FLATSHADE" ); + + if( FBitSet( mat->flags, STUDIO_NF_LUMA )) + GL_AddShaderDirective( options, "HAS_LUMA" ); + + if( FBitSet( mat->flags, STUDIO_NF_HAS_DETAIL ) && CVAR_TO_BOOL( r_detailtextures ) && glConfig.max_varying_floats > 48 ) + GL_AddShaderDirective( options, "HAS_DETAIL" ); + + if( !RP_CUBEPASS() && ( FBitSet( mat->flags, STUDIO_NF_NORMALMAP ) && CVAR_TO_BOOL( cv_bump ))) + { + GL_AddShaderDirective( options, "HAS_NORMALMAP" ); + GL_EncodeNormal( options, mat->gl_normalmap_id ); + GL_AddShaderDirective( options, "COMPUTE_TBN" ); + } + + if( !RP_CUBEPASS() && ( CVAR_TO_BOOL( cv_specular ) && FBitSet( mat->flags, STUDIO_NF_GLOSSMAP ))) + GL_AddShaderDirective( options, "HAS_GLOSSMAP" ); + + if(( world->num_cubemaps > 0 ) && CVAR_TO_BOOL( cv_cubemaps ) && (mat->reflectScale > 0.0f) && !RP_CUBEPASS( )) + { + GL_AddShaderDirective( options, "REFLECTION_CUBEMAP" ); + using_cubemaps = true; + } + + word shaderNum = GL_FindUberShader( glname, options ); + if( !shaderNum ) + { + SetBits( mat->flags, STUDIO_NF_NODRAW ); + return 0; // something bad happens + } + + if( using_cubemaps ) + GL_AddShaderFeature( shaderNum, SHADER_USE_CUBEMAPS ); + + // done + ClearBits( mat->flags, STUDIO_NF_NODRAW ); + mat->deferredScene.SetShader( shaderNum ); + + return shaderNum; +} + +word CStudioModelRenderer :: ShaderLightDeferred( mstudiomaterial_t *mat, bool bone_weighting, int numbones ) +{ + char glname[64]; + char options[MAX_OPTIONS_LENGTH]; + + if( mat->deferredLight.IsValid( )) + return mat->deferredLight.GetHandle(); // valid + + Q_strncpy( glname, "deferred/light_studio", sizeof( glname )); + memset( options, 0, sizeof( options )); + + if( numbones == 1 ) + GL_AddShaderDirective( options, "MAXSTUDIOBONES 1" ); + + if( bone_weighting ) + GL_AddShaderDirective( options, "APPLY_BONE_WEIGHTING" ); + + if( FBitSet( mat->flags, STUDIO_NF_FULLBRIGHT ) || R_FullBright( )) + GL_AddShaderDirective( options, "LIGHTING_FULLBRIGHT" ); + + if( FBitSet( mat->flags, STUDIO_NF_FLATSHADE )) + GL_AddShaderDirective( options, "LIGHTING_FLATSHADE" ); + + if( FBitSet( mat->flags, STUDIO_NF_LUMA )) + GL_AddShaderDirective( options, "HAS_LUMA" ); + + word shaderNum = GL_FindUberShader( glname, options ); + if( !shaderNum ) + { + SetBits( mat->flags, STUDIO_NF_NODRAW ); + return 0; // something bad happens + } + + // done + ClearBits( mat->flags, STUDIO_NF_NODRAW ); + mat->deferredLight.SetShader( shaderNum ); + + return shaderNum; +} + +word CStudioModelRenderer :: ShaderSceneDepth( mstudiomaterial_t *mat, bool bone_weighting, int numbones ) +{ + char glname[64]; + char options[MAX_OPTIONS_LENGTH]; + + if( mat->forwardDepth.IsValid( )) + return mat->forwardDepth.GetHandle(); // valid + + Q_strncpy( glname, "forward/depth_studio", sizeof( glname )); + memset( options, 0, sizeof( options )); + + if( numbones == 1 ) + GL_AddShaderDirective( options, "MAXSTUDIOBONES 1" ); + + if( bone_weighting ) + GL_AddShaderDirective( options, "APPLY_BONE_WEIGHTING" ); + + word shaderNum = GL_FindUberShader( glname, options ); + if( !shaderNum ) return 0; // something bad happens + + mat->forwardDepth.SetShader( shaderNum ); + + return shaderNum; +} + +void CStudioModelRenderer :: DrawSingleMesh( CSolidEntry *entry, bool force ) +{ + bool cache_has_changed = false; + static int cached_lightmap = -1; + Vector4D lightstyles, lightdir; + bool weapon_model = false; + float r, g, b, a, *v; + int map; + + if( entry->m_bDrawType != DRAWTYPE_MESH ) + return; + + if( RI->currentmodel != entry->m_pRenderModel ) + cache_has_changed = true; + + if( RI->currententity != entry->m_pParentEntity ) + cache_has_changed = true; + + RI->currentmodel = entry->m_pRenderModel; + cl_entity_t *e = RI->currententity = entry->m_pParentEntity; + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( RI->currentmodel ); + int m_skinnum = bound( 0, RI->currententity->curstate.skin, MAXSTUDIOSKINS - 1 ); + + ASSERT( RI->currententity->modelhandle != INVALID_HANDLE ); + + ModelInstance_t *inst = m_pModelInstance = &m_ModelInstances[e->modelhandle]; + int num_bones = Q_min( m_pStudioHeader->numbones, glConfig.max_skinning_bones ); + vbomesh_t *pMesh = entry->m_pMesh; + mstudiomaterial_t *mat; + + if( pMesh->lightmapnum != cached_lightmap ) + { + cached_lightmap = pMesh->lightmapnum; + cache_has_changed = true; + } + + short *pskinref = (short *)((byte *)m_pStudioHeader + m_pStudioHeader->skinindex); + if( m_skinnum != 0 && m_skinnum < m_pStudioHeader->numskinfamilies ) + pskinref += (m_skinnum * m_pStudioHeader->numskinref); + + if( entry->m_pRenderModel == IEngineStudio.GetModelByIndex( e->curstate.weaponmodel )) + weapon_model = true; + + if( weapon_model || RI->currentmodel == m_pPlayerLegsModel ) + mat = &entry->m_pRenderModel->materials[pskinref[pMesh->skinref]]; + else mat = &m_pModelInstance->materials[pskinref[pMesh->skinref]]; // NOTE: use local copy for right cache shadernums + mstudiolight_t *light = &inst->light; + + if( mat != m_pCurrentMaterial ) + cache_has_changed = true; + m_pCurrentMaterial = mat; + + if( FBitSet( RI->params, RP_DEFERREDLIGHT|RP_DEFERREDSCENE )) + { + if( FBitSet( RI->params, RP_DEFERREDSCENE )) + entry->m_hProgram = mat->deferredScene.GetHandle(); + else if( FBitSet( RI->params, RP_DEFERREDLIGHT )) + entry->m_hProgram = mat->deferredLight.GetHandle(); + else entry->m_hProgram = 0; + + if( !entry->m_hProgram ) return; + } + + if( force || ( RI->currentshader != &glsl_programs[entry->m_hProgram] )) + { + // force to bind new shader + GL_BindShader( &glsl_programs[entry->m_hProgram] ); + cache_has_changed = true; + } + + if( FBitSet( RI->params, RP_SHADOWVIEW|RP_DEFERREDSCENE )) + { + if( FBitSet( mat->flags, STUDIO_NF_MASKED )) + { + pglAlphaFunc( GL_GEQUAL, 0.5f ); + GL_AlphaTest( GL_TRUE ); + } + else if( FBitSet( mat->flags, STUDIO_NF_HAS_ALPHA )) + { + pglAlphaFunc( GL_GEQUAL, 0.999f ); + GL_AlphaTest( GL_TRUE ); + } + else GL_AlphaTest( GL_FALSE ); + } + else + { + if( FBitSet( mat->flags, STUDIO_NF_MASKED )) + GL_AlphaTest( GL_TRUE ); + else GL_AlphaTest( GL_FALSE ); + } + + if( !FBitSet( RI->params, RP_SHADOWVIEW )) + { + if( FBitSet( mat->flags, STUDIO_NF_TWOSIDE )) + GL_Cull( GL_NONE ); + else GL_Cull( GL_FRONT ); + } + + // each transparent surfaces reqiured an actual screencopy + if( ScreenCopyRequired( RI->currentshader ) && entry->IsTranslucent( )) + { + CTransEntry *trans = (CTransEntry *)entry; + + if( trans->m_bScissorReady ) + { + trans->RequestScreenColor(); + cache_has_changed = true; // force to refresh uniforms + r_stats.c_screen_copy++; + } + } + + glsl_program_t *shader = RI->currentshader; + CDynLight *pl = RI->currentlight; // may be NULL + + // sometime we can't set the uniforms + if( !cache_has_changed || !shader || !shader->numUniforms || !shader->uniforms ) + { + // just draw mesh and out + DrawMeshFromBuffer( pMesh ); + return; + } + + // setup specified uniforms (and texture bindings) + for( int i = 0; i < shader->numUniforms; i++ ) + { + uniform_t *u = &shader->uniforms[i]; + + switch( u->type ) + { + case UT_COLORMAP: + u->SetValue( mat->gl_diffuse_id ); + break; + case UT_NORMALMAP: + u->SetValue( mat->gl_normalmap_id ); + break; + case UT_GLOSSMAP: + u->SetValue( mat->gl_specular_id ); + break; + case UT_DETAILMAP: + u->SetValue( mat->gl_detailmap_id ); + break; + case UT_PROJECTMAP: + if( pl && pl->type == LIGHT_SPOT ) + u->SetValue( pl->spotlightTexture ); + else u->SetValue( tr.whiteTexture ); + break; + case UT_SHADOWMAP: + case UT_SHADOWMAP0: + if( pl ) u->SetValue( pl->shadowTexture[0] ); + else u->SetValue( tr.depthTexture ); + break; + case UT_SHADOWMAP1: + if( pl ) u->SetValue( pl->shadowTexture[1] ); + else u->SetValue( tr.depthTexture ); + break; + case UT_SHADOWMAP2: + if( pl ) u->SetValue( pl->shadowTexture[2] ); + else u->SetValue( tr.depthTexture ); + break; + case UT_SHADOWMAP3: + if( pl ) u->SetValue( pl->shadowTexture[3] ); + else u->SetValue( tr.depthTexture ); + break; + case UT_LIGHTMAP: + if( pMesh->lightmapnum != -1 ) + u->SetValue( tr.lightmaps[pMesh->lightmapnum].lightmap ); + else u->SetValue( tr.whiteTexture ); + break; + case UT_DELUXEMAP: + if( pMesh->lightmapnum != -1 ) + u->SetValue( tr.lightmaps[pMesh->lightmapnum].deluxmap ); + else u->SetValue( tr.whiteTexture ); + break; + case UT_DECALMAP: + // unacceptable for studiomodels + u->SetValue( tr.whiteTexture ); + break; + case UT_SCREENMAP: + u->SetValue( tr.screen_color ); + break; + case UT_DEPTHMAP: + u->SetValue( tr.screen_depth ); + break; + case UT_ENVMAP0: + case UT_ENVMAP: + if( inst->cubemap[0] != NULL ) + u->SetValue( inst->cubemap[0]->texture ); + else u->SetValue( tr.whiteCubeTexture ); + break; + case UT_ENVMAP1: + if( inst->cubemap[1] != NULL ) + u->SetValue( inst->cubemap[1]->texture ); + else u->SetValue( tr.whiteCubeTexture ); + break; + case UT_GLOWMAP: + u->SetValue( mat->gl_glowmap_id ); + break; + case UT_HEIGHTMAP: + u->SetValue( mat->gl_heightmap_id ); + break; + case UT_BSPPLANESMAP: + u->SetValue( tr.packed_planes_texture ); + break; + case UT_BSPNODESMAP: + u->SetValue( tr.packed_nodes_texture ); + break; + case UT_BSPLIGHTSMAP: + u->SetValue( tr.packed_lights_texture ); + break; + case UT_FITNORMALMAP: + u->SetValue( tr.normalsFitting ); + break; + case UT_MODELMATRIX: + u->SetValue( &inst->m_glmatrix[0] ); + break; + case UT_BONESARRAY: + if( weapon_model ) + u->SetValue( &inst->m_glweaponbones[0][0], num_bones * 3 ); + else u->SetValue( &inst->m_glstudiobones[0][0], num_bones * 3 ); + break; + case UT_BONEQUATERNION: + if( weapon_model ) + u->SetValue( &inst->m_weaponquat[0][0], num_bones ); + else u->SetValue( &inst->m_studioquat[0][0], num_bones ); + break; + case UT_BONEPOSITION: + if( weapon_model ) + u->SetValue( &inst->m_weaponpos[0][0], num_bones ); + else u->SetValue( &inst->m_studiopos[0][0], num_bones ); + break; + case UT_SCREENSIZEINV: + u->SetValue( 1.0f / (float)glState.width, 1.0f / (float)glState.height ); + break; + case UT_ZFAR: + u->SetValue( -tr.farclip * 1.74f ); + break; + case UT_LIGHTSTYLES: + for( map = 0; map < MAXLIGHTMAPS; map++ ) + { + if( inst->styles[map] != 255 ) + lightstyles[map] = tr.lightstyle[inst->styles[map]]; + else lightstyles[map] = 0.0f; + } + u->SetValue( lightstyles.x, lightstyles.y, lightstyles.z, lightstyles.w ); + break; + case UT_LIGHTSTYLEVALUES: + u->SetValue( &tr.lightstyle[0], MAX_LIGHTSTYLES ); + break; + case UT_REALTIME: + u->SetValue( (float)tr.time ); + break; + case UT_DETAILSCALE: + u->SetValue( mat->detailScale[0], mat->detailScale[1] ); + break; + case UT_FOGPARAMS: + if( e->curstate.renderfx == SKYBOX_ENTITY ) + u->SetValue( tr.fogColor[0], tr.fogColor[1], tr.fogColor[2], tr.fogSkyDensity ); + else u->SetValue( tr.fogColor[0], tr.fogColor[1], tr.fogColor[2], tr.fogDensity ); + break; + case UT_SHADOWPARMS: + if( pl != NULL ) + { + float shadowWidth = 1.0f / (float)RENDER_GET_PARM( PARM_TEX_WIDTH, pl->shadowTexture[0] ); + float shadowHeight = 1.0f / (float)RENDER_GET_PARM( PARM_TEX_HEIGHT, pl->shadowTexture[0] ); + // depth scale and bias and shadowmap resolution + u->SetValue( shadowWidth, shadowHeight, -pl->projectionMatrix[2][2], pl->projectionMatrix[3][2] ); + } + else u->SetValue( 0.0f, 0.0f, 0.0f, 0.0f ); + break; + case UT_TEXOFFSET: // FIXME: implement conveyors? + u->SetValue( 0.0f, 0.0f ); + break; + case UT_VIEWORIGIN: + u->SetValue( GetVieworg().x, GetVieworg().y, GetVieworg().z ); + break; + case UT_VIEWRIGHT: + u->SetValue( GetVRight().x, GetVRight().y, GetVRight().z ); + break; + case UT_RENDERCOLOR: + if( e->curstate.rendermode == kRenderNormal ) + { + r = g = b = a = 1.0f; + } + else + { + int sum = (e->curstate.rendercolor.r + e->curstate.rendercolor.g + e->curstate.rendercolor.b); + + if( sum > 0 ) + { + r = e->curstate.rendercolor.r / 255.0f; + g = e->curstate.rendercolor.g / 255.0f; + b = e->curstate.rendercolor.b / 255.0f; + } + else + { + r = g = b = 1.0f; + } + + if( e->curstate.rendermode != kRenderTransAlpha ) + a = e->curstate.renderamt / 255.0f; + else a = 1.0f; + } + + if( FBitSet( mat->flags, STUDIO_NF_ADDITIVE )) + a = 0.65f; // FIXME: 0.5 looks ugly + u->SetValue( r, g, b, a ); + break; + case UT_SMOOTHNESS: + u->SetValue( mat->smoothness ); + break; + case UT_SHADOWMATRIX: + if( pl ) u->SetValue( &pl->gl_shadowMatrix[0][0], MAX_SHADOWMAPS ); + break; + case UT_SHADOWSPLITDIST: + v = RI->view.parallelSplitDistances; + u->SetValue( v[0], v[1], v[2], v[3] ); + break; + case UT_TEXELSIZE: + u->SetValue( 1.0f / (float)sunSize[0], 1.0f / (float)sunSize[1], 1.0f / (float)sunSize[2], 1.0f / (float)sunSize[3] ); + break; + case UT_GAMMATABLE: + u->SetValue( &tr.gamma_table[0][0], 64 ); + break; + case UT_LIGHTDIR: + if( pl ) + { + if( pl->type == LIGHT_DIRECTIONAL ) lightdir = -tr.sky_normal; + else lightdir = pl->frustum.GetPlane( FRUSTUM_FAR )->normal; + u->SetValue( lightdir.x, lightdir.y, lightdir.z, pl->fov ); + } + else u->SetValue( light->normal.x, light->normal.y, light->normal.z ); + break; + case UT_LIGHTDIFFUSE: + if( pl ) u->SetValue( pl->color.x, pl->color.y, pl->color.z ); + else u->SetValue( light->diffuse.x, light->diffuse.y, light->diffuse.z ); + break; + case UT_LIGHTSHADE: + u->SetValue( (float)light->ambientlight / 255.0f, (float)light->shadelight / 255.0f ); + break; + case UT_LIGHTORIGIN: + if( pl ) u->SetValue( pl->origin.x, pl->origin.y, pl->origin.z, ( 1.0f / pl->radius )); + break; + case UT_LIGHTVIEWPROJMATRIX: + if( pl ) + { + GLfloat gl_lightViewProjMatrix[16]; + pl->lightviewProjMatrix.CopyToArray( gl_lightViewProjMatrix ); + u->SetValue( &gl_lightViewProjMatrix[0] ); + } + break; + case UT_DIFFUSEFACTOR: + u->SetValue( tr.diffuseFactor ); + break; + case UT_AMBIENTFACTOR: + if( pl && pl->type == LIGHT_DIRECTIONAL ) + u->SetValue( tr.sun_ambient ); + else u->SetValue( tr.ambientFactor ); + break; + case UT_SUNREFRACT: + u->SetValue( tr.sun_refract ); + break; + case UT_AMBIENTCUBE: + u->SetValue( &light->ambient[0][0], 6 ); + break; + case UT_LERPFACTOR: + u->SetValue( inst->lerpFactor ); + break; + case UT_REFRACTSCALE: + u->SetValue( bound( 0.0f, mat->refractScale, 1.0f )); + break; + case UT_REFLECTSCALE: + u->SetValue( bound( 0.0f, mat->reflectScale, 1.0f )); + break; + case UT_ABERRATIONSCALE: + u->SetValue( bound( 0.0f, mat->aberrationScale, 1.0f )); + break; + case UT_BOXMINS: + if( world->num_cubemaps > 0 ) + { + Vector mins[2]; + mins[0] = inst->cubemap[0]->mins; + mins[1] = inst->cubemap[1]->mins; + u->SetValue( &mins[0][0], 2 ); + } + break; + case UT_BOXMAXS: + if( world->num_cubemaps > 0 ) + { + Vector maxs[2]; + maxs[0] = inst->cubemap[0]->maxs; + maxs[1] = inst->cubemap[1]->maxs; + u->SetValue( &maxs[0][0], 2 ); + } + break; + case UT_CUBEORIGIN: + if( world->num_cubemaps > 0 ) + { + Vector origin[2]; + origin[0] = inst->cubemap[0]->origin; + origin[1] = inst->cubemap[1]->origin; + u->SetValue( &origin[0][0], 2 ); + } + break; + case UT_CUBEMIPCOUNT: + if( world->num_cubemaps > 0 ) + { + r = Q_max( 1, inst->cubemap[0]->numMips - cv_cube_lod_bias->value ); + g = Q_max( 1, inst->cubemap[1]->numMips - cv_cube_lod_bias->value ); + u->SetValue( r, g ); + } + break; + case UT_LIGHTNUMS0: + u->SetValue( (float)inst->lights[0], (float)inst->lights[1], (float)inst->lights[2], (float)inst->lights[3] ); + break; + case UT_LIGHTNUMS1: + u->SetValue( (float)inst->lights[4], (float)inst->lights[5], (float)inst->lights[6], (float)inst->lights[7] ); + break; + case UT_LIGHTGAMMA: + u->SetValue( tr.light_gamma ); + break; + case UT_LIGHTSCALE: + u->SetValue( tr.direct_scale ); + break; + case UT_LIGHTTHRESHOLD: + u->SetValue( tr.light_threshold ); + break; + default: + ALERT( at_error, "%s: unhandled uniform %s\n", RI->currentshader->name, u->name ); + break; + } + } + + DrawMeshFromBuffer( pMesh ); +} + +void CStudioModelRenderer :: RenderSolidStudioList( void ) +{ + if( !RI->frame.solid_meshes.Count() ) + return; + + pglAlphaFunc( GL_GEQUAL, 0.5f ); + GL_Blend( GL_FALSE ); + GL_AlphaTest( GL_FALSE ); + GL_DepthMask( GL_TRUE ); + + if( GL_Support( R_SEAMLESS_CUBEMAP )) + pglEnable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); + + // sorting list to reduce shader switches + if( !CVAR_TO_BOOL( cv_nosort )) + RI->frame.solid_meshes.Sort( SortSolidMeshes ); + + RI->currententity = NULL; + RI->currentmodel = NULL; + m_pCurrentMaterial = NULL; + + for( int i = 0; i < RI->frame.solid_meshes.Count(); i++ ) + { + CSolidEntry *entry = &RI->frame.solid_meshes[i]; + + if( entry->m_bDrawType != DRAWTYPE_MESH ) + continue; + + if( m_iDrawModelType == DRAWSTUDIO_NORMAL ) + { + if( entry->m_pParentEntity->curstate.renderfx == SKYBOX_ENTITY ) + { + GL_DepthRange( 0.8f, 0.9f ); + GL_ClipPlane( false ); + } + else + { + GL_DepthRange( gldepthmin, gldepthmax ); + GL_ClipPlane( true ); + } + } + + DrawSingleMesh( entry, ( i == 0 )); + } + + if( GL_Support( R_SEAMLESS_CUBEMAP )) + pglDisable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); + + if( m_iDrawModelType == DRAWSTUDIO_NORMAL ) + GL_DepthRange( gldepthmin, gldepthmax ); + GL_AlphaTest( GL_FALSE ); + GL_ClipPlane( true ); + GL_Cull( GL_FRONT ); + + RenderDynLightList( true ); + + GL_CleanupDrawState(); + + // now draw studio decals (unsorted) + for( i = 0; i < RI->frame.solid_meshes.Count(); i++ ) + { + DrawDecal( &RI->frame.solid_meshes[i] ); + } +} + +void CStudioModelRenderer :: RenderTransMesh( CTransEntry *entry ) +{ + if( entry->m_bDrawType != DRAWTYPE_MESH ) + return; + + pglAlphaFunc( GL_GEQUAL, 0.5f ); + GL_DepthMask( GL_TRUE ); + + if( m_iDrawModelType == DRAWSTUDIO_NORMAL ) + { + if( entry->m_pParentEntity->curstate.renderfx == SKYBOX_ENTITY ) + { + GL_DepthRange( 0.8f, 0.9f ); + GL_ClipPlane( false ); + } + else + { + GL_DepthRange( gldepthmin, gldepthmax ); + GL_ClipPlane( true ); + } + } + + if( entry->m_pParentEntity->curstate.rendermode == kRenderGlow ) + { + pglBlendFunc( GL_ONE, GL_ONE ); + if( FBitSet( entry->m_pParentEntity->curstate.effects, EF_NODEPTHTEST )) + pglDisable( GL_DEPTH_TEST ); + GL_Blend( GL_TRUE ); + } + else + { + pglEnable( GL_DEPTH_TEST ); + GL_Blend( GL_FALSE ); + } + + // draw decals behind the glass + DrawDecal( entry, GL_BACK ); + + DrawSingleMesh( entry, true ); + + // draw decals that lies on glass + DrawDecal( entry, GL_FRONT ); + + pglEnable( GL_DEPTH_TEST ); + GL_Blend( GL_FALSE ); + GL_ClipPlane( true ); +} + +void CStudioModelRenderer :: RenderShadowStudioList( void ) +{ + if( !RI->frame.solid_meshes.Count() ) + return; + + RI->currententity = NULL; + RI->currentmodel = NULL; + m_pCurrentMaterial = NULL; + + for( int i = 0; i < RI->frame.solid_meshes.Count(); i++ ) + { + CSolidEntry *entry = &RI->frame.solid_meshes[i]; + DrawSingleMesh( entry, ( i == 0 )); + } + + GL_AlphaTest( GL_FALSE ); + GL_CleanupDrawState(); +} + +void CStudioModelRenderer :: RenderDebugStudioList( bool bViewModel ) +{ + int i; + + if( r_drawentities->value <= 1.0f ) return; + + if( FBitSet( RI->params, RP_SHADOWVIEW|RP_ENVVIEW|RP_SKYVIEW )) + return; + + GL_CleanupDrawState(); + + if( bViewModel ) + { + StudioDrawDebug( GET_VIEWMODEL( )); + StudioDrawDebug( gHUD.m_pHeadShieldEnt ); + } + else + { + // render debug lines + for( i = 0; i < tr.num_draw_entities; i++ ) + StudioDrawDebug( tr.draw_entities[i] ); + } +} + +void CStudioModelRenderer :: ClearLightCache( void ) +{ + for( int i = 0; i < MAX_LIGHTCACHE; i++ ) + { + mstudiocache_t *cache = tr.surface_light_cache[i]; + + if( !cache || cache->numsurfaces <= 0 ) + continue; + + for( int j = 0; j < cache->numsurfaces; j++ ) + { + mstudiosurface_t *surf = &cache->surfaces[j]; + SetBits( surf->flags, SURF_LM_UPDATE|SURF_GRASS_UPDATE ); + } + cache->update_light = true; + } +} \ No newline at end of file diff --git a/cl_dll/render/gl_studio_init.cpp b/cl_dll/render/gl_studio_init.cpp new file mode 100644 index 0000000..18837c3 --- /dev/null +++ b/cl_dll/render/gl_studio_init.cpp @@ -0,0 +1,2164 @@ +/* +gl_studio_init.cpp - loading studio models +Copyright (C) 2019 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include "cl_util.h" +#include "gl_local.h" +#include "com_model.h" +#include "r_studioint.h" +#include "pm_movevars.h" +#include "gl_studio.h" +#include "gl_sprite.h" +#include "event_api.h" +#include +#include "pm_defs.h" +#include "stringlib.h" +#include "triangleapi.h" +#include "entity_types.h" +#include "gl_shader.h" +#include "gl_world.h" + +// Global engine <-> studio model rendering code interface +engine_studio_api_t IEngineStudio; + +// the renderer object, created on the stack. +CStudioModelRenderer g_StudioRenderer; + +//================================================================================================ +// HUD_GetStudioModelInterface +// Export this function for the engine to use the studio renderer class to render objects. +//================================================================================================ +extern "C" int DLLEXPORT HUD_GetStudioModelInterface( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio ) +{ + if( version != STUDIO_INTERFACE_VERSION ) + return 0; + + // Copy in engine helper functions + memcpy( &IEngineStudio, pstudio, sizeof( IEngineStudio )); + + if( g_fRenderInitialized ) + { + // Initialize local variables, etc. + g_StudioRenderer.Init(); + + g_SpriteRenderer.Init(); + } + + // Success + return 1; +} + +//================================================================================================ +// +// Implementation of bone setup class +// +//================================================================================================ +void CBaseBoneSetup :: debugMsg( char *szFmt, ... ) +{ + char buffer[2048]; // must support > 1k messages + va_list args; + + if( developer_level <= DEV_NONE ) + return; + + va_start( args, szFmt ); + Q_vsnprintf( buffer, 2048, szFmt, args ); + va_end( args ); + + gEngfuncs.Con_Printf( buffer ); +} + +mstudioanim_t *CBaseBoneSetup :: GetAnimSourceData( mstudioseqdesc_t *pseqdesc ) +{ + return g_StudioRenderer.StudioGetAnim( RI->currentmodel, pseqdesc ); +} + +void CBaseBoneSetup :: debugLine( const Vector& origin, const Vector& dest, int r, int g, int b, bool noDepthTest, float duration ) +{ + if( noDepthTest ) + pglDisable( GL_DEPTH_TEST ); + + pglColor3ub( r, g, b ); + + pglBegin( GL_LINES ); + pglVertex3fv( origin ); + pglVertex3fv( dest ); + pglEnd(); +} + +/* +==================== +Init + +==================== +*/ +void CStudioModelRenderer :: Init( void ) +{ + // Set up some variables shared with engine + m_pCvarHiModels = IEngineStudio.GetCvar( "cl_himodels" ); + m_pCvarDrawViewModel = IEngineStudio.GetCvar( "r_drawviewmodel" ); + m_pCvarHand = CVAR_REGISTER( "cl_righthand", "0", FCVAR_ARCHIVE ); + m_pCvarViewmodelFov = CVAR_REGISTER( "cl_viewmodel_fov", "60", FCVAR_ARCHIVE ); + m_pCvarHeadShieldFov = CVAR_REGISTER( "cl_headshield_fov", "63", FCVAR_ARCHIVE ); + m_pCvarLegsOffset = CVAR_REGISTER( "legs_offset", "20", FCVAR_ARCHIVE ); + m_pCvarDrawLegs = CVAR_REGISTER( "r_drawlegs", "1", FCVAR_ARCHIVE ); + m_pCvarCompatible = CVAR_REGISTER( "r_studio_compatible", "1", FCVAR_ARCHIVE ); + m_pCvarLodScale = CVAR_REGISTER( "cl_lod_scale", "5.0", FCVAR_ARCHIVE ); + m_pCvarLodBias = CVAR_REGISTER( "cl_lod_bias", "0", FCVAR_ARCHIVE ); +} + +/* +==================== +Init + +==================== +*/ +void CStudioModelRenderer :: VidInit( void ) +{ + // tell the engine what models is used + m_pPlayerLegsModel = IEngineStudio.Mod_ForName( "models/player_legs.mdl", false ); +} + +/* +==================== +CStudioModelRenderer + +==================== +*/ +CStudioModelRenderer :: CStudioModelRenderer( void ) +{ + m_iDrawModelType = DRAWSTUDIO_NORMAL; + m_pCurrentMaterial = NULL; + m_pCvarHiModels = NULL; + m_pCvarDrawViewModel= NULL; + m_pCvarHand = NULL; + m_pStudioHeader = NULL; + m_pVboModel = NULL; + m_pSubModel = NULL; + m_pModelInstance = NULL; +} + +/* +==================== +~CStudioModelRenderer + +==================== +*/ +CStudioModelRenderer :: ~CStudioModelRenderer( void ) +{ +} + +/* +==================== +Prepare all the pointers for +working with current entity + +==================== +*/ +bool CStudioModelRenderer :: StudioSetEntity( cl_entity_t *pEnt ) +{ + if( !pEnt || !pEnt->model || pEnt->model->type != mod_studio ) + return false; + + RI->currententity = pEnt; + SET_CURRENT_ENTITY( RI->currententity ); + + if( m_iDrawModelType == DRAWSTUDIO_NORMAL && ( RI->currententity->player || RI->currententity->curstate.renderfx == kRenderFxDeadPlayer )) + { + int iPlayerIndex; + + if( RP_NORMALPASS() && RP_LOCALCLIENT( RI->currententity ) && !FBitSet( RI->params, RP_THIRDPERSON )) + { + if( !CVAR_TO_BOOL( m_pCvarDrawLegs ) || !m_pPlayerLegsModel ) + return false; + + // do simple cull for player legs + if( FBitSet( gHUD.m_iKeyBits, IN_DUCK ) && RI->view.angles[PITCH] <= 30.0f ) + return false; + else if( !FBitSet( gHUD.m_iKeyBits, IN_DUCK ) && RI->view.angles[PITCH] <= 50.0f ) + return false; + + RI->currentmodel = m_pPlayerLegsModel; + } + else + { + if( RI->currententity->curstate.renderfx == kRenderFxDeadPlayer ) + iPlayerIndex = RI->currententity->curstate.renderamt - 1; + else iPlayerIndex = RI->currententity->curstate.number - 1; + + if( iPlayerIndex < 0 || iPlayerIndex >= GET_MAX_CLIENTS( )) + return false; + + RI->currentmodel = IEngineStudio.SetupPlayerModel( iPlayerIndex ); + + // show highest resolution multiplayer model + if( CVAR_TO_BOOL( m_pCvarHiModels ) && RI->currentmodel != RI->currententity->model ) + RI->currententity->curstate.body = 255; + + if( !( !developer_level && GET_MAX_CLIENTS() == 1 ) && ( RI->currentmodel == RI->currententity->model )) + RI->currententity->curstate.body = 1; // force helmet + } + } + else + { + RI->currentmodel = RI->currententity->model; + } + + if( RI->currentmodel == NULL ) + return false; + + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( RI->currentmodel ); + + // downloading in-progress ? + if( m_pStudioHeader == NULL ) + return false; + + // tell the engine about model + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + IEngineStudio.SetRenderModel( RI->currentmodel ); + + if( !StudioSetupInstance( )) + { + ALERT( at_error, "Couldn't create instance for entity %d\n", pEnt->index ); + return false; // out of memory ? + } + + // all done + return true; +} + +//----------------------------------------------------------------------------- +// Fast version without data reconstruction or changing +//----------------------------------------------------------------------------- +bool CStudioModelRenderer :: StudioSetEntity( CSolidEntry *entry ) +{ + studiohdr_t *phdr; + + if( !entry || entry->m_bDrawType != DRAWTYPE_MESH ) + return false; + + if( !entry->m_pParentEntity || !entry->m_pRenderModel ) + return false; // bad entry? + + if( entry->m_pParentEntity->modelhandle == INVALID_HANDLE ) + return false; // not initialized? + + if(( phdr = (studiohdr_t *)IEngineStudio.Mod_Extradata( entry->m_pRenderModel )) == NULL ) + return false; // no model? + + RI->currentmodel = entry->m_pRenderModel; + RI->currententity = entry->m_pParentEntity; + m_pModelInstance = &m_ModelInstances[entry->m_pParentEntity->modelhandle]; + m_pStudioHeader = phdr; + + return true; +} + +bool CStudioModelRenderer :: StudioSetupInstance( void ) +{ + // first call ? + if( RI->currententity->modelhandle == INVALID_HANDLE ) + { + RI->currententity->modelhandle = m_ModelInstances.AddToTail(); + + if( RI->currententity->modelhandle == INVALID_HANDLE ) + return false; // out of memory ? + + m_pModelInstance = &m_ModelInstances[RI->currententity->modelhandle]; + ClearInstanceData( true ); + } + else + { + m_pModelInstance = &m_ModelInstances[RI->currententity->modelhandle]; + + // model has been changed or something like + if( !IsModelInstanceValid( m_pModelInstance )) + ClearInstanceData( false ); + } + + m_boneSetup.SetStudioPointers( m_pStudioHeader, m_pModelInstance->m_poseparameter ); + + return true; +} + +//----------------------------------------------------------------------------- +// It's not valid if the model index changed + we have non-zero instance data +//----------------------------------------------------------------------------- +bool CStudioModelRenderer :: IsModelInstanceValid( ModelInstance_t *inst ) +{ + const model_t *pModel; + + if( m_iDrawModelType == DRAWSTUDIO_NORMAL && ( inst->m_pEntity->player || RI->currententity->curstate.renderfx == kRenderFxDeadPlayer )) + { + if( RI->currententity->curstate.renderfx == kRenderFxDeadPlayer ) + pModel = IEngineStudio.SetupPlayerModel( inst->m_pEntity->curstate.renderamt - 1 ); + else pModel = IEngineStudio.SetupPlayerModel( inst->m_pEntity->curstate.number - 1 ); + } + else + { + pModel = inst->m_pEntity->model; + } + + return inst->m_pModel == pModel; +} + +void CStudioModelRenderer :: DeleteVBOMesh( vbomesh_t *pMesh ) +{ + // purge all GPU data + if( pMesh->vao ) pglDeleteVertexArrays( 1, &pMesh->vao ); + if( pMesh->vbo ) pglDeleteBuffersARB( 1, &pMesh->vbo ); + if( pMesh->ibo ) pglDeleteBuffersARB( 1, &pMesh->ibo ); + tr.total_vbo_memory -= pMesh->cacheSize; + pMesh->cacheSize = 0; +} + +void CStudioModelRenderer :: DeleteStudioCache( mstudiocache_t **ppstudiocache ) +{ + ASSERT( ppstudiocache != NULL ); + + mstudiocache_t *pstudiocache = *ppstudiocache; + if( !pstudiocache ) return; + + for( int i = 0; i < pstudiocache->numbodyparts; i++ ) + { + mbodypart_t *pBodyPart = &pstudiocache->bodyparts[i]; + + for( int j = 0; j < pBodyPart->nummodels; j++ ) + { + msubmodel_t *pSubModel = pBodyPart->models[j]; + + if( !pSubModel || pSubModel->nummesh <= 0 ) + continue; // blank submodel + + for( int k = 0; k < pSubModel->nummesh; k++ ) + { + vbomesh_t *pMesh = &pSubModel->meshes[k]; + + DeleteVBOMesh( pMesh ); + } + } + } + + if( pstudiocache != NULL ) + Mem_Free( pstudiocache ); + *ppstudiocache = NULL; +} + +void CStudioModelRenderer :: DestroyMeshCache( void ) +{ + FreeStudioMaterials (); + + DeleteStudioCache( &RI->currentmodel->studiocache ); + + if( RI->currentmodel->poseToBone != NULL ) + Mem_Free( RI->currentmodel->poseToBone ); + RI->currentmodel->poseToBone = NULL; +} + +void CStudioModelRenderer :: DestroyInstance( word handle ) +{ + if( !m_ModelInstances.IsValidIndex( handle )) + return; + + ModelInstance_t *inst = &m_ModelInstances[handle]; + + PurgeDecals( inst ); + + if( inst->materials != NULL ) + Mem_Free( inst->materials ); + inst->materials = NULL; + + if( inst->m_pJiggleBones != NULL ) + delete inst->m_pJiggleBones; + inst->m_pJiggleBones = NULL; + + m_ModelInstances.Remove( handle ); +} + +void CStudioModelRenderer :: DestroyAllModelInstances( void ) +{ + // if caused by Host_Error during draw the viewmodel or gasmask + m_iDrawModelType = DRAWSTUDIO_NORMAL; + m_fShootDecal = false; + + // NOTE: should destroy in reverse-order because it's linked list not array! + for( int i = m_ModelInstances.Count(); --i >= 0; ) + DestroyInstance( i ); +} + +void CStudioModelRenderer :: UpdateInstanceMaterials( void ) +{ + ASSERT( m_pStudioHeader != NULL ); + ASSERT( m_pModelInstance != NULL ); + + // model was changed, so we need to realloc materials + if( m_pModelInstance->materials != NULL ) + Mem_Free( m_pModelInstance->materials ); + + // create a local copy of all the model material for cache uber-shaders + m_pModelInstance->materials = (mstudiomaterial_t *)Mem_Alloc( sizeof( mstudiomaterial_t ) * m_pStudioHeader->numtextures ); + memcpy( m_pModelInstance->materials, RI->currentmodel->materials, sizeof( mstudiomaterial_t ) * m_pStudioHeader->numtextures ); + + // invalidate sequences when a new instance was created + for( int i = 0; i < m_pStudioHeader->numtextures; i++ ) + { + m_pModelInstance->materials[i].forwardScene.Invalidate(); + m_pModelInstance->materials[i].forwardLightSpot.Invalidate(); + m_pModelInstance->materials[i].forwardLightOmni.Invalidate(); + m_pModelInstance->materials[i].forwardLightProj.Invalidate(); + } +} + +void CStudioModelRenderer :: ClearInstanceData( bool create ) +{ + if( create ) + { + m_pModelInstance->m_DecalList.Purge(); + m_pModelInstance->m_pJiggleBones = NULL; + m_pModelInstance->materials = NULL; + } + else + { + if( m_pModelInstance->m_pJiggleBones != NULL ) + delete m_pModelInstance->m_pJiggleBones; + m_pModelInstance->m_pJiggleBones = NULL; + PurgeDecals( m_pModelInstance ); + } + + m_pModelInstance->m_pEntity = RI->currententity; + m_pModelInstance->m_pModel = RI->currentmodel; + m_pModelInstance->m_VlCache = NULL; + m_pModelInstance->m_FlCache = NULL; + m_pModelInstance->m_DecalCount = 0; + m_pModelInstance->cached_frame = -1; + m_pModelInstance->visframe = -1; + m_pModelInstance->radius = 0.0f; + m_pModelInstance->info_flags = 0; + m_pModelInstance->lerpFactor = 0.0f; + m_pModelInstance->cubemap[0] = &world->defaultCubemap; + m_pModelInstance->cubemap[1] = &world->defaultCubemap; + + ClearBounds( m_pModelInstance->absmin, m_pModelInstance->absmax ); + memset( &m_pModelInstance->bonecache, 0, sizeof( BoneCache_t )); + memset( m_pModelInstance->m_protationmatrix, 0, sizeof( matrix3x4 )); + memset( m_pModelInstance->m_pbones, 0, sizeof( matrix3x4 ) * MAXSTUDIOBONES ); + memset( m_pModelInstance->m_pwpnbones, 0, sizeof( matrix3x4 ) * MAXSTUDIOBONES ); + memset( m_pModelInstance->attachment, 0, sizeof( StudioAttachment_t ) * MAXSTUDIOATTACHMENTS ); + memset( m_pModelInstance->m_glstudiobones, 0, sizeof( Vector4D ) * MAXSTUDIOBONES * 3 ); + memset( m_pModelInstance->m_glweaponbones, 0, sizeof( Vector4D ) * MAXSTUDIOBONES * 3 ); + memset( m_pModelInstance->m_studioquat, 0, sizeof( Vector4D ) * MAXSTUDIOBONES ); + memset( m_pModelInstance->m_studiopos, 0, sizeof( Vector ) * MAXSTUDIOBONES ); + memset( m_pModelInstance->m_weaponquat, 0, sizeof( Vector4D ) * MAXSTUDIOBONES ); + memset( m_pModelInstance->m_weaponpos, 0, sizeof( Vector ) * MAXSTUDIOBONES ); + memset( &m_pModelInstance->lerp, 0, sizeof( mstudiolerp_t )); + memset( &m_pModelInstance->light, 0, sizeof( mstudiolight_t )); + memset( &m_pModelInstance->oldlight, 0, sizeof( mstudiolight_t )); + memset( &m_pModelInstance->newlight, 0, sizeof( mstudiolight_t )); + memset( &m_pModelInstance->lights, 255, sizeof( byte[MAXDYNLIGHTS] )); + memset( &m_pModelInstance->m_controller, 0, sizeof( m_pModelInstance->m_controller )); + memset( &m_pModelInstance->m_seqblend, 0, sizeof( m_pModelInstance->m_seqblend )); + m_pModelInstance->lerp.stairoldz = RI->currententity->origin[2]; + m_pModelInstance->lerp.stairtime = tr.time; + m_pModelInstance->m_current_seqblend = 0; + m_pModelInstance->light_update = false; + m_pModelInstance->lighttimecheck = 0.0f; + + m_boneSetup.SetStudioPointers( m_pStudioHeader, m_pModelInstance->m_poseparameter ); + + // set poseparam sliders to their default values + m_boneSetup.CalcDefaultPoseParameters( m_pModelInstance->m_poseparameter ); + + // refresh the materials list + UpdateInstanceMaterials(); + + // copy attachments names + mstudioattachment_t *pattachment = (mstudioattachment_t *)((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex); + StudioAttachment_t *att = m_pModelInstance->attachment; + + // setup attachment names + for( int i = 0; i < Q_min( MAXSTUDIOATTACHMENTS, m_pStudioHeader->numattachments ); i++ ) + { + Q_strncpy( att[i].name, pattachment[i].name, sizeof( att[0].name )); + + att[i].local.Identity(); + att[i].local.SetOrigin( pattachment[i].org ); + + if( !Q_strnicmp( att[i].name, "LightProbe.", 11 )) + SetBits( m_pModelInstance->info_flags, MF_CUSTOM_LIGHTGRID ); + } + m_pModelInstance->numattachments = m_pStudioHeader->numattachments; + + for( int map = 0; map < MAXLIGHTMAPS; map++ ) + m_pModelInstance->styles[map] = 255; +} + +void CStudioModelRenderer :: PrecacheStudioShaders( void ) +{ + bool bone_weights = FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS ) ? true : false; + mstudiomaterial_t *pmaterial = (mstudiomaterial_t *)RI->currentmodel->materials; + + // this function is called when loading vertexlight cache, so we guessed what vertex light is active + for( int i = 0; i < m_pStudioHeader->numtextures; i++, pmaterial++ ) + { + ShaderSceneForward( pmaterial, true, bone_weights, m_pStudioHeader->numbones ); + } +} +#include "material.h" +void CStudioModelRenderer :: LoadStudioMaterials( void ) +{ + // first we need alloc copy of all the materials to prevent modify mstudiotexture_t + RI->currentmodel->materials = (mstudiomaterial_t *)Mem_Alloc( sizeof( mstudiomaterial_t ) * m_pStudioHeader->numtextures ); + + bool bone_weights = FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS ) ? true : false; + mstudiotexture_t *ptexture = (mstudiotexture_t *)((byte *)m_pStudioHeader + m_pStudioHeader->textureindex); + char diffuse[128], bumpmap[128], glossmap[128], glowmap[128], heightmap[128]; + mstudiomaterial_t *pmaterial = (mstudiomaterial_t *)RI->currentmodel->materials; + char texname[128], matname[64], mdlname[64]; + + COM_FileBase( RI->currentmodel->name, mdlname ); + + // loading studio materials from studio textures + for( int i = 0; i < m_pStudioHeader->numtextures; i++, ptexture++, pmaterial++ ) + { + COM_FileBase( ptexture->name, texname ); + + // build material names + Q_snprintf( matname, sizeof( matname ), "%s/%s", mdlname, texname ); // material description + // setup material constants + matdesc_t *desc = CL_FindMaterial( matname ); +#if 0 + if( Q_strlen( desc->diffusemap ) && !IMAGE_EXISTS( desc->diffusemap )) + Msg( "need: %s\n", desc->diffusemap ); +#endif + if( Q_strlen( desc->diffusemap ) && IMAGE_EXISTS( desc->diffusemap )) + Q_strncpy( diffuse, desc->diffusemap, sizeof( diffuse )); + else Q_snprintf( diffuse, sizeof( diffuse ), "textures/%s/%s", mdlname, texname ); + + if( Q_strlen( desc->normalmap ) && IMAGE_EXISTS( desc->normalmap )) + Q_strncpy( bumpmap, desc->normalmap, sizeof( bumpmap )); + else Q_snprintf( bumpmap, sizeof( bumpmap ), "textures/%s/%s_norm", mdlname, texname ); + + if( Q_strlen( desc->glossmap ) && IMAGE_EXISTS( desc->glossmap )) + Q_strncpy( glossmap, desc->glossmap, sizeof( glossmap )); + else Q_snprintf( glossmap, sizeof( glossmap ), "textures/%s/%s_gloss", mdlname, texname ); + + Q_snprintf( glowmap, sizeof( glowmap ), "textures/%s/%s_luma", mdlname, texname ); + Q_snprintf( heightmap, sizeof( heightmap ), "textures/%s/%s_hmap", mdlname, texname ); +#if 0 + Msg( "\"%s\"\n", matname ); + Msg( "{\n" ); + Msg( "\t\"diffuseMap\"\t\"%s\"\n", diffuse ); + Msg( "\t\"normalMap\"\t\"%s\"\n", bumpmap ); + Msg( "\t\"glossMap\"\t\"%s\"\n", glossmap ); + Msg( "\t\"material\"\t\"%s\"\n", desc->effects->name ); + Msg( "}\n\n" ); +#endif + pmaterial->pSource = ptexture; + pmaterial->flags = ptexture->flags; + + if( IMAGE_EXISTS( diffuse )) + { + pmaterial->gl_diffuse_id = LOAD_TEXTURE( diffuse, NULL, 0, 0 ); + + // semi-transparent textures must have additive flag to invoke renderer insert supposed mesh into translist + if( FBitSet( pmaterial->flags, STUDIO_NF_ADDITIVE )) + { + if( RENDER_GET_PARM( PARM_TEX_FLAGS, pmaterial->gl_diffuse_id ) & TF_HAS_ALPHA ) + SetBits( pmaterial->flags, STUDIO_NF_HAS_ALPHA ); + } + + if( FBitSet( pmaterial->flags, STUDIO_NF_MASKED )) + SetBits( pmaterial->flags, STUDIO_NF_HAS_ALPHA ); + } + + if( pmaterial->gl_diffuse_id != 0 ) + { + // so engine can be draw HQ image for gl_renderer 0 + if( ptexture->index != tr.defaultTexture ) + FREE_TEXTURE( ptexture->index ); + ptexture->index = pmaterial->gl_diffuse_id; + } + else + { + // reuse original texture + pmaterial->gl_diffuse_id = ptexture->index; + } + + if( IMAGE_EXISTS( bumpmap )) + { + pmaterial->gl_normalmap_id = LOAD_TEXTURE( bumpmap, NULL, 0, TF_NORMALMAP ); + if( pmaterial->gl_normalmap_id > 0 ) + SetBits( pmaterial->flags, STUDIO_NF_NORMALMAP ); + } + else + { + // try alternate suffix + Q_snprintf( bumpmap, sizeof( bumpmap ), "textures/%s/%s_local", mdlname, texname ); + if( IMAGE_EXISTS( bumpmap )) + pmaterial->gl_normalmap_id = LOAD_TEXTURE( bumpmap, NULL, 0, TF_NORMALMAP ); + else pmaterial->gl_normalmap_id = tr.normalmapTexture; // blank bumpy + } + + if( IMAGE_EXISTS( glossmap )) + { + pmaterial->gl_specular_id = LOAD_TEXTURE( glossmap, NULL, 0, 0 ); + } + else + { + // try alternate suffix + Q_snprintf( glossmap, sizeof( glossmap ), "textures/%s/%s_gloss", mdlname, texname ); + if( IMAGE_EXISTS( glossmap )) + pmaterial->gl_specular_id = LOAD_TEXTURE( glossmap, NULL, 0, 0 ); + else pmaterial->gl_specular_id = tr.blackTexture; + } + + if( IMAGE_EXISTS( glowmap )) + pmaterial->gl_glowmap_id = LOAD_TEXTURE( glowmap, NULL, 0, 0 ); + else pmaterial->gl_glowmap_id = tr.blackTexture; + + if( IMAGE_EXISTS( heightmap )) + { + pmaterial->gl_heightmap_id = LOAD_TEXTURE( heightmap, NULL, 0, 0 ); + } + else + { + // try alternate suffix + Q_snprintf( heightmap, sizeof( heightmap ), "textures/%s/%s_bump", mdlname, texname ); + if( IMAGE_EXISTS( heightmap )) + pmaterial->gl_heightmap_id = LOAD_TEXTURE( heightmap, NULL, 0, 0 ); + else pmaterial->gl_heightmap_id = tr.blackTexture; + } + + // current model has bumpmapping effect + if( pmaterial->gl_normalmap_id > 0 && pmaterial->gl_normalmap_id != tr.normalmapTexture ) + SetBits( m_pStudioHeader->flags, STUDIO_HAS_BUMP ); + + if( pmaterial->gl_specular_id != tr.blackTexture ) + SetBits( pmaterial->flags, STUDIO_NF_GLOSSMAP ); + + if( pmaterial->gl_glowmap_id != tr.blackTexture ) + SetBits( pmaterial->flags, STUDIO_NF_LUMA ); + + if( pmaterial->gl_heightmap_id != tr.blackTexture ) + SetBits( pmaterial->flags, STUDIO_NF_HEIGHTMAP ); + + pmaterial->gl_detailmap_id = desc->dt_texturenum; + pmaterial->smoothness = desc->smoothness; + pmaterial->detailScale[0] = desc->detailScale[0]; + pmaterial->detailScale[1] = desc->detailScale[1]; + pmaterial->reflectScale = desc->reflectScale; + pmaterial->refractScale = desc->refractScale; + pmaterial->aberrationScale = desc->aberrationScale; + pmaterial->reliefScale = desc->reliefScale; + pmaterial->effects = desc->effects; + + if( pmaterial->smoothness <= 0.0f ) // don't waste time + ClearBits( pmaterial->flags, STUDIO_NF_GLOSSMAP ); + + if( pmaterial->gl_detailmap_id > 0 && pmaterial->gl_detailmap_id != tr.grayTexture ) + SetBits( pmaterial->flags, STUDIO_NF_HAS_DETAIL ); + + // time to precache shaders + ShaderSceneForward( pmaterial, false, bone_weights, m_pStudioHeader->numbones ); + ShaderLightForward( &tr.defaultlightSpot, pmaterial, bone_weights, m_pStudioHeader->numbones ); + ShaderLightForward( &tr.defaultlightOmni, pmaterial, bone_weights, m_pStudioHeader->numbones ); + ShaderLightForward( &tr.defaultlightProj, pmaterial, bone_weights, m_pStudioHeader->numbones ); + pmaterial->forwardScene.Invalidate(); // don't keep shadernum + } +} + +void CStudioModelRenderer :: FreeStudioMaterials( void ) +{ + if( !RI->currentmodel->materials ) return; + + mstudiomaterial_t *pmaterial = (mstudiomaterial_t *)RI->currentmodel->materials; + + // release textures for current model + for( int i = 0; i < m_pStudioHeader->numtextures; i++, pmaterial++ ) + { + if( pmaterial->pSource->index != pmaterial->gl_diffuse_id ) + FREE_TEXTURE( pmaterial->gl_diffuse_id ); + + if( pmaterial->gl_normalmap_id != tr.normalmapTexture ) + FREE_TEXTURE( pmaterial->gl_normalmap_id ); + + if( pmaterial->gl_specular_id != tr.blackTexture ) + FREE_TEXTURE( pmaterial->gl_specular_id ); + + if( pmaterial->gl_glowmap_id != tr.blackTexture ) + FREE_TEXTURE( pmaterial->gl_glowmap_id ); + } + + Mem_Free( RI->currentmodel->materials ); + RI->currentmodel->materials = NULL; +} + +void CStudioModelRenderer :: LoadLocalMatrix( int bone, mstudioboneinfo_t *boneinfo ) +{ + mposetobone_t *m = RI->currentmodel->poseToBone; + + // transform Valve matrix to Xash matrix + m->posetobone[bone][0][0] = boneinfo->poseToBone[0][0]; + m->posetobone[bone][0][1] = boneinfo->poseToBone[1][0]; + m->posetobone[bone][0][2] = boneinfo->poseToBone[2][0]; + + m->posetobone[bone][1][0] = boneinfo->poseToBone[0][1]; + m->posetobone[bone][1][1] = boneinfo->poseToBone[1][1]; + m->posetobone[bone][1][2] = boneinfo->poseToBone[2][1]; + + m->posetobone[bone][2][0] = boneinfo->poseToBone[0][2]; + m->posetobone[bone][2][1] = boneinfo->poseToBone[1][2]; + m->posetobone[bone][2][2] = boneinfo->poseToBone[2][2]; + + m->posetobone[bone][3][0] = boneinfo->poseToBone[0][3]; + m->posetobone[bone][3][1] = boneinfo->poseToBone[1][3]; + m->posetobone[bone][3][2] = boneinfo->poseToBone[2][3]; +} + +void CStudioModelRenderer :: ComputeSkinMatrix( mstudioboneweight_t *boneweights, const matrix3x4 worldtransform[], matrix3x4 &result ) +{ + float flWeight0, flWeight1, flWeight2, flWeight3; + int numbones = 0; + float flTotal; + + for( int i = 0; i < MAXSTUDIOBONEWEIGHTS; i++ ) + { + if( boneweights->bone[i] != -1 ) + numbones++; + } + + if( numbones == 4 ) + { + const matrix3x4 &boneMat0 = worldtransform[boneweights->bone[0]]; + const matrix3x4 &boneMat1 = worldtransform[boneweights->bone[1]]; + const matrix3x4 &boneMat2 = worldtransform[boneweights->bone[2]]; + const matrix3x4 &boneMat3 = worldtransform[boneweights->bone[3]]; + flWeight0 = boneweights->weight[0] / 255.0f; + flWeight1 = boneweights->weight[1] / 255.0f; + flWeight2 = boneweights->weight[2] / 255.0f; + flWeight3 = boneweights->weight[3] / 255.0f; + flTotal = flWeight0 + flWeight1 + flWeight2 + flWeight3; + + if( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal; // compensate rounding error + + result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2 + boneMat3[0][0] * flWeight3; + result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2 + boneMat3[0][1] * flWeight3; + result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2 + boneMat3[0][2] * flWeight3; + result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2 + boneMat3[1][0] * flWeight3; + result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2 + boneMat3[1][1] * flWeight3; + result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2 + boneMat3[1][2] * flWeight3; + result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2 + boneMat3[2][0] * flWeight3; + result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2 + boneMat3[2][1] * flWeight3; + result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2 + boneMat3[2][2] * flWeight3; + result[3][0] = boneMat0[3][0] * flWeight0 + boneMat1[3][0] * flWeight1 + boneMat2[3][0] * flWeight2 + boneMat3[3][0] * flWeight3; + result[3][1] = boneMat0[3][1] * flWeight0 + boneMat1[3][1] * flWeight1 + boneMat2[3][1] * flWeight2 + boneMat3[3][1] * flWeight3; + result[3][2] = boneMat0[3][2] * flWeight0 + boneMat1[3][2] * flWeight1 + boneMat2[3][2] * flWeight2 + boneMat3[3][2] * flWeight3; + } + else if( numbones == 3 ) + { + const matrix3x4 &boneMat0 = worldtransform[boneweights->bone[0]]; + const matrix3x4 &boneMat1 = worldtransform[boneweights->bone[1]]; + const matrix3x4 &boneMat2 = worldtransform[boneweights->bone[2]]; + flWeight0 = boneweights->weight[0] / 255.0f; + flWeight1 = boneweights->weight[1] / 255.0f; + flWeight2 = boneweights->weight[2] / 255.0f; + flTotal = flWeight0 + flWeight1 + flWeight2; + + if( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal; // compensate rounding error + + result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2; + result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2; + result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2; + result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2; + result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2; + result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2; + result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2; + result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2; + result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2; + result[3][0] = boneMat0[3][0] * flWeight0 + boneMat1[3][0] * flWeight1 + boneMat2[3][0] * flWeight2; + result[3][1] = boneMat0[3][1] * flWeight0 + boneMat1[3][1] * flWeight1 + boneMat2[3][1] * flWeight2; + result[3][2] = boneMat0[3][2] * flWeight0 + boneMat1[3][2] * flWeight1 + boneMat2[3][2] * flWeight2; + } + else if( numbones == 2 ) + { + const matrix3x4 &boneMat0 = worldtransform[boneweights->bone[0]]; + const matrix3x4 &boneMat1 = worldtransform[boneweights->bone[1]]; + flWeight0 = boneweights->weight[0] / 255.0f; + flWeight1 = boneweights->weight[1] / 255.0f; + flTotal = flWeight0 + flWeight1; + + if( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal; // compensate rounding error + + // NOTE: Inlining here seems to make a fair amount of difference + result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1; + result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1; + result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1; + result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1; + result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1; + result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1; + result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1; + result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1; + result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1; + result[3][0] = boneMat0[3][0] * flWeight0 + boneMat1[3][0] * flWeight1; + result[3][1] = boneMat0[3][1] * flWeight0 + boneMat1[3][1] * flWeight1; + result[3][2] = boneMat0[3][2] * flWeight0 + boneMat1[3][2] * flWeight1; + } + else + { + result = worldtransform[boneweights->bone[0]]; + } +} + +void CStudioModelRenderer :: ComputeSkinMatrix( svert_t *vertex, const matrix3x4 worldtransform[], matrix3x4 &result ) +{ + float flWeight0, flWeight1, flWeight2, flWeight3; + int numbones = 0; + float flTotal; + + for( int i = 0; i < MAXSTUDIOBONEWEIGHTS; i++ ) + { + if( vertex->boneid[i] != -1 ) + numbones++; + } + + if( numbones == 4 ) + { + const matrix3x4 &boneMat0 = worldtransform[vertex->boneid[0]]; + const matrix3x4 &boneMat1 = worldtransform[vertex->boneid[1]]; + const matrix3x4 &boneMat2 = worldtransform[vertex->boneid[2]]; + const matrix3x4 &boneMat3 = worldtransform[vertex->boneid[3]]; + flWeight0 = vertex->weight[0] / 255.0f; + flWeight1 = vertex->weight[1] / 255.0f; + flWeight2 = vertex->weight[2] / 255.0f; + flWeight3 = vertex->weight[3] / 255.0f; + flTotal = flWeight0 + flWeight1 + flWeight2 + flWeight3; + + if( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal; // compensate rounding error + + result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2 + boneMat3[0][0] * flWeight3; + result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2 + boneMat3[0][1] * flWeight3; + result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2 + boneMat3[0][2] * flWeight3; + result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2 + boneMat3[1][0] * flWeight3; + result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2 + boneMat3[1][1] * flWeight3; + result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2 + boneMat3[1][2] * flWeight3; + result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2 + boneMat3[2][0] * flWeight3; + result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2 + boneMat3[2][1] * flWeight3; + result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2 + boneMat3[2][2] * flWeight3; + result[3][0] = boneMat0[3][0] * flWeight0 + boneMat1[3][0] * flWeight1 + boneMat2[3][0] * flWeight2 + boneMat3[3][0] * flWeight3; + result[3][1] = boneMat0[3][1] * flWeight0 + boneMat1[3][1] * flWeight1 + boneMat2[3][1] * flWeight2 + boneMat3[3][1] * flWeight3; + result[3][2] = boneMat0[3][2] * flWeight0 + boneMat1[3][2] * flWeight1 + boneMat2[3][2] * flWeight2 + boneMat3[3][2] * flWeight3; + } + else if( numbones == 3 ) + { + const matrix3x4 &boneMat0 = worldtransform[vertex->boneid[0]]; + const matrix3x4 &boneMat1 = worldtransform[vertex->boneid[1]]; + const matrix3x4 &boneMat2 = worldtransform[vertex->boneid[2]]; + flWeight0 = vertex->weight[0] / 255.0f; + flWeight1 = vertex->weight[1] / 255.0f; + flWeight2 = vertex->weight[2] / 255.0f; + flTotal = flWeight0 + flWeight1 + flWeight2; + + if( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal; // compensate rounding error + + result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2; + result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2; + result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2; + result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2; + result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2; + result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2; + result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2; + result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2; + result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2; + result[3][0] = boneMat0[3][0] * flWeight0 + boneMat1[3][0] * flWeight1 + boneMat2[3][0] * flWeight2; + result[3][1] = boneMat0[3][1] * flWeight0 + boneMat1[3][1] * flWeight1 + boneMat2[3][1] * flWeight2; + result[3][2] = boneMat0[3][2] * flWeight0 + boneMat1[3][2] * flWeight1 + boneMat2[3][2] * flWeight2; + } + else if( numbones == 2 ) + { + const matrix3x4 &boneMat0 = worldtransform[vertex->boneid[0]]; + const matrix3x4 &boneMat1 = worldtransform[vertex->boneid[1]]; + flWeight0 = vertex->weight[0] / 255.0f; + flWeight1 = vertex->weight[1] / 255.0f; + flTotal = flWeight0 + flWeight1; + + if( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal; // compensate rounding error + + // NOTE: Inlining here seems to make a fair amount of difference + result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1; + result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1; + result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1; + result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1; + result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1; + result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1; + result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1; + result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1; + result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1; + result[3][0] = boneMat0[3][0] * flWeight0 + boneMat1[3][0] * flWeight1; + result[3][1] = boneMat0[3][1] * flWeight0 + boneMat1[3][1] * flWeight1; + result[3][2] = boneMat0[3][2] * flWeight0 + boneMat1[3][2] * flWeight1; + } + else + { + result = worldtransform[vertex->boneid[0]]; + } +} + +bool CStudioModelRenderer :: StudioSaveTBN( void ) +{ + char szFilename[MAX_PATH]; + char szModelname[MAX_PATH]; + + Q_strncpy( szModelname, RI->currentmodel->name + Q_strlen( "models/" ), sizeof( szModelname )); + COM_StripExtension( szModelname ); + Q_snprintf( szFilename, sizeof( szFilename ), "cache/%s.tbn", szModelname ); + size_t tbn_size = sizeof( dmodeltbn_t ) + (m_tbnverts->numverts - 1) * sizeof( dvertmatrix_t ); + + if( SAVE_FILE( szFilename, m_tbnverts, tbn_size )) + return true; + + ALERT( at_error, "StudioSaveCache: couldn't store %s\n", szFilename ); + return false; +} + +bool CStudioModelRenderer :: StudioLoadTBN( void ) +{ + char szFilename[MAX_PATH]; + char szModelname[MAX_PATH]; + int length, iCompare; + + Q_strncpy( szModelname, RI->currentmodel->name + Q_strlen( "models/" ), sizeof( szModelname )); + COM_StripExtension( szModelname ); + Q_snprintf( szFilename, sizeof( szFilename ), "cache/%s.tbn", szModelname ); + + if( COMPARE_FILE_TIME( RI->currentmodel->name, szFilename, &iCompare )) + { + // MDL file is newer. + if( iCompare > 0 ) + return false; + } + else + { + return false; + } + + byte *aMemFile = LOAD_FILE( szFilename, &length ); + if( !aMemFile ) return false; + + // TBN is a footprint + m_tbnverts = (dmodeltbn_t *)aMemFile; + + if( m_tbnverts->ident != IDTBNHEADER ) + { + ALERT( at_warning, "%s has wrong id (%x should be %x)\n", szFilename, m_tbnverts->ident, IDTBNHEADER ); + FREE_FILE( aMemFile ); + m_tbnverts = NULL; + return false; + } + + if( m_tbnverts->version != TBN_VERSION ) + { + ALERT( at_warning, "%s has wrong version (%i should be %i)\n", szFilename, m_tbnverts->version, TBN_VERSION ); + FREE_FILE( aMemFile ); + m_tbnverts = NULL; + return false; + } + + if( m_tbnverts->modelCRC != RI->currentmodel->modelCRC ) + { + ALERT( at_console, "%s was changed, TBN cache will be updated\n", szFilename ); + FREE_FILE( aMemFile ); + m_tbnverts = NULL; + return false; + } + + // all done, we can load TBN from disk + m_iTBNState = TBNSTATE_LOADING; + return true; +} + +bool CStudioModelRenderer :: CalcLightmapAxis( mstudiosurface_t *surf, const dfacelight_t *fl, const dmodelfacelight_t *dfl ) +{ + int ssize = dfl->texture_step; + Vector mins, maxs, size, t1, t2; + Vector planeNormal; + Vector lmvecs[2]; + int i, axis; + + ClearBounds( mins, maxs ); + for( i = 0; i < 3; i++ ) + AddPointToBounds( m_arrayverts[m_nNumTempVerts+i], mins, maxs ); + + // compute triangle normal + t1 = m_arrayverts[m_nNumTempVerts+0] - m_arrayverts[m_nNumTempVerts+1]; + t2 = m_arrayverts[m_nNumTempVerts+2] - m_arrayverts[m_nNumTempVerts+1]; + planeNormal = CrossProduct( t1, t2 ); + planeNormal = planeNormal.Normalize(); + + // round to the lightmap resolution + for( i = 0; i < 3; i++ ) + { + mins[i] = ssize * floor( mins[i] / ssize ); + maxs[i] = ssize * ceil( maxs[i] / ssize ); + size[i] = (maxs[i] - mins[i]) / ssize; + } + + // the two largest axis will be the lightmap size + planeNormal.x = fabs( planeNormal.x ); + planeNormal.y = fabs( planeNormal.y ); + planeNormal.z = fabs( planeNormal.z ); + lmvecs[0] = lmvecs[1] = g_vecZero; + + if( planeNormal.x >= planeNormal.y && planeNormal.x >= planeNormal.z ) + { + surf->lightextents[0] = size[1]; + surf->lightextents[1] = size[2]; + lmvecs[0][1] = 1.0 / ssize; + lmvecs[1][2] = 1.0 / ssize; + axis = 0; + } + else if( planeNormal.y >= planeNormal.x && planeNormal.y >= planeNormal.z ) + { + surf->lightextents[0] = size[0]; + surf->lightextents[1] = size[2]; + lmvecs[0][0] = 1.0 / ssize; + lmvecs[1][2] = 1.0 / ssize; + axis = 1; + } + else + { + surf->lightextents[0] = size[0]; + surf->lightextents[1] = size[1]; + lmvecs[0][0] = 1.0 / ssize; + lmvecs[1][1] = 1.0 / ssize; + axis = 2; + } + + // bad normal? + if( !planeNormal[axis] ) + { + // set lightmap number to avoid assertation + surf->lightmaptexturenum = tr.current_lightmap_texture; + return true; + } + + if( surf->lightextents[0] > ( MAX_STUDIO_LIGHTMAP_SIZE - 1 )) + { + lmvecs[0] *= (float)(MAX_STUDIO_LIGHTMAP_SIZE - 1.0f) / surf->lightextents[0]; + surf->lightextents[0] = MAX_STUDIO_LIGHTMAP_SIZE - 1; + } + + if( surf->lightextents[1] > ( MAX_STUDIO_LIGHTMAP_SIZE - 1 )) + { + lmvecs[1] *= (float)(MAX_STUDIO_LIGHTMAP_SIZE - 1.0f) / surf->lightextents[1]; + surf->lightextents[1] = MAX_STUDIO_LIGHTMAP_SIZE - 1; + } + + memcpy( surf->styles, fl->styles, sizeof( surf->styles )); + surf->texture_step = ssize; + + if( fl->lightofs != -1 ) + { + if( worldmodel->lightdata != NULL ) + surf->samples = worldmodel->lightdata + (fl->lightofs/3); + if( world->deluxedata != NULL ) + surf->normals = world->deluxedata + (fl->lightofs/3); + if( world->shadowdata != NULL ) + surf->shadows = world->shadowdata + (fl->lightofs/3); + } + + // calculate the world coordinates of the lightmap samples + if( !GL_AllocLightmapForFace( surf )) + return false; // page is full + + for( i = 0; i < 3; i++ ) + { + svert_t *v = &m_arrayxvert[m_nNumTempVerts+i]; + Vector point = m_arrayverts[m_nNumTempVerts+i] - mins; + R_LightmapCoords( surf, point, lmvecs, v->lmcoord0, 0 ); // styles 0-1 + R_LightmapCoords( surf, point, lmvecs, v->lmcoord1, 2 ); // styles 2-3 + } + + return true; +} + +void CStudioModelRenderer :: AllocLightmapsForMesh( StudioMesh_t *pCurMesh, const dmodelfacelight_t *dfl ) +{ + int start_light_faces = m_nNumLightFaces; + int start_temp_verts = m_nNumTempVerts; + bool lightmap_restarted = false; + + // should have the surface cache to store lightmap results + if( !m_pStudioCache || !m_pStudioCache->surfaces ) + return; + + if( !dfl || dfl->numfaces <= 0 ) + return; +lm_restart: + pCurMesh->lightmapnum = tr.current_lightmap_texture; + m_nNumLightFaces = start_light_faces; + m_nNumTempVerts = start_temp_verts; + + // now vertexes are stored into intermediate array and we can using it to compute lightmatix + for( int i = 0; i < (pCurMesh->numindices / 3); i++ ) + { + const dfacelight_t *fl = &dfl->faces[m_nNumLightFaces]; + mstudiosurface_t *surf = &m_pStudioCache->surfaces[m_nNumLightFaces]; + + if( !CalcLightmapAxis( surf, fl, dfl )) + { + if( !lightmap_restarted ) + { + lightmap_restarted = true; + goto lm_restart; + } + Msg( "%s: mesh %d failed to allocate lightmap\n", RI->currentmodel->name, pCurMesh - m_pTempMesh ); + } + + ASSERT( surf->lightmaptexturenum == pCurMesh->lightmapnum ); +// Msg( "face %d, lightofs %d, extents %d %d\n", m_nNumLightFaces, fl->lightofs, surf->lightextents[0], surf->lightextents[1] ); + ASSERT( m_nNumTempVerts < MAXARRAYVERTS ); + + m_nNumTempVerts += 3; + m_nNumLightFaces++; + } +} + +void CStudioModelRenderer :: SetupSubmodelVerts( const mstudiomodel_t *pSubModel, const matrix3x4 bones[], void *srclight, int lightmode ) +{ + short *pskinref = (short *)((byte *)m_pStudioHeader + m_pStudioHeader->skinindex); // setup skinref for skin == 0 + mstudiotexture_t *ptexture = (mstudiotexture_t *)((byte *)m_pStudioHeader + m_pStudioHeader->textureindex); + mstudioboneweight_t *pvertweight = (mstudioboneweight_t *)((byte *)m_pStudioHeader + pSubModel->blendvertinfoindex); + mstudioboneweight_t *pnormweight = (mstudioboneweight_t *)((byte *)m_pStudioHeader + pSubModel->blendnorminfoindex); + mstudiobone_t *pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + Vector *pstudioverts = (Vector *)((byte *)m_pStudioHeader + pSubModel->vertindex); + Vector *pstudionorms = (Vector *)((byte *)m_pStudioHeader + pSubModel->normindex); + bool has_vertlight = ( lightmode == LIGHTSTATIC_VERTEX ) ? true : false; + bool has_surflight = ( lightmode == LIGHTSTATIC_SURFACE ) ? true : false; + byte *pvertbone = ((byte *)m_pStudioHeader + pSubModel->vertinfoindex); + byte *pnormbone = ((byte *)m_pStudioHeader + pSubModel->norminfoindex); + mstudiomaterial_t *pmaterial = (mstudiomaterial_t *)RI->currentmodel->materials; + static Vector localverts[MAXARRAYVERTS]; + bool use_fan_sequence = false; + bool smooth_tbn = false; + dmodelvertlight_t *dvl = NULL; + dmodelfacelight_t *dfl = NULL; + int i, count; + matrix3x4 skinMat; + + switch( lightmode ) + { + case LIGHTSTATIC_VERTEX: + dvl = (dmodelvertlight_t *)srclight; + break; + case LIGHTSTATIC_SURFACE: + dfl = (dmodelfacelight_t *)srclight; + use_fan_sequence = true; + break; + } + + if( m_iTBNState == TBNSTATE_GENERATE || use_fan_sequence ) + { + // we need to build TBN in refrence pose to avoid seams + if( RI->currentmodel->poseToBone ) + { + // compute weighted vertexes + for( int i = 0; i < pSubModel->numverts; i++ ) + { + ComputeSkinMatrix( &pvertweight[i], bones, skinMat ); + localverts[i] = skinMat.VectorTransform( pstudioverts[i] ); + } + } + else + { + // compute unweighted vertexes + for( int i = 0; i < pSubModel->numverts; i++ ) + localverts[i] = bones[pvertbone[i]].VectorTransform( pstudioverts[i] ); + } + } + + memset( m_pTempMesh, 0 , sizeof( m_pTempMesh )); + m_nNumArrayVerts = m_nNumArrayElems = 0; + m_nNumTempVerts = 0; + + // build all the data for current submodel + for( i = 0; i < pSubModel->nummesh; i++ ) + { + mstudiomesh_t *pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + pSubModel->meshindex) + i; + mstudiomaterial_t *pmaterial = &RI->currentmodel->materials[pskinref[pmesh->skinref]]; + short *ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex); + StudioMesh_t *pCurMesh = &m_pTempMesh[i]; + + // fill temp mesh info + pCurMesh->firstvertex = m_nNumArrayVerts; + pCurMesh->firstindex = m_nNumArrayElems; + pCurMesh->lightmapnum = -1; + pCurMesh->numvertices = 0; + pCurMesh->numindices = 0; + + mstudiotexture_t *ptexture = pmaterial->pSource; + float s = 1.0f / (float)ptexture->width; + float t = 1.0f / (float)ptexture->height; + + // at least one of submodel mesh reqiuried smoothing + if( FBitSet( ptexture->flags, STUDIO_NF_SMOOTH )) + smooth_tbn = true; + + // first create trifan array from studiomodel mesh + while( count = *( ptricmds++ )) + { + bool strip = ( count < 0 ) ? false : true; + int vertexState = 0; + + if( count < 0 ) count = -count; + + for( ; count > 0; count--, ptricmds += 4 ) + { + ASSERT( m_nNumArrayVerts < MAXARRAYVERTS ); + ASSERT( m_nNumArrayElems < MAXARRAYVERTS * 3 ); + + if( vertexState++ < 3 ) + { + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts; + } + else if( strip ) + { + // flip triangles between clockwise and counter clockwise + if( vertexState & 1 ) + { + // draw triangle [n-2 n-1 n] + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - 2; + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - 1; + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts; + } + else + { + // draw triangle [n-1 n-2 n] + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - 1; + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - 2; + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts; + } + } + else + { + // draw triangle fan [0 n-1 n] + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - ( vertexState - 1 ); + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - 1; + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts; + } + + // don't concat by matrix here - it's should be done on GPU + m_arrayxvert[m_nNumArrayVerts].vertex = pstudioverts[ptricmds[0]]; + m_arrayxvert[m_nNumArrayVerts].normal = pstudionorms[ptricmds[1]]; + + if( m_iTBNState == TBNSTATE_GENERATE || use_fan_sequence ) + { + // transformed vertices to build TBN + m_arrayverts[m_nNumArrayVerts] = localverts[ptricmds[0]]; + } + + if( m_iTBNState == TBNSTATE_LOADING && m_tbnverts != NULL ) + { + // loading TBN from cache + m_arrayxvert[m_nNumArrayVerts].tangent = m_tbnverts->verts[m_nNumTBNVerts].tangent; + m_arrayxvert[m_nNumArrayVerts].binormal = m_tbnverts->verts[m_nNumTBNVerts].binormal; + m_arrayxvert[m_nNumArrayVerts].normal = m_tbnverts->verts[m_nNumTBNVerts].normal; + m_nNumTBNVerts++; + } + + if( dvl != NULL && dvl->numverts > 0 ) + { + dvertlight_t *vl = &dvl->verts[m_nNumLightVerts++]; + + // now setup light and deluxe vector + for( int map = 0; map < MAXLIGHTMAPS; map++ ) + { + m_arrayxvert[m_nNumArrayVerts].light[map] = PackColor( vl->light[map] ); + m_arrayxvert[m_nNumArrayVerts].deluxe[map] = PackColor( vl->deluxe[map] ); + } + } + + if( FBitSet( ptexture->flags, STUDIO_NF_CHROME )) + { + // probably always equal 64 (see studiomdl.c for details) + m_arrayxvert[m_nNumArrayVerts].stcoord[0] = s; + m_arrayxvert[m_nNumArrayVerts].stcoord[1] = t; + } + else if( FBitSet( ptexture->flags, STUDIO_NF_UV_COORDS )) + { + m_arrayxvert[m_nNumArrayVerts].stcoord[0] = HalfToFloat( ptricmds[2] ); + m_arrayxvert[m_nNumArrayVerts].stcoord[1] = HalfToFloat( ptricmds[3] ); + } + else + { + m_arrayxvert[m_nNumArrayVerts].stcoord[0] = ptricmds[2] * s; + m_arrayxvert[m_nNumArrayVerts].stcoord[1] = ptricmds[3] * t; + } + + m_arrayxvert[m_nNumArrayVerts].lmcoord0[0] = 0.0f; + m_arrayxvert[m_nNumArrayVerts].lmcoord0[1] = 0.0f; + m_arrayxvert[m_nNumArrayVerts].lmcoord0[2] = 0.0f; + m_arrayxvert[m_nNumArrayVerts].lmcoord0[3] = 0.0f; + m_arrayxvert[m_nNumArrayVerts].lmcoord1[0] = 0.0f; + m_arrayxvert[m_nNumArrayVerts].lmcoord1[1] = 0.0f; + m_arrayxvert[m_nNumArrayVerts].lmcoord1[2] = 0.0f; + m_arrayxvert[m_nNumArrayVerts].lmcoord1[3] = 0.0f; + + if( RI->currentmodel->poseToBone != NULL ) + { + mstudioboneweight_t *pCurWeight = &pvertweight[ptricmds[0]]; + + m_arrayxvert[m_nNumArrayVerts].boneid[0] = pCurWeight->bone[0]; + m_arrayxvert[m_nNumArrayVerts].boneid[1] = pCurWeight->bone[1]; + m_arrayxvert[m_nNumArrayVerts].boneid[2] = pCurWeight->bone[2]; + m_arrayxvert[m_nNumArrayVerts].boneid[3] = pCurWeight->bone[3]; + m_arrayxvert[m_nNumArrayVerts].weight[0] = pCurWeight->weight[0]; + m_arrayxvert[m_nNumArrayVerts].weight[1] = pCurWeight->weight[1]; + m_arrayxvert[m_nNumArrayVerts].weight[2] = pCurWeight->weight[2]; + m_arrayxvert[m_nNumArrayVerts].weight[3] = pCurWeight->weight[3]; + } + else + { + m_arrayxvert[m_nNumArrayVerts].boneid[0] = pvertbone[ptricmds[0]]; + m_arrayxvert[m_nNumArrayVerts].boneid[1] = -1; + m_arrayxvert[m_nNumArrayVerts].boneid[2] = -1; + m_arrayxvert[m_nNumArrayVerts].boneid[3] = -1; + m_arrayxvert[m_nNumArrayVerts].weight[0] = 255; + m_arrayxvert[m_nNumArrayVerts].weight[1] = 0; + m_arrayxvert[m_nNumArrayVerts].weight[2] = 0; + m_arrayxvert[m_nNumArrayVerts].weight[3] = 0; + } + + m_nNumArrayVerts++; + } + } + + // store counts + pCurMesh->numvertices = m_nNumArrayVerts - pCurMesh->firstvertex; + pCurMesh->numindices = m_nNumArrayElems - pCurMesh->firstindex; + } +#if 0 + if( lightmode == LIGHTSTATIC_SURFACE ) + { + Vector *normals = (Vector *)Mem_Alloc( m_nNumArrayVerts * sizeof( Vector )); + + for( int hashSize = 1; hashSize < m_nNumArrayVerts; hashSize <<= 1 ); + hashSize = hashSize >> 2; + + // build a map from vertex to a list of triangles that share the vert. + CUtlArray vertHashMap; + + vertHashMap.AddMultipleToTail( hashSize ); + + for( int vertID = 0; vertID < m_nNumArrayVerts; vertID++ ) + { + svert_t *v = &m_arrayxvert[vertID]; + uint hash = VertexHashKey( v->vertex, hashSize ); + vertHashMap[hash].AddToTail( vertID ); + } + + for( int hashID = 0; hashID < hashSize; hashID++ ) + { + for( int i = 0; i < vertHashMap[hashID].Size(); i++ ) + { + int vertID = vertHashMap[hashID][i]; + svert_t *v0 = &m_arrayxvert[vertID]; + + for( int j = 0; j < vertHashMap[hashID].Size(); j++ ) + { + svert_t *v1 = &m_arrayxvert[vertHashMap[hashID][j]]; + + if( !VectorCompareEpsilon( v0->vertex, v1->vertex, ON_EPSILON )) + continue; + + if( DotProduct( v0->normal, v1->normal ) >= tr.smoothing_threshold ) + VectorAdd( normals[vertID], v1->normal, normals[vertID] ); + } + } + } + + // copy smoothed normals back + for( i = 0; i < m_nNumArrayVerts; i++ ) + m_arrayxvert[i].normal = normals[i].Normalize(); + Mem_Free( normals ); + } +#endif + if( use_fan_sequence ) + { + CUtlArray tempxvert; + CUtlArray tempvert; + + // convert strips to fan sequences (that never exceeds MAXARRAYVERTS) + for( i = 0; i < m_nNumArrayElems; i++ ) + { + tempxvert.AddToTail( m_arrayxvert[m_arrayelems[i]] ); + tempvert.AddToTail( m_arrayverts[m_arrayelems[i]] ); + } + + // also update mesh data + for( i = 0; i < pSubModel->nummesh; i++ ) + { + StudioMesh_t *pCurMesh = &m_pTempMesh[i]; + pCurMesh->firstvertex = pCurMesh->firstindex; + pCurMesh->numvertices = pCurMesh->numindices; + } + + // convert indexes + for( i = 0; i < m_nNumArrayElems; i++ ) + m_arrayelems[i] = i; + + ASSERT( tempxvert.Count() < MAXARRAYVERTS ); + ASSERT( tempvert.Count() < MAXARRAYVERTS ); + + // copy unstripified vertexes back + memcpy( m_arrayxvert, tempxvert.Base(), tempxvert.Count() * sizeof( svert_t )); + memcpy( m_arrayverts, tempvert.Base(), tempvert.Count() * sizeof( Vector )); + m_nNumArrayVerts = tempxvert.Count(); + } + + if( dfl != NULL ) + { + // build all the data for current submodel + for( i = 0; i < pSubModel->nummesh; i++ ) + { + StudioMesh_t *pCurMesh = &m_pTempMesh[i]; + // allocate lightmaps for static meshes + AllocLightmapsForMesh( pCurMesh, dfl ); + } + } + + // compute tangent space for all submodel meshes to avoid seams + if( m_iTBNState == TBNSTATE_GENERATE ) + { + // build a map from vertex to a list of triangles that share the vert. + CUtlArray vertToTriMap; + + vertToTriMap.AddMultipleToTail( m_nNumArrayVerts ); + int triID; + + for( triID = 0; triID < (m_nNumArrayElems / 3); triID++ ) + { + vertToTriMap[m_arrayelems[triID*3+0]].AddToTail( triID ); + vertToTriMap[m_arrayelems[triID*3+1]].AddToTail( triID ); + vertToTriMap[m_arrayelems[triID*3+2]].AddToTail( triID ); + } + + // calculate the tangent space for each triangle. + CUtlArray triSVect, triTVect; + triSVect.AddMultipleToTail( (m_nNumArrayElems / 3) ); + triTVect.AddMultipleToTail( (m_nNumArrayElems / 3) ); + + float *v[3], *tc[3]; + + for( triID = 0; triID < (m_nNumArrayElems / 3); triID++ ) + { + for( int i = 0; i < 3; i++ ) + { + v[i] = (float *)&m_arrayverts[m_arrayelems[triID*3+i]]; // transformed to global pose to avoid seams + tc[i] = (float *)&m_arrayxvert[m_arrayelems[triID*3+i]].stcoord; + } + + CalcTBN( v[0], v[1], v[2], tc[0], tc[1], tc[2], triSVect[triID], triTVect[triID] ); + } + + // calculate an average tangent space for each vertex. + for( int vertID = 0; vertID < m_nNumArrayVerts; vertID++ ) + { + svert_t *v = &m_arrayxvert[vertID]; + const Vector &normal = v->normal; + Vector &finalSVect = v->tangent; + Vector &finalTVect = v->binormal; + Vector sVect, tVect; + + sVect = tVect = g_vecZero; + + for( triID = 0; triID < vertToTriMap[vertID].Size(); triID++ ) + { + sVect += triSVect[vertToTriMap[vertID][triID]]; + tVect += triTVect[vertToTriMap[vertID][triID]]; + } + + if( smooth_tbn ) + { + // smooth TBN + Vector vertPos1 = m_arrayverts[vertID]; // transformed to global pose to avoid seams + + for( int vertID2 = 0; vertID2 < m_nNumArrayVerts; vertID2++ ) + { + if( vertID2 == vertID ) + continue; + + Vector vertPos2 = m_arrayverts[vertID2]; // transformed to global pose to avoid seams + + if( vertPos1 == vertPos2 ) + { + for( int triID2 = 0; triID2 < vertToTriMap[vertID2].Size(); triID2++ ) + { + sVect += triSVect[vertToTriMap[vertID2][triID2]]; + tVect += triTVect[vertToTriMap[vertID2][triID2]]; + } + } + } + } + + // rotate tangent and binormal back to bone space + ComputeSkinMatrix( &m_arrayxvert[vertID], bones, skinMat ); + + sVect = skinMat.VectorIRotate( sVect ); + tVect = skinMat.VectorIRotate( tVect ); + + if( !smooth_tbn ) + { + Vector tmpVect = CrossProduct( sVect, tVect ); + bool leftHanded = DotProduct( tmpVect, normal ) < 0.0f; + + if( !leftHanded ) + { + tVect = CrossProduct( normal, sVect ); + sVect = CrossProduct( tVect, normal ); + } + else + { + tVect = CrossProduct( sVect, normal ); + sVect = CrossProduct( normal, tVect ); + } + } + + finalSVect = sVect.Normalize(); + finalTVect = tVect.Normalize(); + } + + // search for submodel offset + int offset = (byte *)pSubModel - (byte *)m_pStudioHeader; + + for( int j = 0; j < MAXSTUDIOMODELS; j++ ) + { + if( m_tbnverts->submodels[j].submodel_offset == offset ) + break; + } + + // store vertex offset for bounds checking + m_tbnverts->submodels[j].vertex_offset = m_nNumTBNVerts; + + // store precomputed TBN into our cache + for( int i = 0; i < m_nNumArrayVerts; i++ ) + { + m_tbnverts->verts[m_nNumTBNVerts].tangent = m_arrayxvert[i].tangent; + m_tbnverts->verts[m_nNumTBNVerts].binormal = m_arrayxvert[i].binormal; + m_tbnverts->verts[m_nNumTBNVerts].normal = m_arrayxvert[i].normal; + m_nNumTBNVerts++; + } + } +} + +void CStudioModelRenderer :: MeshCreateBuffer( vbomesh_t *pOut, const mstudiomesh_t *pMesh, const StudioMesh_t *pMeshInfo, int lightmode ) +{ + // FIXME: if various skinfamilies has different sizes then our texcoords probably will be invalid for pev->skin != 0 + short *pskinref = (short *)((byte *)m_pStudioHeader + m_pStudioHeader->skinindex); // setup skinref for skin == 0 + mstudiotexture_t *ptexture = (mstudiotexture_t *)((byte *)m_pStudioHeader + m_pStudioHeader->textureindex); + mstudiomaterial_t *pmaterial = (mstudiomaterial_t *)RI->currentmodel->materials + pskinref[pMesh->skinref]; + mstudiobone_t *pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + bool has_bumpmap = FBitSet( pmaterial->flags, STUDIO_NF_NORMALMAP ) ? true : false; + bool has_boneweights = ( RI->currentmodel->poseToBone != NULL ) ? true : false; + bool has_vertexlight = ( lightmode == LIGHTSTATIC_VERTEX ) ? true : false; + bool has_lightmap = ( lightmode == LIGHTSTATIC_SURFACE ) ? true : false; + static uint arrayelems[MAXARRAYVERTS*3]; + Vector mins, maxs; + int i; + + pOut->skinref = pMesh->skinref; + pOut->parentbone = 0xFF; + + ClearBounds( mins, maxs ); + + // we need to compute some things individually per mesh + for( i = 0; i < pMeshInfo->numvertices; i++ ) + { + svert_t *vert = &m_arrayxvert[pMeshInfo->firstvertex + i]; + + AddPointToBounds( vert->vertex, mins, maxs ); + + int boneid = vert->boneid[0]; + + if( pOut->parentbone == 0xFF ) + pOut->parentbone = boneid; + + // update bone if it was parent of current bone + if( pOut->parentbone != boneid ) + { + for( int k = pOut->parentbone; k != -1; k = pbones[k].parent ) + { + if( k == boneid ) + { + pOut->parentbone = boneid; + break; + } + } + } + } + + // remap indices to local range + for( i = 0; i < pMeshInfo->numindices; i++ ) + arrayelems[i] = m_arrayelems[pMeshInfo->firstindex + i] - pMeshInfo->firstvertex; + + pOut->mins = mins; + pOut->maxs = maxs; + pOut->lightmapnum = pMeshInfo->lightmapnum; + pOut->numVerts = pMeshInfo->numvertices; + pOut->numElems = pMeshInfo->numindices; + + GL_CheckVertexArrayBinding(); + + // determine optimal mesh loader + uint attribs = ComputeAttribFlags( m_pStudioHeader->numbones, has_bumpmap, has_boneweights, has_vertexlight, has_lightmap ); + uint type = SelectMeshLoader( m_pStudioHeader->numbones, has_bumpmap, has_boneweights, has_vertexlight, has_lightmap ); + + // move data to video memory + if( glConfig.version < ACTUAL_GL_VERSION ) + m_pfnMeshLoaderGL21[type].CreateBuffer( pOut, &m_arrayxvert[pMeshInfo->firstvertex] ); + else m_pfnMeshLoaderGL30[type].CreateBuffer( pOut, &m_arrayxvert[pMeshInfo->firstvertex] ); + CreateIndexBuffer( pOut, arrayelems ); + +// Msg( "%s -> %s\n", m_pfnMeshLoaderGL21[type].BufferName, RI->currentmodel->name ); + + // link it with vertex array object + pglGenVertexArrays( 1, &pOut->vao ); + pglBindVertexArray( pOut->vao ); + if( glConfig.version < ACTUAL_GL_VERSION ) + m_pfnMeshLoaderGL21[type].BindBuffer( pOut, attribs ); + else m_pfnMeshLoaderGL30[type].BindBuffer( pOut, attribs ); + BindIndexBuffer( pOut ); + pglBindVertexArray( GL_FALSE ); + + // update stats + tr.total_vbo_memory += pOut->cacheSize; +} + +mstudiocache_t *CStudioModelRenderer :: CreateStudioCache( void *srclight, int lightmode ) +{ + float start_time = Sys_DoubleTime(); + bool unique_model = (srclight == NULL); // just for more readable code + TmpModel_t submodel[MAXSTUDIOMODELS]; // list of unique models + float poseparams[MAXSTUDIOPOSEPARAM]; + static matrix3x4 bones[MAXSTUDIOBONES]; + static Vector pos[MAXSTUDIOBONES]; + static Vector4D q[MAXSTUDIOBONES]; + int i, j, k, bufSize = 0; + int num_submodels = 0; + byte *buffer, *bufend; // simple bounds check + dmodelvertlight_t *dvl = NULL; + dmodelfacelight_t *dfl = NULL; + mstudiocache_t *studiocache; + mstudiobodyparts_t *pbodypart; + mstudiomodel_t *psubmodel; + msubmodel_t *pModel; + mstudiobone_t *pbones; + matrix3x4 root; + + switch( lightmode ) + { + case LIGHTSTATIC_VERTEX: + dvl = (dmodelvertlight_t *)srclight; + break; + case LIGHTSTATIC_SURFACE: + dfl = (dmodelfacelight_t *)srclight; + break; + } + + // materials goes first to determine bump + if( unique_model ) LoadStudioMaterials (); + else PrecacheStudioShaders (); + + // build default pose to build seamless TBN-space + pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + m_boneSetup.SetStudioPointers( m_pStudioHeader, poseparams ); + m_boneSetup.CalcDefaultPoseParameters( poseparams ); + root.Identity(); + + // setup local bone matrices + if( unique_model && FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEINFO )) + { + // NOTE: extended boneinfo goes immediately after bones + mstudioboneinfo_t *boneinfo = (mstudioboneinfo_t *)((byte *)pbones + m_pStudioHeader->numbones * sizeof( mstudiobone_t )); + + // alloc storage for bone array + RI->currentmodel->poseToBone = (mposetobone_t *)Mem_Alloc( sizeof( mposetobone_t )); + + for( j = 0; j < m_pStudioHeader->numbones; j++ ) + LoadLocalMatrix( j, &boneinfo[j] ); + } + + if( lightmode == LIGHTSTATIC_SURFACE ) + { + root = matrix3x4( Vector( dfl->origin ), Vector( dfl->angles ), Vector( dfl->scale )); + m_boneSetup.InitPose( pos, q ); + m_boneSetup.AccumulatePose( NULL, pos, q, 0, 0.0f, 1.0f ); + } + else + { + // compute default pose with no anim + m_boneSetup.InitPose( pos, q ); + } + + for( i = 0; i < m_pStudioHeader->numbones; i++ ) + { + // initialize bonematrix + matrix3x4 bonematrix = matrix3x4( pos[i], q[i] ); + + if( pbones[i].parent == -1 ) bones[i] = root.ConcatTransforms( bonematrix ); + else bones[i] = bones[pbones[i].parent].ConcatTransforms( matrix3x4( pos[i], q[i] )); + } + + if( RI->currentmodel->poseToBone != NULL ) + { + // convert bones into worldtransform + for( i = 0; i < m_pStudioHeader->numbones; i++ ) + bones[i] = bones[i].ConcatTransforms( RI->currentmodel->poseToBone->posetobone[i] ); + } + + memset( submodel, 0, sizeof( submodel )); + word meshUniqueID = 0; + m_nNumTBNVerts = 0; // counting through all the submodels + num_submodels = 0; + + // build list of unique submodels (by name) + for( i = 0; i < m_pStudioHeader->numbodyparts; i++ ) + { + pbodypart = (mstudiobodyparts_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bodypartindex) + i; + + for( j = 0; j < pbodypart->nummodels; j++ ) + { + psubmodel = (mstudiomodel_t *)((byte *)m_pStudioHeader + pbodypart->modelindex) + j; + if( !psubmodel->nummesh ) continue; // blank submodel, ignore it + + for( k = 0; k < num_submodels; k++ ) + { + if( !Q_stricmp( submodel[k].name, psubmodel->name )) + break; + } + + // add new one + if( k == num_submodels ) + { + Q_strncpy( submodel[k].name, psubmodel->name, sizeof( submodel[k].name )); + submodel[k].pmodel = psubmodel; + num_submodels++; + } + } + } + + m_iTBNState = TBNSTATE_INACTIVE; + + // only models with bump-mapping is required to build TBN matrices + if( FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BUMP )) + { + if( !StudioLoadTBN( )) + { + int max_model_verts = 0; + + // multiplier 8 is a good enough to predict max vertices count + for( i = 0; i < num_submodels; i++ ) + max_model_verts += submodel[i].pmodel->numverts * 8; + + // reserve space for all the model verts + m_tbnverts = (dmodeltbn_t *)calloc( sizeof( dmodeltbn_t ), max_model_verts ); + m_tbnverts->ident = IDTBNHEADER; + m_tbnverts->version = TBN_VERSION; + m_tbnverts->modelCRC = RI->currentmodel->modelCRC; + + // store submodel offsets here + for( i = 0; i < num_submodels; i++ ) + { + psubmodel = submodel[i].pmodel; + m_tbnverts->submodels[i].submodel_offset = (byte *)psubmodel - (byte *)m_pStudioHeader; + } + m_iTBNState = TBNSTATE_GENERATE; + } + + if( !unique_model && m_iTBNState == TBNSTATE_GENERATE ) + ALERT( at_warning, "%s: generate TBN due loading light cache\n", RI->currentmodel->name ); + } + + // compute cache size (include individual meshes) + bufSize = sizeof( mstudiocache_t ) + sizeof( mbodypart_t ) * m_pStudioHeader->numbodyparts; + + for( i = 0; i < num_submodels; i++ ) + bufSize += sizeof( msubmodel_t ) + sizeof( vbomesh_t ) * submodel[i].pmodel->nummesh; + + if( dfl != NULL && dfl->numfaces > 0 ) + bufSize += sizeof( mstudiosurface_t ) * dfl->numfaces; + + buffer = (byte *)Mem_Alloc( bufSize ); + bufend = buffer + bufSize; + + // setup pointers + studiocache = (mstudiocache_t *)buffer; + buffer += sizeof( mstudiocache_t ); + m_pStudioCache = studiocache; + + if( dfl != NULL && dfl->numfaces > 0 ) + { + studiocache->surfaces = (mstudiosurface_t *)buffer; + studiocache->numsurfaces = dfl->numfaces; + buffer += sizeof( mstudiosurface_t ) * dfl->numfaces; + } + + studiocache->bodyparts = (mbodypart_t *)buffer; + buffer += sizeof( mbodypart_t ) * m_pStudioHeader->numbodyparts; + studiocache->numbodyparts = m_pStudioHeader->numbodyparts; + + // begin to building submodels + for( i = 0; i < num_submodels; i++ ) + { + psubmodel = submodel[i].pmodel; + pModel = (msubmodel_t *)buffer; + buffer += sizeof( msubmodel_t ); + pModel->nummesh = psubmodel->nummesh; + + // setup meshes + pModel->meshes = (vbomesh_t *)buffer; + buffer += sizeof( vbomesh_t ) * psubmodel->nummesh; + + // sanity check for vertexlighting + if( dvl != NULL && dvl->numverts > 0 ) + { + // search for submodel offset + int offset = (byte *)psubmodel - (byte *)m_pStudioHeader; + + for( j = 0; j < MAXSTUDIOMODELS; j++ ) + { + if( dvl->submodels[j].submodel_offset == offset ) + break; + } + + ASSERT( j != MAXSTUDIOMODELS ); + ASSERT( m_nNumLightVerts == dvl->submodels[j].vertex_offset ); + } + + // sanity check for surfacelighting + if( dfl != NULL && dfl->numfaces > 0 ) + { + // search for submodel offset + int offset = (byte *)psubmodel - (byte *)m_pStudioHeader; + + for( j = 0; j < MAXSTUDIOMODELS; j++ ) + { + if( dfl->submodels[j].submodel_offset == offset ) + break; + } + + ASSERT( j != MAXSTUDIOMODELS ); + ASSERT( m_nNumLightFaces == dfl->submodels[j].surface_offset ); + } + + // sanity check for TBN matrix + if( m_iTBNState == TBNSTATE_LOADING && m_tbnverts != NULL ) + { + // search for submodel offset + int offset = (byte *)psubmodel - (byte *)m_pStudioHeader; + + for( j = 0; j < MAXSTUDIOMODELS; j++ ) + { + if( m_tbnverts->submodels[j].submodel_offset == offset ) + break; + } + + ASSERT( j != MAXSTUDIOMODELS ); + ASSERT( m_nNumTBNVerts == m_tbnverts->submodels[j].vertex_offset ); + } + + // setup all the vertices for a given submodel + SetupSubmodelVerts( psubmodel, bones, srclight, lightmode ); + + for( j = 0; j < psubmodel->nummesh; j++ ) + { + mstudiomesh_t *pSrc = (mstudiomesh_t *)((byte *)m_pStudioHeader + psubmodel->meshindex) + j; + vbomesh_t *pDst = &pModel->meshes[j]; + + MeshCreateBuffer( pDst, pSrc, &m_pTempMesh[j], lightmode ); + pDst->uniqueID = meshUniqueID++; + } + submodel[i].pout = pModel; // store unique submodel + } + + // and finally setup bodyparts + for( i = 0; i < m_pStudioHeader->numbodyparts; i++ ) + { + pbodypart = (mstudiobodyparts_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bodypartindex) + i; + mbodypart_t *pBodyPart = &studiocache->bodyparts[i]; + + pBodyPart->base = pbodypart->base; + pBodyPart->nummodels = pbodypart->nummodels; + + // setup pointers to unique models + for( j = 0; j < pBodyPart->nummodels; j++ ) + { + psubmodel = (mstudiomodel_t *)((byte *)m_pStudioHeader + pbodypart->modelindex) + j; + if( !psubmodel->nummesh ) continue; // blank submodel, leave null pointer + + // find supposed model + for( k = 0; k < num_submodels; k++ ) + { + if( !Q_stricmp( submodel[k].name, psubmodel->name )) + { + pBodyPart->models[j] = submodel[k].pout; + break; + } + } + + if( k == num_submodels ) + ALERT( at_error, "Couldn't find submodel %s for bodypart %i\n", psubmodel->name, i ); + } + } + + // TBN are created, time to store it + if( m_iTBNState == TBNSTATE_GENERATE && m_tbnverts != NULL ) + { + // store total vertices count + m_tbnverts->numverts = m_nNumTBNVerts; + if( StudioSaveTBN( )) + ALERT( at_console, "%s: TBN build time %g secs\n", RI->currentmodel->name, Sys_DoubleTime() - start_time ); + m_iTBNState = TBNSTATE_INACTIVE; + free( m_tbnverts ); + m_tbnverts = NULL; + } + else if( m_iTBNState == TBNSTATE_LOADING && m_tbnverts != NULL ) + { + m_iTBNState = TBNSTATE_INACTIVE; + FREE_FILE( m_tbnverts ); + m_tbnverts = NULL; + } + + // load lightmaps + m_pStudioCache->update_light = true; + + // invalidate + m_pStudioCache = NULL; + + // bounds checking + if( buffer != bufend ) + { + if( buffer > bufend ) + ALERT( at_error, "CreateStudioCache: memory buffer overrun\n" ); + else ALERT( at_error, "CreateStudioCache: memory buffer underrun\n" ); + } + + return studiocache; +} + +//----------------------------------------------------------------------------- +// all the caches should be build before starting the new map +//----------------------------------------------------------------------------- +void CStudioModelRenderer :: CreateStudioCacheVL( const char *modelname, int cacheID ) +{ + dvlightlump_t *vl = world->vertex_lighting; + + // first we need throw previous mesh + DeleteStudioCache( &tr.vertex_light_cache[cacheID] ); + + if( world->vertex_lighting == NULL ) + return; // for some reasons we missed this lump + + RI->currentmodel = IEngineStudio.Mod_ForName( modelname, false ); + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( RI->currentmodel ); + + if( !RI->currentmodel || !m_pStudioHeader ) + return; // download in progress? + + // first initialization + if( cacheID < vl->nummodels && vl->dataofs[cacheID] != -1 ) + { + dmodelvertlight_t *dml = (dmodelvertlight_t *)((byte *)vl + vl->dataofs[cacheID]); + + m_nNumLightVerts = 0; + + if( RI->currentmodel->modelCRC == dml->modelCRC ) + { + // now create mesh per entity with instanced vertex lighting + tr.vertex_light_cache[cacheID] = CreateStudioCache( dml, LIGHTSTATIC_VERTEX ); + } + + if( dml->numverts == m_nNumLightVerts ) + { + ALERT( at_aiconsole, "%s vertexlit instance created\n", modelname ); + } + else if( RI->currentmodel->modelCRC != dml->modelCRC ) + { + ALERT( at_error, "%s failed to create vertex lighting: model CRC %p != %p\n", + modelname, RI->currentmodel->modelCRC, dml->modelCRC ); + } + else + { + ALERT( at_error, "%s failed to create vertex lighting: model verts %i != total verts %i\n", + modelname, dml->numverts, m_nNumLightVerts ); + } + m_nNumLightVerts = 0; + } +} + +void CStudioModelRenderer :: CreateStudioCacheFL( const char *modelname, int cacheID ) +{ + dvlightlump_t *vl = world->surface_lighting; + + // first we need throw previous mesh + DeleteStudioCache( &tr.surface_light_cache[cacheID] ); + + if( world->surface_lighting == NULL ) + return; // for some reasons we missed this lump + + RI->currentmodel = IEngineStudio.Mod_ForName( modelname, false ); + m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( RI->currentmodel ); + + if( !RI->currentmodel || !m_pStudioHeader ) + return; // download in progress? + + // first initialization + if( cacheID < vl->nummodels && vl->dataofs[cacheID] != -1 ) + { + dmodelfacelight_t *dml = (dmodelfacelight_t *)((byte *)vl + vl->dataofs[cacheID]); + + m_nNumLightFaces = 0; + + if( RI->currentmodel->modelCRC == dml->modelCRC ) + { + // now create mesh per entity with instanced surface lighting + tr.surface_light_cache[cacheID] = CreateStudioCache( dml, LIGHTSTATIC_SURFACE ); + } + + if( dml->numfaces == m_nNumLightFaces ) + { + ALERT( at_aiconsole, "%s surfacelit instance created\n", modelname ); + } + else if( RI->currentmodel->modelCRC != dml->modelCRC ) + { + ALERT( at_error, "%s failed to create surface lighting: model CRC %p != %p\n", + modelname, RI->currentmodel->modelCRC, dml->modelCRC ); + } + else + { + ALERT( at_error, "%s failed to create surface lighting: model faces %i != total faces %i\n", + modelname, dml->numfaces, m_nNumLightFaces ); + } + m_nNumLightFaces = 0; + } +} + +void CStudioModelRenderer :: CreateStudioCacheVL( dmodelvertlight_t *dml, int cacheID ) +{ + if( RI->currentmodel->modelCRC == dml->modelCRC ) + { + // get lighting cache + m_pModelInstance->m_VlCache = tr.vertex_light_cache[cacheID]; + } + + if( m_pModelInstance->m_VlCache != NULL ) + { + SetBits( m_pModelInstance->info_flags, MF_VERTEX_LIGHTING ); + + for( int map = 0; map < MAXLIGHTMAPS; map++ ) + m_pModelInstance->styles[map] = dml->styles[map]; + + for( int i = 0; i < m_pStudioHeader->numtextures; i++ ) + { + mstudiomaterial_t *mat = &m_pModelInstance->materials[i]; + mat->forwardScene.Invalidate(); // refresh shaders + } + } + else + { + ALERT( at_warning, "failed to create vertex lighting for %s\n", RI->currentmodel->name ); + SetBits( m_pModelInstance->info_flags, MF_VL_BAD_CACHE ); + } +} + +void CStudioModelRenderer :: CreateStudioCacheFL( dmodelfacelight_t *dml, int cacheID ) +{ + if( RI->currentmodel->modelCRC == dml->modelCRC ) + { + // get lighting cache + m_pModelInstance->m_FlCache = tr.surface_light_cache[cacheID]; + } + + if( m_pModelInstance->m_FlCache != NULL ) + { + SetBits( m_pModelInstance->info_flags, MF_SURFACE_LIGHTING ); + + for( int map = 0; map < MAXLIGHTMAPS; map++ ) + m_pModelInstance->styles[map] = dml->styles[map]; + + for( int i = 0; i < m_pStudioHeader->numtextures; i++ ) + { + mstudiomaterial_t *mat = &m_pModelInstance->materials[i]; + mat->forwardScene.Invalidate(); // refresh shaders + } + } + else + { + ALERT( at_warning, "failed to create surface lighting for %s\n", RI->currentmodel->name ); + SetBits( m_pModelInstance->info_flags, MF_VL_BAD_CACHE ); + } +} + +//----------------------------------------------------------------------------- +// all the caches should be build before starting the new map +//----------------------------------------------------------------------------- +void CStudioModelRenderer :: FreeStudioCacheVL( void ) +{ + for( int i = 0; i < MAX_LIGHTCACHE; i++ ) + DeleteStudioCache( &tr.vertex_light_cache[i] ); +} + +void CStudioModelRenderer :: FreeStudioCacheFL( void ) +{ + for( int i = 0; i < MAX_LIGHTCACHE; i++ ) + DeleteStudioCache( &tr.surface_light_cache[i] ); +} + +void CStudioModelRenderer :: ProcessUserData( model_t *mod, qboolean create, const byte *buffer ) +{ + // to get something valid here + RI->currententity = GET_ENTITY( 0 ); + RI->currentmodel = mod; + + if( !( m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( RI->currentmodel ))) + return; + + IEngineStudio.StudioSetHeader( m_pStudioHeader ); + IEngineStudio.SetRenderModel( RI->currentmodel ); + + if( create ) + { + // compute model CRC to verify vertexlighting data + // NOTE: source buffer is not equal to Mod_Extradata! + studiohdr_t *src = (studiohdr_t *)buffer; + RI->currentmodel->modelCRC = FILE_CRC32( buffer, src->length ); + double start = Sys_DoubleTime(); + RI->currentmodel->studiocache = CreateStudioCache(); + double end = Sys_DoubleTime(); + r_buildstats.create_buffer_object += (end - start); + r_buildstats.total_buildtime += (end - start); + } + else + { + // release collision mesh + if( mod->bodymesh != NULL ) + { + mod->bodymesh->CMeshDesc::~CMeshDesc(); + Mem_Free( mod->bodymesh ); + mod->bodymesh = NULL; + } + DestroyMeshCache(); + } +} \ No newline at end of file diff --git a/cl_dll/render/gl_studiodecal.h b/cl_dll/render/gl_studiodecal.h new file mode 100644 index 0000000..a3ff4c3 --- /dev/null +++ b/cl_dll/render/gl_studiodecal.h @@ -0,0 +1,59 @@ +/* +gl_studiodecal.h - studio decal rendering +Copyright (C) 2018 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef GL_STUDIODECAL_H +#define GL_STUDIODECAL_H + +typedef enum +{ + DECAL_CLIP_MINUSU = 0x1, + DECAL_CLIP_MINUSV = 0x2, + DECAL_CLIP_PLUSU = 0x4, + DECAL_CLIP_PLUSV = 0x8, +}; + +typedef struct +{ + Vector2D m_UV; + word m_VertexIndex; // index into the DecalVertex_t list + bool m_FrontFacing; + bool m_InValidArea; +} DecalVertexInfo_t; + +// decal entry +typedef struct studiodecal_s +{ + // this part is goes to savelist + byte flags; + Vector normal; + Vector position; + word modelpose; // m_pModelInstance->pose_stamps[modelpose] + const DecalGroupEntry *texinfo; // pointer to decal material + + // history + int depth; // equal for all the decal fragments (used to remove all frgaments) + + // VBO cache + vbomesh_t mesh; // decal private mesh + + // shader cache + vbomesh_t* modelmesh; // pointer to studio mesh who owned decal + shader_t forwardScene; + shader_t forwardLightSpot; + shader_t forwardLightOmni; + shader_t forwardLightProj; +} studiodecal_t; + +#endif//GL_STUDIODECAL_H \ No newline at end of file diff --git a/cl_dll/render/gl_studiodecal_new.cpp b/cl_dll/render/gl_studiodecal_new.cpp new file mode 100644 index 0000000..285b745 --- /dev/null +++ b/cl_dll/render/gl_studiodecal_new.cpp @@ -0,0 +1,1781 @@ +/* +gl_studiodecal_new.cpp - project and rendering studio decals +Copyright (C) 2018 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include "cl_util.h" +#include "gl_local.h" +#include "com_model.h" +#include "r_studioint.h" +#include "pm_movevars.h" +#include "gl_studio.h" +#include "gl_sprite.h" +#include "event_api.h" +#include +#include "pm_defs.h" +#include "stringlib.h" +#include "triangleapi.h" +#include "entity_types.h" +#include "gl_shader.h" +#include "gl_world.h" + +/* +============================================================= + + COMMON ROUTINES + +============================================================= +*/ +/* +==================== +PushEntityState + +==================== +*/ +void CStudioModelRenderer :: PushEntityState( cl_entity_t *ent ) +{ + ASSERT( ent != NULL ); + + m_savestate = ent->curstate; + m_saveorigin = ent->origin; + m_saveangles = ent->angles; +} + +/* +==================== +PopEntityState + +==================== +*/ +void CStudioModelRenderer :: PopEntityState( cl_entity_t *ent ) +{ + ent->curstate = m_savestate; + ent->origin = m_saveorigin; + ent->angles = m_saveangles; +} + +/* +==================== +EntityToModelState + +create snapshot of current model state +==================== +*/ +void CStudioModelRenderer :: EntityToModelState( modelstate_t *state, const cl_entity_t *ent ) +{ + state->sequence = ent->curstate.sequence; + state->frame = (int)( ent->curstate.frame * 8.0f ); + state->blending[0] = ent->curstate.blending[0]; + state->blending[1] = ent->curstate.blending[1]; + state->controller[0] = ent->curstate.controller[0]; + state->controller[1] = ent->curstate.controller[1]; + state->controller[2] = ent->curstate.controller[2]; + state->controller[3] = ent->curstate.controller[3]; + state->body = ent->curstate.body; + state->skin = ent->curstate.skin; +} + +/* +==================== +ModelStateToEntity + +setup entity pose for decal shooting +==================== +*/ +void CStudioModelRenderer :: ModelStateToEntity( cl_entity_t *ent, const modelstate_t *state ) +{ + ent->curstate.sequence = state->sequence; + ent->curstate.frame = (float)state->frame * (1.0f / 8.0f); + ent->curstate.blending[0] = state->blending[0]; + ent->curstate.blending[1] = state->blending[1]; + ent->curstate.controller[0] = state->controller[0]; + ent->curstate.controller[1] = state->controller[1]; + ent->curstate.controller[2] = state->controller[2]; + ent->curstate.controller[3] = state->controller[3]; + ent->curstate.body = state->body; + ent->curstate.skin = state->skin; +} + +/* +============================================================= + + CLEANUP ROUTINES + +============================================================= +*/ +void CStudioModelRenderer :: PurgeDecals( ModelInstance_t *inst ) +{ + for( int i = 0; i < inst->m_DecalList.Count(); i++ ) + { + studiodecal_t *pDecal = &inst->m_DecalList[i]; + DeleteVBOMesh( &pDecal->mesh ); + } + + // release himself + inst->m_DecalList.Purge(); +} + +void CStudioModelRenderer :: PurgeDecals( cl_entity_t *e ) +{ + if( !e || e->modelhandle == INVALID_HANDLE ) + return; + + PurgeDecals( &m_ModelInstances[e->modelhandle] ); +} + +void CStudioModelRenderer :: StudioClearDecals( void ) +{ + if( !g_fRenderInitialized ) return; + + for( int i = 0; i < m_ModelInstances.Count(); i++ ) + PurgeDecals( &m_ModelInstances[i] ); + + // clear BSP-decals too + ClearDecals(); +} + +//----------------------------------------------------------------------------- +// Purpose: Removes all the decals on a model instance +//----------------------------------------------------------------------------- +void CStudioModelRenderer :: RemoveAllDecals( int entityIndex ) +{ + if( !g_fRenderInitialized ) return; + + PurgeDecals( GET_ENTITY( entityIndex )); +} + +/* +============================================================= + + SERIALIZE + +============================================================= +*/ +int CStudioModelRenderer :: StudioDecalList( decallist_t *pBaseList, int count ) +{ + if( !g_fRenderInitialized ) + return 0; + + int maxStudioDecals = MAX_STUDIO_DECALS + (MAX_BMODEL_DECALS - count); + decallist_t *pList = pBaseList + count; // shift list to first free slot + cl_entity_t *pEntity = NULL; + int total = 0; + + for( int i = 0; i < m_ModelInstances.Count(); i++ ) + { + ModelInstance_t *inst = &m_ModelInstances[i]; + + if( !inst->m_DecalList.Count( )) + continue; // no decals? + + // setup the decal entity + pEntity = inst->m_pEntity; + + for( int i = 0; i < inst->m_DecalList.Count(); i++ ) + { + studiodecal_t *pdecal = &inst->m_DecalList[i]; + const DecalGroupEntry *tex = pdecal->texinfo; + + if( !tex || !tex->group || FBitSet( pdecal->flags, FDECAL_DONTSAVE )) + continue; // ??? + + pList[total].depth = pdecal->depth; // FIXME: calc depth properly + pList[total].flags = pdecal->flags|FDECAL_STUDIO; + pList[total].entityIndex = pEntity->index; + pList[total].position = pdecal->position; + pList[total].impactPlaneNormal = pdecal->normal; + pList[total].studio_state = inst->pose_stamps[pdecal->modelpose]; + Q_snprintf( pList[total].name, sizeof( pList[0].name ), "%s@%s", tex->group->GetName(), tex->m_DecalName ); + pList[total].scale = 1.0f; // unused + total++; + + // check for list overflow + if( total >= maxStudioDecals ) + { + ALERT( at_error, "StudioDecalList: too many studio decals on save\restore\n" ); + goto end_serialize; + } + } + } +end_serialize: + total += SaveDecalList( pBaseList, count + total ); + + return total; +} + +/* +============================================================= + + DECAL CLIPPING & PLACING + +============================================================= +*/ +/* +==================== +ComputePoseToDecal + +Create a transform that projects world coordinates +into a basis for the decal +==================== +*/ +bool CStudioModelRenderer :: ComputePoseToDecal( const Vector &vecNormal, const Vector &vecEnd ) +{ + Vector decalU, decalV, decalN, vecDelta; + Vector vecSrc = vecEnd + vecNormal.Normalize() * 5.0f; // magic Valve constant + matrix3x4 worldToDecal; + + // Bloat a little bit so we get the intersection + vecDelta = (vecEnd - vecSrc) * 1.1f; + + // Get the z axis + decalN = vecDelta * -1.0f; + if( decalN.NormalizeLength() == 0.0f ) + return false; + + // Deal with the u axis + decalU = CrossProduct( Vector( 0.0f, 0.0f, 1.0f ), decalN ); + + if( decalU.NormalizeLength() < 1e-3 ) + { + // if up parallel or antiparallel to ray, deal... + Vector fixup( 0.0f, 1.0f, 0.0f ); + decalU = CrossProduct( fixup, decalN ); + + if( decalU.NormalizeLength() < 1e-3 ) + return false; + } + + decalV = CrossProduct( decalN, decalU ); + + // Since I want world-to-decal, I gotta take the inverse of the decal + // to world. Assuming post-multiplying column vectors, the decal to world = + // [ Ux Vx Nx | vecEnd[0] ] + // [ Uy Vy Ny | vecEnd[1] ] + // [ Uz Vz Nz | vecEnd[2] ] + + worldToDecal[0][0] = decalU.x; + worldToDecal[1][0] = decalU.y; + worldToDecal[2][0] = decalU.z; + + worldToDecal[0][1] = decalV.x; + worldToDecal[1][1] = decalV.y; + worldToDecal[2][1] = decalV.z; + + worldToDecal[0][2] = decalN.x; + worldToDecal[1][2] = decalN.y; + worldToDecal[2][2] = decalN.z; + + // g-cont. just invert matrix here? + worldToDecal[3][0] = -DotProduct( vecEnd, decalU ); + worldToDecal[3][1] = -DotProduct( vecEnd, decalV ); + worldToDecal[3][2] = -DotProduct( vecEnd, decalN ); + + // Compute transforms from pose space to decal plane space + for( int i = 0; i < m_pStudioHeader->numbones; i++ ) + { + m_pdecaltransform[i] = worldToDecal.ConcatTransforms( m_pworldtransform[i] ); + } + + return true; +} + +/* +==================== +IsFrontFacing + +side checking +==================== +*/ +_forceinline bool CStudioModelRenderer :: IsFrontFacing( const svert_t *vert ) +{ + float z = 0.0f; + Vector decalN; + + // NOTE: This only works to rotate normals if there's no scale in the + // pose to world transforms. If we ever add scale, we'll need to + // multiply by the inverse transpose of the pose to decal + if( vert->weight[0] == 255 ) + { + decalN.x = m_pdecaltransform[vert->boneid[0]][0][2]; + decalN.y = m_pdecaltransform[vert->boneid[0]][1][2]; + decalN.z = m_pdecaltransform[vert->boneid[0]][2][2]; + z = DotProduct( vert->normal, decalN ); + } + else + { + for( int i = 0; i < MAXSTUDIOBONEWEIGHTS; i++ ) + { + if( vert->boneid[i] == -1 ) + break; + decalN.x = m_pdecaltransform[vert->boneid[i]][0][2]; + decalN.y = m_pdecaltransform[vert->boneid[i]][1][2]; + decalN.z = m_pdecaltransform[vert->boneid[i]][2][2]; + z += DotProduct( vert->normal, decalN ) * (vert->weight[i] / 255.0f); + } + } + + return ( z >= 0.1f ); +} + +/* +==================== +GetDecalRotateTransform + +==================== +*/ +_forceinline matrix3x3 CStudioModelRenderer :: GetDecalRotateTransform( byte vertexBone ) +{ + matrix3x3 transform; + + transform[0][0] = m_pdecaltransform[vertexBone][0][0]; + transform[0][1] = m_pdecaltransform[vertexBone][1][0]; + transform[0][2] = m_pdecaltransform[vertexBone][2][0]; + + transform[1][0] = m_pdecaltransform[vertexBone][0][1]; + transform[1][1] = m_pdecaltransform[vertexBone][1][1]; + transform[1][2] = m_pdecaltransform[vertexBone][2][1]; + + transform[2][0] = m_pdecaltransform[vertexBone][0][2]; + transform[2][1] = m_pdecaltransform[vertexBone][1][2]; + transform[2][2] = m_pdecaltransform[vertexBone][2][2]; + + return transform; +} + +/* +==================== +TransformToDecalSpace + +==================== +*/ +_forceinline bool CStudioModelRenderer :: TransformToDecalSpace( DecalBuildInfo_t& build, const svert_t *vert, Vector2D& uv ) +{ + cl_entity_t *e = RI->currententity; + + // NOTE: This only works to rotate normals if there's no scale in the + // pose to world transforms. If we ever add scale, we'll need to + // multiply by the inverse transpose of the pose to world + float z; + + if( vert->weight[0] == 255 ) + { + matrix3x3 decalMat = GetDecalRotateTransform( vert->boneid[0] ); + uv.x = (DotProduct( vert->vertex, decalMat[0] ) + m_pdecaltransform[vert->boneid[0]][3][0]); + uv.y = -(DotProduct( vert->vertex, decalMat[1] ) + m_pdecaltransform[vert->boneid[0]][3][1]); + z = DotProduct( vert->vertex, decalMat[2] ) + m_pdecaltransform[vert->boneid[0]][3][2]; + } + else + { + uv.x = uv.y = z = 0.0f; + + for( int i = 0; i < MAXSTUDIOBONEWEIGHTS; i++ ) + { + if( vert->boneid[i] == -1 ) + break; + + matrix3x3 decalMat = GetDecalRotateTransform( vert->boneid[i] ); + float weight = (vert->weight[i] / 255.0f); + + uv.x += (DotProduct( vert->vertex, decalMat[0] ) + m_pdecaltransform[vert->boneid[i]][3][0]) * weight; + uv.y += -(DotProduct( vert->vertex, decalMat[1] ) + m_pdecaltransform[vert->boneid[i]][3][1]) * weight; + z += (DotProduct( vert->vertex, decalMat[2] ) + m_pdecaltransform[vert->boneid[i]][3][2]) * weight; + } + } + + // poke thru + if( FBitSet( e->curstate.iuser1, CF_STATIC_ENTITY ) && !e->efrag ) + return ( fabs( z ) < build.m_Radius ); + return true; +} + +/* +==================== +ProjectDecalOntoMesh + +==================== +*/ +void CStudioModelRenderer :: ProjectDecalOntoMesh( DecalBuildInfo_t& build ) +{ + float invRadius = (build.m_Radius != 0.0f) ? 1.0f / build.m_Radius : 1.0f; + + // for this to work, the plane and intercept must have been transformed + // into pose space. Also, we'll not be bothering with flexes. + for( int j = 0; j < m_nNumArrayVerts; j++ ) + { + // just walk by unique verts + svert_t *vert = &m_arrayxvert[j]; + + // No decal vertex yet... + build.m_pVertexInfo[j].m_VertexIndex = INVALID_HANDLE; + + // We need to know if the normal is pointing in the negative direction + // if so, blow off all triangles connected to that vertex. + build.m_pVertexInfo[j].m_FrontFacing = IsFrontFacing( vert ); + + if( !build.m_pVertexInfo[j].m_FrontFacing ) + continue; + + build.m_pVertexInfo[j].m_InValidArea = TransformToDecalSpace( build, vert, build.m_pVertexInfo[j].m_UV ); + build.m_pVertexInfo[j].m_UV *= build.vecDecalScale * 0.5f; + build.m_pVertexInfo[j].m_UV[0] += 0.5f; + build.m_pVertexInfo[j].m_UV[1] += 0.5f; + } +} + +/* +==================== +ComputeClipFlags + +==================== +*/ +int CStudioModelRenderer :: ComputeClipFlags( const Vector2D &uv ) +{ + // Otherwise we gotta do the test + int flags = 0; + + if( uv.x < 0.0f ) + SetBits( flags, DECAL_CLIP_MINUSU ); + else if( uv.x > 1.0f ) + SetBits( flags, DECAL_CLIP_PLUSU ); + + if( uv.y < 0.0f ) + SetBits( flags, DECAL_CLIP_MINUSV ); + else if( uv.y > 1.0f ) + SetBits( flags, DECAL_CLIP_PLUSV ); + + return flags; +} + +/* +==================== +ConvertMeshVertexToDecalVertex + +Converts a mesh index to a svert_t +==================== +*/ +void CStudioModelRenderer :: ConvertMeshVertexToDecalVertex( DecalBuildInfo_t& build, int vertIndex, svert_t *out ) +{ + // copy over the data (we use through access for all submodel verts) + *out = m_arrayxvert[vertIndex]; + + // get the texture coords from the decal planar projection + out->stcoord[0] = build.m_pVertexInfo[vertIndex].m_UV.x; + out->stcoord[1] = build.m_pVertexInfo[vertIndex].m_UV.y; + + // if meshIndex is valid this vertex was created from original + out->m_MeshVertexIndex = vertIndex; +} + +/* +==================== +AddVertexToDecal + +Adds a vertex to the list of vertices +for this studiomesh +==================== +*/ +word CStudioModelRenderer :: AddVertexToDecal( DecalBuildInfo_t& build, int vertIndex ) +{ + DecalVertexInfo_t* pVertexInfo = build.m_pVertexInfo; + + // If we've never seen this vertex before, we need to add a new decal vert + if( pVertexInfo[vertIndex].m_VertexIndex == INVALID_HANDLE ) + { + int v = build.m_Vertices.AddToTail(); + + // Copy over the data; + ConvertMeshVertexToDecalVertex( build, vertIndex, &build.m_Vertices[v] ); +#ifdef _DEBUG + // Make sure clipped vertices are in the right range... + if( build.m_UseClipVert ) + { + ASSERT(( build.m_Vertices[v].stcoord[0] >= -1e-3 ) && ( build.m_Vertices[v].stcoord[0] - 1.0f < 1e-3 )); + ASSERT(( build.m_Vertices[v].stcoord[1] >= -1e-3 ) && ( build.m_Vertices[v].stcoord[1] - 1.0f < 1e-3 )); + } +#endif + // store off the index of this vertex so we can reference it again + pVertexInfo[vertIndex].m_VertexIndex = v; + } + + return pVertexInfo[vertIndex].m_VertexIndex; +} + +/* +==================== +AddVertexToDecal + +This creates a unique vertex +==================== +*/ +word CStudioModelRenderer :: AddVertexToDecal( DecalBuildInfo_t& build, svert_t *vert ) +{ + // Try to see if the clipped vertex already exists in our decal list... + // Only search for matches with verts appearing in the current decal + for( int i = 0; i < build.m_Vertices.Count(); i++ ) + { + svert_t *test = &build.m_Vertices[i]; + + // Only bother to check against clipped vertices + if( test->m_MeshVertexIndex != INVALID_HANDLE ) + continue; + + if( !VectorCompareEpsilon( test->vertex, vert->vertex, 1e-3 )) + continue; + + if( !VectorCompareEpsilon( test->normal, vert->normal, 1e-3 )) + continue; + + return i; + } + + // this path is the path taken by clipped vertices + ASSERT(( vert->stcoord[0] >= -1e-3 ) && ( vert->stcoord[0] - 1.0f < 1e-3 )); + ASSERT(( vert->stcoord[1] >= -1e-3 ) && ( vert->stcoord[1] - 1.0f < 1e-3 )); + + // must create a new vertex... + return build.m_Vertices.AddToTail( *vert ); +} + +//----------------------------------------------------------------------------- +// Creates a new vertex where the edge intersects the plane +//----------------------------------------------------------------------------- +int CStudioModelRenderer :: IntersectPlane( DecalClipState_t& state, int start, int end, int normalInd, float val ) +{ + svert_t *startVert = &state.m_ClipVerts[start]; + svert_t *endVert = &state.m_ClipVerts[end]; + + Vector2D dir = Vector2D ( endVert->stcoord[0] - startVert->stcoord[0], endVert->stcoord[1] - startVert->stcoord[1] ); + ASSERT( dir[normalInd] != 0.0f ); + + float t = (val - startVert->stcoord[normalInd]) / dir[normalInd]; + + // Allocate a clipped vertex + svert_t *out = &state.m_ClipVerts[state.m_ClipVertCount]; + int newVert = state.m_ClipVertCount++; + + // the clipped vertex has no analogue in the original mesh + out->m_MeshVertexIndex = INVALID_HANDLE; + + // just select bone by interpolation factor + if( t <= 0.5f ) + { + memcpy( out->boneid, startVert->boneid, 4 ); + memcpy( out->weight, startVert->weight, 4 ); + } + else + { + memcpy( out->boneid, endVert->boneid, 4 ); + memcpy( out->weight, endVert->weight, 4 ); + } + + // interpolate position + out->vertex = startVert->vertex * (1.0f - t) + endVert->vertex * t; + + // interpolate tangent + out->tangent = startVert->tangent * (1.0f - t) + endVert->tangent * t; + out->tangent = out->tangent.Normalize(); + + // interpolate binormal + out->binormal = startVert->binormal * (1.0f - t) + endVert->binormal * t; + out->binormal = out->binormal.Normalize(); + + // interpolate normal + out->normal = startVert->normal * (1.0f - t) + endVert->normal * t; + out->normal = out->normal.Normalize(); + + // interpolate texture coord + out->stcoord[0] = startVert->stcoord[0] + (endVert->stcoord[0] - startVert->stcoord[0]) * t; + out->stcoord[1] = startVert->stcoord[1] + (endVert->stcoord[1] - startVert->stcoord[1]) * t; + out->stcoord[2] = startVert->stcoord[2] + (endVert->stcoord[2] - startVert->stcoord[2]) * t; + out->stcoord[3] = startVert->stcoord[3] + (endVert->stcoord[3] - startVert->stcoord[3]) * t; + + ASSERT( !IS_NAN( out->stcoord[0] ) && !IS_NAN( out->stcoord[1] )); + + // interpolate lighting (we need unpack, interpolate and pack again) + for( int map = 0; map < MAXLIGHTMAPS; map++ ) + { + color24 startCol = UnpackColor( startVert->light[map] ); + color24 startNrm = UnpackColor( startVert->deluxe[map] ); + color24 endCol = UnpackColor( endVert->light[map] ); + color24 endNrm = UnpackColor( endVert->deluxe[map] ); + out->light[map] = PackColor( startCol * (1.0f - t) + endCol * t ); + out->deluxe[map] = PackColor( startNrm * (1.0f - t) + endNrm * t ); + } + + // Compute the clip flags baby... + state.m_ClipFlags[newVert] = ComputeClipFlags( Vector2D( out->stcoord )); + + return newVert; +} + +/* +==================== +ClipTriangleAgainstPlane + +Clips a triangle against a plane, +use clip flags to speed it up +==================== +*/ +void CStudioModelRenderer :: ClipTriangleAgainstPlane( DecalClipState_t& state, int normalInd, int flag, float val ) +{ + // Ye Olde Sutherland-Hodgman clipping algorithm + int start = state.m_Indices[state.m_Pass][state.m_VertCount - 1]; + bool startInside = FBitSet( state.m_ClipFlags[start], flag ) == 0; + int outVertCount = 0; + + for( int i = 0; i < state.m_VertCount; i++ ) + { + int end = state.m_Indices[state.m_Pass][i]; + + bool endInside = (state.m_ClipFlags[end] & flag) == 0; + + if( endInside ) + { + if( !startInside ) + { + int clipVert = IntersectPlane( state, start, end, normalInd, val ); + state.m_Indices[!state.m_Pass][outVertCount++] = clipVert; + } + state.m_Indices[!state.m_Pass][outVertCount++] = end; + } + else + { + if( startInside ) + { + int clipVert = IntersectPlane( state, start, end, normalInd, val ); + state.m_Indices[!state.m_Pass][outVertCount++] = clipVert; + } + } + + start = end; + startInside = endInside; + } + + state.m_Pass = !state.m_Pass; + state.m_VertCount = outVertCount; +} + +/* +==================== +AddClippedDecalToTriangle + +Adds the clipped triangle to the decal +==================== +*/ +void CStudioModelRenderer :: AddClippedDecalToTriangle( DecalBuildInfo_t& build, DecalClipState_t& clipState ) +{ + word indices[7]; + int i; + + ASSERT( clipState.m_VertCount <= 7 ); + + // Yeah baby yeah!! Add this sucka + for( i = 0; i < clipState.m_VertCount; i++ ) + { + // First add the vertices + int vertIdx = clipState.m_Indices[clipState.m_Pass][i]; + + if( vertIdx < 3 ) + { + indices[i] = AddVertexToDecal( build, clipState.m_ClipVerts[vertIdx].m_MeshVertexIndex ); + } + else + { + indices[i] = AddVertexToDecal( build, &clipState.m_ClipVerts[vertIdx] ); + } + } + + // Add a trifan worth of triangles + for( i = 1; i < clipState.m_VertCount - 1; i++ ) + { + build.m_Indices.AddToTail( indices[0] ); + build.m_Indices.AddToTail( indices[i] ); + build.m_Indices.AddToTail( indices[i+1] ); + } +} + +/* +==================== +ClipDecal + +Clips the triangle to +/- radius +==================== +*/ +bool CStudioModelRenderer :: ClipDecal( DecalBuildInfo_t& build, int i1, int i2, int i3, int *pClipFlags ) +{ + DecalClipState_t clipState; + clipState.m_VertCount = 3; + + ConvertMeshVertexToDecalVertex( build, i1, &clipState.m_ClipVerts[0] ); + ConvertMeshVertexToDecalVertex( build, i2, &clipState.m_ClipVerts[1] ); + ConvertMeshVertexToDecalVertex( build, i3, &clipState.m_ClipVerts[2] ); + + clipState.m_ClipVertCount = 3; + + for( int i = 0; i < 3; i++ ) + { + clipState.m_ClipFlags[i] = pClipFlags[i]; + clipState.m_Indices[0][i] = i; + } + + clipState.m_Pass = 0; + + // Clip against each plane + ClipTriangleAgainstPlane( clipState, 0, DECAL_CLIP_MINUSU, 0.0f ); + if( clipState.m_VertCount < 3 ) + return false; + + ClipTriangleAgainstPlane( clipState, 0, DECAL_CLIP_PLUSU, 1.0f ); + if( clipState.m_VertCount < 3 ) + return false; + + ClipTriangleAgainstPlane( clipState, 1, DECAL_CLIP_MINUSV, 0.0f ); + if( clipState.m_VertCount < 3 ) + return false; + + ClipTriangleAgainstPlane( clipState, 1, DECAL_CLIP_PLUSV, 1.0f ); + if( clipState.m_VertCount < 3 ) + return false; + + // Only add the clipped decal to the triangle if it's one bone + // otherwise just return if it was clipped + if( build.m_UseClipVert ) + { + AddClippedDecalToTriangle( build, clipState ); + } + + return true; +} + +/* +==================== +AddTriangleToDecal + +Adds a decal to a triangle, +but only if it should +==================== +*/ +void CStudioModelRenderer :: AddTriangleToDecal( DecalBuildInfo_t& build, int i1, int i2, int i3 ) +{ + DecalVertexInfo_t* pVertexInfo = build.m_pVertexInfo; + + // All must be front-facing for a decal to be added + if(( !pVertexInfo[i1].m_FrontFacing ) || ( !pVertexInfo[i2].m_FrontFacing ) || ( !pVertexInfo[i3].m_FrontFacing )) + return; + + // This is used to prevent poke through; if the points are too far away + // from the contact point, then don't add the decal + if(( !pVertexInfo[i1].m_InValidArea ) && ( !pVertexInfo[i2].m_InValidArea ) && ( !pVertexInfo[i3].m_InValidArea )) + return; + + // Clip to +/- radius + int clipFlags[3]; + + clipFlags[0] = ComputeClipFlags( pVertexInfo[i1].m_UV ); + clipFlags[1] = ComputeClipFlags( pVertexInfo[i2].m_UV ); + clipFlags[2] = ComputeClipFlags( pVertexInfo[i3].m_UV ); + + // Cull... The result is non-zero if they're all outside the same plane + if(( clipFlags[0] & ( clipFlags[1] & clipFlags[2] )) != 0 ) + return; + + bool doClip = true; + + // Trivial accept for skinned polys... if even one vert is inside the draw region, accept + if(( !build.m_UseClipVert ) && ( !clipFlags[0] || !clipFlags[1] || !clipFlags[2] )) + doClip = false; + + // Trivial accept... no clip flags set means all in + // Don't clip if we have more than one bone... we'll need to do skinning + // and we can't clip the bone indices + // We *do* want to clip in the one bone case though; useful for static props. + if( doClip && ( clipFlags[0] || clipFlags[1] || clipFlags[2] )) + { + bool validTri = ClipDecal( build, i1, i2, i3, clipFlags ); + + // Don't add the triangle if we culled the triangle or if + // we had one or less bones + if( build.m_UseClipVert || ( !validTri )) + return; + } + + // Add the vertices to the decal since there was no clipping + build.m_Indices.AddToTail( AddVertexToDecal( build, i1 )); + build.m_Indices.AddToTail( AddVertexToDecal( build, i2 )); + build.m_Indices.AddToTail( AddVertexToDecal( build, i3 )); +} + +word CStudioModelRenderer :: ShaderDecalForward( studiodecal_t *pDecal, bool vertex_lighting ) +{ + char glname[64]; + char options[MAX_OPTIONS_LENGTH]; + + if( pDecal->forwardScene.IsValid( )) + return pDecal->forwardScene.GetHandle(); + + Q_strncpy( glname, "forward/decal_studio", sizeof( glname )); + memset( options, 0, sizeof( options )); + + mstudiomaterial_t *mat = (mstudiomaterial_t *)RI->currentmodel->materials; + const DecalGroupEntry *texinfo = pDecal->texinfo; + vbomesh_t *pMesh = pDecal->modelmesh; + + short *pskinref = (short *)((byte *)m_pStudioHeader + m_pStudioHeader->skinindex); + mat = &mat[pskinref[pMesh->skinref]]; + + if( tr.fogEnabled ) + GL_AddShaderDirective( options, "APPLY_FOG_EXP" ); + + if( !texinfo->opaque ) + { + // GL_DST_COLOR, GL_SRC_COLOR + GL_AddShaderDirective( options, "APPLY_COLORBLEND" ); + } + else + { + if( texinfo->gl_heightmap_id != tr.whiteTexture && texinfo->matdesc->reliefScale > 0.0f ) + { + if( cv_parallax->value == 1.0f ) + GL_AddShaderDirective( options, "PARALLAX_SIMPLE" ); + else if( cv_parallax->value >= 2.0f ) + GL_AddShaderDirective( options, "PARALLAX_OCCLUSION" ); + } + } + + if( !texinfo->opaque || FBitSet( mat->flags, STUDIO_NF_FULLBRIGHT ) || R_FullBright( )) + { + GL_AddShaderDirective( options, "LIGHTING_FULLBRIGHT" ); + } + else + { + if( CVAR_TO_BOOL( cv_brdf )) + GL_AddShaderDirective( options, "APPLY_PBS" ); + + if( vertex_lighting ) + GL_AddShaderDirective( options, "VERTEX_LIGHTING" ); + else if( FBitSet( mat->flags, STUDIO_NF_FLATSHADE )) + GL_AddShaderDirective( options, "LIGHTING_FLATSHADE" ); + + // debug visualization + if( r_lightmap->value > 0.0f && r_lightmap->value <= 2.0f ) + { + if( r_lightmap->value == 1.0f && worldmodel->lightdata ) + GL_AddShaderDirective( options, "LIGHTMAP_DEBUG" ); + else if( r_lightmap->value == 2.0f && FBitSet( world->features, WORLD_HAS_DELUXEMAP )) + GL_AddShaderDirective( options, "LIGHTVEC_DEBUG" ); + } + + if( texinfo->gl_heightmap_id != tr.whiteTexture || texinfo->gl_normalmap_id != tr.normalmapTexture ) + GL_AddShaderDirective( options, "COMPUTE_TBN" ); + + // deluxemap required + if( CVAR_TO_BOOL( cv_bump ) && FBitSet( world->features, WORLD_HAS_DELUXEMAP ) && texinfo->gl_normalmap_id != tr.normalmapTexture ) + { + GL_AddShaderDirective( options, "HAS_NORMALMAP" ); + GL_EncodeNormal( options, mat->gl_normalmap_id ); + } + + // deluxemap required + if( CVAR_TO_BOOL( cv_specular ) && FBitSet( world->features, WORLD_HAS_DELUXEMAP ) && texinfo->gl_specular_id != tr.blackTexture ) + GL_AddShaderDirective( options, "HAS_GLOSSMAP" ); + } + + if( m_pStudioHeader->numbones == 1 ) + GL_AddShaderDirective( options, "MAXSTUDIOBONES 1" ); + + if( FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS )) + GL_AddShaderDirective( options, "APPLY_BONE_WEIGHTING" ); + + if( FBitSet( mat->flags, STUDIO_NF_MASKED )) + GL_AddShaderDirective( options, "ALPHA_TEST" ); + + word shaderNum = GL_FindUberShader( glname, options ); + if( !shaderNum ) return 0; + + // done + pDecal->forwardScene.SetShader( shaderNum ); + return shaderNum; +} + +void CStudioModelRenderer :: ComputeDecalTBN( DecalBuildInfo_t& build ) +{ + if( build.m_pTexInfo->gl_normalmap_id == tr.normalmapTexture && build.m_pTexInfo->gl_heightmap_id == tr.whiteTexture ) + return; // not needs + + // build a map from vertex to a list of triangles that share the vert. + CUtlArray vertToTriMap; + + vertToTriMap.AddMultipleToTail( build.m_Vertices.Count() ); + int triID; + + for( triID = 0; triID < (build.m_Indices.Count() / 3); triID++ ) + { + vertToTriMap[build.m_Indices[triID*3+0]].AddToTail( triID ); + vertToTriMap[build.m_Indices[triID*3+1]].AddToTail( triID ); + vertToTriMap[build.m_Indices[triID*3+2]].AddToTail( triID ); + } + + // calculate the tangent space for each triangle. + CUtlArray triSVect, triTVect; + triSVect.AddMultipleToTail( (build.m_Indices.Count() / 3) ); + triTVect.AddMultipleToTail( (build.m_Indices.Count() / 3) ); + + float *v[3], *tc[3]; + + for( triID = 0; triID < (build.m_Indices.Count() / 3); triID++ ) + { + for( int i = 0; i < 3; i++ ) + { + v[i] = (float *)&build.m_Vertices[build.m_Indices[triID*3+i]].vertex; + tc[i] = (float *)&build.m_Vertices[build.m_Indices[triID*3+i]].stcoord; + } + + CalcTBN( v[0], v[1], v[2], tc[0], tc[1], tc[2], triSVect[triID], triTVect[triID] ); + } + + // calculate an average tangent space for each vertex. + for( int vertID = 0; vertID < build.m_Vertices.Count(); vertID++ ) + { + svert_t *v = &build.m_Vertices[vertID]; + const Vector &normal = v->normal; + Vector &finalSVect = v->tangent; + Vector &finalTVect = v->binormal; + Vector sVect, tVect; + + sVect = tVect = g_vecZero; + + for( triID = 0; triID < vertToTriMap[vertID].Size(); triID++ ) + { + sVect += triSVect[vertToTriMap[vertID][triID]]; + tVect += triTVect[vertToTriMap[vertID][triID]]; + } + + Vector tmpVect = CrossProduct( sVect, tVect ); + bool leftHanded = DotProduct( tmpVect, normal ) < 0.0f; + + if( !leftHanded ) + { + tVect = CrossProduct( normal, sVect ); + sVect = CrossProduct( tVect, normal ); + } + else + { + tVect = CrossProduct( sVect, normal ); + sVect = CrossProduct( normal, tVect ); + } + + finalSVect = sVect.Normalize(); + finalTVect = tVect.Normalize(); + } +} + +void CStudioModelRenderer :: DecalCreateBuffer( DecalBuildInfo_t& build, studiodecal_t *pDecal ) +{ + vbomesh_t *pMesh = pDecal->modelmesh; + mstudiomaterial_t *pmaterial = (mstudiomaterial_t *)RI->currentmodel->materials; + short *pskinref = (short *)((byte *)m_pStudioHeader + m_pStudioHeader->skinindex); + pmaterial = &pmaterial[pskinref[pMesh->skinref]]; + + bool has_boneweights = ( RI->currentmodel->poseToBone != NULL ) ? true : false; + bool has_vertexlight = ( build.m_pVertexLight ) ? true : false; + bool has_lightmap = false; + bool has_bumpmap = false; + + if( pDecal->texinfo->gl_normalmap_id != tr.normalmapTexture || pDecal->texinfo->gl_heightmap_id != tr.whiteTexture ) + has_bumpmap = true; + + ShaderDecalForward( pDecal, has_vertexlight ); + + vbomesh_t *pOut = &pDecal->mesh; + + pOut->numVerts = build.m_Vertices.Count(); + pOut->numElems = build.m_Indices.Count(); + + GL_CheckVertexArrayBinding(); + + // determine optimal mesh loader + uint attribs = ComputeAttribFlags( m_pStudioHeader->numbones, has_bumpmap, has_boneweights, has_vertexlight, has_lightmap ); + uint type = SelectMeshLoader( m_pStudioHeader->numbones, has_bumpmap, has_boneweights, has_vertexlight, has_lightmap ); + + // move data to video memory + if( glConfig.version < ACTUAL_GL_VERSION ) + m_pfnMeshLoaderGL21[type].CreateBuffer( pOut, build.m_Vertices.Base() ); + else m_pfnMeshLoaderGL30[type].CreateBuffer( pOut, build.m_Vertices.Base() ); + CreateIndexBuffer( pOut, build.m_Indices.Base() ); + + // link it with vertex array object + pglGenVertexArrays( 1, &pOut->vao ); + pglBindVertexArray( pOut->vao ); + if( glConfig.version < ACTUAL_GL_VERSION ) + m_pfnMeshLoaderGL21[type].BindBuffer( pOut, attribs ); + else m_pfnMeshLoaderGL30[type].BindBuffer( pOut, attribs ); + BindIndexBuffer( pOut ); + pglBindVertexArray( GL_FALSE ); + + // update stats + tr.total_vbo_memory += pOut->cacheSize; +} + +void CStudioModelRenderer :: AllocDecalForMesh( DecalBuildInfo_t& build ) +{ + if( build.m_Vertices.Count() <= 0 || build.m_Indices.Count() <= 0 ) + return; // something went wrong + + int index = m_pModelInstance->m_DecalList.AddToTail(); + studiodecal_t *pDecal = &m_pModelInstance->m_DecalList[index]; + + memset( pDecal, 0, sizeof( studiodecal_t )); + + // copy settings + pDecal->normal = build.vecLocalNormal; + pDecal->position = build.vecLocalEnd; + pDecal->flags = build.decalFlags; + + if( !build.current ) + { + // move modelstate into static memory + build.poseState = m_pModelInstance->pose_stamps.AddToTail( *build.modelState ); + build.current = pDecal; // first decal is saved, next ignored + } + else SetBits( pDecal->flags, FDECAL_DONTSAVE ); + + pDecal->modelmesh = build.m_pModelMesh; + pDecal->modelpose = build.poseState; + pDecal->texinfo = build.m_pTexInfo; + pDecal->depth = build.decalDepth; + + // calc TBN if needs + ComputeDecalTBN( build ); + + // place vertices into VBO + DecalCreateBuffer( build, pDecal ); +} + +void CStudioModelRenderer :: AddDecalToMesh( DecalBuildInfo_t& build ) +{ + const vbomesh_t *mesh = build.m_pModelMesh; + Vector absmin, absmax; + + // setup mesh bounds + TransformAABB( m_pModelInstance->m_pbones[mesh->parentbone], mesh->mins, mesh->maxs, absmin, absmax ); + + if( !BoundsAndSphereIntersect( absmin, absmax, build.vecLocalEnd, build.m_Radius )) + return; // no intersection + + build.m_Vertices.Purge(); + build.m_Indices.Purge(); + + for( int j = 0; j < build.m_pDecalMesh->numindices; j += 3 ) + { + int starttri = build.m_pDecalMesh->firstindex + j; + AddTriangleToDecal( build, m_arrayelems[starttri+0], m_arrayelems[starttri+1], m_arrayelems[starttri+2] ); + } + + // allocate decal for mesh + AllocDecalForMesh( build ); +} + +void CStudioModelRenderer :: AddDecalToModel( DecalBuildInfo_t& buildInfo ) +{ + Vector *pstudioverts = (Vector *)((byte *)m_pStudioHeader + m_pSubModel->vertindex); + Vector *pstudionorms = (Vector *)((byte *)m_pStudioHeader + m_pSubModel->normindex); + byte *pvertbone = ((byte *)m_pStudioHeader + m_pSubModel->vertinfoindex); + short *pskinref; + int i, numVerts; + + // if weights was missed their offsets just equal to 0 + mstudioboneweight_t *pvertweight = (mstudioboneweight_t *)((byte *)m_pStudioHeader + m_pSubModel->blendvertinfoindex); + StudioMesh_t *pDecalMesh = (StudioMesh_t *)stackalloc( m_pSubModel->nummesh * sizeof( StudioMesh_t )); + pskinref = (short *)((byte *)m_pStudioHeader + m_pStudioHeader->skinindex); + if( !pDecalMesh ) return; // empty mesh? + + buildInfo.m_pVertexLight = NULL; + + if( buildInfo.modelLight != NULL ) + { + int offset = (byte *)m_pSubModel - (byte *)m_pStudioHeader; // search for submodel offset + + for( i = 0; i < MAXSTUDIOMODELS; i++ ) + { + if( buildInfo.modelLight->submodels[i].submodel_offset == offset ) + break; + } + + // has vertexlighting for this submodel + if( i != MAXSTUDIOMODELS ) + buildInfo.m_pVertexLight = &buildInfo.modelLight->verts[buildInfo.modelLight->submodels[i].vertex_offset]; + } + + m_nNumArrayVerts = m_nNumArrayElems = 0; + m_nNumLightVerts = 0; + + // build all the data for current submodel + for( i = 0; i < m_pSubModel->nummesh; i++ ) + { + mstudiomesh_t *pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex) + i; + mstudiomaterial_t *pmaterial = &RI->currentmodel->materials[pskinref[pmesh->skinref]]; + short *ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex); + StudioMesh_t *pCurMesh = pDecalMesh + i; + + pCurMesh->firstvertex = m_nNumArrayVerts; + pCurMesh->firstindex = m_nNumArrayElems; + pCurMesh->numvertices = 0; + pCurMesh->numindices = 0; + + mstudiotexture_t *ptexture = pmaterial->pSource; + float s = 1.0f / (float)ptexture->width; + float t = 1.0f / (float)ptexture->height; + + while( numVerts = *( ptricmds++ )) + { + bool strip = ( numVerts < 0 ) ? false : true; + int vertexState = 0; + + numVerts = abs( numVerts ); + + for( ; numVerts > 0; numVerts--, ptricmds += 4 ) + { + // build in indices + if( vertexState++ < 3 ) + { + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts; + } + else if( strip ) + { + // flip triangles between clockwise and counter clockwise + if( vertexState & 1 ) + { + // draw triangle [n-2 n-1 n] + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - 2; + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - 1; + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts; + } + else + { + // draw triangle [n-1 n-2 n] + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - 1; + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - 2; + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts; + } + } + else + { + // draw triangle fan [0 n-1 n] + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - ( vertexState - 1 ); + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - 1; + m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts; + } + + // store mesh into uniform vertex for consistency + svert_t *out = &m_arrayxvert[m_nNumArrayVerts]; + + out->vertex = pstudioverts[ptricmds[0]]; + out->normal = pstudionorms[ptricmds[1]].NormalizeFast(); + out->binormal = g_vecZero; + out->tangent = g_vecZero; + out->stcoord[0] = 0.0f; + out->stcoord[1] = 0.0f; + + if( RI->currentmodel->poseToBone != NULL ) + { + mstudioboneweight_t *pCurWeight = &pvertweight[ptricmds[0]]; + + out->boneid[0] = pCurWeight->bone[0]; + out->boneid[1] = pCurWeight->bone[1]; + out->boneid[2] = pCurWeight->bone[2]; + out->boneid[3] = pCurWeight->bone[3]; + out->weight[0] = pCurWeight->weight[0]; + out->weight[1] = pCurWeight->weight[1]; + out->weight[2] = pCurWeight->weight[2]; + out->weight[3] = pCurWeight->weight[3]; + } + else + { + out->boneid[0] = pvertbone[ptricmds[0]]; + out->boneid[1] = -1; + out->boneid[2] = -1; + out->boneid[3] = -1; + out->weight[0] = 255; + out->weight[1] = 0; + out->weight[2] = 0; + out->weight[3] = 0; + } + + if( FBitSet( ptexture->flags, STUDIO_NF_CHROME )) + { + // probably always equal 64 (see studiomdl.c for details) + out->stcoord[2] = s; + out->stcoord[3] = t; + } + else if( FBitSet( ptexture->flags, STUDIO_NF_UV_COORDS )) + { + out->stcoord[2] = HalfToFloat( ptricmds[2] ); + out->stcoord[3] = HalfToFloat( ptricmds[3] ); + } + else + { + out->stcoord[2] = ptricmds[2] * s; + out->stcoord[3] = ptricmds[3] * t; + } + + // accumulate vertex lighting too + if( buildInfo.m_pVertexLight != NULL ) + { + dvertlight_t *vl = &buildInfo.m_pVertexLight[m_nNumLightVerts++]; + + // now setup light and deluxe vector + for( int map = 0; map < MAXLIGHTMAPS; map++ ) + { + out->light[map] = PackColor( vl->light[map] ); + out->deluxe[map] = PackColor( vl->deluxe[map] ); + } + } + m_nNumArrayVerts++; + } + } + + pCurMesh->numvertices = m_nNumArrayVerts - pCurMesh->firstvertex; + pCurMesh->numindices = m_nNumArrayElems - pCurMesh->firstindex; + } + + // should keep all the verts of this submodel, because we use direct access by vertex number + buildInfo.m_pVertexInfo = (DecalVertexInfo_t *)stackalloc( m_nNumArrayVerts * sizeof( DecalVertexInfo_t )); + + // project all vertices for this group into decal space + // Note we do this work at a mesh level instead of a model level + // because vertices are not shared across mesh boundaries + ProjectDecalOntoMesh( buildInfo ); + + // NOTE: we should add the individual decals for each mesh + // to effectively sorting while renderer translucent meshes + for( i = 0; i < m_pSubModel->nummesh; i++ ) + { + // setup mesh pointers + buildInfo.m_pModelMesh = &m_pVboModel->meshes[i]; + buildInfo.m_pDecalMesh = pDecalMesh + i; + AddDecalToMesh( buildInfo ); + } +} + +/* +==================== +StudioDecalShoot + +simplified version +==================== +*/ +void CStudioModelRenderer :: StudioDecalShoot( struct pmtrace_s *tr, const char *name, bool visent ) +{ + if( !g_fRenderInitialized ) + return; + + if( tr->allsolid || tr->fraction == 1.0 || tr->ent < 0 ) + return; + + physent_t *pe = NULL; + + if( visent ) pe = gEngfuncs.pEventAPI->EV_GetVisent( tr->ent ); + else pe = gEngfuncs.pEventAPI->EV_GetPhysent( tr->ent ); + if( !pe ) return; + + Vector scale = Vector( 1.0f, 1.0f, 1.0f ); + int entityIndex = pe->info; + int flags = FDECAL_STUDIO; + modelstate_t state; + int modelIndex = 0; + + // modelindex is needs for properly save\restore + cl_entity_t *ent = GET_ENTITY( entityIndex ); + + if( !ent ) + { + // something very bad happens... + ALERT( at_error, "StudioDecal: ent == NULL\n" ); + return; + } + + modelIndex = ent->curstate.modelindex; + + // restore model in case decalmessage was delivered early than delta-update + if( !ent->model && modelIndex != 0 ) + ent->model = IEngineStudio.GetModelByIndex( modelIndex ); + + if( !ent->model || ent->model->type != mod_studio ) + return; + + Vector vecNormal = tr->plane.normal; + Vector vecEnd = tr->endpos; + + PushEntityState( ent ); + // create snapshot of current model state + EntityToModelState( &state, ent ); + + if( FBitSet( ent->curstate.iuser1, CF_STATIC_ENTITY )) + { + if( ent->curstate.startpos != g_vecZero ) + scale = Vector( 1.0f / ent->curstate.startpos.x, 1.0f / ent->curstate.startpos.y, 1.0f / ent->curstate.startpos.z ); + } + + // studio decals is always converted into local space to avoid troubles with precision and modelscale + matrix4x4 mat = matrix4x4( ent->origin, ent->angles, scale ); + vecNormal = mat.VectorIRotate( vecNormal ); + vecEnd = mat.VectorITransform( vecEnd ); + SetBits( flags, FDECAL_LOCAL_SPACE ); // now it's in local space + + // simulate network degradation for match results + vecNormal.x = Q_rint( vecNormal.x * 1000.0f ) * 0.001f; + vecNormal.y = Q_rint( vecNormal.y * 1000.0f ) * 0.001f; + vecNormal.z = Q_rint( vecNormal.z * 1000.0f ) * 0.001f; + vecEnd.x = Q_rint( vecEnd.x ); + vecEnd.y = Q_rint( vecEnd.y ); + vecEnd.z = Q_rint( vecEnd.z ); + + StudioDecalShoot( vecNormal, vecEnd, name, ent, flags, &state ); + PopEntityState( ent ); +} + +/* +==================== +StudioDecalShoot + +NOTE: name is decalgroup +==================== +*/ +void CStudioModelRenderer :: StudioDecalShoot( const Vector &vecNormal, const Vector &vecEnd, const char *name, cl_entity_t *ent, int flags, modelstate_t *state ) +{ + DecalBuildInfo_t buildInfo; + float flLocalScale = 1.0f; + + if( !g_fRenderInitialized ) + return; + + // setup studio pointers + if( !StudioSetEntity( ent )) + return; + + if( m_pStudioHeader->numbodyparts == 0 ) + return; // null model? + + // this sucker is state needed only when building decals + buildInfo.m_pTexInfo = DecalGroup::GetEntry( name, flags ); + if( !buildInfo.m_pTexInfo ) return; + DecalGroupEntry *entry = (DecalGroupEntry *)buildInfo.m_pTexInfo; + + // time to cache decal textures + entry->PreloadTextures(); + + if( entry->gl_diffuse_id == 0 ) + { + ALERT( at_warning, "StudioDecal: decal texture '%s' was missed\n", entry->m_DecalName ); + return; // decal texture was missed. + } + + // make sure what time is actual + tr.time = GET_CLIENT_TIME(); + tr.oldtime = GET_CLIENT_OLDTIME(); + + if( FBitSet( flags, FDECAL_LOCAL_SPACE )) + { + if( FBitSet( ent->curstate.iuser1, CF_STATIC_ENTITY )) + { + if( ent->curstate.startpos != g_vecZero ) + { + flLocalScale = ent->curstate.startpos.Average(); + flLocalScale = 1.0f / flLocalScale, 1.0f; + } + } + + // invalidate bonecache + m_pModelInstance->bonecache.frame = -999.0f; + // make sure what model is in local space + ent->curstate.startpos = g_vecZero; + ent->origin = g_vecZero; + ent->angles = g_vecZero; + } + else + { + ALERT( at_warning, "StudioDecal: '%s' not in local space. Ignored!\n", entry->m_DecalName ); + return; + } + + // we are in decal-shooting mode + m_fShootDecal = true; + // setup bones + StudioSetUpTransform ( ); + StudioSetupBones ( ); + m_fShootDecal = false; + + if( FBitSet( ent->curstate.iuser1, CF_STATIC_ENTITY ) && ( ent->curstate.colormap > 0 ) && world->vertex_lighting != NULL ) + { + ModelInstance_t *inst = m_pModelInstance; + + // only do it while restore mode and once only + if( !RENDER_GET_PARM( PARM_CLIENT_ACTIVE, 0 ) && !inst->m_VlCache && !FBitSet( inst->info_flags, MF_VL_BAD_CACHE )) + { + // we need to get right pointers from vertex lighted meshes + CacheVertexLight( RI->currententity ); + } + } + + // setup worldtransform array + if( RI->currentmodel->poseToBone != NULL ) + { + for( int i = 0; i < m_pStudioHeader->numbones; i++ ) + m_pworldtransform[i] = m_pModelInstance->m_pbones[i].ConcatTransforms( RI->currentmodel->poseToBone->posetobone[i] ); + } + else + { + // no pose to bone just copy the bones + for( int i = 0; i < m_pStudioHeader->numbones; i++ ) + m_pworldtransform[i] = m_pModelInstance->m_pbones[i]; + } + + if( !ComputePoseToDecal( vecNormal, vecEnd )) + return; +check_decals: + // too many decals on model, remove + while( m_pModelInstance->m_DecalList.Count() > r_studio_decals->value ) + { + // decals with this depth level are fragments + // of single decal and should be removed together + int depth = m_pModelInstance->m_DecalList[0].depth; + + for( int i = m_pModelInstance->m_DecalList.Count(); --i >= 0; ) + { + studiodecal_t *pDecal = &m_pModelInstance->m_DecalList[i]; + + if( pDecal->depth == depth ) + { + DeleteVBOMesh( &pDecal->mesh ); + memset( pDecal, 0 , sizeof( *pDecal )); + m_pModelInstance->m_DecalList.Remove( i ); + goto check_decals; + } + } + } + + // local scale it's used to keep decal constant size while model is scaled + float xsize = buildInfo.m_pTexInfo->xsize * flLocalScale; + float ysize = buildInfo.m_pTexInfo->ysize * flLocalScale; + + SetBits( flags, FDECAL_NORANDOM ); + buildInfo.m_Radius = sqrt( xsize * xsize + ysize * ysize ) * 0.5f; + buildInfo.vecDecalScale = Vector2D( 1.0f / ysize, 1.0f / xsize ); + buildInfo.m_UseClipVert = ( m_pStudioHeader->numbones <= 1 ); // produce clipped verts only for static props + buildInfo.decalDepth = m_pModelInstance->m_DecalCount++; + buildInfo.decalFlags = (byte)flags; + buildInfo.vecLocalNormal = vecNormal; + buildInfo.vecLocalEnd = vecEnd; + buildInfo.modelState = state; + buildInfo.modelLight = NULL; + buildInfo.current = NULL; + + // special check for per-vertex lighting + if( FBitSet( ent->curstate.iuser1, CF_STATIC_ENTITY ) && ( ent->curstate.colormap > 0 ) && world->vertex_lighting != NULL ) + { + int cacheID = RI->currententity->curstate.colormap - 1; + dvlightlump_t *dvl = world->vertex_lighting; + + if( cacheID < dvl->nummodels && dvl->dataofs[cacheID] != -1 ) + buildInfo.modelLight = (dmodelvertlight_t *)((byte *)world->vertex_lighting + dvl->dataofs[cacheID]); + } + + // step over all body parts + add decals to them all! + for( int k = 0; k < m_pStudioHeader->numbodyparts; k++ ) + { + // grab the model for this body part + StudioSetupModel( k, &m_pSubModel, &m_pVboModel ); + AddDecalToModel( buildInfo ); + } +} + +/* +============================================================= + + DECALS RENDERING + +============================================================= +*/ +void CStudioModelRenderer :: SetDecalUniforms( studiodecal_t *pDecal ) +{ + cl_entity_t *e = RI->currententity; + ModelInstance_t *inst = m_pModelInstance = &m_ModelInstances[e->modelhandle]; + bool vertex_lighting = FBitSet( m_pModelInstance->info_flags, MF_VERTEX_LIGHTING ) ? true : false; + + // rebuild shader if changed + ShaderDecalForward( pDecal, vertex_lighting ); + + GL_BindShader( pDecal->forwardScene.GetShader()); + + glsl_program_t *shader = RI->currentshader; + int num_bones = Q_min( m_pStudioHeader->numbones, glConfig.max_skinning_bones ); + mstudiomaterial_t *pmaterial = (mstudiomaterial_t *)RI->currentmodel->materials; + vbomesh_t *pMesh = pDecal->modelmesh; + Vector4D lightstyles, lightdir; + int width, height; + + int m_skinnum = bound( 0, e->curstate.skin, m_pStudioHeader->numskinfamilies - 1 ); + short *pskinref = (short *)((byte *)m_pStudioHeader + m_pStudioHeader->skinindex); + if( m_skinnum != 0 && m_skinnum < m_pStudioHeader->numskinfamilies ) + pskinref += (m_skinnum * m_pStudioHeader->numskinref); + pmaterial = &pmaterial[pskinref[pMesh->skinref]]; + CDynLight *pl = RI->currentlight; // may be NULL + mstudiolight_t *light = &inst->light; + int map; + + // setup specified uniforms (and texture bindings) + for( int i = 0; i < shader->numUniforms; i++ ) + { + uniform_t *u = &shader->uniforms[i]; + + switch( u->type ) + { + case UT_COLORMAP: + u->SetValue( pmaterial->gl_diffuse_id ); + break; + case UT_DECALMAP: + u->SetValue( pDecal->texinfo->gl_diffuse_id ); + break; + case UT_NORMALMAP: + u->SetValue( pDecal->texinfo->gl_normalmap_id ); + break; + case UT_GLOSSMAP: + u->SetValue( pDecal->texinfo->gl_specular_id ); + break; + case UT_HEIGHTMAP: + u->SetValue( pDecal->texinfo->gl_heightmap_id ); + break; + case UT_PROJECTMAP: + if( pl && pl->type == LIGHT_SPOT ) + u->SetValue( pl->spotlightTexture ); + else u->SetValue( tr.whiteTexture ); + break; + case UT_SHADOWMAP: + case UT_SHADOWMAP0: + if( pl ) u->SetValue( pl->shadowTexture[0] ); + else u->SetValue( tr.depthTexture ); + break; + case UT_SHADOWMAP1: + if( pl ) u->SetValue( pl->shadowTexture[1] ); + else u->SetValue( tr.depthTexture ); + break; + case UT_SHADOWMAP2: + if( pl ) u->SetValue( pl->shadowTexture[2] ); + else u->SetValue( tr.depthTexture ); + break; + case UT_SHADOWMAP3: + if( pl ) u->SetValue( pl->shadowTexture[3] ); + else u->SetValue( tr.depthTexture ); + break; + case UT_LIGHTMAP: + case UT_DELUXEMAP: + // unacceptable for studiomodels + u->SetValue( tr.whiteTexture ); + break; + case UT_SCREENMAP: + u->SetValue( tr.screen_color ); + break; + case UT_DEPTHMAP: + u->SetValue( tr.screen_depth ); + break; + case UT_ENVMAP0: + case UT_ENVMAP: + if( inst->cubemap[0] != NULL ) + u->SetValue( inst->cubemap[0]->texture ); + else u->SetValue( tr.whiteCubeTexture ); + break; + case UT_ENVMAP1: + if( inst->cubemap[1] != NULL ) + u->SetValue( inst->cubemap[1]->texture ); + else u->SetValue( tr.whiteCubeTexture ); + break; + case UT_BSPPLANESMAP: + u->SetValue( tr.packed_planes_texture ); + break; + case UT_BSPNODESMAP: + u->SetValue( tr.packed_nodes_texture ); + break; + case UT_BSPLIGHTSMAP: + u->SetValue( tr.packed_lights_texture ); + break; + case UT_RENDERALPHA: + if( e->curstate.rendermode == kRenderTransTexture || e->curstate.rendermode == kRenderTransAdd ) + u->SetValue( e->curstate.renderamt / 255.0f ); + else u->SetValue( 1.0f ); + break; + case UT_FOGPARAMS: + if( e->curstate.renderfx == SKYBOX_ENTITY ) + u->SetValue( tr.fogColor[0], tr.fogColor[1], tr.fogColor[2], tr.fogSkyDensity ); + else u->SetValue( tr.fogColor[0], tr.fogColor[1], tr.fogColor[2], tr.fogDensity ); + break; + case UT_BONESARRAY: + u->SetValue( &inst->m_glstudiobones[0][0], num_bones * 3 ); + break; + case UT_BONEQUATERNION: + u->SetValue( &inst->m_studioquat[0][0], num_bones ); + break; + case UT_BONEPOSITION: + u->SetValue( &inst->m_studiopos[0][0], num_bones ); + break; + case UT_SCREENSIZEINV: + u->SetValue( 1.0f / (float)glState.width, 1.0f / (float)glState.height ); + break; + case UT_ZFAR: + u->SetValue( -tr.farclip * 1.74f ); + break; + case UT_LIGHTSTYLES: + for( map = 0; map < MAXLIGHTMAPS; map++ ) + { + if( inst->styles[map] != 255 ) + lightstyles[map] = tr.lightstyle[inst->styles[map]]; + else lightstyles[map] = 0.0f; + } + u->SetValue( lightstyles.x, lightstyles.y, lightstyles.z, lightstyles.w ); + break; + case UT_REALTIME: + u->SetValue( (float)tr.time ); + break; + case UT_VIEWORIGIN: + u->SetValue( GetVieworg().x, GetVieworg().y, GetVieworg().z ); + break; + case UT_GAMMATABLE: + u->SetValue( &tr.gamma_table[0][0], 64 ); + break; + case UT_LIGHTDIR: + if( pl ) + { + if( pl->type == LIGHT_DIRECTIONAL ) lightdir = -tr.sky_normal; + else lightdir = pl->frustum.GetPlane( FRUSTUM_FAR )->normal; + u->SetValue( lightdir.x, lightdir.y, lightdir.z, pl->fov ); + } + else u->SetValue( light->normal.x, light->normal.y, light->normal.z ); + break; + case UT_LIGHTDIFFUSE: + if( pl ) u->SetValue( pl->color.x, pl->color.y, pl->color.z ); + else u->SetValue( light->diffuse.x, light->diffuse.y, light->diffuse.z ); + break; + case UT_LIGHTSHADE: + u->SetValue( (float)light->ambientlight / 255.0f, (float)light->shadelight / 255.0f ); + break; + case UT_LIGHTORIGIN: + if( pl ) u->SetValue( pl->origin.x, pl->origin.y, pl->origin.z, ( 1.0f / pl->radius )); + break; + case UT_LIGHTVIEWPROJMATRIX: + if( pl ) + { + GLfloat gl_lightViewProjMatrix[16]; + pl->lightviewProjMatrix.CopyToArray( gl_lightViewProjMatrix ); + u->SetValue( &gl_lightViewProjMatrix[0] ); + } + break; + case UT_DIFFUSEFACTOR: + u->SetValue( tr.diffuseFactor ); + break; + case UT_AMBIENTFACTOR: + if( pl && pl->type == LIGHT_DIRECTIONAL ) + u->SetValue( tr.sun_ambient ); + else u->SetValue( tr.ambientFactor ); + break; + case UT_SUNREFRACT: + u->SetValue( tr.sun_refract ); + break; + case UT_AMBIENTCUBE: + u->SetValue( &light->ambient[0][0], 6 ); + break; + case UT_LERPFACTOR: + u->SetValue( inst->lerpFactor ); + break; + case UT_SMOOTHNESS: + u->SetValue( pDecal->texinfo->matdesc->smoothness ); + break; + case UT_RELIEFPARAMS: + width = RENDER_GET_PARM( PARM_TEX_WIDTH, pDecal->texinfo->gl_heightmap_id ); + height = RENDER_GET_PARM( PARM_TEX_HEIGHT, pDecal->texinfo->gl_heightmap_id ); + u->SetValue( (float)width, (float)height, pDecal->texinfo->matdesc->reliefScale, cv_shadow_offset->value ); + break; + default: + ALERT( at_error, "Unhandled uniform %s\n", u->name ); + break; + } + } +} + +//----------------------------------------------------------------------------- +// Draws all the decals on a particular model +//----------------------------------------------------------------------------- +void CStudioModelRenderer :: DrawDecal( CSolidEntry *entry, GLenum cull_mode ) +{ + if( !StudioSetEntity( entry )) + return; + + if( m_pModelInstance->visframe != tr.realframecount ) + return; // model is culled + + if( !m_pModelInstance->m_DecalList.Count()) + return; // no decals for this model + + if( CVAR_TO_BOOL( r_polyoffset )) + { + pglEnable( GL_POLYGON_OFFSET_FILL ); + pglPolygonOffset( -1.0f, -r_polyoffset->value ); + } + + GL_Blend( GL_TRUE ); + GL_DepthMask( GL_FALSE ); + GL_AlphaTest( GL_FALSE ); + GL_Cull( cull_mode ); + + // Draw decals in history order or reverse order + if( cull_mode == GL_FRONT ) + { + // direct order + for( int i = 0; i < m_pModelInstance->m_DecalList.Count(); i++ ) + { + studiodecal_t *pDecal = &m_pModelInstance->m_DecalList[i]; + + if( entry->m_pMesh->uniqueID != pDecal->modelmesh->uniqueID ) + continue; + + SetDecalUniforms( pDecal ); + if( pDecal->texinfo->opaque ) + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + else pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR ); + DrawMeshFromBuffer( &pDecal->mesh ); + } + } + else + { + // reverse order + for( int i = m_pModelInstance->m_DecalList.Count(); --i >= 0; ) + { + studiodecal_t *pDecal = &m_pModelInstance->m_DecalList[i]; + + if( entry->m_pMesh->uniqueID != pDecal->modelmesh->uniqueID ) + continue; + + SetDecalUniforms( pDecal ); + if( pDecal->texinfo->opaque ) + pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + else pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR ); + DrawMeshFromBuffer( &pDecal->mesh ); + } + } + + if( CVAR_TO_BOOL( r_polyoffset )) + pglDisable( GL_POLYGON_OFFSET_FILL ); + GL_CleanupAllTextureUnits(); + GL_DepthMask( GL_TRUE ); + GL_Blend( GL_FALSE ); + GL_Cull( GL_FRONT ); +} \ No newline at end of file diff --git a/cl_dll/render/gl_studiovbo.cpp b/cl_dll/render/gl_studiovbo.cpp new file mode 100644 index 0000000..c072d7f --- /dev/null +++ b/cl_dll/render/gl_studiovbo.cpp @@ -0,0 +1,1325 @@ +/* +gl_studiovbo.cpp - stored various vertex formats onto GPU +Copyright (C) 2019 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include "cl_util.h" +#include "gl_local.h" +#include "com_model.h" +#include "r_studioint.h" +#include "pm_movevars.h" +#include "gl_studio.h" +#include "gl_sprite.h" +#include "event_api.h" +#include +#include "pm_defs.h" +#include "stringlib.h" +#include "triangleapi.h" +#include "entity_types.h" +#include "gl_shader.h" +#include "gl_world.h" +#include "vertex_fmt.h" + +mesh_loader_t CStudioModelRenderer :: m_pfnMeshLoaderGL21[MESHLOADER_COUNT] = +{ +{ CreateBufferBaseGL21, BindBufferBaseGL21, "BaseBuffer" }, +{ CreateBufferBaseBumpGL21, BindBufferBaseBumpGL21, "BumpBaseBuffer" }, +{ CreateBufferVLightGL21, BindBufferVLightGL21, "VertexLightBuffer" }, +{ CreateBufferVLightBumpGL21, BindBufferVLightBumpGL21, "BumpVertexLightBuffer" }, +{ CreateBufferWeightGL21, BindBufferWeightGL21, "WeightBuffer" }, +{ CreateBufferWeightBumpGL21, BindBufferWeightBumpGL21, "WeightBumpBuffer" }, +{ CreateBufferLightMapGL21, BindBufferLightMapGL21, "LightMapBuffer" }, +{ CreateBufferLightMapBumpGL21, BindBufferLightMapBumpGL21, "BumpLightMapBuffer" }, +{ CreateBufferGenericGL21, BindBufferGenericGL21, "GenericBuffer" }, +}; + +mesh_loader_t CStudioModelRenderer :: m_pfnMeshLoaderGL30[MESHLOADER_COUNT] = +{ +{ CreateBufferBaseGL30, BindBufferBaseGL30, "BaseBuffer" }, +{ CreateBufferBaseBumpGL30, BindBufferBaseBumpGL30, "BumpBaseBuffer" }, +{ CreateBufferVLightGL30, BindBufferVLightGL30, "VertexLightBuffer" }, +{ CreateBufferVLightBumpGL30, BindBufferVLightBumpGL30, "BumpVertexLightBuffer" }, +{ CreateBufferWeightGL30, BindBufferWeightGL30, "WeightBuffer" }, +{ CreateBufferWeightBumpGL30, BindBufferWeightBumpGL30, "WeightBumpBuffer" }, +{ CreateBufferLightMapGL30, BindBufferLightMapGL30, "LightMapBuffer" }, +{ CreateBufferLightMapBumpGL30, BindBufferLightMapBumpGL30, "BumpLightMapBuffer" }, +{ CreateBufferGenericGL30, BindBufferGenericGL30, "GenericBuffer" }, +}; + +void CStudioModelRenderer :: CreateBufferBaseGL21( vbomesh_t *pOut, svert_t *arrayxvert ) +{ + static svert_v0_gl21_t arraysvert[MAXARRAYVERTS]; + + // convert to GLSL-compacted array + for( int i = 0; i < pOut->numVerts; i++ ) + { + arraysvert[i].vertex[0] = arrayxvert[i].vertex[0]; + arraysvert[i].vertex[1] = arrayxvert[i].vertex[1]; + arraysvert[i].vertex[2] = arrayxvert[i].vertex[2]; + arraysvert[i].stcoord[0] = arrayxvert[i].stcoord[0]; + arraysvert[i].stcoord[1] = arrayxvert[i].stcoord[1]; + arraysvert[i].stcoord[2] = arrayxvert[i].stcoord[2]; + arraysvert[i].stcoord[3] = arrayxvert[i].stcoord[3]; + arraysvert[i].normal[0] = arrayxvert[i].normal[0]; + arraysvert[i].normal[1] = arrayxvert[i].normal[1]; + arraysvert[i].normal[2] = arrayxvert[i].normal[2]; + arraysvert[i].boneid = arrayxvert[i].boneid[0]; + } + + pOut->cacheSize = pOut->numVerts * sizeof( svert_v0_gl21_t ); + + pglGenBuffersARB( 1, &pOut->vbo ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, pOut->cacheSize, &arraysvert[0], GL_STATIC_DRAW_ARB ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); +} + +void CStudioModelRenderer :: BindBufferBaseGL21( vbomesh_t *pOut, int attrFlags ) +{ + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION ); + pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v0_gl21_t ), (void *)offsetof( svert_v0_gl21_t, vertex )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD0 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD0, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v0_gl21_t ), (void *)offsetof( svert_v0_gl21_t, stcoord )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v0_gl21_t ), (void *)offsetof( svert_v0_gl21_t, normal )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_BONE_INDEXES ); + pglVertexAttribPointerARB( ATTR_INDEX_BONE_INDEXES, 1, GL_FLOAT, GL_FALSE, sizeof( svert_v0_gl21_t ), (void *)offsetof( svert_v0_gl21_t, boneid )); +} + +void CStudioModelRenderer :: CreateBufferBaseGL30( vbomesh_t *pOut, svert_t *arrayxvert ) +{ + static svert_v0_gl30_t arraysvert[MAXARRAYVERTS]; + + // convert to GLSL-compacted array + for( int i = 0; i < pOut->numVerts; i++ ) + { + arraysvert[i].vertex[0] = arrayxvert[i].vertex[0]; + arraysvert[i].vertex[1] = arrayxvert[i].vertex[1]; + arraysvert[i].vertex[2] = arrayxvert[i].vertex[2]; + arraysvert[i].stcoord[0] = FloatToHalf( arrayxvert[i].stcoord[0] ); + arraysvert[i].stcoord[1] = FloatToHalf( arrayxvert[i].stcoord[1] ); + arraysvert[i].stcoord[2] = FloatToHalf( arrayxvert[i].stcoord[2] ); + arraysvert[i].stcoord[3] = FloatToHalf( arrayxvert[i].stcoord[3] ); + CompressNormalizedVector( arraysvert[i].normal, arrayxvert[i].normal ); + arraysvert[i].boneid = arrayxvert[i].boneid[0]; + } + + pOut->cacheSize = pOut->numVerts * sizeof( svert_v0_gl30_t ); + + pglGenBuffersARB( 1, &pOut->vbo ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, pOut->cacheSize, &arraysvert[0], GL_STATIC_DRAW_ARB ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); +} + +void CStudioModelRenderer :: BindBufferBaseGL30( vbomesh_t *pOut, int attrFlags ) +{ + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION ); + pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v0_gl30_t ), (void *)offsetof( svert_v0_gl30_t, vertex )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD0 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD0, 4, GL_HALF_FLOAT_ARB, GL_FALSE, sizeof( svert_v0_gl30_t ), (void *)offsetof( svert_v0_gl30_t, stcoord )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 3, GL_BYTE, GL_FALSE, sizeof( svert_v0_gl30_t ), (void *)offsetof( svert_v0_gl30_t, normal )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_BONE_INDEXES ); + pglVertexAttribPointerARB( ATTR_INDEX_BONE_INDEXES, 1, GL_BYTE, GL_FALSE, sizeof( svert_v0_gl30_t ), (void *)offsetof( svert_v0_gl30_t, boneid )); +} + +void CStudioModelRenderer :: CreateBufferBaseBumpGL21( vbomesh_t *pOut, svert_t *arrayxvert ) +{ + static svert_v1_gl21_t arraysvert[MAXARRAYVERTS]; + + // convert to GLSL-compacted array + for( int i = 0; i < pOut->numVerts; i++ ) + { + arraysvert[i].vertex[0] = arrayxvert[i].vertex[0]; + arraysvert[i].vertex[1] = arrayxvert[i].vertex[1]; + arraysvert[i].vertex[2] = arrayxvert[i].vertex[2]; + arraysvert[i].stcoord[0] = arrayxvert[i].stcoord[0]; + arraysvert[i].stcoord[1] = arrayxvert[i].stcoord[1]; + arraysvert[i].stcoord[2] = arrayxvert[i].stcoord[2]; + arraysvert[i].stcoord[3] = arrayxvert[i].stcoord[3]; + arraysvert[i].tangent[0] = arrayxvert[i].tangent[0]; + arraysvert[i].tangent[1] = arrayxvert[i].tangent[1]; + arraysvert[i].tangent[2] = arrayxvert[i].tangent[2]; + arraysvert[i].binormal[0] = arrayxvert[i].binormal[0]; + arraysvert[i].binormal[1] = arrayxvert[i].binormal[1]; + arraysvert[i].binormal[2] = arrayxvert[i].binormal[2]; + arraysvert[i].normal[0] = arrayxvert[i].normal[0]; + arraysvert[i].normal[1] = arrayxvert[i].normal[1]; + arraysvert[i].normal[2] = arrayxvert[i].normal[2]; + arraysvert[i].boneid = arrayxvert[i].boneid[0]; + } + + pOut->cacheSize = pOut->numVerts * sizeof( svert_v1_gl21_t ); + + pglGenBuffersARB( 1, &pOut->vbo ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, pOut->cacheSize, &arraysvert[0], GL_STATIC_DRAW_ARB ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); +} + +void CStudioModelRenderer :: BindBufferBaseBumpGL21( vbomesh_t *pOut, int attrFlags ) +{ + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION ); + pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v1_gl21_t ), (void *)offsetof( svert_v1_gl21_t, vertex )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD0 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD0, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v1_gl21_t ), (void *)offsetof( svert_v1_gl21_t, stcoord )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v1_gl21_t ), (void *)offsetof( svert_v1_gl21_t, normal )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_BINORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_BINORMAL, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v1_gl21_t ), (void *)offsetof( svert_v1_gl21_t, binormal )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TANGENT ); + pglVertexAttribPointerARB( ATTR_INDEX_TANGENT, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v1_gl21_t ), (void *)offsetof( svert_v1_gl21_t, tangent )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_BONE_INDEXES ); + pglVertexAttribPointerARB( ATTR_INDEX_BONE_INDEXES, 1, GL_FLOAT, GL_FALSE, sizeof( svert_v1_gl21_t ), (void *)offsetof( svert_v1_gl21_t, boneid )); +} + +void CStudioModelRenderer :: CreateBufferBaseBumpGL30( vbomesh_t *pOut, svert_t *arrayxvert ) +{ + static svert_v1_gl30_t arraysvert[MAXARRAYVERTS]; + + // convert to GLSL-compacted array + for( int i = 0; i < pOut->numVerts; i++ ) + { + arraysvert[i].vertex[0] = arrayxvert[i].vertex[0]; + arraysvert[i].vertex[1] = arrayxvert[i].vertex[1]; + arraysvert[i].vertex[2] = arrayxvert[i].vertex[2]; + arraysvert[i].stcoord[0] = FloatToHalf( arrayxvert[i].stcoord[0] ); + arraysvert[i].stcoord[1] = FloatToHalf( arrayxvert[i].stcoord[1] ); + arraysvert[i].stcoord[2] = FloatToHalf( arrayxvert[i].stcoord[2] ); + arraysvert[i].stcoord[3] = FloatToHalf( arrayxvert[i].stcoord[3] ); + CompressNormalizedVector( arraysvert[i].normal, arrayxvert[i].normal ); + CompressNormalizedVector( arraysvert[i].tangent, arrayxvert[i].tangent ); + CompressNormalizedVector( arraysvert[i].binormal, arrayxvert[i].binormal ); + arraysvert[i].boneid = arrayxvert[i].boneid[0]; + } + + pOut->cacheSize = pOut->numVerts * sizeof( svert_v1_gl30_t ); + + pglGenBuffersARB( 1, &pOut->vbo ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, pOut->cacheSize, &arraysvert[0], GL_STATIC_DRAW_ARB ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); +} + +void CStudioModelRenderer :: BindBufferBaseBumpGL30( vbomesh_t *pOut, int attrFlags ) +{ + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION ); + pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v1_gl30_t ), (void *)offsetof( svert_v1_gl30_t, vertex )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD0 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD0, 4, GL_HALF_FLOAT_ARB, GL_FALSE, sizeof( svert_v1_gl30_t ), (void *)offsetof( svert_v1_gl30_t, stcoord )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 3, GL_BYTE, GL_FALSE, sizeof( svert_v1_gl30_t ), (void *)offsetof( svert_v1_gl30_t, normal )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_BINORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_BINORMAL, 3, GL_BYTE, GL_FALSE, sizeof( svert_v1_gl30_t ), (void *)offsetof( svert_v1_gl30_t, binormal )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TANGENT ); + pglVertexAttribPointerARB( ATTR_INDEX_TANGENT, 3, GL_BYTE, GL_FALSE, sizeof( svert_v1_gl30_t ), (void *)offsetof( svert_v1_gl30_t, tangent )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_BONE_INDEXES ); + pglVertexAttribPointerARB( ATTR_INDEX_BONE_INDEXES, 1, GL_BYTE, GL_FALSE, sizeof( svert_v1_gl30_t ), (void *)offsetof( svert_v1_gl30_t, boneid )); +} + +void CStudioModelRenderer :: CreateBufferVLightGL21( vbomesh_t *pOut, svert_t *arrayxvert ) +{ + static svert_v2_gl21_t arraysvert[MAXARRAYVERTS]; + + // convert to GLSL-compacted array + for( int i = 0; i < pOut->numVerts; i++ ) + { + arraysvert[i].vertex[0] = arrayxvert[i].vertex[0]; + arraysvert[i].vertex[1] = arrayxvert[i].vertex[1]; + arraysvert[i].vertex[2] = arrayxvert[i].vertex[2]; + arraysvert[i].stcoord[0] = arrayxvert[i].stcoord[0]; + arraysvert[i].stcoord[1] = arrayxvert[i].stcoord[1]; + arraysvert[i].stcoord[2] = arrayxvert[i].stcoord[2]; + arraysvert[i].stcoord[3] = arrayxvert[i].stcoord[3]; + arraysvert[i].normal[0] = arrayxvert[i].normal[0]; + arraysvert[i].normal[1] = arrayxvert[i].normal[1]; + arraysvert[i].normal[2] = arrayxvert[i].normal[2]; + arraysvert[i].light[0] = arrayxvert[i].light[0]; + arraysvert[i].light[1] = arrayxvert[i].light[1]; + arraysvert[i].light[2] = arrayxvert[i].light[2]; + arraysvert[i].light[3] = arrayxvert[i].light[3]; + } + + pOut->cacheSize = pOut->numVerts * sizeof( svert_v2_gl21_t ); + + pglGenBuffersARB( 1, &pOut->vbo ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, pOut->cacheSize, &arraysvert[0], GL_STATIC_DRAW_ARB ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); +} + +void CStudioModelRenderer :: BindBufferVLightGL21( vbomesh_t *pOut, int attrFlags ) +{ + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION ); + pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v2_gl21_t ), (void *)offsetof( svert_v2_gl21_t, vertex )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD0 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD0, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v2_gl21_t ), (void *)offsetof( svert_v2_gl21_t, stcoord )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v2_gl21_t ), (void *)offsetof( svert_v2_gl21_t, normal )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_COLOR ); + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_COLOR, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v2_gl21_t ), (void *)offsetof( svert_v2_gl21_t, light )); +} + +void CStudioModelRenderer :: CreateBufferVLightGL30( vbomesh_t *pOut, svert_t *arrayxvert ) +{ + static svert_v2_gl30_t arraysvert[MAXARRAYVERTS]; + + // convert to GLSL-compacted array + for( int i = 0; i < pOut->numVerts; i++ ) + { + arraysvert[i].vertex[0] = arrayxvert[i].vertex[0]; + arraysvert[i].vertex[1] = arrayxvert[i].vertex[1]; + arraysvert[i].vertex[2] = arrayxvert[i].vertex[2]; + arraysvert[i].stcoord[0] = FloatToHalf( arrayxvert[i].stcoord[0] ); + arraysvert[i].stcoord[1] = FloatToHalf( arrayxvert[i].stcoord[1] ); + arraysvert[i].stcoord[2] = FloatToHalf( arrayxvert[i].stcoord[2] ); + arraysvert[i].stcoord[3] = FloatToHalf( arrayxvert[i].stcoord[3] ); + CompressNormalizedVector( arraysvert[i].normal, arrayxvert[i].normal ); + arraysvert[i].light[0] = arrayxvert[i].light[0]; + arraysvert[i].light[1] = arrayxvert[i].light[1]; + arraysvert[i].light[2] = arrayxvert[i].light[2]; + arraysvert[i].light[3] = arrayxvert[i].light[3]; + } + + pOut->cacheSize = pOut->numVerts * sizeof( svert_v2_gl30_t ); + + pglGenBuffersARB( 1, &pOut->vbo ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, pOut->cacheSize, &arraysvert[0], GL_STATIC_DRAW_ARB ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); +} + +void CStudioModelRenderer :: BindBufferVLightGL30( vbomesh_t *pOut, int attrFlags ) +{ + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION ); + pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v2_gl30_t ), (void *)offsetof( svert_v2_gl30_t, vertex )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD0 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD0, 4, GL_HALF_FLOAT_ARB, GL_FALSE, sizeof( svert_v2_gl30_t ), (void *)offsetof( svert_v2_gl30_t, stcoord )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 3, GL_BYTE, GL_FALSE, sizeof( svert_v2_gl30_t ), (void *)offsetof( svert_v2_gl30_t, normal )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_COLOR ); + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_COLOR, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v2_gl30_t ), (void *)offsetof( svert_v2_gl30_t, light )); +} + +void CStudioModelRenderer :: CreateBufferVLightBumpGL21( vbomesh_t *pOut, svert_t *arrayxvert ) +{ + static svert_v3_gl21_t arraysvert[MAXARRAYVERTS]; + + // convert to GLSL-compacted array + for( int i = 0; i < pOut->numVerts; i++ ) + { + arraysvert[i].vertex[0] = arrayxvert[i].vertex[0]; + arraysvert[i].vertex[1] = arrayxvert[i].vertex[1]; + arraysvert[i].vertex[2] = arrayxvert[i].vertex[2]; + arraysvert[i].stcoord[0] = arrayxvert[i].stcoord[0]; + arraysvert[i].stcoord[1] = arrayxvert[i].stcoord[1]; + arraysvert[i].stcoord[2] = arrayxvert[i].stcoord[2]; + arraysvert[i].stcoord[3] = arrayxvert[i].stcoord[3]; + arraysvert[i].light[0] = arrayxvert[i].light[0]; + arraysvert[i].light[1] = arrayxvert[i].light[1]; + arraysvert[i].light[2] = arrayxvert[i].light[2]; + arraysvert[i].light[3] = arrayxvert[i].light[3]; + arraysvert[i].deluxe[0] = arrayxvert[i].deluxe[0]; + arraysvert[i].deluxe[1] = arrayxvert[i].deluxe[1]; + arraysvert[i].deluxe[2] = arrayxvert[i].deluxe[2]; + arraysvert[i].deluxe[3] = arrayxvert[i].deluxe[3]; + arraysvert[i].tangent[0] = arrayxvert[i].tangent[0]; + arraysvert[i].tangent[1] = arrayxvert[i].tangent[1]; + arraysvert[i].tangent[2] = arrayxvert[i].tangent[2]; + arraysvert[i].binormal[0] = arrayxvert[i].binormal[0]; + arraysvert[i].binormal[1] = arrayxvert[i].binormal[1]; + arraysvert[i].binormal[2] = arrayxvert[i].binormal[2]; + arraysvert[i].normal[0] = arrayxvert[i].normal[0]; + arraysvert[i].normal[1] = arrayxvert[i].normal[1]; + arraysvert[i].normal[2] = arrayxvert[i].normal[2]; + } + + pOut->cacheSize = pOut->numVerts * sizeof( svert_v3_gl21_t ); + + pglGenBuffersARB( 1, &pOut->vbo ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, pOut->cacheSize, &arraysvert[0], GL_STATIC_DRAW_ARB ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); +} + +void CStudioModelRenderer :: BindBufferVLightBumpGL21( vbomesh_t *pOut, int attrFlags ) +{ + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION ); + pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v3_gl21_t ), (void *)offsetof( svert_v3_gl21_t, vertex )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD0 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD0, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v3_gl21_t ), (void *)offsetof( svert_v3_gl21_t, stcoord )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v3_gl21_t ), (void *)offsetof( svert_v3_gl21_t, normal )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_BINORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_BINORMAL, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v3_gl21_t ), (void *)offsetof( svert_v3_gl21_t, binormal )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TANGENT ); + pglVertexAttribPointerARB( ATTR_INDEX_TANGENT, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v3_gl21_t ), (void *)offsetof( svert_v3_gl21_t, tangent )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_COLOR ); + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_COLOR, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v3_gl21_t ), (void *)offsetof( svert_v3_gl21_t, light )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_VECS ); + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_VECS, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v3_gl21_t ), (void *)offsetof( svert_v3_gl21_t, deluxe )); +} + +void CStudioModelRenderer :: CreateBufferVLightBumpGL30( vbomesh_t *pOut, svert_t *arrayxvert ) +{ + static svert_v3_gl30_t arraysvert[MAXARRAYVERTS]; + + // convert to GLSL-compacted array + for( int i = 0; i < pOut->numVerts; i++ ) + { + arraysvert[i].vertex[0] = arrayxvert[i].vertex[0]; + arraysvert[i].vertex[1] = arrayxvert[i].vertex[1]; + arraysvert[i].vertex[2] = arrayxvert[i].vertex[2]; + arraysvert[i].stcoord[0] = FloatToHalf( arrayxvert[i].stcoord[0] ); + arraysvert[i].stcoord[1] = FloatToHalf( arrayxvert[i].stcoord[1] ); + arraysvert[i].stcoord[2] = FloatToHalf( arrayxvert[i].stcoord[2] ); + arraysvert[i].stcoord[3] = FloatToHalf( arrayxvert[i].stcoord[3] ); + arraysvert[i].light[0] = arrayxvert[i].light[0]; + arraysvert[i].light[1] = arrayxvert[i].light[1]; + arraysvert[i].light[2] = arrayxvert[i].light[2]; + arraysvert[i].light[3] = arrayxvert[i].light[3]; + arraysvert[i].deluxe[0] = arrayxvert[i].deluxe[0]; + arraysvert[i].deluxe[1] = arrayxvert[i].deluxe[1]; + arraysvert[i].deluxe[2] = arrayxvert[i].deluxe[2]; + arraysvert[i].deluxe[3] = arrayxvert[i].deluxe[3]; + CompressNormalizedVector( arraysvert[i].normal, arrayxvert[i].normal ); + CompressNormalizedVector( arraysvert[i].tangent, arrayxvert[i].tangent ); + CompressNormalizedVector( arraysvert[i].binormal, arrayxvert[i].binormal ); + } + + pOut->cacheSize = pOut->numVerts * sizeof( svert_v3_gl30_t ); + + pglGenBuffersARB( 1, &pOut->vbo ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, pOut->cacheSize, &arraysvert[0], GL_STATIC_DRAW_ARB ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); +} + +void CStudioModelRenderer :: BindBufferVLightBumpGL30( vbomesh_t *pOut, int attrFlags ) +{ + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION ); + pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v3_gl30_t ), (void *)offsetof( svert_v3_gl30_t, vertex )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD0 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD0, 4, GL_HALF_FLOAT_ARB, GL_FALSE, sizeof( svert_v3_gl30_t ), (void *)offsetof( svert_v3_gl30_t, stcoord )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 3, GL_BYTE, GL_FALSE, sizeof( svert_v3_gl30_t ), (void *)offsetof( svert_v3_gl30_t, normal )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_BINORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_BINORMAL, 3, GL_BYTE, GL_FALSE, sizeof( svert_v3_gl30_t ), (void *)offsetof( svert_v3_gl30_t, binormal )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TANGENT ); + pglVertexAttribPointerARB( ATTR_INDEX_TANGENT, 3, GL_BYTE, GL_FALSE, sizeof( svert_v3_gl30_t ), (void *)offsetof( svert_v3_gl30_t, tangent )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_COLOR ); + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_COLOR, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v3_gl30_t ), (void *)offsetof( svert_v3_gl30_t, light )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_VECS ); + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_VECS, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v3_gl30_t ), (void *)offsetof( svert_v3_gl30_t, deluxe )); +} + +void CStudioModelRenderer :: CreateBufferWeightGL21( vbomesh_t *pOut, svert_t *arrayxvert ) +{ + static svert_v4_gl21_t arraysvert[MAXARRAYVERTS]; + + // convert to GLSL-compacted array + for( int i = 0; i < pOut->numVerts; i++ ) + { + arraysvert[i].vertex[0] = arrayxvert[i].vertex[0]; + arraysvert[i].vertex[1] = arrayxvert[i].vertex[1]; + arraysvert[i].vertex[2] = arrayxvert[i].vertex[2]; + arraysvert[i].stcoord[0] = arrayxvert[i].stcoord[0]; + arraysvert[i].stcoord[1] = arrayxvert[i].stcoord[1]; + arraysvert[i].stcoord[2] = arrayxvert[i].stcoord[2]; + arraysvert[i].stcoord[3] = arrayxvert[i].stcoord[3]; + arraysvert[i].normal[0] = arrayxvert[i].normal[0]; + arraysvert[i].normal[1] = arrayxvert[i].normal[1]; + arraysvert[i].normal[2] = arrayxvert[i].normal[2]; + arraysvert[i].boneid[0] = arrayxvert[i].boneid[0]; + arraysvert[i].boneid[1] = arrayxvert[i].boneid[1]; + arraysvert[i].boneid[2] = arrayxvert[i].boneid[2]; + arraysvert[i].boneid[3] = arrayxvert[i].boneid[3]; + arraysvert[i].weight[0] = arrayxvert[i].weight[0]; + arraysvert[i].weight[1] = arrayxvert[i].weight[1]; + arraysvert[i].weight[2] = arrayxvert[i].weight[2]; + arraysvert[i].weight[3] = arrayxvert[i].weight[3]; + } + + pOut->cacheSize = pOut->numVerts * sizeof( svert_v4_gl21_t ); + + pglGenBuffersARB( 1, &pOut->vbo ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, pOut->cacheSize, &arraysvert[0], GL_STATIC_DRAW_ARB ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); +} + +void CStudioModelRenderer :: BindBufferWeightGL21( vbomesh_t *pOut, int attrFlags ) +{ + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION ); + pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v4_gl21_t ), (void *)offsetof( svert_v4_gl21_t, vertex )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD0 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD0, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v4_gl21_t ), (void *)offsetof( svert_v4_gl21_t, stcoord )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v4_gl21_t ), (void *)offsetof( svert_v4_gl21_t, normal )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_BONE_INDEXES ); + pglVertexAttribPointerARB( ATTR_INDEX_BONE_INDEXES, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v4_gl21_t ), (void *)offsetof( svert_v4_gl21_t, boneid )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_BONE_WEIGHTS ); + pglVertexAttribPointerARB( ATTR_INDEX_BONE_WEIGHTS, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v4_gl21_t ), (void *)offsetof( svert_v4_gl21_t, weight )); +} + +void CStudioModelRenderer :: CreateBufferWeightGL30( vbomesh_t *pOut, svert_t *arrayxvert ) +{ + static svert_v4_gl30_t arraysvert[MAXARRAYVERTS]; + + // convert to GLSL-compacted array + for( int i = 0; i < pOut->numVerts; i++ ) + { + arraysvert[i].vertex[0] = arrayxvert[i].vertex[0]; + arraysvert[i].vertex[1] = arrayxvert[i].vertex[1]; + arraysvert[i].vertex[2] = arrayxvert[i].vertex[2]; + arraysvert[i].stcoord[0] = FloatToHalf( arrayxvert[i].stcoord[0] ); + arraysvert[i].stcoord[1] = FloatToHalf( arrayxvert[i].stcoord[1] ); + arraysvert[i].stcoord[2] = FloatToHalf( arrayxvert[i].stcoord[2] ); + arraysvert[i].stcoord[3] = FloatToHalf( arrayxvert[i].stcoord[3] ); + CompressNormalizedVector( arraysvert[i].normal, arrayxvert[i].normal ); + arraysvert[i].boneid[0] = arrayxvert[i].boneid[0]; + arraysvert[i].boneid[1] = arrayxvert[i].boneid[1]; + arraysvert[i].boneid[2] = arrayxvert[i].boneid[2]; + arraysvert[i].boneid[3] = arrayxvert[i].boneid[3]; + arraysvert[i].weight[0] = arrayxvert[i].weight[0]; + arraysvert[i].weight[1] = arrayxvert[i].weight[1]; + arraysvert[i].weight[2] = arrayxvert[i].weight[2]; + arraysvert[i].weight[3] = arrayxvert[i].weight[3]; + } + + pOut->cacheSize = pOut->numVerts * sizeof( svert_v4_gl30_t ); + + pglGenBuffersARB( 1, &pOut->vbo ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, pOut->cacheSize, &arraysvert[0], GL_STATIC_DRAW_ARB ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); +} + +void CStudioModelRenderer :: BindBufferWeightGL30( vbomesh_t *pOut, int attrFlags ) +{ + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION ); + pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v4_gl30_t ), (void *)offsetof( svert_v4_gl30_t, vertex )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD0 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD0, 4, GL_HALF_FLOAT_ARB, GL_FALSE, sizeof( svert_v4_gl30_t ), (void *)offsetof( svert_v4_gl30_t, stcoord )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 3, GL_BYTE, GL_FALSE, sizeof( svert_v4_gl30_t ), (void *)offsetof( svert_v4_gl30_t, normal )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_BONE_INDEXES ); + pglVertexAttribPointerARB( ATTR_INDEX_BONE_INDEXES, 4, GL_BYTE, GL_FALSE, sizeof( svert_v4_gl30_t ), (void *)offsetof( svert_v4_gl30_t, boneid )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_BONE_WEIGHTS ); + pglVertexAttribPointerARB( ATTR_INDEX_BONE_WEIGHTS, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof( svert_v4_gl30_t ), (void *)offsetof( svert_v4_gl30_t, weight )); +} + +void CStudioModelRenderer :: CreateBufferWeightBumpGL21( vbomesh_t *pOut, svert_t *arrayxvert ) +{ + static svert_v5_gl21_t arraysvert[MAXARRAYVERTS]; + + // convert to GLSL-compacted array + for( int i = 0; i < pOut->numVerts; i++ ) + { + arraysvert[i].vertex[0] = arrayxvert[i].vertex[0]; + arraysvert[i].vertex[1] = arrayxvert[i].vertex[1]; + arraysvert[i].vertex[2] = arrayxvert[i].vertex[2]; + arraysvert[i].stcoord[0] = arrayxvert[i].stcoord[0]; + arraysvert[i].stcoord[1] = arrayxvert[i].stcoord[1]; + arraysvert[i].stcoord[2] = arrayxvert[i].stcoord[2]; + arraysvert[i].stcoord[3] = arrayxvert[i].stcoord[3]; + arraysvert[i].tangent[0] = arrayxvert[i].tangent[0]; + arraysvert[i].tangent[1] = arrayxvert[i].tangent[1]; + arraysvert[i].tangent[2] = arrayxvert[i].tangent[2]; + arraysvert[i].binormal[0] = arrayxvert[i].binormal[0]; + arraysvert[i].binormal[1] = arrayxvert[i].binormal[1]; + arraysvert[i].binormal[2] = arrayxvert[i].binormal[2]; + arraysvert[i].normal[0] = arrayxvert[i].normal[0]; + arraysvert[i].normal[1] = arrayxvert[i].normal[1]; + arraysvert[i].normal[2] = arrayxvert[i].normal[2]; + arraysvert[i].boneid[0] = arrayxvert[i].boneid[0]; + arraysvert[i].boneid[1] = arrayxvert[i].boneid[1]; + arraysvert[i].boneid[2] = arrayxvert[i].boneid[2]; + arraysvert[i].boneid[3] = arrayxvert[i].boneid[3]; + arraysvert[i].weight[0] = arrayxvert[i].weight[0]; + arraysvert[i].weight[1] = arrayxvert[i].weight[1]; + arraysvert[i].weight[2] = arrayxvert[i].weight[2]; + arraysvert[i].weight[3] = arrayxvert[i].weight[3]; + } + + pOut->cacheSize = pOut->numVerts * sizeof( svert_v5_gl21_t ); + + pglGenBuffersARB( 1, &pOut->vbo ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, pOut->cacheSize, &arraysvert[0], GL_STATIC_DRAW_ARB ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); +} + +void CStudioModelRenderer :: BindBufferWeightBumpGL21( vbomesh_t *pOut, int attrFlags ) +{ + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION ); + pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v5_gl21_t ), (void *)offsetof( svert_v5_gl21_t, vertex )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD0 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD0, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v5_gl21_t ), (void *)offsetof( svert_v5_gl21_t, stcoord )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v5_gl21_t ), (void *)offsetof( svert_v5_gl21_t, normal )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_BINORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_BINORMAL, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v5_gl21_t ), (void *)offsetof( svert_v5_gl21_t, binormal )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TANGENT ); + pglVertexAttribPointerARB( ATTR_INDEX_TANGENT, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v5_gl21_t ), (void *)offsetof( svert_v5_gl21_t, tangent )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_BONE_INDEXES ); + pglVertexAttribPointerARB( ATTR_INDEX_BONE_INDEXES, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v5_gl21_t ), (void *)offsetof( svert_v5_gl21_t, boneid )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_BONE_WEIGHTS ); + pglVertexAttribPointerARB( ATTR_INDEX_BONE_WEIGHTS, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v5_gl21_t ), (void *)offsetof( svert_v5_gl21_t, weight )); +} + +void CStudioModelRenderer :: CreateBufferWeightBumpGL30( vbomesh_t *pOut, svert_t *arrayxvert ) +{ + static svert_v5_gl30_t arraysvert[MAXARRAYVERTS]; + + // convert to GLSL-compacted array + for( int i = 0; i < pOut->numVerts; i++ ) + { + arraysvert[i].vertex[0] = arrayxvert[i].vertex[0]; + arraysvert[i].vertex[1] = arrayxvert[i].vertex[1]; + arraysvert[i].vertex[2] = arrayxvert[i].vertex[2]; + arraysvert[i].stcoord[0] = FloatToHalf( arrayxvert[i].stcoord[0] ); + arraysvert[i].stcoord[1] = FloatToHalf( arrayxvert[i].stcoord[1] ); + arraysvert[i].stcoord[2] = FloatToHalf( arrayxvert[i].stcoord[2] ); + arraysvert[i].stcoord[3] = FloatToHalf( arrayxvert[i].stcoord[3] ); + CompressNormalizedVector( arraysvert[i].normal, arrayxvert[i].normal ); + CompressNormalizedVector( arraysvert[i].tangent, arrayxvert[i].tangent ); + CompressNormalizedVector( arraysvert[i].binormal, arrayxvert[i].binormal ); + arraysvert[i].boneid[0] = arrayxvert[i].boneid[0]; + arraysvert[i].boneid[1] = arrayxvert[i].boneid[1]; + arraysvert[i].boneid[2] = arrayxvert[i].boneid[2]; + arraysvert[i].boneid[3] = arrayxvert[i].boneid[3]; + arraysvert[i].weight[0] = arrayxvert[i].weight[0]; + arraysvert[i].weight[1] = arrayxvert[i].weight[1]; + arraysvert[i].weight[2] = arrayxvert[i].weight[2]; + arraysvert[i].weight[3] = arrayxvert[i].weight[3]; + } + + pOut->cacheSize = pOut->numVerts * sizeof( svert_v5_gl30_t ); + + pglGenBuffersARB( 1, &pOut->vbo ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, pOut->cacheSize, &arraysvert[0], GL_STATIC_DRAW_ARB ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); +} + +void CStudioModelRenderer :: BindBufferWeightBumpGL30( vbomesh_t *pOut, int attrFlags ) +{ + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION ); + pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v5_gl30_t ), (void *)offsetof( svert_v5_gl30_t, vertex )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD0 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD0, 4, GL_HALF_FLOAT_ARB, GL_FALSE, sizeof( svert_v5_gl30_t ), (void *)offsetof( svert_v5_gl30_t, stcoord )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 3, GL_BYTE, GL_FALSE, sizeof( svert_v5_gl30_t ), (void *)offsetof( svert_v5_gl30_t, normal )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_BINORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_BINORMAL, 3, GL_BYTE, GL_FALSE, sizeof( svert_v5_gl30_t ), (void *)offsetof( svert_v5_gl30_t, binormal )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TANGENT ); + pglVertexAttribPointerARB( ATTR_INDEX_TANGENT, 3, GL_BYTE, GL_FALSE, sizeof( svert_v5_gl30_t ), (void *)offsetof( svert_v5_gl30_t, tangent )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_BONE_INDEXES ); + pglVertexAttribPointerARB( ATTR_INDEX_BONE_INDEXES, 4, GL_BYTE, GL_FALSE, sizeof( svert_v5_gl30_t ), (void *)offsetof( svert_v5_gl30_t, boneid )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_BONE_WEIGHTS ); + pglVertexAttribPointerARB( ATTR_INDEX_BONE_WEIGHTS, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof( svert_v5_gl30_t ), (void *)offsetof( svert_v5_gl30_t, weight )); +} + +void CStudioModelRenderer :: CreateBufferLightMapGL21( vbomesh_t *pOut, svert_t *arrayxvert ) +{ + static svert_v6_gl21_t arraysvert[MAXARRAYVERTS]; + + // convert to GLSL-compacted array + for( int i = 0; i < pOut->numVerts; i++ ) + { + arraysvert[i].vertex[0] = arrayxvert[i].vertex[0]; + arraysvert[i].vertex[1] = arrayxvert[i].vertex[1]; + arraysvert[i].vertex[2] = arrayxvert[i].vertex[2]; + arraysvert[i].stcoord0[0] = arrayxvert[i].stcoord[0]; + arraysvert[i].stcoord0[1] = arrayxvert[i].stcoord[1]; + arraysvert[i].stcoord0[2] = arrayxvert[i].stcoord[2]; + arraysvert[i].stcoord0[3] = arrayxvert[i].stcoord[3]; + arraysvert[i].lmcoord0[0] = arrayxvert[i].lmcoord0[0]; + arraysvert[i].lmcoord0[1] = arrayxvert[i].lmcoord0[1]; + arraysvert[i].lmcoord0[2] = arrayxvert[i].lmcoord0[2]; + arraysvert[i].lmcoord0[3] = arrayxvert[i].lmcoord0[3]; + arraysvert[i].lmcoord1[0] = arrayxvert[i].lmcoord1[0]; + arraysvert[i].lmcoord1[1] = arrayxvert[i].lmcoord1[1]; + arraysvert[i].lmcoord1[2] = arrayxvert[i].lmcoord1[2]; + arraysvert[i].lmcoord1[3] = arrayxvert[i].lmcoord1[3]; + arraysvert[i].normal[0] = arrayxvert[i].normal[0]; + arraysvert[i].normal[1] = arrayxvert[i].normal[1]; + arraysvert[i].normal[2] = arrayxvert[i].normal[2]; + arraysvert[i].styles[0] = arrayxvert[i].styles[0]; + arraysvert[i].styles[1] = arrayxvert[i].styles[1]; + arraysvert[i].styles[2] = arrayxvert[i].styles[2]; + arraysvert[i].styles[3] = arrayxvert[i].styles[3]; + } + + pOut->cacheSize = pOut->numVerts * sizeof( svert_v6_gl21_t ); + + pglGenBuffersARB( 1, &pOut->vbo ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, pOut->cacheSize, &arraysvert[0], GL_STATIC_DRAW_ARB ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); +} + +void CStudioModelRenderer :: BindBufferLightMapGL21( vbomesh_t *pOut, int attrFlags ) +{ + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION ); + pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v6_gl21_t ), (void *)offsetof( svert_v6_gl21_t, vertex )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD0 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD0, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v6_gl21_t ), (void *)offsetof( svert_v6_gl21_t, stcoord0 )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD1 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD1, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v6_gl21_t ), (void *)offsetof( svert_v6_gl21_t, lmcoord0 )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD2 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD2, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v6_gl21_t ), (void *)offsetof( svert_v6_gl21_t, lmcoord1 )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v6_gl21_t ), (void *)offsetof( svert_v6_gl21_t, normal )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_STYLES ); + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_STYLES, 4, GL_UNSIGNED_BYTE, 0, sizeof( svert_v6_gl21_t ), (void *)offsetof( svert_v6_gl21_t, styles )); +} + +void CStudioModelRenderer :: CreateBufferLightMapGL30( vbomesh_t *pOut, svert_t *arrayxvert ) +{ + static svert_v6_gl30_t arraysvert[MAXARRAYVERTS]; + + // convert to GLSL-compacted array + for( int i = 0; i < pOut->numVerts; i++ ) + { + arraysvert[i].vertex[0] = arrayxvert[i].vertex[0]; + arraysvert[i].vertex[1] = arrayxvert[i].vertex[1]; + arraysvert[i].vertex[2] = arrayxvert[i].vertex[2]; + arraysvert[i].stcoord0[0] = FloatToHalf( arrayxvert[i].stcoord[0] ); + arraysvert[i].stcoord0[1] = FloatToHalf( arrayxvert[i].stcoord[1] ); + arraysvert[i].stcoord0[2] = FloatToHalf( arrayxvert[i].stcoord[2] ); + arraysvert[i].stcoord0[3] = FloatToHalf( arrayxvert[i].stcoord[3] ); + arraysvert[i].lmcoord0[0] = arrayxvert[i].lmcoord0[0]; + arraysvert[i].lmcoord0[1] = arrayxvert[i].lmcoord0[1]; + arraysvert[i].lmcoord0[2] = arrayxvert[i].lmcoord0[2]; + arraysvert[i].lmcoord0[3] = arrayxvert[i].lmcoord0[3]; + arraysvert[i].lmcoord1[0] = arrayxvert[i].lmcoord1[0]; + arraysvert[i].lmcoord1[1] = arrayxvert[i].lmcoord1[1]; + arraysvert[i].lmcoord1[2] = arrayxvert[i].lmcoord1[2]; + arraysvert[i].lmcoord1[3] = arrayxvert[i].lmcoord1[3]; + CompressNormalizedVector( arraysvert[i].normal, arrayxvert[i].normal ); + arraysvert[i].styles[0] = arrayxvert[i].styles[0]; + arraysvert[i].styles[1] = arrayxvert[i].styles[1]; + arraysvert[i].styles[2] = arrayxvert[i].styles[2]; + arraysvert[i].styles[3] = arrayxvert[i].styles[3]; + } + + pOut->cacheSize = pOut->numVerts * sizeof( svert_v6_gl30_t ); + + pglGenBuffersARB( 1, &pOut->vbo ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, pOut->cacheSize, &arraysvert[0], GL_STATIC_DRAW_ARB ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); +} + +void CStudioModelRenderer :: BindBufferLightMapGL30( vbomesh_t *pOut, int attrFlags ) +{ + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION ); + pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v6_gl30_t ), (void *)offsetof( svert_v6_gl30_t, vertex )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD0 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD0, 4, GL_HALF_FLOAT_ARB, GL_FALSE, sizeof( svert_v6_gl30_t ), (void *)offsetof( svert_v6_gl30_t, stcoord0 )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD1 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD1, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v6_gl30_t ), (void *)offsetof( svert_v6_gl30_t, lmcoord0 )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD2 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD2, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v6_gl30_t ), (void *)offsetof( svert_v6_gl30_t, lmcoord1 )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 3, GL_BYTE, GL_FALSE, sizeof( svert_v6_gl30_t ), (void *)offsetof( svert_v6_gl30_t, normal )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_STYLES ); + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_STYLES, 4, GL_UNSIGNED_BYTE, 0, sizeof( svert_v6_gl30_t ), (void *)offsetof( svert_v6_gl30_t, styles )); +} + +void CStudioModelRenderer :: CreateBufferLightMapBumpGL21( vbomesh_t *pOut, svert_t *arrayxvert ) +{ + static svert_v7_gl21_t arraysvert[MAXARRAYVERTS]; + + // convert to GLSL-compacted array + for( int i = 0; i < pOut->numVerts; i++ ) + { + arraysvert[i].vertex[0] = arrayxvert[i].vertex[0]; + arraysvert[i].vertex[1] = arrayxvert[i].vertex[1]; + arraysvert[i].vertex[2] = arrayxvert[i].vertex[2]; + arraysvert[i].stcoord0[0] = arrayxvert[i].stcoord[0]; + arraysvert[i].stcoord0[1] = arrayxvert[i].stcoord[1]; + arraysvert[i].stcoord0[2] = arrayxvert[i].stcoord[2]; + arraysvert[i].stcoord0[3] = arrayxvert[i].stcoord[3]; + arraysvert[i].lmcoord0[0] = arrayxvert[i].lmcoord0[0]; + arraysvert[i].lmcoord0[1] = arrayxvert[i].lmcoord0[1]; + arraysvert[i].lmcoord0[2] = arrayxvert[i].lmcoord0[2]; + arraysvert[i].lmcoord0[3] = arrayxvert[i].lmcoord0[3]; + arraysvert[i].lmcoord1[0] = arrayxvert[i].lmcoord1[0]; + arraysvert[i].lmcoord1[1] = arrayxvert[i].lmcoord1[1]; + arraysvert[i].lmcoord1[2] = arrayxvert[i].lmcoord1[2]; + arraysvert[i].lmcoord1[3] = arrayxvert[i].lmcoord1[3]; + arraysvert[i].tangent[0] = arrayxvert[i].tangent[0]; + arraysvert[i].tangent[1] = arrayxvert[i].tangent[1]; + arraysvert[i].tangent[2] = arrayxvert[i].tangent[2]; + arraysvert[i].binormal[0] = arrayxvert[i].binormal[0]; + arraysvert[i].binormal[1] = arrayxvert[i].binormal[1]; + arraysvert[i].binormal[2] = arrayxvert[i].binormal[2]; + arraysvert[i].normal[0] = arrayxvert[i].normal[0]; + arraysvert[i].normal[1] = arrayxvert[i].normal[1]; + arraysvert[i].normal[2] = arrayxvert[i].normal[2]; + arraysvert[i].styles[0] = arrayxvert[i].styles[0]; + arraysvert[i].styles[1] = arrayxvert[i].styles[1]; + arraysvert[i].styles[2] = arrayxvert[i].styles[2]; + arraysvert[i].styles[3] = arrayxvert[i].styles[3]; + } + + pOut->cacheSize = pOut->numVerts * sizeof( svert_v7_gl21_t ); + + pglGenBuffersARB( 1, &pOut->vbo ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, pOut->cacheSize, &arraysvert[0], GL_STATIC_DRAW_ARB ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); +} + +void CStudioModelRenderer :: BindBufferLightMapBumpGL21( vbomesh_t *pOut, int attrFlags ) +{ + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION ); + pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v7_gl21_t ), (void *)offsetof( svert_v7_gl21_t, vertex )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD0 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD0, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v7_gl21_t ), (void *)offsetof( svert_v7_gl21_t, stcoord0 )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD1 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD1, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v7_gl21_t ), (void *)offsetof( svert_v7_gl21_t, lmcoord0 )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD2 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD2, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v7_gl21_t ), (void *)offsetof( svert_v7_gl21_t, lmcoord1 )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v7_gl21_t ), (void *)offsetof( svert_v7_gl21_t, normal )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_BINORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_BINORMAL, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v7_gl21_t ), (void *)offsetof( svert_v7_gl21_t, binormal )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TANGENT ); + pglVertexAttribPointerARB( ATTR_INDEX_TANGENT, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v7_gl21_t ), (void *)offsetof( svert_v7_gl21_t, tangent )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_STYLES ); + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_STYLES, 4, GL_UNSIGNED_BYTE, 0, sizeof( svert_v7_gl21_t ), (void *)offsetof( svert_v7_gl21_t, styles )); +} + +void CStudioModelRenderer :: CreateBufferLightMapBumpGL30( vbomesh_t *pOut, svert_t *arrayxvert ) +{ + static svert_v7_gl30_t arraysvert[MAXARRAYVERTS]; + + // convert to GLSL-compacted array + for( int i = 0; i < pOut->numVerts; i++ ) + { + arraysvert[i].vertex[0] = arrayxvert[i].vertex[0]; + arraysvert[i].vertex[1] = arrayxvert[i].vertex[1]; + arraysvert[i].vertex[2] = arrayxvert[i].vertex[2]; + arraysvert[i].stcoord0[0] = FloatToHalf( arrayxvert[i].stcoord[0] ); + arraysvert[i].stcoord0[1] = FloatToHalf( arrayxvert[i].stcoord[1] ); + arraysvert[i].stcoord0[2] = FloatToHalf( arrayxvert[i].stcoord[2] ); + arraysvert[i].stcoord0[3] = FloatToHalf( arrayxvert[i].stcoord[3] ); + arraysvert[i].lmcoord0[0] = arrayxvert[i].lmcoord0[0]; + arraysvert[i].lmcoord0[1] = arrayxvert[i].lmcoord0[1]; + arraysvert[i].lmcoord0[2] = arrayxvert[i].lmcoord0[2]; + arraysvert[i].lmcoord0[3] = arrayxvert[i].lmcoord0[3]; + arraysvert[i].lmcoord1[0] = arrayxvert[i].lmcoord1[0]; + arraysvert[i].lmcoord1[1] = arrayxvert[i].lmcoord1[1]; + arraysvert[i].lmcoord1[2] = arrayxvert[i].lmcoord1[2]; + arraysvert[i].lmcoord1[3] = arrayxvert[i].lmcoord1[3]; + CompressNormalizedVector( arraysvert[i].normal, arrayxvert[i].normal ); + CompressNormalizedVector( arraysvert[i].tangent, arrayxvert[i].tangent ); + CompressNormalizedVector( arraysvert[i].binormal, arrayxvert[i].binormal ); + arraysvert[i].styles[0] = arrayxvert[i].styles[0]; + arraysvert[i].styles[1] = arrayxvert[i].styles[1]; + arraysvert[i].styles[2] = arrayxvert[i].styles[2]; + arraysvert[i].styles[3] = arrayxvert[i].styles[3]; + } + + pOut->cacheSize = pOut->numVerts * sizeof( svert_v7_gl30_t ); + + pglGenBuffersARB( 1, &pOut->vbo ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, pOut->cacheSize, &arraysvert[0], GL_STATIC_DRAW_ARB ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); +} + +void CStudioModelRenderer :: BindBufferLightMapBumpGL30( vbomesh_t *pOut, int attrFlags ) +{ + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION ); + pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v7_gl30_t ), (void *)offsetof( svert_v7_gl30_t, vertex )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD0 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD0, 4, GL_HALF_FLOAT_ARB, GL_FALSE, sizeof( svert_v7_gl30_t ), (void *)offsetof( svert_v7_gl30_t, stcoord0 )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD1 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD1, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v7_gl30_t ), (void *)offsetof( svert_v7_gl30_t, lmcoord0 )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD2 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD2, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v7_gl30_t ), (void *)offsetof( svert_v7_gl30_t, lmcoord1 )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 3, GL_BYTE, GL_FALSE, sizeof( svert_v7_gl30_t ), (void *)offsetof( svert_v7_gl30_t, normal )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_BINORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_BINORMAL, 3, GL_BYTE, GL_FALSE, sizeof( svert_v7_gl30_t ), (void *)offsetof( svert_v7_gl30_t, binormal )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_TANGENT ); + pglVertexAttribPointerARB( ATTR_INDEX_TANGENT, 3, GL_BYTE, GL_FALSE, sizeof( svert_v7_gl30_t ), (void *)offsetof( svert_v7_gl30_t, tangent )); + + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_STYLES ); + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_STYLES, 4, GL_UNSIGNED_BYTE, 0, sizeof( svert_v7_gl30_t ), (void *)offsetof( svert_v7_gl30_t, styles )); +} + +void CStudioModelRenderer :: CreateBufferGenericGL21( vbomesh_t *pOut, svert_t *arrayxvert ) +{ + static svert_v8_gl21_t arraysvert[MAXARRAYVERTS]; + + // convert to GLSL-compacted array + for( int i = 0; i < pOut->numVerts; i++ ) + { + arraysvert[i].vertex[0] = arrayxvert[i].vertex[0]; + arraysvert[i].vertex[1] = arrayxvert[i].vertex[1]; + arraysvert[i].vertex[2] = arrayxvert[i].vertex[2]; + arraysvert[i].stcoord0[0] = arrayxvert[i].stcoord[0]; + arraysvert[i].stcoord0[1] = arrayxvert[i].stcoord[1]; + arraysvert[i].stcoord0[2] = arrayxvert[i].stcoord[2]; + arraysvert[i].stcoord0[3] = arrayxvert[i].stcoord[3]; + arraysvert[i].lmcoord0[0] = arrayxvert[i].lmcoord0[0]; + arraysvert[i].lmcoord0[1] = arrayxvert[i].lmcoord0[1]; + arraysvert[i].lmcoord0[2] = arrayxvert[i].lmcoord0[2]; + arraysvert[i].lmcoord0[3] = arrayxvert[i].lmcoord0[3]; + arraysvert[i].lmcoord1[0] = arrayxvert[i].lmcoord1[0]; + arraysvert[i].lmcoord1[1] = arrayxvert[i].lmcoord1[1]; + arraysvert[i].lmcoord1[2] = arrayxvert[i].lmcoord1[2]; + arraysvert[i].lmcoord1[3] = arrayxvert[i].lmcoord1[3]; + arraysvert[i].tangent[0] = arrayxvert[i].tangent[0]; + arraysvert[i].tangent[1] = arrayxvert[i].tangent[1]; + arraysvert[i].tangent[2] = arrayxvert[i].tangent[2]; + arraysvert[i].binormal[0] = arrayxvert[i].binormal[0]; + arraysvert[i].binormal[1] = arrayxvert[i].binormal[1]; + arraysvert[i].binormal[2] = arrayxvert[i].binormal[2]; + arraysvert[i].normal[0] = arrayxvert[i].normal[0]; + arraysvert[i].normal[1] = arrayxvert[i].normal[1]; + arraysvert[i].normal[2] = arrayxvert[i].normal[2]; + arraysvert[i].boneid[0] = arrayxvert[i].boneid[0]; + arraysvert[i].boneid[1] = arrayxvert[i].boneid[1]; + arraysvert[i].boneid[2] = arrayxvert[i].boneid[2]; + arraysvert[i].boneid[3] = arrayxvert[i].boneid[3]; + arraysvert[i].weight[0] = arrayxvert[i].weight[0]; + arraysvert[i].weight[1] = arrayxvert[i].weight[1]; + arraysvert[i].weight[2] = arrayxvert[i].weight[2]; + arraysvert[i].weight[3] = arrayxvert[i].weight[3]; + arraysvert[i].light[0] = arrayxvert[i].light[0]; + arraysvert[i].light[1] = arrayxvert[i].light[1]; + arraysvert[i].light[2] = arrayxvert[i].light[2]; + arraysvert[i].light[3] = arrayxvert[i].light[3]; + arraysvert[i].deluxe[0] = arrayxvert[i].deluxe[0]; + arraysvert[i].deluxe[1] = arrayxvert[i].deluxe[1]; + arraysvert[i].deluxe[2] = arrayxvert[i].deluxe[2]; + arraysvert[i].deluxe[3] = arrayxvert[i].deluxe[3]; + arraysvert[i].styles[0] = arrayxvert[i].styles[0]; + arraysvert[i].styles[1] = arrayxvert[i].styles[1]; + arraysvert[i].styles[2] = arrayxvert[i].styles[2]; + arraysvert[i].styles[3] = arrayxvert[i].styles[3]; + } + + pOut->cacheSize = pOut->numVerts * sizeof( svert_v8_gl21_t ); + + pglGenBuffersARB( 1, &pOut->vbo ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, pOut->cacheSize, &arraysvert[0], GL_STATIC_DRAW_ARB ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); +} + +void CStudioModelRenderer :: BindBufferGenericGL21( vbomesh_t *pOut, int attrFlags ) +{ + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + + if( FBitSet( attrFlags, FATTR_POSITION )) + { + pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION ); + pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v8_gl21_t ), (void *)offsetof( svert_v8_gl21_t, vertex )); + } + + if( FBitSet( attrFlags, FATTR_TEXCOORD0 )) + { + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD0 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD0, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v8_gl21_t ), (void *)offsetof( svert_v8_gl21_t, stcoord0 )); + } + + if( FBitSet( attrFlags, FATTR_TEXCOORD1 )) + { + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD1 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD1, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v8_gl21_t ), (void *)offsetof( svert_v8_gl21_t, lmcoord0 )); + } + + if( FBitSet( attrFlags, FATTR_TEXCOORD2 )) + { + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD2 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD2, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v8_gl21_t ), (void *)offsetof( svert_v8_gl21_t, lmcoord1 )); + } + + if( FBitSet( attrFlags, FATTR_NORMAL )) + { + pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v8_gl21_t ), (void *)offsetof( svert_v8_gl21_t, normal )); + } + + if( FBitSet( attrFlags, FATTR_BINORMAL )) + { + pglEnableVertexAttribArrayARB( ATTR_INDEX_BINORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_BINORMAL, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v8_gl21_t ), (void *)offsetof( svert_v8_gl21_t, binormal )); + } + + if( FBitSet( attrFlags, FATTR_TANGENT )) + { + pglEnableVertexAttribArrayARB( ATTR_INDEX_TANGENT ); + pglVertexAttribPointerARB( ATTR_INDEX_TANGENT, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v8_gl21_t ), (void *)offsetof( svert_v8_gl21_t, tangent )); + } + + if( FBitSet( attrFlags, FATTR_BONE_INDEXES )) + { + pglEnableVertexAttribArrayARB( ATTR_INDEX_BONE_INDEXES ); + pglVertexAttribPointerARB( ATTR_INDEX_BONE_INDEXES, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v8_gl21_t ), (void *)offsetof( svert_v8_gl21_t, boneid )); + } + + if( FBitSet( attrFlags, FATTR_BONE_WEIGHTS )) + { + pglEnableVertexAttribArrayARB( ATTR_INDEX_BONE_WEIGHTS ); + pglVertexAttribPointerARB( ATTR_INDEX_BONE_WEIGHTS, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v8_gl21_t ), (void *)offsetof( svert_v8_gl21_t, weight )); + } + + if( FBitSet( attrFlags, FATTR_LIGHT_COLOR )) + { + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_COLOR ); + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_COLOR, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v8_gl21_t ), (void *)offsetof( svert_v8_gl21_t, light )); + } + + if( FBitSet( attrFlags, FATTR_LIGHT_VECS )) + { + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_VECS ); + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_VECS, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v8_gl21_t ), (void *)offsetof( svert_v8_gl21_t, deluxe )); + } + + if( FBitSet( attrFlags, FATTR_LIGHT_STYLES )) + { + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_STYLES ); + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_STYLES, 4, GL_UNSIGNED_BYTE, 0, sizeof( svert_v8_gl21_t ), (void *)offsetof( svert_v8_gl21_t, styles )); + } +} + +void CStudioModelRenderer :: CreateBufferGenericGL30( vbomesh_t *pOut, svert_t *arrayxvert ) +{ + static svert_v8_gl30_t arraysvert[MAXARRAYVERTS]; + + // convert to GLSL-compacted array + for( int i = 0; i < pOut->numVerts; i++ ) + { + arraysvert[i].vertex[0] = arrayxvert[i].vertex[0]; + arraysvert[i].vertex[1] = arrayxvert[i].vertex[1]; + arraysvert[i].vertex[2] = arrayxvert[i].vertex[2]; + arraysvert[i].stcoord0[0] = FloatToHalf( arrayxvert[i].stcoord[0] ); + arraysvert[i].stcoord0[1] = FloatToHalf( arrayxvert[i].stcoord[1] ); + arraysvert[i].stcoord0[2] = FloatToHalf( arrayxvert[i].stcoord[2] ); + arraysvert[i].stcoord0[3] = FloatToHalf( arrayxvert[i].stcoord[3] ); + arraysvert[i].lmcoord0[0] = arrayxvert[i].lmcoord0[0]; + arraysvert[i].lmcoord0[1] = arrayxvert[i].lmcoord0[1]; + arraysvert[i].lmcoord0[2] = arrayxvert[i].lmcoord0[2]; + arraysvert[i].lmcoord0[3] = arrayxvert[i].lmcoord0[3]; + arraysvert[i].lmcoord1[0] = arrayxvert[i].lmcoord1[0]; + arraysvert[i].lmcoord1[1] = arrayxvert[i].lmcoord1[1]; + arraysvert[i].lmcoord1[2] = arrayxvert[i].lmcoord1[2]; + arraysvert[i].lmcoord1[3] = arrayxvert[i].lmcoord1[3]; + CompressNormalizedVector( arraysvert[i].normal, arrayxvert[i].normal ); + CompressNormalizedVector( arraysvert[i].tangent, arrayxvert[i].tangent ); + CompressNormalizedVector( arraysvert[i].binormal, arrayxvert[i].binormal ); + arraysvert[i].boneid[0] = arrayxvert[i].boneid[0]; + arraysvert[i].boneid[1] = arrayxvert[i].boneid[1]; + arraysvert[i].boneid[2] = arrayxvert[i].boneid[2]; + arraysvert[i].boneid[3] = arrayxvert[i].boneid[3]; + arraysvert[i].weight[0] = arrayxvert[i].weight[0]; + arraysvert[i].weight[1] = arrayxvert[i].weight[1]; + arraysvert[i].weight[2] = arrayxvert[i].weight[2]; + arraysvert[i].weight[3] = arrayxvert[i].weight[3]; + arraysvert[i].light[0] = arrayxvert[i].light[0]; + arraysvert[i].light[1] = arrayxvert[i].light[1]; + arraysvert[i].light[2] = arrayxvert[i].light[2]; + arraysvert[i].light[3] = arrayxvert[i].light[3]; + arraysvert[i].deluxe[0] = arrayxvert[i].deluxe[0]; + arraysvert[i].deluxe[1] = arrayxvert[i].deluxe[1]; + arraysvert[i].deluxe[2] = arrayxvert[i].deluxe[2]; + arraysvert[i].deluxe[3] = arrayxvert[i].deluxe[3]; + arraysvert[i].styles[0] = arrayxvert[i].styles[0]; + arraysvert[i].styles[1] = arrayxvert[i].styles[1]; + arraysvert[i].styles[2] = arrayxvert[i].styles[2]; + arraysvert[i].styles[3] = arrayxvert[i].styles[3]; + } + + pOut->cacheSize = pOut->numVerts * sizeof( svert_v8_gl30_t ); + + pglGenBuffersARB( 1, &pOut->vbo ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, pOut->cacheSize, &arraysvert[0], GL_STATIC_DRAW_ARB ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); +} + +void CStudioModelRenderer :: BindBufferGenericGL30( vbomesh_t *pOut, int attrFlags ) +{ + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, pOut->vbo ); + + if( FBitSet( attrFlags, FATTR_POSITION )) + { + pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION ); + pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 3, GL_FLOAT, GL_FALSE, sizeof( svert_v8_gl30_t ), (void *)offsetof( svert_v8_gl30_t, vertex )); + } + + if( FBitSet( attrFlags, FATTR_TEXCOORD0 )) + { + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD0 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD0, 4, GL_HALF_FLOAT_ARB, GL_FALSE, sizeof( svert_v8_gl30_t ), (void *)offsetof( svert_v8_gl30_t, stcoord0 )); + } + + if( FBitSet( attrFlags, FATTR_TEXCOORD1 )) + { + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD1 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD1, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v8_gl30_t ), (void *)offsetof( svert_v8_gl30_t, lmcoord0 )); + } + + if( FBitSet( attrFlags, FATTR_TEXCOORD2 )) + { + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD2 ); + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD2, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v8_gl30_t ), (void *)offsetof( svert_v8_gl30_t, lmcoord1 )); + } + + if( FBitSet( attrFlags, FATTR_NORMAL )) + { + pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 3, GL_BYTE, GL_FALSE, sizeof( svert_v8_gl30_t ), (void *)offsetof( svert_v8_gl30_t, normal )); + } + + if( FBitSet( attrFlags, FATTR_BINORMAL )) + { + pglEnableVertexAttribArrayARB( ATTR_INDEX_BINORMAL ); + pglVertexAttribPointerARB( ATTR_INDEX_BINORMAL, 3, GL_BYTE, GL_FALSE, sizeof( svert_v8_gl30_t ), (void *)offsetof( svert_v8_gl30_t, binormal )); + } + + if( FBitSet( attrFlags, FATTR_TANGENT )) + { + pglEnableVertexAttribArrayARB( ATTR_INDEX_TANGENT ); + pglVertexAttribPointerARB( ATTR_INDEX_TANGENT, 3, GL_BYTE, GL_FALSE, sizeof( svert_v8_gl30_t ), (void *)offsetof( svert_v8_gl30_t, tangent )); + } + + if( FBitSet( attrFlags, FATTR_BONE_INDEXES )) + { + pglEnableVertexAttribArrayARB( ATTR_INDEX_BONE_INDEXES ); + pglVertexAttribPointerARB( ATTR_INDEX_BONE_INDEXES, 4, GL_BYTE, GL_FALSE, sizeof( svert_v8_gl30_t ), (void *)offsetof( svert_v8_gl30_t, boneid )); + } + + if( FBitSet( attrFlags, FATTR_BONE_WEIGHTS )) + { + pglEnableVertexAttribArrayARB( ATTR_INDEX_BONE_WEIGHTS ); + pglVertexAttribPointerARB( ATTR_INDEX_BONE_WEIGHTS, 4, GL_UNSIGNED_BYTE, GL_FALSE, sizeof( svert_v8_gl30_t ), (void *)offsetof( svert_v8_gl30_t, weight )); + } + + if( FBitSet( attrFlags, FATTR_LIGHT_COLOR )) + { + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_COLOR ); + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_COLOR, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v8_gl30_t ), (void *)offsetof( svert_v8_gl30_t, light )); + } + + if( FBitSet( attrFlags, FATTR_LIGHT_VECS )) + { + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_VECS ); + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_VECS, 4, GL_FLOAT, GL_FALSE, sizeof( svert_v8_gl30_t ), (void *)offsetof( svert_v8_gl30_t, deluxe )); + } + + if( FBitSet( attrFlags, FATTR_LIGHT_STYLES )) + { + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_STYLES ); + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_STYLES, 4, GL_UNSIGNED_BYTE, 0, sizeof( svert_v8_gl30_t ), (void *)offsetof( svert_v8_gl30_t, styles )); + } +} + +void CStudioModelRenderer :: CreateIndexBuffer( vbomesh_t *pOut, unsigned int *arrayelems ) +{ + uint cacheSize = pOut->numElems * sizeof( unsigned int ); + + pglGenBuffersARB( 1, &pOut->ibo ); + pglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, pOut->ibo ); + pglBufferDataARB( GL_ELEMENT_ARRAY_BUFFER_ARB, cacheSize, &arrayelems[0], GL_STATIC_DRAW_ARB ); + pglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 ); + + // update total buffer size + pOut->cacheSize += cacheSize; +} + +void CStudioModelRenderer :: BindIndexBuffer( vbomesh_t *pOut ) +{ + pglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, pOut->ibo ); +} + +unsigned int CStudioModelRenderer :: ComputeAttribFlags( int numbones, bool has_bumpmap, bool has_boneweights, bool has_vertexlight, bool has_lightmap ) +{ + unsigned int defaultAttribs = (FATTR_POSITION|FATTR_TEXCOORD0|FATTR_NORMAL|FATTR_BONE_INDEXES); + + if( has_boneweights ) + SetBits( defaultAttribs, FATTR_BONE_WEIGHTS ); + + if( has_bumpmap ) + SetBits( defaultAttribs, FATTR_BINORMAL|FATTR_TANGENT ); + + if( has_vertexlight ) + { + if( has_bumpmap ) + SetBits( defaultAttribs, FATTR_LIGHT_VECS ); + SetBits( defaultAttribs, FATTR_LIGHT_COLOR ); + } + + if( has_lightmap ) + SetBits( defaultAttribs, FATTR_TEXCOORD1|FATTR_TEXCOORD2|FATTR_LIGHT_STYLES ); + + return defaultAttribs; +} + +unsigned int CStudioModelRenderer :: SelectMeshLoader( int numbones, bool has_bumpmap, bool has_boneweights, bool has_vertexlight, bool has_lightmap ) +{ + if( numbones <= 1 && has_lightmap ) + { + // special case for single bone vertex lighting + if( has_bumpmap ) + return MESHLOADER_LIGHTMAPBUMP; + return MESHLOADER_LIGHTMAP; + } + else if( numbones <= 1 && has_vertexlight ) + { + // special case for single bone vertex lighting + if( has_bumpmap ) + return MESHLOADER_VLIGHTBUMP; + return MESHLOADER_VLIGHT; + } + else if( !has_boneweights && !has_vertexlight && !has_lightmap ) + { + // typical GoldSrc models without weights + if( has_bumpmap ) + return MESHLOADER_BASEBUMP; + return MESHLOADER_BASE; + } + else if( has_boneweights && !has_vertexlight && !has_lightmap ) + { + // extended Xash3D models with boneweights + if( has_bumpmap ) + return MESHLOADER_WEIGHTBUMP; + return MESHLOADER_WEIGHT; + } + else + { + if( numbones > 1 && ( has_vertexlight || has_lightmap )) + ALERT( at_aiconsole, "%s static model have skeleton\n", RI->currentmodel->name ); + return MESHLOADER_GENERIC; // all other cases + } +} \ No newline at end of file diff --git a/cl_dll/render/gl_subview.cpp b/cl_dll/render/gl_subview.cpp new file mode 100644 index 0000000..90ed9ca --- /dev/null +++ b/cl_dll/render/gl_subview.cpp @@ -0,0 +1,352 @@ +/* +gl_mirror.cpp - draw reflected surfaces +Copyright (C) 2011 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include "cl_util.h" +#include "gl_local.h" +#include "gl_world.h" +#include "mathlib.h" +#include +#include "gl_occlusion.h" + +#define MIRROR_PLANE_EPSILON 0.1f + +/* +============================================================= + + MIRROR RENDERING + +============================================================= +*/ +/* +================ +R_SetupMirrorView + +Prepare view for mirroring +================ +*/ +int R_SetupMirrorView( msurface_t *surf, ref_viewpass_t *rvp ) +{ + cl_entity_t *ent = surf->info->parent; + matrix3x3 matAngles; + mplane_t plane; + gl_state_t *glm; + float d; + + // setup mirror plane + if( FBitSet( surf->flags, SURF_PLANEBACK )) + SetPlane( &plane, -surf->plane->normal, -surf->plane->dist ); + else SetPlane( &plane, surf->plane->normal, surf->plane->dist ); + + glm = GL_GetCache( ent->hCachedMatrix ); + + if( ent->hCachedMatrix != WORLD_MATRIX ) + glm->transform.TransformPositivePlane( plane, plane ); + + // reflect view by mirror plane + d = -2.0f * ( DotProduct( GetVieworg(), plane.normal ) - plane.dist ); + Vector origin = GetVieworg() + d * plane.normal; + + d = -2.0f * DotProduct( GetVForward(), plane.normal ); + Vector forward = ( GetVForward() + d * plane.normal ).Normalize(); + + d = -2.0f * DotProduct( GetVRight(), plane.normal ); + Vector right = ( GetVRight() + d * plane.normal ).Normalize(); + + d = -2.0f * DotProduct( GetVUp(), plane.normal ); + Vector up = ( GetVUp() + d * plane.normal ).Normalize(); + + // compute mirror angles + matAngles.SetForward( forward ); + matAngles.SetRight( right ); + matAngles.SetUp( up ); + Vector angles = matAngles.GetAngles(); + + plane.dist += ON_EPSILON; // to prevent z-fighting with reflective water + + rvp->flags = RP_MIRRORVIEW|RP_CLIPPLANE|RP_MERGE_PVS; + RI->view.frustum.SetPlane( FRUSTUM_NEAR, plane.normal, plane.dist ); + RI->clipPlane = plane; + + rvp->viewangles[0] = anglemod( angles[0] ); + rvp->viewangles[1] = anglemod( angles[1] ); + rvp->viewangles[2] = anglemod( angles[2] ); + rvp->vieworigin = origin; + + rvp->fov_x = RI->view.fov_x; + rvp->fov_y = RI->view.fov_y; + + // put pvsorigin before the mirror plane to avoid get recursion with himself + if( ent == GET_ENTITY( 0 )) origin = surf->info->origin + (1.0f * plane.normal); + else origin = glm->transform.VectorTransform( surf->info->origin ) + (1.0f * plane.normal); + material_t *mat = R_TextureAnimation( surf )->material; + + RI->view.pvspoint = origin; + rvp->viewport[0] = rvp->viewport[1] = 0; + + if( FBitSet( surf->flags, SURF_REFLECT_PUDDLE )) + { + rvp->viewport[2] = 192; + rvp->viewport[3] = 192; + } + else + { + rvp->viewport[2] = RI->view.port[2]; + rvp->viewport[3] = RI->view.port[3]; + } + + if( GL_Support( R_ARB_TEXTURE_NPOT_EXT )) + { + // allow screen size +// rvp->viewport[2] = bound( 96, rvp->viewport[2], 1024 ); +// rvp->viewport[3] = bound( 72, rvp->viewport[3], 768 ); + } + else + { + rvp->viewport[2] = NearestPOW( rvp->viewport[2], true ); + rvp->viewport[3] = NearestPOW( rvp->viewport[3], true ); +// rvp->viewport[2] = bound( 128, rvp->viewport[2], 1024 ); +// rvp->viewport[3] = bound( 64, rvp->viewport[3], 512 ); + } + + if( FBitSet( mat->flags, BRUSH_LIQUID )) + SetBits( rvp->flags, RP_WATERPASS ); + else if( FBitSet( surf->flags, SURF_REFLECT_PUDDLE )) + SetBits( rvp->flags, RP_NOSHADOWS|RP_WATERPASS|RP_NOGRASS ); // don't draw grass from puddles + + return TF_SCREEN; +} + +/* +================ +R_AllocateSubviewTexture + +Allocate the screen texture and make copy +================ +*/ +int R_AllocateSubviewTexture( int viewport[4], int texFlags ) +{ + int i; + + // first, search for available mirror texture + for( i = 0; i < tr.num_subview_used; i++ ) + { + if( tr.subviewTextures[i].texframe == tr.realframecount ) + continue; // already used for this frame + + if( viewport[2] != RENDER_GET_PARM( PARM_TEX_WIDTH, tr.subviewTextures[i].texturenum )) + continue; // width mismatched + + if( viewport[3] != RENDER_GET_PARM( PARM_TEX_HEIGHT, tr.subviewTextures[i].texturenum )) + continue; // height mismatched + + // screens don't want textures with 'clamp' modifier + if( FBitSet( texFlags, TF_CLAMP ) != FBitSet( RENDER_GET_PARM( PARM_TEX_FLAGS, tr.subviewTextures[i].texturenum ), TF_CLAMP )) + continue; // mismatch texture flags + + // found a valid spot + tr.subviewTextures[i].texframe = tr.realframecount; // now used + break; + } + + if( i == tr.num_subview_used ) + { + if( i == MAX_SUBVIEW_TEXTURES ) + { + ALERT( at_error, "R_AllocSubviewTexture: texture limit exceeded (per frame)!\n" ); + return 0; // disable + } + + // create new mirror texture + tr.subviewTextures[i].texturenum = CREATE_TEXTURE( va( "*subview%i", i ), viewport[2], viewport[3], NULL, texFlags ); + tr.subviewTextures[i].texframe = tr.realframecount; // now used + if( GL_Support( R_FRAMEBUFFER_OBJECT )) + tr.subviewTextures[i].framebuffer = R_AllocFrameBuffer( viewport ); + tr.num_subview_used++; // allocate new one + } + + if( GL_Support( R_FRAMEBUFFER_OBJECT )) + { + GL_BindFrameBuffer( tr.subviewTextures[i].framebuffer, tr.subviewTextures[i].texturenum ); + } + else + { + GL_BindTexture( GL_TEXTURE0, tr.subviewTextures[i].texturenum ); + pglCopyTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, viewport[0], viewport[1], viewport[2], viewport[3], 0 ); + } + + return (i+1); +} + +bool R_CheckMirrorClone( msurface_t *surf, msurface_t *check ) +{ + if( !FBitSet( surf->flags, SURF_REFLECT|SURF_REFLECT_PUDDLE )) + return false; + + if( !FBitSet( check->flags, SURF_REFLECT|SURF_REFLECT_PUDDLE )) + return false; + + if( !check->info->subtexture[glState.stack_position-1] ) + return false; + + if( surf->info->parent != check->info->parent ) + return false; + + if( FBitSet( surf->flags, SURF_PLANEBACK ) != FBitSet( check->flags, SURF_PLANEBACK )) + return false; + + if( surf->plane->normal != check->plane->normal ) + return false; + + if( Q_sign( surf->plane->dist ) != Q_sign( check->plane->dist )) + return false; + + if( fabs( surf->plane->dist - check->plane->dist ) > MIRROR_PLANE_EPSILON ) + return false; + + // just reuse the handle + surf->info->subtexture[glState.stack_position-1] = check->info->subtexture[glState.stack_position-1]; + + return true; +} + +bool R_CanSkipPass( int end, msurface_t *surf, ref_instance_t *prevRI ) +{ + // IMPORTANT: limit the recursion depth + if( surf == prevRI->reject_face ) + return true; + + for( int i = 0; i < end; i++ ) + { + if( R_CheckMirrorClone( surf, prevRI->frame.subview_faces[i] )) + return true; + } + + // occluded by query + if( FBitSet( surf->flags, SURF_OCCLUDED )) + { + // don't use me + surf->info->subtexture[glState.stack_position-1] = 0; + return true; + } + + return false; +} + +/* +================ +R_CheckOutside + +check if main view is outside level +================ +*/ +bool R_CheckOutside( void ) +{ + // only main view should be tested + if( !RP_NORMALPASS( )) + return false; + + mleaf_t *leaf = Mod_PointInLeaf( RI->view.origin, worldmodel->nodes ); + + if( RP_OUTSIDE( leaf )) + return true; + return false; +} + +/* +================ +R_RenderSubview + +Draw scene from another points +e.g. for planar reflection, +remote cameras etc +================ +*/ +void R_RenderSubview( void ) +{ + ref_instance_t *prevRI; + unsigned int oldFBO; + ref_viewpass_t rvp; + + // player is outside world. Don't draw subview for speedup reasons + if( R_CheckOutside( )) + return; + + if( glState.stack_position > (unsigned int)r_recursion_depth->value ) + return; // too deep... + + if( FBitSet( RI->params, RP_DRAW_OVERVIEW )) + return; + + if( !RI->frame.num_subview_faces ) + return; // nothing to render + + R_PushRefState(); // make refinst backup + prevRI = R_GetPrevInstance(); + + // draw the subviews through the list + for( int i = 0; i < prevRI->frame.num_subview_faces; i++ ) + { + msurface_t *surf = prevRI->frame.subview_faces[i]; + cl_entity_t *e = RI->currententity = surf->info->parent; + mextrasurf_t *es = surf->info; + int texFlags = 0, subview = 0; + RI->currentmodel = e->model; + + if( R_CanSkipPass( i, surf, prevRI )) + continue; + + ASSERT( RI->currententity != NULL ); + ASSERT( RI->currentmodel != NULL ); + + // NOTE: we can do additionaly culling here by PVS + if( !e || e->curstate.messagenum != r_currentMessageNum ) + continue; // bad camera, ignore + + // setup view apropriate by type + if( FBitSet( surf->flags, SURF_REFLECT|SURF_REFLECT_PUDDLE )) + { + texFlags = R_SetupMirrorView( surf, &rvp ); + r_stats.c_subview_passes++; + } + else continue; // ??? + + oldFBO = glState.frameBuffer; + + if( GL_Support( R_FRAMEBUFFER_OBJECT )) + { + if(( subview = R_AllocateSubviewTexture( rvp.viewport, texFlags )) == 0 ) + continue; + } + + // reset the subinfo + surf->info->subtexture[glState.stack_position-1] = 0; + RI->reject_face = surf; + R_RenderScene( &rvp, rvp.flags ); + RI->reject_face = NULL; + + if( !GL_Support( R_FRAMEBUFFER_OBJECT )) + subview = R_AllocateSubviewTexture( rvp.viewport, texFlags ); + + ASSERT( subview > 0 && subview < MAX_SUBVIEW_TEXTURES ); + surf->info->subtexture[glState.stack_position-1] = subview; // now it's valid + tr.subviewTextures[subview-1].matrix = RI->view.worldProjectionMatrix; + + GL_BindFBO( oldFBO ); + R_ResetRefState(); + } + + R_PopRefState(); // restore ref instance +} \ No newline at end of file diff --git a/cl_dll/render/gl_world.h b/cl_dll/render/gl_world.h new file mode 100644 index 0000000..b05b451 --- /dev/null +++ b/cl_dll/render/gl_world.h @@ -0,0 +1,183 @@ +/* +gl_world.h - local world data for rendering +this code written for Paranoia 2: Savior modification +Copyright (C) 2016 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef GL_WORLD_H +#define GL_WORLD_H + +#include "cubemap.h" +#include "vertex_fmt.h" + +// world features +#define WORLD_HAS_MOVIES BIT( 0 ) + +#define WORLD_HAS_GRASS BIT( 2 ) +#define WORLD_HAS_DELUXEMAP BIT( 3 ) +#define WORLD_HAS_SKYBOX BIT( 4 ) +#define WORLD_WATERALPHA BIT( 5 ) + +#define MAX_MAP_ELEMS MAX_MAP_VERTS * 5 // should be enough +#define SHADOW_ZBUF_RES 8 // 6 * 8 * 8 * 2 * 4 = 3k bytes per light + +// rebuilding cubemap states +#define CMREBUILD_INACTIVE 0 +#define CMREBUILD_CHECKING 1 +#define CMREBUILD_WAITING 2 + +typedef struct bvert_s +{ + Vector vertex; // position + Vector tangent; // tangent + Vector binormal; // binormal + Vector normal; // normal + float stcoord0[4]; // ST texture coords + float lmcoord0[4]; // LM texture coords for styles 0-1 + float lmcoord1[4]; // LM texture coords for styles 2-3 + byte styles[MAXLIGHTMAPS]; // light styles + byte lights0[4]; // packed light numbers + byte lights1[4]; // packed light numbers +} bvert_t; + +typedef struct +{ + dlightcube_t cube; + vec3_t origin; + mleaf_t *leaf; // ambient linked into this leaf +} mlightprobe_t; + +typedef struct mworldlight_s +{ + emittype_t emittype; + int style; + byte *pvs; // accumulated domain of the light + vec3_t origin; // light abs origin + vec3_t intensity; // RGB + vec3_t normal; // for surfaces and spotlights + float stopdot; // for spotlights + float stopdot2; // for spotlights + float fade; // falloff scaling for linear and inverse square falloff 1.0 = normal, 0.5 = farther, 2.0 = shorter + float radius; // light radius + mleaf_t *leaf; // light linked into this leaf + byte falloff; // falloff style 0 = default (inverse square), 1 = inverse falloff, 2 = inverse square + msurface_t *surface; // surf pointer for emit_surface + int lightnum; // important stuff! + int modelnum; // unused + unsigned short shadow_x; // offset by X in atlas + unsigned short shadow_y; // offset by Y in atlas + unsigned short shadow_w; // 0 is uninitialized + unsigned short shadow_h; // 0 is uninitialized +} mworldlight_t; + +// leaf extradata +typedef struct mextraleaf_s +{ +// leaf specific + vec3_t mins, maxs; // leaf size that updated with grass bounds + + mlightprobe_t *ambient_light; + byte num_lightprobes; + + mworldlight_t *direct_lights; + int num_directlights; +} mextraleaf_t; + +struct BmodelInstance_t +{ + cl_entity_t *m_pEntity; + + // bounds info + Vector bbox[8]; + Vector absmin; + Vector absmax; + float radius; + byte lights[MAXDYNLIGHTS]; + + matrix4x4 m_transform; + GLfloat m_gltransform; +}; + +struct LightShadowZBufferSample_t +{ + float m_flTraceDistance; // how far we traced. 0 = invalid + float m_flHitDistance; // where we hit +}; + +typedef CCubeMap< LightShadowZBufferSample_t, SHADOW_ZBUF_RES> lightzbuffer_t; + +typedef struct +{ + char name[64]; // to avoid reloading on same + + word features; // world features + + int num_visible_models; // visible models + + mextraleaf_t *leafs; // [worldmodel->numleafs] + int totalleafs; // full leaf counting + int numleafs; // [submodels[0].visleafs + 1] + + material_t *materials; // [worldmodel->numtextures] + + int numleaflights; + mlightprobe_t *leaflights; + + int numworldlights; + mworldlight_t *worldlights; + + dvertnorm_t *surfnormals; // is not NULL here a indexed normals + dnormal_t *normals; + int numnormals; + + mcubemap_t cubemaps[MAX_MAP_CUBEMAPS]; + mcubemap_t defaultCubemap; + int num_cubemaps; + + terrain_t *terrains; + unsigned int num_terrains; + + bvert_t *vertexes; + int numvertexes; + + dvlightlump_t *vertex_lighting; // used for env_statics + dvlightlump_t *surface_lighting; // used for env_statics + + byte *vislightdata; + color24 *deluxedata; + byte *shadowdata; + + lightzbuffer_t *shadowzbuffers; + + // single buffer for all the models + uint vertex_buffer_object; + uint vertex_array_object; + uint cacheSize; + + unsigned short *sortedfaces; // surfaces sorted through all models + unsigned short numsortedfaces; + + // misc info + Vector2D orthocenter; // overview stuff + Vector2D orthohalf; + + // cubemap builder internal state + bool loading_cubemaps; + bool build_default_cubemap; + int rebuilding_cubemaps; + int cubemap_build_number; +} gl_world_t; + +extern gl_world_t *world; + +#endif//GL_WORLD_H \ No newline at end of file diff --git a/cl_dll/render/gl_world_new.cpp b/cl_dll/render/gl_world_new.cpp new file mode 100644 index 0000000..fb0b2c8 --- /dev/null +++ b/cl_dll/render/gl_world_new.cpp @@ -0,0 +1,4024 @@ +/* +gl_world.cpp - world and bmodel rendering +Copyright (C) 2016 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "hud.h" +#include "cl_util.h" +#include "gl_local.h" +#include "pm_defs.h" +#include "event_api.h" +#include +#include "gl_studio.h" +#include "gl_shader.h" +#include "gl_world.h" +#include "gl_grass.h" +#include "gl_occlusion.h" +#include "vertex_fmt.h" + +static gl_world_t worlddata; +gl_world_t *world = &worlddata; + +static Vector env_dir[] = +{ + Vector( 1.0f, 0.0f, 0.0f ), + Vector( -1.0f, 0.0f, 0.0f ), + Vector( 0.0f, 1.0f, 0.0f ), + Vector( 0.0f, -1.0f, 0.0f ), + Vector( 0.0f, 0.0f, 1.0f ), + Vector( 0.0f, 0.0f, -1.0f ) +}; + +/* +================== +Mod_SampleSizeForFace + +return the current lightmap resolution per face +================== +*/ +int Mod_SampleSizeForFace( msurface_t *surf ) +{ + if( !surf || !surf->texinfo ) + return LM_SAMPLE_SIZE; + + // world luxels has more priority + if( FBitSet( surf->texinfo->flags, TEX_WORLD_LUXELS )) + return 1; + + if( FBitSet( surf->texinfo->flags, TEX_EXTRA_LIGHTMAP )) + return LM_SAMPLE_EXTRASIZE; + + if( surf->texinfo->faceinfo ) + return surf->texinfo->faceinfo->texture_step; + + return LM_SAMPLE_SIZE; +} + +gl_texbuffer_t *Surf_GetSubview( mextrasurf_t *es ) +{ + ASSERT( glState.stack_position >= 0 && glState.stack_position < MAX_REF_STACK ); + int handle = es->subtexture[glState.stack_position]; + + if( handle > 0 && handle <= MAX_SUBVIEW_TEXTURES ) + return &tr.subviewTextures[handle-1]; + return NULL; +} + +bool Surf_CheckSubview( mextrasurf_t *es, bool puddle ) +{ + ASSERT( glState.stack_position >= 0 && glState.stack_position < MAX_REF_STACK ); + int handle = es->subtexture[glState.stack_position]; + if( !handle ) return false; + + ASSERT( handle > 0 && handle <= MAX_SUBVIEW_TEXTURES ); + + // we can't directly compare here + if(( tr.realframecount - tr.subviewTextures[handle-1].texframe ) <= 1 ) + { + if( puddle && FBitSet( es->surf->flags, SURF_REFLECT_PUDDLE )) + return true; + if( !puddle && FBitSet( es->surf->flags, SURF_REFLECT )) + return true; + } + return false; +} + +/* +============================================================= + + CUBEMAP HANDLING & PROCESSING + +============================================================= +*/ + +/* +================== +Mod_FreeCubemap + +unload a given cubemap +================== +*/ +static void Mod_FreeCubemap( mcubemap_t *m ) +{ + if( m->valid && m->texture && m->texture != tr.whiteCubeTexture ) + FREE_TEXTURE( m->texture ); + + memset( m, 0, sizeof( *m )); +} + +/* +================== +Mod_FreeCubemaps + +purge all the cubemaps +from current level +================== +*/ +static void Mod_FreeCubemaps( void ) +{ + for( int i = 0; i < world->num_cubemaps; i++ ) + Mod_FreeCubemap( &world->cubemaps[i] ); + Mod_FreeCubemap( &world->defaultCubemap ); + + world->rebuilding_cubemaps = CMREBUILD_INACTIVE; + world->build_default_cubemap = false; + world->loading_cubemaps = false; + world->cubemap_build_number = 0; + world->num_cubemaps = 0; +} + +/* +================== +Mod_CheckCubemap + +check cubemap sides for valid +================== +*/ +static bool Mod_CheckCubemap( const char *name ) +{ + char *suf[6] = { "px", "nx", "py", "ny", "pz", "nz" }; + int valid_sides = 0; + char sidename[64]; + int iCompare; + + // FIXME: potentially unsafe checking: looking for DDS_CUBEMAP bit? + if( FILE_EXISTS( va( "maps/env/%s/%s.dds", world->name, name ))) + return true; + + for( int i = 0; i < 6; i++ ) + { + Q_snprintf( sidename, sizeof( sidename ), "maps/env/%s/%s%s.tga", world->name, name, suf[i] ); + + if( COMPARE_FILE_TIME( worldmodel->name, sidename, &iCompare ) && iCompare <= 0 ) + valid_sides++; + } + + return (valid_sides == 6) ? true : false; +} + +/* +================== +Mod_DeleteCubemap + +remove cubemap images from HDD +================== +*/ +static void Mod_DeleteCubemap( const char *name ) +{ + char *suf[6] = { "px", "nx", "py", "ny", "pz", "nz" }; + char sidename[64]; + + for( int i = 0; i < 6; i++ ) + { + Q_snprintf( sidename, sizeof( sidename ), "maps/env/%s/%s%s.tga", world->name, name, suf[i] ); + + if( FILE_EXISTS( sidename )) + Sys_RemoveFile( sidename ); + } +} + +/* +================== +Mod_LoadCubemap + +load cubemap into +video memory +================== +*/ +static bool Mod_LoadCubemap( mcubemap_t *m ) +{ + int flags = 0; + + if( m->valid && !m->texture ) + { + if( GL_Support( R_SEAMLESS_CUBEMAP )) + SetBits( flags, TF_BORDER ); // seamless cubemaps have support for border + else SetBits( flags, TF_CLAMP ); // default method + m->texture = LOAD_TEXTURE( m->name, NULL, 0, flags ); + + // make sure what is really cubemap + if( RENDER_GET_PARM( PARM_TEX_TARGET, m->texture ) == GL_TEXTURE_CUBE_MAP_ARB ) + m->valid = true; + else m->valid = false; + + // NOTE: old DDS cubemaps has no mip-levels + m->numMips = RENDER_GET_PARM( PARM_TEX_MIPCOUNT, m->texture ); + } + + return m->valid; +} + +/* +================== +GL_LoadAndRebuildCubemaps + +rebuild cubemaps that older than bspfile +loading actual cubemaps into videomemory +================== +*/ +void GL_LoadAndRebuildCubemaps( int refParams ) +{ + if( !world->loading_cubemaps && world->rebuilding_cubemaps == CMREBUILD_INACTIVE ) + return; // job is done + + // we are in cubemap-rendering mode + if( FBitSet( refParams, RP_ENVVIEW|RP_SKYVIEW )) + return; + + if( world->rebuilding_cubemaps != CMREBUILD_INACTIVE ) + { + if( world->build_default_cubemap ) + { + mcubemap_t *cm = &world->defaultCubemap; + + if( world->rebuilding_cubemaps == CMREBUILD_WAITING ) + { + cm->valid = Mod_CheckCubemap( "default" ); // need for rebuild? + world->rebuilding_cubemaps = CMREBUILD_CHECKING; + + if( !cm->valid ) + ALERT( at_error, "GL_RebuildCubemaps: can't build default cubemap\n" ); + world->build_default_cubemap = false; // done + } + + if( world->build_default_cubemap ) + { + Mod_DeleteCubemap( "default" ); + + // NOTE: engine function EnvShot not makes shots immediately + // but create a queue. So we will wait for a next frame + ENV_SHOT( cm->origin, va( "%s.tga", cm->name ), false, cm->size ); + world->rebuilding_cubemaps = CMREBUILD_WAITING; + return; + } + } + + for( ; world->cubemap_build_number < world->num_cubemaps; world->cubemap_build_number++ ) + { + mcubemap_t *cm = &world->cubemaps[world->cubemap_build_number]; + + if( world->rebuilding_cubemaps == CMREBUILD_WAITING ) + { + cm->valid = Mod_CheckCubemap( va( "cube#%i", world->cubemap_build_number )); // need for rebuild? + world->rebuilding_cubemaps = CMREBUILD_CHECKING; + + if( !cm->valid ) + { + ALERT( at_error, "GL_RebuildCubemaps: can't build cube#%i\n", world->cubemap_build_number ); + continue; // to avoid infinity cycle + } + } + + if( cm->valid ) continue; // it's valid cubemap + + Mod_DeleteCubemap( va( "cube#%i", world->cubemap_build_number )); + + // NOTE: engine function EnvShot not makes shots immediately + // but create a queue. So we will wait for a next frame + ENV_SHOT( cm->origin, va( "%s.tga", cm->name ), false, cm->size ); + world->rebuilding_cubemaps = CMREBUILD_WAITING; + return; + } + + // we reached the end of list + // next frame will be restored gamma + SetBits( cv_brightness->flags, FCVAR_CHANGED ); + SetBits( cv_gamma->flags, FCVAR_CHANGED ); + world->rebuilding_cubemaps = CMREBUILD_INACTIVE; + world->cubemap_build_number = 0; + tr.params_changed = true; + tr.glsl_valid_sequence++; + tr.fClearScreen = false; + } + + // now all the cubemaps are recreated, so we can starts to upload them + if( world->loading_cubemaps ) + { + Mod_LoadCubemap( &world->defaultCubemap ); + + for( int i = 0; i < world->num_cubemaps; i++ ) + { + mcubemap_t *cm = &world->cubemaps[i]; + Vector vecStart, vecEnd; + pmtrace_t pmtrace; + + Mod_LoadCubemap( cm ); + + // compute viewbox size + for( int j = 0; j < 6; j++ ) + { + vecStart = cm->origin; + vecEnd = vecStart + env_dir[j] * 4096.0f; + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PlayerTrace( vecStart, vecStart + vecEnd, PM_WORLD_ONLY, -1, &pmtrace ); + AddPointToBounds( pmtrace.endpos, cm->mins, cm->maxs ); + } + } + + // bind cubemaps onto world surfaces + // so we don't need to search them again + for( i = 0; i < worldmodel->numsurfaces; i++ ) + { + msurface_t *surf = &worldmodel->surfaces[i]; + mextrasurf_t *es = surf->info; + CL_FindTwoNearestCubeMapForSurface( es->origin, surf, &es->cubemap[0], &es->cubemap[1] ); + + // compute lerp factor + float dist0 = ( es->cubemap[0]->origin - es->origin ).Length(); + float dist1 = ( es->cubemap[1]->origin - es->origin ).Length(); + es->lerpFactor = dist0 / (dist0 + dist1); + } + + world->loading_cubemaps = false; + } +} + +/* +============================================================= + + WORLD LOADING + +============================================================= +*/ + +/* +================= +Mod_LoadCubemaps +================= +*/ +static void Mod_LoadCubemaps( const byte *base, const dlump_t *l ) +{ + dcubemap_t *in; + mcubemap_t *out; + int i, count; + + in = (dcubemap_t *)(base + l->fileofs); + if( l->filelen % sizeof( *in )) + HOST_ERROR( "Mod_LoadCubemaps: funny lump size\n" ); + count = l->filelen / sizeof( *in ); + + if( count >= MAX_MAP_CUBEMAPS ) + { + ALERT( at_error, "Mod_LoadCubemaps: map contain too many cubemaps. Will handle only first %i items\n", MAX_MAP_CUBEMAPS ); + count = MAX_MAP_CUBEMAPS; + } + + out = world->cubemaps; + world->num_cubemaps = count; + world->loading_cubemaps = (count > 0) ? true : false; + + // makes an default cubemap from skybox + if( FBitSet( world->features, WORLD_HAS_SKYBOX ) && (count > 0)) + { + mcubemap_t *cm = &world->defaultCubemap; + Q_snprintf( cm->name, sizeof( cm->name ), "maps/env/%s/default", world->name ); + cm->origin = (worldmodel->mins + worldmodel->maxs) * 0.5f; + cm->size = 256; // default cubemap larger than others + cm->valid = Mod_CheckCubemap( "default" ); // need for rebuild? + world->loading_cubemaps = true; + + if( !cm->valid ) + { + world->rebuilding_cubemaps = CMREBUILD_CHECKING; + world->build_default_cubemap = true; + } + } + else + { + // using stub as default cubemap + mcubemap_t *cm = &world->defaultCubemap; + Q_snprintf( cm->name, sizeof( cm->name ), "*whiteCube" ); + cm->origin = (worldmodel->mins + worldmodel->maxs) * 0.5f; + cm->texture = tr.whiteCubeTexture; + cm->valid = true; + cm->size = 4; + } + + for( i = 0; i < count; i++, in++, out++ ) + { + // build a cubemap name like enum + Q_snprintf( out->name, sizeof( out->name ), "maps/env/%s/cube#%i", world->name, i ); + out->valid = Mod_CheckCubemap( va( "cube#%i", i )); // need for rebuild? + if( !out->valid ) world->rebuilding_cubemaps = CMREBUILD_CHECKING; + VectorCopy( in->origin, out->origin ); + ClearBounds( out->mins, out->maxs ); + out->size = in->size; + + if( out->size <= 0 ) + out->size = DEFAULT_CUBEMAP_SIZE; + out->size = NearestPOW( out->size, false ); + out->size = bound( 1, out->size, 512 ); + } + + // user request for disable autorebuild + if( gEngfuncs.CheckParm( "-noautorebuildcubemaps", NULL )) + { + world->rebuilding_cubemaps = CMREBUILD_INACTIVE; + world->build_default_cubemap = false; + } +} + +/* +======================== +Mod_CopyMaterialDesc + +copy params from description +to real material struct +======================== +*/ +static void Mod_CopyMaterialDesc( material_t *mat, matdesc_t *desc ) +{ + mat->smoothness = desc->smoothness; + mat->detailScale[0] = desc->detailScale[0]; + mat->detailScale[1] = desc->detailScale[1]; + mat->reflectScale = desc->reflectScale; + mat->refractScale = desc->refractScale; + mat->aberrationScale = desc->aberrationScale; + mat->reliefScale = desc->reliefScale; + mat->effects = desc->effects; +} + +/* +======================== +Mod_LoadWorldMaterials + +build a material for each world texture +======================== +*/ +static void Mod_LoadWorldMaterials( void ) +{ + char diffuse[128], bumpmap[128]; + char glossmap[128], glowmap[128]; + char heightmap[128]; + + world->materials = (material_t *)Mem_Alloc( sizeof( material_t ) * worldmodel->numtextures ); + + for( int i = 0; i < worldmodel->numtextures; i++ ) + { + texture_t *tx = worldmodel->textures[i]; + material_t *mat = &world->materials[i]; + + // bad texture? + if( !tx || !tx->name[0] ) continue; + + // make cross-links for consistency + tx->material = mat; + mat->pSource = tx; + + // build material names + Q_snprintf( diffuse, sizeof( diffuse ), "textures/%s", tx->name ); + Q_snprintf( bumpmap, sizeof( bumpmap ), "textures/%s_norm", tx->name ); + Q_snprintf( glossmap, sizeof( glossmap ), "textures/%s_gloss", tx->name ); + Q_snprintf( glowmap, sizeof( glowmap ), "textures/%s_luma", tx->name ); + Q_snprintf( heightmap, sizeof( heightmap ), "textures/%s_hmap", tx->name ); + + if( IMAGE_EXISTS( diffuse )) + { + mat->gl_diffuse_id = LOAD_TEXTURE( diffuse, NULL, 0, 0 ); + + if( tx->gl_texturenum != tr.defaultTexture ) + FREE_TEXTURE( tx->gl_texturenum ); // release wad-texture + // so engine can be draw HQ image for gl_renderer 0 + // FIXME: what about detail texture scales ? + tx->gl_texturenum = mat->gl_diffuse_id; + + if( RENDER_GET_PARM( PARM_TEX_FLAGS, tx->gl_texturenum ) & TF_HAS_ALPHA ) + mat->flags |= BRUSH_HAS_ALPHA; + } + else + { + // use texture from wad + mat->gl_diffuse_id = tx->gl_texturenum; + } + + if( IMAGE_EXISTS( bumpmap )) + { + mat->gl_normalmap_id = LOAD_TEXTURE( bumpmap, NULL, 0, TF_NORMALMAP ); + } + else + { + // try alternate suffix + Q_snprintf( bumpmap, sizeof( bumpmap ), "textures/%s_local", tx->name ); + if( IMAGE_EXISTS( bumpmap )) + mat->gl_normalmap_id = LOAD_TEXTURE( bumpmap, NULL, 0, TF_NORMALMAP ); + else mat->gl_normalmap_id = tr.normalmapTexture; // blank bumpy + } + + if( IMAGE_EXISTS( glossmap )) + { + mat->gl_specular_id = LOAD_TEXTURE( glossmap, NULL, 0, 0 ); + } + else + { + // try alternate suffix + Q_snprintf( glossmap, sizeof( glossmap ), "textures/%s_spec", tx->name ); + if( IMAGE_EXISTS( glossmap )) + mat->gl_specular_id = LOAD_TEXTURE( glossmap, NULL, 0, 0 ); + else mat->gl_specular_id = tr.blackTexture; + } + + if( IMAGE_EXISTS( heightmap )) + { + mat->gl_heightmap_id = LOAD_TEXTURE( heightmap, NULL, 0, 0 ); + } + else + { + // try alternate suffix + Q_snprintf( heightmap, sizeof( heightmap ), "textures/%s_bump", tx->name ); + if( IMAGE_EXISTS( heightmap )) + mat->gl_heightmap_id = LOAD_TEXTURE( heightmap, NULL, 0, 0 ); + else mat->gl_heightmap_id = tr.blackTexture; + } + + if( IMAGE_EXISTS( glowmap )) + mat->gl_glowmap_id = LOAD_TEXTURE( glowmap, NULL, 0, 0 ); + else mat->gl_glowmap_id = tr.blackTexture; + + // setup material flags + if( mat->gl_normalmap_id > 0 && mat->gl_normalmap_id != tr.normalmapTexture ) + SetBits( mat->flags, BRUSH_HAS_BUMP ); + + if( mat->gl_specular_id > 0 && mat->gl_specular_id != tr.blackTexture ) + SetBits( mat->flags, BRUSH_HAS_SPECULAR ); + + if( mat->gl_glowmap_id > 0 && mat->gl_glowmap_id != tr.blackTexture ) + SetBits( mat->flags, BRUSH_HAS_LUMA ); + + if( mat->gl_heightmap_id > 0 && mat->gl_heightmap_id != tr.blackTexture ) + SetBits( mat->flags, BRUSH_HAS_HEIGHTMAP ); + + if( tx->name[0] == '{' ) + SetBits( mat->flags, BRUSH_TRANSPARENT ); + + if( !Q_strnicmp( tx->name, "scroll", 6 )) + SetBits( mat->flags, BRUSH_CONVEYOR ); + + if( !Q_strnicmp( tx->name, "{scroll", 7 )) + SetBits( mat->flags, BRUSH_CONVEYOR|BRUSH_TRANSPARENT ); + + if( !Q_strncmp( tx->name, "mirror", 6 ) || !Q_strncmp( tx->name, "reflect", 7 )) + SetBits( mat->flags, BRUSH_REFLECT ); + + if( !Q_strncmp( tx->name, "movie", 5 )) + SetBits( mat->flags, BRUSH_FULLBRIGHT ); + + if( tx->name[0] == '!' || !Q_strncmp( tx->name, "water", 5 )) + { + SetBits( mat->flags, BRUSH_REFLECT|BRUSH_LIQUID ); + + if( tr.waterTextures[0] ) + SetBits( mat->flags, BRUSH_HAS_BUMP ); + } + + if( !Q_strncmp( tx->name, "sky", 3 )) + SetBits( world->features, WORLD_HAS_SKYBOX ); + + // setup material constants + matdesc_t *desc = CL_FindMaterial( tx->name ); + Mod_CopyMaterialDesc( mat, desc ); + + mat->gl_detailmap_id = desc->dt_texturenum; + + if( mat->smoothness <= 0.0f ) // don't waste time + ClearBits( mat->flags, BRUSH_HAS_SPECULAR ); + + if( mat->gl_detailmap_id > 0 && mat->gl_detailmap_id != tr.grayTexture ) + SetBits( mat->flags, BRUSH_HAS_DETAIL ); + + tx->effects = mat->effects; + } +} + +static void Mod_SetupLeafExtradata( const dlump_t *l, const dlump_t *vis, const byte *buf ) +{ + dleaf_t *in = (dleaf_t *)(buf + l->fileofs); + mextraleaf_t *out; + + world->numleafs = worldmodel->numleafs + 1; // world leafs + outside common leaf + world->leafs = out = (mextraleaf_t *)Mem_Alloc( sizeof( mextraleaf_t ) * world->numleafs ); + world->totalleafs = l->filelen / sizeof( *in ); // keep the total leaf counting + + for( int i = 0; i < world->numleafs; i++, in++, out++ ) + { + VectorCopy( in->mins, out->mins ); + VectorCopy( in->maxs, out->maxs ); + } +} + +/* +================= +Mod_SetupLeafLights +================= +*/ +static void Mod_SetupLeafLights( void ) +{ + mextraleaf_t *out; + int i, j, k; + mleaf_t *leaf; + mworldlight_t *wl; + mlightprobe_t *lp; + + out = (mextraleaf_t *)world->leafs; + wl = world->worldlights; + lp = world->leaflights; + j = k = 0; + + for( i = 0; i < world->numleafs; i++, out++ ) + { + leaf = INFO_LEAF( out, worldmodel ); + + // NOTE: lights already sorted by leafs + if( world->numworldlights > 0 && wl->leaf == leaf ) + { + out->direct_lights = wl; // pointer to first light in the array that belong to this leaf + + for( ;( j < world->numworldlights ) && (wl->leaf == leaf); j++, wl++ ) + out->num_directlights++; + } + + if( world->numleaflights > 0 && lp->leaf == leaf ) + { + out->ambient_light = lp; // pointer to first light in the array that belong to this leaf + + for( ;( k < world->numleaflights ) && (lp->leaf == leaf); k++, lp++ ) + out->num_lightprobes++; + } + } +} + +/* +================= +Mod_LoadVertNormals +================= +*/ +static void Mod_LoadVertNormals( const byte *base, const dlump_t *l ) +{ + dnormallump_t *nhdr; + byte *data; + + if( !l->filelen ) return; + + data = (byte *)(base + l->fileofs); + nhdr = (dnormallump_t *)data; + + // indexed normals + if( nhdr->ident == NORMIDENT ) + { + int table_size = worldmodel->numsurfedges * sizeof( dvertnorm_t ); + int data_size = nhdr->numnormals * sizeof( dnormal_t ); + int total_size = sizeof( dnormallump_t ) + table_size + data_size; + + if( l->filelen != total_size ) + HOST_ERROR( "Mod_LoadVertNormals: funny lump size\n" ); + + data += sizeof( dnormallump_t ); + + // alloc remap table + world->surfnormals = (dvertnorm_t *)Mem_Alloc( table_size ); + memcpy( world->surfnormals, data, table_size ); + data += table_size; + + // copy normals data + world->normals = (dnormal_t *)Mem_Alloc( data_size ); + memcpy( world->normals, data, data_size ); + world->numnormals = nhdr->numnormals; + } + else + { + // old method... + int count; + dnormal_t *in; + + in = (dnormal_t *)(base + l->fileofs); + + if( l->filelen % sizeof( *in )) + HOST_ERROR( "Mod_LoadVertNormals: funny lump size\n" ); + count = l->filelen / sizeof( *in ); + + // all the other counts are invalid + if( count == worldmodel->numvertexes ) + { + world->normals = (dnormal_t *)Mem_Alloc( count * sizeof( dnormal_t )); + memcpy( world->normals, in, count * sizeof( dnormal_t )); + } + } +} + +/* +============= +BuildVisForDLight + +create visibility cache for dlight +============= +*/ +static int Mod_BuildVisForDLight( mworldlight_t *wl ) +{ + int leafnum; + + if( wl->emittype == emit_skylight ) + { + // all leafs that contain skyface should be added to sun visibility + for( leafnum = 0; leafnum < worldmodel->numleafs; leafnum++ ) + { + msurface_t **mark = worldmodel->leafs[leafnum + 1].firstmarksurface; + + for( int markface = 0; markface < worldmodel->leafs[leafnum + 1].nummarksurfaces; markface++, mark++ ) + { + msurface_t *surf = *mark; + + if( FBitSet( surf->flags, SURF_DRAWSKY )) + { + MergeDLightVis( wl, leafnum + 1 ); + break; // no reason to check all faces, go to next leaf + } + } + } + + // technically light_environment is outside of world + return -1; + } + else + { + leafnum = Mod_PointInLeaf( wl->origin, worldmodel->nodes ) - worldmodel->leafs; + SetDLightVis( wl, leafnum ); + + return leafnum; + } +} + +/* +================= +Mod_LoadWorldLights +================= +*/ +static void Mod_LoadWorldLights( const byte *base, const dlump_t *l ) +{ + dworldlight_t *in; + mworldlight_t *out, *out2; + int i, count, dup = 0; + int total = 0; + + if( !l->filelen ) return; + + in = (dworldlight_t *)(base + l->fileofs); + if( l->filelen % sizeof( *in )) + { + ALERT( at_error, "Mod_LoadWorldLights: funny lump size in %s\n", world->name ); + return; + } + count = l->filelen / sizeof( *in ); + + world->worldlights = out = (mworldlight_t *)Mem_Alloc( count * sizeof( *out )); + world->numworldlights = count; + + for( i = 0; i < count; i++, in++, out++ ) + { + out->emittype = (emittype_t)in->emittype; + out->style = in->style; + + VectorCopy( in->origin, out->origin ); + VectorCopy( in->intensity, out->intensity ); + VectorCopy( in->normal, out->normal ); + + out->stopdot = in->stopdot; + out->stopdot2 = in->stopdot2; + out->fade = in->fade; + out->leaf = &worldmodel->leafs[in->leafnum]; + out->radius = in->radius; + out->falloff = in->falloff; + + if( out->emittype == emit_surface ) + out->surface = worldmodel->surfaces + in->facenum; + out->modelnum = in->modelnumber; + out->lightnum = i; // !!! + out->shadow_x = 0xFFFF; + out->shadow_y = 0xFFFF; + + if( out->emittype == emit_skylight ) + out->stopdot2 = -1.0f; + } + + out = world->worldlights; + + for( i = dup = 0; i < count; i++, out++ ) + { + if( out->intensity == g_vecZero ) + out->emittype = emit_ignored; + + if( out->emittype != emit_surface ) + continue; + + VectorMA( out->origin, 1.0f, out->normal, out->origin ); + SetBits( out->surface->flags, SURF_FULLBRIGHT ); // emit faces is always fullbright + SetBits( out->surface->texinfo->texture->material->flags, BRUSH_FULLBRIGHT ); + + out2 = world->worldlights; + + for( int j = 0; j < count; j++, out2++ ) + { + if( out == out2 ) + continue; // himself + + if( out2->emittype != emit_surface ) + continue; + + if( out->surface == out2->surface ) + { + out2->emittype = emit_ignored; + dup++; + } + } + } + + out = world->worldlights; + + for( i = 0; i < count; i++, out++ ) + { + if( out->emittype == emit_ignored ) + continue; + + Mod_BuildVisForDLight( out ); + total++; + } + + // alloc shadow occlusion buffers + world->shadowzbuffers = (lightzbuffer_t *)Mem_Alloc( count * sizeof( lightzbuffer_t )); + ALERT( at_console, "%d world lights\n", total ); +} + +static void Mod_LoadVertexLighting( const byte *base, const dlump_t *l ) +{ + dvlightlump_t *vl; + + if( !l->filelen ) return; + + vl = (dvlightlump_t *)(base + l->fileofs); + + if( vl->ident != VLIGHTIDENT ) + return; // probably it's LUMP_LEAF_LIGHTING + + if( vl->version != VLIGHT_VERSION ) + return; // old version? + + if( vl->nummodels <= 0 ) return; + + world->vertex_lighting = (dvlightlump_t *)Mem_Alloc( l->filelen ); + memcpy( world->vertex_lighting, vl, l->filelen ); +} + +static void Mod_LoadSurfaceLighting( const byte *base, const dlump_t *l ) +{ + dvlightlump_t *vl; + + if( !l->filelen ) return; + + vl = (dvlightlump_t *)(base + l->fileofs); + + if( vl->ident != FLIGHTIDENT ) + return; // probably it's LUMP_LEAF_LIGHTING + + if( vl->version != FLIGHT_VERSION ) + return; // old version? + + if( vl->nummodels <= 0 ) return; + + world->surface_lighting = (dvlightlump_t *)Mem_Alloc( l->filelen ); + memcpy( world->surface_lighting, vl, l->filelen ); +} + +/* +================= +Mod_LoadVisLightData + +worldlights visibility per face +================= +*/ +static void Mod_LoadVisLightData( const byte *base, const dlump_t *l ) +{ + if( !l->filelen ) return; + + world->vislightdata = (byte *)Mem_Alloc( l->filelen ); + memcpy( world->vislightdata, (byte *)(base + l->fileofs), l->filelen ); +} + +/* +================= +Mod_LoadLeafAmbientLighting + +and link into leafs +================= +*/ +static void Mod_LoadLeafAmbientLighting( const byte *base, const dlump_t *l ) +{ + dleafsample_t *in; + dvlightlump_t *vl; + mlightprobe_t *out; + int i, count; + short curleaf = -1; + mextraleaf_t *leaf = NULL; + + if( !l->filelen ) return; + + vl = (dvlightlump_t *)(base + l->fileofs); + + if( vl->ident == VLIGHTIDENT ) + { + // probably it's LUMP_VERTEX_LIGHTING + Mod_LoadVertexLighting( base, l ); + return; + } + + in = (dleafsample_t *)(base + l->fileofs); + if( l->filelen % sizeof( *in )) + { + ALERT( at_warning, "Mod_LoadLeafAmbientLighting: funny lump size in %s\n", world->name ); + return; + } + count = l->filelen / sizeof( *in ); + + world->leaflights = out = (mlightprobe_t *)Mem_Alloc( count * sizeof( *out )); + world->numleaflights = count; + + for( i = 0; i < count; i++, in++, out++ ) + { + memcpy( &out->cube, &in->ambient, sizeof( dlightcube_t )); + out->leaf = &worldmodel->leafs[in->leafnum]; + VectorCopy( in->origin, out->origin ); + } +} + +/* +================= +Mod_SurfaceCompareBuild + +sort faces before lightmap building +================= +*/ +static int Mod_SurfaceCompareBuild( const unsigned short **a, const unsigned short **b ) +{ + msurface_t *surf1, *surf2; + + surf1 = &worldmodel->surfaces[(unsigned short)*a]; + surf2 = &worldmodel->surfaces[(unsigned short)*b]; + + if( FBitSet( surf1->flags, SURF_DRAWSKY ) && !FBitSet( surf2->flags, SURF_DRAWSKY )) + return -1; + + if( !FBitSet( surf1->flags, SURF_DRAWSKY ) && FBitSet( surf2->flags, SURF_DRAWSKY )) + return 1; + + if( FBitSet( surf1->flags, SURF_DRAWTURB ) && !FBitSet( surf2->flags, SURF_DRAWTURB )) + return -1; + + if( !FBitSet( surf1->flags, SURF_DRAWTURB ) && FBitSet( surf2->flags, SURF_DRAWTURB )) + return 1; + + // there faces owned with model in local space, so it *always* have non-identity transform matrix. + // move them to end of the list + if( FBitSet( surf1->flags, SURF_LOCAL_SPACE ) && !FBitSet( surf2->flags, SURF_LOCAL_SPACE )) + return 1; + + if( !FBitSet( surf1->flags, SURF_LOCAL_SPACE ) && FBitSet( surf2->flags, SURF_LOCAL_SPACE )) + return -1; + + return 0; +} + +/* +================= +Mod_SurfaceCompareInGame + +sort faces to reduce shader switches +================= +*/ +static int Mod_SurfaceCompareInGame( const unsigned short **a, const unsigned short **b ) +{ + msurface_t *surf1, *surf2; + mextrasurf_t *esrf1, *esrf2; + + surf1 = &worldmodel->surfaces[(unsigned short)*a]; + surf2 = &worldmodel->surfaces[(unsigned short)*b]; + + esrf1 = surf1->info; + esrf2 = surf2->info; + + if( esrf1->forwardScene[0].GetHandle() > esrf2->forwardScene[0].GetHandle() ) + return 1; + + if( esrf1->forwardScene[0].GetHandle() < esrf2->forwardScene[0].GetHandle() ) + return -1; + + if( surf1->texinfo->texture->gl_texturenum > surf2->texinfo->texture->gl_texturenum ) + return 1; + + if( surf1->texinfo->texture->gl_texturenum < surf2->texinfo->texture->gl_texturenum ) + return -1; + + if( esrf1->lightmaptexturenum > esrf2->lightmaptexturenum ) + return 1; + + if( esrf1->lightmaptexturenum < esrf2->lightmaptexturenum ) + return -1; + + if( !esrf1->parent || !esrf2->parent ) + return 0; + + if( esrf1->parent->hCachedMatrix > esrf2->parent->hCachedMatrix ) + return 1; + + if( esrf1->parent->hCachedMatrix < esrf2->parent->hCachedMatrix ) + return -1; + + return 0; +} + +/* +================= +Mod_FinalizeWorld + +build representation table +of surfaces sorted by texture +then alloc lightmaps +================= +*/ +static void Mod_FinalizeWorld( void ) +{ + int i; + + world->sortedfaces = (unsigned short *)Mem_Alloc( worldmodel->numsurfaces * sizeof( unsigned short )); + world->numsortedfaces = worldmodel->numsurfaces; + + // initial filling + for( i = 0; i < worldmodel->numsurfaces; i++ ) + world->sortedfaces[i] = i; + + qsort( world->sortedfaces, worldmodel->numsurfaces, sizeof( unsigned short ), (cmpfunc)Mod_SurfaceCompareBuild ); + + // alloc surface lightmaps and compute lm coords (for sorted list) + for( i = 0; i < worldmodel->numsurfaces; i++ ) + { + msurface_t *surf = &worldmodel->surfaces[world->sortedfaces[i]]; + + // allocate the lightmap coords, create lightmap textures (empty at this moment) + GL_AllocLightmapForFace( surf ); + } +} + +/* +================= +Mod_ShaderSceneForward + +compute albedo with static lighting +================= +*/ +static word Mod_ShaderSceneForward( msurface_t *s ) +{ + char glname[64]; + char options[MAX_OPTIONS_LENGTH]; + mextrasurf_t *es = s->info; + cl_entity_t *e = es->parent ? es->parent : GET_ENTITY( 0 ); + + // don't cache shader for skyfaces! + if( FBitSet( s->flags, SURF_DRAWSKY )) + return 0; + + // mirror is actual only if we has actual screen texture! + bool mirror = Surf_CheckSubview( s->info ); + + if( es->forwardScene[mirror].IsValid() && es->lastRenderMode == e->curstate.rendermode ) + return es->forwardScene[mirror].GetHandle(); // valid + + Q_strncpy( glname, "forward/scene_bmodel", sizeof( glname )); + memset( options, 0, sizeof( options )); + + mfaceinfo_t *landscape = landscape = s->texinfo->faceinfo; + material_t *mat = R_TextureAnimation( s )->material; + bool shader_translucent = false; + bool shader_additive = false; + bool using_cubemaps = false; + bool fullbright = false; + + if(( FBitSet( mat->flags, BRUSH_FULLBRIGHT ) || R_FullBright( ) || mirror ) && !FBitSet( mat->flags, BRUSH_LIQUID )) + fullbright = true; + + if( e->curstate.rendermode == kRenderTransAdd ) + { + shader_additive = true; + fullbright = true; + } + + if( fullbright ) + { + GL_AddShaderDirective( options, "LIGHTING_FULLBRIGHT" ); + } + else + { + // process lightstyles + for( int i = 0; i < MAXLIGHTMAPS && s->styles[i] != LS_NONE; i++ ) + { + if( tr.sun_light_enabled && s->styles[i] == LS_SKY ) + continue; // skip the sunlight due realtime sun is enabled + GL_AddShaderDirective( options, va( "APPLY_STYLE%i", i )); + } + + if( CVAR_TO_BOOL( cv_brdf )) + GL_AddShaderDirective( options, "APPLY_PBS" ); + + // NOTE: deluxemap and normalmap are separate because some modes may using + // normalmap directly e.g. for mirror distorsion + if( es->normals ) + { + GL_AddShaderDirective( options, "HAS_DELUXEMAP" ); + GL_AddShaderDirective( options, "COMPUTE_TBN" ); + } + + if( r_lightmap->value > 0.0f && r_lightmap->value <= 2.0f ) + { + if( r_lightmap->value == 1.0f && worldmodel && worldmodel->lightdata ) + GL_AddShaderDirective( options, "LIGHTMAP_DEBUG" ); + else if( r_lightmap->value == 2.0f && FBitSet( world->features, WORLD_HAS_DELUXEMAP )) + GL_AddShaderDirective( options, "LIGHTVEC_DEBUG" ); + } + + if( !RP_CUBEPASS() && ( CVAR_TO_BOOL( cv_specular ) && FBitSet( mat->flags, BRUSH_HAS_SPECULAR ))) + GL_AddShaderDirective( options, "HAS_GLOSSMAP" ); + + if( FBitSet( mat->flags, BRUSH_HAS_LUMA )) + GL_AddShaderDirective( options, "HAS_LUMA" ); + } + + if( FBitSet( mat->flags, BRUSH_MULTI_LAYERS ) && landscape && landscape->terrain ) + { + GL_AddShaderDirective( options, va( "TERRAIN_NUM_LAYERS %i", landscape->terrain->numLayers )); + GL_AddShaderDirective( options, "APPLY_TERRAIN" ); + + if( landscape->terrain->indexmap.gl_diffuse_id != 0 && CVAR_TO_BOOL( r_detailtextures )) + GL_AddShaderDirective( options, "HAS_DETAIL" ); + } + else + { + if( FBitSet( mat->flags, BRUSH_HAS_DETAIL ) && CVAR_TO_BOOL( r_detailtextures )) + GL_AddShaderDirective( options, "HAS_DETAIL" ); + } + + if( !RP_CUBEPASS() && ( FBitSet( mat->flags, BRUSH_HAS_BUMP ) && (CVAR_TO_BOOL( cv_bump ) || FBitSet( mat->flags, BRUSH_LIQUID )))) + { + // FIXME: all the waternormals should be encoded as first frame + if( FBitSet( mat->flags, BRUSH_LIQUID )) + GL_EncodeNormal( options, tr.waterTextures[0] ); + else GL_EncodeNormal( options, mat->gl_normalmap_id ); + GL_AddShaderDirective( options, "HAS_NORMALMAP" ); + } + + // and finally select the render-mode + if( FBitSet( mat->flags, BRUSH_LIQUID )) + { + GL_AddShaderDirective( options, "LIQUID_SURFACE" ); + if( tr.waterlevel >= 3 ) + GL_AddShaderDirective( options, "LIQUID_UNDERWATER" ); + + // world watery with compiler feature + if( !FBitSet( s->flags, SURF_OF_SUBMODEL ) && FBitSet( world->features, WORLD_WATERALPHA )) + shader_translucent = true; + else if( e->curstate.rendermode == kRenderTransColor || e->curstate.rendermode == kRenderTransTexture ) + shader_translucent = true; + } + else if(( world->num_cubemaps > 0 ) && CVAR_TO_BOOL( cv_cubemaps ) && (mat->reflectScale > 0.0f) && !RP_CUBEPASS( )) + { + if( !FBitSet( mat->flags, BRUSH_REFLECT )) + { + GL_AddShaderDirective( options, "REFLECTION_CUBEMAP" ); + using_cubemaps = true; + } + } + + if( e->curstate.rendermode == kRenderTransColor || e->curstate.rendermode == kRenderTransTexture ) + shader_translucent = true; + + if( shader_translucent ) + GL_AddShaderDirective( options, "TRANSLUCENT" ); + if( shader_additive ) + GL_AddShaderDirective( options, "ADDITIVE" ); + if( mirror ) + GL_AddShaderDirective( options, "PLANAR_REFLECTION" ); + if( shader_translucent && FBitSet( mat->flags, BRUSH_HAS_ALPHA )) + GL_AddShaderDirective( options, "ALPHA_GLASS" ); + + if( mat->refractScale > 0.0f && Q_stristr( options, "HAS_NORMALMAP" )) + GL_AddShaderDirective( options, "APPLY_REFRACTION" ); + + if( mat->aberrationScale > 0.0f && Q_stristr( options, "HAS_NORMALMAP" )) + GL_AddShaderDirective( options, "APPLY_ABERRATION" ); + + if( tr.fogEnabled ) + GL_AddShaderDirective( options, "APPLY_FOG_EXP" ); + + word shaderNum = GL_FindUberShader( glname, options ); + + if( !shaderNum ) + { + tr.fClearScreen = true; // to avoid ugly blur + SetBits( s->flags, SURF_NODRAW ); + return 0; // something bad happens + } + + if( shader_translucent ) + GL_AddShaderFeature( shaderNum, SHADER_TRANSLUCENT|SHADER_USE_SCREENCOPY ); + if( shader_additive ) + GL_AddShaderFeature( shaderNum, SHADER_ADDITIVE ); + + if( using_cubemaps ) + GL_AddShaderFeature( shaderNum, SHADER_USE_CUBEMAPS ); + + es->lastRenderMode = e->curstate.rendermode; + ClearBits( s->flags, SURF_NODRAW ); + es->forwardScene[mirror].SetShader( shaderNum ); + + return shaderNum; +} + +/* +================= +Mod_ShaderLightForward + +compute dynamic lighting +================= +*/ +static word Mod_ShaderLightForward( CDynLight *dl, msurface_t *s ) +{ + char glname[64]; + char options[MAX_OPTIONS_LENGTH]; + mfaceinfo_t *landscape = NULL; + mextrasurf_t *es = s->info; + + switch( dl->type ) + { + case LIGHT_SPOT: + if( es->forwardLightSpot.IsValid( )) + return es->forwardLightSpot.GetHandle(); // valid + break; + case LIGHT_OMNI: + if( es->forwardLightOmni.IsValid( )) + return es->forwardLightOmni.GetHandle(); // valid + break; + case LIGHT_DIRECTIONAL: + if( es->forwardLightProj.IsValid( )) + return es->forwardLightProj.GetHandle(); // valid + break; + } + + Q_strncpy( glname, "forward/light_bmodel", sizeof( glname )); + memset( options, 0, sizeof( options )); + + switch( dl->type ) + { + case LIGHT_SPOT: + GL_AddShaderDirective( options, "LIGHT_SPOT" ); + break; + case LIGHT_OMNI: + GL_AddShaderDirective( options, "LIGHT_OMNI" ); + break; + case LIGHT_DIRECTIONAL: + GL_AddShaderDirective( options, "LIGHT_PROJ" ); + break; + } + + // mirror is actual only if we has actual screen texture! + bool mirror = Surf_CheckSubview( s->info ); + material_t *mat = R_TextureAnimation( s )->material; + landscape = s->texinfo->faceinfo; + + if( CVAR_TO_BOOL( cv_brdf )) + GL_AddShaderDirective( options, "APPLY_PBS" ); + + if( CVAR_TO_BOOL( cv_bump ) || FBitSet( mat->flags, BRUSH_LIQUID )) + { + if( FBitSet( mat->flags, BRUSH_HAS_BUMP ) && !FBitSet( dl->flags, DLF_NOBUMP )) + { + // FIXME: all the waternormals should be encoded as first frame + if( FBitSet( mat->flags, BRUSH_LIQUID )) + GL_EncodeNormal( options, tr.waterTextures[0] ); + else GL_EncodeNormal( options, mat->gl_normalmap_id ); + GL_AddShaderDirective( options, "HAS_NORMALMAP" ); + GL_AddShaderDirective( options, "COMPUTE_TBN" ); + } + } + + if( CVAR_TO_BOOL( cv_specular ) && FBitSet( mat->flags, BRUSH_HAS_SPECULAR )) + GL_AddShaderDirective( options, "HAS_GLOSSMAP" ); + + if( FBitSet( mat->flags, BRUSH_MULTI_LAYERS ) && landscape && landscape->terrain ) + { + GL_AddShaderDirective( options, va( "TERRAIN_NUM_LAYERS %i", landscape->terrain->numLayers )); + GL_AddShaderDirective( options, "APPLY_TERRAIN" ); + + if( landscape->terrain->indexmap.gl_diffuse_id != 0 && CVAR_TO_BOOL( r_detailtextures ) && glConfig.max_varying_floats > 48 ) + GL_AddShaderDirective( options, "HAS_DETAIL" ); + } + else + { + if( FBitSet( mat->flags, BRUSH_HAS_DETAIL ) && CVAR_TO_BOOL( r_detailtextures ) && glConfig.max_varying_floats > 48 ) + GL_AddShaderDirective( options, "HAS_DETAIL" ); + } + + if( mirror && glConfig.max_varying_floats > 48 ) + GL_AddShaderDirective( options, "PLANAR_REFLECTION" ); + + // and finally select the render-mode + if( FBitSet( mat->flags, BRUSH_LIQUID )) + { + GL_AddShaderDirective( options, "LIQUID_SURFACE" ); + if( tr.waterlevel >= 3 ) + GL_AddShaderDirective( options, "LIQUID_UNDERWATER" ); + } + + if( CVAR_TO_BOOL( r_shadows ) && !FBitSet( dl->flags, DLF_NOSHADOWS )) + { + // shadow cubemaps only support if GL_EXT_gpu_shader4 is support + if( dl->type == LIGHT_DIRECTIONAL && CVAR_TO_BOOL( r_sunshadows )) + { + GL_AddShaderDirective( options, "APPLY_SHADOW" ); + } + else if( dl->type == LIGHT_SPOT || GL_Support( R_EXT_GPU_SHADER4 )) + { + GL_AddShaderDirective( options, "APPLY_SHADOW" ); + + if( r_shadows->value == 2.0f ) + GL_AddShaderDirective( options, "SHADOW_PCF2X2" ); + else if( r_shadows->value >= 3.0f ) + GL_AddShaderDirective( options, "SHADOW_PCF3X3" ); + } + } + + word shaderNum = GL_FindUberShader( glname, options ); + + if( !shaderNum ) + { + if( dl->type == LIGHT_DIRECTIONAL ) + SetBits( s->flags, SURF_NOSUNLIGHT ); + else SetBits( s->flags, SURF_NODLIGHT ); + + return 0; // something bad happens + } + + // done + switch( dl->type ) + { + case LIGHT_SPOT: + es->forwardLightSpot.SetShader( shaderNum ); + ClearBits( s->flags, SURF_NODLIGHT ); + break; + case LIGHT_OMNI: + es->forwardLightOmni.SetShader( shaderNum ); + ClearBits( s->flags, SURF_NODLIGHT ); + break; + case LIGHT_DIRECTIONAL: + es->forwardLightProj.SetShader( shaderNum ); + ClearBits( s->flags, SURF_NOSUNLIGHT ); + break; + } + + return shaderNum; +} + +/* +================= +Mod_ShaderSceneDeferred + +compute deferred albedo +================= +*/ +static word Mod_ShaderSceneDeferred( msurface_t *s ) +{ + char glname[64]; + char options[MAX_OPTIONS_LENGTH]; + bool using_cubemaps = false; + mextrasurf_t *es = s->info; + + if( es->deferredScene.IsValid( )) + return es->deferredScene.GetHandle(); // valid + + Q_strncpy( glname, "deferred/scene_bmodel", sizeof( glname )); + memset( options, 0, sizeof( options )); + + material_t *mat = s->texinfo->texture->material; + mfaceinfo_t *landscape = s->texinfo->faceinfo; + + if( FBitSet( mat->flags, BRUSH_MULTI_LAYERS ) && landscape && landscape->terrain ) + { + GL_AddShaderDirective( options, va( "TERRAIN_NUM_LAYERS %i", landscape->terrain->numLayers )); + GL_AddShaderDirective( options, "APPLY_TERRAIN" ); + + if( landscape->terrain->indexmap.gl_diffuse_id != 0 && CVAR_TO_BOOL( r_detailtextures )) + GL_AddShaderDirective( options, "HAS_DETAIL" ); + } + else + { + if( FBitSet( mat->flags, BRUSH_HAS_DETAIL ) && CVAR_TO_BOOL( r_detailtextures )) + GL_AddShaderDirective( options, "HAS_DETAIL" ); + } + + if( FBitSet( mat->flags, BRUSH_FULLBRIGHT ) || R_FullBright( )) + GL_AddShaderDirective( options, "LIGHTING_FULLBRIGHT" ); + + if( FBitSet( mat->flags, BRUSH_HAS_LUMA )) + GL_AddShaderDirective( options, "HAS_LUMA" ); + + if( !RP_CUBEPASS() && ( FBitSet( mat->flags, BRUSH_HAS_BUMP ) && CVAR_TO_BOOL( cv_bump ))) + { + GL_AddShaderDirective( options, "HAS_NORMALMAP" ); + GL_EncodeNormal( options, mat->gl_normalmap_id ); + GL_AddShaderDirective( options, "COMPUTE_TBN" ); + } + + if( !RP_CUBEPASS() && ( CVAR_TO_BOOL( cv_specular ) && FBitSet( mat->flags, BRUSH_HAS_SPECULAR ))) + { + GL_AddShaderDirective( options, "HAS_GLOSSMAP" ); + + if(( world->num_cubemaps > 0 ) && CVAR_TO_BOOL( cv_cubemaps ) && !RP_CUBEPASS( )) + { + GL_AddShaderDirective( options, "REFLECTION_CUBEMAP" ); + using_cubemaps = true; + } + } + + word shaderNum = GL_FindUberShader( glname, options ); + + if( !shaderNum ) + { + tr.fClearScreen = true; // to avoid ugly blur + SetBits( s->flags, SURF_NODRAW ); + return 0; // something bad happens + } + + if( using_cubemaps ) + GL_AddShaderFeature( shaderNum, SHADER_USE_CUBEMAPS ); + + // done + es->deferredScene.SetShader( shaderNum ); + ClearBits( s->flags, SURF_NODRAW ); + + return shaderNum; +} + +/* +================= +Mod_ShaderLightDeferred + +compute deferred lighting +================= +*/ +static word Mod_ShaderLightDeferred( msurface_t *s ) +{ + char glname[64]; + char options[MAX_OPTIONS_LENGTH]; + mextrasurf_t *es = s->info; + + if( es->deferredLight.IsValid( )) + return es->deferredLight.GetHandle(); // valid + + Q_strncpy( glname, "deferred/light_bmodel", sizeof( glname )); + memset( options, 0, sizeof( options )); + + material_t *mat = s->texinfo->texture->material; + + if( FBitSet( mat->flags, BRUSH_HAS_LUMA ) || FBitSet( mat->flags, BRUSH_FULLBRIGHT ) || R_FullBright( )) + GL_AddShaderDirective( options, "LIGHTING_FULLBRIGHT" ); + + word shaderNum = GL_FindUberShader( glname, options ); + + if( !shaderNum ) + { + tr.fClearScreen = true; // to avoid ugly blur + SetBits( s->flags, SURF_NODRAW ); + return 0; // something bad happens + } + + // done + es->deferredLight.SetShader( shaderNum ); + ClearBits( s->flags, SURF_NODRAW ); + + return shaderNum; +} + +/* +================= +Mod_ShaderSceneDepth + +return bmodel depth-shader +================= +*/ +static word Mod_ShaderSceneDepth( msurface_t *s ) +{ + mextrasurf_t *es = s->info; + + if( es->forwardDepth.IsValid( )) + return es->forwardDepth.GetHandle(); + + word shaderNum = GL_FindUberShader( "forward/depth_bmodel" ); + es->forwardDepth.SetShader( shaderNum ); + + return shaderNum; +} + +/* +================= +Mod_PrecacheShaders + +precache shaders to reduce freezes in-game +================= +*/ +static void Mod_PrecacheShaders( void ) +{ + msurface_t *surf; + int i; + + // preload shaders for all the world faces (but ignore watery faces) + for( i = 0; i < worldmodel->submodels[0].numfaces; i++ ) + { + surf = &worldmodel->surfaces[world->sortedfaces[i]]; + + if( !FBitSet( surf->flags, SURF_DRAWTURB|SURF_DRAWSKY )) + { + Mod_ShaderSceneForward( surf ); + + // also precache a default light shaders + Mod_ShaderLightForward( &tr.defaultlightSpot, surf ); + Mod_ShaderLightForward( &tr.defaultlightOmni, surf ); + Mod_ShaderLightForward( &tr.defaultlightProj, surf ); + } + } + + tr.params_changed = true; +#if 0 + Msg( "sorted faces:\n" ); + for( i = 0; i < worldmodel->numsurfaces; i++ ) + { + surf = &worldmodel->surfaces[world->sortedfaces[i]]; + mextrasurf_t *esrf = SURF_INFO( surf, worldmodel ); + Msg( "face %i (local %s), style[1] %i\n", i, FBitSet( surf->flags, SURF_LOCAL_SPACE ) ? "Yes" : "No", surf->styles[1] ); + } +#endif +} + +/* +================= +Mod_ResortFaces + +if shaders was changed we need to resort them +================= +*/ +void Mod_ResortFaces( void ) +{ + int i; + + if( !tr.params_changed ) return; + + // rebuild shaders + for( i = 0; i < worldmodel->submodels[0].numfaces; i++ ) + Mod_ShaderSceneForward( &worldmodel->surfaces[i] ); + + // resort faces + qsort( world->sortedfaces, worldmodel->numsurfaces, sizeof( unsigned short ), (cmpfunc)Mod_SurfaceCompareInGame ); +#if 0 + Msg( "resorted faces:\n" ); + for( i = 0; i < worldmodel->numsurfaces; i++ ) + { + msurface_t *surf = &worldmodel->surfaces[world->sortedfaces[i]]; + mextrasurf_t *esrf = SURF_INFO( surf, worldmodel ); + Msg( "face %i (submodel %s) (local %s), shader %i, lightmap %i, style[1] %i\n", + i, FBitSet( surf->flags, SURF_OF_SUBMODEL ) ? "Yes" : "No", FBitSet( surf->flags, SURF_LOCAL_SPACE ) ? "Yes" : "No", + esrf->shaderNum, esrf->lightmaptexturenum, surf->styles[1] ); + } +#endif +} + +/* +================= +Mod_ComputeFaceTBN + +compute smooth TBN with baked normals +================= +*/ +static void Mod_ComputeFaceTBN( msurface_t *surf, mextrasurf_t *esrf ) +{ + Vector texdirections[2]; + Vector directionnormals[2]; + Vector faceNormal, vertNormal; + int side; + + for( int i = 0; i < esrf->numverts; i++ ) + { + bvert_t *v = &world->vertexes[esrf->firstvertex + i]; + int l = worldmodel->surfedges[surf->firstedge + i]; + int vert = worldmodel->edges[abs(l)].v[(l > 0) ? 0 : 1]; + + if( world->surfnormals != NULL && world->normals != NULL ) + { + l = world->surfnormals[surf->firstedge + i]; + if( l >= 0 || l < world->numnormals ) + vertNormal = Vector( world->normals[l].normal ); + else ALERT( at_error, "normal index %d out of range (max %d)\n", l, world->numnormals ); + } + else if( world->normals != NULL ) + vertNormal = Vector( world->normals[vert].normal ); + + // calc unsmoothed tangent space + if( FBitSet( surf->flags, SURF_PLANEBACK )) + faceNormal = -surf->plane->normal; + else faceNormal = surf->plane->normal; + + // fallback + if( vertNormal == g_vecZero ) + vertNormal = faceNormal; + vertNormal = vertNormal.Normalize(); + + for( side = 0; side < 2; side++ ) + { + texdirections[side] = CrossProduct( faceNormal, surf->info->lmvecs[!side] ).Normalize(); + if( DotProduct( texdirections[side], surf->info->lmvecs[side] ) < 0.0f ) + texdirections[side] = -texdirections[side]; + } + + for( side = 0; side < 2; side++ ) + { + float dot = DotProduct( texdirections[side], vertNormal ); + VectorMA( texdirections[side], -dot, vertNormal, directionnormals[side] ); + directionnormals[side] = directionnormals[side].Normalize(); + } + + v->tangent = directionnormals[0]; + v->binormal = -directionnormals[1]; + v->normal = vertNormal; + } +} + +/* +================== +GetLayerIndexForPoint + +this function came from q3map2 +================== +*/ +static byte Mod_GetLayerIndexForPoint( indexMap_t *im, const Vector &mins, const Vector &maxs, const Vector &point ) +{ + Vector size; + + if( !im->pixels ) return 0; + + for( int i = 0; i < 3; i++ ) + size[i] = ( maxs[i] - mins[i] ); + + float s = ( point[0] - mins[0] ) / size[0]; + float t = ( maxs[1] - point[1] ) / size[1]; + + int x = s * im->width; + int y = t * im->height; + + x = bound( 0, x, ( im->width - 1 )); + y = bound( 0, y, ( im->height - 1 )); + + return im->pixels[y * im->width + x]; +} + +/* +================= +Mod_LayerNameForPixel + +return layer name per pixel +================= +*/ +bool Mod_CheckLayerNameForPixel( mfaceinfo_t *land, const Vector &point, const char *checkName ) +{ + terrain_t *terra; + layerMap_t *lm; + indexMap_t *im; + + if( !land ) return true; // no landscape specified + terra = land->terrain; + if( !terra ) return true; + + im = &terra->indexmap; + lm = &terra->layermap; + + if( !Q_stricmp( checkName, lm->names[Mod_GetLayerIndexForPoint( im, land->mins, land->maxs, point )] )) + return true; + return false; +} + +/* +================= +Mod_CheckLayerNameForSurf + +return layer name per face +================= +*/ +bool Mod_CheckLayerNameForSurf( msurface_t *surf, const char *checkName ) +{ + mtexinfo_t *tx = surf->texinfo; + mfaceinfo_t *land = tx->faceinfo; + terrain_t *terra; + layerMap_t *lm; + + if( land != NULL && land->terrain != NULL ) + { + terra = land->terrain; + lm = &terra->layermap; + + for( int i = 0; i < terra->numLayers; i++ ) + { + if( !Q_stricmp( checkName, lm->names[i] )) + return true; + } + } + else + { + const char *texname = surf->texinfo->texture->name; + + if( !Q_stricmp( checkName, texname )) + return true; + } + + return false; +} + +/* +================= +Mod_ProcessLandscapes + +handle all the landscapes per level +================= +*/ +static void Mod_ProcessLandscapes( msurface_t *surf, mextrasurf_t *esrf ) +{ + mtexinfo_t *tx = surf->texinfo; + mfaceinfo_t *land = tx->faceinfo; + + if( !land || land->groupid == 0 || !land->landname[0] ) + return; // no landscape specified, just lightmap resolution + + if( !land->terrain ) + { + land->terrain = R_FindTerrain( land->landname ); + + if( !land->terrain ) + { + // land name was specified in bsp but not declared in script file + ALERT( at_error, "Mod_ProcessLandscapes: %s missing description\n", land->landname ); + land->landname[0] = '\0'; // clear name to avoid trying to find invalid terrain + return; + } + + // prepare new landscape params + ClearBounds( land->mins, land->maxs ); + + // setup shared pointers + for( int i = 0; i < land->terrain->numLayers; i++ ) + land->effects[i] = land->terrain->layermap.material[i]->effects; + + land->heightmap = land->terrain->indexmap.pixels; + land->heightmap_width = land->terrain->indexmap.width; + land->heightmap_height = land->terrain->indexmap.height; + } + + // update terrain bounds + AddPointToBounds( esrf->mins, land->mins, land->maxs ); + AddPointToBounds( esrf->maxs, land->mins, land->maxs ); + + for( int j = 0; j < esrf->numverts; j++ ) + { + bvert_t *v = &world->vertexes[esrf->firstvertex + j]; + AddPointToBounds( v->vertex, land->mins, land->maxs ); + } +} + +/* +================= +Mod_MappingLandscapes + +now landscape AABB is actual +mappping the surfaces +================= +*/ +static void Mod_MappingLandscapes( msurface_t *surf, mextrasurf_t *esrf ) +{ + mtexinfo_t *tx = surf->texinfo; + mfaceinfo_t *land = tx->faceinfo; + float mappingScale; + terrain_t *terra; + bvert_t *v; + + if( !land ) return; // no landscape specified + terra = land->terrain; + if( !terra ) return; // ooops! something bad happens! + + // now we have landscape info! + SetBits( surf->flags, SURF_LANDSCAPE ); + mappingScale = terra->texScale; + + // setup layers here + if( surf->texinfo && surf->texinfo->texture && surf->texinfo->texture->material ) + { + material_t *mat = surf->texinfo->texture->material; + + ASSERT( mat != NULL ); + + mat->gl_diffuse_id = terra->layermap.gl_diffuse_id; + mat->gl_normalmap_id = terra->layermap.gl_normalmap_id; + mat->gl_specular_id = terra->layermap.gl_specular_id; + + mat->gl_glowmap_id = tr.blackTexture; + mat->flags |= BRUSH_MULTI_LAYERS; + + if( mat->gl_normalmap_id > 0 && mat->gl_normalmap_id != tr.normalmapTexture ) + mat->flags |= BRUSH_HAS_BUMP; + + if( mat->gl_specular_id > 0 && mat->gl_specular_id != tr.blackTexture ) + mat->flags |= BRUSH_HAS_SPECULAR; + + if( mat->gl_glowmap_id > 0 && mat->gl_glowmap_id != tr.blackTexture ) + mat->flags |= BRUSH_HAS_LUMA; + + if( RENDER_GET_PARM( PARM_TEX_FLAGS, mat->gl_specular_id ) & TF_HAS_ALPHA ) + mat->flags |= BRUSH_GLOSSPOWER; + + // refresh material constants + matdesc_t *desc = CL_FindMaterial( terra->indexmap.diffuse ); + Mod_CopyMaterialDesc( mat, desc ); + } + + // mapping global diffuse texture + for( int i = 0; i < esrf->numverts; i++ ) + { + v = &world->vertexes[esrf->firstvertex + i]; + + v->stcoord0[0] *= mappingScale; + v->stcoord0[1] *= mappingScale; + R_GlobalCoords( surf, v->vertex, v->stcoord0 ); + } +} + +/* +================= +Mod_FindStaticLights + +find a mark lights that affected to this face +================= +*/ +void Mod_FindStaticLights( byte *vislight, byte lights[MAXDYNLIGHTS], const Vector &origin ) +{ + mworldlight_t *wl = world->worldlights; + int indexes[32]; + int i, count; + + memset( lights, 255, sizeof( byte ) * MAXDYNLIGHTS ); + count = 0; + + // failed to vislightdata... + if( !vislight ) return; + + // mark all the lights that can lit this face + for( i = 0; i < world->numworldlights; i++, wl++ ) + { + if( wl->emittype == emit_ignored ) + continue; // bad light? + + // this face is invisible for this light + if( !CHECKVISBIT( vislight, i )) + continue; + + if( count >= ARRAYSIZE( indexes )) + { + ALERT( at_aiconsole, "too many lights on a face\n" ); + break; + } + + indexes[count++] = i;// member this light + } + +get_next_light: + float maxPhotons = 0.0; + int ignored = -1; + int light = 255; + + for( i = 0; i < count; i++ ) + { + if( indexes[i] == -1 ) + continue; + + wl = world->worldlights + indexes[i]; + Vector delta = (wl->origin - origin); + float dist = Q_max( delta.Length(), 1.0 ); + delta = delta.Normalize(); + float ratio = 1.0 / (dist * dist); + Vector add = wl->intensity * ratio; + float photons = VectorMax( add ); + + // skylight has a maximum priority + if( photons > maxPhotons ) + { + maxPhotons = photons; + light = indexes[i]; + ignored = i; + } + } + + if( ignored == -1 ) + { + if( count > (int)cv_deferred_maxlights->value ) + ALERT( at_aiconsole, "total %i lights affected to face\n", count ); + return; + } + for( i = 0; i < (int)cv_deferred_maxlights->value && lights[i] != 255; i++ ); + if( i < (int)cv_deferred_maxlights->value ) + lights[i] = light; // nearest light for surf + indexes[ignored] = -1; // this light is handled + +// if( count > (int)cv_deferred_maxlights->value && i == (int)cv_deferred_maxlights->value ) +// Msg( "skipped light %i intensity %g, type %d\n", light, maxPhotons, world->worldlights[light].emittype ); + goto get_next_light; +} + +/* +================= +Mod_InitLightTexture +================= +*/ +static void Mod_InitLightTexture( void ) +{ + mworldlight_t *wl = world->worldlights; + int height = ((world->numworldlights / 256) + 1) * 3; + int lightnum = 0; + int width = 256; + + if( !world->numworldlights ) + return; + + Vector4D *worldlights = (Vector4D *)Mem_Alloc( sizeof( Vector4D ) * width * height ); +// Msg( "light: %d %d\n", width, height ); + + for( int y = 0; y < height; y += 3 ) + { + for( int x = 0; x < width; x++, lightnum++ ) + { + if( lightnum == world->numworldlights ) + break; + + wl = &world->worldlights[lightnum]; + + worldlights[((y+0)*width)+x] = Vector4D( NormalToFloat( wl->normal ), wl->stopdot, wl->stopdot2, wl->emittype ); + worldlights[((y+1)*width)+x] = Vector4D( wl->origin[0], wl->origin[1], wl->origin[2], wl->falloff ); + worldlights[((y+2)*width)+x] = Vector4D( wl->intensity[0], wl->intensity[1], wl->intensity[2], wl->style ); + } + } + + tr.packed_lights_texture = CREATE_TEXTURE( "*worldlights", width, height, (byte *)worldlights, TF_STORAGE ); + Mem_Free( worldlights ); +} + +/* +================= +Mod_InitBSPTreeTexture +================= +*/ +static void Mod_InitBSPTreeTexture( void ) +{ + int planenum = 0; + int height = 256; + int width = 256; + int nodenum = 0; + mnode_t *cn, *child; + int x, y; + dclipnode_t out; + mplane_t *pl; + + // planes are 4-th components float texture 256x56 (max 65536 planes) + Vector4D *worldplanes = (Vector4D *)Mem_Alloc( sizeof( Vector4D ) * width * height ); + + for( y = 0; y < height; y++ ) + { + for( x = 0; x < width; x++, planenum++ ) + { + if( planenum == worldmodel->numplanes ) + break; + + pl = &worldmodel->planes[planenum]; + + // we not enough free space to store type or signbits but these optimizations doesn't matter on GPU anyway + worldplanes[(y*width)+x] = Vector4D( pl->normal.x, pl->normal.y, pl->normal.z, pl->dist ); + } + } + + tr.packed_planes_texture = CREATE_TEXTURE( "*worldplanes", width, height, (byte *)worldplanes, TF_STORAGE ); + Mem_Free( worldplanes ); + + // nodes are 4-th components float texture 256x256 (max 65536 nodes) + Vector4D *worldnodes = (Vector4D *)Mem_Alloc( sizeof( Vector4D ) * width * height ); + + for( y = 0; y < height; y++ ) + { + for( x = 0; x < width; x++, nodenum++ ) + { + if( nodenum == worldmodel->numnodes ) + break; // all nodes are stored + + cn = &worldmodel->nodes[nodenum]; + out.planenum = cn->plane - worldmodel->planes; + + // convert nodes to clipnodes + for( int j = 0; j < 2; j++ ) + { + child = cn->children[j]; + + if( child->contents < 0 ) + out.children[j] = child->contents; + else out.children[j] = child - worldmodel->nodes; + } + + // FIXME: store into 4-th component something useful :) + worldnodes[(y*width)+x] = Vector4D( out.children[0], out.children[1], out.planenum, 0.0f ); // unused + } + } + + tr.packed_nodes_texture = CREATE_TEXTURE( "*worldnodes", width, height, (byte *)worldnodes, TF_STORAGE ); + Mem_Free( worldnodes ); + +// Msg( "bsp structure placed into textures (%i nodes, %i planes)\n", worldmodel->numnodes, worldmodel->numplanes ); +} + +/* +================= +Mod_InitBSPTreeTexture +================= +*/ +void Mod_InitBSPModelsTexture( void ) +{ + cl_entity_t *visible_ents[MAX_VISIBLE_ENTS]; + Vector absmin, absmax; + static double lastupdate; + gl_state_t *glm; + + if( !CVAR_TO_BOOL( cv_deferred_tracebmodels )) + return; + + if( !FBitSet( RI->params, RP_DEFERREDSCENE|RP_DEFERREDLIGHT )) + return; + + if( lastupdate > Sys_DoubleTime( )) + return; + + // don't upload models too often + lastupdate = Sys_DoubleTime() + 0.01; + world->num_visible_models = 0; + + for( int i = 0; i < tr.num_draw_entities; i++ ) + { + cl_entity_t *e = tr.draw_entities[i]; + + if( !e || !e->model || e->model->type != mod_brush ) + continue; + + if( e->curstate.rendermode != kRenderNormal ) + continue; + + glm = GL_GetCache( e->hCachedMatrix ); + if( glm->m_bSkyEntity ) continue; + + visible_ents[world->num_visible_models] = e; + world->num_visible_models++; + } + + int height = worldmodel->numsubmodels; + int width = 8, flags = 0; + + // data representation [width = 26][height = nummodels] + // first 6 floats - bounding box, next 16 floats - actual model matrix, + // next 2 float - rootnode, totalnodes, last 2 floats - first face, + Vector4D *worldmodels = (Vector4D *)stackalloc( sizeof( Vector4D ) * width * height ); + + for( int y = 0; y < world->num_visible_models; y++ ) + { + cl_entity_t *e = visible_ents[y]; + // grab the transformed vieworg + glm = GL_GetCache( e->hCachedMatrix ); + matrix4x4 im = glm->transform.Invert(); + model_t *m = e->model; + + if( e->angles != g_vecZero ) + { + TransformAABB( glm->transform, e->model->mins, e->model->maxs, absmin, absmax ); + } + else + { + absmin = e->origin + e->model->mins; + absmax = e->origin + e->model->maxs; + } + + // first 2 pixels - transformed bounding box, identity flag, rootnode + worldmodels[(y*width)+0] = Vector4D( absmin.x, absmin.y, absmin.z, e->hCachedMatrix > 0 ? 1.0f : 0.0f ); + worldmodels[(y*width)+1] = Vector4D( absmax.x, absmax.y, absmax.z, m->hulls[0].firstclipnode ); + + // next 4 pixels - actual inverted model matrix + worldmodels[(y*width)+2] = Vector4D( im[0][0], im[1][0], im[2][0], im[3][0] ); + worldmodels[(y*width)+3] = Vector4D( im[0][1], im[1][1], im[2][1], im[3][1] ); + worldmodels[(y*width)+4] = Vector4D( im[0][2], im[1][2], im[2][2], im[3][2] ); + worldmodels[(y*width)+5] = Vector4D( im[0][3], im[1][3], im[2][3], im[3][3] ); + } + + // if texture already present - update it + if( tr.packed_models_texture != 0 ) + SetBits( flags, TF_UPDATE ); + + // it will automatically called glSubImage on next calls + tr.packed_models_texture = CREATE_TEXTURE( "*worldmodels", width, height, (byte *)worldmodels, TF_STORAGE|flags ); +} + +static void CreateBufferBaseGL21( bvert_t *arrayxvert ) +{ + static bvert_v0_gl21_t arraybvert[MAX_MAP_VERTS*4]; + + // convert to GLSL-compacted array + for( int i = 0; i < world->numvertexes; i++ ) + { + arraybvert[i].vertex[0] = arrayxvert[i].vertex[0]; + arraybvert[i].vertex[1] = arrayxvert[i].vertex[1]; + arraybvert[i].vertex[2] = arrayxvert[i].vertex[2]; + arraybvert[i].tangent[0] = arrayxvert[i].tangent[0]; + arraybvert[i].tangent[1] = arrayxvert[i].tangent[1]; + arraybvert[i].tangent[2] = arrayxvert[i].tangent[2]; + arraybvert[i].binormal[0] = arrayxvert[i].binormal[0]; + arraybvert[i].binormal[1] = arrayxvert[i].binormal[1]; + arraybvert[i].binormal[2] = arrayxvert[i].binormal[2]; + arraybvert[i].normal[0] = arrayxvert[i].normal[0]; + arraybvert[i].normal[1] = arrayxvert[i].normal[1]; + arraybvert[i].normal[2] = arrayxvert[i].normal[2]; + arraybvert[i].stcoord0[0] = arrayxvert[i].stcoord0[0]; + arraybvert[i].stcoord0[1] = arrayxvert[i].stcoord0[1]; + arraybvert[i].stcoord0[2] = arrayxvert[i].stcoord0[2]; + arraybvert[i].stcoord0[3] = arrayxvert[i].stcoord0[3]; + arraybvert[i].lmcoord0[0] = arrayxvert[i].lmcoord0[0]; + arraybvert[i].lmcoord0[1] = arrayxvert[i].lmcoord0[1]; + arraybvert[i].lmcoord0[2] = arrayxvert[i].lmcoord0[2]; + arraybvert[i].lmcoord0[3] = arrayxvert[i].lmcoord0[3]; + arraybvert[i].lmcoord1[0] = arrayxvert[i].lmcoord1[0]; + arraybvert[i].lmcoord1[1] = arrayxvert[i].lmcoord1[1]; + arraybvert[i].lmcoord1[2] = arrayxvert[i].lmcoord1[2]; + arraybvert[i].lmcoord1[3] = arrayxvert[i].lmcoord1[3]; + memcpy( arraybvert[i].styles, arrayxvert[i].styles, MAXLIGHTMAPS ); + } + + world->cacheSize = world->numvertexes * sizeof( bvert_v0_gl21_t ); + + // create world vertex buffer + pglGenBuffersARB( 1, &world->vertex_buffer_object ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, world->vertex_buffer_object ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, world->cacheSize, &arraybvert[0], GL_STATIC_DRAW_ARB ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); +} + +static void BindBufferBaseGL21( void ) +{ + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, world->vertex_buffer_object ); + + pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 3, GL_FLOAT, 0, sizeof( bvert_v0_gl21_t ), (void *)offsetof( bvert_v0_gl21_t, vertex )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION ); + + pglVertexAttribPointerARB( ATTR_INDEX_TANGENT, 3, GL_FLOAT, 0, sizeof( bvert_v0_gl21_t ), (void *)offsetof( bvert_v0_gl21_t, tangent )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_TANGENT ); + + pglVertexAttribPointerARB( ATTR_INDEX_BINORMAL, 3, GL_FLOAT, 0, sizeof( bvert_v0_gl21_t ), (void *)offsetof( bvert_v0_gl21_t, binormal )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_BINORMAL ); + + pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 3, GL_FLOAT, 0, sizeof( bvert_v0_gl21_t ), (void *)offsetof( bvert_v0_gl21_t, normal )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL ); + + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD0, 4, GL_FLOAT, 0, sizeof( bvert_v0_gl21_t ), (void *)offsetof( bvert_v0_gl21_t, stcoord0 )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD0 ); + + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD1, 4, GL_FLOAT, 0, sizeof( bvert_v0_gl21_t ), (void *)offsetof( bvert_v0_gl21_t, lmcoord0 )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD1 ); + + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD2, 4, GL_FLOAT, 0, sizeof( bvert_v0_gl21_t ), (void *)offsetof( bvert_v0_gl21_t, lmcoord1 )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD2 ); + + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_STYLES, 4, GL_UNSIGNED_BYTE, 0, sizeof( bvert_v0_gl21_t ), (void *)offsetof( bvert_v0_gl21_t, styles )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_STYLES ); +} + +static void CreateBufferBaseGL30( bvert_t *arrayxvert ) +{ + static bvert_v0_gl30_t arraybvert[MAX_MAP_VERTS*4]; + + // convert to GLSL-compacted array + for( int i = 0; i < world->numvertexes; i++ ) + { + arraybvert[i].vertex[0] = arrayxvert[i].vertex[0]; + arraybvert[i].vertex[1] = arrayxvert[i].vertex[1]; + arraybvert[i].vertex[2] = arrayxvert[i].vertex[2]; + CompressNormalizedVector( arraybvert[i].normal, arrayxvert[i].normal ); + CompressNormalizedVector( arraybvert[i].tangent, arrayxvert[i].tangent ); + CompressNormalizedVector( arraybvert[i].binormal, arrayxvert[i].binormal ); + arraybvert[i].stcoord0[0] = arrayxvert[i].stcoord0[0]; + arraybvert[i].stcoord0[1] = arrayxvert[i].stcoord0[1]; + arraybvert[i].stcoord0[2] = arrayxvert[i].stcoord0[2]; + arraybvert[i].stcoord0[3] = arrayxvert[i].stcoord0[3]; + arraybvert[i].lmcoord0[0] = arrayxvert[i].lmcoord0[0]; + arraybvert[i].lmcoord0[1] = arrayxvert[i].lmcoord0[1]; + arraybvert[i].lmcoord0[2] = arrayxvert[i].lmcoord0[2]; + arraybvert[i].lmcoord0[3] = arrayxvert[i].lmcoord0[3]; + arraybvert[i].lmcoord1[0] = arrayxvert[i].lmcoord1[0]; + arraybvert[i].lmcoord1[1] = arrayxvert[i].lmcoord1[1]; + arraybvert[i].lmcoord1[2] = arrayxvert[i].lmcoord1[2]; + arraybvert[i].lmcoord1[3] = arrayxvert[i].lmcoord1[3]; + memcpy( arraybvert[i].styles, arrayxvert[i].styles, MAXLIGHTMAPS ); + memcpy( arraybvert[i].lights0, arrayxvert[i].lights0, MAXLIGHTMAPS ); + memcpy( arraybvert[i].lights1, arrayxvert[i].lights1, MAXLIGHTMAPS ); + } + + world->cacheSize = world->numvertexes * sizeof( bvert_v0_gl30_t ); + + // create world vertex buffer + pglGenBuffersARB( 1, &world->vertex_buffer_object ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, world->vertex_buffer_object ); + pglBufferDataARB( GL_ARRAY_BUFFER_ARB, world->cacheSize, &arraybvert[0], GL_STATIC_DRAW_ARB ); + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); +} + +static void BindBufferBaseGL30( void ) +{ + pglBindBufferARB( GL_ARRAY_BUFFER_ARB, world->vertex_buffer_object ); + + pglVertexAttribPointerARB( ATTR_INDEX_POSITION, 3, GL_FLOAT, 0, sizeof( bvert_v0_gl30_t ), (void *)offsetof( bvert_v0_gl30_t, vertex )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_POSITION ); + + pglVertexAttribPointerARB( ATTR_INDEX_TANGENT, 3, GL_BYTE, 0, sizeof( bvert_v0_gl30_t ), (void *)offsetof( bvert_v0_gl30_t, tangent )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_TANGENT ); + + pglVertexAttribPointerARB( ATTR_INDEX_BINORMAL, 3, GL_BYTE, 0, sizeof( bvert_v0_gl30_t ), (void *)offsetof( bvert_v0_gl30_t, binormal )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_BINORMAL ); + + pglVertexAttribPointerARB( ATTR_INDEX_NORMAL, 3, GL_BYTE, 0, sizeof( bvert_v0_gl30_t ), (void *)offsetof( bvert_v0_gl30_t, normal )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_NORMAL ); + + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD0, 4, GL_FLOAT, 0, sizeof( bvert_v0_gl30_t ), (void *)offsetof( bvert_v0_gl30_t, stcoord0 )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD0 ); + + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD1, 4, GL_FLOAT, 0, sizeof( bvert_v0_gl30_t ), (void *)offsetof( bvert_v0_gl30_t, lmcoord0 )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD1 ); + + pglVertexAttribPointerARB( ATTR_INDEX_TEXCOORD2, 4, GL_FLOAT, 0, sizeof( bvert_v0_gl30_t ), (void *)offsetof( bvert_v0_gl30_t, lmcoord1 )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_TEXCOORD2 ); + + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_STYLES, 4, GL_UNSIGNED_BYTE, 0, sizeof( bvert_v0_gl30_t ), (void *)offsetof( bvert_v0_gl30_t, styles )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_STYLES ); + + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_NUMS0, 4, GL_UNSIGNED_BYTE, 0, sizeof( bvert_v0_gl30_t ), (void *)offsetof( bvert_v0_gl30_t, lights0 )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_NUMS0 ); + + pglVertexAttribPointerARB( ATTR_INDEX_LIGHT_NUMS1, 4, GL_UNSIGNED_BYTE, 0, sizeof( bvert_v0_gl30_t ), (void *)offsetof( bvert_v0_gl30_t, lights1 )); + pglEnableVertexAttribArrayARB( ATTR_INDEX_LIGHT_NUMS1 ); +} + +/* +================= +Mod_CreateBufferObject +================= +*/ +static void Mod_CreateBufferObject( void ) +{ + if( world->vertex_buffer_object ) + return; // already created + + // calculate number of used faces and vertexes + msurface_t *surf = worldmodel->surfaces; + int i, j, curVert = 0; + byte *vislight = NULL; + mvertex_t *dv; + bvert_t *mv; + + world->numvertexes = 0; + + // compute totalvertex count for VBO but ignore sky polys + for( i = 0; i < worldmodel->numsurfaces; i++, surf++ ) + { + if( FBitSet( surf->flags, SURF_DRAWSKY )) + continue; + world->numvertexes += surf->numedges; + } + + // temporary array will be removed at end of this function + // g-cont. i'm leave local copy of vertexes for some debug purpoces + world->vertexes = (bvert_t *)Mem_Alloc( sizeof( bvert_t ) * world->numvertexes ); + surf = worldmodel->surfaces; + + // create VBO-optimized vertex array (single for world and all brush-models) + for( i = 0; i < worldmodel->numsurfaces; i++, surf++ ) + { + Vector t, b, n; + + if( FBitSet( surf->flags, SURF_DRAWSKY )) + continue; // ignore sky polys it was never be drawed + + mv = &world->vertexes[curVert]; + + // request vislightdata for this surface + if( world->vislightdata ) vislight = world->vislightdata + i * ((world->numworldlights + 7) / 8); + Mod_FindStaticLights( vislight, surf->info->lights, surf->info->origin ); + + // NOTE: all polygons stored as source (no tesselation anyway) + for( j = 0; j < surf->numedges; j++, mv++ ) + { + int l = worldmodel->surfedges[surf->firstedge + j]; + int vert = worldmodel->edges[abs(l)].v[(l > 0) ? 0 : 1]; + memcpy( mv->styles, surf->styles, sizeof( surf->styles )); + memcpy( mv->lights0, surf->info->lights, sizeof( surf->info->lights )); + dv = &worldmodel->vertexes[vert]; + mv->vertex = dv->position; + + R_TextureCoords( surf, mv->vertex, mv->stcoord0 ); + R_LightmapCoords( surf, mv->vertex, mv->lmcoord0, 0 ); // styles 0-1 + R_LightmapCoords( surf, mv->vertex, mv->lmcoord1, 2 ); // styles 2-3 + } + + // NOTE: now firstvertex are handled in world->vertexes[] array, not in world->tbn_vectors[] !!! + surf->info->firstvertex = curVert; + surf->info->numverts = surf->numedges; + curVert += surf->numedges; + + Mod_ComputeFaceTBN( surf, surf->info ); + } + + // compute water global coords + for( i = 1; i < worldmodel->numsubmodels; i++ ) + { + Vector absmin, absmax; + + ClearBounds( absmin, absmax ); + + // first iteration - compute water bbox + for( j = 0; j < worldmodel->submodels[i].numfaces; j++ ) + { + surf = &worldmodel->surfaces[worldmodel->submodels[i].firstface + j]; + if( !FBitSet( surf->flags, SURF_DRAWTURB )) + continue; + + AddPointToBounds( surf->info->mins, absmin, absmax ); + AddPointToBounds( surf->info->maxs, absmin, absmax ); + } + + if( BoundsIsCleared( absmin, absmax )) + continue; // not a water + + float scale = sqrt( (absmax - absmin).Average()) * 0.3f; // FIXME: tune this constant? + + // second iteration - mapping global coords + for( j = 0; j < worldmodel->submodels[i].numfaces; j++ ) + { + surf = &worldmodel->surfaces[worldmodel->submodels[i].firstface + j]; + if( !FBitSet( surf->flags, SURF_DRAWTURB )) + continue; + + for( int k = 0; k < surf->info->numverts; k++ ) + { + mv = &world->vertexes[surf->info->firstvertex + k]; + R_GlobalCoords( surf, mv->vertex, absmin, absmax, scale, mv->stcoord0 ); + } + } + } + + // time to prepare landscapes + for( i = 0; i < worldmodel->numsurfaces; i++ ) + { + msurface_t *surf = &worldmodel->surfaces[i]; + Mod_ProcessLandscapes( surf, surf->info ); + } + + for( i = 0; i < worldmodel->numsurfaces; i++ ) + { + msurface_t *surf = &worldmodel->surfaces[i]; + Mod_MappingLandscapes( surf, surf->info ); + } + + // now normals are merged into single array world->vertexes[] + if( world->normals != NULL ) + Mem_Free( world->normals ); + world->normals = NULL; + + GL_CheckVertexArrayBinding(); + + // create world vertex buffer + if( glConfig.version < ACTUAL_GL_VERSION ) + CreateBufferBaseGL21( world->vertexes ); + else CreateBufferBaseGL30( world->vertexes ); + + // create vertex array object + pglGenVertexArrays( 1, &world->vertex_array_object ); + pglBindVertexArray( world->vertex_array_object ); + + if( glConfig.version < ACTUAL_GL_VERSION ) + BindBufferBaseGL21(); + else BindBufferBaseGL30(); + + pglBindVertexArray( GL_FALSE ); + + // update stats + tr.total_vbo_memory += world->cacheSize; +} + +/* +================= +Mod_DeleteBufferObject +================= +*/ +static void Mod_DeleteBufferObject( void ) +{ + if( world->vertex_array_object ) pglDeleteVertexArrays( 1, &world->vertex_array_object ); + if( world->vertex_buffer_object ) pglDeleteBuffersARB( 1, &world->vertex_buffer_object ); + + tr.total_vbo_memory -= world->cacheSize; + world->vertex_array_object = world->vertex_buffer_object = 0; + world->cacheSize = 0; +} + +/* +================= +Mod_PrepareModelInstances + +throw all the instances before +loading the new map +================= +*/ +void Mod_PrepareModelInstances( void ) +{ + // invalidate model handles + for( int i = 1; i < RENDER_GET_PARM( PARM_MAX_ENTITIES, 0 ); i++ ) + { + cl_entity_t *e = GET_ENTITY( i ); + if( !e ) break; + e->modelhandle = INVALID_HANDLE; + } + + if( gHUD.m_pHeadShieldEnt != NULL ) + { + gHUD.m_pHeadShieldEnt->modelhandle = INVALID_HANDLE; + gHUD.m_pHeadShieldEnt->curstate.movetype = MOVETYPE_WALK; + } + + GET_VIEWMODEL()->modelhandle = INVALID_HANDLE; + + memset( tr.draw_entities, 0, sizeof( tr.draw_entities )); +} + +/* +================= +Mod_ThrowModelInstances + +throw all the instances before +loading the new map +================= +*/ +void Mod_ThrowModelInstances( void ) +{ + // engine already released entity array so we can't release + // model instance for each entity personally + g_StudioRenderer.DestroyAllModelInstances(); + + // may caused by Host_Error, so clear gl state + if( g_fRenderInitialized ) + GL_SetDefaultState(); +} + +/* +================= +Mod_LoadWorld + +================= +*/ +static void Mod_LoadWorld( model_t *mod, const byte *buf ) +{ + dheader_t *header; + dextrahdr_t *extrahdr; + int i; + + header = (dheader_t *)buf; + extrahdr = (dextrahdr_t *)((byte *)buf + sizeof( dheader_t )); + + if( RENDER_GET_PARM( PARM_FEATURES, 0 ) & ENGINE_LARGE_LIGHTMAPS ) + glConfig.block_size = BLOCK_SIZE_MAX; + else glConfig.block_size = BLOCK_SIZE_DEFAULT; + + if( RENDER_GET_PARM( PARM_MAP_HAS_DELUXE, 0 )) + SetBits( world->features, WORLD_HAS_DELUXEMAP ); + + if( RENDER_GET_PARM( PARM_WATER_ALPHA, 0 )) + SetBits( world->features, WORLD_WATERALPHA ); + + COM_FileBase( worldmodel->name, world->name ); + ALERT( at_aiconsole, "Mod_LoadWorld: %s\n", world->name ); + + R_CheckChanges(); // catch all the cvar changes + tr.glsl_valid_sequence = 1; + tr.params_changed = false; + + // precache and upload cinematics + R_InitCinematics(); + + // prepare visibility and setup leaf extradata + Mod_SetupLeafExtradata( &header->lumps[LUMP_LEAFS], &header->lumps[LUMP_VISIBILITY], buf ); + + // all the old lightmaps are freed + GL_BeginBuildingLightmaps(); + + // process landscapes first + R_LoadLandscapes( world->name ); + + // load material textures + Mod_LoadWorldMaterials(); + + // mark submodel faces + for( i = worldmodel->submodels[0].numfaces; i < worldmodel->numsurfaces; i++ ) + SetBits( worldmodel->surfaces[i].flags, SURF_OF_SUBMODEL ); + + // detect surfs in local space + for( i = 0; i < worldmodel->numsubmodels; i++ ) + { + dmodel_t *bm = &worldmodel->submodels[i]; + if( bm->origin == g_vecZero ) + continue; // abs space + + // mark surfs in local space + msurface_t *surf = worldmodel->surfaces + bm->firstface; + for( int j = 0; j < bm->numfaces; j++, surf++ ) + SetBits( surf->flags, SURF_LOCAL_SPACE ); + } + + if( extrahdr->id == IDEXTRAHEADER ) + { + if( extrahdr->version == EXTRA_VERSION ) + { + // new Xash3D extended format + if( GL_Support( R_TEXTURECUBEMAP_EXT )) // loading cubemaps only when it's support + Mod_LoadCubemaps( buf, &extrahdr->lumps[LUMP_CUBEMAPS] ); + Mod_LoadVertNormals( buf, &extrahdr->lumps[LUMP_VERTNORMALS] ); + Mod_LoadWorldLights( buf, &extrahdr->lumps[LUMP_WORLDLIGHTS] ); + Mod_LoadLeafAmbientLighting( buf, &extrahdr->lumps[LUMP_LEAF_LIGHTING] ); + Mod_LoadVertexLighting( buf, &extrahdr->lumps[LUMP_VERTEX_LIGHT] ); + Mod_LoadSurfaceLighting( buf, &extrahdr->lumps[LUMP_SURFACE_LIGHT] ); + Mod_LoadVisLightData( buf, &extrahdr->lumps[LUMP_VISLIGHTDATA] ); + } + else if( extrahdr->version == EXTRA_VERSION_OLD ) + { + // P2: Savior regular format + if( GL_Support( R_TEXTURECUBEMAP_EXT )) // loading cubemaps only when it's support + Mod_LoadCubemaps( buf, &extrahdr->lumps[LUMP_CUBEMAPS] ); + Mod_LoadWorldLights( buf, &extrahdr->lumps[LUMP_WORLDLIGHTS] ); + } + + Mod_SetupLeafLights(); + } + + // mark surfaces for world features + for( i = 0; i < worldmodel->numsurfaces; i++ ) + { + msurface_t *surf = &worldmodel->surfaces[i]; + + if( FBitSet( surf->flags, SURF_DRAWSKY )) + SetBits( world->features, WORLD_HAS_SKYBOX ); + + if( FBitSet( surf->flags, SURF_DRAWTURB )) + { + SetBits( surf->flags, SURF_REFLECT ); + } + + if( !Q_strncmp( surf->texinfo->texture->name, "movie", 5 )) + { + SetBits( world->features, WORLD_HAS_MOVIES ); + SetBits( surf->flags, SURF_MOVIE ); + } + + if( !Q_strncmp( surf->texinfo->texture->name, "mirror", 6 ) || !Q_strncmp( surf->texinfo->texture->name, "reflect", 7 )) + { + SetBits( surf->flags, SURF_REFLECT ); + } + + if( !Q_strncmp( surf->texinfo->texture->name, "water", 5 )) + { + SetBits( surf->flags, SURF_REFLECT ); + } + + if( FBitSet( surf->flags, SURF_REFLECT )) + GL_AllocOcclusionQuery( surf ); + } + + world->deluxedata = (color24 *)RENDER_GET_PARM( PARM_DELUXEDATA, 0 ); + world->shadowdata = (byte *)RENDER_GET_PARM( PARM_SHADOWDATA, 0 ); + + Mod_FinalizeWorld(); + Mod_CreateBufferObject(); + + // helper to precache shaders + R_InitDefaultLights(); + + // time to place grass + for( i = 0; i < worldmodel->numsurfaces; i++ ) + { + // place to initialize our grass + R_GrassInitForSurface( &worldmodel->surfaces[i] ); + } + + // precache world shaders + Mod_PrecacheShaders(); + + // creating float texture from worldlights + Mod_InitLightTexture(); + + // put bsp-tree into two texture (planes and nodes) + Mod_InitBSPTreeTexture(); +} + +static void Mod_FreeWorld( model_t *mod ) +{ + Mod_FreeCubemaps(); + + // destroy VBO & VAO + Mod_DeleteBufferObject(); + + if( world->leafs ) + Mem_Free( world->leafs ); + world->leafs = NULL; + + if( world->vertexes ) + Mem_Free( world->vertexes ); + world->vertexes = NULL; + + if( world->vertex_lighting ) + Mem_Free( world->vertex_lighting ); + world->vertex_lighting = NULL; + + if( world->surface_lighting ) + Mem_Free( world->surface_lighting ); + world->surface_lighting = NULL; + + // free old cinematics + R_FreeCinematics(); + + // free landscapes + R_FreeLandscapes(); + + if( tr.packed_lights_texture ) + { + FREE_TEXTURE( tr.packed_lights_texture ); + tr.packed_lights_texture = 0; + } + + if( tr.packed_planes_texture ) + { + FREE_TEXTURE( tr.packed_planes_texture ); + tr.packed_planes_texture = 0; + } + + if( tr.packed_nodes_texture ) + { + FREE_TEXTURE( tr.packed_nodes_texture ); + tr.packed_nodes_texture = 0; + } + + if( tr.packed_models_texture ) + { + FREE_TEXTURE( tr.packed_models_texture ); + tr.packed_models_texture = 0; + } + + if( world->materials ) + { + for( int i = 0; i < worldmodel->numtextures; i++ ) + { + texture_t *tx = worldmodel->textures[i]; + material_t *mat = &world->materials[i]; + + if( !mat->pSource ) continue; // not initialized? + + if( !FBitSet( mat->flags, BRUSH_MULTI_LAYERS )) + { + if( tx->gl_texturenum != mat->gl_diffuse_id ) + FREE_TEXTURE( mat->gl_diffuse_id ); + + if( mat->gl_normalmap_id != tr.normalmapTexture ) + FREE_TEXTURE( mat->gl_normalmap_id ); + + if( mat->gl_specular_id != tr.blackTexture ) + FREE_TEXTURE( mat->gl_specular_id ); + } + + if( mat->gl_glowmap_id != tr.blackTexture ) + FREE_TEXTURE( mat->gl_glowmap_id ); + } + + Mem_Free( world->materials ); + world->materials = NULL; + } + + // delete queries + for( int i = 0; i < worldmodel->numsurfaces; i++ ) + GL_DeleteOcclusionQuery( &worldmodel->surfaces[i] ); + + if( FBitSet( world->features, WORLD_HAS_GRASS )) + { + // throw grass vbo's + for( int i = 0; i < worldmodel->numsurfaces; i++ ) + R_RemoveGrassForSurface( worldmodel->surfaces[i].info ); + } + + memset( world, 0, sizeof( gl_world_t )); +} + +/* +================== +Mod_SetOrthoBounds + +setup ortho min\maxs for draw overview +================== +*/ +void Mod_SetOrthoBounds( const float *mins, const float *maxs ) +{ + if( !g_fRenderInitialized ) return; + + world->orthocenter.x = ((maxs[0] + mins[0]) * 0.5f); + world->orthocenter.y = ((maxs[1] + mins[1]) * 0.5f); + world->orthohalf.x = maxs[0] - world->orthocenter.x; + world->orthohalf.y = maxs[1] - world->orthocenter.y; +} + +/* +================== +R_ProcessWorldData + +resource management +================== +*/ +void R_ProcessWorldData( model_t *mod, qboolean create, const byte *buffer ) +{ + worldmodel = mod; + + if( create ) + { + double start = Sys_DoubleTime(); + Mod_LoadWorld( mod, buffer ); + double end = Sys_DoubleTime(); + r_buildstats.create_buffer_object += (end - start); + r_buildstats.total_buildtime += (end - start); + } + else Mod_FreeWorld( mod ); +} + +/* +============================================================================= + + WORLD RENDERING + +============================================================================= +*/ +static unsigned int tempElems[MAX_MAP_ELEMS]; +static unsigned int numTempElems; + +/* +================= +R_MarkSubmodelVisibleFaces + +do all the visibility checks, update surface +params, and mark them as visible +================= +*/ +void R_MarkSubmodelVisibleFaces( void ) +{ + cl_entity_t *e = RI->currententity; + model_t *model = RI->currentmodel; + Vector absmin, absmax; + CFrustum *frustum; + mplane_t plane; + msurface_t *surf; + mextrasurf_t *esrf; + gl_state_t *glm; + int i; + + // grab the transformed vieworg + glm = GL_GetCache( e->hCachedMatrix ); + + if( e->angles != g_vecZero ) + { + TransformAABB( glm->transform, model->mins, model->maxs, absmin, absmax ); + } + else + { + absmin = e->origin + model->mins; + absmax = e->origin + model->maxs; + } + + if( !Mod_CheckBoxVisible( absmin, absmax )) + { + r_stats.c_culled_entities++; + return; + } + + // frustum checking + if( R_CullBrushModel( e )) + { + r_stats.c_culled_entities++; + return; + } + + // mark all the visible faces (will added later) + for( i = 0; model != NULL && i < model->nummodelsurfaces; i++ ) + { + surf = model->surfaces + model->firstmodelsurface + i; + esrf = surf->info; + + esrf->parent = RI->currententity; // setup dynamic upcast + if( FBitSet( surf->flags, SURF_DRAWTURB )) + { + if( FBitSet( surf->flags, SURF_PLANEBACK )) + SetPlane( &plane, -surf->plane->normal, -surf->plane->dist ); + else SetPlane( &plane, surf->plane->normal, surf->plane->dist ); + glm->transform.TransformPositivePlane( plane, plane ); + + if( surf->plane->type != PLANE_Z && !FBitSet( e->curstate.effects, EF_WATERSIDES )) + continue; + if( absmin[2] + 1.0 >= plane.dist ) + continue; + } + + // non-moved bmodels can be culled by frustum + if( R_StaticEntity( e )) + frustum = &RI->view.frustum; + else frustum = NULL; + + int cull_type = R_CullSurface( surf, tr.modelorg, frustum ); + + if( cull_type >= CULL_FRUSTUM ) + continue; + + if( cull_type == CULL_BACKSIDE ) + { + if( !FBitSet( surf->flags, SURF_DRAWTURB )) + continue; + } + + SETVISBIT( RI->view.visfaces, model->firstmodelsurface + i ); + + // surface has passed all visibility checks + // and can be update some data (lightmaps, mirror matrix, etc) + if( RP_NORMALPASS( )) R_UpdateSurfaceParams( surf ); + } + + if( e->origin != e->baseline.vuser4 ) + { + R_FindWorldLights( e->origin, e->model->mins, e->model->maxs, e->lights, true ); +#if 0 + Msg( "%s gather lights: %d %d %d %d %d %d %d %d\n", e->model->name, + e->lights[0], e->lights[1], e->lights[2], e->lights[3], e->lights[4], e->lights[4], e->lights[6], e->lights[7] ); +#endif + e->baseline.vuser4 = e->origin; + } +} + +/* +================= +R_UpdateSubmodelParams + +run cinematic, evaluate conveyor etc +================= +*/ +void R_UpdateSubmodelParams( void ) +{ + cl_entity_t *e = RI->currententity; + model_t *model = RI->currentmodel; + msurface_t *surf; + mextrasurf_t *esrf; + + // mark all the visible faces (will added later) + for( int i = 0; model != NULL && i < model->nummodelsurfaces; i++ ) + { + surf = model->surfaces + model->firstmodelsurface + i; + esrf = surf->info; + + esrf->parent = RI->currententity; // setup dynamic upcast + + // and can be update some data (lightmaps, mirror matrix, etc) + R_UpdateSurfaceParams( surf ); + } +} + +_forceinline void R_DrawSurface( mextrasurf_t *es ) +{ + // accumulate the indices + for( int j = 0; j < es->numverts - 2; j++ ) + { + ASSERT( numTempElems < ( MAX_MAP_ELEMS - 3 )); + + tempElems[numTempElems++] = es->firstvertex; + tempElems[numTempElems++] = es->firstvertex + j + 1; + tempElems[numTempElems++] = es->firstvertex + j + 2; + } +} + +void R_MarkVisibleLights( byte lights[MAXDYNLIGHTS] ) +{ + // mark lights that visible for this frame + for( int i = 0; i < (int)cv_deferred_maxlights->value && lights[i] != 255; i++ ) + SETVISBIT( RI->view.vislight, lights[i] ); +} + +/* +================= +R_AddSurfaceToDrawList + +add specified face into sorted drawlist +================= +*/ +bool R_AddSurfaceToDrawList( msurface_t *surf, drawlist_t drawlist_type ) +{ + cl_entity_t *e = RI->currententity; + mextrasurf_t *es = surf->info; + word hProgram = 0; + CSolidEntry entry_s; + CTransEntry entry_t; + + switch( drawlist_type ) + { + case DRAWLIST_SOLID: + if( FBitSet( surf->flags, SURF_NODRAW )) + return false; + if( FBitSet( RI->params, RP_DEFERREDSCENE|RP_DEFERREDLIGHT )) + { + // precache shaders + Mod_ShaderSceneDeferred( surf ); + Mod_ShaderLightDeferred( surf ); + } + else hProgram = Mod_ShaderSceneForward( surf ); + R_MarkVisibleLights( es->lights ); + + entry_s.SetRenderSurface( surf, hProgram ); + RI->frame.solid_faces.AddToTail( entry_s ); + break; + case DRAWLIST_TRANS: + if( FBitSet( surf->flags, SURF_NODRAW )) + return false; + hProgram = Mod_ShaderSceneForward( surf ); + entry_t.SetRenderSurface( surf, hProgram ); + + if( ScreenCopyRequired( &glsl_programs[hProgram] )) + { + Vector mins, maxs; + gl_state_t *glm = GL_GetCache( e->hCachedMatrix ); + TransformAABB( glm->transform, es->mins, es->maxs, mins, maxs ); + // create sentinel border for refractions + ExpandBounds( mins, maxs, 2.0f ); + entry_t.ComputeScissor( mins, maxs ); + } + RI->frame.trans_list.AddToTail( entry_t ); + break; + case DRAWLIST_SHADOW: + if( FBitSet( surf->flags, SURF_NODRAW )) + return false; + hProgram = Mod_ShaderSceneDepth( surf ); + entry_s.SetRenderSurface( surf, hProgram ); + RI->frame.solid_faces.AddToTail( entry_s ); + break; + case DRAWLIST_LIGHT: + if( RI->currentlight->type == LIGHT_DIRECTIONAL ) + { + if( FBitSet( surf->flags, SURF_NOSUNLIGHT )) + return false; + } + else + { + if( FBitSet( surf->flags, SURF_NODLIGHT )) + return false; + } + hProgram = Mod_ShaderLightForward( RI->currentlight, surf ); + entry_s.SetRenderSurface( surf, hProgram ); + RI->frame.light_faces.AddToTail( entry_s ); + break; + case DRAWLIST_SUBVIEW: + if( RI->frame.num_subview_faces >= MAX_SUBVIEW_FACES ) + return false; + if( !FBitSet( surf->flags, SURF_REFLECT )) + { + if( !FBitSet( surf->flags, SURF_REFLECT_PUDDLE ) || !CVAR_TO_BOOL( cv_realtime_puddles )) + return false; // if realtime puddles not allowed + } + RI->frame.subview_faces[RI->frame.num_subview_faces] = surf; + RI->frame.num_subview_faces++; + GL_SurfaceOccluded( surf );// fetch queries result + break; + default: + HOST_ERROR( "R_AddSurfaceToDrawList: unknown drawlist( %i )\n", drawlist_type ); + break; + } + + return true; +} + +/* +================ +R_SetSurfaceUniforms + +================ +*/ +void R_SetSurfaceUniforms( word hProgram, msurface_t *surface, bool force ) +{ + mextrasurf_t *es = surface->info; + Vector4D lightstyles, lightdir; + cl_entity_t *e = es->parent; + msurface_t *s = surface; + float r, g, b, a, *v; + int map; + + // begin draw the sorted list + if( force || ( RI->currentshader != &glsl_programs[hProgram] )) + { + // force to bind new shader + GL_BindShader( &glsl_programs[hProgram] ); + } + + material_t *mat = R_TextureAnimation( s )->material; + glsl_program_t *shader = RI->currentshader; + CDynLight *pl = RI->currentlight; // may be NULL + gl_state_t *glm = GL_GetCache( e->hCachedMatrix ); + mtexinfo_t *tx = s->texinfo; + mfaceinfo_t *land = tx->faceinfo; + GLfloat viewMatrix[16]; + + if( !FBitSet( RI->params, RP_SHADOWVIEW )) + { + // setup some GL-states + if( e->curstate.renderfx == SKYBOX_ENTITY ) + { + GL_DepthRange( 0.8f, 0.9f ); + GL_ClipPlane( false ); + } + else + { + GL_DepthRange( gldepthmin, gldepthmax ); + GL_ClipPlane( true ); + } + + if( tr.waterlevel >= 3 && RP_NORMALPASS() && FBitSet( s->flags, SURF_DRAWTURB )) + GL_Cull( GL_BACK ); + else GL_Cull( GL_FRONT ); + } + + if( !FBitSet( RI->params, RP_DEFERREDLIGHT )) + { + if( FBitSet( mat->flags, BRUSH_TRANSPARENT|BRUSH_HAS_ALPHA )) + GL_AlphaTest( GL_TRUE ); + else GL_AlphaTest( GL_FALSE ); + } + + tr.modelorg = glm->GetModelOrigin(); + + // setup specified uniforms (and texture bindings) + for( int i = 0; i < shader->numUniforms; i++ ) + { + uniform_t *u = &shader->uniforms[i]; + + switch( u->type ) + { + case UT_COLORMAP: + if( Surf_CheckSubview( es )) + u->SetValue( Surf_GetSubview( es )->texturenum ); + else if( FBitSet( s->flags, SURF_MOVIE ) && RI->currententity->curstate.body ) + u->SetValue( tr.cinTextures[es->cintexturenum-1] ); + else u->SetValue( mat->gl_diffuse_id ); + break; + case UT_NORMALMAP: + if( FBitSet( mat->flags, BRUSH_LIQUID ) && tr.waterTextures[0] > 0 ) + u->SetValue( tr.waterTextures[(int)( tr.time * WATER_ANIMTIME ) % WATER_TEXTURES] ); + else u->SetValue( mat->gl_normalmap_id ); + break; + case UT_GLOSSMAP: + u->SetValue( mat->gl_specular_id ); + break; + case UT_DETAILMAP: + if( land && land->terrain && land->terrain->indexmap.gl_diffuse_id != 0 ) + u->SetValue( land->terrain->indexmap.gl_diffuse_id ); + else u->SetValue( mat->gl_detailmap_id ); + break; + case UT_PROJECTMAP: + if( pl && pl->type == LIGHT_SPOT ) + u->SetValue( pl->spotlightTexture ); + else u->SetValue( tr.whiteTexture ); + break; + case UT_SHADOWMAP: + case UT_SHADOWMAP0: + if( pl ) u->SetValue( pl->shadowTexture[0] ); + else u->SetValue( tr.depthTexture ); + break; + case UT_SHADOWMAP1: + if( pl ) u->SetValue( pl->shadowTexture[1] ); + else u->SetValue( tr.depthTexture ); + break; + case UT_SHADOWMAP2: + if( pl ) u->SetValue( pl->shadowTexture[2] ); + else u->SetValue( tr.depthTexture ); + break; + case UT_SHADOWMAP3: + if( pl ) u->SetValue( pl->shadowTexture[3] ); + else u->SetValue( tr.depthTexture ); + break; + case UT_LIGHTMAP: + if( R_FullBright( )) u->SetValue( tr.grayTexture ); + else u->SetValue( tr.lightmaps[es->lightmaptexturenum].lightmap ); + break; + case UT_DELUXEMAP: + if( R_FullBright( )) u->SetValue( tr.deluxemapTexture ); + else u->SetValue( tr.lightmaps[es->lightmaptexturenum].deluxmap ); + break; + case UT_DECALMAP: + // unacceptable for brushmodels + u->SetValue( tr.whiteTexture ); + break; + case UT_SCREENMAP: + u->SetValue( tr.screen_color ); + break; + case UT_DEPTHMAP: + u->SetValue( tr.screen_depth ); + break; + case UT_ENVMAP0: + case UT_ENVMAP: + if( es->cubemap[0] != NULL ) + u->SetValue( es->cubemap[0]->texture ); + else u->SetValue( tr.whiteCubeTexture ); + break; + case UT_ENVMAP1: + if( es->cubemap[1] != NULL ) + u->SetValue( es->cubemap[1]->texture ); + else u->SetValue( tr.whiteCubeTexture ); + break; + case UT_GLOWMAP: + u->SetValue( mat->gl_glowmap_id ); + break; + case UT_LAYERMAP: + if( FBitSet( mat->flags, BRUSH_MULTI_LAYERS ) && land && land->terrain ) + u->SetValue( land->terrain->indexmap.gl_heightmap_id ); + else u->SetValue( tr.whiteTexture ); + break; + case UT_HEIGHTMAP: + u->SetValue( mat->gl_heightmap_id ); + break; + case UT_BSPPLANESMAP: + u->SetValue( tr.packed_planes_texture ); + break; + case UT_BSPNODESMAP: + u->SetValue( tr.packed_nodes_texture ); + break; + case UT_BSPLIGHTSMAP: + u->SetValue( tr.packed_lights_texture ); + break; + case UT_FITNORMALMAP: + u->SetValue( tr.normalsFitting ); + break; + case UT_MODELMATRIX: + u->SetValue( &glm->modelMatrix[0] ); + break; + case UT_REFLECTMATRIX: + if( Surf_CheckSubview( es )) + Surf_GetSubview( es )->matrix.CopyToArray( viewMatrix ); + else memcpy( viewMatrix, glState.identityMatrix, sizeof( float ) * 16 ); + u->SetValue( &viewMatrix[0] ); + break; + case UT_SCREENSIZEINV: + u->SetValue( 1.0f / (float)glState.width, 1.0f / (float)glState.height ); + break; + case UT_ZFAR: + u->SetValue( -tr.farclip * 1.74f ); + break; + case UT_LIGHTSTYLES: + for( map = 0; map < MAXLIGHTMAPS; map++ ) + { + if( s->styles[map] != 255 ) + lightstyles[map] = tr.lightstyle[s->styles[map]]; + else lightstyles[map] = 0.0f; + } + u->SetValue( lightstyles.x, lightstyles.y, lightstyles.z, lightstyles.w ); + break; + case UT_LIGHTSTYLEVALUES: + u->SetValue( &tr.lightstyle[0], MAX_LIGHTSTYLES ); + break; + case UT_REALTIME: + u->SetValue( (float)tr.time ); + break; + case UT_DETAILSCALE: + u->SetValue( mat->detailScale[0], mat->detailScale[1] ); + break; + case UT_FOGPARAMS: + if( e->curstate.renderfx == SKYBOX_ENTITY ) + u->SetValue( tr.fogColor[0], tr.fogColor[1], tr.fogColor[2], tr.fogSkyDensity ); + else u->SetValue( tr.fogColor[0], tr.fogColor[1], tr.fogColor[2], tr.fogDensity ); + break; + case UT_SHADOWPARMS: + if( pl != NULL ) + { + float shadowWidth = 1.0f / (float)RENDER_GET_PARM( PARM_TEX_WIDTH, pl->shadowTexture[0] ); + float shadowHeight = 1.0f / (float)RENDER_GET_PARM( PARM_TEX_HEIGHT, pl->shadowTexture[0] ); + // depth scale and bias and shadowmap resolution + u->SetValue( shadowWidth, shadowHeight, -pl->projectionMatrix[2][2], pl->projectionMatrix[3][2] ); + } + else u->SetValue( 0.0f, 0.0f, 0.0f, 0.0f ); + break; + case UT_TEXOFFSET: + u->SetValue( es->texofs[0], es->texofs[1] ); + break; + case UT_VIEWORIGIN: + if( pl ) u->SetValue( GetVieworg().x, GetVieworg().y, GetVieworg().z ); + else u->SetValue( tr.modelorg.x, tr.modelorg.y, tr.modelorg.z, e->hCachedMatrix ? 1.0f : 0.0f ); + break; + case UT_VIEWRIGHT: + u->SetValue( GetVRight().x, GetVRight().y, GetVRight().z ); + break; + case UT_RENDERCOLOR: + if( e->curstate.rendermode == kRenderNormal ) + { + r = g = b = a = 1.0f; + } + else + { + int sum = (e->curstate.rendercolor.r + e->curstate.rendercolor.g + e->curstate.rendercolor.b); + + if(( sum > 0 ) && !FBitSet( s->flags, SURF_CONVEYOR )) + { + r = e->curstate.rendercolor.r / 255.0f; + g = e->curstate.rendercolor.g / 255.0f; + b = e->curstate.rendercolor.b / 255.0f; + } + else + { + r = g = b = 1.0f; + } + + if( e->curstate.rendermode != kRenderTransAlpha ) + a = Q_max( e->curstate.renderamt / 255.0f, 0.25f ); + else a = 1.0f; + } + u->SetValue( r, g, b, a ); + break; + case UT_SMOOTHNESS: + if( FBitSet( mat->flags, BRUSH_MULTI_LAYERS ) && land && land->terrain ) + { + terrain_t *terra = land->terrain; + u->SetValue( &terra->layermap.smoothness[0], terra->numLayers ); + } + else u->SetValue( mat->smoothness ); + break; + case UT_SHADOWMATRIX: + if( pl ) u->SetValue( &pl->gl_shadowMatrix[0][0], MAX_SHADOWMAPS ); + break; + case UT_SHADOWSPLITDIST: + v = RI->view.parallelSplitDistances; + u->SetValue( v[0], v[1], v[2], v[3] ); + break; + case UT_TEXELSIZE: + u->SetValue( 1.0f / (float)sunSize[0], 1.0f / (float)sunSize[1], 1.0f / (float)sunSize[2], 1.0f / (float)sunSize[3] ); + break; + case UT_GAMMATABLE: + u->SetValue( &tr.gamma_table[0][0], 64 ); + break; + case UT_LIGHTDIR: + if( pl ) + { + if( pl->type == LIGHT_DIRECTIONAL ) lightdir = -tr.sky_normal; + else lightdir = pl->frustum.GetPlane( FRUSTUM_FAR )->normal; + u->SetValue( lightdir.x, lightdir.y, lightdir.z, pl->fov ); + } + break; + case UT_LIGHTDIFFUSE: + if( pl ) u->SetValue( pl->color.x, pl->color.y, pl->color.z ); + break; + case UT_LIGHTORIGIN: + if( pl ) u->SetValue( pl->origin.x, pl->origin.y, pl->origin.z, ( 1.0f / pl->radius )); + break; + case UT_LIGHTVIEWPROJMATRIX: + if( pl ) + { + GLfloat gl_lightViewProjMatrix[16]; + pl->lightviewProjMatrix.CopyToArray( gl_lightViewProjMatrix ); + u->SetValue( &gl_lightViewProjMatrix[0] ); + } + break; + case UT_DIFFUSEFACTOR: + u->SetValue( tr.diffuseFactor ); + break; + case UT_AMBIENTFACTOR: + if( pl && pl->type == LIGHT_DIRECTIONAL ) + u->SetValue( tr.sun_ambient ); + else u->SetValue( tr.ambientFactor ); + break; + case UT_SUNREFRACT: + u->SetValue( tr.sun_refract ); + break; + case UT_LERPFACTOR: + u->SetValue( es->lerpFactor ); + break; + case UT_REFRACTSCALE: + u->SetValue( bound( 0.0f, mat->refractScale, 1.0f )); + break; + case UT_REFLECTSCALE: + u->SetValue( bound( 0.0f, mat->reflectScale, 1.0f )); + break; + case UT_ABERRATIONSCALE: + u->SetValue( bound( 0.0f, mat->aberrationScale, 1.0f )); + break; + case UT_BOXMINS: + if( world->num_cubemaps > 0 ) + { + Vector mins[2]; + mins[0] = es->cubemap[0]->mins; + mins[1] = es->cubemap[1]->mins; + u->SetValue( &mins[0][0], 2 ); + } + break; + case UT_BOXMAXS: + if( world->num_cubemaps > 0 ) + { + Vector maxs[2]; + maxs[0] = es->cubemap[0]->maxs; + maxs[1] = es->cubemap[1]->maxs; + u->SetValue( &maxs[0][0], 2 ); + } + break; + case UT_CUBEORIGIN: + if( world->num_cubemaps > 0 ) + { + Vector origin[2]; + origin[0] = es->cubemap[0]->origin; + origin[1] = es->cubemap[1]->origin; + u->SetValue( &origin[0][0], 2 ); + } + break; + case UT_CUBEMIPCOUNT: + if( world->num_cubemaps > 0 ) + { + r = Q_max( 1, es->cubemap[0]->numMips - cv_cube_lod_bias->value ); + g = Q_max( 1, es->cubemap[1]->numMips - cv_cube_lod_bias->value ); + u->SetValue( r, g ); + } + break; + case UT_LIGHTNUMS0: + u->SetValue( (float)e->lights[0], (float)e->lights[1], (float)e->lights[2], (float)e->lights[3] ); + break; + case UT_LIGHTNUMS1: + u->SetValue( (float)e->lights[4], (float)e->lights[5], (float)e->lights[6], (float)e->lights[7] ); + break; + default: + ALERT( at_error, "%s: unhandled uniform %s\n", RI->currentshader->name, u->name ); + break; + } + } +} + +/* +================ +R_RenderDynLightList + +================ +*/ +void R_BuildFaceListForLight( CDynLight *pl, bool solid ) +{ + int i; + + RI->currententity = GET_ENTITY( 0 ); + RI->currentmodel = RI->currententity->model; + RI->frame.light_faces.Purge(); + RI->frame.light_grass.Purge(); + tr.modelorg = pl->origin; + + if( solid ) + { + // only visible polys passed through the light list + for( i = 0; i < RI->frame.solid_faces.Count(); i++ ) + { + CSolidEntry *entry = &RI->frame.solid_faces[i]; + + if( entry->m_bDrawType != DRAWTYPE_SURFACE ) + continue; + + mextrasurf_t *es = entry->m_pSurf->info; + gl_state_t *glm = GL_GetCache( es->parent->hCachedMatrix ); + + RI->currententity = es->parent; + RI->currentmodel = RI->currententity->model; + + if( RI->currententity->curstate.renderfx == SKYBOX_ENTITY ) + continue; // fast reject + + if( FBitSet( RI->currententity->curstate.effects, EF_FULLBRIGHT )) + continue; + + bool worldpos = R_StaticEntity( es->parent ) ? true : false; + CFrustum *frustum = (worldpos) ? &pl->frustum : NULL; + tr.modelorg = glm->GetModelOrigin(); + + R_AddGrassToDrawList( entry->m_pSurf, DRAWLIST_LIGHT ); + + if( R_CullSurface( entry->m_pSurf, tr.modelorg, frustum )) + continue; + + // move from main list into light list + R_AddSurfaceToDrawList( entry->m_pSurf, DRAWLIST_LIGHT ); + } + } + else + { + // only visible polys passed through the light list + for( i = 0; i < RI->frame.trans_list.Count(); i++ ) + { + CTransEntry *entry = &RI->frame.trans_list[i]; + + if( entry->m_bDrawType != DRAWTYPE_SURFACE ) + continue; + + mextrasurf_t *es = entry->m_pSurf->info; + gl_state_t *glm = GL_GetCache( es->parent->hCachedMatrix ); + + RI->currententity = es->parent; + RI->currentmodel = RI->currententity->model; + + if( RI->currententity->curstate.renderfx == SKYBOX_ENTITY ) + continue; // fast reject + + if( FBitSet( RI->currententity->curstate.effects, EF_FULLBRIGHT )) + continue; + + bool worldpos = R_StaticEntity( es->parent ) ? true : false; + CFrustum *frustum = (worldpos) ? &pl->frustum : NULL; + tr.modelorg = glm->GetModelOrigin(); + + R_AddGrassToDrawList( entry->m_pSurf, DRAWLIST_LIGHT ); + + if( R_CullSurface( entry->m_pSurf, tr.modelorg, frustum )) + continue; + + // move from main list into light list + R_AddSurfaceToDrawList( entry->m_pSurf, DRAWLIST_LIGHT ); + } + } +} + +/* +================ +R_DrawLightForSurfList + +setup light projection for each +================ +*/ +void R_DrawLightForSurfList( CDynLight *pl ) +{ + material_t *cached_material = NULL; + cl_entity_t *cached_entity = NULL; + bool flush_buffer = false; + int startv, endv; + + pglBindVertexArray( world->vertex_array_object ); + pglBlendFunc( GL_ONE, GL_ONE ); + float y2 = (float)RI->view.port[3] - pl->h - pl->y; + pglScissor( pl->x, y2, pl->w, pl->h ); + numTempElems = 0; + + for( int i = 0; i < RI->frame.light_faces.Count(); i++ ) + { + CSolidEntry *entry = &RI->frame.light_faces[i]; + + if( entry->m_bDrawType != DRAWTYPE_SURFACE ) + continue; + + mextrasurf_t *es = entry->m_pSurf->info; + cl_entity_t *e = RI->currententity = es->parent; + RI->currentmodel = e->model; + msurface_t *s = entry->m_pSurf; + + if( !entry->m_hProgram ) continue; + + material_t *mat = R_TextureAnimation( s )->material; + + if(( i == 0 ) || ( RI->currentshader != &glsl_programs[entry->m_hProgram] )) + flush_buffer = true; + + if( cached_entity != RI->currententity ) + flush_buffer = true; + + if( cached_material != mat ) + flush_buffer = true; + + if( flush_buffer ) + { + if( numTempElems ) + { + pglDrawRangeElementsEXT( GL_TRIANGLES, startv, endv - 1, numTempElems, GL_UNSIGNED_INT, tempElems ); + r_stats.c_total_tris += (numTempElems / 3); + r_stats.num_flushes++; + numTempElems = 0; + } + + flush_buffer = false; + startv = MAX_MAP_ELEMS; + endv = 0; + } + + // now cache values + cached_entity = RI->currententity; + cached_material = mat; + + if( numTempElems == 0 ) // new chain has started, apply uniforms + R_SetSurfaceUniforms( entry->m_hProgram, entry->m_pSurf, ( i == 0 )); + startv = Q_min( startv, es->firstvertex ); + endv = Q_max( es->firstvertex + es->numverts, endv ); + + R_DrawSurface( es ); + } + + if( numTempElems ) + { + pglDrawRangeElementsEXT( GL_TRIANGLES, startv, endv - 1, numTempElems, GL_UNSIGNED_INT, tempElems ); + r_stats.c_total_tris += (numTempElems / 3); + r_stats.num_flushes++; + startv = MAX_MAP_ELEMS; + numTempElems = 0; + endv = 0; + } + + R_DrawLightForGrass( pl ); +} + +/* +================ +R_RenderDynLightList + +draw dynamic lights for world and bmodels +================ +*/ +void R_RenderDynLightList( bool solid ) +{ + if( FBitSet( RI->params, RP_ENVVIEW|RP_SKYVIEW )) + return; + + if( !FBitSet( RI->view.flags, RF_HASDYNLIGHTS )) + return; + + if( R_FullBright( )) return; + + GL_Blend( GL_TRUE ); + GL_AlphaTest( GL_FALSE ); + GL_DepthMask( GL_FALSE ); + + CDynLight *pl = tr.dlights; + + for( int i = 0; i < MAX_DLIGHTS; i++, pl++ ) + { + if( pl->Expired( )) continue; + + if( pl->type == LIGHT_SPOT || pl->type == LIGHT_OMNI ) + { + if( !pl->Active( )) continue; + + if( !Mod_CheckBoxVisible( pl->absmin, pl->absmax )) + continue; + + if( R_CullFrustum( &pl->frustum )) + continue; + + pglEnable( GL_SCISSOR_TEST ); + } + else + { + // couldn't use scissor for sunlight + pglDisable( GL_SCISSOR_TEST ); + } + + RI->currentlight = pl; + + // draw world from light position + R_BuildFaceListForLight( pl, solid ); + + if( !RI->frame.light_faces.Count() && !RI->frame.light_grass.Count() ) + continue; // no interaction with this light? + + R_DrawLightForSurfList( pl ); + } + + GL_CleanupDrawState(); + pglDisable( GL_SCISSOR_TEST ); + RI->currentlight = NULL; +} + +/* +================ +R_RenderDeferredBrushList +================ +*/ +void R_RenderDeferredBrushList( void ) +{ + cl_entity_t *cached_entity = NULL; + material_t *cached_material = NULL; + mcubemap_t *cached_cubemap[2]; + bool flush_buffer = false; + int startv, endv; + + if( !RI->frame.solid_faces.Count() ) + return; + + GL_Blend( GL_FALSE ); + GL_AlphaTest( GL_FALSE ); + GL_DepthMask( GL_TRUE ); + pglAlphaFunc( GL_GREATER, 0.25f ); + numTempElems = 0; + + if( GL_Support( R_SEAMLESS_CUBEMAP )) + pglEnable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); + + pglBindVertexArray( world->vertex_array_object ); + cached_cubemap[0] = &world->defaultCubemap; + cached_cubemap[1] = &world->defaultCubemap; + + for( int i = 0; i < RI->frame.solid_faces.Count(); i++ ) + { + CSolidEntry *entry = &RI->frame.solid_faces[i]; + + if( entry->m_bDrawType != DRAWTYPE_SURFACE ) + continue; + + mextrasurf_t *es = entry->m_pSurf->info; + cl_entity_t *e = RI->currententity = es->parent; + RI->currentmodel = e->model; + msurface_t *s = entry->m_pSurf; + + if( FBitSet( RI->params, RP_DEFERREDSCENE )) + entry->m_hProgram = es->deferredScene.GetHandle(); + else if( FBitSet( RI->params, RP_DEFERREDLIGHT )) + entry->m_hProgram = es->deferredLight.GetHandle(); + else entry->m_hProgram = 0; + + if( !entry->m_hProgram ) continue; + + material_t *mat = R_TextureAnimation( s )->material; + + if(( i == 0 ) || ( RI->currentshader != &glsl_programs[entry->m_hProgram] )) + flush_buffer = true; + + if( cached_entity != RI->currententity ) + flush_buffer = true; + + if( cached_material != mat ) + flush_buffer = true; + + if( IsReflectShader( RI->currentshader ) && ( cached_cubemap[0] != es->cubemap[0] || cached_cubemap[1] != es->cubemap[1] )) + flush_buffer = true; + + if( flush_buffer ) + { + if( numTempElems ) + { + pglDrawRangeElementsEXT( GL_TRIANGLES, startv, endv - 1, numTempElems, GL_UNSIGNED_INT, tempElems ); + r_stats.c_total_tris += (numTempElems / 3); + r_stats.num_flushes++; + numTempElems = 0; + } + + flush_buffer = false; + startv = MAX_MAP_ELEMS; + endv = 0; + } + + // now cache values + cached_entity = RI->currententity = es->parent; + RI->currentmodel = es->parent->model; + cached_cubemap[0] = es->cubemap[0]; + cached_cubemap[1] = es->cubemap[1]; + cached_material = mat; + + if( numTempElems == 0 ) // new chain has started, apply uniforms + R_SetSurfaceUniforms( entry->m_hProgram, entry->m_pSurf, ( i == 0 )); + startv = Q_min( startv, es->firstvertex ); + endv = Q_max( es->firstvertex + es->numverts, endv ); + + R_DrawSurface( es ); + } + + if( numTempElems ) + { + pglDrawRangeElementsEXT( GL_TRIANGLES, startv, endv - 1, numTempElems, GL_UNSIGNED_INT, tempElems ); + r_stats.c_total_tris += (numTempElems / 3); + r_stats.num_flushes++; + startv = MAX_MAP_ELEMS; + numTempElems = 0; + endv = 0; + } + + if( GL_Support( R_SEAMLESS_CUBEMAP )) + pglDisable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); + + GL_DepthRange( gldepthmin, gldepthmax ); + GL_SelectTexture( glConfig.max_texture_units - 1 ); // force to cleanup all the units + GL_CleanUpTextureUnits( 0 ); + GL_AlphaTest( GL_FALSE ); + GL_ClipPlane( true ); + + if( FBitSet( RI->params, RP_DEFERREDSCENE )) + { + // render all decals with alpha-channel + R_RenderDecalsSolidList( DRAWLIST_SOLID ); + // render all decals with gray base + R_RenderDecalsSolidList( DRAWLIST_TRANS ); + } + + // draw grass on visible surfaces + R_RenderGrassOnList(); + + GL_CleanupDrawState(); +} + +/* +================ +R_RenderSolidBrushList +================ +*/ +void R_RenderSolidBrushList( void ) +{ + cl_entity_t *cached_entity = NULL; + material_t *cached_material = NULL; + int cached_mirror = -1; + int cached_lightmap = -1; + qboolean flush_buffer = false; + mcubemap_t *cached_cubemap[2]; + int startv, endv; + + if( !RI->frame.solid_faces.Count() ) + return; + + GL_Blend( GL_FALSE ); + GL_AlphaTest( GL_FALSE ); + GL_DepthMask( GL_TRUE ); + pglAlphaFunc( GL_GREATER, 0.25f ); + numTempElems = 0; + + if( GL_Support( R_SEAMLESS_CUBEMAP )) + pglEnable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); + + pglBindVertexArray( world->vertex_array_object ); + cached_cubemap[0] = &world->defaultCubemap; + cached_cubemap[1] = &world->defaultCubemap; + + for( int i = 0; i < RI->frame.solid_faces.Count(); i++ ) + { + CSolidEntry *entry = &RI->frame.solid_faces[i]; + + if( entry->m_bDrawType != DRAWTYPE_SURFACE ) + continue; + + mextrasurf_t *es = entry->m_pSurf->info; + cl_entity_t *e = RI->currententity = es->parent; + RI->currentmodel = e->model; + msurface_t *s = entry->m_pSurf; + + if( !entry->m_hProgram ) continue; + + material_t *mat = R_TextureAnimation( s )->material; + + if(( i == 0 ) || ( RI->currentshader != &glsl_programs[entry->m_hProgram] )) + flush_buffer = true; + + if( cached_entity != RI->currententity ) + flush_buffer = true; + + if( cached_material != mat ) + flush_buffer = true; + + if( cached_lightmap != es->lightmaptexturenum ) + flush_buffer = true; + + if( cached_mirror != es->subtexture[glState.stack_position] ) + flush_buffer = true; + + if( IsReflectShader( RI->currentshader ) && ( cached_cubemap[0] != es->cubemap[0] || cached_cubemap[1] != es->cubemap[1] )) + flush_buffer = true; + + if( flush_buffer ) + { + if( numTempElems ) + { + pglDrawRangeElementsEXT( GL_TRIANGLES, startv, endv - 1, numTempElems, GL_UNSIGNED_INT, tempElems ); + r_stats.c_total_tris += (numTempElems / 3); + r_stats.num_flushes++; + numTempElems = 0; + } + + flush_buffer = false; + startv = MAX_MAP_ELEMS; + endv = 0; + } + + // now cache values + cached_entity = RI->currententity = es->parent; + cached_lightmap = es->lightmaptexturenum; + RI->currentmodel = es->parent->model; + cached_mirror = es->subtexture[glState.stack_position]; + cached_cubemap[0] = es->cubemap[0]; + cached_cubemap[1] = es->cubemap[1]; + cached_material = mat; + + if( numTempElems == 0 ) // new chain has started, apply uniforms + R_SetSurfaceUniforms( entry->m_hProgram, entry->m_pSurf, ( i == 0 )); + startv = Q_min( startv, es->firstvertex ); + endv = Q_max( es->firstvertex + es->numverts, endv ); + + R_DrawSurface( es ); + } + + if( numTempElems ) + { + pglDrawRangeElementsEXT( GL_TRIANGLES, startv, endv - 1, numTempElems, GL_UNSIGNED_INT, tempElems ); + r_stats.c_total_tris += (numTempElems / 3); + r_stats.num_flushes++; + startv = MAX_MAP_ELEMS; + numTempElems = 0; + endv = 0; + } + + if( GL_Support( R_SEAMLESS_CUBEMAP )) + pglDisable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); + + GL_DepthRange( gldepthmin, gldepthmax ); + GL_CleanupDrawState(); + GL_AlphaTest( GL_FALSE ); + GL_ClipPlane( true ); + GL_Cull( GL_FRONT ); + + // render all decals with alpha-channel + R_RenderDecalsSolidList( DRAWLIST_SOLID ); + + // draw grass on visible surfaces + R_RenderGrassOnList(); + + // draw dynamic lighting for world and bmodels + R_RenderDynLightList( true ); + + GL_CleanupDrawState(); + + // render all decals with gray base + R_RenderDecalsSolidList( DRAWLIST_TRANS ); +} + +void R_RenderTransSurface( CTransEntry *entry ) +{ + if( entry->m_bDrawType != DRAWTYPE_SURFACE ) + return; + + if( !entry->m_hProgram ) return; + + mextrasurf_t *es = entry->m_pSurf->info; + cl_entity_t *e = RI->currententity = es->parent; + msurface_t *s = entry->m_pSurf; + RI->currentmodel = e->model; + int startv = MAX_MAP_ELEMS; + numTempElems = 0; + int endv = 0; + + if( ScreenCopyRequired( &glsl_programs[entry->m_hProgram] )) + { + if( !FBitSet( s->flags, SURF_OCCLUDED )) + { + entry->RequestScreenColor(); + + // underwater fog + if( R_WaterEntity( RI->currentmodel )) + entry->RequestScreenDepth(); + r_stats.c_screen_copy++; + } + } + + pglBindVertexArray( world->vertex_array_object ); + R_SetSurfaceUniforms( entry->m_hProgram, entry->m_pSurf, true ); + startv = Q_min( startv, es->firstvertex ); + endv = Q_max( es->firstvertex + es->numverts, endv ); + + if( FBitSet( glsl_programs[entry->m_hProgram].status, SHADER_ADDITIVE )) + { + GL_DepthMask( GL_FALSE ); + GL_Blend( GL_TRUE ); + pglBlendFunc( GL_SRC_ALPHA, GL_ONE ); + } + else + { + GL_DepthMask( GL_TRUE ); + GL_Blend( GL_FALSE ); + } + R_DrawSurface( es ); + + if( numTempElems ) + { + pglDrawRangeElementsEXT( GL_TRIANGLES, startv, endv - 1, numTempElems, GL_UNSIGNED_INT, tempElems ); + r_stats.c_total_tris += (numTempElems / 3); + r_stats.num_flushes++; + numTempElems = 0; + } + + GL_DepthMask( GL_TRUE ); + GL_Blend( GL_FALSE ); + GL_ClipPlane( true ); + GL_Cull( GL_FRONT ); +} + +/* +================ +R_RenderShadowBrushList + +================ +*/ +void R_RenderShadowBrushList( void ) +{ + int cached_matrix = -1; + material_t *cached_material = NULL; + qboolean flush_buffer = false; + int startv, endv; + + if( !RI->frame.solid_faces.Count() ) + return; + + pglBindVertexArray( world->vertex_array_object ); + pglAlphaFunc( GL_GREATER, 0.25f ); + numTempElems = 0; + + for( int i = 0; i < RI->frame.solid_faces.Count(); i++ ) + { + CSolidEntry *entry = &RI->frame.solid_faces[i]; + + if( entry->m_bDrawType != DRAWTYPE_SURFACE ) + continue; + + mextrasurf_t *es = entry->m_pSurf->info; + cl_entity_t *e = RI->currententity = es->parent; + RI->currentmodel = e->model; + msurface_t *s = entry->m_pSurf; + + if( !entry->m_hProgram ) continue; + + material_t *mat = s->texinfo->texture->material; + + if(( i == 0 ) || ( RI->currentshader != &glsl_programs[entry->m_hProgram] )) + flush_buffer = true; + + if( cached_matrix != es->parent->hCachedMatrix ) + flush_buffer = true; + + if( cached_material != mat ) + flush_buffer = true; + + if( flush_buffer ) + { + if( numTempElems ) + { + pglDrawRangeElementsEXT( GL_TRIANGLES, startv, endv - 1, numTempElems, GL_UNSIGNED_INT, tempElems ); + r_stats.c_total_tris += (numTempElems / 3); + r_stats.num_flushes++; + numTempElems = 0; + } + + flush_buffer = false; + startv = MAX_MAP_ELEMS; + endv = 0; + } + + // now cache values + cached_matrix = es->parent->hCachedMatrix; + cached_material = mat; + + if( numTempElems == 0 ) // new chain has started, apply uniforms + R_SetSurfaceUniforms( entry->m_hProgram, entry->m_pSurf, ( i == 0 )); + startv = Q_min( startv, es->firstvertex ); + endv = Q_max( es->firstvertex + es->numverts, endv ); + + R_DrawSurface( es ); + } + + if( numTempElems ) + { + pglDrawRangeElementsEXT( GL_TRIANGLES, startv, endv - 1, numTempElems, GL_UNSIGNED_INT, tempElems ); + r_stats.c_total_tris += (numTempElems / 3); + r_stats.num_flushes++; + startv = MAX_MAP_ELEMS; + numTempElems = 0; + endv = 0; + } + + R_RenderShadowGrassOnList(); + GL_CleanupDrawState(); +} \ No newline at end of file diff --git a/cl_dll/render/rain.cpp b/cl_dll/render/rain.cpp new file mode 100644 index 0000000..0dfbbdc --- /dev/null +++ b/cl_dll/render/rain.cpp @@ -0,0 +1,794 @@ +//========================================= +// rain.cpp +// written by BUzer +//========================================= + +#include +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "parsemsg.h" +#include "entity_types.h" +#include "cdll_int.h" +#include "pm_defs.h" +#include "event_api.h" +#include "custom_alloc.h" +#include "triangleapi.h" +#include "gl_local.h" + +#define DRIPSPEED 900 // ñêîðîñòü ïàäåíèÿ êàïåëü (ïèêñ â ñåê) +#define SNOWSPEED 200 // ñêîðîñòü ïàäåíèÿ ñíåæèíîê +#define SNOWFADEDIST 80 + +#define MAX_RAIN_VERTICES 65536 // snowflakes and waterrings draw as quads +#define MAX_RAIN_INDICES MAX_RAIN_VERTICES * 4 + +#define MAXDRIPS 40000 // ëèìèò êàïåëü (ìîæíî óâåëè÷èòü ïðè íåîáõîäèìîñòè) +#define MAXFX 20000 // ëèìèò äîïîëíèòåëüíûõ ÷àñòèö (êðóãè ïî âîäå è ò.ï.) + +#define MODE_RAIN 0 +#define MODE_SNOW 1 + +#define DRIP_SPRITE_HALFHEIGHT 46 +#define DRIP_SPRITE_HALFWIDTH 8 +#define SNOW_SPRITE_HALFSIZE 3 +#define MAX_RING_HALFSIZE 25 // "ðàäèóñ" êðóãà íà âîäå, äî êîòîðîãî îí ðàçðàñòàåòñÿ çà ñåêóíäó + +typedef struct +{ + int dripsPerSecond; + float distFromPlayer; + float windX, windY; + float randX, randY; + int weatherMode; // 0 - snow, 1 - rain + float globalHeight; +} rain_properties; + +typedef struct cl_drip +{ + float birthTime; + float minHeight; // êàïëÿ áóäåò óíè÷òîæåíà íà ýòîé âûñîòå. + Vector origin; + float alpha; + float xDelta; // side speed + float yDelta; + int landInWater; +} cl_drip_t; + +typedef struct cl_rainfx +{ + float birthTime; + float life; + Vector origin; + float alpha; +} cl_rainfx_t; + +void WaterLandingEffect(cl_drip *drip); + + +rain_properties Rain; +MemBlock g_dripsArray( MAXDRIPS ); +MemBlock g_fxArray( MAXFX ); + +cvar_t *cl_draw_rain = NULL; +cvar_t *cl_debug_rain = NULL; + +double rain_curtime; // current time +double rain_oldtime; // last time we have updated drips +double rain_timedelta; // difference between old time and current time +double rain_nextspawntime; // when the next drip should be spawned +int dripcounter = 0; +int fxcounter = 0; + +static Vector rain_mins, rain_maxs; // for vis culling +static Vector m_vertexarray[MAX_RAIN_VERTICES]; +static byte m_colorarray[MAX_RAIN_VERTICES][4]; +static Vector2D m_coordsarray[MAX_RAIN_VERTICES]; +static word m_indexarray[MAX_RAIN_INDICES]; +static int m_iNumVerts, m_iNumIndex; + +/* +================================= +ProcessRain +Ïåðåìåùàåò ñóùåñòâóþùèå îáúåêòû, óäàëÿåò èõ ïðè íàäîáíîñòè, +è, åñëè äîæäü âêëþ÷åí, ñîçäàåò íîâûå. + +Äîëæíà âûçûâàòüñÿ êàæäûé êàäð. +================================= +*/ +void ProcessRain( void ) +{ + rain_oldtime = rain_curtime; // save old time + rain_curtime = GET_CLIENT_TIME(); + rain_timedelta = rain_curtime - rain_oldtime; + + // first frame + if( rain_oldtime == 0 ) + { + // fix first frame bug with nextspawntime + rain_nextspawntime = rain_curtime; + return; + } + + if(( Rain.dripsPerSecond == 0 && g_dripsArray.IsClear( )) || rain_timedelta > 0.1f ) + { + rain_timedelta = min( rain_timedelta, 0.1f ); + + // keep nextspawntime correct + rain_nextspawntime = rain_curtime; + return; + } + + if( rain_timedelta == 0 ) return; // not in pause + + double timeBetweenDrips = 1.0 / (double)Rain.dripsPerSecond; + + if( !g_dripsArray.StartPass( )) + { + Rain.dripsPerSecond = 0; // disable rain + ALERT( at_error, "rain: failed to allocate memory block!\n" ); + return; + } + + cl_drip *curDrip = g_dripsArray.GetCurrent(); + + // õðàíåíèå îòëàäî÷íîé èíôîðìàöèè + float debug_lifetime = 0; + int debug_howmany = 0; + int debug_attempted = 0; + int debug_dropped = 0; + + ClearBounds( rain_mins, rain_maxs ); + + while( curDrip != NULL ) // go through list + { + if( Rain.weatherMode == MODE_RAIN ) + curDrip->origin.z -= rain_timedelta * DRIPSPEED; + else if( Rain.weatherMode == MODE_SNOW ) + curDrip->origin.z -= rain_timedelta * SNOWSPEED; + else return; + + curDrip->origin.x += rain_timedelta * curDrip->xDelta; + curDrip->origin.y += rain_timedelta * curDrip->yDelta; +#if 1 + // unrolled version of AddPointToBounds (perf) + if( curDrip->origin[0] < rain_mins[0] ) rain_mins[0] = curDrip->origin[0]; + if( curDrip->origin[0] > rain_maxs[0] ) rain_maxs[0] = curDrip->origin[0]; + if( curDrip->origin[1] < rain_mins[1] ) rain_mins[1] = curDrip->origin[1]; + if( curDrip->origin[1] > rain_maxs[1] ) rain_maxs[1] = curDrip->origin[1]; + if( curDrip->origin[2] < rain_mins[2] ) rain_mins[2] = curDrip->origin[2]; + if( curDrip->origin[2] > rain_maxs[2] ) rain_maxs[2] = curDrip->origin[2]; +#else + AddPointToBounds( curDrip->origin, rain_mins, rain_maxs ); +#endif + // remove drip if its origin lower than minHeight + if( curDrip->origin.z < curDrip->minHeight ) + { + if( curDrip->landInWater ) + WaterLandingEffect( curDrip ); // create water rings + + if( cl_debug_rain->value ) + { + debug_lifetime += (rain_curtime - curDrip->birthTime); + debug_howmany++; + } + + g_dripsArray.DeleteCurrent(); + dripcounter--; + } + else + g_dripsArray.MoveNext(); + + curDrip = g_dripsArray.GetCurrent(); + } + + int maxDelta; // maximum height randomize distance + float falltime; + + if( Rain.weatherMode == MODE_RAIN ) + { + maxDelta = DRIPSPEED * rain_timedelta; // for rain + falltime = (Rain.globalHeight + 4096) / DRIPSPEED; + } + else + { + maxDelta = SNOWSPEED * rain_timedelta; // for snow + falltime = (Rain.globalHeight + 4096) / SNOWSPEED; + } + + while( rain_nextspawntime < rain_curtime ) + { + rain_nextspawntime += timeBetweenDrips; + + if( cl_debug_rain->value ) + debug_attempted++; + + // check for overflow + if( dripcounter < MAXDRIPS ) + { + float deathHeight; + vec3_t vecStart, vecEnd; + + vecStart[0] = RANDOM_FLOAT( GetVieworg().x - Rain.distFromPlayer, GetVieworg().x + Rain.distFromPlayer ); + vecStart[1] = RANDOM_FLOAT( GetVieworg().y - Rain.distFromPlayer, GetVieworg().y + Rain.distFromPlayer ); + vecStart[2] = Rain.globalHeight; + + float xDelta = Rain.windX + RANDOM_FLOAT( Rain.randX * -1, Rain.randX ); + float yDelta = Rain.windY + RANDOM_FLOAT( Rain.randY * -1, Rain.randY ); + + // find a point at bottom of map + vecEnd[0] = falltime * xDelta; + vecEnd[1] = falltime * yDelta; + vecEnd[2] = -4096; + + pmtrace_t pmtrace; + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PlayerTrace( vecStart, vecStart + vecEnd, PM_STUDIO_IGNORE|PM_CUSTOM_IGNORE, -1, &pmtrace ); + + if( pmtrace.startsolid || pmtrace.allsolid ) + { + if( cl_debug_rain->value ) + debug_dropped++; + continue; // drip cannot be placed + } + + // falling to water? + int contents = gEngfuncs.PM_PointContents( pmtrace.endpos, NULL ); + + if( contents == CONTENTS_WATER ) + { + int waterEntity = WATER_ENTITY( pmtrace.endpos ); + + if( waterEntity > 0 ) + { + cl_entity_t *pwater = gEngfuncs.GetEntityByIndex( waterEntity ); + + if( pwater && ( pwater->model != NULL )) + { + deathHeight = pwater->curstate.maxs.z - 1.0f; + + if( !Mod_BoxVisible( pwater->curstate.mins, pwater->curstate.maxs, Mod_GetCurrentVis( ))) + contents = CONTENTS_EMPTY; // not error, just water out of PVS + } + else + { + ALERT( at_error, "rain: can't get water entity\n" ); + continue; + } + } + else + { + ALERT( at_error, "rain: water is not func_water entity\n" ); + continue; + } + } + else + { + deathHeight = pmtrace.endpos.z; + } + + // just in case.. + if( deathHeight > vecStart.z ) + { + ALERT( at_error, "rain: can't create drip in water\n"); + continue; + } + + + cl_drip *newClDrip = g_dripsArray.Allocate(); + + if( !newClDrip ) + { + Rain.dripsPerSecond = 0; // disable rain + ALERT( at_error, "rain: failed to allocate object!\n" ); + return; + } + + vecStart.z -= RANDOM_FLOAT( 0, maxDelta ); // randomize a bit + + newClDrip->alpha = RANDOM_FLOAT( 0.12f, 0.2f ); + newClDrip->origin = vecStart; + + newClDrip->xDelta = xDelta; + newClDrip->yDelta = yDelta; + + newClDrip->birthTime = rain_curtime; // store time when it was spawned + newClDrip->minHeight = deathHeight; + + if( contents == CONTENTS_WATER ) + newClDrip->landInWater = 1; + else + newClDrip->landInWater = 0; + + // add to first place in chain + dripcounter++; + } + else + { + ALERT( at_error, "rain: Drip limit overflow!\n" ); + return; + } + } + + if( cl_debug_rain->value ) + { + // print debug info + gEngfuncs.Con_NPrintf( 1, "rain info: Drips exist: %i\n", dripcounter ); + gEngfuncs.Con_NPrintf( 2, "rain info: FX's exist: %i\n", fxcounter ); + gEngfuncs.Con_NPrintf( 3, "rain info: Attempted/Dropped: %i, %i\n", debug_attempted, debug_dropped ); + + if( debug_howmany ) + { + float ave = debug_lifetime / (float)debug_howmany; + gEngfuncs.Con_NPrintf( 4, "rain info: Average drip life time: %f\n", ave ); + } + else + gEngfuncs.Con_NPrintf( 4, "rain info: Average drip life time: --\n" ); + } +} + +/* +================================= +WaterLandingEffect +ñîçäàåò êðóã íà âîäíîé ïîâåðõíîñòè +================================= +*/ +void WaterLandingEffect( cl_drip *drip ) +{ + if( fxcounter >= MAXFX ) + { + ALERT( at_error, "rain: FX limit overflow!\n" ); + return; + } + + cl_rainfx *newFX = g_fxArray.Allocate(); + if( !newFX ) + { + ALERT( at_error, "rain: failed to allocate FX object!\n" ); + return; + } + + newFX->alpha = RANDOM_FLOAT( 0.6f, 0.9f ); + newFX->origin = drip->origin; + newFX->origin.z = drip->minHeight - 1; // correct position + + newFX->birthTime = GET_CLIENT_TIME(); + newFX->life = RANDOM_FLOAT( 0.7f, 1.0f ); + + // add to first place in chain + fxcounter++; +} + +/* +================================= +ProcessFXObjects +óäàëÿåò FX îáúåêòû, ó êîòîðûõ âûøåë ñðîê æèçíè + +Êàæäûé êàäð âûçûâàåòñÿ ïåðåä ProcessRain +================================= +*/ +void ProcessFXObjects( void ) +{ + if( !g_fxArray.StartPass( )) + { + Rain.dripsPerSecond = 0; // disable rain + ALERT( at_error, "rain: failed to allocate FX object!\n" ); + return; + } + + cl_rainfx* curFX = g_fxArray.GetCurrent(); + + while( curFX != NULL ) // go through FX objects list + { + // delete current? + if(( curFX->birthTime + curFX->life ) < rain_curtime ) + { + g_fxArray.DeleteCurrent(); + fxcounter--; + } + else + g_fxArray.MoveNext(); + + curFX = g_fxArray.GetCurrent(); + } +} + +/* +================================= +ResetRain +î÷èùàåò ïàìÿòü, óäàëÿÿ âñå îáúåêòû. +================================= +*/ +void ResetRain( void ) +{ + // delete all drips + g_dripsArray.Clear(); + g_fxArray.Clear(); + + dripcounter = 0; + fxcounter = 0; + + InitRain(); +} + +/* +================================= +DrawRain + +Ðèñîâàíèå êàïåëü è ñíåæèíîê. +================================= +*/ +void DrawRain( void ) +{ + if( g_dripsArray.IsClear( )) + return; // no drips to draw + + if( !Mod_BoxVisible( rain_mins, rain_maxs, Mod_GetCurrentVis( ))) + return; // rain volume is invisible + + HSPRITE hsprTexture; + + if( Rain.weatherMode == MODE_RAIN ) + hsprTexture = LoadSprite("sprites/hi_rain.spr" ); // load rain sprite + else if( Rain.weatherMode == MODE_SNOW ) + hsprTexture = LoadSprite( "sprites/snowflake.spr" ); // load snow sprite + else hsprTexture = 0; + + if( !hsprTexture ) return; + + GL_SelectTexture( GL_TEXTURE0 ); // keep texcoords at 0-th unit + + // usual triapi stuff + const model_s *pTexture = gEngfuncs.GetSpritePointer( hsprTexture ); + if( !gEngfuncs.pTriAPI->SpriteTexture(( struct model_s *)pTexture, 0 )) + return; + + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + + float visibleHeight = Rain.globalHeight - SNOWFADEDIST; + + m_iNumVerts = m_iNumIndex = 0; + + // go through drips list + g_dripsArray.StartPass(); + cl_drip *Drip = g_dripsArray.GetCurrent(); + + if( Rain.weatherMode == MODE_RAIN ) + { + while( Drip != NULL ) + { + // cull invisible drips + if( R_CullSphere( Drip->origin, SNOW_SPRITE_HALFSIZE + 1 )) + { + g_dripsArray.MoveNext(); + Drip = g_dripsArray.GetCurrent(); + continue; + } + + if(( m_iNumVerts + 3 ) >= MAX_RAIN_VERTICES ) + { + // this should never happens because we used vertex array only at 75% + ALERT( at_error, "Too many drips specified\n" ); + break; + } + + Vector2D toPlayer; + toPlayer.x = GetVieworg().x - Drip->origin[0]; + toPlayer.y = GetVieworg().y - Drip->origin[1]; + toPlayer = toPlayer.Normalize(); + + toPlayer.x *= DRIP_SPRITE_HALFWIDTH; + toPlayer.y *= DRIP_SPRITE_HALFWIDTH; + + float shiftX = (Drip->xDelta / DRIPSPEED) * DRIP_SPRITE_HALFHEIGHT; + float shiftY = (Drip->yDelta / DRIPSPEED) * DRIP_SPRITE_HALFHEIGHT; + + byte alpha = Drip->alpha * 255; + + m_coordsarray[m_iNumVerts].x = 0.0f; + m_coordsarray[m_iNumVerts].y = 0.0f; + m_colorarray[m_iNumVerts][0] = 255; + m_colorarray[m_iNumVerts][1] = 255; + m_colorarray[m_iNumVerts][2] = 255; + m_colorarray[m_iNumVerts][3] = alpha; + m_vertexarray[m_iNumVerts] = Vector( Drip->origin.x - toPlayer.y - shiftX, Drip->origin.y + toPlayer.x - shiftY, Drip->origin.z + DRIP_SPRITE_HALFHEIGHT ); + m_indexarray[m_iNumIndex++] = m_iNumVerts++; + + m_coordsarray[m_iNumVerts].x = 0.5f; + m_coordsarray[m_iNumVerts].y = 1.0f; + m_colorarray[m_iNumVerts][0] = 255; + m_colorarray[m_iNumVerts][1] = 255; + m_colorarray[m_iNumVerts][2] = 255; + m_colorarray[m_iNumVerts][3] = alpha; + m_vertexarray[m_iNumVerts] = Vector( Drip->origin.x + shiftX, Drip->origin.y + shiftY, Drip->origin.z - DRIP_SPRITE_HALFHEIGHT ); + m_indexarray[m_iNumIndex++] = m_iNumVerts++; + + // set right top corner + m_coordsarray[m_iNumVerts].x = 1.0f; + m_coordsarray[m_iNumVerts].y = 0.0f; + m_colorarray[m_iNumVerts][0] = 255; + m_colorarray[m_iNumVerts][1] = 255; + m_colorarray[m_iNumVerts][2] = 255; + m_colorarray[m_iNumVerts][3] = alpha; + m_vertexarray[m_iNumVerts] = Vector( Drip->origin.x + toPlayer.y - shiftX, Drip->origin.y - toPlayer.x - shiftY, Drip->origin.z + DRIP_SPRITE_HALFHEIGHT ); + m_indexarray[m_iNumIndex++] = m_iNumVerts++; + + g_dripsArray.MoveNext(); + Drip = g_dripsArray.GetCurrent(); + } + + pglEnableClientState( GL_VERTEX_ARRAY ); + pglVertexPointer( 3, GL_FLOAT, 0, m_vertexarray ); + + pglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + pglTexCoordPointer( 2, GL_FLOAT, 0, m_coordsarray ); + + pglEnableClientState( GL_COLOR_ARRAY ); + pglColorPointer( 4, GL_UNSIGNED_BYTE, 0, m_colorarray ); + + if( GL_Support( R_DRAW_RANGEELEMENTS_EXT )) + pglDrawRangeElementsEXT( GL_TRIANGLES, 0, m_iNumVerts - 1, m_iNumIndex, GL_UNSIGNED_SHORT, m_indexarray ); + else pglDrawElements( GL_TRIANGLES, m_iNumIndex, GL_UNSIGNED_SHORT, m_indexarray ); + + r_stats.c_total_tris += (m_iNumIndex / 3); + pglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + pglDisableClientState( GL_VERTEX_ARRAY ); + pglDisableClientState( GL_COLOR_ARRAY ); + } + else if( Rain.weatherMode == MODE_SNOW ) + { + while( Drip != NULL ) + { + // cull invisible flakes + if( R_CullSphere( Drip->origin, SNOW_SPRITE_HALFSIZE + 1 )) + { + g_dripsArray.MoveNext(); + Drip = g_dripsArray.GetCurrent(); + continue; + } + + if(( m_iNumVerts + 4 ) >= MAX_RAIN_VERTICES ) + { + ALERT( at_error, "Too many snowflakes specified\n" ); + break; + } + + // apply start fading effect + byte alpha; + + if( Drip->origin.z <= visibleHeight ) + alpha = Drip->alpha * 255; + else alpha = ((( Rain.globalHeight - Drip->origin.z ) / (float)SNOWFADEDIST ) * Drip->alpha) * 255; + + // set left bottom corner + m_coordsarray[m_iNumVerts].x = 0.0f; + m_coordsarray[m_iNumVerts].y = 1.0f; + m_colorarray[m_iNumVerts][0] = 255; + m_colorarray[m_iNumVerts][1] = 255; + m_colorarray[m_iNumVerts][2] = 255; + m_colorarray[m_iNumVerts][3] = alpha; + m_vertexarray[m_iNumVerts] = Drip->origin + GetVLeft() * -SNOW_SPRITE_HALFSIZE + GetVUp() * -SNOW_SPRITE_HALFSIZE; + m_indexarray[m_iNumIndex++] = m_iNumVerts++; + + // set left top corner + m_coordsarray[m_iNumVerts].x = 0.0f; + m_coordsarray[m_iNumVerts].y = 0.0f; + m_colorarray[m_iNumVerts][0] = 255; + m_colorarray[m_iNumVerts][1] = 255; + m_colorarray[m_iNumVerts][2] = 255; + m_colorarray[m_iNumVerts][3] = alpha; + m_vertexarray[m_iNumVerts] = Drip->origin + GetVLeft() * -SNOW_SPRITE_HALFSIZE + GetVUp() * SNOW_SPRITE_HALFSIZE; + m_indexarray[m_iNumIndex++] = m_iNumVerts++; + + // set right top corner + m_coordsarray[m_iNumVerts].x = 1.0f; + m_coordsarray[m_iNumVerts].y = 0.0f; + m_colorarray[m_iNumVerts][0] = 255; + m_colorarray[m_iNumVerts][1] = 255; + m_colorarray[m_iNumVerts][2] = 255; + m_colorarray[m_iNumVerts][3] = alpha; + m_vertexarray[m_iNumVerts] = Drip->origin + GetVLeft() * SNOW_SPRITE_HALFSIZE + GetVUp() * SNOW_SPRITE_HALFSIZE; + m_indexarray[m_iNumIndex++] = m_iNumVerts++; + + // set right bottom corner + m_coordsarray[m_iNumVerts].x = 1.0f; + m_coordsarray[m_iNumVerts].y = 1.0f; + m_colorarray[m_iNumVerts][0] = 255; + m_colorarray[m_iNumVerts][1] = 255; + m_colorarray[m_iNumVerts][2] = 255; + m_colorarray[m_iNumVerts][3] = alpha; + m_vertexarray[m_iNumVerts] = Drip->origin + GetVLeft() * SNOW_SPRITE_HALFSIZE + GetVUp() * -SNOW_SPRITE_HALFSIZE; + m_indexarray[m_iNumIndex++] = m_iNumVerts++; + + g_dripsArray.MoveNext(); + Drip = g_dripsArray.GetCurrent(); + } + + pglEnableClientState( GL_VERTEX_ARRAY ); + pglVertexPointer( 3, GL_FLOAT, 0, m_vertexarray ); + + pglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + pglTexCoordPointer( 2, GL_FLOAT, 0, m_coordsarray ); + + pglEnableClientState( GL_COLOR_ARRAY ); + pglColorPointer( 4, GL_UNSIGNED_BYTE, 0, m_colorarray ); + + if( GL_Support( R_DRAW_RANGEELEMENTS_EXT )) + pglDrawRangeElementsEXT( GL_QUADS, 0, m_iNumVerts - 1, m_iNumIndex, GL_UNSIGNED_SHORT, m_indexarray ); + else pglDrawElements( GL_QUADS, m_iNumIndex, GL_UNSIGNED_SHORT, m_indexarray ); + + r_stats.c_total_tris += (m_iNumIndex / 4); // FIXME: this is correct? + pglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + pglDisableClientState( GL_VERTEX_ARRAY ); + pglDisableClientState( GL_COLOR_ARRAY ); + } +} + +/* rain +================================= +DrawFXObjects + +Ðèñîâàíèå âîäÿíûõ êðóãîâ +================================= +*/ +void DrawFXObjects( void ) +{ + if( g_fxArray.IsClear( )) + return; // no objects to draw + + HSPRITE hsprTexture; + + hsprTexture = LoadSprite( "sprites/waterring.spr" ); // load water ring sprite + if( !hsprTexture ) return; + + const model_s *pTexture = gEngfuncs.GetSpritePointer( hsprTexture ); + if( !gEngfuncs.pTriAPI->SpriteTexture(( struct model_s *)pTexture, 0 )) + return; + + gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); // because we also want to view water rings underwater + + // go through objects list + g_fxArray.StartPass(); + cl_rainfx *curFX = g_fxArray.GetCurrent(); + + m_iNumVerts = m_iNumIndex = 0; + + while( curFX != NULL ) + { + if(( m_iNumVerts + 4 ) >= MAX_RAIN_VERTICES ) + { + ALERT( at_error, "Too many water rings\n" ); + break; + } + + // cull invisible rings + if( R_CullSphere( curFX->origin, MAX_RING_HALFSIZE + 1 )) + { + g_fxArray.MoveNext(); + curFX = g_fxArray.GetCurrent(); + continue; + } + + // fadeout + byte alpha = (((curFX->birthTime + curFX->life - rain_curtime) / curFX->life) * curFX->alpha) * 255; + float size = (rain_curtime - curFX->birthTime) * MAX_RING_HALFSIZE; + + m_coordsarray[m_iNumVerts].x = 0.0f; + m_coordsarray[m_iNumVerts].y = 0.0f; + m_colorarray[m_iNumVerts][0] = 255; + m_colorarray[m_iNumVerts][1] = 255; + m_colorarray[m_iNumVerts][2] = 255; + m_colorarray[m_iNumVerts][3] = alpha; + m_vertexarray[m_iNumVerts] = Vector( curFX->origin.x - size, curFX->origin.y - size, curFX->origin.z ); + m_indexarray[m_iNumIndex++] = m_iNumVerts++; + + m_coordsarray[m_iNumVerts].x = 0.0f; + m_coordsarray[m_iNumVerts].y = 1.0f; + m_colorarray[m_iNumVerts][0] = 255; + m_colorarray[m_iNumVerts][1] = 255; + m_colorarray[m_iNumVerts][2] = 255; + m_colorarray[m_iNumVerts][3] = alpha; + m_vertexarray[m_iNumVerts] = Vector( curFX->origin.x - size, curFX->origin.y + size, curFX->origin.z ); + m_indexarray[m_iNumIndex++] = m_iNumVerts++; + + m_coordsarray[m_iNumVerts].x = 1.0f; + m_coordsarray[m_iNumVerts].y = 1.0f; + m_colorarray[m_iNumVerts][0] = 255; + m_colorarray[m_iNumVerts][1] = 255; + m_colorarray[m_iNumVerts][2] = 255; + m_colorarray[m_iNumVerts][3] = alpha; + m_vertexarray[m_iNumVerts] = Vector( curFX->origin.x + size, curFX->origin.y + size, curFX->origin.z ); + m_indexarray[m_iNumIndex++] = m_iNumVerts++; + + m_coordsarray[m_iNumVerts].x = 1.0f; + m_coordsarray[m_iNumVerts].y = 0.0f; + m_colorarray[m_iNumVerts][0] = 255; + m_colorarray[m_iNumVerts][1] = 255; + m_colorarray[m_iNumVerts][2] = 255; + m_colorarray[m_iNumVerts][3] = alpha; + m_vertexarray[m_iNumVerts] = Vector( curFX->origin.x + size, curFX->origin.y - size, curFX->origin.z ); + m_indexarray[m_iNumIndex++] = m_iNumVerts++; + + g_fxArray.MoveNext(); + curFX = g_fxArray.GetCurrent(); + } + + pglEnableClientState( GL_VERTEX_ARRAY ); + pglVertexPointer( 3, GL_FLOAT, 0, m_vertexarray ); + + pglEnableClientState( GL_TEXTURE_COORD_ARRAY ); + pglTexCoordPointer( 2, GL_FLOAT, 0, m_coordsarray ); + + pglEnableClientState( GL_COLOR_ARRAY ); + pglColorPointer( 4, GL_UNSIGNED_BYTE, 0, m_colorarray ); + + if( GL_Support( R_DRAW_RANGEELEMENTS_EXT )) + pglDrawRangeElementsEXT( GL_QUADS, 0, m_iNumVerts - 1, m_iNumIndex, GL_UNSIGNED_SHORT, m_indexarray ); + else pglDrawElements( GL_QUADS, m_iNumIndex, GL_UNSIGNED_SHORT, m_indexarray ); + + r_stats.c_total_tris += (m_iNumIndex / 4); // FIXME: this is correct? + pglDisableClientState( GL_TEXTURE_COORD_ARRAY ); + pglDisableClientState( GL_VERTEX_ARRAY ); + pglDisableClientState( GL_COLOR_ARRAY ); + + gEngfuncs.pTriAPI->CullFace( TRI_FRONT ); +} + +/* +================================= +InitRain +Èíèöèàëèçèðóåò âñå ïåðåìåííûå íóëåâûìè çíà÷åíèÿìè. +================================= +*/ +void InitRain( void ) +{ + cl_draw_rain = CVAR_REGISTER( "cl_draw_rain", "1", FCVAR_ARCHIVE ); + cl_debug_rain = CVAR_REGISTER( "cl_debug_rain", "0", 0 ); + memset( &Rain, 0, sizeof( Rain )); + + rain_oldtime = 0; + rain_curtime = 0; + rain_nextspawntime = 0; +} + +/* +================================= +ParseRain +================================= +*/ +void ParseRain( void ) +{ + Rain.dripsPerSecond = READ_SHORT(); + Rain.distFromPlayer = READ_COORD(); + Rain.windX = READ_COORD(); + Rain.windY = READ_COORD(); + Rain.randX = READ_COORD(); + Rain.randY = READ_COORD(); + Rain.weatherMode = READ_SHORT(); + Rain.globalHeight = READ_COORD(); +} + +/* +================================= +R_DrawWeather +================================= +*/ +void R_DrawWeather( void ) +{ + if( !CVAR_TO_BOOL( cl_draw_rain )) + return; + + if( FBitSet( RI->params, ( RP_ENVVIEW|RP_SKYVIEW ))) + return; + + GL_CleanupDrawState(); + GL_AlphaTest( GL_FALSE ); + GL_DepthMask( GL_FALSE ); + + ProcessRain(); + ProcessFXObjects(); + + DrawRain(); + DrawFXObjects(); +} \ No newline at end of file diff --git a/cl_dll/render/tbnfile.h b/cl_dll/render/tbnfile.h new file mode 100644 index 0000000..0ea8530 --- /dev/null +++ b/cl_dll/render/tbnfile.h @@ -0,0 +1,54 @@ +/* +tbnfile.h - studio cached TBN +Copyright (C) 2019 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef TBNFILE_H +#define TBNFILE_H + +/* +============================================================================== + +TBN FILES + +.tbn contain precomputed TBN +============================================================================== +*/ + +#define IDTBNHEADER (('\0'<<24)+('N'<<16)+('B'<<8)+'T') // little-endian "TBN " +#define TBN_VERSION 1 + +typedef struct +{ + vec3_t tangent; + vec3_t binormal; + vec3_t normal; +} dvertmatrix_t; + +typedef struct +{ + int submodel_offset; // hack to determine submodel + int vertex_offset; +} dvmodelofs_t; + +typedef struct +{ + int ident; // to differentiate from previous lump LUMP_LEAF_LIGHTING + int version; // data package version + unsigned int modelCRC; // catch for model changes + int numverts; + dvmodelofs_t submodels[32]; // MAXSTUDIOMODELS + dvertmatrix_t verts[1]; // variable sized +} dmodeltbn_t; + +#endif//TBNFILE_H \ No newline at end of file diff --git a/cl_dll/render/tri.cpp b/cl_dll/render/tri.cpp new file mode 100644 index 0000000..4fe5d65 --- /dev/null +++ b/cl_dll/render/tri.cpp @@ -0,0 +1,207 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// Triangle rendering, if any + +#include "hud.h" +#include "cl_util.h" +#include "wrect.h" + +// Triangle rendering apis are in gEngfuncs.pTriAPI + +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "triangleapi.h" +#include "custom_alloc.h" +#include "com_model.h" +#include "gl_local.h"// buz + +#define DLLEXPORT __declspec( dllexport ) + +extern "C" +{ + void DLLEXPORT HUD_DrawNormalTriangles( void ); + void DLLEXPORT HUD_DrawTransparentTriangles( void ); +}; + +// +//----------------------------------------------------- +// +/* +==================== +buz: +Orthogonal polygons +==================== +*/ +// helper functions +void OrthoQuad(int x1, int y1, int x2, int y2) +{ + gEngfuncs.pTriAPI->Begin(TRI_QUADS); //start our quad + + gEngfuncs.pTriAPI->TexCoord2f(0.01, 0.01); + gEngfuncs.pTriAPI->Vertex3f(x1, y1, 0); + + gEngfuncs.pTriAPI->TexCoord2f(0.01, 0.99); + gEngfuncs.pTriAPI->Vertex3f(x1, y2, 0); + + gEngfuncs.pTriAPI->TexCoord2f(0.99, 0.99); + gEngfuncs.pTriAPI->Vertex3f(x2, y2, 0); + + gEngfuncs.pTriAPI->TexCoord2f(0.99f, 0.01); + gEngfuncs.pTriAPI->Vertex3f(x2, y1, 0); + + gEngfuncs.pTriAPI->End(); //end our list of vertexes +} + +// buz: use triapi to draw sprites on screen +void DrawSpriteAsPoly( HSPRITE hspr, wrect_t *rect, wrect_t *screenpos, int mode, float r, float g, float b, float a ) +{ + if( !hspr ) return; + + const struct model_s *sprmodel = gEngfuncs.GetSpritePointer(hspr); + gEngfuncs.pTriAPI->RenderMode(mode); + gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *) sprmodel, 0); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); //no culling + gEngfuncs.pTriAPI->Color4f( r, g, b, a ); + + float x = rect->left / (float)SPR_Width(hspr, 0) + 0.01; + float x2 = rect->right / (float)SPR_Width(hspr, 0) - 0.01; + float y = rect->top / (float)SPR_Height(hspr, 0) + 0.01; + float y2 = rect->bottom / (float)SPR_Height(hspr, 0) - 0.01; + + gEngfuncs.pTriAPI->Begin(TRI_QUADS); //start our quad + gEngfuncs.pTriAPI->TexCoord2f(x, y); + gEngfuncs.pTriAPI->Vertex3f(screenpos->left, screenpos->top, 0); + + gEngfuncs.pTriAPI->TexCoord2f(x, y2); + gEngfuncs.pTriAPI->Vertex3f(screenpos->left, screenpos->bottom, 0); + + gEngfuncs.pTriAPI->TexCoord2f(x2, y2); + gEngfuncs.pTriAPI->Vertex3f(screenpos->right, screenpos->bottom, 0); + + gEngfuncs.pTriAPI->TexCoord2f(x2, y); + gEngfuncs.pTriAPI->Vertex3f(screenpos->right, screenpos->top, 0); + gEngfuncs.pTriAPI->End(); + + gEngfuncs.pTriAPI->CullFace( TRI_FRONT ); +} + +void OrthoDraw( HSPRITE spr, int mode, float r, float g, float b, float a ) +{ + if( !spr ) return; + + const struct model_s *sprmodel = gEngfuncs.GetSpritePointer(spr); + gEngfuncs.pTriAPI->RenderMode(mode); + + int frames = SPR_Frames(spr); + + switch (frames) + { + case 1: + gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *) sprmodel, 0); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); //no culling + gEngfuncs.pTriAPI->Color4f( r, g, b, a ); + OrthoQuad(0, 0, ScreenWidth, ScreenHeight); + break; + + case 2: + gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *) sprmodel, 0); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); //no culling + gEngfuncs.pTriAPI->Color4f( r, g, b, a ); + OrthoQuad(0, 0, ScreenWidth/2, ScreenHeight); + + gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *) sprmodel, 1); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); //no culling + gEngfuncs.pTriAPI->Color4f( r, g, b, a ); + OrthoQuad(ScreenWidth/2, 0, ScreenWidth, ScreenHeight); + break; + + case 4: + gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *) sprmodel, 0); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); //no culling + gEngfuncs.pTriAPI->Color4f( r, g, b, a ); + OrthoQuad(0, 0, ScreenWidth/2, ScreenHeight/2); + + gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *) sprmodel, 1); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); //no culling + gEngfuncs.pTriAPI->Color4f( r, g, b, a ); + OrthoQuad(ScreenWidth/2, 0, ScreenWidth, ScreenHeight/2); + + gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *) sprmodel, 2); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); //no culling + gEngfuncs.pTriAPI->Color4f( r, g, b, a ); + OrthoQuad(0, ScreenHeight/2, ScreenWidth/2, ScreenHeight); + + gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *) sprmodel, 3); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); //no culling + gEngfuncs.pTriAPI->Color4f( r, g, b, a ); + OrthoQuad(ScreenWidth/2, ScreenHeight/2, ScreenWidth, ScreenHeight); + break; + + default: + gEngfuncs.Con_Printf("ERROR: illegal number of frames in ortho sprite (%i)\n", frames); + break; + } + + gEngfuncs.pTriAPI->CullFace( TRI_FRONT ); +} + +void OrthoScope( void ) +{ + // scope base + HSPRITE spr1 = SPR_Load("sprites/sniper1.spr"); + if (!spr1) + { + gEngfuncs.Con_Printf("ERROR: Can't load sprites/sniper1.spr\n"); + return; + } + + // dirt decal + HSPRITE spr2 = SPR_Load("sprites/sniper2.spr"); + if (!spr2) + { + gEngfuncs.Con_Printf("ERROR: Can't load sprites/sniper2.spr\n"); + return; + } + + // transparent glass + HSPRITE spr3 = SPR_Load("sprites/sniper3.spr"); + if (!spr3) + { + gEngfuncs.Con_Printf("ERROR: Can't load sprites/sniper3.spr\n"); + return; + } + + OrthoDraw( spr3, kRenderTransAdd, tr.ambientLight.x * 0.5f, tr.ambientLight.y * 0.5f, tr.ambientLight.z * 0.5f, 1.0f ); + OrthoDraw( spr2, kRenderTransColor, tr.ambientLight.x, tr.ambientLight.y, tr.ambientLight.z, 1.0f ); + OrthoDraw( spr1, kRenderNormal, tr.ambientLight.x * 0.5f, tr.ambientLight.y * 0.5f, tr.ambientLight.z * 0.5f, 1.0f ); + + gEngfuncs.pTriAPI->RenderMode(kRenderNormal); //return to normal +} + +/* +================= +HUD_DrawNormalTriangles + +Non-transparent triangles-- add them here +================= +*/ +void DLLEXPORT HUD_DrawNormalTriangles( void ) +{ +} + +/* +================= +HUD_DrawTransparentTriangles + +Render any triangles with transparent rendermode needs here +================= +*/ +void DLLEXPORT HUD_DrawTransparentTriangles( void ) +{ +} \ No newline at end of file diff --git a/cl_dll/render/vertex_fmt.h b/cl_dll/render/vertex_fmt.h new file mode 100644 index 0000000..0033cb8 --- /dev/null +++ b/cl_dll/render/vertex_fmt.h @@ -0,0 +1,339 @@ +/* +vertex_fmt.h - VBO-stored vertex formats +this code written for Paranoia 2: Savior modification +Copyright (C) 2019 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef VERTEX_FMT_H +#define VERTEX_FMT_H + +#define no_align __declspec(align(1)) + +// name specific: +// fisrt letter is model type: B - BrushModel, G - Grass, D - Decal, S - StudioModel +// next four letters is always equal "vert" for more readability +// next is always underline +// next two symbols is vertex version v0 - v9 +// next is always underline +// next four symbols is a target OpenGL version (gl21 - for very old machines that doesn't support GL3.0, gl30 - for modern hardware) + +/* +============================================================= + + BMODEL VERTEXES FORMAT + +============================================================= +*/ + +// 100 bytes here +typedef struct +{ + float vertex[3]; // position + float tangent[3]; // tangent + float binormal[3]; // binormal + float normal[3]; // normal + float stcoord0[4]; // ST texture coords + float lmcoord0[4]; // LM texture coords for styles 0-1 + float lmcoord1[4]; // LM texture coords for styles 2-3 + byte styles[MAXLIGHTMAPS]; // light styles +} bvert_v0_gl21_t; + +// 84 bytes here +no_align typedef struct +{ + float vertex[3]; // position + char tangent[3]; // tangent + char binormal[3]; // binormal + char normal[3]; // normal + float stcoord0[4]; // ST texture coords + float lmcoord0[4]; // LM texture coords for styles 0-1 + float lmcoord1[4]; // LM texture coords for styles 2-3 + byte styles[MAXLIGHTMAPS]; // light styles + byte lights0[4]; // packed light numbers + byte lights1[4]; // packed light numbers +} bvert_v0_gl30_t; + +/* +============================================================= + + GRASS VERTEXES FORMAT + +============================================================= +*/ + +// version 0. no lightvectors +// 52 bytes +typedef struct +{ + float center[4]; // used for rescale + float normal[4]; // center + vertex[2] * vertex[3]; + float light[MAXLIGHTMAPS]; // packed color + unused entry + byte styles[MAXLIGHTMAPS]; // styles on surface +} gvert_v0_gl21_t; + +// 32 bytes +typedef struct +{ + short center[4]; // used for rescale + char normal[4]; // center + vertex[2] * vertex[3]; + float light[MAXLIGHTMAPS]; // packed color + unused entry + byte styles[MAXLIGHTMAPS]; // styles on surface +} gvert_v0_gl30_t; + +// version 1. width lightvectors (ugly case) +// 68 bytes +typedef struct +{ + float center[4]; // used for rescale + float normal[4]; // center + vertex[2] * vertex[3]; + float light[MAXLIGHTMAPS]; // packed color + unused entry + float delux[MAXLIGHTMAPS]; // packed lightdir + unused entry + byte styles[MAXLIGHTMAPS]; // styles on surface +} gvert_v1_gl21_t; + +// 48 bytes +typedef struct +{ + short center[4]; // used for rescale + char normal[4]; // center + vertex[2] * vertex[3]; + float light[MAXLIGHTMAPS]; // packed color + unused entry + float delux[MAXLIGHTMAPS]; // packed lightdir + unused entry + byte styles[MAXLIGHTMAPS]; // styles on surface +} gvert_v1_gl30_t; + +/* +============================================================= + + STUDIO VERTEXES FORMAT + +============================================================= +*/ + +// version 0. no bump, no boneweights, no vertexlight +// 44 bytes +typedef struct +{ + float vertex[3]; // position + float stcoord[4]; // ST texture coords + float normal[3]; // normal + float boneid; // control bones +} svert_v0_gl21_t; + +// 24 bytes +no_align typedef struct +{ + float vertex[3]; // position + short stcoord[4]; // ST texture coords + char normal[3]; // normal + char boneid; // control bones +} svert_v0_gl30_t; + +// version 1. have bump, no boneweights, no vertexlight +// 68 bytes +typedef struct +{ + float vertex[3]; // position + float stcoord[4]; // ST texture coords + float normal[3]; // normal + float tangent[3]; // tangent + float binormal[3]; // binormal + float boneid; // control bones +} svert_v1_gl21_t; + +// 32 bytes +no_align typedef struct +{ + float vertex[3]; // position + short stcoord[4]; // ST texture coords + char normal[3]; // normal + char tangent[3]; // tangent + char binormal[3]; // binormal + char boneid; // control bones +} svert_v1_gl30_t; + +// version 2. no bump, single bone, has vertex lighting +// 56 bytes +typedef struct +{ + float vertex[3]; // position + float stcoord[4]; // ST texture coords + float normal[3]; // normal + float light[MAXLIGHTMAPS]; // packed color +} svert_v2_gl21_t; + +// 40 bytes +no_align typedef struct +{ + float vertex[3]; // position + short stcoord[4]; // ST texture coords + char normal[3]; // normal + float light[MAXLIGHTMAPS]; // packed color +} svert_v2_gl30_t; + +// version 3. have bump, single bone, has vertex lighting +// 96 bytes +typedef struct +{ + float vertex[3]; // position + float stcoord[4]; // ST texture coords + float light[MAXLIGHTMAPS]; // packed color + float deluxe[MAXLIGHTMAPS]; // packed lightdir + float normal[3]; // normal + float tangent[3]; // tangent + float binormal[3]; // binormal +} svert_v3_gl21_t; + +// 64 bytes +no_align typedef struct +{ + float vertex[3]; // position + short stcoord[4]; // ST texture coords + float light[MAXLIGHTMAPS]; // packed color + float deluxe[MAXLIGHTMAPS]; // packed lightdir + char normal[3]; // normal + char tangent[3]; // tangent + char binormal[3]; // binormal +} svert_v3_gl30_t; + +// version 4. no bump, have boneweights, no vertexlight +// 72 bytes +typedef struct +{ + float vertex[3]; // position + float stcoord[4]; // ST texture coords + float normal[3]; // normal + float boneid[4]; // control bones + float weight[4]; // boneweights +} svert_v4_gl21_t; + +// 32 bytes +no_align typedef struct +{ + float vertex[3]; // position + short stcoord[4]; // ST texture coords + char normal[3]; // normal + char boneid[4]; // control bones + byte weight[4]; // boneweights +} svert_v4_gl30_t; + +// version 5. have bump, have boneweights, no vertexlight +// 96 bytes +typedef struct +{ + float vertex[3]; // position + float stcoord[4]; // ST texture coords + float normal[3]; // normal + float tangent[3]; // tangent + float binormal[3]; // binormal + float boneid[4]; // control bones + float weight[4]; // boneweights +} svert_v5_gl21_t; + +// 40 bytes +no_align typedef struct +{ + float vertex[3]; // position + short stcoord[4]; // ST texture coords + char normal[3]; // normal + char tangent[3]; // tangent + char binormal[3]; // binormal + char boneid[4]; // control bones + byte weight[4]; // boneweights +} svert_v5_gl30_t; + +// version 6. no bump, single bone, has lightmaps +// 76 bytes +typedef struct +{ + float vertex[3]; // position + float stcoord0[4]; // ST texture coords + float lmcoord0[4]; // LM texture coords 0-1 + float lmcoord1[4]; // LM texture coords 2-3 + float normal[3]; // normal + byte styles[MAXLIGHTMAPS]; // light styles +} svert_v6_gl21_t; + +// 60 bytes +no_align typedef struct +{ + float vertex[3]; // position + short stcoord0[4]; // ST texture coords + float lmcoord0[4]; // LM texture coords 0-1 + float lmcoord1[4]; // LM texture coords 2-3 + char normal[3]; // normal + byte styles[MAXLIGHTMAPS]; // light styles +} svert_v6_gl30_t; + +// version 7. have bump, single bone, has lightmaps +// 100 bytes +typedef struct +{ + float vertex[3]; // position + float stcoord0[4]; // ST texture coords + float lmcoord0[4]; // LM texture coords 0-1 + float lmcoord1[4]; // LM texture coords 2-3 + float normal[3]; // normal + float tangent[3]; // tangent + float binormal[3]; // binormal + byte styles[MAXLIGHTMAPS]; // light styles +} svert_v7_gl21_t; + +// 68 bytes +no_align typedef struct +{ + float vertex[3]; // position + short stcoord0[4]; // ST texture coords + float lmcoord0[4]; // LM texture coords 0-1 + float lmcoord1[4]; // LM texture coords 2-3 + char normal[3]; // normal + char tangent[3]; // tangent + char binormal[3]; // binormal + byte styles[MAXLIGHTMAPS]; // light styles +} svert_v7_gl30_t; + +// version 8. includes all posible combination, slowest +// 164 bytes here +typedef struct +{ + float vertex[3]; // position + float stcoord0[4]; // ST texture coords + float lmcoord0[4]; // LM texture coords 0-1 + float lmcoord1[4]; // LM texture coords 2-3 + float normal[3]; // normal + float tangent[3]; // tangent + float binormal[3]; // binormal + float boneid[4]; // control bones + float weight[4]; // boneweights + float light[MAXLIGHTMAPS]; // packed color + float deluxe[MAXLIGHTMAPS]; // packed lightdir + byte styles[MAXLIGHTMAPS]; // light styles +} svert_v8_gl21_t; + +// 108 bytes here +no_align typedef struct +{ + float vertex[3]; // position + short stcoord0[4]; // ST texture coords + float lmcoord0[4]; // LM texture coords 0-1 + float lmcoord1[4]; // LM texture coords 2-3 + char normal[3]; // normal + char tangent[3]; // tangent + char binormal[3]; // binormal + char boneid[4]; // control bones + byte weight[4]; // boneweights + float light[MAXLIGHTMAPS]; // packed color + float deluxe[MAXLIGHTMAPS]; // packed lightdir + byte styles[MAXLIGHTMAPS]; // light styles +} svert_v8_gl30_t; + +#endif//VERTEX_FMT_H \ No newline at end of file diff --git a/cl_dll/render/view.cpp b/cl_dll/render/view.cpp new file mode 100644 index 0000000..b732066 --- /dev/null +++ b/cl_dll/render/view.cpp @@ -0,0 +1,2036 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// view/refresh setup functions + +#include "hud.h" +#include "cl_util.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "stringlib.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "ref_params.h" +#include "in_defs.h" // PITCH YAW ROLL +#include "pm_shared.h" +#include "pm_defs.h" +#include "event_api.h" +#include "pmtrace.h" +#include "screenfade.h" +#include "shake.h" +#include "hltv.h" +#include "gl_local.h" // buz + +extern int g_flashlight; +extern int g_iGunMode; // buz + +void SetupFlashlight( struct ref_params_s *pparams ); + +cvar_t *r_test; +cvar_t *gl_extensions; +cvar_t *r_detailtextures; +cvar_t *r_lighting_ambient; +cvar_t *r_lighting_modulate; +cvar_t *r_lighting_extended; +cvar_t *r_lightstyle_lerping; +cvar_t *r_occlusion_culling; +cvar_t *r_show_lightprobes; +cvar_t *r_show_cubemaps; +cvar_t *r_show_viewleaf; +cvar_t *r_allow_mirrors; +cvar_t *r_drawentities; +cvar_t *r_draw_beams; +cvar_t *cv_crosshair; +cvar_t *r_fullbright; +cvar_t *r_overview; +cvar_t *r_dynamic; +cvar_t *r_shadows; +cvar_t *r_novis; +cvar_t *r_nocull; +cvar_t *r_lockpvs; +cvar_t *r_dof; +cvar_t *r_dof_hold_time; +cvar_t *r_dof_change_time; +cvar_t *r_dof_focal_length; +cvar_t *r_dof_fstop; +cvar_t *r_dof_debug; +cvar_t *cv_renderer; +cvar_t *cv_brdf; +cvar_t *cv_bump; +cvar_t *cv_bumpvecs; +cvar_t *cv_specular; +cvar_t *cv_cubemaps; +cvar_t *cv_deferred; +cvar_t *cv_deferred_full; +cvar_t *cv_deferred_maxlights; +cvar_t *cv_deferred_tracebmodels; +cvar_t *cv_cube_lod_bias; +cvar_t *cv_dynamiclight; +cvar_t *cv_realtime_puddles; +cvar_t *cv_shadow_offset; +cvar_t *cv_parallax; +cvar_t *cv_decals; +cvar_t *cv_gamma; +cvar_t *cv_brightness; +cvar_t *cv_water; +cvar_t *cv_decalsdebug; +cvar_t *cv_show_tbn; +cvar_t *cv_nosort; +cvar_t *r_wireframe; +cvar_t *r_lightmap; +cvar_t *r_speeds; +cvar_t *r_decals; +cvar_t *r_studio_decals; +cvar_t *r_clear; +cvar_t *r_finish; +cvar_t *r_sunshadows; +cvar_t *r_sun_allowed; +cvar_t *r_shadow_split_weight; +cvar_t *r_lightstyles; +cvar_t *r_polyoffset; +cvar_t *r_grass; +cvar_t *r_grass_alpha; +cvar_t *r_grass_lighting; +cvar_t *r_grass_shadows; +cvar_t *r_grass_fade_start; +cvar_t *r_grass_fade_dist; +cvar_t *r_shadowmap_size; +cvar_t *r_scissor_glass_debug; +cvar_t *r_scissor_light_debug; +cvar_t *r_showlightmaps; +cvar_t *r_recursion_depth; +cvar_t *r_pssm_show_split; +cvar_t *v_glows; + +// Spectator Mode +float vecNewViewAngles[3]; +int iHasNewViewAngles; +float vecNewViewOrigin[3]; +int iHasNewViewOrigin; +int iIsSpectator; +float m_flOfs; + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +extern "C" +{ + int CL_IsThirdPerson( void ); + void CL_CameraOffset( float *ofs ); + + void DLLEXPORT V_CalcRefdef( struct ref_params_s *pparams ); +} + +void PM_ParticleLine( float *start, float *end, int pcolor, float life, float vert); +int PM_GetVisEntInfo( int ent ); +int PM_GetPhysEntInfo( int ent ); +void InterpolateAngles( float * start, float * end, float * output, float frac ); +float AngleBetweenVectors( const float * v1, const float * v2 ); + +extern float vJumpOrigin[3]; +extern float vJumpAngles[3]; +ref_params_t *g_pViewParams = NULL; // pointer to ref_params for use outside V_CalcRefdef + +void V_DropPunchAngle ( float frametime, float *ev_punchangle ); +void VectorAngles( const float *forward, float *angles ); + +#include "r_studioint.h" +#include "com_model.h" + +extern engine_studio_api_t IEngineStudio; + +/* +The view is allowed to move slightly from it's true position for bobbing, +but if it exceeds 8 pixels linear distance (spherical, not box), the list of +entities sent from the server may not include everything in the pvs, especially +when crossing a water boudnary. +*/ + +extern cvar_t *cl_forwardspeed; +extern cvar_t *chase_active; +extern cvar_t *scr_ofsx, *scr_ofsy, *scr_ofsz; +extern cvar_t *cl_vsmoothing; +extern cvar_t *cl_rollspeed; +extern cvar_t *cl_rollangle; + +#define CAM_MODE_RELAX 1 +#define CAM_MODE_FOCUS 2 + +#define clamp( val, min, max ) ( ((val) > (max)) ? (max) : ( ((val) < (min)) ? (min) : (val) ) ) // thx BUzer + +#define HL2_BOB_CYCLE_MIN 0.1f +#define HL2_BOB_CYCLE_MAX 0.3f +#define HL2_BOB 0.1f +#define HL2_BOB_UP 0.5f + +float g_lateralBob; +float g_verticalBob; + +Vector v_origin, v_angles, v_cl_angles, v_sim_org, v_lastAngles; +float v_frametime, v_lastDistance; +float v_cameraRelaxAngle = 5.0f; +float v_cameraFocusAngle = 35.0f; +int v_cameraMode = CAM_MODE_FOCUS; +qboolean v_resetCamera = 1; + +Vector g_CrosshairAngle; // buz + +Vector ev_punchangle; + +cvar_t *scr_ofsx; +cvar_t *scr_ofsy; +cvar_t *scr_ofsz; + +cvar_t *v_centermove; +cvar_t *v_centerspeed; + +cvar_t *cl_bobcycle; +cvar_t *cl_bob; +cvar_t *cl_bobup; +cvar_t *cl_waterdist; +cvar_t *cl_chasedist; + +// These cvars are not registered (so users can't cheat), so set the ->value field directly +// Register these cvars in V_Init() if needed for easy tweaking +cvar_t v_iyaw_cycle = {"v_iyaw_cycle", "2", 0, 2}; +cvar_t v_iroll_cycle = {"v_iroll_cycle", "0.5", 0, 0.5}; +cvar_t v_ipitch_cycle = {"v_ipitch_cycle", "1", 0, 1}; +cvar_t v_iyaw_level = {"v_iyaw_level", "0.3", 0, 0.3}; +cvar_t v_iroll_level = {"v_iroll_level", "0.1", 0, 0.1}; +cvar_t v_ipitch_level = {"v_ipitch_level", "0.3", 0, 0.3}; + +float v_idlescale; // used by TFC for concussion grenade effect + +//============================================================================= +void V_NormalizeAngles( float *angles ) +{ + // Normalize angles + for( int i = 0; i < 3; i++ ) + { + if ( angles[i] > 180.0 ) + { + angles[i] -= 360.0; + } + else if ( angles[i] < -180.0 ) + { + angles[i] += 360.0; + } + } +} + +float Distance( const Vector &v1, const Vector &v2 ) +{ + return (v2 - v1).Length(); +} + +/* +=================== +V_InterpolateAngles + +Interpolate Euler angles. +Frac is 0.0 to 1.0 ( i.e., should probably be clamped, but doesn't have to be ) +=================== +*/ +void V_InterpolateAngles( float *start, float *end, float *output, float frac ) +{ + int i; + float ang1, ang2; + float d; + + V_NormalizeAngles( start ); + V_NormalizeAngles( end ); + + for ( i = 0 ; i < 3 ; i++ ) + { + ang1 = start[i]; + ang2 = end[i]; + + d = ang2 - ang1; + if ( d > 180 ) + { + d -= 360; + } + else if ( d < -180 ) + { + d += 360; + } + + output[i] = ang1 + d * frac; + } + + V_NormalizeAngles( output ); +} + +// Quakeworld bob code, this fixes jitters in the mutliplayer since the clock (pparams->time) isn't quite linear +float V_CalcBob ( struct ref_params_s *pparams ) +{ + static double bobtime; + static float bob; + float cycle; + static float lasttime; + Vector vel; + + + if ( pparams->onground == -1 || + pparams->time == lasttime ) + { + // just use old value + return bob; + } + + lasttime = pparams->time; + + bobtime += pparams->frametime; + cycle = bobtime - (int)( bobtime / cl_bobcycle->value ) * cl_bobcycle->value; + cycle /= cl_bobcycle->value; + + if ( cycle < cl_bobup->value ) + { + cycle = M_PI * cycle / cl_bobup->value; + } + else + { + cycle = M_PI + M_PI * ( cycle - cl_bobup->value )/( 1.0 - cl_bobup->value ); + } + + // bob is proportional to simulated velocity in the xy plane + // (don't count Z, or jumping messes it up) + VectorCopy( pparams->simvel, vel ); + vel[2] = 0; + + bob = sqrt( vel[0] * vel[0] + vel[1] * vel[1] ) * cl_bob->value; + bob = bob * 0.3 + bob * 0.7 * sin(cycle); + bob = min( bob, 4 ); + bob = max( bob, -7 ); + return bob; + +} + +float V_CalcNewBob ( struct ref_params_s *pparams ) +{ + static float bobtime; + static float lastbobtime; + float cycle; + + Vector vel; + VectorCopy( pparams->simvel, vel ); + vel[2] = 0; + + if ( pparams->onground == -1 || pparams->time == lastbobtime ) + { + return 0.0f; + } + + float speed = sqrt(vel[0] * vel[0] + vel[1] * vel[1]); + + speed = clamp( speed, -320, 320 ); + + float bob_offset = RemapVal( speed, 0, 320, 0.0f, 1.0f ); + + bobtime += ( pparams->time - lastbobtime ) * bob_offset; + lastbobtime = pparams->time; + + //Calculate the vertical bob + cycle = bobtime - (int)(bobtime/HL2_BOB_CYCLE_MAX)*HL2_BOB_CYCLE_MAX; + cycle /= HL2_BOB_CYCLE_MAX; + + if ( cycle < HL2_BOB_UP ) + { + cycle = M_PI * cycle / HL2_BOB_UP; + } + else + { + cycle = M_PI + M_PI*(cycle-HL2_BOB_UP)/(1.0 - HL2_BOB_UP); + } + + g_verticalBob = speed*0.015f; + g_verticalBob = g_verticalBob*0.3 + g_verticalBob*0.7*sin(cycle); + + g_verticalBob = clamp( g_verticalBob, -15.0f, 4.0f ); + + //Calculate the lateral bob + cycle = bobtime - (int)(bobtime/HL2_BOB_CYCLE_MAX*2)*HL2_BOB_CYCLE_MAX*2; + cycle /= HL2_BOB_CYCLE_MAX*2; + + if ( cycle < HL2_BOB_UP ) + { + cycle = M_PI * cycle / HL2_BOB_UP; + } + else + { + cycle = M_PI + M_PI*(cycle-HL2_BOB_UP)/(1.0 - HL2_BOB_UP); + } + + g_lateralBob = speed*0.004f; + g_lateralBob = g_lateralBob*0.3 + g_lateralBob*0.7*sin(cycle); + + g_lateralBob = clamp( g_lateralBob, -7.0f, 4.0f ); + + //NOTENOTE: We don't use this return value in our case (need to restructure the calculation function setup!) + return 0.0f; +} + +/* +=============== +V_CalcRoll +Used by view and sv_user +=============== +*/ +float V_CalcRoll (Vector angles, Vector velocity, float rollangle, float rollspeed ) +{ + float sign; + float side; + float value; + Vector forward, right, up; + + AngleVectors ( angles, forward, right, up ); + + side = DotProduct (velocity, right); + sign = side < 0 ? -1 : 1; + side = fabs( side ); + + value = rollangle; + if (side < rollspeed) + { + side = side * value / rollspeed; + } + else + { + side = value; + } + return side * sign; +} + +typedef struct pitchdrift_s +{ + float pitchvel; + int nodrift; + float driftmove; + double laststop; +} pitchdrift_t; + +static pitchdrift_t pd; + +void V_StartPitchDrift( void ) +{ + if ( pd.laststop == gEngfuncs.GetClientTime() ) + { + return; // something else is keeping it from drifting + } + + if ( pd.nodrift || !pd.pitchvel ) + { + pd.pitchvel = v_centerspeed->value; + pd.nodrift = 0; + pd.driftmove = 0; + } +} + +void V_StopPitchDrift ( void ) +{ + pd.laststop = gEngfuncs.GetClientTime(); + pd.nodrift = 1; + pd.pitchvel = 0; +} + +/* +=============== +V_DriftPitch + +Moves the client pitch angle towards idealpitch sent by the server. + +If the user is adjusting pitch manually, either with lookup/lookdown, +mlook and mouse, or klook and keyboard, pitch drifting is constantly stopped. +=============== +*/ +void V_DriftPitch ( struct ref_params_s *pparams ) +{ + float delta, move; + + if ( gEngfuncs.IsNoClipping() || !pparams->onground || pparams->demoplayback || pparams->spectator ) + { + pd.driftmove = 0; + pd.pitchvel = 0; + return; + } + + // don't count small mouse motion + if (pd.nodrift) + { + if ( fabs( pparams->cmd->forwardmove ) < cl_forwardspeed->value ) + pd.driftmove = 0; + else + pd.driftmove += pparams->frametime; + + if ( pd.driftmove > v_centermove->value) + { + V_StartPitchDrift (); + } + return; + } + + delta = pparams->idealpitch - pparams->cl_viewangles[PITCH]; + + if (!delta) + { + pd.pitchvel = 0; + return; + } + + move = pparams->frametime * pd.pitchvel; + pd.pitchvel += pparams->frametime * v_centerspeed->value; + + if (delta > 0) + { + if (move > delta) + { + pd.pitchvel = 0; + move = delta; + } + pparams->cl_viewangles[PITCH] += move; + } + else if (delta < 0) + { + if (move > -delta) + { + pd.pitchvel = 0; + move = -delta; + } + pparams->cl_viewangles[PITCH] -= move; + } +} + +/* +============================================================================== + VIEW RENDERING +============================================================================== +*/ +/* +================== +V_CalcGunAngle +================== +*/ +void V_CalcGunAngle ( struct ref_params_s *pparams ) +{ + cl_entity_t *viewent; + + viewent = gEngfuncs.GetViewModel(); + if ( !viewent ) + return; + + viewent->angles[YAW] = pparams->viewangles[YAW] + pparams->crosshairangle[YAW]; + viewent->angles[PITCH] = -pparams->viewangles[PITCH] + pparams->crosshairangle[PITCH] * 0.25; + viewent->angles[ROLL] -= v_idlescale * sin(pparams->time*v_iroll_cycle.value) * v_iroll_level.value; + + // don't apply all of the v_ipitch to prevent normally unseen parts of viewmodel from coming into view. + viewent->angles[PITCH] -= v_idlescale * sin(pparams->time*v_ipitch_cycle.value) * (v_ipitch_level.value * 0.5); + viewent->angles[YAW] -= v_idlescale * sin(pparams->time*v_iyaw_cycle.value) * v_iyaw_level.value; + + // buz: add half of the punchangle to viewmodel + viewent->angles[0] -= pparams->punchangle[0]/2; + viewent->angles[1] += pparams->punchangle[1]/2; + viewent->angles[2] += pparams->punchangle[2]/2; + + VectorCopy( viewent->angles, viewent->curstate.angles ); + VectorCopy( viewent->angles, viewent->latched.prevangles ); +} + +/* +============== +V_AddIdle + +Idle swaying +============== +*/ +void V_AddIdle( struct ref_params_s *pparams ) +{ + pparams->viewangles[ROLL] += v_idlescale * sin(pparams->time*v_iroll_cycle.value) * v_iroll_level.value; + pparams->viewangles[PITCH] += v_idlescale * sin(pparams->time*v_ipitch_cycle.value) * v_ipitch_level.value; + pparams->viewangles[YAW] += v_idlescale * sin(pparams->time*v_iyaw_cycle.value) * v_iyaw_level.value; +} + + +/* +============== +V_CalcViewRoll + +Roll is induced by movement and damage +============== +*/ +void V_CalcViewRoll ( struct ref_params_s *pparams ) +{ + cl_entity_t *viewentity; + + viewentity = gEngfuncs.GetEntityByIndex( pparams->viewentity ); + if( !viewentity ) return; + + pparams->viewangles[ROLL] = V_CalcRoll (pparams->viewangles, pparams->simvel, cl_rollangle->value, cl_rollspeed->value ) * 4; + + if ( pparams->health <= 0 && ( pparams->viewheight[2] != 0 ) ) + { + // only roll the view if the player is dead and the viewheight[2] is nonzero + // this is so deadcam in multiplayer will work. + pparams->viewangles[ROLL] = 80; // dead view angle + return; + } +} + + +/* +================== +V_CalcIntermissionRefdef + +================== +*/ +void V_CalcIntermissionRefdef ( struct ref_params_s *pparams ) +{ + cl_entity_t *ent, *view; + float old; + + // ent is the player model ( visible when out of body ) + ent = gEngfuncs.GetLocalPlayer(); + + // view is the weapon model (only visible from inside body ) + view = gEngfuncs.GetViewModel(); + + VectorCopy ( pparams->simorg, pparams->vieworg ); + VectorCopy ( pparams->cl_viewangles, pparams->viewangles ); + + view->model = NULL; + + // allways idle in intermission + old = v_idlescale; + v_idlescale = 1; + + V_AddIdle ( pparams ); + + if ( gEngfuncs.IsSpectateOnly() ) + { + // in HLTV we must go to 'intermission' position by ourself + VectorCopy( gHUD.m_Spectator.m_cameraOrigin, pparams->vieworg ); + VectorCopy( gHUD.m_Spectator.m_cameraAngles, pparams->viewangles ); + } + + v_idlescale = old; + + v_cl_angles = pparams->cl_viewangles; + v_origin = pparams->vieworg; + v_angles = pparams->viewangles; +} + +#define ORIGIN_BACKUP 64 +#define ORIGIN_MASK ( ORIGIN_BACKUP - 1 ) + +typedef struct +{ + Vector Origins[ORIGIN_BACKUP]; + float OriginTime[ORIGIN_BACKUP]; + + Vector Angles[ORIGIN_BACKUP]; + float AngleTime[ORIGIN_BACKUP]; + + int CurrentOrigin; + int CurrentAngle; +} viewinterp_t; + +float m_flWeaponLag = 0.5f; + +void V_CalcViewModelLag( ref_params_t *pparams, Vector &origin, Vector &angles, Vector original_angles ) +{ + static Vector m_vecLastFacing; + Vector vOriginalOrigin = origin; + Vector vOriginalAngles = angles; + + // Calculate our drift + Vector forward, right, up; + AngleVectors( angles, forward, right, up ); + + if( pparams->paused ) // not in paused + return; + + if ( pparams->frametime != 0.0f ) + { + Vector vDifference; + + vDifference = forward - m_vecLastFacing; + + float flSpeed = 4.0f; + + // If we start to lag too far behind, we'll increase the "catch up" speed. + // Solves the problem with fast cl_yawspeed, m_yaw or joysticks rotating quickly. + // The old code would slam lastfacing with origin causing the viewmodel to pop to a new position + float flDiff = vDifference.Length(); + + if (( flDiff > m_flWeaponLag ) && ( m_flWeaponLag > 0.0f )) + { + float flScale = flDiff / m_flWeaponLag; + flSpeed *= flScale; + } + + m_vecLastFacing = m_vecLastFacing + vDifference * ( flSpeed * pparams->frametime ); + // Make sure it doesn't grow out of control!!! + m_vecLastFacing = m_vecLastFacing.Normalize(); + origin = origin + (vDifference * -1.0f) * 1.0f; + } + + AngleVectors( original_angles, forward, right, up ); + + float pitch = original_angles[PITCH]; + + if ( pitch > 180.0f ) + { + pitch -= 360.0f; + } + else if ( pitch < -180.0f ) + { + pitch += 360.0f; + } + + if ( m_flWeaponLag <= 0.0f ) + { + origin = vOriginalOrigin; + angles = vOriginalAngles; + } + else + { + origin = origin + forward * ( -pitch * 0.00f ); + origin = origin + right * ( -pitch * 0.00f ); + origin = origin + up * ( -pitch * 0.00f ); + } +} + +//========================== +// V_CalcWaterLevel +//========================== +float V_CalcWaterLevel( struct ref_params_s *pparams ) +{ + float waterOffset = 0.0f; + + if( pparams->waterlevel >= 2 ) + { + int waterEntity = WATER_ENTITY( pparams->simorg ); + float waterDist = cl_waterdist->value; + + if( waterEntity >= 0 && waterEntity < pparams->max_entities ) + { + cl_entity_t *pwater = GET_ENTITY( waterEntity ); + if( pwater && ( pwater->model != NULL )) + waterDist += ( pwater->curstate.scale * 16.0f ); + } + + Vector point = pparams->vieworg; + + // eyes are above water, make sure we're above the waves + if( pparams->waterlevel == 2 ) + { + point.z -= waterDist; + + for( int i = 0; i < waterDist; i++ ) + { + int contents = POINT_CONTENTS( point ); + if( contents > CONTENTS_WATER ) + break; + point.z += 1; + } + waterOffset = (point.z + waterDist) - pparams->vieworg[2]; + } + else + { + // eyes are under water. Make sure we're far enough under + point[2] += waterDist; + + for( int i = 0; i < waterDist; i++ ) + { + int contents = POINT_CONTENTS( point ); + if( contents <= CONTENTS_WATER ) + break; + + point.z -= 1; + } + waterOffset = (point.z - waterDist) - pparams->vieworg[2]; + } + } + + return waterOffset; +} + +/* +================== +V_CalcRefdef + +================== +*/ +void V_CalcNormalRefdef ( struct ref_params_s *pparams ) +{ + cl_entity_t *ent, *view; + Vector angles; + float bob; + static viewinterp_t ViewInterp; + int i; + + static float oldz = 0; + static float lasttime; + + Vector camAngles, camForward, camRight, camUp; + + static struct model_s *savedviewmodel; + + VectorCopy(pparams->crosshairangle, g_CrosshairAngle); // save it for crosshair rendering + + V_DriftPitch ( pparams ); + + if ( gEngfuncs.IsSpectateOnly() ) + { + ent = gEngfuncs.GetEntityByIndex( g_iUser2 ); + } + else + { + // ent is the player model ( visible when out of body ) + ent = gEngfuncs.GetLocalPlayer(); + } + + // view is the weapon model (only visible from inside body ) + view = gEngfuncs.GetViewModel(); + + // save old position for light lerping + view->prevstate.origin = view->origin; + + // transform the view offset by the model's matrix to get the offset from + // model origin for the view + bob = V_CalcBob ( pparams ); + + // refresh position + VectorCopy ( pparams->simorg, pparams->vieworg ); + //pparams->vieworg[2] += ( bob ); + VectorAdd( pparams->vieworg, pparams->viewheight, pparams->vieworg ); + + VectorCopy ( pparams->cl_viewangles, pparams->viewangles ); + + gEngfuncs.V_CalcShake(); + gEngfuncs.V_ApplyShake( pparams->vieworg, pparams->viewangles, 1.0 ); + + // never let view origin sit exactly on a node line, because a water plane can + // dissapear when viewed with the eye exactly on it. + // the server protocol only specifies to 1/16 pixel, so add 1/32 in each axis + + pparams->vieworg[0] += 1.0/32; + pparams->vieworg[1] += 1.0/32; + pparams->vieworg[2] += 1.0/32; + + float waterOffset = V_CalcWaterLevel( pparams ); + pparams->vieworg[2] += waterOffset; + + V_CalcViewRoll ( pparams ); + + if( gHUD.m_flBlurAmount <= 0.0f ) + { + if( g_iGunMode == 3 ) + v_idlescale = 0.15f; + else v_idlescale = 0.0f; + } + + V_AddIdle ( pparams ); + + // offsets + VectorCopy( pparams->cl_viewangles, angles ); + + AngleVectors ( angles, pparams->forward, pparams->right, pparams->up ); + + // buz: spec tank view + if (gHUD.m_SpecTank_on) + { + VectorCopy(gHUD.m_SpecTank_point, pparams->vieworg); + VectorMA(pparams->vieworg, -gHUD.m_SpecTank_distFwd, pparams->forward, pparams->vieworg); + VectorMA(pparams->vieworg, gHUD.m_SpecTank_distUp, pparams->up, pparams->vieworg); + } + + + // don't allow cheats in multiplayer + if ( pparams->maxclients <= 1 ) + { + pparams->vieworg += scr_ofsx->value*pparams->forward + scr_ofsy->value*pparams->right + scr_ofsz->value*pparams->up; + } + + Vector pl_vel; + VectorCopy(pparams->simvel, pl_vel); + + float m_flSpeed = pl_vel.Length2D(); + + if (!(gHUD.m_iKeyBits & IN_DUCK) && !(gHUD.m_iKeyBits & IN_JUMP)) + { + if ((gHUD.m_iKeyBits & IN_RUN) && (gHUD.m_iKeyBits & IN_FORWARD) && (m_flSpeed > 210.0)) + { + if ( m_flOfs <= 1 ) + m_flOfs += 0.05; + + if(m_flOfs >= 1) + m_flOfs = 1; + } + else + { + if ( m_flOfs >=0 ) + m_flOfs -=0.05; + + if(m_flOfs <= 0) + m_flOfs = 0; + } + } + + pparams->vieworg[2] += m_flOfs; + + // Treating cam_ofs[2] as the distance + if( CL_IsThirdPerson() ) + { + Vector ofs; + + ofs[0] = ofs[1] = ofs[2] = 0.0; + + CL_CameraOffset( (float *)&ofs ); + + VectorCopy( ofs, camAngles ); + camAngles[ ROLL ] = 0; + + AngleVectors( camAngles, camForward, camRight, camUp ); + + for ( i = 0; i < 3; i++ ) + { + pparams->vieworg[ i ] += -ofs[2] * camForward[ i ]; + } + } + + // Give gun our viewangles + VectorCopy ( pparams->cl_viewangles, view->angles ); + Vector lastAngles = view->angles;// save oldangles + + // set up gun position + V_CalcGunAngle ( pparams ); + + // Use predicted origin as view origin. + VectorCopy ( pparams->vieworg, view->origin ); +// VectorAdd( view->origin, pparams->viewheight, view->origin ); + + // Let the viewmodel shake at about 10% of the amplitude + gEngfuncs.V_ApplyShake( view->origin, view->angles, 0.9 ); + + Vector forward, right; + + AngleVectors( view->angles, forward, right, NULL ); + + V_CalcNewBob ( pparams ); + + // Apply bob, but scaled down to 40% + VectorMA( view->origin, g_verticalBob * 0.1f, forward, view->origin ); + + // Z bob a bit more + view->origin[2] += g_verticalBob * 0.1f; + + // bob the angles + angles[ ROLL ]+= g_verticalBob * 0.3f; + angles[ PITCH ]-= g_verticalBob * 0.8f; + + angles[ YAW ]-= g_lateralBob * 0.5f; + + VectorMA( view->origin, g_lateralBob * 0.8f, right, view->origin ); + + for ( i = 0; i < 2; i++ ) + { + pparams->viewangles[ROLL] += bob * 0.3; + pparams->viewangles[YAW] += bob * 0.2; + } + + pparams->vieworg[ROLL] -= sqrt(bob * bob) * 2 * pparams->up[ ROLL ]; + view->origin[ROLL] -= sqrt(bob * bob) * 2 * pparams->up[ ROLL ]; + + // pushing the view origin down off of the same X/Z plane as the ent's origin will give the + // gun a very nice 'shifting' effect when the player looks up/down. If there is a problem + // with view model distortion, this may be a cause. (SJB). + // g-cont. disabled to avoid IronSight issues +// view->origin[2] -= 1; + + V_CalcViewModelLag( pparams, view->origin, view->angles, lastAngles ); + + // fudge position around to keep amount of weapon visible + // roughly equal with different FOV + if (pparams->viewsize == 110) + { + view->origin[2] += 1; + } + else if (pparams->viewsize == 100) + { + view->origin[2] += 2; + } + else if (pparams->viewsize == 90) + { + view->origin[2] += 1; + } + else if (pparams->viewsize == 80) + { + view->origin[2] += 0.5; + } + + // Add in the punchangle, if any + VectorAdd ( pparams->viewangles, pparams->punchangle, pparams->viewangles ); + + // Include client side punch, too + VectorAdd ( pparams->viewangles, (float *)&ev_punchangle, pparams->viewangles); + + V_DropPunchAngle ( pparams->frametime, (float *)&ev_punchangle ); + + // smooth out stair step ups +#if 1 + if ( !pparams->smoothing && pparams->onground && pparams->simorg[2] - oldz > 0) + { + float steptime; + + steptime = pparams->time - lasttime; + if (steptime < 0) + steptime = 0; + + oldz += steptime * 120; + if (oldz > pparams->simorg[2]) + oldz = pparams->simorg[2]; + if (pparams->simorg[2] - oldz > 18) + oldz = pparams->simorg[2]- 18; + pparams->vieworg[2] += oldz - pparams->simorg[2]; + view->origin[2] += oldz - pparams->simorg[2]; + } + else + { + oldz = pparams->simorg[2]; + } +#endif + SetupFlashlight( pparams ); // buz + + { + static float lastorg[3]; + Vector delta; + + VectorSubtract( pparams->simorg, lastorg, delta ); + + if ( Length( delta ) != 0.0 ) + { + VectorCopy( pparams->simorg, ViewInterp.Origins[ ViewInterp.CurrentOrigin & ORIGIN_MASK ] ); + ViewInterp.OriginTime[ ViewInterp.CurrentOrigin & ORIGIN_MASK ] = pparams->time; + ViewInterp.CurrentOrigin++; + + VectorCopy( pparams->simorg, lastorg ); + } + } + + // Smooth out whole view in multiplayer when on trains, lifts + if ( cl_vsmoothing && cl_vsmoothing->value && ( pparams->smoothing && ( pparams->maxclients > 1 ) ) ) + { + int foundidx; + int i; + float t; + + if ( cl_vsmoothing->value < 0.0 ) + { + gEngfuncs.Cvar_SetValue( "cl_vsmoothing", 0.0 ); + } + + t = pparams->time - cl_vsmoothing->value; + + for ( i = 1; i < ORIGIN_MASK; i++ ) + { + foundidx = ViewInterp.CurrentOrigin - 1 - i; + if ( ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ] <= t ) + break; + } + + if ( i < ORIGIN_MASK && ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ] != 0.0 ) + { + // Interpolate + Vector delta; + double frac; + double dt; + Vector neworg; + + dt = ViewInterp.OriginTime[ (foundidx + 1) & ORIGIN_MASK ] - ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ]; + if ( dt > 0.0 ) + { + frac = ( t - ViewInterp.OriginTime[ foundidx & ORIGIN_MASK] ) / dt; + frac = min( 1.0, frac ); + delta = ViewInterp.Origins[(foundidx + 1) & ORIGIN_MASK] - ViewInterp.Origins[foundidx & ORIGIN_MASK]; + VectorMA( ViewInterp.Origins[ foundidx & ORIGIN_MASK ], frac, delta, neworg ); + + // Dont interpolate large changes + if ( Length( delta ) < 64 ) + { + VectorSubtract( neworg, pparams->simorg, delta ); + + VectorAdd( pparams->simorg, delta, pparams->simorg ); + VectorAdd( pparams->vieworg, delta, pparams->vieworg ); + VectorAdd( view->origin, delta, view->origin ); + + } + } + } + } + + // Store off v_angles before munging for third person + v_angles = pparams->viewangles; + v_lastAngles = pparams->viewangles; +// v_cl_angles = pparams->cl_viewangles; // keep old user mouse angles ! + + if ( CL_IsThirdPerson() ) + { + VectorCopy( camAngles, pparams->viewangles); + float pitch = camAngles[ 0 ]; + + // Normalize angles + if ( pitch > 180 ) + pitch -= 360.0; + else if ( pitch < -180 ) + pitch += 360; + + // Player pitch is inverted + pitch /= -3.0; + + // Slam local player's pitch value + ent->angles[ 0 ] = pitch; + ent->curstate.angles[ 0 ] = pitch; + ent->prevstate.angles[ 0 ] = pitch; + ent->latched.prevangles[ 0 ] = pitch; + } + + // override all previous settings if the viewent isn't the client + if ( pparams->viewentity > pparams->maxclients ) + { + cl_entity_t *viewentity; + viewentity = gEngfuncs.GetEntityByIndex( pparams->viewentity ); + if ( viewentity ) + { + VectorCopy( viewentity->origin, pparams->vieworg ); + VectorCopy( viewentity->angles, pparams->viewangles ); + + // Store off overridden viewangles + v_angles = pparams->viewangles; + } + } + + lasttime = pparams->time; + + v_origin = pparams->vieworg; +} + +void V_SmoothInterpolateAngles( float * startAngle, float * endAngle, float * finalAngle, float degreesPerSec ) +{ + float absd,frac,d,threshhold; + + V_NormalizeAngles( startAngle ); + V_NormalizeAngles( endAngle ); + + for ( int i = 0 ; i < 3 ; i++ ) + { + d = endAngle[i] - startAngle[i]; + + if ( d > 180.0f ) + { + d -= 360.0f; + } + else if ( d < -180.0f ) + { + d += 360.0f; + } + + absd = fabs(d); + + if ( absd > 0.01f ) + { + frac = degreesPerSec * v_frametime; + + threshhold= degreesPerSec / 4; + + if ( absd < threshhold ) + { + float h = absd / threshhold; + h *= h; + frac*= h; // slow down last degrees + } + + if ( frac > absd ) + { + finalAngle[i] = endAngle[i]; + } + else + { + if ( d>0) + finalAngle[i] = startAngle[i] + frac; + else + finalAngle[i] = startAngle[i] - frac; + } + } + else + { + finalAngle[i] = endAngle[i]; + } + + } + + V_NormalizeAngles( finalAngle ); +} + +// Get the origin of the Observer based around the target's position and angles +void V_GetChaseOrigin( float * angles, float * origin, float distance, float * returnvec ) +{ + Vector vecEnd; + Vector forward; + Vector vecStart; + pmtrace_t * trace; + int maxLoops = 8; + + int ignoreent = -1; // first, ignore no entity + + cl_entity_t * ent = NULL; + + // Trace back from the target using the player's view angles + AngleVectors(angles, forward, NULL, NULL); + + VectorScale(forward,-1,forward); + + VectorCopy( origin, vecStart ); + + VectorMA(vecStart, distance , forward, vecEnd); + + while ( maxLoops > 0) + { + trace = gEngfuncs.PM_TraceLine( vecStart, vecEnd, PM_TRACELINE_PHYSENTSONLY, 2, ignoreent ); + + // WARNING! trace->ent is is the number in physent list not the normal entity number + + if ( trace->ent <= 0) + break; // we hit the world or nothing, stop trace + + ent = gEngfuncs.GetEntityByIndex( PM_GetPhysEntInfo( trace->ent ) ); + + if ( ent == NULL ) + break; + + // hit non-player solid BSP , stop here + if ( ent->curstate.solid == SOLID_BSP && !ent->player ) + break; + + // if close enought to end pos, stop, otherwise continue trace + if( Distance(trace->endpos, vecEnd ) < 1.0f ) + { + break; + } + else + { + ignoreent = trace->ent; // ignore last hit entity + VectorCopy( trace->endpos, vecStart); + } + + maxLoops--; + } + + VectorMA( trace->endpos, 4, trace->plane.normal, returnvec ); + + v_lastDistance = Distance(trace->endpos, origin); // real distance without offset +} + +void V_GetSingleTargetCam(cl_entity_t * ent1, float * angle, float * origin) +{ + float newAngle[3]; float newOrigin[3]; + + int flags = gHUD.m_Spectator.m_iObserverFlags; + + // see is target is a dead player + qboolean deadPlayer = ent1->player && (ent1->curstate.solid == SOLID_NOT); + + float dfactor = ( flags & DRC_FLAG_DRAMATIC )? -1.0f : 1.0f; + + float distance = 112.0f + ( 16.0f * dfactor ); // get close if dramatic; + + // go away in final scenes or if player just died + if ( flags & DRC_FLAG_FINAL ) + distance*=2.0f; + else if ( deadPlayer ) + distance*=1.5f; + + // let v_lastDistance float smoothly away + v_lastDistance+= v_frametime * 32.0f; // move unit per seconds back + + if ( distance > v_lastDistance ) + distance = v_lastDistance; + + VectorCopy(ent1->origin, newOrigin); + + if ( ent1->player ) + { + if ( deadPlayer ) + newOrigin[2]+= 2; //laying on ground + else + newOrigin[2]+= 17; // head level of living player + + } + else + newOrigin[2]+= 8; // object, tricky, must be above bomb in CS + + // we have no second target, choose view direction based on + // show front of primary target + VectorCopy(ent1->angles, newAngle); + + // show dead players from front, normal players back + if ( flags & DRC_FLAG_FACEPLAYER ) + newAngle[1]+= 180.0f; + + + newAngle[0]+= 12.5f * dfactor; // lower angle if dramatic + + // if final scene (bomb), show from real high pos + if ( flags & DRC_FLAG_FINAL ) + newAngle[0] = 22.5f; + + // choose side of object/player + if ( flags & DRC_FLAG_SIDE ) + newAngle[1]+=22.5f; + else + newAngle[1]-=22.5f; + + V_SmoothInterpolateAngles( v_lastAngles, newAngle, angle, 120.0f ); + + // HACK, if player is dead don't clip against his dead body, can't check this + V_GetChaseOrigin( angle, newOrigin, distance, origin ); +} + +float MaxAngleBetweenAngles( float * a1, float * a2 ) +{ + float d, maxd = 0.0f; + + V_NormalizeAngles( a1 ); + V_NormalizeAngles( a2 ); + + for ( int i = 0 ; i < 3 ; i++ ) + { + d = a2[i] - a1[i]; + if ( d > 180 ) + { + d -= 360; + } + else if ( d < -180 ) + { + d += 360; + } + + d = fabs(d); + + if ( d > maxd ) + maxd=d; + } + + return maxd; +} + +void V_GetDoubleTargetsCam(cl_entity_t * ent1, cl_entity_t * ent2,float * angle, float * origin) +{ + float newAngle[3]; float newOrigin[3]; float tempVec[3]; + + int flags = gHUD.m_Spectator.m_iObserverFlags; + + float dfactor = ( flags & DRC_FLAG_DRAMATIC )? -1.0f : 1.0f; + + float distance = 112.0f + ( 16.0f * dfactor ); // get close if dramatic; + + // go away in final scenes or if player just died + if ( flags & DRC_FLAG_FINAL ) + distance*=2.0f; + + // let v_lastDistance float smoothly away + v_lastDistance+= v_frametime * 32.0f; // move unit per seconds back + + if ( distance > v_lastDistance ) + distance = v_lastDistance; + + VectorCopy(ent1->origin, newOrigin); + + if ( ent1->player ) + newOrigin[2]+= 17; // head level of living player + else + newOrigin[2]+= 8; // object, tricky, must be above bomb in CS + + // get new angle towards second target + VectorSubtract( ent2->origin, ent1->origin, newAngle ); + + VectorAngles( newAngle, newAngle ); + newAngle[0] = -newAngle[0]; + + // set angle diffrent in Dramtaic scenes + newAngle[0]+= 15.5f * dfactor; // lower angle if dramatic + + if ( flags & DRC_FLAG_SIDE ) + newAngle[1]+=22.5f; + else + newAngle[1]-=22.5f; + + float d = MaxAngleBetweenAngles( v_lastAngles, newAngle ); + + if ( ( d < v_cameraFocusAngle) && ( v_cameraMode == CAM_MODE_RELAX ) ) + { + // difference is to small and we are in relax camera mode, keep viewangles + VectorCopy(v_lastAngles, newAngle ); + } + else if ( (d < v_cameraRelaxAngle) && (v_cameraMode == CAM_MODE_FOCUS) ) + { + // we catched up with our target, relax again + v_cameraMode = CAM_MODE_RELAX; + } + else + { + // target move too far away, focus camera again + v_cameraMode = CAM_MODE_FOCUS; + } + + // and smooth view, if not a scene cut + if ( v_resetCamera || (v_cameraMode == CAM_MODE_RELAX) ) + { + VectorCopy( newAngle, angle ); + } + else + { + V_SmoothInterpolateAngles( v_lastAngles, newAngle, angle, 180.0f ); + } + + V_GetChaseOrigin( newAngle, newOrigin, distance, origin ); + + // move position up, if very close at target + if ( v_lastDistance < 64.0f ) + origin[2]+= 16.0f*( 1.0f - (v_lastDistance / 64.0f ) ); + + // calculate angle to second target + VectorSubtract( ent2->origin, origin, tempVec ); + VectorAngles( tempVec, tempVec ); + tempVec[0] = -tempVec[0]; + + /* take middle between two viewangles + InterpolateAngles( newAngle, tempVec, newAngle, 0.5f); */ +} + +void V_GetDirectedChasePosition(cl_entity_t* ent1, cl_entity_t * ent2,float * angle, float * origin) +{ + if ( v_resetCamera ) + { + v_lastDistance = 4096.0f; + // v_cameraMode = CAM_MODE_FOCUS; + } + + if ( ( ent2 == (cl_entity_t*)0xFFFFFFFF ) || ( ent1->player && (ent1->curstate.solid == SOLID_NOT) ) ) + { + // we have no second target or player just died + V_GetSingleTargetCam(ent1, angle, origin); + } + else if ( ent2 ) + { + // keep both target in view + V_GetDoubleTargetsCam( ent1, ent2, angle, origin ); + } + else + { + // second target disappeard somehow (dead) + + // keep last good viewangle + float newOrigin[3]; + + int flags = gHUD.m_Spectator.m_iObserverFlags; + + float dfactor = ( flags & DRC_FLAG_DRAMATIC )? -1.0f : 1.0f; + + float distance = 112.0f + ( 16.0f * dfactor ); // get close if dramatic; + + // go away in final scenes or if player just died + if ( flags & DRC_FLAG_FINAL ) + distance*=2.0f; + + // let v_lastDistance float smoothly away + v_lastDistance+= v_frametime * 32.0f; // move unit per seconds back + + if ( distance > v_lastDistance ) + distance = v_lastDistance; + + VectorCopy(ent1->origin, newOrigin); + + if ( ent1->player ) + newOrigin[2]+= 17; // head level of living player + else + newOrigin[2]+= 8; // object, tricky, must be above bomb in CS + + V_GetChaseOrigin( angle, newOrigin, distance, origin ); + } + + VectorCopy(angle, v_lastAngles); +} + +void V_GetChasePos(int target, float * cl_angles, float * origin, float * angles) +{ + cl_entity_t * ent = NULL; + + if ( target ) + { + ent = gEngfuncs.GetEntityByIndex( target ); + }; + + if (!ent) + { + // just copy a save in-map position + VectorCopy ( vJumpAngles, angles ); + VectorCopy ( vJumpOrigin, origin ); + return; + } + + + + if ( gHUD.m_Spectator.m_autoDirector->value ) + { + if ( g_iUser3 ) + V_GetDirectedChasePosition( ent, gEngfuncs.GetEntityByIndex( g_iUser3 ), + angles, origin ); + else + V_GetDirectedChasePosition( ent, ( cl_entity_t*)0xFFFFFFFF, + angles, origin ); + } + else + { + if ( cl_angles == NULL ) // no mouse angles given, use entity angles ( locked mode ) + { + VectorCopy ( ent->angles, angles); + angles[0]*=-1; + } + else + VectorCopy ( cl_angles, angles); + + + VectorCopy ( ent->origin, origin); + + origin[2]+= 28; // DEFAULT_VIEWHEIGHT - some offset + + V_GetChaseOrigin( angles, origin, cl_chasedist->value, origin ); + } + + v_resetCamera = false; +} + +void V_ResetChaseCam() +{ + v_resetCamera = true; +} + +void V_GetInEyePos(int target, float * origin, float * angles ) +{ + if ( !target) + { + // just copy a save in-map position + VectorCopy ( vJumpAngles, angles ); + VectorCopy ( vJumpOrigin, origin ); + return; + }; + + + cl_entity_t * ent = gEngfuncs.GetEntityByIndex( target ); + + if ( !ent ) + return; + + VectorCopy ( ent->origin, origin ); + VectorCopy ( ent->angles, angles ); + + angles[PITCH]*=-3.0f; // see CL_ProcessEntityUpdate() + + if ( ent->curstate.solid == SOLID_NOT ) + { + angles[ROLL] = 80; // dead view angle + origin[2]+= -8 ; // PM_DEAD_VIEWHEIGHT + } + else if (ent->curstate.usehull == 1 ) + origin[2]+= 12; // VEC_DUCK_VIEW; + else + // exacty eye position can't be caluculated since it depends on + // client values like cl_bobcycle, this offset matches the default values + origin[2]+= 28; // DEFAULT_VIEWHEIGHT +} + +void V_GetMapFreePosition( float * cl_angles, float * origin, float * angles ) +{ + Vector forward; + Vector zScaledTarget; + + VectorCopy(cl_angles, angles); + + // modify angles since we don't wanna see map's bottom + angles[0] = 51.25f + 38.75f*(angles[0]/90.0f); + + zScaledTarget[0] = gHUD.m_Spectator.m_mapOrigin[0]; + zScaledTarget[1] = gHUD.m_Spectator.m_mapOrigin[1]; + zScaledTarget[2] = gHUD.m_Spectator.m_mapOrigin[2] * (( 90.0f - angles[0] ) / 90.0f ); + + + AngleVectors(angles, forward, NULL, NULL); + + VectorNormalize(forward); + + VectorMA(zScaledTarget, -( 4096.0f / gHUD.m_Spectator.m_mapZoom ), forward , origin); +} + +void V_GetMapChasePosition(int target, float * cl_angles, float * origin, float * angles) +{ + Vector forward; + + if ( target ) + { + cl_entity_t * ent = gEngfuncs.GetEntityByIndex( target ); + + if ( gHUD.m_Spectator.m_autoDirector->value ) + { + // this is done to get the angles made by director mode + V_GetChasePos(target, cl_angles, origin, angles); + VectorCopy(ent->origin, origin); + + // keep fix chase angle horizontal + angles[0] = 45.0f; + } + else + { + VectorCopy(cl_angles, angles); + VectorCopy(ent->origin, origin); + + // modify angles since we don't wanna see map's bottom + angles[0] = 51.25f + 38.75f*(angles[0]/90.0f); + } + } + else + { + // keep out roaming position, but modify angles + VectorCopy(cl_angles, angles); + angles[0] = 51.25f + 38.75f*(angles[0]/90.0f); + } + + origin[2] *= (( 90.0f - angles[0] ) / 90.0f ); + angles[2] = 0.0f; // don't roll angle (if chased player is dead) + + AngleVectors(angles, forward, NULL, NULL); + + VectorNormalize(forward); + + VectorMA(origin, -1536, forward, origin); +} + +int V_FindViewModelByWeaponModel(int weaponindex) +{ + + static char * modelmap[][2] = { + { "models/p_crowbar.mdl", "models/v_crowbar.mdl" }, + { "models/p_9mmhandgun.mdl", "models/v_9mmhandgun.mdl" }, + { "models/p_grenade.mdl", "models/v_grenade.mdl" }, + { "models/p_9mmAR.mdl", "models/v_9mmAR.mdl" }, + { "models/p_rpg.mdl", "models/v_rpg.mdl" }, + { "models/p_shotgun.mdl", "models/v_shotgun.mdl" }, + { NULL, NULL } }; + + struct model_s * weaponModel = IEngineStudio.GetModelByIndex( weaponindex ); + + if ( weaponModel ) + { + int len = strlen( weaponModel->name ); + int i = 0; + + while ( modelmap[i] != NULL ) + { + if ( !strnicmp( weaponModel->name, modelmap[i][0], len ) ) + { + return gEngfuncs.pEventAPI->EV_FindModelIndex( modelmap[i][1] ); + } + i++; + } + } + + return 0; +} + + +/* +================== +V_CalcSpectatorRefdef + +================== +*/ +void V_CalcSpectatorRefdef ( struct ref_params_s * pparams ) +{ + static Vector velocity ( 0.0f, 0.0f, 0.0f); + + static int lastWeaponModelIndex = 0; + static int lastViewModelIndex = 0; + + cl_entity_t * ent = gEngfuncs.GetEntityByIndex( g_iUser2 ); + + pparams->onlyClientDraw = false; + + // refresh position + VectorCopy ( pparams->simorg, v_sim_org ); + + // get old values + VectorCopy ( pparams->cl_viewangles, v_cl_angles ); + VectorCopy ( pparams->viewangles, v_angles ); + VectorCopy ( pparams->vieworg, v_origin ); + + if ( ( g_iUser1 == OBS_IN_EYE || gHUD.m_Spectator.m_pip->value == INSET_IN_EYE ) && ent ) + { + // calculate player velocity + float timeDiff = ent->curstate.msg_time - ent->prevstate.msg_time; + + if ( timeDiff > 0 ) + { + Vector distance; + VectorSubtract(ent->prevstate.origin, ent->curstate.origin, distance); + VectorScale(distance, 1/timeDiff, distance ); + + velocity[0] = velocity[0]*0.9f + distance[0]*0.1f; + velocity[1] = velocity[1]*0.9f + distance[1]*0.1f; + velocity[2] = velocity[2]*0.9f + distance[2]*0.1f; + + VectorCopy(velocity, pparams->simvel); + } + + // predict missing client data and set weapon model ( in HLTV mode or inset in eye mode ) + if ( gEngfuncs.IsSpectateOnly() ) + { + V_GetInEyePos( g_iUser2, pparams->simorg, pparams->cl_viewangles ); + + pparams->health = 1; + + cl_entity_t * gunModel = gEngfuncs.GetViewModel(); + + if ( lastWeaponModelIndex != ent->curstate.weaponmodel ) + { + // weapon model changed + + lastWeaponModelIndex = ent->curstate.weaponmodel; + lastViewModelIndex = V_FindViewModelByWeaponModel( lastWeaponModelIndex ); + if ( lastViewModelIndex ) + { + gEngfuncs.pfnWeaponAnim(0,0); // reset weapon animation + } + else + { + // model not found + gunModel->model = NULL; // disable weapon model + lastWeaponModelIndex = lastViewModelIndex = 0; + } + } + + if ( lastViewModelIndex ) + { + gunModel->model = IEngineStudio.GetModelByIndex( lastViewModelIndex ); + gunModel->curstate.modelindex = lastViewModelIndex; + gunModel->curstate.frame = 0; + gunModel->curstate.colormap = 0; + gunModel->index = g_iUser2; + } + else + { + gunModel->model = NULL; // disable weaopn model + } + } + else + { + // only get viewangles from entity + VectorCopy ( ent->angles, pparams->cl_viewangles ); + pparams->cl_viewangles[PITCH]*=-3.0f; // see CL_ProcessEntityUpdate() + } + } + + v_frametime = pparams->frametime; + + if ( pparams->nextView == 0 ) + { + // first renderer cycle, full screen + + switch( g_iUser1 ) + { + case OBS_CHASE_LOCKED: + V_GetChasePos( g_iUser2, NULL, v_origin, v_angles ); + break; + case OBS_CHASE_FREE: + V_GetChasePos( g_iUser2, v_cl_angles, v_origin, v_angles ); + break; + case OBS_ROAMING: + VectorCopy (v_cl_angles, v_angles); + VectorCopy (v_sim_org, v_origin); + break; + case OBS_IN_EYE: + V_CalcNormalRefdef ( pparams ); + break; + case OBS_MAP_FREE: + pparams->onlyClientDraw = true; + V_GetMapFreePosition( v_cl_angles, v_origin, v_angles ); + break; + case OBS_MAP_CHASE: + pparams->onlyClientDraw = true; + V_GetMapChasePosition( g_iUser2, v_cl_angles, v_origin, v_angles ); + break; + } + + if ( gHUD.m_Spectator.m_pip->value ) + pparams->nextView = 1; // force a second renderer view + + gHUD.m_Spectator.m_iDrawCycle = 0; + + } + else + { + // second renderer cycle, inset window + + // set inset parameters + pparams->viewport[0] = XRES(gHUD.m_Spectator.m_OverviewData.insetWindowX); // change viewport to inset window + pparams->viewport[1] = YRES(gHUD.m_Spectator.m_OverviewData.insetWindowY); + pparams->viewport[2] = XRES(gHUD.m_Spectator.m_OverviewData.insetWindowWidth); + pparams->viewport[3] = YRES(gHUD.m_Spectator.m_OverviewData.insetWindowHeight); + pparams->nextView = 0; // on further view + + // override some settings in certain modes + switch( (int)gHUD.m_Spectator.m_pip->value ) + { + case INSET_CHASE_FREE: + V_GetChasePos( g_iUser2, v_cl_angles, v_origin, v_angles ); + break; + case INSET_IN_EYE: + V_CalcNormalRefdef ( pparams ); + break; + case INSET_MAP_FREE: + pparams->onlyClientDraw = true; + V_GetMapFreePosition( v_cl_angles, v_origin, v_angles ); + break; + case INSET_MAP_CHASE: + pparams->onlyClientDraw = true; + + if( g_iUser1 == OBS_ROAMING ) + V_GetMapChasePosition( 0, v_cl_angles, v_origin, v_angles ); + else + V_GetMapChasePosition( g_iUser2, v_cl_angles, v_origin, v_angles ); + break; + } + + gHUD.m_Spectator.m_iDrawCycle = 1; + } + + // write back new values into pparams + VectorCopy ( v_cl_angles, pparams->cl_viewangles ); + VectorCopy ( v_angles, pparams->viewangles ); + VectorCopy ( v_origin, pparams->vieworg ); + +} + +void DLLEXPORT V_CalcRefdef( struct ref_params_s *pparams ) +{ + g_pViewParams = pparams; + + // intermission / finale rendering + if ( pparams->intermission ) + { + V_CalcIntermissionRefdef ( pparams ); + } + else if ( pparams->spectator || g_iUser1 ) // g_iUser true if in spectator mode + { + V_CalcSpectatorRefdef ( pparams ); + } + else if ( !pparams->paused ) + { + V_CalcNormalRefdef ( pparams ); + } +} + +/* +============= +V_DropPunchAngle + +============= +*/ +void V_DropPunchAngle ( float frametime, float *ev_punchangle ) +{ + float len; + + len = VectorNormalize ( ev_punchangle ); + len -= (10.0 + len * 0.5) * frametime; + len = max( len, 0.0 ); + VectorScale ( ev_punchangle, len, ev_punchangle ); +} + +/* +============= +V_PunchAxis + +Client side punch effect +============= +*/ +void V_PunchAxis( int axis, float punch ) +{ +// ev_punchangle[ axis ] = punch; // buz - disable +} + +/* +============= +V_Init +============= +*/ +void V_Init (void) +{ + gEngfuncs.pfnAddCommand ("centerview", V_StartPitchDrift ); + gEngfuncs.pfnAddCommand ("buildcubemaps", CL_BuildCubemaps_f ); + ADD_COMMAND( "gpu_mem_usage", GL_GpuMemUsage_f ); + + scr_ofsx = CVAR_REGISTER( "scr_ofsx","0", 0 ); + scr_ofsy = CVAR_REGISTER( "scr_ofsy","0", 0 ); + scr_ofsz = CVAR_REGISTER( "scr_ofsz","0", 0 ); + + v_centermove = CVAR_REGISTER( "v_centermove", "0.15", 0 ); + v_centerspeed = CVAR_REGISTER( "v_centerspeed","500", 0 ); + + cl_bobcycle = CVAR_REGISTER( "cl_bobcycle","0.8", 0 );// best default for my experimental gun wag (sjb) + cl_bob = CVAR_REGISTER( "cl_bob","0.01", 0 );// best default for my experimental gun wag (sjb) + cl_bobup = CVAR_REGISTER( "cl_bobup","0.5", 0 ); + cl_waterdist = CVAR_REGISTER( "cl_waterdist","4", FCVAR_ARCHIVE ); + cl_chasedist = CVAR_REGISTER( "cl_chasedist","112", FCVAR_ARCHIVE ); + r_shadows = CVAR_REGISTER( "r_shadows", "0", FCVAR_CLIENTDLL|FCVAR_ARCHIVE ); + r_shadow_split_weight = CVAR_REGISTER( "r_pssm_split_weight", "0.9", FCVAR_ARCHIVE ); + r_pssm_show_split = CVAR_REGISTER( "r_pssm_show_split", "0", 0 ); // debug feature + r_scissor_glass_debug = CVAR_REGISTER( "r_scissor_glass_debug", "0", FCVAR_ARCHIVE ); + r_scissor_light_debug = CVAR_REGISTER( "r_scissor_light_debug", "0", FCVAR_ARCHIVE ); + r_recursion_depth = CVAR_REGISTER( "gl_recursion_depth", "1", FCVAR_ARCHIVE ); + r_showlightmaps = CVAR_REGISTER( "r_showlightmaps", "0", 0 ); + + // setup some engine cvars for custom rendering + r_test = CVAR_GET_POINTER( "gl_test" ); + cv_nosort = CVAR_GET_POINTER( "gl_nosort" ); + r_overview = CVAR_GET_POINTER( "dev_overview" ); + r_fullbright = CVAR_GET_POINTER( "r_fullbright" ); + r_drawentities = CVAR_GET_POINTER( "r_drawentities" ); + gl_extensions = CVAR_GET_POINTER( "gl_allow_extensions" ); + r_finish = CVAR_GET_POINTER( "gl_finish" ); + cv_crosshair = CVAR_GET_POINTER( "crosshair" ); + r_lighting_ambient = CVAR_GET_POINTER( "r_lighting_ambient" ); + r_lighting_modulate = CVAR_GET_POINTER( "r_lighting_modulate" ); + r_lightstyle_lerping= CVAR_GET_POINTER( "cl_lightstyle_lerping" ); + r_lighting_extended = CVAR_GET_POINTER( "r_lighting_extended" ); + r_draw_beams = CVAR_GET_POINTER( "cl_draw_beams" ); + r_detailtextures = CVAR_GET_POINTER( "r_detailtextures" ); + r_speeds = CVAR_GET_POINTER( "r_speeds" ); + r_novis = CVAR_GET_POINTER( "r_novis" ); + r_nocull = CVAR_GET_POINTER( "r_nocull" ); + r_lockpvs = CVAR_GET_POINTER( "r_lockpvs" ); + r_wireframe = CVAR_GET_POINTER( "gl_wireframe" ); + r_lightmap = CVAR_GET_POINTER( "r_lightmap" ); + r_decals = CVAR_GET_POINTER( "r_decals" ); + r_clear = CVAR_GET_POINTER( "gl_clear" ); + r_dynamic = CVAR_GET_POINTER( "r_dynamic" ); + cv_gamma = CVAR_GET_POINTER( "gamma" ); + cv_brightness = CVAR_GET_POINTER( "brightness" ); + r_polyoffset = CVAR_GET_POINTER( "gl_polyoffset" ); + + v_glows = CVAR_REGISTER( "gl_glows", "1", FCVAR_ARCHIVE ); + + r_dof = CVAR_REGISTER( "r_dof", "1", FCVAR_ARCHIVE ); + r_dof_hold_time = CVAR_REGISTER( "r_dof_hold_time", "0.2", FCVAR_ARCHIVE ); + r_dof_change_time = CVAR_REGISTER( "r_dof_change_time", "0.8", FCVAR_ARCHIVE ); + r_dof_focal_length = CVAR_REGISTER( "r_dof_focal_length", "600.0", FCVAR_ARCHIVE ); + r_dof_fstop = CVAR_REGISTER( "r_dof_fstop", "8", FCVAR_ARCHIVE ); + r_dof_debug = CVAR_REGISTER( "r_dof_debug", "0", FCVAR_ARCHIVE ); + + cv_renderer = CVAR_REGISTER( "gl_renderer", "1", FCVAR_CLIENTDLL|FCVAR_ARCHIVE ); + cv_bump = CVAR_REGISTER( "gl_bump", "1", FCVAR_ARCHIVE ); + cv_bumpvecs = CVAR_REGISTER( "bump_vecs", "0", 0 ); + cv_specular = CVAR_REGISTER( "gl_specular", "1", FCVAR_ARCHIVE ); + cv_dynamiclight = CVAR_REGISTER( "gl_dynlight", "1", FCVAR_ARCHIVE ); + cv_parallax = CVAR_REGISTER( "gl_parallax", "1", FCVAR_ARCHIVE ); + cv_shadow_offset = CVAR_REGISTER( "gl_shadow_offset", "0.02", FCVAR_ARCHIVE ); + cv_deferred = CVAR_REGISTER( "gl_deferred", "0", FCVAR_ARCHIVE ); + cv_deferred_full = CVAR_REGISTER( "gl_deferred_fullres_shadows", "0", FCVAR_ARCHIVE ); + cv_deferred_maxlights = CVAR_REGISTER( "gl_deferred_maxlights", "8", FCVAR_ARCHIVE|FCVAR_LATCH ); + cv_deferred_tracebmodels = CVAR_REGISTER( "gl_deferred_tracebmodels", "0", FCVAR_ARCHIVE ); + cv_cubemaps = CVAR_REGISTER( "gl_cubemap_reflection", "1", FCVAR_ARCHIVE ); + cv_cube_lod_bias = CVAR_REGISTER( "gl_cube_lod_bias", "3", FCVAR_ARCHIVE ); + cv_water = CVAR_REGISTER( "gl_waterblur", "1", FCVAR_ARCHIVE ); + cv_decalsdebug = CVAR_REGISTER( "gl_decals_debug", "0", FCVAR_ARCHIVE ); + cv_realtime_puddles = CVAR_REGISTER( "gl_realtime_puddles", "0", FCVAR_ARCHIVE ); + r_sunshadows = CVAR_REGISTER( "gl_sun_shadows", "0", FCVAR_ARCHIVE ); + r_sun_allowed = CVAR_REGISTER( "r_sun_allowed", "1", FCVAR_ARCHIVE ); + r_shadowmap_size = CVAR_REGISTER( "gl_shadowmap_size", "512", FCVAR_ARCHIVE ); + r_occlusion_culling = CVAR_REGISTER( "r_occlusion_culling", "0", FCVAR_ARCHIVE ); + r_show_lightprobes = CVAR_REGISTER( "r_show_lightprobes", "0", FCVAR_ARCHIVE ); + r_show_cubemaps = CVAR_REGISTER( "r_show_cubemaps", "0", FCVAR_ARCHIVE ); + r_show_viewleaf = CVAR_REGISTER( "r_show_viewleaf", "0", FCVAR_ARCHIVE ); + cv_decals = CVAR_REGISTER( "gl_decals", "1", FCVAR_ARCHIVE ); + r_lightstyles = CVAR_REGISTER( "gl_lightstyles", "1", FCVAR_ARCHIVE ); + r_allow_mirrors = CVAR_REGISTER( "gl_allow_mirrors", "1", FCVAR_ARCHIVE ); + r_studio_decals = CVAR_REGISTER( "r_studio_decals", "32", FCVAR_ARCHIVE ); + cv_show_tbn = CVAR_REGISTER( "gl_show_basis", "0", FCVAR_ARCHIVE ); + cv_brdf = CVAR_REGISTER( "r_lighting_brdf", "1", FCVAR_ARCHIVE ); + + r_grass = CVAR_REGISTER( "r_grass", "1", FCVAR_ARCHIVE ); + r_grass_alpha = CVAR_REGISTER( "r_grass_alpha", "0.5", FCVAR_ARCHIVE ); + r_grass_lighting = CVAR_REGISTER( "r_grass_lighting", "1", FCVAR_ARCHIVE ); + r_grass_shadows = CVAR_REGISTER( "r_grass_shadows", "1", FCVAR_ARCHIVE ); + r_grass_fade_start = CVAR_REGISTER( "r_grass_fade_start", "1024", FCVAR_ARCHIVE ); + r_grass_fade_dist = CVAR_REGISTER( "r_grass_fade_dist", "2048", FCVAR_ARCHIVE ); +} + +//=============================== +// buz: flashlight managenemt +//=============================== +void SetupFlashlight( struct ref_params_s *pparams ) +{ + if( !g_flashlight ) + return; + + static float add = 0.0f; + float addideal = 0.0f; + pmtrace_t ptr; + + Vector origin = pparams->vieworg + Vector( 0.0f, 0.0f, 6.0f ) + (pparams->right * 5.0f) + (pparams->forward * 2.0f); + Vector vecEnd = origin + (pparams->forward * 700.0f); + + gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); + gEngfuncs.pEventAPI->EV_PlayerTrace( origin, vecEnd, PM_NORMAL, -1, &ptr ); + const char *texName = gEngfuncs.pEventAPI->EV_TraceTexture( ptr.ent, origin, vecEnd ); + + if( ptr.fraction < 1.0f ) + addideal = (1.0f - ptr.fraction) * 30.0f; + float speed = (add - addideal) * 10.0f; + if( speed < 0 ) speed *= -1.0f; + + if( add < addideal ) + { + add += pparams->frametime * speed; + if( add > addideal ) add = addideal; + } + else if( add > addideal ) + { + add -= pparams->frametime * speed; + if( add < addideal ) add = addideal; + } + + CDynLight *flashlight = CL_AllocDlight( FLASHLIGHT_KEY ); + + R_SetupLightParams( flashlight, origin, pparams->viewangles, 700.0f, 35.0f + add, LIGHT_SPOT ); + R_SetupLightTexture( flashlight, tr.flashlightTexture ); + + flashlight->color = Vector( 1.4f, 1.4f, 1.4f ); // make model dymanic lighting happy + flashlight->die = pparams->time + 0.05f; + + if( texName && ( !Q_strnicmp( texName, "reflect", 7 ) || !Q_strnicmp( texName, "mirror", 6 )) && r_allow_mirrors->value ) + { + CDynLight *flashlight2 = CL_AllocDlight( FLASHLIGHT_KEY + 1 ); + float d = 2.0f * DotProduct( pparams->forward, ptr.plane.normal ); + Vector angles, dir = (pparams->forward - ptr.plane.normal) * d; + VectorAngles( dir, angles ); + + R_SetupLightParams( flashlight2, ptr.endpos, angles, 700.0f, 35.0f + add, LIGHT_SPOT ); + R_SetupLightTexture( flashlight2, tr.flashlightTexture ); + flashlight2->color = Vector( 1.4f, 1.4f, 1.4f ); // make model dymanic lighting happy + flashlight2->die = pparams->time + 0.01f; + } +} \ No newline at end of file diff --git a/cl_dll/saytext.cpp b/cl_dll/saytext.cpp new file mode 100644 index 0000000..7f24cf4 --- /dev/null +++ b/cl_dll/saytext.cpp @@ -0,0 +1,321 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// saytext.cpp +// +// implementation of CHudSayText class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +#include "vgui_TeamFortressViewport.h" + +extern float *GetClientColor( int clientIndex ); + +#define MAX_LINES 5 +#define MAX_CHARS_PER_LINE 256 /* it can be less than this, depending on char size */ + +// allow 20 pixels on either side of the text +#define MAX_LINE_WIDTH ( ScreenWidth - 40 ) +#define LINE_START 10 +static float SCROLL_SPEED = 5; + +static char g_szLineBuffer[ MAX_LINES + 1 ][ MAX_CHARS_PER_LINE ]; +static float *g_pflNameColors[ MAX_LINES + 1 ]; +static int g_iNameLengths[ MAX_LINES + 1 ]; +static float flScrollTime = 0; // the time at which the lines next scroll up + +static int Y_START = 0; +static int line_height = 0; + +DECLARE_MESSAGE( m_SayText, SayText ); + +int CHudSayText :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( SayText ); + + InitHUDData(); + + m_HUD_saytext = gEngfuncs.pfnRegisterVariable( "hud_saytext", "1", 0 ); + m_HUD_saytext_time = gEngfuncs.pfnRegisterVariable( "hud_saytext_time", "5", 0 ); + + m_iFlags |= HUD_INTERMISSION; // is always drawn during an intermission + + return 1; +} + + +void CHudSayText :: InitHUDData( void ) +{ + memset( g_szLineBuffer, 0, sizeof g_szLineBuffer ); + memset( g_pflNameColors, 0, sizeof g_pflNameColors ); + memset( g_iNameLengths, 0, sizeof g_iNameLengths ); +} + +int CHudSayText :: VidInit( void ) +{ + return 1; +} + + +int ScrollTextUp( void ) +{ + ConsolePrint( g_szLineBuffer[0] ); // move the first line into the console buffer + g_szLineBuffer[MAX_LINES][0] = 0; + memmove( g_szLineBuffer[0], g_szLineBuffer[1], sizeof(g_szLineBuffer) - sizeof(g_szLineBuffer[0]) ); // overwrite the first line + memmove( &g_pflNameColors[0], &g_pflNameColors[1], sizeof(g_pflNameColors) - sizeof(g_pflNameColors[0]) ); + memmove( &g_iNameLengths[0], &g_iNameLengths[1], sizeof(g_iNameLengths) - sizeof(g_iNameLengths[0]) ); + g_szLineBuffer[MAX_LINES-1][0] = 0; + + if ( g_szLineBuffer[0][0] == ' ' ) // also scroll up following lines + { + g_szLineBuffer[0][0] = 2; + return 1 + ScrollTextUp(); + } + + return 1; +} + +int CHudSayText :: Draw( float flTime ) +{ + int y = Y_START; + + if ( ( gViewPort && gViewPort->AllowedToPrintText() == FALSE) || !m_HUD_saytext->value ) + return 1; + + // make sure the scrolltime is within reasonable bounds, to guard against the clock being reset + flScrollTime = min( flScrollTime, flTime + m_HUD_saytext_time->value ); + + // make sure the scrolltime is within reasonable bounds, to guard against the clock being reset + flScrollTime = min( flScrollTime, flTime + m_HUD_saytext_time->value ); + + if ( flScrollTime <= flTime ) + { + if ( *g_szLineBuffer[0] ) + { + flScrollTime = flTime + m_HUD_saytext_time->value; + // push the console up + ScrollTextUp(); + } + else + { // buffer is empty, just disable drawing of this section + m_iFlags &= ~HUD_ACTIVE; + } + } + + for ( int i = 0; i < MAX_LINES; i++ ) + { + if ( *g_szLineBuffer[i] ) + { + if ( *g_szLineBuffer[i] == 2 && g_pflNameColors[i] ) + { + // it's a saytext string + static char buf[MAX_PLAYER_NAME_LENGTH+32]; + + // draw the first x characters in the player color + strncpy( buf, g_szLineBuffer[i], min(g_iNameLengths[i], MAX_PLAYER_NAME_LENGTH+32) ); + buf[ min(g_iNameLengths[i], MAX_PLAYER_NAME_LENGTH+31) ] = 0; + gEngfuncs.pfnDrawSetTextColor( g_pflNameColors[i][0], g_pflNameColors[i][1], g_pflNameColors[i][2] ); + int x = DrawConsoleString( LINE_START, y, buf ); + + // color is reset after each string draw + DrawConsoleString( x, y, g_szLineBuffer[i] + g_iNameLengths[i] ); + } + else + { + // normal draw + DrawConsoleString( LINE_START, y, g_szLineBuffer[i] ); + } + } + + y += line_height; + } + + + return 1; +} + +int CHudSayText :: MsgFunc_SayText( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int client_index = READ_BYTE(); // the client who spoke the message + SayTextPrint( READ_STRING(), iSize - 1, client_index ); + + return 1; +} + +void CHudSayText :: SayTextPrint( const char *pszBuf, int iBufSize, int clientIndex ) +{ + if ( gViewPort && gViewPort->AllowedToPrintText() == FALSE ) + { + // Print it straight to the console + ConsolePrint( pszBuf ); + return; + } + + // find an empty string slot + for ( int i = 0; i < MAX_LINES; i++ ) + { + if ( ! *g_szLineBuffer[i] ) + break; + } + if ( i == MAX_LINES ) + { + // force scroll buffer up + ScrollTextUp(); + i = MAX_LINES - 1; + } + + g_iNameLengths[i] = 0; + g_pflNameColors[i] = NULL; + + // if it's a say message, search for the players name in the string + if ( *pszBuf == 2 && clientIndex > 0 ) + { + GetPlayerInfo( clientIndex, &g_PlayerInfoList[clientIndex] ); + const char *pName = g_PlayerInfoList[clientIndex].name; + + if ( pName ) + { + const char *nameInString = strstr( pszBuf, pName ); + + if ( nameInString ) + { + g_iNameLengths[i] = strlen( pName ) + (nameInString - pszBuf); + g_pflNameColors[i] = GetClientColor( clientIndex ); + } + } + } + + strncpy( g_szLineBuffer[i], pszBuf, max(iBufSize -1, MAX_CHARS_PER_LINE-1) ); + + // make sure the text fits in one line + EnsureTextFitsInOneLineAndWrapIfHaveTo( i ); + + // Set scroll time + if ( i == 0 ) + { + flScrollTime = gHUD.m_flTime + m_HUD_saytext_time->value; + } + + m_iFlags |= HUD_ACTIVE; + PlaySound( "misc/talk.wav", 1 ); + + if ( ScreenHeight >= 480 ) + Y_START = ScreenHeight - 60; + else + Y_START = ScreenHeight - 45; + Y_START -= (line_height * (MAX_LINES+1)); + +} + +void CHudSayText :: EnsureTextFitsInOneLineAndWrapIfHaveTo( int line ) +{ + int line_width = 0; + GetConsoleStringSize( g_szLineBuffer[line], &line_width, &line_height ); + + if ( (line_width + LINE_START) > MAX_LINE_WIDTH ) + { // string is too long to fit on line + // scan the string until we find what word is too long, and wrap the end of the sentence after the word + int length = LINE_START; + int tmp_len = 0; + char *last_break = NULL; + for ( char *x = g_szLineBuffer[line]; *x != 0; x++ ) + { + // check for a color change, if so skip past it + if ( x[0] == '/' && x[1] == '(' ) + { + x += 2; + // skip forward until past mode specifier + while ( *x != 0 && *x != ')' ) + x++; + + if ( *x != 0 ) + x++; + + if ( *x == 0 ) + break; + } + + char buf[2]; + buf[1] = 0; + + if ( *x == ' ' && x != g_szLineBuffer[line] ) // store each line break, except for the very first character + last_break = x; + + buf[0] = *x; // get the length of the current character + GetConsoleStringSize( buf, &tmp_len, &line_height ); + length += tmp_len; + + if ( length > MAX_LINE_WIDTH ) + { // needs to be broken up + if ( !last_break ) + last_break = x-1; + + x = last_break; + + // find an empty string slot + int j; + do + { + for ( j = 0; j < MAX_LINES; j++ ) + { + if ( ! *g_szLineBuffer[j] ) + break; + } + if ( j == MAX_LINES ) + { + // need to make more room to display text, scroll stuff up then fix the pointers + int linesmoved = ScrollTextUp(); + line -= linesmoved; + last_break = last_break - (sizeof(g_szLineBuffer[0]) * linesmoved); + } + } + while ( j == MAX_LINES ); + + // copy remaining string into next buffer, making sure it starts with a space character + if ( (char)*last_break == (char)' ' ) + { + int linelen = strlen(g_szLineBuffer[j]); + int remaininglen = strlen(last_break); + + if ( (linelen - remaininglen) <= MAX_CHARS_PER_LINE ) + strcat( g_szLineBuffer[j], last_break ); + } + else + { + if ( (strlen(g_szLineBuffer[j]) - strlen(last_break) - 2) < MAX_CHARS_PER_LINE ) + { + strcat( g_szLineBuffer[j], " " ); + strcat( g_szLineBuffer[j], last_break ); + } + } + + *last_break = 0; // cut off the last string + + EnsureTextFitsInOneLineAndWrapIfHaveTo( j ); + break; + } + } + } +} \ No newline at end of file diff --git a/cl_dll/stamina.cpp b/cl_dll/stamina.cpp new file mode 100644 index 0000000..1a7fa80 --- /dev/null +++ b/cl_dll/stamina.cpp @@ -0,0 +1,75 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +#include "vgui_TeamFortressViewport.h" +#include "vgui_hud.h" + +DECLARE_MESSAGE( m_HudStamina, Stamina ) + +int CHudStamina :: Init(void) +{ + m_iStam = 100; + m_fFade = 0; + m_iFlags = 0; + + HOOK_MESSAGE(Stamina); + + gHUD.AddHudElem(this); + + return 1; +} + +int CHudStamina :: VidInit( void ) +{ + int HUD_suit_empty = gHUD.GetSpriteIndex( "suit_empty" ); + int HUD_suit_full = gHUD.GetSpriteIndex( "suit_full" ); + + m_hSprite1 = m_hSprite2 = 0; // delaying get sprite handles until we know the sprites are loaded + m_prc1 = &gHUD.GetSpriteRect( HUD_suit_empty ); + m_prc2 = &gHUD.GetSpriteRect( HUD_suit_full ); + m_iHeight = m_prc2->bottom - m_prc1->top; + m_fFade = 0; + return 1; +} + +int CHudStamina :: MsgFunc_Stamina( const char *pszName, int iSize, void *pbuf ) +{ + m_iFlags |= HUD_ACTIVE; + + BEGIN_READ( pbuf, iSize ); + int x = READ_SHORT(); + + if( x != m_iStam ) + { + m_fFade = FADE_TIME; + m_iStam = x; + } + + gViewPort->m_pHud2->UpdateStamina(x); + + return 1; +} + +int CHudStamina :: Draw( float flTime ) +{ + return 1; +} \ No newline at end of file diff --git a/cl_dll/status_icons.cpp b/cl_dll/status_icons.cpp new file mode 100644 index 0000000..5bbcec8 --- /dev/null +++ b/cl_dll/status_icons.cpp @@ -0,0 +1,162 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// status_icons.cpp +// +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include +#include +#include "parsemsg.h" +#include "event_api.h" + +DECLARE_MESSAGE( m_StatusIcons, StatusIcon ); + +int CHudStatusIcons::Init( void ) +{ + HOOK_MESSAGE( StatusIcon ); + + gHUD.AddHudElem( this ); + + Reset(); + + return 1; +} + +int CHudStatusIcons::VidInit( void ) +{ + + return 1; +} + +void CHudStatusIcons::Reset( void ) +{ + memset( m_IconList, 0, sizeof m_IconList ); + m_iFlags &= ~HUD_ACTIVE; +} + +// Draw status icons along the left-hand side of the screen +int CHudStatusIcons::Draw( float flTime ) +{ + if (gEngfuncs.IsSpectateOnly()) + return 1; + // find starting position to draw from, along right-hand side of screen + int x = 5; + int y = ScreenHeight / 2; + + // loop through icon list, and draw any valid icons drawing up from the middle of screen + for ( int i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( m_IconList[i].spr ) + { + y -= ( m_IconList[i].rc.bottom - m_IconList[i].rc.top ) + 5; + + SPR_Set( m_IconList[i].spr, m_IconList[i].r, m_IconList[i].g, m_IconList[i].b ); + SPR_DrawAdditive( 0, x, y, &m_IconList[i].rc ); + } + } + + return 1; +} + +// Message handler for StatusIcon message +// accepts five values: +// byte : TRUE = ENABLE icon, FALSE = DISABLE icon +// string : the sprite name to display +// byte : red +// byte : green +// byte : blue +int CHudStatusIcons::MsgFunc_StatusIcon( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int ShouldEnable = READ_BYTE(); + char *pszIconName = READ_STRING(); + if ( ShouldEnable ) + { + int r = READ_BYTE(); + int g = READ_BYTE(); + int b = READ_BYTE(); + EnableIcon( pszIconName, r, g, b ); + m_iFlags |= HUD_ACTIVE; + } + else + { + DisableIcon( pszIconName ); + } + + return 1; +} + +// add the icon to the icon list, and set it's drawing color +void CHudStatusIcons::EnableIcon( char *pszIconName, unsigned char red, unsigned char green, unsigned char blue ) +{ + // check to see if the sprite is in the current list + for ( int i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( !stricmp( m_IconList[i].szSpriteName, pszIconName ) ) + break; + } + + if ( i == MAX_ICONSPRITES ) + { + // icon not in list, so find an empty slot to add to + for ( i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( !m_IconList[i].spr ) + break; + } + } + + // if we've run out of space in the list, overwrite the first icon + if ( i == MAX_ICONSPRITES ) + { + i = 0; + } + + // Load the sprite and add it to the list + // the sprite must be listed in hud.txt + int spr_index = gHUD.GetSpriteIndex( pszIconName ); + m_IconList[i].spr = gHUD.GetSprite( spr_index ); + m_IconList[i].rc = gHUD.GetSpriteRect( spr_index ); + m_IconList[i].r = red; + m_IconList[i].g = green; + m_IconList[i].b = blue; + strcpy( m_IconList[i].szSpriteName, pszIconName ); + + // Hack: Play Timer sound when a grenade icon is played (in 0.8 seconds) + if ( strstr(m_IconList[i].szSpriteName, "grenade") ) + { + cl_entity_t *pthisplayer = gEngfuncs.GetLocalPlayer(); + gEngfuncs.pEventAPI->EV_PlaySound( pthisplayer->index, pthisplayer->origin, CHAN_STATIC, "weapons/timer.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); + } +} + +void CHudStatusIcons::DisableIcon( char *pszIconName ) +{ + // find the sprite is in the current list + for ( int i = 0; i < MAX_ICONSPRITES; i++ ) + { + if ( !stricmp( m_IconList[i].szSpriteName, pszIconName ) ) + { + // clear the item from the list + memset( &m_IconList[i], 0, sizeof icon_sprite_t ); + return; + } + } +} diff --git a/cl_dll/statusbar.cpp b/cl_dll/statusbar.cpp new file mode 100644 index 0000000..f13bb69 --- /dev/null +++ b/cl_dll/statusbar.cpp @@ -0,0 +1,265 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// statusbar.cpp +// +// generic text status bar, set by game dll +// runs across bottom of screen +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +DECLARE_MESSAGE( m_StatusBar, StatusText ); +DECLARE_MESSAGE( m_StatusBar, StatusValue ); + +#define STATUSBAR_ID_LINE 1 + +float *GetClientColor( int clientIndex ); +extern float g_ColorYellow[3]; + +int CHudStatusBar :: Init( void ) +{ + gHUD.AddHudElem( this ); + + HOOK_MESSAGE( StatusText ); + HOOK_MESSAGE( StatusValue ); + + Reset(); + + CVAR_REGISTER( "hud_centerid", "0", FCVAR_ARCHIVE ); + + return 1; +} + +int CHudStatusBar :: VidInit( void ) +{ + // Load sprites here + + return 1; +} + +void CHudStatusBar :: Reset( void ) +{ + int i = 0; + + m_iFlags &= ~HUD_ACTIVE; // start out inactive + for ( i = 0; i < MAX_STATUSBAR_LINES; i++ ) + m_szStatusText[i][0] = 0; + memset( m_iStatusValues, 0, sizeof m_iStatusValues ); + + m_iStatusValues[0] = 1; // 0 is the special index, which always returns true + + // reset our colors for the status bar lines (yellow is default) + for ( i = 0; i < MAX_STATUSBAR_LINES; i++ ) + m_pflNameColors[i] = g_ColorYellow; +} + +void CHudStatusBar :: ParseStatusString( int line_num ) +{ + // localise string first + char szBuffer[MAX_STATUSTEXT_LENGTH]; + memset( szBuffer, 0, sizeof szBuffer ); + gHUD.m_TextMessage.LocaliseTextString( m_szStatusText[line_num], szBuffer, MAX_STATUSTEXT_LENGTH ); + + // parse m_szStatusText & m_iStatusValues into m_szStatusBar + memset( m_szStatusBar[line_num], 0, MAX_STATUSTEXT_LENGTH ); + char *src = szBuffer; + char *dst = m_szStatusBar[line_num]; + + char *src_start = src, *dst_start = dst; + + while ( *src != 0 ) + { + while ( *src == '\n' ) + src++; // skip over any newlines + + if ( ((src - src_start) >= MAX_STATUSTEXT_LENGTH) || ((dst - dst_start) >= MAX_STATUSTEXT_LENGTH) ) + break; + + int index = atoi( src ); + // should we draw this line? + if ( (index >= 0 && index < MAX_STATUSBAR_VALUES) && (m_iStatusValues[index] != 0) ) + { // parse this line and append result to the status bar + while ( *src >= '0' && *src <= '9' ) + src++; + + if ( *src == '\n' || *src == 0 ) + continue; // no more left in this text line + + // copy the text, char by char, until we hit a % or a \n + while ( *src != '\n' && *src != 0 ) + { + if ( *src != '%' ) + { // just copy the character + *dst = *src; + dst++, src++; + } + else + { + // get the descriptor + char valtype = *(++src); // move over % + + // if it's a %, draw a % sign + if ( valtype == '%' ) + { + *dst = valtype; + dst++, src++; + continue; + } + + // move over descriptor, then get and move over the index + index = atoi( ++src ); + while ( *src >= '0' && *src <= '9' ) + src++; + + if ( index >= 0 && index < MAX_STATUSBAR_VALUES ) + { + int indexval = m_iStatusValues[index]; + + // get the string to substitute in place of the %XX + char szRepString[MAX_PLAYER_NAME_LENGTH]; + switch ( valtype ) + { + case 'p': // player name + GetPlayerInfo( indexval, &g_PlayerInfoList[indexval] ); + if ( g_PlayerInfoList[indexval].name != NULL ) + { + strncpy( szRepString, g_PlayerInfoList[indexval].name, MAX_PLAYER_NAME_LENGTH ); + m_pflNameColors[line_num] = GetClientColor( indexval ); + } + else + { + strcpy( szRepString, "******" ); + } + + break; + case 'i': // number + sprintf( szRepString, "%d", indexval ); + break; + default: + szRepString[0] = 0; + } + + for ( char *cp = szRepString; *cp != 0 && ((dst - dst_start) < MAX_STATUSTEXT_LENGTH); cp++, dst++ ) + *dst = *cp; + } + } + } + } + else + { + // skip to next line of text + while ( *src != 0 && *src != '\n' ) + src++; + } + } +} + +int CHudStatusBar :: Draw( float fTime ) +{ + if ( m_bReparseString ) + { + for ( int i = 0; i < MAX_STATUSBAR_LINES; i++ ) + { + m_pflNameColors[i] = g_ColorYellow; + ParseStatusString( i ); + } + m_bReparseString = FALSE; + } + + int Y_START = ScreenHeight - YRES(32 + 4); + + // Draw the status bar lines + for ( int i = 0; i < MAX_STATUSBAR_LINES; i++ ) + { + int TextHeight, TextWidth; + GetConsoleStringSize( m_szStatusBar[i], &TextWidth, &TextHeight ); + + int x = 4; + int y = Y_START - ( 4 + TextHeight * i ); // draw along bottom of screen + + // let user set status ID bar centering + if ( (i == STATUSBAR_ID_LINE) && CVAR_GET_FLOAT("hud_centerid") ) + { + x = max( 0, max(2, (ScreenWidth - TextWidth)) / 2 ); + y = (ScreenHeight / 2) + (TextHeight*CVAR_GET_FLOAT("hud_centerid")); + } + + if ( m_pflNameColors[i] ) + gEngfuncs.pfnDrawSetTextColor( m_pflNameColors[i][0], m_pflNameColors[i][1], m_pflNameColors[i][2] ); + + DrawConsoleString( x, y, m_szStatusBar[i] ); + } + + return 1; +} + +// Message handler for StatusText message +// accepts two values: +// byte: line number of status bar text +// string: status bar text +// this string describes how the status bar should be drawn +// a semi-regular expression: +// ( slotnum ([a..z] [%pX] [%iX])*)* +// where slotnum is an index into the Value table (see below) +// if slotnum is 0, the string is always drawn +// if StatusValue[slotnum] != 0, the following string is drawn, upto the next newline - otherwise the text is skipped upto next newline +// %pX, where X is an integer, will substitute a player name here, getting the player index from StatusValue[X] +// %iX, where X is an integer, will substitute a number here, getting the number from StatusValue[X] +int CHudStatusBar :: MsgFunc_StatusText( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int line = READ_BYTE(); + + if ( line < 0 || line >= MAX_STATUSBAR_LINES ) + return 1; + + strncpy( m_szStatusText[line], READ_STRING(), MAX_STATUSTEXT_LENGTH ); + m_szStatusText[line][MAX_STATUSTEXT_LENGTH-1] = 0; // ensure it's null terminated ( strncpy() won't null terminate if read string too long) + + if ( m_szStatusText[0] == 0 ) + m_iFlags &= ~HUD_ACTIVE; + else + m_iFlags |= HUD_ACTIVE; // we have status text, so turn on the status bar + + m_bReparseString = TRUE; + + return 1; +} + +// Message handler for StatusText message +// accepts two values: +// byte: index into the status value array +// short: value to store +int CHudStatusBar :: MsgFunc_StatusValue( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int index = READ_BYTE(); + if ( index < 1 || index >= MAX_STATUSBAR_VALUES ) + return 1; // index out of range + + m_iStatusValues[index] = READ_SHORT(); + + m_bReparseString = TRUE; + + return 1; +} \ No newline at end of file diff --git a/cl_dll/text_message.cpp b/cl_dll/text_message.cpp new file mode 100644 index 0000000..c18dd25 --- /dev/null +++ b/cl_dll/text_message.cpp @@ -0,0 +1,213 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// text_message.cpp +// +// implementation of CHudTextMessage class +// +// this class routes messages through titles.txt for localisation +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +#include "vgui_TeamFortressViewport.h" + +DECLARE_MESSAGE( m_TextMessage, TextMsg ); + +int CHudTextMessage::Init(void) +{ + HOOK_MESSAGE( TextMsg ); + + gHUD.AddHudElem( this ); + + Reset(); + + return 1; +}; + +// Searches through the string for any msg names (indicated by a '#') +// any found are looked up in titles.txt and the new message substituted +// the new value is pushed into dst_buffer +char *CHudTextMessage::LocaliseTextString( const char *msg, char *dst_buffer, int buffer_size ) +{ + char *dst = dst_buffer; + for ( char *src = (char*)msg; *src != 0 && buffer_size > 0; buffer_size-- ) + { + if ( *src == '#' ) + { + // cut msg name out of string + static char word_buf[255]; + char *wdst = word_buf, *word_start = src; + for ( ++src ; (*src >= 'A' && *src <= 'z') || (*src >= '0' && *src <= '9'); wdst++, src++ ) + { + *wdst = *src; + } + *wdst = 0; + + // lookup msg name in titles.txt + client_textmessage_t *clmsg = TextMessageGet( word_buf ); + if ( !clmsg || !(clmsg->pMessage) ) + { + src = word_start; + *dst = *src; + dst++, src++; + continue; + } + + // copy string into message over the msg name + for ( char *wsrc = (char*)clmsg->pMessage; *wsrc != 0; wsrc++, dst++ ) + { + *dst = *wsrc; + } + *dst = 0; + } + else + { + *dst = *src; + dst++, src++; + *dst = 0; + } + } + + dst_buffer[buffer_size-1] = 0; // ensure null termination + return dst_buffer; +} + +// As above, but with a local static buffer +char *CHudTextMessage::BufferedLocaliseTextString( const char *msg ) +{ + static char dst_buffer[1024]; + LocaliseTextString( msg, dst_buffer, 1024 ); + return dst_buffer; +} + +// Simplified version of LocaliseTextString; assumes string is only one word +char *CHudTextMessage::LookupString( const char *msg, int *msg_dest ) +{ + if ( !msg ) + return ""; + + // '#' character indicates this is a reference to a string in titles.txt, and not the string itself + if ( msg[0] == '#' ) + { + // this is a message name, so look up the real message + client_textmessage_t *clmsg = TextMessageGet( msg+1 ); + + if ( !clmsg || !(clmsg->pMessage) ) + return (char*)msg; // lookup failed, so return the original string + + if ( msg_dest ) + { + // check to see if titles.txt info overrides msg destination + // if clmsg->effect is less than 0, then clmsg->effect holds -1 * message_destination + if ( clmsg->effect < 0 ) // + *msg_dest = -clmsg->effect; + } + + return (char*)clmsg->pMessage; + } + else + { // nothing special about this message, so just return the same string + return (char*)msg; + } +} + +void StripEndNewlineFromString( char *str ) +{ + int s = strlen( str ) - 1; + if ( str[s] == '\n' || str[s] == '\r' ) + str[s] = 0; +} + +// converts all '\r' characters to '\n', so that the engine can deal with the properly +// returns a pointer to str +char* ConvertCRtoNL( char *str ) +{ + for ( char *ch = str; *ch != 0; ch++ ) + if ( *ch == '\r' ) + *ch = '\n'; + return str; +} + +// Message handler for text messages +// displays a string, looking them up from the titles.txt file, which can be localised +// parameters: +// byte: message direction ( HUD_PRINTCONSOLE, HUD_PRINTNOTIFY, HUD_PRINTCENTER, HUD_PRINTTALK ) +// string: message +// optional parameters: +// string: message parameter 1 +// string: message parameter 2 +// string: message parameter 3 +// string: message parameter 4 +// any string that starts with the character '#' is a message name, and is used to look up the real message in titles.txt +// the next (optional) one to four strings are parameters for that string (which can also be message names if they begin with '#') +int CHudTextMessage::MsgFunc_TextMsg( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int msg_dest = READ_BYTE(); + + static char szBuf[6][128]; + char *msg_text = LookupString( READ_STRING(), &msg_dest ); + msg_text = strcpy( szBuf[0], msg_text ); + + // keep reading strings and using C format strings for subsituting the strings into the localised text string + char *sstr1 = LookupString( READ_STRING() ); + sstr1 = strcpy( szBuf[1], sstr1 ); + StripEndNewlineFromString( sstr1 ); // these strings are meant for subsitution into the main strings, so cull the automatic end newlines + char *sstr2 = LookupString( READ_STRING() ); + sstr2 = strcpy( szBuf[2], sstr2 ); + StripEndNewlineFromString( sstr2 ); + char *sstr3 = LookupString( READ_STRING() ); + sstr3 = strcpy( szBuf[3], sstr3 ); + StripEndNewlineFromString( sstr3 ); + char *sstr4 = LookupString( READ_STRING() ); + sstr4 = strcpy( szBuf[4], sstr4 ); + StripEndNewlineFromString( sstr4 ); + char *psz = szBuf[5]; + + if ( gViewPort && gViewPort->AllowedToPrintText() == FALSE ) + return 1; + + switch ( msg_dest ) + { + case HUD_PRINTCENTER: + sprintf( psz, msg_text, sstr1, sstr2, sstr3, sstr4 ); + CenterPrint( ConvertCRtoNL( psz ) ); + break; + + case HUD_PRINTNOTIFY: + psz[0] = 1; // mark this message to go into the notify buffer + sprintf( psz+1, msg_text, sstr1, sstr2, sstr3, sstr4 ); + ConsolePrint( ConvertCRtoNL( psz ) ); + break; + + case HUD_PRINTTALK: + sprintf( psz, msg_text, sstr1, sstr2, sstr3, sstr4 ); + gHUD.m_SayText.SayTextPrint( ConvertCRtoNL( psz ), 128 ); + break; + + case HUD_PRINTCONSOLE: + sprintf( psz, msg_text, sstr1, sstr2, sstr3, sstr4 ); + ConsolePrint( ConvertCRtoNL( psz ) ); + break; + } + + return 1; +} diff --git a/cl_dll/tf_defs.h b/cl_dll/tf_defs.h new file mode 100644 index 0000000..f178c55 --- /dev/null +++ b/cl_dll/tf_defs.h @@ -0,0 +1,1389 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#ifndef __TF_DEFS_H +#define __TF_DEFS_H + +//=========================================================================== +// OLD OPTIONS.QC +//=========================================================================== +#define DEFAULT_AUTOZOOM FALSE +#define WEINER_SNIPER // autoaiming for sniper rifle +#define FLAME_MAXWORLDNUM 20 // maximum number of flames in the world. DO NOT PUT BELOW 20. + +//#define MAX_WORLD_PIPEBOMBS 15 // This is divided between teams - this is the most you should have on a net server +#define MAX_PLAYER_PIPEBOMBS 8 // maximum number of pipebombs any 1 player can have active +#define MAX_PLAYER_AMMOBOXES 3 // maximum number of ammoboxes any 1 player can have active + +//#define MAX_WORLD_FLARES 9 // This is the total number of flares allowed in the world at one time +//#define MAX_WORLD_AMMOBOXES 20 // This is divided between teams - this is the most you should have on a net server +#define GR_TYPE_MIRV_NO 4 // Number of Mirvs a Mirv Grenade breaks into +#define GR_TYPE_NAPALM_NO 8 // Number of flames napalm grenade breaks into (unused if net server) +#define MEDIKIT_IS_BIOWEAPON // Medikit acts as a bioweapon against enemies + +#define TEAM_HELP_RATE 60 // used only if teamplay bit 64 (help team with lower score) is set. + // 60 is a mild setting, and won't make too much difference + // increasing it _decreases_ the amount of help the losing team gets + // Minimum setting is 1, which would really help the losing team + +#define DISPLAY_CLASS_HELP TRUE // Change this to #OFF if you don't want the class help to + // appear whenever a player connects +#define NEVER_TEAMFRAGS FALSE // teamfrags options always off +#define ALWAYS_TEAMFRAGS FALSE // teamfrags options always on +#define CHECK_SPEEDS TRUE // makes sure players aren't moving too fast +#define SNIPER_RIFLE_RELOAD_TIME 1.5 // seconds + +#define MAPBRIEFING_MAXTEXTLENGTH 512 +#define PLAYER_PUSH_VELOCITY 50 // Players push teammates if they're moving under this speed + +// Debug Options +//#define MAP_DEBUG // Debug for Map code. I suggest running in a hi-res + // mode and/or piping the output from the server to a file. +#ifdef MAP_DEBUG + #define MDEBUG(x) x +#else + #define MDEBUG(x) +#endif +//#define VERBOSE // Verbose Debugging on/off + +//=========================================================================== +// OLD QUAKE Defs +//=========================================================================== +// items +#define IT_AXE 4096 +#define IT_SHOTGUN 1 +#define IT_SUPER_SHOTGUN 2 +#define IT_NAILGUN 4 +#define IT_SUPER_NAILGUN 8 +#define IT_GRENADE_LAUNCHER 16 +#define IT_ROCKET_LAUNCHER 32 +#define IT_LIGHTNING 64 +#define IT_EXTRA_WEAPON 128 + +#define IT_SHELLS 256 +#define IT_NAILS 512 +#define IT_ROCKETS 1024 +#define IT_CELLS 2048 + +#define IT_ARMOR1 8192 +#define IT_ARMOR2 16384 +#define IT_ARMOR3 32768 +#define IT_SUPERHEALTH 65536 + +#define IT_KEY1 131072 +#define IT_KEY2 262144 + +#define IT_INVISIBILITY 524288 +#define IT_INVULNERABILITY 1048576 +#define IT_SUIT 2097152 +#define IT_QUAD 4194304 +#define IT_HOOK 8388608 + +#define IT_KEY3 16777216 // Stomp invisibility +#define IT_KEY4 33554432 // Stomp invulnerability + +//=========================================================================== +// TEAMFORTRESS Defs +//=========================================================================== +// TeamFortress State Flags +#define TFSTATE_GRENPRIMED 1 // Whether the player has a primed grenade +#define TFSTATE_RELOADING 2 // Whether the player is reloading +#define TFSTATE_ALTKILL 4 // #TRUE if killed with a weapon not in self.weapon: NOT USED ANYMORE +#define TFSTATE_RANDOMPC 8 // Whether Playerclass is random, new one each respawn +#define TFSTATE_INFECTED 16 // set when player is infected by the bioweapon +#define TFSTATE_INVINCIBLE 32 // Player has permanent Invincibility (Usually by GoalItem) +#define TFSTATE_INVISIBLE 64 // Player has permanent Invisibility (Usually by GoalItem) +#define TFSTATE_QUAD 128 // Player has permanent Quad Damage (Usually by GoalItem) +#define TFSTATE_RADSUIT 256 // Player has permanent Radsuit (Usually by GoalItem) +#define TFSTATE_BURNING 512 // Is on fire +#define TFSTATE_GRENTHROWING 1024 // is throwing a grenade +#define TFSTATE_AIMING 2048 // is using the laser sight +#define TFSTATE_ZOOMOFF 4096 // doesn't want the FOV changed when zooming +#define TFSTATE_RESPAWN_READY 8192 // is waiting for respawn, and has pressed fire +#define TFSTATE_HALLUCINATING 16384 // set when player is hallucinating +#define TFSTATE_TRANQUILISED 32768 // set when player is tranquilised +#define TFSTATE_CANT_MOVE 65536 // set when player is setting a detpack +#define TFSTATE_RESET_FLAMETIME 131072 // set when the player has to have his flames increased in health + +// Defines used by TF_T_Damage (see combat.qc) +#define TF_TD_IGNOREARMOUR 1 // Bypasses the armour of the target +#define TF_TD_NOTTEAM 2 // Doesn't damage a team member (indicates direct fire weapon) +#define TF_TD_NOTSELF 4 // Doesn't damage self + +#define TF_TD_OTHER 0 // Ignore armorclass +#define TF_TD_SHOT 1 // Bullet damage +#define TF_TD_NAIL 2 // Nail damage +#define TF_TD_EXPLOSION 4 // Explosion damage +#define TF_TD_ELECTRICITY 8 // Electric damage +#define TF_TD_FIRE 16 // Fire damage +#define TF_TD_NOSOUND 256 // Special damage. Makes no sound/painframe, etc + +/*==================================================*/ +/* Toggleable Game Settings */ +/*==================================================*/ +#define TF_RESPAWNDELAY1 5 // seconds of waiting before player can respawn +#define TF_RESPAWNDELAY2 10 // seconds of waiting before player can respawn +#define TF_RESPAWNDELAY3 20 // seconds of waiting before player can respawn + +#define TEAMPLAY_NORMAL 1 +#define TEAMPLAY_HALFDIRECT 2 +#define TEAMPLAY_NODIRECT 4 +#define TEAMPLAY_HALFEXPLOSIVE 8 +#define TEAMPLAY_NOEXPLOSIVE 16 +#define TEAMPLAY_LESSPLAYERSHELP 32 +#define TEAMPLAY_LESSSCOREHELP 64 +#define TEAMPLAY_HALFDIRECTARMOR 128 +#define TEAMPLAY_NODIRECTARMOR 256 +#define TEAMPLAY_HALFEXPARMOR 512 +#define TEAMPLAY_NOEXPARMOR 1024 +#define TEAMPLAY_HALFDIRMIRROR 2048 +#define TEAMPLAY_FULLDIRMIRROR 4096 +#define TEAMPLAY_HALFEXPMIRROR 8192 +#define TEAMPLAY_FULLEXPMIRROR 16384 + +#define TEAMPLAY_TEAMDAMAGE (TEAMPLAY_NODIRECT | TEAMPLAY_HALFDIRECT | TEAMPLAY_HALFEXPLOSIVE | TEAMPLAY_NOEXPLOSIVE) +// FortressMap stuff +#define TEAM1_CIVILIANS 1 +#define TEAM2_CIVILIANS 2 +#define TEAM3_CIVILIANS 4 +#define TEAM4_CIVILIANS 8 + +// Defines for the playerclass +#define PC_UNDEFINED 0 + +#define PC_SCOUT 1 +#define PC_SNIPER 2 +#define PC_SOLDIER 3 +#define PC_DEMOMAN 4 +#define PC_MEDIC 5 +#define PC_HVYWEAP 6 +#define PC_PYRO 7 +#define PC_SPY 8 +#define PC_ENGINEER 9 + +// Insert new class definitions here + +// PC_RANDOM _MUST_ be the third last class +#define PC_RANDOM 10 // Random playerclass +#define PC_CIVILIAN 11 // Civilians are a special class. They cannot + // be chosen by players, only enforced by maps +#define PC_LASTCLASS 12 // Use this as the high-boundary for any loops + // through the playerclass. + +#define SENTRY_COLOR 10 // will be in the PC_RANDOM slot for team colors + +// These are just for the scanner +#define SCAN_SENTRY 13 +#define SCAN_GOALITEM 14 + +// Values returned by CheckArea +enum +{ + CAREA_CLEAR, + CAREA_BLOCKED, + CAREA_NOBUILD +}; + +/*==================================================*/ +/* Impulse Defines */ +/*==================================================*/ +// Alias check to see whether they already have the aliases +#define TF_ALIAS_CHECK 13 + +// CTF Support Impulses +#define HOOK_IMP1 22 +#define FLAG_INFO 23 +#define HOOK_IMP2 39 + +// Axe +#define AXE_IMP 40 + +// Camera Impulse +#define TF_CAM_TARGET 50 +#define TF_CAM_ZOOM 51 +#define TF_CAM_ANGLE 52 +#define TF_CAM_VEC 53 +#define TF_CAM_PROJECTILE 54 +#define TF_CAM_PROJECTILE_Z 55 +#define TF_CAM_REVANGLE 56 +#define TF_CAM_OFFSET 57 +#define TF_CAM_DROP 58 +#define TF_CAM_FADETOBLACK 59 +#define TF_CAM_FADEFROMBLACK 60 +#define TF_CAM_FADETOWHITE 61 +#define TF_CAM_FADEFROMWHITE 62 + +// Last Weapon impulse +#define TF_LAST_WEAPON 69 + +// Status Bar Resolution Settings. Same as CTF to maintain ease of use. +#define TF_STATUSBAR_RES_START 71 +#define TF_STATUSBAR_RES_END 81 + +// Clan Messages +#define TF_MESSAGE_1 82 +#define TF_MESSAGE_2 83 +#define TF_MESSAGE_3 84 +#define TF_MESSAGE_4 85 +#define TF_MESSAGE_5 86 + +#define TF_CHANGE_CLASS 99 // Bring up the Class Change menu + +// Added to PC_??? to get impulse to use if this clashes with your +// own impulses, just change this value, not the PC_?? +#define TF_CHANGEPC 100 +// The next few impulses are all the class selections +//PC_SCOUT 101 +//PC_SNIPER 102 +//PC_SOLDIER 103 +//PC_DEMOMAN 104 +//PC_MEDIC 105 +//PC_HVYWEAP 106 +//PC_PYRO 107 +//PC_RANDOM 108 +//PC_CIVILIAN 109 // Cannot be used +//PC_SPY 110 +//PC_ENGINEER 111 + +// Help impulses +#define TF_DISPLAYLOCATION 118 +#define TF_STATUS_QUERY 119 + +#define TF_HELP_MAP 131 + +// Information impulses +#define TF_INVENTORY 135 +#define TF_SHOWTF 136 +#define TF_SHOWLEGALCLASSES 137 + +// Team Impulses +#define TF_TEAM_1 140 // Join Team 1 +#define TF_TEAM_2 141 // Join Team 2 +#define TF_TEAM_3 142 // Join Team 3 +#define TF_TEAM_4 143 // Join Team 4 +#define TF_TEAM_CLASSES 144 // Impulse to display team classes +#define TF_TEAM_SCORES 145 // Impulse to display team scores +#define TF_TEAM_LIST 146 // Impulse to display the players in each team. + +// Grenade Impulses +#define TF_GRENADE_1 150 // Prime grenade type 1 +#define TF_GRENADE_2 151 // Prime grenade type 2 +#define TF_GRENADE_T 152 // Throw primed grenade + +// Impulses for new items +//#define TF_SCAN 159 // Scanner Pre-Impulse +#define TF_AUTO_SCAN 159 // Scanner On/Off +#define TF_SCAN_ENEMY 160 // Impulses to toggle scanning of enemies +#define TF_SCAN_FRIENDLY 161 // Impulses to toggle scanning of friendlies +//#define TF_SCAN_10 162 // Scan using 10 enery (1 cell) +#define TF_SCAN_SOUND 162 // Scanner sounds on/off +#define TF_SCAN_30 163 // Scan using 30 energy (2 cells) +#define TF_SCAN_100 164 // Scan using 100 energy (5 cells) +#define TF_DETPACK_5 165 // Detpack set to 5 seconds +#define TF_DETPACK_20 166 // Detpack set to 20 seconds +#define TF_DETPACK_50 167 // Detpack set to 50 seconds +#define TF_DETPACK 168 // Detpack Pre-Impulse +#define TF_DETPACK_STOP 169 // Impulse to stop setting detpack +#define TF_PB_DETONATE 170 // Detonate Pipebombs + +// Special skill +#define TF_SPECIAL_SKILL 171 + +// Ammo Drop impulse +#define TF_DROP_AMMO 172 + +// Reload impulse +#define TF_RELOAD 173 + +// auto-zoom toggle +#define TF_AUTOZOOM 174 + +// drop/pass commands +#define TF_DROPKEY 175 + +// Select Medikit +#define TF_MEDIKIT 176 + +// Spy Impulses +#define TF_SPY_SPY 177 // On net, go invisible, on LAN, change skin/color +#define TF_SPY_DIE 178 // Feign Death + +// Engineer Impulses +#define TF_ENGINEER_BUILD 179 +#define TF_ENGINEER_SANDBAG 180 + +// Medic +#define TF_MEDIC_HELPME 181 + +// Status bar +#define TF_STATUSBAR_ON 182 +#define TF_STATUSBAR_OFF 183 + +// Discard impulse +#define TF_DISCARD 184 + +// ID Player impulse +#define TF_ID 185 + +// Clan Battle impulses +#define TF_SHOWIDS 186 + +// More Engineer Impulses +#define TF_ENGINEER_DETDISP 187 +#define TF_ENGINEER_DETSENT 188 + +// Admin Commands +#define TF_ADMIN_DEAL_CYCLE 189 +#define TF_ADMIN_KICK 190 +#define TF_ADMIN_BAN 191 +#define TF_ADMIN_COUNTPLAYERS 192 +#define TF_ADMIN_CEASEFIRE 193 + +// Drop Goal Items +#define TF_DROPGOALITEMS 194 + +// More Admin Commands +#define TF_ADMIN_NEXT 195 + +// More Engineer Impulses +#define TF_ENGINEER_DETEXIT 196 +#define TF_ENGINEER_DETENTRANCE 197 + +// Yet MORE Admin Commands +#define TF_ADMIN_LISTIPS 198 + +// Silent Spy Feign +#define TF_SPY_SILENTDIE 199 + + +/*==================================================*/ +/* Defines for the ENGINEER's Building ability */ +/*==================================================*/ +// Ammo costs +#define AMMO_COST_SHELLS 2 // Metal needed to make 1 shell +#define AMMO_COST_NAILS 1 +#define AMMO_COST_ROCKETS 2 +#define AMMO_COST_CELLS 2 + +// Building types +#define BUILD_DISPENSER 1 +#define BUILD_SENTRYGUN 2 +#define BUILD_MORTAR 3 +#define BUILD_TELEPORTER_ENTRANCE 4 +#define BUILD_TELEPORTER_EXIT 5 + +// Building metal costs +#define BUILD_COST_DISPENSER 100 // Metal needed to built +#define BUILD_COST_SENTRYGUN 130 +#define BUILD_COST_MORTAR 150 +#define BUILD_COST_TELEPORTER 125 + +#define BUILD_COST_SANDBAG 20 // Built with a separate alias + +// Building times +#define BUILD_TIME_DISPENSER 2 // seconds to build +#define BUILD_TIME_SENTRYGUN 5 +#define BUILD_TIME_MORTAR 5 +#define BUILD_TIME_TELEPORTER 4 + +// Building health levels +#define BUILD_HEALTH_DISPENSER 150 // Health of the building +#define BUILD_HEALTH_SENTRYGUN 150 +#define BUILD_HEALTH_MORTAR 200 +#define BUILD_HEALTH_TELEPORTER 80 + +// Dispenser's maximum carrying capability +#define BUILD_DISPENSER_MAX_SHELLS 400 +#define BUILD_DISPENSER_MAX_NAILS 600 +#define BUILD_DISPENSER_MAX_ROCKETS 300 +#define BUILD_DISPENSER_MAX_CELLS 400 +#define BUILD_DISPENSER_MAX_ARMOR 500 + +// Build state sent down to client +#define BS_BUILDING (1<<0) +#define BS_HAS_DISPENSER (1<<1) +#define BS_HAS_SENTRYGUN (1<<2) +#define BS_CANB_DISPENSER (1<<3) +#define BS_CANB_SENTRYGUN (1<<4) +/*==================================================*/ +/* Ammo quantities for dropping & dispenser use */ +/*==================================================*/ +#define DROP_SHELLS 20 +#define DROP_NAILS 20 +#define DROP_ROCKETS 10 +#define DROP_CELLS 10 +#define DROP_ARMOR 40 + +/*==================================================*/ +/* Team Defines */ +/*==================================================*/ +#define TM_MAX_NO 4 // Max number of teams. Simply changing this value isn't enough. + // A new global to hold new team colors is needed, and more flags + // in the spawnpoint spawnflags may need to be used. + // Basically, don't change this unless you know what you're doing :) + +/*==================================================*/ +/* New Weapon Defines */ +/*==================================================*/ +#define WEAP_HOOK 1 +#define WEAP_BIOWEAPON 2 +#define WEAP_MEDIKIT 4 +#define WEAP_SPANNER 8 +#define WEAP_AXE 16 +#define WEAP_SNIPER_RIFLE 32 +#define WEAP_AUTO_RIFLE 64 +#define WEAP_SHOTGUN 128 +#define WEAP_SUPER_SHOTGUN 256 +#define WEAP_NAILGUN 512 +#define WEAP_SUPER_NAILGUN 1024 +#define WEAP_GRENADE_LAUNCHER 2048 +#define WEAP_FLAMETHROWER 4096 +#define WEAP_ROCKET_LAUNCHER 8192 +#define WEAP_INCENDIARY 16384 +#define WEAP_ASSAULT_CANNON 32768 +#define WEAP_LIGHTNING 65536 +#define WEAP_DETPACK 131072 +#define WEAP_TRANQ 262144 +#define WEAP_LASER 524288 +// still room for 12 more weapons +// but we can remove some by giving the weapons +// a weapon mode (like the rifle) + +// HL-compatible weapon numbers +#define WEAPON_HOOK 1 +#define WEAPON_BIOWEAPON (WEAPON_HOOK+1) +#define WEAPON_MEDIKIT (WEAPON_HOOK+2) +#define WEAPON_SPANNER (WEAPON_HOOK+3) +#define WEAPON_AXE (WEAPON_HOOK+4) +#define WEAPON_SNIPER_RIFLE (WEAPON_HOOK+5) +#define WEAPON_AUTO_RIFLE (WEAPON_HOOK+6) +#define WEAPON_TF_SHOTGUN (WEAPON_HOOK+7) +#define WEAPON_SUPER_SHOTGUN (WEAPON_HOOK+8) +#define WEAPON_NAILGUN (WEAPON_HOOK+9) +#define WEAPON_SUPER_NAILGUN (WEAPON_HOOK+10) +#define WEAPON_GRENADE_LAUNCHER (WEAPON_HOOK+11) +#define WEAPON_FLAMETHROWER (WEAPON_HOOK+12) +#define WEAPON_ROCKET_LAUNCHER (WEAPON_HOOK+13) +#define WEAPON_INCENDIARY (WEAPON_HOOK+14) +#define WEAPON_ASSAULT_CANNON (WEAPON_HOOK+16) +#define WEAPON_LIGHTNING (WEAPON_HOOK+17) +#define WEAPON_DETPACK (WEAPON_HOOK+18) +#define WEAPON_TRANQ (WEAPON_HOOK+19) +#define WEAPON_LASER (WEAPON_HOOK+20) +#define WEAPON_PIPEBOMB_LAUNCHER (WEAPON_HOOK+21) +#define WEAPON_KNIFE (WEAPON_HOOK+22) +#define WEAPON_BENCHMARK (WEAPON_HOOK+23) + +/*==================================================*/ +/* New Weapon Related Defines */ +/*==================================================*/ +// shots per reload +#define RE_SHOTGUN 8 +#define RE_SUPER_SHOTGUN 16 // 8 shots +#define RE_GRENADE_LAUNCHER 6 +#define RE_ROCKET_LAUNCHER 4 + +// reload times +#define RE_SHOTGUN_TIME 2 +#define RE_SUPER_SHOTGUN_TIME 3 +#define RE_GRENADE_LAUNCHER_TIME 4 +#define RE_ROCKET_LAUNCHER_TIME 5 + +// Maximum velocity you can move and fire the Sniper Rifle +#define WEAP_SNIPER_RIFLE_MAX_MOVE 50 + +// Medikit +#define WEAP_MEDIKIT_HEAL 200 // Amount medikit heals per hit +#define WEAP_MEDIKIT_OVERHEAL 50 // Amount of superhealth over max_health the medikit will dispense + +// Spanner +#define WEAP_SPANNER_REPAIR 10 + +// Detpack +#define WEAP_DETPACK_DISARMTIME 3 // Time it takes to disarm a Detpack +#define WEAP_DETPACK_SETTIME 3 // Time it takes to set a Detpack +#define WEAP_DETPACK_SIZE 700 // Explosion Size +#define WEAP_DETPACK_GOAL_SIZE 1500 // Explosion Size for goal triggering +#define WEAP_DETPACK_BITS_NO 12 // Bits that detpack explodes into + +// Tranquiliser Gun +#define TRANQ_TIME 15 + +// Grenades +#define GR_PRIMETIME 3 +#define GR_CALTROP_PRIME 0.5 +#define GR_TYPE_NONE 0 +#define GR_TYPE_NORMAL 1 +#define GR_TYPE_CONCUSSION 2 +#define GR_TYPE_NAIL 3 +#define GR_TYPE_MIRV 4 +#define GR_TYPE_NAPALM 5 +//#define GR_TYPE_FLARE 6 +#define GR_TYPE_GAS 7 +#define GR_TYPE_EMP 8 +#define GR_TYPE_CALTROP 9 +//#define GR_TYPE_FLASH 10 + +// Defines for WeaponMode +#define GL_NORMAL 0 +#define GL_PIPEBOMB 1 + +// Defines for OLD Concussion Grenade +#define GR_OLD_CONCUSS_TIME 5 +#define GR_OLD_CONCUSS_DEC 20 + +// Defines for Concussion Grenade +#define GR_CONCUSS_TIME 0.25 +#define GR_CONCUSS_DEC 10 +#define MEDIUM_PING 150 +#define HIGH_PING 200 + +// Defines for the Gas Grenade +#define GR_HALLU_TIME 0.3 +#define GR_OLD_HALLU_TIME 0.5 +#define GR_HALLU_DEC 2.5 + +// Defines for the BioInfection +#define BIO_JUMP_RADIUS 128 // The distance the bioinfection can jump between players + +/*==================================================*/ +/* New Items */ +/*==================================================*/ +#define NIT_SCANNER 1 + +#define NIT_SILVER_DOOR_OPENED #IT_KEY1 // 131072 +#define NIT_GOLD_DOOR_OPENED #IT_KEY2 // 262144 + +/*==================================================*/ +/* New Item Flags */ +/*==================================================*/ +#define NIT_SCANNER_ENEMY 1 // Detect enemies +#define NIT_SCANNER_FRIENDLY 2 // Detect friendlies (team members) +#define NIT_SCANNER_SOUND 4 // Motion detection. Only report moving entities. + +/*==================================================*/ +/* New Item Related Defines */ +/*==================================================*/ +#define NIT_SCANNER_POWER 25 // The amount of power spent on a scan with the scanner + // is multiplied by this to get the scanrange. +#define NIT_SCANNER_MAXCELL 50 // The maximum number of cells than can be used in one scan +#define NIT_SCANNER_MIN_MOVEMENT 50 // The minimum velocity an entity must have to be detected + // by scanners that only detect movement + +/*==================================================*/ +/* Variables used for New Weapons and Reloading */ +/*==================================================*/ +// Armor Classes : Bitfields. Use the "armorclass" of armor for the Armor Type. +#define AT_SAVESHOT 1 // Kevlar : Reduces bullet damage by 15% +#define AT_SAVENAIL 2 // Wood :) : Reduces nail damage by 15% +#define AT_SAVEEXPLOSION 4 // Blast : Reduces explosion damage by 15% +#define AT_SAVEELECTRICITY 8 // Shock : Reduces electricity damage by 15% +#define AT_SAVEFIRE 16 // Asbestos : Reduces fire damage by 15% + +/*==========================================================================*/ +/* TEAMFORTRESS CLASS DETAILS */ +/*==========================================================================*/ +// Class Details for SCOUT +#define PC_SCOUT_SKIN 4 // Skin for this class when Classkin is on. +#define PC_SCOUT_MAXHEALTH 75 // Maximum Health Level +#define PC_SCOUT_MAXSPEED 400 // Maximum movement speed +#define PC_SCOUT_MAXSTRAFESPEED 400 // Maximum strafing movement speed +#define PC_SCOUT_MAXARMOR 50 // Maximum Armor Level, of any armor class +#define PC_SCOUT_INITARMOR 25 // Armor level when respawned +#define PC_SCOUT_MAXARMORTYPE 0.3 // Maximum level of Armor absorption +#define PC_SCOUT_INITARMORTYPE 0.3 // Absorption Level of armor when respawned +#define PC_SCOUT_ARMORCLASSES 3 // #AT_SAVESHOT | #AT_SAVENAIL <-Armor Classes allowed for this class +#define PC_SCOUT_INITARMORCLASS 0 // Armorclass worn when respawned +#define PC_SCOUT_WEAPONS WEAP_AXE | WEAP_SHOTGUN | WEAP_NAILGUN +#define PC_SCOUT_MAXAMMO_SHOT 50 // Maximum amount of shot ammo this class can carry +#define PC_SCOUT_MAXAMMO_NAIL 200 // Maximum amount of nail ammo this class can carry +#define PC_SCOUT_MAXAMMO_CELL 100 // Maximum amount of cell ammo this class can carry +#define PC_SCOUT_MAXAMMO_ROCKET 25 // Maximum amount of rocket ammo this class can carry +#define PC_SCOUT_INITAMMO_SHOT 25 // Amount of shot ammo this class has when respawned +#define PC_SCOUT_INITAMMO_NAIL 100 // Amount of nail ammo this class has when respawned +#define PC_SCOUT_INITAMMO_CELL 50 // Amount of cell ammo this class has when respawned +#define PC_SCOUT_INITAMMO_ROCKET 0 // Amount of rocket ammo this class has when respawned +#define PC_SCOUT_GRENADE_TYPE_1 GR_TYPE_CALTROP // <- 1st Type of Grenade this class has +#define PC_SCOUT_GRENADE_TYPE_2 GR_TYPE_CONCUSSION // <- 2nd Type of Grenade this class has +#define PC_SCOUT_GRENADE_INIT_1 2 // Number of grenades of Type 1 this class has when respawned +#define PC_SCOUT_GRENADE_INIT_2 3 // Number of grenades of Type 2 this class has when respawned +#define PC_SCOUT_TF_ITEMS NIT_SCANNER // <- TeamFortress Items this class has + +#define PC_SCOUT_MOTION_MIN_I 0.5 // < Short range +#define PC_SCOUT_MOTION_MIN_MOVE 50 // Minimum vlen of player velocity to be picked up by motion detector +#define PC_SCOUT_SCAN_TIME 2 // # of seconds between each scan pulse +#define PC_SCOUT_SCAN_RANGE 100 // Default scanner range +#define PC_SCOUT_SCAN_COST 2 // Default scanner cell useage per scan + +// Class Details for SNIPER +#define PC_SNIPER_SKIN 5 +#define PC_SNIPER_MAXHEALTH 90 +#define PC_SNIPER_MAXSPEED 300 +#define PC_SNIPER_MAXSTRAFESPEED 300 +#define PC_SNIPER_MAXARMOR 50 +#define PC_SNIPER_INITARMOR 0 +#define PC_SNIPER_MAXARMORTYPE 0.3 +#define PC_SNIPER_INITARMORTYPE 0.3 +#define PC_SNIPER_ARMORCLASSES 3 // #AT_SAVESHOT | #AT_SAVENAIL +#define PC_SNIPER_INITARMORCLASS 0 +#define PC_SNIPER_WEAPONS WEAP_SNIPER_RIFLE | WEAP_AUTO_RIFLE | WEAP_AXE | WEAP_NAILGUN +#define PC_SNIPER_MAXAMMO_SHOT 75 +#define PC_SNIPER_MAXAMMO_NAIL 100 +#define PC_SNIPER_MAXAMMO_CELL 50 +#define PC_SNIPER_MAXAMMO_ROCKET 25 +#define PC_SNIPER_INITAMMO_SHOT 60 +#define PC_SNIPER_INITAMMO_NAIL 50 +#define PC_SNIPER_INITAMMO_CELL 0 +#define PC_SNIPER_INITAMMO_ROCKET 0 +#define PC_SNIPER_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_SNIPER_GRENADE_TYPE_2 GR_TYPE_NONE +#define PC_SNIPER_GRENADE_INIT_1 2 +#define PC_SNIPER_GRENADE_INIT_2 0 +#define PC_SNIPER_TF_ITEMS 0 + +// Class Details for SOLDIER +#define PC_SOLDIER_SKIN 6 +#define PC_SOLDIER_MAXHEALTH 100 +#define PC_SOLDIER_MAXSPEED 240 +#define PC_SOLDIER_MAXSTRAFESPEED 240 +#define PC_SOLDIER_MAXARMOR 200 +#define PC_SOLDIER_INITARMOR 100 +#define PC_SOLDIER_MAXARMORTYPE 0.8 +#define PC_SOLDIER_INITARMORTYPE 0.8 +#define PC_SOLDIER_ARMORCLASSES 31 // ALL +#define PC_SOLDIER_INITARMORCLASS 0 +#define PC_SOLDIER_WEAPONS WEAP_AXE | WEAP_SHOTGUN | WEAP_SUPER_SHOTGUN | WEAP_ROCKET_LAUNCHER +#define PC_SOLDIER_MAXAMMO_SHOT 100 +#define PC_SOLDIER_MAXAMMO_NAIL 100 +#define PC_SOLDIER_MAXAMMO_CELL 50 +#define PC_SOLDIER_MAXAMMO_ROCKET 50 +#define PC_SOLDIER_INITAMMO_SHOT 50 +#define PC_SOLDIER_INITAMMO_NAIL 0 +#define PC_SOLDIER_INITAMMO_CELL 0 +#define PC_SOLDIER_INITAMMO_ROCKET 10 +#define PC_SOLDIER_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_SOLDIER_GRENADE_TYPE_2 GR_TYPE_NAIL +#define PC_SOLDIER_GRENADE_INIT_1 2 +#define PC_SOLDIER_GRENADE_INIT_2 1 +#define PC_SOLDIER_TF_ITEMS 0 + +#define MAX_NAIL_GRENS 2 // Can only have 2 Nail grens active +#define MAX_NAPALM_GRENS 2 // Can only have 2 Napalm grens active +#define MAX_GAS_GRENS 2 // Can only have 2 Gas grenades active +#define MAX_MIRV_GRENS 2 // Can only have 2 Mirv's +#define MAX_CONCUSSION_GRENS 3 +#define MAX_CALTROP_CANS 3 + +// Class Details for DEMOLITION MAN +#define PC_DEMOMAN_SKIN 1 +#define PC_DEMOMAN_MAXHEALTH 90 +#define PC_DEMOMAN_MAXSPEED 280 +#define PC_DEMOMAN_MAXSTRAFESPEED 280 +#define PC_DEMOMAN_MAXARMOR 120 +#define PC_DEMOMAN_INITARMOR 50 +#define PC_DEMOMAN_MAXARMORTYPE 0.6 +#define PC_DEMOMAN_INITARMORTYPE 0.6 +#define PC_DEMOMAN_ARMORCLASSES 31 // ALL +#define PC_DEMOMAN_INITARMORCLASS 0 +#define PC_DEMOMAN_WEAPONS WEAP_AXE | WEAP_SHOTGUN | WEAP_GRENADE_LAUNCHER | WEAP_DETPACK +#define PC_DEMOMAN_MAXAMMO_SHOT 75 +#define PC_DEMOMAN_MAXAMMO_NAIL 50 +#define PC_DEMOMAN_MAXAMMO_CELL 50 +#define PC_DEMOMAN_MAXAMMO_ROCKET 50 +#define PC_DEMOMAN_MAXAMMO_DETPACK 1 +#define PC_DEMOMAN_INITAMMO_SHOT 30 +#define PC_DEMOMAN_INITAMMO_NAIL 0 +#define PC_DEMOMAN_INITAMMO_CELL 0 +#define PC_DEMOMAN_INITAMMO_ROCKET 20 +#define PC_DEMOMAN_INITAMMO_DETPACK 1 +#define PC_DEMOMAN_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_DEMOMAN_GRENADE_TYPE_2 GR_TYPE_MIRV +#define PC_DEMOMAN_GRENADE_INIT_1 2 +#define PC_DEMOMAN_GRENADE_INIT_2 2 +#define PC_DEMOMAN_TF_ITEMS 0 + +// Class Details for COMBAT MEDIC +#define PC_MEDIC_SKIN 3 +#define PC_MEDIC_MAXHEALTH 90 +#define PC_MEDIC_MAXSPEED 320 +#define PC_MEDIC_MAXSTRAFESPEED 320 +#define PC_MEDIC_MAXARMOR 100 +#define PC_MEDIC_INITARMOR 50 +#define PC_MEDIC_MAXARMORTYPE 0.6 +#define PC_MEDIC_INITARMORTYPE 0.3 +#define PC_MEDIC_ARMORCLASSES 11 // ALL except EXPLOSION +#define PC_MEDIC_INITARMORCLASS 0 +#define PC_MEDIC_WEAPONS WEAP_BIOWEAPON | WEAP_MEDIKIT | WEAP_SHOTGUN | WEAP_SUPER_SHOTGUN | WEAP_SUPER_NAILGUN +#define PC_MEDIC_MAXAMMO_SHOT 75 +#define PC_MEDIC_MAXAMMO_NAIL 150 +#define PC_MEDIC_MAXAMMO_CELL 50 +#define PC_MEDIC_MAXAMMO_ROCKET 25 +#define PC_MEDIC_MAXAMMO_MEDIKIT 100 +#define PC_MEDIC_INITAMMO_SHOT 50 +#define PC_MEDIC_INITAMMO_NAIL 50 +#define PC_MEDIC_INITAMMO_CELL 0 +#define PC_MEDIC_INITAMMO_ROCKET 0 +#define PC_MEDIC_INITAMMO_MEDIKIT 50 +#define PC_MEDIC_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_MEDIC_GRENADE_TYPE_2 GR_TYPE_CONCUSSION +#define PC_MEDIC_GRENADE_INIT_1 2 +#define PC_MEDIC_GRENADE_INIT_2 2 +#define PC_MEDIC_TF_ITEMS 0 +#define PC_MEDIC_REGEN_TIME 3 // Number of seconds between each regen. +#define PC_MEDIC_REGEN_AMOUNT 2 // Amount of health regenerated each regen. + +// Class Details for HVYWEAP +#define PC_HVYWEAP_SKIN 2 +#define PC_HVYWEAP_MAXHEALTH 100 +#define PC_HVYWEAP_MAXSPEED 230 +#define PC_HVYWEAP_MAXSTRAFESPEED 230 +#define PC_HVYWEAP_MAXARMOR 300 +#define PC_HVYWEAP_INITARMOR 150 +#define PC_HVYWEAP_MAXARMORTYPE 0.8 +#define PC_HVYWEAP_INITARMORTYPE 0.8 +#define PC_HVYWEAP_ARMORCLASSES 31 // ALL +#define PC_HVYWEAP_INITARMORCLASS 0 +#define PC_HVYWEAP_WEAPONS WEAP_ASSAULT_CANNON | WEAP_AXE | WEAP_SHOTGUN | WEAP_SUPER_SHOTGUN +#define PC_HVYWEAP_MAXAMMO_SHOT 200 +#define PC_HVYWEAP_MAXAMMO_NAIL 200 +#define PC_HVYWEAP_MAXAMMO_CELL 50 +#define PC_HVYWEAP_MAXAMMO_ROCKET 25 +#define PC_HVYWEAP_INITAMMO_SHOT 200 +#define PC_HVYWEAP_INITAMMO_NAIL 0 +#define PC_HVYWEAP_INITAMMO_CELL 30 +#define PC_HVYWEAP_INITAMMO_ROCKET 0 +#define PC_HVYWEAP_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_HVYWEAP_GRENADE_TYPE_2 GR_TYPE_MIRV +#define PC_HVYWEAP_GRENADE_INIT_1 2 +#define PC_HVYWEAP_GRENADE_INIT_2 1 +#define PC_HVYWEAP_TF_ITEMS 0 +#define PC_HVYWEAP_CELL_USAGE 7 // Amount of cells spent to power up assault cannon + + + +// Class Details for PYRO +#define PC_PYRO_SKIN 21 +#define PC_PYRO_MAXHEALTH 100 +#define PC_PYRO_MAXSPEED 300 +#define PC_PYRO_MAXSTRAFESPEED 300 +#define PC_PYRO_MAXARMOR 150 +#define PC_PYRO_INITARMOR 50 +#define PC_PYRO_MAXARMORTYPE 0.6 +#define PC_PYRO_INITARMORTYPE 0.6 +#define PC_PYRO_ARMORCLASSES 27 // ALL except EXPLOSION +#define PC_PYRO_INITARMORCLASS 16 // #AT_SAVEFIRE +#define PC_PYRO_WEAPONS WEAP_INCENDIARY | WEAP_FLAMETHROWER | WEAP_AXE | WEAP_SHOTGUN +#define PC_PYRO_MAXAMMO_SHOT 40 +#define PC_PYRO_MAXAMMO_NAIL 50 +#define PC_PYRO_MAXAMMO_CELL 200 +#define PC_PYRO_MAXAMMO_ROCKET 20 +#define PC_PYRO_INITAMMO_SHOT 20 +#define PC_PYRO_INITAMMO_NAIL 0 +#define PC_PYRO_INITAMMO_CELL 120 +#define PC_PYRO_INITAMMO_ROCKET 5 +#define PC_PYRO_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_PYRO_GRENADE_TYPE_2 GR_TYPE_NAPALM +#define PC_PYRO_GRENADE_INIT_1 2 +#define PC_PYRO_GRENADE_INIT_2 4 +#define PC_PYRO_TF_ITEMS 0 +#define PC_PYRO_ROCKET_USAGE 3 // Number of rockets per incendiary cannon shot + +// Class Details for SPY +#define PC_SPY_SKIN 22 +#define PC_SPY_MAXHEALTH 90 +#define PC_SPY_MAXSPEED 300 +#define PC_SPY_MAXSTRAFESPEED 300 +#define PC_SPY_MAXARMOR 100 +#define PC_SPY_INITARMOR 25 +#define PC_SPY_MAXARMORTYPE 0.6 // Was 0.3 +#define PC_SPY_INITARMORTYPE 0.6 // Was 0.3 +#define PC_SPY_ARMORCLASSES 27 // ALL except EXPLOSION +#define PC_SPY_INITARMORCLASS 0 +#define PC_SPY_WEAPONS WEAP_AXE | WEAP_TRANQ | WEAP_SUPER_SHOTGUN | WEAP_NAILGUN +#define PC_SPY_MAXAMMO_SHOT 40 +#define PC_SPY_MAXAMMO_NAIL 100 +#define PC_SPY_MAXAMMO_CELL 30 +#define PC_SPY_MAXAMMO_ROCKET 15 +#define PC_SPY_INITAMMO_SHOT 40 +#define PC_SPY_INITAMMO_NAIL 50 +#define PC_SPY_INITAMMO_CELL 10 +#define PC_SPY_INITAMMO_ROCKET 0 +#define PC_SPY_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_SPY_GRENADE_TYPE_2 GR_TYPE_GAS +#define PC_SPY_GRENADE_INIT_1 2 +#define PC_SPY_GRENADE_INIT_2 2 +#define PC_SPY_TF_ITEMS 0 +#define PC_SPY_CELL_REGEN_TIME 5 +#define PC_SPY_CELL_REGEN_AMOUNT 1 +#define PC_SPY_CELL_USAGE 3 // Amount of cells spent while invisible +#define PC_SPY_GO_UNDERCOVER_TIME 4 // Time it takes to go undercover + +// Class Details for ENGINEER +#define PC_ENGINEER_SKIN 22 // Not used anymore +#define PC_ENGINEER_MAXHEALTH 80 +#define PC_ENGINEER_MAXSPEED 300 +#define PC_ENGINEER_MAXSTRAFESPEED 300 +#define PC_ENGINEER_MAXARMOR 50 +#define PC_ENGINEER_INITARMOR 25 +#define PC_ENGINEER_MAXARMORTYPE 0.6 +#define PC_ENGINEER_INITARMORTYPE 0.3 +#define PC_ENGINEER_ARMORCLASSES 31 // ALL +#define PC_ENGINEER_INITARMORCLASS 0 +#define PC_ENGINEER_WEAPONS WEAP_SPANNER | WEAP_LASER | WEAP_SUPER_SHOTGUN +#define PC_ENGINEER_MAXAMMO_SHOT 50 +#define PC_ENGINEER_MAXAMMO_NAIL 50 +#define PC_ENGINEER_MAXAMMO_CELL 200 // synonymous with metal +#define PC_ENGINEER_MAXAMMO_ROCKET 30 +#define PC_ENGINEER_INITAMMO_SHOT 20 +#define PC_ENGINEER_INITAMMO_NAIL 25 +#define PC_ENGINEER_INITAMMO_CELL 100 // synonymous with metal +#define PC_ENGINEER_INITAMMO_ROCKET 0 +#define PC_ENGINEER_GRENADE_TYPE_1 GR_TYPE_NORMAL +#define PC_ENGINEER_GRENADE_TYPE_2 GR_TYPE_EMP +#define PC_ENGINEER_GRENADE_INIT_1 2 +#define PC_ENGINEER_GRENADE_INIT_2 2 +#define PC_ENGINEER_TF_ITEMS 0 + +// Class Details for CIVILIAN +#define PC_CIVILIAN_SKIN 22 +#define PC_CIVILIAN_MAXHEALTH 50 +#define PC_CIVILIAN_MAXSPEED 240 +#define PC_CIVILIAN_MAXSTRAFESPEED 240 +#define PC_CIVILIAN_MAXARMOR 0 +#define PC_CIVILIAN_INITARMOR 0 +#define PC_CIVILIAN_MAXARMORTYPE 0 +#define PC_CIVILIAN_INITARMORTYPE 0 +#define PC_CIVILIAN_ARMORCLASSES 0 +#define PC_CIVILIAN_INITARMORCLASS 0 +#define PC_CIVILIAN_WEAPONS WEAP_AXE +#define PC_CIVILIAN_MAXAMMO_SHOT 0 +#define PC_CIVILIAN_MAXAMMO_NAIL 0 +#define PC_CIVILIAN_MAXAMMO_CELL 0 +#define PC_CIVILIAN_MAXAMMO_ROCKET 0 +#define PC_CIVILIAN_INITAMMO_SHOT 0 +#define PC_CIVILIAN_INITAMMO_NAIL 0 +#define PC_CIVILIAN_INITAMMO_CELL 0 +#define PC_CIVILIAN_INITAMMO_ROCKET 0 +#define PC_CIVILIAN_GRENADE_TYPE_1 0 +#define PC_CIVILIAN_GRENADE_TYPE_2 0 +#define PC_CIVILIAN_GRENADE_INIT_1 0 +#define PC_CIVILIAN_GRENADE_INIT_2 0 +#define PC_CIVILIAN_TF_ITEMS 0 + + +/*==========================================================================*/ +/* TEAMFORTRESS GOALS */ +/*==========================================================================*/ +// For all these defines, see the tfortmap.txt that came with the zip +// for complete descriptions. +// Defines for Goal Activation types : goal_activation (in goals) +#define TFGA_TOUCH 1 // Activated when touched +#define TFGA_TOUCH_DETPACK 2 // Activated when touched by a detpack explosion +#define TFGA_REVERSE_AP 4 // Activated when AP details are _not_ met +#define TFGA_SPANNER 8 // Activated when hit by an engineer's spanner +#define TFGA_DROPTOGROUND 2048 // Drop to Ground when spawning + +// Defines for Goal Effects types : goal_effect +#define TFGE_AP 1 // AP is affected. Default. +#define TFGE_AP_TEAM 2 // All of the AP's team. +#define TFGE_NOT_AP_TEAM 4 // All except AP's team. +#define TFGE_NOT_AP 8 // All except AP. +#define TFGE_WALL 16 // If set, walls stop the Radius effects +#define TFGE_SAME_ENVIRONMENT 32 // If set, players in a different environment to the Goal are not affected +#define TFGE_TIMER_CHECK_AP 64 // If set, Timer Goals check their critera for all players fitting their effects + +// Defines for Goal Result types : goal_result +#define TFGR_SINGLE 1 // Goal can only be activated once +#define TFGR_ADD_BONUSES 2 // Any Goals activated by this one give their bonuses +#define TFGR_ENDGAME 4 // Goal fires Intermission, displays scores, and ends level +#define TFGR_NO_ITEM_RESULTS 8 // GoalItems given by this Goal don't do results +#define TFGR_REMOVE_DISGUISE 16 // Prevent/Remove undercover from any Spy +#define TFGR_FORCE_RESPAWN 32 // Forces the player to teleport to a respawn point +#define TFGR_DESTROY_BUILDINGS 64 // Destroys this player's buildings, if anys + +// Defines for Goal Group Result types : goal_group +// None! +// But I'm leaving this variable in there, since it's fairly likely +// that some will show up sometime. + +// Defines for Goal Item types, : goal_activation (in items) +#define TFGI_GLOW 1 // Players carrying this GoalItem will glow +#define TFGI_SLOW 2 // Players carrying this GoalItem will move at half-speed +#define TFGI_DROP 4 // Players dying with this item will drop it +#define TFGI_RETURN_DROP 8 // Return if a player with it dies +#define TFGI_RETURN_GOAL 16 // Return if a player with it has it removed by a goal's activation +#define TFGI_RETURN_REMOVE 32 // Return if it is removed by TFGI_REMOVE +#define TFGI_REVERSE_AP 64 // Only pickup if the player _doesn't_ match AP Details +#define TFGI_REMOVE 128 // Remove if left untouched for 2 minutes after being dropped +#define TFGI_KEEP 256 // Players keep this item even when they die +#define TFGI_ITEMGLOWS 512 // Item glows when on the ground +#define TFGI_DONTREMOVERES 1024 // Don't remove results when the item is removed +#define TFGI_DROPTOGROUND 2048 // Drop To Ground when spawning +#define TFGI_CANBEDROPPED 4096 // Can be voluntarily dropped by players +#define TFGI_SOLID 8192 // Is solid... blocks bullets, etc + +// Defines for methods of GoalItem returning +#define GI_RET_DROP_DEAD 0 // Dropped by a dead player +#define GI_RET_DROP_LIVING 1 // Dropped by a living player +#define GI_RET_GOAL 2 // Returned by a Goal +#define GI_RET_TIME 3 // Returned due to timeout + +// Defines for TeamSpawnpoints : goal_activation (in teamspawns) +#define TFSP_MULTIPLEITEMS 1 // Give out the GoalItem multiple times +#define TFSP_MULTIPLEMSGS 2 // Display the message multiple times + +// Defines for TeamSpawnpoints : goal_effects (in teamspawns) +#define TFSP_REMOVESELF 1 // Remove itself after being spawned on + +// Defines for Goal States +#define TFGS_ACTIVE 1 +#define TFGS_INACTIVE 2 +#define TFGS_REMOVED 3 +#define TFGS_DELAYED 4 + +// Defines for GoalItem Removing from Player Methods +#define GI_DROP_PLAYERDEATH 0 // Dropped by a dying player +#define GI_DROP_REMOVEGOAL 1 // Removed by a Goal +#define GI_DROP_PLAYERDROP 2 // Dropped by a player + +// Legal Playerclass Handling +#define TF_ILL_SCOUT 1 +#define TF_ILL_SNIPER 2 +#define TF_ILL_SOLDIER 4 +#define TF_ILL_DEMOMAN 8 +#define TF_ILL_MEDIC 16 +#define TF_ILL_HVYWEP 32 +#define TF_ILL_PYRO 64 +#define TF_ILL_RANDOMPC 128 +#define TF_ILL_SPY 256 +#define TF_ILL_ENGINEER 512 + +// Addition classes +#define CLASS_TFGOAL 128 +#define CLASS_TFGOAL_TIMER 129 +#define CLASS_TFGOAL_ITEM 130 +#define CLASS_TFSPAWN 131 + +/*==========================================================================*/ +/* Flamethrower */ +/*==========================================================================*/ +#define FLAME_PLYRMAXTIME 5.0 // lifetime in seconds of a flame on a player +#define FLAME_MAXBURNTIME 8 // lifetime in seconds of a flame on the world (big ones) +#define NAPALM_MAXBURNTIME 20 // lifetime in seconds of flame from a napalm grenade +#define FLAME_MAXPLYRFLAMES 4 // maximum number of flames on a player +#define FLAME_NUMLIGHTS 1 // maximum number of light flame +#define FLAME_BURNRATIO 0.3 // the chance of a flame not 'sticking' +#define GR_TYPE_FLAMES_NO 15 // number of flames spawned when a grenade explode +#define FLAME_DAMAGE_TIME 1 // Interval between damage burns from flames +#define FLAME_EFFECT_TIME 0.2 // frequency at which we display flame effects. +#define FLAME_THINK_TIME 0.1 // Seconds between times the flame checks burn +#define PER_FLAME_DAMAGE 2 // Damage taken per second per flame by burning players + +/*==================================================*/ +/* CTF Support defines */ +/*==================================================*/ +#define CTF_FLAG1 1 +#define CTF_FLAG2 2 +#define CTF_DROPOFF1 3 +#define CTF_DROPOFF2 4 +#define CTF_SCORE1 5 +#define CTF_SCORE2 6 + +//.float hook_out; + +/*==================================================*/ +/* Camera defines */ +/*==================================================*/ +/* +float live_camera; +.float camdist; +.vector camangle; +.entity camera_list; +*/ + +/*==================================================*/ +/* QuakeWorld defines */ +/*==================================================*/ +/* +float already_chosen_map; + +// grappling hook variables +.entity hook; +.float on_hook; +.float fire_held_down;// flag - TRUE if player is still holding down the + // fire button after throwing a hook. +*/ +/*==================================================*/ +/* Server Settings */ +/*==================================================*/ +// Admin modes +#define ADMIN_MODE_NONE 0 +#define ADMIN_MODE_DEAL 1 + +/*==================================================*/ +/* Death Message defines */ +/*==================================================*/ +#define DMSG_SHOTGUN 1 +#define DMSG_SSHOTGUN 2 +#define DMSG_NAILGUN 3 +#define DMSG_SNAILGUN 4 +#define DMSG_GRENADEL 5 +#define DMSG_ROCKETL 6 +#define DMSG_LIGHTNING 7 +#define DMSG_GREN_HAND 8 +#define DMSG_GREN_NAIL 9 +#define DMSG_GREN_MIRV 10 +#define DMSG_GREN_PIPE 11 +#define DMSG_DETPACK 12 +#define DMSG_BIOWEAPON 13 +#define DMSG_BIOWEAPON_ATT 14 +#define DMSG_FLAME 15 +#define DMSG_DETPACK_DIS 16 +#define DMSG_AXE 17 +#define DMSG_SNIPERRIFLE 18 +#define DMSG_AUTORIFLE 19 +#define DMSG_ASSAULTCANNON 20 +#define DMSG_HOOK 21 +#define DMSG_BACKSTAB 22 +#define DMSG_MEDIKIT 23 +#define DMSG_GREN_GAS 24 +#define DMSG_TRANQ 25 +#define DMSG_LASERBOLT 26 +#define DMSG_SENTRYGUN_BULLET 27 +#define DMSG_SNIPERLEGSHOT 28 +#define DMSG_SNIPERHEADSHOT 29 +#define DMSG_GREN_EMP 30 +#define DMSG_GREN_EMP_AMMO 31 +#define DMSG_SPANNER 32 +#define DMSG_INCENDIARY 33 +#define DMSG_SENTRYGUN_ROCKET 34 +#define DMSG_GREN_FLASH 35 +#define DMSG_TRIGGER 36 +#define DMSG_MIRROR 37 +#define DMSG_SENTRYDEATH 38 +#define DMSG_DISPENSERDEATH 39 +#define DMSG_GREN_AIRPIPE 40 +#define DMSG_CALTROP 41 + +/*==================================================*/ +// TOGGLEFLAGS +/*==================================================*/ +// Some of the toggleflags aren't used anymore, but the bits are still +// there to provide compatability with old maps +#define TFLAG_CLASS_PERSIST (1 << 0) // Persistent Classes Bit +#define TFLAG_CHEATCHECK (1 << 1) // Cheatchecking Bit +#define TFLAG_RESPAWNDELAY (1 << 2) // RespawnDelay bit +//#define TFLAG_UN (1 << 3) // NOT USED ANYMORE +#define TFLAG_OLD_GRENS (1 << 3) // Use old concussion grenade and flash grenade +#define TFLAG_UN2 (1 << 4) // NOT USED ANYMORE +#define TFLAG_UN3 (1 << 5) // NOT USED ANYMORE +#define TFLAG_UN4 (1 << 6) // NOT USED ANYMORE: Was Autoteam. CVAR tfc_autoteam used now. +#define TFLAG_TEAMFRAGS (1 << 7) // Individual Frags, or Frags = TeamScore +#define TFLAG_FIRSTENTRY (1 << 8) // Used to determine the first time toggleflags is set + // In a map. Cannot be toggled by players. +#define TFLAG_SPYINVIS (1 << 9) // Spy invisible only +#define TFLAG_GRAPPLE (1 << 10) // Grapple on/off +//#define TFLAG_FULLTEAMSCORE (1 << 11) // Each Team's score is TeamScore + Frags +#define TFLAG_FLAGEMULATION (1 << 12) // Flag emulation on for old TF maps +#define TFLAG_USE_STANDARD (1 << 13) // Use the TF War standard for Flag emulation + +#define TFLAG_FRAGSCORING (1 << 14) // Use frag scoring only + +/*======================*/ +// Menu stuff // +/*======================*/ + +#define MENU_DEFAULT 1 +#define MENU_TEAM 2 +#define MENU_CLASS 3 +#define MENU_MAPBRIEFING 4 +#define MENU_INTRO 5 +#define MENU_CLASSHELP 6 +#define MENU_CLASSHELP2 7 +#define MENU_REPEATHELP 8 + +#define MENU_SPECHELP 9 + + +#define MENU_SPY 12 +#define MENU_SPY_SKIN 13 +#define MENU_SPY_COLOR 14 +#define MENU_ENGINEER 15 +#define MENU_ENGINEER_FIX_DISPENSER 16 +#define MENU_ENGINEER_FIX_SENTRYGUN 17 +#define MENU_ENGINEER_FIX_MORTAR 18 +#define MENU_DISPENSER 19 +#define MENU_CLASS_CHANGE 20 +#define MENU_TEAM_CHANGE 21 + +#define MENU_REFRESH_RATE 25 + +#define MENU_VOICETWEAK 50 + +//============================ +// Timer Types +#define TF_TIMER_ANY 0 +#define TF_TIMER_CONCUSSION 1 +#define TF_TIMER_INFECTION 2 +#define TF_TIMER_HALLUCINATION 3 +#define TF_TIMER_TRANQUILISATION 4 +#define TF_TIMER_ROTHEALTH 5 +#define TF_TIMER_REGENERATION 6 +#define TF_TIMER_GRENPRIME 7 +#define TF_TIMER_CELLREGENERATION 8 +#define TF_TIMER_DETPACKSET 9 +#define TF_TIMER_DETPACKDISARM 10 +#define TF_TIMER_BUILD 11 +#define TF_TIMER_CHECKBUILDDISTANCE 12 +#define TF_TIMER_DISGUISE 13 +#define TF_TIMER_DISPENSERREFILL 14 + +// Non Player timers +#define TF_TIMER_RETURNITEM 100 +#define TF_TIMER_DELAYEDGOAL 101 +#define TF_TIMER_ENDROUND 102 + +//============================ +// Teamscore printing +#define TS_PRINT_SHORT 1 +#define TS_PRINT_LONG 2 +#define TS_PRINT_LONG_TO_ALL 3 + +#ifndef TF_DEFS_ONLY + +typedef struct +{ + int topColor; + int bottomColor; +} team_color_t; + + +/*==================================================*/ +/* GLOBAL VARIABLES */ +/*==================================================*/ +// FortressMap stuff +extern float number_of_teams; // number of teams supported by the map +extern int illegalclasses[5]; // Illegal playerclasses for all teams +extern int civilianteams; // Bitfield holding Civilian teams +extern Vector rgbcolors[5]; // RGB colors for each of the 4 teams + +extern team_color_t teamcolors[5][PC_LASTCLASS]; // Colors for each of the 4 teams + +extern int teamscores[5]; // Goal Score of each team +extern int g_iOrderedTeams[5]; // Teams ordered into order of winners->losers +extern int teamfrags[5]; // Total Frags for each team +extern int teamlives[5]; // Number of lives each team's players have +extern int teammaxplayers[5]; // Max number of players allowed in each team +extern float teamadvantage[5]; // only used if the teamplay equalisation bits are set + // stores the damage ratio players take/give +extern int teamallies[5]; // Keeps track of which teams are allied +extern string_t team_names[5]; + +extern BOOL CTF_Map; +extern BOOL birthday; +extern BOOL christmas; + +extern float num_world_flames; + +// Clan Battle stuff +extern float clan_scores_dumped; +extern float cb_prematch_time; +extern float fOldPrematch; +extern float fOldCeaseFire; +extern float cb_ceasefire_time; +extern float last_id; +extern float spy_off; +extern float old_grens; +extern float flagem_checked; +extern float flNextEqualisationCalc; +extern BOOL cease_fire; +extern BOOL no_cease_fire_text; +extern BOOL initial_cease_fire; +extern BOOL last_cease_fire; +// Autokick stuff +extern float autokick_kills; + +extern float deathmsg; // Global, which is set before every T_Damage, to indicate + // the death message that should be used. + +extern char *sTeamSpawnNames[]; +extern char *sClassNames[]; +extern char *sNewClassModelFiles[]; +extern char *sOldClassModelFiles[]; +extern char *sClassModels[]; +extern char *sClassCfgs[]; +extern char *sGrenadeNames[]; +extern string_t team_menu_string; + +extern int toggleflags; // toggleable flags + +extern CBaseEntity* g_pLastSpawns[5]; +extern BOOL g_bFirstClient; + +extern float g_fNextPrematchAlert; + +typedef struct +{ + int ip; + edict_t *pEdict; +} ip_storage_t; + +extern ip_storage_t g_IpStorage[32]; + +class CGhost; +/*==========================================================================*/ +BOOL ClassIsRestricted(float tno, int pc); +char* GetTeamName(int tno); +int TeamFortress_GetNoPlayers(); +void DestroyBuilding(CBaseEntity *eng, char *bld); +void teamsprint( int tno, CBaseEntity *ignore, int msg_dest, const char *st, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL ); +float anglemod( float v ); + +// Team Funcs +BOOL TeamFortress_TeamIsCivilian(float tno); +void TeamFortress_TeamShowScores(BOOL bLong, CBasePlayer *pPlayer); +BOOL TeamFortress_TeamPutPlayerInTeam(); +void TeamFortress_TeamSetColor(int tno); +void TeamFortress_TeamIncreaseScore(int tno, int scoretoadd); +int TeamFortress_TeamGetScoreFrags(int tno); +int TeamFortress_TeamGetNoPlayers(int tno); +float TeamEqualiseDamage(CBaseEntity *targ, CBaseEntity *attacker, float damage); +BOOL IsSpawnPointValid( Vector &pos ); +BOOL TeamFortress_SortTeams( void ); +void DumpClanScores( void ); +void CalculateTeamEqualiser(); + +// mapscript funcs +void ParseTFServerSettings(); +void ParseTFMapSettings(); +CBaseEntity* Finditem(int ino); +CBaseEntity* Findgoal(int gno); +CBaseEntity* Findteamspawn(int gno); +void RemoveGoal(CBaseEntity *Goal); +void tfgoalitem_GiveToPlayer(CBaseEntity *Item, CBasePlayer *AP, CBaseEntity *Goal); +void dremove( CBaseEntity *te ); +void tfgoalitem_RemoveFromPlayer(CBaseEntity *Item, CBasePlayer *AP, int iMethod); +void tfgoalitem_drop(CBaseEntity *Item, BOOL PAlive, CBasePlayer *P); +void DisplayItemStatus(CBaseEntity *Goal, CBasePlayer *Player, CBaseEntity *Item); +void tfgoalitem_checkgoalreturn(CBaseEntity *Item); +void DoGoalWork(CBaseEntity *Goal, CBasePlayer *AP); +void DoResults(CBaseEntity *Goal, CBasePlayer *AP, BOOL bAddBonuses); +void DoGroupWork(CBaseEntity *Goal, CBasePlayer *AP); +// hooks into the mapscript for all entities +BOOL ActivateDoResults(CBaseEntity *Goal, CBasePlayer *AP, CBaseEntity *ActivatingGoal); +BOOL ActivationSucceeded(CBaseEntity *Goal, CBasePlayer *AP, CBaseEntity *ActivatingGoal); + +// prematch & ceasefire +void Display_Prematch(); +void Check_Ceasefire(); + +// admin +void KickPlayer( CBaseEntity *pTarget ); +void BanPlayer( CBaseEntity *pTarget ); +CGhost *FindGhost( int iGhostID ); +int GetBattleID( edict_t *pEntity ); + +extern cvar_t tfc_spam_penalty1;// the initial gag penalty for a spammer (seconds) +extern cvar_t tfc_spam_penalty2;// incremental gag penalty (seconds) for each time gagged spammer continues to speak. +extern cvar_t tfc_spam_limit; // at this many points, gag the spammer +extern cvar_t tfc_clanbattle, tfc_clanbattle_prematch, tfc_prematch, tfc_clanbattle_ceasefire, tfc_balance_teams, tfc_balance_scores; +extern cvar_t tfc_clanbattle_locked, tfc_birthday, tfc_autokick_kills, tfc_fragscoring, tfc_autokick_time, tfc_adminpwd; +extern cvar_t weaponstay, footsteps, flashlight, aimcrosshair, falldamage, teamplay; +extern cvar_t allow_spectators; + +/*==========================================================================*/ +class CTFFlame : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void EXPORT FlameThink( void ); + static CTFFlame *FlameSpawn( CBaseEntity *pOwner, CBaseEntity *pTarget ); + void FlameDestroy( void ); + + float m_flNextDamageTime; +}; + +/*==========================================================================*/ +// MAPSCRIPT CLASSES +class CTFGoal : public CBaseAnimating +{ +public: + void Spawn( void ); + void StartGoal( void ); + void EXPORT PlaceGoal( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int Classify ( void ) { return CLASS_TFGOAL; } + + void SetObjectCollisionBox( void ); +}; + +class CTFGoalItem : public CTFGoal +{ +public: + void Spawn( void ); + void StartItem( void ); + void EXPORT PlaceItem( void ); + int Classify ( void ) { return CLASS_TFGOAL_ITEM; } + + float m_flDroppedAt; +}; + +class CTFTimerGoal : public CTFGoal +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_TFGOAL_TIMER; } +}; + +class CTFSpawn : public CBaseEntity +{ +public: + void Spawn( void ); + void Activate( void ); + int Classify ( void ) { return CLASS_TFSPAWN; } + BOOL CheckTeam( int iTeamNo ); + + EHANDLE m_pTeamCheck; +}; + +class CTFDetect : public CBaseEntity +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_TFGOAL; } +}; + +class CTelefragDeath : public CBaseEntity +{ +public: + void Spawn( void ); + void EXPORT DeathTouch( CBaseEntity *pOther ); +}; + +class CTeamCheck : public CBaseDelay +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + BOOL TeamMatches( int iTeam ); +}; + +class CTeamSet : public CBaseDelay +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; + +#endif // TF_DEFS_ONLY +#endif // __TF_DEFS_H + + diff --git a/cl_dll/timing.h b/cl_dll/timing.h new file mode 100644 index 0000000..5ce204c --- /dev/null +++ b/cl_dll/timing.h @@ -0,0 +1,57 @@ +/* +Simple helper macros to time blocks or statements. + +Copyright (C) 2007 Frank Richter + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __TIMING_H__ +#define __TIMING_H__ + +#if defined(DO_TIMING) + +#define TIMING_BEGIN double _timing_end_, _timing_start_ = gEngfuncs.pfnSys_FloatTime(); +#define TIMING_END_STR(S) \ + _timing_end_ = gEngfuncs.pfnSys_FloatTime(); \ + Msg ("%s: %.3g s\n", S, _timing_end_ - _timing_start_); +#define TIMING_END TIMING_END_STR(__FUNCTION__) + +#define TIMING_INTERMEDIATE(S) \ + { \ + double currentTime = gEngfuncs.pfnSys_FloatTime(); \ + Msg("%s: %.3g s\n", S, currentTime - _timing_start_); \ + } + +#define TIMING_TIMESTATEMENT(Stmt) \ + { \ + TIMING_BEGIN \ + Stmt; \ + TIMING_END_STR(#Stmt); \ + } + +#else + +#define TIMING_BEGIN +#define TIMING_END_STR(S) +#define TIMING_END +#define TIMING_INTERMEDIATE(S) +#define TIMING_TIMESTATEMENT(Stmt) Stmt + +#endif + +#endif // __TIMING_H__ \ No newline at end of file diff --git a/cl_dll/train.cpp b/cl_dll/train.cpp new file mode 100644 index 0000000..f4cc2a5 --- /dev/null +++ b/cl_dll/train.cpp @@ -0,0 +1,85 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Train.cpp +// +// implementation of CHudAmmo class +// + +#include "hud.h" +#include "cl_util.h" +#include +#include +#include "parsemsg.h" + +DECLARE_MESSAGE(m_Train, Train ) + + +int CHudTrain::Init(void) +{ + HOOK_MESSAGE( Train ); + + m_iPos = 0; + m_iFlags = 0; + gHUD.AddHudElem(this); + + return 1; +}; + +int CHudTrain::VidInit(void) +{ + m_hSprite = 0; + + return 1; +}; + +int CHudTrain::Draw(float fTime) +{ + if ( !m_hSprite ) + m_hSprite = LoadSprite("sprites/%d_train.spr"); + + if (m_iPos) + { + int r, g, b, x, y; + + UnpackRGB(r,g,b, gHUD.m_iHUDColor); + SPR_Set(m_hSprite, r, g, b ); + + // This should show up to the right and part way up the armor number + y = ScreenHeight - SPR_Height(m_hSprite,0) - gHUD.m_iFontHeight; + x = ScreenWidth/3 + SPR_Width(m_hSprite,0)/4; + + SPR_DrawAdditive( m_iPos - 1, x, y, NULL); + + } + + return 1; +} + + +int CHudTrain::MsgFunc_Train(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + // update Train data + m_iPos = READ_BYTE(); + + if (m_iPos) + m_iFlags |= HUD_ACTIVE; + else + m_iFlags &= ~HUD_ACTIVE; + + return 1; +} diff --git a/cl_dll/util.cpp b/cl_dll/util.cpp new file mode 100644 index 0000000..e3dc27b --- /dev/null +++ b/cl_dll/util.cpp @@ -0,0 +1,1421 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// util.cpp +// +// implementation of class-less helper functions +// + +#include "STDIO.H" +#include "STDLIB.H" +#include "MATH.H" + +#include "hud.h" +#include "cl_util.h" +#include +#include "com_model.h" +#include "triangleapi.h" +#include +#include "gl_local.h" +#include "event_api.h" +#include "r_efx.h" +#include "pmtrace.h" +#include "gl_studio.h" +#include "gl_world.h" + +static byte decompressed[MAX_MAP_LEAFS/8]; + +/* +============= +pfnAlertMessage + +============= +*/ +void ALERT( ALERT_TYPE level, char *szFmt, ... ) +{ + char buffer[2048]; // must support > 1k messages + va_list args; + + if( developer_level <= DEV_NONE ) + return; + + if( level == at_aiconsole && developer_level < DEV_EXTENDED ) + return; + + va_start( args, szFmt ); + Q_vsnprintf( buffer, 2048, szFmt, args ); + va_end( args ); + + if( level == at_warning ) + { + gEngfuncs.Con_Printf( va( "^3Warning:^7 %s", buffer )); + } + else if( level == at_error ) + { + gEngfuncs.Con_Printf( va( "^1Error:^7 %s", buffer )); + } + else + { + gEngfuncs.Con_Printf( buffer ); + } +} + +/* +==================== +Sys LoadGameDLL + +==================== +*/ +bool Sys_LoadLibrary( const char* dllname, dllhandle_t* handle, const dllfunc_t *fcts ) +{ + if( !handle ) return false; + + const dllfunc_t *gamefunc; + + // Initializations + for( gamefunc = fcts; gamefunc && gamefunc->name != NULL; gamefunc++ ) + *gamefunc->func = NULL; + + char dllpath[128]; + + // is direct path used ? + if( dllname[0] == '*' ) Q_strncpy( dllpath, dllname + 1, sizeof( dllpath )); + else Q_snprintf( dllpath, sizeof( dllpath ), "%s/cl_dlls/%s", gEngfuncs.pfnGetGameDirectory(), dllname ); + + dllhandle_t dllhandle = LoadLibrary( dllpath ); + + // No DLL found + if( !dllhandle ) return false; + + // Get the function adresses + for( gamefunc = fcts; gamefunc && gamefunc->name != NULL; gamefunc++ ) + { + if( !( *gamefunc->func = (void *)Sys_GetProcAddress( dllhandle, gamefunc->name ))) + { + Sys_FreeLibrary( &dllhandle ); + return false; + } + } + + ALERT( at_aiconsole, "%s loaded succesfully!\n", (dllname[0] == '*') ? (dllname+1) : (dllname)); + *handle = dllhandle; + + return true; +} + +void *Sys_GetProcAddress( dllhandle_t handle, const char *name ) +{ + return (void *)GetProcAddress( handle, name ); +} + +void Sys_FreeLibrary( dllhandle_t *handle ) +{ + if( !handle || !*handle ) + return; + + FreeLibrary( *handle ); + *handle = NULL; +} + +bool Sys_RemoveFile( const char *path ) +{ + char real_path[1024]; + int iRet = false; + + if( !path || !*path ) + return false; + + Q_snprintf( real_path, sizeof( real_path ), "%s/%s", gEngfuncs.pfnGetGameDirectory(), path ); + COM_FixSlashes( real_path ); + iRet = remove( real_path ); + + return (iRet == 0) ? true : false; +} + +/* +============= +WorldToScreen + +convert world coordinates (x,y,z) into screen (x, y) +============= +*/ +int WorldToScreen( const Vector &world, Vector &screen ) +{ + int retval = R_WorldToScreen( world, screen ); + + screen[0] = 0.5f * screen[0] * (float)RI->view.port[2]; + screen[1] = -0.5f * screen[1] * (float)RI->view.port[3]; + screen[0] += 0.5f * (float)RI->view.port[2]; + screen[1] += 0.5f * (float)RI->view.port[3]; + + return retval; +} + +//================= +// UTIL_IsPlayer +//================= +bool UTIL_IsPlayer( int idx ) +{ + if ( idx >= 1 && idx <= gEngfuncs.GetMaxClients() ) + return true; + return false; +} + + +//================= +// UTIL_IsLocal +//================= +bool UTIL_IsLocal( int idx ) +{ + return gEngfuncs.pEventAPI->EV_IsLocal( idx - 1 ) ? true : false; +} + +//================= +// UTIL_WeaponAnimation +//================= +void UTIL_WeaponAnimation( int iAnim, float framerate ) +{ + cl_entity_t *view = GET_VIEWMODEL(); + + gEngfuncs.pEventAPI->EV_WeaponAnimation( iAnim, view->curstate.body ); + + view->curstate.framerate = framerate; + view->curstate.scale = 1.0f; +#if 0 + // just for test Glow Shell effect + view->curstate.renderfx = kRenderFxGlowShell; + view->curstate.rendercolor.r = 255; + view->curstate.rendercolor.g = 255; + view->curstate.rendercolor.b = 255; + view->curstate.renderamt = 100; +#endif +} + +void UTIL_StudioDecal( const char *pDecalName, pmtrace_t *pTrace, const Vector &vecSrc ) +{ + g_StudioRenderer.StudioDecalShoot( pTrace, pDecalName, false ); +} + +/* +==================== +SPRITE_GetList + +==================== +*/ +char *ParseHudSprite( char *pfile, char *psz, client_sprite_t *result ) +{ + client_sprite_t tempSprite; + int x = 0, y = 0, width = 0, height = 0; + int section = 0; + char token[256]; + + memset( &tempSprite, 0, sizeof( tempSprite )); + memset( result, 0, sizeof( *result )); + + while( pfile ) + { + pfile = COM_ParseFile( pfile, token ); + + if( !Q_stricmp( token, psz )) + { + pfile = COM_ParseFile( pfile, token ); + if( !Q_stricmp( token, "{" )) section = 1; + } + + if( section ) + { + if( !Q_stricmp( token, "}" )) + break; // end section + + if( !Q_stricmp( token, "file" )) + { + pfile = COM_ParseFile( pfile, token ); + Q_strcpy( tempSprite.szSprite, token ); + void *testSprite; + + if(( testSprite = gEngfuncs.COM_LoadFile( tempSprite.szSprite, 5, NULL )) != NULL ) + { + gEngfuncs.COM_FreeFile( testSprite ); + + // fill structure at default + HSPRITE m_hSprite = SPR_Load( tempSprite.szSprite ); + + width = SPR_Width( m_hSprite, 0 ); + height = SPR_Height( m_hSprite, 0 ); + x = y = 0; + } + else + { + return pfile; + } + } + else if( !Q_stricmp( token, "name" )) + { + pfile = COM_ParseFile( pfile, token ); + Q_strcpy( tempSprite.szName, token ); + } + else if( !Q_stricmp( token, "x" )) + { + pfile = COM_ParseFile( pfile, token ); + x = Q_atoi( token ); + } + else if( !Q_stricmp( token, "y" )) + { + pfile = COM_ParseFile( pfile, token ); + y = Q_atoi( token ); + } + else if( !Q_stricmp( token, "width" )) + { + pfile = COM_ParseFile( pfile, token ); + width = Q_atoi( token ); + } + else if( !Q_stricmp( token, "height" )) + { + pfile = COM_ParseFile( pfile, token ); + height = Q_atoi( token ); + } + } + } + + if( !section ) + return pfile; // data not found + + // calculate sprite position + tempSprite.rc.left = x; + tempSprite.rc.right = x + width; + tempSprite.rc.top = y; + tempSprite.rc.bottom = y + height; + + // write resolution for backward compatibility + if( ScreenWidth < 640 ) + tempSprite.iRes = 320; + else tempSprite.iRes = 640; + + *result = tempSprite; + + return pfile; +} + +client_sprite_t *SPR2_GetList( char *psz, int *piCount ) +{ + int iSprCount = 0; + char *afile = (char *)gEngfuncs.COM_LoadFile( psz, 5, NULL ); + + if( !afile ) + { + *piCount = iSprCount; + return NULL; + } + + char token[256]; + char *pfile = afile; + + while( pfile ) + { + // calculate count of sprites + pfile = COM_ParseFile( pfile, token ); + + if( !Q_stricmp( token, "hudsprite" )) + iSprCount++; + } + + client_sprite_t *phud = new client_sprite_t[iSprCount]; + pfile = afile; + + for( int i = 0; i < iSprCount; i++ ) + { + pfile = ParseHudSprite( pfile, "hudsprite", &phud[i] ); + } + + gEngfuncs.COM_FreeFile( afile ); + + *piCount = iSprCount; + + return phud; +} + +/* +====================================================================== + +LEAF LISTING + +NOTE: this code ripped out from Xash3D +====================================================================== +*/ +static void Mod_BoxLeafnums_r( leaflist_t *ll, mnode_t *node, model_t *worldmodel ) +{ + int s; + + while( 1 ) + { + if( node->contents == CONTENTS_SOLID ) + return; + + if( node->contents < 0 ) + { + mleaf_t *leaf = (mleaf_t *)node; + + // it's a leaf! + if( ll->count >= ll->maxcount ) + { + ll->overflowed = true; + return; + } + + ll->list[ll->count++] = leaf->cluster; + return; + } + + s = BOX_ON_PLANE_SIDE( ll->mins, ll->maxs, node->plane ); + + if( s == 1 ) + { + node = node->children[0]; + } + else if( s == 2 ) + { + node = node->children[1]; + } + else + { + // go down both + if( ll->headnode == NULL ) + ll->headnode = node; + Mod_BoxLeafnums_r( ll, node->children[0], worldmodel ); + node = node->children[1]; + } + } +} + +/* +================== +Mod_BoxLeafnums +================== +*/ +int Mod_BoxLeafnums( const Vector &mins, const Vector &maxs, short *list, int listsize, mnode_t **headnode ) +{ + leaflist_t ll; + model_t *worldmodel; + + worldmodel = gEngfuncs.GetEntityByIndex( 0 )->model; + if( headnode ) *headnode = NULL; + + if( !worldmodel ) + return 0; + + ll.mins = mins; + ll.maxs = maxs; + ll.count = 0; + ll.maxcount = listsize; + ll.list = list; + ll.headnode = NULL; + ll.overflowed = false; + + Mod_BoxLeafnums_r( &ll, worldmodel->nodes, worldmodel ); + + if( headnode ) + *headnode = ll.headnode; + return ll.count; +} + +/* +============= +Mod_HeadnodeVisible +============= +*/ +bool Mod_HeadnodeVisible( mnode_t *node, const byte *visbits ) +{ + if( !node || node->contents == CONTENTS_SOLID ) + return false; + + if( node->contents < 0 ) + { + if( CHECKVISBIT( visbits, ((mleaf_t *)node)->cluster )) + return true; + return false; + } + + if( Mod_HeadnodeVisible( node->children[0], visbits )) + return true; + + return Mod_HeadnodeVisible( node->children[1], visbits ); +} + +/* +============= +Mod_BoxVisible + +Returns true if any leaf in boxspace +is potentially visible +============= +*/ +bool Mod_BoxVisible( const Vector &mins, const Vector &maxs, const byte *visbits ) +{ + short leafList[48]; + leaflist_t ll; + + if( !visbits ) return true; + + ll.maxcount = ARRAYSIZE( leafList ); + ll.list = leafList; + ll.headnode = NULL; + ll.overflowed = false; + ll.mins = mins; + ll.maxs = maxs; + ll.count = 0; + + Mod_BoxLeafnums_r( &ll, worldmodel->nodes, worldmodel ); + + for( int i = 0; i < ll.count; i++ ) + { + if( CHECKVISBIT( visbits, leafList[i] )) + return true; + } + + if( !ll.overflowed ) + return false; // invisible + + // overflowed? go by headnode + if( Mod_HeadnodeVisible( ll.headnode, visbits )) + return true; + + return false; +} + +/* +=================== +Mod_DecompressVis +=================== +*/ +void Mod_DecompressVis( const byte *in, byte *pvs ) +{ + int c; + byte *out; + int row; + + row = (worldmodel->numleafs+7)>>3; + out = pvs; + + if( !in ) + { // no vis info, so make all visible + while( row ) + { + *out++ = 0xff; + row--; + } + return; + } + + do + { + if( *in ) + { + *out++ = *in++; + continue; + } + + c = in[1]; + in += 2; + while( c ) + { + *out++ = 0; + c--; + } + } while( out - pvs < row ); +} + +byte *Mod_LeafPVS( mleaf_t *leaf, model_t *model ) +{ + if( !model || !leaf || leaf == model->leafs || !model->visdata ) + Mod_DecompressVis( NULL, decompressed ); + else Mod_DecompressVis( leaf->compressed_vis, decompressed ); + + return decompressed; +} + +/* +================== +Mod_PointInLeaf + +================== +*/ +mleaf_t *Mod_PointInLeaf( const Vector &p, mnode_t *node ) +{ + while( 1 ) + { + if( node->contents < 0 ) + return (mleaf_t *)node; + node = node->children[PlaneDiff( p, node->plane ) < 0]; + } + + // never reached + return NULL; +} + +bool Mod_PointInSolid( const Vector &p ) +{ + return (Mod_PointInLeaf( p, worldmodel->nodes )->contents == CONTENTS_SOLID) ? true : false; +} + +byte *Mod_GetCurrentVis( void ) +{ + return RI->view.pvsarray; +} + +bool Mod_CheckBoxVisible( const Vector &absmin, const Vector &absmax ) +{ + return Mod_BoxVisible( absmin, absmax, Mod_GetCurrentVis( )); +} + +bool Mod_CheckEntityPVS( cl_entity_t *ent ) +{ + if( !ent || !ent->index ) + return false; // not exist on the client + + if( ent->curstate.messagenum != r_currentMessageNum ) + return false; // already culled by server + + Vector mins = ent->curstate.origin + ent->curstate.mins; + Vector maxs = ent->curstate.origin + ent->curstate.maxs; + + return Mod_BoxVisible( mins, maxs, Mod_GetCurrentVis( )); +} + +bool Mod_CheckEntityLeafPVS( const Vector &absmin, const Vector &absmax, mleaf_t *leaf ) +{ + return Mod_BoxVisible( absmin, absmax, Mod_GetCurrentVis( )); +} + +bool Mod_CheckTempEntityPVS( TEMPENTITY *pTemp ) +{ + if( !pTemp ) return false; // not exist on the client + + Vector mins = pTemp->entity.curstate.origin + pTemp->entity.curstate.mins; + Vector maxs = pTemp->entity.curstate.origin + pTemp->entity.curstate.maxs; + + return Mod_BoxVisible( mins, maxs, Mod_GetCurrentVis( )); +} + +model_t *Mod_Handle( int modelIndex ) +{ + return IEngineStudio.GetModelByIndex( modelIndex ); +} + +int Mod_GetType( int modelIndex ) +{ + model_t *m_pModel; + + m_pModel = IEngineStudio.GetModelByIndex( modelIndex ); + if( m_pModel == NULL ) + return mod_bad; + + return m_pModel->type; +} + +/* +=================== +Mod_GetFrames +=================== +*/ +void Mod_GetFrames( int modelIndex, int &numFrames ) +{ + model_t *m_pModel; + + m_pModel = IEngineStudio.GetModelByIndex( modelIndex ); + + if( !m_pModel ) + { + numFrames = 1; + return; + } + + numFrames = m_pModel->numframes; + if( numFrames < 1 ) numFrames = 1; +} + +/* +============== +GetVisCache + +decompress visibility string +============== +*/ +mleaf_t *GetVisCache( mleaf_t *lastleaf, mleaf_t *leaf, byte *pvs ) +{ + // get the PVS for the pos to limit the number of checks + if( !worldmodel->visdata ) + { + if( leaf != lastleaf ) + { + memset( pvs, 255, (worldmodel->numleafs + 7) / 8 ); + leaf = worldmodel->leafs; + } + } + else if( leaf != lastleaf ) + { + if( leaf == worldmodel->leafs ) + memset( pvs, 0, (worldmodel->numleafs + 7) / 8 ); + else Mod_DecompressVis( leaf->compressed_vis, pvs ); + lastleaf = leaf; + } + + return lastleaf; +} + +/* +============== +SetDLightVis + +Init dlight visibility +============== +*/ +void SetDLightVis( mworldlight_t *wl, int leafnum ) +{ + if( !wl->pvs ) + wl->pvs = (byte *)Mem_Alloc( (worldmodel->numleafs + 7) / 8 ); + GetVisCache( NULL, worldmodel->leafs + leafnum, wl->pvs ); +} + +/* +============== +MergeDLightVis + +Merge dlight vis with another leaf +============== +*/ +void MergeDLightVis( mworldlight_t *wl, int leafnum ) +{ + if( !wl->pvs ) + { + SetDLightVis( wl, leafnum ); + } + else + { + byte pvs[(MAX_MAP_LEAFS + 7) / 8]; + + GetVisCache( NULL, worldmodel->leafs + leafnum, pvs ); + + // merge both vis graphs + for( int i = 0; i < (worldmodel->numleafs + 7) / 8; i++ ) + wl->pvs[i] |= pvs[i]; + } +} + +#define MAX_POLYGON_POINTS 64 +#define PLANESIDE_FRONT 1 +#define PLANESIDE_BACK 2 +#define PLANESIDE_ON 3 + +/* +================== +R_ClipPolygon +================== +*/ +bool R_ClipPolygon( int numPoints, Vector *points, const mplane_t *plane, int *numClipped, Vector *clipped ) +{ + float dists[MAX_POLYGON_POINTS]; + int sides[MAX_POLYGON_POINTS]; + bool frontSide, backSide; + float frac; + int i; + + if( numPoints >= MAX_POLYGON_POINTS - 2 ) + HOST_ERROR( "R_ClipPolygon: MAX_POLYGON_POINTS hit\n" ); + + *numClipped = 0; + + // Determine sides for each point + frontSide = false; + backSide = false; + + for( i = 0; i < numPoints; i++ ) + { + dists[i] = PlaneDiff( points[i], plane ); + + if( dists[i] > ON_EPSILON ) + { + sides[i] = PLANESIDE_FRONT; + frontSide = true; + continue; + } + + if( dists[i] < -ON_EPSILON ) + { + sides[i] = PLANESIDE_BACK; + backSide = true; + continue; + } + + sides[i] = PLANESIDE_ON; + } + + if( !frontSide ) + return false; // Not clipped + + if( !backSide ) + { + *numClipped = numPoints; + memcpy( clipped, points, numPoints * sizeof( Vector )); + + return true; + } + + // xlip it + points[i] = points[0]; + dists[i] = dists[0]; + sides[i] = sides[0]; + + for( i = 0; i < numPoints; i++ ) + { + if( sides[i] == PLANESIDE_ON ) + { + clipped[(*numClipped)++] = points[i]; + continue; + } + + if( sides[i] == PLANESIDE_FRONT ) + clipped[(*numClipped)++] = points[i]; + + if( sides[i+1] == PLANESIDE_ON || sides[i+1] == sides[i] ) + continue; + + if( dists[i] == dists[i+1] ) + { + clipped[(*numClipped)++] = points[i]; + } + else + { + frac = dists[i] / (dists[i] - dists[i+1]); + clipped[(*numClipped)++] = points[i] + (points[i+1] - points[i]) * frac; + } + } + + return true; +} + +/* +================== +R_SplitPolygon +================== +*/ +void R_SplitPolygon( int numPoints, Vector *points, const mplane_t *plane, int *numFront, Vector *front, int *numBack, Vector *back ) +{ + float dists[MAX_POLYGON_POINTS]; + int sides[MAX_POLYGON_POINTS]; + bool frontSide, backSide; + Vector mid; + float frac; + int i; + + if( numPoints >= MAX_POLYGON_POINTS - 2 ) + HOST_ERROR( "R_SplitPolygon: MAX_POLYGON_POINTS hit\n" ); + + *numFront = 0; + *numBack = 0; + + // Determine sides for each point + frontSide = false; + backSide = false; + + for( i = 0; i < numPoints; i++ ) + { + dists[i] = PlaneDiff( points[i], plane ); + + if( dists[i] > ON_EPSILON ) + { + sides[i] = PLANESIDE_FRONT; + frontSide = true; + continue; + } + + if( dists[i] < -ON_EPSILON ) + { + sides[i] = PLANESIDE_BACK; + backSide = true; + continue; + } + + sides[i] = PLANESIDE_ON; + } + + if( !frontSide ) + { + *numBack = numPoints; + memcpy( back, points, numPoints * sizeof( Vector )); + return; + } + + if( !backSide ) + { + *numFront = numPoints; + memcpy( front, points, numPoints * sizeof( Vector )); + return; + } + + // split it + points[i] = points[0]; + + dists[i] = dists[0]; + sides[i] = sides[0]; + + for( i = 0; i < numPoints; i++ ) + { + if( sides[i] == PLANESIDE_ON ) + { + front[(*numFront)++] = points[i]; + back[(*numBack)++] = points[i]; + continue; + } + + if( sides[i] == PLANESIDE_FRONT ) + front[(*numFront)++] = points[i]; + + if( sides[i] == PLANESIDE_BACK ) + back[(*numBack)++] = points[i]; + + if( sides[i+1] == PLANESIDE_ON || sides[i+1] == sides[i] ) + continue; + + if( dists[i] == dists[i+1] ) + { + front[(*numFront)++] = points[i]; + back[(*numBack)++] = points[i]; + } + else + { + frac = dists[i] / (dists[i] - dists[i+1]); + mid = points[i] + (points[i+1] - points[i]) * frac; + front[(*numFront)++] = mid; + back[(*numBack)++] = mid; + } + } +} + +/* +================== +R_TransformWorldToDevice +================== +*/ +void R_TransformWorldToDevice( const Vector &world, Vector &ndc ) +{ + Vector4D clip; + float scale; + + clip[0] = world[0] * RI->view.worldProjectionMatrix[0][0] + world[1] * RI->view.worldProjectionMatrix[1][0] + world[2] * RI->view.worldProjectionMatrix[2][0] + RI->view.worldProjectionMatrix[3][0]; + clip[1] = world[0] * RI->view.worldProjectionMatrix[0][1] + world[1] * RI->view.worldProjectionMatrix[1][1] + world[2] * RI->view.worldProjectionMatrix[2][1] + RI->view.worldProjectionMatrix[3][1]; + clip[2] = world[0] * RI->view.worldProjectionMatrix[0][2] + world[1] * RI->view.worldProjectionMatrix[1][2] + world[2] * RI->view.worldProjectionMatrix[2][2] + RI->view.worldProjectionMatrix[3][2]; + clip[3] = world[0] * RI->view.worldProjectionMatrix[0][3] + world[1] * RI->view.worldProjectionMatrix[1][3] + world[2] * RI->view.worldProjectionMatrix[2][3] + RI->view.worldProjectionMatrix[3][3]; + + if( clip[3] == 0.0f ) + { + ndc[0] = clip[0]; + ndc[1] = clip[1]; + ndc[2] = -1.0f; + } + else + { + scale = 1.0f / clip[3]; + ndc[0] = clip[0] * scale; + ndc[1] = clip[1] * scale; + ndc[2] = clip[2] * scale; + } +} + +/* +================== +R_TransformDeviceToScreen +================== +*/ +void R_TransformDeviceToScreen( const Vector &ndc, Vector &screen ) +{ + screen[0] = (ndc[0] * 0.5f + 0.5f) * (RI->view.port[2] - RI->view.port[0]) + RI->view.port[0]; + screen[1] = (ndc[1] * 0.5f + 0.5f) * (RI->view.port[3] - RI->view.port[1]) + RI->view.port[1]; + screen[2] = (ndc[2] * 0.5f + 0.5f); +} + +/* +================== +R_ScissorForBounds +================== +*/ +static bool R_ScissorForBounds( const Vector bbox[8], float *x, float *y, float *w, float *h ) +{ + static int cornerIndices[6][4] = {{3, 2, 6, 7}, {0, 1, 5, 4}, {2, 3, 1, 0}, {4, 5, 7, 6}, {1, 3, 7, 5}, {2, 0, 4, 6}}; + Vector points[2][MAX_POLYGON_POINTS]; + float ix1, iy1, ix2, iy2; + float x1, y1, x2, y2; + int numPoints; + Vector bounds[2]; + Vector ndc; + int pingPong = 0; + int i, j; + + // Clip the light volume to the view frustum + ClearBounds( bounds[0], bounds[1] ); + + for( i = 0; i < 6; i++ ) + { + numPoints = 4; + + points[pingPong][0] = bbox[cornerIndices[i][0]]; + points[pingPong][1] = bbox[cornerIndices[i][1]]; + points[pingPong][2] = bbox[cornerIndices[i][2]]; + points[pingPong][3] = bbox[cornerIndices[i][3]]; + + for( j = 0; j < FRUSTUM_PLANES; j++ ) + { + if( !FBitSet( RI->view.frustum.GetClipFlags(), BIT( j ))) + continue; + + if( !R_ClipPolygon( numPoints, points[pingPong], RI->view.frustum.GetPlane( j ), &numPoints, points[!pingPong] )) + break; + + pingPong ^= 1; + } + + if( j != FRUSTUM_PLANES ) + continue; + + // Add the clipped points + for( j = 0; j < numPoints; j++ ) + { + // Transform into normalized device coordinates + R_TransformWorldToDevice( points[pingPong][j], ndc ); + + // Add it + AddPointToBounds( ndc, bounds[0], bounds[1] ); + } + } + + // If completely clipped away, clear the scissor + if( BoundsIsCleared( bounds[0], bounds[1] )) + return false; + + // Transform into screen space + R_TransformDeviceToScreen( bounds[0], bounds[0] ); + R_TransformDeviceToScreen( bounds[1], bounds[1] ); + + x1 = bounds[0].x - 1.0f; + y1 = bounds[0].y - 1.0f; + x2 = bounds[1].x + 1.0f; + y2 = bounds[1].y + 1.0f; + + ix1 = Q_max( x1, RI->view.port[0] ); + ix2 = Q_min( x2, RI->view.port[2] ); + iy1 = Q_max( y1, RI->view.port[1] ); + iy2 = Q_min( y2, RI->view.port[3] ); + + *x = ix1 + 1.0f; + *y = RI->view.port[3] - iy2; // stupid OpenGL bug + *w = ix2 - ix1 - 1.0f; + *h = iy2 - iy1 - 1.0f; + + // headshield issues + if( ix1 >= ix2 || iy1 >= iy2 ) + return false; + return true; +} + +void R_BoundsForAABB( const Vector &absmin, const Vector &absmax, Vector bbox[8] ) +{ + ASSERT( bbox != NULL ); + + // compute a full bounding box + for( int i = 0; i < 8; i++ ) + { + bbox[i][0] = ( i & 1 ) ? absmin[0] : absmax[0]; + bbox[i][1] = ( i & 2 ) ? absmin[1] : absmax[1]; + bbox[i][2] = ( i & 4 ) ? absmin[2] : absmax[2]; + } +} + +bool R_ScissorForAABB( const Vector &absmin, const Vector &absmax, float *x, float *y, float *w, float *h ) +{ + Vector bbox[8]; + + R_BoundsForAABB( absmin, absmax, bbox ); + + return R_ScissorForBounds( bbox, x, y, w, h ); +} + +bool R_ScissorForFrustum( CFrustum *frustum, float *x, float *y, float *w, float *h ) +{ + Vector bbox[8]; + + frustum->ComputeFrustumCorners( bbox ); + + // NOTE: disable farplane because dynamic zFar + RI->view.frustum.DisablePlane( FRUSTUM_FAR ); + bool result = R_ScissorForBounds( bbox, x, y, w, h ); + RI->view.frustum.EnablePlane( FRUSTUM_FAR ); + + return result; +} + +bool R_AABBToScreen( const Vector &absmin, const Vector &absmax, Vector2D &scrmin, Vector2D &scrmax, wrect_t *rect ) +{ + float x, y, w, h; + Vector bbox[8]; + + R_BoundsForAABB( absmin, absmax, bbox ); + ClearBounds( scrmin, scrmax ); + + if( !R_ScissorForBounds( bbox, &x, &y, &w, &h )) + { + if( rect ) memset( rect, 0, sizeof( *rect )); + return false; + } + + // copy rectangle + if( rect ) + { + rect->left = x; + rect->right = RI->view.port[3] - h - y; // left bottom corner + rect->top = w; + rect->bottom = h; + } + + // compute a bounding box + AddPointToBounds( Vector2D( x, y ), scrmin, scrmax ); + AddPointToBounds( Vector2D( x + w, y ), scrmin, scrmax ); + AddPointToBounds( Vector2D( x, y + h ), scrmin, scrmax ); + AddPointToBounds( Vector2D( x + w, y + h ), scrmin, scrmax ); + + return true; +} + +// debug thing +void R_DrawScissorRectangle( float x, float y, float w, float h ) +{ + GL_BindTexture( GL_TEXTURE0, tr.whiteTexture ); + pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + GL_Setup2D(); + + pglColor3f( 1, 0.5, 0 ); + + pglBegin( GL_LINES ); + pglVertex2f( x, y ); + pglVertex2f( x + w, y ); + pglVertex2f( x, y ); + pglVertex2f( x, y + h ); + pglVertex2f( x + w, y ); + pglVertex2f( x + w, y + h ); + pglVertex2f( x, y + h ); + pglVertex2f( x + w, y + h ); + pglEnd(); + + GL_Setup3D(); +} + +//----------------------------------------------------------------------------- +// This returns the diameter of the sphere in pixels based on +// the current model, view, + projection matrices and viewport. +//----------------------------------------------------------------------------- +static float ComputePixelDiameterOfSphere( const Vector& vecAbsOrigin, float flRadius ) +{ + // This is sort of faked, but it's faster that way + // FIXME: Also, there's a much faster way to do this with similar triangles + // but I want to make sure it exactly matches the current matrices, so + // for now, I do it this conservative way + Vector4D testPoint1 = vecAbsOrigin + GetVUp() * flRadius; + Vector4D testPoint2 = vecAbsOrigin + GetVUp() * -flRadius; + Vector4D clipPos1 = RI->view.worldProjectionMatrix.VectorTransform( testPoint1 ); + Vector4D clipPos2 = RI->view.worldProjectionMatrix.VectorTransform( testPoint2 ); + + if( clipPos1.w >= 0.001f ) + { + clipPos1.y /= clipPos1.w; + } + else + { + clipPos1.y *= 1000.0f; + } + + if( clipPos2.w >= 0.001f ) + { + clipPos2.y /= clipPos2.w; + } + else + { + clipPos2.y *= 1000.0f; + } + + // The divide-by-two here is because y goes from -1 to 1 in projection space + return RI->view.port[3] * fabs( clipPos2.y - clipPos1.y ) / 2.0f; +} + +float ComputePixelWidthOfSphere( const Vector& vecOrigin, float flRadius ) +{ + return ComputePixelDiameterOfSphere( vecOrigin, flRadius ) * 2.0f; +} + +HSPRITE LoadSprite(const char *pszName) +{ + int i; + char sz[256]; + + if (ScreenWidth < 640) + i = 320; + else + i = 640; + + sprintf(sz, pszName, i); + + return SPR_Load(sz); +} + +void Physic_SweepTest( cl_entity_t *pTouch, const Vector &start, const Vector &mins, const Vector &maxs, const Vector &end, trace_t *tr ) +{ + if( !pTouch->model || !pTouch->model->cache.data ) + { + // bad model? + tr->allsolid = false; + return; + } + + Vector trace_mins, trace_maxs, bounds[2]; + UTIL_MoveBounds( start, mins, maxs, end, trace_mins, trace_maxs ); + + if( !R_StudioGetBounds( pTouch, bounds )) + { + tr->allsolid = false; + return; + } + + // NOTE: pmove code completely ignore a bounds checking. So we need to do it here + if( !BoundsIntersect( trace_mins, trace_maxs, bounds[0], bounds[1] )) + { + tr->allsolid = false; + return; + } + + CMeshDesc *bodyMesh = UTIL_GetCollisionMesh( pTouch->curstate.modelindex ); + + if( !bodyMesh ) + { + tr->allsolid = false; + return; + } + + mmesh_t *pMesh = bodyMesh->GetMesh(); + areanode_t *pHeadNode = bodyMesh->GetHeadNode(); + entity_state_t *pev = &pTouch->curstate; + + if( !pMesh ) + { + // failed to build mesh for some reasons, so skip them + tr->allsolid = false; + return; + } + + TraceMesh trm; // a name like Doom3 :-) + + trm.SetTraceMesh( pMesh, pHeadNode, pTouch->curstate.modelindex ); + trm.SetMeshOrientation( pev->origin, pev->angles, pev->startpos ); + trm.SetupTrace( start, mins, maxs, end, tr ); + + if( trm.DoTrace( )) + { + if( tr->fraction < 1.0f || tr->startsolid ) + tr->ent = (edict_t *)pTouch; // g-cont. no need, really. i'm leave this just for fun + tr->surf = trm.GetLastHitSurface(); + } +} + +#define GAMMA ( 2.2f ) // Valve Software gamma +#define INVGAMMA ( 1.0f / 2.2f ) // back to 1.0 + +static float texturetolinear[256]; // texture (0..255) to linear (0..1) +static int lineartotexture[1024]; // linear (0..1) to texture (0..255) + +void BuildGammaTable( void ) +{ + int i; + float g1, g3; + float g = bound( 0.5f, GAMMA, 3.0f ); + + g = 1.0 / g; + g1 = GAMMA * g; + g3 = 0.125f; + + for( i = 0; i < 256; i++ ) + { + // convert from nonlinear texture space (0..255) to linear space (0..1) + texturetolinear[i] = pow( i / 255.0f, GAMMA ); + } + + for( i = 0; i < 1024; i++ ) + { + // convert from linear space (0..1) to nonlinear texture space (0..255) + lineartotexture[i] = pow( i / 1023.0, INVGAMMA ) * 255; + } +} + +// convert texture to linear 0..1 value +float TextureToLinear( int c ) { return texturetolinear[bound( 0, c, 255 )]; } + +// convert texture to linear 0..1 value +int LinearToTexture( float f ) { return lineartotexture[bound( 0, (int)(f * 1023), 1023 )]; } + +/* +=============================================================================== + + LEGACY STUFF + + moved here from studio_util.cpp +=============================================================================== +*/ +/* +==================== +AngleMatrix + +==================== +*/ +void AngleMatrix (const float *angles, float (*matrix)[4] ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + // matrix = (YAW * PITCH) * ROLL + matrix[0][0] = cp*cy; + matrix[1][0] = cp*sy; + matrix[2][0] = -sp; + matrix[0][1] = sr*sp*cy+cr*-sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[2][1] = sr*cp; + matrix[0][2] = (cr*sp*cy+-sr*-sy); + matrix[1][2] = (cr*sp*sy+-sr*cy); + matrix[2][2] = cr*cp; + matrix[0][3] = 0.0; + matrix[1][3] = 0.0; + matrix[2][3] = 0.0; +} + +/* +==================== +VectorTransform + +==================== +*/ +void VectorTransform (const float *in1, float in2[3][4], float *out) +{ + out[0] = DotProduct(in1, in2[0]) + in2[0][3]; + out[1] = DotProduct(in1, in2[1]) + in2[1][3]; + out[2] = DotProduct(in1, in2[2]) + in2[2][3]; +} + +vec3_t vec3_origin( 0, 0, 0 ); + +float Length(const float *v) +{ + int i; + float length; + + length = 0; + for (i=0 ; i< 3 ; i++) + length += v[i]*v[i]; + length = sqrt (length); // FIXME + + return length; +} + +void VectorAngles( const float *forward, float *angles ) +{ + float tmp, yaw, pitch; + + if (forward[1] == 0 && forward[0] == 0) + { + yaw = 0; + if (forward[2] > 0) + pitch = 90; + else + pitch = 270; + } + else + { + yaw = (atan2(forward[1], forward[0]) * 180 / M_PI); + if (yaw < 0) + yaw += 360; + + tmp = sqrt (forward[0]*forward[0] + forward[1]*forward[1]); + pitch = (atan2(forward[2], tmp) * 180 / M_PI); + if (pitch < 0) + pitch += 360; + } + + angles[0] = pitch; + angles[1] = yaw; + angles[2] = 0; +} + +float VectorNormalize (float *v) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); // FIXME + + if (length) + { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; + +} + +void VectorScale (const float *in, float scale, float *out) +{ + out[0] = in[0]*scale; + out[1] = in[1]*scale; + out[2] = in[2]*scale; +} + +void VectorMA (const float *veca, float scale, const float *vecb, float *vecc) +{ + vecc[0] = veca[0] + scale*vecb[0]; + vecc[1] = veca[1] + scale*vecb[1]; + vecc[2] = veca[2] + scale*vecb[2]; +} + +float PackColor( const color24 &color ) +{ + return (float)((double)((color.r << 16) | (color.g << 8) | color.b) / (double)(1 << 24)); +} + +color24 UnpackColor( float pack ) +{ + Vector color = Vector( 1.0f, 256.0f, 65536.0f ) * pack; + + // same as fract( color ) + color.x = color.x - floor( color.x ); + color.y = color.y - floor( color.y ); + color.z = color.z - floor( color.z ); + + return color24( color.x * 256, color.y * 256, color.z * 256 ); +} \ No newline at end of file diff --git a/cl_dll/vgui_ClassMenu.cpp b/cl_dll/vgui_ClassMenu.cpp new file mode 100644 index 0000000..ba94fd0 --- /dev/null +++ b/cl_dll/vgui_ClassMenu.cpp @@ -0,0 +1,432 @@ +//=========== (C) Copyright 1996-2002 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: TFC Class Menu +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "VGUI_Font.h" +#include + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "parsemsg.h" + +#include "vgui_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ServerBrowser.h" + +// Class Menu Dimensions +#define CLASSMENU_TITLE_X XRES(40) +#define CLASSMENU_TITLE_Y YRES(32) +#define CLASSMENU_TOPLEFT_BUTTON_X XRES(40) +#define CLASSMENU_TOPLEFT_BUTTON_Y YRES(80) +#define CLASSMENU_BUTTON_SIZE_X XRES(124) +#define CLASSMENU_BUTTON_SIZE_Y YRES(24) +#define CLASSMENU_BUTTON_SPACER_Y YRES(8) +#define CLASSMENU_WINDOW_X XRES(176) +#define CLASSMENU_WINDOW_Y YRES(80) +#define CLASSMENU_WINDOW_SIZE_X XRES(424) +#define CLASSMENU_WINDOW_SIZE_Y YRES(312) +#define CLASSMENU_WINDOW_TEXT_X XRES(150) +#define CLASSMENU_WINDOW_TEXT_Y YRES(80) +#define CLASSMENU_WINDOW_NAME_X XRES(150) +#define CLASSMENU_WINDOW_NAME_Y YRES(8) +#define CLASSMENU_WINDOW_PLAYERS_Y YRES(42) + +// Creation +CClassMenuPanel::CClassMenuPanel(int iTrans, int iRemoveMe, int x,int y,int wide,int tall) : CMenuPanel(iTrans, iRemoveMe, x,y,wide,tall) +{ + // don't show class graphics at below 640x480 resolution + bool bShowClassGraphic = true; + if ( ScreenWidth < 640 ) + { + bShowClassGraphic = false; + } + + memset( m_pClassImages, 0, sizeof(m_pClassImages) ); + + // Get the scheme used for the Titles + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + + // schemes + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle( "Title Font" ); + SchemeHandle_t hClassWindowText = pSchemes->getSchemeHandle( "Briefing Text" ); + + // color schemes + int r, g, b, a; + + // Create the title + Label *pLabel = new Label( "", CLASSMENU_TITLE_X, CLASSMENU_TITLE_Y ); + pLabel->setParent( this ); + pLabel->setFont( pSchemes->getFont(hTitleScheme) ); + pSchemes->getFgColor( hTitleScheme, r, g, b, a ); + pLabel->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hTitleScheme, r, g, b, a ); + pLabel->setBgColor( r, g, b, a ); + pLabel->setContentAlignment( vgui::Label::a_west ); + pLabel->setText(gHUD.m_TextMessage.BufferedLocaliseTextString("#Title_SelectYourClass")); + + // Create the Scroll panel + m_pScrollPanel = new CTFScrollPanel( CLASSMENU_WINDOW_X, CLASSMENU_WINDOW_Y, CLASSMENU_WINDOW_SIZE_X, CLASSMENU_WINDOW_SIZE_Y ); + m_pScrollPanel->setParent(this); + //force the scrollbars on, so after the validate clientClip will be smaller + m_pScrollPanel->setScrollBarAutoVisible(false, false); + m_pScrollPanel->setScrollBarVisible(true, true); + m_pScrollPanel->setBorder( new LineBorder( Color(255 * 0.7,170 * 0.7,0,0) ) ); + m_pScrollPanel->validate(); + + int clientWide=m_pScrollPanel->getClient()->getWide(); + + //turn scrollpanel back into auto show scrollbar mode and validate + m_pScrollPanel->setScrollBarAutoVisible(false,true); + m_pScrollPanel->setScrollBarVisible(false,false); + m_pScrollPanel->validate(); + + // Create the Class buttons + for (int i = 0; i <= PC_RANDOM; i++) + { + char sz[256]; + int iYPos = CLASSMENU_TOPLEFT_BUTTON_Y + ( (CLASSMENU_BUTTON_SIZE_Y + CLASSMENU_BUTTON_SPACER_Y) * i ); + + ActionSignal *pASignal = new CMenuHandler_StringCommandClassSelect( sTFClassSelection[i], true ); + + // Class button + sprintf(sz, "%s", CHudTextMessage::BufferedLocaliseTextString( sLocalisedClasses[i] ) ); + m_pButtons[i] = new ClassButton( i, sz, CLASSMENU_TOPLEFT_BUTTON_X, iYPos, CLASSMENU_BUTTON_SIZE_X, CLASSMENU_BUTTON_SIZE_Y, true); + // RandomPC uses '0' + if ( i >= 1 && i <= 9 ) + { + sprintf(sz,"%d",i); + } + else + { + sprintf(sz,"0"); + } + m_pButtons[i]->setBoundKey( sz[0] ); + m_pButtons[i]->setContentAlignment( vgui::Label::a_west ); + m_pButtons[i]->addActionSignal( pASignal ); + m_pButtons[i]->addInputSignal( new CHandler_MenuButtonOver(this, i) ); + m_pButtons[i]->setParent( this ); + + // Create the Class Info Window + //m_pClassInfoPanel[i] = new CTransparentPanel( 255, CLASSMENU_WINDOW_X, CLASSMENU_WINDOW_Y, CLASSMENU_WINDOW_SIZE_X, CLASSMENU_WINDOW_SIZE_Y ); + m_pClassInfoPanel[i] = new CTransparentPanel( 255, 0, 0, clientWide, CLASSMENU_WINDOW_SIZE_Y ); + m_pClassInfoPanel[i]->setParent( m_pScrollPanel->getClient() ); + //m_pClassInfoPanel[i]->setVisible( false ); + + // don't show class pic in lower resolutions + int textOffs = XRES(8); + + if ( bShowClassGraphic ) + { + textOffs = CLASSMENU_WINDOW_NAME_X; + } + + // Create the Class Name Label + sprintf(sz, "#Title_%s", sTFClassSelection[i]); + char* localName=CHudTextMessage::BufferedLocaliseTextString( sz ); + Label *pNameLabel = new Label( "", textOffs, CLASSMENU_WINDOW_NAME_Y ); + pNameLabel->setFont( pSchemes->getFont(hTitleScheme) ); + pNameLabel->setParent( m_pClassInfoPanel[i] ); + pSchemes->getFgColor( hTitleScheme, r, g, b, a ); + pNameLabel->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hTitleScheme, r, g, b, a ); + pNameLabel->setBgColor( r, g, b, a ); + pNameLabel->setContentAlignment( vgui::Label::a_west ); + //pNameLabel->setBorder(new LineBorder()); + pNameLabel->setText(localName); + + // Create the Class Image + if ( bShowClassGraphic ) + { + for ( int team = 0; team < 2; team++ ) + { + if ( team == 1 ) + { + sprintf( sz, "%sred", sTFClassSelection[i] ); + } + else + { + sprintf( sz, "%sblue", sTFClassSelection[i] ); + } + + m_pClassImages[team][i] = new CImageLabel( sz, 0, 0, CLASSMENU_WINDOW_TEXT_X, CLASSMENU_WINDOW_TEXT_Y ); + + CImageLabel *pLabel = m_pClassImages[team][i]; + pLabel->setParent( m_pClassInfoPanel[i] ); + //pLabel->setBorder(new LineBorder()); + + if ( team != 1 ) + { + pLabel->setVisible( false ); + } + + // Reposition it based upon it's size + int xOut, yOut; + pNameLabel->getTextSize( xOut, yOut ); + pLabel->setPos( (CLASSMENU_WINDOW_TEXT_X - pLabel->getWide()) / 2, yOut /2 ); + } + } + + // Create the Player count string + gHUD.m_TextMessage.LocaliseTextString( "#Title_CurrentlyOnYourTeam", m_sPlayersOnTeamString, STRLENMAX_PLAYERSONTEAM ); + m_pPlayers[i] = new Label( "", textOffs, CLASSMENU_WINDOW_PLAYERS_Y ); + m_pPlayers[i]->setParent( m_pClassInfoPanel[i] ); + m_pPlayers[i]->setBgColor( 0, 0, 0, 255 ); + m_pPlayers[i]->setContentAlignment( vgui::Label::a_west ); + m_pPlayers[i]->setFont( pSchemes->getFont(hClassWindowText) ); + + // Open up the Class Briefing File + sprintf(sz, "classes/short_%s.txt", sTFClassSelection[i]); + char *cText = "Class Description not available."; + char *pfile = (char *)gEngfuncs.COM_LoadFile( sz, 5, NULL ); + if (pfile) + { + cText = pfile; + } + + // Create the Text info window + TextPanel *pTextWindow = new TextPanel(cText, textOffs, CLASSMENU_WINDOW_TEXT_Y, (CLASSMENU_WINDOW_SIZE_X - textOffs)-5, CLASSMENU_WINDOW_SIZE_Y - CLASSMENU_WINDOW_TEXT_Y); + pTextWindow->setParent( m_pClassInfoPanel[i] ); + pTextWindow->setFont( pSchemes->getFont(hClassWindowText) ); + pSchemes->getFgColor( hClassWindowText, r, g, b, a ); + pTextWindow->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hClassWindowText, r, g, b, a ); + pTextWindow->setBgColor( r, g, b, a ); + + // Resize the Info panel to fit it all + int wide,tall; + pTextWindow->getTextImage()->getTextSizeWrapped( wide,tall); + pTextWindow->setSize(wide,tall); + + int xx,yy; + pTextWindow->getPos(xx,yy); + int maxX=xx+wide; + int maxY=yy+tall; + + //check to see if the image goes lower than the text + //just use the red teams [0] images + if(m_pClassImages[0][i]!=null) + { + m_pClassImages[0][i]->getPos(xx,yy); + if((yy+m_pClassImages[0][i]->getTall())>maxY) + { + maxY=yy+m_pClassImages[0][i]->getTall(); + } + } + + m_pClassInfoPanel[i]->setSize( maxX , maxY ); + if (pfile) gEngfuncs.COM_FreeFile( pfile ); + //m_pClassInfoPanel[i]->setBorder(new LineBorder()); + + } + + // Create the Cancel button + m_pCancelButton = new CommandButton( gHUD.m_TextMessage.BufferedLocaliseTextString( "#Menu_Cancel" ), CLASSMENU_TOPLEFT_BUTTON_X, 0, CLASSMENU_BUTTON_SIZE_X, CLASSMENU_BUTTON_SIZE_Y); + m_pCancelButton->setParent( this ); + m_pCancelButton->addActionSignal( new CMenuHandler_TextWindow(HIDE_TEXTWINDOW) ); + + m_iCurrentInfo = 0; + +} + + +// Update +void CClassMenuPanel::Update() +{ + // Don't allow the player to join a team if they're not in a team + if (!g_iTeamNumber) + return; + + int iYPos = CLASSMENU_TOPLEFT_BUTTON_Y; + + // Cycle through the rest of the buttons + for (int i = 0; i <= PC_RANDOM; i++) + { + bool bCivilian = (gViewPort->GetValidClasses(g_iTeamNumber) == -1); + + if ( bCivilian ) + { + // If this team can only be civilians, only the civilian button's visible + if (i == 0) + { + m_pButtons[0]->setVisible( true ); + SetActiveInfo( 0 ); + iYPos += CLASSMENU_BUTTON_SIZE_Y + CLASSMENU_BUTTON_SPACER_Y; + } + else + { + m_pButtons[i]->setVisible( false ); + } + } + else + { + if ( m_pButtons[i]->IsNotValid() || i == 0 ) + { + m_pButtons[i]->setVisible( false ); + } + else + { + m_pButtons[i]->setVisible( true ); + m_pButtons[i]->setPos( CLASSMENU_TOPLEFT_BUTTON_X, iYPos ); + iYPos += CLASSMENU_BUTTON_SIZE_Y + CLASSMENU_BUTTON_SPACER_Y; + + // Start with the first option up + if (!m_iCurrentInfo) + SetActiveInfo( i ); + } + } + + // Now count the number of teammembers of this class + int iTotal = 0; + for ( int j = 1; j < MAX_PLAYERS; j++ ) + { + if ( g_PlayerInfoList[j].name == NULL ) + continue; // empty player slot, skip + if ( g_PlayerExtraInfo[j].teamname[0] == 0 ) + continue; // skip over players who are not in a team + if ( g_PlayerInfoList[j].thisplayer ) + continue; // skip this player + if ( g_PlayerExtraInfo[j].teamnumber != g_iTeamNumber ) + continue; // skip over players in other teams + + // If this team is forced to be civilians, just count the number of teammates + if ( g_PlayerExtraInfo[j].playerclass != i && !bCivilian ) + continue; + + iTotal++; + } + + char sz[256]; + sprintf(sz, m_sPlayersOnTeamString, iTotal); + m_pPlayers[i]->setText( sz ); + + // Set the text color to the teamcolor + m_pPlayers[i]->setFgColor( iTeamColors[g_iTeamNumber % iNumberOfTeamColors][0], + iTeamColors[g_iTeamNumber % iNumberOfTeamColors][1], + iTeamColors[g_iTeamNumber % iNumberOfTeamColors][2], + 0 ); + + // set the graphic to be the team pick + for ( int team = 0; team < MAX_TEAMS; team++ ) + { + // unset all the other images + if ( m_pClassImages[team][i] ) + { + m_pClassImages[team][i]->setVisible( false ); + } + + // set the current team image + if ( m_pClassImages[g_iTeamNumber-1][i] != NULL ) + { + m_pClassImages[g_iTeamNumber-1][i]->setVisible( true ); + } + else if ( m_pClassImages[0][i] ) + { + m_pClassImages[0][i]->setVisible( true ); + } + } + } + + // If the player already has a class, make the cancel button visible + if ( g_iPlayerClass ) + { + m_pCancelButton->setPos( CLASSMENU_TOPLEFT_BUTTON_X, iYPos ); + m_pCancelButton->setVisible( true ); + } + else + { + m_pCancelButton->setVisible( false ); + } +} + +//====================================== +// Key inputs for the Class Menu +bool CClassMenuPanel::SlotInput( int iSlot ) +{ + if ( (iSlot < 0) || (iSlot > 9) ) + return false; + if ( !m_pButtons[ iSlot ] ) + return false; + + // Is the button pushable? (0 is special case) + if (iSlot == 0) + { + // Selects Civilian and RandomPC + if ( gViewPort->GetValidClasses(g_iTeamNumber) == -1 ) + { + m_pButtons[ 0 ]->fireActionSignal(); + return true; + } + + // Select RandomPC + iSlot = 10; + } + + if ( !(m_pButtons[ iSlot ]->IsNotValid()) ) + { + m_pButtons[ iSlot ]->fireActionSignal(); + return true; + } + + return false; +} + +//====================================== +// Update the Class menu before opening it +void CClassMenuPanel::Open( void ) +{ + Update(); + CMenuPanel::Open(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called each time a new level is started. +//----------------------------------------------------------------------------- +void CClassMenuPanel::Initialize( void ) +{ + setVisible( false ); + m_pScrollPanel->setScrollValue( 0, 0 ); +} + +//====================================== +// Mouse is over a class button, bring up the class info +void CClassMenuPanel::SetActiveInfo( int iInput ) +{ + // Remove all the Info panels and bring up the specified one + for (int i = 0; i <= PC_RANDOM; i++) + { + m_pButtons[i]->setArmed( false ); + m_pClassInfoPanel[i]->setVisible( false ); + } + + if ( iInput > PC_RANDOM || iInput < 0 ) + iInput = 0; + + m_pButtons[iInput]->setArmed( true ); + m_pClassInfoPanel[iInput]->setVisible( true ); + m_iCurrentInfo = iInput; + + m_pScrollPanel->setScrollValue(0,0); + m_pScrollPanel->validate(); +} + diff --git a/cl_dll/vgui_ConsolePanel.cpp b/cl_dll/vgui_ConsolePanel.cpp new file mode 100644 index 0000000..a8b304c --- /dev/null +++ b/cl_dll/vgui_ConsolePanel.cpp @@ -0,0 +1,101 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include"vgui_ConsolePanel.h" +#include"hud.h" +#include +#include +#include +#include +#include + +using namespace vgui; + + +namespace +{ + +class Handler : public ActionSignal +{ +private: + + ConsolePanel* _consolePanel; + +public: + + Handler(ConsolePanel* consolePanel) + { + _consolePanel=consolePanel; + } + +public: + + virtual void actionPerformed(Panel* panel) + { + _consolePanel->doExecCommand(); + } + +}; + +} + + + +ConsolePanel::ConsolePanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) +{ + setBorder(new EtchedBorder()); + + _textGrid=new TextGrid(80,21,5,5,200,100); + _textGrid->setBorder(new LoweredBorder()); + _textGrid->setParent(this); + + _textEntry=new TextEntry("",5,5,200,20); + _textEntry->setParent(this); + _textEntry->addActionSignal(new Handler(this)); +} + +int ConsolePanel::print(const char* text) +{ + return _textGrid->printf("%s",text); +} + +int ConsolePanel::vprintf(const char* format,va_list argList) +{ + return _textGrid->vprintf(format,argList); +} + +int ConsolePanel::printf(const char* format,...) +{ + va_list argList; + va_start(argList,format); + int ret=vprintf(format,argList); + va_end(argList); + return ret; +} + +void ConsolePanel::doExecCommand() +{ + char buf[2048]; + _textEntry->getText(0,buf,2048); + _textEntry->setText(null,0); + gEngfuncs.pfnClientCmd(buf); +} + +void ConsolePanel::setSize(int wide,int tall) +{ + Panel::setSize(wide,tall); + + getPaintSize(wide,tall); + + _textGrid->setBounds(5,5,wide-10,tall-35); + _textEntry->setBounds(5,tall-25,wide-10,20); +} + + + + + diff --git a/cl_dll/vgui_ConsolePanel.h b/cl_dll/vgui_ConsolePanel.h new file mode 100644 index 0000000..84821f7 --- /dev/null +++ b/cl_dll/vgui_ConsolePanel.h @@ -0,0 +1,38 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef CONSOLEPANEL_H +#define CONSOLEPANEL_H + +#include +#include + +namespace vgui +{ +class TextGrid; +class TextEntry; +} + + +class ConsolePanel : public vgui::Panel +{ +private: + vgui::TextGrid* _textGrid; + vgui::TextEntry* _textEntry; +public: + ConsolePanel(int x,int y,int wide,int tall); +public: + virtual void setSize(int wide,int tall); + virtual int print(const char* text); + virtual int vprintf(const char* format,va_list argList); + virtual int printf(const char* format,...); + virtual void doExecCommand(); +}; + + + +#endif \ No newline at end of file diff --git a/cl_dll/vgui_ControlConfigPanel.cpp b/cl_dll/vgui_ControlConfigPanel.cpp new file mode 100644 index 0000000..0fd5264 --- /dev/null +++ b/cl_dll/vgui_ControlConfigPanel.cpp @@ -0,0 +1,212 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include"vgui_ControlConfigPanel.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace vgui; + +namespace +{ +class FooTablePanel : public TablePanel +{ +private: + Label* _label; + TextEntry* _textEntry; + ControlConfigPanel* _controlConfigPanel; +public: + FooTablePanel(ControlConfigPanel* controlConfigPanel,int x,int y,int wide,int tall,int columnCount) : TablePanel(x,y,wide,tall,columnCount) + { + _controlConfigPanel=controlConfigPanel; + _label=new Label("You are a dumb monkey",0,0,100,20); + _label->setBgColor(Scheme::sc_primary3); + _label->setFgColor(Scheme::sc_primary1); + _label->setFont(Scheme::sf_primary3); + + _textEntry=new TextEntry("",0,0,100,20); + //_textEntry->setFont(Scheme::sf_primary3); + } +public: + virtual int getRowCount() + { + return _controlConfigPanel->GetCVarCount(); + } + virtual int getCellTall(int row) + { + return 12; + } + virtual Panel* getCellRenderer(int column,int row,bool columnSelected,bool rowSelected,bool cellSelected) + { + char cvar[128],desc[128],bind[128],bindAlt[128]; + _controlConfigPanel->GetCVar(row,cvar,128,desc,128); + + if(cellSelected) + { + _label->setBgColor(Scheme::sc_primary1); + _label->setFgColor(Scheme::sc_primary3); + } + else + if(rowSelected) + { + _label->setBgColor(Scheme::sc_primary2); + _label->setFgColor(Scheme::sc_primary1); + } + else + { + _label->setBgColor(Scheme::sc_primary3); + _label->setFgColor(Scheme::sc_primary1); + } + + switch(column) + { + case 0: + { + _label->setText(desc); + _label->setContentAlignment(Label::a_west); + break; + } + case 1: + { + _controlConfigPanel->GetCVarBind(cvar,bind,128,bindAlt,128); + _label->setText(bind); + _label->setContentAlignment(Label::a_center); + break; + } + case 2: + { + _controlConfigPanel->GetCVarBind(cvar,bind,128,bindAlt,128); + _label->setText(bindAlt); + _label->setContentAlignment(Label::a_center); + break; + } + default: + { + _label->setText(""); + break; + } + } + + return _label; + } + virtual Panel* startCellEditing(int column,int row) + { + _textEntry->setText("Goat",strlen("Goat")); + _textEntry->requestFocus(); + return _textEntry; + } +}; +} + +ControlConfigPanel::ControlConfigPanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) +{ + setPaintBorderEnabled(false); + setPaintBackgroundEnabled(false); + setPaintEnabled(false); + + _actionLabel=new Label("Action"); + _actionLabel->setBgColor(Scheme::sc_primary3); + _actionLabel->setFgColor(Scheme::sc_primary3); + + _keyButtonLabel=new Label("Key / Button"); + _keyButtonLabel->setBgColor(Scheme::sc_primary3); + _keyButtonLabel->setFgColor(Scheme::sc_primary3); + + _alternateLabel=new Label("Alternate"); + _alternateLabel->setBgColor(Scheme::sc_primary3); + _alternateLabel->setFgColor(Scheme::sc_primary3); + + _headerPanel=new HeaderPanel(0,0,wide,20); + _headerPanel->setParent(this); + + _headerPanel->addSectionPanel(_actionLabel); + _headerPanel->addSectionPanel(_keyButtonLabel); + _headerPanel->addSectionPanel(_alternateLabel); + + _headerPanel->setSliderPos( 0, wide/2 ); + _headerPanel->setSliderPos( 1, (wide/2) + (wide/4) ); + _headerPanel->setSliderPos( 2, wide ); + + _scrollPanel=new ScrollPanel(0,20,wide,tall-20); + _scrollPanel->setParent(this); + _scrollPanel->setPaintBorderEnabled(false); + _scrollPanel->setPaintBackgroundEnabled(false); + _scrollPanel->setPaintEnabled(false); + _scrollPanel->getClient()->setPaintBorderEnabled(false); + _scrollPanel->getClient()->setPaintBackgroundEnabled(false); + _scrollPanel->getClient()->setPaintEnabled(false); + _scrollPanel->setScrollBarVisible(false,true); + + _tablePanel=new FooTablePanel(this,0,0,_scrollPanel->getClient()->getWide(),800, 3); + _tablePanel->setParent(_scrollPanel->getClient()); + _tablePanel->setHeaderPanel(_headerPanel); + _tablePanel->setBgColor(Color(200,0,0,255)); + _tablePanel->setFgColor(Color(Scheme::sc_primary2)); + _tablePanel->setGridVisible(true,true); + _tablePanel->setGridSize(1,1); +} + +void ControlConfigPanel::AddCVar(const char* cvar,const char* desc) +{ + _cvarDar.addElement(vgui_strdup(cvar)); + _descDar.addElement(vgui_strdup(desc)); +} + +int ControlConfigPanel::GetCVarCount() +{ + return _cvarDar.getCount(); +} + +void ControlConfigPanel::GetCVar(int index,char* cvar,int cvarLen,char* desc,int descLen) +{ + vgui_strcpy(cvar,cvarLen,_cvarDar[index]); + vgui_strcpy(desc,descLen,_descDar[index]); +} + +void ControlConfigPanel::AddCVarFromInputStream(InputStream* is) +{ + if(is==null) + { + return; + } + + DataInputStream dis(is); + + bool success; + + while(1) + { + char buf[256],cvar[128],desc[128]; + dis.readLine(buf,256,success); + if(!success) + { + break; + } + if(sscanf(buf,"\"%[^\"]\" \"%[^\"]\"",cvar,desc)==2) + { + AddCVar(cvar,desc); + } + } +} + +void ControlConfigPanel::GetCVarBind(const char* cvar,char* bind,int bindLen,char* bindAlt,int bindAltLen) +{ + sprintf(bind,"%s : Bind",cvar); + sprintf(bindAlt,"%s : BindAlt",cvar); +} + +void ControlConfigPanel::SetCVarBind(const char* cvar,const char* bind,const char* bindAlt) +{ +} + diff --git a/cl_dll/vgui_ControlConfigPanel.h b/cl_dll/vgui_ControlConfigPanel.h new file mode 100644 index 0000000..15ca3fd --- /dev/null +++ b/cl_dll/vgui_ControlConfigPanel.h @@ -0,0 +1,47 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef CONTROLCONFIGPANEL_H +#define CONTROLCONFIGPANEL_H + +#include +#include + +namespace vgui +{ +class HeaderPanel; +class TablePanel; +class ScrollPanel; +class InputStream; +class Label; +} + +class ControlConfigPanel : public vgui::Panel +{ +private: + vgui::HeaderPanel* _headerPanel; + vgui::TablePanel* _tablePanel; + vgui::ScrollPanel* _scrollPanel; + vgui::Dar _cvarDar; + vgui::Dar _descDar; + vgui::Label* _actionLabel; + vgui::Label* _keyButtonLabel; + vgui::Label* _alternateLabel; +public: + ControlConfigPanel(int x,int y,int wide,int tall); +public: + void AddCVar(const char* cvar,const char* desc); + void AddCVarFromInputStream(vgui::InputStream* is); + int GetCVarCount(); + void GetCVar(int index,char* cvar,int cvarLen,char* desc,int descLen); + void GetCVarBind(const char* cvar,char* bind,int bindLen,char* bindAlt,int bindAltLen); + void SetCVarBind(const char* cvar,const char* bind,const char* bindAlt); +}; + + + +#endif \ No newline at end of file diff --git a/cl_dll/vgui_CustomObjects.cpp b/cl_dll/vgui_CustomObjects.cpp new file mode 100644 index 0000000..441a50e --- /dev/null +++ b/cl_dll/vgui_CustomObjects.cpp @@ -0,0 +1,536 @@ +//=========== (C) Copyright 1996-2002 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: Contains implementation of various VGUI-derived objects +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "VGUI_Font.h" + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "parsemsg.h" + +#include "vgui_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ServerBrowser.h" +#include "..\game_shared\vgui_LoadTGA.h" + +// Arrow filenames +char *sArrowFilenames[] = +{ + "arrowup", + "arrowdn", + "arrowlt", + "arrowrt", +}; + +// Get the name of TGA file, without a gamedir +char *GetTGANameForRes(const char *pszName) +{ + int i; + char sz[256]; + static char gd[256]; + if (ScreenWidth < 640) + i = 320; + else + i = 640; + sprintf(sz, pszName, i); + sprintf(gd, "gfx/vgui/%s.tga", sz); + return gd; +} + +//----------------------------------------------------------------------------- +// Purpose: Loads a .tga file and returns a pointer to the VGUI tga object +//----------------------------------------------------------------------------- +BitmapTGA *LoadTGAForRes( const char* pImageName ) +{ + BitmapTGA *pTGA; + + char sz[256]; + sprintf(sz, "%%d_%s", pImageName); + pTGA = vgui_LoadTGA(GetTGANameForRes(sz)); + + return pTGA; +} + +//=========================================================== +// All TFC Hud buttons are derived from this one. +CommandButton::CommandButton( const char* text,int x,int y,int wide,int tall, bool bNoHighlight) : Button("",x,y,wide,tall) +{ + m_iPlayerClass = 0; + m_bNoHighlight = bNoHighlight; + m_bFlat = false; + Init(); + setText( text ); +} + +CommandButton::CommandButton( int iPlayerClass, const char* text,int x,int y,int wide,int tall, bool bFlat) : Button("",x,y,wide,tall) +{ + m_iPlayerClass = iPlayerClass; + m_bNoHighlight = false; + m_bFlat = bFlat; + Init(); + setText( text ); +} + +CommandButton::CommandButton(const char *text, int x, int y, int wide, int tall, bool bNoHighlight, bool bFlat) : Button("",x,y,wide,tall) +{ + m_iPlayerClass = 0; + m_bFlat = bFlat; + m_bNoHighlight = bNoHighlight; + Init(); + setText( text ); +} + +void CommandButton::Init( void ) +{ + m_pSubMenu = NULL; + m_pSubLabel = NULL; + m_pParentMenu = NULL; + + // Set text color to orange + setFgColor(Scheme::sc_primary1); + + // left align + setContentAlignment( vgui::Label::a_west ); + + // Add the Highlight signal + if (!m_bNoHighlight) + addInputSignal( new CHandler_CommandButtonHighlight(this) ); + + // not bound to any button yet + m_cBoundKey = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Prepends the button text with the current bound key +// if no bound key, then a clear space ' ' instead +//----------------------------------------------------------------------------- +void CommandButton::RecalculateText( void ) +{ + char szBuf[128]; + + if ( m_cBoundKey != 0 ) + { + if ( m_cBoundKey == (char)255 ) + { + strcpy( szBuf, m_sMainText ); + } + else + { + sprintf( szBuf, " %c %s", m_cBoundKey, m_sMainText ); + } + szBuf[MAX_BUTTON_SIZE-1] = 0; + } + else + { + // just draw a space if no key bound + sprintf( szBuf, " %s", m_sMainText ); + szBuf[MAX_BUTTON_SIZE-1] = 0; + } + + Button::setText( szBuf ); +} + +void CommandButton::setText( const char *text ) +{ + strncpy( m_sMainText, text, MAX_BUTTON_SIZE ); + m_sMainText[MAX_BUTTON_SIZE-1] = 0; + + RecalculateText(); +} + +void CommandButton::setBoundKey( char boundKey ) +{ + m_cBoundKey = boundKey; + RecalculateText(); +} + +char CommandButton::getBoundKey( void ) +{ + return m_cBoundKey; +} + +void CommandButton::AddSubMenu( CCommandMenu *pNewMenu ) +{ + m_pSubMenu = pNewMenu; + + // Prevent this button from being pushed + setMouseClickEnabled( MOUSE_LEFT, false ); +} + +void CommandButton::UpdateSubMenus( int iAdjustment ) +{ + if ( m_pSubMenu ) + m_pSubMenu->RecalculatePositions( iAdjustment ); +} + +void CommandButton::paint() +{ + // Make the sub label paint the same as the button + if ( m_pSubLabel ) + { + if ( isSelected() ) + m_pSubLabel->PushDown(); + else + m_pSubLabel->PushUp(); + } + + // draw armed button text in white + if ( isArmed() ) + { + setFgColor( Scheme::sc_secondary2 ); + } + else + { + setFgColor( Scheme::sc_primary1 ); + } + + Button::paint(); +} + +void CommandButton::paintBackground() +{ + if ( m_bFlat ) + { + if ( isArmed() ) + { + // Orange Border + drawSetColor( Scheme::sc_secondary1 ); + drawOutlinedRect(0,0,_size[0],_size[1]); + } + } + else + { + if ( isArmed() ) + { + // Orange highlight background + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect(0,0,_size[0],_size[1]); + } + + // Orange Border + drawSetColor( Scheme::sc_secondary1 ); + drawOutlinedRect(0,0,_size[0],_size[1]); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Highlights the current button, and all it's parent menus +//----------------------------------------------------------------------------- +void CommandButton::cursorEntered( void ) +{ + // unarm all the other buttons in this menu + CCommandMenu *containingMenu = getParentMenu(); + if ( containingMenu ) + { + containingMenu->ClearButtonsOfArmedState(); + + // make all our higher buttons armed + CCommandMenu *pCParent = containingMenu->GetParentMenu(); + if ( pCParent ) + { + CommandButton *pParentButton = pCParent->FindButtonWithSubmenu( containingMenu ); + + pParentButton->cursorEntered(); + } + } + + // arm ourselves + setArmed( true ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CommandButton::cursorExited( void ) +{ + // only clear ourselves if we have do not have a containing menu + // only stay armed if we have a sub menu + // the buttons only unarm themselves when another button is armed instead + if ( !getParentMenu() || !GetSubMenu() ) + { + setArmed( false ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns the command menu that the button is part of, if any +// Output : CCommandMenu * +//----------------------------------------------------------------------------- +CCommandMenu *CommandButton::getParentMenu( void ) +{ + return m_pParentMenu; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the menu that contains this button +// Input : *pParentMenu - +//----------------------------------------------------------------------------- +void CommandButton::setParentMenu( CCommandMenu *pParentMenu ) +{ + m_pParentMenu = pParentMenu; +} + + +//=========================================================== +int ClassButton::IsNotValid() +{ + // If this is the main ChangeClass button, remove it if the player's only able to be civilians + if ( m_iPlayerClass == -1 ) + { + if (gViewPort->GetValidClasses(g_iTeamNumber) == -1) + return true; + + return false; + } + + // Is it an illegal class? + if ((gViewPort->GetValidClasses(0) & sTFValidClassInts[ m_iPlayerClass ]) || (gViewPort->GetValidClasses(g_iTeamNumber) & sTFValidClassInts[ m_iPlayerClass ])) + return true; + + // Only check current class if they've got autokill on + bool bAutoKill = CVAR_GET_FLOAT( "hud_classautokill" ) != 0; + if ( bAutoKill ) + { + // Is it the player's current class? + if ( (gViewPort->IsRandomPC() && m_iPlayerClass == PC_RANDOM) || (!gViewPort->IsRandomPC() && (m_iPlayerClass == g_iPlayerClass)) ) + return true; + } + + return false; +} + +//=========================================================== +// Button with Class image beneath it +CImageLabel::CImageLabel( const char* pImageName,int x,int y ) : Label( "", x,y ) +{ + setContentFitted(true); + m_pTGA = LoadTGAForRes(pImageName); + setImage( m_pTGA ); +} + +CImageLabel::CImageLabel( const char* pImageName,int x,int y,int wide,int tall ) : Label( "", x,y,wide,tall ) +{ + setContentFitted(true); + m_pTGA = LoadTGAForRes(pImageName); + setImage( m_pTGA ); +} + +//=========================================================== +// Image size +int CImageLabel::getImageWide( void ) +{ + if( m_pTGA ) + { + int iXSize, iYSize; + m_pTGA->getSize( iXSize, iYSize ); + return iXSize; + } + else + { + return 1; + } +} + +int CImageLabel::getImageTall( void ) +{ + if( m_pTGA ) + { + int iXSize, iYSize; + m_pTGA->getSize( iXSize, iYSize ); + return iYSize; + } + else + { + return 1; + } +} + +void CImageLabel::LoadImage(const char * pImageName) +{ + if ( m_pTGA ) + delete m_pTGA; + + // Load the Image + m_pTGA = LoadTGAForRes(pImageName); + + if ( m_pTGA == NULL ) + { + // we didn't find a matching image file for this resolution + // try to load file resolution independent + + char sz[256]; + sprintf(sz, "%s/%s",gEngfuncs.pfnGetGameDirectory(), pImageName ); + FileInputStream* fis = new FileInputStream( sz, false ); + m_pTGA = new BitmapTGA(fis,true); + fis->close(); + } + + if ( m_pTGA == NULL ) + return; // unable to load image + + int w,t; + + m_pTGA->getSize( w, t ); + + setSize( XRES (w),YRES (t) ); + setImage( m_pTGA ); +} + +//=========================================================== +// Various overloaded paint functions for Custom VGUI objects +void CCommandMenu::paintBackground() +{ + // Transparent black background + + if ( m_iSpectCmdMenu ) + drawSetColor( 0, 0, 0, 64 ); + else + drawSetColor(Scheme::sc_primary3); + + drawFilledRect(0,0,_size[0],_size[1]); +} + +//================================================================================= +// CUSTOM SCROLLPANEL +//================================================================================= +CTFScrollButton::CTFScrollButton(int iArrow, const char* text,int x,int y,int wide,int tall) : CommandButton(text,x,y,wide,tall) +{ + // Set text color to orange + setFgColor(Scheme::sc_primary1); + + // Load in the arrow + m_pTGA = LoadTGAForRes( sArrowFilenames[iArrow] ); + setImage( m_pTGA ); + + // Highlight signal + InputSignal *pISignal = new CHandler_CommandButtonHighlight(this); + addInputSignal(pISignal); +} + +void CTFScrollButton::paint( void ) +{ + if (!m_pTGA) + return; + + // draw armed button text in white + if ( isArmed() ) + { + m_pTGA->setColor( Color(255,255,255, 0) ); + } + else + { + m_pTGA->setColor( Color(255,255,255, 128) ); + } + + m_pTGA->doPaint(this); +} + +void CTFScrollButton::paintBackground( void ) +{ +/* + if ( isArmed() ) + { + // Orange highlight background + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect(0,0,_size[0],_size[1]); + } + + // Orange Border + drawSetColor( Scheme::sc_secondary1 ); + drawOutlinedRect(0,0,_size[0]-1,_size[1]); +*/ +} + +void CTFSlider::paintBackground( void ) +{ + int wide,tall,nobx,noby; + getPaintSize(wide,tall); + getNobPos(nobx,noby); + + // Border + drawSetColor( Scheme::sc_secondary1 ); + drawOutlinedRect( 0,0,wide,tall ); + + if( isVertical() ) + { + // Nob Fill + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect( 0,nobx,wide,noby ); + + // Nob Outline + drawSetColor( Scheme::sc_primary1 ); + drawOutlinedRect( 0,nobx,wide,noby ); + } + else + { + // Nob Fill + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect( nobx,0,noby,tall ); + + // Nob Outline + drawSetColor( Scheme::sc_primary1 ); + drawOutlinedRect( nobx,0,noby,tall ); + } +} + +CTFScrollPanel::CTFScrollPanel(int x,int y,int wide,int tall) : ScrollPanel(x,y,wide,tall) +{ + ScrollBar *pScrollBar = getVerticalScrollBar(); + pScrollBar->setButton( new CTFScrollButton( ARROW_UP, "", 0,0,16,16 ), 0 ); + pScrollBar->setButton( new CTFScrollButton( ARROW_DOWN, "", 0,0,16,16 ), 1 ); + pScrollBar->setSlider( new CTFSlider(0,wide-1,wide,(tall-(wide*2))+2,true) ); + pScrollBar->setPaintBorderEnabled(false); + pScrollBar->setPaintBackgroundEnabled(false); + pScrollBar->setPaintEnabled(false); + + pScrollBar = getHorizontalScrollBar(); + pScrollBar->setButton( new CTFScrollButton( ARROW_LEFT, "", 0,0,16,16 ), 0 ); + pScrollBar->setButton( new CTFScrollButton( ARROW_RIGHT, "", 0,0,16,16 ), 1 ); + pScrollBar->setSlider( new CTFSlider(tall,0,wide-(tall*2),tall,false) ); + pScrollBar->setPaintBorderEnabled(false); + pScrollBar->setPaintBackgroundEnabled(false); + pScrollBar->setPaintEnabled(false); +} + + +//================================================================================= +// CUSTOM HANDLERS +//================================================================================= +void CHandler_MenuButtonOver::cursorEntered(Panel *panel) +{ + if ( gViewPort && m_pMenuPanel ) + { + m_pMenuPanel->SetActiveInfo( m_iButton ); + } +} + +void CMenuHandler_StringCommandClassSelect::actionPerformed(Panel* panel) +{ + CMenuHandler_StringCommand::actionPerformed( panel ); + + bool bAutoKill = CVAR_GET_FLOAT( "hud_classautokill" ) != 0; + if ( bAutoKill && g_iPlayerClass != 0 ) + gEngfuncs.pfnClientCmd("kill"); +} + diff --git a/cl_dll/vgui_MOTDWindow.cpp b/cl_dll/vgui_MOTDWindow.cpp new file mode 100644 index 0000000..b6f46dd --- /dev/null +++ b/cl_dll/vgui_MOTDWindow.cpp @@ -0,0 +1,154 @@ +//=========== (C) Copyright 1996-2002 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "VGUI_Font.h" +#include "VGUI_ScrollPanel.h" +#include "VGUI_TextImage.h" + +#include + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "const.h" + +#include "vgui_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ServerBrowser.h" + +#define MOTD_TITLE_X XRES(16) +#define MOTD_TITLE_Y YRES(16) + +#define MOTD_WINDOW_X XRES(112) +#define MOTD_WINDOW_Y YRES(80) +#define MOTD_WINDOW_SIZE_X XRES(424) +#define MOTD_WINDOW_SIZE_Y YRES(312) + +//----------------------------------------------------------------------------- +// Purpose: Displays the MOTD and basic server information +//----------------------------------------------------------------------------- +class CMessageWindowPanel : public CMenuPanel +{ +public: + CMessageWindowPanel( const char *szMOTD, const char *szTitle, int iShadeFullScreen, int iRemoveMe, int x, int y, int wide, int tall ); + +private: + CTransparentPanel *m_pBackgroundPanel; + +}; + +//----------------------------------------------------------------------------- +// Purpose: Creates a new CMessageWindowPanel +// Output : CMenuPanel - interface to the panel +//----------------------------------------------------------------------------- +CMenuPanel *CMessageWindowPanel_Create( const char *szMOTD, const char *szTitle, int iShadeFullscreen, int iRemoveMe, int x, int y, int wide, int tall ) +{ + return new CMessageWindowPanel( szMOTD, szTitle, iShadeFullscreen, iRemoveMe, x, y, wide, tall ); +} + +//----------------------------------------------------------------------------- +// Purpose: Constructs a message panel +//----------------------------------------------------------------------------- +CMessageWindowPanel::CMessageWindowPanel( const char *szMOTD, const char *szTitle, int iShadeFullscreen, int iRemoveMe, int x, int y, int wide, int tall ) : CMenuPanel( iShadeFullscreen ? 100 : 255, iRemoveMe, x, y, wide, tall ) +{ + // Get the scheme used for the Titles + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + + // schemes + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle( "Title Font" ); + SchemeHandle_t hMOTDText = pSchemes->getSchemeHandle( "Briefing Text" ); + + // color schemes + int r, g, b, a; + + // Create the window + m_pBackgroundPanel = new CTransparentPanel( iShadeFullscreen ? 255 : 100, MOTD_WINDOW_X, MOTD_WINDOW_Y, MOTD_WINDOW_SIZE_X, MOTD_WINDOW_SIZE_Y ); + m_pBackgroundPanel->setParent( this ); + m_pBackgroundPanel->setBorder( new LineBorder( Color(255 * 0.7,170 * 0.7,0,0)) ); + m_pBackgroundPanel->setVisible( true ); + + int iXSize,iYSize,iXPos,iYPos; + m_pBackgroundPanel->getPos( iXPos,iYPos ); + m_pBackgroundPanel->getSize( iXSize,iYSize ); + + // Create the title + Label *pLabel = new Label( "", iXPos + MOTD_TITLE_X, iYPos + MOTD_TITLE_Y ); + pLabel->setParent( this ); + pLabel->setFont( pSchemes->getFont(hTitleScheme) ); + pLabel->setFont( Scheme::sf_primary1 ); + + pSchemes->getFgColor( hTitleScheme, r, g, b, a ); + pLabel->setFgColor( r, g, b, a ); + pLabel->setFgColor( Scheme::sc_primary1 ); + pSchemes->getBgColor( hTitleScheme, r, g, b, a ); + pLabel->setBgColor( r, g, b, a ); + pLabel->setContentAlignment( vgui::Label::a_west ); + pLabel->setText(szTitle); + + // Create the Scroll panel + ScrollPanel *pScrollPanel = new CTFScrollPanel( iXPos + XRES(16), iYPos + MOTD_TITLE_Y*2 + YRES(16), iXSize - XRES(32), iYSize - (YRES(48) + BUTTON_SIZE_Y*2) ); + pScrollPanel->setParent(this); + + //force the scrollbars on so clientClip will take them in account after the validate + pScrollPanel->setScrollBarAutoVisible(false, false); + pScrollPanel->setScrollBarVisible(true, true); + pScrollPanel->validate(); + + // Create the text panel + TextPanel *pText = new TextPanel( "", 0,0, 64,64); + pText->setParent( pScrollPanel->getClient() ); + + // get the font and colors from the scheme + pText->setFont( pSchemes->getFont(hMOTDText) ); + pSchemes->getFgColor( hMOTDText, r, g, b, a ); + pText->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hMOTDText, r, g, b, a ); + pText->setBgColor( r, g, b, a ); + pText->setText(szMOTD); + + // Get the total size of the MOTD text and resize the text panel + int iScrollSizeX, iScrollSizeY; + + // First, set the size so that the client's wdith is correct at least because the + // width is critical for getting the "wrapped" size right. + // You'll see a horizontal scroll bar if there is a single word that won't wrap in the + // specified width. + pText->getTextImage()->setSize(pScrollPanel->getClientClip()->getWide(), pScrollPanel->getClientClip()->getTall()); + pText->getTextImage()->getTextSizeWrapped( iScrollSizeX, iScrollSizeY ); + + // Now resize the textpanel to fit the scrolled size + pText->setSize( iScrollSizeX , iScrollSizeY ); + + //turn the scrollbars back into automode + pScrollPanel->setScrollBarAutoVisible(true, true); + pScrollPanel->setScrollBarVisible(false, false); + + pScrollPanel->validate(); + + CommandButton *pButton = new CommandButton( CHudTextMessage::BufferedLocaliseTextString( "#Menu_OK" ), iXPos + XRES(16), iYPos + iYSize - YRES(16) - BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_TextWindow(HIDE_TEXTWINDOW)); + pButton->setParent(this); + +} + + + + + + diff --git a/cl_dll/vgui_SchemeManager.cpp b/cl_dll/vgui_SchemeManager.cpp new file mode 100644 index 0000000..ee3b9c1 --- /dev/null +++ b/cl_dll/vgui_SchemeManager.cpp @@ -0,0 +1,272 @@ +//============================================================================= +// new Scheme Manager class +// rewritten by BUzer for Half-life: Paranoia modification +// textscheme.txt files are not used +// +// based on original valve's code +//============================================================================= + +#include "hud.h" +#include "cl_util.h" // buz +#include "vgui_SchemeManager.h" +#include "VGUI_Font.h" +#include "cvardef.h" + +#include + + +typedef struct tgaheader_s +{ + unsigned char IdLength; + unsigned char ColorMap; + unsigned char DataType; + unsigned char unused[5]; + unsigned short OriginX; + unsigned short OriginY; + unsigned short Width; + unsigned short Height; + unsigned char BPP; + unsigned char Description; +} tgaheader_t; + +class CConstFont : public vgui::Font +{ +public: + CConstFont(const char* name,void *pFileData,int fileDataLen, int tall,int wide,float rotation,int weight,bool italic,bool underline,bool strikeout,bool symbol) : + Font(name, pFileData, fileDataLen, tall, wide, rotation, weight, italic, underline, strikeout, symbol) + { + tgaheader_t* head = (tgaheader_t*)pFileData; + m_iCharWidth = head->Width / 256; + } + + virtual void getCharABCwide(int ch,int& a,int& b,int& c) + { + a = 0; + b = m_iCharWidth; + c = 0; + } + +private: + int m_iCharWidth; +}; + + + +class CWidthFont : public vgui::Font +{ +public: + CWidthFont(const char* name,void *pFileData, int fileDataLen, void *pWidthData, int tall,int wide,float rotation,int weight,bool italic,bool underline,bool strikeout,bool symbol) : + Font(name, pFileData, fileDataLen, tall, wide, rotation, weight, italic, underline, strikeout, symbol) + { + memcpy(widths, pWidthData, sizeof(widths)); + } + + virtual void getCharABCwide(int ch,int& a,int& b,int& c) + { + signed char ch2; + unsigned char *ch3; + ch2 = ch; + ch3 = (unsigned char*)&ch2; + + myABC *pABC = &widths[*ch3]; + a = pABC->a; + b = pABC->b; + c = pABC->c; + } + +private: + typedef struct _myABC { + signed char a; + unsigned char b; + signed char c; + } myABC; + + myABC widths[256]; +}; + + +void Scheme_Init() +{ +// g_CV_BitmapFonts = gEngfuncs.pfnRegisterVariable("bitmapfonts", "1", 0); +} + + + +//----------------------------------------------------------------------------- +// Purpose: initializes the scheme manager +// loading the scheme files for the current resolution +// Input : xRes - +// yRes - dimensions of output window +//----------------------------------------------------------------------------- +CSchemeManager::CSchemeManager( int xRes, int yRes ) +{ + // basic setup + m_iNumSchemes = 0; +} + + + +SchemeHandle_t CSchemeManager::LoadScheme( const char *schemeName ) +{ + int fontFileLength = -1; + char fontFilename[512]; + void *pFontData = NULL; + + if (m_iNumSchemes == MAX_SCHEMES) + { + ALERT( at_error, "ERROR: too many schemes!\n" ); + return 0; + } + + strcpy( m_Schemes[m_iNumSchemes].schemeName, schemeName ); + + static int resArray[] = + { + 320, 400, 512, 640, 800, + 1024, 1152, 1280, 1600 + }; + + int resArrayIndex = 0; + int i2 = 0; + while ((resArray[i2] <= ScreenWidth) && (i2 < 9)) + { + resArrayIndex = i2; + i2++; + } + + while(pFontData == NULL && resArrayIndex >= 0) + { + sprintf(fontFilename, "gfx\\vgui\\fonts\\%d_%s.tga", resArray[resArrayIndex], schemeName); + pFontData = gEngfuncs.COM_LoadFile( fontFilename, 5, &fontFileLength ); + // gEngfuncs.Con_Printf("=== trying to load: %s\n", imgName); + resArrayIndex--; + } + + if(!pFontData) + { + ALERT( at_aiconsole, "Failed to load bitmap font for scheme \"%s\"\n", schemeName); + m_Schemes[m_iNumSchemes].font = new vgui::Font("Arial", 20, 0, 0, 700, false, false, false, false); + } + else + { + int wdataFileLength = -1; + void *pWidthData = NULL; + + fontFilename[strlen(fontFilename) - 4] = 0; + strcat(fontFilename, ".chw"); + pWidthData = gEngfuncs.COM_LoadFile( fontFilename, 5, &wdataFileLength ); + fontFilename[strlen(fontFilename) - 4] = 0; + strcat(fontFilename, ".tga"); + if (pWidthData && wdataFileLength == 768) + { + ALERT( at_aiconsole, "Loading bitmap font \"%s\" using width info file\n", fontFilename ); + m_Schemes[m_iNumSchemes].font = new CWidthFont("Arial", pFontData, fontFileLength, pWidthData, + 20, 0, 0, 700, false, false, false, false); + } + else + { + if (pWidthData && wdataFileLength != 768) + ALERT( at_error, "ERROR: Bogus width info file for font \"%s\"\n", fontFilename); + else + ALERT( at_aiconsole, "Loading bitmap font \"%s\"\n", fontFilename ); + + m_Schemes[m_iNumSchemes].font = new CConstFont("Arial", pFontData, fontFileLength, + 20, 0, 0, 700, false, false, false, false); + } + + if (pWidthData) + gEngfuncs.COM_FreeFile(pWidthData); + } + + m_iNumSchemes++; + return (m_iNumSchemes - 1); +} + +//----------------------------------------------------------------------------- +// Purpose: frees all the memory used by the scheme manager +//----------------------------------------------------------------------------- +CSchemeManager::~CSchemeManager() +{ + m_iNumSchemes = 0; +} + +//----------------------------------------------------------------------------- +// Purpose: Finds a scheme in the list, by name +// Input : char *schemeName - string name of the scheme +// Output : SchemeHandle_t handle to the scheme +//----------------------------------------------------------------------------- +SchemeHandle_t CSchemeManager::getSchemeHandle( const char *schemeName ) +{ + // iterate through the list + for ( int i = 0; i < m_iNumSchemes; i++ ) + { + if ( !stricmp(schemeName, m_Schemes[i].schemeName) ) + return i; + } + + return LoadScheme( schemeName ); +} + +//----------------------------------------------------------------------------- +// Purpose: always returns a valid scheme handle +// Input : schemeHandle - +// Output : CScheme +//----------------------------------------------------------------------------- +CSchemeManager::CScheme *CSchemeManager::getSafeScheme( SchemeHandle_t schemeHandle ) +{ + if ( schemeHandle < m_iNumSchemes ) + return &m_Schemes[schemeHandle]; + + return m_Schemes; +} + + +//----------------------------------------------------------------------------- +// Purpose: Returns the schemes pointer to a font +// Input : schemeHandle - +// Output : vgui::Font +//----------------------------------------------------------------------------- +vgui::Font *CSchemeManager::getFont( SchemeHandle_t schemeHandle ) +{ + return getSafeScheme( schemeHandle )->font; +} + + +// buz: stubs +void CSchemeManager::getFgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + r = 255; g = 255; b = 255; a = 0; +} + +void CSchemeManager::getBgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + r = 0; g = 0; b = 0; a = 0; +} + +void CSchemeManager::getFgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + r = 255; g = 255; b = 255; a = 0; +} + +void CSchemeManager::getBgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + r = 0; g = 0; b = 0; a = 0; +} + +void CSchemeManager::getFgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + r = 255; g = 255; b = 255; a = 0; +} + +void CSchemeManager::getBgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + r = 255; g = 255; b = 255; a = 0; +} + +void CSchemeManager::getBorderColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ) +{ + r = 255; g = 255; b = 255; a = 0; +} + + + diff --git a/cl_dll/vgui_SchemeManager.h b/cl_dll/vgui_SchemeManager.h new file mode 100644 index 0000000..aa86bd8 --- /dev/null +++ b/cl_dll/vgui_SchemeManager.h @@ -0,0 +1,79 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include + +#define MAX_SCHEMES 64 + +// handle to an individual scheme +typedef int SchemeHandle_t; + + +// Register console variables, etc.. +void Scheme_Init(); + + +//----------------------------------------------------------------------------- +// Purpose: Handles the loading of text scheme description from disk +// supports different font/color/size schemes at different resolutions +//----------------------------------------------------------------------------- +class CSchemeManager +{ +public: + // initialization + CSchemeManager( int xRes, int yRes ); + virtual ~CSchemeManager(); + + // scheme handling + SchemeHandle_t getSchemeHandle( const char *schemeName ); + + // getting info from schemes + vgui::Font *getFont( SchemeHandle_t schemeHandle ); + void getFgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBgColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getFgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBgArmedColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getFgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBgMousedownColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + void getBorderColor( SchemeHandle_t schemeHandle, int &r, int &g, int &b, int &a ); + +private: + + class CScheme + { + public: + enum { + SCHEME_NAME_LENGTH = 32, + }; + + // name + char schemeName[SCHEME_NAME_LENGTH]; + vgui::Font *font; + + // construction/destruction + CScheme() + { + schemeName[0] = 0; + font = NULL; + } + + ~CScheme() + { + delete font; + } + }; + + CScheme m_Schemes[MAX_SCHEMES]; + int m_iNumSchemes; + + // Resolution we were initted at. + int m_xRes; + CScheme *getSafeScheme( SchemeHandle_t schemeHandle ); + SchemeHandle_t LoadScheme( const char *schemeName ); +}; + + diff --git a/cl_dll/vgui_ScorePanel.cpp b/cl_dll/vgui_ScorePanel.cpp new file mode 100644 index 0000000..bb941af --- /dev/null +++ b/cl_dll/vgui_ScorePanel.cpp @@ -0,0 +1,1095 @@ +//=========== (C) Copyright 1996-2002 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: VGUI scoreboard +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + + +#include + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ScorePanel.h" +#include "..\game_shared\vgui_helpers.h" +#include "..\game_shared\vgui_loadtga.h" +#include "vgui_SpectatorPanel.h" + +hud_player_info_t g_PlayerInfoList[MAX_PLAYERS+1]; // player info from the engine +extra_player_info_t g_PlayerExtraInfo[MAX_PLAYERS+1]; // additional player info sent directly to the client dll +team_info_t g_TeamInfo[MAX_TEAMS+1]; +int g_IsSpectator[MAX_PLAYERS+1]; + +int HUD_IsGame( const char *game ); +int EV_TFC_IsAllyTeam( int iTeam1, int iTeam2 ); + +// Scoreboard dimensions +#define SBOARD_TITLE_SIZE_Y YRES(22) + +#define X_BORDER XRES(4) + +// Column sizes +class SBColumnInfo +{ +public: + char *m_pTitle; // If null, ignore, if starts with #, it's localized, otherwise use the string directly. + int m_Width; // Based on 640 width. Scaled to fit other resolutions. + Label::Alignment m_Alignment; +}; + +// grid size is marked out for 640x480 screen + +SBColumnInfo g_ColumnInfo[NUM_COLUMNS] = +{ + {NULL, 24, Label::a_east}, + {NULL, 140, Label::a_east}, // name + {NULL, 56, Label::a_east}, // class + {"#SCORE", 40, Label::a_east}, + {"#DEATHS", 46, Label::a_east}, + {"#LATENCY", 46, Label::a_east}, + {"#VOICE", 40, Label::a_east}, + {NULL, 2, Label::a_east}, // blank column to take up the slack +}; + + +#define TEAM_NO 0 +#define TEAM_YES 1 +#define TEAM_SPECTATORS 2 +#define TEAM_BLANK 3 + + +//----------------------------------------------------------------------------- +// ScorePanel::HitTestPanel. +//----------------------------------------------------------------------------- + +void ScorePanel::HitTestPanel::internalMousePressed(MouseCode code) +{ + for(int i=0;i<_inputSignalDar.getCount();i++) + { + _inputSignalDar[i]->mousePressed(code,this); + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: Create the ScoreBoard panel +//----------------------------------------------------------------------------- +ScorePanel::ScorePanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) +{ + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle("Scoreboard Title Text"); + SchemeHandle_t hSmallScheme = pSchemes->getSchemeHandle("Scoreboard Small Text"); + Font *tfont = pSchemes->getFont(hTitleScheme); + Font *smallfont = pSchemes->getFont(hSmallScheme); + + setBgColor(0, 0, 0, 96); + m_pCurrentHighlightLabel = NULL; + m_iHighlightRow = -1; + + // Initialize the top title. + m_TitleLabel.setFont(tfont); + m_TitleLabel.setText(""); + m_TitleLabel.setBgColor( 0, 0, 0, 255 ); + m_TitleLabel.setFgColor( Scheme::sc_primary1 ); + m_TitleLabel.setContentAlignment( vgui::Label::a_west ); + + LineBorder *border = new LineBorder(Color(60, 60, 60, 128)); + setBorder(border); + setPaintBorderEnabled(true); + + int xpos = g_ColumnInfo[0].m_Width + 3; + if (ScreenWidth >= 640) + { + // only expand column size for res greater than 640 + xpos = XRES(xpos); + } + m_TitleLabel.setBounds(xpos, 4, wide, SBOARD_TITLE_SIZE_Y); + m_TitleLabel.setContentFitted(false); + m_TitleLabel.setParent(this); + + // Setup the header (labels like "name", "class", etc..). + m_HeaderGrid.SetDimensions(NUM_COLUMNS, 1); + m_HeaderGrid.SetSpacing(0, 0); + + for(int i=0; i < NUM_COLUMNS; i++) + { + if (g_ColumnInfo[i].m_pTitle && g_ColumnInfo[i].m_pTitle[0] == '#') + m_HeaderLabels[i].setText(CHudTextMessage::BufferedLocaliseTextString(g_ColumnInfo[i].m_pTitle)); + else if(g_ColumnInfo[i].m_pTitle) + m_HeaderLabels[i].setText(g_ColumnInfo[i].m_pTitle); + + int xwide = g_ColumnInfo[i].m_Width; + if (ScreenWidth >= 640) + { + xwide = XRES(xwide); + } + else if (ScreenWidth == 400) + { + // hack to make 400x300 resolution scoreboard fit + if (i == 1) + { + // reduces size of player name cell + xwide -= 28; + } + else if (i == 0) + { + xwide -= 8; + } + } + + m_HeaderGrid.SetColumnWidth(i, xwide); + m_HeaderGrid.SetEntry(i, 0, &m_HeaderLabels[i]); + + m_HeaderLabels[i].setBgColor(0,0,0,255); + m_HeaderLabels[i].setFgColor(Scheme::sc_primary1); + m_HeaderLabels[i].setFont(smallfont); + m_HeaderLabels[i].setContentAlignment(g_ColumnInfo[i].m_Alignment); + + int yres = 12; + if (ScreenHeight >= 480) + { + yres = YRES(yres); + } + m_HeaderLabels[i].setSize(50, yres); + } + + // Set the width of the last column to be the remaining space. + int ex, ey, ew, eh; + m_HeaderGrid.GetEntryBox(NUM_COLUMNS - 2, 0, ex, ey, ew, eh); + m_HeaderGrid.SetColumnWidth(NUM_COLUMNS - 1, (wide - X_BORDER) - (ex + ew)); + + m_HeaderGrid.AutoSetRowHeights(); + m_HeaderGrid.setBounds(X_BORDER, SBOARD_TITLE_SIZE_Y, wide - X_BORDER*2, m_HeaderGrid.GetRowHeight(0)); + m_HeaderGrid.setParent(this); + m_HeaderGrid.setBgColor(0,0,0,255); + + + // Now setup the listbox with the actual player data in it. + int headerX, headerY, headerWidth, headerHeight; + m_HeaderGrid.getBounds(headerX, headerY, headerWidth, headerHeight); + m_PlayerList.setBounds(headerX, headerY+headerHeight, headerWidth, tall - headerY - headerHeight - 6); + m_PlayerList.setBgColor(0,0,0,255); + m_PlayerList.setParent(this); + + for(int row=0; row < NUM_ROWS; row++) + { + CGrid *pGridRow = &m_PlayerGrids[row]; + + pGridRow->SetDimensions(NUM_COLUMNS, 1); + + for(int col=0; col < NUM_COLUMNS; col++) + { + m_PlayerEntries[col][row].setContentFitted(false); + m_PlayerEntries[col][row].setRow(row); + m_PlayerEntries[col][row].addInputSignal(this); + pGridRow->SetEntry(col, 0, &m_PlayerEntries[col][row]); + } + + pGridRow->setBgColor(0,0,0,255); +// pGridRow->SetSpacing(2, 0); + pGridRow->SetSpacing(0, 0); + pGridRow->CopyColumnWidths(&m_HeaderGrid); + pGridRow->AutoSetRowHeights(); + pGridRow->setSize(PanelWidth(pGridRow), pGridRow->CalcDrawHeight()); + pGridRow->RepositionContents(); + + m_PlayerList.AddItem(pGridRow); + } + + + // Add the hit test panel. It is invisible and traps mouse clicks so we can go into squelch mode. + m_HitTestPanel.setBgColor(0,0,0,255); + m_HitTestPanel.setParent(this); + m_HitTestPanel.setBounds(0, 0, wide, tall); + m_HitTestPanel.addInputSignal(this); + + m_pCloseButton = new CommandButton( "x", wide-XRES(12 + 4), YRES(2), XRES( 12 ) , YRES( 12 ) ); + m_pCloseButton->setParent( this ); + m_pCloseButton->addActionSignal( new CMenuHandler_StringCommandWatch( "-showscores", true ) ); + m_pCloseButton->setBgColor(0,0,0,255); + m_pCloseButton->setFgColor( 255, 255, 255, 0 ); + m_pCloseButton->setFont(tfont); + m_pCloseButton->setBoundKey( (char)255 ); + m_pCloseButton->setContentAlignment(Label::a_center); + + + Initialize(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Called each time a new level is started. +//----------------------------------------------------------------------------- +void ScorePanel::Initialize( void ) +{ + // Clear out scoreboard data + m_iLastKilledBy = 0; + m_fLastKillTime = 0; + m_iPlayerNum = 0; + m_iNumTeams = 0; + memset( g_PlayerExtraInfo, 0, sizeof g_PlayerExtraInfo ); + memset( g_TeamInfo, 0, sizeof g_TeamInfo ); +} + +bool HACK_GetPlayerUniqueID( int iPlayer, char playerID[16] ) +{ + return !!gEngfuncs.GetPlayerUniqueID( iPlayer, playerID ); +} + +//----------------------------------------------------------------------------- +// Purpose: Recalculate the internal scoreboard data +//----------------------------------------------------------------------------- +void ScorePanel::Update() +{ + // Set the title + if (gViewPort->m_szServerName) + { + char sz[MAX_SERVERNAME_LENGTH + 16]; + sprintf(sz, "%s", gViewPort->m_szServerName ); + m_TitleLabel.setText(sz); + } + + m_iRows = 0; + gViewPort->GetAllPlayersInfo(); + + // Clear out sorts + for (int i = 0; i < NUM_ROWS; i++) + { + m_iSortedRows[i] = 0; + m_iIsATeam[i] = TEAM_NO; + m_bHasBeenSorted[i] = false; + } + + // If it's not teamplay, sort all the players. Otherwise, sort the teams. + if ( !gHUD.m_Teamplay ) + SortPlayers( 0, NULL ); + else + SortTeams(); + + // set scrollbar range + m_PlayerList.SetScrollRange(m_iRows); + + FillGrid(); + +// if ( gViewPort->m_pSpectatorPanel->m_menuVisible ) +// { +// m_pCloseButton->setVisible ( true ); +// } +// else +// { + m_pCloseButton->setVisible ( false ); +// } +} + +//----------------------------------------------------------------------------- +// Purpose: Sort all the teams +//----------------------------------------------------------------------------- +void ScorePanel::SortTeams() +{ + // clear out team scores + for ( int i = 1; i <= m_iNumTeams; i++ ) + { + if ( !g_TeamInfo[i].scores_overriden ) + g_TeamInfo[i].frags = g_TeamInfo[i].deaths = 0; + g_TeamInfo[i].ping = g_TeamInfo[i].packetloss = 0; + } + + // recalc the team scores, then draw them + for ( i = 1; i < MAX_PLAYERS; i++ ) + { + if ( g_PlayerInfoList[i].name == NULL ) + continue; // empty player slot, skip + + if ( g_PlayerExtraInfo[i].teamname[0] == 0 ) + continue; // skip over players who are not in a team + + // find what team this player is in + for ( int j = 1; j <= m_iNumTeams; j++ ) + { + if ( !stricmp( g_PlayerExtraInfo[i].teamname, g_TeamInfo[j].name ) ) + break; + } + if ( j > m_iNumTeams ) // player is not in a team, skip to the next guy + continue; + + if ( !g_TeamInfo[j].scores_overriden ) + { + g_TeamInfo[j].frags += g_PlayerExtraInfo[i].frags; + g_TeamInfo[j].deaths += g_PlayerExtraInfo[i].deaths; + } + + g_TeamInfo[j].ping += g_PlayerInfoList[i].ping; + g_TeamInfo[j].packetloss += g_PlayerInfoList[i].packetloss; + + if ( g_PlayerInfoList[i].thisplayer ) + g_TeamInfo[j].ownteam = TRUE; + else + g_TeamInfo[j].ownteam = FALSE; + + // Set the team's number (used for team colors) + g_TeamInfo[j].teamnumber = g_PlayerExtraInfo[i].teamnumber; + } + + // find team ping/packetloss averages + for ( i = 1; i <= m_iNumTeams; i++ ) + { + g_TeamInfo[i].already_drawn = FALSE; + + if ( g_TeamInfo[i].players > 0 ) + { + g_TeamInfo[i].ping /= g_TeamInfo[i].players; // use the average ping of all the players in the team as the teams ping + g_TeamInfo[i].packetloss /= g_TeamInfo[i].players; // use the average ping of all the players in the team as the teams ping + } + } + + // Draw the teams + while ( 1 ) + { + int highest_frags = -99999; int lowest_deaths = 99999; + int best_team = 0; + + for ( i = 1; i <= m_iNumTeams; i++ ) + { + if ( g_TeamInfo[i].players < 1 ) + continue; + + if ( !g_TeamInfo[i].already_drawn && g_TeamInfo[i].frags >= highest_frags ) + { + if ( g_TeamInfo[i].frags > highest_frags || g_TeamInfo[i].deaths < lowest_deaths ) + { + best_team = i; + lowest_deaths = g_TeamInfo[i].deaths; + highest_frags = g_TeamInfo[i].frags; + } + } + } + + // draw the best team on the scoreboard + if ( !best_team ) + break; + + // Put this team in the sorted list + m_iSortedRows[ m_iRows ] = best_team; + m_iIsATeam[ m_iRows ] = TEAM_YES; + g_TeamInfo[best_team].already_drawn = TRUE; // set the already_drawn to be TRUE, so this team won't get sorted again + m_iRows++; + + // Now sort all the players on this team + SortPlayers( 0, g_TeamInfo[best_team].name ); + } + + // Add all the players who aren't in a team yet into spectators + SortPlayers( TEAM_SPECTATORS, NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: Sort a list of players +//----------------------------------------------------------------------------- +void ScorePanel::SortPlayers( int iTeam, char *team ) +{ + bool bCreatedTeam = false; + + // draw the players, in order, and restricted to team if set + while ( 1 ) + { + // Find the top ranking player + int highest_frags = -99999; int lowest_deaths = 99999; + int best_player; + best_player = 0; + + for ( int i = 1; i < MAX_PLAYERS; i++ ) + { + if ( m_bHasBeenSorted[i] == false && g_PlayerInfoList[i].name && g_PlayerExtraInfo[i].frags >= highest_frags ) + { + cl_entity_t *ent = gEngfuncs.GetEntityByIndex( i ); + + if ( ent && !(team && stricmp(g_PlayerExtraInfo[i].teamname, team)) ) + { + extra_player_info_t *pl_info = &g_PlayerExtraInfo[i]; + if ( pl_info->frags > highest_frags || pl_info->deaths < lowest_deaths ) + { + best_player = i; + lowest_deaths = pl_info->deaths; + highest_frags = pl_info->frags; + } + } + } + } + + if ( !best_player ) + break; + + // If we haven't created the Team yet, do it first + if (!bCreatedTeam && iTeam) + { + m_iIsATeam[ m_iRows ] = iTeam; + m_iRows++; + + bCreatedTeam = true; + } + + // Put this player in the sorted list + m_iSortedRows[ m_iRows ] = best_player; + m_bHasBeenSorted[ best_player ] = true; + m_iRows++; + } + + if (team) + { + m_iIsATeam[m_iRows++] = TEAM_BLANK; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Recalculate the existing teams in the match +//----------------------------------------------------------------------------- +void ScorePanel::RebuildTeams() +{ + // clear out player counts from teams + for ( int i = 1; i <= m_iNumTeams; i++ ) + { + g_TeamInfo[i].players = 0; + } + + // rebuild the team list + gViewPort->GetAllPlayersInfo(); + m_iNumTeams = 0; + for ( i = 1; i < MAX_PLAYERS; i++ ) + { + if ( g_PlayerInfoList[i].name == NULL ) + continue; + + if ( g_PlayerExtraInfo[i].teamname[0] == 0 ) + continue; // skip over players who are not in a team + + // is this player in an existing team? + for ( int j = 1; j <= m_iNumTeams; j++ ) + { + if ( g_TeamInfo[j].name[0] == '\0' ) + break; + + if ( !stricmp( g_PlayerExtraInfo[i].teamname, g_TeamInfo[j].name ) ) + break; + } + + if ( j > m_iNumTeams ) + { // they aren't in a listed team, so make a new one + // search through for an empty team slot + for ( int j = 1; j <= m_iNumTeams; j++ ) + { + if ( g_TeamInfo[j].name[0] == '\0' ) + break; + } + m_iNumTeams = max( j, m_iNumTeams ); + + strncpy( g_TeamInfo[j].name, g_PlayerExtraInfo[i].teamname, MAX_TEAM_NAME ); + g_TeamInfo[j].players = 0; + } + + g_TeamInfo[j].players++; + } + + // clear out any empty teams + for ( i = 1; i <= m_iNumTeams; i++ ) + { + if ( g_TeamInfo[i].players < 1 ) + memset( &g_TeamInfo[i], 0, sizeof(team_info_t) ); + } + + // Update the scoreboard + Update(); +} + + +void ScorePanel::FillGrid() +{ + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + SchemeHandle_t hScheme = pSchemes->getSchemeHandle("Scoreboard Text"); + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle("Scoreboard Title Text"); + SchemeHandle_t hSmallScheme = pSchemes->getSchemeHandle("Scoreboard Small Text"); + + Font *sfont = pSchemes->getFont(hScheme); + Font *tfont = pSchemes->getFont(hTitleScheme); + Font *smallfont = pSchemes->getFont(hSmallScheme); + + // update highlight position + int x, y; + getApp()->getCursorPos(x, y); + cursorMoved(x, y, this); + + // remove highlight row if we're not in squelch mode + if (!GetClientVoiceMgr()->IsInSquelchMode()) + { + m_iHighlightRow = -1; + } + + bool bNextRowIsGap = false; + + for(int row=0; row < NUM_ROWS; row++) + { + CGrid *pGridRow = &m_PlayerGrids[row]; + pGridRow->SetRowUnderline(0, false, 0, 0, 0, 0, 0); + + if(row >= m_iRows) + { + for(int col=0; col < NUM_COLUMNS; col++) + m_PlayerEntries[col][row].setVisible(false); + + continue; + } + + bool bRowIsGap = false; + if (bNextRowIsGap) + { + bNextRowIsGap = false; + bRowIsGap = true; + } + + for(int col=0; col < NUM_COLUMNS; col++) + { + CLabelHeader *pLabel = &m_PlayerEntries[col][row]; + + pLabel->setVisible(true); + pLabel->setText2(""); + pLabel->setImage(NULL); + pLabel->setFont(sfont); + pLabel->setTextOffset(0, 0); + + int rowheight = 13; + if (ScreenHeight > 480) + { + rowheight = YRES(rowheight); + } + else + { + // more tweaking, make sure icons fit at low res + rowheight = 15; + } + pLabel->setSize(pLabel->getWide(), rowheight); + pLabel->setBgColor(0, 0, 0, 255); + + char sz[128]; + hud_player_info_t *pl_info = NULL; + team_info_t *team_info = NULL; + + if (m_iIsATeam[row] == TEAM_BLANK) + { + pLabel->setText(" "); + continue; + } + else if ( m_iIsATeam[row] == TEAM_YES ) + { + // Get the team's data + team_info = &g_TeamInfo[ m_iSortedRows[row] ]; + + // team color text for team names + pLabel->setFgColor( iTeamColors[team_info->teamnumber % iNumberOfTeamColors][0], + iTeamColors[team_info->teamnumber % iNumberOfTeamColors][1], + iTeamColors[team_info->teamnumber % iNumberOfTeamColors][2], + 0 ); + + // different height for team header rows + rowheight = 20; + if (ScreenHeight >= 480) + { + rowheight = YRES(rowheight); + } + pLabel->setSize(pLabel->getWide(), rowheight); + pLabel->setFont(tfont); + + pGridRow->SetRowUnderline( 0, + true, + YRES(3), + iTeamColors[team_info->teamnumber % iNumberOfTeamColors][0], + iTeamColors[team_info->teamnumber % iNumberOfTeamColors][1], + iTeamColors[team_info->teamnumber % iNumberOfTeamColors][2], + 0 ); + } + else if ( m_iIsATeam[row] == TEAM_SPECTATORS ) + { + // grey text for spectators + pLabel->setFgColor(100, 100, 100, 0); + + // different height for team header rows + rowheight = 20; + if (ScreenHeight >= 480) + { + rowheight = YRES(rowheight); + } + pLabel->setSize(pLabel->getWide(), rowheight); + pLabel->setFont(tfont); + + pGridRow->SetRowUnderline(0, true, YRES(3), 100, 100, 100, 0); + } + else + { + // team color text for player names + pLabel->setFgColor( iTeamColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber % iNumberOfTeamColors ][0], + iTeamColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber % iNumberOfTeamColors ][1], + iTeamColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber % iNumberOfTeamColors ][2], + 0 ); + + // Get the player's data + pl_info = &g_PlayerInfoList[ m_iSortedRows[row] ]; + + // Set background color + if ( pl_info->thisplayer ) // if it is their name, draw it a different color + { + // Highlight this player + pLabel->setFgColor(Scheme::sc_white); + pLabel->setBgColor( iTeamColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber % iNumberOfTeamColors ][0], + iTeamColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber % iNumberOfTeamColors ][1], + iTeamColors[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber % iNumberOfTeamColors ][2], + 196 ); + } + else if ( m_iSortedRows[row] == m_iLastKilledBy && m_fLastKillTime && m_fLastKillTime > gHUD.m_flTime ) + { + // Killer's name + pLabel->setBgColor( 255,0,0, 255 - ((float)15 * (float)(m_fLastKillTime - gHUD.m_flTime)) ); + } + } + + // Align + if (col == COLUMN_NAME || col == COLUMN_CLASS) + { + pLabel->setContentAlignment( vgui::Label::a_west ); + } + else if (col == COLUMN_TRACKER) + { + pLabel->setContentAlignment( vgui::Label::a_center ); + } + else + { + pLabel->setContentAlignment( vgui::Label::a_east ); + } + + // Fill out with the correct data + strcpy(sz, ""); + if ( m_iIsATeam[row] ) + { + char sz2[128]; + + switch (col) + { + case COLUMN_NAME: + if ( m_iIsATeam[row] == TEAM_SPECTATORS ) + { + sprintf( sz2, CHudTextMessage::BufferedLocaliseTextString( "#Spectators" ) ); + } + else + { + sprintf( sz2, gViewPort->GetTeamName(team_info->teamnumber) ); + } + + strcpy(sz, sz2); + + // Append the number of players + if ( m_iIsATeam[row] == TEAM_YES ) + { + if (team_info->players == 1) + { + sprintf(sz2, "(%d %s)", team_info->players, CHudTextMessage::BufferedLocaliseTextString( "#Player" ) ); + } + else + { + sprintf(sz2, "(%d %s)", team_info->players, CHudTextMessage::BufferedLocaliseTextString( "#Player_plural" ) ); + } + + pLabel->setText2(sz2); + pLabel->setFont2(smallfont); + } + break; + case COLUMN_VOICE: + break; + case COLUMN_CLASS: + break; + case COLUMN_KILLS: + if ( m_iIsATeam[row] == TEAM_YES ) + sprintf(sz, "%d", team_info->frags ); + break; + case COLUMN_DEATHS: + if ( m_iIsATeam[row] == TEAM_YES ) + sprintf(sz, "%d", team_info->deaths ); + break; + case COLUMN_LATENCY: + if ( m_iIsATeam[row] == TEAM_YES ) + sprintf(sz, "%d", team_info->ping ); + break; + default: + break; + } + } + else + { + bool bShowClass = false; + + switch (col) + { + case COLUMN_NAME: + sprintf(sz, "%s ", pl_info->name); + break; + case COLUMN_VOICE: + sz[0] = 0; + // in HLTV mode allow spectator to turn on/off commentator voice + if (!pl_info->thisplayer || gEngfuncs.IsSpectateOnly() ) + { + GetClientVoiceMgr()->UpdateSpeakerImage(pLabel, m_iSortedRows[row]); + } + break; + case COLUMN_CLASS: + // No class for other team's members (unless allied or spectator) + if ( gViewPort && EV_TFC_IsAllyTeam( g_iTeamNumber, g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber ) ) + bShowClass = true; + // Don't show classes if this client hasnt picked a team yet + if ( g_iTeamNumber == 0 ) + bShowClass = false; + + if (bShowClass) + { + // Only print Civilian if this team are all civilians + bool bNoClass = false; + if ( g_PlayerExtraInfo[ m_iSortedRows[row] ].playerclass == 0 ) + { + if ( gViewPort->GetValidClasses( g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber ) != -1 ) + bNoClass = true; + } + + if (bNoClass) + sprintf(sz, ""); + else + sprintf( sz, "%s", CHudTextMessage::BufferedLocaliseTextString( sLocalisedClasses[ g_PlayerExtraInfo[ m_iSortedRows[row] ].playerclass ] ) ); + } + else + { + strcpy(sz, ""); + } + break; + + case COLUMN_TRACKER: + break; + case COLUMN_KILLS: + sprintf(sz, "%d", g_PlayerExtraInfo[ m_iSortedRows[row] ].frags ); + break; + case COLUMN_DEATHS: + sprintf(sz, "%d", g_PlayerExtraInfo[ m_iSortedRows[row] ].deaths ); + break; + case COLUMN_LATENCY: + sprintf(sz, "%d", g_PlayerInfoList[ m_iSortedRows[row] ].ping ); + break; + default: + break; + } + } + + pLabel->setText(sz); + } + } + + for(row=0; row < NUM_ROWS; row++) + { + CGrid *pGridRow = &m_PlayerGrids[row]; + + pGridRow->AutoSetRowHeights(); + pGridRow->setSize(PanelWidth(pGridRow), pGridRow->CalcDrawHeight()); + pGridRow->RepositionContents(); + } + + // hack, for the thing to resize + m_PlayerList.getSize(x, y); + m_PlayerList.setSize(x, y); +} + + +//----------------------------------------------------------------------------- +// Purpose: Setup highlights for player names in scoreboard +//----------------------------------------------------------------------------- +void ScorePanel::DeathMsg( int killer, int victim ) +{ + // if we were the one killed, or the world killed us, set the scoreboard to indicate suicide + if ( victim == m_iPlayerNum || killer == 0 ) + { + m_iLastKilledBy = killer ? killer : m_iPlayerNum; + m_fLastKillTime = gHUD.m_flTime + 10; // display who we were killed by for 10 seconds + + if ( killer == m_iPlayerNum ) + m_iLastKilledBy = m_iPlayerNum; + } +} + + +void ScorePanel::Open( void ) +{ + RebuildTeams(); + setVisible(true); + m_HitTestPanel.setVisible(true); +} + + +void ScorePanel::mousePressed(MouseCode code, Panel* panel) +{ + if(gHUD.m_iIntermission) + return; + + if (!GetClientVoiceMgr()->IsInSquelchMode()) + { + GetClientVoiceMgr()->StartSquelchMode(); + m_HitTestPanel.setVisible(false); + } + else if (m_iHighlightRow >= 0) + { + // mouse has been pressed, toggle mute state + int iPlayer = m_iSortedRows[m_iHighlightRow]; + if (iPlayer > 0) + { + // print text message + hud_player_info_t *pl_info = &g_PlayerInfoList[iPlayer]; + + if (pl_info && pl_info->name && pl_info->name[0]) + { + char string[256]; + if (GetClientVoiceMgr()->IsPlayerBlocked(iPlayer)) + { + char string1[1024]; + + // remove mute + GetClientVoiceMgr()->SetPlayerBlockedState(iPlayer, false); + + sprintf( string1, CHudTextMessage::BufferedLocaliseTextString( "#Unmuted" ), pl_info->name ); + sprintf( string, "%c** %s\n", HUD_PRINTTALK, string1 ); + + gHUD.m_TextMessage.MsgFunc_TextMsg(NULL, strlen(string)+1, string ); + } + else + { + char string1[1024]; + char string2[1024]; + + // mute the player + GetClientVoiceMgr()->SetPlayerBlockedState(iPlayer, true); + + sprintf( string1, CHudTextMessage::BufferedLocaliseTextString( "#Muted" ), pl_info->name ); + sprintf( string2, CHudTextMessage::BufferedLocaliseTextString( "#No_longer_hear_that_player" ) ); + sprintf( string, "%c** %s %s\n", HUD_PRINTTALK, string1, string2 ); + + gHUD.m_TextMessage.MsgFunc_TextMsg(NULL, strlen(string)+1, string ); + } + } + } + } +} + +void ScorePanel::cursorMoved(int x, int y, Panel *panel) +{ + if (GetClientVoiceMgr()->IsInSquelchMode()) + { + // look for which cell the mouse is currently over + for (int i = 0; i < NUM_ROWS; i++) + { + int row, col; + if (m_PlayerGrids[i].getCellAtPoint(x, y, row, col)) + { + MouseOverCell(i, col); + return; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handles mouse movement over a cell +// Input : row - +// col - +//----------------------------------------------------------------------------- +void ScorePanel::MouseOverCell(int row, int col) +{ + CLabelHeader *label = &m_PlayerEntries[col][row]; + + // clear the previously highlighted label + if (m_pCurrentHighlightLabel != label) + { + m_pCurrentHighlightLabel = NULL; + m_iHighlightRow = -1; + } + if (!label) + return; + + // don't act on teams + if (m_iIsATeam[row] != TEAM_NO) + return; + + // don't act on disconnected players or ourselves + hud_player_info_t *pl_info = &g_PlayerInfoList[ m_iSortedRows[row] ]; + if (!pl_info->name || !pl_info->name[0]) + return; + + if (pl_info->thisplayer && !gEngfuncs.IsSpectateOnly() ) + return; + + // setup the new highlight + m_pCurrentHighlightLabel = label; + m_iHighlightRow = row; +} + +//----------------------------------------------------------------------------- +// Purpose: Label paint functions - take into account current highligh status +//----------------------------------------------------------------------------- +void CLabelHeader::paintBackground() +{ + Color oldBg; + getBgColor(oldBg); + + if (gViewPort->GetScoreBoard()->m_iHighlightRow == _row) + { + setBgColor(134, 91, 19, 0); + } + + Panel::paintBackground(); + + setBgColor(oldBg); +} + + +//----------------------------------------------------------------------------- +// Purpose: Label paint functions - take into account current highligh status +//----------------------------------------------------------------------------- +void CLabelHeader::paint() +{ + Color oldFg; + getFgColor(oldFg); + + if (gViewPort->GetScoreBoard()->m_iHighlightRow == _row) + { + setFgColor(255, 255, 255, 0); + } + + // draw text + int x, y, iwide, itall; + getTextSize(iwide, itall); + calcAlignment(iwide, itall, x, y); + _dualImage->setPos(x, y); + + int x1, y1; + _dualImage->GetImage(1)->getPos(x1, y1); + _dualImage->GetImage(1)->setPos(_gap, y1); + + _dualImage->doPaint(this); + + // get size of the panel and the image + if (_image) + { + Color imgColor; + getFgColor( imgColor ); + if( _useFgColorAsImageColor ) + { + _image->setColor( imgColor ); + } + + _image->getSize(iwide, itall); + calcAlignment(iwide, itall, x, y); + _image->setPos(x, y); + _image->doPaint(this); + } + + setFgColor(oldFg[0], oldFg[1], oldFg[2], oldFg[3]); +} + + +void CLabelHeader::calcAlignment(int iwide, int itall, int &x, int &y) +{ + // calculate alignment ourselves, since vgui is so broken + int wide, tall; + getSize(wide, tall); + + x = 0, y = 0; + + // align left/right + switch (_contentAlignment) + { + // left + case Label::a_northwest: + case Label::a_west: + case Label::a_southwest: + { + x = 0; + break; + } + + // center + case Label::a_north: + case Label::a_center: + case Label::a_south: + { + x = (wide - iwide) / 2; + break; + } + + // right + case Label::a_northeast: + case Label::a_east: + case Label::a_southeast: + { + x = wide - iwide; + break; + } + } + + // top/down + switch (_contentAlignment) + { + // top + case Label::a_northwest: + case Label::a_north: + case Label::a_northeast: + { + y = 0; + break; + } + + // center + case Label::a_west: + case Label::a_center: + case Label::a_east: + { + y = (tall - itall) / 2; + break; + } + + // south + case Label::a_southwest: + case Label::a_south: + case Label::a_southeast: + { + y = tall - itall; + break; + } + } + +// don't clip to Y +// if (y < 0) +// { +// y = 0; +// } + if (x < 0) + { + x = 0; + } + + x += _offset[0]; + y += _offset[1]; +} diff --git a/cl_dll/vgui_ScorePanel.h b/cl_dll/vgui_ScorePanel.h new file mode 100644 index 0000000..f7a8543 --- /dev/null +++ b/cl_dll/vgui_ScorePanel.h @@ -0,0 +1,310 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef SCOREPANEL_H +#define SCOREPANEL_H + +#include +#include +#include +#include +#include +#include +#include "..\game_shared\vgui_listbox.h" + +#include + +#define MAX_SCORES 10 +#define MAX_SCOREBOARD_TEAMS 5 + +// Scoreboard cells +#define COLUMN_TRACKER 0 +#define COLUMN_NAME 1 +#define COLUMN_CLASS 2 +#define COLUMN_KILLS 3 +#define COLUMN_DEATHS 4 +#define COLUMN_LATENCY 5 +#define COLUMN_VOICE 6 +#define COLUMN_BLANK 7 +#define NUM_COLUMNS 8 +#define NUM_ROWS (MAX_PLAYERS + (MAX_SCOREBOARD_TEAMS * 2)) + +using namespace vgui; + +class CTextImage2 : public Image +{ +public: + CTextImage2() + { + _image[0] = new TextImage(""); + _image[1] = new TextImage(""); + } + + ~CTextImage2() + { + delete _image[0]; + delete _image[1]; + } + + TextImage *GetImage(int image) + { + return _image[image]; + } + + void getSize(int &wide, int &tall) + { + int w1, w2, t1, t2; + _image[0]->getTextSize(w1, t1); + _image[1]->getTextSize(w2, t2); + + wide = w1 + w2; + tall = max(t1, t2); + setSize(wide, tall); + } + + void doPaint(Panel *panel) + { + _image[0]->doPaint(panel); + _image[1]->doPaint(panel); + } + + void setPos(int x, int y) + { + _image[0]->setPos(x, y); + + int swide, stall; + _image[0]->getSize(swide, stall); + + int wide, tall; + _image[1]->getSize(wide, tall); + _image[1]->setPos(x + wide, y + (stall * 0.9) - tall); + } + + void setColor(Color color) + { + _image[0]->setColor(color); + } + + void setColor2(Color color) + { + _image[1]->setColor(color); + } + +private: + TextImage *_image[2]; + +}; + +//----------------------------------------------------------------------------- +// Purpose: Custom label for cells in the Scoreboard's Table Header +//----------------------------------------------------------------------------- +class CLabelHeader : public Label +{ +public: + CLabelHeader() : Label("") + { + _dualImage = new CTextImage2(); + _dualImage->setColor2(Color(255, 170, 0, 0)); + _row = -2; + _useFgColorAsImageColor = true; + _offset[0] = 0; + _offset[1] = 0; + } + + ~CLabelHeader() + { + delete _dualImage; + } + + void setRow(int row) + { + _row = row; + } + + void setFgColorAsImageColor(bool state) + { + _useFgColorAsImageColor = state; + } + + virtual void setText(int textBufferLen, const char* text) + { + _dualImage->GetImage(0)->setText(text); + + // calculate the text size + Font *font = _dualImage->GetImage(0)->getFont(); + _gap = 0; + for (const char *ch = text; *ch != 0; ch++) + { + int a, b, c; + font->getCharABCwide(*ch, a, b, c); + _gap += (a + b + c); + } + + _gap += XRES(5); + } + + virtual void setText(const char* text) + { + // strip any non-alnum characters from the end + char buf[512]; + strcpy(buf, text); + + int len = strlen(buf); + while (len && isspace(buf[--len])) + { + buf[len] = 0; + } + + CLabelHeader::setText(0, buf); + } + + void setText2(const char *text) + { + _dualImage->GetImage(1)->setText(text); + } + + void getTextSize(int &wide, int &tall) + { + _dualImage->getSize(wide, tall); + } + + void setFgColor(int r,int g,int b,int a) + { + Label::setFgColor(r,g,b,a); + Color color(r,g,b,a); + _dualImage->setColor(color); + _dualImage->setColor2(color); + repaint(); + } + + void setFgColor(Scheme::SchemeColor sc) + { + int r, g, b, a; + Label::setFgColor(sc); + Label::getFgColor( r, g, b, a ); + + // Call the r,g,b,a version so it sets the color in the dualImage.. + setFgColor( r, g, b, a ); + } + + void setFont(Font *font) + { + _dualImage->GetImage(0)->setFont(font); + } + + void setFont2(Font *font) + { + _dualImage->GetImage(1)->setFont(font); + } + + // this adjust the absolute position of the text after alignment is calculated + void setTextOffset(int x, int y) + { + _offset[0] = x; + _offset[1] = y; + } + + void paint(); + void paintBackground(); + void calcAlignment(int iwide, int itall, int &x, int &y); + +private: + CTextImage2 *_dualImage; + int _row; + int _gap; + int _offset[2]; + bool _useFgColorAsImageColor; +}; + +class ScoreTablePanel; + +#include "..\game_shared\vgui_grid.h" +#include "..\game_shared\vgui_defaultinputsignal.h" + +//----------------------------------------------------------------------------- +// Purpose: Scoreboard back panel +//----------------------------------------------------------------------------- +class ScorePanel : public Panel, public vgui::CDefaultInputSignal +{ +private: + // Default panel implementation doesn't forward mouse messages when there is no cursor and we need them. + class HitTestPanel : public Panel + { + public: + virtual void internalMousePressed(MouseCode code); + }; + + +private: + + Label m_TitleLabel; + + // Here is how these controls are arranged hierarchically. + // m_HeaderGrid + // m_HeaderLabels + + // m_PlayerGridScroll + // m_PlayerGrid + // m_PlayerEntries + + CGrid m_HeaderGrid; + CLabelHeader m_HeaderLabels[NUM_COLUMNS]; // Labels above the + CLabelHeader *m_pCurrentHighlightLabel; + int m_iHighlightRow; + + vgui::CListBox m_PlayerList; + CGrid m_PlayerGrids[NUM_ROWS]; // The grid with player and team info. + CLabelHeader m_PlayerEntries[NUM_COLUMNS][NUM_ROWS]; // Labels for the grid entries. + + ScorePanel::HitTestPanel m_HitTestPanel; + CommandButton *m_pCloseButton; + CLabelHeader* GetPlayerEntry(int x, int y) {return &m_PlayerEntries[x][y];} + +public: + + int m_iNumTeams; + int m_iPlayerNum; + int m_iShowscoresHeld; + + int m_iRows; + int m_iSortedRows[NUM_ROWS]; + int m_iIsATeam[NUM_ROWS]; + bool m_bHasBeenSorted[MAX_PLAYERS]; + int m_iLastKilledBy; + int m_fLastKillTime; + + +public: + + ScorePanel(int x,int y,int wide,int tall); + + void Update( void ); + + void SortTeams( void ); + void SortPlayers( int iTeam, char *team ); + void RebuildTeams( void ); + + void FillGrid(); + + void DeathMsg( int killer, int victim ); + + void Initialize( void ); + + void Open( void ); + + void MouseOverCell(int row, int col); + +// InputSignal overrides. +public: + + virtual void mousePressed(MouseCode code, Panel* panel); + virtual void cursorMoved(int x, int y, Panel *panel); + + friend CLabelHeader; +}; + +#endif diff --git a/cl_dll/vgui_ServerBrowser.cpp b/cl_dll/vgui_ServerBrowser.cpp new file mode 100644 index 0000000..6e90259 --- /dev/null +++ b/cl_dll/vgui_ServerBrowser.cpp @@ -0,0 +1,623 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include +#include +#include +#include +#include + +#include "hud.h" +#include "cl_util.h" +#include "hud_servers.h" +#include "net_api.h" + +#include "vgui_TeamFortressViewport.h" +#include "vgui_ServerBrowser.h" + +using namespace vgui; + +namespace +{ + +#define MAX_SB_ROWS 24 + +#define NUM_COLUMNS 5 + +#define HEADER_SIZE_Y YRES(18) + +// Column sizes +#define CSIZE_ADDRESS XRES(200) +#define CSIZE_SERVER XRES(400) +#define CSIZE_MAP XRES(500) +#define CSIZE_CURRENT XRES(570) +#define CSIZE_PING XRES(640) + +#define CELL_HEIGHT YRES(15) + +class ServerBrowserTablePanel; + +class CBrowser_InputSignal : public InputSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; +public: + CBrowser_InputSignal( ServerBrowserTablePanel *pBrowser ) + { + m_pBrowser = pBrowser; + } + + virtual void cursorMoved(int x,int y,Panel* panel) {}; + virtual void cursorEntered(Panel* panel){}; + virtual void cursorExited(Panel* Panel) {}; + + virtual void mousePressed(MouseCode code,Panel* panel); + + virtual void mouseDoublePressed(MouseCode code,Panel* panel); + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; +}; + +class ServerBrowserTablePanel : public TablePanel +{ +private: + Label *m_pLabel; + int m_nMouseOverRow; + +public: + + ServerBrowserTablePanel( int x,int y,int wide,int tall,int columnCount) : TablePanel( x,y,wide,tall,columnCount) + { + m_pLabel = new Label( "", 0, 0 /*,wide, tall*/ ); + + m_nMouseOverRow = 0; + } + +public: + void setMouseOverRow( int row ) + { + m_nMouseOverRow = row; + } + + void DoSort( char *sortkey ) + { + // Request server list and refresh servers... + SortServers( sortkey ); + } + + void DoRefresh( void ) + { + // Request server list and refresh servers... + ServersList(); + BroadcastServersList( 0 ); + } + + void DoBroadcastRefresh( void ) + { + // Request server list and refresh servers... + BroadcastServersList( 1 ); + } + + void DoStop( void ) + { + // Stop requesting + ServersCancel(); + } + + void DoCancel( void ) + { + ClientCmd( "togglebrowser\n" ); + } + + void DoConnect( void ) + { + const char *info; + const char *address; + char sz[ 256 ]; + + info = ServersGetInfo( m_nMouseOverRow ); + if ( !info ) + return; + + address = gEngfuncs.pNetAPI->ValueForKey( info, "address" ); + //gEngfuncs.Con_Printf( "Connecting to %s\n", address ); + + sprintf( sz, "connect %s\n", address ); + + ClientCmd( sz ); + + DoCancel(); + } + + void DoPing( void ) + { + ServerPing( 0 ); + ServerRules( 0 ); + ServerPlayers( 0 ); + } + + virtual int getRowCount() + { + int rowcount; + int height, width; + + getSize( width, height ); + + // Space for buttons + height -= YRES(20); + height = max( 0, height ); + + rowcount = height / CELL_HEIGHT; + + return rowcount; + } + + virtual int getCellTall(int row) + { + return CELL_HEIGHT - 2; + } + + virtual Panel* getCellRenderer(int column,int row,bool columnSelected,bool rowSelected,bool cellSelected) + { + const char *info; + const char *val, *val2; + char sz[ 32 ]; + + info = ServersGetInfo( row ); + + if ( row == m_nMouseOverRow ) + { + m_pLabel->setFgColor( 200, 240, 63, 100 ); + } + else + { + m_pLabel->setFgColor( 255, 255, 255, 0 ); + } + m_pLabel->setBgColor( 0, 0, 0, 200 ); + m_pLabel->setContentAlignment( vgui::Label::a_west ); + m_pLabel->setFont( Scheme::sf_primary2 ); + + if ( info ) + { + // Fill out with the correct data + switch ( column ) + { + case 0: + val = gEngfuncs.pNetAPI->ValueForKey( info, "address" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Name; + m_pLabel->setText( sz ); + } + break; + case 1: + val = gEngfuncs.pNetAPI->ValueForKey( info, "hostname" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Map; + m_pLabel->setText( sz ); + } + break; + case 2: + val = gEngfuncs.pNetAPI->ValueForKey( info, "map" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Name; + m_pLabel->setText( sz ); + } + break; + case 3: + val = gEngfuncs.pNetAPI->ValueForKey( info, "current" ); + val2 = gEngfuncs.pNetAPI->ValueForKey( info, "max" ); + if ( val && val2 ) + { + sprintf( sz, "%s/%s", val, val2 ); + sz[ 31 ] = '\0'; + // Server Map; + m_pLabel->setText( sz ); + } + break; + case 4: + val = gEngfuncs.pNetAPI->ValueForKey( info, "ping" ); + if ( val ) + { + strncpy( sz, val, 31 ); + sz[ 31 ] = '\0'; + // Server Name; + m_pLabel->setText( sz ); + } + break; + default: + break; + } + } + else + { + if ( !row && !column ) + { + if ( ServersIsQuerying() ) + { + m_pLabel->setText( "Waiting for servers to respond..." ); + } + else + { + m_pLabel->setText( "Press 'Refresh' to search for servers..." ); + } + } + else + { + m_pLabel->setText( "" ); + } + } + + return m_pLabel; + } + + virtual Panel* startCellEditing(int column,int row) + { + return null; + } + +}; + +class ConnectHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + ConnectHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoConnect(); + } +}; + +class RefreshHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + RefreshHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoRefresh(); + } +}; + +class BroadcastRefreshHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + BroadcastRefreshHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoBroadcastRefresh(); + } +}; + +class StopHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + StopHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoStop(); + } +}; + +class CancelHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + CancelHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoCancel(); + } +}; + +class PingHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + PingHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoPing(); + } +}; + +class SortHandler : public ActionSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + +public: + SortHandler( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + } + + virtual void actionPerformed( Panel *panel ) + { + m_pBrowser->DoSort( "map" ); + } +}; + +} + +class LabelSortInputHandler : public InputSignal +{ +private: + ServerBrowserTablePanel *m_pBrowser; + char m_szSortKey[ 64 ]; + +public: + LabelSortInputHandler( ServerBrowserTablePanel *pBrowser, char *name ) + { + m_pBrowser = pBrowser; + strcpy( m_szSortKey, name ); + } + + virtual void cursorMoved(int x,int y,Panel* panel) {}; + virtual void cursorEntered(Panel* panel){}; + virtual void cursorExited(Panel* Panel) {}; + + virtual void mousePressed(MouseCode code,Panel* panel) + { + m_pBrowser->DoSort( m_szSortKey ); + } + + virtual void mouseDoublePressed(MouseCode code,Panel* panel) + { + m_pBrowser->DoSort( m_szSortKey ); + } + + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; +}; + +class CSBLabel : public Label +{ + +private: + char m_szSortKey[ 64 ]; + ServerBrowserTablePanel *m_pBrowser; + +public: + CSBLabel( char *name, char *sortkey ) : Label( name ) + { + m_pBrowser = NULL; + + strcpy( m_szSortKey, sortkey ); + + int label_bg_r = 120, + label_bg_g = 75, + label_bg_b = 32, + label_bg_a = 200; + + int label_fg_r = 255, + label_fg_g = 0, + label_fg_b = 0, + label_fg_a = 0; + + setContentAlignment( vgui::Label::a_west ); + setFgColor( label_fg_r, label_fg_g, label_fg_b, label_fg_a ); + setBgColor( label_bg_r, label_bg_g, label_bg_b, label_bg_a ); + setFont( Scheme::sf_primary2 ); + + } + + void setTable( ServerBrowserTablePanel *browser ) + { + m_pBrowser = browser; + + addInputSignal( new LabelSortInputHandler( (ServerBrowserTablePanel * )m_pBrowser, m_szSortKey ) ); + } +}; + +ServerBrowser::ServerBrowser(int x,int y,int wide,int tall) : CTransparentPanel( 100, x,y,wide,tall ) +{ + int i; + + _headerPanel = new HeaderPanel(0,0,wide,HEADER_SIZE_Y); + _headerPanel->setParent(this); + _headerPanel->setFgColor( 100,100,100, 100 ); + _headerPanel->setBgColor( 0, 0, 0, 100 ); + + CSBLabel *pLabel[5]; + + pLabel[0] = new CSBLabel( "Address", "address" ); + pLabel[1] = new CSBLabel( "Server", "hostname" ); + pLabel[2] = new CSBLabel( "Map", "map" ); + pLabel[3] = new CSBLabel( "Current", "current" ); + pLabel[4] = new CSBLabel( "Latency", "ping" ); + + for ( i = 0; i < 5; i++ ) + { + _headerPanel->addSectionPanel( pLabel[i] ); + } + + // _headerPanel->setFont( Scheme::sf_primary1 ); + + _headerPanel->setSliderPos( 0, CSIZE_ADDRESS ); + _headerPanel->setSliderPos( 1, CSIZE_SERVER ); + _headerPanel->setSliderPos( 2, CSIZE_MAP ); + _headerPanel->setSliderPos( 3, CSIZE_CURRENT ); + _headerPanel->setSliderPos( 4, CSIZE_PING ); + + _tablePanel = new ServerBrowserTablePanel( 0, HEADER_SIZE_Y, wide, tall - HEADER_SIZE_Y, NUM_COLUMNS ); + _tablePanel->setParent(this); + _tablePanel->setHeaderPanel(_headerPanel); + _tablePanel->setFgColor( 100,100,100, 100 ); + _tablePanel->setBgColor( 0, 0, 0, 100 ); + + _tablePanel->addInputSignal( new CBrowser_InputSignal( (ServerBrowserTablePanel *)_tablePanel ) ); + + for ( i = 0; i < 5; i++ ) + { + pLabel[i]->setTable( (ServerBrowserTablePanel * )_tablePanel ); + } + + int bw = 80, bh = 15; + int by = tall - HEADER_SIZE_Y; + + int btnx = 10; + + _connectButton = new CommandButton( "Connect", btnx, by, bw, bh ); + _connectButton->setParent( this ); + _connectButton->addActionSignal( new ConnectHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + btnx += bw; + + _refreshButton = new CommandButton( "Refresh", btnx, by, bw, bh ); + _refreshButton->setParent( this ); + _refreshButton->addActionSignal( new RefreshHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + /* + btnx += bw; + + _broadcastRefreshButton = new CommandButton( "LAN", btnx, by, bw, bh ); + _broadcastRefreshButton->setParent( this ); + _broadcastRefreshButton->addActionSignal( new BroadcastRefreshHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + */ + + btnx += bw; + + _stopButton = new CommandButton( "Stop", btnx, by, bw, bh ); + _stopButton->setParent( this ); + _stopButton->addActionSignal( new StopHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + /* + btnx += bw; + + _pingButton = new CommandButton( "Test", btnx, by, bw, bh ); + _pingButton->setParent( this ); + _pingButton->addActionSignal( new PingHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + btnx += bw; + + _sortButton = new CommandButton( "Sort", btnx, by, bw, bh ); + _sortButton->setParent( this ); + _sortButton->addActionSignal( new SortHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + */ + + btnx += bw; + + _cancelButton = new CommandButton( "Close", btnx, by, bw, bh ); + _cancelButton->setParent( this ); + _cancelButton->addActionSignal( new CancelHandler( (ServerBrowserTablePanel * )_tablePanel ) ); + + setPaintBorderEnabled(false); + setPaintBackgroundEnabled(false); + setPaintEnabled(false); + +} + +void ServerBrowser::setSize(int wide,int tall) +{ + Panel::setSize(wide,tall); + + _headerPanel->setBounds(0,0,wide,HEADER_SIZE_Y); + _tablePanel->setBounds(0,HEADER_SIZE_Y,wide,tall - HEADER_SIZE_Y); + + _connectButton->setBounds( 5, tall - HEADER_SIZE_Y, 75, 15 ); + _refreshButton->setBounds( 85, tall - HEADER_SIZE_Y, 75, 15 ); + /* + _broadcastRefreshButton->setBounds( 165, tall - HEADER_SIZE_Y, 75, 15 ); + */ + _stopButton->setBounds( 165, tall - HEADER_SIZE_Y, 75, 15 ); + /* + _pingButton->setBounds( 325, tall - HEADER_SIZE_Y, 75, 15 ); + */ + _cancelButton->setBounds( 245, tall - HEADER_SIZE_Y, 75, 15 ); +} + +void CBrowser_InputSignal::mousePressed(MouseCode code,Panel* panel) +{ + int x, y; + int therow = 2; + + if ( code != MOUSE_LEFT ) + return; + + panel->getApp()->getCursorPos(x,y); + panel->screenToLocal( x, y ); + + therow = y / CELL_HEIGHT; + + // Figure out which row it's on + m_pBrowser->setMouseOverRow( therow ); +} + +void CBrowser_InputSignal::mouseDoublePressed(MouseCode code,Panel* panel) +{ + int x, y; + int therow = 2; + + if ( code != MOUSE_LEFT ) + return; + + panel->getApp()->getCursorPos(x,y); + panel->screenToLocal( x, y ); + + therow = y / CELL_HEIGHT; + + // Figure out which row it's on + m_pBrowser->setMouseOverRow( therow ); + m_pBrowser->DoConnect(); +} diff --git a/cl_dll/vgui_ServerBrowser.h b/cl_dll/vgui_ServerBrowser.h new file mode 100644 index 0000000..43da4f4 --- /dev/null +++ b/cl_dll/vgui_ServerBrowser.h @@ -0,0 +1,50 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef ServerBrowser_H +#define ServerBrowser_H + +#include + +namespace vgui +{ +class Button; +class TablePanel; +class HeaderPanel; +} + +class CTransparentPanel; +class CommandButton; + +// Scoreboard positions +#define SB_X_INDENT (20 * ((float)ScreenHeight / 640)) +#define SB_Y_INDENT (20 * ((float)ScreenHeight / 480)) + +class ServerBrowser : public CTransparentPanel +{ +private: + HeaderPanel * _headerPanel; + TablePanel* _tablePanel; + + CommandButton* _connectButton; + CommandButton* _refreshButton; + CommandButton* _broadcastRefreshButton; + CommandButton* _stopButton; + CommandButton* _sortButton; + CommandButton* _cancelButton; + + CommandButton* _pingButton; + +public: + ServerBrowser(int x,int y,int wide,int tall); +public: + virtual void setSize(int wide,int tall); +}; + + + +#endif \ No newline at end of file diff --git a/cl_dll/vgui_SpectatorPanel.cpp b/cl_dll/vgui_SpectatorPanel.cpp new file mode 100644 index 0000000..582a319 --- /dev/null +++ b/cl_dll/vgui_SpectatorPanel.cpp @@ -0,0 +1,383 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// vgui_SpectatorPanel.cpp: implementation of the SpectatorPanel class. +// +////////////////////////////////////////////////////////////////////// + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "pm_shared.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_SpectatorPanel.h" +#include "vgui_scorepanel.h" + +#define PANEL_HEIGHT 32 + + +#define BANNER_WIDTH 256 +#define BANNER_HEIGHT 64 + + +#define OPTIONS_BUTTON_X 96 +#define CAMOPTIONS_BUTTON_X 200 + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +SpectatorPanel::SpectatorPanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) +{ +} + +SpectatorPanel::~SpectatorPanel() +{ + +} + +void SpectatorPanel::ActionSignal(int cmd) +{ + switch (cmd) + { + case SPECTATOR_PANEL_CMD_NONE : break; + + case SPECTATOR_PANEL_CMD_OPTIONS : gViewPort->ShowCommandMenu( gViewPort->m_SpectatorOptionsMenu ); + break; + + case SPECTATOR_PANEL_CMD_NEXTPLAYER : gHUD.m_Spectator.FindNextPlayer(true); + break; + + case SPECTATOR_PANEL_CMD_PREVPLAYER : gHUD.m_Spectator.FindNextPlayer(false); + break; + + case SPECTATOR_PANEL_CMD_HIDEMENU : ShowMenu(false); + break; + + case SPECTATOR_PANEL_CMD_CAMERA : gViewPort->ShowCommandMenu( gViewPort->m_SpectatorCameraMenu ); + break; + + case SPECTATOR_PANEL_CMD_TOGGLE_INSET : gHUD.m_Spectator.SetModes( -1, + gHUD.m_Spectator.ToggleInset(false) ); + break; + + + default : gEngfuncs.Con_DPrintf("Unknown SpectatorPanel ActionSingal %i.\n",cmd); break; + } + +} + + +void SpectatorPanel::Initialize() +{ + int x,y,wide,tall; + + getBounds(x,y,wide,tall); + + CSchemeManager * pSchemes = gViewPort->GetSchemeManager(); + + SchemeHandle_t hSmallScheme = pSchemes->getSchemeHandle( "Team Info Text" ); + + m_TopBorder = new CTransparentPanel(64, 0, 0, ScreenWidth, YRES(PANEL_HEIGHT)); + m_TopBorder->setParent(this); + + m_BottomBorder = new CTransparentPanel(64, 0, ScreenHeight - YRES(32), ScreenWidth, YRES(PANEL_HEIGHT)); + m_BottomBorder->setParent(this); + + setPaintBackgroundEnabled(false); + + m_ExtraInfo = new Label( "Extra Info", 0, 0, wide, YRES(PANEL_HEIGHT) ); + m_ExtraInfo->setParent(m_TopBorder); + m_ExtraInfo->setFont( pSchemes->getFont(hSmallScheme) ); + + m_ExtraInfo->setPaintBackgroundEnabled(false); + m_ExtraInfo->setFgColor( 143, 143, 54, 0 ); + m_ExtraInfo->setContentAlignment( vgui::Label::a_west ); + + + + m_TimerImage = new CImageLabel( "timer", 0, 0, 14, 14 ); + m_TimerImage->setParent(m_TopBorder); + + m_TopBanner = new CImageLabel( "banner", 0, 0, XRES(BANNER_WIDTH), YRES(BANNER_HEIGHT) ); + m_TopBanner->setParent(this); + + m_CurrentTime = new Label( "00:00", 0, 0, wide, YRES(PANEL_HEIGHT) ); + m_CurrentTime->setParent(m_TopBorder); + m_CurrentTime->setFont( pSchemes->getFont(hSmallScheme) ); + m_CurrentTime->setPaintBackgroundEnabled(false); + m_CurrentTime->setFgColor( 143, 143, 54, 0 ); + m_CurrentTime->setContentAlignment( vgui::Label::a_west ); + + m_Separator = new Panel( 0, 0, XRES( 64 ), YRES( 96 )); + m_Separator->setParent( m_TopBorder ); + m_Separator->setFgColor( 59, 58, 34, 48 ); + m_Separator->setBgColor( 59, 58, 34, 48 ); + + for ( int j= 0; j < TEAM_NUMBER; j++ ) + { + m_TeamScores[j] = new Label( " ", 0, 0, wide, YRES(PANEL_HEIGHT) ); + m_TeamScores[j]->setParent( m_TopBorder ); + m_TeamScores[j]->setFont( pSchemes->getFont(hSmallScheme) ); + m_TeamScores[j]->setPaintBackgroundEnabled(false); + m_TeamScores[j]->setFgColor( 143, 143, 54, 0 ); + m_TeamScores[j]->setContentAlignment( vgui::Label::a_west ); + m_TeamScores[j]->setVisible ( false ); + } + + + // Initialize command buttons. + m_OptionButton = new ColorButton( CHudTextMessage::BufferedLocaliseTextString( "#SPECT_OPTIONS" ), XRES(15), YRES(6), XRES(OPTIONS_BUTTON_X), YRES(20), false, false ); + m_OptionButton->setParent( m_BottomBorder ); + m_OptionButton->setContentAlignment( vgui::Label::a_center ); + m_OptionButton->setBoundKey( (char)255 ); // special no bound to avoid leading spaces in name + m_OptionButton->addActionSignal( new CSpectatorHandler_Command(this,SPECTATOR_PANEL_CMD_OPTIONS) ); + m_OptionButton->setUnArmedBorderColor ( 59, 58, 34, 48 ); + m_OptionButton->setArmedBorderColor ( 194, 202, 54, 0 ); + m_OptionButton->setUnArmedColor ( 143, 143, 54, 0 ); + m_OptionButton->setArmedColor ( 194, 202, 54, 0 ); + + m_CamButton = new ColorButton( CHudTextMessage::BufferedLocaliseTextString( "#CAM_OPTIONS" ), ScreenWidth - ( XRES ( CAMOPTIONS_BUTTON_X ) + 15 ), YRES(6), XRES ( CAMOPTIONS_BUTTON_X ), YRES(20), false, false ); + m_CamButton->setParent( m_BottomBorder ); + m_CamButton->setContentAlignment( vgui::Label::a_center ); + m_CamButton->setBoundKey( (char)255 ); // special no bound to avoid leading spaces in name + m_CamButton->addActionSignal( new CSpectatorHandler_Command( this, SPECTATOR_PANEL_CMD_CAMERA ) ); + m_CamButton->setUnArmedBorderColor ( 59, 58, 34, 48 ); + m_CamButton->setArmedBorderColor ( 194, 202, 54, 0 ); + m_CamButton->setUnArmedColor ( 143, 143, 54, 0 ); + m_CamButton->setArmedColor ( 194, 202, 54, 0 ); + + m_PrevPlayerButton= new ColorButton("<", XRES( 15 + OPTIONS_BUTTON_X + 15 ), YRES(6), XRES(24), YRES(20), false, false ); + m_PrevPlayerButton->setParent( m_BottomBorder ); + m_PrevPlayerButton->setContentAlignment( vgui::Label::a_center ); + m_PrevPlayerButton->setBoundKey( (char)255 ); // special no bound to avoid leading spaces in name + m_PrevPlayerButton->addActionSignal( new CSpectatorHandler_Command(this,SPECTATOR_PANEL_CMD_PREVPLAYER) ); + m_PrevPlayerButton->setUnArmedBorderColor ( 59, 58, 34, 48 ); + m_PrevPlayerButton->setArmedBorderColor ( 194, 202, 54, 0 ); + m_PrevPlayerButton->setUnArmedColor ( 143, 143, 54, 0 ); + m_PrevPlayerButton->setArmedColor ( 194, 202, 54, 0 ); + + m_NextPlayerButton= new ColorButton(">", (ScreenWidth - (XRES ( CAMOPTIONS_BUTTON_X ) + 15)) - XRES ( 24 + 15 ), YRES(6), XRES(24), YRES(20),false, false ); + m_NextPlayerButton->setParent( m_BottomBorder ); + m_NextPlayerButton->setContentAlignment( vgui::Label::a_center ); + m_NextPlayerButton->setBoundKey( (char)255 ); // special no bound to avoid leading spaces in name + m_NextPlayerButton->addActionSignal( new CSpectatorHandler_Command(this,SPECTATOR_PANEL_CMD_NEXTPLAYER) ); + m_NextPlayerButton->setUnArmedBorderColor ( 59, 58, 34, 48 ); + m_NextPlayerButton->setArmedBorderColor ( 194, 202, 54, 0 ); + m_NextPlayerButton->setUnArmedColor ( 143, 143, 54, 0 ); + m_NextPlayerButton->setArmedColor ( 194, 202, 54, 0 ); + + // Initialize the bottom title. + + float flLabelSize = ( (ScreenWidth - (XRES ( CAMOPTIONS_BUTTON_X ) + 15)) - XRES ( 24 + 15 ) ) - XRES( (15 + OPTIONS_BUTTON_X + 15) + 38 ); + + m_BottomMainLabel = new Label( "Spectator Bottom", XRES( ( 15 + OPTIONS_BUTTON_X + 15 ) + 31 ), YRES(6), flLabelSize, YRES(20) ); + m_BottomMainLabel->setParent(m_BottomBorder); + m_BottomMainLabel->setPaintBackgroundEnabled(false); + m_BottomMainLabel->setFgColor( Scheme::sc_primary1 ); + m_BottomMainLabel->setContentAlignment( vgui::Label::a_center ); + m_BottomMainLabel->setBorder( new LineBorder( Color( 59, 58, 34, 48 ) ) ); + + m_InsetViewButton = new ColorButton("", XRES(2), YRES(2), XRES(240), YRES(180), false, false ); + m_InsetViewButton->setParent( this ); + m_InsetViewButton->setBoundKey( (char)255 ); + m_InsetViewButton->addActionSignal( new CSpectatorHandler_Command(this,SPECTATOR_PANEL_CMD_TOGGLE_INSET) ); + m_InsetViewButton->setUnArmedBorderColor ( 59, 58, 34, 48 ); + m_InsetViewButton->setArmedBorderColor ( 194, 202, 54, 0 ); + m_InsetViewButton->setUnArmedColor ( 143, 143, 54, 0 ); + m_InsetViewButton->setArmedColor ( 194, 202, 54, 0 ); + + + m_menuVisible = false; + m_insetVisible = false; +// m_HideButton->setVisible(false); + m_CamButton->setVisible(false); + m_OptionButton->setVisible(false); + m_NextPlayerButton->setVisible(false); + m_PrevPlayerButton->setVisible(false); + m_TopBanner->setVisible( false ); + m_ExtraInfo->setVisible( false ); + m_Separator->setVisible( false ); + m_TimerImage->setVisible( false ); + +} + +void SpectatorPanel::ShowMenu(bool isVisible) +{ +// m_HideButton->setVisible(isVisible); m_HideButton->setArmed( false ); + m_OptionButton->setVisible(isVisible); m_OptionButton->setArmed( false ); + m_CamButton->setVisible(isVisible); m_CamButton->setArmed( false ); + m_NextPlayerButton->setVisible(isVisible); m_NextPlayerButton->setArmed( false ); + m_PrevPlayerButton->setVisible(isVisible); m_PrevPlayerButton->setArmed( false ); + + if ( !isVisible ) + { + int iLabelSizeX, iLabelSizeY; + m_BottomMainLabel->getSize( iLabelSizeX, iLabelSizeY ); + m_BottomMainLabel->setPos( ( ScreenWidth / 2 ) - (iLabelSizeX/2), YRES(6) ); + } + else + m_BottomMainLabel->setPos( XRES( ( 15 + OPTIONS_BUTTON_X + 15 ) + 31 ), YRES(6) ); + + if ( !isVisible ) + { + gViewPort->HideCommandMenu(); + + // if switching from visible menu to invisible menu, show help text + if ( m_menuVisible && this->isVisible() ) + { + char string[ 64 ]; + + _snprintf( string, sizeof( string ) - 1, "%c%s", HUD_PRINTCENTER, CHudTextMessage::BufferedLocaliseTextString( "#Spec_Duck" ) ); + string[ sizeof( string ) - 1 ] = '\0'; + + gHUD.m_TextMessage.MsgFunc_TextMsg( NULL, strlen( string ) + 1, string ); + } + } + + m_menuVisible = isVisible; + + gViewPort->UpdateCursorState(); +} + + +const char *GetSpectatorLabel ( int iMode ) +{ + switch ( iMode ) + { + case OBS_CHASE_LOCKED: + return "#OBS_CHASE_LOCKED"; + + case OBS_CHASE_FREE: + return "#OBS_CHASE_FREE"; + + case OBS_ROAMING: + return "#OBS_ROAMING"; + + case OBS_IN_EYE: + return "#OBS_IN_EYE"; + + case OBS_MAP_FREE: + return "#OBS_MAP_FREE"; + + case OBS_MAP_CHASE: + return "#OBS_MAP_CHASE"; + + case OBS_NONE: + default: + return "#OBS_NONE"; + } + + return ""; +} + +void SpectatorPanel::EnableInsetView(bool isEnabled) +{ + int x = gHUD.m_Spectator.m_OverviewData.insetWindowX; + int y = gHUD.m_Spectator.m_OverviewData.insetWindowY; + int wide = gHUD.m_Spectator.m_OverviewData.insetWindowWidth; + int tall = gHUD.m_Spectator.m_OverviewData.insetWindowHeight; + int offset = x + wide + 2; + + if ( isEnabled ) + { + // short black bar to see full inset + m_TopBorder->setBounds( XRES(offset), 0, XRES(640 - offset ), YRES(PANEL_HEIGHT) ); + + if ( gEngfuncs.IsSpectateOnly() ) + { + m_TopBanner->setVisible( true ); + m_TopBanner->setPos( XRES(offset), 0 ); + } + else + m_TopBanner->setVisible( false ); + + m_InsetViewButton->setBounds( XRES( x ), YRES( y ), + XRES( wide ), YRES( tall ) ); + m_InsetViewButton->setVisible(true); + } + else + { + // full black bar, no inset border + // show banner only in real HLTV mode + if ( gEngfuncs.IsSpectateOnly() ) + { + m_TopBanner->setVisible( true ); + m_TopBanner->setPos( 0,0 ); + } + else + m_TopBanner->setVisible( false ); + + m_TopBorder->setBounds( 0, 0, ScreenWidth, YRES(PANEL_HEIGHT) ); + + m_InsetViewButton->setVisible(false); + } + + m_insetVisible = isEnabled; + + Update(); + + m_CamButton->setText( CHudTextMessage::BufferedLocaliseTextString( GetSpectatorLabel( g_iUser1 ) ) ); +} + + + + +void SpectatorPanel::Update() +{ + int iTextWidth, iTextHeight; + int iTimeHeight, iTimeWidth; + int offset,j; + + if ( m_insetVisible ) + offset = gHUD.m_Spectator.m_OverviewData.insetWindowX + gHUD.m_Spectator.m_OverviewData.insetWindowWidth + 2; + else + offset = 0; + + bool visible = gHUD.m_Spectator.m_drawstatus->value != 0; + + m_ExtraInfo->setVisible( visible ); + m_TimerImage->setVisible( visible ); + m_CurrentTime->setVisible( visible ); + m_Separator->setVisible( visible ); + + for ( j= 0; j < TEAM_NUMBER; j++ ) + m_TeamScores[j]->setVisible( visible ); + + if ( !visible ) + return; + + m_ExtraInfo->getTextSize( iTextWidth, iTextHeight ); + m_CurrentTime->getTextSize( iTimeWidth, iTimeHeight ); + + iTimeWidth += XRES ( 14 ); // +timer icon + iTimeWidth += ( 4-(iTimeWidth%4) ); + + if ( iTimeWidth > iTextWidth ) + iTextWidth = iTimeWidth; + + int xPos = ScreenWidth - ( iTextWidth + XRES ( 4 + offset ) ); + + m_ExtraInfo->setBounds( xPos, YRES( 1 ), iTextWidth, iTextHeight ); + + m_TimerImage->setBounds( xPos, YRES( 2 ) + iTextHeight , XRES(14), YRES(14) ); + + m_CurrentTime->setBounds( xPos + XRES ( 14 + 1 ), YRES( 2 ) + iTextHeight , iTimeWidth, iTimeHeight ); + + m_Separator->setPos( ScreenWidth - ( iTextWidth + XRES ( 4+2+4+offset ) ) , YRES( 1 ) ); + m_Separator->setSize( XRES( 4 ), YRES( PANEL_HEIGHT - 2 ) ); + + for ( j= 0; j < TEAM_NUMBER; j++ ) + { + int iwidth, iheight; + + m_TeamScores[j]->getTextSize( iwidth, iheight ); + m_TeamScores[j]->setBounds( ScreenWidth - ( iTextWidth + XRES ( 4+2+4+2+offset ) + iwidth ), YRES( 1 ) + ( iheight * j ), iwidth, iheight ); + } +} diff --git a/cl_dll/vgui_SpectatorPanel.h b/cl_dll/vgui_SpectatorPanel.h new file mode 100644 index 0000000..d77e900 --- /dev/null +++ b/cl_dll/vgui_SpectatorPanel.h @@ -0,0 +1,102 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// vgui_SpectatorPanel.h: interface for the SpectatorPanel class. +// +////////////////////////////////////////////////////////////////////// + +#ifndef SPECTATORPANEL_H +#define SPECTATORPANEL_H + +#include +#include +#include + +using namespace vgui; + +#define SPECTATOR_PANEL_CMD_NONE 0 + +#define SPECTATOR_PANEL_CMD_OPTIONS 1 +#define SPECTATOR_PANEL_CMD_PREVPLAYER 2 +#define SPECTATOR_PANEL_CMD_NEXTPLAYER 3 +#define SPECTATOR_PANEL_CMD_HIDEMENU 4 +#define SPECTATOR_PANEL_CMD_TOGGLE_INSET 5 +#define SPECTATOR_PANEL_CMD_CAMERA 6 + +#define TEAM_NUMBER 2 + +class SpectatorPanel : public Panel //, public vgui::CDefaultInputSignal +{ + +public: + SpectatorPanel(int x,int y,int wide,int tall); + virtual ~SpectatorPanel(); + + void ActionSignal(int cmd); + + // InputSignal overrides. +public: + void Initialize(); + void Update(); + + + +public: + + void EnableInsetView(bool isEnabled); + void ShowMenu(bool isVisible); + + + ColorButton * m_OptionButton; +// CommandButton * m_HideButton; + ColorButton * m_PrevPlayerButton; + ColorButton * m_NextPlayerButton; + ColorButton * m_CamButton; + + CTransparentPanel * m_TopBorder; + CTransparentPanel * m_BottomBorder; + + ColorButton *m_InsetViewButton; + + Label *m_BottomMainLabel; + CImageLabel *m_TimerImage; + Label *m_CurrentTime; + Label *m_ExtraInfo; + Panel *m_Separator; + + Label *m_TeamScores[TEAM_NUMBER]; + + CImageLabel *m_TopBanner; + + bool m_menuVisible; + bool m_insetVisible; +}; + + + +class CSpectatorHandler_Command : public ActionSignal +{ + +private: + SpectatorPanel * m_pFather; + int m_cmd; + +public: + CSpectatorHandler_Command( SpectatorPanel * panel, int cmd ) + { + m_pFather = panel; + m_cmd = cmd; + } + + virtual void actionPerformed( Panel * panel ) + { + m_pFather->ActionSignal(m_cmd); + } +}; + + +#endif // !defined SPECTATORPANEL_H diff --git a/cl_dll/vgui_TeamFortressViewport.cpp b/cl_dll/vgui_TeamFortressViewport.cpp new file mode 100644 index 0000000..b2cf925 --- /dev/null +++ b/cl_dll/vgui_TeamFortressViewport.cpp @@ -0,0 +1,2548 @@ +//=========== (C) Copyright 1996-2002 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: Client DLL VGUI Viewport +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vgui_paranoiatext.h"// buz + +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "parsemsg.h" +#include "pm_shared.h" +#include "../engine/keydefs.h" +#include "demo.h" +#include "demo_api.h" + +#include "vgui_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ServerBrowser.h" +#include "vgui_ScorePanel.h" +#include "vgui_SpectatorPanel.h" + +#include "vgui_subtitles.h"// buz +#include "vgui_radio.h"// buz +#include "vgui_hud.h" // buz +#include "vgui_tabpanel.h" // buz +#include "vgui_tips.h" // buz +#include "vgui_gamma.h" // buz +#include "vgui_screenmsg.h" // buz + + +extern int g_iVisibleMouse; +class CCommandMenu; +int g_iPlayerClass; +int g_iTeamNumber; +int g_iUser1; +int g_iUser2; +int g_iUser3; + +// Scoreboard positions +#define SBOARD_INDENT_X XRES(104) +#define SBOARD_INDENT_Y YRES(40) + +// low-res scoreboard indents +#define SBOARD_INDENT_X_512 30 +#define SBOARD_INDENT_Y_512 30 + +#define SBOARD_INDENT_X_400 0 +#define SBOARD_INDENT_Y_400 20 + +void IN_ResetMouse( void ); +extern CMenuPanel *CMessageWindowPanel_Create( const char *szMOTD, const char *szTitle, int iShadeFullscreen, int iRemoveMe, int x, int y, int wide, int tall ); +extern float * GetClientColor( int clientIndex ); + +using namespace vgui; +/* +typedef struct tga_s { + unsigned char IDLength, ColorMapType, ImageType; + unsigned short ColorMapStart; // + unsigned short ColorMapLength; // unused + unsigned char ColorMapDepth; // + unsigned short XOrigin, YOrigin, Width, Height; + unsigned char ColorBits, ImageDescriptor; +} tga_t; + + +//buz test +class CTestPanel : public Panel +{ +public: + CTestPanel() : Panel(0, 0, ScreenWidth, ScreenHeight) + { + setPaintBackgroundEnabled(false); + setPaintEnabled(true); + + int fontFileLength; + void* pFontData = gEngfuncs.COM_LoadFile( "gfx/vgui/fonts/640_Default Text.tga", 5, &fontFileLength ); + if(!pFontData) + gEngfuncs.Con_Printf("Missing bitmap font\n"); + + fffffffff tga_t* tgaDesc = (tga_t*)pFontData; + gEngfuncs.Con_Printf("Description length: %d\n", tgaDesc->IDLength); + gEngfuncs.Con_Printf("Color map: %d\n", tgaDesc->ColorMapType); + gEngfuncs.Con_Printf("Image type: %d\n", tgaDesc->ImageType); + gEngfuncs.Con_Printf("color map start: %d\n", tgaDesc->ColorMapStart); + gEngfuncs.Con_Printf("color map length: %d\n", tgaDesc->ColorMapLength); + gEngfuncs.Con_Printf("color map depth: %d\n", tgaDesc->ColorMapDepth); + gEngfuncs.Con_Printf("X origin: %d\n", tgaDesc->XOrigin); + gEngfuncs.Con_Printf("Y origin: %d\n", tgaDesc->YOrigin); + gEngfuncs.Con_Printf("Width: %d\n", tgaDesc->Width); + gEngfuncs.Con_Printf("Height: %d\n", tgaDesc->Width); + gEngfuncs.Con_Printf("Colorbits: %d\n", tgaDesc->ColorBits); + + tgaDesc++; + char* pFileData = (char*)tgaDesc; + for (int i=0; i < 20; i++, pFileData++) + { + char byte = *pFileData; + gEngfuncs.Con_Printf("byte n%d: %d\n", i, byte); + } fffffffff + + m_pFont = new Font("Arial",pFontData,fontFileLength,17,0,0,0,false,false,false,false); + //m_pFont = new Font("Arial",17,0,0,0,false,false,false,false); + } + + ~CTestPanel() + { + if (m_pFont) delete m_pFont; + } + + void paint() + { + if (!m_pFont) + { + return; + } + + drawSetColor(0, 0, 0, 0); + drawFilledRect(0, 0, getWide(), getTall()/3); + + drawSetTextFont(m_pFont); + drawSetTextColor(0, 250, 0, 0); + + fff char* pos = "ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞß"; + int xpos = 0; + + while(*pos != 0) + { + drawPrintChar(xpos, 0, *pos); + pos++; + xpos += 16; + } + + pos = "àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ"; + xpos = 0; + + while(*pos != 0) + { + drawPrintChar(xpos, 17, *pos); + pos++; + xpos += 16; + } fffffffffff + drawSetTextPos(0,17); + char* textstring = "Èçíà÷àëüíî èìåíîâàëàñü â ïðàâèòåëüñòâåííûõ îáúåêòàõ..."; + drawPrintText(textstring, strlen(textstring) - 1); + + int xpos = 0; + while(*textstring != 0) + { + int a, b, c; + drawPrintChar(xpos, 0, *textstring); + textstring++; + m_pFont->getCharABCwide(*textstring, a, b, c); + xpos += (b + a + c); + } + + int wide, tall; + m_pFont->getTextSize("Èçíà÷àëüíî èìåíîâàëàñü â ïðàâèòåëüñòâåííûõ îáúåêòàõ...", wide, tall); + drawSetColor(100, 0, 0, 0); + drawFilledRect(0, 34, wide, 34+tall); + //gEngfuncs.Con_Printf("wide %d, tall %d\n", wide, tall); + } + +private: + Font* m_pFont; +}; +*/ + +// Team Colors +int iNumberOfTeamColors = 5; +int iTeamColors[5][3] = +{ + { 255, 170, 0 }, // HL orange (default) + { 125, 165, 210 }, // Blue + { 200, 90, 70 }, // Red + { 225, 205, 45 }, // Yellow + { 145, 215, 140 }, // Green +}; + + +// Used for Class specific buttons +char *sTFClasses[] = +{ + "", + "SCOUT", + "SNIPER", + "SOLDIER", + "DEMOMAN", + "MEDIC", + "HWGUY", + "PYRO", + "SPY", + "ENGINEER", + "CIVILIAN", +}; + +char *sLocalisedClasses[] = +{ + "#Civilian", + "#Scout", + "#Sniper", + "#Soldier", + "#Demoman", + "#Medic", + "#HWGuy", + "#Pyro", + "#Spy", + "#Engineer", + "#Random", + "#Civilian", +}; + +char *sTFClassSelection[] = +{ + "civilian", + "scout", + "sniper", + "soldier", + "demoman", + "medic", + "hwguy", + "pyro", + "spy", + "engineer", + "randompc", + "civilian", +}; + +int iBuildingCosts[] = +{ + BUILD_COST_DISPENSER, + BUILD_COST_SENTRYGUN +}; + +// This maps class numbers to the Invalid Class bit. +// This is needed for backwards compatability in maps that were finished before +// all the classes were in TF. Hence the wacky sequence. +int sTFValidClassInts[] = +{ + 0, + TF_ILL_SCOUT, + TF_ILL_SNIPER, + TF_ILL_SOLDIER, + TF_ILL_DEMOMAN, + TF_ILL_MEDIC, + TF_ILL_HVYWEP, + TF_ILL_PYRO, + TF_ILL_SPY, + TF_ILL_ENGINEER, + TF_ILL_RANDOMPC, +}; + +// Get the name of TGA file, based on GameDir +char* GetVGUITGAName(const char *pszName) +{ + int i; + char sz[256]; + static char gd[256]; + const char *gamedir; + + if (ScreenWidth < 640) + i = 320; + else + i = 640; + sprintf(sz, pszName, i); + + gamedir = gEngfuncs.pfnGetGameDirectory(); + sprintf(gd, "%s/gfx/vgui/%s.tga",gamedir,sz); + + return gd; +} + +//================================================================ +// COMMAND MENU +//================================================================ +void CCommandMenu::AddButton( CommandButton *pButton ) +{ + if (m_iButtons >= MAX_BUTTONS) + return; + + m_aButtons[m_iButtons] = pButton; + m_iButtons++; + pButton->setParent( this ); + pButton->setFont( Scheme::sf_primary3 ); + + // give the button a default key binding + if ( m_iButtons < 10 ) + { + pButton->setBoundKey( m_iButtons + '0' ); + } + else if ( m_iButtons == 10 ) + { + pButton->setBoundKey( '0' ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Tries to find a button that has a key bound to the input, and +// presses the button if found +// Input : keyNum - the character number of the input key +// Output : Returns true if the command menu should close, false otherwise +//----------------------------------------------------------------------------- +bool CCommandMenu::KeyInput( int keyNum ) +{ + // loop through all our buttons looking for one bound to keyNum + for ( int i = 0; i < m_iButtons; i++ ) + { + if ( !m_aButtons[i]->IsNotValid() ) + { + if ( m_aButtons[i]->getBoundKey() == keyNum ) + { + // hit the button + if ( m_aButtons[i]->GetSubMenu() ) + { + // open the sub menu + gViewPort->SetCurrentCommandMenu( m_aButtons[i]->GetSubMenu() ); + return false; + } + else + { + // run the bound command + m_aButtons[i]->fireActionSignal(); + return true; + } + } + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: clears the current menus buttons of any armed (highlighted) +// state, and all their sub buttons +//----------------------------------------------------------------------------- +void CCommandMenu::ClearButtonsOfArmedState( void ) +{ + for ( int i = 0; i < GetNumButtons(); i++ ) + { + m_aButtons[i]->setArmed( false ); + + if ( m_aButtons[i]->GetSubMenu() ) + { + m_aButtons[i]->GetSubMenu()->ClearButtonsOfArmedState(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pSubMenu - +// Output : CommandButton +//----------------------------------------------------------------------------- +CommandButton *CCommandMenu::FindButtonWithSubmenu( CCommandMenu *pSubMenu ) +{ + for ( int i = 0; i < GetNumButtons(); i++ ) + { + if ( m_aButtons[i]->GetSubMenu() == pSubMenu ) + return m_aButtons[i]; + } + + return NULL; +} + +// Recalculate the visible buttons +bool CCommandMenu::RecalculateVisibles( int iYOffset, bool bHideAll ) +{ + int i, iCurrentY = 0; + int iVisibleButtons = 0; + + // Cycle through all the buttons in this menu, and see which will be visible + for (i = 0; i < m_iButtons; i++) + { + int iClass = m_aButtons[i]->GetPlayerClass(); + + if ( (iClass && iClass != g_iPlayerClass ) || ( m_aButtons[i]->IsNotValid() ) || bHideAll ) + { + m_aButtons[i]->setVisible( false ); + if ( m_aButtons[i]->GetSubMenu() != NULL ) + { + (m_aButtons[i]->GetSubMenu())->RecalculateVisibles( 0, true ); + } + } + else + { + // If it's got a submenu, force it to check visibilities + if ( m_aButtons[i]->GetSubMenu() != NULL ) + { + if ( !(m_aButtons[i]->GetSubMenu())->RecalculateVisibles( 0 , false ) ) + { + // The submenu had no visible buttons, so don't display this button + m_aButtons[i]->setVisible( false ); + continue; + } + } + + m_aButtons[i]->setVisible( true ); + iVisibleButtons++; + } + } + + // Set Size + setSize( _size[0], (iVisibleButtons * (m_flButtonSizeY-1)) + 1 ); + + if ( iYOffset ) + { + m_iYOffset = iYOffset; + } + + for (i = 0; i < m_iButtons; i++) + { + if ( m_aButtons[i]->isVisible() ) + { + if ( m_aButtons[i]->GetSubMenu() != NULL ) + (m_aButtons[i]->GetSubMenu())->RecalculateVisibles( iCurrentY + m_iYOffset, false ); + + + // Make sure it's at the right Y position + // m_aButtons[i]->getPos( iXPos, iYPos ); + + if ( m_iDirection ) + { + m_aButtons[i]->setPos( 0, (iVisibleButtons-1) * (m_flButtonSizeY-1) - iCurrentY ); + } + else + { + m_aButtons[i]->setPos( 0, iCurrentY ); + } + + iCurrentY += (m_flButtonSizeY-1); + } + } + + return iVisibleButtons?true:false; +} + +// Make sure all submenus can fit on the screen +void CCommandMenu::RecalculatePositions( int iYOffset ) +{ + int iTop; + int iAdjust = 0; + + m_iYOffset+= iYOffset; + + if ( m_iDirection ) + iTop = ScreenHeight - (m_iYOffset + _size[1] ); + else + iTop = m_iYOffset; + + if ( iTop < 0 ) + iTop = 0; + + // Calculate if this is going to fit onscreen, and shuffle it up if it won't + int iBottom = iTop + _size[1]; + + if ( iBottom > ScreenHeight ) + { + // Move in increments of button sizes + while (iAdjust < (iBottom - ScreenHeight)) + { + iAdjust += m_flButtonSizeY - 1; + } + + iTop -= iAdjust; + + // Make sure it doesn't move off the top of the screen (the menu's too big to fit it all) + if ( iTop < 0 ) + { + iAdjust -= (0 - iTop); + iTop = 0; + } + } + + setPos( _pos[0], iTop ); + + // We need to force all menus below this one to update their positions now, because they + // might have submenus riding off buttons in this menu that have just shifted. + for (int i = 0; i < m_iButtons; i++) + m_aButtons[i]->UpdateSubMenus( iAdjust ); +} + + +// Make this menu and all menus above it in the chain visible +void CCommandMenu::MakeVisible( CCommandMenu *pChildMenu ) +{ +/* + // Push down the button leading to the child menu + for (int i = 0; i < m_iButtons; i++) + { + if ( (pChildMenu != NULL) && (m_aButtons[i]->GetSubMenu() == pChildMenu) ) + { + m_aButtons[i]->setArmed( true ); + } + else + { + m_aButtons[i]->setArmed( false ); + } + } +*/ + + setVisible(true); + if (m_pParentMenu) + m_pParentMenu->MakeVisible( this ); +} + +//================================================================ +// CreateSubMenu +CCommandMenu *TeamFortressViewport::CreateSubMenu( CommandButton *pButton, CCommandMenu *pParentMenu, int iYOffset, int iXOffset ) +{ + int iXPos = 0; + int iYPos = 0; + int iWide = CMENU_SIZE_X; + int iTall = 0; + int iDirection = 0; + + if (pParentMenu) + { + iXPos = m_pCurrentCommandMenu->GetXOffset() + (CMENU_SIZE_X - 1) + iXOffset; + iYPos = m_pCurrentCommandMenu->GetYOffset() + iYOffset; + iDirection = pParentMenu->GetDirection(); + } + + CCommandMenu *pMenu = new CCommandMenu(pParentMenu, iDirection, iXPos, iYPos, iWide, iTall ); + pMenu->setParent(this); + pButton->AddSubMenu( pMenu ); + pButton->setFont( Scheme::sf_primary3 ); + pMenu->m_flButtonSizeY = m_pCurrentCommandMenu->m_flButtonSizeY; + + // Create the Submenu-open signal + InputSignal *pISignal = new CMenuHandler_PopupSubMenuInput(pButton, pMenu); + pButton->addInputSignal(pISignal); + + // Put a > to show it's a submenu + CImageLabel *pLabel = new CImageLabel( "arrow", CMENU_SIZE_X - SUBMENU_SIZE_X, SUBMENU_SIZE_Y ); + pLabel->setParent(pButton); + pLabel->addInputSignal(pISignal); + + // Reposition + pLabel->getPos( iXPos, iYPos ); + pLabel->setPos( CMENU_SIZE_X - pLabel->getImageWide(), (BUTTON_SIZE_Y - pLabel->getImageTall()) / 2 ); + + // Create the mouse off signal for the Label too + if (!pButton->m_bNoHighlight) + pLabel->addInputSignal( new CHandler_CommandButtonHighlight(pButton) ); + + return pMenu; +} + +//----------------------------------------------------------------------------- +// Purpose: Makes sure the memory allocated for TeamFortressViewport is nulled out +// Input : stAllocateBlock - +// Output : void * +//----------------------------------------------------------------------------- +void *TeamFortressViewport::operator new( size_t stAllocateBlock ) +{ +// void *mem = Panel::operator new( stAllocateBlock ); + void *mem = ::operator new( stAllocateBlock ); + memset( mem, 0, stAllocateBlock ); + return mem; +} + +//----------------------------------------------------------------------------- +// Purpose: InputSignal handler for the main viewport +//----------------------------------------------------------------------------- +class CViewPortInputHandler : public InputSignal +{ +public: + bool bPressed; + + CViewPortInputHandler() + { + } + + virtual void cursorMoved(int x,int y,Panel* panel) {} + virtual void cursorEntered(Panel* panel) {} + virtual void cursorExited(Panel* panel) {} + virtual void mousePressed(MouseCode code,Panel* panel) + { + if ( code != MOUSE_LEFT ) + { + // send a message to close the command menu + // this needs to be a message, since a direct call screws the timing + gEngfuncs.pfnClientCmd( "ForceCloseCommandMenu\n" ); + } + } + virtual void mouseReleased(MouseCode code,Panel* panel) + { + } + + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {} + virtual void mouseWheeled(int delta,Panel* panel) {} + virtual void keyPressed(KeyCode code,Panel* panel) {} + virtual void keyTyped(KeyCode code,Panel* panel) {} + virtual void keyReleased(KeyCode code,Panel* panel) {} + virtual void keyFocusTicked(Panel* panel) {} +}; + + +//================================================================ +TeamFortressViewport::TeamFortressViewport(int x,int y,int wide,int tall) : Panel(x,y,wide,tall), m_SchemeManager(wide,tall) +{ + gViewPort = this; + m_iInitialized = false; +// m_pTeamMenu = NULL; +// m_pClassMenu = NULL; + m_pScoreBoard = NULL; +// m_pSpectatorPanel = NULL; + m_pCurrentMenu = NULL; + m_pCurrentCommandMenu = NULL; + m_pSubtitle = NULL; // buz + m_pTips = NULL; // buz + m_pScreenMsg = NULL; + m_pRadio = NULL; // buz + m_pGamma = NULL; // buz + + // Wargon: Ñêðîëëÿùèéñÿ òåêñò. + m_pScrollingMsg = NULL; + + CreatePickupMessagePanels(); // buz + + Initialize(); + addInputSignal( new CViewPortInputHandler ); + +/* int r, g, b, a; + + Scheme* pScheme = App::getInstance()->getScheme(); + + // primary text color + // Get the colors + //!! two different types of scheme here, need to integrate + SchemeHandle_t hPrimaryScheme = m_SchemeManager.getSchemeHandle( "Primary Button Text" ); + { + // font + pScheme->setFont( Scheme::sf_primary1, m_SchemeManager.getFont(hPrimaryScheme) ); + + // text color + m_SchemeManager.getFgColor( hPrimaryScheme, r, g, b, a ); + pScheme->setColor(Scheme::sc_primary1, r, g, b, a ); // sc_primary1 is non-transparent orange + + // background color (transparent black) + m_SchemeManager.getBgColor( hPrimaryScheme, r, g, b, a ); + pScheme->setColor(Scheme::sc_primary3, r, g, b, a ); + + // armed foreground color + m_SchemeManager.getFgArmedColor( hPrimaryScheme, r, g, b, a ); + pScheme->setColor(Scheme::sc_secondary2, r, g, b, a ); + + // armed background color + m_SchemeManager.getBgArmedColor( hPrimaryScheme, r, g, b, a ); + pScheme->setColor(Scheme::sc_primary2, r, g, b, a ); + + //!! need to get this color from scheme file + // used for orange borders around buttons + m_SchemeManager.getBorderColor( hPrimaryScheme, r, g, b, a ); + // pScheme->setColor(Scheme::sc_secondary1, r, g, b, a ); + pScheme->setColor(Scheme::sc_secondary1, 255*0.7, 170*0.7, 0, 0); + } + + // Change the second primary font (used in the scoreboard) + SchemeHandle_t hScoreboardScheme = m_SchemeManager.getSchemeHandle( "Scoreboard Text" ); + { + pScheme->setFont(Scheme::sf_primary2, m_SchemeManager.getFont(hScoreboardScheme) ); + } + + // Change the third primary font (used in command menu) + SchemeHandle_t hCommandMenuScheme = m_SchemeManager.getSchemeHandle( "CommandMenu Text" ); + { + pScheme->setFont(Scheme::sf_primary3, m_SchemeManager.getFont(hCommandMenuScheme) ); + } + + App::getInstance()->setScheme(pScheme);*/ + + // VGUI MENUS +// CreateTeamMenu(); +// CreateClassMenu(); +// CreateSpectatorMenu(); + CreateScoreBoard(); + // Init command menus + m_iNumMenus = 0; + m_iCurrentTeamNumber = m_iUser1 = m_iUser2 = m_iUser3 = 0; + + m_StandardMenu = CreateCommandMenu("commandmenu.txt", 0, CMENU_TOP, false, CMENU_SIZE_X, BUTTON_SIZE_Y, 0 ); + m_SpectatorOptionsMenu = CreateCommandMenu("spectatormenu.txt", 1, YRES(32), true, CMENU_SIZE_X, BUTTON_SIZE_Y / 2, 0 ); // above bottom bar, flat design + m_SpectatorCameraMenu = CreateCommandMenu("spectcammenu.txt", 1, YRES(32), true, XRES( 200 ), BUTTON_SIZE_Y / 2, ScreenWidth - ( XRES ( 200 ) + 15 ) ); // above bottom bar, flat design + CreateServerBrowser(); + +// CTestPanel* pan = new CTestPanel; +// pan->setParent(this); +// pan->setVisible(true); + + m_pHud2 = new CHud2(); + m_pHud2->setParent(this); + m_pHud2->setVisible(true); + + // buz + m_pSubtitle = new CSubtitle(); + m_pSubtitle->setParent(this); + m_pSubtitle->setVisible(false); + + // buz + m_pRadio = new CRadioIcon(); + m_pRadio->setParent(this); + + // buz + m_pTabPanel = new CTabPanel(); + m_pTabPanel->setParent(this); + + // buz + m_pTips = new CTips(); + m_pTips->setParent(this); + + // buz + m_pGamma = new CGammaView(); + m_pGamma->setParent(this); + + // buz + m_pScreenMsg = new CScreenMessage(); + m_pScreenMsg->setParent(this); + + // Wargon: Ñêðîëëÿùèéñÿ òåêñò. + m_pScrollingMsg = new CScrollingMessage(); + m_pScrollingMsg->setParent(this); +} + +//----------------------------------------------------------------------------- +// Purpose: Called everytime a new level is started. Viewport clears out it's data. +//----------------------------------------------------------------------------- +void TeamFortressViewport::Initialize( void ) +{ + // Force each menu to Initialize +// if (m_pTeamMenu) +// { +// m_pTeamMenu->Initialize(); +// } +// if (m_pClassMenu) +// { +// m_pClassMenu->Initialize(); +// } + if (m_pScoreBoard) + { + m_pScoreBoard->Initialize(); + HideScoreBoard(); + } +// if (m_pSpectatorPanel) +// { +// // Spectator menu doesn't need initializing +// m_pSpectatorPanel->setVisible( false ); +// } + + // buz + if (m_pParanoiaText) + { + m_pParanoiaText->setVisible(false); + UpdateCursorState(); + } + if (m_pRadio) + m_pRadio->Initialize(); // buz + if (m_pSubtitle) + m_pSubtitle->Initialize(); // buz + if (m_pHud2) + m_pHud2->Initialize(); // buz + if (m_pTabPanel) + m_pTabPanel->Initialize(); // buz + if (m_pTips) + m_pTips->Initialize(); // buz + if (m_pScreenMsg) + m_pScreenMsg->Initialize(); // buz + // Wargon: Ñêðîëëÿùèéñÿ òåêñò. + if (m_pScrollingMsg) + m_pScrollingMsg->Initialize(); + + InitializePickupMessagePanels(); // buz + + // Make sure all menus are hidden + HideVGUIMenu(); + HideCommandMenu(); + + // Clear out some data + m_iGotAllMOTD = true; + m_iRandomPC = false; + m_flScoreBoardLastUpdated = 0; + m_flSpectatorPanelLastUpdated = 0; + + // reset player info + g_iPlayerClass = 0; + g_iTeamNumber = 0; + + strcpy(m_sMapName, ""); + strcpy(m_szServerName, ""); + for (int i = 0; i < 5; i++) + { + m_iValidClasses[i] = 0; + strcpy(m_sTeamNames[i], ""); + } + + App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::SchemeCursor::scu_none) ); +} + +class CException; +//----------------------------------------------------------------------------- +// Purpose: Read the Command Menu structure from the txt file and create the menu. +// Returns Index of menu in m_pCommandMenus +//----------------------------------------------------------------------------- +int TeamFortressViewport::CreateCommandMenu( char * menuFile, int direction, int yOffset, bool flatDesign, float flButtonSizeX, float flButtonSizeY, int xOffset ) +{ + // COMMAND MENU + // Create the root of this new Command Menu + + int newIndex = m_iNumMenus; + + m_pCommandMenus[newIndex] = new CCommandMenu(NULL, direction, xOffset, yOffset, flButtonSizeX, 300); // This will be resized once we know how many items are in it + m_pCommandMenus[newIndex]->setParent(this); + m_pCommandMenus[newIndex]->setVisible(false); + m_pCommandMenus[newIndex]->m_flButtonSizeY = flButtonSizeY; + m_pCommandMenus[newIndex]->m_iSpectCmdMenu = direction; + + m_iNumMenus++; + + // Read Command Menu from the txt file + char token[1024]; + char *pfile = (char*)gEngfuncs.COM_LoadFile( menuFile, 5, NULL); + if (!pfile) + { + ALERT( at_aiconsole, "Unable to open %s\n", menuFile); + SetCurrentCommandMenu( NULL ); + return newIndex; + } + +try +{ + // First, read in the localisation strings + + // Detpack strings + gHUD.m_TextMessage.LocaliseTextString( "#DetpackSet_For5Seconds", m_sDetpackStrings[0], MAX_BUTTON_SIZE ); + gHUD.m_TextMessage.LocaliseTextString( "#DetpackSet_For20Seconds", m_sDetpackStrings[1], MAX_BUTTON_SIZE ); + gHUD.m_TextMessage.LocaliseTextString( "#DetpackSet_For50Seconds", m_sDetpackStrings[2], MAX_BUTTON_SIZE ); + + // Now start parsing the menu structure + m_pCurrentCommandMenu = m_pCommandMenus[newIndex]; + char szLastButtonText[32] = "file start"; + pfile = gEngfuncs.COM_ParseFile(pfile, token); + while ( ( strlen ( token ) > 0 ) && ( m_iNumMenus < MAX_MENUS ) ) + { + // Keep looping until we hit the end of this menu + while ( token[0] != '}' && ( strlen( token ) > 0 ) ) + { + char cText[32] = ""; + char cBoundKey[32] = ""; + char cCustom[32] = ""; + static const int cCommandLength = 128; + char cCommand[cCommandLength] = ""; + char szMap[MAX_MAPNAME] = ""; + int iPlayerClass = 0; + int iCustom = false; + int iTeamOnly = -1; + int iToggle = 0; + int iButtonY; + bool bGetExtraToken = true; + CommandButton *pButton = NULL; + + // We should never be here without a Command Menu + if (!m_pCurrentCommandMenu) + { + gEngfuncs.Con_Printf("Error in %s file after '%s'.\n",menuFile, szLastButtonText ); + m_iInitialized = false; + return newIndex; + } + + // token should already be the bound key, or the custom name + strncpy( cCustom, token, 32 ); + cCustom[31] = '\0'; + + // See if it's a custom button + if (!strcmp(cCustom, "CUSTOM") ) + { + iCustom = true; + + // Get the next token + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } + // See if it's a map + else if (!strcmp(cCustom, "MAP") ) + { + // Get the mapname + pfile = gEngfuncs.COM_ParseFile(pfile, token); + strncpy( szMap, token, MAX_MAPNAME ); + szMap[MAX_MAPNAME-1] = '\0'; + + // Get the next token + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } + else if ( !strncmp(cCustom, "TEAM", 4) ) // TEAM1, TEAM2, TEAM3, TEAM4 + { + // make it a team only button + iTeamOnly = atoi( cCustom + 4 ); + + // Get the next token + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } + else if ( !strncmp(cCustom, "TOGGLE", 6) ) + { + iToggle = true; + // Get the next token + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } + else + { + // See if it's a Class + for (int i = 1; i <= PC_ENGINEER; i++) + { + if ( !strcmp(token, sTFClasses[i]) ) + { + // Save it off + iPlayerClass = i; + + // Get the button text + pfile = gEngfuncs.COM_ParseFile(pfile, token); + break; + } + } + } + + // Get the button bound key + strncpy( cBoundKey, token, 32 ); + cText[31] = '\0'; + + // Get the button text + pfile = gEngfuncs.COM_ParseFile(pfile, token); + strncpy( cText, token, 32 ); + cText[31] = '\0'; + + // save off the last button text we've come across (for error reporting) + strcpy( szLastButtonText, cText ); + + // Get the button command + pfile = gEngfuncs.COM_ParseFile(pfile, token); + strncpy( cCommand, token, cCommandLength ); + cCommand[cCommandLength - 1] = '\0'; + + iButtonY = (BUTTON_SIZE_Y-1) * m_pCurrentCommandMenu->GetNumButtons(); + + // Custom button handling + if ( iCustom ) + { + pButton = CreateCustomButton( cText, cCommand, iButtonY ); + + // Get the next token to see if we're a menu + pfile = gEngfuncs.COM_ParseFile(pfile, token); + + if ( token[0] == '{' ) + { + strcpy( cCommand, token ); + } + else + { + bGetExtraToken = false; + } + } + else if ( szMap[0] != '\0' ) + { + // create a map button + pButton = new MapButton(szMap, cText, xOffset, iButtonY, flButtonSizeX, flButtonSizeY ); + } + else if ( iTeamOnly != -1) + { + // button that only shows up if the player is on team iTeamOnly + pButton = new TeamOnlyCommandButton( iTeamOnly, cText, xOffset, iButtonY, flButtonSizeX, flButtonSizeY, flatDesign ); + } + else if ( iToggle && direction == 0 ) + { + pButton = new ToggleCommandButton( cCommand, cText, xOffset, iButtonY, flButtonSizeX, flButtonSizeY, flatDesign ); + } + else if ( direction == 1 ) + { + if ( iToggle ) + pButton = new SpectToggleButton( cCommand, cText, xOffset, iButtonY, flButtonSizeX, flButtonSizeY, flatDesign ); + else + pButton = new SpectButton( iPlayerClass, cText, xOffset, iButtonY, flButtonSizeX, flButtonSizeY ); + } + else + { + // normal button + pButton = new CommandButton( iPlayerClass, cText, xOffset, iButtonY, flButtonSizeX, flButtonSizeY, flatDesign ); + } + + // add the button into the command menu + if ( pButton ) + { + m_pCurrentCommandMenu->AddButton( pButton ); + pButton->setBoundKey( cBoundKey[0] ); + pButton->setParentMenu( m_pCurrentCommandMenu ); + + // Override font in CommandMenu + pButton->setFont( Scheme::sf_primary3 ); + } + + // Find out if it's a submenu or a button we're dealing with + if ( cCommand[0] == '{' ) + { + if ( m_iNumMenus >= MAX_MENUS ) + { + gEngfuncs.Con_Printf( "Too many menus in %s past '%s'\n",menuFile, szLastButtonText ); + } + else + { + // Create the menu + m_pCommandMenus[m_iNumMenus] = CreateSubMenu(pButton, m_pCurrentCommandMenu, iButtonY ); + m_pCurrentCommandMenu = m_pCommandMenus[m_iNumMenus]; + m_iNumMenus++; + } + } + else if ( !iCustom ) + { + // Create the button and attach it to the current menu + if ( iToggle ) + pButton->addActionSignal(new CMenuHandler_ToggleCvar(cCommand)); + else + pButton->addActionSignal(new CMenuHandler_StringCommand(cCommand)); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + + // Get the next token + if ( bGetExtraToken ) + { + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } + } + + // Move back up a menu + m_pCurrentCommandMenu = m_pCurrentCommandMenu->GetParentMenu(); + + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } +} +catch( CException *e ) +{ + e; + //e->Delete(); + e = NULL; + m_iInitialized = false; + return newIndex; +} + + SetCurrentMenu( NULL ); + SetCurrentCommandMenu( NULL ); + gEngfuncs.COM_FreeFile( pfile ); + + m_iInitialized = true; + return newIndex; +} + +//----------------------------------------------------------------------------- +// Purpose: Creates all the class choices under a spy's disguise menus, and +// maps a command to them +// Output : CCommandMenu +//----------------------------------------------------------------------------- +CCommandMenu *TeamFortressViewport::CreateDisguiseSubmenu( CommandButton *pButton, CCommandMenu *pParentMenu, const char *commandText, int iYOffset, int iXOffset ) +{ + // create the submenu, under which the class choices will be listed + CCommandMenu *pMenu = CreateSubMenu( pButton, pParentMenu, iYOffset, iXOffset ); + m_pCommandMenus[m_iNumMenus] = pMenu; + m_iNumMenus++; + + // create the class choice buttons + for ( int i = PC_SCOUT; i <= PC_ENGINEER; i++ ) + { + CommandButton *pDisguiseButton = new CommandButton( CHudTextMessage::BufferedLocaliseTextString( sLocalisedClasses[i] ), 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y ); + + char sz[256]; + sprintf(sz, "%s %d", commandText, i ); + pDisguiseButton->addActionSignal(new CMenuHandler_StringCommand(sz)); + + pMenu->AddButton( pDisguiseButton ); + } + + return pMenu; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pButtonText - +// *pButtonName - +// Output : CommandButton +//----------------------------------------------------------------------------- +CommandButton *TeamFortressViewport::CreateCustomButton( char *pButtonText, char *pButtonName, int iYOffset ) +{ + CommandButton *pButton = NULL; + CCommandMenu *pMenu = NULL; + + // ChangeTeam + if ( !strcmp( pButtonName, "!CHANGETEAM" ) ) + { + // ChangeTeam Submenu + pButton = new CommandButton(pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + + // Create the submenu + pMenu = CreateSubMenu(pButton, m_pCurrentCommandMenu, iYOffset ); + m_pCommandMenus[m_iNumMenus] = pMenu; + m_iNumMenus++; + + // ChangeTeam buttons + for (int i = 0; i < 4; i++) + { + char sz[256]; + sprintf(sz, "jointeam %d", i+1); + m_pTeamButtons[i] = new TeamButton(i+1, "teamname", 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + m_pTeamButtons[i]->addActionSignal(new CMenuHandler_StringCommandWatch( sz )); + pMenu->AddButton( m_pTeamButtons[i] ); + } + + // Auto Assign button + m_pTeamButtons[4] = new TeamButton(5, gHUD.m_TextMessage.BufferedLocaliseTextString( "#Team_AutoAssign" ), 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + m_pTeamButtons[4]->addActionSignal(new CMenuHandler_StringCommand( "jointeam 5" )); + pMenu->AddButton( m_pTeamButtons[4] ); + + // Spectate button + m_pTeamButtons[5] = new SpectateButton( CHudTextMessage::BufferedLocaliseTextString( "#Menu_Spectate" ), 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y, false); + m_pTeamButtons[5]->addActionSignal(new CMenuHandler_StringCommand( "spectate" )); + pMenu->AddButton( m_pTeamButtons[5] ); + } + // ChangeClass + else if ( !strcmp( pButtonName, "!CHANGECLASS" ) ) + { + // Create the Change class menu + pButton = new ClassButton(-1, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y, false); + + // ChangeClass Submenu + pMenu = CreateSubMenu(pButton, m_pCurrentCommandMenu, iYOffset ); + m_pCommandMenus[m_iNumMenus] = pMenu; + m_iNumMenus++; + + for (int i = PC_SCOUT; i <= PC_RANDOM; i++ ) + { + char sz[256]; + + // ChangeClass buttons + CHudTextMessage::LocaliseTextString( sLocalisedClasses[i], sz, 256 ); + ClassButton *pClassButton = new ClassButton( i, sz, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y, false); + + sprintf(sz, "%s", sTFClassSelection[i]); + pClassButton->addActionSignal(new CMenuHandler_StringCommandClassSelect(sz)); + pMenu->AddButton( pClassButton ); + } + } + // Map Briefing + else if ( !strcmp( pButtonName, "!MAPBRIEFING" ) ) + { + pButton = new CommandButton(pButtonText, 0, BUTTON_SIZE_Y * m_pCurrentCommandMenu->GetNumButtons(), CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_TextWindow(MENU_MAPBRIEFING)); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Class Descriptions + else if ( !strcmp( pButtonName, "!CLASSDESC" ) ) + { + pButton = new ClassButton(0, pButtonText, 0, BUTTON_SIZE_Y * m_pCurrentCommandMenu->GetNumButtons(), CMENU_SIZE_X, BUTTON_SIZE_Y, false); + pButton->addActionSignal(new CMenuHandler_TextWindow(MENU_CLASSHELP)); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!SERVERINFO" ) ) + { + pButton = new ClassButton(0, pButtonText, 0, BUTTON_SIZE_Y * m_pCurrentCommandMenu->GetNumButtons(), CMENU_SIZE_X, BUTTON_SIZE_Y, false); + pButton->addActionSignal(new CMenuHandler_TextWindow(MENU_INTRO)); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Spy abilities + else if ( !strcmp( pButtonName, "!SPY" ) ) + { + pButton = new DisguiseButton( 0, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y ); + } + // Feign + else if ( !strcmp( pButtonName, "!FEIGN" ) ) + { + pButton = new FeignButton(FALSE, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand( "feign" )); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Feign Silently + else if ( !strcmp( pButtonName, "!FEIGNSILENT" ) ) + { + pButton = new FeignButton(FALSE, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand( "sfeign" )); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Stop Feigning + else if ( !strcmp( pButtonName, "!FEIGNSTOP" ) ) + { + pButton = new FeignButton(TRUE, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand( "feign" )); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Disguise + else if ( !strcmp( pButtonName, "!DISGUISEENEMY" ) ) + { + // Create the disguise enemy button, which active only if there are 2 teams + pButton = new DisguiseButton(DISGUISE_TEAM2, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + CreateDisguiseSubmenu( pButton, m_pCurrentCommandMenu, "disguise_enemy", iYOffset); + } + else if ( !strcmp( pButtonName, "!DISGUISEFRIENDLY" ) ) + { + // Create the disguise friendly button, which active only if there are 1 or 2 teams + pButton = new DisguiseButton(DISGUISE_TEAM1 | DISGUISE_TEAM2, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + CreateDisguiseSubmenu( pButton, m_pCurrentCommandMenu, "disguise_friendly", iYOffset ); + } + else if ( !strcmp( pButtonName, "!DISGUISE" ) ) + { + // Create the Disguise button + pButton = new DisguiseButton( DISGUISE_TEAM3 | DISGUISE_TEAM4, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + CCommandMenu *pDisguiseMenu = CreateSubMenu( pButton, m_pCurrentCommandMenu, iYOffset ); + m_pCommandMenus[m_iNumMenus] = pDisguiseMenu; + m_iNumMenus++; + + // Disguise Enemy submenu buttons + for ( int i = 1; i <= 4; i++ ) + { + // only show the 4th disguise button if we have 4 teams + m_pDisguiseButtons[i] = new DisguiseButton( ((i < 4) ? DISGUISE_TEAM3 : 0) | DISGUISE_TEAM4, "Disguise", 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + + pDisguiseMenu->AddButton( m_pDisguiseButtons[i] ); + m_pDisguiseButtons[i]->setParentMenu( pDisguiseMenu ); + + char sz[256]; + sprintf( sz, "disguise %d", i ); + CreateDisguiseSubmenu( m_pDisguiseButtons[i], pDisguiseMenu, sz, iYOffset, CMENU_SIZE_X - 1 ); + } + } + // Start setting a Detpack + else if ( !strcmp( pButtonName, "!DETPACKSTART" ) ) + { + // Detpack Submenu + pButton = new DetpackButton(2, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + + // Create the submenu + pMenu = CreateSubMenu(pButton, m_pCurrentCommandMenu, iYOffset ); + m_pCommandMenus[m_iNumMenus] = pMenu; + m_iNumMenus++; + + // Set detpack buttons + CommandButton *pDetButton; + pDetButton = new CommandButton(m_sDetpackStrings[0], 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pDetButton->addActionSignal(new CMenuHandler_StringCommand("detstart 5")); + pMenu->AddButton( pDetButton ); + pDetButton = new CommandButton(m_sDetpackStrings[1], 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pDetButton->addActionSignal(new CMenuHandler_StringCommand("detstart 20")); + pMenu->AddButton( pDetButton ); + pDetButton = new CommandButton(m_sDetpackStrings[2], 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pDetButton->addActionSignal(new CMenuHandler_StringCommand("detstart 50")); + pMenu->AddButton( pDetButton ); + } + // Stop setting a Detpack + else if ( !strcmp( pButtonName, "!DETPACKSTOP" ) ) + { + pButton = new DetpackButton(1, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand( "detstop" )); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Engineer building + else if ( !strcmp( pButtonName, "!BUILD" ) ) + { + // only appears if the player is an engineer, and either they have built something or have enough metal to build + pButton = new BuildButton( BUILDSTATE_BASE, 0, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + } + else if ( !strcmp( pButtonName, "!BUILDSENTRY" ) ) + { + pButton = new BuildButton( BUILDSTATE_CANBUILD, BuildButton::SENTRYGUN, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("build 2")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!BUILDDISPENSER" ) ) + { + pButton = new BuildButton( BUILDSTATE_CANBUILD, BuildButton::DISPENSER, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("build 1")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!ROTATESENTRY180" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::SENTRYGUN, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("rotatesentry180")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!ROTATESENTRY" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::SENTRYGUN, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("rotatesentry")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!DISMANTLEDISPENSER" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::DISPENSER, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("dismantle 1")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!DISMANTLESENTRY" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::SENTRYGUN, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("dismantle 2")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!DETONATEDISPENSER" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::DISPENSER, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("detdispenser")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + else if ( !strcmp( pButtonName, "!DETONATESENTRY" ) ) + { + pButton = new BuildButton( BUILDSTATE_HASBUILDING, BuildButton::SENTRYGUN, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("detsentry")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + // Stop building + else if ( !strcmp( pButtonName, "!BUILDSTOP" ) ) + { + pButton = new BuildButton( BUILDSTATE_BUILDING, 0, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton->addActionSignal(new CMenuHandler_StringCommand("build")); + // Create an input signal that'll popup the current menu + pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); + } + + return pButton; +} + +void TeamFortressViewport::ToggleServerBrowser() +{ + if (!m_iInitialized) + return; + + if ( !m_pServerBrowser ) + return; + + if ( m_pServerBrowser->isVisible() ) + { + m_pServerBrowser->setVisible( false ); + } + else + { + m_pServerBrowser->setVisible( true ); + } + + UpdateCursorState(); +} + +//======================================================================= +void TeamFortressViewport::ShowCommandMenu(int menuIndex) +{ + if (!m_iInitialized) + return; + + //Already have a menu open. + if ( m_pCurrentMenu ) + return; + + // is the command menu open? + if ( m_pCurrentCommandMenu == m_pCommandMenus[menuIndex] ) + { + HideCommandMenu(); + return; + } + + // Not visible while in intermission + if ( gHUD.m_iIntermission ) + return; + + // Recalculate visible menus + UpdateCommandMenu( menuIndex ); + HideVGUIMenu(); + + SetCurrentCommandMenu( m_pCommandMenus[menuIndex] ); + m_flMenuOpenTime = gHUD.m_flTime; + UpdateCursorState(); + + // get command menu parameters + for ( int i = 2; i < gEngfuncs.Cmd_Argc(); i++ ) + { + const char *param = gEngfuncs.Cmd_Argv( i - 1 ); + if ( param ) + { + if ( m_pCurrentCommandMenu->KeyInput(param[0]) ) + { + // kill the menu open time, since the key input is final + HideCommandMenu(); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Handles the key input of "-commandmenu" +// Input : +//----------------------------------------------------------------------------- +void TeamFortressViewport::InputSignalHideCommandMenu() +{ + if (!m_iInitialized) + return; + + // if they've just tapped the command menu key, leave it open + if ( (m_flMenuOpenTime + 0.3) > gHUD.m_flTime ) + return; + + HideCommandMenu(); +} + +//----------------------------------------------------------------------------- +// Purpose: Hides the command menu +//----------------------------------------------------------------------------- +void TeamFortressViewport::HideCommandMenu() +{ + if (!m_iInitialized) + return; + + if ( m_pCommandMenus[m_StandardMenu] ) + { + m_pCommandMenus[m_StandardMenu]->ClearButtonsOfArmedState(); + } + + if ( m_pCommandMenus[m_SpectatorOptionsMenu] ) + { + m_pCommandMenus[m_SpectatorOptionsMenu]->ClearButtonsOfArmedState(); + } + + if ( m_pCommandMenus[m_SpectatorCameraMenu] ) + { + m_pCommandMenus[m_SpectatorCameraMenu]->ClearButtonsOfArmedState(); + } + + m_flMenuOpenTime = 0.0f; + SetCurrentCommandMenu( NULL ); + UpdateCursorState(); +} + +//----------------------------------------------------------------------------- +// Purpose: Bring up the scoreboard +//----------------------------------------------------------------------------- +void TeamFortressViewport::ShowScoreBoard( void ) +{ + if (m_pScoreBoard) + { + // No Scoreboard in single-player + if ( gEngfuncs.GetMaxClients() > 1 ) + { + m_pScoreBoard->Open(); + UpdateCursorState(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns true if the scoreboard is up +//----------------------------------------------------------------------------- +bool TeamFortressViewport::IsScoreBoardVisible( void ) +{ + if (m_pScoreBoard) + return m_pScoreBoard->isVisible(); + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Hide the scoreboard +//----------------------------------------------------------------------------- +void TeamFortressViewport::HideScoreBoard( void ) +{ + // Prevent removal of scoreboard during intermission + if ( gHUD.m_iIntermission ) + return; + + if (m_pScoreBoard) + { + m_pScoreBoard->setVisible(false); + + GetClientVoiceMgr()->StopSquelchMode(); + + UpdateCursorState(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Activate's the player special ability +// called when the player hits their "special" key +//----------------------------------------------------------------------------- +void TeamFortressViewport::InputPlayerSpecial( void ) +{ + if (!m_iInitialized) + return; + + if ( g_iPlayerClass == PC_ENGINEER || g_iPlayerClass == PC_SPY ) + { + ShowCommandMenu( gViewPort->m_StandardMenu ); + + if ( m_pCurrentCommandMenu ) + { + m_pCurrentCommandMenu->KeyInput( '7' ); + } + } + else + { + // if it's any other class, just send the command down to the server + ClientCmd( "_special" ); + } +} + +// Set the submenu of the Command Menu +void TeamFortressViewport::SetCurrentCommandMenu( CCommandMenu *pNewMenu ) +{ + for (int i = 0; i < m_iNumMenus; i++) + m_pCommandMenus[i]->setVisible(false); + + m_pCurrentCommandMenu = pNewMenu; + + if (m_pCurrentCommandMenu) + m_pCurrentCommandMenu->MakeVisible( NULL ); +} + +void TeamFortressViewport::UpdateCommandMenu(int menuIndex) +{ + m_pCommandMenus[menuIndex]->RecalculateVisibles( 0, false ); + m_pCommandMenus[menuIndex]->RecalculatePositions( 0 ); +} + +void COM_FileBase ( const char *in, char *out); + +//====================================================================== +void TeamFortressViewport::CreateScoreBoard( void ) +{ + int xdent = SBOARD_INDENT_X, ydent = SBOARD_INDENT_Y; + if (ScreenWidth == 512) + { + xdent = SBOARD_INDENT_X_512; + ydent = SBOARD_INDENT_Y_512; + } + else if (ScreenWidth == 400) + { + xdent = SBOARD_INDENT_X_400; + ydent = SBOARD_INDENT_Y_400; + } + + m_pScoreBoard = new ScorePanel(xdent, ydent, ScreenWidth - (xdent * 2), ScreenHeight - (ydent * 2)); + m_pScoreBoard->setParent(this); + m_pScoreBoard->setVisible(false); +} + +void TeamFortressViewport::CreateServerBrowser( void ) +{ + m_pServerBrowser = new ServerBrowser( 0, 0, ScreenWidth, ScreenHeight ); + m_pServerBrowser->setParent(this); + m_pServerBrowser->setVisible(false); +} + + +//====================================================================== +// Set the VGUI Menu +void TeamFortressViewport::SetCurrentMenu( CMenuPanel *pMenu ) +{ + m_pCurrentMenu = pMenu; + if ( m_pCurrentMenu ) + { + // Don't open menus in demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + return; + + m_pCurrentMenu->Open(); + } +} + +//================================================================ +// Text Window +CMenuPanel* TeamFortressViewport::CreateTextWindow( int iTextToShow ) +{ + char sz[256]; + char *cText; + char *pfile = NULL; + static const int MAX_TITLE_LENGTH = 32; + char cTitle[MAX_TITLE_LENGTH]; + + if ( iTextToShow == SHOW_MOTD ) + { + if (!m_szServerName || !m_szServerName[0]) + strcpy( cTitle, "Half-Life" ); + else + strncpy( cTitle, m_szServerName, MAX_TITLE_LENGTH ); + cTitle[MAX_TITLE_LENGTH-1] = 0; + cText = m_szMOTD; + } + else if ( iTextToShow == SHOW_MAPBRIEFING ) + { + // Get the current mapname, and open it's map briefing text + if (m_sMapName && m_sMapName[0]) + { + strcpy( sz, "maps/"); + strcat( sz, m_sMapName ); + strcat( sz, ".txt" ); + } + else + { + const char *level = gEngfuncs.pfnGetLevelName(); + if (!level) + return NULL; + + strcpy( sz, level ); + char *ch = strchr( sz, '.' ); + *ch = '\0'; + strcat( sz, ".txt" ); + + // pull out the map name + strcpy( m_sMapName, level ); + ch = strchr( m_sMapName, '.' ); + if ( ch ) + { + *ch = 0; + } + + ch = strchr( m_sMapName, '/' ); + if ( ch ) + { + // move the string back over the '/' + memmove( m_sMapName, ch+1, strlen(ch)+1 ); + } + } + + pfile = (char*)gEngfuncs.COM_LoadFile( sz, 5, NULL ); + + if (!pfile) + return NULL; + + cText = pfile; + + strncpy( cTitle, m_sMapName, MAX_TITLE_LENGTH ); + cTitle[MAX_TITLE_LENGTH-1] = 0; + } + else if ( iTextToShow == SHOW_CLASSDESC ) + { + switch ( g_iPlayerClass ) + { + case PC_SCOUT: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_scout" ); + CHudTextMessage::LocaliseTextString( "#Title_scout", cTitle, MAX_TITLE_LENGTH ); break; + case PC_SNIPER: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_sniper" ); + CHudTextMessage::LocaliseTextString( "#Title_sniper", cTitle, MAX_TITLE_LENGTH ); break; + case PC_SOLDIER: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_soldier" ); + CHudTextMessage::LocaliseTextString( "#Title_soldier", cTitle, MAX_TITLE_LENGTH ); break; + case PC_DEMOMAN: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_demoman" ); + CHudTextMessage::LocaliseTextString( "#Title_demoman", cTitle, MAX_TITLE_LENGTH ); break; + case PC_MEDIC: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_medic" ); + CHudTextMessage::LocaliseTextString( "#Title_medic", cTitle, MAX_TITLE_LENGTH ); break; + case PC_HVYWEAP: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_hwguy" ); + CHudTextMessage::LocaliseTextString( "#Title_hwguy", cTitle, MAX_TITLE_LENGTH ); break; + case PC_PYRO: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_pyro" ); + CHudTextMessage::LocaliseTextString( "#Title_pyro", cTitle, MAX_TITLE_LENGTH ); break; + case PC_SPY: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_spy" ); + CHudTextMessage::LocaliseTextString( "#Title_spy", cTitle, MAX_TITLE_LENGTH ); break; + case PC_ENGINEER: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_engineer" ); + CHudTextMessage::LocaliseTextString( "#Title_engineer", cTitle, MAX_TITLE_LENGTH ); break; + case PC_CIVILIAN: cText = CHudTextMessage::BufferedLocaliseTextString( "#Help_civilian" ); + CHudTextMessage::LocaliseTextString( "#Title_civilian", cTitle, MAX_TITLE_LENGTH ); break; + default: + return NULL; + } + + if ( g_iPlayerClass == PC_CIVILIAN ) + { + sprintf(sz, "classes/long_civilian.txt"); + } + else + { + sprintf(sz, "classes/long_%s.txt", sTFClassSelection[ g_iPlayerClass ]); + } + char *pfile = (char*)gEngfuncs.COM_LoadFile( sz, 5, NULL ); + if (pfile) + { + cText = pfile; + } + } + else if ( iTextToShow == SHOW_SPECHELP ) + { + CHudTextMessage::LocaliseTextString( "#Spec_Help_Title", cTitle, MAX_TITLE_LENGTH ); + cTitle[MAX_TITLE_LENGTH-1] = 0; + + char *pfile = CHudTextMessage::BufferedLocaliseTextString( "#Spec_Help_Text" ); + if ( pfile ) + { + cText = pfile; + } + } + + // if we're in the game (ie. have selected a class), flag the menu to be only grayed in the dialog box, instead of full screen + CMenuPanel *pMOTDPanel = CMessageWindowPanel_Create( cText, cTitle, g_iPlayerClass == PC_UNDEFINED, false, 0, 0, ScreenWidth, ScreenHeight ); + pMOTDPanel->setParent( this ); + + if ( pfile ) + gEngfuncs.COM_FreeFile( pfile ); + + return pMOTDPanel; +} + +//================================================================ +// VGUI Menus +void TeamFortressViewport::ShowVGUIMenu( int iMenu ) +{ + CMenuPanel *pNewMenu = NULL; + + // Don't open menus in demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + return; + + // Don't open any menus except the MOTD during intermission + // MOTD needs to be accepted because it's sent down to the client + // after map change, before intermission's turned off + if ( gHUD.m_iIntermission && iMenu != MENU_INTRO ) + return; + + // Don't create one if it's already in the list + if (m_pCurrentMenu) + { + CMenuPanel *pMenu = m_pCurrentMenu; + while (pMenu != NULL) + { + if (pMenu->GetMenuID() == iMenu) + return; + pMenu = pMenu->GetNextMenu(); + } + } + + switch ( iMenu ) + { +// case MENU_TEAM: +// pNewMenu = ShowTeamMenu(); +// break; + + // Map Briefing removed now that it appears in the team menu + case MENU_MAPBRIEFING: + pNewMenu = CreateTextWindow( SHOW_MAPBRIEFING ); + break; + + case MENU_INTRO: + pNewMenu = CreateTextWindow( SHOW_MOTD ); + break; + + case MENU_CLASSHELP: + pNewMenu = CreateTextWindow( SHOW_CLASSDESC ); + break; + + case MENU_SPECHELP: + pNewMenu = CreateTextWindow( SHOW_SPECHELP ); + break; +// case MENU_CLASS: +// pNewMenu = ShowClassMenu(); +// break; + + default: + break; + } + + if (!pNewMenu) + return; + + // Close the Command Menu if it's open + HideCommandMenu(); + + pNewMenu->SetMenuID( iMenu ); + pNewMenu->SetActive( true ); + pNewMenu->setParent(this); + + // See if another menu is visible, and if so, cache this one for display once the other one's finished + if (m_pCurrentMenu) + { + if ( m_pCurrentMenu->GetMenuID() == MENU_CLASS && iMenu == MENU_TEAM ) + { + CMenuPanel *temp = m_pCurrentMenu; + m_pCurrentMenu->Close(); + m_pCurrentMenu = pNewMenu; + m_pCurrentMenu->SetNextMenu( temp ); + m_pCurrentMenu->Open(); + UpdateCursorState(); + } + else + { + m_pCurrentMenu->SetNextMenu( pNewMenu ); + } + } + else + { + m_pCurrentMenu = pNewMenu; + m_pCurrentMenu->Open(); + UpdateCursorState(); + } +} + +// Removes all VGUI Menu's onscreen +void TeamFortressViewport::HideVGUIMenu() +{ + while (m_pCurrentMenu) + { + HideTopMenu(); + } +} + +// Remove the top VGUI menu, and bring up the next one +void TeamFortressViewport::HideTopMenu() +{ + if (m_pCurrentMenu) + { + // Close the top one + m_pCurrentMenu->Close(); + + // Bring up the next one + gViewPort->SetCurrentMenu( m_pCurrentMenu->GetNextMenu() ); + } + + UpdateCursorState(); +} + +// Return TRUE if the HUD's allowed to print text messages +bool TeamFortressViewport::AllowedToPrintText( void ) +{ + // Prevent text messages when fullscreen menus are up + if ( m_pCurrentMenu && g_iPlayerClass == 0 ) + { + int iId = m_pCurrentMenu->GetMenuID(); + if ( iId == MENU_TEAM || iId == MENU_CLASS || iId == MENU_INTRO || iId == MENU_CLASSHELP ) + return FALSE; + } + + return TRUE; +} + +//====================================================================================== +// TEAM MENU +//====================================================================================== +// Bring up the Team selection Menu +/*CMenuPanel* TeamFortressViewport::ShowTeamMenu() +{ + // Don't open menus in demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + return NULL; + + m_pTeamMenu->Reset(); + return m_pTeamMenu; +} + +void TeamFortressViewport::CreateTeamMenu() +{ + // Create the panel + m_pTeamMenu = new CTeamMenuPanel(100, false, 0, 0, ScreenWidth, ScreenHeight); + m_pTeamMenu->setParent( this ); + m_pTeamMenu->setVisible( false ); +}*/ + +//====================================================================================== +// CLASS MENU +//====================================================================================== +// Bring up the Class selection Menu +/*CMenuPanel* TeamFortressViewport::ShowClassMenu() +{ + // Don't open menus in demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() ) + return NULL; + + m_pClassMenu->Reset(); + return m_pClassMenu; +} + +void TeamFortressViewport::CreateClassMenu() +{ + // Create the panel + m_pClassMenu = new CClassMenuPanel(100, false, 0, 0, ScreenWidth, ScreenHeight); + m_pClassMenu->setParent(this); + m_pClassMenu->setVisible( false ); +}*/ + +//====================================================================================== +//====================================================================================== +// SPECTATOR MENU +//====================================================================================== +// Spectator "Menu" explaining the Spectator buttons +/*void TeamFortressViewport::CreateSpectatorMenu() +{ + // Create the Panel + m_pSpectatorPanel = new SpectatorPanel(0, 0, ScreenWidth, ScreenHeight); + m_pSpectatorPanel->setParent(this); + m_pSpectatorPanel->setVisible(false); + m_pSpectatorPanel->Initialize(); +}*/ + +//====================================================================================== +// UPDATE HUD SECTIONS +//====================================================================================== +// We've got an update on player info +// Recalculate any menus that use it. +void TeamFortressViewport::UpdateOnPlayerInfo() +{ +// if (m_pTeamMenu) +// m_pTeamMenu->Update(); +// if (m_pClassMenu) +// m_pClassMenu->Update(); + if (m_pScoreBoard) + m_pScoreBoard->Update(); +} + +void TeamFortressViewport::UpdateCursorState() +{ + // Need cursor if any VGUI window is up +// if ( /*m_pSpectatorPanel->m_menuVisible ||*/ m_pCurrentMenu || /*m_pTeamMenu->isVisible() ||*/ m_pServerBrowser->isVisible() || GetClientVoiceMgr()->IsInSquelchMode() ) + if (m_pParanoiaText && m_pParanoiaText->isVisible()) + { + g_iVisibleMouse = true; + App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::SchemeCursor::scu_arrow) ); + return; + } + else if ( m_pCurrentCommandMenu ) + { + // commandmenu doesn't have cursor if hud_capturemouse is turned off + if ( gHUD.m_pCvarStealMouse->value != 0.0f ) + { + g_iVisibleMouse = true; + App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::SchemeCursor::scu_arrow) ); + return; + } + } + + // Don't reset mouse in demo playback + if ( !gEngfuncs.pDemoAPI->IsPlayingback() ) + { + IN_ResetMouse(); + } + + g_iVisibleMouse = false; + App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::SchemeCursor::scu_none) ); +} + +void TeamFortressViewport::UpdateHighlights() +{ + if (m_pCurrentCommandMenu) + m_pCurrentCommandMenu->MakeVisible( NULL ); +} + +void TeamFortressViewport::GetAllPlayersInfo( void ) +{ + for ( int i = 1; i < MAX_PLAYERS; i++ ) + { + GetPlayerInfo( i, &g_PlayerInfoList[i] ); + + if ( g_PlayerInfoList[i].thisplayer ) + m_pScoreBoard->m_iPlayerNum = i; // !!!HACK: this should be initialized elsewhere... maybe gotten from the engine + } +} + +void TeamFortressViewport::paintBackground() +{ + if (m_pScoreBoard) + { + int x, y; + getApp()->getCursorPos(x, y); + m_pScoreBoard->cursorMoved(x, y, m_pScoreBoard); + } + + // See if the command menu is visible and needs recalculating due to some external change + if ( g_iTeamNumber != m_iCurrentTeamNumber ) + { + UpdateCommandMenu( m_StandardMenu ); + +// if ( m_pClassMenu ) +// { +// m_pClassMenu->Update(); +// } + + m_iCurrentTeamNumber = g_iTeamNumber; + } + + if ( g_iPlayerClass != m_iCurrentPlayerClass ) + { + UpdateCommandMenu( m_StandardMenu ); + + m_iCurrentPlayerClass = g_iPlayerClass; + } + + // See if the Spectator Menu needs to be update +// if ( ( g_iUser1 != m_iUser1 || g_iUser2 != m_iUser2 ) || +// ( m_flSpectatorPanelLastUpdated < gHUD.m_flTime ) ) +// { +// UpdateSpectatorPanel(); +// } + + // Update the Scoreboard, if it's visible + if ( m_pScoreBoard->isVisible() && (m_flScoreBoardLastUpdated < gHUD.m_flTime) ) + { + m_pScoreBoard->Update(); + m_flScoreBoardLastUpdated = gHUD.m_flTime + 0.5; + } + + int extents[4]; + getAbsExtents(extents[0],extents[1],extents[2],extents[3]); + VGui_ViewportPaintBackground(extents); +} + +//================================================================ +// Input Handler for Drag N Drop panels +void CDragNDropHandler::cursorMoved(int x,int y,Panel* panel) +{ + if(m_bDragging) + { + App::getInstance()->getCursorPos(x,y); + m_pPanel->setPos(m_iaDragOrgPos[0]+(x-m_iaDragStart[0]),m_iaDragOrgPos[1]+(y-m_iaDragStart[1])); + + if(m_pPanel->getParent()!=null) + { + m_pPanel->getParent()->repaint(); + } + } +} + +void CDragNDropHandler::mousePressed(MouseCode code,Panel* panel) +{ + int x,y; + App::getInstance()->getCursorPos(x,y); + m_bDragging=true; + m_iaDragStart[0]=x; + m_iaDragStart[1]=y; + m_pPanel->getPos(m_iaDragOrgPos[0],m_iaDragOrgPos[1]); + App::getInstance()->setMouseCapture(panel); + + m_pPanel->setDragged(m_bDragging); + m_pPanel->requestFocus(); +} + +void CDragNDropHandler::mouseReleased(MouseCode code,Panel* panel) +{ + m_bDragging=false; + m_pPanel->setDragged(m_bDragging); + App::getInstance()->setMouseCapture(null); +} + +//================================================================ +// Number Key Input +bool TeamFortressViewport::SlotInput( int iSlot ) +{ + // If there's a menu up, give it the input + if ( m_pCurrentMenu ) + return m_pCurrentMenu->SlotInput( iSlot ); + + return FALSE; +} + +// Direct Key Input +int TeamFortressViewport::KeyInput( int down, int keynum, const char *pszCurrentBinding ) +{ + // buz + if (m_pParanoiaText && m_pParanoiaText->isVisible()) + return m_pParanoiaText->KeyInput(down, keynum, pszCurrentBinding); + + // buz + if ( down && m_pTabPanel && ( keynum == K_TAB ) && gEngfuncs.GetMaxClients() == 1 ) + return m_pTabPanel->Toggle(); + + // Enter gets out of Spectator Mode by bringing up the Team Menu + if (m_iUser1 && gEngfuncs.Con_IsVisible() == false ) + { + if ( down && (keynum == K_ENTER || keynum == K_KP_ENTER) ) + ShowVGUIMenu( MENU_TEAM ); + } + + // Open Text Window? + if (m_pCurrentMenu && gEngfuncs.Con_IsVisible() == false) + { + int iMenuID = m_pCurrentMenu->GetMenuID(); + + // Get number keys as Input for Team/Class menus + if (iMenuID == MENU_TEAM || iMenuID == MENU_CLASS) + { + // Escape gets you out of Team/Class menus if the Cancel button is visible + if ( keynum == K_ESCAPE ) + { + if ( (iMenuID == MENU_TEAM && g_iTeamNumber) || (iMenuID == MENU_CLASS && g_iPlayerClass) ) + { + HideTopMenu(); + return 0; + } + } + + for (int i = '0'; i <= '9'; i++) + { + if ( down && (keynum == i) ) + { + SlotInput( i - '0' ); + return 0; + } + } + } + + // Grab enter keys to close TextWindows + if ( down && (keynum == K_ENTER || keynum == K_KP_ENTER || keynum == K_SPACE || keynum == K_ESCAPE) ) + { + if ( iMenuID == MENU_MAPBRIEFING || iMenuID == MENU_INTRO || iMenuID == MENU_CLASSHELP ) + { + HideTopMenu(); + return 0; + } + } + + // Grab jump key on Team Menu as autoassign +/* if ( pszCurrentBinding && down && !strcmp(pszCurrentBinding, "+jump") ) + { + if (iMenuID == MENU_TEAM) + { + m_pTeamMenu->SlotInput(5); + return 0; + } + }*/ + + } + + // if we're in a command menu, try hit one of it's buttons + if ( down && m_pCurrentCommandMenu ) + { + // Escape hides the command menu + if ( keynum == K_ESCAPE ) + { + HideCommandMenu(); + return 0; + } + + // only trap the number keys + if ( keynum >= '0' && keynum <= '9' ) + { + if ( m_pCurrentCommandMenu->KeyInput(keynum) ) + { + // a final command has been issued, so close the command menu + HideCommandMenu(); + } + + return 0; + } + } + + return 1; +} + +//================================================================ +// Message Handlers +int TeamFortressViewport::MsgFunc_ValClass(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + for (int i = 0; i < 5; i++) + m_iValidClasses[i] = READ_SHORT(); + + // Force the menu to update + UpdateCommandMenu( m_StandardMenu ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_TeamNames(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iNumberOfTeams = READ_BYTE(); + + for (int i = 0; i < m_iNumberOfTeams; i++) + { + int teamNum = i + 1; + + gHUD.m_TextMessage.LocaliseTextString( READ_STRING(), m_sTeamNames[teamNum], MAX_TEAMNAME_SIZE ); + + // Set the team name buttons + if (m_pTeamButtons[i]) + m_pTeamButtons[i]->setText( m_sTeamNames[teamNum] ); + + // range check this value...m_pDisguiseButtons[5]; + if ( teamNum < 5 ) + { + // Set the disguise buttons + if ( m_pDisguiseButtons[teamNum] ) + m_pDisguiseButtons[teamNum]->setText( m_sTeamNames[teamNum] ); + } + } + + // Update the Team Menu +// if (m_pTeamMenu) +// m_pTeamMenu->Update(); + + return 1; +} + +int TeamFortressViewport::MsgFunc_Feign(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iIsFeigning = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu( m_StandardMenu ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_Detpack(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iIsSettingDetpack = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu( m_StandardMenu ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_VGUIMenu(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + int iMenu = READ_BYTE(); + + // Map briefing includes the name of the map (because it's sent down before the client knows what map it is) + if (iMenu == MENU_MAPBRIEFING) + { + strncpy( m_sMapName, READ_STRING(), sizeof(m_sMapName) ); + m_sMapName[ sizeof(m_sMapName) - 1 ] = '\0'; + } + + // Bring up the menu6 + ShowVGUIMenu( iMenu ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_MOTD( const char *pszName, int iSize, void *pbuf ) +{ + if (m_iGotAllMOTD) + m_szMOTD[0] = 0; + + BEGIN_READ( pbuf, iSize ); + + m_iGotAllMOTD = READ_BYTE(); + + int roomInArray = sizeof(m_szMOTD) - strlen(m_szMOTD) - 1; + + strncat( m_szMOTD, READ_STRING(), roomInArray >= 0 ? roomInArray : 0 ); + m_szMOTD[ sizeof(m_szMOTD)-1 ] = '\0'; + + // don't show MOTD for HLTV spectators + if ( m_iGotAllMOTD && !gEngfuncs.IsSpectateOnly() ) + { + ShowVGUIMenu( MENU_INTRO ); + } + + return 1; +} + +int TeamFortressViewport::MsgFunc_BuildSt( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iBuildState = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu( m_StandardMenu ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_RandomPC( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iRandomPC = READ_BYTE(); + + return 1; +} + +int TeamFortressViewport::MsgFunc_ServerName( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + strncpy( m_szServerName, READ_STRING(), MAX_SERVERNAME_LENGTH ); + + return 1; +} + +int TeamFortressViewport::MsgFunc_ScoreInfo( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + short cl = READ_BYTE(); + short frags = READ_SHORT(); + short deaths = READ_SHORT(); + short playerclass = READ_SHORT(); + short teamnumber = READ_SHORT(); + + if ( cl > 0 && cl <= MAX_PLAYERS ) + { + g_PlayerExtraInfo[cl].frags = frags; + g_PlayerExtraInfo[cl].deaths = deaths; + g_PlayerExtraInfo[cl].playerclass = playerclass; + g_PlayerExtraInfo[cl].teamnumber = teamnumber; + + //Dont go bellow 0! + if ( g_PlayerExtraInfo[cl].teamnumber < 0 ) + g_PlayerExtraInfo[cl].teamnumber = 0; + + UpdateOnPlayerInfo(); + } + + return 1; +} + +// Message handler for TeamScore message +// accepts three values: +// string: team name +// short: teams kills +// short: teams deaths +// if this message is never received, then scores will simply be the combined totals of the players. +int TeamFortressViewport::MsgFunc_TeamScore( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + char *TeamName = READ_STRING(); + + // find the team matching the name + for ( int i = 1; i <= m_pScoreBoard->m_iNumTeams; i++ ) + { + if ( !stricmp( TeamName, g_TeamInfo[i].name ) ) + break; + } + + if ( i > m_pScoreBoard->m_iNumTeams ) + return 1; + + // use this new score data instead of combined player scoresw + g_TeamInfo[i].scores_overriden = TRUE; + g_TeamInfo[i].frags = READ_SHORT(); + g_TeamInfo[i].deaths = READ_SHORT(); + + return 1; +} + +// Message handler for TeamInfo message +// accepts two values: +// byte: client number +// string: client team name +int TeamFortressViewport::MsgFunc_TeamInfo( const char *pszName, int iSize, void *pbuf ) +{ + if (!m_pScoreBoard) + return 1; + + BEGIN_READ( pbuf, iSize ); + short cl = READ_BYTE(); + + if ( cl > 0 && cl <= MAX_PLAYERS ) + { + // set the players team + strncpy( g_PlayerExtraInfo[cl].teamname, READ_STRING(), MAX_TEAM_NAME ); + } + + // rebuild the list of teams + m_pScoreBoard->RebuildTeams(); + + return 1; +} + +void TeamFortressViewport::DeathMsg( int killer, int victim ) +{ + m_pScoreBoard->DeathMsg(killer,victim); +} + +int TeamFortressViewport::MsgFunc_Spectator( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + short cl = READ_BYTE(); + if ( cl > 0 && cl <= MAX_PLAYERS ) + { + g_IsSpectator[cl] = READ_BYTE(); + } + + return 1; +} + +int TeamFortressViewport::MsgFunc_AllowSpec( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + m_iAllowSpectators = READ_BYTE(); + + // Force the menu to update + UpdateCommandMenu( m_StandardMenu ); + + // If the team menu is up, update it too +// if (m_pTeamMenu) +// m_pTeamMenu->Update(); + + return 1; +} + + +// buz: text windows +int TeamFortressViewport::MsgFunc_ShowTextWindow(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + + // only one parameter - text file name + char* str = READ_STRING(); + + if (m_pParanoiaText) + { + if (strcmp(m_pParanoiaText->m_loadedFileName, str)) + { + removeChild(m_pParanoiaText); + m_pParanoiaText = new CParanoiaTextPanel(str); + m_pParanoiaText->setParent(this); + } + } + else + { + m_pParanoiaText = new CParanoiaTextPanel(str); + m_pParanoiaText->setParent(this); + } + + m_pParanoiaText->setVisible(true); + m_pParanoiaText->ResetBackground(); + UpdateCursorState(); + return 1; +} + +//=========================================== +// buz: pickup messages system +// +// works similar to half-life's ammohistory, but uses text instead of images +//=========================================== + +void TeamFortressViewport::CreatePickupMessagePanels() +{ + m_iCurrentPickupMessage = 0; + int h = PICKUP_MESSAGES_MINHEIGHT; + for (int i = 0; i < MAX_PICKUP_MESSAGES; i++) + { + m_pPickupMessages[i] = new CPickupMessage( h ); + m_pPickupMessages[i]->setParent(this); + h -= PICKUP_MESSAGES_DISTANCE; + } +} + + +// hide all messages when another map starts +void TeamFortressViewport::InitializePickupMessagePanels() +{ + m_iCurrentPickupMessage = 0; + for (int i = 0; i < MAX_PICKUP_MESSAGES; i++) + { + if (m_pPickupMessages[i]) + m_pPickupMessages[i]->Initialize(); + } +} + + +// reset m_iCurrentPickupMessage to zero when no active messages +void TeamFortressViewport::CheckCurrentPickupMessagePanel() +{ + for (int i = 0; i < MAX_PICKUP_MESSAGES; i++) + { + if (m_pPickupMessages[i]) + { + if (m_pPickupMessages[i]->isVisible()) + return; + } + } + + m_iCurrentPickupMessage = 0; +} + +void CheckPanel() +{ + gViewPort->CheckCurrentPickupMessagePanel(); +} + + +void TeamFortressViewport::AddPickupMessage( client_textmessage_t *msg ) +{ + CPickupMessage *msgpanel = m_pPickupMessages[m_iCurrentPickupMessage]; + if (msgpanel) + { + msgpanel->SetMessage(msg); + m_iCurrentPickupMessage++; + if (m_iCurrentPickupMessage >= MAX_PICKUP_MESSAGES) + m_iCurrentPickupMessage = 0; + } + else + CONPRINT("ACHTUNG! pickup msgpanel is not created!\n"); +} + + +// link to world +void VGuiAddPickupMessage( client_textmessage_t *msg ) +{ + if (gViewPort) + gViewPort->AddPickupMessage( msg ); + else + gEngfuncs.Con_Printf("Pickup message error: Viewport is not constructed!\n"); +} \ No newline at end of file diff --git a/cl_dll/vgui_TeamFortressViewport.h b/cl_dll/vgui_TeamFortressViewport.h new file mode 100644 index 0000000..4beb6e0 --- /dev/null +++ b/cl_dll/vgui_TeamFortressViewport.h @@ -0,0 +1,1830 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef TEAMFORTRESSVIEWPORT_H +#define TEAMFORTRESSVIEWPORT_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vgui_subtitles.h" // buz +#include "vgui_radio.h" // buz +#include "vgui_pickup.h" // buz +//#include "vgui_hud.h" // buz + +#define MAX_PICKUP_MESSAGES 14 +#define PICKUP_MESSAGES_DISTANCE (YRES(25)) +#define PICKUP_MESSAGES_MINHEIGHT (YRES(370)) + +// Defines for the playerclass +#define PC_UNDEFINED 0 + +#define PC_SCOUT 1 +#define PC_SNIPER 2 +#define PC_SOLDIER 3 +#define PC_DEMOMAN 4 +#define PC_MEDIC 5 +#define PC_HVYWEAP 6 +#define PC_PYRO 7 +#define PC_SPY 8 +#define PC_ENGINEER 9 +#define PC_RANDOM 10 // Random playerclass +#define PC_CIVILIAN 11 // Civilians are a special class. They cannot + // be chosen by players, only enforced by maps +#define PC_LASTCLASS 12 // Use this as the high-boundary for any loops + // through the playerclass. + +/*======================*/ +// Menu stuff // +/*======================*/ + +#define MENU_DEFAULT 1 +#define MENU_TEAM 2 +#define MENU_CLASS 3 +#define MENU_MAPBRIEFING 4 +#define MENU_INTRO 5 +#define MENU_CLASSHELP 6 +#define MENU_CLASSHELP2 7 +#define MENU_REPEATHELP 8 +#define MENU_SPECHELP 9 + +#define MENU_SPY 12 +#define MENU_SPY_SKIN 13 +#define MENU_SPY_COLOR 14 +#define MENU_ENGINEER 15 +#define MENU_ENGINEER_FIX_DISPENSER 16 +#define MENU_ENGINEER_FIX_SENTRYGUN 17 +#define MENU_ENGINEER_FIX_MORTAR 18 +#define MENU_DISPENSER 19 +#define MENU_CLASS_CHANGE 20 +#define MENU_TEAM_CHANGE 21 +#define MENU_REFRESH_RATE 25 +#define MENU_VOICETWEAK 50 + +// Legal Playerclass Handling +#define TF_ILL_SCOUT 1 +#define TF_ILL_SNIPER 2 +#define TF_ILL_SOLDIER 4 +#define TF_ILL_DEMOMAN 8 +#define TF_ILL_MEDIC 16 +#define TF_ILL_HVYWEP 32 +#define TF_ILL_PYRO 64 +#define TF_ILL_RANDOMPC 128 +#define TF_ILL_SPY 256 +#define TF_ILL_ENGINEER 512 + +// Building metal costs +#define BUILD_COST_DISPENSER 100 // Metal needed to built +#define BUILD_COST_SENTRYGUN 130 +#define BUILD_COST_MORTAR 150 +#define BUILD_COST_TELEPORTER 125 + +#define BUILD_COST_SANDBAG 20 // Built with a separate alias + +// Build state sent down to client +#define BS_BUILDING (1<<0) +#define BS_HAS_DISPENSER (1<<1) +#define BS_HAS_SENTRYGUN (1<<2) +#define BS_CANB_DISPENSER (1<<3) +#define BS_CANB_SENTRYGUN (1<<4) + +// custom scheme handling +#include "vgui_SchemeManager.h" + +using namespace vgui; + +class Cursor; +class ScorePanel; +class SpectatorPanel; +class CCommandMenu; +class CommandLabel; +class CommandButton; +class BuildButton; +class ClassButton; +class CMenuPanel; +class ServerBrowser; +class DragNDropPanel; +class CTransparentPanel; +class CClassMenuPanel; +class CTeamMenuPanel; + +// buz +class CParanoiaTextPanel; +class CHud2; +class CTabPanel; +class CTips; +class CScreenMessage; +class CGammaView; + +// Wargon: Ñêðîëëÿùèéñÿ òåêñò. +class CScrollingMessage; + +char* GetVGUITGAName(const char *pszName); +BitmapTGA *LoadTGAForRes(const char* pImageName); +void ScaleColors( int &r, int &g, int &b, int a ); +extern char *sTFClassSelection[]; +extern int sTFValidClassInts[]; +extern char *sLocalisedClasses[]; +extern int iTeamColors[5][3]; +extern int iNumberOfTeamColors; + +#define MAX_SERVERNAME_LENGTH 32 + +// Command Menu positions +#define MAX_MENUS 80 +#define MAX_BUTTONS 100 + +#define BUTTON_SIZE_Y YRES(30) +#define CMENU_SIZE_X XRES(160) + +#define SUBMENU_SIZE_X (CMENU_SIZE_X / 8) +#define SUBMENU_SIZE_Y (BUTTON_SIZE_Y / 6) + +#define CMENU_TOP (BUTTON_SIZE_Y * 4) + +#define MAX_TEAMNAME_SIZE 64 +#define MAX_BUTTON_SIZE 32 + +// Map Briefing Window +#define MAPBRIEF_INDENT 30 + +// Team Menu +#define TMENU_INDENT_X (30 * ((float)ScreenHeight / 640)) +#define TMENU_HEADER 100 +#define TMENU_SIZE_X (ScreenWidth - (TMENU_INDENT_X * 2)) +#define TMENU_SIZE_Y (TMENU_HEADER + BUTTON_SIZE_Y * 7) +#define TMENU_PLAYER_INDENT (((float)TMENU_SIZE_X / 3) * 2) +#define TMENU_INDENT_Y (((float)ScreenHeight - TMENU_SIZE_Y) / 2) + +// Class Menu +#define CLMENU_INDENT_X (30 * ((float)ScreenHeight / 640)) +#define CLMENU_HEADER 100 +#define CLMENU_SIZE_X (ScreenWidth - (CLMENU_INDENT_X * 2)) +#define CLMENU_SIZE_Y (CLMENU_HEADER + BUTTON_SIZE_Y * 11) +#define CLMENU_PLAYER_INDENT (((float)CLMENU_SIZE_X / 3) * 2) +#define CLMENU_INDENT_Y (((float)ScreenHeight - CLMENU_SIZE_Y) / 2) + +// Arrows +enum +{ + ARROW_UP, + ARROW_DOWN, + ARROW_LEFT, + ARROW_RIGHT, +}; + +//============================================================================== +// VIEWPORT PIECES +//============================================================ +// Wrapper for an Image Label without a background +class CImageLabel : public Label +{ +public: + BitmapTGA *m_pTGA; + +public: + void LoadImage(const char * pImageName); + CImageLabel( const char* pImageName,int x,int y ); + CImageLabel( const char* pImageName,int x,int y,int wide,int tall ); + + virtual int getImageTall(); + virtual int getImageWide(); + + virtual void paintBackground() + { + // Do nothing, so the background's left transparent. + } +}; + +// Command Label +// Overridden label so we can darken it when submenus open +class CommandLabel : public Label +{ +private: + int m_iState; + +public: + CommandLabel(const char* text,int x,int y,int wide,int tall) : Label(text,x,y,wide,tall) + { + m_iState = false; + } + + void PushUp() + { + m_iState = false; + repaint(); + } + + void PushDown() + { + m_iState = true; + repaint(); + } +}; + +//============================================================ +// Command Buttons +class CommandButton : public Button +{ +private: + int m_iPlayerClass; + bool m_bFlat; + + // Submenus under this button + CCommandMenu *m_pSubMenu; + CCommandMenu *m_pParentMenu; + CommandLabel *m_pSubLabel; + + char m_sMainText[MAX_BUTTON_SIZE]; + char m_cBoundKey; + + SchemeHandle_t m_hTextScheme; + + void RecalculateText( void ); + +public: + bool m_bNoHighlight; + +public: + CommandButton(const char* text,int x,int y,int wide,int tall, bool bNoHighlight, bool bFlat); + // Constructors + CommandButton( const char* text,int x,int y,int wide,int tall, bool bNoHighlight = false); + CommandButton( int iPlayerClass, const char* text,int x,int y,int wide,int tall, bool bFlat ); + + void Init( void ); + + // Menu Handling + void AddSubMenu( CCommandMenu *pNewMenu ); + void AddSubLabel( CommandLabel *pSubLabel ) + { + m_pSubLabel = pSubLabel; + } + + virtual int IsNotValid( void ) + { + return false; + } + + void UpdateSubMenus( int iAdjustment ); + int GetPlayerClass() { return m_iPlayerClass; }; + CCommandMenu *GetSubMenu() { return m_pSubMenu; }; + + CCommandMenu *getParentMenu( void ); + void setParentMenu( CCommandMenu *pParentMenu ); + + // Overloaded vgui functions + virtual void paint(); + virtual void setText( const char *text ); + virtual void paintBackground(); + + void cursorEntered( void ); + void cursorExited( void ); + + void setBoundKey( char boundKey ); + char getBoundKey( void ); +}; + +class ColorButton : public CommandButton +{ +private: + + Color *ArmedColor; + Color *UnArmedColor; + + Color *ArmedBorderColor; + Color *UnArmedBorderColor; + +public: + ColorButton( const char* text,int x,int y,int wide,int tall, bool bNoHighlight, bool bFlat ) : + CommandButton( text, x, y, wide, tall, bNoHighlight, bFlat ) + { + ArmedColor = NULL; + UnArmedColor = NULL; + ArmedBorderColor = NULL; + UnArmedBorderColor = NULL; + } + + + virtual void paintBackground() + { + int r, g, b, a; + Color bgcolor; + + if ( isArmed() ) + { + // Highlight background + /* getBgColor(bgcolor); + bgcolor.getColor(r, g, b, a); + drawSetColor( r,g,b,a ); + drawFilledRect(0,0,_size[0],_size[1]);*/ + + if ( ArmedBorderColor ) + { + ArmedBorderColor->getColor( r, g, b, a); + drawSetColor( r, g, b, a ); + drawOutlinedRect(0,0,_size[0],_size[1]); + } + } + else + { + if ( UnArmedBorderColor ) + { + UnArmedBorderColor->getColor( r, g, b, a); + drawSetColor( r, g, b, a ); + drawOutlinedRect(0,0,_size[0],_size[1]); + } + } + } + void paint() + { + int r, g, b, a; + if ( isArmed() ) + { + if (ArmedColor) + { + ArmedColor->getColor(r, g, b, a); + setFgColor(r, g, b, a); + } + else + setFgColor( Scheme::sc_secondary2 ); + } + else + { + if (UnArmedColor) + { + UnArmedColor->getColor(r, g, b, a); + setFgColor(r, g, b, a); + } + else + setFgColor( Scheme::sc_primary1 ); + } + + Button::paint(); + } + + void setArmedColor ( int r, int g, int b, int a ) + { + ArmedColor = new Color( r, g, b, a ); + } + + void setUnArmedColor ( int r, int g, int b, int a ) + { + UnArmedColor = new Color( r, g, b, a ); + } + + void setArmedBorderColor ( int r, int g, int b, int a ) + { + ArmedBorderColor = new Color( r, g, b, a ); + } + + void setUnArmedBorderColor ( int r, int g, int b, int a ) + { + UnArmedBorderColor = new Color( r, g, b, a ); + } +}; + +class SpectButton : public CommandButton +{ +private: + +public: + SpectButton( int iPlayerClass, const char* text,int x,int y,int wide,int tall ) : + CommandButton( text, x, y, wide, tall, false) + { + Init(); + + setText( text ); + } + + virtual void paintBackground() + { + if ( isArmed()) + { + drawSetColor( 143,143, 54, 125 ); + drawFilledRect( 5, 0,_size[0] - 5,_size[1]); + } + } + + virtual void paint() + { + + if ( isArmed() ) + { + setFgColor( 194, 202, 54, 0 ); + } + else + { + setFgColor( 143, 143, 54, 15 ); + } + + Button::paint(); + } +}; +//============================================================ +// Command Menus +class CCommandMenu : public Panel +{ +private: + CCommandMenu *m_pParentMenu; + int m_iXOffset; + int m_iYOffset; + + // Buttons in this menu + CommandButton *m_aButtons[ MAX_BUTTONS ]; + int m_iButtons; + + // opens menu from top to bottom (0 = default), or from bottom to top (1)? + int m_iDirection; +public: + CCommandMenu( CCommandMenu *pParentMenu, int x,int y,int wide,int tall ) : Panel(x,y,wide,tall) + { + m_pParentMenu = pParentMenu; + m_iXOffset = x; + m_iYOffset = y; + m_iButtons = 0; + m_iDirection = 0; + } + + CCommandMenu( CCommandMenu *pParentMenu, int direction, int x,int y,int wide,int tall ) : Panel(x,y,wide,tall) + { + m_pParentMenu = pParentMenu; + m_iXOffset = x; + m_iYOffset = y; + m_iButtons = 0; + m_iDirection = direction; + } + + float m_flButtonSizeY; + int m_iSpectCmdMenu; + void AddButton( CommandButton *pButton ); + bool RecalculateVisibles( int iNewYPos, bool bHideAll ); + void RecalculatePositions( int iYOffset ); + void MakeVisible( CCommandMenu *pChildMenu ); + + CCommandMenu *GetParentMenu() { return m_pParentMenu; }; + int GetXOffset() { return m_iXOffset; }; + int GetYOffset() { return m_iYOffset; }; + int GetDirection() { return m_iDirection; }; + int GetNumButtons() { return m_iButtons; }; + CommandButton *FindButtonWithSubmenu( CCommandMenu *pSubMenu ); + + void ClearButtonsOfArmedState( void ); + + + bool KeyInput( int keyNum ); + + virtual void paintBackground(); +}; + +//============================================================================== +class TeamFortressViewport : public Panel +{ +private: + vgui::Cursor* _cursorNone; + vgui::Cursor* _cursorArrow; + + int m_iInitialized; + + CCommandMenu *m_pCommandMenus[ MAX_MENUS ]; + CCommandMenu *m_pCurrentCommandMenu; + float m_flMenuOpenTime; + float m_flScoreBoardLastUpdated; + float m_flSpectatorPanelLastUpdated; + int m_iNumMenus; + int m_iCurrentTeamNumber; + int m_iCurrentPlayerClass; + int m_iUser1; + int m_iUser2; + int m_iUser3; + + // VGUI Menus +// void CreateTeamMenu( void ); +// CMenuPanel* ShowTeamMenu( void ); +// void CreateClassMenu( void ); +// CMenuPanel* ShowClassMenu( void ); +// void CreateSpectatorMenu( void ); + + // Scheme handler + CSchemeManager m_SchemeManager; + + // MOTD + int m_iGotAllMOTD; + char m_szMOTD[ MAX_MOTD_LENGTH ]; + + // Command Menu Team buttons + CommandButton *m_pTeamButtons[6]; + CommandButton *m_pDisguiseButtons[5]; + BuildButton *m_pBuildButtons[3]; + BuildButton *m_pBuildActiveButtons[3]; + + // Server Browser + ServerBrowser *m_pServerBrowser; + + int m_iAllowSpectators; + + // Data for specific sections of the Command Menu + int m_iValidClasses[5]; + int m_iIsFeigning; + int m_iIsSettingDetpack; + int m_iNumberOfTeams; + int m_iBuildState; + int m_iRandomPC; + char m_sTeamNames[5][MAX_TEAMNAME_SIZE]; + + // Localisation strings + char m_sDetpackStrings[3][MAX_BUTTON_SIZE]; + + char m_sMapName[64]; +public: + TeamFortressViewport(int x,int y,int wide,int tall); + void Initialize( void ); + + int CreateCommandMenu( char * menuFile, int direction, int yOffset, bool flatDesign, float flButtonSizeX, float flButtonSizeY, int xOffset ); + void CreateScoreBoard( void ); + void CreateServerBrowser( void ); + CommandButton * CreateCustomButton( char *pButtonText, char * pButtonName, int iYOffset ); + CCommandMenu * CreateDisguiseSubmenu( CommandButton *pButton, CCommandMenu *pParentMenu, const char *commandText, int iYOffset, int iXOffset = 0 ); + + void UpdateCursorState( void ); + void UpdateCommandMenu(int menuIndex); + void UpdateOnPlayerInfo( void ); + void UpdateHighlights( void ); +// void UpdateSpectatorPanel( void ); + + int KeyInput( int down, int keynum, const char *pszCurrentBinding ); + void InputPlayerSpecial( void ); + void GetAllPlayersInfo( void ); + void DeathMsg( int killer, int victim ); + + void ShowCommandMenu(int menuIndex); + void InputSignalHideCommandMenu( void ); + void HideCommandMenu( void ); + void SetCurrentCommandMenu( CCommandMenu *pNewMenu ); + void SetCurrentMenu( CMenuPanel *pMenu ); + + void ShowScoreBoard( void ); + void HideScoreBoard( void ); + bool IsScoreBoardVisible( void ); + + bool AllowedToPrintText( void ); + + void ShowVGUIMenu( int iMenu ); + void HideVGUIMenu( void ); + void HideTopMenu( void ); + + void ToggleServerBrowser( void ); + + CMenuPanel* CreateTextWindow( int iTextToShow ); + + CCommandMenu *CreateSubMenu( CommandButton *pButton, CCommandMenu *pParentMenu, int iYOffset, int iXOffset = 0 ); + + // Data Handlers + int GetValidClasses(int iTeam) { return m_iValidClasses[iTeam]; }; + int GetNumberOfTeams() { return m_iNumberOfTeams; }; + int GetIsFeigning() { return m_iIsFeigning; }; + int GetIsSettingDetpack() { return m_iIsSettingDetpack; }; + int GetBuildState() { return m_iBuildState; }; + int IsRandomPC() { return m_iRandomPC; }; + char *GetTeamName( int iTeam ) { return m_sTeamNames[iTeam]; }; + int GetAllowSpectators() { return m_iAllowSpectators; }; + + // Message Handlers + int MsgFunc_ValClass(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_TeamNames(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_Feign(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_Detpack(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_VGUIMenu(const char *pszName, int iSize, void *pbuf ); + int MsgFunc_MOTD( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_BuildSt( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_RandomPC( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_ServerName( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_ScoreInfo( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_TeamScore( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_TeamInfo( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_Spectator( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_AllowSpec( const char *pszName, int iSize, void *pbuf ); + + // buz + int MsgFunc_ShowTextWindow( const char *pszName, int iSize, void *pbuf ); + + // buz + void CreatePickupMessagePanels(); + void InitializePickupMessagePanels(); + void CheckCurrentPickupMessagePanel(); // called from message panels... + void AddPickupMessage( client_textmessage_t *msg ); + + CPickupMessage* m_pPickupMessages[MAX_PICKUP_MESSAGES]; + int m_iCurrentPickupMessage; + + // Input + bool SlotInput( int iSlot ); + + virtual void paintBackground(); + + CSchemeManager *GetSchemeManager( void ) { return &m_SchemeManager; } + ScorePanel *GetScoreBoard( void ) { return m_pScoreBoard; } + + void *operator new( size_t stAllocateBlock ); + +public: + // VGUI Menus + // buz: remove some unused shit.. + CMenuPanel *m_pCurrentMenu; +// CTeamMenuPanel *m_pTeamMenu; + int m_StandardMenu; // indexs in m_pCommandMenus + int m_SpectatorOptionsMenu; + int m_SpectatorCameraMenu; +// CClassMenuPanel *m_pClassMenu; + ScorePanel *m_pScoreBoard; +// SpectatorPanel * m_pSpectatorPanel; + char m_szServerName[ MAX_SERVERNAME_LENGTH ]; + + // buz + CParanoiaTextPanel* m_pParanoiaText; + CSubtitle* m_pSubtitle; + CRadioIcon* m_pRadio; + CHud2* m_pHud2; + CTabPanel *m_pTabPanel; + CTips *m_pTips; + CGammaView *m_pGamma; + CScreenMessage *m_pScreenMsg; + + // Wargon: Ñêðîëëÿùèéñÿ òåêñò. + CScrollingMessage *m_pScrollingMsg; +}; + +//============================================================ +// Command Menu Button Handlers +#define MAX_COMMAND_SIZE 256 + +class CMenuHandler_StringCommand : public ActionSignal +{ +protected: + char m_pszCommand[MAX_COMMAND_SIZE]; + int m_iCloseVGUIMenu; +public: + CMenuHandler_StringCommand( char *pszCommand ) + { + strncpy( m_pszCommand, pszCommand, MAX_COMMAND_SIZE); + m_pszCommand[MAX_COMMAND_SIZE-1] = '\0'; + m_iCloseVGUIMenu = false; + } + + CMenuHandler_StringCommand( char *pszCommand, int iClose ) + { + strncpy( m_pszCommand, pszCommand, MAX_COMMAND_SIZE); + m_pszCommand[MAX_COMMAND_SIZE-1] = '\0'; + m_iCloseVGUIMenu = true; + } + + virtual void actionPerformed(Panel* panel) + { + gEngfuncs.pfnClientCmd(m_pszCommand); + + if (m_iCloseVGUIMenu) + gViewPort->HideTopMenu(); + else + gViewPort->HideCommandMenu(); + } +}; + +// This works the same as CMenuHandler_StringCommand, except it watches the string command +// for specific commands, and modifies client vars based upon them. +class CMenuHandler_StringCommandWatch : public CMenuHandler_StringCommand +{ +private: +public: + CMenuHandler_StringCommandWatch( char *pszCommand ) : CMenuHandler_StringCommand( pszCommand ) + { + } + + CMenuHandler_StringCommandWatch( char *pszCommand, int iClose ) : CMenuHandler_StringCommand( pszCommand, iClose ) + { + } + + virtual void actionPerformed(Panel* panel) + { + CMenuHandler_StringCommand::actionPerformed( panel ); + + // Try to guess the player's new team (it'll be corrected if it's wrong) + if ( !strcmp( m_pszCommand, "jointeam 1" ) ) + g_iTeamNumber = 1; + else if ( !strcmp( m_pszCommand, "jointeam 2" ) ) + g_iTeamNumber = 2; + else if ( !strcmp( m_pszCommand, "jointeam 3" ) ) + g_iTeamNumber = 3; + else if ( !strcmp( m_pszCommand, "jointeam 4" ) ) + g_iTeamNumber = 4; + } +}; + +// Used instead of CMenuHandler_StringCommand for Class Selection buttons. +// Checks the state of hud_classautokill and kills the player if set +class CMenuHandler_StringCommandClassSelect : public CMenuHandler_StringCommand +{ +private: +public: + CMenuHandler_StringCommandClassSelect( char *pszCommand ) : CMenuHandler_StringCommand( pszCommand ) + { + } + + CMenuHandler_StringCommandClassSelect( char *pszCommand, int iClose ) : CMenuHandler_StringCommand( pszCommand, iClose ) + { + } + + virtual void actionPerformed(Panel* panel); +}; + +class CMenuHandler_PopupSubMenuInput : public InputSignal +{ +private: + CCommandMenu *m_pSubMenu; + Button *m_pButton; +public: + CMenuHandler_PopupSubMenuInput( Button *pButton, CCommandMenu *pSubMenu ) + { + m_pSubMenu = pSubMenu; + m_pButton = pButton; + } + + virtual void cursorMoved(int x,int y,Panel* panel) + { + //gViewPort->SetCurrentCommandMenu( m_pSubMenu ); + } + + virtual void cursorEntered(Panel* panel) + { + gViewPort->SetCurrentCommandMenu( m_pSubMenu ); + + if (m_pButton) + m_pButton->setArmed(true); + }; + virtual void cursorExited(Panel* Panel) {}; + virtual void mousePressed(MouseCode code,Panel* panel) {}; + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {}; + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; +}; + +class CMenuHandler_LabelInput : public InputSignal +{ +private: + ActionSignal *m_pActionSignal; +public: + CMenuHandler_LabelInput( ActionSignal *pSignal ) + { + m_pActionSignal = pSignal; + } + + virtual void mousePressed(MouseCode code,Panel* panel) + { + m_pActionSignal->actionPerformed( panel ); + } + + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void cursorEntered(Panel* panel) {}; + virtual void cursorExited(Panel* Panel) {}; + virtual void cursorMoved(int x,int y,Panel* panel) {}; + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; +}; + +#define HIDE_TEXTWINDOW 0 +#define SHOW_MAPBRIEFING 1 +#define SHOW_CLASSDESC 2 +#define SHOW_MOTD 3 +#define SHOW_SPECHELP 4 + +class CMenuHandler_TextWindow : public ActionSignal +{ +private: + int m_iState; +public: + CMenuHandler_TextWindow( int iState ) + { + m_iState = iState; + } + + virtual void actionPerformed(Panel* panel) + { + if (m_iState == HIDE_TEXTWINDOW) + { + gViewPort->HideTopMenu(); + } + else + { + gViewPort->HideCommandMenu(); + gViewPort->ShowVGUIMenu( m_iState ); + } + } +}; + +class CMenuHandler_ToggleCvar : public ActionSignal +{ +private: + struct cvar_s * m_cvar; + +public: + CMenuHandler_ToggleCvar( char * cvarname ) + { + m_cvar = gEngfuncs.pfnGetCvarPointer( cvarname ); + } + + virtual void actionPerformed(Panel* panel) + { + if ( m_cvar->value ) + m_cvar->value = 0.0f; + else + m_cvar->value = 1.0f; + +// gViewPort->UpdateSpectatorPanel(); + } + + +}; +class CDragNDropHandler : public InputSignal +{ +private: + DragNDropPanel* m_pPanel; + bool m_bDragging; + int m_iaDragOrgPos[2]; + int m_iaDragStart[2]; + +public: + CDragNDropHandler(DragNDropPanel* pPanel) + { + m_pPanel = pPanel; + m_bDragging = false; + } + + void cursorMoved(int x,int y,Panel* panel); + void mousePressed(MouseCode code,Panel* panel); + void mouseReleased(MouseCode code,Panel* panel); + + void mouseDoublePressed(MouseCode code,Panel* panel) {}; + void cursorEntered(Panel* panel) {}; + void cursorExited(Panel* panel) {}; + void mouseWheeled(int delta,Panel* panel) {}; + void keyPressed(KeyCode code,Panel* panel) {}; + void keyTyped(KeyCode code,Panel* panel) {}; + void keyReleased(KeyCode code,Panel* panel) {}; + void keyFocusTicked(Panel* panel) {}; +}; + +class CHandler_MenuButtonOver : public InputSignal +{ +private: + int m_iButton; + CMenuPanel *m_pMenuPanel; +public: + CHandler_MenuButtonOver( CMenuPanel *pPanel, int iButton ) + { + m_iButton = iButton; + m_pMenuPanel = pPanel; + } + + void cursorEntered(Panel *panel); + + void cursorMoved(int x,int y,Panel* panel) {}; + void mousePressed(MouseCode code,Panel* panel) {}; + void mouseReleased(MouseCode code,Panel* panel) {}; + void mouseDoublePressed(MouseCode code,Panel* panel) {}; + void cursorExited(Panel* panel) {}; + void mouseWheeled(int delta,Panel* panel) {}; + void keyPressed(KeyCode code,Panel* panel) {}; + void keyTyped(KeyCode code,Panel* panel) {}; + void keyReleased(KeyCode code,Panel* panel) {}; + void keyFocusTicked(Panel* panel) {}; +}; + +class CHandler_ButtonHighlight : public InputSignal +{ +private: + Button *m_pButton; +public: + CHandler_ButtonHighlight( Button *pButton ) + { + m_pButton = pButton; + } + + virtual void cursorEntered(Panel* panel) + { + m_pButton->setArmed(true); + }; + virtual void cursorExited(Panel* Panel) + { + m_pButton->setArmed(false); + }; + virtual void mousePressed(MouseCode code,Panel* panel) {}; + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void cursorMoved(int x,int y,Panel* panel) {}; + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; +}; + +//----------------------------------------------------------------------------- +// Purpose: Special handler for highlighting of command menu buttons +//----------------------------------------------------------------------------- +class CHandler_CommandButtonHighlight : public CHandler_ButtonHighlight +{ +private: + CommandButton *m_pCommandButton; +public: + CHandler_CommandButtonHighlight( CommandButton *pButton ) : CHandler_ButtonHighlight( pButton ) + { + m_pCommandButton = pButton; + } + + virtual void cursorEntered( Panel *panel ) + { + m_pCommandButton->cursorEntered(); + } + + virtual void cursorExited( Panel *panel ) + { + m_pCommandButton->cursorExited(); + } +}; + + +//================================================================ +// Overidden Command Buttons for special visibilities +class ClassButton : public CommandButton +{ +protected: + int m_iPlayerClass; + +public: + ClassButton( int iClass, const char* text,int x,int y,int wide,int tall, bool bNoHighlight ) : CommandButton( text,x,y,wide,tall, bNoHighlight) + { + m_iPlayerClass = iClass; + } + + virtual int IsNotValid(); +}; + +class TeamButton : public CommandButton +{ +private: + int m_iTeamNumber; +public: + TeamButton( int iTeam, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + m_iTeamNumber = iTeam; + } + + virtual int IsNotValid() + { + int iTeams = gViewPort->GetNumberOfTeams(); + // Never valid if there's only 1 team + if (iTeams == 1) + return true; + + // Auto Team's always visible + if (m_iTeamNumber == 5) + return false; + + if (iTeams >= m_iTeamNumber && m_iTeamNumber != g_iTeamNumber) + return false; + + return true; + } +}; + +class FeignButton : public CommandButton +{ +private: + int m_iFeignState; +public: + FeignButton( int iState, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + m_iFeignState = iState; + } + + virtual int IsNotValid() + { + // Only visible for spies + if (g_iPlayerClass != PC_SPY) + return true; + + if (m_iFeignState == gViewPort->GetIsFeigning()) + return false; + + return true; + } +}; + +class SpectateButton : public CommandButton +{ +public: + SpectateButton( const char* text,int x,int y,int wide,int tall, bool bNoHighlight ) : CommandButton( text,x,y,wide,tall, bNoHighlight) + { + } + + virtual int IsNotValid() + { + // Only visible if the server allows it + if ( gViewPort->GetAllowSpectators() != 0 ) + return false; + + return true; + } +}; + +#define DISGUISE_TEAM1 (1<<0) +#define DISGUISE_TEAM2 (1<<1) +#define DISGUISE_TEAM3 (1<<2) +#define DISGUISE_TEAM4 (1<<3) + +class DisguiseButton : public CommandButton +{ +private: + int m_iValidTeamsBits; + int m_iThisTeam; +public: + DisguiseButton( int iValidTeamNumsBits, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall,false ) + { + m_iValidTeamsBits = iValidTeamNumsBits; + } + + virtual int IsNotValid() + { + // Only visible for spies + if ( g_iPlayerClass != PC_SPY ) + return true; + + // if it's not tied to a specific team, then always show (for spies) + if ( !m_iValidTeamsBits ) + return false; + + // if we're tied to a team make sure we can change to that team + int iTmp = 1 << (gViewPort->GetNumberOfTeams() - 1); + if ( m_iValidTeamsBits & iTmp ) + return false; + + return true; + } +}; + +class DetpackButton : public CommandButton +{ +private: + int m_iDetpackState; +public: + DetpackButton( int iState, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + m_iDetpackState = iState; + } + + virtual int IsNotValid() + { + // Only visible for demomen + if (g_iPlayerClass != PC_DEMOMAN) + return true; + + if (m_iDetpackState == gViewPort->GetIsSettingDetpack()) + return false; + + return true; + } +}; + +extern int iBuildingCosts[]; +#define BUILDSTATE_HASBUILDING (1<<0) // Data is building ID (1 = Dispenser, 2 = Sentry) +#define BUILDSTATE_BUILDING (1<<1) +#define BUILDSTATE_BASE (1<<2) +#define BUILDSTATE_CANBUILD (1<<3) // Data is building ID (0 = Dispenser, 1 = Sentry) + +class BuildButton : public CommandButton +{ +private: + int m_iBuildState; + int m_iBuildData; + +public: + enum Buildings + { + DISPENSER = 0, + SENTRYGUN = 1, + }; + + BuildButton( int iState, int iData, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + m_iBuildState = iState; + m_iBuildData = iData; + } + + virtual int IsNotValid() + { + // Only visible for engineers + if (g_iPlayerClass != PC_ENGINEER) + return true; + + // If this isn't set, it's only active when they're not building + if (m_iBuildState & BUILDSTATE_BUILDING) + { + // Make sure the player's building + if ( !(gViewPort->GetBuildState() & BS_BUILDING) ) + return true; + } + else + { + // Make sure the player's not building + if ( gViewPort->GetBuildState() & BS_BUILDING ) + return true; + } + + if (m_iBuildState & BUILDSTATE_BASE) + { + // Only appear if we've got enough metal to build something, or something already built + if ( gViewPort->GetBuildState() & (BS_HAS_SENTRYGUN | BS_HAS_DISPENSER | BS_CANB_SENTRYGUN | BS_CANB_DISPENSER) ) + return false; + + return true; + } + + // Must have a building + if (m_iBuildState & BUILDSTATE_HASBUILDING) + { + if ( m_iBuildData == BuildButton::DISPENSER && !(gViewPort->GetBuildState() & BS_HAS_DISPENSER) ) + return true; + if ( m_iBuildData == BuildButton::SENTRYGUN && !(gViewPort->GetBuildState() & BS_HAS_SENTRYGUN) ) + return true; + } + + // Can build something + if (m_iBuildState & BUILDSTATE_CANBUILD) + { + // Make sure they've got the ammo and don't have one already + if ( m_iBuildData == BuildButton::DISPENSER && (gViewPort->GetBuildState() & BS_CANB_DISPENSER) ) + return false; + if ( m_iBuildData == BuildButton::SENTRYGUN && (gViewPort->GetBuildState() & BS_CANB_SENTRYGUN) ) + return false; + + return true; + } + + return false; + } +}; + +#define MAX_MAPNAME 256 + +class MapButton : public CommandButton +{ +private: + char m_szMapName[ MAX_MAPNAME ]; + +public: + MapButton( const char *pMapName, const char* text,int x,int y,int wide,int tall ) : CommandButton( text,x,y,wide,tall) + { + sprintf( m_szMapName, "maps/%s.bsp", pMapName ); + } + + virtual int IsNotValid() + { + const char *level = gEngfuncs.pfnGetLevelName(); + if (!level) + return true; + + // Does it match the current map name? + if ( strcmp(m_szMapName, level) ) + return true; + + return false; + } +}; + +//----------------------------------------------------------------------------- +// Purpose: CommandButton which is only displayed if the player is on team X +//----------------------------------------------------------------------------- +class TeamOnlyCommandButton : public CommandButton +{ +private: + int m_iTeamNum; + +public: + TeamOnlyCommandButton( int iTeamNum, const char* text,int x,int y,int wide,int tall, bool flat ) : + CommandButton( text, x, y, wide, tall, false, flat ), m_iTeamNum(iTeamNum) {} + + virtual int IsNotValid() + { + if ( g_iTeamNumber != m_iTeamNum ) + return true; + + return CommandButton::IsNotValid(); + } + + virtual void paintBackground() + { + if ( isArmed() ) + { + drawSetColor( 143,143, 54, 125 ); + drawFilledRect( 5, 0,_size[0] - 5,_size[1]); + } + } + + virtual void paint( void ) + { + if ( isArmed() ) + { + setFgColor( 194, 202, 54, 0 ); + } + else + { + setFgColor( 143, 143, 54, 15 ); + } + + Button::paint(); + } +}; + +//----------------------------------------------------------------------------- +// Purpose: CommandButton which is only displayed if the player is on team X +//----------------------------------------------------------------------------- +class ToggleCommandButton : public CommandButton, public InputSignal +{ +private: + struct cvar_s * m_cvar; + CImageLabel * pLabelOn; + CImageLabel * pLabelOff; + + +public: + ToggleCommandButton( const char* cvarname, const char* text,int x,int y,int wide,int tall, bool flat ) : + CommandButton( text, x, y, wide, tall, false, flat ) + { + m_cvar = gEngfuncs.pfnGetCvarPointer( cvarname ); + + // Put a > to show it's a submenu + pLabelOn = new CImageLabel( "checked", 0, 0 ); + pLabelOn->setParent(this); + pLabelOn->addInputSignal(this); + + pLabelOff = new CImageLabel( "unchecked", 0, 0 ); + pLabelOff->setParent(this); + pLabelOff->setEnabled(true); + pLabelOff->addInputSignal(this); + + int textwide, texttall; + getTextSize( textwide, texttall); + + // Reposition + pLabelOn->setPos( textwide, (tall - pLabelOn->getTall()) / 2 ); + + pLabelOff->setPos( textwide, (tall - pLabelOff->getTall()) / 2 ); + + // Set text color to orange + setFgColor(Scheme::sc_primary1); + } + + virtual void cursorEntered(Panel* panel) + { + CommandButton::cursorEntered(); + } + + virtual void cursorExited(Panel* panel) + { + CommandButton::cursorExited(); + } + + virtual void mousePressed(MouseCode code,Panel* panel) + { + doClick(); + }; + + virtual void cursorMoved(int x,int y,Panel* panel) {}; + + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {}; + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; + + virtual void paint( void ) + { + if ( !m_cvar ) + { + pLabelOff->setVisible(false); + pLabelOn->setVisible(false); + } + else if ( m_cvar->value ) + { + pLabelOff->setVisible(false); + pLabelOn->setVisible(true); + } + else + { + pLabelOff->setVisible(true); + pLabelOn->setVisible(false); + } + + CommandButton::paint(); + + } +}; +class SpectToggleButton : public CommandButton, public InputSignal +{ +private: + struct cvar_s * m_cvar; + CImageLabel * pLabelOn; + +public: + SpectToggleButton( const char* cvarname, const char* text,int x,int y,int wide,int tall, bool flat ) : + CommandButton( text, x, y, wide, tall, false, flat ) + { + m_cvar = gEngfuncs.pfnGetCvarPointer( cvarname ); + + // Put a > to show it's a submenu + pLabelOn = new CImageLabel( "checked", 0, 0 ); + pLabelOn->setParent(this); + pLabelOn->addInputSignal(this); + + + int textwide, texttall; + getTextSize( textwide, texttall); + + // Reposition + pLabelOn->setPos( textwide, (tall - pLabelOn->getTall()) / 2 ); + } + + virtual void cursorEntered(Panel* panel) + { + CommandButton::cursorEntered(); + } + + virtual void cursorExited(Panel* panel) + { + CommandButton::cursorExited(); + } + + virtual void mousePressed(MouseCode code,Panel* panel) + { + doClick(); + }; + + virtual void cursorMoved(int x,int y,Panel* panel) {}; + + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {}; + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; + + virtual void paintBackground() + { + if ( isArmed() ) + { + drawSetColor( 143,143, 54, 125 ); + drawFilledRect( 5, 0,_size[0] - 5,_size[1]); + } + } + + virtual void paint( void ) + { + if ( isArmed() ) + { + setFgColor( 194, 202, 54, 0 ); + } + else + { + setFgColor( 143, 143, 54, 15 ); + } + + if ( !m_cvar ) + { + pLabelOn->setVisible(false); + } + else if ( m_cvar->value ) + { + pLabelOn->setVisible(true); + } + else + { + pLabelOn->setVisible(false); + } + + Button::paint(); + } +}; + +/* +class SpectToggleButton : public ToggleCommandButton +{ +private: + struct cvar_s * m_cvar; + CImageLabel * pLabelOn; + CImageLabel * pLabelOff; + +public: + + SpectToggleButton( const char* cvarname, const char* text,int x,int y,int wide,int tall, bool flat ) : + ToggleCommandButton( cvarname, text, x, y, wide, tall, flat, TRUE ) + { + m_cvar = gEngfuncs.pfnGetCvarPointer( cvarname ); + + // Put a > to show it's a submenu + pLabelOn = new CImageLabel( "checked", 0, 0 ); + pLabelOn->setParent(this); + pLabelOn->addInputSignal(this); + + pLabelOff = new CImageLabel( "unchecked", 0, 0 ); + pLabelOff->setParent(this); + pLabelOff->setEnabled(true); + pLabelOff->addInputSignal(this); + + int textwide, texttall; + getTextSize( textwide, texttall); + + // Reposition + pLabelOn->setPos( textwide, (tall - pLabelOn->getTall()) / 2 ); + + pLabelOff->setPos( textwide, (tall - pLabelOff->getTall()) / 2 ); + + // Set text color to orange + setFgColor(Scheme::sc_primary1); + } + + virtual void paintBackground() + { + if ( isArmed()) + { + drawSetColor( 143,143, 54, 125 ); + drawFilledRect( 5, 0,_size[0] - 5,_size[1]); + } + } + + virtual void paint() + { + + if ( isArmed() ) + { + setFgColor( 194, 202, 54, 0 ); + } + else + { + setFgColor( 143, 143, 54, 15 ); + } + + if ( !m_cvar ) + { + pLabelOff->setVisible(false); + pLabelOn->setVisible(false); + } + else if ( m_cvar->value ) + { + pLabelOff->setVisible(false); + pLabelOn->setVisible(true); + } + else + { + pLabelOff->setVisible(true); + pLabelOn->setVisible(false); + } + + Button::paint(); + } +}; +*/ +//============================================================ +// Panel that can be dragged around +class DragNDropPanel : public Panel +{ +private: + bool m_bBeingDragged; + LineBorder *m_pBorder; +public: + DragNDropPanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) + { + m_bBeingDragged = false; + + // Create the Drag Handler + addInputSignal( new CDragNDropHandler(this) ); + + // Create the border (for dragging) + m_pBorder = new LineBorder(); + } + + virtual void setDragged( bool bState ) + { + m_bBeingDragged = bState; + + if (m_bBeingDragged) + setBorder(m_pBorder); + else + setBorder(NULL); + } +}; + +//================================================================ +// Panel that draws itself with a transparent black background +class CTransparentPanel : public Panel +{ +private: + int m_iTransparency; +public: + CTransparentPanel(int iTrans, int x,int y,int wide,int tall) : Panel(x,y,wide,tall) + { + m_iTransparency = iTrans; + } + + virtual void paintBackground() + { + if (m_iTransparency) + { + // Transparent black background + drawSetColor( 0,0,0, m_iTransparency ); + drawFilledRect(0,0,_size[0],_size[1]); + } + } +}; + +//================================================================ +// Menu Panel that supports buffering of menus +class CMenuPanel : public CTransparentPanel +{ +private: + CMenuPanel *m_pNextMenu; + int m_iMenuID; + int m_iRemoveMe; + int m_iIsActive; + float m_flOpenTime; +public: + CMenuPanel(int iRemoveMe, int x,int y,int wide,int tall) : CTransparentPanel(100, x,y,wide,tall) + { + Reset(); + m_iRemoveMe = iRemoveMe; + } + + CMenuPanel(int iTrans, int iRemoveMe, int x,int y,int wide,int tall) : CTransparentPanel(iTrans, x,y,wide,tall) + { + Reset(); + m_iRemoveMe = iRemoveMe; + } + + virtual void Reset( void ) + { + m_pNextMenu = NULL; + m_iIsActive = false; + m_flOpenTime = 0; + } + + void SetNextMenu( CMenuPanel *pNextPanel ) + { + if (m_pNextMenu) + m_pNextMenu->SetNextMenu( pNextPanel ); + else + m_pNextMenu = pNextPanel; + } + + void SetMenuID( int iID ) + { + m_iMenuID = iID; + } + + void SetActive( int iState ) + { + m_iIsActive = iState; + } + + virtual void Open( void ) + { + setVisible( true ); + + // Note the open time, so we can delay input for a bit + m_flOpenTime = gHUD.m_flTime; + } + + virtual void Close( void ) + { + setVisible( false ); + m_iIsActive = false; + + if ( m_iRemoveMe ) + gViewPort->removeChild( this ); + + // This MenuPanel has now been deleted. Don't append code here. + } + + int ShouldBeRemoved() { return m_iRemoveMe; }; + CMenuPanel* GetNextMenu() { return m_pNextMenu; }; + int GetMenuID() { return m_iMenuID; }; + int IsActive() { return m_iIsActive; }; + float GetOpenTime() { return m_flOpenTime; }; + + // Numeric input + virtual bool SlotInput( int iSlot ) { return false; }; + virtual void SetActiveInfo( int iInput ) {}; +}; + +//================================================================ +// Custom drawn scroll bars +class CTFScrollButton : public CommandButton +{ +private: + BitmapTGA *m_pTGA; + +public: + CTFScrollButton(int iArrow, const char* text,int x,int y,int wide,int tall); + + virtual void paint( void ); + virtual void paintBackground( void ); +}; + +// Custom drawn slider bar +class CTFSlider : public Slider +{ +public: + CTFSlider(int x,int y,int wide,int tall,bool vertical) : Slider(x,y,wide,tall,vertical) + { + }; + + virtual void paintBackground( void ); +}; + +// Custom drawn scrollpanel +class CTFScrollPanel : public ScrollPanel +{ +public: + CTFScrollPanel(int x,int y,int wide,int tall); +}; + +//================================================================ +// Menu Panels that take key input +//============================================================ +class CClassMenuPanel : public CMenuPanel +{ +private: + CTransparentPanel *m_pClassInfoPanel[PC_LASTCLASS]; + Label *m_pPlayers[PC_LASTCLASS]; + ClassButton *m_pButtons[PC_LASTCLASS]; + CommandButton *m_pCancelButton; + ScrollPanel *m_pScrollPanel; + + CImageLabel *m_pClassImages[MAX_TEAMS][PC_LASTCLASS]; + + int m_iCurrentInfo; + + enum { STRLENMAX_PLAYERSONTEAM = 128 }; + char m_sPlayersOnTeamString[STRLENMAX_PLAYERSONTEAM]; + +public: + CClassMenuPanel(int iTrans, int iRemoveMe, int x,int y,int wide,int tall); + + virtual bool SlotInput( int iSlot ); + virtual void Open( void ); + virtual void Update( void ); + virtual void SetActiveInfo( int iInput ); + virtual void Initialize( void ); + + virtual void Reset( void ) + { + CMenuPanel::Reset(); + m_iCurrentInfo = 0; + } +}; + +class CTeamMenuPanel : public CMenuPanel +{ +public: + ScrollPanel *m_pScrollPanel; + CTransparentPanel *m_pTeamWindow; + Label *m_pMapTitle; + TextPanel *m_pBriefing; + TextPanel *m_pTeamInfoPanel[6]; + CommandButton *m_pButtons[6]; + bool m_bUpdatedMapName; + CommandButton *m_pCancelButton; + CommandButton *m_pSpectateButton; + + int m_iCurrentInfo; + +public: + CTeamMenuPanel(int iTrans, int iRemoveMe, int x,int y,int wide,int tall); + + virtual bool SlotInput( int iSlot ); + virtual void Open( void ); + virtual void Update( void ); + virtual void SetActiveInfo( int iInput ); + virtual void paintBackground( void ); + + virtual void Initialize( void ); + + virtual void Reset( void ) + { + CMenuPanel::Reset(); + m_iCurrentInfo = 0; + } +}; + +//========================================================= +// Specific Menus to handle old HUD sections +class CHealthPanel : public DragNDropPanel +{ +private: + BitmapTGA *m_pHealthTGA; + Label *m_pHealthLabel; +public: + CHealthPanel(int x,int y,int wide,int tall) : DragNDropPanel(x,y,wide,tall) + { + // Load the Health icon + FileInputStream* fis = new FileInputStream( GetVGUITGAName("%d_hud_health"), false); + m_pHealthTGA = new BitmapTGA(fis,true); + fis->close(); + + // Create the Health Label + int iXSize,iYSize; + m_pHealthTGA->getSize(iXSize,iYSize); + m_pHealthLabel = new Label("",0,0,iXSize,iYSize); + m_pHealthLabel->setImage(m_pHealthTGA); + m_pHealthLabel->setParent(this); + + // Set panel dimension + // Shouldn't be needed once Billy's fized setImage not recalculating the size + //setSize( iXSize + 100, gHUD.m_iFontHeight + 10 ); + //m_pHealthLabel->setPos( 10, (getTall() - iYSize) / 2 ); + } + + virtual void paintBackground() + { + } + + void paint() + { + // Get the paint color + int r,g,b,a; + // Has health changed? Flash the health # + if (gHUD.m_Health.m_fFade) + { + gHUD.m_Health.m_fFade -= (gHUD.m_flTimeDelta * 20); + if (gHUD.m_Health.m_fFade <= 0) + { + a = MIN_ALPHA; + gHUD.m_Health.m_fFade = 0; + } + + // Fade the health number back to dim + a = MIN_ALPHA + (gHUD.m_Health.m_fFade/FADE_TIME) * 128; + } + else + a = MIN_ALPHA; + + gHUD.m_Health.GetPainColor( r, g, b ); + ScaleColors(r, g, b, a ); + + // If health is getting low, make it bright red + if (gHUD.m_Health.m_iHealth <= 15) + a = 255; + + int iXSize,iYSize, iXPos, iYPos; + m_pHealthTGA->getSize(iXSize,iYSize); + m_pHealthTGA->getPos(iXPos, iYPos); + + // Paint the player's health + int x = gHUD.DrawHudNumber( iXPos + iXSize + 5, iYPos + 5, DHN_3DIGITS | DHN_DRAWZERO, gHUD.m_Health.m_iHealth, r, g, b); + + // Draw the vertical line + int HealthWidth = gHUD.GetSpriteRect(gHUD.m_HUD_number_0).right - gHUD.GetSpriteRect(gHUD.m_HUD_number_0).left; + x += HealthWidth / 2; + FillRGBA(x, iYPos + 5, HealthWidth / 10, gHUD.m_iFontHeight, 255, 160, 0, a); + } +}; + +#endif diff --git a/cl_dll/vgui_gamma.cpp b/cl_dll/vgui_gamma.cpp new file mode 100644 index 0000000..588fbea --- /dev/null +++ b/cl_dll/vgui_gamma.cpp @@ -0,0 +1,78 @@ +// ==================================== +// Paranoia gamma table viewer +// written by BUzer. +// ==================================== + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" +#include "const.h" +#include "entity_types.h" +#include "cdll_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_gamma.h" +//#include "..\game_shared\vgui_loadtga.h" +//#include + +#include "com_model.h" + +// gamma functions +//void ApplyGammaCorrection ( byte *pData, int count ); + + +CGammaView::CGammaView() : Panel(XRES(620)-256, YRES(400)-256, XRES(10)+256, YRES(10)+256) +{ + setBgColor(0, 0, 0, 100); + setVisible(false); + setPaintEnabled(true); +} + +CGammaView::~CGammaView() +{ + +} + +void CGammaView::paint() +{ + int i; + byte buf[256]; + for (i = 0; i < 256; i++) + buf[i] = i; + +// ApplyGammaCorrection( buf, 256 ); + + for (i = 0; i < 256; i++) + { + drawSetColor(255-i, 255-i, 255-i, 0); + drawFilledRect(0, i, XRES(10), i+1); + } + for (i = 0; i < 256; i++) + { + drawSetColor(i, i, i, 0); + drawFilledRect(XRES(10) + i, 256, XRES(10) + i + 1, 256 + YRES(10)); + } + + drawSetColor(255, 255, 255, 0); + for (i = 0; i < 256; i++) + { + drawFilledRect(XRES(10) + i, 256 - buf[i], XRES(10) + i + 1, 256 - buf[i] + 1); + } +} + +void GraphEnable() +{ + if (gViewPort) + gViewPort->m_pGamma->setVisible(true); +} + +void GraphDisable() +{ + if (gViewPort) + gViewPort->m_pGamma->setVisible(false); +} + +void GammaGraphInit() +{ + gEngfuncs.pfnAddCommand ("+gammagraph",GraphEnable); + gEngfuncs.pfnAddCommand ("-gammagraph",GraphDisable); +} \ No newline at end of file diff --git a/cl_dll/vgui_gamma.h b/cl_dll/vgui_gamma.h new file mode 100644 index 0000000..36776f1 --- /dev/null +++ b/cl_dll/vgui_gamma.h @@ -0,0 +1,17 @@ + +#ifndef _GAMMAVIEW_H +#define _GAMMAVIEW_H +using namespace vgui; + +class CGammaView : public Panel +{ +public: + CGammaView(); + ~CGammaView(); +// void Initialize(); + +protected: + virtual void paint(); +}; + +#endif // _GAMMAVIEW_H \ No newline at end of file diff --git a/cl_dll/vgui_hud.cpp b/cl_dll/vgui_hud.cpp new file mode 100644 index 0000000..d57bcda --- /dev/null +++ b/cl_dll/vgui_hud.cpp @@ -0,0 +1,866 @@ +// ==================================== +// Paranoia vgui hud +// written by BUzer. +// ==================================== + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" +#include "const.h" +#include "entity_types.h" +#include "cdll_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_hud.h" +#include "vgui_paranoiatext.h" // Wargon +#include "..\game_shared\vgui_loadtga.h" +#include + +#include "ammohistory.h" +#include "stringlib.h" + +#define WEAPON_PAINKILLER 31 // should match server's index + + +#define HEALTH_RIGHT_OFFSET (XRES(10)) +#define HEALTH_DOWN_OFFSET (YRES(15)) +#define HEALTH_MIN_SPACE (YRES(3)) // minimum space between bars +#define HEALTH_FLASH_TIME 0.3 + +#define HEALTH_FADE_TIME 5 +#define HEALTH_ZERO_ALPHA 150 +#define HEALTH_ALPHA 70 + +// Wargon: Èêîíêà þçà. +#define USAGE_FADE_TIME 1 +#define USAGE_ALPHA 70 + +static int resArray[] = { 320, 400, 512, 640, 800, 1024, 1152, 1280, 1600 }; + +// Wargon: Èêîíêà þçà. +int CanUseStatus; +int __MsgFunc_CanUse( const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + CanUseStatus = READ_BYTE(); + return 1; +} + +void CanUseInit( void ) +{ + HOOK_MESSAGE( CanUse ); +} + +BitmapTGA *LoadResolutionImage( const char *imgname ) +{ + BitmapTGA *pBitmap = NULL; + + // resolution based image. Should contain %d substring + if( Q_stristr( imgname, "%d" )) + { + int resArrayIndex = 0; + int i = 0; + + while(( resArray[i] <= ScreenWidth ) && ( i < ARRAYSIZE( resArray ))) + { + resArrayIndex = i; + i++; + } + + while( pBitmap == NULL && resArrayIndex >= 0 ) + { + char imgName[256]; + Q_snprintf( imgName, sizeof( imgName ), imgname, resArray[resArrayIndex] ); + pBitmap = vgui_LoadTGA( imgName ); + resArrayIndex--; + } + } + else + { + // try to load image directly + pBitmap = vgui_LoadTGA( imgname ); + } + + return pBitmap; +} + +int ShouldDrawHUD() +{ + if (!gHUD.m_pCvarDraw->value) + return FALSE; + + if ((gHUD.m_iHideHUDDisplay & HIDEHUD_HEALTH) || gEngfuncs.IsSpectateOnly() ) + return FALSE; + + if (FBitSet( gHUD.m_iHideHUDDisplay, ITEM_SUIT )) + return TRUE; + + return FALSE; +} + + +// simple class that owns pointer to a bitmap and draws it +class ImageHolder : public Panel +{ +public: + ImageHolder(const char *imgname, Panel *parent) : Panel(0, 0, 10, 10) + { + m_pBitmap = LoadResolutionImage(imgname); + setParent(parent); + setPaintBackgroundEnabled(false); + setVisible(true); + if (m_pBitmap) m_pBitmap->setPos(0, 0); + } + + ~ImageHolder() {delete m_pBitmap;} + BitmapTGA *GetBitmap() {return m_pBitmap;} + +protected: + virtual void paint() + { + if (ShouldDrawHUD()) + { + if (m_pBitmap) + m_pBitmap->doPaint(this); + } + } + + BitmapTGA *m_pBitmap; +}; + + +class ShadowLabel : public Label +{ +public: + ShadowLabel(const char* text,int x,int y) : Label(text, x, y) {} + +protected: + virtual void paint() + { + int mr, mg, mb, ma; + int ix, iy; + getFgColor(mr, mg, mb, ma); + _textImage->getPos(ix, iy); + _textImage->setPos(ix+1, iy+1); + _textImage->setColor( Color(0, 0, 0, ma) ); + _textImage->doPaint(this); + _textImage->setPos(ix, iy); + _textImage->setColor( Color(mr, mg, mb, ma) ); + _textImage->doPaint(this); + } +}; + +void Hud2Init() +{ +// gEngfuncs.pfnHookUserMsg("RadioIcon", MsgShowRadioIcon); +} + +void CHud2::Initialize() +{ + health = -1; + armor = -1; + stamina = 1; + m_fMedkitUpdateTime = 0; + m_fUsageUpdateTime = 0; // Wargon: Èêîíêà þçà. +} + +CHud2::CHud2() : Panel(0, 0, XRES(640), YRES(480)) +{ + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + SchemeHandle_t hTextScheme = pSchemes->getSchemeHandle( "Default Text" ); + Font *pFont = pSchemes->getFont( hTextScheme ); + + hTextScheme = pSchemes->getSchemeHandle( "impact" ); + m_pFontDigits = pSchemes->getFont( hTextScheme ); + + // + // create medkits icon + // + int mediconypos = YRES(340); + m_pMedkitsIcon = new CImageLabel("painkiller", XRES(10), mediconypos); + if (!m_pMedkitsIcon->m_pTGA) + { + delete m_pMedkitsIcon; + m_pMedkitsIcon = NULL; + m_pMedkitsCount = NULL; + gEngfuncs.Con_Printf("Painkiller icon cannot be loaded\n"); + } + else + { + m_pMedkitsIcon->setParent(this); + m_pMedkitsIcon->setPaintBackgroundEnabled(false); + m_pMedkitsIcon->setVisible(false); + + int bw, bt; + m_pMedkitsIcon->m_pTGA->getSize(bw, bt); + m_pMedkitsIcon->setSize(bw, bt); + m_pMedkitsCount = new ShadowLabel("", XRES(15)+bw, (mediconypos+bt/2)-(m_pFontDigits->getTall()/2)); + m_pMedkitsCount->setSize(XRES(200), YRES(200)); // ? + m_pMedkitsCount->setContentAlignment(Label::a_northwest); // ? + m_pMedkitsCount->setTextAlignment(Label::a_northwest); // ? + m_pMedkitsCount->setParent(this); + m_pMedkitsCount->setVisible(false); + m_pMedkitsCount->setFont(m_pFontDigits /*pFont*/); + m_pMedkitsCount->setFgColor(255, 255, 255, 0); + m_pMedkitsCount->setPaintBackgroundEnabled(false); + m_pMedkitsOldNum = 0; + m_fMedkitUpdateTime = 0; + } + + // + // create head shield icon + // + mediconypos = YRES(300); + m_pShieldIcon = new CImageLabel( "shield", XRES( 10 ), mediconypos ); + if (!m_pShieldIcon->m_pTGA) + { + delete m_pShieldIcon; + m_pShieldIcon = NULL; + ALERT( at_error, "Head Shield icon cannot be loaded\n" ); + } + else + { + m_pShieldIcon->setParent(this); + m_pShieldIcon->setPaintBackgroundEnabled(false); + m_pShieldIcon->setVisible(false); + int bw, bt; + m_pShieldIcon->m_pTGA->getSize(bw, bt); + m_pShieldIcon->setSize(bw, bt); + } + + // + // create head shield icon + // + mediconypos = YRES(260); + m_pGasMaskIcon = new CImageLabel( "gasmask", XRES( 10 ), mediconypos ); + if (!m_pGasMaskIcon->m_pTGA) + { + delete m_pGasMaskIcon; + m_pGasMaskIcon = NULL; + ALERT( at_error, "Gas Mask icon cannot be loaded\n" ); + } + else + { + m_pGasMaskIcon->setParent(this); + m_pGasMaskIcon->setPaintBackgroundEnabled(false); + m_pGasMaskIcon->setVisible(false); + int bw, bt; + m_pGasMaskIcon->m_pTGA->getSize(bw, bt); + m_pGasMaskIcon->setSize(bw, bt); + } + + // + // load health bars + // + m_pBitmapHealthFull = new ImageHolder("gfx/vgui/%d_health_full.tga", this); + m_pBitmapHealthEmpty = new ImageHolder("gfx/vgui/%d_health_empty.tga", this); + m_pBitmapHealthFlash = new ImageHolder("gfx/vgui/%d_health_flash.tga", this); + if (m_pBitmapHealthFull->GetBitmap()) + m_pBitmapHealthFull->GetBitmap()->getSize(m_iHealthBarWidth, m_iHealthBarHeight); + else + m_iHealthBarWidth = m_iHealthBarHeight = 0; + + m_pBitmapArmorFull = new ImageHolder("gfx/vgui/%d_armor_full.tga", this); + m_pBitmapArmorEmpty = new ImageHolder("gfx/vgui/%d_armor_empty.tga", this); + m_pBitmapArmorFlash = new ImageHolder("gfx/vgui/%d_armor_flash.tga", this); + if (m_pBitmapArmorFull->GetBitmap()) + m_pBitmapArmorFull->GetBitmap()->getSize(m_iArmorBarWidth, m_iArmorBarHeight); + else + m_iArmorBarWidth = m_iArmorBarHeight = 0; + + m_pBitmapStaminaFull = new ImageHolder("gfx/vgui/%d_stamina_full.tga", this); + m_pBitmapStaminaEmpty = new ImageHolder("gfx/vgui/%d_stamina_empty.tga", this); + m_pBitmapStaminaFlash = new ImageHolder("gfx/vgui/%d_stamina_flash.tga", this); + if (m_pBitmapStaminaFull->GetBitmap()) + m_pBitmapStaminaFull->GetBitmap()->getSize(m_iStaminaBarWidth, m_iStaminaBarHeight); + else + m_iStaminaBarWidth = m_iStaminaBarHeight = 0; + + m_pHealthIcon = new ImageHolder("gfx/vgui/%d_health_icon.tga", this); + m_pArmorIcon = new ImageHolder("gfx/vgui/%d_armor_icon.tga", this); + m_pStaminaIcon = new ImageHolder("gfx/vgui/%d_stamina_icon.tga", this); + int healthiconwidth = 0, healthiconheight = 0; + int armoriconwidth = 0, armoriconheight = 0; + int staminaiconwidth = 0, staminaiconheight = 0; + if (m_pHealthIcon->GetBitmap()) + { + m_pHealthIcon->GetBitmap()->getSize(healthiconwidth, healthiconheight); + m_pHealthIcon->setSize(healthiconwidth, healthiconheight); + } + + if (m_pArmorIcon->GetBitmap()) + { + m_pArmorIcon->GetBitmap()->getSize(armoriconwidth, armoriconheight); + m_pArmorIcon->setSize(armoriconwidth, armoriconheight); + } + + if (m_pStaminaIcon->GetBitmap()) + { + m_pStaminaIcon->GetBitmap()->getSize(staminaiconwidth, staminaiconheight); + m_pStaminaIcon->setSize(staminaiconwidth, staminaiconheight); + } + + int line = ScreenHeight - HEALTH_DOWN_OFFSET; + if (m_pStaminaIcon->GetBitmap()) + { + if (staminaiconheight > m_iStaminaBarHeight) line -= staminaiconheight/2; + else line -= m_iStaminaBarHeight/2; + + m_pStaminaIcon->setPos(HEALTH_RIGHT_OFFSET, line - staminaiconheight/2); + m_iStaminaBarXpos = HEALTH_RIGHT_OFFSET + staminaiconwidth + YRES(5); + m_iStaminaBarYpos = line - m_iStaminaBarHeight/2; + + if (staminaiconheight > m_iStaminaBarHeight) line = ScreenHeight - HEALTH_DOWN_OFFSET - staminaiconheight - HEALTH_MIN_SPACE; + //else line = ScreenHeight - HEALTH_DOWN_OFFSET - m_iStaminaBarHeight - HEALTH_MIN_SPACE; + else line -= m_iStaminaBarHeight - HEALTH_MIN_SPACE; + } + else + { + line -= m_iStaminaBarHeight; + m_iStaminaBarXpos = HEALTH_RIGHT_OFFSET + YRES(5); + m_iStaminaBarYpos = line; + line -= HEALTH_MIN_SPACE; + } + + if (m_pArmorIcon->GetBitmap()) + { + if (armoriconheight > m_iArmorBarHeight) line -= armoriconheight/2; + else line -= m_iArmorBarHeight/2; + + m_pArmorIcon->setPos(HEALTH_RIGHT_OFFSET, line - armoriconheight/2); + m_iArmorBarXpos = HEALTH_RIGHT_OFFSET + armoriconwidth + YRES(5); + m_iArmorBarYpos = line - m_iArmorBarHeight/2; + + if (armoriconheight > m_iArmorBarHeight) line = ScreenHeight - HEALTH_DOWN_OFFSET - armoriconheight - HEALTH_MIN_SPACE; + // else line = ScreenHeight - HEALTH_DOWN_OFFSET - m_iArmorBarHeight - HEALTH_MIN_SPACE; + else line -= m_iArmorBarHeight - HEALTH_MIN_SPACE; + } + else + { + line -= m_iArmorBarHeight; + m_iArmorBarXpos = HEALTH_RIGHT_OFFSET + YRES(5); + m_iArmorBarYpos = line; + line -= HEALTH_MIN_SPACE; + } + + if (m_pHealthIcon->GetBitmap()) + { + if (healthiconheight > m_iHealthBarHeight) line -= healthiconheight/2; + else line -= m_iHealthBarHeight/2; + + m_pHealthIcon->setPos(HEALTH_RIGHT_OFFSET, line - healthiconheight/2); + m_iHealthBarXpos = HEALTH_RIGHT_OFFSET + healthiconwidth + YRES(5); + m_iHealthBarYpos = line - m_iHealthBarHeight/2; + } + else + { + line -= m_iHealthBarHeight; + m_iHealthBarXpos = HEALTH_RIGHT_OFFSET + YRES(5); + m_iHealthBarYpos = line; + } + + m_iNumWeaponNames = 0; + + char **filenames = NULL; + char token[256], *weapon_name; + int i = 0; + + if( g_fXashEngine && g_fRenderInitialized ) + filenames = FS_SEARCH( "gfx/vgui/ammo/*.tga", &m_iNumWeaponNames, FALSE ); + + if( m_iNumWeaponNames > NUM_WEAPON_ICONS ) + { + ALERT( at_warning, "too many ammo images in gfx/vgui/ammo folder\n" ); + m_iNumWeaponNames = NUM_WEAPON_ICONS; + } + + // convert filepathes into weapon names + for( i = 0; i < m_iNumWeaponNames; i++ ) + { + COM_FileBase( filenames[i], token ); + weapon_name = Q_strchr( token, '_' ) + 1; // skip first _ + m_pWeaponNames[i] = copystring( weapon_name ); + } + + // + // load ammo icons + // + for( i = 0; i < m_iNumWeaponNames; i++ ) + { + char path[256] = "gfx/vgui/ammo/%d_"; + Q_strcat( path, m_pWeaponNames[i] ); + Q_strcat( path, ".tga" ); + m_pWeaponIconsArray[i] = LoadResolutionImage( path ); + + if( !m_pWeaponIconsArray[i] ) // probably this is not possible but who knews... + ALERT( at_error, "Failed to load ammo icon [%s]\n", m_pWeaponNames[i] ); + } + + // + // Wargon: Èêîíêà þçà. + // + m_pUsageIcon = new CImageLabel( "usage", ScreenWidth / 2 - 12, ScreenHeight / 2 + 100 ); + + if( !m_pUsageIcon->m_pTGA ) + { + delete m_pUsageIcon; + m_pUsageIcon = NULL; + ALERT( at_console, "Usage icon can't be loaded\n" ); + } + else + { + int bw, bt; + m_pUsageIcon->setParent(this); + m_pUsageIcon->setPaintBackgroundEnabled(false); + m_pUsageIcon->setVisible(false); + m_pUsageIcon->m_pTGA->getSize(bw, bt); + m_pUsageIcon->setSize(bw, bt); + m_fUsageUpdateTime = 0; + } +} + +CHud2 :: ~CHud2() +{ + for (int i = 0; i < m_iNumWeaponNames; i++) + { + delete m_pWeaponNames[i]; + delete m_pWeaponIconsArray[i]; + } +} + +void CHud2::paintBackground() +{ +// Panel::paintBackground(); +} + + +// buz: I dunno exactly, what solve() function does. I just wanna to find +// some function, who being called each frame, to put panels position setting in there. +// I've tried paintBackground, Chud::Redraw, CHud::Think, and some others, but +// health and armor bars sizes are jumping during interpolation and +// panels motion looks jerky (especially in steam version). +// Putting this in solve() seems to fix first problem. +void CHud2::solve() +{ + float curtime = gEngfuncs.GetClientTime(); + + // Wargon: Èêîíêà þçà. + if (m_pUsageIcon) + { + m_fUsageUpdateTime = curtime; + if (CanUseStatus && gHUD.m_pCvarDraw->value && !(gViewPort && gViewPort->m_pParanoiaText && gViewPort->m_pParanoiaText->isVisible())) + { + m_pUsageIcon->setVisible(true); + if (m_fUsageUpdateTime > curtime) + m_fUsageUpdateTime = curtime; + float frac = curtime - m_fUsageUpdateTime; + int alpha = USAGE_ALPHA; + if (frac < USAGE_FADE_TIME) + { + frac = frac / USAGE_FADE_TIME; + alpha = (int)(frac * USAGE_ALPHA); + } + m_pUsageIcon->m_pTGA->setColor(Color(255, 255, 255, alpha)); + } + else + m_pUsageIcon->setVisible(false); + } + + // update medkits counter + if (m_pMedkitsIcon) + { + WEAPON *pPainkillers = gWR.GetWeapon( WEAPON_PAINKILLER ); + int pkcount = gWR.CountAmmo( pPainkillers->iAmmoType ); + if ( pkcount != m_pMedkitsOldNum ) + { + char temp[16]; + sprintf(temp, "%d", pkcount); + m_pMedkitsCount->setText(temp); + m_pMedkitsOldNum = pkcount; + m_fMedkitUpdateTime = curtime; + } + + if (pkcount && ShouldDrawHUD()) + { + m_pMedkitsIcon->setVisible(true); + m_pMedkitsCount->setVisible(true); + + if (m_fMedkitUpdateTime > curtime) // fix possible map change bugs + m_fMedkitUpdateTime = curtime; + + float frac = curtime - m_fMedkitUpdateTime; + int alpha = HEALTH_ALPHA; + if (frac < HEALTH_FADE_TIME) + { + frac = frac / HEALTH_FADE_TIME; + alpha = (int)(frac * HEALTH_ALPHA); + } + m_pMedkitsIcon->m_pTGA->setColor(Color(255, 255, 255, alpha)); + m_pMedkitsCount->setFgColor(255, 255, 255, alpha); + } + else + { + m_pMedkitsIcon->setVisible(false); + m_pMedkitsCount->setVisible(false); + } + } + + if (m_pShieldIcon) + { + if (FBitSet( gHUD.m_iHideHUDDisplay, ITEM_HEADSHIELD ) && ShouldDrawHUD()) + { + m_pShieldIcon->setVisible(true); + m_pShieldIcon->m_pTGA->setColor(Color(255, 255, 255, 0 )); + } + else + { + m_pShieldIcon->setVisible(false); + } + } + + if (m_pGasMaskIcon) + { + if (FBitSet( gHUD.m_iHideHUDDisplay, ITEM_GASMASK ) && ShouldDrawHUD()) + { + m_pGasMaskIcon->setVisible(true); + m_pGasMaskIcon->m_pTGA->setColor(Color(255, 255, 255, 0 )); + } + else + { + m_pGasMaskIcon->setVisible(false); + } + } + + // + // update health and armor bars + // (damn, it's so messy...) + + int healthdiv; + //Stamina bar + if (m_pBitmapStaminaEmpty->GetBitmap() && m_pBitmapStaminaFull->GetBitmap()) + { + m_pBitmapStaminaFlash->setVisible(false); + m_pBitmapStaminaEmpty->GetBitmap()->setColor(Color(255, 255, 255, 0)); + m_pBitmapStaminaFull->GetBitmap()->setColor(Color(255, 255, 255, 0)); + if (m_pStaminaIcon->GetBitmap()) + m_pStaminaIcon->GetBitmap()->setColor(Color(255, 255, 255, 0)); + if (curtime >= m_fStaminaUpdateTime + HEALTH_FLASH_TIME) + { + healthdiv = (int)((float)stamina/100 * m_iStaminaBarWidth); + float frac = curtime - m_fStaminaUpdateTime - HEALTH_FLASH_TIME; + int targetalpha = HEALTH_ALPHA; + if (stamina == 0) targetalpha = HEALTH_ZERO_ALPHA; + int alpha = targetalpha; + if (frac < HEALTH_FADE_TIME) + { + frac = frac / HEALTH_FADE_TIME; + alpha = (int)(frac * targetalpha); + } + m_pBitmapStaminaEmpty->GetBitmap()->setColor(Color(255, 255, 255, alpha)); + m_pBitmapStaminaFull->GetBitmap()->setColor(Color(255, 255, 255, alpha)); + if (m_pStaminaIcon->GetBitmap()) + m_pStaminaIcon->GetBitmap()->setColor(Color(255, 255, 255, alpha)); + } + else + { + float frac = (curtime - m_fStaminaUpdateTime) / HEALTH_FLASH_TIME; + if ((stamina < oldstamina) && m_pBitmapStaminaFlash->GetBitmap()) // we had take damage, make red flash + { + m_pBitmapStaminaFlash->setVisible(true); + m_pBitmapStaminaFlash->GetBitmap()->setColor(Color(255, 255, 255, 255*frac)); + } + frac = 1 - frac; + frac *= frac; + healthdiv = stamina - (int)((float)(stamina - oldstamina)*frac); + healthdiv = (int)((float)healthdiv/100 * m_iStaminaBarWidth); + } + + m_pBitmapStaminaFull->setBounds(m_iStaminaBarXpos, m_iStaminaBarYpos, healthdiv, m_iStaminaBarHeight); + m_pBitmapStaminaFlash->setBounds(m_iStaminaBarXpos, m_iStaminaBarYpos, healthdiv, m_iStaminaBarHeight); + + m_pBitmapStaminaEmpty->setBounds(m_iStaminaBarXpos + healthdiv, m_iStaminaBarYpos, + m_iStaminaBarWidth - healthdiv, m_iStaminaBarHeight); + m_pBitmapStaminaEmpty->GetBitmap()->setPos( -healthdiv, 0 ); + } + // health bar + if (m_pBitmapHealthEmpty->GetBitmap() && m_pBitmapHealthFull->GetBitmap()) + { + m_pBitmapHealthFlash->setVisible(false); + m_pBitmapHealthEmpty->GetBitmap()->setColor(Color(255, 255, 255, 0)); + m_pBitmapHealthFull->GetBitmap()->setColor(Color(255, 255, 255, 0)); + if (m_pHealthIcon->GetBitmap()) + m_pHealthIcon->GetBitmap()->setColor(Color(255, 255, 255, 0)); + + if (curtime >= m_fHealthUpdateTime + HEALTH_FLASH_TIME) + { + healthdiv = (int)((float)health/100 * m_iHealthBarWidth); + float frac = curtime - m_fHealthUpdateTime - HEALTH_FLASH_TIME; + int targetalpha = HEALTH_ALPHA; + if (health == 0) targetalpha = HEALTH_ZERO_ALPHA; + int alpha = targetalpha; + if (frac < HEALTH_FADE_TIME) + { + frac = frac / HEALTH_FADE_TIME; + alpha = (int)(frac * targetalpha); + } + m_pBitmapHealthEmpty->GetBitmap()->setColor(Color(255, 255, 255, alpha)); + m_pBitmapHealthFull->GetBitmap()->setColor(Color(255, 255, 255, alpha)); + if (m_pHealthIcon->GetBitmap()) + m_pHealthIcon->GetBitmap()->setColor(Color(255, 255, 255, alpha)); + + } + else + { + float frac = (curtime - m_fHealthUpdateTime) / HEALTH_FLASH_TIME; + if ((health < oldhealth) && m_pBitmapHealthFlash->GetBitmap()) // we had take damage, make red flash + { + m_pBitmapHealthFlash->setVisible(true); + m_pBitmapHealthFlash->GetBitmap()->setColor(Color(255, 255, 255, 255*frac)); + } + frac = 1 - frac; + frac *= frac; + healthdiv = health - (int)((float)(health - oldhealth)*frac); + healthdiv = (int)((float)healthdiv/100 * m_iHealthBarWidth); + } + + m_pBitmapHealthFull->setBounds(m_iHealthBarXpos, m_iHealthBarYpos, healthdiv, m_iHealthBarHeight); + m_pBitmapHealthFlash->setBounds(m_iHealthBarXpos, m_iHealthBarYpos, healthdiv, m_iHealthBarHeight); + + m_pBitmapHealthEmpty->setBounds(m_iHealthBarXpos + healthdiv, m_iHealthBarYpos, + m_iHealthBarWidth - healthdiv, m_iHealthBarHeight); + m_pBitmapHealthEmpty->GetBitmap()->setPos( -healthdiv, 0 ); + } + + // armor bar + if (m_pBitmapArmorEmpty->GetBitmap() && m_pBitmapArmorFull->GetBitmap()) + { + m_pBitmapArmorFlash->setVisible(false); + m_pBitmapArmorEmpty->GetBitmap()->setColor(Color(255, 255, 255, 0)); + m_pBitmapArmorFull->GetBitmap()->setColor(Color(255, 255, 255, 0)); + if (m_pArmorIcon->GetBitmap()) + m_pArmorIcon->GetBitmap()->setColor(Color(255, 255, 255, 0)); + if (curtime >= m_fArmorUpdateTime + HEALTH_FLASH_TIME) + { + healthdiv = (int)((float)armor/100 * m_iArmorBarWidth); + float frac = curtime - m_fArmorUpdateTime - HEALTH_FLASH_TIME; + int targetalpha = HEALTH_ALPHA; + if (armor == 0) targetalpha = HEALTH_ZERO_ALPHA; + int alpha = targetalpha; + if (frac < HEALTH_FADE_TIME) + { + frac = frac / HEALTH_FADE_TIME; + alpha = (int)(frac * targetalpha); + } + m_pBitmapArmorEmpty->GetBitmap()->setColor(Color(255, 255, 255, alpha)); + m_pBitmapArmorFull->GetBitmap()->setColor(Color(255, 255, 255, alpha)); + if (m_pArmorIcon->GetBitmap()) + m_pArmorIcon->GetBitmap()->setColor(Color(255, 255, 255, alpha)); + } + else + { + float frac = (curtime - m_fArmorUpdateTime) / HEALTH_FLASH_TIME; + if ((armor < oldarmor) && m_pBitmapArmorFlash->GetBitmap()) // we had take damage, make red flash + { + m_pBitmapArmorFlash->setVisible(true); + m_pBitmapArmorFlash->GetBitmap()->setColor(Color(255, 255, 255, 255*frac)); + } + frac = 1 - frac; + frac *= frac; + healthdiv = armor - (int)((float)(armor - oldarmor)*frac); + healthdiv = (int)((float)healthdiv/100 * m_iArmorBarWidth); + } + + m_pBitmapArmorFull->setBounds(m_iArmorBarXpos, m_iArmorBarYpos, healthdiv, m_iArmorBarHeight); + m_pBitmapArmorFlash->setBounds(m_iArmorBarXpos, m_iArmorBarYpos, healthdiv, m_iArmorBarHeight); + + m_pBitmapArmorEmpty->setBounds(m_iArmorBarXpos + healthdiv, m_iArmorBarYpos, + m_iArmorBarWidth - healthdiv, m_iArmorBarHeight); + m_pBitmapArmorEmpty->GetBitmap()->setPos( -healthdiv, 0 ); + } + Panel::solve(); +} + + +void CHud2::paint() +{ + // + // draw ammo counters + // + if ( ShouldDrawHUD() ) // Wargon: Èíôîðìàöèÿ î ïàòðîíàõ ðèñóåòñÿ òîëüêî åñëè hud_draw = 1. + { + WEAPON *pw = gHUD.m_Ammo.m_pWeapon; // shorthand + if (gHUD.m_SpecTank_on) + { + int x = ScreenWidth - HEALTH_RIGHT_OFFSET; + int y = ScreenHeight - HEALTH_DOWN_OFFSET; + + BitmapTGA* pImg = FindAmmoImageForWeapon("machinegun"); + if (pImg) + { + int iw, ih; + pImg->getSize(iw, ih); + x -= iw; + pImg->setColor(Color(255, 255, 255, 0)); // TEST + pImg->setPos(x, y - ih); + pImg->doPaint(this); + x -= XRES(6); // make some space between icon and text + y -= 10; + } + + if (gHUD.m_SpecTank_Ammo != -1) + { + // draw ammo count string + int tw, th; + char buf[256]; + drawSetTextFont(m_pFontDigits); + drawSetTextColor(250, 250, 250, 0); // TEST + + sprintf(buf, "%d", gHUD.m_SpecTank_Ammo); + m_pFontDigits->getTextSize(buf, tw, th); + x -= tw; y -= th; + drawSetTextPos(x, y); drawPrintText(buf, strlen(buf)); + } + } + else if (pw) + { + int x = ScreenWidth - HEALTH_RIGHT_OFFSET; + int y = ScreenHeight - HEALTH_DOWN_OFFSET; + + // Do we have secondary ammo? + if ((pw->iAmmo2Type > 0) && (gWR.CountAmmo(pw->iAmmo2Type) > 0)) + { + // Draw the secondary ammo Icon + char buf[256]; + sprintf(buf, "%s_sec", pw->szName); + BitmapTGA* pImg = FindAmmoImageForWeapon(buf); + if (pImg) + { + int iw, ih; + pImg->getSize(iw, ih); + x -= iw; + pImg->setColor(Color(255, 255, 255, 0)); // TEST + pImg->setPos(x, y - ih); + pImg->doPaint(this); + x -= XRES(6); // make some space between icon and text + y -= 10; + } + + // draw ammo count string + int tw, th; + sprintf(buf, "%d", gWR.CountAmmo(pw->iAmmo2Type)); + + m_pFontDigits->getTextSize(buf, tw, th); + drawSetTextFont(m_pFontDigits); + drawSetTextColor(250, 250, 250, 0); // TEST + drawSetTextPos(x - tw, y - th); + drawPrintText(buf, strlen(buf)); + x = ScreenWidth - HEALTH_RIGHT_OFFSET - XRES(100); + } + + if (pw->iAmmoType > 0) + { + y = ScreenHeight - HEALTH_DOWN_OFFSET; + + // Draw the ammo Icon + BitmapTGA* pImg = FindAmmoImageForWeapon(pw->szName); + if (pImg) + { + int iw, ih; + pImg->getSize(iw, ih); + x -= iw; + pImg->setColor(Color(255, 255, 255, 0)); // TEST + pImg->setPos(x, y - ih); + pImg->doPaint(this); + x -= XRES(6); // make some space between icon and text + y -= 10; + } + + // draw ammo count string + int tw, th; + char buf[256]; + drawSetTextFont(m_pFontDigits); + drawSetTextColor(250, 250, 250, 0); // TEST + + sprintf(buf, "%d", gWR.CountAmmo(pw->iAmmoType)); + m_pFontDigits->getTextSize(buf, tw, th); + x -= tw; y -= th; + drawSetTextPos(x, y); drawPrintText(buf, strlen(buf)); + + if (pw->iClip >= 0) // has clip? + { + x -= YRES(12); + drawSetTextPos(x, y); drawPrintText("/", 1); + + sprintf(buf, "%d", pw->iClip); + m_pFontDigits->getTextSize(buf, tw, th); + x = x - tw - YRES(5); + drawSetTextPos(x, y); drawPrintText(buf, strlen(buf)); + } + } + } + } + + Panel::paint(); +} + +BitmapTGA* CHud2 :: FindAmmoImageForWeapon( const char *weapon ) +{ + for( int i = 0; i < m_iNumWeaponNames; i++ ) + { + if( !Q_strcmp( weapon, m_pWeaponNames[i] )) + return m_pWeaponIconsArray[i]; + } + + return NULL; +} + +void CHud2::UpdateHealth( int newhealth ) +{ + if (newhealth == health) + return; + + if (health == -1) // first update, dont do effects + { + health = newhealth; + m_fHealthUpdateTime = -666; + return; + } + + oldhealth = health; + health = newhealth; + m_fHealthUpdateTime = gEngfuncs.GetClientTime(); +} + + +void CHud2::UpdateArmor( int newarmor ) +{ + if (newarmor == armor) + return; + + if (armor == -1) // first update, dont do effects + { + armor = newarmor; + m_fArmorUpdateTime = -666; + return; + } + + oldarmor = armor; + armor = newarmor; + m_fArmorUpdateTime = gEngfuncs.GetClientTime(); +} + +void CHud2::UpdateStamina( int newStamina ) +{ + if (newStamina == stamina) + return; + + if (stamina == -1) // first update, dont do effects + { + stamina = newStamina; + m_fStaminaUpdateTime = -666; + return; + } + + oldstamina = stamina; + stamina = newStamina; + m_fStaminaUpdateTime = gEngfuncs.GetClientTime(); +} diff --git a/cl_dll/vgui_hud.h b/cl_dll/vgui_hud.h new file mode 100644 index 0000000..2d0c5a8 --- /dev/null +++ b/cl_dll/vgui_hud.h @@ -0,0 +1,86 @@ +// ====================================== +// Paranoia vgui hud header file +// written by BUzer. +// ====================================== + +#ifndef _VGUIHUD_H +#define _VGUIHUD_H +using namespace vgui; + +#define NUM_WEAPON_ICONS 256 // ammo + miscellaneous items + +class ImageHolder; + +class CHud2 : public Panel +{ +public: + CHud2(); + ~CHud2(); + void Initialize(); + + void UpdateHealth (int newHealth); + void UpdateArmor (int newArmor); + void UpdateStamina(int newStamina); + +// void Think(); + virtual void solve(); + +protected: + virtual void paintBackground(); // per-frame calculations and checks + virtual void paint(); + +protected: + // Wargon: Èêîíêà þçà. + CImageLabel *m_pUsageIcon; + float m_fUsageUpdateTime; + + // painkiller icon + CImageLabel *m_pMedkitsIcon; + Label *m_pMedkitsCount; + int m_pMedkitsOldNum; + float m_fMedkitUpdateTime; + + // gasmask & headshield + CImageLabel *m_pShieldIcon; + CImageLabel *m_pGasMaskIcon; + + // health and armor bars + ImageHolder *m_pBitmapHealthFull; + ImageHolder *m_pBitmapHealthEmpty; + ImageHolder *m_pBitmapHealthFlash; + + ImageHolder *m_pBitmapArmorFull; + ImageHolder *m_pBitmapArmorEmpty; + ImageHolder *m_pBitmapArmorFlash; + + ImageHolder *m_pBitmapStaminaFull; + ImageHolder *m_pBitmapStaminaEmpty; + ImageHolder *m_pBitmapStaminaFlash; + + ImageHolder *m_pHealthIcon; + ImageHolder *m_pArmorIcon; + ImageHolder *m_pStaminaIcon; + + int m_iHealthBarWidth, m_iHealthBarHeight; + int m_iHealthBarXpos, m_iHealthBarYpos; + int m_iArmorBarWidth, m_iArmorBarHeight; + int m_iArmorBarXpos, m_iArmorBarYpos; + int m_iStaminaBarWidth, m_iStaminaBarHeight; + int m_iStaminaBarXpos, m_iStaminaBarYpos; + + BitmapTGA *m_pWeaponIconsArray[NUM_WEAPON_ICONS]; + BitmapTGA *FindAmmoImageForWeapon(const char *wpn); + + Font *m_pFontDigits; + + char *m_pWeaponNames[NUM_WEAPON_ICONS]; + int m_iNumWeaponNames; + + int health, armor, stamina; + int oldhealth, oldarmor, oldstamina; + float m_fHealthUpdateTime, m_fArmorUpdateTime, m_fStaminaUpdateTime; +}; + +void Hud2Init(); + +#endif // _VGUIHUD_H \ No newline at end of file diff --git a/cl_dll/vgui_int.cpp b/cl_dll/vgui_int.cpp new file mode 100644 index 0000000..36fa80b --- /dev/null +++ b/cl_dll/vgui_int.cpp @@ -0,0 +1,128 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include"vgui_int.h" +#include +#include +#include +#include +#include +#include +#include +#include "hud.h" +#include "cl_util.h" +#include "camera.h" +#include "kbutton.h" +#include "cvardef.h" +#include "usercmd.h" +#include "const.h" +#include "camera.h" +#include "in_defs.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_ControlConfigPanel.h" + +namespace +{ + +class TexturePanel : public Panel , public ActionSignal +{ +private: + int _bindIndex; + TextEntry* _textEntry; +public: + TexturePanel() : Panel(0,0,256,276) + { + _bindIndex=2700; + _textEntry=new TextEntry("2700",0,0,128,20); + _textEntry->setParent(this); + _textEntry->addActionSignal(this); + } +public: + virtual bool isWithin(int x,int y) + { + return _textEntry->isWithin(x,y); + } +public: + virtual void actionPerformed(Panel* panel) + { + char buf[256]; + _textEntry->getText(0,buf,256); + sscanf(buf,"%d",&_bindIndex); + } +protected: + virtual void paintBackground() + { + Panel::paintBackground(); + + int wide,tall; + getPaintSize(wide,tall); + + drawSetColor(0,0,255,0); + drawSetTexture(_bindIndex); + drawTexturedRect(0,19,257,257); + } + +}; + +} + + +using namespace vgui; + +void VGui_ViewportPaintBackground(int extents[4]) +{ + gEngfuncs.VGui_ViewportPaintBackground(extents); +} + +void* VGui_GetPanel() +{ + return (Panel*)gEngfuncs.VGui_GetPanel(); +} + +void VGui_Startup() +{ + Panel* root=(Panel*)VGui_GetPanel(); + root->setBgColor(128,128,0,0); + //root->setNonPainted(false); + //root->setBorder(new LineBorder()); + root->setLayout(new BorderLayout(0)); + + + //root->getSurfaceBase()->setEmulatedCursorVisible(true); + + if (gViewPort != NULL) + { +// root->removeChild(gViewPort); + + // free the memory +// delete gViewPort; +// gViewPort = NULL; + + gViewPort->Initialize(); + } + else + { + gViewPort = new TeamFortressViewport(0,0,root->getWide(),root->getTall()); + gViewPort->setParent(root); + } + + +/* TexturePanel* texturePanel=new TexturePanel(); + texturePanel->setParent(gViewPort); + // TEST*/ +} + +void VGui_Shutdown() +{ + delete gViewPort; + gViewPort = NULL; +} + + + + + diff --git a/cl_dll/vgui_int.h b/cl_dll/vgui_int.h new file mode 100644 index 0000000..b5302b2 --- /dev/null +++ b/cl_dll/vgui_int.h @@ -0,0 +1,21 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_INT_H +#define VGUI_INT_H + +extern "C" +{ +void VGui_Startup(); +void VGui_Shutdown(); + +//Only safe to call from inside subclass of Panel::paintBackground +void VGui_ViewportPaintBackground(int extents[4]); +} + + +#endif \ No newline at end of file diff --git a/cl_dll/vgui_paranoiatext.cpp b/cl_dll/vgui_paranoiatext.cpp new file mode 100644 index 0000000..2c3e6bf --- /dev/null +++ b/cl_dll/vgui_paranoiatext.cpp @@ -0,0 +1,1402 @@ +#include "hud.h" +#include "cl_util.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_paranoiatext.h" +#include "VGUI_LineBorder.h" +#include "VGUI_TextImage.h" +#include "../engine/keydefs.h" +#include "triangleapi.h" +#include "..\game_shared\vgui_LoadTGA.h" +#include "r_studioint.h" +#include "com_model.h" +#include "stringlib.h" +#include "gl_local.h" + +extern engine_studio_api_t IEngineStudio; + +#define WSIZE_X XRES(400); +#define WSIZE_Y YRES(400); + +// âîçâðàùàåò 1, åñëè òýã ñóùåñòâóåò, è 0 â ñëó÷àå êîíöà ôàéëà +// ptext îñòàíàâëèâàåòñÿ íà îòêðûâàþùåé ñêîáêå òýãà +int FindNextTag(char* &ptext) +{ + while (*ptext != '<') + { + if (*ptext == 0) + return 0; // end of file + + ptext++; + } + + // òåïåðü íàäî óáåäèòüñÿ, ÷òî òýã èìååò çàêðûâàþùóþ ñêîáêó + char* hlp = ptext; + while (*hlp != '>') + { + if (*hlp == 0) + return 0; + + hlp++; + } + + return 1; +} + + +// ïîëó÷àåò óêàçàòåëü íà íà÷àëî íàçâàíèÿ òýãà, è äîõîäèò ëèáî äî ïðîáåëà, ëèáî +// äî çàêðûâàþùåé ñêîáêè +// ðàçìåð áóôåðà - 32 ñèìâîëà +// âîçâðàùàåò 1, åñëè ó òýãà åñòü ïàðàìåòðû, è 0 - åñëè íåòó +int GetTagName(char* &ptext, char* bufTagName) +{ + char* pstart = ptext; + while(1) + { + if (*ptext == '>') + { + int length = ptext - pstart; + if (length > 31) length = 31; + + memcpy(bufTagName, pstart, length); + bufTagName[length] = 0; + + ptext++; + return 0; + } + else if (*ptext == ' ') + { + int length = ptext - pstart; + if (length > 31) length = 31; + + memcpy(bufTagName, pstart, length); + bufTagName[length] = 0; + + return 1; + } + + ptext++; + } +} + + +// ïîëó÷àåò óêàçàòåëü íà ìåñòî ñðàçó çà èìåíåì òýãà, è âîçâðàùàåò åãî ïàðàìåòðû â áóôåðàõ +// áóôåðû äîëæíû áûòü ðàçìåðîì â 32 áàéòà +// ïðåäïîëàãàåò, ÷òî êîíåö ôàéëà íàñòóïèòü íå äîëæåí äî çàêðûâàþùåé ñêîáêè +int GetTagParameter(char* &ptext, char* bufParamName, char* bufParamValue) +{ + // ïðîïóñêàåì íà÷àëüíûå ïðîáåëû è ïåðåíîñû + while (*ptext == ' ' || *ptext == '\n') + ptext++; + + // íà÷èíàåì ÷òåíèå íàçâàíèÿ ïàðàìåòðà + char* start = ptext; + while (*ptext != '=') + { + if (*ptext == '>') + { + ptext++; + return 0; // òýã êîí÷èëñÿ + } + + ptext++; + } + + int paramNameLength = ptext - start; + if (paramNameLength > 31) paramNameLength = 31; // îáðåçàòü ïî áóôåðó + + memcpy(bufParamName, start, paramNameLength); + bufParamName[paramNameLength] = 0; + + // òåïåðü ÷èòàåì åãî çíà÷åíèå + ptext++; // ïåðåïðûãèâàåì çíàê ðàâíî + + if (*ptext == '\"') + { + // àðãóìåíò çàêëþ÷åí â êàâû÷êè + ptext++; + start = ptext; + while (1) + { + if (*ptext == '\"') + { + int paramValueLength = ptext - start; + if (paramValueLength > 31) paramValueLength = 31; + + memcpy(bufParamValue, start, paramValueLength); + bufParamValue[paramValueLength] = 0; + + ptext++; + return 1; + } + else if (*ptext == '>') + { + int paramValueLength = ptext - start; + if (paramValueLength > 31) paramValueLength = 31; + + memcpy(bufParamValue, start, paramValueLength); + bufParamValue[paramValueLength] = 0; + + return 1; + } + ptext++; + } + } + + start = ptext; + while(1) + { + if (*ptext == '>' || *ptext == ' ' || *ptext == '\n') + { + int paramValueLength = ptext - start; + if (paramValueLength > 31) paramValueLength = 31; + + memcpy(bufParamValue, start, paramValueLength); + bufParamValue[paramValueLength] = 0; + + return 1; + } + ptext++; + } +} + + +void ParseColor(char* ptext, int &r, int &g, int &b, int &a) +{ + int iArray[4]; + int current = 0; + char tmp[8]; + + memset(iArray, 0, sizeof(int)*4); + while (current < 4 && *ptext != 0) + { + // search for space or end of string + char* pstart = ptext; + while (*ptext != ' ' && *ptext != 0) + ptext++; + + int length = ptext - pstart; + if (length > 7) length = 7; + + memcpy(tmp, pstart, length); + tmp[length] = 0; + + iArray[current] = atoi(tmp); + current++; + + if (*ptext == ' ') ptext++; + } + + r = iArray[0]; + g = iArray[1]; + b = iArray[2]; + a = iArray[3]; +} + + + + + + +// ===================================================================== + +/*class CMainPanel : public Panel, public CRenderable +{ +public: + CMainPanel(const char* imgname, int x,int y,int wide,int tall) : Panel(x, y, wide, tall) + { + m_pBitmap = vgui_LoadTGA(imgname); + setPaintBackgroundEnabled(false); + + if (!m_pBitmap) + gEngfuncs.Con_Printf("Cant load image for background: [%s]\n", imgname); + + m_pBorderPanel = NULL; + } + + ~CMainPanel() + { + if (m_pBitmap) + delete m_pBitmap; + } + + void paint() + { + if (m_pBitmap) + { + int imgX, imgY; + int panX, panY; + m_pBitmap->getSize(imgX, imgY); + getSize(panX, panY); + for (int curY = 0; curY < panY; curY += imgY) + { + for (int curX = 0; curX < panX; curX += imgX) + { + m_pBitmap->setPos(curX, curY); + m_pBitmap->doPaint(this); + } + } + } + else + { + drawSetColor(0, 0, 0, 100); + drawFilledRect(0, 0, getWide(), getTall()); + } + + drawSetColor(0, 0, 0, 150); + drawOutlinedRect(0, 0, getWide() - 1, getTall() - 1); + + drawSetColor(255, 255, 255, 150); + drawOutlinedRect(1, 1, getWide(), getTall()); + + drawSetColor(0, 0, 0, 0); + drawOutlinedRect(0, 0, getWide(), getTall()); + + // sort of hack, to draw frame around the scrollpanel + if (m_pBorderPanel) + { + int x, y, wide, tall; + m_pBorderPanel->getBounds(x, y, wide, tall); + + drawSetColor(255, 255, 255, 180); + drawOutlinedRect(x-2, y-2, x+wide+1, y+tall+1); + + drawSetColor(0, 0, 0, 60); + drawOutlinedRect(x-1, y-1, x+wide+2, y+tall+2); + + drawSetColor(0, 0, 0, 0); + drawOutlinedRect(x-2, y-2, x+wide+2, y+tall+2); + } + } + + void SetDrawBorder(Panel *pan) + { + m_pBorderPanel = pan; + } + +private: + BitmapTGA* m_pBitmap; + Panel* m_pBorderPanel; +}; + + +class CMyButton : public Button, public CRenderable +{ +public: + CMyButton(const char* text, const char* imgname, int x, int y) : Button(text, x, y) + { + m_pBitmap = vgui_LoadTGA(imgname); + + if (m_pBitmap) + m_pBitmap->setPos(0,0); + else + gEngfuncs.Con_Printf("Cant load image for button: [%s]\n", imgname); + } + + ~CMyButton() + { + if (m_pBitmap) + delete m_pBitmap; + } + + void paintBackground() + { + if (m_pBitmap) + m_pBitmap->doPaint(this); + + if (isSelected()) + { + drawSetColor(0, 0, 0, 200); + drawFilledRect(0, 0, getWide(), getTall()); + + drawSetColor(255, 255, 255, 150); + drawOutlinedRect(0, 0, getWide()-1, getTall()-1); + + drawSetColor(0, 0, 0, 150); + drawOutlinedRect(1, 1, getWide(), getTall()); + } + else + { + drawSetColor(0, 0, 0, 150); + drawOutlinedRect(0, 0, getWide()-1, getTall()-1); + + drawSetColor(255, 255, 255, 150); + drawOutlinedRect(1, 1, getWide(), getTall()); + } + + drawSetColor(0, 0, 0, 0); + drawOutlinedRect(0, 0, getWide(), getTall()); + } + + void internalCursorExited() + { + setSelected(false); + } + +private: + BitmapTGA* m_pBitmap; +};*/ + +void OrthoQuad(int x1, int y1, int x2, int y2); + +//================================== +// CMainPanel - ãëàâíàÿ ïàíåëü îêíà. +//================================== +class CMainPanel : public Panel, public CRenderable +{ +public: + CMainPanel(const char* imgname, int x,int y,int wide,int tall) : Panel(x, y, wide, tall) + { + setPaintBackgroundEnabled(false); + + if (!IEngineStudio.IsHardware() || imgname[0] == 0) + { + setPaintEnabled(true); + m_hsprImage = NULL; + m_hBitmap = 0; + return; + } + else + setPaintEnabled(false); + + const char *ext = UTIL_FileExtension( imgname ); + + m_hBitmap = 0; + + if(( !Q_stricmp( ext, "dds" ) || !Q_stricmp( ext, "tga" )) && g_fRenderInterfaceValid ) + { + m_hBitmap = LOAD_TEXTURE( imgname, NULL, 0, TF_IMAGE ); + + if (!m_hBitmap) + { + gEngfuncs.Con_Printf("ERROR: Cant load image for background: [%s]\n", imgname); + setPaintEnabled(true); + } + return; + } + + m_hsprImage = SPR_Load(imgname); + + if (!m_hsprImage) + { + gEngfuncs.Con_Printf("ERROR: Cant load image for background: [%s]\n", imgname); + setPaintEnabled(true); + return; + } + + if (SPR_Frames(m_hsprImage) != 4) + { + gEngfuncs.Con_Printf("ERROR: Expecting 4 frames in sprite: [%s]\n", imgname); + m_hsprImage = 0; + setPaintEnabled(true); + } + } + + void Render() + { + int x, y; + getPos(x, y); + + if( m_hBitmap && g_fRenderInterfaceValid ) + { + gEngfuncs.pTriAPI->RenderMode( kRenderTransAlpha ); + GL_BindTexture( GL_TEXTURE0, m_hBitmap ); + OrthoQuad( x, y, x + getWide(), y + getTall() ); + gEngfuncs.pTriAPI->RenderMode(kRenderNormal); + return; + } + + if (!m_hsprImage) + return; + + const struct model_s *sprmodel = gEngfuncs.GetSpritePointer(m_hsprImage); + gEngfuncs.pTriAPI->RenderMode(kRenderNormal); + + gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *) sprmodel, 0); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); //no culling + gEngfuncs.pTriAPI->Color4f( 1, 1, 1, 1 ); + OrthoQuad(x, y, x+getWide()/2, y+getTall()/2); + + gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *) sprmodel, 1); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); //no culling + gEngfuncs.pTriAPI->Color4f( 1, 1, 1, 1 ); + OrthoQuad(x+getWide()/2, y, x+getWide(), y+getTall()/2); + + gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *) sprmodel, 2); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); //no culling + gEngfuncs.pTriAPI->Color4f( 1, 1, 1, 1 ); + OrthoQuad(x, y+getTall()/2, x+getWide()/2, y+getTall()); + + gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *) sprmodel, 3); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); //no culling + gEngfuncs.pTriAPI->Color4f( 1, 1, 1, 1 ); + OrthoQuad(x+getWide()/2, y+getTall()/2, x+getWide(), y+getTall()); + + gEngfuncs.pTriAPI->CullFace( TRI_FRONT ); + } + + // in case we didnt loaded texture.. + void paint() + { + drawSetColor(0, 0, 0, 100); + drawFilledRect(0, 0, getWide(), getTall()); + + drawSetColor(255, 255, 2550, 150); + drawOutlinedRect(0, 0, getWide(), getTall()); + + if (m_pBorderPanel) + { + int x, y, wide, tall; + m_pBorderPanel->getBounds(x, y, wide, tall); + + drawSetColor(255, 255, 255, 180); + drawOutlinedRect(x-2, y-2, x+wide+1, y+tall+1); + + drawSetColor(0, 0, 0, 60); + drawOutlinedRect(x-1, y-1, x+wide+2, y+tall+2); + + drawSetColor(0, 0, 0, 0); + drawOutlinedRect(x-2, y-2, x+wide+2, y+tall+2); + } + } + + void SetDrawBorder(Panel *pan) + { + m_pBorderPanel = pan; + } + + bool IsTgaPanel( void ) { return (m_hBitmap != 0) ? true : false; } + +private: + HSPRITE m_hsprImage; + int m_hBitmap; + Panel* m_pBorderPanel; +}; + +//================================== +// CMyButton - êíîïêà çàêðûòèÿ îêíà +//================================== +class CMyButton : public Button, public CRenderable +{ +public: + CMyButton(const char* text, const char* imgname, int x, int y) : Button(text, x, y) + { + if (!IEngineStudio.IsHardware() || imgname[0] == 0) + { + setPaintBackgroundEnabled(true); + m_hsprImage = NULL; + return; + } + else + setPaintBackgroundEnabled(false); + + m_hsprImage = SPR_Load(imgname); + + if (!m_hsprImage) + { + gEngfuncs.Con_Printf("ERROR: Cant load image for button: [%s]\n", imgname); + setPaintBackgroundEnabled(true); + return; + } + + if (SPR_Frames(m_hsprImage) != 2) + { + gEngfuncs.Con_Printf("ERROR: Expecting 2 frames in sprite: [%s]\n", imgname); + m_hsprImage = 0; + setPaintBackgroundEnabled(true); + } + } + + void Render() + { + if (!m_hsprImage) + return; + + int x, y, xparent, yparent; + getPos(x, y); + getParent()->getPos(xparent, yparent); + x += xparent; + y += yparent; + + int frame = 0; + if (isSelected()) frame = 1; + + const struct model_s *sprmodel = gEngfuncs.GetSpritePointer(m_hsprImage); + gEngfuncs.pTriAPI->RenderMode(kRenderNormal); + gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *) sprmodel, frame); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); //no culling + gEngfuncs.pTriAPI->Color4f( 1, 1, 1, 1 ); + OrthoQuad(x, y, x+getWide(), y+getTall()); + gEngfuncs.pTriAPI->CullFace( TRI_FRONT ); + } + + void paintBackground() + { + if (isSelected()) + { + drawSetColor(58, 37, 19, 0); + drawFilledRect(0, 0, getWide(), getTall()); + + drawSetColor(255, 255, 255, 150); + drawOutlinedRect(0, 0, getWide()-1, getTall()-1); + + drawSetColor(0, 0, 0, 150); + drawOutlinedRect(1, 1, getWide(), getTall()); + } + else + { + drawSetColor(98, 60, 30, 0); + drawFilledRect(0, 0, getWide(), getTall()); + + drawSetColor(0, 0, 0, 150); + drawOutlinedRect(0, 0, getWide()-1, getTall()-1); + + drawSetColor(255, 255, 255, 150); + drawOutlinedRect(1, 1, getWide(), getTall()); + } + + drawSetColor(0, 0, 0, 0); + drawOutlinedRect(0, 0, getWide(), getTall()); + } + + void internalCursorExited() + { + setSelected(false); + } + +private: + HSPRITE m_hsprImage; +}; + + +//================================== +// CMySlider - ïîëçóíîê ïðîêðóòêè â ñòèëå ïàðàíîéè +//================================== +class CMySlider : public Slider +{ +public: + CMySlider(int x,int y,int wide,int tall,bool vertical) : Slider(x,y,wide,tall,vertical){}; + + void paintBackground( void ) + { + int wide,tall,nobx,noby; + getPaintSize(wide,tall); + getNobPos(nobx,noby); + + //background + drawSetColor(98, 60, 30, 150); + drawFilledRect( 0,0,wide,tall ); + + // nob + drawSetColor(98, 60, 30, 0); + drawFilledRect( 0,nobx,wide,noby ); + + drawSetColor(0, 0, 0, 150); + drawOutlinedRect( 0,nobx,wide-1,noby-1 ); + + drawSetColor(255, 255, 255, 150); + drawOutlinedRect( 1,nobx+1,wide,noby ); + + drawSetColor(0, 0, 0, 0); + drawOutlinedRect( 0,nobx,wide,noby ); + } +}; + + +//================================== +// CMyScrollButton - êíîïêà ïðîêðóòêè +//================================== +class CMyScrollbutton : public Button +{ +public: + CMyScrollbutton(int up, int x, int y) : Button("", x, y, 16, 16) + { + if (up) + setImage(vgui_LoadTGA("gfx/vgui/arrowup.tga")); + else + setImage(vgui_LoadTGA("gfx/vgui/arrowdown.tga")); + + setPaintEnabled(true); + setPaintBackgroundEnabled(true); + } + + void paintBackground() + { + if (isSelected()) + { + drawSetColor(58, 37, 19, 0); + drawFilledRect(0, 0, getWide(), getTall()); + + drawSetColor(255, 255, 255, 150); + drawOutlinedRect(0, 0, getWide()-1, getTall()-1); + + drawSetColor(0, 0, 0, 150); + drawOutlinedRect(1, 1, getWide(), getTall()); + } + else + { + drawSetColor(98, 60, 30, 0); + drawFilledRect(0, 0, getWide(), getTall()); + + drawSetColor(0, 0, 0, 150); + drawOutlinedRect(0, 0, getWide()-1, getTall()-1); + + drawSetColor(255, 255, 255, 150); + drawOutlinedRect(1, 1, getWide(), getTall()); + } + + drawSetColor(0, 0, 0, 0); + drawOutlinedRect(0, 0, getWide(), getTall()); + } + + void internalCursorExited() + { + setSelected(false); + } +}; + + +//================================== +// CMyScrollPanel - ïðîêðó÷èâàåìàÿ ïàíåëü +//================================== +class CMyScrollPanel : public ScrollPanel, public CRenderable +{ +public: + CMyScrollPanel(const char* imgname, int x,int y,int wide,int tall) : ScrollPanel(x, y, wide, tall) + { + ScrollBar *pScrollBar = getVerticalScrollBar(); + pScrollBar->setButton( new CMyScrollbutton( 1, 0,0 ), 0 ); + pScrollBar->setButton( new CMyScrollbutton( 0, 0,0 ), 1 ); + pScrollBar->setSlider( new CMySlider(0,wide-1,wide,(tall-(wide*2))+2,true) ); + pScrollBar->setPaintBorderEnabled(false); + pScrollBar->setPaintBackgroundEnabled(false); + pScrollBar->setPaintEnabled(false); + + setPaintBackgroundEnabled(false); + setPaintEnabled(false); + + if (!IEngineStudio.IsHardware() || imgname[0] == 0) + { + m_hsprImage = NULL; + // setPaintBackgroundEnabled(true); + return; + } + + m_hsprImage = SPR_Load(imgname); + + if (!m_hsprImage) + { + gEngfuncs.Con_Printf("ERROR: Cant load image for scrollpanel: [%s]\n", imgname); + } + } + + void Render() + { + if (!m_hsprImage) + return; + + int x, y, xparent, yparent; + getPos(x, y); + getParent()->getPos(xparent, yparent); + x += xparent; + y += yparent; + + const struct model_s *sprmodel = gEngfuncs.GetSpritePointer(m_hsprImage); + gEngfuncs.pTriAPI->RenderMode(kRenderNormal); + gEngfuncs.pTriAPI->SpriteTexture( (struct model_s *) sprmodel, 0); + gEngfuncs.pTriAPI->CullFace( TRI_NONE ); //no culling + gEngfuncs.pTriAPI->Color4f( 1, 1, 1, 1 ); + OrthoQuad(x, y, x+getWide(), y+getTall()); + gEngfuncs.pTriAPI->CullFace( TRI_FRONT ); + } + +/* void paint() + { + drawSetColor(255, 255, 255, 180); + drawOutlinedRect(0, 0, getWide(), getTall()); + }*/ + +private: + HSPRITE m_hsprImage; +}; + + + + +// ============================================================== + + +void CParanoiaTextPanel::BuildErrorPanel(const char* errorString) +{ + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + SchemeHandle_t hTextScheme = pSchemes->getSchemeHandle( "Default Text" ); + Font *pNormalFont = pSchemes->getFont( hTextScheme ); + + Panel* panel = new Panel(XRES(120), YRES(180), XRES(400), YRES(120)); + panel->setParent(this); + panel->setBgColor(0, 0, 0, 100); + panel->setBorder(new LineBorder); + + int butX, butY; + Button* button = new Button(" OK ", 0, 0); + button->setParent(panel); + button->addActionSignal(this); + button->getSize(butX, butY); + butX = (panel->getWide() - butX) / 2; + butY = panel->getTall() - butY - YRES(10); + button->setPos( butX, butY ); + + int labelpos = (butY - (pNormalFont->getTall() + YRES(8))) / 2; + Label* label = new Label("", XRES(10), labelpos, panel->getWide() - XRES(20), pNormalFont->getTall() + YRES(8)); + label->setParent(panel); + label->setFont(pNormalFont); + label->setPaintBackgroundEnabled(false); + label->setFgColor(255, 255, 255, 0); + label->setContentAlignment( Label::a_center ); + label->setText( errorString ); + + return; +} + + +CParanoiaTextPanel::CParanoiaTextPanel(char* filename) : Panel(0, 0, ScreenWidth, ScreenHeight) +{ + strcpy(m_loadedFileName, filename); // remember file name + m_iRenderElms = 0; + panel = NULL; + + setVisible(true); + setPaintBackgroundEnabled(false); + gViewPort->UpdateCursorState(); + + int fileLength; + char* pFile = (char*)gEngfuncs.COM_LoadFile( filename, 5, &fileLength ); + if (!pFile) + { + char buf[128]; + sprintf(buf, "Unable to load file %s", filename); + BuildErrorPanel(buf); + return; + } + + + char* ptext = pFile; + char tagName[32]; + + if (!FindNextTag(ptext)) + { + char buf[128]; + sprintf(buf, "%s - empty file", filename); + BuildErrorPanel(buf); + return; + } + + + int size_x = WSIZE_X; + int size_y = WSIZE_Y; + int upperBound = YRES(10); + int lowerBound; + + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + SchemeHandle_t hTextScheme = pSchemes->getSchemeHandle( "Default Text" ); +// SchemeHandle_t hTextScheme = pSchemes->getSchemeHandle( "Title Font" ); + Font *pDefaultFont = pSchemes->getFont( hTextScheme ); + + ptext++; + int hasParams = GetTagName(ptext, tagName); + + char panelImage[32]; + char buttonImage[32]; + char scrollImage[32]; + panelImage[0] = 0; + buttonImage[0] = 0; + scrollImage[0] = 0; + int butclr_r = 255, butclr_g = 255, butclr_b = 255, butclr_a = 0; + int scroll_x = 0, scroll_y = 0, scroll_wide = 0, scroll_tall = 0; + + // parse HEAD section + if(!strcmp(tagName, "HEAD")) + { + if (hasParams) + { + char paramName[32]; + char paramValue[32]; + + while( GetTagParameter(ptext, paramName, paramValue) ) + { + if (!strcmp(paramName, "xsize")) + size_x = XRES(atoi(paramValue)); + else if (!strcmp(paramName, "ysize")) + size_y = YRES(atoi(paramValue)); + else if (!strcmp(paramName, "defaultfont")) + { + hTextScheme = pSchemes->getSchemeHandle( paramValue ); + pDefaultFont = pSchemes->getFont( hTextScheme ); + } + else if (!strcmp(paramName, "background")) + strcpy(panelImage, paramValue); + else if (!strcmp(paramName, "imgscroll")) + strcpy(scrollImage, paramValue); + else if (!strcmp(paramName, "imgbutton")) + strcpy(buttonImage, paramValue); + else if (!strcmp(paramName, "buttoncolor")) + ParseColor(paramValue, butclr_r, butclr_g, butclr_b, butclr_a); + else if (!strcmp(paramName, "scrollpos")) + ParseColor(paramValue, scroll_x, scroll_y, scroll_wide, scroll_tall); + else + gEngfuncs.Con_Printf("File %s - unknown HEAD parameter: [%s]\n", filename, paramName); + } + } + + if (!FindNextTag(ptext)) + { + char buf[128]; + sprintf(buf, "%s - got nothing, except HEAD", filename); + BuildErrorPanel(buf); + return; + } + + ptext++; + hasParams = GetTagName(ptext, tagName); + } + + // create main panel + panel = new CMainPanel(panelImage, (getWide()-size_x)/2, (getTall()-size_y)/2, size_x, size_y); + panel->setParent(this); + AddToRenderList(panel); +// panel->setBgColor(0, 0, 0, 100); +// panel->setBorder(new LineBorder); + ResetBackground(); + + // create closing button + int butX, butY; + CMyButton* button = new CMyButton("", buttonImage, 0, 0); + button->setParent(panel); + button->setFont(pDefaultFont); + button->setText(" Close "); + AddToRenderList(button); + button->addActionSignal(this); + button->getSize(butX, butY); + button->setFgColor(butclr_r, butclr_g, butclr_b, butclr_a); + butX = panel->getWide() - butX - XRES(10); + butY = panel->getTall() - butY - YRES(10); + button->setPos( butX, butY ); + lowerBound = butY - YRES(10); + +/* CCheckButton2* pSwitch = new CCheckButton2(); + pSwitch->setParent(panel); + pSwitch->SetImages("gfx/vgui/checked.tga", "gfx/vgui/unchecked.tga"); + pSwitch->SetText("Dont draw world"); + pSwitch->setPos(XRES(10), butY); + pSwitch->SetCheckboxLeft(true); + pSwitch->SetChecked(g_DontDrawWorld ? true : false); + pSwitch->SetHandler(this);*/ + + // parse TITLE section + if(!strcmp(tagName, "TITLE")) + { + Label* pTitle = new Label("", XRES(10), upperBound, panel->getWide() - XRES(20), pDefaultFont->getTall()+YRES(8)); + pTitle->setParent(panel); + pTitle->setPaintBackgroundEnabled(false); + pTitle->setFont(pDefaultFont); // default font + pTitle->setFgColor(255, 255, 255, 0); // default color + pTitle->setContentAlignment( Label::a_center ); // default alignment + + if (hasParams) + { + char paramName[32]; + char paramValue[32]; + + while( GetTagParameter(ptext, paramName, paramValue) ) + { + if (!strcmp(paramName, "font")) + { + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle(paramValue); + Font* pTitleFont = pSchemes->getFont( hTitleScheme ); + pTitle->setFont(pTitleFont); + pTitle->setSize(panel->getWide() - XRES(20), pTitleFont->getTall()+YRES(8)); + } + else if (!strcmp(paramName, "color")) + { + int r, g, b, a; + ParseColor(paramValue, r, g, b, a); + pTitle->setFgColor(r, g, b, a); + } + else if (!strcmp(paramName, "align")) + { + if (!strcmp(paramValue, "left")) + pTitle->setContentAlignment( Label::a_west ); + else if (!strcmp(paramValue, "right")) + pTitle->setContentAlignment( Label::a_east ); + else if (!strcmp(paramValue, "center")) + pTitle->setContentAlignment( Label::a_center ); + else + gEngfuncs.Con_Printf("File %s - unknown align value: [%s]\n", filename, paramValue); + } + else + gEngfuncs.Con_Printf("File %s - unknown TITLE parameter: [%s]\n", filename, paramName); + } + } + + upperBound = YRES(20) + pTitle->getTall(); + + char* titleStart = ptext; + int haveNextTag = FindNextTag(ptext); + int titleLength = ptext - titleStart + 1; + pTitle->setText(titleLength, titleStart); + + if (!haveNextTag) + return; + + ptext++; + hasParams = GetTagName(ptext, tagName); + } + + + // scroll panel begins + if (!scroll_wide) + { + scroll_wide = size_x * 0.8; + scroll_tall = size_y * 0.6; + scroll_x = (size_x - scroll_wide) / 2; + scroll_y = (size_y - scroll_tall) / 2; + } + else + { + scroll_wide = XRES(scroll_wide); + scroll_tall = YRES(scroll_tall); + scroll_x = XRES(scroll_x); + scroll_y = YRES(scroll_y); + } + + button->setPos( scroll_x, scroll_y + scroll_tall + YRES(15) ); +// m_pScrollPanel = new CMyScrollPanel(scrollImage, XRES(40), upperBound, panel->getWide()-XRES(80), lowerBound - upperBound ); + m_pScrollPanel = new CMyScrollPanel(scrollImage, scroll_x, scroll_y, scroll_wide, scroll_tall ); + m_pScrollPanel->setParent(panel); + AddToRenderList(m_pScrollPanel); + panel->SetDrawBorder(m_pScrollPanel); +// pScrollPanel->setBorder( new LineBorder(Color(0,0,0,0)) ); + m_pScrollPanel->setScrollBarAutoVisible(true, true); + m_pScrollPanel->setScrollBarVisible(false, false); + m_pScrollPanel->validate(); + + // including panel + int panelsize = YRES(5); + Panel* pDocument = new Panel(0, 0, m_pScrollPanel->getClientClip()->getWide(), 64); + pDocument->setParent(m_pScrollPanel->getClient()); + pDocument->setPaintBackgroundEnabled(false); +// pDocument->setBgColor(0, 0, 0, 100); + + // reading document elements + while(1) + { + // parse text field + if(!strcmp(tagName, "TEXT")) + { + TextPanel* pTextPanel = new TextPanel("", XRES(5), panelsize, pDocument->getWide() - XRES(10), 64); + pTextPanel->setParent(pDocument); + pTextPanel->setPaintBackgroundEnabled(false); + pTextPanel->setFont(pDefaultFont); // default font + pTextPanel->setFgColor(255, 255, 255, 0); // default color + int dspace = 0; + + bool bgColorSet = false; + + if (hasParams) + { + char paramName[32]; + char paramValue[32]; + + while( GetTagParameter(ptext, paramName, paramValue) ) + { + if (!strcmp(paramName, "font")) + { + SchemeHandle_t hTextScheme = pSchemes->getSchemeHandle(paramValue); + Font* pTextFont = pSchemes->getFont( hTextScheme ); + pTextPanel->setFont(pTextFont); + } + else if (!strcmp(paramName, "color")) + { + int r, g, b, a; + ParseColor(paramValue, r, g, b, a); + pTextPanel->setFgColor(r, g, b, a); + } + else if (!strcmp(paramName, "bgcolor")) + { + int r, g, b, a; + ParseColor(paramValue, r, g, b, a); + pTextPanel->setPaintBackgroundEnabled(true); + pTextPanel->setBgColor(r, g, b, a); + bgColorSet = true; + } + else if (!strcmp(paramName, "lspace")) + { + int tmp_posx, tmp_posy, tmp_sizex, tmp_sizey; + pTextPanel->getBounds(tmp_posx, tmp_posy, tmp_sizex, tmp_sizey); + int ofs = XRES(atoi(paramValue)); + pTextPanel->setBounds(tmp_posx + ofs, tmp_posy, tmp_sizex - ofs, tmp_sizey); + } + else if (!strcmp(paramName, "rspace")) + { + int tmp_sizex, tmp_sizey; + pTextPanel->getSize(tmp_sizex, tmp_sizey); + int ofs = XRES(atoi(paramValue)); + pTextPanel->setSize(tmp_sizex - ofs, tmp_sizey); + } + else if (!strcmp(paramName, "dspace")) + { + dspace = YRES(atoi(paramValue)); + } + else if (!strcmp(paramName, "uspace")) + { + int x, y; + pTextPanel->getPos(x, y); + int uspace = YRES(atoi(paramValue)); + y += uspace; + panelsize += uspace; + pTextPanel->setPos(x, y); + } + else + gEngfuncs.Con_Printf("File %s - unknown TEXT parameter: [%s]\n", filename, paramName); + } + } + + char* textStart = ptext; + int haveNextTag = FindNextTag(ptext); + int textLength = ptext - textStart + 1; + pTextPanel->getTextImage()->setText(textLength, textStart); + pTextPanel->getTextImage()->setSize( pTextPanel->getWide(), pTextPanel->getTall() ); + + int wrappedX, wrappedY; + pTextPanel->getTextImage()->getTextSizeWrapped(wrappedX, wrappedY); + + if (bgColorSet) + { + int x, y, realY, unused; + pTextPanel->setSize( m_pScrollPanel->getClientClip()->getWide() , wrappedY ); + pTextPanel->getTextImage()->setSize( wrappedX , wrappedY ); + pTextPanel->getPos(x, realY); + pTextPanel->getTextImage()->getPos(unused, y); + pTextPanel->getTextImage()->setPos(x, y); + pTextPanel->setPos(0, realY); + } + else + pTextPanel->setSize( wrappedX , wrappedY ); + + panelsize += (wrappedY + dspace); + + if (!haveNextTag) + break; // no more tags + + ptext++; + hasParams = GetTagName(ptext, tagName); + } + + // parse image parameters + else if(!strcmp(tagName, "IMG")) + { + if (hasParams) + { + char paramName[32]; + char paramValue[32]; + BitmapTGA* pImage = NULL; + int uspace = 0; + int dspace = 0; + int align = 0; // 0 - center, 1 - left, 2 - right + + while( GetTagParameter(ptext, paramName, paramValue) ) + { + if (!strcmp(paramName, "src")) + { + static int resArray[] = + { + 320, 400, 512, 640, 800, + 1024, 1152, 1280, 1600 + }; + + if (pImage) + { + gEngfuncs.Con_Printf("File %s - ignoring [src] parameter - already has an image\n", filename); + continue; + } + + // try to load image directly + pImage = vgui_LoadTGA(paramValue); + + if (!pImage) + { + //resolution based image. + // should contain %d substring + int resArrayIndex = 0; + int i = 0; + while ((resArray[i] <= ScreenWidth) && (i < 9)) + { + resArrayIndex = i; + i++; + } + + while(pImage == NULL && resArrayIndex >= 0) + { + char imgName[64]; + sprintf(imgName, paramValue, resArray[resArrayIndex]); + // gEngfuncs.Con_Printf("=== trying to load: %s\n", imgName); + pImage = vgui_LoadTGA(imgName); + resArrayIndex--; + } + } + + if (!pImage) + { + // still no image + gEngfuncs.Con_Printf("File %s - image not loaded: [%s]\n", filename, paramValue); + } + } + else if (!strcmp(paramName, "align")) + { + if (!strcmp(paramValue, "left")) + align = 1; + else if (!strcmp(paramValue, "right")) + align = 2; + else if (!strcmp(paramValue, "center")) + align = 3; + else + gEngfuncs.Con_Printf("File %s - unknown align value: [%s]\n", filename, paramValue); + } + else if (!strcmp(paramName, "uspace")) + uspace = YRES(atoi(paramValue)); + else if (!strcmp(paramName, "dspace")) + dspace = YRES(atoi(paramValue)); + else + gEngfuncs.Con_Printf("File %s - unknown IMG parameter: [%s]\n", filename, paramName); + } + + // create image panel + if (pImage) + { + int tmp_x, tmp_y; + pImage->getSize(tmp_x, tmp_y); + + switch (align) + { + case 0: tmp_x = (pDocument->getWide() - tmp_x) / 2; break; + case 1: default: tmp_x = 0; break; + case 2: tmp_x = pDocument->getWide() - tmp_x; break; + } + + Label *pImg = new Label("", tmp_x, panelsize + uspace); + pImg->setParent(pDocument); + pImg->setImage(pImage); + pImg->setPaintBackgroundEnabled(false); + + panelsize = panelsize + uspace + tmp_y + dspace; + } + } + else + { + gEngfuncs.Con_Printf("File %s - IMG with no parameters\n", filename); + } + + if (!FindNextTag(ptext)) + break; + + ptext++; + hasParams = GetTagName(ptext, tagName); + } + + // unknown tag + else + { + gEngfuncs.Con_Printf("File %s - unknown tag: [%s]\n", filename, tagName); + + if (!FindNextTag(ptext)) + break; // no more tags + + ptext++; + hasParams = GetTagName(ptext, tagName); + } + } + + gEngfuncs.COM_FreeFile(pFile); + + // document is ready, panelsize now contains the height of panel + panelsize += YRES(5); + int doc_x = pDocument->getWide(); + pDocument->setSize(doc_x, panelsize); + m_pScrollPanel->validate(); + + m_iMaxScrollValue = panelsize - m_pScrollPanel->getClientClip()->getTall(); +} + + +void CParanoiaTextPanel::paint() +{ + if (panel && !panel->IsTgaPanel( )) + { + float curtime = gEngfuncs.GetClientTime(); + float delta = (curtime - m_flStartTime) / 0.5; + if (delta > 1) delta = 1; + + int x, y, wide, tall; + panel->getBounds(x, y, wide, tall); + drawSetColor(0, 0, 0, 255 - (int)(delta * 120)); + + drawFilledRect(0, 0, getWide(), y); + drawFilledRect(0, y+tall, getWide(), getTall()); + drawFilledRect(0, y, x, y+tall); + drawFilledRect(x+wide, y, getWide(), y+tall); + } +} + + +void CParanoiaTextPanel::actionPerformed(Panel* panel) +{ + CloseWindow(); +} + + +// return 0 to hook key +// return 1 to allow key +int CParanoiaTextPanel::KeyInput(int down, int keynum, const char *pszCurrentBinding) +{ + if (!down) + return 1; // dont disable releasing of the keys + + switch (keynum) + { + // close window + case K_ENTER: + case K_ESCAPE: + { + CloseWindow(); + return 0; + } + + // mouse and arrows key scroll + case K_MWHEELUP: + case K_UPARROW: + case K_KP_UPARROW: + { + int hor, ver; + m_pScrollPanel->getScrollValue(hor, ver); + ver -= YRES(30); + if (ver < 0) ver = 0; + m_pScrollPanel->setScrollValue(hor, ver); + return 0; + } + + case K_MWHEELDOWN: + case K_DOWNARROW: + case K_KP_DOWNARROW: + { + int hor, ver; + m_pScrollPanel->getScrollValue(hor, ver); + ver += YRES(30); + if (ver > m_iMaxScrollValue) ver = m_iMaxScrollValue; + m_pScrollPanel->setScrollValue(hor, ver); + return 0; + } + + // keyboard page listing + case K_HOME: + case K_KP_HOME: + { + int hor, ver; + m_pScrollPanel->getScrollValue(hor, ver); + m_pScrollPanel->setScrollValue(hor, 0); + return 0; + } + + case K_END: + case K_KP_END: + { + int hor, ver; + m_pScrollPanel->getScrollValue(hor, ver); + m_pScrollPanel->setScrollValue(hor, m_iMaxScrollValue); + return 0; + } + + case K_PGDN: + case K_KP_PGDN: + { + int hor, ver; + m_pScrollPanel->getScrollValue(hor, ver); + ver += m_pScrollPanel->getClientClip()->getTall(); + if (ver > m_iMaxScrollValue) ver = m_iMaxScrollValue; + m_pScrollPanel->setScrollValue(hor, ver); + return 0; + } + + case K_PGUP: + case K_KP_PGUP: + { + int hor, ver; + m_pScrollPanel->getScrollValue(hor, ver); + ver -= m_pScrollPanel->getClientClip()->getTall(); + if (ver < 0) ver = 0; + m_pScrollPanel->setScrollValue(hor, ver); + return 0; + } + + // Wargon: Êîíñîëü, ôóíêöèîíàëüíûå êëàâèøè è êíîïêè ìûøè ïðîïóñêàþòñÿ. + case 96: + case 126: + case K_F1: + case K_F2: + case K_F3: + case K_F4: + case K_F5: + case K_F6: + case K_F7: + case K_F8: + case K_F9: + case K_F10: + case K_F11: + case K_F12: + case K_MOUSE1: + case K_MOUSE2: + case K_MOUSE3: + case K_MOUSE4: + case K_MOUSE5: + return 1; + } + + // Wargon: Âñå îñòàëüíûå êëàâèøè áëîêèðóþòñÿ. + return 0; +} + + +void CParanoiaTextPanel::CloseWindow() +{ + setVisible(false); + gViewPort->UpdateCursorState(); +} + + +void CParanoiaTextPanel::ResetBackground() +{ + m_flStartTime = gEngfuncs.GetClientTime(); +} + +void CParanoiaTextPanel::AddToRenderList(CRenderable* pnew) +{ + if (m_iRenderElms < 4) + { + m_pRenderList[m_iRenderElms] = pnew; + m_iRenderElms++; + } + else + gEngfuncs.Con_Printf("ERROR: too many renderable objects!\n"); +} + + +void OrthoVGUI(void) +{ + if (gViewPort && gViewPort->m_pParanoiaText && gViewPort->m_pParanoiaText->isVisible()) + gViewPort->m_pParanoiaText->Render(); +} + + + +//void CParanoiaTextPanel::StateChanged(CCheckButton2 *pButton) +//{ +// g_DontDrawWorld = !g_DontDrawWorld; +//} diff --git a/cl_dll/vgui_paranoiatext.h b/cl_dll/vgui_paranoiatext.h new file mode 100644 index 0000000..6928692 --- /dev/null +++ b/cl_dll/vgui_paranoiatext.h @@ -0,0 +1,52 @@ +#ifndef _PARANOIATEXT_H +#define _PARANOIATEXT_H +using namespace vgui; + +//#include "..\game_shared\vgui_checkbutton2.h" +#include "..\game_shared\vgui_loadtga.h" +#include "VGUI_ScrollPanel.h" + +class CMyScrollPanel; +class CMainPanel; + +class CRenderable +{ +public: + virtual void Render() = 0; +}; + + +class CParanoiaTextPanel : public Panel, public ActionSignal//, public ICheckButton2Handler +{ +public: + CParanoiaTextPanel(char* filename); + void actionPerformed(Panel* panel); + int KeyInput(int down, int keynum, const char *pszCurrentBinding); + void paint(); + void ResetBackground(); +// void StateChanged(CCheckButton2 *pButton); // áóäåò èñïîëüçîâàíî äëÿ ïåðåêëþ÷åíèÿ ðåæèìà îòðèñîâêè ìèðà + char m_loadedFileName[128]; + + void Render( void ) + { + for (int i=0; i < m_iRenderElms; ++i) + m_pRenderList[i]->Render(); + } + +private: + void BuildErrorPanel(const char* errorString); + void CloseWindow(void); + + void AddToRenderList(CRenderable* pnew); + + CMyScrollPanel* m_pScrollPanel; + CMainPanel* panel; + int m_iMaxScrollValue; + float m_flStartTime; + + CRenderable* m_pRenderList[4]; + int m_iRenderElms; +}; + + +#endif \ No newline at end of file diff --git a/cl_dll/vgui_pickup.h b/cl_dll/vgui_pickup.h new file mode 100644 index 0000000..f9814b1 --- /dev/null +++ b/cl_dll/vgui_pickup.h @@ -0,0 +1,131 @@ +// ====================================== +// written by BUzer for HL: Paranoia modification +// ====================================== + +#ifndef _VGUIPICKMSG_H +#define _VGUIPICKMSG_H +using namespace vgui; + +#include "vgui_shadowtext.h" +#include "VGUI_TextImage.h" + +Font* FontFromMessage(const char* &ptext); +void CheckPanel(); + + +#define RIGHTSPACE (XRES(10)) +#define MOVEOFFSET (XRES(20)) + +extern int g_ammoAdded; // hack - store amount of added ammo last time in global variable + + + +class CPickupMessage : public ShadowTextPanel +{ +public: + CPickupMessage(int height) : ShadowTextPanel("", 0, 0, ScreenWidth, ScreenHeight) + { + setVisible(false); + setPaintBackgroundEnabled(false); + m_iHeight = height; + } + + void Initialize() + { + setVisible(false); + } + + void SetMessage( client_textmessage_t *msg ) + { + setSize(ScreenWidth, 16); + const char *text = msg->pMessage; + Font *pFont = FontFromMessage(text); + char buf[1024]; + sprintf(buf, text, g_ammoAdded); // text message should contain %d substring + setFont(pFont); + setText(buf); + + int tw, th; + getTextImage()->getTextSizeWrapped( tw, th ); + setSize( tw, th ); + + m_starttime = gEngfuncs.GetClientTime(); + m_hold = msg->holdtime; + m_fadein = msg->fadein; + m_fadeout = msg->fadeout; + r1 = msg->r1; g1 = msg->g1; b1 = msg->b1; a1 = 0; + r2 = msg->r2; g2 = msg->g2; b2 = msg->b2; a2 = 64; + + int xpos = ScreenWidth - RIGHTSPACE - tw - MOVEOFFSET; + setPos(xpos, m_iHeight); + + setVisible(true); + } + +protected: + virtual void paint() + { + float curtime = gEngfuncs.GetClientTime() - m_starttime; + if (curtime < 0) + return; + + if(curtime > (m_hold + m_fadein + m_fadeout)) + { + setVisible(false); + CheckPanel(); + return; + } + + if (curtime < m_fadein) + { + // fadein stage + // interpolate color1->color2 + float fraction = curtime / m_fadein; + int r3, g3, b3, a3; + r3 = r1 + (byte)((float)(r2 - r1) * fraction); + g3 = g1 + (byte)((float)(g2 - g1) * fraction); + b3 = b1 + (byte)((float)(b2 - b1) * fraction); + a3 = a1 + (byte)((float)(a2 - a1) * fraction); + setFgColor(r3, g3, b3, a3); + + int wide, tall; + fraction = 1 - fraction; + fraction *= fraction; + getSize(wide, tall); + int xpos = ScreenWidth - RIGHTSPACE - wide - (int)((float)MOVEOFFSET * fraction); + setPos(xpos, m_iHeight); + } + else if (curtime > (m_fadein + m_hold)) + { + // fadeout stage + float fraction = (curtime - m_fadein - m_hold) / m_fadeout; + int a = a2 + (byte)((float)(255 - a2) * fraction); + setFgColor(r2, g2, b2, a); + + int wide, tall; + getSize(wide, tall); + setPos(ScreenWidth - RIGHTSPACE - wide, m_iHeight); + } + else + { + setFgColor(r2, g2, b2, a2); + + int wide, tall; + getSize(wide, tall); + setPos(ScreenWidth - RIGHTSPACE - wide, m_iHeight); + } + + ShadowTextPanel::paint(); + } + + float m_starttime; + float m_hold; + float m_fadein; + float m_fadeout; + int m_iHeight; + + byte r1, g1, b1, a1; // start (fadein) color + byte r2, g2, b2, a2; // end (hold) color +}; + +#endif // _VGUIPICKMSG_H \ No newline at end of file diff --git a/cl_dll/vgui_radio.cpp b/cl_dll/vgui_radio.cpp new file mode 100644 index 0000000..3df77cc --- /dev/null +++ b/cl_dll/vgui_radio.cpp @@ -0,0 +1,197 @@ +// ==================================== +// Paranoia radio icon +// written by BUzer, based on valve's code +// ==================================== + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" +#include "const.h" +#include "entity_types.h" +#include "cdll_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_radio.h" +#include "..\game_shared\vgui_loadtga.h" +#include "getfont.h" + +#define RADIO_FADE_TIME 0.25 + + +int MsgShowRadioIcon(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + char *title = READ_STRING(); + float time = READ_COORD(); + int r = READ_BYTE(); + int g = READ_BYTE(); + int b = READ_BYTE(); + int a = READ_BYTE(); + + if (gViewPort && gViewPort->m_pRadio) + gViewPort->m_pRadio->Show(title, time, r, g, b, a); + + return 1; +} + +void RadioIconInit() +{ + gEngfuncs.pfnHookUserMsg("RadioIcon", MsgShowRadioIcon); +} + +void CRadioIcon::Initialize() +{ + setVisible(false); + m_fShowTime = 0; + m_fHideTime = 0; + next_a = 0; +// gEngfuncs.Con_Printf("--- initialize called!\n"); +} + +CRadioIcon::CRadioIcon() : Panel(0, 0, 10, 10) +{ + if( m_pSpeakerBitmap = vgui_LoadTGANoInvertAlpha("gfx/vgui/speaker4.tga" ) ) + m_pSpeakerBitmap->setColor( Color(255,255,255,1) ); + else + gEngfuncs.Con_Printf("Cannot load gfx/vgui/speaker4.tga!\n"); + +// CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); +// SchemeHandle_t hTextScheme = pSchemes->getSchemeHandle( "Default Text" ); +// Font *pFont = pSchemes->getFont( hTextScheme ); + + m_pIcon = new ImagePanel(m_pSpeakerBitmap); + m_pIcon->setParent(this); + m_pLabel = new Label(""); + m_pLabel->setParent(this); +// m_pLabel->setFont(pFont); + m_pLabel->setPaintBackgroundEnabled(false); +// m_pIcon->setPaintBackgroundEnabled(false); + Initialize(); +} + + +CRadioIcon::~CRadioIcon() +{ + delete m_pSpeakerBitmap; +} + + +void CRadioIcon::Reposition() +{ + int y = ScreenHeight / 2; + + int iconWide = 8, iconTall = 8; + if( m_pSpeakerBitmap ) + { + m_pSpeakerBitmap->getSize( iconWide, iconTall ); + // gEngfuncs.Con_Printf("speaker sizes %d, %d\n", iconWide, iconTall); + } + + int textWide, textTall; + m_pLabel->getContentSize( textWide, textTall ); + + // Don't let it stretch too far across their screen. + if( textWide > (ScreenWidth*2)/3 ) + textWide = (ScreenWidth*2)/3; + + // Setup the background label to fit everything in. + int border = 2; + int bgWide = textWide + iconWide + border*3; + int bgTall = max( textTall, iconTall ) + border*2; + setBounds( ScreenWidth - bgWide - 8, y, bgWide, bgTall ); + + // Put the text at the left. + m_pLabel->setBounds( border, (bgTall - textTall) / 2, textWide, textTall ); + + // Put the icon at the right. + int iconLeft = border + textWide + border; + int iconTop = (bgTall - iconTall) / 2; + m_pIcon->setBounds( iconLeft, iconTop, iconWide, iconTall ); +} + + +void CRadioIcon::Show(const char *title, float time, int _r, int _g, int _b, int _a) +{ + float curtime = gEngfuncs.GetClientTime(); + + if (curtime > m_fHideTime) + { + setVisible(true); + m_fShowTime = curtime; + m_fHideTime = curtime + time; + char *pText = CHudTextMessage::BufferedLocaliseTextString(title); + Font *pFont = FontFromMessage(pText); + m_pLabel->setFont(pFont); + m_pLabel->setText(pText); + r = _r; g = _g; b = _b; a = _a; + next_a = 0; + Reposition(); + } + else if (curtime > (m_fHideTime - RADIO_FADE_TIME)) + { + // current title is about to go away, just queue new + strcpy(nextTitle, title); + nextTime = time; + next_r = _r; next_g = _g; next_b = _b; next_a = _a; + } + else + { + // hide current and queue new + m_fHideTime = curtime + RADIO_FADE_TIME; + strcpy(nextTitle, title); + nextTime = time; + next_r = _r; next_g = _g; next_b = _b; next_a = _a; + } +} + + +void CRadioIcon::paintBackground() +{ + float curtime = gEngfuncs.GetClientTime(); + + if (curtime > m_fHideTime) + { + if (next_a) + { + // next title queued + m_fShowTime = curtime; + m_fHideTime = curtime + nextTime; + char *pText = CHudTextMessage::BufferedLocaliseTextString(nextTitle); + Font *pFont = FontFromMessage(pText); + m_pLabel->setFont(pFont); + m_pLabel->setText(pText); + r = next_r; g = next_g; b = next_b; a = next_a; + next_a = 0; + Reposition(); + } + else + { + // put away from screen + setVisible(false); + return; + } + } + + if (curtime < m_fShowTime + RADIO_FADE_TIME) + { + float alpha = (curtime - m_fShowTime) / RADIO_FADE_TIME; + setBgColor( r, g, b, 255 - ((float)a * alpha) ); + m_pLabel->setFgColor( 255, 255, 255, (1 - alpha) * 255 ); + m_pSpeakerBitmap->setColor( Color(255, 255, 255, (1 - alpha) * 255) ); + } + else if (curtime > m_fHideTime - RADIO_FADE_TIME) + { + float alpha = (m_fHideTime - curtime) / RADIO_FADE_TIME; + setBgColor( r, g, b, 255 - ((float)a * alpha) ); + m_pLabel->setFgColor( 255, 255, 255, (1 - alpha) * 255 ); + m_pSpeakerBitmap->setColor( Color(255, 255, 255, (1 - alpha) * 255) ); + } + else + { + setBgColor( r, g, b, 255 - a ); + m_pLabel->setFgColor( 255, 255, 255, 0 ); + m_pSpeakerBitmap->setColor( Color(255, 255, 255, 0) ); + } + + Panel::paintBackground(); +} \ No newline at end of file diff --git a/cl_dll/vgui_radio.h b/cl_dll/vgui_radio.h new file mode 100644 index 0000000..57bb64f --- /dev/null +++ b/cl_dll/vgui_radio.h @@ -0,0 +1,38 @@ +// ====================================== +// Paranoia radio icon header file +// written by BUzer, based on valve's code +// ====================================== + +#ifndef _RADIO_H +#define _RADIO_H +using namespace vgui; + +class CRadioIcon : public Panel +{ +public: + CRadioIcon(); + ~CRadioIcon(); + void Show(const char *title, float time, int r, int g, int b, int a); + void Initialize(); + +protected: + virtual void paintBackground(); + void Reposition(); + +protected: + Image *m_pSpeakerBitmap; + Label *m_pLabel; + ImagePanel *m_pIcon; + + float m_fShowTime; + float m_fHideTime; + int r, g, b, a; + + char nextTitle[256]; + float nextTime; + int next_r, next_g, next_b, next_a; // next_a shows - is next message present +}; + +void RadioIconInit(); + +#endif // _RADIO_H \ No newline at end of file diff --git a/cl_dll/vgui_screenmsg.h b/cl_dll/vgui_screenmsg.h new file mode 100644 index 0000000..e3d8594 --- /dev/null +++ b/cl_dll/vgui_screenmsg.h @@ -0,0 +1,173 @@ +// ====================================== +// written by BUzer for HL: Paranoia modification +// ====================================== + +#ifndef _VGUIMSG_H +#define _VGUIMSG_H +using namespace vgui; + +#include "vgui_shadowtext.h" + +Font* FontFromMessage(const char* &ptext); + + +class CScreenMessage : public ShadowTextPanel +{ +public: + CScreenMessage() : ShadowTextPanel("", 0, 0, ScreenWidth, ScreenHeight) + { + setVisible(false); + setPaintBackgroundEnabled(false); + } + + void Initialize() + { + setVisible(false); + } + + void SetMessage( client_textmessage_t *msg ) + { + const char *text = msg->pMessage; + Font *pFont = FontFromMessage(text); + setFont(pFont); + setText(text); + setFgColor(msg->r1, msg->g1, msg->b1, msg->a1); + + m_starttime = gEngfuncs.GetClientTime(); + m_hold = msg->holdtime; + m_fadein = msg->fadein; + m_fadeout = msg->fadeout; + + // Wargon: Åñëè êîîðäèíàòû çàäàíû íåïðàâèëüíî, òî òåêñò ïðîñòî öåíòðèðóåòñÿ. + if (msg->x < 0 || msg->x > 1 || msg->y < 0 || msg->y > 1) + { + // gEngfuncs.Con_Printf("Error: invalid message coordinates!\n"); + // return; + int tw, th; + getTextImage()->getTextSizeWrapped(tw, th); + setSize(tw, th); + setPos((ScreenWidth - tw) * 0.5, (ScreenHeight - th) * 0.5); + } + else + { + int x = msg->x * ScreenWidth; + int y = msg->y * ScreenHeight; + setPos(x, y); + } + + setVisible(true); + } + +protected: + virtual void paint() + { + int mr, mg, mb, ma; + getFgColor(mr, mg, mb, ma); + + float curtime = gEngfuncs.GetClientTime() - m_starttime; + if (curtime < 0) + return; + + if (curtime > (m_hold + m_fadein + m_fadeout)) + { + setVisible(false); + return; + } + + if (curtime < m_fadein) + { + float alpha = curtime / m_fadein; + setFgColor( mr, mg, mb, (1 - alpha) * 255 ); + } + else if (curtime > (m_fadein + m_hold)) + { + float alpha = (curtime - m_fadein - m_hold) / m_fadeout; + setFgColor( mr, mg, mb, (alpha) * 255 ); + } + else + { + setFgColor( mr, mg, mb, 0 ); + } + + ShadowTextPanel::paint(); + } + + float m_starttime; + float m_hold; + float m_fadein; + float m_fadeout; +}; + +// Wargon: Êîä ñêðîëëÿùåãîñÿ ñíèçó ââåðõ òåêñòà. Èñïîëüçîâàí $effect 6. +class CScrollingMessage : public ShadowTextPanel +{ +public: + CScrollingMessage() : ShadowTextPanel("", 0, 0, ScreenWidth, ScreenHeight) + { + setVisible(false); + setPaintBackgroundEnabled(false); + } + + void Initialize() + { + setVisible(false); + } + + void SetMessage( client_textmessage_t *msg ) + { + int w, h; + const char *text = msg->pMessage; + Font *pFont = FontFromMessage(text); + setSize(ScreenWidth, ScreenHeight); + setFont(pFont); + setText(text); + setFgColor(msg->r1, msg->g1, msg->b1, msg->a1); + getTextImage()->getTextSizeWrapped(w, h); + setSize(w, h); + m_starttime = gEngfuncs.GetClientTime(); + m_hold = msg->holdtime; + m_speed = ((ScreenHeight + h) / m_hold) * 0.02; + m_delay = 0; + if (msg->x < 0 || msg->x > 1) + { + setPos((ScreenWidth - w) * 0.5, ScreenHeight - 1); + } + else + { + setPos(msg->x * ScreenWidth, ScreenHeight - 1); + } + setVisible(true); + } + +protected: + virtual void paint() + { + float curtime = gEngfuncs.GetClientTime() - m_starttime; + if (curtime < 0) + return; + + m_delay += m_speed; + + if (curtime > m_hold) + { + setVisible(false); + return; + } + else if (m_delay >= 1) + { + int x, y; + getPos(x, y); + setPos(x, y - 1); + m_delay = 0; + } + + ShadowTextPanel::paint(); + } + + float m_starttime; + float m_hold; + float m_speed; + float m_delay; +}; + +#endif // _VGUIMSG_H \ No newline at end of file diff --git a/cl_dll/vgui_shadowtext.h b/cl_dll/vgui_shadowtext.h new file mode 100644 index 0000000..eb9e9e0 --- /dev/null +++ b/cl_dll/vgui_shadowtext.h @@ -0,0 +1,29 @@ + +#ifndef _SHADOWTEXT_H +#define _SHADOWTEXT_H + +#include "VGUI_TextImage.h" + +class ShadowTextPanel : public TextPanel +{ +public: + ShadowTextPanel(const char* text,int x,int y,int wide,int tall) : TextPanel(text, x, y, wide, tall) + { + } + + virtual void paint() + { + int mr, mg, mb, ma; + int ix, iy; + getFgColor(mr, mg, mb, ma); + getTextImage()->getPos(ix, iy); + getTextImage()->setPos(ix+1, iy+1); + getTextImage()->setColor( Color(0, 0, 0, ma) ); + getTextImage()->doPaint(this); + getTextImage()->setPos(ix, iy); + getTextImage()->setColor( Color(mr, mg, mb, ma) ); + getTextImage()->doPaint(this); + } +}; + +#endif \ No newline at end of file diff --git a/cl_dll/vgui_subtitles.cpp b/cl_dll/vgui_subtitles.cpp new file mode 100644 index 0000000..4c14851 --- /dev/null +++ b/cl_dll/vgui_subtitles.cpp @@ -0,0 +1,323 @@ +// ==================================== +// Paranoia subtitle system interface +// written by BUzer +// ==================================== + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_types.h" +#include "cdll_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_subtitles.h" +#include "VGUI_TextImage.h" + + +cvar_t *time_min; +cvar_t *time_max; +cvar_t *subtitles_enabled; +cvar_t *scroll_speed; +cvar_t *fade_speed; + + +Font* FontFromMessage(const char* &ptext) +{ + char fontname[64] = "Default Text"; + if (ptext != NULL && ptext[0] != 0) + { + if (ptext[0] == '@') + { + // get font name + ptext++; + ptext = gEngfuncs.COM_ParseFile((char*)ptext, fontname); + ptext+=2; + } + } + + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + SchemeHandle_t hTextScheme = pSchemes->getSchemeHandle( fontname ); + return pSchemes->getFont( hTextScheme ); +} + +void SubtitleMessageAdd( client_textmessage_t *tempMessage ) +{ + if (!subtitles_enabled->value) + return; + + if (gViewPort && gViewPort->m_pSubtitle) + gViewPort->m_pSubtitle->AddMessage( tempMessage ); + else + gEngfuncs.Con_Printf("Subtitle error: CSubtitle or ViewPort is not constructed!\n"); +} + +void SubtitleInit() +{ + time_min = gEngfuncs.pfnRegisterVariable( "subt_mintime", "0", 0 ); + time_max = gEngfuncs.pfnRegisterVariable( "subt_maxtime", "0", 0 ); + subtitles_enabled = gEngfuncs.pfnRegisterVariable( "subtitles", "0", FCVAR_ARCHIVE ); + scroll_speed = gEngfuncs.pfnRegisterVariable( "subt_scrollspd", "250", 0 ); + fade_speed = gEngfuncs.pfnRegisterVariable( "subt_fadespd", "0.2", 0 ); +} + + +void CSubtitleTextPanel::paintBackground() +{ + float fade = fade_speed->value; + float time = gEngfuncs.GetClientTime(); + + int r, g, b, a; + getFgColor(r, g, b ,a); + + if (m_fBirthTime && (m_fBirthTime + fade > time)) + { + float alpha = (time - m_fBirthTime) / fade; + setFgColor( r, g, b, 255 - (alpha * 255)); + /* if (bkalpha) + { + // draw background + getBgColor(r, g, b ,a); + drawSetColor(r, g, b, 255 - (alpha * bkalpha)); + drawFilledRect(0, 0, getWide(), getTall()); + }*/ + return; + } + + if (gViewPort && gViewPort->m_pSubtitle) // hm.. + { + if (gViewPort->m_pSubtitle->m_pCur == this) + { + float dietime = gViewPort->m_pSubtitle->m_fCurStartTime + m_fHoldTime; + if (dietime - fade < time) + { + float alpha = (dietime - time) / fade; + setFgColor( r, g, b, 255 - (alpha * 255)); + /* if (bkalpha) + { + getBgColor(r, g, b ,a); + setBgColor(r, g, b, 255 - (alpha * bkalpha)); + TextPanel::paintBackground(); + }*/ + return; + } + } + } + + setFgColor( r, g, b, 0 ); +} + +void CSubtitleTextPanel::paint() +{ + int mr, mg, mb, ma; + int ix, iy; + getFgColor(mr, mg, mb, ma); + getTextImage()->getPos(ix, iy); + getTextImage()->setPos(ix+1, iy+1); + getTextImage()->setColor( Color(0, 0, 0, ma) ); + getTextImage()->doPaint(this); + getTextImage()->setPos(ix, iy); + getTextImage()->setColor( Color(mr, mg, mb, ma) ); + getTextImage()->doPaint(this); +} + + +CSubtitle::CSubtitle() : Panel(XRES(10), YRES(10), XRES(330), YRES(240)) +{ + m_pLayer = new Panel; + m_pLayer->setParent(this); + m_pLayer->setPaintBackgroundEnabled(false); + setVisible(false); + m_pCur = NULL; + m_pWait = NULL; + lasttime = 0; + layerpos = 0; +} + +void CSubtitle::Initialize() +{ + m_pLayer->removeAllChildren(); + setVisible(false); + m_pCur = NULL; + m_pWait = NULL; + lasttime = 0; + layerpos = 0; +} + + +void CSubtitle::AddMessage( client_textmessage_t *msg ) +{ + float time = gEngfuncs.GetClientTime(); + float holdtime = msg->holdtime; + if (time_min->value && (holdtime < time_min->value)) holdtime = time_min->value; + if (time_max->value && (holdtime > time_max->value)) holdtime = time_max->value; + +// CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); +// SchemeHandle_t hTextScheme = pSchemes->getSchemeHandle( "Default Text" ); +// Font *pFont = pSchemes->getFont( hTextScheme ); + + const char *pText = msg->pMessage; + client_textmessage_t *postMsg = NULL; + if (pText[0] == '$') + { + // get postMsg + char postMsgName[64]; + pText = gEngfuncs.COM_ParseFile((char*)pText, postMsgName); + postMsg = TextMessageGet( &postMsgName[1] ); + pText+=2; + if (!postMsg) + gEngfuncs.Con_Printf("WARNING: post-message %s not found in titles.txt!\n", postMsgName); + } + + Font *pFont = FontFromMessage(pText); + + int tw, th; + CSubtitleTextPanel *text = new CSubtitleTextPanel(pText, 0, 0, getWide(), 64 ); + text->setParent( m_pLayer ); + text->setFont(pFont); + text->msgAfterDeath = postMsg; +// text->setPaintBackgroundEnabled(false); +// text->setBgColor(msg->r2, msg->g2, msg->b2, 255); +// text->bkalpha = msg->fadeout; +// gEngfuncs.Con_Printf("added bkalpha: %d\n", text->bkalpha); + text->setFgColor(msg->r1, msg->g1, msg->b1, 0); + text->getTextImage()->getTextSizeWrapped( tw, th ); + text->getTextImage()->setPos(0, 5); + th += 10; + text->setSize( getWide(), th ); + text->m_fHoldTime = holdtime; + + if (!isVisible()) + { + layerpos = getTall()-th; + m_pLayer->setBounds(0, layerpos, getWide(), th); + text->m_fBirthTime = time; + m_fCurStartTime = time; + m_pCur = text; + m_pWait = NULL; + text->setVisible(true); + setVisible(true); + } + else + { + text->setVisible(false); + text->m_fBirthTime = 0; + + if (!m_pWait) + { + int lt = m_pLayer->getTall(); + m_pLayer->setSize(getWide(), lt+th); + text->setPos(0, lt); + m_pWait = text; + } + } +} + +void CSubtitle::paintBackground() +{ + float curtime = gEngfuncs.GetClientTime(); + if (lasttime == 0) lasttime = curtime; + float deltatime = curtime - lasttime; + + if (!subtitles_enabled->value) + { + Initialize(); + return; + } + + if (m_pCur) + { + if (m_fCurStartTime + m_pCur->m_fHoldTime <= curtime) + { + m_fCurStartTime = curtime; + client_textmessage_t *newmsg = m_pCur->msgAfterDeath; + m_pLayer->removeChild(m_pCur); + m_pCur = NULL; + + if (newmsg) + AddMessage(newmsg); + + if (m_pLayer->getChildCount() == 0) + { + m_pWait = NULL; + setVisible(false); + return; + } + + // find oldest child to start fading int + float mintime = 99999; + for (int i = 0; i < m_pLayer->getChildCount(); i++) + { + CSubtitleTextPanel *chld = (CSubtitleTextPanel*)m_pLayer->getChild(i); + if (chld->isVisible() && chld->m_fBirthTime < mintime) + { + m_pCur = chld; + mintime = chld->m_fBirthTime; + } + } + + if (!m_pCur) + { + // get cur from waiting queue + m_pCur = m_pWait; + if (m_pCur) + { + layerpos = getTall() - m_pCur->getTall(); + m_pLayer->setBounds(0, layerpos, getWide(), m_pCur->getTall()); + m_pCur->setPos(0,0); + m_pCur->m_fBirthTime = curtime; + m_pCur->setVisible(true); + + m_pWait = NULL; + for (i = 0; i < m_pLayer->getChildCount(); i++) + { + if (!m_pLayer->getChild(i)->isVisible()) + { + m_pWait = (CSubtitleTextPanel*)m_pLayer->getChild(i); + int lt = m_pLayer->getTall(); + m_pLayer->setSize(getWide(), lt + m_pWait->getTall()); + m_pWait->setPos(0, lt); + break; + } + } + } + else + { + gEngfuncs.Con_Printf("ERROR: subtitles - has childs but no lists!\n"); + setVisible(false); + return; + } + } + } + } + + if (m_pWait) + { + float rest = layerpos + m_pLayer->getTall() - getTall(); + float spd = rest > 40 ? 0.5 : (rest / 40 * 0.5); spd += 0.5; + layerpos -= (deltatime * scroll_speed->value * spd); + // gEngfuncs.Con_Printf("layerpos: %f, spd: %f, deltatime: %f, cvarspeed: %f\n", layerpos, spd, deltatime, scroll_speed->value); + if (layerpos + m_pLayer->getTall() <= getTall()) + { + layerpos = getTall() - m_pLayer->getTall(); + m_pWait->setVisible(true); + m_pWait->m_fBirthTime = curtime; + + // find next waiting message + m_pWait = NULL; + for (int i = 0; i < m_pLayer->getChildCount(); i++) + { + if (!m_pLayer->getChild(i)->isVisible()) + { + m_pWait = (CSubtitleTextPanel*)m_pLayer->getChild(i); + int lt = m_pLayer->getTall(); + m_pLayer->setSize(getWide(), lt + m_pWait->getTall()); + m_pWait->setPos(0, lt); + break; + } + } + } + m_pLayer->setPos(0, layerpos); + } + + lasttime = curtime; +// Panel::paintBackground(); +} \ No newline at end of file diff --git a/cl_dll/vgui_subtitles.h b/cl_dll/vgui_subtitles.h new file mode 100644 index 0000000..0e3aa37 --- /dev/null +++ b/cl_dll/vgui_subtitles.h @@ -0,0 +1,55 @@ +// ====================================== +// Paranoia subtitle system header file +// written by BUzer +// ====================================== + +#ifndef _SUBTITLES_H +#define _SUBTITLES_H +using namespace vgui; + +class CSubtitleTextPanel : public TextPanel +{ +public: + CSubtitleTextPanel(const char* text,int x,int y,int wide,int tall) : TextPanel(text, x, y, wide, tall) + { + m_fBirthTime = 0; + m_fHoldTime = 0; + msgAfterDeath = NULL; +// bkalpha = 0; + } + + virtual void paintBackground(); + virtual void paint(); + + float m_fBirthTime; + float m_fHoldTime; +// int bkalpha; + client_textmessage_t *msgAfterDeath; +}; + + +class CSubtitle : public Panel +{ +public: + CSubtitle(); + void AddMessage( client_textmessage_t *tempMessage ); + void Initialize(); + +protected: + virtual void paintBackground(); + +//protected: +public: // hacks.. + Panel *m_pLayer; + CSubtitleTextPanel *m_pCur; + CSubtitleTextPanel *m_pWait; + + float layerpos; // float version of layer's y-coordinate + float lasttime; + float m_fCurStartTime; +}; + +void SubtitleMessageAdd( client_textmessage_t *tempMessage ); +void SubtitleInit(); + +#endif // _SUBTITLES_H \ No newline at end of file diff --git a/cl_dll/vgui_tabpanel.cpp b/cl_dll/vgui_tabpanel.cpp new file mode 100644 index 0000000..0cb8c42 --- /dev/null +++ b/cl_dll/vgui_tabpanel.cpp @@ -0,0 +1,264 @@ +// ==================================== +// Paranoia goal panel +// written by BUzer. +// ==================================== + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" +#include "const.h" +#include "entity_types.h" +#include "cdll_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_tabpanel.h" +#include "..\game_shared\vgui_loadtga.h" +#include +#include +#include "getfont.h" + +#define TP_FL_ENABLE 1 +#define TP_FL_TITLE 2 +#define TP_FL_IMAGE 4 +#define TP_FL_POPUP 8 + +#define XSTEP XRES(5) +#define YSTEP YRES(5) + +cvar_t *hidetime; + +int MsgShowTabPanel(const char *pszName, int iSize, void *pbuf) +{ + if (gViewPort && gViewPort->m_pTabPanel) + gViewPort->m_pTabPanel->LoadMessage(pszName, iSize, pbuf); + + return 1; +} + +void TabPanelInit() +{ + gEngfuncs.pfnHookUserMsg("TabPanel", MsgShowTabPanel); + hidetime = gEngfuncs.pfnRegisterVariable( "gpanel_hidetime", "20", 0 ); +} + + +void CTabPanel::LoadMessage(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + int flags = READ_BYTE(); + if (!flags) + { + Initialize(); + return; + } + + m_pTitle->setVisible(false); + m_pImage->setVisible(false); + m_pImage->setImage(NULL); + if (m_pBitmap) delete m_pBitmap; + m_pBitmap = NULL; + + int popup = flags & TP_FL_POPUP; + static char dst_buffer[4096]; + CHudTextMessage::LocaliseTextString( READ_STRING(), dst_buffer, 4096 ); + char *pText = dst_buffer; + Font *pFont = FontFromMessage(pText); + m_pTextPanel->setText(pText); + m_pTextPanel->setFont( pFont ); + + int ypos = YSTEP; + if (flags & TP_FL_TITLE) + { + char *pt = READ_STRING(); + // if (*pt == '#') pt++; + // client_textmessage_t *clmsg = TextMessageGet( pt ); + CHudTextMessage::LocaliseTextString(pt, dst_buffer, 4096 ); + pText = dst_buffer; + // pText = clmsg->pMessage; + int ln = strlen(pText); + if (pText[ln-1] == 13) pText[ln-1] = 0; + /* gEngfuncs.Con_Printf("SET TEXT ---[%s]--- %d\n", pText, ln); + for (int i = 0; i < ln; i++) + gEngfuncs.Con_Printf("%d, ",pText[i]); + gEngfuncs.Con_Printf("\n");*/ + + pFont = FontFromMessage(pText); + m_pTitle->setFont( pFont ); + m_pTitle->setText(pText); + m_pTitle->setVisible(true); + // m_pTitle->setContentAlignment( Label::a_west ); + int textWide, textTall; + m_pTitle->getContentSize( textWide, textTall ); + m_pTitle->setSize(textWide, textTall); + // gEngfuncs.Con_Printf("%d, %d\n", textWide, textTall); + ypos = ypos + textTall + YSTEP; + } + m_pBkPanel->setBounds(XSTEP, ypos, getWide() - (XSTEP*2), 16); + + if (flags & TP_FL_IMAGE) + { + static int resArray[] = + { + 320, 400, 512, 640, 800, + 1024, 1152, 1280, 1600 + }; + + // try to load image directly + const char* imgname = READ_STRING(); + m_pBitmap = vgui_LoadTGA(imgname); + if (!m_pBitmap) + { + //resolution based image. Should contain %d substring + int resArrayIndex = 0; + int i = 0; + while ((resArray[i] <= ScreenWidth) && (i < 9)) + { + resArrayIndex = i; + i++; + } + + while(m_pBitmap == NULL && resArrayIndex >= 0) + { + char imgName[64]; + sprintf(imgName, imgname, resArray[resArrayIndex]); + m_pBitmap = vgui_LoadTGA(imgName); + resArrayIndex--; + } + } + + if (!m_pBitmap) + { + // still no image + gEngfuncs.Con_Printf("Could not load image [%s]\n", imgname); + } + } + + if (m_pBitmap) + { + m_pBitmap->setColor(Color(255, 255, 255, 0)); + m_pImage->setImage(m_pBitmap); + m_pImage->setFgColor(255, 255, 255, 0); + m_pImage->setBgColor(255, 255, 255, 0); + m_pImage->setVisible(true); + + int imgx, imgy; + m_pBitmap->getSize(imgx, imgy); + m_pTextPanel->setBounds((XSTEP*2)+imgx, YSTEP, m_pBkPanel->getWide()-((XSTEP*3)+imgx), 16 ); + m_pImage->setSize(imgx, imgy); + + int tw, th; + m_pTextPanel->getTextImage()->getTextSizeWrapped( tw, th ); + m_pTextPanel->setSize( tw, th ); + + int ymax = (imgy > th) ? imgy : th; + m_pBkPanel->setSize( m_pBkPanel->getWide(), ymax+YSTEP*2); + } + else + { + m_pTextPanel->setBounds(XSTEP, YSTEP, m_pBkPanel->getWide()-(XSTEP*2), 16 ); + + int tw, th; + m_pTextPanel->getTextImage()->getTextSizeWrapped( tw, th ); + m_pTextPanel->setSize( tw, th ); + m_pBkPanel->setSize( m_pBkPanel->getWide(), th+YSTEP*2); + } + + setSize( getWide(), ypos + m_pBkPanel->getTall() + YSTEP ); + + m_iTextLoaded = 1; + if (hidetime->value) + m_fHideTime = gEngfuncs.GetClientTime() + hidetime->value; + else + m_fHideTime = 0; + + if (popup) + { + setVisible(true); + PlaySound("common/gpanel_popup.wav", 1); + } + // non-popup messages are sent after initialization +} + +int CTabPanel::Toggle() +{ + if (m_iTextLoaded) + { + m_fHideTime = 0; + if (isVisible()) + { + PlaySound("common/gpanel_close.wav", 1); + setVisible(false); + } + else + { + PlaySound("common/gpanel_open.wav", 1); + setVisible(true); + } + } + return 0; +} + +void CTabPanel::Initialize() +{ + setVisible(false); + m_iTextLoaded = 0; + m_fHideTime = 0; + m_pTitle->setVisible(false); + m_pImage->setVisible(false); + m_pImage->setImage(NULL); + if (m_pBitmap) + delete m_pBitmap; + + m_pBitmap = NULL; +} + +CTabPanel::CTabPanel() : Panel(XRES(160), YRES(10), XRES(320), 16) +{ +// CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); +// SchemeHandle_t hTextScheme = pSchemes->getSchemeHandle( "Default Text" ); +// Font *pFont = pSchemes->getFont( hTextScheme ); + + setBgColor(0, 0, 0, 100); +// setBorder(new LineBorder(1, Color(176, 110, 40, 0))); + + m_pTitle = new Label("", XSTEP, YSTEP); + m_pTitle->setParent(this); + m_pBkPanel = new Panel; + m_pBkPanel->setParent(this); + m_pTextPanel = new TextPanel("", 10, 10, 10, 10); + m_pTextPanel->setParent(m_pBkPanel); + m_pImage = new ImagePanel(NULL); + m_pImage->setParent(m_pBkPanel); + m_pImage->setPos(XSTEP, YSTEP); + m_pBitmap = NULL; + + m_pTitle->setPaintBackgroundEnabled(false); + m_pTextPanel->setPaintBackgroundEnabled(false); +// m_pImage->setPaintBackgroundEnabled(false); + + m_pTitle->setFgColor(255, 255, 255, 0); + m_pBkPanel->setBgColor(50, 80, 90, 100); + m_pTextPanel->setFgColor(255, 255, 255, 0); + + Initialize(); +} + +CTabPanel::~CTabPanel() +{ + if (m_pBitmap) + delete m_pBitmap; + + m_pBitmap = NULL; +} + +void CTabPanel::paintBackground() +{ + float curtime = gEngfuncs.GetClientTime(); + if (m_fHideTime && curtime > m_fHideTime) + { + m_fHideTime = 0; + setVisible(false); + } + + Panel::paintBackground(); +} \ No newline at end of file diff --git a/cl_dll/vgui_tabpanel.h b/cl_dll/vgui_tabpanel.h new file mode 100644 index 0000000..3daf164 --- /dev/null +++ b/cl_dll/vgui_tabpanel.h @@ -0,0 +1,35 @@ +// ====================================== +// Paranoia goal panel header file +// written by BUzer. +// ====================================== + +#ifndef _TABPANEL_H +#define _TABPANEL_H +using namespace vgui; + +class CTabPanel : public Panel +{ +public: + CTabPanel(); + ~CTabPanel(); + void Initialize(); + + void LoadMessage(const char *pszName, int iSize, void *pbuf); + int Toggle(); + +protected: + virtual void paintBackground(); // per-frame calculations and checks + +protected: + Panel *m_pBkPanel; + TextPanel *m_pTextPanel; + Label *m_pTitle; + ImagePanel *m_pImage; + BitmapTGA *m_pBitmap; + int m_iTextLoaded; + float m_fHideTime; +}; + +void TabPanelInit(); + +#endif // _TABPANEL_H \ No newline at end of file diff --git a/cl_dll/vgui_teammenu.cpp b/cl_dll/vgui_teammenu.cpp new file mode 100644 index 0000000..a137079 --- /dev/null +++ b/cl_dll/vgui_teammenu.cpp @@ -0,0 +1,393 @@ +//=========== (C) Copyright 1996-2002 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: TFC Team Menu +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "vgui_int.h" +#include "VGUI_Font.h" +#include "VGUI_ScrollPanel.h" +#include "VGUI_TextImage.h" + +#include "hud.h" +#include "cl_util.h" +#include "vgui_TeamFortressViewport.h" + +// Team Menu Dimensions +#define TEAMMENU_TITLE_X XRES(40) +#define TEAMMENU_TITLE_Y YRES(32) +#define TEAMMENU_TOPLEFT_BUTTON_X XRES(40) +#define TEAMMENU_TOPLEFT_BUTTON_Y YRES(80) +#define TEAMMENU_BUTTON_SIZE_X XRES(124) +#define TEAMMENU_BUTTON_SIZE_Y YRES(24) +#define TEAMMENU_BUTTON_SPACER_Y YRES(8) +#define TEAMMENU_WINDOW_X XRES(176) +#define TEAMMENU_WINDOW_Y YRES(80) +#define TEAMMENU_WINDOW_SIZE_X XRES(424) +#define TEAMMENU_WINDOW_SIZE_Y YRES(312) +#define TEAMMENU_WINDOW_TITLE_X XRES(16) +#define TEAMMENU_WINDOW_TITLE_Y YRES(16) +#define TEAMMENU_WINDOW_TEXT_X XRES(16) +#define TEAMMENU_WINDOW_TEXT_Y YRES(48) +#define TEAMMENU_WINDOW_TEXT_SIZE_Y YRES(178) +#define TEAMMENU_WINDOW_INFO_X XRES(16) +#define TEAMMENU_WINDOW_INFO_Y YRES(234) + +// Creation +CTeamMenuPanel::CTeamMenuPanel(int iTrans, int iRemoveMe, int x,int y,int wide,int tall) : CMenuPanel(iTrans, iRemoveMe, x,y,wide,tall) +{ + // Get the scheme used for the Titles + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + + // schemes + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle( "Title Font" ); + SchemeHandle_t hTeamWindowText = pSchemes->getSchemeHandle( "Briefing Text" ); + SchemeHandle_t hTeamInfoText = pSchemes->getSchemeHandle( "Team Info Text" ); + + // get the Font used for the Titles + Font *pTitleFont = pSchemes->getFont( hTitleScheme ); + int r, g, b, a; + + // Create the title + Label *pLabel = new Label( "", TEAMMENU_TITLE_X, TEAMMENU_TITLE_Y ); + pLabel->setParent( this ); + pLabel->setFont( pTitleFont ); + pSchemes->getFgColor( hTitleScheme, r, g, b, a ); + pLabel->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hTitleScheme, r, g, b, a ); + pLabel->setBgColor( r, g, b, a ); + pLabel->setContentAlignment( vgui::Label::a_west ); + pLabel->setText(gHUD.m_TextMessage.BufferedLocaliseTextString("#Title_SelectYourTeam")); + + // Create the Info Window + m_pTeamWindow = new CTransparentPanel( 255, TEAMMENU_WINDOW_X, TEAMMENU_WINDOW_Y, TEAMMENU_WINDOW_SIZE_X, TEAMMENU_WINDOW_SIZE_Y ); + m_pTeamWindow->setParent( this ); + m_pTeamWindow->setBorder( new LineBorder( Color(255*0.7,170*0.7,0,0 )) ); + + // Create the Map Name Label + m_pMapTitle = new Label( "", TEAMMENU_WINDOW_TITLE_X, TEAMMENU_WINDOW_TITLE_Y ); + m_pMapTitle->setFont( pTitleFont ); + m_pMapTitle->setParent( m_pTeamWindow ); + pSchemes->getFgColor( hTitleScheme, r, g, b, a ); + m_pMapTitle->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hTitleScheme, r, g, b, a ); + m_pMapTitle->setBgColor( r, g, b, a ); + m_pMapTitle->setContentAlignment( vgui::Label::a_west ); + + // Create the Scroll panel + m_pScrollPanel = new CTFScrollPanel( TEAMMENU_WINDOW_TEXT_X, TEAMMENU_WINDOW_TEXT_Y, TEAMMENU_WINDOW_SIZE_X - (TEAMMENU_WINDOW_TEXT_X * 2), TEAMMENU_WINDOW_TEXT_SIZE_Y ); + m_pScrollPanel->setParent(m_pTeamWindow); + m_pScrollPanel->setScrollBarVisible(false, false); + + // Create the Map Briefing panel + m_pBriefing = new TextPanel("", 0,0, TEAMMENU_WINDOW_SIZE_X - TEAMMENU_WINDOW_TEXT_X, TEAMMENU_WINDOW_TEXT_SIZE_Y ); + m_pBriefing->setParent( m_pScrollPanel->getClient() ); + m_pBriefing->setFont( pSchemes->getFont(hTeamWindowText) ); + pSchemes->getFgColor( hTeamWindowText, r, g, b, a ); + m_pBriefing->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hTeamWindowText, r, g, b, a ); + m_pBriefing->setBgColor( r, g, b, a ); + + m_pBriefing->setText( gHUD.m_TextMessage.BufferedLocaliseTextString("#Map_Description_not_available") ); + + // Team Menu buttons + for (int i = 1; i <= 5; i++) + { + char sz[256]; + + int iYPos = TEAMMENU_TOPLEFT_BUTTON_Y + ( (TEAMMENU_BUTTON_SIZE_Y + TEAMMENU_BUTTON_SPACER_Y) * i ); + + // Team button + m_pButtons[i] = new CommandButton( "", TEAMMENU_TOPLEFT_BUTTON_X, iYPos, TEAMMENU_BUTTON_SIZE_X, TEAMMENU_BUTTON_SIZE_Y, true); + m_pButtons[i]->setParent( this ); + m_pButtons[i]->setContentAlignment( vgui::Label::a_west ); + m_pButtons[i]->setVisible( false ); + + // AutoAssign button uses special case + if (i == 5) + { + m_pButtons[5]->setBoundKey( '5' ); + m_pButtons[5]->setText( gHUD.m_TextMessage.BufferedLocaliseTextString("#Team_AutoAssign") ); + m_pButtons[5]->setVisible( true ); + } + + // Create the Signals + sprintf(sz, "jointeam %d", i); + m_pButtons[i]->addActionSignal( new CMenuHandler_StringCommandWatch( sz, true ) ); + m_pButtons[i]->addInputSignal( new CHandler_MenuButtonOver(this, i) ); + + // Create the Team Info panel + m_pTeamInfoPanel[i] = new TextPanel("", TEAMMENU_WINDOW_INFO_X, TEAMMENU_WINDOW_INFO_Y, TEAMMENU_WINDOW_SIZE_X - TEAMMENU_WINDOW_INFO_X, TEAMMENU_WINDOW_SIZE_X - TEAMMENU_WINDOW_INFO_Y ); + m_pTeamInfoPanel[i]->setParent( m_pTeamWindow ); + m_pTeamInfoPanel[i]->setFont( pSchemes->getFont(hTeamInfoText) ); + m_pTeamInfoPanel[i]->setFgColor( iTeamColors[i % iNumberOfTeamColors][0], + iTeamColors[i % iNumberOfTeamColors][1], + iTeamColors[i % iNumberOfTeamColors][2], + 0 ); + m_pTeamInfoPanel[i]->setBgColor( 0,0,0, 255 ); + } + + // Create the Cancel button + m_pCancelButton = new CommandButton( CHudTextMessage::BufferedLocaliseTextString( "#Menu_Cancel" ), TEAMMENU_TOPLEFT_BUTTON_X, 0, TEAMMENU_BUTTON_SIZE_X, TEAMMENU_BUTTON_SIZE_Y); + m_pCancelButton->setParent( this ); + m_pCancelButton->addActionSignal( new CMenuHandler_TextWindow(HIDE_TEXTWINDOW) ); + + // Create the Spectate button + m_pSpectateButton = new SpectateButton( CHudTextMessage::BufferedLocaliseTextString( "#Menu_Spectate" ), TEAMMENU_TOPLEFT_BUTTON_X, 0, TEAMMENU_BUTTON_SIZE_X, TEAMMENU_BUTTON_SIZE_Y, true); + m_pSpectateButton->setParent( this ); + m_pSpectateButton->addActionSignal( new CMenuHandler_StringCommand( "spectate", true ) ); + m_pSpectateButton->setBoundKey( '6' ); + m_pSpectateButton->addInputSignal( new CHandler_MenuButtonOver(this, 6) ); + + Initialize(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called each time a new level is started. +//----------------------------------------------------------------------------- +void CTeamMenuPanel::Initialize( void ) +{ + m_bUpdatedMapName = false; + m_iCurrentInfo = 0; + m_pScrollPanel->setScrollValue( 0, 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Called everytime the Team Menu is displayed +//----------------------------------------------------------------------------- +void CTeamMenuPanel::Update( void ) +{ + int iYPos = TEAMMENU_TOPLEFT_BUTTON_Y; + + // Set the team buttons + for (int i = 1; i <= 4; i++) + { + if (m_pButtons[i]) + { + if ( i <= gViewPort->GetNumberOfTeams() ) + { + m_pButtons[i]->setText( gViewPort->GetTeamName(i) ); + + // bound key replacement + char sz[32]; + sprintf( sz, "%d", i ); + m_pButtons[i]->setBoundKey( sz[0] ); + + m_pButtons[i]->setVisible( true ); + m_pButtons[i]->setPos( TEAMMENU_TOPLEFT_BUTTON_X, iYPos ); + iYPos += TEAMMENU_BUTTON_SIZE_Y + TEAMMENU_BUTTON_SPACER_Y; + + // Start with the first option up + if (!m_iCurrentInfo) + SetActiveInfo( i ); + + char szPlayerList[ (MAX_PLAYER_NAME_LENGTH + 3) * 31 ]; // name + ", " + strcpy(szPlayerList, "\n"); + // Update the Team Info + // Now count the number of teammembers of this class + int iTotal = 0; + for ( int j = 1; j < MAX_PLAYERS; j++ ) + { + if ( g_PlayerInfoList[j].name == NULL ) + continue; // empty player slot, skip + if ( g_PlayerInfoList[j].thisplayer ) + continue; // skip this player + if ( g_PlayerExtraInfo[j].teamnumber != i ) + continue; // skip over players in other teams + + iTotal++; + if (iTotal > 1) + strncat( szPlayerList, ", ", sizeof(szPlayerList) - strlen(szPlayerList) ); + strncat( szPlayerList, g_PlayerInfoList[j].name, sizeof(szPlayerList) - strlen(szPlayerList) ); + szPlayerList[ sizeof(szPlayerList) - 1 ] = '\0'; + } + + if (iTotal > 0) + { + // Set the text of the info Panel + char szText[ ((MAX_PLAYER_NAME_LENGTH + 3) * 31) + 256 ]; + if (iTotal == 1) + sprintf(szText, "%s: %d Player (%d points)", gViewPort->GetTeamName(i), iTotal, g_TeamInfo[i].frags ); + else + sprintf(szText, "%s: %d Players (%d points)", gViewPort->GetTeamName(i), iTotal, g_TeamInfo[i].frags ); + strncat( szText, szPlayerList, sizeof(szText) - strlen(szText) ); + szText[ sizeof(szText) - 1 ] = '\0'; + + m_pTeamInfoPanel[i]->setText( szText ); + } + else + { + m_pTeamInfoPanel[i]->setText( "" ); + } + } + else + { + // Hide the button (may be visible from previous maps) + m_pButtons[i]->setVisible( false ); + } + } + } + + // Move the AutoAssign button into place + m_pButtons[5]->setPos( TEAMMENU_TOPLEFT_BUTTON_X, iYPos ); + iYPos += TEAMMENU_BUTTON_SIZE_Y + TEAMMENU_BUTTON_SPACER_Y; + + // Spectate button + if (m_pSpectateButton->IsNotValid()) + { + m_pSpectateButton->setVisible( false ); + } + else + { + m_pSpectateButton->setPos( TEAMMENU_TOPLEFT_BUTTON_X, iYPos ); + m_pSpectateButton->setVisible( true ); + iYPos += TEAMMENU_BUTTON_SIZE_Y + TEAMMENU_BUTTON_SPACER_Y; + } + + // If the player is already in a team, make the cancel button visible + if ( g_iTeamNumber ) + { + m_pCancelButton->setPos( TEAMMENU_TOPLEFT_BUTTON_X, iYPos ); + iYPos += TEAMMENU_BUTTON_SIZE_Y + TEAMMENU_BUTTON_SPACER_Y; + m_pCancelButton->setVisible( true ); + } + else + { + m_pCancelButton->setVisible( false ); + } + + // Set the Map Title + if (!m_bUpdatedMapName) + { + const char *level = gEngfuncs.pfnGetLevelName(); + if (level && level[0]) + { + char sz[256]; + char szTitle[256]; + char *ch; + + // Update the level name + strcpy( sz, level ); + ch = strchr( sz, '/' ); + if (!ch) + ch = strchr( sz, '\\' ); + strcpy( szTitle, ch+1 ); + ch = strchr( szTitle, '.' ); + *ch = '\0'; + m_pMapTitle->setText( szTitle ); + *ch = '.'; + + // Update the map briefing + strcpy( sz, level ); + ch = strchr( sz, '.' ); + *ch = '\0'; + strcat( sz, ".txt" ); + char *pfile = (char*)gEngfuncs.COM_LoadFile( sz, 5, NULL ); + if (pfile) + { + m_pBriefing->setText( pfile ); + + // Get the total size of the Briefing text and resize the text panel + int iXSize, iYSize; + m_pBriefing->getTextImage()->getTextSize( iXSize, iYSize ); + m_pBriefing->setSize( iXSize, iYSize ); + gEngfuncs.COM_FreeFile( pfile ); + } + + m_bUpdatedMapName = true; + } + } + + m_pScrollPanel->validate(); +} + +//===================================== +// Key inputs +bool CTeamMenuPanel::SlotInput( int iSlot ) +{ + // Check for AutoAssign + if ( iSlot == 5) + { + m_pButtons[5]->fireActionSignal(); + return true; + } + + // Spectate + if ( iSlot == 6) + { + m_pSpectateButton->fireActionSignal(); + return true; + } + + // Otherwise, see if a particular team is selectable + if ( (iSlot < 1) || (iSlot > gViewPort->GetNumberOfTeams()) ) + return false; + if ( !m_pButtons[ iSlot ] ) + return false; + + // Is the button pushable? + if ( m_pButtons[ iSlot ]->isVisible() ) + { + m_pButtons[ iSlot ]->fireActionSignal(); + return true; + } + + return false; +} + +//====================================== +// Update the Team menu before opening it +void CTeamMenuPanel::Open( void ) +{ + Update(); + CMenuPanel::Open(); +} + +void CTeamMenuPanel::paintBackground() +{ + // make sure we get the map briefing up + if ( !m_bUpdatedMapName ) + Update(); + + CMenuPanel::paintBackground(); +} + +//====================================== +// Mouse is over a team button, bring up the class info +void CTeamMenuPanel::SetActiveInfo( int iInput ) +{ + // Remove all the Info panels and bring up the specified one + m_pSpectateButton->setArmed( false ); + for (int i = 1; i <= 5; i++) + { + m_pButtons[i]->setArmed( false ); + m_pTeamInfoPanel[i]->setVisible( false ); + } + + // 6 is Spectate + if (iInput == 6) + { + m_pSpectateButton->setArmed( true ); + } + else + { + m_pButtons[iInput]->setArmed( true ); + m_pTeamInfoPanel[iInput]->setVisible( true ); + } + + m_iCurrentInfo = iInput; + + m_pScrollPanel->validate(); +} diff --git a/cl_dll/vgui_tips.cpp b/cl_dll/vgui_tips.cpp new file mode 100644 index 0000000..c850fa3 --- /dev/null +++ b/cl_dll/vgui_tips.cpp @@ -0,0 +1,220 @@ +// ==================================== +// written by BUzer for HL: Paranoia modification +// ==================================== + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" +#include "const.h" +#include "entity_types.h" +#include "cdll_int.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_tips.h" +#include "vgui_shadowtext.h" +#include "..\game_shared\vgui_loadtga.h" +#include "getfont.h" + +#define TIPS_APPEAR_TIME 0.15 +#define TIPS_DISAPPEAR_TIME 1 + +#define TIPS_BASE_HEIGHT (YRES(390)) +#define TIPS_XSTEP (XRES(20)) + +int errorshows[10]; + +void ShowTip( client_textmessage_t *tempMessage ) +{ + if (gViewPort && gViewPort->m_pTips) + gViewPort->m_pTips->Show( tempMessage ); + else + gEngfuncs.Con_Printf("Tips error: m_pTips or ViewPort is not constructed!\n"); +} + + +Font* FontFromMessage(const char* &ptext); + +void CTips::Initialize() +{ + setVisible(false); + m_fShowTime = 0; + m_fHideTime = 0; + + if (m_pBitmap) delete m_pBitmap; + m_pBitmap = NULL; +} + +CTips::CTips() : Panel(0, 0, ScreenWidth, ScreenHeight) +{ + m_pBitmap = NULL; + m_pText = new ShadowTextPanel("", 0, 0, (ScreenWidth*3)/2, ScreenHeight); + m_pText->setParent(this); + m_pText->setPaintBackgroundEnabled(false); + Initialize(); + + for (int i = 0; i < 10; i++) + errorshows[i] = 0; +} + + +CTips::~CTips() +{ + if (m_pBitmap) delete m_pBitmap; + m_pBitmap = NULL; +} + +void CTips::Show( client_textmessage_t *tempMessage ) +{ + if (isVisible()) + { + nextMsg = tempMessage; + m_fHideTime = gEngfuncs.GetClientTime() + TIPS_APPEAR_TIME; // use fast disappear when someone waits + return; + } + + nextMsg = NULL; + setVisible(true); + LoadMsg(tempMessage); +} + +// it assumes that +// 20 <= (msg->effect) < 30 +void CTips::LoadMsg( client_textmessage_t *msg ) +{ + float curtime = gEngfuncs.GetClientTime(); +// gEngfuncs.Con_Printf("loaded\n"); + + m_fShowTime = curtime; + m_fHideTime = curtime + msg->holdtime; + const char *pText = msg->pMessage; + Font *pFont = FontFromMessage(pText); + m_pText->setFont(pFont); + m_pText->setText(pText); + m_pText->setFgColor(msg->r1, msg->g1, msg->b1, 0); + + if (m_pBitmap) delete m_pBitmap; + m_pBitmap = NULL; + + // load image + // (damn, instead of copying this block of code everywhere, + // i really need to create a single function to load images this way...) + int iconnum = msg->effect - 20; + + static int resArray[] = + { + 320, 400, 512, 640, 800, + 1024, 1152, 1280, 1600 + }; + + //find current resolution + int resArrayIndex = 0; + int i = 0; + while ((resArray[i] <= ScreenWidth) && (i < 9)) + { + resArrayIndex = i; + i++; + } + + while(m_pBitmap == NULL && resArrayIndex >= 0) + { + char imgName[64]; + sprintf(imgName, "gfx/vgui/icon%d_%d.tga", iconnum, resArray[resArrayIndex]); + m_pBitmap = vgui_LoadTGA(imgName); + resArrayIndex--; + } + + int iconWide = 0, iconTall = 0; + if (!m_pBitmap) + { + if (!errorshows[iconnum]) // dont show error message for same icon wice + { + gEngfuncs.Con_Printf("Could not load icon gfx/vgui/icon%d_...\n", iconnum); + errorshows[iconnum] = 1; + } + } + else + { + m_pBitmap->getSize( iconWide, iconTall ); + m_pBitmap->setPos( TIPS_XSTEP, TIPS_BASE_HEIGHT - (iconTall / 2)); + iconWide += XRES(7); + } + + m_pText->setPos( TIPS_XSTEP + iconWide, TIPS_BASE_HEIGHT - (pFont->getTall() / 2)); +} + + +void CTips::paintBackground() +{ + float curtime = gEngfuncs.GetClientTime(); + + if (curtime > m_fHideTime) + { + if (nextMsg) + { + LoadMsg(nextMsg); + nextMsg = NULL; + } + else + { + // put away from screen + Initialize(); + return; + } + } + + int mr, mg, mb, ma; + m_pText->getFgColor(mr, mg, mb, ma); + + if (curtime < m_fShowTime + TIPS_APPEAR_TIME) + { + float alpha = (curtime - m_fShowTime) / TIPS_APPEAR_TIME; + m_pText->setFgColor( mr, mg, mb, (1 - alpha) * 255 ); + if (m_pBitmap) m_pBitmap->setColor( Color(255, 255, 255, (1 - alpha) * 255) ); + } + else if (nextMsg && (curtime > m_fHideTime - TIPS_APPEAR_TIME)) + { + float alpha = (m_fHideTime - curtime) / TIPS_APPEAR_TIME; + m_pText->setFgColor( mr, mg, mb, (1 - alpha) * 255 ); + if (m_pBitmap) m_pBitmap->setColor( Color(255, 255, 255, (1 - alpha) * 255) ); + } + else if (!nextMsg && (curtime > m_fHideTime - TIPS_DISAPPEAR_TIME)) + { + float alpha = (m_fHideTime - curtime) / TIPS_DISAPPEAR_TIME; + m_pText->setFgColor( mr, mg, mb, (1 - alpha) * 255 ); + if (m_pBitmap) m_pBitmap->setColor( Color(255, 255, 255, (1 - alpha) * 255) ); + } + else + { + m_pText->setFgColor( mr, mg, mb, 0 ); + if (m_pBitmap) m_pBitmap->setColor( Color(255, 255, 255, 0) ); + } + + if (m_pBitmap) + m_pBitmap->doPaint(this); + +// Panel::paintBackground(); +} + + + +// íåâàæíî, êóäà-áû ýòî çàñóíóòü.. + +#include "vgui_screenmsg.h" + +void VGuiAddScreenMessage( client_textmessage_t *msg ) +{ + if (gViewPort && gViewPort->m_pScreenMsg) + { + gViewPort->m_pScreenMsg->SetMessage( msg ); + } + else + gEngfuncs.Con_Printf("Screenmessage error: ViewPort is not constructed!\n"); +} + +// Wargon: Çàñóíó è ÿ ýòî ñþäà. ) +void VGuiAddScrollingMessage( client_textmessage_t *msg ) +{ + if (gViewPort && gViewPort->m_pScrollingMsg) + gViewPort->m_pScrollingMsg->SetMessage(msg); + else + gEngfuncs.Con_Printf("Scrollingmessage error: ViewPort is not constructed!\n"); +} diff --git a/cl_dll/vgui_tips.h b/cl_dll/vgui_tips.h new file mode 100644 index 0000000..c112513 --- /dev/null +++ b/cl_dll/vgui_tips.h @@ -0,0 +1,32 @@ +// ====================================== +// written by BUzer for HL: Paranoia modification +// ====================================== + +#ifndef _TIPS_H +#define _TIPS_H +using namespace vgui; + + +class CTips : public Panel +{ +public: + CTips(); + ~CTips(); + void Show( client_textmessage_t *tempMessage ); + void Initialize(); + +protected: + virtual void paintBackground(); + void LoadMsg( client_textmessage_t *tempMessage ); + +protected: + BitmapTGA *m_pBitmap; + TextPanel *m_pText; + + float m_fShowTime; + float m_fHideTime; + + client_textmessage_t *nextMsg; +}; + +#endif // _TIPS_H \ No newline at end of file diff --git a/common/areanode.h b/common/areanode.h new file mode 100644 index 0000000..824aa93 --- /dev/null +++ b/common/areanode.h @@ -0,0 +1,33 @@ +/* +areanode.h - arenode description +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef AREANODE_H +#define AREANODE_H + +typedef struct areanode_s +{ + int axis; // -1 = leaf node + float dist; + struct areanode_s *children[2]; + link_t trigger_edicts; + link_t solid_edicts; + link_t portal_edicts; +} areanode_t; + +#define STRUCT_FROM_LINK( l, t, m ) ((t *)((byte *)l - (int)&(((t *)0)->m))) +#define EDICT_FROM_AREA( l ) STRUCT_FROM_LINK( l, edict_t, area ) +#define FACET_FROM_AREA( l ) STRUCT_FROM_LINK( l, mfacet_t, area ) + +#endif//AREANODE_H \ No newline at end of file diff --git a/common/beamdef.h b/common/beamdef.h new file mode 100644 index 0000000..f493391 --- /dev/null +++ b/common/beamdef.h @@ -0,0 +1,62 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined ( BEAMDEFH ) +#define BEAMDEFH +#ifdef _WIN32 +#pragma once +#endif + +#define FBEAM_STARTENTITY 0x00000001 +#define FBEAM_ENDENTITY 0x00000002 +#define FBEAM_FADEIN 0x00000004 +#define FBEAM_FADEOUT 0x00000008 +#define FBEAM_SINENOISE 0x00000010 +#define FBEAM_SOLID 0x00000020 +#define FBEAM_SHADEIN 0x00000040 +#define FBEAM_SHADEOUT 0x00000080 +#define FBEAM_STARTVISIBLE 0x10000000 // Has this client actually seen this beam's start entity yet? +#define FBEAM_ENDVISIBLE 0x20000000 // Has this client actually seen this beam's end entity yet? +#define FBEAM_ISACTIVE 0x40000000 +#define FBEAM_FOREVER 0x80000000 + +typedef struct beam_s BEAM; +struct beam_s +{ + BEAM *next; + int type; + int flags; + vec3_t source; + vec3_t target; + vec3_t delta; + float t; // 0 .. 1 over lifetime of beam + float freq; + float die; + float width; + float amplitude; + float r, g, b; + float brightness; + float speed; + float frameRate; + float frame; + int segments; + int startEntity; + int endEntity; + int modelIndex; + int frameCount; + struct model_s *pFollowModel; + struct particle_s *particles; +}; + +#endif diff --git a/common/bspfile.h b/common/bspfile.h new file mode 100644 index 0000000..48c32f2 --- /dev/null +++ b/common/bspfile.h @@ -0,0 +1,485 @@ +/* +bspfile.h - BSP format included q1, hl1 support +Copyright (C) 2010 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef BSPFILE_H +#define BSPFILE_H + +/* +============================================================================== + +BRUSH MODELS + +.bsp contain level static geometry with including PVS and lightning info +============================================================================== +*/ +// header +#define Q1BSP_VERSION 29 // quake1 regular version (beta is 28) +#define HLBSP_VERSION 30 // half-life regular version +#define P2BSP_VERSION 32 // P2:Savior 32-bit limits BSP version + +#define IDEXTRAHEADER (('H'<<24)+('S'<<16)+('A'<<8)+'X') // little-endian "XASH" +#define EXTRA_VERSION 4 // ver. 1 was occupied by old versions of XashXT, ver. 2 was occupied by old vesrions of P2:savior + // ver. 3 was occupied by experimental versions of P2:savior change fmt +#define EXTRA_VERSION_OLD 2 // extra version 2 (P2:Savior regular version) to get minimal backward compatibility + +// worldcraft predefined angles +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + +// bmodel limits +#define MAX_MAP_HULLS 4 // MAX_HULLS +#define MIPLEVELS 4 // software renderer mipmap count + +#define WORLD_MINS -32768 +#define WORLD_MAXS 32768 +#define WORLD_SIZE ( WORLD_MAXS - WORLD_MINS ) +#define BOGUS_RANGE WORLD_SIZE * 1.74 // half-diagonal + +// these are for entity key:value pairs +#define MAX_KEY 64 +#define MAX_VALUE 4096 + +// lightstyle management +#define MAXLIGHTMAPS 4 // max lightstyles per each face +#define MAXSTYLES 64 // very fundamental thing +#define LS_NORMAL 0x00 +#define LS_SKY 0x14 // light_environment style +#define LS_UNUSED 0xFE +#define LS_NONE 0xFF + +#define VERTEXNORMAL_CONE_INNER_ANGLE DEG2RAD( 7.275 ) + +#define MAX_MAP_MODELS 1024 // can be increased up to 2048 if needed +#define MAX_MAP_ENTITIES 4096 // can be increased up to 32768 if needed +#define MAX_MAP_ENTSTRING 0x200000 // 2 mB should be enough +#define MAX_MAP_PLANES 65536 // can be increased without problems +#define MAX_MAP_NODES 32767 // because negative shorts are leafnums +#define MAX_MAP_CLIPNODES 32767 // because negative shorts are contents +#define MAX_MAP_CLIPNODES32 262144 // can be increased but not needed +#define MAX_MAP_LEAFS 32768 // signed short limit (GoldSrc have internal limit at 8192) +#define MAX_MAP_VERTS 65535 // unsigned short limit +#define MAX_MAP_FACES 65535 // unsigned short limit +#define MAX_MAP_MARKSURFACES 65535 // unsigned short limit +#define MAX_MAP_TEXINFO 32768 // because signed short +#define MAX_MAP_FACEINFO 8192 // can be increased but not needs +#define MAX_MAP_EDGES 0x100000 // can be increased but not needed +#define MAX_MAP_SURFEDGES 0x200000 // can be increased but not needed +#define MAX_MAP_TEXTURES 2048 // can be increased but not needed +#define MAX_MAP_MIPTEX 0x2000000 // 32 Mb internal textures data +#define MAX_MAP_LIGHTING 0x4000000 // 64 Mb lightmap raw data (can contain deluxemaps) +#define MAX_MAP_VISIBILITY 0x2000000 // 32 Mb visdata +#define MAX_MAP_VISLIGHTDATA 0x4000000 // 64 Mb for lights visibility +#define MAX_MAP_CUBEMAPS 1024 +#define MAX_MAP_LEAFLIGHTS 0x40000 // can be increased +#define MAX_MAP_WORLDLIGHTS 65535 // including a light surfaces too + +// quake lump ordering +#define LUMP_ENTITIES 0 +#define LUMP_PLANES 1 +#define LUMP_TEXTURES 2 // internal textures +#define LUMP_VERTEXES 3 +#define LUMP_VISIBILITY 4 +#define LUMP_NODES 5 +#define LUMP_TEXINFO 6 +#define LUMP_FACES 7 +#define LUMP_LIGHTING 8 +#define LUMP_CLIPNODES 9 +#define LUMP_LEAFS 10 +#define LUMP_MARKSURFACES 11 +#define LUMP_EDGES 12 +#define LUMP_SURFEDGES 13 +#define LUMP_MODELS 14 // internal submodels +#define HEADER_LUMPS 15 + +// extra version 4 +#define LUMP_LIGHTVECS 0 // deluxemap data +#define LUMP_FACEINFO 1 // landscape and lightmap resolution info +#define LUMP_CUBEMAPS 2 // cubemap description +#define LUMP_VERTNORMALS 3 // phong shaded vertex normals +#define LUMP_LEAF_LIGHTING 4 // store vertex lighting for statics +#define LUMP_WORLDLIGHTS 5 // list of all the virtual and real lights (used to relight models in-game) +#define LUMP_COLLISION 6 // physics engine collision hull dump +#define LUMP_AINODEGRAPH 7 // node graph that stored into the bsp +#define LUMP_SHADOWMAP 8 // contains shadow map for direct light +#define LUMP_VERTEX_LIGHT 9 // store vertex lighting for statics +#define LUMP_VISLIGHTDATA 10 // how many lights affects to faces +#define LUMP_SURFACE_LIGHT 11 // models lightmapping +#define EXTRA_LUMPS 12 // count of the extra lumps + +// texture flags +#define TEX_SPECIAL BIT( 0 ) // sky or slime, no lightmap or 256 subdivision +#define TEX_WORLD_LUXELS BIT( 1 ) // alternative lightmap matrix will be used (luxels per world units instead of luxels per texels) +#define TEX_AXIAL_LUXELS BIT( 2 ) // force world luxels to axial positive scales +#define TEX_EXTRA_LIGHTMAP BIT( 3 ) // bsp31 legacy - using 8 texels per luxel instead of 16 texels per luxel +#define TEX_NOSHADOW BIT( 4 ) +#define TEX_NODIRT BIT( 5 ) +#define TEX_SCROLL BIT( 6 ) // Doom special FX + +// ambient sound types +enum +{ + AMBIENT_WATER = 0, // waterfall + AMBIENT_SKY, // wind + AMBIENT_SLIME, // never used in quake + AMBIENT_LAVA, // never used in quake + NUM_AMBIENTS, // automatic ambient sounds +}; + +// leaf contents +#define CONTENTS_NONE 0 +#define CONTENTS_EMPTY -1 +#define CONTENTS_SOLID -2 +#define CONTENTS_WATER -3 +#define CONTENTS_SLIME -4 +#define CONTENTS_LAVA -5 +#define CONTENTS_SKY -6 +// reserved +// reserved +#define CONTENTS_CURRENT_0 -9 +#define CONTENTS_CURRENT_90 -10 +#define CONTENTS_CURRENT_180 -11 +#define CONTENTS_CURRENT_270 -12 +#define CONTENTS_CURRENT_UP -13 +#define CONTENTS_CURRENT_DOWN -14 +#define CONTENTS_TRANSLUCENT -15 +// user contents that never present into bsp +#define CONTENTS_LADDER -16 +#define CONTENTS_FLYFIELD -17 +#define CONTENTS_GRAVITY_FLYFIELD -18 +#define CONTENTS_FOG -19 +#define CONTENTS_SPECIAL1 -20 +#define CONTENTS_SPECIAL2 -21 +#define CONTENTS_SPECIAL3 -22 +#define CONTENTS_SPOTLIGHT -23 // in of cone of spotlight + +// +// BSP File Structures +// + +typedef struct +{ + int fileofs; + int filelen; +} dlump_t; + +typedef struct +{ + int version; + dlump_t lumps[HEADER_LUMPS]; +} dheader_t; + +typedef struct +{ + int id; // must be little endian XASH + int version; + dlump_t lumps[EXTRA_LUMPS]; +} dextrahdr_t; + +typedef struct +{ + float mins[3]; + float maxs[3]; + float origin[3]; // for sounds or lights + int headnode[MAX_MAP_HULLS]; + int visleafs; // not including the solid leaf 0 + int firstface; + int numfaces; +} dmodel_t; + +typedef struct +{ + int nummiptex; + int dataofs[4]; // [nummiptex] +} dmiptexlump_t; + +typedef struct miptex_s +{ + char name[16]; + uint width; + uint height; + uint offsets[MIPLEVELS]; // four mip maps stored +} miptex_t; + +typedef struct +{ + float point[3]; +} dvertex_t; + +typedef struct +{ + float normal[3]; + float dist; + int type; +} dplane_t; + +typedef struct +{ + int planenum; // allow planes >= 65535 + short children[2]; // negative numbers are -(leafs + 1), not nodes + short mins[3]; // for sphere culling + short maxs[3]; + word firstface; + word numfaces; // counting both sides +} dnode_t; + +typedef struct +{ + int planenum; + int children[2]; // negative numbers are -(leafs+1), not nodes + float mins[3]; // for sphere culling + float maxs[3]; + int firstface; + int numfaces; // counting both sides +} dnode32_t; + +// leaf 0 is the generic CONTENTS_SOLID leaf, used for all solid areas +// all other leafs need visibility info +typedef struct +{ + int contents; + int visofs; // -1 = no visibility info + + short mins[3]; // for frustum culling + short maxs[3]; + word firstmarksurface; + word nummarksurfaces; + + // automatic ambient sounds + byte ambient_level[NUM_AMBIENTS]; // ambient sound level (0 - 255) +} dleaf_t; + +typedef struct +{ + int contents; + int visofs; // -1 = no visibility info + + float mins[3]; // for frustum culling + float maxs[3]; + + int firstmarksurface; + int nummarksurfaces; + + byte ambient_level[NUM_AMBIENTS]; +} dleaf32_t; + +typedef struct +{ + int planenum; // allow planes >= 65535 + short children[2]; // negative numbers are contents +} dclipnode_t; + +typedef struct +{ + int planenum; + int children[2]; // negative numbers are contents +} dclipnode32_t; + +typedef struct +{ + float vecs[2][4]; // texmatrix [s/t][xyz offset] + int miptex; + short flags; + short faceinfo; // -1 no face info otherwise dfaceinfo_t +} dtexinfo_t; + +typedef word dmarkface_t; // leaf marksurfaces indexes +typedef int dmarkface32_t; // leaf marksurfaces indexes +typedef int dsurfedge_t; // map surfedges +typedef short dvertnorm_t; // map vert normals + +// NOTE: that edge 0 is never used, because negative edge nums +// are used for counterclockwise use of the edge in a face +typedef struct +{ + word v[2]; // vertex numbers +} dedge_t; + +typedef struct +{ + int v[2]; // vertex numbers +} dedge32_t; + +typedef struct +{ + word planenum; + short side; + + int firstedge; // we must support > 64k edges + short numedges; + short texinfo; + + // lighting info + byte styles[MAXLIGHTMAPS]; + int lightofs; // start of [numstyles*surfsize] samples +} dface_t; + +typedef struct +{ + int planenum; + int side; + + int firstedge; // we must support > 64k edges + int numedges; + int texinfo; + + // lighting info + byte styles[MAXLIGHTMAPS]; + int lightofs; // start of [numstyles*surfsize] samples +} dface32_t; + +//============================================================================ +typedef struct +{ + short origin[3]; // position of light snapped to the nearest integer + short size; // cubemap side size +} dcubemap_t; + +typedef struct +{ + float normal[3]; +} dnormal_t; + +typedef struct +{ + char landname[16]; // name of decsription in mapname_land.txt + word texture_step; // default is 16, pixels\luxels ratio + word max_extent; // default is 16, subdivision step ((texture_step * max_extent) - texture_step) + short groupid; // to determine equal landscapes from various groups, -1 - no group +} dfaceinfo_t; + +typedef struct +{ + byte color[6][3]; // 6 sides 1x1 (single pixel per side) +} dlightcube_t; + +typedef struct +{ + dlightcube_t ambient; + short origin[3]; + short leafnum; // leaf that contain this sample +} dleafsample_t; + +typedef enum +{ + emit_ignored = -1, + emit_surface, + emit_point, + emit_spotlight, + emit_skylight +} emittype_t; + +typedef enum +{ + falloff_quake = 0, // linear (x) (DEFAULT) + falloff_inverse, // inverse (1/x), scaled by 1/128 + falloff_inverse2, // inverse square (1/(x^2)), scaled by 1/(128^2) + falloff_infinite, // no attenuation, same brightness at any distance + falloff_localmin, // no attenuation, non-additive minlight effect within line of sight of the light source. + falloff_inverse2a, // inverse square, with distance adjusted. (1/(x+128)^2), scaled by 1/(128^2) + falloff_valve, // special case for valve attenuation style (unreachable by "delay" field) +} fallofftype_t; + +#define DWL_FLAGS_INAMBIENTCUBE 0x0001 // This says that the light was put into the per-leaf ambient cubes. + +typedef struct +{ + byte emittype; + byte style; + byte flags; // will be set in ComputeLeafAmbientLighting + short origin[3]; // light abs origin + float intensity[3]; // RGB + float normal[3]; // for surfaces and spotlights + float stopdot; // for spotlights + float stopdot2; // for spotlights + float fade; // falloff scaling for linear and inverse square falloff (0.5 = farther, 2.0 = shorter etc) + float radius; // light radius + short leafnum; // light linked into this leaf + byte falloff; // falloff style 0 = default (inverse square), 1 = inverse falloff, 2 = inverse square + unsigned short facenum; // face number for emit_surface + short modelnumber; // g-cont. we can't link lights with entities by entity number so we link it by bmodel number +} dworldlight_t; + +#define VLIGHTIDENT (('T'<<24)+('I'<<16)+('L'<<8)+'V') // little-endian "VLIT" +#define VLIGHT_VERSION 1 + +#define FLIGHTIDENT (('P'<<24)+('A'<<16)+('M'<<8)+'L') // little-endian "LMAP" +#define FLIGHT_VERSION 2 + +#include "color24.h" + +typedef struct +{ + color24 light[MAXLIGHTMAPS]; // lightvalue + color24 deluxe[MAXLIGHTMAPS]; // deluxe vectors + byte shadow[MAXLIGHTMAPS]; // shadowmap +} dvertlight_t; + +typedef struct +{ + byte styles[MAXLIGHTMAPS]; + int lightofs; // -1 no lightdata +} dfacelight_t; + +typedef struct +{ + int submodel_offset; // hack to determine submodel + int vertex_offset; +} dvlightofs_t; + +typedef struct +{ + int submodel_offset; // hack to determine submodel + int surface_offset; +} dflightofs_t; + +typedef struct +{ + unsigned int modelCRC; // catch for model changes + int numverts; + byte styles[MAXLIGHTMAPS]; + dvlightofs_t submodels[32]; // MAXSTUDIOMODELS + dvertlight_t verts[3]; // variable sized +} dmodelvertlight_t; + +typedef struct +{ + unsigned int modelCRC; // catch for model changes + int numfaces; + byte styles[MAXLIGHTMAPS]; + short texture_step; + float origin[3]; + float angles[3]; + float scale[3]; + dflightofs_t submodels[32]; // MAXSTUDIOMODELS + dfacelight_t faces[3]; // variable sized +} dmodelfacelight_t; + +typedef struct +{ + int ident; // to differentiate from previous lump LUMP_LEAF_LIGHTING + int version; // data package version + int nummodels; + int dataofs[4]; // [nummodels] +} dvlightlump_t; + +#define NORMIDENT (('T'<<24)+('B'<<16)+('T'<<8)+'Q') // little-endian "QTBN" + +typedef struct +{ + int ident; // to differentiate from non-indexed normals storage + int numnormals; // dvertnorm[numsurfedges] dnormals[numnormals] +} dnormallump_t; + +#endif//BSPFILE_H \ No newline at end of file diff --git a/common/cl_entity.h b/common/cl_entity.h new file mode 100644 index 0000000..8cb5a1e --- /dev/null +++ b/common/cl_entity.h @@ -0,0 +1,111 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef CL_ENTITY_H +#define CL_ENTITY_H + +typedef struct efrag_s +{ + struct mleaf_s *leaf; + struct efrag_s *leafnext; + struct cl_entity_s *entity; + struct efrag_s *entnext; +} efrag_t; + +typedef struct +{ + byte mouthopen; // 0 = mouth closed, 255 = mouth agape + byte sndcount; // counter for running average + int sndavg; // running average +} mouth_t; + +typedef struct +{ + float prevanimtime; + float sequencetime; + byte prevseqblending[2]; + vec3_t prevorigin; + vec3_t prevangles; + + int prevsequence; + float prevframe; + + byte prevcontroller[4]; + byte prevblending[2]; +} latchedvars_t; + +typedef struct +{ + // Time stamp for this movement + float animtime; + + vec3_t origin; + vec3_t angles; +} position_history_t; + +typedef struct cl_entity_s cl_entity_t; + +#define HISTORY_MAX 64 // Must be power of 2 +#define HISTORY_MASK ( HISTORY_MAX - 1 ) + +#include "entity_state.h" +#include "event_args.h" + +struct cl_entity_s +{ + int index; // Index into cl_entities ( should match actual slot, but not necessarily ) + qboolean player; // True if this entity is a "player" + + entity_state_t baseline; // The original state from which to delta during an uncompressed message + entity_state_t prevstate; // The state information from the penultimate message received from the server + entity_state_t curstate; // The state information from the last message received from server + + int current_position; // Last received history update index + position_history_t ph[HISTORY_MAX]; // History of position and angle updates for this player + + mouth_t mouth; // For synchronizing mouth movements. + + latchedvars_t latched; // Variables used by studio model rendering routines + + // Information based on interplocation, extrapolation, prediction, or just copied from last msg received. + // + float lastmove; + + // Actual render position and angles + vec3_t origin; + vec3_t angles; + + // Attachment points + vec3_t attachment[4]; + + // Other entity local information + int modelhandle; // was trivial_accept + + struct model_s *model; // cl.model_precache[ curstate.modelindes ]; all visible entities have a model + struct efrag_s *efrag; // linked list of efrags + struct mnode_s *topnode; // for bmodels, first world node that splits bmodel, or NULL if not split + + word hCachedMatrix; // modelviewProjectionMatrix handle in matrix cache (was syncbase) + word reserved; + int visframe; // last frame this entity was found in an active leaf + + union + { + colorVec cvFloorColor; + byte lights[16]; // only first eight entries is used + }; +}; + +#endif//CL_ENTITY_H \ No newline at end of file diff --git a/common/color24.h b/common/color24.h new file mode 100644 index 0000000..7268816 --- /dev/null +++ b/common/color24.h @@ -0,0 +1,35 @@ +/* +color24.h - simple object for handling color +Copyright (C) 2019 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef COLOR24_H +#define COLOR24_H + +struct color24 +{ +public: + color24() {} + color24( byte cr, byte cg, byte cb ) + { + r = cr; + g = cg; + b = cb; + } + inline color24 operator+(const color24& v) const { return color24(r+v.r, g+v.g, b+v.b); } + inline color24 operator-(const color24& v) const { return color24(r-v.r, g-v.g, b-v.b); } + inline color24 operator*(float fl) const { return color24(r*fl, g*fl, b*fl); } + byte r, g, b; +}; + +#endif//COLOR24_H \ No newline at end of file diff --git a/common/com_model.h b/common/com_model.h new file mode 100644 index 0000000..f1359ce --- /dev/null +++ b/common/com_model.h @@ -0,0 +1,557 @@ +/* +com_model.h - cient model structures +Copyright (C) 2010 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef COM_MODEL_H +#define COM_MODEL_H + +#include "bspfile.h" // we need some declarations from it +#include "shader.h" + +/* +============================================================================== + + ENGINE MODEL FORMAT +============================================================================== +*/ +#define STUDIO_RENDER 1 +#define STUDIO_EVENTS 2 + +#define ZISCALE ((float)0x8000) + +#define MIPLEVELS 4 +#define VERTEXSIZE 7 +#define NUM_AMBIENTS 4 // automatic ambient sounds + +// model types +typedef enum +{ + mod_bad = -1, + mod_brush, + mod_sprite, + mod_alias, + mod_studio +} modtype_t; + +// brush model flags (stored in model_t->flags) +#define MODEL_CONVEYOR (1<<0) +#define MODEL_HAS_ORIGIN (1<<1) + +#define MODEL_WORLD BIT( 29 ) // it's a worldmodel + +class CMeshDesc; + +typedef struct mplane_s +{ + vec3_t normal; + float dist; + byte type; // for fast side tests + byte signbits; // signx + (signy<<1) + (signz<<1) + byte pad[2]; +} mplane_t; + +typedef struct +{ + vec3_t position; +} mvertex_t; + +typedef struct +{ + unsigned short v[2]; + unsigned int cachededgeoffset; +} medge_t; + +// brush material flags +#define BRUSH_TRANSPARENT 0x0001 // alpha-transparent +#define BRUSH_FULLBRIGHT 0x0002 // ignore lightmap for this surface (probably water) +#define BRUSH_HAS_DETAIL 0x0004 // has detail texture +#define BRUSH_HAS_BUMP 0x0008 // has normalmap +#define BRUSH_HAS_SPECULAR 0x0010 // has specular +#define BRUSH_TWOSIDE 0x0020 // some types of water +#define BRUSH_CONVEYOR 0x0040 // scrolled texture +#define BRUSH_GLOSSPOWER 0x0080 // gloss power stored in alphachannel of gloss map +#define BRUSH_REFLECT 0x0100 // reflect surface +#define BRUSH_UNDERWATER 0x0200 // ugly hack to tell GLSL about camera inside water volume +#define BRUSH_HAS_LUMA 0x0400 // self-illuminate parts +#define BRUSH_HAS_ALPHA 0x0800 // diffuse texture has alpha-cahhnel +#define BRUSH_LIQUID 0x1000 // reflective & refractive water +#define BRUSH_MULTI_LAYERS 0x2000 // this face has multiply layers (random-tiling or landscape) +#define BRUSH_HAS_HEIGHTMAP 0x4000 // have heightmap + +// each texture_t has a material +typedef struct material_s +{ + // this part is shared with matdesc_t + float smoothness; // smoothness factor + float detailScale[2]; // detail texture scales x, y + float reflectScale; // reflection scale for translucent water + float refractScale; // refraction scale for mirrors, windows, water + float aberrationScale; // chromatic abberation + float reliefScale; // relief-mapping + struct matdef_s *effects; // hit, impact, particle effects etc + + // this part is world-specific params + struct texture_s *pSource; // pointer to original texture + + unsigned short gl_diffuse_id; // diffuse texture + unsigned short gl_detailmap_id; // detail texture + unsigned short gl_normalmap_id; // normalmap + unsigned short gl_specular_id; // specular + unsigned short gl_glowmap_id; // self-illuminate parts + unsigned short gl_heightmap_id; // parallax bumpmap + + int flags; // brush material flags +} material_t; + +typedef struct texture_s +{ + char name[16]; + unsigned int width, height; + int gl_texturenum; + struct msurface_s *texturechain; // for gl_texsort drawing + int anim_total; // total tenths in sequence ( 0 = no) + int anim_min, anim_max; // time for this frame min <=time< max + struct texture_s *anim_next; // in the animation sequence + struct texture_s *alternate_anims; // bmodels in frame 1 use these + unsigned short fb_texturenum; // auto-luma texturenum + unsigned short dt_texturenum; // detail-texture binding + material_t *material; // pointer to texture material + struct matdef_s *effects; // hit, impact, particle effects etc + unsigned int unused; // reserved +} texture_t; + +typedef struct +{ + char landname[16]; // name of decsription in mapname_land.txt + unsigned short texture_step; // default is 16, pixels\luxels ratio + unsigned short max_extent; // default is 16, subdivision step ((texture_step * max_extent) - texture_step) + short groupid; // to determine equal landscapes from various groups, -1 - no group + + vec3_t mins, maxs; + + // effects per-layers for playing sounds etc + struct matdef_s *effects[MAX_LANDSCAPE_LAYERS]; // hit, impact, particle effects etc + + byte *heightmap; // terrain heightmap raw data + unsigned short heightmap_width; + unsigned short heightmap_height; + + struct terrain_s *terrain; // valid only for client-side or local game + + int reserved[13]; // just for future expansions or mod-makers +} mfaceinfo_t; + +typedef struct +{ + mplane_t *edges; + int numedges; + vec3_t origin; + float radius; // for culling tests + int contents; // sky or solid +} mfacebevel_t; + +typedef struct +{ + float vecs[2][4]; // [s/t] unit vectors in world space. + // [i][3] is the s/t offset relative to the origin. + // s or t = dot( 3Dpoint, vecs[i] ) + vecs[i][3] + mfaceinfo_t *faceinfo; // pointer to landscape info and lightmap resolution (may be NULL) + texture_t *texture; + int flags; // sky or slime, no lightmap or 256 subdivision +} mtexinfo_t; + +typedef struct glpoly_s +{ + struct glpoly_s *next; + struct glpoly_s *chain; + int numverts; + int flags; // for SURF_UNDERWATER + float verts[4][VERTEXSIZE]; // variable sized (xyz s1t1 s2t2) +} glpoly_t; + +typedef struct mnode_s +{ +// common with leaf + int contents; // 0, to differentiate from leafs + int visframe; // node needs to be traversed if current + + float minmaxs[6]; // for bounding box culling + struct mnode_s *parent; + +// node specific + mplane_t *plane; + struct mnode_s *children[2]; + + unsigned short firstsurface; + unsigned short numsurfaces; +} mnode_t; + +typedef struct msurface_s msurface_t; +typedef struct decal_s decal_t; + +// JAY: Compress this as much as possible +struct decal_s +{ + decal_t *pnext; // linked list for each surface + msurface_t *psurface; // Surface id for persistence / unlinking + float dx; // local texture coordinates + float dy; // + float scale; // Pixel scale + short texture; // Decal texture + byte flags; // Decal flags FDECAL_* + short entityIndex; // Entity this is attached to +// Xash3D added + vec3_t position; // location of the decal center in world space. + glpoly_t *polys; // precomputed decal vertices +}; + +typedef struct mleaf_s +{ +// common with node + int contents; + int visframe; // node needs to be traversed if current + + float minmaxs[6]; // for bounding box culling + + struct mnode_s *parent; +// leaf specific + byte *compressed_vis; + struct efrag_s *efrags; + + msurface_t **firstmarksurface; + int nummarksurfaces; + int cluster; // helper to acess to uncompressed visdata + byte ambient_sound_level[NUM_AMBIENTS]; + +} mleaf_t; + +typedef struct mcubemap_s +{ + char name[64]; // for envshots + int texture; // gl texturenum + vec3_t mins, maxs; + bool valid; // don't need to rebuild + vec3_t origin; + short size; // cubemap size + byte numMips; // cubemap mipcount +} mcubemap_t; + +#define SURF_TWOSIDE (1<<0) // two-sided polygon (e.g. 'water4b') +#define SURF_PLANEBACK (1<<1) // plane should be negated +#define SURF_DRAWSKY (1<<2) // sky surface +#define SURF_WATERCSG (1<<3) // culled by csg (was SURF_DRAWSPRITE) +#define SURF_DRAWTURB (1<<4) // warp surface +#define SURF_DRAWTILED (1<<5) // face without lighmap +#define SURF_CONVEYOR (1<<6) // scrolled texture (was SURF_DRAWBACKGROUND) +#define SURF_UNDERWATER (1<<7) // caustics +#define SURF_TRANSPARENT (1<<8) // it's a transparent texture (was SURF_DONTWARP) +#define SURF_HAS_DECALS (1<<9) // this surface has decals +#define SURF_LANDSCAPE (1<<10) // this is multi-layered texture surface +#define SURF_MOVIE (1<<11) // screen movie +#define SURF_GRASS_UPDATE BIT( 12 ) // gamma has changed, need to update grass +#define SURF_NODRAW (1<<13) // failed to create shader for this surface +#define SURF_OCCLUDED (1<<14) // occlusion query result +#define SURF_QUEUED (1<<15) // add to queue for occlusion +#define SURF_NODLIGHT (1<<16) // failed to create dlight shader for this surface +#define SURF_NOSUNLIGHT (1<<17) // failed to create sun light shader for this surface + +#define SURF_FULLBRIGHT BIT( 25 ) // completely ignore lighting on this brush +#define SURF_OF_SUBMODEL BIT( 26 ) // this face is owned by submodel (to differentiate from world faces) +#define SURF_LOCAL_SPACE BIT( 27 ) // if bmodel has origin this surf is in local space (used for sorting) +#define SURF_LM_UPDATE BIT( 28 ) // lightmap was changed, need to reload +#define SURF_DM_UPDATE BIT( 29 ) // deluxmap was changed, need to reload +#define SURF_REFLECT_PUDDLE BIT( 30 ) // special reflected surface (puddle reflection) +#define SURF_REFLECT BIT( 31 ) // reflect surface (mirror) + +// surface extradata +typedef struct mextrasurf_s +{ + vec3_t mins, maxs; + vec3_t origin; // surface origin + struct msurface_s *surf; // upcast to surface + + // extended light info + int dlight_s, dlight_t; // gl lightmap coordinates for dynamic lightmaps + + short lightmapmins[2]; + short lightextents[2]; + float lmvecs[2][4]; + + color24 *normals; // note: this is the actual deluxemap data for this surface + byte *shadows; // note: occlusion map for this surface +// begin userdata + struct msurface_s *lightmapchain; // lightmapped polys + struct mextrasurf_s *detailchain; // for detail textures drawing + mfacebevel_t *bevel; // for exact face traceline + struct mextrasurf_s *lumachain; // draw fullbrights + struct cl_entity_s *parent; // upcast to owner entity +// was mirrortexturenum, mirrormatrix (17 * sizeof( int )) + unsigned short light_s[MAXLIGHTMAPS]; + unsigned short light_t[MAXLIGHTMAPS]; + byte lights[MAXDYNLIGHTS];// static lights that affected this face (255 = no lights) + float texofs[2]; // conveyor offsets + mcubemap_t *cubemap[2]; // for environment reflective bump-mapping + float lerpFactor; + short subtexture[8]; // MAX_REF_STACK + word cintexturenum; // cinematic handle + word lightmaptexturenum; // custom lightmap number + word lastRenderMode; // for catch change render modes + word checkcount; // used for cin textures +// again matched with engine declaration + struct grasshdr_s *grass; // grass that linked by this surface + unsigned short grasscount; // number of bushes per polygon (used to determine total VBO size) + unsigned short numverts; // world->vertexes[] + int firstvertex; // fisrt look up in tr.tbn_vectors[], then acess to world->vertexes[] +// reserved fields (32 * sizeof( int )) + unsigned int query; // test surface for occluding (active if query != 0) +// shader cache + shader_t forwardScene[2]; // two for mirrors + shader_t forwardLightSpot; + shader_t forwardLightOmni; + shader_t forwardLightProj; + shader_t deferredScene; + shader_t deferredLight; + shader_t forwardDepth; + + struct brushdecal_s *pdecals; // linked decals + int reserved[22]; // just for future expansions or mod-makers +} mextrasurf_t; + +typedef struct msurface_s +{ + int visframe; // should be drawn when node is crossed + + mplane_t *plane; // pointer to shared plane + int flags; // see SURF_ #defines + + int firstedge; // look up in model->surfedges[], negative numbers + int numedges; // are backwards edges + + short texturemins[2]; + short extents[2]; + + int light_s, light_t; // gl lightmap coordinates + + glpoly_t *polys; // multiple if warped + struct msurface_s *texturechain; + + mtexinfo_t *texinfo; + + // lighting info + int dlightframe; // last frame the surface was checked by an animated light + int dlightbits; // dynamically generated. Indicates if the surface illumination + // is modified by an animated light. + + int lightmaptexturenum; + byte styles[MAXLIGHTMAPS]; + int cached_light[MAXLIGHTMAPS]; // values currently used in lightmap + mextrasurf_t *info; // pointer to surface extradata (was cached_dlight) + + color24 *samples; // note: this is the actual lightmap data for this surface + decal_t *pdecals; +} msurface_t; + +typedef struct hull_s +{ + dclipnode_t *clipnodes; + mplane_t *planes; + int firstclipnode; + int lastclipnode; + vec3_t clip_mins; + vec3_t clip_maxs; +} hull_t; + +#ifndef CACHE_USER +#define CACHE_USER +typedef struct cache_user_s +{ + void *data; // extradata +} cache_user_t; +#endif + +typedef struct model_s +{ + char name[64]; // model name + qboolean needload; // bmodels and sprites don't cache normally + + // shared modelinfo + modtype_t type; // model type + int numframes; // sprite's framecount + byte *mempool; // private mempool (was synctype) + int flags; // hl compatibility + +// +// volume occupied by the model +// + vec3_t mins, maxs; // bounding box at angles '0 0 0' + float radius; + + // brush model + union + { + int firstmodelsurface; + unsigned long modelCRC; + }; + int nummodelsurfaces; + + int numsubmodels; + dmodel_t *submodels; // or studio animations + + int numplanes; + mplane_t *planes; // bsp or studio collision planes + + int numleafs; // number of visible leafs, not counting 0 + mleaf_t *leafs; + + int numvertexes; + mvertex_t *vertexes; + + int numedges; + medge_t *edges; + + int numnodes; + mnode_t *nodes; + + int numtexinfo; + mtexinfo_t *texinfo; + + int numsurfaces; + msurface_t *surfaces; + + int numsurfedges; + union + { + int *surfedges; + CMeshDesc *bodymesh; // collision mesh for studiomodel + }; + + int numclipnodes; + dclipnode_t *clipnodes; + + int nummarksurfaces; + msurface_t **marksurfaces; + + hull_t hulls[MAX_MAP_HULLS]; + + int numtextures; + texture_t **textures; // BSP textures or remap textures + + union + { + byte *visdata; + struct mposebone_s *poseToBone; + }; + + union + { + color24 *lightdata; + struct mvbocache_s *studiocache; // pointer to VBO-prepared model (only for mod_studio) + }; + + union + { + char *entities; + struct mstudiomat_s *materials; // studio materials (only for mod_studio) + }; +// +// additional model data +// + cache_user_t cache; // only access through Mod_Extradata +} model_t; + +typedef struct alight_s +{ + int ambientlight; // clip at 128 + int shadelight; // clip at 192 - ambientlight + vec3_t color; + float *plightvec; +} alight_t; + +typedef struct auxvert_s +{ + float fv[3]; // viewspace x, y +} auxvert_t; + +#define MAX_SCOREBOARDNAME 32 +#define MAX_INFO_STRING 256 + +#include "custom.h" + +typedef struct player_info_s +{ + int userid; // User id on server + char userinfo[MAX_INFO_STRING]; // User info string + char name[MAX_SCOREBOARDNAME]; // Name (extracted from userinfo) + int spectator; // Spectator or not, unused + + int ping; + int packet_loss; + + // skin information + char model[64]; + int topcolor; + int bottomcolor; + + // last frame rendered + int renderframe; + + // Gait frame estimation + int gaitsequence; + float gaitframe; + float gaityaw; + vec3_t prevgaitorigin; + + customization_t customdata; +} player_info_t; + +// +// sprite representation in memory +// +typedef enum { SPR_SINGLE = 0, SPR_GROUP, SPR_ANGLED } spriteframetype_t; + +typedef struct mspriteframe_s +{ + int width; + int height; + float up, down, left, right; + int gl_texturenum; +} mspriteframe_t; + +typedef struct +{ + int numframes; + float *intervals; + mspriteframe_t *frames[1]; +} mspritegroup_t; + +typedef struct +{ + spriteframetype_t type; + mspriteframe_t *frameptr; +} mspriteframedesc_t; + +typedef struct +{ + short type; + short texFormat; + int maxwidth; + int maxheight; + int numframes; + int radius; + int facecull; + int synctype; + mspriteframedesc_t frames[1]; +} msprite_t; + +#endif//COM_MODEL_H \ No newline at end of file diff --git a/common/con_nprint.h b/common/con_nprint.h new file mode 100644 index 0000000..caf4000 --- /dev/null +++ b/common/con_nprint.h @@ -0,0 +1,31 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( CON_NPRINTH ) +#define CON_NPRINTH +#ifdef _WIN32 +#pragma once +#endif + +typedef struct con_nprint_s +{ + int index; // Row # + float time_to_live; // # of seconds before it dissappears + float color[ 3 ]; // RGB colors ( 0.0 -> 1.0 scale ) +} con_nprint_t; + +void Con_NPrintf( int idx, char *fmt, ... ); +void Con_NXPrintf( struct con_nprint_s *info, char *fmt, ... ); + +#endif diff --git a/common/const.h b/common/const.h new file mode 100644 index 0000000..59344a4 --- /dev/null +++ b/common/const.h @@ -0,0 +1,770 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef CONST_H +#define CONST_H +// +// Constants shared by the engine and dlls +// This header file included by engine files and DLL files. +// Most came from server.h + +// edict->flags +#define FL_FLY (1<<0) // Changes the SV_Movestep() behavior to not need to be on ground +#define FL_SWIM (1<<1) // Changes the SV_Movestep() behavior to not need to be on ground (but stay in water) +#define FL_CONVEYOR (1<<2) +#define FL_CLIENT (1<<3) +#define FL_INWATER (1<<4) +#define FL_MONSTER (1<<5) +#define FL_GODMODE (1<<6) +#define FL_NOTARGET (1<<7) +#define FL_SKIPLOCALHOST (1<<8) // Don't send entity to local host, it's predicting this entity itself +#define FL_ONGROUND (1<<9) // At rest / on the ground +#define FL_PARTIALGROUND (1<<10) // not all corners are valid +#define FL_WATERJUMP (1<<11) // player jumping out of water +#define FL_FROZEN (1<<12) // Player is frozen for 3rd person camera +#define FL_FAKECLIENT (1<<13) // JAC: fake client, simulated server side; don't send network messages to them +#define FL_DUCKING (1<<14) // Player flag -- Player is fully crouched +#define FL_FLOAT (1<<15) // Apply floating force to this entity when in water +#define FL_GRAPHED (1<<16) // worldgraph has this ent listed as something that blocks a connection + +// UNDONE: Do we need these? +#define FL_IMMUNE_WATER (1<<17) +#define FL_IMMUNE_SLIME (1<<18) +#define FL_IMMUNE_LAVA (1<<19) + +#define FL_PROXY (1<<20) // This is a spectator proxy +#define FL_ALWAYSTHINK (1<<21) // Brush model flag -- call think every frame regardless of nextthink - ltime (for constantly changing velocity/path) +#define FL_BASEVELOCITY (1<<22) // Base velocity has been applied this frame (used to convert base velocity into momentum) +#define FL_MONSTERCLIP (1<<23) // Only collide in with monsters who have FL_MONSTERCLIP set +#define FL_ONTRAIN (1<<24) // Player is _controlling_ a train, so movement commands should be ignored on client during prediction. +#define FL_WORLDBRUSH (1<<25) // Not moveable/removeable brush entity (really part of the world, but represented as an entity for transparency or something) +#define FL_SPECTATOR (1<<26) // This client is a spectator, don't run touch functions, etc. +#define FL_STUCKED (1<<27) // Client or monster that stuck in the BSP-geometry or convex hull + +#define FL_CUSTOMENTITY (1<<29) // This is a custom entity +#define FL_KILLME (1<<30) // This entity is marked for death -- This allows the engine to kill ents at the appropriate time +#define FL_DORMANT (1<<31) // Entity is dormant, no updates to client + +// Goes into globalvars_t.trace_flags +#define FTRACE_SIMPLEBOX (1<<0) // Traceline with a simple box +#define FTRACE_IGNORE_GLASS (1<<1) // traceline will be ignored entities with rendermode != kRenderNormal +#define FTRACE_IGNORE_ALPHATEST (1<<2) // all the studio models with transparent surfaces will be traced like normal surfaces + +// walkmove modes +#define WALKMOVE_NORMAL 0 // normal walkmove +#define WALKMOVE_WORLDONLY 1 // doesn't hit ANY entities, no matter what the solid type +#define WALKMOVE_CHECKONLY 2 // move, but don't touch triggers + +// edict->movetype values +#define MOVETYPE_NONE 0 // never moves +//#define MOVETYPE_ANGLENOCLIP 1 +//#define MOVETYPE_ANGLECLIP 2 +#define MOVETYPE_WALK 3 // Player only - moving on the ground +#define MOVETYPE_STEP 4 // gravity, special edge handling -- monsters use this +#define MOVETYPE_FLY 5 // No gravity, but still collides with stuff +#define MOVETYPE_TOSS 6 // gravity/collisions +#define MOVETYPE_PUSH 7 // no clip to world, push and crush +#define MOVETYPE_NOCLIP 8 // No gravity, no collisions, still do velocity/avelocity +#define MOVETYPE_FLYMISSILE 9 // extra size to monsters +#define MOVETYPE_BOUNCE 10 // Just like Toss, but reflect velocity when contacting surfaces +#define MOVETYPE_BOUNCEMISSILE 11 // bounce w/o gravity +#define MOVETYPE_FOLLOW 12 // track movement of aiment +#define MOVETYPE_PUSHSTEP 13 // BSP model that needs physics/world collisions (uses nearest hull for world collision) +#define MOVETYPE_COMPOUND 14 // glue two entities together (simple movewith) + +// edict->solid values +// NOTE: Some movetypes will cause collisions independent of SOLID_NOT/SOLID_TRIGGER when the entity moves +// SOLID only effects OTHER entities colliding with this one when they move - UGH! +#define SOLID_NOT 0 // no interaction with other objects +#define SOLID_TRIGGER 1 // touch on edge, but not blocking +#define SOLID_BBOX 2 // touch on edge, block +#define SOLID_SLIDEBOX 3 // touch on edge, but not an onground +#define SOLID_BSP 4 // bsp clip, touch on edge, block +#define SOLID_CUSTOM 5 // call external callbacks for tracing + +// edict->deadflag values +#define DEAD_NO 0 // alive +#define DEAD_DYING 1 // playing death animation or still falling off of a ledge waiting to hit ground +#define DEAD_DEAD 2 // dead. lying still. +#define DEAD_RESPAWNABLE 3 +#define DEAD_DISCARDBODY 4 + +#define DAMAGE_NO 0 +#define DAMAGE_YES 1 +#define DAMAGE_AIM 2 + +// entity effects +#define EF_BRIGHTFIELD (1<<0) // swirling cloud of particles +#define EF_MUZZLEFLASH (1<<1) // single frame ELIGHT on entity attachment 0 +#define EF_BRIGHTLIGHT (1<<2) // DLIGHT centered at entity origin +#define EF_DIMLIGHT (1<<3) // player flashlight +#define EF_INVLIGHT (1<<4) // get lighting from ceiling +#define EF_NOINTERP (1<<5) // don't interpolate the next frame +#define EF_LIGHT (1<<6) // rocket flare glow sprite +#define EF_NODRAW (1<<7) // don't draw entity +#define EF_NODEPTHTEST (1<<8) // hack for weapon muzzleflashes + +#define EF_SCREENMOVIE (1<<11) // marker for func_screenmovie +#define EF_NOBUMP (1<<12) // no bump for this entity or light source +#define EF_LENSFLARE (1<<13) // enable lensflare effect for dynamic light + +#define EF_NOREFLECT (1<<24) // Entity won't reflecting in mirrors +#define EF_REFLECTONLY (1<<25) // Entity will be drawing only in mirrors +#define EF_WATERSIDES (1<<26) // Do not remove sides for func_water entity +#define EF_FULLBRIGHT (1<<27) // Just get fullbright +#define EF_NOSHADOW (1<<28) // ignore shadow for this entity or light source +#define EF_MERGE_VISIBILITY (1<<29) // this entity allowed to merge vis (e.g. env_sky or portal camera) +#define EF_REQUEST_PHS (1<<30) // This entity requested phs bitvector instead of pvsbitvector in AddToFullPack calls +// g-cont. one reserved bit here for me + +// custom flags for entities +#define CF_STATIC_ENTITY (1<<0) // this entity is completely static (non-moving brush or env_static) +#define CF_STATIC_LIGHTMAPPED (1<<1) // this entity have a baked lightmaps + +// custom flags for func_screenmovie +#define CF_LOOPED_MOVIE (1<<0) // loop movie at end point +#define CF_MONOCHROME (1<<1) // use monocrhome image +#define CF_MOVIE_SOUND (1<<2) // allow sound + +// entity flags +#define EFLAG_SLERP 1 // do studio interpolation of this entity + +// landscape +#define MAX_LANDSCAPE_LAYERS 16 // fundamental limit, don't modify + +// +// temp entity events +// +#define TE_BEAMPOINTS 0 // beam effect between two points +// coord coord coord (start position) +// coord coord coord (end position) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_BEAMENTPOINT 1 // beam effect between point and entity +// short (start entity) +// coord coord coord (end position) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_GUNSHOT 2 // particle effect plus ricochet sound +// coord coord coord (position) + +#define TE_EXPLOSION 3 // additive sprite, 2 dynamic lights, flickering particles, explosion sound, move vertically 8 pps +// coord coord coord (position) +// short (sprite index) +// byte (scale in 0.1's) +// byte (framerate) +// byte (flags) +// +// The Explosion effect has some flags to control performance/aesthetic features: +#define TE_EXPLFLAG_NONE 0 // all flags clear makes default Half-Life explosion +#define TE_EXPLFLAG_NOADDITIVE 1 // sprite will be drawn opaque (ensure that the sprite you send is a non-additive sprite) +#define TE_EXPLFLAG_NODLIGHTS 2 // do not render dynamic lights +#define TE_EXPLFLAG_NOSOUND 4 // do not play client explosion sound +#define TE_EXPLFLAG_NOPARTICLES 8 // do not draw particles +#define TE_EXPLFLAG_DRAWALPHA 16 // sprite will be drawn alpha +#define TE_EXPLFLAG_ROTATE 32 // rotate the sprite randomly + +#define TE_TAREXPLOSION 4 // Quake1 "tarbaby" explosion with sound +// coord coord coord (position) + +#define TE_SMOKE 5 // alphablend sprite, move vertically 30 pps +// coord coord coord (position) +// short (sprite index) +// byte (scale in 0.1's) +// byte (framerate) + +#define TE_TRACER 6 // tracer effect from point to point +// coord, coord, coord (start) +// coord, coord, coord (end) + +#define TE_LIGHTNING 7 // TE_BEAMPOINTS with simplified parameters +// coord, coord, coord (start) +// coord, coord, coord (end) +// byte (life in 0.1's) +// byte (width in 0.1's) +// byte (amplitude in 0.01's) +// short (sprite model index) + +#define TE_BEAMENTS 8 +// short (start entity) +// short (end entity) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_SPARKS 9 // 8 random tracers with gravity, ricochet sprite +// coord coord coord (position) + +#define TE_LAVASPLASH 10 // Quake1 lava splash +// coord coord coord (position) + +#define TE_TELEPORT 11 // Quake1 teleport splash +// coord coord coord (position) + +#define TE_EXPLOSION2 12 // Quake1 colormaped (base palette) particle explosion with sound +// coord coord coord (position) +// byte (starting color) +// byte (num colors) + +#define TE_BSPDECAL 13 // Decal from the .BSP file +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// short (texture index of precached decal texture name) +// short (entity index) +// [optional - only included if previous short is non-zero (not the world)] short (index of model of above entity) + +#define TE_IMPLOSION 14 // tracers moving toward a point +// coord, coord, coord (position) +// byte (radius) +// byte (count) +// byte (life in 0.1's) + +#define TE_SPRITETRAIL 15 // line of moving glow sprites with gravity, fadeout, and collisions +// coord, coord, coord (start) +// coord, coord, coord (end) +// short (sprite index) +// byte (count) +// byte (life in 0.1's) +// byte (scale in 0.1's) +// byte (velocity along vector in 10's) +// byte (randomness of velocity in 10's) + +#define TE_BEAM 16 // obsolete + +#define TE_SPRITE 17 // additive sprite, plays 1 cycle +// coord, coord, coord (position) +// short (sprite index) +// byte (scale in 0.1's) +// byte (brightness) + +#define TE_BEAMSPRITE 18 // A beam with a sprite at the end +// coord, coord, coord (start position) +// coord, coord, coord (end position) +// short (beam sprite index) +// short (end sprite index) + +#define TE_BEAMTORUS 19 // screen aligned beam ring, expands to max radius over lifetime +// coord coord coord (center position) +// coord coord coord (axis and radius) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_BEAMDISK 20 // disk that expands to max radius over lifetime +// coord coord coord (center position) +// coord coord coord (axis and radius) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_BEAMCYLINDER 21 // cylinder that expands to max radius over lifetime +// coord coord coord (center position) +// coord coord coord (axis and radius) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_BEAMFOLLOW 22 // create a line of decaying beam segments until entity stops moving +// short (entity:attachment to follow) +// short (sprite index) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte,byte,byte (color) +// byte (brightness) + +#define TE_GLOWSPRITE 23 +// coord, coord, coord (pos) short (model index) byte (scale / 10) + +#define TE_BEAMRING 24 // connect a beam ring to two entities +// short (start entity) +// short (end entity) +// short (sprite index) +// byte (starting frame) +// byte (frame rate in 0.1's) +// byte (life in 0.1's) +// byte (line width in 0.1's) +// byte (noise amplitude in 0.01's) +// byte,byte,byte (color) +// byte (brightness) +// byte (scroll speed in 0.1's) + +#define TE_STREAK_SPLASH 25 // oriented shower of tracers +// coord coord coord (start position) +// coord coord coord (direction vector) +// byte (color) +// short (count) +// short (base speed) +// short (random velocity) + +#define TE_BEAMHOSE 26 // obsolete + +#define TE_DLIGHT 27 // dynamic light, effect world, minor entity effect +// coord, coord, coord (pos) +// byte (radius in 10's) +// byte byte byte (color) +// byte (life in 10's) +// byte (decay rate in 10's) + +#define TE_ELIGHT 28 // point entity light, no world effect +// short (entity:attachment to follow) +// coord coord coord (initial position) +// coord (radius) +// byte byte byte (color) +// byte (life in 0.1's) +// coord (decay rate) + +#define TE_TEXTMESSAGE 29 +// short 1.2.13 x (-1 = center) +// short 1.2.13 y (-1 = center) +// byte Effect 0 = fade in/fade out +// 1 is flickery credits +// 2 is write out (training room) +// 4 bytes r,g,b,a color1 (text color) +// 4 bytes r,g,b,a color2 (effect color) +// ushort 8.8 fadein time +// ushort 8.8 fadeout time +// ushort 8.8 hold time +// optional ushort 8.8 fxtime (time the highlight lags behing the leading text in effect 2) +// string text message (512 chars max sz string) +#define TE_LINE 30 +// coord, coord, coord startpos +// coord, coord, coord endpos +// short life in 0.1 s +// 3 bytes r, g, b + +#define TE_BOX 31 +// coord, coord, coord boxmins +// coord, coord, coord boxmaxs +// short life in 0.1 s +// 3 bytes r, g, b + +#define TE_KILLBEAM 99 // kill all beams attached to entity +// short (entity) + +#define TE_LARGEFUNNEL 100 +// coord coord coord (funnel position) +// short (sprite index) +// short (flags) + +#define TE_BLOODSTREAM 101 // particle spray +// coord coord coord (start position) +// coord coord coord (spray vector) +// byte (color) +// byte (speed) + +#define TE_SHOWLINE 102 // line of particles every 5 units, dies in 30 seconds +// coord coord coord (start position) +// coord coord coord (end position) + +#define TE_BLOOD 103 // particle spray +// coord coord coord (start position) +// coord coord coord (spray vector) +// byte (color) +// byte (speed) + +#define TE_DECAL 104 // Decal applied to a brush entity (not the world) +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// byte (texture index of precached decal texture name) +// short (entity index) + +#define TE_FIZZ 105 // create alpha sprites inside of entity, float upwards +// short (entity) +// short (sprite index) +// byte (density) + +#define TE_MODEL 106 // create a moving model that bounces and makes a sound when it hits +// coord, coord, coord (position) +// coord, coord, coord (velocity) +// angle (initial yaw) +// short (model index) +// byte (bounce sound type) +// byte (life in 0.1's) + +#define TE_EXPLODEMODEL 107 // spherical shower of models, picks from set +// coord, coord, coord (origin) +// coord (velocity) +// short (model index) +// short (count) +// byte (life in 0.1's) + +#define TE_BREAKMODEL 108 // box of models or sprites +// coord, coord, coord (position) +// coord, coord, coord (size) +// coord, coord, coord (velocity) +// byte (random velocity in 10's) +// short (sprite or model index) +// byte (count) +// byte (life in 0.1 secs) +// byte (flags) + +#define TE_GUNSHOTDECAL 109 // decal and ricochet sound +// coord, coord, coord (position) +// short (entity index???) +// byte (decal???) + +#define TE_SPRITE_SPRAY 110 // spay of alpha sprites +// coord, coord, coord (position) +// coord, coord, coord (velocity) +// short (sprite index) +// byte (count) +// byte (speed) +// byte (noise) + +#define TE_ARMOR_RICOCHET 111 // quick spark sprite, client ricochet sound. +// coord, coord, coord (position) +// byte (scale in 0.1's) + +#define TE_PLAYERDECAL 112 // ??? +// byte (playerindex) +// coord, coord, coord (position) +// short (entity???) +// byte (decal number???) +// [optional] short (model index???) + +#define TE_BUBBLES 113 // create alpha sprites inside of box, float upwards +// coord, coord, coord (min start position) +// coord, coord, coord (max start position) +// coord (float height) +// short (model index) +// byte (count) +// coord (speed) + +#define TE_BUBBLETRAIL 114 // create alpha sprites along a line, float upwards +// coord, coord, coord (min start position) +// coord, coord, coord (max start position) +// coord (float height) +// short (model index) +// byte (count) +// coord (speed) + +#define TE_BLOODSPRITE 115 // spray of opaque sprite1's that fall, single sprite2 for 1..2 secs (this is a high-priority tent) +// coord, coord, coord (position) +// short (sprite1 index) +// short (sprite2 index) +// byte (color) +// byte (scale) + +#define TE_WORLDDECAL 116 // Decal applied to the world brush +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// byte (texture index of precached decal texture name) + +#define TE_WORLDDECALHIGH 117 // Decal (with texture index > 256) applied to world brush +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// byte (texture index of precached decal texture name - 256) + +#define TE_DECALHIGH 118 // Same as TE_DECAL, but the texture index was greater than 256 +// coord, coord, coord (x,y,z), decal position (center of texture in world) +// byte (texture index of precached decal texture name - 256) +// short (entity index) + +#define TE_PROJECTILE 119 // Makes a projectile (like a nail) (this is a high-priority tent) +// coord, coord, coord (position) +// coord, coord, coord (velocity) +// short (modelindex) +// byte (life) +// byte (owner) projectile won't collide with owner (if owner == 0, projectile will hit any client). + +#define TE_SPRAY 120 // Throws a shower of sprites or models +// coord, coord, coord (position) +// coord, coord, coord (direction) +// short (modelindex) +// byte (count) +// byte (speed) +// byte (noise) +// byte (rendermode) + +#define TE_PLAYERSPRITES 121 // sprites emit from a player's bounding box (ONLY use for players!) +// byte (playernum) +// short (sprite modelindex) +// byte (count) +// byte (variance) (0 = no variance in size) (10 = 10% variance in size) + +#define TE_PARTICLEBURST 122 // very similar to lavasplash. +// coord (origin) +// short (radius) +// byte (particle color) +// byte (duration * 10) (will be randomized a bit) + +#define TE_FIREFIELD 123 // makes a field of fire. +// coord (origin) +// short (radius) (fire is made in a square around origin. -radius, -radius to radius, radius) +// short (modelindex) +// byte (count) +// byte (flags) +// byte (duration (in seconds) * 10) (will be randomized a bit) +// +// to keep network traffic low, this message has associated flags that fit into a byte: +#define TEFIRE_FLAG_ALLFLOAT 1 // all sprites will drift upwards as they animate +#define TEFIRE_FLAG_SOMEFLOAT 2 // some of the sprites will drift upwards. (50% chance) +#define TEFIRE_FLAG_LOOP 4 // if set, sprite plays at 15 fps, otherwise plays at whatever rate stretches the animation over the sprite's duration. +#define TEFIRE_FLAG_ALPHA 8 // if set, sprite is rendered alpha blended at 50% else, opaque +#define TEFIRE_FLAG_PLANAR 16 // if set, all fire sprites have same initial Z instead of randomly filling a cube. + +#define TE_PLAYERATTACHMENT 124 // attaches a TENT to a player (this is a high-priority tent) +// byte (entity index of player) +// coord (vertical offset) ( attachment origin.z = player origin.z + vertical offset ) +// short (model index) +// short (life * 10 ); + +#define TE_KILLPLAYERATTACHMENTS 125 // will expire all TENTS attached to a player. +// byte (entity index of player) + +#define TE_MULTIGUNSHOT 126 // much more compact shotgun message +// This message is used to make a client approximate a 'spray' of gunfire. +// Any weapon that fires more than one bullet per frame and fires in a bit of a spread is +// a good candidate for MULTIGUNSHOT use. (shotguns) +// +// NOTE: This effect makes the client do traces for each bullet, these client traces ignore +// entities that have studio models.Traces are 4096 long. +// +// coord (origin) +// coord (origin) +// coord (origin) +// coord (direction) +// coord (direction) +// coord (direction) +// coord (x noise * 100) +// coord (y noise * 100) +// byte (count) +// byte (bullethole decal texture index) + +#define TE_USERTRACER 127 // larger message than the standard tracer, but allows some customization. +// coord (origin) +// coord (origin) +// coord (origin) +// coord (velocity) +// coord (velocity) +// coord (velocity) +// byte ( life * 10 ) +// byte ( color ) this is an index into an array of color vectors in the engine. (0 - ) +// byte ( length * 10 ) + +#define MSG_BROADCAST 0 // unreliable to all +#define MSG_ONE 1 // reliable to one (msg_entity) +#define MSG_ALL 2 // reliable to all +#define MSG_INIT 3 // write to the init string +#define MSG_PVS 4 // Ents in PVS of org +#define MSG_PAS 5 // Ents in PAS of org +#define MSG_PVS_R 6 // Reliable to PVS +#define MSG_PAS_R 7 // Reliable to PAS +#define MSG_ONE_UNRELIABLE 8 // Send to one client, but don't put in reliable stream, put in unreliable datagram ( could be dropped ) +#define MSG_SPEC 9 // Sends to all spectator proxies + +// channels +#define CHAN_AUTO 0 +#define CHAN_WEAPON 1 +#define CHAN_VOICE 2 +#define CHAN_ITEM 3 +#define CHAN_BODY 4 +#define CHAN_STREAM 5 // allocate stream channel from the static or dynamic area +#define CHAN_STATIC 6 // allocate channel from the static area +#define CHAN_NETWORKVOICE_BASE 7 // voice data coming across the network +#define CHAN_NETWORKVOICE_END 500 // network voice data reserves slots (CHAN_NETWORKVOICE_BASE through CHAN_NETWORKVOICE_END). + +// attenuation values +#define ATTN_NONE 0 +#define ATTN_NORM (float)0.8 +#define ATTN_IDLE (float)2 +#define ATTN_STATIC (float)1.25 + +// pitch values +#define PITCH_NORM 100 // non-pitch shifted +#define PITCH_LOW 95 // other values are possible - 0-255, where 255 is very high +#define PITCH_HIGH 120 + +// volume values +#define VOL_NORM 1.0 + +// plats +#define PLAT_LOW_TRIGGER 1 + +// Trains +#define SF_TRAIN_WAIT_RETRIGGER 1 +#define SF_TRAIN_START_ON 4 // Train is initially moving +#define SF_TRAIN_PASSABLE 8 // Train is not solid -- used to make water trains + +// buttons +#define IN_ATTACK (1<<0) +#define IN_JUMP (1<<1) +#define IN_DUCK (1<<2) +#define IN_FORWARD (1<<3) +#define IN_BACK (1<<4) +#define IN_USE (1<<5) +#define IN_CANCEL (1<<6) +#define IN_LEFT (1<<7) +#define IN_RIGHT (1<<8) +#define IN_MOVELEFT (1<<9) +#define IN_MOVERIGHT (1<<10) +#define IN_ATTACK2 (1<<11) +#define IN_RUN (1<<12) +#define IN_RELOAD (1<<13) +#define IN_ALT1 (1<<14) +#define IN_SCORE (1<<15) // Used by client.dll for when scoreboard is held down + +// Break Model Defines +#define BREAK_TYPEMASK 0x4F +#define BREAK_GLASS 0x01 +#define BREAK_METAL 0x02 +#define BREAK_FLESH 0x04 +#define BREAK_WOOD 0x08 +#define BREAK_SMOKE 0x10 +#define BREAK_TRANS 0x20 +#define BREAK_CONCRETE 0x40 +#define BREAK_2 0x80 + +// Colliding temp entity sounds +#define BOUNCE_GLASS BREAK_GLASS +#define BOUNCE_METAL BREAK_METAL +#define BOUNCE_FLESH BREAK_FLESH +#define BOUNCE_WOOD BREAK_WOOD +#define BOUNCE_SHRAP 0x10 +#define BOUNCE_SHELL 0x20 +#define BOUNCE_CONCRETE BREAK_CONCRETE +#define BOUNCE_SHOTSHELL 0x80 + +// Temp entity bounce sound types +#define TE_BOUNCE_NULL 0 +#define TE_BOUNCE_SHELL 1 +#define TE_BOUNCE_SHOTSHELL 2 + +// Rendering constants +enum +{ + kRenderNormal, // src + kRenderTransColor, // c*a+dest*(1-a) + kRenderTransTexture, // src*a+dest*(1-a) + kRenderGlow, // src*a+dest -- No Z buffer checks + kRenderTransAlpha, // src*srca+dest*(1-srca) + kRenderTransAdd, // src*a+dest +}; + +enum +{ + kRenderFxNone = 0, + kRenderFxPulseSlow, + kRenderFxPulseFast, + kRenderFxPulseSlowWide, + kRenderFxPulseFastWide, + kRenderFxFadeSlow, + kRenderFxFadeFast, + kRenderFxSolidSlow, + kRenderFxSolidFast, + kRenderFxStrobeSlow, + kRenderFxStrobeFast, + kRenderFxStrobeFaster, + kRenderFxFlickerSlow, + kRenderFxFlickerFast, + kRenderFxNoDissipation, + kRenderFxDistort, // Distort/scale/translate flicker + kRenderFxHologram, // kRenderFxDistort + distance fade + kRenderFxDeadPlayer, // kRenderAmt is the player index + kRenderFxExplode, // Scale up really big! + kRenderFxGlowShell, // Glowing Shell + kRenderFxClampMinScale, // Keep this sprite from getting very small (SPRITES only!) +}; + +typedef int func_t; +typedef int string_t; + +typedef unsigned char byte; +typedef unsigned short word; +typedef unsigned int uint; + +#undef true +#undef false + +#ifndef __cplusplus +typedef enum { false, true } qboolean; +#else +typedef int qboolean; +#endif + +#include "color24.h" +#include "lightlimits.h" + +typedef struct +{ + unsigned r, g, b, a; +} colorVec; + +typedef struct link_s +{ + struct link_s *prev, *next; +} link_t; + +typedef struct edict_s edict_t; + +typedef struct plane_s +{ + vec3_t normal; + float dist; +} plane_t; + +typedef struct +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + qboolean inopen, inwater; + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + plane_t plane; // surface normal at impact + edict_t *ent; // entity the surface is on + + union + { + int hitgroup; // 0 == generic, non zero is specific body part + struct mstudiomat_s *surf; // only if ent->v.solid == SOLID_CUSTOM! + }; +} trace_t; + +#endif//CONST_H \ No newline at end of file diff --git a/common/crc.h b/common/crc.h new file mode 100644 index 0000000..15fdb0d --- /dev/null +++ b/common/crc.h @@ -0,0 +1,52 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* crc.h */ +#ifndef CRC_H +#define CRC_H +#ifdef _WIN32 +#pragma once +#endif + +// MD5 Hash +typedef struct +{ + unsigned int buf[4]; + unsigned int bits[2]; + unsigned char in[64]; +} MD5Context_t; + + +typedef unsigned long CRC32_t; +void CRC32_Init(CRC32_t *pulCRC); +CRC32_t CRC32_Final(CRC32_t pulCRC); +void CRC32_ProcessBuffer(CRC32_t *pulCRC, void *p, int len); +void CRC32_ProcessByte(CRC32_t *pulCRC, unsigned char ch); +int CRC_File(CRC32_t *crcvalue, char *pszFileName); + +unsigned char COM_BlockSequenceCRCByte (unsigned char *base, int length, int sequence); + +void MD5Init(MD5Context_t *context); +void MD5Update(MD5Context_t *context, unsigned char const *buf, + unsigned int len); +void MD5Final(unsigned char digest[16], MD5Context_t *context); +void Transform(unsigned int buf[4], unsigned int const in[16]); + +int MD5_Hash_File(unsigned char digest[16], char *pszFileName, int bUsefopen, int bSeed, unsigned int seed[4]); +char *MD5_Print(unsigned char hash[16]); +int MD5_Hash_CachedFile(unsigned char digest[16], unsigned char *pCache, int nFileSize, int bSeed, unsigned int seed[4]); + +int CRC_MapFile(CRC32_t *crcvalue, char *pszFileName); + +#endif diff --git a/common/cvardef.h b/common/cvardef.h new file mode 100644 index 0000000..7c4dcce --- /dev/null +++ b/common/cvardef.h @@ -0,0 +1,45 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef CVARDEF_H +#define CVARDEF_H + +#define FCVAR_ARCHIVE (1<<0) // set to cause it to be saved to vars.rc +#define FCVAR_USERINFO (1<<1) // changes the client's info string +#define FCVAR_SERVER (1<<2) // notifies players when changed +#define FCVAR_EXTDLL (1<<3) // defined by external DLL +#define FCVAR_CLIENTDLL (1<<4) // defined by the client dll +#define FCVAR_PROTECTED (1<<5) // It's a server cvar, but we don't send the data since it's a password, etc. Sends 1 if it's not bland/zero, 0 otherwise as value +#define FCVAR_SPONLY (1<<6) // This cvar cannot be changed by clients connected to a multiplayer server. +#define FCVAR_PRINTABLEONLY (1<<7) // This cvar's string cannot contain unprintable characters ( e.g., used for player name etc ). +#define FCVAR_UNLOGGED (1<<8) // If this is a FCVAR_SERVER, don't log changes to the log file / console if we are creating a log +#define FCVAR_NOEXTRAWHITEPACE (1<<9) // strip trailing/leading white space from this cvar + +#define FCVAR_MOVEVARS (1<<10) // this cvar is a part of movevars_t struct that shared between client and server +#define FCVAR_LATCH (1<<11) // notify client what this cvar will be applied only after server restart (but don't does more nothing) +#define FCVAR_GLCONFIG (1<<12) // write it into opengl.cfg +#define FCVAR_CHANGED (1<<13) // set each time the cvar is changed +#define FCVAR_GAMEUIDLL (1<<14) // defined by the menu DLL +#define FCVAR_CHEAT (1<<15) // can not be changed if cheats are disabled + +typedef struct cvar_s +{ + char *name; + char *string; + int flags; + float value; + struct cvar_s *next; +} cvar_t; + +#endif//CVARDEF_H \ No newline at end of file diff --git a/common/demo_api.h b/common/demo_api.h new file mode 100644 index 0000000..be20cc1 --- /dev/null +++ b/common/demo_api.h @@ -0,0 +1,31 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined ( DEMO_APIH ) +#define DEMO_APIH +#ifdef _WIN32 +#pragma once +#endif + +typedef struct demo_api_s +{ + int ( *IsRecording ) ( void ); + int ( *IsPlayingback ) ( void ); + int ( *IsTimeDemo ) ( void ); + void ( *WriteBuffer ) ( int size, unsigned char *buffer ); +} demo_api_t; + +extern demo_api_t demoapi; + +#endif diff --git a/common/director_cmds.h b/common/director_cmds.h new file mode 100644 index 0000000..da99929 --- /dev/null +++ b/common/director_cmds.h @@ -0,0 +1,38 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// director_cmds.h +// sub commands for svc_director + +#define DRC_ACTIVE 0 // tells client that he's an spectator and will get director command +#define DRC_STATUS 1 // send status infos about proxy +#define DRC_CAMERA 2 // set the actual director camera position +#define DRC_EVENT 3 // informs the dircetor about ann important game event + + +#define DRC_FLAG_PRIO_MASK 0x0F // priorities between 0 and 15 (15 most important) +#define DRC_FLAG_SIDE (1<<4) +#define DRC_FLAG_DRAMATIC (1<<5) + + + +// commands of the director API function CallDirectorProc(...) + +#define DRCAPI_NOP 0 // no operation +#define DRCAPI_ACTIVE 1 // de/acivates director mode in engine +#define DRCAPI_STATUS 2 // request proxy information +#define DRCAPI_SETCAM 3 // set camera n to given position and angle +#define DRCAPI_GETCAM 4 // request camera n position and angle +#define DRCAPI_DIRPLAY 5 // set director time and play with normal speed +#define DRCAPI_DIRFREEZE 6 // freeze directo at this time +#define DRCAPI_SETVIEWMODE 7 // overview or 4 cameras +#define DRCAPI_SETOVERVIEWPARAMS 8 // sets parameter for overview mode +#define DRCAPI_SETFOCUS 9 // set the camera which has the input focus +#define DRCAPI_GETTARGETS 10 // queries engine for player list +#define DRCAPI_SETVIEWPOINTS 11 // gives engine all waypoints + + diff --git a/common/dlight.h b/common/dlight.h new file mode 100644 index 0000000..0abc857 --- /dev/null +++ b/common/dlight.h @@ -0,0 +1,33 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined ( DLIGHTH ) +#define DLIGHTH +#ifdef _WIN32 +#pragma once +#endif + +typedef struct dlight_s +{ + vec3_t origin; + float radius; + color24 color; + float die; // stop lighting after this time + float decay; // drop this each second + float minlight; // don't add when contributing less + int key; + qboolean dark; // subtracts light instead of adding +} dlight_t; + +#endif diff --git a/common/entity_state.h b/common/entity_state.h new file mode 100644 index 0000000..ef5d856 --- /dev/null +++ b/common/entity_state.h @@ -0,0 +1,193 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( ENTITY_STATEH ) +#define ENTITY_STATEH +#ifdef _WIN32 +#pragma once +#endif + +// For entityType below +#define ENTITY_NORMAL (1<<0) +#define ENTITY_BEAM (1<<1) + +// Entity state is used for the baseline and for delta compression of a packet of +// entities that is sent to a client. +typedef struct entity_state_s entity_state_t; + +struct entity_state_s +{ +// Fields which are filled in by routines outside of delta compression + int entityType; + // Index into cl_entities array for this entity. + int number; + float msg_time; + + // Message number last time the player/entity state was updated. + int messagenum; + + // Fields which can be transitted and reconstructed over the network stream + vec3_t origin; + vec3_t angles; + + int modelindex; + int sequence; + float frame; + int colormap; + short skin; + short solid; + int effects; + float scale; + + byte eflags; + + // Render information + int rendermode; + int renderamt; + color24 rendercolor; + int renderfx; + + int movetype; + float animtime; + float framerate; + int body; + byte controller[4]; + byte blending[4]; + vec3_t velocity; + + // Send bbox down to client for use during prediction. + vec3_t mins; + vec3_t maxs; + + int aiment; + // If owned by a player, the index of that player ( for projectiles ). + int owner; + + // Friction, for prediction. + float friction; + // Gravity multiplier + float gravity; + +// PLAYER SPECIFIC + int team; + int playerclass; + int health; + qboolean spectator; + int weaponmodel; + int gaitsequence; + // If standing on conveyor, e.g. + vec3_t basevelocity; + // Use the crouched hull, or the regular player hull. + int usehull; + // Latched buttons last time state updated. + int oldbuttons; + // -1 = in air, else pmove entity number + int onground; + int iStepLeft; + // How fast we are falling + float flFallVelocity; + + float fov; + int weaponanim; + + // Parametric movement overrides + vec3_t startpos; + vec3_t endpos; + float impacttime; + float starttime; + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; +}; + +#include "pm_info.h" + +typedef struct clientdata_s +{ + vec3_t origin; + vec3_t velocity; + + int viewmodel; + vec3_t punchangle; + int flags; + int waterlevel; + int watertype; + vec3_t view_ofs; + float health; + + int bInDuck; + + int weapons; // remove? + + int flTimeStepSound; + int flDuckTime; + int flSwimTime; + int waterjumptime; + + float maxspeed; + + float fov; + int weaponanim; + + int m_iId; + int ammo_shells; + int ammo_nails; + int ammo_cells; + int ammo_rockets; + float m_flNextAttack; + + int tfstate; + + int pushmsec; + + int deadflag; + + char physinfo[ MAX_PHYSINFO_STRING ]; + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; +} clientdata_t; + +#include "weaponinfo.h" + +typedef struct local_state_s +{ + entity_state_t playerstate; + clientdata_t client; + weapon_data_t weapondata[ 64 ]; +} local_state_t; + +#endif // !ENTITY_STATEH diff --git a/common/entity_types.h b/common/entity_types.h new file mode 100644 index 0000000..2ab96e3 --- /dev/null +++ b/common/entity_types.h @@ -0,0 +1,26 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// entity_types.h +#if !defined( ENTITY_TYPESH ) +#define ENTITY_TYPESH + +#define ET_NORMAL 0 +#define ET_PLAYER 1 +#define ET_TEMPENTITY 2 +#define ET_BEAM 3 +// BMODEL or SPRITE that was split across BSP nodes +#define ET_FRAGMENTED 4 + +#endif // !ENTITY_TYPESH diff --git a/common/event_api.h b/common/event_api.h new file mode 100644 index 0000000..aa01d0f --- /dev/null +++ b/common/event_api.h @@ -0,0 +1,58 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef EVENT_API_H +#define EVENT_API_H + +#define EVENT_API_VERSION 1 + +typedef struct event_api_s +{ + int version; + void ( *EV_PlaySound )( int ent, float *origin, int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch ); + void ( *EV_StopSound )( int ent, int channel, const char *sample ); + int ( *EV_FindModelIndex )( const char *pmodel ); + int ( *EV_IsLocal )( int playernum ); + int ( *EV_LocalPlayerDucking )( void ); + void ( *EV_LocalPlayerViewheight )( float * ); + void ( *EV_LocalPlayerBounds )( int hull, float *mins, float *maxs ); + int ( *EV_IndexFromTrace)( struct pmtrace_s *pTrace ); + struct physent_s *( *EV_GetPhysent )( int idx ); + void ( *EV_SetUpPlayerPrediction )( int dopred, int bIncludeLocalClient ); + void ( *EV_PushPMStates )( void ); + void ( *EV_PopPMStates )( void ); + void ( *EV_SetSolidPlayers )( int playernum ); + void ( *EV_SetTraceHull )( int hull ); + void ( *EV_PlayerTrace )( float *start, float *end, int traceFlags, int ignore_pe, struct pmtrace_s *tr ); + void ( *EV_WeaponAnimation )( int sequence, int body ); + unsigned short ( *EV_PrecacheEvent )( int type, const char* psz ); + void ( *EV_PlaybackEvent )( int flags, const struct edict_s *pInvoker, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); + const char *( *EV_TraceTexture )( int ground, float *vstart, float *vend ); + void ( *EV_StopAllSounds )( int entnum, int entchannel ); + void ( *EV_KillEvents )( int entnum, const char *eventname ); + + // Xash3D extension + void ( *EV_PlayerTraceExt )( float *start, float *end, int traceFlags, int (*pfnIgnore)( struct physent_s *pe ), struct pmtrace_s *tr ); + const char *(*EV_SoundForIndex)( int index ); + struct msurface_s *( *EV_TraceSurface )( int ground, float *vstart, float *vend ); + struct movevars_s *( *EV_GetMovevars )( void ); + struct pmtrace_s *( *EV_VisTraceLine )( float *start, float *end, int flags ); + struct physent_s *( *EV_GetVisent )( int idx ); + int ( *EV_TestLine)( const vec3_t start, const vec3_t end, int flags ); + void ( *EV_PushTraceBounds)( int hullnum, const float *mins, const float *maxs ); + void ( *EV_PopTraceBounds)( void ); +} event_api_t; + +#endif//EVENT_API_H \ No newline at end of file diff --git a/common/event_args.h b/common/event_args.h new file mode 100644 index 0000000..8c41076 --- /dev/null +++ b/common/event_args.h @@ -0,0 +1,50 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( EVENT_ARGSH ) +#define EVENT_ARGSH +#ifdef _WIN32 +#pragma once +#endif + +// Event was invoked with stated origin +#define FEVENT_ORIGIN ( 1<<0 ) + +// Event was invoked with stated angles +#define FEVENT_ANGLES ( 1<<1 ) + +typedef struct event_args_s +{ + int flags; + + // Transmitted + int entindex; + + float origin[3]; + float angles[3]; + float velocity[3]; + + int ducking; + + float fparam1; + float fparam2; + + int iparam1; + int iparam2; + + int bparam1; + int bparam2; +} event_args_t; + +#endif diff --git a/common/event_flags.h b/common/event_flags.h new file mode 100644 index 0000000..d601e43 --- /dev/null +++ b/common/event_flags.h @@ -0,0 +1,47 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( EVENT_FLAGSH ) +#define EVENT_FLAGSH +#ifdef _WIN32 +#pragma once +#endif + +// Skip local host for event send. +#define FEV_NOTHOST (1<<0) + +// Send the event reliably. You must specify the origin and angles and use +// PLAYBACK_EVENT_FULL for this to work correctly on the server for anything +// that depends on the event origin/angles. I.e., the origin/angles are not +// taken from the invoking edict for reliable events. +#define FEV_RELIABLE (1<<1) + +// Don't restrict to PAS/PVS, send this event to _everybody_ on the server ( useful for stopping CHAN_STATIC +// sounds started by client event when client is not in PVS anymore ( hwguy in TFC e.g. ). +#define FEV_GLOBAL (1<<2) + +// If this client already has one of these events in its queue, just update the event instead of sending it as a duplicate +// +#define FEV_UPDATE (1<<3) + +// Only send to entity specified as the invoker +#define FEV_HOSTONLY (1<<4) + +// Only send if the event was created on the server. +#define FEV_SERVER (1<<5) + +// Only issue event client side ( from shared code ) +#define FEV_CLIENT (1<<6) + +#endif diff --git a/common/features.h b/common/features.h new file mode 100644 index 0000000..dc65a48 --- /dev/null +++ b/common/features.h @@ -0,0 +1,30 @@ +/* +features.h - engine features that can be enabled by mod-maker request +Copyright (C) 2012 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef FEATURES_H +#define FEATURES_H + +// list of engine features that can be enabled through callback SV_CheckFeatures +#define ENGINE_WRITE_LARGE_COORD (1<<0) // replace standard message WRITE_COORD with big message for support more than 8192 units in world +// reserved +#define ENGINE_LOAD_DELUXEDATA (1<<2) // loading deluxemap for map (if present) +#define ENGINE_TRANSFORM_TRACE_AABB (1<<3) // transform trace bbox into local space of rotating bmodels +#define ENGINE_LARGE_LIGHTMAPS (1<<4) // change lightmap sizes from 128x128 to 256x256 +#define ENGINE_COMPENSATE_QUAKE_BUG (1<<5) // compensate stupid quake bug (inverse pitch) for mods where this bug is fixed +#define ENGINE_DISABLE_HDTEXTURES (1<<6) // disable support of HD-textures in case custom renderer have separate way to load them +#define ENGINE_COMPUTE_STUDIO_LERP (1<<7) // enable MOVETYPE_STEP lerping back in engine +#define ENGINE_FIXED_FRAMERATE (1<<8) // keep constant rate for client and server (but don't clamp renderer calls) + +#endif//FEATURES_H \ No newline at end of file diff --git a/common/gameinfo.h b/common/gameinfo.h new file mode 100644 index 0000000..020e920 --- /dev/null +++ b/common/gameinfo.h @@ -0,0 +1,49 @@ +/* +gameinfo.h - current game info +Copyright (C) 2010 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef GAMEINFO_H +#define GAMEINFO_H + +#define GFL_NOMODELS (1<<0) + +/* +======================================================================== + +GAMEINFO stuff + +internal shared gameinfo structure (readonly for engine parts) +======================================================================== +*/ +typedef struct +{ + // filesystem info + char gamefolder[64]; // used for change game '-game x' + char startmap[64]; // map to start singleplayer game + char trainmap[64]; // map to start hazard course (if specified) + char title[64]; // Game Main Title + char version[14]; // game version (optional) + short flags; // game flags + + // about mod info + char game_url[256]; // link to a developer's site + char update_url[256]; // link to updates page + char type[64]; // single, toolkit, multiplayer etc + char date[64]; + char size[64]; // displayed mod size + + int gamemode; +} GAMEINFO; + +#endif//GAMEINFO_H \ No newline at end of file diff --git a/common/hltv.h b/common/hltv.h new file mode 100644 index 0000000..ff00d24 --- /dev/null +++ b/common/hltv.h @@ -0,0 +1,57 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// hltv.h +// all shared consts between server, clients and proxy + +#ifndef HLTV_H +#define HLTV_H + +#define TYPE_CLIENT 0 // client is a normal HL client (default) +#define TYPE_PROXY 1 // client is another proxy +#define TYPE_COMMENTATOR 3 // client is a commentator +#define TYPE_DEMO 4 // client is a demo file +// sub commands of svc_hltv: +#define HLTV_ACTIVE 0 // tells client that he's an spectator and will get director commands +#define HLTV_STATUS 1 // send status infos about proxy +#define HLTV_LISTEN 2 // tell client to listen to a multicast stream + +// sub commands of svc_director: +#define DRC_CMD_NONE 0 // NULL director command +#define DRC_CMD_START 1 // start director mode +#define DRC_CMD_EVENT 2 // informs about director command +#define DRC_CMD_MODE 3 // switches camera modes +#define DRC_CMD_CAMERA 4 // sets camera registers +#define DRC_CMD_TIMESCALE 5 // sets time scale +#define DRC_CMD_MESSAGE 6 // send HUD centerprint +#define DRC_CMD_SOUND 7 // plays a particular sound +#define DRC_CMD_STATUS 8 // status info about broadcast +#define DRC_CMD_BANNER 9 // banner file name for HLTV gui +#define DRC_CMD_FADE 10 // send screen fade command +#define DRC_CMD_SHAKE 11 // send screen shake command +#define DRC_CMD_STUFFTEXT 12 // like the normal svc_stufftext but as director command + +#define DRC_CMD_LAST 12 + + + +// HLTV_EVENT event flags +#define DRC_FLAG_PRIO_MASK 0x0F // priorities between 0 and 15 (15 most important) +#define DRC_FLAG_SIDE (1<<4) // +#define DRC_FLAG_DRAMATIC (1<<5) // is a dramatic scene +#define DRC_FLAG_SLOWMOTION (1<<6) // would look good in SloMo +#define DRC_FLAG_FACEPLAYER (1<<7) // player is doning something (reload/defuse bomb etc) +#define DRC_FLAG_INTRO (1<<8) // is a introduction scene +#define DRC_FLAG_FINAL (1<<9) // is a final scene +#define DRC_FLAG_NO_RANDOM (1<<10) // don't randomize event data + + +#define MAX_DIRECTOR_CMD_PARAMETERS 4 +#define MAX_DIRECTOR_CMD_STRING 128 + + +#endif // HLTV_H diff --git a/common/ivoicetweak.h b/common/ivoicetweak.h new file mode 100644 index 0000000..9bdb2bd --- /dev/null +++ b/common/ivoicetweak.h @@ -0,0 +1,35 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef IVOICETWEAK_H +#define IVOICETWEAK_H +#ifdef _WIN32 +#pragma once +#endif + +// These provide access to the voice controls. +typedef enum +{ + MicrophoneVolume=0, // values 0-1. + OtherSpeakerScale // values 0-1. Scales how loud other players are. +} VoiceTweakControl; + + +typedef struct IVoiceTweak_s +{ + // These turn voice tweak mode on and off. While in voice tweak mode, the user's voice is echoed back + // without sending to the server. + int (*StartVoiceTweakMode)(); // Returns 0 on error. + void (*EndVoiceTweakMode)(); + + // Get/set control values. + void (*SetControlFloat)(VoiceTweakControl iControl, float value); + float (*GetControlFloat)(VoiceTweakControl iControl); +} IVoiceTweak; + + +#endif // IVOICETWEAK_H diff --git a/common/lightlimits.h b/common/lightlimits.h new file mode 100644 index 0000000..8c3391e --- /dev/null +++ b/common/lightlimits.h @@ -0,0 +1,14 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef LIGHTLIMITS_H +#define LIGHTLIMITS_H + +#define MAXDYNLIGHTS 8 // maximum dynamic lights per one pixel +#define MAXLIGHTMAPS 4 // max light styles per face + +#endif//LIGHTLIMITS_H \ No newline at end of file diff --git a/common/lightstyle.h b/common/lightstyle.h new file mode 100644 index 0000000..af4add8 --- /dev/null +++ b/common/lightstyle.h @@ -0,0 +1,29 @@ +/* +lightstyle.h - lighstyle description +Copyright (C) 2011 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef LIGHTSTYLE_H +#define LIGHTSTYLE_H + +typedef struct +{ + char pattern[256]; + float map[256]; + int length; + float value; + qboolean interp; // allow to interpolate this lightstyle + float time; // local time is gurantee what new style begins from the start, not mid or end of the sequence +} lightstyle_t; + +#endif//LIGHTSTYLE_H \ No newline at end of file diff --git a/common/net_api.h b/common/net_api.h new file mode 100644 index 0000000..832568e --- /dev/null +++ b/common/net_api.h @@ -0,0 +1,99 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( NET_APIH ) +#define NET_APIH +#ifdef _WIN32 +#pragma once +#endif + +#if !defined ( NETADRH ) +#include "netadr.h" +#endif + +#define NETAPI_REQUEST_SERVERLIST ( 0 ) // Doesn't need a remote address +#define NETAPI_REQUEST_PING ( 1 ) +#define NETAPI_REQUEST_RULES ( 2 ) +#define NETAPI_REQUEST_PLAYERS ( 3 ) +#define NETAPI_REQUEST_DETAILS ( 4 ) + +// Set this flag for things like broadcast requests, etc. where the engine should not +// kill the request hook after receiving the first response +#define FNETAPI_MULTIPLE_RESPONSE ( 1<<0 ) + +typedef void ( *net_api_response_func_t ) ( struct net_response_s *response ); + +#define NET_SUCCESS ( 0 ) +#define NET_ERROR_TIMEOUT ( 1<<0 ) +#define NET_ERROR_PROTO_UNSUPPORTED ( 1<<1 ) +#define NET_ERROR_UNDEFINED ( 1<<2 ) + +typedef struct net_adrlist_s +{ + struct net_adrlist_s *next; + netadr_t remote_address; +} net_adrlist_t; + +typedef struct net_response_s +{ + // NET_SUCCESS or an error code + int error; + + // Context ID + int context; + // Type + int type; + + // Server that is responding to the request + netadr_t remote_address; + + // Response RTT ping time + double ping; + // Key/Value pair string ( separated by backlash \ characters ) + // WARNING: You must copy this buffer in the callback function, because it is freed + // by the engine right after the call!!!! + // ALSO: For NETAPI_REQUEST_SERVERLIST requests, this will be a pointer to a linked list of net_adrlist_t's + void *response; +} net_response_t; + +typedef struct net_status_s +{ + // Connected to remote server? 1 == yes, 0 otherwise + int connected; + // Client's IP address + netadr_t local_address; + // Address of remote server + netadr_t remote_address; + // Packet Loss ( as a percentage ) + int packet_loss; + // Latency, in seconds ( multiply by 1000.0 to get milliseconds ) + double latency; + // Connection time, in seconds + double connection_time; + // Rate setting ( for incoming data ) + double rate; +} net_status_t; + +typedef struct net_api_s +{ + // APIs + void ( *InitNetworking )( void ); + void ( *Status ) ( struct net_status_s *status ); + void ( *SendRequest) ( int context, int request, int flags, double timeout, struct netadr_s *remote_address, net_api_response_func_t response ); + void ( *CancelRequest ) ( int context ); + void ( *CancelAllRequests ) ( void ); + char *( *AdrToString ) ( struct netadr_s *a ); + int ( *CompareAdr ) ( struct netadr_s *a, struct netadr_s *b ); + int ( *StringToAdr ) ( char *s, struct netadr_s *a ); + const char *( *ValueForKey ) ( const char *s, const char *key ); + void ( *RemoveKey ) ( char *s, const char *key ); + void ( *SetValueForKey ) (char *s, const char *key, const char *value, int maxsize ); +} net_api_t; + +extern net_api_t netapi; + +#endif // NET_APIH \ No newline at end of file diff --git a/common/netadr.h b/common/netadr.h new file mode 100644 index 0000000..5cbf0ea --- /dev/null +++ b/common/netadr.h @@ -0,0 +1,40 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// netadr.h +#ifndef NETADR_H +#define NETADR_H +#ifdef _WIN32 +#pragma once +#endif + +typedef enum +{ + NA_UNUSED, + NA_LOOPBACK, + NA_BROADCAST, + NA_IP, + NA_IPX, + NA_BROADCAST_IPX, +} netadrtype_t; + +typedef struct netadr_s +{ + netadrtype_t type; + unsigned char ip[4]; + unsigned char ipx[10]; + unsigned short port; +} netadr_t; + +#endif // NETADR_H diff --git a/common/particledef.h b/common/particledef.h new file mode 100644 index 0000000..72aa73c --- /dev/null +++ b/common/particledef.h @@ -0,0 +1,53 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef PARTICLEDEF_H +#define PARTICLEDEF_H + +typedef enum +{ + pt_static, + pt_grav, + pt_slowgrav, + pt_fire, + pt_explode, + pt_explode2, + pt_blob, + pt_blob2, + pt_vox_slowgrav, + pt_vox_grav, + pt_clientcustom // Must have callback function specified +} ptype_t; + +typedef struct particle_s +{ + vec3_t org; + short color; + short packedColor; + struct particle_s *next; + vec3_t vel; + float ramp; + float die; + ptype_t type; + void (*deathfunc)( struct particle_s *particle ); + + // for pt_clientcusttom, we'll call this function each frame + void (*callback)( struct particle_s *particle, float frametime ); + + // For deathfunc, etc. + unsigned char context; +} particle_t; + +#endif//PARTICLEDEF_H \ No newline at end of file diff --git a/common/pmtrace.h b/common/pmtrace.h new file mode 100644 index 0000000..0102bbc --- /dev/null +++ b/common/pmtrace.h @@ -0,0 +1,47 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( PMTRACEH ) +#define PMTRACEH +#ifdef _WIN32 +#pragma once +#endif + +typedef struct +{ + vec3_t normal; + float dist; +} pmplane_t; + +typedef struct pmtrace_s pmtrace_t; + +struct pmtrace_s +{ + qboolean allsolid; // if true, plane is not valid + qboolean startsolid; // if true, the initial point was in a solid area + qboolean inopen, inwater; // End point is in empty space or in water + float fraction; // time completed, 1.0 = didn't hit anything + vec3_t endpos; // final position + pmplane_t plane; // surface normal at impact + int ent; // entity at impact + vec3_t deltavelocity; // Change in player's velocity caused by impact. + // Only run on server. + union + { + int hitgroup; // 0 == generic, non zero is specific body part + struct mstudiomat_s *surf; // only if ent->v.solid == SOLID_CUSTOM! + }; +}; + +#endif diff --git a/common/qfont.h b/common/qfont.h new file mode 100644 index 0000000..14d846e --- /dev/null +++ b/common/qfont.h @@ -0,0 +1,40 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( QFONTH ) +#define QFONTH +#ifdef _WIN32 +#pragma once +#endif + +// Font stuff + +#define NUM_GLYPHS 256 + +typedef struct +{ + short startoffset; + short charwidth; +} charinfo; + +typedef struct qfont_s +{ + int width, height; + int rowcount; + int rowheight; + charinfo fontinfo[ NUM_GLYPHS ]; + byte data[4]; +} qfont_t; + +#endif // qfont.h diff --git a/common/r_efx.h b/common/r_efx.h new file mode 100644 index 0000000..80d94ee --- /dev/null +++ b/common/r_efx.h @@ -0,0 +1,197 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef R_EFX_H +#define R_EFX_H + +// particle_t +#if !defined( PARTICLEDEFH ) +#include "particledef.h" +#endif + +// BEAM +#if !defined( BEAMDEFH ) +#include "beamdef.h" +#endif + +// dlight_t +#if !defined ( DLIGHTH ) +#include "dlight.h" +#endif + +// cl_entity_t +#if !defined( CL_ENTITYH ) +#include "cl_entity.h" +#endif + +/* +// FOR REFERENCE, These are the built-in tracer colors. Note, color 4 is the one +// that uses the tracerred/tracergreen/tracerblue and traceralpha cvar settings +color24 gTracerColors[] = +{ + { 255, 255, 255 }, // White + { 255, 0, 0 }, // Red + { 0, 255, 0 }, // Green + { 0, 0, 255 }, // Blue + { 0, 0, 0 }, // Tracer default, filled in from cvars, etc. + { 255, 167, 17 }, // Yellow-orange sparks + { 255, 130, 90 }, // Yellowish streaks (garg) + { 55, 60, 144 }, // Blue egon streak + { 255, 130, 90 }, // More Yellowish streaks (garg) + { 255, 140, 90 }, // More Yellowish streaks (garg) + { 200, 130, 90 }, // More red streaks (garg) + { 255, 120, 70 }, // Darker red streaks (garg) +}; +*/ + +// Temporary entity array +#define TENTPRIORITY_LOW 0 +#define TENTPRIORITY_HIGH 1 + +// TEMPENTITY flags +#define FTENT_NONE 0x00000000 +#define FTENT_SINEWAVE 0x00000001 +#define FTENT_GRAVITY 0x00000002 +#define FTENT_ROTATE 0x00000004 +#define FTENT_SLOWGRAVITY 0x00000008 +#define FTENT_SMOKETRAIL 0x00000010 +#define FTENT_COLLIDEWORLD 0x00000020 +#define FTENT_FLICKER 0x00000040 +#define FTENT_FADEOUT 0x00000080 +#define FTENT_SPRANIMATE 0x00000100 +#define FTENT_HITSOUND 0x00000200 +#define FTENT_SPIRAL 0x00000400 +#define FTENT_SPRCYCLE 0x00000800 +#define FTENT_COLLIDEALL 0x00001000 // will collide with world and slideboxes +#define FTENT_PERSIST 0x00002000 // tent is not removed when unable to draw +#define FTENT_COLLIDEKILL 0x00004000 // tent is removed upon collision with anything +#define FTENT_PLYRATTACHMENT 0x00008000 // tent is attached to a player (owner) +#define FTENT_SPRANIMATELOOP 0x00010000 // animating sprite doesn't die when last frame is displayed +#define FTENT_SPARKSHOWER 0x00020000 +#define FTENT_NOMODEL 0x00040000 // Doesn't have a model, never try to draw ( it just triggers other things ) +#define FTENT_CLIENTCUSTOM 0x00080000 // Must specify callback. Callback function is responsible for killing tempent and updating fields ( unless other flags specify how to do things ) +#define FTENT_SCALE 0x00100000 // An experiment +#define FTENT_MDLANIMATE 0x00200000 +#define FTENT_MDLANIMATELOOP 0x00400000 + +typedef struct tempent_s TEMPENTITY; +typedef struct tempent_s +{ + int flags; + float die; + float frameMax; + float x; + float y; + float z; + float fadeSpeed; + float bounceFactor; + int hitSound; + void (*hitcallback)( struct tempent_s *ent, struct pmtrace_s *ptr ); + void (*callback)( struct tempent_s *ent, float frametime, float currenttime ); + TEMPENTITY *next; + int priority; + short clientIndex; // if attached, this is the index of the client to stick to + // if COLLIDEALL, this is the index of the client to ignore + // TENTS with FTENT_PLYRATTACHMENT MUST set the clientindex! + + vec3_t tentOffset; // if attached, client origin + tentOffset = tent origin. + cl_entity_t entity; + + // baseline.origin - velocity + // baseline.renderamt - starting fadeout intensity + // baseline.angles - angle velocity +} TEMPENTITY; + +typedef struct efx_api_s efx_api_t; + +struct efx_api_s +{ + particle_t *(*R_AllocParticle)( void (*callback)( struct particle_s *particle, float frametime )); + void (*R_BlobExplosion)( float *org ); + void (*R_Blood)( float *org, float *dir, int pcolor, int speed ); + void (*R_BloodSprite)( float *org, int colorindex, int modelIndex, int modelIndex2, float size ); + void (*R_BloodStream)( float *org, float *dir, int pcolor, int speed ); + void (*R_BreakModel)( float *pos, float *size, float *dir, float random, float life, int count, int modelIndex, char flags ); + void (*R_Bubbles)( float *mins, float *maxs, float height, int modelIndex, int count, float speed ); + void (*R_BubbleTrail)( float *start, float *end, float height, int modelIndex, int count, float speed ); + void (*R_BulletImpactParticles)( float *pos ); + void (*R_EntityParticles)( struct cl_entity_s *ent ); + void (*R_Explosion)( float *pos, int model, float scale, float framerate, int flags ); + void (*R_FizzEffect)( struct cl_entity_s *pent, int modelIndex, int density ); + void (*R_FireField)( float *org, int radius, int modelIndex, int count, int flags, float life ); + void (*R_FlickerParticles)( float *org ); + void (*R_FunnelSprite)( float *org, int modelIndex, int reverse ); + void (*R_Implosion)( float *end, float radius, int count, float life ); + void (*R_LargeFunnel)( float *org, int reverse ); + void (*R_LavaSplash)( float *org ); + void (*R_MultiGunshot)( float *org, float *dir, float *noise, int count, int decalCount, int *decalIndices ); + void (*R_MuzzleFlash)( float *pos1, int type ); + void (*R_ParticleBox)( float *mins, float *maxs, unsigned char r, unsigned char g, unsigned char b, float life ); + void (*R_ParticleBurst)( float *pos, int size, int color, float life ); + void (*R_ParticleExplosion)( float *org ); + void (*R_ParticleExplosion2)( float *org, int colorStart, int colorLength ); + void (*R_ParticleLine)( float *start, float *end, unsigned char r, unsigned char g, unsigned char b, float life ); + void (*R_PlayerSprites)( int client, int modelIndex, int count, int size ); + void (*R_Projectile)( float *origin, float *velocity, int modelIndex, int life, int owner, void (*hitcallback)( struct tempent_s *ent, struct pmtrace_s *ptr ) ); + void (*R_RicochetSound)( float *pos ); + void (*R_RicochetSprite)( float *pos, struct model_s *pmodel, float duration, float scale ); + void (*R_RocketFlare)( float *pos ); + void (*R_RocketTrail)( float *start, float *end, int type ); + void (*R_RunParticleEffect)( float *org, float *dir, int color, int count ); + void (*R_ShowLine)( float *start, float *end ); + void (*R_SparkEffect)( float *pos, int count, int velocityMin, int velocityMax ); + void (*R_SparkShower)( float *pos ); + void (*R_SparkStreaks)( float *pos, int count, int velocityMin, int velocityMax ); + void (*R_Spray)( float *pos, float *dir, int modelIndex, int count, int speed, int spread, int rendermode ); + void (*R_Sprite_Explode)( TEMPENTITY *pTemp, float scale, int flags ); + void (*R_Sprite_Smoke)( TEMPENTITY *pTemp, float scale ); + void (*R_Sprite_Spray)( float *pos, float *dir, int modelIndex, int count, int speed, int iRand ); + void (*R_Sprite_Trail)( int type, float *start, float *end, int modelIndex, int count, float life, float size, float amplitude, int renderamt, float speed ); + void (*R_Sprite_WallPuff)( TEMPENTITY *pTemp, float scale ); + void (*R_StreakSplash)( float *pos, float *dir, int color, int count, float speed, int velocityMin, int velocityMax ); + void (*R_TracerEffect)( float *start, float *end ); + void (*R_UserTracerParticle)( float *org, float *vel, float life, int colorIndex, float length, unsigned char deathcontext, void (*deathfunc)( struct particle_s *particle )); + particle_t *(*R_TracerParticles)( float *org, float *vel, float life ); + void (*R_TeleportSplash)( float *org ); + void (*R_TempSphereModel)( float *pos, float speed, float life, int count, int modelIndex ); + TEMPENTITY *(*R_TempModel)( float *pos, float *dir, float *angles, float life, int modelIndex, int soundtype ); + TEMPENTITY *(*R_DefaultSprite)( float *pos, int spriteIndex, float framerate ); + TEMPENTITY *(*R_TempSprite)( float *pos, float *dir, float scale, int modelIndex, int rendermode, int renderfx, float a, float life, int flags ); + int (*Draw_DecalIndex)( int id ); + int (*Draw_DecalIndexFromName)( char *name ); + void (*R_DecalShoot)( int textureIndex, int entity, int modelIndex, float *position, int flags ); + void (*R_AttachTentToPlayer)( int client, int modelIndex, float zoffset, float life ); + void (*R_KillAttachedTents)( int client ); + BEAM *(*R_BeamCirclePoints)( int type, float *start, float *end, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); + BEAM *(*R_BeamEntPoint)( int startEnt, float *end, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); + BEAM *(*R_BeamEnts)( int startEnt, int endEnt, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); + BEAM *(*R_BeamFollow)( int startEnt, int modelIndex, float life, float width, float r, float g, float b, float brightness ); + void (*R_BeamKill)( int deadEntity ); + BEAM *(*R_BeamLightning)( float *start, float *end, int modelIndex, float life, float width, float amplitude, float brightness, float speed ); + BEAM *(*R_BeamPoints)( float *start, float *end, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); + BEAM *(*R_BeamRing)( int startEnt, int endEnt, int modelIndex, float life, float width, float amplitude, float brightness, float speed, int startFrame, float framerate, float r, float g, float b ); + dlight_t *(*CL_AllocDlight)( int key ); + dlight_t *(*CL_AllocElight)( int key ); + TEMPENTITY *(*CL_TempEntAlloc)( float *org, struct model_s *model ); + TEMPENTITY *(*CL_TempEntAllocNoModel)( float *org ); + TEMPENTITY *(*CL_TempEntAllocHigh)( float *org, struct model_s *model ); + TEMPENTITY *(*CL_TentEntAllocCustom)( float *origin, struct model_s *model, int high, void (*callback)( struct tempent_s *ent, float frametime, float currenttime )); + void (*R_GetPackedColor)( short *packed, short color ); + short (*R_LookupColor)( unsigned char r, unsigned char g, unsigned char b ); + void (*R_DecalRemoveAll)( int textureIndex ); // textureIndex points to the decal index in the array, not the actual texture index. + void (*R_FireCustomDecal)( int textureIndex, int entity, int modelIndex, float *position, int flags, float scale ); +}; + +#endif//R_EFX_H \ No newline at end of file diff --git a/common/r_studioint.h b/common/r_studioint.h new file mode 100644 index 0000000..8c5598e --- /dev/null +++ b/common/r_studioint.h @@ -0,0 +1,154 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + + +#ifndef R_STUDIOINT_H +#define R_STUDIOINT_H + +#define STUDIO_INTERFACE_VERSION 1 + +typedef struct engine_studio_api_s +{ + // Allocate number*size bytes and zero it + void *( *Mem_Calloc )( int number, size_t size ); + // Check to see if pointer is in the cache + void *( *Cache_Check )( struct cache_user_s *c ); + // Load file into cache ( can be swapped out on demand ) + void ( *LoadCacheFile )( char *path, struct cache_user_s *cu ); + // Retrieve model pointer for the named model + struct model_s *( *Mod_ForName )( const char *name, int crash_if_missing ); + // Retrieve pointer to studio model data block from a model + void *( *Mod_Extradata )( struct model_s *mod ); + // Retrieve indexed model from client side model precache list + struct model_s *( *GetModelByIndex )( int index ); + // Get entity that is set for rendering + struct cl_entity_s * ( *GetCurrentEntity )( void ); + // Get referenced player_info_t + struct player_info_s *( *PlayerInfo )( int index ); + // Get most recently received player state data from network system + struct entity_state_s *( *GetPlayerState )( int index ); + // Get viewentity + struct cl_entity_s * ( *GetViewEntity )( void ); + // Get current frame count, and last two timestampes on client + void ( *GetTimes )( int *framecount, double *current, double *old ); + // Get a pointer to a cvar by name + struct cvar_s *( *GetCvar )( const char *name ); + // Get current render origin and view vectors ( up, right and vpn ) + void ( *GetViewInfo )( float *origin, float *upv, float *rightv, float *vpnv ); + // Get sprite model used for applying chrome effect + struct model_s *( *GetChromeSprite )( void ); + // Get model counters so we can incement instrumentation + void ( *GetModelCounters )( int **s, int **a ); + // Get software scaling coefficients + void ( *GetAliasScale )( float *x, float *y ); + + // Get bone, light, alias, and rotation matrices + float ****( *StudioGetBoneTransform )( void ); + float ****( *StudioGetLightTransform )( void ); + float ***( *StudioGetAliasTransform )( void ); + float ***( *StudioGetRotationMatrix )( void ); + + // Set up body part, and get submodel pointers + void ( *StudioSetupModel )( int bodypart, void **ppbodypart, void **ppsubmodel ); + // Check if entity's bbox is in the view frustum + int ( *StudioCheckBBox )( void ); + // Apply lighting effects to model + void ( *StudioDynamicLight )( struct cl_entity_s *ent, struct alight_s *plight ); + void ( *StudioEntityLight )( struct alight_s *plight ); + void ( *StudioSetupLighting )( struct alight_s *plighting ); + + // Draw mesh vertices + void ( *StudioDrawPoints )( void ); + + // Draw hulls around bones + void ( *StudioDrawHulls )( void ); + // Draw bbox around studio models + void ( *StudioDrawAbsBBox )( void ); + // Draws bones + void ( *StudioDrawBones )( void ); + // Loads in appropriate texture for model + void ( *StudioSetupSkin )( void *ptexturehdr, int index ); + // Sets up for remapped colors + void ( *StudioSetRemapColors )( int top, int bottom ); + // Set's player model and returns model pointer + struct model_s *( *SetupPlayerModel )( int index ); + // Fires any events embedded in animation + void ( *StudioClientEvents )( void ); + // Retrieve/set forced render effects flags + int ( *GetForceFaceFlags )( void ); + void ( *SetForceFaceFlags )( int flags ); + // Tell engine the value of the studio model header + void ( *StudioSetHeader )( void *header ); + // Tell engine which model_t * is being renderered + void ( *SetRenderModel )( struct model_s *model ); + + // Final state setup and restore for rendering + void ( *SetupRenderer )( int rendermode ); + void ( *RestoreRenderer )( void ); + + // Set render origin for applying chrome effect + void ( *SetChromeOrigin )( void ); + + // True if using D3D/OpenGL + int ( *IsHardware )( void ); + + // Only called by hardware interface + void ( *GL_StudioDrawShadow )( void ); + void ( *GL_SetRenderMode )( int mode ); + + void ( *StudioSetRenderamt )( int iRenderamt ); + void ( *StudioSetCullState )( int iCull ); + void ( *StudioRenderShadow )( int iSprite, float *p1, float *p2, float *p3, float *p4 ); +} engine_studio_api_t; + +typedef struct server_studio_api_s +{ + // Allocate number*size bytes and zero it + void *( *Mem_Calloc )( int number, size_t size ); + // Check to see if pointer is in the cache + void *( *Cache_Check )( struct cache_user_s *c ); + // Load file into cache ( can be swapped out on demand ) + void ( *LoadCacheFile )( char *path, struct cache_user_s *cu ); + // Retrieve pointer to studio model data block from a model + void *( *Mod_Extradata )( struct model_s *mod ); +} server_studio_api_t; + +// client blending +typedef struct r_studio_interface_s +{ + int version; + int ( *StudioDrawModel )( int flags ); + int ( *StudioDrawPlayer )( int flags, struct entity_state_s *pplayer ); +} r_studio_interface_t; + +// server blending +#define SV_BLENDING_INTERFACE_VERSION 1 + +typedef struct sv_blending_interface_s +{ + int version; + + void ( *SV_StudioSetupBones )( struct model_s *pModel, + float frame, + int sequence, + const vec3_t angles, + const vec3_t origin, + const byte *pcontroller, + const byte *pblending, + int iBone, + const edict_t *pEdict ); +} sv_blending_interface_t; + +#endif//R_STUDIOINT_H \ No newline at end of file diff --git a/common/ref_params.h b/common/ref_params.h new file mode 100644 index 0000000..c39f46f --- /dev/null +++ b/common/ref_params.h @@ -0,0 +1,105 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef REF_PARAMS_H +#define REF_PARAMS_H + +typedef struct ref_params_s +{ + // output + vec3_t vieworg; + vec3_t viewangles; + + vec3_t forward; + vec3_t right; + vec3_t up; + + // Client frametime; + float frametime; + // Client time + float time; + + // Misc + int intermission; + int paused; + int spectator; + int onground; + int waterlevel; + + vec3_t simvel; + vec3_t simorg; + + vec3_t viewheight; + float idealpitch; + + vec3_t cl_viewangles; + int health; + vec3_t crosshairangle; + float viewsize; + + vec3_t punchangle; + int maxclients; + int viewentity; + int playernum; + int max_entities; + int demoplayback; + int hardware; + int smoothing; + + // Last issued usercmd + struct usercmd_s *cmd; + + // Movevars + struct movevars_s *movevars; + + int viewport[4]; // the viewport coordinates x, y, width, height + int nextView; // the renderer calls ClientDLL_CalcRefdef() and Renderview + // so long in cycles until this value is 0 (multiple views) + int onlyClientDraw; // if !=0 nothing is drawn by the engine except clientDraw functions +} ref_params_t; + +// same as ref_params but for overview mode +typedef struct ref_overview_s +{ + vec3_t origin; + qboolean rotated; + + float xLeft; + float xRight; + float yTop; + float yBottom; + float zFar; + float zNear; + float flZoom; +} ref_overview_t; + +// ref_viewpass_t->flags +#define RF_DRAW_WORLD (1<<0) // pass should draw the world (otherwise it's player menu model) +#define RF_DRAW_CUBEMAP (1<<1) // special 6x pass to render cubemap\skybox sides +#define RF_DRAW_OVERVIEW (1<<2) // overview mode is active +#define RF_ONLY_CLIENTDRAW (1<<3) // nothing is drawn by the engine except clientDraw functions + +// intermediate struct for viewpass (or just a single frame) +typedef struct ref_viewpass_s +{ + int viewport[4]; // size of new viewport + vec3_t vieworigin; // view origin + vec3_t viewangles; // view angles + int viewentity; // entitynum (P2: Savior uses this) + float fov_x, fov_y; // vertical & horizontal FOV + int flags; // if !=0 nothing is drawn by the engine except clientDraw functions +} ref_viewpass_t; + +#endif//REF_PARAMS_H \ No newline at end of file diff --git a/common/render_api.h b/common/render_api.h new file mode 100644 index 0000000..0e76fbe --- /dev/null +++ b/common/render_api.h @@ -0,0 +1,284 @@ +/* +render_api.h - Xash3D extension for client interface +Copyright (C) 2011 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef RENDER_API_H +#define RENDER_API_H + +#include "lightstyle.h" +#include "dlight.h" + +#define CL_RENDER_INTERFACE_VERSION 37 // Xash3D 1.0 +#define MAX_STUDIO_DECALS 4096 // + unused space of BSP decals +#define MAX_BMODEL_DECALS 4096 + +// helper macroses +#define LEAF_INFO( leaf, mod ) ((mextraleaf_t *)world->leafs + (leaf - mod->leafs)) +#define INFO_LEAF( leaf, mod ) (mod->leafs + (leaf - (mextraleaf_t *)world->leafs)) + +// render info parms +#define PARM_TEX_WIDTH 1 // all parms with prefix 'TEX_' receive arg as texnum +#define PARM_TEX_HEIGHT 2 // otherwise it's not used +#define PARM_TEX_SRC_WIDTH 3 +#define PARM_TEX_SRC_HEIGHT 4 +#define PARM_TEX_SKYBOX 5 // second arg as skybox ordering num +#define PARM_TEX_SKYTEXNUM 6 // skytexturenum for quake sky +#define PARM_TEX_LIGHTMAP 7 // second arg as number 0 - 128 +#define PARM_TEX_TARGET 8 +#define PARM_TEX_TEXNUM 9 +#define PARM_TEX_FLAGS 10 +#define PARM_TEX_DEPTH 11 // 3D texture depth or 2D array num layers +//reserved +#define PARM_TEX_GLFORMAT 13 // get a texture GL-format +#define PARM_TEX_ENCODE 14 // custom encoding for DXT image +#define PARM_TEX_MIPCOUNT 15 // count of mipmaps (0 - autogenerated, 1 - disabled of mipmapping) +#define PARM_BSP2_SUPPORTED 16 // tell custom renderer what engine is support BSP2 in this build +#define PARM_SKY_SPHERE 17 // sky is quake sphere ? +#define PARAM_GAMEPAUSED 18 // game is paused +#define PARM_MAP_HAS_DELUXE 19 // map has deluxedata +#define PARM_MAX_ENTITIES 20 +#define PARM_WIDESCREEN 21 +#define PARM_FULLSCREEN 22 +#define PARM_SCREEN_WIDTH 23 +#define PARM_SCREEN_HEIGHT 24 +#define PARM_CLIENT_INGAME 25 +#define PARM_FEATURES 26 // same as movevars->features +#define PARM_ACTIVE_TMU 27 // for debug +#define PARM_LIGHTSTYLEVALUE 28 // second arg is stylenum +#define PARM_MAX_IMAGE_UNITS 29 +#define PARM_CLIENT_ACTIVE 30 +#define PARM_REBUILD_GAMMA 31 // if true lightmaps rebuilding for gamma change +#define PARM_DEDICATED_SERVER 32 +#define PARM_SURF_SAMPLESIZE 33 // lightmap resolution per face (second arg interpret as facenumber) +#define PARM_GL_CONTEXT_TYPE 34 // opengl or opengles +#define PARM_GLES_WRAPPER 35 // +#define PARM_STENCIL_ACTIVE 36 +#define PARM_WATER_ALPHA 37 +#define PARM_TEX_MEMORY 38 // returns total memory of uploaded texture in bytes +#define PARM_DELUXEDATA 39 // nasty hack, convert int to pointer +#define PARM_SHADOWDATA 40 // nasty hack, convert int to pointer + +// skybox ordering +enum +{ + SKYBOX_RIGHT = 0, + SKYBOX_BACK, + SKYBOX_LEFT, + SKYBOX_FORWARD, + SKYBOX_UP, + SKYBOX_DOWN, +}; + +#define DXT_ENCODE_DEFAULT 0 // don't use custom encoders +#define DXT_ENCODE_COLOR_YCoCg 0x1A01 // make sure that value dosn't collide with anything +#define DXT_ENCODE_ALPHA_1BIT 0x1A02 // normal 1-bit alpha +#define DXT_ENCODE_ALPHA_8BIT 0x1A03 // normal 8-bit alpha +#define DXT_ENCODE_ALPHA_SDF 0x1A04 // signed distance field +#define DXT_ENCODE_NORMAL_AG_PARABOLOID 0x1A07 // paraboloid projection + +typedef enum +{ + TF_COLORMAP = 0, // just for tabulate source + TF_NEAREST = (1<<0), // disable texfilter + TF_KEEP_SOURCE = (1<<1), // some images keep source + TF_NOFLIP_TGA = (1<<2), // Steam background completely ignore tga attribute 0x20 + TF_EXPAND_SOURCE = (1<<3), // Don't keep source as 8-bit expand to RGBA +// reserved + TF_RECTANGLE = (1<<5), // this is GL_TEXTURE_RECTANGLE + TF_CUBEMAP = (1<<6), // it's cubemap texture + TF_DEPTHMAP = (1<<7), // custom texture filter used + TF_QUAKEPAL = (1<<8), // image has an quake1 palette + TF_LUMINANCE = (1<<9), // force image to grayscale + TF_SKYSIDE = (1<<10), // this is a part of skybox + TF_CLAMP = (1<<11), // clamp texcoords to [0..1] range + TF_NOMIPMAP = (1<<12), // don't build mips for this image + TF_HAS_LUMA = (1<<13), // sets by GL_UploadTexture + TF_MAKELUMA = (1<<14), // create luma from quake texture (only q1 textures contain luma-pixels) + TF_NORMALMAP = (1<<15), // is a normalmap + TF_HAS_ALPHA = (1<<16), // image has alpha (used only for GL_CreateTexture) + TF_FORCE_COLOR = (1<<17), // force upload monochrome textures as RGB (detail textures) + TF_UPDATE = (1<<18), // allow to update already loaded texture + TF_BORDER = (1<<19), // zero clamp for projected textures + TF_TEXTURE_3D = (1<<20), // this is GL_TEXTURE_3D + TF_ATLAS_PAGE = (1<<21), // bit who indicate lightmap page or deluxemap page + TF_ALPHACONTRAST = (1<<22), // special texture mode for A2C +// reserved +// reserved + TF_IMG_UPLOADED = (1<<25), // this is set for first time when called glTexImage, otherwise it will be call glTexSubImage + TF_ARB_FLOAT = (1<<26), // float textures + TF_NOCOMPARE = (1<<27), // disable comparing for depth textures + TF_ARB_16BIT = (1<<28), // keep image as 16-bit (not 24) +} texFlags_t; + +typedef enum +{ + CONTEXT_TYPE_GL = 0, + CONTEXT_TYPE_GLES_1_X, + CONTEXT_TYPE_GLES_2_X +} gl_context_type_t; + +typedef enum +{ + GLES_WRAPPER_NONE = 0, // native GLES + GLES_WRAPPER_NANOGL, // used on GLES platforms +} gles_wrapper_t; + +// 30 bytes here +typedef struct modelstate_s +{ + short sequence; + short frame; // 10 bits multiple by 4, should be enough + byte blending[2]; + byte controller[4]; + byte poseparam[16]; + byte body; + byte skin; + short scale; // model scale (multiplied by 16) +} modelstate_t; + +typedef struct decallist_s +{ + vec3_t position; + char name[64]; + short entityIndex; + byte depth; + byte flags; + float scale; + + // this is the surface plane that we hit so that + // we can move certain decals across + // transitions if they hit similar geometry + vec3_t impactPlaneNormal; + + modelstate_t studio_state; // studio decals only +} decallist_t; + +typedef struct render_api_s +{ + // Get renderer info (doesn't changes engine state at all) + int (*RenderGetParm)( int parm, int arg ); // generic + void (*GetDetailScaleForTexture)( int texture, float *xScale, float *yScale ); + void (*GetExtraParmsForTexture)( int texture, byte *red, byte *green, byte *blue, byte *alpha ); + lightstyle_t* (*GetLightStyle)( int number ); + dlight_t* (*GetDynamicLight)( int number ); + dlight_t* (*GetEntityLight)( int number ); + byte (*LightToTexGamma)( byte color ); // software gamma support + float (*GetFrameTime)( void ); + + // Set renderer info (tell engine about changes) + void (*R_SetCurrentEntity)( struct cl_entity_s *ent ); // tell engine about both currententity and currentmodel + void (*R_SetCurrentModel)( struct model_s *mod ); // change currentmodel but leave currententity unchanged + int (*R_FatPVS)( const float *org, float radius, byte *visbuffer, qboolean merge, qboolean fullvis ); + void (*R_StoreEfrags)( struct efrag_s **ppefrag, int framecount );// store efrags for static entities + + // Texture tools + int (*GL_FindTexture)( const char *name ); + const char* (*GL_TextureName)( unsigned int texnum ); + const byte* (*GL_TextureData)( unsigned int texnum ); // may be NULL + int (*GL_LoadTexture)( const char *name, const byte *buf, size_t size, int flags ); + int (*GL_CreateTexture)( const char *name, int width, int height, const void *buffer, int flags ); + int (*GL_LoadTextureArray)( const char **names, int flags ); + int (*GL_CreateTextureArray)( const char *name, int width, int height, int depth, const void *buffer, int flags ); + void (*GL_FreeTexture)( unsigned int texnum ); + + // Decals manipulating (draw & remove) + void (*DrawSingleDecal)( struct decal_s *pDecal, struct msurface_s *fa ); + float *(*R_DecalSetupVerts)( struct decal_s *pDecal, struct msurface_s *surf, int texture, int *outCount ); + void (*R_EntityRemoveDecals)( struct model_s *mod ); // remove all the decals from specified entity (BSP only) + + // AVIkit support + void *(*AVI_LoadVideo)( const char *filename, qboolean load_audio ); + int (*AVI_GetVideoInfo)( void *Avi, long *xres, long *yres, float *duration ); + long (*AVI_GetVideoFrameNumber)( void *Avi, float time ); + byte *(*AVI_GetVideoFrame)( void *Avi, long frame ); + void (*AVI_UploadRawFrame)( int texture, int cols, int rows, int width, int height, const byte *data ); + void (*AVI_FreeVideo)( void *Avi ); + int (*AVI_IsActive)( void *Avi ); + void (*AVI_StreamSound)( void *Avi, int entnum, float fvol, float attn, float synctime ); + void (*AVI_Reserved0)( void ); // for potential interface expansion without broken compatibility + void (*AVI_Reserved1)( void ); + + // glState related calls (must use this instead of normal gl-calls to prevent de-synchornize local states between engine and the client) + void (*GL_Bind)( int tmu, unsigned int texnum ); + void (*GL_SelectTexture)( int tmu ); + void (*GL_LoadTextureMatrix)( const float *glmatrix ); + void (*GL_TexMatrixIdentity)( void ); + void (*GL_CleanUpTextureUnits)( int last ); // pass 0 for clear all the texture units + void (*GL_TexGen)( unsigned int coord, unsigned int mode ); + void (*GL_TextureTarget)( unsigned int target ); // change texture unit mode without bind texture + void (*GL_TexCoordArrayMode)( unsigned int texmode ); + void* (*GL_GetProcAddress)( const char *name ); + void (*GL_UpdateTexSize)( int texnum, int width, int height, int depth ); // recalc statistics + void (*GL_Reserved0)( void ); // for potential interface expansion without broken compatibility + void (*GL_Reserved1)( void ); + + // Misc renderer functions + void (*GL_DrawParticles)( const struct ref_viewpass_s *rvp, qboolean trans_pass, float frametime ); + void (*EnvShot)( const float *vieworg, const char *name, qboolean skyshot, int shotsize ); // store skybox into gfx\env folder + int (*SPR_LoadExt)( const char *szPicName, unsigned int texFlags ); // extended version of SPR_Load + colorVec (*LightVec)( const float *start, const float *end, float *lightspot ); + struct mstudiotex_s *( *StudioGetTexture )( struct cl_entity_s *e ); + const struct ref_overview_s *( *GetOverviewParms )( void ); + const char *( *GetFileByIndex )( int fileindex ); + int (*pfnSaveFile)( const char *filename, const void *data, long len ); + void (*R_Reserved0)( void ); + + // static allocations + void *(*pfnMemAlloc)( size_t cb, const char *filename, const int fileline ); + void (*pfnMemFree)( void *mem, const char *filename, const int fileline ); + + // engine utils (not related with render API but placed here) + char **(*pfnGetFilesList)( const char *pattern, int *numFiles, int gamedironly ); + unsigned long (*pfnFileBufferCRC32)( const void *buffer, const int length ); + int (*COM_CompareFileTime)( const char *filename1, const char *filename2, int *iCompare ); + void (*Host_Error)( const char *error, ... ); // cause Host Error + struct model_s* (*pfnGetModel)( int modelindex ); + float (*pfnTime)( void ); // Sys_DoubleTime + void (*Cvar_Set)( char *name, char *value ); + void (*S_FadeMusicVolume)( float fadePercent ); // fade background track (0-100 percents) + void (*SetRandomSeed)( long lSeed ); // set custom seed for RANDOM_FLOAT\RANDOM_LONG for predictable random + // ONLY ADD NEW FUNCTIONS TO THE END OF THIS STRUCT. INTERFACE VERSION IS FROZEN AT 37 +} render_api_t; + +// render callbacks +typedef struct render_interface_s +{ + int version; + // passed through R_RenderFrame (0 - use engine renderer, 1 - use custom client renderer) + int (*GL_RenderFrame)( const struct ref_viewpass_s *rvp ); + // build all the lightmaps on new level or when gamma is changed + void (*GL_BuildLightmaps)( void ); + // setup map bounds for ortho-projection when we in dev_overview mode + void (*GL_OrthoBounds)( const float *mins, const float *maxs ); + // prepare studio decals for save + int (*R_CreateStudioDecalList)( decallist_t *pList, int count ); + // clear decals by engine request (e.g. for demo recording or vid_restart) + void (*R_ClearStudioDecals)( void ); + // grab r_speeds message + qboolean (*R_SpeedsMessage)( char *out, size_t size ); + // alloc or destroy model custom data + void (*Mod_ProcessUserData)( struct model_s *mod, qboolean create, const byte *buffer ); + // alloc or destroy entity custom data + void (*R_ProcessEntData)( qboolean allocate ); + // get visdata for current frame from custom renderer + byte* (*Mod_GetCurrentVis)( void ); + // tell the renderer what new map is started + void (*R_NewMap)( void ); + // clear the render entities before each frame + void (*R_ClearScene)( void ); + // shuffle previous & next states for lerping + void (*CL_UpdateLatchedVars)( struct cl_entity_s *e, qboolean reset ); +} render_interface_t; + +#endif//RENDER_API_H \ No newline at end of file diff --git a/common/screenfade.h b/common/screenfade.h new file mode 100644 index 0000000..d1d7126 --- /dev/null +++ b/common/screenfade.h @@ -0,0 +1,24 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( SCREENFADEH ) +#define SCREENFADEH +#ifdef _WIN32 +#pragma once +#endif + +typedef struct screenfade_s +{ + float fadeSpeed; // How fast to fade (tics / second) (+ fade in, - fade out) + float fadeEnd; // When the fading hits maximum + float fadeTotalEnd; // Total End Time of the fade (used for FFADE_OUT) + float fadeReset; // When to reset to not fading (for fadeout and hold) + byte fader, fadeg, fadeb, fadealpha; // Fade color + int fadeFlags; // Fading flags +} screenfade_t; + +#endif // !SCREENFADEH diff --git a/common/shader.h b/common/shader.h new file mode 100644 index 0000000..788832b --- /dev/null +++ b/common/shader.h @@ -0,0 +1,32 @@ +/* +shader.h - shadercache implementation +Copyright (C) 2019 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef SHADER_H +#define SHADER_H + +// simple cache (4 bytes here) +class shader_t +{ + unsigned short shadernum; + unsigned short sequence; +public: + struct glsl_prog_s *GetShader( void ); + void SetShader( unsigned short hand ); + unsigned short GetHandle( void ) { return shadernum; } + void Invalidate( void ) { shadernum = sequence = 0; } + bool IsValid( void ); +}; + +#endif//SHADER_H \ No newline at end of file diff --git a/common/triangleapi.h b/common/triangleapi.h new file mode 100644 index 0000000..d975a26 --- /dev/null +++ b/common/triangleapi.h @@ -0,0 +1,62 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef TRIANGLEAPI_H +#define TRIANGLEAPI_H + +typedef enum +{ + TRI_FRONT = 0, + TRI_NONE = 1, +} TRICULLSTYLE; + +#define TRI_API_VERSION 1 + +#define TRI_TRIANGLES 0 +#define TRI_TRIANGLE_FAN 1 +#define TRI_QUADS 2 +#define TRI_POLYGON 3 +#define TRI_LINES 4 +#define TRI_TRIANGLE_STRIP 5 +#define TRI_QUAD_STRIP 6 +#define TRI_POINTS 7 // Xash3D added + +typedef struct triangleapi_s +{ + int version; + + void (*RenderMode)( int mode ); + void (*Begin)( int primitiveCode ); + void (*End)( void ); + + void (*Color4f)( float r, float g, float b, float a ); + void (*Color4ub)( unsigned char r, unsigned char g, unsigned char b, unsigned char a ); + void (*TexCoord2f)( float u, float v ); + void (*Vertex3fv)( float *worldPnt ); + void (*Vertex3f)( float x, float y, float z ); + void (*Brightness)( float brightness ); + void (*CullFace)( TRICULLSTYLE style ); + int (*SpriteTexture)( struct model_s *pSpriteModel, int frame ); + int (*WorldToScreen)( float *world, float *screen ); // Returns 1 if it's z clipped + void (*Fog)( float flFogColor[3], float flStart, float flEnd, int bOn ); //Works just like GL_FOG, flFogColor is r/g/b. + void (*ScreenToWorld)( float *screen, float *world ); + void (*GetMatrix)( const int pname, float *matrix ); + int (*BoxInPVS)( float *mins, float *maxs ); + void (*LightAtPoint)( float *pos, float *value ); + void (*Color4fRendermode)( float r, float g, float b, float a, int rendermode ); + void (*FogParams)( float flDensity, int iFogSkybox ); +} triangleapi_t; + +#endif//TRIANGLEAPI_H \ No newline at end of file diff --git a/common/usercmd.h b/common/usercmd.h new file mode 100644 index 0000000..ab88ddb --- /dev/null +++ b/common/usercmd.h @@ -0,0 +1,41 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef USERCMD_H +#define USERCMD_H +#ifdef _WIN32 +#pragma once +#endif + +typedef struct usercmd_s +{ + short lerp_msec; // Interpolation time on client + byte msec; // Duration in ms of command + vec3_t viewangles; // Command view angles. + +// intended velocities + float forwardmove; // Forward velocity. + float sidemove; // Sideways velocity. + float upmove; // Upward velocity. + byte lightlevel; // Light level at spot where we are standing. + unsigned short buttons; // Attack buttons + byte impulse; // Impulse command issued. + byte weaponselect; // Current weapon id + +// Experimental player impact stuff. + int impact_index; + vec3_t impact_position; +} usercmd_t; + +#endif // USERCMD_H diff --git a/common/wadfile.h b/common/wadfile.h new file mode 100644 index 0000000..78b1b92 --- /dev/null +++ b/common/wadfile.h @@ -0,0 +1,143 @@ +/* +wadfile.h - WAD3 file format +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef WADFILE_H +#define WADFILE_H + +/* +======================================================================== +.WAD archive format (WhereAllData - WAD) + +List of compressed files, that can be identify only by TYP_* + + +header: dwadinfo_t[dwadinfo_t] +file_1: byte[dwadinfo_t[num]->disksize] +file_2: byte[dwadinfo_t[num]->disksize] +file_3: byte[dwadinfo_t[num]->disksize] +... +file_n: byte[dwadinfo_t[num]->disksize] +infotable dlumpinfo_t[dwadinfo_t->numlumps] +======================================================================== +*/ + +#define IDWAD2HEADER (('2'<<24)+('D'<<16)+('A'<<8)+'W') // little-endian "WAD2" quake wads +#define IDWAD3HEADER (('3'<<24)+('D'<<16)+('A'<<8)+'W') // little-endian "WAD3" half-life wads + +// dlumpinfo_t->attribs +#define ATTR_NONE 0 // allow to read-write +#define ATTR_READONLY BIT( 0 ) // don't overwrite this lump in anyway +#define ATTR_COMPRESSED BIT( 1 ) // not used for now, just reserved +#define ATTR_HIDDEN BIT( 2 ) // not used for now, just reserved +#define ATTR_SYSTEM BIT( 3 ) // not used for now, just reserved + +// dlumpinfo_t->type +#define TYP_ANY -1 // any type can be accepted +#define TYP_NONE 0 // unknown lump type +#define TYP_LABEL 1 // legacy from Doom1. Empty lump - label (like P_START, P_END etc) +#define TYP_PALETTE 64 // quake or half-life palette (768 bytes) +#define TYP_DDSTEX 65 // contain DDS texture +#define TYP_GFXPIC 66 // menu or hud image (not contain mip-levels) +#define TYP_MIPTEX 67 // quake1 and half-life in-game textures with four miplevels +#define TYP_SCRIPT 68 // contain script files +#define TYP_COLORMAP2 69 // old stuff. build palette from LBM file (not used) +#define TYP_QFONT 70 // half-life font (qfont_t) + +// dlumpinfo_t->img_type +#define IMG_DIFFUSE 0 // same as default pad1 always equal 0 +#define IMG_ALPHAMASK 1 // alpha-channel that stored separate as luminance texture +#define IMG_NORMALMAP 2 // indexed normalmap +#define IMG_GLOSSMAP 3 // luminance or color specularity map +#define IMG_GLOSSPOWER 4 // gloss power map (each value is a specular pow) +#define IMG_HEIGHTMAP 5 // heightmap (for parallax occlusion mapping or source of normalmap) +#define IMG_LUMA 6 // luma or glow texture with self-illuminated parts +#define IMG_DECAL_ALPHA 7 // it's a decal texture (last color in palette is base color, and other colors his graduations) +#define IMG_DECAL_COLOR 8 // decal without alpha-channel uses base, like 127 127 127 as transparent color + +#define WAD3_NAMELEN 16 +#define HINT_NAMELEN 5 // e.g. _mask, _norm +#define MAX_FILES_IN_WAD 65535 // real limit as above <2Gb size not a lumpcount + +typedef struct +{ + int ident; // should be WAD3 + int numlumps; // num files + int infotableofs; // LUT offset +} dwadinfo_t; + +typedef struct +{ + int filepos; // file offset in WAD + int disksize; // compressed or uncompressed + int size; // uncompressed + char type; // TYP_* + char attribs; // file attribs + char img_type; // IMG_* + char pad; + char name[WAD3_NAMELEN]; // must be null terminated +} dlumpinfo_t; + +/* +======================================================================== + +.LMP image format (Half-Life gfx.wad lumps) + +======================================================================== +*/ +typedef struct lmp_s +{ + unsigned int width; + unsigned int height; +} lmp_t; + +/* +======================================================================== + +.MIP image format (half-Life textures) + +======================================================================== +*/ +typedef struct mip_s +{ + char name[WAD3_NAMELEN]; + unsigned int width; + unsigned int height; + unsigned int offsets[4]; // four mip maps stored +} mip_t; + +/* +======================================================================== + +.FNT image format (half-Life fonts) + +======================================================================== +*/ +#define NUM_GLYPHS 256 + +typedef struct +{ + short startoffset; + short charwidth; +} charinfo; + +typedef struct qfont_s +{ + int width, height; + int rowcount; + int rowheight; + charinfo fontinfo[NUM_GLYPHS]; +} qfont_t; + +#endif//WADFILE_H \ No newline at end of file diff --git a/common/weaponinfo.h b/common/weaponinfo.h new file mode 100644 index 0000000..6808aeb --- /dev/null +++ b/common/weaponinfo.h @@ -0,0 +1,52 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined ( WEAPONINFOH ) +#define WEAPONINFOH +#ifdef _WIN32 +#pragma once +#endif + +// Info about weapons player might have in his/her possession +typedef struct weapon_data_s +{ + int m_iId; + int m_iClip; + + float m_flNextPrimaryAttack; + float m_flNextSecondaryAttack; + float m_flTimeWeaponIdle; + + int m_fInReload; + int m_fInSpecialReload; + float m_flNextReload; + float m_flPumpTime; + float m_fReloadTime; + + float m_fAimedDamage; + float m_fNextAimBonus; + int m_fInZoom; + int m_iWeaponState; + + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; +} weapon_data_t; + +#endif diff --git a/common/wrect.h b/common/wrect.h new file mode 100644 index 0000000..620b816 --- /dev/null +++ b/common/wrect.h @@ -0,0 +1,16 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined( WRECTH ) +#define WRECTH + +typedef struct rect_s +{ + int left, right, top, bottom; +} wrect_t; + +#endif \ No newline at end of file diff --git a/debug.bat b/debug.bat new file mode 100644 index 0000000..4e0d347 --- /dev/null +++ b/debug.bat @@ -0,0 +1,116 @@ +@echo off + +set MSDEV=BuildConsole +set CONFIG=/ShowTime /ShowAgent /nologo /cfg= +set MSDEV=msdev +set CONFIG=/make +set build_type=debug +set BUILD_ERROR= +call vcvars32 + +%MSDEV% cl_dll/cl_dll.dsp %CONFIG%"cl_dll - Win32 Debug" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% dlls/hl.dsp %CONFIG%"hl - Win32 Debug" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% mainui/mainui.dsp %CONFIG%"mainui - Win32 Debug" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/bsp31migrate/bsp31migrate.dsp %CONFIG%"bsp31migrate - Win32 Debug" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/hlmv/hlmv.dsp %CONFIG%"hlmv - Win32 Debug" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/p2csg/p2csg.dsp %CONFIG%"p2csg - Win32 Debug" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/p2bsp/p2bsp.dsp %CONFIG%"p2bsp - Win32 Debug" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/p2vis/p2vis.dsp %CONFIG%"p2vis - Win32 Debug" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/p2rad/dlight.dsp %CONFIG%"dlight - Win32 Debug" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/p2rad/hlrad.dsp %CONFIG%"hlrad - Win32 Debug" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/p2rad/p1rad.dsp %CONFIG%"p1rad - Win32 Debug" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/p2rad/p2rad.dsp %CONFIG%"p2rad - Win32 Debug" %build_target% +if errorlevel 1 set BUILD_ERROR=1 +%MSDEV% utils/makefont/makefont.dsp %CONFIG%"makefont - Win32 Debug" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/maketex/maketex.dsp %CONFIG%"maketex - Win32 Debug" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/makewad/makewad.dsp %CONFIG%"makewad - Win32 Debug" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/decal2tga/decal2tga.dsp %CONFIG%"decal2tga - Win32 Debug" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/stalker2tga/stalker2tga.dsp %CONFIG%"stalker2tga - Win32 Debug" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/reqtest/reqtest.dsp %CONFIG%"reqtest - Win32 Debug" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/spritegen/spritegen.dsp %CONFIG%"spritegen - Win32 Debug" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/stalker2tga/stalker2tga.dsp %CONFIG%"stalker2tga - Win32 Debug" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/studiomdl/studiomdl.dsp %CONFIG%"studiomdl - Win32 Debug" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +if "%BUILD_ERROR%"=="" goto build_ok + +echo ********************* +echo ********************* +echo *** Build Errors! *** +echo ********************* +echo ********************* +echo press any key to exit +echo ********************* +pause>nul +goto done + + +@rem +@rem Successful build +@rem +:build_ok + +rem //delete log files +if exist dlls\hl.plg del /f /q dlls\hl.plg +if exist cl_dll\cl_dll.plg del /f /q cl_dll\cl_dll.plg +if exist mainui\mainui.plg del /f /q mainui\mainui.plg +if exist utils\bsp31migrate\bsp31migrate.plg del /f /q utils\bsp31migrate\bsp31migrate.plg +if exist utils\hlmv\hlmv.plg del /f /q utils\hlmv\hlmv.plg +if exist utils\makefont\makefont.plg del /f /q utils\makefont\makefont.plg +if exist utils\maketex\maketex.plg del /f /q utils\maketex\maketex.plg +if exist utils\makewad\makewad.plg del /f /q utils\makewad\makewad.plg +if exist utils\p2csg\p2csg.plg del /f /q utils\p2csg\p2csg.plg +if exist utils\p2bsp\p2bsp.plg del /f /q utils\p2bsp\p2bsp.plg +if exist utils\p2vis\p2vis.plg del /f /q utils\p2vis\p2vis.plg +if exist utils\p2rad\dlight.plg del /f /q utils\p2rad\dlight.plg +if exist utils\p2rad\hlrad.plg del /f /q utils\p2rad\hlrad.plg +if exist utils\p2rad\p1rad.plg del /f /q utils\p2rad\p1rad.plg +if exist utils\p2rad\p2rad.plg del /f /q utils\p2rad\p2rad.plg +if exist utils\decal2tga\decal2tga.plg del /f /q utils\decal2tga\decal2tga.plg +if exist utils\stalker2tga\stalker2tga.plg del /f /q utils\stalker2tga\stalker2tga.plg +if exist utils\reqtest\reqtest.plg del /f /q utils\reqtest\reqtest.plg +if exist utils\spritegen\spritegen.plg del /f /q utils\spritegen\spritegen.plg +if exist utils\studiomdl\studiomdl.plg del /f /q utils\studiomdl\studiomdl.plg + +echo +echo Build succeeded! +echo +:done \ No newline at end of file diff --git a/dlls/AI_Target.h b/dlls/AI_Target.h new file mode 100644 index 0000000..28531f5 --- /dev/null +++ b/dlls/AI_Target.h @@ -0,0 +1,162 @@ +//========= Copyright © 2003, Valve LLC, All rights reserved. ========== +// +// Purpose: Hooks and classes for the support of humanoid NPCs with +// groovy facial animation capabilities, aka, "Actors" +// +//============================================================================= + +#ifndef AI_TARGET_H +#define AI_TARGET_H + +#include + +#if defined( _WIN32 ) +#pragma once +#endif + +//----------------------------------------------------------------------------- +// CAI_BaseActor +// +// Purpose: The base class for all facially expressive NPCS. +// +//----------------------------------------------------------------------------- + +class CMonsterTarget_t +{ +public: + enum TargetType_e + { + LOOKAT_ENTITY = 0, + LOOKAT_POSITION, + LOOKAT_BOTH + }; +public: + bool IsThis( CBaseEntity *pThis ) + { + return (pThis == m_hTarget); + }; + + const Vector &GetPosition( void ) + { + if( m_eType == LOOKAT_ENTITY && m_hTarget != NULL ) + m_vecPosition = m_hTarget->EyePosition(); + return m_vecPosition; + }; + + bool IsActive( void ) + { + if( m_flEndTime < gpGlobals->time ) + return false; + if( m_eType == LOOKAT_ENTITY && m_hTarget == NULL ) + return false; + return true; + }; + + float Interest( void ) + { + float t = (gpGlobals->time - m_flStartTime) / (m_flEndTime - m_flStartTime); + + if( t < 0.0f || t > 1.0f ) + return 0.0f; + + if( t < m_flRamp ) + { + t = t / m_flRamp; + } + else if( t > 1.0f - m_flRamp ) + { + t = (1.0 - t) / m_flRamp; + } + else + { + t = 1.0f; + } + + // ramp + t = 3.0f * t * t - 2.0f * t * t * t; + t *= m_flInterest; + + return t; + } + +public: + TargetType_e m_eType; // ???? + EHANDLE m_hTarget; + Vector m_vecPosition; + float m_flStartTime; + float m_flEndTime; + float m_flRamp; + float m_flInterest; +}; + + +class CMonsterTarget : public CUtlArray +{ +public: + void Add( CBaseEntity *pTarget, float flImportance, float flDuration, float flRamp ) + { + for( int i = 0; i < Count(); i++ ) + { + CMonsterTarget_t &target = Element( i ); + + if( target.m_hTarget == pTarget ) + { + Remove( i ); + break; + } + } + + Add( CMonsterTarget_t::LOOKAT_ENTITY, pTarget, g_vecZero, flImportance, flDuration, flRamp ); + } + + void Add( const Vector &vecPosition, float flImportance, float flDuration, float flRamp ) + { + for( int i = 0; i < Count(); i++ ) + { + CMonsterTarget_t &target = Element( i ); + + if( target.m_vecPosition == vecPosition ) + { + Remove( i ); + break; + } + } + + Add( CMonsterTarget_t::LOOKAT_POSITION, NULL, vecPosition, flImportance, flDuration, flRamp ); + } + + void Add( CBaseEntity *pTarget, const Vector &vecPosition, float flImportance, float flDuration, float flRamp ) + { + for( int i = 0; i < Count(); i++ ) + { + CMonsterTarget_t &target = Element( i ); + + if( target.m_hTarget == pTarget ) + { + Remove( i ); + break; + } + } + + Add( CMonsterTarget_t::LOOKAT_BOTH, pTarget, vecPosition, flImportance, flDuration, flRamp ); + } + +private: + void Add( CMonsterTarget_t::TargetType_e type, CBaseEntity *pTarget, const Vector &vecPosition, float flImportance, float flDuration, float flRamp ) + { + int i = AddToTail(); + CMonsterTarget_t &target = Element( i ); + + target.m_eType = type; + target.m_hTarget = pTarget; + target.m_vecPosition = vecPosition; + target.m_flInterest = flImportance; + target.m_flStartTime = gpGlobals->time; + target.m_flEndTime = gpGlobals->time + flDuration; + target.m_flRamp = flRamp / flDuration; + } +}; + + +//----------------------------------------------------------------------------- +#endif // AI_TARGET_H \ No newline at end of file diff --git a/dlls/activity.h b/dlls/activity.h new file mode 100644 index 0000000..be534e8 --- /dev/null +++ b/dlls/activity.h @@ -0,0 +1,172 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef ACTIVITY_H +#define ACTIVITY_H + +typedef enum +{ + ACT_RESET = 0, // Set m_Activity to this invalid value to force a reset to m_IdealActivity + ACT_IDLE = 1, + ACT_GUARD, + ACT_WALK, + ACT_RUN, + ACT_FLY, // Fly (and flap if appropriate) + ACT_SWIM, + ACT_HOP, // vertical jump + ACT_LEAP, // long forward jump + ACT_FALL, + ACT_LAND, + ACT_STRAFE_LEFT, + ACT_STRAFE_RIGHT, + ACT_ROLL_LEFT, // tuck and roll, left + ACT_ROLL_RIGHT, // tuck and roll, right + ACT_TURN_LEFT, // turn quickly left (stationary) + ACT_TURN_RIGHT, // turn quickly right (stationary) + ACT_CROUCH, // the act of crouching down from a standing position + ACT_CROUCHIDLE, // holding body in crouched position (loops) + ACT_STAND, // the act of standing from a crouched position + ACT_USE, + ACT_SIGNAL1, + ACT_SIGNAL2, + ACT_SIGNAL3, + ACT_TWITCH, + ACT_COWER, + ACT_SMALL_FLINCH, + ACT_BIG_FLINCH, + ACT_RANGE_ATTACK1, + ACT_RANGE_ATTACK2, + ACT_MELEE_ATTACK1, + ACT_MELEE_ATTACK2, + ACT_RELOAD, + ACT_ARM, // pull out gun, for instance + ACT_DISARM, // reholster gun + ACT_EAT, // monster chowing on a large food item (loop) + ACT_DIESIMPLE, + ACT_DIEBACKWARD, + ACT_DIEFORWARD, + ACT_DIEVIOLENT, + ACT_BARNACLE_HIT, // barnacle tongue hits a monster + ACT_BARNACLE_PULL, // barnacle is lifting the monster ( loop ) + ACT_BARNACLE_CHOMP, // barnacle latches on to the monster + ACT_BARNACLE_CHEW, // barnacle is holding the monster in its mouth ( loop ) + ACT_SLEEP, + ACT_INSPECT_FLOOR, // for active idles, look at something on or near the floor + ACT_INSPECT_WALL, // for active idles, look at something directly ahead of you ( doesn't HAVE to be a wall or on a wall ) + ACT_IDLE_ANGRY, // alternate idle animation in which the monster is clearly agitated. (loop) + ACT_WALK_HURT, // limp (loop) + ACT_RUN_HURT, // limp (loop) + ACT_HOVER, // Idle while in flight + ACT_GLIDE, // Fly (don't flap) + ACT_FLY_LEFT, // Turn left in flight + ACT_FLY_RIGHT, // Turn right in flight + ACT_DETECT_SCENT, // this means the monster smells a scent carried by the air + ACT_SNIFF, // this is the act of actually sniffing an item in front of the monster + ACT_BITE, // some large monsters can eat small things in one bite. This plays one time, EAT loops. + ACT_THREAT_DISPLAY, // without attacking, monster demonstrates that it is angry. (Yell, stick out chest, etc ) + ACT_FEAR_DISPLAY, // monster just saw something that it is afraid of + ACT_EXCITED, // for some reason, monster is excited. Sees something he really likes to eat, or whatever. + ACT_SPECIAL_ATTACK1, // very monster specific special attacks. + ACT_SPECIAL_ATTACK2, + ACT_COMBAT_IDLE, // agitated idle. + ACT_WALK_SCARED, + ACT_RUN_SCARED, + ACT_VICTORY_DANCE, // killed a player, do a victory dance. + ACT_DIE_HEADSHOT, // die, hit in head. + ACT_DIE_CHESTSHOT, // die, hit in chest + ACT_DIE_GUTSHOT, // die, hit in gut + ACT_DIE_BACKSHOT, // die, hit in back + ACT_FLINCH_HEAD, + ACT_FLINCH_CHEST, + ACT_FLINCH_STOMACH, + ACT_FLINCH_LEFTARM, + ACT_FLINCH_RIGHTARM, + ACT_FLINCH_LEFTLEG, + ACT_FLINCH_RIGHTLEG, + + // acts for viewmodel + ACT_VM_NONE, // weapon viewmodel animations + ACT_VM_DEPLOY, // deploy + ACT_VM_DEPLOY_EMPTY, // deploy empty weapon + ACT_VM_HOLSTER, // holster empty weapon + ACT_VM_HOLSTER_EMPTY, + ACT_VM_IDLE, + ACT_VM_IDLE_IS, // IronSight animations + ACT_VM_RANGE_ATTACK, + ACT_VM_RANGE_ATTACK_IS, // IronSight animations + ACT_VM_MELEE_ATTACK, + ACT_VM_MELEE_ATTACK_IS, // IronSight animations + ACT_VM_SHOOT_LAST, + ACT_VM_SHOOT_LAST_IS, // IronSight animations + ACT_VM_LAST_MELEE_ATTACK, + ACT_VM_LAST_MELEE_ATTACK_IS, // IronSight animations + ACT_VM_START_RELOAD, + ACT_VM_START_RELOAD_IS, + ACT_VM_RELOAD, + ACT_VM_RELOAD_IS, + ACT_VM_RELOAD_EMPTY, + ACT_VM_RELOAD_EMPTY_IS, + ACT_VM_TURNON, // switch firemode on + ACT_VM_TURNOFF, // switch firemode off + ACT_VM_PUMP, // user animations + ACT_VM_PUMP_IS, + ACT_VM_PUMP_EMPTY, + ACT_VM_PUMP_EMPTY_IS, + ACT_VM_START_CHARGE, + ACT_VM_CHARGE, + ACT_VM_OVERLOAD, + ACT_VM_IDLE_EMPTY, + ACT_VM_IDLE_EMPTY_IS, + ACT_VM_IRONSIGHT_ON, + ACT_VM_IRONSIGHT_OFF, + ACT_VM_SHOOT_EMPTY, + ACT_VM_SHOOT_EMPTY_IS, + ACT_VM_IRONSIGHT_ON_EMPTY, + ACT_VM_IRONSIGHT_OFF_EMPTY, + ACT_VM_RESERVED0, // reserved acts for future expansions + ACT_VM_RESERVED1, + ACT_VM_RESERVED2, + ACT_VM_RESERVED3, + ACT_VM_RESERVED4, + + // continue enumerate acts for monsters + ACT_FLASHLIGHT, + ACT_WALKBACK_FIRE, + + ACT_FIRINGWALK, + ACT_FIRINGRUN, + ACT_DIERAGDOLL, + ACT_180_LEFT, // 180 degree left turn + ACT_180_RIGHT, + ACT_90_LEFT, // 90 degree turns + ACT_90_RIGHT, + + // Sometimes, you just want to set an NPC's sequence to a sequence that doesn't actually + // have an activity. The AI will reset the NPC's sequence to whatever its IDEAL activity + // is, though. So if you set ideal activity to DO_NOT_DISTURB, the AI will not interfere + // with the NPC's current sequence. (SJB) + ACT_DO_NOT_DISTURB, + ACT_TRANSITION, +} Activity; + +typedef struct +{ + int type; + char *name; +} activity_map_t; + +extern activity_map_t activity_map[]; + +#endif // ACTIVITY_H \ No newline at end of file diff --git a/dlls/activitymap.h b/dlls/activitymap.h new file mode 100644 index 0000000..749b1de --- /dev/null +++ b/dlls/activitymap.h @@ -0,0 +1,151 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#define _A( a ) { a, #a } + +activity_map_t activity_map[] = +{ + _A( ACT_IDLE ), + _A( ACT_GUARD ), + _A( ACT_WALK ), + _A( ACT_RUN ), + _A( ACT_FLY ), + _A( ACT_SWIM ), + _A( ACT_HOP ), + _A( ACT_LEAP ), + _A( ACT_FALL ), + _A( ACT_LAND ), + _A( ACT_STRAFE_LEFT ), + _A( ACT_STRAFE_RIGHT ), + _A( ACT_ROLL_LEFT ), + _A( ACT_ROLL_RIGHT ), + _A( ACT_TURN_LEFT ), + _A( ACT_TURN_RIGHT ), + _A( ACT_CROUCH ), + _A( ACT_CROUCHIDLE ), + _A( ACT_STAND ), + _A( ACT_USE ), + _A( ACT_SIGNAL1 ), + _A( ACT_SIGNAL2 ), + _A( ACT_SIGNAL3 ), + _A( ACT_TWITCH ), + _A( ACT_COWER ), + _A( ACT_SMALL_FLINCH ), + _A( ACT_BIG_FLINCH ), + _A( ACT_RANGE_ATTACK1 ), + _A( ACT_RANGE_ATTACK2 ), + _A( ACT_MELEE_ATTACK1 ), + _A( ACT_MELEE_ATTACK2 ), + _A( ACT_RELOAD ), + _A( ACT_ARM ), + _A( ACT_DISARM ), + _A( ACT_EAT ), + _A( ACT_DIESIMPLE ), + _A( ACT_DIEBACKWARD ), + _A( ACT_DIEFORWARD ), + _A( ACT_DIEVIOLENT ), + _A( ACT_BARNACLE_HIT ), + _A( ACT_BARNACLE_PULL ), + _A( ACT_BARNACLE_CHOMP ), + _A( ACT_BARNACLE_CHEW ), + _A( ACT_SLEEP ), + _A( ACT_INSPECT_FLOOR ), + _A( ACT_INSPECT_WALL ), + _A( ACT_IDLE_ANGRY ), + _A( ACT_WALK_HURT ), + _A( ACT_RUN_HURT ), + _A( ACT_HOVER ), + _A( ACT_GLIDE ), + _A( ACT_FLY_LEFT ), + _A( ACT_FLY_RIGHT ), + _A( ACT_DETECT_SCENT ), + _A( ACT_SNIFF ), + _A( ACT_BITE ), + _A( ACT_THREAT_DISPLAY ), + _A( ACT_FEAR_DISPLAY ), + _A( ACT_EXCITED ), + _A( ACT_SPECIAL_ATTACK1 ), + _A( ACT_SPECIAL_ATTACK2 ), + _A( ACT_COMBAT_IDLE ), + _A( ACT_WALK_SCARED ), + _A( ACT_RUN_SCARED ), + _A( ACT_VICTORY_DANCE ), + _A( ACT_DIE_HEADSHOT ), + _A( ACT_DIE_CHESTSHOT ), + _A( ACT_DIE_GUTSHOT ), + _A( ACT_DIE_BACKSHOT ), + _A( ACT_FLINCH_HEAD ), + _A( ACT_FLINCH_CHEST ), + _A( ACT_FLINCH_STOMACH ), + _A( ACT_FLINCH_LEFTARM ), + _A( ACT_FLINCH_RIGHTARM ), + _A( ACT_FLINCH_LEFTLEG ), + _A( ACT_FLINCH_RIGHTLEG ), + _A( ACT_VM_NONE ), + _A( ACT_VM_DEPLOY ), + _A( ACT_VM_DEPLOY_EMPTY ), + _A( ACT_VM_HOLSTER ), + _A( ACT_VM_HOLSTER_EMPTY ), + _A( ACT_VM_IDLE ), + _A( ACT_VM_IDLE_IS ), + _A( ACT_VM_RANGE_ATTACK ), + _A( ACT_VM_RANGE_ATTACK_IS ), + _A( ACT_VM_MELEE_ATTACK ), + _A( ACT_VM_MELEE_ATTACK_IS ), + _A( ACT_VM_SHOOT_LAST ), + _A( ACT_VM_SHOOT_LAST_IS ), + _A( ACT_VM_LAST_MELEE_ATTACK ), + _A( ACT_VM_LAST_MELEE_ATTACK_IS ), + _A( ACT_VM_START_RELOAD ), + _A( ACT_VM_START_RELOAD_IS ), + _A( ACT_VM_RELOAD ), + _A( ACT_VM_RELOAD_IS ), + _A( ACT_VM_RELOAD_EMPTY ), + _A( ACT_VM_RELOAD_EMPTY_IS ), + _A( ACT_VM_TURNON ), + _A( ACT_VM_TURNOFF ), + _A( ACT_VM_PUMP ), + _A( ACT_VM_PUMP_IS ), + _A( ACT_VM_PUMP_EMPTY ), + _A( ACT_VM_PUMP_EMPTY_IS ), + _A( ACT_VM_START_CHARGE ), + _A( ACT_VM_CHARGE ), + _A( ACT_VM_OVERLOAD ), + _A( ACT_VM_IDLE_EMPTY ), + _A( ACT_VM_IDLE_EMPTY_IS ), + _A( ACT_VM_IRONSIGHT_ON ), + _A( ACT_VM_IRONSIGHT_OFF ), + _A( ACT_VM_SHOOT_EMPTY ), + _A( ACT_VM_SHOOT_EMPTY_IS ), + _A( ACT_VM_IRONSIGHT_ON_EMPTY ), + _A( ACT_VM_IRONSIGHT_OFF_EMPTY ), + _A( ACT_VM_RESERVED0 ), + _A( ACT_VM_RESERVED1 ), + _A( ACT_VM_RESERVED2 ), + _A( ACT_VM_RESERVED3 ), + _A( ACT_VM_RESERVED4 ), + _A( ACT_FLASHLIGHT ), + _A( ACT_WALKBACK_FIRE ), + _A( ACT_FIRINGWALK ), + _A( ACT_FIRINGRUN ), + _A( ACT_DIERAGDOLL ), + _A( ACT_180_LEFT ), + _A( ACT_180_RIGHT ), + _A( ACT_90_LEFT ), + _A( ACT_90_RIGHT ), + _A( ACT_DO_NOT_DISTURB ), + _A( ACT_TRANSITION ), + 0, NULL +}; \ No newline at end of file diff --git a/dlls/aflock.cpp b/dlls/aflock.cpp new file mode 100644 index 0000000..451af2e --- /dev/null +++ b/dlls/aflock.cpp @@ -0,0 +1,910 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "squadmonster.h" + +#define AFLOCK_MAX_RECRUIT_RADIUS 1024 +#define AFLOCK_FLY_SPEED 125 +#define AFLOCK_TURN_RATE 75 +#define AFLOCK_ACCELERATE 10 +#define AFLOCK_CHECK_DIST 192 +#define AFLOCK_TOO_CLOSE 100 +#define AFLOCK_TOO_FAR 256 + +//========================================================= +//========================================================= +class CFlockingFlyerFlock : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void SpawnFlock( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + // Sounds are shared by the flock + static void PrecacheFlockSounds( void ); + + int m_cFlockSize; + float m_flFlockRadius; +}; + +TYPEDESCRIPTION CFlockingFlyerFlock::m_SaveData[] = +{ + DEFINE_FIELD( CFlockingFlyerFlock, m_cFlockSize, FIELD_INTEGER ), + DEFINE_FIELD( CFlockingFlyerFlock, m_flFlockRadius, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CFlockingFlyerFlock, CBaseMonster ); + +//========================================================= +//========================================================= +class CFlockingFlyer : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SpawnCommonCode( void ); + void EXPORT IdleThink( void ); + void BoidAdvanceFrame( void ); + void EXPORT FormFlock( void ); + void EXPORT Start( void ); + void EXPORT FlockLeaderThink( void ); + void EXPORT FlockFollowerThink( void ); + void EXPORT FallHack( void ); + void MakeSound( void ); + void AlertFlock( void ); + void SpreadFlock( void ); + void SpreadFlock2( void ); + void Killed( entvars_t *pevAttacker, int iGib ); + void Poop ( void ); + BOOL FPathBlocked( void ); + //void KeyValue( KeyValueData *pkvd ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int IsLeader( void ) { return m_pSquadLeader == this; } + int InSquad( void ) { return m_pSquadLeader != NULL; } + int SquadCount( void ); + void SquadRemove( CFlockingFlyer *pRemove ); + void SquadUnlink( void ); + void SquadAdd( CFlockingFlyer *pAdd ); + void SquadDisband( void ); + + CFlockingFlyer *m_pSquadLeader; + CFlockingFlyer *m_pSquadNext; + BOOL m_fTurning;// is this boid turning? + BOOL m_fCourseAdjust;// followers set this flag TRUE to override flocking while they avoid something + BOOL m_fPathBlocked;// TRUE if there is an obstacle ahead + Vector m_vecReferencePoint;// last place we saw leader + Vector m_vecAdjustedVelocity;// adjusted velocity (used when fCourseAdjust is TRUE) + float m_flGoalSpeed; + float m_flLastBlockedTime; + float m_flFakeBlockedTime; + float m_flAlertTime; + float m_flFlockNextSoundTime; +}; +LINK_ENTITY_TO_CLASS( monster_flyer, CFlockingFlyer ); +LINK_ENTITY_TO_CLASS( monster_flyer_flock, CFlockingFlyerFlock ); + +TYPEDESCRIPTION CFlockingFlyer::m_SaveData[] = +{ + DEFINE_FIELD( CFlockingFlyer, m_pSquadLeader, FIELD_CLASSPTR ), + DEFINE_FIELD( CFlockingFlyer, m_pSquadNext, FIELD_CLASSPTR ), + DEFINE_FIELD( CFlockingFlyer, m_fTurning, FIELD_BOOLEAN ), + DEFINE_FIELD( CFlockingFlyer, m_fCourseAdjust, FIELD_BOOLEAN ), + DEFINE_FIELD( CFlockingFlyer, m_fPathBlocked, FIELD_BOOLEAN ), + DEFINE_FIELD( CFlockingFlyer, m_vecReferencePoint, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CFlockingFlyer, m_vecAdjustedVelocity, FIELD_VECTOR ), + DEFINE_FIELD( CFlockingFlyer, m_flGoalSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CFlockingFlyer, m_flLastBlockedTime, FIELD_TIME ), + DEFINE_FIELD( CFlockingFlyer, m_flFakeBlockedTime, FIELD_TIME ), + DEFINE_FIELD( CFlockingFlyer, m_flAlertTime, FIELD_TIME ), +// DEFINE_FIELD( CFlockingFlyer, m_flFlockNextSoundTime, FIELD_TIME ), // don't need to save +}; + +IMPLEMENT_SAVERESTORE( CFlockingFlyer, CBaseMonster ); + +//========================================================= +//========================================================= +void CFlockingFlyerFlock :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "iFlockSize")) + { + m_cFlockSize = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "flFlockRadius")) + { + m_flFlockRadius = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } +} + +//========================================================= +//========================================================= +void CFlockingFlyerFlock :: Spawn( ) +{ + Precache( ); + SpawnFlock(); + + REMOVE_ENTITY(ENT(pev)); // dump the spawn ent +} + +//========================================================= +//========================================================= +void CFlockingFlyerFlock :: Precache( ) +{ + //PRECACHE_MODEL("models/aflock.mdl"); + PRECACHE_MODEL("models/boid.mdl"); + + PrecacheFlockSounds(); +} + + +void CFlockingFlyerFlock :: PrecacheFlockSounds( void ) +{ + PRECACHE_SOUND("boid/boid_alert1.wav" ); + PRECACHE_SOUND("boid/boid_alert2.wav" ); + + PRECACHE_SOUND("boid/boid_idle1.wav" ); + PRECACHE_SOUND("boid/boid_idle2.wav" ); +} + +//========================================================= +//========================================================= +void CFlockingFlyerFlock :: SpawnFlock( void ) +{ + float R = m_flFlockRadius; + int iCount; + Vector vecSpot; + CFlockingFlyer *pBoid, *pLeader; + + pLeader = pBoid = NULL; + + for ( iCount = 0 ; iCount < m_cFlockSize ; iCount++ ) + { + pBoid = GetClassPtr( (CFlockingFlyer *)NULL ); + + if ( !pLeader ) + { + // make this guy the leader. + pLeader = pBoid; + + pLeader->m_pSquadLeader = pLeader; + pLeader->m_pSquadNext = NULL; + } + + vecSpot.x = RANDOM_FLOAT( -R, R ); + vecSpot.y = RANDOM_FLOAT( -R, R ); + vecSpot.z = RANDOM_FLOAT( 0, 16 ); + vecSpot = pev->origin + vecSpot; + + UTIL_SetOrigin(pBoid, vecSpot); + pBoid->pev->movetype = MOVETYPE_FLY; + pBoid->SpawnCommonCode(); + pBoid->pev->flags &= ~FL_ONGROUND; + pBoid->pev->velocity = g_vecZero; + pBoid->pev->angles = pev->angles; + + pBoid->pev->frame = 0; + pBoid->SetNextThink( 0.2 ); + pBoid->SetThink(& CFlockingFlyer :: IdleThink ); + + if ( pBoid != pLeader ) + { + pLeader->SquadAdd( pBoid ); + } + } +} + +//========================================================= +//========================================================= +void CFlockingFlyer :: Spawn( ) +{ + Precache( ); + SpawnCommonCode(); + + pev->frame = 0; + SetNextThink( 0.1 ); + SetThink(&CFlockingFlyer :: IdleThink ); +} + +//========================================================= +//========================================================= +void CFlockingFlyer :: Precache( ) +{ + //PRECACHE_MODEL("models/aflock.mdl"); + PRECACHE_MODEL("models/boid.mdl"); + CFlockingFlyerFlock::PrecacheFlockSounds(); +} + +//========================================================= +//========================================================= +void CFlockingFlyer :: MakeSound( void ) +{ + if ( m_flAlertTime > gpGlobals->time ) + { + // make agitated sounds + switch ( RANDOM_LONG( 0, 1 ) ) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "boid/boid_alert1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "boid/boid_alert2.wav", 1, ATTN_NORM ); break; + } + + return; + } + + // make normal sound + switch ( RANDOM_LONG( 0, 1 ) ) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "boid/boid_idle1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "boid/boid_idle2.wav", 1, ATTN_NORM ); break; + } +} + +//========================================================= +//========================================================= +void CFlockingFlyer :: Killed( entvars_t *pevAttacker, int iGib ) +{ + CFlockingFlyer *pSquad; + + pSquad = (CFlockingFlyer *)m_pSquadLeader; + + while ( pSquad ) + { + pSquad->m_flAlertTime = gpGlobals->time + 15; + pSquad = (CFlockingFlyer *)pSquad->m_pSquadNext; + } + + if ( m_pSquadLeader ) + { + m_pSquadLeader->SquadRemove( this ); + } + + pev->deadflag = DEAD_DEAD; + + pev->framerate = 0; + pev->effects |= EF_NOINTERP; + + UTIL_SetSize( pev, Vector(0,0,0), Vector(0,0,0) ); + pev->movetype = MOVETYPE_TOSS; + + SetThink(&CFlockingFlyer :: FallHack ); + SetNextThink( 0.1 ); +} + +void CFlockingFlyer :: FallHack( void ) +{ + if ( pev->flags & FL_ONGROUND ) + { + if ( !FClassnameIs ( pev->groundentity, "worldspawn" ) ) + { + pev->flags &= ~FL_ONGROUND; + SetNextThink( 0.1 ); + } + else + { + pev->velocity = g_vecZero; + SetThink( NULL ); + } + } +} + +//========================================================= +//========================================================= +void CFlockingFlyer :: SpawnCommonCode( ) +{ + pev->deadflag = DEAD_NO; + pev->classname = MAKE_STRING("monster_flyer"); + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_FLY; + pev->takedamage = DAMAGE_NO; + pev->health = 1; + + m_fPathBlocked = FALSE;// obstacles will be detected + m_flFieldOfView = 0.2; + + //SET_MODEL(ENT(pev), "models/aflock.mdl"); + SET_MODEL(ENT(pev), "models/boid.mdl"); + +// UTIL_SetSize(pev, Vector(0,0,0), Vector(0,0,0)); + UTIL_SetSize(pev, Vector(-5,-5,0), Vector(5,5,2)); +} + +//========================================================= +//========================================================= +void CFlockingFlyer :: BoidAdvanceFrame ( ) +{ + float flapspeed = (pev->speed - pev->armorvalue) / AFLOCK_ACCELERATE; + pev->armorvalue = pev->armorvalue * .8 + pev->speed * .2; + + if (flapspeed < 0) flapspeed = -flapspeed; + if (flapspeed < 0.25) flapspeed = 0.25; + if (flapspeed > 1.9) flapspeed = 1.9; + + pev->framerate = flapspeed; + + // lean + pev->avelocity.x = - (pev->angles.x + flapspeed * 5); + + // bank + pev->avelocity.z = - (pev->angles.z + pev->avelocity.y); + + // pev->framerate = flapspeed; + StudioFrameAdvance( 0.1 ); +} + +//========================================================= +//========================================================= +void CFlockingFlyer :: IdleThink( void ) +{ + SetNextThink( 0.2 ); + + // see if there's a client in the same pvs as the monster + if ( !FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) + { + SetThink(&CFlockingFlyer :: Start ); + SetNextThink( 0.1 ); + } +} + +//========================================================= +// Start - player enters the pvs, so get things going. +//========================================================= +void CFlockingFlyer :: Start( void ) +{ + SetNextThink( 0.1 ); + + if ( IsLeader() ) + { + SetThink(&CFlockingFlyer :: FlockLeaderThink ); + } + else + { + SetThink(&CFlockingFlyer :: FlockFollowerThink ); + } + +/* + Vector vecTakeOff; + vecTakeOff = Vector ( 0 , 0 , 0 ); + + vecTakeOff.z = 50 + RANDOM_FLOAT ( 0, 100 ); + vecTakeOff.x = 20 - RANDOM_FLOAT ( 0, 40); + vecTakeOff.y = 20 - RANDOM_FLOAT ( 0, 40); + + pev->velocity = vecTakeOff; + + + pev->speed = pev->velocity.Length(); + pev->sequence = 0; +*/ + SetActivity ( ACT_FLY ); + ResetSequenceInfo( ); + BoidAdvanceFrame( ); + + pev->speed = AFLOCK_FLY_SPEED;// no delay! +} + +//========================================================= +// Leader boid calls this to form a flock from surrounding boids +//========================================================= +void CFlockingFlyer :: FormFlock( void ) +{ + if ( !InSquad() ) + { + // I am my own leader + m_pSquadLeader = this; + m_pSquadNext = NULL; + int squadCount = 1; + + CBaseEntity *pEntity = NULL; + + while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, AFLOCK_MAX_RECRUIT_RADIUS )) != NULL) + { + CBaseMonster *pRecruit = pEntity->MyMonsterPointer( ); + + if ( pRecruit && pRecruit != this && pRecruit->IsAlive() && !pRecruit->m_pCine ) + { + // Can we recruit this guy? + if ( FClassnameIs ( pRecruit->pev, "monster_flyer" ) ) + { + squadCount++; + SquadAdd( (CFlockingFlyer *)pRecruit ); + } + } + } + } + + SetThink(&CFlockingFlyer :: IdleThink );// now that flock is formed, go to idle and wait for a player to come along. + SetNextThink( 0 ); +} + +//========================================================= +// Searches for boids that are too close and pushes them away +//========================================================= +void CFlockingFlyer :: SpreadFlock( ) +{ + Vector vecDir; + float flSpeed;// holds vector magnitude while we fiddle with the direction + + CFlockingFlyer *pList = m_pSquadLeader; + while ( pList ) + { + if ( pList != this && ( pev->origin - pList->pev->origin ).Length() <= AFLOCK_TOO_CLOSE ) + { + // push the other away + vecDir = ( pList->pev->origin - pev->origin ); + vecDir = vecDir.Normalize(); + + // store the magnitude of the other boid's velocity, and normalize it so we + // can average in a course that points away from the leader. + flSpeed = pList->pev->velocity.Length(); + pList->pev->velocity = pList->pev->velocity.Normalize(); + pList->pev->velocity = ( pList->pev->velocity + vecDir ) * 0.5; + pList->pev->velocity = pList->pev->velocity * flSpeed; + } + + pList = pList->m_pSquadNext; + } +} + +//========================================================= +// Alters the caller's course if he's too close to others +// +// This function should **ONLY** be called when Caller's velocity is normalized!! +//========================================================= +void CFlockingFlyer :: SpreadFlock2 ( ) +{ + Vector vecDir; + + CFlockingFlyer *pList = m_pSquadLeader; + while ( pList ) + { + if ( pList != this && ( pev->origin - pList->pev->origin ).Length() <= AFLOCK_TOO_CLOSE ) + { + vecDir = ( pev->origin - pList->pev->origin ); + vecDir = vecDir.Normalize(); + + pev->velocity = (pev->velocity + vecDir); + } + + pList = pList->m_pSquadNext; + } +} + +//========================================================= +// FBoidPathBlocked - returns TRUE if there is an obstacle ahead +//========================================================= +BOOL CFlockingFlyer :: FPathBlocked( ) +{ + TraceResult tr; + Vector vecDist;// used for general measurements + Vector vecDir;// used for general measurements + BOOL fBlocked; + + if ( m_flFakeBlockedTime > gpGlobals->time ) + { + m_flLastBlockedTime = gpGlobals->time; + return TRUE; + } + + // use VELOCITY, not angles, not all boids point the direction they are flying + //vecDir = UTIL_VecToAngles( pevBoid->velocity ); + UTIL_MakeVectors ( pev->angles ); + + fBlocked = FALSE;// assume the way ahead is clear + + // check for obstacle ahead + UTIL_TraceLine(pev->origin, pev->origin + gpGlobals->v_forward * AFLOCK_CHECK_DIST, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + m_flLastBlockedTime = gpGlobals->time; + fBlocked = TRUE; + } + + // extra wide checks + UTIL_TraceLine(pev->origin + gpGlobals->v_right * 12, pev->origin + gpGlobals->v_right * 12 + gpGlobals->v_forward * AFLOCK_CHECK_DIST, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + m_flLastBlockedTime = gpGlobals->time; + fBlocked = TRUE; + } + + UTIL_TraceLine(pev->origin - gpGlobals->v_right * 12, pev->origin - gpGlobals->v_right * 12 + gpGlobals->v_forward * AFLOCK_CHECK_DIST, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + m_flLastBlockedTime = gpGlobals->time; + fBlocked = TRUE; + } + + if ( !fBlocked && gpGlobals->time - m_flLastBlockedTime > 6 ) + { + // not blocked, and it's been a few seconds since we've actually been blocked. + m_flFakeBlockedTime = gpGlobals->time + RANDOM_LONG(1, 3); + } + + return fBlocked; +} + + +//========================================================= +// Leader boids use this think every tenth +//========================================================= +void CFlockingFlyer :: FlockLeaderThink( void ) +{ + TraceResult tr; + Vector vecDist;// used for general measurements + Vector vecDir;// used for general measurements + int cProcessed = 0;// keep track of how many other boids we've processed + float flLeftSide; + float flRightSide; + + + SetNextThink( 0.1 ); + + UTIL_MakeVectors ( pev->angles ); + + // is the way ahead clear? + if ( !FPathBlocked () ) + { + // if the boid is turning, stop the trend. + if ( m_fTurning ) + { + m_fTurning = FALSE; + pev->avelocity.y = 0; + } + + m_fPathBlocked = FALSE; + + if (pev->speed <= AFLOCK_FLY_SPEED ) + pev->speed+= 5; + + pev->velocity = gpGlobals->v_forward * pev->speed; + + BoidAdvanceFrame( ); + + return; + } + + // IF we get this far in the function, the leader's path is blocked! + m_fPathBlocked = TRUE; + + if ( !m_fTurning)// something in the way and boid is not already turning to avoid + { + // measure clearance on left and right to pick the best dir to turn + UTIL_TraceLine(pev->origin, pev->origin + gpGlobals->v_right * AFLOCK_CHECK_DIST, ignore_monsters, ENT(pev), &tr); + vecDist = (tr.vecEndPos - pev->origin); + flRightSide = vecDist.Length(); + + UTIL_TraceLine(pev->origin, pev->origin - gpGlobals->v_right * AFLOCK_CHECK_DIST, ignore_monsters, ENT(pev), &tr); + vecDist = (tr.vecEndPos - pev->origin); + flLeftSide = vecDist.Length(); + + // turn right if more clearance on right side + if ( flRightSide > flLeftSide ) + { + pev->avelocity.y = -AFLOCK_TURN_RATE; + m_fTurning = TRUE; + } + // default to left turn :) + else if ( flLeftSide > flRightSide ) + { + pev->avelocity.y = AFLOCK_TURN_RATE; + m_fTurning = TRUE; + } + else + { + // equidistant. Pick randomly between left and right. + m_fTurning = TRUE; + + if ( RANDOM_LONG( 0, 1 ) == 0 ) + { + pev->avelocity.y = AFLOCK_TURN_RATE; + } + else + { + pev->avelocity.y = -AFLOCK_TURN_RATE; + } + } + } + SpreadFlock( ); + + pev->velocity = gpGlobals->v_forward * pev->speed; + + // check and make sure we aren't about to plow into the ground, don't let it happen + UTIL_TraceLine(pev->origin, pev->origin - gpGlobals->v_up * 16, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0 && pev->velocity.z < 0 ) + pev->velocity.z = 0; + + // maybe it did, though. + if ( FBitSet (pev->flags, FL_ONGROUND) ) + { + UTIL_SetOrigin (this, pev->origin + Vector ( 0 , 0 , 1 ) ); + pev->velocity.z = 0; + } + + if ( m_flFlockNextSoundTime < gpGlobals->time ) + { + MakeSound(); + m_flFlockNextSoundTime = gpGlobals->time + RANDOM_FLOAT( 1, 3 ); + } + + BoidAdvanceFrame( ); + + return; +} + +//========================================================= +// follower boids execute this code when flocking +//========================================================= +void CFlockingFlyer :: FlockFollowerThink( void ) +{ + TraceResult tr; + Vector vecDist; + Vector vecDir; + Vector vecDirToLeader; + float flDistToLeader; + + SetNextThink( 0.1 ); + + if ( IsLeader() || !InSquad() ) + { + // the leader has been killed and this flyer suddenly finds himself the leader. + SetThink(&CFlockingFlyer :: FlockLeaderThink ); + return; + } + + vecDirToLeader = ( m_pSquadLeader->pev->origin - pev->origin ); + flDistToLeader = vecDirToLeader.Length(); + + // match heading with leader + pev->angles = m_pSquadLeader->pev->angles; + + // + // We can see the leader, so try to catch up to it + // + if ( FInViewCone ( m_pSquadLeader ) ) + { + // if we're too far away, speed up + if ( flDistToLeader > AFLOCK_TOO_FAR ) + { + m_flGoalSpeed = m_pSquadLeader->pev->velocity.Length() * 1.5; + } + + // if we're too close, slow down + else if ( flDistToLeader < AFLOCK_TOO_CLOSE ) + { + m_flGoalSpeed = m_pSquadLeader->pev->velocity.Length() * 0.5; + } + } + else + { + // wait up! the leader isn't out in front, so we slow down to let him pass + m_flGoalSpeed = m_pSquadLeader->pev->velocity.Length() * 0.5; + } + + SpreadFlock2(); + + pev->speed = pev->velocity.Length(); + pev->velocity = pev->velocity.Normalize(); + + // if we are too far from leader, average a vector towards it into our current velocity + if ( flDistToLeader > AFLOCK_TOO_FAR ) + { + vecDirToLeader = vecDirToLeader.Normalize(); + pev->velocity = (pev->velocity + vecDirToLeader) * 0.5; + } + + // clamp speeds and handle acceleration + if ( m_flGoalSpeed > AFLOCK_FLY_SPEED * 2 ) + { + m_flGoalSpeed = AFLOCK_FLY_SPEED * 2; + } + + if ( pev->speed < m_flGoalSpeed ) + { + pev->speed += AFLOCK_ACCELERATE; + } + else if ( pev->speed > m_flGoalSpeed ) + { + pev->speed -= AFLOCK_ACCELERATE; + } + + pev->velocity = pev->velocity * pev->speed; + + BoidAdvanceFrame( ); +} + +/* + // Is this boid's course blocked? + if ( FBoidPathBlocked (pev) ) + { + // course is still blocked from last time. Just keep flying along adjusted + // velocity + if ( m_fCourseAdjust ) + { + pev->velocity = m_vecAdjustedVelocity * pev->speed; + return; + } + else // set course adjust flag and calculate adjusted velocity + { + m_fCourseAdjust = TRUE; + + // use VELOCITY, not angles, not all boids point the direction they are flying + //vecDir = UTIL_VecToAngles( pev->velocity ); + //UTIL_MakeVectors ( vecDir ); + + UTIL_MakeVectors ( pev->angles ); + + // measure clearance on left and right to pick the best dir to turn + UTIL_TraceLine(pev->origin, pev->origin + gpGlobals->v_right * AFLOCK_CHECK_DIST, ignore_monsters, ENT(pev), &tr); + vecDist = (tr.vecEndPos - pev->origin); + flRightSide = vecDist.Length(); + + UTIL_TraceLine(pev->origin, pev->origin - gpGlobals->v_right * AFLOCK_CHECK_DIST, ignore_monsters, ENT(pev), &tr); + vecDist = (tr.vecEndPos - pev->origin); + flLeftSide = vecDist.Length(); + + // slide right if more clearance on right side + if ( flRightSide > flLeftSide ) + { + m_vecAdjustedVelocity = gpGlobals->v_right; + } + // else slide left + else + { + m_vecAdjustedVelocity = gpGlobals->v_right * -1; + } + } + return; + } + + // if we make it this far, boids path is CLEAR! + m_fCourseAdjust = FALSE; +*/ + + +//========================================================= +// +// SquadUnlink(), Unlink the squad pointers. +// +//========================================================= +void CFlockingFlyer :: SquadUnlink( void ) +{ + m_pSquadLeader = NULL; + m_pSquadNext = NULL; +} + +//========================================================= +// +// SquadAdd(), add pAdd to my squad +// +//========================================================= +void CFlockingFlyer :: SquadAdd( CFlockingFlyer *pAdd ) +{ + ASSERT( pAdd!=NULL ); + ASSERT( !pAdd->InSquad() ); + ASSERT( this->IsLeader() ); + + pAdd->m_pSquadNext = m_pSquadNext; + m_pSquadNext = pAdd; + pAdd->m_pSquadLeader = this; +} +//========================================================= +// +// SquadRemove(), remove pRemove from my squad. +// If I am pRemove, promote m_pSquadNext to leader +// +//========================================================= +void CFlockingFlyer :: SquadRemove( CFlockingFlyer *pRemove ) +{ + ASSERT( pRemove!=NULL ); + ASSERT( this->IsLeader() ); + ASSERT( pRemove->m_pSquadLeader == this ); + + if ( SquadCount() > 2 ) + { + // Removing the leader, promote m_pSquadNext to leader + if ( pRemove == this ) + { + CFlockingFlyer *pLeader = m_pSquadNext; + + // copy the enemy LKP to the new leader + pLeader->m_vecEnemyLKP = m_vecEnemyLKP; + + if ( pLeader ) + { + CFlockingFlyer *pList = pLeader; + + while ( pList ) + { + pList->m_pSquadLeader = pLeader; + pList = pList->m_pSquadNext; + } + + } + SquadUnlink(); + } + else // removing a node + { + CFlockingFlyer *pList = this; + + // Find the node before pRemove + while ( pList->m_pSquadNext != pRemove ) + { + // assert to test valid list construction + ASSERT( pList->m_pSquadNext != NULL ); + pList = pList->m_pSquadNext; + } + // List validity + ASSERT( pList->m_pSquadNext == pRemove ); + + // Relink without pRemove + pList->m_pSquadNext = pRemove->m_pSquadNext; + + // Unlink pRemove + pRemove->SquadUnlink(); + } + } + else + SquadDisband(); +} +//========================================================= +// +// SquadCount(), return the number of members of this squad +// callable from leaders & followers +// +//========================================================= +int CFlockingFlyer :: SquadCount( void ) +{ + CFlockingFlyer *pList = m_pSquadLeader; + int squadCount = 0; + while ( pList ) + { + squadCount++; + pList = pList->m_pSquadNext; + } + + return squadCount; +} + +//========================================================= +// +// SquadDisband(), Unlink all squad members +// +//========================================================= +void CFlockingFlyer :: SquadDisband( void ) +{ + CFlockingFlyer *pList = m_pSquadLeader; + CFlockingFlyer *pNext; + + while ( pList ) + { + pNext = pList->m_pSquadNext; + pList->SquadUnlink(); + pList = pNext; + } +} diff --git a/dlls/agrunt.cpp b/dlls/agrunt.cpp new file mode 100644 index 0000000..a9d5be6 --- /dev/null +++ b/dlls/agrunt.cpp @@ -0,0 +1,1197 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Agrunt - Dominant, warlike alien grunt monster +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "squadmonster.h" +#include "weapons.h" +#include "soundent.h" +#include "hornet.h" +#include "scripted.h" + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_AGRUNT_SUPPRESS = LAST_COMMON_SCHEDULE + 1, + SCHED_AGRUNT_THREAT_DISPLAY, +}; + +//========================================================= +// monster-specific tasks +//========================================================= +enum +{ + TASK_AGRUNT_SETUP_HIDE_ATTACK = LAST_COMMON_TASK + 1, + TASK_AGRUNT_GET_PATH_TO_ENEMY_CORPSE, +}; + +int iAgruntMuzzleFlash; + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define AGRUNT_AE_HORNET1 ( 1 ) +#define AGRUNT_AE_HORNET2 ( 2 ) +#define AGRUNT_AE_HORNET3 ( 3 ) +#define AGRUNT_AE_HORNET4 ( 4 ) +#define AGRUNT_AE_HORNET5 ( 5 ) +// some events are set up in the QC file that aren't recognized by the code yet. +#define AGRUNT_AE_PUNCH ( 6 ) +#define AGRUNT_AE_BITE ( 7 ) + +#define AGRUNT_AE_LEFT_FOOT ( 10 ) +#define AGRUNT_AE_RIGHT_FOOT ( 11 ) + +#define AGRUNT_AE_LEFT_PUNCH ( 12 ) +#define AGRUNT_AE_RIGHT_PUNCH ( 13 ) + + + +#define AGRUNT_MELEE_DIST 100 + +//LRC - body definitions for the Agrunt model +#define AGRUNT_BODY_HASGUN 0 +#define AGRUNT_BODY_NOGUN 1 + +class CAGrunt : public CSquadMonster +{ +public: + void Spawn( void ); + void Precache( void ); + float MaxYawSpeed ( void ); + int Classify ( void ); + int ISoundMask ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void SetObjectCollisionBox( void ) + { + pev->absmin = pev->origin + Vector( -32, -32, 0 ); + pev->absmax = pev->origin + Vector( 32, 32, 85 ); + } + + Schedule_t* GetSchedule ( void ); + Schedule_t* GetScheduleOfType ( int Type ); + BOOL FCanCheckAttacks ( void ); + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + void StartTask ( Task_t *pTask ); + void AlertSound( void ); + void DeathSound ( void ); + void PainSound ( void ); + void AttackSound ( void ); + void PrescheduleThink ( void ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + int IRelationship( CBaseEntity *pTarget ); + void StopTalking ( void ); + BOOL ShouldSpeak( void ); + + CUSTOM_SCHEDULES; + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + static const char *pAttackHitSounds[]; + static const char *pAttackMissSounds[]; + static const char *pAttackSounds[]; + static const char *pDieSounds[]; + static const char *pPainSounds[]; + static const char *pIdleSounds[]; + static const char *pAlertSounds[]; + + BOOL m_fCanHornetAttack; + float m_flNextHornetAttackCheck; + + float m_flNextPainTime; + + // three hacky fields for speech stuff. These don't really need to be saved. + float m_flNextSpeakTime; + float m_flNextWordTime; + int m_iLastWord; +}; +LINK_ENTITY_TO_CLASS( monster_alien_grunt, CAGrunt ); + +TYPEDESCRIPTION CAGrunt::m_SaveData[] = +{ + DEFINE_FIELD( CAGrunt, m_fCanHornetAttack, FIELD_BOOLEAN ), + DEFINE_FIELD( CAGrunt, m_flNextHornetAttackCheck, FIELD_TIME ), + DEFINE_FIELD( CAGrunt, m_flNextPainTime, FIELD_TIME ), + DEFINE_FIELD( CAGrunt, m_flNextSpeakTime, FIELD_TIME ), + DEFINE_FIELD( CAGrunt, m_flNextWordTime, FIELD_TIME ), + DEFINE_FIELD( CAGrunt, m_iLastWord, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CAGrunt, CSquadMonster ); + +const char *CAGrunt::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CAGrunt::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CAGrunt::pAttackSounds[] = +{ + "agrunt/ag_attack1.wav", + "agrunt/ag_attack2.wav", + "agrunt/ag_attack3.wav", +}; + +const char *CAGrunt::pDieSounds[] = +{ + "agrunt/ag_die1.wav", + "agrunt/ag_die4.wav", + "agrunt/ag_die5.wav", +}; + +const char *CAGrunt::pPainSounds[] = +{ + "agrunt/ag_pain1.wav", + "agrunt/ag_pain2.wav", + "agrunt/ag_pain3.wav", + "agrunt/ag_pain4.wav", + "agrunt/ag_pain5.wav", +}; + +const char *CAGrunt::pIdleSounds[] = +{ + "agrunt/ag_idle1.wav", + "agrunt/ag_idle2.wav", + "agrunt/ag_idle3.wav", + "agrunt/ag_idle4.wav", +}; + +const char *CAGrunt::pAlertSounds[] = +{ + "agrunt/ag_alert1.wav", + "agrunt/ag_alert3.wav", + "agrunt/ag_alert4.wav", + "agrunt/ag_alert5.wav", +}; + +//========================================================= +// IRelationship - overridden because Human Grunts are +// Alien Grunt's nemesis. +//========================================================= +int CAGrunt::IRelationship ( CBaseEntity *pTarget ) +{ + if ( FClassnameIs( pTarget->pev, "monster_human_grunt" ) ) + { + return R_NM; + } + + return CSquadMonster :: IRelationship( pTarget ); +} + +//========================================================= +// ISoundMask +//========================================================= +int CAGrunt :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER | + bits_SOUND_DANGER; +} + +//========================================================= +// TraceAttack +//========================================================= +void CAGrunt :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if ( ptr->iHitgroup == 10 && (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_CLUB))) + { + // hit armor + if ( pev->dmgtime != gpGlobals->time || (RANDOM_LONG(0,10) < 1) ) + { + UTIL_Ricochet( ptr->vecEndPos, RANDOM_FLOAT( 1, 2) ); + pev->dmgtime = gpGlobals->time; + } + + if ( RANDOM_LONG( 0, 1 ) == 0 ) + { + Vector vecTracerDir = vecDir; + + vecTracerDir.x += RANDOM_FLOAT( -0.3, 0.3 ); + vecTracerDir.y += RANDOM_FLOAT( -0.3, 0.3 ); + vecTracerDir.z += RANDOM_FLOAT( -0.3, 0.3 ); + + vecTracerDir = vecTracerDir * -512; + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, ptr->vecEndPos ); + WRITE_BYTE( TE_TRACER ); + WRITE_COORD( ptr->vecEndPos.x ); + WRITE_COORD( ptr->vecEndPos.y ); + WRITE_COORD( ptr->vecEndPos.z ); + + WRITE_COORD( vecTracerDir.x ); + WRITE_COORD( vecTracerDir.y ); + WRITE_COORD( vecTracerDir.z ); + MESSAGE_END(); + } + + flDamage -= 20; + if (flDamage <= 0) + flDamage = 0.1;// don't hurt the monster much, but allow bits_COND_LIGHT_DAMAGE to be generated + } + else + { + SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage);// a little surface blood. + TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); + } + + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); +} + +//========================================================= +// StopTalking - won't speak again for 10-20 seconds. +//========================================================= +void CAGrunt::StopTalking( void ) +{ + m_flNextWordTime = m_flNextSpeakTime = gpGlobals->time + 10 + RANDOM_LONG(0, 10); +} + +//========================================================= +// ShouldSpeak - Should this agrunt be talking? +//========================================================= +BOOL CAGrunt::ShouldSpeak( void ) +{ + if ( m_flNextSpeakTime > gpGlobals->time ) + { + // my time to talk is still in the future. + return FALSE; + } + + if ( pev->spawnflags & SF_MONSTER_GAG ) + { + if ( m_MonsterState != MONSTERSTATE_COMBAT ) + { + // if gagged, don't talk outside of combat. + // if not going to talk because of this, put the talk time + // into the future a bit, so we don't talk immediately after + // going into combat + m_flNextSpeakTime = gpGlobals->time + 3; + return FALSE; + } + } + + return TRUE; +} + +//========================================================= +// PrescheduleThink +//========================================================= +void CAGrunt :: PrescheduleThink ( void ) +{ + if ( ShouldSpeak() ) + { + if ( m_flNextWordTime < gpGlobals->time ) + { + int num = -1; + + do + { + num = RANDOM_LONG(0,ARRAYSIZE(pIdleSounds)-1); + } while( num == m_iLastWord ); + + m_iLastWord = num; + + // play a new sound + EMIT_SOUND ( ENT(pev), CHAN_VOICE, pIdleSounds[ num ], 1.0, ATTN_NORM ); + + // is this word our last? + if ( RANDOM_LONG( 1, 10 ) <= 1 ) + { + // stop talking. + StopTalking(); + } + else + { + m_flNextWordTime = gpGlobals->time + RANDOM_FLOAT( 0.5, 1 ); + } + } + } +} + +//========================================================= +// DieSound +//========================================================= +void CAGrunt :: DeathSound ( void ) +{ + StopTalking(); + + EMIT_SOUND ( ENT(pev), CHAN_VOICE, pDieSounds[RANDOM_LONG(0,ARRAYSIZE(pDieSounds)-1)], 1.0, ATTN_NORM ); +} + +//========================================================= +// AlertSound +//========================================================= +void CAGrunt :: AlertSound ( void ) +{ + StopTalking(); + + EMIT_SOUND ( ENT(pev), CHAN_VOICE, pAlertSounds[RANDOM_LONG(0,ARRAYSIZE(pAlertSounds)-1)], 1.0, ATTN_NORM ); +} + +//========================================================= +// AttackSound +//========================================================= +void CAGrunt :: AttackSound ( void ) +{ + StopTalking(); + + EMIT_SOUND ( ENT(pev), CHAN_VOICE, pAttackSounds[RANDOM_LONG(0,ARRAYSIZE(pAttackSounds)-1)], 1.0, ATTN_NORM ); +} + +//========================================================= +// PainSound +//========================================================= +void CAGrunt :: PainSound ( void ) +{ + if ( m_flNextPainTime > gpGlobals->time ) + { + return; + } + + m_flNextPainTime = gpGlobals->time + 0.6; + + StopTalking(); + + EMIT_SOUND ( ENT(pev), CHAN_VOICE, pPainSounds[RANDOM_LONG(0,ARRAYSIZE(pPainSounds)-1)], 1.0, ATTN_NORM ); +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CAGrunt :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MILITARY; +} + +//========================================================= +// MaxYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +float CAGrunt :: MaxYawSpeed( void ) +{ + float ys; + + switch ( m_Activity ) + { + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 110; + break; + default: ys = 100; + } + + return ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CAGrunt :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case AGRUNT_AE_HORNET1: + case AGRUNT_AE_HORNET2: + case AGRUNT_AE_HORNET3: + case AGRUNT_AE_HORNET4: + case AGRUNT_AE_HORNET5: + { + // m_vecEnemyLKP should be center of enemy body + Vector vecArmPos, vecArmDir; + Vector vecDirToEnemy; + Vector angDir; + + if (HasConditions( bits_COND_SEE_ENEMY)) + { + vecDirToEnemy = ( ( m_vecEnemyLKP ) - pev->origin ); + angDir = UTIL_VecToAngles( vecDirToEnemy ); + vecDirToEnemy = vecDirToEnemy.Normalize(); + } + else + { + angDir = pev->angles; + UTIL_MakeAimVectors( angDir ); + vecDirToEnemy = gpGlobals->v_forward; + } + + pev->effects |= EF_MUZZLEFLASH; + + // make angles +-180 + if (angDir.x > 180) + { + angDir.x = angDir.x - 360; + } + + SetBlending( 0, angDir.x ); + GetAttachment( 0, vecArmPos, vecArmDir ); + + vecArmPos = vecArmPos + vecDirToEnemy * 32; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecArmPos ); + WRITE_BYTE( TE_SPRITE ); + WRITE_COORD( vecArmPos.x ); // pos + WRITE_COORD( vecArmPos.y ); + WRITE_COORD( vecArmPos.z ); + WRITE_SHORT( iAgruntMuzzleFlash ); // model + WRITE_BYTE( 6 ); // size * 10 + WRITE_BYTE( 128 ); // brightness + MESSAGE_END(); + + CBaseEntity *pHornet = CBaseEntity::Create( "hornet", vecArmPos, UTIL_VecToAngles( vecDirToEnemy ), edict() ); + UTIL_MakeVectors ( pHornet->pev->angles ); + pHornet->pev->velocity = gpGlobals->v_forward * 300; + + CBaseMonster *pHornetMonster = pHornet->MyMonsterPointer(); + + //LRC - hornets have the same allegiance as their creators + pHornetMonster->m_iPlayerReact = m_iPlayerReact; + pHornetMonster->m_iClass = m_iClass; + if (m_afMemory & bits_MEMORY_PROVOKED) // if I'm mad at the player, so are my hornets + pHornetMonster->Remember(bits_MEMORY_PROVOKED); + + if ( pHornetMonster ) + { + if (m_pCine && m_pCine->PreciseAttack()) //LRC- are we doing a scripted action? + pHornetMonster->m_hEnemy = m_hTargetEnt; + else + pHornetMonster->m_hEnemy = m_hEnemy; + } + } + break; + + case AGRUNT_AE_LEFT_FOOT: + switch (RANDOM_LONG(0,1)) + { + // left foot + case 0: EMIT_SOUND_DYN ( ENT(pev), CHAN_BODY, "player/pl_ladder2.wav", 1, ATTN_NORM, 0, 70 ); break; + case 1: EMIT_SOUND_DYN ( ENT(pev), CHAN_BODY, "player/pl_ladder4.wav", 1, ATTN_NORM, 0, 70 ); break; + } + break; + case AGRUNT_AE_RIGHT_FOOT: + // right foot + switch (RANDOM_LONG(0,1)) + { + case 0: EMIT_SOUND_DYN ( ENT(pev), CHAN_BODY, "player/pl_ladder1.wav", 1, ATTN_NORM, 0, 70 ); break; + case 1: EMIT_SOUND_DYN ( ENT(pev), CHAN_BODY, "player/pl_ladder3.wav", 1, ATTN_NORM, 0 ,70); break; + } + break; + + case AGRUNT_AE_LEFT_PUNCH: + { + CBaseEntity *pHurt = CheckTraceHullAttack( AGRUNT_MELEE_DIST, gSkillData.agruntDmgPunch, DMG_CLUB ); + + if ( pHurt ) + { + pHurt->pev->punchangle.y = -25; + pHurt->pev->punchangle.x = 8; + + // OK to use gpGlobals without calling MakeVectors, cause CheckTraceHullAttack called it above. + if ( pHurt->IsPlayer() ) + { + // this is a player. Knock him around. + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_right * 250; + } + + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + + Vector vecArmPos, vecArmAng; + GetAttachment( 0, vecArmPos, vecArmAng ); + SpawnBlood(vecArmPos, pHurt->BloodColor(), 25);// a little surface blood. + } + else + { + // Play a random attack miss sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + } + } + break; + + case AGRUNT_AE_RIGHT_PUNCH: + { + CBaseEntity *pHurt = CheckTraceHullAttack( AGRUNT_MELEE_DIST, gSkillData.agruntDmgPunch, DMG_CLUB ); + + if ( pHurt ) + { + pHurt->pev->punchangle.y = 25; + pHurt->pev->punchangle.x = 8; + + // OK to use gpGlobals without calling MakeVectors, cause CheckTraceHullAttack called it above. + if ( pHurt->IsPlayer() ) + { + // this is a player. Knock him around. + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_right * -250; + } + + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + + Vector vecArmPos, vecArmAng; + GetAttachment( 0, vecArmPos, vecArmAng ); + SpawnBlood(vecArmPos, pHurt->BloodColor(), 25);// a little surface blood. + } + else + { + // Play a random attack miss sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + } + } + break; + + default: + CSquadMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CAGrunt :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/agrunt.mdl"); + UTIL_SetSize(pev, Vector(-32, -32, 0), Vector(32, 32, 64)); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + if (pev->health == 0) + pev->health = gSkillData.agruntHealth; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_afCapability = 0; + m_afCapability |= bits_CAP_SQUAD; + + m_HackedGunPos = Vector( 24, 64, 48 ); + + m_flNextSpeakTime = m_flNextWordTime = gpGlobals->time + 10 + RANDOM_LONG(0, 10); + + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CAGrunt :: Precache() +{ + int i; + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/agrunt.mdl"); + + for ( i = 0; i < ARRAYSIZE( pAttackHitSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackHitSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAttackMissSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackMissSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pIdleSounds ); i++ ) + PRECACHE_SOUND((char *)pIdleSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pDieSounds ); i++ ) + PRECACHE_SOUND((char *)pDieSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pPainSounds ); i++ ) + PRECACHE_SOUND((char *)pPainSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAttackSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAlertSounds ); i++ ) + PRECACHE_SOUND((char *)pAlertSounds[i]); + + + PRECACHE_SOUND( "hassault/hw_shoot1.wav" ); + + iAgruntMuzzleFlash = PRECACHE_MODEL( "sprites/muz4.spr" ); + + UTIL_PrecacheOther( "hornet" ); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +//========================================================= +// Fail Schedule +//========================================================= +Task_t tlAGruntFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slAGruntFail[] = +{ + { + tlAGruntFail, + ARRAYSIZE ( tlAGruntFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1, + 0, + "AGrunt Fail" + }, +}; + +//========================================================= +// Combat Fail Schedule +//========================================================= +Task_t tlAGruntCombatFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slAGruntCombatFail[] = +{ + { + tlAGruntCombatFail, + ARRAYSIZE ( tlAGruntCombatFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1, + 0, + "AGrunt Combat Fail" + }, +}; + +//========================================================= +// Standoff schedule. Used in combat when a monster is +// hiding in cover or the enemy has moved out of sight. +// Should we look around in this schedule? +//========================================================= +Task_t tlAGruntStandoff[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, +}; + +Schedule_t slAGruntStandoff[] = +{ + { + tlAGruntStandoff, + ARRAYSIZE ( tlAGruntStandoff ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Agrunt Standoff" + } +}; + +//========================================================= +// Suppress +//========================================================= +Task_t tlAGruntSuppressHornet[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slAGruntSuppress[] = +{ + { + tlAGruntSuppressHornet, + ARRAYSIZE ( tlAGruntSuppressHornet ), + 0, + 0, + "AGrunt Suppress Hornet", + }, +}; + +//========================================================= +// primary range attacks +//========================================================= +Task_t tlAGruntRangeAttack1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slAGruntRangeAttack1[] = +{ + { + tlAGruntRangeAttack1, + ARRAYSIZE ( tlAGruntRangeAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE, + + 0, + "AGrunt Range Attack1" + }, +}; + + +Task_t tlAGruntHiddenRangeAttack1[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_STANDOFF }, + { TASK_AGRUNT_SETUP_HIDE_ATTACK, 0 }, + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, 0 }, + { TASK_RANGE_ATTACK1_NOTURN, (float)0 }, +}; + +Schedule_t slAGruntHiddenRangeAttack[] = +{ + { + tlAGruntHiddenRangeAttack1, + ARRAYSIZE ( tlAGruntHiddenRangeAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "AGrunt Hidden Range Attack1" + }, +}; + +//========================================================= +// Take cover from enemy! Tries lateral cover before node +// cover! +//========================================================= +Task_t tlAGruntTakeCoverFromEnemy[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_FACE_ENEMY, (float)0 }, +}; + +Schedule_t slAGruntTakeCoverFromEnemy[] = +{ + { + tlAGruntTakeCoverFromEnemy, + ARRAYSIZE ( tlAGruntTakeCoverFromEnemy ), + bits_COND_NEW_ENEMY, + 0, + "AGruntTakeCoverFromEnemy" + }, +}; + +//========================================================= +// Victory dance! +//========================================================= +Task_t tlAGruntVictoryDance[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_AGRUNT_THREAT_DISPLAY }, + { TASK_WAIT, (float)0.2 }, + { TASK_AGRUNT_GET_PATH_TO_ENEMY_CORPSE, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_CROUCH }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_STAND }, + { TASK_PLAY_SEQUENCE, (float)ACT_THREAT_DISPLAY }, + { TASK_PLAY_SEQUENCE, (float)ACT_CROUCH }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_STAND }, +}; + +Schedule_t slAGruntVictoryDance[] = +{ + { + tlAGruntVictoryDance, + ARRAYSIZE ( tlAGruntVictoryDance ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "AGruntVictoryDance" + }, +}; + +//========================================================= +//========================================================= +Task_t tlAGruntThreatDisplay[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_THREAT_DISPLAY }, +}; + +Schedule_t slAGruntThreatDisplay[] = +{ + { + tlAGruntThreatDisplay, + ARRAYSIZE ( tlAGruntThreatDisplay ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + + bits_SOUND_PLAYER | + bits_SOUND_COMBAT | + bits_SOUND_WORLD, + "AGruntThreatDisplay" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CAGrunt ) +{ + slAGruntFail, + slAGruntCombatFail, + slAGruntStandoff, + slAGruntSuppress, + slAGruntRangeAttack1, + slAGruntHiddenRangeAttack, + slAGruntTakeCoverFromEnemy, + slAGruntVictoryDance, + slAGruntThreatDisplay, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CAGrunt, CSquadMonster ); + +//========================================================= +// FCanCheckAttacks - this is overridden for alien grunts +// because they can use their smart weapons against unseen +// enemies. Base class doesn't attack anyone it can't see. +//========================================================= +BOOL CAGrunt :: FCanCheckAttacks ( void ) +{ + if ( !HasConditions( bits_COND_ENEMY_TOOFAR ) ) + { + return TRUE; + } + else + { + return FALSE; + } +} + +//========================================================= +// CheckMeleeAttack1 - alien grunts zap the crap out of +// any enemy that gets too close. +//========================================================= +BOOL CAGrunt :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + if ( HasConditions ( bits_COND_SEE_ENEMY ) && flDist <= AGRUNT_MELEE_DIST && flDot >= 0.6 && m_hEnemy != NULL ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack1 +// +// !!!LATER - we may want to load balance this. Several +// tracelines are done, so we may not want to do this every +// server frame. Definitely not while firing. +//========================================================= +BOOL CAGrunt :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( gpGlobals->time < m_flNextHornetAttackCheck ) + { + return m_fCanHornetAttack; + } + + if ( HasConditions( bits_COND_SEE_ENEMY ) && flDist >= AGRUNT_MELEE_DIST && flDist <= 1024 && flDot >= 0.5 && NoFriendlyFire() ) + { + TraceResult tr; + Vector vecArmPos, vecArmDir; + + // verify that a shot fired from the gun will hit the enemy before the world. + // !!!LATER - we may wish to do something different for projectile weapons as opposed to instant-hit + UTIL_MakeVectors( pev->angles ); + GetAttachment( 0, vecArmPos, vecArmDir ); +// UTIL_TraceLine( vecArmPos, vecArmPos + gpGlobals->v_forward * 256, ignore_monsters, ENT(pev), &tr); + UTIL_TraceLine( vecArmPos, m_hEnemy->BodyTarget(vecArmPos), dont_ignore_monsters, ENT(pev), &tr); + + if ( tr.flFraction == 1.0 || tr.pHit == m_hEnemy->edict() ) + { + m_flNextHornetAttackCheck = gpGlobals->time + RANDOM_FLOAT( 2, 5 ); + m_fCanHornetAttack = TRUE; + return m_fCanHornetAttack; + } + } + + m_flNextHornetAttackCheck = gpGlobals->time + 0.2;// don't check for half second if this check wasn't successful + m_fCanHornetAttack = FALSE; + return m_fCanHornetAttack; +} + +//========================================================= +// StartTask +//========================================================= +void CAGrunt :: StartTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_AGRUNT_GET_PATH_TO_ENEMY_CORPSE: + { + UTIL_MakeVectors( pev->angles ); + if ( BuildRoute ( m_vecEnemyLKP - gpGlobals->v_forward * 50, bits_MF_TO_LOCATION, NULL ) ) + { + TaskComplete(); + } + else + { + ALERT ( at_aiconsole, "AGruntGetPathToEnemyCorpse failed!!\n" ); + TaskFail(); + } + } + break; + + case TASK_AGRUNT_SETUP_HIDE_ATTACK: + // alien grunt shoots hornets back out into the open from a concealed location. + // try to find a spot to throw that gives the smart weapon a good chance of finding the enemy. + // ideally, this spot is along a line that is perpendicular to a line drawn from the agrunt to the enemy. + + CBaseMonster *pEnemyMonsterPtr; + + pEnemyMonsterPtr = m_hEnemy->MyMonsterPointer(); + + if ( pEnemyMonsterPtr ) + { + Vector vecCenter; + TraceResult tr; + BOOL fSkip; + + fSkip = FALSE; + vecCenter = Center(); + + UTIL_VecToAngles( m_vecEnemyLKP - pev->origin ); + + UTIL_TraceLine( Center() + gpGlobals->v_forward * 128, m_vecEnemyLKP, ignore_monsters, ENT(pev), &tr); + if ( tr.flFraction == 1.0 ) + { + SetIdealYawToTargetAndUpdate( pev->origin + gpGlobals->v_right * 128 ); + fSkip = TRUE; + TaskComplete(); + } + + if ( !fSkip ) + { + UTIL_TraceLine( Center() - gpGlobals->v_forward * 128, m_vecEnemyLKP, ignore_monsters, ENT(pev), &tr); + if ( tr.flFraction == 1.0 ) + { + SetIdealYawToTargetAndUpdate( pev->origin - gpGlobals->v_right * 128 ); + fSkip = TRUE; + TaskComplete(); + } + } + + if ( !fSkip ) + { + UTIL_TraceLine( Center() + gpGlobals->v_forward * 256, m_vecEnemyLKP, ignore_monsters, ENT(pev), &tr); + if ( tr.flFraction == 1.0 ) + { + SetIdealYawToTargetAndUpdate( pev->origin + gpGlobals->v_right * 256 ); + fSkip = TRUE; + TaskComplete(); + } + } + + if ( !fSkip ) + { + UTIL_TraceLine( Center() - gpGlobals->v_forward * 256, m_vecEnemyLKP, ignore_monsters, ENT(pev), &tr); + if ( tr.flFraction == 1.0 ) + { + SetIdealYawToTargetAndUpdate( pev->origin - gpGlobals->v_right * 256 ); + fSkip = TRUE; + TaskComplete(); + } + } + + if ( !fSkip ) + { + TaskFail(); + } + } + else + { + ALERT ( at_aiconsole, "AGRunt - no enemy monster ptr!!!\n" ); + TaskFail(); + } + break; + + default: + CSquadMonster :: StartTask ( pTask ); + break; + } +} + +//========================================================= +// GetSchedule - Decides which type of schedule best suits +// the monster's current state and conditions. Then calls +// monster's member function to get a pointer to a schedule +// of the proper type. +//========================================================= +Schedule_t *CAGrunt :: GetSchedule ( void ) +{ + if ( HasConditions(bits_COND_HEAR_SOUND) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) + { + // dangerous sound nearby! + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + } + + switch ( m_MonsterState ) + { + case MONSTERSTATE_COMBAT: + { +// dead enemy + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + + if ( HasConditions(bits_COND_NEW_ENEMY) ) + { + return GetScheduleOfType( SCHED_WAKE_ANGRY ); + } + + // zap player! + if ( HasConditions ( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + AttackSound();// this is a total hack. Should be parto f the schedule + return GetScheduleOfType ( SCHED_MELEE_ATTACK1 ); + } + + if ( HasConditions ( bits_COND_HEAVY_DAMAGE ) ) + { + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + + // can attack + if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) && OccupySlot ( bits_SLOTS_AGRUNT_HORNET ) ) + { + return GetScheduleOfType ( SCHED_RANGE_ATTACK1 ); + } + + if ( OccupySlot ( bits_SLOT_AGRUNT_CHASE ) ) + { + return GetScheduleOfType ( SCHED_CHASE_ENEMY ); + } + + return GetScheduleOfType ( SCHED_STANDOFF ); + } + } + + return CSquadMonster :: GetSchedule(); +} + +//========================================================= +//========================================================= +Schedule_t* CAGrunt :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_TAKE_COVER_FROM_ENEMY: + return &slAGruntTakeCoverFromEnemy[ 0 ]; + break; + + case SCHED_RANGE_ATTACK1: + if ( HasConditions( bits_COND_SEE_ENEMY ) ) + { + //normal attack + return &slAGruntRangeAttack1[ 0 ]; + } + else + { + // attack an unseen enemy + // return &slAGruntHiddenRangeAttack[ 0 ]; + return &slAGruntRangeAttack1[ 0 ]; + } + break; + + case SCHED_AGRUNT_THREAT_DISPLAY: + return &slAGruntThreatDisplay[ 0 ]; + break; + + case SCHED_AGRUNT_SUPPRESS: + return &slAGruntSuppress[ 0 ]; + break; + + case SCHED_STANDOFF: + return &slAGruntStandoff[ 0 ]; + break; + + case SCHED_VICTORY_DANCE: + return &slAGruntVictoryDance[ 0 ]; + break; + + case SCHED_FAIL: + // no fail schedule specified, so pick a good generic one. + { + if ( m_hEnemy != NULL ) + { + // I have an enemy + // !!!LATER - what if this enemy is really far away and i'm chasing him? + // this schedule will make me stop, face his last known position for 2 + // seconds, and then try to move again + return &slAGruntCombatFail[ 0 ]; + } + + return &slAGruntFail[ 0 ]; + } + break; + + } + + return CSquadMonster :: GetScheduleOfType( Type ); +} \ No newline at end of file diff --git a/dlls/airtank.cpp b/dlls/airtank.cpp new file mode 100644 index 0000000..18b301c --- /dev/null +++ b/dlls/airtank.cpp @@ -0,0 +1,118 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" + +class CAirtank : public CGrenade +{ + void Spawn( void ); + void Precache( void ); + void EXPORT TankThink( void ); + void EXPORT TankTouch( CBaseEntity *pOther ); + int BloodColor( void ) { return DONT_BLEED; }; + void Killed( entvars_t *pevAttacker, int iGib ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int m_state; +}; + + +LINK_ENTITY_TO_CLASS( item_airtank, CAirtank ); +TYPEDESCRIPTION CAirtank::m_SaveData[] = +{ + DEFINE_FIELD( CAirtank, m_state, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CAirtank, CGrenade ); + + +void CAirtank :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/w_oxygen.mdl"); + UTIL_SetSize(pev, Vector( -16, -16, 0), Vector(16, 16, 36)); + UTIL_SetOrigin( this, pev->origin ); + + SetTouch(&CAirtank :: TankTouch ); + SetThink(&CAirtank :: TankThink ); + + pev->flags |= FL_MONSTER; + pev->takedamage = DAMAGE_YES; + pev->health = 20; + pev->dmg = 50; + m_state = 1; +} + +void CAirtank::Precache( void ) +{ + PRECACHE_MODEL("models/w_oxygen.mdl"); + PRECACHE_SOUND("doors/aliendoor3.wav"); +} + + +void CAirtank :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->owner = ENT( pevAttacker ); + + // UNDONE: this should make a big bubble cloud, not an explosion + + Explode( pev->origin, Vector( 0, 0, -1 ) ); +} + + +void CAirtank::TankThink( void ) +{ + // Fire trigger + m_state = 1; + SUB_UseTargets( this, USE_TOGGLE, 0 ); +} + + +void CAirtank::TankTouch( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + return; + + if (!m_state) + { + // "no oxygen" sound + EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_swim2.wav", 1.0, ATTN_NORM ); + return; + } + + // give player 12 more seconds of air + pOther->pev->air_finished = gpGlobals->time + 12; + + // suit recharge sound + EMIT_SOUND( ENT(pev), CHAN_VOICE, "doors/aliendoor3.wav", 1.0, ATTN_NORM ); + + // recharge airtank in 30 seconds + SetNextThink( 30 ); + m_state = 0; + SUB_UseTargets( this, USE_TOGGLE, 1 ); +} diff --git a/dlls/alias.cpp b/dlls/alias.cpp new file mode 100644 index 0000000..0867c53 --- /dev/null +++ b/dlls/alias.cpp @@ -0,0 +1,355 @@ +/*** +* +* NEW file for the Mod "Spirit of Half-Life", by Laurie R. Cheers. (LRC) +* Created 19/11/00 +* +***/ +/* + +===== alias.cpp ======================================================== + +Alias entities, replace the less powerful and (IMHO) less intuitive +trigger_changetarget entity. + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" + +TYPEDESCRIPTION CBaseAlias::m_SaveData[] = +{ + DEFINE_FIELD( CBaseAlias, m_pNextAlias, FIELD_CLASSPTR ), +}; +IMPLEMENT_SAVERESTORE( CBaseAlias, CPointEntity ); + +/********************* +* Worldcraft entity: info_alias +* +* targetname- alias name +* target- alias destination while ON +* netname- alias destination while OFF +**********************/ + +#define SF_ALIAS_OFF 1 +#define SF_ALIAS_DEBUG 2 + +class CInfoAlias : public CBaseAlias +{ +public: + void Use(CBaseEntity* pActivator, CBaseEntity* pCaller, USE_TYPE useType, float value); + void Spawn( void ); + STATE GetState() { return (pev->spawnflags & SF_ALIAS_OFF)?STATE_OFF:STATE_ON; } + + CBaseEntity *FollowAlias( CBaseEntity *pFrom ); + void ChangeValue( int iszValue ); + void FlushChanges( void ); +}; + +LINK_ENTITY_TO_CLASS( info_alias, CInfoAlias ); + +void CInfoAlias::Spawn( void ) +{ + if (pev->spawnflags & SF_ALIAS_OFF) + pev->message = pev->netname; + else + pev->message = pev->target; +} + +void CInfoAlias::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (pev->spawnflags & SF_ALIAS_OFF) + { + if (pev->spawnflags & SF_ALIAS_DEBUG) + ALERT(at_debug,"DEBUG: info_alias %s turns on\n",STRING(pev->targetname)); + pev->spawnflags &= ~SF_ALIAS_OFF; + pev->noise = pev->target; + } + else + { + if (pev->spawnflags & SF_ALIAS_DEBUG) + ALERT(at_debug,"DEBUG: info_alias %s turns off\n",STRING(pev->targetname)); + pev->spawnflags |= SF_ALIAS_OFF; + pev->noise = pev->netname; + } + UTIL_AddToAliasList( this ); +} + +CBaseEntity *CInfoAlias::FollowAlias( CBaseEntity *pFrom ) +{ + return UTIL_FindEntityByTargetname( pFrom, STRING(pev->message) ); +} + +void CInfoAlias::ChangeValue( int iszValue ) +{ + pev->noise = iszValue; + UTIL_AddToAliasList( this ); +} + +void CInfoAlias::FlushChanges( void ) +{ + pev->message = pev->noise; + if (pev->spawnflags & SF_ALIAS_DEBUG) + ALERT(at_debug,"DEBUG: info_alias %s now refers to \"%s\"\n", STRING(pev->targetname), STRING(pev->message)); +} + +/********************* +* Worldcraft entity: info_group +* +* targetname- name +* target- alias entity to affect +* other values are handled in a multi_manager-like way. +**********************/ +// definition in cbase.h + +#define SF_GROUP_DEBUG 2 + +LINK_ENTITY_TO_CLASS( info_group, CInfoGroup ); + +TYPEDESCRIPTION CInfoGroup::m_SaveData[] = +{ + DEFINE_FIELD( CInfoGroup, m_cMembers, FIELD_INTEGER ), + DEFINE_ARRAY( CInfoGroup, m_iszMemberName, FIELD_STRING, MAX_MULTI_TARGETS ), + DEFINE_ARRAY( CInfoGroup, m_iszMemberValue, FIELD_STRING, MAX_MULTI_TARGETS ), + DEFINE_FIELD( CInfoGroup, m_iszDefaultMember, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE(CInfoGroup,CBaseEntity); + +void CInfoGroup :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "defaultmember")) + { + m_iszDefaultMember = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + // this assumes that additional fields are targetnames and their values are delay values. + else if ( m_cMembers < MAX_MULTI_TARGETS ) + { + char tmp[128]; + UTIL_StripToken( pkvd->szKeyName, tmp ); + m_iszMemberName [ m_cMembers ] = ALLOC_STRING( tmp ); + m_iszMemberValue [ m_cMembers ] = ALLOC_STRING (pkvd->szValue); + m_cMembers++; + pkvd->fHandled = TRUE; + } + else + { + ALERT(at_error,"Too many members for info_group %s (limit is %d)\n",STRING(pev->targetname),MAX_MULTI_TARGETS); + } +} + +void CInfoGroup::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBaseEntity *pTarget = UTIL_FindEntityByTargetname( NULL, STRING( pev->target ) ); + + if (pTarget && pTarget->IsAlias()) + { + if (pev->spawnflags & SF_GROUP_DEBUG) + ALERT(at_debug, "DEBUG: info_group %s changes the contents of %s \"%s\"\n",STRING(pev->targetname), STRING(pTarget->pev->classname), STRING(pTarget->pev->targetname)); + ((CBaseAlias*)pTarget)->ChangeValue(this); + } + else if (pev->target) + { + ALERT(at_debug, "info_group \"%s\": alias \"%s\" was not found or not an alias!", STRING(pev->targetname), STRING(pev->target)); + } +} + +int CInfoGroup::GetMember( const char* szMemberName ) +{ + if (!szMemberName) + { + ALERT(at_debug,"info_group: GetMember called with null szMemberName!?\n"); + return NULL; + } + for (int i = 0; i < m_cMembers; i++) + { + if (FStrEq(szMemberName, STRING(m_iszMemberName[i]))) + { +// ALERT(at_console,"getMember: found member\n"); + return m_iszMemberValue[i]; + } + } + + if (m_iszDefaultMember) + { + static char szBuffer[128]; + strcpy(szBuffer, STRING(m_iszDefaultMember)); + strcat(szBuffer, szMemberName); + return MAKE_STRING(szBuffer); + // this is a messy way to do it... but currently, only one + // GetMember gets performed at a time, so it works. + } + + ALERT(at_debug,"info_group \"%s\" has no member called \"%s\".\n",STRING(pev->targetname),szMemberName); +// ALERT(at_console,"getMember: fail\n"); + return NULL; +} + +/********************* +* Worldcraft entity: multi_alias +* +* targetname- name +* other values are handled in a multi_manager-like way. +**********************/ +// definition in cbase.h + +LINK_ENTITY_TO_CLASS( multi_alias, CMultiAlias ); + +TYPEDESCRIPTION CMultiAlias::m_SaveData[] = +{ + DEFINE_FIELD( CMultiAlias, m_cTargets, FIELD_INTEGER ), + DEFINE_ARRAY( CMultiAlias, m_iszTargets, FIELD_STRING, MAX_MULTI_TARGETS ), + DEFINE_FIELD( CMultiAlias, m_iTotalValue, FIELD_INTEGER ), + DEFINE_ARRAY( CMultiAlias, m_iValues, FIELD_INTEGER, MAX_MULTI_TARGETS ), + DEFINE_FIELD( CMultiAlias, m_iMode, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE(CMultiAlias,CBaseAlias); + +void CMultiAlias :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iMode")) + { + m_iMode = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + // this assumes that additional fields are targetnames and their values are probability values. + else if ( m_cTargets < MAX_MULTI_TARGETS ) + { + char tmp[128]; + UTIL_StripToken( pkvd->szKeyName, tmp ); + + m_iszTargets [ m_cTargets ] = ALLOC_STRING( tmp ); + m_iValues [ m_cTargets ] = atoi( pkvd->szValue ); + + m_iTotalValue += m_iValues [ m_cTargets ]; + m_cTargets++; + + pkvd->fHandled = TRUE; + } + else + { + ALERT(at_error,"Too many targets for multi_alias %s (limit is %d)\n",STRING(pev->targetname), MAX_MULTI_TARGETS); + } +} + +CBaseEntity *CMultiAlias::FollowAlias( CBaseEntity *pStartEntity ) +{ + CBaseEntity* pBestEntity = NULL; // the entity we're currently planning to return. + int iBestOffset = -1; // the offset of that entity. + CBaseEntity* pTempEntity; + int iTempOffset; + + int i = 0; + if (m_iMode) + { + // During any given 'game moment', this code may be called more than once. It must use the + // same random values each time (because otherwise it gets really messy). I'm using srand + // to arrange this. + srand( (int)(gpGlobals->time * 100) ); + rand(); // throw away the first result - it's just the seed value + if (m_iMode == 1) // 'choose one' mode + { + int iRandom = 1 + (rand() % m_iTotalValue); + for (i = 0; i < m_cTargets; i++) + { + iRandom -= m_iValues[i]; + if (iRandom <= 0) + break; + } + } + else // 'percent chance' mode + { + for (i = 0; i < m_cTargets; i++) + { + if (m_iValues[i] >= rand() % 100) + break; + } + } + } + + while (i < m_cTargets) + { + pTempEntity = UTIL_FindEntityByTargetname(pStartEntity,STRING(m_iszTargets[i])); + if ( pTempEntity ) + { + // We've found an entity; only use it if its offset is lower than the offset we've currently got. + iTempOffset = OFFSET(pTempEntity->pev); + if (iBestOffset == -1 || iTempOffset < iBestOffset) + { + iBestOffset = iTempOffset; + pBestEntity = pTempEntity; + } + } + if (m_iMode == 1) + break; // if it's in "pick one" mode, stop after the first. + else if (m_iMode == 2) + { + i++; + // if it's in "percent chance" mode, try to find another one to fire. + while (i < m_cTargets) + { + if (m_iValues[i] > rand() % 100) + break; + i++; + } + } + else + i++; + } + + return pBestEntity; +} + +/********************* +* Worldcraft entity: trigger_changealias +* +* target- alias entity to affect +* netname- value to change the alias to +**********************/ + +#define SF_CHANGEALIAS_RESOLVE 1 +#define SF_CHANGEALIAS_DEBUG 2 + +class CTriggerChangeAlias : public CBaseEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int ObjectCaps( void ) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; +LINK_ENTITY_TO_CLASS( trigger_changealias, CTriggerChangeAlias ); + +void CTriggerChangeAlias::Spawn( void ) +{ +} + +void CTriggerChangeAlias::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBaseEntity *pTarget = UTIL_FindEntityByTargetname( NULL, STRING( pev->target ), pActivator ); + + if (pTarget && pTarget->IsAlias()) + { + CBaseEntity *pValue; + + if (FStrEq(STRING(pev->netname), "*locus")) + { + pValue = pActivator; + } + else if (pev->spawnflags & SF_CHANGEALIAS_RESOLVE) + { + pValue = UTIL_FollowReference(NULL, STRING(pev->netname)); + } + + if (pValue) + ((CBaseAlias*)pTarget)->ChangeValue(pValue); + else + ((CBaseAlias*)pTarget)->ChangeValue(pev->netname); + } + else + { + ALERT(at_error, "trigger_changealias %s: alias \"%s\" was not found or not an alias!", STRING(pev->targetname), STRING(pev->target)); + } +} diff --git a/dlls/ammodesc.cpp b/dlls/ammodesc.cpp new file mode 100644 index 0000000..744e73f --- /dev/null +++ b/dlls/ammodesc.cpp @@ -0,0 +1,503 @@ +/* +ammodesc.cpp - virtual generic ammo that not needs to be hardcoded +Copyright (C) 2014 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "soundent.h" +#include "decals.h" +#include "gamerules.h" + +extern int gEvilImpulse101; + +AmmoInfo CBasePlayerAmmo :: AmmoInfoArray[MAX_AMMO_SLOTS]; +AmmoDesc CBasePlayerAmmo :: AmmoDescArray[MAX_AMMO_DESC]; +int giAmmoIndex = 0; +int giAmmoEnts = 0; + +// Precaches the ammo and queues the ammo info for sending to clients +void AddAmmoNameToAmmoRegistry( const char *szAmmoname ) +{ + // make sure it's not already in the registry + for( int i = 1; i < MAX_AMMO_SLOTS; i++ ) + { + if( !CBasePlayerAmmo :: AmmoInfoArray[i].pszName ) + continue; + + if( !Q_stricmp( CBasePlayerAmmo :: AmmoInfoArray[i].pszName, szAmmoname )) + return; // ammo already in registry, just quite + } + + if( giAmmoIndex >= MAX_AMMO_SLOTS ) + { + ALERT( at_error, "Too many ammo types. ammotype %s was ignored\n", szAmmoname ); + return; + } + + CBasePlayerAmmo :: AmmoInfoArray[giAmmoIndex].pszName = szAmmoname; + CBasePlayerAmmo :: AmmoInfoArray[giAmmoIndex].iId = giAmmoIndex; // yes, this info is redundant + giAmmoIndex++; +} + +void AddAmmoNameToAmmoRegistry( const AmmoInfo *pInfo ) +{ + if( !pInfo || !pInfo->pszName || !*pInfo->pszName ) + return; // bad name specified + + // make sure it's not already in the registry + for( int i = 1; i < MAX_AMMO_SLOTS; i++ ) + { + if( !CBasePlayerAmmo :: AmmoInfoArray[i].pszName ) + continue; + + if( !Q_stricmp( CBasePlayerAmmo :: AmmoInfoArray[i].pszName, pInfo->pszName )) + { + ALERT( at_warning, "AmmoInfo: duplicate info for ammotype %s was ignored\n", pInfo->pszName ); + return; // ammo already in registry + } + } + + if( giAmmoIndex >= MAX_AMMO_SLOTS ) + { + ALERT( at_error, "Too many ammo types. ammotype %s was ignored\n", pInfo->pszName ); + return; + } + + CBasePlayerAmmo :: AmmoInfoArray[giAmmoIndex] = *pInfo; + CBasePlayerAmmo :: AmmoInfoArray[giAmmoIndex].iId = giAmmoIndex; // yes, this info is redundant + giAmmoIndex++; +} + +AmmoInfo *UTIL_FindAmmoType( const char *szAmmoname ) +{ + if( !szAmmoname || !*szAmmoname ) + { + ALERT( at_error, "FindAmmoType: NULL type\n" ); + return NULL; // bad name specified + } + + if( !Q_stricmp( szAmmoname, "none" )) + return NULL; // no ammo specified + + for( int i = 1; i < MAX_AMMO_SLOTS; i++ ) + { + if( !CBasePlayerAmmo :: AmmoInfoArray[i].pszName ) + continue; + + if( !Q_stricmp( CBasePlayerAmmo :: AmmoInfoArray[i].pszName, szAmmoname )) + return &CBasePlayerAmmo :: AmmoInfoArray[i]; + } + + ALERT( at_error, "FindAmmoType: coudn't find ammo type '%s'\n", szAmmoname ); + + return NULL; +} + +AmmoDesc *UTIL_FindAmmoDesc( const char *szClassname ) +{ + if( !szClassname || !*szClassname ) + { + ALERT( at_error, "FindAmmoDesc: NULL classname\n" ); + return NULL; // bad classname specified + } + + for( int i = 0; i < giAmmoEnts; i++ ) + { + if( !CBasePlayerAmmo :: AmmoDescArray[i].classname ) + continue; + + if( !Q_stricmp( STRING( CBasePlayerAmmo :: AmmoDescArray[i].classname ), szClassname )) + return &CBasePlayerAmmo :: AmmoDescArray[i]; + } + + ALERT( at_error, "FindAmmoDesc: coudn't find entity '%s'\n", szClassname ); + + return NULL; +} + +void AddAmmoEntityToArray( const AmmoDesc *pDesc ) +{ + if( !pDesc || !pDesc->classname || !pDesc->type ) + return; // bad name specified or type not found + + if( giAmmoEnts >= MAX_AMMO_DESC ) + { + ALERT( at_error, "Too many ammo entities specified. %s was ignored\n", STRING( pDesc->classname )); + return; + } + + // make sure it's not already in the registry + for( int i = 0; i < giAmmoEnts; i++ ) + { + if( !Q_stricmp( STRING( CBasePlayerAmmo :: AmmoDescArray[i].classname ), STRING( pDesc->classname ))) + { + ALERT( at_warning, "AmmoDesc: duplicate info for %s was ignored\n", STRING( pDesc->classname )); + return; // ammo already in registry + } + } + + // add new entry + CBasePlayerAmmo :: AmmoDescArray[giAmmoEnts] = *pDesc; + giAmmoEnts++; +} + +void UTIL_ParseAmmoInfo( char *&pfile ) +{ + AmmoInfo tmpInfo; + char token[256]; + int section = 0; + + memset( &tmpInfo, 0, sizeof( AmmoInfo )); + + // apply default params + tmpInfo.iMaxCarry = 255; + tmpInfo.flDistance = 8192.0f; + tmpInfo.iNumShots = 1; + + while( pfile != NULL ) + { + pfile = COM_ParseFile( pfile, token ); + + if( !Q_stricmp( token, "{" )) + section++; + else if( !Q_stricmp( token, "}" )) + { + section--; + break; + } + else if( section > 0 ) + { + if( !Q_stricmp( token, "name" )) + { + pfile = COM_ParseFile( pfile, token ); + tmpInfo.pszName = STRING( ALLOC_STRING( token )); // zone memory + } + else if( !Q_stricmp( token, "MaxCarry" )) + { + pfile = COM_ParseFile( pfile, token ); + tmpInfo.iMaxCarry = bound( 1, Q_atoi( token ), 255 ); // a byte limit + } + else if( !Q_stricmp( token, "ShellModel" )) + { + pfile = COM_ParseFile( pfile, token ); + tmpInfo.iShellIndex = PRECACHE_MODEL( token ); // shell model + } + else if( !Q_stricmp( token, "Damage" )) + { + pfile = COM_ParseFile( pfile, token ); + tmpInfo.flPlayerDamage = tmpInfo.flMonsterDamage = Q_atof( token ); // healing damage is allowed + } + else if( !Q_stricmp( token, "PlayerDamage" )) + { + pfile = COM_ParseFile( pfile, token ); + tmpInfo.flPlayerDamage = Q_atof( token ); // healing damage is allowed + } + else if( !Q_stricmp( token, "MonsterDamage" )) + { + pfile = COM_ParseFile( pfile, token ); + tmpInfo.flMonsterDamage = Q_atof( token ); // healing damage is allowed + } + else if( !Q_stricmp( token, "NumShots" )) + { + pfile = COM_ParseFile( pfile, token ); + tmpInfo.iNumShots = bound( 1, Q_atoi( token ), 32 ); // shots per one shot + } + else if( !Q_stricmp( token, "Distance" )) + { + pfile = COM_ParseFile( pfile, token ); + tmpInfo.flDistance = bound( 64.0f, Q_atof( token ), 32768.0f ); // shots per one shot + } + else if( !Q_stricmp( token, "Missile" )) + { + pfile = COM_ParseFile( pfile, token ); + tmpInfo.iMissileClassName = ALLOC_STRING( token ); + UTIL_PrecacheOther( token ); + } + } + } + + if( section ) ALERT( at_warning, "invalid braced sections %i in ammodescripton\n", section ); + else AddAmmoNameToAmmoRegistry( &tmpInfo ); +} + +void UTIL_ParseAmmoEntity( char *&pfile, const char *classname ) +{ + AmmoDesc tmpDesc; + char token[256]; + int section = 0; + + memset( &tmpDesc, 0, sizeof( AmmoDesc )); + + tmpDesc.classname = ALLOC_STRING( classname ); + + while( pfile != NULL ) + { + pfile = COM_ParseFile( pfile, token ); + + if( !Q_stricmp( token, "{" )) + section++; + else if( !Q_stricmp( token, "}" )) + { + section--; + break; + } + else if( section > 0 ) + { + if( !Q_stricmp( token, "model" )) + { + pfile = COM_ParseFile( pfile, token ); + tmpDesc.ammomodel = ALLOC_STRING( token ); + PRECACHE_MODEL( STRING( tmpDesc.ammomodel )); + } + else if( !Q_stricmp( token, "sound" )) + { + pfile = COM_ParseFile( pfile, token ); + tmpDesc.clipsound = ALLOC_STRING( token ); + PRECACHE_SOUND( STRING( tmpDesc.clipsound )); + } + else if( !Q_stricmp( token, "type" )) + { + pfile = COM_ParseFile( pfile, token ); + tmpDesc.type = UTIL_FindAmmoType( token ); + } + else if( !Q_stricmp( token, "count" )) + { + pfile = COM_ParseFile( pfile, token ); + tmpDesc.count = RandomRange( token ); + } + } + } + + if( section ) ALERT( at_warning, "invalid braced sections %i in %s\n", section, classname ); + else AddAmmoEntityToArray( &tmpDesc ); +} + +void UTIL_InitAmmoDescription( const char *filename ) +{ + char *afile = (char *)LOAD_FILE( filename, NULL ); + + giAmmoIndex = 1; + giAmmoEnts = 0; + + if( !afile ) + { + ALERT( at_error, "ammo description file %s not found!\n", filename ); + return; + } + + char *pfile = afile; + char token[256]; + + // parse all the ammoinfos + while( pfile != NULL ) + { + pfile = COM_ParseFile( pfile, token ); + + if( !Q_stricmp( token, "ammoinfo" )) + UTIL_ParseAmmoInfo( pfile ); + else pfile = COM_SkipBracedSection( pfile ); + } + + pfile = afile; // reset pointer + + // parse ammo_ virtual entities + while( pfile != NULL ) + { + pfile = COM_ParseFile( pfile, token ); + + if( Q_stricmp( token, "ammoinfo" )) + UTIL_ParseAmmoEntity( pfile, token ); + else pfile = COM_SkipBracedSection( pfile ); + } + + FREE_FILE( afile ); +} + +// ====================== AMMO_GENERIC ================================ + +LINK_ENTITY_TO_CLASS( ammo_generic, CBasePlayerAmmo ); + +// Wargon: SaveData äëÿ þçàáåëüíîñòè ïàòðîíîâ. +TYPEDESCRIPTION CBasePlayerAmmo::m_SaveData[] = +{ + DEFINE_FIELD( CBasePlayerAmmo, m_bCustomAmmo, FIELD_BOOLEAN ), + DEFINE_FIELD( CBasePlayerAmmo, m_iAmmoCaps, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerAmmo, m_rAmmoCount, FIELD_RANGE ), + DEFINE_FIELD( CBasePlayerAmmo, m_iAmmoType, FIELD_STRING ), +}; IMPLEMENT_SAVERESTORE( CBasePlayerAmmo, CBaseEntity ); + +void CBasePlayerAmmo :: Precache( void ) +{ + if( IsGenericAmmo() && !pev->model ) + { + if( !InitGenericAmmo( )) + return; + } + + PRECACHE_MODEL( STRING( pev->model )); + PRECACHE_SOUND( STRING( pev->noise )); +} + +void CBasePlayerAmmo :: KeyValue( KeyValueData *pkvd ) +{ + if( FStrEq( pkvd->szKeyName, "count" )) + { + m_rAmmoCount = RandomRange( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else CBaseEntity :: KeyValue( pkvd ); +} + +BOOL CBasePlayerAmmo :: InitGenericAmmo( void ) +{ + // two way to create this - manually fill field 'netname' or just to create virtual entity + if( IsGenericAmmo( )) + { + // find description of virtual entity + AmmoDesc *pDesc = UTIL_FindAmmoDesc( STRING( pev->netname )); + + if( !pDesc ) + { + REMOVE_ENTITY( edict( )); + return FALSE; // description is missed + } + + // copy the description into entity struct + m_iAmmoType = ALLOC_STRING( pDesc->type->pszName ); + pev->model = pDesc->ammomodel; + pev->noise = pDesc->clipsound; + + // level-designer specified ammocount for current entity + if( !m_rAmmoCount.IsDefined( )) + m_rAmmoCount = pDesc->count; + m_bCustomAmmo = TRUE; + + return TRUE; + } + return FALSE; // not a generic +} + +void CBasePlayerAmmo :: Spawn( void ) +{ + if( IsGenericAmmo( )) + { + // try to initialize virtual entity + if( !InitGenericAmmo( )) + return; + Precache(); + SET_MODEL(ENT(pev), STRING( pev->model )); + } + + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + UTIL_SetSize( pev, Vector( -16, -16, 0 ), Vector( 16, 16, 16 )); + UTIL_SetOrigin( this, pev->origin ); + + if( !FBitSet( ObjectCaps(), FCAP_USE_ONLY ) || FBitSet( pev->spawnflags, SF_NORESPAWN )) + SetTouch( &CBasePlayerAmmo:: DefaultTouch ); + + // Wargon: Ïàòðîíû þçàáåëüíû. + SetUse( &CBasePlayerAmmo :: DefaultUse ); + m_iAmmoCaps = CBaseEntity::ObjectCaps() | FCAP_IMPULSE_USE; +} + +BOOL CBasePlayerAmmo :: AddAmmo( CBaseEntity *pOther ) +{ + if( pOther->GiveAmmo( m_rAmmoCount.Random(), STRING( m_iAmmoType )) != -1 ) + { + EMIT_SOUND( ENT(pev), CHAN_ITEM, STRING( pev->noise ), 1, ATTN_NORM ); + return TRUE; + } + + return FALSE; +} + +CBaseEntity *CBasePlayerAmmo :: Respawn( void ) +{ + SetBits( pev->effects, EF_NODRAW ); + SetTouch( NULL ); + + // Wargon: Ïàòðîíû íåþçàáåëüíû. + SetUse( NULL ); + m_iAmmoCaps = CBaseEntity::ObjectCaps(); + + // move to wherever I'm supposed to respawn. + UTIL_SetOrigin( this, g_pGameRules->VecAmmoRespawnSpot( this )); + + SetThink( &CBasePlayerAmmo:: Materialize ); + AbsoluteNextThink( g_pGameRules->FlAmmoRespawnTime( this )); + + return this; +} + +void CBasePlayerAmmo :: Materialize( void ) +{ + if( FBitSet( pev->effects, EF_NODRAW )) + { + // changing from invisible state to visible. + EMIT_SOUND_DYN( ENT( pev ), CHAN_WEAPON, "items/suitchargeok1.wav", 1, ATTN_NORM, 0, 150 ); + pev->effects &= ~EF_NODRAW; + pev->effects |= EF_MUZZLEFLASH; + } + + if( !FBitSet( ObjectCaps(), FCAP_USE_ONLY )) + SetTouch( &CBasePlayerAmmo:: DefaultTouch ); + + // Wargon: Ïàòðîíû þçàáåëüíû. + SetUse( &CBasePlayerAmmo :: DefaultUse ); + m_iAmmoCaps = CBaseEntity :: ObjectCaps() | FCAP_IMPULSE_USE; +} + +void CBasePlayerAmmo :: DefaultTouch( CBaseEntity *pOther ) +{ + if( !pOther->IsPlayer( )) + { + return; + } + + if( AddAmmo( pOther )) + { + if( g_pGameRules->AmmoShouldRespawn( this ) == GR_AMMO_RESPAWN_YES ) + { + Respawn(); + } + else + { + SetTouch( NULL ); + + // Wargon: Ïàòðîíû íåþçàáåëüíû. + SetUse( NULL ); + m_iAmmoCaps = CBaseEntity :: ObjectCaps(); + SetThink( &CBasePlayerAmmo :: SUB_Remove ); + SetNextThink( 0.1 ); + } + } + else if( gEvilImpulse101 ) + { + // evil impulse 101 hack, kill always + SetTouch( NULL ); + + // Wargon: Ïàòðîíû íåþçàáåëüíû. + SetUse( NULL ); + m_iAmmoCaps = CBaseEntity :: ObjectCaps(); + + SetThink( &CBasePlayerAmmo :: SUB_Remove ); + SetNextThink( 0.1 ); + } +} \ No newline at end of file diff --git a/dlls/animating.cpp b/dlls/animating.cpp new file mode 100644 index 0000000..74b92e9 --- /dev/null +++ b/dlls/animating.cpp @@ -0,0 +1,580 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== monsters.cpp ======================================================== + + Monster-related utility code + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "animation.h" +#include "saverestore.h" +#include "stringlib.h" +#include "activity.h" +#include "activitymap.h" +#include "bs_defs.h" + +TYPEDESCRIPTION CBaseAnimating::m_SaveData[] = +{ + DEFINE_FIELD( CBaseMonster, m_flFrameRate, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_flGroundSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_flLastEventCheck, FIELD_TIME ), + DEFINE_FIELD( CBaseMonster, m_fSequenceFinished, FIELD_BOOLEAN ), + DEFINE_FIELD( CBaseMonster, m_fSequenceLoops, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE( CBaseAnimating, CBaseDelay ); + +const char *CBaseAnimating :: GetNameForActivity( int activity ) +{ + static char string[64]; + + if( activity == -1 ) + return "not found"; + + if( activity > ( ARRAYSIZE( activity_map ) - 1 )) + Q_snprintf( string, sizeof( string ), "%i", activity - 1 ); + else Q_strncpy( string, activity_map[activity - 1].name, sizeof( string )); + + return string; +} + +void *CBaseAnimating :: GetModelPtr( void ) +{ + return GET_MODEL_PTR( edict() ); +} + +void *CBaseAnimating :: GetModelPtr( int modelindex ) +{ + if( !g_fPhysicInitialized || modelindex <= 1 ) + return NULL; + + model_t *mod = (model_t *)MODEL_HANDLE( modelindex ); + + if( mod && mod->type == mod_studio ) + return mod->cache.data; + return NULL; +} + +//========================================================= +// StudioFrameAdvance - advance the animation frame up to the current time +// if an flInterval is passed in, only advance animation that number of seconds +//========================================================= +float CBaseAnimating :: StudioFrameAdvance ( float flInterval ) +{ + if (flInterval == 0.0) + { + flInterval = (gpGlobals->time - pev->animtime); + if (flInterval <= 0.001) + { + pev->animtime = gpGlobals->time; + return 0.0; + } + } + if (! pev->animtime) + flInterval = 0.0; + + pev->frame += flInterval * m_flFrameRate * pev->framerate; + pev->animtime = gpGlobals->time; + + if (pev->frame < 0.0 || pev->frame >= 256.0) + { + if (m_fSequenceLoops) + pev->frame -= (int)(pev->frame / 256.0) * 256.0; + else + pev->frame = (pev->frame < 0.0) ? 0 : 255; + m_fSequenceFinished = TRUE; // just in case it wasn't caught in GetEvents + } + + return flInterval; +} + +float CBaseAnimating :: StudioGaitFrameAdvance( void ) +{ + if( !pev->gaitsequence || ( IsNetClient() && FBitSet( pev->fixangle, 1 ))) + { + if( IsPlayer( )) + { + // reset torso controllers + pev->controller[0] = 0x7F; + pev->controller[1] = 0x7F; + pev->controller[2] = 0x7F; + pev->controller[3] = 0x7F; + } + return 0.0f; + } + + float delta = gpGlobals->frametime; + + m_flGaitMovement = pev->velocity.Length() * delta * 0.5f; // FXIME + + if( pev->velocity.x == 0.0f && pev->velocity.y == 0.0f ) + { + float flYawDiff = pev->angles[YAW] - m_flGaitYaw; + flYawDiff = flYawDiff - (int)(flYawDiff / 360) * 360; + + if( flYawDiff > 180 ) flYawDiff -= 360; + if( flYawDiff < -180 ) flYawDiff += 360; + + if( delta < 0.25f ) + flYawDiff *= delta * 4.0f; + else flYawDiff *= delta; + + m_flGaitYaw += flYawDiff; + m_flGaitYaw -= (int)(m_flGaitYaw / 360) * 360; + + m_flGaitMovement = 0.0f; + } + else + { + float gaityaw = RAD2DEG( atan2( pev->velocity.y, pev->velocity.x )); + m_flGaitYaw = bound( -180.0f, gaityaw, 180.0f ); + } + + // calc side to side turning + float flYaw = pev->angles[YAW] - m_flGaitYaw; // view direction relative to movement + flYaw -= (int)(flYaw / 360) * 360; + + if( flYaw < -180.0f ) flYaw += 360.0f; + if( flYaw > 180.0f ) flYaw -= 360.0f; + + flYaw = (int)flYaw; + + // kill the yaw jitter + if( flYaw > -1.0f && flYaw < 1.0f ) + flYaw = 0.0f; + + if( flYaw > 120.0f ) + { + m_flGaitYaw -= 180.0f; + m_flGaitMovement = -m_flGaitMovement; + flYaw -= 180.0f; + } + else if( flYaw < -120.0f ) + { + m_flGaitYaw += 180.0f; + m_flGaitMovement = -m_flGaitMovement; + flYaw += 180.0f; + } + + // classic Half-Life method + byte iTorsoAdjust = (byte)bound( 0, ((flYaw / 4.0f) + 30) / (60.0f / 255.0f), 255 ); + + // value it's already in range 0-255 + pev->controller[0] = iTorsoAdjust; + pev->controller[1] = iTorsoAdjust; + pev->controller[2] = iTorsoAdjust; + pev->controller[3] = iTorsoAdjust; + + SetBlending( 0, (pev->angles[PITCH] * 3.0f)); + pev->angles[YAW] = m_flGaitYaw; + + if( pev->angles[YAW] < -0.0f ) + pev->angles[YAW] += 360.0f; + + CalcGaitFrame( GetModelPtr(), m_flPoseParameter, pev->gaitsequence, pev->fuser1, m_flGaitMovement ); + + return m_flGaitMovement; +} + +//========================================================= +// LookupActivity +//========================================================= +int CBaseAnimating :: GetSequenceCount( void ) +{ + return ::GetSequenceCount( GetModelPtr() ); +} + +//========================================================= +// LookupActivity +//========================================================= +int CBaseAnimating :: LookupActivity ( int activity ) +{ + ASSERT( activity != 0 ); + + return ::LookupActivity( GetModelPtr(), activity ); +} + +//========================================================= +// LookupActivityHeaviest +// +// Get activity with highest 'weight' +// +//========================================================= +int CBaseAnimating :: LookupActivityHeaviest ( int activity ) +{ + return ::LookupActivityHeaviest( GetModelPtr(), activity ); +} + +//========================================================= +//========================================================= +int CBaseAnimating :: LookupSequence ( const char *label ) +{ + return ::LookupSequence( GetModelPtr(), label ); +} + +float CBaseAnimating :: GetSequenceMoveYaw( int iSequence ) +{ + Vector vecReturn; + + ::GetSequenceLinearMotion( GetModelPtr(), m_flPoseParameter, iSequence, &vecReturn ); + + if( vecReturn.Length() > 0 ) + { + return UTIL_VecToYaw( vecReturn ); + } + + return NOMOTION; +} + +float CBaseAnimating :: GetSequenceMoveDist( int iSequence ) +{ + Vector vecReturn; + + ::GetSequenceLinearMotion( GetModelPtr(), m_flPoseParameter, iSequence, &vecReturn ); + + return vecReturn.Length(); +} + +float CBaseAnimating :: GetSequenceGroundSpeed( int iSequence ) +{ + float t = SequenceDuration( iSequence ); + + if( t > 0 ) + { + return GetSequenceMoveDist( iSequence ) / t; + } + else + { + return 0; + } +} + +//========================================================= +//========================================================= +void CBaseAnimating :: ResetSequenceInfo ( ) +{ + GetSequenceInfo( GetModelPtr(), m_flPoseParameter, pev->sequence, &m_flFrameRate, &m_flGroundSpeed ); + m_fSequenceLoops = ((GetSequenceFlags() & STUDIO_LOOPING) != 0); + pev->animtime = gpGlobals->time; + pev->framerate = 1.0; + m_fSequenceFinished = FALSE; + m_flLastEventCheck = gpGlobals->time; +} + +//========================================================= +//========================================================= +void CBaseEntity :: ResetPoseParameters ( ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + CalcDefaultPoseParameters( pmodel, m_flPoseParameter ); + + pev->vuser1[0] = m_flPoseParameter[0]; + pev->vuser1[1] = m_flPoseParameter[1]; + pev->vuser1[2] = m_flPoseParameter[2]; + + pev->vuser2[0] = m_flPoseParameter[3]; + pev->vuser2[1] = m_flPoseParameter[4]; + pev->vuser2[2] = m_flPoseParameter[5]; + + pev->vuser3[0] = m_flPoseParameter[6]; + pev->vuser3[1] = m_flPoseParameter[7]; + pev->vuser3[2] = m_flPoseParameter[8]; + + pev->vuser4[0] = m_flPoseParameter[9]; + pev->vuser4[1] = m_flPoseParameter[10]; + pev->vuser4[2] = m_flPoseParameter[11]; +} + +int CBaseEntity :: LookupPoseParameter( const char *szName ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::LookupPoseParameter( pmodel, szName, m_flPoseParameter ); +} + +void CBaseEntity :: SetPoseParameter( int iParameter, float flValue ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + ::SetPoseParameter( pmodel, iParameter, flValue, m_flPoseParameter ); +} + +void CBaseEntity :: SetPoseParameter( const char *szName, float flValue ) +{ + SetPoseParameter( LookupPoseParameter( szName ), flValue ); +} + +float CBaseEntity :: GetPoseParameter( int iParameter ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::GetPoseParameter( pmodel, iParameter, m_flPoseParameter ); +} + +float CBaseEntity :: GetPoseParameter( const char *szName ) +{ + return GetPoseParameter( LookupPoseParameter( szName ) ); +} + +bool CBaseEntity :: HasPoseParameter( int iSequence, const char *szName ) +{ + int iParameter = LookupPoseParameter( szName ); + if( iParameter == -1 ) + return false; + return HasPoseParameter( iSequence, iParameter ); +} + +//========================================================= +//========================================================= +bool CBaseEntity :: HasPoseParameter( int iSequence, int iParameter ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + return ::HasPoseParameter( pmodel, iSequence, iParameter ); +} + +//========================================================= +//========================================================= +BOOL CBaseAnimating :: GetSequenceFlags( ) +{ + return ::GetSequenceFlags( GetModelPtr(), pev->sequence ); +} + +//========================================================= +//========================================================= +float CBaseAnimating :: SequenceDuration( int iSequence ) +{ + return ::SequenceDuration( GetModelPtr(), m_flPoseParameter, iSequence ) * pev->framerate; +} + +//========================================================= +// DispatchAnimEvents +//========================================================= +void CBaseAnimating :: DispatchAnimEvents ( float flInterval ) +{ + MonsterEvent_t event; + + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + if ( !pmodel ) + { + ALERT( at_aiconsole, "Gibbed monster is thinking!\n" ); + return; + } + + // FIXME: I have to do this or some events get missed, and this is probably causing the problem below + flInterval = 0.1; + + // FIX: this still sometimes hits events twice + float flStart = pev->frame + (m_flLastEventCheck - pev->animtime) * m_flFrameRate * pev->framerate; + float flEnd = pev->frame + flInterval * m_flFrameRate * pev->framerate; + m_flLastEventCheck = pev->animtime + flInterval; + + m_fSequenceFinished = FALSE; + if (flEnd >= 256 || flEnd <= 0.0) + m_fSequenceFinished = TRUE; + + int index = 0; + + while ( (index = GetAnimationEvent( pmodel, pev->sequence, &event, flStart, flEnd, index ) ) != 0 ) + { + HandleAnimEvent( &event ); + } +} + + +//========================================================= +//========================================================= +float CBaseAnimating :: SetBoneController ( int iController, float flValue ) +{ + return SetController( GetModelPtr(), pev->controller, iController, flValue ); +} + + +// buz +float CBaseAnimating :: GetControllerBound( int iController ) +{ + return GetControllerBound2( GetModelPtr(), iController ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: InitBoneControllers ( void ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + SetController( pmodel, pev->controller, 0, 0.0 ); + SetController( pmodel, pev->controller, 1, 0.0 ); + SetController( pmodel, pev->controller, 2, 0.0 ); + SetController( pmodel, pev->controller, 3, 0.0 ); + + ResetPoseParameters(); +} + +//========================================================= +//========================================================= +float CBaseAnimating :: SetBlending ( int iBlender, float flValue ) +{ + return ::SetBlending( GetModelPtr(), pev->sequence, pev->blending, iBlender, flValue ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: GetBonePosition ( int iBone, Vector &origin, Vector &angles ) +{ + GET_BONE_POSITION( ENT(pev), iBone, origin, angles ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: GetAttachment ( int iAttachment, Vector &origin, Vector &angles ) +{ + GET_ATTACHMENT( ENT(pev), iAttachment, origin, angles ); +} + +int CBaseAnimating :: GetAttachment ( const char *pszAttachment, Vector &origin, Vector &angles ) +{ + CStudioBoneSetup *pStudioHdr = GetBoneSetup(); + if( !pStudioHdr ) return -1; + + int nAttachment = pStudioHdr->FindAttachment( pszAttachment ); + if( nAttachment == -1 ) return -1; + + GET_ATTACHMENT( edict(), nAttachment, origin, angles ); + + return nAttachment; +} + +//========================================================= +//========================================================= +int CBaseAnimating :: FindTransition( int iEndingSequence, int iGoalSequence, int *piDir ) +{ + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + if (piDir == NULL) + { + int iDir; + int sequence = ::FindTransition( pmodel, iEndingSequence, iGoalSequence, &iDir ); + if (iDir != 1) + return -1; + else + return sequence; + } + + return ::FindTransition( pmodel, iEndingSequence, iGoalSequence, piDir ); +} + +//========================================================= +//========================================================= +void CBaseAnimating :: GetAutomovement( Vector &origin, Vector &angles, float flInterval ) +{ + +} + +int CBaseAnimating :: GetHitboxSetByName( const char *szName ) +{ + return ::FindHitboxSetByName( GetModelPtr(), szName ); +} + +void CBaseAnimating :: SetBodygroup( int iGroup, int iValue ) +{ + ::SetBodygroup( GetModelPtr(), pev->body, iGroup, iValue ); +} + +int CBaseAnimating :: GetBodygroup( int iGroup ) +{ + return ::GetBodygroup( GetModelPtr(), pev->body, iGroup ); +} + +int CBaseAnimating :: ExtractBbox( int sequence, Vector &mins, Vector &maxs ) +{ + return ::ExtractBbox( GetModelPtr(), sequence, mins, maxs ); +} + +CStudioBoneSetup *CBaseAnimating :: GetBoneSetup( void ) +{ + return ::GetBaseBoneSetup( pev->modelindex, m_flPoseParameter ); +} + +//========================================================= +//========================================================= + +void CBaseAnimating :: SetSequenceBox( void ) +{ + Vector mins, maxs; + + // Get sequence bbox + if ( ExtractBbox( pev->sequence, mins, maxs ) ) + { + // expand box for rotation + // find min / max for rotations + float yaw = pev->angles.y * (M_PI / 180.0); + + Vector xvector, yvector; + xvector.x = cos(yaw); + xvector.y = sin(yaw); + yvector.x = -sin(yaw); + yvector.y = cos(yaw); + Vector bounds[2]; + + bounds[0] = mins; + bounds[1] = maxs; + + Vector rmin( 9999, 9999, 9999 ); + Vector rmax( -9999, -9999, -9999 ); + Vector base, transformed; + + for (int i = 0; i <= 1; i++ ) + { + base.x = bounds[i].x; + for ( int j = 0; j <= 1; j++ ) + { + base.y = bounds[j].y; + for ( int k = 0; k <= 1; k++ ) + { + base.z = bounds[k].z; + + // transform the point + transformed.x = xvector.x*base.x + yvector.x*base.y; + transformed.y = xvector.y*base.x + yvector.y*base.y; + transformed.z = base.z; + + if (transformed.x < rmin.x) + rmin.x = transformed.x; + if (transformed.x > rmax.x) + rmax.x = transformed.x; + if (transformed.y < rmin.y) + rmin.y = transformed.y; + if (transformed.y > rmax.y) + rmax.y = transformed.y; + if (transformed.z < rmin.z) + rmin.z = transformed.z; + if (transformed.z > rmax.z) + rmax.z = transformed.z; + } + } + } + rmin.z = 0; + rmax.z = rmin.z + 1; + UTIL_SetSize( pev, rmin, rmax ); + } +} + diff --git a/dlls/animation.cpp b/dlls/animation.cpp new file mode 100644 index 0000000..5a7c9d1 --- /dev/null +++ b/dlls/animation.cpp @@ -0,0 +1,795 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include "extdll.h" +#include "util.h" + +#include "studio.h" +#include "bs_defs.h" +#include "r_studioint.h" + +#ifndef ACTIVITY_H +#include "activity.h" +#endif + +#ifndef ANIMATION_H +#include "animation.h" +#endif + +#ifndef SCRIPTEVENT_H +#include "scriptevent.h" +#endif + +#include "cbase.h" + +// Global engine <-> studio model rendering code interface +float m_poseparameter[MAXSTUDIOPOSEPARAM]; // stub +server_studio_api_t IEngineStudio; + +class CBaseBoneSetup : public CStudioBoneSetup +{ + model_t *m_pSubModel; +public: + virtual void debugMsg( char *szFmt, ... ) + { + char buffer[2048]; // must support > 1k messages + va_list args; + + va_start( args, szFmt ); + Q_vsnprintf( buffer, 2048, szFmt, args ); + va_end( args ); + + ALERT( at_console, "%s", buffer ); + } + + virtual mstudioanim_t *GetAnimSourceData( mstudioseqdesc_t *pseqdesc ) + { + mstudioseqgroup_t *pseqgroup; + cache_user_t *paSequences; + + pseqgroup = (mstudioseqgroup_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqgroupindex) + pseqdesc->seqgroup; + + if( pseqdesc->seqgroup == 0 ) + return (mstudioanim_t *)((byte *)m_pStudioHeader + pseqgroup->data + pseqdesc->animindex); + + assert( m_pSubModel ); // assume model is set + + paSequences = (cache_user_t *)m_pSubModel->submodels; + + if( paSequences == NULL ) + { + paSequences = (cache_user_t *)IEngineStudio.Mem_Calloc( MAXSTUDIOGROUPS, sizeof( cache_user_t )); + m_pSubModel->submodels = (dmodel_t *)paSequences; + } + + // check for already loaded + if( !IEngineStudio.Cache_Check(( struct cache_user_s *)&(paSequences[pseqdesc->seqgroup] ))) + { + char filepath[128], modelpath[128], modelname[64]; + + COM_FileBase( m_pSubModel->name, modelname ); + COM_ExtractFilePath( m_pSubModel->name, modelpath ); + + // NOTE: here we build real sub-animation filename because stupid user may rename model without recompile + Q_snprintf( filepath, sizeof( filepath ), "%s/%s%i%i.mdl", modelpath, modelname, pseqdesc->seqgroup / 10, pseqdesc->seqgroup % 10 ); + + ALERT( at_console, "loading: %s\n", filepath ); + IEngineStudio.LoadCacheFile( filepath, (struct cache_user_s *)&paSequences[pseqdesc->seqgroup] ); + } + + return (mstudioanim_t *)((byte *)paSequences[pseqdesc->seqgroup].data + pseqdesc->animindex); + } + + void SetBaseModel( model_t *mod ) { m_pSubModel = mod; } +}; + +static CBaseBoneSetup g_boneSetup; + +//================================================================================================ +// HUD_GetStudioModelInterface +// Export this function for the engine to use the studio renderer class to render objects. +//================================================================================================ +int Server_GetBlendingInterface( int version, sv_blending_interface_t **ppinterface, server_studio_api_t *pstudio, float (*transform)[3][4], float (*bones)[MAXSTUDIOBONES][3][4] ) +{ + if( version != SV_BLENDING_INTERFACE_VERSION ) + return 0; + + ALERT( at_aiconsole, "Server_GetBlendingInterface()\n" ); + + // Copy in engine helper functions + memcpy( &IEngineStudio, pstudio, sizeof( IEngineStudio )); + + // Success + return 1; +} + +void SetupModelBones( studiohdr_t *header ) +{ + g_boneSetup.SetStudioPointers( header, m_poseparameter ); +} + +void CalcDefaultPoseParameters( void *pmodel, float *poseparams ) +{ + studiohdr_t *pstudiohdr; + + if( !( pstudiohdr = (studiohdr_t *)pmodel )) + return; + + g_boneSetup.SetStudioPointers( pstudiohdr, poseparams ); + g_boneSetup.CalcDefaultPoseParameters( poseparams ); +} + +CStudioBoneSetup *GetBaseBoneSetup( int modelindex, float *poseparams ) +{ + studiohdr_t *pstudiohdr; + + model_t *mod = (model_t *)MODEL_HANDLE( modelindex ); + + if( !mod || mod->type != mod_studio ) + return NULL; + + if( !( pstudiohdr = (studiohdr_t *)mod->cache.data )) + return NULL; + + g_boneSetup.SetBaseModel( mod ); + g_boneSetup.SetStudioPointers( pstudiohdr, poseparams ); + + return &g_boneSetup; +} + +//========================================================= +//========================================================= +int LookupPoseParameter( void *pmodel, const char *szName, float *poseparams ) +{ + studiohdr_t *pstudiohdr; + + if( !( pstudiohdr = (studiohdr_t *)pmodel )) + return -1; + + g_boneSetup.SetStudioPointers( pstudiohdr, poseparams ); + + for( int i = 0; i < g_boneSetup.CountPoseParameters(); i++ ) + { + if( !Q_stricmp( g_boneSetup.pPoseParameter( i )->name, szName )) + return i; + } + + return -1; // Error +} + +void SetPoseParameter( void *pmodel, int iParameter, float flValue, float *poseparams ) +{ + studiohdr_t *pstudiohdr; + + if( !( pstudiohdr = (studiohdr_t *)pmodel )) + return; + + g_boneSetup.SetStudioPointers( pstudiohdr, poseparams ); + g_boneSetup.SetPoseParameter( iParameter, flValue, poseparams[iParameter] ); +} + +float GetPoseParameter( void *pmodel, int iParameter, float *poseparams ) +{ + studiohdr_t *pstudiohdr; + + if( !( pstudiohdr = (studiohdr_t *)pmodel )) + return 0.0f; + + g_boneSetup.SetStudioPointers( pstudiohdr, poseparams ); + return g_boneSetup.GetPoseParameter( iParameter, poseparams[iParameter] ); +} + +bool HasPoseParameter( void *pmodel, int iSequence, int iParameter ) +{ + studiohdr_t *pstudiohdr; + + if( !( pstudiohdr = (studiohdr_t *)pmodel )) + return false; + + if( iSequence < 0 || iSequence >= pstudiohdr->numseq ) + return false; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + iSequence; + + if( pseqdesc->blendtype[0] == iParameter || pseqdesc->blendtype[1] == iParameter ) + return true; + return false; +} + +int FindHitboxSetByName( void *pmodel, const char *name ) +{ + studiohdr_t *pstudiohdr; + + if( !( pstudiohdr = (studiohdr_t *)pmodel )) + return -1; + + SetupModelBones( pstudiohdr ); + + for( int i = 0; i < g_boneSetup.GetNumHitboxSets(); i++ ) + { + mstudiohitboxset_t *set = g_boneSetup.pHitboxSet( i ); + + if( !set ) continue; + + if( !Q_stricmp( set->name, name )) + return i; + } + + return -1; +} + +int ExtractBbox( void *pmodel, int sequence, Vector &mins, Vector &maxs ) +{ + studiohdr_t *pstudiohdr; + + if( !( pstudiohdr = (studiohdr_t *)pmodel )) + return 0; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + mins = pseqdesc[sequence].bbmin; + maxs = pseqdesc[sequence].bbmax; + + return 1; +} + +int LookupActivity( void *pmodel, int activity ) +{ + studiohdr_t *pstudiohdr; + + if( !( pstudiohdr = (studiohdr_t *)pmodel )) + return ACTIVITY_NOT_AVAILABLE; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + int weighttotal = 0; + int seq = ACTIVITY_NOT_AVAILABLE; + + for( int i = 0; i < pstudiohdr->numseq; i++ ) + { + if( pseqdesc[i].activity == activity ) + { + weighttotal += pseqdesc[i].actweight; + + if( !weighttotal || RANDOM_LONG( 0, weighttotal - 1 ) < pseqdesc[i].actweight ) + seq = i; + } + } + + return seq; +} + +int LookupActivityHeaviest( void *pmodel, int activity ) +{ + studiohdr_t *pstudiohdr; + + if( !( pstudiohdr = (studiohdr_t *)pmodel )) + return ACTIVITY_NOT_AVAILABLE; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + int weight = 0; + int seq = ACTIVITY_NOT_AVAILABLE; + + for( int i = 0; i < pstudiohdr->numseq; i++ ) + { + if( pseqdesc[i].activity == activity ) + { + if( pseqdesc[i].actweight > weight ) + { + weight = pseqdesc[i].actweight; + seq = i; + } + } + } + + return seq; +} + +int GetEyePosition( void *pmodel, Vector &vecEyePosition ) +{ + studiohdr_t *pstudiohdr; + + if( !( pstudiohdr = (studiohdr_t *)pmodel )) + return 0; + + vecEyePosition = pstudiohdr->eyeposition; + + return 1; +} + +int LookupSequence( void *pmodel, const char *label ) +{ + studiohdr_t *pstudiohdr; + + if( !( pstudiohdr = (studiohdr_t *)pmodel )) + return -1; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + for( int i = 0; i < pstudiohdr->numseq; i++ ) + { + if( !Q_stricmp( pseqdesc[i].label, label )) + return i; + } + + return -1; +} + +int IsSoundEvent( int eventNumber ) +{ + if( eventNumber == SCRIPT_EVENT_SOUND || eventNumber == SCRIPT_EVENT_SOUND_VOICE ) + return 1; + return 0; +} + +void SequencePrecache( void *pmodel, const char *pSequenceName ) +{ + int index = LookupSequence( pmodel, pSequenceName ); + + if( index >= 0 ) + { + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + + if( !pstudiohdr || index >= pstudiohdr->numseq ) + return; + + mstudioseqdesc_t *pseqdesc; + mstudioevent_t *pevent; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + index; + pevent = (mstudioevent_t *)((byte *)pstudiohdr + pseqdesc->eventindex); + + for( int i = 0; i < pseqdesc->numevents; i++ ) + { + // don't send client-side events to the server AI + if( pevent[i].event >= EVENT_CLIENT ) + continue; + + if( IsSoundEvent( pevent[i].event )) + { + if( !Q_strlen(pevent[i].options )) + { + ALERT( at_error, "Bad sound event %d in sequence %s :: %s (sound is \"%s\")\n", + pevent[i].event, pstudiohdr->name, pSequenceName, pevent[i].options ); + } + + PRECACHE_SOUND( pevent[i].options ); + } + } + } +} + +void CalcGaitFrame( void *pmodel, float poseparams[], int &gaitsequence, float &flGaitFrame, float flGaitMovement ) +{ + studiohdr_t *pstudiohdr; + Vector vecMove, vecAngle; + + if( !( pstudiohdr = (studiohdr_t *)pmodel )) + return; + + if( gaitsequence < 0 || gaitsequence >= pstudiohdr->numseq ) + gaitsequence = 0; + + g_boneSetup.SetStudioPointers( pstudiohdr, poseparams ); + g_boneSetup.SeqMovement( gaitsequence, 0.0f, 1.0f, vecMove, vecAngle ); + int numframes = g_boneSetup.LocalMaxFrame( gaitsequence ); + float fps = g_boneSetup.LocalFPS( gaitsequence ); + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + gaitsequence; + float seqMovement = vecMove.Length(); + + // calc gait frame + if( seqMovement > 0.0f ) + flGaitFrame += (flGaitMovement / seqMovement) * numframes; + else flGaitFrame += fps * gpGlobals->frametime; + + // do modulo + flGaitFrame = fmod( flGaitFrame, (float)numframes ); + while( flGaitFrame < 0.0 ) flGaitFrame += numframes; +} + +void GetSequenceInfo( void *pmodel, float poseparams[], int sequence, float *pflFrameRate, float *pflGroundSpeed ) +{ + studiohdr_t *pstudiohdr; + Vector vecMove, vecAngle; + + if( !( pstudiohdr = (studiohdr_t *)pmodel )) + return; + + g_boneSetup.SetStudioPointers( pstudiohdr, poseparams ); + + mstudioseqdesc_t *pseqdesc; + + if( sequence >= pstudiohdr->numseq ) + { + *pflFrameRate = 0.0f; + *pflGroundSpeed = 0.0f; + return; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence; + g_boneSetup.SeqMovement( sequence, 0.0f, 1.0f, vecMove, vecAngle ); + int numframes = g_boneSetup.LocalMaxFrame( sequence ); + float fps = g_boneSetup.LocalFPS( sequence ); + + if( numframes > 1 ) + { + *pflFrameRate = 256.0f * fps / numframes; + *pflGroundSpeed = vecMove.Length(); + *pflGroundSpeed = *pflGroundSpeed * fps / numframes; + } + else + { + *pflFrameRate = 256.0f; + *pflGroundSpeed = 0.0f; + } +} + +void GetSequenceLinearMotion( void *pmodel, float poseparams[], int sequence, Vector *pVec ) +{ + studiohdr_t *pstudiohdr; + Vector vecMove, vecAngle; + + *pVec = g_vecZero; + + if( !( pstudiohdr = (studiohdr_t *)pmodel )) + return; + + if( sequence < 0 || sequence >= pstudiohdr->numseq ) + return; + + g_boneSetup.SetStudioPointers( pstudiohdr, poseparams ); + g_boneSetup.SeqMovement( sequence, 0.0f, 1.0f, vecMove, vecAngle ); + *pVec = vecMove; +} + +int GetSequenceCount( void *pmodel ) +{ + studiohdr_t *pstudiohdr = (studiohdr_t *)pmodel; + if ( !pstudiohdr ) return 0; + + return pstudiohdr->numseq; +} + +int GetSequenceFlags( void *pmodel, int sequence ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + + if( !pstudiohdr || sequence >= pstudiohdr->numseq ) + return 0; + + mstudioseqdesc_t *pseqdesc; + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence; + + return pseqdesc->flags; +} + +int GetAnimationEvent( void *pmodel, int sequence, MonsterEvent_t *pMonsterEvent, float flStart, float flEnd, int index ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + + if( !pstudiohdr || sequence >= pstudiohdr->numseq || !pMonsterEvent ) + return 0; + + int events = 0; + + mstudioseqdesc_t *pseqdesc; + mstudioevent_t *pevent; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence; + pevent = (mstudioevent_t *)((byte *)pstudiohdr + pseqdesc->eventindex); + + if (pseqdesc->numevents == 0 || index > pseqdesc->numevents ) + return 0; + + if( pseqdesc->numframes > 1 ) + { + flStart *= (pseqdesc->numframes - 1) / 256.0f; + flEnd *= (pseqdesc->numframes - 1) / 256.0f; + } + else + { + flStart = 0.0f; + flEnd = 1.0f; + } + + for( ; index < pseqdesc->numevents; index++ ) + { + // Don't send client-side events to the server AI + if( pevent[index].event >= EVENT_CLIENT ) + continue; + + if(( pevent[index].frame >= flStart && pevent[index].frame < flEnd ) || ((pseqdesc->flags & STUDIO_LOOPING) && flEnd >= pseqdesc->numframes - 1 && pevent[index].frame < flEnd - pseqdesc->numframes + 1) ) + { + pMonsterEvent->event = pevent[index].event; + pMonsterEvent->options = pevent[index].options; + return index + 1; + } + } + + return 0; +} + +float SetController( void *pmodel, byte *controller, int iController, float flValue ) +{ + studiohdr_t *pstudiohdr; + + if( !( pstudiohdr = (studiohdr_t *)pmodel )) + return flValue; + + mstudiobonecontroller_t *pbonecontroller = (mstudiobonecontroller_t *)((byte *)pstudiohdr + pstudiohdr->bonecontrollerindex); + + // find first controller that matches the index + for( int i = 0; i < pstudiohdr->numbonecontrollers; i++, pbonecontroller++ ) + { + if( pbonecontroller->index == iController ) + break; + } + + if( i >= pstudiohdr->numbonecontrollers ) + return flValue; + + // wrap 0..360 if it's a rotational controller + if( pbonecontroller->type & ( STUDIO_XR|STUDIO_YR|STUDIO_ZR )) + { + // ugly hack, invert value if end < start + if( pbonecontroller->end < pbonecontroller->start ) + flValue = -flValue; + + // does the controller not wrap? + if( pbonecontroller->start + 359.0f >= pbonecontroller->end ) + { + if( flValue > (( pbonecontroller->start + pbonecontroller->end ) / 2.0f ) + 180 ) + flValue = flValue - 360; + if( flValue < (( pbonecontroller->start + pbonecontroller->end ) / 2.0f ) - 180 ) + flValue = flValue + 360; + } + else + { + if( flValue > 360.0f ) + flValue = flValue - (int)(flValue / 360.0f) * 360.0f; + else if( flValue < 0.0f ) + flValue = flValue + (int)((flValue / -360.0f) + 1.0f) * 360.0f; + } + } + + int setting = 255 * (flValue - pbonecontroller->start) / (pbonecontroller->end - pbonecontroller->start); + + setting = bound( 0, setting, 255 ); + controller[iController] = setting; + + return setting * (1.0f / 255.0f) * (pbonecontroller->end - pbonecontroller->start) + pbonecontroller->start; +} + + +// buz: GetControllerBound +float GetControllerBound2( void *pmodel, int iController ) +{ + studiohdr_t *pstudiohdr; + + if( !( pstudiohdr = (studiohdr_t *)pmodel )) + return 0; + + mstudiobonecontroller_t *pbonecontroller = (mstudiobonecontroller_t *)((byte *)pstudiohdr + pstudiohdr->bonecontrollerindex); + + // find first controller that matches the index + for( int i = 0; i < pstudiohdr->numbonecontrollers; i++, pbonecontroller++ ) + { + if( pbonecontroller->index == iController ) + break; + } + + if( i >= pstudiohdr->numbonecontrollers ) + return 0; + + float result = pbonecontroller->end; + if (result < 0) result *= -1; + return result; +} + + +float SetBlending( void *pmodel, int sequence, byte *blending, int iBlender, float flValue ) +{ + studiohdr_t *pstudiohdr; + + if( !( pstudiohdr = (studiohdr_t *)pmodel )) + return flValue; + + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence; + + if( pseqdesc->blendtype[iBlender] == 0 ) + return flValue; + + if( pseqdesc->blendtype[iBlender] & ( STUDIO_XR|STUDIO_YR|STUDIO_ZR )) + { + // ugly hack, invert value if end < start + if( pseqdesc->blendend[iBlender] < pseqdesc->blendstart[iBlender] ) + flValue = -flValue; + + // does the controller not wrap? + if( pseqdesc->blendstart[iBlender] + 359.0f >= pseqdesc->blendend[iBlender] ) + { + if( flValue > (( pseqdesc->blendstart[iBlender] + pseqdesc->blendend[iBlender] ) / 2.0f ) + 180.0f ) + flValue = flValue - 360.0f; + if( flValue < (( pseqdesc->blendstart[iBlender] + pseqdesc->blendend[iBlender] ) / 2.0f ) - 180.0f ) + flValue = flValue + 360.0f; + } + } + + int setting = 255 * (flValue - pseqdesc->blendstart[iBlender]) / (pseqdesc->blendend[iBlender] - pseqdesc->blendstart[iBlender]); + + setting = bound( 0, setting, 255 ); + blending[iBlender] = setting; + + return setting * (1.0f / 255.0f) * (pseqdesc->blendend[iBlender] - pseqdesc->blendstart[iBlender]) + pseqdesc->blendstart[iBlender]; +} + +int FindTransition( void *pmodel, int iEndingAnim, int iGoalAnim, int *piDir ) +{ + studiohdr_t *pstudiohdr; + + if( !( pstudiohdr = (studiohdr_t *)pmodel )) + return iGoalAnim; + + mstudioseqdesc_t *pseqdesc; + pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + + // bail if we're going to or from a node 0 + if( pseqdesc[iEndingAnim].entrynode == 0 || pseqdesc[iGoalAnim].entrynode == 0 ) + { + return iGoalAnim; + } + + int iEndNode; + + // ALERT( at_console, "from %d to %d: ", pEndNode->iEndNode, pGoalNode->iStartNode ); + + if( *piDir > 0 ) + { + iEndNode = pseqdesc[iEndingAnim].exitnode; + } + else + { + iEndNode = pseqdesc[iEndingAnim].entrynode; + } + + if( iEndNode == pseqdesc[iGoalAnim].entrynode ) + { + *piDir = 1; + return iGoalAnim; + } + + byte *pTransition = ((byte *)pstudiohdr + pstudiohdr->transitionindex); + + int iInternNode = pTransition[(iEndNode-1)*pstudiohdr->numtransitions + (pseqdesc[iGoalAnim].entrynode-1)]; + + if( iInternNode == 0 ) + return iGoalAnim; + + // look for someone going + for( int i = 0; i < pstudiohdr->numseq; i++ ) + { + if( pseqdesc[i].entrynode == iEndNode && pseqdesc[i].exitnode == iInternNode ) + { + *piDir = 1; + return i; + } + + if( pseqdesc[i].nodeflags ) + { + if( pseqdesc[i].exitnode == iEndNode && pseqdesc[i].entrynode == iInternNode ) + { + *piDir = -1; + return i; + } + } + } + + ALERT( at_console, "error in transition graph" ); + + return iGoalAnim; +} + +void SetBodygroup( void *pmodel, int &iBody, int iGroup, int iValue ) +{ + studiohdr_t *pstudiohdr; + + if( !( pstudiohdr = (studiohdr_t *)pmodel )) + return; + + if( iGroup > pstudiohdr->numbodyparts ) + return; + + mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)pstudiohdr + pstudiohdr->bodypartindex) + iGroup; + + if( iValue >= pbodypart->nummodels ) + return; + + int iCurrent = (iBody / pbodypart->base) % pbodypart->nummodels; + iBody = (iBody - (iCurrent * pbodypart->base) + (iValue * pbodypart->base)); +} + +int GetBodygroup( void *pmodel, int iBody, int iGroup ) +{ + studiohdr_t *pstudiohdr; + + if( !( pstudiohdr = (studiohdr_t *)pmodel )) + return 0; + + if( iGroup > pstudiohdr->numbodyparts ) + return 0; + + mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)pstudiohdr + pstudiohdr->bodypartindex) + iGroup; + if( pbodypart->nummodels <= 1 ) + return 0; + + int iCurrent = (iBody / pbodypart->base) % pbodypart->nummodels; + + return iCurrent; +} + +int FindAttachmentByName( void *pmodel, const char *pName ) +{ + studiohdr_t *pstudiohdr; + + if( !( pstudiohdr = (studiohdr_t *)pmodel )) + return 0; + + for( int i = 0; i < pstudiohdr->numattachments; i++ ) + { + mstudioattachment_t *pattachment = (mstudioattachment_t *) ((byte *)pstudiohdr + pstudiohdr->attachmentindex); + + if( !Q_stricmp( pattachment[i].name, pName )) + return i; + + } + + return -1; +} + +float SequenceDuration( void *pmodel, float poseparams[], int iSequence ) +{ + studiohdr_t *pstudiohdr; + + pstudiohdr = (studiohdr_t *)pmodel; + if( !pstudiohdr ) + return 0.1f; + + if( iSequence < 0 || iSequence >= pstudiohdr->numseq ) + return 0.1f; + + g_boneSetup.SetStudioPointers( pstudiohdr, poseparams ); + return g_boneSetup.LocalDuration( iSequence ); +} \ No newline at end of file diff --git a/dlls/animation.h b/dlls/animation.h new file mode 100644 index 0000000..872d49a --- /dev/null +++ b/dlls/animation.h @@ -0,0 +1,64 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef ANIMATION_H +#define ANIMATION_H + +#define ACTIVITY_NOT_AVAILABLE -1 +#define NOMOTION 99999 + +#ifndef MONSTEREVENT_H +#include "monsterevent.h" +#endif + +class CBaseBoneSetup; + +extern int IsSoundEvent( int eventNumber ); + +int LookupActivity( void *pmodel, int activity ); +int LookupActivityHeaviest( void *pmodel, int activity ); +int LookupSequence( void *pmodel, const char *label ); +void GetSequenceInfo( void *pmodel, float poseparams[], int sequence, float *pflFrameRate, float *pflGroundSpeed ); +void GetSequenceLinearMotion( void *pmodel, float poseparams[], int sequence, Vector *pVec ); +int GetSequenceFlags( void *pmodel, int sequence ); +void CalcGaitFrame( void *pmodel, float poseparams[], int &gaitsequence, float &flGaitFrame, float flGaitMovement ); +int LookupAnimationEvents( void *pmodel, CBaseEntity *pEnt, float flStart, float flEnd ); +float SetController( void *pmodel, byte *controller, int iController, float flValue ); +float SetBlending( void *pmodel, int sequence, byte *blending, int iBlender, float flValue ); +int GetEyePosition( void *pmodel, Vector &vecEyePosition ); +void SequencePrecache( void *pmodel, const char *pSequenceName ); +int FindTransition( void *pmodel, int iEndingAnim, int iGoalAnim, int *piDir ); +void SetBodygroup( void *pmodel, int &iBody, int iGroup, int iValue ); +int GetBodygroup( void *pmodel, int iBody, int iGroup ); +int GetAnimationEvent( void *pmodel, int sequence, MonsterEvent_t *pMonsterEvent, float flStart, float flEnd, int index ); +int ExtractBbox( void *pmodel, int sequence, Vector &mins, Vector &maxs ); +int FindAttachmentByName( void *pmodel, const char *pName ); +void CalcDefaultPoseParameters( void *pmodel, float *poseparams ); +bool HasPoseParameter( void *pmodel, int iSequence, int iParameter ); +int LookupPoseParameter( void *pmodel, const char *szName, float *poseparams ); +void SetPoseParameter( void *pmodel, int iParameter, float flValue, float *poseparams ); +float GetPoseParameter( void *pmodel, int iParameter, float *poseparams ); +int FindHitboxSetByName( void *pmodel, const char *name ); +CStudioBoneSetup *GetBaseBoneSetup( int modelindex, float *poseparams ); + +// paranoia 2 added +float GetControllerBound2( void *pmodel, int iController ); +float SequenceDuration( void *pmodel, float poseparams[], int iSequence ); +int GetSequenceCount( void *pmodel ); + +// From /engine/studio.h +#define STUDIO_LOOPING 0x0001 + + +#endif //ANIMATION_H diff --git a/dlls/apache.cpp b/dlls/apache.cpp new file mode 100644 index 0000000..387a67b --- /dev/null +++ b/dlls/apache.cpp @@ -0,0 +1,1082 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef OEM_BUILD + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "effects.h" + +extern DLL_GLOBAL int g_iSkillLevel; + +#define SF_WAITFORTRIGGER (0x04 | 0x40) // UNDONE: Fix! +#define SF_NOWRECKAGE 0x08 + +class CApache : public CBaseMonster +{ + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + int Classify( void ) { return m_iClass?m_iClass:CLASS_HUMAN_MILITARY; }; + int BloodColor( void ) { return DONT_BLEED; } + void Killed( entvars_t *pevAttacker, int iGib ); + void GibMonster( void ); + + void SetObjectCollisionBox( void ) + { + pev->absmin = pev->origin + Vector( -300, -300, -172); + pev->absmax = pev->origin + Vector(300, 300, 8); + } + + void EXPORT HuntThink( void ); + void EXPORT FlyTouch( CBaseEntity *pOther ); + void EXPORT CrashTouch( CBaseEntity *pOther ); + void EXPORT DyingThink( void ); + void EXPORT StartupUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT NullThink( void ); + + void ShowDamage( void ); + void Flight( void ); + void FireRocket( void ); + BOOL FireGun( void ); + + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + + // Wargon: Îâåððàéä ðåëàòèîíøèïà âåðòîëåòà. (1.1) + int IRelationship( CBaseEntity *pTarget ); + + int m_iRockets; + float m_flForce; + float m_flNextRocket; + + Vector m_vecTarget; + Vector m_posTarget; + + Vector m_vecDesired; + Vector m_posDesired; + + Vector m_vecGoal; + + Vector m_angGun; + float m_flLastSeen; + float m_flPrevSeen; + + int m_iSoundState; // don't save this + + int m_iSpriteTexture; + int m_iExplode; + int m_iBodyGibs; + + float m_flGoalSpeed; + + int m_iDoSmokePuff; + CBeam *m_pBeam; +}; +LINK_ENTITY_TO_CLASS( monster_apache, CApache ); + +TYPEDESCRIPTION CApache::m_SaveData[] = +{ + DEFINE_FIELD( CApache, m_iRockets, FIELD_INTEGER ), + DEFINE_FIELD( CApache, m_flForce, FIELD_FLOAT ), + DEFINE_FIELD( CApache, m_flNextRocket, FIELD_TIME ), + DEFINE_FIELD( CApache, m_vecTarget, FIELD_VECTOR ), + DEFINE_FIELD( CApache, m_posTarget, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CApache, m_vecDesired, FIELD_VECTOR ), + DEFINE_FIELD( CApache, m_posDesired, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CApache, m_vecGoal, FIELD_VECTOR ), + DEFINE_FIELD( CApache, m_angGun, FIELD_VECTOR ), + DEFINE_FIELD( CApache, m_flLastSeen, FIELD_TIME ), + DEFINE_FIELD( CApache, m_flPrevSeen, FIELD_TIME ), +// DEFINE_FIELD( CApache, m_iSoundState, FIELD_INTEGER ), // Don't save, precached +// DEFINE_FIELD( CApache, m_iSpriteTexture, FIELD_INTEGER ), +// DEFINE_FIELD( CApache, m_iExplode, FIELD_INTEGER ), +// DEFINE_FIELD( CApache, m_iBodyGibs, FIELD_INTEGER ), + DEFINE_FIELD( CApache, m_pBeam, FIELD_CLASSPTR ), + DEFINE_FIELD( CApache, m_flGoalSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CApache, m_iDoSmokePuff, FIELD_INTEGER ), +}; +IMPLEMENT_SAVERESTORE( CApache, CBaseMonster ); + + +void CApache :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/apache.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, -64 ), Vector( 32, 32, 0 ) ); + UTIL_SetOrigin( this, pev->origin ); + + pev->flags |= FL_MONSTER; + pev->takedamage = DAMAGE_AIM; + if (pev->health == 0) + pev->health = gSkillData.apacheHealth; + + m_flFieldOfView = -0.707; // 270 degrees + + pev->sequence = 0; + ResetSequenceInfo( ); + pev->frame = RANDOM_LONG(0, 0xFF); + + InitBoneControllers(); + + if (pev->spawnflags & SF_WAITFORTRIGGER) + { + SetUse(&CApache :: StartupUse ); + } + else + { + SetThink(&CApache :: HuntThink ); + SetTouch(&CApache :: FlyTouch ); + SetNextThink( 1.0 ); + } + + m_iRockets = 10; +} + + +void CApache::Precache( void ) +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/apache.mdl"); + + PRECACHE_SOUND("apache/ap_rotor1.wav"); + PRECACHE_SOUND("apache/ap_rotor2.wav"); + PRECACHE_SOUND("apache/ap_rotor3.wav"); + PRECACHE_SOUND("apache/ap_whine1.wav"); + + PRECACHE_SOUND("weapons/mortarhit.wav"); + + m_iSpriteTexture = PRECACHE_MODEL( "sprites/white.spr" ); + + PRECACHE_SOUND("turret/tu_fire1.wav"); + + PRECACHE_MODEL("sprites/lgtning.spr"); + + m_iExplode = PRECACHE_MODEL( "sprites/fexplo.spr" ); + m_iBodyGibs = PRECACHE_MODEL( "models/metalplategibs_green.mdl" ); + + UTIL_PrecacheOther( "hvr_rocket" ); +} + + + +void CApache::NullThink( void ) +{ + StudioFrameAdvance( ); + SetNextThink( 0.5 ); +} + + +void CApache::StartupUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Wargon: Ôèêñ íåñáèâàåìîñòè âåðòîëåòà. + pev->spawnflags &= ~SF_MONSTER_INVINCIBLE; + + SetThink(&CApache:: HuntThink ); + SetTouch(&CApache:: FlyTouch ); + SetNextThink( 0.1 ); + SetUse( NULL ); +} + +void CApache :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->gravity = 0.3; + + STOP_SOUND( ENT(pev), CHAN_STATIC, "apache/ap_rotor2.wav" ); + + UTIL_SetSize( pev, Vector( -32, -32, -64), Vector( 32, 32, 0) ); + SetThink(&CApache :: DyingThink ); + SetTouch(&CApache :: CrashTouch ); + SetNextThink( 0.1 ); + pev->health = 0; + pev->takedamage = DAMAGE_NO; + + // Wargon: Ôèêñ íåñðàáàòûâàíèÿ òðèããåðà ïðè ñìåðòè âåðòîëåòà. + pev->deadflag = DEAD_DEAD; + FCheckAITrigger(); + + if (pev->spawnflags & SF_NOWRECKAGE) + { + m_flNextRocket = gpGlobals->time + 4.0; + } + else + { + m_flNextRocket = gpGlobals->time + 15.0; + } +} + +void CApache :: DyingThink( void ) +{ + StudioFrameAdvance( ); + SetNextThink( 0.1 ); + + pev->avelocity = pev->avelocity * 1.02; + + // still falling? + if (m_flNextRocket > gpGlobals->time ) + { + // random explosions + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION); // This just makes a dynamic light now + WRITE_COORD( pev->origin.x + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( pev->origin.y + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( pev->origin.z + RANDOM_FLOAT( -150, -50 )); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( RANDOM_LONG(0,29) + 30 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + + // lots of smoke + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( pev->origin.x + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( pev->origin.y + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( pev->origin.z + RANDOM_FLOAT( -150, -50 )); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 100 ); // scale * 10 + WRITE_BYTE( 10 ); // framerate + MESSAGE_END(); + + Vector vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z ); + + // size + WRITE_COORD( 400 ); + WRITE_COORD( 400 ); + WRITE_COORD( 132 ); + + // velocity + WRITE_COORD( pev->velocity.x ); + WRITE_COORD( pev->velocity.y ); + WRITE_COORD( pev->velocity.z ); + + // randomization + WRITE_BYTE( 50 ); + + // Model + WRITE_SHORT( m_iBodyGibs ); //model id# + + // # of shards + WRITE_BYTE( 4 ); // let client decide + + // duration + WRITE_BYTE( 30 );// 3.0 seconds + + // flags + + WRITE_BYTE( BREAK_METAL ); + MESSAGE_END(); + + // don't stop it we touch a entity + pev->flags &= ~FL_ONGROUND; + SetNextThink( 0.2 ); + return; + } + else + { + Vector vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + + /* + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_EXPLOSION); // This just makes a dynamic light now + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 300 ); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( 250 ); // scale * 10 + WRITE_BYTE( 8 ); // framerate + MESSAGE_END(); + */ + + // fireball + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_SPRITE ); + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 256 ); + WRITE_SHORT( m_iExplode ); + WRITE_BYTE( 120 ); // scale * 10 + WRITE_BYTE( 255 ); // brightness + MESSAGE_END(); + + // big smoke + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 512 ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 250 ); // scale * 10 + WRITE_BYTE( 5 ); // framerate + MESSAGE_END(); + + // blast circle + 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 + 2000 ); // reach damage radius over .2 seconds + WRITE_SHORT( m_iSpriteTexture ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 4 ); // life + WRITE_BYTE( 32 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 192 ); // r, g, b + WRITE_BYTE( 128 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + EMIT_SOUND(ENT(pev), CHAN_STATIC, "weapons/mortarhit.wav", 1.0, 0.3); + + RadiusDamage( pev->origin, pev, pev, 300, CLASS_NONE, DMG_BLAST ); + + if (/*!(pev->spawnflags & SF_NOWRECKAGE) && */(pev->flags & FL_ONGROUND)) + { + CBaseEntity *pWreckage = Create( "cycler_wreckage", pev->origin, pev->angles ); + // SET_MODEL( ENT(pWreckage->pev), STRING(pev->model) ); + UTIL_SetSize( pWreckage->pev, Vector( -200, -200, -128 ), Vector( 200, 200, -32 ) ); + pWreckage->pev->frame = pev->frame; + pWreckage->pev->sequence = pev->sequence; + pWreckage->pev->framerate = 0; + pWreckage->pev->dmgtime = gpGlobals->time + 5; + } + + // gibs + vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 64); + + // size + WRITE_COORD( 400 ); + WRITE_COORD( 400 ); + WRITE_COORD( 128 ); + + // velocity + WRITE_COORD( 0 ); + WRITE_COORD( 0 ); + WRITE_COORD( 200 ); + + // randomization + WRITE_BYTE( 30 ); + + // Model + WRITE_SHORT( m_iBodyGibs ); //model id# + + // # of shards + WRITE_BYTE( 200 ); + + // duration + WRITE_BYTE( 200 );// 10.0 seconds + + // flags + + WRITE_BYTE( BREAK_METAL ); + MESSAGE_END(); + + SetThink(&CApache :: SUB_Remove ); + SetNextThink( 0.1 ); + } +} + + +void CApache::FlyTouch( CBaseEntity *pOther ) +{ + // bounce if we hit something solid + if ( pOther->pev->solid == SOLID_BSP) + { + TraceResult tr = UTIL_GetGlobalTrace( ); + + // UNDONE, do a real bounce + pev->velocity = pev->velocity + tr.vecPlaneNormal * (pev->velocity.Length() + 200); + } +} + + +void CApache::CrashTouch( CBaseEntity *pOther ) +{ + // only crash if we hit something solid + if ( pOther->pev->solid == SOLID_BSP) + { + SetTouch( NULL ); + m_flNextRocket = gpGlobals->time; + SetNextThink( 0 ); + } +} + + + +void CApache :: GibMonster( void ) +{ + // EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "common/bodysplat.wav", 0.75, ATTN_NORM, 0, 200); +} + + +void CApache :: HuntThink( void ) +{ + StudioFrameAdvance( ); + SetNextThink( 0.1 ); + + ShowDamage( ); + + if ( m_pGoalEnt == NULL && !FStringNull(pev->target) )// this monster has a target + { + m_pGoalEnt = UTIL_FindEntityByTargetname( NULL, STRING( pev->target ) ); + if (m_pGoalEnt) + { + m_posDesired = m_pGoalEnt->pev->origin; + UTIL_MakeAimVectors( m_pGoalEnt->pev->angles ); + m_vecGoal = gpGlobals->v_forward; + } + } + + // if (m_hEnemy == NULL) + { + Look( 4092 ); + m_hEnemy = BestVisibleEnemy( ); + } + + // generic speed up + if (m_flGoalSpeed < 800) + m_flGoalSpeed += 5; + + if (m_hEnemy != NULL) + { + // ALERT( at_console, "%s\n", STRING( m_hEnemy->pev->classname ) ); + if (FVisible( m_hEnemy )) + { + if (m_flLastSeen < gpGlobals->time - 5) + m_flPrevSeen = gpGlobals->time; + m_flLastSeen = gpGlobals->time; + + // Wargon: Ôèêñ òî÷êè ïðèöåëèâàíèÿ. + m_posTarget = m_hEnemy->Center() + Vector( -16, 16, 64 ); + } + else + { + m_hEnemy = NULL; + } + } + + m_vecTarget = (m_posTarget - pev->origin).Normalize(); + + float flLength = (pev->origin - m_posDesired).Length(); + + if (m_pGoalEnt) + { + // ALERT( at_console, "%.0f\n", flLength ); + + if (flLength < 128) + { + m_pGoalEnt = UTIL_FindEntityByTargetname( NULL, STRING( m_pGoalEnt->pev->target ) ); + if (m_pGoalEnt) + { + m_posDesired = m_pGoalEnt->pev->origin; + UTIL_MakeAimVectors( m_pGoalEnt->pev->angles ); + m_vecGoal = gpGlobals->v_forward; + flLength = (pev->origin - m_posDesired).Length(); + } + } + } + else + { + m_posDesired = pev->origin; + } + + if (flLength > 250) // 500 + { + // float flLength2 = (m_posTarget - pev->origin).Length() * (1.5 - DotProduct((m_posTarget - pev->origin).Normalize(), pev->velocity.Normalize() )); + // if (flLength2 < flLength) + if (m_flLastSeen + 90 > gpGlobals->time && DotProduct( (m_posTarget - pev->origin).Normalize(), (m_posDesired - pev->origin).Normalize( )) > 0.25) + { + m_vecDesired = (m_posTarget - pev->origin).Normalize( ); + } + else + { + m_vecDesired = (m_posDesired - pev->origin).Normalize( ); + } + } + else + { + m_vecDesired = m_vecGoal; + } + + Flight( ); + + // ALERT( at_console, "%.0f %.0f %.0f\n", gpGlobals->time, m_flLastSeen, m_flPrevSeen ); + if ((m_flLastSeen + 1 > gpGlobals->time) && (m_flPrevSeen + 2 < gpGlobals->time)) + { + if (FireGun( )) + { + // slow down if we're fireing + if (m_flGoalSpeed > 400) + m_flGoalSpeed = 400; + } + + // don't fire rockets and gun on easy mode + if (g_iSkillLevel == SKILL_EASY) + m_flNextRocket = gpGlobals->time + 10.0; + } + + UTIL_MakeAimVectors( pev->angles ); + Vector vecEst = (gpGlobals->v_forward * 800 + pev->velocity).Normalize( ); + // ALERT( at_console, "%d %d %d %4.2f\n", pev->angles.x < 0, DotProduct( pev->velocity, gpGlobals->v_forward ) > -100, m_flNextRocket < gpGlobals->time, DotProduct( m_vecTarget, vecEst ) ); + + if ((m_iRockets % 2) == 1) + { + FireRocket( ); + // ALERT(at_console, "FIRE FIRST\n"); + m_flNextRocket = gpGlobals->time + 0.5; + if (m_iRockets <= 0) + { + m_flNextRocket = gpGlobals->time + 10; + m_iRockets = 10; + } + } + else if (pev->angles.x < 0 && DotProduct( pev->velocity, gpGlobals->v_forward ) > -100 && m_flNextRocket < gpGlobals->time) + { + if (m_flLastSeen + 60 > gpGlobals->time) + { + if (m_hEnemy != NULL) + { + // make sure it's a good shot + if (DotProduct( m_vecTarget, vecEst) > .965) + { + TraceResult tr; + + UTIL_TraceLine( pev->origin, pev->origin + vecEst * 4096, ignore_monsters, edict(), &tr ); + if ((tr.vecEndPos - m_posTarget).Length() < 512) + { + // ALERT(at_console, "FIRE SECOND\n"); + FireRocket( ); + } + } + } + /* else + { + TraceResult tr; + + UTIL_TraceLine( pev->origin, pev->origin + vecEst * 4096, dont_ignore_monsters, edict(), &tr ); + // just fire when close + if ((tr.vecEndPos - m_posTarget).Length() < 512) + { + ALERT(at_console, "FIRE THIRD\n"); + FireRocket( ); + } + }*/ + } + } +} + + +void CApache :: Flight( void ) +{ + // tilt model 5 degrees + Vector vecAdj = Vector( 5.0, 0, 0 ); + + // estimate where I'll be facing in one seconds + UTIL_MakeAimVectors( pev->angles + pev->avelocity * 2 + vecAdj); + // Vector vecEst1 = pev->origin + pev->velocity + gpGlobals->v_up * m_flForce - Vector( 0, 0, 384 ); + // float flSide = DotProduct( m_posDesired - vecEst1, gpGlobals->v_right ); + + float flSide = DotProduct( m_vecDesired, gpGlobals->v_right ); + + if (flSide < 0) + { + if (pev->avelocity.y < 60) + { + pev->avelocity.y += 8; // 9 * (3.0/2.0); + } + } + else + { + if (pev->avelocity.y > -60) + { + pev->avelocity.y -= 8; // 9 * (3.0/2.0); + } + } + pev->avelocity.y *= 0.98; + + // estimate where I'll be in two seconds + UTIL_MakeAimVectors( pev->angles + pev->avelocity * 1 + vecAdj); + Vector vecEst = pev->origin + pev->velocity * 2.0 + gpGlobals->v_up * m_flForce * 20 - Vector( 0, 0, 384 * 2 ); + + // add immediate force + UTIL_MakeAimVectors( pev->angles + vecAdj); + pev->velocity.x += gpGlobals->v_up.x * m_flForce; + pev->velocity.y += gpGlobals->v_up.y * m_flForce; + pev->velocity.z += gpGlobals->v_up.z * m_flForce; + // add gravity + pev->velocity.z -= 38.4; // 32ft/sec + + + float flSpeed = pev->velocity.Length(); + float flDir = DotProduct( Vector( gpGlobals->v_forward.x, gpGlobals->v_forward.y, 0 ), Vector( pev->velocity.x, pev->velocity.y, 0 ) ); + if (flDir < 0) + flSpeed = -flSpeed; + + float flDist = DotProduct( m_posDesired - vecEst, gpGlobals->v_forward ); + + // float flSlip = DotProduct( pev->velocity, gpGlobals->v_right ); + float flSlip = -DotProduct( m_posDesired - vecEst, gpGlobals->v_right ); + + // fly sideways + if (flSlip > 0) + { + if (pev->angles.z > -30 && pev->avelocity.z > -15) + pev->avelocity.z -= 4; + else + pev->avelocity.z += 2; + } + else + { + + if (pev->angles.z < 30 && pev->avelocity.z < 15) + pev->avelocity.z += 4; + else + pev->avelocity.z -= 2; + } + + // sideways drag + pev->velocity.x = pev->velocity.x * (1.0 - fabs( gpGlobals->v_right.x ) * 0.05); + pev->velocity.y = pev->velocity.y * (1.0 - fabs( gpGlobals->v_right.y ) * 0.05); + pev->velocity.z = pev->velocity.z * (1.0 - fabs( gpGlobals->v_right.z ) * 0.05); + + // general drag + pev->velocity = pev->velocity * 0.995; + + // apply power to stay correct height + if (m_flForce < 80 && vecEst.z < m_posDesired.z) + { + m_flForce += 12; + } + else if (m_flForce > 30) + { + if (vecEst.z > m_posDesired.z) + m_flForce -= 8; + } + + // pitch forward or back to get to target + if (flDist > 0 && flSpeed < m_flGoalSpeed /* && flSpeed < flDist */ && pev->angles.x + pev->avelocity.x > -40) + { + // ALERT( at_console, "F " ); + // lean forward + pev->avelocity.x -= 12.0; + } + else if (flDist < 0 && flSpeed > -50 && pev->angles.x + pev->avelocity.x < 20) + { + // ALERT( at_console, "B " ); + // lean backward + pev->avelocity.x += 12.0; + } + else if (pev->angles.x + pev->avelocity.x > 0) + { + // ALERT( at_console, "f " ); + pev->avelocity.x -= 4.0; + } + else if (pev->angles.x + pev->avelocity.x < 0) + { + // ALERT( at_console, "b " ); + pev->avelocity.x += 4.0; + } + + // ALERT( at_console, "%.0f %.0f : %.0f %.0f : %.0f %.0f : %.0f\n", pev->origin.x, pev->velocity.x, flDist, flSpeed, pev->angles.x, pev->avelocity.x, m_flForce ); + // ALERT( at_console, "%.0f %.0f : %.0f %0.f : %.0f\n", pev->origin.z, pev->velocity.z, vecEst.z, m_posDesired.z, m_flForce ); + + // make rotor, engine sounds + if (m_iSoundState == 0) + { + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_rotor2.wav", 1.0, 0.3, 0, 110 ); + // EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_whine1.wav", 0.5, 0.2, 0, 110 ); + + m_iSoundState = SND_CHANGE_PITCH; // hack for going through level transitions + } + else + { + CBaseEntity *pPlayer = NULL; + + pPlayer = UTIL_FindEntityByClassname( NULL, "player" ); + // UNDONE: this needs to send different sounds to every player for multiplayer. + if (pPlayer) + { + + float pitch = DotProduct( pev->velocity - pPlayer->pev->velocity, (pPlayer->pev->origin - pev->origin).Normalize() ); + + pitch = (int)(100 + pitch / 50.0); + + if (pitch > 250) + pitch = 250; + if (pitch < 50) + pitch = 50; + if (pitch == 100) + pitch = 101; + + float flVol = (m_flForce / 100.0) + .1; + if (flVol > 1.0) + flVol = 1.0; + + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_rotor2.wav", 1.0, 0.3, SND_CHANGE_PITCH | SND_CHANGE_VOL, pitch); + } + // EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_whine1.wav", flVol, 0.2, SND_CHANGE_PITCH | SND_CHANGE_VOL, pitch); + + // ALERT( at_console, "%.0f %.2f\n", pitch, flVol ); + } +} + + +void CApache :: FireRocket( void ) +{ + static float side = 1.0; + static int count; + + if (m_iRockets <= 0) + return; + + UTIL_MakeAimVectors( pev->angles ); + Vector vecSrc = pev->origin + 1.5 * (gpGlobals->v_forward * 21 + gpGlobals->v_right * 70 * side + gpGlobals->v_up * -79); + + switch( m_iRockets % 5) + { + case 0: vecSrc = vecSrc + gpGlobals->v_right * 10; break; + case 1: vecSrc = vecSrc - gpGlobals->v_right * 10; break; + case 2: vecSrc = vecSrc + gpGlobals->v_up * 10; break; + case 3: vecSrc = vecSrc - gpGlobals->v_up * 10; break; + case 4: break; + } + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSrc ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSrc.x ); + WRITE_COORD( vecSrc.y ); + WRITE_COORD( vecSrc.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 20 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + + CBaseEntity *pRocket = CBaseEntity::Create( "hvr_rocket", vecSrc, pev->angles, edict() ); + if (pRocket) + pRocket->pev->velocity = pev->velocity + gpGlobals->v_forward * 100; + + m_iRockets--; + + side = - side; +} + + + +BOOL CApache :: FireGun( ) +{ + UTIL_MakeAimVectors( pev->angles ); + + Vector posGun, angGun; + GetAttachment( 1, posGun, angGun ); + + Vector vecTarget = (m_posTarget - posGun).Normalize( ); + + Vector vecOut; + + vecOut.x = DotProduct( gpGlobals->v_forward, vecTarget ); + vecOut.y = -DotProduct( gpGlobals->v_right, vecTarget ); + vecOut.z = DotProduct( gpGlobals->v_up, vecTarget ); + + Vector angles = UTIL_VecToAngles (vecOut); + + angles.x = -angles.x; + if (angles.y > 180) + angles.y = angles.y - 360; + if (angles.y < -180) + angles.y = angles.y + 360; + if (angles.x > 180) + angles.x = angles.x - 360; + if (angles.x < -180) + angles.x = angles.x + 360; + + if (angles.x > m_angGun.x) + m_angGun.x = min( angles.x, m_angGun.x + 12 ); + if (angles.x < m_angGun.x) + m_angGun.x = max( angles.x, m_angGun.x - 12 ); + if (angles.y > m_angGun.y) + m_angGun.y = min( angles.y, m_angGun.y + 12 ); + if (angles.y < m_angGun.y) + m_angGun.y = max( angles.y, m_angGun.y - 12 ); + + m_angGun.y = SetBoneController( 0, m_angGun.y ); + m_angGun.x = SetBoneController( 1, m_angGun.x ); + + Vector posBarrel, angBarrel; + GetAttachment( 0, posBarrel, angBarrel ); + Vector vecGun = (posBarrel - posGun).Normalize( ); + + if (DotProduct( vecGun, vecTarget ) > 0.98) + { +#if 1 + FireBullets( 1, posGun, vecGun, VECTOR_CONE_4DEGREES, 8192, BULLET_NORMAL, gSkillData.monDmg12MM ); + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "turret/tu_fire1.wav", 1, 0.3); +#else + static float flNext; + TraceResult tr; + UTIL_TraceLine( posGun, posGun + vecGun * 8192, dont_ignore_monsters, ENT( pev ), &tr ); + + if (!m_pBeam) + { + m_pBeam = CBeam::BeamCreate( "sprites/lgtning.spr", 80 ); + m_pBeam->PointEntInit( pev->origin, entindex( ) ); + m_pBeam->SetEndAttachment( 1 ); + m_pBeam->SetColor( 255, 180, 96 ); + m_pBeam->SetBrightness( 192 ); + } + + if (flNext < gpGlobals->time) + { + flNext = gpGlobals->time + 0.5; + m_pBeam->SetStartPos( tr.vecEndPos ); + } +#endif + return TRUE; + } + else + { + if (m_pBeam) + { + UTIL_Remove( m_pBeam ); + m_pBeam = NULL; + } + } + return FALSE; +} + + + +void CApache :: ShowDamage( void ) +{ + if (m_iDoSmokePuff > 0 || RANDOM_LONG(0,99) > pev->health) + { + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z - 32 ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( RANDOM_LONG(0,9) + 20 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + } + if (m_iDoSmokePuff > 0) + m_iDoSmokePuff--; +} + + +int CApache :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if (pevInflictor->owner == edict()) + return 0; + + if (bitsDamageType & DMG_BLAST) + { + flDamage *= 2; + } + + /* + if ( (bitsDamageType & DMG_BULLET) && flDamage > 50) + { + // clip bullet damage at 50 + flDamage = 50; + } + */ + + // ALERT( at_console, "%.0f\n", flDamage ); + return CBaseEntity::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + + +void CApache::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + // ALERT( at_console, "%d %.0f\n", ptr->iHitgroup, flDamage ); + + // ignore blades + if (ptr->iHitgroup == 6 && (bitsDamageType & (DMG_ENERGYBEAM|DMG_BULLET|DMG_CLUB))) + return; + + // hit hard, hits cockpit, hits engines + if (flDamage > 50 || ptr->iHitgroup == 1 || ptr->iHitgroup == 2) + { + // ALERT( at_console, "%.0f\n", flDamage ); + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + m_iDoSmokePuff = 3 + (flDamage / 5.0); + } + else + { + // do half damage in the body + // AddMultiDamage( pevAttacker, this, flDamage / 2.0, bitsDamageType ); + UTIL_Ricochet( ptr->vecEndPos, 2.0 ); + } +} + +// Wargon: Âåðòîëåò íå ñ÷èòàåò àëüôîâöåâ âðàãàìè. (1.1) +int CApache::IRelationship ( CBaseEntity *pTarget ) +{ + if (FClassnameIs(pTarget->pev, "monster_human_alpha")) + return R_NO; + return CBaseMonster::IRelationship(pTarget); +} + + + + +class CApacheHVR : public CGrenade +{ + void Spawn( void ); + void Precache( void ); + void EXPORT IgniteThink( void ); + void EXPORT AccelerateThink( void ); + + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_iTrail; + Vector m_vecForward; +}; +LINK_ENTITY_TO_CLASS( hvr_rocket, CApacheHVR ); + +TYPEDESCRIPTION CApacheHVR::m_SaveData[] = +{ +// DEFINE_FIELD( CApacheHVR, m_iTrail, FIELD_INTEGER ), // Dont' save, precache + DEFINE_FIELD( CApacheHVR, m_vecForward, FIELD_VECTOR ), +}; + +IMPLEMENT_SAVERESTORE( CApacheHVR, CGrenade ); + +void CApacheHVR :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/HVR.mdl"); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + UTIL_SetOrigin( this, pev->origin ); + + SetThink(&CApacheHVR :: IgniteThink ); + SetTouch(&CApacheHVR :: ExplodeTouch ); + + UTIL_MakeAimVectors( pev->angles ); + m_vecForward = gpGlobals->v_forward; + pev->gravity = 0.5; + + SetNextThink( 0.1 ); + + pev->dmg = 150; +} + + +void CApacheHVR :: Precache( void ) +{ + PRECACHE_MODEL("models/HVR.mdl"); + m_iTrail = PRECACHE_MODEL("sprites/smoke.spr"); + PRECACHE_SOUND ("weapons/rocket1.wav"); +} + + +void CApacheHVR :: IgniteThink( void ) +{ + // pev->movetype = MOVETYPE_TOSS; + + // pev->movetype = MOVETYPE_FLY; + pev->effects |= EF_LIGHT; + + // make rocket sound + EMIT_SOUND( ENT(pev), CHAN_VOICE, "weapons/rocket1.wav", 1, 0.5 ); + + // rocket trail + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + + WRITE_BYTE( TE_BEAMFOLLOW ); + WRITE_SHORT(entindex()); // entity + WRITE_SHORT(m_iTrail ); // model + WRITE_BYTE( 15 ); // life + WRITE_BYTE( 5 ); // width + WRITE_BYTE( 224 ); // r, g, b + WRITE_BYTE( 224 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + + MESSAGE_END(); // move PHS/PVS data sending into here (SEND_ALL, SEND_PVS, SEND_PHS) + + // set to accelerate + SetThink(&CApacheHVR :: AccelerateThink ); + SetNextThink( 0.1 ); +} + + +void CApacheHVR :: AccelerateThink( void ) +{ + // check world boundaries + if (pev->origin.x < -4096 || pev->origin.x > 4096 || pev->origin.y < -4096 || pev->origin.y > 4096 || pev->origin.z < -4096 || pev->origin.z > 4096) + { + UTIL_Remove( this ); + return; + } + + // accelerate + float flSpeed = pev->velocity.Length(); + if (flSpeed < 1800) + { + pev->velocity = pev->velocity + m_vecForward * 200; + } + + // re-aim + pev->angles = UTIL_VecToAngles( pev->velocity ); + + SetNextThink( 0.1 ); +} + + +#endif diff --git a/dlls/barnacle.cpp b/dlls/barnacle.cpp new file mode 100644 index 0000000..3e865eb --- /dev/null +++ b/dlls/barnacle.cpp @@ -0,0 +1,435 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// barnacle - stationary ceiling mounted 'fishing' monster +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" + +#define BARNACLE_BODY_HEIGHT 44 // how 'tall' the barnacle's model is. +#define BARNACLE_PULL_SPEED 8 +#define BARNACLE_KILL_VICTIM_DELAY 5 // how many seconds after pulling prey in to gib them. + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define BARNACLE_AE_PUKEGIB 2 + +class CBarnacle : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + CBaseEntity *TongueTouchEnt ( float *pflLength ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void EXPORT BarnacleThink ( void ); + void EXPORT WaitTillDead ( void ); + void Killed( entvars_t *pevAttacker, int iGib ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + float m_flAltitude; + float m_flKillVictimTime; + int m_cGibs;// barnacle loads up on gibs each time it kills something. + BOOL m_fTongueExtended; + BOOL m_fLiftingPrey; + float m_flTongueAdj; +}; +LINK_ENTITY_TO_CLASS( monster_barnacle, CBarnacle ); + +TYPEDESCRIPTION CBarnacle::m_SaveData[] = +{ + DEFINE_FIELD( CBarnacle, m_flAltitude, FIELD_FLOAT ), + DEFINE_FIELD( CBarnacle, m_flKillVictimTime, FIELD_TIME ), + DEFINE_FIELD( CBarnacle, m_cGibs, FIELD_INTEGER ),// barnacle loads up on gibs each time it kills something. + DEFINE_FIELD( CBarnacle, m_fTongueExtended, FIELD_BOOLEAN ), + DEFINE_FIELD( CBarnacle, m_fLiftingPrey, FIELD_BOOLEAN ), + DEFINE_FIELD( CBarnacle, m_flTongueAdj, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CBarnacle, CBaseMonster ); + + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CBarnacle :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MONSTER; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CBarnacle :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case BARNACLE_AE_PUKEGIB: + CGib::SpawnRandomGibs( pev, 1, 1 ); + break; + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CBarnacle :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/barnacle.mdl"); + UTIL_SetSize( pev, Vector(-16, -16, -32), Vector(16, 16, 0) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_NONE; + pev->takedamage = DAMAGE_AIM; + m_bloodColor = BLOOD_COLOR_RED; + pev->effects |= EF_INVLIGHT; // take light from the ceiling + pev->health = 25; + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_flKillVictimTime = 0; + m_cGibs = 0; + m_fLiftingPrey = FALSE; + m_flTongueAdj = -100; + + InitBoneControllers(); + + SetActivity ( ACT_IDLE ); + + SetThink(&CBarnacle :: BarnacleThink ); + SetNextThink( 0.5 ); + + UTIL_SetOrigin ( this, pev->origin ); +} + +int CBarnacle::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( bitsDamageType & DMG_CLUB ) + { + flDamage = pev->health; + } + + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +//========================================================= +//========================================================= +void CBarnacle :: BarnacleThink ( void ) +{ + CBaseEntity *pTouchEnt; + CBaseMonster *pVictim; + float flLength; + + SetNextThink( 0.1 ); + + if ( m_hEnemy != NULL ) + { +// barnacle has prey. + + if ( !m_hEnemy->IsAlive() ) + { + // someone (maybe even the barnacle) killed the prey. Reset barnacle. + m_fLiftingPrey = FALSE;// indicate that we're not lifting prey. + m_hEnemy = NULL; + return; + } + + if ( m_fLiftingPrey ) + { + if ( m_hEnemy != NULL && m_hEnemy->pev->deadflag != DEAD_NO ) + { + // crap, someone killed the prey on the way up. + m_hEnemy = NULL; + m_fLiftingPrey = FALSE; + return; + } + + // still pulling prey. + Vector vecNewEnemyOrigin = m_hEnemy->pev->origin; + vecNewEnemyOrigin.x = pev->origin.x; + vecNewEnemyOrigin.y = pev->origin.y; + + // guess as to where their neck is + vecNewEnemyOrigin.x -= 6 * cos(m_hEnemy->pev->angles.y * M_PI/180.0); + vecNewEnemyOrigin.y -= 6 * sin(m_hEnemy->pev->angles.y * M_PI/180.0); + + m_flAltitude -= BARNACLE_PULL_SPEED; + vecNewEnemyOrigin.z += BARNACLE_PULL_SPEED; + + if ( fabs( pev->origin.z - ( vecNewEnemyOrigin.z + m_hEnemy->pev->view_ofs.z - 8 ) ) < BARNACLE_BODY_HEIGHT ) + { + // prey has just been lifted into position ( if the victim origin + eye height + 8 is higher + // than the bottom of the barnacle, it is assumed that the head is within barnacle's body ) + m_fLiftingPrey = FALSE; + + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_bite3.wav", 1, ATTN_NORM ); + + pVictim = m_hEnemy->MyMonsterPointer(); + + m_flKillVictimTime = gpGlobals->time + 10;// now that the victim is in place, the killing bite will be administered in 10 seconds. + + if ( pVictim ) + { + pVictim->BarnacleVictimBitten( pev ); + SetActivity ( ACT_EAT ); + } + } + + UTIL_SetOrigin ( m_hEnemy, vecNewEnemyOrigin ); + } + else + { + // prey is lifted fully into feeding position and is dangling there. + + pVictim = m_hEnemy->MyMonsterPointer(); + + if ( m_flKillVictimTime != -1 && gpGlobals->time > m_flKillVictimTime ) + { + // kill! + if ( pVictim ) + { + pVictim->TakeDamage ( pev, pev, pVictim->pev->health, DMG_SLASH | DMG_ALWAYSGIB ); + m_cGibs = 3; + } + + return; + } + + // bite prey every once in a while + if ( pVictim && ( RANDOM_LONG(0,49) == 0 ) ) + { + switch ( RANDOM_LONG(0,2) ) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew2.wav", 1, ATTN_NORM ); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew3.wav", 1, ATTN_NORM ); break; + } + + pVictim->BarnacleVictimBitten( pev ); + } + + } + } + else + { +// barnacle has no prey right now, so just idle and check to see if anything is touching the tongue. + + // If idle and no nearby client, don't think so often + if ( FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) + SetNextThink( RANDOM_FLOAT(1,1.5) ); // Stagger a bit to keep barnacles from thinking on the same frame + + if ( m_fSequenceFinished ) + {// this is done so barnacle will fidget. + SetActivity ( ACT_IDLE ); + m_flTongueAdj = -100; + } + + if ( m_cGibs && RANDOM_LONG(0,99) == 1 ) + { + // cough up a gib. + CGib::SpawnRandomGibs( pev, 1, 1 ); + m_cGibs--; + + switch ( RANDOM_LONG(0,2) ) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew2.wav", 1, ATTN_NORM ); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew3.wav", 1, ATTN_NORM ); break; + } + } + + pTouchEnt = TongueTouchEnt( &flLength ); + + if ( pTouchEnt != NULL && m_fTongueExtended ) + { + // tongue is fully extended, and is touching someone. + if ( pTouchEnt->FBecomeProne() ) + { + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_alert2.wav", 1, ATTN_NORM ); + + SetSequenceByName ( "attack1" ); + m_flTongueAdj = -20; + + m_hEnemy = pTouchEnt; + + pTouchEnt->pev->movetype = MOVETYPE_FLY; + pTouchEnt->pev->velocity = pev->velocity; //LRC- make him come _with_ me + pTouchEnt->pev->basevelocity = pev->velocity; //LRC + pTouchEnt->pev->origin.x = pev->origin.x; + pTouchEnt->pev->origin.y = pev->origin.y; + + m_fLiftingPrey = TRUE;// indicate that we should be lifting prey. + m_flKillVictimTime = -1;// set this to a bogus time while the victim is lifted. + + m_flAltitude = (pev->origin.z - pTouchEnt->EyePosition().z); + } + } + else + { + // calculate a new length for the tongue to be clear of anything else that moves under it. + if ( m_flAltitude < flLength ) + { + // if tongue is higher than is should be, lower it kind of slowly. + m_flAltitude += BARNACLE_PULL_SPEED; + m_fTongueExtended = FALSE; + } + else + { + m_flAltitude = flLength; + m_fTongueExtended = TRUE; + } + + } + + } + + // ALERT( at_console, "tounge %f\n", m_flAltitude + m_flTongueAdj ); + SetBoneController( 0, -(m_flAltitude + m_flTongueAdj) ); + StudioFrameAdvance( 0.1 ); +} + +//========================================================= +// Killed. +//========================================================= +void CBarnacle :: Killed( entvars_t *pevAttacker, int iGib ) +{ + CBaseMonster *pVictim; + + pev->solid = SOLID_NOT; + pev->takedamage = DAMAGE_NO; + + if ( m_hEnemy != NULL ) + { + pVictim = m_hEnemy->MyMonsterPointer(); + + if ( pVictim ) + { + pVictim->BarnacleVictimReleased(); + } + } + +// CGib::SpawnRandomGibs( pev, 4, 1 ); + + switch ( RANDOM_LONG ( 0, 1 ) ) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_die1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_die3.wav", 1, ATTN_NORM ); break; + } + + SetActivity ( ACT_DIESIMPLE ); + SetBoneController( 0, 0 ); + + StudioFrameAdvance( 0.1 ); + + SetNextThink( 0.1 ); + SetThink(&CBarnacle :: WaitTillDead ); +} + +//========================================================= +//========================================================= +void CBarnacle :: WaitTillDead ( void ) +{ + SetNextThink( 0.1 ); + + float flInterval = StudioFrameAdvance( 0.1 ); + DispatchAnimEvents ( flInterval ); + + if ( m_fSequenceFinished ) + { + // death anim finished. + StopAnimation(); + SetThink ( NULL ); + } +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CBarnacle :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/barnacle.mdl"); + + PRECACHE_SOUND("barnacle/bcl_alert2.wav");//happy, lifting food up + PRECACHE_SOUND("barnacle/bcl_bite3.wav");//just got food to mouth + PRECACHE_SOUND("barnacle/bcl_chew1.wav"); + PRECACHE_SOUND("barnacle/bcl_chew2.wav"); + PRECACHE_SOUND("barnacle/bcl_chew3.wav"); + PRECACHE_SOUND("barnacle/bcl_die1.wav" ); + PRECACHE_SOUND("barnacle/bcl_die3.wav" ); +} + +//========================================================= +// TongueTouchEnt - does a trace along the barnacle's tongue +// to see if any entity is touching it. Also stores the length +// of the trace in the int pointer provided. +//========================================================= +#define BARNACLE_CHECK_SPACING 8 +CBaseEntity *CBarnacle :: TongueTouchEnt ( float *pflLength ) +{ + TraceResult tr; + float length; + + // trace once to hit architecture and see if the tongue needs to change position. + UTIL_TraceLine ( pev->origin, pev->origin - Vector ( 0 , 0 , 2048 ), ignore_monsters, ENT(pev), &tr ); + length = fabs( pev->origin.z - tr.vecEndPos.z ); + if ( pflLength ) + { + *pflLength = length; + } + + Vector delta = Vector( BARNACLE_CHECK_SPACING, BARNACLE_CHECK_SPACING, 0 ); + Vector mins = pev->origin - delta; + Vector maxs = pev->origin + delta; + maxs.z = pev->origin.z; + mins.z -= length; + + CBaseEntity *pList[10]; + int count = UTIL_EntitiesInBox( pList, 10, mins, maxs, (FL_CLIENT|FL_MONSTER) ); + if ( count ) + { + for ( int i = 0; i < count; i++ ) + { + // only clients and monsters + if ( pList[i] != this && IRelationship( pList[i] ) > R_NO && pList[ i ]->pev->deadflag == DEAD_NO ) // this ent is one of our enemies. Barnacle tries to eat it. + { + return pList[i]; + } + } + } + + return NULL; +} diff --git a/dlls/barney.cpp b/dlls/barney.cpp new file mode 100644 index 0000000..ffbbdfb --- /dev/null +++ b/dlls/barney.cpp @@ -0,0 +1,939 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// monster template +//========================================================= +// UNDONE: Holster weapon? + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "talkmonster.h" +#include "schedule.h" +#include "defaultai.h" +#include "scripted.h" +#include "weapons.h" +#include "soundent.h" +#include "rushscript.h" // buz + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +// first flag is barney dying for scripted sequences? +#define BARNEY_AE_DRAW ( 2 ) +#define BARNEY_AE_SHOOT ( 3 ) +#define BARNEY_AE_HOLSTER ( 4 ) + +#define BARNEY_BODY_GUNHOLSTERED 0 +#define BARNEY_BODY_GUNDRAWN 1 +#define BARNEY_BODY_GUNGONE 2 + +class CBarney : public CTalkMonster +{ +public: + void Spawn( void ); + void Precache( void ); + float MaxYawSpeed( void ); + int ISoundMask( void ); + void BarneyFirePistol( void ); + void AlertSound( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + + void RunTask( Task_t *pTask ); + void StartTask( Task_t *pTask ); + + // Wargon: Þçàòü ìîíñòðà ìîæíî òîëüêî åñëè îí æèâ. Ýòî íóæíî ÷òîáû èêîíêà þçà íå ïîêàçûâàëàñü íà ìåðòâûõ ìîíñòðàõ. + virtual int ObjectCaps( void ) { if (pev->deadflag == DEAD_NO) return CTalkMonster :: ObjectCaps() | FCAP_IMPULSE_USE | FCAP_DISTANCE_USE; else return CTalkMonster::ObjectCaps(); } + + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + + void DeclineFollowing( void ); + + // Override these to set behavior + Schedule_t *GetScheduleOfType ( int Type ); + Schedule_t *GetSchedule ( void ); + MONSTERSTATE GetIdealState ( void ); + + void DeathSound( void ); + void PainSound( void ); + + void TalkInit( void ); + + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + void Killed( entvars_t *pevAttacker, int iGib ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_iBaseBody; //LRC - for barneys with different bodies + BOOL m_fGunDrawn; + float m_painTime; + float m_checkAttackTime; + BOOL m_lastAttackCheck; + + // UNDONE: What is this for? It isn't used? + float m_flPlayerDamage;// how much pain has the player inflicted on me? + + CUSTOM_SCHEDULES; +}; + +LINK_ENTITY_TO_CLASS( monster_barney, CBarney ); + +TYPEDESCRIPTION CBarney::m_SaveData[] = +{ + DEFINE_FIELD( CBarney, m_iBaseBody, FIELD_INTEGER ), //LRC + DEFINE_FIELD( CBarney, m_fGunDrawn, FIELD_BOOLEAN ), + DEFINE_FIELD( CBarney, m_painTime, FIELD_TIME ), + DEFINE_FIELD( CBarney, m_checkAttackTime, FIELD_TIME ), + DEFINE_FIELD( CBarney, m_lastAttackCheck, FIELD_BOOLEAN ), + DEFINE_FIELD( CBarney, m_flPlayerDamage, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CBarney, CTalkMonster ); + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +Task_t tlBaFollow[] = +{ + { TASK_MOVE_TO_TARGET_RANGE,(float)128 }, // Move within 128 of target ent (client) + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE }, +}; + +Schedule_t slBaFollow[] = +{ + { + tlBaFollow, + ARRAYSIZE ( tlBaFollow ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_PROVOKED, + bits_SOUND_DANGER, + "Follow" + }, +}; + +//========================================================= +// BarneyDraw- much better looking draw schedule for when +// barney knows who he's gonna attack. +//========================================================= +Task_t tlBarneyEnemyDraw[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, 0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float) ACT_ARM }, +}; + +Schedule_t slBarneyEnemyDraw[] = +{ + { + tlBarneyEnemyDraw, + ARRAYSIZE ( tlBarneyEnemyDraw ), + 0, + 0, + "Barney Enemy Draw" + } +}; + +Task_t tlBaFaceTarget[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_TARGET, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_CHASE }, +}; + +Schedule_t slBaFaceTarget[] = +{ + { + tlBaFaceTarget, + ARRAYSIZE ( tlBaFaceTarget ), + bits_COND_CLIENT_PUSH | + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_PROVOKED, + bits_SOUND_DANGER, + "FaceTarget" + }, +}; + + +Task_t tlIdleBaStand[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, // repick IDLESTAND every two seconds. + { TASK_TLK_HEADRESET, (float)0 }, // reset head position +}; + +Schedule_t slIdleBaStand[] = +{ + { + tlIdleBaStand, + ARRAYSIZE ( tlIdleBaStand ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_SMELL | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT |// sound flags - change these, and you'll break the talking code. + //bits_SOUND_PLAYER | + //bits_SOUND_WORLD | + + bits_SOUND_DANGER | + bits_SOUND_MEAT |// scents + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "IdleStand" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CBarney ) +{ + slBaFollow, + slBarneyEnemyDraw, + slBaFaceTarget, + slIdleBaStand, +}; + + +IMPLEMENT_CUSTOM_SCHEDULES( CBarney, CTalkMonster ); + +void CBarney :: StartTask( Task_t *pTask ) +{ + CTalkMonster::StartTask( pTask ); +} + +void CBarney :: RunTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_RANGE_ATTACK1: + if (m_hEnemy != NULL && (m_hEnemy->IsPlayer())) + { + pev->framerate = 1.5; + } + CTalkMonster::RunTask( pTask ); + break; + default: + CTalkMonster::RunTask( pTask ); + break; + } +} + + + + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. +//========================================================= +int CBarney :: ISoundMask ( void) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_CARCASS | + bits_SOUND_MEAT | + bits_SOUND_GARBAGE | + bits_SOUND_DANGER | + bits_SOUND_PLAYER; +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CBarney :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_PLAYER_ALLY; +} + +//========================================================= +// ALertSound - barney says "Freeze!" +//========================================================= +void CBarney :: AlertSound( void ) +{ + if ( m_hEnemy != NULL ) + { + if ( FOkToSpeak() ) + { + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_ATTACK"); + PlaySentence( szBuf, RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + } + else + { + PlaySentence( "BA_ATTACK", RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + } + } + } + +} +//========================================================= +// MaxYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +float CBarney :: MaxYawSpeed( void ) +{ + float ys; + + ys = 0; + + switch ( m_Activity ) + { + case ACT_IDLE: + ys = 70; + break; + case ACT_WALK: + ys = 70; + break; + case ACT_RUN: + ys = 90; + break; + default: + ys = 70; + break; + } + + return ys; +} + + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CBarney :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( flDist <= 1024 && flDot >= 0.5 ) + { + if ( gpGlobals->time > m_checkAttackTime ) + { + TraceResult tr; + + Vector shootOrigin = pev->origin + Vector( 0, 0, 55 ); + CBaseEntity *pEnemy = m_hEnemy; + Vector shootTarget = ( (pEnemy->BodyTarget( shootOrigin ) - pEnemy->pev->origin) + m_vecEnemyLKP ); + UTIL_TraceLine( shootOrigin, shootTarget, dont_ignore_monsters, ENT(pev), &tr ); + m_checkAttackTime = gpGlobals->time + 1; + if ( tr.flFraction == 1.0 || (tr.pHit != NULL && CBaseEntity::Instance(tr.pHit) == pEnemy) ) + m_lastAttackCheck = TRUE; + else + m_lastAttackCheck = FALSE; + m_checkAttackTime = gpGlobals->time + 1.5; + } + return m_lastAttackCheck; + } + return FALSE; +} + + +//========================================================= +// BarneyFirePistol - shoots one round from the pistol at +// the enemy barney is facing. +//========================================================= +void CBarney :: BarneyFirePistol ( void ) +{ + Vector vecShootOrigin; + + UTIL_MakeVectors(pev->angles); + vecShootOrigin = pev->origin + Vector( 0, 0, 55 ); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); + pev->effects |= EF_MUZZLEFLASH; + + FireBullets( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_2DEGREES, 1024, BULLET_NORMAL, gSkillData.monDmg9MM ); + + int pitchShift = RANDOM_LONG( 0, 20 ); + + // Only shift about half the time + if ( pitchShift > 10 ) + pitchShift = 0; + else + pitchShift -= 5; + + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "barney/ba_attack2.wav", 1, ATTN_NORM, 0, 100 + pitchShift ); + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 384, 0.3 ); + + // UNDONE: Reload? + m_cAmmoLoaded--;// take away a bullet! +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CBarney :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case BARNEY_AE_SHOOT: + BarneyFirePistol(); + break; + + case BARNEY_AE_DRAW: + // barney's bodygroup switches here so he can pull gun from holster + pev->body = m_iBaseBody + BARNEY_BODY_GUNDRAWN; + m_fGunDrawn = TRUE; + break; + + case BARNEY_AE_HOLSTER: + // change bodygroup to replace gun in holster + pev->body = m_iBaseBody + BARNEY_BODY_GUNHOLSTERED; + m_fGunDrawn = FALSE; + break; + + default: + CTalkMonster::HandleAnimEvent( pEvent ); + } +} + +//========================================================= +// Spawn +//========================================================= +void CBarney :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/barney.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + if (pev->health == 0) //LRC + pev->health = gSkillData.barneyHealth; + pev->view_ofs = Vector ( 0, 0, 50 );// position of the eyes relative to monster's origin. + m_flFieldOfView = VIEW_FIELD_WIDE; // NOTE: we need a wide field of view so npc will notice player and say hello + m_MonsterState = MONSTERSTATE_NONE; + + m_iBaseBody = pev->body; //LRC + pev->body = m_iBaseBody + BARNEY_BODY_GUNHOLSTERED; // gun in holster + m_fGunDrawn = FALSE; + + m_afCapability = bits_CAP_HEAR | bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP; + + MonsterInit(); + SetUse(&CBarney :: FollowerUse ); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CBarney :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/barney.mdl"); + + PRECACHE_SOUND("barney/ba_attack1.wav" ); + PRECACHE_SOUND("barney/ba_attack2.wav" ); + + PRECACHE_SOUND("barney/ba_pain1.wav"); + PRECACHE_SOUND("barney/ba_pain2.wav"); + PRECACHE_SOUND("barney/ba_pain3.wav"); + + PRECACHE_SOUND("barney/ba_die1.wav"); + PRECACHE_SOUND("barney/ba_die2.wav"); + PRECACHE_SOUND("barney/ba_die3.wav"); + + // every new barney must call this, otherwise + // when a level is loaded, nobody will talk (time is reset to 0) + TalkInit(); + CTalkMonster::Precache(); +} + +// Init talk data +void CBarney :: TalkInit() +{ + + CTalkMonster::TalkInit(); + + // barney speech group names (group names are in sentences.txt) + + if (!m_iszSpeakAs) + { + m_szGrp[TLK_ANSWER] = "BA_ANSWER"; + m_szGrp[TLK_QUESTION] = "BA_QUESTION"; + m_szGrp[TLK_IDLE] = "BA_IDLE"; + m_szGrp[TLK_STARE] = "BA_STARE"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) //LRC + m_szGrp[TLK_USE] = "BA_PFOLLOW"; + else + m_szGrp[TLK_USE] = "BA_OK"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) + m_szGrp[TLK_UNUSE] = "BA_PWAIT"; + else + m_szGrp[TLK_UNUSE] = "BA_WAIT"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) + m_szGrp[TLK_DECLINE] = "BA_POK"; + else + m_szGrp[TLK_DECLINE] = "BA_NOTOK"; + m_szGrp[TLK_STOP] = "BA_STOP"; + + m_szGrp[TLK_NOSHOOT] = "BA_SCARED"; + m_szGrp[TLK_HELLO] = "BA_HELLO"; + + m_szGrp[TLK_PLHURT1] = "!BA_CUREA"; + m_szGrp[TLK_PLHURT2] = "!BA_CUREB"; + m_szGrp[TLK_PLHURT3] = "!BA_CUREC"; + + m_szGrp[TLK_PHELLO] = NULL; //"BA_PHELLO"; // UNDONE + m_szGrp[TLK_PIDLE] = NULL; //"BA_PIDLE"; // UNDONE + m_szGrp[TLK_PQUESTION] = "BA_PQUEST"; // UNDONE + + m_szGrp[TLK_SMELL] = "BA_SMELL"; + + m_szGrp[TLK_WOUND] = "BA_WOUND"; + m_szGrp[TLK_MORTAL] = "BA_MORTAL"; + } + + // get voice for head - just one barney voice for now + m_voicePitch = 100; +} + + +static BOOL IsFacing( entvars_t *pevTest, const Vector &reference ) +{ + Vector vecDir = (reference - pevTest->origin); + vecDir.z = 0; + vecDir = vecDir.Normalize(); + Vector forward, angle; + angle = pevTest->v_angle; + angle.x = 0; + UTIL_MakeVectorsPrivate( angle, forward, NULL, NULL ); + // He's facing me, he meant it + if ( DotProduct( forward, vecDir ) > 0.96 ) // +/- 15 degrees or so + { + return TRUE; + } + return FALSE; +} + + +int CBarney :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType) +{ + // make sure friends talk about it if player hurts talkmonsters... + int ret = CTalkMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); + if ( !IsAlive() || pev->deadflag == DEAD_DYING ) + return ret; + + // LRC - if my reaction to the player has been overridden, don't do this stuff + if (m_iPlayerReact) return ret; + + if ( m_MonsterState != MONSTERSTATE_PRONE && (pevAttacker->flags & FL_CLIENT) ) + { + m_flPlayerDamage += flDamage; + + // This is a heurstic to determine if the player intended to harm me + // If I have an enemy, we can't establish intent (may just be crossfire) + if ( m_hEnemy == NULL ) + { + // If the player was facing directly at me, or I'm already suspicious, get mad + if ( (m_afMemory & bits_MEMORY_SUSPICIOUS) || IsFacing( pevAttacker, pev->origin ) ) + { + // Alright, now I'm pissed! + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_MAD"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "BA_MAD", 4, VOL_NORM, ATTN_NORM ); + } + + Remember( bits_MEMORY_PROVOKED ); + StopFollowing( TRUE ); + } + else + { + // Hey, be careful with that + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_SHOT"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "BA_SHOT", 4, VOL_NORM, ATTN_NORM ); + } + Remember( bits_MEMORY_SUSPICIOUS ); + } + } + else if ( !(m_hEnemy->IsPlayer()) && pev->deadflag == DEAD_NO ) + { + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_SHOT"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "BA_SHOT", 4, VOL_NORM, ATTN_NORM ); + } + } + } + + return ret; +} + + +//========================================================= +// PainSound +//========================================================= +void CBarney :: PainSound ( void ) +{ + if (gpGlobals->time < m_painTime) + return; + + m_painTime = gpGlobals->time + RANDOM_FLOAT(0.5, 0.75); + + switch (RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "barney/ba_pain1.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 1: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "barney/ba_pain2.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 2: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "barney/ba_pain3.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CBarney :: DeathSound ( void ) +{ + switch (RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "barney/ba_die1.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 1: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "barney/ba_die2.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 2: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "barney/ba_die3.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + } +} + + +void CBarney::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + switch( ptr->iHitgroup) + { + case HITGROUP_CHEST: + case HITGROUP_STOMACH: + if (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_BLAST)) + { + flDamage = flDamage / 2; + } + break; + case 10: + if (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_CLUB)) + { + flDamage -= 20; + if (flDamage <= 0) + { + UTIL_Ricochet( ptr->vecEndPos, 1.0 ); + flDamage = 0.01; + } + } + // always a head shot + ptr->iHitgroup = HITGROUP_HEAD; + break; + } + + CTalkMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + + +void CBarney::Killed( entvars_t *pevAttacker, int iGib ) +{ + if ( pev->body < m_iBaseBody + BARNEY_BODY_GUNGONE && !(pev->spawnflags & SF_MONSTER_NO_WPN_DROP)) + {// drop the gun! + Vector vecGunPos; + Vector vecGunAngles; + + pev->body = m_iBaseBody + BARNEY_BODY_GUNGONE; + + GetAttachment( 0, vecGunPos, vecGunAngles ); + + CBaseEntity *pGun = DropItem( "weapon_aps", vecGunPos, vecGunAngles ); + + } + + SetUse( NULL ); + CTalkMonster::Killed( pevAttacker, iGib ); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +Schedule_t* CBarney :: GetScheduleOfType ( int Type ) +{ + Schedule_t *psched; + + switch( Type ) + { + case SCHED_ARM_WEAPON: + if ( m_hEnemy != NULL ) + { + // face enemy, then draw. + return slBarneyEnemyDraw; + } + break; + + // Hook these to make a looping schedule + case SCHED_TARGET_FACE: + // call base class default so that barney will talk + // when 'used' + psched = CTalkMonster::GetScheduleOfType(Type); + + if (psched == slIdleStand) + return slBaFaceTarget; // override this for different target face behavior + else + return psched; + + case SCHED_TARGET_CHASE: + return slBaFollow; + + case SCHED_IDLE_STAND: + // call base class default so that scientist will talk + // when standing during idle + psched = CTalkMonster::GetScheduleOfType(Type); + + if (psched == slIdleStand) + { + // just look straight ahead. + return slIdleBaStand; + } + else + return psched; + } + + return CTalkMonster::GetScheduleOfType( Type ); +} + +//========================================================= +// GetSchedule - Decides which type of schedule best suits +// the monster's current state and conditions. Then calls +// monster's member function to get a pointer to a schedule +// of the proper type. +//========================================================= +Schedule_t *CBarney :: GetSchedule ( void ) +{ + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + if ( HasConditions( bits_COND_ENEMY_DEAD ) && FOkToSpeak() ) + { + // Hey, be careful with that + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_KILL"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "BA_KILL", 4, VOL_NORM, ATTN_NORM ); + } + } + + switch( m_MonsterState ) + { + case MONSTERSTATE_COMBAT: + { +// dead enemy + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + + // always act surprized with a new enemy + if ( HasConditions( bits_COND_NEW_ENEMY ) && HasConditions( bits_COND_LIGHT_DAMAGE) ) + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + + // wait for one schedule to draw gun + if (!m_fGunDrawn ) + return GetScheduleOfType( SCHED_ARM_WEAPON ); + + if ( HasConditions( bits_COND_HEAVY_DAMAGE ) ) + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + break; + + case MONSTERSTATE_ALERT: + case MONSTERSTATE_IDLE: + if ( HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE)) + { + // flinch if hurt + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + + // buz: rush target + if (!FStringNull(m_hRushEntity) && (gpGlobals->time > m_flRushNextTime) && (m_flRushNextTime != -1)) + { + CBaseEntity *pRushEntity = UTIL_FindEntityByTargetname( NULL, STRING( m_hRushEntity ) ); + if (pRushEntity) + { + CStartRush* pRush = (CStartRush*)pRushEntity; + m_hTargetEnt = pRush->GetDestinationEntity(); + + return GetScheduleOfType( SCHED_RUSH_TARGET ); + } + else + // rush entity not found on this map. + // try again after next changelevel + m_flRushNextTime = -1; + } + + if ( m_hEnemy == NULL && IsFollowing() ) + { + if ( !m_hTargetEnt->IsAlive() ) + { + // UNDONE: Comment about the recently dead player here? + StopFollowing( FALSE ); + break; + } + else + { + if ( HasConditions( bits_COND_CLIENT_PUSH ) ) + { + return GetScheduleOfType( SCHED_MOVE_AWAY_FOLLOW ); + } + return GetScheduleOfType( SCHED_TARGET_FACE ); + } + } + + if ( HasConditions( bits_COND_CLIENT_PUSH ) ) + { + return GetScheduleOfType( SCHED_MOVE_AWAY ); + } + + // try to say something about smells + TrySmellTalk(); + break; + } + + return CTalkMonster::GetSchedule(); +} + +MONSTERSTATE CBarney :: GetIdealState ( void ) +{ + return CTalkMonster::GetIdealState(); +} + + + +void CBarney::DeclineFollowing( void ) +{ + PlaySentence( m_szGrp[TLK_DECLINE], 2, VOL_NORM, ATTN_NORM ); //LRC +} + + + + + +//========================================================= +// DEAD BARNEY PROP +// +// Designer selects a pose in worldcraft, 0 through num_poses-1 +// this value is added to what is selected as the 'first dead pose' +// among the monster's normal animations. All dead poses must +// appear sequentially in the model file. Be sure and set +// the m_iFirstPose properly! +// +//========================================================= +class CDeadBarney : public CBaseMonster +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_PLAYER_ALLY; } + + void KeyValue( KeyValueData *pkvd ); + float MaxYawSpeed( void ) { return 8.0f; } + int m_iPose;// which sequence to display -- temporary, don't need to save + static char *m_szPoses[3]; +}; + +char *CDeadBarney::m_szPoses[] = { "lying_on_back", "lying_on_side", "lying_on_stomach" }; + +void CDeadBarney::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "pose")) + { + m_iPose = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + +LINK_ENTITY_TO_CLASS( monster_barney_dead, CDeadBarney ); + +//========================================================= +// ********** DeadBarney SPAWN ********** +//========================================================= +void CDeadBarney :: Spawn( ) +{ + PRECACHE_MODEL("models/barney.mdl"); + SET_MODEL(ENT(pev), "models/barney.mdl"); + + pev->sequence = 0; + m_bloodColor = BLOOD_COLOR_RED; + + pev->sequence = LookupSequence( m_szPoses[m_iPose] ); + if (pev->sequence == -1) + { + ALERT ( at_debug, "Dead barney with bad pose\n" ); + } + // Corpses have less health + pev->health = 8;//gSkillData.barneyHealth; + + MonsterInitDead(); +} + + diff --git a/dlls/basemonster.h b/dlls/basemonster.h new file mode 100644 index 0000000..0c750d6 --- /dev/null +++ b/dlls/basemonster.h @@ -0,0 +1,420 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ + +#ifndef BASEMONSTER_H +#define BASEMONSTER_H + +#include "AI_Target.h" + +#define AI_CALC_YAW_SPEED -1 +#define AI_KEEP_YAW_SPEED -2 + +// +// generic Monster +// +class CBaseMonster : public CBaseToggle +{ +private: + int m_afConditions; + +public: + typedef enum + { + SCRIPT_PLAYING = 0, // Playing the sequence + SCRIPT_WAIT, // Waiting on everyone in the script to be ready + SCRIPT_CLEANUP, // Cancelling the script / cleaning up + SCRIPT_WALK_TO_MARK, + SCRIPT_RUN_TO_MARK, + } SCRIPTSTATE; + + + + // these fields have been added in the process of reworking the state machine. (sjb) + EHANDLE m_hEnemy; // the entity that the monster is fighting. + EHANDLE m_hTargetEnt; // the entity that the monster is trying to reach + EHANDLE m_hOldEnemy[ MAX_OLD_ENEMIES ]; + Vector m_vecOldEnemy[ MAX_OLD_ENEMIES ]; + + float m_flFieldOfView;// width of monster's field of view ( dot product ) + float m_flWaitFinished;// if we're told to wait, this is the time that the wait will be over. + float m_flMoveWaitFinished; + + Activity m_Activity;// what the monster is doing (animation) + Activity m_IdealActivity;// monster should switch to this activity + + int m_LastHitGroup; // the last body region that took damage + + MONSTERSTATE m_MonsterState;// monster's current state + MONSTERSTATE m_IdealMonsterState;// monster should change to this state + + int m_iTaskStatus; + Schedule_t *m_pSchedule; + int m_iScheduleIndex; + + WayPoint_t m_Route[ ROUTE_SIZE ]; // Positions of movement + int m_movementGoal; // Goal that defines route + int m_iRouteIndex; // index into m_Route[] + float m_moveWaitTime; // How long I should wait for something to move + + Vector m_vecMoveGoal; // kept around for node graph moves, so we know our ultimate goal + Activity m_movementActivity; // When moving, set this activity + + int m_iAudibleList; // first index of a linked list of sounds that the monster can hear. + int m_afSoundTypes; + + Vector m_vecLastPosition;// monster sometimes wants to return to where it started after an operation. + + int m_iHintNode; // this is the hint node that the monster is moving towards or performing active idle on. + + int m_afMemory; + + int m_iMaxHealth;// keeps track of monster's maximum health value (for re-healing, etc) + + Vector m_vecEnemyLKP;// last known position of enemy. (enemy's origin) + + int m_cAmmoLoaded; // how much ammo is in the weapon (used to trigger reload anim sequences) + + int m_afCapability;// tells us what a monster can/can't do. + + float m_flNextAttack; // cannot attack again until this time + + int m_bitsDamageType; // what types of damage has monster (player) taken + BYTE m_rgbTimeBasedDamage[CDMG_TIMEBASED]; + + int m_lastDamageAmount;// how much damage did monster (player) last take + // time based damage counters, decr. 1 per 2 seconds + int m_bloodColor; // color of blood particless + + int m_failSchedule; // Schedule type to choose if current schedule fails + + float m_flHungryTime;// set this is a future time to stop the monster from eating for a while. + + float m_flDistTooFar; // if enemy farther away than this, bits_COND_ENEMY_TOOFAR set in CheckEnemy + float m_flDistLook; // distance monster sees (Default 2048) + + int m_iTriggerCondition;// for scripted AI, this is the condition that will cause the activation of the monster's TriggerTarget + string_t m_iszTriggerTarget;// name of target that should be fired. + + Vector m_HackedGunPos; // HACK until we can query end of gun + + int m_iUseAlertAnims; // buz: start in alert state + +// Scripted sequence Info + SCRIPTSTATE m_scriptState; // internal cinematic state + CCineMonster *m_pCine; + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + void KeyValue( KeyValueData *pkvd ); + +// monster use function + void EXPORT MonsterUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT CorpseUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +// overrideable Monster member functions + + // LRC- to allow level-designers to change monster allegiances + int m_iClass; + int m_iPlayerReact; + virtual int Classify( void ) { return m_iClass?m_iClass:CLASS_NONE; } + + virtual int BloodColor( void ) { return m_bloodColor; } + + virtual CBaseMonster *MyMonsterPointer( void ) { return this; } + virtual void Look ( int iDistance );// basic sight function for monsters + virtual void RunAI ( void );// core ai function! + void RunAnimation( void ); + void Listen ( void ); + + virtual BOOL AllowDecal( entvars_t *pevAttacker ); + virtual BOOL IsAlive( void ) { return (pev->deadflag != DEAD_DEAD); } + virtual BOOL ShouldFadeOnDeath( void ); + +// Basic Monster AI functions + virtual float ChangeYaw ( int speed ); + float VecToYaw( Vector vecDir ); + float DeltaIdealYaw( void ); + + void CalcFacing( float flInterval ); + float ClampYaw( float yawSpeedPerSec, float current, float target, float time ); + float CalcIdealYaw( const Vector &vecTarget ); + // Run the current or specified timestep worth of rotation + void UpdateYaw( int speed = -1 ); + + void MoveFacing( void ); + + // ---------------------------------------------------- + // Rotational movement (yaw); goal and speed + // ---------------------------------------------------- + + void SetYawSpeed( float yawSpeed ) { pev->yaw_speed = yawSpeed; } + float GetYawSpeed( void ) const { return pev->yaw_speed; } + float GetIdealYaw( void ) const { return pev->ideal_yaw; } + void SetIdealYaw( float idealYaw ) { pev->ideal_yaw = idealYaw; } + + // Set ideal yaw specified as a vector + void SetIdealYaw( const Vector &vecFacing ) { SetIdealYaw( UTIL_VecToYaw( vecFacing )); } + + // Force the heading to the ideal yaw + void SnapYaw( void ) { UpdateYaw( 360 ); } + + // Set ideal yaw based on a specified target + void SetIdealYawToTarget( const Vector &target ); + float CalcYawSpeed( void ); + void RecalculateYawSpeed( void ); + + // Set the ideal yaw and run the current or specified timestep worth of rotation. Note + // it is not correct to call any "update" variant of these methods more + // than once per think cycle + void SetIdealYawAndUpdate( float idealYaw, float yawSpeed = AI_CALC_YAW_SPEED ); + void SetIdealYawAndUpdate( const Vector &vecFacing, float yawSpeed = AI_CALC_YAW_SPEED ) { SetIdealYawAndUpdate( UTIL_VecToYaw( vecFacing ), yawSpeed ); } + void SetIdealYawToTargetAndUpdate( const Vector &target, float yawSpeed = AI_CALC_YAW_SPEED ); + + float DamageForce( float damage ); + void AutoSetSize( void ); + +// stuff written for new state machine + virtual void MonsterThink( void ); + void EXPORT CallMonsterThink( void ) { this->MonsterThink(); } + virtual int IRelationship ( CBaseEntity *pTarget ); + virtual void MonsterInit ( void ); + virtual void MonsterInitDead( void ); // Call after animation/pose is set up + virtual void BecomeDead( void ); + void EXPORT CorpseFallThink( void ); + + void EXPORT MonsterInitThink ( void ); + virtual void StartMonster ( void ); + virtual CBaseEntity* BestVisibleEnemy ( void );// finds best visible enemy for attack + virtual BOOL FInViewCone ( CBaseEntity *pEntity );// see if pEntity is in monster's view cone + virtual BOOL FInViewCone ( Vector *pOrigin );// see if given location is in monster's view cone + virtual void HandleAnimEvent( MonsterEvent_t *pEvent ); + + virtual int CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist );// check validity of a straight move through space + virtual void Move( float flInterval = 0.1 ); + virtual void MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ); + virtual BOOL ShouldAdvanceRoute( float flWaypointDist ); + + virtual Activity GetStoppedActivity( void ) { return ACT_IDLE; } + virtual void Stop( void ) { m_IdealActivity = GetStoppedActivity(); } + + // This will stop animation until you call ResetSequenceInfo() at some point in the future + inline void StopAnimation( void ) { pev->framerate = 0; } + + // these functions will survey conditions and set appropriate conditions bits for attack types. + virtual BOOL CheckRangeAttack1( float flDot, float flDist ); + virtual BOOL CheckRangeAttack2( float flDot, float flDist ); + virtual BOOL CheckMeleeAttack1( float flDot, float flDist ); + virtual BOOL CheckMeleeAttack2( float flDot, float flDist ); + + BOOL FHaveSchedule( void ); + BOOL FScheduleValid ( void ); + void ClearSchedule( void ); + BOOL FScheduleDone ( void ); + void ChangeSchedule ( Schedule_t *pNewSchedule ); + void NextScheduledTask ( void ); + Schedule_t *ScheduleInList( const char *pName, Schedule_t **pList, int listCount ); + + virtual Schedule_t *ScheduleFromName( const char *pName ); + static Schedule_t *m_scheduleList[]; + + void MaintainSchedule ( void ); + virtual void StartTask ( Task_t *pTask ); + virtual void RunTask ( Task_t *pTask ); + virtual Schedule_t *GetScheduleOfType( int Type ); + virtual Schedule_t *GetSchedule( void ); + virtual void ScheduleChange( void ) {} +// virtual int CanPlaySequence( void ) { return ((m_pCine == NULL) && (m_MonsterState == MONSTERSTATE_NONE || m_MonsterState == MONSTERSTATE_IDLE || m_IdealMonsterState == MONSTERSTATE_IDLE)); } + virtual int CanPlaySequence( int interruptFlags ); +// virtual int CanPlaySequence( BOOL fDisregardState, int interruptLevel ); + virtual int CanPlaySentence( BOOL fDisregardState ) { return IsAlive(); } + virtual void PlaySentence( const char *pszSentence, float duration, float volume, float attenuation ); + virtual void PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ); + + virtual void SentenceStop( void ); + + Task_t *GetTask ( void ); + virtual MONSTERSTATE GetIdealState ( void ); + virtual void SetActivity ( Activity NewActivity ); + void SetIdealActivity( Activity NewActivity ); + void SetSequenceByName ( char *szSequence ); + void SetState ( MONSTERSTATE State ); + virtual void ReportAIState( void ); + + void CheckAttacks ( CBaseEntity *pTarget, float flDist ); + virtual int CheckEnemy ( CBaseEntity *pEnemy ); + void PushEnemy( CBaseEntity *pEnemy, Vector &vecLastKnownPos ); + BOOL PopEnemy( void ); + + BOOL FGetNodeRoute ( Vector vecDest ); + + inline void TaskComplete( void ) { if ( !HasConditions(bits_COND_TASK_FAILED) ) m_iTaskStatus = TASKSTATUS_COMPLETE; } + void MovementComplete( void ); + inline void TaskFail( void ) { SetConditions(bits_COND_TASK_FAILED); } + inline void TaskBegin( void ) { m_iTaskStatus = TASKSTATUS_RUNNING; } + int TaskIsRunning( void ); + inline int TaskIsComplete( void ) { return (m_iTaskStatus == TASKSTATUS_COMPLETE); } + inline int MovementIsComplete( void ) { return (m_movementGoal == MOVEGOAL_NONE); } + + int IScheduleFlags ( void ); + BOOL FRefreshRoute( void ); + BOOL FRouteClear ( void ); + void RouteSimplify( CBaseEntity *pTargetEnt ); + void AdvanceRoute ( float distance ); + virtual BOOL FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, CBaseEntity *pTargetEnt, Vector *pApex ); + virtual float MaxYawSpeed ( void ); // allows different yaw_speeds for each activity + BOOL BuildRoute ( const Vector &vecGoal, int iMoveFlag, CBaseEntity *pTarget ); + virtual BOOL BuildNearestRoute ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ); + int RouteClassify( int iMoveFlag ); + void InsertWaypoint ( Vector vecLocation, int afMoveFlags ); + + BOOL FindLateralCover ( const Vector &vecThreat, const Vector &vecViewOffset ); + virtual BOOL FindCover ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ); + virtual BOOL FValidateCover ( const Vector &vecCoverLocation ) { return TRUE; }; + virtual float CoverRadius( void ) { return 784; } // Default cover radius + + virtual BOOL FCanCheckAttacks ( void ); + virtual void CheckAmmo( void ) { return; }; + virtual int IgnoreConditions ( void ); + + inline void SetConditions( int iConditions ) { m_afConditions |= iConditions; } + inline void ClearConditions( int iConditions ) { m_afConditions &= ~iConditions; } + inline BOOL HasConditions( int iConditions ) { if ( m_afConditions & iConditions ) return TRUE; return FALSE; } + inline BOOL HasAllConditions( int iConditions ) { if ( (m_afConditions & iConditions) == iConditions ) return TRUE; return FALSE; } + + virtual BOOL FValidateHintType( short sHint ); + int FindHintNode ( void ); + virtual BOOL FCanActiveIdle ( void ); + void SetTurnActivity ( void ); + float FLSoundVolume ( CSound *pSound ); + + BOOL MoveToNode( Activity movementAct, float waitTime, const Vector &goal ); + BOOL MoveToTarget( Activity movementAct, float waitTime ); + BOOL MoveToLocation( Activity movementAct, float waitTime, const Vector &goal ); + BOOL MoveToEnemy( Activity movementAct, float waitTime ); + + // Returns the time when the door will be open + float OpenDoorAndWait( entvars_t *pevDoor ); + + virtual int ISoundMask( void ); + virtual CSound* PBestSound ( void ); + virtual CSound* PBestScent ( void ); + virtual float HearingSensitivity( void ) { return 1.0; }; + + BOOL FBecomeProne ( void ); + virtual void BarnacleVictimBitten( entvars_t *pevBarnacle ); + virtual void BarnacleVictimReleased( void ); + + virtual void SetEyePosition ( void ); + + BOOL FShouldEat( void );// see if a monster is 'hungry' + void Eat ( float flFullDuration );// make the monster 'full' for a while. + + CBaseEntity *CheckTraceHullAttack( float flDist, int iDamage, int iDmgType ); + BOOL FacingIdeal( void ); + + BOOL FCheckAITrigger( void );// checks and, if necessary, fires the monster's trigger target. + BOOL NoFriendlyFire( void ); + + BOOL BBoxFlat( void ); + + // PrescheduleThink + virtual void PrescheduleThink( void ) { return; }; + + virtual BOOL GetEnemy ( void ); // buz: make virtual + void MakeDamageBloodDecal ( int cCount, float flNoise, TraceResult *ptr, const Vector &vecDir ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + + // combat functions + float UpdateTarget ( entvars_t *pevTarget ); + virtual Activity GetDeathActivity ( void ); + Activity GetSmallFlinchActivity( void ); + virtual void Killed( entvars_t *pevAttacker, int iGib ); + virtual void GibMonster( void ); + BOOL ShouldGibMonster( int iGib ); + void CallGibMonster( void ); + virtual const char* DamageDecal( int bitsDamageType ); + virtual int HasCustomGibs( void ) { return FALSE; } //LRC + virtual BOOL HasHumanGibs( void ); + virtual BOOL HasAlienGibs( void ); + virtual void FadeMonster( void ); // Called instead of GibMonster() when gibs are disabled + + virtual Vector ShootAtEnemy( const Vector &shootOrigin ); +// virtual Vector BodyTarget( const Vector &posSrc ) { return Center( ) * 0.75 + EyePosition() * 0.25; }; // position to shoot at + virtual Vector BodyTarget( const Vector &posSrc ) { return Center( ) * 0.5 + EyePosition() * 0.5; }; // buz: fix to allow grunts shoot over obstacles + + virtual Vector GetGunPosition( void ); + + virtual int TakeHealth( float flHealth, int bitsDamageType ); + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); + int DeadTakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + void RadiusDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ); + void RadiusDamage(Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ); + virtual int IsMoving( void ) { return m_movementGoal != MOVEGOAL_NONE; } + + void RouteClear( void ); + void RouteNew( void ); + + virtual void DeathSound ( void ) { return; }; + virtual void AlertSound ( void ) { return; }; + virtual void IdleSound ( void ) { return; }; + virtual void PainSound ( void ) { return; }; + virtual void BlockedByPlayer ( CBasePlayer *pBlocker ) { return; }; // buz + virtual void StepSound( void ); + + virtual void StopFollowing( BOOL clearSchedule, int speakSentence = TRUE ) {} // buz + + inline void Remember( int iMemory ) { m_afMemory |= iMemory; } + inline void Forget( int iMemory ) { m_afMemory &= ~iMemory; } + inline BOOL HasMemory( int iMemory ) { if ( m_afMemory & iMemory ) return TRUE; return FALSE; } + inline BOOL HasAllMemories( int iMemory ) { if ( (m_afMemory & iMemory) == iMemory ) return TRUE; return FALSE; } + + BOOL ExitScriptedSequence( ); + BOOL CineCleanup( ); + + void StartPatrol( CBaseEntity *path ); + + CBaseEntity* DropItem ( char *pszItemName, const Vector &vecPos, const Vector &vecAng );// drop an item. + + //LRC + float CalcRatio( CBaseEntity *pLocus ) + { + /*ALERT(at_console, "monster CR: %f/%f = %f\n", pev->health, pev->max_health, pev->health / pev->max_health);*/ + return pev->health / pev->max_health; + } + + void AddFacingTarget( CBaseEntity *pTarget, float flImportance, float flDuration, float flRamp ); + void AddFacingTarget( const Vector &vecPosition, float flImportance, float flDuration, float flRamp ); + void AddFacingTarget( CBaseEntity *pTarget, const Vector &vecPosition, float flImportance, float flDuration, float flRamp ); + float GetFacingDirection( Vector &vecDir ); + + CMonsterTarget m_facingQueue; + + Vector m_vecFacingDir; + Vector m_vecDirection; + + // buz: rush vars + string_t m_hRushEntity; + int m_iRushMovetype; + float m_flRushDistance; + float m_flRushNextTime; +}; + + + +#endif // BASEMONSTER_H diff --git a/dlls/bigmomma.cpp b/dlls/bigmomma.cpp new file mode 100644 index 0000000..8cc72e9 --- /dev/null +++ b/dlls/bigmomma.cpp @@ -0,0 +1,1320 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +//========================================================= +// monster template +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "decals.h" +#include "weapons.h" +#include "game.h" + +//LRC brought in from animation.h +#define ACTIVITY_NOT_AVAILABLE -1 + +#define SF_INFOBM_RUN 0x0001 +#define SF_INFOBM_WAIT 0x0002 + +// AI Nodes for Big Momma +class CInfoBM : public CPointEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData* pkvd ); + + // name in pev->targetname + // next in pev->target + // radius in pev->scale + // health in pev->health + // Reach target in pev->message + // Reach delay in pev->speed + // Reach sequence in pev->netname + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_preSequence; +}; + +LINK_ENTITY_TO_CLASS( info_bigmomma, CInfoBM ); + +TYPEDESCRIPTION CInfoBM::m_SaveData[] = +{ + DEFINE_FIELD( CInfoBM, m_preSequence, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CInfoBM, CPointEntity ); + +void CInfoBM::Spawn( void ) +{ +} + + +void CInfoBM::KeyValue( KeyValueData* pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "radius")) + { + pev->scale = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "reachdelay")) + { + pev->speed = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "reachtarget")) + { + pev->message = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "reachsequence")) + { + pev->netname = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "presequence")) + { + m_preSequence = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +//========================================================= +// Mortar shot entity +//========================================================= +class CBMortar : public CBaseEntity +{ +public: + void Spawn( void ); + + static CBMortar *Shoot( edict_t *pOwner, Vector vecStart, Vector vecVelocity ); + void Touch( CBaseEntity *pOther ); + void EXPORT Animate( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_maxFrame; +}; + +LINK_ENTITY_TO_CLASS( bmortar, CBMortar ); + +TYPEDESCRIPTION CBMortar::m_SaveData[] = +{ + DEFINE_FIELD( CBMortar, m_maxFrame, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CBMortar, CBaseEntity ); + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define BIG_AE_STEP1 1 // Footstep left +#define BIG_AE_STEP2 2 // Footstep right +#define BIG_AE_STEP3 3 // Footstep back left +#define BIG_AE_STEP4 4 // Footstep back right +#define BIG_AE_SACK 5 // Sack slosh +#define BIG_AE_DEATHSOUND 6 // Death sound + +#define BIG_AE_MELEE_ATTACKBR 8 // Leg attack +#define BIG_AE_MELEE_ATTACKBL 9 // Leg attack +#define BIG_AE_MELEE_ATTACK1 10 // Leg attack +#define BIG_AE_MORTAR_ATTACK1 11 // Launch a mortar +#define BIG_AE_LAY_CRAB 12 // Lay a headcrab +#define BIG_AE_JUMP_FORWARD 13 // Jump up and forward +#define BIG_AE_SCREAM 14 // alert sound +#define BIG_AE_PAIN_SOUND 15 // pain sound +#define BIG_AE_ATTACK_SOUND 16 // attack sound +#define BIG_AE_BIRTH_SOUND 17 // birth sound +#define BIG_AE_EARLY_TARGET 50 // Fire target early + + + +// User defined conditions +#define bits_COND_NODE_SEQUENCE ( bits_COND_SPECIAL1 ) // pev->netname contains the name of a sequence to play + +// Attack distance constants +#define BIG_ATTACKDIST 170 +#define BIG_MORTARDIST 800 +#define BIG_MAXCHILDREN 20 // Max # of live headcrab children + + +#define bits_MEMORY_CHILDPAIR (bits_MEMORY_CUSTOM1) +#define bits_MEMORY_ADVANCE_NODE (bits_MEMORY_CUSTOM2) +#define bits_MEMORY_COMPLETED_NODE (bits_MEMORY_CUSTOM3) +#define bits_MEMORY_FIRED_NODE (bits_MEMORY_CUSTOM4) + +int gSpitSprite, gSpitDebrisSprite; +Vector VecCheckSplatToss( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float maxHeight ); +void MortarSpray( const Vector &position, const Vector &direction, int spriteModel, int count ); + + +// UNDONE: +// +#define BIG_CHILDCLASS "monster_babycrab" + +class CBigMomma : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void Activate( void ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + void RunTask( Task_t *pTask ); + void StartTask( Task_t *pTask ); + Schedule_t *GetSchedule( void ); + Schedule_t *GetScheduleOfType( int Type ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ); + void SetActivity ( Activity NewActivity ); + + void NodeStart( int iszNextNode ); + void NodeReach( void ); + BOOL ShouldGoToNode( void ); + + float MaxYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void LayHeadcrab( void ); + + int GetNodeSequence( void ) + { + CBaseEntity *pTarget = m_hTargetEnt; + if ( pTarget ) + { + return pTarget->pev->netname; // netname holds node sequence + } + return 0; + } + + + int GetNodePresequence( void ) + { + CInfoBM *pTarget = (CInfoBM *)(CBaseEntity *)m_hTargetEnt; + if ( pTarget ) + { + return pTarget->m_preSequence; + } + return 0; + } + + float GetNodeDelay( void ) + { + CBaseEntity *pTarget = m_hTargetEnt; + if ( pTarget ) + { + return pTarget->pev->speed; // Speed holds node delay + } + return 0; + } + + float GetNodeRange( void ) + { + CBaseEntity *pTarget = m_hTargetEnt; + if ( pTarget ) + { + return pTarget->pev->scale; // Scale holds node delay + } + return 1e6; + } + + float GetNodeYaw( void ) + { + CBaseEntity *pTarget = m_hTargetEnt; + if ( pTarget ) + { + if ( pTarget->pev->angles.y != 0 ) + return pTarget->pev->angles.y; + } + return pev->angles.y; + } + + // Restart the crab count on each new level + void OverrideReset( void ) + { + m_crabCount = 0; + } + + void DeathNotice( entvars_t *pevChild ); + + BOOL CanLayCrab( void ) + { + if ( m_crabTime < gpGlobals->time && m_crabCount < BIG_MAXCHILDREN ) + { + // Don't spawn crabs inside each other + Vector mins = pev->origin - Vector( 32, 32, 0 ); + Vector maxs = pev->origin + Vector( 32, 32, 0 ); + + CBaseEntity *pList[2]; + int count = UTIL_EntitiesInBox( pList, 2, mins, maxs, FL_MONSTER ); + for ( int i = 0; i < count; i++ ) + { + if ( pList[i] != this ) // Don't hurt yourself! + return FALSE; + } + return TRUE; + } + + return FALSE; + } + + void LaunchMortar( void ); + + void SetObjectCollisionBox( void ) + { + pev->absmin = pev->origin + Vector( -95, -95, 0 ); + pev->absmax = pev->origin + Vector( 95, 95, 190 ); + } + + BOOL CheckMeleeAttack1( float flDot, float flDist ); // Slash + BOOL CheckMeleeAttack2( float flDot, float flDist ); // Lay a crab + BOOL CheckRangeAttack1( float flDot, float flDist ); // Mortar launch + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + static const char *pChildDieSounds[]; + static const char *pSackSounds[]; + static const char *pDeathSounds[]; + static const char *pAttackSounds[]; + static const char *pAttackHitSounds[]; + static const char *pBirthSounds[]; + static const char *pAlertSounds[]; + static const char *pPainSounds[]; + static const char *pFootSounds[]; + + CUSTOM_SCHEDULES; + +private: + float m_nodeTime; + float m_crabTime; + float m_mortarTime; + float m_painSoundTime; + int m_crabCount; +}; +LINK_ENTITY_TO_CLASS( monster_bigmomma, CBigMomma ); + +TYPEDESCRIPTION CBigMomma::m_SaveData[] = +{ + DEFINE_FIELD( CBigMomma, m_nodeTime, FIELD_TIME ), + DEFINE_FIELD( CBigMomma, m_crabTime, FIELD_TIME ), + DEFINE_FIELD( CBigMomma, m_mortarTime, FIELD_TIME ), + DEFINE_FIELD( CBigMomma, m_painSoundTime, FIELD_TIME ), + DEFINE_FIELD( CBigMomma, m_crabCount, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CBigMomma, CBaseMonster ); + +const char *CBigMomma::pChildDieSounds[] = +{ + "gonarch/gon_childdie1.wav", + "gonarch/gon_childdie2.wav", + "gonarch/gon_childdie3.wav", +}; + +const char *CBigMomma::pSackSounds[] = +{ + "gonarch/gon_sack1.wav", + "gonarch/gon_sack2.wav", + "gonarch/gon_sack3.wav", +}; + +const char *CBigMomma::pDeathSounds[] = +{ + "gonarch/gon_die1.wav", +}; + +const char *CBigMomma::pAttackSounds[] = +{ + "gonarch/gon_attack1.wav", + "gonarch/gon_attack2.wav", + "gonarch/gon_attack3.wav", +}; +const char *CBigMomma::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CBigMomma::pBirthSounds[] = +{ + "gonarch/gon_birth1.wav", + "gonarch/gon_birth2.wav", + "gonarch/gon_birth3.wav", +}; + +const char *CBigMomma::pAlertSounds[] = +{ + "gonarch/gon_alert1.wav", + "gonarch/gon_alert2.wav", + "gonarch/gon_alert3.wav", +}; + +const char *CBigMomma::pPainSounds[] = +{ + "gonarch/gon_pain2.wav", + "gonarch/gon_pain4.wav", + "gonarch/gon_pain5.wav", +}; + +const char *CBigMomma::pFootSounds[] = +{ + "gonarch/gon_step1.wav", + "gonarch/gon_step2.wav", + "gonarch/gon_step3.wav", +}; + + + +void CBigMomma :: KeyValue( KeyValueData *pkvd ) +{ +#if 0 + if (FStrEq(pkvd->szKeyName, "volume")) + { + m_volume = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else +#endif + CBaseMonster::KeyValue( pkvd ); +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CBigMomma :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MONSTER; +} + +//========================================================= +// MaxYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +float CBigMomma :: MaxYawSpeed( void ) +{ + float ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + ys = 100; + break; + default: + ys = 90; + } + return ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CBigMomma :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case BIG_AE_MELEE_ATTACKBR: + case BIG_AE_MELEE_ATTACKBL: + case BIG_AE_MELEE_ATTACK1: + { + Vector forward, right; + + UTIL_MakeVectorsPrivate( pev->angles, forward, right, NULL ); + + Vector center = pev->origin + forward * 128; + Vector mins = center - Vector( 64, 64, 0 ); + Vector maxs = center + Vector( 64, 64, 64 ); + + CBaseEntity *pList[8]; + int count = UTIL_EntitiesInBox( pList, 8, mins, maxs, FL_MONSTER|FL_CLIENT ); + CBaseEntity *pHurt = NULL; + + for ( int i = 0; i < count && !pHurt; i++ ) + { + if ( pList[i] != this ) + { + if ( pList[i]->pev->owner != edict() ) + pHurt = pList[i]; + } + } + + if ( pHurt ) + { + pHurt->TakeDamage( pev, pev, gSkillData.bigmommaDmgSlash, DMG_CRUSH | DMG_SLASH ); + pHurt->pev->punchangle.x = 15; + switch( pEvent->event ) + { + case BIG_AE_MELEE_ATTACKBR: + pHurt->pev->velocity = pHurt->pev->velocity + (forward * 150) + Vector(0,0,250) - (right * 200); + break; + + case BIG_AE_MELEE_ATTACKBL: + pHurt->pev->velocity = pHurt->pev->velocity + (forward * 150) + Vector(0,0,250) + (right * 200); + break; + + case BIG_AE_MELEE_ATTACK1: + pHurt->pev->velocity = pHurt->pev->velocity + (forward * 220) + Vector(0,0,200); + break; + } + + pHurt->pev->flags &= ~FL_ONGROUND; + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pAttackHitSounds), 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + } + } + break; + + case BIG_AE_SCREAM: + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pAlertSounds ); + break; + + case BIG_AE_PAIN_SOUND: + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pPainSounds ); + break; + + case BIG_AE_ATTACK_SOUND: + EMIT_SOUND_ARRAY_DYN( CHAN_WEAPON, pAttackSounds ); + break; + + case BIG_AE_BIRTH_SOUND: + EMIT_SOUND_ARRAY_DYN( CHAN_BODY, pBirthSounds ); + break; + + case BIG_AE_SACK: + if ( RANDOM_LONG(0,100) < 30 ) + EMIT_SOUND_ARRAY_DYN( CHAN_BODY, pSackSounds ); + break; + + case BIG_AE_DEATHSOUND: + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pDeathSounds ); + break; + + case BIG_AE_STEP1: // Footstep left + case BIG_AE_STEP3: // Footstep back left + EMIT_SOUND_ARRAY_DYN( CHAN_ITEM, pFootSounds ); + break; + + case BIG_AE_STEP4: // Footstep back right + case BIG_AE_STEP2: // Footstep right + EMIT_SOUND_ARRAY_DYN( CHAN_BODY, pFootSounds ); + break; + + case BIG_AE_MORTAR_ATTACK1: + LaunchMortar(); + break; + + case BIG_AE_LAY_CRAB: + LayHeadcrab(); + break; + + case BIG_AE_JUMP_FORWARD: + ClearBits( pev->flags, FL_ONGROUND ); + + UTIL_SetOrigin (this, pev->origin + Vector ( 0 , 0 , 1) );// take her off ground so engine doesn't instantly reset onground + UTIL_MakeVectors ( pev->angles ); + + pev->velocity = (gpGlobals->v_forward * 200) + gpGlobals->v_up * 500; + break; + + case BIG_AE_EARLY_TARGET: + { + CBaseEntity *pTarget = m_hTargetEnt; + if ( pTarget && pTarget->pev->message ) + FireTargets( STRING(pTarget->pev->message), this, this, USE_TOGGLE, 0 ); + Remember( bits_MEMORY_FIRED_NODE ); + } + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +void CBigMomma :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + if ( ptr->iHitgroup != 1 ) + { + // didn't hit the sack? + + if ( pev->dmgtime != gpGlobals->time || (RANDOM_LONG(0,10) < 1) ) + { + UTIL_Ricochet( ptr->vecEndPos, RANDOM_FLOAT( 1, 2) ); + pev->dmgtime = gpGlobals->time; + } + + flDamage = 0.1;// don't hurt the monster much, but allow bits_COND_LIGHT_DAMAGE to be generated + } + else if ( gpGlobals->time > m_painSoundTime ) + { + m_painSoundTime = gpGlobals->time + RANDOM_LONG(1, 3); + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pPainSounds ); + } + + + CBaseMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + + +int CBigMomma :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // Don't take any acid damage -- BigMomma's mortar is acid + if ( bitsDamageType & DMG_ACID ) + flDamage = 0; + + if ( !HasMemory(bits_MEMORY_PATH_FINISHED) ) + { + if ( pev->health <= flDamage ) + { + pev->health = flDamage + 1; + Remember( bits_MEMORY_ADVANCE_NODE | bits_MEMORY_COMPLETED_NODE ); + ALERT( at_aiconsole, "BM: Finished node health!!!\n" ); + } + } + + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +void CBigMomma :: LayHeadcrab( void ) +{ + CBaseEntity *pChild = CBaseEntity::Create( BIG_CHILDCLASS, pev->origin, pev->angles, edict() ); + + pChild->pev->spawnflags |= SF_MONSTER_FALL_TO_GROUND; + + // Is this the second crab in a pair? + if ( HasMemory( bits_MEMORY_CHILDPAIR ) ) + { + m_crabTime = gpGlobals->time + RANDOM_FLOAT( 5, 10 ); + Forget( bits_MEMORY_CHILDPAIR ); + } + else + { + m_crabTime = gpGlobals->time + RANDOM_FLOAT( 0.5, 2.5 ); + Remember( bits_MEMORY_CHILDPAIR ); + } + + TraceResult tr; + UTIL_TraceLine( pev->origin, pev->origin - Vector(0,0,100), ignore_monsters, edict(), &tr); + UTIL_TraceCustomDecal( &tr, "mommabirth",RANDOM_FLOAT( 0.0f, 360.0f )); + + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pBirthSounds), 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + m_crabCount++; +} + + + +void CBigMomma::DeathNotice( entvars_t *pevChild ) +{ + if ( m_crabCount > 0 ) // Some babies may cross a transition, but we reset the count then + m_crabCount--; + if ( IsAlive() ) + { + // Make the "my baby's dead" noise! + EMIT_SOUND_ARRAY_DYN( CHAN_WEAPON, pChildDieSounds ); + } +} + + +void CBigMomma::LaunchMortar( void ) +{ + m_mortarTime = gpGlobals->time + RANDOM_FLOAT( 2, 15 ); + + Vector startPos = pev->origin; + startPos.z += 180; + Vector vecLaunch = g_vecZero; + + if (m_pCine) // is a scripted_action making me shoot? + { + if (m_hTargetEnt != NULL) // don't check m_fTurnType- bigmomma can fire in any direction. + { + vecLaunch = VecCheckSplatToss( pev, startPos, m_hTargetEnt->pev->origin, RANDOM_FLOAT( 150, 500 ) ); + } + if (vecLaunch == g_vecZero) + { + vecLaunch = pev->movedir; + } + } + else + { + vecLaunch = pev->movedir; + } + + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pSackSounds), 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + CBMortar *pBomb = CBMortar::Shoot( edict(), startPos, vecLaunch ); + pBomb->pev->gravity = 1.0; + MortarSpray( startPos, Vector(0,0,1), gSpitSprite, 24 ); +} + +//========================================================= +// Spawn +//========================================================= +void CBigMomma :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/big_mom.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, 0 ), Vector( 32, 32, 64 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + if (pev->health == 0) + pev->health = 150 * gSkillData.bigmommaHealthFactor; + pev->view_ofs = Vector ( 0, 0, 128 );// position of the eyes relative to monster's origin. + m_flFieldOfView = 0.3;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CBigMomma :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/big_mom.mdl"); + + PRECACHE_SOUND_ARRAY( pChildDieSounds ); + PRECACHE_SOUND_ARRAY( pSackSounds ); + PRECACHE_SOUND_ARRAY( pDeathSounds ); + PRECACHE_SOUND_ARRAY( pAttackSounds ); + PRECACHE_SOUND_ARRAY( pAttackHitSounds ); + PRECACHE_SOUND_ARRAY( pBirthSounds ); + PRECACHE_SOUND_ARRAY( pAlertSounds ); + PRECACHE_SOUND_ARRAY( pPainSounds ); + PRECACHE_SOUND_ARRAY( pFootSounds ); + + UTIL_PrecacheOther( BIG_CHILDCLASS ); + + // TEMP: Squid + PRECACHE_MODEL("sprites/mommaspit.spr");// spit projectile. + gSpitSprite = PRECACHE_MODEL("sprites/mommaspout.spr");// client side spittle. + gSpitDebrisSprite = PRECACHE_MODEL("sprites/mommablob.spr" ); + + PRECACHE_SOUND( "bullchicken/bc_acid1.wav" ); + PRECACHE_SOUND( "bullchicken/bc_spithit1.wav" ); + PRECACHE_SOUND( "bullchicken/bc_spithit2.wav" ); +} + + +void CBigMomma::Activate( void ) +{ + if ( m_hTargetEnt == NULL ) + Remember( bits_MEMORY_ADVANCE_NODE ); // Start 'er up + + CBaseMonster::Activate(); +} + + +void CBigMomma::NodeStart( int iszNextNode ) +{ + pev->netname = iszNextNode; + + CBaseEntity *pTarget = NULL; + + if ( pev->netname ) + { + edict_t *pentTarget = FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(pev->netname) ); + + if ( !FNullEnt(pentTarget) ) + pTarget = Instance( pentTarget ); + } + + + if ( !pTarget ) + { + ALERT( at_aiconsole, "BM: Finished the path!!\n" ); + Remember( bits_MEMORY_PATH_FINISHED ); + return; + } + Remember( bits_MEMORY_ON_PATH ); + m_hTargetEnt = pTarget; +} + + +void CBigMomma::NodeReach( void ) +{ + CBaseEntity *pTarget = m_hTargetEnt; + + Forget( bits_MEMORY_ADVANCE_NODE ); + + if ( !pTarget ) + return; + + if ( pTarget->pev->health ) + pev->max_health = pev->health = pTarget->pev->health * gSkillData.bigmommaHealthFactor; + + if ( !HasMemory( bits_MEMORY_FIRED_NODE ) ) + { + if ( pTarget->pev->message ) + FireTargets( STRING(pTarget->pev->message), this, this, USE_TOGGLE, 0 ); + } + Forget( bits_MEMORY_FIRED_NODE ); + + pev->netname = pTarget->pev->target; + if ( pTarget->pev->health == 0 ) + Remember( bits_MEMORY_ADVANCE_NODE ); // Move on if no health at this node +} + + + // Slash +BOOL CBigMomma::CheckMeleeAttack1( float flDot, float flDist ) +{ + if (flDot >= 0.7) + { + if ( flDist <= BIG_ATTACKDIST ) + return TRUE; + } + return FALSE; +} + + +// Lay a crab +BOOL CBigMomma::CheckMeleeAttack2( float flDot, float flDist ) +{ + return CanLayCrab(); +} + + +// Mortar launch +BOOL CBigMomma::CheckRangeAttack1( float flDot, float flDist ) +{ + if ( flDist <= BIG_MORTARDIST && m_mortarTime < gpGlobals->time ) + { + CBaseEntity *pEnemy = m_hEnemy; + + if ( pEnemy ) + { + Vector startPos = pev->origin; + startPos.z += 180; + pev->movedir = VecCheckSplatToss( pev, startPos, pEnemy->BodyTarget( pev->origin ), RANDOM_FLOAT( 150, 500 ) ); + if ( pev->movedir != g_vecZero ) + return TRUE; + } + } + return FALSE; +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +enum +{ + SCHED_BIG_NODE = LAST_COMMON_SCHEDULE + 1, + SCHED_NODE_FAIL, +}; + +enum +{ + TASK_MOVE_TO_NODE_RANGE = LAST_COMMON_TASK + 1, // Move within node range + TASK_FIND_NODE, // Find my next node + TASK_PLAY_NODE_PRESEQUENCE, // Play node pre-script + TASK_PLAY_NODE_SEQUENCE, // Play node script + TASK_PROCESS_NODE, // Fire targets, etc. + TASK_WAIT_NODE, // Wait at the node + TASK_NODE_DELAY, // Delay walking toward node for a bit. You've failed to get there + TASK_NODE_YAW, // Get the best facing direction for this node +}; + + +Task_t tlBigNode[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_NODE_FAIL }, + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_NODE, (float)0 }, // Find my next node + { TASK_PLAY_NODE_PRESEQUENCE,(float)0 }, // Play the pre-approach sequence if any + { TASK_MOVE_TO_NODE_RANGE, (float)0 }, // Move within node range + { TASK_STOP_MOVING, (float)0 }, + { TASK_NODE_YAW, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_WAIT_NODE, (float)0 }, // Wait for node delay + { TASK_PLAY_NODE_SEQUENCE, (float)0 }, // Play the sequence if one exists + { TASK_PROCESS_NODE, (float)0 }, // Fire targets, etc. + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slBigNode[] = +{ + { + tlBigNode, + ARRAYSIZE ( tlBigNode ), + 0, + 0, + "Big Node" + }, +}; + + +Task_t tlNodeFail[] = +{ + { TASK_NODE_DELAY, (float)10 }, // Try to do something else for 10 seconds + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slNodeFail[] = +{ + { + tlNodeFail, + ARRAYSIZE ( tlNodeFail ), + 0, + 0, + "NodeFail" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CBigMomma ) +{ + slBigNode, + slNodeFail, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CBigMomma, CBaseMonster ); + + + + +Schedule_t *CBigMomma::GetScheduleOfType( int Type ) +{ + switch( Type ) + { + case SCHED_BIG_NODE: + return slBigNode; + break; + + case SCHED_NODE_FAIL: + return slNodeFail; + break; + } + + return CBaseMonster::GetScheduleOfType( Type ); +} + + +BOOL CBigMomma::ShouldGoToNode( void ) +{ + if ( HasMemory( bits_MEMORY_ADVANCE_NODE ) ) + { + if ( m_nodeTime < gpGlobals->time ) + return TRUE; + } + return FALSE; +} + +// Overridden to make BigMomma jump on command; the model doesn't support it otherwise. +void CBigMomma :: SetActivity ( Activity NewActivity ) +{ + int iSequence; + + if (NewActivity == ACT_HOP) + { + iSequence = LookupSequence( "jump" ); + } + else + { + iSequence = LookupActivity ( NewActivity ); + } + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + { + if ( pev->sequence != iSequence || !m_fSequenceLoops ) + { + // don't reset frame between walk and run + if ( !(m_Activity == ACT_WALK || m_Activity == ACT_RUN) || !(NewActivity == ACT_WALK || NewActivity == ACT_RUN)) + pev->frame = 0; + } + + pev->sequence = iSequence; // Set to the reset anim (if it's there) + ResetSequenceInfo( ); + RecalculateYawSpeed(); + } + else + { + // Not available try to get default anim + ALERT ( at_aiconsole, "%s has no sequence for act:%s\n", STRING(pev->classname), GetNameForActivity( NewActivity )); + pev->sequence = 0; // Set to the reset anim (if it's there) + } + + m_Activity = NewActivity; // Go ahead and set this so it doesn't keep trying when the anim is not present + + // In case someone calls this with something other than the ideal activity + m_IdealActivity = m_Activity; +} + +Schedule_t *CBigMomma::GetSchedule( void ) +{ + if ( ShouldGoToNode() ) + { + return GetScheduleOfType( SCHED_BIG_NODE ); + } + + return CBaseMonster::GetSchedule(); +} + + +void CBigMomma::StartTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_FIND_NODE: + { + CBaseEntity *pTarget = m_hTargetEnt; + if ( !HasMemory( bits_MEMORY_ADVANCE_NODE ) ) + { + if ( pTarget ) + pev->netname = m_hTargetEnt->pev->target; + } + NodeStart( pev->netname ); + TaskComplete(); + ALERT( at_aiconsole, "BM: Found node %s\n", STRING(pev->netname) ); + } + break; + + case TASK_NODE_DELAY: + m_nodeTime = gpGlobals->time + pTask->flData; + TaskComplete(); + ALERT( at_aiconsole, "BM: FAIL! Delay %.2f\n", pTask->flData ); + break; + + case TASK_PROCESS_NODE: + ALERT( at_aiconsole, "BM: Reached node %s\n", STRING(pev->netname) ); + NodeReach(); + TaskComplete(); + break; + + case TASK_PLAY_NODE_PRESEQUENCE: + case TASK_PLAY_NODE_SEQUENCE: + { + int sequence; + if ( pTask->iTask == TASK_PLAY_NODE_SEQUENCE ) + sequence = GetNodeSequence(); + else + sequence = GetNodePresequence(); + + ALERT( at_aiconsole, "BM: Playing node sequence %s\n", STRING(sequence) ); + if ( sequence ) + { + sequence = LookupSequence( STRING( sequence ) ); + if ( sequence != -1 ) + { + pev->sequence = sequence; + pev->frame = 0; + ResetSequenceInfo( ); + ALERT( at_aiconsole, "BM: Sequence %s\n", STRING(GetNodeSequence()) ); + return; + } + } + TaskComplete(); + } + break; + + case TASK_NODE_YAW: + pev->ideal_yaw = GetNodeYaw(); + TaskComplete(); + break; + + case TASK_WAIT_NODE: + m_flWait = gpGlobals->time + GetNodeDelay(); + if ( m_hTargetEnt->pev->spawnflags & SF_INFOBM_WAIT ) + ALERT( at_aiconsole, "BM: Wait at node %s forever\n", STRING(pev->netname) ); + else + ALERT( at_aiconsole, "BM: Wait at node %s for %.2f\n", STRING(pev->netname), GetNodeDelay() ); + break; + + + case TASK_MOVE_TO_NODE_RANGE: + { + CBaseEntity *pTarget = m_hTargetEnt; + if ( !pTarget ) + TaskFail(); + else + { + if ( (pTarget->pev->origin - pev->origin).Length() < GetNodeRange() ) + TaskComplete(); + else + { + Activity act = ACT_WALK; + if ( pTarget->pev->spawnflags & SF_INFOBM_RUN ) + act = ACT_RUN; + + m_vecMoveGoal = pTarget->pev->origin; + if ( !MoveToTarget( act, 2 ) ) + { + TaskFail(); + } + } + } + } + ALERT( at_aiconsole, "BM: Moving to node %s\n", STRING(pev->netname) ); + + break; + + case TASK_MELEE_ATTACK1: + // Play an attack sound here + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, RANDOM_SOUND_ARRAY(pAttackSounds), 1.0, ATTN_NORM, 0, PITCH_NORM ); + CBaseMonster::StartTask( pTask ); + break; + + default: + CBaseMonster::StartTask( pTask ); + break; + } +} + +//========================================================= +// RunTask +//========================================================= +void CBigMomma::RunTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_MOVE_TO_NODE_RANGE: + { + float distance; + + if ( m_hTargetEnt == NULL ) + TaskFail(); + else + { + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + // Set the appropriate activity based on an overlapping range + // overlap the range to prevent oscillation + if ( (distance < GetNodeRange()) || MovementIsComplete() ) + { + ALERT( at_aiconsole, "BM: Reached node!\n" ); + TaskComplete(); + RouteClear(); // Stop moving + } + } + } + + break; + + case TASK_WAIT_NODE: + if ( m_hTargetEnt != NULL && (m_hTargetEnt->pev->spawnflags & SF_INFOBM_WAIT) ) + return; + + if ( gpGlobals->time > m_flWaitFinished ) + TaskComplete(); + ALERT( at_aiconsole, "BM: The WAIT is over!\n" ); + break; + + case TASK_PLAY_NODE_PRESEQUENCE: + case TASK_PLAY_NODE_SEQUENCE: + if ( m_fSequenceFinished ) + { + m_Activity = ACT_RESET; + TaskComplete(); + } + break; + + default: + CBaseMonster::RunTask( pTask ); + break; + } +} + + + +Vector VecCheckSplatToss( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float maxHeight ) +{ + TraceResult tr; + Vector vecMidPoint;// halfway point between Spot1 and Spot2 + Vector vecApex;// highest point + Vector vecScale; + Vector vecGrenadeVel; + Vector vecTemp; + float flGravity = g_psv_gravity->value; + + // calculate the midpoint and apex of the 'triangle' + vecMidPoint = vecSpot1 + (vecSpot2 - vecSpot1) * 0.5; + UTIL_TraceLine(vecMidPoint, vecMidPoint + Vector(0,0,maxHeight), ignore_monsters, ENT(pev), &tr); + vecApex = tr.vecEndPos; + + UTIL_TraceLine(vecSpot1, vecApex, dont_ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + // Don't worry about actually hitting the target, this won't hurt us! + + // How high should the grenade travel (subtract 15 so the grenade doesn't hit the ceiling)? + float height = (vecApex.z - vecSpot1.z) - 15; + // How fast does the grenade need to travel to reach that height given gravity? + float speed = sqrt( 2 * flGravity * height ); + + // How much time does it take to get there? + float time = speed / flGravity; + vecGrenadeVel = (vecSpot2 - vecSpot1); + vecGrenadeVel.z = 0; + float distance = vecGrenadeVel.Length(); + + // Travel half the distance to the target in that time (apex is at the midpoint) + vecGrenadeVel = vecGrenadeVel * ( 0.5 / time ); + // Speed to offset gravity at the desired height + vecGrenadeVel.z = speed; + + return vecGrenadeVel; +} + + + + +// --------------------------------- +// +// Mortar +// +// --------------------------------- +void MortarSpray( const Vector &position, const Vector &direction, int spriteModel, int count ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, position ); + WRITE_BYTE( TE_SPRITE_SPRAY ); + WRITE_COORD( position.x); // pos + WRITE_COORD( position.y); + WRITE_COORD( position.z); + WRITE_COORD( direction.x); // dir + WRITE_COORD( direction.y); + WRITE_COORD( direction.z); + WRITE_SHORT( spriteModel ); // model + WRITE_BYTE ( count ); // count + WRITE_BYTE ( 130 ); // speed + WRITE_BYTE ( 80 ); // noise ( client will divide by 100 ) + MESSAGE_END(); +} + + +// UNDONE: right now this is pretty much a copy of the squid spit with minor changes to the way it does damage +void CBMortar:: Spawn( void ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->classname = MAKE_STRING( "bmortar" ); + + pev->solid = SOLID_BBOX; + pev->rendermode = kRenderTransAlpha; + pev->renderamt = 255; + + SET_MODEL(ENT(pev), "sprites/mommaspit.spr"); + pev->frame = 0; + pev->scale = 0.5; + + UTIL_SetSize( pev, Vector( 0, 0, 0), Vector(0, 0, 0) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; + pev->dmgtime = gpGlobals->time + 0.4; +} + +void CBMortar::Animate( void ) +{ + SetNextThink( 0.1 ); + + if ( gpGlobals->time > pev->dmgtime ) + { + pev->dmgtime = gpGlobals->time + 0.2; + MortarSpray( pev->origin, -pev->velocity.Normalize(), gSpitSprite, 3 ); + } + if ( pev->frame++ ) + { + if ( pev->frame > m_maxFrame ) + { + pev->frame = 0; + } + } +} + +CBMortar *CBMortar::Shoot( edict_t *pOwner, Vector vecStart, Vector vecVelocity ) +{ + CBMortar *pSpit = GetClassPtr( (CBMortar *)NULL ); + pSpit->Spawn(); + + UTIL_SetOrigin( pSpit, vecStart ); + pSpit->pev->velocity = vecVelocity; + pSpit->pev->owner = pOwner; + pSpit->pev->scale = 2.5; + pSpit->SetThink(&CBMortar:: Animate ); + pSpit->SetNextThink( 0.1 ); + + return pSpit; +} + + +void CBMortar::Touch( CBaseEntity *pOther ) +{ + TraceResult tr; + int iPitch; + + // splat sound + iPitch = RANDOM_FLOAT( 90, 110 ); + + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_acid1.wav", 1, ATTN_NORM, 0, iPitch ); + + switch ( RANDOM_LONG( 0, 1 ) ) + { + case 0: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "bullchicken/bc_spithit1.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "bullchicken/bc_spithit2.wav", 1, ATTN_NORM, 0, iPitch ); + break; + } + + if ( pOther->IsBSPModel() ) + { + + // make a splat on the wall + UTIL_TraceLine( pev->origin, pev->origin + pev->velocity * 10, dont_ignore_monsters, ENT( pev ), &tr ); + UTIL_TraceCustomDecal( &tr, "mommasplat", RANDOM_FLOAT( 0.0f, 360.0f )); + } + else + { + tr.vecEndPos = pev->origin; + tr.vecPlaneNormal = -1 * pev->velocity.Normalize(); + } + // make some flecks + MortarSpray( tr.vecEndPos, tr.vecPlaneNormal, gSpitSprite, 24 ); + + entvars_t *pevOwner = NULL; + if ( pev->owner ) + pevOwner = VARS(pev->owner); + + RadiusDamage( pev->origin, pev, pevOwner, gSkillData.bigmommaDmgBlast, gSkillData.bigmommaRadiusBlast, CLASS_NONE, DMG_ACID ); + UTIL_Remove( this ); +} + +#endif diff --git a/dlls/bloater.cpp b/dlls/bloater.cpp new file mode 100644 index 0000000..e4ed981 --- /dev/null +++ b/dlls/bloater.cpp @@ -0,0 +1,225 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Bloater +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define BLOATER_AE_ATTACK_MELEE1 0x01 + + +class CBloater : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + float MaxYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + + void PainSound( void ); + void AlertSound( void ); + void IdleSound( void ); + void AttackSnd( void ); + + // No range attacks + BOOL CheckRangeAttack1 ( float flDot, float flDist ) { return FALSE; } + BOOL CheckRangeAttack2 ( float flDot, float flDist ) { return FALSE; } + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); +}; + +LINK_ENTITY_TO_CLASS( monster_bloater, CBloater ); + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CBloater :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MONSTER; +} + +//========================================================= +// MaxYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +float CBloater :: MaxYawSpeed( void ) +{ + float ys; + + ys = 120; + +#if 0 + switch ( m_Activity ) + { + } +#endif + + return ys; +} + +int CBloater :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + PainSound(); + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +void CBloater :: PainSound( void ) +{ +#if 0 + int pitch = 95 + RANDOM_LONG(0,9); + + switch (RANDOM_LONG(0,5)) + { + case 0: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_pain1.wav", 1.0, ATTN_NORM, 0, pitch); + break; + case 1: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_pain2.wav", 1.0, ATTN_NORM, 0, pitch); + break; + default: + break; + } +#endif +} + +void CBloater :: AlertSound( void ) +{ +#if 0 + int pitch = 95 + RANDOM_LONG(0,9); + + switch (RANDOM_LONG(0,2)) + { + case 0: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_alert10.wav", 1.0, ATTN_NORM, 0, pitch); + break; + case 1: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_alert20.wav", 1.0, ATTN_NORM, 0, pitch); + break; + case 2: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_alert30.wav", 1.0, ATTN_NORM, 0, pitch); + break; + } +#endif +} + +void CBloater :: IdleSound( void ) +{ +#if 0 + int pitch = 95 + RANDOM_LONG(0,9); + + switch (RANDOM_LONG(0,2)) + { + case 0: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_idle1.wav", 1.0, ATTN_NORM, 0, pitch); + break; + case 1: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_idle2.wav", 1.0, ATTN_NORM, 0, pitch); + break; + case 2: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_idle3.wav", 1.0, ATTN_NORM, 0, pitch); + break; + } +#endif +} + +void CBloater :: AttackSnd( void ) +{ +#if 0 + int pitch = 95 + RANDOM_LONG(0,9); + + switch (RANDOM_LONG(0,1)) + { + case 0: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_attack1.wav", 1.0, ATTN_NORM, 0, pitch); + break; + case 1: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_attack2.wav", 1.0, ATTN_NORM, 0, pitch); + break; + } +#endif +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CBloater :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case BLOATER_AE_ATTACK_MELEE1: + { + // do stuff for this event. + AttackSnd(); + } + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CBloater :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/floater.mdl"); + UTIL_SetSize( pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_FLY; + pev->spawnflags |= FL_FLY; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->health = 40; + pev->view_ofs = VEC_VIEW;// position of the eyes relative to monster's origin. + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CBloater :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/floater.mdl"); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + diff --git a/dlls/bmodels.cpp b/dlls/bmodels.cpp new file mode 100644 index 0000000..3ed3973 --- /dev/null +++ b/dlls/bmodels.cpp @@ -0,0 +1,1053 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== bmodels.cpp ======================================================== + + spawn, think, and use functions for entities that use brush models + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "doors.h" +#include "movewith.h" + +extern DLL_GLOBAL Vector g_vecAttackDir; + +#define SF_BRUSH_ACCDCC 16// brush should accelerate and decelerate when toggled +#define SF_BRUSH_HURT 32// rotating brush that inflicts pain based on rotation speed +#define SF_ROTATING_NOT_SOLID 64 // some special rotating objects are not solid. + +// covering cheesy noise1, noise2, & noise3 fields so they make more sense (for rotating fans) +#define noiseStart noise1 +#define noiseStop noise2 +#define noiseRunning noise3 + +#define SF_PENDULUM_SWING 2 // spawnflag that makes a pendulum a rope swing. +// +// BModelOrigin - calculates origin of a bmodel from absmin/size because all bmodel origins are 0 0 0 +// +Vector VecBModelOrigin( entvars_t* pevBModel ) +{ + return (pevBModel->absmin + pevBModel->absmax) * 0.5; //LRC - bug fix for rotating ents +// return pevBModel->absmin + ( pevBModel->size * 0.5 ); +} + +// =================== FUNC_WALL ============================================== + +/*QUAKED func_wall (0 .5 .8) ? +This is just a solid wall if not inhibited +*/ +class CFuncWall : public CBaseEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual STATE GetState( void ) { return pev->frame?STATE_ON:STATE_OFF; }; + + // Bmodels don't go across transitions + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + int m_iStyle; +}; + +LINK_ENTITY_TO_CLASS( func_wall, CFuncWall ); + +void CFuncWall :: Spawn( void ) +{ + pev->angles = g_vecZero; + pev->movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything + pev->solid = SOLID_BSP; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + // If it can't move/go away, it's really part of the world + if( !m_pMoveWith ) //LRC + { + pev->flags |= FL_WORLDBRUSH; + + // tell the client about static entity + SetBits( pev->iuser1, CF_STATIC_ENTITY ); + } + + //LRC +// if (m_iStyle >= 32) LIGHT_STYLE(m_iStyle, "a"); buz +// else if (m_iStyle <= -32) LIGHT_STYLE(-m_iStyle, "z"); +} + + +void CFuncWall :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( ShouldToggle( useType, (int)(pev->frame)) ) + { + pev->frame = 1 - pev->frame; + /* if (m_iStyle >= 32) + { + if (pev->frame) + LIGHT_STYLE(m_iStyle, "z"); + else + LIGHT_STYLE(m_iStyle, "a"); + } + else if (m_iStyle <= -32) + { + if (pev->frame) + LIGHT_STYLE(-m_iStyle, "a"); + else + LIGHT_STYLE(-m_iStyle, "z"); + }*/ // buz + } +} + + +#define SF_WALL_START_OFF 0x0001 + +class CFuncWallToggle : public CFuncWall +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void TurnOff( void ); + void TurnOn( void ); + BOOL IsOn( void ); + virtual STATE GetState( void ) { return (pev->solid == SOLID_NOT)?STATE_OFF:STATE_ON; }; +}; + +LINK_ENTITY_TO_CLASS( func_wall_toggle, CFuncWallToggle ); + +void CFuncWallToggle :: Spawn( void ) +{ + CFuncWall::Spawn(); + if ( pev->spawnflags & SF_WALL_START_OFF ) + TurnOff(); + + if( !m_pMoveWith ) + { + // tell the client about static entity + SetBits( pev->iuser1, CF_STATIC_ENTITY ); + } +} + +void CFuncWallToggle :: TurnOff( void ) +{ + pev->solid = SOLID_NOT; + pev->effects |= EF_NODRAW; + UTIL_SetOrigin( this, pev->origin ); +} + + +void CFuncWallToggle :: TurnOn( void ) +{ + pev->solid = SOLID_BSP; + pev->effects &= ~EF_NODRAW; + UTIL_SetOrigin( this, pev->origin ); +} + + +BOOL CFuncWallToggle :: IsOn( void ) +{ + if ( pev->solid == SOLID_NOT ) + return FALSE; + return TRUE; +} + + +void CFuncWallToggle :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ +// int status = IsOn(); + BOOL status = (GetState() == STATE_ON); + + if ( ShouldToggle( useType, status ) ) + { + if ( status ) + TurnOff(); + else + TurnOn(); + } +} + + +#define SF_CONVEYOR_VISUAL 0x0001 +#define SF_CONVEYOR_NOTSOLID 0x0002 + +class CFuncConveyor : public CFuncWall +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void UpdateSpeed( float speed ); +}; + +LINK_ENTITY_TO_CLASS( func_conveyor, CFuncConveyor ); +void CFuncConveyor :: Spawn( void ) +{ + SetMovedir( pev ); + CFuncWall::Spawn(); + + if ( !(pev->spawnflags & SF_CONVEYOR_VISUAL) ) + SetBits( pev->flags, FL_CONVEYOR ); + + // HACKHACK - This is to allow for some special effects + if ( pev->spawnflags & SF_CONVEYOR_NOTSOLID ) + { + pev->solid = SOLID_NOT; + pev->skin = 0; // Don't want the engine thinking we've got special contents on this brush + } + + if( !m_pMoveWith ) + { + // tell the client about static entity + SetBits( pev->iuser1, CF_STATIC_ENTITY ); + } + + if ( pev->speed == 0 ) + pev->speed = 100; + + UpdateSpeed( pev->speed ); +} + + +// HACKHACK -- This is ugly, but encode the speed in the rendercolor to avoid adding more data to the network stream +void CFuncConveyor :: UpdateSpeed( float speed ) +{ + // Encode it as an integer with 4 fractional bits + int speedCode = (int)(fabs(speed) * 16.0); + + if ( speed < 0 ) + pev->rendercolor.x = 1; + else + pev->rendercolor.x = 0; + + pev->rendercolor.y = (speedCode >> 8); + pev->rendercolor.z = (speedCode & 0xFF); +} + + +void CFuncConveyor :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + pev->speed = -pev->speed; + UpdateSpeed( pev->speed ); +} + + + +// =================== FUNC_ILLUSIONARY ============================================== + + +/*QUAKED func_illusionary (0 .5 .8) ? +A simple entity that looks solid but lets you walk through it. +*/ +class CFuncIllusionary : public CBaseToggle +{ +public: + void Spawn( void ); + void EXPORT SloshTouch( CBaseEntity *pOther ); + void KeyValue( KeyValueData *pkvd ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + +LINK_ENTITY_TO_CLASS( func_illusionary, CFuncIllusionary ); + +void CFuncIllusionary :: KeyValue( KeyValueData *pkvd ) +{ + // LRC- surely it just parses this automatically? pev values are handled by the engine. + if (FStrEq(pkvd->szKeyName, "skin"))//skin is used for content type + { + pev->skin = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CFuncIllusionary :: Spawn( void ) +{ + pev->angles = g_vecZero; + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_NOT;// always solid_not + SET_MODEL( ENT(pev), STRING(pev->model) ); + + if( !m_pMoveWith ) + { + // tell the client about static entity + SetBits( pev->iuser1, CF_STATIC_ENTITY ); + } + + // I'd rather eat the network bandwidth of this than figure out how to save/restore + // these entities after they have been moved to the client, or respawn them ala Quake + // Perhaps we can do this in deathmatch only. + // MAKE_STATIC(ENT(pev)); +} + +// ------------------------------------------------------------------------------- +// +// Monster only clip brush +// +// This brush will be solid for any entity who has the FL_MONSTERCLIP flag set +// in pev->flags +// +// otherwise it will be invisible and not solid. This can be used to keep +// specific monsters out of certain areas +// +// ------------------------------------------------------------------------------- +class CFuncMonsterClip : public CFuncWall +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) {} // Clear out func_wall's use function +}; + +LINK_ENTITY_TO_CLASS( func_monsterclip, CFuncMonsterClip ); + +void CFuncMonsterClip::Spawn( void ) +{ + CFuncWall::Spawn(); + if ( CVAR_GET_FLOAT("showtriggers") == 0 ) + pev->effects |= EF_NODRAW; + pev->flags |= FL_MONSTERCLIP; +} + + +// =================== FUNC_ROTATING ============================================== +class CFuncRotating : public CBaseEntity +{ +public: + // basic functions + void Spawn( void ); + void Precache( void ); + void EXPORT SpinUp ( void ); + void EXPORT SpinDown ( void ); + void KeyValue( KeyValueData* pkvd); + void EXPORT HurtTouch ( CBaseEntity *pOther ); + void EXPORT RotatingUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT WaitForStart (); //LRC - get round 1.1.0.8's bizarre behaviour on startup + void EXPORT Rotate( void ); + void RampPitchVol (int fUp ); + void Blocked( CBaseEntity *pOther ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flFanFriction; + float m_flAttenuation; + float m_flVolume; + float m_pitch; + int m_sounds; + + float m_fCurSpeed; //LRC - during spin-up and spin-down, this is + // the current speed factor (between 0 and 1). + // storing this here lets us avoid the hassle of deriving it + // from pev->avelocity. + + STATE m_iState; //LRC + virtual STATE GetState( void ) { return m_iState; }; //LRC +}; + +TYPEDESCRIPTION CFuncRotating::m_SaveData[] = +{ + DEFINE_FIELD( CFuncRotating, m_flFanFriction, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_flAttenuation, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_flVolume, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_pitch, FIELD_FLOAT ), + DEFINE_FIELD( CFuncRotating, m_sounds, FIELD_INTEGER ), + DEFINE_FIELD( CFuncRotating, m_fCurSpeed, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CFuncRotating, CBaseEntity ); + + +LINK_ENTITY_TO_CLASS( func_rotating, CFuncRotating ); + +void CFuncRotating :: KeyValue( KeyValueData* pkvd) +{ + if (FStrEq(pkvd->szKeyName, "fanfriction")) + { + m_flFanFriction = atof(pkvd->szValue)/100; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "Volume")) + { + m_flVolume = atof(pkvd->szValue)/10.0; + + if (m_flVolume > 1.0) + m_flVolume = 1.0; + if (m_flVolume < 0.0) + m_flVolume = 0.0; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spawnorigin")) + { + Vector tmp; + UTIL_StringToVector( (float *)tmp, pkvd->szValue ); + if ( tmp != g_vecZero ) + pev->origin = tmp; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "axes")) + { + UTIL_StringToVector( (float *)(pev->movedir), pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +/*QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS +You need to have an origin brush as part of this entity. The +center of that brush will be +the point around which it is rotated. It will rotate around the Z +axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"speed" determines how fast it moves; default value is 100. +"dmg" damage to inflict when blocked (2 default) + +REVERSE will cause the it to rotate in the opposite direction. +*/ + + +void CFuncRotating :: Spawn( ) +{ + m_iState = STATE_OFF; + + m_fCurSpeed = 0; //LRC + + // set final pitch. Must not be PITCH_NORM, since we + // plan on pitch shifting later. + + m_pitch = PITCH_NORM - 1; + + // maintain compatibility with previous maps + if (m_flVolume == 0.0) + m_flVolume = 1.0; + + // if the designer didn't set a sound attenuation, default to one. + m_flAttenuation = ATTN_NORM; + + if ( FBitSet ( pev->spawnflags, SF_BRUSH_ROTATE_SMALLRADIUS) ) + { + m_flAttenuation = ATTN_IDLE; + } + else if ( FBitSet ( pev->spawnflags, SF_BRUSH_ROTATE_MEDIUMRADIUS) ) + { + m_flAttenuation = ATTN_STATIC; + } + else if ( FBitSet ( pev->spawnflags, SF_BRUSH_ROTATE_LARGERADIUS) ) + { + m_flAttenuation = ATTN_NORM; + } + + // prevent divide by zero if level designer forgets friction! + if ( m_flFanFriction <= 0 ) //LRC - ensure it's not negative + { + m_flFanFriction = 1; + } + + if (pev->movedir == g_vecZero) + { + if ( FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_Z_AXIS) ) + pev->movedir = Vector(0,0,1); + else if ( FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_X_AXIS) ) + pev->movedir = Vector(1,0,0); + else + pev->movedir = Vector(0,1,0); // y-axis + } + + // check for reverse rotation + if ( FBitSet(pev->spawnflags, SF_BRUSH_ROTATE_BACKWARDS) ) + pev->movedir = pev->movedir * -1; + + // some rotating objects like fake volumetric lights will not be solid. + if ( FBitSet(pev->spawnflags, SF_ROTATING_NOT_SOLID) ) + { + pev->solid = SOLID_NOT; + pev->skin = CONTENTS_EMPTY; + pev->movetype = MOVETYPE_PUSH; + } + else + { + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + } + + UTIL_SetOrigin(this, pev->origin); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + SetUse(&CFuncRotating :: RotatingUse ); + // did level designer forget to assign speed? + if (pev->speed <= 0) + pev->speed = 0; + + // Removed this per level designers request. -- JAY + // if (pev->dmg == 0) + // pev->dmg = 2; + + // instant-use brush? + //LRC - start immediately if unnamed, too. + if ( FBitSet( pev->spawnflags, SF_BRUSH_ROTATE_INSTANT) || FStringNull(pev->targetname) ) + { + SetThink(&CFuncRotating :: WaitForStart ); + SetNextThink( 1.5 ); // leave a magic delay for client to start up + } + // can this brush inflict pain? + if ( FBitSet (pev->spawnflags, SF_BRUSH_HURT) ) + { + SetTouch(&CFuncRotating :: HurtTouch ); + } + + Precache( ); +} + + +void CFuncRotating :: Precache( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + + // set up fan sounds + + if (!FStringNull( pev->message ) && strlen( szSoundFile ) > 0) + { + // if a path is set for a wave, use it + + PRECACHE_SOUND(szSoundFile); + + pev->noiseRunning = ALLOC_STRING(szSoundFile); + } else + { + // otherwise use preset sound + switch (m_sounds) + { + case 1: + PRECACHE_SOUND ("fans/fan1.wav"); + pev->noiseRunning = MAKE_STRING("fans/fan1.wav"); + break; + case 2: + PRECACHE_SOUND ("fans/fan2.wav"); + pev->noiseRunning = MAKE_STRING("fans/fan2.wav"); + break; + case 3: + PRECACHE_SOUND ("fans/fan3.wav"); + pev->noiseRunning = MAKE_STRING("fans/fan3.wav"); + break; + case 4: + PRECACHE_SOUND ("fans/fan4.wav"); + pev->noiseRunning = MAKE_STRING("fans/fan4.wav"); + break; + case 5: + PRECACHE_SOUND ("fans/fan5.wav"); + pev->noiseRunning = MAKE_STRING("fans/fan5.wav"); + break; + + case 0: + default: + if (!FStringNull( pev->message ) && strlen( szSoundFile ) > 0) + { + PRECACHE_SOUND(szSoundFile); + + pev->noiseRunning = ALLOC_STRING(szSoundFile); + break; + } else + { + pev->noiseRunning = MAKE_STRING("common/null.wav"); + break; + } + } + } + + if (m_fCurSpeed != 0 ) + { + // if fan was spinning, and we went through transition or save/restore, + // make sure we restart the sound. 1.5 sec delay is magic number. KDB + + SetThink(&CFuncRotating :: SpinUp ); + SetNextThink( 1.5 ); + } +} + + +void CFuncRotating :: WaitForStart() +{ + if (gpGlobals->time > 1) // has the client started yet? + { + SUB_CallUseToggle(); + } + else + { + SetNextThink( 0.1 ); + } +} + +// +// Touch - will hurt others based on how fast the brush is spinning +// +void CFuncRotating :: HurtTouch ( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + // we can't hurt this thing, so we're not concerned with it + if ( !pevOther->takedamage ) + return; + + // calculate damage based on rotation speed + pev->dmg = m_fCurSpeed / 10; //LRC +// pev->dmg = pev->avelocity.Length() / 10; + + pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH); + + pevOther->velocity = (pevOther->origin - VecBModelOrigin(pev) ).Normalize() * pev->dmg; +} + +// +// RampPitchVol - ramp pitch and volume up to final values, based on difference +// between how fast we're going vs how fast we plan to go +// +#define FANPITCHMIN 30 +#define FANPITCHMAX 100 + +void CFuncRotating :: RampPitchVol (int fUp) +{ + float fvol; + float fpitch; + int pitch; + float speedfactor = m_fCurSpeed/pev->speed; + + fvol = m_flVolume * speedfactor; // slowdown volume ramps down to 0 + + fpitch = FANPITCHMIN + (FANPITCHMAX - FANPITCHMIN) * speedfactor; + + pitch = (int) fpitch; + if (pitch == PITCH_NORM) + pitch = PITCH_NORM-1; + + // change the fan's vol and pitch + + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + fvol, m_flAttenuation, SND_CHANGE_PITCH | SND_CHANGE_VOL, pitch); + +} + +// +// SpinUp - accelerates a non-moving func_rotating up to it's speed +// +void CFuncRotating :: SpinUp( void ) +{ + //Vector vecAVel;//rotational velocity + + SetNextThink( 0.1 ); + m_fCurSpeed = m_fCurSpeed + ( pev->speed * m_flFanFriction ); + UTIL_SetAvelocity(this, pev->movedir * m_fCurSpeed); + //pev->avelocity = pev->avelocity + ( pev->movedir * ( pev->speed * m_flFanFriction ) ); + + //vecAVel = pev->avelocity;// cache entity's rotational velocity + + // if we've met or exceeded target speed, set target speed and stop thinking + if ( m_fCurSpeed >= pev->speed ) + { + m_iState = STATE_ON; + m_fCurSpeed = pev->speed; + UTIL_SetAvelocity(this, pev->movedir * pev->speed); + //pev->avelocity = pev->movedir * pev->speed;// set speed in case we overshot + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + m_flVolume, m_flAttenuation, SND_CHANGE_PITCH | SND_CHANGE_VOL, FANPITCHMAX); + + SetThink(&CFuncRotating :: Rotate ); + Rotate(); + } + else + { + RampPitchVol(TRUE); + } +} + +// +// SpinDown - decelerates a moving func_rotating to a standstill. +// +void CFuncRotating :: SpinDown( void ) +{ + SetNextThink( 0.1 ); + + m_fCurSpeed = m_fCurSpeed - ( pev->speed * m_flFanFriction ); + UTIL_SetAvelocity(this, pev->movedir * m_fCurSpeed); + //pev->avelocity = pev->avelocity - ( pev->movedir * ( pev->speed * m_flFanFriction ) );//spin down slower than spinup + + // if we've met or exceeded target speed, set target speed and stop thinking + if (m_fCurSpeed <= 0) + { + m_iState = STATE_OFF; + m_fCurSpeed = 0; + UTIL_SetAvelocity(this, g_vecZero); + //pev->avelocity = g_vecZero;// set speed in case we overshot + + // stop sound, we're done + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning /* Stop */), + 0, 0, SND_STOP, m_pitch); + + SetThink(&CFuncRotating :: Rotate ); + Rotate(); + } + else + { + RampPitchVol(FALSE); + } +} + +void CFuncRotating :: Rotate( void ) +{ + SetNextThink( 10 ); +} + +//========================================================= +// Rotating Use - when a rotating brush is triggered +//========================================================= +void CFuncRotating :: RotatingUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (!ShouldToggle(useType)) return; + + // is this a brush that should accelerate and decelerate when turned on/off (fan)? + if ( FBitSet ( pev->spawnflags, SF_BRUSH_ACCDCC ) ) + { + // fan is spinning, so stop it. + if ( m_fCurSpeed != 0 ) +// if ( pev->avelocity != g_vecZero ) + { + m_iState = STATE_TURN_OFF; + SetThink(&CFuncRotating :: SpinDown ); + //EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, (char *)STRING(pev->noiseStop), + // m_flVolume, m_flAttenuation, 0, m_pitch); + + SetNextThink( 0.1 ); + } + else// fan is not moving, so start it + { + m_iState = STATE_TURN_ON; + SetThink(&CFuncRotating :: SpinUp ); + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + 0.01, m_flAttenuation, 0, FANPITCHMIN); + + SetNextThink( 0.1 ); + } + } + else // if ( !FBitSet ( pev->spawnflags, SF_BRUSH_ACCDCC ) )//this is a normal start/stop brush. + { + if ( m_fCurSpeed != 0 ) //LRC +// if ( pev->avelocity != g_vecZero ) + { + m_iState = STATE_OFF; + // play stopping sound here + SetThink(&CFuncRotating :: SpinDown ); + + // EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, (char *)STRING(pev->noiseStop), + // m_flVolume, m_flAttenuation, 0, m_pitch); + + SetNextThink( 0.1 ); + // pev->avelocity = g_vecZero; + } + else + { + m_iState = STATE_ON; + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char *)STRING(pev->noiseRunning), + m_flVolume, m_flAttenuation, 0, FANPITCHMAX); + + //LRC + m_fCurSpeed = pev->speed; + UTIL_SetAvelocity(this, pev->movedir * pev->speed); +// pev->avelocity = pev->movedir * pev->speed; + + SetThink(&CFuncRotating :: Rotate ); + Rotate(); + } + } +} + + +// +// RotatingBlocked - An entity has blocked the brush +// +void CFuncRotating :: Blocked( CBaseEntity *pOther ) + +{ + pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH); +} + + + + + + +//#endif + + +class CPendulum : public CBaseEntity +{ +public: + void Spawn ( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT SwingThink( void ); + void EXPORT PendulumUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT StopThink( void ); + void Touch( CBaseEntity *pOther ); + void EXPORT RopeTouch ( CBaseEntity *pOther );// this touch func makes the pendulum a rope + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + void Blocked( CBaseEntity *pOther ); + virtual STATE GetState( void ) { return (pev->speed)?STATE_ON:STATE_OFF; } + + static TYPEDESCRIPTION m_SaveData[]; + + float m_accel; // Acceleration + float m_distance; // + float m_time; + float m_damp; + float m_maxSpeed; + float m_dampSpeed; + vec3_t m_center; + vec3_t m_start; +}; + +LINK_ENTITY_TO_CLASS( func_pendulum, CPendulum ); + +TYPEDESCRIPTION CPendulum::m_SaveData[] = +{ + DEFINE_FIELD( CPendulum, m_accel, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_distance, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_time, FIELD_TIME ), + DEFINE_FIELD( CPendulum, m_damp, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_maxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_dampSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CPendulum, m_center, FIELD_VECTOR ), + DEFINE_FIELD( CPendulum, m_start, FIELD_VECTOR ), +}; + +IMPLEMENT_SAVERESTORE( CPendulum, CBaseEntity ); + + + +void CPendulum :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "distance")) + { + m_distance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "axes")) + { + UTIL_StringToVector( (float*)(pev->movedir), pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damp")) + { + m_damp = atof(pkvd->szValue) * 0.001; + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +void CPendulum :: Spawn( void ) +{ + // set the axis of rotation + CBaseToggle :: AxisDir( pev ); + + if ( FBitSet (pev->spawnflags, SF_DOOR_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + UTIL_SetOrigin(this, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + if ( m_distance == 0 ) + return; + + if (pev->speed == 0) + pev->speed = 100; + + m_accel = (pev->speed * pev->speed) / (2 * fabs(m_distance)); // Calculate constant acceleration from speed and distance + m_maxSpeed = pev->speed; + m_start = pev->angles; + m_center = pev->angles + (m_distance * 0.5) * pev->movedir; + + if ( FBitSet( pev->spawnflags, SF_BRUSH_ROTATE_INSTANT) ) + { + SetThink(&CPendulum :: SUB_CallUseToggle ); + SetNextThink( 0.1 ); + } + pev->speed = 0; + SetUse(&CPendulum :: PendulumUse ); + + if ( FBitSet( pev->spawnflags, SF_PENDULUM_SWING ) ) + { + SetTouch(&CPendulum :: RopeTouch ); + } +} + + +void CPendulum :: PendulumUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (!ShouldToggle(useType)) return; + + if ( pev->speed ) // Pendulum is moving, stop it and auto-return if necessary + { + if ( FBitSet( pev->spawnflags, SF_PENDULUM_AUTO_RETURN ) ) + { + float delta; + + delta = CBaseToggle :: AxisDelta( pev->spawnflags, pev->angles, m_start ); + + UTIL_SetAvelocity(this, m_maxSpeed * pev->movedir); //LRC + //pev->avelocity = m_maxSpeed * pev->movedir; + SetNextThink(delta / m_maxSpeed); + SetThink(&CPendulum ::StopThink); + } + else + { + pev->speed = 0; // Dead stop + DontThink(); + UTIL_SetAvelocity(this, g_vecZero); //LRC + //pev->avelocity = g_vecZero; + } + } + else + { + SetNextThink(0.1); // start the pendulum moving + SetThink(&CPendulum ::SwingThink); + m_time = gpGlobals->time; // Save time to calculate dt + m_dampSpeed = m_maxSpeed; + } +} + +void CPendulum :: StopThink( void ) +{ + UTIL_SetAngles(this, m_start); //LRC + //pev->angles = m_start; + pev->speed = 0; + DontThink(); + UTIL_SetAvelocity(this, g_vecZero); //LRC + //pev->avelocity = g_vecZero; +} + + +void CPendulum::Blocked( CBaseEntity *pOther ) +{ + m_time = gpGlobals->time; +} + +void CPendulum :: SwingThink( void ) +{ + float delta, dt; + + delta = CBaseToggle :: AxisDelta( pev->spawnflags, pev->angles, m_center ); +// dt = gpGlobals->time - m_time; // How much time has passed? +// m_time = gpGlobals->time; // Remember the last time called + dt = 0.1; // buz: it is thinking with 0.1 step... + + if ( delta > 0 && m_accel > 0 ) + pev->speed -= m_accel * dt; // Integrate velocity + else + pev->speed += m_accel * dt; + + if ( pev->speed > m_maxSpeed ) + pev->speed = m_maxSpeed; + else if ( pev->speed < -m_maxSpeed ) + pev->speed = -m_maxSpeed; + + // scale the destdelta vector by the time spent traveling to get velocity + UTIL_SetAvelocity(this, pev->speed * pev->movedir); //LRC + //pev->avelocity = pev->speed * pev->movedir; + +// ALERT(at_console, "m_damp %f, m_dampSpeed %f\n", m_damp, m_dampSpeed); +// ALERT(at_console, "SwingThink: delta %f, dt %f, speed %f, avel %f %f %f\n", delta, dt, pev->speed, pev->avelocity.x, pev->avelocity.y, pev->avelocity.z); + + // Call this again + SetNextThink(0.1); + SetThink(&CPendulum ::SwingThink); + + if (m_pMoveWith) // correct MoveWith problems associated with fast-thinking entities + UTIL_AssignOrigin(this, m_vecMoveWithOffset + m_pMoveWith->pev->origin); + + if ( m_damp ) + { + m_dampSpeed -= m_damp * m_dampSpeed * dt; + if ( m_dampSpeed < 30.0 ) + { + UTIL_SetAngles(this, m_center); //LRC + //pev->angles = m_center; + pev->speed = 0; + ALERT(at_debug, "**CANCELLING pendulum think!\n"); + DontThink(); + UTIL_SetAvelocity(this, g_vecZero); //LRC + //pev->avelocity = g_vecZero; + } + else if ( pev->speed > m_dampSpeed ) + pev->speed = m_dampSpeed; + else if ( pev->speed < -m_dampSpeed ) + pev->speed = -m_dampSpeed; + + } +} + + +void CPendulum :: Touch ( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + if ( pev->dmg <= 0 ) + return; + + // we can't hurt this thing, so we're not concerned with it + if ( !pevOther->takedamage ) + return; + + // calculate damage based on rotation speed + float damage = pev->dmg * pev->speed * 0.01; + + if ( damage < 0 ) + damage = -damage; + + pOther->TakeDamage( pev, pev, damage, DMG_CRUSH ); + + pevOther->velocity = (pevOther->origin - VecBModelOrigin(pev) ).Normalize() * damage; +} + +void CPendulum :: RopeTouch ( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + if ( !pOther->IsPlayer() ) + {// not a player! + ALERT ( at_console, "Not a client\n" ); + return; + } + + if ( ENT(pevOther) == pev->enemy ) + {// this player already on the rope. + return; + } + + pev->enemy = pOther->edict(); + pevOther->velocity = g_vecZero; + pevOther->movetype = MOVETYPE_NONE; +} + + diff --git a/dlls/bullsquid.cpp b/dlls/bullsquid.cpp new file mode 100644 index 0000000..5a2082e --- /dev/null +++ b/dlls/bullsquid.cpp @@ -0,0 +1,1292 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// bullsquid - big, spotty tentacle-mouthed meanie. +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "nodes.h" +#include "effects.h" +#include "decals.h" +#include "soundent.h" +#include "scripted.h" +#include "game.h" + +#define SQUID_SPRINT_DIST 256 // how close the squid has to get before starting to sprint and refusing to swerve + +int iSquidSpitSprite; + + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_SQUID_HURTHOP = LAST_COMMON_SCHEDULE + 1, + SCHED_SQUID_SMELLFOOD, + SCHED_SQUID_SEECRAB, + SCHED_SQUID_EAT, + SCHED_SQUID_SNIFF_AND_EAT, + SCHED_SQUID_WALLOW, +}; + +//========================================================= +// monster-specific tasks +//========================================================= +enum +{ + TASK_SQUID_HOPTURN = LAST_COMMON_TASK + 1, +}; + +//========================================================= +// Bullsquid's spit projectile +//========================================================= +class CSquidSpit : public CBaseEntity +{ +public: + void Spawn( void ); + + static void Shoot( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ); + void Touch( CBaseEntity *pOther ); + void EXPORT Animate( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_maxFrame; +}; + +LINK_ENTITY_TO_CLASS( squidspit, CSquidSpit ); + +TYPEDESCRIPTION CSquidSpit::m_SaveData[] = +{ + DEFINE_FIELD( CSquidSpit, m_maxFrame, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CSquidSpit, CBaseEntity ); + +void CSquidSpit:: Spawn( void ) +{ + pev->movetype = MOVETYPE_FLY; + pev->classname = MAKE_STRING( "squidspit" ); + + pev->solid = SOLID_BBOX; + pev->rendermode = kRenderTransAlpha; + pev->renderamt = 255; + + SET_MODEL(ENT(pev), "sprites/bigspit.spr"); + pev->frame = 0; + pev->scale = 0.5; + + UTIL_SetSize( pev, Vector( 0, 0, 0), Vector(0, 0, 0) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; +} + +void CSquidSpit::Animate( void ) +{ + SetNextThink( 0.1 ); + + if ( pev->frame++ ) + { + if ( pev->frame > m_maxFrame ) + { + pev->frame = 0; + } + } +} + +void CSquidSpit::Shoot( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ) +{ + CSquidSpit *pSpit = GetClassPtr( (CSquidSpit *)NULL ); + pSpit->Spawn(); + + UTIL_SetOrigin( pSpit, vecStart ); + pSpit->pev->velocity = vecVelocity; + pSpit->pev->owner = ENT(pevOwner); + + pSpit->SetThink(&CSquidSpit:: Animate ); + pSpit->SetNextThink( 0.1 ); +} + +void CSquidSpit :: Touch ( CBaseEntity *pOther ) +{ + TraceResult tr; + int iPitch; + + // splat sound + iPitch = RANDOM_FLOAT( 90, 110 ); + + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_acid1.wav", 1, ATTN_NORM, 0, iPitch ); + + switch ( RANDOM_LONG( 0, 1 ) ) + { + case 0: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "bullchicken/bc_spithit1.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "bullchicken/bc_spithit2.wav", 1, ATTN_NORM, 0, iPitch ); + break; + } + + if ( !pOther->pev->takedamage ) + { + + // make a splat on the wall + UTIL_TraceLine( pev->origin, pev->origin + pev->velocity * 10, dont_ignore_monsters, ENT( pev ), &tr ); + UTIL_TraceCustomDecal( &tr, "spit", RANDOM_FLOAT( 0.0f, 360.0f )); + + // make some flecks + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, tr.vecEndPos ); + WRITE_BYTE( TE_SPRITE_SPRAY ); + WRITE_COORD( tr.vecEndPos.x); // pos + WRITE_COORD( tr.vecEndPos.y); + WRITE_COORD( tr.vecEndPos.z); + WRITE_COORD( tr.vecPlaneNormal.x); // dir + WRITE_COORD( tr.vecPlaneNormal.y); + WRITE_COORD( tr.vecPlaneNormal.z); + WRITE_SHORT( iSquidSpitSprite ); // model + WRITE_BYTE ( 5 ); // count + WRITE_BYTE ( 30 ); // speed + WRITE_BYTE ( 80 ); // noise ( client will divide by 100 ) + MESSAGE_END(); + } + else + { + pOther->TakeDamage ( pev, pev, gSkillData.bullsquidDmgSpit, DMG_GENERIC ); + } + + SetThink(&CSquidSpit :: SUB_Remove ); + SetNextThink( 0 ); +} + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define BSQUID_AE_SPIT ( 1 ) +#define BSQUID_AE_BITE ( 2 ) +#define BSQUID_AE_BLINK ( 3 ) +#define BSQUID_AE_TAILWHIP ( 4 ) +#define BSQUID_AE_HOP ( 5 ) +#define BSQUID_AE_THROW ( 6 ) + +class CBullsquid : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + float MaxYawSpeed( void ); + int ISoundMask( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void IdleSound( void ); + void PainSound( void ); + void DeathSound( void ); + void AlertSound ( void ); + void AttackSound( void ); + void StartTask ( Task_t *pTask ); + void RunTask ( Task_t *pTask ); + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); + BOOL CheckMeleeAttack2 ( float flDot, float flDist ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + void RunAI( void ); + BOOL FValidateHintType ( short sHint ); + Schedule_t *GetSchedule( void ); + Schedule_t *GetScheduleOfType ( int Type ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + int IRelationship ( CBaseEntity *pTarget ); + int IgnoreConditions ( void ); + MONSTERSTATE GetIdealState ( void ); + + int Save( CSave &save ); + int Restore( CRestore &restore ); + + CUSTOM_SCHEDULES; + static TYPEDESCRIPTION m_SaveData[]; + + BOOL m_fCanThreatDisplay;// this is so the squid only does the "I see a headcrab!" dance one time. + + float m_flLastHurtTime;// we keep track of this, because if something hurts a squid, it will forget about its love of headcrabs for a while. + float m_flNextSpitTime;// last time the bullsquid used the spit attack. +}; +LINK_ENTITY_TO_CLASS( monster_bullchicken, CBullsquid ); +LINK_ENTITY_TO_CLASS( monster_bullsquid, CBullsquid ); //LRC - let's get the right name... + +TYPEDESCRIPTION CBullsquid::m_SaveData[] = +{ + DEFINE_FIELD( CBullsquid, m_fCanThreatDisplay, FIELD_BOOLEAN ), + DEFINE_FIELD( CBullsquid, m_flLastHurtTime, FIELD_TIME ), + DEFINE_FIELD( CBullsquid, m_flNextSpitTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CBullsquid, CBaseMonster ); + +//========================================================= +// IgnoreConditions +//========================================================= +int CBullsquid::IgnoreConditions ( void ) +{ + int iIgnore = CBaseMonster::IgnoreConditions(); + + if ( gpGlobals->time - m_flLastHurtTime <= 20 ) + { + // haven't been hurt in 20 seconds, so let the squid care about stink. + // Er, more like, we HAVE been hurt in the last 20 seconds, so DON'T let it care about food. --LRC + iIgnore = bits_COND_SMELL | bits_COND_SMELL_FOOD; + } + + if ( m_hEnemy != NULL ) + { + if ( FClassnameIs( m_hEnemy->pev, "monster_headcrab" ) ) + { + // (Unless after a tasty headcrab) + // i.e. when chasing a headcrab, don't worry about other food. --LRC + iIgnore = bits_COND_SMELL | bits_COND_SMELL_FOOD; + } + } + + + return iIgnore; +} + +//========================================================= +// IRelationship - overridden for bullsquid so that it can +// be made to ignore its love of headcrabs for a while. +//========================================================= +int CBullsquid::IRelationship ( CBaseEntity *pTarget ) +{ + if ( gpGlobals->time - m_flLastHurtTime < 5 && FClassnameIs ( pTarget->pev, "monster_headcrab" ) ) + { + // if squid has been hurt in the last 5 seconds, and is getting relationship for a headcrab, + // tell squid to disregard crab. + return R_NO; + } + + return CBaseMonster :: IRelationship ( pTarget ); +} + +//========================================================= +// TakeDamage - overridden for bullsquid so we can keep track +// of how much time has passed since it was last injured +//========================================================= +int CBullsquid :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + float flDist; + Vector vecApex; + + // if the squid is running, has an enemy, was hurt by the enemy, hasn't been hurt in the last 3 seconds, and isn't too close to the enemy, + // it will swerve. (whew). + if ( m_hEnemy != NULL && IsMoving() && pevAttacker == m_hEnemy->pev && gpGlobals->time - m_flLastHurtTime > 3 ) + { + flDist = ( pev->origin - m_hEnemy->pev->origin ).Length2D(); + + if ( flDist > SQUID_SPRINT_DIST ) + { + flDist = ( pev->origin - m_Route[ m_iRouteIndex ].vecLocation ).Length2D();// reusing flDist. + + if ( FTriangulate( pev->origin, m_Route[ m_iRouteIndex ].vecLocation, flDist * 0.5, m_hEnemy, &vecApex ) ) + { + InsertWaypoint( vecApex, bits_MF_TO_DETOUR | bits_MF_DONT_SIMPLIFY ); + } + } + } + + if ( !FClassnameIs ( pevAttacker, "monster_headcrab" ) ) + { + // don't forget about headcrabs if it was a headcrab that hurt the squid. + m_flLastHurtTime = gpGlobals->time; + } + + return CBaseMonster :: TakeDamage ( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CBullsquid :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( IsMoving() && flDist >= 512 ) + { + // squid will far too far behind if he stops running to spit at this distance from the enemy. + return FALSE; + } + + if ( flDist > 64 && flDist <= 784 && flDot >= 0.5 && gpGlobals->time >= m_flNextSpitTime ) + { + if ( m_hEnemy != NULL ) + { + if ( fabs( pev->origin.z - m_hEnemy->pev->origin.z ) > 256 ) + { + // don't try to spit at someone up really high or down really low. + return FALSE; + } + } + + if ( IsMoving() ) + { + // don't spit again for a long time, resume chasing enemy. + m_flNextSpitTime = gpGlobals->time + 5; + } + else + { + // not moving, so spit again pretty soon. + m_flNextSpitTime = gpGlobals->time + 0.5; + } + + return TRUE; + } + + return FALSE; +} + +//========================================================= +// CheckMeleeAttack1 - bullsquid is a big guy, so has a longer +// melee range than most monsters. This is the tailwhip attack +//========================================================= +BOOL CBullsquid :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + if ( m_hEnemy->pev->health <= gSkillData.bullsquidDmgWhip && flDist <= 85 && flDot >= 0.7 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckMeleeAttack2 - bullsquid is a big guy, so has a longer +// melee range than most monsters. This is the bite attack. +// this attack will not be performed if the tailwhip attack +// is valid. +//========================================================= +BOOL CBullsquid :: CheckMeleeAttack2 ( float flDot, float flDist ) +{ + if ( flDist <= 85 && flDot >= 0.7 && !HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) ) // The player & bullsquid can be as much as their bboxes + { // apart (48 * sqrt(3)) and he can still attack (85 is a little more than 48*sqrt(3)) + return TRUE; + } + return FALSE; +} + +//========================================================= +// FValidateHintType +//========================================================= +BOOL CBullsquid :: FValidateHintType ( short sHint ) +{ + int i; + + static short sSquidHints[] = + { + HINT_WORLD_HUMAN_BLOOD, + }; + + for ( i = 0 ; i < ARRAYSIZE ( sSquidHints ) ; i++ ) + { + if ( sSquidHints[ i ] == sHint ) + { + return TRUE; + } + } + + ALERT ( at_aiconsole, "Couldn't validate hint type" ); + return FALSE; +} + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. In the base class implementation, +// monsters care about all sounds, but no scents. +//========================================================= +int CBullsquid :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_CARCASS | + bits_SOUND_MEAT | + bits_SOUND_GARBAGE | + bits_SOUND_PLAYER; +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CBullsquid :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_PREDATOR; +} + +//========================================================= +// IdleSound +//========================================================= +#define SQUID_ATTN_IDLE (float)1.5 +void CBullsquid :: IdleSound ( void ) +{ + switch ( RANDOM_LONG(0,4) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle1.wav", 1, SQUID_ATTN_IDLE ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle2.wav", 1, SQUID_ATTN_IDLE ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle3.wav", 1, SQUID_ATTN_IDLE ); + break; + case 3: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle4.wav", 1, SQUID_ATTN_IDLE ); + break; + case 4: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle5.wav", 1, SQUID_ATTN_IDLE ); + break; + } +} + +//========================================================= +// PainSound +//========================================================= +void CBullsquid :: PainSound ( void ) +{ + int iPitch = RANDOM_LONG( 85, 120 ); + + switch ( RANDOM_LONG(0,3) ) + { + case 0: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_pain1.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_pain2.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 2: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_pain3.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 3: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_pain4.wav", 1, ATTN_NORM, 0, iPitch ); + break; + } +} + +//========================================================= +// AlertSound +//========================================================= +void CBullsquid :: AlertSound ( void ) +{ + int iPitch = RANDOM_LONG( 140, 160 ); + + switch ( RANDOM_LONG ( 0, 1 ) ) + { + case 0: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle1.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle2.wav", 1, ATTN_NORM, 0, iPitch ); + break; + } +} + +//========================================================= +// MaxYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +float CBullsquid :: MaxYawSpeed( void ) +{ + float ys; + + ys = 0; + + switch ( m_Activity ) + { + case ACT_WALK: ys = 90; break; + case ACT_RUN: ys = 90; break; + case ACT_IDLE: ys = 90; break; + case ACT_RANGE_ATTACK1: ys = 90; break; + default: + ys = 90; + break; + } + + return ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CBullsquid :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case BSQUID_AE_SPIT: + { + Vector vecSpitOffset; + Vector vecSpitDir; + + UTIL_MakeVectors ( pev->angles ); + + // !!!HACKHACK - the spot at which the spit originates (in front of the mouth) was measured in 3ds and hardcoded here. + // we should be able to read the position of bones at runtime for this info. + vecSpitOffset = ( gpGlobals->v_right * 8 + gpGlobals->v_forward * 37 + gpGlobals->v_up * 23 ); + vecSpitOffset = ( pev->origin + vecSpitOffset ); + if (m_pCine) // LRC- are we being told to do this by a scripted_action? + { + if (m_hTargetEnt != NULL && m_pCine->PreciseAttack()) + vecSpitDir = ( ( m_hTargetEnt->pev->origin ) - vecSpitOffset ).Normalize(); + else + vecSpitDir = gpGlobals->v_forward; + } + else + vecSpitDir = ( ( m_hEnemy->pev->origin + m_hEnemy->pev->view_ofs ) - vecSpitOffset ).Normalize(); + + vecSpitDir.x += RANDOM_FLOAT( -0.05, 0.05 ); + vecSpitDir.y += RANDOM_FLOAT( -0.05, 0.05 ); + vecSpitDir.z += RANDOM_FLOAT( -0.05, 0 ); + + + // do stuff for this event. + AttackSound(); + + // spew the spittle temporary ents. + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpitOffset ); + WRITE_BYTE( TE_SPRITE_SPRAY ); + WRITE_COORD( vecSpitOffset.x); // pos + WRITE_COORD( vecSpitOffset.y); + WRITE_COORD( vecSpitOffset.z); + WRITE_COORD( vecSpitDir.x); // dir + WRITE_COORD( vecSpitDir.y); + WRITE_COORD( vecSpitDir.z); + WRITE_SHORT( iSquidSpitSprite ); // model + WRITE_BYTE ( 15 ); // count + WRITE_BYTE ( 210 ); // speed + WRITE_BYTE ( 25 ); // noise ( client will divide by 100 ) + MESSAGE_END(); + + CSquidSpit::Shoot( pev, vecSpitOffset, vecSpitDir * 900 ); + } + break; + + case BSQUID_AE_BITE: + { + // SOUND HERE! + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.bullsquidDmgBite, DMG_SLASH ); + + if ( pHurt ) + { + //pHurt->pev->punchangle.z = -15; + //pHurt->pev->punchangle.x = -45; + pHurt->pev->velocity = pHurt->pev->velocity - gpGlobals->v_forward * 100; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_up * 100; + } + } + break; + + case BSQUID_AE_TAILWHIP: + { + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.bullsquidDmgWhip, DMG_CLUB | DMG_ALWAYSGIB ); + if ( pHurt ) + { + pHurt->pev->punchangle.z = -20; + pHurt->pev->punchangle.x = 20; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_right * 200; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_up * 100; + } + } + break; + + case BSQUID_AE_BLINK: + { + // close eye. + pev->skin = 1; + } + break; + + case BSQUID_AE_HOP: + { + float flGravity = g_psv_gravity->value; + + // throw the squid up into the air on this frame. + if ( FBitSet ( pev->flags, FL_ONGROUND ) ) + { + pev->flags -= FL_ONGROUND; + } + + // jump into air for 0.8 (24/30) seconds +// pev->velocity.z += (0.875 * flGravity) * 0.5; + pev->velocity.z += (0.625 * flGravity) * 0.5; + } + break; + + case BSQUID_AE_THROW: + { + int iPitch; + + // squid throws its prey IF the prey is a client. + CBaseEntity *pHurt = CheckTraceHullAttack( 70, 0, 0 ); + + + if ( pHurt ) + { + // croonchy bite sound + iPitch = RANDOM_FLOAT( 90, 110 ); + switch ( RANDOM_LONG( 0, 1 ) ) + { + case 0: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "bullchicken/bc_bite2.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "bullchicken/bc_bite3.wav", 1, ATTN_NORM, 0, iPitch ); + break; + } + + + //pHurt->pev->punchangle.x = RANDOM_LONG(0,34) - 5; + //pHurt->pev->punchangle.z = RANDOM_LONG(0,49) - 25; + //pHurt->pev->punchangle.y = RANDOM_LONG(0,89) - 45; + + // screeshake transforms the viewmodel as well as the viewangle. No problems with seeing the ends of the viewmodels. + UTIL_ScreenShake( pHurt->pev->origin, 25.0, 1.5, 0.7, 2 ); + + if ( pHurt->IsPlayer() ) + { + UTIL_MakeVectors( pev->angles ); + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 300 + gpGlobals->v_up * 300; + } + } + } + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + } +} + +//========================================================= +// Spawn +//========================================================= +void CBullsquid :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/bullsquid.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, 0 ), Vector( 32, 32, 64 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + if (pev->health == 0) + pev->health = gSkillData.bullsquidHealth; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + m_fCanThreatDisplay = TRUE; + m_flNextSpitTime = gpGlobals->time; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CBullsquid :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/bullsquid.mdl"); + + PRECACHE_MODEL("sprites/bigspit.spr");// spit projectile. + + iSquidSpitSprite = PRECACHE_MODEL("sprites/tinyspit.spr");// client side spittle. + + PRECACHE_SOUND("zombie/claw_miss2.wav");// because we use the basemonster SWIPE animation event + + PRECACHE_SOUND("bullchicken/bc_attack2.wav"); + PRECACHE_SOUND("bullchicken/bc_attack3.wav"); + + PRECACHE_SOUND("bullchicken/bc_die1.wav"); + PRECACHE_SOUND("bullchicken/bc_die2.wav"); + PRECACHE_SOUND("bullchicken/bc_die3.wav"); + + PRECACHE_SOUND("bullchicken/bc_idle1.wav"); + PRECACHE_SOUND("bullchicken/bc_idle2.wav"); + PRECACHE_SOUND("bullchicken/bc_idle3.wav"); + PRECACHE_SOUND("bullchicken/bc_idle4.wav"); + PRECACHE_SOUND("bullchicken/bc_idle5.wav"); + + PRECACHE_SOUND("bullchicken/bc_pain1.wav"); + PRECACHE_SOUND("bullchicken/bc_pain2.wav"); + PRECACHE_SOUND("bullchicken/bc_pain3.wav"); + PRECACHE_SOUND("bullchicken/bc_pain4.wav"); + + PRECACHE_SOUND("bullchicken/bc_attackgrowl.wav"); + PRECACHE_SOUND("bullchicken/bc_attackgrowl2.wav"); + PRECACHE_SOUND("bullchicken/bc_attackgrowl3.wav"); + + PRECACHE_SOUND("bullchicken/bc_acid1.wav"); + + PRECACHE_SOUND("bullchicken/bc_bite2.wav"); + PRECACHE_SOUND("bullchicken/bc_bite3.wav"); + + PRECACHE_SOUND("bullchicken/bc_spithit1.wav"); + PRECACHE_SOUND("bullchicken/bc_spithit2.wav"); + +} + +//========================================================= +// DeathSound +//========================================================= +void CBullsquid :: DeathSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_die1.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_die2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_die3.wav", 1, ATTN_NORM ); + break; + } +} + +//========================================================= +// AttackSound +//========================================================= +void CBullsquid :: AttackSound ( void ) +{ + switch ( RANDOM_LONG(0,1) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "bullchicken/bc_attack2.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "bullchicken/bc_attack3.wav", 1, ATTN_NORM ); + break; + } +} + + +//======================================================== +// RunAI - overridden for bullsquid because there are things +// that need to be checked every think. +//======================================================== +void CBullsquid :: RunAI ( void ) +{ + // first, do base class stuff + CBaseMonster :: RunAI(); + + if ( pev->skin != 0 ) + { + // close eye if it was open. + pev->skin = 0; + } + + if ( RANDOM_LONG(0,39) == 0 ) + { + pev->skin = 1; + } + + if ( m_hEnemy != NULL && m_Activity == ACT_RUN ) + { + // chasing enemy. Sprint for last bit + if ( (pev->origin - m_hEnemy->pev->origin).Length2D() < SQUID_SPRINT_DIST ) + { + pev->framerate = 1.25; + } + } + +} + +//======================================================== +// AI Schedules Specific to this monster +//========================================================= + +// primary range attack +Task_t tlSquidRangeAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slSquidRangeAttack1[] = +{ + { + tlSquidRangeAttack1, + ARRAYSIZE ( tlSquidRangeAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED, + 0, + "Squid Range Attack1" + }, +}; + +// Chase enemy schedule +Task_t tlSquidChaseEnemy1[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RANGE_ATTACK1 },// !!!OEM - this will stop nasty squid oscillation. + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slSquidChaseEnemy[] = +{ + { + tlSquidChaseEnemy1, + ARRAYSIZE ( tlSquidChaseEnemy1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_SMELL_FOOD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_TASK_FAILED | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER | + bits_SOUND_MEAT, + "Squid Chase Enemy" + }, +}; + +Task_t tlSquidHurtHop[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SOUND_WAKE, (float)0 }, + { TASK_SQUID_HOPTURN, (float)0 }, + { TASK_FACE_ENEMY, (float)0 },// in case squid didn't turn all the way in the air. +}; + +Schedule_t slSquidHurtHop[] = +{ + { + tlSquidHurtHop, + ARRAYSIZE ( tlSquidHurtHop ), + 0, + 0, + "SquidHurtHop" + } +}; + +Task_t tlSquidSeeCrab[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SOUND_WAKE, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_EXCITED }, + { TASK_FACE_ENEMY, (float)0 }, +}; + +Schedule_t slSquidSeeCrab[] = +{ + { + tlSquidSeeCrab, + ARRAYSIZE ( tlSquidSeeCrab ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "SquidSeeCrab" + } +}; + +// squid walks to something tasty and eats it. +Task_t tlSquidEat[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_EAT, (float)10 },// this is in case the squid can't get to the food + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_GET_PATH_TO_BESTSCENT, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_EAT }, + { TASK_PLAY_SEQUENCE, (float)ACT_EAT }, + { TASK_PLAY_SEQUENCE, (float)ACT_EAT }, + { TASK_EAT, (float)50 }, + { TASK_GET_PATH_TO_LASTPOSITION,(float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_CLEAR_LASTPOSITION, (float)0 }, +}; + +Schedule_t slSquidEat[] = +{ + { + tlSquidEat, + ARRAYSIZE( tlSquidEat ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_NEW_ENEMY , + + // even though HEAR_SOUND/SMELL FOOD doesn't break this schedule, we need this mask + // here or the monster won't detect these sounds at ALL while running this schedule. + bits_SOUND_MEAT | + bits_SOUND_CARCASS, + "SquidEat" + } +}; + +// this is a bit different than just Eat. We use this schedule when the food is far away, occluded, or behind +// the squid. This schedule plays a sniff animation before going to the source of food. +Task_t tlSquidSniffAndEat[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_EAT, (float)10 },// this is in case the squid can't get to the food + { TASK_PLAY_SEQUENCE, (float)ACT_DETECT_SCENT }, + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_GET_PATH_TO_BESTSCENT, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_EAT }, + { TASK_PLAY_SEQUENCE, (float)ACT_EAT }, + { TASK_PLAY_SEQUENCE, (float)ACT_EAT }, + { TASK_EAT, (float)50 }, + { TASK_GET_PATH_TO_LASTPOSITION,(float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_CLEAR_LASTPOSITION, (float)0 }, +}; + +Schedule_t slSquidSniffAndEat[] = +{ + { + tlSquidSniffAndEat, + ARRAYSIZE( tlSquidSniffAndEat ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_NEW_ENEMY , + + // even though HEAR_SOUND/SMELL FOOD doesn't break this schedule, we need this mask + // here or the monster won't detect these sounds at ALL while running this schedule. + bits_SOUND_MEAT | + bits_SOUND_CARCASS, + "SquidSniffAndEat" + } +}; + +// squid does this to stinky things. +Task_t tlSquidWallow[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_EAT, (float)10 },// this is in case the squid can't get to the stinkiness + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_GET_PATH_TO_BESTSCENT, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_INSPECT_FLOOR}, + { TASK_EAT, (float)50 },// keeps squid from eating or sniffing anything else for a while. + { TASK_GET_PATH_TO_LASTPOSITION,(float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_CLEAR_LASTPOSITION, (float)0 }, +}; + +Schedule_t slSquidWallow[] = +{ + { + tlSquidWallow, + ARRAYSIZE( tlSquidWallow ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_NEW_ENEMY , + + // even though HEAR_SOUND/SMELL FOOD doesn't break this schedule, we need this mask + // here or the monster won't detect these sounds at ALL while running this schedule. + bits_SOUND_GARBAGE, + + "SquidWallow" + } +}; + +DEFINE_CUSTOM_SCHEDULES( CBullsquid ) +{ + slSquidRangeAttack1, + slSquidChaseEnemy, + slSquidHurtHop, + slSquidSeeCrab, + slSquidEat, + slSquidSniffAndEat, + slSquidWallow +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CBullsquid, CBaseMonster ); + +//========================================================= +// GetSchedule +//========================================================= +Schedule_t *CBullsquid :: GetSchedule( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_ALERT: + { + if ( HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE) ) + { + return GetScheduleOfType ( SCHED_SQUID_HURTHOP ); + } + + if ( HasConditions(bits_COND_SMELL_FOOD) ) + { + CSound *pSound; + + pSound = PBestScent(); + + if ( pSound && (!FInViewCone ( &pSound->m_vecOrigin ) || !FVisible ( pSound->m_vecOrigin )) ) + { + // scent is behind or occluded + return GetScheduleOfType( SCHED_SQUID_SNIFF_AND_EAT ); + } + + // food is right out in the open. Just go get it. + return GetScheduleOfType( SCHED_SQUID_EAT ); + } + + if ( HasConditions(bits_COND_SMELL) ) + { + // there's something stinky. + CSound *pSound; + + pSound = PBestScent(); + if ( pSound ) + return GetScheduleOfType( SCHED_SQUID_WALLOW); + } + + break; + } + case MONSTERSTATE_COMBAT: + { +// dead enemy + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + + if ( HasConditions(bits_COND_NEW_ENEMY) ) + { + if ( m_fCanThreatDisplay && IRelationship( m_hEnemy ) == R_HT ) + { + // this means squid sees a headcrab! + m_fCanThreatDisplay = FALSE;// only do the headcrab dance once per lifetime. + return GetScheduleOfType ( SCHED_SQUID_SEECRAB ); + } + else + { + return GetScheduleOfType ( SCHED_WAKE_ANGRY ); + } + } + + if ( HasConditions(bits_COND_SMELL_FOOD) ) + { + CSound *pSound; + + pSound = PBestScent(); + + if ( pSound && (!FInViewCone ( &pSound->m_vecOrigin ) || !FVisible ( pSound->m_vecOrigin )) ) + { + // scent is behind or occluded + return GetScheduleOfType( SCHED_SQUID_SNIFF_AND_EAT ); + } + + // food is right out in the open. Just go get it. + return GetScheduleOfType( SCHED_SQUID_EAT ); + } + + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_RANGE_ATTACK1 ); + } + + if ( HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_MELEE_ATTACK1 ); + } + + if ( HasConditions( bits_COND_CAN_MELEE_ATTACK2 ) ) + { + return GetScheduleOfType ( SCHED_MELEE_ATTACK2 ); + } + + return GetScheduleOfType ( SCHED_CHASE_ENEMY ); + + break; + } + } + + return CBaseMonster :: GetSchedule(); +} + +//========================================================= +// GetScheduleOfType +//========================================================= +Schedule_t* CBullsquid :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_RANGE_ATTACK1: + return &slSquidRangeAttack1[ 0 ]; + break; + case SCHED_SQUID_HURTHOP: + return &slSquidHurtHop[ 0 ]; + break; + case SCHED_SQUID_SEECRAB: + return &slSquidSeeCrab[ 0 ]; + break; + case SCHED_SQUID_EAT: + return &slSquidEat[ 0 ]; + break; + case SCHED_SQUID_SNIFF_AND_EAT: + return &slSquidSniffAndEat[ 0 ]; + break; + case SCHED_SQUID_WALLOW: + return &slSquidWallow[ 0 ]; + break; + case SCHED_CHASE_ENEMY: + return &slSquidChaseEnemy[ 0 ]; + break; + } + + return CBaseMonster :: GetScheduleOfType ( Type ); +} + +//========================================================= +// Start task - selects the correct activity and performs +// any necessary calculations to start the next task on the +// schedule. OVERRIDDEN for bullsquid because it needs to +// know explicitly when the last attempt to chase the enemy +// failed, since that impacts its attack choices. +//========================================================= +void CBullsquid :: StartTask ( Task_t *pTask ) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch ( pTask->iTask ) + { + case TASK_MELEE_ATTACK2: + { + switch ( RANDOM_LONG ( 0, 2 ) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_attackgrowl.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_attackgrowl2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_attackgrowl3.wav", 1, ATTN_NORM ); + break; + } + + CBaseMonster :: StartTask ( pTask ); + break; + } + case TASK_SQUID_HOPTURN: + { + SetActivity ( ACT_HOP ); + SetIdealYawToTargetAndUpdate( m_vecEnemyLKP ); + break; + } + case TASK_GET_PATH_TO_ENEMY: + { + if ( BuildRoute ( m_hEnemy->pev->origin, bits_MF_TO_ENEMY, m_hEnemy ) ) + { + m_iTaskStatus = TASKSTATUS_COMPLETE; + } + else + { + ALERT ( at_aiconsole, "GetPathToEnemy failed!!\n" ); + TaskFail(); + } + break; + } + default: + { + CBaseMonster :: StartTask ( pTask ); + break; + } + } +} + +//========================================================= +// RunTask +//========================================================= +void CBullsquid :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_SQUID_HOPTURN: + { + SetIdealYawToTargetAndUpdate( m_vecEnemyLKP , AI_KEEP_YAW_SPEED ); + + if ( m_fSequenceFinished ) + { + m_iTaskStatus = TASKSTATUS_COMPLETE; + } + break; + } + default: + { + CBaseMonster :: RunTask( pTask ); + break; + } + } +} + + +//========================================================= +// GetIdealState - Overridden for Bullsquid to deal with +// the feature that makes it lose interest in headcrabs for +// a while if something injures it. +//========================================================= +MONSTERSTATE CBullsquid :: GetIdealState ( void ) +{ + int iConditions; + + iConditions = IScheduleFlags(); + + // If no schedule conditions, the new ideal state is probably the reason we're in here. + switch ( m_MonsterState ) + { + case MONSTERSTATE_COMBAT: + /* + COMBAT goes to ALERT upon death of enemy + */ + { + if ( m_hEnemy != NULL && ( iConditions & bits_COND_LIGHT_DAMAGE || iConditions & bits_COND_HEAVY_DAMAGE ) && FClassnameIs( m_hEnemy->pev, "monster_headcrab" ) ) + { + // if the squid has a headcrab enemy and something hurts it, it's going to forget about the crab for a while. + m_hEnemy = NULL; + m_IdealMonsterState = MONSTERSTATE_ALERT; + } + break; + } + } + + m_IdealMonsterState = CBaseMonster :: GetIdealState(); + + return m_IdealMonsterState; +} + diff --git a/dlls/buttons.cpp b/dlls/buttons.cpp new file mode 100644 index 0000000..a221802 --- /dev/null +++ b/dlls/buttons.cpp @@ -0,0 +1,1716 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== buttons.cpp ======================================================== + + button-related code + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "doors.h" + + +#define SF_BUTTON_DONTMOVE 1 +#define SF_ROTBUTTON_NOTSOLID 1 +#define SF_BUTTON_ONLYDIRECT 16 //LRC - button can't be used through walls. +#define SF_BUTTON_TOGGLE 32 // button stays pushed until reactivated +#define SF_BUTTON_SPARK_IF_OFF 64 // button sparks in OFF state +#define SF_BUTTON_NOT_SOLID 128 // button isn't solid +#define SF_BUTTON_TOUCH_ONLY 256 // button must be touched to be used. +#define SF_BUTTON_USEKEY 512 // change the reaction of the button to the USE key. + // (i.e. if it's meant to be ignored, don't ignore it; otherwise ignore it.) + +#define SF_GLOBAL_SET 1 // Set global state to initial state on spawn + +class CEnvGlobal : public CPointEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + string_t m_globalstate; + int m_triggermode; + int m_initialstate; +}; + +TYPEDESCRIPTION CEnvGlobal::m_SaveData[] = +{ + DEFINE_FIELD( CEnvGlobal, m_globalstate, FIELD_STRING ), + DEFINE_FIELD( CEnvGlobal, m_triggermode, FIELD_INTEGER ), + DEFINE_FIELD( CEnvGlobal, m_initialstate, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CEnvGlobal, CBaseEntity ); + +LINK_ENTITY_TO_CLASS( env_global, CEnvGlobal ); + +void CEnvGlobal::KeyValue( KeyValueData *pkvd ) +{ + pkvd->fHandled = TRUE; + + if ( FStrEq(pkvd->szKeyName, "globalstate") ) // State name + m_globalstate = ALLOC_STRING( pkvd->szValue ); + else if ( FStrEq(pkvd->szKeyName, "triggermode") ) + m_triggermode = atoi( pkvd->szValue ); + else if ( FStrEq(pkvd->szKeyName, "initialstate") ) + m_initialstate = atoi( pkvd->szValue ); + else + CPointEntity::KeyValue( pkvd ); +} + +void CEnvGlobal::Spawn( void ) +{ + if ( !m_globalstate ) + { + REMOVE_ENTITY( ENT(pev) ); + return; + } + if ( FBitSet( pev->spawnflags, SF_GLOBAL_SET ) ) + { + if ( !gGlobalState.EntityInTable( m_globalstate ) ) + gGlobalState.EntityAdd( m_globalstate, gpGlobals->mapname, (GLOBALESTATE)m_initialstate ); + } +} + + +void CEnvGlobal::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + GLOBALESTATE oldState = gGlobalState.EntityGetState( m_globalstate ); + GLOBALESTATE newState; + + if( useType == USE_ON ) + { + newState = GLOBAL_ON; + } + else if( useType == USE_OFF ) + { + newState = GLOBAL_OFF; + } + else + { + switch( m_triggermode ) + { + case 0: + newState = GLOBAL_OFF; + break; + + case 1: + newState = GLOBAL_ON; + break; + + case 2: + newState = GLOBAL_DEAD; + break; + + default: + case 3: + if ( oldState == GLOBAL_ON ) + newState = GLOBAL_OFF; + else if ( oldState == GLOBAL_OFF ) + newState = GLOBAL_ON; + else + newState = oldState; + } + } + + if ( gGlobalState.EntityInTable( m_globalstate ) ) + gGlobalState.EntitySetState( m_globalstate, newState ); + else + gGlobalState.EntityAdd( m_globalstate, gpGlobals->mapname, newState ); +} + + +//================================================== +//LRC- a simple entity, just maintains a state +//================================================== + +#define SF_ENVSTATE_START_ON 1 +#define SF_ENVSTATE_DEBUG 2 + +class CEnvState : public CPointEntity +{ +public: + void Spawn( void ); + void Think( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + BOOL IsLockedByMaster( void ) { return !UTIL_IsMasterTriggered(m_sMaster, NULL); }; + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + virtual STATE GetState() { return m_iState; } + + static TYPEDESCRIPTION m_SaveData[]; + + STATE m_iState; + float m_fTurnOnTime; + float m_fTurnOffTime; + int m_sMaster; +}; + +void CEnvState::Spawn( void ) +{ + if (pev->spawnflags & SF_ENVSTATE_START_ON) + m_iState = STATE_ON; + else + m_iState = STATE_OFF; +} + +TYPEDESCRIPTION CEnvState::m_SaveData[] = +{ + DEFINE_FIELD( CEnvState, m_iState, FIELD_INTEGER ), + DEFINE_FIELD( CEnvState, m_fTurnOnTime, FIELD_INTEGER ), + DEFINE_FIELD( CEnvState, m_fTurnOffTime, FIELD_INTEGER ), + DEFINE_FIELD( CEnvState, m_sMaster, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CEnvState, CPointEntity ); + +LINK_ENTITY_TO_CLASS( env_state, CEnvState ); + +void CEnvState::KeyValue( KeyValueData *pkvd ) +{ + pkvd->fHandled = TRUE; + + if ( FStrEq(pkvd->szKeyName, "turnontime") ) + m_fTurnOnTime = atof( pkvd->szValue ); + else if ( FStrEq(pkvd->szKeyName, "turnofftime") ) + m_fTurnOffTime = atof( pkvd->szValue ); + else if ( FStrEq(pkvd->szKeyName, "master") ) + m_sMaster = ALLOC_STRING( pkvd->szValue ); + else + CPointEntity::KeyValue( pkvd ); +} + +void CEnvState::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (!ShouldToggle(useType) || IsLockedByMaster()) + { + if (pev->spawnflags & SF_ENVSTATE_DEBUG) + { + ALERT(at_debug,"DEBUG: env_state \"%s\" ",STRING(pev->targetname)); + if (IsLockedByMaster()) + ALERT(at_debug,"ignored trigger %s; locked by master \"%s\".\n",GetStringForUseType(useType),STRING(m_sMaster)); + else if (useType == USE_ON) + ALERT(at_debug,"ignored trigger USE_ON; already on\n"); + else if (useType == USE_OFF) + ALERT(at_debug,"ignored trigger USE_OFF; already off\n"); + else + ALERT(at_debug,"ignored trigger %s.\n",GetStringForUseType(useType)); + } + return; + } + + switch (GetState()) + { + case STATE_ON: + case STATE_TURN_ON: + if (m_fTurnOffTime) + { + m_iState = STATE_TURN_OFF; + if (pev->spawnflags & SF_ENVSTATE_DEBUG) + { + ALERT(at_debug,"DEBUG: env_state \"%s\" triggered; will turn off in %f seconds.\n", STRING(pev->targetname), m_fTurnOffTime); + } + SetNextThink( m_fTurnOffTime ); + } + else + { + m_iState = STATE_OFF; + if (pev->spawnflags & SF_ENVSTATE_DEBUG) + { + ALERT(at_debug,"DEBUG: env_state \"%s\" triggered, turned off", STRING(pev->targetname)); + if (pev->target) + { + ALERT(at_debug,": firing \"%s\"",STRING(pev->target)); + if (pev->noise2) + ALERT(at_debug," and \"%s\"",STRING(pev->noise2)); + } + else if (pev->noise2) + ALERT(at_debug,": firing \"%s\"",STRING(pev->noise2)); + ALERT(at_debug,".\n"); + } + FireTargets(STRING(pev->target),pActivator,this,USE_OFF,0); + FireTargets(STRING(pev->noise2),pActivator,this,USE_TOGGLE,0); + DontThink(); + } + break; + case STATE_OFF: + case STATE_TURN_OFF: + if (m_fTurnOnTime) + { + m_iState = STATE_TURN_ON; + if (pev->spawnflags & SF_ENVSTATE_DEBUG) + { + ALERT(at_debug,"DEBUG: env_state \"%s\" triggered; will turn on in %f seconds.\n", STRING(pev->targetname), m_fTurnOnTime); + } + SetNextThink( m_fTurnOnTime ); + } + else + { + m_iState = STATE_ON; + if (pev->spawnflags & SF_ENVSTATE_DEBUG) + { + ALERT(at_debug,"DEBUG: env_state \"%s\" triggered, turned on",STRING(pev->targetname)); + if (pev->target) + { + ALERT(at_debug,": firing \"%s\"",STRING(pev->target)); + if (pev->noise1) + ALERT(at_debug," and \"%s\"",STRING(pev->noise1)); + } + else if (pev->noise1) + ALERT(at_debug,": firing \"%s\"", STRING(pev->noise1)); + ALERT(at_debug,".\n"); + } + FireTargets(STRING(pev->target),pActivator,this,USE_ON,0); + FireTargets(STRING(pev->noise1),pActivator,this,USE_TOGGLE,0); + DontThink(); + } + break; + } +} + +void CEnvState::Think( void ) +{ + if (m_iState == STATE_TURN_ON) + { + m_iState = STATE_ON; + if (pev->spawnflags & SF_ENVSTATE_DEBUG) + { + ALERT(at_debug,"DEBUG: env_state \"%s\" turned itself on",STRING(pev->targetname)); + if (pev->target) + { + ALERT(at_debug,": firing %s",STRING(pev->target)); + if (pev->noise1) + ALERT(at_debug," and %s",STRING(pev->noise1)); + } + else if (pev->noise1) + ALERT(at_debug,": firing %s",STRING(pev->noise1)); + ALERT(at_debug,".\n"); + } + FireTargets(STRING(pev->target),this,this,USE_ON,0); + FireTargets(STRING(pev->noise1),this,this,USE_TOGGLE,0); + } + else if (m_iState == STATE_TURN_OFF) + { + m_iState = STATE_OFF; + if (pev->spawnflags & SF_ENVSTATE_DEBUG) + { + ALERT(at_debug,"DEBUG: env_state \"%s\" turned itself off",STRING(pev->targetname)); + if (pev->target) + ALERT(at_debug,": firing %s",STRING(pev->target)); + if (pev->noise2) + ALERT(at_debug," and %s",STRING(pev->noise2)); + else if (pev->noise2) + ALERT(at_debug,": firing %s",STRING(pev->noise2)); + ALERT(at_debug,".\n"); + } + FireTargets(STRING(pev->target),this,this,USE_OFF,0); + FireTargets(STRING(pev->noise2),this,this,USE_TOGGLE,0); + } +} + + +//=========================== +//LRC- the evil multisource... +//=========================== + +TYPEDESCRIPTION CMultiSource::m_SaveData[] = +{ + //!!!BUGBUG FIX + DEFINE_ARRAY( CMultiSource, m_rgEntities, FIELD_EHANDLE, MS_MAX_TARGETS ), + DEFINE_ARRAY( CMultiSource, m_rgTriggered, FIELD_INTEGER, MS_MAX_TARGETS ), + DEFINE_FIELD( CMultiSource, m_iTotal, FIELD_INTEGER ), + DEFINE_FIELD( CMultiSource, m_globalstate, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CMultiSource, CBaseEntity ); + +LINK_ENTITY_TO_CLASS( multisource, CMultiSource ); +// +// Cache user-entity-field values until spawn is called. +// + +void CMultiSource::KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "style") || + FStrEq(pkvd->szKeyName, "height") || + FStrEq(pkvd->szKeyName, "killtarget") || + FStrEq(pkvd->szKeyName, "value1") || + FStrEq(pkvd->szKeyName, "value2") || + FStrEq(pkvd->szKeyName, "value3")) + pkvd->fHandled = TRUE; + else if ( FStrEq(pkvd->szKeyName, "globalstate") ) + { + m_globalstate = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +#define SF_MULTI_FIREONCLOSE 1 +#define SF_MULTI_INIT 2 + +void CMultiSource::Spawn() +{ + // set up think for later registration + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + SetNextThink( 0.1 ); + pev->spawnflags |= SF_MULTI_INIT; // Until it's initialized + SetThink(&CMultiSource::Register); +} + +void CMultiSource::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int i = 0; + + // Find the entity in our list + while (i < m_iTotal) + if ( m_rgEntities[i++] == pCaller ) + break; + + // if we didn't find it, report error and leave + if (i > m_iTotal) + { + if (pCaller->pev->targetname) + ALERT(at_debug, "multisource \"%s\": Used by non-member %s \"%s\"\n", STRING(pev->targetname), STRING(pCaller->pev->classname), STRING(pCaller->pev->targetname)); + else + ALERT(at_debug, "multisource \"%s\": Used by non-member %s\n", STRING(pev->targetname), STRING(pCaller->pev->classname)); + return; + } + + // CONSIDER: a Use input to the multisource always toggles. Could check useType for ON/OFF/TOGGLE + // LRC- On could be meaningful. Off, sadly, can't work in the obvious manner. + // LRC (09/06/01)- er... why not? + // LRC (28/04/02)- that depends what the "obvious" manner is. + + // store the state before the change, so we can compare it to the new state + STATE s = GetState(); + + // do the change + m_rgTriggered[i-1] ^= 1; + + // did we change state? + if ( s == GetState() ) + return; + + if ( s == STATE_ON && pev->netname) + { + // the change disabled me and I have a "fire on disable" field + ALERT( at_aiconsole, "Multisource %s deactivated (%d inputs)\n", STRING(pev->targetname), m_iTotal ); + if ( m_globalstate ) + FireTargets( STRING(pev->netname), NULL, this, USE_OFF, 0 ); + else + FireTargets( STRING(pev->netname), NULL, this, USE_TOGGLE, 0 ); + } + else if ( s == STATE_OFF ) + { + // the change activated me + ALERT( at_aiconsole, "Multisource %s enabled (%d inputs)\n", STRING(pev->targetname), m_iTotal ); + USE_TYPE useType = USE_TOGGLE; + if ( m_globalstate ) + useType = USE_ON; + SUB_UseTargets( NULL, useType, 0 ); + } +} + + +//LRC- while we're in STATE_OFF, mastered entities can't do anything. +STATE CMultiSource::GetState( void ) +{ + // Is everything triggered? + int i = 0; + + // Still initializing? + if ( pev->spawnflags & SF_MULTI_INIT ) + return STATE_OFF; + + while (i < m_iTotal) + { + if (m_rgTriggered[i] == 0) + break; + i++; + } + + if (i == m_iTotal) + { + if ( !m_globalstate || gGlobalState.EntityGetState( m_globalstate ) == GLOBAL_ON ) + return STATE_ON; + } + + return STATE_OFF; +} +/* +void CMultiSource::Register(void) +{ + edict_t *pentTarget = NULL; + + m_iTotal = 0; + memset( m_rgEntities, 0, MS_MAX_TARGETS * sizeof(EHANDLE) ); + + SetThink(&CMultiSource::SUB_DoNothing); + + // search for all entities which target this multisource (pev->targetname) + + pentTarget = FIND_ENTITY_BY_STRING(NULL, "target", STRING(pev->targetname)); + + while (!FNullEnt(pentTarget) && (m_iTotal < MS_MAX_TARGETS)) + { + CBaseEntity *pTarget = CBaseEntity::Instance(pentTarget); + if ( pTarget ) + m_rgEntities[m_iTotal++] = pTarget; + + pentTarget = FIND_ENTITY_BY_STRING( pentTarget, "target", STRING(pev->targetname)); + } + + pentTarget = FIND_ENTITY_BY_STRING(NULL, "classname", "multi_manager"); + while (!FNullEnt(pentTarget) && (m_iTotal < MS_MAX_TARGETS)) + { + CBaseEntity *pTarget = CBaseEntity::Instance(pentTarget); + if ( pTarget && pTarget->HasTarget(pev->targetname) ) + m_rgEntities[m_iTotal++] = pTarget; + + pentTarget = FIND_ENTITY_BY_STRING( pentTarget, "classname", "multi_manager" ); + } + + pev->spawnflags &= ~SF_MULTI_INIT; +} +*/ +void CMultiSource::Register(void) +{ + m_iTotal = 0; + memset( m_rgEntities, 0, MS_MAX_TARGETS * sizeof(EHANDLE) ); + + SetThink(&CMultiSource::SUB_DoNothing); + + // search for all entities which target this multisource (pev->targetname) + + CBaseEntity *pTarget = UTIL_FindEntityByTarget( NULL, STRING(pev->targetname) ); + while (pTarget && (m_iTotal < MS_MAX_TARGETS)) + { + m_rgEntities[m_iTotal++] = pTarget; + + pTarget = UTIL_FindEntityByTarget( pTarget, STRING(pev->targetname)); + } + + pTarget = UTIL_FindEntityByClassname(NULL, "multi_manager"); + while (pTarget && (m_iTotal < MS_MAX_TARGETS)) + { + if ( pTarget->HasTarget(pev->targetname) ) + m_rgEntities[m_iTotal++] = pTarget; + + pTarget = UTIL_FindEntityByClassname( pTarget, "multi_manager" ); + } + + if (m_iTotal >= MS_MAX_TARGETS) + { + ALERT(at_debug,"WARNING: There are too many entities targetting multisource \"%s\". (limit is %d)\n", STRING(pev->targetname), MS_MAX_TARGETS); + } + + pev->spawnflags &= ~SF_MULTI_INIT; +} + +//=================================== +// func_button (= CBaseButton) +//=================================== + +//LRC - moved here from cbase.h to use the spawnflags defined in this file +// Buttons that don't take damage can be IMPULSE used +int CBaseButton::ObjectCaps( void ) +{ + return (CBaseToggle:: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | + (pev->takedamage?0:FCAP_IMPULSE_USE) | + (pev->spawnflags & SF_BUTTON_ONLYDIRECT?FCAP_ONLYDIRECT_USE:0) | + (m_hide_use?FCAP_HIDE_USE:0); // Wargon: Èêîíêà þçà íå áóäåò îòîáðàæàòüñÿ íà ýòîé ýíòèòè åñëè m_hide_use = 1. +} + +TYPEDESCRIPTION CBaseButton::m_SaveData[] = +{ + DEFINE_FIELD( CBaseButton, m_fStayPushed, FIELD_BOOLEAN ), + DEFINE_FIELD( CBaseButton, m_fRotating, FIELD_BOOLEAN ), + + // Wargon: Ñîõðàíåíèå m_hide_use. + DEFINE_FIELD( CBaseButton, m_hide_use, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseButton, m_sounds, FIELD_INTEGER ), + DEFINE_FIELD( CBaseButton, m_bLockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_bLockedSentence, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_bUnlockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_bUnlockedSentence, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseButton, m_strChangeTarget, FIELD_STRING ), +// DEFINE_FIELD( CBaseButton, m_ls, FIELD_??? ), // This is restored in Precache() +}; + + +IMPLEMENT_SAVERESTORE( CBaseButton, CBaseToggle ); + +void CBaseButton::Precache( void ) +{ + char *pszSound; + + if ( FBitSet ( pev->spawnflags, SF_BUTTON_SPARK_IF_OFF ) )// this button should spark in OFF state + { + PRECACHE_SOUND ("buttons/spark1.wav"); + PRECACHE_SOUND ("buttons/spark2.wav"); + PRECACHE_SOUND ("buttons/spark3.wav"); + PRECACHE_SOUND ("buttons/spark4.wav"); + PRECACHE_SOUND ("buttons/spark5.wav"); + PRECACHE_SOUND ("buttons/spark6.wav"); + } + + // get door button sounds, for doors which require buttons to open + + if (m_bLockedSound) + { + pszSound = ButtonSound( (int)m_bLockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sLockedSound = ALLOC_STRING(pszSound); + } + + if (m_bUnlockedSound) + { + pszSound = ButtonSound( (int)m_bUnlockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sUnlockedSound = ALLOC_STRING(pszSound); + } + + // get sentence group names, for doors which are directly 'touched' to open + + switch (m_bLockedSentence) + { + case 1: m_ls.sLockedSentence = MAKE_STRING("NA"); break; // access denied + case 2: m_ls.sLockedSentence = MAKE_STRING("ND"); break; // security lockout + case 3: m_ls.sLockedSentence = MAKE_STRING("NF"); break; // blast door + case 4: m_ls.sLockedSentence = MAKE_STRING("NFIRE"); break; // fire door + case 5: m_ls.sLockedSentence = MAKE_STRING("NCHEM"); break; // chemical door + case 6: m_ls.sLockedSentence = MAKE_STRING("NRAD"); break; // radiation door + case 7: m_ls.sLockedSentence = MAKE_STRING("NCON"); break; // gen containment + case 8: m_ls.sLockedSentence = MAKE_STRING("NH"); break; // maintenance door + case 9: m_ls.sLockedSentence = MAKE_STRING("NG"); break; // broken door + + default: m_ls.sLockedSentence = 0; break; + } + + switch (m_bUnlockedSentence) + { + case 1: m_ls.sUnlockedSentence = MAKE_STRING("EA"); break; // access granted + case 2: m_ls.sUnlockedSentence = MAKE_STRING("ED"); break; // security door + case 3: m_ls.sUnlockedSentence = MAKE_STRING("EF"); break; // blast door + case 4: m_ls.sUnlockedSentence = MAKE_STRING("EFIRE"); break; // fire door + case 5: m_ls.sUnlockedSentence = MAKE_STRING("ECHEM"); break; // chemical door + case 6: m_ls.sUnlockedSentence = MAKE_STRING("ERAD"); break; // radiation door + case 7: m_ls.sUnlockedSentence = MAKE_STRING("ECON"); break; // gen containment + case 8: m_ls.sUnlockedSentence = MAKE_STRING("EH"); break; // maintenance door + + default: m_ls.sUnlockedSentence = 0; break; + } +} + +// +// Cache user-entity-field values until spawn is called. +// + +void CBaseButton::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "changetarget")) + { + m_strChangeTarget = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sound")) + { + m_bLockedSound = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sentence")) + { + m_bLockedSentence = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sound")) + { + m_bUnlockedSound = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sentence")) + { + m_bUnlockedSentence = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + // Wargon: hide_use èñïîëüçóåòñÿ äëÿ ñêðûòèÿ èêîíêè þçà. + else if (FStrEq(pkvd->szKeyName, "hide_use")) + { + m_hide_use = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +// +// ButtonShot +// +int CBaseButton::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + BUTTON_CODE code = ButtonResponseToTouch(); + + if ( code == BUTTON_NOTHING ) + return 0; + // Temporarily disable the touch function, until movement is finished. + SetTouch( NULL ); + + m_hActivator = CBaseEntity::Instance( pevAttacker ); + if ( m_hActivator == NULL ) + return 0; + + if ( code == BUTTON_RETURN ) + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + // Toggle buttons fire when they get back to their "home" position + if ( !(pev->spawnflags & SF_BUTTON_TOGGLE) ) + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + ButtonReturn(); + } + else // code == BUTTON_ACTIVATE + ButtonActivate( ); + + return 0; +} + +/*QUAKED func_button (0 .5 .8) ? +When a button is touched, it moves some distance in the direction of it's angle, +triggers all of it's targets, waits some time, then returns to it's original position +where it can be triggered again. + +"angle" determines the opening direction +"target" all entities with a matching targetname will be used +"speed" override the default 40 speed +"wait" override the default 1 second wait (-1 = never return) +"lip" override the default 4 pixel lip remaining at end of move +"health" if set, the button must be killed instead of touched +"sounds" +0) steam metal +1) wooden clunk +2) metallic click +3) in-out +*/ +LINK_ENTITY_TO_CLASS( func_button, CBaseButton ); + + +void CBaseButton::Spawn( ) +{ + char *pszSound; + + //---------------------------------------------------- + //determine sounds for buttons + //a sound of 0 should not make a sound + //---------------------------------------------------- + pszSound = ButtonSound( m_sounds ); + PRECACHE_SOUND(pszSound); + pev->noise = ALLOC_STRING(pszSound); + + Precache(); + + if ( FBitSet ( pev->spawnflags, SF_BUTTON_SPARK_IF_OFF ) )// this button should spark in OFF state + { + SetThink(&CBaseButton:: ButtonSpark ); + SetNextThink( 0.5 );// no hurry, make sure everything else spawns + } + + SetMovedir(pev); + + pev->movetype = MOVETYPE_PUSH; + if ( FBitSet ( pev->spawnflags, SF_BUTTON_NOT_SOLID ) ) + { + pev->solid = SOLID_NOT; + pev->skin = CONTENTS_EMPTY; + } + else + { + pev->solid = SOLID_BSP; + } + SET_MODEL(ENT(pev), STRING(pev->model)); + + //LRC +// if (m_iStyle >= 32) LIGHT_STYLE(m_iStyle, "z"); buz +// else if (m_iStyle <= -32) LIGHT_STYLE(-m_iStyle, "a"); + + if (pev->speed == 0) + pev->speed = 40; + + if (pev->health > 0) + { + pev->takedamage = DAMAGE_YES; + } + + if (m_flWait == 0) + m_flWait = 1; + if (m_flLip == 0) + m_flLip = 4; + + m_toggle_state = TS_AT_BOTTOM; + m_vecPosition1 = pev->origin; + // Subtract 2 from size because the engine expands bboxes by 1 in all directions making the size too big + m_vecPosition2 = m_vecPosition1 + (pev->movedir * (fabs( pev->movedir.x * (pev->size.x-2) ) + fabs( pev->movedir.y * (pev->size.y-2) ) + fabs( pev->movedir.z * (pev->size.z-2) ) - m_flLip)); + + + // Is this a non-moving button? + if ( ((m_vecPosition2 - m_vecPosition1).Length() < 1) || (pev->spawnflags & SF_BUTTON_DONTMOVE) ) + m_vecPosition2 = m_vecPosition1; + + m_fStayPushed = (m_flWait == -1 ? TRUE : FALSE); + m_fRotating = FALSE; + + // if the button is flagged for USE button activation only, take away it's touch function and add a use function + + if ( FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) // touchable button + { + SetTouch(&CBaseButton:: ButtonTouch ); + if ( !FBitSet ( pev->spawnflags, SF_BUTTON_USEKEY ) ) + SetUse(&CBaseButton:: ButtonUse_IgnorePlayer ); + else + SetUse(&CBaseButton:: ButtonUse ); + } + else + { + SetTouch ( NULL ); + if ( FBitSet ( pev->spawnflags, SF_BUTTON_USEKEY ) ) + SetUse(&CBaseButton:: ButtonUse_IgnorePlayer ); + else + SetUse(&CBaseButton:: ButtonUse ); + } +} + +//LRC +void CBaseButton :: PostSpawn( void ) +{ + if (m_pMoveWith) + m_vecPosition1 = pev->origin - m_pMoveWith->pev->origin; + else + m_vecPosition1 = pev->origin; + // Subtract 2 from size because the engine expands bboxes by 1 in all directions + m_vecPosition2 = m_vecPosition1 + (pev->movedir * (fabs( pev->movedir.x * (pev->size.x-2) ) + fabs( pev->movedir.y * (pev->size.y-2) ) + fabs( pev->movedir.z * (pev->size.z-2) ) - m_flLip)); + + // Is this a non-moving button? + if ( ((m_vecPosition2 - m_vecPosition1).Length() < 1) || (pev->spawnflags & SF_BUTTON_DONTMOVE) ) + m_vecPosition2 = m_vecPosition1; +} + +// Button sound table. +// Also used by CBaseDoor to get 'touched' door lock/unlock sounds + +char *ButtonSound( int sound ) +{ + char *pszSound; + + switch ( sound ) + { + case 0: pszSound = "common/null.wav"; break; + case 1: pszSound = "buttons/button1.wav"; break; + case 2: pszSound = "buttons/button2.wav"; break; + case 3: pszSound = "buttons/button3.wav"; break; + case 4: pszSound = "buttons/button4.wav"; break; + case 5: pszSound = "buttons/button5.wav"; break; + case 6: pszSound = "buttons/button6.wav"; break; + case 7: pszSound = "buttons/button7.wav"; break; + case 8: pszSound = "buttons/button8.wav"; break; + case 9: pszSound = "buttons/button9.wav"; break; + case 10: pszSound = "buttons/button10.wav"; break; + case 11: pszSound = "buttons/button11.wav"; break; + case 12: pszSound = "buttons/latchlocked1.wav"; break; + case 13: pszSound = "buttons/latchunlocked1.wav"; break; + case 14: pszSound = "buttons/lightswitch2.wav";break; + +// next 6 slots reserved for any additional sliding button sounds we may add + + case 21: pszSound = "buttons/lever1.wav"; break; + case 22: pszSound = "buttons/lever2.wav"; break; + case 23: pszSound = "buttons/lever3.wav"; break; + case 24: pszSound = "buttons/lever4.wav"; break; + case 25: pszSound = "buttons/lever5.wav"; break; + + default:pszSound = "buttons/button9.wav"; break; + } + + return pszSound; +} + +// +// Makes flagged buttons spark when turned off +// + +void DoSpark(entvars_t *pev, const Vector &location ) +{ + Vector tmp = location + pev->size * 0.5; + UTIL_Sparks( tmp ); + + float flVolume = RANDOM_FLOAT ( 0.25 , 0.75 ) * 0.4;//random volume range + switch ( (int)(RANDOM_FLOAT(0,1) * 6) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark1.wav", flVolume, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark2.wav", flVolume, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark3.wav", flVolume, ATTN_NORM); break; + case 3: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark4.wav", flVolume, ATTN_NORM); break; + case 4: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark5.wav", flVolume, ATTN_NORM); break; + case 5: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark6.wav", flVolume, ATTN_NORM); break; + } +} + +void CBaseButton::ButtonSpark ( void ) +{ + SetThink(&CBaseButton:: ButtonSpark ); + SetNextThink( 0.1 + RANDOM_FLOAT ( 0, 1.5 ) );// spark again at random interval + + DoSpark( pev, pev->mins ); +} + + +// +// Button's Use function +// +void CBaseButton::ButtonUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Ignore touches if button is moving, or pushed-in and waiting to auto-come-out. + // UNDONE: Should this use ButtonResponseToTouch() too? + if (m_toggle_state == TS_GOING_UP || m_toggle_state == TS_GOING_DOWN ) + return; + + m_hActivator = pActivator; + if ( m_toggle_state == TS_AT_TOP) + { + if (!m_fStayPushed && FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE)) + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + //SUB_UseTargets( m_eoActivator ); + ButtonReturn(); + } + } + else + ButtonActivate( ); +} + +//LRC - they had it set up so that a touch-only button couldn't even be triggered!? +void CBaseButton::ButtonUse_IgnorePlayer ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !pCaller || !pCaller->IsPlayer() ) + ButtonUse( pActivator, pCaller, useType, value ); +} + +CBaseButton::BUTTON_CODE CBaseButton::ButtonResponseToTouch( void ) +{ + // Ignore touches if button is moving, or pushed-in and waiting to auto-come-out. + if (m_toggle_state == TS_GOING_UP || + m_toggle_state == TS_GOING_DOWN || + (m_toggle_state == TS_AT_TOP && !m_fStayPushed && !FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE) ) ) + return BUTTON_NOTHING; + + if (m_toggle_state == TS_AT_TOP) + { + if((FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE) ) && !m_fStayPushed) + { + return BUTTON_RETURN; + } + } + else + return BUTTON_ACTIVATE; + + return BUTTON_NOTHING; +} + + +// +// Touching a button simply "activates" it. +// +void CBaseButton:: ButtonTouch( CBaseEntity *pOther ) +{ + // Ignore touches by anything but players + if (!FClassnameIs(pOther->pev, "player")) + return; + + m_hActivator = pOther; + + BUTTON_CODE code = ButtonResponseToTouch(); + + if ( code == BUTTON_NOTHING ) + return; + + if (!UTIL_IsMasterTriggered(m_sMaster, pOther)) + { + // play button locked sound + PlayLockSounds(pev, &m_ls, TRUE, TRUE); + return; + } + + // Temporarily disable the touch function, until movement is finished. + SetTouch( NULL ); + + if ( code == BUTTON_RETURN ) + { + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + ButtonReturn(); + } + else // code == BUTTON_ACTIVATE + ButtonActivate( ); +} + +// +// Starts the button moving "in/up". +// +void CBaseButton::ButtonActivate( ) +{ + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + { + // button is locked, play locked sound + PlayLockSounds(pev, &m_ls, TRUE, TRUE); + return; + } + else + { + // button is unlocked, play unlocked sound + PlayLockSounds(pev, &m_ls, FALSE, TRUE); + } + + ASSERT(m_toggle_state == TS_AT_BOTTOM); + m_toggle_state = TS_GOING_UP; + + //LRC - unhelpfully, SF_BUTTON_DONTMOVE is the same value as + // SF_ROTBUTTON_NOTSOLID, so we have to assume that a rotbutton will + // never be DONTMOVE. + if (pev->spawnflags & SF_BUTTON_DONTMOVE && !m_fRotating) + { + TriggerAndWait(); + } + else + { + SetMoveDone(&CBaseButton:: TriggerAndWait ); + if (!m_fRotating) + LinearMove( m_vecPosition2, pev->speed); + else + AngularMove( m_vecAngle2, pev->speed); + } +} + +// +// Button has reached the "in/up" position. Activate its "targets", and pause before "popping out". +// +void CBaseButton::TriggerAndWait( void ) +{ + ASSERT(m_toggle_state == TS_GOING_UP); + + if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + return; + + m_toggle_state = TS_AT_TOP; + + pev->frame = 1; // use alternate textures + //LRC +// if (m_iStyle >= 32) LIGHT_STYLE(m_iStyle, "a"); buz +// else if (m_iStyle <= -32) LIGHT_STYLE(-m_iStyle, "z"); + + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + + if( m_fStayPushed && !FBitSet( pev->spawnflags, SF_BUTTON_TOGGLE )) + m_hide_use = 1; // button will stay forever + + // If button automatically comes back out, start it moving out. + // Else re-instate touch method + if (m_fStayPushed || FBitSet ( pev->spawnflags, SF_BUTTON_TOGGLE ) ) + { + if ( !FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) // this button only works if USED, not touched! + { + // ALL buttons are now use only + SetTouch ( NULL ); + } + else + SetTouch(&CBaseButton:: ButtonTouch ); + } + else + { + SetThink(&CBaseButton:: ButtonReturn ); + if ( m_flWait ) + { + SetNextThink( m_flWait ); + } + else + { + ButtonReturn(); + } + } +} + + +// +// Starts the button moving "out/down". +// +void CBaseButton::ButtonReturn( void ) +{ + ASSERT(m_toggle_state == TS_AT_TOP); + m_toggle_state = TS_GOING_DOWN; + + pev->frame = 0; // use normal textures + + //LRC +// if (m_iStyle >= 32) LIGHT_STYLE(m_iStyle, "z"); buz +// else if (m_iStyle <= -32) LIGHT_STYLE(-m_iStyle, "a"); + + if (pev->spawnflags & SF_BUTTON_DONTMOVE) + { + ButtonBackHome(); + } + else + { + SetMoveDone(&CBaseButton:: ButtonBackHome ); + if (!m_fRotating) + LinearMove( m_vecPosition1, pev->speed); + else + AngularMove( m_vecAngle1, pev->speed); + } +} + + +// +// Button has returned to start state. Quiesce it. +// +void CBaseButton::ButtonBackHome( void ) +{ + ASSERT(m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_AT_BOTTOM; + + if ( FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE) ) + { + //EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + } + + + if (!FStringNull(pev->target)) + { + CBaseEntity *pTarget = NULL; + for (;;) + { + pTarget = UTIL_FindEntityByTargetname(pTarget, STRING(pev->target), m_hActivator); + + if (FNullEnt(pTarget)) + break; + + if (!FClassnameIs(pTarget->pev, "multisource")) + // LRC- hmm... I see. On returning, a button will only turn off multisources. + continue; + + pTarget->Use( m_hActivator, this, USE_TOGGLE, 0 ); + } + } + +// Re-instate touch method, movement cycle is complete. + if ( !FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) // this button only works if USED, not touched! + { + // All buttons are now use only + SetTouch ( NULL ); + } + else + SetTouch(&CBaseButton:: ButtonTouch ); + +// reset think for a sparking button + if ( FBitSet ( pev->spawnflags, SF_BUTTON_SPARK_IF_OFF ) ) + { + SetThink(&CBaseButton:: ButtonSpark ); + SetNextThink( 0.5 );// no hurry. + } + else + { + DontThink(); + } +} + + + +// +// Rotating button (aka "lever") +// +class CRotButton : public CBaseButton +{ +public: + void Spawn( void ); + void PostSpawn( void ) {} // don't use the moveWith fix from CBaseButton + virtual void KeyValue( KeyValueData* pkvd); +}; + +LINK_ENTITY_TO_CLASS( func_rot_button, CRotButton ); + +void CRotButton::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "axes")) + { + UTIL_StringToVector( (float*)(pev->movedir), pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseButton::KeyValue( pkvd ); +} + +void CRotButton::Spawn( void ) +{ + char *pszSound; + //---------------------------------------------------- + //determine sounds for buttons + //a sound of 0 should not make a sound + //---------------------------------------------------- + pszSound = ButtonSound( m_sounds ); + PRECACHE_SOUND(pszSound); + pev->noise = ALLOC_STRING(pszSound); + + // set the axis of rotation + CBaseToggle::AxisDir( pev ); + + // check for clockwise rotation + if ( FBitSet (pev->spawnflags, SF_DOOR_ROTATE_BACKWARDS) ) + pev->movedir = pev->movedir * -1; + + pev->movetype = MOVETYPE_PUSH; + + if ( pev->spawnflags & SF_ROTBUTTON_NOTSOLID ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + + SET_MODEL(ENT(pev), STRING(pev->model)); + + if (pev->speed == 0) + pev->speed = 40; + + if (m_flWait == 0) + m_flWait = 1; + + if (pev->health > 0) + { + pev->takedamage = DAMAGE_YES; + } + + m_toggle_state = TS_AT_BOTTOM; + m_vecAngle1 = pev->angles; + m_vecAngle2 = pev->angles + pev->movedir * m_flMoveDistance; + ASSERTSZ(m_vecAngle1 != m_vecAngle2, "rotating button start/end positions are equal"); + + m_fStayPushed = (m_flWait == -1 ? TRUE : FALSE); + m_fRotating = TRUE; + + // if the button is flagged for USE button activation only, take away it's touch function and add a use function + if ( !FBitSet ( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) + { + SetTouch ( NULL ); + if ( FBitSet ( pev->spawnflags, SF_BUTTON_USEKEY ) ) + SetUse(&CRotButton:: ButtonUse_IgnorePlayer ); + else + SetUse(&CRotButton:: ButtonUse ); + } + else // touchable button + { + SetTouch(&CRotButton:: ButtonTouch ); + if ( !FBitSet ( pev->spawnflags, SF_BUTTON_USEKEY ) ) + SetUse(&CRotButton:: ButtonUse_IgnorePlayer ); + else + SetUse(&CRotButton:: ButtonUse ); + } + + //SetTouch( ButtonTouch ); +} + + +// Make this button behave like a door (HACKHACK) +// This will disable use and make the button solid +// rotating buttons were made SOLID_NOT by default since their were some +// collision problems with them... +#define SF_MOMENTARY_DOOR 0x0001 + +class CMomentaryRotButton : public CBaseToggle +{ +public: + void Spawn ( void ); + void KeyValue( KeyValueData *pkvd ); + virtual int ObjectCaps( void ) + { + int flags = CBaseToggle :: ObjectCaps() & (~FCAP_ACROSS_TRANSITION); + if ( pev->spawnflags & SF_MOMENTARY_DOOR ) + return flags; + return flags | FCAP_CONTINUOUS_USE; + } + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Off( void ); + void EXPORT Return( void ); + void UpdateSelf( float value ); + void UpdateSelfReturn( float value ); + void UpdateAllButtons( float value, int start ); + + void PlaySound( void ); + void UpdateTarget( float value ); + + static CMomentaryRotButton *Instance( edict_t *pent ) { return (CMomentaryRotButton *)GET_PRIVATE(pent);}; + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int m_lastUsed; + int m_direction; + float m_returnSpeed; + vec3_t m_start; + vec3_t m_end; + int m_sounds; +}; +TYPEDESCRIPTION CMomentaryRotButton::m_SaveData[] = +{ + DEFINE_FIELD( CMomentaryRotButton, m_lastUsed, FIELD_INTEGER ), + DEFINE_FIELD( CMomentaryRotButton, m_direction, FIELD_INTEGER ), + DEFINE_FIELD( CMomentaryRotButton, m_returnSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CMomentaryRotButton, m_start, FIELD_VECTOR ), + DEFINE_FIELD( CMomentaryRotButton, m_end, FIELD_VECTOR ), + DEFINE_FIELD( CMomentaryRotButton, m_sounds, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CMomentaryRotButton, CBaseToggle ); + +LINK_ENTITY_TO_CLASS( momentary_rot_button, CMomentaryRotButton ); + +void CMomentaryRotButton::Spawn( void ) +{ + CBaseToggle::AxisDir( pev ); + + if ( pev->speed == 0 ) + pev->speed = 100; + + if ( m_flMoveDistance < 0 ) + { + m_start = pev->angles + pev->movedir * m_flMoveDistance; + m_end = pev->angles; + m_direction = 1; // This will toggle to -1 on the first use() + m_flMoveDistance = -m_flMoveDistance; + } + else + { + m_start = pev->angles; + m_end = pev->angles + pev->movedir * m_flMoveDistance; + m_direction = -1; // This will toggle to +1 on the first use() + } + + if ( pev->spawnflags & SF_MOMENTARY_DOOR ) + pev->solid = SOLID_BSP; + else + pev->solid = SOLID_NOT; + + pev->movetype = MOVETYPE_PUSH; + UTIL_SetOrigin(this, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + char *pszSound = ButtonSound( m_sounds ); + PRECACHE_SOUND(pszSound); + pev->noise = ALLOC_STRING(pszSound); + m_lastUsed = 0; +} + +void CMomentaryRotButton::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "returnspeed")) + { + m_returnSpeed = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "axes")) + { + UTIL_StringToVector((float*)(pev->movedir), pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CMomentaryRotButton::PlaySound( void ) +{ + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); +} + +// BUGBUG: This design causes a latentcy. When the button is retriggered, the first impulse +// will send the target in the wrong direction because the parameter is calculated based on the +// current, not future position. +void CMomentaryRotButton::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (IsLockedByMaster()) return; //LRC + // the distance between the current angle and the "base" angle. + pev->ideal_yaw = CBaseToggle::AxisDelta( pev->spawnflags, pev->angles, m_start ) / m_flMoveDistance; + + UpdateAllButtons( pev->ideal_yaw, 1 ); + + float f = m_fNextThink - pev->ltime; + f = CBaseToggle::AxisDelta( pev->spawnflags, pev->angles + pev->avelocity*f, m_start ) / m_flMoveDistance; +// ALERT(at_console,"sending update = %f\n", f); + UpdateTarget( f ); +} + +void CMomentaryRotButton::UpdateAllButtons( float value, int start ) +{ + // Update all rot buttons attached to my target + // (this includes myself) + CBaseEntity *pTarget = NULL; + for (;;) + { + pTarget = UTIL_FindEntityByTarget(pTarget, STRING(pev->target)); + if (FNullEnt(pTarget)) + break; + + if ( FClassnameIs( pTarget->pev, "momentary_rot_button" ) ) + { + CMomentaryRotButton *pEntity = (CMomentaryRotButton*)pTarget; + if ( start ) + pEntity->UpdateSelf( value ); + else + pEntity->UpdateSelfReturn( value ); + } + } +} + +void CMomentaryRotButton::UpdateSelf( float value ) +{ + BOOL fplaysound = FALSE; + + if ( !m_lastUsed ) + { + fplaysound = TRUE; + m_direction = -m_direction; + } + m_lastUsed = 1; + + SetNextThink( 0.1 ); + + //LRC check if we're outside the boundaries + if ( m_direction > 0 && value >= 1.0 ) + { + pev->avelocity = g_vecZero; + pev->angles = m_end; + return; + } + else if ( m_direction < 0 && value <= 0 ) + { + pev->avelocity = g_vecZero; + pev->angles = m_start; + return; + } + + if (fplaysound) + PlaySound(); + + // HACKHACK -- If we're going slow, we'll get multiple player packets per frame; + // bump nexthink on each one to avoid stalling + //LRC- that is to say: our avelocity will get us to the target point in 0.1 secs. + // If we're being told to move further than that, wait that much longer. + if ( m_fNextThink < pev->ltime ) + SetNextThink( 0.1 ); + else + { + AbsoluteNextThink( m_fNextThink + 0.1 ); + } + + pev->avelocity = (m_direction * pev->speed) * pev->movedir; + SetThink(&CMomentaryRotButton:: Off ); +} + +void CMomentaryRotButton::UpdateTarget( float value ) +{ + if (!FStringNull(pev->target)) + { + CBaseEntity* pTarget = NULL; + for (;;) + { + pTarget = UTIL_FindEntityByTargetname(pTarget, STRING(pev->target)); + if ( !pTarget ) + break; + pTarget->Use( this, this, USE_SET, value ); + } + } +} + +void CMomentaryRotButton::Off( void ) +{ + pev->avelocity = g_vecZero; + m_lastUsed = 0; + if ( FBitSet( pev->spawnflags, SF_PENDULUM_AUTO_RETURN ) && m_returnSpeed > 0 ) + { + SetThink(&CMomentaryRotButton:: Return ); + SetNextThink( 0.1 ); + m_direction = -1; + } + else + SetThink( NULL ); +} + +void CMomentaryRotButton::Return( void ) +{ + float value = CBaseToggle::AxisDelta( pev->spawnflags, pev->angles, m_start ) / m_flMoveDistance; + + UpdateAllButtons( value, 0 ); // This will end up calling UpdateSelfReturn() n times, but it still works right + if ( value > 0 ) + UpdateTarget( value ); +} + + +void CMomentaryRotButton::UpdateSelfReturn( float value ) +{ + if ( value <= 0 ) + { + pev->avelocity = g_vecZero; + pev->angles = m_start; + DontThink(); + SetThink( NULL ); + } + else + { + pev->avelocity = -m_returnSpeed * pev->movedir; + SetNextThink( 0.1 ); + } +} + + +//---------------------------------------------------------------- +// Spark +//---------------------------------------------------------------- + +class CEnvSpark : public CPointEntity +{ +public: + void Spawn(void); + void Precache(void); + void EXPORT SparkThink(void); + void EXPORT SparkWait(void); + void EXPORT SparkCyclic(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT SparkStart(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT SparkStop(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue(KeyValueData *pkvd); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flDelay; + STATE m_iState; //LRC + virtual STATE GetState( void ) { return m_iState; }; +}; + + +TYPEDESCRIPTION CEnvSpark::m_SaveData[] = +{ + DEFINE_FIELD( CEnvSpark, m_flDelay, FIELD_FLOAT), + DEFINE_FIELD( CEnvSpark, m_iState, FIELD_INTEGER), //LRC +}; + +IMPLEMENT_SAVERESTORE( CEnvSpark, CBaseEntity ); + +LINK_ENTITY_TO_CLASS(env_spark, CEnvSpark); +LINK_ENTITY_TO_CLASS(env_debris, CEnvSpark); + +void CEnvSpark::Spawn(void) +{ + SetThink( NULL ); + SetUse( NULL ); + + if (FBitSet(pev->spawnflags, 16)) + { + SetUse(&CEnvSpark::SparkCyclic); + } + else if (FBitSet(pev->spawnflags, 32)) // Use for on/off + { + if (FBitSet(pev->spawnflags, 64)) // Start on + { + SetThink(&CEnvSpark::SparkThink); // start sparking + SetUse(&CEnvSpark::SparkStop); // set up +USE to stop sparking + } + else + SetUse(&CEnvSpark::SparkStart); + } + else + SetThink(&CEnvSpark::SparkThink); + + if (this->m_pfnThink) + { + SetNextThink( 0.1 + RANDOM_FLOAT ( 0, 1.5 ) ); + + if (m_flDelay <= 0) + m_flDelay = 1.5; + } + + Precache( ); +} + + +void CEnvSpark::Precache(void) +{ + PRECACHE_SOUND( "buttons/spark1.wav" ); + PRECACHE_SOUND( "buttons/spark2.wav" ); + PRECACHE_SOUND( "buttons/spark3.wav" ); + PRECACHE_SOUND( "buttons/spark4.wav" ); + PRECACHE_SOUND( "buttons/spark5.wav" ); + PRECACHE_SOUND( "buttons/spark6.wav" ); +} + +void CEnvSpark::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "MaxDelay")) + { + m_flDelay = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "style") || + FStrEq(pkvd->szKeyName, "height") || + FStrEq(pkvd->szKeyName, "killtarget") || + FStrEq(pkvd->szKeyName, "value1") || + FStrEq(pkvd->szKeyName, "value2") || + FStrEq(pkvd->szKeyName, "value3")) + pkvd->fHandled = TRUE; + else + CBaseEntity::KeyValue( pkvd ); +} + +void EXPORT CEnvSpark::SparkCyclic(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (m_pfnThink == NULL) + { + DoSpark( pev, pev->origin ); + SetThink(&CEnvSpark:: SparkWait ); + SetNextThink( m_flDelay ); + } + else + { + SetThink(&CEnvSpark::SparkThink ); // if we're on SparkWait, change to actually spark at the specified time. + } +} + +void EXPORT CEnvSpark::SparkWait(void) +{ + SetThink( NULL ); +} + +void EXPORT CEnvSpark::SparkThink(void) +{ + DoSpark( pev, pev->origin ); + if (pev->spawnflags & 16) + { + SetThink( NULL ); + } + else + { + SetNextThink( 0.1 + RANDOM_FLOAT (0, m_flDelay) ); + } +} + +void EXPORT CEnvSpark::SparkStart(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetUse(&CEnvSpark::SparkStop); + SetThink(&CEnvSpark::SparkThink); + m_iState = STATE_ON; //LRC + SetNextThink( 0.1 + RANDOM_FLOAT ( 0, m_flDelay) ); +} + +void EXPORT CEnvSpark::SparkStop(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetUse(&CEnvSpark::SparkStart); + SetThink(NULL); + m_iState = STATE_OFF; //LRC +} + +#define SF_BTARGET_USE 0x0001 +#define SF_BTARGET_ON 0x0002 + +class CButtonTarget : public CBaseEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + int ObjectCaps( void ); + +}; + +LINK_ENTITY_TO_CLASS( button_target, CButtonTarget ); + +void CButtonTarget::Spawn( void ) +{ + pev->movetype = MOVETYPE_PUSH; + pev->solid = SOLID_BSP; + SET_MODEL(ENT(pev), STRING(pev->model)); + pev->takedamage = DAMAGE_YES; + + if ( FBitSet( pev->spawnflags, SF_BTARGET_ON ) ) + pev->frame = 1; +} + +void CButtonTarget::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, (int)pev->frame ) ) + return; + pev->frame = 1-pev->frame; + if ( pev->frame ) + SUB_UseTargets( pActivator, USE_ON, 0 ); + else + SUB_UseTargets( pActivator, USE_OFF, 0 ); +} + + +int CButtonTarget :: ObjectCaps( void ) +{ + int caps = CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; + + if ( FBitSet(pev->spawnflags, SF_BTARGET_USE) ) + return caps | FCAP_IMPULSE_USE; + else + return caps; +} + + +int CButtonTarget::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + Use( Instance(pevAttacker), this, USE_TOGGLE, 0 ); + + return 1; +} diff --git a/dlls/cbase.cpp b/dlls/cbase.cpp new file mode 100644 index 0000000..956eedd --- /dev/null +++ b/dlls/cbase.cpp @@ -0,0 +1,1089 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "client.h" +#include "decals.h" +#include "gamerules.h" +#include "game.h" +#include "movewith.h" +#include "skill.h" + +void EntvarsKeyvalue( entvars_t *pev, KeyValueData *pkvd ); + +extern void PM_Move ( struct playermove_s *ppmove, int server ); +extern void PM_Init ( struct playermove_s *ppmove ); + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); +extern DLL_GLOBAL Vector g_vecAttackDir; +extern DLL_GLOBAL int g_iSkillLevel; + +static DLL_FUNCTIONS gFunctionTable = +{ + GameDLLInit, //pfnGameInit + DispatchSpawn, //pfnSpawn + DispatchThink, //pfnThink + DispatchUse, //pfnUse + DispatchTouch, //pfnTouch + DispatchBlocked, //pfnBlocked + DispatchKeyValue, //pfnKeyValue + DispatchSave, //pfnSave + DispatchRestore, //pfnRestore + DispatchObjectCollsionBox, //pfnAbsBox + + SaveWriteFields, //pfnSaveWriteFields + SaveReadFields, //pfnSaveReadFields + + SaveGlobalState, //pfnSaveGlobalState + RestoreGlobalState, //pfnRestoreGlobalState + ResetGlobalState, //pfnResetGlobalState + + ClientConnect, //pfnClientConnect + ClientDisconnect, //pfnClientDisconnect + ClientKill, //pfnClientKill + ClientPutInServer, //pfnClientPutInServer + ClientCommand, //pfnClientCommand + ClientUserInfoChanged, //pfnClientUserInfoChanged + ServerActivate, //pfnServerActivate + ServerDeactivate, //pfnServerDeactivate + + PlayerPreThink, //pfnPlayerPreThink + PlayerPostThink, //pfnPlayerPostThink + + StartFrame, //pfnStartFrame + ParmsNewLevel, //pfnParmsNewLevel + ParmsChangeLevel, //pfnParmsChangeLevel + + GetGameDescription, //pfnGetGameDescription Returns string describing current .dll game. + PlayerCustomization, //pfnPlayerCustomization Notifies .dll of new customization for player. + + SpectatorConnect, //pfnSpectatorConnect Called when spectator joins server + SpectatorDisconnect, //pfnSpectatorDisconnect Called when spectator leaves the server + SpectatorThink, //pfnSpectatorThink Called when spectator sends a command packet (usercmd_t) + + Sys_Error, //pfnSys_Error Called when engine has encountered an error + + PM_Move, //pfnPM_Move + PM_Init, //pfnPM_Init Server version of player movement initialization + NULL, //pfnPM_FindTextureType + + SetupVisibility, //pfnSetupVisibility Set up PVS and PAS for networking for this client + UpdateClientData, //pfnUpdateClientData Set up data sent only to specific client + AddToFullPack, //pfnAddToFullPack + CreateBaseline, //pfnCreateBaseline Tweak entity baseline for network encoding, allows setup of player baselines, too. + RegisterEncoders, //pfnRegisterEncoders Callbacks for network encoding + GetWeaponData, //pfnGetWeaponData + CmdStart, //pfnCmdStart + CmdEnd, //pfnCmdEnd + ConnectionlessPacket, //pfnConnectionlessPacket + GetHullBounds, //pfnGetHullBounds + CreateInstancedBaselines, //pfnCreateInstancedBaselines + InconsistentFile, //pfnInconsistentFile + AllowLagCompensation, //pfnAllowLagCompensation +}; + +NEW_DLL_FUNCTIONS gNewDLLFunctions = +{ + OnFreeEntPrivateData, // pfnOnFreeEntPrivateData + GameDLLShutdown, // pfnGameShutdown + ShouldCollide, // pfnShouldCollide +}; + +static void SetObjectCollisionBox( entvars_t *pev ); + +#ifndef _WIN32 +extern "C" { +#endif +int GetEntityAPI( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion ) +{ + if ( !pFunctionTable || interfaceVersion != INTERFACE_VERSION ) + { + return FALSE; + } + + memcpy( pFunctionTable, &gFunctionTable, sizeof( DLL_FUNCTIONS ) ); + + if( g_iXashEngineBuildNumber <= 3584 ) + { + ALERT( at_error, "Your build of Xash3D is old. Please update engine to an actual version\n" ); + return FALSE; + } + + return TRUE; +} + +int GetEntityAPI2( DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ) +{ + if ( !pFunctionTable || *interfaceVersion != INTERFACE_VERSION ) + { + // Tell engine what version we had, so it can figure out who is out of date. + *interfaceVersion = INTERFACE_VERSION; + return FALSE; + } + + memcpy( pFunctionTable, &gFunctionTable, sizeof( DLL_FUNCTIONS ) ); + + if( g_iXashEngineBuildNumber <= 3584 ) + { + ALERT( at_error, "Your build of Xash3D is old. Please update engine to an actual version\n" ); + return FALSE; + } + + return TRUE; +} + +#ifndef _WIN32 +} +#endif + +int GetNewDLLFunctions( NEW_DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ) +{ + if( !pFunctionTable || *interfaceVersion != NEW_DLL_FUNCTIONS_VERSION ) + { + *interfaceVersion = NEW_DLL_FUNCTIONS_VERSION; + return FALSE; + } + + memcpy( pFunctionTable, &gNewDLLFunctions, sizeof( gNewDLLFunctions )); + return TRUE; +} + +int DispatchSpawn( edict_t *pent ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if (pEntity) + { + // Initialize these or entities who don't link to the world won't have anything in here + pEntity->pev->absmin = pEntity->pev->origin - Vector(1,1,1); + pEntity->pev->absmax = pEntity->pev->origin + Vector(1,1,1); + +// pEntity->InitMoveWith(); //LRC + pEntity->Spawn(); + + // Try to get the pointer again, in case the spawn function deleted the entity. + // UNDONE: Spawn() should really return a code to ask that the entity be deleted, but + // that would touch too much code for me to do that right now. + pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if ( pEntity ) + { + if ( g_pGameRules && !g_pGameRules->IsAllowedToSpawn( pEntity ) ) + return -1; // return that this entity should be deleted + if ( pEntity->pev->flags & FL_KILLME ) + return -1; + if ( g_iSkillLevel == SKILL_EASY && pEntity->m_iLFlags & LF_NOTEASY ) + return -1; //LRC + if (g_iSkillLevel == SKILL_MEDIUM && pEntity->m_iLFlags & LF_NOTMEDIUM ) + return -1; //LRC + if (g_iSkillLevel == SKILL_HARD && pEntity->m_iLFlags & LF_NOTHARD ) + return -1; //LRC + } + + + // Handle global stuff here + if ( pEntity && pEntity->pev->globalname ) + { + const globalentity_t *pGlobal = gGlobalState.EntityFromTable( pEntity->pev->globalname ); + if ( pGlobal ) + { + // Already dead? delete + if ( pGlobal->state == GLOBAL_DEAD ) + return -1; + else if ( !FStrEq( STRING(gpGlobals->mapname), pGlobal->levelName ) ) + pEntity->MakeDormant(); // Hasn't been moved to this level yet, wait but stay alive + // In this level & not dead, continue on as normal + } + else + { + // Spawned entities default to 'On' + gGlobalState.EntityAdd( pEntity->pev->globalname, gpGlobals->mapname, GLOBAL_ON ); +// ALERT( at_console, "Added global entity %s (%s)\n", STRING(pEntity->pev->classname), STRING(pEntity->pev->globalname) ); + } + } + } + + return 0; +} + +void DispatchKeyValue( edict_t *pentKeyvalue, KeyValueData *pkvd ) +{ + if ( !pkvd || !pentKeyvalue ) + return; + + if( FClassnameIs( pentKeyvalue, "ammo_generic" )) + { + // if custom name specified as classname and already moved into netname + if( FStrEq( pkvd->szKeyName, "classname" ) && FStrEq( pkvd->szValue, STRING( pentKeyvalue->v.netname ))) + { + pkvd->fHandled = TRUE; + return; + } + // don't override netname + else if( FStrEq( pkvd->szKeyName, "netname" ) && pentKeyvalue->v.netname ) + { + pkvd->fHandled = TRUE; + return; + } + } + else if( FClassnameIs( pentKeyvalue, "weapon_generic" )) + { + // if custom name specified as classname and already moved into netname + if( FStrEq( pkvd->szKeyName, "classname" ) && FStrEq( pkvd->szValue, STRING( pentKeyvalue->v.netname ))) + { + // don't restore old name + pkvd->fHandled = TRUE; + return; + } + // don't override netname + else if( FStrEq( pkvd->szKeyName, "netname" ) && pentKeyvalue->v.netname ) + { + // don't restore old name + pkvd->fHandled = TRUE; + return; + } + } + + EntvarsKeyvalue( VARS(pentKeyvalue), pkvd ); + + // If the key was an entity variable, or there's no class set yet, don't look for the object, it may + // not exist yet. + if ( pkvd->fHandled || pkvd->szClassName == NULL ) + return; + + // Get the actualy entity object + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pentKeyvalue); + + if ( !pEntity ) + return; + + pEntity->KeyValue( pkvd ); +} + + +// HACKHACK -- this is a hack to keep the node graph entity from "touching" things (like triggers) +// while it builds the graph +BOOL gTouchDisabled = FALSE; +void DispatchTouch( edict_t *pentTouched, edict_t *pentOther ) +{ + if ( gTouchDisabled ) + return; + + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pentTouched); + CBaseEntity *pOther = (CBaseEntity *)GET_PRIVATE( pentOther ); + + if ( pEntity && pOther && ! ((pEntity->pev->flags | pOther->pev->flags) & FL_KILLME) ) + pEntity->Touch( pOther ); +} + + +void DispatchUse( edict_t *pentUsed, edict_t *pentOther ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pentUsed); + CBaseEntity *pOther = (CBaseEntity *)GET_PRIVATE(pentOther); + + if (pEntity && !(pEntity->pev->flags & FL_KILLME) ) + pEntity->Use( pOther, pOther, USE_TOGGLE, 0 ); +} + +void DispatchThink( edict_t *pent ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if (pEntity) + { + if ( FBitSet( pEntity->pev->flags, FL_DORMANT ) ) + ALERT( at_error, "Dormant entity %s is thinking!!\n", STRING(pEntity->pev->classname) ); + + //if (pEntity->pev->classname) ALERT(at_console, "DispatchThink %s\n", STRING(pEntity->pev->targetname)); + pEntity->Think(); + } +} + +void DispatchBlocked( edict_t *pentBlocked, edict_t *pentOther ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE( pentBlocked ); + CBaseEntity *pOther = (CBaseEntity *)GET_PRIVATE( pentOther ); + + if (pEntity) + pEntity->Blocked( pOther ); +} + +void DispatchSave( edict_t *pent, SAVERESTOREDATA *pSaveData ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if ( pEntity && pSaveData ) + { + ENTITYTABLE *pTable = &pSaveData->pTable[ pSaveData->currentIndex ]; + + if ( pTable->pent != pent ) + ALERT( at_error, "ENTITY TABLE OR INDEX IS WRONG!!!!\n" ); + + if ( pEntity->ObjectCaps() & FCAP_DONT_SAVE ) + return; + + // These don't use ltime & nextthink as times really, but we'll fudge around it. + if ( pEntity->pev->movetype == MOVETYPE_PUSH ) + { + //LRC - rearranged so that we can correct m_fNextThink too. + float delta = gpGlobals->time - pEntity->pev->ltime; + pEntity->pev->ltime += delta; + pEntity->pev->nextthink += delta; + pEntity->m_fPevNextThink = pEntity->pev->nextthink; + pEntity->m_fNextThink += delta; + } + + pTable->location = pSaveData->size; // Remember entity position for file I/O + pTable->classname = pEntity->pev->classname; // Remember entity class for respawn + + CSave saveHelper( pSaveData ); + pEntity->Save( saveHelper ); + + pTable->size = pSaveData->size - pTable->location; // Size of entity block is data size written to block + } +} + + +// Find the matching global entity. Spit out an error if the designer made entities of +// different classes with the same global name +CBaseEntity *FindGlobalEntity( string_t classname, string_t globalname ) +{ + CBaseEntity *pReturn = UTIL_FindEntityByString( NULL, "globalname", STRING(globalname) ); + if ( pReturn ) + { + if ( !FClassnameIs( pReturn->pev, STRING(classname) ) ) + { + ALERT( at_debug, "Global entity found %s, wrong class %s\n", STRING(globalname), STRING(pReturn->pev->classname) ); + pReturn = NULL; + } + } + + return pReturn; +} + + +int DispatchRestore( edict_t *pent, SAVERESTOREDATA *pSaveData, int globalEntity ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if ( pEntity && pSaveData ) + { + entvars_t tmpVars; + Vector oldOffset; + + CRestore restoreHelper( pSaveData ); + if ( globalEntity ) + { + CRestore tmpRestore( pSaveData ); + tmpRestore.PrecacheMode( 0 ); + tmpRestore.ReadEntVars( "ENTVARS", &tmpVars ); + + // HACKHACK - reset the save pointers, we're going to restore for real this time + pSaveData->size = pSaveData->pTable[pSaveData->currentIndex].location; + pSaveData->pCurrentData = pSaveData->pBaseData + pSaveData->size; + // ------------------- + + + const globalentity_t *pGlobal = gGlobalState.EntityFromTable( tmpVars.globalname ); + + // Don't overlay any instance of the global that isn't the latest + // pSaveData->szCurrentMapName is the level this entity is coming from + // pGlobla->levelName is the last level the global entity was active in. + // If they aren't the same, then this global update is out of date. + if ( !FStrEq( pSaveData->szCurrentMapName, pGlobal->levelName ) ) + return 0; + + // Compute the new global offset + oldOffset = pSaveData->vecLandmarkOffset; + CBaseEntity *pNewEntity = FindGlobalEntity( tmpVars.classname, tmpVars.globalname ); + if ( pNewEntity ) + { +// ALERT( at_console, "Overlay %s with %s\n", STRING(pNewEntity->pev->classname), STRING(tmpVars.classname) ); + // Tell the restore code we're overlaying a global entity from another level + restoreHelper.SetGlobalMode( 1 ); // Don't overwrite global fields + pSaveData->vecLandmarkOffset = (pSaveData->vecLandmarkOffset - pNewEntity->pev->mins) + tmpVars.mins; + pEntity = pNewEntity;// we're going to restore this data OVER the old entity + pent = ENT( pEntity->pev ); + // Update the global table to say that the global definition of this entity should come from this level + gGlobalState.EntityUpdate( pEntity->pev->globalname, gpGlobals->mapname ); + } + else + { + // This entity will be freed automatically by the engine. If we don't do a restore on a matching entity (below) + // or call EntityUpdate() to move it to this level, we haven't changed global state at all. + return 0; + } + + } + + if ( pEntity->ObjectCaps() & FCAP_MUST_SPAWN ) + { + pEntity->Restore( restoreHelper ); + pEntity->Spawn(); + } + else + { + pEntity->Restore( restoreHelper ); + pEntity->Precache( ); + } + + // Again, could be deleted, get the pointer again. + pEntity = (CBaseEntity *)GET_PRIVATE(pent); + + if( pEntity && pEntity->pev->solid == SOLID_CUSTOM && ( !g_precache_meshes || g_precache_meshes->value )) + UTIL_GetCollisionMesh( pEntity->pev->modelindex ); +#if 0 + if ( pEntity && pEntity->pev->globalname && globalEntity ) + { + ALERT( at_debug, "Global %s is %s\n", STRING(pEntity->pev->globalname), STRING(pEntity->pev->model) ); + } +#endif + + // Is this an overriding global entity (coming over the transition), or one restoring in a level + if ( globalEntity ) + { +// ALERT( at_console, "After: %f %f %f %s\n", pEntity->pev->origin.x, pEntity->pev->origin.y, pEntity->pev->origin.z, STRING(pEntity->pev->model) ); + pSaveData->vecLandmarkOffset = oldOffset; + if ( pEntity ) + { + UTIL_SetOrigin( pEntity, pEntity->pev->origin ); + pEntity->OverrideReset(); + } + } + else if ( pEntity && pEntity->pev->globalname ) + { + const globalentity_t *pGlobal = gGlobalState.EntityFromTable( pEntity->pev->globalname ); + if ( pGlobal ) + { + // Already dead? delete + if ( pGlobal->state == GLOBAL_DEAD ) + return -1; + else if ( !FStrEq( STRING(gpGlobals->mapname), pGlobal->levelName ) ) + { + pEntity->MakeDormant(); // Hasn't been moved to this level yet, wait but stay alive + } + // In this level & not dead, continue on as normal + } + else + { + ALERT( at_error, "Global Entity %s (%s) not in table!!!\n", STRING(pEntity->pev->globalname), STRING(pEntity->pev->classname) ); + // Spawned entities default to 'On' + gGlobalState.EntityAdd( pEntity->pev->globalname, gpGlobals->mapname, GLOBAL_ON ); + } + } + } + return 0; +} + + +void DispatchObjectCollsionBox( edict_t *pent ) +{ + CBaseEntity *pEntity = (CBaseEntity *)GET_PRIVATE(pent); + if (pEntity) + { + pEntity->SetObjectCollisionBox(); + } + else + SetObjectCollisionBox( &pent->v ); +} + +void OnFreeEntPrivateData( edict_s *pEdict ) +{ + if( g_fPhysicInitialized ) + { + if( GET_SERVER_STATE() == SERVER_DEAD ) + return; + } + + if( pEdict && pEdict->pvPrivateData ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( pEdict ); + pEntity->UpdateOnRemove(); + } +} + +void SaveWriteFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + CSave saveHelper( pSaveData ); + saveHelper.WriteFields( "SWF", pname, pBaseData, pFields, fieldCount ); +} + + +void SaveReadFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + CRestore restoreHelper( pSaveData ); + restoreHelper.ReadFields( pname, pBaseData, pFields, fieldCount ); +} + + +edict_t * EHANDLE::Get( void ) +{ + if (m_pent) + { + if (m_pent->serialnumber == m_serialnumber) + return m_pent; + else + return NULL; + } + return NULL; +}; + +edict_t * EHANDLE::Set( edict_t *pent ) +{ + m_pent = pent; + if (pent) + m_serialnumber = m_pent->serialnumber; + return pent; +}; + + +EHANDLE :: operator CBaseEntity *() +{ + return (CBaseEntity *)GET_PRIVATE( Get( ) ); +}; + + +CBaseEntity * EHANDLE :: operator = (CBaseEntity *pEntity) +{ + if (pEntity) + { + m_pent = ENT( pEntity->pev ); + if (m_pent) + m_serialnumber = m_pent->serialnumber; + } + else + { + m_pent = NULL; + m_serialnumber = 0; + } + return pEntity; +} + +EHANDLE :: operator int () +{ + return Get() != NULL; +} + +CBaseEntity * EHANDLE :: operator -> () +{ + return (CBaseEntity *)GET_PRIVATE( Get( ) ); +} + +//LRC +void CBaseEntity::Activate( void ) +{ + //LRC - rebuild the new assistlist as the game starts + if (m_iLFlags & LF_ASSISTLIST) + { + UTIL_AddToAssistList(this); + } + + //LRC - and the aliaslist too + if (m_iLFlags & LF_ALIASLIST) + { + UTIL_AddToAliasList((CBaseAlias*)this); + } + + if (m_activated) return; + m_activated = TRUE; + InitMoveWith(); + PostSpawn(); +} + +//LRC- called by activate() to support movewith +void CBaseEntity::InitMoveWith( void ) +{ + if (!m_MoveWith) return; + + m_pMoveWith = UTIL_FindEntityByTargetname(NULL, STRING(m_MoveWith)); + if (!m_pMoveWith) + { + ALERT(at_debug,"Missing movewith entity %s\n", STRING(m_MoveWith)); + return; + } + +// if (pev->targetname) +// ALERT(at_console,"Init: %s %s moves with %s\n", STRING(pev->classname), STRING(pev->targetname), STRING(m_MoveWith)); +// else +// ALERT(at_console,"Init: %s moves with %s\n", STRING(pev->classname), STRING(m_MoveWith)); + + CBaseEntity *pSibling = m_pMoveWith->m_pChildMoveWith; + while (pSibling) // check that this entity isn't already in the list of children + { + if (pSibling == this) break; + pSibling = pSibling->m_pSiblingMoveWith; + } + if (!pSibling) // if movewith is being set up for the first time... + { + // add this entity to the list of children + m_pSiblingMoveWith = m_pMoveWith->m_pChildMoveWith; // may be null: that's fine by me. + m_pMoveWith->m_pChildMoveWith = this; + + if (pev->movetype == MOVETYPE_NONE) + { + if (pev->solid == SOLID_BSP) + pev->movetype = MOVETYPE_PUSH; + else + pev->movetype = MOVETYPE_NOCLIP; // or _FLY, perhaps? + } + + // was the parent shifted at spawn-time? + if (m_pMoveWith->m_vecSpawnOffset != g_vecZero) + { + //ALERT(at_console,"Corrected using SpawnOffset\n"); + // shift this by the same amount the parent was shifted by. + UTIL_AssignOrigin(this, pev->origin + m_pMoveWith->m_vecSpawnOffset); + //...and inherit the same offset. + m_vecSpawnOffset = m_vecSpawnOffset + m_pMoveWith->m_vecSpawnOffset; + } + else + { + // This gets set up by AssignOrigin, but otherwise we'll need to do it manually. + m_vecMoveWithOffset = pev->origin - m_pMoveWith->pev->origin; + } + m_vecRotWithOffset = pev->angles - m_pMoveWith->pev->angles; + } + +// if (pev->flags & FL_WORLDBRUSH) // not sure what this does, exactly. +// pev->flags &= ~FL_WORLDBRUSH; +} + +//LRC +void CBaseEntity::DontThink( void ) +{ + m_fNextThink = 0; + if (m_pMoveWith == NULL && m_pChildMoveWith == NULL) + { + pev->nextthink = 0; + m_fPevNextThink = 0; + } + +// ALERT(at_console, "DontThink for %s\n", STRING(pev->targetname)); +} + +//LRC +// PUSH entities won't have their velocity applied unless they're thinking. +// make them do so for the foreseeable future. +void CBaseEntity :: SetEternalThink( void ) +{ + if (pev->movetype == MOVETYPE_PUSH) + { + // record m_fPevNextThink as well, because we want to be able to + // tell when the bloody engine CHANGES IT! +// pev->nextthink = 1E9; + pev->nextthink = pev->ltime + 1E6; + m_fPevNextThink = pev->nextthink; + } + + CBaseEntity *pChild; + for (pChild = m_pChildMoveWith; pChild != NULL; pChild = pChild->m_pSiblingMoveWith) + pChild->SetEternalThink( ); +} + +//LRC - for getting round the engine's preconceptions. +// MoveWith entities have to be able to think independently of moving. +// This is how we do so. +void CBaseEntity :: SetNextThink( float delay, BOOL correctSpeed ) +{ + // now monsters use this method, too. + if (m_pMoveWith || m_pChildMoveWith || pev->flags & FL_MONSTER) + { + // use the Assist system, so that thinking doesn't mess up movement. + if (pev->movetype == MOVETYPE_PUSH) + m_fNextThink = pev->ltime + delay; + else + m_fNextThink = gpGlobals->time + delay; + SetEternalThink( ); + UTIL_MarkForAssist( this, correctSpeed ); + +// ALERT(at_console, "SetAssistedThink for %s: %f\n", STRING(pev->targetname), m_fNextThink); + } + else + { + // set nextthink as normal. + if (pev->movetype == MOVETYPE_PUSH) + { + pev->nextthink = pev->ltime + delay; + } + else + { + pev->nextthink = gpGlobals->time + delay; + } + + m_fPevNextThink = m_fNextThink = pev->nextthink; + +// if (pev->classname) ALERT(at_console, "SetNormThink for %s: %f\n", STRING(pev->targetname), m_fNextThink); + } +} + +//LRC +void CBaseEntity :: AbsoluteNextThink( float time, BOOL correctSpeed ) +{ + if (m_pMoveWith || m_pChildMoveWith) + { + // use the Assist system, so that thinking doesn't mess up movement. + m_fNextThink = time; + SetEternalThink( ); + UTIL_MarkForAssist( this, correctSpeed ); + } + else + { + // set nextthink as normal. + pev->nextthink = time; + m_fPevNextThink = m_fNextThink = pev->nextthink; + } +} + +//LRC - check in case the engine has changed our nextthink. (which it does +// on a depressingly frequent basis.) +// for some reason, this doesn't always produce perfect movement - but it's close +// enough for government work. (the player doesn't get stuck, at least.) +void CBaseEntity :: ThinkCorrection( void ) +{ + if (pev->nextthink != m_fPevNextThink) + { + // The engine has changed our nextthink, in its typical endearing way. + // Now we have to apply that change in the _right_ places. +// ALERT(at_console, "StoredThink corrected for %s \"%s\": %f -> %f\n", STRING(pev->classname), STRING(pev->targetname), m_fNextThink, m_fNextThink + pev->nextthink - m_fPevNextThink); + m_fNextThink += pev->nextthink - m_fPevNextThink; + m_fPevNextThink = pev->nextthink; + } +} + +// give health +int CBaseEntity :: TakeHealth( float flHealth, int bitsDamageType ) +{ + if (!pev->takedamage) + return 0; + +// heal + if ( pev->health >= pev->max_health ) + return 0; + + pev->health += flHealth; + + if (pev->health > pev->max_health) + pev->health = pev->max_health; + + return 1; +} + +// inflict damage on this entity. bitsDamageType indicates type of damage inflicted, ie: DMG_CRUSH + +int CBaseEntity :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + Vector vecTemp; + + if (!pev->takedamage) + return 0; + + // UNDONE: some entity types may be immune or resistant to some bitsDamageType + + // if Attacker == Inflictor, the attack was a melee or other instant-hit attack. + // (that is, no actual entity projectile was involved in the attack so use the shooter's origin). + if ( pevAttacker == pevInflictor ) + { + vecTemp = pevInflictor->origin - ( VecBModelOrigin(pev) ); + } + else + // an actual missile was involved. + { + vecTemp = pevInflictor->origin - ( VecBModelOrigin(pev) ); + } + +// this global is still used for glass and other non-monster killables, along with decals. + g_vecAttackDir = vecTemp.Normalize(); + +// save damage based on the target's armor level + +// figure momentum add (don't let hurt brushes or other triggers move player) + if ((!FNullEnt(pevInflictor)) && (pev->movetype == MOVETYPE_WALK || pev->movetype == MOVETYPE_STEP) && (pevAttacker->solid != SOLID_TRIGGER) ) + { + Vector vecDir = pev->origin - (pevInflictor->absmin + pevInflictor->absmax) * 0.5; + vecDir = vecDir.Normalize(); + + float flForce = flDamage * ((32 * 32 * 72.0) / (pev->size.x * pev->size.y * pev->size.z)) * 5; + + if (flForce > 1000.0) + flForce = 1000.0; + pev->velocity = pev->velocity + vecDir * flForce; + } + +// do the damage + pev->health -= flDamage; + if (pev->health <= 0) + { + Killed( pevAttacker, GIB_NORMAL ); + return 0; + } + + return 1; +} + + +void CBaseEntity :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->takedamage = DAMAGE_NO; + pev->deadflag = DEAD_DEAD; + UTIL_Remove( this ); +} + + +CBaseEntity *CBaseEntity::GetNextTarget( void ) +{ + if ( FStringNull( pev->target ) ) + return NULL; + return UTIL_FindEntityByTargetname( NULL, STRING(pev->target)); +} + +// Global Savedata for Delay +TYPEDESCRIPTION CBaseEntity::m_SaveData[] = +{ + DEFINE_FIELD( CBaseEntity, m_pGoalEnt, FIELD_CLASSPTR ), + + DEFINE_FIELD( CBaseEntity, m_MoveWith, FIELD_STRING ), //LRC + DEFINE_FIELD( CBaseEntity, m_pMoveWith, FIELD_CLASSPTR ), //LRC + DEFINE_FIELD( CBaseEntity, m_pChildMoveWith, FIELD_CLASSPTR ), //LRC + DEFINE_FIELD( CBaseEntity, m_pSiblingMoveWith, FIELD_CLASSPTR ), //LRC + + DEFINE_FIELD( CBaseEntity, m_iLFlags, FIELD_INTEGER ), //LRC + DEFINE_FIELD( CBaseEntity, m_iStyle, FIELD_INTEGER ), //LRC + DEFINE_FIELD( CBaseEntity, m_vecMoveWithOffset, FIELD_VECTOR ), //LRC + DEFINE_FIELD( CBaseEntity, m_vecRotWithOffset, FIELD_VECTOR ), //LRC + DEFINE_FIELD( CBaseEntity, m_activated, FIELD_BOOLEAN ), //LRC + DEFINE_FIELD( CBaseEntity, m_fNextThink, FIELD_TIME ), //LRC + DEFINE_FIELD( CBaseEntity, m_fPevNextThink, FIELD_TIME ), //LRC +// DEFINE_FIELD( CBaseEntity, m_pAssistLink, FIELD_CLASSPTR ), //LRC - don't save this, we'll just rebuild the list on restore + DEFINE_FIELD( CBaseEntity, m_vecPostAssistVel, FIELD_VECTOR ), //LRC + DEFINE_FIELD( CBaseEntity, m_vecPostAssistAVel, FIELD_VECTOR ), //LRC + DEFINE_FIELD( CBaseEntity, m_flGaitYaw, FIELD_FLOAT ), + + // studio pose parameters + DEFINE_ARRAY( CBaseEntity, m_flPoseParameter, FIELD_FLOAT, MAXSTUDIOPOSEPARAM ), + + DEFINE_FIELD( CBaseEntity, m_pfnThink, FIELD_FUNCTION ), // UNDONE: Build table of these!!! + DEFINE_FIELD( CBaseEntity, m_pfnTouch, FIELD_FUNCTION ), + DEFINE_FIELD( CBaseEntity, m_pfnUse, FIELD_FUNCTION ), + DEFINE_FIELD( CBaseEntity, m_pfnBlocked, FIELD_FUNCTION ), +}; + + +int CBaseEntity::Save( CSave &save ) +{ + ThinkCorrection(); //LRC + + if ( save.WriteEntVars( "ENTVARS", pev ) ) + { + if (pev->targetname) + return save.WriteFields( STRING(pev->targetname), "BASE", this, m_SaveData, ARRAYSIZE(m_SaveData) ); + else + return save.WriteFields( STRING(pev->classname), "BASE", this, m_SaveData, ARRAYSIZE(m_SaveData) ); + } + + return 0; +} + +int CBaseEntity::Restore( CRestore &restore ) +{ + int status; + + status = restore.ReadEntVars( "ENTVARS", pev ); + if ( status ) + status = restore.ReadFields( "BASE", this, m_SaveData, ARRAYSIZE(m_SaveData) ); + + if ( pev->modelindex != 0 && !FStringNull(pev->model) ) + { + Vector mins, maxs; + mins = pev->mins; // Set model is about to destroy these + maxs = pev->maxs; + + + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL(ENT(pev), STRING(pev->model)); + UTIL_SetSize(pev, mins, maxs); // Reset them + } + + return status; +} + + +// Initialize absmin & absmax to the appropriate box +void SetObjectCollisionBox( entvars_t *pev ) +{ + if ( (pev->solid == SOLID_BSP) && + (pev->angles.x || pev->angles.y|| pev->angles.z) ) + { // expand for rotation + float max, v; + int i; + + max = 0; + for (i=0 ; i<3 ; i++) + { + v = fabs( ((float *)pev->mins)[i]); + if (v > max) + max = v; + v = fabs( ((float *)pev->maxs)[i]); + if (v > max) + max = v; + } + for (i=0 ; i<3 ; i++) + { + ((float *)pev->absmin)[i] = ((float *)pev->origin)[i] - max; + ((float *)pev->absmax)[i] = ((float *)pev->origin)[i] + max; + } + } + else + { + pev->absmin = pev->origin + pev->mins; + pev->absmax = pev->origin + pev->maxs; + } + + pev->absmin.x -= 1; + pev->absmin.y -= 1; + pev->absmin.z -= 1; + pev->absmax.x += 1; + pev->absmax.y += 1; + pev->absmax.z += 1; +} + + +void CBaseEntity::SetObjectCollisionBox( void ) +{ + ::SetObjectCollisionBox( pev ); +} + + +int CBaseEntity :: Intersects( CBaseEntity *pOther ) +{ + if ( pOther->pev->absmin.x > pev->absmax.x || + pOther->pev->absmin.y > pev->absmax.y || + pOther->pev->absmin.z > pev->absmax.z || + pOther->pev->absmax.x < pev->absmin.x || + pOther->pev->absmax.y < pev->absmin.y || + pOther->pev->absmax.z < pev->absmin.z ) + return 0; + return 1; +} + +void CBaseEntity :: MakeDormant( void ) +{ + SetBits( pev->flags, FL_DORMANT ); + + // Don't touch + pev->solid = SOLID_NOT; + // Don't move + pev->movetype = MOVETYPE_NONE; + // Don't draw + SetBits( pev->effects, EF_NODRAW ); + // Don't think + DontThink(); + // Relink + UTIL_SetOrigin( this, pev->origin ); +} + +int CBaseEntity :: IsDormant( void ) +{ + return FBitSet( pev->flags, FL_DORMANT ); +} + +BOOL CBaseEntity :: IsInWorld( void ) +{ + // position + if (pev->origin.x >= 32768.0f) return FALSE; + if (pev->origin.y >= 32768.0f) return FALSE; + if (pev->origin.z >= 32768.0f) return FALSE; + if (pev->origin.x <= -32768.0f) return FALSE; + if (pev->origin.y <= -32768.0f) return FALSE; + if (pev->origin.z <= -32768.0f) return FALSE; + // speed + if (pev->velocity.x >= 2000) return FALSE; + if (pev->velocity.y >= 2000) return FALSE; + if (pev->velocity.z >= 2000) return FALSE; + if (pev->velocity.x <= -2000) return FALSE; + if (pev->velocity.y <= -2000) return FALSE; + if (pev->velocity.z <= -2000) return FALSE; + + return TRUE; +} + +BOOL CBaseEntity::ShouldToggle( USE_TYPE useType, BOOL currentState ) +{ + if ( useType != USE_TOGGLE && useType != USE_SET ) + { + if ( (currentState && useType == USE_ON) || (!currentState && useType == USE_OFF) ) + return FALSE; + } + return TRUE; +} + +BOOL CBaseEntity::ShouldToggle( USE_TYPE useType ) +{ + STATE currentState = GetState(); + if ( useType != USE_TOGGLE && useType != USE_SET ) + { + switch(currentState) + { + case STATE_ON: + case STATE_TURN_ON: + if (useType == USE_ON) return FALSE; + break; + case STATE_OFF: + case STATE_TURN_OFF: + if (useType == USE_OFF) return FALSE; + break; + } + } + return TRUE; +} + + +const char* CBaseEntity :: DamageDecal( int bitsDamageType ) +{ + if ( pev->rendermode != kRenderNormal ) + return "shot_glass"; + + return "shot"; +} + +BOOL CBaseEntity :: ShouldCollide( CBaseEntity *pOther ) +{ + return TRUE; +} + +// NOTE: szName must be a pointer to constant memory, e.g. "monster_class" because the entity +// will keep a pointer to it after this call. +CBaseEntity *CBaseEntity :: Create( const char *szName, const Vector &vecOrigin, const Vector &vecAngles, edict_t *pentOwner ) +{ + edict_t *pent; + CBaseEntity *pEntity; + + pent = CREATE_NAMED_ENTITY( MAKE_STRING( szName )); + if ( FNullEnt( pent ) ) + { + ALERT ( at_debug, "NULL Ent in Create!\n" ); + return NULL; + } + pEntity = Instance( pent ); + pEntity->pev->owner = pentOwner; + pEntity->pev->origin = vecOrigin; + pEntity->pev->angles = vecAngles; + DispatchSpawn( pEntity->edict() ); + return pEntity; +} + + diff --git a/dlls/cbase.h b/dlls/cbase.h new file mode 100644 index 0000000..308cbe8 --- /dev/null +++ b/dlls/cbase.h @@ -0,0 +1,1035 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +Class Hierachy + +CBaseEntity + CPointEntity + CBasePlayerAmmo + CBaseDelay + CBaseAnimating + CBasePlayerItem + CBaseToggle + CBaseButton + CBaseDoor + CBaseTrigger + CBasePlatTrain + CBaseMonster + CCycler + CBasePlayer + CCineMonster +*/ + +#define MAX_PATH_SIZE 10 // max number of nodes available for a path. + +// These are caps bits to indicate what an object's capabilities (currently used for save/restore and level transitions) +#define FCAP_CUSTOMSAVE 0x00000001 +#define FCAP_ACROSS_TRANSITION 0x00000002 // should transfer between transitions +#define FCAP_MUST_SPAWN 0x00000004 // Spawn after restore +#define FCAP_DONT_SAVE 0x80000000 // Don't save this +#define FCAP_IMPULSE_USE 0x00000008 // can be used by the player +#define FCAP_CONTINUOUS_USE 0x00000010 // can be used by the player +#define FCAP_ONOFF_USE 0x00000020 // can be used by the player +#define FCAP_DIRECTIONAL_USE 0x00000040 // Player sends +/- 1 when using (currently only tracktrains) +#define FCAP_MASTER 0x00000080 // Can be used to "master" other entities (like multisource) + // LRC: no longer used +#define FCAP_ONLYDIRECT_USE 0x00000100 //LRC - can't use this entity through a wall. +#define FCAP_DISTANCE_USE 0x00000200 //buz - can be used without distance restrictions +#define FCAP_USE_ONLY 0x00000800 //g-cont. for items & weapons + +// UNDONE: This will ignore transition volumes (trigger_transition), but not the PVS!!! +#define FCAP_FORCE_TRANSITION 0x00000080 // ALWAYS goes across transitions + +// Wargon: Ýíòèòÿ íå áóäåò ïîêàçûâàòü èêîíêó þçà, äàæå åñëè îíà þçàáåëüíà. +#define FCAP_HIDE_USE 0x00000400 + +#include "saverestore.h" +#include "schedule.h" + +#ifndef MONSTEREVENT_H +#include "monsterevent.h" +#endif + +#include "meshdesc.h" + +// C functions for external declarations that call the appropriate C++ methods + +#ifdef _WIN32 +#define EXPORT _declspec( dllexport ) +#else +#define EXPORT /* */ +#endif + +extern "C" EXPORT int GetEntityAPI( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion ); +extern "C" EXPORT int GetEntityAPI2( DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ); +extern "C" EXPORT int GetNewDLLFunctions( NEW_DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ); +extern "C" EXPORT int Server_GetPhysicsInterface( int iVersion, server_physics_api_t *pfuncsFromEngine, physics_interface_t *pFunctionTable ); +extern "C" EXPORT int Server_GetBlendingInterface( int, struct sv_blending_interface_s**, struct server_studio_api_s*, float (*t)[3][4], float (*b)[128][3][4] ); + +extern int DispatchSpawn( edict_t *pent ); +extern void DispatchKeyValue( edict_t *pentKeyvalue, KeyValueData *pkvd ); +extern void DispatchTouch( edict_t *pentTouched, edict_t *pentOther ); +extern void DispatchUse( edict_t *pentUsed, edict_t *pentOther ); +extern void DispatchThink( edict_t *pent ); +extern void DispatchBlocked( edict_t *pentBlocked, edict_t *pentOther ); +extern void DispatchSave( edict_t *pent, SAVERESTOREDATA *pSaveData ); +extern int DispatchRestore( edict_t *pent, SAVERESTOREDATA *pSaveData, int globalEntity ); +extern void DispatchObjectCollsionBox( edict_t *pent ); +extern void SaveWriteFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); +extern void SaveReadFields( SAVERESTOREDATA *pSaveData, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); +extern void SaveGlobalState( SAVERESTOREDATA *pSaveData ); +extern void RestoreGlobalState( SAVERESTOREDATA *pSaveData ); +extern void ResetGlobalState( void ); + +//extern CBaseEntity *g_pDesiredList; //LRC- handles DesiredVel, for movewith + +//LRC- added USE_SAME, USE_NOT, and USE_KILL +typedef enum +{ + USE_OFF = 0, + USE_ON = 1, + USE_SET = 2, + USE_TOGGLE = 3, + USE_KILL = 4, +// special signals, never actually get sent: + USE_SAME = 5, + USE_NOT = 6, +} USE_TYPE; + +extern char* GetStringForUseType( USE_TYPE useType ); + +extern void FireTargets( const char *targetName, CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +typedef void (CBaseEntity::*BASEPTR)(void); +typedef void (CBaseEntity::*ENTITYFUNCPTR)(CBaseEntity *pOther ); +typedef void (CBaseEntity::*USEPTR)( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +// For CLASSIFY +#define CLASS_NONE 0 +#define CLASS_MACHINE 1 +#define CLASS_PLAYER 2 +#define CLASS_HUMAN_PASSIVE 3 +#define CLASS_HUMAN_MILITARY 4 +#define CLASS_ALIEN_MILITARY 5 +#define CLASS_ALIEN_PASSIVE 6 +#define CLASS_ALIEN_MONSTER 7 +#define CLASS_ALIEN_PREY 8 +#define CLASS_ALIEN_PREDATOR 9 +#define CLASS_INSECT 10 +#define CLASS_PLAYER_ALLY 11 +#define CLASS_PLAYER_BIOWEAPON 12 // hornets and snarks.launched by players +#define CLASS_ALIEN_BIOWEAPON 13 // hornets and snarks.launched by the alien menace +#define CLASS_FACTION_A 14 //LRC - very simple new classes, for use with Behaves As +#define CLASS_FACTION_B 15 +#define CLASS_FACTION_C 16 +#define CLASS_TERROR 17 // buz +#define CLASS_BARNACLE 99 // special because no one pays attention to it, and it eats a wide cross-section of creatures. + +class CBaseEntity; +class CBaseMonster; +class CBasePlayerItem; +class CSquadMonster; +class CStudioBoneSetup; + +#define SF_NORESPAWN ( 1 << 30 )// !!!set this bit on guns and stuff that should never respawn. + +// +// EHANDLE. Safe way to point to CBaseEntities who may die between frames +// +class EHANDLE +{ +private: + edict_t *m_pent; + int m_serialnumber; +public: + edict_t *Get( void ); + edict_t *Set( edict_t *pent ); + + operator int (); + + operator CBaseEntity *(); + + CBaseEntity * operator = (CBaseEntity *pEntity); + CBaseEntity * operator ->(); +}; + + +// +// Base Entity. All entity types derive from this +// +class CBaseEntity +{ +public: + // Constructor. Set engine to use C/C++ callback functions + // pointers to engine data + entvars_t *pev; // Don't need to save/restore this pointer, the engine resets it + + // path corners + CBaseEntity *m_pGoalEnt;// path corner we are heading towards + CBaseEntity *m_pLink;// used for temporary link-list operations. + + CBaseEntity *m_pMoveWith; // LRC- the entity I move with. + int m_MoveWith; //LRC- Name of that entity + CBaseEntity *m_pChildMoveWith; //LRC- one of the entities that's moving with me. + CBaseEntity *m_pSiblingMoveWith; //LRC- another entity that's Moving With the same ent as me. (linked list.) + Vector m_vecMoveWithOffset; // LRC- Position I should be in relative to m_pMoveWith->pev->origin. + Vector m_vecRotWithOffset; // LRC- Angles I should be facing relative to m_pMoveWith->pev->angles. + CBaseEntity *m_pAssistLink; // LRC- link to the next entity which needs to be Assisted before physics are applied. + Vector m_vecPostAssistVel; // LRC + Vector m_vecPostAssistAVel; // LRC + float m_fNextThink; // LRC - for SetNextThink and SetPhysThink. Marks the time when a think will be performed - not necessarily the same as pev->nextthink! + float m_fPevNextThink; // LRC - always set equal to pev->nextthink, so that we can tell when the latter gets changed by the @#$^¬! engine. + int m_iLFlags; // LRC- a new set of flags. (pev->spawnflags and pev->flags are full...) + virtual void DesiredAction( void ) {}; // LRC - for postponing stuff until PostThink time, not as a think. + int m_iStyle; // LRC - almost anything can have a lightstyle these days... + + Vector m_vecSpawnOffset; // LRC- To fix things which (for example) MoveWith a door which Starts Open. + BOOL m_activated; // LRC- moved here from func_train. Signifies that an entity has already been + // activated. (and hence doesn't need reactivating.) + float m_flGaitYaw; // player stuff + + float m_flPoseParameter[MAXSTUDIOPOSEPARAM]; + + //LRC - decent mechanisms for setting think times! + // this should have been done a long time ago, but MoveWith finally forced me. + virtual void SetNextThink( float delay ) { SetNextThink(delay, FALSE); } + virtual void SetNextThink( float delay, BOOL correctSpeed ); + virtual void AbsoluteNextThink( float time ) { AbsoluteNextThink(time, FALSE); } + virtual void AbsoluteNextThink( float time, BOOL correctSpeed ); + void SetEternalThink( ); + // this is called by an entity which is starting to move, and will reach + // its destination after the given wait. + // Its think function should be called at that time, to make it stop moving. +// void SetPhysThink( float delay ); + // this is called by an entity which is movingWith another entity. + // it signifies that the other entity is starting to move and will reach its + // destination after the given wait. + // This entity will need to think at that time (so that physics gets + // processed correctly), but the Think function shouldn't actually get + // called; the parent will intervene to make it stop. +// void SetMWPhysThink( float delay ); + // this is called by an entity which is starting to move, and wants its children + // to follow it. +// void SetChildrenThink( float delay ); + + //LRC use this instead of "SetThink( NULL )" or "pev->nextthink = -1". + void DontThink( void ); + //LRC similar, but called by the parent when a think needs to be aborted. +// void DontMWThink( void ); + + virtual void ThinkCorrection( void ); + + virtual BOOL AllowDecal( entvars_t *pevAttacker ) { return TRUE; } + + //LRC - loci + virtual Vector CalcPosition( CBaseEntity *pLocus ) { return pev->origin; } + virtual Vector CalcVelocity( CBaseEntity *pLocus ) { return pev->velocity; } + virtual float CalcRatio( CBaseEntity *pLocus ) { return 0; } + + //LRC - aliases + virtual BOOL IsAlias( void ) { return FALSE; } + + // buz - gas reactions (should be used by soldiers to turn gas mask on and off) + virtual void GasWarning( int warnlevel ) {}; + + void ResetPoseParameters( void ); + int LookupPoseParameter( const char *szName ); + void SetPoseParameter( int iParameter, float flValue ); + float GetPoseParameter( int iParameter ); + void SetPoseParameter( const char *szName, float flValue ); + float GetPoseParameter( const char *szName ); + bool HasPoseParameter( int iSequence, const char *szName ); + bool HasPoseParameter( int iSequence, int iParameter ); + + // initialization functions + virtual void Spawn( void ) { return; } + virtual void Precache( void ) { return; } + virtual void KeyValue( KeyValueData* pkvd) + { + //LRC - MoveWith for all! + if (FStrEq(pkvd->szKeyName, "movewith")) + { + m_MoveWith = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "skill")) + { + m_iLFlags = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "style")) + { + m_iStyle = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if( FStrEq( pkvd->szKeyName, "reflection" )) + { + switch( atoi( pkvd->szValue )) + { + case 1: pev->effects |= EF_NOREFLECT; break; + case 2: pev->effects |= EF_REFLECTONLY; break; + } + } + else pkvd->fHandled = FALSE; + } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + //LRC - if I MoveWith something, then only cross transitions if the MoveWith entity does too. + virtual int ObjectCaps( void ) { return m_pMoveWith?m_pMoveWith->ObjectCaps()&FCAP_ACROSS_TRANSITION:FCAP_ACROSS_TRANSITION; } + virtual void Activate( void ); //LRC + void InitMoveWith( void ); //LRC - called by Activate() to set up moveWith values + virtual void PostSpawn( void ) {} //LRC - called by Activate() to handle entity-specific initialisation. + // (mostly setting positions, for MoveWith support) + + void FireBulletsWater( Vector vecEnd, Vector vecSrc, float ScaleSplash1, float ScaleSplash2 ); + + // Setup the object->object collision box (pev->mins / pev->maxs is the object->world collision box) + virtual void SetObjectCollisionBox( void ); + +// Classify - returns the type of group (e.g., "alien monster", or "human military" so that monsters +// on the same side won't attack each other, even if they have different classnames. + virtual int Classify ( void ) { return CLASS_NONE; }; + virtual void DeathNotice ( entvars_t *pevChild ) {}// monster maker children use this to tell the monster maker that they have died. + + +// LRC- this supports a global concept of "entities with states", so that state_watchers and +// mastership (mastery? masterhood?) can work universally. + virtual STATE GetState ( void ) { return STATE_OFF; }; + +// For team-specific doors in multiplayer, etc: a master's state depends on who wants to know. + virtual STATE GetState ( CBaseEntity* pEnt ) { return GetState(); }; + + static TYPEDESCRIPTION m_SaveData[]; + + virtual void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + virtual int TakeHealth( float flHealth, int bitsDamageType ); + virtual void Killed( entvars_t *pevAttacker, int iGib ); + virtual int BloodColor( void ) { return DONT_BLEED; } + virtual void TraceBleed( float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ); +//LRC- superceded by GetState ( pActivator ). +// virtual BOOL IsTriggered( CBaseEntity *pActivator ) {return TRUE;} + virtual CBaseMonster *MyMonsterPointer( void ) { return NULL;} + virtual CSquadMonster *MySquadMonsterPointer( void ) { return NULL;} + virtual int GetToggleState( void ) { return TS_AT_TOP; } + virtual void AddPoints( int score, BOOL bAllowNegativeScore ) {} + virtual void AddPointsToTeam( int score, BOOL bAllowNegativeScore ) {} + virtual BOOL AddPlayerItem( CBasePlayerItem *pItem ) { return 0; } + virtual BOOL RemovePlayerItem( CBasePlayerItem *pItem ) { return 0; } + virtual int GiveAmmo( int iAmount, const char *szName ) { return -1; }; + virtual float GetDelay( void ) { return 0; } + virtual int IsMoving( void ) { return pev->velocity != g_vecZero; } + virtual void OverrideReset( void ) {} + virtual const char* DamageDecal( int bitsDamageType ); + // This is ONLY used by the node graph to test movement through a door + virtual void SetToggleState( int state ) {} + virtual void StartSneaking( void ) {} + virtual void StopSneaking( void ) {} + virtual BOOL OnControls( entvars_t *pev ) { return FALSE; } + virtual BOOL IsSneaking( void ) { return FALSE; } + virtual BOOL IsAlive( void ) { return (pev->deadflag == DEAD_NO) && pev->health > 0; } + virtual BOOL IsBSPModel( void ) { return pev->solid == SOLID_BSP || pev->movetype == MOVETYPE_PUSHSTEP; } + virtual BOOL HasTarget( string_t targetname ) { return FStrEq(STRING(targetname), STRING(pev->targetname) ); } + virtual BOOL IsInWorld( void ); + virtual BOOL IsPlayer( void ) { return FALSE; } + virtual BOOL IsNetClient( void ) { return FALSE; } + virtual const char *TeamID( void ) { return ""; } + +// virtual void SetActivator( CBaseEntity *pActivator ) {} + virtual CBaseEntity *GetNextTarget( void ); + + // fundamental callbacks + void (CBaseEntity ::*m_pfnThink)(void); + void (CBaseEntity ::*m_pfnTouch)( CBaseEntity *pOther ); + void (CBaseEntity ::*m_pfnUse)( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void (CBaseEntity ::*m_pfnBlocked)( CBaseEntity *pOther ); + + virtual void Think( void ) { if (m_pfnThink) (this->*m_pfnThink)(); }; + virtual void Touch( CBaseEntity *pOther ) { if (m_pfnTouch) (this->*m_pfnTouch)( pOther ); }; + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) + { + if (m_pfnUse) + (this->*m_pfnUse)( pActivator, pCaller, useType, value ); + } + virtual void Blocked( CBaseEntity *pOther ) { if (m_pfnBlocked) (this->*m_pfnBlocked)( pOther ); }; + + // allow engine to allocate instance data + void *operator new( size_t stAllocateBlock, entvars_t *pev ) + { + return (void *)ALLOC_PRIVATE(ENT(pev), stAllocateBlock); + }; + + // don't use this. +#if _MSC_VER >= 1200 // only build this code if MSVC++ 6.0 or higher + void operator delete(void *pMem, entvars_t *pev) + { + pev->flags |= FL_KILLME; + }; +#endif + + void UpdateOnRemove( void ); + + // common member functions + void EXPORT SUB_Remove( void ); + void EXPORT SUB_DoNothing( void ); + void EXPORT SUB_StartFadeOut ( void ); + void EXPORT SUB_FadeOut ( void ); + void EXPORT SUB_CallUseToggle( void ) { this->Use( this, this, USE_TOGGLE, 0 ); } // a think function used at spawn time. Don't apply the moveWith fix to it. + int ShouldToggle( USE_TYPE useType, BOOL currentState ); + int ShouldToggle( USE_TYPE useType ); //LRC this version uses GetState() + void FireBullets( ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, float flDamage = 0.0f, entvars_t *pevAttacker = NULL ); + Vector FireBulletsPlayer( ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, float flDamage, entvars_t *pevAttacker = NULL, int shared_rand = 0 ); + + virtual CBaseEntity *Respawn( void ) { return NULL; } + + void SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ); + // Do the bounding boxes of these two intersect? + int Intersects( CBaseEntity *pOther ); + void MakeDormant( void ); + int IsDormant( void ); + BOOL IsLockedByMaster( void ) { return FALSE; } + + static CBaseEntity *Instance( edict_t *pent ) + { + if ( !pent ) + pent = ENT(0); + CBaseEntity *pEnt = (CBaseEntity *)GET_PRIVATE(pent); + return pEnt; + } + + static CBaseEntity *Instance( entvars_t *pev ) { return Instance( ENT( pev ) ); } + static CBaseEntity *Instance( int eoffset) { return Instance( ENT( eoffset) ); } + + CBaseMonster *GetMonsterPointer( entvars_t *pevMonster ) + { + CBaseEntity *pEntity = Instance( pevMonster ); + if ( pEntity ) + return pEntity->MyMonsterPointer(); + return NULL; + } + CBaseMonster *GetMonsterPointer( edict_t *pentMonster ) + { + CBaseEntity *pEntity = Instance( pentMonster ); + if ( pEntity ) + return pEntity->MyMonsterPointer(); + return NULL; + } + + struct model_s *GetModel( void ) + { + if( g_fPhysicInitialized ) + return (struct model_s *)MODEL_HANDLE( pev->modelindex ); + return NULL; + } + + // Ugly code to lookup all functions to make sure they are exported when set. +#ifdef _DEBUG + void FunctionCheck( void *pFunction, char *name ) + { + if (pFunction && !NAME_FOR_FUNCTION((unsigned long)(pFunction)) ) + ALERT( at_error, "No EXPORT: %s:%s (%08lx)\n", STRING(pev->classname), name, (unsigned long)pFunction ); + } + + BASEPTR ThinkSet( BASEPTR func, char *name ) + { + m_pfnThink = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnThink)))), name ); + return func; + } + ENTITYFUNCPTR TouchSet( ENTITYFUNCPTR func, char *name ) + { + m_pfnTouch = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnTouch)))), name ); + return func; + } + USEPTR UseSet( USEPTR func, char *name ) + { + m_pfnUse = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnUse)))), name ); + return func; + } + ENTITYFUNCPTR BlockedSet( ENTITYFUNCPTR func, char *name ) + { + m_pfnBlocked = func; + FunctionCheck( (void *)*((int *)((char *)this + ( offsetof(CBaseEntity,m_pfnBlocked)))), name ); + return func; + } + +#endif + + + // virtual functions used by a few classes + + // used by monsters that are created by the MonsterMaker + virtual void UpdateOwner( void ) { return; }; + + + // + static CBaseEntity *Create( const char *szName, const Vector &vecOrigin, const Vector &vecAngles, edict_t *pentOwner = NULL ); + + virtual BOOL FBecomeProne( void ) {return FALSE;}; + edict_t *edict() { return ENT( pev ); }; + EOFFSET eoffset( ) { return OFFSET( pev ); }; + int entindex( ) { return ENTINDEX( edict() ); }; + + virtual Vector Center( ) { return (pev->absmin + pev->absmax) * 0.5; }; // center point of entity + virtual Vector EyePosition( ) { return pev->origin + pev->view_ofs; }; // position of eyes + virtual Vector EarPosition( ) { return pev->origin + pev->view_ofs; }; // position of ears + virtual Vector BodyTarget( const Vector &posSrc ) { return Center( ); }; // position to shoot at + + virtual int Illumination( ) { return GETENTITYILLUM( ENT( pev ) ); }; + + virtual BOOL FVisible ( CBaseEntity *pEntity ); + virtual BOOL FVisible ( const Vector &vecOrigin ); + virtual BOOL ShouldCollide( CBaseEntity *pOther ); + + virtual void SendInitMessage( CBasePlayer *player ) {}; // buz - does nothing by default +}; + +//LRC- moved here from player.cpp. I'd put it in util.h with its friends, but it needs CBaseEntity to be declared. +inline BOOL FNullEnt( CBaseEntity *ent ) { return ent == NULL || FNullEnt( ent->edict() ); } + +// Ugly technique to override base member functions +// Normally it's illegal to cast a pointer to a member function of a derived class to a pointer to a +// member function of a base class. static_cast is a sleezy way around that problem. + +#ifdef _DEBUG + +#define SetThink( a ) ThinkSet( static_cast (a), #a ) +#define SetTouch( a ) TouchSet( static_cast (a), #a ) +#define SetUse( a ) UseSet( static_cast (a), #a ) +#define SetBlocked( a ) BlockedSet( static_cast (a), #a ) + +#else + +#define SetThink( a ) m_pfnThink = static_cast (a) +#define SetTouch( a ) m_pfnTouch = static_cast (a) +#define SetUse( a ) m_pfnUse = static_cast (a) +#define SetBlocked( a ) m_pfnBlocked = static_cast (a) + +#endif + + +class CPointEntity : public CBaseEntity +{ +public: + void Spawn( void ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +private: +}; + + +typedef struct locksounds // sounds that doors and buttons make when locked/unlocked +{ + string_t sLockedSound; // sound a door makes when it's locked + string_t sLockedSentence; // sentence group played when door is locked + string_t sUnlockedSound; // sound a door makes when it's unlocked + string_t sUnlockedSentence; // sentence group played when door is unlocked + + int iLockedSentence; // which sentence in sentence group to play next + int iUnlockedSentence; // which sentence in sentence group to play next + + float flwaitSound; // time delay between playing consecutive 'locked/unlocked' sounds + float flwaitSentence; // time delay between playing consecutive sentences + BYTE bEOFLocked; // true if hit end of list of locked sentences + BYTE bEOFUnlocked; // true if hit end of list of unlocked sentences +} locksound_t; + +void PlayLockSounds(entvars_t *pev, locksound_t *pls, int flocked, int fbutton); + +// +// MultiSouce +// + +#define MAX_MULTI_TARGETS 16 // maximum number of targets a single multi_manager entity may be assigned. +#define MS_MAX_TARGETS 32 + +class CMultiSource : public CPointEntity +{ +public: + void Spawn( ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + STATE GetState( void ); + void EXPORT Register( void ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + EHANDLE m_rgEntities[MS_MAX_TARGETS]; + int m_rgTriggered[MS_MAX_TARGETS]; + + int m_iTotal; + string_t m_globalstate; +}; + + +// +// generic Delay entity. +// +class CBaseDelay : public CBaseEntity +{ +public: + float m_flDelay; + int m_iszKillTarget; + EHANDLE m_hActivator; //LRC - moved here from CBaseToggle + + virtual void KeyValue( KeyValueData* pkvd); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + // common member functions + void SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ); + void EXPORT DelayThink( void ); +}; + + +class CBaseAnimating : public CBaseDelay +{ +public: + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + // Basic Monster Animation functions + float StudioFrameAdvance( float flInterval = 0.0 ); // accumulate animation frame time from last time called until now + float StudioGaitFrameAdvance( void ); // accumulate gait frame time from last time called until now + int GetSequenceFlags( void ); + int LookupActivity ( int activity ); + int LookupActivityHeaviest ( int activity ); + int LookupSequence ( const char *label ); + static void *GetModelPtr( int modelindex ); + void *GetModelPtr( void ); + void ResetSequenceInfo ( ); + float SequenceDuration( int iSequence ); + void DispatchAnimEvents ( float flFutureInterval = 0.1 ); // Handle events that have happend since last time called up until X seconds into the future + virtual void HandleAnimEvent( MonsterEvent_t *pEvent ) { return; }; + float SetBoneController ( int iController, float flValue ); + void InitBoneControllers ( void ); + float SetBlending ( int iBlender, float flValue ); + void GetBonePosition ( int iBone, Vector &origin, Vector &angles ); + void GetAutomovement( Vector &origin, Vector &angles, float flInterval = 0.1 ); + int FindTransition( int iEndingSequence, int iGoalSequence, int *piDir ); + void GetAttachment ( int iAttachment, Vector &origin, Vector &angles ); + int GetAttachment ( const char *pszAttachment, Vector &origin, Vector &angles ); + void SetBodygroup( int iGroup, int iValue ); + int GetBodygroup( int iGroup ); + int ExtractBbox( int sequence, Vector &mins, Vector &maxs ); + int GetHitboxSetByName( const char *szName ); + void SetSequenceBox( void ); + CStudioBoneSetup *GetBoneSetup(); + int GetSequenceCount( void ); + float GetSequenceMoveYaw( int iSequence ); + float GetSequenceMoveDist( int iSequence ); + float GetSequenceGroundSpeed( int iSequence ); + + // buz + float GetControllerBound( int iController ); + + const char *GetNameForActivity( int activity ); + + // animation needs + float m_flFrameRate; // computed FPS for current sequence + float m_flGroundSpeed; // computed linear movement rate for current sequence + float m_flLastEventCheck; // last time the event list was checked + BOOL m_fSequenceFinished;// flag set when StudioAdvanceFrame moves across a frame boundry + BOOL m_fSequenceLoops; // true if the sequence loops + // gaitsequence needs + float m_flGaitMovement; +}; + + +// +// generic Toggle entity. +// +#define SF_ITEM_USE_ONLY 256 // ITEM_USE_ONLY = BUTTON_USE_ONLY = DOOR_USE_ONLY!!! + +class CBaseToggle : public CBaseAnimating +{ +public: + void KeyValue( KeyValueData *pkvd ); + + TOGGLE_STATE m_toggle_state; + float m_flActivateFinished;//like attack_finished, but for doors + float m_flMoveDistance;// how far a door should slide or rotate + float m_flWait; + float m_flLip; + float m_flTWidth;// for plats + float m_flTLength;// for plats + + Vector m_vecPosition1; + Vector m_vecPosition2; + Vector m_vecAngle1; + Vector m_vecAngle2; + + int m_cTriggersLeft; // trigger_counter only, # of activations remaining + float m_flHeight; + void (CBaseToggle::*m_pfnCallWhenMoveDone)(void); + Vector m_vecFinalDest; + float m_flLinearMoveSpeed; // LRC- allows a LinearMove to be delayed until a think. + float m_flAngularMoveSpeed; // LRC + Vector m_vecFinalAngle; + + int m_bitsDamageInflict; // DMG_ damage type that the door or tigger does + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + virtual int GetToggleState( void ) { return m_toggle_state; } + + // LRC- overridden because toggling entities have general rules governing their states. + virtual STATE GetState( void ); + + virtual float GetDelay( void ) { return m_flWait; } + + // common member functions + void LinearMove( Vector vecInput, float flSpeed ); + //void LinearMove( Vector vecInput, float flSpeed, BOOL bNow ); + void EXPORT LinearMoveNow( void ); //LRC- think function that lets us guarantee a LinearMove gets done as a think. + void EXPORT LinearMoveDone( void ); + void EXPORT LinearMoveDoneNow( void ); //LRC +// void EXPORT LinearMoveFinalDone( void ); + void AngularMove( Vector vecDestAngle, float flSpeed ); + void EXPORT AngularMoveNow( void ); //LRC- think function that lets us guarantee an AngularMove gets done as a think. + void EXPORT AngularMoveDone( void ); + void EXPORT AngularMoveDoneNow( void ); + BOOL IsLockedByMaster( void ); + + static float AxisValue( int flags, const Vector &angles ); + static void AxisDir( entvars_t *pev ); + static float AxisDelta( int flags, const Vector &angle1, const Vector &angle2 ); + + string_t m_sMaster; // If this button has a master switch, this is the targetname. + // A master switch must be of the multisource type. If all + // of the switches in the multisource have been triggered, then + // the button will be allowed to operate. Otherwise, it will be + // deactivated. +}; +#define SetMoveDone( a ) m_pfnCallWhenMoveDone = static_cast (a) + + +// people gib if their health is <= this at the time of death +#define GIB_HEALTH_VALUE -30 + +#define ROUTE_SIZE 8 // how many waypoints a monster can store at one time +#define MAX_OLD_ENEMIES 4 // how many old enemies to remember + +#define bits_CAP_DUCK ( 1 << 0 )// crouch +#define bits_CAP_JUMP ( 1 << 1 )// jump/leap +#define bits_CAP_STRAFE ( 1 << 2 )// strafe ( walk/run sideways) +#define bits_CAP_SQUAD ( 1 << 3 )// can form squads +#define bits_CAP_SWIM ( 1 << 4 )// proficiently navigate in water +#define bits_CAP_CLIMB ( 1 << 5 )// climb ladders/ropes +#define bits_CAP_USE ( 1 << 6 )// open doors/push buttons/pull levers +#define bits_CAP_HEAR ( 1 << 7 )// can hear forced sounds +#define bits_CAP_AUTO_DOORS ( 1 << 8 )// can trigger auto doors +#define bits_CAP_OPEN_DOORS ( 1 << 9 )// can open manual doors +#define bits_CAP_TURN_HEAD ( 1 << 10)// can turn head, always bone controller 0 + +#define bits_CAP_RANGE_ATTACK1 ( 1 << 11)// can do a range attack 1 +#define bits_CAP_RANGE_ATTACK2 ( 1 << 12)// can do a range attack 2 +#define bits_CAP_MELEE_ATTACK1 ( 1 << 13)// can do a melee attack 1 +#define bits_CAP_MELEE_ATTACK2 ( 1 << 14)// can do a melee attack 2 + +#define bits_CAP_CROUCH_COVER ( 1 << 16)// buz + +#define bits_CAP_FLY ( 1 << 15)// can fly, move all around + +#define bits_CAP_DOORS_GROUP (bits_CAP_USE | bits_CAP_AUTO_DOORS | bits_CAP_OPEN_DOORS) + +// used by suit voice to indicate damage sustained and repaired type to player + +// instant damage + +#define DMG_GENERIC 0 // generic damage was done +#define DMG_CRUSH (1 << 0) // crushed by falling or moving object +#define DMG_BULLET (1 << 1) // shot +#define DMG_SLASH (1 << 2) // cut, clawed, stabbed +#define DMG_BURN (1 << 3) // heat burned +#define DMG_FREEZE (1 << 4) // frozen +#define DMG_FALL (1 << 5) // fell too far +#define DMG_BLAST (1 << 6) // explosive blast damage +#define DMG_CLUB (1 << 7) // crowbar, punch, headbutt +#define DMG_SHOCK (1 << 8) // electric shock +#define DMG_SONIC (1 << 9) // sound pulse shockwave +#define DMG_ENERGYBEAM (1 << 10) // laser or other high energy beam +#define DMG_NEVERGIB (1 << 12) // with this bit OR'd in, no damage type will be able to gib victims upon death +#define DMG_ALWAYSGIB (1 << 13) // with this bit OR'd in, any damage type can be made to gib victims upon death. +#define DMG_DROWN (1 << 14) // Drowning +// time-based damage +#define DMG_TIMEBASED (~(0x3fff)) // mask for time-based damage + +#define DMG_PARALYZE (1 << 15) // slows affected creature down +#define DMG_NERVEGAS (1 << 16) // nerve toxins, very bad +#define DMG_POISON (1 << 17) // blood poisioning +#define DMG_RADIATION (1 << 18) // radiation exposure +#define DMG_DROWNRECOVER (1 << 19) // drowning recovery +#define DMG_ACID (1 << 20) // toxic chemicals or acid burns +#define DMG_SLOWBURN (1 << 21) // in an oven +#define DMG_SLOWFREEZE (1 << 22) // in a subzero freezer +#define DMG_MORTAR (1 << 23) // Hit by air raid (done to distinguish grenade from mortar) + +// these are the damage types that are allowed to gib corpses +#define DMG_GIB_CORPSE ( DMG_CRUSH | DMG_FALL | DMG_BLAST | DMG_SONIC /* | DMG_CLUB */ ) // Wargon: DMG_CLUB íå ìîæåò ãèáàòü òðóïû. + +// these are the damage types that have client hud art +#define DMG_SHOWNHUD (DMG_POISON | DMG_ACID | DMG_FREEZE | DMG_SLOWFREEZE | DMG_DROWN | DMG_BURN | DMG_SLOWBURN | DMG_NERVEGAS | DMG_RADIATION | DMG_SHOCK) + +// NOTE: tweak these values based on gameplay feedback: + +#define PARALYZE_DURATION 2 // number of 2 second intervals to take damage +#define PARALYZE_DAMAGE 1.0 // damage to take each 2 second interval + +#define NERVEGAS_DURATION 2 +#define NERVEGAS_DAMAGE 5.0 + +#define POISON_DURATION 5 +#define POISON_DAMAGE 2.0 + +#define RADIATION_DURATION 2 +#define RADIATION_DAMAGE 1.0 + +#define ACID_DURATION 2 +#define ACID_DAMAGE 5.0 + +#define SLOWBURN_DURATION 2 +#define SLOWBURN_DAMAGE 1.0 + +#define SLOWFREEZE_DURATION 2 +#define SLOWFREEZE_DAMAGE 1.0 + + +#define itbd_Paralyze 0 +#define itbd_NerveGas 1 +#define itbd_Poison 2 +#define itbd_Radiation 3 +#define itbd_DrownRecover 4 +#define itbd_Acid 5 +#define itbd_SlowBurn 6 +#define itbd_SlowFreeze 7 +#define CDMG_TIMEBASED 8 + +// when calling KILLED(), a value that governs gib behavior is expected to be +// one of these three values +#define GIB_NORMAL 0// gib if entity was overkilled +#define GIB_NEVER 1// never gib, no matter how much death damage is done ( freezing, etc ) +#define GIB_ALWAYS 2// always gib ( Houndeye Shock, Barnacle Bite ) + +class CBaseMonster; +class CCineMonster; +class CSound; + +#include "basemonster.h" + + +char *ButtonSound( int sound ); // get string of button sound number + + +// +// Generic Button +// +class CBaseButton : public CBaseToggle +{ +public: + void Spawn( void ); + virtual void PostSpawn( void ); //LRC + virtual void Precache( void ); + void RotSpawn( void ); + virtual void KeyValue( KeyValueData* pkvd); + + void ButtonActivate( ); + void SparkSoundCache( void ); + + void EXPORT ButtonShot( void ); + void EXPORT ButtonTouch( CBaseEntity *pOther ); + void EXPORT ButtonSpark ( void ); + void EXPORT TriggerAndWait( void ); + void EXPORT ButtonReturn( void ); + void EXPORT ButtonBackHome( void ); + void EXPORT ButtonUse_IgnorePlayer( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT ButtonUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + enum BUTTON_CODE { BUTTON_NOTHING, BUTTON_ACTIVATE, BUTTON_RETURN }; + BUTTON_CODE ButtonResponseToTouch( void ); + + static TYPEDESCRIPTION m_SaveData[]; + virtual int ObjectCaps( void ); + + BOOL m_fStayPushed; // button stays pushed in until touched again? + BOOL m_fRotating; // a rotating button? default is a sliding button. + + string_t m_strChangeTarget; // if this field is not null, this is an index into the engine string array. + // when this button is touched, it's target entity's TARGET field will be set + // to the button's ChangeTarget. This allows you to make a func_train switch paths, etc. + + locksound_t m_ls; // door lock sounds + + BYTE m_bLockedSound; // ordinals from entity selection + BYTE m_bLockedSentence; + BYTE m_bUnlockedSound; + BYTE m_bUnlockedSentence; + int m_sounds; + + // Wargon: Ïåðåìåííàÿ äëÿ ñêðûòèÿ èêîíêè þçà. + int m_hide_use; +}; + +// +// Weapons +// + +#define BAD_WEAPON 0x00007FFF + +// +// Converts a entvars_t * to a class pointer +// It will allocate the class and entity if necessary +// +template T * GetClassPtr( T *a ) +{ + entvars_t *pev = (entvars_t *)a; + + // allocate entity if necessary + if (pev == NULL) + pev = VARS(CREATE_ENTITY()); + + // get the private data + a = (T *)GET_PRIVATE(ENT(pev)); + + if (a == NULL) + { + // allocate private data + a = new(pev) T; + a->pev = pev; + } + return a; +} + + +/* +bit_PUSHBRUSH_DATA | bit_TOGGLE_DATA +bit_MONSTER_DATA +bit_DELAY_DATA +bit_TOGGLE_DATA | bit_DELAY_DATA | bit_MONSTER_DATA +bit_PLAYER_DATA | bit_MONSTER_DATA +bit_MONSTER_DATA | CYCLER_DATA +bit_LIGHT_DATA +path_corner_data +bit_MONSTER_DATA | wildcard_data +bit_MONSTER_DATA | bit_GROUP_DATA +boid_flock_data +boid_data +CYCLER_DATA +bit_ITEM_DATA +bit_ITEM_DATA | func_hud_data +bit_TOGGLE_DATA | bit_ITEM_DATA +EOFFSET +env_sound_data +env_sound_data +push_trigger_data +*/ + +typedef struct _SelAmmo +{ + BYTE Ammo1Type; + BYTE Ammo1; + BYTE Ammo2Type; + BYTE Ammo2; +} SelAmmo; + +//LRC- much as I hate to add new globals, I can't see how to read data from the World entity. +//extern BOOL g_startSuit; + +//LRC- moved here from alias.cpp so that util functions can use these defs. +class CBaseAlias : public CPointEntity +{ +public: + BOOL IsAlias( void ) { return TRUE; }; + virtual CBaseEntity *FollowAlias( CBaseEntity *pFrom ) { return NULL; }; + virtual void ChangeValue( int iszValue ) { ALERT(at_error, "%s entities cannot change value!", STRING(pev->classname)); } + virtual void ChangeValue( CBaseEntity *pValue ) { ChangeValue(pValue->pev->targetname); } + virtual void FlushChanges( void ) {}; + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + CBaseAlias *m_pNextAlias; +}; + +class CInfoGroup : public CPointEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Use(CBaseEntity* pActivator, CBaseEntity* pCaller, USE_TYPE useType, float value); + int GetMember( const char* szMemberName ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int m_cMembers; + int m_iszMemberName [ MAX_MULTI_TARGETS ]; + int m_iszMemberValue [ MAX_MULTI_TARGETS ]; + int m_iszDefaultMember; +}; + +class CMultiAlias : public CBaseAlias +{ +public: + void KeyValue( KeyValueData *pkvd ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + CBaseEntity *FollowAlias( CBaseEntity *pFrom ); + + int m_cTargets; + int m_iszTargets [ MAX_MULTI_TARGETS ]; + int m_iTotalValue; + int m_iValues [ MAX_MULTI_TARGETS ]; + int m_iMode; +}; + + +// this moved here from world.cpp, to allow classes to be derived from it +//======================= +// CWorld +// +// This spawns first when each level begins. +//======================= +class CWorld : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + CBaseAlias *m_pFirstAlias; + Vector m_vecTime; +}; + +extern CWorld *g_pWorld; diff --git a/dlls/client.cpp b/dlls/client.cpp new file mode 100644 index 0000000..e937c35 --- /dev/null +++ b/dlls/client.cpp @@ -0,0 +1,1996 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Robin, 4-22-98: Moved set_suicide_frame() here from player.cpp to allow us to +// have one without a hardcoded player.mdl in tf_client.cpp + +/* + +===== client.cpp ======================================================== + + client/server game specific stuff + +*/ + +#include + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "player.h" +#include "spectator.h" +#include "client.h" +#include "soundent.h" +#include "gamerules.h" +#include "game.h" +#include "customentity.h" +#include "weapons.h" +#include "weaponinfo.h" +#include "usercmd.h" +#include "netadr.h" +#include "movewith.h" +#include "material.h" +#include "skill.h" // buz + +extern DLL_GLOBAL ULONG g_ulModelIndexPlayer; +extern DLL_GLOBAL BOOL g_fGameOver; +extern DLL_GLOBAL int g_iSkillLevel; +extern DLL_GLOBAL ULONG g_ulFrameCount; + +extern void CopyToBodyQue(entvars_t* pev); +extern int gmsgSayText; +extern int gmsgHUDColor; +extern int giPrecacheGrunt; + +extern int g_teamplay; + +/* + * used by kill command and disconnect command + * ROBIN: Moved here from player.cpp, to allow multiple player models + */ +void set_suicide_frame(entvars_t* pev) +{ + if (!FStrEq(STRING(pev->model), "models/player.mdl")) + return; // allready gibbed + +// pev->frame = $deatha11; + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_TOSS; + pev->deadflag = DEAD_DEAD; + pev->nextthink = -1; +} + + +/* +=========== +ClientConnect + +called when a player connects to a server +============ +*/ +BOOL ClientConnect( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) +{ + return g_pGameRules->ClientConnected( pEntity, pszName, pszAddress, szRejectReason ); + +// a client connecting during an intermission can cause problems +// if (intermission_running) +// ExitIntermission (); + +} + + +/* +=========== +ClientDisconnect + +called when a player disconnects from a server + +GLOBALS ASSUMED SET: g_fGameOver +============ +*/ +void ClientDisconnect( edict_t *pEntity ) +{ + if (g_fGameOver) + return; + + char text[256]; + sprintf( text, "- %s has left the game\n", STRING(pEntity->v.netname) ); + MESSAGE_BEGIN( MSG_ALL, gmsgSayText, NULL ); + WRITE_BYTE( ENTINDEX(pEntity) ); + WRITE_STRING( text ); + MESSAGE_END(); + + CSound *pSound; + pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex( pEntity ) ); + { + // since this client isn't around to think anymore, reset their sound. + if ( pSound ) + { + pSound->Reset(); + } + } + +// since the edict doesn't get deleted, fix it so it doesn't interfere. + pEntity->v.takedamage = DAMAGE_NO;// don't attract autoaim + pEntity->v.solid = SOLID_NOT;// nonsolid + UTIL_SetEdictOrigin ( pEntity, pEntity->v.origin ); + + g_pGameRules->ClientDisconnected( pEntity ); +} + + +// called by ClientKill and DeadThink +void respawn(entvars_t* pev, BOOL fCopyCorpse) +{ + if (gpGlobals->coop || gpGlobals->deathmatch) + { + if ( fCopyCorpse ) + { + // make a copy of the dead body for appearances sake + CopyToBodyQue(pev); + } + + // respawn player + GetClassPtr( (CBasePlayer *)pev)->Spawn( ); + } + else + { // restart the entire server + SERVER_COMMAND("reload\n"); + } +} + +/* +============ +ClientKill + +Player entered the suicide command + +GLOBALS ASSUMED SET: g_ulModelIndexPlayer +============ +*/ +void ClientKill( edict_t *pEntity ) +{ + entvars_t *pev = &pEntity->v; + + CBasePlayer *pl = (CBasePlayer*) CBasePlayer::Instance( pev ); + + if ( pl->m_fNextSuicideTime > gpGlobals->time ) + return; // prevent suiciding too ofter + + pl->m_fNextSuicideTime = gpGlobals->time + 1; // don't let them suicide for 5 seconds after suiciding + + // have the player kill themself + pev->health = 0; + pl->Killed( pev, GIB_NEVER ); + +// pev->modelindex = g_ulModelIndexPlayer; +// pev->frags -= 2; // extra penalty +// respawn( pev ); +} + +/* +=========== +ClientPutInServer + +called each time a player is spawned +============ +*/ +void ClientPutInServer( edict_t *pEntity ) +{ + CBasePlayer *pPlayer; + + entvars_t *pev = &pEntity->v; + + pPlayer = GetClassPtr((CBasePlayer *)pev); + pPlayer->SetCustomDecalFrames(-1); // Assume none; + + // Allocate a CBasePlayer for pev, and call spawn + pPlayer->Spawn() ; + + // Reset interpolation during first frame + pPlayer->pev->effects |= EF_NOINTERP; +} + +#include "voice_gamemgr.h" +extern CVoiceGameMgr g_VoiceGameMgr; + +//// HOST_SAY +// String comes in as +// say blah blah blah +// or as +// blah blah blah +// +void Host_Say( edict_t *pEntity, int teamonly ) +{ + CBasePlayer *client; + int j; + char *p; + char text[128]; + char szTemp[256]; + const char *cpSay = "say"; + const char *cpSayTeam = "say_team"; + const char *pcmd = CMD_ARGV(0); + + // We can get a raw string now, without the "say " prepended + if ( CMD_ARGC() == 0 ) + return; + + entvars_t *pev = &pEntity->v; + CBasePlayer* player = GetClassPtr((CBasePlayer *)pev); + + //Not yet. + if ( player->m_flNextChatTime > gpGlobals->time ) + return; + + if ( !stricmp( pcmd, cpSay) || !stricmp( pcmd, cpSayTeam ) ) + { + if ( CMD_ARGC() >= 2 ) + { + p = (char *)CMD_ARGS(); + } + else + { + // say with a blank message, nothing to do + return; + } + } + else // Raw text, need to prepend argv[0] + { + if ( CMD_ARGC() >= 2 ) + { + sprintf( szTemp, "%s %s", ( char * )pcmd, (char *)CMD_ARGS() ); + } + else + { + // Just a one word command, use the first word...sigh + sprintf( szTemp, "%s", ( char * )pcmd ); + } + p = szTemp; + } + +// remove quotes if present + if (*p == '"') + { + p++; + p[strlen(p)-1] = 0; + } + +// make sure the text has content + char *pc; + for ( pc = p; pc != NULL && *pc != 0; pc++ ) + { + if ( isprint( *pc ) && !isspace( *pc ) ) + { + pc = NULL; // we've found an alphanumeric character, so text is valid + break; + } + } + if ( pc != NULL ) + return; // no character found, so say nothing + +// turn on color set 2 (color on, no sound) + if ( teamonly ) + sprintf( text, "%c(TEAM) %s: ", 2, STRING( pEntity->v.netname ) ); + else + sprintf( text, "%c%s: ", 2, STRING( pEntity->v.netname ) ); + + j = sizeof(text) - 2 - strlen(text); // -2 for /n and null terminator + if ( (int)strlen(p) > j ) + p[j] = 0; + + strcat( text, p ); + strcat( text, "\n" ); + + + player->m_flNextChatTime = gpGlobals->time + CHAT_INTERVAL; + + // loop through all players + // Start with the first player. + // This may return the world in single player if the client types something between levels or during spawn + // so check it, or it will infinite loop + + client = NULL; + while ( ((client = (CBasePlayer*)UTIL_FindEntityByClassname( client, "player" )) != NULL) && (!FNullEnt(client->edict())) ) + { + if ( !client->pev ) + continue; + + if ( client->edict() == pEntity ) + continue; + + if ( !(client->IsNetClient()) ) // Not a client ? (should never be true) + continue; + + // can the receiver hear the sender? or has he muted him? + if ( g_VoiceGameMgr.PlayerHasBlockedPlayer( client, player ) ) + continue; + + if ( teamonly && g_pGameRules->PlayerRelationship(client, CBaseEntity::Instance(pEntity)) != GR_TEAMMATE ) + continue; + + MESSAGE_BEGIN( MSG_ONE, gmsgSayText, NULL, client->pev ); + WRITE_BYTE( ENTINDEX(pEntity) ); + WRITE_STRING( text ); + MESSAGE_END(); + + } + + // print to the sending client + MESSAGE_BEGIN( MSG_ONE, gmsgSayText, NULL, &pEntity->v ); + WRITE_BYTE( ENTINDEX(pEntity) ); + WRITE_STRING( text ); + MESSAGE_END(); + + // echo to server console + g_engfuncs.pfnServerPrint( text ); + + char * temp; + if ( teamonly ) + temp = "say_team"; + else + temp = "say"; + + // team match? + if ( g_teamplay ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" %s \"%s\"\n", + STRING( pEntity->v.netname ), + GETPLAYERUSERID( pEntity ), + GETPLAYERAUTHID( pEntity ), + g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pEntity ), "model" ), + temp, + p ); + } + else + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" %s \"%s\"\n", + STRING( pEntity->v.netname ), + GETPLAYERUSERID( pEntity ), + GETPLAYERAUTHID( pEntity ), + GETPLAYERUSERID( pEntity ), + temp, + p ); + } +} + + +/* +=========== +ClientCommand +called each time a player uses a "cmd" command +============ +*/ +extern float g_flWeaponCheat; + +// Use CMD_ARGV, CMD_ARGV, and CMD_ARGC to get pointers the character string command. +void ClientCommand( edict_t *pEntity ) +{ + const char *pcmd = CMD_ARGV(0); + const char *pstr; + + // Is the client spawned yet? + if ( !pEntity->pvPrivateData ) + return; + + entvars_t *pev = &pEntity->v; + + if ( FStrEq(pcmd, "hud_color") ) //LRC + { + if (CMD_ARGC() == 4) + { + int col = (atoi(CMD_ARGV(1)) & 255) << 16; + col += (atoi(CMD_ARGV(2)) & 255) << 8; + col += (atoi(CMD_ARGV(3)) & 255); + MESSAGE_BEGIN( MSG_ONE, gmsgHUDColor, NULL, &pEntity->v ); + WRITE_LONG(col); + MESSAGE_END(); + } + else + { + ALERT(at_console, "Syntax: hud_color RRR GGG BBB\n"); + } + } + else if ( FStrEq(pcmd, "fire") ) //LRC - trigger entities manually + { + if (g_flWeaponCheat) + { + CBaseEntity *pPlayer = CBaseEntity::Instance(pEntity); + if (CMD_ARGC() > 1) + { + FireTargets(CMD_ARGV(1), pPlayer, pPlayer, USE_TOGGLE, 0); + } + else + { + TraceResult tr; + UTIL_MakeVectors(pev->v_angle); + UTIL_TraceLine( + pev->origin + pev->view_ofs, + pev->origin + pev->view_ofs + gpGlobals->v_forward * 1000, + dont_ignore_monsters, pEntity, &tr + ); + + if (tr.pHit) + { + CBaseEntity *pHitEnt = CBaseEntity::Instance(tr.pHit); + if (pHitEnt) + { + pHitEnt->Use(pPlayer, pPlayer, USE_TOGGLE, 0); + ClientPrint( &pEntity->v, HUD_PRINTCONSOLE, UTIL_VarArgs( "Fired %s \"%s\"\n", STRING(pHitEnt->pev->classname), STRING(pHitEnt->pev->targetname) ) ); + } + } + } + } + } + else if ( FStrEq(pcmd, "setblend") ) //buz - test set blending func + { + if (CMD_ARGC() != 3) + { + ALERT(at_console, "usage: setblend \n"); + return; + } + + TraceResult tr; + UTIL_MakeVectors(pev->v_angle); + UTIL_TraceLine( + pev->origin + pev->view_ofs, + pev->origin + pev->view_ofs + gpGlobals->v_forward * 1000, + dont_ignore_monsters, pEntity, &tr); + + if (tr.pHit) + { + CBaseEntity *pHitEnt = CBaseEntity::Instance(tr.pHit); + if (pHitEnt) + { + ALERT(at_console, "ok.\n"); + pHitEnt->pev->blending[0] = atoi( CMD_ARGV(1) ); + pHitEnt->pev->blending[1] = atoi( CMD_ARGV(2) ); + return; + } + } + + ALERT(at_console, "no ent on the way.\n"); + } + else if ( FStrEq(pcmd, "say" ) ) + { + Host_Say( pEntity, 0 ); + } + else if ( FStrEq(pcmd, "say_team" ) ) + { + Host_Say( pEntity, 1 ); + } + else if ( FStrEq(pcmd, "fullupdate" ) ) + { + GetClassPtr((CBasePlayer *)pev)->ForceClientDllUpdate(); + } + else if ( FStrEq(pcmd, "give" ) ) + { + if ( g_flWeaponCheat != 0.0) + { + int iszItem = ALLOC_STRING( CMD_ARGV(1) ); // Make a copy of the classname + GetClassPtr((CBasePlayer *)pev)->GiveNamedItem( STRING(iszItem) ); + } + } + + else if ( FStrEq(pcmd, "drop" ) ) + { + // player is dropping an item. + GetClassPtr((CBasePlayer *)pev)->DropPlayerItem((char *)CMD_ARGV(1)); + } + else if ( FStrEq(pcmd, "fov" ) ) + { + if ( g_flWeaponCheat && CMD_ARGC() > 1) + { + GetClassPtr((CBasePlayer *)pev)->m_iFOV = atoi( CMD_ARGV(1) ); + } + else + { + CLIENT_PRINTF( pEntity, print_console, UTIL_VarArgs( "\"fov\" is \"%d\"\n", (int)GetClassPtr((CBasePlayer *)pev)->m_iFOV ) ); + } + } + else if ( FStrEq(pcmd, "use" ) ) + { + GetClassPtr((CBasePlayer *)pev)->SelectItem((char *)CMD_ARGV(1)); + } + else if (((pstr = strstr(pcmd, "weapon_")) != NULL) && (pstr == pcmd)) + { + GetClassPtr((CBasePlayer *)pev)->SelectItem(pcmd); + } + else if (FStrEq(pcmd, "lastinv" )) + { + GetClassPtr((CBasePlayer *)pev)->SelectLastItem(); + } + else if ( FStrEq( pcmd, "spectate" ) && (pev->flags & FL_PROXY) ) // added for proxy support + { + CBasePlayer * pPlayer = GetClassPtr((CBasePlayer *)pev); + + edict_t *pentSpawnSpot = g_pGameRules->GetPlayerSpawnSpot( pPlayer ); + pPlayer->StartObserver( pev->origin, VARS(pentSpawnSpot)->angles); + } + else if (FStrEq(pcmd, "gasmask" )) + { + CBasePlayer * pPlayer = GetClassPtr((CBasePlayer *)pev); + pPlayer->ToggleGasMask(); + } + else if (FStrEq(pcmd, "headshield" )) + { + CBasePlayer * pPlayer = GetClassPtr((CBasePlayer *)pev); + pPlayer->ToggleHeadShield(); + } + else if (FStrEq(pcmd, "painkiller" )) + { + CBasePlayer * pPlayer = GetClassPtr((CBasePlayer *)pev); + int i = pPlayer->GetAmmoIndex( "painkillers" ); + + if ( i < 0 || i >= MAX_AMMO_SLOTS ) + return; + + if (pPlayer->m_rgAmmo[i] > 0) + { + if (pPlayer->TakeHealth( gSkillData.painkillerCapacity, DMG_GENERIC )) + { + pPlayer->m_rgAmmo[i]--; + EMIT_SOUND_DYN( pPlayer->edict(), CHAN_WEAPON, "items/painkiller_use.wav", 1.0, ATTN_NORM, 0, PITCH_NORM ); + + + if (pPlayer->m_rgAmmo[i] == 0) + pPlayer->pev->weapons &= ~(1<ClientCommand( GetClassPtr((CBasePlayer *)pev), pcmd ) ) + { + // MenuSelect returns true only if the command is properly handled, so don't print a warning + } + else if ( FStrEq(pcmd, "VModEnable") ) //LRC - shut up about VModEnable... + { + // g-cont. VModEnable at top the cases broke VoiceMod system in multiplayer + // you don't know about it Laurie! + return; + } + else + { + // tell the user they entered an unknown command + char command[128]; + + // check the length of the command (prevents crash) + // max total length is 192 ...and we're adding a string below ("Unknown command: %s\n") + strncpy( command, pcmd, 127 ); + command[127] = '\0'; + + // tell the user they entered an unknown command + ClientPrint( &pEntity->v, HUD_PRINTCONSOLE, UTIL_VarArgs( "Unknown command: %s\n", command ) ); + } +} + + +/* +======================== +ClientUserInfoChanged + +called after the player changes +userinfo - gives dll a chance to modify it before +it gets sent into the rest of the engine. +======================== +*/ +void ClientUserInfoChanged( edict_t *pEntity, char *infobuffer ) +{ + // Is the client spawned yet? + if ( !pEntity->pvPrivateData ) + return; + + // msg everyone if someone changes their name, and it isn't the first time (changing no name to current name) + if ( pEntity->v.netname && STRING(pEntity->v.netname)[0] != 0 && !FStrEq( STRING(pEntity->v.netname), g_engfuncs.pfnInfoKeyValue( infobuffer, "name" )) ) + { + char sName[256]; + char *pName = g_engfuncs.pfnInfoKeyValue( infobuffer, "name" ); + strncpy( sName, pName, sizeof(sName) - 1 ); + sName[ sizeof(sName) - 1 ] = '\0'; + + // First parse the name and remove any %'s + for ( char *pApersand = sName; pApersand != NULL && *pApersand != 0; pApersand++ ) + { + // Replace it with a space + if ( *pApersand == '%' ) + *pApersand = ' '; + } + + // Set the name + g_engfuncs.pfnSetClientKeyValue( ENTINDEX(pEntity), infobuffer, "name", sName ); + + char text[256]; + sprintf( text, "* %s changed name to %s\n", STRING(pEntity->v.netname), g_engfuncs.pfnInfoKeyValue( infobuffer, "name" ) ); + MESSAGE_BEGIN( MSG_ALL, gmsgSayText, NULL ); + WRITE_BYTE( ENTINDEX(pEntity) ); + WRITE_STRING( text ); + MESSAGE_END(); + + // team match? + if ( g_teamplay ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" changed name to \"%s\"\n", + STRING( pEntity->v.netname ), + GETPLAYERUSERID( pEntity ), + GETPLAYERAUTHID( pEntity ), + g_engfuncs.pfnInfoKeyValue( infobuffer, "model" ), + g_engfuncs.pfnInfoKeyValue( infobuffer, "name" ) ); + } + else + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" changed name to \"%s\"\n", + STRING( pEntity->v.netname ), + GETPLAYERUSERID( pEntity ), + GETPLAYERAUTHID( pEntity ), + GETPLAYERUSERID( pEntity ), + g_engfuncs.pfnInfoKeyValue( infobuffer, "name" ) ); + } + } + + g_pGameRules->ClientUserInfoChanged( GetClassPtr((CBasePlayer *)&pEntity->v), infobuffer ); +} + +static int g_serveractive = 0; + +void ServerDeactivate( void ) +{ + // make sure they reinitialise the World in the next server + g_pWorld = NULL; + + // It's possible that the engine will call this function more times than is necessary + // Therefore, only run it one time for each call to ServerActivate + if ( g_serveractive != 1 ) + { + return; + } + + g_serveractive = 0; + + // Peform any shutdown operations here... + // +} + +void ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) +{ + int i; + CBaseEntity *pClass; + + // Every call to ServerActivate should be matched by a call to ServerDeactivate + g_serveractive = 1; + + // allow save in case we restart the map + g_fAllowSaves = TRUE; + + // Clients have not been initialized yet + for ( i = 0; i < edictCount; i++ ) + { + if ( pEdictList[i].free ) + continue; + + // Clients aren't necessarily initialized until ClientPutInServer() + if ( i < clientMax || !pEdictList[i].pvPrivateData ) + continue; + + pClass = CBaseEntity::Instance( &pEdictList[i] ); + // Activate this entity if it's got a class & isn't dormant + if ( pClass && !(pClass->pev->flags & FL_DORMANT) ) + { + pClass->Activate(); + } + else + { + ALERT( at_debug, "Can't instance %s\n", STRING(pEdictList[i].v.classname) ); + } + } +} + +// a cached version of gpGlobals->frametime. The engine sets frametime to 0 if the player is frozen... so we just cache it in prethink, +// allowing it to be restored later and used by CheckDesiredList. +float cached_frametime = 0.0f; + +/* +================ +PlayerPreThink + +Called every frame before physics are run +================ +*/ +void PlayerPreThink( edict_t *pEntity ) +{ + entvars_t *pev = &pEntity->v; + CBasePlayer *pPlayer = (CBasePlayer *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->PreThink( ); + + cached_frametime = gpGlobals->frametime; +} + +/* +================ +PlayerPostThink + +Called every frame after physics are run +================ +*/ +void PlayerPostThink( edict_t *pEntity ) +{ + entvars_t *pev = &pEntity->v; + CBasePlayer *pPlayer = (CBasePlayer *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->PostThink( ); + + // use the old frametime, even if the engine has reset it + gpGlobals->frametime = cached_frametime; + + //LRC - moved to here from CBasePlayer::PostThink, so that + // things don't stop when the player dies + CheckDesiredList( ); +} + + + +void ParmsNewLevel( void ) +{ +} + + +void ParmsChangeLevel( void ) +{ + // retrieve the pointer to the save data + SAVERESTOREDATA *pSaveData = (SAVERESTOREDATA *)gpGlobals->pSaveData; + + if ( pSaveData ) + pSaveData->connectionCount = BuildChangeList( pSaveData->levelList, MAX_LEVEL_CONNECTIONS ); +} + + +// +// GLOBALS ASSUMED SET: g_ulFrameCount +// +void StartFrame( void ) +{ + if ( g_pGameRules ) + g_pGameRules->Think(); + + if ( g_fGameOver ) + return; + + gpGlobals->teamplay = teamplay.value; + g_ulFrameCount++; + +// CheckDesiredList(); //LRC + CheckAssistList(); //LRC +} + +void PrecacheMaterialSounds( void ) +{ + ALERT( at_aiconsole, "loading materials.def\n" ); + + char *afile = (char *)LOAD_FILE( "scripts/materials.def", NULL ); + + if( !afile ) + { + ALERT( at_error, "Cannot open file \"scripts/materials.def\"\n" ); + return; + } + + char *pfile = afile; + char token[256]; + int current = 0; + + while( pfile != NULL ) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + + // skip the material name + + // read opening brace + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + + if( token[0] != '{' ) + { + ALERT( at_error, "found %s when expecting {\n", token ); + break; + } + + while( pfile != NULL ) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "EOF without closing brace\n" ); + goto getout; + } + + // description end goto next material + if( token[0] == '}' ) + { + break; + } + else if( !Q_stricmp( token, "impact_sound" )) + { + while(( pfile = COM_ParseLine( pfile, token )) != NULL ) + { + if( !Q_strlen( token )) break; // end of line + PRECACHE_SOUND( token ); // be warning. In GoldSrc this method invoke to crash engine + } + } + else if( !Q_stricmp( token, "step_sound" )) + { + while(( pfile = COM_ParseLine( pfile, token )) != NULL ) + { + if( !Q_strlen( token )) break; // end of line + PRECACHE_SOUND( token ); // be warning. In GoldSrc this method invoke to crash engine + } + } + } + } +getout: + FREE_FILE( afile ); +} + +void ClientPrecache( void ) +{ + // setup precaches always needed + PRECACHE_SOUND("player/sprayer.wav"); // spray paint sound for PreAlpha + + PRECACHE_SOUND("player/gameover.wav"); + + PRECACHE_SOUND("player/pl_fallpain2.wav"); + PRECACHE_SOUND("player/pl_fallpain3.wav"); + + PRECACHE_SOUND("player/pl_step1.wav"); // walk on concrete + PRECACHE_SOUND("player/pl_step2.wav"); + PRECACHE_SOUND("player/pl_step3.wav"); + PRECACHE_SOUND("player/pl_step4.wav"); + + PRECACHE_SOUND("common/npc_step1.wav"); // NPC walk on concrete + PRECACHE_SOUND("common/npc_step2.wav"); + PRECACHE_SOUND("common/npc_step3.wav"); + PRECACHE_SOUND("common/npc_step4.wav"); + + PRECACHE_SOUND("player/pl_metal1.wav"); // walk on metal + PRECACHE_SOUND("player/pl_metal2.wav"); + PRECACHE_SOUND("player/pl_metal3.wav"); + PRECACHE_SOUND("player/pl_metal4.wav"); + + PRECACHE_SOUND("player/pl_dirt1.wav"); // walk on dirt + PRECACHE_SOUND("player/pl_dirt2.wav"); + PRECACHE_SOUND("player/pl_dirt3.wav"); + PRECACHE_SOUND("player/pl_dirt4.wav"); + + PRECACHE_SOUND("player/pl_duct1.wav"); // walk in duct + PRECACHE_SOUND("player/pl_duct2.wav"); + PRECACHE_SOUND("player/pl_duct3.wav"); + PRECACHE_SOUND("player/pl_duct4.wav"); + + PRECACHE_SOUND("player/pl_grate1.wav"); // walk on grate + PRECACHE_SOUND("player/pl_grate2.wav"); + PRECACHE_SOUND("player/pl_grate3.wav"); + PRECACHE_SOUND("player/pl_grate4.wav"); + + PRECACHE_SOUND("player/pl_slosh1.wav"); // walk in shallow water + PRECACHE_SOUND("player/pl_slosh2.wav"); + PRECACHE_SOUND("player/pl_slosh3.wav"); + PRECACHE_SOUND("player/pl_slosh4.wav"); + + PRECACHE_SOUND("player/pl_tile1.wav"); // walk on tile + PRECACHE_SOUND("player/pl_tile2.wav"); + PRECACHE_SOUND("player/pl_tile3.wav"); + PRECACHE_SOUND("player/pl_tile4.wav"); + PRECACHE_SOUND("player/pl_tile5.wav"); + + PRECACHE_SOUND("player/pl_swim1.wav"); // breathe bubbles + PRECACHE_SOUND("player/pl_swim2.wav"); + PRECACHE_SOUND("player/pl_swim3.wav"); + PRECACHE_SOUND("player/pl_swim4.wav"); + + PRECACHE_SOUND("player/pl_ladder1.wav"); // climb ladder rung + PRECACHE_SOUND("player/pl_ladder2.wav"); + PRECACHE_SOUND("player/pl_ladder3.wav"); + PRECACHE_SOUND("player/pl_ladder4.wav"); + + PRECACHE_SOUND("player/pl_wade1.wav"); // wade in water + PRECACHE_SOUND("player/pl_wade2.wav"); + PRECACHE_SOUND("player/pl_wade3.wav"); + PRECACHE_SOUND("player/pl_wade4.wav"); + + /*PRECACHE_SOUND("debris/wood1.wav"); // hit wood texture + PRECACHE_SOUND("debris/wood2.wav"); + PRECACHE_SOUND("debris/wood3.wav"); + PRECACHE_SOUND("debris/wood4.wav"); + + PRECACHE_SOUND("debris/metal1.wav"); // hit metal texture + PRECACHE_SOUND("debris/metal2.wav"); + PRECACHE_SOUND("debris/metal3.wav"); + PRECACHE_SOUND("debris/metal4.wav");*/ + + // buz: paranoia step sounds + PRECACHE_SOUND("player/pl_wood_scr1.wav"); + PRECACHE_SOUND("player/pl_wood_scr2.wav"); + PRECACHE_SOUND("player/pl_wood_scr3.wav"); + PRECACHE_SOUND("player/pl_wood_scr4.wav"); + + PRECACHE_SOUND("player/pl_wood1.wav"); + PRECACHE_SOUND("player/pl_wood2.wav"); + PRECACHE_SOUND("player/pl_wood3.wav"); + PRECACHE_SOUND("player/pl_wood4.wav"); + + PRECACHE_SOUND("player/pl_asf1.wav"); + PRECACHE_SOUND("player/pl_asf2.wav"); + PRECACHE_SOUND("player/pl_asf3.wav"); + PRECACHE_SOUND("player/pl_asf4.wav"); + + PRECACHE_SOUND("player/pl_beton1.wav"); + PRECACHE_SOUND("player/pl_beton2.wav"); + PRECACHE_SOUND("player/pl_beton3.wav"); + PRECACHE_SOUND("player/pl_beton4.wav"); + + PRECACHE_SOUND("player/pl_grass1.wav"); + PRECACHE_SOUND("player/pl_grass2.wav"); + PRECACHE_SOUND("player/pl_grass3.wav"); + PRECACHE_SOUND("player/pl_grass4.wav"); + +// PRECACHE_SOUND("plats/train_use1.wav"); // use a train + + PRECACHE_SOUND("buttons/spark5.wav"); // hit computer texture + PRECACHE_SOUND("buttons/spark6.wav"); + /*PRECACHE_SOUND("debris/glass1.wav"); + PRECACHE_SOUND("debris/glass2.wav"); + PRECACHE_SOUND("debris/glass3.wav");*/ + + PRECACHE_SOUND( "player/pl_gas_hard.wav" ); + + PRECACHE_SOUND( SOUND_FLASHLIGHT_ON ); + PRECACHE_SOUND( SOUND_FLASHLIGHT_OFF ); + + // buz + PRECACHE_SOUND( SOUND_GASMASK_ON ); + PRECACHE_SOUND( SOUND_GASMASK_OFF ); + + // buz + PRECACHE_SOUND( SOUND_SHIELD_ON ); + PRECACHE_SOUND( SOUND_SHIELD_OFF ); + + // buz + PRECACHE_SOUND("common/headshot.wav"); + +// player gib sounds + PRECACHE_SOUND("common/bodysplat.wav"); + +// player pain sounds + PRECACHE_SOUND("player/pl_pain2.wav"); + PRECACHE_SOUND("player/pl_pain4.wav"); + PRECACHE_SOUND("player/pl_pain5.wav"); + PRECACHE_SOUND("player/pl_pain6.wav"); + PRECACHE_SOUND("player/pl_pain7.wav"); + + PRECACHE_MODEL("models/player.mdl"); + + // hud sounds + + PRECACHE_SOUND("common/wpn_hudoff.wav"); + PRECACHE_SOUND("common/wpn_hudon.wav"); + PRECACHE_SOUND("common/wpn_moveselect.wav"); + PRECACHE_SOUND("common/wpn_select.wav"); + PRECACHE_SOUND("common/wpn_denyselect.wav"); + + // geiger sounds + + PRECACHE_SOUND("player/geiger6.wav"); + PRECACHE_SOUND("player/geiger5.wav"); + PRECACHE_SOUND("player/geiger4.wav"); + PRECACHE_SOUND("player/geiger3.wav"); + PRECACHE_SOUND("player/geiger2.wav"); + PRECACHE_SOUND("player/geiger1.wav"); + + // buz + PRECACHE_SOUND("items/gasm_breath1.wav"); + PRECACHE_SOUND("items/gasm_breath2.wav"); + PRECACHE_SOUND("items/gasm_breath3.wav"); + PRECACHE_SOUND("items/gasm_breath4.wav"); + + PRECACHE_SOUND("items/painkiller_use.wav"); + + if (giPrecacheGrunt) + UTIL_PrecacheOther("monster_human_grunt"); + + PrecacheMaterialSounds (); +} + +/* +=============== +GetGameDescription + +Returns the descriptive name of this .dll. E.g., Half-Life, or Team Fortress 2 +=============== +*/ + +const char *GetGameDescription() +{ + return GAME_NAME; +} + +/* +================ +Sys_Error + +Engine is going to shut down, allows setting a breakpoint in game .dll to catch that occasion +================ +*/ +void Sys_Error( const char *error_string ) +{ + // Default case, do nothing. MOD AUTHORS: Add code ( e.g., _asm { int 3 }; here to cause a breakpoint for debugging your game .dlls +} + +/* +================ +PlayerCustomization + +A new player customization has been registered on the server +UNDONE: This only sets the # of frames of the spray can logo +animation right now. +================ +*/ +void PlayerCustomization( edict_t *pEntity, customization_t *pCust ) +{ + entvars_t *pev = &pEntity->v; + CBasePlayer *pPlayer = (CBasePlayer *)GET_PRIVATE(pEntity); + + if (!pPlayer) + { + ALERT(at_debug, "PlayerCustomization: Couldn't get player!\n"); + return; + } + + if (!pCust) + { + ALERT(at_debug, "PlayerCustomization: NULL customization!\n"); + return; + } + + switch (pCust->resource.type) + { + case t_decal: + pPlayer->SetCustomDecalFrames(pCust->nUserData2); // Second int is max # of frames. + break; + case t_sound: + case t_skin: + case t_model: + // Ignore for now. + break; + default: + ALERT(at_debug, "PlayerCustomization: Unknown customization type!\n"); + break; + } +} + +/* +================ +SpectatorConnect + +A spectator has joined the game +================ +*/ +void SpectatorConnect( edict_t *pEntity ) +{ + entvars_t *pev = &pEntity->v; + CBaseSpectator *pPlayer = (CBaseSpectator *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->SpectatorConnect( ); +} + +/* +================ +SpectatorConnect + +A spectator has left the game +================ +*/ +void SpectatorDisconnect( edict_t *pEntity ) +{ + entvars_t *pev = &pEntity->v; + CBaseSpectator *pPlayer = (CBaseSpectator *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->SpectatorDisconnect( ); +} + +/* +================ +SpectatorConnect + +A spectator has sent a usercmd +================ +*/ +void SpectatorThink( edict_t *pEntity ) +{ + entvars_t *pev = &pEntity->v; + CBaseSpectator *pPlayer = (CBaseSpectator *)GET_PRIVATE(pEntity); + + if (pPlayer) + pPlayer->SpectatorThink( ); +} + +//////////////////////////////////////////////////////// +// PAS and PVS routines for client messaging +// + +/* +================ +SetupVisibility + +A client can have a separate "view entity" indicating that his/her view should depend on the origin of that +view entity. If that's the case, then pViewEntity will be non-NULL and will be used. Otherwise, the current +entity's origin is used. Either is offset by the view_ofs to get the eye position. + +From the eye position, we set up the PAS and PVS to use for filtering network messages to the client. At this point, we could + override the actual PAS or PVS values, or use a different origin. + +NOTE: Do not cache the values of pas and pvs, as they depend on reusable memory in the engine, they are only good for this one frame +================ +*/ +void SetupVisibility( edict_t *pViewEntity, edict_t *pClient, unsigned char **pvs, unsigned char **pas ) +{ + Vector org; + edict_t *pView = pClient; + + // Find the client's PVS + if ( pViewEntity ) + { + pView = pViewEntity; + } + + if ( pClient->v.flags & FL_PROXY ) + { + *pvs = NULL; // the spectator proxy sees + *pas = NULL; // and hears everything + return; + } + + org = pView->v.origin + pView->v.view_ofs; + if ( pView->v.flags & FL_DUCKING ) + { + org = org + ( VEC_HULL_MIN - VEC_DUCK_HULL_MIN ); + } + + *pvs = ENGINE_SET_PVS ( (float *)&org ); + *pas = ENGINE_SET_PAS ( (float *)&org ); +} + +#include "entity_state.h" + +/* +AddToFullPack + +Return 1 if the entity state has been filled in for the ent and the entity will be propagated to the client, 0 otherwise + +state is the server maintained copy of the state info that is transmitted to the client +a MOD could alter values copied into state to send the "host" a different look for a particular entity update, etc. +e and ent are the entity that is being added to the update, if 1 is returned +host is the player's edict of the player whom we are sending the update to +player is 1 if the ent/e is a player and 0 otherwise +pSet is either the PAS or PVS that we previous set up. We can use it to ask the engine to filter the entity against the PAS or PVS. +we could also use the pas/ pvs that we set in SetupVisibility, if we wanted to. Caching the value is valid in that case, but still only for the current frame +*/ +int AddToFullPack( struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, unsigned char *pSet ) +{ + int i; + + // don't send if flagged for NODRAW and it's not the host getting the message + if ( ( ent->v.effects == EF_NODRAW ) && ( ent != host ) ) + return 0; + + // Ignore ents without valid / visible models + if ( !ent->v.modelindex || !STRING( ent->v.model ) ) + return 0; + + // Don't send spectators to other players + if ( ( ent->v.flags & FL_SPECTATOR ) && ( ent != host ) ) + { + return 0; + } + + CBaseEntity *pEntity = CBaseEntity::Instance( ent ); + + if( !pEntity ) + { + return 0; + } + + // Ignore if not the host and not touching a PVS/PAS leaf + // If pSet is NULL, then the test will always succeed and the entity will be added to the update + if ( ent != host && ent->v.renderfx != 70) // buz: always send 3d skybox ents + { + if ( !ENGINE_CHECK_VISIBILITY( (const struct edict_s *)ent, pSet ) ) + { + if(( ent->v.renderfx == 71 || ent->v.renderfx == 72 ) && ent->v.enemy != NULL ) + { + // projected light have second PVS point so we must check her too + if ( !ENGINE_CHECK_VISIBILITY( (const struct edict_s *)ent->v.enemy, pSet ) ) + return 0; + } + else + { + return 0; + } + } + } + + + // Don't send entity to local client if the client says it's predicting the entity itself. + if ( ent->v.flags & FL_SKIPLOCALHOST ) + { + if ( ( hostflags & 1 ) && ( ent->v.owner == host ) ) + return 0; + } + + if ( host->v.groupinfo ) + { + UTIL_SetGroupTrace( host->v.groupinfo, GROUP_OP_AND ); + + // Should always be set, of course + if ( ent->v.groupinfo ) + { + if ( g_groupop == GROUP_OP_AND ) + { + if ( !(ent->v.groupinfo & host->v.groupinfo ) ) + return 0; + } + else if ( g_groupop == GROUP_OP_NAND ) + { + if ( ent->v.groupinfo & host->v.groupinfo ) + return 0; + } + } + + UTIL_UnsetGroupTrace(); + } + + memset( state, 0, sizeof( *state ) ); + + // Assign index so we can track this entity from frame to frame and + // delta from it. + state->number = e; + state->entityType = ENTITY_NORMAL; + + // Flag custom entities. + if ( ent->v.flags & FL_CUSTOMENTITY ) + { + state->entityType = ENTITY_BEAM; + } + + // + // Copy state data + // + + // Round animtime to nearest millisecond + state->animtime = (int)(1000.0 * ent->v.animtime ) / 1000.0; + + memcpy( state->origin, ent->v.origin, 3 * sizeof( float ) ); + memcpy( state->angles, ent->v.angles, 3 * sizeof( float ) ); + memcpy( state->mins, ent->v.mins, 3 * sizeof( float ) ); + memcpy( state->maxs, ent->v.maxs, 3 * sizeof( float ) ); + + memcpy( state->startpos, ent->v.startpos, 3 * sizeof( float ) ); + memcpy( state->endpos, ent->v.endpos, 3 * sizeof( float ) ); + + state->impacttime = ent->v.impacttime; + state->starttime = ent->v.starttime; + + state->modelindex = ent->v.modelindex; + + state->frame = ent->v.frame; + + state->skin = ent->v.skin; + state->effects = ent->v.effects; + + // This non-player entity is being moved by the game .dll and not the physics simulation system + // make sure that we interpolate it's position on the client if it moves + if ( !player && + ent->v.animtime && + ent->v.velocity[ 0 ] == 0 && + ent->v.velocity[ 1 ] == 0 && + ent->v.velocity[ 2 ] == 0 ) + { + state->eflags |= EFLAG_SLERP; + } + + state->scale = ent->v.scale; + state->solid = ent->v.solid; + state->colormap = ent->v.colormap; + + state->movetype = ent->v.movetype; + state->sequence = ent->v.sequence; + state->framerate = ent->v.framerate; + state->body = ent->v.body; + + for (i = 0; i < 4; i++) + { + state->controller[i] = ent->v.controller[i]; + } + + for (i = 0; i < 2; i++) + { + state->blending[i] = ent->v.blending[i]; + } + + state->rendermode = ent->v.rendermode; + state->renderamt = ent->v.renderamt; + state->renderfx = ent->v.renderfx; + state->rendercolor.r = ent->v.rendercolor.x; + state->rendercolor.g = ent->v.rendercolor.y; + state->rendercolor.b = ent->v.rendercolor.z; + state->fuser1 = ent->v.fuser1; // FOV + state->fuser2 = ent->v.fuser2; // FOV + state->fuser3 = ent->v.fuser3; // FOV + state->fuser4 = ent->v.fuser4; // FOV + state->iuser1 = ent->v.iuser1; // flags + state->iuser2 = ent->v.iuser2; // flags + state->iuser3 = ent->v.iuser3; // vertexlight cachenum + state->iuser4 = ent->v.iuser4; + + // copy poseparams across network + state->vuser1.x = pEntity->m_flPoseParameter[0]; + state->vuser1.y = pEntity->m_flPoseParameter[1]; + state->vuser1.z = pEntity->m_flPoseParameter[2]; + state->vuser2.x = pEntity->m_flPoseParameter[3]; + state->vuser2.y = pEntity->m_flPoseParameter[4]; + state->vuser2.z = pEntity->m_flPoseParameter[5]; + state->vuser3.x = pEntity->m_flPoseParameter[6]; + state->vuser3.y = pEntity->m_flPoseParameter[7]; + state->vuser3.z = pEntity->m_flPoseParameter[8]; + state->vuser4.x = pEntity->m_flPoseParameter[9]; + state->vuser4.y = pEntity->m_flPoseParameter[10]; + state->vuser4.z = pEntity->m_flPoseParameter[11]; + + state->aiment = 0; + if ( ent->v.aiment ) + { + state->aiment = ENTINDEX( ent->v.aiment ); + } + + state->owner = 0; + if ( ent->v.owner ) + { + int owner = ENTINDEX( ent->v.owner ); + + // Only care if owned by a player + if ( owner >= 1 && owner <= gpGlobals->maxClients ) + { + state->owner = owner; + } + } + + // HACK: Somewhat... + // Class is overridden for non-players to signify a breakable glass object ( sort of a class? ) + // that's 'class' in the sense medic, engineer, etc... !! --LRC + if ( !player ) + { + state->playerclass = ent->v.playerclass; + } + + // Special stuff for players only + if ( player ) + { + memcpy( state->basevelocity, ent->v.basevelocity, 3 * sizeof( float ) ); + + state->weaponmodel = MODEL_INDEX( STRING( ent->v.weaponmodel ) ); + state->gaitsequence = ent->v.gaitsequence; + state->spectator = ent->v.flags & FL_SPECTATOR; + state->friction = ent->v.friction; + state->fuser1 = ent->v.fuser1; // gaitframe + state->gravity = ent->v.gravity; +// state->team = ent->v.team; +// + state->usehull = ( ent->v.flags & FL_DUCKING ) ? 1 : 0; + state->health = ent->v.health; + } + + return 1; +} + +// defaults for clientinfo messages +#define DEFAULT_VIEWHEIGHT 28 + +/* +=================== +CreateBaseline + +Creates baselines used for network encoding, especially for player data since players are not spawned until connect time. +=================== +*/ +void CreateBaseline( int player, int eindex, struct entity_state_s *baseline, struct edict_s *entity, int playermodelindex, vec3_t player_mins, vec3_t player_maxs ) +{ + CBaseEntity *pEntity = CBaseEntity::Instance( entity ); + + baseline->origin = entity->v.origin; + baseline->angles = entity->v.angles; + baseline->frame = entity->v.frame; + baseline->skin = (short)entity->v.skin; + baseline->body = entity->v.body; + + // render information + baseline->rendermode = (byte)entity->v.rendermode; + baseline->renderamt = (byte)entity->v.renderamt; + baseline->rendercolor.r = (byte)entity->v.rendercolor.x; + baseline->rendercolor.g = (byte)entity->v.rendercolor.y; + baseline->rendercolor.b = (byte)entity->v.rendercolor.z; + baseline->renderfx = (byte)entity->v.renderfx; + + if ( player ) + { + baseline->mins = player_mins; + baseline->maxs = player_maxs; + + baseline->colormap = eindex; + baseline->modelindex = playermodelindex; + baseline->friction = 1.0; + baseline->movetype = MOVETYPE_WALK; + + baseline->scale = entity->v.scale; + baseline->solid = SOLID_SLIDEBOX; + baseline->framerate = 1.0; + baseline->gravity = 1.0; + + } + else + { + baseline->mins = entity->v.mins; + baseline->maxs = entity->v.maxs; + + baseline->sequence = entity->v.sequence; + baseline->colormap = entity->v.colormap; + baseline->modelindex = entity->v.modelindex;//SV_ModelIndex(pr_strings + entity->v.model); + baseline->movetype = entity->v.movetype; + + baseline->scale = entity->v.scale; + baseline->solid = entity->v.solid; + baseline->framerate = entity->v.framerate; + baseline->gravity = entity->v.gravity; + baseline->startpos = entity->v.startpos; + baseline->iuser1 = entity->v.iuser1; // flags + baseline->iuser2 = entity->v.iuser2; // flags + baseline->iuser3 = entity->v.iuser3; // vertexlight cachenum + + if( pEntity ) + { + baseline->vuser1.x = pEntity->m_flPoseParameter[0]; + baseline->vuser1.y = pEntity->m_flPoseParameter[1]; + baseline->vuser1.z = pEntity->m_flPoseParameter[2]; + baseline->vuser2.x = pEntity->m_flPoseParameter[3]; + baseline->vuser2.y = pEntity->m_flPoseParameter[4]; + baseline->vuser2.z = pEntity->m_flPoseParameter[5]; + baseline->vuser3.x = pEntity->m_flPoseParameter[6]; + baseline->vuser3.y = pEntity->m_flPoseParameter[7]; + baseline->vuser3.z = pEntity->m_flPoseParameter[8]; + baseline->vuser4.x = pEntity->m_flPoseParameter[9]; + baseline->vuser4.y = pEntity->m_flPoseParameter[10]; + baseline->vuser4.z = pEntity->m_flPoseParameter[11]; + } + } +} + +typedef struct +{ + char name[32]; + int field; +} entity_field_alias_t; + +#define FIELD_ORIGIN0 0 +#define FIELD_ORIGIN1 1 +#define FIELD_ORIGIN2 2 +#define FIELD_ANGLES0 3 +#define FIELD_ANGLES1 4 +#define FIELD_ANGLES2 5 + +static entity_field_alias_t entity_field_alias[]= +{ + { "origin[0]", 0 }, + { "origin[1]", 0 }, + { "origin[2]", 0 }, + { "angles[0]", 0 }, + { "angles[1]", 0 }, + { "angles[2]", 0 }, +}; + +void Entity_FieldInit( struct delta_s *pFields ) +{ + entity_field_alias[ FIELD_ORIGIN0 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ORIGIN0 ].name ); + entity_field_alias[ FIELD_ORIGIN1 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ORIGIN1 ].name ); + entity_field_alias[ FIELD_ORIGIN2 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ORIGIN2 ].name ); + entity_field_alias[ FIELD_ANGLES0 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ANGLES0 ].name ); + entity_field_alias[ FIELD_ANGLES1 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ANGLES1 ].name ); + entity_field_alias[ FIELD_ANGLES2 ].field = DELTA_FINDFIELD( pFields, entity_field_alias[ FIELD_ANGLES2 ].name ); +} + +/* +================== +Entity_Encode + +Callback for sending entity_state_t info over network. +FIXME: Move to script +================== +*/ +void Entity_Encode( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) +{ + entity_state_t *f, *t; + int localplayer = 0; + static int initialized = 0; + + if ( !initialized ) + { + Entity_FieldInit( pFields ); + initialized = 1; + } + + f = (entity_state_t *)from; + t = (entity_state_t *)to; + + // Never send origin to local player, it's sent with more resolution in clientdata_t structure + localplayer = ( t->number - 1 ) == ENGINE_CURRENT_PLAYER(); + if ( localplayer ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } + + if ( ( t->impacttime != 0 ) && ( t->starttime != 0 ) ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ANGLES0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ANGLES1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ANGLES2 ].field ); + } + + if ( ( t->movetype == MOVETYPE_FOLLOW ) && + ( t->aiment != 0 ) ) + { + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } + else if ( t->aiment != f->aiment ) + { + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_SETBYINDEX( pFields, entity_field_alias[ FIELD_ORIGIN2 ].field ); + } +} + +static entity_field_alias_t player_field_alias[]= +{ + { "origin[0]", 0 }, + { "origin[1]", 0 }, + { "origin[2]", 0 }, +}; + +void Player_FieldInit( struct delta_s *pFields ) +{ + player_field_alias[ FIELD_ORIGIN0 ].field = DELTA_FINDFIELD( pFields, player_field_alias[ FIELD_ORIGIN0 ].name ); + player_field_alias[ FIELD_ORIGIN1 ].field = DELTA_FINDFIELD( pFields, player_field_alias[ FIELD_ORIGIN1 ].name ); + player_field_alias[ FIELD_ORIGIN2 ].field = DELTA_FINDFIELD( pFields, player_field_alias[ FIELD_ORIGIN2 ].name ); +} + +/* +================== +Player_Encode + +Callback for sending entity_state_t for players info over network. +================== +*/ +void Player_Encode( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) +{ + entity_state_t *f, *t; + int localplayer = 0; + static int initialized = 0; + + if ( !initialized ) + { + Player_FieldInit( pFields ); + initialized = 1; + } + + f = (entity_state_t *)from; + t = (entity_state_t *)to; + + // Never send origin to local player, it's sent with more resolution in clientdata_t structure + localplayer = ( t->number - 1 ) == ENGINE_CURRENT_PLAYER(); + if ( localplayer ) + { + DELTA_UNSETBYINDEX( pFields, player_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, player_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, player_field_alias[ FIELD_ORIGIN2 ].field ); + } + + if ( ( t->movetype == MOVETYPE_FOLLOW ) && + ( t->aiment != 0 ) ) + { + DELTA_UNSETBYINDEX( pFields, player_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, player_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, player_field_alias[ FIELD_ORIGIN2 ].field ); + } + else if ( t->aiment != f->aiment ) + { + DELTA_SETBYINDEX( pFields, player_field_alias[ FIELD_ORIGIN0 ].field ); + DELTA_SETBYINDEX( pFields, player_field_alias[ FIELD_ORIGIN1 ].field ); + DELTA_SETBYINDEX( pFields, player_field_alias[ FIELD_ORIGIN2 ].field ); + } +} + +#define CUSTOMFIELD_ORIGIN0 0 +#define CUSTOMFIELD_ORIGIN1 1 +#define CUSTOMFIELD_ORIGIN2 2 +#define CUSTOMFIELD_ANGLES0 3 +#define CUSTOMFIELD_ANGLES1 4 +#define CUSTOMFIELD_ANGLES2 5 +#define CUSTOMFIELD_SKIN 6 +#define CUSTOMFIELD_SEQUENCE 7 +#define CUSTOMFIELD_ANIMTIME 8 + +entity_field_alias_t custom_entity_field_alias[]= +{ + { "origin[0]", 0 }, + { "origin[1]", 0 }, + { "origin[2]", 0 }, + { "angles[0]", 0 }, + { "angles[1]", 0 }, + { "angles[2]", 0 }, + { "skin", 0 }, + { "sequence", 0 }, + { "animtime", 0 }, +}; + +void Custom_Entity_FieldInit( struct delta_s *pFields ) +{ + custom_entity_field_alias[ CUSTOMFIELD_ORIGIN0 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN0 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ORIGIN1 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN1 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ORIGIN2 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN2 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANGLES0 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES0 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANGLES1 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES1 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANGLES2 ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES2 ].name ); + custom_entity_field_alias[ CUSTOMFIELD_SKIN ].field = DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_SKIN ].name ); + custom_entity_field_alias[ CUSTOMFIELD_SEQUENCE ].field= DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_SEQUENCE ].name ); + custom_entity_field_alias[ CUSTOMFIELD_ANIMTIME ].field= DELTA_FINDFIELD( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANIMTIME ].name ); +} + +/* +================== +Custom_Encode + +Callback for sending entity_state_t info ( for custom entities ) over network. +FIXME: Move to script +================== +*/ +void Custom_Encode( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) +{ + entity_state_t *f, *t; + int beamType; + static int initialized = 0; + + if ( !initialized ) + { + Custom_Entity_FieldInit( pFields ); + initialized = 1; + } + + f = (entity_state_t *)from; + t = (entity_state_t *)to; + + beamType = t->rendermode & 0x0f; + + if ( beamType != BEAM_POINTS && beamType != BEAM_ENTPOINT ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN0 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN1 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ORIGIN2 ].field ); + } + + if ( beamType != BEAM_POINTS ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES0 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES1 ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANGLES2 ].field ); + } + + if ( beamType != BEAM_ENTS && beamType != BEAM_ENTPOINT ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_SKIN ].field ); + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_SEQUENCE ].field ); + } + + // animtime is compared by rounding first + // see if we really shouldn't actually send it + if ( (int)f->animtime == (int)t->animtime ) + { + DELTA_UNSETBYINDEX( pFields, custom_entity_field_alias[ CUSTOMFIELD_ANIMTIME ].field ); + } +} + +/* +================= +RegisterEncoders + +Allows game .dll to override network encoding of certain types of entities and tweak values, etc. +================= +*/ +void RegisterEncoders( void ) +{ + DELTA_ADDENCODER( "Entity_Encode", Entity_Encode ); + DELTA_ADDENCODER( "Custom_Encode", Custom_Encode ); + DELTA_ADDENCODER( "Player_Encode", Player_Encode ); +} + +int GetWeaponData( struct edict_s *player, struct weapon_data_s *info ) +{ + memset( info, 0, 32 * sizeof( weapon_data_t )); + + return 1; +} + +/* +================= +UpdateClientData + +Data sent to current client only +engine sets cd to 0 before calling. +================= +*/ +void UpdateClientData ( const struct edict_s *ent, int sendweapons, struct clientdata_s *cd ) +{ + cd->flags = ent->v.flags; + cd->health = ent->v.health; + + cd->viewmodel = MODEL_INDEX( STRING( ent->v.viewmodel ) ); + + cd->waterlevel = ent->v.waterlevel; + cd->watertype = ent->v.watertype; + cd->weapons = ent->v.weapons; + + // Vectors + cd->origin = ent->v.origin; + cd->velocity = ent->v.velocity; + cd->view_ofs = ent->v.view_ofs; + cd->punchangle = ent->v.punchangle; + + cd->bInDuck = ent->v.bInDuck; + cd->flTimeStepSound = ent->v.flTimeStepSound; + cd->flDuckTime = ent->v.flDuckTime; + cd->flSwimTime = ent->v.flSwimTime; + cd->waterjumptime = ent->v.teleport_time; + + strcpy( cd->physinfo, ENGINE_GETPHYSINFO( ent ) ); + + cd->maxspeed = ent->v.maxspeed; + cd->fov = ent->v.fov; + cd->weaponanim = ent->v.weaponanim; + + cd->pushmsec = ent->v.pushmsec; + cd->iuser3 = MODEL_INDEX( STRING( ent->v.iuser3 ) ); + + // buz: send spread angle value + entvars_t *pev = (entvars_t *)&ent->v; + CBasePlayer *pl = ( CBasePlayer *) CBasePlayer::Instance( pev ); + if (pl && pl->m_pActiveItem) + { + Vector vecSpread = pl->m_pActiveItem->GetSpreadVec(); + // buz: spread is very small value to send it on network directly + vecSpread = vecSpread * 500; + cd->vuser1 = vecSpread; + + // buz: send gun mode for hud indication + cd->iuser4 = pl->m_pActiveItem->GetMode(); + } + else + { + cd->vuser1 = g_vecZero; + cd->iuser4 = 0; + } +} + +/* +================= +CmdStart + +We're about to run this usercmd for the specified player. We can set up groupinfo and masking here, etc. +This is the time to examine the usercmd for anything extra. This call happens even if think does not. +================= +*/ +void CmdStart( const edict_t *player, const struct usercmd_s *cmd, unsigned int random_seed ) +{ +// ALERT(at_console, "got usercmd with lightlevel %f\n", cmd->viewangles[2]); + + entvars_t *pev = (entvars_t *)&player->v; + CBasePlayer *pl = ( CBasePlayer *) CBasePlayer::Instance( pev ); + + if( !pl ) + return; + + pl->m_fLightlevel = cmd->viewangles[2] > 1 ? 1 : cmd->viewangles[2]; // buz + + if ( pl->pev->groupinfo != 0 ) + { + UTIL_SetGroupTrace( pl->pev->groupinfo, GROUP_OP_AND ); + } + + pl->random_seed = random_seed; +} + +/* +================= +CmdEnd + +Each cmdstart is exactly matched with a cmd end, clean up any group trace flags, etc. here +================= +*/ +void CmdEnd ( const edict_t *player ) +{ + entvars_t *pev = (entvars_t *)&player->v; + CBasePlayer *pl = ( CBasePlayer *) CBasePlayer::Instance( pev ); + + if( !pl ) + return; + if ( pl->pev->groupinfo != 0 ) + { + UTIL_UnsetGroupTrace(); + } +} + +/* +================================ +ConnectionlessPacket + + Return 1 if the packet is valid. Set response_buffer_size if you want to send a response packet. Incoming, it holds the max + size of the response_buffer, so you must zero it out if you choose not to respond. +================================ +*/ +int ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ) +{ + // Parse stuff from args + int max_buffer_size = *response_buffer_size; + + // Zero it out since we aren't going to respond. + // If we wanted to response, we'd write data into response_buffer + *response_buffer_size = 0; + + // Since we don't listen for anything here, just respond that it's a bogus message + // If we didn't reject the message, we'd return 1 for success instead. + return 0; +} + +/* +================================ +GetHullBounds + + Engine calls this to enumerate player collision hulls, for prediction. Return 0 if the hullnumber doesn't exist. +================================ +*/ +int GetHullBounds( int hullnumber, float *mins, float *maxs ) +{ + int iret = 0; + + switch ( hullnumber ) + { + case 0: // Normal player + mins = VEC_HULL_MIN; + maxs = VEC_HULL_MAX; + iret = 1; + break; + case 1: // Crouched player + mins = VEC_DUCK_HULL_MIN; + maxs = VEC_DUCK_HULL_MAX; + iret = 1; + break; + case 2: // Point based hull + mins = Vector( 0, 0, 0 ); + maxs = Vector( 0, 0, 0 ); + iret = 1; + break; + } + + return iret; +} + +/* +================================ +CreateInstancedBaselines + +Create pseudo-baselines for items that aren't placed in the map at spawn time, but which are likely +to be created during play ( e.g., grenades, ammo packs, projectiles, corpses, etc. ) +================================ +*/ +void CreateInstancedBaselines ( void ) +{ + int iret = 0; + entity_state_t state; + + memset( &state, 0, sizeof( state ) ); + + // Create any additional baselines here for things like grendates, etc. + // iret = ENGINE_INSTANCE_BASELINE( pc->pev->classname, &state ); + + // Destroy objects. + //UTIL_Remove( pc ); +} + +/* +================================ +InconsistentFile + +One of the ENGINE_FORCE_UNMODIFIED files failed the consistency check for the specified player + Return 0 to allow the client to continue, 1 to force immediate disconnection ( with an optional disconnect message of up to 256 characters ) +================================ +*/ +int InconsistentFile( const edict_t *player, const char *filename, char *disconnect_message ) +{ + // Server doesn't care? + if ( CVAR_GET_FLOAT( "mp_consistency" ) != 1 ) + return 0; + + // Default behavior is to kick the player + sprintf( disconnect_message, "Server is enforcing file consistency for %s\n", filename ); + + // Kick now with specified disconnect message. + return 1; +} + +/* +================================ +AllowLagCompensation + + The game .dll should return 1 if lag compensation should be allowed ( could also just set + the sv_unlag cvar. + Most games right now should return 0, until client-side weapon prediction code is written + and tested for them ( note you can predict weapons, but not do lag compensation, too, + if you want. +================================ +*/ +int AllowLagCompensation( void ) +{ + return 1; +} + +/* +================================ +ShouldCollide + + Called when the engine believes two entities are about to collide. Return 0 if you + want the two entities to just pass through each other without colliding or calling the + touch function. +================================ +*/ +int ShouldCollide( edict_t *pentTouched, edict_t *pentOther ) +{ + CBaseEntity *pTouch = CBaseEntity::Instance( pentTouched ); + CBaseEntity *pOther = CBaseEntity::Instance( pentOther ); + + if( pTouch && pOther ) + return pOther->ShouldCollide( pTouch ); + + return 1; +} \ No newline at end of file diff --git a/dlls/client.h b/dlls/client.h new file mode 100644 index 0000000..da206dd --- /dev/null +++ b/dlls/client.h @@ -0,0 +1,69 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef CLIENT_H +#define CLIENT_H + +extern void respawn( entvars_t* pev, BOOL fCopyCorpse ); +extern BOOL ClientConnect( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ); +extern void ClientDisconnect( edict_t *pEntity ); +extern void ClientKill( edict_t *pEntity ); +extern void ClientPutInServer( edict_t *pEntity ); +extern void ClientCommand( edict_t *pEntity ); +extern void ClientUserInfoChanged( edict_t *pEntity, char *infobuffer ); +extern void ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ); +extern void ServerDeactivate( void ); +extern void StartFrame( void ); +extern void PlayerPostThink( edict_t *pEntity ); +extern void PlayerPreThink( edict_t *pEntity ); +extern void ParmsNewLevel( void ); +extern void ParmsChangeLevel( void ); + +extern void ClientPrecache( void ); + +extern const char *GetGameDescription( void ); +extern void PlayerCustomization( edict_t *pEntity, customization_t *pCust ); + +extern void SpectatorConnect ( edict_t *pEntity ); +extern void SpectatorDisconnect ( edict_t *pEntity ); +extern void SpectatorThink ( edict_t *pEntity ); + +extern void Sys_Error( const char *error_string ); + +extern void SetupVisibility( edict_t *pViewEntity, edict_t *pClient, unsigned char **pvs, unsigned char **pas ); +extern void UpdateClientData ( const struct edict_s *ent, int sendweapons, struct clientdata_s *cd ); +extern int AddToFullPack( struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, unsigned char *pSet ); +extern void CreateBaseline( int player, int eindex, struct entity_state_s *baseline, struct edict_s *entity, int playermodelindex, vec3_t player_mins, vec3_t player_maxs ); +extern void RegisterEncoders( void ); + +extern int GetWeaponData( struct edict_s *player, struct weapon_data_s *info ); + +extern void CmdStart( const edict_t *player, const struct usercmd_s *cmd, unsigned int random_seed ); +extern void CmdEnd ( const edict_t *player ); + +extern int ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ); + +extern int GetHullBounds( int hullnumber, float *mins, float *maxs ); + +extern int ShouldCollide( edict_t *pentTouched, edict_t *pentOther ); + +extern void OnFreeEntPrivateData( edict_s *pEdict ); + +extern void CreateInstancedBaselines ( void ); + +extern int InconsistentFile( const edict_t *player, const char *filename, char *disconnect_message ); + +extern int AllowLagCompensation( void ); + +#endif // CLIENT_H diff --git a/dlls/combat.cpp b/dlls/combat.cpp new file mode 100644 index 0000000..ab699fb --- /dev/null +++ b/dlls/combat.cpp @@ -0,0 +1,1968 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== combat.cpp ======================================================== + + functions dealing with damage infliction & death + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "soundent.h" +#include "decals.h" +#include "animation.h" +#include "weapons.h" +#include "func_break.h" +#include "studio.h" //LRC +#include "material.h" + +extern DLL_GLOBAL Vector g_vecAttackDir; +extern DLL_GLOBAL int g_iSkillLevel; + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); +extern entvars_t *g_pevLastInflictor; + +#define GERMAN_GIB_COUNT 4 +#define HUMAN_GIB_COUNT 6 +#define ALIEN_GIB_COUNT 4 + +MULTIDAMAGE gMultiDamage; + +/* +============================================================================== + +MULTI-DAMAGE + +Collects multiple small damages into a single damage + +============================================================================== +*/ + +// +// ClearMultiDamage - resets the global multi damage accumulator +// +void ClearMultiDamage(void) +{ + gMultiDamage.pEntity = NULL; + gMultiDamage.amount = 0; + gMultiDamage.type = 0; +} + + +// +// ApplyMultiDamage - inflicts contents of global multi damage register on gMultiDamage.pEntity +// +// GLOBALS USED: +// gMultiDamage + +void ApplyMultiDamage(entvars_t *pevInflictor, entvars_t *pevAttacker ) +{ + Vector vecSpot1;//where blood comes from + Vector vecDir;//direction blood should go + TraceResult tr; + + if ( !gMultiDamage.pEntity ) + return; + + gMultiDamage.pEntity->TakeDamage(pevInflictor, pevAttacker, gMultiDamage.amount, gMultiDamage.type ); +} + + +// GLOBALS USED: +// gMultiDamage + +void AddMultiDamage( entvars_t *pevInflictor, CBaseEntity *pEntity, float flDamage, int bitsDamageType) +{ + if ( !pEntity ) + return; + + gMultiDamage.type |= bitsDamageType; + + if ( pEntity != gMultiDamage.pEntity ) + { + ApplyMultiDamage(pevInflictor,pevInflictor); // UNDONE: wrong attacker! + gMultiDamage.pEntity = pEntity; + gMultiDamage.amount = 0; + } + + gMultiDamage.amount += flDamage; +} + +/* +================ +SpawnBlood +================ +*/ +void SpawnBlood(Vector vecSpot, int bloodColor, float flDamage) +{ + UTIL_BloodDrips( vecSpot, g_vecAttackDir, bloodColor, (int)flDamage ); +} + + +const char *DamageDecal( CBaseEntity *pEntity, int bitsDamageType ) +{ + if( !pEntity ) + return "shot"; + + return pEntity->DamageDecal( bitsDamageType ); +} + +void SpawnPartEffect( TraceResult *pTrace, matdef_t *pMat ) +{ + if( !pMat || pTrace->flFraction == 1.0f ) + return; + + // fire effects + for( int i = 0; pMat->impact_parts[i] != NULL; i++ ) + { + MESSAGE_BEGIN( MSG_PVS, gmsgPartEffect, pTrace->vecEndPos ); + WRITE_COORD( pTrace->vecEndPos.x ); + WRITE_COORD( pTrace->vecEndPos.y ); + WRITE_COORD( pTrace->vecEndPos.z ); + WRITE_COORD( pTrace->vecPlaneNormal.x * 8192 ); + WRITE_COORD( pTrace->vecPlaneNormal.y * 8192 ); + WRITE_COORD( pTrace->vecPlaneNormal.z * 8192 ); + WRITE_STRING( pMat->impact_parts[i] ); + MESSAGE_END(); + } +} + +void DecalGunshot( TraceResult *pTrace, int iBulletType, const Vector &vecSrc, bool fromPlayer ) +{ + // buz + if (pTrace->fInWater ) + return; + + // Is the entity valid + if ( !UTIL_IsValidEntity( pTrace->pHit ) ) + return; + + CBaseEntity *pHit = CBaseEntity :: Instance( pTrace->pHit ); + + matdef_t *pMat = NULL; + + if( FBitSet( pHit->pev->flags, ( FL_MONSTER|FL_CLIENT ))) + { + switch( iBulletType ) + { + case BULLET_NORMAL: + case BULLET_BUCKSHOT: + default: + UTIL_StudioDecalTrace( pTrace, DamageDecal( pHit, DMG_BULLET )); + break; + case BULLET_STAB: + UTIL_StudioDecalTrace( pTrace, DamageDecal( pHit, DMG_CLUB )); + break; + } + } + else if( !fromPlayer ) + { + if( pHit->pev->solid == SOLID_BSP || pHit->pev->movetype == MOVETYPE_PUSHSTEP ) + { + pMat = COM_MatDefFromSurface( TRACE_SURFACE( pTrace->pHit, vecSrc, pTrace->vecEndPos ), pTrace->vecEndPos ); + if ( pMat ) UTIL_TraceCustomDecal( pTrace, pMat->impact_decal, RANDOM_FLOAT( 0.0f, 360.0f )); + } + else if( pHit->pev->solid == SOLID_CUSTOM && pTrace->pMat ) + { + pMat = pTrace->pMat->effects; + if ( pMat ) UTIL_StudioDecalTrace( pTrace, pMat->impact_decal ); + } + } + + SpawnPartEffect( pTrace, pMat ); +} + +// +// EjectBrass - tosses a brass shell from passed origin at passed velocity +// +void EjectBrass ( const Vector &vecOrigin, const Vector &vecVelocity, float rotation, int model, int soundtype ) +{ + // FIX: when the player shoots, their gun isn't in the same position as it is on the model other players see. + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecOrigin ); + WRITE_BYTE( TE_MODEL); + WRITE_COORD( vecOrigin.x); + WRITE_COORD( vecOrigin.y); + WRITE_COORD( vecOrigin.z); + WRITE_COORD( vecVelocity.x); + WRITE_COORD( vecVelocity.y); + WRITE_COORD( vecVelocity.z); + WRITE_ANGLE( rotation ); + WRITE_SHORT( model ); + WRITE_BYTE ( soundtype); + WRITE_BYTE ( 100 );// 10 seconds + MESSAGE_END(); +} + +// HACKHACK -- The gib velocity equations don't work +void CGib :: LimitVelocity( void ) +{ + float length = pev->velocity.Length(); + + // ceiling at 1500. The gib velocity equation is not bounded properly. Rather than tune it + // in 3 separate places again, I'll just limit it here. + if ( length > 1500.0 ) + pev->velocity = pev->velocity.Normalize() * 1500; // This should really be sv_maxvelocity * 0.75 or something +} + + +void CGib :: SpawnStickyGibs( entvars_t *pevVictim, Vector vecOrigin, int cGibs ) +{ + int i; + + if ( g_Language == LANGUAGE_GERMAN ) + { + // no sticky gibs in germany right now! + return; + } + + for ( i = 0 ; i < cGibs ; i++ ) + { + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + pGib->Spawn( "models/stickygib.mdl" ); + pGib->pev->body = RANDOM_LONG(0,2); + + if ( pevVictim ) + { + pGib->pev->origin.x = vecOrigin.x + RANDOM_FLOAT( -3, 3 ); + pGib->pev->origin.y = vecOrigin.y + RANDOM_FLOAT( -3, 3 ); + pGib->pev->origin.z = vecOrigin.z + RANDOM_FLOAT( -3, 3 ); + + /* + pGib->pev->origin.x = pevVictim->absmin.x + pevVictim->size.x * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.y = pevVictim->absmin.y + pevVictim->size.y * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.z = pevVictim->absmin.z + pevVictim->size.z * (RANDOM_FLOAT ( 0 , 1 ) ); + */ + + // make the gib fly away from the attack vector + pGib->pev->velocity = g_vecAttackDir * -1; + + // mix in some noise + pGib->pev->velocity.x += RANDOM_FLOAT ( -0.15, 0.15 ); + pGib->pev->velocity.y += RANDOM_FLOAT ( -0.15, 0.15 ); + pGib->pev->velocity.z += RANDOM_FLOAT ( -0.15, 0.15 ); + + pGib->pev->velocity = pGib->pev->velocity * 900; + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 250, 400 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 250, 400 ); + + // copy owner's blood color + pGib->m_bloodColor = (CBaseEntity::Instance(pevVictim))->BloodColor(); + + if ( pevVictim->health > -50) + { + pGib->pev->velocity = pGib->pev->velocity * 0.7; + } + else if ( pevVictim->health > -200) + { + pGib->pev->velocity = pGib->pev->velocity * 2; + } + else + { + pGib->pev->velocity = pGib->pev->velocity * 4; + } + + + pGib->pev->movetype = MOVETYPE_TOSS; + pGib->pev->solid = SOLID_BBOX; + UTIL_SetSize ( pGib->pev, Vector ( 0, 0 ,0 ), Vector ( 0, 0, 0 ) ); + pGib->SetTouch(&CGib:: StickyGibTouch ); + pGib->SetThink (NULL); + } + pGib->LimitVelocity(); + } +} + +void CGib :: SpawnHeadGib( entvars_t *pevVictim ) +{ + if ( g_Language == LANGUAGE_GERMAN ) + SpawnHeadGib(pevVictim, "models/germangibs.mdl" );// throw one head + else + SpawnHeadGib(pevVictim, "models/hgibs.mdl" ); +} + +void CGib :: SpawnHeadGib( entvars_t *pevVictim, const char* szGibModel ) +{ + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + pGib->Spawn( szGibModel );// throw one head + pGib->pev->body = 0; + + if ( pevVictim ) + { + pGib->pev->origin = pevVictim->origin + pevVictim->view_ofs; + + edict_t *pentPlayer = FIND_CLIENT_IN_PVS( pGib->edict() ); + + if ( RANDOM_LONG ( 0, 100 ) <= 5 && pentPlayer ) + { + // 5% chance head will be thrown at player's face. + entvars_t *pevPlayer; + + pevPlayer = VARS( pentPlayer ); + pGib->pev->velocity = ( ( pevPlayer->origin + pevPlayer->view_ofs ) - pGib->pev->origin ).Normalize() * 300; + pGib->pev->velocity.z += 100; + } + else + { + pGib->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + } + + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 100, 200 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 100, 300 ); + + // copy owner's blood color + pGib->m_bloodColor = (CBaseEntity::Instance(pevVictim))->BloodColor(); + + if ( pevVictim->health > -50) + { + pGib->pev->velocity = pGib->pev->velocity * 0.7; + } + else if ( pevVictim->health > -200) + { + pGib->pev->velocity = pGib->pev->velocity * 2; + } + else + { + pGib->pev->velocity = pGib->pev->velocity * 4; + } + } + pGib->LimitVelocity(); +} + +void CGib :: SpawnRandomGibs( entvars_t *pevVictim, int cGibs, int human ) +{ + if ( g_Language == LANGUAGE_GERMAN ) + SpawnRandomGibs(pevVictim, cGibs, 1, "models/germangibs.mdl"); + else if (human) + SpawnRandomGibs(pevVictim, cGibs, 1, "models/hgibs.mdl"); + else + SpawnRandomGibs(pevVictim, cGibs, 0, "models/agibs.mdl"); +} + +//LRC - changed signature, to support custom gib models +void CGib :: SpawnRandomGibs( entvars_t *pevVictim, int cGibs, int notfirst, const char *szGibModel ) +{ + if (cGibs == 0) return; // spawn nothing! + + CGib *pGib = GetClassPtr( (CGib *)NULL ); + pGib->Spawn( szGibModel ); + + //LRC - check the model itself to find out how many gibs are available + studiohdr_t *pstudiohdr = (studiohdr_t *)(GET_MODEL_PTR( ENT(pGib->pev) )); + if (! pstudiohdr) + return; + + mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)pstudiohdr + pstudiohdr->bodypartindex); + //ALERT(at_console, "read %d bodyparts, canonical is %d\n", pbodypart->nummodels, HUMAN_GIB_COUNT); + + for (int cSplat = 0 ; cSplat < cGibs ; cSplat++ ) + { + if (pGib == NULL) // first time through, we set pGib before the loop started + { + pGib = GetClassPtr( (CGib *)NULL ); + pGib->Spawn( szGibModel ); + } + + if (notfirst) + pGib->pev->body = RANDOM_LONG(1, pbodypart->nummodels - 1);// start at one to avoid throwing random amounts of skulls (0th gib) + else + pGib->pev->body = RANDOM_LONG(0, pbodypart->nummodels - 1); + + if ( pevVictim ) + { + // spawn the gib somewhere in the monster's bounding volume + pGib->pev->origin.x = pevVictim->absmin.x + pevVictim->size.x * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.y = pevVictim->absmin.y + pevVictim->size.y * (RANDOM_FLOAT ( 0 , 1 ) ); + pGib->pev->origin.z = pevVictim->absmin.z + pevVictim->size.z * (RANDOM_FLOAT ( 0 , 1 ) ) + 1; // absmin.z is in the floor because the engine subtracts 1 to enlarge the box + + // make the gib fly away from the attack vector + pGib->pev->velocity = g_vecAttackDir * -1; + + // mix in some noise + pGib->pev->velocity.x += RANDOM_FLOAT ( -0.25, 0.25 ); + pGib->pev->velocity.y += RANDOM_FLOAT ( -0.25, 0.25 ); + pGib->pev->velocity.z += RANDOM_FLOAT ( -0.25, 0.25 ); + + pGib->pev->velocity = pGib->pev->velocity * RANDOM_FLOAT ( 300, 400 ); + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 100, 200 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 100, 300 ); + + // copy owner's blood color + pGib->m_bloodColor = (CBaseEntity::Instance(pevVictim))->BloodColor(); + + if ( pevVictim->health > -50) + { + pGib->pev->velocity = pGib->pev->velocity * 0.7; + } + else if ( pevVictim->health > -200) + { + pGib->pev->velocity = pGib->pev->velocity * 2; + } + else + { + pGib->pev->velocity = pGib->pev->velocity * 4; + } + + pGib->pev->solid = SOLID_BBOX; + UTIL_SetSize ( pGib->pev, Vector( 0 , 0 , 0 ), Vector ( 0, 0, 0 ) ); + } + pGib->LimitVelocity(); + pGib = NULL; //LRC + } +} + +// automatically set collision box +void CBaseMonster :: AutoSetSize( void ) +{ + studiohdr_t *pstudiohdr; + pstudiohdr = (studiohdr_t *)GET_MODEL_PTR( edict() ); + + if( pstudiohdr == NULL ) + { + UTIL_SetSize( pev, Vector( -10, -10, -10 ), Vector( 10, 10, 10 )); + ALERT( at_error, "env_static: unable to fetch model pointer!\n" ); + return; + } + + mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + UTIL_SetSize( pev, pseqdesc[pev->sequence].bbmin, pseqdesc[pev->sequence].bbmax ); +} + +//LRC - work out gibs from blood colour, instead of from class. +BOOL CBaseMonster :: HasHumanGibs( void ) +{ + int myClass = Classify(); + + // these types of monster don't use gibs + if ( myClass == CLASS_NONE || myClass == CLASS_MACHINE || + myClass == CLASS_PLAYER_BIOWEAPON && myClass == CLASS_ALIEN_BIOWEAPON) + { + return FALSE; + } + else + { + return (this->m_bloodColor == BLOOD_COLOR_RED); + } +} + +//LRC - work out gibs from blood colour, instead. +BOOL CBaseMonster :: HasAlienGibs( void ) +{ + int myClass = Classify(); + + // these types of monster don't use gibs + if ( myClass == CLASS_NONE || myClass == CLASS_MACHINE || + myClass == CLASS_PLAYER_BIOWEAPON && myClass == CLASS_ALIEN_BIOWEAPON) + { + return FALSE; + } + else + { + return (this->m_bloodColor == BLOOD_COLOR_GREEN); + } +} + +void CBaseMonster::FadeMonster( void ) +{ + StopAnimation(); + pev->velocity = g_vecZero; + pev->movetype = MOVETYPE_NONE; + pev->avelocity = g_vecZero; + pev->animtime = gpGlobals->time; + pev->effects |= EF_NOINTERP; + SUB_StartFadeOut(); +} + +//========================================================= +// GibMonster - create some gore and get rid of a monster's +// model. +//========================================================= +void CBaseMonster :: GibMonster( void ) +{ + TraceResult tr; + BOOL gibbed = FALSE; + int iszCustomGibs; + + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "common/bodysplat.wav", 1, ATTN_NORM); + + if ( iszCustomGibs = HasCustomGibs() ) //LRC - monster_generic can have a custom gibset + { + if ( CVAR_GET_FLOAT("violence_hgibs") != 0 ) + { + CGib::SpawnHeadGib( pev, STRING(iszCustomGibs) ); + CGib::SpawnRandomGibs( pev, 4, 1, STRING(iszCustomGibs) ); + } + gibbed = TRUE; + } + // only humans throw skulls !!!UNDONE - eventually monsters will have their own sets of gibs + else if ( HasHumanGibs() ) + { + if ( CVAR_GET_FLOAT("violence_hgibs") != 0 )// Only the player will ever fail this test + { + CGib::SpawnHeadGib( pev ); + CGib::SpawnRandomGibs( pev, 4, 1 ); // throw some human gibs. + } + gibbed = TRUE; + } + else if ( HasAlienGibs() ) + { + if ( CVAR_GET_FLOAT("violence_agibs") != 0 )// Should never fail this test, but someone might call it directly + { + CGib::SpawnRandomGibs( pev, 4, 0 ); // Throw alien gibs + } + gibbed = TRUE; + } + + if ( !IsPlayer() ) + { + if ( gibbed ) + { + // don't remove players! + SetThink(&CBaseMonster :: SUB_Remove ); + SetNextThink( 0 ); + } + else + { + FadeMonster(); + } + } +} + +//========================================================= +// GetDeathActivity - determines the best type of death +// anim to play. +//========================================================= +Activity CBaseMonster :: GetDeathActivity ( void ) +{ + Activity deathActivity; + BOOL fTriedDirection; + float flDot; + TraceResult tr; + Vector vecSrc; + + if ( pev->deadflag != DEAD_NO ) + { + // don't run this while dying. + return m_IdealActivity; + } + + vecSrc = Center(); + + fTriedDirection = FALSE; + deathActivity = ACT_DIESIMPLE;// in case we can't find any special deaths to do. + + UTIL_MakeVectors ( pev->angles ); + flDot = DotProduct ( gpGlobals->v_forward, g_vecAttackDir * -1 ); + + switch ( m_LastHitGroup ) + { + // try to pick a region-specific death. + case HITGROUP_HEAD: + + // buz: play brains squish sound + // ALERT(at_console, "HEADSHOT SOUND\n"); + UTIL_EmitAmbientSound(ENT(0), vecSrc, "common/headshot.wav", 1, ATTN_NORM, 0, 100); + deathActivity = ACT_DIE_HEADSHOT; + break; + + case HITGROUP_STOMACH: + deathActivity = ACT_DIE_GUTSHOT; + break; + + case HITGROUP_GENERIC: + // try to pick a death based on attack direction + fTriedDirection = TRUE; + + if ( flDot > 0.3 ) + { + deathActivity = ACT_DIEFORWARD; + } + else if ( flDot <= -0.3 ) + { + deathActivity = ACT_DIEBACKWARD; + } + break; + + default: + // try to pick a death based on attack direction + fTriedDirection = TRUE; + + if ( flDot > 0.3 ) + { + deathActivity = ACT_DIEFORWARD; + } + else if ( flDot <= -0.3 ) + { + deathActivity = ACT_DIEBACKWARD; + } + break; + } + + + // can we perform the prescribed death? + if ( LookupActivity ( deathActivity ) == ACTIVITY_NOT_AVAILABLE ) + { + // no! did we fail to perform a directional death? + if ( fTriedDirection ) + { + // if yes, we're out of options. Go simple. + deathActivity = ACT_DIESIMPLE; + } + else + { + // cannot perform the ideal region-specific death, so try a direction. + if ( flDot > 0.3 ) + { + deathActivity = ACT_DIEFORWARD; + } + else if ( flDot <= -0.3 ) + { + deathActivity = ACT_DIEBACKWARD; + } + } + } + + if ( LookupActivity ( deathActivity ) == ACTIVITY_NOT_AVAILABLE ) + { + // if we're still invalid, simple is our only option. + deathActivity = ACT_DIESIMPLE; + } + + if ( deathActivity == ACT_DIEFORWARD ) + { + // make sure there's room to fall forward + UTIL_TraceHull ( vecSrc, vecSrc + gpGlobals->v_forward * 64, dont_ignore_monsters, head_hull, edict(), &tr ); + + if ( tr.flFraction != 1.0 ) + { + deathActivity = ACT_DIESIMPLE; + } + } + + if ( deathActivity == ACT_DIEBACKWARD ) + { + // make sure there's room to fall backward + UTIL_TraceHull ( vecSrc, vecSrc - gpGlobals->v_forward * 64, dont_ignore_monsters, head_hull, edict(), &tr ); + + if ( tr.flFraction != 1.0 ) + { + deathActivity = ACT_DIESIMPLE; + } + } + + return deathActivity; +} + +//========================================================= +// GetSmallFlinchActivity - determines the best type of flinch +// anim to play. +//========================================================= +Activity CBaseMonster :: GetSmallFlinchActivity ( void ) +{ + Activity flinchActivity; + BOOL fTriedDirection; + float flDot; + + fTriedDirection = FALSE; + UTIL_MakeVectors ( pev->angles ); + flDot = DotProduct ( gpGlobals->v_forward, g_vecAttackDir * -1 ); + + switch ( m_LastHitGroup ) + { + // pick a region-specific flinch + case HITGROUP_HEAD: + flinchActivity = ACT_FLINCH_HEAD; + break; + case HITGROUP_STOMACH: + flinchActivity = ACT_FLINCH_STOMACH; + break; + case HITGROUP_LEFTARM: + flinchActivity = ACT_FLINCH_LEFTARM; + break; + case HITGROUP_RIGHTARM: + flinchActivity = ACT_FLINCH_RIGHTARM; + break; + case HITGROUP_LEFTLEG: + flinchActivity = ACT_FLINCH_LEFTLEG; + break; + case HITGROUP_RIGHTLEG: + flinchActivity = ACT_FLINCH_RIGHTLEG; + break; + case HITGROUP_GENERIC: + default: + // just get a generic flinch. + flinchActivity = ACT_SMALL_FLINCH; + break; + } + + + // do we have a sequence for the ideal activity? + if ( LookupActivity ( flinchActivity ) == ACTIVITY_NOT_AVAILABLE ) + { + flinchActivity = ACT_SMALL_FLINCH; + } + + return flinchActivity; +} + + +void CBaseMonster::BecomeDead( void ) +{ + pev->takedamage = DAMAGE_YES;// don't let autoaim aim at corpses. + + // give the corpse half of the monster's original maximum health. + pev->health = pev->max_health / 2; + pev->max_health = 5; // max_health now becomes a counter for how many blood decals the corpse can place. + + // make the corpse fly away from the attack vector + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_NOT; + //pev->flags &= ~FL_ONGROUND; + //pev->origin.z += 2; + //pev->velocity = g_vecAttackDir * -1; + //pev->velocity = pev->velocity * RANDOM_FLOAT( 300, 400 ); +} + +BOOL CBaseMonster::ShouldGibMonster( int iGib ) +{ + if ( ( iGib == GIB_NORMAL && pev->health < GIB_HEALTH_VALUE ) || ( iGib == GIB_ALWAYS ) ) + return TRUE; + + return FALSE; +} + +void CBaseMonster::CallGibMonster( void ) +{ + BOOL fade = FALSE; + + if ( HasHumanGibs() ) + { + if ( CVAR_GET_FLOAT("violence_hgibs") == 0 ) + fade = TRUE; + } + else if ( HasAlienGibs() ) + { + if ( CVAR_GET_FLOAT("violence_agibs") == 0 ) + fade = TRUE; + } + + pev->takedamage = DAMAGE_NO; + pev->solid = SOLID_NOT;// do something with the body. while monster blows up + + if ( fade ) + { + FadeMonster(); + } + else + { + pev->effects |= EF_NODRAW; // make the model invisible. + GibMonster(); + } + + pev->deadflag = DEAD_DEAD; + FCheckAITrigger(); + + // don't let the status bar glitch for players.with <0 health. + if (pev->health < -99) + { + pev->health = 0; + } + + if ( ShouldFadeOnDeath() && !fade ) + UTIL_Remove(this); +} + +const char *CBaseMonster::DamageDecal( int bitsDamageType ) +{ + if( bitsDamageType & ( DMG_BULLET|DMG_CLUB )) + { + if( BloodColor() == BLOOD_COLOR_RED ) + return "bodyhurt_red"; + else if( BloodColor() == BLOOD_COLOR_YELLOW ) + return "bodyhurt_yellow"; + } + + // Assume no blood is vehicles or furniture + return "shot"; +} + +/* +============ +Killed +============ +*/ +void CBaseMonster :: Killed( entvars_t *pevAttacker, int iGib ) +{ + unsigned int cCount = 0; + BOOL fDone = FALSE; + + if ( HasMemory( bits_MEMORY_KILLED ) ) + { + if ( ShouldGibMonster( iGib ) ) + CallGibMonster(); + return; + } + + Remember( bits_MEMORY_KILLED ); + + // clear the deceased's sound channels.(may have been firing or reloading when killed) + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "common/null.wav", 1, ATTN_NORM); + m_IdealMonsterState = MONSTERSTATE_DEAD; + // Make sure this condition is fired too (TakeDamage breaks out before this happens on death) + SetConditions( bits_COND_LIGHT_DAMAGE ); + + // tell owner ( if any ) that we're dead.This is mostly for MonsterMaker functionality. + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + if ( pOwner ) + { + pOwner->DeathNotice( pev ); + } + + if ( ShouldGibMonster( iGib ) ) + { + CallGibMonster(); + return; + } + else if ( pev->flags & FL_MONSTER ) + { + SetTouch( NULL ); + BecomeDead(); + } + + // don't let the status bar glitch for players.with <0 health. + if (pev->health < -99) + { + pev->health = 0; + } + + //pev->enemy = ENT( pevAttacker );//why? (sjb) + + m_IdealMonsterState = MONSTERSTATE_DEAD; +} + +// +// fade out - slowly fades a entity out, then removes it. +// +// DON'T USE ME FOR GIBS AND STUFF IN MULTIPLAYER! +// SET A FUTURE THINK AND A RENDERMODE!! +void CBaseEntity :: SUB_StartFadeOut ( void ) +{ + if (pev->rendermode == kRenderNormal) + { + pev->renderamt = 255; + pev->rendermode = kRenderTransTexture; + } + + pev->solid = SOLID_NOT; + pev->avelocity = g_vecZero; + + SetNextThink( 0.1 ); + SetThink(&CBaseEntity :: SUB_FadeOut ); +} + +void CBaseEntity :: SUB_FadeOut ( void ) +{ + if ( pev->renderamt > 7 ) + { + pev->renderamt -= 7; + SetNextThink( 0.1 ); + } + else + { + pev->renderamt = 0; + SetNextThink( 0.2 ); + SetThink(&CBaseEntity :: SUB_Remove ); + } +} + +//========================================================= +// WaitTillLand - in order to emit their meaty scent from +// the proper location, gibs should wait until they stop +// bouncing to emit their scent. That's what this function +// does. +//========================================================= +void CGib :: WaitTillLand ( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + if ( pev->velocity == g_vecZero ) + { + SetThink(&CGib ::SUB_StartFadeOut); + SetNextThink( m_lifeTime ); + + // If you bleed, you stink! + if ( m_bloodColor != DONT_BLEED ) + { + // ok, start stinkin! + CSoundEnt::InsertSound ( bits_SOUND_MEAT, pev->origin, 384, 25 ); + } + } + else + { + // wait and check again in another half second. + SetNextThink( 0.5 ); + } +} + +// +// Gib bounces on the ground or wall, sponges some blood down, too! +// +void CGib :: BounceGibTouch ( CBaseEntity *pOther ) +{ + Vector vecSpot; + TraceResult tr; + + //if ( RANDOM_LONG(0,1) ) + // return;// don't bleed everytime + + if (pev->flags & FL_ONGROUND) + { + pev->velocity = pev->velocity * 0.9; + pev->angles.x = 0; + pev->angles.z = 0; + pev->avelocity.x = 0; + pev->avelocity.z = 0; + } + else + { + if ( g_Language != LANGUAGE_GERMAN && m_cBloodDecals > 0 && m_bloodColor != DONT_BLEED ) + { + vecSpot = pev->origin + Vector ( 0 , 0 , 8 );//move up a bit, and trace down. + UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -24 ), ignore_monsters, ENT(pev), & tr); + + UTIL_BloodDecalTrace( &tr, m_bloodColor ); + + m_cBloodDecals--; + } + + if ( m_material != matNone && RANDOM_LONG(0,2) == 0 ) + { + float volume; + float zvel = fabs(pev->velocity.z); + + volume = 0.8 * min(1.0, ((float)zvel) / 450.0); + + CBreakable::MaterialSoundRandom( edict(), (Materials)m_material, volume ); + } + } +} + +// +// Sticky gib puts blood on the wall and stays put. +// +void CGib :: StickyGibTouch ( CBaseEntity *pOther ) +{ + Vector vecSpot; + TraceResult tr; + + SetThink(&CGib :: SUB_Remove ); + SetNextThink( 10 ); + + if ( !FClassnameIs( pOther->pev, "worldspawn" ) ) + { + SetNextThink( 0 ); + return; + } + + UTIL_TraceLine ( pev->origin, pev->origin + pev->velocity * 32, ignore_monsters, ENT(pev), & tr); + + UTIL_BloodDecalTrace( &tr, m_bloodColor ); + + pev->velocity = tr.vecPlaneNormal * -1; + pev->angles = UTIL_VecToAngles ( pev->velocity ); + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + pev->movetype = MOVETYPE_NONE; +} + +// +// Throw a chunk +// +void CGib :: Spawn( const char *szGibModel ) +{ + pev->movetype = MOVETYPE_BOUNCE; + pev->friction = 0.55; // deading the bounce a bit + + // sometimes an entity inherits the edict from a former piece of glass, + // and will spawn using the same render FX or rendermode! bad! + pev->renderamt = 255; + pev->rendermode = kRenderNormal; + pev->renderfx = kRenderFxNone; + pev->solid = SOLID_TRIGGER; //LRC - so that they don't get in each other's way when we fire lots +// pev->solid = SOLID_SLIDEBOX;/// hopefully this will fix the VELOCITY TOO LOW crap + pev->classname = MAKE_STRING("gib"); + + SET_MODEL(ENT(pev), szGibModel); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + + SetNextThink( 4 ); + m_lifeTime = 25; + SetThink(&CGib :: WaitTillLand ); + SetTouch(&CGib :: BounceGibTouch ); + + m_material = matNone; + m_cBloodDecals = 5;// how many blood decals this gib can place (1 per bounce until none remain). +} + +// take health +int CBaseMonster :: TakeHealth (float flHealth, int bitsDamageType) +{ + if (!pev->takedamage) + return 0; + + // clear out any damage types we healed. + // UNDONE: generic health should not heal any + // UNDONE: time-based damage + + m_bitsDamageType &= ~(bitsDamageType & ~DMG_TIMEBASED); + + return CBaseEntity::TakeHealth(flHealth, bitsDamageType); +} + +/* +============ +TakeDamage + +The damage is coming from inflictor, but get mad at attacker +This should be the only function that ever reduces health. +bitsDamageType indicates the type of damage sustained, ie: DMG_SHOCK + +Time-based damage: only occurs while the monster is within the trigger_hurt. +When a monster is poisoned via an arrow etc it takes all the poison damage at once. + +GLOBALS ASSUMED SET: g_iSkillLevel +============ +*/ +int CBaseMonster :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + float flTake; + Vector vecDir; + + if (!pev->takedamage) + return 0; + + if ( !IsAlive() ) + { + return DeadTakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); + } + + if ( pev->deadflag == DEAD_NO ) + { + // no pain sound during death animation. + PainSound();// "Ouch!" + } + + //!!!LATER - make armor consideration here! + flTake = flDamage; + + // set damage type sustained + m_bitsDamageType |= bitsDamageType; + + // grab the vector of the incoming attack. ( pretend that the inflictor is a little lower than it really is, so the body will tend to fly upward a bit). + vecDir = Vector( 0, 0, 0 ); + if (!FNullEnt( pevInflictor )) + { + CBaseEntity *pInflictor = CBaseEntity :: Instance( pevInflictor ); + if (pInflictor) + { + vecDir = ( pInflictor->Center() - Vector ( 0, 0, 10 ) - Center() ).Normalize(); + vecDir = g_vecAttackDir = vecDir.Normalize(); + } + } + + // add to the damage total for clients, which will be sent as a single + // message at the end of the frame + // todo: remove after combining shotgun blasts? + if ( IsPlayer() ) + { + if ( pevInflictor ) + pev->dmg_inflictor = ENT(pevInflictor); + + pev->dmg_take += flTake; + + // check for godmode or invincibility + if ( pev->flags & FL_GODMODE ) + { + return 0; + } + } + + // if this is a player, move him around! + if ( ( !FNullEnt( pevInflictor ) ) && (pev->movetype == MOVETYPE_WALK) && (!pevAttacker || pevAttacker->solid != SOLID_TRIGGER) ) + { + pev->velocity = pev->velocity + vecDir * -DamageForce( flDamage ); + } + + // do the damage + pev->health -= flTake; + + + // HACKHACK Don't kill monsters in a script. Let them break their scripts first + if ( m_MonsterState == MONSTERSTATE_SCRIPT ) + { + SetConditions( bits_COND_LIGHT_DAMAGE ); + return 0; + } + + if ( pev->health <= 0 ) + { + g_pevLastInflictor = pevInflictor; + + if ( bitsDamageType & DMG_ALWAYSGIB ) + { + Killed( pevAttacker, GIB_ALWAYS ); + } + // Wargon: Íèêîãäà íå ãèáàòü åñëè òèï äàìàãè - DMG_SLASH. (1.1) + else if ((bitsDamageType & DMG_NEVERGIB) || (bitsDamageType & DMG_SLASH)) + { + Killed( pevAttacker, GIB_NEVER ); + } + else + { + Killed( pevAttacker, GIB_NORMAL ); + } + + g_pevLastInflictor = NULL; + + return 0; + } + + // react to the damage (get mad) + if ( (pev->flags & FL_MONSTER) && !FNullEnt(pevAttacker) ) + { + //LRC - new behaviours, for m_iPlayerReact. + if (pevAttacker->flags & FL_CLIENT) + { + if (m_iPlayerReact == 2) + { + // just get angry. + Remember( bits_MEMORY_PROVOKED ); + } + else if (m_iPlayerReact == 3) + { + // try to decide whether it was deliberate... if I have an enemy, assume it was just crossfire. + if ( m_hEnemy == NULL ) + { + if ( (m_afMemory & bits_MEMORY_SUSPICIOUS) || UTIL_IsFacing( pevAttacker, pev->origin ) ) + Remember( bits_MEMORY_PROVOKED ); + else + Remember( bits_MEMORY_SUSPICIOUS ); + } + } + } + + if ( pevAttacker->flags & (FL_MONSTER | FL_CLIENT) ) + {// only if the attack was a monster or client! + + // enemy's last known position is somewhere down the vector that the attack came from. + if (pevInflictor) + { + if (m_hEnemy == NULL || pevInflictor == m_hEnemy->pev || !HasConditions(bits_COND_SEE_ENEMY)) + { + m_vecEnemyLKP = pevInflictor->origin; + } + } + else + { + m_vecEnemyLKP = pev->origin + ( g_vecAttackDir * 64 ); + } + + SetIdealYawToTargetAndUpdate( m_vecEnemyLKP ); + + // add pain to the conditions + // !!!HACKHACK - fudged for now. Do we want to have a virtual function to determine what is light and + // heavy damage per monster class? + if ( flDamage > 0 ) + { + SetConditions(bits_COND_LIGHT_DAMAGE); + } + + if ( flDamage >= 20 ) + { + SetConditions(bits_COND_HEAVY_DAMAGE); + } + } + } + + return 1; +} + +//========================================================= +// DeadTakeDamage - takedamage function called when a monster's +// corpse is damaged. +//========================================================= +int CBaseMonster :: DeadTakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + Vector vecDir; + + // grab the vector of the incoming attack. ( pretend that the inflictor is a little lower than it really is, so the body will tend to fly upward a bit). + vecDir = Vector( 0, 0, 0 ); + if (!FNullEnt( pevInflictor )) + { + CBaseEntity *pInflictor = CBaseEntity :: Instance( pevInflictor ); + if (pInflictor) + { + vecDir = ( pInflictor->Center() - Vector ( 0, 0, 10 ) - Center() ).Normalize(); + vecDir = g_vecAttackDir = vecDir.Normalize(); + } + } + + pev->frame = RANDOM_FLOAT ( 220, 225 ); + //pev->velocity = pev->velocity + vecDir * -DamageForce( flDamage ); + ResetSequenceInfo( ); + +#if 0// turn this back on when the bounding box issues are resolved. + + pev->flags &= ~FL_ONGROUND; + pev->origin.z += 1; + + // let the damage scoot the corpse around a bit. + if ( !FNullEnt(pevInflictor) && (pevAttacker->solid != SOLID_TRIGGER) ) + { + pev->velocity = pev->velocity + vecDir * -DamageForce( flDamage ); + } + +#endif + // kill the corpse if enough damage was done to destroy the corpse and the damage is of a type that is allowed to destroy the corpse. + if ( bitsDamageType & DMG_GIB_CORPSE ) + { + if ( pev->health <= flDamage ) + { + pev->health = -50; + Killed( pevAttacker, GIB_ALWAYS ); + return 0; + } + // Accumulate corpse gibbing damage, so you can gib with multiple hits + pev->health -= flDamage * 0.1; + } + + return 1; +} + +float CBaseMonster :: DamageForce( float damage ) +{ + float force = damage * ((32 * 32 * 72.0) / (pev->size.x * pev->size.y * pev->size.z)) * 5; + + if ( force > 1000.0) + { + force = 1000.0; + } + + return force; +} + +// +// RadiusDamage - this entity is exploding, or otherwise needs to inflict damage upon entities within a certain range. +// +// only damage ents that can clearly be seen by the explosion! + +void RadiusDamage( Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, float flRadius, int iClassIgnore, int bitsDamageType ) +{ + CBaseEntity *pEntity = NULL; + TraceResult tr; + float flAdjustedDamage, falloff; + Vector vecSpot; + + if ( flRadius ) + falloff = flDamage / flRadius; + else + falloff = 1.0; + + int bInWater = (UTIL_PointContents ( vecSrc ) == CONTENTS_WATER); + + vecSrc.z += 1;// in case grenade is lying on the ground + + if ( !pevAttacker ) + pevAttacker = pevInflictor; + + // iterate on all entities in the vicinity. + while ((pEntity = UTIL_FindEntityInSphere( pEntity, vecSrc, flRadius )) != NULL) + { + if ( pEntity->pev->takedamage != DAMAGE_NO ) + { + // buz: skip grenade damage from player's grenades to invincible monsters + if ( pEntity->pev->spawnflags & SF_MONSTER_INVINCIBLE ) + { + CBaseEntity *pEnt = CBaseEntity::Instance( pevAttacker ); + if (pEnt->IsPlayer()) + continue; + } + + // UNDONE: this should check a damage mask, not an ignore + if ( iClassIgnore != CLASS_NONE && pEntity->Classify() == iClassIgnore ) + {// houndeyes don't hurt other houndeyes with their attack + continue; + } + + // blast's don't tavel into or out of water + if (bInWater && pEntity->pev->waterlevel == 0) + continue; + if (!bInWater && pEntity->pev->waterlevel == 3) + continue; + + vecSpot = pEntity->BodyTarget( vecSrc ); + + UTIL_TraceLine ( vecSrc, vecSpot, dont_ignore_monsters, ENT(pevInflictor), &tr ); + + if ( tr.flFraction == 1.0 || tr.pHit == pEntity->edict() ) + {// the explosion can 'see' this entity, so hurt them! + if (tr.fStartSolid) + { + // if we're stuck inside them, fixup the position and distance + tr.vecEndPos = vecSrc; + tr.flFraction = 0.0; + } + + // decrease damage for an ent that's farther from the bomb. + flAdjustedDamage = ( vecSrc - tr.vecEndPos ).Length() * falloff; + flAdjustedDamage = flDamage - flAdjustedDamage; + + if ( flAdjustedDamage < 0 ) + { + flAdjustedDamage = 0; + } + + // ALERT( at_console, "hit %s\n", STRING( pEntity->pev->classname ) ); + if (tr.flFraction != 1.0) + { + ClearMultiDamage( ); + pEntity->TraceAttack( pevInflictor, flAdjustedDamage, (tr.vecEndPos - vecSrc).Normalize( ), &tr, bitsDamageType ); + ApplyMultiDamage( pevInflictor, pevAttacker ); + } + else + { + pEntity->TakeDamage ( pevInflictor, pevAttacker, flAdjustedDamage, bitsDamageType ); + } + } + } + } +} + +void CBaseMonster :: RadiusDamage(entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) +{ + ::RadiusDamage( pev->origin, pevInflictor, pevAttacker, flDamage, flDamage * 2.5, iClassIgnore, bitsDamageType ); +} + + +void CBaseMonster :: RadiusDamage( Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) +{ + ::RadiusDamage( vecSrc, pevInflictor, pevAttacker, flDamage, flDamage * 2.5, iClassIgnore, bitsDamageType ); +} + +//========================================================= +// CheckTraceHullAttack - expects a length to trace, amount +// of damage to do, and damage type. Returns a pointer to +// the damaged entity in case the monster wishes to do +// other stuff to the victim (punchangle, etc) +// +// Used for many contact-range melee attacks. Bites, claws, etc. +//========================================================= +CBaseEntity* CBaseMonster :: CheckTraceHullAttack( float flDist, int iDamage, int iDmgType ) +{ + TraceResult tr; + + if (IsPlayer()) + UTIL_MakeVectors( pev->angles ); + else + UTIL_MakeAimVectors( pev->angles ); + + Vector vecStart = pev->origin; + vecStart.z += pev->size.z * 0.5; + Vector vecEnd = vecStart + (gpGlobals->v_forward * flDist ); + + UTIL_TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_hull, ENT(pev), &tr ); + + if ( tr.pHit ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + + if ( iDamage > 0 ) + { + pEntity->TakeDamage( pev, pev, iDamage, iDmgType ); + } + + return pEntity; + } + + return NULL; +} + + +//========================================================= +// FInViewCone - returns true is the passed ent is in +// the caller's forward view cone. The dot product is performed +// in 2d, making the view cone infinitely tall. +//========================================================= +BOOL CBaseMonster :: FInViewCone ( CBaseEntity *pEntity ) +{ + Vector2D vec2LOS; + float flDot; + + UTIL_MakeVectors ( pev->angles ); + + vec2LOS = ( pEntity->pev->origin - pev->origin ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + flDot = DotProduct (vec2LOS , gpGlobals->v_forward.Make2D() ); + + if ( flDot > m_flFieldOfView ) + { + return TRUE; + } + else + { + return FALSE; + } +} + +//========================================================= +// FInViewCone - returns true is the passed vector is in +// the caller's forward view cone. The dot product is performed +// in 2d, making the view cone infinitely tall. +//========================================================= +BOOL CBaseMonster :: FInViewCone ( Vector *pOrigin ) +{ + Vector2D vec2LOS; + float flDot; + + UTIL_MakeVectors ( pev->angles ); + + vec2LOS = ( *pOrigin - pev->origin ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + flDot = DotProduct (vec2LOS , gpGlobals->v_forward.Make2D() ); + + if ( flDot > m_flFieldOfView ) + { + return TRUE; + } + else + { + return FALSE; + } +} + +//========================================================= +// FVisible - returns true if a line can be traced from +// the caller's eyes to the target +//========================================================= +BOOL CBaseEntity :: FVisible ( CBaseEntity *pEntity ) +{ + TraceResult tr; + Vector vecLookerOrigin; + Vector vecTargetOrigin; + + if (FBitSet( pEntity->pev->flags, FL_NOTARGET )) + return FALSE; + + // don't look through water + if ((pev->waterlevel != 3 && pEntity->pev->waterlevel == 3) + || (pev->waterlevel == 3 && pEntity->pev->waterlevel == 0)) + return FALSE; + + vecLookerOrigin = pev->origin + pev->view_ofs;//look through the caller's 'eyes' + vecTargetOrigin = pEntity->EyePosition(); + + UTIL_TraceLine(vecLookerOrigin, vecTargetOrigin, ignore_monsters, ignore_glass, ENT(pev)/*pentIgnore*/, &tr); + + if (tr.flFraction != 1.0 && tr.pHit != ENT(pEntity->pev)) //LRC - added so that monsters can "see" some bsp objects + { + // ALERT(at_console, "can't see \"%s\"\n", STRING(pEntity->pev->classname)); + return FALSE;// Line of sight is not established + } + else + { + // ALERT(at_console, "Seen ok\n"); + return TRUE;// line of sight is valid. + } +} + +//========================================================= +// FVisible - returns true if a line can be traced from +// the caller's eyes to the target vector +//========================================================= +BOOL CBaseEntity :: FVisible ( const Vector &vecOrigin ) +{ + TraceResult tr; + Vector vecLookerOrigin; + + vecLookerOrigin = EyePosition();//look through the caller's 'eyes' + + UTIL_TraceLine(vecLookerOrigin, vecOrigin, ignore_monsters, ignore_glass, ENT(pev)/*pentIgnore*/, &tr); + + if (tr.flFraction != 1.0) + { + return FALSE;// Line of sight is not established + } + else + { + return TRUE;// line of sight is valid. + } +} + +/* +================ +TraceAttack +================ +*/ +void CBaseEntity::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + Vector vecOrigin = ptr->vecEndPos - vecDir * 4; + + if ( pev->takedamage ) + { + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + + int blood = BloodColor(); + + if ( blood != DONT_BLEED ) + { + SpawnBlood(vecOrigin, blood, flDamage);// a little surface blood. + TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); + } + } +} + + +/* +//========================================================= +// TraceAttack +//========================================================= +void CBaseMonster::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + Vector vecOrigin = ptr->vecEndPos - vecDir * 4; + + ALERT ( at_console, "%d\n", ptr->iHitgroup ); + + + if ( pev->takedamage ) + { + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + + int blood = BloodColor(); + + if ( blood != DONT_BLEED ) + { + SpawnBlood(vecOrigin, blood, flDamage);// a little surface blood. + } + } +} +*/ + +BOOL CBaseMonster :: AllowDecal( entvars_t *pevAttacker ) +{ + if ( pev->takedamage ) + { + if ( pev->spawnflags & SF_MONSTER_INVINCIBLE ) + { + // check for player + CBaseEntity *pEnt = CBaseEntity::Instance( pevAttacker ); + if (pEnt->IsPlayer()) + { + return FALSE; + } + + // check for owner (it may be grenade, thrown by player) + if (pevAttacker->owner) + { + pEnt = CBaseEntity::Instance( pevAttacker->owner ); + if (pEnt->IsPlayer()) + { + return FALSE; + } + } + } + + return TRUE; + } + + return FALSE; +} + +//========================================================= +// TraceAttack +//========================================================= +void CBaseMonster :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if ( pev->takedamage ) + { + if ( pev->spawnflags & SF_MONSTER_INVINCIBLE ) + { + // ALERT(at_console, "yes im incvincible\n"); + // check for player + CBaseEntity *pEnt = CBaseEntity::Instance( pevAttacker ); + if (pEnt->IsPlayer()) + { + // ALERT(at_console, "ent is player\n"); + return; + } + + // check for owner (it may be grenade, thrown by player) + if (pevAttacker->owner) + { + pEnt = CBaseEntity::Instance( pevAttacker->owner ); + if (pEnt->IsPlayer()) + { + // ALERT(at_console, "owner is player\n"); + return; + } + } + } + + m_LastHitGroup = ptr->iHitgroup; + TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); + + TraceResult btr; // Wargon: Ïåðåìåííàÿ äëÿ òðåéñà äåêàëè ìîçãîâ îò õåäøîòîâ. + + switch ( ptr->iHitgroup ) + { + case HITGROUP_GENERIC: + break; + case HITGROUP_HEAD: + // Wargon: Äåêàëü ìîçãîâ îò õåäøîòîâ. + UTIL_TraceLine( ptr->vecEndPos, ptr->vecEndPos + vecDir * 172, ignore_monsters, ENT(pev), &btr ); + UTIL_TraceCustomDecal( &btr, "brains", RANDOM_FLOAT( 0.0f, 360.0f )); + + // buz: more blood in the head!!! let the brains fly out and smash on ground, muahahaha!! + SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage * 4); + flDamage *= gSkillData.monHead; + break; + case HITGROUP_CHEST: + flDamage *= gSkillData.monChest; + break; + case HITGROUP_STOMACH: + flDamage *= gSkillData.monStomach; + break; + case HITGROUP_LEFTARM: + case HITGROUP_RIGHTARM: + flDamage *= gSkillData.monArm; + break; + case HITGROUP_LEFTLEG: + case HITGROUP_RIGHTLEG: + flDamage *= gSkillData.monLeg; + break; + default: + break; + } + + SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage * 2); // Wargon: Ïîáîëüøå êðîâè. + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + } +} + +/* +================ +FireBullets + +Go to the trouble of combining multiple pellets into a single damage call. + +This version is used by Monsters. +================ +*/ +void CBaseEntity :: FireBullets( ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, float flDamage, entvars_t *pevAttacker ) +{ + Vector vecRight = gpGlobals->v_right; + Vector vecUp = gpGlobals->v_up; + TraceResult tr; + + if ( pevAttacker == NULL ) + pevAttacker = pev; // the default attacker is ourselves + + ClearMultiDamage(); + gMultiDamage.type = DMG_BULLET | DMG_NEVERGIB; + + for( ULONG iShot = 1; iShot <= cShots; iShot++ ) + { + // get circular gaussian spread + float x, y, z; + do { + x = RANDOM_FLOAT( -0.5f, 0.5f ) + RANDOM_FLOAT( -0.5f, 0.5f ); + y = RANDOM_FLOAT( -0.5f, 0.5f ) + RANDOM_FLOAT( -0.5f, 0.5f ); + z = x * x + y * y; + } while( z > 1.0f ); + + Vector vecDir = vecDirShooting + x * vecSpread.x * vecRight + y * vecSpread.y * vecUp; + Vector vecEnd; + + vecEnd = vecSrc + vecDir * flDistance; + UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, ENT( pev ), &tr ); + + // do damage, paint decals + if ( tr.flFraction != 1.0f ) + { + CBaseEntity *pEntity = CBaseEntity :: Instance( tr.pHit ); + + switch( iBulletType ) + { + case BULLET_NORMAL: + case BULLET_BUCKSHOT: + pEntity->TraceAttack( pevAttacker, flDamage, vecDir, &tr, DMG_BULLET|DMG_NEVERGIB ); + TEXTURETYPE_PlaySound( &tr, vecSrc, vecEnd, iBulletType ); + if( pEntity->AllowDecal( pevAttacker )) + DecalGunshot( &tr, iBulletType, vecSrc ); + break; + } + } + + // make bullet trails + UTIL_BubbleTrail( vecSrc, tr.vecEndPos, ( flDistance * tr.flFraction ) / 64.0f ); + } + + ApplyMultiDamage( pev, pevAttacker ); +} + +//Water splash +extern int gmsgWaterSplash; + +void CBaseEntity::FireBulletsWater( Vector vecSrc, Vector vecEnd, float ScaleSplash1, float ScaleSplash2 ) +{ + if( !( POINT_CONTENTS( vecEnd ) == CONTENTS_WATER && POINT_CONTENTS( vecSrc ) != CONTENTS_WATER ) ) + return; + + float x = vecEnd.x - vecSrc.x; + float y = vecEnd.y - vecSrc.y; + float z = vecEnd.z - vecSrc.z; + float len = Vector( vecEnd - vecSrc).Length(); + + Vector vecTemp = ( vecEnd + vecSrc ) / 2; + while( len >= 1 ) + { + if( POINT_CONTENTS( vecTemp ) == CONTENTS_WATER ) + vecEnd = vecTemp; + else + vecSrc = vecTemp; + vecTemp = ( vecEnd + vecSrc ) / 2; + len = Vector( vecEnd - vecSrc).Length(); + } + + if( len <= 1 ) + { + MESSAGE_BEGIN(MSG_ALL, gmsgWaterSplash); + WRITE_COORD( vecTemp.x ); + WRITE_COORD( vecTemp.y ); + WRITE_COORD( vecTemp.z ); + WRITE_COORD( ScaleSplash1 ); + WRITE_COORD( ScaleSplash2 ); + MESSAGE_END(); + + switch( RANDOM_LONG( 1, 3 ) ) + { + case 1: + UTIL_EmitAmbientSound( ENT(0), vecTemp, "player/water_splash1.wav", 1, ATTN_NORM, 0, 100 ); + break; + case 2: + UTIL_EmitAmbientSound( ENT(0), vecTemp, "player/water_splash2.wav", 1, ATTN_NORM, 0, 100 ); + break; + case 3: + UTIL_EmitAmbientSound( ENT(0), vecTemp, "player/water_splash3.wav", 1, ATTN_NORM, 0, 100 ); + break; + } + + } + else + { + if( POINT_CONTENTS( vecTemp ) == CONTENTS_WATER ) + FireBulletsWater( vecSrc, vecTemp, ScaleSplash1, ScaleSplash2 ); + else + FireBulletsWater( vecTemp, vecEnd, ScaleSplash1, ScaleSplash2 ); + } +} + +/* +================ +FireBullets + +Go to the trouble of combining multiple pellets into a single damage call. + +This version is used by Players, uses the random seed generator to sync client and server side shots. +================ +*/ +Vector CBaseEntity :: FireBulletsPlayer( ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, float flDamage, entvars_t *pevAttacker, int shared_rand ) +{ + TraceResult tr; + Vector vecRight = gpGlobals->v_right; + Vector vecUp = gpGlobals->v_up; + float x, y, z; + + if ( pevAttacker == NULL ) + pevAttacker = pev; // the default attacker is ourselves + + ClearMultiDamage(); + gMultiDamage.type = DMG_BULLET | DMG_NEVERGIB; + + for ( ULONG iShot = 1; iShot <= cShots; iShot++ ) + { + //Use player's random seed. + // get circular gaussian spread + x = UTIL_SharedRandomFloat( shared_rand + iShot, -0.5, 0.5 ) + UTIL_SharedRandomFloat( shared_rand + ( 1 + iShot ) , -0.5, 0.5 ); + y = UTIL_SharedRandomFloat( shared_rand + ( 2 + iShot ), -0.5, 0.5 ) + UTIL_SharedRandomFloat( shared_rand + ( 3 + iShot ), -0.5, 0.5 ); + z = x * x + y * y; + + Vector vecDir = vecDirShooting + x * vecSpread.x * vecRight + y * vecSpread.y * vecUp; + Vector vecEnd; + + vecEnd = vecSrc + vecDir * flDistance; + UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, ENT(pev), &tr ); + + // do damage, paint decals + if( tr.flFraction != 1.0 ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + pEntity->TraceAttack( pevAttacker, flDamage, vecDir, &tr, DMG_BULLET ); + if( pEntity->AllowDecal( pevAttacker )) + DecalGunshot( &tr, BULLET_NORMAL, vecSrc, true ); + } + + // make bullet trails + UTIL_BubbleTrail( vecSrc, tr.vecEndPos, (flDistance * tr.flFraction) / 64.0 ); + FireBulletsWater( vecSrc, tr.vecEndPos, 0.15f, 0.15f ); + } + + ApplyMultiDamage( pev, pevAttacker ); + + return Vector( x * vecSpread.x, y * vecSpread.y, 0.0f ); +} + +void CBaseEntity :: TraceBleed( float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + if (BloodColor() == DONT_BLEED) + return; + + if (flDamage == 0) + return; + + if (! (bitsDamageType & (DMG_CRUSH | DMG_BULLET | DMG_SLASH | DMG_BLAST | DMG_CLUB | DMG_MORTAR))) + return; + + // make blood decal on the wall! + TraceResult Bloodtr; + Vector vecTraceDir; + float flNoise; + int cCount; + int i; + +/* + if ( !IsAlive() ) + { + // dealing with a dead monster. + if ( pev->max_health <= 0 ) + { + // no blood decal for a monster that has already decalled its limit. + return; + } + else + { + pev->max_health--; + } + } +*/ + + if (flDamage < 10) + { + flNoise = 0.1; + cCount = 1; + } + else if (flDamage < 25) + { + flNoise = 0.2; + cCount = 2; + } + else + { + flNoise = 0.3; + cCount = 4; + } + + for ( i = 0 ; i < cCount ; i++ ) + { + vecTraceDir = vecDir * -1;// trace in the opposite direction the shot came from (the direction the shot is going) + + vecTraceDir.x += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.y += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.z += RANDOM_FLOAT( -flNoise, flNoise ); + + UTIL_TraceLine( ptr->vecEndPos, ptr->vecEndPos + vecTraceDir * -172, ignore_monsters, ENT(pev), &Bloodtr); + + if ( Bloodtr.flFraction != 1.0 ) + { + UTIL_BloodDecalTrace( &Bloodtr, BloodColor() ); + } + } +} + +//========================================================= +//========================================================= +void CBaseMonster :: MakeDamageBloodDecal ( int cCount, float flNoise, TraceResult *ptr, const Vector &vecDir ) +{ + // make blood decal on the wall! + TraceResult Bloodtr; + Vector vecTraceDir; + int i; + + if ( !IsAlive() ) + { + // dealing with a dead monster. + if ( pev->max_health <= 0 ) + { + // no blood decal for a monster that has already decalled its limit. + return; + } + else + { + pev->max_health--; + } + } + + for ( i = 0 ; i < cCount ; i++ ) + { + vecTraceDir = vecDir; + + vecTraceDir.x += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.y += RANDOM_FLOAT( -flNoise, flNoise ); + vecTraceDir.z += RANDOM_FLOAT( -flNoise, flNoise ); + + UTIL_TraceLine( ptr->vecEndPos, ptr->vecEndPos + vecTraceDir * 172, ignore_monsters, ENT(pev), &Bloodtr); + +/* + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + WRITE_COORD( ptr->vecEndPos.x ); + WRITE_COORD( ptr->vecEndPos.y ); + WRITE_COORD( ptr->vecEndPos.z ); + + WRITE_COORD( Bloodtr.vecEndPos.x ); + WRITE_COORD( Bloodtr.vecEndPos.y ); + WRITE_COORD( Bloodtr.vecEndPos.z ); + MESSAGE_END(); +*/ + + if ( Bloodtr.flFraction != 1.0 ) + { + UTIL_BloodDecalTrace( &Bloodtr, BloodColor() ); + } + } +} diff --git a/dlls/controller.cpp b/dlls/controller.cpp new file mode 100644 index 0000000..a2c10a3 --- /dev/null +++ b/dlls/controller.cpp @@ -0,0 +1,1461 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +//========================================================= +// CONTROLLER +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "effects.h" +#include "schedule.h" +#include "weapons.h" +#include "squadmonster.h" +#include "scripted.h" + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define CONTROLLER_AE_HEAD_OPEN 1 +#define CONTROLLER_AE_BALL_SHOOT 2 +#define CONTROLLER_AE_SMALL_SHOOT 3 +#define CONTROLLER_AE_POWERUP_FULL 4 +#define CONTROLLER_AE_POWERUP_HALF 5 + +#define CONTROLLER_FLINCH_DELAY 2 // at most one flinch every n secs + +class CController : public CSquadMonster +{ +public: + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + float MaxYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + + void RunAI( void ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); // balls + BOOL CheckRangeAttack2 ( float flDot, float flDist ); // head + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); // block, throw + Schedule_t* GetSchedule ( void ); + Schedule_t* GetScheduleOfType ( int Type ); + void StartTask ( Task_t *pTask ); + void RunTask ( Task_t *pTask ); + CUSTOM_SCHEDULES; + + void Stop( void ); + void Move ( float flInterval ); + int CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ); + void MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ); + void SetActivity ( Activity NewActivity ); + BOOL ShouldAdvanceRoute( float flWaypointDist ); + int LookupFloat( ); + + float m_flNextFlinch; + + float m_flShootTime; + float m_flShootEnd; + + void PainSound( void ); + void AlertSound( void ); + void IdleSound( void ); + void AttackSound( void ); + void DeathSound( void ); + + static const char *pAttackSounds[]; + static const char *pIdleSounds[]; + static const char *pAlertSounds[]; + static const char *pPainSounds[]; + static const char *pDeathSounds[]; + + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + void Killed( entvars_t *pevAttacker, int iGib ); + void GibMonster( void ); + + CSprite *m_pBall[2]; // hand balls + int m_iBall[2]; // how bright it should be + float m_iBallTime[2]; // when it should be that color + int m_iBallCurrent[2]; // current brightness + + Vector m_vecEstVelocity; + + Vector m_velocity; + int m_fInCombat; +}; + +LINK_ENTITY_TO_CLASS( monster_alien_controller, CController ); + +TYPEDESCRIPTION CController::m_SaveData[] = +{ + DEFINE_ARRAY( CController, m_pBall, FIELD_CLASSPTR, 2 ), + DEFINE_ARRAY( CController, m_iBall, FIELD_INTEGER, 2 ), + DEFINE_ARRAY( CController, m_iBallTime, FIELD_TIME, 2 ), + DEFINE_ARRAY( CController, m_iBallCurrent, FIELD_INTEGER, 2 ), + DEFINE_FIELD( CController, m_vecEstVelocity, FIELD_VECTOR ), +}; +IMPLEMENT_SAVERESTORE( CController, CSquadMonster ); + + +const char *CController::pAttackSounds[] = +{ + "controller/con_attack1.wav", + "controller/con_attack2.wav", + "controller/con_attack3.wav", +}; + +const char *CController::pIdleSounds[] = +{ + "controller/con_idle1.wav", + "controller/con_idle2.wav", + "controller/con_idle3.wav", + "controller/con_idle4.wav", + "controller/con_idle5.wav", +}; + +const char *CController::pAlertSounds[] = +{ + "controller/con_alert1.wav", + "controller/con_alert2.wav", + "controller/con_alert3.wav", +}; + +const char *CController::pPainSounds[] = +{ + "controller/con_pain1.wav", + "controller/con_pain2.wav", + "controller/con_pain3.wav", +}; + +const char *CController::pDeathSounds[] = +{ + "controller/con_die1.wav", + "controller/con_die2.wav", +}; + + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CController :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MILITARY; +} + +//========================================================= +// MaxYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +float CController :: MaxYawSpeed( void ) +{ + float ys; + + ys = 120; + +#if 0 + switch ( m_Activity ) + { + } +#endif + + return ys; +} + +int CController :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // HACK HACK -- until we fix this. + if ( IsAlive() ) + PainSound(); + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + + +void CController::Killed( entvars_t *pevAttacker, int iGib ) +{ + // shut off balls + /* + m_iBall[0] = 0; + m_iBallTime[0] = gpGlobals->time + 4.0; + m_iBall[1] = 0; + m_iBallTime[1] = gpGlobals->time + 4.0; + */ + + // fade balls + if (m_pBall[0]) + { + m_pBall[0]->SUB_StartFadeOut(); + m_pBall[0] = NULL; + } + if (m_pBall[1]) + { + m_pBall[1]->SUB_StartFadeOut(); + m_pBall[1] = NULL; + } + + CSquadMonster::Killed( pevAttacker, iGib ); +} + + +void CController::GibMonster( void ) +{ + // delete balls + if (m_pBall[0]) + { + UTIL_Remove( m_pBall[0] ); + m_pBall[0] = NULL; + } + if (m_pBall[1]) + { + UTIL_Remove( m_pBall[1] ); + m_pBall[1] = NULL; + } + CSquadMonster::GibMonster( ); +} + + + + +void CController :: PainSound( void ) +{ + if (RANDOM_LONG(0,5) < 2) + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pPainSounds ); +} + +void CController :: AlertSound( void ) +{ + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pAlertSounds ); +} + +void CController :: IdleSound( void ) +{ + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pIdleSounds ); +} + +void CController :: AttackSound( void ) +{ + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pAttackSounds ); +} + +void CController :: DeathSound( void ) +{ + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pDeathSounds ); +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CController :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case CONTROLLER_AE_HEAD_OPEN: + { + //ALERT(at_console,"Controller Head Open\n"); + Vector vecStart, angleGun; + + GetAttachment( 0, vecStart, angleGun ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x1000 ); // entity, attachment + WRITE_COORD( vecStart.x ); // origin + WRITE_COORD( vecStart.y ); + WRITE_COORD( vecStart.z ); + WRITE_COORD( 1 ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 192 ); // G + WRITE_BYTE( 64 ); // B + WRITE_BYTE( 20 ); // life * 10 + WRITE_COORD( -32 ); // decay + MESSAGE_END(); + + m_iBall[0] = 192; + m_iBallTime[0] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + m_iBall[1] = 255; + m_iBallTime[1] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + + } + break; + + case CONTROLLER_AE_BALL_SHOOT: + { + //ALERT(at_console,"Controller Ball Shoot\n"); + Vector vecStart, angleGun; + + GetAttachment( 0, vecStart, angleGun ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x1000 ); // entity, attachment + WRITE_COORD( 0 ); // origin + WRITE_COORD( 0 ); + WRITE_COORD( 0 ); + WRITE_COORD( 32 ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 192 ); // G + WRITE_BYTE( 64 ); // B + WRITE_BYTE( 10 ); // life * 10 + WRITE_COORD( 32 ); // decay + MESSAGE_END(); + + CBaseMonster *pBall = (CBaseMonster*)Create( "controller_head_ball", vecStart, pev->angles, edict() ); + + pBall->pev->velocity = Vector( 0, 0, 32 ); + if (m_pCine) + { + pBall->m_hEnemy = m_hTargetEnt; + } + else + { + pBall->m_hEnemy = m_hEnemy; + } + + m_iBall[0] = 0; + m_iBall[1] = 0; + } + break; + + case CONTROLLER_AE_SMALL_SHOOT: + { + //ALERT(at_console,"Controller Small Shoot\n"); + AttackSound( ); + m_flShootTime = gpGlobals->time; + m_flShootEnd = m_flShootTime + atoi( pEvent->options ) / 15.0; + } + break; + case CONTROLLER_AE_POWERUP_FULL: + { + //ALERT(at_console,"Controller Powerup Full\n"); + m_iBall[0] = 255; + m_iBallTime[0] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + m_iBall[1] = 255; + m_iBallTime[1] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + } + break; + case CONTROLLER_AE_POWERUP_HALF: + { + //ALERT(at_console,"Controller Powerup Half\n"); + m_iBall[0] = 192; + m_iBallTime[0] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + m_iBall[1] = 192; + m_iBallTime[1] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + } + break; + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CController :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/controller.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, 0 ), Vector( 32, 32, 64 )); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_FLY; + pev->flags |= FL_FLY; + m_bloodColor = BLOOD_COLOR_GREEN; + if (pev->health == 0) + pev->health = gSkillData.controllerHealth; + pev->view_ofs = Vector( 0, 0, -2 );// position of the eyes relative to monster's origin. + m_flFieldOfView = VIEW_FIELD_FULL;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CController :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/controller.mdl"); + + PRECACHE_SOUND_ARRAY( pAttackSounds ); + PRECACHE_SOUND_ARRAY( pIdleSounds ); + PRECACHE_SOUND_ARRAY( pAlertSounds ); + PRECACHE_SOUND_ARRAY( pPainSounds ); + PRECACHE_SOUND_ARRAY( pDeathSounds ); + + PRECACHE_MODEL( "sprites/xspark4.spr"); + + UTIL_PrecacheOther( "controller_energy_ball" ); + UTIL_PrecacheOther( "controller_head_ball" ); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + + +// Chase enemy schedule +Task_t tlControllerChaseEnemy[] = +{ + { TASK_GET_PATH_TO_ENEMY, (float)128 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + +}; + +Schedule_t slControllerChaseEnemy[] = +{ + { + tlControllerChaseEnemy, + ARRAYSIZE ( tlControllerChaseEnemy ), + bits_COND_NEW_ENEMY | + bits_COND_TASK_FAILED, + 0, + "ControllerChaseEnemy" + }, +}; + + + +Task_t tlControllerStrafe[] = +{ + { TASK_WAIT, (float)0.2 }, + { TASK_GET_PATH_TO_ENEMY, (float)128 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slControllerStrafe[] = +{ + { + tlControllerStrafe, + ARRAYSIZE ( tlControllerStrafe ), + bits_COND_NEW_ENEMY, + 0, + "ControllerStrafe" + }, +}; + + +Task_t tlControllerTakeCover[] = +{ + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slControllerTakeCover[] = +{ + { + tlControllerTakeCover, + ARRAYSIZE ( tlControllerTakeCover ), + bits_COND_NEW_ENEMY, + 0, + "ControllerTakeCover" + }, +}; + + +Task_t tlControllerFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slControllerFail[] = +{ + { + tlControllerFail, + ARRAYSIZE ( tlControllerFail ), + 0, + 0, + "ControllerFail" + }, +}; + + + +DEFINE_CUSTOM_SCHEDULES( CController ) +{ + slControllerChaseEnemy, + slControllerStrafe, + slControllerTakeCover, + slControllerFail, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CController, CSquadMonster ); + + + +//========================================================= +// StartTask +//========================================================= +void CController :: StartTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_RANGE_ATTACK1: + CSquadMonster :: StartTask ( pTask ); + break; + case TASK_GET_PATH_TO_ENEMY_LKP: + { + if (BuildNearestRoute( m_vecEnemyLKP, pev->view_ofs, pTask->flData, (m_vecEnemyLKP - pev->origin).Length() + 1024 )) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToEnemyLKP failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_ENEMY: + { + CBaseEntity *pEnemy = m_hEnemy; + + if ( pEnemy == NULL ) + { + TaskFail(); + return; + } + + if (BuildNearestRoute( pEnemy->pev->origin, pEnemy->pev->view_ofs, pTask->flData, (pEnemy->pev->origin - pev->origin).Length() + 1024 )) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToEnemy failed!!\n" ); + TaskFail(); + } + break; + } + default: + CSquadMonster :: StartTask ( pTask ); + break; + } +} + + +Vector Intersect( Vector vecSrc, Vector vecDst, Vector vecMove, float flSpeed ) +{ + Vector vecTo = vecDst - vecSrc; + + float a = DotProduct( vecMove, vecMove ) - flSpeed * flSpeed; + float b = 0 * DotProduct(vecTo, vecMove); // why does this work? + float c = DotProduct( vecTo, vecTo ); + + float t; + if (a == 0) + { + t = c / (flSpeed * flSpeed); + } + else + { + t = b * b - 4 * a * c; + t = sqrt( t ) / (2.0 * a); + float t1 = -b +t; + float t2 = -b -t; + + if (t1 < 0 || t2 < t1) + t = t2; + else + t = t1; + } + + // ALERT( at_console, "Intersect %f\n", t ); + + if (t < 0.1) + t = 0.1; + if (t > 10.0) + t = 10.0; + + Vector vecHit = vecTo + vecMove * t; + return vecHit.Normalize( ) * flSpeed; +} + + +int CController::LookupFloat( ) +{ + if (m_velocity.Length( ) < 32.0) + { + return LookupSequence( "up" ); + } + + UTIL_MakeAimVectors( pev->angles ); + float x = DotProduct( gpGlobals->v_forward, m_velocity ); + float y = DotProduct( gpGlobals->v_right, m_velocity ); + float z = DotProduct( gpGlobals->v_up, m_velocity ); + + if (fabs(x) > fabs(y) && fabs(x) > fabs(z)) + { + if (x > 0) + return LookupSequence( "forward"); + else + return LookupSequence( "backward"); + } + else if (fabs(y) > fabs(z)) + { + if (y > 0) + return LookupSequence( "right"); + else + return LookupSequence( "left"); + } + else + { + if (z > 0) + return LookupSequence( "up"); + else + return LookupSequence( "down"); + } +} + + +//========================================================= +// RunTask +//========================================================= +void CController :: RunTask ( Task_t *pTask ) +{ + + if (m_flShootEnd > gpGlobals->time) + { + Vector vecHand, vecAngle; + + GetAttachment( 2, vecHand, vecAngle ); + + while (m_flShootTime < m_flShootEnd && m_flShootTime < gpGlobals->time) + { + Vector vecSrc = vecHand + pev->velocity * (m_flShootTime - gpGlobals->time); + Vector vecDir; + + if (m_pCine != NULL || m_hEnemy != NULL) + { + if (m_pCine != NULL) // LRC- is this a script that's telling it to fire? + { + if (m_hTargetEnt != NULL && m_pCine->PreciseAttack()) + { + vecDir = (m_hTargetEnt->pev->origin - pev->origin).Normalize() * gSkillData.controllerSpeedBall; + } + else + { + UTIL_MakeVectors(pev->angles); + vecDir = gpGlobals->v_forward * gSkillData.controllerSpeedBall; + } + } + else if (m_hEnemy != NULL) + { + if (HasConditions( bits_COND_SEE_ENEMY )) + { + m_vecEstVelocity = m_vecEstVelocity * 0.5 + m_hEnemy->pev->velocity * 0.5; + } + else + { + m_vecEstVelocity = m_vecEstVelocity * 0.8; + } + vecDir = Intersect( vecSrc, m_hEnemy->BodyTarget( pev->origin ), m_vecEstVelocity, gSkillData.controllerSpeedBall ); + } + + float delta = 0.03490; // +-2 degree + vecDir = vecDir + Vector( RANDOM_FLOAT( -delta, delta ), RANDOM_FLOAT( -delta, delta ), RANDOM_FLOAT( -delta, delta ) ) * gSkillData.controllerSpeedBall; + + vecSrc = vecSrc + vecDir * (gpGlobals->time - m_flShootTime); + CBaseMonster *pBall = (CBaseMonster*)Create( "controller_energy_ball", vecSrc, pev->angles, edict() ); + pBall->pev->velocity = vecDir; + } + m_flShootTime += 0.2; + } + + if (m_flShootTime > m_flShootEnd) + { + m_iBall[0] = 64; + m_iBallTime[0] = m_flShootEnd; + m_iBall[1] = 64; + m_iBallTime[1] = m_flShootEnd; + m_fInCombat = FALSE; + } + } + + switch ( pTask->iTask ) + { + case TASK_WAIT_FOR_MOVEMENT: + case TASK_WAIT: + case TASK_WAIT_FACE_ENEMY: + case TASK_WAIT_PVS: + SetIdealYawToTargetAndUpdate( m_vecEnemyLKP , AI_KEEP_YAW_SPEED ); + + if (m_fSequenceFinished) + { + m_fInCombat = FALSE; + } + + CSquadMonster :: RunTask ( pTask ); + + if (!m_fInCombat) + { + if (HasConditions ( bits_COND_CAN_RANGE_ATTACK1 )) + { + pev->sequence = LookupActivity( ACT_RANGE_ATTACK1 ); + pev->frame = 0; + ResetSequenceInfo( ); + m_fInCombat = TRUE; + } + else if (HasConditions ( bits_COND_CAN_RANGE_ATTACK2 )) + { + pev->sequence = LookupActivity( ACT_RANGE_ATTACK2 ); + pev->frame = 0; + ResetSequenceInfo( ); + m_fInCombat = TRUE; + } + else + { + int iFloat = LookupFloat( ); + if (m_fSequenceFinished || iFloat != pev->sequence) + { + pev->sequence = iFloat; + pev->frame = 0; + ResetSequenceInfo( ); + } + } + } + break; + default: + CSquadMonster :: RunTask ( pTask ); + break; + } +} + + +//========================================================= +// GetSchedule - Decides which type of schedule best suits +// the monster's current state and conditions. Then calls +// monster's member function to get a pointer to a schedule +// of the proper type. +//========================================================= +Schedule_t *CController :: GetSchedule ( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_IDLE: + break; + + case MONSTERSTATE_ALERT: + break; + + case MONSTERSTATE_COMBAT: + { + Vector vecTmp = Intersect( Vector( 0, 0, 0 ), Vector( 100, 4, 7 ), Vector( 2, 10, -3 ), 20.0 ); + + // dead enemy + if ( HasConditions ( bits_COND_LIGHT_DAMAGE ) ) + { + // m_iFrustration++; + } + if ( HasConditions ( bits_COND_HEAVY_DAMAGE ) ) + { + // m_iFrustration++; + } + } + break; + } + + return CSquadMonster :: GetSchedule(); +} + + + +//========================================================= +//========================================================= +Schedule_t* CController :: GetScheduleOfType ( int Type ) +{ + // ALERT( at_console, "%d\n", m_iFrustration ); + switch ( Type ) + { + case SCHED_CHASE_ENEMY: + return slControllerChaseEnemy; + case SCHED_RANGE_ATTACK1: + return slControllerStrafe; + case SCHED_RANGE_ATTACK2: + case SCHED_MELEE_ATTACK1: + case SCHED_MELEE_ATTACK2: + case SCHED_TAKE_COVER_FROM_ENEMY: + return slControllerTakeCover; + case SCHED_FAIL: + return slControllerFail; + } + + return CBaseMonster :: GetScheduleOfType( Type ); +} + + + + + +//========================================================= +// CheckRangeAttack1 - shoot a bigass energy ball out of their head +// +//========================================================= +BOOL CController :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( flDot > 0.5 && flDist > 256 && flDist <= 2048 ) + { + return TRUE; + } + return FALSE; +} + + +BOOL CController :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + if ( flDot > 0.5 && flDist > 64 && flDist <= 2048 ) + { + return TRUE; + } + return FALSE; +} + + +BOOL CController :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + return FALSE; +} + + +void CController :: SetActivity ( Activity NewActivity ) +{ + CBaseMonster::SetActivity( NewActivity ); + + switch ( m_Activity) + { + case ACT_WALK: + m_flGroundSpeed = 100; + break; + default: + m_flGroundSpeed = 100; + break; + } +} + + + +//========================================================= +// RunAI +//========================================================= +void CController :: RunAI( void ) +{ + CBaseMonster :: RunAI(); + Vector vecStart, angleGun; + + if ( HasMemory( bits_MEMORY_KILLED ) ) + return; + + for (int i = 0; i < 2; i++) + { + if (m_pBall[i] == NULL) + { + m_pBall[i] = CSprite::SpriteCreate( "sprites/xspark4.spr", pev->origin, TRUE ); + m_pBall[i]->SetTransparency( kRenderGlow, 255, 255, 255, 255, kRenderFxNoDissipation ); + m_pBall[i]->SetAttachment( edict(), (i + 3) ); + m_pBall[i]->SetScale( 1.0 ); + } + + float t = m_iBallTime[i] - gpGlobals->time; + if (t > 0.1) + t = 0.1 / t; + else + t = 1.0; + + m_iBallCurrent[i] += (m_iBall[i] - m_iBallCurrent[i]) * t; + + m_pBall[i]->SetBrightness( m_iBallCurrent[i] ); + + GetAttachment( i + 2, vecStart, angleGun ); + UTIL_SetOrigin( m_pBall[i], vecStart ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x1000 * (i + 3) ); // entity, attachment + WRITE_COORD( vecStart.x ); // origin + WRITE_COORD( vecStart.y ); + WRITE_COORD( vecStart.z ); + WRITE_COORD( m_iBallCurrent[i] / 8 ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 192 ); // G + WRITE_BYTE( 64 ); // B + WRITE_BYTE( 5 ); // life * 10 + WRITE_COORD( 0 ); // decay + MESSAGE_END(); + } +} + + +extern void DrawRoute( entvars_t *pev, WayPoint_t *m_Route, int m_iRouteIndex, int r, int g, int b ); + +void CController::Stop( void ) +{ + m_IdealActivity = GetStoppedActivity(); +} + + +#define DIST_TO_CHECK 200 +void CController :: Move ( float flInterval ) +{ + float flWaypointDist; + float flCheckDist; + float flDist;// how far the lookahead check got before hitting an object. + float flMoveDist; + Vector vecDir; + Vector vecApex; + CBaseEntity *pTargetEnt; + + // Don't move if no valid route + if ( FRouteClear() ) + { + ALERT( at_aiconsole, "Tried to move with no route!\n" ); + TaskFail(); + return; + } + + if ( m_flMoveWaitFinished > gpGlobals->time ) + return; + +// Debug, test movement code +#if 0 +// if ( CVAR_GET_FLOAT("stopmove" ) != 0 ) + { + if ( m_movementGoal == MOVEGOAL_ENEMY ) + RouteSimplify( m_hEnemy ); + else + RouteSimplify( m_hTargetEnt ); + FRefreshRoute(); + return; + } +#else +// Debug, draw the route +// DrawRoute( pev, m_Route, m_iRouteIndex, 0, 0, 255 ); +#endif + + // if the monster is moving directly towards an entity (enemy for instance), we'll set this pointer + // to that entity for the CheckLocalMove and Triangulate functions. + pTargetEnt = NULL; + + if (m_flGroundSpeed == 0) + { + m_flGroundSpeed = 100; + // TaskFail( ); + // return; + } + + flMoveDist = m_flGroundSpeed * flInterval; + + do + { + // local move to waypoint. + vecDir = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Normalize(); + flWaypointDist = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Length(); + + // SetIdealYawToTargetAndUpdate( m_Route[ m_iRouteIndex ].vecLocation, AI_KEEP_YAW_SPEED ); + + // if the waypoint is closer than CheckDist, CheckDist is the dist to waypoint + if ( flWaypointDist < DIST_TO_CHECK ) + { + flCheckDist = flWaypointDist; + } + else + { + flCheckDist = DIST_TO_CHECK; + } + + if ( (m_Route[ m_iRouteIndex ].iType & (~bits_MF_NOT_TO_MASK)) == bits_MF_TO_ENEMY ) + { + // only on a PURE move to enemy ( i.e., ONLY MF_TO_ENEMY set, not MF_TO_ENEMY and DETOUR ) + pTargetEnt = m_hEnemy; + } + else if ( (m_Route[ m_iRouteIndex ].iType & ~bits_MF_NOT_TO_MASK) == bits_MF_TO_TARGETENT ) + { + pTargetEnt = m_hTargetEnt; + } + + // !!!BUGBUG - CheckDist should be derived from ground speed. + // If this fails, it should be because of some dynamic entity blocking this guy. + // We've already checked this path, so we should wait and time out if the entity doesn't move + flDist = 0; + if ( CheckLocalMove ( pev->origin, pev->origin + vecDir * flCheckDist, pTargetEnt, &flDist ) != LOCALMOVE_VALID ) + { + CBaseEntity *pBlocker; + + // Can't move, stop + Stop(); + // Blocking entity is in global trace_ent + pBlocker = CBaseEntity::Instance( gpGlobals->trace_ent ); + if (pBlocker) + { + DispatchBlocked( edict(), pBlocker->edict() ); + } + if ( pBlocker && m_moveWaitTime > 0 && pBlocker->IsMoving() && !pBlocker->IsPlayer() && (gpGlobals->time-m_flMoveWaitFinished) > 3.0 ) + { + // Can we still move toward our target? + if ( flDist < m_flGroundSpeed ) + { + // Wait for a second + m_flMoveWaitFinished = gpGlobals->time + m_moveWaitTime; + // ALERT( at_aiconsole, "Move %s!!!\n", STRING( pBlocker->pev->classname ) ); + return; + } + } + else + { + // try to triangulate around whatever is in the way. + if ( FTriangulate( pev->origin, m_Route[ m_iRouteIndex ].vecLocation, flDist, pTargetEnt, &vecApex ) ) + { + InsertWaypoint( vecApex, bits_MF_TO_DETOUR ); + RouteSimplify( pTargetEnt ); + } + else + { + ALERT ( at_aiconsole, "Couldn't Triangulate\n" ); + Stop(); + if ( m_moveWaitTime > 0 ) + { + FRefreshRoute(); + m_flMoveWaitFinished = gpGlobals->time + m_moveWaitTime * 0.5; + } + else + { + TaskFail(); + ALERT( at_aiconsole, "Failed to move!\n" ); + //ALERT( at_aiconsole, "%f, %f, %f\n", pev->origin.z, (pev->origin + (vecDir * flCheckDist)).z, m_Route[m_iRouteIndex].vecLocation.z ); + } + return; + } + } + } + + // UNDONE: this is a hack to quit moving farther than it has looked ahead. + if (flCheckDist < flMoveDist) + { + MoveExecute( pTargetEnt, vecDir, flCheckDist / m_flGroundSpeed ); + + // ALERT( at_console, "%.02f\n", flInterval ); + AdvanceRoute( flWaypointDist ); + flMoveDist -= flCheckDist; + } + else + { + MoveExecute( pTargetEnt, vecDir, flMoveDist / m_flGroundSpeed ); + + if ( ShouldAdvanceRoute( flWaypointDist - flMoveDist ) ) + { + AdvanceRoute( flWaypointDist ); + } + flMoveDist = 0; + } + + if ( MovementIsComplete() ) + { + Stop(); + RouteClear(); + } + } while (flMoveDist > 0 && flCheckDist > 0); + + // cut corner? + if (flWaypointDist < 128) + { + if ( m_movementGoal == MOVEGOAL_ENEMY ) + RouteSimplify( m_hEnemy ); + else + RouteSimplify( m_hTargetEnt ); + FRefreshRoute(); + + if (m_flGroundSpeed > 100) + m_flGroundSpeed -= 40; + } + else + { + if (m_flGroundSpeed < 400) + m_flGroundSpeed += 10; + } +} + + + +BOOL CController:: ShouldAdvanceRoute( float flWaypointDist ) +{ + if ( flWaypointDist <= 32 ) + { + return TRUE; + } + + return FALSE; +} + + +int CController :: CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ) +{ + TraceResult tr; + + UTIL_TraceHull( vecStart + Vector( 0, 0, 32), vecEnd + Vector( 0, 0, 32), dont_ignore_monsters, large_hull, edict(), &tr ); + + // ALERT( at_console, "%.0f %.0f %.0f : ", vecStart.x, vecStart.y, vecStart.z ); + // ALERT( at_console, "%.0f %.0f %.0f\n", vecEnd.x, vecEnd.y, vecEnd.z ); + + if (pflDist) + { + *pflDist = ( (tr.vecEndPos - Vector( 0, 0, 32 )) - vecStart ).Length();// get the distance. + } + + // ALERT( at_console, "check %d %d %f\n", tr.fStartSolid, tr.fAllSolid, tr.flFraction ); + if (tr.fStartSolid || tr.flFraction < 1.0) + { + if ( pTarget && pTarget->edict() == gpGlobals->trace_ent ) + return LOCALMOVE_VALID; + return LOCALMOVE_INVALID; + } + + return LOCALMOVE_VALID; +} + + +void CController::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ) +{ + if ( m_IdealActivity != m_movementActivity ) + m_IdealActivity = m_movementActivity; + + // ALERT( at_console, "move %.4f %.4f %.4f : %f\n", vecDir.x, vecDir.y, vecDir.z, flInterval ); + + // float flTotal = m_flGroundSpeed * pev->framerate * flInterval; + // UTIL_MoveToOrigin ( ENT(pev), m_Route[ m_iRouteIndex ].vecLocation, flTotal, MOVE_STRAFE ); + + m_velocity = m_velocity * 0.8 + m_flGroundSpeed * vecDir * 0.2; + + UTIL_MoveToOrigin ( ENT(pev), pev->origin + m_velocity, m_velocity.Length() * flInterval, MOVE_STRAFE ); + +} + + + + +//========================================================= +// Controller bouncy ball attack +//========================================================= +class CControllerHeadBall : public CBaseMonster +{ + void Spawn( void ); + void Precache( void ); + void EXPORT HuntThink( void ); + void EXPORT DieThink( void ); + void EXPORT BounceTouch( CBaseEntity *pOther ); + void MovetoTarget( Vector vecTarget ); + void Crawl( void ); + int m_iTrail; + int m_flNextAttack; + Vector m_vecIdeal; + EHANDLE m_hOwner; +}; +LINK_ENTITY_TO_CLASS( controller_head_ball, CControllerHeadBall ); + + + +void CControllerHeadBall :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "sprites/xspark4.spr"); + pev->rendermode = kRenderTransAdd; + pev->rendercolor.x = 255; + pev->rendercolor.y = 255; + pev->rendercolor.z = 255; + pev->renderamt = 255; + pev->scale = 2.0; + + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + UTIL_SetOrigin( this, pev->origin ); + + SetThink(&CControllerHeadBall :: HuntThink ); + SetTouch(&CControllerHeadBall :: BounceTouch ); + + m_vecIdeal = Vector( 0, 0, 0 ); + + SetNextThink( 0.1 ); + + m_hOwner = Instance( pev->owner ); + pev->dmgtime = gpGlobals->time; +} + + +void CControllerHeadBall :: Precache( void ) +{ + PRECACHE_MODEL("sprites/xspark1.spr"); + PRECACHE_SOUND("debris/zap4.wav"); + PRECACHE_SOUND("weapons/electro4.wav"); +} + + +void CControllerHeadBall :: HuntThink( void ) +{ + SetNextThink( 0.1 ); + + pev->renderamt -= 5; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( pev->renderamt / 16 ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 255 ); // G + WRITE_BYTE( 255 ); // B + WRITE_BYTE( 2 ); // life * 10 + WRITE_COORD( 0 ); // decay + MESSAGE_END(); + + // check world boundaries + if (gpGlobals->time - pev->dmgtime > 5 || pev->renderamt < 64 || m_hEnemy == NULL || m_hOwner == NULL || pev->origin.x < -4096 || pev->origin.x > 4096 || pev->origin.y < -4096 || pev->origin.y > 4096 || pev->origin.z < -4096 || pev->origin.z > 4096) + { + SetTouch( NULL ); + UTIL_Remove( this ); + return; + } + + MovetoTarget( m_hEnemy->Center( ) ); + + if ((m_hEnemy->Center() - pev->origin).Length() < 64) + { + TraceResult tr; + + UTIL_TraceLine( pev->origin, m_hEnemy->Center(), dont_ignore_monsters, ENT(pev), &tr ); + + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + if (pEntity != NULL && pEntity->pev->takedamage) + { + ClearMultiDamage( ); + pEntity->TraceAttack( m_hOwner->pev, gSkillData.controllerDmgZap, pev->velocity, &tr, DMG_SHOCK ); + ApplyMultiDamage( pev, m_hOwner->pev ); + } + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( entindex() ); + WRITE_COORD( tr.vecEndPos.x ); + WRITE_COORD( tr.vecEndPos.y ); + WRITE_COORD( tr.vecEndPos.z ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 3 ); // life + WRITE_BYTE( 20 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); + + UTIL_EmitAmbientSound( ENT(pev), tr.vecEndPos, "weapons/electro4.wav", 0.5, ATTN_NORM, 0, RANDOM_LONG( 140, 160 ) ); + + m_flNextAttack = gpGlobals->time + 3.0; + + SetThink(&CControllerHeadBall :: DieThink ); + SetNextThink( 0.3 ); + } + + // Crawl( ); +} + + +void CControllerHeadBall :: DieThink( void ) +{ + UTIL_Remove( this ); +} + + +void CControllerHeadBall :: MovetoTarget( Vector vecTarget ) +{ + // accelerate + float flSpeed = m_vecIdeal.Length(); + if (flSpeed == 0) + { + m_vecIdeal = pev->velocity; + flSpeed = m_vecIdeal.Length(); + } + + if (flSpeed > 400) + { + m_vecIdeal = m_vecIdeal.Normalize( ) * 400; + } + m_vecIdeal = m_vecIdeal + (vecTarget - pev->origin).Normalize() * 100; + pev->velocity = m_vecIdeal; +} + + + +void CControllerHeadBall :: Crawl( void ) +{ + + Vector vecAim = Vector( RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 ) ).Normalize( ); + Vector vecPnt = pev->origin + pev->velocity * 0.3 + vecAim * 64; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( entindex() ); + WRITE_COORD( vecPnt.x); + WRITE_COORD( vecPnt.y); + WRITE_COORD( vecPnt.z); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 3 ); // life + WRITE_BYTE( 20 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); +} + + +void CControllerHeadBall::BounceTouch( CBaseEntity *pOther ) +{ + Vector vecDir = m_vecIdeal.Normalize( ); + + TraceResult tr = UTIL_GetGlobalTrace( ); + + float n = -DotProduct(tr.vecPlaneNormal, vecDir); + + vecDir = 2.0 * tr.vecPlaneNormal * n + vecDir; + + m_vecIdeal = vecDir * m_vecIdeal.Length(); +} + + + + +class CControllerZapBall : public CBaseMonster +{ + void Spawn( void ); + void Precache( void ); + void EXPORT AnimateThink( void ); + void EXPORT ExplodeTouch( CBaseEntity *pOther ); + + EHANDLE m_hOwner; +}; +LINK_ENTITY_TO_CLASS( controller_energy_ball, CControllerZapBall ); + + +void CControllerZapBall :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "sprites/xspark4.spr"); + pev->rendermode = kRenderTransAdd; + pev->rendercolor.x = 255; + pev->rendercolor.y = 255; + pev->rendercolor.z = 255; + pev->renderamt = 255; + pev->scale = 0.5; + + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + UTIL_SetOrigin( this, pev->origin ); + + SetThink(&CControllerZapBall :: AnimateThink ); + SetTouch(&CControllerZapBall :: ExplodeTouch ); + + m_hOwner = Instance( pev->owner ); + pev->dmgtime = gpGlobals->time; // keep track of when ball spawned + SetNextThink( 0.1 ); +} + + +void CControllerZapBall :: Precache( void ) +{ + PRECACHE_MODEL("sprites/xspark4.spr"); + // PRECACHE_SOUND("debris/zap4.wav"); + // PRECACHE_SOUND("weapons/electro4.wav"); +} + + +void CControllerZapBall :: AnimateThink( void ) +{ + SetNextThink( 0.1 ); + + pev->frame = ((int)pev->frame + 1) % 11; + + if (gpGlobals->time - pev->dmgtime > 5 || pev->velocity.Length() < 10) + { + SetTouch( NULL ); + UTIL_Remove( this ); + } +} + + +void CControllerZapBall::ExplodeTouch( CBaseEntity *pOther ) +{ + if (pOther->pev->takedamage) + { + TraceResult tr = UTIL_GetGlobalTrace( ); + + entvars_t *pevOwner; + if (m_hOwner != NULL) + { + pevOwner = m_hOwner->pev; + } + else + { + pevOwner = pev; + } + + ClearMultiDamage( ); + pOther->TraceAttack(pevOwner, gSkillData.controllerDmgBall, pev->velocity.Normalize(), &tr, DMG_ENERGYBEAM ); + ApplyMultiDamage( pevOwner, pevOwner ); + + UTIL_EmitAmbientSound( ENT(pev), tr.vecEndPos, "weapons/electro4.wav", 0.3, ATTN_NORM, 0, RANDOM_LONG( 90, 99 ) ); + + } + + UTIL_Remove( this ); +} + + + +#endif // !OEM && !HLDEMO diff --git a/dlls/decals.h b/dlls/decals.h new file mode 100644 index 0000000..0f8ff4e --- /dev/null +++ b/dlls/decals.h @@ -0,0 +1,75 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef DECALS_H +#define DECALS_H + +// +// Dynamic Decals +// +enum decal_e +{ + DECAL_GUNSHOT1 = 0, + DECAL_GUNSHOT2, + DECAL_GUNSHOT3, + DECAL_GUNSHOT4, + DECAL_GUNSHOT5, + DECAL_LAMBDA1, + DECAL_LAMBDA2, + DECAL_LAMBDA3, + DECAL_LAMBDA4, + DECAL_LAMBDA5, + DECAL_LAMBDA6, + DECAL_SCORCH1, + DECAL_SCORCH2, + DECAL_BLOOD1, + DECAL_BLOOD2, + DECAL_BLOOD3, + DECAL_BLOOD4, + DECAL_BLOOD5, + DECAL_BLOOD6, + DECAL_YBLOOD1, + DECAL_YBLOOD2, + DECAL_YBLOOD3, + DECAL_YBLOOD4, + DECAL_YBLOOD5, + DECAL_YBLOOD6, + DECAL_GLASSBREAK1, + DECAL_GLASSBREAK2, + DECAL_GLASSBREAK3, + DECAL_BIGSHOT1, + DECAL_BIGSHOT2, + DECAL_BIGSHOT3, + DECAL_BIGSHOT4, + DECAL_BIGSHOT5, + DECAL_SPIT1, + DECAL_SPIT2, + DECAL_BPROOF1, // Bulletproof glass decal + DECAL_GARGSTOMP1, // Gargantua stomp crack + DECAL_SMALLSCORCH1, // Small scorch mark + DECAL_SMALLSCORCH2, // Small scorch mark + DECAL_SMALLSCORCH3, // Small scorch mark + DECAL_MOMMABIRTH, // Big momma birth splatter + DECAL_MOMMASPLAT, +}; + +typedef struct +{ + char *name; + int index; +} DLL_DECALLIST; + +extern DLL_DECALLIST gDecals[]; + +#endif // DECALS_H diff --git a/dlls/defaultai.cpp b/dlls/defaultai.cpp new file mode 100644 index 0000000..ccab9f3 --- /dev/null +++ b/dlls/defaultai.cpp @@ -0,0 +1,1288 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Default behaviors. +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "defaultai.h" +#include "soundent.h" +#include "nodes.h" +#include "scripted.h" + +//========================================================= +// Fail +//========================================================= +Task_t tlFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slFail[] = +{ + { + tlFail, + ARRAYSIZE ( tlFail ), + bits_COND_CAN_ATTACK, + 0, + "Fail" + }, +}; + +//========================================================= +// Idle Schedules +//========================================================= +Task_t tlIdleStand1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)5 },// repick IDLESTAND every five seconds. gives us a chance to pick an active idle, fidget, etc. +}; + +Schedule_t slIdleStand[] = +{ + { + tlIdleStand1, + ARRAYSIZE ( tlIdleStand1 ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_FEAR | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_SMELL_FOOD | + bits_COND_SMELL | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT |// sound flags + bits_SOUND_WORLD | + bits_SOUND_PLAYER | + bits_SOUND_DANGER | + + bits_SOUND_MEAT |// scents + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "IdleStand" + }, +}; + +Schedule_t slIdleTrigger[] = +{ + { + tlIdleStand1, + ARRAYSIZE ( tlIdleStand1 ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "Idle Trigger" + }, +}; + + +Task_t tlIdleWalk1[] = +{ + { TASK_WALK_PATH, (float)9999 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slIdleWalk[] = +{ + { + tlIdleWalk1, + ARRAYSIZE ( tlIdleWalk1 ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_SMELL_FOOD | + bits_COND_SMELL | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT |// sound flags + + bits_SOUND_MEAT |// scents + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "Idle Walk" + }, +}; + +//========================================================= +// Ambush - monster stands in place and waits for a new +// enemy, or chance to attack an existing enemy. +//========================================================= +Task_t tlAmbush[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_INDEFINITE, (float)0 }, +}; + +Schedule_t slAmbush[] = +{ + { + tlAmbush, + ARRAYSIZE ( tlAmbush ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED, + + 0, + "Ambush" + }, +}; + +//========================================================= +// ActiveIdle schedule - !!!BUGBUG - if this schedule doesn't +// complete on its own, the monster's HintNode will not be +// cleared, and the rest of the monster's group will avoid +// that node because they think the group member that was +// previously interrupted is still using that node to active +// idle. +///========================================================= +Task_t tlActiveIdle[] = +{ + { TASK_FIND_HINTNODE, (float)0 }, + { TASK_GET_PATH_TO_HINTNODE, (float)0 }, + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_FACE_HINTNODE, (float)0 }, + { TASK_PLAY_ACTIVE_IDLE, (float)0 }, + { TASK_GET_PATH_TO_LASTPOSITION,(float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_CLEAR_LASTPOSITION, (float)0 }, + { TASK_CLEAR_HINTNODE, (float)0 }, +}; + +Schedule_t slActiveIdle[] = +{ + { + tlActiveIdle, + ARRAYSIZE( tlActiveIdle ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED | + bits_COND_HEAR_SOUND, + + bits_SOUND_COMBAT | + bits_SOUND_WORLD | + bits_SOUND_PLAYER | + bits_SOUND_DANGER, + "Active Idle" + } +}; + +//========================================================= +// Wake Schedules +//========================================================= +Task_t tlWakeAngry1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_SOUND_WAKE, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, +}; + +Schedule_t slWakeAngry[] = +{ + { + tlWakeAngry1, + ARRAYSIZE ( tlWakeAngry1 ), + 0, + 0, + "Wake Angry" + } +}; + +//========================================================= +// AlertFace Schedules +//========================================================= +Task_t tlAlertFace1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_IDEAL, (float)0 }, +}; + +Schedule_t slAlertFace[] = +{ + { + tlAlertFace1, + ARRAYSIZE ( tlAlertFace1 ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_FEAR | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED, + 0, + "Alert Face" + }, +}; + +//========================================================= +// AlertSmallFlinch Schedule - shot, but didn't see attacker, +// flinch then face +//========================================================= +Task_t tlAlertSmallFlinch[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_REMEMBER, (float)bits_MEMORY_FLINCHED }, + { TASK_SMALL_FLINCH, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_ALERT_FACE }, +}; + +Schedule_t slAlertSmallFlinch[] = +{ + { + tlAlertSmallFlinch, + ARRAYSIZE ( tlAlertSmallFlinch ), + 0, + 0, + "Alert Small Flinch" + }, +}; + +//========================================================= +// AlertIdle Schedules +//========================================================= +Task_t tlAlertStand1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)20 }, + { TASK_SUGGEST_STATE, (float)MONSTERSTATE_IDLE }, +}; + +Schedule_t slAlertStand[] = +{ + { + tlAlertStand1, + ARRAYSIZE ( tlAlertStand1 ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_ENEMY | + bits_COND_SEE_FEAR | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED | + bits_COND_SMELL | + bits_COND_SMELL_FOOD | + bits_COND_HEAR_SOUND, + + bits_SOUND_COMBAT |// sound flags + bits_SOUND_WORLD | + bits_SOUND_PLAYER | + bits_SOUND_DANGER | + + bits_SOUND_MEAT |// scent flags + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "Alert Stand" + }, +}; + +//========================================================= +// InvestigateSound - sends a monster to the location of the +// sound that was just heard, to check things out. +//========================================================= +Task_t tlInvestigateSound[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_GET_PATH_TO_BESTSOUND, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_IDLE }, + { TASK_WAIT, (float)10 }, + { TASK_GET_PATH_TO_LASTPOSITION,(float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_CLEAR_LASTPOSITION, (float)0 }, +}; + +Schedule_t slInvestigateSound[] = +{ + { + tlInvestigateSound, + ARRAYSIZE ( tlInvestigateSound ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_FEAR | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "InvestigateSound" + }, +}; + +//========================================================= +// CombatIdle Schedule +//========================================================= +Task_t tlCombatStand1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_INDEFINITE, (float)0 }, +}; + +Schedule_t slCombatStand[] = +{ + { + tlCombatStand1, + ARRAYSIZE ( tlCombatStand1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_CAN_ATTACK, + 0, + "Combat Stand" + }, +}; + +//========================================================= +// CombatFace Schedule +//========================================================= +Task_t tlCombatFace1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_ENEMY, (float)0 }, +}; + +Schedule_t slCombatFace[] = +{ + { + tlCombatFace1, + ARRAYSIZE ( tlCombatFace1 ), + bits_COND_CAN_ATTACK | + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD, + 0, + "Combat Face" + }, +}; + +//========================================================= +// Standoff schedule. Used in combat when a monster is +// hiding in cover or the enemy has moved out of sight. +// Should we look around in this schedule? +//========================================================= +Task_t tlStandoff[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, +}; + +Schedule_t slStandoff[] = +{ + { + tlStandoff, + ARRAYSIZE ( tlStandoff ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_ENEMY_DEAD | + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Standoff" + } +}; + +//========================================================= +// Arm weapon (draw gun) +//========================================================= +Task_t tlArmWeapon[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_PLAY_SEQUENCE, (float) ACT_ARM } +}; + +Schedule_t slArmWeapon[] = +{ + { + tlArmWeapon, + ARRAYSIZE ( tlArmWeapon ), + 0, + 0, + "Arm Weapon" + } +}; + +//========================================================= +// reload schedule +//========================================================= +Task_t tlReload[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_PLAY_SEQUENCE, float(ACT_RELOAD) }, +}; + +Schedule_t slReload[] = +{ + { + tlReload, + ARRAYSIZE ( tlReload ), + bits_COND_HEAVY_DAMAGE, + 0, + "Reload" + } +}; + +//========================================================= +// Attack Schedules +//========================================================= + +// primary range attack +Task_t tlRangeAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slRangeAttack1[] = +{ + { + tlRangeAttack1, + ARRAYSIZE ( tlRangeAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Range Attack1" + }, +}; + +// secondary range attack +Task_t tlRangeAttack2[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_RANGE_ATTACK2, (float)0 }, +}; + +Schedule_t slRangeAttack2[] = +{ + { + tlRangeAttack2, + ARRAYSIZE ( tlRangeAttack2 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Range Attack2" + }, +}; + +// primary melee attack +Task_t tlPrimaryMeleeAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MELEE_ATTACK1, (float)0 }, +}; + +Schedule_t slPrimaryMeleeAttack[] = +{ + { + tlPrimaryMeleeAttack1, + ARRAYSIZE ( tlPrimaryMeleeAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED, + 0, + "Primary Melee Attack" + }, +}; + +// secondary melee attack +Task_t tlSecondaryMeleeAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MELEE_ATTACK2, (float)0 }, +}; + +Schedule_t slSecondaryMeleeAttack[] = +{ + { + tlSecondaryMeleeAttack1, + ARRAYSIZE ( tlSecondaryMeleeAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED, + 0, + "Secondary Melee Attack" + }, +}; + +// special attack1 +Task_t tlSpecialAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_SPECIAL_ATTACK1, (float)0 }, +}; + +Schedule_t slSpecialAttack1[] = +{ + { + tlSpecialAttack1, + ARRAYSIZE ( tlSpecialAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Special Attack1" + }, +}; + +// special attack2 +Task_t tlSpecialAttack2[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_SPECIAL_ATTACK2, (float)0 }, +}; + +Schedule_t slSpecialAttack2[] = +{ + { + tlSpecialAttack2, + ARRAYSIZE ( tlSpecialAttack2 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Special Attack2" + }, +}; + +// Chase enemy schedule +Task_t tlChaseEnemy1[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_CHASE_ENEMY_FAILED }, + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slChaseEnemy[] = +{ + { + tlChaseEnemy1, + ARRAYSIZE ( tlChaseEnemy1 ), + bits_COND_NEW_ENEMY | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_TASK_FAILED | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Chase Enemy" + }, +}; + + +// Chase enemy failure schedule +Task_t tlChaseEnemyFailed[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, +// { TASK_TURN_LEFT, (float)179 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slChaseEnemyFailed[] = +{ + { + tlChaseEnemyFailed, + ARRAYSIZE ( tlChaseEnemyFailed ), + bits_COND_NEW_ENEMY | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "tlChaseEnemyFailed" + }, +}; + + +//========================================================= +// small flinch, played when minor damage is taken. +//========================================================= +Task_t tlSmallFlinch[] = +{ + { TASK_REMEMBER, (float)bits_MEMORY_FLINCHED }, + { TASK_STOP_MOVING, 0 }, + { TASK_SMALL_FLINCH, 0 }, +}; + +Schedule_t slSmallFlinch[] = +{ + { + tlSmallFlinch, + ARRAYSIZE ( tlSmallFlinch ), + 0, + 0, + "Small Flinch" + }, +}; + +//========================================================= +// Die! +//========================================================= +Task_t tlDie1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SOUND_DIE, (float)0 }, + { TASK_DIE, (float)0 }, +}; + +Schedule_t slDie[] = +{ + { + tlDie1, + ARRAYSIZE( tlDie1 ), + 0, + 0, + "Die" + }, +}; + +//========================================================= +// Victory Dance +//========================================================= +Task_t tlVictoryDance[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_WAIT, (float)0 }, +}; + +Schedule_t slVictoryDance[] = +{ + { + tlVictoryDance, + ARRAYSIZE( tlVictoryDance ), + 0, + 0, + "Victory Dance" + }, +}; + +//========================================================= +// BarnacleVictimGrab - barnacle tongue just hit the monster, +// so play a hit animation, then play a cycling pull animation +// as the creature is hoisting the monster. +//========================================================= +Task_t tlBarnacleVictimGrab[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_BARNACLE_HIT }, + { TASK_SET_ACTIVITY, (float)ACT_BARNACLE_PULL }, + { TASK_WAIT_INDEFINITE, (float)0 },// just cycle barnacle pull anim while barnacle hoists. +}; + +Schedule_t slBarnacleVictimGrab[] = +{ + { + tlBarnacleVictimGrab, + ARRAYSIZE ( tlBarnacleVictimGrab ), + 0, + 0, + "Barnacle Victim" + } +}; + +//========================================================= +// BarnacleVictimChomp - barnacle has pulled the prey to its +// mouth. Victim should play the BARNCLE_CHOMP animation +// once, then loop the BARNACLE_CHEW animation indefinitely +//========================================================= +Task_t tlBarnacleVictimChomp[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_BARNACLE_CHOMP }, + { TASK_SET_ACTIVITY, (float)ACT_BARNACLE_CHEW }, + { TASK_WAIT_INDEFINITE, (float)0 },// just cycle barnacle pull anim while barnacle hoists. +}; + +Schedule_t slBarnacleVictimChomp[] = +{ + { + tlBarnacleVictimChomp, + ARRAYSIZE ( tlBarnacleVictimChomp ), + 0, + 0, + "Barnacle Chomp" + } +}; + + +// Universal Error Schedule +Task_t tlError[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_WAIT_INDEFINITE, (float)0 }, +}; + +Schedule_t slError[] = +{ + { + tlError, + ARRAYSIZE ( tlError ), + 0, + 0, + "Error" + }, +}; + +//LRC +Task_t tlScriptedTeleport[] = +{ + { TASK_PLANT_ON_SCRIPT, (float)0 }, + { TASK_WAIT_FOR_SCRIPT, (float)0 }, + { TASK_PLAY_SCRIPT, (float)0 }, + { TASK_END_SCRIPT, (float)0 }, +}; + +//LRC +Schedule_t slTeleportToScript[] = +{ + { + tlScriptedTeleport, + ARRAYSIZE ( tlScriptedTeleport ), + SCRIPT_BREAK_CONDITIONS, + 0, + "TeleportToScript" + }, +}; + +Task_t tlScriptedWalk[] = +{ + { TASK_WALK_TO_SCRIPT, (float)TARGET_MOVE_SCRIPTED }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_PLANT_ON_SCRIPT, (float)0 }, + { TASK_FACE_SCRIPT, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_ENABLE_SCRIPT, (float)0 }, + { TASK_WAIT_FOR_SCRIPT, (float)0 }, + { TASK_PLAY_SCRIPT, (float)0 }, + { TASK_END_SCRIPT, (float)0 }, +}; + +Schedule_t slWalkToScript[] = +{ + { + tlScriptedWalk, + ARRAYSIZE ( tlScriptedWalk ), + SCRIPT_BREAK_CONDITIONS, + 0, + "WalkToScript" + }, +}; + + +Task_t tlScriptedRun[] = +{ + { TASK_RUN_TO_SCRIPT, (float)TARGET_MOVE_SCRIPTED }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_PLANT_ON_SCRIPT, (float)0 }, + { TASK_FACE_SCRIPT, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_ENABLE_SCRIPT, (float)0 }, + { TASK_WAIT_FOR_SCRIPT, (float)0 }, + { TASK_PLAY_SCRIPT, (float)0 }, + { TASK_END_SCRIPT, (float)0 }, +}; + +Schedule_t slRunToScript[] = +{ + { + tlScriptedRun, + ARRAYSIZE ( tlScriptedRun ), + SCRIPT_BREAK_CONDITIONS, + 0, + "RunToScript" + }, +}; + +Task_t tlScriptedWait[] = +{ + { TASK_STOP_MOVING, 0 }, +// { TASK_ENABLE_SCRIPT, (float)0 }, + { TASK_WAIT_FOR_SCRIPT, (float)0 }, + { TASK_PLAY_SCRIPT, (float)0 }, + { TASK_END_SCRIPT, (float)0 }, +}; + +Schedule_t slWaitScript[] = +{ + { + tlScriptedWait, + ARRAYSIZE ( tlScriptedWait ), + SCRIPT_BREAK_CONDITIONS, + 0, + "WaitForScript" + }, +}; + +Task_t tlScriptedFace[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_SCRIPT, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_WAIT_FOR_SCRIPT, (float)0 }, + { TASK_PLAY_SCRIPT, (float)0 }, + { TASK_END_SCRIPT, (float)0 }, +}; + +Schedule_t slFaceScript[] = +{ + { + tlScriptedFace, + ARRAYSIZE ( tlScriptedFace ), + SCRIPT_BREAK_CONDITIONS, + 0, + "FaceScript" + }, +}; + +//========================================================= +// Cower - this is what is usually done when attempts +// to escape danger fail. +//========================================================= +Task_t tlCower[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_COWER }, +}; + +Schedule_t slCower[] = +{ + { + tlCower, + ARRAYSIZE ( tlCower ), + 0, + 0, + "Cower" + }, +}; + +//========================================================= +// move away from where you're currently standing. +//========================================================= +Task_t tlTakeCoverFromOrigin[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_ORIGIN, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_TURN_LEFT, (float)179 }, +}; + +Schedule_t slTakeCoverFromOrigin[] = +{ + { + tlTakeCoverFromOrigin, + ARRAYSIZE ( tlTakeCoverFromOrigin ), + bits_COND_NEW_ENEMY, + 0, + "TakeCoverFromOrigin" + }, +}; + +//========================================================= +// hide from the loudest sound source +//========================================================= +Task_t tlTakeCoverFromBestSound[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_BEST_SOUND, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_TURN_LEFT, (float)179 }, +}; + +Schedule_t slTakeCoverFromBestSound[] = +{ + { + tlTakeCoverFromBestSound, + ARRAYSIZE ( tlTakeCoverFromBestSound ), + bits_COND_NEW_ENEMY, + 0, + "TakeCoverFromBestSound" + }, +}; + +//========================================================= +// Take cover from enemy! Tries lateral cover before node +// cover! +//========================================================= +Task_t tlTakeCoverFromEnemy[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, +// { TASK_TURN_LEFT, (float)179 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slTakeCoverFromEnemy[] = +{ + { + tlTakeCoverFromEnemy, + ARRAYSIZE ( tlTakeCoverFromEnemy ), + bits_COND_NEW_ENEMY, + 0, + "tlTakeCoverFromEnemy" + }, +}; + +// buz: rush schedule +Task_t tlRushTarget[] = +{ + { TASK_MOVE_TO_TARGET_RANGE2,(float)0 }, +}; + +Schedule_t slRushTarget[] = +{ + { + tlRushTarget, + ARRAYSIZE ( tlRushTarget ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_PROVOKED, + bits_SOUND_DANGER, + "Rush Target" + }, +}; + +Schedule_t *CBaseMonster::m_scheduleList[] = +{ + slIdleStand, + slIdleTrigger, + slIdleWalk, + slAmbush, + slActiveIdle, + slWakeAngry, + slAlertFace, + slAlertSmallFlinch, + slAlertStand, + slInvestigateSound, + slCombatStand, + slCombatFace, + slStandoff, + slArmWeapon, + slReload, + slRangeAttack1, + slRangeAttack2, + slPrimaryMeleeAttack, + slSecondaryMeleeAttack, + slSpecialAttack1, + slSpecialAttack2, + slChaseEnemy, + slChaseEnemyFailed, + slSmallFlinch, + slDie, + slVictoryDance, + slBarnacleVictimGrab, + slBarnacleVictimChomp, + slError, + slWalkToScript, + slRunToScript, + slWaitScript, + slFaceScript, + slCower, + slTakeCoverFromOrigin, + slTakeCoverFromBestSound, + slTakeCoverFromEnemy, + slFail, + slRushTarget, // buz + slTeleportToScript // buz - Laurie forgot this? +}; + +Schedule_t *CBaseMonster::ScheduleFromName( const char *pName ) +{ + return ScheduleInList( pName, m_scheduleList, ARRAYSIZE(m_scheduleList) ); +} + + +Schedule_t *CBaseMonster :: ScheduleInList( const char *pName, Schedule_t **pList, int listCount ) +{ + int i; + + if ( !pName ) + { + ALERT( at_debug, "%s set to unnamed schedule!\n", STRING(pev->classname) ); + return NULL; + } + + + for ( i = 0; i < listCount; i++ ) + { + if ( !pList[i]->pName ) + { + ALERT( at_debug, "Unnamed schedule!\n" ); + continue; + } + if ( stricmp( pName, pList[i]->pName ) == 0 ) + return pList[i]; + } + return NULL; +} + +//========================================================= +// GetScheduleOfType - returns a pointer to one of the +// monster's available schedules of the indicated type. +//========================================================= +Schedule_t* CBaseMonster :: GetScheduleOfType ( int Type ) +{ +// ALERT ( at_console, "Sched Type:%d\n", Type ); + switch ( Type ) + { + // This is the schedule for scripted sequences AND scripted AI. // LRC- And scripted actions, too. + case SCHED_AISCRIPT: + { +// ALERT(at_console, "Doing AISCRIPT\n"); + ASSERT( m_pCine != NULL ); + if ( !m_pCine ) + { + ALERT( at_aiconsole, "Script failed for %s\n", STRING(pev->classname) ); + CineCleanup(); + return GetScheduleOfType( SCHED_IDLE_STAND ); + } +// else +// ALERT( at_aiconsole, "Starting script %s for %s\n", STRING( m_pCine->m_iszPlay ), STRING(pev->classname) ); + + switch ( m_pCine->m_fMoveTo ) + { + case 0: + return slWaitScript; + case 4: case 6: + return slTeleportToScript; + case 1: + return slWalkToScript; + case 2: + return slRunToScript; + case 5: + return slFaceScript; + } + break; + } + case SCHED_IDLE_STAND: + { + if ( RANDOM_LONG(0,14) == 0 && FCanActiveIdle() ) + { + return &slActiveIdle[ 0 ]; + } + + return &slIdleStand[ 0 ]; + } + case SCHED_IDLE_WALK: + { + return &slIdleWalk[ 0 ]; + } + case SCHED_WAIT_TRIGGER: + { + return &slIdleTrigger[ 0 ]; + } + case SCHED_WAKE_ANGRY: + { + return &slWakeAngry[ 0 ]; + } + case SCHED_ALERT_FACE: + { + return &slAlertFace[ 0 ]; + } + case SCHED_ALERT_STAND: + { + return &slAlertStand[ 0 ]; + } + case SCHED_COMBAT_STAND: + { + return &slCombatStand[ 0 ]; + } + case SCHED_COMBAT_FACE: + { + return &slCombatFace[ 0 ]; + } + case SCHED_CHASE_ENEMY: + { + return &slChaseEnemy[ 0 ]; + } + case SCHED_CHASE_ENEMY_FAILED: + { + return &slFail[ 0 ]; + } + case SCHED_SMALL_FLINCH: + { + return &slSmallFlinch[ 0 ]; + } + case SCHED_ALERT_SMALL_FLINCH: + { + return &slAlertSmallFlinch[ 0 ]; + } + case SCHED_RELOAD: + { + return &slReload[ 0 ]; + } + case SCHED_ARM_WEAPON: + { + return &slArmWeapon[ 0 ]; + } + case SCHED_STANDOFF: + { + return &slStandoff[ 0 ]; + } + case SCHED_RANGE_ATTACK1: + { + return &slRangeAttack1[ 0 ]; + } + case SCHED_RANGE_ATTACK2: + { + return &slRangeAttack2[ 0 ]; + } + case SCHED_MELEE_ATTACK1: + { + return &slPrimaryMeleeAttack[ 0 ]; + } + case SCHED_MELEE_ATTACK2: + { + return &slSecondaryMeleeAttack[ 0 ]; + } + case SCHED_SPECIAL_ATTACK1: + { + return &slSpecialAttack1[ 0 ]; + } + case SCHED_SPECIAL_ATTACK2: + { + return &slSpecialAttack2[ 0 ]; + } + case SCHED_TAKE_COVER_FROM_BEST_SOUND: + { + return &slTakeCoverFromBestSound[ 0 ]; + } + case SCHED_TAKE_COVER_FROM_ENEMY: + { + return &slTakeCoverFromEnemy[ 0 ]; + } + case SCHED_COWER: + { + return &slCower[ 0 ]; + } + case SCHED_AMBUSH: + { + return &slAmbush[ 0 ]; + } + case SCHED_BARNACLE_VICTIM_GRAB: + { + return &slBarnacleVictimGrab[ 0 ]; + } + case SCHED_BARNACLE_VICTIM_CHOMP: + { + return &slBarnacleVictimChomp[ 0 ]; + } + case SCHED_INVESTIGATE_SOUND: + { + return &slInvestigateSound[ 0 ]; + } + case SCHED_DIE: + { + UTIL_SetSize(pev, g_vecZero, g_vecZero ); + return &slDie[ 0 ]; + } + case SCHED_TAKE_COVER_FROM_ORIGIN: + { + return &slTakeCoverFromOrigin[ 0 ]; + } + case SCHED_VICTORY_DANCE: + { + return &slVictoryDance[ 0 ]; + } + case SCHED_FAIL: + { + return slFail; + } + case SCHED_RUSH_TARGET: // buz + { + return slRushTarget; + } + default: + { + ALERT ( at_debug, "GetScheduleOfType()\nNo CASE for Schedule Type %d!\n", Type ); + + return &slIdleStand[ 0 ]; + break; + } + } + + return NULL; +} diff --git a/dlls/defaultai.h b/dlls/defaultai.h new file mode 100644 index 0000000..652d108 --- /dev/null +++ b/dlls/defaultai.h @@ -0,0 +1,98 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef DEFAULTAI_H +#define DEFAULTAI_H + +//========================================================= +// Failed +//========================================================= +extern Schedule_t slFail[]; + +//========================================================= +// Idle Schedules +//========================================================= +extern Schedule_t slIdleStand[]; +extern Schedule_t slIdleTrigger[]; +extern Schedule_t slIdleWalk[]; + +//========================================================= +// Wake Schedules +//========================================================= +extern Schedule_t slWakeAngry[]; + +//========================================================= +// AlertTurn Schedules +//========================================================= +extern Schedule_t slAlertFace[]; + +//========================================================= +// AlertIdle Schedules +//========================================================= +extern Schedule_t slAlertStand[]; + +//========================================================= +// CombatIdle Schedule +//========================================================= +extern Schedule_t slCombatStand[]; + +//========================================================= +// CombatFace Schedule +//========================================================= +extern Schedule_t slCombatFace[]; + +//========================================================= +// reload schedule +//========================================================= +extern Schedule_t slReload[]; + +//========================================================= +// Attack Schedules +//========================================================= + +extern Schedule_t slRangeAttack1[]; +extern Schedule_t slRangeAttack2[]; + +extern Schedule_t slTakeCoverFromBestSound[]; + +// primary melee attack +extern Schedule_t slMeleeAttack[]; + +// Chase enemy schedule +extern Schedule_t slChaseEnemy[]; + +//========================================================= +// small flinch, used when a relatively minor bit of damage +// is inflicted. +//========================================================= +extern Schedule_t slSmallFlinch[]; + +//========================================================= +// Die! +//========================================================= +extern Schedule_t slDie[]; + +//========================================================= +// Universal Error Schedule +//========================================================= +extern Schedule_t slError[]; + +//========================================================= +// Scripted sequences +//========================================================= +extern Schedule_t slWalkToScript[]; +extern Schedule_t slRunToScript[]; +extern Schedule_t slWaitScript[]; + +#endif // DEFAULTAI_H diff --git a/dlls/doors.cpp b/dlls/doors.cpp new file mode 100644 index 0000000..15a38aa --- /dev/null +++ b/dlls/doors.cpp @@ -0,0 +1,1318 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== doors.cpp ======================================================== + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "doors.h" +#include "movewith.h" + +extern void SetMovedir(entvars_t* ev); + +#define noiseMoving noise1 +#define noiseArrived noise2 + +class CBaseDoor : public CBaseToggle +{ +public: + void Spawn( void ); + void Precache( void ); + virtual void PostSpawn( void ); + virtual void KeyValue( KeyValueData *pkvd ); + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual void Blocked( CBaseEntity *pOther ); + + + virtual int ObjectCaps( void ) + { + if (pev->spawnflags & SF_ITEM_USE_ONLY) + { + return (CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_IMPULSE_USE | + (m_iDirectUse ? FCAP_ONLYDIRECT_USE : 0); + } + else + return (CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION); + }; + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + virtual void SetToggleState( int state ); + + // used to selectivly override defaults + void EXPORT DoorTouch( CBaseEntity *pOther ); + + // local functions + int DoorActivate( ); + void EXPORT DoorGoUp( void ); + void EXPORT DoorGoDown( void ); + void EXPORT DoorHitTop( void ); + void EXPORT DoorHitBottom( void ); + + BYTE m_bHealthValue;// some doors are medi-kit doors, they give players health + + BYTE m_bMoveSnd; // sound a door makes while moving + BYTE m_bStopSnd; // sound a door makes when it stops + + locksound_t m_ls; // door lock sounds + + BYTE m_bLockedSound; // ordinals from entity selection + BYTE m_bLockedSentence; + BYTE m_bUnlockedSound; + BYTE m_bUnlockedSentence; + + BOOL m_iOnOffMode; + BOOL m_iImmediateMode; + + BOOL m_iDirectUse; +}; + + +TYPEDESCRIPTION CBaseDoor::m_SaveData[] = +{ + DEFINE_FIELD( CBaseDoor, m_bHealthValue, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bMoveSnd, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bStopSnd, FIELD_CHARACTER ), + + DEFINE_FIELD( CBaseDoor, m_bLockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bLockedSentence, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bUnlockedSound, FIELD_CHARACTER ), + DEFINE_FIELD( CBaseDoor, m_bUnlockedSentence, FIELD_CHARACTER ), + + DEFINE_FIELD( CBaseDoor, m_iOnOffMode, FIELD_BOOLEAN ), + DEFINE_FIELD( CBaseDoor, m_iImmediateMode, FIELD_BOOLEAN ), + + DEFINE_FIELD( CBaseDoor, m_iDirectUse, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE( CBaseDoor, CBaseToggle ); + + +#define DOOR_SENTENCEWAIT 6 +#define DOOR_SOUNDWAIT 3 +#define BUTTON_SOUNDWAIT 0.5 + +// play door or button locked or unlocked sounds. +// pass in pointer to valid locksound struct. +// if flocked is true, play 'door is locked' sound, +// otherwise play 'door is unlocked' sound +// NOTE: this routine is shared by doors and buttons + +void PlayLockSounds(entvars_t *pev, locksound_t *pls, int flocked, int fbutton) +{ + // LOCKED SOUND + + // CONSIDER: consolidate the locksound_t struct (all entries are duplicates for lock/unlock) + // CONSIDER: and condense this code. + float flsoundwait; + + if (fbutton) + flsoundwait = BUTTON_SOUNDWAIT; + else + flsoundwait = DOOR_SOUNDWAIT; + + if (flocked) + { + int fplaysound = (pls->sLockedSound && gpGlobals->time > pls->flwaitSound); + int fplaysentence = (pls->sLockedSentence && !pls->bEOFLocked && gpGlobals->time > pls->flwaitSentence); + float fvol; + + if (fplaysound && fplaysentence) + fvol = 0.25; + else + fvol = 1.0; + + // if there is a locked sound, and we've debounced, play sound + if (fplaysound) + { + // play 'door locked' sound + EMIT_SOUND(ENT(pev), CHAN_ITEM, (char*)STRING(pls->sLockedSound), fvol, ATTN_NORM); + pls->flwaitSound = gpGlobals->time + flsoundwait; + } + + // if there is a sentence, we've not played all in list, and we've debounced, play sound + if (fplaysentence) + { + // play next 'door locked' sentence in group + int iprev = pls->iLockedSentence; + + pls->iLockedSentence = SENTENCEG_PlaySequentialSz(ENT(pev), STRING(pls->sLockedSentence), + 0.85, ATTN_NORM, 0, 100, pls->iLockedSentence, FALSE); + pls->iUnlockedSentence = 0; + + // make sure we don't keep calling last sentence in list + pls->bEOFLocked = (iprev == pls->iLockedSentence); + + pls->flwaitSentence = gpGlobals->time + DOOR_SENTENCEWAIT; + } + } + else + { + // UNLOCKED SOUND + + int fplaysound = (pls->sUnlockedSound && gpGlobals->time > pls->flwaitSound); + int fplaysentence = (pls->sUnlockedSentence && !pls->bEOFUnlocked && gpGlobals->time > pls->flwaitSentence); + float fvol; + + // if playing both sentence and sound, lower sound volume so we hear sentence + if (fplaysound && fplaysentence) + fvol = 0.25; + else + fvol = 1.0; + + // play 'door unlocked' sound if set + if (fplaysound) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, (char*)STRING(pls->sUnlockedSound), fvol, ATTN_NORM); + pls->flwaitSound = gpGlobals->time + flsoundwait; + } + + // play next 'door unlocked' sentence in group + if (fplaysentence) + { + int iprev = pls->iUnlockedSentence; + + pls->iUnlockedSentence = SENTENCEG_PlaySequentialSz(ENT(pev), STRING(pls->sUnlockedSentence), + 0.85, ATTN_NORM, 0, 100, pls->iUnlockedSentence, FALSE); + pls->iLockedSentence = 0; + + // make sure we don't keep calling last sentence in list + pls->bEOFUnlocked = (iprev == pls->iUnlockedSentence); + pls->flwaitSentence = gpGlobals->time + DOOR_SENTENCEWAIT; + } + } +} + +// +// Cache user-entity-field values until spawn is called. +// + +void CBaseDoor::KeyValue( KeyValueData *pkvd ) +{ + + if (FStrEq(pkvd->szKeyName, "skin"))//skin is used for content type + { + pev->skin = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "movesnd")) + { + m_bMoveSnd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "stopsnd")) + { + m_bStopSnd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "healthvalue")) + { + m_bHealthValue = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sound")) + { + m_bLockedSound = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_sentence")) + { + m_bLockedSentence = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sound")) + { + m_bUnlockedSound = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlocked_sentence")) + { + m_bUnlockedSentence = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "immediatemode")) + { + m_iImmediateMode = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "onoffmode")) + { + m_iOnOffMode = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "directuse")) + { + m_iDirectUse = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "WaveHeight")) + { + pev->scale = atof(pkvd->szValue) * (1.0/8.0); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +/*QUAKED func_door (0 .5 .8) ? START_OPEN x DOOR_DONT_LINK TOGGLE +if two doors touch, they are assumed to be connected and operate as a unit. + +TOGGLE causes the door to wait in both the start and end states for a trigger event. + +START_OPEN causes the door to move to its destination when spawned, and operate in reverse. +It is used to temporarily or permanently close off an area when triggered (not usefull for +touch or takedamage doors). + +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote button or trigger + field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"lip" lip remaining at end of move (8 default) +"dmg" damage to inflict when blocked (2 default) +"sounds" +0) no sound +1) stone +2) base +3) stone chain +4) screechy metal +*/ + +LINK_ENTITY_TO_CLASS( func_door, CBaseDoor ); +// +// func_water - same as a door. +// +LINK_ENTITY_TO_CLASS( func_water, CBaseDoor ); + +//MH this new spawn function messes up SF_DOOR_START_OPEN +// so replace it with the old one (below) +/* +void CBaseDoor::Spawn( ) +{ + Precache(); + SetMovedir (pev); + + if ( pev->skin == 0 ) + {//normal door + if ( FBitSet (pev->spawnflags, SF_DOOR_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + } + else + {// special contents + pev->solid = SOLID_NOT; + SetBits( pev->spawnflags, SF_DOOR_SILENT ); // water is silent for now + } + + pev->movetype = MOVETYPE_PUSH; + SET_MODEL( ENT(pev), STRING(pev->model) ); + UTIL_SetOrigin(this, pev->origin); + + if (pev->speed == 0) + pev->speed = 100; + + m_vecPosition1 = pev->origin; + // Subtract 2 from size because the engine expands bboxes by 1 in all directions making the size too big + m_vecPosition2 = m_vecPosition1 + (pev->movedir * (fabs( pev->movedir.x * (pev->size.x-2) ) + fabs( pev->movedir.y * (pev->size.y-2) ) + fabs( pev->movedir.z * (pev->size.z-2) ) - m_flLip)); + ASSERTSZ(m_vecPosition1 != m_vecPosition2, "door start/end positions are equal"); + if ( FBitSet (pev->spawnflags, SF_DOOR_START_OPEN) ) + { // swap pos1 and pos2, put door at pos2 + UTIL_SetOrigin(this, m_vecPosition2); + m_vecPosition2 = m_vecPosition1; + m_vecPosition1 = pev->origin; + } + + m_toggle_state = TS_AT_BOTTOM; + + // if the door is flagged for USE button activation only, use NULL touch function + // (unless it's overridden, of course- LRC) + if ( FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) && + !FBitSet ( pev->spawnflags, SF_DOOR_FORCETOUCHABLE )) + { + SetTouch ( NULL ); + } + else // touchable button + SetTouch(&CBaseDoor:: DoorTouch ); +} +*/ + +//standard Spirit 1.0 spawn function +void CBaseDoor::Spawn( ) +{ + Precache(); + SetMovedir (pev); + + if ( pev->skin == 0 ) + {//normal door + if ( FBitSet (pev->spawnflags, SF_DOOR_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + } + else + {// special contents + pev->solid = SOLID_NOT; + SetBits( pev->spawnflags, SF_DOOR_SILENT ); // water is silent for now + } + + pev->movetype = MOVETYPE_PUSH; + SET_MODEL( ENT(pev), STRING(pev->model) ); + UTIL_SetOrigin(this, pev->origin); + + if (pev->speed == 0) + pev->speed = 100; + + m_toggle_state = TS_AT_BOTTOM; + + // if the door is flagged for USE button activation only, use NULL touch function + // (unless it's overridden, of course- LRC) + if ( FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) && + !FBitSet ( pev->spawnflags, SF_DOOR_FORCETOUCHABLE )) + { + SetTouch ( NULL ); + } + else // touchable button + SetTouch( DoorTouch ); +} +//END + +//LRC +void CBaseDoor :: PostSpawn( void ) +{ + if (m_pMoveWith) + m_vecPosition1 = pev->origin - m_pMoveWith->pev->origin; + else + m_vecPosition1 = pev->origin; + + // Subtract 2 from size because the engine expands bboxes by 1 in all directions + m_vecPosition2 = m_vecPosition1 + (pev->movedir * (fabs( pev->movedir.x * (pev->size.x-2) ) + fabs( pev->movedir.y * (pev->size.y-2) ) + fabs( pev->movedir.z * (pev->size.z-2) ) - m_flLip)); + + ASSERTSZ(m_vecPosition1 != m_vecPosition2, "door start/end positions are equal"); + if ( FBitSet (pev->spawnflags, SF_DOOR_START_OPEN) ) + { // swap pos1 and pos2, put door at pos2 + if (m_pMoveWith) + { + m_vecSpawnOffset = m_vecSpawnOffset + (m_vecPosition2 + m_pMoveWith->pev->origin) - pev->origin; + UTIL_AssignOrigin(this, m_vecPosition2 + m_pMoveWith->pev->origin); + } + else + { + m_vecSpawnOffset = m_vecSpawnOffset + m_vecPosition2 - pev->origin; + UTIL_AssignOrigin(this, m_vecPosition2); + } + Vector vecTemp = m_vecPosition2; + m_vecPosition2 = m_vecPosition1; + m_vecPosition1 = vecTemp; +// ALERT(at_console, "func_door postspawn: origin %f %f %f\n", pev->origin.x, pev->origin.y, pev->origin.z); + } +} + +//void CBaseDoor :: PostMoveWith( void ) +//{ +// Vector vecTemp = m_vecPosition1 - m_pMoveWith->m_vecSpawnOffset; +// ALERT(at_console, "door %s pmw: pos1 changes from (%f %f %f) to (%f %f %f)\n", STRING(pev->targetname), m_vecPosition1.x, m_vecPosition1.y, m_vecPosition1.z, vecTemp.x, vecTemp.y, vecTemp.z); +// m_vecPosition1 = m_vecPosition1 - m_pMoveWith->m_vecSpawnOffset; +// m_vecPosition2 = m_vecPosition2 - m_pMoveWith->m_vecSpawnOffset; +//} + +void CBaseDoor :: SetToggleState( int state ) +{ + if ( state == TS_AT_TOP ) + { + if (m_pMoveWith) + UTIL_AssignOrigin( this, m_vecPosition2 + m_pMoveWith->pev->origin); + else + UTIL_AssignOrigin( this, m_vecPosition2 ); + } + else + { + if (m_pMoveWith) + UTIL_AssignOrigin( this, m_vecPosition1 + m_pMoveWith->pev->origin); + else + UTIL_AssignOrigin( this, m_vecPosition1 ); + } +} + + +void CBaseDoor::Precache( void ) +{ + char *pszSound; + +// set the door's "in-motion" sound + switch (m_bMoveSnd) + { + case 0: + pev->noiseMoving = MAKE_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("doors/doormove1.wav"); + pev->noiseMoving = MAKE_STRING("doors/doormove1.wav"); + break; + case 2: + PRECACHE_SOUND ("doors/doormove2.wav"); + pev->noiseMoving = MAKE_STRING("doors/doormove2.wav"); + break; + case 3: + PRECACHE_SOUND ("doors/doormove3.wav"); + pev->noiseMoving = MAKE_STRING("doors/doormove3.wav"); + break; + case 4: + PRECACHE_SOUND ("doors/doormove4.wav"); + pev->noiseMoving = MAKE_STRING("doors/doormove4.wav"); + break; + case 5: + PRECACHE_SOUND ("doors/doormove5.wav"); + pev->noiseMoving = MAKE_STRING("doors/doormove5.wav"); + break; + case 6: + PRECACHE_SOUND ("doors/doormove6.wav"); + pev->noiseMoving = MAKE_STRING("doors/doormove6.wav"); + break; + case 7: + PRECACHE_SOUND ("doors/doormove7.wav"); + pev->noiseMoving = MAKE_STRING("doors/doormove7.wav"); + break; + case 8: + PRECACHE_SOUND ("doors/doormove8.wav"); + pev->noiseMoving = MAKE_STRING("doors/doormove8.wav"); + break; + case 9: + PRECACHE_SOUND ("doors/doormove9.wav"); + pev->noiseMoving = MAKE_STRING("doors/doormove9.wav"); + break; + case 10: + PRECACHE_SOUND ("doors/doormove10.wav"); + pev->noiseMoving = MAKE_STRING("doors/doormove10.wav"); + break; + default: + pev->noiseMoving = MAKE_STRING("common/null.wav"); + break; + } + +// set the door's 'reached destination' stop sound + switch (m_bStopSnd) + { + case 0: + pev->noiseArrived = MAKE_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("doors/doorstop1.wav"); + pev->noiseArrived = MAKE_STRING("doors/doorstop1.wav"); + break; + case 2: + PRECACHE_SOUND ("doors/doorstop2.wav"); + pev->noiseArrived = MAKE_STRING("doors/doorstop2.wav"); + break; + case 3: + PRECACHE_SOUND ("doors/doorstop3.wav"); + pev->noiseArrived = MAKE_STRING("doors/doorstop3.wav"); + break; + case 4: + PRECACHE_SOUND ("doors/doorstop4.wav"); + pev->noiseArrived = MAKE_STRING("doors/doorstop4.wav"); + break; + case 5: + PRECACHE_SOUND ("doors/doorstop5.wav"); + pev->noiseArrived = MAKE_STRING("doors/doorstop5.wav"); + break; + case 6: + PRECACHE_SOUND ("doors/doorstop6.wav"); + pev->noiseArrived = MAKE_STRING("doors/doorstop6.wav"); + break; + case 7: + PRECACHE_SOUND ("doors/doorstop7.wav"); + pev->noiseArrived = MAKE_STRING("doors/doorstop7.wav"); + break; + case 8: + PRECACHE_SOUND ("doors/doorstop8.wav"); + pev->noiseArrived = MAKE_STRING("doors/doorstop8.wav"); + break; + default: + pev->noiseArrived = MAKE_STRING("common/null.wav"); + break; + } + + // get door button sounds, for doors which are directly 'touched' to open + + if (m_bLockedSound) + { + pszSound = ButtonSound( (int)m_bLockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sLockedSound = ALLOC_STRING(pszSound); + } + + if (m_bUnlockedSound) + { + pszSound = ButtonSound( (int)m_bUnlockedSound ); + PRECACHE_SOUND(pszSound); + m_ls.sUnlockedSound = ALLOC_STRING(pszSound); + } + + // get sentence group names, for doors which are directly 'touched' to open + + switch (m_bLockedSentence) + { + case 1: m_ls.sLockedSentence = MAKE_STRING("NA"); break; // access denied + case 2: m_ls.sLockedSentence = MAKE_STRING("ND"); break; // security lockout + case 3: m_ls.sLockedSentence = MAKE_STRING("NF"); break; // blast door + case 4: m_ls.sLockedSentence = MAKE_STRING("NFIRE"); break; // fire door + case 5: m_ls.sLockedSentence = MAKE_STRING("NCHEM"); break; // chemical door + case 6: m_ls.sLockedSentence = MAKE_STRING("NRAD"); break; // radiation door + case 7: m_ls.sLockedSentence = MAKE_STRING("NCON"); break; // gen containment + case 8: m_ls.sLockedSentence = MAKE_STRING("NH"); break; // maintenance door + case 9: m_ls.sLockedSentence = MAKE_STRING("NG"); break; // broken door + + default: m_ls.sLockedSentence = 0; break; + } + + switch (m_bUnlockedSentence) + { + case 1: m_ls.sUnlockedSentence = MAKE_STRING("EA"); break; // access granted + case 2: m_ls.sUnlockedSentence = MAKE_STRING("ED"); break; // security door + case 3: m_ls.sUnlockedSentence = MAKE_STRING("EF"); break; // blast door + case 4: m_ls.sUnlockedSentence = MAKE_STRING("EFIRE"); break; // fire door + case 5: m_ls.sUnlockedSentence = MAKE_STRING("ECHEM"); break; // chemical door + case 6: m_ls.sUnlockedSentence = MAKE_STRING("ERAD"); break; // radiation door + case 7: m_ls.sUnlockedSentence = MAKE_STRING("ECON"); break; // gen containment + case 8: m_ls.sUnlockedSentence = MAKE_STRING("EH"); break; // maintenance door + + default: m_ls.sUnlockedSentence = 0; break; + } +} + +// +// Doors not tied to anything (e.g. button, another door) can be touched, to make them activate. +// +void CBaseDoor::DoorTouch( CBaseEntity *pOther ) +{ + entvars_t* pevToucher = pOther->pev; + + // Ignore touches by anything but players + if (!FClassnameIs(pevToucher, "player")) + return; + + // If door has master, and it's not ready to trigger, + // play 'locked' sound + + if (m_sMaster && !UTIL_IsMasterTriggered(m_sMaster, pOther)) + PlayLockSounds(pev, &m_ls, TRUE, FALSE); + + // If door is somebody's target, then touching does nothing. + // You have to activate the owner (e.g. button). + //LRC- allow flags to override this + if (!FStringNull(pev->targetname) && !FBitSet(pev->spawnflags,SF_DOOR_FORCETOUCHABLE)) + { + // play locked sound + PlayLockSounds(pev, &m_ls, TRUE, FALSE); + return; + } + + m_hActivator = pOther;// remember who activated the door + + if (DoorActivate( )) + SetTouch( NULL ); // Temporarily disable the touch function, until movement is finished. +} + + +// +// Used by SUB_UseTargets, when a door is the target of a button. +// +void CBaseDoor::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_hActivator = pActivator; + + if (!UTIL_IsMasterTriggered(m_sMaster, pActivator)) + return; + + //LRC: + if (m_iOnOffMode) + { + if (useType == USE_ON) + { + if (m_toggle_state == TS_AT_BOTTOM) + { + PlayLockSounds(pev, &m_ls, FALSE, FALSE); + DoorGoUp(); + } + return; + } + else if (useType == USE_OFF) + { + if (m_toggle_state == TS_AT_TOP) + { + DoorGoDown(); + } + return; + } + } + + // if not ready to be used, ignore "use" command. + if (m_toggle_state == TS_AT_TOP) + { + if (!FBitSet(pev->spawnflags, SF_DOOR_NO_AUTO_RETURN)) + return; + } + else if (m_toggle_state != TS_AT_BOTTOM) + return; + + DoorActivate(); +} + +// +// Causes the door to "do its thing", i.e. start moving, and cascade activation. +// +int CBaseDoor::DoorActivate( ) +{ + if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + return 0; + + if (FBitSet(pev->spawnflags, SF_DOOR_NO_AUTO_RETURN) && m_toggle_state == TS_AT_TOP) + {// door should close + DoorGoDown(); + } + else + {// door should open + + if ( m_hActivator != NULL && m_hActivator->IsPlayer() ) + {// give health if player opened the door (medikit) + // VARS( m_eoActivator )->health += m_bHealthValue; + + m_hActivator->TakeHealth( m_bHealthValue, DMG_GENERIC ); + + } + + // play door unlock sounds + PlayLockSounds(pev, &m_ls, FALSE, FALSE); + + DoorGoUp(); + } + + return 1; +} + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); + +// +// Starts the door going to its "up" position (simply ToggleData->vecPosition2). +// +void CBaseDoor::DoorGoUp( void ) +{ + entvars_t *pevActivator; + + // It could be going-down, if blocked. + ASSERT(m_toggle_state == TS_AT_BOTTOM || m_toggle_state == TS_GOING_DOWN); + + // emit door moving and stop sounds on CHAN_STATIC so that the multicast doesn't + // filter them out and leave a client stuck with looping door sounds! + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving), 1, ATTN_NORM); + +// ALERT(at_debug, "%s go up (was %d)\n", STRING(pev->targetname), m_toggle_state); + m_toggle_state = TS_GOING_UP; + + SetMoveDone(&CBaseDoor:: DoorHitTop ); + + // LRC- if synched, we fire as soon as we start to go up + if (m_iImmediateMode) + { + if (m_iOnOffMode) + SUB_UseTargets( m_hActivator, USE_ON, 0 ); + else + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + } + + if ( FClassnameIs(pev, "func_door_rotating")) // !!! BUGBUG Triggered doors don't work with this yet + { + float sign = 1.0; + + if ( m_hActivator != NULL ) + { + pevActivator = m_hActivator->pev; + + if ( !FBitSet( pev->spawnflags, SF_DOOR_ONEWAY ) && pev->movedir.y ) // Y axis rotation, move away from the player + { + Vector vec = pevActivator->origin - pev->origin; + Vector angles = pevActivator->angles; + angles.x = 0; + angles.z = 0; + UTIL_MakeVectors (angles); + // Vector vnext = (pevToucher->origin + (pevToucher->velocity * 10)) - pev->origin; + UTIL_MakeVectors ( pevActivator->angles ); + Vector vnext = (pevActivator->origin + (gpGlobals->v_forward * 10)) - pev->origin; + if ( (vec.x*vnext.y - vec.y*vnext.x) < 0 ) + sign = -1.0; + } + } + AngularMove(m_vecAngle2*sign, pev->speed); + } + else + LinearMove(m_vecPosition2, pev->speed); +} + + +// +// The door has reached the "up" position. Either go back down, or wait for another activation. +// +void CBaseDoor::DoorHitTop( void ) +{ + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + { + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving) ); + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseArrived), 1, ATTN_NORM); + } + +// ALERT(at_debug, "%s hit top\n", STRING(pev->targetname)); + ASSERT(m_toggle_state == TS_GOING_UP); + m_toggle_state = TS_AT_TOP; + + // toggle-doors don't come down automatically, they wait for refire. + if (FBitSet(pev->spawnflags, SF_DOOR_NO_AUTO_RETURN)) + { + // Re-instate touch method, movement is complete + if ( !FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) || + FBitSet ( pev->spawnflags, SF_DOOR_FORCETOUCHABLE ) ) + SetTouch(&CBaseDoor:: DoorTouch ); + } + else + { + // In flWait seconds, DoorGoDown will fire, unless wait is -1, then door stays open + SetNextThink( m_flWait ); + SetThink(&CBaseDoor:: DoorGoDown ); + + if ( m_flWait == -1 ) + { + DontThink(); + } + } + + // Fire the close target (if startopen is set, then "top" is closed) - netname is the close target + if (pev->spawnflags & SF_DOOR_START_OPEN) + { + if (pev->netname) + FireTargets( STRING(pev->netname), m_hActivator, this, USE_TOGGLE, 0 ); + } + else + { + if (pev->message) + FireTargets( STRING(pev->message), m_hActivator, this, USE_TOGGLE, 0 ); + } + + // LRC + if (!m_iImmediateMode) + { + if (m_iOnOffMode) + SUB_UseTargets( m_hActivator, USE_OFF, 0 ); + else + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + } +} + + +// +// Starts the door going to its "down" position (simply ToggleData->vecPosition1). +// +void CBaseDoor::DoorGoDown( void ) +{ + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving), 1, ATTN_NORM); + +// ALERT(at_debug, "%s go down (was %d)\n", STRING(pev->targetname), m_toggle_state); + +//FYI: not defined, so this doesn't happen. --LRC +#ifdef DOOR_ASSERT + ASSERT(m_toggle_state == TS_AT_TOP); +#endif // DOOR_ASSERT + m_toggle_state = TS_GOING_DOWN; + + SetMoveDone(&CBaseDoor:: DoorHitBottom ); + if ( FClassnameIs(pev, "func_door_rotating"))//rotating door + { + // LRC- if synched, we fire as soon as we start to go down + if (m_iImmediateMode) + { + if (m_iOnOffMode) + SUB_UseTargets( m_hActivator, USE_OFF, 0 ); + else + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + } + AngularMove( m_vecAngle1, pev->speed); + } + else + { + // LRC- if synched, we fire as soon as we start to go down + if (m_iImmediateMode) + { + SUB_UseTargets( m_hActivator, USE_OFF, 0 ); + } + LinearMove( m_vecPosition1, pev->speed); + } +} + +// +// The door has reached the "down" position. Back to quiescence. +// +void CBaseDoor::DoorHitBottom( void ) +{ + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + { + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving) ); + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseArrived), 1, ATTN_NORM); + } + +// ALERT(at_debug, "%s hit bottom\n", STRING(pev->targetname)); + ASSERT(m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_AT_BOTTOM; + + // Re-instate touch method, cycle is complete + if ( FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) && + !FBitSet ( pev->spawnflags, SF_DOOR_FORCETOUCHABLE ) ) + {// use only door + SetTouch ( NULL ); + } + else // touchable door + SetTouch(&CBaseDoor:: DoorTouch ); + + // Fire the close target (if startopen is set, then "top" is closed) - netname is the close target + // LRC- 'message' is the open target + if (pev->spawnflags & SF_DOOR_START_OPEN) + { + if (pev->message) + FireTargets( STRING(pev->message), m_hActivator, this, USE_TOGGLE, 0 ); + } + else + { + if (pev->netname) + FireTargets( STRING(pev->netname), m_hActivator, this, USE_TOGGLE, 0 ); + } +// else +// { +// ALERT(at_console,"didn't fire closetarget because "); +// if (!(pev->netname)) +// ALERT(at_console,"no netname\n"); +// else if (pev->spawnflags & SF_DOOR_START_OPEN) +// ALERT(at_console,"startopen\n"); +// else +// ALERT(at_console,"!!?!\n"); +// } + + // LRC- if synched, don't fire now + if (!m_iImmediateMode) + { + if (m_iOnOffMode) + SUB_UseTargets( m_hActivator, USE_ON, 0 ); + else + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + } +} + +void CBaseDoor::Blocked( CBaseEntity *pOther ) +{ + CBaseEntity *pTarget = NULL; + CBaseDoor *pDoor = NULL; + +// ALERT(at_debug, "%s blocked\n", STRING(pev->targetname)); + + // Hurt the blocker a little. + if ( pev->dmg ) + pOther->TakeDamage( pev, pev, pev->dmg, DMG_CRUSH ); + + // if a door has a negative wait, it would never come back if blocked, + // so let it just squash the object to death real fast + + if (m_flWait >= 0) + { + //LRC - thanks to [insert name here] for this + if ( !FBitSet( pev->spawnflags, SF_DOOR_SILENT ) ) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving) ); + if (m_toggle_state == TS_GOING_DOWN) + { + DoorGoUp(); + } + else + { + DoorGoDown(); + } + } + + // Block all door pieces with the same targetname here. + //LRC - in immediate mode don't do this, doors are expected to do it themselves. + if ( !m_iImmediateMode && !FStringNull ( pev->targetname ) ) + { + for (;;) + { + pTarget = UTIL_FindEntityByTargetname(pTarget, STRING(pev->targetname)); + + if ( !pTarget ) + break; + + if ( VARS( pTarget->pev ) != pev && FClassnameIs ( pTarget->pev, "func_door" ) || + FClassnameIs ( pTarget->pev, "func_door_rotating" ) ) + { + pDoor = GetClassPtr( (CBaseDoor *) VARS(pTarget->pev) ); + if ( pDoor->m_flWait >= 0) + { + // avelocity == velocity!? LRC + if (pDoor->pev->velocity == pev->velocity && pDoor->pev->avelocity == pev->velocity) + { + // this is the most hacked, evil, bastardized thing I've ever seen. kjb + if ( FClassnameIs ( pTarget->pev, "func_door" ) ) + {// set origin to realign normal doors + pDoor->pev->origin = pev->origin; + UTIL_SetVelocity(pDoor, g_vecZero);// stop! + } + else + {// set angles to realign rotating doors + pDoor->pev->angles = pev->angles; + UTIL_SetAvelocity(pDoor, g_vecZero); + } + } + if ( pDoor->m_toggle_state == TS_GOING_DOWN) + pDoor->DoorGoUp(); + else + pDoor->DoorGoDown(); + } + } + } + } +} + + +/*QUAKED FuncRotDoorSpawn (0 .5 .8) ? START_OPEN REVERSE +DOOR_DONT_LINK TOGGLE X_AXIS Y_AXIS +if two doors touch, they are assumed to be connected and operate as +a unit. + +TOGGLE causes the door to wait in both the start and end states for +a trigger event. + +START_OPEN causes the door to move to its destination when spawned, +and operate in reverse. It is used to temporarily or permanently +close off an area when triggered (not usefull for touch or +takedamage doors). + +You need to have an origin brush as part of this entity. The +center of that brush will be +the point around which it is rotated. It will rotate around the Z +axis by default. You can +check either the X_AXIS or Y_AXIS box to change that. + +"distance" is how many degrees the door will be rotated. +"speed" determines how fast the door moves; default value is 100. + +REVERSE will cause the door to rotate in the opposite direction. + +"angle" determines the opening direction +"targetname" if set, no touch field will be spawned and a remote +button or trigger field activates the door. +"health" if set, door must be shot open +"speed" movement speed (100 default) +"wait" wait before returning (3 default, -1 = never return) +"dmg" damage to inflict when blocked (2 default) +"sounds" +0) no sound +1) stone +2) base +3) stone chain +4) screechy metal +*/ +class CRotDoor : public CBaseDoor +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + virtual void PostSpawn( void ) {} // don't use the moveWith fix from CBaseDoor + virtual void SetToggleState( int state ); +}; + +LINK_ENTITY_TO_CLASS( func_door_rotating, CRotDoor ); + + +void CRotDoor::Spawn( void ) +{ + Precache(); + // set the axis of rotation + CBaseToggle::AxisDir( pev ); + + // check for clockwise rotation + if ( FBitSet (pev->spawnflags, SF_DOOR_ROTATE_BACKWARDS) ) + pev->movedir = pev->movedir * -1; + + //m_flWait = 2; who the hell did this? (sjb) + m_vecAngle1 = pev->angles; + m_vecAngle2 = pev->angles + pev->movedir * m_flMoveDistance; + + ASSERTSZ(m_vecAngle1 != m_vecAngle2, "rotating door start/end positions are equal"); + + if ( FBitSet (pev->spawnflags, SF_DOOR_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + + pev->movetype = MOVETYPE_PUSH; + UTIL_SetOrigin(this, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + if (pev->speed == 0) + pev->speed = 100; + +// DOOR_START_OPEN is to allow an entity to be lighted in the closed position +// but spawn in the open position + if ( FBitSet (pev->spawnflags, SF_DOOR_START_OPEN) ) + { // swap pos1 and pos2, put door at pos2, invert movement direction + pev->angles = m_vecAngle2; + Vector vecSav = m_vecAngle1; + m_vecAngle2 = m_vecAngle1; + m_vecAngle1 = vecSav; + pev->movedir = pev->movedir * -1; + } + + m_toggle_state = TS_AT_BOTTOM; + + if ( FBitSet ( pev->spawnflags, SF_DOOR_USE_ONLY ) && !FBitSet(pev->spawnflags, SF_DOOR_FORCETOUCHABLE) ) + { + SetTouch ( NULL ); + } + else // touchable button + SetTouch(&CRotDoor:: DoorTouch ); +} + +void CRotDoor::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "axes")) + { + UTIL_StringToVector( (float*)(pev->movedir), pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseDoor::KeyValue( pkvd ); +} + +void CRotDoor :: SetToggleState( int state ) +{ + if ( state == TS_AT_TOP ) + pev->angles = m_vecAngle2; + else + pev->angles = m_vecAngle1; + + UTIL_SetOrigin( this, pev->origin ); +} + + +#define SF_MOMDOOR_MOVESTART = 0x80000000 + +class CMomentaryDoor : public CBaseToggle +{ +public: + void Spawn( void ); + void Precache( void ); + void EXPORT MomentaryMoveDone( void ); + + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return CBaseToggle :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + BYTE m_bMoveSnd; // sound a door makes while moving + STATE m_iState; + float m_fLastPos; + + STATE GetState( void ) { return m_iState; } + float CalcRatio( CBaseEntity *pLocus ) { return m_fLastPos; } +}; + +LINK_ENTITY_TO_CLASS( momentary_door, CMomentaryDoor ); + +TYPEDESCRIPTION CMomentaryDoor::m_SaveData[] = +{ + DEFINE_FIELD( CMomentaryDoor, m_bMoveSnd, FIELD_CHARACTER ), + DEFINE_FIELD( CMomentaryDoor, m_iState, FIELD_INTEGER ), + DEFINE_FIELD( CMomentaryDoor, m_fLastPos, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CMomentaryDoor, CBaseToggle ); + +void CMomentaryDoor::Spawn( void ) +{ + SetMovedir (pev); + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(this, pev->origin); + SET_MODEL( ENT(pev), STRING(pev->model) ); + +// if (pev->speed == 0) +// pev->speed = 100; + if (pev->dmg == 0) + pev->dmg = 2; + + m_iState = STATE_OFF; + + m_vecPosition1 = pev->origin; + // Subtract 2 from size because the engine expands bboxes by 1 in all directions making the size too big + m_vecPosition2 = m_vecPosition1 + (pev->movedir * (fabs( pev->movedir.x * (pev->size.x-2) ) + fabs( pev->movedir.y * (pev->size.y-2) ) + fabs( pev->movedir.z * (pev->size.z-2) ) - m_flLip)); + ASSERTSZ(m_vecPosition1 != m_vecPosition2, "door start/end positions are equal"); + + //LRC: FIXME, move to PostSpawn + if ( FBitSet (pev->spawnflags, SF_DOOR_START_OPEN) ) + { // swap pos1 and pos2, put door at pos2 + UTIL_AssignOrigin(this, m_vecPosition2); + Vector vecTemp = m_vecPosition2; + m_vecPosition2 = m_vecPosition1; + m_vecPosition1 = vecTemp; + } + + if (m_pMoveWith) + { + m_vecPosition1 = m_vecPosition1 - m_pMoveWith->pev->origin; + m_vecPosition2 = m_vecPosition2 - m_pMoveWith->pev->origin; + } + + SetTouch( NULL ); + + Precache(); +} + +void CMomentaryDoor::Precache( void ) +{ + +// set the door's "in-motion" sound + switch (m_bMoveSnd) + { + case 0: + pev->noiseMoving = MAKE_STRING("common/null.wav"); + break; + case 1: + PRECACHE_SOUND ("doors/doormove1.wav"); + pev->noiseMoving = MAKE_STRING("doors/doormove1.wav"); + break; + case 2: + PRECACHE_SOUND ("doors/doormove2.wav"); + pev->noiseMoving = MAKE_STRING("doors/doormove2.wav"); + break; + case 3: + PRECACHE_SOUND ("doors/doormove3.wav"); + pev->noiseMoving = MAKE_STRING("doors/doormove3.wav"); + break; + case 4: + PRECACHE_SOUND ("doors/doormove4.wav"); + pev->noiseMoving = MAKE_STRING("doors/doormove4.wav"); + break; + case 5: + PRECACHE_SOUND ("doors/doormove5.wav"); + pev->noiseMoving = MAKE_STRING("doors/doormove5.wav"); + break; + case 6: + PRECACHE_SOUND ("doors/doormove6.wav"); + pev->noiseMoving = MAKE_STRING("doors/doormove6.wav"); + break; + case 7: + PRECACHE_SOUND ("doors/doormove7.wav"); + pev->noiseMoving = MAKE_STRING("doors/doormove7.wav"); + break; + case 8: + PRECACHE_SOUND ("doors/doormove8.wav"); + pev->noiseMoving = MAKE_STRING("doors/doormove8.wav"); + break; + default: + pev->noiseMoving = MAKE_STRING("common/null.wav"); + break; + } +} + +void CMomentaryDoor::KeyValue( KeyValueData *pkvd ) +{ + + if (FStrEq(pkvd->szKeyName, "movesnd")) + { + m_bMoveSnd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "stopsnd")) + { +// m_bStopSnd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "healthvalue")) + { +// m_bHealthValue = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CMomentaryDoor::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( useType != USE_SET ) // Momentary buttons will pass down a float in here + return; + + if ( value > 1.0 ) + value = 1.0; + + if (IsLockedByMaster()) return; + + Vector move = m_vecPosition1 + (value * (m_vecPosition2 - m_vecPosition1)); + + float speed = 0; + if (pev->speed) + { + //LRC- move at the given speed, if any. + speed = pev->speed; + } + else + { + // default: get there in 0.1 secs + Vector delta; + delta = move - pev->origin; + + speed = delta.Length() * 10; + } + + + //FIXME: allow for it being told to move at the same speed in the _opposite_ direction! + if ( speed != 0 ) + { + // This entity only thinks when it moves + //LRC- nope, in a MoveWith world you can't rely on that. Check the state instead. + if ( m_iState == STATE_OFF ) + { + //ALERT(at_console,"USE: start moving to %f %f %f.\n", move.x, move.y, move.z); + m_iState = STATE_ON; + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving), 1, ATTN_NORM); + } + + m_fLastPos = value; + LinearMove( move, speed ); + SetMoveDone(&CMomentaryDoor:: MomentaryMoveDone ); + } +} + +void CMomentaryDoor::MomentaryMoveDone( void ) +{ + m_iState = STATE_OFF; + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMoving)); +} diff --git a/dlls/doors.h b/dlls/doors.h new file mode 100644 index 0000000..611cbba --- /dev/null +++ b/dlls/doors.h @@ -0,0 +1,38 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef DOORS_H +#define DOORS_H + +// doors +#define SF_DOOR_ROTATE_Y 0 +#define SF_DOOR_START_OPEN 1 +#define SF_DOOR_ROTATE_BACKWARDS 2 +#define SF_DOOR_PASSABLE 8 +#define SF_DOOR_ONEWAY 16 +#define SF_DOOR_NO_AUTO_RETURN 32 +#define SF_DOOR_ROTATE_Z 64 +#define SF_DOOR_ROTATE_X 128 +#define SF_DOOR_USE_ONLY 256 // door must be opened by player's use button. +#define SF_DOOR_NOMONSTERS 512 // Monster can't open +#define SF_DOOR_FORCETOUCHABLE 1024 //LRC- Opens when touched, even though it's named and/or "use only" +//LRC - clashes with 'not in deathmatch'. Replaced with 'Target mode' and 'On/Off Mode' fields. +//#define SF_DOOR_SYNCHED 2048 //LRC- sends USE_ON/OFF when it starts to open/close (instead of sending + // USE_TOGGLE when fully open/closed); also responds to USE_ON and USE_OFF + // 'correctly'. +#define SF_DOOR_SILENT 0x80000000 + + + +#endif //DOORS_H diff --git a/dlls/effects.cpp b/dlls/effects.cpp new file mode 100644 index 0000000..e8fe4e7 --- /dev/null +++ b/dlls/effects.cpp @@ -0,0 +1,5217 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "customentity.h" +#include "effects.h" +#include "weapons.h" +#include "decals.h" +#include "game.h" +#include "func_break.h" +#include "shake.h" +#include "player.h" //LRC - footstep stuff +#include "locus.h" //LRC - locus utilities +#include "movewith.h" //LRC - the DesiredThink system +#include + +#define SF_GIBSHOOTER_REPEATABLE 1 // allows a gibshooter to be refired + +#define SF_FUNNEL_REVERSE 1 // funnel effect repels particles instead of attracting them. +#define SF_FUNNEL_REPEATABLE 2 // allows a funnel to be refired + + +//LRC - make info_target an entity class in its own right +class CInfoTarget : public CPointEntity +{ +public: + void Spawn( void ); + void Precache( void ); +}; + +LINK_ENTITY_TO_CLASS( info_target, CInfoTarget ); + +//LRC- force an info_target to use the sprite null.spr +#define SF_TARGET_HACK_VISIBLE 1 + +// Landmark class +void CInfoTarget :: Spawn( void ) +{ + //Precache(); + pev->solid = SOLID_NOT; + if (pev->spawnflags & SF_TARGET_HACK_VISIBLE) + { + PRECACHE_MODEL("sprites/null.spr"); + SET_MODEL(ENT(pev),"sprites/null.spr"); + UTIL_SetSize(pev, g_vecZero, g_vecZero); + } +} + +void CInfoTarget :: Precache( void ) +{ + if (pev->spawnflags & SF_TARGET_HACK_VISIBLE) + PRECACHE_MODEL("sprites/null.spr"); +} + + +class CBubbling : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + void EXPORT FizzThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual int ObjectCaps( void ) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + static TYPEDESCRIPTION m_SaveData[]; + + int m_density; + int m_frequency; + int m_bubbleModel; + int m_state; + + virtual STATE GetState( void ) { return m_state?STATE_ON:STATE_OFF; }; +}; + +LINK_ENTITY_TO_CLASS( env_bubbles, CBubbling ); + +TYPEDESCRIPTION CBubbling::m_SaveData[] = +{ + DEFINE_FIELD( CBubbling, m_density, FIELD_INTEGER ), + DEFINE_FIELD( CBubbling, m_frequency, FIELD_INTEGER ), + DEFINE_FIELD( CBubbling, m_state, FIELD_INTEGER ), + // Let spawn restore this! + // DEFINE_FIELD( CBubbling, m_bubbleModel, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CBubbling, CBaseEntity ); + + +#define SF_BUBBLES_STARTOFF 0x0001 + +void CBubbling::Spawn( void ) +{ + Precache( ); + SET_MODEL( ENT(pev), STRING(pev->model) ); // Set size + + pev->solid = SOLID_NOT; // Remove model & collisions + pev->renderamt = 0; // The engine won't draw this model if this is set to 0 and blending is on + pev->rendermode = kRenderTransTexture; + int speed = pev->speed > 0 ? pev->speed : -pev->speed; + + // HACKHACK!!! - Speed in rendercolor + pev->rendercolor.x = speed >> 8; + pev->rendercolor.y = speed & 255; + pev->rendercolor.z = (pev->speed < 0) ? 1 : 0; + + + if ( !(pev->spawnflags & SF_BUBBLES_STARTOFF) ) + { + SetThink(&CBubbling:: FizzThink ); + SetNextThink( 2.0 ); + m_state = 1; + } + else + m_state = 0; +} + +void CBubbling::Precache( void ) +{ + m_bubbleModel = PRECACHE_MODEL("sprites/bubble.spr"); // Precache bubble sprite +} + + +void CBubbling::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( ShouldToggle( useType, m_state ) ) + m_state = !m_state; + + if ( m_state ) + { + SetThink(&CBubbling:: FizzThink ); + SetNextThink( 0.1 ); + } + else + { + SetThink( NULL ); + DontThink(); + } +} + + +void CBubbling::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "density")) + { + m_density = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "frequency")) + { + m_frequency = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "current")) + { + pev->speed = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +void CBubbling::FizzThink( void ) +{ + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, VecBModelOrigin(pev) ); + WRITE_BYTE( TE_FIZZ ); + WRITE_SHORT( (short)ENTINDEX( edict() ) ); + WRITE_SHORT( (short)m_bubbleModel ); + WRITE_BYTE( m_density ); + MESSAGE_END(); + + if ( m_frequency > 19 ) // frequencies above 20 are treated as 20. + SetNextThink( 0.5 ); + else + SetNextThink( 2.5 - (0.1 * m_frequency) ); +} + +// -------------------------------------------------- +// +// Beams +// +// -------------------------------------------------- + +LINK_ENTITY_TO_CLASS( beam, CBeam ); + +void CBeam::Spawn( void ) +{ + pev->solid = SOLID_NOT; // Remove model & collisions + Precache( ); +} + +void CBeam::Precache( void ) +{ + if ( pev->owner ) + SetStartEntity( ENTINDEX( pev->owner ) ); + if ( pev->aiment ) + SetEndEntity( ENTINDEX( pev->aiment ) ); +} + +void CBeam::SetStartEntity( int entityIndex ) +{ + pev->sequence = (entityIndex & 0x0FFF) | ((pev->sequence&0xF000)<<12); + pev->owner = g_engfuncs.pfnPEntityOfEntIndex( entityIndex ); +} + +void CBeam::SetEndEntity( int entityIndex ) +{ + pev->skin = (entityIndex & 0x0FFF) | ((pev->skin&0xF000)<<12); + pev->aiment = g_engfuncs.pfnPEntityOfEntIndex( entityIndex ); +} + + +// These don't take attachments into account +const Vector &CBeam::GetStartPos( void ) +{ + if ( GetType() == BEAM_ENTS ) + { + edict_t *pent = g_engfuncs.pfnPEntityOfEntIndex( GetStartEntity() ); + return pent->v.origin; + } + return pev->origin; +} + + +const Vector &CBeam::GetEndPos( void ) +{ + int type = GetType(); + if ( type == BEAM_POINTS || type == BEAM_HOSE ) + { + return pev->angles; + } + + edict_t *pent = g_engfuncs.pfnPEntityOfEntIndex( GetEndEntity() ); + if ( pent ) + return pent->v.origin; + return pev->angles; +} + + +CBeam *CBeam::BeamCreate( const char *pSpriteName, int width ) +{ + // Create a new entity with CBeam private data + CBeam *pBeam = GetClassPtr( (CBeam *)NULL ); + pBeam->pev->classname = MAKE_STRING("beam"); + + pBeam->BeamInit( pSpriteName, width ); + + return pBeam; +} + + +void CBeam::BeamInit( const char *pSpriteName, int width ) +{ + pev->flags |= FL_CUSTOMENTITY; + SetColor( 255, 255, 255 ); + SetBrightness( 255 ); + SetNoise( 0 ); + SetFrame( 0 ); + SetScrollRate( 0 ); + pev->model = MAKE_STRING( pSpriteName ); + SetTexture( PRECACHE_MODEL( (char *)pSpriteName ) ); + SetWidth( width ); + pev->skin = 0; + pev->sequence = 0; + pev->rendermode = 0; +} + + +void CBeam::PointsInit( const Vector &start, const Vector &end ) +{ + SetType( BEAM_POINTS ); + SetStartPos( start ); + SetEndPos( end ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + + +void CBeam::HoseInit( const Vector &start, const Vector &direction ) +{ + SetType( BEAM_HOSE ); + SetStartPos( start ); + SetEndPos( direction ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + + +void CBeam::PointEntInit( const Vector &start, int endIndex ) +{ + SetType( BEAM_ENTPOINT ); + SetStartPos( start ); + SetEndEntity( endIndex ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + +void CBeam::EntsInit( int startIndex, int endIndex ) +{ + SetType( BEAM_ENTS ); + SetStartEntity( startIndex ); + SetEndEntity( endIndex ); + SetStartAttachment( 0 ); + SetEndAttachment( 0 ); + RelinkBeam(); +} + + +void CBeam::RelinkBeam( void ) +{ + const Vector &startPos = GetStartPos(), &endPos = GetEndPos(); + + pev->mins.x = min( startPos.x, endPos.x ); + pev->mins.y = min( startPos.y, endPos.y ); + pev->mins.z = min( startPos.z, endPos.z ); + pev->maxs.x = max( startPos.x, endPos.x ); + pev->maxs.y = max( startPos.y, endPos.y ); + pev->maxs.z = max( startPos.z, endPos.z ); + pev->mins = pev->mins - pev->origin; + pev->maxs = pev->maxs - pev->origin; + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( this, pev->origin ); +} + +#if 0 +void CBeam::SetObjectCollisionBox( void ) +{ + const Vector &startPos = GetStartPos(), &endPos = GetEndPos(); + + pev->absmin.x = min( startPos.x, endPos.x ); + pev->absmin.y = min( startPos.y, endPos.y ); + pev->absmin.z = min( startPos.z, endPos.z ); + pev->absmax.x = max( startPos.x, endPos.x ); + pev->absmax.y = max( startPos.y, endPos.y ); + pev->absmax.z = max( startPos.z, endPos.z ); +} +#endif + + +void CBeam::TriggerTouch( CBaseEntity *pOther ) +{ + if ( pOther->pev->flags & (FL_CLIENT | FL_MONSTER) ) + { + if ( pev->owner ) + { + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + pOwner->Use( pOther, this, USE_TOGGLE, 0 ); + } + ALERT( at_debug, "Firing targets!!!\n" ); + } +} + + +CBaseEntity *CBeam::RandomTargetname( const char *szName ) +{ + int total = 0; + + CBaseEntity *pEntity = NULL; + CBaseEntity *pNewEntity = NULL; + while ((pNewEntity = UTIL_FindEntityByTargetname( pNewEntity, szName )) != NULL) + { + total++; + if (RANDOM_LONG(0,total-1) < 1) + pEntity = pNewEntity; + } + return pEntity; +} + + +void CBeam::DoSparks( const Vector &start, const Vector &end ) +{ + if ( pev->spawnflags & (SF_BEAM_SPARKSTART|SF_BEAM_SPARKEND) ) + { + if ( pev->spawnflags & SF_BEAM_SPARKSTART ) + { + UTIL_Sparks( start ); + } + if ( pev->spawnflags & SF_BEAM_SPARKEND ) + { + UTIL_Sparks( end ); + } + } +} + + +class CLightning : public CBeam +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void Activate( void ); + + void EXPORT StrikeThink( void ); + void EXPORT TripThink( void ); + void RandomArea( void ); + void RandomPoint( Vector &vecSrc ); + void Zap( const Vector &vecSrc, const Vector &vecDest ); + void EXPORT StrikeUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT ToggleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + inline BOOL ServerSide( void ) + { + if ( m_life == 0 && !(pev->spawnflags & SF_BEAM_RING) ) + return TRUE; + return FALSE; + } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void BeamUpdatePoints( void ); //LRC + void BeamUpdateVars( void ); + + virtual STATE GetState( void ) { return m_active?STATE_OFF:STATE_ON; }; + + int m_active; + int m_iszStartEntity; + int m_iszEndEntity; + float m_life; + int m_boltWidth; + int m_noiseAmplitude; + int m_brightness; + int m_speed; + float m_restrike; + int m_spriteTexture; + int m_iszSpriteName; + int m_frameStart; + + float m_radius; +}; + +LINK_ENTITY_TO_CLASS( env_lightning, CLightning ); +LINK_ENTITY_TO_CLASS( env_beam, CLightning ); + +// UNDONE: Jay -- This is only a test +#if _DEBUG +class CTripBeam : public CLightning +{ + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS( trip_beam, CTripBeam ); + +void CTripBeam::Spawn( void ) +{ + CLightning::Spawn(); + SetTouch(&CTripBeam:: TriggerTouch ); + pev->solid = SOLID_TRIGGER; + RelinkBeam(); +} +#endif + + + +TYPEDESCRIPTION CLightning::m_SaveData[] = +{ + DEFINE_FIELD( CLightning, m_active, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_iszStartEntity, FIELD_STRING ), + DEFINE_FIELD( CLightning, m_iszEndEntity, FIELD_STRING ), + DEFINE_FIELD( CLightning, m_life, FIELD_FLOAT ), + DEFINE_FIELD( CLightning, m_boltWidth, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_noiseAmplitude, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_brightness, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_speed, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_restrike, FIELD_FLOAT ), + DEFINE_FIELD( CLightning, m_spriteTexture, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_iszSpriteName, FIELD_STRING ), + DEFINE_FIELD( CLightning, m_frameStart, FIELD_INTEGER ), + DEFINE_FIELD( CLightning, m_radius, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CLightning, CBeam ); + + +void CLightning::Spawn( void ) +{ + if ( FStringNull( m_iszSpriteName ) ) + { + SetThink(&CLightning:: SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; // Remove model & collisions + Precache( ); + + pev->dmgtime = gpGlobals->time; + + //LRC- a convenience for mappers. Will this mess anything up? + if (pev->rendercolor == g_vecZero) + pev->rendercolor = Vector(255, 255, 255); + + if (pev->frags == 0) + { + pev->frags = DMG_ENERGYBEAM; + } + + if ( ServerSide() ) + { + SetThink( NULL ); + if ( pev->dmg != 0 || !FStringNull(pev->target) ) + { + SetThink(&CLightning:: TripThink ); + SetNextThink( 0.1 ); + } + if ( pev->targetname ) + { + if ( !(pev->spawnflags & SF_BEAM_STARTON) ) + { + pev->effects |= EF_NODRAW; + m_active = 0; + DontThink(); + } + else + m_active = 1; + + SetUse(&CLightning:: ToggleUse ); + } + } + else + { + m_active = 0; + if ( !FStringNull(pev->targetname) ) + { + SetUse(&CLightning:: StrikeUse ); + } + if ( FStringNull(pev->targetname) || FBitSet(pev->spawnflags, SF_BEAM_STARTON) ) + { + SetThink(&CLightning:: StrikeThink ); + SetNextThink( 1.0 ); + } + } +} + +void CLightning::Precache( void ) +{ + m_spriteTexture = PRECACHE_MODEL( (char *)STRING(m_iszSpriteName) ); + CBeam::Precache(); +} + + +void CLightning::Activate( void ) +{ + if ( ServerSide() ) + BeamUpdateVars(); + + CBeam::Activate(); +} + + +void CLightning::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "LightningStart")) + { + m_iszStartEntity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "LightningEnd")) + { + m_iszEndEntity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "life")) + { + m_life = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "BoltWidth")) + { + m_boltWidth = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "NoiseAmplitude")) + { + m_noiseAmplitude = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "TextureScroll")) + { + m_speed = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "StrikeTime")) + { + m_restrike = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "texture")) + { + m_iszSpriteName = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "framestart")) + { + m_frameStart = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "Radius")) + { + m_radius = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damage")) + { + pev->dmg = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBeam::KeyValue( pkvd ); +} + + +void CLightning::ToggleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_active ) ) + return; + if ( m_active ) + { + m_active = 0; + SUB_UseTargets( this, USE_OFF, 0 ); //LRC + pev->effects |= EF_NODRAW; + DontThink(); + } + else + { + m_active = 1; + SUB_UseTargets( this, USE_ON, 0 ); //LRC + BeamUpdatePoints(); + pev->effects &= ~EF_NODRAW; + DoSparks( GetStartPos(), GetEndPos() ); + if ( pev->dmg > 0 ) + { + SetNextThink( 0 ); + pev->dmgtime = gpGlobals->time; + } + } +} + + +void CLightning::StrikeUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_active ) ) + return; + + if ( m_active ) + { + m_active = 0; + SetThink( NULL ); + } + else + { + SetThink(&CLightning:: StrikeThink ); + SetNextThink( 0.1 ); + } + + if ( !FBitSet( pev->spawnflags, SF_BEAM_TOGGLE ) ) + SetUse( NULL ); +} + + +int IsPointEntity( CBaseEntity *pEnt ) +{ +// ALERT(at_console, "IsPE: %s, %d\n", STRING(pEnt->pev->classname), pEnt->pev->modelindex); + if (pEnt->pev->modelindex && !(pEnt->pev->flags & FL_CUSTOMENTITY)) //LRC- follow (almost) any entity that has a model + return 0; + else + return 1; + + return 0; +} + + +void CLightning::StrikeThink( void ) +{ + if ( m_life != 0 && m_restrike != -1) //LRC non-restriking beams! what an idea! + { + if ( pev->spawnflags & SF_BEAM_RANDOM ) + SetNextThink( m_life + RANDOM_FLOAT( 0, m_restrike ) ); + else + SetNextThink( m_life + m_restrike ); + } + m_active = 1; + + if (FStringNull(m_iszEndEntity)) + { + if (FStringNull(m_iszStartEntity)) + { + RandomArea( ); + } + else + { + CBaseEntity *pStart = RandomTargetname( STRING(m_iszStartEntity) ); + if (pStart != NULL) + RandomPoint( pStart->pev->origin ); + else + ALERT( at_debug, "env_beam: unknown entity \"%s\"\n", STRING(m_iszStartEntity) ); + } + return; + } + + CBaseEntity *pStart = RandomTargetname( STRING(m_iszStartEntity) ); + CBaseEntity *pEnd = RandomTargetname( STRING(m_iszEndEntity) ); + + if ( pStart != NULL && pEnd != NULL ) + { + if ( IsPointEntity( pStart ) || IsPointEntity( pEnd ) ) + { + if ( pev->spawnflags & SF_BEAM_RING) + { + // don't work + //LRC- FIXME: tell the user there's a problem. + return; + } + } + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + if ( IsPointEntity( pStart ) || IsPointEntity( pEnd ) ) + { + if ( !IsPointEntity( pEnd ) ) // One point entity must be in pEnd + { + CBaseEntity *pTemp; + pTemp = pStart; + pStart = pEnd; + pEnd = pTemp; + } + if ( !IsPointEntity( pStart ) ) // One sided + { + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( pStart->entindex() ); + WRITE_COORD( pEnd->pev->origin.x); + WRITE_COORD( pEnd->pev->origin.y); + WRITE_COORD( pEnd->pev->origin.z); + } + else + { + WRITE_BYTE( TE_BEAMPOINTS); + WRITE_COORD( pStart->pev->origin.x); + WRITE_COORD( pStart->pev->origin.y); + WRITE_COORD( pStart->pev->origin.z); + WRITE_COORD( pEnd->pev->origin.x); + WRITE_COORD( pEnd->pev->origin.y); + WRITE_COORD( pEnd->pev->origin.z); + } + + + } + else + { + if ( pev->spawnflags & SF_BEAM_RING) + WRITE_BYTE( TE_BEAMRING ); + else + WRITE_BYTE( TE_BEAMENTS ); + WRITE_SHORT( pStart->entindex() ); + WRITE_SHORT( pEnd->entindex() ); + } + + WRITE_SHORT( m_spriteTexture ); + WRITE_BYTE( m_frameStart ); // framestart + WRITE_BYTE( (int)pev->framerate); // framerate + WRITE_BYTE( (int)(m_life*10.0) ); // life + WRITE_BYTE( m_boltWidth ); // width + WRITE_BYTE( m_noiseAmplitude ); // noise + WRITE_BYTE( (int)pev->rendercolor.x ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.y ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.z ); // r, g, b + WRITE_BYTE( pev->renderamt ); // brightness + WRITE_BYTE( m_speed ); // speed + MESSAGE_END(); + DoSparks( pStart->pev->origin, pEnd->pev->origin ); + if ( pev->dmg || !FStringNull(pev->target)) + { + TraceResult tr; + UTIL_TraceLine( pStart->pev->origin, pEnd->pev->origin, dont_ignore_monsters, NULL, &tr ); + if (pev->dmg) + BeamDamageInstant( &tr, pev->dmg ); + + //LRC - tripbeams + CBaseEntity* pTrip; + if (!FStringNull(pev->target) && (pTrip = GetTripEntity( &tr )) != NULL) + FireTargets(STRING(pev->target), pTrip, this, USE_TOGGLE, 0); + } + } +} + + +CBaseEntity* CBeam::GetTripEntity( TraceResult *ptr ) +{ + CBaseEntity* pTrip; + + if (ptr->flFraction == 1.0 || ptr->pHit == NULL) + return NULL; + + pTrip = CBaseEntity::Instance(ptr->pHit); + if (pTrip == NULL) + return NULL; + + if ( FStringNull(pev->netname)) + { + if (pTrip->pev->flags & (FL_CLIENT | FL_MONSTER)) + return pTrip; + else + return NULL; + } + else if ( FClassnameIs(pTrip->pev, STRING(pev->netname) )) + return pTrip; + else if ( FStrEq( STRING(pTrip->pev->targetname), STRING(pev->netname) )) + return pTrip; + else + return NULL; +} + +void CBeam::BeamDamage( TraceResult *ptr ) +{ + RelinkBeam(); + if ( ptr->flFraction != 1.0 && ptr->pHit != NULL ) + { + CBaseEntity *pHit = CBaseEntity::Instance(ptr->pHit); + if ( pHit ) + { + if (pev->dmg > 0) + { + ClearMultiDamage(); + pHit->TraceAttack( pev, pev->dmg * (gpGlobals->time - pev->dmgtime), (ptr->vecEndPos - pev->origin).Normalize(), ptr, pev->frags ); + ApplyMultiDamage( pev, pev ); + if ( pev->spawnflags & SF_BEAM_DECALS ) + { + if ( pHit->IsBSPModel() ) + UTIL_TraceCustomDecal( ptr, "shot" ); + } + } + else + { + //LRC - beams that heal people + pHit->TakeHealth( -(pev->dmg * (gpGlobals->time - pev->dmgtime)), DMG_GENERIC ); + } + } + } + pev->dmgtime = gpGlobals->time; +} + +//LRC - used to be DamageThink, but now it's more general. +void CLightning::TripThink( void ) +{ + SetNextThink( 0.1 ); + TraceResult tr; + + //ALERT(at_console,"TripThink\n"); + + if (pev->dmg != 0) + { + UTIL_TraceLine( GetStartPos(), GetEndPos(), dont_ignore_monsters, NULL, &tr ); + BeamDamage( &tr ); + } + + //LRC - tripbeams + if (!FStringNull(pev->target)) + { + //HACKHACK Set simple box using this really nice global! + gpGlobals->trace_flags = FTRACE_SIMPLEBOX; + UTIL_TraceLine( GetStartPos(), GetEndPos(), dont_ignore_monsters, NULL, &tr ); + CBaseEntity *pTrip = GetTripEntity( &tr ); + if (pTrip) + { + if (!FBitSet(pev->spawnflags, SF_BEAM_TRIPPED)) + { + FireTargets(STRING(pev->target), pTrip, this, USE_TOGGLE, 0); + pev->spawnflags |= SF_BEAM_TRIPPED; + } + } + else + { + pev->spawnflags &= ~SF_BEAM_TRIPPED; + } + } +} + + + +void CLightning::Zap( const Vector &vecSrc, const Vector &vecDest ) +{ +#if 1 + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS); + WRITE_COORD(vecSrc.x); + WRITE_COORD(vecSrc.y); + WRITE_COORD(vecSrc.z); + WRITE_COORD(vecDest.x); + WRITE_COORD(vecDest.y); + WRITE_COORD(vecDest.z); + WRITE_SHORT( m_spriteTexture ); + WRITE_BYTE( m_frameStart ); // framestart + WRITE_BYTE( (int)pev->framerate); // framerate + WRITE_BYTE( (int)(m_life*10.0) ); // life + WRITE_BYTE( m_boltWidth ); // width + WRITE_BYTE( m_noiseAmplitude ); // noise + WRITE_BYTE( (int)pev->rendercolor.x ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.y ); // r, g, b + WRITE_BYTE( (int)pev->rendercolor.z ); // r, g, b + WRITE_BYTE( pev->renderamt ); // brightness + WRITE_BYTE( m_speed ); // speed + MESSAGE_END(); +#else + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE(TE_LIGHTNING); + WRITE_COORD(vecSrc.x); + WRITE_COORD(vecSrc.y); + WRITE_COORD(vecSrc.z); + WRITE_COORD(vecDest.x); + WRITE_COORD(vecDest.y); + WRITE_COORD(vecDest.z); + WRITE_BYTE(10); + WRITE_BYTE(50); + WRITE_BYTE(40); + WRITE_SHORT(m_spriteTexture); + MESSAGE_END(); +#endif + DoSparks( vecSrc, vecDest ); +} + +void CLightning::RandomArea( void ) +{ + int iLoops = 0; + + for (iLoops = 0; iLoops < 10; iLoops++) + { + Vector vecSrc = pev->origin; + + Vector vecDir1 = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + vecDir1 = vecDir1.Normalize(); + TraceResult tr1; + UTIL_TraceLine( vecSrc, vecSrc + vecDir1 * m_radius, ignore_monsters, ENT(pev), &tr1 ); + + if (tr1.flFraction == 1.0) + continue; + + Vector vecDir2; + do { + vecDir2 = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + } while (DotProduct(vecDir1, vecDir2 ) > 0); + vecDir2 = vecDir2.Normalize(); + TraceResult tr2; + UTIL_TraceLine( vecSrc, vecSrc + vecDir2 * m_radius, ignore_monsters, ENT(pev), &tr2 ); + + if (tr2.flFraction == 1.0) + continue; + + if ((tr1.vecEndPos - tr2.vecEndPos).Length() < m_radius * 0.1) + continue; + + UTIL_TraceLine( tr1.vecEndPos, tr2.vecEndPos, ignore_monsters, ENT(pev), &tr2 ); + + if (tr2.flFraction != 1.0) + continue; + + Zap( tr1.vecEndPos, tr2.vecEndPos ); + + break; + } +} + + +void CLightning::RandomPoint( Vector &vecSrc ) +{ + int iLoops = 0; + + for (iLoops = 0; iLoops < 10; iLoops++) + { + Vector vecDir1 = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + vecDir1 = vecDir1.Normalize(); + TraceResult tr1; + UTIL_TraceLine( vecSrc, vecSrc + vecDir1 * m_radius, ignore_monsters, ENT(pev), &tr1 ); + + if ((tr1.vecEndPos - vecSrc).Length() < m_radius * 0.1) + continue; + + if (tr1.flFraction == 1.0) + continue; + + Zap( vecSrc, tr1.vecEndPos ); + break; + } +} + + +// LRC: Called whenever the beam gets turned on, in case an alias changed or one of the points has moved. +void CLightning::BeamUpdatePoints( void ) +{ + int beamType; + int pointStart, pointEnd; + + CBaseEntity *pStart = UTIL_FindEntityByTargetname ( NULL, STRING(m_iszStartEntity) ); + CBaseEntity *pEnd = UTIL_FindEntityByTargetname ( NULL, STRING(m_iszEndEntity) ); + if (!pStart || !pEnd) return; + pointStart = IsPointEntity( pStart ); + pointEnd = IsPointEntity( pEnd ); + + beamType = BEAM_ENTS; + if ( pointStart || pointEnd ) + { + if ( !pointStart ) // One point entity must be in pStart + { + CBaseEntity *pTemp; + // Swap start & end + pTemp = pStart; + pStart = pEnd; + pEnd = pTemp; + int swap = pointStart; + pointStart = pointEnd; + pointEnd = swap; + } + if ( !pointEnd ) + beamType = BEAM_ENTPOINT; + else + beamType = BEAM_POINTS; + } + + SetType( beamType ); + if ( beamType == BEAM_POINTS || beamType == BEAM_ENTPOINT || beamType == BEAM_HOSE ) + { + SetStartPos( pStart->pev->origin ); + if ( beamType == BEAM_POINTS || beamType == BEAM_HOSE ) + SetEndPos( pEnd->pev->origin ); + else + SetEndEntity( ENTINDEX(ENT(pEnd->pev)) ); + } + else + { + SetStartEntity( ENTINDEX(ENT(pStart->pev)) ); + SetEndEntity( ENTINDEX(ENT(pEnd->pev)) ); + } + + RelinkBeam(); +} + +void CLightning::BeamUpdateVars( void ) +{ + pev->skin = 0; + pev->sequence = 0; + pev->rendermode = 0; + pev->flags |= FL_CUSTOMENTITY; + pev->model = m_iszSpriteName; + SetTexture( m_spriteTexture ); + + BeamUpdatePoints(); //LRC + + SetWidth( m_boltWidth ); + SetNoise( m_noiseAmplitude ); + SetFrame( m_frameStart ); + SetScrollRate( m_speed ); + if ( pev->spawnflags & SF_BEAM_SHADEIN ) + SetFlags( BEAM_FSHADEIN ); + else if ( pev->spawnflags & SF_BEAM_SHADEOUT ) + SetFlags( BEAM_FSHADEOUT ); + else if ( pev->spawnflags & SF_BEAM_SOLID ) + SetFlags( BEAM_FSOLID ); +} + + +LINK_ENTITY_TO_CLASS( env_laser, CLaser ); + +TYPEDESCRIPTION CLaser::m_SaveData[] = +{ + DEFINE_FIELD( CLaser, m_pStartSprite, FIELD_CLASSPTR ), + DEFINE_FIELD( CLaser, m_pEndSprite, FIELD_CLASSPTR ), + DEFINE_FIELD( CLaser, m_iszStartSpriteName, FIELD_STRING ), + DEFINE_FIELD( CLaser, m_iszEndSpriteName, FIELD_STRING ), + DEFINE_FIELD( CLaser, m_firePosition, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CLaser, m_iProjection, FIELD_INTEGER ), + DEFINE_FIELD( CLaser, m_iStoppedBy, FIELD_INTEGER ), + DEFINE_FIELD( CLaser, m_iszStartPosition, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CLaser, CBeam ); + +void CLaser::Spawn( void ) +{ + if ( FStringNull( pev->model ) ) + { + SetThink(&CLaser:: SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; // Remove model & collisions + Precache( ); + + SetThink(&CLaser:: StrikeThink ); + pev->flags |= FL_CUSTOMENTITY; +} + +void CLaser::PostSpawn( void ) +{ + if ( m_iszStartSpriteName ) + { + //LRC: allow the spritename to be the name of an env_sprite + CBaseEntity *pTemp = UTIL_FindEntityByTargetname(NULL, STRING(m_iszStartSpriteName)); + if (pTemp == NULL) + { + m_pStartSprite = CSprite::SpriteCreate( STRING(m_iszStartSpriteName), pev->origin, TRUE ); + if (m_pStartSprite) + m_pStartSprite->SetTransparency( kRenderGlow, pev->rendercolor.x, pev->rendercolor.y, pev->rendercolor.z, pev->renderamt, pev->renderfx ); + } + else if (!FClassnameIs(pTemp->pev, "env_sprite")) + { + ALERT(at_error, "env_laser \"%s\" found startsprite %s, but can't use: not an env_sprite\n", STRING(pev->targetname), STRING(m_iszStartSpriteName)); + m_pStartSprite = NULL; + } + else + { + // use an env_sprite defined by the mapper + m_pStartSprite = (CSprite*)pTemp; + m_pStartSprite->pev->movetype = MOVETYPE_NOCLIP; + } + } + else if ( pev->spawnflags & SF_LASER_INTERPOLATE) // interpolated lasers must have sprites at the start + { + m_pStartSprite = CSprite::SpriteCreate( "sprites/null.spr", pev->origin, TRUE ); + } + else + m_pStartSprite = NULL; + + + if ( m_iszEndSpriteName ) + { + CBaseEntity *pTemp = UTIL_FindEntityByTargetname(NULL, STRING(m_iszEndSpriteName)); + if (pTemp == NULL) + { + m_pEndSprite = CSprite::SpriteCreate( STRING(m_iszEndSpriteName), pev->origin, TRUE ); + if (m_pEndSprite) + m_pEndSprite->SetTransparency( kRenderGlow, pev->rendercolor.x, pev->rendercolor.y, pev->rendercolor.z, pev->renderamt, pev->renderfx ); + } + else if (!FClassnameIs(pTemp->pev, "env_sprite")) + { + ALERT(at_error, "env_laser \"%s\" found endsprite %s, but can't use: not an env_sprite\n", STRING(pev->targetname), STRING(m_iszEndSpriteName)); + m_pEndSprite = NULL; + } + else + { + // use an env_sprite defined by the mapper + m_pEndSprite = (CSprite*)pTemp; + m_pEndSprite->pev->movetype = MOVETYPE_NOCLIP; + } + } + else if ( pev->spawnflags & SF_LASER_INTERPOLATE) // interpolated lasers must have sprites at the end + { + m_pEndSprite = CSprite::SpriteCreate( "sprites/null.spr", pev->origin, TRUE ); + } + else + m_pEndSprite = NULL; + + //LRC + if ( m_pStartSprite && m_pEndSprite && pev->spawnflags & SF_LASER_INTERPOLATE ) + EntsInit( m_pStartSprite->entindex(), m_pEndSprite->entindex() ); + else + PointsInit( pev->origin, pev->origin ); + + if ( pev->targetname && !(pev->spawnflags & SF_BEAM_STARTON) ) + TurnOff(); + else + TurnOn(); +} + +void CLaser::Precache( void ) +{ + PRECACHE_MODEL( "sprites/null.spr" ); + pev->modelindex = PRECACHE_MODEL( (char *)STRING(pev->model) ); + if ( m_iszStartSpriteName ) + { + // UGLY HACK to check whether this is a filename: does it contain a dot? + const char *c = STRING(m_iszStartSpriteName); + while (*c) + { + if (*c == '.') + { + PRECACHE_MODEL( (char *)STRING(m_iszStartSpriteName) ); + break; + } + c++; // the magic word? + } + } + + if ( m_iszEndSpriteName ) + { + const char *c = STRING(m_iszEndSpriteName); + while (*c) + { + if (*c == '.') + { + PRECACHE_MODEL( (char *)STRING(m_iszEndSpriteName) ); + break; + } + c++; + } + } +} + + +void CLaser::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "LaserStart")) + { + m_iszStartPosition = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "LaserTarget")) + { + pev->message = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iTowardsMode")) + { + m_iTowardsMode = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "width")) + { + SetWidth( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "NoiseAmplitude")) + { + SetNoise( atoi(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "TextureScroll")) + { + SetScrollRate( atoi(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "texture")) + { + pev->model = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "StartSprite")) + { + m_iszStartSpriteName = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "EndSprite")) + { + m_iszEndSpriteName = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "framestart")) + { + pev->frame = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damage")) + { + pev->dmg = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iProjection")) + { + m_iProjection = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iStoppedBy")) + { + m_iStoppedBy = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBeam::KeyValue( pkvd ); +} + + +void CLaser::TurnOff( void ) +{ + pev->effects |= EF_NODRAW; + DontThink(); + if ( m_pStartSprite ) + { + m_pStartSprite->TurnOff(); + UTIL_SetVelocity(m_pStartSprite, g_vecZero); //LRC + } + if ( m_pEndSprite ) + { + m_pEndSprite->TurnOff(); + UTIL_SetVelocity(m_pEndSprite, g_vecZero); //LRC + } +} + + +void CLaser::TurnOn( void ) +{ + pev->effects &= ~EF_NODRAW; + + if ( m_pStartSprite ) + m_pStartSprite->TurnOn(); + + if ( m_pEndSprite ) + m_pEndSprite->TurnOn(); + + pev->dmgtime = gpGlobals->time; + + if ( pev->spawnflags & SF_BEAM_SHADEIN ) + SetFlags( BEAM_FSHADEIN ); + else if ( pev->spawnflags & SF_BEAM_SHADEOUT ) + SetFlags( BEAM_FSHADEOUT ); + else if ( pev->spawnflags & SF_BEAM_SOLID ) + SetFlags( BEAM_FSOLID ); + + SetNextThink( 0 ); +} + + +void CLaser::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int active = (GetState() == STATE_ON); + + if ( !ShouldToggle( useType, active ) ) + return; + if ( active ) + TurnOff(); + else + TurnOn(); +} + + +void CLaser::FireAtPoint( Vector startpos, TraceResult &tr ) +{ + if (pev->spawnflags & SF_LASER_INTERPOLATE && m_pStartSprite && m_pEndSprite) + { + UTIL_SetVelocity(m_pStartSprite, (startpos - m_pStartSprite->pev->origin)*10 ); + UTIL_SetVelocity(m_pEndSprite, (tr.vecEndPos - m_pEndSprite->pev->origin)*10 ); + } + else + { + if (m_pStartSprite) + UTIL_AssignOrigin( m_pStartSprite, startpos ); + + if (m_pEndSprite) + UTIL_AssignOrigin( m_pEndSprite, tr.vecEndPos ); + + SetStartPos( startpos ); + SetEndPos( tr.vecEndPos ); + } + + BeamDamage( &tr ); + DoSparks( startpos, tr.vecEndPos ); +} + +void CLaser::StrikeThink( void ) +{ + Vector startpos = pev->origin; + if (m_iszStartPosition) + { + startpos = CalcLocus_Position(this, NULL, STRING(m_iszStartPosition)); + } + + if (m_iTowardsMode) + { + m_firePosition = startpos + CalcLocus_Velocity(this, NULL, STRING(pev->message)); + } + else + { + CBaseEntity *pEnd = RandomTargetname( STRING(pev->message) ); + + if ( pEnd ) + m_firePosition = pEnd->pev->origin; + } + + TraceResult tr; + +//LRC +// UTIL_TraceLine( pev->origin, m_firePosition, dont_ignore_monsters, NULL, &tr ); + IGNORE_GLASS iIgnoreGlass; + if (m_iStoppedBy % 2) // if it's an odd number + iIgnoreGlass = ignore_glass; + else + iIgnoreGlass = dont_ignore_glass; + + IGNORE_MONSTERS iIgnoreMonsters; + if (m_iStoppedBy <= 1) + iIgnoreMonsters = dont_ignore_monsters; + else if (m_iStoppedBy <= 3) + iIgnoreMonsters = missile; + else + iIgnoreMonsters = ignore_monsters; + + if ( m_iProjection ) + { + Vector vecProject = startpos + 4096*((m_firePosition - startpos).Normalize()); + UTIL_TraceLine( startpos, vecProject, iIgnoreMonsters, iIgnoreGlass, NULL, &tr ); + } + else + { + UTIL_TraceLine( startpos, m_firePosition, iIgnoreMonsters, iIgnoreGlass, NULL, &tr ); + } + + FireAtPoint( startpos, tr ); + + //LRC - tripbeams + if (pev->target) + { + //HACKHACK Set simple box using this really nice global! + gpGlobals->trace_flags = FTRACE_SIMPLEBOX; + UTIL_TraceLine( startpos, m_firePosition, dont_ignore_monsters, NULL, &tr ); + CBaseEntity *pTrip = GetTripEntity( &tr ); + if (pTrip) + { + if (!FBitSet(pev->spawnflags, SF_BEAM_TRIPPED)) + { + FireTargets(STRING(pev->target), pTrip, this, USE_TOGGLE, 0); + pev->spawnflags |= SF_BEAM_TRIPPED; + } + } + else + { + pev->spawnflags &= ~SF_BEAM_TRIPPED; + } + } + SetNextThink( 0.1 ); +} + + + +class CGlow : public CPointEntity +{ +public: + void Spawn( void ); + void Think( void ); + void Animate( float frames ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + float m_lastTime; + float m_maxFrame; +}; + +LINK_ENTITY_TO_CLASS( env_glow, CGlow ); + +TYPEDESCRIPTION CGlow::m_SaveData[] = +{ + DEFINE_FIELD( CGlow, m_lastTime, FIELD_TIME ), + DEFINE_FIELD( CGlow, m_maxFrame, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CGlow, CPointEntity ); + +void CGlow::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->frame = 0; + + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; + if ( m_maxFrame > 1.0 && pev->framerate != 0 ) + SetNextThink( 0.1 ); + + m_lastTime = gpGlobals->time; +} + + +void CGlow::Think( void ) +{ + Animate( pev->framerate * (gpGlobals->time - m_lastTime) ); + + SetNextThink( 0.1 ); + m_lastTime = gpGlobals->time; +} + + +void CGlow::Animate( float frames ) +{ + if ( m_maxFrame > 0 ) + pev->frame = fmod( pev->frame + frames, m_maxFrame ); +} + + +LINK_ENTITY_TO_CLASS( env_sprite, CSprite ); + +TYPEDESCRIPTION CSprite::m_SaveData[] = +{ + DEFINE_FIELD( CSprite, m_lastTime, FIELD_TIME ), + DEFINE_FIELD( CSprite, m_maxFrame, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CSprite, CPointEntity ); + +void CSprite::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->frame = 0; + + Precache(); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; + if ( pev->targetname && !(pev->spawnflags & SF_SPRITE_STARTON) ) + TurnOff(); + else + TurnOn(); + + // Worldcraft only sets y rotation, copy to Z + if ( pev->angles.y != 0 && pev->angles.z == 0 ) + { + pev->angles.z = pev->angles.y; + pev->angles.y = 0; + } +} + + +void CSprite::Precache( void ) +{ + PRECACHE_MODEL( (char *)STRING(pev->model) ); + + // Reset attachment after save/restore + if ( pev->aiment ) + SetAttachment( pev->aiment, pev->body ); + else + { + // Clear attachment + pev->skin = 0; + pev->body = 0; + } +} + + +void CSprite::SpriteInit( const char *pSpriteName, const Vector &origin ) +{ + pev->model = MAKE_STRING(pSpriteName); + pev->origin = origin; + Spawn(); +} + +CSprite *CSprite::SpriteCreate( const char *pSpriteName, const Vector &origin, BOOL animate ) +{ + CSprite *pSprite = GetClassPtr( (CSprite *)NULL ); + pSprite->SpriteInit( pSpriteName, origin ); + pSprite->pev->classname = MAKE_STRING("env_sprite"); + pSprite->pev->solid = SOLID_NOT; + pSprite->pev->movetype = MOVETYPE_NOCLIP; + if ( animate ) + pSprite->TurnOn(); + + return pSprite; +} + + +void CSprite::AnimateThink( void ) +{ + Animate( pev->framerate * (gpGlobals->time - m_lastTime) ); + + SetNextThink( 0.1 ); + m_lastTime = gpGlobals->time; +} + +void CSprite::AnimateUntilDead( void ) +{ + if ( gpGlobals->time > pev->dmgtime ) + UTIL_Remove(this); + else + { + AnimateThink(); + SetNextThink( 0 ); + } +} + +void CSprite::Expand( float scaleSpeed, float fadeSpeed ) +{ + pev->speed = scaleSpeed; + pev->health = fadeSpeed; + SetThink(&CSprite:: ExpandThink ); + + SetNextThink( 0 ); + m_lastTime = gpGlobals->time; +} + + +void CSprite::ExpandThink( void ) +{ + float frametime = gpGlobals->time - m_lastTime; + pev->scale += pev->speed * frametime; + pev->renderamt -= pev->health * frametime; + if ( pev->renderamt <= 0 ) + { + pev->renderamt = 0; + UTIL_Remove( this ); + } + else + { + SetNextThink( 0.1 ); + m_lastTime = gpGlobals->time; + } +} + + +void CSprite::Animate( float frames ) +{ + pev->frame += frames; + if ( pev->frame > m_maxFrame ) + { + if ( pev->spawnflags & SF_SPRITE_ONCE ) + { + TurnOff(); + } + else + { + if ( m_maxFrame > 0 ) + pev->frame = fmod( pev->frame, m_maxFrame ); + } + } +} + + +void CSprite::TurnOff( void ) +{ + pev->effects |= EF_NODRAW; + DontThink(); +} + + +void CSprite::TurnOn( void ) +{ + if (pev->message) + { + CBaseEntity *pTemp = UTIL_FindEntityByTargetname(NULL, STRING(pev->message)); + if (pTemp) + SetAttachment(pTemp->edict(), pev->frags); + else + return; + } + pev->effects &= ~EF_NODRAW; + if ( (pev->framerate && m_maxFrame > 1.0) || (pev->spawnflags & SF_SPRITE_ONCE) ) + { + SetThink(&CSprite:: AnimateThink ); + SetNextThink( 0 ); + m_lastTime = gpGlobals->time; + } + pev->frame = 0; +} + + +void CSprite::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int on = !(pev->effects & EF_NODRAW); + if ( ShouldToggle( useType, on ) ) + { + if ( on ) + { + SUB_UseTargets( this, USE_OFF, 0 ); //LRC + TurnOff(); + } + else + { + SUB_UseTargets( this, USE_ON, 0 ); //LRC + TurnOn(); + } + } +} + +//================================================================= +// env_model: like env_sprite, except you can specify a sequence. +//================================================================= +#define SF_ENVMODEL_OFF 1 +#define SF_ENVMODEL_DROPTOFLOOR 2 +#define SF_ENVMODEL_SOLID 4 +#define SF_ENVMODEL_NEWLIGHTING 8 + +class CEnvModel : public CBaseAnimating +{ + void Spawn( void ); + void Precache( void ); + void EXPORT Think( void ); + void KeyValue( KeyValueData *pkvd ); + STATE GetState( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void SetSequence( void ); + + string_t m_iszSequence_On; + string_t m_iszSequence_Off; + int m_iAction_On; + int m_iAction_Off; +}; + +TYPEDESCRIPTION CEnvModel::m_SaveData[] = +{ + DEFINE_FIELD( CEnvModel, m_iszSequence_On, FIELD_STRING ), + DEFINE_FIELD( CEnvModel, m_iszSequence_Off, FIELD_STRING ), + DEFINE_FIELD( CEnvModel, m_iAction_On, FIELD_INTEGER ), + DEFINE_FIELD( CEnvModel, m_iAction_Off, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CEnvModel, CBaseAnimating ); +LINK_ENTITY_TO_CLASS( env_model, CEnvModel ); + +void CEnvModel::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iszSequence_On")) + { + m_iszSequence_On = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszSequence_Off")) + { + m_iszSequence_Off = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iAction_On")) + { + m_iAction_On = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iAction_Off")) + { + m_iAction_Off = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + { + CBaseAnimating::KeyValue( pkvd ); + } +} + +void CEnvModel :: Spawn( void ) +{ + Precache(); + SET_MODEL( ENT(pev), STRING(pev->model) ); + UTIL_SetOrigin(this, pev->origin); + +#if 0 // g-cont. just for testing gloss-effect + pev->movetype = MOVETYPE_NOCLIP; + pev->avelocity = Vector( 5, 5, 5 ); +#endif + if (pev->spawnflags & SF_ENVMODEL_SOLID) + { + pev->solid = SOLID_SLIDEBOX; + UTIL_SetSize(pev, Vector(-10, -10, -10), Vector(10, 10, 10)); //LRCT + } + + if (pev->spawnflags & SF_ENVMODEL_DROPTOFLOOR) + { + pev->origin.z += 1; + DROP_TO_FLOOR ( ENT(pev) ); + } + + if( !m_pMoveWith && FBitSet( pev->spawnflags, SF_ENVMODEL_NEWLIGHTING )) + { + // tell the client about static entity + SetBits( pev->iuser1, CF_STATIC_ENTITY ); + } + + SetBoneController( 0, 0 ); + SetBoneController( 1, 0 ); + + SetSequence(); + + SetNextThink( 0.1 ); +} + +void CEnvModel::Precache( void ) +{ + PRECACHE_MODEL( (char *)STRING(pev->model) ); +} + +STATE CEnvModel::GetState( void ) +{ + if (pev->spawnflags & SF_ENVMODEL_OFF) + return STATE_OFF; + else + return STATE_ON; +} + +void CEnvModel::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (ShouldToggle(useType, !(pev->spawnflags & SF_ENVMODEL_OFF))) + { + if (pev->spawnflags & SF_ENVMODEL_OFF) + pev->spawnflags &= ~SF_ENVMODEL_OFF; + else + pev->spawnflags |= SF_ENVMODEL_OFF; + + SetSequence(); + SetNextThink( 0.1 ); + } +} + +void CEnvModel::Think( void ) +{ + int iTemp; + +// ALERT(at_console, "env_model Think fr=%f\n", pev->framerate); + + StudioFrameAdvance ( ); // set m_fSequenceFinished if necessary + +// if (m_fSequenceLoops) +// { +// SetNextThink( 1E6 ); +// return; // our work here is done. +// } + if (m_fSequenceFinished && !m_fSequenceLoops) + { + if (pev->spawnflags & SF_ENVMODEL_OFF) + iTemp = m_iAction_Off; + else + iTemp = m_iAction_On; + + switch (iTemp) + { +// case 1: // loop +// pev->animtime = gpGlobals->time; +// m_fSequenceFinished = FALSE; +// m_flLastEventCheck = gpGlobals->time; +// pev->frame = 0; +// break; + case 2: // change state + if (pev->spawnflags & SF_ENVMODEL_OFF) + pev->spawnflags &= ~SF_ENVMODEL_OFF; + else + pev->spawnflags |= SF_ENVMODEL_OFF; + SetSequence(); + break; + default: //remain frozen + return; + } + } + SetNextThink( 0.1 ); +} + +void CEnvModel :: SetSequence( void ) +{ + int iszSeq; + + if (pev->spawnflags & SF_ENVMODEL_OFF) + iszSeq = m_iszSequence_Off; + else + iszSeq = m_iszSequence_On; + + if (!iszSeq) + return; + pev->sequence = LookupSequence( STRING( iszSeq )); + + if (pev->sequence == -1) + { + if (pev->targetname) + ALERT( at_error, "env_model %s: unknown sequence \"%s\"\n", STRING( pev->targetname ), STRING( iszSeq )); + else + ALERT( at_error, "env_model: unknown sequence \"%s\"\n", STRING( pev->targetname ), STRING( iszSeq )); + pev->sequence = 0; + } + + pev->frame = 0; + ResetSequenceInfo( ); + + if (pev->spawnflags & SF_ENVMODEL_OFF) + { + if (m_iAction_Off == 1) + m_fSequenceLoops = 1; + else + m_fSequenceLoops = 0; + } + else + { + if (m_iAction_On == 1) + m_fSequenceLoops = 1; + else + m_fSequenceLoops = 0; + } +} + +// =================== ENV_STATIC ============================================== +#define SF_STATIC_SOLID BIT( 0 ) +#define SF_STATIC_DROPTOFLOOR BIT( 1 ) +#define SF_STATIC_NOSHADOW BIT( 2 ) // hlrad affected + +class CEnvStatic : public CBaseEntity +{ +public: + void Spawn( void ); + void AutoSetSize( void ); + virtual int ObjectCaps( void ) { return 0; } + void SetObjectCollisionBox( void ); + void KeyValue( KeyValueData *pkvd ); + int vertex_light_cache; + int surface_light_cache; +}; + +LINK_ENTITY_TO_CLASS( env_static, CEnvStatic ); + +void CEnvStatic :: KeyValue( KeyValueData *pkvd ) +{ + if( FStrEq(pkvd->szKeyName, "xform")) + { + UTIL_StringToVector( (float*)pev->startpos, pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if( FStrEq(pkvd->szKeyName, "vlight_cache")) + { + vertex_light_cache = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if( FStrEq(pkvd->szKeyName, "flight_cache")) + { + surface_light_cache = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else CBaseEntity::KeyValue( pkvd ); +} + +void CEnvStatic :: Spawn( void ) +{ + if( pev->startpos == g_vecZero ) + pev->startpos = Vector( pev->scale, pev->scale, pev->scale ); + + if( surface_light_cache ) + { + SetBits( pev->iuser1, CF_STATIC_LIGHTMAPPED ); + pev->colormap = surface_light_cache; + } + else + { + pev->colormap = vertex_light_cache; + } + + // check xform values + if( pev->startpos.x < 0.01f ) pev->startpos.x = 1.0f; + if( pev->startpos.y < 0.01f ) pev->startpos.y = 1.0f; + if( pev->startpos.z < 0.01f ) pev->startpos.z = 1.0f; + if( pev->startpos.x > 16.0f ) pev->startpos.x = 16.0f; + if( pev->startpos.y > 16.0f ) pev->startpos.y = 16.0f; + if( pev->startpos.z > 16.0f ) pev->startpos.z = 16.0f; + + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + // tell the client about static entity + SetBits( pev->iuser1, CF_STATIC_ENTITY ); + + if( FBitSet( pev->spawnflags, SF_STATIC_SOLID )) + { + if( g_fPhysicInitialized ) + pev->solid = SOLID_CUSTOM; + pev->movetype = MOVETYPE_NONE; + AutoSetSize(); + } + + if( FBitSet( pev->spawnflags, SF_STATIC_DROPTOFLOOR )) + { + TraceResult trace; + Vector vecStart = pev->origin; + Vector vecEnd = pev->origin - Vector( 0, 0, 256 ); + + TRACE_LINE( vecStart, vecEnd, TRUE, edict(), &trace ); + + if( !trace.fAllSolid && trace.flFraction < 1.0f ) + UTIL_SetOrigin( this, trace.vecEndPos ); + } + else + { + UTIL_SetOrigin( this, pev->origin ); + } + + if( g_fXashEngine ) + { + if( FBitSet( pev->spawnflags, SF_STATIC_SOLID )) + { + if( !g_precache_meshes || g_precache_meshes->value ) + { + if( !UTIL_GetCollisionMesh( pev->modelindex )) + { + // for some reasons we can't build collision mesh + // for this model, just make it non-solid + pev->solid = SOLID_NOT; + MAKE_STATIC( edict() ); + } + } + } + else + { + // remove from server + MAKE_STATIC( edict() ); + } + } +} + +void CEnvStatic :: SetObjectCollisionBox( void ) +{ + Vector angles = pev->angles; + angles.x = -angles.x; // Stupid Quake bug workaround + + // expand for rotation + TransformAABB( matrix4x4( pev->origin, angles ), pev->mins, pev->maxs, pev->absmin, pev->absmax ); + + pev->absmin.x -= 1; + pev->absmin.y -= 1; + pev->absmin.z -= 1; + pev->absmax.x += 1; + pev->absmax.y += 1; + pev->absmax.z += 1; +} + +// automatically set collision box +void CEnvStatic :: AutoSetSize( void ) +{ + studiohdr_t *pstudiohdr; + pstudiohdr = (studiohdr_t *)GET_MODEL_PTR( edict() ); + + if( pstudiohdr == NULL ) + { + UTIL_SetSize( pev, Vector( -10, -10, -10 ), Vector( 10, 10, 10 )); + ALERT( at_error, "env_static: unable to fetch model pointer!\n" ); + return; + } + + mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex); + UTIL_SetSize( pev, pseqdesc[pev->sequence].bbmin * pev->startpos, pseqdesc[pev->sequence].bbmax * pev->startpos ); +} + +#define SF_GIBSHOOTER_REPEATABLE 1 // allows a gibshooter to be refired +#define SF_GIBSHOOTER_DEBUG 4 //LRC + +class CGibShooter : public CBaseDelay +{ +public: + virtual void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT ShootThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual CBaseEntity *CreateGib( Vector vecPos, Vector vecVel ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_iGibs; + int m_iGibCapacity; + int m_iGibMaterial; + int m_iGibModelIndex; +// float m_flGibVelocity; + float m_flVariance; + float m_flGibLife; + int m_iszTargetname; + int m_iszPosition; + int m_iszVelocity; + int m_iszVelFactor; + int m_iszSpawnTarget; + int m_iBloodColor; +}; + +TYPEDESCRIPTION CGibShooter::m_SaveData[] = +{ + DEFINE_FIELD( CGibShooter, m_iGibs, FIELD_INTEGER ), + DEFINE_FIELD( CGibShooter, m_iGibCapacity, FIELD_INTEGER ), + DEFINE_FIELD( CGibShooter, m_iGibMaterial, FIELD_INTEGER ), + DEFINE_FIELD( CGibShooter, m_iGibModelIndex, FIELD_INTEGER ), +// DEFINE_FIELD( CGibShooter, m_flGibVelocity, FIELD_FLOAT ), + DEFINE_FIELD( CGibShooter, m_flVariance, FIELD_FLOAT ), + DEFINE_FIELD( CGibShooter, m_flGibLife, FIELD_FLOAT ), + DEFINE_FIELD( CGibShooter, m_iszTargetname, FIELD_STRING), + DEFINE_FIELD( CGibShooter, m_iszPosition, FIELD_STRING), + DEFINE_FIELD( CGibShooter, m_iszVelocity, FIELD_STRING), + DEFINE_FIELD( CGibShooter, m_iszVelFactor, FIELD_STRING), + DEFINE_FIELD( CGibShooter, m_iszSpawnTarget, FIELD_STRING), + DEFINE_FIELD( CGibShooter, m_iBloodColor, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CGibShooter, CBaseDelay ); +LINK_ENTITY_TO_CLASS( gibshooter, CGibShooter ); + + +void CGibShooter :: Precache ( void ) +{ + if ( g_Language == LANGUAGE_GERMAN ) + { + m_iGibModelIndex = PRECACHE_MODEL ("models/germanygibs.mdl"); + } + else if (m_iBloodColor == BLOOD_COLOR_YELLOW) + { + m_iGibModelIndex = PRECACHE_MODEL ("models/agibs.mdl"); + } + else + { + m_iGibModelIndex = PRECACHE_MODEL ("models/hgibs.mdl"); + } +} + + +void CGibShooter::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iGibs")) + { + m_iGibs = m_iGibCapacity = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flVelocity")) + { + m_iszVelFactor = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flVariance")) + { + m_flVariance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flGibLife")) + { + m_flGibLife = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszTargetName")) + { + m_iszTargetname = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszPosition")) + { + m_iszPosition = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszVelocity")) + { + m_iszVelocity = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszVelFactor")) + { + m_iszVelFactor = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszSpawnTarget")) + { + m_iszSpawnTarget = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iBloodColor")) + { + m_iBloodColor = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + { + CBaseDelay::KeyValue( pkvd ); + } +} + +void CGibShooter::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_hActivator = pActivator; + SetThink(&CGibShooter:: ShootThink ); + SetNextThink( 0 ); +} + +void CGibShooter::Spawn( void ) +{ + Precache(); + + pev->solid = SOLID_NOT; + pev->effects |= EF_NODRAW; + +// if ( m_flDelay == 0 ) +// { +// m_flDelay = 0.1; +// } + + if ( m_flGibLife == 0 ) + { + m_flGibLife = 25; + } + + SetMovedir ( pev ); + if (pev->body == 0) + pev->body = MODEL_FRAMES( m_iGibModelIndex ); +} + + +CBaseEntity *CGibShooter :: CreateGib ( Vector vecPos, Vector vecVel ) +{ + if ( CVAR_GET_FLOAT("violence_hgibs") == 0 ) + return NULL; + + CGib *pGib = GetClassPtr( (CGib *)NULL ); +// if (pGib) +// ALERT(at_console, "Gib created ok\n"); + + pGib->pev->origin = vecPos; + pGib->pev->velocity = vecVel; + + if (m_iBloodColor == BLOOD_COLOR_YELLOW) + { + pGib->Spawn( "models/agibs.mdl" ); + pGib->m_bloodColor = BLOOD_COLOR_YELLOW; + } + else if (m_iBloodColor) + { + pGib->Spawn( "models/hgibs.mdl" ); + pGib->m_bloodColor = m_iBloodColor; + } + else + { + pGib->Spawn( "models/hgibs.mdl" ); + pGib->m_bloodColor = BLOOD_COLOR_RED; + } + + if ( pev->body <= 1 ) + { + ALERT ( at_aiconsole, "GibShooter Body is <= 1!\n" ); + } + + pGib->pev->body = RANDOM_LONG ( 1, pev->body - 1 );// avoid throwing random amounts of the 0th gib. (skull). + + float thinkTime = pGib->m_fNextThink - gpGlobals->time; + + pGib->m_lifeTime = (m_flGibLife * RANDOM_FLOAT( 0.95, 1.05 )); // +/- 5% + if ( pGib->m_lifeTime < thinkTime ) + { + pGib->SetNextThink( pGib->m_lifeTime ); + pGib->m_lifeTime = 0; + } + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 100, 200 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 100, 300 ); + + return pGib; +} + + +void CGibShooter :: ShootThink ( void ) +{ + int i; + if (m_flDelay == 0) // LRC - delay is 0, fire them all at once. + { + i = m_iGibs; + } + else + { + i = 1; + SetNextThink( m_flDelay ); + } + + while (i > 0) + { + Vector vecShootDir; + Vector vecPos; + float flGibVelocity; + if (!FStringNull(m_iszVelFactor)) + flGibVelocity = CalcLocus_Ratio(m_hActivator, STRING(m_iszVelFactor)); + else + flGibVelocity = 1; + + if (!FStringNull(m_iszVelocity)) + { + vecShootDir = CalcLocus_Velocity(this, m_hActivator, STRING(m_iszVelocity)); + flGibVelocity = flGibVelocity * vecShootDir.Length(); + vecShootDir = vecShootDir.Normalize(); + } + else + vecShootDir = pev->movedir; + + vecShootDir = vecShootDir + gpGlobals->v_right * RANDOM_FLOAT( -1, 1) * m_flVariance;; + vecShootDir = vecShootDir + gpGlobals->v_forward * RANDOM_FLOAT( -1, 1) * m_flVariance;; + vecShootDir = vecShootDir + gpGlobals->v_up * RANDOM_FLOAT( -1, 1) * m_flVariance;; + + vecShootDir = vecShootDir.Normalize(); + + if (!FStringNull(m_iszPosition)) + vecPos = CalcLocus_Position(this, m_hActivator, STRING(m_iszPosition)); + else + vecPos = pev->origin; + CBaseEntity *pGib = CreateGib(vecPos, vecShootDir * flGibVelocity); + + if ( pGib ) + { + pGib->pev->targetname = m_iszTargetname; +// pGib->pev->velocity = vecShootDir * flGibVelocity; + + if (pev->spawnflags & SF_GIBSHOOTER_DEBUG) + ALERT(at_debug, "DEBUG: %s \"%s\" creates a shot at %f %f %f; vel %f %f %f; pos \"%s\"\n", STRING(pev->classname), STRING(pev->targetname), pGib->pev->origin.x, pGib->pev->origin.y, pGib->pev->origin.z, pGib->pev->velocity.x, pGib->pev->velocity.y, pGib->pev->velocity.z, STRING(m_iszPosition)); + + if (m_iszSpawnTarget) + FireTargets( STRING(m_iszSpawnTarget), pGib, this, USE_TOGGLE, 0 ); + } + + i--; + m_iGibs--; + } + + if ( m_iGibs <= 0 ) + { + if ( pev->spawnflags & SF_GIBSHOOTER_REPEATABLE ) + { + m_iGibs = m_iGibCapacity; + SetThink ( NULL ); + DontThink(); + } + else + { + SetThink(&CGibShooter :: SUB_Remove ); + SetNextThink( 0 ); + } + } +} + + +// Shooter particle +class CShot : public CSprite +{ +public: + void Touch ( CBaseEntity *pOther ); +}; + +void CShot :: Touch ( CBaseEntity *pOther ) +{ + if (pev->teleport_time > gpGlobals->time) + return; + // don't fire too often in collisions! + // teleport_time is the soonest this can be touched again. + pev->teleport_time = gpGlobals->time + 0.1; + + if (pev->netname) + FireTargets( STRING(pev->netname), this, this, USE_TOGGLE, 0 ); + if (pev->message && pOther && pOther != g_pWorld) + FireTargets( STRING(pev->message), pOther, this, USE_TOGGLE, 0 ); +} + +class CEnvShooter : public CGibShooter +{ + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + void Spawn( void ); + + static TYPEDESCRIPTION m_SaveData[]; + + CBaseEntity *CreateGib( Vector vecPos, Vector vecVel ); + + int m_iszTouch; + int m_iszTouchOther; + int m_iPhysics; + float m_fFriction; + Vector m_vecSize; +}; + +TYPEDESCRIPTION CEnvShooter::m_SaveData[] = +{ + DEFINE_FIELD( CEnvShooter, m_iszTouch, FIELD_STRING), + DEFINE_FIELD( CEnvShooter, m_iszTouchOther, FIELD_STRING), + DEFINE_FIELD( CEnvShooter, m_iPhysics, FIELD_INTEGER), + DEFINE_FIELD( CEnvShooter, m_fFriction, FIELD_FLOAT), + DEFINE_FIELD( CEnvShooter, m_vecSize, FIELD_VECTOR), +}; + +IMPLEMENT_SAVERESTORE(CEnvShooter,CGibShooter); +LINK_ENTITY_TO_CLASS( env_shooter, CEnvShooter ); + +void CEnvShooter::Spawn( void ) +{ + int iBody = pev->body; + CGibShooter::Spawn(); + pev->body = iBody; +} + +void CEnvShooter :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "shootmodel")) + { + pev->model = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "shootsounds")) + { + int iNoise = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + switch( iNoise ) + { + case 0: + m_iGibMaterial = matGlass; + break; + case 1: + m_iGibMaterial = matWood; + break; + case 2: + m_iGibMaterial = matMetal; + break; + case 3: + m_iGibMaterial = matFlesh; + break; + case 4: + m_iGibMaterial = matRocks; + break; + + default: + case -1: + m_iGibMaterial = matNone; + break; + } + } + else if (FStrEq(pkvd->szKeyName, "m_iszTouch")) + { + m_iszTouch = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszTouchOther")) + { + m_iszTouchOther = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iPhysics")) + { + m_iPhysics = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fFriction")) + { + m_fFriction = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_vecSize")) + { + UTIL_StringToVector((float*)m_vecSize, pkvd->szValue); + m_vecSize = m_vecSize/2; + pkvd->fHandled = TRUE; + } + else + { + CGibShooter::KeyValue( pkvd ); + } +} + + +void CEnvShooter :: Precache ( void ) +{ + if (pev->model) + m_iGibModelIndex = PRECACHE_MODEL( (char *)STRING(pev->model) ); + CBreakable::MaterialSoundPrecache( (Materials)m_iGibMaterial ); +} + + +CBaseEntity *CEnvShooter :: CreateGib ( Vector vecPos, Vector vecVel ) +{ + if (m_iPhysics <= 1) // normal gib or sticky gib +{ + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + pGib->pev->origin = vecPos; + pGib->pev->velocity = vecVel; + + pGib->Spawn( STRING(pev->model) ); + if (m_iPhysics) // sticky gib + { + pGib->pev->movetype = MOVETYPE_TOSS; + pGib->pev->solid = SOLID_BBOX; + UTIL_SetSize ( pGib->pev, Vector ( 0, 0 ,0 ), Vector ( 0, 0, 0 ) ); + pGib->SetTouch(&CGib::StickyGibTouch); + } + if ( pev->body > 0 ) + pGib->pev->body = RANDOM_LONG( 0, pev->body-1 ); + if (m_iBloodColor) + pGib->m_bloodColor = m_iBloodColor; + else + pGib->m_bloodColor = DONT_BLEED; + pGib->m_material = m_iGibMaterial; + + pGib->pev->rendermode = pev->rendermode; + pGib->pev->renderamt = pev->renderamt; + pGib->pev->rendercolor = pev->rendercolor; + pGib->pev->renderfx = pev->renderfx; + pGib->pev->scale = pev->scale; + pGib->pev->skin = pev->skin; + + float thinkTime = pGib->m_fNextThink - gpGlobals->time; + + pGib->m_lifeTime = (m_flGibLife * RANDOM_FLOAT( 0.95, 1.05 )); // +/- 5% + if ( pGib->m_lifeTime < thinkTime ) + { + pGib->SetNextThink( pGib->m_lifeTime ); + pGib->m_lifeTime = 0; + } + + pGib->pev->avelocity.x = RANDOM_FLOAT ( 100, 200 ); + pGib->pev->avelocity.y = RANDOM_FLOAT ( 100, 300 ); + + return pGib; + } + + // special shot + CShot *pShot = GetClassPtr( (CShot*)NULL ); + pShot->pev->classname = MAKE_STRING("shot"); + pShot->pev->solid = SOLID_SLIDEBOX; + pShot->pev->origin = vecPos; + pShot->pev->velocity = vecVel; + SET_MODEL(ENT(pShot->pev), STRING(pev->model)); + UTIL_SetSize(pShot->pev, -m_vecSize, m_vecSize); + pShot->pev->renderamt = pev->renderamt; + pShot->pev->rendermode = pev->rendermode; + pShot->pev->rendercolor = pev->rendercolor; + pShot->pev->renderfx = pev->renderfx; + pShot->pev->netname = m_iszTouch; + pShot->pev->message = m_iszTouchOther; + pShot->pev->skin = pev->skin; + pShot->pev->body = pev->body; + pShot->pev->scale = pev->scale; + pShot->pev->frame = pev->frame; + pShot->pev->framerate = pev->framerate; + pShot->pev->friction = m_fFriction; + + switch (m_iPhysics) + { + case 2: pShot->pev->movetype = MOVETYPE_NOCLIP; pShot->pev->solid = SOLID_NOT; break; + case 3: pShot->pev->movetype = MOVETYPE_FLYMISSILE; break; + case 4: pShot->pev->movetype = MOVETYPE_BOUNCEMISSILE; break; + case 5: pShot->pev->movetype = MOVETYPE_TOSS; break; + case 6: pShot->pev->movetype = MOVETYPE_BOUNCE; break; + } + + if (pShot->pev->framerate) + { + pShot->m_maxFrame = (float) MODEL_FRAMES( pShot->pev->modelindex ) - 1; + if (pShot->m_maxFrame > 1.0) + { + if (m_flGibLife) + { + pShot->pev->dmgtime = gpGlobals->time + m_flGibLife; + pShot->SetThink(& CSprite::AnimateUntilDead ); + } + else + { + pShot->SetThink(& CSprite::AnimateThink ); + } + pShot->SetNextThink( 0 ); + pShot->m_lastTime = gpGlobals->time; + return pShot; + } + } + + // if it's not animating + if (m_flGibLife) + { + pShot->SetThink(&CShot::SUB_Remove); + pShot->SetNextThink(m_flGibLife); + } + return pShot; +} + + + + +class CTestEffect : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + // void KeyValue( KeyValueData *pkvd ); + void EXPORT TestThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int m_iLoop; + int m_iBeam; + CBeam *m_pBeam[24]; + float m_flBeamTime[24]; + float m_flStartTime; +}; + + +LINK_ENTITY_TO_CLASS( test_effect, CTestEffect ); + +void CTestEffect::Spawn( void ) +{ + Precache( ); +} + +void CTestEffect::Precache( void ) +{ + PRECACHE_MODEL( "sprites/lgtning.spr" ); +} + +void CTestEffect::TestThink( void ) +{ + int i; + float t = (gpGlobals->time - m_flStartTime); + + if (m_iBeam < 24) + { + CBeam *pbeam = CBeam::BeamCreate( "sprites/lgtning.spr", 100 ); + + TraceResult tr; + + Vector vecSrc = pev->origin; + Vector vecDir = Vector( RANDOM_FLOAT( -1.0, 1.0 ), RANDOM_FLOAT( -1.0, 1.0 ),RANDOM_FLOAT( -1.0, 1.0 ) ); + vecDir = vecDir.Normalize(); + UTIL_TraceLine( vecSrc, vecSrc + vecDir * 128, ignore_monsters, ENT(pev), &tr); + + pbeam->PointsInit( vecSrc, tr.vecEndPos ); + // pbeam->SetColor( 80, 100, 255 ); + pbeam->SetColor( 255, 180, 100 ); + pbeam->SetWidth( 100 ); + pbeam->SetScrollRate( 12 ); + + m_flBeamTime[m_iBeam] = gpGlobals->time; + m_pBeam[m_iBeam] = pbeam; + m_iBeam++; + +#if 0 + Vector vecMid = (vecSrc + tr.vecEndPos) * 0.5; + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE(TE_DLIGHT); + WRITE_COORD(vecMid.x); // X + WRITE_COORD(vecMid.y); // Y + WRITE_COORD(vecMid.z); // Z + WRITE_BYTE( 20 ); // radius * 0.1 + WRITE_BYTE( 255 ); // r + WRITE_BYTE( 180 ); // g + WRITE_BYTE( 100 ); // b + WRITE_BYTE( 20 ); // time * 10 + WRITE_BYTE( 0 ); // decay * 0.1 + MESSAGE_END( ); +#endif + } + + if (t < 3.0) + { + for (i = 0; i < m_iBeam; i++) + { + t = (gpGlobals->time - m_flBeamTime[i]) / ( 3 + m_flStartTime - m_flBeamTime[i]); + m_pBeam[i]->SetBrightness( 255 * t ); + // m_pBeam[i]->SetScrollRate( 20 * t ); + } + SetNextThink( 0.1 ); + } + else + { + for (i = 0; i < m_iBeam; i++) + { + UTIL_Remove( m_pBeam[i] ); + } + m_flStartTime = gpGlobals->time; + m_iBeam = 0; + // pev->nextthink = gpGlobals->time; + SetThink( NULL ); + } +} + + +void CTestEffect::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink(&CTestEffect:: TestThink ); + SetNextThink( 0.1 ); + m_flStartTime = gpGlobals->time; +} + + + +// Blood effects +class CBlood : public CPointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + inline int Color( void ) { return pev->impulse; } + inline float BloodAmount( void ) { return pev->dmg; } + + inline void SetColor( int color ) { pev->impulse = color; } + inline void SetBloodAmount( float amount ) { pev->dmg = amount; } + + Vector Direction( CBaseEntity *pActivator ); //LRC - added pActivator, for locus system + Vector BloodPosition( CBaseEntity *pActivator ); + +private: +}; + +LINK_ENTITY_TO_CLASS( env_blood, CBlood ); + + + +#define SF_BLOOD_RANDOM 0x0001 +#define SF_BLOOD_STREAM 0x0002 +#define SF_BLOOD_PLAYER 0x0004 +#define SF_BLOOD_DECAL 0x0008 + +void CBlood::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->frame = 0; + SetMovedir( pev ); + if (Color() == 0) SetColor( BLOOD_COLOR_RED ); +} + + +void CBlood::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "color")) + { + int color = atoi(pkvd->szValue); + switch( color ) + { + case 1: + SetColor( BLOOD_COLOR_YELLOW ); + break; + default: + SetColor( BLOOD_COLOR_RED ); + break; + } + + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "amount")) + { + SetBloodAmount( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +Vector CBlood::Direction( CBaseEntity *pActivator ) +{ + if ( pev->spawnflags & SF_BLOOD_RANDOM ) + return UTIL_RandomBloodVector(); + else if (pev->netname) + return CalcLocus_Velocity(this, pActivator, STRING(pev->netname)); + else + return pev->movedir; +} + + +Vector CBlood::BloodPosition( CBaseEntity *pActivator ) +{ + if ( pev->spawnflags & SF_BLOOD_PLAYER ) + { + edict_t *pPlayer; + + if ( pActivator && pActivator->IsPlayer() ) + { + pPlayer = pActivator->edict(); + } + else + pPlayer = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + if ( pPlayer ) + return (pPlayer->v.origin + pPlayer->v.view_ofs) + Vector( RANDOM_FLOAT(-10,10), RANDOM_FLOAT(-10,10), RANDOM_FLOAT(-10,10) ); + // if no player found, fall through + } + else if (pev->target) + { + return CalcLocus_Position(this, pActivator, STRING(pev->target)); + } + + return pev->origin; +} + + +void CBlood::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->spawnflags & SF_BLOOD_STREAM ) + UTIL_BloodStream( BloodPosition(pActivator), Direction(pActivator), (Color() == BLOOD_COLOR_RED) ? 70 : Color(), BloodAmount() ); + else + UTIL_BloodDrips( BloodPosition(pActivator), Direction(pActivator), Color(), BloodAmount() ); + + if ( pev->spawnflags & SF_BLOOD_DECAL ) + { + Vector forward = Direction(pActivator); + Vector start = BloodPosition( pActivator ); + TraceResult tr; + + UTIL_TraceLine( start, start + forward * BloodAmount() * 2, ignore_monsters, NULL, &tr ); + if ( tr.flFraction != 1.0 ) + UTIL_BloodDecalTrace( &tr, Color() ); + } +} + + + +// Screen shake +class CShake : public CPointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + inline float Amplitude( void ) { return pev->scale; } + inline float Frequency( void ) { return pev->dmg_save; } + inline float Duration( void ) { return pev->dmg_take; } + inline float Radius( void ) { return pev->dmg; } + + inline void SetAmplitude( float amplitude ) { pev->scale = amplitude; } + inline void SetFrequency( float frequency ) { pev->dmg_save = frequency; } + inline void SetDuration( float duration ) { pev->dmg_take = duration; } + inline void SetRadius( float radius ) { pev->dmg = radius; } + + STATE m_iState; //LRC + virtual STATE GetState( void ) { return m_iState; }; //LRC + void Think( void ) { m_iState = STATE_OFF; }; //LRC +private: +}; + +LINK_ENTITY_TO_CLASS( env_shake, CShake ); + +// pev->scale is amplitude +// pev->dmg_save is frequency +// pev->dmg_take is duration +// pev->dmg is radius +// radius of 0 means all players +// NOTE: UTIL_ScreenShake() will only shake players who are on the ground + +#define SF_SHAKE_EVERYONE 0x0001 // Don't check radius +// UNDONE: These don't work yet +#define SF_SHAKE_DISRUPT 0x0002 // Disrupt controls +#define SF_SHAKE_INAIR 0x0004 // Shake players in air + +void CShake::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->frame = 0; + + m_iState = STATE_OFF; //LRC + + if ( pev->spawnflags & SF_SHAKE_EVERYONE ) + pev->dmg = 0; +} + + +void CShake::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "amplitude")) + { + SetAmplitude( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "frequency")) + { + SetFrequency( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "duration")) + { + SetDuration( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "radius")) + { + SetRadius( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CShake::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + UTIL_ScreenShake( pev->origin, Amplitude(), Frequency(), Duration(), Radius() ); + m_iState = STATE_ON; //LRC + SetNextThink( Duration() ); //LRC +} + + +class CFade : public CPointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + virtual STATE GetState( void ) { return m_iState; }; // LRC + void Think( void ); //LRC + + inline float Duration( void ) { return pev->dmg_take; } + inline float HoldTime( void ) { return pev->dmg_save; } + + inline void SetDuration( float duration ) { pev->dmg_take = duration; } + inline void SetHoldTime( float hold ) { pev->dmg_save = hold; } + + STATE m_iState; // LRC. Don't saverestore this value, it's not worth it. +private: +}; + +LINK_ENTITY_TO_CLASS( env_fade, CFade ); + +// pev->dmg_take is duration +// pev->dmg_save is hold duration +#define SF_FADE_IN 0x0001 // Fade in, not out +#define SF_FADE_MODULATE 0x0002 // Modulate, don't blend +#define SF_FADE_ONLYONE 0x0004 +#define SF_FADE_PERMANENT 0x0008 //LRC - hold permanently + +void CFade::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->frame = 0; + + m_iState = STATE_OFF; //LRC +} + + +void CFade::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "duration")) + { + SetDuration( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "holdtime")) + { + SetHoldTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CFade::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int fadeFlags = 0; + + m_iState = STATE_TURN_ON; //LRC + SetNextThink( Duration() ); //LRC + + if ( !(pev->spawnflags & SF_FADE_IN) ) + fadeFlags |= FFADE_OUT; + + if ( pev->spawnflags & SF_FADE_MODULATE ) + fadeFlags |= FFADE_MODULATE; + + if ( pev->spawnflags & SF_FADE_PERMANENT ) //LRC + fadeFlags |= FFADE_STAYOUT; //LRC + + if ( pev->spawnflags & SF_FADE_ONLYONE ) + { + if ( pActivator->IsNetClient() ) + { + UTIL_ScreenFade( pActivator, pev->rendercolor, Duration(), HoldTime(), pev->renderamt, fadeFlags ); + } + } + else + { + UTIL_ScreenFadeAll( pev->rendercolor, Duration(), HoldTime(), pev->renderamt, fadeFlags ); + } + SUB_UseTargets( this, USE_TOGGLE, 0 ); +} + +//LRC: a bolt-on state! +void CFade::Think( void ) +{ + if (m_iState == STATE_TURN_ON) + { + m_iState = STATE_ON; + if (!( pev->spawnflags & SF_FADE_PERMANENT)) + SetNextThink( HoldTime() ); + } + else + m_iState = STATE_OFF; +} + + +class CMessage : public CPointEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); +private: +}; + +LINK_ENTITY_TO_CLASS( env_message, CMessage ); + + +void CMessage::Spawn( void ) +{ + Precache(); + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + switch( pev->impulse ) + { + case 1: // Medium radius + pev->speed = ATTN_STATIC; + break; + + case 2: // Large radius + pev->speed = ATTN_NORM; + break; + + case 3: //EVERYWHERE + pev->speed = ATTN_NONE; + break; + + default: + case 0: // Small radius + pev->speed = ATTN_IDLE; + break; + } + pev->impulse = 0; + + // No volume, use normal + if ( pev->scale <= 0 ) + pev->scale = 1.0; +} + + +void CMessage::Precache( void ) +{ + if ( pev->noise ) + PRECACHE_SOUND( (char *)STRING(pev->noise) ); +} + +void CMessage::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "messagesound")) + { + pev->noise = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "messagevolume")) + { + pev->scale = atof(pkvd->szValue) * 0.1; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "messageattenuation")) + { + pev->impulse = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CMessage::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBaseEntity *pPlayer = NULL; + + if ( pev->spawnflags & SF_MESSAGE_ALL ) + UTIL_ShowMessageAll( STRING(pev->message) ); + else + { + if ( pActivator && pActivator->IsPlayer() ) + pPlayer = pActivator; + else + { + pPlayer = CBaseEntity::Instance( g_engfuncs.pfnPEntityOfEntIndex( 1 ) ); + } + if ( pPlayer ) + UTIL_ShowMessage( STRING(pev->message), pPlayer ); + } + if ( pev->noise ) + { + EMIT_SOUND( edict(), CHAN_BODY, STRING(pev->noise), pev->scale, pev->speed ); + } + if ( pev->spawnflags & SF_MESSAGE_ONCE ) + UTIL_Remove( this ); + + SUB_UseTargets( this, USE_TOGGLE, 0 ); +} + + + +//========================================================= +// FunnelEffect +//========================================================= +class CEnvFunnel : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int m_iSprite; // Don't save, precache +}; + +void CEnvFunnel :: Precache ( void ) +{ + //LRC + if (pev->netname) + m_iSprite = PRECACHE_MODEL ( (char*)STRING(pev->netname) ); + else + m_iSprite = PRECACHE_MODEL ( "sprites/flare6.spr" ); +} + +LINK_ENTITY_TO_CLASS( env_funnel, CEnvFunnel ); + +void CEnvFunnel::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + //LRC + Vector vecPos; + if (pev->message) + vecPos = CalcLocus_Position( this, pActivator, STRING(pev->message) ); + else + vecPos = pev->origin; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_LARGEFUNNEL ); + WRITE_COORD( vecPos.x ); + WRITE_COORD( vecPos.y ); + WRITE_COORD( vecPos.z ); + WRITE_SHORT( m_iSprite ); + + if ( pev->spawnflags & SF_FUNNEL_REVERSE )// funnel flows in reverse? + { + WRITE_SHORT( 1 ); + } + else + { + WRITE_SHORT( 0 ); + } + + + MESSAGE_END(); + + if (!(pev->spawnflags & SF_FUNNEL_REPEATABLE)) + { + SetThink(&CEnvFunnel:: SUB_Remove ); + SetNextThink( 0 ); + } +} + +void CEnvFunnel::Spawn( void ) +{ + Precache(); + pev->solid = SOLID_NOT; + pev->effects |= EF_NODRAW; +} + + +//========================================================= +// LRC - All the particle effects from Quake 1 +//========================================================= +#define SF_QUAKEFX_REPEATABLE 1 +class CEnvQuakeFx : public CPointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; + +LINK_ENTITY_TO_CLASS( env_quakefx, CEnvQuakeFx ); + +void CEnvQuakeFx::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + Vector vecPos; + if (pev->message) + vecPos = CalcLocus_Position( this, pActivator, STRING(pev->message) ); + else + vecPos = pev->origin; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( pev->impulse ); + WRITE_COORD( vecPos.x ); + WRITE_COORD( vecPos.y ); + WRITE_COORD( vecPos.z ); + if (pev->impulse == TE_PARTICLEBURST) + { + WRITE_SHORT( pev->armortype ); // radius + WRITE_BYTE( pev->frags ); // particle colour + WRITE_BYTE( pev->health * 10 ); // duration + } + else if (pev->impulse == TE_EXPLOSION2) + { + // these fields seem to have no effect - except that it + // crashes when I send "0" for the number of colours.. + WRITE_BYTE( 0 ); // colour + WRITE_BYTE( 1 ); // number of colours + } + MESSAGE_END(); + + if (!(pev->spawnflags & SF_QUAKEFX_REPEATABLE)) + { + SetThink(&CEnvQuakeFx:: SUB_Remove ); + SetNextThink( 0 ); + } +} + + +//========================================================= +// LRC - Beam Trail effect +//========================================================= +#define SF_BEAMTRAIL_OFF 1 +class CEnvBeamTrail : public CPointEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + STATE GetState( void ); + void EXPORT StartTrailThink ( void ); + void Affect( CBaseEntity *pTarget, USE_TYPE useType ); + + int m_iSprite; // Don't save, precache +}; + +void CEnvBeamTrail :: Precache ( void ) +{ + if (pev->target) + PRECACHE_MODEL("sprites/null.spr"); + if (pev->netname) + m_iSprite = PRECACHE_MODEL ( (char*)STRING(pev->netname) ); +} + +LINK_ENTITY_TO_CLASS( env_beamtrail, CEnvBeamTrail ); + +STATE CEnvBeamTrail :: GetState ( void ) +{ + if (pev->spawnflags & SF_BEAMTRAIL_OFF) + return STATE_OFF; + else + return STATE_ON; +} + +void CEnvBeamTrail :: StartTrailThink ( void ) +{ + pev->spawnflags |= SF_BEAMTRAIL_OFF; // fake turning off, so the Use turns it on properly + Use(this, this, USE_ON, 0); +} + +void CEnvBeamTrail::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (pev->target) + { + CBaseEntity *pTarget = UTIL_FindEntityByTargetname(NULL, STRING(pev->target), pActivator ); + while (pTarget) + { + Affect(pTarget, useType); + pTarget = UTIL_FindEntityByTargetname(pTarget, STRING(pev->target), pActivator ); + } + } + else + { + if (!ShouldToggle( useType )) + return; + Affect(this, useType); + } + + if (useType == USE_ON) + pev->spawnflags &= ~SF_BEAMTRAIL_OFF; + else if (useType == USE_OFF) + pev->spawnflags |= SF_BEAMTRAIL_OFF; + else if (useType == USE_TOGGLE) + { + if (pev->spawnflags & SF_BEAMTRAIL_OFF) + pev->spawnflags &= ~SF_BEAMTRAIL_OFF; + else + pev->spawnflags |= SF_BEAMTRAIL_OFF; + } +} + +void CEnvBeamTrail::Affect( CBaseEntity *pTarget, USE_TYPE useType ) +{ + if (useType == USE_ON || pev->spawnflags & SF_BEAMTRAIL_OFF) + { + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMFOLLOW ); + WRITE_SHORT(pTarget->entindex()); // entity + WRITE_SHORT( m_iSprite ); // model + WRITE_BYTE( pev->health*10 ); // life + WRITE_BYTE( pev->armorvalue ); // width + WRITE_BYTE( pev->rendercolor.x ); // r, g, b + WRITE_BYTE( pev->rendercolor.y ); // r, g, b + WRITE_BYTE( pev->rendercolor.z ); // r, g, b + WRITE_BYTE( pev->renderamt ); // brightness + MESSAGE_END(); + } + else + { + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE(TE_KILLBEAM); + WRITE_SHORT(pTarget->entindex()); + MESSAGE_END(); + } +} + +void CEnvBeamTrail::Spawn( void ) +{ + Precache(); + + SET_MODEL(ENT(pev), "sprites/null.spr"); + UTIL_SetSize(pev, Vector(0, 0, 0), Vector(0, 0, 0)); + + if (!(pev->spawnflags & SF_BEAMTRAIL_OFF)) + { + SetThink(&CEnvBeamTrail::StartTrailThink); + UTIL_DesiredThink( this ); + } +} + +//========================================================= +//LRC- the long-awaited effect. (Rain, in the desert? :) +// +//FIXME: give designers a _lot_ more control. +//========================================================= +#define MAX_RAIN_BEAMS 32 + +#define AXIS_X 1 +#define AXIS_Y 2 +#define AXIS_Z 0 + +#define EXTENT_OBSTRUCTED 1 +#define EXTENT_ARCING 2 +#define EXTENT_OBSTRUCTED_REVERSE 3 +#define EXTENT_ARCING_REVERSE 4 +#define EXTENT_ARCING_THROUGH 5 //AJH + +class CEnvRain : public CBaseEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Think( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + STATE m_iState; + int m_spriteTexture; + int m_iszSpriteName; // have to saverestore this, the beams keep a link to it + int m_dripSize; + int m_minDripSpeed; + int m_maxDripSpeed; + int m_burstSize; + int m_brightness; + int m_pitch; // don't saverestore this + float m_flUpdateTime; + float m_flMaxUpdateTime; +// CBeam* m_pBeams[MAX_RAIN_BEAMS]; + int m_axis; + int m_iExtent; + float m_fLifeTime; + int m_iNoise; + + virtual STATE GetState( void ) { return m_iState; }; +}; + +LINK_ENTITY_TO_CLASS( env_rain, CEnvRain ); + +TYPEDESCRIPTION CEnvRain::m_SaveData[] = +{ + DEFINE_FIELD( CEnvRain, m_iState, FIELD_INTEGER ), + DEFINE_FIELD( CEnvRain, m_spriteTexture, FIELD_INTEGER ), + DEFINE_FIELD( CEnvRain, m_dripSize, FIELD_INTEGER ), + DEFINE_FIELD( CEnvRain, m_minDripSpeed, FIELD_INTEGER ), + DEFINE_FIELD( CEnvRain, m_maxDripSpeed, FIELD_INTEGER ), + DEFINE_FIELD( CEnvRain, m_burstSize, FIELD_INTEGER ), + DEFINE_FIELD( CEnvRain, m_brightness, FIELD_INTEGER ), + DEFINE_FIELD( CEnvRain, m_flUpdateTime, FIELD_FLOAT ), + DEFINE_FIELD( CEnvRain, m_flMaxUpdateTime, FIELD_FLOAT ), + DEFINE_FIELD( CEnvRain, m_iszSpriteName, FIELD_STRING ), + DEFINE_FIELD( CEnvRain, m_axis, FIELD_INTEGER ), + DEFINE_FIELD( CEnvRain, m_iExtent, FIELD_INTEGER ), + DEFINE_FIELD( CEnvRain, m_fLifeTime, FIELD_FLOAT ), + DEFINE_FIELD( CEnvRain, m_iNoise, FIELD_INTEGER ), +// DEFINE_FIELD( CEnvRain, m_pBeams, FIELD_CLASSPTR, MAX_RAIN_BEAMS ), +}; + +IMPLEMENT_SAVERESTORE( CEnvRain, CBaseEntity ); + +void CEnvRain::Precache( void ) +{ + m_spriteTexture = PRECACHE_MODEL( (char *)STRING(m_iszSpriteName) ); +} + +void CEnvRain::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_dripSize")) + { + m_dripSize = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_burstSize")) + { + m_burstSize = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_dripSpeed")) + { + int temp = atoi(pkvd->szValue); + m_maxDripSpeed = temp + (temp/4); + m_minDripSpeed = temp - (temp/4); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_brightness")) + { + m_brightness = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flUpdateTime")) + { + m_flUpdateTime = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flMaxUpdateTime")) + { + m_flMaxUpdateTime = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitch")) + { + m_pitch = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "texture")) + { + m_iszSpriteName = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_axis")) + { + m_axis = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iExtent")) + { + m_iExtent = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fLifeTime")) + { + m_fLifeTime = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iNoise")) + { + m_iNoise = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CEnvRain::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (!ShouldToggle(useType)) return; + + if (m_iState == STATE_ON) + { + m_iState = STATE_OFF; + DontThink(); + } + else + { + m_iState = STATE_ON; + SetNextThink( 0.1 ); + } +} + +#define SF_RAIN_START_OFF 1 + +void CEnvRain::Spawn( void ) +{ + Precache(); + SET_MODEL( ENT(pev), STRING(pev->model) ); // Set size + pev->solid = SOLID_NOT; + pev->effects |= EF_NODRAW; + + if (pev->rendercolor == g_vecZero) + pev->rendercolor = Vector(255,255,255); + + if (m_pitch) + pev->angles.x = m_pitch; + else if (pev->angles.x == 0) // don't allow horizontal rain. + pev->angles.x = 90; + + if (m_burstSize == 0) // in case the level designer forgot to set it. + m_burstSize = 2; + + if (pev->spawnflags & SF_RAIN_START_OFF) + m_iState = STATE_OFF; + else + { + m_iState = STATE_ON; + SetNextThink( 0.1 ); + } +} + +void CEnvRain::Think( void ) +{ +// ALERT(at_console,"RainThink %d %d %d %s\n",m_spriteTexture,m_dripSize,m_brightness,STRING(m_iszSpriteName)); + Vector vecSrc; + Vector vecDest; + + UTIL_MakeVectors(pev->angles); + Vector vecOffs = gpGlobals->v_forward; + switch (m_axis) + { + case AXIS_X: + vecOffs = vecOffs * (pev->size.x / vecOffs.x); + break; + case AXIS_Y: + vecOffs = vecOffs * (pev->size.y / vecOffs.y); + break; + case AXIS_Z: + vecOffs = vecOffs * (pev->size.z / vecOffs.z); + break; + } + +// ALERT(at_console,"RainThink offs.z = %f, size.z = %f\n",vecOffs.z,pev->size.z); + + int repeats; + if (!m_fLifeTime && !m_flUpdateTime && !m_flMaxUpdateTime) + repeats = m_burstSize * 3; + else + repeats = m_burstSize; + + int drawn = 0; + int tries = 0; + TraceResult tr; + BOOL bDraw; + + while (drawn < repeats && tries < (repeats*3)) + { + tries++; + if (m_axis == AXIS_X) + vecSrc.x = pev->maxs.x; + else + vecSrc.x = pev->mins.x + RANDOM_LONG(0, pev->size.x); + if (m_axis == AXIS_Y) + vecSrc.y = pev->maxs.y; + else + vecSrc.y = pev->mins.y + RANDOM_LONG(0, pev->size.y); + if (m_axis == AXIS_Z) + vecSrc.z = pev->maxs.z; + else + vecSrc.z = pev->mins.z + RANDOM_LONG(0, pev->size.z); + vecDest = vecSrc - vecOffs; + bDraw = TRUE; + + switch (m_iExtent) + { + case EXTENT_OBSTRUCTED: + UTIL_TraceLine( vecSrc, vecDest, ignore_monsters, NULL, &tr); + vecDest = tr.vecEndPos; + break; + case EXTENT_OBSTRUCTED_REVERSE: + UTIL_TraceLine( vecDest, vecSrc, ignore_monsters, NULL, &tr); + vecSrc = tr.vecEndPos; + break; + case EXTENT_ARCING: + UTIL_TraceLine( vecSrc, vecDest, ignore_monsters, NULL, &tr); + if (tr.flFraction == 1.0) bDraw = FALSE; + vecDest = tr.vecEndPos; + break; + case EXTENT_ARCING_THROUGH: //AJH - Arcs full length of brush only when blocked + UTIL_TraceLine( vecDest, vecSrc, dont_ignore_monsters, NULL, &tr); + if (tr.flFraction == 1.0) bDraw = FALSE; + break; + case EXTENT_ARCING_REVERSE: + UTIL_TraceLine( vecDest, vecSrc, ignore_monsters, NULL, &tr); + if (tr.flFraction == 1.0) bDraw = FALSE; + vecSrc = tr.vecEndPos; + break; + } +// vecDest.z = pev->mins.z; + if (!bDraw) continue; + + drawn++; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS ); + WRITE_COORD(vecDest.x); + WRITE_COORD(vecDest.y); + WRITE_COORD(vecDest.z); + WRITE_COORD(vecSrc.x); + WRITE_COORD(vecSrc.y); + WRITE_COORD(vecSrc.z); + WRITE_SHORT( m_spriteTexture ); + WRITE_BYTE( (int)0 ); // framestart + WRITE_BYTE( (int)0 ); // framerate + if (m_fLifeTime) // life + WRITE_BYTE( (int)(m_fLifeTime*10) ); + else if (m_flMaxUpdateTime) + WRITE_BYTE( (int)( RANDOM_FLOAT(m_flUpdateTime, m_flMaxUpdateTime)*30 )); + else + WRITE_BYTE( (int)(m_flUpdateTime * 30) ); // life + WRITE_BYTE( m_dripSize ); // width + WRITE_BYTE( m_iNoise ); // noise + WRITE_BYTE( (int)pev->rendercolor.x ); // r, + WRITE_BYTE( (int)pev->rendercolor.y ); // g, + WRITE_BYTE( (int)pev->rendercolor.z ); // b + WRITE_BYTE( m_brightness ); // brightness + WRITE_BYTE( (int)RANDOM_LONG(m_minDripSpeed,m_maxDripSpeed) ); // speed + MESSAGE_END(); + } + + // drawn will be false if we didn't draw anything. + if (pev->target && drawn) + FireTargets(STRING(pev->target), this, this, USE_TOGGLE, 0); + + if (m_flMaxUpdateTime) + SetNextThink( RANDOM_FLOAT(m_flMaxUpdateTime, m_flUpdateTime) ); + else if (m_flUpdateTime) + SetNextThink( m_flUpdateTime ); +} + +//================================================================== +//LRC- Xen monsters' warp-in effect, for those too lazy to build it. :) +//================================================================== +class CEnvWarpBall : public CBaseEntity +{ +public: + void Precache( void ); + void Spawn( void ) { Precache(); } + void Think( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + +LINK_ENTITY_TO_CLASS( env_warpball, CEnvWarpBall ); + +void CEnvWarpBall::Precache( void ) +{ + PRECACHE_MODEL( "sprites/lgtning.spr" ); + PRECACHE_MODEL( "sprites/Fexplo1.spr" ); + PRECACHE_MODEL( "sprites/XFlare1.spr" ); + PRECACHE_SOUND( "debris/beamstart2.wav" ); + PRECACHE_SOUND( "debris/beamstart7.wav" ); +} + +void CEnvWarpBall::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int iTimes = 0; + int iDrawn = 0; + TraceResult tr; + Vector vecDest; + CBeam *pBeam; + while (iDrawnfrags && iTimes<(pev->frags * 3)) // try to draw beams, but give up after 3x tries. + { + vecDest = pev->health * (Vector(RANDOM_FLOAT(-1,1), RANDOM_FLOAT(-1,1), RANDOM_FLOAT(-1,1)).Normalize()); + UTIL_TraceLine( pev->origin, pev->origin + vecDest, ignore_monsters, NULL, &tr); + if (tr.flFraction != 1.0) + { + // we hit something. + iDrawn++; + pBeam = CBeam::BeamCreate("sprites/lgtning.spr",200); + pBeam->PointsInit( pev->origin, tr.vecEndPos ); + pBeam->SetColor( 197, 243, 169 ); + pBeam->SetNoise( 65 ); + pBeam->SetBrightness( 150 ); + pBeam->SetWidth( 18 ); + pBeam->SetScrollRate( 35 ); + pBeam->SetThink(&CBeam:: SUB_Remove ); + pBeam->SetNextThink( 1 ); + } + iTimes++; + } + EMIT_SOUND( edict(), CHAN_BODY, "debris/beamstart2.wav", 1, ATTN_NORM ); + + CSprite *pSpr = CSprite::SpriteCreate( "sprites/Fexplo1.spr", pev->origin, TRUE ); + pSpr->AnimateAndDie( 10 ); + pSpr->SetTransparency(kRenderGlow, 77, 210, 130, 255, kRenderFxNoDissipation); + + pSpr = CSprite::SpriteCreate( "sprites/XFlare1.spr", pev->origin, TRUE ); + pSpr->AnimateAndDie( 10 ); + pSpr->SetTransparency(kRenderGlow, 184, 250, 214, 255, kRenderFxNoDissipation); + + SetNextThink( 0.5 ); +} + +void CEnvWarpBall::Think( void ) +{ + EMIT_SOUND( edict(), CHAN_ITEM, "debris/beamstart7.wav", 1, ATTN_NORM ); + SUB_UseTargets( this, USE_TOGGLE, 0); +} + +//================================================================== +//LRC- Shockwave effect, like when a Houndeye attacks. +//================================================================== +#define SF_SHOCKWAVE_CENTERED 1 +#define SF_SHOCKWAVE_REPEATABLE 2 + +class CEnvShockwave : public CPointEntity +{ +public: + void Precache( void ); + void Spawn( void ) { Precache(); } + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void DoEffect( Vector vecPos ); + + int m_iTime; + int m_iRadius; + int m_iHeight; + int m_iScrollRate; + int m_iNoise; + int m_iFrameRate; + int m_iStartFrame; + int m_iSpriteTexture; + char m_cType; + int m_iszPosition; +}; + +LINK_ENTITY_TO_CLASS( env_shockwave, CEnvShockwave ); + +TYPEDESCRIPTION CEnvShockwave::m_SaveData[] = +{ + DEFINE_FIELD( CEnvShockwave, m_iHeight, FIELD_INTEGER ), + DEFINE_FIELD( CEnvShockwave, m_iTime, FIELD_INTEGER ), + DEFINE_FIELD( CEnvShockwave, m_iRadius, FIELD_INTEGER ), + DEFINE_FIELD( CEnvShockwave, m_iScrollRate, FIELD_INTEGER ), + DEFINE_FIELD( CEnvShockwave, m_iNoise, FIELD_INTEGER ), + DEFINE_FIELD( CEnvShockwave, m_iFrameRate, FIELD_INTEGER ), + DEFINE_FIELD( CEnvShockwave, m_iStartFrame, FIELD_INTEGER ), + DEFINE_FIELD( CEnvShockwave, m_iSpriteTexture, FIELD_INTEGER ), + DEFINE_FIELD( CEnvShockwave, m_cType, FIELD_CHARACTER ), + DEFINE_FIELD( CEnvShockwave, m_iszPosition, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CEnvShockwave, CBaseEntity ); + +void CEnvShockwave::Precache( void ) +{ + m_iSpriteTexture = PRECACHE_MODEL( (char *)STRING(pev->netname) ); +} + +void CEnvShockwave::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iTime")) + { + m_iTime = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iRadius")) + { + m_iRadius = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iHeight")) + { + m_iHeight = atoi(pkvd->szValue)/2; //LRC- the actual height is doubled when drawn + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iScrollRate")) + { + m_iScrollRate = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iNoise")) + { + m_iNoise = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iFrameRate")) + { + m_iFrameRate = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iStartFrame")) + { + m_iStartFrame = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszPosition")) + { + m_iszPosition = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_cType")) + { + m_cType = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CEnvShockwave::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + Vector vecPos; + if (m_iszPosition) + vecPos = CalcLocus_Position( this, pActivator, STRING(m_iszPosition) ); + else + vecPos = pev->origin; + + if (!(pev->spawnflags & SF_SHOCKWAVE_CENTERED)) + vecPos.z += m_iHeight; + + // blast circle + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + if (m_cType) + WRITE_BYTE( m_cType ); + else + WRITE_BYTE( TE_BEAMCYLINDER ); + WRITE_COORD( vecPos.x );// coord coord coord (center position) + WRITE_COORD( vecPos.y ); + WRITE_COORD( vecPos.z ); + WRITE_COORD( vecPos.x );// coord coord coord (axis and radius) + WRITE_COORD( vecPos.y ); + WRITE_COORD( vecPos.z + m_iRadius ); + WRITE_SHORT( m_iSpriteTexture ); // short (sprite index) + WRITE_BYTE( m_iStartFrame ); // byte (starting frame) + WRITE_BYTE( m_iFrameRate ); // byte (frame rate in 0.1's) + WRITE_BYTE( m_iTime ); // byte (life in 0.1's) + WRITE_BYTE( m_iHeight ); // byte (line width in 0.1's) + WRITE_BYTE( m_iNoise ); // byte (noise amplitude in 0.01's) + WRITE_BYTE( pev->rendercolor.x ); // byte,byte,byte (color) + WRITE_BYTE( pev->rendercolor.y ); + WRITE_BYTE( pev->rendercolor.z ); + WRITE_BYTE( pev->renderamt ); // byte (brightness) + WRITE_BYTE( m_iScrollRate ); // byte (scroll speed in 0.1's) + MESSAGE_END(); + + if (!(pev->spawnflags & SF_SHOCKWAVE_REPEATABLE)) + { + SetThink(&CEnvShockwave:: SUB_Remove ); + SetNextThink( 0 ); + } +} + +//================================================================== +//LRC- env_dlight; Dynamic Entity Light creator +//================================================================== +#define SF_DLIGHT_ONLYONCE 1 +#define SF_DLIGHT_STARTON 2 +class CEnvDLight : public CPointEntity +{ +public: + void PostSpawn( void ); + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Think( void ); + void DesiredAction( void ); + virtual void MakeLight( int iTime ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + STATE GetState( void ) + { + if (pev->spawnflags & SF_DLIGHT_STARTON) + return STATE_ON; + else + return STATE_OFF; + } + + Vector m_vecPos; + int m_iKey; + static int ms_iNextFreeKey; +}; + +LINK_ENTITY_TO_CLASS( env_dlight, CEnvDLight ); + +TYPEDESCRIPTION CEnvDLight::m_SaveData[] = +{ + DEFINE_FIELD( CEnvDLight, m_vecPos, FIELD_VECTOR ), + DEFINE_FIELD( CEnvDLight, m_iKey, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CEnvDLight, CPointEntity ); + +int CEnvDLight::ms_iNextFreeKey = 1; + +void CEnvDLight::PostSpawn( void ) +{ + // each env_dlight uses its own key to reference the light on the client + m_iKey = ms_iNextFreeKey; + ms_iNextFreeKey++; + + if (FStringNull(pev->targetname) || pev->spawnflags & SF_DLIGHT_STARTON) + { + UTIL_DesiredAction(this); + } +} + +void CEnvDLight::DesiredAction( void ) +{ + pev->spawnflags &= ~SF_DLIGHT_STARTON; + Use(this, this, USE_ON, 0); +} + +void CEnvDLight::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (!ShouldToggle(useType)) + { + return; + } + if (GetState() == STATE_ON) + { + // turn off + MakeLight(false); + pev->spawnflags &= ~SF_DLIGHT_STARTON; + DontThink(); + return; + } + + if (pev->message) + { + m_vecPos = CalcLocus_Position( this, pActivator, STRING(pev->message) ); + } + else + { + m_vecPos = pev->origin; + } + + // turn on + MakeLight(true); + pev->spawnflags |= SF_DLIGHT_STARTON; + + if (pev->health) + { + SetNextThink(pev->health); + } + else + { + if (pev->spawnflags & SF_DLIGHT_ONLYONCE) + { + SetThink( SUB_Remove ); + SetNextThink( 0 ); + } + } +} + +extern int gmsgKeyedDLight; + +void CEnvDLight::MakeLight( BOOL bActive) +{ + MESSAGE_BEGIN( MSG_ALL, gmsgKeyedDLight, NULL ); + WRITE_BYTE( m_iKey ); + WRITE_BYTE( bActive ); // visible? + if (bActive) + { + WRITE_COORD( m_vecPos.x ); // X + WRITE_COORD( m_vecPos.y ); // Y + WRITE_COORD( m_vecPos.z ); // Z + WRITE_BYTE( pev->renderamt ); // radius * 0.1 + WRITE_BYTE( pev->rendercolor.x ); // r + WRITE_BYTE( pev->rendercolor.y ); // g + WRITE_BYTE( pev->rendercolor.z ); // b + } + MESSAGE_END(); +} + +void CEnvDLight::Think( void ) +{ + // turn off the light + MakeLight( false ); + pev->spawnflags &= ~SF_DLIGHT_STARTON; + + if (pev->spawnflags & SF_DLIGHT_ONLYONCE) + { + SetThink( SUB_Remove ); + SetNextThink( 0 ); + } +} + +//================================================================== +//LRC- env_elight; Dynamic Entity Light creator +//================================================================== +class CEnvELight : public CEnvDLight +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void MakeLight(int iTime); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + EHANDLE m_hAttach; +}; + +LINK_ENTITY_TO_CLASS( env_elight, CEnvELight ); + +TYPEDESCRIPTION CEnvELight::m_SaveData[] = +{ + DEFINE_FIELD( CEnvELight, m_hAttach, FIELD_EHANDLE ), +}; + +IMPLEMENT_SAVERESTORE( CEnvELight, CEnvDLight ); + +void CEnvELight::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (pev->target) + { + m_hAttach = UTIL_FindEntityByTargetname( NULL, STRING(pev->target), pActivator); + if (m_hAttach == NULL) + { + ALERT(at_console, "env_elight \"%s\" can't find target %s\n", STRING(pev->targetname), STRING(pev->target)); + return; // error? + } + } + else + { + m_hAttach = this; + } + + CEnvDLight::Use(pActivator, pCaller, useType, value); +} + +void CEnvELight::MakeLight(int iTime) +{ + if (m_hAttach == NULL) + { + DontThink(); + pev->takedamage = 0; + return; + } + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( m_hAttach->entindex( ) + 0x1000 * pev->impulse ); // entity, attachment + WRITE_COORD( m_vecPos.x ); // X + WRITE_COORD( m_vecPos.y ); // Y + WRITE_COORD( m_vecPos.z ); // Z + WRITE_COORD( pev->renderamt ); // radius * 0.1 + WRITE_BYTE( pev->rendercolor.x ); // r + WRITE_BYTE( pev->rendercolor.y ); // g + WRITE_BYTE( pev->rendercolor.z ); // b + WRITE_BYTE( iTime ); // time * 10 + WRITE_COORD( pev->frags ); // decay * 0.1 + MESSAGE_END( ); +} + +//========================================================= +// Beverage Dispenser +// overloaded pev->frags, is now a flag for whether or not a can is stuck in the dispenser. +// overloaded pev->health, is now how many cans remain in the machine. +//========================================================= +class CEnvBeverage : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + // it's 'on' while there are cans left + virtual STATE GetState( void ) { return (pev->health > 0)?STATE_ON:STATE_OFF; }; +}; + +void CEnvBeverage :: Precache ( void ) +{ + PRECACHE_MODEL( "models/can.mdl" ); + PRECACHE_SOUND( "weapons/g_bounce3.wav" ); +} + +LINK_ENTITY_TO_CLASS( env_beverage, CEnvBeverage ); + +void CEnvBeverage::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->frags != 0 || pev->health <= 0 ) + { + // no more cans while one is waiting in the dispenser, or if I'm out of cans. + return; + } + + Vector vecPos; + if (pev->target) + vecPos = CalcLocus_Position( this, pActivator, STRING(pev->target) ); + else + vecPos = pev->origin; + + CBaseEntity *pCan = CBaseEntity::Create( "item_sodacan", vecPos, pev->angles, edict() ); + + if ( pev->skin == 6 ) + { + // random + pCan->pev->skin = RANDOM_LONG( 0, 5 ); + } + else + { + pCan->pev->skin = pev->skin; + } + + pev->frags = 1; + pev->health--; + + //SetThink (SUB_Remove); + //pev->nextthink = gpGlobals->time; +} + +void CEnvBeverage::Spawn( void ) +{ + Precache(); + pev->solid = SOLID_NOT; + pev->effects |= EF_NODRAW; + pev->frags = 0; + + if ( pev->health == 0 ) + { + pev->health = 10; + } +} + +//========================================================= +// Soda can +//========================================================= +class CItemSoda : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void EXPORT CanThink ( void ); + void EXPORT CanTouch ( CBaseEntity *pOther ); +}; + +void CItemSoda :: Precache ( void ) +{ + // added for Nemo1024 --LRC + PRECACHE_MODEL( "models/can.mdl" ); + PRECACHE_SOUND( "weapons/g_bounce3.wav" ); +} + +LINK_ENTITY_TO_CLASS( item_sodacan, CItemSoda ); + +void CItemSoda::Spawn( void ) +{ + Precache(); + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_TOSS; + + SET_MODEL ( ENT(pev), "models/can.mdl" ); + UTIL_SetSize ( pev, Vector ( 0, 0, 0 ), Vector ( 0, 0, 0 ) ); + + SetThink(&CItemSoda::CanThink); + SetNextThink( 0.5 ); +} + +void CItemSoda::CanThink ( void ) +{ + EMIT_SOUND (ENT(pev), CHAN_WEAPON, "weapons/g_bounce3.wav", 1, ATTN_NORM ); + + pev->solid = SOLID_TRIGGER; + UTIL_SetSize ( pev, Vector ( -8, -8, 0 ), Vector ( 8, 8, 8 ) ); + SetThink ( NULL ); + SetTouch(&CItemSoda:: CanTouch ); +} + +void CItemSoda::CanTouch ( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + { + return; + } + + // spoit sound here + + pOther->TakeHealth( 1, DMG_GENERIC );// a bit of health. + + if ( !FNullEnt( pev->owner ) ) + { + // tell the machine the can was taken + pev->owner->v.frags = 0; + } + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects |= EF_NODRAW; + SetTouch ( NULL ); + SetThink(&CItemSoda:: SUB_Remove ); + SetNextThink( 0 ); +} + +//========================================================= +// LRC - env_fog, extended a bit from the DMC version +//========================================================= +#define SF_FOG_ACTIVE 1 +#define SF_FOG_FADING 0x8000 + +class CEnvFog : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void EXPORT ResumeThink( void ); + void EXPORT Resume2Think( void ); + void EXPORT TurnOn( void ); + void EXPORT TurnOff( void ); + void EXPORT FadeInDone( void ); + void EXPORT FadeOutDone( void ); + void SendData( Vector col, int fFadeTime, int StartDist, int iEndDist); + void KeyValue( KeyValueData *pkvd ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + STATE GetState( void ); + + int m_iStartDist; + int m_iEndDist; + float m_iFadeIn; + float m_iFadeOut; + float m_fHoldTime; + float m_fFadeStart; // if we're fading in/out, then when did the fade start? +}; + +TYPEDESCRIPTION CEnvFog::m_SaveData[] = +{ + DEFINE_FIELD( CEnvFog, m_iStartDist, FIELD_INTEGER ), + DEFINE_FIELD( CEnvFog, m_iEndDist, FIELD_INTEGER ), + DEFINE_FIELD( CEnvFog, m_iFadeIn, FIELD_INTEGER ), + DEFINE_FIELD( CEnvFog, m_iFadeOut, FIELD_INTEGER ), + DEFINE_FIELD( CEnvFog, m_fHoldTime, FIELD_FLOAT ), + DEFINE_FIELD( CEnvFog, m_fFadeStart, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CEnvFog, CBaseEntity ); + +void CEnvFog :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "startdist")) + { + m_iStartDist = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "enddist")) + { + m_iEndDist = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "fadein")) + { + m_iFadeIn = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "fadeout")) + { + m_iFadeOut = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "holdtime")) + { + m_fHoldTime = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +STATE CEnvFog::GetState( void ) +{ + if (pev->spawnflags & SF_FOG_ACTIVE) + { + if (pev->spawnflags & SF_FOG_FADING) + return STATE_TURN_ON; + else + return STATE_ON; + } + else + { + if (pev->spawnflags & SF_FOG_FADING) + return STATE_TURN_OFF; + else + return STATE_OFF; + } +} + +void CEnvFog :: Spawn ( void ) +{ + pev->effects |= EF_NODRAW; + + if (pev->targetname == 0) + pev->spawnflags |= SF_FOG_ACTIVE; + + if (pev->spawnflags & SF_FOG_ACTIVE) + { + SetThink(&CEnvFog :: TurnOn ); + UTIL_DesiredThink( this ); + } + +// Precache is now used only to continue after a game has loaded. +// Precache(); + + // things get messed up if we try to draw fog with a startdist + // or an enddist of 0, so we don't allow it. + if (m_iStartDist == 0) m_iStartDist = 1; + if (m_iEndDist == 0) m_iEndDist = 1; +} + +void CEnvFog :: Precache ( void ) +{ + if (pev->spawnflags & SF_FOG_ACTIVE) + { + SetThink(&CEnvFog :: ResumeThink ); + SetNextThink( 0.1 ); + } +} + +extern int gmsgSetFog; + +void CEnvFog :: TurnOn ( void ) +{ +// ALERT(at_console, "Fog turnon %f\n", gpGlobals->time); + + pev->spawnflags |= SF_FOG_ACTIVE; + + if( m_iFadeIn ) + { + pev->spawnflags |= SF_FOG_FADING; + SendData( pev->rendercolor, m_iFadeIn, m_iStartDist, m_iEndDist); + SetNextThink( m_iFadeIn ); + SetThink(&CEnvFog :: FadeInDone ); + } + else + { + pev->spawnflags &= ~SF_FOG_FADING; + SendData( pev->rendercolor, 0, m_iStartDist, m_iEndDist); + if (m_fHoldTime) + { + SetNextThink( m_fHoldTime ); + SetThink(&CEnvFog :: TurnOff ); + } + } +} + +void CEnvFog :: TurnOff ( void ) +{ +// ALERT(at_console, "Fog turnoff\n"); + + pev->spawnflags &= ~SF_FOG_ACTIVE; + + if( m_iFadeOut ) + { + pev->spawnflags |= SF_FOG_FADING; + SendData( pev->rendercolor, -m_iFadeOut, m_iStartDist, m_iEndDist); + SetNextThink( m_iFadeOut ); + SetThink(&CEnvFog :: FadeOutDone ); + } + else + { + pev->spawnflags &= ~SF_FOG_FADING; + SendData( g_vecZero, 0, 0, 0 ); + DontThink(); + } +} + +//yes, this intermediate think function is necessary. +// the engine seems to ignore the nextthink time when starting up. +// So this function gets called immediately after the precache finishes, +// regardless of what nextthink time is specified. +void CEnvFog :: ResumeThink ( void ) +{ +// ALERT(at_console, "Fog resume %f\n", gpGlobals->time); + SetThink(&CEnvFog ::FadeInDone); + SetNextThink(0.1); +} + +void CEnvFog :: FadeInDone ( void ) +{ + pev->spawnflags &= ~SF_FOG_FADING; + SendData( pev->rendercolor, 0, m_iStartDist, m_iEndDist); + + if (m_fHoldTime) + { + SetNextThink( m_fHoldTime ); + SetThink(&CEnvFog :: TurnOff ); + } +} + +void CEnvFog :: FadeOutDone ( void ) +{ + pev->spawnflags &= ~SF_FOG_FADING; + SendData( g_vecZero, 0, 0, 0); +} + +void CEnvFog :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ +// ALERT(at_console, "Fog use %s %s\n", GetStringForUseType(useType), GetStringForState(GetState())); + if (ShouldToggle(useType)) + { + if (pev->spawnflags & SF_FOG_ACTIVE) + TurnOff(); + else + TurnOn(); + } +} + +void CEnvFog :: SendData ( Vector col, int iFadeTime, int iStartDist, int iEndDist ) +{ +// ALERT(at_console, "Fog send (%d %d %d), %d - %d\n", col.x, col.y, col.z, iStartDist, iEndDist); + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = (CBasePlayer*)UTIL_PlayerByIndex( i ); + if ( pPlayer ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgSetFog, NULL, pPlayer->pev ); + WRITE_BYTE ( col.x ); + WRITE_BYTE ( col.y ); + WRITE_BYTE ( col.z ); + WRITE_SHORT ( iFadeTime ); + WRITE_SHORT ( iStartDist ); + WRITE_SHORT ( iEndDist ); + MESSAGE_END(); + +// pPlayer->m_iFogStartDist = iStartDist; +// pPlayer->m_iFogEndDist = iEndDist; +// pPlayer->m_vecFogColor = col; +// pPlayer->m_bClientFogRefresh = FALSE; + } + } +} + +LINK_ENTITY_TO_CLASS( env_fog, CEnvFog ); + +//========================================================= +// LRC - env_sky, an unreal tournament-style sky effect +//========================================================= +class CEnvSky : public CBaseEntity +{ +public: + void Activate( void ); + void Think( void ); +}; + +void CEnvSky :: Activate ( void ) +{ + pev->effects |= EF_NODRAW; + pev->nextthink = gpGlobals->time + 1.0; +} + +extern int gmsgSetSky; + +void CEnvSky :: Think () +{ + MESSAGE_BEGIN(MSG_BROADCAST, gmsgSetSky, NULL); + WRITE_BYTE(1); // mode + WRITE_COORD(pev->origin.x); // view position + WRITE_COORD(pev->origin.y); + WRITE_COORD(pev->origin.z); + MESSAGE_END(); +} + +LINK_ENTITY_TO_CLASS( env_sky, CEnvSky ); + + + +//========================================================= +// LRC - env_particle, uses the aurora particle system +//========================================================= +//extern int gmsgParticle = 0; +#define SF_PARTICLE_ON 1 + +class CParticle : public CPointEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void SendInitMessage( CBasePlayer *player ); // buz + + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; + +LINK_ENTITY_TO_CLASS( env_particle, CParticle ); + +void CParticle::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->renderamt = 128; + pev->rendermode = kRenderTransTexture; + + // 'body' determines whether the effect is active or not + pev->body = (pev->spawnflags & SF_PARTICLE_ON) != 0; + + Precache(); + + UTIL_SetOrigin(this, pev->origin); + SET_MODEL(edict(), "sprites/null.spr"); +} + + +void CParticle::Precache( void ) +{ + PRECACHE_MODEL("sprites/null.spr"); +} + +void CParticle::SendInitMessage( CBasePlayer *player ) +{ + MESSAGE_BEGIN( MSG_ONE, gmsgParticle, NULL, player->pev ); + WRITE_ENTITY( entindex() ); + WRITE_STRING( STRING(pev->message) ); + WRITE_BYTE( 0 ); // attachment + MESSAGE_END(); +} + +void CParticle::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( ShouldToggle( useType, pev->body ) ) + { + pev->body = !pev->body; + } +} + +// ======= rain ========= +void CRainSettings :: Spawn() +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects |= EF_NODRAW; +} + +void CRainSettings::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_flDistance")) + { + Rain_Distance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iMode")) + { + Rain_Mode = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + { + CBaseEntity::KeyValue( pkvd ); + } +} + +LINK_ENTITY_TO_CLASS( rain_settings, CRainSettings ); + +TYPEDESCRIPTION CRainSettings::m_SaveData[] = +{ + DEFINE_FIELD( CRainSettings, Rain_Distance, FIELD_FLOAT ), + DEFINE_FIELD( CRainSettings, Rain_Mode, FIELD_INTEGER ), +}; IMPLEMENT_SAVERESTORE( CRainSettings, CBaseEntity ); + +void CRainModify::Spawn() +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects |= EF_NODRAW; + + if (FStringNull(pev->targetname)) + pev->spawnflags |= 1; +} + +void CRainModify::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iDripsPerSecond")) + { + Rain_Drips = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flWindX")) + { + Rain_windX = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flWindY")) + { + Rain_windY = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flRandX")) + { + Rain_randX = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flRandY")) + { + Rain_randY = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flTime")) + { + fadeTime = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + { + CBaseEntity::KeyValue( pkvd ); + } +} + +void CRainModify::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (pev->spawnflags & 1) + return; // constant + + if (gpGlobals->deathmatch) + { + ALERT(at_console, "Rain error: only static rain in multiplayer\n"); + return; // not in multiplayer + } + + CBasePlayer *pPlayer; + pPlayer = (CBasePlayer *)CBaseEntity::Instance(g_engfuncs.pfnPEntityOfEntIndex(1)); + + if (fadeTime) + { // write to 'ideal' settings + pPlayer->Rain_ideal_dripsPerSecond = Rain_Drips; + pPlayer->Rain_ideal_randX = Rain_randX; + pPlayer->Rain_ideal_randY = Rain_randY; + pPlayer->Rain_ideal_windX = Rain_windX; + pPlayer->Rain_ideal_windY = Rain_windY; + + pPlayer->Rain_endFade = gpGlobals->time + fadeTime; + pPlayer->Rain_nextFadeUpdate = gpGlobals->time + 1; + } + else + { + pPlayer->Rain_dripsPerSecond = Rain_Drips; + pPlayer->Rain_randX = Rain_randX; + pPlayer->Rain_randY = Rain_randY; + pPlayer->Rain_windX = Rain_windX; + pPlayer->Rain_windY = Rain_windY; + + pPlayer->Rain_needsUpdate = 1; + } +} + +LINK_ENTITY_TO_CLASS( rain_modify, CRainModify ); + +TYPEDESCRIPTION CRainModify::m_SaveData[] = +{ + DEFINE_FIELD( CRainModify, fadeTime, FIELD_FLOAT ), + DEFINE_FIELD( CRainModify, Rain_Drips, FIELD_INTEGER ), + DEFINE_FIELD( CRainModify, Rain_randX, FIELD_FLOAT ), + DEFINE_FIELD( CRainModify, Rain_randY, FIELD_FLOAT ), + DEFINE_FIELD( CRainModify, Rain_windX, FIELD_FLOAT ), + DEFINE_FIELD( CRainModify, Rain_windY, FIELD_FLOAT ), +}; IMPLEMENT_SAVERESTORE( CRainModify, CBaseEntity ); + +// ================================= +// buz: static decals +// +// netname - decal group name +// skin - direction specified +// ================================= +class CStaticDecal : public CPointEntity +{ +public: + void KeyValue( KeyValueData *pkvd ) + { + if (FStrEq(pkvd->szKeyName, "texture")) + { + pev->netname = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else CBaseEntity::KeyValue( pkvd ); + } + + int PasteDecal( int dir ) + { + Vector vecdir = g_vecZero; + vecdir[dir % 3] = (dir & 1) ? 32.0f : -32.0f; + + TraceResult tr; + UTIL_TraceLine ( pev->origin, pev->origin + vecdir, ignore_monsters, edict(), &tr ); + return UTIL_TraceCustomDecal( &tr, STRING( pev->netname ), pev->angles.y, TRUE ); + } + + void Spawn( void ) + { + if( pev->skin <= 0 || pev->skin > 6 ) + { + // try all directions + for( int i = 0; i < 6; i++ ) + if( PasteDecal( i )) break; + if( i == 6 ) ALERT( at_warning, "failed to place decal %s\n", STRING( pev->netname )); + } + else + { + // try specified direction + PasteDecal( pev->skin - 1 ); + } + + // NOTE: don't need to keep this entity + // with new custom decal save\restore system + UTIL_Remove( this ); + } +}; + +LINK_ENTITY_TO_CLASS( env_static_decal, CStaticDecal ); +LINK_ENTITY_TO_CLASS( infodecal, CStaticDecal ); // now an alias + +// ================================= +// g-cont: puddles +// +// just a prefab +// ================================= +class CPuddleDecal : public CPointEntity +{ +public: + void Spawn( void ) + { + TraceResult tr; + UTIL_TraceLine ( pev->origin, pev->origin + Vector( 0.0f, 0.0f, -64.0f ), ignore_monsters, edict(), &tr ); + UTIL_TraceCustomDecal( &tr, "puddle", pev->angles.y, TRUE ); + + // NOTE: don't need to keep this entity + // with new custom decal save\restore system + UTIL_Remove( this ); + } +}; + +LINK_ENTITY_TO_CLASS( env_puddle, CPuddleDecal ); + +// ================================= +// buz: 3d sky info messages +// +// envpos_sky: sets view origin in 3d sky +// origin - origin +// skin - ambientlight +// body - shadelight +// flags - use amblight hack | use shadelight hack +// +// envpos_world: sets view origin in world (when sky movement requed) +// origin - origin +// health - speed +// +// ================================= +extern int gmsgSkymarker_Sky; +extern int gmsgSkymarker_World; + +class CEnvPos_Sky : public CPointEntity +{ +public: + void Spawn() + { + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects |= EF_NODRAW; + } + + void SendInitMessage( CBasePlayer *player ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgSkymarker_Sky, NULL, player->pev ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + MESSAGE_END(); + } +}; + + +class CEnvPos_World : public CPointEntity +{ +public: + void Spawn() + { + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects |= EF_NODRAW; + } + + void SendInitMessage( CBasePlayer *player ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgSkymarker_World, NULL, player->pev ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( pev->health ); + MESSAGE_END(); + } +}; + +LINK_ENTITY_TO_CLASS( envpos_sky, CEnvPos_Sky ); +LINK_ENTITY_TO_CLASS( envpos_world, CEnvPos_World ); + + + +// buz: trigger_followme +// formally, it does not any special, just fires target entity with chosen use type, +// and player sent as caller + +class CTriggerFollow : public CPointEntity +{ +public: + void Spawn() + { + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects |= EF_NODRAW; + } + + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) + { + CBaseEntity *pEnt = CBaseEntity::Instance(g_engfuncs.pfnPEntityOfEntIndex(1)); + if (pEnt && pEnt->IsPlayer()) + { + USE_TYPE send = USE_TOGGLE; + if (pev->impulse == 0) send = USE_ON; + else if (pev->impulse == 1) send = USE_OFF; + FireTargets(STRING(pev->target), this, pEnt, send, 0); + } + else + ALERT(at_console, "ERROR: cant get player!\n"); + } +}; + +LINK_ENTITY_TO_CLASS( trigger_followme, CTriggerFollow ); + + +// buz: dynamic light entity +#define SF_DYNLIGHT_STARTOFF 1 +#define SF_DYNLIGHT_NOSHADOW 2 +#define SF_DYNLIGHT_NOBUMP 4 +#define SF_DYNLIGHT_FLARE 8 + +class CDynamicLight : public CPointEntity +{ +public: + void Spawn() + { + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + Precache(); + + if( pev->sequence ) + pev->renderfx = 72; // dynamic light with avi-texture + else pev->renderfx = 71; // dynamic light + + SET_MODEL(ENT(pev),"sprites/null.spr"); // should be visible to send to client + + pev->renderamt = pev->renderamt / 8; + + // spotlight + if( pev->scale ) + { + // create second PVS point + pev->enemy = Create( "info_target", pev->origin, g_vecZero, edict() )->edict(); + SET_MODEL( pev->enemy, "sprites/null.spr" ); // allow to collect visibility info + UTIL_SetSize ( VARS( pev->enemy ), Vector ( -8, -8, -8 ), Vector ( 8, 8, 8 ) ); + + // to prevent disapperaing from PVS (renderamt is premultiplied by 0.125) + UTIL_SetSize( pev, Vector( -pev->renderamt, -pev->renderamt, -pev->renderamt ), Vector( pev->renderamt, pev->renderamt, pev->renderamt )); + } + else + { + // to prevent disapperaing from PVS (renderamt is premultiplied by 0.125) + UTIL_SetSize( pev, Vector( -pev->renderamt * 4, -pev->renderamt * 4, -pev->renderamt * 4 ), Vector( pev->renderamt * 4, pev->renderamt * 4, pev->renderamt * 4 )); + } + + if( pev->spawnflags & SF_DYNLIGHT_NOSHADOW ) + pev->effects |= EF_NOSHADOW; + if( pev->spawnflags & SF_DYNLIGHT_NOBUMP ) + pev->effects |= EF_NOBUMP; + if( pev->spawnflags & SF_DYNLIGHT_FLARE ) + pev->effects |= EF_LENSFLARE; + + if( pev->spawnflags & SF_DYNLIGHT_STARTOFF ) + { + pev->effects |= EF_NODRAW; + } + else if( pev->sequence ) + { + SetThink( CineThink ); + SetNextThink( 0.1f ); + } + else if( pev->scale ) + { + SetThink( PVSThink ); + SetNextThink( 0.1f ); + } + } + + void Precache() + { + PRECACHE_MODEL("sprites/null.spr"); + + if( !FStringNull( pev->message )) + { + const char *ext = UTIL_FileExtension( STRING( pev->message )); + + if( !Q_stricmp( ext, "avi" )) + { + // 0 if movie not found + pev->sequence = UTIL_PrecacheMovie( pev->message ); + } + } + } + + STATE GetState( void ) + { + if (pev->effects & EF_NODRAW) + return STATE_OFF; + else + return STATE_ON; + } + + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) + { + if( useType == USE_ON ) + pev->effects &= ~EF_NODRAW; + else if( useType == USE_OFF ) + pev->effects |= EF_NODRAW; + else if( useType == USE_TOGGLE ) + { + if( pev->effects & EF_NODRAW ) + pev->effects &= ~EF_NODRAW; + else pev->effects |= EF_NODRAW; + } + + if( pev->effects & EF_NODRAW ) + { + DontThink(); + } + else if( pev->sequence ) + { + SetThink( CineThink ); + SetNextThink( 0.1f ); + } + else if( pev->scale ) + { + SetThink( PVSThink ); + SetNextThink( 0.1f ); + } + } + + void UpdatePVSPoint( void ) + { + TraceResult tr; + Vector forward; + + UTIL_MakeVectorsPrivate( pev->angles, forward, NULL, NULL ); + Vector vecSrc = pev->origin + forward * 8.0f; + Vector vecEnd = vecSrc + forward * (pev->renderamt * 8.0f); + UTIL_TraceLine( vecSrc, vecEnd, ignore_monsters, edict(), &tr ); + + // this is our second PVS point + CBaseEntity *pVisHelper = CBaseEntity::Instance( pev->enemy ); + if( pVisHelper ) UTIL_SetOrigin( pVisHelper, tr.vecEndPos + tr.vecPlaneNormal * 8.0f ); + } + + void EXPORT CineThink( void ) + { + UpdatePVSPoint(); + + // update as 30 frames per second + pev->fuser2 += CIN_FRAMETIME; + SetNextThink( CIN_FRAMETIME ); + } + + void EXPORT PVSThink( void ) + { + UpdatePVSPoint(); + + // static light should be updated in case + // moving platform under them + SetNextThink( m_pMoveWith ? 0.01f : 0.1f ); + } +}; + +LINK_ENTITY_TO_CLASS( env_dynlight, CDynamicLight ); + + +// =================== FUNC_SCREENMOVIE ============================================== + +#define SF_SCREENMOVIE_START_ON BIT( 0 ) +#define SF_SCREENMOVIE_PASSABLE BIT( 1 ) +#define SF_SCREENMOVIE_LOOPED BIT( 2 ) +#define SF_SCREENMOVIE_MONOCRHOME BIT( 3 ) // black & white +#define SF_SCREENMOVIE_SOUND BIT( 4 ) // allow sound + +class CFuncScreenMovie : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + STATE GetState( void ) { return (pev->body) ? STATE_ON : STATE_OFF; } + void EXPORT CineThink( void ); +}; + +LINK_ENTITY_TO_CLASS( func_screenmovie, CFuncScreenMovie ); + +void CFuncScreenMovie::KeyValue( KeyValueData *pkvd ) +{ + if( FStrEq( pkvd->szKeyName, "movie" )) + { + pev->message = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else CBaseDelay::KeyValue( pkvd ); +} + +void CFuncScreenMovie :: Precache( void ) +{ + // store movie name as event index + pev->sequence = UTIL_PrecacheMovie( pev->message, FBitSet( pev->spawnflags, SF_SCREENMOVIE_SOUND )); +} + +void CFuncScreenMovie :: Spawn( void ) +{ + Precache(); + + pev->movetype = MOVETYPE_PUSH; + + if( FBitSet( pev->spawnflags, SF_SCREENMOVIE_PASSABLE )) + pev->solid = SOLID_NOT; + else pev->solid = SOLID_BSP; + + SET_MODEL( ENT(pev), STRING(pev->model) ); + pev->effects |= EF_SCREENMOVIE; + + if( FBitSet( pev->spawnflags, SF_SCREENMOVIE_LOOPED )) + pev->iuser1 |= CF_LOOPED_MOVIE; + + if( FBitSet( pev->spawnflags, SF_SCREENMOVIE_MONOCRHOME )) + pev->iuser1 |= CF_MONOCHROME; + + if( FBitSet( pev->spawnflags, SF_SCREENMOVIE_SOUND )) + pev->iuser1 |= CF_MOVIE_SOUND; + + // enable monitor + if( FBitSet( pev->spawnflags, SF_SCREENMOVIE_START_ON )) + { + SetThink( SUB_CallUseToggle ); + SetNextThink( 0.1 ); + } +} + +void CFuncScreenMovie :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if( !pev->sequence ) + { + ALERT( at_error, "func_screenmovie with name %s can't loaded video %s\n", STRING( pev->targetname ), STRING( pev->message )); + return; + } + + SetThink( CineThink ); + + if( ShouldToggle( useType )) + { + pev->body = !pev->body; + + if( pev->body ) + SetNextThink( CIN_FRAMETIME ); + else DontThink(); + } +} + +void CFuncScreenMovie :: CineThink( void ) +{ + // update as 30 frames per second + pev->fuser2 += CIN_FRAMETIME; + SetNextThink( CIN_FRAMETIME ); +} + +class CBlurEffect : public CBaseDelay +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void ApplyBlur( CBasePlayer *pPlayer ); + void KeyValue( KeyValueData *pkvd ); + void Think( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + CBasePlayer *m_pPlayer; +}; + +TYPEDESCRIPTION CBlurEffect :: m_SaveData[] = +{ + DEFINE_FIELD( CBlurEffect, m_pPlayer, FIELD_CLASSPTR ), +}; IMPLEMENT_SAVERESTORE( CBlurEffect, CBaseDelay ); + +LINK_ENTITY_TO_CLASS( env_blur, CBlurEffect ); + +void CBlurEffect :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "fadetime")) + { + pev->frags = atof(pkvd->szValue); + pev->frags = bound( 0.0f, pev->frags, 1000.0f ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "magnitude")) + { + pev->armorvalue = atof(pkvd->szValue); + pev->armorvalue = bound( 0.0f, pev->armorvalue, 1.0f ); + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + +void CBlurEffect :: ApplyBlur( CBasePlayer *pPlayer ) +{ + if( !pPlayer ) return; // not spawned? + + m_pPlayer = pPlayer; + + if( pev->frags ) + { + pev->health = m_pPlayer->m_flBlurAmount; // start pos + pev->max_health = pev->armorvalue - pPlayer->m_flBlurAmount; + pev->dmgtime = gpGlobals->time; + SetNextThink( 0.01 ); + } + else + { + m_pPlayer->m_flBlurAmount = pev->armorvalue; + pev->max_health = pev->armorvalue; + pev->health = pev->dmgtime = 0.0f; + } +} + +void CBlurEffect :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if( pev->dmgtime ) return; // fading out + + if( !pActivator || !pActivator->IsPlayer( )) + ApplyBlur( (CBasePlayer *)UTIL_PlayerByIndex( 1 )); + else ApplyBlur( (CBasePlayer *)pActivator ); +} + +void CBlurEffect :: Think( void ) +{ + float elapsed = gpGlobals->time - pev->dmgtime; + float f = elapsed / pev->frags; + f = bound( 0.0f, f, 1.0f ); + + m_pPlayer->m_flBlurAmount = pev->health + pev->max_health * f; + + if( f == 1.0f ) // hit 100% fade + { + pev->dmgtime = 0.0f; + return; + } + + SetNextThink( 0.01 ); +} + +//======================= +// Gradient sprite class +//======================= + +class CGradientControl : public CBaseEntity +{ +public: + void Spawn( void ); + void Think( void ); + int m_iGradAmt; + int m_iGlowAmt; +}; + +void CGradientControl::Spawn( void ) +{ + m_iGradAmt = 0; + m_iGlowAmt = 0; + SetNextThink(0.01); +} + +void CGradientControl::Think( void ) +{ + CBaseEntity *pPlayer, *pGlow, *pGradient; + pPlayer = UTIL_FindEntityByClassname( NULL, "player" ); + pGlow = UTIL_FindEntityByTargetname( NULL, STRING( pev->target ) ); + pGradient = UTIL_FindEntityByTargetname( NULL, STRING( pev->message ) ); + + if (pPlayer && pGlow && pGradient) + { + if ( !m_iGradAmt) m_iGradAmt = pGradient->pev->renderamt; + + if ( !m_iGlowAmt) m_iGlowAmt = pGlow->pev->renderamt; + + TraceResult tr; + UTIL_TraceLine( pev->origin, pPlayer->pev->origin, ignore_monsters, ENT(pev), &tr ); + Vector vecDir = pev->origin - pPlayer->pev->origin; + + float flDist = vecDir.Length(); + float flDist2 = flDist; + + flDist-=60; + + if ( flDist < 0 ) flDist = 0; + + if ( flDist <= pev->health ) + { + pGradient->pev->renderamt = fabs( flDist / pev->health * m_iGradAmt ); + pGlow->pev->renderamt = fabs( pev->health / flDist2 * m_iGlowAmt ); + } + else + { + pGradient->pev->renderamt = m_iGradAmt; + pGlow->pev->renderamt = m_iGlowAmt; + } + } + + SetNextThink(0.01); +} + +LINK_ENTITY_TO_CLASS( env_gradient, CGradientControl ); diff --git a/dlls/effects.h b/dlls/effects.h new file mode 100644 index 0000000..9be9038 --- /dev/null +++ b/dlls/effects.h @@ -0,0 +1,265 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef EFFECTS_H +#define EFFECTS_H + +#define SF_BEAM_STARTON 0x0001 +#define SF_BEAM_TOGGLE 0x0002 +#define SF_BEAM_RANDOM 0x0004 +#define SF_BEAM_RING 0x0008 +#define SF_BEAM_SPARKSTART 0x0010 +#define SF_BEAM_SPARKEND 0x0020 +#define SF_BEAM_DECALS 0x0040 +#define SF_BEAM_SHADEIN 0x0080 +#define SF_BEAM_SHADEOUT 0x0100 +#define SF_BEAM_SOLID 0x0200 +#define SF_BEAM_TEMPORARY 0x8000 +//LRC - tripbeams +#define SF_BEAM_TRIPPED 0x80000 +//LRC - smoother lasers +#define SF_LASER_INTERPOLATE 0x0400 + +#define SF_SPRITE_STARTON 0x0001 +#define SF_SPRITE_ONCE 0x0002 +#define SF_SPRITE_TEMPORARY 0x8000 + +class CSprite : public CPointEntity +{ +public: + void Spawn( void ); + void Precache( void ); + + int ObjectCaps( void ) + { + int flags = 0; + if ( pev->spawnflags & SF_SPRITE_TEMPORARY ) + flags = FCAP_DONT_SAVE; + return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | flags; + } + void EXPORT AnimateThink( void ); + void EXPORT ExpandThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Animate( float frames ); + void Expand( float scaleSpeed, float fadeSpeed ); + void SpriteInit( const char *pSpriteName, const Vector &origin ); + + virtual STATE GetState( void ) { return (pev->effects & EF_NODRAW)?STATE_OFF:STATE_ON; }; + + inline void SetAttachment( edict_t *pEntity, int attachment ) + { + if ( pEntity ) + { + pev->skin = ENTINDEX(pEntity); + pev->body = attachment; + pev->aiment = pEntity; + pev->movetype = MOVETYPE_FOLLOW; + } + } + void TurnOff( void ); + void TurnOn( void ); + inline float Frames( void ) { return m_maxFrame; } + inline void SetTransparency( int rendermode, int r, int g, int b, int a, int fx ) + { + pev->rendermode = rendermode; + pev->rendercolor.x = r; + pev->rendercolor.y = g; + pev->rendercolor.z = b; + pev->renderamt = a; + pev->renderfx = fx; + } + inline void SetTexture( int spriteIndex ) { pev->modelindex = spriteIndex; } + inline void SetScale( float scale ) { pev->scale = scale; } + inline void SetColor( int r, int g, int b ) { pev->rendercolor.x = r; pev->rendercolor.y = g; pev->rendercolor.z = b; } + inline void SetBrightness( int brightness ) { pev->renderamt = brightness; } + + inline void AnimateAndDie( float framerate ) + { + SetThink(&CSprite ::AnimateUntilDead); + pev->framerate = framerate; + pev->dmgtime = gpGlobals->time + (m_maxFrame / framerate); + SetNextThink( 0 ); + } + + void EXPORT AnimateUntilDead( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + static CSprite *SpriteCreate( const char *pSpriteName, const Vector &origin, BOOL animate ); + +//private: + + float m_lastTime; + float m_maxFrame; +}; + + +class CBeam : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + int ObjectCaps( void ) + { + int flags = 0; + if ( pev->spawnflags & SF_BEAM_TEMPORARY ) + flags = FCAP_DONT_SAVE; + return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | flags; + } + + void EXPORT TriggerTouch( CBaseEntity *pOther ); + + // These functions are here to show the way beams are encoded as entities. + // Encoding beams as entities simplifies their management in the client/server architecture + inline void SetType( int type ) { pev->rendermode = (pev->rendermode & 0xF0) | (type&0x0F); } + inline void SetFlags( int flags ) { pev->rendermode = (pev->rendermode & 0x0F) | (flags&0xF0); } + inline void SetStartPos( const Vector& pos ) { pev->origin = pos; } + inline void SetEndPos( const Vector& pos ) { pev->angles = pos; } + void SetStartEntity( int entityIndex ); + void SetEndEntity( int entityIndex ); + + inline void SetStartAttachment( int attachment ) { pev->sequence = (pev->sequence & 0x0FFF) | ((attachment&0xF)<<12); } + inline void SetEndAttachment( int attachment ) { pev->skin = (pev->skin & 0x0FFF) | ((attachment&0xF)<<12); } + + inline void SetTexture( int spriteIndex ) { pev->modelindex = spriteIndex; } + inline void SetWidth( int width ) { pev->scale = width; } + inline void SetNoise( int amplitude ) { pev->body = amplitude; } + inline void SetColor( int r, int g, int b ) { pev->rendercolor.x = r; pev->rendercolor.y = g; pev->rendercolor.z = b; } + inline void SetBrightness( int brightness ) { pev->renderamt = brightness; } + inline void SetFrame( float frame ) { pev->frame = frame; } + inline void SetScrollRate( int speed ) { pev->animtime = speed; } + + inline int GetType( void ) { return pev->rendermode & 0x0F; } + inline int GetFlags( void ) { return pev->rendermode & 0xF0; } + inline int GetStartEntity( void ) { return pev->sequence & 0xFFF; } + inline int GetEndEntity( void ) { return pev->skin & 0xFFF; } + + const Vector &GetStartPos( void ); + const Vector &GetEndPos( void ); + + Vector Center( void ) { return (GetStartPos() + GetEndPos()) * 0.5; }; // center point of beam + + inline int GetTexture( void ) { return pev->modelindex; } + inline int GetWidth( void ) { return pev->scale; } + inline int GetNoise( void ) { return pev->body; } + // inline void GetColor( int r, int g, int b ) { pev->rendercolor.x = r; pev->rendercolor.y = g; pev->rendercolor.z = b; } + inline int GetBrightness( void ) { return pev->renderamt; } + inline int GetFrame( void ) { return pev->frame; } + inline int GetScrollRate( void ) { return pev->animtime; } + + CBaseEntity* GetTripEntity( TraceResult *ptr ); //LRC + + // Call after you change start/end positions + void RelinkBeam( void ); +// void SetObjectCollisionBox( void ); + + void DoSparks( const Vector &start, const Vector &end ); + CBaseEntity *RandomTargetname( const char *szName ); + void BeamDamage( TraceResult *ptr ); + // Init after BeamCreate() + void BeamInit( const char *pSpriteName, int width ); + void PointsInit( const Vector &start, const Vector &end ); + void PointEntInit( const Vector &start, int endIndex ); + void EntsInit( int startIndex, int endIndex ); + void HoseInit( const Vector &start, const Vector &direction ); + + static CBeam *BeamCreate( const char *pSpriteName, int width ); + + inline void LiveForTime( float time ) { SetThink(&CBeam::SUB_Remove); SetNextThink( time ); } + inline void BeamDamageInstant( TraceResult *ptr, float damage ) + { + pev->dmg = damage; + pev->dmgtime = gpGlobals->time - 1; + BeamDamage(ptr); + } +}; + + +#define SF_MESSAGE_ONCE 0x0001 // Fade in, not out +#define SF_MESSAGE_ALL 0x0002 // Send to all clients + + +class CLaser : public CBeam +{ +public: + void Spawn( void ); + void PostSpawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + void TurnOn( void ); + void TurnOff( void ); + virtual STATE GetState( void ) { return (pev->effects & EF_NODRAW)?STATE_OFF:STATE_ON; }; + + void FireAtPoint( Vector startpos, TraceResult &point ); + + void EXPORT StrikeThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + CSprite *m_pStartSprite; + CSprite *m_pEndSprite; + int m_iszStartSpriteName; + int m_iszEndSpriteName; + Vector m_firePosition; + int m_iProjection; + int m_iStoppedBy; + int m_iszStartPosition; + int m_iTowardsMode; +}; + +// ====== rain ======== + +class CRainSettings : public CBaseEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + + int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + float Rain_Distance; + int Rain_Mode; +}; + +class CRainModify : public CBaseEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int Rain_Drips; + float Rain_windX, Rain_windY; + float Rain_randX, Rain_randY; + float fadeTime; +}; + + + + +#endif //EFFECTS_H diff --git a/dlls/enginecallback.h b/dlls/enginecallback.h new file mode 100644 index 0000000..43ac034 --- /dev/null +++ b/dlls/enginecallback.h @@ -0,0 +1,162 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef ENGINECALLBACK_H +#define ENGINECALLBACK_H +#pragma once + +#include "event_flags.h" + +// Must be provided by user of this code +extern enginefuncs_t g_engfuncs; + +// The actual engine callbacks +#define GETPLAYERUSERID (*g_engfuncs.pfnGetPlayerUserId) +#define PRECACHE_MODEL (*g_engfuncs.pfnPrecacheModel) +#define PRECACHE_SOUND (*g_engfuncs.pfnPrecacheSound) +#define PRECACHE_GENERIC (*g_engfuncs.pfnPrecacheGeneric) +#define SET_MODEL (*g_engfuncs.pfnSetModel) +#define MODEL_INDEX (*g_engfuncs.pfnModelIndex) +#define MODEL_FRAMES (*g_engfuncs.pfnModelFrames) +#define SET_SIZE (*g_engfuncs.pfnSetSize) +#define CHANGE_LEVEL (*g_engfuncs.pfnChangeLevel) +#define GET_SPAWN_PARMS (*g_engfuncs.pfnGetSpawnParms) +#define SAVE_SPAWN_PARMS (*g_engfuncs.pfnSaveSpawnParms) +#define VEC_TO_YAW (*g_engfuncs.pfnVecToYaw) +#define VEC_TO_ANGLES (*g_engfuncs.pfnVecToAngles) +#define MOVE_TO_ORIGIN (*g_engfuncs.pfnMoveToOrigin) +#define CHANGE_PITCH (*g_engfuncs.pfnChangePitch) +#define MAKE_VECTORS (*g_engfuncs.pfnMakeVectors) +#define CREATE_ENTITY (*g_engfuncs.pfnCreateEntity) +#define REMOVE_ENTITY (*g_engfuncs.pfnRemoveEntity) +#define CREATE_NAMED_ENTITY (*g_engfuncs.pfnCreateNamedEntity) +#define MAKE_STATIC (*g_engfuncs.pfnMakeStatic) +#define ENT_IS_ON_FLOOR (*g_engfuncs.pfnEntIsOnFloor) +#define DROP_TO_FLOOR (*g_engfuncs.pfnDropToFloor) +#define WALK_MOVE (*g_engfuncs.pfnWalkMove) +#define SET_ORIGIN (*g_engfuncs.pfnSetOrigin) +#define EMIT_SOUND_DYN2 (*g_engfuncs.pfnEmitSound) +#define BUILD_SOUND_MSG (*g_engfuncs.pfnBuildSoundMsg) +#define TRACE_LINE (*g_engfuncs.pfnTraceLine) +#define TRACE_TOSS (*g_engfuncs.pfnTraceToss) +#define TRACE_MONSTER_HULL (*g_engfuncs.pfnTraceMonsterHull) +#define TRACE_HULL (*g_engfuncs.pfnTraceHull) +#define GET_AIM_VECTOR (*g_engfuncs.pfnGetAimVector) +#define SERVER_COMMAND (*g_engfuncs.pfnServerCommand) +#define SERVER_EXECUTE (*g_engfuncs.pfnServerExecute) +#define CLIENT_COMMAND (*g_engfuncs.pfnClientCommand) +#define PARTICLE_EFFECT (*g_engfuncs.pfnParticleEffect) +#define LIGHT_STYLE (*g_engfuncs.pfnLightStyle) +#define DECAL_INDEX (*g_engfuncs.pfnDecalIndex) +#define POINT_CONTENTS (*g_engfuncs.pfnPointContents) +#define CRC32_INIT (*g_engfuncs.pfnCRC32_Init) +#define CRC32_PROCESS_BUFFER (*g_engfuncs.pfnCRC32_ProcessBuffer) +#define CRC32_PROCESS_BYTE (*g_engfuncs.pfnCRC32_ProcessByte) +#define CRC32_FINAL (*g_engfuncs.pfnCRC32_Final) +#define RANDOM_LONG (*g_engfuncs.pfnRandomLong) +#define RANDOM_FLOAT (*g_engfuncs.pfnRandomFloat) +#define GETPLAYERAUTHID (*g_engfuncs.pfnGetPlayerAuthId) +#define Sys_DoubleTime (*g_engfuncs.pfnTime) + +inline void MESSAGE_BEGIN( int msg_dest, int msg_type, const float *pOrigin = NULL, edict_t *ed = NULL ) { + (*g_engfuncs.pfnMessageBegin)(msg_dest, msg_type, pOrigin, ed); +} +#define MESSAGE_END (*g_engfuncs.pfnMessageEnd) +#define WRITE_BYTE (*g_engfuncs.pfnWriteByte) +#define WRITE_CHAR (*g_engfuncs.pfnWriteChar) +#define WRITE_SHORT (*g_engfuncs.pfnWriteShort) +#define WRITE_LONG (*g_engfuncs.pfnWriteLong) +#define WRITE_ANGLE (*g_engfuncs.pfnWriteAngle) +#define WRITE_COORD (*g_engfuncs.pfnWriteCoord) +#define WRITE_STRING (*g_engfuncs.pfnWriteString) +#define WRITE_ENTITY (*g_engfuncs.pfnWriteEntity) +#define CVAR_REGISTER (*g_engfuncs.pfnCVarRegister) +#define CVAR_GET_FLOAT (*g_engfuncs.pfnCVarGetFloat) +#define CVAR_GET_STRING (*g_engfuncs.pfnCVarGetString) +#define CVAR_SET_FLOAT (*g_engfuncs.pfnCVarSetFloat) +#define CVAR_SET_STRING (*g_engfuncs.pfnCVarSetString) +#define CVAR_GET_POINTER (*g_engfuncs.pfnCVarGetPointer) +#define ALERT (*g_engfuncs.pfnAlertMessage) +#define ENGINE_FPRINTF (*g_engfuncs.pfnEngineFprintf) +#define ALLOC_PRIVATE (*g_engfuncs.pfnPvAllocEntPrivateData) +inline void *GET_PRIVATE( edict_t *pent ) +{ + if ( pent ) + return pent->pvPrivateData; + return NULL; +} + +#define FREE_PRIVATE (*g_engfuncs.pfnFreeEntPrivateData) +//#define STRING (*g_engfuncs.pfnSzFromIndex) +#define ALLOC_STRING (*g_engfuncs.pfnAllocString) +#define FIND_ENTITY_BY_STRING (*g_engfuncs.pfnFindEntityByString) +#define GETENTITYILLUM (*g_engfuncs.pfnGetEntityIllum) +#define FIND_ENTITY_IN_SPHERE (*g_engfuncs.pfnFindEntityInSphere) +#define FIND_CLIENT_IN_PVS (*g_engfuncs.pfnFindClientInPVS) +#define EMIT_AMBIENT_SOUND (*g_engfuncs.pfnEmitAmbientSound) +#define GET_MODEL_PTR (*g_engfuncs.pfnGetModelPtr) +#define REG_USER_MSG (*g_engfuncs.pfnRegUserMsg) +#define GET_BONE_POSITION (*g_engfuncs.pfnGetBonePosition) +#define FUNCTION_FROM_NAME (*g_engfuncs.pfnFunctionFromName) +#define NAME_FOR_FUNCTION (*g_engfuncs.pfnNameForFunction) +#define TRACE_TEXTURE (*g_engfuncs.pfnTraceTexture) +#define CLIENT_PRINTF (*g_engfuncs.pfnClientPrintf) +#define CMD_ARGS (*g_engfuncs.pfnCmd_Args) +#define CMD_ARGC (*g_engfuncs.pfnCmd_Argc) +#define CMD_ARGV (*g_engfuncs.pfnCmd_Argv) +#define GET_ATTACHMENT (*g_engfuncs.pfnGetAttachment) +#define SET_VIEW (*g_engfuncs.pfnSetView) +#define SET_CROSSHAIRANGLE (*g_engfuncs.pfnCrosshairAngle) +#define LOAD_FILE( x, y ) (*g_engfuncs.pfnLoadFileForMe)( x, y ) +#define FREE_FILE (*g_engfuncs.pfnFreeFile) +#define COMPARE_FILE_TIME (*g_engfuncs.pfnCompareFileTime) +#define GET_GAME_DIR (*g_engfuncs.pfnGetGameDir) +#define IS_MAP_VALID (*g_engfuncs.pfnIsMapValid) +#define NUMBER_OF_ENTITIES (*g_engfuncs.pfnNumberOfEntities) +#define IS_DEDICATED_SERVER (*g_engfuncs.pfnIsDedicatedServer) + +#define PRECACHE_EVENT (*g_engfuncs.pfnPrecacheEvent) +#define PLAYBACK_EVENT_FULL (*g_engfuncs.pfnPlaybackEvent) + +#define ENGINE_SET_PVS (*g_engfuncs.pfnSetFatPVS) +#define ENGINE_SET_PAS (*g_engfuncs.pfnSetFatPAS) + +#define ENGINE_CHECK_VISIBILITY (*g_engfuncs.pfnCheckVisibility) + +#define DELTA_SET ( *g_engfuncs.pfnDeltaSetField ) +#define DELTA_UNSET ( *g_engfuncs.pfnDeltaUnsetField ) +#define DELTA_ADDENCODER ( *g_engfuncs.pfnDeltaAddEncoder ) +#define ENGINE_CURRENT_PLAYER ( *g_engfuncs.pfnGetCurrentPlayer ) + +#define ENGINE_CANSKIP ( *g_engfuncs.pfnCanSkipPlayer ) + +#define DELTA_FINDFIELD ( *g_engfuncs.pfnDeltaFindField ) +#define DELTA_SETBYINDEX ( *g_engfuncs.pfnDeltaSetFieldByIndex ) +#define DELTA_UNSETBYINDEX ( *g_engfuncs.pfnDeltaUnsetFieldByIndex ) + +#define ENGINE_GETPHYSINFO ( *g_engfuncs.pfnGetPhysicsInfoString ) + +#define ENGINE_SETGROUPMASK ( *g_engfuncs.pfnSetGroupMask ) + +#define ENGINE_INSTANCE_BASELINE ( *g_engfuncs.pfnCreateInstancedBaseline ) + +#define ENGINE_FORCE_UNMODIFIED ( *g_engfuncs.pfnForceUnmodified ) + +#define PLAYER_CNX_STATS ( *g_engfuncs.pfnGetPlayerStats ) + +extern globalvars_t *gpGlobals; + +void Msg( const char *szText, ... ); + +#endif //ENGINECALLBACK_H \ No newline at end of file diff --git a/dlls/explode.cpp b/dlls/explode.cpp new file mode 100644 index 0000000..6bad7dc --- /dev/null +++ b/dlls/explode.cpp @@ -0,0 +1,294 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== explode.cpp ======================================================== + + Explosion-related code + +*/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "decals.h" +#include "explode.h" +#include "locus.h" + +// Spark Shower +class CShower : public CBaseEntity +{ + void Spawn( void ); + void Think( void ); + void Touch( CBaseEntity *pOther ); + int ObjectCaps( void ) { return FCAP_DONT_SAVE; } +}; + +LINK_ENTITY_TO_CLASS( spark_shower, CShower ); + +void CShower::Spawn( void ) +{ + pev->velocity = RANDOM_FLOAT( 200, 300 ) * pev->angles; + pev->velocity.x += RANDOM_FLOAT(-100.f,100.f); + pev->velocity.y += RANDOM_FLOAT(-100.f,100.f); + if ( pev->velocity.z >= 0 ) + pev->velocity.z += 200; + else + pev->velocity.z -= 200; + pev->movetype = MOVETYPE_BOUNCE; + pev->gravity = 0.5; + SetNextThink( 0.1 ); + pev->solid = SOLID_NOT; + SET_MODEL( edict(), "models/grenade.mdl"); // Need a model, just use the grenade, we don't draw it anyway + UTIL_SetSize(pev, g_vecZero, g_vecZero ); + pev->effects |= EF_NODRAW; + pev->speed = RANDOM_FLOAT( 0.5, 1.5 ); + + pev->angles = g_vecZero; +} + + +void CShower::Think( void ) +{ + UTIL_Sparks( pev->origin ); + + pev->speed -= 0.1; + if ( pev->speed > 0 ) + SetNextThink( 0.1 ); + else + UTIL_Remove( this ); + pev->flags &= ~FL_ONGROUND; +} + +void CShower::Touch( CBaseEntity *pOther ) +{ + if ( pev->flags & FL_ONGROUND ) + pev->velocity = pev->velocity * 0.1; + else + pev->velocity = pev->velocity * 0.6; + + if ( (pev->velocity.x*pev->velocity.x+pev->velocity.y*pev->velocity.y) < 10.0 ) + pev->speed = 0; +} + +class CEnvExplosion : public CBaseMonster +{ +public: + void Spawn( ); + void EXPORT Smoke ( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_iMagnitude;// how large is the fireball? how much damage? + int m_spriteScale; // what's the exact fireball sprite scale? +}; + +TYPEDESCRIPTION CEnvExplosion::m_SaveData[] = +{ + DEFINE_FIELD( CEnvExplosion, m_iMagnitude, FIELD_INTEGER ), + DEFINE_FIELD( CEnvExplosion, m_spriteScale, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CEnvExplosion, CBaseMonster ); +LINK_ENTITY_TO_CLASS( env_explosion, CEnvExplosion ); + +void CEnvExplosion::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "iMagnitude")) + { + m_iMagnitude = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CEnvExplosion::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->effects |= EF_NODRAW; + + pev->movetype = MOVETYPE_NONE; + /* + if ( m_iMagnitude > 250 ) + { + m_iMagnitude = 250; + } + */ + + float flSpriteScale; + flSpriteScale = ( m_iMagnitude - 50) * 0.6; + + /* + if ( flSpriteScale > 50 ) + { + flSpriteScale = 50; + } + */ + if ( flSpriteScale < 10 ) + { + flSpriteScale = 10; + } + + m_spriteScale = (int)flSpriteScale; +} + +extern int gmsgCustomDLight; // buz + +void CEnvExplosion::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + TraceResult tr; + + pev->model = iStringNull;//invisible + pev->solid = SOLID_NOT;// intangible + + Vector vecSpot;// trace starts here! + + //LRC + if (FStringNull(pev->target)) + { + vecSpot = pev->origin; + } + else + { + vecSpot = CalcLocus_Position(this, pActivator, STRING(pev->target)); + } + + UTIL_TraceLine ( vecSpot + Vector( 0, 0, 8 ), vecSpot + Vector ( 0, 0, -32 ), ignore_monsters, ENT(pev), & tr); + + // Pull out of the wall a bit + if ( tr.flFraction != 1.0 ) + { + pev->origin = tr.vecEndPos + (tr.vecPlaneNormal * (m_iMagnitude - 24) * 0.6); + } + else + { + pev->origin = vecSpot; //LRC + } + + // draw decal + if (! ( pev->spawnflags & SF_ENVEXPLOSION_NODECAL)) + { + if ( RANDOM_FLOAT( 0 , 1 ) < 0.5 ) + { + UTIL_TraceCustomDecal( &tr, "scorch1", RANDOM_FLOAT( 0.0f, 360.0f )); + } + else + { + UTIL_TraceCustomDecal( &tr, "scorch2", RANDOM_FLOAT( 0.0f, 360.0f )); + } + } + + // draw fireball + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NOFIREBALL ) ) + { + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( (BYTE)m_spriteScale ); // scale * 10 + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + + // buz: + MESSAGE_BEGIN( MSG_PAS, gmsgCustomDLight, pev->origin ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_BYTE( 25 ); // radius / 10 + WRITE_BYTE( 15 ); // life * 10 + WRITE_BYTE( 30 ); // decay / 10 + MESSAGE_END(); + } + else + { + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( 0 ); // no sprite + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + } + + // do damage + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NODAMAGE ) ) + { + RadiusDamage ( pev, pev, m_iMagnitude, CLASS_NONE, DMG_BLAST ); + } + + SetThink(&CEnvExplosion:: Smoke ); + SetNextThink( 0.3 ); + + // draw sparks + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NOSPARKS ) ) + { + int sparkCount = RANDOM_LONG(0,3); + + for ( int i = 0; i < sparkCount; i++ ) + { + Create( "spark_shower", pev->origin, tr.vecPlaneNormal, NULL ); + } + } +} + +void CEnvExplosion::Smoke( void ) +{ + if ( !( pev->spawnflags & SF_ENVEXPLOSION_NOSMOKE ) ) + { + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( (BYTE)m_spriteScale ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + } + + if ( !(pev->spawnflags & SF_ENVEXPLOSION_REPEATABLE) ) + { + UTIL_Remove( this ); + } +} + + +// HACKHACK -- create one of these and fake a keyvalue to get the right explosion setup +void ExplosionCreate( const Vector ¢er, const Vector &angles, edict_t *pOwner, int magnitude, BOOL doDamage ) +{ + KeyValueData kvd; + char buf[128]; + + CBaseEntity *pExplosion = CBaseEntity::Create( "env_explosion", center, angles, pOwner ); + sprintf( buf, "%3d", magnitude ); + kvd.szKeyName = "iMagnitude"; + kvd.szValue = buf; + pExplosion->KeyValue( &kvd ); + if ( !doDamage ) + pExplosion->pev->spawnflags |= SF_ENVEXPLOSION_NODAMAGE; + + pExplosion->Spawn(); + pExplosion->Use( NULL, NULL, USE_TOGGLE, 0 ); +} diff --git a/dlls/explode.h b/dlls/explode.h new file mode 100644 index 0000000..a261338 --- /dev/null +++ b/dlls/explode.h @@ -0,0 +1,33 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef EXPLODE_H +#define EXPLODE_H + + +#define SF_ENVEXPLOSION_NODAMAGE ( 1 << 0 ) // when set, ENV_EXPLOSION will not actually inflict damage +#define SF_ENVEXPLOSION_REPEATABLE ( 1 << 1 ) // can this entity be refired? +#define SF_ENVEXPLOSION_NOFIREBALL ( 1 << 2 ) // don't draw the fireball +#define SF_ENVEXPLOSION_NOSMOKE ( 1 << 3 ) // don't draw the smoke +#define SF_ENVEXPLOSION_NODECAL ( 1 << 4 ) // don't make a scorch mark +#define SF_ENVEXPLOSION_NOSPARKS ( 1 << 5 ) // don't make a scorch mark + +extern DLL_GLOBAL short g_sModelIndexFireball; +extern DLL_GLOBAL short g_sModelIndexSmoke; +extern DLL_GLOBAL short g_sModelIndexSmokeTrail; + + +extern void ExplosionCreate( const Vector ¢er, const Vector &angles, edict_t *pOwner, int magnitude, BOOL doDamage ); + +#endif//EXPLODE_H \ No newline at end of file diff --git a/dlls/extdll.h b/dlls/extdll.h new file mode 100644 index 0000000..b424d86 --- /dev/null +++ b/dlls/extdll.h @@ -0,0 +1,86 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef EXTDLL_H +#define EXTDLL_H + + +// +// Global header file for extension DLLs +// + +// Allow "DEBUG" in addition to default "_DEBUG" +#ifdef _DEBUG +#define DEBUG 1 +#endif + +// Silence certain warnings +#pragma warning(disable : 4305) // int or float data truncation +#pragma warning(disable : 4201) // nameless struct/union +#pragma warning(disable : 4514) // unreferenced inline function removed +#pragma warning(disable : 4100) // unreferenced formal parameter + +// Prevent tons of unused windows definitions +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#define NOWINRES +#define NOSERVICE +#define NOMCX +#define NOIME +#include "windows.h" +#else // _WIN32 +#define FALSE 0 +#define TRUE (!FALSE) +typedef unsigned long ULONG; +typedef unsigned char BYTE; +typedef int BOOL; +#define MAX_PATH PATH_MAX +#include +#include +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#define _vsnprintf(a,b,c,d) vsnprintf(a,b,c,d) +#endif +#endif //_WIN32 + +// Misc C-runtime library headers +#include "stdio.h" +#include "stdlib.h" +#include "math.h" +#include "mathlib.h" + +// Header file containing definition of globalvars_t and entvars_t +typedef int string_t; // from engine's pr_comp.h; + +// Vector class +#include "vector.h" + +// Matrix class +#include "matrix.h" + +// Shared engine/DLL constants +#include "const.h" +#include "progdefs.h" +#include "edict.h" + +// Shared header describing protocol between engine and DLLs +#include "eiface.h" + +// Shared header between the client DLL and the game DLLs +#include "cdll_dll.h" + +#endif //EXTDLL_H diff --git a/dlls/flyingmonster.cpp b/dlls/flyingmonster.cpp new file mode 100644 index 0000000..5537fd4 --- /dev/null +++ b/dlls/flyingmonster.cpp @@ -0,0 +1,281 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "flyingmonster.h" + +#define FLYING_AE_FLAP (8) +#define FLYING_AE_FLAPSOUND (9) + + +extern DLL_GLOBAL edict_t *g_pBodyQueueHead; + +int CFlyingMonster :: CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ) +{ + // UNDONE: need to check more than the endpoint + if (FBitSet(pev->flags, FL_SWIM) && (UTIL_PointContents(vecEnd) != CONTENTS_WATER)) + { + // ALERT(at_aiconsole, "can't swim out of water\n"); + return FALSE; + } + + TraceResult tr; + + UTIL_TraceHull( vecStart + Vector( 0, 0, 32 ), vecEnd + Vector( 0, 0, 32 ), dont_ignore_monsters, large_hull, edict(), &tr ); + + // ALERT( at_console, "%.0f %.0f %.0f : ", vecStart.x, vecStart.y, vecStart.z ); + // ALERT( at_console, "%.0f %.0f %.0f\n", vecEnd.x, vecEnd.y, vecEnd.z ); + + if (pflDist) + { + *pflDist = ( (tr.vecEndPos - Vector( 0, 0, 32 )) - vecStart ).Length();// get the distance. + } + + // ALERT( at_console, "check %d %d %f\n", tr.fStartSolid, tr.fAllSolid, tr.flFraction ); + if (tr.fStartSolid || tr.flFraction < 1.0) + { + if ( pTarget && pTarget->edict() == gpGlobals->trace_ent ) + return LOCALMOVE_VALID; + return LOCALMOVE_INVALID; + } + + return LOCALMOVE_VALID; +} + + +BOOL CFlyingMonster :: FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, CBaseEntity *pTargetEnt, Vector *pApex ) +{ + return CBaseMonster::FTriangulate( vecStart, vecEnd, flDist, pTargetEnt, pApex ); +} + + +Activity CFlyingMonster :: GetStoppedActivity( void ) +{ + if ( pev->movetype != MOVETYPE_FLY ) // UNDONE: Ground idle here, IDLE may be something else + return ACT_IDLE; + + return ACT_HOVER; +} + + +void CFlyingMonster :: Stop( void ) +{ + Activity stopped = GetStoppedActivity(); + if ( m_IdealActivity != stopped ) + { + m_flightSpeed = 0; + m_IdealActivity = stopped; + } + pev->angles.z = 0; + pev->angles.x = 0; + m_vecTravel = g_vecZero; +} + + +float CFlyingMonster :: ChangeYaw( int speed ) +{ + if ( pev->movetype == MOVETYPE_FLY ) + { + float diff = DeltaIdealYaw(); + float target = 0; + + if ( m_IdealActivity != GetStoppedActivity() ) + { + if ( diff < -20 ) + target = 90; + else if ( diff > 20 ) + target = -90; + } + pev->angles.z = UTIL_Approach( target, pev->angles.z, 220.0 * gpGlobals->frametime ); + } + return CBaseMonster::ChangeYaw( speed ); +} + + +void CFlyingMonster :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->movetype = MOVETYPE_STEP; + ClearBits( pev->flags, FL_ONGROUND ); + pev->angles.z = 0; + pev->angles.x = 0; + CBaseMonster::Killed( pevAttacker, iGib ); +} + + +void CFlyingMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case FLYING_AE_FLAP: + m_flightSpeed = 400; + break; + + case FLYING_AE_FLAPSOUND: + if ( m_pFlapSound ) + EMIT_SOUND( edict(), CHAN_BODY, m_pFlapSound, 1, ATTN_NORM ); + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + + +void CFlyingMonster :: Move( float flInterval ) +{ + if ( pev->movetype == MOVETYPE_FLY ) + m_flGroundSpeed = m_flightSpeed; + CBaseMonster::Move( flInterval ); +} + + +BOOL CFlyingMonster:: ShouldAdvanceRoute( float flWaypointDist ) +{ + // Get true 3D distance to the goal so we actually reach the correct height + if ( m_Route[ m_iRouteIndex ].iType & bits_MF_IS_GOAL ) + flWaypointDist = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Length(); + + if ( flWaypointDist <= 64 + (m_flGroundSpeed * gpGlobals->frametime) ) + return TRUE; + + return FALSE; +} + + +void CFlyingMonster::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ) +{ + if ( pev->movetype == MOVETYPE_FLY ) + { + if ( gpGlobals->time - m_stopTime > 1.0 ) + { + if ( m_IdealActivity != m_movementActivity ) + { + m_IdealActivity = m_movementActivity; + m_flGroundSpeed = m_flightSpeed = 200; + } + } + Vector vecMove = pev->origin + (( vecDir + (m_vecTravel * m_momentum) ).Normalize() * (m_flGroundSpeed * flInterval)); + + if ( m_IdealActivity != m_movementActivity ) + { + m_flightSpeed = UTIL_Approach( 100, m_flightSpeed, 75 * gpGlobals->frametime ); + if ( m_flightSpeed < 100 ) + m_stopTime = gpGlobals->time; + } + else + m_flightSpeed = UTIL_Approach( 20, m_flightSpeed, 300 * gpGlobals->frametime ); + + if ( CheckLocalMove ( pev->origin, vecMove, pTargetEnt, NULL ) ) + { + m_vecTravel = (vecMove - pev->origin); + m_vecTravel = m_vecTravel.Normalize(); + UTIL_MoveToOrigin(ENT(pev), vecMove, (m_flGroundSpeed * flInterval), MOVE_STRAFE); + } + else + { + m_IdealActivity = GetStoppedActivity(); + m_stopTime = gpGlobals->time; + m_vecTravel = g_vecZero; + } + } + else + CBaseMonster::MoveExecute( pTargetEnt, vecDir, flInterval ); +} + + +float CFlyingMonster::CeilingZ( const Vector &position ) +{ + TraceResult tr; + + Vector minUp = position; + Vector maxUp = position; + maxUp.z += 4096.0; + + UTIL_TraceLine(position, maxUp, ignore_monsters, NULL, &tr); + if (tr.flFraction != 1.0) + maxUp.z = tr.vecEndPos.z; + + if ((pev->flags) & FL_SWIM) + { + return UTIL_WaterLevel( position, minUp.z, maxUp.z ); + } + return maxUp.z; +} + +BOOL CFlyingMonster::ProbeZ( const Vector &position, const Vector &probe, float *pFraction) +{ + int conPosition = UTIL_PointContents(position); + if ( (((pev->flags) & FL_SWIM) == FL_SWIM) ^ (conPosition == CONTENTS_WATER)) + { + // SWIMING & !WATER + // or FLYING & WATER + // + *pFraction = 0.0; + return TRUE; // We hit a water boundary because we are where we don't belong. + } + int conProbe = UTIL_PointContents(probe); + if (conProbe == conPosition) + { + // The probe is either entirely inside the water (for fish) or entirely + // outside the water (for birds). + // + *pFraction = 1.0; + return FALSE; + } + + Vector ProbeUnit = (probe-position).Normalize(); + float ProbeLength = (probe-position).Length(); + float maxProbeLength = ProbeLength; + float minProbeLength = 0; + + float diff = maxProbeLength - minProbeLength; + while (diff > 1.0) + { + float midProbeLength = minProbeLength + diff/2.0; + Vector midProbeVec = midProbeLength * ProbeUnit; + if (UTIL_PointContents(position+midProbeVec) == conPosition) + { + minProbeLength = midProbeLength; + } + else + { + maxProbeLength = midProbeLength; + } + diff = maxProbeLength - minProbeLength; + } + *pFraction = minProbeLength/ProbeLength; + + return TRUE; +} + +float CFlyingMonster::FloorZ( const Vector &position ) +{ + TraceResult tr; + + Vector down = position; + down.z -= 2048; + + UTIL_TraceLine( position, down, ignore_monsters, NULL, &tr ); + + if ( tr.flFraction != 1.0 ) + return tr.vecEndPos.z; + + return down.z; +} + diff --git a/dlls/flyingmonster.h b/dlls/flyingmonster.h new file mode 100644 index 0000000..616194d --- /dev/null +++ b/dlls/flyingmonster.h @@ -0,0 +1,53 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +// Base class for flying monsters. This overrides the movement test & execution code from CBaseMonster + +#ifndef FLYINGMONSTER_H +#define FLYINGMONSTER_H + +class CFlyingMonster : public CBaseMonster +{ +public: + int CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist );// check validity of a straight move through space + BOOL FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, CBaseEntity *pTargetEnt, Vector *pApex ); + Activity GetStoppedActivity( void ); + void Killed( entvars_t *pevAttacker, int iGib ); + void Stop( void ); + float ChangeYaw( int speed ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ); + void Move( float flInterval = 0.1 ); + BOOL ShouldAdvanceRoute( float flWaypointDist ); + + inline void SetFlyingMomentum( float momentum ) { m_momentum = momentum; } + inline void SetFlyingFlapSound( const char *pFlapSound ) { m_pFlapSound = pFlapSound; } + inline void SetFlyingSpeed( float speed ) { m_flightSpeed = speed; } + float CeilingZ( const Vector &position ); + float FloorZ( const Vector &position ); + BOOL ProbeZ( const Vector &position, const Vector &probe, float *pFraction ); + + + // UNDONE: Save/restore this stuff!!! +protected: + Vector m_vecTravel; // Current direction + float m_flightSpeed; // Current flight speed (decays when not flapping or gliding) + float m_stopTime; // Last time we stopped (to avoid switching states too soon) + float m_momentum; // Weight for desired vs. momentum velocity + const char *m_pFlapSound; +}; + + +#endif //FLYINGMONSTER_H + diff --git a/dlls/func_break.cpp b/dlls/func_break.cpp new file mode 100644 index 0000000..945b247 --- /dev/null +++ b/dlls/func_break.cpp @@ -0,0 +1,1207 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== bmodels.cpp ======================================================== + + spawn, think, and use functions for entities that use brush models + +*/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "func_break.h" +#include "decals.h" +#include "explode.h" + +extern DLL_GLOBAL Vector g_vecAttackDir; + +// =================== FUNC_Breakable ============================================== + +// Just add more items to the bottom of this array and they will automagically be supported +// This is done instead of just a classname in the FGD so we can control which entities can +// be spawned, and still remain fairly flexible +const char *CBreakable::pSpawnObjects[] = +{ + NULL, // 0 + "ammo_9mmclip", // 4 + "weapon_shotgun", // 8 + "ammo_buckshot", // 9 + "weapon_rpg", // 14 + "ammo_rpgclip", // 15 + "weapon_handgrenade",// 17 + "weapon_aks", //22 + "ammo_aks" //23 + "ammo_aksbox" //24 + "weapon_aps" //25 + "ammo_aps" //26 + "ammo_apsbox" //27 + "weapon_val" //28 + "ammo_val" //29 + "ammo_valbox" //30 + "weapon_pkm", //31 + "ammo_rpk" //32 + "ammo_rpkbox" //33 + "weapon_ak74" //34 + "ammo_ak74" //35 + "ammo_ak74box" //36 + "weapon_groza" //37 + "ammo_groza" //38 + "ammo_grozabox" //39 +}; + +void CBreakable::KeyValue( KeyValueData* pkvd ) +{ + // UNDONE_WC: explicitly ignoring these fields, but they shouldn't be in the map file! + if (FStrEq(pkvd->szKeyName, "explosion")) + { + if (!stricmp(pkvd->szValue, "directed")) + m_Explosion = expDirected; + else if (!stricmp(pkvd->szValue, "random")) + m_Explosion = expRandom; + else + m_Explosion = expRandom; + + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "material")) + { + int i = atoi( pkvd->szValue); + + // 0:glass, 1:metal, 2:flesh, 3:wood + + if ((i < 0) || (i >= matLastMaterial)) + m_Material = matWood; + else + m_Material = (Materials)i; + + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "deadmodel")) + { + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "shards")) + { +// m_iShards = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "gibmodel") ) + { + m_iszGibModel = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spawnobject") ) + { + if( Q_isdigit( pkvd->szValue )) + { + int object = Q_atoi( pkvd->szValue ); + if ( object > 0 && object < ARRAYSIZE(pSpawnObjects) ) + m_iszSpawnObject = MAKE_STRING( pSpawnObjects[object] ); + } + else m_iszSpawnObject = ALLOC_STRING( pkvd->szValue ); // allow custom spawn + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "explodemagnitude") ) + { + ExplosionSetMagnitude( atoi( pkvd->szValue ) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "lip") ) + pkvd->fHandled = TRUE; + else if (FStrEq(pkvd->szKeyName, "respawn") ) //LRC + { + m_iRespawnTime = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "whenhit") ) //LRC + { + m_iszWhenHit = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iClass") ) + { + m_iClass = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + + +// +// func_breakable - bmodel that breaks into pieces after taking damage +// +LINK_ENTITY_TO_CLASS( func_breakable, CBreakable ); +TYPEDESCRIPTION CBreakable::m_SaveData[] = +{ + DEFINE_FIELD( CBreakable, m_Material, FIELD_INTEGER ), + DEFINE_FIELD( CBreakable, m_Explosion, FIELD_INTEGER ), + +// Don't need to save/restore these because we precache after restore +// DEFINE_FIELD( CBreakable, m_idShard, FIELD_INTEGER ), + + DEFINE_FIELD( CBreakable, m_angle, FIELD_FLOAT ), + DEFINE_FIELD( CBreakable, m_iszGibModel, FIELD_STRING ), + DEFINE_FIELD( CBreakable, m_iszSpawnObject, FIELD_STRING ), + + // Explosion magnitude is stored in pev->impulse + + //LRC- time until respawn + DEFINE_FIELD( CBreakable, m_iRespawnTime, FIELD_INTEGER ), + //LRC- health to set on respawn + DEFINE_FIELD( CBreakable, m_iInitialHealth, FIELD_INTEGER ), + DEFINE_FIELD( CBreakable, m_iInitialRenderAmt, FIELD_INTEGER ), + DEFINE_FIELD( CBreakable, m_iInitialRenderMode, FIELD_INTEGER ), + DEFINE_FIELD( CBreakable, m_iszWhenHit, FIELD_STRING ), +// DEFINE_FIELD( CBreakable, m_pHitProxy, FIELD_CLASSPTR ), +}; + +IMPLEMENT_SAVERESTORE( CBreakable, CBaseEntity ); + +void CBreakable::Spawn( void ) +{ + Precache( ); + + if ( FBitSet( pev->spawnflags, SF_BREAK_TRIGGER_ONLY ) ) + pev->takedamage = DAMAGE_NO; + else + pev->takedamage = DAMAGE_YES; + + if (m_iClass) //LRC - might these additions cause problems? + { + pev->flags |= FL_MONSTER; + pev->view_ofs = (pev->maxs + pev->mins) / 2; + } + +/* if (m_iszWhenHit) //LRC - locus trigger + { + m_pHitProxy = GetClassPtr( (CPointEntity*)NULL ); + }*/ + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + m_angle = pev->angles.y; + pev->angles.y = 0; + m_iInitialHealth = pev->health; + m_iInitialRenderAmt = pev->renderamt; + m_iInitialRenderMode = pev->rendermode; + + // HACK: matGlass can receive decals, we need the client to know about this + // so use class to store the material flag + if ( m_Material == matGlass ) + { + pev->playerclass = 1; + } + + SET_MODEL(ENT(pev), STRING(pev->model) );//set size and link into world. + + SetTouch(&CBreakable:: BreakTouch ); + SetUse(&CBreakable:: BreakUse ); + if ( FBitSet( pev->spawnflags, SF_BREAK_TRIGGER_ONLY ) ) // Only break on trigger + SetTouch( NULL ); + + // Flag unbreakable glass as "worldbrush" so it will block ALL tracelines + if ( !IsBreakable() && pev->rendermode != kRenderNormal ) + pev->flags |= FL_WORLDBRUSH; + + if (m_iStyle >= 32) + LIGHT_STYLE(m_iStyle, "z"); + else if (m_iStyle <= -32) + LIGHT_STYLE(-m_iStyle, "a"); +} + +STATE CBreakable::GetState( void ) +{ + if (m_iRespawnTime) + { + if (pev->effects & EF_NODRAW) + return STATE_OFF; + else + return STATE_ON; + } + else return STATE_OFF; +} + +const char *CBreakable::pSoundsWood[] = +{ + "debris/wood1.wav", + "debris/wood2.wav", + "debris/wood3.wav", +}; + +const char *CBreakable::pSoundsFlesh[] = +{ + "debris/flesh1.wav", + "debris/flesh2.wav", + "debris/flesh3.wav", + "debris/flesh5.wav", + "debris/flesh6.wav", + "debris/flesh7.wav", +}; + +const char *CBreakable::pSoundsMetal[] = +{ + "debris/metal1.wav", + "debris/metal2.wav", + "debris/metal3.wav", +}; + +const char *CBreakable::pSoundsConcrete[] = +{ + "debris/concrete1.wav", + "debris/concrete2.wav", + "debris/concrete3.wav", +}; + + +const char *CBreakable::pSoundsGlass[] = +{ + "debris/glass1.wav", + "debris/glass2.wav", + "debris/glass3.wav", +}; + +const char **CBreakable::MaterialSoundList( Materials precacheMaterial, int &soundCount ) +{ + const char **pSoundList = NULL; + + switch ( precacheMaterial ) + { + case matWood: + pSoundList = pSoundsWood; + soundCount = ARRAYSIZE(pSoundsWood); + break; + case matFlesh: + pSoundList = pSoundsFlesh; + soundCount = ARRAYSIZE(pSoundsFlesh); + break; + case matComputer: + case matUnbreakableGlass: + case matGlass: + pSoundList = pSoundsGlass; + soundCount = ARRAYSIZE(pSoundsGlass); + break; + + case matMetal: + pSoundList = pSoundsMetal; + soundCount = ARRAYSIZE(pSoundsMetal); + break; + + case matCinderBlock: + case matRocks: + pSoundList = pSoundsConcrete; + soundCount = ARRAYSIZE(pSoundsConcrete); + break; + + + case matCeilingTile: + case matNone: + default: + soundCount = 0; + break; + } + + return pSoundList; +} + +void CBreakable::MaterialSoundPrecache( Materials precacheMaterial ) +{ + const char **pSoundList; + int i, soundCount = 0; + + pSoundList = MaterialSoundList( precacheMaterial, soundCount ); + + for ( i = 0; i < soundCount; i++ ) + { + PRECACHE_SOUND( (char *)pSoundList[i] ); + } +} + +void CBreakable::MaterialSoundRandom( edict_t *pEdict, Materials soundMaterial, float volume ) +{ + const char **pSoundList; + int soundCount = 0; + + pSoundList = MaterialSoundList( soundMaterial, soundCount ); + + if ( soundCount ) + EMIT_SOUND( pEdict, CHAN_BODY, pSoundList[ RANDOM_LONG(0,soundCount-1) ], volume, 1.0 ); +} + + +void CBreakable::Precache( void ) +{ + const char *pGibName; + + switch (m_Material) + { + case matWood: + pGibName = "models/woodgibs.mdl"; + + PRECACHE_SOUND("debris/bustcrate1.wav"); + PRECACHE_SOUND("debris/bustcrate2.wav"); + break; + case matFlesh: + pGibName = "models/fleshgibs.mdl"; + + PRECACHE_SOUND("debris/bustflesh1.wav"); + PRECACHE_SOUND("debris/bustflesh2.wav"); + break; + case matComputer: + PRECACHE_SOUND("buttons/spark5.wav"); + PRECACHE_SOUND("buttons/spark6.wav"); + pGibName = "models/computergibs.mdl"; + + PRECACHE_SOUND("debris/bustmetal1.wav"); + PRECACHE_SOUND("debris/bustmetal2.wav"); + break; + + case matUnbreakableGlass: + case matGlass: + pGibName = "models/glassgibs.mdl"; + + PRECACHE_SOUND("debris/bustglass1.wav"); + PRECACHE_SOUND("debris/bustglass2.wav"); + break; + case matMetal: + pGibName = "models/metalplategibs.mdl"; + + PRECACHE_SOUND("debris/bustmetal1.wav"); + PRECACHE_SOUND("debris/bustmetal2.wav"); + break; + case matCinderBlock: + pGibName = "models/cindergibs.mdl"; + + PRECACHE_SOUND("debris/bustconcrete1.wav"); + PRECACHE_SOUND("debris/bustconcrete2.wav"); + break; + case matRocks: + pGibName = "models/rockgibs.mdl"; + + PRECACHE_SOUND("debris/bustconcrete1.wav"); + PRECACHE_SOUND("debris/bustconcrete2.wav"); + break; + case matCeilingTile: + pGibName = "models/ceilinggibs.mdl"; + + PRECACHE_SOUND ("debris/bustceiling.wav"); + break; + } + MaterialSoundPrecache( m_Material ); + if ( m_iszGibModel ) + pGibName = STRING(m_iszGibModel); + + m_idShard = PRECACHE_MODEL( (char *)pGibName ); + //ALERT(at_debug,"Breakable: Shard is %d\n",m_idShard); + + // Precache the spawn item's data + if ( m_iszSpawnObject ) + UTIL_PrecacheOther( (char *)STRING( m_iszSpawnObject ) ); +} + +// play shard sound when func_breakable takes damage. +// the more damage, the louder the shard sound. + + +void CBreakable::DamageSound( void ) +{ + int pitch; + float fvol; + char *rgpsz[6]; + int i; + int material = m_Material; + +// if (RANDOM_LONG(0,1)) +// return; + + if (RANDOM_LONG(0,2)) + pitch = PITCH_NORM; + else + pitch = 95 + RANDOM_LONG(0,34); + + fvol = RANDOM_FLOAT(0.75, 1.0); + + if (material == matComputer && RANDOM_LONG(0,1)) + material = matMetal; + + switch (material) + { + case matComputer: + case matGlass: + case matUnbreakableGlass: + rgpsz[0] = "debris/glass1.wav"; + rgpsz[1] = "debris/glass2.wav"; + rgpsz[2] = "debris/glass3.wav"; + i = 3; + break; + + case matWood: + rgpsz[0] = "debris/wood1.wav"; + rgpsz[1] = "debris/wood2.wav"; + rgpsz[2] = "debris/wood3.wav"; + i = 3; + break; + + case matMetal: + rgpsz[0] = "debris/metal1.wav"; + rgpsz[1] = "debris/metal3.wav"; + rgpsz[2] = "debris/metal2.wav"; + i = 2; + break; + + case matFlesh: + rgpsz[0] = "debris/flesh1.wav"; + rgpsz[1] = "debris/flesh2.wav"; + rgpsz[2] = "debris/flesh3.wav"; + rgpsz[3] = "debris/flesh5.wav"; + rgpsz[4] = "debris/flesh6.wav"; + rgpsz[5] = "debris/flesh7.wav"; + i = 6; + break; + + case matRocks: + case matCinderBlock: + rgpsz[0] = "debris/concrete1.wav"; + rgpsz[1] = "debris/concrete2.wav"; + rgpsz[2] = "debris/concrete3.wav"; + i = 3; + break; + + case matCeilingTile: + // UNDONE: no ceiling tile shard sound yet + i = 0; + break; + } + + if (i) + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, rgpsz[RANDOM_LONG(0,i-1)], fvol, ATTN_NORM, 0, pitch); +} + +void CBreakable::BreakTouch( CBaseEntity *pOther ) +{ + float flDamage; + entvars_t* pevToucher = pOther->pev; + + // only players can break these right now + if ( !pOther->IsPlayer() || !IsBreakable() ) + { + return; + } + + if ( FBitSet ( pev->spawnflags, SF_BREAK_TOUCH ) ) + {// can be broken when run into + flDamage = pevToucher->velocity.Length() * 0.01; + + if (flDamage >= pev->health) + { + SetTouch( NULL ); + TakeDamage(pevToucher, pevToucher, flDamage, DMG_CRUSH); + + // do a little damage to player if we broke glass or computer + pOther->TakeDamage( pev, pev, flDamage/4, DMG_SLASH ); + } + } + + if ( FBitSet ( pev->spawnflags, SF_BREAK_PRESSURE ) && pevToucher->absmin.z >= pev->maxs.z - 2 ) + {// can be broken when stood upon + + // play creaking sound here. + DamageSound(); + + SetThink(&CBreakable:: Die ); + SetTouch( NULL ); + + if ( m_flDelay == 0 ) + {// !!!BUGBUG - why doesn't zero delay work? + m_flDelay = 0.1; + } + + SetNextThink( m_flDelay ); + + } + +} + + +// +// Smash the our breakable object +// + +// Break when triggered +void CBreakable::BreakUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // for a respawnable entity, ON means someone wants it to respawn- but this one's solid already. + if (m_iRespawnTime && useType == USE_ON) + return; + if ( IsBreakable() ) + { + pev->angles.y = m_angle; + UTIL_MakeVectors(pev->angles); + g_vecAttackDir = gpGlobals->v_forward; + + Die(); + } +} + +//LRC +void CBreakable::RespawnUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // OFF means someone wants it to break, but this one's broken already. + if (useType == USE_OFF) + { + if (m_iRespawnTime > 0) + { + // reset my respawn time + SetThink(&CBreakable:: RespawnThink ); + SetNextThink( m_iRespawnTime ); + } + return; + } +// ALERT(at_debug,"Respawn trigger received\n"); + SetThink(&CBreakable:: RespawnThink ); + SetNextThink( 0.1 ); +} + +//LRC +void CBreakable::RespawnThink( void ) +{ +// ALERT(at_debug,"RespawnThink: "); + CBaseEntity *pList[2]; + int count = UTIL_EntitiesInBox( pList, 2, pev->mins, pev->maxs, FL_MONSTER | FL_CLIENT ); + if ( count ) + { + // Can't respawn right now, a monster or player is in the way. Wait a bit. +// ALERT(at_debug,"Respawn failed, count is %d\n",count); + SetThink(&CBreakable:: RespawnThink ); + SetNextThink( 2 ); // CONSIDER: change this number? + } + else + { + // fade in, don't just appear(?) + if (pev->spawnflags & SF_BREAK_FADE_RESPAWN) + { + SetThink(&CBreakable:: RespawnFadeThink ); + SetNextThink( 0.1 ); + pev->renderamt = 0; + if (m_iInitialRenderMode == kRenderNormal) + { + pev->rendermode = kRenderTransTexture; + m_iInitialRenderAmt = 255; + } + } +// ALERT(at_debug,"Respawn OK\n"); + pev->solid = SOLID_BSP; + pev->effects &= ~EF_NODRAW; + pev->health = m_iInitialHealth; + SetUse(&CBreakable:: BreakUse ); + if ( !FBitSet( pev->spawnflags, SF_BREAK_TRIGGER_ONLY ) ) + pev->takedamage = DAMAGE_YES; + + // trigger the "fire on respawn" target + FireTargets( STRING(pev->netname), this, this, USE_TOGGLE, 0); + } +} + +void CBreakable::RespawnFadeThink ( void ) +{ + int newamt = min( pev->renderamt + 50, m_iInitialRenderAmt); +// ALERT(at_debug, "FadeThink: %d changed to %d\n",pev->renderamt,newamt); + pev->renderamt = newamt; + if (pev->renderamt < m_iInitialRenderAmt) + { + SetNextThink( 0.1 ); + } + else + { + pev->rendermode = m_iInitialRenderMode; + } +} + +void CBreakable::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + // random spark if this is a 'computer' object + if (RANDOM_LONG(0,1) ) + { + switch( m_Material ) + { + case matComputer: + { + UTIL_Sparks( ptr->vecEndPos ); + + float flVolume = RANDOM_FLOAT ( 0.7 , 1.0 );//random volume range + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark5.wav", flVolume, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/spark6.wav", flVolume, ATTN_NORM); break; + } + } + break; + + case matUnbreakableGlass: + UTIL_Ricochet( ptr->vecEndPos, RANDOM_FLOAT(0.5,1.5) ); + break; + } + } + + //LRC + if (m_iszWhenHit) + { +// m_pHitProxy->pev->origin = ptr->vecEndPos; +// m_pHitProxy->pev->velocity = vecDir; +// FireTargets( STRING(m_iszWhenHit), m_pHitProxy, this, USE_TOGGLE, 0 ); + FireTargets( STRING(m_iszWhenHit), this, this, USE_TOGGLE, 0 ); + } + + CBaseDelay::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + +//========================================================= +// Special takedamage for func_breakable. Allows us to make +// exceptions that are breakable-specific +// bitsDamageType indicates the type of damage sustained ie: DMG_CRUSH +//========================================================= +int CBreakable :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + Vector vecTemp; + + // if Attacker == Inflictor, the attack was a melee or other instant-hit attack. + // (that is, no actual entity projectile was involved in the attack so use the shooter's origin). + if ( pevAttacker == pevInflictor ) + { + vecTemp = pevInflictor->origin - ( pev->absmin + ( pev->size * 0.5 ) ); + + // if a client hit the breakable with a crowbar, and breakable is crowbar-sensitive, break it now. + if ( FBitSet ( pevAttacker->flags, FL_CLIENT ) && + FBitSet ( pev->spawnflags, SF_BREAK_CROWBAR ) && (bitsDamageType & DMG_CLUB)) + flDamage = pev->health; + } + else + // an actual missile was involved. + { + vecTemp = pevInflictor->origin - ( pev->absmin + ( pev->size * 0.5 ) ); + } + + if (!IsBreakable()) + return 0; + + // Breakables take double damage from the crowbar + if ( bitsDamageType & DMG_CLUB ) + flDamage *= 2; + + // Boxes / glass / etc. don't take much poison damage, just the impact of the dart - consider that 10% + if ( bitsDamageType & DMG_POISON ) + flDamage *= 0.1; + +// this global is still used for glass and other non-monster killables, along with decals. + g_vecAttackDir = vecTemp.Normalize(); + +// do the damage + pev->health -= flDamage; + if (pev->health <= 0) + { +// LRC - Die() does everything necessary +// if (!m_iRespawnTime) +// { +// Killed( pevAttacker, GIB_NORMAL ); +// } + Die(); + return 0; + } + + // Make a shard noise each time func breakable is hit. + // Don't play shard noise if cbreakable actually died. + + DamageSound(); + + return 1; +} + + +void CBreakable::Die( void ) +{ + Vector vecSpot;// shard origin + Vector vecVelocity;// shard velocity + CBaseEntity *pEntity = NULL; + char cFlag = 0; + int pitch; + float fvol; + + pitch = 95 + RANDOM_LONG(0,29); + + if (pitch > 97 && pitch < 103) + pitch = 100; + + // The more negative pev->health, the louder + // the sound should be. + + fvol = RANDOM_FLOAT(0.85, 1.0) + (abs(pev->health) / 100.0); + + if (fvol > 1.0) + fvol = 1.0; + + + switch (m_Material) + { + case matGlass: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustglass1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustglass2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_GLASS; + break; + + case matWood: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustcrate1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustcrate2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_WOOD; + break; + + case matComputer: + case matMetal: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustmetal1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustmetal2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_METAL; + break; + + case matFlesh: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustflesh1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustflesh2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_FLESH; + break; + + case matRocks: + case matCinderBlock: + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustconcrete1.wav", fvol, ATTN_NORM, 0, pitch); + break; + case 1: EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustconcrete2.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + cFlag = BREAK_CONCRETE; + break; + + case matCeilingTile: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "debris/bustceiling.wav", fvol, ATTN_NORM, 0, pitch); + break; + } + + + if (m_Explosion == expDirected) + vecVelocity = g_vecAttackDir * 200; + else + { + vecVelocity.x = 0; + vecVelocity.y = 0; + vecVelocity.z = 0; + } + + vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z ); + + // size + WRITE_COORD( pev->size.x); + WRITE_COORD( pev->size.y); + WRITE_COORD( pev->size.z); + + // velocity + WRITE_COORD( vecVelocity.x ); + WRITE_COORD( vecVelocity.y ); + WRITE_COORD( vecVelocity.z ); + + // randomization + WRITE_BYTE( 10 ); + + // Model + WRITE_SHORT( m_idShard ); //model id# + + // # of shards + WRITE_BYTE( 0 ); // let client decide + + // duration + WRITE_BYTE( 25 );// 2.5 seconds + + // flags + WRITE_BYTE( cFlag ); + MESSAGE_END(); + + float size = pev->size.x; + if ( size < pev->size.y ) + size = pev->size.y; + if ( size < pev->size.z ) + size = pev->size.z; + + // !!! HACK This should work! + // Build a box above the entity that looks like an 8 pixel high sheet + Vector mins = pev->absmin; + Vector maxs = pev->absmax; + mins.z = pev->absmax.z; + maxs.z += 8; + + // BUGBUG -- can only find 256 entities on a breakable -- should be enough + CBaseEntity *pList[256]; + int count = UTIL_EntitiesInBox( pList, 256, mins, maxs, FL_ONGROUND ); + if ( count ) + { + for ( int i = 0; i < count; i++ ) + { + ClearBits( pList[i]->pev->flags, FL_ONGROUND ); + pList[i]->pev->groundentity = NULL; + } + } + + // If I'm getting removed, don't fire something that could fire myself + if (!m_iRespawnTime) + pev->targetname = 0; + + pev->solid = SOLID_NOT; + pev->effects |= EF_NODRAW; + pev->takedamage = DAMAGE_NO; + +// if (m_iStyle >= 32) buz +// LIGHT_STYLE(m_iStyle, "a"); +// else if (m_iStyle <= -32) +// LIGHT_STYLE(-m_iStyle, "z"); + + // Fire targets on break + SUB_UseTargets( NULL, USE_TOGGLE, 0 ); + + if (m_iRespawnTime == -1) + { +// ALERT(at_debug,"Waiting for respawn trigger\n"); + SetUse(&CBreakable:: RespawnUse ); + } + else if (m_iRespawnTime) + { +// ALERT(at_debug,"Respawning in %d secs\n",m_iRespawnTime); + SetThink(&CBreakable:: RespawnThink ); + SetNextThink( m_iRespawnTime ); + } + else + { +// ALERT(at_debug,"No respawn\n"); + + //tidy up +/* if (m_pHitProxy) + { + m_pHitProxy->SetThink(&CBreakable::SUB_Remove ); + m_pHitProxy->SetNextThink( 0.1 ); + m_pHitProxy = NULL; + }*/ + + SetThink(&CBreakable::SUB_Remove ); + SetNextThink( 0.1 ); +// ALERT(at_console, "Set SUB_Remove\n"); + } + + if ( m_iszSpawnObject ) + CBaseEntity::Create( (char *)STRING(m_iszSpawnObject), VecBModelOrigin(pev), pev->angles, edict() ); + + + if ( Explodable() ) + { + ExplosionCreate( Center(), pev->angles, edict(), ExplosionMagnitude(), TRUE ); + } +} + + +BOOL CBreakable :: IsBreakable( void ) +{ + return m_Material != matUnbreakableGlass; +} + + +const char *CBreakable :: DamageDecal( int bitsDamageType ) +{ + switch( m_Material ) + { + case matGlass: + case matUnbreakableGlass: + return "shot_glass"; + case matWood: + return "shot_wood"; + case matMetal: + return "shot_metal"; + } + + return CBaseEntity::DamageDecal( bitsDamageType ); +} + + +class CPushable : public CBreakable +{ +public: + void Spawn ( void ); + void Precache( void ); + void Touch ( CBaseEntity *pOther ); + void Move( CBaseEntity *pMover, int push ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT StopSound( void ); +// virtual void SetActivator( CBaseEntity *pActivator ) { m_pPusher = pActivator; } + + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_CONTINUOUS_USE; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + inline float MaxSpeed( void ) { return m_maxSpeed; } + + // breakables use an overridden takedamage + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + + static TYPEDESCRIPTION m_SaveData[]; + + static char *m_soundNames[3]; + int m_lastSound; // no need to save/restore, just keeps the same sound from playing twice in a row + float m_maxSpeed; + float m_soundTime; +}; + +TYPEDESCRIPTION CPushable::m_SaveData[] = +{ + DEFINE_FIELD( CPushable, m_maxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CPushable, m_soundTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CPushable, CBreakable ); + +LINK_ENTITY_TO_CLASS( func_pushable, CPushable ); + +char *CPushable :: m_soundNames[3] = { "debris/pushbox1.wav", "debris/pushbox2.wav", "debris/pushbox3.wav" }; + + +void CPushable :: Spawn( void ) +{ + Vector vecMins = pev->mins; + Vector vecMaxs = pev->maxs; + + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + CBreakable::Spawn(); + else + Precache( ); + + pev->movetype = MOVETYPE_PUSHSTEP; + pev->solid = SOLID_BBOX; + SET_MODEL( ENT(pev), STRING(pev->model) ); + +// UTIL_SetSize( pev, vecMins, vecMaxs ); + + if ( pev->friction > 399 ) + pev->friction = 399; + + m_maxSpeed = 400 - pev->friction; + SetBits( pev->flags, FL_FLOAT ); + pev->friction = 0; + + pev->origin.z += 1; // Pick up off of the floor + UTIL_SetOrigin( this, pev->origin ); + + // Multiply by area of the box's cross-section (assume 1000 units^3 standard volume) + pev->skin = ( pev->skin * (pev->maxs.x - pev->mins.x) * (pev->maxs.y - pev->mins.y) ) * 0.0005; + m_soundTime = 0; +} + + +void CPushable :: Precache( void ) +{ + for ( int i = 0; i < 3; i++ ) + PRECACHE_SOUND( m_soundNames[i] ); + + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + CBreakable::Precache( ); +} + + +void CPushable :: KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "size") ) + { + int bbox = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + switch( bbox ) + { + case 0: // Point + UTIL_SetSize(pev, Vector(-8, -8, -8), Vector(8, 8, 8)); + break; + + case 2: // Big Hull!?!? !!!BUGBUG Figure out what this hull really is + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN*2, VEC_DUCK_HULL_MAX*2); + break; + + case 3: // Player duck + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); + break; + + default: + case 1: // Player + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + break; + } + + } + else if ( FStrEq(pkvd->szKeyName, "buoyancy") ) + { + pev->skin = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBreakable::KeyValue( pkvd ); +} + + +// Pull the func_pushable +void CPushable :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !pActivator || !pActivator->IsPlayer() ) + { + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + this->CBreakable::Use( pActivator, pCaller, useType, value ); + return; + } + + if ( pev->spawnflags & SF_PUSH_NOPULL ) return; //LRC: a non-pullable pushable. + + if ( pActivator->pev->velocity != g_vecZero ) + Move( pActivator, 0 ); +} + + +void CPushable :: Touch( CBaseEntity *pOther ) +{ + if ( FClassnameIs( pOther->pev, "worldspawn" ) ) + return; + + Move( pOther, 1 ); +} + + +void CPushable :: Move( CBaseEntity *pOther, int push ) +{ + entvars_t* pevToucher = pOther->pev; + int playerTouch = 0; + + // Is entity standing on this pushable ? + if ( FBitSet(pevToucher->flags,FL_ONGROUND) && pevToucher->groundentity && VARS(pevToucher->groundentity) == pev ) + { + // Only push if floating + if ( pev->waterlevel > 0 && pev->watertype > CONTENTS_FLYFIELD) + pev->velocity.z += pevToucher->velocity.z * 0.1; + + return; + } + + + if ( pOther->IsPlayer() ) + { + if ( push && !(pevToucher->button & (IN_FORWARD|IN_MOVERIGHT|IN_MOVELEFT|IN_BACK)) ) + return; + + if ( !push && !(pevToucher->button & (IN_BACK)) ) return; + playerTouch = 1; + } + + float factor; + + if ( playerTouch ) + { + if ( !(pevToucher->flags & FL_ONGROUND) ) // Don't push away from jumping/falling players unless in water + { + if ( pev->waterlevel < 1 || pev->watertype <= CONTENTS_FLYFIELD) + return; + else + factor = 0.1; + } + else + factor = 1; + } + else + factor = 0.25; + + if (!push) + factor = factor*0.5; + + pev->velocity.x += pevToucher->velocity.x * factor; + pev->velocity.y += pevToucher->velocity.y * factor; + + float length = sqrt( pev->velocity.x * pev->velocity.x + pev->velocity.y * pev->velocity.y ); + if ( push && (length > MaxSpeed()) ) + { + pev->velocity.x = (pev->velocity.x * MaxSpeed() / length ); + pev->velocity.y = (pev->velocity.y * MaxSpeed() / length ); + } + if ( playerTouch ) + { + pevToucher->velocity.x = pev->velocity.x; + pevToucher->velocity.y = pev->velocity.y; + if ( (gpGlobals->time - m_soundTime) > 0.7 ) + { + m_soundTime = gpGlobals->time; + if ( length > 0 && FBitSet(pev->flags,FL_ONGROUND) ) + { + m_lastSound = RANDOM_LONG(0,2); + EMIT_SOUND(ENT(pev), CHAN_WEAPON, m_soundNames[m_lastSound], 0.5, ATTN_NORM); + // SetThink( StopSound ); + // SetNextThink( 0.1 ); + } + else + STOP_SOUND( ENT(pev), CHAN_WEAPON, m_soundNames[m_lastSound] ); + } + } +} + +#if 0 +void CPushable::StopSound( void ) +{ + Vector dist = pev->oldorigin - pev->origin; + if ( dist.Length() <= 0 ) + STOP_SOUND( ENT(pev), CHAN_WEAPON, m_soundNames[m_lastSound] ); +} +#endif + +int CPushable::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + return CBreakable::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); + + return 1; +} + diff --git a/dlls/func_break.h b/dlls/func_break.h new file mode 100644 index 0000000..4ed14d7 --- /dev/null +++ b/dlls/func_break.h @@ -0,0 +1,90 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef FUNC_BREAK_H +#define FUNC_BREAK_H + +typedef enum { expRandom, expDirected} Explosions; +typedef enum { matGlass = 0, matWood, matMetal, matFlesh, matCinderBlock, matCeilingTile, matComputer, matUnbreakableGlass, matRocks, matNone, matLastMaterial } Materials; + +extern unsigned short m_usSmokePuff; // buz - smoke event + +#define NUM_SHARDS 6 // this many shards spawned when breakable objects break; + +class CBreakable : public CBaseDelay +{ +public: + // basic functions + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData* pkvd); + void EXPORT BreakTouch( CBaseEntity *pOther ); + void EXPORT BreakUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT RespawnUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT RespawnThink( void ); + void EXPORT RespawnFadeThink( void ); + void DamageSound( void ); + virtual int Classify ( void ) { return m_iClass; } + + // breakables use an overridden takedamage + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + // To spark when hit + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ); + + BOOL IsBreakable( void ); + BOOL SparkWhenHit( void ); + + STATE GetState( void ); + + const char* DamageDecal( int bitsDamageType ); + + void EXPORT Die( void ); + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + inline BOOL Explodable( void ) { return ExplosionMagnitude() > 0; } + inline int ExplosionMagnitude( void ) { return pev->impulse; } + inline void ExplosionSetMagnitude( int magnitude ) { pev->impulse = magnitude; } + + static void MaterialSoundPrecache( Materials precacheMaterial ); + static void MaterialSoundRandom( edict_t *pEdict, Materials soundMaterial, float volume ); + static const char **MaterialSoundList( Materials precacheMaterial, int &soundCount ); + + static const char *pSoundsWood[]; + static const char *pSoundsFlesh[]; + static const char *pSoundsGlass[]; + static const char *pSoundsMetal[]; + static const char *pSoundsConcrete[]; + static const char *pSpawnObjects[]; + + static TYPEDESCRIPTION m_SaveData[]; + + Materials m_Material; + Explosions m_Explosion; + int m_idShard; + float m_angle; + int m_iszGibModel; + int m_iszSpawnObject; + //LRC + int m_iRespawnTime; + int m_iInitialHealth; + int m_iInitialRenderAmt; + int m_iInitialRenderMode; + int m_iClass; //so that monsters will attack it + int m_iszWhenHit; // locus trigger +// CPointEntity *m_pHitProxy; +}; + +#endif // FUNC_BREAK_H diff --git a/dlls/func_physbox.cpp b/dlls/func_physbox.cpp new file mode 100644 index 0000000..e251bf1 --- /dev/null +++ b/dlls/func_physbox.cpp @@ -0,0 +1,575 @@ + +/********************************************************* +* Closed mod Physical entity: Original code of Nucleo * +**********************************************************/ + + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "func_break.h" +#include "decals.h" +#include "explode.h" + +extern DLL_GLOBAL Vector g_vecAttackDir; + +#define M_PI 3.141592653589793238462643 //NCL: Correct It! + +// NCL: Based On "func_breakable" +class CPhy : public CBreakable +{ +public: + void Spawn ( void ); + void Precache( void ); + void EXPORT PushableThink( void ); + void Touch ( CBaseEntity *pOther ); + void Move( CBaseEntity *pMover, int push ); + void KeyValue( KeyValueData *pkvd ); + void Solid( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT StopSound( void ); + + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_CONTINUOUS_USE; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + void SetModelCollisionBox( void ); + + void TestBeam( Vector org, Vector end, Vector color, int life, int width ); + + inline float MaxSpeed( void ) { return m_maxSpeed; } + + // NCL: Åñëè ñòîèò ôëàã Breakable + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + + static TYPEDESCRIPTION m_SaveData[]; + + int m_lastSound; + float m_maxSpeed; + float m_soundTime; +}; + +TYPEDESCRIPTION CPhy::m_SaveData[] = +{ + DEFINE_FIELD( CPhy, m_maxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CPhy, m_soundTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CPhy, CBreakable ); + +// NCL: Temporary name +LINK_ENTITY_TO_CLASS( func_physbox, CPhy ); + +void CPhy :: Spawn( void ) +{ + // NCL: Brushes only + Vector vecMins = pev->mins; + Vector vecMaxs = pev->maxs; + + CBreakable::Spawn(); + + pev->movetype = MOVETYPE_BOUNCE;//MOVETYPE_PUSHSTEP; + pev->solid = SOLID_SLIDEBOX; + + // NCL: UNDONE! If developer sets path to the model file + SET_MODEL( ENT(pev), STRING(pev->model) ); + // NCL: Model needs collision! I Can do it, but i'm lazy... =) + + if ( pev->friction > 399 ) + pev->friction = 399; + + m_maxSpeed = 400 - pev->friction; + SetBits( pev->flags, FL_FLOAT ); + + pev->friction = 0.5; + pev->gravity = 1; + + pev->origin.z += 1; // NCL: Pick up it to fix collision bug + + UTIL_SetOrigin( this, pev->origin ); + + SetThink(&CPhy:: PushableThink ); + pev->nextthink = 0.1; + + // Multiply by area of the box's cross-section (assume 1000 units^3 standard volume) + pev->skin = ( pev->skin * (pev->maxs.x - pev->mins.x) * (pev->maxs.y - pev->mins.y) ) * 0.0005; + m_soundTime = 0; +} + +void CPhy::SetModelCollisionBox(void) +{ + +} + + +void CPhy :: Precache( void ) +{ + + //PRECACHE_MODEL(pev->model); // Temporary unused. + CBreakable::Precache( ); + +} + + +void CPhy :: KeyValue( KeyValueData *pkvd ) +{ + + // NCL: UnDone! Physics needs more parameters! + if ( FStrEq(pkvd->szKeyName, "size") ) // NCL: Id developer sets size manually + { + int bbox = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + SetModelCollisionBox(); + + } + else if ( FStrEq(pkvd->szKeyName, "skin") ) // NCL: Brushes needs a skinning? + { + pev->skin = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBreakable::KeyValue( pkvd ); +} + + +void CPhy :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !pActivator || !pActivator->IsPlayer() ) + { + this->CBreakable::Use( pActivator, pCaller, useType, value ); + return; + } + + if ( pActivator->pev->velocity != g_vecZero ) + Move( pActivator, 0 ); +} + +/* +=================== +AngleBetweenVectors +=================== +*/ + +float AngleBetweenVectors( const vec3_t v1, const vec3_t v2 ) +{ + float angle; + float l1 = v1.Length(); + float l2 = v2.Length(); + + if ( !l1 || !l2 ) + return 0.0f; + + angle = acos( DotProduct( v1, v2 ) / (l1*l2) ); + angle = ( angle * 180.0f ) / M_PI; + + return angle; +} + +void NormalizeAngles( float *angles ) +{ + int i; + // NCL: Normalize angles. It needs to update. + for ( i = 0; i < 3; i++ ) + { + if ( angles[i] > 180.0 ) + { + angles[i] -= 360.0; + } + else if ( angles[i] < -180.0 ) + { + angles[i] += 360.0; + } + } +} + +void CPhy :: PushableThink( void ) +{ +// pev->velocity = pev->velocity * 0.8; +// pev->avelocity = pev->avelocity * 0.8; +// ALERT( at_debug, "Angles yaw: %3.2f\n", pev->angles.y ); + +/* if( pev->angles.x >= 360 ) + pev->angles.x -= 360; + if( pev->angles.x >= -360 ) + pev->angles.x += 360;*/ + + //if( pev->flags & FL_ONGROUND ) + //{ + + // pev->avelocity = pev->avelocity;// * 5.5; + + //} + +// SetNextThink( 0.1 ); + pev->nextthink = 0.1; +} + +void CPhy :: Touch( CBaseEntity *pOther ) +{ + Vector savedangles = Vector( 0, 0, 0 ); + int negate = 0; + TraceResult tr; + // look down directly to know the surface we're lying. + UTIL_TraceLine( pev->origin, pev->origin - Vector(0,0,64), ignore_monsters, edict(), &tr ); + + pev->velocity = pev->velocity * 0.8; +// pev->avelocity = pev->avelocity * 0.5; + + if( !(pev->flags & FL_ONGROUND) ) + { + //pev->avelocity.x = RANDOM_FLOAT( -400, 400 );// NCL: Enable it to fun =) + + //NCL: no sound, temporary. + if (pev->frags == 0) + { + // You can place sounds here... + } + } + else +/****** +This code is nice, but has a bug: +The angles of the entity it's normalized, but if you touch or move the entity, and if it have another initial +angle, the entity will move too quickly in the reverse angle. + +Look that: +1- We have a stand barrel. + + ------ + |||||| + | | + | | + | | + ------ + +2- and you hit the barrel and the barrel fall of and it's lie on the ground + + ----------- + | || + | || + ----------- + +3- Well, do a simple touch to the barrel... + + ----------- + || | + || | + ----------- + +If you have a barrel, don't make any diference, but if you have a more complex object (a monitor i suposed), +you will see the bug. + +It's too hard to explain, you need see it... +******/ + { + for( int i = 0; i<3; i++ ) + { + if( pev->angles.x < 0 ) + negate = 1; + + if( fabs(pev->angles.x) < 45 ) + savedangles.x = 0; + else if( fabs(pev->angles.x) >= 45 && fabs(pev->angles.x) <= 135 ) + savedangles.x = 90; + else if( fabs(pev->angles.x) > 135 && fabs(pev->angles.x) <= 180 ) + savedangles.x = 180; + } + + #ifndef M_PI + #define M_PI 3.1415926535897932384626433832795 //NCL: Correct It! + #endif + #define ang2rad (2 * M_PI / 360) + + if ( tr.flFraction < 1.0 ) + { + Vector forward, right, angdir, angdiry; + Vector Angles = pev->angles; + + NormalizeAngles( Angles ); + + UTIL_MakeVectorsPrivate( Angles, forward, right, NULL ); + angdir = forward; + Vector left = -right; + angdiry = left; + + pev->angles.x = -UTIL_VecToAngles( angdir - DotProduct(angdir, tr.vecPlaneNormal) * tr.vecPlaneNormal).x; + pev->angles.y = UTIL_VecToAngles( angdir - DotProduct(angdir, tr.vecPlaneNormal) * tr.vecPlaneNormal).y; + + pev->angles.z = UTIL_VecToAngles( angdiry - DotProduct(angdiry, tr.vecPlaneNormal) * tr.vecPlaneNormal).x; + + pev->angles.z = UTIL_VecToAngles( angdiry - DotProduct(angdiry, tr.vecPlaneNormal) * right ).x; + } + + #undef ang2rad + + if( negate ) + pev->angles.x -= savedangles.x; + else + pev->angles.x += savedangles.x; + } + + + if ( FClassnameIs( pOther->pev, "worldspawn" ) ) + return; + + Move( pOther, 1 ); +} + + +void CPhy :: Move( CBaseEntity *pOther, int push ) +{ + entvars_t* pevToucher = pOther->pev; + int playerTouch = 0; + + // Is entity standing on this pushable ? + if ( FBitSet(pevToucher->flags,FL_ONGROUND) && pevToucher->groundentity && VARS(pevToucher->groundentity) == pev ) + { + // Only push if floating + if ( pev->waterlevel > 0 && pev->watertype > CONTENT_FLYFIELD) + pev->velocity.z += pevToucher->velocity.z * 0.1; + + return; + } + + if ( pOther->IsPlayer() ) + { + if ( push && !(pevToucher->button & (IN_FORWARD|IN_USE)) ) // Don't push unless the player is pushing forward and NOT use (pull) + return; + playerTouch = 1; + } + + float factor; + + if ( playerTouch ) + { + if ( !(pevToucher->flags & FL_ONGROUND) ) // Don't push away from jumping/falling players unless in water + { + if ( pev->waterlevel < 1 || pev->watertype <= CONTENT_FLYFIELD) + // { + // pOther->TakeDamage(pev, pev, 30, DMG_PARALYZE | DMG_NEVERGIB); + return; + // } + else + // { + factor = 0.1; + // } + } + else + factor = 1; + } + else + factor = 0.25; + + if (!push) + factor = factor*0.1; + + pev->velocity.x += pevToucher->velocity.x * factor; + pev->velocity.y += pevToucher->velocity.y * factor; + + float length = sqrt( pev->velocity.x * pev->velocity.x + pev->velocity.y * pev->velocity.y ); + if ( push && (length > MaxSpeed()) ) + { + pev->velocity.x = (pev->velocity.x * MaxSpeed() / length ); + pev->velocity.y = (pev->velocity.y * MaxSpeed() / length ); + } + if ( playerTouch ) + { + pevToucher->velocity.x = pev->velocity.x; + pevToucher->velocity.y = pev->velocity.y; + if ( (gpGlobals->time - m_soundTime) > 0.7 ) + { + m_soundTime = gpGlobals->time; + if ( length > 0 && FBitSet(pev->flags,FL_ONGROUND) ) + { +/****** +Sys: +A big hack... +When you move the entity, it will make a sound. + +There are 6 materials type: + -No defined (no material) The entity doesn't make any sound. + -Metal (metal rough sound) + -Plaster (a plastic tube sound) + -Glass (UNDONE: glass move sound??) + -Cement (concrete sound) + -Wood (wood sound) +*******/ + if (pev->frags == 0) + { + //no sound + } + + if (pev->frags == 1) //metal + { + + } + + if (pev->frags == 2) //plaster + { + + } + + if (pev->frags == 3) { }//glass + + if (pev->frags == 4) //cemento + { + + } + + if (pev->frags == 5) //wood + { + + } + // SetThink( StopSound ); + // pev->nextthink = 0.1; + } + } + } +} + +#if 0 +void CPhy::StopSound( void ) +{ + Vector dist = pev->oldorigin - pev->origin; + if ( dist.Length() <= 0 ) + STOP_SOUND( ENT(pev), CHAN_WEAPON, m_soundNames[m_lastSound] ); +} +#endif + +void CPhy :: TestBeam( Vector org, Vector end, Vector color, int life, int width ) +{ + extern short g_sModelIndexLaser; + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS ); + WRITE_COORD( org.x ); + WRITE_COORD( org.y ); + WRITE_COORD( org.z ); + + WRITE_COORD( end.x ); + WRITE_COORD( end.y ); + WRITE_COORD( end.z ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 0 ); // framerate + + WRITE_BYTE( life ); // life + WRITE_BYTE( width ); // width + + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( color.x ); // r, g, b + WRITE_BYTE( color.y ); // r, g, b + WRITE_BYTE( color.z ); // r, g, b + WRITE_BYTE( 160 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); +} + +int CPhy :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + Vector vecDir, r, anorm, rforward, rup, rright; + float a; + float force = flDamage * 10; + TraceResult trace = UTIL_GetGlobalTrace( ); + UTIL_MakeVectors( pev->angles ); + + // grab the vector of the incoming attack. ( pretend that the inflictor is a little lower than it really is, so the body will tend to fly upward a bit). + vecDir = r = Vector( 0, 0, 0 ); + if (!FNullEnt( pevInflictor )) + { + if ( FClassnameIs( pevInflictor, "projectile" ) ) + pevInflictor = VARS( pevInflictor->owner ); + + CBaseEntity *pInflictor = CBaseEntity :: Instance( pevInflictor ); + if (pInflictor) + { + vecDir = g_vecAttackDir = ( trace.vecEndPos - pInflictor->Center() ).Normalize(); + r = ( trace.vecEndPos - Center() ).Normalize(); + } + } + + anorm = UTIL_VecToAngles( r ); + NormalizeAngles( r ); + anorm.x = -anorm.x; + UTIL_MakeVectorsPrivate( anorm, rforward, rright, rup ); + + + if (pev->frags == 1) //metal + { + //if ( bitsDamageType & (DMG_BULLET | DMG_CLUB) ) + if ( ( bitsDamageType & DMG_BULLET)|| ( bitsDamageType & DMG_CLUB) ) + { + UTIL_Ricochet( trace.vecEndPos, 0.5 ); + + switch (RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_BODY, "fisica/metal/b_impact1.wav", 0.9, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_BODY, "fisica/metal/b_impact2.wav", 0.9, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_BODY, "fisica/metal/b_impact3.wav", 0.9, ATTN_NORM); break; + } + + //int color, int count, int speed, int velocityRange//PARAMs + + // StreakSplash( trace.vecEndPos, trace.vecPlaneNormal, 6, 20, 50, 400 );//REFERENCE + // if (RANDOM_LONG( 0, 99 ) < 40) + // UTIL_Sparks( trace.vecEndPos, trace.vecPlaneNormal, 0, 5, 500, 500 );//chispas + // UTIL_Sparks( trace.vecEndPos, trace.vecPlaneNormal, 9, 5, 5, 100 );//puntos + // UTIL_Sparks( trace.vecEndPos, trace.vecPlaneNormal, 0, 5, 500, 20 );//chispas + + } + } + + if (pev->frags == 3) //glass + UTIL_Ricochet( trace.vecEndPos, 0.5 ); + + if (pev->frags == 4) //cement + UTIL_Sparks( trace.vecEndPos ); + + if (pev->frags == 5) //wood + { + switch (RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "debris/wood1.wav", 0.9, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "debris/wood2.wav", 0.9, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "debris/wood3.wav", 0.9, ATTN_NORM); break; + } + } + +// if ( bitsDamageType & DMG_BULLET ) +/******* +Sys: What the .... + +If the material is "plaster" let's do more movement. +*******/ + //if (pev->frags == 2) //plastico + //{ + force *= 0.8; + pev->avelocity.z = cos( AngleBetweenVectors( vecDir, rup ) ) * force * 1; + //} + //else// Isn't plaster + //{ + // if ( ( bitsDamageType & DMG_BULLET)|| ( bitsDamageType & DMG_CLUB) ) + // force *= 0.5; + //} + + pev->flags &= ~FL_ONGROUND; + //pev->origin.z += 1; + + pev->avelocity.x = cos( AngleBetweenVectors( vecDir, rup ) ) * 100; + pev->avelocity.y = cos( AngleBetweenVectors( vecDir, -rright ) ) * 200; + // pev->avelocity.z = cos( AngleBetweenVectors( vecDir, -rup ) ) * 200; + +// pev->avelocity.z = cos( AngleBetweenVectors( vecDir, rup ) ) * force * 2;//fooz +// pev->avelocity.z = sin( AngleBetweenVectors( vecDir, rup ) ) * force * 2;//fooz + + ALERT( at_console, "X : %3.1f %3.1f° Y: %3.1f %3.1f°\n", pev->avelocity.x, AngleBetweenVectors( vecDir, rup ), pev->avelocity.y, AngleBetweenVectors( vecDir, -rright ) ); + + pev->velocity = pev->velocity /*+ gpGlobals->v_up * force * RANDOM_FLOAT( 0, 0.5 )*/ + vecDir * force * RANDOM_FLOAT( 0.5, 1.0 ); + + pev->velocity = pev->velocity + gpGlobals->v_up * force * RANDOM_FLOAT( 0, 0.5 ) + vecDir * force * RANDOM_FLOAT( 0.5, 1.0 ); + + //if ( pev->spawnflags & SF_PUSH_BREAKABLE ) + // return CBreakable::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); + + return 1; +} + diff --git a/dlls/func_tank.cpp b/dlls/func_tank.cpp new file mode 100644 index 0000000..88bf3c5 --- /dev/null +++ b/dlls/func_tank.cpp @@ -0,0 +1,1964 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "effects.h" +#include "weapons.h" +#include "explode.h" +#include "monsters.h" +#include "movewith.h" + +#include "player.h" + + +#define SF_TANK_ACTIVE 0x0001 +//#define SF_TANK_PLAYER 0x0002 +//#define SF_TANK_HUMANS 0x0004 +//#define SF_TANK_ALIENS 0x0008 +#define SF_TANK_LINEOFSIGHT 0x0010 +#define SF_TANK_CANCONTROL 0x0020 +#define SF_TANK_LASERSPOT 0x0040 //LRC +#define SF_TANK_MATCHTARGET 0x0080 //LRC +#define SF_TANK_SOUNDON 0x8000 +#define SF_TANK_SEQFIRE 0x10000 //LRC - a TankSequence is telling me to fire + +enum TANKBULLET +{ + TANK_BULLET_NONE = 0, + TANK_BULLET_9MM = 1, + TANK_BULLET_MP5 = 2, + TANK_BULLET_12MM = 3, +}; + +class CFuncTank; +class CTankSequence; + +// declare Controls up here to stop the compiler complaining. (come back Java, all is forgiven...) +class CFuncTankControls : public CBaseEntity +{ +public: + virtual int ObjectCaps( void ); + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +// void Think( void ); + void KeyValue( KeyValueData *pkvd ); + STATE GetState(void) { return m_active?STATE_ON:STATE_OFF; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + BOOL CFuncTankControls :: OnControls( entvars_t *pevTest ); + + BOOL m_active; // am I being used to control tanks right now? + Vector m_vecControllerUsePos; // where was the player standing when he used me? + // for a 'movewith' controls entity, this is relative to the movewith ent. + CBasePlayer* m_pController; + int m_iCrosshair; //LRC - show a crosshair while in use. (currently this is just yes or no, + // but in future it will be the id of the weapon whose crosshair should be used.) +// CFuncTank *m_pTank; +}; + + +#define SF_TSEQ_DUMPPLAYER 1 +#define SF_TSEQ_REPEATABLE 2 + +#define TSEQ_UNTIL_NONE 0 +#define TSEQ_UNTIL_FACING 1 +#define TSEQ_UNTIL_DEATH 2 + +#define TSEQ_TURN_NO 0 +#define TSEQ_TURN_ANGLE 1 +#define TSEQ_TURN_FACE 2 +#define TSEQ_TURN_ENEMY 3 + +#define TSEQ_SHOOT_NO 0 +#define TSEQ_SHOOT_ONCE 1 +#define TSEQ_SHOOT_ALWAYS 2 +#define TSEQ_SHOOT_FACING 3 + +#define TSEQ_FLAG_NOCHANGE 0 +#define TSEQ_FLAG_ON 1 +#define TSEQ_FLAG_OFF 2 +#define TSEQ_FLAG_TOGGLE 3 + +class CTankSequence : public CBaseEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EndThink( void ); + void TimeOutThink( void ); + void KeyValue( KeyValueData *pkvd ); + STATE GetState( void ) { return m_pTank?STATE_ON:STATE_OFF; } + virtual int ObjectCaps( void ); + + void StopSequence( void ); + void FacingNotify( void ); + void DeadEnemyNotify( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + string_t m_iszEntity; + string_t m_iszEnemy; + int m_iUntil; + float m_fDuration; + int m_iTurn; + int m_iShoot; + int m_iActive; + int m_iControllable; + int m_iLaserSpot; + CFuncTank *m_pTank; // the sequence can only control one tank at a time, for the moment +}; + +// Custom damage +// env_laser (duration is 0.5 rate of fire) +// rockets +// explosion? + +class CFuncTank : public CBaseEntity +{ +public: + void Spawn( void ); + void PostSpawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Think( void ); + void TrackTarget( void ); + CBaseEntity* BestVisibleEnemy( void ); + int IRelationship( CBaseEntity* pTarget ); + + int Classify( void ) { return m_iTankClass; } + + void TryFire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); + virtual void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); + virtual Vector UpdateTargetPosition( CBaseEntity *pTarget ) + { + return pTarget->BodyTarget( pev->origin ); + } + + void StartRotSound( void ); + void StopRotSound( void ); + + // Bmodels don't go across transitions + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + inline BOOL IsActive( void ) { return (pev->spawnflags & SF_TANK_ACTIVE)?TRUE:FALSE; } + inline void TankActivate( void ) { pev->spawnflags |= SF_TANK_ACTIVE; SetNextThink(0.1); m_fireLast = 0; } + inline void TankDeactivate( void ) { pev->spawnflags &= ~SF_TANK_ACTIVE; m_fireLast = 0; StopRotSound(); } + inline BOOL CanFire( void ) { return (gpGlobals->time - m_lastSightTime) < m_persist; } + BOOL InRange( float range ); + + // Acquire a target. pPlayer is a player in the PVS + edict_t *FindTarget( edict_t *pPlayer ); + + void TankTrace( const Vector &vecStart, const Vector &vecForward, const Vector &vecSpread, TraceResult &tr ); + + Vector BarrelPosition( void ) + { + Vector forward, right, up; + UTIL_MakeVectorsPrivate( pev->angles, forward, right, up ); + return pev->origin + (forward * m_barrelPos.x) + (right * m_barrelPos.y) + (up * m_barrelPos.z); + } + + void AdjustAnglesForBarrel( Vector &angles, float distance ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +// BOOL OnControls( entvars_t *pevTest ); + BOOL StartControl( CBasePlayer* pController, CFuncTankControls* pControls ); + void StopControl( CFuncTankControls* pControls ); +// void ControllerPostFrame( void ); + + CFuncTankControls* m_pControls; //LRC - tankcontrols is used as a go-between. + + void StartSequence( CTankSequence *pSequence); + void StopSequence(); + + CTankSequence *m_pSequence; //LRC - if set, then this is the sequence the tank is currently performing + CBaseEntity *m_pSequenceEnemy; //LRC - the entity that our sequence wants us to attack + CLaserSpot* m_pSpot; // Laser spot entity + + //LRC - unprotected these, so that TankSequence can look at them + float m_maxRange; // Max range to aim/track + float m_fireLast; // Last time I fired + float m_fireRate; // How many rounds/second + +protected: +// CBasePlayer* m_pController; + float m_flNextAttack; +//LRC Vector m_vecControllerUsePos; + + float m_yawCenter; // "Center" yaw + float m_yawRate; // Max turn rate to track targets + float m_yawRange; // Range of turning motion (one-sided: 30 is +/- 30 degress from center) + // Zero is full rotation + float m_yawTolerance; // Tolerance angle + + float m_pitchCenter; // "Center" pitch + float m_pitchRate; // Max turn rate on pitch + float m_pitchRange; // Range of pitch motion as above + float m_pitchTolerance; // Tolerance angle + + float m_lastSightTime;// Last time I saw target + float m_persist; // Persistence of firing (how long do I shoot when I can't see) + float m_minRange; // Minimum range to aim/track + + Vector m_barrelPos; // Length of the freakin barrel + float m_spriteScale; // Scale of any sprites we shoot + int m_iszSpriteSmoke; + int m_iszSpriteFlash; + TANKBULLET m_bulletType; // Bullet type + float m_flBulletDamage; // 0 means use Bullet type's default damage + + Vector m_sightOrigin; // Last sight of target + int m_spread; // firing spread + int m_iszMaster; // Master entity + int m_iszFireMaster;//LRC - Fire-Master entity (prevents firing when inactive) + + int m_iTankClass; // Behave As + + void CFuncTank::UpdateSpot( void ); +// CLaserSpot* m_pViewTarg; // Player view indicator + + CPointEntity *m_pFireProxy; //LRC - locus position for custom shots + int m_iszLocusFire; +}; + +TYPEDESCRIPTION CFuncTank::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTank, m_yawCenter, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_yawRate, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_yawRange, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_yawTolerance, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_pitchCenter, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_pitchRate, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_pitchRange, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_pitchTolerance, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_fireLast, FIELD_TIME ), + DEFINE_FIELD( CFuncTank, m_fireRate, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_lastSightTime, FIELD_TIME ), + DEFINE_FIELD( CFuncTank, m_persist, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_minRange, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_maxRange, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_barrelPos, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTank, m_spriteScale, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_iszSpriteSmoke, FIELD_STRING ), + DEFINE_FIELD( CFuncTank, m_iszSpriteFlash, FIELD_STRING ), + DEFINE_FIELD( CFuncTank, m_bulletType, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTank, m_sightOrigin, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTank, m_spread, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTank, m_pControls, FIELD_CLASSPTR ), //LRC + DEFINE_FIELD( CFuncTank, m_pSequence, FIELD_CLASSPTR ), //LRC + DEFINE_FIELD( CFuncTank, m_pSequenceEnemy, FIELD_CLASSPTR ), //LRC + DEFINE_FIELD( CFuncTank, m_pSpot, FIELD_CLASSPTR ), //LRC +//LRC DEFINE_FIELD( CFuncTank, m_pController, FIELD_CLASSPTR ), +//LRC DEFINE_FIELD( CFuncTank, m_vecControllerUsePos, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTank, m_flNextAttack, FIELD_TIME ), + DEFINE_FIELD( CFuncTank, m_flBulletDamage, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTank, m_iszMaster, FIELD_STRING ), + DEFINE_FIELD( CFuncTank, m_iszFireMaster, FIELD_STRING ), //LRC + DEFINE_FIELD( CFuncTank, m_iszLocusFire, FIELD_STRING ), //LRC + DEFINE_FIELD( CFuncTank, m_pFireProxy, FIELD_CLASSPTR ), //LRC +}; + +IMPLEMENT_SAVERESTORE( CFuncTank, CBaseEntity ); + +static Vector gTankSpread[] = +{ + Vector( 0, 0, 0 ), // perfect + Vector( 0.025, 0.025, 0.025 ), // small cone + Vector( 0.05, 0.05, 0.05 ), // medium cone + Vector( 0.1, 0.1, 0.1 ), // large cone + Vector( 0.25, 0.25, 0.25 ), // extra-large cone +}; +#define MAX_FIRING_SPREADS ARRAYSIZE(gTankSpread) + + +void CFuncTank :: Spawn( void ) +{ + Precache(); + + pev->movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything + pev->solid = SOLID_BSP; + SET_MODEL( ENT(pev), STRING(pev->model) ); + +// if (pev->health) pev->flags |= FL_MONSTER; //LRC - maybe? + + if ( IsActive() ) + { + SetNextThink(1.0); + } + + m_sightOrigin = BarrelPosition(); // Point at the end of the barrel + + if ( m_fireRate <= 0 ) + m_fireRate = 1; + if ( m_spread > MAX_FIRING_SPREADS ) + m_spread = 0; + + pev->oldorigin = pev->origin; + + if (m_iszLocusFire) //LRC - locus trigger + { + m_pFireProxy = GetClassPtr( (CPointEntity*)NULL ); + } +} + +void CFuncTank::PostSpawn( void ) +{ + if (m_pMoveWith) + { + m_yawCenter = pev->angles.y - m_pMoveWith->pev->angles.y; + m_pitchCenter = pev->angles.x - m_pMoveWith->pev->angles.x; + } + else + { + m_yawCenter = pev->angles.y; + m_pitchCenter = pev->angles.x; + } +} + +void CFuncTank :: Precache( void ) +{ +// PRECACHE_MODEL( "sprites/mommablob.spr" ); + if ( m_iszSpriteSmoke ) + PRECACHE_MODEL( (char *)STRING(m_iszSpriteSmoke) ); + if ( m_iszSpriteFlash ) + PRECACHE_MODEL( (char *)STRING(m_iszSpriteFlash) ); + + if ( pev->noise ) + PRECACHE_SOUND( (char *)STRING(pev->noise) ); +} + + +void CFuncTank :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "yawrate")) + { + m_yawRate = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "yawrange")) + { + m_yawRange = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "yawtolerance")) + { + m_yawTolerance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitchrange")) + { + m_pitchRange = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitchrate")) + { + m_pitchRate = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitchtolerance")) + { + m_pitchTolerance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "firerate")) + { + m_fireRate = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "barrel")) + { + m_barrelPos.x = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "barrely")) + { + m_barrelPos.y = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "barrelz")) + { + m_barrelPos.z = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spritescale")) + { + m_spriteScale = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spritesmoke")) + { + m_iszSpriteSmoke = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "spriteflash")) + { + m_iszSpriteFlash = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "rotatesound")) + { + pev->noise = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "persistence")) + { + m_persist = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "bullet")) + { + m_bulletType = (TANKBULLET)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "bullet_damage" )) + { + m_flBulletDamage = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "firespread")) + { + m_spread = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "minRange")) + { + m_minRange = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "maxRange")) + { + m_maxRange = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "master")) + { + m_iszMaster = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "firemaster")) + { + m_iszFireMaster = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iClass")) + { + m_iTankClass = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszLocusFire")) + { + m_iszLocusFire = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +//================================================================================== +// TANK CONTROLLING +/*LRC- TankControls checks this instead +BOOL CFuncTank :: OnControls( entvars_t *pevTest ) +{ + if ( !(pev->spawnflags & SF_TANK_CANCONTROL) ) + return FALSE; + + Vector offset = pevTest->origin - pev->origin; + + if ( (m_vecControllerUsePos - pevTest->origin).Length() < 30 ) + return TRUE; + + return FALSE; +} */ + +BOOL CFuncTank :: StartControl( CBasePlayer* pController, CFuncTankControls *pControls ) +{ +// ALERT(at_console, "StartControl\n"); + // we're already being controlled or playing a sequence + if ( m_pControls != NULL || m_pSequence != NULL ) + { +// ALERT(at_debug,"StartControl failed, already in use\n"); + return FALSE; + } + + // Team only or disabled? + if ( m_iszMaster ) + { + if ( !UTIL_IsMasterTriggered( m_iszMaster, pController ) ) + { +// ALERT(at_debug,"StartControl failed, locked\n"); + return FALSE; + } + } + +// ALERT( at_console, "using TANK!\n"); + + m_pControls = pControls; + + if (m_pSpot) m_pSpot->Revive(); +// if (m_pViewTarg) m_pViewTarg->Revive(); + + SetNextThink(0.1); +// ALERT(at_debug,"StartControl succeeded\n"); + return TRUE; +} + +void CFuncTank :: StopControl( CFuncTankControls* pControls) +{ +//LRC- various commands moved from here to FuncTankControls + if ( !m_pControls || m_pControls != pControls) + { + //ALERT(at_debug,"StopControl failed, not in use\n"); + return; + } + +// ALERT(at_debug,"StopControl succeeded\n"); + +// ALERT( at_debug, "stopped using TANK\n"); + + if (m_pSpot) m_pSpot->Suspend(-1); +// if (m_pViewTarg) m_pViewTarg->Suspend(-1); + StopRotSound(); //LRC + + DontThink(); + UTIL_SetAvelocity(this, g_vecZero); + m_pControls = NULL; + + if ( IsActive() ) + { + SetNextThink(1.0); + } +} + +void CFuncTank::UpdateSpot( void ) +{ + if ( pev->spawnflags & SF_TANK_LASERSPOT ) + { + if (!m_pSpot) + { + m_pSpot = CLaserSpot::CreateSpot(); + } + + Vector vecAiming; + UTIL_MakeVectorsPrivate( pev->angles, vecAiming, NULL, NULL ); + Vector vecSrc = BarrelPosition( ); + + TraceResult tr; + UTIL_TraceLine ( vecSrc, vecSrc + vecAiming * 8192, dont_ignore_monsters, ENT(pev), &tr ); + + // ALERT( "%f %f\n", gpGlobals->v_forward.y, vecAiming.y ); + + /* + float a = gpGlobals->v_forward.y * vecAiming.y + gpGlobals->v_forward.x * vecAiming.x; + m_pPlayer->pev->punchangle.y = acos( a ) * (180 / M_PI); + + ALERT( at_console, "%f\n", a ); + */ + + UTIL_SetOrigin( m_pSpot, tr.vecEndPos ); + } +} + +// Called each frame by PostThink, via Use. +// all we do here is handle firing. +// LRC- this is now never called. Think functions are handling it all. +/*void CFuncTank :: ControllerPostFrame( void ) +{ + ASSERT(m_pController != NULL); + + if ( gpGlobals->time < m_flNextAttack ) + return; + + if ( m_pController->pev->button & IN_ATTACK ) + { + Vector vecForward; + UTIL_MakeVectorsPrivate( pev->angles, vecForward, NULL, NULL ); + + m_fireLast = gpGlobals->time - (1/m_fireRate) - 0.01; // to make sure the gun doesn't fire too many bullets + + Fire( BarrelPosition(), vecForward, m_pController->pev ); + + // HACKHACK -- make some noise (that the AI can hear) + if ( m_pController && m_pController->IsPlayer() ) + ((CBasePlayer *)m_pController)->m_iWeaponVolume = LOUD_GUN_VOLUME; + + m_flNextAttack = gpGlobals->time + (1/m_fireRate); + } +}*/ +////////////// END NEW STUFF ////////////// + + +void CFuncTank :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( pev->spawnflags & SF_TANK_CANCONTROL ) + { // player controlled turret + + if ( pActivator->Classify() != CLASS_PLAYER ) + return; + + // from Player::PostThink. ("try fire the gun") + if ( value == 2 && useType == USE_SET ) + { +// LRC- actually, we handle firing with TrackTarget, to support multitank. +// ControllerPostFrame(); + } + +// LRC- tankcontrols handles all this +// else if ( !m_pController && useType != USE_OFF ) +// { +// // LRC- add one more tank to the ones the player's using +// ((CBasePlayer*)pActivator)->m_pTank = this; +// StartControl( (CBasePlayer*)pActivator ); +// } +// else +// { +// // probably from Player::PostThink- player stopped using tank. +// StopControl(); +// } + } + else + { + if ( !ShouldToggle( useType, IsActive() ) ) + return; + + if ( IsActive() ) + { + TankDeactivate(); + if (m_pSpot) m_pSpot->Suspend(-1); + } + else + { + TankActivate(); + if (m_pSpot) m_pSpot->Revive(); + } + } +} + + +edict_t *CFuncTank :: FindTarget( edict_t *pPlayer ) +{ + return pPlayer; +} + +CBaseEntity *CFuncTank:: BestVisibleEnemy ( void ) +{ + CBaseEntity *pReturn; + int iNearest; + int iDist; + int iBestRelationship; + int iLookDist = m_maxRange?m_maxRange:512; //thanks to Waldo for this. + + iNearest = 8192;// so first visible entity will become the closest. + pReturn = NULL; + iBestRelationship = R_DL; + + CBaseEntity *pList[100]; + + Vector delta = Vector( iLookDist, iLookDist, iLookDist ); + + // Find only monsters/clients in box, NOT limited to PVS + int count = UTIL_EntitiesInBox( pList, 100, pev->origin - delta, pev->origin + delta, FL_CLIENT|FL_MONSTER ); + int i; + + for (i = 0; i < count; i++ ) + { + if ( pList[i]->IsAlive() ) + { + if ( IRelationship( pList[i] ) > iBestRelationship ) + { + // this entity is disliked MORE than the entity that we + // currently think is the best visible enemy. No need to do + // a distance check, just get mad at this one for now. + iBestRelationship = IRelationship ( pList[i] ); + iNearest = ( pList[i]->pev->origin - pev->origin ).Length(); + pReturn = pList[i]; + } + else if ( IRelationship( pList[i] ) == iBestRelationship ) + { + // this entity is disliked just as much as the entity that + // we currently think is the best visible enemy, so we only + // get mad at it if it is closer. + iDist = ( pList[i]->pev->origin - pev->origin ).Length(); + + if ( iDist <= iNearest ) + { + iNearest = iDist; + //these are guaranteed to be the same! iBestRelationship = IRelationship ( pList[i] ); + pReturn = pList[i]; + } + } + } + } + +// if (pReturn) +// ALERT(at_debug, "Tank's best enemy is %s\n", STRING(pReturn->pev->classname)); +// else +// ALERT(at_debug, "Tank has no best enemy\n"); + return pReturn; +} + + +int CFuncTank::IRelationship( CBaseEntity* pTarget ) +{ + int iOtherClass = pTarget->Classify(); + if (iOtherClass == CLASS_NONE) return R_NO; + + if (!m_iTankClass) + { + if (iOtherClass == CLASS_PLAYER) + return R_HT; + else + return R_NO; + } + else if (m_iTankClass == CLASS_PLAYER_ALLY) + { + switch (iOtherClass) + { + case CLASS_HUMAN_MILITARY: + case CLASS_MACHINE: + case CLASS_ALIEN_MILITARY: + case CLASS_ALIEN_MONSTER: + case CLASS_ALIEN_PREDATOR: + case CLASS_ALIEN_PREY: + return R_HT; + default: + return R_NO; + } + } + else if (m_iTankClass == CLASS_HUMAN_MILITARY) + { + switch (iOtherClass) + { + case CLASS_PLAYER: + case CLASS_PLAYER_ALLY: + case CLASS_ALIEN_MILITARY: + case CLASS_ALIEN_MONSTER: + case CLASS_ALIEN_PREDATOR: + case CLASS_ALIEN_PREY: + return R_HT; + case CLASS_HUMAN_PASSIVE: + return R_DL; + default: + return R_NO; + } + } + else if (m_iTankClass == CLASS_ALIEN_MILITARY) + { + switch (iOtherClass) + { + case CLASS_PLAYER: + case CLASS_PLAYER_ALLY: + case CLASS_HUMAN_MILITARY: + return R_HT; + case CLASS_HUMAN_PASSIVE: + return R_DL; + default: + return R_NO; + } + } + else + return R_NO; +} + + +BOOL CFuncTank :: InRange( float range ) +{ + if ( range < m_minRange ) + return FALSE; + if ( m_maxRange > 0 && range > m_maxRange ) + return FALSE; + + return TRUE; +} + +//LRC +void CFuncTank :: StartSequence(CTankSequence *pSequence) +{ + m_pSequence = pSequence; + SetNextThink(1.0); +} + +//LRC +void CFuncTank :: StopSequence( ) +{ + StopRotSound(); + DontThink(); + pev->avelocity = g_vecZero; + m_pSequence = NULL; + m_pSequenceEnemy = NULL; +} + +// NB: tracktarget updates nextthink +void CFuncTank :: Think( void ) +{ +// pev->avelocity = g_vecZero; + TrackTarget(); + + if ( fabs(pev->avelocity.x) > 1 || fabs(pev->avelocity.y) > 1 ) + StartRotSound(); + else + StopRotSound(); +} + +void CFuncTank::TrackTarget( void ) +{ + TraceResult tr; +// edict_t *pPlayer; + BOOL updateTime = FALSE, lineOfSight; + Vector angles, direction, targetPosition, barrelEnd; + Vector v_right, v_up; + CBaseEntity *pTarget; + CBasePlayer* pController = NULL; + +// ALERT(at_console,"TrackTarget\n"); + + // update the barrel position + if (m_pFireProxy) + { + m_pFireProxy->pev->origin = BarrelPosition(); + UTIL_MakeVectorsPrivate( pev->angles, m_pFireProxy->pev->velocity, NULL, NULL ); + } + + // Get a position to aim for + if (m_pSequence) + { + UpdateSpot(); + SetNextThink(0.05, FALSE); + + if (m_pSequence->m_iTurn == TSEQ_TURN_ENEMY) + { + CBaseMonster *pMonst = m_pSequenceEnemy->MyMonsterPointer(); + if (pMonst && !pMonst->IsAlive()) + m_pSequence->DeadEnemyNotify(); + + // Work out what angle we need to face to look at the enemy + targetPosition = m_pSequenceEnemy->pev->origin + m_pSequenceEnemy->pev->view_ofs; + direction = targetPosition - pev->origin; + angles = UTIL_VecToAngles( direction ); + AdjustAnglesForBarrel( angles, direction.Length() ); + } + else if (m_pSequence->m_iTurn == TSEQ_TURN_ANGLE) + { + angles = m_pSequence->pev->angles; + } + else if (m_pSequence->m_iTurn == TSEQ_TURN_FACE) + { + // Work out what angle we need to face to look at the sequence + direction = m_pSequence->pev->origin - pev->origin; + angles = UTIL_VecToAngles( direction ); + AdjustAnglesForBarrel( angles, direction.Length() ); + } + } + else if (m_pControls && m_pControls->m_pController) + { +// ALERT( at_console, "TANK has controller\n"); + UpdateSpot(); + pController = m_pControls->m_pController; + SetNextThink(0.05, FALSE); + + // LRC- changed here to allow "match target" as well as "match angles" mode. + if (pev->spawnflags & SF_TANK_MATCHTARGET) + { + // "Match target" mode: + // first, get the player's angles + angles = pController->pev->v_angle; + // Work out what point the player is looking at + UTIL_MakeVectorsPrivate(angles, direction,NULL,NULL); + + targetPosition = pController->EyePosition() + direction * 1000; + + edict_t *ownerTemp = pev->owner; //LRC store the owner, so we can put it back after the check + pev->owner = pController->edict(); //LRC when doing the matchtarget check, don't hit the player or the tank. + + UTIL_TraceLine( + pController->EyePosition(), + targetPosition, + missile, //the opposite of ignore_monsters: target them if we go anywhere near! + ignore_glass, + edict(), &tr + ); + + pev->owner = ownerTemp; //LRC put the owner back + +// if (!m_pViewTarg) +// { +// m_pViewTarg = CLaserSpot::CreateSpot("sprites/mommablob.spr"); +// } +// UTIL_SetOrigin( m_pViewTarg, tr.vecEndPos ); + + // Work out what angle we need to face to look at that point + direction = tr.vecEndPos - pev->origin; + angles = UTIL_VecToAngles( direction ); + targetPosition = tr.vecEndPos; + +// ALERT( at_console, "TANK: look at pos %.0f %.0f %.0f; target angle %.0f %.0f %.0f\n", +// targetPosition.x,targetPosition.y,targetPosition.z,angles.x,angles.y,angles.z); + + // Calculate the additional rotation to point the end of the barrel at the target + // (instead of the gun's center) + AdjustAnglesForBarrel( angles, direction.Length() ); + } + else + { + // "Match angles" mode + // just get the player's angles + angles = pController->pev->v_angle; + angles[0] = 0 - angles[0]; + } + } + else + { +// ALERT( at_console, "TANK has no controller\n"); + if ( IsActive() ) + { + SetNextThink(0.1); + } + else + { + DontThink(); + UTIL_SetAvelocity(this, g_vecZero); + return; + } + + UpdateSpot(); + + // if we can't see any players + //pPlayer = FIND_CLIENT_IN_PVS( edict() ); + pTarget = BestVisibleEnemy(); + if ( FNullEnt( pTarget ) ) + { + if ( IsActive() ) + SetNextThink(2); // No enemies visible, wait 2 secs + return; + } + + // Calculate angle needed to aim at target + barrelEnd = BarrelPosition(); + targetPosition = pTarget->pev->origin + pTarget->pev->view_ofs; + float range = (targetPosition - barrelEnd).Length(); + + if ( !InRange( range ) ) + return; + + UTIL_TraceLine( barrelEnd, targetPosition, dont_ignore_monsters, edict(), &tr ); + + if ( tr.flFraction == 1.0 || tr.pHit == ENT(pTarget->pev) ) + { + lineOfSight = TRUE; + + if ( InRange( range ) && pTarget->IsAlive() ) + { + updateTime = TRUE; // I think I saw him, pa! + m_sightOrigin = UpdateTargetPosition( pTarget ); + } + } + else + { + // No line of sight, don't track + lineOfSight = FALSE; + } + + // Track sight origin + +// !!! I'm not sure what i changed (cuh, these Valve cowboys... --LRC) +// m_sightOrigin is the last known location of the player. + + direction = m_sightOrigin - pev->origin; +// direction = m_sightOrigin - barrelEnd; + + angles = UTIL_VecToAngles( direction ); + + // Calculate the additional rotation to point the end of the barrel at the target + // (not the gun's center) + AdjustAnglesForBarrel( angles, direction.Length() ); + } + + angles.x = -angles.x; + + float currentYawCenter, currentPitchCenter; + + // Force the angles to be relative to the center position + if (m_pMoveWith) + { + currentYawCenter = m_yawCenter + m_pMoveWith->pev->angles.y; + currentPitchCenter = m_pitchCenter + m_pMoveWith->pev->angles.x; + } + else + { + currentYawCenter = m_yawCenter; + currentPitchCenter = m_pitchCenter; + } + + angles.y = currentYawCenter + UTIL_AngleDistance( angles.y, currentYawCenter ); + angles.x = currentPitchCenter + UTIL_AngleDistance( angles.x, currentPitchCenter ); + + // Limit against range in y + if (m_yawRange < 360) + { + if ( angles.y > currentYawCenter + m_yawRange ) + { + angles.y = currentYawCenter + m_yawRange; + updateTime = FALSE; // If player is outside fire arc, we didn't really see him + } + else if ( angles.y < (currentYawCenter - m_yawRange) ) + { + angles.y = (currentYawCenter - m_yawRange); + updateTime = FALSE; // If player is outside fire arc, we didn't really see him + } + } + // we can always 'see' the whole vertical arc, so it's just the yaw we needed to check. + + if ( updateTime ) + m_lastSightTime = gpGlobals->time; + + // Move toward target at rate or less + float distY = UTIL_AngleDistance( angles.y, pev->angles.y ); +// ALERT(at_console, "%f -> %f: dist= %f\n", angles.y, pev->angles.y, distY); + Vector setAVel = g_vecZero; + + setAVel.y = distY * 10; + if ( setAVel.y > m_yawRate ) + setAVel.y = m_yawRate; + else if ( setAVel.y < -m_yawRate ) + setAVel.y = -m_yawRate; + + // Limit against range in x + if ( angles.x > currentPitchCenter + m_pitchRange ) + angles.x = currentPitchCenter + m_pitchRange; + else if ( angles.x < currentPitchCenter - m_pitchRange ) + angles.x = currentPitchCenter - m_pitchRange; + + // Move toward target at rate or less + float distX = UTIL_AngleDistance( angles.x, pev->angles.x ); + setAVel.x = distX * 10; + + if ( setAVel.x > m_pitchRate ) + setAVel.x = m_pitchRate; + else if ( setAVel.x < -m_pitchRate ) + setAVel.x = -m_pitchRate; + + UTIL_SetAvelocity(this, setAVel); + + // notify the TankSequence if we're (pretty close to) facing the target + if (m_pSequence && abs(distY) < 0.1 && abs(distX) < 0.1) + m_pSequence->FacingNotify(); + + // firing in tanksequences: + if ( m_pSequence ) + { + if ( gpGlobals->time < m_flNextAttack ) return; + + if ( pev->spawnflags & SF_TANK_SEQFIRE ) // does the sequence want me to fire? + { + Vector forward; + UTIL_MakeVectorsPrivate( pev->angles, forward, NULL, NULL ); + + // to make sure the gun doesn't fire too many bullets + m_fireLast = gpGlobals->time - (1/m_fireRate) - 0.01; + + TryFire( BarrelPosition(), forward, pev ); + + m_flNextAttack = gpGlobals->time + (1/m_fireRate); + } + return; + } + // firing with player-controlled tanks: + else if ( pController ) + { + if ( gpGlobals->time < m_flNextAttack ) + return; + + // FIXME- use m_???Tolerance to fire in the desired direction, + // instead of the one we're facing. + + if ( pController->pev->button & IN_ATTACK ) + { + Vector forward; + UTIL_MakeVectorsPrivate( pev->angles, forward, NULL, NULL ); + + // to make sure the gun doesn't fire too many bullets + m_fireLast = gpGlobals->time - (1/m_fireRate) - 0.01; + + TryFire( BarrelPosition(), forward, pController->pev ); + + // HACKHACK -- make some noise (that the AI can hear) + if ( pController && pController->IsPlayer() ) + ((CBasePlayer *)pController)->m_iWeaponVolume = LOUD_GUN_VOLUME; + + m_flNextAttack = gpGlobals->time + (1/m_fireRate); + } + } + // firing with automatic guns: + else if ( CanFire() && ( (fabs(distX) < m_pitchTolerance && fabs(distY) < m_yawTolerance) || (pev->spawnflags & SF_TANK_LINEOFSIGHT) ) ) + { + BOOL fire = FALSE; + Vector forward; + UTIL_MakeVectorsPrivate( pev->angles, forward, NULL, NULL ); + + if ( pev->spawnflags & SF_TANK_LINEOFSIGHT ) + { + float length = direction.Length(); + UTIL_TraceLine( barrelEnd, barrelEnd + forward * length, dont_ignore_monsters, edict(), &tr ); + if ( tr.pHit == ENT(pTarget->pev) ) + fire = TRUE; + } + else + fire = TRUE; + + if ( fire ) + { + TryFire( BarrelPosition(), forward, pev ); + } + else + m_fireLast = 0; + } + else + m_fireLast = 0; +} + + +// If barrel is offset, add in additional rotation +void CFuncTank::AdjustAnglesForBarrel( Vector &angles, float distance ) +{ + float r2, d2; + + + if ( m_barrelPos.y != 0 || m_barrelPos.z != 0 ) + { + distance -= m_barrelPos.z; + d2 = distance * distance; + if ( m_barrelPos.y ) + { + r2 = m_barrelPos.y * m_barrelPos.y; + angles.y += (180.0 / M_PI) * atan2( m_barrelPos.y, sqrt( d2 - r2 ) ); + } + if ( m_barrelPos.z ) + { + r2 = m_barrelPos.z * m_barrelPos.z; + angles.x += (180.0 / M_PI) * atan2( -m_barrelPos.z, sqrt( d2 - r2 ) ); + } + } +} + +// Check the FireMaster before actually firing +void CFuncTank::TryFire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ +// ALERT(at_console, "TryFire\n"); + if (UTIL_IsMasterTriggered(m_iszFireMaster, NULL)) + { +// ALERT(at_console, "Fire is %p, rocketfire %p, tankfire %p\n", this->Fire, CFuncTankRocket::Fire, CFuncTank::Fire); + Fire( barrelEnd, forward, pevAttacker ); + } +// else +// m_fireLast = 0; +} + +// Fire targets and spawn sprites +void CFuncTank::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ +// ALERT(at_console, "FuncTank::Fire\n"); + if ( m_fireLast != 0 ) + { + if ( m_iszSpriteSmoke ) + { + CSprite *pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteSmoke), barrelEnd, TRUE ); + pSprite->AnimateAndDie( RANDOM_FLOAT( 15.0, 20.0 ) ); + pSprite->SetTransparency( kRenderTransAlpha, pev->rendercolor.x, pev->rendercolor.y, pev->rendercolor.z, 255, kRenderFxNone ); + pSprite->pev->velocity.z = RANDOM_FLOAT(40, 80); + pSprite->SetScale( m_spriteScale ); + } + if ( m_iszSpriteFlash ) + { + CSprite *pSprite = CSprite::SpriteCreate( STRING(m_iszSpriteFlash), barrelEnd, TRUE ); + pSprite->AnimateAndDie( 60 ); + pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation ); + pSprite->SetScale( m_spriteScale ); + + // Hack Hack, make it stick around for at least 100 ms. + pSprite->AbsoluteNextThink( pSprite->m_fNextThink + 0.1 ); + } + + //LRC + if (m_iszLocusFire) + { + FireTargets( STRING(m_iszLocusFire), m_pFireProxy, this, USE_TOGGLE, 0 ); + } + + SUB_UseTargets( this, USE_TOGGLE, 0 ); + } + m_fireLast = gpGlobals->time; +} + + +void CFuncTank::TankTrace( const Vector &vecStart, const Vector &vecForward, const Vector &vecSpread, TraceResult &tr ) +{ + // get circular gaussian spread + float x, y, z; + do { + x = RANDOM_FLOAT(-0.5,0.5) + RANDOM_FLOAT(-0.5,0.5); + y = RANDOM_FLOAT(-0.5,0.5) + RANDOM_FLOAT(-0.5,0.5); + z = x*x+y*y; + } while (z > 1); + Vector vecDir = vecForward + + x * vecSpread.x * gpGlobals->v_right + + y * vecSpread.y * gpGlobals->v_up; + Vector vecEnd; + + vecEnd = vecStart + vecDir * 4096; + UTIL_TraceLine( vecStart, vecEnd, dont_ignore_monsters, edict(), &tr ); +} + + +void CFuncTank::StartRotSound( void ) +{ + if ( !pev->noise || (pev->spawnflags & SF_TANK_SOUNDON) ) + return; + pev->spawnflags |= SF_TANK_SOUNDON; + EMIT_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noise), 0.85, ATTN_NORM); +} + + +void CFuncTank::StopRotSound( void ) +{ + if ( pev->spawnflags & SF_TANK_SOUNDON ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noise) ); + pev->spawnflags &= ~SF_TANK_SOUNDON; +} + +class CFuncTankGun : public CFuncTank +{ +public: + void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); +}; +LINK_ENTITY_TO_CLASS( func_tank, CFuncTankGun ); + +void CFuncTankGun::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ +// ALERT(at_console, "FuncTankGun::Fire\n"); + int i; + + if ( m_fireLast != 0 ) + { + // FireBullets needs gpGlobals->v_up, etc. + UTIL_MakeAimVectors(pev->angles); + + int bulletCount = (gpGlobals->time - m_fireLast) * m_fireRate; + if ( bulletCount > 0 ) + { + for ( i = 0; i < bulletCount; i++ ) + { + switch( m_bulletType ) + { + case TANK_BULLET_9MM: + if( !m_flBulletDamage ) m_flBulletDamage = gSkillData.monDmg9MM; + FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], 4096.0f, BULLET_NORMAL, m_flBulletDamage, pevAttacker ); + break; + case TANK_BULLET_MP5: + if( !m_flBulletDamage ) m_flBulletDamage = gSkillData.monDmgMP5; + FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], 4096.0f, BULLET_NORMAL, m_flBulletDamage, pevAttacker ); + break; + case TANK_BULLET_12MM: + if( !m_flBulletDamage ) m_flBulletDamage = gSkillData.monDmg12MM; + FireBullets( 1, barrelEnd, forward, gTankSpread[m_spread], 4096.0f, BULLET_NORMAL, m_flBulletDamage, pevAttacker ); + break; + case TANK_BULLET_NONE: + default: break; + } + } + CFuncTank::Fire( barrelEnd, forward, pevAttacker ); + } + } + else CFuncTank::Fire( barrelEnd, forward, pevAttacker ); +} + +class CFuncTankLaser : public CFuncTank +{ +public: + void Activate( void ); + void KeyValue( KeyValueData *pkvd ); + void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); + void Think( void ); + CLaser *GetLaser( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + CLaser *m_pLaser; + float m_laserTime; +}; +LINK_ENTITY_TO_CLASS( func_tanklaser, CFuncTankLaser ); + +TYPEDESCRIPTION CFuncTankLaser::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTankLaser, m_pLaser, FIELD_CLASSPTR ), + DEFINE_FIELD( CFuncTankLaser, m_laserTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTankLaser, CFuncTank ); + +void CFuncTankLaser::Activate( void ) +{ + if ( !GetLaser() ) + { + UTIL_Remove(this); + ALERT( at_error, "Laser tank with no env_laser!\n" ); + } + else + { + m_pLaser->TurnOff(); + } + CFuncTank::Activate(); +} + + +void CFuncTankLaser::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "laserentity")) + { + pev->message = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CFuncTank::KeyValue( pkvd ); +} + + +CLaser *CFuncTankLaser::GetLaser( void ) +{ + if ( m_pLaser ) + return m_pLaser; + + CBaseEntity *pEntity; + + pEntity = UTIL_FindEntityByTargetname( NULL, STRING(pev->message) ); + while ( pEntity ) + { + // Found the laser + if ( FClassnameIs( pEntity->pev, "env_laser" ) ) + { + m_pLaser = (CLaser *)pEntity; + break; + } + else + pEntity = UTIL_FindEntityByTargetname( pEntity, STRING(pev->message) ); + } + + return m_pLaser; +} + + +void CFuncTankLaser::Think( void ) +{ + if ( m_pLaser && (gpGlobals->time > m_laserTime) ) + m_pLaser->TurnOff(); + + CFuncTank::Think(); +} + + +void CFuncTankLaser::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ +// ALERT(at_console, "FuncTankLaser::Fire\n"); + int i; + TraceResult tr; + + if ( m_fireLast != 0 && GetLaser() ) + { + // TankTrace needs gpGlobals->v_up, etc. + UTIL_MakeAimVectors(pev->angles); + + int bulletCount = (gpGlobals->time - m_fireLast) * m_fireRate; + if ( bulletCount ) + { + for ( i = 0; i < bulletCount; i++ ) + { + m_pLaser->pev->origin = barrelEnd; + TankTrace( barrelEnd, forward, gTankSpread[m_spread], tr ); + + m_laserTime = gpGlobals->time; + m_pLaser->TurnOn(); + m_pLaser->pev->dmgtime = gpGlobals->time - 1.0; + m_pLaser->FireAtPoint( barrelEnd, tr ); + + //LRC - tripbeams + CBaseEntity* pTrip; + if (!FStringNull(m_pLaser->pev->target) && (pTrip = m_pLaser->GetTripEntity( &tr )) != NULL) + FireTargets(STRING(m_pLaser->pev->target), pTrip, m_pLaser, USE_TOGGLE, 0); + + m_pLaser->DontThink(); + } + CFuncTank::Fire( barrelEnd, forward, pev ); + } + } + else + { + CFuncTank::Fire( barrelEnd, forward, pev ); + } +} + +class CFuncTankRocket : public CFuncTank +{ +public: + void Precache( void ); + virtual void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); +}; +LINK_ENTITY_TO_CLASS( func_tankrocket, CFuncTankRocket ); + +void CFuncTankRocket::Precache( void ) +{ + UTIL_PrecacheOther( "rpg_rocket" ); + CFuncTank::Precache(); +} + + + +void CFuncTankRocket::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ +// ALERT(at_console, "FuncTankRocket::Fire\n"); + + int i; + + if ( m_fireLast != 0 ) + { + int bulletCount = (gpGlobals->time - m_fireLast) * m_fireRate; + if ( bulletCount > 0 ) + { + for ( i = 0; i < bulletCount; i++ ) + { + CBaseEntity *pRocket = CBaseEntity::Create( "rpg_rocket", barrelEnd, pev->angles, edict() ); + } + CFuncTank::Fire( barrelEnd, forward, pev ); + } + } + else + CFuncTank::Fire( barrelEnd, forward, pev ); +} + + +class CFuncTankMortar : public CFuncTank +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ); +}; +LINK_ENTITY_TO_CLASS( func_tankmortar, CFuncTankMortar ); + + +void CFuncTankMortar::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "iMagnitude")) + { + pev->impulse = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CFuncTank::KeyValue( pkvd ); +} + + +void CFuncTankMortar::Fire( const Vector &barrelEnd, const Vector &forward, entvars_t *pevAttacker ) +{ +// ALERT(at_console, "FuncTankMortar::Fire\n"); + if ( m_fireLast != 0 ) + { + int bulletCount = (gpGlobals->time - m_fireLast) * m_fireRate; + // Only create 1 explosion + if ( bulletCount > 0 ) + { + TraceResult tr; + + // TankTrace needs gpGlobals->v_up, etc. + UTIL_MakeAimVectors(pev->angles); + + TankTrace( barrelEnd, forward, gTankSpread[m_spread], tr ); + + ExplosionCreate( tr.vecEndPos, pev->angles, edict(), pev->impulse, TRUE ); + + CFuncTank::Fire( barrelEnd, forward, pev ); + } + } + else + CFuncTank::Fire( barrelEnd, forward, pev ); +} + + + +//============================================================================ +// FUNC TANK CONTROLS +//============================================================================ +#define SF_TANKCONTROLS_NO_USE 1 +#define SF_TANKCONTROLS_VISIBLE 2 + +LINK_ENTITY_TO_CLASS( func_tankcontrols, CFuncTankControls ); + +TYPEDESCRIPTION CFuncTankControls::m_SaveData[] = +{ +// DEFINE_FIELD( CFuncTankControls, m_pTank, FIELD_CLASSPTR ), + DEFINE_FIELD( CFuncTankControls, m_active, FIELD_BOOLEAN ), + DEFINE_FIELD( CFuncTankControls, m_pController, FIELD_CLASSPTR ), + DEFINE_FIELD( CFuncTankControls, m_vecControllerUsePos, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTankControls, m_iCrosshair, FIELD_INTEGER ), //LRC +}; + +IMPLEMENT_SAVERESTORE( CFuncTankControls, CBaseEntity ); + + +void CFuncTankControls :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "crosshair")) + { + m_iCrosshair = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +int CFuncTankControls :: ObjectCaps( void ) +{ + if (pev->spawnflags & SF_TANKCONTROLS_NO_USE) + return (CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION); + else + return (CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_IMPULSE_USE; +} + +//LRC- copied here from FuncTank. +BOOL CFuncTankControls :: OnControls( entvars_t *pevTest ) +{ +// if ( !(pev->spawnflags & SF_TANK_CANCONTROL) ) +// return FALSE; + + Vector offset = pevTest->origin - pev->origin; + + if (pev->frags == -1) + { +// ALERT(at_console, "TANK OnControls: TRUE(full tolerance)\n"); + return TRUE; + } + + if (m_pMoveWith) + { + if ( ((m_vecControllerUsePos + m_pMoveWith->pev->origin) - pevTest->origin).Length() <= pev->frags ) + { +// ALERT(at_console, "TANK OnControls: TRUE(movewith)\n"); + return TRUE; + } + } + else if ( (m_vecControllerUsePos - pevTest->origin).Length() <= pev->frags ) + { +// ALERT(at_console, "TANK OnControls: TRUE\n"); + return TRUE; + } + +// ALERT(at_console, "TANK OnControls: FALSE\n"); + + return FALSE; +} + +void CFuncTankControls :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ +// LRC- rewritten to allow TankControls to be the thing that handles the relationship +// between the player and one or more faithful tanks. + CBaseEntity *tryTank = NULL; + +// ALERT(at_console,"controls %p triggered by \"%s\" %p\n", this, STRING(pCaller->pev->classname), pCaller); + + if ( !m_pController && useType != USE_OFF ) + { + // if not activated by a player, don't work. + if (!pActivator || !(pActivator->IsPlayer())) + return; + // if I've already got a controller, or the player's already using + // another controls, then forget it. + if (m_active != FALSE || ((CBasePlayer*)pActivator)->m_pTank != NULL) + return; + + //LRC- Now uses FindEntityByTargetname, so that aliases work. + while (tryTank = UTIL_FindEntityByTargetname(tryTank, STRING(pev->target))) + { + if (!strncmp( STRING(tryTank->pev->classname), "func_tank", 9 )) + { + if (((CFuncTank*)tryTank)->StartControl((CBasePlayer*)pActivator, this)) + { + //ALERT(at_console,"started controlling tank %s\n",STRING(tryTank->pev->targetname)); + // here's a tank we can control. Phew. + m_active = TRUE; + } + } + } + if (m_active) + { + // we found at least one tank to use, so holster player's weapon + m_pController = (CBasePlayer*)pActivator; + m_pController->m_pTank = this; + if ( m_pController->m_pActiveItem ) + { + m_pController->m_pActiveItem->Holster( true ); + m_pController->pev->weaponmodel = 0; + m_pController->pev->viewmodel = 0; + } + + m_pController->m_iHideHUD |= HIDEHUD_WEAPONS; + + // remember where the player's standing, so we can tell when he walks away + if (m_pMoveWith) + m_vecControllerUsePos = m_pController->pev->origin - m_pMoveWith->pev->origin; + else + m_vecControllerUsePos = m_pController->pev->origin; + //ALERT( at_console, "TANK controls activated\n"); + } + } + else if (m_pController && useType != USE_ON) + { + // player stepped away or died, most likely. + //ALERT(at_console, "TANK controls deactivated\n"); + + //LRC- Now uses FindEntityByTargetname, so that aliases work. + while (tryTank = UTIL_FindEntityByTargetname(tryTank, STRING(pev->target))) + { + if (FClassnameIs(tryTank->pev, "func_tank") || FClassnameIs(tryTank->pev, "func_tanklaser") || FClassnameIs(tryTank->pev, "func_tankmortar") || FClassnameIs(tryTank->pev, "func_tankrocket")) + { + // this is a tank we're controlling. + ((CFuncTank*)tryTank)->StopControl(this); + } + } +// if (!m_pController) +// return; + + // bring back player's weapons + if ( m_pController->m_pActiveItem ) + m_pController->m_pActiveItem->Deploy(); + + m_pController->m_iHideHUD &= ~ HIDEHUD_WEAPONS; + m_pController->m_pTank = NULL; + + m_pController = NULL; + m_active = false; + } + + +// if ( m_pTank ) +// m_pTank->Use( pActivator, pCaller, useType, value ); + +// ASSERT( m_pTank != NULL ); // if this fails, most likely means save/restore hasn't worked properly +} + + +/* LRC- no need to set up m_pTank any more... +void CFuncTankControls :: Think( void ) +{ + CBaseEntity *pTarget = NULL; + + do + { + pTarget = UTIL_FindEntityByClassname( pTarget, STRING(pev->target) ); + } while ( pTarget && strncmp( STRING(pTarget->v.classname), "func_tank", 9 ) ); + + + if ( !pTarget ) + { + ALERT( at_console, "No tank %s\n", STRING(pev->target) ); + return; + } + else + { + m_pTank = (CFuncTank*)pTarget; + do + { + pTarget = UTIL_FindEntityByClassname( pTarget, STRING(pev->target) ); + } while ( pTarget && strncmp( STRING(pTarget->v.classname), "func_tank", 9 ) ); + + if ( pTarget ) + { + m_pTank2 = (CFuncTank*)pTarget; + ALERT( at_console, "Got second tank %s\n", STRING(pev->target) ); + } + } +}*/ + +void CFuncTankControls::Spawn( void ) +{ + pev->solid = SOLID_TRIGGER; + pev->movetype = MOVETYPE_NONE; + if (!(pev->spawnflags & SF_TANKCONTROLS_VISIBLE)) + pev->effects |= EF_NODRAW; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + if (pev->frags == 0) //LRC- in case the level designer didn't set it. + pev->frags = 30; + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( this, pev->origin ); + +//LRC SetNextThink( 0.3 ); // After all the func_tanks have spawned + + CBaseEntity::Spawn(); +} + +//============================================================================ +//LRC - Scripted Tank Sequence +//============================================================================ + +LINK_ENTITY_TO_CLASS( scripted_tanksequence, CTankSequence ); + +TYPEDESCRIPTION CTankSequence::m_SaveData[] = +{ + DEFINE_FIELD( CTankSequence, m_iszEntity, FIELD_STRING ), + DEFINE_FIELD( CTankSequence, m_iszEnemy, FIELD_STRING ), + DEFINE_FIELD( CTankSequence, m_iUntil, FIELD_INTEGER ), + DEFINE_FIELD( CTankSequence, m_fDuration, FIELD_FLOAT ), + DEFINE_FIELD( CTankSequence, m_iTurn, FIELD_INTEGER ), + DEFINE_FIELD( CTankSequence, m_iShoot, FIELD_INTEGER ), + DEFINE_FIELD( CTankSequence, m_iActive, FIELD_INTEGER ), + DEFINE_FIELD( CTankSequence, m_iControllable, FIELD_INTEGER ), + DEFINE_FIELD( CTankSequence, m_iLaserSpot, FIELD_INTEGER ), + DEFINE_FIELD( CTankSequence, m_pTank, FIELD_CLASSPTR), +}; + +IMPLEMENT_SAVERESTORE( CTankSequence, CBaseEntity ); + +int CTankSequence :: ObjectCaps( void ) +{ + return (CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION); +} + +void CTankSequence :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iUntil")) + { + m_iUntil = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iTurn")) + { + m_iTurn = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iShoot")) + { + m_iShoot = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iActive")) + { + m_iActive = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iControllable")) + { + m_iControllable = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iLaserSpot")) + { + m_iLaserSpot = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszEntity")) + { + m_iszEntity = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszEnemy")) + { + m_iszEnemy = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CTankSequence :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (!ShouldToggle(useType)) return; + + if (GetState() == STATE_OFF) + { + // take control of the tank, start the sequence + + CBaseEntity* pEnt = UTIL_FindEntityByTargetname(NULL, STRING(m_iszEntity)); + if (!pEnt || !FStrEq(STRING(pEnt->pev->classname), "func_tank")) + { + ALERT(at_error, "Invalid or missing tank \"%s\" for scripted_tanksequence!\n", STRING(m_iszEntity)); + return; + } + CFuncTank* pTank = (CFuncTank*)pEnt; + + // check whether it's being controlled by another sequence + if (pTank->m_pSequence) + return; + + // check whether it's being controlled by the player + if (pTank->m_pControls) + { + if (pev->spawnflags & SF_TSEQ_DUMPPLAYER) + { + pTank->StopControl( pTank->m_pControls ); + } + else if (!FBitSet(pev->spawnflags, SF_TSEQ_DUMPPLAYER)) + { + //ALERT(at_debug, "scripted_tanksequence can't take tank away from player\n"); + return; + } + } + + //passed all the tests, so we can now take control of it. + if (m_iTurn == TSEQ_TURN_ENEMY) + { + CBaseEntity *pEnemy; + if (m_iszEnemy) + pEnemy = UTIL_FindEntityGeneric(STRING(m_iszEnemy), pTank->pev->origin, pTank->m_maxRange ); + else + pEnemy = pTank->BestVisibleEnemy(); + + if (pEnemy) + { + pTank->m_pSequenceEnemy = pEnemy; + pTank->StartSequence(this); + } + } + else + { + pTank->StartSequence(this); + } + + if (m_iShoot == TSEQ_SHOOT_ALWAYS) + pTank->pev->spawnflags |= SF_TANK_SEQFIRE; + else + pTank->pev->spawnflags &= ~SF_TANK_SEQFIRE; + + m_pTank = pTank; + if (m_fDuration) + { + SetThink(&CTankSequence :: TimeOutThink ); + SetNextThink( m_fDuration ); + } + } + else // don't check UNTIL_TRIGGER - any UNTIL value can be prematurely ended. + { + //disable any other end conditions + DontThink(); + + // release control of the tank + StopSequence(); + } +} + +void CTankSequence :: FacingNotify() +{ + if (m_iUntil == TSEQ_UNTIL_FACING) + { + SetThink(&CTankSequence :: EndThink ); + SetNextThink( 0 ); + } + else if (m_iShoot == TSEQ_SHOOT_FACING) + m_pTank->pev->spawnflags |= SF_TANK_SEQFIRE; +} + +void CTankSequence :: DeadEnemyNotify() +{ + if (m_iUntil == TSEQ_UNTIL_DEATH) + { + SetThink(&CTankSequence :: EndThink ); + SetNextThink( 0 ); + } +// else +// m_pTank->pev->spawnflags &= ~SF_TANK_SEQFIRE; // if the enemy's dead, stop firing +} + +void CTankSequence :: EndThink() +{ + //the sequence has expired. Release control of the tank. + StopSequence(); + if (!FStringNull(pev->target)) + FireTargets(STRING(pev->target), this, this, USE_TOGGLE, 0); +} + +void CTankSequence :: TimeOutThink() +{ + //the sequence has timed out. Release control of the tank. + StopSequence(); + if (!FStringNull(pev->netname)) + FireTargets(STRING(pev->netname), this, this, USE_TOGGLE, 0); +} + +void CTankSequence :: StopSequence() +{ + if (!m_pTank) + { + ALERT(at_error, "TankSeq: StopSequence with no tank!\n"); + return; // this shouldn't happen. Just insurance... + } + + // if we're doing "shoot at end", fire that shot now. + if (m_iShoot == TSEQ_SHOOT_ONCE) + { + m_pTank->m_fireLast = gpGlobals->time - 1/m_pTank->m_fireRate; // exactly one shot. + Vector forward; + UTIL_MakeVectorsPrivate( m_pTank->pev->angles, forward, NULL, NULL ); + m_pTank->TryFire( m_pTank->BarrelPosition(), forward, m_pTank->pev ); + } + + if (m_iLaserSpot) + { + if (m_pTank->pev->spawnflags & SF_TANK_LASERSPOT && m_iLaserSpot != TSEQ_FLAG_ON) + { + m_pTank->pev->spawnflags &= ~SF_TANK_LASERSPOT; + } + else if (!FBitSet(m_pTank->pev->spawnflags, SF_TANK_LASERSPOT) && m_iLaserSpot != TSEQ_FLAG_OFF) + { + m_pTank->pev->spawnflags |= SF_TANK_LASERSPOT; + } + } + + if (m_iControllable) + { + if (m_pTank->pev->spawnflags & SF_TANK_CANCONTROL && m_iControllable != TSEQ_FLAG_ON) + { + m_pTank->pev->spawnflags &= ~SF_TANK_CANCONTROL; + } + else if (!(m_pTank->pev->spawnflags & SF_TANK_CANCONTROL) && m_iControllable != TSEQ_FLAG_OFF) + { + m_pTank->pev->spawnflags |= SF_TANK_CANCONTROL; + } + } + + m_pTank->StopSequence(); + + if (!FBitSet(pev->spawnflags, SF_TSEQ_REPEATABLE)) + UTIL_Remove( this ); + + if (m_pTank->IsActive() && (m_iActive == TSEQ_FLAG_OFF || m_iActive == TSEQ_FLAG_TOGGLE)) + { + m_pTank->TankDeactivate(); + if (m_pTank->m_pSpot) m_pTank->m_pSpot->Suspend(-1); + } + else if (!m_pTank->IsActive() && (m_iActive == TSEQ_FLAG_ON || m_iActive == TSEQ_FLAG_TOGGLE)) + { + m_pTank->TankActivate(); + if (m_pTank->m_pSpot) m_pTank->m_pSpot->Revive(); + } + + m_pTank = NULL; +} diff --git a/dlls/game.cpp b/dlls/game.cpp new file mode 100644 index 0000000..341ff9e --- /dev/null +++ b/dlls/game.cpp @@ -0,0 +1,1087 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "eiface.h" +#include "util.h" +#include "game.h" + +cvar_t displaysoundlist = {"displaysoundlist","0"}; + +// multiplayer server rules +cvar_t fragsleft = {"mp_fragsleft","0", FCVAR_SERVER | FCVAR_UNLOGGED }; // Don't spam console/log files/users with this changing +cvar_t timeleft = {"mp_timeleft","0" , FCVAR_SERVER | FCVAR_UNLOGGED }; // " " + +// multiplayer server rules +cvar_t teamplay = {"mp_teamplay","0", FCVAR_SERVER }; +cvar_t fraglimit = {"mp_fraglimit","0", FCVAR_SERVER }; +cvar_t timelimit = {"mp_timelimit","0", FCVAR_SERVER }; +cvar_t friendlyfire= {"mp_friendlyfire","0", FCVAR_SERVER }; +cvar_t falldamage = {"mp_falldamage","0", FCVAR_SERVER }; +cvar_t weaponstay = {"mp_weaponstay","0", FCVAR_SERVER }; +cvar_t forcerespawn= {"mp_forcerespawn","1", FCVAR_SERVER }; +cvar_t flashlight = {"mp_flashlight","0", FCVAR_SERVER }; +cvar_t aimcrosshair= {"mp_autocrosshair","1", FCVAR_SERVER }; +cvar_t decalfrequency = {"decalfrequency","0", FCVAR_SERVER }; +cvar_t teamlist = {"mp_teamlist","hgrunt;scientist", FCVAR_SERVER }; +cvar_t teamoverride = {"mp_teamoverride","1" }; +cvar_t defaultteam = {"mp_defaultteam","0" }; +cvar_t allowmonsters={"mp_allowmonsters","0", FCVAR_SERVER }; + +cvar_t impulsetarget={"sohl_impulsetarget","0", FCVAR_SERVER }; //LRC - trigger ents manually +cvar_t mw_debug={"sohl_mwdebug","0", FCVAR_SERVER }; //LRC - debug info. for MoveWith. (probably not useful for most people.) + +cvar_t mp_chattime = {"mp_chattime","10", FCVAR_SERVER }; + +// Engine Cvars +cvar_t *g_psv_gravity = NULL; +cvar_t *g_psv_aim = NULL; +cvar_t *g_footsteps = NULL; +cvar_t *g_precache_meshes = NULL; + +cvar_t weapon_x = { "weapon_x", "0", 0 }; +cvar_t weapon_y = { "weapon_y", "0", 0 }; +cvar_t weapon_z = { "weapon_z", "0", 0 }; + +//CVARS FOR SKILL LEVEL SETTINGS +// Agrunt +cvar_t sk_agrunt_health1 = {"sk_agrunt_health1","0"}; +cvar_t sk_agrunt_health2 = {"sk_agrunt_health2","0"}; +cvar_t sk_agrunt_health3 = {"sk_agrunt_health3","0"}; + +cvar_t sk_agrunt_dmg_punch1 = {"sk_agrunt_dmg_punch1","0"}; +cvar_t sk_agrunt_dmg_punch2 = {"sk_agrunt_dmg_punch2","0"}; +cvar_t sk_agrunt_dmg_punch3 = {"sk_agrunt_dmg_punch3","0"}; + +// Apache +cvar_t sk_apache_health1 = {"sk_apache_health1","0"}; +cvar_t sk_apache_health2 = {"sk_apache_health2","0"}; +cvar_t sk_apache_health3 = {"sk_apache_health3","0"}; + +// Barney +cvar_t sk_barney_health1 = {"sk_barney_health1","0"}; +cvar_t sk_barney_health2 = {"sk_barney_health2","0"}; +cvar_t sk_barney_health3 = {"sk_barney_health3","0"}; + +// Bullsquid +cvar_t sk_bullsquid_health1 = {"sk_bullsquid_health1","0"}; +cvar_t sk_bullsquid_health2 = {"sk_bullsquid_health2","0"}; +cvar_t sk_bullsquid_health3 = {"sk_bullsquid_health3","0"}; + +cvar_t sk_bullsquid_dmg_bite1 = {"sk_bullsquid_dmg_bite1","0"}; +cvar_t sk_bullsquid_dmg_bite2 = {"sk_bullsquid_dmg_bite2","0"}; +cvar_t sk_bullsquid_dmg_bite3 = {"sk_bullsquid_dmg_bite3","0"}; + +cvar_t sk_bullsquid_dmg_whip1 = {"sk_bullsquid_dmg_whip1","0"}; +cvar_t sk_bullsquid_dmg_whip2 = {"sk_bullsquid_dmg_whip2","0"}; +cvar_t sk_bullsquid_dmg_whip3 = {"sk_bullsquid_dmg_whip3","0"}; + +cvar_t sk_bullsquid_dmg_spit1 = {"sk_bullsquid_dmg_spit1","0"}; +cvar_t sk_bullsquid_dmg_spit2 = {"sk_bullsquid_dmg_spit2","0"}; +cvar_t sk_bullsquid_dmg_spit3 = {"sk_bullsquid_dmg_spit3","0"}; + + +// Big Momma +cvar_t sk_bigmomma_health_factor1 = {"sk_bigmomma_health_factor1","1.0"}; +cvar_t sk_bigmomma_health_factor2 = {"sk_bigmomma_health_factor2","1.0"}; +cvar_t sk_bigmomma_health_factor3 = {"sk_bigmomma_health_factor3","1.0"}; + +cvar_t sk_bigmomma_dmg_slash1 = {"sk_bigmomma_dmg_slash1","50"}; +cvar_t sk_bigmomma_dmg_slash2 = {"sk_bigmomma_dmg_slash2","50"}; +cvar_t sk_bigmomma_dmg_slash3 = {"sk_bigmomma_dmg_slash3","50"}; + +cvar_t sk_bigmomma_dmg_blast1 = {"sk_bigmomma_dmg_blast1","100"}; +cvar_t sk_bigmomma_dmg_blast2 = {"sk_bigmomma_dmg_blast2","100"}; +cvar_t sk_bigmomma_dmg_blast3 = {"sk_bigmomma_dmg_blast3","100"}; + +cvar_t sk_bigmomma_radius_blast1 = {"sk_bigmomma_radius_blast1","250"}; +cvar_t sk_bigmomma_radius_blast2 = {"sk_bigmomma_radius_blast2","250"}; +cvar_t sk_bigmomma_radius_blast3 = {"sk_bigmomma_radius_blast3","250"}; + +// Gargantua +cvar_t sk_gargantua_health1 = {"sk_gargantua_health1","0"}; +cvar_t sk_gargantua_health2 = {"sk_gargantua_health2","0"}; +cvar_t sk_gargantua_health3 = {"sk_gargantua_health3","0"}; + +cvar_t sk_gargantua_dmg_slash1 = {"sk_gargantua_dmg_slash1","0"}; +cvar_t sk_gargantua_dmg_slash2 = {"sk_gargantua_dmg_slash2","0"}; +cvar_t sk_gargantua_dmg_slash3 = {"sk_gargantua_dmg_slash3","0"}; + +cvar_t sk_gargantua_dmg_fire1 = {"sk_gargantua_dmg_fire1","0"}; +cvar_t sk_gargantua_dmg_fire2 = {"sk_gargantua_dmg_fire2","0"}; +cvar_t sk_gargantua_dmg_fire3 = {"sk_gargantua_dmg_fire3","0"}; + +cvar_t sk_gargantua_dmg_stomp1 = {"sk_gargantua_dmg_stomp1","0"}; +cvar_t sk_gargantua_dmg_stomp2 = {"sk_gargantua_dmg_stomp2","0"}; +cvar_t sk_gargantua_dmg_stomp3 = {"sk_gargantua_dmg_stomp3","0"}; + + +// Hassassin +cvar_t sk_hassassin_health1 = {"sk_hassassin_health1","0"}; +cvar_t sk_hassassin_health2 = {"sk_hassassin_health2","0"}; +cvar_t sk_hassassin_health3 = {"sk_hassassin_health3","0"}; + + +// Headcrab +cvar_t sk_headcrab_health1 = {"sk_headcrab_health1","0"}; +cvar_t sk_headcrab_health2 = {"sk_headcrab_health2","0"}; +cvar_t sk_headcrab_health3 = {"sk_headcrab_health3","0"}; + +cvar_t sk_headcrab_dmg_bite1 = {"sk_headcrab_dmg_bite1","0"}; +cvar_t sk_headcrab_dmg_bite2 = {"sk_headcrab_dmg_bite2","0"}; +cvar_t sk_headcrab_dmg_bite3 = {"sk_headcrab_dmg_bite3","0"}; + + +// Hgrunt +cvar_t sk_hgrunt_health1 = {"sk_hgrunt_health1","0"}; +cvar_t sk_hgrunt_health2 = {"sk_hgrunt_health2","0"}; +cvar_t sk_hgrunt_health3 = {"sk_hgrunt_health3","0"}; + +cvar_t sk_hgrunt_kick1 = {"sk_hgrunt_kick1","0"}; +cvar_t sk_hgrunt_kick2 = {"sk_hgrunt_kick2","0"}; +cvar_t sk_hgrunt_kick3 = {"sk_hgrunt_kick3","0"}; + +cvar_t sk_hgrunt_pellets1 = {"sk_hgrunt_pellets1","0"}; +cvar_t sk_hgrunt_pellets2 = {"sk_hgrunt_pellets2","0"}; +cvar_t sk_hgrunt_pellets3 = {"sk_hgrunt_pellets3","0"}; + +cvar_t sk_hgrunt_gspeed1 = {"sk_hgrunt_gspeed1","0"}; +cvar_t sk_hgrunt_gspeed2 = {"sk_hgrunt_gspeed2","0"}; +cvar_t sk_hgrunt_gspeed3 = {"sk_hgrunt_gspeed3","0"}; + +// Houndeye +cvar_t sk_houndeye_health1 = {"sk_houndeye_health1","0"}; +cvar_t sk_houndeye_health2 = {"sk_houndeye_health2","0"}; +cvar_t sk_houndeye_health3 = {"sk_houndeye_health3","0"}; + +cvar_t sk_houndeye_dmg_blast1 = {"sk_houndeye_dmg_blast1","0"}; +cvar_t sk_houndeye_dmg_blast2 = {"sk_houndeye_dmg_blast2","0"}; +cvar_t sk_houndeye_dmg_blast3 = {"sk_houndeye_dmg_blast3","0"}; + + +// ISlave +cvar_t sk_islave_health1 = {"sk_islave_health1","0"}; +cvar_t sk_islave_health2 = {"sk_islave_health2","0"}; +cvar_t sk_islave_health3 = {"sk_islave_health3","0"}; + +cvar_t sk_islave_dmg_claw1 = {"sk_islave_dmg_claw1","0"}; +cvar_t sk_islave_dmg_claw2 = {"sk_islave_dmg_claw2","0"}; +cvar_t sk_islave_dmg_claw3 = {"sk_islave_dmg_claw3","0"}; + +cvar_t sk_islave_dmg_clawrake1 = {"sk_islave_dmg_clawrake1","0"}; +cvar_t sk_islave_dmg_clawrake2 = {"sk_islave_dmg_clawrake2","0"}; +cvar_t sk_islave_dmg_clawrake3 = {"sk_islave_dmg_clawrake3","0"}; + +cvar_t sk_islave_dmg_zap1 = {"sk_islave_dmg_zap1","0"}; +cvar_t sk_islave_dmg_zap2 = {"sk_islave_dmg_zap2","0"}; +cvar_t sk_islave_dmg_zap3 = {"sk_islave_dmg_zap3","0"}; + + +// Icthyosaur +cvar_t sk_ichthyosaur_health1 = {"sk_ichthyosaur_health1","0"}; +cvar_t sk_ichthyosaur_health2 = {"sk_ichthyosaur_health2","0"}; +cvar_t sk_ichthyosaur_health3 = {"sk_ichthyosaur_health3","0"}; + +cvar_t sk_ichthyosaur_shake1 = {"sk_ichthyosaur_shake1","0"}; +cvar_t sk_ichthyosaur_shake2 = {"sk_ichthyosaur_shake2","0"}; +cvar_t sk_ichthyosaur_shake3 = {"sk_ichthyosaur_shake3","0"}; + + +// Leech +cvar_t sk_leech_health1 = {"sk_leech_health1","0"}; +cvar_t sk_leech_health2 = {"sk_leech_health2","0"}; +cvar_t sk_leech_health3 = {"sk_leech_health3","0"}; + +cvar_t sk_leech_dmg_bite1 = {"sk_leech_dmg_bite1","0"}; +cvar_t sk_leech_dmg_bite2 = {"sk_leech_dmg_bite2","0"}; +cvar_t sk_leech_dmg_bite3 = {"sk_leech_dmg_bite3","0"}; + +// Controller +cvar_t sk_controller_health1 = {"sk_controller_health1","0"}; +cvar_t sk_controller_health2 = {"sk_controller_health2","0"}; +cvar_t sk_controller_health3 = {"sk_controller_health3","0"}; + +cvar_t sk_controller_dmgzap1 = {"sk_controller_dmgzap1","0"}; +cvar_t sk_controller_dmgzap2 = {"sk_controller_dmgzap2","0"}; +cvar_t sk_controller_dmgzap3 = {"sk_controller_dmgzap3","0"}; + +cvar_t sk_controller_speedball1 = {"sk_controller_speedball1","0"}; +cvar_t sk_controller_speedball2 = {"sk_controller_speedball2","0"}; +cvar_t sk_controller_speedball3 = {"sk_controller_speedball3","0"}; + +cvar_t sk_controller_dmgball1 = {"sk_controller_dmgball1","0"}; +cvar_t sk_controller_dmgball2 = {"sk_controller_dmgball2","0"}; +cvar_t sk_controller_dmgball3 = {"sk_controller_dmgball3","0"}; + +// Nihilanth +cvar_t sk_nihilanth_health1 = {"sk_nihilanth_health1","0"}; +cvar_t sk_nihilanth_health2 = {"sk_nihilanth_health2","0"}; +cvar_t sk_nihilanth_health3 = {"sk_nihilanth_health3","0"}; + +cvar_t sk_nihilanth_zap1 = {"sk_nihilanth_zap1","0"}; +cvar_t sk_nihilanth_zap2 = {"sk_nihilanth_zap2","0"}; +cvar_t sk_nihilanth_zap3 = {"sk_nihilanth_zap3","0"}; + +// Scientist +cvar_t sk_scientist_health1 = {"sk_scientist_health1","0"}; +cvar_t sk_scientist_health2 = {"sk_scientist_health2","0"}; +cvar_t sk_scientist_health3 = {"sk_scientist_health3","0"}; + + +// Snark +cvar_t sk_snark_health1 = {"sk_snark_health1","0"}; +cvar_t sk_snark_health2 = {"sk_snark_health2","0"}; +cvar_t sk_snark_health3 = {"sk_snark_health3","0"}; + +cvar_t sk_snark_dmg_bite1 = {"sk_snark_dmg_bite1","0"}; +cvar_t sk_snark_dmg_bite2 = {"sk_snark_dmg_bite2","0"}; +cvar_t sk_snark_dmg_bite3 = {"sk_snark_dmg_bite3","0"}; + +cvar_t sk_snark_dmg_pop1 = {"sk_snark_dmg_pop1","0"}; +cvar_t sk_snark_dmg_pop2 = {"sk_snark_dmg_pop2","0"}; +cvar_t sk_snark_dmg_pop3 = {"sk_snark_dmg_pop3","0"}; + + + +// Zombie +cvar_t sk_zombie_health1 = {"sk_zombie_health1","0"}; +cvar_t sk_zombie_health2 = {"sk_zombie_health2","0"}; +cvar_t sk_zombie_health3 = {"sk_zombie_health3","0"}; + +cvar_t sk_zombie_dmg_one_slash1 = {"sk_zombie_dmg_one_slash1","0"}; +cvar_t sk_zombie_dmg_one_slash2 = {"sk_zombie_dmg_one_slash2","0"}; +cvar_t sk_zombie_dmg_one_slash3 = {"sk_zombie_dmg_one_slash3","0"}; + +cvar_t sk_zombie_dmg_both_slash1 = {"sk_zombie_dmg_both_slash1","0"}; +cvar_t sk_zombie_dmg_both_slash2 = {"sk_zombie_dmg_both_slash2","0"}; +cvar_t sk_zombie_dmg_both_slash3 = {"sk_zombie_dmg_both_slash3","0"}; + + +//Turret +cvar_t sk_turret_health1 = {"sk_turret_health1","0"}; +cvar_t sk_turret_health2 = {"sk_turret_health2","0"}; +cvar_t sk_turret_health3 = {"sk_turret_health3","0"}; + + +// MiniTurret +cvar_t sk_miniturret_health1 = {"sk_miniturret_health1","0"}; +cvar_t sk_miniturret_health2 = {"sk_miniturret_health2","0"}; +cvar_t sk_miniturret_health3 = {"sk_miniturret_health3","0"}; + + +// Sentry Turret +cvar_t sk_sentry_health1 = {"sk_sentry_health1","0"}; +cvar_t sk_sentry_health2 = {"sk_sentry_health2","0"}; +cvar_t sk_sentry_health3 = {"sk_sentry_health3","0"}; + + +// PLAYER WEAPONS + +// Crowbar whack +cvar_t sk_plr_crowbar1 = {"sk_plr_crowbar1","0"}; +cvar_t sk_plr_crowbar2 = {"sk_plr_crowbar2","0"}; +cvar_t sk_plr_crowbar3 = {"sk_plr_crowbar3","0"}; +cvar_t sk_plr_crowbarsec1 = {"sk_plr_crowbarsec1","0"}; +cvar_t sk_plr_crowbarsec2 = {"sk_plr_crowbarsec2","0"}; +cvar_t sk_plr_crowbarsec3 = {"sk_plr_crowbarsec3","0"}; + +// Glock Round +cvar_t sk_plr_9mm_bullet1 = {"sk_plr_9mm_bullet1","0"}; +cvar_t sk_plr_9mm_bullet2 = {"sk_plr_9mm_bullet2","0"}; +cvar_t sk_plr_9mm_bullet3 = {"sk_plr_9mm_bullet3","0"}; + +// MP5 Round +cvar_t sk_plr_9mmAR_bullet1 = {"sk_plr_9mmAR_bullet1","0"}; +cvar_t sk_plr_9mmAR_bullet2 = {"sk_plr_9mmAR_bullet2","0"}; +cvar_t sk_plr_9mmAR_bullet3 = {"sk_plr_9mmAR_bullet3","0"}; + + +// M203 grenade +cvar_t sk_plr_9mmAR_grenade1 = {"sk_plr_9mmAR_grenade1","0"}; +cvar_t sk_plr_9mmAR_grenade2 = {"sk_plr_9mmAR_grenade2","0"}; +cvar_t sk_plr_9mmAR_grenade3 = {"sk_plr_9mmAR_grenade3","0"}; + + +// Shotgun buckshot +cvar_t sk_plr_buckshot1 = {"sk_plr_buckshot1","0"}; +cvar_t sk_plr_buckshot2 = {"sk_plr_buckshot2","0"}; +cvar_t sk_plr_buckshot3 = {"sk_plr_buckshot3","0"}; + +// RPG +cvar_t sk_plr_rpg1 = {"sk_plr_rpg1","0"}; +cvar_t sk_plr_rpg2 = {"sk_plr_rpg2","0"}; +cvar_t sk_plr_rpg3 = {"sk_plr_rpg3","0"}; + +// Hand Grendade +cvar_t sk_plr_hand_grenade1 = {"sk_plr_hand_grenade1","0"}; +cvar_t sk_plr_hand_grenade2 = {"sk_plr_hand_grenade2","0"}; +cvar_t sk_plr_hand_grenade3 = {"sk_plr_hand_grenade3","0"}; + +// WORLD WEAPONS +cvar_t sk_12mm_bullet1 = {"sk_12mm_bullet1","0"}; +cvar_t sk_12mm_bullet2 = {"sk_12mm_bullet2","0"}; +cvar_t sk_12mm_bullet3 = {"sk_12mm_bullet3","0"}; + +cvar_t sk_9mmAR_bullet1 = {"sk_9mmAR_bullet1","0"}; +cvar_t sk_9mmAR_bullet2 = {"sk_9mmAR_bullet2","0"}; +cvar_t sk_9mmAR_bullet3 = {"sk_9mmAR_bullet3","0"}; + +cvar_t sk_9mm_bullet1 = {"sk_9mm_bullet1","0"}; +cvar_t sk_9mm_bullet2 = {"sk_9mm_bullet2","0"}; +cvar_t sk_9mm_bullet3 = {"sk_9mm_bullet3","0"}; + +// HEALTH/CHARGE +cvar_t sk_suitcharger1 = { "sk_suitcharger1","0" }; +cvar_t sk_suitcharger2 = { "sk_suitcharger2","0" }; +cvar_t sk_suitcharger3 = { "sk_suitcharger3","0" }; + +cvar_t sk_battery1 = { "sk_battery1","0" }; +cvar_t sk_battery2 = { "sk_battery2","0" }; +cvar_t sk_battery3 = { "sk_battery3","0" }; + +// HORNET +cvar_t sk_hornet_dmg1 = {"sk_hornet_dmg1","0"}; +cvar_t sk_hornet_dmg2 = {"sk_hornet_dmg2","0"}; +cvar_t sk_hornet_dmg3 = {"sk_hornet_dmg3","0"}; + +cvar_t sk_healthcharger1 = { "sk_healthcharger1","0" }; +cvar_t sk_healthcharger2 = { "sk_healthcharger2","0" }; +cvar_t sk_healthcharger3 = { "sk_healthcharger3","0" }; + +cvar_t sk_healthkit1 = { "sk_healthkit1","0" }; +cvar_t sk_healthkit2 = { "sk_healthkit2","0" }; +cvar_t sk_healthkit3 = { "sk_healthkit3","0" }; + +// buz +cvar_t sk_bighealthkit1 = { "sk_bighealthkit1","0" }; +cvar_t sk_bighealthkit2 = { "sk_bighealthkit2","0" }; +cvar_t sk_bighealthkit3 = { "sk_bighealthkit3","0" }; + +cvar_t sk_painkiller1 = { "sk_painkiller1","0" }; +cvar_t sk_painkiller2 = { "sk_painkiller2","0" }; +cvar_t sk_painkiller3 = { "sk_painkiller3","0" }; + +cvar_t sk_scientist_heal1 = { "sk_scientist_heal1","0" }; +cvar_t sk_scientist_heal2 = { "sk_scientist_heal2","0" }; +cvar_t sk_scientist_heal3 = { "sk_scientist_heal3","0" }; + + +// monster damage adjusters +cvar_t sk_monster_head1 = { "sk_monster_head1","2" }; +cvar_t sk_monster_head2 = { "sk_monster_head2","2" }; +cvar_t sk_monster_head3 = { "sk_monster_head3","2" }; + +cvar_t sk_monster_chest1 = { "sk_monster_chest1","1" }; +cvar_t sk_monster_chest2 = { "sk_monster_chest2","1" }; +cvar_t sk_monster_chest3 = { "sk_monster_chest3","1" }; + +cvar_t sk_monster_stomach1 = { "sk_monster_stomach1","1" }; +cvar_t sk_monster_stomach2 = { "sk_monster_stomach2","1" }; +cvar_t sk_monster_stomach3 = { "sk_monster_stomach3","1" }; + +cvar_t sk_monster_arm1 = { "sk_monster_arm1","1" }; +cvar_t sk_monster_arm2 = { "sk_monster_arm2","1" }; +cvar_t sk_monster_arm3 = { "sk_monster_arm3","1" }; + +cvar_t sk_monster_leg1 = { "sk_monster_leg1","1" }; +cvar_t sk_monster_leg2 = { "sk_monster_leg2","1" }; +cvar_t sk_monster_leg3 = { "sk_monster_leg3","1" }; + +// player damage adjusters +cvar_t sk_player_head1 = { "sk_player_head1","2" }; +cvar_t sk_player_head2 = { "sk_player_head2","2" }; +cvar_t sk_player_head3 = { "sk_player_head3","2" }; + +cvar_t sk_player_chest1 = { "sk_player_chest1","1" }; +cvar_t sk_player_chest2 = { "sk_player_chest2","1" }; +cvar_t sk_player_chest3 = { "sk_player_chest3","1" }; + +cvar_t sk_player_stomach1 = { "sk_player_stomach1","1" }; +cvar_t sk_player_stomach2 = { "sk_player_stomach2","1" }; +cvar_t sk_player_stomach3 = { "sk_player_stomach3","1" }; + +cvar_t sk_player_arm1 = { "sk_player_arm1","1" }; +cvar_t sk_player_arm2 = { "sk_player_arm2","1" }; +cvar_t sk_player_arm3 = { "sk_player_arm3","1" }; + +cvar_t sk_player_leg1 = { "sk_player_leg1","1" }; +cvar_t sk_player_leg2 = { "sk_player_leg2","1" }; +cvar_t sk_player_leg3 = { "sk_player_leg3","1" }; + +// Wargon: Ìíîæèòåëè äàìàãè äëÿ monster_zombie. (1.1) +cvar_t sk_zombie_head1 = { "sk_zombie_head1","2" }; +cvar_t sk_zombie_head2 = { "sk_zombie_head2","2" }; +cvar_t sk_zombie_head3 = { "sk_zombie_head3","2" }; + +cvar_t sk_zombie_chest1 = { "sk_zombie_chest1","1" }; +cvar_t sk_zombie_chest2 = { "sk_zombie_chest2","1" }; +cvar_t sk_zombie_chest3 = { "sk_zombie_chest3","1" }; + +cvar_t sk_zombie_stomach1 = { "sk_zombie_stomach1","1" }; +cvar_t sk_zombie_stomach2 = { "sk_zombie_stomach2","1" }; +cvar_t sk_zombie_stomach3 = { "sk_zombie_stomach3","1" }; + +cvar_t sk_zombie_arm1 = { "sk_zombie_arm1","1" }; +cvar_t sk_zombie_arm2 = { "sk_zombie_arm2","1" }; +cvar_t sk_zombie_arm3 = { "sk_zombie_arm3","1" }; + +cvar_t sk_zombie_leg1 = { "sk_zombie_leg1","1" }; +cvar_t sk_zombie_leg2 = { "sk_zombie_leg2","1" }; +cvar_t sk_zombie_leg3 = { "sk_zombie_leg3","1" }; + +// buz: paranoia cvars +cvar_t sk_primary_speed1 = { "sk_primary_speed1","1" }; +cvar_t sk_primary_speed2 = { "sk_primary_speed2","1" }; +cvar_t sk_primary_speed3 = { "sk_primary_speed3","1" }; + +cvar_t sk_secondary_speed1 = { "sk_secondary_speed1","1" }; +cvar_t sk_secondary_speed2 = { "sk_secondary_speed2","1" }; +cvar_t sk_secondary_speed3 = { "sk_secondary_speed3","1" }; + +// buz: paranoia aps +cvar_t sk_plr_aps1 = {"sk_plr_aps1","0"}; +cvar_t sk_plr_aps2 = {"sk_plr_aps2","0"}; +cvar_t sk_plr_aps3 = {"sk_plr_aps3","0"}; + +// buz: paranoia barret +cvar_t sk_plr_barret1 = {"sk_plr_barret1","0"}; +cvar_t sk_plr_barret2 = {"sk_plr_barret2","0"}; +cvar_t sk_plr_barret3 = {"sk_plr_barret3","0"}; + +// buz: paranoia aks +cvar_t sk_plr_aks1 = {"sk_plr_aks1","0"}; +cvar_t sk_plr_aks2 = {"sk_plr_aks2","0"}; +cvar_t sk_plr_aks3 = {"sk_plr_aks3","0"}; + +// buz: paranoia ak47 +cvar_t sk_plr_ak471 = {"sk_plr_ak471","0"}; +cvar_t sk_plr_ak472 = {"sk_plr_ak472","0"}; +cvar_t sk_plr_ak473 = {"sk_plr_ak473","0"}; + +// buz: paranoia asval +cvar_t sk_plr_asval1 = {"sk_plr_asval1","0"}; +cvar_t sk_plr_asval2 = {"sk_plr_asval2","0"}; +cvar_t sk_plr_asval3 = {"sk_plr_asval3","0"}; + +// buz: paranoia groza +cvar_t sk_plr_groza1 = {"sk_plr_groza1","0"}; +cvar_t sk_plr_groza2 = {"sk_plr_groza2","0"}; +cvar_t sk_plr_groza3 = {"sk_plr_groza3","0"}; + +// buz: paranoia aps +cvar_t sk_plr_rpk1 = {"sk_plr_rpk1","0"}; +cvar_t sk_plr_rpk2 = {"sk_plr_rpk2","0"}; +cvar_t sk_plr_rpk3 = {"sk_plr_rpk3","0"}; + +// paranoia monster's AK +cvar_t sk_ak_bullet1 = {"sk_ak_bullet1","0"}; +cvar_t sk_ak_bullet2 = {"sk_ak_bullet2","0"}; +cvar_t sk_ak_bullet3 = {"sk_ak_bullet3","0"}; + +cvar_t sk_asval_bullet1 = {"sk_asval_bullet1","0"}; +cvar_t sk_asval_bullet2 = {"sk_asval_bullet2","0"}; +cvar_t sk_asval_bullet3 = {"sk_asval_bullet3","0"}; + +cvar_t sk_groza_bullet1 = {"sk_groza_bullet1","0"}; +cvar_t sk_groza_bullet2 = {"sk_groza_bullet2","0"}; +cvar_t sk_groza_bullet3 = {"sk_groza_bullet3","0"}; + +cvar_t sk_ter_rpk_bullet1 = {"sk_ter_rpk_bullet1","0"}; +cvar_t sk_ter_rpk_bullet2 = {"sk_ter_rpk_bullet2","0"}; +cvar_t sk_ter_rpk_bullet3 = {"sk_ter_rpk_bullet3","0"}; + +cvar_t sk_ter_ak_bullet1 = {"sk_ter_ak_bullet1","0"}; +cvar_t sk_ter_ak_bullet2 = {"sk_ter_ak_bullet2","0"}; +cvar_t sk_ter_ak_bullet3 = {"sk_ter_ak_bullet3","0"}; + +cvar_t sk_glock_bullet1 = {"sk_glock_bullet1","0"}; +cvar_t sk_glock_bullet2 = {"sk_glock_bullet2","0"}; +cvar_t sk_glock_bullet3 = {"sk_glock_bullet3","0"}; + +cvar_t sk_mil_kick1 = {"sk_mil_kick1","0"}; +cvar_t sk_mil_kick2 = {"sk_mil_kick2","0"}; +cvar_t sk_mil_kick3 = {"sk_mil_kick3","0"}; + +cvar_t sk_mil_health1 = {"sk_mil_health1","0"}; +cvar_t sk_mil_health2 = {"sk_mil_health2","0"}; +cvar_t sk_mil_health3 = {"sk_mil_health3","0"}; + +cvar_t sk_alpha_health1 = {"sk_alpha_health1","0"}; +cvar_t sk_alpha_health2 = {"sk_alpha_health2","0"}; +cvar_t sk_alpha_health3 = {"sk_alpha_health3","0"}; + +cvar_t sk_terror_health1 = {"sk_terror_health1","0"}; +cvar_t sk_terror_health2 = {"sk_terror_health2","0"}; +cvar_t sk_terror_health3 = {"sk_terror_health3","0"}; + +cvar_t sk_clone_health1 = {"sk_clone_health1","0"}; +cvar_t sk_clone_health2 = {"sk_clone_health2","0"}; +cvar_t sk_clone_health3 = {"sk_clone_health3","0"}; + +cvar_t sk_clone_health_heavy1 = {"sk_clone_health_heavy1","0"}; +cvar_t sk_clone_health_heavy2 = {"sk_clone_health_heavy2","0"}; +cvar_t sk_clone_health_heavy3 = {"sk_clone_health_heavy3","0"}; + +// buz: damage punch settings +cvar_t bullet_punch_max = {"bullet_punch_max","20"}; +cvar_t bullet_punch_divide = {"bullet_punch_divide","8"}; + +cvar_t blast_punch_max = {"blast_punch_max","100"}; +cvar_t blast_punch_divide = {"blast_punch_divide","5"}; + +// Wargon: Ïàòðîíû èç ìåðòâûõ âðàæèí. (1.1) +cvar_t sk_dead_enemy_ammo1 = {"sk_dead_enemy_ammo1","0"}; +cvar_t sk_dead_enemy_ammo2 = {"sk_dead_enemy_ammo2","0"}; +cvar_t sk_dead_enemy_ammo3 = {"sk_dead_enemy_ammo3","0"}; + +// END Cvars for Skill Level settings + +// Register your console variables here +// This gets called one time when the game is initialied +void GameDLLInit( void ) +{ + // Register cvars here: + + g_psv_gravity = CVAR_GET_POINTER( "sv_gravity" ); + g_psv_aim = CVAR_GET_POINTER( "sv_aim" ); + g_footsteps = CVAR_GET_POINTER( "mp_footsteps" ); + g_precache_meshes = CVAR_GET_POINTER( "sv_precache_meshes" ); + + CVAR_REGISTER (&displaysoundlist); + + CVAR_REGISTER (&weapon_x); + CVAR_REGISTER (&weapon_y); + CVAR_REGISTER (&weapon_z); + + CVAR_REGISTER (&teamplay); + CVAR_REGISTER (&fraglimit); + CVAR_REGISTER (&timelimit); + + CVAR_REGISTER (&fragsleft); + CVAR_REGISTER (&timeleft); + + CVAR_REGISTER (&friendlyfire); + CVAR_REGISTER (&falldamage); + CVAR_REGISTER (&weaponstay); + CVAR_REGISTER (&forcerespawn); + CVAR_REGISTER (&flashlight); + CVAR_REGISTER (&aimcrosshair); + CVAR_REGISTER (&decalfrequency); + CVAR_REGISTER (&teamlist); + CVAR_REGISTER (&teamoverride); + CVAR_REGISTER (&defaultteam); + CVAR_REGISTER (&allowmonsters); + CVAR_REGISTER (&impulsetarget); //LRC + CVAR_REGISTER (&mw_debug); //LRC + + CVAR_REGISTER (&mp_chattime); + +// REGISTER CVARS FOR SKILL LEVEL STUFF + // Agrunt + CVAR_REGISTER ( &sk_agrunt_health1 );// {"sk_agrunt_health1","0"}; + CVAR_REGISTER ( &sk_agrunt_health2 );// {"sk_agrunt_health2","0"}; + CVAR_REGISTER ( &sk_agrunt_health3 );// {"sk_agrunt_health3","0"}; + + CVAR_REGISTER ( &sk_agrunt_dmg_punch1 );// {"sk_agrunt_dmg_punch1","0"}; + CVAR_REGISTER ( &sk_agrunt_dmg_punch2 );// {"sk_agrunt_dmg_punch2","0"}; + CVAR_REGISTER ( &sk_agrunt_dmg_punch3 );// {"sk_agrunt_dmg_punch3","0"}; + + // Apache + CVAR_REGISTER ( &sk_apache_health1 );// {"sk_apache_health1","0"}; + CVAR_REGISTER ( &sk_apache_health2 );// {"sk_apache_health2","0"}; + CVAR_REGISTER ( &sk_apache_health3 );// {"sk_apache_health3","0"}; + + // Barney + CVAR_REGISTER ( &sk_barney_health1 );// {"sk_barney_health1","0"}; + CVAR_REGISTER ( &sk_barney_health2 );// {"sk_barney_health2","0"}; + CVAR_REGISTER ( &sk_barney_health3 );// {"sk_barney_health3","0"}; + + // Bullsquid + CVAR_REGISTER ( &sk_bullsquid_health1 );// {"sk_bullsquid_health1","0"}; + CVAR_REGISTER ( &sk_bullsquid_health2 );// {"sk_bullsquid_health2","0"}; + CVAR_REGISTER ( &sk_bullsquid_health3 );// {"sk_bullsquid_health3","0"}; + + CVAR_REGISTER ( &sk_bullsquid_dmg_bite1 );// {"sk_bullsquid_dmg_bite1","0"}; + CVAR_REGISTER ( &sk_bullsquid_dmg_bite2 );// {"sk_bullsquid_dmg_bite2","0"}; + CVAR_REGISTER ( &sk_bullsquid_dmg_bite3 );// {"sk_bullsquid_dmg_bite3","0"}; + + CVAR_REGISTER ( &sk_bullsquid_dmg_whip1 );// {"sk_bullsquid_dmg_whip1","0"}; + CVAR_REGISTER ( &sk_bullsquid_dmg_whip2 );// {"sk_bullsquid_dmg_whip2","0"}; + CVAR_REGISTER ( &sk_bullsquid_dmg_whip3 );// {"sk_bullsquid_dmg_whip3","0"}; + + CVAR_REGISTER ( &sk_bullsquid_dmg_spit1 );// {"sk_bullsquid_dmg_spit1","0"}; + CVAR_REGISTER ( &sk_bullsquid_dmg_spit2 );// {"sk_bullsquid_dmg_spit2","0"}; + CVAR_REGISTER ( &sk_bullsquid_dmg_spit3 );// {"sk_bullsquid_dmg_spit3","0"}; + + + CVAR_REGISTER ( &sk_bigmomma_health_factor1 );// {"sk_bigmomma_health_factor1","1.0"}; + CVAR_REGISTER ( &sk_bigmomma_health_factor2 );// {"sk_bigmomma_health_factor2","1.0"}; + CVAR_REGISTER ( &sk_bigmomma_health_factor3 );// {"sk_bigmomma_health_factor3","1.0"}; + + CVAR_REGISTER ( &sk_bigmomma_dmg_slash1 );// {"sk_bigmomma_dmg_slash1","50"}; + CVAR_REGISTER ( &sk_bigmomma_dmg_slash2 );// {"sk_bigmomma_dmg_slash2","50"}; + CVAR_REGISTER ( &sk_bigmomma_dmg_slash3 );// {"sk_bigmomma_dmg_slash3","50"}; + + CVAR_REGISTER ( &sk_bigmomma_dmg_blast1 );// {"sk_bigmomma_dmg_blast1","100"}; + CVAR_REGISTER ( &sk_bigmomma_dmg_blast2 );// {"sk_bigmomma_dmg_blast2","100"}; + CVAR_REGISTER ( &sk_bigmomma_dmg_blast3 );// {"sk_bigmomma_dmg_blast3","100"}; + + CVAR_REGISTER ( &sk_bigmomma_radius_blast1 );// {"sk_bigmomma_radius_blast1","250"}; + CVAR_REGISTER ( &sk_bigmomma_radius_blast2 );// {"sk_bigmomma_radius_blast2","250"}; + CVAR_REGISTER ( &sk_bigmomma_radius_blast3 );// {"sk_bigmomma_radius_blast3","250"}; + + // Gargantua + CVAR_REGISTER ( &sk_gargantua_health1 );// {"sk_gargantua_health1","0"}; + CVAR_REGISTER ( &sk_gargantua_health2 );// {"sk_gargantua_health2","0"}; + CVAR_REGISTER ( &sk_gargantua_health3 );// {"sk_gargantua_health3","0"}; + + CVAR_REGISTER ( &sk_gargantua_dmg_slash1 );// {"sk_gargantua_dmg_slash1","0"}; + CVAR_REGISTER ( &sk_gargantua_dmg_slash2 );// {"sk_gargantua_dmg_slash2","0"}; + CVAR_REGISTER ( &sk_gargantua_dmg_slash3 );// {"sk_gargantua_dmg_slash3","0"}; + + CVAR_REGISTER ( &sk_gargantua_dmg_fire1 );// {"sk_gargantua_dmg_fire1","0"}; + CVAR_REGISTER ( &sk_gargantua_dmg_fire2 );// {"sk_gargantua_dmg_fire2","0"}; + CVAR_REGISTER ( &sk_gargantua_dmg_fire3 );// {"sk_gargantua_dmg_fire3","0"}; + + CVAR_REGISTER ( &sk_gargantua_dmg_stomp1 );// {"sk_gargantua_dmg_stomp1","0"}; + CVAR_REGISTER ( &sk_gargantua_dmg_stomp2 );// {"sk_gargantua_dmg_stomp2","0"}; + CVAR_REGISTER ( &sk_gargantua_dmg_stomp3 );// {"sk_gargantua_dmg_stomp3","0"}; + + + // Hassassin + CVAR_REGISTER ( &sk_hassassin_health1 );// {"sk_hassassin_health1","0"}; + CVAR_REGISTER ( &sk_hassassin_health2 );// {"sk_hassassin_health2","0"}; + CVAR_REGISTER ( &sk_hassassin_health3 );// {"sk_hassassin_health3","0"}; + + + // Headcrab + CVAR_REGISTER ( &sk_headcrab_health1 );// {"sk_headcrab_health1","0"}; + CVAR_REGISTER ( &sk_headcrab_health2 );// {"sk_headcrab_health2","0"}; + CVAR_REGISTER ( &sk_headcrab_health3 );// {"sk_headcrab_health3","0"}; + + CVAR_REGISTER ( &sk_headcrab_dmg_bite1 );// {"sk_headcrab_dmg_bite1","0"}; + CVAR_REGISTER ( &sk_headcrab_dmg_bite2 );// {"sk_headcrab_dmg_bite2","0"}; + CVAR_REGISTER ( &sk_headcrab_dmg_bite3 );// {"sk_headcrab_dmg_bite3","0"}; + + + // Hgrunt + CVAR_REGISTER ( &sk_hgrunt_health1 );// {"sk_hgrunt_health1","0"}; + CVAR_REGISTER ( &sk_hgrunt_health2 );// {"sk_hgrunt_health2","0"}; + CVAR_REGISTER ( &sk_hgrunt_health3 );// {"sk_hgrunt_health3","0"}; + + CVAR_REGISTER ( &sk_hgrunt_kick1 );// {"sk_hgrunt_kick1","0"}; + CVAR_REGISTER ( &sk_hgrunt_kick2 );// {"sk_hgrunt_kick2","0"}; + CVAR_REGISTER ( &sk_hgrunt_kick3 );// {"sk_hgrunt_kick3","0"}; + + CVAR_REGISTER ( &sk_hgrunt_pellets1 ); + CVAR_REGISTER ( &sk_hgrunt_pellets2 ); + CVAR_REGISTER ( &sk_hgrunt_pellets3 ); + + CVAR_REGISTER ( &sk_hgrunt_gspeed1 ); + CVAR_REGISTER ( &sk_hgrunt_gspeed2 ); + CVAR_REGISTER ( &sk_hgrunt_gspeed3 ); + + // Houndeye + CVAR_REGISTER ( &sk_houndeye_health1 );// {"sk_houndeye_health1","0"}; + CVAR_REGISTER ( &sk_houndeye_health2 );// {"sk_houndeye_health2","0"}; + CVAR_REGISTER ( &sk_houndeye_health3 );// {"sk_houndeye_health3","0"}; + + CVAR_REGISTER ( &sk_houndeye_dmg_blast1 );// {"sk_houndeye_dmg_blast1","0"}; + CVAR_REGISTER ( &sk_houndeye_dmg_blast2 );// {"sk_houndeye_dmg_blast2","0"}; + CVAR_REGISTER ( &sk_houndeye_dmg_blast3 );// {"sk_houndeye_dmg_blast3","0"}; + + + // ISlave + CVAR_REGISTER ( &sk_islave_health1 );// {"sk_islave_health1","0"}; + CVAR_REGISTER ( &sk_islave_health2 );// {"sk_islave_health2","0"}; + CVAR_REGISTER ( &sk_islave_health3 );// {"sk_islave_health3","0"}; + + CVAR_REGISTER ( &sk_islave_dmg_claw1 );// {"sk_islave_dmg_claw1","0"}; + CVAR_REGISTER ( &sk_islave_dmg_claw2 );// {"sk_islave_dmg_claw2","0"}; + CVAR_REGISTER ( &sk_islave_dmg_claw3 );// {"sk_islave_dmg_claw3","0"}; + + CVAR_REGISTER ( &sk_islave_dmg_clawrake1 );// {"sk_islave_dmg_clawrake1","0"}; + CVAR_REGISTER ( &sk_islave_dmg_clawrake2 );// {"sk_islave_dmg_clawrake2","0"}; + CVAR_REGISTER ( &sk_islave_dmg_clawrake3 );// {"sk_islave_dmg_clawrake3","0"}; + + CVAR_REGISTER ( &sk_islave_dmg_zap1 );// {"sk_islave_dmg_zap1","0"}; + CVAR_REGISTER ( &sk_islave_dmg_zap2 );// {"sk_islave_dmg_zap2","0"}; + CVAR_REGISTER ( &sk_islave_dmg_zap3 );// {"sk_islave_dmg_zap3","0"}; + + + // Icthyosaur + CVAR_REGISTER ( &sk_ichthyosaur_health1 );// {"sk_ichthyosaur_health1","0"}; + CVAR_REGISTER ( &sk_ichthyosaur_health2 );// {"sk_ichthyosaur_health2","0"}; + CVAR_REGISTER ( &sk_ichthyosaur_health3 );// {"sk_ichthyosaur_health3","0"}; + + CVAR_REGISTER ( &sk_ichthyosaur_shake1 );// {"sk_ichthyosaur_health3","0"}; + CVAR_REGISTER ( &sk_ichthyosaur_shake2 );// {"sk_ichthyosaur_health3","0"}; + CVAR_REGISTER ( &sk_ichthyosaur_shake3 );// {"sk_ichthyosaur_health3","0"}; + + + + // Leech + CVAR_REGISTER ( &sk_leech_health1 );// {"sk_leech_health1","0"}; + CVAR_REGISTER ( &sk_leech_health2 );// {"sk_leech_health2","0"}; + CVAR_REGISTER ( &sk_leech_health3 );// {"sk_leech_health3","0"}; + + CVAR_REGISTER ( &sk_leech_dmg_bite1 );// {"sk_leech_dmg_bite1","0"}; + CVAR_REGISTER ( &sk_leech_dmg_bite2 );// {"sk_leech_dmg_bite2","0"}; + CVAR_REGISTER ( &sk_leech_dmg_bite3 );// {"sk_leech_dmg_bite3","0"}; + + + // Controller + CVAR_REGISTER ( &sk_controller_health1 ); + CVAR_REGISTER ( &sk_controller_health2 ); + CVAR_REGISTER ( &sk_controller_health3 ); + + CVAR_REGISTER ( &sk_controller_dmgzap1 ); + CVAR_REGISTER ( &sk_controller_dmgzap2 ); + CVAR_REGISTER ( &sk_controller_dmgzap3 ); + + CVAR_REGISTER ( &sk_controller_speedball1 ); + CVAR_REGISTER ( &sk_controller_speedball2 ); + CVAR_REGISTER ( &sk_controller_speedball3 ); + + CVAR_REGISTER ( &sk_controller_dmgball1 ); + CVAR_REGISTER ( &sk_controller_dmgball2 ); + CVAR_REGISTER ( &sk_controller_dmgball3 ); + + // Nihilanth + CVAR_REGISTER ( &sk_nihilanth_health1 );// {"sk_nihilanth_health1","0"}; + CVAR_REGISTER ( &sk_nihilanth_health2 );// {"sk_nihilanth_health2","0"}; + CVAR_REGISTER ( &sk_nihilanth_health3 );// {"sk_nihilanth_health3","0"}; + + CVAR_REGISTER ( &sk_nihilanth_zap1 ); + CVAR_REGISTER ( &sk_nihilanth_zap2 ); + CVAR_REGISTER ( &sk_nihilanth_zap3 ); + + // Scientist + CVAR_REGISTER ( &sk_scientist_health1 );// {"sk_scientist_health1","0"}; + CVAR_REGISTER ( &sk_scientist_health2 );// {"sk_scientist_health2","0"}; + CVAR_REGISTER ( &sk_scientist_health3 );// {"sk_scientist_health3","0"}; + + + // Snark + CVAR_REGISTER ( &sk_snark_health1 );// {"sk_snark_health1","0"}; + CVAR_REGISTER ( &sk_snark_health2 );// {"sk_snark_health2","0"}; + CVAR_REGISTER ( &sk_snark_health3 );// {"sk_snark_health3","0"}; + + CVAR_REGISTER ( &sk_snark_dmg_bite1 );// {"sk_snark_dmg_bite1","0"}; + CVAR_REGISTER ( &sk_snark_dmg_bite2 );// {"sk_snark_dmg_bite2","0"}; + CVAR_REGISTER ( &sk_snark_dmg_bite3 );// {"sk_snark_dmg_bite3","0"}; + + CVAR_REGISTER ( &sk_snark_dmg_pop1 );// {"sk_snark_dmg_pop1","0"}; + CVAR_REGISTER ( &sk_snark_dmg_pop2 );// {"sk_snark_dmg_pop2","0"}; + CVAR_REGISTER ( &sk_snark_dmg_pop3 );// {"sk_snark_dmg_pop3","0"}; + + + + // Zombie + CVAR_REGISTER ( &sk_zombie_health1 );// {"sk_zombie_health1","0"}; + CVAR_REGISTER ( &sk_zombie_health2 );// {"sk_zombie_health3","0"}; + CVAR_REGISTER ( &sk_zombie_health3 );// {"sk_zombie_health3","0"}; + + CVAR_REGISTER ( &sk_zombie_dmg_one_slash1 );// {"sk_zombie_dmg_one_slash1","0"}; + CVAR_REGISTER ( &sk_zombie_dmg_one_slash2 );// {"sk_zombie_dmg_one_slash2","0"}; + CVAR_REGISTER ( &sk_zombie_dmg_one_slash3 );// {"sk_zombie_dmg_one_slash3","0"}; + + CVAR_REGISTER ( &sk_zombie_dmg_both_slash1 );// {"sk_zombie_dmg_both_slash1","0"}; + CVAR_REGISTER ( &sk_zombie_dmg_both_slash2 );// {"sk_zombie_dmg_both_slash2","0"}; + CVAR_REGISTER ( &sk_zombie_dmg_both_slash3 );// {"sk_zombie_dmg_both_slash3","0"}; + + + //Turret + CVAR_REGISTER ( &sk_turret_health1 );// {"sk_turret_health1","0"}; + CVAR_REGISTER ( &sk_turret_health2 );// {"sk_turret_health2","0"}; + CVAR_REGISTER ( &sk_turret_health3 );// {"sk_turret_health3","0"}; + + + // MiniTurret + CVAR_REGISTER ( &sk_miniturret_health1 );// {"sk_miniturret_health1","0"}; + CVAR_REGISTER ( &sk_miniturret_health2 );// {"sk_miniturret_health2","0"}; + CVAR_REGISTER ( &sk_miniturret_health3 );// {"sk_miniturret_health3","0"}; + + + // Sentry Turret + CVAR_REGISTER ( &sk_sentry_health1 );// {"sk_sentry_health1","0"}; + CVAR_REGISTER ( &sk_sentry_health2 );// {"sk_sentry_health2","0"}; + CVAR_REGISTER ( &sk_sentry_health3 );// {"sk_sentry_health3","0"}; + + + // PLAYER WEAPONS + + // Crowbar whack + CVAR_REGISTER ( &sk_plr_crowbar1 );// {"sk_plr_crowbar1","0"}; + CVAR_REGISTER ( &sk_plr_crowbar2 );// {"sk_plr_crowbar2","0"}; + CVAR_REGISTER ( &sk_plr_crowbar3 );// {"sk_plr_crowbar3","0"}; + CVAR_REGISTER ( &sk_plr_crowbarsec1 );// {"sk_plr_crowbar1","0"}; + CVAR_REGISTER ( &sk_plr_crowbarsec2 );// {"sk_plr_crowbar2","0"}; + CVAR_REGISTER ( &sk_plr_crowbarsec3 );// {"sk_plr_crowbar3","0"}; + + // Glock Round + CVAR_REGISTER ( &sk_plr_9mm_bullet1 );// {"sk_plr_9mm_bullet1","0"}; + CVAR_REGISTER ( &sk_plr_9mm_bullet2 );// {"sk_plr_9mm_bullet2","0"}; + CVAR_REGISTER ( &sk_plr_9mm_bullet3 );// {"sk_plr_9mm_bullet3","0"}; + + // MP5 Round + CVAR_REGISTER ( &sk_plr_9mmAR_bullet1 );// {"sk_plr_9mmAR_bullet1","0"}; + CVAR_REGISTER ( &sk_plr_9mmAR_bullet2 );// {"sk_plr_9mmAR_bullet2","0"}; + CVAR_REGISTER ( &sk_plr_9mmAR_bullet3 );// {"sk_plr_9mmAR_bullet3","0"}; + + + // M203 grenade + CVAR_REGISTER ( &sk_plr_9mmAR_grenade1 );// {"sk_plr_9mmAR_grenade1","0"}; + CVAR_REGISTER ( &sk_plr_9mmAR_grenade2 );// {"sk_plr_9mmAR_grenade2","0"}; + CVAR_REGISTER ( &sk_plr_9mmAR_grenade3 );// {"sk_plr_9mmAR_grenade3","0"}; + + + // Shotgun buckshot + CVAR_REGISTER ( &sk_plr_buckshot1 );// {"sk_plr_buckshot1","0"}; + CVAR_REGISTER ( &sk_plr_buckshot2 );// {"sk_plr_buckshot2","0"}; + CVAR_REGISTER ( &sk_plr_buckshot3 );// {"sk_plr_buckshot3","0"}; + + // RPG + CVAR_REGISTER ( &sk_plr_rpg1 );// {"sk_plr_rpg1","0"}; + CVAR_REGISTER ( &sk_plr_rpg2 );// {"sk_plr_rpg2","0"}; + CVAR_REGISTER ( &sk_plr_rpg3 );// {"sk_plr_rpg3","0"}; + + // Hand Grendade + CVAR_REGISTER ( &sk_plr_hand_grenade1 );// {"sk_plr_hand_grenade1","0"}; + CVAR_REGISTER ( &sk_plr_hand_grenade2 );// {"sk_plr_hand_grenade2","0"}; + CVAR_REGISTER ( &sk_plr_hand_grenade3 );// {"sk_plr_hand_grenade3","0"}; + + // WORLD WEAPONS + CVAR_REGISTER ( &sk_12mm_bullet1 );// {"sk_12mm_bullet1","0"}; + CVAR_REGISTER ( &sk_12mm_bullet2 );// {"sk_12mm_bullet2","0"}; + CVAR_REGISTER ( &sk_12mm_bullet3 );// {"sk_12mm_bullet3","0"}; + + CVAR_REGISTER ( &sk_9mmAR_bullet1 );// {"sk_9mm_bullet1","0"}; + CVAR_REGISTER ( &sk_9mmAR_bullet2 );// {"sk_9mm_bullet1","0"}; + CVAR_REGISTER ( &sk_9mmAR_bullet3 );// {"sk_9mm_bullet1","0"}; + + CVAR_REGISTER ( &sk_9mm_bullet1 );// {"sk_9mm_bullet1","0"}; + CVAR_REGISTER ( &sk_9mm_bullet2 );// {"sk_9mm_bullet2","0"}; + CVAR_REGISTER ( &sk_9mm_bullet3 );// {"sk_9mm_bullet3","0"}; + + // HORNET + CVAR_REGISTER ( &sk_hornet_dmg1 );// {"sk_hornet_dmg1","0"}; + CVAR_REGISTER ( &sk_hornet_dmg2 );// {"sk_hornet_dmg2","0"}; + CVAR_REGISTER ( &sk_hornet_dmg3 );// {"sk_hornet_dmg3","0"}; + + // HEALTH/SUIT CHARGE DISTRIBUTION + CVAR_REGISTER ( &sk_suitcharger1 ); + CVAR_REGISTER ( &sk_suitcharger2 ); + CVAR_REGISTER ( &sk_suitcharger3 ); + + CVAR_REGISTER ( &sk_battery1 ); + CVAR_REGISTER ( &sk_battery2 ); + CVAR_REGISTER ( &sk_battery3 ); + + CVAR_REGISTER ( &sk_healthcharger1 ); + CVAR_REGISTER ( &sk_healthcharger2 ); + CVAR_REGISTER ( &sk_healthcharger3 ); + + CVAR_REGISTER ( &sk_healthkit1 ); + CVAR_REGISTER ( &sk_healthkit2 ); + CVAR_REGISTER ( &sk_healthkit3 ); + + // buz + CVAR_REGISTER ( &sk_bighealthkit1 ); + CVAR_REGISTER ( &sk_bighealthkit2 ); + CVAR_REGISTER ( &sk_bighealthkit3 ); + + CVAR_REGISTER ( &sk_painkiller1 ); + CVAR_REGISTER ( &sk_painkiller2 ); + CVAR_REGISTER ( &sk_painkiller3 ); + + CVAR_REGISTER ( &sk_scientist_heal1 ); + CVAR_REGISTER ( &sk_scientist_heal2 ); + CVAR_REGISTER ( &sk_scientist_heal3 ); + +// monster damage adjusters + CVAR_REGISTER ( &sk_monster_head1 ); + CVAR_REGISTER ( &sk_monster_head2 ); + CVAR_REGISTER ( &sk_monster_head3 ); + + CVAR_REGISTER ( &sk_monster_chest1 ); + CVAR_REGISTER ( &sk_monster_chest2 ); + CVAR_REGISTER ( &sk_monster_chest3 ); + + CVAR_REGISTER ( &sk_monster_stomach1 ); + CVAR_REGISTER ( &sk_monster_stomach2 ); + CVAR_REGISTER ( &sk_monster_stomach3 ); + + CVAR_REGISTER ( &sk_monster_arm1 ); + CVAR_REGISTER ( &sk_monster_arm2 ); + CVAR_REGISTER ( &sk_monster_arm3 ); + + CVAR_REGISTER ( &sk_monster_leg1 ); + CVAR_REGISTER ( &sk_monster_leg2 ); + CVAR_REGISTER ( &sk_monster_leg3 ); + +// player damage adjusters + CVAR_REGISTER ( &sk_player_head1 ); + CVAR_REGISTER ( &sk_player_head2 ); + CVAR_REGISTER ( &sk_player_head3 ); + + CVAR_REGISTER ( &sk_player_chest1 ); + CVAR_REGISTER ( &sk_player_chest2 ); + CVAR_REGISTER ( &sk_player_chest3 ); + + CVAR_REGISTER ( &sk_player_stomach1 ); + CVAR_REGISTER ( &sk_player_stomach2 ); + CVAR_REGISTER ( &sk_player_stomach3 ); + + CVAR_REGISTER ( &sk_player_arm1 ); + CVAR_REGISTER ( &sk_player_arm2 ); + CVAR_REGISTER ( &sk_player_arm3 ); + + CVAR_REGISTER ( &sk_player_leg1 ); + CVAR_REGISTER ( &sk_player_leg2 ); + CVAR_REGISTER ( &sk_player_leg3 ); + +// Wargon: Ìíîæèòåëè äàìàãè äëÿ monster_zombie. (1.1) + CVAR_REGISTER ( &sk_zombie_head1 ); + CVAR_REGISTER ( &sk_zombie_head2 ); + CVAR_REGISTER ( &sk_zombie_head3 ); + + CVAR_REGISTER ( &sk_zombie_chest1 ); + CVAR_REGISTER ( &sk_zombie_chest2 ); + CVAR_REGISTER ( &sk_zombie_chest3 ); + + CVAR_REGISTER ( &sk_zombie_stomach1 ); + CVAR_REGISTER ( &sk_zombie_stomach2 ); + CVAR_REGISTER ( &sk_zombie_stomach3 ); + + CVAR_REGISTER ( &sk_zombie_arm1 ); + CVAR_REGISTER ( &sk_zombie_arm2 ); + CVAR_REGISTER ( &sk_zombie_arm3 ); + + CVAR_REGISTER ( &sk_zombie_leg1 ); + CVAR_REGISTER ( &sk_zombie_leg2 ); + CVAR_REGISTER ( &sk_zombie_leg3 ); + + // buz: paranoia entries + CVAR_REGISTER ( &sk_primary_speed1 ); + CVAR_REGISTER ( &sk_primary_speed2 ); + CVAR_REGISTER ( &sk_primary_speed3 ); + + CVAR_REGISTER ( &sk_secondary_speed1 ); + CVAR_REGISTER ( &sk_secondary_speed2 ); + CVAR_REGISTER ( &sk_secondary_speed3 ); + + CVAR_REGISTER ( &sk_plr_aps1 ); + CVAR_REGISTER ( &sk_plr_aps2 ); + CVAR_REGISTER ( &sk_plr_aps3 ); + + CVAR_REGISTER ( &sk_plr_barret1 ); + CVAR_REGISTER ( &sk_plr_barret2 ); + CVAR_REGISTER ( &sk_plr_barret3 ); + + CVAR_REGISTER ( &sk_plr_aks1 ); + CVAR_REGISTER ( &sk_plr_aks2 ); + CVAR_REGISTER ( &sk_plr_aks3 ); + + CVAR_REGISTER ( &sk_plr_ak471 ); + CVAR_REGISTER ( &sk_plr_ak472 ); + CVAR_REGISTER ( &sk_plr_ak473 ); + + CVAR_REGISTER ( &sk_plr_asval1 ); + CVAR_REGISTER ( &sk_plr_asval2 ); + CVAR_REGISTER ( &sk_plr_asval3 ); + + CVAR_REGISTER ( &sk_plr_groza1 ); + CVAR_REGISTER ( &sk_plr_groza2 ); + CVAR_REGISTER ( &sk_plr_groza3 ); + + CVAR_REGISTER ( &sk_plr_rpk1 ); + CVAR_REGISTER ( &sk_plr_rpk2 ); + CVAR_REGISTER ( &sk_plr_rpk3 ); + + CVAR_REGISTER ( &sk_ak_bullet1 ); + CVAR_REGISTER ( &sk_ak_bullet2 ); + CVAR_REGISTER ( &sk_ak_bullet3 ); + + CVAR_REGISTER ( &sk_asval_bullet1 ); + CVAR_REGISTER ( &sk_asval_bullet2 ); + CVAR_REGISTER ( &sk_asval_bullet3 ); + + CVAR_REGISTER ( &sk_groza_bullet1 ); + CVAR_REGISTER ( &sk_groza_bullet2 ); + CVAR_REGISTER ( &sk_groza_bullet3 ); + + CVAR_REGISTER ( &sk_ter_rpk_bullet1 ); + CVAR_REGISTER ( &sk_ter_rpk_bullet2 ); + CVAR_REGISTER ( &sk_ter_rpk_bullet3 ); + + CVAR_REGISTER ( &sk_ter_ak_bullet1 ); + CVAR_REGISTER ( &sk_ter_ak_bullet2 ); + CVAR_REGISTER ( &sk_ter_ak_bullet3 ); + + CVAR_REGISTER ( &sk_glock_bullet1 ); + CVAR_REGISTER ( &sk_glock_bullet2 ); + CVAR_REGISTER ( &sk_glock_bullet3 ); + + CVAR_REGISTER ( &sk_mil_kick1 ); + CVAR_REGISTER ( &sk_mil_kick2 ); + CVAR_REGISTER ( &sk_mil_kick3 ); + + CVAR_REGISTER ( &sk_mil_health1 ); + CVAR_REGISTER ( &sk_mil_health2 ); + CVAR_REGISTER ( &sk_mil_health3 ); + + CVAR_REGISTER ( &sk_alpha_health1 ); + CVAR_REGISTER ( &sk_alpha_health2 ); + CVAR_REGISTER ( &sk_alpha_health3 ); + + CVAR_REGISTER ( &sk_terror_health1 ); + CVAR_REGISTER ( &sk_terror_health2 ); + CVAR_REGISTER ( &sk_terror_health3 ); + + CVAR_REGISTER ( &sk_clone_health1 ); + CVAR_REGISTER ( &sk_clone_health2 ); + CVAR_REGISTER ( &sk_clone_health3 ); + + CVAR_REGISTER ( &sk_clone_health_heavy1 ); + CVAR_REGISTER ( &sk_clone_health_heavy2 ); + CVAR_REGISTER ( &sk_clone_health_heavy3 ); + + CVAR_REGISTER ( &bullet_punch_max ); + CVAR_REGISTER ( &bullet_punch_divide ); + CVAR_REGISTER ( &blast_punch_max ); + CVAR_REGISTER ( &blast_punch_divide ); + +// Wargon: Ïàòðîíû èç ìåðòâûõ âðàæèí. (1.1) + CVAR_REGISTER ( &sk_dead_enemy_ammo1 ); + CVAR_REGISTER ( &sk_dead_enemy_ammo2 ); + CVAR_REGISTER ( &sk_dead_enemy_ammo3 ); + +// END REGISTER CVARS FOR SKILL LEVEL STUFF + + SERVER_COMMAND( "exec skill.cfg\n" ); + + // Yes in the Xash3D we can register messages here + LinkUserMessages(); + + if( g_fPhysicInitialized && IS_DEDICATED_SERVER( )) + { + COM_InitMatdef(); + SV_InitMaterials(); + } +} + +void GameDLLShutdown( void ) +{ +} \ No newline at end of file diff --git a/dlls/game.h b/dlls/game.h new file mode 100644 index 0000000..d486545 --- /dev/null +++ b/dlls/game.h @@ -0,0 +1,52 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef GAME_H +#define GAME_H + +extern void GameDLLInit( void ); +extern void GameDLLShutdown( void ); + +extern cvar_t displaysoundlist; + +// multiplayer server rules +extern cvar_t teamplay; +extern cvar_t fraglimit; +extern cvar_t timelimit; +extern cvar_t friendlyfire; +extern cvar_t falldamage; +extern cvar_t weaponstay; +extern cvar_t forcerespawn; +extern cvar_t flashlight; +extern cvar_t aimcrosshair; +extern cvar_t decalfrequency; +extern cvar_t teamlist; +extern cvar_t teamoverride; +extern cvar_t defaultteam; +extern cvar_t allowmonsters; + +// Engine Cvars +extern cvar_t *g_psv_gravity; +extern cvar_t *g_psv_aim; +extern cvar_t *g_footsteps; +extern cvar_t *g_precache_meshes; + +extern cvar_t weapon_x; +extern cvar_t weapon_y; +extern cvar_t weapon_z; + +void LinkUserMessages( void ); + +#endif // GAME_H \ No newline at end of file diff --git a/dlls/gamerules.cpp b/dlls/gamerules.cpp new file mode 100644 index 0000000..df424a0 --- /dev/null +++ b/dlls/gamerules.cpp @@ -0,0 +1,364 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// GameRules.cpp +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "teamplay_gamerules.h" +#include "skill.h" +#include "game.h" + +extern edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ); + +DLL_GLOBAL CGameRules* g_pGameRules = NULL; +extern DLL_GLOBAL BOOL g_fGameOver; +extern int gmsgDeathMsg; // client dll messages +extern int gmsgMOTD; + +int g_teamplay = 0; + +//========================================================= +//========================================================= +BOOL CGameRules :: CanHaveAmmo( CBasePlayer *pPlayer, const char *pszAmmoName, int iMaxCarry ) +{ + int iAmmoIndex; + + if ( pszAmmoName ) + { + iAmmoIndex = pPlayer->GetAmmoIndex( pszAmmoName ); + + if ( iAmmoIndex > -1 ) + { + if ( pPlayer->AmmoInventory( iAmmoIndex ) < iMaxCarry ) + { + // player has room for more of this type of ammo + return TRUE; + } + } + } + + return FALSE; +} + +//========================================================= +//========================================================= +edict_t *CGameRules :: GetPlayerSpawnSpot( CBasePlayer *pPlayer ) +{ + edict_t *pentSpawnSpot = EntSelectSpawnPoint( pPlayer ); + + pPlayer->pev->origin = VARS(pentSpawnSpot)->origin + Vector(0,0,1); + pPlayer->pev->v_angle = g_vecZero; + pPlayer->pev->velocity = g_vecZero; + pPlayer->pev->angles = VARS(pentSpawnSpot)->angles; + pPlayer->pev->punchangle = g_vecZero; + pPlayer->pev->fixangle = TRUE; + + return pentSpawnSpot; +} + +//========================================================= +//========================================================= +BOOL CGameRules::CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ + // only living players can have items + if ( pPlayer->pev->deadflag != DEAD_NO ) + return FALSE; + + if ( pWeapon->pszAmmo1() ) + { + if ( !CanHaveAmmo( pPlayer, pWeapon->pszAmmo1(), pWeapon->iMaxAmmo1() ) ) + { + // we can't carry anymore ammo for this gun. We can only + // have the gun if we aren't already carrying one of this type + if ( pPlayer->HasPlayerItem( pWeapon ) ) + { + return FALSE; + } + } + } + else + { + // weapon doesn't use ammo, don't take another if you already have it. + if ( pPlayer->HasPlayerItem( pWeapon ) ) + { + return FALSE; + } + } + + // note: will fall through to here if GetItemInfo doesn't fill the struct! + return TRUE; +} + +//========================================================= +// load the SkillData struct with the proper values based on the skill level. +//========================================================= +void CGameRules::RefreshSkillData ( void ) +{ + int iSkill; + + iSkill = (int)CVAR_GET_FLOAT("skill"); + g_iSkillLevel = iSkill; + + if ( iSkill < 1 ) + { + iSkill = 1; + } + else if ( iSkill > 3 ) + { + iSkill = 3; + } + + gSkillData.iSkillLevel = iSkill; + + ALERT ( at_debug, "\nGAME SKILL LEVEL:%d\n",iSkill ); + + //Agrunt + gSkillData.agruntHealth = GetSkillCvar( "sk_agrunt_health" ); + gSkillData.agruntDmgPunch = GetSkillCvar( "sk_agrunt_dmg_punch"); + + // Apache + gSkillData.apacheHealth = GetSkillCvar( "sk_apache_health"); + + // Barney + gSkillData.barneyHealth = GetSkillCvar( "sk_barney_health"); + + // Big Momma + gSkillData.bigmommaHealthFactor = GetSkillCvar( "sk_bigmomma_health_factor" ); + gSkillData.bigmommaDmgSlash = GetSkillCvar( "sk_bigmomma_dmg_slash" ); + gSkillData.bigmommaDmgBlast = GetSkillCvar( "sk_bigmomma_dmg_blast" ); + gSkillData.bigmommaRadiusBlast = GetSkillCvar( "sk_bigmomma_radius_blast" ); + + // Bullsquid + gSkillData.bullsquidHealth = GetSkillCvar( "sk_bullsquid_health"); + gSkillData.bullsquidDmgBite = GetSkillCvar( "sk_bullsquid_dmg_bite"); + gSkillData.bullsquidDmgWhip = GetSkillCvar( "sk_bullsquid_dmg_whip"); + gSkillData.bullsquidDmgSpit = GetSkillCvar( "sk_bullsquid_dmg_spit"); + + // Gargantua + gSkillData.gargantuaHealth = GetSkillCvar( "sk_gargantua_health"); + gSkillData.gargantuaDmgSlash = GetSkillCvar( "sk_gargantua_dmg_slash"); + gSkillData.gargantuaDmgFire = GetSkillCvar( "sk_gargantua_dmg_fire"); + gSkillData.gargantuaDmgStomp = GetSkillCvar( "sk_gargantua_dmg_stomp"); + + // Hassassin + gSkillData.hassassinHealth = GetSkillCvar( "sk_hassassin_health"); + + // Headcrab + gSkillData.headcrabHealth = GetSkillCvar( "sk_headcrab_health"); + gSkillData.headcrabDmgBite = GetSkillCvar( "sk_headcrab_dmg_bite"); + + // Hgrunt + gSkillData.hgruntHealth = GetSkillCvar( "sk_hgrunt_health"); + gSkillData.hgruntDmgKick = GetSkillCvar( "sk_hgrunt_kick"); + gSkillData.hgruntShotgunPellets = GetSkillCvar( "sk_hgrunt_pellets"); + gSkillData.hgruntGrenadeSpeed = GetSkillCvar( "sk_hgrunt_gspeed"); + + // Houndeye + gSkillData.houndeyeHealth = GetSkillCvar( "sk_houndeye_health"); + gSkillData.houndeyeDmgBlast = GetSkillCvar( "sk_houndeye_dmg_blast"); + + // ISlave + gSkillData.slaveHealth = GetSkillCvar( "sk_islave_health"); + gSkillData.slaveDmgClaw = GetSkillCvar( "sk_islave_dmg_claw"); + gSkillData.slaveDmgClawrake = GetSkillCvar( "sk_islave_dmg_clawrake"); + gSkillData.slaveDmgZap = GetSkillCvar( "sk_islave_dmg_zap"); + + // Icthyosaur + gSkillData.ichthyosaurHealth = GetSkillCvar( "sk_ichthyosaur_health"); + gSkillData.ichthyosaurDmgShake = GetSkillCvar( "sk_ichthyosaur_shake"); + + // Leech + gSkillData.leechHealth = GetSkillCvar( "sk_leech_health"); + + gSkillData.leechDmgBite = GetSkillCvar( "sk_leech_dmg_bite"); + + // Controller + gSkillData.controllerHealth = GetSkillCvar( "sk_controller_health"); + gSkillData.controllerDmgZap = GetSkillCvar( "sk_controller_dmgzap"); + gSkillData.controllerSpeedBall = GetSkillCvar( "sk_controller_speedball"); + gSkillData.controllerDmgBall = GetSkillCvar( "sk_controller_dmgball"); + + // Nihilanth + gSkillData.nihilanthHealth = GetSkillCvar( "sk_nihilanth_health"); + gSkillData.nihilanthZap = GetSkillCvar( "sk_nihilanth_zap"); + + // Scientist + gSkillData.scientistHealth = GetSkillCvar( "sk_scientist_health"); + + // Snark + gSkillData.snarkHealth = GetSkillCvar( "sk_snark_health"); + gSkillData.snarkDmgBite = GetSkillCvar( "sk_snark_dmg_bite"); + gSkillData.snarkDmgPop = GetSkillCvar( "sk_snark_dmg_pop"); + + // Zombie + gSkillData.zombieHealth = GetSkillCvar( "sk_zombie_health"); + gSkillData.zombieDmgOneSlash = GetSkillCvar( "sk_zombie_dmg_one_slash"); + gSkillData.zombieDmgBothSlash = GetSkillCvar( "sk_zombie_dmg_both_slash"); + + //Turret + gSkillData.turretHealth = GetSkillCvar( "sk_turret_health"); + + // MiniTurret + gSkillData.miniturretHealth = GetSkillCvar( "sk_miniturret_health"); + + // Sentry Turret + gSkillData.sentryHealth = GetSkillCvar( "sk_sentry_health"); + +// PLAYER WEAPONS + + // Crowbar whack + gSkillData.plrDmgCrowbar = GetSkillCvar( "sk_plr_crowbar"); + gSkillData.plrDmgCrowbarSec = GetSkillCvar( "sk_plr_crowbarsec"); // buz + + // Glock Round + gSkillData.plrDmg9MM = GetSkillCvar( "sk_plr_9mm_bullet"); + + // MP5 Round + gSkillData.plrDmgMP5 = GetSkillCvar( "sk_plr_9mmAR_bullet"); + + // M203 grenade + gSkillData.plrDmgM203Grenade = GetSkillCvar( "sk_plr_9mmAR_grenade"); + + // Shotgun buckshot + gSkillData.plrDmgBuckshot = GetSkillCvar( "sk_plr_buckshot"); + + // RPG + gSkillData.plrDmgRPG = GetSkillCvar( "sk_plr_rpg"); + + // Hand Grendade + gSkillData.plrDmgHandGrenade = GetSkillCvar( "sk_plr_hand_grenade"); + +///////// +// buz: paranoia weapons + gSkillData.plrDmgAps = GetSkillCvar( "sk_plr_aps"); + gSkillData.plrDmgBarret = GetSkillCvar( "sk_plr_barret"); + gSkillData.plrDmgAks = GetSkillCvar( "sk_plr_aks"); + gSkillData.plrDmgAk47 = GetSkillCvar( "sk_plr_ak47"); + gSkillData.plrDmgAsval = GetSkillCvar( "sk_plr_asval"); + gSkillData.plrDmgRpk = GetSkillCvar( "sk_plr_rpk"); + gSkillData.plrDmgGroza = GetSkillCvar( "sk_plr_groza"); + + + // MONSTER WEAPONS + gSkillData.monDmg12MM = GetSkillCvar( "sk_12mm_bullet"); + gSkillData.monDmgMP5 = GetSkillCvar ("sk_9mmAR_bullet" ); + gSkillData.monDmg9MM = GetSkillCvar( "sk_9mm_bullet"); + gSkillData.monDmgAK = GetSkillCvar( "sk_ak_bullet"); // buz + gSkillData.monDmgAsval = GetSkillCvar( "sk_asval_bullet"); // buz + gSkillData.monDmgGroza = GetSkillCvar( "sk_groza_bullet"); // buz + gSkillData.monTerDmgRPK = GetSkillCvar( "sk_ter_rpk_bullet"); // buz + gSkillData.monTerDmgAK = GetSkillCvar( "sk_ter_ak_bullet"); // buz + gSkillData.monDmgGlock = GetSkillCvar( "sk_glock_bullet"); // buz + + gSkillData.MilDmgKick = GetSkillCvar( "sk_mil_kick"); // buz + gSkillData.milHealth = GetSkillCvar( "sk_mil_health"); // buz + gSkillData.alphaHealth = GetSkillCvar( "sk_alpha_health"); // buz + gSkillData.terrorHealth = GetSkillCvar( "sk_terror_health"); // buz + gSkillData.cloneHealth = GetSkillCvar( "sk_clone_health"); // buz + gSkillData.cloneHealthHeavy = GetSkillCvar( "sk_clone_health_heavy"); // buz + + // MONSTER HORNET + gSkillData.monDmgHornet = GetSkillCvar( "sk_hornet_dmg"); + + // HEALTH/CHARGE + gSkillData.suitchargerCapacity = GetSkillCvar( "sk_suitcharger" ); + gSkillData.batteryCapacity = GetSkillCvar( "sk_battery" ); + gSkillData.healthchargerCapacity = GetSkillCvar ( "sk_healthcharger" ); + gSkillData.healthkitCapacity = GetSkillCvar ( "sk_healthkit" ); + gSkillData.bighealthkitCapacity = GetSkillCvar ( "sk_bighealthkit" ); // buz + gSkillData.painkillerCapacity = GetSkillCvar ( "sk_painkiller" ); // buz + gSkillData.scientistHeal = GetSkillCvar ( "sk_scientist_heal" ); + + // monster damage adj + gSkillData.monHead = GetSkillCvar( "sk_monster_head" ); + gSkillData.monChest = GetSkillCvar( "sk_monster_chest" ); + gSkillData.monStomach = GetSkillCvar( "sk_monster_stomach" ); + gSkillData.monLeg = GetSkillCvar( "sk_monster_leg" ); + gSkillData.monArm = GetSkillCvar( "sk_monster_arm" ); + + // player damage adj + gSkillData.plrHead = GetSkillCvar( "sk_player_head" ); + gSkillData.plrChest = GetSkillCvar( "sk_player_chest" ); + gSkillData.plrStomach = GetSkillCvar( "sk_player_stomach" ); + gSkillData.plrLeg = GetSkillCvar( "sk_player_leg" ); + gSkillData.plrArm = GetSkillCvar( "sk_player_arm" ); + + // Wargon: Ìíîæèòåëè äàìàãè äëÿ monster_zombie. (1.1) + gSkillData.zomHead = GetSkillCvar( "sk_zombie_head" ); + gSkillData.zomChest = GetSkillCvar( "sk_zombie_chest" ); + gSkillData.zomStomach = GetSkillCvar( "sk_zombie_stomach" ); + gSkillData.zomLeg = GetSkillCvar( "sk_zombie_leg" ); + gSkillData.zomArm = GetSkillCvar( "sk_zombie_arm" ); + + gSkillData.superofficerHealth = GetSkillCvar( "sk_superofficer_health" ); + gSkillData.superofficerDmgKick = GetSkillCvar( "sk_superofficer_dmg_kick" ); + gSkillData.superofficerDmgPunch = GetSkillCvar( "sk_superofficer_dmg_punch" ); + gSkillData.superofficerDmgBlood = GetSkillCvar( "sk_superofficer_dmg_blood" ); + + // buz: maxspeeds + gSkillData.plrPrimaryMaxSpeed = GetSkillCvar( "sk_primary_speed" ); + gSkillData.plrSecondaryMaxSpeed = GetSkillCvar( "sk_secondary_speed" ); + + // Wargon: Ïàòðîíû èç ìåðòâûõ âðàæèí. (1.1) + gSkillData.maxDeadEnemyAmmo = GetSkillCvar( "sk_dead_enemy_ammo" ); +} + +//========================================================= +// instantiate the proper game rules object +//========================================================= + +CGameRules *InstallGameRules( void ) +{ + SERVER_COMMAND( "exec game.cfg\n" ); + SERVER_EXECUTE( ); + + if ( !gpGlobals->deathmatch ) + { + // generic half-life + g_teamplay = 0; + return new CHalfLifeRules; + } + else + { + if ( teamplay.value > 0 ) + { + // teamplay + + g_teamplay = 1; + return new CHalfLifeTeamplay; + } + if ((int)gpGlobals->deathmatch == 1) + { + // vanilla deathmatch + g_teamplay = 0; + return new CHalfLifeMultiplay; + } + else + { + // vanilla deathmatch?? + g_teamplay = 0; + return new CHalfLifeMultiplay; + } + } +} + + + diff --git a/dlls/gamerules.h b/dlls/gamerules.h new file mode 100644 index 0000000..a66d415 --- /dev/null +++ b/dlls/gamerules.h @@ -0,0 +1,363 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// GameRules +//========================================================= + +//LRC // buz :) +#define GAME_NAME "Paranoia 2: Savior ( ver.1.51 )" + +//#include "weapons.h" +//#include "items.h" +class CBasePlayerItem; +class CBasePlayer; +class CItem; +class CBasePlayerAmmo; + +// weapon respawning return codes +enum +{ + GR_NONE = 0, + + GR_WEAPON_RESPAWN_YES, + GR_WEAPON_RESPAWN_NO, + + GR_AMMO_RESPAWN_YES, + GR_AMMO_RESPAWN_NO, + + GR_ITEM_RESPAWN_YES, + GR_ITEM_RESPAWN_NO, + + GR_PLR_DROP_GUN_ALL, + GR_PLR_DROP_GUN_ACTIVE, + GR_PLR_DROP_GUN_NO, + + GR_PLR_DROP_AMMO_ALL, + GR_PLR_DROP_AMMO_ACTIVE, + GR_PLR_DROP_AMMO_NO, +}; + +// Player relationship return codes +enum +{ + GR_NOTTEAMMATE = 0, + GR_TEAMMATE, + GR_ENEMY, + GR_ALLY, + GR_NEUTRAL, +}; + +class CGameRules +{ +public: + virtual void RefreshSkillData( void );// fill skill data struct with proper values + virtual void Think( void ) = 0;// GR_Think - runs every server frame, should handle any timer tasks, periodic events, etc. + virtual BOOL IsAllowedToSpawn( CBaseEntity *pEntity ) = 0; // Can this item spawn (eg monsters don't spawn in deathmatch). + + virtual BOOL FAllowFlashlight( void ) = 0;// Are players allowed to switch on their flashlight? + virtual BOOL FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) = 0;// should the player switch to this weapon? + virtual BOOL GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) = 0;// I can't use this weapon anymore, get me the next best one. + +// Functions to verify the single/multiplayer status of a game + virtual BOOL IsMultiplayer( void ) = 0;// is this a multiplayer game? (either coop or deathmatch) + virtual BOOL IsDeathmatch( void ) = 0;//is this a deathmatch game? + virtual BOOL IsTeamplay( void ) { return FALSE; };// is this deathmatch game being played with team rules? + virtual BOOL IsCoOp( void ) = 0;// is this a coop game? + virtual const char *GetGameDescription( void ) { return GAME_NAME; } // this is the game name that gets seen in the server browser + +// Client connection/disconnection + virtual BOOL ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) = 0;// a client just connected to the server (player hasn't spawned yet) + virtual void InitHUD( CBasePlayer *pl ) = 0; // the client dll is ready for updating + virtual void ClientDisconnected( edict_t *pClient ) = 0;// a client just disconnected from the server + virtual void UpdateGameMode( CBasePlayer *pPlayer ) {} // the client needs to be informed of the current game mode + +// Client damage rules + virtual float FlPlayerFallDamage( CBasePlayer *pPlayer ) = 0;// this client just hit the ground after a fall. How much damage? + virtual BOOL FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ) {return TRUE;};// can this player take damage from this attacker? + virtual BOOL ShouldAutoAim( CBasePlayer *pPlayer, edict_t *target ) { return TRUE; } + +// Client spawn/respawn control + virtual void PlayerSpawn( CBasePlayer *pPlayer ) = 0;// called by CBasePlayer::Spawn just before releasing player into the game + virtual void PlayerThink( CBasePlayer *pPlayer ) = 0; // called by CBasePlayer::PreThink every frame, before physics are run and after keys are accepted + virtual BOOL FPlayerCanRespawn( CBasePlayer *pPlayer ) = 0;// is this player allowed to respawn now? + virtual float FlPlayerSpawnTime( CBasePlayer *pPlayer ) = 0;// When in the future will this player be able to spawn? + virtual edict_t *GetPlayerSpawnSpot( CBasePlayer *pPlayer );// Place this player on their spawnspot and face them the proper direction. + + virtual BOOL AllowAutoTargetCrosshair( void ) { return TRUE; }; + virtual BOOL ClientCommand( CBasePlayer *pPlayer, const char *pcmd ) { return FALSE; }; // handles the user commands; returns TRUE if command handled properly + virtual void ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ) {} // the player has changed userinfo; can change it now + +// Client kills/scoring + virtual int IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) = 0;// how many points do I award whoever kills this player? + virtual void PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) = 0;// Called each time a player dies + virtual void DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor )= 0;// Call this from within a GameRules class to report an obituary. +// Weapon retrieval + virtual BOOL CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon );// The player is touching an CBasePlayerItem, do I give it to him? + virtual void PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) = 0;// Called each time a player picks up a weapon from the ground + +// Weapon spawn/respawn control + virtual int WeaponShouldRespawn( CBasePlayerItem *pWeapon ) = 0;// should this weapon respawn? + virtual float FlWeaponRespawnTime( CBasePlayerItem *pWeapon ) = 0;// when may this weapon respawn? + virtual float FlWeaponTryRespawn( CBasePlayerItem *pWeapon ) = 0; // can i respawn now, and if not, when should i try again? + virtual Vector VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ) = 0;// where in the world should this weapon respawn? + +// Item retrieval + virtual BOOL CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ) = 0;// is this player allowed to take this item? + virtual void PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ) = 0;// call each time a player picks up an item (battery, healthkit, longjump) + +// Item spawn/respawn control + virtual int ItemShouldRespawn( CItem *pItem ) = 0;// Should this item respawn? + virtual float FlItemRespawnTime( CItem *pItem ) = 0;// when may this item respawn? + virtual Vector VecItemRespawnSpot( CItem *pItem ) = 0;// where in the world should this item respawn? + +// Ammo retrieval + virtual BOOL CanHaveAmmo( CBasePlayer *pPlayer, const char *pszAmmoName, int iMaxCarry );// can this player take more of this ammo? + virtual void PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ) = 0;// called each time a player picks up some ammo in the world + +// Ammo spawn/respawn control + virtual int AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ) = 0;// should this ammo item respawn? + virtual float FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ) = 0;// when should this ammo item respawn? + virtual Vector VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ) = 0;// where in the world should this ammo item respawn? + // by default, everything spawns + +// Healthcharger respawn control + virtual float FlHealthChargerRechargeTime( void ) = 0;// how long until a depleted HealthCharger recharges itself? + virtual float FlHEVChargerRechargeTime( void ) { return 0; }// how long until a depleted HealthCharger recharges itself? + +// What happens to a dead player's weapons + virtual int DeadPlayerWeapons( CBasePlayer *pPlayer ) = 0;// what do I do with a player's weapons when he's killed? + +// What happens to a dead player's ammo + virtual int DeadPlayerAmmo( CBasePlayer *pPlayer ) = 0;// Do I drop ammo when the player dies? How much? + +// Teamplay stuff + virtual const char *GetTeamID( CBaseEntity *pEntity ) = 0;// what team is this entity on? + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) = 0;// What is the player's relationship with this entity? + virtual int GetTeamIndex( const char *pTeamName ) { return -1; } + virtual const char *GetIndexedTeamName( int teamIndex ) { return ""; } + virtual BOOL IsValidTeam( const char *pTeamName ) { return TRUE; } + virtual void ChangePlayerTeam( CBasePlayer *pPlayer, const char *pTeamName, BOOL bKill, BOOL bGib ) {} + virtual const char *SetDefaultPlayerTeam( CBasePlayer *pPlayer ) { return ""; } + +// Sounds + virtual BOOL PlayTextureSounds( void ) { return TRUE; } + virtual BOOL PlayFootstepSounds( CBasePlayer *pl, float fvol ) { return TRUE; } + +// Monsters + virtual BOOL FAllowMonsters( void ) = 0;//are monsters allowed + + // Immediately end a multiplayer game + virtual void EndMultiplayerGame( void ) {} +}; + +extern CGameRules *InstallGameRules( void ); + + +//========================================================= +// CHalfLifeRules - rules for the single player Half-Life +// game. +//========================================================= +class CHalfLifeRules : public CGameRules +{ +public: + CHalfLifeRules ( void ); + +// GR_Think + virtual void Think( void ); + virtual BOOL IsAllowedToSpawn( CBaseEntity *pEntity ); + virtual BOOL FAllowFlashlight( void ) { return TRUE; }; + + virtual BOOL FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + virtual BOOL GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ); + +// Functions to verify the single/multiplayer status of a game + virtual BOOL IsMultiplayer( void ); + virtual BOOL IsDeathmatch( void ); + virtual BOOL IsCoOp( void ); + +// Client connection/disconnection + virtual BOOL ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ); + virtual void InitHUD( CBasePlayer *pl ); // the client dll is ready for updating + virtual void ClientDisconnected( edict_t *pClient ); + +// Client damage rules + virtual float FlPlayerFallDamage( CBasePlayer *pPlayer ); + +// Client spawn/respawn control + virtual void PlayerSpawn( CBasePlayer *pPlayer ); + virtual void PlayerThink( CBasePlayer *pPlayer ); + virtual BOOL FPlayerCanRespawn( CBasePlayer *pPlayer ); + virtual float FlPlayerSpawnTime( CBasePlayer *pPlayer ); + + virtual BOOL AllowAutoTargetCrosshair( void ); + +// Client kills/scoring + virtual int IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ); + virtual void PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ); + virtual void DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ); + +// Weapon retrieval + virtual void PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + +// Weapon spawn/respawn control + virtual int WeaponShouldRespawn( CBasePlayerItem *pWeapon ); + virtual float FlWeaponRespawnTime( CBasePlayerItem *pWeapon ); + virtual float FlWeaponTryRespawn( CBasePlayerItem *pWeapon ); + virtual Vector VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ); + +// Item retrieval + virtual BOOL CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ); + virtual void PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ); + +// Item spawn/respawn control + virtual int ItemShouldRespawn( CItem *pItem ); + virtual float FlItemRespawnTime( CItem *pItem ); + virtual Vector VecItemRespawnSpot( CItem *pItem ); + +// Ammo retrieval + virtual void PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ); + +// Ammo spawn/respawn control + virtual int AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ); + virtual float FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ); + virtual Vector VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ); + +// Healthcharger respawn control + virtual float FlHealthChargerRechargeTime( void ); + +// What happens to a dead player's weapons + virtual int DeadPlayerWeapons( CBasePlayer *pPlayer ); + +// What happens to a dead player's ammo + virtual int DeadPlayerAmmo( CBasePlayer *pPlayer ); + +// Monsters + virtual BOOL FAllowMonsters( void ); + +// Teamplay stuff + virtual const char *GetTeamID( CBaseEntity *pEntity ) {return "";}; + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ); +}; + +//========================================================= +// CHalfLifeMultiplay - rules for the basic half life multiplayer +// competition +//========================================================= +class CHalfLifeMultiplay : public CGameRules +{ +public: + CHalfLifeMultiplay(); + +// GR_Think + virtual void Think( void ); + virtual void RefreshSkillData( void ); + virtual BOOL IsAllowedToSpawn( CBaseEntity *pEntity ); + virtual BOOL FAllowFlashlight( void ); + + virtual BOOL FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + virtual BOOL GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ); + +// Functions to verify the single/multiplayer status of a game + virtual BOOL IsMultiplayer( void ); + virtual BOOL IsDeathmatch( void ); + virtual BOOL IsCoOp( void ); + +// Client connection/disconnection + // If ClientConnected returns FALSE, the connection is rejected and the user is provided the reason specified in + // svRejectReason + // Only the client's name and remote address are provided to the dll for verification. + virtual BOOL ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ); + virtual void InitHUD( CBasePlayer *pl ); // the client dll is ready for updating + virtual void ClientDisconnected( edict_t *pClient ); + virtual void UpdateGameMode( CBasePlayer *pPlayer ); // the client needs to be informed of the current game mode + +// Client damage rules + virtual float FlPlayerFallDamage( CBasePlayer *pPlayer ); + virtual BOOL FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ); + +// Client spawn/respawn control + virtual void PlayerSpawn( CBasePlayer *pPlayer ); + virtual void PlayerThink( CBasePlayer *pPlayer ); + virtual BOOL FPlayerCanRespawn( CBasePlayer *pPlayer ); + virtual float FlPlayerSpawnTime( CBasePlayer *pPlayer ); + virtual edict_t *GetPlayerSpawnSpot( CBasePlayer *pPlayer ); + + virtual BOOL AllowAutoTargetCrosshair( void ); + virtual BOOL ClientCommand( CBasePlayer *pPlayer, const char *pcmd ); + +// Client kills/scoring + virtual int IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ); + virtual void PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ); + virtual void DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ); + +// Weapon retrieval + virtual void PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ); + virtual BOOL CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon );// The player is touching an CBasePlayerItem, do I give it to him? + +// Weapon spawn/respawn control + virtual int WeaponShouldRespawn( CBasePlayerItem *pWeapon ); + virtual float FlWeaponRespawnTime( CBasePlayerItem *pWeapon ); + virtual float FlWeaponTryRespawn( CBasePlayerItem *pWeapon ); + virtual Vector VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ); + +// Item retrieval + virtual BOOL CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ); + virtual void PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ); + +// Item spawn/respawn control + virtual int ItemShouldRespawn( CItem *pItem ); + virtual float FlItemRespawnTime( CItem *pItem ); + virtual Vector VecItemRespawnSpot( CItem *pItem ); + +// Ammo retrieval + virtual void PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ); + +// Ammo spawn/respawn control + virtual int AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ); + virtual float FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ); + virtual Vector VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ); + +// Healthcharger respawn control + virtual float FlHealthChargerRechargeTime( void ); + virtual float FlHEVChargerRechargeTime( void ); + +// What happens to a dead player's weapons + virtual int DeadPlayerWeapons( CBasePlayer *pPlayer ); + +// What happens to a dead player's ammo + virtual int DeadPlayerAmmo( CBasePlayer *pPlayer ); + +// Teamplay stuff + virtual const char *GetTeamID( CBaseEntity *pEntity ) {return "";} + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ); + + virtual BOOL PlayTextureSounds( void ) { return FALSE; } + virtual BOOL PlayFootstepSounds( CBasePlayer *pl, float fvol ); + +// Monsters + virtual BOOL FAllowMonsters( void ); + + // Immediately end a multiplayer game + virtual void EndMultiplayerGame( void ) { GoToIntermission(); } + +protected: + virtual void ChangeLevel( void ); + virtual void GoToIntermission( void ); + float m_flIntermissionEndTime; + BOOL m_iEndIntermissionButtonHit; + void SendMOTDToClient( edict_t *client ); +}; + +extern DLL_GLOBAL CGameRules* g_pGameRules; diff --git a/dlls/gargantua.cpp b/dlls/gargantua.cpp new file mode 100644 index 0000000..9e5fa8d --- /dev/null +++ b/dlls/gargantua.cpp @@ -0,0 +1,1419 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef OEM_BUILD + +//========================================================= +// Gargantua +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "nodes.h" +#include "monsters.h" +#include "schedule.h" +#include "customentity.h" +#include "weapons.h" +#include "effects.h" +#include "soundent.h" +#include "decals.h" +#include "explode.h" +#include "func_break.h" +#include "scripted.h" + +//========================================================= +// Gargantua Monster +//========================================================= +const float GARG_ATTACKDIST = 80.0; + +// Garg animation events +#define GARG_AE_SLASH_LEFT 1 +//#define GARG_AE_BEAM_ATTACK_RIGHT 2 // No longer used +#define GARG_AE_LEFT_FOOT 3 +#define GARG_AE_RIGHT_FOOT 4 +#define GARG_AE_STOMP 5 +#define GARG_AE_BREATHE 6 + + +// Gargantua is immune to any damage but this +#define GARG_DAMAGE (DMG_ENERGYBEAM|DMG_CRUSH|DMG_MORTAR|DMG_BLAST) +#define GARG_EYE_SPRITE_NAME "sprites/gargeye1.spr" +#define GARG_BEAM_SPRITE_NAME "sprites/xbeam3.spr" +#define GARG_BEAM_SPRITE2 "sprites/xbeam3.spr" +#define GARG_STOMP_SPRITE_NAME "sprites/gargeye1.spr" +#define GARG_STOMP_BUZZ_SOUND "weapons/mine_charge.wav" +#define GARG_FLAME_LENGTH 330 +#define GARG_GIB_MODEL "models/metalplategibs.mdl" + +#define ATTN_GARG (ATTN_NORM) + +#define STOMP_SPRITE_COUNT 10 + +int gStompSprite = 0, gGargGibModel = 0; +void SpawnExplosion( Vector center, float randomRange, float time, int magnitude ); + +class CSmoker; + +// Spiral Effect +class CSpiral : public CBaseEntity +{ +public: + void Spawn( void ); + void Think( void ); + int ObjectCaps( void ) { return FCAP_DONT_SAVE; } + static CSpiral *Create( const Vector &origin, float height, float radius, float duration ); +}; +LINK_ENTITY_TO_CLASS( streak_spiral, CSpiral ); + + +class CStomp : public CBaseEntity +{ +public: + void Spawn( void ); + void Think( void ); + static CStomp *StompCreate( const Vector &origin, const Vector &end, float speed ); + +private: +// UNDONE: re-use this sprite list instead of creating new ones all the time +// CSprite *m_pSprites[ STOMP_SPRITE_COUNT ]; +}; + +LINK_ENTITY_TO_CLASS( garg_stomp, CStomp ); +CStomp *CStomp::StompCreate( const Vector &origin, const Vector &end, float speed ) +{ + CStomp *pStomp = GetClassPtr( (CStomp *)NULL ); + + pStomp->pev->origin = origin; + Vector dir = (end - origin); + pStomp->pev->scale = dir.Length(); + pStomp->pev->movedir = dir.Normalize(); + pStomp->pev->speed = speed; + pStomp->Spawn(); + + return pStomp; +} + +void CStomp::Spawn( void ) +{ + SetNextThink( 0 ); + pev->classname = MAKE_STRING("garg_stomp"); + pev->dmgtime = gpGlobals->time; + + pev->framerate = 30; + pev->model = MAKE_STRING(GARG_STOMP_SPRITE_NAME); + pev->rendermode = kRenderTransTexture; + pev->renderamt = 0; + EMIT_SOUND_DYN( edict(), CHAN_BODY, GARG_STOMP_BUZZ_SOUND, 1, ATTN_NORM, 0, PITCH_NORM * 0.55); +} + + +#define STOMP_INTERVAL 0.025 + +void CStomp::Think( void ) +{ + TraceResult tr; + + SetNextThink( 0.1 ); + + // Do damage for this frame + Vector vecStart = pev->origin; + vecStart.z += 30; + Vector vecEnd = vecStart + (pev->movedir * pev->speed * gpGlobals->frametime); + + UTIL_TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_hull, ENT(pev), &tr ); + + if ( tr.pHit && tr.pHit != pev->owner ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + entvars_t *pevOwner = pev; + if ( pev->owner ) + pevOwner = VARS(pev->owner); + + if ( pEntity ) + pEntity->TakeDamage( pev, pevOwner, gSkillData.gargantuaDmgStomp, DMG_SONIC ); + } + + // Accelerate the effect + pev->speed = pev->speed + (gpGlobals->frametime) * pev->framerate; + pev->framerate = pev->framerate + (gpGlobals->frametime) * 1500; + + // Move and spawn trails + while ( gpGlobals->time - pev->dmgtime > STOMP_INTERVAL ) + { + pev->origin = pev->origin + pev->movedir * pev->speed * STOMP_INTERVAL; + for ( int i = 0; i < 2; i++ ) + { + CSprite *pSprite = CSprite::SpriteCreate( GARG_STOMP_SPRITE_NAME, pev->origin, TRUE ); + if ( pSprite ) + { + UTIL_TraceLine( pev->origin, pev->origin - Vector(0,0,500), ignore_monsters, edict(), &tr ); + pSprite->pev->origin = tr.vecEndPos; + pSprite->pev->velocity = Vector(RANDOM_FLOAT(-200,200),RANDOM_FLOAT(-200,200),175); + // pSprite->AnimateAndDie( RANDOM_FLOAT( 8.0, 12.0 ) ); + pSprite->SetNextThink( 0.3 ); + pSprite->SetThink(&CSprite:: SUB_Remove ); + pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxFadeFast ); + } + } + pev->dmgtime += STOMP_INTERVAL; + // Scale has the "life" of this effect + pev->scale -= STOMP_INTERVAL * pev->speed; + if ( pev->scale <= 0 ) + { + // Life has run out + UTIL_Remove(this); + STOP_SOUND( edict(), CHAN_BODY, GARG_STOMP_BUZZ_SOUND ); + } + + } +} + + +void StreakSplash( const Vector &origin, const Vector &direction, int color, int count, int speed, int velocityRange ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, origin ); + WRITE_BYTE( TE_STREAK_SPLASH ); + WRITE_COORD( origin.x ); // origin + WRITE_COORD( origin.y ); + WRITE_COORD( origin.z ); + WRITE_COORD( direction.x ); // direction + WRITE_COORD( direction.y ); + WRITE_COORD( direction.z ); + WRITE_BYTE( color ); // Streak color 6 + WRITE_SHORT( count ); // count + WRITE_SHORT( speed ); + WRITE_SHORT( velocityRange ); // Random velocity modifier + MESSAGE_END(); +} + + +class CGargantua : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + float MaxYawSpeed( void ); + int Classify ( void ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + + BOOL CheckMeleeAttack1( float flDot, float flDist ); // Swipe + BOOL CheckMeleeAttack2( float flDot, float flDist ); // Flames + BOOL CheckRangeAttack1( float flDot, float flDist ); // Stomp attack + void SetObjectCollisionBox( void ) + { + pev->absmin = pev->origin + Vector( -80, -80, 0 ); + pev->absmax = pev->origin + Vector( 80, 80, 214 ); + } + + Schedule_t *GetScheduleOfType( int Type ); + void StartTask( Task_t *pTask ); + void RunTask( Task_t *pTask ); + + void PrescheduleThink( void ); + + void Killed( entvars_t *pevAttacker, int iGib ); + void DeathEffect( void ); + + void EyeOff( void ); + void EyeOn( int level ); + void EyeUpdate( void ); + void Leap( void ); + void StompAttack( void ); + void FlameCreate( void ); + void FlameUpdate( void ); + void FlameControls( float angleX, float angleY ); + void FlameDestroy( void ); + inline BOOL FlameIsOn( void ) { return m_pFlame[0] != NULL; } + + void FlameDamage( Vector vecStart, Vector vecEnd, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + CUSTOM_SCHEDULES; + +private: + static const char *pAttackHitSounds[]; + static const char *pBeamAttackSounds[]; + static const char *pAttackMissSounds[]; + static const char *pRicSounds[]; + static const char *pFootSounds[]; + static const char *pIdleSounds[]; + static const char *pAlertSounds[]; + static const char *pPainSounds[]; + static const char *pAttackSounds[]; + static const char *pStompSounds[]; + static const char *pBreatheSounds[]; + + CBaseEntity* GargantuaCheckTraceHullAttack(float flDist, int iDamage, int iDmgType); + + CSprite *m_pEyeGlow; // Glow around the eyes + CBeam *m_pFlame[4]; // Flame beams + + int m_eyeBrightness; // Brightness target + float m_seeTime; // Time to attack (when I see the enemy, I set this) + float m_flameTime; // Time of next flame attack + float m_painSoundTime; // Time of next pain sound + float m_streakTime; // streak timer (don't send too many) + float m_flameX; // Flame thrower aim + float m_flameY; +}; + +LINK_ENTITY_TO_CLASS( monster_gargantua, CGargantua ); + +TYPEDESCRIPTION CGargantua::m_SaveData[] = +{ + DEFINE_FIELD( CGargantua, m_pEyeGlow, FIELD_CLASSPTR ), + DEFINE_FIELD( CGargantua, m_eyeBrightness, FIELD_INTEGER ), + DEFINE_FIELD( CGargantua, m_seeTime, FIELD_TIME ), + DEFINE_FIELD( CGargantua, m_flameTime, FIELD_TIME ), + DEFINE_FIELD( CGargantua, m_streakTime, FIELD_TIME ), + DEFINE_FIELD( CGargantua, m_painSoundTime, FIELD_TIME ), + DEFINE_ARRAY( CGargantua, m_pFlame, FIELD_CLASSPTR, 4 ), + DEFINE_FIELD( CGargantua, m_flameX, FIELD_FLOAT ), + DEFINE_FIELD( CGargantua, m_flameY, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CGargantua, CBaseMonster ); + +const char *CGargantua::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CGargantua::pBeamAttackSounds[] = +{ + "garg/gar_flameoff1.wav", + "garg/gar_flameon1.wav", + "garg/gar_flamerun1.wav", +}; + + +const char *CGargantua::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CGargantua::pRicSounds[] = +{ +#if 0 + "weapons/ric1.wav", + "weapons/ric2.wav", + "weapons/ric3.wav", + "weapons/ric4.wav", + "weapons/ric5.wav", +#else + "debris/metal4.wav", + "debris/metal6.wav", + "weapons/ric4.wav", + "weapons/ric5.wav", +#endif +}; + +const char *CGargantua::pFootSounds[] = +{ + "garg/gar_step1.wav", + "garg/gar_step2.wav", +}; + + +const char *CGargantua::pIdleSounds[] = +{ + "garg/gar_idle1.wav", + "garg/gar_idle2.wav", + "garg/gar_idle3.wav", + "garg/gar_idle4.wav", + "garg/gar_idle5.wav", +}; + + +const char *CGargantua::pAttackSounds[] = +{ + "garg/gar_attack1.wav", + "garg/gar_attack2.wav", + "garg/gar_attack3.wav", +}; + +const char *CGargantua::pAlertSounds[] = +{ + "garg/gar_alert1.wav", + "garg/gar_alert2.wav", + "garg/gar_alert3.wav", +}; + +const char *CGargantua::pPainSounds[] = +{ + "garg/gar_pain1.wav", + "garg/gar_pain2.wav", + "garg/gar_pain3.wav", +}; + +const char *CGargantua::pStompSounds[] = +{ + "garg/gar_stomp1.wav", +}; + +const char *CGargantua::pBreatheSounds[] = +{ + "garg/gar_breathe1.wav", + "garg/gar_breathe2.wav", + "garg/gar_breathe3.wav", +}; +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +#if 0 +enum +{ + SCHED_ = LAST_COMMON_SCHEDULE + 1, +}; +#endif + +enum +{ + TASK_SOUND_ATTACK = LAST_COMMON_TASK + 1, + TASK_FLAME_SWEEP, +}; + +Task_t tlGargFlame[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_SOUND_ATTACK, (float)0 }, + // { TASK_PLAY_SEQUENCE, (float)ACT_SIGNAL1 }, + { TASK_SET_ACTIVITY, (float)ACT_MELEE_ATTACK2 }, + { TASK_FLAME_SWEEP, (float)4.5 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slGargFlame[] = +{ + { + tlGargFlame, + ARRAYSIZE ( tlGargFlame ), + 0, + 0, + "GargFlame" + }, +}; + + +// primary melee attack +Task_t tlGargSwipe[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MELEE_ATTACK1, (float)0 }, +}; + +Schedule_t slGargSwipe[] = +{ + { + tlGargSwipe, + ARRAYSIZE ( tlGargSwipe ), + bits_COND_CAN_MELEE_ATTACK2, + 0, + "GargSwipe" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CGargantua ) +{ + slGargFlame, + slGargSwipe, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CGargantua, CBaseMonster ); + + +void CGargantua::EyeOn( int level ) +{ + m_eyeBrightness = level; +} + + +void CGargantua::EyeOff( void ) +{ + m_eyeBrightness = 0; +} + + +void CGargantua::EyeUpdate( void ) +{ + if ( m_pEyeGlow ) + { + m_pEyeGlow->pev->renderamt = UTIL_Approach( m_eyeBrightness, m_pEyeGlow->pev->renderamt, 26 ); + if ( m_pEyeGlow->pev->renderamt == 0 ) + m_pEyeGlow->pev->effects |= EF_NODRAW; + else + m_pEyeGlow->pev->effects &= ~EF_NODRAW; + UTIL_SetOrigin( m_pEyeGlow, pev->origin ); + } +} + + +void CGargantua::StompAttack( void ) +{ + TraceResult trace; + + UTIL_MakeVectors( pev->angles ); + Vector vecStart = pev->origin + Vector(0,0,60) + 35 * gpGlobals->v_forward; + Vector vecAim = ShootAtEnemy( vecStart ); + Vector vecEnd = (vecAim * 1024) + vecStart; + + UTIL_TraceLine( vecStart, vecEnd, ignore_monsters, edict(), &trace ); + CStomp::StompCreate( vecStart, trace.vecEndPos, 0 ); + UTIL_ScreenShake( pev->origin, 12.0, 100.0, 2.0, 1000 ); + EMIT_SOUND_DYN ( edict(), CHAN_WEAPON, pStompSounds[ RANDOM_LONG(0,ARRAYSIZE(pStompSounds)-1) ], 1.0, ATTN_GARG, 0, PITCH_NORM + RANDOM_LONG(-10,10) ); + + UTIL_TraceLine( pev->origin, pev->origin - Vector(0,0,20), ignore_monsters, edict(), &trace ); + if ( trace.flFraction < 1.0 ) + UTIL_TraceCustomDecal( &trace, "gargstomp", RANDOM_FLOAT( 0.0f, 360.0f )); +} + + +void CGargantua :: FlameCreate( void ) +{ + int i; + Vector posGun, angleGun; + TraceResult trace; + + UTIL_MakeVectors( pev->angles ); + + for ( i = 0; i < 4; i++ ) + { + if ( i < 2 ) + m_pFlame[i] = CBeam::BeamCreate( GARG_BEAM_SPRITE_NAME, 240 ); + else + m_pFlame[i] = CBeam::BeamCreate( GARG_BEAM_SPRITE2, 140 ); + if ( m_pFlame[i] ) + { + int attach = i%2; + // attachment is 0 based in GetAttachment + GetAttachment( attach+1, posGun, angleGun ); + + Vector vecEnd = (gpGlobals->v_forward * GARG_FLAME_LENGTH) + posGun; + UTIL_TraceLine( posGun, vecEnd, dont_ignore_monsters, edict(), &trace ); + + m_pFlame[i]->PointEntInit( trace.vecEndPos, entindex() ); + if ( i < 2 ) + m_pFlame[i]->SetColor( 255, 130, 90 ); + else + m_pFlame[i]->SetColor( 0, 120, 255 ); + m_pFlame[i]->SetBrightness( 190 ); + m_pFlame[i]->SetFlags( BEAM_FSHADEIN ); + m_pFlame[i]->SetScrollRate( 20 ); + // attachment is 1 based in SetEndAttachment + m_pFlame[i]->SetEndAttachment( attach + 2 ); + CSoundEnt::InsertSound( bits_SOUND_COMBAT, posGun, 384, 0.3 ); + } + } + EMIT_SOUND_DYN ( edict(), CHAN_BODY, pBeamAttackSounds[ 1 ], 1.0, ATTN_NORM, 0, PITCH_NORM ); + EMIT_SOUND_DYN ( edict(), CHAN_WEAPON, pBeamAttackSounds[ 2 ], 1.0, ATTN_NORM, 0, PITCH_NORM ); +} + + +void CGargantua :: FlameControls( float angleX, float angleY ) +{ + if ( angleY < -180 ) + angleY += 360; + else if ( angleY > 180 ) + angleY -= 360; + + if ( angleY < -45 ) + angleY = -45; + else if ( angleY > 45 ) + angleY = 45; + + m_flameX = UTIL_ApproachAngle( angleX, m_flameX, 4 ); + m_flameY = UTIL_ApproachAngle( angleY, m_flameY, 8 ); + SetBoneController( 0, m_flameY ); + SetBoneController( 1, m_flameX ); +} + + +void CGargantua :: FlameUpdate( void ) +{ + int i; + static float offset[2] = { 60, -60 }; + TraceResult trace; + Vector vecStart, angleGun; + BOOL streaks = FALSE; + + for ( i = 0; i < 2; i++ ) + { + if ( m_pFlame[i] ) + { + Vector vecAim = pev->angles; + vecAim.x += m_flameX; + vecAim.y += m_flameY; + + UTIL_MakeVectors( vecAim ); + + GetAttachment( i+1, vecStart, angleGun ); + Vector vecEnd = vecStart + (gpGlobals->v_forward * GARG_FLAME_LENGTH); // - offset[i] * gpGlobals->v_right; + + UTIL_TraceLine( vecStart, vecEnd, dont_ignore_monsters, edict(), &trace ); + + m_pFlame[i]->SetStartPos( trace.vecEndPos ); + m_pFlame[i+2]->SetStartPos( (vecStart * 0.6) + (trace.vecEndPos * 0.4) ); + + if ( trace.flFraction != 1.0 && gpGlobals->time > m_streakTime ) + { + StreakSplash( trace.vecEndPos, trace.vecPlaneNormal, 6, 20, 50, 400 ); + streaks = TRUE; + UTIL_TraceCustomDecal( &trace, "smallscorch", RANDOM_FLOAT( 0.0f, 360.0f )); + } + // RadiusDamage( trace.vecEndPos, pev, pev, gSkillData.gargantuaDmgFire, CLASS_ALIEN_MONSTER, DMG_BURN ); + FlameDamage( vecStart, trace.vecEndPos, pev, pev, gSkillData.gargantuaDmgFire, CLASS_ALIEN_MONSTER, DMG_BURN ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x1000 * (i + 2) ); // entity, attachment + WRITE_COORD( vecStart.x ); // origin + WRITE_COORD( vecStart.y ); + WRITE_COORD( vecStart.z ); + WRITE_COORD( RANDOM_FLOAT( 32, 48 ) ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 255 ); // G + WRITE_BYTE( 255 ); // B + WRITE_BYTE( 2 ); // life * 10 + WRITE_COORD( 0 ); // decay + MESSAGE_END(); + } + } + if ( streaks ) + m_streakTime = gpGlobals->time; +} + + + +void CGargantua :: FlameDamage( Vector vecStart, Vector vecEnd, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) +{ + CBaseEntity *pEntity = NULL; + TraceResult tr; + float flAdjustedDamage; + Vector vecSpot; + + Vector vecMid = (vecStart + vecEnd) * 0.5; + + float searchRadius = (vecStart - vecMid).Length(); + + Vector vecAim = (vecEnd - vecStart).Normalize( ); + + // iterate on all entities in the vicinity. + while ((pEntity = UTIL_FindEntityInSphere( pEntity, vecMid, searchRadius )) != NULL) + { + if ( pEntity->pev->takedamage != DAMAGE_NO ) + { + // UNDONE: this should check a damage mask, not an ignore + if ( iClassIgnore != CLASS_NONE && pEntity->Classify() == iClassIgnore ) + {// houndeyes don't hurt other houndeyes with their attack + continue; + } + + vecSpot = pEntity->BodyTarget( vecMid ); + + float dist = DotProduct( vecAim, vecSpot - vecMid ); + if (dist > searchRadius) + dist = searchRadius; + else if (dist < -searchRadius) + dist = searchRadius; + + Vector vecSrc = vecMid + dist * vecAim; + + UTIL_TraceLine ( vecSrc, vecSpot, dont_ignore_monsters, ENT(pev), &tr ); + + if ( tr.flFraction == 1.0 || tr.pHit == pEntity->edict() ) + {// the explosion can 'see' this entity, so hurt them! + // decrease damage for an ent that's farther from the flame. + dist = ( vecSrc - tr.vecEndPos ).Length(); + + if (dist > 64) + { + flAdjustedDamage = flDamage - (dist - 64) * 0.4; + if (flAdjustedDamage <= 0) + continue; + } + else + { + flAdjustedDamage = flDamage; + } + + // ALERT( at_console, "hit %s\n", STRING( pEntity->pev->classname ) ); + if (tr.flFraction != 1.0) + { + ClearMultiDamage( ); + pEntity->TraceAttack( pevInflictor, flAdjustedDamage, (tr.vecEndPos - vecSrc).Normalize( ), &tr, bitsDamageType ); + ApplyMultiDamage( pevInflictor, pevAttacker ); + } + else + { + pEntity->TakeDamage ( pevInflictor, pevAttacker, flAdjustedDamage, bitsDamageType ); + } + } + } + } +} + + +void CGargantua :: FlameDestroy( void ) +{ + int i; + + EMIT_SOUND_DYN ( edict(), CHAN_WEAPON, pBeamAttackSounds[ 0 ], 1.0, ATTN_NORM, 0, PITCH_NORM ); + for ( i = 0; i < 4; i++ ) + { + if ( m_pFlame[i] ) + { + UTIL_Remove( m_pFlame[i] ); + m_pFlame[i] = NULL; + } + } +} + + +void CGargantua :: PrescheduleThink( void ) +{ + if ( !HasConditions( bits_COND_SEE_ENEMY ) ) + { + m_seeTime = gpGlobals->time + 5; + EyeOff(); + } + else + EyeOn( 200 ); + + EyeUpdate(); +} + + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CGargantua :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MONSTER; +} + +//========================================================= +// MaxYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +float CGargantua :: MaxYawSpeed( void ) +{ + float ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + ys = 60; + break; + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 180; + break; + case ACT_WALK: + case ACT_RUN: + ys = 60; + break; + + default: + ys = 60; + break; + } + + return ys; +} + + +//========================================================= +// Spawn +//========================================================= +void CGargantua :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/garg.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, 0 ), Vector( 32, 32, 64 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + if (pev->health == 0) + pev->health = gSkillData.gargantuaHealth; + //pev->view_ofs = Vector ( 0, 0, 96 );// taken from mdl file + m_flFieldOfView = -0.2;// width of forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); + + m_pEyeGlow = CSprite::SpriteCreate( GARG_EYE_SPRITE_NAME, pev->origin, FALSE ); + m_pEyeGlow->SetTransparency( kRenderGlow, 255, 255, 255, 0, kRenderFxNoDissipation ); + m_pEyeGlow->SetAttachment( edict(), 1 ); + EyeOff(); + m_seeTime = gpGlobals->time + 5; + m_flameTime = gpGlobals->time + 2; +} + + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CGargantua :: Precache() +{ + int i; + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/garg.mdl"); + PRECACHE_MODEL( GARG_EYE_SPRITE_NAME ); + PRECACHE_MODEL( GARG_BEAM_SPRITE_NAME ); + PRECACHE_MODEL( GARG_BEAM_SPRITE2 ); + gStompSprite = PRECACHE_MODEL( GARG_STOMP_SPRITE_NAME ); + gGargGibModel = PRECACHE_MODEL( GARG_GIB_MODEL ); + PRECACHE_SOUND( GARG_STOMP_BUZZ_SOUND ); + + for ( i = 0; i < ARRAYSIZE( pAttackHitSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackHitSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pBeamAttackSounds ); i++ ) + PRECACHE_SOUND((char *)pBeamAttackSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAttackMissSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackMissSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pRicSounds ); i++ ) + PRECACHE_SOUND((char *)pRicSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pFootSounds ); i++ ) + PRECACHE_SOUND((char *)pFootSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pIdleSounds ); i++ ) + PRECACHE_SOUND((char *)pIdleSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAlertSounds ); i++ ) + PRECACHE_SOUND((char *)pAlertSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pPainSounds ); i++ ) + PRECACHE_SOUND((char *)pPainSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAttackSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pStompSounds ); i++ ) + PRECACHE_SOUND((char *)pStompSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pBreatheSounds ); i++ ) + PRECACHE_SOUND((char *)pBreatheSounds[i]); +} + + +void CGargantua::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + ALERT( at_aiconsole, "CGargantua::TraceAttack\n"); + + if ( !IsAlive() ) + { + CBaseMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); + return; + } + + // UNDONE: Hit group specific damage? + if ( bitsDamageType & (GARG_DAMAGE|DMG_BLAST) ) + { + if ( m_painSoundTime < gpGlobals->time ) + { + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, pPainSounds[ RANDOM_LONG(0,ARRAYSIZE(pPainSounds)-1) ], 1.0, ATTN_GARG, 0, PITCH_NORM ); + m_painSoundTime = gpGlobals->time + RANDOM_FLOAT( 2.5, 4 ); + } + } + + bitsDamageType &= GARG_DAMAGE; + + if ( bitsDamageType == 0) + { + if ( pev->dmgtime != gpGlobals->time || (RANDOM_LONG(0,100) < 20) ) + { + UTIL_Ricochet( ptr->vecEndPos, RANDOM_FLOAT(0.5,1.5) ); + pev->dmgtime = gpGlobals->time; +// if ( RANDOM_LONG(0,100) < 25 ) +// EMIT_SOUND_DYN( ENT(pev), CHAN_BODY, pRicSounds[ RANDOM_LONG(0,ARRAYSIZE(pRicSounds)-1) ], 1.0, ATTN_NORM, 0, PITCH_NORM ); + } + flDamage = 0; + } + + CBaseMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); + +} + + + +int CGargantua::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + ALERT( at_aiconsole, "CGargantua::TakeDamage\n"); + + if ( IsAlive() ) + { + if ( !(bitsDamageType & GARG_DAMAGE) ) + flDamage *= 0.01; + if ( bitsDamageType & DMG_BLAST ) + SetConditions( bits_COND_LIGHT_DAMAGE ); + } + + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + + +void CGargantua::DeathEffect( void ) +{ + int i; + UTIL_MakeVectors(pev->angles); + Vector deathPos = pev->origin + gpGlobals->v_forward * 100; + + // Create a spiral of streaks + CSpiral::Create( deathPos, (pev->absmax.z - pev->absmin.z) * 0.6, 125, 1.5 ); + + Vector position = pev->origin; + position.z += 32; + for ( i = 0; i < 7; i+=2 ) + { + SpawnExplosion( position, 70, (i * 0.3), 60 + (i*20) ); + position.z += 15; + } + + CBaseEntity *pSmoker = CBaseEntity::Create( "env_smoker", pev->origin, g_vecZero, NULL ); + pSmoker->pev->health = 1; // 1 smoke balls + pSmoker->pev->scale = 46; // 4.6X normal size + pSmoker->pev->dmg = 0; // 0 radial distribution + pSmoker->SetNextThink( 2.5 ); // Start in 2.5 seconds +} + + +void CGargantua::Killed( entvars_t *pevAttacker, int iGib ) +{ + EyeOff(); + UTIL_Remove( m_pEyeGlow ); + m_pEyeGlow = NULL; + CBaseMonster::Killed( pevAttacker, GIB_NEVER ); +} + +//========================================================= +// CheckMeleeAttack1 +// Garg swipe attack +// +//========================================================= +BOOL CGargantua::CheckMeleeAttack1( float flDot, float flDist ) +{ +// ALERT(at_aiconsole, "CheckMelee(%f, %f)\n", flDot, flDist); + + if (flDot >= 0.7) + { + if (flDist <= GARG_ATTACKDIST) + return TRUE; + } + return FALSE; +} + + +// Flame thrower madness! +BOOL CGargantua::CheckMeleeAttack2( float flDot, float flDist ) +{ +// ALERT(at_aiconsole, "CheckMelee(%f, %f)\n", flDot, flDist); + + if ( gpGlobals->time > m_flameTime ) + { + if (flDot >= 0.8 && flDist > GARG_ATTACKDIST) + { + if ( flDist <= GARG_FLAME_LENGTH ) + return TRUE; + } + } + return FALSE; +} + + +//========================================================= +// CheckRangeAttack1 +// flDot is the cos of the angle of the cone within which +// the attack can occur. +//========================================================= +// +// Stomp attack +// +//========================================================= +BOOL CGargantua::CheckRangeAttack1( float flDot, float flDist ) +{ + if ( gpGlobals->time > m_seeTime ) + { + if (flDot >= 0.7 && flDist > GARG_ATTACKDIST) + { + return TRUE; + } + } + return FALSE; +} + + + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CGargantua::HandleAnimEvent(MonsterEvent_t *pEvent) +{ + switch( pEvent->event ) + { + case GARG_AE_SLASH_LEFT: + { + // HACKHACK!!! + CBaseEntity *pHurt = GargantuaCheckTraceHullAttack( GARG_ATTACKDIST + 10.0, gSkillData.gargantuaDmgSlash, DMG_SLASH ); + if (pHurt) + { + if ( pHurt->pev->flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->pev->punchangle.x = -30; // pitch + pHurt->pev->punchangle.y = -30; // yaw + pHurt->pev->punchangle.z = 30; // roll + //UTIL_MakeVectors(pev->angles); // called by CheckTraceHullAttack + pHurt->pev->velocity = pHurt->pev->velocity - gpGlobals->v_right * 100; + } + EMIT_SOUND_DYN ( edict(), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, 50 + RANDOM_LONG(0,15) ); + } + else // Play a random attack miss sound + EMIT_SOUND_DYN ( edict(), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, 50 + RANDOM_LONG(0,15) ); + + Vector forward; + UTIL_MakeVectorsPrivate( pev->angles, forward, NULL, NULL ); + } + break; + + case GARG_AE_RIGHT_FOOT: + case GARG_AE_LEFT_FOOT: + UTIL_ScreenShake( pev->origin, 4.0, 3.0, 1.0, 750 ); + EMIT_SOUND_DYN ( edict(), CHAN_BODY, pFootSounds[ RANDOM_LONG(0,ARRAYSIZE(pFootSounds)-1) ], 1.0, ATTN_GARG, 0, PITCH_NORM + RANDOM_LONG(-10,10) ); + break; + + case GARG_AE_STOMP: + StompAttack(); + m_seeTime = gpGlobals->time + 12; + break; + + case GARG_AE_BREATHE: + if ( !(pev->spawnflags & SF_MONSTER_GAG) || m_MonsterState != MONSTERSTATE_IDLE) + EMIT_SOUND_DYN ( edict(), CHAN_VOICE, pBreatheSounds[ RANDOM_LONG(0,ARRAYSIZE(pBreatheSounds)-1) ], 1.0, ATTN_GARG, 0, PITCH_NORM + RANDOM_LONG(-10,10) ); + break; + + default: + CBaseMonster::HandleAnimEvent(pEvent); + break; + } +} + + +//========================================================= +// CheckTraceHullAttack - expects a length to trace, amount +// of damage to do, and damage type. Returns a pointer to +// the damaged entity in case the monster wishes to do +// other stuff to the victim (punchangle, etc) +// Used for many contact-range melee attacks. Bites, claws, etc. + +// Overridden for Gargantua because his swing starts lower as +// a percentage of his height (otherwise he swings over the +// players head) +//========================================================= +CBaseEntity* CGargantua::GargantuaCheckTraceHullAttack(float flDist, int iDamage, int iDmgType) +{ + TraceResult tr; + + UTIL_MakeVectors( pev->angles ); + Vector vecStart = pev->origin; + vecStart.z += 64; + Vector vecEnd = vecStart + (gpGlobals->v_forward * flDist) - (gpGlobals->v_up * flDist * 0.3); + + UTIL_TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_hull, ENT(pev), &tr ); + + if ( tr.pHit ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + + if ( iDamage > 0 ) + { + pEntity->TakeDamage( pev, pev, iDamage, iDmgType ); + } + + return pEntity; + } + + return NULL; +} + + +Schedule_t *CGargantua::GetScheduleOfType( int Type ) +{ + // HACKHACK - turn off the flames if they are on and garg goes scripted / dead + if ( FlameIsOn() ) + FlameDestroy(); + + switch( Type ) + { + case SCHED_MELEE_ATTACK2: + return slGargFlame; + case SCHED_MELEE_ATTACK1: + return slGargSwipe; + break; + } + + return CBaseMonster::GetScheduleOfType( Type ); +} + + +void CGargantua::StartTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_FLAME_SWEEP: + FlameCreate(); + m_flWaitFinished = gpGlobals->time + pTask->flData; + m_flameTime = gpGlobals->time + 6; + m_flameX = 0; + m_flameY = 0; + break; + + case TASK_SOUND_ATTACK: + if ( RANDOM_LONG(0,100) < 30 ) + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, pAttackSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackSounds)-1) ], 1.0, ATTN_GARG, 0, PITCH_NORM ); + TaskComplete(); + break; + + // allow a scripted_action to make gargantua shoot flames. + case TASK_PLAY_SCRIPT: + if ( m_pCine->IsAction() && m_pCine->m_fAction == 3) + { + FlameCreate(); + m_flWaitFinished = gpGlobals->time + 4.5; + m_flameTime = gpGlobals->time + 6; + m_flameX = 0; + m_flameY = 0; + } + else + CBaseMonster::StartTask( pTask ); + break; + + case TASK_DIE: + m_flWaitFinished = gpGlobals->time + 1.6; + DeathEffect(); + // FALL THROUGH + default: + CBaseMonster::StartTask( pTask ); + break; + } +} + +//========================================================= +// RunTask +//========================================================= +void CGargantua::RunTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_DIE: + if ( gpGlobals->time > m_flWaitFinished ) + { + pev->renderfx = kRenderFxExplode; + pev->rendercolor.x = 255; + pev->rendercolor.y = 0; + pev->rendercolor.z = 0; + StopAnimation(); + SetNextThink( 0.15 ); + SetThink(&CGargantua:: SUB_Remove ); + int i; + int parts = MODEL_FRAMES( gGargGibModel ); + for ( i = 0; i < 10; i++ ) + { + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + pGib->Spawn( GARG_GIB_MODEL ); + + int bodyPart = 0; + if ( parts > 1 ) + bodyPart = RANDOM_LONG( 0, pev->body-1 ); + + pGib->pev->body = bodyPart; + pGib->m_bloodColor = BLOOD_COLOR_YELLOW; + pGib->m_material = matNone; + pGib->pev->origin = pev->origin; + pGib->pev->velocity = UTIL_RandomBloodVector() * RANDOM_FLOAT( 300, 500 ); + pGib->SetNextThink( 1.25 ); + pGib->SetThink(&CGib:: SUB_FadeOut ); + } + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + + // size + WRITE_COORD( 200 ); + WRITE_COORD( 200 ); + WRITE_COORD( 128 ); + + // velocity + WRITE_COORD( 0 ); + WRITE_COORD( 0 ); + WRITE_COORD( 0 ); + + // randomization + WRITE_BYTE( 200 ); + + // Model + WRITE_SHORT( gGargGibModel ); //model id# + + // # of shards + WRITE_BYTE( 50 ); + + // duration + WRITE_BYTE( 20 );// 3.0 seconds + + // flags + + WRITE_BYTE( BREAK_FLESH ); + MESSAGE_END(); + + return; + } + else + CBaseMonster::RunTask(pTask); + break; + + case TASK_PLAY_SCRIPT: + if (m_pCine->IsAction() && m_pCine->m_fAction == 3) + { + if (m_fSequenceFinished) + { + if (m_pCine->m_iRepeatsLeft > 0) + CBaseMonster::RunTask( pTask ); + else + { + FlameDestroy(); + FlameControls( 0, 0 ); + SetBoneController( 0, 0 ); + SetBoneController( 1, 0 ); + m_pCine->SequenceDone( this ); + } + break; + } + //if not finished, drop through into task_flame_sweep! + } + else + { + CBaseMonster::RunTask( pTask ); + break; + } + case TASK_FLAME_SWEEP: + if ( gpGlobals->time > m_flWaitFinished ) + { + FlameDestroy(); + TaskComplete(); + FlameControls( 0, 0 ); + SetBoneController( 0, 0 ); + SetBoneController( 1, 0 ); + } + else + { + BOOL cancel = FALSE; + + Vector angles = g_vecZero; + + FlameUpdate(); + CBaseEntity *pEnemy; + if (m_pCine) // LRC- are we obeying a scripted_action? + pEnemy = m_hTargetEnt; + else + pEnemy = m_hEnemy; + if ( pEnemy ) + { + Vector org = pev->origin; + org.z += 64; + Vector dir = pEnemy->BodyTarget(org) - org; + angles = UTIL_VecToAngles( dir ); + angles.x = -angles.x; + angles.y -= pev->angles.y; + if ( dir.Length() > 400 ) + cancel = TRUE; + } + if ( fabs(angles.y) > 60 ) + cancel = TRUE; + + if ( cancel ) + { + m_flWaitFinished -= 0.5; + m_flameTime -= 0.5; + } + // FlameControls( angles.x + 2 * sin(gpGlobals->time*8), angles.y + 28 * sin(gpGlobals->time*8.5) ); + FlameControls( angles.x, angles.y ); + } + break; + + default: + CBaseMonster::RunTask( pTask ); + break; + } +} + + +class CSmoker : public CBaseEntity +{ +public: + void Spawn( void ); + void Think( void ); +}; + +LINK_ENTITY_TO_CLASS( env_smoker, CSmoker ); + +void CSmoker::Spawn( void ) +{ + pev->movetype = MOVETYPE_NONE; + SetNextThink( 0 ); + pev->solid = SOLID_NOT; + UTIL_SetSize(pev, g_vecZero, g_vecZero ); + pev->effects |= EF_NODRAW; + pev->angles = g_vecZero; +} + + +void CSmoker::Think( void ) +{ + // lots of smoke + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( pev->origin.x + RANDOM_FLOAT( -pev->dmg, pev->dmg )); + WRITE_COORD( pev->origin.y + RANDOM_FLOAT( -pev->dmg, pev->dmg )); + WRITE_COORD( pev->origin.z); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( RANDOM_LONG(pev->scale, pev->scale * 1.1) ); + WRITE_BYTE( RANDOM_LONG(8,14) ); // framerate + MESSAGE_END(); + + pev->health--; + if ( pev->health > 0 ) + SetNextThink( RANDOM_FLOAT(0.1, 0.2) ); + else + UTIL_Remove( this ); +} + + +void CSpiral::Spawn( void ) +{ + pev->movetype = MOVETYPE_NONE; + SetNextThink( 0 ); + pev->solid = SOLID_NOT; + UTIL_SetSize(pev, g_vecZero, g_vecZero ); + pev->effects |= EF_NODRAW; + pev->angles = g_vecZero; +} + + +CSpiral *CSpiral::Create( const Vector &origin, float height, float radius, float duration ) +{ + if ( duration <= 0 ) + return NULL; + + CSpiral *pSpiral = GetClassPtr( (CSpiral *)NULL ); + pSpiral->Spawn(); + pSpiral->pev->dmgtime = pSpiral->m_fNextThink; + pSpiral->pev->origin = origin; + pSpiral->pev->scale = radius; + pSpiral->pev->dmg = height; + pSpiral->pev->speed = duration; + pSpiral->pev->health = 0; + pSpiral->pev->angles = g_vecZero; + + return pSpiral; +} + +#define SPIRAL_INTERVAL 0.1 //025 + +void CSpiral::Think( void ) +{ + float time = gpGlobals->time - pev->dmgtime; + + while ( time > SPIRAL_INTERVAL ) + { + Vector position = pev->origin; + Vector direction = Vector(0,0,1); + + float fraction = 1.0 / pev->speed; + + float radius = (pev->scale * pev->health) * fraction; + + position.z += (pev->health * pev->dmg) * fraction; + pev->angles.y = (pev->health * 360 * 8) * fraction; + UTIL_MakeVectors( pev->angles ); + position = position + gpGlobals->v_forward * radius; + direction = (direction + gpGlobals->v_forward).Normalize(); + + StreakSplash( position, Vector(0,0,1), RANDOM_LONG(8,11), 20, RANDOM_LONG(50,150), 400 ); + + // Jeez, how many counters should this take ? :) + pev->dmgtime += SPIRAL_INTERVAL; + pev->health += SPIRAL_INTERVAL; + time -= SPIRAL_INTERVAL; + } + + SetNextThink( 0 ); + + if ( pev->health >= pev->speed ) + UTIL_Remove( this ); +} + + +// HACKHACK Cut and pasted from explode.cpp +void SpawnExplosion( Vector center, float randomRange, float time, int magnitude ) +{ + KeyValueData kvd; + char buf[128]; + + center.x += RANDOM_FLOAT( -randomRange, randomRange ); + center.y += RANDOM_FLOAT( -randomRange, randomRange ); + + CBaseEntity *pExplosion = CBaseEntity::Create( "env_explosion", center, g_vecZero, NULL ); + sprintf( buf, "%3d", magnitude ); + kvd.szKeyName = "iMagnitude"; + kvd.szValue = buf; + pExplosion->KeyValue( &kvd ); + pExplosion->pev->spawnflags |= SF_ENVEXPLOSION_NODAMAGE; + + pExplosion->Spawn(); + pExplosion->SetThink(& CBaseEntity::SUB_CallUseToggle ); + pExplosion->SetNextThink( time ); +} + + + +#endif diff --git a/dlls/genericmonster.cpp b/dlls/genericmonster.cpp new file mode 100644 index 0000000..0b237d6 --- /dev/null +++ b/dlls/genericmonster.cpp @@ -0,0 +1,296 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Generic Monster - purely for scripted sequence work. +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "animation.h" + +// For holograms, make them not solid so the player can walk through them +//LRC- this seems to interfere with SF_MONSTER_CLIP +#define SF_GENERICMONSTER_NOTSOLID 4 +#define SF_GENERICMONSTER_PLAYERMODEL 8 +#define SF_GENERICMONSTER_INVULNERABLE 32 +//Not implemented: +#define SF_GENERICMONSTER_CORPSE 64 + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= + +class CGenericMonster : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + float MaxYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int ISoundMask ( void ); + void KeyValue( KeyValueData *pkvd ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + virtual int HasCustomGibs( void ) { return m_iszGibModel; } + + int m_iszGibModel; +}; +LINK_ENTITY_TO_CLASS( monster_generic, CGenericMonster ); + +TYPEDESCRIPTION CGenericMonster::m_SaveData[] = +{ + DEFINE_FIELD( CGenericMonster, m_iszGibModel, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CGenericMonster, CBaseMonster ); + +void CGenericMonster::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_bloodColor")) + { + m_bloodColor = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszGibModel")) + { + m_iszGibModel = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CGenericMonster :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_PLAYER_ALLY; +} + +//========================================================= +// MaxYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +float CGenericMonster :: MaxYawSpeed( void ) +{ + float ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + default: + ys = 90; + } + + return ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CGenericMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case 0: + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// ISoundMask - generic monster can't hear. +//========================================================= +int CGenericMonster :: ISoundMask ( void ) +{ + return NULL; +} + +//========================================================= +// Spawn +//========================================================= +void CGenericMonster :: Spawn() +{ + // store the size, so we can use it to set up the hulls after Set_Model overwrites it. + Vector vecSize = pev->size; + + //LRC - if the level designer forgets to set a model, don't crash! + if (FStringNull(pev->model)) + { + if (pev->targetname) + ALERT(at_error, "No model specified for monster_generic \"%s\"\n", STRING(pev->targetname)); + else + ALERT(at_error, "No model specified for monster_generic at %.2f %.2f %.2f\n", pev->origin.x, pev->origin.y, pev->origin.z); + pev->model = MAKE_STRING("models/player.mdl"); + } + + Precache(); + + SET_MODEL( ENT(pev), STRING(pev->model) ); + + if (vecSize != g_vecZero) + { + Vector vecMax = vecSize/2; + Vector vecMin = -vecMax; + if (!FBitSet(pev->spawnflags,SF_GENERICMONSTER_PLAYERMODEL)) + { + vecMin.z = 0; + vecMax.z = vecSize.z; + } + UTIL_SetSize(pev, vecMin, vecMax); + } + else if ( + pev->spawnflags & SF_GENERICMONSTER_PLAYERMODEL || + FStrEq( STRING(pev->model), "models/player.mdl" ) || + FStrEq( STRING(pev->model), "models/holo.mdl" ) + ) + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + else + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + if (!m_bloodColor) m_bloodColor = BLOOD_COLOR_RED; + if (!pev->health) pev->health = 8; + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); + + if ( pev->spawnflags & SF_GENERICMONSTER_NOTSOLID ) + { + pev->solid = SOLID_NOT; + pev->takedamage = DAMAGE_NO; + } + else if ( pev->spawnflags & SF_GENERICMONSTER_INVULNERABLE ) + { + pev->takedamage = DAMAGE_NO; + } +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CGenericMonster :: Precache() +{ + PRECACHE_MODEL( (char *)STRING(pev->model) ); + if (m_iszGibModel) + PRECACHE_MODEL( (char*)STRING(m_iszGibModel) ); //LRC +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + + +//========================================================= +// GENERIC DEAD MONSTER, PROP +//========================================================= +class CDeadGenericMonster : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + int Classify ( void ) { return CLASS_PLAYER_ALLY; } + void KeyValue( KeyValueData *pkvd ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + virtual int HasCustomGibs( void ) { return m_iszGibModel; } + float MaxYawSpeed( void ) { return 8.0f; } + int m_iszGibModel; +}; + +LINK_ENTITY_TO_CLASS( monster_generic_dead, CDeadGenericMonster ); + +TYPEDESCRIPTION CDeadGenericMonster::m_SaveData[] = +{ + DEFINE_FIELD( CDeadGenericMonster, m_iszGibModel, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CDeadGenericMonster, CBaseMonster ); + +void CDeadGenericMonster::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_bloodColor")) + { + m_bloodColor = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszGibModel")) + { + m_iszGibModel = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + +//========================================================= +// ********** DeadGenericMonster SPAWN ********** +//========================================================= +void CDeadGenericMonster :: Spawn( void ) +{ + Precache(); + SET_MODEL(ENT(pev), STRING(pev->model)); + + pev->sequence = 0; + + if (pev->netname) + { + pev->sequence = LookupSequence( STRING(pev->netname) ); + + if (pev->sequence == -1) + { + ALERT ( at_debug, "Invalid sequence name \"%s\" in monster_generic_dead\n", STRING(pev->netname) ); + } + } + else + { + pev->sequence = LookupActivity( pev->frags ); +// if (pev->sequence == -1) +// { +// ALERT ( at_error, "monster_generic_dead - specify a sequence name or choose a different death type: model \"%s\" has no available death sequences.\n", STRING(pev->model) ); +// } + //...and if that doesn't work, forget it. + } + + // Corpses have less health + pev->health = 8; + + MonsterInitDead(); + + ResetSequenceInfo( ); + pev->frame = 255; // pose at the _end_ of its death sequence. +} + +void CDeadGenericMonster :: Precache() +{ + PRECACHE_MODEL( (char*)STRING(pev->model) ); + if (m_iszGibModel) + PRECACHE_MODEL( (char*)STRING(m_iszGibModel) ); //LRC +} diff --git a/dlls/ggrenade.cpp b/dlls/ggrenade.cpp new file mode 100644 index 0000000..8787dc8 --- /dev/null +++ b/dlls/ggrenade.cpp @@ -0,0 +1,480 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== generic grenade.cpp ======================================================== + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "soundent.h" +#include "decals.h" + + +//===================grenade + + +LINK_ENTITY_TO_CLASS( grenade, CGrenade ); +LINK_ENTITY_TO_CLASS( grenade_vog25, CGrenade ); + +// +// Grenade Explode +// +void CGrenade::Explode( Vector vecSrc, Vector vecAim ) +{ + TraceResult tr; + UTIL_TraceLine ( pev->origin, pev->origin + Vector ( 0, 0, -32 ), ignore_monsters, ENT(pev), & tr); + + Explode( &tr, DMG_BLAST ); +} + +extern int gmsgCustomDLight; // buz +extern int gmsgNewExplode; // lev + +// UNDONE: temporary scorching for PreAlpha - find a less sleazy permenant solution. +void CGrenade::Explode( TraceResult *pTrace, int bitsDamageType ) +{ + float flRndSound;// sound randomizer + float ScaleExplode1 = 1.5f; + + pev->model = iStringNull;//invisible + pev->solid = SOLID_NOT;// intangible + + pev->takedamage = DAMAGE_NO; + + // Pull out of the wall a bit + if ( pTrace->flFraction != 1.0 ) + { + pev->origin = pTrace->vecEndPos + (pTrace->vecPlaneNormal * (pev->dmg - 24) * 0.6); + } + + int iContents = UTIL_PointContents ( pev->origin ); + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION ); // This makes a dynamic light and the explosion sprites/sound + WRITE_COORD( pev->origin.x ); // Send to PAS because of the sound + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z -= 18 ); + if (iContents != CONTENTS_WATER) + { + WRITE_SHORT( g_sModelIndexNull ); // çàãëóøêà... + } + else + { + WRITE_SHORT( g_sModelIndexWExplosion ); + } + WRITE_BYTE( (pev->dmg - 40) * .10 ); // scale * 10 + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + + FireBulletsWater( pev->origin + Vector( 0, 0, 50 ), pev->origin, 0.3, 0.4 ); + + if (iContents != CONTENTS_WATER) + { + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_SPARKS ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z -= 5 ); + MESSAGE_END(); + } + + if (iContents != CONTENTS_WATER) + { + MESSAGE_BEGIN( MSG_ALL, gmsgNewExplode ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( ScaleExplode1 ); + MESSAGE_END(); + } + +// ALERT(at_console, "%f\n", pev->dmg); + + // buz: make dlight in custom renderer too + MESSAGE_BEGIN( MSG_PAS, gmsgCustomDLight, pev->origin ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z += 10 ); + WRITE_BYTE( 20 ); // radius / 10 + WRITE_BYTE( 5 ); // life * 10 + WRITE_BYTE( 35 ); // decay / 10 + MESSAGE_END(); + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, NORMAL_EXPLOSION_VOLUME, 3.0 ); + entvars_t *pevOwner; + if ( pev->owner ) + pevOwner = VARS( pev->owner ); + else + pevOwner = NULL; + + pev->owner = NULL; // can't traceline attack owner if this is set + + RadiusDamage ( pev, pevOwner, pev->dmg, CLASS_NONE, bitsDamageType ); + + UTIL_ScreenShake( pev->origin, 16.0f, 180.0f, 0.5f, pev->dmg * 6 ); + + CBaseEntity *pEntity = CBaseEntity::Instance( pTrace->pHit ); + + if( pEntity && GET_MODEL_PTR( pEntity->edict() )) + UTIL_StudioDecalTrace( pTrace, "scorch" ); + else UTIL_TraceCustomDecal( pTrace, "scorch", RANDOM_FLOAT( 0.0f, 360.0f )); + + flRndSound = RANDOM_FLOAT( 0 , 1 ); + + switch ( RANDOM_LONG( 0, 2 ) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris1.wav", 0.55, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris2.wav", 0.55, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris3.wav", 0.55, ATTN_NORM); break; + } + + pev->effects |= EF_NODRAW; + SetThink(&CGrenade:: Smoke ); + pev->velocity = g_vecZero; + SetNextThink( 0.3 ); + + if (iContents != CONTENTS_WATER) + { + int sparkCount = RANDOM_LONG(0,3); + for ( int i = 0; i < sparkCount; i++ ) + Create( "spark_shower", pev->origin, pTrace->vecPlaneNormal, NULL ); + } +} + +void CGrenade::Smoke( void ) +{ + if (UTIL_PointContents ( pev->origin ) == CONTENTS_WATER) + { + UTIL_Bubbles( pev->origin - Vector( 64, 64, 64 ), pev->origin + Vector( 64, 64, 64 ), 100 ); + } + /*else + { + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( (pev->dmg - 50) * 0.30 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + }*/ + + UTIL_Remove( this ); +} + +void CGrenade::Killed( entvars_t *pevAttacker, int iGib ) +{ + Detonate( ); +} + + +// Timed grenade, this think is called when time runs out. +void CGrenade::DetonateUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink(&CGrenade:: Detonate ); + SetNextThink( 0 ); +} + +void CGrenade::PreDetonate( void ) +{ + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin, 400, 0.3 ); + + SetThink(&CGrenade:: Detonate ); + SetNextThink( 1 ); +} + +void CGrenade::Detonate( void ) +{ + TraceResult tr; + Vector vecSpot;// trace starts here! + + vecSpot = pev->origin + Vector ( 0 , 0 , 8 ); + UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -40 ), ignore_monsters, ENT(pev), & tr); + + Explode( &tr, DMG_BLAST ); +} + +// +// Contact grenade, explode when it touches something +// +void CGrenade::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, ignore_monsters, ENT(pev), &tr ); + + Explode( &tr, DMG_BLAST ); +} + +void CGrenade::DangerSoundThink( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + if( FClassnameIs( pev, "grenade_vog25" )) + pev->angles = UTIL_VecToAngles( pev->velocity ); + + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin + pev->velocity * 0.5, pev->velocity.Length( ), 0.2 ); + SetNextThink( 0.2 ); + + if (pev->waterlevel != 0 && pev->watertype > CONTENTS_FLYFIELD) + { + pev->velocity = pev->velocity * 0.5; + } +} + + +void CGrenade::BounceTouch( CBaseEntity *pOther ) +{ + // don't hit the guy that launched this grenade + if ( pOther->edict() == pev->owner ) + return; + + // only do damage if we're moving fairly fast + if (m_flNextAttack < gpGlobals->time && pev->velocity.Length() > 100) + { + entvars_t *pevOwner = VARS( pev->owner ); + if (pevOwner) + { + TraceResult tr = UTIL_GetGlobalTrace( ); + ClearMultiDamage( ); + pOther->TraceAttack(pevOwner, 1, gpGlobals->v_forward, &tr, DMG_CLUB ); + ApplyMultiDamage( pev, pevOwner); + } + m_flNextAttack = gpGlobals->time + 1.0; // debounce + } + + Vector vecTestVelocity; + // pev->avelocity = Vector (300, 300, 300); + + // this is my heuristic for modulating the grenade velocity because grenades dropped purely vertical + // or thrown very far tend to slow down too quickly for me to always catch just by testing velocity. + // trimming the Z velocity a bit seems to help quite a bit. + vecTestVelocity = pev->velocity; + vecTestVelocity.z *= 0.45; + + if ( !m_fRegisteredSound && vecTestVelocity.Length() <= 60 ) + { + //ALERT( at_console, "Grenade Registered!: %f\n", vecTestVelocity.Length() ); + + // grenade is moving really slow. It's probably very close to where it will ultimately stop moving. + // go ahead and emit the danger sound. + + // register a radius louder than the explosion, so we make sure everyone gets out of the way + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin, pev->dmg / 0.4, 0.3 ); + m_fRegisteredSound = TRUE; + } + + if (pev->flags & FL_ONGROUND) + { + // add a bit of static friction + pev->velocity = pev->velocity * 0.8; + + pev->sequence = RANDOM_LONG( 1, 1 ); + } + else + { + // play bounce sound + BounceSound(); + } + pev->framerate = pev->velocity.Length() / 200.0; + if (pev->framerate > 1.0) + pev->framerate = 1; + else if (pev->framerate < 0.5) + pev->framerate = 0; + +} + +void CGrenade::SlideTouch( CBaseEntity *pOther ) +{ + // don't hit the guy that launched this grenade + if ( pOther->edict() == pev->owner ) + return; + + // pev->avelocity = Vector (300, 300, 300); + + if (pev->flags & FL_ONGROUND) + { + // add a bit of static friction + pev->velocity = pev->velocity * 0.95; + + if (pev->velocity.x != 0 || pev->velocity.y != 0) + { + // maintain sliding sound + } + } + else + { + BounceSound(); + } +} + +void CGrenade :: BounceSound( void ) +{ + switch ( RANDOM_LONG( 0, 2 ) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/grenade_hit1.wav", 0.25, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/grenade_hit2.wav", 0.25, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/grenade_hit3.wav", 0.25, ATTN_NORM); break; + } +} + +void CGrenade :: TumbleThink( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + StudioFrameAdvance( ); + SetNextThink( 0.1 ); + + if (pev->dmgtime - 1 < gpGlobals->time) + { + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin + pev->velocity * (pev->dmgtime - gpGlobals->time), 400, 0.1 ); + } + + if (pev->dmgtime <= gpGlobals->time) + { + SetThink(&CGrenade :: Detonate ); + } + if (pev->waterlevel != 0 && pev->watertype > CONTENTS_FLYFIELD) + { + pev->velocity = pev->velocity * 0.5; + pev->framerate = 0.2; + } +} + + +void CGrenade:: Spawn( void ) +{ + pev->movetype = MOVETYPE_BOUNCE; + pev->classname = MAKE_STRING( "grenade" ); + + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/grenade.mdl"); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + + pev->dmg = 100; + m_fRegisteredSound = FALSE; +} + +CGrenade *CGrenade :: ShootGeneric( CBaseEntity *pOwner, Vector vecStart, Vector vecVelocity, string_t classname ) +{ + CGrenade *pGrenade = GetClassPtr( (CGrenade *)NULL ); + pGrenade->Spawn(); + pGrenade->pev->classname = classname; // allow save\restore + UTIL_SetOrigin( pGrenade, vecStart ); + pGrenade->pev->velocity = vecVelocity; + pGrenade->pev->angles = UTIL_VecToAngles( pGrenade->pev->velocity ); + pGrenade->pev->owner = pOwner->edict(); + + // make monsters afaid of it while in the air + pGrenade->SetThink( &CGrenade:: DangerSoundThink ); + pGrenade->SetNextThink( 0 ); + + // Explode on contact + pGrenade->SetTouch( &CGrenade:: ExplodeTouch ); + + return pGrenade; +} + +CGrenade *CGrenade::ShootContact( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ) +{ + CGrenade *pGrenade = GetClassPtr( (CGrenade *)NULL ); + pGrenade->Spawn(); + // contact grenades arc lower + pGrenade->pev->gravity = 0.5;// lower gravity since grenade is aerodynamic and engine doesn't know it. + UTIL_SetOrigin( pGrenade, vecStart ); + pGrenade->pev->velocity = vecVelocity; + pGrenade->pev->angles = UTIL_VecToAngles (pGrenade->pev->velocity); + pGrenade->pev->owner = ENT(pevOwner); + + // make monsters afaid of it while in the air + pGrenade->SetThink(&CGrenade:: DangerSoundThink ); + pGrenade->SetNextThink( 0 ); + + // Tumble in air + pGrenade->pev->avelocity.x = RANDOM_FLOAT ( -100, -500 ); + + // Explode on contact + pGrenade->SetTouch(&CGrenade:: ExplodeTouch ); + + pGrenade->pev->dmg = gSkillData.plrDmgM203Grenade; + + return pGrenade; +} + + +CGrenade * CGrenade:: ShootTimed( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity, float time, float damage ) +{ + CGrenade *pGrenade = GetClassPtr( (CGrenade *)NULL ); + pGrenade->Spawn(); + UTIL_SetOrigin( pGrenade, vecStart ); + pGrenade->pev->velocity = vecVelocity; + pGrenade->pev->angles = UTIL_VecToAngles(pGrenade->pev->velocity); + pGrenade->pev->owner = ENT(pevOwner); + + pGrenade->SetTouch(&CGrenade:: BounceTouch ); // Bounce if touched + + // Take one second off of the desired detonation time and set the think to PreDetonate. PreDetonate + // will insert a DANGER sound into the world sound list and delay detonation for one second so that + // the grenade explodes after the exact amount of time specified in the call to ShootTimed(). + + pGrenade->pev->dmgtime = gpGlobals->time + time; + pGrenade->SetThink(&CGrenade:: TumbleThink ); + pGrenade->SetNextThink( 0.1 ); + + if( time < 0.1f ) + { + pGrenade->SetNextThink( 0 ); + pGrenade->pev->velocity = g_vecZero; + } + + pGrenade->pev->sequence = 0; + pGrenade->pev->framerate = 1.0; + + // Tumble through the air + // pGrenade->pev->avelocity.x = -400; + + pGrenade->pev->gravity = 0.5; + pGrenade->pev->friction = 0.8; + + // buz + pGrenade->pev->angles.y -= 90; + pGrenade->pev->avelocity.z = -400; + + SET_MODEL(ENT(pGrenade->pev), "models/w_grenade.mdl"); + pGrenade->pev->dmg = damage; + + return pGrenade; +} \ No newline at end of file diff --git a/dlls/globals.cpp b/dlls/globals.cpp new file mode 100644 index 0000000..2a4ce88 --- /dev/null +++ b/dlls/globals.cpp @@ -0,0 +1,42 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== globals.cpp ======================================================== + + DLL-wide global variable definitions. + They're all defined here, for convenient centralization. + Source files that need them should "extern ..." declare each + variable, to better document what globals they care about. + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "soundent.h" + +DLL_GLOBAL ULONG g_ulFrameCount; +DLL_GLOBAL ULONG g_ulModelIndexEyes; +DLL_GLOBAL ULONG g_ulModelIndexPlayer; +DLL_GLOBAL Vector g_vecAttackDir; +DLL_GLOBAL int g_iSkillLevel; +DLL_GLOBAL int gDisplayTitle; +DLL_GLOBAL BOOL g_fGameOver; +DLL_GLOBAL int g_Language; +DLL_GLOBAL BOOL g_fPhysicInitialized = FALSE; +DLL_GLOBAL int g_iXashEngineBuildNumber; +DLL_GLOBAL BOOL g_fXashEngine = FALSE; +DLL_GLOBAL BOOL g_fAllowSaves = TRUE; \ No newline at end of file diff --git a/dlls/gman.cpp b/dlls/gman.cpp new file mode 100644 index 0000000..1205e86 --- /dev/null +++ b/dlls/gman.cpp @@ -0,0 +1,243 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// GMan - misunderstood servant of the people +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "weapons.h" + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= + +class CGMan : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + float MaxYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int ISoundMask ( void ); + + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void StartTask( Task_t *pTask ); + void RunTask( Task_t *pTask ); + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + + void PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ); + + EHANDLE m_hPlayer; + EHANDLE m_hTalkTarget; + float m_flTalkTime; +}; +LINK_ENTITY_TO_CLASS( monster_gman, CGMan ); + + +TYPEDESCRIPTION CGMan::m_SaveData[] = +{ + DEFINE_FIELD( CGMan, m_hTalkTarget, FIELD_EHANDLE ), + DEFINE_FIELD( CGMan, m_flTalkTime, FIELD_TIME ), +}; +IMPLEMENT_SAVERESTORE( CGMan, CBaseMonster ); + + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CGMan :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_NONE; +} + +//========================================================= +// MaxYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +float CGMan :: MaxYawSpeed( void ) +{ + float ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + default: + ys = 90; + } + + return ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CGMan :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case 0: + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// ISoundMask - generic monster can't hear. +//========================================================= +int CGMan :: ISoundMask ( void ) +{ + return NULL; +} + +//========================================================= +// Spawn +//========================================================= +void CGMan :: Spawn() +{ + Precache(); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL( ENT(pev), "models/gman.mdl" ); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = DONT_BLEED; + pev->health = 100; + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CGMan :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL( "models/gman.mdl" ); +} + + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + + +void CGMan :: StartTask( Task_t *pTask ) +{ + switch( pTask->iTask ) + { + case TASK_WAIT: + if (m_hPlayer == NULL) + { + m_hPlayer = UTIL_FindEntityByClassname( NULL, "player" ); + } + break; + } + CBaseMonster::StartTask( pTask ); +} + +void CGMan :: RunTask( Task_t *pTask ) +{ + switch( pTask->iTask ) + { + case TASK_WAIT: + // look at who I'm talking to + if (m_flTalkTime > gpGlobals->time && m_hTalkTarget != NULL) + { + float yaw = VecToYaw(m_hTalkTarget->pev->origin - pev->origin) - pev->angles.y; + + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + + // turn towards vector + SetBoneController( 0, yaw ); + } + // look at player, but only if playing a "safe" idle animation + else if (m_hPlayer != NULL && pev->sequence == 0) + { + float yaw = VecToYaw(m_hPlayer->pev->origin - pev->origin) - pev->angles.y; + + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + + // turn towards vector + SetBoneController( 0, yaw ); + } + else + { + SetBoneController( 0, 0 ); + } + CBaseMonster::RunTask( pTask ); + break; + default: + SetBoneController( 0, 0 ); + CBaseMonster::RunTask( pTask ); + break; + } +} + + +//========================================================= +// Override all damage +//========================================================= +int CGMan :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + pev->health = pev->max_health / 2; // always trigger the 50% damage aitrigger + + if ( flDamage > 0 ) + { + SetConditions(bits_COND_LIGHT_DAMAGE); + } + + if ( flDamage >= 20 ) + { + SetConditions(bits_COND_HEAVY_DAMAGE); + } + return TRUE; +} + + +void CGMan::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + UTIL_Ricochet( ptr->vecEndPos, 1.0 ); + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); +} + + +void CGMan::PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ) +{ + CBaseMonster::PlayScriptedSentence( pszSentence, duration, volume, attenuation, bConcurrent, pListener ); + + m_flTalkTime = gpGlobals->time + duration; + m_hTalkTarget = pListener; +} diff --git a/dlls/h_ai.cpp b/dlls/h_ai.cpp new file mode 100644 index 0000000..73a5064 --- /dev/null +++ b/dlls/h_ai.cpp @@ -0,0 +1,199 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + + h_ai.cpp - halflife specific ai code + +*/ + + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "game.h" + +#define NUM_LATERAL_CHECKS 13 // how many checks are made on each side of a monster looking for lateral cover +#define NUM_LATERAL_LOS_CHECKS 6 // how many checks are made on each side of a monster looking for lateral cover + +//float flRandom = RANDOM_FLOAT(0,1); + +DLL_GLOBAL BOOL g_fDrawLines = FALSE; + +//========================================================= +// +// AI UTILITY FUNCTIONS +// +// !!!UNDONE - move CBaseMonster functions to monsters.cpp +//========================================================= + +//========================================================= +// FBoxVisible - a more accurate ( and slower ) version +// of FVisible. +// +// !!!UNDONE - make this CBaseMonster? +//========================================================= +BOOL FBoxVisible ( entvars_t *pevLooker, entvars_t *pevTarget, Vector &vecTargetOrigin, float flSize ) +{ + // don't look through water + if ((pevLooker->waterlevel != 3 && pevTarget->waterlevel == 3) + || (pevLooker->waterlevel == 3 && pevTarget->waterlevel == 0)) + return FALSE; + + TraceResult tr; + Vector vecLookerOrigin = pevLooker->origin + pevLooker->view_ofs;//look through the monster's 'eyes' + for (int i = 0; i < 5; i++) + { + Vector vecTarget = pevTarget->origin; + vecTarget.x += RANDOM_FLOAT( pevTarget->mins.x + flSize, pevTarget->maxs.x - flSize); + vecTarget.y += RANDOM_FLOAT( pevTarget->mins.y + flSize, pevTarget->maxs.y - flSize); + vecTarget.z += RANDOM_FLOAT( pevTarget->mins.z + flSize, pevTarget->maxs.z - flSize); + + UTIL_TraceLine(vecLookerOrigin, vecTarget, ignore_monsters, ignore_glass, ENT(pevLooker)/*pentIgnore*/, &tr); + + if (tr.flFraction == 1.0) + { + vecTargetOrigin = vecTarget; + return TRUE;// line of sight is valid. + } + } + return FALSE;// Line of sight is not established +} + +// +// VecCheckToss - returns the velocity at which an object should be lobbed from vecspot1 to land near vecspot2. +// returns g_vecZero if toss is not feasible. +// +Vector VecCheckToss ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flGravityAdj ) +{ + TraceResult tr; + Vector vecMidPoint;// halfway point between Spot1 and Spot2 + Vector vecApex;// highest point + Vector vecScale; + Vector vecGrenadeVel; + Vector vecTemp; + float flGravity = g_psv_gravity->value * flGravityAdj; + + if (vecSpot2.z - vecSpot1.z > 500) + { + // to high, fail + return g_vecZero; + } + + UTIL_MakeVectors (pev->angles); + + // toss a little bit to the left or right, not right down on the enemy's bean (head). + vecSpot2 = vecSpot2 + gpGlobals->v_right * ( RANDOM_FLOAT(-8,8) + RANDOM_FLOAT(-16,16) ); + vecSpot2 = vecSpot2 + gpGlobals->v_forward * ( RANDOM_FLOAT(-8,8) + RANDOM_FLOAT(-16,16) ); + + // calculate the midpoint and apex of the 'triangle' + // UNDONE: normalize any Z position differences between spot1 and spot2 so that triangle is always RIGHT + + // How much time does it take to get there? + + // get a rough idea of how high it can be thrown + vecMidPoint = vecSpot1 + (vecSpot2 - vecSpot1) * 0.5; + UTIL_TraceLine(vecMidPoint, vecMidPoint + Vector(0,0,500), ignore_monsters, ENT(pev), &tr); + vecMidPoint = tr.vecEndPos; + // (subtract 15 so the grenade doesn't hit the ceiling) + vecMidPoint.z -= 15; + + if (vecMidPoint.z < vecSpot1.z || vecMidPoint.z < vecSpot2.z) + { + // to not enough space, fail + return g_vecZero; + } + + // How high should the grenade travel to reach the apex + float distance1 = (vecMidPoint.z - vecSpot1.z); + float distance2 = (vecMidPoint.z - vecSpot2.z); + + // How long will it take for the grenade to travel this distance + float time1 = sqrt( distance1 / (0.5 * flGravity) ); + float time2 = sqrt( distance2 / (0.5 * flGravity) ); + + if (time1 < 0.1) + { + // too close + return g_vecZero; + } + + // how hard to throw sideways to get there in time. + vecGrenadeVel = (vecSpot2 - vecSpot1) / (time1 + time2); + // how hard upwards to reach the apex at the right time. + vecGrenadeVel.z = flGravity * time1; + + // find the apex + vecApex = vecSpot1 + vecGrenadeVel * time1; + vecApex.z = vecMidPoint.z; + + UTIL_TraceLine(vecSpot1, vecApex, dont_ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + // UNDONE: either ignore monsters or change it to not care if we hit our enemy + UTIL_TraceLine(vecSpot2, vecApex, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + return vecGrenadeVel; +} + + +// +// VecCheckThrow - returns the velocity vector at which an object should be thrown from vecspot1 to hit vecspot2. +// returns g_vecZero if throw is not feasible. +// +Vector VecCheckThrow ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flSpeed, float flGravityAdj ) +{ + float flGravity = g_psv_gravity->value * flGravityAdj; + + Vector vecGrenadeVel = (vecSpot2 - vecSpot1); + + // throw at a constant time + float time = vecGrenadeVel.Length( ) / flSpeed; + vecGrenadeVel = vecGrenadeVel * (1.0 / time); + + // adjust upward toss to compensate for gravity loss + vecGrenadeVel.z += flGravity * time * 0.5; + + Vector vecApex = vecSpot1 + (vecSpot2 - vecSpot1) * 0.5; + vecApex.z += 0.5 * flGravity * (time * 0.5) * (time * 0.5); + + TraceResult tr; + UTIL_TraceLine(vecSpot1, vecApex, dont_ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + UTIL_TraceLine(vecSpot2, vecApex, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + return vecGrenadeVel; +} + + diff --git a/dlls/h_battery.cpp b/dlls/h_battery.cpp new file mode 100644 index 0000000..5ea056e --- /dev/null +++ b/dlls/h_battery.cpp @@ -0,0 +1,223 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== h_battery.cpp ======================================================== + + battery-related code + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "skill.h" +#include "gamerules.h" +#include "player.h" + +class CRecharge : public CBaseToggle +{ +public: + void Spawn( ); + void Precache( void ); + void EXPORT Off(void); + void EXPORT Recharge(void); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return (CBaseToggle :: ObjectCaps() | FCAP_CONTINUOUS_USE) & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual STATE GetState( void ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flNextCharge; + int m_iReactivate ; // DeathMatch Delay until reactvated + int m_iJuice; + int m_iOn; // 0 = off, 1 = startup, 2 = going + float m_flSoundTime; +}; + +TYPEDESCRIPTION CRecharge::m_SaveData[] = +{ + DEFINE_FIELD( CRecharge, m_flNextCharge, FIELD_TIME ), + DEFINE_FIELD( CRecharge, m_iReactivate, FIELD_INTEGER), + DEFINE_FIELD( CRecharge, m_iJuice, FIELD_INTEGER), + DEFINE_FIELD( CRecharge, m_iOn, FIELD_INTEGER), + DEFINE_FIELD( CRecharge, m_flSoundTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CRecharge, CBaseEntity ); + +LINK_ENTITY_TO_CLASS(func_recharge, CRecharge); + + +void CRecharge::KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "style") || + FStrEq(pkvd->szKeyName, "height") || + FStrEq(pkvd->szKeyName, "value1") || + FStrEq(pkvd->szKeyName, "value2") || + FStrEq(pkvd->szKeyName, "value3")) + { + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "dmdelay")) + { + m_iReactivate = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CRecharge::Spawn() +{ + Precache( ); + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(this, pev->origin); // set size and link into world + UTIL_SetSize(pev, pev->mins, pev->maxs); + SET_MODEL(ENT(pev), STRING(pev->model) ); + m_iJuice = gSkillData.suitchargerCapacity; + pev->frame = 0; + //LRC +// if (m_iStyle >= 32) LIGHT_STYLE(m_iStyle, "a"); buz +// else if (m_iStyle <= -32) LIGHT_STYLE(-m_iStyle, "z"); +} + +void CRecharge::Precache() +{ + PRECACHE_SOUND("items/suitcharge1.wav"); + PRECACHE_SOUND("items/suitchargeno1.wav"); + PRECACHE_SOUND("items/suitchargeok1.wav"); +} + + +void CRecharge::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // if it's not a player, ignore + if (!pActivator->IsPlayer() ) + return; + + // if there is no juice left, turn it off + if (m_iJuice <= 0) + { + pev->frame = 1; + //LRC +// if (m_iStyle >= 32) LIGHT_STYLE(m_iStyle, "z"); buz +// else if (m_iStyle <= -32) LIGHT_STYLE(-m_iStyle, "a"); + Off(); + } + + CBasePlayer *pPlayer = (CBasePlayer *)pActivator; + + // if the player doesn't have the suit, or there is no juice left, make the deny noise + if ((m_iJuice <= 0) || !FBitSet( pPlayer->m_iHideHUD, ITEM_SUIT )) + { + if (m_flSoundTime <= gpGlobals->time) + { + m_flSoundTime = gpGlobals->time + 0.62; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/suitchargeno1.wav", 0.85, ATTN_NORM ); + } + return; + } + + SetNextThink( 0.25 ); + SetThink(&CRecharge::Off); + + // Time to recharge yet? + + if (m_flNextCharge >= gpGlobals->time) + return; + + // Make sure that we have a caller + if (!pActivator) + return; + + m_hActivator = pActivator; + + //only recharge the player + + if (!m_hActivator->IsPlayer() ) + return; + + // Play the on sound or the looping charging sound + if (!m_iOn) + { + m_iOn++; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/suitchargeok1.wav", 0.85, ATTN_NORM ); + m_flSoundTime = 0.56 + gpGlobals->time; + } + if ((m_iOn == 1) && (m_flSoundTime <= gpGlobals->time)) + { + m_iOn++; + EMIT_SOUND(ENT(pev), CHAN_STATIC, "items/suitcharge1.wav", 0.85, ATTN_NORM ); + } + + + // charge the player + if (m_hActivator->pev->armorvalue < 100) + { + m_iJuice--; + m_hActivator->pev->armorvalue += 1; + + if (m_hActivator->pev->armorvalue > 100) + m_hActivator->pev->armorvalue = 100; + } + + // govern the rate of charge + m_flNextCharge = gpGlobals->time + 0.1; +} + +void CRecharge::Recharge(void) +{ + m_iJuice = gSkillData.suitchargerCapacity; + pev->frame = 0; + //LRC +// if (m_iStyle >= 32) LIGHT_STYLE(m_iStyle, "a"); buz +// else if (m_iStyle <= -32) LIGHT_STYLE(-m_iStyle, "z"); + SetThink(&CRecharge:: SUB_DoNothing ); +} + +void CRecharge::Off(void) +{ + // Stop looping sound. + if (m_iOn > 1) + STOP_SOUND( ENT(pev), CHAN_STATIC, "items/suitcharge1.wav" ); + + m_iOn = 0; + + if ((!m_iJuice) && ( ( m_iReactivate = g_pGameRules->FlHEVChargerRechargeTime() ) > 0) ) + { + SetNextThink( m_iReactivate ); + SetThink(&CRecharge::Recharge); + } + else + SetThink(&CRecharge:: SUB_DoNothing ); +} + +STATE CRecharge::GetState( void ) +{ + if (m_iOn == 2) + return STATE_IN_USE; + else if (m_iJuice) + return STATE_ON; + else + return STATE_OFF; +} diff --git a/dlls/h_cine.cpp b/dlls/h_cine.cpp new file mode 100644 index 0000000..6d96cdc --- /dev/null +++ b/dlls/h_cine.cpp @@ -0,0 +1,240 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +/* + +===== h_cine.cpp ======================================================== + + The Halflife hard coded "scripted sequence". + + I'm pretty sure all this code is obsolete + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "decals.h" + + +class CLegacyCineMonster : public CBaseMonster +{ +public: + void CineSpawn( char *szModel ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + float MaxYawSpeed( void ) { return 10.0f; } + void EXPORT CineThink( void ); + void Pain( void ); + void Die( void ); +}; + +class CCineScientist : public CLegacyCineMonster +{ +public: + void Spawn( void ) { CineSpawn("models/cine-scientist.mdl"); } +}; +class CCine2Scientist : public CLegacyCineMonster +{ +public: + void Spawn( void ) { CineSpawn("models/cine2-scientist.mdl"); } +}; +class CCinePanther : public CLegacyCineMonster +{ +public: + void Spawn( void ) { CineSpawn("models/cine-panther.mdl"); } +}; + +class CCineBarney : public CLegacyCineMonster +{ +public: + void Spawn( void ) { CineSpawn("models/cine-barney.mdl"); } +}; + +class CCine2HeavyWeapons : public CLegacyCineMonster +{ +public: + void Spawn( void ) { CineSpawn("models/cine2_hvyweapons.mdl"); } +}; + +class CCine2Slave : public CLegacyCineMonster +{ +public: + void Spawn( void ) { CineSpawn("models/cine2_slave.mdl"); } +}; + +class CCine3Scientist : public CLegacyCineMonster +{ +public: + void Spawn( void ) { CineSpawn("models/cine3-scientist.mdl"); } +}; + +class CCine3Barney : public CLegacyCineMonster +{ +public: + void Spawn( void ) { CineSpawn("models/cine3-barney.mdl"); } +}; + +// +// ********** Scientist SPAWN ********** +// + +LINK_ENTITY_TO_CLASS( monster_cine_scientist, CCineScientist ); +LINK_ENTITY_TO_CLASS( monster_cine_panther, CCinePanther ); +LINK_ENTITY_TO_CLASS( monster_cine_barney, CCineBarney ); +LINK_ENTITY_TO_CLASS( monster_cine2_scientist, CCine2Scientist ); +LINK_ENTITY_TO_CLASS( monster_cine2_hvyweapons, CCine2HeavyWeapons ); +LINK_ENTITY_TO_CLASS( monster_cine2_slave, CCine2Slave ); +LINK_ENTITY_TO_CLASS( monster_cine3_scientist, CCine3Scientist ); +LINK_ENTITY_TO_CLASS( monster_cine3_barney, CCine3Barney ); + +// +// ********** Scientist SPAWN ********** +// + +void CLegacyCineMonster :: CineSpawn( char *szModel ) +{ + PRECACHE_MODEL(szModel); + SET_MODEL(ENT(pev), szModel); + UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 64)); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + pev->health = 1; + + // ugly alpha hack, can't set ints from the bsp. + pev->sequence = (int)pev->impulse; + ResetSequenceInfo( ); + pev->framerate = 0.0; + + m_bloodColor = BLOOD_COLOR_RED; + + // if no targetname, start now + if ( FStringNull(pev->targetname) ) + { + SetThink(&CLegacyCineMonster :: CineThink ); + AbsoluteNextThink( m_fNextThink + 0.1 ); + } +} + + +// +// CineStart +// +void CLegacyCineMonster :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + pev->animtime = 0; // reset the sequence + SetThink(&CLegacyCineMonster :: CineThink ); + SetNextThink( 0 ); +} + +// +// ********** Scientist DIE ********** +// +void CLegacyCineMonster :: Die( void ) +{ + SetThink(&CLegacyCineMonster :: SUB_Remove ); +} + +// +// ********** Scientist PAIN ********** +// +void CLegacyCineMonster :: Pain( void ) +{ + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pain3.wav", 1, ATTN_NORM); +} + +void CLegacyCineMonster :: CineThink( void ) +{ + // DBG_CheckMonsterData(pev); + + // Emit particles from origin (double check animator's placement of model) + // THIS is a test feature + //UTIL_ParticleEffect(pev->origin, g_vecZero, 255, 20); + + if (!pev->animtime) + ResetSequenceInfo( ); + + SetNextThink( 1.0 ); + + if (pev->spawnflags != 0 && m_fSequenceFinished) + { + Die(); + return; + } + + StudioFrameAdvance ( ); +} + +// +// cine_blood +// +// e3/prealpha only. +class CCineBlood : public CBaseEntity +{ +public: + void Spawn( void ); + void EXPORT BloodStart ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT BloodGush ( void ); +}; + +LINK_ENTITY_TO_CLASS( cine_blood, CCineBlood ); + + +void CCineBlood :: BloodGush ( void ) +{ + Vector vecSplatDir; + TraceResult tr; + SetNextThink( 0.1 ); + + UTIL_MakeVectors(pev->angles); + if ( pev->health-- < 0 ) + REMOVE_ENTITY(ENT(pev)); +// CHANGE_METHOD ( ENT(pev), em_think, SUB_Remove ); + + if ( RANDOM_FLOAT ( 0 , 1 ) < 0.7 )// larger chance of globs + { + UTIL_BloodDrips( pev->origin, UTIL_RandomBloodVector(), BLOOD_COLOR_RED, 10 ); + } + else// slim chance of geyser + { + UTIL_BloodStream( pev->origin, UTIL_RandomBloodVector(), BLOOD_COLOR_RED, RANDOM_LONG(50, 150) ); + } + + if ( RANDOM_FLOAT ( 0, 1 ) < 0.75 ) + {// decals the floor with blood. + vecSplatDir = Vector ( 0 , 0 , -1 ); + vecSplatDir = vecSplatDir + (RANDOM_FLOAT(-1,1) * 0.6 * gpGlobals->v_right) + (RANDOM_FLOAT(-1,1) * 0.6 * gpGlobals->v_forward);// randomize a bit + UTIL_TraceLine( pev->origin + Vector ( 0, 0 , 64) , pev->origin + vecSplatDir * 256, ignore_monsters, ENT(pev), &tr); + if ( tr.flFraction != 1.0 ) + { + // Decal with a bloodsplat + UTIL_BloodDecalTrace( &tr, BLOOD_COLOR_RED ); + } + } +} + +void CCineBlood :: BloodStart ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink(&CCineBlood :: BloodGush ); + SetNextThink( 0 );// now! +} + +void CCineBlood :: Spawn ( void ) +{ + pev->solid = SOLID_NOT; + SetUse(&CCineBlood :: BloodStart ); + pev->health = 20;//hacked health to count iterations +} + diff --git a/dlls/h_cycler.cpp b/dlls/h_cycler.cpp new file mode 100644 index 0000000..51871f0 --- /dev/null +++ b/dlls/h_cycler.cpp @@ -0,0 +1,380 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== h_cycler.cpp ======================================================== + + The Halflife Cycler Monsters + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "animation.h" +#include "weapons.h" +#include "player.h" + + +#define TEMP_FOR_SCREEN_SHOTS +#ifdef TEMP_FOR_SCREEN_SHOTS //=================================================== + +class CCycler : public CBaseMonster +{ +public: + void GenericCyclerSpawn(char *szModel, Vector vecMin, Vector vecMax); + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() | FCAP_IMPULSE_USE); } + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void Spawn( void ); + void Think( void ); + //void Pain( float flDamage ); + void Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + float MaxYawSpeed( void ) { return 5.0f; } + // Don't treat as a live target + virtual BOOL IsAlive( void ) { return FALSE; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_animate; +}; + +TYPEDESCRIPTION CCycler::m_SaveData[] = +{ + DEFINE_FIELD( CCycler, m_animate, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CCycler, CBaseMonster ); + + +// +// we should get rid of all the other cyclers and replace them with this. +// +class CGenericCycler : public CCycler +{ +public: + void Spawn( void ) { GenericCyclerSpawn( (char *)STRING(pev->model), Vector(-16, -16, 0), Vector(16, 16, 72) ); } +}; +LINK_ENTITY_TO_CLASS( cycler, CGenericCycler ); + + + +// Probe droid imported for tech demo compatibility +// +// PROBE DROID +// +class CCyclerProbe : public CCycler +{ +public: + void Spawn( void ); +}; +LINK_ENTITY_TO_CLASS( cycler_prdroid, CCyclerProbe ); +void CCyclerProbe :: Spawn( void ) +{ + pev->origin = pev->origin + Vector ( 0, 0, 16 ); + GenericCyclerSpawn( "models/prdroid.mdl", Vector(-16,-16,-16), Vector(16,16,16)); +} + + + +// Cycler member functions + +void CCycler :: GenericCyclerSpawn(char *szModel, Vector vecMin, Vector vecMax) +{ + if (!szModel || !*szModel) + { + ALERT(at_error, "cycler at %.0f %.0f %0.f missing modelname", pev->origin.x, pev->origin.y, pev->origin.z ); + REMOVE_ENTITY(ENT(pev)); + return; + } + + pev->classname = MAKE_STRING("cycler"); + PRECACHE_MODEL( szModel ); + SET_MODEL(ENT(pev), szModel); + + CCycler::Spawn( ); + + UTIL_SetSize(pev, vecMin, vecMax); +} + + +void CCycler :: Spawn( ) +{ + InitBoneControllers(); + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_NONE; + pev->takedamage = DAMAGE_YES; + pev->health = 80000;// no cycler should die + + SetIdealYaw( pev->angles.y ); + SnapYaw(); + + m_flFrameRate = 75; + m_flGroundSpeed = 0; + + AbsoluteNextThink( m_fNextThink + 1.0 ); + + ResetSequenceInfo( ); + + if (pev->sequence != 0 || pev->frame != 0) + { + m_animate = 0; + pev->framerate = 0; + } + else + { + m_animate = 1; + } +} + + + + +// +// cycler think +// +void CCycler :: Think( void ) +{ + SetNextThink( 0.1 ); + + if (m_animate) + { + StudioFrameAdvance ( ); + } + if (m_fSequenceFinished && !m_fSequenceLoops) + { + // ResetSequenceInfo(); + // hack to avoid reloading model every frame + pev->animtime = gpGlobals->time; + pev->framerate = 1.0; + m_fSequenceFinished = FALSE; + m_flLastEventCheck = gpGlobals->time; + pev->frame = 0; + if (!m_animate) + pev->framerate = 0.0; // FIX: don't reset framerate + } +} + +// +// CyclerUse - starts a rotation trend +// +void CCycler :: Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_animate = !m_animate; + if (m_animate) + pev->framerate = 1.0; + else + pev->framerate = 0.0; +} + +// +// CyclerPain , changes sequences when shot +// +//void CCycler :: Pain( float flDamage ) +int CCycler :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if (m_animate) + { + pev->sequence++; + + ResetSequenceInfo( ); + + if (m_flFrameRate == 0.0) + { + pev->sequence = 0; + ResetSequenceInfo( ); + } + pev->frame = 0; + } + else + { + pev->framerate = 1.0; + StudioFrameAdvance ( 0.1 ); + pev->framerate = 0; + ALERT( at_debug, "sequence: %d, frame %.0f\n", pev->sequence, pev->frame ); + } + + return 0; +} + +#endif + + +class CCyclerSprite : public CBaseEntity +{ +public: + void Spawn( void ); + void Think( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() | FCAP_IMPULSE_USE); } + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void Animate( float frames ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + inline int ShouldAnimate( void ) { return m_animate && m_maxFrame > 1.0; } + int m_animate; + float m_lastTime; + float m_maxFrame; +}; + +LINK_ENTITY_TO_CLASS( cycler_sprite, CCyclerSprite ); + +TYPEDESCRIPTION CCyclerSprite::m_SaveData[] = +{ + DEFINE_FIELD( CCyclerSprite, m_animate, FIELD_INTEGER ), + DEFINE_FIELD( CCyclerSprite, m_lastTime, FIELD_TIME ), + DEFINE_FIELD( CCyclerSprite, m_maxFrame, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CCyclerSprite, CBaseEntity ); + + +void CCyclerSprite::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->takedamage = DAMAGE_YES; + + pev->frame = 0; + SetNextThink( 0.1 ); + m_animate = 1; + m_lastTime = gpGlobals->time; + + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL( ENT(pev), STRING(pev->model) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; +} + + +void CCyclerSprite::Think( void ) +{ + if ( ShouldAnimate() ) + Animate( pev->framerate * (gpGlobals->time - m_lastTime) ); + + SetNextThink( 0.1 ); + m_lastTime = gpGlobals->time; +} + + +void CCyclerSprite::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_animate = !m_animate; + ALERT( at_debug, "Sprite: %s\n", STRING(pev->model) ); +} + + +int CCyclerSprite::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( m_maxFrame > 1.0 ) + { + Animate( 1.0 ); + } + return 1; +} + +void CCyclerSprite::Animate( float frames ) +{ + pev->frame += frames; + if ( m_maxFrame > 0 ) + pev->frame = fmod( pev->frame, m_maxFrame ); +} + +// Flaming Wreakage +class CWreckage : public CBaseMonster +{ + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + void Think( void ); + + int m_flStartTime; +}; +TYPEDESCRIPTION CWreckage::m_SaveData[] = +{ + DEFINE_FIELD( CWreckage, m_flStartTime, FIELD_TIME ), +}; +IMPLEMENT_SAVERESTORE( CWreckage, CBaseMonster ); + + +LINK_ENTITY_TO_CLASS( cycler_wreckage, CWreckage ); + +void CWreckage::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->takedamage = 0; + + pev->frame = 0; + SetNextThink( 0.1 ); + + if (pev->model) + { + PRECACHE_MODEL( (char *)STRING(pev->model) ); + SET_MODEL( ENT(pev), STRING(pev->model) ); + } + // pev->scale = 5.0; + + m_flStartTime = gpGlobals->time; +} + +void CWreckage::Precache( ) +{ + if ( pev->model ) + PRECACHE_MODEL( (char *)STRING(pev->model) ); +} + +void CWreckage::Think( void ) +{ + StudioFrameAdvance( ); + SetNextThink( 0.2 ); + + if (pev->dmgtime) + { + if (pev->dmgtime < gpGlobals->time) + { + UTIL_Remove( this ); + return; + } + else if (RANDOM_FLOAT( 0, pev->dmgtime - m_flStartTime ) > pev->dmgtime - gpGlobals->time) + { + return; + } + } + + Vector VecSrc; + + VecSrc.x = RANDOM_FLOAT( pev->absmin.x, pev->absmax.x ); + VecSrc.y = RANDOM_FLOAT( pev->absmin.y, pev->absmax.y ); + VecSrc.z = RANDOM_FLOAT( pev->absmin.z, pev->absmax.z ); + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, VecSrc ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( VecSrc.x ); + WRITE_COORD( VecSrc.y ); + WRITE_COORD( VecSrc.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( RANDOM_LONG(0,49) + 50 ); // scale * 10 + WRITE_BYTE( RANDOM_LONG(0, 3) + 8 ); // framerate + MESSAGE_END(); +} diff --git a/dlls/h_export.cpp b/dlls/h_export.cpp new file mode 100644 index 0000000..c7e8ab6 --- /dev/null +++ b/dlls/h_export.cpp @@ -0,0 +1,80 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== h_export.cpp ======================================================== + + Entity classes exported by Halflife. + +*/ + +#include "extdll.h" +#include "util.h" + +#include "cbase.h" + +// Holds engine functionality callbacks +enginefuncs_t g_engfuncs; +globalvars_t *gpGlobals; +server_physics_api_t g_physfuncs; + +#ifdef _WIN32 + +// Required DLL entry point +BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved ) +{ + if( fdwReason == DLL_PROCESS_ATTACH ) + { + } + else if( fdwReason == DLL_PROCESS_DETACH ) + { + } + return TRUE; +} + +void DLLEXPORT GiveFnptrsToDll( enginefuncs_t* pengfuncsFromEngine, globalvars_t *pGlobals ) +{ + memcpy(&g_engfuncs, pengfuncsFromEngine, sizeof(enginefuncs_t)); + gpGlobals = pGlobals; + + if( CVAR_GET_POINTER( "host_gameloaded" )) + g_fXashEngine = TRUE; // Xash3D engine detected + + g_iXashEngineBuildNumber = (int)CVAR_GET_FLOAT( "build" ); // 0 for old builds or GoldSrc + if( g_iXashEngineBuildNumber <= 0 ) + g_iXashEngineBuildNumber = (int)CVAR_GET_FLOAT( "buildnum" ); +} + +#else + +extern "C" +{ + +void GiveFnptrsToDll( enginefuncs_t* pengfuncsFromEngine, globalvars_t *pGlobals ) +{ + memcpy(&g_engfuncs, pengfuncsFromEngine, sizeof(enginefuncs_t)); + gpGlobals = pGlobals; + + if( CVAR_GET_POINTER( "host_gameloaded" )) + g_fXashEngine = TRUE; // Xash3D engine detected + + g_iXashEngineBuildNumber = (int)CVAR_GET_FLOAT( "build" ); // 0 for old builds or GoldSrc + if( g_iXashEngineBuildNumber <= 0 ) + g_iXashEngineBuildNumber = (int)CVAR_GET_FLOAT( "buildnum" ); +} + +} + +#endif \ No newline at end of file diff --git a/dlls/handgrenade.cpp b/dlls/handgrenade.cpp new file mode 100644 index 0000000..b49077b --- /dev/null +++ b/dlls/handgrenade.cpp @@ -0,0 +1,156 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "player.h" + +class CHandGrenade : public CBasePlayerItem +{ +public: + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void PrimaryAttack( void ); + void Deploy( void ); + BOOL CanHolster( void ); + void WeaponIdle( void ); + + float m_flStartThrow; + float m_flReleaseThrow; +}; + +TYPEDESCRIPTION CHandGrenade :: m_SaveData[] = +{ + DEFINE_FIELD( CHandGrenade, m_flStartThrow, FIELD_TIME ), + DEFINE_FIELD( CHandGrenade, m_flReleaseThrow, FIELD_TIME ), +}; IMPLEMENT_SAVERESTORE( CHandGrenade, CBasePlayerItem ); + +LINK_ENTITY_TO_CLASS( weapon_handgrenade, CHandGrenade ); + +void CHandGrenade :: Deploy( void ) +{ + m_flReleaseThrow = -1.0f; + DefaultDeploy( ACT_VM_DEPLOY ); +} + +BOOL CHandGrenade :: CanHolster( void ) +{ + // can only holster hand grenades when not primed! + return ( m_flStartThrow == 0.0f ); +} + +void CHandGrenade :: PrimaryAttack( void ) +{ + if( !m_flStartThrow && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] > 0 ) + { + m_flStartThrow = gpGlobals->time; + m_flReleaseThrow = 0.0f; + SetAnimation( ACT_VM_START_CHARGE ); + } +} + +void CHandGrenade :: WeaponIdle( void ) +{ + if ( m_flTimeWeaponIdle > UTIL_WeaponTimeBase( )) + return; + + if ( m_flStartThrow ) + { + if ( m_flReleaseThrow > 0.0f ) + { + // buz: óæå êèíóëè, òåïåðü íàäî äîñòàòü íîâóþ èëè ïåðåêëþ÷èòü îðóæèå + m_flStartThrow = 0.0f; + m_flReleaseThrow = -1.0f; + + if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] > 0 ) + { + SetAnimation( ACT_VM_DEPLOY ); + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + RANDOM_FLOAT( 10.0f, 15.0f ); + } + else + { + RetireWeapon(); + } + return; + } + + AmmoInfo *pInfo = UTIL_FindAmmoType( pszAmmo1() ); + + float flDamage = gSkillData.plrDmgHandGrenade; + float flDistance = 500.0f; + + if( pInfo != NULL ) + { + // overwrite default values + flDamage = pInfo->flPlayerDamage; + flDistance = pInfo->flDistance; + } + + m_flReleaseThrow = gpGlobals->time; + Vector angThrow = m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle; + + if ( angThrow.x < 0 ) + angThrow.x = -10.0f + angThrow.x * ( ( 90.0f - 10.0f ) / 90.0f ); + else angThrow.x = -10.0f + angThrow.x * ( ( 90.0f + 10.0f ) / 90.0f ); + + float flVel = ( 90.0f - angThrow.x ) * 4.0f; + if ( flVel > flDistance ) + flVel = flDistance; + + UTIL_MakeVectors( angThrow ); + + Vector vecSrc = m_pPlayer->GetGunPosition() + gpGlobals->v_forward * vecThrowOffset().x + + gpGlobals->v_right * vecThrowOffset().y + gpGlobals->v_up * vecThrowOffset().z; + Vector vecThrow = gpGlobals->v_forward * flVel + m_pPlayer->pev->velocity; + + // alway explode 3 seconds after the pin was pulled + float time = m_flStartThrow - gpGlobals->time + 3.0f; + if( time < 0.0f ) time = 0.0f; + float framerate = 1.0f; + + CGrenade :: ShootTimed( m_pPlayer->pev, vecSrc, vecThrow, time, flDamage ); + + // use single anim with various framerate instead of three anims + if ( flVel < 500 ) + framerate = 0.8f; + else if ( flVel < 1000 ) + framerate = 1.2f; + else framerate = 1.8f; + + SetAnimation( ACT_VM_MELEE_ATTACK, framerate ); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + float flNextAttack = fNextAttack1(); + if( flNextAttack == -1.0f ) + flNextAttack = SequenceDuration(); + + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + flNextAttack; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + SequenceDuration() + 0.2f; + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + return; + } + + if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] ) + { + SetAnimation( ACT_VM_IDLE ); + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + RANDOM_FLOAT( 10.0f, 15.0f ); + } +} \ No newline at end of file diff --git a/dlls/hassassin.cpp b/dlls/hassassin.cpp new file mode 100644 index 0000000..92a7360 --- /dev/null +++ b/dlls/hassassin.cpp @@ -0,0 +1,1067 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +//========================================================= +// hassassin - Human assassin, fast and stealthy +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "squadmonster.h" +#include "weapons.h" +#include "soundent.h" +#include "scripted.h" +#include "game.h" + +extern DLL_GLOBAL int g_iSkillLevel; + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_ASSASSIN_EXPOSED = LAST_COMMON_SCHEDULE + 1,// cover was blown. + SCHED_ASSASSIN_JUMP, // fly through the air + SCHED_ASSASSIN_JUMP_ATTACK, // fly through the air and shoot + SCHED_ASSASSIN_JUMP_LAND, // hit and run away +}; + +//========================================================= +// monster-specific tasks +//========================================================= + +enum +{ + TASK_ASSASSIN_FALL_TO_GROUND = LAST_COMMON_TASK + 1, // falling and waiting to hit ground +}; + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define ASSASSIN_AE_SHOOT1 1 +#define ASSASSIN_AE_TOSS1 2 +#define ASSASSIN_AE_JUMP 3 + + +#define bits_MEMORY_BADJUMP (bits_MEMORY_CUSTOM1) + +class CHAssassin : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + float MaxYawSpeed( void ); + int Classify ( void ); + int ISoundMask ( void); + void Shoot( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + Schedule_t* GetSchedule ( void ); + Schedule_t* GetScheduleOfType ( int Type ); + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); // jump + // BOOL CheckMeleeAttack2 ( float flDot, float flDist ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); // shoot + BOOL CheckRangeAttack2 ( float flDot, float flDist ); // throw grenade + void StartTask ( Task_t *pTask ); + void RunAI( void ); + void RunTask ( Task_t *pTask ); + void DeathSound ( void ); + void IdleSound ( void ); + CUSTOM_SCHEDULES; + + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + float m_flLastShot; + float m_flDiviation; + + float m_flNextJump; + Vector m_vecJumpVelocity; + + float m_flNextGrenadeCheck; + Vector m_vecTossVelocity; + BOOL m_fThrowGrenade; + + int m_iTargetRanderamt; + + int m_iFrustration; + + int m_iShell; +}; +LINK_ENTITY_TO_CLASS( monster_human_assassin, CHAssassin ); + + +TYPEDESCRIPTION CHAssassin::m_SaveData[] = +{ + DEFINE_FIELD( CHAssassin, m_flLastShot, FIELD_TIME ), + DEFINE_FIELD( CHAssassin, m_flDiviation, FIELD_FLOAT ), + + DEFINE_FIELD( CHAssassin, m_flNextJump, FIELD_TIME ), + DEFINE_FIELD( CHAssassin, m_vecJumpVelocity, FIELD_VECTOR ), + + DEFINE_FIELD( CHAssassin, m_flNextGrenadeCheck, FIELD_TIME ), + DEFINE_FIELD( CHAssassin, m_vecTossVelocity, FIELD_VECTOR ), + DEFINE_FIELD( CHAssassin, m_fThrowGrenade, FIELD_BOOLEAN ), + + DEFINE_FIELD( CHAssassin, m_iTargetRanderamt, FIELD_INTEGER ), + DEFINE_FIELD( CHAssassin, m_iFrustration, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CHAssassin, CBaseMonster ); + + +//========================================================= +// DieSound +//========================================================= +void CHAssassin :: DeathSound ( void ) +{ +} + +//========================================================= +// IdleSound +//========================================================= +void CHAssassin :: IdleSound ( void ) +{ +} + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. +//========================================================= +int CHAssassin :: ISoundMask ( void) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_DANGER | + bits_SOUND_PLAYER; +} + + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CHAssassin :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_HUMAN_MILITARY; +} + +//========================================================= +// MaxYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +float CHAssassin :: MaxYawSpeed( void ) +{ + float ys; + + switch ( m_Activity ) + { + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 360; + break; + default: + ys = 360; + break; + } + + return ys; +} + + +//========================================================= +// Shoot +//========================================================= +void CHAssassin :: Shoot ( void ) +{ + if (m_hEnemy == NULL && !m_pCine) //LRC + { + return; + } + + Vector vecShootOrigin = GetGunPosition(); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); + + if (m_flLastShot + 2 < gpGlobals->time) + { + m_flDiviation = 0.10; + } + else + { + m_flDiviation -= 0.01; + if (m_flDiviation < 0.02) + m_flDiviation = 0.02; + } + m_flLastShot = gpGlobals->time; + + UTIL_MakeVectors ( pev->angles ); + + Vector vecShellVelocity = gpGlobals->v_right * RANDOM_FLOAT(40,90) + gpGlobals->v_up * RANDOM_FLOAT(75,200) + gpGlobals->v_forward * RANDOM_FLOAT(-40, 40); + EjectBrass( pev->origin + gpGlobals->v_up * 32 + gpGlobals->v_forward * 12, vecShellVelocity, pev->angles.y, m_iShell, TE_BOUNCE_SHELL); + FireBullets( 1, vecShootOrigin, vecShootDir, Vector( m_flDiviation, m_flDiviation, m_flDiviation ), 2048.0f, BULLET_NORMAL, gSkillData.monDmg9MM ); // shoot +-8 degrees + + switch(RANDOM_LONG(0,1)) + { + case 0: + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/pl_gun1.wav", RANDOM_FLOAT(0.6, 0.8), ATTN_NORM); + break; + case 1: + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/pl_gun2.wav", RANDOM_FLOAT(0.6, 0.8), ATTN_NORM); + break; + } + + pev->effects |= EF_MUZZLEFLASH; + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); + + m_cAmmoLoaded--; +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CHAssassin :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case ASSASSIN_AE_SHOOT1: + Shoot( ); + break; + case ASSASSIN_AE_TOSS1: + { + Vector vecGunPosition = pev->origin + gpGlobals->v_forward * 34 + Vector (0, 0, 32); + UTIL_MakeVectors( pev->angles ); + //LRC + if (m_pCine && m_pCine->IsAction()) + { + Vector vecToss; + if (m_pCine->PreciseAttack() && m_hTargetEnt != NULL) + { + vecToss = VecCheckToss( pev, vecGunPosition, m_hTargetEnt->pev->origin, 0.5 ); + //if (vecToss != g_vecZero) + // ALERT(at_console,"Assassin %s throws precise grenade\n",STRING(pev->targetname)); + } + else + { + //ALERT(at_console,"Assassin %s throws nonprecise grenade\n",STRING(pev->targetname)); + // what speed would be best to use, here? Borrowing the hgrunt grenade speed seems silly... + vecToss = ((gpGlobals->v_forward*0.5)+(gpGlobals->v_up*0.5)).Normalize()*gSkillData.hgruntGrenadeSpeed; + } + CGrenade::ShootTimed( pev, vecGunPosition, vecToss, 2.0 ); + } + else + CGrenade::ShootTimed( pev, vecGunPosition, m_vecTossVelocity, 2.0 ); + + m_flNextGrenadeCheck = gpGlobals->time + 6;// wait six seconds before even looking again to see if a grenade can be thrown. + m_fThrowGrenade = FALSE; + // !!!LATER - when in a group, only try to throw grenade if ordered. + } + break; + case ASSASSIN_AE_JUMP: + { + // ALERT( at_console, "jumping"); + UTIL_MakeAimVectors( pev->angles ); + pev->movetype = MOVETYPE_TOSS; + pev->flags &= ~FL_ONGROUND; + if (m_pCine) //LRC... + { + pev->velocity = g_vecZero; + if (m_pCine->PreciseAttack() && m_hTargetEnt != NULL) + { + Vector vecTemp = m_hTargetEnt->pev->origin; + vecTemp.y = vecTemp.y + 50; // put her feet on the target. + pev->velocity = VecCheckToss( pev, pev->origin, vecTemp, 0.5 ); + //if (pev->velocity != g_vecZero) + // ALERT(at_console,"Precise jump for assassin %s\n",STRING(pev->targetname)); + //else + // ALERT(at_console,"Precise jump failed. "); + } + if (pev->velocity == g_vecZero) + { // just jump, it doesn't matter where to. + //ALERT(at_console,"Nonprecise jump for assassin %s\n",STRING(pev->targetname)); + float flGravity = g_psv_gravity->value; + float time = sqrt( 160 / (0.5 * flGravity)); + float speed = flGravity * time / 160; + UTIL_MakeVectors(pev->angles); + Vector vecDest = pev->origin + (gpGlobals->v_forward * 32); + vecDest.z += 160; // don't forget to jump into the air, now... + pev->velocity= (vecDest - pev->origin) * speed; + } + } + else + pev->velocity = m_vecJumpVelocity; + m_flNextJump = gpGlobals->time + 3.0; + } + return; + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CHAssassin :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/hassassin.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + if (pev->health == 0) + pev->health = gSkillData.hassassinHealth; + m_flFieldOfView = VIEW_FIELD_WIDE; // indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_afCapability = bits_CAP_MELEE_ATTACK1 | bits_CAP_DOORS_GROUP; + pev->friction = 1; + + m_HackedGunPos = Vector( 0, 24, 48 ); + + m_iTargetRanderamt = 20; + pev->renderamt = 20; + pev->rendermode = kRenderTransTexture; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CHAssassin :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/hassassin.mdl"); + + PRECACHE_SOUND("weapons/pl_gun1.wav"); + PRECACHE_SOUND("weapons/pl_gun2.wav"); + + PRECACHE_SOUND("debris/beamstart1.wav"); + + m_iShell = PRECACHE_MODEL ("models/shell.mdl");// brass shell +} + + + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +//========================================================= +// Fail Schedule +//========================================================= +Task_t tlAssassinFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, + // { TASK_WAIT_PVS, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_CHASE_ENEMY }, +}; + +Schedule_t slAssassinFail[] = +{ + { + tlAssassinFail, + ARRAYSIZE ( tlAssassinFail ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER | + bits_SOUND_PLAYER, + "AssassinFail" + }, +}; + + +//========================================================= +// Enemy exposed Agrunt's cover +//========================================================= +Task_t tlAssassinExposed[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_ASSASSIN_JUMP }, + { TASK_SET_SCHEDULE, (float)SCHED_TAKE_COVER_FROM_ENEMY }, +}; + +Schedule_t slAssassinExposed[] = +{ + { + tlAssassinExposed, + ARRAYSIZE ( tlAssassinExposed ), + bits_COND_CAN_MELEE_ATTACK1, + 0, + "AssassinExposed", + }, +}; + + +//========================================================= +// Take cover from enemy! Tries lateral cover before node +// cover! +//========================================================= +Task_t tlAssassinTakeCoverFromEnemy[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_WAIT, (float)0.2 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RANGE_ATTACK1 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_FACE_ENEMY, (float)0 }, +}; + +Schedule_t slAssassinTakeCoverFromEnemy[] = +{ + { + tlAssassinTakeCoverFromEnemy, + ARRAYSIZE ( tlAssassinTakeCoverFromEnemy ), + bits_COND_NEW_ENEMY | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "AssassinTakeCoverFromEnemy" + }, +}; + + +//========================================================= +// Take cover from enemy! Tries lateral cover before node +// cover! +//========================================================= +Task_t tlAssassinTakeCoverFromEnemy2[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_WAIT, (float)0.2 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RANGE_ATTACK2 }, + { TASK_FIND_FAR_NODE_COVER_FROM_ENEMY, (float)384 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_FACE_ENEMY, (float)0 }, +}; + +Schedule_t slAssassinTakeCoverFromEnemy2[] = +{ + { + tlAssassinTakeCoverFromEnemy2, + ARRAYSIZE ( tlAssassinTakeCoverFromEnemy2 ), + bits_COND_NEW_ENEMY | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "AssassinTakeCoverFromEnemy2" + }, +}; + + +//========================================================= +// hide from the loudest sound source +//========================================================= +Task_t tlAssassinTakeCoverFromBestSound[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_MELEE_ATTACK1 }, + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_BEST_SOUND, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_TURN_LEFT, (float)179 }, +}; + +Schedule_t slAssassinTakeCoverFromBestSound[] = +{ + { + tlAssassinTakeCoverFromBestSound, + ARRAYSIZE ( tlAssassinTakeCoverFromBestSound ), + bits_COND_NEW_ENEMY, + 0, + "AssassinTakeCoverFromBestSound" + }, +}; + + + + + +//========================================================= +// AlertIdle Schedules +//========================================================= +Task_t tlAssassinHide[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_SET_SCHEDULE, (float)SCHED_CHASE_ENEMY }, +}; + +Schedule_t slAssassinHide[] = +{ + { + tlAssassinHide, + ARRAYSIZE ( tlAssassinHide ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_ENEMY | + bits_COND_SEE_FEAR | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "AssassinHide" + }, +}; + + + +//========================================================= +// HUNT Schedules +//========================================================= +Task_t tlAssassinHunt[] = +{ + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slAssassinHunt[] = +{ + { + tlAssassinHunt, + ARRAYSIZE ( tlAssassinHunt ), + bits_COND_NEW_ENEMY | + // bits_COND_SEE_ENEMY | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "AssassinHunt" + }, +}; + + +//========================================================= +// Jumping Schedules +//========================================================= +Task_t tlAssassinJump[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_HOP }, + { TASK_SET_SCHEDULE, (float)SCHED_ASSASSIN_JUMP_ATTACK }, +}; + +Schedule_t slAssassinJump[] = +{ + { + tlAssassinJump, + ARRAYSIZE ( tlAssassinJump ), + 0, + 0, + "AssassinJump" + }, +}; + + +//========================================================= +// repel +//========================================================= +Task_t tlAssassinJumpAttack[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_ASSASSIN_JUMP_LAND }, + // { TASK_SET_ACTIVITY, (float)ACT_FLY }, + { TASK_ASSASSIN_FALL_TO_GROUND, (float)0 }, +}; + + +Schedule_t slAssassinJumpAttack[] = +{ + { + tlAssassinJumpAttack, + ARRAYSIZE ( tlAssassinJumpAttack ), + 0, + 0, + "AssassinJumpAttack" + }, +}; + + +//========================================================= +// repel +//========================================================= +Task_t tlAssassinJumpLand[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_ASSASSIN_EXPOSED }, + // { TASK_SET_FAIL_SCHEDULE, (float)SCHED_MELEE_ATTACK1 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_REMEMBER, (float)bits_MEMORY_BADJUMP }, + { TASK_FIND_NODE_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_FORGET, (float)bits_MEMORY_BADJUMP }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RANGE_ATTACK1 }, +}; + +Schedule_t slAssassinJumpLand[] = +{ + { + tlAssassinJumpLand, + ARRAYSIZE ( tlAssassinJumpLand ), + 0, + 0, + "AssassinJumpLand" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CHAssassin ) +{ + slAssassinFail, + slAssassinExposed, + slAssassinTakeCoverFromEnemy, + slAssassinTakeCoverFromEnemy2, + slAssassinTakeCoverFromBestSound, + slAssassinHide, + slAssassinHunt, + slAssassinJump, + slAssassinJumpAttack, + slAssassinJumpLand, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CHAssassin, CBaseMonster ); + + +//========================================================= +// CheckMeleeAttack1 - jump like crazy if the enemy gets too close. +//========================================================= +BOOL CHAssassin :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + if ( m_flNextJump < gpGlobals->time && (flDist <= 128 || HasMemory( bits_MEMORY_BADJUMP )) && m_hEnemy != NULL ) + { + TraceResult tr; + + Vector vecDest = pev->origin + Vector( RANDOM_FLOAT( -64, 64), RANDOM_FLOAT( -64, 64 ), 160 ); + + UTIL_TraceHull( pev->origin + Vector( 0, 0, 36 ), vecDest + Vector( 0, 0, 36 ), dont_ignore_monsters, human_hull, ENT(pev), &tr); + + if ( tr.fStartSolid || tr.flFraction < 1.0) + { + return FALSE; + } + + float flGravity = g_psv_gravity->value; + + float time = sqrt( 160 / (0.5 * flGravity)); + float speed = flGravity * time / 160; + m_vecJumpVelocity = (vecDest - pev->origin) * speed; + + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack1 - drop a cap in their ass +// +//========================================================= +BOOL CHAssassin :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( !HasConditions( bits_COND_ENEMY_OCCLUDED ) && flDist > 64 && flDist <= 2048 /* && flDot >= 0.5 */ /* && NoFriendlyFire() */ ) + { + TraceResult tr; + + Vector vecSrc = GetGunPosition(); + + // verify that a bullet fired from the gun will hit the enemy before the world. + UTIL_TraceLine( vecSrc, m_hEnemy->BodyTarget(vecSrc), dont_ignore_monsters, ENT(pev), &tr); + + if ( tr.flFraction == 1 || tr.pHit == m_hEnemy->edict() ) + { + return TRUE; + } + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack2 - toss grenade is enemy gets in the way and is too close. +//========================================================= +BOOL CHAssassin :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + m_fThrowGrenade = FALSE; + if ( !FBitSet ( m_hEnemy->pev->flags, FL_ONGROUND ) ) + { + // don't throw grenades at anything that isn't on the ground! + return FALSE; + } + + // don't get grenade happy unless the player starts to piss you off + if ( m_iFrustration <= 2) + return FALSE; + + if ( m_flNextGrenadeCheck < gpGlobals->time && !HasConditions( bits_COND_ENEMY_OCCLUDED ) && flDist <= 512 /* && flDot >= 0.5 */ /* && NoFriendlyFire() */ ) + { + Vector vecToss = VecCheckThrow( pev, GetGunPosition( ), m_hEnemy->Center(), flDist, 0.5 ); // use dist as speed to get there in 1 second + + if ( vecToss != g_vecZero ) + { + m_vecTossVelocity = vecToss; + + // throw a hand grenade + m_fThrowGrenade = TRUE; + + return TRUE; + } + } + + return FALSE; +} + + +//========================================================= +// RunAI +//========================================================= +void CHAssassin :: RunAI( void ) +{ + CBaseMonster :: RunAI(); + + // always visible if moving + // always visible is not on hard + if (g_iSkillLevel != SKILL_HARD || m_hEnemy == NULL || pev->deadflag != DEAD_NO || m_Activity == ACT_RUN || m_Activity == ACT_WALK || !(pev->flags & FL_ONGROUND)) + m_iTargetRanderamt = 255; + else + m_iTargetRanderamt = 20; + + if (pev->renderamt > m_iTargetRanderamt) + { + if (pev->renderamt == 255) + { + EMIT_SOUND (ENT(pev), CHAN_BODY, "debris/beamstart1.wav", 0.2, ATTN_NORM ); + } + + pev->renderamt = max( pev->renderamt - 50, m_iTargetRanderamt ); + pev->rendermode = kRenderTransTexture; + } + else if (pev->renderamt < m_iTargetRanderamt) + { + pev->renderamt = min( pev->renderamt + 50, m_iTargetRanderamt ); + if (pev->renderamt == 255) + pev->rendermode = kRenderNormal; + } + + if (m_Activity == ACT_RUN || m_Activity == ACT_WALK) + { + static int iStep = 0; + iStep = ! iStep; + if (iStep) + { + switch( RANDOM_LONG( 0, 3 ) ) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step1.wav", 0.5, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step3.wav", 0.5, ATTN_NORM); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step2.wav", 0.5, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step4.wav", 0.5, ATTN_NORM); break; + } + } + } +} + + +//========================================================= +// StartTask +//========================================================= +void CHAssassin :: StartTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_RANGE_ATTACK2: + if (!m_fThrowGrenade) + { + TaskComplete( ); + } + else + { + CBaseMonster :: StartTask ( pTask ); + } + break; + case TASK_ASSASSIN_FALL_TO_GROUND: + break; + default: + CBaseMonster :: StartTask ( pTask ); + break; + } +} + + +//========================================================= +// RunTask +//========================================================= +void CHAssassin :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_ASSASSIN_FALL_TO_GROUND: + SetIdealYawToTargetAndUpdate( m_vecEnemyLKP , AI_KEEP_YAW_SPEED ); + + if (m_fSequenceFinished) + { + if (pev->velocity.z > 0) + { + pev->sequence = LookupSequence( "fly_up" ); + } + else if (HasConditions ( bits_COND_SEE_ENEMY )) + { + pev->sequence = LookupSequence( "fly_attack" ); + pev->frame = 0; + } + else + { + pev->sequence = LookupSequence( "fly_down" ); + pev->frame = 0; + } + + ResetSequenceInfo( ); + RecalculateYawSpeed(); + } + if (pev->flags & FL_ONGROUND) + { + // ALERT( at_console, "on ground\n"); + TaskComplete( ); + } + break; + default: + CBaseMonster :: RunTask ( pTask ); + break; + } +} + +//========================================================= +// GetSchedule - Decides which type of schedule best suits +// the monster's current state and conditions. Then calls +// monster's member function to get a pointer to a schedule +// of the proper type. +//========================================================= +Schedule_t *CHAssassin :: GetSchedule ( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_IDLE: + case MONSTERSTATE_ALERT: + { + if ( HasConditions ( bits_COND_HEAR_SOUND )) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) + { + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + if ( pSound && (pSound->m_iType & bits_SOUND_COMBAT) ) + { + return GetScheduleOfType( SCHED_INVESTIGATE_SOUND ); + } + } + } + break; + + case MONSTERSTATE_COMBAT: + { +// dead enemy + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + + // flying? + if ( pev->movetype == MOVETYPE_TOSS) + { + if (pev->flags & FL_ONGROUND) + { + // ALERT( at_console, "landed\n"); + // just landed + pev->movetype = MOVETYPE_STEP; + return GetScheduleOfType ( SCHED_ASSASSIN_JUMP_LAND ); + } + else + { + // ALERT( at_console, "jump\n"); + // jump or jump/shoot + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + return GetScheduleOfType ( SCHED_ASSASSIN_JUMP ); + else + return GetScheduleOfType ( SCHED_ASSASSIN_JUMP_ATTACK ); + } + } + + if ( HasConditions ( bits_COND_HEAR_SOUND )) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) + { + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + } + + if ( HasConditions ( bits_COND_LIGHT_DAMAGE ) ) + { + m_iFrustration++; + } + if ( HasConditions ( bits_COND_HEAVY_DAMAGE ) ) + { + m_iFrustration++; + } + + // jump player! + if ( HasConditions ( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + // ALERT( at_console, "melee attack 1\n"); + return GetScheduleOfType ( SCHED_MELEE_ATTACK1 ); + } + + // throw grenade + if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK2 ) ) + { + // ALERT( at_console, "range attack 2\n"); + return GetScheduleOfType ( SCHED_RANGE_ATTACK2 ); + } + + // spotted + if ( HasConditions ( bits_COND_SEE_ENEMY ) && HasConditions ( bits_COND_ENEMY_FACING_ME ) ) + { + // ALERT( at_console, "exposed\n"); + m_iFrustration++; + return GetScheduleOfType ( SCHED_ASSASSIN_EXPOSED ); + } + + // can attack + if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + // ALERT( at_console, "range attack 1\n"); + m_iFrustration = 0; + return GetScheduleOfType ( SCHED_RANGE_ATTACK1 ); + } + + if ( HasConditions ( bits_COND_SEE_ENEMY ) ) + { + // ALERT( at_console, "face\n"); + return GetScheduleOfType ( SCHED_COMBAT_FACE ); + } + + // new enemy + if ( HasConditions ( bits_COND_NEW_ENEMY ) ) + { + // ALERT( at_console, "take cover\n"); + return GetScheduleOfType ( SCHED_TAKE_COVER_FROM_ENEMY ); + } + + // ALERT( at_console, "stand\n"); + return GetScheduleOfType ( SCHED_ALERT_STAND ); + } + break; + } + + return CBaseMonster :: GetSchedule(); +} + +//========================================================= +//========================================================= +Schedule_t* CHAssassin :: GetScheduleOfType ( int Type ) +{ + // ALERT( at_console, "%d\n", m_iFrustration ); + switch ( Type ) + { + case SCHED_TAKE_COVER_FROM_ENEMY: + if (pev->health > 30) + return slAssassinTakeCoverFromEnemy; + else + return slAssassinTakeCoverFromEnemy2; + case SCHED_TAKE_COVER_FROM_BEST_SOUND: + return slAssassinTakeCoverFromBestSound; + case SCHED_ASSASSIN_EXPOSED: + return slAssassinExposed; + case SCHED_FAIL: + if (m_MonsterState == MONSTERSTATE_COMBAT) + return slAssassinFail; + break; + case SCHED_ALERT_STAND: + if (m_MonsterState == MONSTERSTATE_COMBAT) + return slAssassinHide; + break; + case SCHED_CHASE_ENEMY: + return slAssassinHunt; + case SCHED_MELEE_ATTACK1: + if (pev->flags & FL_ONGROUND) + { + if (m_flNextJump > gpGlobals->time) + { + // can't jump yet, go ahead and fail + return slAssassinFail; + } + else + { + return slAssassinJump; + } + } + else + { + return slAssassinJumpAttack; + } + case SCHED_ASSASSIN_JUMP: + case SCHED_ASSASSIN_JUMP_ATTACK: + return slAssassinJumpAttack; + case SCHED_ASSASSIN_JUMP_LAND: + return slAssassinJumpLand; + } + + return CBaseMonster :: GetScheduleOfType( Type ); +} + +#endif diff --git a/dlls/headcrab.cpp b/dlls/headcrab.cpp new file mode 100644 index 0000000..bf7002b --- /dev/null +++ b/dlls/headcrab.cpp @@ -0,0 +1,566 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// headcrab.cpp - tiny, jumpy alien parasite +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "game.h" + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define HC_AE_JUMPATTACK ( 2 ) + +Task_t tlHCRangeAttack1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_WAIT_RANDOM, (float)0.5 }, +}; + +Schedule_t slHCRangeAttack1[] = +{ + { + tlHCRangeAttack1, + ARRAYSIZE ( tlHCRangeAttack1 ), + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED, + 0, + "HCRangeAttack1" + }, +}; + +Task_t tlHCRangeAttack1Fast[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slHCRangeAttack1Fast[] = +{ + { + tlHCRangeAttack1Fast, + ARRAYSIZE ( tlHCRangeAttack1Fast ), + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED, + 0, + "HCRAFast" + }, +}; + +class CHeadCrab : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void RunTask ( Task_t *pTask ); + void StartTask ( Task_t *pTask ); + float MaxYawSpeed( void ); + void EXPORT LeapTouch ( CBaseEntity *pOther ); + Vector Center( void ); + Vector BodyTarget( const Vector &posSrc ); + void PainSound( void ); + void DeathSound( void ); + void IdleSound( void ); + void AlertSound( void ); + void PrescheduleThink( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack2 ( float flDot, float flDist ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + virtual float GetDamageAmount( void ) { return gSkillData.headcrabDmgBite; } + virtual int GetVoicePitch( void ) { return 100; } + virtual float GetSoundVolue( void ) { return 1.0; } + Schedule_t* GetScheduleOfType ( int Type ); + + CUSTOM_SCHEDULES; + + static const char *pIdleSounds[]; + static const char *pAlertSounds[]; + static const char *pPainSounds[]; + static const char *pAttackSounds[]; + static const char *pDeathSounds[]; + static const char *pBiteSounds[]; +}; +LINK_ENTITY_TO_CLASS( monster_headcrab, CHeadCrab ); + +DEFINE_CUSTOM_SCHEDULES( CHeadCrab ) +{ + slHCRangeAttack1, + slHCRangeAttack1Fast, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CHeadCrab, CBaseMonster ); + +const char *CHeadCrab::pIdleSounds[] = +{ + "headcrab/hc_idle1.wav", + "headcrab/hc_idle2.wav", + "headcrab/hc_idle3.wav", +}; +const char *CHeadCrab::pAlertSounds[] = +{ + "headcrab/hc_alert1.wav", +}; +const char *CHeadCrab::pPainSounds[] = +{ + "headcrab/hc_pain1.wav", + "headcrab/hc_pain2.wav", + "headcrab/hc_pain3.wav", +}; +const char *CHeadCrab::pAttackSounds[] = +{ + "headcrab/hc_attack1.wav", + "headcrab/hc_attack2.wav", + "headcrab/hc_attack3.wav", +}; + +const char *CHeadCrab::pDeathSounds[] = +{ + "headcrab/hc_die1.wav", + "headcrab/hc_die2.wav", +}; + +const char *CHeadCrab::pBiteSounds[] = +{ + "headcrab/hc_headbite.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CHeadCrab :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_PREY; +} + +//========================================================= +// Center - returns the real center of the headcrab. The +// bounding box is much larger than the actual creature so +// this is needed for targeting +//========================================================= +Vector CHeadCrab :: Center ( void ) +{ + return Vector( pev->origin.x, pev->origin.y, pev->origin.z + 6 ); +} + + +Vector CHeadCrab :: BodyTarget( const Vector &posSrc ) +{ + return Center( ); +} + +//========================================================= +// MaxYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +float CHeadCrab :: MaxYawSpeed( void ) +{ + float ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + ys = 30; + break; + case ACT_RUN: + case ACT_WALK: + ys = 20; + break; + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 60; + break; + case ACT_RANGE_ATTACK1: + ys = 30; + break; + default: + ys = 30; + break; + } + + return ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CHeadCrab :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case HC_AE_JUMPATTACK: + { + ClearBits( pev->flags, FL_ONGROUND ); + + UTIL_SetOrigin (this, pev->origin + Vector ( 0 , 0 , 1) );// take him off ground so engine doesn't instantly reset onground + UTIL_MakeVectors ( pev->angles ); + + Vector vecJumpDir; + if (m_hEnemy != NULL) + { + float gravity = g_psv_gravity->value; + if (gravity <= 1) + gravity = 1; + + // How fast does the headcrab need to travel to reach that height given gravity? + float height = (m_hEnemy->pev->origin.z + m_hEnemy->pev->view_ofs.z - pev->origin.z); + if (height < 16) + height = 16; + float speed = sqrt( 2 * gravity * height ); + float time = speed / gravity; + + // Scale the sideways velocity to get there at the right time + vecJumpDir = (m_hEnemy->pev->origin + m_hEnemy->pev->view_ofs - pev->origin); + vecJumpDir = vecJumpDir * ( 1.0 / time ); + + // Speed to offset gravity at the desired height + vecJumpDir.z = speed; + + // Don't jump too far/fast + float distance = vecJumpDir.Length(); + + if (distance > 650) + { + vecJumpDir = vecJumpDir * ( 650.0 / distance ); + } + } + else + { + // jump hop, don't care where + vecJumpDir = Vector( gpGlobals->v_forward.x, gpGlobals->v_forward.y, gpGlobals->v_up.z ) * 350; + } + + int iSound = RANDOM_LONG(0,2); + if ( iSound != 0 ) + EMIT_SOUND_DYN( edict(), CHAN_VOICE, pAttackSounds[iSound], GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); + + pev->velocity = vecJumpDir; + m_flNextAttack = gpGlobals->time + 2; + } + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CHeadCrab :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/headcrab.mdl"); + UTIL_SetSize(pev, Vector(-12, -12, 0), Vector(12, 12, 24)); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + if (pev->health == 0) + pev->health = gSkillData.headcrabHealth; + pev->view_ofs = Vector ( 0, 0, 20 );// position of the eyes relative to monster's origin. + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CHeadCrab :: Precache() +{ + PRECACHE_SOUND_ARRAY(pIdleSounds); + PRECACHE_SOUND_ARRAY(pAlertSounds); + PRECACHE_SOUND_ARRAY(pPainSounds); + PRECACHE_SOUND_ARRAY(pAttackSounds); + PRECACHE_SOUND_ARRAY(pDeathSounds); + PRECACHE_SOUND_ARRAY(pBiteSounds); + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/headcrab.mdl"); +} + + +//========================================================= +// RunTask +//========================================================= +void CHeadCrab :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_RANGE_ATTACK1: + case TASK_RANGE_ATTACK2: + { + if ( m_fSequenceFinished ) + { + TaskComplete(); + SetTouch( NULL ); + m_IdealActivity = ACT_IDLE; + } + break; + } + default: + { + CBaseMonster :: RunTask(pTask); + } + } +} + +//========================================================= +// LeapTouch - this is the headcrab's touch function when it +// is in the air +//========================================================= +void CHeadCrab :: LeapTouch ( CBaseEntity *pOther ) +{ + if ( !pOther->pev->takedamage ) + { + return; + } + + if ( pOther->Classify() == Classify() ) + { + return; + } + + // Don't hit if back on ground + if ( !FBitSet( pev->flags, FL_ONGROUND ) ) + { + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pBiteSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); + + pOther->TakeDamage( pev, pev, GetDamageAmount(), DMG_SLASH ); + } + + SetTouch( NULL ); +} + +//========================================================= +// PrescheduleThink +//========================================================= +void CHeadCrab :: PrescheduleThink ( void ) +{ + // make the crab coo a little bit in combat state + if ( m_MonsterState == MONSTERSTATE_COMBAT && RANDOM_FLOAT( 0, 5 ) < 0.1 ) + { + IdleSound(); + } +} + +void CHeadCrab :: StartTask ( Task_t *pTask ) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch ( pTask->iTask ) + { + case TASK_RANGE_ATTACK1: + { + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, pAttackSounds[0], GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); + m_IdealActivity = ACT_RANGE_ATTACK1; + SetTouch(&CHeadCrab :: LeapTouch ); + break; + } + default: + { + CBaseMonster :: StartTask( pTask ); + } + } +} + + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CHeadCrab :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( FBitSet( pev->flags, FL_ONGROUND ) && flDist <= 256 && flDot >= 0.65 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack2 +//========================================================= +BOOL CHeadCrab :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + return FALSE; + // BUGBUG: Why is this code here? There is no ACT_RANGE_ATTACK2 animation. I've disabled it for now. +#if 0 + if ( FBitSet( pev->flags, FL_ONGROUND ) && flDist > 64 && flDist <= 256 && flDot >= 0.5 ) + { + return TRUE; + } + return FALSE; +#endif +} + +int CHeadCrab :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // Don't take any acid damage -- BigMomma's mortar is acid + if ( bitsDamageType & DMG_ACID ) + flDamage = 0; + + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +//========================================================= +// IdleSound +//========================================================= +#define CRAB_ATTN_IDLE (float)1.5 +void CHeadCrab :: IdleSound ( void ) +{ + EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pIdleSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); +} + +//========================================================= +// AlertSound +//========================================================= +void CHeadCrab :: AlertSound ( void ) +{ + EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pAlertSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); +} + +//========================================================= +// AlertSound +//========================================================= +void CHeadCrab :: PainSound ( void ) +{ + EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pPainSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); +} + +//========================================================= +// DeathSound +//========================================================= +void CHeadCrab :: DeathSound ( void ) +{ + EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pDeathSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); +} + +Schedule_t* CHeadCrab :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_RANGE_ATTACK1: + { + return &slHCRangeAttack1[ 0 ]; + } + break; + } + + return CBaseMonster::GetScheduleOfType( Type ); +} + + +class CBabyCrab : public CHeadCrab +{ +public: + void Spawn( void ); + void Precache( void ); + float MaxYawSpeed( void ); + float GetDamageAmount( void ) { return gSkillData.headcrabDmgBite * 0.3; } + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + Schedule_t* GetScheduleOfType ( int Type ); + virtual int GetVoicePitch( void ) { return PITCH_NORM + RANDOM_LONG(40,50); } + virtual float GetSoundVolue( void ) { return 0.8; } +}; +LINK_ENTITY_TO_CLASS( monster_babycrab, CBabyCrab ); + +void CBabyCrab :: Spawn( void ) +{ + CHeadCrab::Spawn(); + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/baby_headcrab.mdl"); + pev->rendermode = kRenderTransTexture; + pev->renderamt = 192; + UTIL_SetSize(pev, Vector(-12, -12, 0), Vector(12, 12, 24)); + + pev->health = gSkillData.headcrabHealth * 0.25; // less health than full grown +} + +void CBabyCrab :: Precache( void ) +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL( "models/baby_headcrab.mdl" ); + CHeadCrab::Precache(); +} + + +float CBabyCrab :: MaxYawSpeed( void ) +{ + return 120; +} + + +BOOL CBabyCrab :: CheckRangeAttack1( float flDot, float flDist ) +{ + if ( pev->flags & FL_ONGROUND ) + { + if ( pev->groundentity && (pev->groundentity->v.flags & (FL_CLIENT|FL_MONSTER)) ) + return TRUE; + + // A little less accurate, but jump from closer + if ( flDist <= 180 && flDot >= 0.55 ) + return TRUE; + } + + return FALSE; +} + + +Schedule_t* CBabyCrab :: GetScheduleOfType ( int Type ) +{ + switch( Type ) + { + case SCHED_FAIL: // If you fail, try to jump! + if ( m_hEnemy != NULL ) + return slHCRangeAttack1Fast; + break; + + case SCHED_RANGE_ATTACK1: + { + return slHCRangeAttack1Fast; + } + break; + } + + return CHeadCrab::GetScheduleOfType( Type ); +} diff --git a/dlls/healthkit.cpp b/dlls/healthkit.cpp new file mode 100644 index 0000000..4e7cbc0 --- /dev/null +++ b/dlls/healthkit.cpp @@ -0,0 +1,343 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "items.h" +#include "gamerules.h" + +extern int gmsgItemPickup; + +class CHealthKit : public CItem +{ + void Spawn( void ); + void Precache( void ); + BOOL MyTouch( CBasePlayer *pPlayer ); + +/* + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; +*/ + +}; + + +LINK_ENTITY_TO_CLASS( item_healthkit, CHealthKit ); + +/* +TYPEDESCRIPTION CHealthKit::m_SaveData[] = +{ + +}; + + +IMPLEMENT_SAVERESTORE( CHealthKit, CItem); +*/ + +void CHealthKit :: Spawn( void ) +{ + Precache( ); + SET_MODEL(ENT(pev), "models/w_medkit.mdl"); + + CItem::Spawn(); +} + +void CHealthKit::Precache( void ) +{ + PRECACHE_MODEL("models/w_medkit.mdl"); + PRECACHE_SOUND("items/smallmedkit1.wav"); +} + +BOOL CHealthKit::MyTouch( CBasePlayer *pPlayer ) +{ + if ( pPlayer->pev->deadflag != DEAD_NO ) + { + return FALSE; + } + + if ( pPlayer->TakeHealth( gSkillData.healthkitCapacity, DMG_GENERIC ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgItemPickup, NULL, pPlayer->pev ); + WRITE_STRING( STRING(pev->classname) ); + MESSAGE_END(); + + EMIT_SOUND(ENT(pPlayer->pev), CHAN_ITEM, "items/smallmedkit1.wav", 1, ATTN_NORM); + + if ( g_pGameRules->ItemShouldRespawn( this ) ) + { + Respawn(); + } + else + { + UTIL_Remove(this); + } + + return TRUE; + } + + return FALSE; +} + +// buz: big healthkit +class CBigHealthKit : public CItem +{ +public: + void Spawn( void ) + { + Precache( ); + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/w_bigmedkit.mdl"); + + CItem::Spawn(); + } + + void Precache( void ) + { + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/w_bigmedkit.mdl"); + + PRECACHE_SOUND("items/bigmedkit.wav"); + } + + BOOL MyTouch( CBasePlayer *pPlayer ) + { + if ( pPlayer->pev->deadflag != DEAD_NO ) + { + return FALSE; + } + + if ( pPlayer->TakeHealth( gSkillData.bighealthkitCapacity, DMG_GENERIC ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgItemPickup, NULL, pPlayer->pev ); + WRITE_STRING( STRING(pev->classname) ); + MESSAGE_END(); + + EMIT_SOUND(ENT(pPlayer->pev), CHAN_ITEM, "items/bigmedkit.wav", 1, ATTN_NORM); + + if ( g_pGameRules->ItemShouldRespawn( this ) ) + { + Respawn(); + } + else + { + UTIL_Remove(this); + } + + return TRUE; + } + + return FALSE; + } +}; + + +LINK_ENTITY_TO_CLASS( item_bighealthkit, CBigHealthKit ); + + +//------------------------------------------------------------- +// Wall mounted health kit +//------------------------------------------------------------- +class CWallHealth : public CBaseToggle +{ +public: + void Spawn( ); + void Precache( void ); + void EXPORT Off(void); + void EXPORT Recharge(void); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return (CBaseToggle :: ObjectCaps() | FCAP_CONTINUOUS_USE) & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual STATE GetState( void ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flNextCharge; + int m_iReactivate ; // DeathMatch Delay until reactivated + int m_iJuice; + int m_iOn; // 0 = off, 1 = startup, 2 = going + float m_flSoundTime; +}; + +TYPEDESCRIPTION CWallHealth::m_SaveData[] = +{ + DEFINE_FIELD( CWallHealth, m_flNextCharge, FIELD_TIME), + DEFINE_FIELD( CWallHealth, m_iReactivate, FIELD_INTEGER), + DEFINE_FIELD( CWallHealth, m_iJuice, FIELD_INTEGER), + DEFINE_FIELD( CWallHealth, m_iOn, FIELD_INTEGER), + DEFINE_FIELD( CWallHealth, m_flSoundTime, FIELD_TIME), +}; + +IMPLEMENT_SAVERESTORE( CWallHealth, CBaseEntity ); + +LINK_ENTITY_TO_CLASS(func_healthcharger, CWallHealth); + + +void CWallHealth::KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "style") || + FStrEq(pkvd->szKeyName, "height") || + FStrEq(pkvd->szKeyName, "value1") || + FStrEq(pkvd->szKeyName, "value2") || + FStrEq(pkvd->szKeyName, "value3")) + { + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "dmdelay")) + { + m_iReactivate = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CWallHealth::Spawn() +{ + Precache( ); + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(this, pev->origin); // set size and link into world + UTIL_SetSize(pev, pev->mins, pev->maxs); + SET_MODEL(ENT(pev), STRING(pev->model) ); + m_iJuice = gSkillData.healthchargerCapacity; + pev->frame = 0; + //LRC +// if (m_iStyle >= 32) LIGHT_STYLE(m_iStyle, "a"); buz +// else if (m_iStyle <= -32) LIGHT_STYLE(-m_iStyle, "z"); +} + +void CWallHealth::Precache() +{ + PRECACHE_SOUND("items/medshot4.wav"); + PRECACHE_SOUND("items/medshotno1.wav"); + PRECACHE_SOUND("items/medcharge4.wav"); +} + + +void CWallHealth::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Make sure that we have a caller + if (!pActivator) + return; + // if it's not a player, ignore + if ( !pActivator->IsPlayer() ) + return; + + // if there is no juice left, turn it off + if (m_iJuice <= 0) + { + pev->frame = 1; + //LRC +// if (m_iStyle >= 32) LIGHT_STYLE(m_iStyle, "z"); buz +// else if (m_iStyle <= -32) LIGHT_STYLE(-m_iStyle, "a"); + Off(); + } + + CBasePlayer *pPlayer = (CBasePlayer *)pActivator; + + // if the player doesn't have the suit, or there is no juice left, make the deny noise + if ((m_iJuice <= 0) || !FBitSet( pPlayer->m_iHideHUD, ITEM_SUIT )) + { + if (m_flSoundTime <= gpGlobals->time) + { + m_flSoundTime = gpGlobals->time + 0.62; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/medshotno1.wav", 1.0, ATTN_NORM ); + } + return; + } + + SetNextThink( 0.25 ); + SetThink(&CWallHealth::Off); + + // Time to recharge yet? + + if (m_flNextCharge >= gpGlobals->time) + return; + + // Play the on sound or the looping charging sound + if (!m_iOn) + { + m_iOn++; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/medshot4.wav", 1.0, ATTN_NORM ); + m_flSoundTime = 0.56 + gpGlobals->time; + } + if ((m_iOn == 1) && (m_flSoundTime <= gpGlobals->time)) + { + m_iOn++; + EMIT_SOUND(ENT(pev), CHAN_STATIC, "items/medcharge4.wav", 1.0, ATTN_NORM ); + } + + + // charge the player + if ( pActivator->TakeHealth( 1, DMG_GENERIC ) ) + { + m_iJuice--; + } + + // govern the rate of charge + m_flNextCharge = gpGlobals->time + 0.1; +} + +void CWallHealth::Recharge(void) +{ + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/medshot4.wav", 1.0, ATTN_NORM ); + m_iJuice = gSkillData.healthchargerCapacity; + pev->frame = 0; + //LRC +// if (m_iStyle >= 32) LIGHT_STYLE(m_iStyle, "a"); buz +// else if (m_iStyle <= -32) LIGHT_STYLE(-m_iStyle, "z"); + SetThink(&CWallHealth:: SUB_DoNothing ); +} + +void CWallHealth::Off(void) +{ + // Stop looping sound. + if (m_iOn > 1) + STOP_SOUND( ENT(pev), CHAN_STATIC, "items/medcharge4.wav" ); + + m_iOn = 0; + + if ((!m_iJuice) && ( ( m_iReactivate = g_pGameRules->FlHealthChargerRechargeTime() ) > 0) ) + { + SetNextThink( m_iReactivate ); + SetThink(&CWallHealth::Recharge); + } + else + SetThink(&CWallHealth:: SUB_DoNothing ); +} + +STATE CWallHealth::GetState( void ) +{ + if (m_iOn == 2) + return STATE_IN_USE; + else if (m_iJuice) + return STATE_ON; + else + return STATE_OFF; +} diff --git a/dlls/hgrunt.cpp b/dlls/hgrunt.cpp new file mode 100644 index 0000000..06de4ec --- /dev/null +++ b/dlls/hgrunt.cpp @@ -0,0 +1,4969 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// hgrunt +//========================================================= + +//========================================================= +// Hit groups! +//========================================================= +/* + + 1 - Head + 2 - Stomach + 3 - Gun + +*/ + + +#include "extdll.h" +#include "plane.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "animation.h" +#include "squadmonster.h" +#include "weapons.h" +#include "talkmonster.h" +#include "soundent.h" +#include "effects.h" +#include "customentity.h" +#include "scripted.h" //LRC + + +extern short g_sModelIndexLaser;// buz + +// buz: test draw line +void BuzTestDrawLine( Vector p1, Vector p2, int r, int g, int b ) +{ + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS ); + WRITE_COORD( p1.x ); + WRITE_COORD( p1.y ); + WRITE_COORD( p1.z ); + + WRITE_COORD( p2.x ); + WRITE_COORD( p2.y ); + WRITE_COORD( p2.z ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 1 ); // life + WRITE_BYTE( 40 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( r ); // r, g, b + WRITE_BYTE( g ); // r, g, b + WRITE_BYTE( b ); // r, g, b + WRITE_BYTE( 128 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); +} + + +int g_fGruntQuestion; // true if an idle grunt asked a question. Cleared when someone answers. +int g_fTerrorQuestion; // buz: same for terrorists + +extern DLL_GLOBAL int g_iSkillLevel; + +//========================================================= +// monster-specific DEFINE's +//========================================================= +#define GRUNT_CLIP_SIZE 36 // how many bullets in a clip? - NOTE: 3 round burst sound, so keep as 3 * x! +#define GRUNT_VOL 0.35 // volume of grunt sounds +#define GRUNT_ATTN ATTN_NORM // attenutation of grunt sentences +#define HGRUNT_LIMP_HEALTH (pev->max_health*0.3) +#define HGRUNT_DMG_HEADSHOT ( DMG_BULLET | DMG_CLUB ) // damage types that can kill a grunt with a single headshot. +#define HGRUNT_NUM_HEADS 2 // how many grunt heads are there? +#define HGRUNT_MINIMUM_HEADSHOT_DAMAGE 15 // must do at least this much damage in one shot to head to score a headshot kill +#define HGRUNT_SENTENCE_VOLUME (float)0.35 // volume of grunt sentences + +#define HGRUNT_9MMAR ( 1 << 0) // AK for terrorists +#define HGRUNT_HANDGRENADE ( 1 << 1) +#define HGRUNT_GRENADELAUNCHER ( 1 << 2) +#define HGRUNT_SHOTGUN ( 1 << 3) // RPK for terrorists + +#define HEAD_GROUP 1 // used by terrors +//#define HEAD_GRUNT 0 +//#define HEAD_COMMANDER 1 +//#define HEAD_SHOTGUN 2 +//#define HEAD_M203 3 + +//buz: +#define DIVERSANT_HEADSTUFF_GROUP 2 +#define DIVERSANT_HEADSTUFF_PNV 0 +#define DIVERSANT_HEADSTUFF_KASKA 1 +#define DIVERSANT_HEADSTUFF_KASKA_PNV 2 +#define DIVERSANT_HEADSTUFF_NO 3 + +#define DIVERSANT_AMMUNITION_GROUP 3 +#define DIVERSANT_AMMUNITION_BACKPACK 0 +#define DIVERSANT_AMMUNITION_NO 1 + +#define DIVERSANT_GUN_GROUP 4 +#define DIVERSANT_GUN_MP5 0 +#define DIVERSANT_GUN_SHOTGUN 1 +#define DIVERSANT_GUN_NONE 2 + +//#define GUN_GROUP 2 +//#define GUN_MP5 0 +//#define GUN_SHOTGUN 1 +//#define GUN_NONE 2 + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define HGRUNT_AE_RELOAD ( 2 ) +#define HGRUNT_AE_KICK ( 3 ) +#define HGRUNT_AE_BURST1 ( 4 ) +#define HGRUNT_AE_BURST2 ( 5 ) +#define HGRUNT_AE_BURST3 ( 6 ) +#define HGRUNT_AE_GREN_TOSS ( 7 ) +#define HGRUNT_AE_GREN_LAUNCH ( 8 ) +#define HGRUNT_AE_GREN_DROP ( 9 ) +#define HGRUNT_AE_CAUGHT_ENEMY ( 10) // grunt established sight with an enemy (player only) that had previously eluded the squad. +#define HGRUNT_AE_DROP_GUN ( 11) // grunt (probably dead) is dropping his mp5. + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_GRUNT_SUPPRESS = LAST_COMMON_SCHEDULE + 1, + SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE,// move to a location to set up an attack against the enemy. (usually when a friendly is in the way). + SCHED_GRUNT_COVER_AND_RELOAD, + SCHED_GRUNT_SWEEP, + SCHED_GRUNT_FOUND_ENEMY, + SCHED_GRUNT_REPEL, + SCHED_GRUNT_REPEL_ATTACK, + SCHED_GRUNT_REPEL_LAND, + SCHED_GRUNT_WAIT_FACE_ENEMY, + SCHED_GRUNT_TAKECOVER_FAILED,// special schedule type that forces analysis of conditions and picks the best possible schedule to recover from this type of failure. + SCHED_GRUNT_ELOF_FAIL, + SCHED_GRUNT_DUCK_COVER_WAIT, // buz +// SCHED_GRUNT_RUN_SHOOTING, // buz +}; + +//========================================================= +// monster-specific tasks +//========================================================= +enum +{ + TASK_GRUNT_FACE_TOSS_DIR = LAST_COMMON_TASK + 1, + TASK_GRUNT_SPEAK_SENTENCE, + TASK_GRUNT_CHECK_FIRE, + TASK_GRUNT_MOVE_SHOOTING, // buz +}; + +//========================================================= +// monster-specific conditions +//========================================================= +#define bits_COND_GRUNT_NOFIRE ( bits_COND_SPECIAL1 ) + +class CHGrunt : public CSquadMonster +{ +public: + void Spawn( void ); + void Precache( void ); + float MaxYawSpeed( void ); + int Classify ( void ); + int ISoundMask ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + BOOL FCanCheckAttacks ( void ); + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack2 ( float flDot, float flDist ); + void CheckAmmo ( void ); + void SetActivity ( Activity NewActivity ); + void StartTask ( Task_t *pTask ); + void RunTask ( Task_t *pTask ); + void DeathSound( void ); + void PainSound( void ); + void IdleSound ( void ); + Vector GetGunPosition( void ); + virtual void Shoot ( void ); + void Shotgun ( void ); + void PrescheduleThink ( void ); + void GibMonster( void ); + virtual void SpeakSentence( void ); + virtual void SetEyePosition ( void ); // buz + + // Wargon: Âîçìîæíîñòü ïîäáèðàòü ïàòðîíû þçîì èç ìåðòâûõ âðàæèí. (1.1) + virtual int ObjectCaps( void ) { return CSquadMonster::ObjectCaps() | (m_iDeadAmmo?FCAP_IMPULSE_USE:0); } + int m_iDeadAmmo; + void EXPORT DeadUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Killed( entvars_t *pevAttacker, int iGib ); + + // buz: overriden for soldiers - eye position is differs when monster is crouching + virtual Vector EyePosition( ) + { + if (m_Activity == ACT_TWITCH) + return pev->origin + Vector(0, 0, 36); + else + return pev->origin + pev->view_ofs; + } + + void ResetSequenceInfo ( ); + + int Save( CSave &save ); + int Restore( CRestore &restore ); + + CBaseEntity *Kick( void ); + Schedule_t *GetSchedule( void ); + Schedule_t *GetScheduleOfType ( int Type ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + int IRelationship ( CBaseEntity *pTarget ); + + BOOL FOkToSpeak( void ); + void JustSpoke( void ); + + CUSTOM_SCHEDULES; + static TYPEDESCRIPTION m_SaveData[]; + + // checking the feasibility of a grenade toss is kind of costly, so we do it every couple of seconds, + // not every server frame. + float m_flNextGrenadeCheck; + float m_flNextPainTime; + float m_flLastEnemySightTime; + + Vector m_vecTossVelocity; + + BOOL m_fThrowGrenade; + BOOL m_fStanding; + BOOL m_fFirstEncounter;// only put on the handsign show in the squad's first encounter. + int m_cClipSize; + + int m_voicePitch; + + int m_iBrassShell; + int m_iShotgunShell; + + int m_iSentence; + + int m_iLastFireCheckResult; // buz. 1-only crouch, 2-only standing, 0-any + + static const char *pGruntSentences[]; +}; + +LINK_ENTITY_TO_CLASS( monster_human_grunt, CHGrunt ); +LINK_ENTITY_TO_CLASS( monster_diversant, CHGrunt ); + +TYPEDESCRIPTION CHGrunt::m_SaveData[] = +{ + DEFINE_FIELD( CHGrunt, m_flNextGrenadeCheck, FIELD_TIME ), + DEFINE_FIELD( CHGrunt, m_flNextPainTime, FIELD_TIME ), +// DEFINE_FIELD( CHGrunt, m_flLastEnemySightTime, FIELD_TIME ), // don't save, go to zero + DEFINE_FIELD( CHGrunt, m_vecTossVelocity, FIELD_VECTOR ), + DEFINE_FIELD( CHGrunt, m_fThrowGrenade, FIELD_BOOLEAN ), + DEFINE_FIELD( CHGrunt, m_fStanding, FIELD_BOOLEAN ), + DEFINE_FIELD( CHGrunt, m_fFirstEncounter, FIELD_BOOLEAN ), + DEFINE_FIELD( CHGrunt, m_cClipSize, FIELD_INTEGER ), + DEFINE_FIELD( CHGrunt, m_voicePitch, FIELD_INTEGER ), +// DEFINE_FIELD( CShotgun, m_iBrassShell, FIELD_INTEGER ), +// DEFINE_FIELD( CShotgun, m_iShotgunShell, FIELD_INTEGER ), + DEFINE_FIELD( CHGrunt, m_iSentence, FIELD_INTEGER ), + // Wargon: Âîçìîæíîñòü ïîäáèðàòü ïàòðîíû þçîì èç ìåðòâûõ âðàæèí. (1.1) + DEFINE_FIELD( CHGrunt, m_iDeadAmmo, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CHGrunt, CSquadMonster ); + +const char *CHGrunt::pGruntSentences[] = +{ + "DV_GREN", // grenade scared grunt + "DV_ALERT", // sees player + "DV_MONSTER", // sees monster + "DV_COVER", // running to cover + "DV_THROW", // about to throw grenade + "DV_CHARGE", // running out to get the enemy + "DV_TAUNT", // say rude things +}; + +enum +{ + HGRUNT_SENT_NONE = -1, + HGRUNT_SENT_GREN = 0, + HGRUNT_SENT_ALERT, + HGRUNT_SENT_MONSTER, + HGRUNT_SENT_COVER, + HGRUNT_SENT_THROW, + HGRUNT_SENT_CHARGE, + HGRUNT_SENT_TAUNT, +} HGRUNT_SENTENCE_TYPES; + + + +// buz: ResetSequenceInfo overriden for grunts - +// m_flGroundSpeed should be set accrording to pev->gaitsequence +void CHGrunt :: ResetSequenceInfo ( ) +{ + CBaseAnimating::ResetSequenceInfo(); + + if( pev->gaitsequence ) + { + float dummy; + GetSequenceInfo( GetModelPtr(), m_flPoseParameter, pev->gaitsequence, &dummy, &m_flGroundSpeed ); + } +} + +//========================================================= +// Speak Sentence - say your cued up sentence. +// +// Some grunt sentences (take cover and charge) rely on actually +// being able to execute the intended action. It's really lame +// when a grunt says 'COVER ME' and then doesn't move. The problem +// is that the sentences were played when the decision to TRY +// to move to cover was made. Now the sentence is played after +// we know for sure that there is a valid path. The schedule +// may still fail but in most cases, well after the grunt has +// started moving. +//========================================================= +void CHGrunt :: SpeakSentence( void ) +{ + if ( m_iSentence == HGRUNT_SENT_NONE ) + { + // no sentence cued up. + return; + } + + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), pGruntSentences[ m_iSentence ], HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } +} + +//========================================================= +// IRelationship - overridden because Alien Grunts are +// Human Grunt's nemesis. +//========================================================= +int CHGrunt::IRelationship ( CBaseEntity *pTarget ) +{ + //LRC- only hate alien grunts if my behaviour hasn't been overridden + if (!m_iClass && FClassnameIs( pTarget->pev, "monster_alien_grunt" ) || ( FClassnameIs( pTarget->pev, "monster_gargantua" ) ) ) + { + return R_NM; + } + + return CSquadMonster::IRelationship( pTarget ); +} + +//========================================================= +// GibMonster - make gun fly through the air. +//========================================================= +void CHGrunt :: GibMonster ( void ) +{ + Vector vecGunPos; + Vector vecGunAngles; + + if ( GetBodygroup( DIVERSANT_GUN_GROUP ) != DIVERSANT_GUN_NONE && !(pev->spawnflags & SF_MONSTER_NO_WPN_DROP)) + {// throw a gun if the grunt has one + GetAttachment( 0, vecGunPos, vecGunAngles ); + + CBaseEntity *pGun; + if (FBitSet( pev->weapons, HGRUNT_SHOTGUN )) + { + pGun = DropItem( "weapon_shotgun", vecGunPos, vecGunAngles ); + } + else + { + pGun = DropItem( "weapon_groza", vecGunPos, vecGunAngles ); + } + if ( pGun ) + { + pGun->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + pGun->pev->avelocity = Vector ( 0, RANDOM_FLOAT( 200, 400 ), 0 ); + } + + if (FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER )) + { + pGun = DropItem( "ammo_vog25", vecGunPos, vecGunAngles ); + if ( pGun ) + { + pGun->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + pGun->pev->avelocity = Vector ( 0, RANDOM_FLOAT( 200, 400 ), 0 ); + } + } + } + + CBaseMonster :: GibMonster(); +} + +//========================================================= +// ISoundMask - Overidden for human grunts because they +// hear the DANGER sound that is made by hand grenades and +// other dangerous items. +//========================================================= +int CHGrunt :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER | + bits_SOUND_DANGER; +} + +//========================================================= +// someone else is talking - don't speak +//========================================================= +BOOL CHGrunt :: FOkToSpeak( void ) +{ +// if someone else is talking, don't speak + if (gpGlobals->time <= CTalkMonster::g_talkWaitTime) + return FALSE; + + if ( pev->spawnflags & SF_MONSTER_GAG ) + { + if ( m_MonsterState != MONSTERSTATE_COMBAT ) + { + // no talking outside of combat if gagged. + return FALSE; + } + } + + // if player is not in pvs, don't speak +// if (FNullEnt(FIND_CLIENT_IN_PVS(edict()))) +// return FALSE; + + return TRUE; +} + +//========================================================= +//========================================================= +void CHGrunt :: JustSpoke( void ) +{ + CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(1.5, 2.0); + m_iSentence = HGRUNT_SENT_NONE; +} + +//========================================================= +// PrescheduleThink - this function runs after conditions +// are collected and before scheduling code is run. +//========================================================= +void CHGrunt :: PrescheduleThink ( void ) +{ + if ( InSquad() && m_hEnemy != NULL ) + { + if ( HasConditions ( bits_COND_SEE_ENEMY ) ) + { + // update the squad's last enemy sighting time. + MySquadLeader()->m_flLastEnemySightTime = gpGlobals->time; + } + else + { + if ( gpGlobals->time - MySquadLeader()->m_flLastEnemySightTime > 5 ) + { + // been a while since we've seen the enemy + MySquadLeader()->m_fEnemyEluded = TRUE; + } + } + } +} + +//========================================================= +// FCanCheckAttacks - this is overridden for human grunts +// because they can throw/shoot grenades when they can't see their +// target and the base class doesn't check attacks if the monster +// cannot see its enemy. +// +// !!!BUGBUG - this gets called before a 3-round burst is fired +// which means that a friendly can still be hit with up to 2 rounds. +// ALSO, grenades will not be tossed if there is a friendly in front, +// this is a bad bug. Friendly machine gun fire avoidance +// will unecessarily prevent the throwing of a grenade as well. +//========================================================= +BOOL CHGrunt :: FCanCheckAttacks ( void ) +{ + if ( !HasConditions( bits_COND_ENEMY_TOOFAR ) ) + { + return TRUE; + } + else + { + return FALSE; + } +} + + +//========================================================= +// CheckMeleeAttack1 +//========================================================= +BOOL CHGrunt :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + CBaseMonster *pEnemy; + + if ( m_hEnemy != NULL ) + { + pEnemy = m_hEnemy->MyMonsterPointer(); + + if ( !pEnemy ) + { + return FALSE; + } + } + + if ( flDist <= 64 && flDot >= 0.7 && + pEnemy->Classify() != CLASS_ALIEN_BIOWEAPON && + pEnemy->Classify() != CLASS_PLAYER_BIOWEAPON ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack1 - overridden for HGrunt, cause +// FCanCheckAttacks() doesn't disqualify all attacks based +// on whether or not the enemy is occluded because unlike +// the base class, the HGrunt can attack when the enemy is +// occluded (throw grenade over wall, etc). We must +// disqualify the machine gun attack if the enemy is occluded. +//========================================================= +BOOL CHGrunt :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( !HasConditions( bits_COND_ENEMY_OCCLUDED ) && flDist <= 2048 && flDot >= 0.5 && NoFriendlyFire() ) + { + TraceResult tr; + + if ( !m_hEnemy->IsPlayer() && flDist <= 64 ) + { + // kick nonclients who are close enough, but don't shoot at them. + return FALSE; + } + + BOOL savedStanding = m_fStanding; + m_fStanding = FALSE; // buz: check chrouched fire first + Vector vecSrc = GetGunPosition(); + + // verify that a bullet fired from the gun will hit the enemy before the world. + UTIL_TraceLine( vecSrc, m_hEnemy->BodyTarget(vecSrc), ignore_monsters, ignore_glass, ENT(pev), &tr); + if ( tr.flFraction == 1.0 && !pev->gaitsequence) // buz: cant fire crouched when moving + { + // buz: we can fire crouched, now check for standing + m_fStanding = TRUE; + vecSrc = GetGunPosition(); + UTIL_TraceLine( vecSrc, m_hEnemy->BodyTarget(vecSrc), ignore_monsters, ignore_glass, ENT(pev), &tr); + m_fStanding = savedStanding; + if ( tr.flFraction == 1.0 ) + m_iLastFireCheckResult = 0; // shoot as you wish + else + m_iLastFireCheckResult = 1; // only chrouched + + return TRUE; + } + else + { + // buz: cant fire crouching, maybe me or enemy in some kind of cover (or running). Check standing. + m_fStanding = TRUE; + vecSrc = GetGunPosition(); + UTIL_TraceLine( vecSrc, m_hEnemy->BodyTarget(vecSrc), ignore_monsters, ignore_glass, ENT(pev), &tr); + m_fStanding = savedStanding; + if ( tr.flFraction == 1.0 ) + { + m_iLastFireCheckResult = 2; // buz: standing is our only one choice + return TRUE; + } + else + { + m_iLastFireCheckResult = 0; + return FALSE; // cant fire + } + } + } + + return FALSE; +} + +//========================================================= +// CheckRangeAttack2 - this checks the Grunt's grenade +// attack. +//========================================================= +BOOL CHGrunt :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + if (! FBitSet(pev->weapons, (HGRUNT_HANDGRENADE | HGRUNT_GRENADELAUNCHER))) + { + return FALSE; + } + + // if the grunt isn't moving, it's ok to check. + if ( m_flGroundSpeed != 0 ) + { + m_fThrowGrenade = FALSE; + return m_fThrowGrenade; + } + + // assume things haven't changed too much since last time + if (gpGlobals->time < m_flNextGrenadeCheck ) + { + return m_fThrowGrenade; + } + + if ( !FBitSet ( m_hEnemy->pev->flags, FL_ONGROUND ) && (m_hEnemy->pev->waterlevel == 0 || m_hEnemy->pev->watertype==CONTENTS_FOG) && m_vecEnemyLKP.z > pev->absmax.z ) + { + //!!!BUGBUG - we should make this check movetype and make sure it isn't FLY? Players who jump a lot are unlikely to + // be grenaded. + // don't throw grenades at anything that isn't on the ground! + m_fThrowGrenade = FALSE; + return m_fThrowGrenade; + } + + Vector vecTarget; + + if (FBitSet( pev->weapons, HGRUNT_HANDGRENADE)) + { + // find feet + if (RANDOM_LONG(0,1)) + { + // magically know where they are + vecTarget = Vector( m_hEnemy->pev->origin.x, m_hEnemy->pev->origin.y, m_hEnemy->pev->absmin.z ); + } + else + { + // toss it to where you last saw them + vecTarget = m_vecEnemyLKP; + } + // vecTarget = m_vecEnemyLKP + (m_hEnemy->BodyTarget( pev->origin ) - m_hEnemy->pev->origin); + // estimate position + // vecTarget = vecTarget + m_hEnemy->pev->velocity * 2; + } + else + { + // find target + // vecTarget = m_hEnemy->BodyTarget( pev->origin ); + vecTarget = m_vecEnemyLKP + (m_hEnemy->BodyTarget( pev->origin ) - m_hEnemy->pev->origin); + // estimate position + if (HasConditions( bits_COND_SEE_ENEMY)) + vecTarget = vecTarget + ((vecTarget - pev->origin).Length() / gSkillData.hgruntGrenadeSpeed) * m_hEnemy->pev->velocity; + } + + // are any of my squad members near the intended grenade impact area? + if ( InSquad() ) + { + if (SquadMemberInRange( vecTarget, 256 )) + { + // crap, I might blow my own guy up. Don't throw a grenade and don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + m_fThrowGrenade = FALSE; + } + } + + if ( ( vecTarget - pev->origin ).Length2D() <= 256 ) + { + // crap, I don't want to blow myself up + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + m_fThrowGrenade = FALSE; + return m_fThrowGrenade; + } + + + if (FBitSet( pev->weapons, HGRUNT_HANDGRENADE)) + { + Vector vecToss = VecCheckToss( pev, GetGunPosition(), vecTarget, 0.5 ); + + if ( vecToss != g_vecZero ) + { + m_vecTossVelocity = vecToss; + + // throw a hand grenade + m_fThrowGrenade = TRUE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time; // 1/3 second. + } + else + { + // don't throw + m_fThrowGrenade = FALSE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + } + } + else + { + Vector vecToss = VecCheckThrow( pev, GetGunPosition(), vecTarget, gSkillData.hgruntGrenadeSpeed, 0.5 ); + + if ( vecToss != g_vecZero ) + { + m_vecTossVelocity = vecToss; + + // throw a hand grenade + m_fThrowGrenade = TRUE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 0.3; // 1/3 second. + } + else + { + // don't throw + m_fThrowGrenade = FALSE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + } + } + + + + return m_fThrowGrenade; +} + + +//========================================================= +// TraceAttack - make sure we're not taking it in the helmet +//========================================================= +void CHGrunt :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ +// ALERT(at_console, "hgrunt hitgr: %d\n", ptr->iHitgroup); + // check for helmet shot + if ((ptr->iHitgroup == 8) || (ptr->iHitgroup == 1)) + { + // make sure we're wearing one +/* if (GetBodygroup( 1 ) == HEAD_GRUNT && (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_BLAST | DMG_CLUB))) + { + // absorb damage + flDamage -= 20; + if (flDamage <= 0) + { + UTIL_Ricochet( ptr->vecEndPos, 1.0 ); + flDamage = 0.01; + } + }*/ // buz + // it's head shot anyways + ptr->iHitgroup = HITGROUP_HEAD; + } + CSquadMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + + +//========================================================= +// TakeDamage - overridden for the grunt because the grunt +// needs to forget that he is in cover if he's hurt. (Obviously +// not in a safe place anymore). +//========================================================= +int CHGrunt :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + Forget( bits_MEMORY_INCOVER ); + + return CSquadMonster :: TakeDamage ( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +//========================================================= +// MaxYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +float CHGrunt :: MaxYawSpeed( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + ys = 150; + break; + case ACT_RUN: + ys = 150; + break; + case ACT_WALK: + ys = 180; + break; + case ACT_RANGE_ATTACK1: + ys = 120; + break; + case ACT_RANGE_ATTACK2: + ys = 120; + break; + case ACT_MELEE_ATTACK1: + ys = 120; + break; + case ACT_MELEE_ATTACK2: + ys = 120; + break; + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 180; + break; + case ACT_GLIDE: + case ACT_FLY: + ys = 30; + break; + default: + ys = 90; + break; + } + + return ys; +} + +void CHGrunt :: IdleSound( void ) +{ + if (FOkToSpeak() && (g_fGruntQuestion || RANDOM_LONG(0,1))) + { + if (!g_fGruntQuestion) + { + // ask question or make statement + switch (RANDOM_LONG(0,2)) + { + case 0: // check in + SENTENCEG_PlayRndSz(ENT(pev), "DV_CHECK", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + g_fGruntQuestion = 1; + break; + case 1: // question + SENTENCEG_PlayRndSz(ENT(pev), "DV_QUEST", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + g_fGruntQuestion = 2; + break; + case 2: // statement + SENTENCEG_PlayRndSz(ENT(pev), "DV_IDLE", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + break; + } + } + else + { + switch (g_fGruntQuestion) + { + case 1: // check in + SENTENCEG_PlayRndSz(ENT(pev), "DV_CLEAR", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + break; + case 2: // question + SENTENCEG_PlayRndSz(ENT(pev), "DV_ANSWER", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + break; + } + g_fGruntQuestion = 0; + } + JustSpoke(); + } +} + +//========================================================= +// CheckAmmo - overridden for the grunt because he actually +// uses ammo! (base class doesn't) +//========================================================= +void CHGrunt :: CheckAmmo ( void ) +{ + if ( m_cAmmoLoaded <= 0 ) + { + SetConditions(bits_COND_NO_AMMO_LOADED); + } +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CHGrunt :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_HUMAN_MILITARY; +} + +//========================================================= +//========================================================= +CBaseEntity *CHGrunt :: Kick( void ) +{ + TraceResult tr; + + UTIL_MakeVectors( pev->angles ); + Vector vecStart = pev->origin; + vecStart.z += pev->size.z * 0.5; + Vector vecEnd = vecStart + (gpGlobals->v_forward * 70); + + UTIL_TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_hull, ENT(pev), &tr ); + + if ( tr.pHit ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + return pEntity; + } + + return NULL; +} + +//========================================================= +// GetGunPosition return the end of the barrel +//========================================================= + +Vector CHGrunt :: GetGunPosition( ) +{ + if (m_fStanding ) + { + return pev->origin + Vector( 0, 0, 60 ); + } + else + { + // return pev->origin + Vector( 0, 0, 48 ); + return pev->origin + Vector( 0, 0, 40 ); // buz: prevent firing crouched, when hiding behind the barrels, crates etc. + } +} + +//========================================================= +// Shoot +//========================================================= +void CHGrunt :: Shoot ( void ) +{ +// if (m_hEnemy == NULL && m_pCine == NULL) //LRC - scripts may fire when you have no enemy +// { +// return; +// } + + UTIL_MakeVectors ( pev->angles ); + Vector vecShootOrigin = GetGunPosition(); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); + + if (m_cAmmoLoaded > 0) + { + Vector vecBrassPos, vecBrassDir; + GetAttachment(3, vecBrassPos, vecBrassDir); + Vector vecShellVelocity = gpGlobals->v_right * RANDOM_FLOAT(40,90) + gpGlobals->v_up * RANDOM_FLOAT(75,200) + gpGlobals->v_forward * RANDOM_FLOAT(-40, 40); + EjectBrass ( vecBrassPos, vecShellVelocity, pev->angles.y, m_iBrassShell, TE_BOUNCE_SHELL); + FireBullets( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_7DEGREES, 2048.0f, BULLET_NORMAL, gSkillData.monDmgMP5 ); // shoot +-5 degrees + + pev->effects |= EF_MUZZLEFLASH; + + m_cAmmoLoaded--;// take away a bullet! + // ALERT(at_console, "grunt ammo has %d\n", m_cAmmoLoaded); + } + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); +} + +//========================================================= +// Shoot +//========================================================= +void CHGrunt :: Shotgun ( void ) +{ + if (m_hEnemy == NULL && m_pCine == NULL) + { + return; + } + + Vector vecShootOrigin = GetGunPosition(); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); + + UTIL_MakeVectors ( pev->angles ); + + Vector vecBrassPos, vecBrassDir; + GetAttachment(3, vecBrassPos, vecBrassDir); + Vector vecShellVelocity = gpGlobals->v_right * RANDOM_FLOAT(40,90) + gpGlobals->v_up * RANDOM_FLOAT(75,200) + gpGlobals->v_forward * RANDOM_FLOAT(-40, 40); + EjectBrass( vecBrassPos, vecShellVelocity, pev->angles.y, m_iShotgunShell, TE_BOUNCE_SHOTSHELL ); + FireBullets( gSkillData.hgruntShotgunPellets, vecShootOrigin, vecShootDir, VECTOR_CONE_15DEGREES, 2048, BULLET_BUCKSHOT, gSkillData.plrDmgBuckshot ); // shoot +-7.5 degrees + + pev->effects |= EF_MUZZLEFLASH; + + m_cAmmoLoaded--;// take away a bullet! + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CHGrunt :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + Vector vecShootDir; + Vector vecShootOrigin; + + switch( pEvent->event ) + { + case HGRUNT_AE_DROP_GUN: + { + if (pev->spawnflags & SF_MONSTER_NO_WPN_DROP) break; //LRC + + Vector vecGunPos; + Vector vecGunAngles; + + GetAttachment( 0, vecGunPos, vecGunAngles ); + + // switch to body group with no gun. + SetBodygroup( DIVERSANT_GUN_GROUP, DIVERSANT_GUN_NONE ); + + // now spawn a gun. + if (FBitSet( pev->weapons, HGRUNT_SHOTGUN )) + { + DropItem( "weapon_shotgun", vecGunPos, vecGunAngles ); + } + else + { + DropItem( "weapon_groza", vecGunPos, vecGunAngles ); + } + if (FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER )) + { + DropItem( "ammo_vog25", BodyTarget( pev->origin ), vecGunAngles ); + } + + } + break; + + case HGRUNT_AE_RELOAD: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "diversant/gr_reload1.wav", 1, ATTN_NORM ); + m_cAmmoLoaded = m_cClipSize; + ClearConditions(bits_COND_NO_AMMO_LOADED); + break; + + case HGRUNT_AE_GREN_TOSS: + { + UTIL_MakeVectors( pev->angles ); + // CGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 34 + Vector (0, 0, 32), m_vecTossVelocity, 3.5 ); + //LRC - a bit of a hack. Ideally the grunts would work out in advance whether it's ok to throw. + if (m_pCine) + { + Vector vecToss = g_vecZero; + if (m_hTargetEnt != NULL && m_pCine->PreciseAttack()) + { + vecToss = VecCheckToss( pev, GetGunPosition(), m_hTargetEnt->pev->origin, 0.5 ); + } + if (vecToss == g_vecZero) + { + vecToss = (gpGlobals->v_forward*0.5+gpGlobals->v_up*0.5).Normalize()*gSkillData.hgruntGrenadeSpeed; + } + CGrenade::ShootTimed( pev, GetGunPosition(), vecToss, 3.5 ); + } + else + CGrenade::ShootTimed( pev, GetGunPosition(), m_vecTossVelocity, 3.5 ); + + m_fThrowGrenade = FALSE; + m_flNextGrenadeCheck = gpGlobals->time + 6;// wait six seconds before even looking again to see if a grenade can be thrown. + // !!!LATER - when in a group, only try to throw grenade if ordered. + } + break; + + case HGRUNT_AE_GREN_LAUNCH: + { + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/glauncher.wav", 0.8, ATTN_NORM); + //LRC: firing due to a script? + if (m_pCine) + { + Vector vecToss; + if (m_hTargetEnt != NULL && m_pCine->PreciseAttack()) + vecToss = VecCheckThrow( pev, GetGunPosition(), m_hTargetEnt->pev->origin, gSkillData.hgruntGrenadeSpeed, 0.5 ); + else + { + // just shoot diagonally up+forwards + UTIL_MakeVectors(pev->angles); + vecToss = (gpGlobals->v_forward*0.5 + gpGlobals->v_up*0.5).Normalize() * gSkillData.hgruntGrenadeSpeed; + } + CGrenade::ShootContact( pev, GetGunPosition(), vecToss ); + } + else + CGrenade::ShootContact( pev, GetGunPosition(), m_vecTossVelocity ); + m_fThrowGrenade = FALSE; + if (g_iSkillLevel == SKILL_HARD) + m_flNextGrenadeCheck = gpGlobals->time + RANDOM_FLOAT( 2, 5 );// wait a random amount of time before shooting again + else + m_flNextGrenadeCheck = gpGlobals->time + 6;// wait six seconds before even looking again to see if a grenade can be thrown. + } + break; + + case HGRUNT_AE_GREN_DROP: + { + UTIL_MakeVectors( pev->angles ); + CGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 17 - gpGlobals->v_right * 27 + gpGlobals->v_up * 6, g_vecZero, 3 ); + } + break; + + case HGRUNT_AE_BURST1: + { + // ALERT(at_console, "-------- grunt burst 1\n"); + if ( FBitSet( pev->weapons, HGRUNT_9MMAR )) + { + // the first round of the three round burst plays the sound and puts a sound in the world sound list. + if (m_cAmmoLoaded > 0) + { + if ( RANDOM_LONG(0,1) ) + { + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "diversant/gr_mgun1.wav", 1, ATTN_NORM ); + } + else + { + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "diversant/gr_mgun2.wav", 1, ATTN_NORM ); + } + } + else + { + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/dryfire1.wav", 1, ATTN_NORM ); + } + + Shoot(); + } + else + { + Shotgun( ); + + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/sbarrel1.wav", 1, ATTN_NORM ); + } + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 384, 0.3 ); + } + break; + + case HGRUNT_AE_BURST2: + case HGRUNT_AE_BURST3: + Shoot(); + break; + + case HGRUNT_AE_KICK: + { + CBaseEntity *pHurt = Kick(); + + if ( pHurt ) + { + // SOUND HERE! + UTIL_MakeVectors( pev->angles ); + pHurt->pev->punchangle.x = 15; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 100 + gpGlobals->v_up * 50; + pHurt->TakeDamage( pev, pev, gSkillData.hgruntDmgKick, DMG_CLUB ); + } + } + break; + + case HGRUNT_AE_CAUGHT_ENEMY: + { + if ( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz(ENT(pev), "DV_ALERT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + + } + + default: + CSquadMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CHGrunt :: Spawn() +{ + Precache( ); + + // buz: rename to monster_human_grunt if set as monster_diversant + pev->classname = MAKE_STRING("monster_human_grunt"); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/diversant.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + if (pev->health == 0) + pev->health = gSkillData.hgruntHealth; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_flNextGrenadeCheck = gpGlobals->time + 1; + m_flNextPainTime = gpGlobals->time; + m_iSentence = HGRUNT_SENT_NONE; + + m_afCapability = bits_CAP_SQUAD | bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP; + + m_fEnemyEluded = FALSE; + m_fFirstEncounter = TRUE;// this is true when the grunt spawns, because he hasn't encountered an enemy yet. + + m_HackedGunPos = Vector ( 0, 0, 55 ); + + if (pev->weapons == 0) + { + // initialize to original values + pev->weapons = HGRUNT_9MMAR | HGRUNT_HANDGRENADE; + } + + // buz: pev->body is head stuff number + int headstuff = pev->body; + pev->body = 0; + SetBodygroup( DIVERSANT_HEADSTUFF_GROUP, headstuff ); + + // hack for studiomodelrenderer to draw goggles + if (headstuff == DIVERSANT_HEADSTUFF_PNV) + pev->renderfx = 50; + + // buz: pev->effects is ammunition number + int backpack = pev->effects; + SetBodygroup( DIVERSANT_AMMUNITION_GROUP, backpack ); + + if (FBitSet( pev->weapons, HGRUNT_SHOTGUN )) + { + SetBodygroup( DIVERSANT_GUN_GROUP, DIVERSANT_GUN_SHOTGUN ); + m_cClipSize = 8; + } + else + { + SetBodygroup( DIVERSANT_GUN_GROUP, DIVERSANT_GUN_MP5 ); + m_cClipSize = GRUNT_CLIP_SIZE; + } + + m_cAmmoLoaded = m_cClipSize; + + CTalkMonster::g_talkWaitTime = 0; + + // Wargon: Âîçìîæíîñòü ïîäáèðàòü ïàòðîíû þçîì èç ìåðòâûõ âðàæèí. (1.1) + m_iDeadAmmo = 0; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CHGrunt :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/diversant.mdl"); + + PRECACHE_SOUND( "weapons/dryfire1.wav" ); //LRC + + PRECACHE_MODEL("models/mask.mdl"); // buz test + + PRECACHE_SOUND( "diversant/gr_mgun1.wav" ); + PRECACHE_SOUND( "diversant/gr_mgun2.wav" ); + + PRECACHE_SOUND( "diversant/gr_die1.wav" ); + PRECACHE_SOUND( "diversant/gr_die2.wav" ); + PRECACHE_SOUND( "diversant/gr_die3.wav" ); + + PRECACHE_SOUND( "diversant/gr_pain1.wav" ); + PRECACHE_SOUND( "diversant/gr_pain2.wav" ); + PRECACHE_SOUND( "diversant/gr_pain3.wav" ); + PRECACHE_SOUND( "diversant/gr_pain4.wav" ); + PRECACHE_SOUND( "diversant/gr_pain5.wav" ); + + PRECACHE_SOUND( "diversant/gr_reload1.wav" ); + + PRECACHE_SOUND( "weapons/glauncher.wav" ); + + PRECACHE_SOUND( "weapons/sbarrel1.wav" ); + + PRECACHE_SOUND("zombie/claw_miss2.wav");// because we use the basemonster SWIPE animation event + + // Wargon: Âîçìîæíîñòü ïîäáèðàòü ïàòðîíû þçîì èç ìåðòâûõ âðàæèí. (1.1) + PRECACHE_SOUND("items/9mmclip1.wav"); + + // get voice pitch + if (RANDOM_LONG(0,1)) + m_voicePitch = 109 + RANDOM_LONG(0,7); + else + m_voicePitch = 100; + + m_iBrassShell = PRECACHE_MODEL ("models/shell.mdl");// brass shell + m_iShotgunShell = PRECACHE_MODEL ("models/shotgunshell.mdl"); +} + +//========================================================= +// start task +//========================================================= +void CHGrunt :: StartTask ( Task_t *pTask ) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch ( pTask->iTask ) + { + case TASK_GRUNT_CHECK_FIRE: + if ( !NoFriendlyFire() ) + { + SetConditions( bits_COND_GRUNT_NOFIRE ); + } + TaskComplete(); + break; + + case TASK_GRUNT_MOVE_SHOOTING: // buz + if (FRouteClear()) + TaskComplete(); + + Forget( bits_MEMORY_INCOVER ); + pev->gaitsequence = LookupSequence( "run" ); // test + if (HasConditions( bits_COND_CAN_RANGE_ATTACK1 )) + m_IdealActivity = ACT_USE; // special shoot anim (blended) + else + m_IdealActivity = ACT_GUARD; // special wait anim (blended) + + break; + + case TASK_GRUNT_SPEAK_SENTENCE: + SpeakSentence(); + TaskComplete(); + break; + + case TASK_WALK_PATH: + case TASK_RUN_PATH: + // grunt no longer assumes he is covered if he moves + Forget( bits_MEMORY_INCOVER ); + CSquadMonster ::StartTask( pTask ); + break; + + case TASK_RELOAD: + m_IdealActivity = ACT_RELOAD; + break; + + case TASK_GRUNT_FACE_TOSS_DIR: + break; + + case TASK_FACE_IDEAL: + case TASK_FACE_ENEMY: + CSquadMonster :: StartTask( pTask ); + if (pev->movetype == MOVETYPE_FLY) + { + m_IdealActivity = ACT_GLIDE; + } + break; + + default: + CSquadMonster :: StartTask( pTask ); + break; + } +} + +//========================================================= +// RunTask +//========================================================= +void CHGrunt :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_GRUNT_FACE_TOSS_DIR: + { + // project a point along the toss vector and turn to face that point. + SetIdealYawToTargetAndUpdate( pev->origin + m_vecTossVelocity * 64, AI_KEEP_YAW_SPEED ); + + if ( FacingIdeal() ) + { + m_iTaskStatus = TASKSTATUS_COMPLETE; + } + break; + } + + case TASK_GRUNT_MOVE_SHOOTING: // buz + { + SetIdealYawToTargetAndUpdate( m_vecEnemyLKP , AI_KEEP_YAW_SPEED ); + + if ( m_fSequenceFinished ) + { + if (MovementIsComplete() || HasConditions(bits_COND_ENEMY_DEAD)) + { + TaskComplete(); + RouteClear(); // Stop moving + m_Activity = ACT_RESET; + break; + } + + if (HasConditions( bits_COND_CAN_RANGE_ATTACK1 )) + m_IdealActivity = ACT_USE; // special shoot anim (blended) + else + m_IdealActivity = ACT_GUARD; // special wait anim (blended) + } + break; + } + default: + { + CSquadMonster :: RunTask( pTask ); + break; + } + } +} + +//========================================================= +// PainSound +//========================================================= +void CHGrunt :: PainSound ( void ) +{ + if ( gpGlobals->time > m_flNextPainTime ) + { +#if 0 + if ( RANDOM_LONG(0,99) < 5 ) + { + // pain sentences are rare + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz(ENT(pev), "HG_PAIN", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, PITCH_NORM); + JustSpoke(); + return; + } + } +#endif + switch ( RANDOM_LONG(0,6) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "diversant/gr_pain3.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "diversant/gr_pain4.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "diversant/gr_pain5.wav", 1, ATTN_NORM ); + break; + case 3: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "diversant/gr_pain1.wav", 1, ATTN_NORM ); + break; + case 4: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "diversant/gr_pain2.wav", 1, ATTN_NORM ); + break; + } + + m_flNextPainTime = gpGlobals->time + 1; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CHGrunt :: DeathSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "diversant/gr_die1.wav", 1, ATTN_IDLE ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "diversant/gr_die2.wav", 1, ATTN_IDLE ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "diversant/gr_die3.wav", 1, ATTN_IDLE ); + break; + } +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +//========================================================= +// GruntFail +//========================================================= +Task_t tlGruntFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slGruntFail[] = +{ + { + tlGruntFail, + ARRAYSIZE ( tlGruntFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2, + 0, + "Grunt Fail" + }, +}; + +//========================================================= +// Grunt Combat Fail +//========================================================= +Task_t tlGruntCombatFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slGruntCombatFail[] = +{ + { + tlGruntCombatFail, + ARRAYSIZE ( tlGruntCombatFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2, + 0, + "Grunt Combat Fail" + }, +}; + +//========================================================= +// Victory dance! +//========================================================= +Task_t tlGruntVictoryDance[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_WAIT, (float)1.5 }, + { TASK_GET_PATH_TO_ENEMY_CORPSE, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, +}; + +Schedule_t slGruntVictoryDance[] = +{ + { + tlGruntVictoryDance, + ARRAYSIZE ( tlGruntVictoryDance ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "GruntVictoryDance" + }, +}; + +//========================================================= +// Establish line of fire - move to a position that allows +// the grunt to attack. +//========================================================= +Task_t tlGruntEstablishLineOfFire[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_GRUNT_ELOF_FAIL }, + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_GRUNT_SPEAK_SENTENCE,(float)0 }, + { TASK_RUN_PATH, (float)0 }, +// { TASK_GRUNT_MOVE_SHOOTING, (float)0 }, // buz: TEST TEST TEST + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slGruntEstablishLineOfFire[] = +{ + { + tlGruntEstablishLineOfFire, + ARRAYSIZE ( tlGruntEstablishLineOfFire ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "GruntEstablishLineOfFire" + }, +}; + +//========================================================= +// GruntFoundEnemy - grunt established sight with an enemy +// that was hiding from the squad. +//========================================================= +Task_t tlGruntFoundEnemy[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY,(float)ACT_SIGNAL1 }, +}; + +Schedule_t slGruntFoundEnemy[] = +{ + { + tlGruntFoundEnemy, + ARRAYSIZE ( tlGruntFoundEnemy ), + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "GruntFoundEnemy" + }, +}; + +//========================================================= +// GruntCombatFace Schedule +//========================================================= +Task_t tlGruntCombatFace1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_WAIT, (float)1.5 }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_SWEEP }, +}; + +Schedule_t slGruntCombatFace[] = +{ + { + tlGruntCombatFace1, + ARRAYSIZE ( tlGruntCombatFace1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2, + 0, + "Combat Face" + }, +}; + +//========================================================= +// Suppressing fire - don't stop shooting until the clip is +// empty or grunt gets hurt. +//========================================================= +Task_t tlGruntSignalSuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_SIGNAL2 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntSignalSuppress[] = +{ + { + tlGruntSignalSuppress, + ARRAYSIZE ( tlGruntSignalSuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_GRUNT_NOFIRE | + bits_COND_NO_AMMO_LOADED, + + bits_SOUND_DANGER, + "SignalSuppress" + }, +}; + +Task_t tlGruntSuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntSuppress[] = +{ + { + tlGruntSuppress, + ARRAYSIZE ( tlGruntSuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_GRUNT_NOFIRE | + bits_COND_NO_AMMO_LOADED, + + bits_SOUND_DANGER, + "Suppress" + }, +}; + + +//========================================================= +// grunt wait in cover - we don't allow danger or the ability +// to attack to break a grunt's run to cover schedule, but +// when a grunt is in cover, we do want them to attack if they can. +//========================================================= +Task_t tlGruntWaitInCover[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)1 }, +}; + +Schedule_t slGruntWaitInCover[] = +{ + { + tlGruntWaitInCover, + ARRAYSIZE ( tlGruntWaitInCover ), + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2, + + bits_SOUND_DANGER, + "GruntWaitInCover" + }, +}; + +//========================================================= +// run to cover. +// !!!BUGBUG - set a decent fail schedule here. +//========================================================= +Task_t tlGruntTakeCover1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_GRUNT_TAKECOVER_FAILED }, + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_GRUNT_SPEAK_SENTENCE, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_WAIT_FACE_ENEMY }, +}; + +Schedule_t slGruntTakeCover[] = +{ + { + tlGruntTakeCover1, + ARRAYSIZE ( tlGruntTakeCover1 ), + 0, + 0, + "TakeCover" + }, +}; + +//========================================================= +// drop grenade then run to cover. +//========================================================= +Task_t tlGruntGrenadeCover1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)99 }, + { TASK_FIND_FAR_NODE_COVER_FROM_ENEMY, (float)384 }, + { TASK_PLAY_SEQUENCE, (float)ACT_SPECIAL_ATTACK1 }, + { TASK_CLEAR_MOVE_WAIT, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_WAIT_FACE_ENEMY }, +}; + +Schedule_t slGruntGrenadeCover[] = +{ + { + tlGruntGrenadeCover1, + ARRAYSIZE ( tlGruntGrenadeCover1 ), + 0, + 0, + "GrenadeCover" + }, +}; + + +//========================================================= +// drop grenade then run to cover. +//========================================================= +Task_t tlGruntTossGrenadeCover1[] = +{ + { TASK_FACE_ENEMY, (float)0 }, + { TASK_RANGE_ATTACK2, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_TAKE_COVER_FROM_ENEMY }, +}; + +Schedule_t slGruntTossGrenadeCover[] = +{ + { + tlGruntTossGrenadeCover1, + ARRAYSIZE ( tlGruntTossGrenadeCover1 ), + 0, + 0, + "TossGrenadeCover" + }, +}; + +//========================================================= +// hide from the loudest sound source (to run from grenade) +//========================================================= +Task_t tlGruntTakeCoverFromBestSound[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_COWER },// duck and cover if cannot move from explosion + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_BEST_SOUND, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_TURN_LEFT, (float)179 }, +}; + +Schedule_t slGruntTakeCoverFromBestSound[] = +{ + { + tlGruntTakeCoverFromBestSound, + ARRAYSIZE ( tlGruntTakeCoverFromBestSound ), + 0, + 0, + "GruntTakeCoverFromBestSound" + }, +}; + +//========================================================= +// Grunt reload schedule +//========================================================= +Task_t tlGruntHideReload[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RELOAD }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_RELOAD }, +}; + +Schedule_t slGruntHideReload[] = +{ + { + tlGruntHideReload, + ARRAYSIZE ( tlGruntHideReload ), + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "GruntHideReload" + } +}; + +//========================================================= +// Do a turning sweep of the area +//========================================================= +Task_t tlGruntSweep[] = +{ + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slGruntSweep[] = +{ + { + tlGruntSweep, + ARRAYSIZE ( tlGruntSweep ), + + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_HEAR_SOUND, + + bits_SOUND_WORLD |// sound flags + bits_SOUND_DANGER | + bits_SOUND_PLAYER, + + "Grunt Sweep" + }, +}; + +//========================================================= +// primary range attack. Overriden because base class stops attacking when the enemy is occluded. +// grunt's grenade toss requires the enemy be occluded. +//========================================================= +Task_t tlGruntRangeAttack1A[] = +{ + { TASK_STOP_MOVING, (float)0 }, +// { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCH }, buz + { TASK_FACE_ENEMY, (float)0 }, // buz + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntRangeAttack1A[] = +{ + { + tlGruntRangeAttack1A, + ARRAYSIZE ( tlGruntRangeAttack1A ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_HEAR_SOUND | + bits_COND_GRUNT_NOFIRE | + bits_COND_NO_AMMO_LOADED, + + bits_SOUND_DANGER, + "Range Attack1A" + }, +}; + + +//========================================================= +// primary range attack. Overriden because base class stops attacking when the enemy is occluded. +// grunt's grenade toss requires the enemy be occluded. +//========================================================= +Task_t tlGruntRangeAttack1B[] = +{ + { TASK_STOP_MOVING, (float)0 }, +// { TASK_PLAY_SEQUENCE_FACE_ENEMY,(float)ACT_IDLE_ANGRY }, buz + { TASK_FACE_ENEMY, (float)0 }, // buz + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntRangeAttack1B[] = +{ + { + tlGruntRangeAttack1B, + ARRAYSIZE ( tlGruntRangeAttack1B ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_LIGHT_DAMAGE | // buz: 1B is interruptable by light damage + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED | + bits_COND_GRUNT_NOFIRE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Range Attack1B" + }, +}; + +//========================================================= +// secondary range attack. Overriden because base class stops attacking when the enemy is occluded. +// grunt's grenade toss requires the enemy be occluded. +//========================================================= +Task_t tlGruntRangeAttack2[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_GRUNT_FACE_TOSS_DIR, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_RANGE_ATTACK2 }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_WAIT_FACE_ENEMY },// don't run immediately after throwing grenade. +}; + +Schedule_t slGruntRangeAttack2[] = +{ + { + tlGruntRangeAttack2, + ARRAYSIZE ( tlGruntRangeAttack2 ), + 0, + 0, + "RangeAttack2" + }, +}; + + +//========================================================= +// repel +//========================================================= +Task_t tlGruntRepel[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_GLIDE }, +}; + +Schedule_t slGruntRepel[] = +{ + { + tlGruntRepel, + ARRAYSIZE ( tlGruntRepel ), + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER, + "Repel" + }, +}; + + +//========================================================= +// repel +//========================================================= +Task_t tlGruntRepelAttack[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_FLY }, +}; + +Schedule_t slGruntRepelAttack[] = +{ + { + tlGruntRepelAttack, + ARRAYSIZE ( tlGruntRepelAttack ), + bits_COND_ENEMY_OCCLUDED, + 0, + "Repel Attack" + }, +}; + +//========================================================= +// repel land +//========================================================= +Task_t tlGruntRepelLand[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_LAND }, + { TASK_GET_PATH_TO_LASTPOSITION,(float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_CLEAR_LASTPOSITION, (float)0 }, +}; + +Schedule_t slGruntRepelLand[] = +{ + { + tlGruntRepelLand, + ARRAYSIZE ( tlGruntRepelLand ), + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER, + "Repel Land" + }, +}; + +//========================================================= +// buz: duck and wait couple seconds behind the barrel, crate, etc.. +//========================================================= +Task_t tlGruntDuckAndCoverWait[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_GRUNT_SPEAK_SENTENCE, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_TWITCH }, + { TASK_WAIT, (float)3 }, // randomize a bit? +}; + +Schedule_t slGruntDuckAndCoverWait[] = +{ + { + tlGruntDuckAndCoverWait, + ARRAYSIZE ( tlGruntDuckAndCoverWait ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + // bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_CROUCH_NOT_SAFE | // buz: terminate, if crouching is not safe more + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "DuckAndCover!" + }, +}; +/* +// ==================================== +// SCHED_GRUNT_RUN_SHOOTING - buz +// ==================================== +Task_t tlGruntRepelLand[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GET_PATH_TO_LASTPOSITION,(float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_CLEAR_LASTPOSITION, (float)0 }, +}; + +Schedule_t slGruntRepelLand[] = +{ + { + tlGruntRepelLand, + ARRAYSIZE ( tlGruntRepelLand ), + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER, + "Repel Land" + }, +};*/ + +DEFINE_CUSTOM_SCHEDULES( CHGrunt ) +{ + slGruntFail, + slGruntCombatFail, + slGruntVictoryDance, + slGruntEstablishLineOfFire, + slGruntFoundEnemy, + slGruntCombatFace, + slGruntSignalSuppress, + slGruntSuppress, + slGruntWaitInCover, + slGruntTakeCover, + slGruntGrenadeCover, + slGruntTossGrenadeCover, + slGruntTakeCoverFromBestSound, + slGruntHideReload, + slGruntSweep, + slGruntRangeAttack1A, + slGruntRangeAttack1B, + slGruntRangeAttack2, + slGruntRepel, + slGruntRepelAttack, + slGruntRepelLand, + slGruntDuckAndCoverWait, // buz +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CHGrunt, CSquadMonster ); + +//========================================================= +// SetActivity +//========================================================= +void CHGrunt :: SetActivity ( Activity NewActivity ) +{ + int iSequence = ACTIVITY_NOT_AVAILABLE; + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + switch ( NewActivity) + { + case ACT_RANGE_ATTACK1: + // grunt is either shooting standing or shooting crouched + if (FBitSet( pev->weapons, HGRUNT_9MMAR)) + { + if ( m_fStanding ) + { + // get aimable sequence + iSequence = LookupSequence( "standing_mp5" ); + } + else + { + // get crouching shoot + iSequence = LookupSequence( "crouching_mp5" ); + } + } + else + { + if ( m_fStanding ) + { + // get aimable sequence + iSequence = LookupSequence( "standing_shotgun" ); + } + else + { + // get crouching shoot + iSequence = LookupSequence( "crouching_shotgun" ); + } + } + break; + case ACT_RANGE_ATTACK2: + // grunt is going to a secondary long range attack. This may be a thrown + // grenade or fired grenade, we must determine which and pick proper sequence + if ( pev->weapons & HGRUNT_HANDGRENADE ) + { + // get toss anim + iSequence = LookupSequence( "throwgrenade" ); + } + // LRC: added a test to stop a marine without a launcher from firing. + else if ( pev->weapons & HGRUNT_GRENADELAUNCHER ) + { + // get launch anim + iSequence = LookupSequence( "launchgrenade" ); + } + else + { + // ALERT( at_debug, "No grenades available. "); // flow into the error message we get at the end... + } + break; + case ACT_RUN: + if ( pev->health <= HGRUNT_LIMP_HEALTH ) + { + // limp! + iSequence = LookupActivity ( ACT_RUN_HURT ); + } + else + { + // buz: get combat movement animation in combat state + // if (m_MonsterState == MONSTERSTATE_COMBAT || m_MonsterState == MONSTERSTATE_ALERT) + if (m_iUseAlertAnims) + { + iSequence = LookupSequence("combat_run_primary"); + if (iSequence != -1) + break; + } + iSequence = LookupActivity ( NewActivity ); + } + break; + case ACT_WALK: + if ( pev->health <= HGRUNT_LIMP_HEALTH ) + { + // limp! + iSequence = LookupActivity ( ACT_WALK_HURT ); + } + else + { + // buz: get combat movement animation in combat state + // if (m_MonsterState == MONSTERSTATE_COMBAT || m_MonsterState == MONSTERSTATE_ALERT) + if (m_iUseAlertAnims) + { + iSequence = LookupSequence("combat_walk_primary"); + if (iSequence != -1) + break; + } + iSequence = LookupActivity ( NewActivity ); + } + break; + case ACT_IDLE: + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + { + NewActivity = ACT_IDLE_ANGRY; + } + iSequence = LookupActivity ( NewActivity ); + break; + default: + iSequence = LookupActivity ( NewActivity ); + break; + } + + m_Activity = NewActivity; // Go ahead and set this so it doesn't keep trying when the anim is not present + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + { + if ( pev->sequence != iSequence || !m_fSequenceLoops ) + { + pev->frame = 0; + } + + pev->sequence = iSequence; // Set to the reset anim (if it's there) + ResetSequenceInfo( ); + RecalculateYawSpeed(); + } + else + { + // Not available try to get default anim + ALERT ( at_debug, "%s has no sequence for act:%s\n", STRING(pev->classname), GetNameForActivity( NewActivity )); + pev->sequence = 0; // Set to the reset anim (if it's there) + } +} + +//========================================================= +// Get Schedule! +//========================================================= +Schedule_t *CHGrunt :: GetSchedule( void ) +{ + + // clear old sentence + m_iSentence = HGRUNT_SENT_NONE; + + // flying? If PRONE, barnacle has me. IF not, it's assumed I am rapelling. + if ( pev->movetype == MOVETYPE_FLY && m_MonsterState != MONSTERSTATE_PRONE ) + { + if (pev->flags & FL_ONGROUND) + { + // just landed + pev->movetype = MOVETYPE_STEP; + return GetScheduleOfType ( SCHED_GRUNT_REPEL_LAND ); + } + else + { + // repel down a rope, + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + return GetScheduleOfType ( SCHED_GRUNT_REPEL_ATTACK ); + else + return GetScheduleOfType ( SCHED_GRUNT_REPEL ); + } + } + + // grunts place HIGH priority on running away from danger sounds. + if ( HasConditions(bits_COND_HEAR_SOUND) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound) + { + if (pSound->m_iType & bits_SOUND_DANGER) + { + // dangerous sound nearby! + + //!!!KELLY - currently, this is the grunt's signal that a grenade has landed nearby, + // and the grunt should find cover from the blast + // good place for "SHIT!" or some other colorful verbal indicator of dismay. + // It's not safe to play a verbal order here "Scatter", etc cause + // this may only affect a single individual in a squad. + + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), "DV_GREN", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + // buz: this was commented. Why? + if (!HasConditions( bits_COND_SEE_ENEMY ) && ( pSound->m_iType & (bits_SOUND_PLAYER | bits_SOUND_COMBAT) )) + { + SetIdealYawToTargetAndUpdate( pSound->m_vecOrigin ); + } + + } + } + switch ( m_MonsterState ) + { + case MONSTERSTATE_IDLE: // buz: ïåðåçàðÿäèòüñÿ, åñëè âðàãà íåò è ìàãàçèí ïîëóïóñò + case MONSTERSTATE_ALERT: + + if (m_cAmmoLoaded < m_cClipSize / 2) + { + return GetScheduleOfType ( SCHED_RELOAD ); + } + break; + + case MONSTERSTATE_COMBAT: + { +// dead enemy + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + +// new enemy + if ( HasConditions(bits_COND_NEW_ENEMY) ) + { + if ( InSquad() ) + { + MySquadLeader()->m_fEnemyEluded = FALSE; + + if ( !IsLeader() ) + { + return GetScheduleOfType ( SCHED_TAKE_COVER_FROM_ENEMY ); + } + else + { + // ALERT(at_aiconsole,"leader spotted player!\n"); + //!!!KELLY - the leader of a squad of grunts has just seen the player or a + // monster and has made it the squad's enemy. You + // can check pev->flags for FL_CLIENT to determine whether this is the player + // or a monster. He's going to immediately start + // firing, though. If you'd like, we can make an alternate "first sight" + // schedule where the leader plays a handsign anim + // that gives us enough time to hear a short sentence or spoken command + // before he starts pluggin away. + if (FOkToSpeak())// && RANDOM_LONG(0,1)) + { + if (m_hEnemy != NULL) + { + if (m_hEnemy->IsPlayer()) + SENTENCEG_PlayRndSz( ENT(pev), "DV_ALERT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + else if ((m_hEnemy->Classify() == CLASS_ALIEN_MILITARY) || + (m_hEnemy->Classify() == CLASS_ALIEN_MONSTER) || + (m_hEnemy->Classify() == CLASS_ALIEN_PREY) || + (m_hEnemy->Classify() == CLASS_ALIEN_PREDATOR)) + SENTENCEG_PlayRndSz( ENT(pev), "DV_MONST", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + } + + JustSpoke(); + } + + if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_GRUNT_SUPPRESS ); + } + else + { + return GetScheduleOfType ( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } + } + } + } +// no ammo + else if ( HasConditions ( bits_COND_NO_AMMO_LOADED ) ) + { + if ((m_afCapability & bits_CAP_CROUCH_COVER) && !HasConditions(bits_COND_CROUCH_NOT_SAFE) ) // buz: reload here, if safe + return GetScheduleOfType ( SCHED_RELOAD ); + else + return GetScheduleOfType ( SCHED_GRUNT_COVER_AND_RELOAD ); + } + +// damaged just a little + else if ( HasConditions( bits_COND_LIGHT_DAMAGE ) ) + { + /* // if hurt: + // 90% chance of taking cover + // 10% chance of flinch. + int iPercent = RANDOM_LONG(0,99); + + if ( iPercent <= 90 && m_hEnemy != NULL ) + { + // only try to take cover if we actually have an enemy! + + //!!!KELLY - this grunt was hit and is going to run to cover. + if (FOkToSpeak()) // && RANDOM_LONG(0,1)) + { + //SENTENCEG_PlayRndSz( ENT(pev), "HG_COVER", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + m_iSentence = HGRUNT_SENT_COVER; + //JustSpoke(); + } + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + else + { + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + }*/ + + int iPercent; + + // buz: 90% to duck and cover, if can + if ((m_afCapability & bits_CAP_CROUCH_COVER) && !HasConditions(bits_COND_CROUCH_NOT_SAFE) && m_hEnemy != NULL ) + { + iPercent = RANDOM_LONG(0,99); + if (iPercent <= 90) + return GetScheduleOfType( SCHED_GRUNT_DUCK_COVER_WAIT ); // wait some time in cover + } + + // buz: now 50% to try normal way of taking cover + iPercent = RANDOM_LONG(0,99); + if ( iPercent <= 50 && m_hEnemy != NULL ) + { + //!!!KELLY - this grunt was hit and is going to run to cover. + if (FOkToSpeak()) // && RANDOM_LONG(0,1)) + { + //SENTENCEG_PlayRndSz( ENT(pev), "HG_COVER", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + m_iSentence = HGRUNT_SENT_COVER; + //JustSpoke(); + } + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + else + { + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + } +// can kick + else if ( HasConditions ( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_MELEE_ATTACK1 ); + } +// can grenade launch + + else if ( FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER) && HasConditions ( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + // shoot a grenade if you can + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } +// can shoot + else if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + if ( InSquad() ) + { + // if the enemy has eluded the squad and a squad member has just located the enemy + // and the enemy does not see the squad member, issue a call to the squad to waste a + // little time and give the player a chance to turn. + if ( MySquadLeader()->m_fEnemyEluded && !HasConditions ( bits_COND_ENEMY_FACING_ME ) ) + { + MySquadLeader()->m_fEnemyEluded = FALSE; + return GetScheduleOfType ( SCHED_GRUNT_FOUND_ENEMY ); + } + } + + if ( OccupySlot ( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + // try to take an available ENGAGE slot + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + else if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + // throw a grenade if can and no engage slots are available + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + else + { + // hide! + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + } +// can't see enemy + else if ( HasConditions( bits_COND_ENEMY_OCCLUDED ) ) + { + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + //!!!KELLY - this grunt is about to throw or fire a grenade at the player. Great place for "fire in the hole" "frag out" etc + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), "DV_THROW", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + else if ( OccupySlot( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + //!!!KELLY - grunt cannot see the enemy and has just decided to + // charge the enemy's position. + if (FOkToSpeak())// && RANDOM_LONG(0,1)) + { + //SENTENCEG_PlayRndSz( ENT(pev), "HG_CHARGE", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + m_iSentence = HGRUNT_SENT_CHARGE; + //JustSpoke(); + } + + return GetScheduleOfType( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } + else + { + //!!!KELLY - grunt is going to stay put for a couple seconds to see if + // the enemy wanders back out into the open, or approaches the + // grunt's covered position. Good place for a taunt, I guess? + if (FOkToSpeak() && RANDOM_LONG(0,1)) + { + SENTENCEG_PlayRndSz( ENT(pev), "DV_TAUNT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + return GetScheduleOfType( SCHED_STANDOFF ); + } + } + + if ( HasConditions( bits_COND_SEE_ENEMY ) && !HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } + } + } + + // no special cases here, call the base class + return CSquadMonster :: GetSchedule(); +} + +//========================================================= +//========================================================= +Schedule_t* CHGrunt :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_TAKE_COVER_FROM_ENEMY: + { + if ( InSquad() ) + { + if ( g_iSkillLevel == SKILL_HARD && HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), "DV_THROW", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + return slGruntTossGrenadeCover; + } + else + { + return &slGruntTakeCover[ 0 ]; + } + } + else + { + if ( OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) && RANDOM_LONG(0,1) ) + { + return &slGruntGrenadeCover[ 0 ]; + } + else + { + return &slGruntTakeCover[ 0 ]; + } + } + } + case SCHED_GRUNT_DUCK_COVER_WAIT: // buz + { + return &slGruntDuckAndCoverWait[ 0 ]; + } + case SCHED_TAKE_COVER_FROM_BEST_SOUND: + { + return &slGruntTakeCoverFromBestSound[ 0 ]; + } + case SCHED_GRUNT_TAKECOVER_FAILED: + { + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) && OccupySlot( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + + return GetScheduleOfType ( SCHED_FAIL ); + } + break; + case SCHED_GRUNT_ELOF_FAIL: + { + // human grunt is unable to move to a position that allows him to attack the enemy. + return GetScheduleOfType ( SCHED_TAKE_COVER_FROM_ENEMY ); + } + break; + case SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE: + { + return &slGruntEstablishLineOfFire[ 0 ]; + } + break; + case SCHED_RANGE_ATTACK1: + { + // randomly stand or crouch + // buz: use CheckRangedAttack1's recommendations + switch (m_iLastFireCheckResult) + { + case 0: // fire any + default: + if (RANDOM_LONG(0,5) == 0) + m_fStanding = RANDOM_LONG(0,1); + break; + case 1: // only crouched + m_fStanding = FALSE; + break; + case 2: // only standing + m_fStanding = TRUE; + break; + } + +/* if (m_fStanding) + return &slGruntRangeAttack1B[ 0 ]; + else + return &slGruntRangeAttack1A[ 0 ];*/ + // buz: 1B is interruptable - use it if grunt can fast take cover when hit + if ((m_afCapability & bits_CAP_CROUCH_COVER) && !HasConditions(bits_COND_CROUCH_NOT_SAFE) ) + return &slGruntRangeAttack1B[ 0 ]; + else + return &slGruntRangeAttack1A[ 0 ]; + } + case SCHED_RANGE_ATTACK2: + { + return &slGruntRangeAttack2[ 0 ]; + } + case SCHED_COMBAT_FACE: + { + return &slGruntCombatFace[ 0 ]; + } + case SCHED_GRUNT_WAIT_FACE_ENEMY: + { + return &slGruntWaitInCover[ 0 ]; + } + case SCHED_GRUNT_SWEEP: + { + return &slGruntSweep[ 0 ]; + } + case SCHED_GRUNT_COVER_AND_RELOAD: + { + return &slGruntHideReload[ 0 ]; + } + case SCHED_GRUNT_FOUND_ENEMY: + { + return &slGruntFoundEnemy[ 0 ]; + } + case SCHED_VICTORY_DANCE: + { + if ( InSquad() ) + { + if ( !IsLeader() ) + { + return &slGruntFail[ 0 ]; + } + } + + return &slGruntVictoryDance[ 0 ]; + } + case SCHED_GRUNT_SUPPRESS: + { + // buz: use CheckRangedAttack1's recommendations + switch (m_iLastFireCheckResult) + { + case 0: // fire any + default: + if (RANDOM_LONG(0,5) == 0) + m_fStanding = RANDOM_LONG(0,1); + break; + case 1: // only crouched + m_fStanding = FALSE; + break; + case 2: // only standing + m_fStanding = TRUE; + break; + } + + if ( m_hEnemy->IsPlayer() && m_fFirstEncounter ) + { + m_fFirstEncounter = FALSE;// after first encounter, leader won't issue handsigns anymore when he has a new enemy + return &slGruntSignalSuppress[ 0 ]; + } + else + { + return &slGruntSuppress[ 0 ]; + } + } + case SCHED_FAIL: + { + if ( m_hEnemy != NULL ) + { + // grunt has an enemy, so pick a different default fail schedule most likely to help recover. + return &slGruntCombatFail[ 0 ]; + } + + return &slGruntFail[ 0 ]; + } + case SCHED_GRUNT_REPEL: + { + if (pev->velocity.z > -128) + pev->velocity.z -= 32; + return &slGruntRepel[ 0 ]; + } + case SCHED_GRUNT_REPEL_ATTACK: + { + if (pev->velocity.z > -128) + pev->velocity.z -= 32; + return &slGruntRepelAttack[ 0 ]; + } + case SCHED_GRUNT_REPEL_LAND: + { + return &slGruntRepelLand[ 0 ]; + } + default: + { + return CSquadMonster :: GetScheduleOfType ( Type ); + } + } +} + + +//========================================================= +// CHGruntRepel - when triggered, spawns a monster_human_grunt +// repelling down a line. +//========================================================= + +class CHGruntRepel : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void EXPORT RepelUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int m_iSpriteTexture; // Don't save, precache +}; + +LINK_ENTITY_TO_CLASS( monster_grunt_repel, CHGruntRepel ); + +void CHGruntRepel::Spawn( void ) +{ + Precache( ); + pev->solid = SOLID_NOT; + + SetUse(&CHGruntRepel:: RepelUse ); +} + +void CHGruntRepel::Precache( void ) +{ + UTIL_PrecacheOther( "monster_human_grunt" ); + m_iSpriteTexture = PRECACHE_MODEL( "sprites/rope.spr" ); +} + +void CHGruntRepel::RepelUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + TraceResult tr; + UTIL_TraceLine( pev->origin, pev->origin + Vector( 0, 0, -4096.0), dont_ignore_monsters, ENT(pev), &tr); + /* + if ( tr.pHit && Instance( tr.pHit )->pev->solid != SOLID_BSP) + return NULL; + */ + + CBaseEntity *pEntity = Create( "monster_human_grunt", pev->origin, pev->angles ); + CBaseMonster *pGrunt = pEntity->MyMonsterPointer( ); + pGrunt->pev->movetype = MOVETYPE_FLY; + pGrunt->pev->velocity = Vector( 0, 0, RANDOM_FLOAT( -196, -128 ) ); + pGrunt->SetActivity( ACT_GLIDE ); + // UNDONE: position? + pGrunt->m_vecLastPosition = tr.vecEndPos; + + CBeam *pBeam = CBeam::BeamCreate( "sprites/rope.spr", 10 ); + pBeam->PointEntInit( pev->origin + Vector(0,0,112), pGrunt->entindex() ); + pBeam->SetFlags( BEAM_FSOLID ); + pBeam->SetColor( 255, 255, 255 ); + pBeam->SetThink(&CBeam:: SUB_Remove ); + pBeam->SetNextThink( -4096.0 * tr.flFraction / pGrunt->pev->velocity.z + 0.5 ); + + UTIL_Remove( this ); +} + + + +//========================================================= +// DEAD HGRUNT PROP +//========================================================= +class CDeadHGrunt : public CBaseMonster +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_HUMAN_MILITARY; } + + void KeyValue( KeyValueData *pkvd ); + float MaxYawSpeed( void ) { return 8.0f; } + int m_iPose;// which sequence to display -- temporary, don't need to save + static char *m_szPoses[3]; +}; + +char *CDeadHGrunt::m_szPoses[] = { "deadstomach", "deadside", "deadsitting" }; + +void CDeadHGrunt::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "pose")) + { + m_iPose = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + +LINK_ENTITY_TO_CLASS( monster_hgrunt_dead, CDeadHGrunt ); + +//========================================================= +// ********** DeadHGrunt SPAWN ********** +//========================================================= +void CDeadHGrunt :: Spawn( void ) +{ + int oldBody; + + PRECACHE_MODEL("models/diversant.mdl"); + PRECACHE_MODEL("models/mask.mdl"); + SET_MODEL(ENT(pev), "models/diversant.mdl"); + + pev->sequence = 0; + m_bloodColor = BLOOD_COLOR_RED; + + pev->sequence = LookupSequence( m_szPoses[m_iPose] ); + + if (pev->sequence == -1) + { + ALERT ( at_debug, "Dead hgrunt with bad pose\n" ); + } + + // Corpses have less health + pev->health = 8; + + oldBody = pev->body; + pev->body = 0; + + SetBodygroup( DIVERSANT_HEADSTUFF_GROUP, DIVERSANT_HEADSTUFF_NO ); + SetBodygroup( DIVERSANT_GUN_GROUP, DIVERSANT_GUN_NONE ); + + MonsterInitDead(); +} + +// buz: overriden for grunts to fix model's bugs... +void CHGrunt :: SetEyePosition ( void ) +{ + Vector vecEyePosition; + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + GetEyePosition( pmodel, vecEyePosition ); + + pev->view_ofs = vecEyePosition; + + if ( pev->view_ofs == g_vecZero ) + { + pev->view_ofs = Vector(0, 0 ,73); + // ALERT ( at_aiconsole, "using default view ofs for %s\n", STRING ( pev->classname ) ); + } +} + +// Wargon: Âîçìîæíîñòü ïîäáèðàòü ïàòðîíû þçîì èç ìåðòâûõ âðàæèí. (1.1) +void CHGrunt :: DeadUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (!pActivator->IsPlayer()) + return; + if (FBitSet(pev->weapons, HGRUNT_9MMAR) && pActivator->GiveAmmo( m_iDeadAmmo, "ak" ) != -1) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + m_iDeadAmmo = 0; + SetUse(NULL); + } + else if (FBitSet(pev->weapons, HGRUNT_SHOTGUN) && pActivator->GiveAmmo( m_iDeadAmmo, "buckshot" ) != -1) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + m_iDeadAmmo = 0; + SetUse(NULL); + } +} + +// Wargon: Âîçìîæíîñòü ïîäáèðàòü ïàòðîíû þçîì èç ìåðòâûõ âðàæèí. (1.1) +void CHGrunt :: Killed( entvars_t *pevAttacker, int iGib ) +{ + if (gSkillData.maxDeadEnemyAmmo >= 1 && !ShouldGibMonster(iGib)) + { + m_iDeadAmmo = RANDOM_LONG(1, gSkillData.maxDeadEnemyAmmo); + SetUse(&CHGrunt::DeadUse); + } + CSquadMonster::Killed(pevAttacker, iGib); +} + + +// ====================== DIVERSANTS WITH GLOCK ==================== + +#define DIVERSANT2_GUN_GROUP 3 +#define DIVERSANT2_GUN_GLOCK 0 +#define DIVERSANT2_GUN_NO 1 + +class CHGruntGlock : public CHGrunt +{ +public: + void Spawn( void ); + void Precache( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void Shoot ( void ); + void SetActivity ( Activity NewActivity ); + // Wargon: Âîçìîæíîñòü ïîäáèðàòü ïàòðîíû þçîì èç ìåðòâûõ âðàæèí. (1.1) + void EXPORT DeadUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Killed( entvars_t *pevAttacker, int iGib ); +}; + +LINK_ENTITY_TO_CLASS( monster_diversant_pistol, CHGruntGlock ); + +void CHGruntGlock :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/diversant_pistol.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + if (pev->health == 0) + pev->health = gSkillData.hgruntHealth; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_flNextGrenadeCheck = gpGlobals->time + 1; + m_flNextPainTime = gpGlobals->time; + m_iSentence = HGRUNT_SENT_NONE; + + m_afCapability = bits_CAP_SQUAD | bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP; + + m_fEnemyEluded = FALSE; + m_fFirstEncounter = TRUE;// this is true when the grunt spawns, because he hasn't encountered an enemy yet. + + m_HackedGunPos = Vector ( 0, 0, 55 ); + + // clear all bits except grenades + if (FBitSet(pev->weapons, HGRUNT_HANDGRENADE)) + pev->weapons = HGRUNT_HANDGRENADE; + else + pev->weapons = 0; + + // buz: pev->body is head stuff number + int headstuff = pev->body; + pev->body = 0; + SetBodygroup( DIVERSANT_HEADSTUFF_GROUP, headstuff ); + + // hack for studiomodelrenderer to draw goggles + if (headstuff == DIVERSANT_HEADSTUFF_PNV) + pev->renderfx = 50; + + m_cClipSize = 14; + m_cAmmoLoaded = m_cClipSize; + + CTalkMonster::g_talkWaitTime = 0; + + // Wargon: Âîçìîæíîñòü ïîäáèðàòü ïàòðîíû þçîì èç ìåðòâûõ âðàæèí. (1.1) + m_iDeadAmmo = 0; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CHGruntGlock :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/diversant_pistol.mdl"); + + PRECACHE_SOUND( "weapons/dryfire1.wav" ); //LRC + + PRECACHE_MODEL("models/mask.mdl"); // buz test + + PRECACHE_SOUND( "weapons/glock_fire.wav" ); + + PRECACHE_SOUND( "diversant/gr_die1.wav" ); + PRECACHE_SOUND( "diversant/gr_die2.wav" ); + PRECACHE_SOUND( "diversant/gr_die3.wav" ); + + PRECACHE_SOUND( "diversant/gr_pain1.wav" ); + PRECACHE_SOUND( "diversant/gr_pain2.wav" ); + PRECACHE_SOUND( "diversant/gr_pain3.wav" ); + PRECACHE_SOUND( "diversant/gr_pain4.wav" ); + PRECACHE_SOUND( "diversant/gr_pain5.wav" ); + + PRECACHE_SOUND( "diversant/gr_reload_glock.wav" ); + + PRECACHE_SOUND( "weapons/glauncher.wav" ); + + PRECACHE_SOUND( "weapons/sbarrel1.wav" ); + + PRECACHE_SOUND("zombie/claw_miss2.wav");// because we use the basemonster SWIPE animation event + + // get voice pitch + if (RANDOM_LONG(0,1)) + m_voicePitch = 109 + RANDOM_LONG(0,7); + else + m_voicePitch = 100; + + m_iBrassShell = PRECACHE_MODEL ("models/shell.mdl");// brass shell + m_iShotgunShell = PRECACHE_MODEL ("models/shotgunshell.mdl"); +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CHGruntGlock :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + Vector vecShootDir; + Vector vecShootOrigin; + + switch( pEvent->event ) + { + case HGRUNT_AE_DROP_GUN: + { + if (pev->spawnflags & SF_MONSTER_NO_WPN_DROP) break; //LRC + + Vector vecGunPos; + Vector vecGunAngles; + + GetAttachment( 0, vecGunPos, vecGunAngles ); + + // switch to body group with no gun. + SetBodygroup( DIVERSANT2_GUN_GROUP, DIVERSANT2_GUN_NO ); + + // now spawn a gun. + DropItem( "weapon_glock", vecGunPos, vecGunAngles ); + + if (FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER )) + { + DropItem( "ammo_vog25", BodyTarget( pev->origin ), vecGunAngles ); + } + + } + break; + + case HGRUNT_AE_RELOAD: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "diversant/gr_reload_glock.wav", 1, ATTN_NORM ); + m_cAmmoLoaded = m_cClipSize; + ClearConditions(bits_COND_NO_AMMO_LOADED); + break; + + case HGRUNT_AE_BURST1: + { + // the first round of the three round burst plays the sound and puts a sound in the world sound list. + if (m_cAmmoLoaded > 0) + { + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/glock_fire.wav", 1, ATTN_NORM ); + Shoot(); + } + else + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/dryfire1.wav", 1, ATTN_NORM ); + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 384, 0.3 ); + } + break; + + default: + CHGrunt::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Shoot +//========================================================= +void CHGruntGlock :: Shoot ( void ) +{ +// if (m_hEnemy == NULL && m_pCine == NULL) //LRC - scripts may fire when you have no enemy +// { +// return; +// } + + UTIL_MakeVectors ( pev->angles ); + Vector vecShootOrigin = GetGunPosition(); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); + + Vector vecBrassPos, vecBrassDir; + GetAttachment(3, vecBrassPos, vecBrassDir); + Vector vecShellVelocity = gpGlobals->v_right * RANDOM_FLOAT(40,90) + gpGlobals->v_up * RANDOM_FLOAT(75,200) + gpGlobals->v_forward * RANDOM_FLOAT(-40, 40); + EjectBrass( vecBrassPos, vecShellVelocity, pev->angles.y, m_iBrassShell, TE_BOUNCE_SHELL); + FireBullets( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_5DEGREES, 2048.0f, BULLET_NORMAL, gSkillData.monDmgGlock ); + + pev->effects |= EF_MUZZLEFLASH; + + m_cAmmoLoaded--;// take away a bullet! + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); +} + + +void CHGruntGlock :: SetActivity ( Activity NewActivity ) +{ + int iSequence = ACTIVITY_NOT_AVAILABLE; + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + switch ( NewActivity) + { + case ACT_RANGE_ATTACK1: + //iSequence = LookupActivity ( NewActivity ); + if ( m_fStanding ) + { + // get aimable sequence + iSequence = LookupSequence( "standing" ); + } + else + { + // get crouching shoot + iSequence = LookupSequence( "crouching" ); + } + + break; + case ACT_RANGE_ATTACK2: + // get toss anim + iSequence = LookupSequence( "throwgrenade" ); + + break; + case ACT_RUN: + if ( pev->health <= HGRUNT_LIMP_HEALTH ) + { + // limp! + iSequence = LookupActivity ( ACT_RUN_HURT ); + } + else + { + // buz: get combat movement animation in combat state + // if (m_MonsterState == MONSTERSTATE_COMBAT || m_MonsterState == MONSTERSTATE_ALERT) + if (m_iUseAlertAnims) + { + iSequence = LookupSequence("combat_run_primary"); + if (iSequence != -1) + break; + } + iSequence = LookupActivity ( NewActivity ); + } + break; + case ACT_WALK: + if ( pev->health <= HGRUNT_LIMP_HEALTH ) + { + // limp! + iSequence = LookupActivity ( ACT_WALK_HURT ); + } + else + { + // buz: get combat movement animation in combat state + // if (m_MonsterState == MONSTERSTATE_COMBAT || m_MonsterState == MONSTERSTATE_ALERT) + if (m_iUseAlertAnims) + { + iSequence = LookupSequence("combat_walk_primary"); + if (iSequence != -1) + break; + } + iSequence = LookupActivity ( NewActivity ); + } + break; + case ACT_IDLE: + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + { + NewActivity = ACT_IDLE_ANGRY; + } + iSequence = LookupActivity ( NewActivity ); + break; + default: + iSequence = LookupActivity ( NewActivity ); + break; + } + + m_Activity = NewActivity; // Go ahead and set this so it doesn't keep trying when the anim is not present + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + { + if ( pev->sequence != iSequence || !m_fSequenceLoops ) + { + pev->frame = 0; + } + + pev->sequence = iSequence; // Set to the reset anim (if it's there) + ResetSequenceInfo( ); + RecalculateYawSpeed(); + } + else + { + // Not available try to get default anim + ALERT ( at_debug, "%s has no sequence for act:%s\n", STRING(pev->classname), GetNameForActivity( NewActivity )); + pev->sequence = 0; // Set to the reset anim (if it's there) + } +} + +// Wargon: Âîçìîæíîñòü ïîäáèðàòü ïàòðîíû þçîì èç ìåðòâûõ âðàæèí. (1.1) +void CHGruntGlock :: DeadUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (!pActivator->IsPlayer()) + return; + if (pActivator->GiveAmmo( m_iDeadAmmo, "barret" ) != -1) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + m_iDeadAmmo = 0; + SetUse(NULL); + } +} + +// Wargon: Âîçìîæíîñòü ïîäáèðàòü ïàòðîíû þçîì èç ìåðòâûõ âðàæèí. (1.1) +void CHGruntGlock :: Killed( entvars_t *pevAttacker, int iGib ) +{ + if (gSkillData.maxDeadEnemyAmmo >= 1 && !ShouldGibMonster(iGib)) + { + m_iDeadAmmo = RANDOM_LONG(1, gSkillData.maxDeadEnemyAmmo); + SetUse(&CHGruntGlock::DeadUse); + } + CSquadMonster::Killed(pevAttacker, iGib); +} + + +// ====================== TERRORISTS ============================= + +#define TERR_GUN_GROUP 2 +#define TERR_GUN_AK 0 +#define TERR_GUN_RPK 1 +#define TERR_GUN_NONE 2 + +#define TERR_HEAD_GASMASK 3 + + +class CTerror : public CHGrunt +{ +public: + void Spawn( void ); + void Precache( void ); + int Classify ( void ) {return m_iClass?m_iClass:CLASS_TERROR;} +// int ISoundMask ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); +// BOOL FCanCheckAttacks ( void ); +// BOOL CheckMeleeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); +// BOOL CheckRangeAttack2 ( float flDot, float flDist ); +// void CheckAmmo ( void ); + void SetActivity ( Activity NewActivity ); +// void StartTask ( Task_t *pTask ); +// void RunTask ( Task_t *pTask ); + void DeathSound( void ); + void PainSound( void ); + void IdleSound ( void ); +// Vector GetGunPosition( void ); + void Shoot ( void ); +// void Shotgun ( void ); +// void PrescheduleThink ( void ); + void GibMonster( void ); + void SpeakSentence( void ); + +// void ResetSequenceInfo ( ); + + int Save( CSave &save ); + int Restore( CRestore &restore ); + +// CBaseEntity *Kick( void ); + Schedule_t *GetSchedule( void ); + Schedule_t *GetScheduleOfType ( int Type ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + +// int IRelationship ( CBaseEntity *pTarget ); + +// BOOL FOkToSpeak( void ); +// void JustSpoke( void ); + +// CUSTOM_SCHEDULES; + static TYPEDESCRIPTION m_SaveData[]; + + static const char *pTerrorSentences[]; + + int m_iNoGasDamage; + + // Wargon: Âîçìîæíîñòü ïîäáèðàòü ïàòðîíû þçîì èç ìåðòâûõ âðàæèí. (1.1) + void EXPORT DeadUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Killed( entvars_t *pevAttacker, int iGib ); +}; + +LINK_ENTITY_TO_CLASS( monster_human_terror, CTerror ); + +TYPEDESCRIPTION CTerror::m_SaveData[] = +{ + DEFINE_FIELD( CTerror, m_iNoGasDamage, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CTerror, CHGrunt ); + +const char *CTerror::pTerrorSentences[] = +{ + "TE_GREN", // grenade scared grunt + "TE_ALERT", // sees player + "TE_MONSTER", // sees monster + "TE_COVER", // running to cover + "TE_THROW", // about to throw grenade + "TE_CHARGE", // running out to get the enemy + "TE_TAUNT", // say rude things +}; + + +//========================================================= +// Spawn +//========================================================= +void CTerror :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/terror.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + if (pev->health == 0) + pev->health = gSkillData.terrorHealth; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_flNextGrenadeCheck = gpGlobals->time + 1; + m_flNextPainTime = gpGlobals->time; + m_iSentence = HGRUNT_SENT_NONE; + + m_afCapability = bits_CAP_SQUAD | bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP; + + m_fEnemyEluded = FALSE; + m_fFirstEncounter = TRUE;// this is true when the grunt spawns, because he hasn't encountered an enemy yet. + + m_HackedGunPos = Vector ( 0, 0, 55 ); + + int head = pev->body; // body is head number + pev->body = 0; + SetBodygroup( HEAD_GROUP, head ); + + if (head == TERR_HEAD_GASMASK) m_iNoGasDamage = 1; + else m_iNoGasDamage = 0; + + if (pev->weapons == 0) + { + pev->weapons = HGRUNT_9MMAR | HGRUNT_HANDGRENADE; // ak + grens + } + + if (FBitSet( pev->weapons, HGRUNT_SHOTGUN )) // rpk + { + SetBodygroup( TERR_GUN_GROUP, TERR_GUN_RPK ); + m_cClipSize = 100; // hehe + } + else + { + m_cClipSize = GRUNT_CLIP_SIZE; + } + m_cAmmoLoaded = m_cClipSize; + + CTalkMonster::g_talkWaitTime = 0; + + // Wargon: Âîçìîæíîñòü ïîäáèðàòü ïàòðîíû þçîì èç ìåðòâûõ âðàæèí. (1.1) + m_iDeadAmmo = 0; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CTerror :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/terror.mdl"); + + PRECACHE_SOUND( "weapons/dryfire1.wav" ); //LRC + + if (FBitSet( pev->weapons, HGRUNT_SHOTGUN )) // rpk + { + PRECACHE_SOUND( "weapons/rpk_fire1.wav" ); + PRECACHE_SOUND( "weapons/rpk_fire2.wav" ); + PRECACHE_SOUND( "weapons/rpk_fire3.wav" ); + PRECACHE_SOUND( "terror/ter_reload_rpk.wav" ); + m_iBrassShell = PRECACHE_MODEL ("models/rpk_shell.mdl"); + } + else + { + PRECACHE_SOUND( "weapons/ak74_fire1.wav" ); + PRECACHE_SOUND( "weapons/ak74_fire2.wav" ); + PRECACHE_SOUND( "terror/ter_reload_ak.wav" ); + m_iBrassShell = PRECACHE_MODEL ("models/ak74_shell.mdl"); + } + + PRECACHE_SOUND( "terror/ter_die1.wav" ); + PRECACHE_SOUND( "terror/ter_die2.wav" ); + PRECACHE_SOUND( "terror/ter_die3.wav" ); + + PRECACHE_SOUND( "terror/ter_pain1.wav" ); + PRECACHE_SOUND( "terror/ter_pain2.wav" ); + PRECACHE_SOUND( "terror/ter_pain3.wav" ); + PRECACHE_SOUND( "terror/ter_pain4.wav" ); + PRECACHE_SOUND( "terror/ter_pain5.wav" ); + + PRECACHE_SOUND( "weapons/glauncher.wav" ); + + PRECACHE_SOUND("zombie/claw_miss2.wav");// because we use the basemonster SWIPE animation event + + // get voice pitch + if (RANDOM_LONG(0,1)) + m_voicePitch = 109 + RANDOM_LONG(0,7); + else + m_voicePitch = 100; +} + +// buz - overriden because terrorist with RPK cannot fire crouched +BOOL CTerror :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( !HasConditions( bits_COND_ENEMY_OCCLUDED ) && flDist <= 2048 && flDot >= 0.5 && NoFriendlyFire() ) + { + TraceResult tr; + + if ( !m_hEnemy->IsPlayer() && flDist <= 64 ) + { + // kick nonclients who are close enough, but don't shoot at them. + // ALERT(at_aiconsole, "== too close\n"); + return FALSE; + } + + BOOL savedStanding = m_fStanding; + m_fStanding = FALSE; // buz: check chrouched fire first + Vector vecSrc = GetGunPosition(); + + // verify that a bullet fired from the gun will hit the enemy before the world. + UTIL_TraceLine( vecSrc, m_hEnemy->BodyTarget(vecSrc), ignore_monsters, ignore_glass, ENT(pev), &tr); + if ( tr.flFraction == 1.0 && !pev->gaitsequence && !(FBitSet( pev->weapons, HGRUNT_SHOTGUN ))) + { + // buz: we can fire crouched, now check for standing + m_fStanding = TRUE; + vecSrc = GetGunPosition(); + UTIL_TraceLine( vecSrc, m_hEnemy->BodyTarget(vecSrc), ignore_monsters, ignore_glass, ENT(pev), &tr); + m_fStanding = savedStanding; + if ( tr.flFraction == 1.0 ) + { + // ALERT(at_aiconsole, "== shoot any\n"); + m_iLastFireCheckResult = 0; // shoot as you wish + } + else + { + // ALERT(at_aiconsole, "== shoot crouched\n"); + m_iLastFireCheckResult = 1; // only chrouched + } + + return TRUE; + } + else + { + // buz: cant fire crouching, maybe me or enemy in some kind of cover (or running). Check standing. + m_fStanding = TRUE; + vecSrc = GetGunPosition(); + UTIL_TraceLine( vecSrc, m_hEnemy->BodyTarget(vecSrc), ignore_monsters, ignore_glass, ENT(pev), &tr); + + // BuzTestDrawLine( vecSrc, m_hEnemy->BodyTarget(vecSrc), 255, 255, 0 ); + + m_fStanding = savedStanding; + if ( tr.flFraction == 1.0 ) + { + // ALERT(at_aiconsole, "== shoot standing\n"); + m_iLastFireCheckResult = 2; // buz: standing is our only one choice + return TRUE; + } + else + { + // ALERT(at_aiconsole, "== cant fire\n"); + m_iLastFireCheckResult = 0; + return FALSE; // cant fire + } + } + } + return FALSE; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CTerror :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + Vector vecShootDir; + Vector vecShootOrigin; + + switch( pEvent->event ) + { + case HGRUNT_AE_DROP_GUN: + { + if (pev->spawnflags & SF_MONSTER_NO_WPN_DROP) break; //LRC + + Vector vecGunPos; + Vector vecGunAngles; + + GetAttachment( 0, vecGunPos, vecGunAngles ); + + // switch to body group with no gun. + SetBodygroup( TERR_GUN_GROUP, TERR_GUN_NONE ); + + // now spawn a gun. + if (FBitSet( pev->weapons, HGRUNT_SHOTGUN )) + DropItem( "weapon_pkm", vecGunPos, vecGunAngles ); + else + DropItem( "weapon_ak74", vecGunPos, vecGunAngles ); + + if (FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER )) + DropItem( "ammo_vog25", BodyTarget( pev->origin ), vecGunAngles ); + + } + break; + + case HGRUNT_AE_RELOAD: + if (FBitSet( pev->weapons, HGRUNT_SHOTGUN )) + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "terror/ter_reload_rpk.wav", 1, ATTN_NORM ); + else + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "terror/ter_reload_ak.wav", 1, ATTN_NORM ); + + m_cAmmoLoaded = m_cClipSize; + ClearConditions(bits_COND_NO_AMMO_LOADED); + break; + + case HGRUNT_AE_BURST1: + // Shoot() will play sound + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 384, 0.3 ); + + case HGRUNT_AE_BURST2: + case HGRUNT_AE_BURST3: + Shoot(); + break; + + case HGRUNT_AE_CAUGHT_ENEMY: + if ( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz(ENT(pev), "TE_ALERT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + + default: + CHGrunt::HandleAnimEvent( pEvent ); + break; + } +} + + +//========================================================= +// SetActivity +//========================================================= +void CTerror :: SetActivity ( Activity NewActivity ) +{ + int iSequence = ACTIVITY_NOT_AVAILABLE; + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + switch ( NewActivity) + { + case ACT_RANGE_ATTACK1: + // grunt is either shooting standing or shooting crouched + if (FBitSet( pev->weapons, HGRUNT_9MMAR)) + { + if ( m_fStanding ) + { + // get aimable sequence + iSequence = LookupSequence( "standing_mp5" ); + } + else + { + // get crouching shoot + iSequence = LookupSequence( "crouching_mp5" ); + } + } + else + { + // buz: no crouched shoot animation for RPK + iSequence = LookupSequence( "machinegun_fire" ); + } + break; + case ACT_RANGE_ATTACK2: + // grunt is going to a secondary long range attack. This may be a thrown + // grenade or fired grenade, we must determine which and pick proper sequence + if ( pev->weapons & HGRUNT_HANDGRENADE ) + { + // get toss anim + iSequence = LookupSequence( "throwgrenade" ); + } + // LRC: added a test to stop a marine without a launcher from firing. + else if ( pev->weapons & HGRUNT_GRENADELAUNCHER ) + { + // get launch anim + iSequence = LookupSequence( "launchgrenade" ); + } + else + { + // ALERT( at_debug, "No grenades available. "); // flow into the error message we get at the end... + } + break; + case ACT_RUN: + if ( pev->health <= HGRUNT_LIMP_HEALTH ) + { + // limp! + iSequence = LookupActivity ( ACT_RUN_HURT ); + } + else + { + // buz: get combat movement animation in combat state + // if (m_MonsterState == MONSTERSTATE_COMBAT || m_MonsterState == MONSTERSTATE_ALERT) + if (m_iUseAlertAnims) + { + iSequence = LookupSequence("combat_run_primary"); + if (iSequence != -1) + break; + } + iSequence = LookupActivity ( NewActivity ); + } + break; + case ACT_WALK: + if ( pev->health <= HGRUNT_LIMP_HEALTH ) + { + // limp! + iSequence = LookupActivity ( ACT_WALK_HURT ); + } + else + { + // buz: get combat movement animation in combat state + // if (m_MonsterState == MONSTERSTATE_COMBAT || m_MonsterState == MONSTERSTATE_ALERT) + if (m_iUseAlertAnims) + { + iSequence = LookupSequence("combat_walk_primary"); + if (iSequence != -1) + break; + } + iSequence = LookupActivity ( NewActivity ); + } + break; + case ACT_IDLE: + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + { + NewActivity = ACT_IDLE_ANGRY; + } + iSequence = LookupActivity ( NewActivity ); + break; + default: + iSequence = LookupActivity ( NewActivity ); + break; + } + + m_Activity = NewActivity; // Go ahead and set this so it doesn't keep trying when the anim is not present + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + { + if ( pev->sequence != iSequence || !m_fSequenceLoops ) + { + pev->frame = 0; + } + + pev->sequence = iSequence; // Set to the reset anim (if it's there) + ResetSequenceInfo( ); + RecalculateYawSpeed(); + } + else + { + // Not available try to get default anim + ALERT ( at_debug, "%s has no sequence for act:%s\n", STRING(pev->classname), GetNameForActivity( NewActivity )); + pev->sequence = 0; // Set to the reset anim (if it's there) + } +} + + +//========================================================= +// PainSound +//========================================================= +void CTerror :: PainSound ( void ) +{ + if ( gpGlobals->time > m_flNextPainTime ) + { + switch ( RANDOM_LONG(0,6) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "terror/ter_pain3.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "terror/ter_pain4.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "terror/ter_pain5.wav", 1, ATTN_NORM ); + break; + case 3: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "terror/ter_pain1.wav", 1, ATTN_NORM ); + break; + case 4: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "terror/ter_pain2.wav", 1, ATTN_NORM ); + break; + } + + m_flNextPainTime = gpGlobals->time + 1; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CTerror :: DeathSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "terror/ter_die1.wav", 1, ATTN_IDLE ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "terror/ter_die2.wav", 1, ATTN_IDLE ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "terror/ter_die3.wav", 1, ATTN_IDLE ); + break; + } +} + +//========================================================= +// Shoot +//========================================================= +void CTerror :: Shoot ( void ) +{ +// if (m_hEnemy == NULL && m_pCine == NULL) //LRC - scripts may fire when you have no enemy +// { +// return; +// } + + UTIL_MakeVectors ( pev->angles ); + Vector vecShootOrigin = GetGunPosition(); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); + + if (m_cAmmoLoaded > 0) + { + Vector vecBrassPos, vecBrassDir; + GetAttachment(3, vecBrassPos, vecBrassDir); + Vector vecShellVelocity = gpGlobals->v_right * RANDOM_FLOAT(40,90) + gpGlobals->v_up * RANDOM_FLOAT(75,200) + gpGlobals->v_forward * RANDOM_FLOAT(-40, 40); + EjectBrass ( vecBrassPos, vecShellVelocity, pev->angles.y, m_iBrassShell, TE_BOUNCE_SHELL); + + if (FBitSet( pev->weapons, HGRUNT_9MMAR)) + { + switch(RANDOM_LONG(0,1)) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/ak74_fire1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/ak74_fire2.wav", 1, ATTN_NORM ); break; + } + FireBullets( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_7DEGREES, 2048.0f, BULLET_NORMAL, gSkillData.monTerDmgAK ); + } + else + { + switch(RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/rpk_fire1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/rpk_fire2.wav", 1, ATTN_NORM ); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/rpk_fire3.wav", 1, ATTN_NORM ); break; + } + FireBullets(1, vecShootOrigin, vecShootDir, VECTOR_CONE_10DEGREES, 2048.0f, BULLET_NORMAL, gSkillData.monTerDmgRPK ); + } + + pev->effects |= EF_MUZZLEFLASH; + + m_cAmmoLoaded--;// take away a bullet! + // ALERT(at_console, "grunt ammo has %d\n", m_cAmmoLoaded); + } + else + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/dryfire1.wav", 1, ATTN_NORM ); + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); +} + + +void CTerror :: SpeakSentence( void ) +{ + if ( m_iSentence == HGRUNT_SENT_NONE ) + return; + + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), pTerrorSentences[ m_iSentence ], HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } +} + + + +//========================================================= +// Get Schedule! +//========================================================= +Schedule_t *CTerror :: GetSchedule( void ) +{ + // clear old sentence + m_iSentence = HGRUNT_SENT_NONE; + + // flying? If PRONE, barnacle has me. IF not, it's assumed I am rapelling. + if ( pev->movetype == MOVETYPE_FLY && m_MonsterState != MONSTERSTATE_PRONE ) + return CHGrunt::GetSchedule(); + + // grunts place HIGH priority on running away from danger sounds. + if ( HasConditions(bits_COND_HEAR_SOUND) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound) + { + if (pSound->m_iType & bits_SOUND_DANGER) + { + // dangerous sound nearby! + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), "TE_GREN", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + // ALERT(at_aiconsole, "COVER FROM SOUND\n"); + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + /* + if (!HasConditions( bits_COND_SEE_ENEMY ) && ( pSound->m_iType & (bits_SOUND_PLAYER | bits_SOUND_COMBAT) )) + { + SetIdealYawToTargetAndUpdate( pSound->m_vecOrigin ); + } + */ + } + } + switch ( m_MonsterState ) + { + case MONSTERSTATE_IDLE: // buz: ïåðåçàðÿäèòüñÿ, åñëè âðàãà íåò è ìàãàçèí ïîëóïóñò + case MONSTERSTATE_ALERT: + + if (m_cAmmoLoaded < m_cClipSize / 2) + { + return GetScheduleOfType ( SCHED_RELOAD ); + } + break; + + case MONSTERSTATE_COMBAT: + { +// dead enemy + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + +// new enemy + if ( HasConditions(bits_COND_NEW_ENEMY) ) + { + if ( InSquad() ) + { + MySquadLeader()->m_fEnemyEluded = FALSE; + + if ( !IsLeader() ) + { + // ALERT(at_aiconsole, "COVER FROM ENEMY (SQUAD)\n"); + return GetScheduleOfType ( SCHED_TAKE_COVER_FROM_ENEMY ); + } + else + { + // ALERT(at_aiconsole,"leader spotted player!\n"); + + if (FOkToSpeak())// && RANDOM_LONG(0,1)) + { + if (m_hEnemy != NULL) + { + if (m_hEnemy->IsPlayer()) + SENTENCEG_PlayRndSz( ENT(pev), "TE_ALERT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + else if ((m_hEnemy->Classify() == CLASS_ALIEN_MILITARY) || + (m_hEnemy->Classify() == CLASS_ALIEN_MONSTER) || + (m_hEnemy->Classify() == CLASS_ALIEN_PREY) || + (m_hEnemy->Classify() == CLASS_ALIEN_PREDATOR)) + SENTENCEG_PlayRndSz( ENT(pev), "TE_MONST", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + } + + JustSpoke(); + } + + if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_GRUNT_SUPPRESS ); + } + else + { + return GetScheduleOfType ( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } + } + } + } +// no ammo + else if ( HasConditions ( bits_COND_NO_AMMO_LOADED ) ) + { + if ((m_afCapability & bits_CAP_CROUCH_COVER) && !HasConditions(bits_COND_CROUCH_NOT_SAFE) ) // buz: reload here, if safe + return GetScheduleOfType ( SCHED_RELOAD ); + else + return GetScheduleOfType ( SCHED_GRUNT_COVER_AND_RELOAD ); + } + +// damaged just a little + else if ( HasConditions( bits_COND_LIGHT_DAMAGE ) ) + { + int iPercent; + + // buz: 90% to duck and cover, if can + if ((m_afCapability & bits_CAP_CROUCH_COVER) && !HasConditions(bits_COND_CROUCH_NOT_SAFE) && m_hEnemy != NULL ) + { +// ALERT(at_console, "TERROR HIDES\n"); + iPercent = RANDOM_LONG(0,99); + if (iPercent <= 90) + return GetScheduleOfType( SCHED_GRUNT_DUCK_COVER_WAIT ); // wait some time in cover + } + + // buz: now 50% to try normal way of taking cover + iPercent = RANDOM_LONG(0,99); + if ( iPercent <= 50 && m_hEnemy != NULL ) + { + //!!!KELLY - this grunt was hit and is going to run to cover. + if (FOkToSpeak()) // && RANDOM_LONG(0,1)) + { + //SENTENCEG_PlayRndSz( ENT(pev), "HG_COVER", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + m_iSentence = HGRUNT_SENT_COVER; + //JustSpoke(); + } +// ALERT(at_console, "TERROR TAKES COVER\n"); + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + else + { +// ALERT(at_console, "TERROR FLINCHES\n"); + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + } +// can kick + else if ( HasConditions ( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_MELEE_ATTACK1 ); + } +// can grenade launch + + else if ( FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER) && HasConditions ( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + // shoot a grenade if you can + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } +// can shoot + else if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + if ( InSquad() ) + { + // if the enemy has eluded the squad and a squad member has just located the enemy + // and the enemy does not see the squad member, issue a call to the squad to waste a + // little time and give the player a chance to turn. + if ( MySquadLeader()->m_fEnemyEluded && !HasConditions ( bits_COND_ENEMY_FACING_ME ) ) + { + MySquadLeader()->m_fEnemyEluded = FALSE; + return GetScheduleOfType ( SCHED_GRUNT_FOUND_ENEMY ); + } + } + + if ( OccupySlot ( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + // try to take an available ENGAGE slot + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + else if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + // throw a grenade if can and no engage slots are available + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + else + { + // hide! + // ALERT(at_aiconsole, "COVER FROM ENEMY (HIDE)\n"); + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + } +// can't see enemy + else if ( HasConditions( bits_COND_ENEMY_OCCLUDED ) ) + { + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + //!!!KELLY - this grunt is about to throw or fire a grenade at the player. Great place for "fire in the hole" "frag out" etc + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), "TE_THROW", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + else if ( OccupySlot( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + //!!!KELLY - grunt cannot see the enemy and has just decided to + // charge the enemy's position. + if (FOkToSpeak())// && RANDOM_LONG(0,1)) + { + //SENTENCEG_PlayRndSz( ENT(pev), "HG_CHARGE", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + m_iSentence = HGRUNT_SENT_CHARGE; + //JustSpoke(); + } + + return GetScheduleOfType( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } + else + { + //!!!KELLY - grunt is going to stay put for a couple seconds to see if + // the enemy wanders back out into the open, or approaches the + // grunt's covered position. Good place for a taunt, I guess? + if (FOkToSpeak() && RANDOM_LONG(0,1)) + { + SENTENCEG_PlayRndSz( ENT(pev), "TE_TAUNT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + return GetScheduleOfType( SCHED_STANDOFF ); + } + } + + if ( HasConditions( bits_COND_SEE_ENEMY ) && !HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } + } + } + + // no special cases here, call the base class + return CSquadMonster :: GetSchedule(); +} + +//========================================================= +//========================================================= +Schedule_t* CTerror :: GetScheduleOfType ( int Type ) +{ + if (Type == SCHED_TAKE_COVER_FROM_ENEMY) + { + if ( InSquad() ) + { + if ( g_iSkillLevel == SKILL_HARD && HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), "TE_THROW", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + return slGruntTossGrenadeCover; + } + else + { + return &slGruntTakeCover[ 0 ]; + } + } + else + { + if ( OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) && RANDOM_LONG(0,1) ) + { + return &slGruntGrenadeCover[ 0 ]; + } + else + { + return &slGruntTakeCover[ 0 ]; + } + } + } + else + return CHGrunt::GetScheduleOfType(Type); +} + +void CTerror :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + // terrorists doesnt have helmets + CSquadMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + +int CTerror :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // buz: refuse gas damage while wearing gasmask + if (m_iNoGasDamage && ( bitsDamageType & DMG_NERVEGAS )) + return 0; + + Forget( bits_MEMORY_INCOVER ); + return CSquadMonster :: TakeDamage ( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +//========================================================= +// GibMonster - make gun fly through the air. +//========================================================= +void CTerror :: GibMonster ( void ) +{ + Vector vecGunPos; + Vector vecGunAngles; + + if (GetBodygroup(TERR_GUN_GROUP) != TERR_GUN_NONE && !(pev->spawnflags & SF_MONSTER_NO_WPN_DROP)) + {// throw a gun if the grunt has one + GetAttachment( 0, vecGunPos, vecGunAngles ); + + CBaseEntity *pGun; + if (FBitSet( pev->weapons, HGRUNT_SHOTGUN )) + { + pGun = DropItem( "weapon_pkm", vecGunPos, vecGunAngles ); + } + else + { + pGun = DropItem( "weapon_ak74", vecGunPos, vecGunAngles ); + } + if ( pGun ) + { + pGun->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + pGun->pev->avelocity = Vector ( 0, RANDOM_FLOAT( 200, 400 ), 0 ); + } + + if (FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER )) + { + pGun = DropItem( "ammo_vog25", vecGunPos, vecGunAngles ); + if ( pGun ) + { + pGun->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + pGun->pev->avelocity = Vector ( 0, RANDOM_FLOAT( 200, 400 ), 0 ); + } + } + } + + CBaseMonster :: GibMonster(); +} + + +void CTerror :: IdleSound( void ) +{ + if (FOkToSpeak() && (g_fTerrorQuestion || RANDOM_LONG(0,1))) + { + if (!g_fTerrorQuestion) + { + // ask question or make statement + switch (RANDOM_LONG(0,2)) + { + case 0: // check in + SENTENCEG_PlayRndSz(ENT(pev), "TE_CHECK", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + g_fTerrorQuestion = 1; + break; + case 1: // question + SENTENCEG_PlayRndSz(ENT(pev), "TE_QUEST", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + g_fTerrorQuestion = 2; + break; + case 2: // statement + SENTENCEG_PlayRndSz(ENT(pev), "TE_IDLE", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + break; + } + } + else + { + switch (g_fTerrorQuestion) + { + case 1: // check in + SENTENCEG_PlayRndSz(ENT(pev), "TE_CLEAR", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + break; + case 2: // question + SENTENCEG_PlayRndSz(ENT(pev), "TE_ANSWER", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + break; + } + g_fTerrorQuestion = 0; + } + JustSpoke(); + } +} + +// Wargon: Âîçìîæíîñòü ïîäáèðàòü ïàòðîíû þçîì èç ìåðòâûõ âðàæèí. (1.1) +void CTerror :: DeadUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (!pActivator->IsPlayer()) + return; + if (FBitSet(pev->weapons, HGRUNT_9MMAR) && pActivator->GiveAmmo( m_iDeadAmmo, "ak" ) != -1) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + m_iDeadAmmo = 0; + SetUse(NULL); + } + else if (FBitSet(pev->weapons, HGRUNT_SHOTGUN) && pActivator->GiveAmmo( m_iDeadAmmo, "rpk" ) != -1) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + m_iDeadAmmo = 0; + SetUse(NULL); + } +} + +// Wargon: Âîçìîæíîñòü ïîäáèðàòü ïàòðîíû þçîì èç ìåðòâûõ âðàæèí. (1.1) +void CTerror :: Killed( entvars_t *pevAttacker, int iGib ) +{ + if (gSkillData.maxDeadEnemyAmmo >= 1 && !ShouldGibMonster(iGib)) + { + m_iDeadAmmo = RANDOM_LONG(1, gSkillData.maxDeadEnemyAmmo); + SetUse(&CTerror::DeadUse); + } + CSquadMonster::Killed(pevAttacker, iGib); +} + + +// ======================== CLONES ============================== + +class CClone : public CTerror +{ +public: + void Spawn( void ); + void Precache( void ); + + int Classify ( void ) {return m_iClass?m_iClass:CLASS_ALIEN_MONSTER;} + + void HandleAnimEvent( MonsterEvent_t *pEvent ); + + void DeathSound( void ); + void PainSound( void ); + void IdleSound ( void ); + + void SpeakSentence( void ); + +// int Save( CSave &save ); +// int Restore( CRestore &restore ); + + Schedule_t *GetSchedule( void ); + Schedule_t *GetScheduleOfType ( int Type ); + +// static TYPEDESCRIPTION m_SaveData[]; + + static const char *pCloneSentences[]; +}; + +LINK_ENTITY_TO_CLASS( monster_soldier_clone, CClone ); + +/*TYPEDESCRIPTION CClone::m_SaveData[] = +{ + DEFINE_FIELD( CClone, m_iNoGasDamage, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CClone, CTerror );*/ + +const char *CClone::pCloneSentences[] = +{ + "CL_GREN", // grenade scared grunt + "CL_ALERT", // sees player + "CL_MONSTER", // sees monster + "CL_COVER", // running to cover + "CL_THROW", // about to throw grenade + "CL_CHARGE", // running out to get the enemy + "CL_TAUNT", // say rude things +}; + + +//========================================================= +// Spawn +//========================================================= +void CClone :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/soldier_clon.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + if (pev->health == 0) + pev->health = gSkillData.cloneHealth; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_flNextGrenadeCheck = gpGlobals->time + 1; + m_flNextPainTime = gpGlobals->time; + m_iSentence = HGRUNT_SENT_NONE; + + m_afCapability = bits_CAP_SQUAD | bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP; + + m_fEnemyEluded = FALSE; + m_fFirstEncounter = TRUE;// this is true when the grunt spawns, because he hasn't encountered an enemy yet. + + m_HackedGunPos = Vector ( 0, 0, 55 ); + + int head = pev->body; // body is head number + pev->body = 0; + SetBodygroup( HEAD_GROUP, head ); + + if (pev->weapons == 0) + { + pev->weapons = HGRUNT_9MMAR | HGRUNT_HANDGRENADE; // ak + grens + } + + if (FBitSet( pev->weapons, HGRUNT_SHOTGUN )) // rpk + { + SetBodygroup( TERR_GUN_GROUP, TERR_GUN_RPK ); + m_cClipSize = 100; // hehe + } + else + { + m_cClipSize = GRUNT_CLIP_SIZE; + } + m_cAmmoLoaded = m_cClipSize; + + CTalkMonster::g_talkWaitTime = 0; + m_iNoGasDamage = 0; + + // Wargon: Âîçìîæíîñòü ïîäáèðàòü ïàòðîíû þçîì èç ìåðòâûõ âðàæèí. (1.1) + m_iDeadAmmo = 0; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CClone :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/soldier_clon.mdl"); + + PRECACHE_SOUND( "weapons/dryfire1.wav" ); //LRC + + if (FBitSet( pev->weapons, HGRUNT_SHOTGUN )) // rpk + { + PRECACHE_SOUND( "weapons/rpk_fire1.wav" ); + PRECACHE_SOUND( "weapons/rpk_fire2.wav" ); + PRECACHE_SOUND( "weapons/rpk_fire3.wav" ); + PRECACHE_SOUND( "clone/cl_reload_rpk.wav" ); + m_iBrassShell = PRECACHE_MODEL ("models/rpk_shell.mdl"); + } + else + { + PRECACHE_SOUND( "weapons/ak74_fire1.wav" ); + PRECACHE_SOUND( "weapons/ak74_fire2.wav" ); + PRECACHE_SOUND( "clone/cl_reload_ak.wav" ); + m_iBrassShell = PRECACHE_MODEL ("models/ak74_shell.mdl"); + } + + PRECACHE_SOUND( "clone/cl_die1.wav" ); + PRECACHE_SOUND( "clone/cl_die2.wav" ); + PRECACHE_SOUND( "clone/cl_die3.wav" ); + + PRECACHE_SOUND( "clone/cl_pain1.wav" ); + PRECACHE_SOUND( "clone/cl_pain2.wav" ); + PRECACHE_SOUND( "clone/cl_pain3.wav" ); + PRECACHE_SOUND( "clone/cl_pain4.wav" ); + PRECACHE_SOUND( "clone/cl_pain5.wav" ); + + PRECACHE_SOUND( "weapons/glauncher.wav" ); + + PRECACHE_SOUND("zombie/claw_miss2.wav");// because we use the basemonster SWIPE animation event + + // get voice pitch + if (RANDOM_LONG(0,1)) + m_voicePitch = 109 + RANDOM_LONG(0,7); + else + m_voicePitch = 100; +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CClone :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + Vector vecShootDir; + Vector vecShootOrigin; + + switch( pEvent->event ) + { + case HGRUNT_AE_DROP_GUN: + { + if (pev->spawnflags & SF_MONSTER_NO_WPN_DROP) break; //LRC + + Vector vecGunPos; + Vector vecGunAngles; + + GetAttachment( 0, vecGunPos, vecGunAngles ); + + // switch to body group with no gun. + SetBodygroup( TERR_GUN_GROUP, TERR_GUN_NONE ); + + // now spawn a gun. + if (FBitSet( pev->weapons, HGRUNT_SHOTGUN )) + DropItem( "weapon_pkm", vecGunPos, vecGunAngles ); + else + DropItem( "weapon_ak74", vecGunPos, vecGunAngles ); + + if (FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER )) + DropItem( "ammo_vog25", BodyTarget( pev->origin ), vecGunAngles ); + + } + break; + + case HGRUNT_AE_RELOAD: + if (FBitSet( pev->weapons, HGRUNT_SHOTGUN )) + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "clone/cl_reload_rpk.wav", 1, ATTN_NORM ); + else + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "clone/cl_reload_ak.wav", 1, ATTN_NORM ); + + m_cAmmoLoaded = m_cClipSize; + ClearConditions(bits_COND_NO_AMMO_LOADED); + break; + + case HGRUNT_AE_BURST1: + // Shoot() will play sound + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 384, 0.3 ); + + case HGRUNT_AE_BURST2: + case HGRUNT_AE_BURST3: + Shoot(); + break; + + case HGRUNT_AE_CAUGHT_ENEMY: + if ( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz(ENT(pev), "CL_ALERT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + + default: + CTerror::HandleAnimEvent( pEvent ); + break; + } +} + + +//========================================================= +// PainSound +//========================================================= +void CClone :: PainSound ( void ) +{ + if ( gpGlobals->time > m_flNextPainTime ) + { + switch ( RANDOM_LONG(0,6) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "clone/cl_pain3.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "clone/cl_pain4.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "clone/cl_pain5.wav", 1, ATTN_NORM ); + break; + case 3: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "clone/cl_pain1.wav", 1, ATTN_NORM ); + break; + case 4: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "clone/cl_pain2.wav", 1, ATTN_NORM ); + break; + } + + m_flNextPainTime = gpGlobals->time + 1; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CClone :: DeathSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "clone/cl_die1.wav", 1, ATTN_IDLE ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "clone/cl_die2.wav", 1, ATTN_IDLE ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "clone/cl_die3.wav", 1, ATTN_IDLE ); + break; + } +} + + +//========================================================= +// IdleSound +//========================================================= +void CClone :: IdleSound( void ) +{ + // no idle sounds for clones! + +/* if (FOkToSpeak() && (g_fTerrorQuestion || RANDOM_LONG(0,1))) + { + if (!g_fTerrorQuestion) + { + // ask question or make statement + switch (RANDOM_LONG(0,2)) + { + case 0: // check in + SENTENCEG_PlayRndSz(ENT(pev), "CL_CHECK", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + g_fTerrorQuestion = 1; + break; + case 1: // question + SENTENCEG_PlayRndSz(ENT(pev), "CL_QUEST", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + g_fTerrorQuestion = 2; + break; + case 2: // statement + SENTENCEG_PlayRndSz(ENT(pev), "CL_IDLE", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + break; + } + } + else + { + switch (g_fTerrorQuestion) + { + case 1: // check in + SENTENCEG_PlayRndSz(ENT(pev), "CL_CLEAR", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + break; + case 2: // question + SENTENCEG_PlayRndSz(ENT(pev), "CL_ANSWER", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + break; + } + g_fTerrorQuestion = 0; + } + JustSpoke(); + }*/ +} + +void CClone :: SpeakSentence( void ) +{ + if ( m_iSentence == HGRUNT_SENT_NONE ) + return; + + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), pCloneSentences[ m_iSentence ], HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } +} + + + +//========================================================= +// Get Schedule! +//========================================================= +Schedule_t *CClone :: GetSchedule( void ) +{ + // clear old sentence + m_iSentence = HGRUNT_SENT_NONE; + + // flying? If PRONE, barnacle has me. IF not, it's assumed I am rapelling. + if ( pev->movetype == MOVETYPE_FLY && m_MonsterState != MONSTERSTATE_PRONE ) + return CTerror::GetSchedule(); + + // grunts place HIGH priority on running away from danger sounds. + if ( HasConditions(bits_COND_HEAR_SOUND) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound) + { + if (pSound->m_iType & bits_SOUND_DANGER) + { + // dangerous sound nearby! + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), "CL_GREN", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + // ALERT(at_aiconsole, "COVER FROM SOUND\n"); + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + /* + if (!HasConditions( bits_COND_SEE_ENEMY ) && ( pSound->m_iType & (bits_SOUND_PLAYER | bits_SOUND_COMBAT) )) + { + SetIdealYawToTargetAndUpdate( pSound->m_vecOrigin ); + } + */ + } + } + switch ( m_MonsterState ) + { + case MONSTERSTATE_IDLE: // buz: ïåðåçàðÿäèòüñÿ, åñëè âðàãà íåò è ìàãàçèí ïîëóïóñò + case MONSTERSTATE_ALERT: + + if (m_cAmmoLoaded < m_cClipSize / 2) + { + return GetScheduleOfType ( SCHED_RELOAD ); + } + break; + + case MONSTERSTATE_COMBAT: + { +// dead enemy + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + +// new enemy + if ( HasConditions(bits_COND_NEW_ENEMY) ) + { + if ( InSquad() ) + { + MySquadLeader()->m_fEnemyEluded = FALSE; + + if ( !IsLeader() ) + { + // ALERT(at_aiconsole, "COVER FROM ENEMY (SQUAD)\n"); + return GetScheduleOfType ( SCHED_TAKE_COVER_FROM_ENEMY ); + } + else + { + // ALERT(at_aiconsole,"leader spotted player!\n"); + + if (FOkToSpeak())// && RANDOM_LONG(0,1)) + { + if (m_hEnemy != NULL) + { + // if (m_hEnemy->IsPlayer()) + SENTENCEG_PlayRndSz( ENT(pev), "CL_ALERT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + } + + JustSpoke(); + } + + if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_GRUNT_SUPPRESS ); + } + else + { + return GetScheduleOfType ( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } + } + } + } +// no ammo + else if ( HasConditions ( bits_COND_NO_AMMO_LOADED ) ) + { + if ((m_afCapability & bits_CAP_CROUCH_COVER) && !HasConditions(bits_COND_CROUCH_NOT_SAFE) ) // buz: reload here, if safe + return GetScheduleOfType ( SCHED_RELOAD ); + else + return GetScheduleOfType ( SCHED_GRUNT_COVER_AND_RELOAD ); + } + +// damaged just a little + else if ( HasConditions( bits_COND_LIGHT_DAMAGE ) ) + { + int iPercent; + + // buz: 90% to duck and cover, if can + if ((m_afCapability & bits_CAP_CROUCH_COVER) && !HasConditions(bits_COND_CROUCH_NOT_SAFE) && m_hEnemy != NULL ) + { +// ALERT(at_console, "TERROR HIDES\n"); + iPercent = RANDOM_LONG(0,99); + if (iPercent <= 90) + return GetScheduleOfType( SCHED_GRUNT_DUCK_COVER_WAIT ); // wait some time in cover + } + + // buz: now 50% to try normal way of taking cover + iPercent = RANDOM_LONG(0,99); + if ( iPercent <= 50 && m_hEnemy != NULL ) + { + //!!!KELLY - this grunt was hit and is going to run to cover. + if (FOkToSpeak()) // && RANDOM_LONG(0,1)) + { + //SENTENCEG_PlayRndSz( ENT(pev), "HG_COVER", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + m_iSentence = HGRUNT_SENT_COVER; + //JustSpoke(); + } +// ALERT(at_console, "TERROR TAKES COVER\n"); + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + else + { +// ALERT(at_console, "TERROR FLINCHES\n"); + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + } +// can kick + else if ( HasConditions ( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_MELEE_ATTACK1 ); + } +// can grenade launch + + else if ( FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER) && HasConditions ( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + // shoot a grenade if you can + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } +// can shoot + else if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + if ( InSquad() ) + { + // if the enemy has eluded the squad and a squad member has just located the enemy + // and the enemy does not see the squad member, issue a call to the squad to waste a + // little time and give the player a chance to turn. + if ( MySquadLeader()->m_fEnemyEluded && !HasConditions ( bits_COND_ENEMY_FACING_ME ) ) + { + MySquadLeader()->m_fEnemyEluded = FALSE; + return GetScheduleOfType ( SCHED_GRUNT_FOUND_ENEMY ); + } + } + + if ( OccupySlot ( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + // try to take an available ENGAGE slot + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + else if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + // throw a grenade if can and no engage slots are available + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + else + { + // hide! + // ALERT(at_aiconsole, "COVER FROM ENEMY (HIDE)\n"); + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + } +// can't see enemy + else if ( HasConditions( bits_COND_ENEMY_OCCLUDED ) ) + { + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + //!!!KELLY - this grunt is about to throw or fire a grenade at the player. Great place for "fire in the hole" "frag out" etc + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), "CL_THROW", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + else if ( OccupySlot( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + //!!!KELLY - grunt cannot see the enemy and has just decided to + // charge the enemy's position. + if (FOkToSpeak())// && RANDOM_LONG(0,1)) + { + //SENTENCEG_PlayRndSz( ENT(pev), "HG_CHARGE", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + m_iSentence = HGRUNT_SENT_CHARGE; + //JustSpoke(); + } + + return GetScheduleOfType( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } + else + { + //!!!KELLY - grunt is going to stay put for a couple seconds to see if + // the enemy wanders back out into the open, or approaches the + // grunt's covered position. Good place for a taunt, I guess? + if (FOkToSpeak() && RANDOM_LONG(0,1)) + { + SENTENCEG_PlayRndSz( ENT(pev), "CL_TAUNT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + return GetScheduleOfType( SCHED_STANDOFF ); + } + } + + if ( HasConditions( bits_COND_SEE_ENEMY ) && !HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } + } + } + + // no special cases here, call the base class + return CSquadMonster :: GetSchedule(); +} + +//========================================================= +//========================================================= +Schedule_t* CClone :: GetScheduleOfType ( int Type ) +{ + if (Type == SCHED_TAKE_COVER_FROM_ENEMY) + { + if ( InSquad() ) + { + if ( g_iSkillLevel == SKILL_HARD && HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), "CL_THROW", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + return slGruntTossGrenadeCover; + } + else + { + return &slGruntTakeCover[ 0 ]; + } + } + else + { + if ( OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) && RANDOM_LONG(0,1) ) + { + return &slGruntGrenadeCover[ 0 ]; + } + else + { + return &slGruntTakeCover[ 0 ]; + } + } + } + else + return CHGrunt::GetScheduleOfType(Type); +} + + + + + + +class CCloneHeavy : public CClone +{ +public: + void Spawn( void ); + void Precache( void ); +}; + +LINK_ENTITY_TO_CLASS( monster_soldier_clone_heavy, CCloneHeavy ); + +//========================================================= +// Spawn +//========================================================= +void CCloneHeavy :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/soldier_clon_heavy.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + if (pev->health == 0) + pev->health = gSkillData.cloneHealthHeavy; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_flNextGrenadeCheck = gpGlobals->time + 1; + m_flNextPainTime = gpGlobals->time; + m_iSentence = HGRUNT_SENT_NONE; + + m_afCapability = bits_CAP_SQUAD | bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP; + + m_fEnemyEluded = FALSE; + m_fFirstEncounter = TRUE;// this is true when the grunt spawns, because he hasn't encountered an enemy yet. + + m_HackedGunPos = Vector ( 0, 0, 55 ); + + int head = pev->body; // body is head number + pev->body = 0; + SetBodygroup( HEAD_GROUP, head ); + + if (pev->weapons == 0) + { + pev->weapons = HGRUNT_9MMAR | HGRUNT_HANDGRENADE; // ak + grens + } + + if (FBitSet( pev->weapons, HGRUNT_SHOTGUN )) // rpk + { + SetBodygroup( TERR_GUN_GROUP, TERR_GUN_RPK ); + m_cClipSize = 100; // hehe + } + else + { + m_cClipSize = GRUNT_CLIP_SIZE; + } + m_cAmmoLoaded = m_cClipSize; + + CTalkMonster::g_talkWaitTime = 0; + m_iNoGasDamage = 0; + + // Wargon: Âîçìîæíîñòü ïîäáèðàòü ïàòðîíû þçîì èç ìåðòâûõ âðàæèí. (1.1) + m_iDeadAmmo = 0; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CCloneHeavy :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/soldier_clon_heavy.mdl"); + + PRECACHE_SOUND( "weapons/dryfire1.wav" ); //LRC + + if (FBitSet( pev->weapons, HGRUNT_SHOTGUN )) // rpk + { + PRECACHE_SOUND( "weapons/rpk_fire1.wav" ); + PRECACHE_SOUND( "weapons/rpk_fire2.wav" ); + PRECACHE_SOUND( "weapons/rpk_fire3.wav" ); + PRECACHE_SOUND( "clone/cl_reload_rpk.wav" ); + m_iBrassShell = PRECACHE_MODEL ("models/rpk_shell.mdl"); + } + else + { + PRECACHE_SOUND( "weapons/ak74_fire1.wav" ); + PRECACHE_SOUND( "weapons/ak74_fire2.wav" ); + PRECACHE_SOUND( "clone/cl_reload_ak.wav" ); + m_iBrassShell = PRECACHE_MODEL ("models/ak74_shell.mdl"); + } + + PRECACHE_SOUND( "clone/cl_die1.wav" ); + PRECACHE_SOUND( "clone/cl_die2.wav" ); + PRECACHE_SOUND( "clone/cl_die3.wav" ); + + PRECACHE_SOUND( "clone/cl_pain1.wav" ); + PRECACHE_SOUND( "clone/cl_pain2.wav" ); + PRECACHE_SOUND( "clone/cl_pain3.wav" ); + PRECACHE_SOUND( "clone/cl_pain4.wav" ); + PRECACHE_SOUND( "clone/cl_pain5.wav" ); + + PRECACHE_SOUND( "weapons/glauncher.wav" ); + + PRECACHE_SOUND("zombie/claw_miss2.wav");// because we use the basemonster SWIPE animation event + + // get voice pitch + if (RANDOM_LONG(0,1)) + m_voicePitch = 109 + RANDOM_LONG(0,7); + else + m_voicePitch = 100; +} diff --git a/dlls/hl.def b/dlls/hl.def new file mode 100644 index 0000000..ace2eed --- /dev/null +++ b/dlls/hl.def @@ -0,0 +1,5 @@ +LIBRARY spirit +EXPORTS + GiveFnptrsToDll @1 +SECTIONS + .data READ WRITE diff --git a/dlls/hl.dsp b/dlls/hl.dsp new file mode 100644 index 0000000..d364d2b --- /dev/null +++ b/dlls/hl.dsp @@ -0,0 +1,766 @@ +# Microsoft Developer Studio Project File - Name="hl" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=hl - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "hl.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "hl.mak" CFG="hl - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "hl - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "hl - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/GoldSrc/dlls", ELEBAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "hl - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\temp\spirit\!release" +# PROP Intermediate_Dir "..\temp\spirit\!release" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /MD /W3 /GX /O2 /I "..\dlls" /I "..\engine" /I "..\common" /I "..\pm_shared" /I "..\game_shared" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "QUIVER" /D "VOXEL" /D "QUAKE2" /D "VALVE_DLL" /FD /c +# SUBTRACT CPP /Fr /YX +# ADD BASE MTL /nologo /D "NDEBUG" /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /dll /machine:I386 +# ADD LINK32 msvcrt.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /pdb:none /machine:I386 /nodefaultlib:"libc.lib" /def:".\hl.def" /out:"..\temp\spirit\!release\spirit.dll" +# SUBTRACT LINK32 /map +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\spirit\!release +InputPath=\Paranoia2\src_main\temp\spirit\!release\spirit.dll +SOURCE="$(InputPath)" + +"D:\Paranoia2\base\bin\spirit.dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\spirit.dll "D:\Paranoia2\base\bin\spirit.dll" + +# End Custom Build + +!ELSEIF "$(CFG)" == "hl - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "hl___Win32_Debug" +# PROP BASE Intermediate_Dir "hl___Win32_Debug" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\temp\spirit\!debug" +# PROP Intermediate_Dir "..\temp\spirit\!debug" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /G5 /MT /W3 /O2 /I "..\dlls" /I "..\engine" /I "..\common" /I "..\pm_shared" /I "..\game_shared" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "QUIVER" /D "VOXEL" /D "QUAKE2" /D "VALVE_DLL" /Fr /YX /FD /c +# ADD CPP /nologo /MDd /W3 /Gi /GX /ZI /Od /I "..\dlls" /I "..\engine" /I "..\common" /I "..\pm_shared" /I "..\game_shared" /D "DEBUG" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "QUIVER" /D "VOXEL" /D "QUAKE2" /D "VALVE_DLL" /FAs /FR /FD /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /D "DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" /d "DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /map /machine:I386 /def:".\hl.def" /out:".\Release/spirit.dll" +# ADD LINK32 msvcrtd.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /incremental:yes /debug /machine:I386 /nodefaultlib:"libcd.lib" /def:".\hl.def" /out:"..\temp\spirit\!debug\spirit.dll" +# SUBTRACT LINK32 /map +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\spirit\!debug +InputPath=\Paranoia2\src_main\temp\spirit\!debug\spirit.dll +SOURCE="$(InputPath)" + +"D:\Paranoia2\base\bin\spirit.dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\spirit.dll "D:\Paranoia2\base\bin\spirit.dll" + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "hl - Win32 Release" +# Name "hl - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=.\aflock.cpp +# End Source File +# Begin Source File + +SOURCE=.\agrunt.cpp +# End Source File +# Begin Source File + +SOURCE=.\airtank.cpp +# End Source File +# Begin Source File + +SOURCE=.\alias.cpp +# End Source File +# Begin Source File + +SOURCE=.\ammodesc.cpp +# End Source File +# Begin Source File + +SOURCE=.\animating.cpp +# End Source File +# Begin Source File + +SOURCE=.\animation.cpp +# End Source File +# Begin Source File + +SOURCE=.\apache.cpp +# End Source File +# Begin Source File + +SOURCE=.\barnacle.cpp +# End Source File +# Begin Source File + +SOURCE=.\barney.cpp +# End Source File +# Begin Source File + +SOURCE=.\bigmomma.cpp +# End Source File +# Begin Source File + +SOURCE=.\bloater.cpp +# End Source File +# Begin Source File + +SOURCE=.\bmodels.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\bone_setup.cpp +# End Source File +# Begin Source File + +SOURCE=.\bullsquid.cpp +# End Source File +# Begin Source File + +SOURCE=.\buttons.cpp +# End Source File +# Begin Source File + +SOURCE=.\cbase.cpp +# End Source File +# Begin Source File + +SOURCE=.\client.cpp +# End Source File +# Begin Source File + +SOURCE=.\combat.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\common.cpp +# End Source File +# Begin Source File + +SOURCE=.\controller.cpp +# End Source File +# Begin Source File + +SOURCE=.\defaultai.cpp +# End Source File +# Begin Source File + +SOURCE=.\doors.cpp +# End Source File +# Begin Source File + +SOURCE=.\effects.cpp +# End Source File +# Begin Source File + +SOURCE=.\explode.cpp +# End Source File +# Begin Source File + +SOURCE=.\flyingmonster.cpp +# End Source File +# Begin Source File + +SOURCE=.\func_break.cpp +# End Source File +# Begin Source File + +SOURCE=.\func_tank.cpp +# End Source File +# Begin Source File + +SOURCE=.\game.cpp +# End Source File +# Begin Source File + +SOURCE=.\gamerules.cpp +# End Source File +# Begin Source File + +SOURCE=.\gargantua.cpp +# End Source File +# Begin Source File + +SOURCE=.\genericmonster.cpp +# End Source File +# Begin Source File + +SOURCE=.\ggrenade.cpp +# End Source File +# Begin Source File + +SOURCE=.\globals.cpp +# End Source File +# Begin Source File + +SOURCE=.\gman.cpp +# End Source File +# Begin Source File + +SOURCE=.\h_ai.cpp +# End Source File +# Begin Source File + +SOURCE=.\h_battery.cpp +# End Source File +# Begin Source File + +SOURCE=.\h_cine.cpp +# End Source File +# Begin Source File + +SOURCE=.\h_cycler.cpp +# End Source File +# Begin Source File + +SOURCE=.\h_export.cpp +# End Source File +# Begin Source File + +SOURCE=.\handgrenade.cpp +# End Source File +# Begin Source File + +SOURCE=.\hassassin.cpp +# End Source File +# Begin Source File + +SOURCE=.\headcrab.cpp +# End Source File +# Begin Source File + +SOURCE=.\healthkit.cpp +# End Source File +# Begin Source File + +SOURCE=.\hgrunt.cpp +# End Source File +# Begin Source File + +SOURCE=.\hornet.cpp +# End Source File +# Begin Source File + +SOURCE=.\houndeye.cpp +# End Source File +# Begin Source File + +SOURCE=.\ichthyosaur.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\ikcontext.cpp +# End Source File +# Begin Source File + +SOURCE=.\islave.cpp +# End Source File +# Begin Source File + +SOURCE=.\items.cpp +# End Source File +# Begin Source File + +SOURCE=.\leech.cpp +# End Source File +# Begin Source File + +SOURCE=.\lights.cpp +# End Source File +# Begin Source File + +SOURCE=.\locus.cpp +# End Source File +# Begin Source File + +SOURCE=.\maprules.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\material.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\mathlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\matrix.cpp +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\meshdesc.cpp +# End Source File +# Begin Source File + +SOURCE=.\monster_superofficer.cpp +# End Source File +# Begin Source File + +SOURCE=.\monstermaker.cpp +# End Source File +# Begin Source File + +SOURCE=.\monsters.cpp +# End Source File +# Begin Source File + +SOURCE=.\monsterstate.cpp +# End Source File +# Begin Source File + +SOURCE=.\mortar.cpp +# End Source File +# Begin Source File + +SOURCE=.\movewith.cpp +# End Source File +# Begin Source File + +SOURCE=.\multiplay_gamerules.cpp +# End Source File +# Begin Source File + +SOURCE=.\nihilanth.cpp +# End Source File +# Begin Source File + +SOURCE=.\nodes.cpp +# End Source File +# Begin Source File + +SOURCE=.\osprey.cpp +# End Source File +# Begin Source File + +SOURCE=.\paranoia_military.cpp +# End Source File +# Begin Source File + +SOURCE=.\paranoia_turret.cpp +# End Source File +# Begin Source File + +SOURCE=.\pathcorner.cpp +# End Source File +# Begin Source File + +SOURCE=.\physic.cpp +# End Source File +# Begin Source File + +SOURCE=.\plane.cpp +# End Source File +# Begin Source File + +SOURCE=.\plats.cpp +# End Source File +# Begin Source File + +SOURCE=.\player.cpp +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_debug.cpp +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_shared.cpp +# End Source File +# Begin Source File + +SOURCE=.\roach.cpp +# End Source File +# Begin Source File + +SOURCE=.\rpg_rocket.cpp +# End Source File +# Begin Source File + +SOURCE=.\schedule.cpp +# End Source File +# Begin Source File + +SOURCE=.\scientist.cpp +# End Source File +# Begin Source File + +SOURCE=.\scripted.cpp +# End Source File +# Begin Source File + +SOURCE=.\singleplay_gamerules.cpp +# End Source File +# Begin Source File + +SOURCE=.\skill.cpp +# End Source File +# Begin Source File + +SOURCE=.\sound.cpp +# End Source File +# Begin Source File + +SOURCE=.\soundent.cpp +# End Source File +# Begin Source File + +SOURCE=.\spectator.cpp +# End Source File +# Begin Source File + +SOURCE=.\spider.cpp +# End Source File +# Begin Source File + +SOURCE=.\squadmonster.cpp +# End Source File +# Begin Source File + +SOURCE=.\squeakgrenade.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\stringlib.cpp +# End Source File +# Begin Source File + +SOURCE=.\subs.cpp +# End Source File +# Begin Source File + +SOURCE=.\sv_materials.cpp +# End Source File +# Begin Source File + +SOURCE=.\talkmonster.cpp +# End Source File +# Begin Source File + +SOURCE=.\teamplay_gamerules.cpp +# End Source File +# Begin Source File + +SOURCE=.\tempmonster.cpp +# End Source File +# Begin Source File + +SOURCE=.\tentacle.cpp +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\trace.cpp +# End Source File +# Begin Source File + +SOURCE=.\triggers.cpp +# End Source File +# Begin Source File + +SOURCE=.\turret.cpp +# End Source File +# Begin Source File + +SOURCE=.\util.cpp +# End Source File +# Begin Source File + +SOURCE=..\game_shared\voice_gamemgr.cpp +# End Source File +# Begin Source File + +SOURCE=.\weapons.cpp +# End Source File +# Begin Source File + +SOURCE=.\world.cpp +# End Source File +# Begin Source File + +SOURCE=.\xen.cpp +# End Source File +# Begin Source File + +SOURCE=.\zombie.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=.\activity.h +# End Source File +# Begin Source File + +SOURCE=.\activitymap.h +# End Source File +# Begin Source File + +SOURCE=.\animation.h +# End Source File +# Begin Source File + +SOURCE=.\basemonster.h +# End Source File +# Begin Source File + +SOURCE=.\cbase.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\cdll_dll.h +# End Source File +# Begin Source File + +SOURCE=.\client.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\clipfile.h +# End Source File +# Begin Source File + +SOURCE=.\decals.h +# End Source File +# Begin Source File + +SOURCE=.\defaultai.h +# End Source File +# Begin Source File + +SOURCE=.\doors.h +# End Source File +# Begin Source File + +SOURCE=.\effects.h +# End Source File +# Begin Source File + +SOURCE=..\engine\eiface.h +# End Source File +# Begin Source File + +SOURCE=.\enginecallback.h +# End Source File +# Begin Source File + +SOURCE=.\explode.h +# End Source File +# Begin Source File + +SOURCE=.\extdll.h +# End Source File +# Begin Source File + +SOURCE=.\flyingmonster.h +# End Source File +# Begin Source File + +SOURCE=.\func_break.h +# End Source File +# Begin Source File + +SOURCE=.\gamerules.h +# End Source File +# Begin Source File + +SOURCE=.\hornet.h +# End Source File +# Begin Source File + +SOURCE=.\items.h +# End Source File +# Begin Source File + +SOURCE=.\locus.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\material.h +# End Source File +# Begin Source File + +SOURCE=..\game_shared\mathlib.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\meshdesc.h +# End Source File +# Begin Source File + +SOURCE=.\monsterevent.h +# End Source File +# Begin Source File + +SOURCE=.\monsters.h +# End Source File +# Begin Source File + +SOURCE=.\movewith.h +# End Source File +# Begin Source File + +SOURCE=.\nodes.h +# End Source File +# Begin Source File + +SOURCE=.\plane.h +# End Source File +# Begin Source File + +SOURCE=.\player.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_debug.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_defs.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_info.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_materials.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_movevars.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\pm_shared.h +# End Source File +# Begin Source File + +SOURCE=.\rushscript.h +# End Source File +# Begin Source File + +SOURCE=.\saverestore.h +# End Source File +# Begin Source File + +SOURCE=.\schedule.h +# End Source File +# Begin Source File + +SOURCE=.\scripted.h +# End Source File +# Begin Source File + +SOURCE=.\scriptevent.h +# End Source File +# Begin Source File + +SOURCE=.\skill.h +# End Source File +# Begin Source File + +SOURCE=.\soundent.h +# End Source File +# Begin Source File + +SOURCE=.\spectator.h +# End Source File +# Begin Source File + +SOURCE=.\squadmonster.h +# End Source File +# Begin Source File + +SOURCE=.\talkmonster.h +# End Source File +# Begin Source File + +SOURCE=.\teamplay_gamerules.h +# End Source File +# Begin Source File + +SOURCE=..\pm_shared\trace.h +# End Source File +# Begin Source File + +SOURCE=.\trains.h +# End Source File +# Begin Source File + +SOURCE=.\util.h +# End Source File +# Begin Source File + +SOURCE=.\vector.h +# End Source File +# Begin Source File + +SOURCE=.\weapons.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/dlls/hornet.cpp b/dlls/hornet.cpp new file mode 100644 index 0000000..15940e1 --- /dev/null +++ b/dlls/hornet.cpp @@ -0,0 +1,411 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// Hornets +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "soundent.h" +#include "hornet.h" +#include "gamerules.h" + + +int iHornetTrail; +int iHornetPuff; + +LINK_ENTITY_TO_CLASS( hornet, CHornet ); + +//========================================================= +// Save/Restore +//========================================================= +TYPEDESCRIPTION CHornet::m_SaveData[] = +{ + DEFINE_FIELD( CHornet, m_flStopAttack, FIELD_TIME ), + DEFINE_FIELD( CHornet, m_iHornetType, FIELD_INTEGER ), + DEFINE_FIELD( CHornet, m_flFlySpeed, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CHornet, CBaseMonster ); + +//========================================================= +// don't let hornets gib, ever. +//========================================================= +int CHornet :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // filter these bits a little. + bitsDamageType &= ~ ( DMG_ALWAYSGIB ); + bitsDamageType |= DMG_NEVERGIB; + + return CBaseMonster :: TakeDamage ( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +//========================================================= +//========================================================= +void CHornet :: Spawn( void ) +{ + Precache(); + + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + pev->takedamage = DAMAGE_YES; + pev->flags |= FL_MONSTER; + pev->health = 1;// weak! + + if ( g_pGameRules->IsMultiplayer() ) + { + // hornets don't live as long in multiplayer + m_flStopAttack = gpGlobals->time + 3.5; + } + else + { + m_flStopAttack = gpGlobals->time + 5.0; + } + + m_flFieldOfView = 0.9; // +- 25 degrees + + if ( RANDOM_LONG ( 1, 5 ) <= 2 ) + { + m_iHornetType = HORNET_TYPE_RED; + m_flFlySpeed = HORNET_RED_SPEED; + } + else + { + m_iHornetType = HORNET_TYPE_ORANGE; + m_flFlySpeed = HORNET_ORANGE_SPEED; + } + + SET_MODEL(ENT( pev ), "models/hornet.mdl"); + UTIL_SetSize( pev, Vector( -4, -4, -4 ), Vector( 4, 4, 4 ) ); + + SetTouch(&CHornet :: DieTouch ); + SetThink(&CHornet :: StartTrack ); + + edict_t *pSoundEnt = pev->owner; + if ( !pSoundEnt ) + pSoundEnt = edict(); + + pev->dmg = gSkillData.monDmgHornet; + + SetNextThink( 0.1 ); + ResetSequenceInfo( ); +} + + +void CHornet :: Precache() +{ + PRECACHE_MODEL("models/hornet.mdl"); + + PRECACHE_SOUND( "agrunt/ag_fire1.wav" ); + PRECACHE_SOUND( "agrunt/ag_fire2.wav" ); + PRECACHE_SOUND( "agrunt/ag_fire3.wav" ); + + PRECACHE_SOUND( "hornet/ag_buzz1.wav" ); + PRECACHE_SOUND( "hornet/ag_buzz2.wav" ); + PRECACHE_SOUND( "hornet/ag_buzz3.wav" ); + + PRECACHE_SOUND( "hornet/ag_hornethit1.wav" ); + PRECACHE_SOUND( "hornet/ag_hornethit2.wav" ); + PRECACHE_SOUND( "hornet/ag_hornethit3.wav" ); + + iHornetPuff = PRECACHE_MODEL( "sprites/muz1.spr" ); + iHornetTrail = PRECACHE_MODEL("sprites/laserbeam.spr"); +} + +//========================================================= +// hornets will never get mad at each other, no matter who the owner is. +//========================================================= +int CHornet::IRelationship ( CBaseEntity *pTarget ) +{ + if ( pTarget->pev->modelindex == pev->modelindex ) + { + return R_NO; + } + + return CBaseMonster :: IRelationship( pTarget ); +} + +//========================================================= +// ID's Hornet as their owner +//========================================================= +int CHornet::Classify ( void ) +{ + if (m_iClass) return m_iClass; + if ( pev->owner && pev->owner->v.flags & FL_CLIENT) + { + return CLASS_PLAYER_BIOWEAPON; + } + + return CLASS_ALIEN_BIOWEAPON; +} + +//========================================================= +// StartTrack - starts a hornet out tracking its target +//========================================================= +void CHornet :: StartTrack ( void ) +{ + IgniteTrail(); + + SetTouch(&CHornet :: TrackTouch ); + SetThink(&CHornet :: TrackTarget ); + + SetNextThink( 0.1 ); +} + +//========================================================= +// StartDart - starts a hornet out just flying straight. +//========================================================= +void CHornet :: StartDart ( void ) +{ + IgniteTrail(); + + SetTouch(&CHornet :: DartTouch ); + + SetThink(&CHornet :: SUB_Remove ); + SetNextThink( 4 ); +} + +void CHornet::IgniteTrail( void ) +{ +/* + + ted's suggested trail colors: + +r161 +g25 +b97 + +r173 +g39 +b14 + +old colors + case HORNET_TYPE_RED: + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 128 ); // r, g, b + WRITE_BYTE( 0 ); // r, g, b + break; + case HORNET_TYPE_ORANGE: + WRITE_BYTE( 0 ); // r, g, b + WRITE_BYTE( 100 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + break; + +*/ + + // trail + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMFOLLOW ); + WRITE_SHORT( entindex() ); // entity + WRITE_SHORT( iHornetTrail ); // model + WRITE_BYTE( 10 ); // life + WRITE_BYTE( 2 ); // width + + switch ( m_iHornetType ) + { + case HORNET_TYPE_RED: + WRITE_BYTE( 179 ); // r, g, b + WRITE_BYTE( 39 ); // r, g, b + WRITE_BYTE( 14 ); // r, g, b + break; + case HORNET_TYPE_ORANGE: + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 128 ); // r, g, b + WRITE_BYTE( 0 ); // r, g, b + break; + } + + WRITE_BYTE( 128 ); // brightness + + MESSAGE_END(); +} + +//========================================================= +// Hornet is flying, gently tracking target +//========================================================= +void CHornet :: TrackTarget ( void ) +{ + Vector vecFlightDir; + Vector vecDirToEnemy; + float flDelta; + + StudioFrameAdvance( ); + + if (gpGlobals->time > m_flStopAttack) + { + SetTouch( NULL ); + SetThink(&CHornet :: SUB_Remove ); + SetNextThink( 0.1 ); + return; + } + + // UNDONE: The player pointer should come back after returning from another level + if ( m_hEnemy == NULL ) + {// enemy is dead. + Look( 512 ); + m_hEnemy = BestVisibleEnemy( ); + } + + if ( m_hEnemy != NULL && FVisible( m_hEnemy )) + { + m_vecEnemyLKP = m_hEnemy->BodyTarget( pev->origin ); + } + else + { + m_vecEnemyLKP = m_vecEnemyLKP + pev->velocity * m_flFlySpeed * 0.1; + } + + vecDirToEnemy = ( m_vecEnemyLKP - pev->origin ).Normalize(); + + if (pev->velocity.Length() < 0.1) + vecFlightDir = vecDirToEnemy; + else + vecFlightDir = pev->velocity.Normalize(); + + // measure how far the turn is, the wider the turn, the slow we'll go this time. + flDelta = DotProduct ( vecFlightDir, vecDirToEnemy ); + + if ( flDelta < 0.5 ) + {// hafta turn wide again. play sound + switch (RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_buzz1.wav", HORNET_BUZZ_VOLUME, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_buzz2.wav", HORNET_BUZZ_VOLUME, ATTN_NORM); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_buzz3.wav", HORNET_BUZZ_VOLUME, ATTN_NORM); break; + } + } + + if ( flDelta <= 0 && m_iHornetType == HORNET_TYPE_RED ) + {// no flying backwards, but we don't want to invert this, cause we'd go fast when we have to turn REAL far. + flDelta = 0.25; + } + + pev->velocity = ( vecFlightDir + vecDirToEnemy).Normalize(); + + if ( pev->owner && (pev->owner->v.flags & FL_MONSTER) ) + { + // random pattern only applies to hornets fired by monsters, not players. + + pev->velocity.x += RANDOM_FLOAT ( -0.10, 0.10 );// scramble the flight dir a bit. + pev->velocity.y += RANDOM_FLOAT ( -0.10, 0.10 ); + pev->velocity.z += RANDOM_FLOAT ( -0.10, 0.10 ); + } + + switch ( m_iHornetType ) + { + case HORNET_TYPE_RED: + pev->velocity = pev->velocity * ( m_flFlySpeed * flDelta );// scale the dir by the ( speed * width of turn ) + SetNextThink( RANDOM_FLOAT( 0.1, 0.3 ) ); + break; + case HORNET_TYPE_ORANGE: + pev->velocity = pev->velocity * m_flFlySpeed;// do not have to slow down to turn. + SetNextThink( 0.1 );// fixed think time + break; + } + + pev->angles = UTIL_VecToAngles (pev->velocity); + + pev->solid = SOLID_BBOX; + + // if hornet is close to the enemy, jet in a straight line for a half second. + // (only in the single player game) + if ( m_hEnemy != NULL && !g_pGameRules->IsMultiplayer() ) + { + if ( flDelta >= 0.4 && ( pev->origin - m_vecEnemyLKP ).Length() <= 300 ) + { + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_SPRITE ); + WRITE_COORD( pev->origin.x); // pos + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z); + WRITE_SHORT( iHornetPuff ); // model + // WRITE_BYTE( 0 ); // life * 10 + WRITE_BYTE( 2 ); // size * 10 + WRITE_BYTE( 128 ); // brightness + MESSAGE_END(); + + switch (RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_buzz1.wav", HORNET_BUZZ_VOLUME, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_buzz2.wav", HORNET_BUZZ_VOLUME, ATTN_NORM); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_buzz3.wav", HORNET_BUZZ_VOLUME, ATTN_NORM); break; + } + pev->velocity = pev->velocity * 2; + SetNextThink( 1.0 ); + // don't attack again + m_flStopAttack = gpGlobals->time; + } + } +} + +//========================================================= +// Tracking Hornet hit something +//========================================================= +void CHornet :: TrackTouch ( CBaseEntity *pOther ) +{ + if ( pOther->edict() == pev->owner || pOther->pev->modelindex == pev->modelindex ) + {// bumped into the guy that shot it. + pev->solid = SOLID_NOT; + return; + } + + if ( IRelationship( pOther ) <= R_NO ) + { + // hit something we don't want to hurt, so turn around. + + pev->velocity = pev->velocity.Normalize(); + + pev->velocity.x *= -1; + pev->velocity.y *= -1; + + pev->origin = pev->origin + pev->velocity * 4; // bounce the hornet off a bit. + pev->velocity = pev->velocity * m_flFlySpeed; + + return; + } + + DieTouch( pOther ); +} + +void CHornet::DartTouch( CBaseEntity *pOther ) +{ + DieTouch( pOther ); +} + +void CHornet::DieTouch ( CBaseEntity *pOther ) +{ + if ( pOther && pOther->pev->takedamage ) + {// do the damage + + switch (RANDOM_LONG(0,2)) + {// buzz when you plug someone + case 0: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_hornethit1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_hornethit2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hornet/ag_hornethit3.wav", 1, ATTN_NORM); break; + } + + pOther->TakeDamage( pev, VARS( pev->owner ), pev->dmg, DMG_BULLET ); + } + + pev->modelindex = 0;// so will disappear for the 0.1 secs we wait until NEXTTHINK gets rid + pev->solid = SOLID_NOT; + + SetThink(&CHornet:: SUB_Remove ); + SetNextThink( 1 );// stick around long enough for the sound to finish! +} + diff --git a/dlls/hornet.h b/dlls/hornet.h new file mode 100644 index 0000000..98d1710 --- /dev/null +++ b/dlls/hornet.h @@ -0,0 +1,58 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// Hornets +//========================================================= + +//========================================================= +// Hornet Defines +//========================================================= +#define HORNET_TYPE_RED 0 +#define HORNET_TYPE_ORANGE 1 +#define HORNET_RED_SPEED (float)600 +#define HORNET_ORANGE_SPEED (float)800 +#define HORNET_BUZZ_VOLUME (float)0.8 + +extern int iHornetPuff; + +//========================================================= +// Hornet - this is the projectile that the Alien Grunt fires. +//========================================================= +class CHornet : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + int Classify ( void ); + int IRelationship ( CBaseEntity *pTarget ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void IgniteTrail( void ); + void EXPORT StartTrack ( void ); + void EXPORT StartDart ( void ); + void EXPORT TrackTarget ( void ); + void EXPORT TrackTouch ( CBaseEntity *pOther ); + void EXPORT DartTouch( CBaseEntity *pOther ); + void EXPORT DieTouch ( CBaseEntity *pOther ); + + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + float m_flStopAttack; + int m_iHornetType; + float m_flFlySpeed; +}; + diff --git a/dlls/houndeye.cpp b/dlls/houndeye.cpp new file mode 100644 index 0000000..d90a872 --- /dev/null +++ b/dlls/houndeye.cpp @@ -0,0 +1,1307 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Houndeye - spooky sonic dog. +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "animation.h" +#include "nodes.h" +#include "squadmonster.h" +#include "soundent.h" +#include "game.h" + +extern CGraph WorldGraph; + +// houndeye does 20 points of damage spread over a sphere 384 units in diameter, and each additional +// squad member increases the BASE damage by 110%, per the spec. +#define HOUNDEYE_MAX_SQUAD_SIZE 4 +#define HOUNDEYE_MAX_ATTACK_RADIUS 384 +#define HOUNDEYE_SQUAD_BONUS (float)1.1 + +#define HOUNDEYE_EYE_FRAMES 4 // how many different switchable maps for the eye + +#define HOUNDEYE_SOUND_STARTLE_VOLUME 128 // how loud a sound has to be to badly scare a sleeping houndeye + +//========================================================= +// monster-specific tasks +//========================================================= +enum +{ + TASK_HOUND_CLOSE_EYE = LAST_COMMON_TASK + 1, + TASK_HOUND_OPEN_EYE, + TASK_HOUND_THREAT_DISPLAY, + TASK_HOUND_FALL_ASLEEP, + TASK_HOUND_WAKE_UP, + TASK_HOUND_HOP_BACK +}; + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_HOUND_AGITATED = LAST_COMMON_SCHEDULE + 1, + SCHED_HOUND_HOP_RETREAT, + SCHED_HOUND_FAIL, +}; + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define HOUND_AE_WARN 1 +#define HOUND_AE_STARTATTACK 2 +#define HOUND_AE_THUMP 3 +#define HOUND_AE_ANGERSOUND1 4 +#define HOUND_AE_ANGERSOUND2 5 +#define HOUND_AE_HOPBACK 6 +#define HOUND_AE_CLOSE_EYE 7 + +class CHoundeye : public CSquadMonster +{ +public: + void Spawn( void ); + void Precache( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + float MaxYawSpeed( void ); + void WarmUpSound ( void ); + void AlertSound( void ); + void DeathSound( void ); + void WarnSound( void ); + void PainSound( void ); + void IdleSound( void ); + void StartTask( Task_t *pTask ); + void RunTask ( Task_t *pTask ); + void SonicAttack( void ); + void PrescheduleThink( void ); + void SetActivity ( Activity NewActivity ); + void WriteBeamColor ( void ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + BOOL FValidateHintType ( short sHint ); + BOOL FCanActiveIdle ( void ); + Schedule_t *GetScheduleOfType ( int Type ); + Schedule_t *CHoundeye :: GetSchedule( void ); + + int Save( CSave &save ); + int Restore( CRestore &restore ); + + CUSTOM_SCHEDULES; + static TYPEDESCRIPTION m_SaveData[]; + + int m_iSpriteTexture; + BOOL m_fAsleep;// some houndeyes sleep in idle mode if this is set, the houndeye is lying down + BOOL m_fDontBlink;// don't try to open/close eye if this bit is set! + Vector m_vecPackCenter; // the center of the pack. The leader maintains this by averaging the origins of all pack members. +}; +LINK_ENTITY_TO_CLASS( monster_houndeye, CHoundeye ); + +TYPEDESCRIPTION CHoundeye::m_SaveData[] = +{ + DEFINE_FIELD( CHoundeye, m_iSpriteTexture, FIELD_INTEGER ), + DEFINE_FIELD( CHoundeye, m_fAsleep, FIELD_BOOLEAN ), + DEFINE_FIELD( CHoundeye, m_fDontBlink, FIELD_BOOLEAN ), + DEFINE_FIELD( CHoundeye, m_vecPackCenter, FIELD_POSITION_VECTOR ), +}; + +IMPLEMENT_SAVERESTORE( CHoundeye, CSquadMonster ); + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CHoundeye :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MONSTER; +} + +//========================================================= +// FValidateHintType +//========================================================= +BOOL CHoundeye :: FValidateHintType ( short sHint ) +{ + int i; + + static short sHoundHints[] = + { + HINT_WORLD_MACHINERY, + HINT_WORLD_BLINKING_LIGHT, + HINT_WORLD_HUMAN_BLOOD, + HINT_WORLD_ALIEN_BLOOD, + }; + + for ( i = 0 ; i < ARRAYSIZE ( sHoundHints ) ; i++ ) + { + if ( sHoundHints[ i ] == sHint ) + { + return TRUE; + } + } + + ALERT ( at_aiconsole, "Couldn't validate hint type" ); + return FALSE; +} + + +//========================================================= +// FCanActiveIdle +//========================================================= +BOOL CHoundeye :: FCanActiveIdle ( void ) +{ + if ( InSquad() ) + { + CSquadMonster *pSquadLeader = MySquadLeader(); + + for (int i = 0; i < MAX_SQUAD_MEMBERS;i++) + { + CSquadMonster *pMember = pSquadLeader->MySquadMember(i); + + if ( pMember != NULL && pMember != this && pMember->m_iHintNode != NO_NODE ) + { + // someone else in the group is active idling right now! + return FALSE; + } + } + + return TRUE; + } + + return TRUE; +} + + +//========================================================= +// CheckRangeAttack1 - overridden for houndeyes so that they +// try to get within half of their max attack radius before +// attacking, so as to increase their chances of doing damage. +//========================================================= +BOOL CHoundeye :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( flDist <= ( HOUNDEYE_MAX_ATTACK_RADIUS * 0.5 ) && flDot >= 0.3 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// MaxYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +float CHoundeye :: MaxYawSpeed( void ) +{ + float ys; + + ys = 90; + + switch ( m_Activity ) + { + case ACT_CROUCHIDLE://sleeping! + ys = 0; + break; + case ACT_IDLE: + ys = 60; + break; + case ACT_WALK: + ys = 90; + break; + case ACT_RUN: + ys = 90; + break; + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 90; + break; + } + + return ys; +} + +//========================================================= +// SetActivity +//========================================================= +void CHoundeye :: SetActivity ( Activity NewActivity ) +{ + int iSequence; + + if ( NewActivity == m_Activity ) + return; + + if ( m_MonsterState == MONSTERSTATE_COMBAT && NewActivity == ACT_IDLE && RANDOM_LONG(0,1) ) + { + // play pissed idle. + iSequence = LookupSequence( "madidle" ); + + m_Activity = NewActivity; // Go ahead and set this so it doesn't keep trying when the anim is not present + + // In case someone calls this with something other than the ideal activity + m_IdealActivity = m_Activity; + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + { + pev->sequence = iSequence; // Set to the reset anim (if it's there) + pev->frame = 0; // FIX: frame counter shouldn't be reset when its the same activity as before + ResetSequenceInfo(); + RecalculateYawSpeed(); + } + } + else + { + CSquadMonster :: SetActivity ( NewActivity ); + } +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CHoundeye :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch ( pEvent->event ) + { + case HOUND_AE_WARN: + // do stuff for this event. + WarnSound(); + break; + + case HOUND_AE_STARTATTACK: + WarmUpSound(); + break; + + case HOUND_AE_HOPBACK: + { + float flGravity = g_psv_gravity->value; + + pev->flags &= ~FL_ONGROUND; + + pev->velocity = gpGlobals->v_forward * -200; + pev->velocity.z += (0.6 * flGravity) * 0.5; + + break; + } + + case HOUND_AE_THUMP: + // emit the shockwaves + SonicAttack(); + break; + + case HOUND_AE_ANGERSOUND1: + EMIT_SOUND(ENT(pev), CHAN_VOICE, "houndeye/he_pain3.wav", 1, ATTN_NORM); + break; + + case HOUND_AE_ANGERSOUND2: + EMIT_SOUND(ENT(pev), CHAN_VOICE, "houndeye/he_pain1.wav", 1, ATTN_NORM); + break; + + case HOUND_AE_CLOSE_EYE: + if ( !m_fDontBlink ) + { + pev->skin = HOUNDEYE_EYE_FRAMES - 1; + } + break; + + default: + CSquadMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CHoundeye :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/houndeye.mdl"); + UTIL_SetSize(pev, Vector ( -16, -16, 0 ), Vector ( 16, 16, 36 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_YELLOW; + if (pev->health == 0) + pev->health = gSkillData.houndeyeHealth; + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_fAsleep = FALSE; // everyone spawns awake + m_fDontBlink = FALSE; + m_afCapability |= bits_CAP_SQUAD; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CHoundeye :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/houndeye.mdl"); + + PRECACHE_SOUND("houndeye/he_alert1.wav"); + PRECACHE_SOUND("houndeye/he_alert2.wav"); + PRECACHE_SOUND("houndeye/he_alert3.wav"); + + PRECACHE_SOUND("houndeye/he_die1.wav"); + PRECACHE_SOUND("houndeye/he_die2.wav"); + PRECACHE_SOUND("houndeye/he_die3.wav"); + + PRECACHE_SOUND("houndeye/he_idle1.wav"); + PRECACHE_SOUND("houndeye/he_idle2.wav"); + PRECACHE_SOUND("houndeye/he_idle3.wav"); + + PRECACHE_SOUND("houndeye/he_hunt1.wav"); + PRECACHE_SOUND("houndeye/he_hunt2.wav"); + PRECACHE_SOUND("houndeye/he_hunt3.wav"); + + PRECACHE_SOUND("houndeye/he_pain1.wav"); + PRECACHE_SOUND("houndeye/he_pain3.wav"); + PRECACHE_SOUND("houndeye/he_pain4.wav"); + PRECACHE_SOUND("houndeye/he_pain5.wav"); + + PRECACHE_SOUND("houndeye/he_attack1.wav"); + PRECACHE_SOUND("houndeye/he_attack3.wav"); + + PRECACHE_SOUND("houndeye/he_blast1.wav"); + PRECACHE_SOUND("houndeye/he_blast2.wav"); + PRECACHE_SOUND("houndeye/he_blast3.wav"); + + m_iSpriteTexture = PRECACHE_MODEL( "sprites/shockwave.spr" ); +} + +//========================================================= +// IdleSound +//========================================================= +void CHoundeye :: IdleSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_idle1.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_idle2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_idle3.wav", 1, ATTN_NORM ); + break; + } +} + +//========================================================= +// IdleSound +//========================================================= +void CHoundeye :: WarmUpSound ( void ) +{ + switch ( RANDOM_LONG(0,1) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "houndeye/he_attack1.wav", 0.7, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "houndeye/he_attack3.wav", 0.7, ATTN_NORM ); + break; + } +} + +//========================================================= +// WarnSound +//========================================================= +void CHoundeye :: WarnSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_hunt1.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_hunt2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_hunt3.wav", 1, ATTN_NORM ); + break; + } +} + +//========================================================= +// AlertSound +//========================================================= +void CHoundeye :: AlertSound ( void ) +{ + + if ( InSquad() && !IsLeader() ) + { + return; // only leader makes ALERT sound. + } + + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_alert1.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_alert2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_alert3.wav", 1, ATTN_NORM ); + break; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CHoundeye :: DeathSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_die1.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_die2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_die3.wav", 1, ATTN_NORM ); + break; + } +} + +//========================================================= +// PainSound +//========================================================= +void CHoundeye :: PainSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_pain3.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_pain4.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_pain5.wav", 1, ATTN_NORM ); + break; + } +} + +//========================================================= +// WriteBeamColor - writes a color vector to the network +// based on the size of the group. +//========================================================= +void CHoundeye :: WriteBeamColor ( void ) +{ + BYTE bRed, bGreen, bBlue; + + if ( InSquad() ) + { + switch ( SquadCount() ) + { + case 2: + // no case for 0 or 1, cause those are impossible for monsters in Squads. + bRed = 101; + bGreen = 133; + bBlue = 221; + break; + case 3: + bRed = 67; + bGreen = 85; + bBlue = 255; + break; + case 4: + bRed = 62; + bGreen = 33; + bBlue = 211; + break; + default: + ALERT ( at_aiconsole, "Unsupported Houndeye SquadSize!\n" ); + bRed = 188; + bGreen = 220; + bBlue = 255; + break; + } + } + else + { + // solo houndeye - weakest beam + bRed = 188; + bGreen = 220; + bBlue = 255; + } + + WRITE_BYTE( bRed ); + WRITE_BYTE( bGreen ); + WRITE_BYTE( bBlue ); +} + + +//========================================================= +// SonicAttack +//========================================================= +void CHoundeye :: SonicAttack ( void ) +{ + float flAdjustedDamage; + float flDist; + + switch ( RANDOM_LONG( 0, 2 ) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "houndeye/he_blast1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "houndeye/he_blast2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "houndeye/he_blast3.wav", 1, ATTN_NORM); break; + } + + // blast circles + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_BEAMCYLINDER ); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z + 16); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z + 16 + HOUNDEYE_MAX_ATTACK_RADIUS / .2); // reach damage radius over .3 seconds + WRITE_SHORT( m_iSpriteTexture ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 2 ); // life + WRITE_BYTE( 16 ); // width + WRITE_BYTE( 0 ); // noise + + WriteBeamColor(); + + WRITE_BYTE( 255 ); //brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_BEAMCYLINDER ); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z + 16); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z + 16 + ( HOUNDEYE_MAX_ATTACK_RADIUS / 2 ) / .2); // reach damage radius over .3 seconds + WRITE_SHORT( m_iSpriteTexture ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 2 ); // life + WRITE_BYTE( 16 ); // width + WRITE_BYTE( 0 ); // noise + + WriteBeamColor(); + + WRITE_BYTE( 255 ); //brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + + CBaseEntity *pEntity = NULL; + // iterate on all entities in the vicinity. + while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, HOUNDEYE_MAX_ATTACK_RADIUS )) != NULL) + { + if ( pEntity->pev->takedamage != DAMAGE_NO ) + { + if ( !FClassnameIs(pEntity->pev, "monster_houndeye") ) + {// houndeyes don't hurt other houndeyes with their attack + + // houndeyes do FULL damage if the ent in question is visible. Half damage otherwise. + // This means that you must get out of the houndeye's attack range entirely to avoid damage. + // Calculate full damage first + + if ( SquadCount() > 1 ) + { + // squad gets attack bonus. + flAdjustedDamage = gSkillData.houndeyeDmgBlast + gSkillData.houndeyeDmgBlast * ( HOUNDEYE_SQUAD_BONUS * ( SquadCount() - 1 ) ); + } + else + { + // solo + flAdjustedDamage = gSkillData.houndeyeDmgBlast; + } + + flDist = (pEntity->Center() - pev->origin).Length(); + + flAdjustedDamage -= ( flDist / HOUNDEYE_MAX_ATTACK_RADIUS ) * flAdjustedDamage; + + if ( !FVisible( pEntity ) ) + { + if ( pEntity->IsPlayer() ) + { + // if this entity is a client, and is not in full view, inflict half damage. We do this so that players still + // take the residual damage if they don't totally leave the houndeye's effective radius. We restrict it to clients + // so that monsters in other parts of the level don't take the damage and get pissed. + flAdjustedDamage *= 0.5; + } + else if ( !FClassnameIs( pEntity->pev, "func_breakable" ) && !FClassnameIs( pEntity->pev, "func_pushable" ) ) + { + // do not hurt nonclients through walls, but allow damage to be done to breakables + flAdjustedDamage = 0; + } + } + + //ALERT ( at_aiconsole, "Damage: %f\n", flAdjustedDamage ); + + if (flAdjustedDamage > 0 ) + { + pEntity->TakeDamage ( pev, pev, flAdjustedDamage, DMG_SONIC | DMG_ALWAYSGIB ); + } + } + } + } +} + +//========================================================= +// start task +//========================================================= +void CHoundeye :: StartTask ( Task_t *pTask ) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch ( pTask->iTask ) + { + case TASK_HOUND_FALL_ASLEEP: + { + m_fAsleep = TRUE; // signal that hound is lying down (must stand again before doing anything else!) + m_iTaskStatus = TASKSTATUS_COMPLETE; + break; + } + case TASK_HOUND_WAKE_UP: + { + m_fAsleep = FALSE; // signal that hound is standing again + m_iTaskStatus = TASKSTATUS_COMPLETE; + break; + } + case TASK_HOUND_OPEN_EYE: + { + m_fDontBlink = FALSE; // turn blinking back on and that code will automatically open the eye + m_iTaskStatus = TASKSTATUS_COMPLETE; + break; + } + case TASK_HOUND_CLOSE_EYE: + { + pev->skin = 0; + m_fDontBlink = TRUE; // tell blink code to leave the eye alone. + break; + } + case TASK_HOUND_THREAT_DISPLAY: + { + m_IdealActivity = ACT_IDLE_ANGRY; + break; + } + case TASK_HOUND_HOP_BACK: + { + m_IdealActivity = ACT_LEAP; + break; + } + case TASK_RANGE_ATTACK1: + { + m_IdealActivity = ACT_RANGE_ATTACK1; + +/* + if ( InSquad() ) + { + // see if there is a battery to connect to. + CSquadMonster *pSquad = m_pSquadLeader; + + while ( pSquad ) + { + if ( pSquad->m_iMySlot == bits_SLOT_HOUND_BATTERY ) + { + // draw a beam. + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTS ); + WRITE_SHORT( ENTINDEX( this->edict() ) ); + WRITE_SHORT( ENTINDEX( pSquad->edict() ) ); + WRITE_SHORT( m_iSpriteTexture ); + WRITE_BYTE( 0 ); // framestart + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 10 ); // life + WRITE_BYTE( 40 ); // width + WRITE_BYTE( 10 ); // noise + WRITE_BYTE( 0 ); // r, g, b + WRITE_BYTE( 50 ); // r, g, b + WRITE_BYTE( 250); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 30 ); // speed + MESSAGE_END(); + break; + } + + pSquad = pSquad->m_pSquadNext; + } + } +*/ + + break; + } + case TASK_SPECIAL_ATTACK1: + { + m_IdealActivity = ACT_SPECIAL_ATTACK1; + break; + } + case TASK_GUARD: + { + m_IdealActivity = ACT_GUARD; + break; + } + default: + { + CSquadMonster :: StartTask(pTask); + break; + } + } +} + +//========================================================= +// RunTask +//========================================================= +void CHoundeye :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_HOUND_THREAT_DISPLAY: + { + SetIdealYawToTargetAndUpdate( m_vecEnemyLKP , AI_KEEP_YAW_SPEED ); + + if ( m_fSequenceFinished ) + { + TaskComplete(); + } + + break; + } + case TASK_HOUND_CLOSE_EYE: + { + if ( pev->skin < HOUNDEYE_EYE_FRAMES - 1 ) + { + pev->skin++; + } + break; + } + case TASK_HOUND_HOP_BACK: + { + if ( m_fSequenceFinished ) + { + TaskComplete(); + } + break; + } + case TASK_SPECIAL_ATTACK1: + { + pev->skin = RANDOM_LONG(0, HOUNDEYE_EYE_FRAMES - 1); + + SetIdealYawToTargetAndUpdate( m_vecEnemyLKP , AI_KEEP_YAW_SPEED ); + + float life; + life = ((255 - pev->frame) / (pev->framerate * m_flFrameRate)); + if (life < 0.1) life = 0.1; + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_IMPLOSION); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z + 16); + WRITE_BYTE( 50 * life + 100); // radius + WRITE_BYTE( pev->frame / 25.0 ); // count + WRITE_BYTE( life * 10 ); // life + MESSAGE_END(); + + if ( m_fSequenceFinished ) + { + SonicAttack(); + TaskComplete(); + } + + break; + } + default: + { + CSquadMonster :: RunTask(pTask); + break; + } + } +} + +//========================================================= +// PrescheduleThink +//========================================================= +void CHoundeye::PrescheduleThink ( void ) +{ + // if the hound is mad and is running, make hunt noises. + if ( m_MonsterState == MONSTERSTATE_COMBAT && m_Activity == ACT_RUN && RANDOM_FLOAT( 0, 1 ) < 0.2 ) + { + WarnSound(); + } + + // at random, initiate a blink if not already blinking or sleeping + if ( !m_fDontBlink ) + { + if ( ( pev->skin == 0 ) && RANDOM_LONG(0,0x7F) == 0 ) + {// start blinking! + pev->skin = HOUNDEYE_EYE_FRAMES - 1; + } + else if ( pev->skin != 0 ) + {// already blinking + pev->skin--; + } + } + + // if you are the leader, average the origins of each pack member to get an approximate center. + if ( IsLeader() ) + { + CSquadMonster *pSquadMember; + int iSquadCount = 0; + + for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) + { + pSquadMember = MySquadMember(i); + + if (pSquadMember) + { + iSquadCount++; + m_vecPackCenter = m_vecPackCenter + pSquadMember->pev->origin; + } + } + + m_vecPackCenter = m_vecPackCenter / iSquadCount; + } +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +Task_t tlHoundGuardPack[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_GUARD, (float)0 }, +}; + +Schedule_t slHoundGuardPack[] = +{ + { + tlHoundGuardPack, + ARRAYSIZE ( tlHoundGuardPack ), + bits_COND_SEE_HATE | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED | + bits_COND_HEAR_SOUND, + + bits_SOUND_COMBAT |// sound flags + bits_SOUND_WORLD | + bits_SOUND_MEAT | + bits_SOUND_PLAYER, + "GuardPack" + }, +}; + +// primary range attack +Task_t tlHoundYell1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_HOUND_AGITATED }, +}; + +Task_t tlHoundYell2[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slHoundRangeAttack[] = +{ + { + tlHoundYell1, + ARRAYSIZE ( tlHoundYell1 ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "HoundRangeAttack1" + }, + { + tlHoundYell2, + ARRAYSIZE ( tlHoundYell2 ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "HoundRangeAttack2" + }, +}; + +// lie down and fall asleep +Task_t tlHoundSleep[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_RANDOM, (float)5 }, + { TASK_PLAY_SEQUENCE, (float)ACT_CROUCH }, + { TASK_SET_ACTIVITY, (float)ACT_CROUCHIDLE }, + { TASK_HOUND_FALL_ASLEEP, (float)0 }, + { TASK_WAIT_RANDOM, (float)25 }, + { TASK_HOUND_CLOSE_EYE, (float)0 }, + //{ TASK_WAIT, (float)10 }, + //{ TASK_WAIT_RANDOM, (float)10 }, +}; + +Schedule_t slHoundSleep[] = +{ + { + tlHoundSleep, + ARRAYSIZE ( tlHoundSleep ), + bits_COND_HEAR_SOUND | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_NEW_ENEMY, + + bits_SOUND_COMBAT | + bits_SOUND_PLAYER | + bits_SOUND_WORLD, + "Hound Sleep" + }, +}; + +// wake and stand up lazily +Task_t tlHoundWakeLazy[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_HOUND_OPEN_EYE, (float)0 }, + { TASK_WAIT_RANDOM, (float)2.5 }, + { TASK_PLAY_SEQUENCE, (float)ACT_STAND }, + { TASK_HOUND_WAKE_UP, (float)0 }, +}; + +Schedule_t slHoundWakeLazy[] = +{ + { + tlHoundWakeLazy, + ARRAYSIZE ( tlHoundWakeLazy ), + 0, + 0, + "WakeLazy" + }, +}; + +// wake and stand up with great urgency! +Task_t tlHoundWakeUrgent[] = +{ + { TASK_HOUND_OPEN_EYE, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_HOP }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_HOUND_WAKE_UP, (float)0 }, +}; + +Schedule_t slHoundWakeUrgent[] = +{ + { + tlHoundWakeUrgent, + ARRAYSIZE ( tlHoundWakeUrgent ), + 0, + 0, + "WakeUrgent" + }, +}; + + +Task_t tlHoundSpecialAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_SPECIAL_ATTACK1, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_IDLE_ANGRY }, +}; + +Schedule_t slHoundSpecialAttack1[] = +{ + { + tlHoundSpecialAttack1, + ARRAYSIZE ( tlHoundSpecialAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED, + + 0, + "Hound Special Attack1" + }, +}; + +Task_t tlHoundAgitated[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_HOUND_THREAT_DISPLAY, 0 }, +}; + +Schedule_t slHoundAgitated[] = +{ + { + tlHoundAgitated, + ARRAYSIZE ( tlHoundAgitated ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "Hound Agitated" + }, +}; + +Task_t tlHoundHopRetreat[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_HOUND_HOP_BACK, 0 }, + { TASK_SET_SCHEDULE, (float)SCHED_TAKE_COVER_FROM_ENEMY }, +}; + +Schedule_t slHoundHopRetreat[] = +{ + { + tlHoundHopRetreat, + ARRAYSIZE ( tlHoundHopRetreat ), + 0, + 0, + "Hound Hop Retreat" + }, +}; + +// hound fails in combat with client in the PVS +Task_t tlHoundCombatFailPVS[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_HOUND_THREAT_DISPLAY, 0 }, + { TASK_WAIT_FACE_ENEMY, (float)1 }, +}; + +Schedule_t slHoundCombatFailPVS[] = +{ + { + tlHoundCombatFailPVS, + ARRAYSIZE ( tlHoundCombatFailPVS ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "HoundCombatFailPVS" + }, +}; + +// hound fails in combat with no client in the PVS. Don't keep peeping! +Task_t tlHoundCombatFailNoPVS[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_HOUND_THREAT_DISPLAY, 0 }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_PVS, 0 }, +}; + +Schedule_t slHoundCombatFailNoPVS[] = +{ + { + tlHoundCombatFailNoPVS, + ARRAYSIZE ( tlHoundCombatFailNoPVS ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "HoundCombatFailNoPVS" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CHoundeye ) +{ + slHoundGuardPack, + slHoundRangeAttack, + &slHoundRangeAttack[ 1 ], + slHoundSleep, + slHoundWakeLazy, + slHoundWakeUrgent, + slHoundSpecialAttack1, + slHoundAgitated, + slHoundHopRetreat, + slHoundCombatFailPVS, + slHoundCombatFailNoPVS, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CHoundeye, CSquadMonster ); + +//========================================================= +// GetScheduleOfType +//========================================================= +Schedule_t* CHoundeye :: GetScheduleOfType ( int Type ) +{ + if ( m_fAsleep ) + { + // if the hound is sleeping, must wake and stand! + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + { + CSound *pWakeSound; + + pWakeSound = PBestSound(); + ASSERT( pWakeSound != NULL ); + if ( pWakeSound ) + { + SetIdealYawToTargetAndUpdate( pWakeSound->m_vecOrigin ); + + if ( FLSoundVolume ( pWakeSound ) >= HOUNDEYE_SOUND_STARTLE_VOLUME ) + { + // awakened by a loud sound + return &slHoundWakeUrgent[ 0 ]; + } + } + // sound was not loud enough to scare the bejesus out of houndeye + return &slHoundWakeLazy[ 0 ]; + } + else if ( HasConditions( bits_COND_NEW_ENEMY ) ) + { + // get up fast, to fight. + return &slHoundWakeUrgent[ 0 ]; + } + + else + { + // hound is waking up on its own + return &slHoundWakeLazy[ 0 ]; + } + } + switch ( Type ) + { + case SCHED_IDLE_STAND: + { + // we may want to sleep instead of stand! + if ( InSquad() && !IsLeader() && !m_fAsleep && RANDOM_LONG(0,29) < 1 ) + { + return &slHoundSleep[ 0 ]; + } + else + { + return CSquadMonster :: GetScheduleOfType( Type ); + } + } + case SCHED_RANGE_ATTACK1: + { + return &slHoundRangeAttack[ 0 ]; +/* + if ( InSquad() ) + { + return &slHoundRangeAttack[ RANDOM_LONG( 0, 1 ) ]; + } + + return &slHoundRangeAttack[ 1 ]; +*/ + } + case SCHED_SPECIAL_ATTACK1: + { + return &slHoundSpecialAttack1[ 0 ]; + } + case SCHED_GUARD: + { + return &slHoundGuardPack[ 0 ]; + } + case SCHED_HOUND_AGITATED: + { + return &slHoundAgitated[ 0 ]; + } + case SCHED_HOUND_HOP_RETREAT: + { + return &slHoundHopRetreat[ 0 ]; + } + case SCHED_FAIL: + { + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + { + if ( !FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) + { + // client in PVS + return &slHoundCombatFailPVS[ 0 ]; + } + else + { + // client has taken off! + return &slHoundCombatFailNoPVS[ 0 ]; + } + } + else + { + return CSquadMonster :: GetScheduleOfType ( Type ); + } + } + default: + { + return CSquadMonster :: GetScheduleOfType ( Type ); + } + } +} + +//========================================================= +// GetSchedule +//========================================================= +Schedule_t *CHoundeye :: GetSchedule( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_COMBAT: + { +// dead enemy + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + + if ( HasConditions( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) ) + { + if ( RANDOM_FLOAT( 0 , 1 ) <= 0.4 ) + { + TraceResult tr; + UTIL_MakeVectors( pev->angles ); + UTIL_TraceHull( pev->origin, pev->origin + gpGlobals->v_forward * -128, dont_ignore_monsters, head_hull, ENT( pev ), &tr ); + + if ( tr.flFraction == 1.0 ) + { + // it's clear behind, so the hound will jump + return GetScheduleOfType ( SCHED_HOUND_HOP_RETREAT ); + } + } + + return GetScheduleOfType ( SCHED_TAKE_COVER_FROM_ENEMY ); + } + + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + if ( OccupySlot ( bits_SLOTS_HOUND_ATTACK ) ) + { + return GetScheduleOfType ( SCHED_RANGE_ATTACK1 ); + } + + return GetScheduleOfType ( SCHED_HOUND_AGITATED ); + } + break; + } + } + + return CSquadMonster :: GetSchedule(); +} diff --git a/dlls/ichthyosaur.cpp b/dlls/ichthyosaur.cpp new file mode 100644 index 0000000..5e58193 --- /dev/null +++ b/dlls/ichthyosaur.cpp @@ -0,0 +1,1115 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +//========================================================= +// icthyosaur - evin, satan fish monster +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "flyingmonster.h" +#include "nodes.h" +#include "soundent.h" +#include "animation.h" +#include "effects.h" +#include "weapons.h" + +#define SEARCH_RETRY 16 + +#define ICHTHYOSAUR_SPEED 150 + +extern CGraph WorldGraph; + +#define EYE_MAD 0 +#define EYE_BASE 1 +#define EYE_CLOSED 2 +#define EYE_BACK 3 +#define EYE_LOOK 4 + + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= + +// UNDONE: Save/restore here +class CIchthyosaur : public CFlyingMonster +{ +public: + void Spawn( void ); + void Precache( void ); + float MaxYawSpeed( void ); + int Classify( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + CUSTOM_SCHEDULES; + + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + Schedule_t *GetSchedule( void ); + Schedule_t *GetScheduleOfType ( int Type ); + + void Killed( entvars_t *pevAttacker, int iGib ); + void BecomeDead( void ); + + void EXPORT CombatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT BiteTouch( CBaseEntity *pOther ); + + void StartTask( Task_t *pTask ); + void RunTask( Task_t *pTask ); + + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + + float ChangeYaw( int speed ); + Activity GetStoppedActivity( void ); + + void Move( float flInterval ); + void MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ); + void MonsterThink( void ); + void Stop( void ); + void Swim( void ); + Vector DoProbe(const Vector &Probe); + + float VectorToPitch( const Vector &vec); + float FlPitchDiff( void ); + float ChangePitch( int speed ); + + Vector m_SaveVelocity; + float m_idealDist; + + float m_flBlink; + + float m_flEnemyTouched; + BOOL m_bOnAttack; + + float m_flMaxSpeed; + float m_flMinSpeed; + float m_flMaxDist; + + CBeam *m_pBeam; + + float m_flNextAlert; + + static const char *pIdleSounds[]; + static const char *pAlertSounds[]; + static const char *pAttackSounds[]; + static const char *pBiteSounds[]; + static const char *pDieSounds[]; + static const char *pPainSounds[]; + + void IdleSound( void ); + void AlertSound( void ); + void AttackSound( void ); + void BiteSound( void ); + void DeathSound( void ); + void PainSound( void ); +}; + +LINK_ENTITY_TO_CLASS( monster_ichthyosaur, CIchthyosaur ); + +TYPEDESCRIPTION CIchthyosaur::m_SaveData[] = +{ + DEFINE_FIELD( CIchthyosaur, m_SaveVelocity, FIELD_VECTOR ), + DEFINE_FIELD( CIchthyosaur, m_idealDist, FIELD_FLOAT ), + DEFINE_FIELD( CIchthyosaur, m_flBlink, FIELD_FLOAT ), + DEFINE_FIELD( CIchthyosaur, m_flEnemyTouched, FIELD_FLOAT ), + DEFINE_FIELD( CIchthyosaur, m_bOnAttack, FIELD_BOOLEAN ), + DEFINE_FIELD( CIchthyosaur, m_flMaxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CIchthyosaur, m_flMinSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CIchthyosaur, m_flMaxDist, FIELD_FLOAT ), + DEFINE_FIELD( CIchthyosaur, m_flNextAlert, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CIchthyosaur, CFlyingMonster ); + + +const char *CIchthyosaur::pIdleSounds[] = +{ + "ichy/ichy_idle1.wav", + "ichy/ichy_idle2.wav", + "ichy/ichy_idle3.wav", + "ichy/ichy_idle4.wav", +}; + +const char *CIchthyosaur::pAlertSounds[] = +{ + "ichy/ichy_alert2.wav", + "ichy/ichy_alert3.wav", +}; + +const char *CIchthyosaur::pAttackSounds[] = +{ + "ichy/ichy_attack1.wav", + "ichy/ichy_attack2.wav", +}; + +const char *CIchthyosaur::pBiteSounds[] = +{ + "ichy/ichy_bite1.wav", + "ichy/ichy_bite2.wav", +}; + +const char *CIchthyosaur::pPainSounds[] = +{ + "ichy/ichy_pain2.wav", + "ichy/ichy_pain3.wav", + "ichy/ichy_pain5.wav", +}; + +const char *CIchthyosaur::pDieSounds[] = +{ + "ichy/ichy_die2.wav", + "ichy/ichy_die4.wav", +}; + +#define EMIT_ICKY_SOUND( chan, array ) \ + EMIT_SOUND_DYN ( ENT(pev), chan , array [ RANDOM_LONG(0,ARRAYSIZE( array )-1) ], 1.0, 0.6, 0, RANDOM_LONG(95,105) ); + + +void CIchthyosaur :: IdleSound( void ) +{ + EMIT_ICKY_SOUND( CHAN_VOICE, pIdleSounds ); +} + +void CIchthyosaur :: AlertSound( void ) +{ + EMIT_ICKY_SOUND( CHAN_VOICE, pAlertSounds ); +} + +void CIchthyosaur :: AttackSound( void ) +{ + EMIT_ICKY_SOUND( CHAN_VOICE, pAttackSounds ); +} + +void CIchthyosaur :: BiteSound( void ) +{ + EMIT_ICKY_SOUND( CHAN_WEAPON, pBiteSounds ); +} + +void CIchthyosaur :: DeathSound( void ) +{ + EMIT_ICKY_SOUND( CHAN_VOICE, pDieSounds ); +} + +void CIchthyosaur :: PainSound( void ) +{ + EMIT_ICKY_SOUND( CHAN_VOICE, pPainSounds ); +} + +//========================================================= +// monster-specific tasks and states +//========================================================= +enum +{ + TASK_ICHTHYOSAUR_CIRCLE_ENEMY = LAST_COMMON_TASK + 1, + TASK_ICHTHYOSAUR_SWIM, + TASK_ICHTHYOSAUR_FLOAT, +}; + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +static Task_t tlSwimAround[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_WALK }, + { TASK_ICHTHYOSAUR_SWIM, 0.0 }, +}; + +static Schedule_t slSwimAround[] = +{ + { + tlSwimAround, + ARRAYSIZE(tlSwimAround), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND, + bits_SOUND_PLAYER | + bits_SOUND_COMBAT, + "SwimAround" + }, +}; + +static Task_t tlSwimAgitated[] = +{ + { TASK_STOP_MOVING, (float) 0 }, + { TASK_SET_ACTIVITY, (float)ACT_RUN }, + { TASK_WAIT, (float)2.0 }, +}; + +static Schedule_t slSwimAgitated[] = +{ + { + tlSwimAgitated, + ARRAYSIZE(tlSwimAgitated), + 0, + 0, + "SwimAgitated" + }, +}; + + +static Task_t tlCircleEnemy[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_WALK }, + { TASK_ICHTHYOSAUR_CIRCLE_ENEMY, 0.0 }, +}; + +static Schedule_t slCircleEnemy[] = +{ + { + tlCircleEnemy, + ARRAYSIZE(tlCircleEnemy), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK1, + 0, + "CircleEnemy" + }, +}; + + +Task_t tlTwitchDie[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SOUND_DIE, (float)0 }, + { TASK_DIE, (float)0 }, + { TASK_ICHTHYOSAUR_FLOAT, (float)0 }, +}; + +Schedule_t slTwitchDie[] = +{ + { + tlTwitchDie, + ARRAYSIZE( tlTwitchDie ), + 0, + 0, + "Die" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES(CIchthyosaur) +{ + slSwimAround, + slSwimAgitated, + slCircleEnemy, + slTwitchDie, +}; +IMPLEMENT_CUSTOM_SCHEDULES(CIchthyosaur, CFlyingMonster); + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CIchthyosaur :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MONSTER; +} + + +//========================================================= +// CheckMeleeAttack1 +//========================================================= +BOOL CIchthyosaur :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + if ( flDot >= 0.7 && m_flEnemyTouched > gpGlobals->time - 0.2 ) + { + return TRUE; + } + return FALSE; +} + +void CIchthyosaur::BiteTouch( CBaseEntity *pOther ) +{ + // bite if we hit who we want to eat + if ( pOther == m_hEnemy ) + { + m_flEnemyTouched = gpGlobals->time; + m_bOnAttack = TRUE; + } +} + +void CIchthyosaur::CombatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_bOnAttack ) ) + return; + + if (m_bOnAttack) + { + m_bOnAttack = 0; + } + else + { + m_bOnAttack = 1; + } +} + +//========================================================= +// CheckRangeAttack1 - swim in for a chomp +// +//========================================================= +BOOL CIchthyosaur :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( flDot > -0.7 && (m_bOnAttack || ( flDist <= 192 && m_idealDist <= 192))) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// MaxYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +float CIchthyosaur :: MaxYawSpeed( void ) +{ + return 100.0f; +} + + + +//========================================================= +// Killed - overrides CFlyingMonster. +// +void CIchthyosaur :: Killed( entvars_t *pevAttacker, int iGib ) +{ + CBaseMonster::Killed( pevAttacker, iGib ); + pev->velocity = Vector( 0, 0, 0 ); +} + +void CIchthyosaur::BecomeDead( void ) +{ + pev->takedamage = DAMAGE_YES;// don't let autoaim aim at corpses. + + // give the corpse half of the monster's original maximum health. + pev->health = pev->max_health / 2; + pev->max_health = 5; // max_health now becomes a counter for how many blood decals the corpse can place. +} + +#define ICHTHYOSAUR_AE_SHAKE_RIGHT 1 +#define ICHTHYOSAUR_AE_SHAKE_LEFT 2 + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CIchthyosaur :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + int bDidAttack = FALSE; + switch( pEvent->event ) + { + case ICHTHYOSAUR_AE_SHAKE_RIGHT: + case ICHTHYOSAUR_AE_SHAKE_LEFT: + { + if (m_hEnemy != NULL && FVisible( m_hEnemy )) + { + CBaseEntity *pHurt = m_hEnemy; + + if (m_flEnemyTouched < gpGlobals->time - 0.2 && (m_hEnemy->BodyTarget( pev->origin ) - pev->origin).Length() > (32+16+32)) + break; + + Vector vecShootDir = ShootAtEnemy( pev->origin ); + UTIL_MakeAimVectors ( pev->angles ); + + if (DotProduct( vecShootDir, gpGlobals->v_forward ) > 0.707) + { + m_bOnAttack = TRUE; + pHurt->pev->punchangle.z = -18; + pHurt->pev->punchangle.x = 5; + pHurt->pev->velocity = pHurt->pev->velocity - gpGlobals->v_right * 300; + if (pHurt->IsPlayer()) + { + pHurt->pev->angles.x += RANDOM_FLOAT( -35, 35 ); + pHurt->pev->angles.y += RANDOM_FLOAT( -90, 90 ); + pHurt->pev->angles.z = 0; + pHurt->pev->fixangle = TRUE; + } + pHurt->TakeDamage( pev, pev, gSkillData.ichthyosaurDmgShake, DMG_SLASH ); + } + } + BiteSound(); + + bDidAttack = TRUE; + } + break; + default: + CFlyingMonster::HandleAnimEvent( pEvent ); + break; + } + + if (bDidAttack) + { + Vector vecSrc = pev->origin + gpGlobals->v_forward * 32; + UTIL_Bubbles( vecSrc - Vector( 8, 8, 8 ), vecSrc + Vector( 8, 8, 8 ), 16 ); + } +} + +//========================================================= +// Spawn +//========================================================= +void CIchthyosaur :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/icky.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, -32 ), Vector( 32, 32, 32 ) ); + + pev->solid = SOLID_BBOX; + pev->movetype = MOVETYPE_FLY; + m_bloodColor = BLOOD_COLOR_GREEN; + if (pev->health == 0) + pev->health = gSkillData.ichthyosaurHealth; + pev->view_ofs = Vector ( 0, 0, 16 ); + m_flFieldOfView = VIEW_FIELD_WIDE; + m_MonsterState = MONSTERSTATE_NONE; + SetBits(pev->flags, FL_SWIM); + SetFlyingSpeed( ICHTHYOSAUR_SPEED ); + SetFlyingMomentum( 2.5 ); // Set momentum constant + + m_afCapability = bits_CAP_RANGE_ATTACK1 | bits_CAP_SWIM; + + MonsterInit(); + + SetTouch(&CIchthyosaur :: BiteTouch ); + SetUse(&CIchthyosaur :: CombatUse ); + + m_idealDist = 384; + m_flMinSpeed = 80; + m_flMaxSpeed = 300; + m_flMaxDist = 384; + + Vector Forward; + UTIL_MakeVectorsPrivate(pev->angles, Forward, 0, 0); + pev->velocity = m_flightSpeed * Forward.Normalize(); + m_SaveVelocity = pev->velocity; +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CIchthyosaur :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/icky.mdl"); + + PRECACHE_SOUND_ARRAY( pIdleSounds ); + PRECACHE_SOUND_ARRAY( pAlertSounds ); + PRECACHE_SOUND_ARRAY( pAttackSounds ); + PRECACHE_SOUND_ARRAY( pBiteSounds ); + PRECACHE_SOUND_ARRAY( pDieSounds ); + PRECACHE_SOUND_ARRAY( pPainSounds ); +} + +//========================================================= +// GetSchedule +//========================================================= +Schedule_t* CIchthyosaur::GetSchedule() +{ + // ALERT( at_console, "GetSchedule( )\n" ); + switch(m_MonsterState) + { + case MONSTERSTATE_IDLE: + m_flightSpeed = 80; + return GetScheduleOfType( SCHED_IDLE_WALK ); + + case MONSTERSTATE_ALERT: + m_flightSpeed = 150; + return GetScheduleOfType( SCHED_IDLE_WALK ); + + case MONSTERSTATE_COMBAT: + m_flMaxSpeed = 400; + // eat them + if ( HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + return GetScheduleOfType( SCHED_MELEE_ATTACK1 ); + } + // chase them down and eat them + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType( SCHED_CHASE_ENEMY ); + } + if ( HasConditions( bits_COND_HEAVY_DAMAGE ) ) + { + m_bOnAttack = TRUE; + } + if ( pev->health < pev->max_health - 20 ) + { + m_bOnAttack = TRUE; + } + + return GetScheduleOfType( SCHED_STANDOFF ); + } + + return CFlyingMonster :: GetSchedule(); +} + + +//========================================================= +//========================================================= +Schedule_t* CIchthyosaur :: GetScheduleOfType ( int Type ) +{ + // ALERT( at_console, "GetScheduleOfType( %d ) %d\n", Type, m_bOnAttack ); + switch ( Type ) + { + case SCHED_IDLE_WALK: + return slSwimAround; + case SCHED_STANDOFF: + return slCircleEnemy; + case SCHED_FAIL: + return slSwimAgitated; + case SCHED_DIE: + return slTwitchDie; + case SCHED_CHASE_ENEMY: + AttackSound( ); + } + + return CBaseMonster :: GetScheduleOfType( Type ); +} + + + +//========================================================= +// Start task - selects the correct activity and performs +// any necessary calculations to start the next task on the +// schedule. +//========================================================= +void CIchthyosaur::StartTask(Task_t *pTask) +{ + switch (pTask->iTask) + { + case TASK_ICHTHYOSAUR_CIRCLE_ENEMY: + break; + case TASK_ICHTHYOSAUR_SWIM: + break; + case TASK_SMALL_FLINCH: + if (m_idealDist > 128) + { + m_flMaxDist = 512; + m_idealDist = 512; + } + else + { + m_bOnAttack = TRUE; + } + CFlyingMonster::StartTask(pTask); + break; + + case TASK_ICHTHYOSAUR_FLOAT: + pev->skin = EYE_BASE; + SetSequenceByName( "bellyup" ); + break; + + default: + CFlyingMonster::StartTask(pTask); + break; + } +} + +void CIchthyosaur :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_ICHTHYOSAUR_CIRCLE_ENEMY: + if (m_hEnemy == NULL) + { + TaskComplete( ); + } + else if (FVisible( m_hEnemy )) + { + Vector vecFrom = m_hEnemy->EyePosition( ); + + Vector vecDelta = (pev->origin - vecFrom).Normalize( ); + Vector vecSwim = CrossProduct( vecDelta, Vector( 0, 0, 1 ) ).Normalize( ); + + if (DotProduct( vecSwim, m_SaveVelocity ) < 0) + vecSwim = vecSwim * -1.0; + + Vector vecPos = vecFrom + vecDelta * m_idealDist + vecSwim * 32; + + // ALERT( at_console, "vecPos %.0f %.0f %.0f\n", vecPos.x, vecPos.y, vecPos.z ); + + TraceResult tr; + + UTIL_TraceHull( vecFrom, vecPos, ignore_monsters, large_hull, m_hEnemy->edict(), &tr ); + + if (tr.flFraction > 0.5) + vecPos = tr.vecEndPos; + + m_SaveVelocity = m_SaveVelocity * 0.8 + 0.2 * (vecPos - pev->origin).Normalize() * m_flightSpeed; + + // ALERT( at_console, "m_SaveVelocity %.2f %.2f %.2f\n", m_SaveVelocity.x, m_SaveVelocity.y, m_SaveVelocity.z ); + + if (HasConditions( bits_COND_ENEMY_FACING_ME ) && m_hEnemy->FVisible( this )) + { + m_flNextAlert -= 0.1; + + if (m_idealDist < m_flMaxDist) + { + m_idealDist += 4; + } + if (m_flightSpeed > m_flMinSpeed) + { + m_flightSpeed -= 2; + } + else if (m_flightSpeed < m_flMinSpeed) + { + m_flightSpeed += 2; + } + if (m_flMinSpeed < m_flMaxSpeed) + { + m_flMinSpeed += 0.5; + } + } + else + { + m_flNextAlert += 0.1; + + if (m_idealDist > 128) + { + m_idealDist -= 4; + } + if (m_flightSpeed < m_flMaxSpeed) + { + m_flightSpeed += 4; + } + } + // ALERT( at_console, "%.0f\n", m_idealDist ); + } + else + { + m_flNextAlert = gpGlobals->time + 0.2; + } + + if (m_flNextAlert < gpGlobals->time) + { + // ALERT( at_console, "AlertSound()\n"); + AlertSound( ); + m_flNextAlert = gpGlobals->time + RANDOM_FLOAT( 3, 5 ); + } + + break; + case TASK_ICHTHYOSAUR_SWIM: + if (m_fSequenceFinished) + { + TaskComplete( ); + } + break; + case TASK_DIE: + if ( m_fSequenceFinished ) + { + pev->deadflag = DEAD_DEAD; + + TaskComplete( ); + } + break; + + case TASK_ICHTHYOSAUR_FLOAT: + pev->angles.x = UTIL_ApproachAngle( 0, pev->angles.x, 20 ); + pev->velocity = pev->velocity * 0.8; + if (pev->waterlevel > 1 && pev->watertype != CONTENTS_FOG && pev->velocity.z < 64) + { + pev->velocity.z += 8; + } + else + { + pev->velocity.z -= 8; + } + // ALERT( at_console, "%f\n", pev->velocity.z ); + break; + + default: + CFlyingMonster :: RunTask ( pTask ); + break; + } +} + + + +float CIchthyosaur::VectorToPitch( const Vector &vec ) +{ + float pitch; + if (vec.z == 0 && vec.x == 0) + pitch = 0; + else + { + pitch = (int) (atan2(vec.z, sqrt(vec.x*vec.x+vec.y*vec.y)) * 180 / M_PI); + if (pitch < 0) + pitch += 360; + } + return pitch; +} + +//========================================================= +void CIchthyosaur::Move(float flInterval) +{ + CFlyingMonster::Move( flInterval ); +} + +float CIchthyosaur::FlPitchDiff( void ) +{ + float flPitchDiff; + float flCurrentPitch; + + flCurrentPitch = UTIL_AngleMod( pev->angles.z ); + + if ( flCurrentPitch == pev->idealpitch ) + { + return 0; + } + + flPitchDiff = pev->idealpitch - flCurrentPitch; + + if ( pev->idealpitch > flCurrentPitch ) + { + if (flPitchDiff >= 180) + flPitchDiff = flPitchDiff - 360; + } + else + { + if (flPitchDiff <= -180) + flPitchDiff = flPitchDiff + 360; + } + return flPitchDiff; +} + +float CIchthyosaur :: ChangePitch( int speed ) +{ + if ( pev->movetype == MOVETYPE_FLY ) + { + float diff = FlPitchDiff(); + float target = 0; + if ( m_IdealActivity != GetStoppedActivity() ) + { + if (diff < -20) + target = 45; + else if (diff > 20) + target = -45; + } + pev->angles.x = UTIL_Approach(target, pev->angles.x, 220.0 * 0.1 ); + } + return 0; +} + +float CIchthyosaur::ChangeYaw( int speed ) +{ + if ( pev->movetype == MOVETYPE_FLY ) + { + float diff = DeltaIdealYaw(); + float target = 0; + + if ( m_IdealActivity != GetStoppedActivity() ) + { + if ( diff < -20 ) + target = 20; + else if ( diff > 20 ) + target = -20; + } + pev->angles.z = UTIL_Approach( target, pev->angles.z, 220.0 * 0.1 ); + } + return CFlyingMonster::ChangeYaw( speed ); +} + + +Activity CIchthyosaur:: GetStoppedActivity( void ) +{ + if ( pev->movetype != MOVETYPE_FLY ) // UNDONE: Ground idle here, IDLE may be something else + return ACT_IDLE; + return ACT_WALK; +} + +void CIchthyosaur::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ) +{ + m_SaveVelocity = vecDir * m_flightSpeed; +} + + +void CIchthyosaur::MonsterThink ( void ) +{ + CFlyingMonster::MonsterThink( ); + + if (pev->deadflag == DEAD_NO) + { + if (m_MonsterState != MONSTERSTATE_SCRIPT) + { + Swim( ); + + // blink the eye + if (m_flBlink < gpGlobals->time) + { + pev->skin = EYE_CLOSED; + if (m_flBlink + 0.2 < gpGlobals->time) + { + m_flBlink = gpGlobals->time + RANDOM_FLOAT( 3, 4 ); + if (m_bOnAttack) + pev->skin = EYE_MAD; + else + pev->skin = EYE_BASE; + } + } + } + } +} + +void CIchthyosaur :: Stop( void ) +{ + if (!m_bOnAttack) + m_flightSpeed = 80.0; +} + +void CIchthyosaur::Swim( ) +{ + int retValue = 0; + + Vector start = pev->origin; + + Vector Angles; + Vector Forward, Right, Up; + + if (FBitSet( pev->flags, FL_ONGROUND)) + { + pev->angles.x = 0; + pev->angles.y += RANDOM_FLOAT( -45, 45 ); + ClearBits( pev->flags, FL_ONGROUND ); + + Angles = Vector( -pev->angles.x, pev->angles.y, pev->angles.z ); + UTIL_MakeVectorsPrivate(Angles, Forward, Right, Up); + + pev->velocity = Forward * 200 + Up * 200; + + return; + } + + if (m_bOnAttack && m_flightSpeed < m_flMaxSpeed) + { + m_flightSpeed += 40; + } + if (m_flightSpeed < 180) + { + if (m_IdealActivity == ACT_RUN) + SetActivity( ACT_WALK ); + if (m_IdealActivity == ACT_WALK) + pev->framerate = m_flightSpeed / 150.0; + // ALERT( at_console, "walk %.2f\n", pev->framerate ); + } + else + { + if (m_IdealActivity == ACT_WALK) + SetActivity( ACT_RUN ); + if (m_IdealActivity == ACT_RUN) + pev->framerate = m_flightSpeed / 150.0; + // ALERT( at_console, "run %.2f\n", pev->framerate ); + } + +/* + if (!m_pBeam) + { + m_pBeam = CBeam::BeamCreate( "sprites/laserbeam.spr", 80 ); + m_pBeam->PointEntInit( pev->origin + m_SaveVelocity, entindex( ) ); + m_pBeam->SetEndAttachment( 1 ); + m_pBeam->SetColor( 255, 180, 96 ); + m_pBeam->SetBrightness( 192 ); + } +*/ +#define PROBE_LENGTH 150 + Angles = UTIL_VecToAngles( m_SaveVelocity ); + Angles.x = -Angles.x; + UTIL_MakeVectorsPrivate(Angles, Forward, Right, Up); + + Vector f, u, l, r, d; + f = DoProbe(start + PROBE_LENGTH * Forward); + r = DoProbe(start + PROBE_LENGTH/3 * Forward+Right); + l = DoProbe(start + PROBE_LENGTH/3 * Forward-Right); + u = DoProbe(start + PROBE_LENGTH/3 * Forward+Up); + d = DoProbe(start + PROBE_LENGTH/3 * Forward-Up); + + Vector SteeringVector = f+r+l+u+d; + m_SaveVelocity = (m_SaveVelocity + SteeringVector/2).Normalize(); + + Angles = Vector( -pev->angles.x, pev->angles.y, pev->angles.z ); + UTIL_MakeVectorsPrivate(Angles, Forward, Right, Up); + // ALERT( at_console, "%f : %f\n", Angles.x, Forward.z ); + + float flDot = DotProduct( Forward, m_SaveVelocity ); + if (flDot > 0.5) + pev->velocity = m_SaveVelocity = m_SaveVelocity * m_flightSpeed; + else if (flDot > 0) + pev->velocity = m_SaveVelocity = m_SaveVelocity * m_flightSpeed * (flDot + 0.5); + else + pev->velocity = m_SaveVelocity = m_SaveVelocity * 80; + + // ALERT( at_console, "%.0f %.0f\n", m_flightSpeed, pev->velocity.Length() ); + + + // ALERT( at_console, "Steer %f %f %f\n", SteeringVector.x, SteeringVector.y, SteeringVector.z ); + +/* + m_pBeam->SetStartPos( pev->origin + pev->velocity ); + m_pBeam->RelinkBeam( ); +*/ + + // ALERT( at_console, "speed %f\n", m_flightSpeed ); + + Angles = UTIL_VecToAngles( m_SaveVelocity ); + + // Smooth Pitch + // + if (Angles.x > 180) + Angles.x = Angles.x - 360; + pev->angles.x = UTIL_Approach(Angles.x, pev->angles.x, 50 * 0.1 ); + if (pev->angles.x < -80) pev->angles.x = -80; + if (pev->angles.x > 80) pev->angles.x = 80; + + // Smooth Yaw and generate Roll + // + float turn = 360; + // ALERT( at_console, "Y %.0f %.0f\n", Angles.y, pev->angles.y ); + + if (fabs(Angles.y - pev->angles.y) < fabs(turn)) + { + turn = Angles.y - pev->angles.y; + } + if (fabs(Angles.y - pev->angles.y + 360) < fabs(turn)) + { + turn = Angles.y - pev->angles.y + 360; + } + if (fabs(Angles.y - pev->angles.y - 360) < fabs(turn)) + { + turn = Angles.y - pev->angles.y - 360; + } + + float speed = m_flightSpeed * 0.1; + + // ALERT( at_console, "speed %.0f %f\n", turn, speed ); + if (fabs(turn) > speed) + { + if (turn < 0.0) + { + turn = -speed; + } + else + { + turn = speed; + } + } + pev->angles.y += turn; + pev->angles.z -= turn; + pev->angles.y = fmod((pev->angles.y + 360.0), 360.0); + + static float yaw_adj; + + yaw_adj = yaw_adj * 0.8 + turn; + + // ALERT( at_console, "yaw %f : %f\n", turn, yaw_adj ); + + SetBoneController( 0, -yaw_adj / 4.0 ); + + // Roll Smoothing + // + turn = 360; + if (fabs(Angles.z - pev->angles.z) < fabs(turn)) + { + turn = Angles.z - pev->angles.z; + } + if (fabs(Angles.z - pev->angles.z + 360) < fabs(turn)) + { + turn = Angles.z - pev->angles.z + 360; + } + if (fabs(Angles.z - pev->angles.z - 360) < fabs(turn)) + { + turn = Angles.z - pev->angles.z - 360; + } + speed = m_flightSpeed/2 * 0.1; + if (fabs(turn) < speed) + { + pev->angles.z += turn; + } + else + { + if (turn < 0.0) + { + pev->angles.z -= speed; + } + else + { + pev->angles.z += speed; + } + } + if (pev->angles.z < -20) pev->angles.z = -20; + if (pev->angles.z > 20) pev->angles.z = 20; + + UTIL_MakeVectorsPrivate( Vector( -Angles.x, Angles.y, Angles.z ), Forward, Right, Up); + + // UTIL_MoveToOrigin ( ENT(pev), pev->origin + Forward * speed, speed, MOVE_STRAFE ); +} + + +Vector CIchthyosaur::DoProbe(const Vector &Probe) +{ + Vector WallNormal = Vector(0,0,-1); // WATER normal is Straight Down for fish. + float frac; + BOOL bBumpedSomething = ProbeZ(pev->origin, Probe, &frac); + + TraceResult tr; + TRACE_MONSTER_HULL(edict(), pev->origin, Probe, dont_ignore_monsters, edict(), &tr); + if ( tr.fAllSolid || tr.flFraction < 0.99 ) + { + if (tr.flFraction < 0.0) tr.flFraction = 0.0; + if (tr.flFraction > 1.0) tr.flFraction = 1.0; + if (tr.flFraction < frac) + { + frac = tr.flFraction; + bBumpedSomething = TRUE; + WallNormal = tr.vecPlaneNormal; + } + } + + if (bBumpedSomething && (m_hEnemy == NULL || tr.pHit != m_hEnemy->edict())) + { + Vector ProbeDir = Probe - pev->origin; + + Vector NormalToProbeAndWallNormal = CrossProduct(ProbeDir, WallNormal); + Vector SteeringVector = CrossProduct( NormalToProbeAndWallNormal, ProbeDir); + + float SteeringForce = m_flightSpeed * (1-frac) * (DotProduct(WallNormal.Normalize(), m_SaveVelocity.Normalize())); + if (SteeringForce < 0.0) + { + SteeringForce = -SteeringForce; + } + SteeringVector = SteeringForce * SteeringVector.Normalize(); + + return SteeringVector; + } + return Vector(0, 0, 0); +} + +#endif diff --git a/dlls/islave.cpp b/dlls/islave.cpp new file mode 100644 index 0000000..7e418c2 --- /dev/null +++ b/dlls/islave.cpp @@ -0,0 +1,875 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Alien slave monster +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "squadmonster.h" +#include "schedule.h" +#include "effects.h" +#include "weapons.h" +#include "soundent.h" + +extern DLL_GLOBAL int g_iSkillLevel; + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define ISLAVE_AE_CLAW ( 1 ) +#define ISLAVE_AE_CLAWRAKE ( 2 ) +#define ISLAVE_AE_ZAP_POWERUP ( 3 ) +#define ISLAVE_AE_ZAP_SHOOT ( 4 ) +#define ISLAVE_AE_ZAP_DONE ( 5 ) + +#define ISLAVE_MAX_BEAMS 8 + +class CISlave : public CSquadMonster +{ +public: + void Spawn( void ); + void Precache( void ); + float MaxYawSpeed( void ); + int ISoundMask( void ); + int Classify ( void ); + int IRelationship( CBaseEntity *pTarget ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack2 ( float flDot, float flDist ); + void CallForHelp( char *szClassname, float flDist, EHANDLE hEnemy, Vector &vecLocation ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); + + void DeathSound( void ); + void PainSound( void ); + void AlertSound( void ); + void IdleSound( void ); + + void Killed( entvars_t *pevAttacker, int iGib ); + + void StartTask ( Task_t *pTask ); + Schedule_t *GetSchedule( void ); + Schedule_t *GetScheduleOfType ( int Type ); + CUSTOM_SCHEDULES; + + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void ClearBeams( ); + void ArmBeam( int side ); + void WackBeam( int side, CBaseEntity *pEntity ); + void ZapBeam( int side ); + void BeamGlow( void ); + + int m_iBravery; + + CBeam *m_pBeam[ISLAVE_MAX_BEAMS]; + + int m_iBeams; + float m_flNextAttack; + + int m_voicePitch; + + EHANDLE m_hDead; + + static const char *pAttackHitSounds[]; + static const char *pAttackMissSounds[]; + static const char *pPainSounds[]; + static const char *pDeathSounds[]; +}; +LINK_ENTITY_TO_CLASS( monster_alien_slave, CISlave ); +LINK_ENTITY_TO_CLASS( monster_vortigaunt, CISlave ); + + +TYPEDESCRIPTION CISlave::m_SaveData[] = +{ + DEFINE_FIELD( CISlave, m_iBravery, FIELD_INTEGER ), + + DEFINE_ARRAY( CISlave, m_pBeam, FIELD_CLASSPTR, ISLAVE_MAX_BEAMS ), + DEFINE_FIELD( CISlave, m_iBeams, FIELD_INTEGER ), + DEFINE_FIELD( CISlave, m_flNextAttack, FIELD_TIME ), + + DEFINE_FIELD( CISlave, m_voicePitch, FIELD_INTEGER ), + + DEFINE_FIELD( CISlave, m_hDead, FIELD_EHANDLE ), + +}; + +IMPLEMENT_SAVERESTORE( CISlave, CSquadMonster ); + + + + +const char *CISlave::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CISlave::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CISlave::pPainSounds[] = +{ + "aslave/slv_pain1.wav", + "aslave/slv_pain2.wav", +}; + +const char *CISlave::pDeathSounds[] = +{ + "aslave/slv_die1.wav", + "aslave/slv_die2.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CISlave :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MILITARY; +} + + +int CISlave::IRelationship( CBaseEntity *pTarget ) +{ + if ( (pTarget->IsPlayer()) ) + if ( (pev->spawnflags & SF_MONSTER_WAIT_UNTIL_PROVOKED ) && ! (m_afMemory & bits_MEMORY_PROVOKED )) + return R_NO; + return CBaseMonster::IRelationship( pTarget ); +} + + +void CISlave :: CallForHelp( char *szClassname, float flDist, EHANDLE hEnemy, Vector &vecLocation ) +{ + // ALERT( at_aiconsole, "help " ); + + // skip ones not on my netname + if ( FStringNull( pev->netname )) + return; + + CBaseEntity *pEntity = NULL; + + while ((pEntity = UTIL_FindEntityByString( pEntity, "netname", STRING( pev->netname ))) != NULL) + { + float d = (pev->origin - pEntity->pev->origin).Length(); + if (d < flDist) + { + CBaseMonster *pMonster = pEntity->MyMonsterPointer( ); + if (pMonster) + { + pMonster->m_afMemory |= bits_MEMORY_PROVOKED; + pMonster->PushEnemy( hEnemy, vecLocation ); + } + } + } +} + + +//========================================================= +// ALertSound - scream +//========================================================= +void CISlave :: AlertSound( void ) +{ + if ( m_hEnemy != NULL ) + { + SENTENCEG_PlayRndSz(ENT(pev), "SLV_ALERT", 0.85, ATTN_NORM, 0, m_voicePitch); + + CallForHelp( "monster_alien_slave", 512, m_hEnemy, m_vecEnemyLKP ); + } +} + +//========================================================= +// IdleSound +//========================================================= +void CISlave :: IdleSound( void ) +{ + if (RANDOM_LONG( 0, 2 ) == 0) + { + SENTENCEG_PlayRndSz(ENT(pev), "SLV_IDLE", 0.85, ATTN_NORM, 0, m_voicePitch); + } + +#if 0 + int side = RANDOM_LONG( 0, 1 ) * 2 - 1; + + ClearBeams( ); + ArmBeam( side ); + + UTIL_MakeAimVectors( pev->angles ); + Vector vecSrc = pev->origin + gpGlobals->v_right * 2 * side; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSrc ); + WRITE_BYTE(TE_DLIGHT); + WRITE_COORD(vecSrc.x); // X + WRITE_COORD(vecSrc.y); // Y + WRITE_COORD(vecSrc.z); // Z + WRITE_BYTE( 8 ); // radius * 0.1 + WRITE_BYTE( 255 ); // r + WRITE_BYTE( 180 ); // g + WRITE_BYTE( 96 ); // b + WRITE_BYTE( 10 ); // time * 10 + WRITE_BYTE( 0 ); // decay * 0.1 + MESSAGE_END( ); + + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "debris/zap1.wav", 1, ATTN_NORM, 0, 100 ); +#endif +} + +//========================================================= +// PainSound +//========================================================= +void CISlave :: PainSound( void ) +{ + if (RANDOM_LONG( 0, 2 ) == 0) + { + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pPainSounds[ RANDOM_LONG(0,ARRAYSIZE(pPainSounds)-1) ], 1.0, ATTN_NORM, 0, m_voicePitch ); + } +} + +//========================================================= +// DieSound +//========================================================= + +void CISlave :: DeathSound( void ) +{ + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pDeathSounds[ RANDOM_LONG(0,ARRAYSIZE(pDeathSounds)-1) ], 1.0, ATTN_NORM, 0, m_voicePitch ); +} + + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. +//========================================================= +int CISlave :: ISoundMask ( void) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_DANGER | + bits_SOUND_PLAYER; +} + + +void CISlave::Killed( entvars_t *pevAttacker, int iGib ) +{ + ClearBeams( ); + CSquadMonster::Killed( pevAttacker, iGib ); +} + +//========================================================= +// MaxYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +float CISlave :: MaxYawSpeed( void ) +{ + float ys; + + switch ( m_Activity ) + { + case ACT_WALK: + ys = 50; + break; + case ACT_RUN: + ys = 70; + break; + case ACT_IDLE: + ys = 50; + break; + default: + ys = 90; + break; + } + + return ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CISlave :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + // ALERT( at_console, "event %d : %f\n", pEvent->event, pev->frame ); + switch( pEvent->event ) + { + case ISLAVE_AE_CLAW: + { + // SOUND HERE! + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.slaveDmgClaw, DMG_SLASH ); + if ( pHurt ) + { + if ( pHurt->pev->flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->pev->punchangle.z = -18; + pHurt->pev->punchangle.x = 5; + } + // Play a random attack hit sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, m_voicePitch ); + } + else + { + // Play a random attack miss sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, m_voicePitch ); + } + } + break; + + case ISLAVE_AE_CLAWRAKE: + { + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.slaveDmgClawrake, DMG_SLASH ); + if ( pHurt ) + { + if ( pHurt->pev->flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->pev->punchangle.z = -18; + pHurt->pev->punchangle.x = 5; + } + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, m_voicePitch ); + } + else + { + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, m_voicePitch ); + } + } + break; + + case ISLAVE_AE_ZAP_POWERUP: + { + // speed up attack when on hard + if (g_iSkillLevel == SKILL_HARD) + pev->framerate = 1.5; + + UTIL_MakeAimVectors( pev->angles ); + + if (m_iBeams == 0) + { + Vector vecSrc = pev->origin + gpGlobals->v_forward * 2; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSrc ); + WRITE_BYTE(TE_DLIGHT); + WRITE_COORD(vecSrc.x); // X + WRITE_COORD(vecSrc.y); // Y + WRITE_COORD(vecSrc.z); // Z + WRITE_BYTE( 12 ); // radius * 0.1 + WRITE_BYTE( 255 ); // r + WRITE_BYTE( 180 ); // g + WRITE_BYTE( 96 ); // b + WRITE_BYTE( 20 / pev->framerate ); // time * 10 + WRITE_BYTE( 0 ); // decay * 0.1 + MESSAGE_END( ); + + } + if (m_hDead != NULL) + { + WackBeam( -1, m_hDead ); + WackBeam( 1, m_hDead ); + } + else + { + ArmBeam( -1 ); + ArmBeam( 1 ); + BeamGlow( ); + } + + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "debris/zap4.wav", 1, ATTN_NORM, 0, 100 + m_iBeams * 10 ); + pev->skin = m_iBeams / 2; + } + break; + + case ISLAVE_AE_ZAP_SHOOT: + { + ClearBeams( ); + + if (m_hDead != NULL) + { + Vector vecDest = m_hDead->pev->origin + Vector( 0, 0, 38 ); + TraceResult trace; + UTIL_TraceHull( vecDest, vecDest, dont_ignore_monsters, human_hull, m_hDead->edict(), &trace ); + + if ( !trace.fStartSolid ) + { + CBaseEntity *pNew = Create( "monster_alien_slave", m_hDead->pev->origin, m_hDead->pev->angles ); + CBaseMonster *pNewMonster = pNew->MyMonsterPointer( ); + pNew->pev->spawnflags |= 1; + WackBeam( -1, pNew ); + WackBeam( 1, pNew ); + UTIL_Remove( m_hDead ); + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "hassault/hw_shoot1.wav", 1, ATTN_NORM, 0, RANDOM_LONG( 130, 160 ) ); + + /* + CBaseEntity *pEffect = Create( "test_effect", pNew->Center(), pev->angles ); + pEffect->Use( this, this, USE_ON, 1 ); + */ + break; + } + } + ClearMultiDamage(); + + UTIL_MakeAimVectors( pev->angles ); + + ZapBeam( -1 ); + ZapBeam( 1 ); + + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "hassault/hw_shoot1.wav", 1, ATTN_NORM, 0, RANDOM_LONG( 130, 160 ) ); + // STOP_SOUND( ENT(pev), CHAN_WEAPON, "debris/zap4.wav" ); + ApplyMultiDamage(pev, pev); + + m_flNextAttack = gpGlobals->time + RANDOM_FLOAT( 0.5, 4.0 ); + } + break; + + case ISLAVE_AE_ZAP_DONE: + { + ClearBeams( ); + } + break; + + default: + CSquadMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// CheckRangeAttack1 - normal beam attack +//========================================================= +BOOL CISlave :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if (m_flNextAttack > gpGlobals->time) + { + return FALSE; + } + + return CSquadMonster::CheckRangeAttack1( flDot, flDist ); +} + +//========================================================= +// CheckRangeAttack2 - check bravery and try to resurect dead comrades +//========================================================= +BOOL CISlave :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + return FALSE; + + if (m_flNextAttack > gpGlobals->time) + { + return FALSE; + } + + m_hDead = NULL; + m_iBravery = 0; + + CBaseEntity *pEntity = NULL; + while ((pEntity = UTIL_FindEntityByClassname( pEntity, "monster_alien_slave" )) != NULL) + { + TraceResult tr; + + UTIL_TraceLine( EyePosition( ), pEntity->EyePosition( ), ignore_monsters, ENT(pev), &tr ); + if (tr.flFraction == 1.0 || tr.pHit == pEntity->edict()) + { + if (pEntity->pev->deadflag == DEAD_DEAD) + { + float d = (pev->origin - pEntity->pev->origin).Length(); + if (d < flDist) + { + m_hDead = pEntity; + flDist = d; + } + m_iBravery--; + } + else + { + m_iBravery++; + } + } + } + if (m_hDead != NULL) + return TRUE; + else + return FALSE; +} + + +//========================================================= +// StartTask +//========================================================= +void CISlave :: StartTask ( Task_t *pTask ) +{ + ClearBeams( ); + + CSquadMonster :: StartTask ( pTask ); +} + + +//========================================================= +// Spawn +//========================================================= +void CISlave :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/islave.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + if (pev->health == 0) + pev->health = gSkillData.slaveHealth; + pev->view_ofs = Vector ( 0, 0, 64 );// position of the eyes relative to monster's origin. + m_flFieldOfView = VIEW_FIELD_WIDE; // NOTE: we need a wide field of view so npc will notice player and say hello + m_MonsterState = MONSTERSTATE_NONE; + m_afCapability = bits_CAP_HEAR | bits_CAP_TURN_HEAD | bits_CAP_RANGE_ATTACK2 | bits_CAP_DOORS_GROUP; + + m_voicePitch = RANDOM_LONG( 85, 110 ); + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CISlave :: Precache() +{ + int i; + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/islave.mdl"); + PRECACHE_MODEL("sprites/lgtning.spr"); + PRECACHE_SOUND("debris/zap1.wav"); + PRECACHE_SOUND("debris/zap4.wav"); + PRECACHE_SOUND("weapons/electro4.wav"); + PRECACHE_SOUND("hassault/hw_shoot1.wav"); + PRECACHE_SOUND("zombie/zo_pain2.wav"); + PRECACHE_SOUND("headcrab/hc_headbite.wav"); + PRECACHE_SOUND("weapons/cbar_miss1.wav"); + + for ( i = 0; i < ARRAYSIZE( pAttackHitSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackHitSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAttackMissSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackMissSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pPainSounds ); i++ ) + PRECACHE_SOUND((char *)pPainSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pDeathSounds ); i++ ) + PRECACHE_SOUND((char *)pDeathSounds[i]); + + UTIL_PrecacheOther( "test_effect" ); +} + + +//========================================================= +// TakeDamage - get provoked when injured +//========================================================= + +int CISlave :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType) +{ + // don't slash one of your own + if ((bitsDamageType & DMG_SLASH) && pevAttacker && IRelationship( Instance(pevAttacker) ) < R_DL) + return 0; + + //LRC - if my player reaction has been overridden, leave this alone + if (m_iPlayerReact == 0) + m_afMemory |= bits_MEMORY_PROVOKED; + + return CSquadMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); +} + + +void CISlave::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if (bitsDamageType & DMG_SHOCK) + return; + + CSquadMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + + + +// primary range attack +Task_t tlSlaveAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slSlaveAttack1[] = +{ + { + tlSlaveAttack1, + ARRAYSIZE ( tlSlaveAttack1 ), + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_HEAR_SOUND | + bits_COND_HEAVY_DAMAGE, + + bits_SOUND_DANGER, + "Slave Range Attack1" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CISlave ) +{ + slSlaveAttack1, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CISlave, CSquadMonster ); + + +//========================================================= +//========================================================= +Schedule_t *CISlave :: GetSchedule( void ) +{ + ClearBeams( ); + +/* + if (pev->spawnflags) + { + pev->spawnflags = 0; + return GetScheduleOfType( SCHED_RELOAD ); + } +*/ + + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + + if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + if ( pSound->m_iType & bits_SOUND_COMBAT ) + m_afMemory |= bits_MEMORY_PROVOKED; + } + + switch (m_MonsterState) + { + case MONSTERSTATE_COMBAT: +// dead enemy + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + + if (pev->health < 20 || m_iBravery < 0) + { + if (!HasConditions( bits_COND_CAN_MELEE_ATTACK1 )) + { + m_failSchedule = SCHED_CHASE_ENEMY; + if (HasConditions( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE)) + { + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + if ( HasConditions ( bits_COND_SEE_ENEMY ) && HasConditions ( bits_COND_ENEMY_FACING_ME ) ) + { + // ALERT( at_console, "exposed\n"); + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + } + } + break; + } + return CSquadMonster::GetSchedule( ); +} + + +Schedule_t *CISlave :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_FAIL: + if (HasConditions( bits_COND_CAN_MELEE_ATTACK1 )) + { + return CSquadMonster :: GetScheduleOfType( SCHED_MELEE_ATTACK1 ); ; + } + break; + case SCHED_RANGE_ATTACK1: + return slSlaveAttack1; + case SCHED_RANGE_ATTACK2: + return slSlaveAttack1; + } + return CSquadMonster :: GetScheduleOfType( Type ); +} + + +//========================================================= +// ArmBeam - small beam from arm to nearby geometry +//========================================================= + +void CISlave :: ArmBeam( int side ) +{ + TraceResult tr; + float flDist = 1.0; + + if (m_iBeams >= ISLAVE_MAX_BEAMS) + return; + + UTIL_MakeAimVectors( pev->angles ); + Vector vecSrc = pev->origin + gpGlobals->v_up * 36 + gpGlobals->v_right * side * 16 + gpGlobals->v_forward * 32; + + for (int i = 0; i < 3; i++) + { + Vector vecAim = gpGlobals->v_right * side * RANDOM_FLOAT( 0, 1 ) + gpGlobals->v_up * RANDOM_FLOAT( -1, 1 ); + TraceResult tr1; + UTIL_TraceLine ( vecSrc, vecSrc + vecAim * 512, dont_ignore_monsters, ENT( pev ), &tr1); + if (flDist > tr1.flFraction) + { + tr = tr1; + flDist = tr.flFraction; + } + } + + // Couldn't find anything close enough + if ( flDist == 1.0 ) + return; + + DecalGunshot( &tr, BULLET_STAB, vecSrc ); + + m_pBeam[m_iBeams] = CBeam::BeamCreate( "sprites/lgtning.spr", 30 ); + if (!m_pBeam[m_iBeams]) + return; + + m_pBeam[m_iBeams]->PointEntInit( tr.vecEndPos, entindex( ) ); + m_pBeam[m_iBeams]->SetEndAttachment( side < 0 ? 2 : 1 ); + // m_pBeam[m_iBeams]->SetColor( 180, 255, 96 ); + m_pBeam[m_iBeams]->SetColor( 240, 96, 16 ); + m_pBeam[m_iBeams]->SetBrightness( 64 ); + m_pBeam[m_iBeams]->SetNoise( 80 ); + m_iBeams++; +} + + +//========================================================= +// BeamGlow - brighten all beams +//========================================================= +void CISlave :: BeamGlow( ) +{ + int b = m_iBeams * 32; + if (b > 255) + b = 255; + + for (int i = 0; i < m_iBeams; i++) + { + if (m_pBeam[i]->GetBrightness() != 255) + { + m_pBeam[i]->SetBrightness( b ); + } + } +} + + +//========================================================= +// WackBeam - regenerate dead colleagues +//========================================================= +void CISlave :: WackBeam( int side, CBaseEntity *pEntity ) +{ + Vector vecDest; + float flDist = 1.0; + + if (m_iBeams >= ISLAVE_MAX_BEAMS) + return; + + if (pEntity == NULL) + return; + + m_pBeam[m_iBeams] = CBeam::BeamCreate( "sprites/lgtning.spr", 30 ); + if (!m_pBeam[m_iBeams]) + return; + + m_pBeam[m_iBeams]->PointEntInit( pEntity->Center(), entindex( ) ); + m_pBeam[m_iBeams]->SetEndAttachment( side < 0 ? 2 : 1 ); + m_pBeam[m_iBeams]->SetColor( 255, 128, 96 ); + m_pBeam[m_iBeams]->SetBrightness( 255 ); + m_pBeam[m_iBeams]->SetNoise( 80 ); + m_iBeams++; +} + +//========================================================= +// ZapBeam - heavy damage directly forward +//========================================================= +void CISlave :: ZapBeam( int side ) +{ + Vector vecSrc, vecAim; + TraceResult tr; + CBaseEntity *pEntity; + + if (m_iBeams >= ISLAVE_MAX_BEAMS) + return; + + vecSrc = pev->origin + gpGlobals->v_up * 36; + vecAim = ShootAtEnemy( vecSrc ); + float deflection = 0.01; + vecAim = vecAim + side * gpGlobals->v_right * RANDOM_FLOAT( 0, deflection ) + gpGlobals->v_up * RANDOM_FLOAT( -deflection, deflection ); + UTIL_TraceLine ( vecSrc, vecSrc + vecAim * 1024, dont_ignore_monsters, ENT( pev ), &tr); + + m_pBeam[m_iBeams] = CBeam::BeamCreate( "sprites/lgtning.spr", 50 ); + if (!m_pBeam[m_iBeams]) + return; + + m_pBeam[m_iBeams]->PointEntInit( tr.vecEndPos, entindex( ) ); + m_pBeam[m_iBeams]->SetEndAttachment( side < 0 ? 2 : 1 ); + m_pBeam[m_iBeams]->SetColor( 255, 128, 96 ); + m_pBeam[m_iBeams]->SetBrightness( 255 ); + m_pBeam[m_iBeams]->SetNoise( 20 ); + m_iBeams++; + + pEntity = CBaseEntity::Instance(tr.pHit); + if (pEntity != NULL && pEntity->pev->takedamage) + { + pEntity->TraceAttack( pev, gSkillData.slaveDmgZap, vecAim, &tr, DMG_SHOCK ); + } + UTIL_EmitAmbientSound( ENT(pev), tr.vecEndPos, "weapons/electro4.wav", 0.5, ATTN_NORM, 0, RANDOM_LONG( 140, 160 ) ); +} + + +//========================================================= +// ClearBeams - remove all beams +//========================================================= +void CISlave :: ClearBeams( ) +{ + for (int i = 0; i < ISLAVE_MAX_BEAMS; i++) + { + if (m_pBeam[i]) + { + UTIL_Remove( m_pBeam[i] ); + m_pBeam[i] = NULL; + } + } + m_iBeams = 0; + pev->skin = 0; + + STOP_SOUND( ENT(pev), CHAN_WEAPON, "debris/zap4.wav" ); +} diff --git a/dlls/items.cpp b/dlls/items.cpp new file mode 100644 index 0000000..5469654 --- /dev/null +++ b/dlls/items.cpp @@ -0,0 +1,507 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== items.cpp ======================================================== + + functions governing the selection/use of weapons for players + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "player.h" +#include "skill.h" +#include "items.h" +#include "gamerules.h" + +extern int gmsgItemPickup; + +class CWorldItem : public CBaseEntity +{ +public: + void KeyValue(KeyValueData *pkvd ); + void Spawn( void ); + int m_iType; +}; + +LINK_ENTITY_TO_CLASS(world_items, CWorldItem); + +void CWorldItem::KeyValue(KeyValueData *pkvd) +{ + if (FStrEq(pkvd->szKeyName, "type")) + { + m_iType = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CWorldItem::Spawn( void ) +{ + CBaseEntity *pEntity = NULL; + + switch (m_iType) + { + case 44: // ITEM_BATTERY: + pEntity = CBaseEntity::Create( "item_battery", pev->origin, pev->angles ); + break; + case 42: // ITEM_ANTIDOTE: + pEntity = CBaseEntity::Create( "item_antidote", pev->origin, pev->angles ); + break; + case 43: // ITEM_SECURITY: + pEntity = CBaseEntity::Create( "item_security", pev->origin, pev->angles ); + break; + case 45: // ITEM_SUIT: + pEntity = CBaseEntity::Create( "item_suit", pev->origin, pev->angles ); + break; + } + + if (!pEntity) + { + ALERT( at_debug, "unable to create world_item %d\n", m_iType ); + } + else + { + pEntity->pev->target = pev->target; + pEntity->pev->targetname = pev->targetname; + pEntity->pev->spawnflags = pev->spawnflags; + } + + REMOVE_ENTITY(edict()); +} + +// Wargon: SaveData äëÿ þçàáåëüíûõ èòåìîâ. +TYPEDESCRIPTION CItem::m_SaveData[] = +{ + DEFINE_FIELD( CItem, m_iCurrCaps, FIELD_INTEGER ), +}; +IMPLEMENT_SAVERESTORE( CItem, CBaseEntity ); + +void CItem::Spawn( void ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + UTIL_SetOrigin( this, pev->origin ); + UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 16)); + + if( !FBitSet( ObjectCaps(), FCAP_USE_ONLY ) || FBitSet( pev->spawnflags, SF_NORESPAWN )) + SetTouch(&CItem::ItemTouch); + + // Wargon: Èòåì þçàáåëüíûé. + SetUse( &CItem::ItemUse ); + m_iCurrCaps = CBaseEntity::ObjectCaps() | FCAP_IMPULSE_USE; + + DROP_TO_FLOOR(ENT(pev)); +} + +extern int gEvilImpulse101; + +void CItem::ItemTouch( CBaseEntity *pOther ) +{ + // if it's not a player, ignore + if ( !pOther->IsPlayer() ) + { + return; + } + + CBasePlayer *pPlayer = (CBasePlayer *)pOther; + + // ok, a player is touching this item, but can he have it? + if ( !g_pGameRules->CanHaveItem( pPlayer, this ) ) + { + // no? Ignore the touch. + return; + } + + if (MyTouch( pPlayer )) + { + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); + SetTouch( NULL ); + + // Wargon: Èòåì íåþçàáåëüíûé. + SetUse( NULL ); + m_iCurrCaps = CBaseEntity::ObjectCaps(); + + // player grabbed the item. + g_pGameRules->PlayerGotItem( pPlayer, this ); + if ( g_pGameRules->ItemShouldRespawn( this ) == GR_ITEM_RESPAWN_YES ) + { + Respawn(); + } + else + { + UTIL_Remove( this ); + } + } + else if (gEvilImpulse101) + { + UTIL_Remove( this ); + } +} + +CBaseEntity* CItem::Respawn( void ) +{ + // Wargon: Èòåì þçàáåëüíûé. + SetUse( NULL ); + m_iCurrCaps = CBaseEntity::ObjectCaps(); + + SetTouch( NULL ); + pev->effects |= EF_NODRAW; + + UTIL_SetOrigin( this, g_pGameRules->VecItemRespawnSpot( this ) );// blip to whereever you should respawn. + + SetThink(&CItem:: Materialize ); + AbsoluteNextThink( g_pGameRules->FlItemRespawnTime( this ) ); + return this; +} + +void CItem::Materialize( void ) +{ + if ( pev->effects & EF_NODRAW ) + { + // changing from invisible state to visible. + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "items/suitchargeok1.wav", 1, ATTN_NORM, 0, 150 ); + pev->effects &= ~EF_NODRAW; + pev->effects |= EF_MUZZLEFLASH; + } + + if( !FBitSet( ObjectCaps(), FCAP_USE_ONLY )) + SetTouch(&CItem:: ItemTouch ); + + // Wargon: Èòåì þçàáåëüíûé. + SetUse( &CItem::ItemUse ); + m_iCurrCaps = CBaseEntity::ObjectCaps() | FCAP_IMPULSE_USE; +} + +#define SF_SUIT_SHORTLOGON 0x0001 +#define SF_SUIT_WITHHEADSHIELD 0x0002 +#define SF_SUIT_WITHARMOR 0x0004 + +class CItemSuit : public CItem +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_suit.mdl"); + CItem::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_suit.mdl"); + } + BOOL MyTouch( CBasePlayer *pPlayer ) + { + if ( pPlayer->pev->deadflag != DEAD_NO ) + { + return FALSE; + } + + if( FBitSet( pPlayer->m_iHideHUD, ITEM_SUIT )) + return FALSE; + + if ( pev->spawnflags & SF_SUIT_WITHHEADSHIELD ) + { + if ( FBitSet( pPlayer->m_iHideHUD, ITEM_HEADSHIELD )) + return FALSE; + + SetBits( pPlayer->m_iHideHUD, ITEM_HEADSHIELD ); + } + + if ( pev->spawnflags & SF_SUIT_WITHARMOR ) + { + pPlayer->pev->armorvalue = MAX_NORMAL_BATTERY; + } + + SetBits( pPlayer->m_iHideHUD, ITEM_SUIT ); + + return TRUE; + } +}; + +LINK_ENTITY_TO_CLASS(item_suit, CItemSuit); + + + +class CItemBattery : public CItem +{ + void Spawn( void ) + { + Precache( ); + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/w_battery.mdl"); + CItem::Spawn( ); + } + void Precache( void ) + { + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL ("models/w_battery.mdl"); + + if (pev->noise) + PRECACHE_SOUND( (char*)STRING(pev->noise) ); //LRC + else + PRECACHE_SOUND( "items/gunpickup2.wav" ); + } + BOOL MyTouch( CBasePlayer *pPlayer ) + { + if ( pPlayer->pev->deadflag != DEAD_NO ) + { + return FALSE; + } + + if ((pPlayer->pev->armorvalue < MAX_NORMAL_BATTERY) && FBitSet( pPlayer->m_iHideHUD, ITEM_SUIT )) + { + int pct; + char szcharge[64]; + + if (pev->armorvalue) + pPlayer->pev->armorvalue += pev->armorvalue; + else + pPlayer->pev->armorvalue += gSkillData.batteryCapacity; + pPlayer->pev->armorvalue = min(pPlayer->pev->armorvalue, MAX_NORMAL_BATTERY); + + if (pev->noise) + EMIT_SOUND( pPlayer->edict(), CHAN_ITEM, STRING(pev->noise), 1, ATTN_NORM ); //LRC + else + EMIT_SOUND( pPlayer->edict(), CHAN_ITEM, "items/gunpickup2.wav", 1, ATTN_NORM ); + + MESSAGE_BEGIN( MSG_ONE, gmsgItemPickup, NULL, pPlayer->pev ); + WRITE_STRING( STRING(pev->classname) ); + MESSAGE_END(); + + + // Suit reports new power level + // For some reason this wasn't working in release build -- round it. + pct = (int)( (float)(pPlayer->pev->armorvalue * 100.0) * (1.0/MAX_NORMAL_BATTERY) + 0.5); + pct = (pct / 5); + if (pct > 0) + pct--; + + sprintf( szcharge,"!HEV_%1dP", pct ); + + //EMIT_SOUND_SUIT(ENT(pev), szcharge); + pPlayer->SetSuitUpdate(szcharge, FALSE, SUIT_NEXT_IN_30SEC); + return TRUE; + } + return FALSE; + } +}; + +LINK_ENTITY_TO_CLASS(item_battery, CItemBattery); + + +// buz: big battery, 100% +class CItemArmour : public CItem +{ + void Spawn( void ) + { + Precache( ); + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/w_armour.mdl"); + CItem::Spawn( ); + } + void Precache( void ) + { + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL ("models/w_armour.mdl"); + + PRECACHE_SOUND( "items/armour.wav" ); + } + BOOL MyTouch( CBasePlayer *pPlayer ) + { + if ( pPlayer->pev->deadflag != DEAD_NO ) + { + return FALSE; + } + + if (pPlayer->pev->armorvalue < MAX_NORMAL_BATTERY) + { + pPlayer->pev->armorvalue = MAX_NORMAL_BATTERY; + EMIT_SOUND( pPlayer->edict(), CHAN_ITEM, "items/armour.wav", 1, ATTN_NORM ); + + MESSAGE_BEGIN( MSG_ONE, gmsgItemPickup, NULL, pPlayer->pev ); + WRITE_STRING( STRING(pev->classname) ); + MESSAGE_END(); + + return TRUE; + } + return FALSE; + } +}; + +LINK_ENTITY_TO_CLASS(item_armour, CItemArmour); + + + +class CItemAntidote : public CItem +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_antidote.mdl"); + CItem::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_antidote.mdl"); + } + BOOL MyTouch( CBasePlayer *pPlayer ) + { + pPlayer->SetSuitUpdate("!HEV_DET4", FALSE, SUIT_NEXT_IN_1MIN); + + pPlayer->m_rgItems[ITEM_ANTIDOTE] += 1; + return TRUE; + } +}; + +LINK_ENTITY_TO_CLASS(item_antidote, CItemAntidote); + + +class CItemSecurity : public CItem +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_security.mdl"); + CItem::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_security.mdl"); + } + BOOL MyTouch( CBasePlayer *pPlayer ) + { + pPlayer->m_rgItems[ITEM_SECURITY] += 1; + return TRUE; + } +}; + +LINK_ENTITY_TO_CLASS(item_security, CItemSecurity); + +class CItemLongJump : public CItem +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_longjump.mdl"); + CItem::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_longjump.mdl"); + } + BOOL MyTouch( CBasePlayer *pPlayer ) + { + if ( pPlayer->m_fLongJump ) + { + return FALSE; + } + + if ( FBitSet( pPlayer->m_iHideHUD, ITEM_SUIT )) + { + pPlayer->m_fLongJump = TRUE;// player now has longjump module + + g_engfuncs.pfnSetPhysicsKeyValue( pPlayer->edict(), "slj", "1" ); + + MESSAGE_BEGIN( MSG_ONE, gmsgItemPickup, NULL, pPlayer->pev ); + WRITE_STRING( STRING(pev->classname) ); + MESSAGE_END(); + +// buz EMIT_SOUND_SUIT( pPlayer->edict(), "!HEV_A1" ); // Play the longjump sound UNDONE: Kelly? correct sound? + return TRUE; + } + return FALSE; + } +}; + +LINK_ENTITY_TO_CLASS( item_longjump, CItemLongJump ); + + +// buz: gasmask +class CItemGasMask : public CItem +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_gasmask.mdl"); + CItem::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_gasmask.mdl"); + PRECACHE_SOUND( "items/gunpickup2.wav" ); + } + BOOL MyTouch( CBasePlayer *pPlayer ) + { + if( FBitSet( pPlayer->m_iHideHUD, ITEM_GASMASK )) + return FALSE; + + SetBits( pPlayer->m_iHideHUD, ITEM_GASMASK ); + + MESSAGE_BEGIN( MSG_ONE, gmsgItemPickup, NULL, pPlayer->pev ); + WRITE_STRING( STRING(pev->classname) ); + MESSAGE_END(); + + EMIT_SOUND( pPlayer->edict(), CHAN_ITEM, "items/gunpickup2.wav", 1, ATTN_NORM ); + return TRUE; + } +}; + +LINK_ENTITY_TO_CLASS( item_gasmask, CItemGasMask ); + +// buz: head shield +class CItemHeadShield : public CItem +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_headshield.mdl"); + CItem::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_headshield.mdl"); + PRECACHE_SOUND( "items/gunpickup2.wav" ); + } + BOOL MyTouch( CBasePlayer *pPlayer ) + { + if ( FBitSet( pPlayer->m_iHideHUD, ITEM_HEADSHIELD) ) + { + return FALSE; + } + + SetBits( pPlayer->m_iHideHUD, ITEM_HEADSHIELD ); + + MESSAGE_BEGIN( MSG_ONE, gmsgItemPickup, NULL, pPlayer->pev ); + WRITE_STRING( STRING(pev->classname) ); + MESSAGE_END(); + + EMIT_SOUND( pPlayer->edict(), CHAN_ITEM, "items/gunpickup2.wav", 1, ATTN_NORM ); + return TRUE; + } +}; + +LINK_ENTITY_TO_CLASS( item_headshield, CItemHeadShield ); \ No newline at end of file diff --git a/dlls/items.h b/dlls/items.h new file mode 100644 index 0000000..0464f9b --- /dev/null +++ b/dlls/items.h @@ -0,0 +1,38 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef ITEMS_H +#define ITEMS_H + + +class CItem : public CBaseEntity +{ +public: + void Spawn( void ); + CBaseEntity* Respawn( void ); + + // Wargon: Ïåðåìåííûå äëÿ þçàáåëüíûõ èòåìîâ. + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + void EXPORT ItemUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { ItemTouch( pActivator ); } + virtual int ObjectCaps( void ) { return m_iCurrCaps | FCAP_ACROSS_TRANSITION | FCAP_USE_ONLY; } + int m_iCurrCaps; + + void EXPORT ItemTouch( CBaseEntity *pOther ); + void EXPORT Materialize( void ); + virtual BOOL MyTouch( CBasePlayer *pPlayer ) { return FALSE; }; +}; + +#endif // ITEMS_H diff --git a/dlls/leech.cpp b/dlls/leech.cpp new file mode 100644 index 0000000..f1e78af --- /dev/null +++ b/dlls/leech.cpp @@ -0,0 +1,731 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// leech - basic little swimming monster +//========================================================= +// +// UNDONE: +// DONE:Steering force model for attack +// DONE:Attack animation control / damage +// DONE:Establish range of up/down motion and steer around vertical obstacles +// DONE:Re-evaluate height periodically +// DONE:Fall (MOVETYPE_TOSS) and play different anim if out of water +// Test in complex room (c2a3?) +// DONE:Sounds? - Kelly will fix +// Blood cloud? Hurt effect? +// Group behavior? +// DONE:Save/restore +// Flop animation - just bind to ACT_TWITCH +// Fix fatal push into wall case +// +// Try this on a bird +// Try this on a model with hulls/tracehull? +// + + +#include "float.h" +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" + + + + +// Animation events +#define LEECH_AE_ATTACK 1 +#define LEECH_AE_FLOP 2 + + +// Movement constants + +#define LEECH_ACCELERATE 10 +#define LEECH_CHECK_DIST 45 +#define LEECH_SWIM_SPEED 50 +#define LEECH_SWIM_ACCEL 80 +#define LEECH_SWIM_DECEL 10 +#define LEECH_TURN_RATE 90 +#define LEECH_SIZEX 10 +#define LEECH_FRAMETIME 0.1 + + + +#define DEBUG_BEAMS 0 + +#if DEBUG_BEAMS +#include "effects.h" +#endif + + +class CLeech : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + + void EXPORT SwimThink( void ); + void EXPORT DeadThink( void ); + void Touch( CBaseEntity *pOther ) + { + if ( pOther->IsPlayer() ) + { + // If the client is pushing me, give me some base velocity + if ( gpGlobals->trace_ent && gpGlobals->trace_ent == edict() ) + { + pev->basevelocity = pOther->pev->velocity; + pev->flags |= FL_BASEVELOCITY; + } + } + } + + void SetObjectCollisionBox( void ) + { + pev->absmin = pev->origin + Vector(-8,-8,0); + pev->absmax = pev->origin + Vector(8,8,2); + } + + void AttackSound( void ); + void AlertSound( void ); + void UpdateMotion( void ); + float ObstacleDistance( CBaseEntity *pTarget ); + void MakeVectors( void ); + void RecalculateWaterlevel( void ); + void SwitchLeechState( void ); + + // Base entity functions + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int BloodColor( void ) { return DONT_BLEED; } + void Killed( entvars_t *pevAttacker, int iGib ); + void Activate( void ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + int Classify( void ) { return CLASS_INSECT; } + int IRelationship( CBaseEntity *pTarget ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + static const char *pAttackSounds[]; + static const char *pAlertSounds[]; + +private: + // UNDONE: Remove unused boid vars, do group behavior + float m_flTurning;// is this boid turning? + BOOL m_fPathBlocked;// TRUE if there is an obstacle ahead + float m_flAccelerate; + float m_obstacle; + float m_top; + float m_bottom; + float m_height; + float m_waterTime; + float m_sideTime; // Timer to randomly check clearance on sides + float m_zTime; + float m_stateTime; + float m_attackSoundTime; + +#if DEBUG_BEAMS + CBeam *m_pb; + CBeam *m_pt; +#endif +}; + + + +LINK_ENTITY_TO_CLASS( monster_leech, CLeech ); + +TYPEDESCRIPTION CLeech::m_SaveData[] = +{ + DEFINE_FIELD( CLeech, m_flTurning, FIELD_FLOAT ), + DEFINE_FIELD( CLeech, m_fPathBlocked, FIELD_BOOLEAN ), + DEFINE_FIELD( CLeech, m_flAccelerate, FIELD_FLOAT ), + DEFINE_FIELD( CLeech, m_obstacle, FIELD_FLOAT ), + DEFINE_FIELD( CLeech, m_top, FIELD_FLOAT ), + DEFINE_FIELD( CLeech, m_bottom, FIELD_FLOAT ), + DEFINE_FIELD( CLeech, m_height, FIELD_FLOAT ), + DEFINE_FIELD( CLeech, m_waterTime, FIELD_TIME ), + DEFINE_FIELD( CLeech, m_sideTime, FIELD_TIME ), + DEFINE_FIELD( CLeech, m_zTime, FIELD_TIME ), + DEFINE_FIELD( CLeech, m_stateTime, FIELD_TIME ), + DEFINE_FIELD( CLeech, m_attackSoundTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CLeech, CBaseMonster ); + + +const char *CLeech::pAttackSounds[] = +{ + "leech/leech_bite1.wav", + "leech/leech_bite2.wav", + "leech/leech_bite3.wav", +}; + +const char *CLeech::pAlertSounds[] = +{ + "leech/leech_alert1.wav", + "leech/leech_alert2.wav", +}; + + +void CLeech::Spawn( void ) +{ + Precache(); + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/leech.mdl"); + // Just for fun + // SET_MODEL(ENT(pev), "models/icky.mdl"); + +// UTIL_SetSize( pev, g_vecZero, g_vecZero ); + UTIL_SetSize( pev, Vector(-1,-1,0), Vector(1,1,2)); + // Don't push the minz down too much or the water check will fail because this entity is really point-sized + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_FLY; + SetBits(pev->flags, FL_SWIM); + if (pev->health == 0) + pev->health = gSkillData.leechHealth; + + m_flFieldOfView = -0.5; // 180 degree FOV + m_flDistLook = 750; + MonsterInit(); + SetThink(&CLeech:: SwimThink ); + SetUse( NULL ); + SetTouch( NULL ); + pev->view_ofs = g_vecZero; + + m_flTurning = 0; + m_fPathBlocked = FALSE; + SetActivity( ACT_SWIM ); + SetState( MONSTERSTATE_IDLE ); + m_stateTime = gpGlobals->time + RANDOM_FLOAT( 1, 5 ); +} + + +void CLeech::Activate( void ) +{ + RecalculateWaterlevel(); + CBaseMonster::Activate(); +} + + + +void CLeech::RecalculateWaterlevel( void ) +{ + // Calculate boundaries + Vector vecTest = pev->origin - Vector(0,0,400); + + TraceResult tr; + + UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); + if ( tr.flFraction != 1.0 ) + m_bottom = tr.vecEndPos.z + 1; + else + m_bottom = vecTest.z; + + m_top = UTIL_WaterLevel( pev->origin, pev->origin.z, pev->origin.z + 400 ) - 1; + + // Chop off 20% of the outside range + float newBottom = m_bottom * 0.8 + m_top * 0.2; + m_top = m_bottom * 0.2 + m_top * 0.8; + m_bottom = newBottom; + m_height = RANDOM_FLOAT( m_bottom, m_top ); + m_waterTime = gpGlobals->time + RANDOM_FLOAT( 5, 7 ); +} + + +void CLeech::SwitchLeechState( void ) +{ + m_stateTime = gpGlobals->time + RANDOM_FLOAT( 3, 6 ); + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + { + m_hEnemy = NULL; + SetState( MONSTERSTATE_IDLE ); + // We may be up against the player, so redo the side checks + m_sideTime = 0; + } + else + { + Look( m_flDistLook ); + CBaseEntity *pEnemy = BestVisibleEnemy(); + if ( pEnemy && pEnemy->pev->waterlevel != 0 && pEnemy->pev->watertype != CONTENTS_FOG) + { + m_hEnemy = pEnemy; + SetState( MONSTERSTATE_COMBAT ); + m_stateTime = gpGlobals->time + RANDOM_FLOAT( 18, 25 ); + AlertSound(); + } + } +} + + +int CLeech::IRelationship( CBaseEntity *pTarget ) +{ + if ( pTarget->IsPlayer() ) + return R_DL; + return CBaseMonster::IRelationship( pTarget ); +} + + + +void CLeech::AttackSound( void ) +{ + if ( gpGlobals->time > m_attackSoundTime ) + { + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pAttackSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackSounds)-1) ], 1.0, ATTN_NORM, 0, PITCH_NORM ); + m_attackSoundTime = gpGlobals->time + 0.5; + } +} + + +void CLeech::AlertSound( void ) +{ + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pAlertSounds[ RANDOM_LONG(0,ARRAYSIZE(pAlertSounds)-1) ], 1.0, ATTN_NORM * 0.5, 0, PITCH_NORM ); +} + + +void CLeech::Precache( void ) +{ + int i; + + //PRECACHE_MODEL("models/icky.mdl"); + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/leech.mdl"); + + for ( i = 0; i < ARRAYSIZE( pAttackSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackSounds[i]); + for ( i = 0; i < ARRAYSIZE( pAlertSounds ); i++ ) + PRECACHE_SOUND((char *)pAlertSounds[i]); +} + + +int CLeech::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + pev->velocity = g_vecZero; + + // Nudge the leech away from the damage + if ( pevInflictor ) + { + pev->velocity = (pev->origin - pevInflictor->origin).Normalize() * 25; + } + + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + + +void CLeech::HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case LEECH_AE_ATTACK: + AttackSound(); + CBaseEntity *pEnemy; + + pEnemy = m_hEnemy; + if ( pEnemy != NULL ) + { + Vector dir, face; + + UTIL_MakeVectorsPrivate( pev->angles, face, NULL, NULL ); + face.z = 0; + dir = (pEnemy->pev->origin - pev->origin); + dir.z = 0; + dir = dir.Normalize(); + face = face.Normalize(); + + + if ( DotProduct(dir, face) > 0.9 ) // Only take damage if the leech is facing the prey + pEnemy->TakeDamage( pev, pev, gSkillData.leechDmgBite, DMG_SLASH ); + } + m_stateTime -= 2; + break; + + case LEECH_AE_FLOP: + // Play flop sound + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + + +void CLeech::MakeVectors( void ) +{ + Vector tmp = pev->angles; + tmp.x = -tmp.x; + UTIL_MakeVectors ( tmp ); +} + + +// +// ObstacleDistance - returns normalized distance to obstacle +// +float CLeech::ObstacleDistance( CBaseEntity *pTarget ) +{ + TraceResult tr; + Vector vecTest; + + // use VELOCITY, not angles, not all boids point the direction they are flying + //Vector vecDir = UTIL_VecToAngles( pev->velocity ); + MakeVectors(); + + // check for obstacle ahead + vecTest = pev->origin + gpGlobals->v_forward * LEECH_CHECK_DIST; + UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); + + if ( tr.fStartSolid ) + { + pev->speed = -LEECH_SWIM_SPEED * 0.5; +// ALERT( at_console, "Stuck from (%f %f %f) to (%f %f %f)\n", pev->oldorigin.x, pev->oldorigin.y, pev->oldorigin.z, pev->origin.x, pev->origin.y, pev->origin.z ); +// UTIL_SetOrigin( pev, pev->oldorigin ); + } + + if ( tr.flFraction != 1.0 ) + { + if ( (pTarget == NULL || tr.pHit != pTarget->edict()) ) + { + return tr.flFraction; + } + else + { + if ( fabs(m_height - pev->origin.z) > 10 ) + return tr.flFraction; + } + } + + if ( m_sideTime < gpGlobals->time ) + { + // extra wide checks + vecTest = pev->origin + gpGlobals->v_right * LEECH_SIZEX * 2 + gpGlobals->v_forward * LEECH_CHECK_DIST; + UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); + if (tr.flFraction != 1.0) + return tr.flFraction; + + vecTest = pev->origin - gpGlobals->v_right * LEECH_SIZEX * 2 + gpGlobals->v_forward * LEECH_CHECK_DIST; + UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); + if (tr.flFraction != 1.0) + return tr.flFraction; + + // Didn't hit either side, so stop testing for another 0.5 - 1 seconds + m_sideTime = gpGlobals->time + RANDOM_FLOAT(0.5,1); + } + return 1.0; +} + + +void CLeech::DeadThink( void ) +{ + if ( m_fSequenceFinished ) + { + if ( m_Activity == ACT_DIEFORWARD ) + { + SetThink( NULL ); + StopAnimation(); + return; + } + else if ( pev->flags & FL_ONGROUND ) + { + pev->solid = SOLID_NOT; + SetActivity(ACT_DIEFORWARD); + } + } + StudioFrameAdvance(); + SetNextThink( 0.1 ); + + // Apply damage velocity, but keep out of the walls + if ( pev->velocity.x != 0 || pev->velocity.y != 0 ) + { + TraceResult tr; + + // Look 0.5 seconds ahead + UTIL_TraceLine(pev->origin, pev->origin + pev->velocity * 0.5, missile, edict(), &tr); + if (tr.flFraction != 1.0) + { + pev->velocity.x = 0; + pev->velocity.y = 0; + } + } +} + + + +void CLeech::UpdateMotion( void ) +{ + float flapspeed = (pev->speed - m_flAccelerate) / LEECH_ACCELERATE; + m_flAccelerate = m_flAccelerate * 0.8 + pev->speed * 0.2; + + if (flapspeed < 0) + flapspeed = -flapspeed; + flapspeed += 1.0; + if (flapspeed < 0.5) + flapspeed = 0.5; + if (flapspeed > 1.9) + flapspeed = 1.9; + + pev->framerate = flapspeed; + + if ( !m_fPathBlocked ) + pev->avelocity.y = pev->ideal_yaw; + else + pev->avelocity.y = pev->ideal_yaw * m_obstacle; + + if ( pev->avelocity.y > 150 ) + m_IdealActivity = ACT_TURN_LEFT; + else if ( pev->avelocity.y < -150 ) + m_IdealActivity = ACT_TURN_RIGHT; + else + m_IdealActivity = ACT_SWIM; + + // lean + float targetPitch, delta; + delta = m_height - pev->origin.z; + + if ( delta < -10 ) + targetPitch = -30; + else if ( delta > 10 ) + targetPitch = 30; + else + targetPitch = 0; + + pev->angles.x = UTIL_Approach( targetPitch, pev->angles.x, 60 * LEECH_FRAMETIME ); + + // bank + pev->avelocity.z = - (pev->angles.z + (pev->avelocity.y * 0.25)); + + if ( m_MonsterState == MONSTERSTATE_COMBAT && HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) ) + m_IdealActivity = ACT_MELEE_ATTACK1; + + // Out of water check + if ( !pev->waterlevel || pev->watertype == CONTENTS_FOG) + { + pev->movetype = MOVETYPE_TOSS; + m_IdealActivity = ACT_TWITCH; + pev->velocity = g_vecZero; + + // Animation will intersect the floor if either of these is non-zero + pev->angles.z = 0; + pev->angles.x = 0; + + if ( pev->framerate < 1.0 ) + pev->framerate = 1.0; + } + else if ( pev->movetype == MOVETYPE_TOSS ) + { + pev->movetype = MOVETYPE_FLY; + pev->flags &= ~FL_ONGROUND; + RecalculateWaterlevel(); + m_waterTime = gpGlobals->time + 2; // Recalc again soon, water may be rising + } + + if ( m_Activity != m_IdealActivity ) + { + SetActivity ( m_IdealActivity ); + } + float flInterval = StudioFrameAdvance(); + DispatchAnimEvents ( flInterval ); + +#if DEBUG_BEAMS + if ( !m_pb ) + m_pb = CBeam::BeamCreate( "sprites/laserbeam.spr", 5 ); + if ( !m_pt ) + m_pt = CBeam::BeamCreate( "sprites/laserbeam.spr", 5 ); + m_pb->PointsInit( pev->origin, pev->origin + gpGlobals->v_forward * LEECH_CHECK_DIST ); + m_pt->PointsInit( pev->origin, pev->origin - gpGlobals->v_right * (pev->avelocity.y*0.25) ); + if ( m_fPathBlocked ) + { + float color = m_obstacle * 30; + if ( m_obstacle == 1.0 ) + color = 0; + if ( color > 255 ) + color = 255; + m_pb->SetColor( 255, (int)color, (int)color ); + } + else + m_pb->SetColor( 255, 255, 0 ); + m_pt->SetColor( 0, 0, 255 ); +#endif +} + + +void CLeech::SwimThink( void ) +{ + TraceResult tr; + float flLeftSide; + float flRightSide; + float targetSpeed; + float targetYaw = 0; + CBaseEntity *pTarget; + + if ( FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) + { + SetNextThink( RANDOM_FLOAT(1,1.5) ); + pev->velocity = g_vecZero; + return; + } + else + SetNextThink( 0.1 ); + + targetSpeed = LEECH_SWIM_SPEED; + + if ( m_waterTime < gpGlobals->time ) + RecalculateWaterlevel(); + + if ( m_stateTime < gpGlobals->time ) + SwitchLeechState(); + + ClearConditions( bits_COND_CAN_MELEE_ATTACK1 ); + switch( m_MonsterState ) + { + case MONSTERSTATE_COMBAT: + pTarget = m_hEnemy; + if ( !pTarget ) + SwitchLeechState(); + else + { + // Chase the enemy's eyes + m_height = pTarget->pev->origin.z + pTarget->pev->view_ofs.z - 5; + // Clip to viable water area + if ( m_height < m_bottom ) + m_height = m_bottom; + else if ( m_height > m_top ) + m_height = m_top; + Vector location = pTarget->pev->origin - pev->origin; + location.z += (pTarget->pev->view_ofs.z); + if ( location.Length() < 40 ) + SetConditions( bits_COND_CAN_MELEE_ATTACK1 ); + // Turn towards target ent + targetYaw = UTIL_VecToYaw( location ); + + targetYaw = UTIL_AngleDiff( targetYaw, UTIL_AngleMod( pev->angles.y ) ); + + if ( targetYaw < (-LEECH_TURN_RATE*0.75) ) + targetYaw = (-LEECH_TURN_RATE*0.75); + else if ( targetYaw > (LEECH_TURN_RATE*0.75) ) + targetYaw = (LEECH_TURN_RATE*0.75); + else + targetSpeed *= 2; + } + + break; + + default: + if ( m_zTime < gpGlobals->time ) + { + float newHeight = RANDOM_FLOAT( m_bottom, m_top ); + m_height = 0.5 * m_height + 0.5 * newHeight; + m_zTime = gpGlobals->time + RANDOM_FLOAT( 1, 4 ); + } + if ( RANDOM_LONG( 0, 100 ) < 10 ) + targetYaw = RANDOM_LONG( -30, 30 ); + pTarget = NULL; + // oldorigin test + if ( (pev->origin - pev->oldorigin).Length() < 1 ) + { + // If leech didn't move, there must be something blocking it, so try to turn + m_sideTime = 0; + } + + break; + } + + m_obstacle = ObstacleDistance( pTarget ); + pev->oldorigin = pev->origin; + if ( m_obstacle < 0.1 ) + m_obstacle = 0.1; + + // is the way ahead clear? + if ( m_obstacle == 1.0 ) + { + // if the leech is turning, stop the trend. + if ( m_flTurning != 0 ) + { + m_flTurning = 0; + } + + m_fPathBlocked = FALSE; + pev->speed = UTIL_Approach( targetSpeed, pev->speed, LEECH_SWIM_ACCEL * LEECH_FRAMETIME ); + pev->velocity = gpGlobals->v_forward * pev->speed; + + } + else + { + m_obstacle = 1.0 / m_obstacle; + // IF we get this far in the function, the leader's path is blocked! + m_fPathBlocked = TRUE; + + if ( m_flTurning == 0 )// something in the way and leech is not already turning to avoid + { + Vector vecTest; + // measure clearance on left and right to pick the best dir to turn + vecTest = pev->origin + (gpGlobals->v_right * LEECH_SIZEX) + (gpGlobals->v_forward * LEECH_CHECK_DIST); + UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); + flRightSide = tr.flFraction; + + vecTest = pev->origin + (gpGlobals->v_right * -LEECH_SIZEX) + (gpGlobals->v_forward * LEECH_CHECK_DIST); + UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); + flLeftSide = tr.flFraction; + + // turn left, right or random depending on clearance ratio + float delta = (flRightSide - flLeftSide); + if ( delta > 0.1 || (delta > -0.1 && RANDOM_LONG(0,100)<50) ) + m_flTurning = -LEECH_TURN_RATE; + else + m_flTurning = LEECH_TURN_RATE; + } + pev->speed = UTIL_Approach( -(LEECH_SWIM_SPEED*0.5), pev->speed, LEECH_SWIM_DECEL * LEECH_FRAMETIME * m_obstacle ); + pev->velocity = gpGlobals->v_forward * pev->speed; + } + pev->ideal_yaw = m_flTurning + targetYaw; + UpdateMotion(); +} + + +void CLeech::Killed(entvars_t *pevAttacker, int iGib) +{ + Vector vecSplatDir; + TraceResult tr; + + //ALERT(at_aiconsole, "Leech: killed\n"); + // tell owner ( if any ) that we're dead.This is mostly for MonsterMaker functionality. + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + if (pOwner) + pOwner->DeathNotice(pev); + + // When we hit the ground, play the "death_end" activity + if ( pev->waterlevel && pev->watertype != CONTENTS_FOG) + { + pev->angles.z = 0; + pev->angles.x = 0; + pev->origin.z += 1; + pev->avelocity = g_vecZero; + if ( RANDOM_LONG( 0, 99 ) < 70 ) + pev->avelocity.y = RANDOM_LONG( -720, 720 ); + + pev->gravity = 0.02; + ClearBits(pev->flags, FL_ONGROUND); + SetActivity( ACT_DIESIMPLE ); + } + else + SetActivity( ACT_DIEFORWARD ); + + pev->movetype = MOVETYPE_TOSS; + pev->takedamage = DAMAGE_NO; + SetThink(&CLeech:: DeadThink ); +} + + diff --git a/dlls/lights.cpp b/dlls/lights.cpp new file mode 100644 index 0000000..9dd5047 --- /dev/null +++ b/dlls/lights.cpp @@ -0,0 +1,705 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== lights.cpp ======================================================== + + spawn and think functions for editor-placed lights + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" + +//LRC +int GetStdLightStyle (int iStyle) +{ + switch (iStyle) + { + // 0 normal + case 0: return MAKE_STRING("m"); + + // 1 FLICKER (first variety) + case 1: return MAKE_STRING("mmnmmommommnonmmonqnmmo"); + + // 2 SLOW STRONG PULSE + case 2: return MAKE_STRING("abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba"); + + // 3 CANDLE (first variety) + case 3: return MAKE_STRING("mmmmmaaaaammmmmaaaaaabcdefgabcdefg"); + + // 4 FAST STROBE + case 4: return MAKE_STRING("mamamamamama"); + + // 5 GENTLE PULSE 1 + case 5: return MAKE_STRING("jklmnopqrstuvwxyzyxwvutsrqponmlkj"); + + // 6 FLICKER (second variety) + case 6: return MAKE_STRING("nmonqnmomnmomomno"); + + // 7 CANDLE (second variety) + case 7: return MAKE_STRING("mmmaaaabcdefgmmmmaaaammmaamm"); + + // 8 CANDLE (third variety) + case 8: return MAKE_STRING("mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa"); + + // 9 SLOW STROBE (fourth variety) + case 9: return MAKE_STRING("aaaaaaaazzzzzzzz"); + + // 10 FLUORESCENT FLICKER + case 10: return MAKE_STRING("mmamammmmammamamaaamammma"); + + // 11 SLOW PULSE NOT FADE TO BLACK + case 11: return MAKE_STRING("abcdefghijklmnopqrrqponmlkjihgfedcba"); + + // 12 UNDERWATER LIGHT MUTATION + // this light only distorts the lightmap - no contribution + // is made to the brightness of affected surfaces + case 12: return MAKE_STRING("mmnnmmnnnmmnn"); + + // 13 OFF (LRC) + case 13: return MAKE_STRING("a"); + + // 14 SLOW FADE IN (LRC) + case 14: return MAKE_STRING("aabbccddeeffgghhiijjkkllmmmmmmmmmmmmmm"); + + // 15 MED FADE IN (LRC) + case 15: return MAKE_STRING("abcdefghijklmmmmmmmmmmmmmmmmmmmmmmmmmm"); + + // 16 FAST FADE IN (LRC) + case 16: return MAKE_STRING("acegikmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm"); + + // 17 SLOW FADE OUT (LRC) + case 17: return MAKE_STRING("llkkjjiihhggffeeddccbbaaaaaaaaaaaaaaaa"); + + // 18 MED FADE OUT (LRC) + case 18: return MAKE_STRING("lkjihgfedcbaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + + // 19 FAST FADE OUT (LRC) + case 19: return MAKE_STRING("kigecaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); + + default: return MAKE_STRING("m"); + } +} + + +class CLight : public CPointEntity +{ +public: + virtual void KeyValue( KeyValueData* pkvd ); + virtual void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Think( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual STATE GetState(void) { return m_iState; }; //LRC + + static TYPEDESCRIPTION m_SaveData[]; + + int GetStyle( void ) { return m_iszCurrentStyle; }; //LRC + void SetStyle( int iszPattern ); //LRC + + void SetCorrectStyle( void ); //LRC + float m_flPitch; +private: + STATE m_iState; // current state + int m_iOnStyle; // style to use while on + int m_iOffStyle; // style to use while off + int m_iTurnOnStyle; // style to use while turning on + int m_iTurnOffStyle; // style to use while turning off + int m_iTurnOnTime; // time taken to turn on + int m_iTurnOffTime; // time taken to turn off + int m_iszPattern; // custom style to use while on + int m_iszCurrentStyle; // current style string +}; +LINK_ENTITY_TO_CLASS( light, CLight ); + +TYPEDESCRIPTION CLight::m_SaveData[] = +{ + DEFINE_FIELD( CLight, m_iState, FIELD_INTEGER ), + DEFINE_FIELD( CLight, m_iszPattern, FIELD_STRING ), + DEFINE_FIELD( CLight, m_iszCurrentStyle, FIELD_STRING ), + DEFINE_FIELD( CLight, m_iOnStyle, FIELD_INTEGER ), + DEFINE_FIELD( CLight, m_iOffStyle, FIELD_INTEGER ), + DEFINE_FIELD( CLight, m_iTurnOnStyle, FIELD_INTEGER ), + DEFINE_FIELD( CLight, m_iTurnOffStyle, FIELD_INTEGER ), + DEFINE_FIELD( CLight, m_iTurnOnTime, FIELD_INTEGER ), + DEFINE_FIELD( CLight, m_iTurnOffTime, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CLight, CPointEntity ); + + +// +// Cache user-entity-field values until spawn is called. +// +void CLight :: KeyValue( KeyValueData* pkvd) +{ + if (FStrEq(pkvd->szKeyName, "m_iOnStyle")) + { + m_iOnStyle = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iOffStyle")) + { + m_iOffStyle = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iTurnOnStyle")) + { + m_iTurnOnStyle = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iTurnOffStyle")) + { + m_iTurnOffStyle = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iTurnOnTime")) + { + m_iTurnOnTime = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iTurnOffTime")) + { + m_iTurnOffTime = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pitch")) + { + m_flPitch = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "pattern")) + { + m_iszPattern = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "firetarget")) + { + pev->target = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + { + CPointEntity::KeyValue( pkvd ); + } +} + +void CLight :: SetStyle ( int iszPattern ) +{ + if (m_iStyle < 32) // if it's using a global style, don't change it + return; + + m_iszCurrentStyle = iszPattern; + LIGHT_STYLE(m_iStyle, (char *)STRING( iszPattern )); +} + +// regardless of what's been set by trigger_lightstyle ents, set the style I think I need +void CLight :: SetCorrectStyle ( void ) +{ + if (m_iStyle >= 32) + { + switch (m_iState) + { + case STATE_ON: + if (m_iszPattern) // custom styles have priority over standard ones + SetStyle( m_iszPattern ); + else if (m_iOnStyle) + SetStyle(GetStdLightStyle(m_iOnStyle)); + else + SetStyle(MAKE_STRING("m")); + break; + case STATE_OFF: + if (m_iOffStyle) + SetStyle(GetStdLightStyle(m_iOffStyle)); + else + SetStyle(MAKE_STRING("a")); + break; + case STATE_TURN_ON: + if (m_iTurnOnStyle) + SetStyle(GetStdLightStyle(m_iTurnOnStyle)); + else + SetStyle(MAKE_STRING("a")); + break; + case STATE_TURN_OFF: + if (m_iTurnOffStyle) + SetStyle(GetStdLightStyle(m_iTurnOffStyle)); + else + SetStyle(MAKE_STRING("m")); + break; + } + } + else + { + m_iszCurrentStyle = GetStdLightStyle( m_iStyle ); + } +} + +void CLight :: Think( void ) +{ + switch (GetState()) + { + case STATE_TURN_ON: + m_iState = STATE_ON; + FireTargets(STRING(pev->target),this,this,USE_ON,0); + break; + case STATE_TURN_OFF: + m_iState = STATE_OFF; + FireTargets(STRING(pev->target),this,this,USE_OFF,0); + break; + } + + SetCorrectStyle(); +} + +/*QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) LIGHT_START_OFF +Non-displayed light. +Default light value is 300 +Default style is 0 +If targeted, it will toggle between on or off. +*/ +void CLight :: Spawn( void ) +{ + if (FStringNull(pev->targetname)) + { + // inert light + REMOVE_ENTITY(ENT(pev)); + return; + } + + if (FBitSet(pev->spawnflags,SF_LIGHT_START_OFF)) + m_iState = STATE_OFF; + else + m_iState = STATE_ON; + + SetCorrectStyle(); +} + +void CLight :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (m_iStyle >= 32) + { + if ( !ShouldToggle( useType ) ) + return; + + switch (GetState()) + { + case STATE_ON: + case STATE_TURN_ON: + if (m_iTurnOffTime) + { + m_iState = STATE_TURN_OFF; + SetNextThink( m_iTurnOffTime ); + } + else + m_iState = STATE_OFF; + break; + case STATE_OFF: + case STATE_TURN_OFF: + if (m_iTurnOnTime) + { + m_iState = STATE_TURN_ON; + SetNextThink( m_iTurnOnTime ); + } + else + m_iState = STATE_ON; + break; + } + SetCorrectStyle(); + } +} + +// +// shut up spawn functions for new spotlights +// +LINK_ENTITY_TO_CLASS( light_spot, CLight ); + + +class CEnvLight : public CLight +{ +public: + void KeyValue( KeyValueData* pkvd ); + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( light_environment, CEnvLight ); + +void CEnvLight::KeyValue( KeyValueData* pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "_light")) + { + int r, g, b, v, j; + char szColor[64]; + j = sscanf( pkvd->szValue, "%d %d %d %d\n", &r, &g, &b, &v ); + if (j == 1) + { + g = b = r; + } + else if (j == 4) + { + r = r * (v / 255.0); + g = g * (v / 255.0); + b = b * (v / 255.0); + } + + // simulate qrad direct, ambient,and gamma adjustments, as well as engine scaling + r = pow( r / 114.0, 0.6 ) * 264; + g = pow( g / 114.0, 0.6 ) * 264; + b = pow( b / 114.0, 0.6 ) * 264; + + pkvd->fHandled = TRUE; + sprintf( szColor, "%d", r ); + CVAR_SET_STRING( "sv_skycolor_r", szColor ); + sprintf( szColor, "%d", g ); + CVAR_SET_STRING( "sv_skycolor_g", szColor ); + sprintf( szColor, "%d", b ); + CVAR_SET_STRING( "sv_skycolor_b", szColor ); + } + else + { + CLight::KeyValue( pkvd ); + } +} + + +void CEnvLight :: Spawn( void ) +{ + if( !pev->angles.x ) + pev->angles.x = m_flPitch; + + char szVector[64]; + UTIL_MakeAimVectors( pev->angles ); + + sprintf( szVector, "%f", gpGlobals->v_forward.x ); + CVAR_SET_STRING( "sv_skyvec_x", szVector ); + sprintf( szVector, "%f", gpGlobals->v_forward.y ); + CVAR_SET_STRING( "sv_skyvec_y", szVector ); + sprintf( szVector, "%f", gpGlobals->v_forward.z ); + CVAR_SET_STRING( "sv_skyvec_z", szVector ); + + CLight::Spawn( ); +} + +//********************************************************** +//LRC- the CLightDynamic entity - works like the flashlight. +//********************************************************** + +#define SF_LIGHTDYNAMIC_START_OFF 1 +#define SF_LIGHTDYNAMIC_FLARE 2 + +class CLightDynamic : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int ObjectCaps( void ) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + void SetEffects( void ); + STATE GetState( void ); +}; + +LINK_ENTITY_TO_CLASS( light_glow, CLightDynamic ); + +void CLightDynamic::Spawn( void ) +{ + Precache( ); + + SET_MODEL(ENT(pev), "sprites/null.spr"); + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + if (!(pev->spawnflags & SF_LIGHTDYNAMIC_START_OFF)) + { + pev->health = 1; + SetEffects(); + } +} + +void CLightDynamic :: Precache( void ) +{ + PRECACHE_MODEL("sprites/null.spr"); +} + +void CLightDynamic::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (ShouldToggle(useType, pev->health)) + { + if (pev->health) + pev->health = 0; + else + pev->health = 1; + SetEffects(); + } +} + +void CLightDynamic::SetEffects( void ) +{ + if (pev->health) + { + if (pev->frags == 2) + pev->effects |= EF_BRIGHTLIGHT; + else if (pev->frags) + pev->effects |= EF_DIMLIGHT; + + if (pev->spawnflags & SF_LIGHTDYNAMIC_FLARE) + pev->effects |= EF_LIGHT; + } + else + { + pev->effects &= ~(EF_DIMLIGHT | EF_BRIGHTLIGHT | EF_LIGHT); + } +} + +STATE CLightDynamic::GetState( void ) +{ + if (pev->health) + return STATE_ON; + else + return STATE_OFF; +} + +//********************************************************** +//LRC- the CTriggerLightstyle entity - changes the style of a light temporarily. +//********************************************************** +class CLightFader : public CPointEntity +{ +public: + void EXPORT FadeThink( void ); + void EXPORT WaitThink( void ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + CLight *m_pLight; + char m_cFrom; + char m_cTo; + char m_szCurStyle[256]; + float m_fEndTime; + string_t m_iszPattern; + float m_fStep; + int m_iWait; + int m_iHardwareLerped; +}; + +LINK_ENTITY_TO_CLASS( lightfader, CLightFader ); + +TYPEDESCRIPTION CLightFader::m_SaveData[] = +{ + DEFINE_FIELD( CLightFader, m_pLight, FIELD_CLASSPTR ), + DEFINE_FIELD( CLightFader, m_cFrom, FIELD_CHARACTER ), + DEFINE_FIELD( CLightFader, m_cTo, FIELD_CHARACTER ), + DEFINE_ARRAY( CLightFader, m_szCurStyle, FIELD_CHARACTER, 256 ), + DEFINE_FIELD( CLightFader, m_fEndTime, FIELD_FLOAT ), + DEFINE_FIELD( CLightFader, m_iszPattern, FIELD_STRING ), + DEFINE_FIELD( CLightFader, m_fStep, FIELD_FLOAT ), + DEFINE_FIELD( CLightFader, m_iWait, FIELD_INTEGER ), + DEFINE_FIELD( CLightFader, m_iHardwareLerped, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE(CLightFader,CPointEntity); + +void CLightFader::FadeThink( void ) +{ + if (m_fEndTime > gpGlobals->time) + { + if( !m_iHardwareLerped ) + { + m_szCurStyle[0] = m_cTo + (char)((m_cFrom - m_cTo) * (m_fEndTime - gpGlobals->time) * m_fStep); + m_szCurStyle[1] = 0; // null terminator + m_pLight->SetStyle(MAKE_STRING(m_szCurStyle)); + } + SetNextThink( 0.1 ); + } + else + { + // fade is finished + m_pLight->SetStyle(m_iszPattern); + if (m_iWait > -1) + { + // wait until it's time to switch off + SetThink(&CLightFader:: WaitThink ); + SetNextThink( m_iWait ); + } + else + { + // we've finished, kill the fader + SetThink(&CLightFader:: SUB_Remove ); + SetNextThink( 0.1 ); + } + } +} + +// we've finished. revert the light and kill the fader. +void CLightFader::WaitThink( void ) +{ + if( m_iszPattern ) + m_pLight->SetStyle( m_iszPattern ); + else m_pLight->SetCorrectStyle(); + SetThink(&CLightFader:: SUB_Remove ); + SetNextThink( 0.1 ); +} + + + +class CTriggerLightstyle : public CPointEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + char m_szOldPattern[256]; + int m_iszPattern; + int m_iFade; + int m_iWait; +}; + +LINK_ENTITY_TO_CLASS( trigger_lightstyle, CTriggerLightstyle ); + +TYPEDESCRIPTION CTriggerLightstyle::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerLightstyle, m_iszPattern, FIELD_STRING ), + DEFINE_FIELD( CTriggerLightstyle, m_iFade, FIELD_INTEGER ), + DEFINE_FIELD( CTriggerLightstyle, m_iWait, FIELD_INTEGER ), + DEFINE_ARRAY( CTriggerLightstyle, m_szOldPattern, FIELD_CHARACTER, 256 ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerLightstyle,CBaseEntity); + +void CTriggerLightstyle::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "pattern")) + { + m_iszPattern = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iFade")) + { + m_iFade = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iWait")) + { + m_iWait = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CTriggerLightstyle::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBaseEntity *pTarget = NULL; + if ( !pev->target ) + return; + + //ALERT( at_console, "Lightstyle change for: (%s)\n", STRING(pev->target) ); + bool fFisrt = true; + + while( 1 ) + { + pTarget = UTIL_FindEntityByTargetname(pTarget,STRING(pev->target), pActivator); + if (FNullEnt(pTarget)) + break; + + int iszPattern; + if (m_iszPattern) + iszPattern = m_iszPattern; + else + iszPattern = GetStdLightStyle(m_iStyle); + + // not a light entity? + if (!FClassnameIs(pTarget->pev, "light") && !FClassnameIs(pTarget->pev, "light_spot") && !FClassnameIs(pTarget->pev, "light_environment")) + { + if (pTarget->m_iStyle >= 32) + LIGHT_STYLE(pTarget->m_iStyle, (char*)STRING(iszPattern)); + } + else + { + CLight *pLight = (CLight*)pTarget; + + if (m_iFade) + { + CLightFader *pFader = GetClassPtr( (CLightFader*)NULL ); + pFader->pev->classname = MAKE_STRING( "lightfader" ); + pFader->m_pLight = pLight; + pFader->m_cFrom = ((char*)STRING(pLight->GetStyle()))[0]; + pFader->m_cTo = ((char*)STRING(iszPattern))[0]; + pFader->m_iszPattern = iszPattern; + pFader->m_fEndTime = gpGlobals->time + m_iFade; + pFader->m_fStep = 1.0f / m_iFade; + pFader->m_iWait = m_iWait; + pFader->SetThink( &CLightFader::FadeThink ); + pFader->SetNextThink( 0.1 ); + + float time = 1.0; + float end = time + m_iFade; + char lerpedPattern[256]; + char *lpPattern = lerpedPattern; + pFader->m_iHardwareLerped = TRUE; + + while( end > time ) + { + *lpPattern = pFader->m_cTo + (char)((pFader->m_cFrom - pFader->m_cTo) * (end - time) * pFader->m_fStep); + time += 0.09; // to prevent loop the style + lpPattern++; + + // exceed hardware pattern length? + if(( lpPattern - lerpedPattern ) > 250 ) + { + pFader->m_iHardwareLerped = FALSE; + break; + } + } + *lpPattern = '\0'; + + if( pFader->m_iHardwareLerped ) + { + // build the lerped sequence and let the engine lerping the lightstyle + pFader->m_pLight->SetStyle( ALLOC_STRING( lerpedPattern )); + } + } + else + { + if( fFisrt ) + { + // save old pattern in case we needs to be restore it + Q_strncpy( m_szOldPattern, GET_LIGHT_STYLE( pLight->m_iStyle ), 256 ); + fFisrt = false; + } + + pLight->SetStyle( iszPattern ); + + if( m_iWait != -1 ) + { + CLightFader *pFader = GetClassPtr( (CLightFader*)NULL ); + pFader->pev->classname = MAKE_STRING( "lightfader" ); + pFader->m_pLight = pLight; + // i'm hope somebody don't delete this entity from map :-) + pFader->m_iszPattern = MAKE_STRING( m_szOldPattern ); + pFader->SetThink( &CLightFader::WaitThink ); + pFader->SetNextThink( m_iWait ); + } + } + } + } +} diff --git a/dlls/locus.cpp b/dlls/locus.cpp new file mode 100644 index 0000000..f578125 --- /dev/null +++ b/dlls/locus.cpp @@ -0,0 +1,780 @@ +//========================================= +// NEW file for Spirit of Half-Life 0.7 +// Created 14/01/02 +//========================================= + +// Spirit of Half-Life's particle system uses "locus triggers" to tell +// entities where to perform their actions. + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "locus.h" +#include "effects.h" +#include "decals.h" + + +Vector CalcLocus_Position( CBaseEntity *pEntity, CBaseEntity *pLocus, const char *szText ) +{ + if ((*szText >= '0' && *szText <= '9') || *szText == '-') + { // it's a vector + Vector tmp; + UTIL_StringToRandomVector( (float *)tmp, szText ); + return tmp; + } + + CBaseEntity *pCalc = UTIL_FindEntityByTargetname(NULL, szText, pLocus); + + if (pCalc != NULL) + { + return pCalc->CalcPosition( pLocus ); + } + + ALERT(at_error, "%s \"%s\" has bad or missing calc_position value \"%s\"\n", STRING(pEntity->pev->classname), STRING(pEntity->pev->targetname), szText); + return g_vecZero; +} + +Vector CalcLocus_Velocity( CBaseEntity *pEntity, CBaseEntity *pLocus, const char *szText ) +{ + if ((*szText >= '0' && *szText <= '9') || *szText == '-') + { // it's a vector + Vector tmp; + UTIL_StringToRandomVector( (float *)tmp, szText ); + return tmp; + } + + CBaseEntity *pCalc = UTIL_FindEntityByTargetname(NULL, szText, pLocus); + + if (pCalc != NULL) + return pCalc->CalcVelocity( pLocus ); + + ALERT(at_error, "%s \"%s\" has bad or missing calc_velocity value \"%s\"\n", STRING(pEntity->pev->classname), STRING(pEntity->pev->targetname), szText); + return g_vecZero; +} + +float CalcLocus_Ratio( CBaseEntity *pLocus, const char *szText ) +{ + if ((*szText >= '0' && *szText <= '9') || *szText == '-') + { // assume it's a float + return atof( szText ); + } + + CBaseEntity *pCalc = UTIL_FindEntityByTargetname(NULL, szText, pLocus); + + if (pCalc != NULL) + return pCalc->CalcRatio( pLocus ); + + ALERT(at_error, "Bad or missing calc_ratio entity \"%s\"\n", szText); + return 0; // we need some signal for "fail". NaN, maybe? +} + +//============================================= +//locus_x effects +//============================================= + +// Entity variable +class CLocusAlias : public CBaseAlias +{ +public: + void PostSpawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + CBaseEntity *FollowAlias( CBaseEntity *pFrom ); + void FlushChanges( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + EHANDLE m_hValue; + EHANDLE m_hChangeTo; +}; + +TYPEDESCRIPTION CLocusAlias::m_SaveData[] = +{ + DEFINE_FIELD( CLocusAlias, m_hValue, FIELD_EHANDLE), + DEFINE_FIELD( CLocusAlias, m_hChangeTo, FIELD_EHANDLE), +}; + +LINK_ENTITY_TO_CLASS( locus_alias, CLocusAlias ); +IMPLEMENT_SAVERESTORE( CLocusAlias, CBaseAlias ); + +void CLocusAlias::PostSpawn( void ) +{ + m_hValue = UTIL_FindEntityByTargetname( NULL, STRING(pev->netname) ); +} + +void CLocusAlias::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_hChangeTo = pActivator; + UTIL_AddToAliasList( this ); +} + +void CLocusAlias::FlushChanges( void ) +{ + m_hValue = m_hChangeTo; + m_hChangeTo = NULL; +} + +CBaseEntity *CLocusAlias::FollowAlias( CBaseEntity *pFrom ) +{ + if (m_hValue == NULL) + return NULL; + else if ( pFrom == NULL || (OFFSET(m_hValue->pev) > OFFSET(pFrom->pev)) ) + { +// ALERT(at_console, "LocusAlias returns %s: %f %f %f\n", STRING(m_pValue->pev->targetname), m_pValue->pev->origin.x, m_pValue->pev->origin.y, m_pValue->pev->origin.z); + return m_hValue; + } + else + return NULL; +} + + + +// Beam maker +#define BEAM_FSINE 0x10 +#define BEAM_FSOLID 0x20 +#define BEAM_FSHADEIN 0x40 +#define BEAM_FSHADEOUT 0x80 + +#define SF_LBEAM_SHADEIN 128 +#define SF_LBEAM_SHADEOUT 256 +#define SF_LBEAM_SOLID 512 +#define SF_LBEAM_SINE 1024 + +class CLocusBeam : public CPointEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + void KeyValue( KeyValueData *pkvd ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int m_iszSprite; + int m_iszTargetName; + int m_iszStart; + int m_iszEnd; + int m_iWidth; + int m_iDistortion; + float m_fFrame; + int m_iScrollRate; + float m_fDuration; + float m_fDamage; + int m_iDamageType; + int m_iFlags; +}; + +TYPEDESCRIPTION CLocusBeam::m_SaveData[] = +{ + DEFINE_FIELD( CLocusBeam, m_iszSprite, FIELD_STRING), + DEFINE_FIELD( CLocusBeam, m_iszTargetName, FIELD_STRING), + DEFINE_FIELD( CLocusBeam, m_iszStart, FIELD_STRING), + DEFINE_FIELD( CLocusBeam, m_iszEnd, FIELD_STRING), + DEFINE_FIELD( CLocusBeam, m_iWidth, FIELD_INTEGER), + DEFINE_FIELD( CLocusBeam, m_iDistortion, FIELD_INTEGER), + DEFINE_FIELD( CLocusBeam, m_fFrame, FIELD_FLOAT), + DEFINE_FIELD( CLocusBeam, m_iScrollRate, FIELD_INTEGER), + DEFINE_FIELD( CLocusBeam, m_fDuration, FIELD_FLOAT), + DEFINE_FIELD( CLocusBeam, m_fDamage, FIELD_FLOAT), + DEFINE_FIELD( CLocusBeam, m_iDamageType, FIELD_INTEGER), + DEFINE_FIELD( CLocusBeam, m_iFlags, FIELD_INTEGER), +}; + +LINK_ENTITY_TO_CLASS( locus_beam, CLocusBeam ); +IMPLEMENT_SAVERESTORE(CLocusBeam,CPointEntity); + +void CLocusBeam :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iszSprite")) + { + m_iszSprite = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszTargetName")) + { + m_iszTargetName = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszStart")) + { + m_iszStart = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszEnd")) + { + m_iszEnd = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iWidth")) + { + m_iWidth = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iDistortion")) + { + m_iDistortion = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fFrame")) + { + m_fFrame = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iScrollRate")) + { + m_iScrollRate = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fDuration")) + { + m_fDuration = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fDamage")) + { + m_fDamage = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iDamageType")) + { + m_iDamageType = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CLocusBeam :: Precache ( void ) +{ + PRECACHE_MODEL ( (char*)STRING(m_iszSprite) ); +} + +void CLocusBeam::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBaseEntity *pStartEnt; + CBaseEntity *pEndEnt; + Vector vecStartPos; + Vector vecEndPos; + CBeam *pBeam; + + switch(pev->impulse) + { + case 0: // ents + pStartEnt = UTIL_FindEntityByTargetname(NULL, STRING(m_iszStart), pActivator); + pEndEnt = UTIL_FindEntityByTargetname(NULL, STRING(m_iszEnd), pActivator); + + if (pStartEnt == NULL || pEndEnt == NULL) + return; + pBeam = CBeam::BeamCreate( STRING(m_iszSprite), m_iWidth ); + pBeam->EntsInit( pStartEnt->entindex(), pEndEnt->entindex() ); + break; + + case 1: // pointent + vecStartPos = CalcLocus_Position( this, pActivator, STRING(m_iszStart) ); + pEndEnt = UTIL_FindEntityByTargetname(NULL, STRING(m_iszEnd), pActivator); + + if (pEndEnt == NULL) + return; + pBeam = CBeam::BeamCreate( STRING(m_iszSprite), m_iWidth ); + pBeam->PointEntInit( vecStartPos, pEndEnt->entindex() ); + break; + case 2: // points + vecStartPos = CalcLocus_Position( this, pActivator, STRING(m_iszStart) ); + vecEndPos = CalcLocus_Position( this, pActivator, STRING(m_iszEnd) ); + + pBeam = CBeam::BeamCreate( STRING(m_iszSprite), m_iWidth ); + pBeam->PointsInit( vecStartPos, vecEndPos ); + break; + case 3: // point & offset + vecStartPos = CalcLocus_Position( this, pActivator, STRING(m_iszStart) ); + vecEndPos = CalcLocus_Velocity( this, pActivator, STRING(m_iszEnd) ); + + pBeam = CBeam::BeamCreate( STRING(m_iszSprite), m_iWidth ); + pBeam->PointsInit( vecStartPos, vecStartPos + vecEndPos ); + break; + } + pBeam->SetColor( pev->rendercolor.x, pev->rendercolor.y, pev->rendercolor.z ); + pBeam->SetBrightness( pev->renderamt ); + pBeam->SetNoise( m_iDistortion ); + pBeam->SetFrame( m_fFrame ); + pBeam->SetScrollRate( m_iScrollRate ); + pBeam->SetFlags( m_iFlags ); + pBeam->pev->dmg = m_fDamage; + pBeam->pev->frags = m_iDamageType; + pBeam->pev->spawnflags |= pev->spawnflags & (SF_BEAM_RING | + SF_BEAM_SPARKSTART | SF_BEAM_SPARKEND | SF_BEAM_DECALS); + if (m_fDuration) + { + pBeam->SetThink(&CBeam:: SUB_Remove ); + pBeam->SetNextThink( m_fDuration ); + } + pBeam->pev->targetname = m_iszTargetName; + + if (pev->target) + { + FireTargets( STRING(pev->target), pBeam, this, USE_TOGGLE, 0 ); + } +} + +void CLocusBeam::Spawn( void ) +{ + Precache(); + m_iFlags = 0; + if (pev->spawnflags & SF_LBEAM_SHADEIN) + m_iFlags |= BEAM_FSHADEIN; + if (pev->spawnflags & SF_LBEAM_SHADEOUT) + m_iFlags |= BEAM_FSHADEOUT; + if (pev->spawnflags & SF_LBEAM_SINE) + m_iFlags |= BEAM_FSINE; + if (pev->spawnflags & SF_LBEAM_SOLID) + m_iFlags |= BEAM_FSOLID; +} + + +//============================================= +//calc_x entities +//============================================= + +class CCalcPosition : public CPointEntity +{ +public: + Vector CalcPosition( CBaseEntity *pLocus ); +}; + +LINK_ENTITY_TO_CLASS( calc_position, CCalcPosition ); + +Vector CCalcPosition::CalcPosition( CBaseEntity *pLocus ) +{ + CBaseEntity *pSubject = UTIL_FindEntityByTargetname(NULL, STRING(pev->netname), pLocus); + + Vector vecOffset = CalcLocus_Velocity( this, pLocus, STRING(pev->message)); + + if( FNullEnt( pSubject )) + return vecOffset; + + Vector vecPosition; + Vector vecJunk; + + Vector vecResult; + switch (pev->impulse) + { + case 1: //eyes + vecResult = vecOffset + pSubject->EyePosition(); + //ALERT(at_console, "calc_subpos returns %f %f %f\n", vecResult.x, vecResult.y, vecResult.z); + return vecResult; + //return vecOffset + pLocus->EyePosition(); + case 2: // top + return vecOffset + pSubject->pev->origin + Vector( + (pSubject->pev->mins.x + pSubject->pev->maxs.x)/2, + (pSubject->pev->mins.y + pSubject->pev->maxs.y)/2, + pSubject->pev->maxs.z + ); + case 3: // centre + return vecOffset + pSubject->pev->origin + Vector( + (pSubject->pev->mins.x + pSubject->pev->maxs.x)/2, + (pSubject->pev->mins.y + pSubject->pev->maxs.y)/2, + (pSubject->pev->mins.z + pSubject->pev->maxs.z)/2 + ); + case 4: // bottom + return vecOffset + pSubject->pev->origin + Vector( + (pSubject->pev->mins.x + pSubject->pev->maxs.x)/2, + (pSubject->pev->mins.y + pSubject->pev->maxs.y)/2, + pSubject->pev->mins.z + ); + case 5: + // this could cause problems. + // is there a good way to check whether it's really a CBaseAnimating? + ((CBaseAnimating*)pSubject)->GetAttachment( 0, vecPosition, vecJunk ); + return vecOffset + vecPosition; + case 6: + ((CBaseAnimating*)pSubject)->GetAttachment( 1, vecPosition, vecJunk ); + return vecOffset + vecPosition; + case 7: + ((CBaseAnimating*)pSubject)->GetAttachment( 2, vecPosition, vecJunk ); + return vecOffset + vecPosition; + case 8: + ((CBaseAnimating*)pSubject)->GetAttachment( 3, vecPosition, vecJunk ); + return vecOffset + vecPosition; + case 9: + return vecOffset + pSubject->pev->origin + Vector( + RANDOM_FLOAT(pSubject->pev->mins.x, pSubject->pev->maxs.x), + RANDOM_FLOAT(pSubject->pev->mins.y, pSubject->pev->maxs.y), + RANDOM_FLOAT(pSubject->pev->mins.z, pSubject->pev->maxs.z) + ); + default: + return vecOffset + pSubject->pev->origin; + } +} + +//======================================================= + +class CCalcRatio : public CPointEntity +{ +public: + float CalcRatio( CBaseEntity *pLocus ); +}; + +LINK_ENTITY_TO_CLASS( calc_ratio, CCalcRatio ); + +float CCalcRatio::CalcRatio( CBaseEntity *pLocus ) +{ + float fBasis = CalcLocus_Ratio( pLocus, STRING(pev->target)); + + switch (pev->impulse) + { + case 1: fBasis = 1-fBasis; break; //reversed + case 2: fBasis = -fBasis; break; //negative + case 3: fBasis = 1/fBasis; break; //reciprocal + } + + fBasis += CalcLocus_Ratio( pLocus, STRING(pev->netname)); + fBasis = fBasis * CalcLocus_Ratio( pLocus, STRING(pev->message)); + + if (!FStringNull(pev->noise)) + { + float fMin = CalcLocus_Ratio( pLocus, STRING(pev->noise)); + + if (!FStringNull(pev->noise1)) + { + float fMax = CalcLocus_Ratio( pLocus, STRING(pev->noise1)); + + if (fBasis >= fMin && fBasis <= fMax) + return fBasis; + switch ((int)pev->frags) + { + case 0: + if (fBasis < fMin) + return fMin; + else + return fMax; + case 1: + while (fBasis < fMin) + fBasis += fMax - fMin; + while (fBasis > fMax) + fBasis -= fMax - fMin; + return fBasis; + case 2: + while (fBasis < fMin || fBasis > fMax) + { + if (fBasis < fMin) + fBasis = fMin + fMax - fBasis; + else + fBasis = fMax + fMax - fBasis; + } + return fBasis; + } + } + + if (fBasis > fMin) + return fBasis; + else + return fMin; // crop to nearest value + } + else if (!FStringNull(pev->noise1)) + { + float fMax = CalcLocus_Ratio( pLocus, STRING(pev->noise1)); + + if (fBasis < fMax) + return fBasis; + else + return fMax; // crop to nearest value + } + else + return fBasis; +} + + +//======================================================= +#define SF_CALCVELOCITY_NORMALIZE 1 +#define SF_CALCVELOCITY_SWAPZ 2 +class CCalcSubVelocity : public CPointEntity +{ + Vector Convert( CBaseEntity *pLocus, Vector vecVel ); + Vector ConvertAngles( CBaseEntity *pLocus, Vector vecAngles ); +public: + Vector CalcVelocity( CBaseEntity *pLocus ); +}; + +LINK_ENTITY_TO_CLASS( calc_subvelocity, CCalcSubVelocity ); + +Vector CCalcSubVelocity::CalcVelocity( CBaseEntity *pLocus ) +{ + pLocus = UTIL_FindEntityByTargetname( NULL, STRING(pev->netname), pLocus ); + + Vector vecAngles; + Vector vecJunk; + + switch (pev->impulse) + { + case 1: //angles + return ConvertAngles( pLocus, pLocus->pev->angles ); + case 2: //v_angle + return ConvertAngles( pLocus, pLocus->pev->v_angle ); + case 5: + // this could cause problems. + // is there a good way to check whether it's really a CBaseAnimating? + ((CBaseAnimating*)pLocus)->GetAttachment( 0, vecJunk, vecAngles ); + return ConvertAngles( pLocus, vecAngles ); + case 6: + ((CBaseAnimating*)pLocus)->GetAttachment( 1, vecJunk, vecAngles ); + return ConvertAngles( pLocus, vecAngles ); + case 7: + ((CBaseAnimating*)pLocus)->GetAttachment( 2, vecJunk, vecAngles ); + return ConvertAngles( pLocus, vecAngles ); + case 8: + ((CBaseAnimating*)pLocus)->GetAttachment( 3, vecJunk, vecAngles ); + return ConvertAngles( pLocus, vecAngles ); + default: + return Convert( pLocus, pLocus->pev->velocity ); + } +} + +Vector CCalcSubVelocity::Convert( CBaseEntity *pLocus, Vector vecDir ) +{ + if (pev->spawnflags & SF_CALCVELOCITY_NORMALIZE) + vecDir = vecDir.Normalize(); + + float fRatio = CalcLocus_Ratio( pLocus, STRING(pev->noise) ); + Vector vecOffset = CalcLocus_Velocity( this, pLocus, STRING(pev->message)); + + Vector vecResult = vecOffset + (vecDir*fRatio); + + if (pev->spawnflags & SF_CALCVELOCITY_SWAPZ) + vecResult.z = -vecResult.z; +// ALERT(at_console, "calc_subvel returns (%f %f %f) = (%f %f %f) + ((%f %f %f) * %f)\n", vecResult.x, vecResult.y, vecResult.z, vecOffset.x, vecOffset.y, vecOffset.z, vecDir.x, vecDir.y, vecDir.z, fRatio); + return vecResult; +} + +Vector CCalcSubVelocity::ConvertAngles( CBaseEntity *pLocus, Vector vecAngles ) +{ + UTIL_MakeVectors( vecAngles ); + return Convert( pLocus, gpGlobals->v_forward ); +} + + + +//======================================================= +class CCalcVelocityPath : public CPointEntity +{ +public: + Vector CalcVelocity( CBaseEntity *pLocus ); +}; + +LINK_ENTITY_TO_CLASS( calc_velocity_path, CCalcVelocityPath ); + +Vector CCalcVelocityPath::CalcVelocity( CBaseEntity *pLocus ) +{ + Vector vecStart = CalcLocus_Position( this, pLocus, STRING(pev->target) ); +// ALERT(at_console, "vecStart %f %f %f\n", vecStart.x, vecStart.y, vecStart.z); + Vector vecOffs; + float fFactor = CalcLocus_Ratio( pLocus, STRING(pev->noise) ); + + switch ((int)pev->armorvalue) + { + case 0: + vecOffs = CalcLocus_Position( this, pLocus, STRING(pev->netname) ) - vecStart; + break; + case 1: + vecOffs = CalcLocus_Velocity( this, pLocus, STRING(pev->netname) ); + break; + } +// ALERT(at_console, "vecOffs %f %f %f\n", vecOffs.x, vecOffs.y, vecOffs.z); + + if (pev->health) + { + float len = vecOffs.Length(); + switch ((int)pev->health) + { + case 1: + vecOffs = vecOffs/len; + break; + case 2: + vecOffs = vecOffs/(len*len); + break; + case 3: + vecOffs = vecOffs/(len*len*len); + break; + case 4: + vecOffs = vecOffs*len; + break; + } + } + + vecOffs = vecOffs * fFactor; + + if (pev->frags) + { + TraceResult tr; + IGNORE_GLASS iIgnoreGlass = ignore_glass; + IGNORE_MONSTERS iIgnoreMonsters = ignore_monsters; + + switch ((int)pev->frags) + { + case 2: + iIgnoreGlass = dont_ignore_glass; + break; + case 4: + iIgnoreGlass = dont_ignore_glass; + // fall through + case 3: + iIgnoreMonsters = dont_ignore_monsters; + break; + } + + UTIL_TraceLine( vecStart, vecStart+vecOffs, iIgnoreMonsters, iIgnoreGlass, NULL, &tr ); + vecOffs = tr.vecEndPos - vecStart; + } + +// ALERT(at_console, "path: %f %f %f\n", vecOffs.x, vecOffs.y, vecOffs.z); + return vecOffs; +} + + +//======================================================= +class CCalcVelocityPolar : public CPointEntity +{ +public: + Vector CalcVelocity( CBaseEntity *pLocus ); +}; + +LINK_ENTITY_TO_CLASS( calc_velocity_polar, CCalcVelocityPolar ); + +Vector CCalcVelocityPolar::CalcVelocity( CBaseEntity *pLocus ) +{ + Vector vecBasis = CalcLocus_Velocity( this, pLocus, STRING(pev->netname) ); + Vector vecAngles = UTIL_VecToAngles( vecBasis ) + pev->angles; + Vector vecOffset = CalcLocus_Velocity( this, pLocus, STRING(pev->message) ); + + float fFactor = CalcLocus_Ratio( pLocus, STRING(pev->noise) ); + + if (!(pev->spawnflags & SF_CALCVELOCITY_NORMALIZE)) + fFactor = fFactor * vecBasis.Length(); + + UTIL_MakeVectors( vecAngles ); + return (gpGlobals->v_forward * fFactor) + vecOffset; +} + +//======================================================= + +// Position marker +class CMark : public CPointEntity +{ +public: + Vector CalcVelocity(CBaseEntity *pLocus) { return pev->movedir; } + float CalcRatio(CBaseEntity *pLocus) { return pev->frags; } + void Think( void ) { SUB_Remove(); } +}; + +class CLocusVariable : public CPointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + Vector CalcVelocity(CBaseEntity *pLocus) { return pev->movedir; } + float CalcRatio(CBaseEntity *pLocus) { return pev->frags; } + + void KeyValue( KeyValueData *pkvd ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int m_iszPosition; + int m_iszVelocity; + int m_iszRatio; + int m_iszTargetName; + int m_iszFireOnSpawn; + float m_fDuration; +}; + +TYPEDESCRIPTION CLocusVariable::m_SaveData[] = +{ + DEFINE_FIELD( CLocusVariable, m_iszPosition, FIELD_STRING), + DEFINE_FIELD( CLocusVariable, m_iszVelocity, FIELD_STRING), + DEFINE_FIELD( CLocusVariable, m_iszRatio, FIELD_STRING), + DEFINE_FIELD( CLocusVariable, m_iszTargetName, FIELD_STRING), + DEFINE_FIELD( CLocusVariable, m_iszFireOnSpawn, FIELD_STRING), + DEFINE_FIELD( CLocusVariable, m_fDuration, FIELD_FLOAT), +}; + +IMPLEMENT_SAVERESTORE( CLocusVariable, CPointEntity ); +LINK_ENTITY_TO_CLASS( locus_variable, CLocusVariable ); + +void CLocusVariable :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iszPosition")) + { + m_iszPosition = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszVelocity")) + { + m_iszVelocity = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszRatio")) + { + m_iszRatio = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszTargetName")) + { + m_iszTargetName = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszFireOnSpawn")) + { + m_iszFireOnSpawn = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fDuration")) + { + m_fDuration = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +void CLocusVariable::Spawn( void ) +{ + SetMovedir(pev); +} + +void CLocusVariable::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + Vector vecPos = g_vecZero; + Vector vecDir = g_vecZero; + float fRatio = 0; + if (m_iszPosition) + vecPos = CalcLocus_Position(this, pActivator, STRING(m_iszPosition)); + if (m_iszVelocity) + vecDir = CalcLocus_Velocity(this, pActivator, STRING(m_iszVelocity)); + if (m_iszRatio) + fRatio = CalcLocus_Ratio(pActivator, STRING(m_iszRatio)); + + if (m_iszTargetName) + { + CMark *pMark = GetClassPtr( (CMark*)NULL ); + pMark->pev->classname = MAKE_STRING("mark"); + pMark->pev->origin = vecPos; + pMark->pev->movedir = vecDir; + pMark->pev->frags = fRatio; + pMark->pev->targetname = m_iszTargetName; + pMark->SetNextThink(m_fDuration); + + FireTargets(STRING(m_iszFireOnSpawn), pMark, this, USE_TOGGLE, 0); + } + else + { + pev->origin = vecPos; + pev->movedir = vecDir; + pev->frags = fRatio; + + FireTargets(STRING(m_iszFireOnSpawn), this, this, USE_TOGGLE, 0); + } +} diff --git a/dlls/locus.h b/dlls/locus.h new file mode 100644 index 0000000..f1093a1 --- /dev/null +++ b/dlls/locus.h @@ -0,0 +1,3 @@ +Vector CalcLocus_Position ( CBaseEntity *pEntity, CBaseEntity *pLocus, const char *szText ); +Vector CalcLocus_Velocity ( CBaseEntity *pEntity, CBaseEntity *pLocus, const char *szText ); +float CalcLocus_Ratio ( CBaseEntity *pLocus, const char *szText ); \ No newline at end of file diff --git a/dlls/maprules.cpp b/dlls/maprules.cpp new file mode 100644 index 0000000..1413f88 --- /dev/null +++ b/dlls/maprules.cpp @@ -0,0 +1,964 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +// ------------------------------------------- +// +// maprules.cpp +// +// This module contains entities for implementing/changing game +// rules dynamically within each map (.BSP) +// +// ------------------------------------------- + +#include "extdll.h" +#include "eiface.h" +#include "util.h" +#include "gamerules.h" +#include "maprules.h" +#include "cbase.h" +#include "player.h" + +class CRuleEntity : public CBaseEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void SetMaster( int iszMaster ) { m_iszMaster = iszMaster; } + +protected: + BOOL CanFireForActivator( CBaseEntity *pActivator ); + +private: + string_t m_iszMaster; +}; + +TYPEDESCRIPTION CRuleEntity::m_SaveData[] = +{ + DEFINE_FIELD( CRuleEntity, m_iszMaster, FIELD_STRING), +}; + +IMPLEMENT_SAVERESTORE( CRuleEntity, CBaseEntity ); + + +void CRuleEntity::Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects |= EF_NODRAW; +} + + +void CRuleEntity::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "master")) + { + SetMaster( ALLOC_STRING(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +BOOL CRuleEntity::CanFireForActivator( CBaseEntity *pActivator ) +{ + if (!pActivator) + { + return TRUE; + } + else if ( m_iszMaster ) + { + if ( UTIL_IsMasterTriggered( m_iszMaster, pActivator ) ) + return TRUE; + else + return FALSE; + } + + return TRUE; +} + +// +// CRulePointEntity -- base class for all rule "point" entities (not brushes) +// +class CRulePointEntity : public CRuleEntity +{ +public: + void Spawn( void ); +}; + +void CRulePointEntity::Spawn( void ) +{ + CRuleEntity::Spawn(); + pev->frame = 0; + pev->model = 0; +} + +// +// CRuleBrushEntity -- base class for all rule "brush" entities (not brushes) +// Default behavior is to set up like a trigger, invisible, but keep the model for volume testing +// +class CRuleBrushEntity : public CRuleEntity +{ +public: + void Spawn( void ); + +private: +}; + +void CRuleBrushEntity::Spawn( void ) +{ + SET_MODEL( edict(), STRING(pev->model) ); + CRuleEntity::Spawn(); +} + + +// CGameScore / game_score -- award points to player / team +// Points +/- total +// Flag: Allow negative scores SF_SCORE_NEGATIVE +// Flag: Award points to team in teamplay SF_SCORE_TEAM + +#define SF_SCORE_NEGATIVE 0x0001 +#define SF_SCORE_TEAM 0x0002 + +class CGameScore : public CRulePointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + inline int Points( void ) { return pev->frags; } + inline BOOL AllowNegativeScore( void ) { return pev->spawnflags & SF_SCORE_NEGATIVE; } + inline BOOL AwardToTeam( void ) { return pev->spawnflags & SF_SCORE_TEAM; } + + inline void SetPoints( int points ) { pev->frags = points; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_score, CGameScore ); + + +void CGameScore::Spawn( void ) +{ + CRulePointEntity::Spawn(); +} + + +void CGameScore::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "points")) + { + SetPoints( atoi(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CRulePointEntity::KeyValue( pkvd ); +} + + + +void CGameScore::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + // Only players can use this + if ( pActivator->IsPlayer() ) + { + if ( AwardToTeam() ) + { + pActivator->AddPointsToTeam( Points(), AllowNegativeScore() ); + } + else + { + pActivator->AddPoints( Points(), AllowNegativeScore() ); + } + } +} + + +// CGameEnd / game_end -- Ends the game in MP + +class CGameEnd : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +private: +}; + +LINK_ENTITY_TO_CLASS( game_end, CGameEnd ); + + +void CGameEnd::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + g_pGameRules->EndMultiplayerGame(); +} + + +// +// CGameText / game_text -- NON-Localized HUD Message (use env_message to display a titles.txt message) +// Flag: All players SF_ENVTEXT_ALLPLAYERS +// + + +#define SF_ENVTEXT_ALLPLAYERS 0x0001 +#define SF_ENVTEXT_ONLY_ONCE 0x0002 + + +class CGameText : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + inline BOOL MessageToAll( void ) { return (pev->spawnflags & SF_ENVTEXT_ALLPLAYERS); } + inline void MessageSet( const char *pMessage ) { pev->message = ALLOC_STRING(pMessage); } + inline const char *MessageGet( void ) { return STRING(pev->message); } + + void EXPORT TriggerThink( void ); + +private: + + hudtextparms_t m_textParms; + CBaseEntity *m_pActivator; +}; + +LINK_ENTITY_TO_CLASS( game_text, CGameText ); + +// Save parms as a block. Will break save/restore if the structure changes, but this entity didn't ship with Half-Life, so +// it can't impact saved Half-Life games. +TYPEDESCRIPTION CGameText::m_SaveData[] = +{ + DEFINE_ARRAY( CGameText, m_textParms, FIELD_CHARACTER, sizeof(hudtextparms_t) ), + DEFINE_FIELD( CGameText, m_pActivator, FIELD_CLASSPTR ), +}; + +IMPLEMENT_SAVERESTORE( CGameText, CRulePointEntity ); + + +void CGameText::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "channel")) + { + m_textParms.channel = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "x")) + { + m_textParms.x = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "y")) + { + m_textParms.y = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "effect")) + { + m_textParms.effect = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "color")) + { + int color[4]; + UTIL_StringToIntArray( color, 4, pkvd->szValue ); + m_textParms.r1 = color[0]; + m_textParms.g1 = color[1]; + m_textParms.b1 = color[2]; + m_textParms.a1 = color[3]; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "color2")) + { + int color[4]; + UTIL_StringToIntArray( color, 4, pkvd->szValue ); + m_textParms.r2 = color[0]; + m_textParms.g2 = color[1]; + m_textParms.b2 = color[2]; + m_textParms.a2 = color[3]; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "fadein")) + { + m_textParms.fadeinTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "fadeout")) + { + m_textParms.fadeoutTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "holdtime")) + { + m_textParms.holdTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "fxtime")) + { + m_textParms.fxTime = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CRulePointEntity::KeyValue( pkvd ); +} + + +void CGameText::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( MessageToAll() ) + { + UTIL_HudMessageAll( m_textParms, MessageGet() ); + } + else + { + if ( pActivator && pActivator->IsNetClient() ) + { + UTIL_HudMessage( pActivator, m_textParms, MessageGet() ); + } + } + + if ( pev->target ) + { + m_pActivator = pActivator; + SetThink(&CGameText:: TriggerThink ); + SetNextThink( m_textParms.fadeinTime + m_textParms.holdTime + m_textParms.fadeoutTime ); +// ALERT(at_console, "GameText sets NextThink = %f\n", m_textParms.fadeinTime + m_textParms.holdTime + m_textParms.fadeoutTime); +} + else if ( pev->spawnflags & SF_ENVTEXT_ONLY_ONCE ) + { + SetThink(&CGameText:: SUB_Remove ); + SetNextThink( 0.1 ); + } +} + +//LRC +void CGameText::TriggerThink( void ) +{ +// ALERT(at_console, "GameText uses targets\n"); + SUB_UseTargets( m_pActivator, USE_TOGGLE, 0 ); + + if ( pev->spawnflags & SF_ENVTEXT_ONLY_ONCE ) + { + SetThink(&CGameText:: SUB_Remove ); + SetNextThink( 0.1 ); + } +} + + + +// +// CGameTeamMaster / game_team_master -- "Masters" like multisource, but based on the team of the activator +// Only allows mastered entity to fire if the team matches my team +// +// team index (pulled from server team list "mp_teamlist" +// Flag: Remove on Fire +// Flag: Any team until set? -- Any team can use this until the team is set (otherwise no teams can use it) +// + +#define SF_TEAMMASTER_FIREONCE 0x0001 +#define SF_TEAMMASTER_ANYTEAM 0x0002 + +class CGameTeamMaster : public CRulePointEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +// int ObjectCaps( void ) { return CRulePointEntity:: ObjectCaps() | FCAP_MASTER; } + + BOOL IsTriggered( CBaseEntity *pActivator ); + const char *TeamID( void ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_TEAMMASTER_FIREONCE) ? TRUE : FALSE; } + inline BOOL AnyTeam( void ) { return (pev->spawnflags & SF_TEAMMASTER_ANYTEAM) ? TRUE : FALSE; } + +private: + BOOL TeamMatch( CBaseEntity *pActivator ); + + int m_teamIndex; + USE_TYPE triggerType; +}; + +LINK_ENTITY_TO_CLASS( game_team_master, CGameTeamMaster ); + +void CGameTeamMaster::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "teamindex")) + { + m_teamIndex = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "triggerstate")) + { + int type = atoi( pkvd->szValue ); + switch( type ) + { + case 0: + triggerType = USE_OFF; + break; + case 2: + triggerType = USE_TOGGLE; + break; + default: + triggerType = USE_ON; + break; + } + pkvd->fHandled = TRUE; + } + else + CRulePointEntity::KeyValue( pkvd ); +} + + +void CGameTeamMaster::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( useType == USE_SET ) + { + if ( value < 0 ) + { + m_teamIndex = -1; + } + else + { + m_teamIndex = g_pGameRules->GetTeamIndex( pActivator->TeamID() ); + } + return; + } + + if ( TeamMatch( pActivator ) ) + { + SUB_UseTargets( pActivator, triggerType, value ); + if ( RemoveOnFire() ) + UTIL_Remove( this ); + } +} + + +BOOL CGameTeamMaster::IsTriggered( CBaseEntity *pActivator ) +{ + return TeamMatch( pActivator ); +} + + +const char *CGameTeamMaster::TeamID( void ) +{ + if ( m_teamIndex < 0 ) // Currently set to "no team" + return ""; + + return g_pGameRules->GetIndexedTeamName( m_teamIndex ); // UNDONE: Fill this in with the team from the "teamlist" +} + + +BOOL CGameTeamMaster::TeamMatch( CBaseEntity *pActivator ) +{ + if ( m_teamIndex < 0 && AnyTeam() ) + return TRUE; + + if ( !pActivator ) + return FALSE; + + return UTIL_TeamsMatch( pActivator->TeamID(), TeamID() ); +} + + +// +// CGameTeamSet / game_team_set -- Changes the team of the entity it targets to the activator's team +// Flag: Fire once +// Flag: Clear team -- Sets the team to "NONE" instead of activator + +#define SF_TEAMSET_FIREONCE 0x0001 +#define SF_TEAMSET_CLEARTEAM 0x0002 + +class CGameTeamSet : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_TEAMSET_FIREONCE) ? TRUE : FALSE; } + inline BOOL ShouldClearTeam( void ) { return (pev->spawnflags & SF_TEAMSET_CLEARTEAM) ? TRUE : FALSE; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_team_set, CGameTeamSet ); + + +void CGameTeamSet::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( ShouldClearTeam() ) + { + SUB_UseTargets( pActivator, USE_SET, -1 ); + } + else + { + SUB_UseTargets( pActivator, USE_SET, 0 ); + } + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + +// +// CGamePlayerZone / game_player_zone -- players in the zone fire my target when I'm fired +// +// Needs master? +class CGamePlayerZone : public CRuleBrushEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + string_t m_iszInTarget; + string_t m_iszOutTarget; + string_t m_iszInCount; + string_t m_iszOutCount; +}; + +LINK_ENTITY_TO_CLASS( game_zone_player, CGamePlayerZone ); +TYPEDESCRIPTION CGamePlayerZone::m_SaveData[] = +{ + DEFINE_FIELD( CGamePlayerZone, m_iszInTarget, FIELD_STRING ), + DEFINE_FIELD( CGamePlayerZone, m_iszOutTarget, FIELD_STRING ), + DEFINE_FIELD( CGamePlayerZone, m_iszInCount, FIELD_STRING ), + DEFINE_FIELD( CGamePlayerZone, m_iszOutCount, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CGamePlayerZone, CRuleBrushEntity ); + +void CGamePlayerZone::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "intarget")) + { + m_iszInTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "outtarget")) + { + m_iszOutTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "incount")) + { + m_iszInCount = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "outcount")) + { + m_iszOutCount = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CRuleBrushEntity::KeyValue( pkvd ); +} + +void CGamePlayerZone::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int playersInCount = 0; + int playersOutCount = 0; + + if ( !CanFireForActivator( pActivator ) ) + return; + + CBaseEntity *pPlayer = NULL; + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + { + TraceResult trace; + int hullNumber; + BOOL inside = FALSE; + + if (pev->origin == g_vecZero) //LRC - to support movewith + { + hullNumber = human_hull; + if ( pPlayer->pev->flags & FL_DUCKING ) + { + hullNumber = head_hull; + } + UTIL_TraceModel( pPlayer->pev->origin, pPlayer->pev->origin, hullNumber, edict(), &trace ); + inside = trace.fStartSolid; + } + else + { + //LIMITATION: this doesn't allow for non-cuboid game_zone_player entities. + // (is that a problem?) + inside = this->Intersects(pPlayer); + } + + if ( inside ) + { + playersInCount++; + if ( m_iszInTarget ) + { + FireTargets( STRING(m_iszInTarget), pPlayer, pActivator, useType, value ); + } + } + else + { + playersOutCount++; + if ( m_iszOutTarget ) + { + FireTargets( STRING(m_iszOutTarget), pPlayer, pActivator, useType, value ); + } + } + } + } + + if ( m_iszInCount ) + { + FireTargets( STRING(m_iszInCount), pActivator, this, USE_SET, playersInCount ); + } + + if ( m_iszOutCount ) + { + FireTargets( STRING(m_iszOutCount), pActivator, this, USE_SET, playersOutCount ); + } +} + + + +// +// CGamePlayerHurt / game_player_hurt -- Damages the player who fires it +// Flag: Fire once + +#define SF_PKILL_FIREONCE 0x0001 +class CGamePlayerHurt : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_PKILL_FIREONCE) ? TRUE : FALSE; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_player_hurt, CGamePlayerHurt ); + + +void CGamePlayerHurt::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( pActivator->IsPlayer() ) + { + if ( pev->dmg < 0 ) + pActivator->TakeHealth( -pev->dmg, DMG_GENERIC ); + else + pActivator->TakeDamage( pev, pev, pev->dmg, DMG_GENERIC ); + } + + SUB_UseTargets( pActivator, useType, value ); + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + + +// +// CGameCounter / game_counter -- Counts events and fires target +// Flag: Fire once +// Flag: Reset on Fire + +#define SF_GAMECOUNT_FIREONCE 0x0001 +#define SF_GAMECOUNT_RESET 0x0002 + +class CGameCounter : public CRulePointEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_GAMECOUNT_FIREONCE) ? TRUE : FALSE; } + inline BOOL ResetOnFire( void ) { return (pev->spawnflags & SF_GAMECOUNT_RESET) ? TRUE : FALSE; } + + inline void CountUp( void ) { pev->frags++; } + inline void CountDown( void ) { pev->frags--; } + inline void ResetCount( void ) { pev->frags = pev->dmg; } + inline int CountValue( void ) { return pev->frags; } + inline int LimitValue( void ) { return pev->health; } + + inline BOOL HitLimit( void ) { return CountValue() == LimitValue(); } + +private: + + inline void SetCountValue( int value ) { pev->frags = value; } + inline void SetInitialValue( int value ) { pev->dmg = value; } +}; + +LINK_ENTITY_TO_CLASS( game_counter, CGameCounter ); + +void CGameCounter::Spawn( void ) +{ + // Save off the initial count + SetInitialValue( CountValue() ); + CRulePointEntity::Spawn(); +} + + +void CGameCounter::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + switch( useType ) + { + case USE_ON: + case USE_TOGGLE: + CountUp(); + break; + + case USE_OFF: + CountDown(); + break; + + case USE_SET: + SetCountValue( (int)value ); + break; + } + + if ( HitLimit() ) + { + SUB_UseTargets( pActivator, USE_TOGGLE, 0 ); + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } + + if ( ResetOnFire() ) + { + ResetCount(); + } + } +} + + + +// +// CGameCounterSet / game_counter_set -- Sets the counter's value +// Flag: Fire once + +#define SF_GAMECOUNTSET_FIREONCE 0x0001 + +class CGameCounterSet : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_GAMECOUNTSET_FIREONCE) ? TRUE : FALSE; } + +private: +}; + +LINK_ENTITY_TO_CLASS( game_counter_set, CGameCounterSet ); + + +void CGameCounterSet::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + SUB_UseTargets( pActivator, USE_SET, pev->frags ); + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + +// +// CGamePlayerEquip / game_playerequip -- Sets the default player equipment +// Flag: USE Only + +#define SF_PLAYEREQUIP_USEONLY 0x0001 +#define MAX_EQUIP 32 + +class CGamePlayerEquip : public CRulePointEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Touch( CBaseEntity *pOther ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + inline BOOL UseOnly( void ) { return (pev->spawnflags & SF_PLAYEREQUIP_USEONLY) ? TRUE : FALSE; } + +private: + + void EquipPlayer( CBaseEntity *pPlayer ); + + string_t m_weaponNames[MAX_EQUIP]; + int m_weaponCount[MAX_EQUIP]; +}; + +LINK_ENTITY_TO_CLASS( game_player_equip, CGamePlayerEquip ); + + +void CGamePlayerEquip::KeyValue( KeyValueData *pkvd ) +{ + CRulePointEntity::KeyValue( pkvd ); + + if ( !pkvd->fHandled ) + { + for ( int i = 0; i < MAX_EQUIP; i++ ) + { + if ( !m_weaponNames[i] ) + { + char tmp[128]; + + UTIL_StripToken( pkvd->szKeyName, tmp ); + + m_weaponNames[i] = ALLOC_STRING(tmp); + m_weaponCount[i] = atoi(pkvd->szValue); + m_weaponCount[i] = max(1,m_weaponCount[i]); + pkvd->fHandled = TRUE; + break; + } + } + } +} + + +void CGamePlayerEquip::Touch( CBaseEntity *pOther ) +{ + if ( !CanFireForActivator( pOther ) ) + return; + + if ( UseOnly() ) + return; + + EquipPlayer( pOther ); +} + +void CGamePlayerEquip::EquipPlayer( CBaseEntity *pEntity ) +{ + CBasePlayer *pPlayer = NULL; + + if ( pEntity->IsPlayer() ) + { + pPlayer = (CBasePlayer *)pEntity; + } + + if ( !pPlayer ) + return; + + for ( int i = 0; i < MAX_EQUIP; i++ ) + { + if ( !m_weaponNames[i] ) + break; + for ( int j = 0; j < m_weaponCount[i]; j++ ) + { + pPlayer->GiveNamedItem( STRING(m_weaponNames[i]) ); + } + } +} + + +void CGamePlayerEquip::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + EquipPlayer( pActivator ); +} + + +// +// CGamePlayerTeam / game_player_team -- Changes the team of the player who fired it +// Flag: Fire once +// Flag: Kill Player +// Flag: Gib Player + +#define SF_PTEAM_FIREONCE 0x0001 +#define SF_PTEAM_KILL 0x0002 +#define SF_PTEAM_GIB 0x0004 + +class CGamePlayerTeam : public CRulePointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +private: + + inline BOOL RemoveOnFire( void ) { return (pev->spawnflags & SF_PTEAM_FIREONCE) ? TRUE : FALSE; } + inline BOOL ShouldKillPlayer( void ) { return (pev->spawnflags & SF_PTEAM_KILL) ? TRUE : FALSE; } + inline BOOL ShouldGibPlayer( void ) { return (pev->spawnflags & SF_PTEAM_GIB) ? TRUE : FALSE; } + + const char *TargetTeamName( const char *pszTargetName ); +}; + +LINK_ENTITY_TO_CLASS( game_player_team, CGamePlayerTeam ); + + +const char *CGamePlayerTeam::TargetTeamName( const char *pszTargetName ) +{ + CBaseEntity *pTeamEntity = NULL; + + while ((pTeamEntity = UTIL_FindEntityByTargetname( pTeamEntity, pszTargetName )) != NULL) + { + if ( FClassnameIs( pTeamEntity->pev, "game_team_master" ) ) + return pTeamEntity->TeamID(); + } + + return NULL; +} + + +void CGamePlayerTeam::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !CanFireForActivator( pActivator ) ) + return; + + if ( pActivator->IsPlayer() ) + { + const char *pszTargetTeam = TargetTeamName( STRING(pev->target) ); + if ( pszTargetTeam ) + { + CBasePlayer *pPlayer = (CBasePlayer *)pActivator; + g_pGameRules->ChangePlayerTeam( pPlayer, pszTargetTeam, ShouldKillPlayer(), ShouldGibPlayer() ); + } + } + + if ( RemoveOnFire() ) + { + UTIL_Remove( this ); + } +} + + diff --git a/dlls/maprules.h b/dlls/maprules.h new file mode 100644 index 0000000..57f9939 --- /dev/null +++ b/dlls/maprules.h @@ -0,0 +1,22 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef MAPRULES_H +#define MAPRULES_H + + + +#endif // MAPRULES_H + diff --git a/dlls/monster_head_controller.h b/dlls/monster_head_controller.h new file mode 100644 index 0000000..33ae5b0 --- /dev/null +++ b/dlls/monster_head_controller.h @@ -0,0 +1,169 @@ +#ifndef MONSTER_FLASHLIGHT_H +#define MONSTER_FLASHLIGHT_H + +//Flashlight +class CFlashlight : public CPointEntity +{ +public: + void Spawn( entvars_t *pevOwner ) + { + pev->classname = MAKE_STRING( "head_flashlight" ); + pev->owner = ENT( pevOwner ); + pev->solid = SOLID_NOT; + pev->skin = ENTINDEX( ENT( pevOwner )); + pev->body = 3; //TESTTEST + pev->aiment = ENT(pevOwner); + pev->movetype = MOVETYPE_FOLLOW; + Precache(); + + pev->renderfx = 71; // dynamic light + + SET_MODEL(ENT(pev),"sprites/null.spr"); // should be visible to send to client + UTIL_SetOrigin( this, pevOwner->origin ); + + pev->rendercolor.x = pev->rendercolor.y = pev->rendercolor.z = 255; + pev->renderamt = 90.0f; // (already divided by 8); + pev->rendermode = 0; // number of light texture + pev->scale = 90.0f; + + // spotlight + // create second PVS point + pev->enemy = Create( "info_target", pev->origin, g_vecZero, edict() )->edict(); + SET_MODEL( pev->enemy, "sprites/null.spr" ); // allow to collect visibility info + UTIL_SetSize ( VARS( pev->enemy ), Vector ( -8, -8, -8 ), Vector ( 8, 8, 8 ) ); + + // to prevent disapperaing from PVS (renderamt is premultiplied by 0.125) + UTIL_SetSize( pev, Vector( -pev->renderamt, -pev->renderamt, -pev->renderamt ), Vector( pev->renderamt, pev->renderamt, pev->renderamt )); + + pev->effects |= EF_NODRAW; + } + + void Precache() + { + PRECACHE_MODEL("sprites/null.spr"); + } + + STATE GetState( void ) + { + if (pev->effects & EF_NODRAW) + return STATE_OFF; + else + return STATE_ON; + } + + void On() + { + pev->effects &= ~EF_NODRAW; + + SetThink( PVSThink ); + SetNextThink( 0.1f ); + } + + void Off() + { + pev->effects |= EF_NODRAW; + DontThink(); + } + + void UpdatePVSPoint( void ) + { + TraceResult tr; + Vector forward; + + UTIL_MakeVectorsPrivate( pev->angles, forward, NULL, NULL ); + Vector vecSrc = pev->origin + forward * 8.0f; + Vector vecEnd = vecSrc + forward * (pev->renderamt * 8.0f); + UTIL_TraceLine( vecSrc, vecEnd, ignore_monsters, edict(), &tr ); + + // this is our second PVS point + CBaseEntity *pVisHelper = CBaseEntity::Instance( pev->enemy ); + if( pVisHelper ) UTIL_SetOrigin( pVisHelper, tr.vecEndPos + tr.vecPlaneNormal * 8.0f ); + } + + void EXPORT PVSThink( void ) + { + UpdatePVSPoint(); + + // static light should be updated in case + // moving platform under them + SetNextThink( m_pMoveWith ? 0.01f : 0.1f ); + } +}; + +#define DEFAULT_SAIR_HEIGTH 17 + +//MaSTeR: Head controller for monsters +class CHeadController : public CBaseEntity +{ +public: + void Spawn( CBaseEntity *pOwner ) + { + pev->classname = MAKE_STRING( "head_controller" ); + m_iLightLevel = 1000; // Because if m_iLightLevel = 0, monster can turn on the flashlight, even if world is bright. + m_iBackwardLen = 0; + pev->origin = pOwner->pev->origin + Vector ( 0 , 0 , 32 ); + pev->angles = pOwner->pev->angles; + m_hOwner = pOwner; + SetNextThink( 0.1 ); + } + + BOOL ShouldUseLights( void ) + { + if( m_iLightLevel < 10 ) + return TRUE; + return FALSE; + } + + void Think( void ) + { + if( m_hOwner == NULL ) + { + ALERT( at_error, "HeadController: onwer is lost!\n" ); + return; + } + + MeasureLightLevel(); + TraceBack(); + SetNextThink( 0.5 ); + } + + int GetLightLevel( void ) + { + return m_iLightLevel; + } + + void MeasureLightLevel( void ) + { + TraceResult tr; + pev->origin = m_hOwner->pev->origin + Vector( 0.0f, 0.0f, 32 ); + pev->angles = m_hOwner->pev->angles; + UTIL_MakeVectors( pev->angles ); + UTIL_TraceLine( pev->origin, pev->origin + gpGlobals->v_forward * 128.0f, ignore_monsters,ignore_glass, m_hOwner->edict(), &tr ); + m_iLightLevel = Illumination(); + } + + void TraceBack( void ) + { + TraceResult tr; + pev->origin = m_hOwner->pev->origin + Vector( 0, 0, DEFAULT_SAIR_HEIGTH ); + pev->angles = m_hOwner->pev->angles; + UTIL_MakeVectors( pev->angles ); + UTIL_TraceLine( pev->origin, pev->origin + gpGlobals->v_forward * -512.0f, ignore_monsters, dont_ignore_glass, pev->owner, &tr ); + m_iBackwardLen = (pev->origin - tr.vecEndPos).Length2D(); + } + + int GetBackTrace( void ) + { + return m_iBackwardLen; + } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int m_iLightLevel; + int m_iBackwardLen; + EHANDLE m_hOwner; +}; +#endif \ No newline at end of file diff --git a/dlls/monster_superofficer.cpp b/dlls/monster_superofficer.cpp new file mode 100644 index 0000000..09cc58a --- /dev/null +++ b/dlls/monster_superofficer.cpp @@ -0,0 +1,882 @@ +//===================================================================================================== +// +// Written by MaSTeR for PARANOIA 2 +// +//===================================================================================================== + +//Super Officer - an infected officer that spits his infected blood and kicks the player (melee attack) +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "nodes.h" +#include "effects.h" +#include "decals.h" +#include "soundent.h" +#include "game.h" + +//Specific defines +#define OFFICER_RUN_SPEED 100 +#define OFFICER_WALK_SPEED 70 +#define OFFICER_WALK_INJURED_SPEED 50 + +#define OFFICER_SPIT_DISTANCE 256 +#define OFFICER_MELEE_ATTACK_MIN_DIST 32 +#define OFFICER_NEXT_SPIT_TIME 1.5f + +int iOfficerBlood; + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_OFFICER_HOP = LAST_COMMON_SCHEDULE + 1, +}; + +//========================================================= +// monster-specific tasks +//========================================================= +enum +{ + TASK_OFFICER_HOP = LAST_COMMON_TASK + 1, +}; + +//========================================================= +// Oficer's infected blood projectile +//========================================================= +class COfficerBlood : public CBaseEntity +{ +public: + void Spawn( void ); + + static void Shoot( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ); + void Touch( CBaseEntity *pOther ); + void EXPORT Animate( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_maxFrame; +}; + +LINK_ENTITY_TO_CLASS( officerblood, COfficerBlood ); + +TYPEDESCRIPTION COfficerBlood::m_SaveData[] = +{ + DEFINE_FIELD( COfficerBlood, m_maxFrame, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( COfficerBlood, CBaseEntity ); + +void COfficerBlood::Spawn( void ) +{ + pev->movetype = MOVETYPE_FLY; + pev->classname = MAKE_STRING( "officerblood" ); + + pev->solid = SOLID_BBOX; + pev->rendermode = kRenderTransAlpha; + pev->renderamt = 255; + + SET_MODEL(ENT(pev), "sprites/bigspit.spr"); + pev->frame = 0; + pev->scale = 0.5; + + UTIL_SetSize( pev, Vector( 0, 0, 0), Vector(0, 0, 0) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; +} + +void COfficerBlood::Animate( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + if ( pev->frame++ ) + { + if ( pev->frame > m_maxFrame ) + { + pev->frame = 0; + } + } +} + +void COfficerBlood::Shoot( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ) +{ + COfficerBlood *pBlood = GetClassPtr( (COfficerBlood *)NULL ); + pBlood->Spawn(); + + UTIL_SetOrigin( pBlood, vecStart ); + pBlood->pev->velocity = vecVelocity; + pBlood->pev->gravity = 1000; + pBlood->pev->owner = ENT(pevOwner); + + pBlood->SetThink ( Animate ); + pBlood->pev->nextthink = gpGlobals->time + 0.1; +} + +void COfficerBlood :: Touch ( CBaseEntity *pOther ) +{ + TraceResult tr; + int iPitch; + + // splat sound + iPitch = RANDOM_FLOAT( 90, 110 ); + + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "superofficer/acid1.wav", 1, ATTN_NORM, 0, iPitch ); + + switch ( RANDOM_LONG( 0, 1 ) ) + { + case 0: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "superofficer/spithit1.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "superofficer/spithit2.wav", 1, ATTN_NORM, 0, iPitch ); + break; + } + + if ( !pOther->pev->takedamage ) + { + + // make a splat on the wall + UTIL_TraceLine( pev->origin, pev->origin + pev->velocity * 10, dont_ignore_monsters, ENT( pev ), &tr ); + UTIL_BloodDecalTrace( &tr, BLOOD_COLOR_GREEN ); + // make some flecks + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, tr.vecEndPos ); + WRITE_BYTE( TE_SPRITE_SPRAY ); + WRITE_COORD( tr.vecEndPos.x); // pos + WRITE_COORD( tr.vecEndPos.y); + WRITE_COORD( tr.vecEndPos.z); + WRITE_COORD( tr.vecPlaneNormal.x); // dir + WRITE_COORD( tr.vecPlaneNormal.y); + WRITE_COORD( tr.vecPlaneNormal.z); + WRITE_SHORT( iOfficerBlood ); // model + WRITE_BYTE ( 5 ); // count + WRITE_BYTE ( 30 ); // speed + WRITE_BYTE ( 80 ); // noise ( client will divide by 100 ) + MESSAGE_END(); + } + else + { + pOther->TakeDamage ( pev, pev, gSkillData.superofficerDmgBlood, DMG_GENERIC ); + } + + SetThink ( SUB_Remove ); + pev->nextthink = gpGlobals->time; +} + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define OFFICER_AE_SPIT ( 1 ) +#define OFFICER_AE_PUNCH ( 2 ) +#define OFFICER_AE_KICK ( 3 ) +class CSuperOfficer : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + int ISoundMask( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + float MaxYawSpeed( void ); + void IdleSound( void ); + void PainSound( void ); + void DeathSound( void ); + void AlertSound ( void ); + void AttackSound( void ); + void StartTask ( Task_t *pTask ); + void RunTask ( Task_t *pTask ); + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); + BOOL CheckMeleeAttack2 ( float flDot, float flDist ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + BOOL CheckHop(); + Schedule_t *GetSchedule( void ); + Schedule_t *GetScheduleOfType ( int Type ); + + int Save( CSave &save ); + int Restore( CRestore &restore ); + + CUSTOM_SCHEDULES; + static TYPEDESCRIPTION m_SaveData[]; + + float m_flNextSpitTime; +}; + +LINK_ENTITY_TO_CLASS( monster_superofficer, CSuperOfficer ); + +TYPEDESCRIPTION CSuperOfficer::m_SaveData[] = +{ + DEFINE_FIELD( CSuperOfficer, m_flNextSpitTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CSuperOfficer, CBaseMonster ); + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CSuperOfficer :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( IsMoving() && flDist >= OFFICER_SPIT_DISTANCE) + { + return FALSE; + } + + if ( flDist > 64 && flDist <= 128 && flDot >= 0.5 && gpGlobals->time >= m_flNextSpitTime ) + { + if ( m_hEnemy != NULL ) + { + if ( fabs( pev->origin.z - m_hEnemy->pev->origin.z ) > 64 ) + { + // don't try to spit at someone up really high or down really low. + return FALSE; + } + } + + if ( IsMoving() ) + { + // don't spit again for a long time, resume chasing enemy. + m_flNextSpitTime = gpGlobals->time + 5; + } + else + { + // not moving, so spit again pretty soon. + m_flNextSpitTime = gpGlobals->time + OFFICER_NEXT_SPIT_TIME; + } + + return TRUE; + } + + return FALSE; +} + +BOOL CSuperOfficer :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + if ( flDist <= 64 && flDot >= 0.7 ) + { + return TRUE; + } + return FALSE; +} + + +BOOL CSuperOfficer :: CheckMeleeAttack2 ( float flDot, float flDist ) +{ + if (flDist <= 85 && flDot >= 0.7) + { + return TRUE; + } + return FALSE; +} + +BOOL CSuperOfficer :: CheckHop() +{ + TraceResult tr; + Vector vecHopCheck = ( 0,0, gpGlobals->v_up * 128 ); + if (m_hEnemy) + { + if( fabs(m_hEnemy->pev->origin.z - pev->origin.z) >= 32) + { + ALERT(at_console,"checking jump"); + float flDist1; + UTIL_TraceLine ( pev->origin, pev->origin + gpGlobals->v_forward * 64, ignore_monsters,ignore_glass, pev->owner, & tr); + flDist1 = (pev->origin - tr.vecEndPos).Length2D(); + ALERT(at_console,"Dist1 %f \n",flDist1); + if (flDist1 <= 36) + return TRUE; + } + else + return FALSE; + } + return FALSE; +} + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. In the base class implementation, +// monsters care about all sounds, but no scents. +//========================================================= +int CSuperOfficer :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_CARCASS | + bits_SOUND_MEAT | + bits_SOUND_GARBAGE | + bits_SOUND_PLAYER; +} + +//========================================================= +// RunTask +//========================================================= +void CSuperOfficer :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_OFFICER_HOP: + { + { + SetActivity(ACT_HOP); + ClearBits( pev->flags, FL_ONGROUND ); + + UTIL_SetOrigin (this, pev->origin + Vector ( 0 , 0 , 1) );// take him off ground so engine doesn't instantly reset onground + UTIL_MakeVectors ( pev->angles ); + + Vector vecJumpDir; + if (m_hEnemy != NULL) + { + float gravity = g_psv_gravity->value; + if (gravity <= 1) + gravity = 1; + + // How fast does the headcrab need to travel to reach that height given gravity? + float height = (m_hEnemy->pev->origin.z + m_hEnemy->pev->view_ofs.z - pev->origin.z); + if (height < 8) + height = 8; + float speed = sqrt( 2 * gravity * height ); + float time = speed / gravity; + + // Scale the sideways velocity to get there at the right time + vecJumpDir = (m_hEnemy->pev->origin + m_hEnemy->pev->view_ofs - pev->origin); + vecJumpDir = vecJumpDir * ( 1.0 / time ); + + // Speed to offset gravity at the desired height + vecJumpDir.z = speed; + + // Don't jump too far/fast + float distance = vecJumpDir.Length(); + + if (distance > 650) + { + vecJumpDir = vecJumpDir * ( 650.0 / distance ); + } + } + else + { + // jump hop, don't care where + vecJumpDir = Vector( gpGlobals->v_forward.x, gpGlobals->v_forward.y, gpGlobals->v_up.z ) * 350; + } + + pev->velocity = vecJumpDir; + m_flNextAttack = gpGlobals->time + 2; + } + break; + } + case TASK_MELEE_ATTACK1: + { + if ( m_fSequenceFinished ) + { + m_iTaskStatus = TASKSTATUS_COMPLETE; + } + break; + } + case TASK_MELEE_ATTACK2: + { + if ( m_fSequenceFinished ) + { + m_iTaskStatus = TASKSTATUS_COMPLETE; + } + break; + } + default: + { + CBaseMonster :: RunTask( pTask ); + break; + } + } +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CSuperOfficer :: Classify ( void ) +{ + return CLASS_ALIEN_MONSTER; +} + +//========================================================= +// IdleSound +//========================================================= +void CSuperOfficer :: IdleSound ( void ) +{ + switch ( RANDOM_LONG(0,4) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "superofficer/idle1.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "superofficer/idle2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "superofficer/idle3.wav", 1, ATTN_NORM ); + break; + case 3: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "superofficer/idle4.wav", 1, ATTN_NORM ); + break; + case 4: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "superofficer/idle5.wav", 1, ATTN_NORM ); + break; + } +} + +//========================================================= +// PainSound +//========================================================= +void CSuperOfficer :: PainSound ( void ) +{ + int iPitch = RANDOM_LONG( 85, 120 ); + + switch ( RANDOM_LONG(0,3) ) + { + case 0: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "superofficer/pain1.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "superofficer/pain2.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 2: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "superofficer/pain3.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 3: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "superofficer/pain4.wav", 1, ATTN_NORM, 0, iPitch ); + break; + } +} + +//========================================================= +// AlertSound +//========================================================= +void CSuperOfficer :: AlertSound ( void ) +{ + int iPitch = RANDOM_LONG( 140, 160 ); + + switch ( RANDOM_LONG ( 0, 1 ) ) + { + case 0: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "superofficer/alert1.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "superofficer/alert2.wav", 1, ATTN_NORM, 0, iPitch ); + break; + } +} + +//========================================================= +// MaxYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +float CSuperOfficer :: MaxYawSpeed( void ) +{ + float ys; + + ys = 0; + + switch ( m_Activity ) + { + case ACT_WALK: ys = 90; break; + case ACT_RUN: ys = 90; break; + case ACT_IDLE: ys = 90; break; + case ACT_RANGE_ATTACK1: ys = 90; break; + default: + ys = 90; + break; + } + + return ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CSuperOfficer :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case OFFICER_AE_SPIT: + { + Vector vecSpitOffset; + Vector vecSpitDir; + + UTIL_MakeVectors ( pev->angles ); + + // !!!HACKHACK - the spot at which the spit originates (in front of the mouth) was measured in 3ds and hardcoded here. + // we should be able to read the position of bones at runtime for this info. + vecSpitOffset = ( 0,0, gpGlobals->v_up * 64 ); + vecSpitOffset = ( pev->origin + vecSpitOffset ); + vecSpitDir = ( ( m_hEnemy->pev->origin + m_hEnemy->pev->view_ofs ) - vecSpitOffset ).Normalize(); + + vecSpitDir.x += RANDOM_FLOAT( -0.05, 0.05 ); + vecSpitDir.y += RANDOM_FLOAT( -0.05, 0.05 ); + vecSpitDir.z += RANDOM_FLOAT( -0.05, 0 ); + + + // do stuff for this event. + AttackSound(); + + // spew the spittle temporary ents. + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpitOffset ); + WRITE_BYTE( TE_SPRITE_SPRAY ); + WRITE_COORD( vecSpitOffset.x); // pos + WRITE_COORD( vecSpitOffset.y); + WRITE_COORD( vecSpitOffset.z); + WRITE_COORD( vecSpitDir.x); // dir + WRITE_COORD( vecSpitDir.y); + WRITE_COORD( vecSpitDir.z); + WRITE_SHORT( iOfficerBlood ); // model + WRITE_BYTE ( 15 ); // count + WRITE_BYTE ( 210 ); // speed + WRITE_BYTE ( 25 ); // noise ( client will divide by 100 ) + MESSAGE_END(); + + COfficerBlood::Shoot( pev, vecSpitOffset, vecSpitDir * 900 ); + } + break; + + case OFFICER_AE_PUNCH: + { + // SOUND HERE! + CBaseEntity *pHurt = CheckTraceHullAttack( 70, RANDOM_LONG(8,13), DMG_SLASH ); + + if ( pHurt ) + { + //pHurt->pev->punchangle.z = -15; + //pHurt->pev->punchangle.x = -45; + pHurt->pev->velocity = pHurt->pev->velocity - gpGlobals->v_forward * 100; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_up * 100; + } + } + break; + case OFFICER_AE_KICK: + { + CBaseEntity *pHurt = CheckTraceHullAttack( 70, 10, DMG_CLUB | DMG_ALWAYSGIB ); + if ( pHurt ) + { + pHurt->pev->punchangle.z = -20; + pHurt->pev->punchangle.x = 20; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_right * 200; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_up * 100; + } + } + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + } +} + +//========================================================= +// Spawn +//========================================================= +void CSuperOfficer :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/monsters/monster_superofficer.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, 0 ), Vector( 32, 32, 64 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->effects = 0; + pev->health = gSkillData.superofficerHealth; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + m_flNextSpitTime = gpGlobals->time; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CSuperOfficer :: Precache() +{ + PRECACHE_MODEL("models/monsters/monster_superofficer.mdl"); + + PRECACHE_MODEL("sprites/bigspit.spr");// spit projectile. + + iOfficerBlood = PRECACHE_MODEL("sprites/tinyspit.spr");// client side spittle. + + PRECACHE_SOUND("zombie/claw_miss2.wav");// because we use the basemonster SWIPE animation event + + PRECACHE_SOUND("superofficer/attack1.wav"); + PRECACHE_SOUND("superofficer/attack2.wav"); + + PRECACHE_SOUND("superofficer/attackgrowl.wav"); + PRECACHE_SOUND("superofficer/attackgrowl2.wav"); + PRECACHE_SOUND("superofficer/attackgrowl3.wav"); + + PRECACHE_SOUND("superofficer/die1.wav"); + PRECACHE_SOUND("superofficer/die2.wav"); + PRECACHE_SOUND("superofficer/die3.wav"); + + PRECACHE_SOUND("superofficer/idle1.wav"); + PRECACHE_SOUND("superofficer/idle2.wav"); + PRECACHE_SOUND("superofficer/idle3.wav"); + PRECACHE_SOUND("superofficer/idle4.wav"); + PRECACHE_SOUND("superofficer/idle5.wav"); + + PRECACHE_SOUND("superofficer/pain1.wav"); + PRECACHE_SOUND("superofficer/pain2.wav"); + PRECACHE_SOUND("superofficer/pain3.wav"); + PRECACHE_SOUND("superofficer/pain4.wav"); + + PRECACHE_SOUND("superofficer/acid1.wav"); + + PRECACHE_SOUND("superofficer/spithit1.wav"); + PRECACHE_SOUND("superofficer/spithit2.wav"); + +} + +//========================================================= +// DeathSound +//========================================================= +void CSuperOfficer :: DeathSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "superofficer/die1.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "superofficer/die2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "superofficer/die3.wav", 1, ATTN_NORM ); + break; + } +} + +//========================================================= +// AttackSound +//========================================================= +void CSuperOfficer :: AttackSound ( void ) +{ + switch ( RANDOM_LONG(0,1) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "superofficer/attack1.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "superofficer/attack2.wav", 1, ATTN_NORM ); + break; + } +} + + +//======================================================== +// AI Schedules Specific to this monster +//========================================================= + +//Hop +Task_t tlOfficerHop[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_OFFICER_HOP, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; +Schedule_t slOfficerHop[] = +{ + { + tlOfficerHop, + ARRAYSIZE( tlOfficerHop ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_TASK_FAILED, + 0, + "Officer Hop" + + }, +}; + +// primary range attack +Task_t tlOfficerRangeAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slOfficerRangeAttack1[] = +{ + { + tlOfficerRangeAttack1, + ARRAYSIZE ( tlOfficerRangeAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED, + 0, + "Officer Range Attack1" + }, +}; + + + +// Chase enemy schedule +Task_t tlOfficerChaseEnemy1[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RANGE_ATTACK1 }, + { TASK_GET_PATH_TO_TARGET, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slOfficerChaseEnemy[] = +{ + { + tlOfficerChaseEnemy1, + ARRAYSIZE ( tlOfficerChaseEnemy1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_TASK_FAILED | + bits_COND_HEAR_SOUND, + 0, + "Officer Chase Enemy" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CSuperOfficer ) +{ + slOfficerRangeAttack1, + slOfficerChaseEnemy, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CSuperOfficer, CBaseMonster ); + +//========================================================= +// GetSchedule +//========================================================= +Schedule_t *CSuperOfficer :: GetSchedule( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_COMBAT: + { +// dead enemy + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + + if ( HasConditions(bits_COND_NEW_ENEMY) ) + { + return GetScheduleOfType ( SCHED_WAKE_ANGRY ); + } + + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) && !HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) && !HasConditions( bits_COND_CAN_MELEE_ATTACK2 )) + { + return GetScheduleOfType ( SCHED_RANGE_ATTACK1 ); + } + + if ( HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) && !HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_MELEE_ATTACK1 ); + } + + if ( HasConditions( bits_COND_CAN_MELEE_ATTACK2 ) && !HasConditions( bits_COND_CAN_MELEE_ATTACK1 )) + { + return GetScheduleOfType ( SCHED_MELEE_ATTACK2 ); + } + + return GetScheduleOfType ( SCHED_CHASE_ENEMY ); + + break; + } + } + + return CBaseMonster :: GetSchedule(); +} + + +//========================================================= +// GetScheduleOfType +//========================================================= +Schedule_t* CSuperOfficer :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_OFFICER_HOP: + return &slOfficerHop[0]; + break; + case SCHED_RANGE_ATTACK1: + return &slOfficerRangeAttack1[ 0 ]; + break; + case SCHED_CHASE_ENEMY: + return &slOfficerChaseEnemy[ 0 ]; + break; + } + + return CBaseMonster :: GetScheduleOfType ( Type ); +} + +//========================================================= +// Start task - selects the correct activity and performs +// any necessary calculations to start the next task on the +// schedule. OVERRIDDEN for bullsquid because it needs to +// know explicitly when the last attempt to chase the enemy +// failed, since that impacts its attack choices. +//========================================================= +void CSuperOfficer :: StartTask ( Task_t *pTask ) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch ( pTask->iTask ) + { + + case TASK_MELEE_ATTACK1: + { + SetActivity(ACT_MELEE_ATTACK1); + break; + } + + case TASK_MELEE_ATTACK2: + { + SetActivity(ACT_MELEE_ATTACK2); + switch ( RANDOM_LONG ( 0, 2 ) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "superofficer/attackgrowl.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "superofficer/attackgrowl2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "superofficer/attackgrowl3.wav", 1, ATTN_NORM ); + break; + } + break; + } + case TASK_GET_PATH_TO_ENEMY: + { + if ( BuildRoute ( m_hEnemy->pev->origin, bits_MF_TO_ENEMY, m_hEnemy ) ) + { + m_iTaskStatus = TASKSTATUS_COMPLETE; + } + else + { + ALERT ( at_aiconsole, "GetPathToEnemy failed!!\n" ); + TaskFail(); + } + break; + } + default: + { + CBaseMonster :: StartTask ( pTask ); + break; + } + } +} diff --git a/dlls/monsterevent.h b/dlls/monsterevent.h new file mode 100644 index 0000000..46c5624 --- /dev/null +++ b/dlls/monsterevent.h @@ -0,0 +1,34 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef MONSTEREVENT_H +#define MONSTEREVENT_H + +typedef struct +{ + int event; + char *options; +} MonsterEvent_t; + +#define EVENT_SPECIFIC 0 +#define EVENT_SCRIPTED 1000 +#define EVENT_SHARED 2000 +#define EVENT_CLIENT 5000 + +#define MONSTER_EVENT_BODYDROP_LIGHT 2001 +#define MONSTER_EVENT_BODYDROP_HEAVY 2002 + +#define MONSTER_EVENT_SWISHSOUND 2010 + +#endif // MONSTEREVENT_H diff --git a/dlls/monstermaker.cpp b/dlls/monstermaker.cpp new file mode 100644 index 0000000..9499e7e --- /dev/null +++ b/dlls/monstermaker.cpp @@ -0,0 +1,360 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// Monster Maker - this is an entity that creates monsters +// in the game. +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "saverestore.h" + +// Monstermaker spawnflags +#define SF_MONSTERMAKER_START_ON 1 // start active ( if has targetname ) +#define SF_MONSTERMAKER_CYCLIC 4 // drop one monster every time fired. +#define SF_MONSTERMAKER_MONSTERCLIP 8 // Children are blocked by monsterclip +#define SF_MONSTERMAKER_LEAVECORPSE 16 // Don't fade corpses. +#define SF_MONSTERMAKER_NO_WPN_DROP 1024 // Corpses don't drop weapons. + +//========================================================= +// MonsterMaker - this ent creates monsters during the game. +//========================================================= +class CMonsterMaker : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData* pkvd); + void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT CyclicUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT MakerThink ( void ); + void EXPORT MakeMonsterThink( void ); + void DeathNotice ( entvars_t *pevChild );// monster maker children use this to tell the monster maker that they have died. + void TryMakeMonster( void ); //LRC - to allow for a spawndelay + CBaseMonster* MakeMonster( void ); //LRC - actually make a monster (and return the new creation) + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + string_t m_iszMonsterClassname;// classname of the monster(s) that will be created. + + int m_cNumMonsters;// max number of monsters this ent can create + + + int m_cLiveChildren;// how many monsters made by this monster maker that are currently alive + int m_iMaxLiveChildren;// max number of monsters that this maker may have out at one time. + + float m_flGround; // z coord of the ground under me, used to make sure no monsters are under the maker when it drops a new child + + BOOL m_fActive; + BOOL m_fFadeChildren;// should we make the children fadeout? + float m_fSpawnDelay;// LRC- delay between triggering targets and making a child (for env_warpball, mainly) +}; + +LINK_ENTITY_TO_CLASS( monstermaker, CMonsterMaker ); + +TYPEDESCRIPTION CMonsterMaker::m_SaveData[] = +{ + DEFINE_FIELD( CMonsterMaker, m_iszMonsterClassname, FIELD_STRING ), + DEFINE_FIELD( CMonsterMaker, m_cNumMonsters, FIELD_INTEGER ), + DEFINE_FIELD( CMonsterMaker, m_cLiveChildren, FIELD_INTEGER ), + DEFINE_FIELD( CMonsterMaker, m_flGround, FIELD_FLOAT ), + DEFINE_FIELD( CMonsterMaker, m_iMaxLiveChildren, FIELD_INTEGER ), + DEFINE_FIELD( CMonsterMaker, m_fActive, FIELD_BOOLEAN ), + DEFINE_FIELD( CMonsterMaker, m_fFadeChildren, FIELD_BOOLEAN ), + DEFINE_FIELD( CMonsterMaker, m_fSpawnDelay, FIELD_FLOAT ), +}; + + +IMPLEMENT_SAVERESTORE( CMonsterMaker, CBaseMonster ); + +void CMonsterMaker :: KeyValue( KeyValueData *pkvd ) +{ + + if ( FStrEq(pkvd->szKeyName, "monstercount") ) + { + m_cNumMonsters = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "m_imaxlivechildren") ) + { + m_iMaxLiveChildren = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "monstertype") ) + { + m_iszMonsterClassname = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "spawndelay") ) + { + m_fSpawnDelay = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + + +void CMonsterMaker :: Spawn( ) +{ + pev->solid = SOLID_NOT; + + m_cLiveChildren = 0; + Precache(); + if ( !FStringNull ( pev->targetname ) ) + { + if ( pev->spawnflags & SF_MONSTERMAKER_CYCLIC ) + { + SetUse(&CMonsterMaker :: CyclicUse );// drop one monster each time we fire + m_fActive = FALSE; + } + else + { + SetUse(&CMonsterMaker :: ToggleUse );// can be turned on/off + + if ( FBitSet ( pev->spawnflags, SF_MONSTERMAKER_START_ON ) ) + {// start making monsters as soon as monstermaker spawns + m_fActive = TRUE; + SetThink(&CMonsterMaker :: MakerThink ); + } + else + {// wait to be activated. + m_fActive = FALSE; + SetThink(&CMonsterMaker :: SUB_DoNothing ); + } + } + } + else + {// no targetname, just start. + SetNextThink( m_flDelay ); + m_fActive = TRUE; + SetThink(&CMonsterMaker :: MakerThink ); + } + + if ( m_cNumMonsters == 1 || (m_cNumMonsters != -1 && pev->spawnflags & SF_MONSTERMAKER_LEAVECORPSE )) + { + m_fFadeChildren = FALSE; + } + else + { + m_fFadeChildren = TRUE; + } + + m_flGround = 0; +} + +void CMonsterMaker :: Precache( void ) +{ + CBaseMonster::Precache(); + + UTIL_PrecacheOther( STRING( m_iszMonsterClassname ) ); +} + +//========================================================= +// TryMakeMonster- check that it's ok to drop a monster. +//========================================================= +void CMonsterMaker::TryMakeMonster( void ) +{ + if ( m_iMaxLiveChildren > 0 && m_cLiveChildren >= m_iMaxLiveChildren ) + {// not allowed to make a new one yet. Too many live ones out right now. + return; + } + + if ( !m_flGround ) + { + // set altitude. Now that I'm activated, any breakables, etc should be out from under me. + TraceResult tr; + + UTIL_TraceLine ( pev->origin, pev->origin - Vector ( 0, 0, 2048 ), ignore_monsters, ENT(pev), &tr ); + m_flGround = tr.vecEndPos.z; + } + + Vector mins = pev->origin - Vector( 34, 34, 0 ); + Vector maxs = pev->origin + Vector( 34, 34, 0 ); + maxs.z = pev->origin.z; + mins.z = m_flGround; + + CBaseEntity *pList[2]; + int count = UTIL_EntitiesInBox( pList, 2, mins, maxs, FL_CLIENT|FL_MONSTER ); + if ( count ) + { + // don't build a stack of monsters! + return; + } + + if (m_fSpawnDelay) + { + // If I have a target, fire. (no locus) + if ( !FStringNull ( pev->target ) ) + { + // delay already overloaded for this entity, so can't call SUB_UseTargets() + FireTargets( STRING(pev->target), this, this, USE_TOGGLE, 0 ); + } + +// ALERT(at_console,"Making Monster in %f seconds\n",m_fSpawnDelay); + SetThink(&CMonsterMaker:: MakeMonsterThink ); + SetNextThink( m_fSpawnDelay ); + } + else + { +// ALERT(at_console,"No delay. Making monster.\n",m_fSpawnDelay); + CBaseMonster* pMonst = MakeMonster(); + + // If I have a target, fire! (the new monster is the locus) + if ( !FStringNull ( pev->target ) ) + { + FireTargets( STRING(pev->target), pMonst, this, USE_TOGGLE, 0 ); + } + } +} + +//========================================================= +// MakeMonsterThink- a really trivial think function +//========================================================= +void CMonsterMaker::MakeMonsterThink( void ) +{ + MakeMonster(); +} + +//========================================================= +// MakeMonster- this is the code that drops the monster +//========================================================= +CBaseMonster* CMonsterMaker::MakeMonster( void ) +{ + edict_t *pent; + entvars_t *pevCreate; + +// ALERT(at_console,"Making Monster NOW\n"); + + pent = CREATE_NAMED_ENTITY( m_iszMonsterClassname ); + + if ( FNullEnt( pent ) ) + { + ALERT ( at_debug, "NULL Ent in MonsterMaker!\n" ); + return NULL; + } + + pevCreate = VARS( pent ); + pevCreate->origin = pev->origin; + pevCreate->angles = pev->angles; + pevCreate->health = pev->health; + pevCreate->model = pev->model; + SetBits( pevCreate->spawnflags, SF_MONSTER_FALL_TO_GROUND ); + + if (pev->spawnflags & SF_MONSTERMAKER_NO_WPN_DROP) + SetBits( pevCreate->spawnflags, SF_MONSTER_NO_WPN_DROP); + + // Children hit monsterclip brushes + if ( pev->spawnflags & SF_MONSTERMAKER_MONSTERCLIP ) + SetBits( pevCreate->spawnflags, SF_MONSTER_HITMONSTERCLIP ); + + DispatchSpawn( ENT( pevCreate ) ); + pevCreate->owner = edict(); + + //LRC - custom monster behaviour + CBaseEntity *pEntity = CBaseEntity::Instance( pevCreate ); + CBaseMonster *pMonst = NULL; + if (pEntity && (pMonst = pEntity->MyMonsterPointer()) != NULL) + { + pMonst->m_iClass = this->m_iClass; + pMonst->m_iPlayerReact = this->m_iPlayerReact; + } + + if ( !FStringNull( pev->netname ) ) + { + // if I have a netname (overloaded), give the child monster that name as a targetname + pevCreate->targetname = pev->netname; + } + + m_cLiveChildren++;// count this monster + m_cNumMonsters--; + + if ( m_cNumMonsters == 0 ) + { + // Disable this forever. Don't kill it because it still gets death notices + SetThink( NULL ); + SetUse( NULL ); + } + else if (m_fActive) + { + SetNextThink( m_flDelay ); + SetThink(&CMonsterMaker:: MakerThink ); + } + + return pMonst; +} + +//========================================================= +// CyclicUse - drops one monster from the monstermaker +// each time we call this. +//========================================================= +void CMonsterMaker::CyclicUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + TryMakeMonster(); +// ALERT(at_console,"CyclicUse complete\n"); +} + +//========================================================= +// ToggleUse - activates/deactivates the monster maker +//========================================================= +void CMonsterMaker :: ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_fActive ) ) + return; + + if ( m_fActive ) + { + m_fActive = FALSE; + SetThink ( NULL ); + } + else + { + m_fActive = TRUE; + SetThink(&CMonsterMaker :: MakerThink ); + } + + SetNextThink( 0 ); +} + +//========================================================= +// MakerThink - creates a new monster every so often +//========================================================= +void CMonsterMaker :: MakerThink ( void ) +{ + SetNextThink( m_flDelay ); + + TryMakeMonster(); +} + + +//========================================================= +//========================================================= +void CMonsterMaker :: DeathNotice ( entvars_t *pevChild ) +{ + // ok, we've gotten the deathnotice from our child, now clear out its owner if we don't want it to fade. + m_cLiveChildren--; + + if ( !m_fFadeChildren ) + { + pevChild->owner = NULL; + } +} + + diff --git a/dlls/monsters.cpp b/dlls/monsters.cpp new file mode 100644 index 0000000..1795057 --- /dev/null +++ b/dlls/monsters.cpp @@ -0,0 +1,4007 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +/* + +===== monsters.cpp ======================================================== + + Monster-related utility code + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "nodes.h" +#include "monsters.h" +#include "animation.h" +#include "saverestore.h" +#include "weapons.h" +#include "scripted.h" +#include "squadmonster.h" +#include "decals.h" +#include "soundent.h" +#include "gamerules.h" +#include "player.h" // buz +#include "material.h" // g-cont + +#define MONSTER_CUT_CORNER_DIST 8 // 8 means the monster's bounding box is contained without the box of the node in WC + + +Vector VecBModelOrigin( entvars_t* pevBModel ); + +extern DLL_GLOBAL BOOL g_fDrawLines; +extern DLL_GLOBAL short g_sModelIndexLaser;// holds the index for the laser beam +extern DLL_GLOBAL short g_sModelIndexLaserDot;// holds the index for the laser beam dot + +extern CGraph WorldGraph;// the world node graph + +// Global Savedata for monster +// UNDONE: Save schedule data? Can this be done? We may +// lose our enemy pointer or other data (goal ent, target, etc) +// that make the current schedule invalid, perhaps it's best +// to just pick a new one when we start up again. +TYPEDESCRIPTION CBaseMonster::m_SaveData[] = +{ + DEFINE_FIELD( CBaseMonster, m_hEnemy, FIELD_EHANDLE ), + DEFINE_FIELD( CBaseMonster, m_hTargetEnt, FIELD_EHANDLE ), + DEFINE_ARRAY( CBaseMonster, m_hOldEnemy, FIELD_EHANDLE, MAX_OLD_ENEMIES ), + DEFINE_ARRAY( CBaseMonster, m_vecOldEnemy, FIELD_POSITION_VECTOR, MAX_OLD_ENEMIES ), + + DEFINE_FIELD( CBaseMonster, m_iClass, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_iPlayerReact, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseMonster, m_flFieldOfView, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_flWaitFinished, FIELD_TIME ), + DEFINE_FIELD( CBaseMonster, m_flMoveWaitFinished, FIELD_TIME ), + + DEFINE_FIELD( CBaseMonster, m_Activity, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_IdealActivity, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_LastHitGroup, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_MonsterState, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_IdealMonsterState, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_iTaskStatus, FIELD_INTEGER ), + + //Schedule_t *m_pSchedule; + + DEFINE_FIELD( CBaseMonster, m_iScheduleIndex, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_afConditions, FIELD_INTEGER ), + //WayPoint_t m_Route[ ROUTE_SIZE ]; +// DEFINE_FIELD( CBaseMonster, m_movementGoal, FIELD_INTEGER ), +// DEFINE_FIELD( CBaseMonster, m_iRouteIndex, FIELD_INTEGER ), +// DEFINE_FIELD( CBaseMonster, m_moveWaitTime, FIELD_FLOAT ), + + DEFINE_FIELD( CBaseMonster, m_vecMoveGoal, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseMonster, m_movementActivity, FIELD_INTEGER ), + + // int m_iAudibleList; // first index of a linked list of sounds that the monster can hear. +// DEFINE_FIELD( CBaseMonster, m_afSoundTypes, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_vecLastPosition, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseMonster, m_iHintNode, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_afMemory, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_iMaxHealth, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseMonster, m_vecEnemyLKP, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseMonster, m_cAmmoLoaded, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_afCapability, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_vecFacingDir, FIELD_VECTOR ), + DEFINE_FIELD( CBaseMonster, m_vecDirection, FIELD_VECTOR ), + + DEFINE_FIELD( CBaseMonster, m_flNextAttack, FIELD_TIME ), + DEFINE_FIELD( CBaseMonster, m_bitsDamageType, FIELD_INTEGER ), + DEFINE_ARRAY( CBaseMonster, m_rgbTimeBasedDamage, FIELD_CHARACTER, CDMG_TIMEBASED ), + DEFINE_FIELD( CBaseMonster, m_bloodColor, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_failSchedule, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseMonster, m_flHungryTime, FIELD_TIME ), + DEFINE_FIELD( CBaseMonster, m_flDistTooFar, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_flDistLook, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_iTriggerCondition, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_iszTriggerTarget, FIELD_STRING ), + + DEFINE_FIELD( CBaseMonster, m_HackedGunPos, FIELD_VECTOR ), + + DEFINE_FIELD( CBaseMonster, m_scriptState, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_pCine, FIELD_CLASSPTR ), + + DEFINE_FIELD( CBaseMonster, m_hRushEntity, FIELD_STRING ), // buz + DEFINE_FIELD( CBaseMonster, m_iRushMovetype, FIELD_INTEGER ), // buz + DEFINE_FIELD( CBaseMonster, m_flRushDistance, FIELD_FLOAT ), // buz + DEFINE_FIELD( CBaseMonster, m_iUseAlertAnims, FIELD_INTEGER ), // buz +}; + +//IMPLEMENT_SAVERESTORE( CBaseMonster, CBaseToggle ); +int CBaseMonster::Save( CSave &save ) +{ + if ( !CBaseToggle::Save(save) ) + return 0; + if ( pev->targetname ) + return save.WriteFields( STRING(pev->targetname), "CBaseMonster", this, m_SaveData, ARRAYSIZE(m_SaveData) ); + else + return save.WriteFields( STRING(pev->classname), "CBaseMonster", this, m_SaveData, ARRAYSIZE(m_SaveData) ); +} + +int CBaseMonster::Restore( CRestore &restore ) +{ + if ( !CBaseToggle::Restore(restore) ) + return 0; + int status = restore.ReadFields( "CBaseMonster", this, m_SaveData, ARRAYSIZE(m_SaveData) ); + + // We don't save/restore routes yet + RouteClear(); + + // We don't save/restore schedules yet + m_pSchedule = NULL; + m_iTaskStatus = TASKSTATUS_NEW; + + // Reset animation + m_Activity = ACT_RESET; + + // If we don't have an enemy, clear conditions like see enemy, etc. + if ( m_hEnemy == NULL ) + m_afConditions = 0; + + return status; +} + + +//========================================================= +// Eat - makes a monster full for a little while. +//========================================================= +void CBaseMonster :: Eat ( float flFullDuration ) +{ + m_flHungryTime = gpGlobals->time + flFullDuration; +} + +//========================================================= +// FShouldEat - returns true if a monster is hungry. +//========================================================= +BOOL CBaseMonster :: FShouldEat ( void ) +{ + if ( m_flHungryTime > gpGlobals->time ) + { + return FALSE; + } + + return TRUE; +} + +//========================================================= +// BarnacleVictimBitten - called +// by Barnacle victims when the barnacle pulls their head +// into its mouth +//========================================================= +void CBaseMonster :: BarnacleVictimBitten ( entvars_t *pevBarnacle ) +{ + Schedule_t *pNewSchedule; + + pNewSchedule = GetScheduleOfType( SCHED_BARNACLE_VICTIM_CHOMP ); + + if ( pNewSchedule ) + { + ChangeSchedule( pNewSchedule ); + } +} + +//========================================================= +// BarnacleVictimReleased - called by barnacle victims when +// the host barnacle is killed. +//========================================================= +void CBaseMonster :: BarnacleVictimReleased ( void ) +{ + m_IdealMonsterState = MONSTERSTATE_IDLE; + + pev->velocity = g_vecZero; + pev->movetype = MOVETYPE_STEP; +} + +//========================================================= +// Listen - monsters dig through the active sound list for +// any sounds that may interest them. (smells, too!) +//========================================================= +void CBaseMonster :: Listen ( void ) +{ + int iSound; + int iMySounds; + float hearingSensitivity; + CSound *pCurrentSound; + + m_iAudibleList = SOUNDLIST_EMPTY; + ClearConditions(bits_COND_HEAR_SOUND | bits_COND_SMELL | bits_COND_SMELL_FOOD); + m_afSoundTypes = 0; + + iMySounds = ISoundMask(); + + if ( m_pSchedule ) + { + //!!!WATCH THIS SPOT IF YOU ARE HAVING SOUND RELATED BUGS! + // Make sure your schedule AND personal sound masks agree! + iMySounds &= m_pSchedule->iSoundMask; + } + + iSound = CSoundEnt::ActiveList(); + + // UNDONE: Clear these here? + ClearConditions( bits_COND_HEAR_SOUND | bits_COND_SMELL_FOOD | bits_COND_SMELL ); + hearingSensitivity = HearingSensitivity( ); + + while ( iSound != SOUNDLIST_EMPTY ) + { + pCurrentSound = CSoundEnt::SoundPointerForIndex( iSound ); + + if ( pCurrentSound && + ( pCurrentSound->m_iType & iMySounds ) && + ( pCurrentSound->m_vecOrigin - EarPosition() ).Length() <= pCurrentSound->m_iVolume * hearingSensitivity ) + + //if ( ( g_pSoundEnt->m_SoundPool[ iSound ].m_iType & iMySounds ) && ( g_pSoundEnt->m_SoundPool[ iSound ].m_vecOrigin - EarPosition()).Length () <= g_pSoundEnt->m_SoundPool[ iSound ].m_iVolume * hearingSensitivity ) + { + // the monster cares about this sound, and it's close enough to hear. + //g_pSoundEnt->m_SoundPool[ iSound ].m_iNextAudible = m_iAudibleList; + pCurrentSound->m_iNextAudible = m_iAudibleList; + + if ( pCurrentSound->FIsSound() ) + { + // this is an audible sound. + SetConditions( bits_COND_HEAR_SOUND ); + } + else + { + // if not a sound, must be a smell - determine if it's just a scent, or if it's a food scent +// if ( g_pSoundEnt->m_SoundPool[ iSound ].m_iType & ( bits_SOUND_MEAT | bits_SOUND_CARCASS ) ) + if ( pCurrentSound->m_iType & ( bits_SOUND_MEAT | bits_SOUND_CARCASS ) ) + { + // the detected scent is a food item, so set both conditions. + // !!!BUGBUG - maybe a virtual function to determine whether or not the scent is food? + SetConditions( bits_COND_SMELL_FOOD ); + SetConditions( bits_COND_SMELL ); + } + else + { + // just a normal scent. + SetConditions( bits_COND_SMELL ); + } + } + +// m_afSoundTypes |= g_pSoundEnt->m_SoundPool[ iSound ].m_iType; + m_afSoundTypes |= pCurrentSound->m_iType; + + m_iAudibleList = iSound; + } + +// iSound = g_pSoundEnt->m_SoundPool[ iSound ].m_iNext; + iSound = pCurrentSound->m_iNext; + } +} + +//========================================================= +// FLSoundVolume - subtracts the volume of the given sound +// from the distance the sound source is from the caller, +// and returns that value, which is considered to be the 'local' +// volume of the sound. +//========================================================= +float CBaseMonster :: FLSoundVolume ( CSound *pSound ) +{ + return ( pSound->m_iVolume - ( ( pSound->m_vecOrigin - pev->origin ).Length() ) ); +} + +//========================================================= +// FValidateHintType - tells use whether or not the monster cares +// about the type of Hint Node given +//========================================================= +BOOL CBaseMonster :: FValidateHintType ( short sHint ) +{ + return FALSE; +} + +//========================================================= +// Look - Base class monster function to find enemies or +// food by sight. iDistance is distance ( in units ) that the +// monster can see. +// +// Sets the sight bits of the m_afConditions mask to indicate +// which types of entities were sighted. +// Function also sets the Looker's m_pLink +// to the head of a link list that contains all visible ents. +// (linked via each ent's m_pLink field) +// +//========================================================= +void CBaseMonster :: Look ( int iDistance ) +{ + int iSighted = 0; + + // DON'T let visibility information from last frame sit around! + ClearConditions(bits_COND_SEE_HATE | bits_COND_SEE_DISLIKE | bits_COND_SEE_ENEMY | bits_COND_SEE_FEAR | bits_COND_SEE_NEMESIS | bits_COND_SEE_CLIENT); + + m_pLink = NULL; + + CBaseEntity *pSightEnt = NULL;// the current visible entity that we're dealing with + + // See no evil if prisoner is set + if ( !FBitSet( pev->spawnflags, SF_MONSTER_PRISONER ) ) + { + CBaseEntity *pList[100]; + + Vector delta = Vector( iDistance, iDistance, iDistance ); + + // Find only monsters/clients in box, NOT limited to PVS + int count = UTIL_EntitiesInBox( pList, 100, pev->origin - delta, pev->origin + delta, FL_CLIENT|FL_MONSTER ); + for ( int i = 0; i < count; i++ ) + { + pSightEnt = pList[i]; + // !!!temporarily only considering other monsters and clients, don't see prisoners + + if ( pSightEnt != this && + !FBitSet( pSightEnt->pev->spawnflags, SF_MONSTER_PRISONER ) && + pSightEnt->pev->health > 0 ) + { + // buz + if (!FStringNull(pSightEnt->pev->noise3) && !FStringNull(pev->targetname)) + { + if (FClassnameIs(pSightEnt->pev,"monster_target")) + { + if (!FStrEq(STRING(pev->targetname), STRING(pSightEnt->pev->noise3))) + continue; + } + } + + // the looker will want to consider this entity + // don't check anything else about an entity that can't be seen, or an entity that you don't care about. + if ( IRelationship( pSightEnt ) != R_NO && FInViewCone( pSightEnt ) && !FBitSet( pSightEnt->pev->flags, FL_NOTARGET ) && FVisible( pSightEnt ) ) + { + if ( pSightEnt->IsPlayer() ) + { + if ( pev->spawnflags & SF_MONSTER_WAIT_TILL_SEEN ) + { + CBaseMonster *pClient; + + pClient = pSightEnt->MyMonsterPointer(); + // don't link this client in the list if the monster is wait till seen and the player isn't facing the monster + if ( pSightEnt && !pClient->FInViewCone( this ) ) + { + // we're not in the player's view cone. + continue; + } + else + { + // player sees us, become normal now. + pev->spawnflags &= ~SF_MONSTER_WAIT_TILL_SEEN; + } + } + + // if we see a client, remember that (mostly for scripted AI) + iSighted |= bits_COND_SEE_CLIENT; + } + + pSightEnt->m_pLink = m_pLink; + m_pLink = pSightEnt; + + if ( pSightEnt == m_hEnemy ) + { + // we know this ent is visible, so if it also happens to be our enemy, store that now. + iSighted |= bits_COND_SEE_ENEMY; + } + + // don't add the Enemy's relationship to the conditions. We only want to worry about conditions when + // we see monsters other than the Enemy. + switch ( IRelationship ( pSightEnt ) ) + { + case R_NM: + iSighted |= bits_COND_SEE_NEMESIS; + break; + case R_HT: + iSighted |= bits_COND_SEE_HATE; + break; + case R_DL: + iSighted |= bits_COND_SEE_DISLIKE; + break; + case R_FR: + iSighted |= bits_COND_SEE_FEAR; + break; + case R_AL: + break; + default: + ALERT ( at_aiconsole, "%s can't assess %s\n", STRING(pev->classname), STRING(pSightEnt->pev->classname ) ); + break; + } + } + } + } + } + + SetConditions( iSighted ); +} + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. In the base class implementation, +// monsters care about all sounds, but no scents. +//========================================================= +int CBaseMonster :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER; +} + +//========================================================= +// PBestSound - returns a pointer to the sound the monster +// should react to. Right now responds only to nearest sound. +//========================================================= +CSound* CBaseMonster :: PBestSound ( void ) +{ + int iThisSound; + int iBestSound = -1; + float flBestDist = 8192;// so first nearby sound will become best so far. + float flDist; + CSound *pSound; + + iThisSound = m_iAudibleList; + + if ( iThisSound == SOUNDLIST_EMPTY ) + { + ALERT ( at_aiconsole, "ERROR! monster %s has no audible sounds!\n", STRING(pev->classname) ); +#if _DEBUG + ALERT( at_error, "NULL Return from PBestSound\n" ); +#endif + return NULL; + } + + while ( iThisSound != SOUNDLIST_EMPTY ) + { + pSound = CSoundEnt::SoundPointerForIndex( iThisSound ); + + if ( pSound && pSound->FIsSound() ) + { + flDist = ( pSound->m_vecOrigin - EarPosition()).Length(); + + if ( flDist < flBestDist ) + { + iBestSound = iThisSound; + flBestDist = flDist; + } + } + + iThisSound = pSound->m_iNextAudible; + } + if ( iBestSound >= 0 ) + { + pSound = CSoundEnt::SoundPointerForIndex( iBestSound ); + return pSound; + } +#if _DEBUG + ALERT( at_error, "NULL Return from PBestSound\n" ); +#endif + return NULL; +} + +//========================================================= +// PBestScent - returns a pointer to the scent the monster +// should react to. Right now responds only to nearest scent +//========================================================= +CSound* CBaseMonster :: PBestScent ( void ) +{ + int iThisScent; + int iBestScent = -1; + float flBestDist = 8192;// so first nearby smell will become best so far. + float flDist; + CSound *pSound; + + iThisScent = m_iAudibleList;// smells are in the sound list. + + if ( iThisScent == SOUNDLIST_EMPTY ) + { + ALERT ( at_aiconsole, "ERROR! PBestScent() has empty soundlist!\n" ); +#if _DEBUG + ALERT( at_error, "NULL Return from PBestSound\n" ); +#endif + return NULL; + } + + while ( iThisScent != SOUNDLIST_EMPTY ) + { + pSound = CSoundEnt::SoundPointerForIndex( iThisScent ); + + if ( pSound->FIsScent() ) + { + flDist = ( pSound->m_vecOrigin - pev->origin ).Length(); + + if ( flDist < flBestDist ) + { + iBestScent = iThisScent; + flBestDist = flDist; + } + } + + iThisScent = pSound->m_iNextAudible; + } + if ( iBestScent >= 0 ) + { + pSound = CSoundEnt::SoundPointerForIndex( iBestScent ); + + return pSound; + } +#if _DEBUG + ALERT( at_error, "NULL Return from PBestScent\n" ); +#endif + return NULL; +} + +void CBaseMonster :: RunAnimation( void ) +{ + float flInterval = StudioFrameAdvance( ); // animate + + // start or end a fidget + // This needs a better home -- switching animations over time should be encapsulated on a per-activity basis + // perhaps MaintainActivity() or a ShiftAnimationOverTime() or something. + if( m_MonsterState != MONSTERSTATE_SCRIPT && m_MonsterState != MONSTERSTATE_DEAD && m_Activity == ACT_IDLE && m_fSequenceFinished ) + { + int iSequence; + + if ( m_fSequenceLoops ) + { + // animation does loop, which means we're playing subtle idle. Might need to + // fidget. + iSequence = LookupActivity ( m_Activity ); + } + else + { + // animation that just ended doesn't loop! That means we just finished a fidget + // and should return to our heaviest weighted idle (the subtle one) + iSequence = LookupActivityHeaviest ( m_Activity ); + } + + if ( iSequence != ACTIVITY_NOT_AVAILABLE ) + { + pev->sequence = iSequence; // Set to new anim (if it's there) + ResetSequenceInfo( ); + } + } + + DispatchAnimEvents( flInterval ); + + if ( !MovementIsComplete() ) + { + Move( flInterval ); + } +#if _DEBUG + else + { + if ( !TaskIsRunning() && !TaskIsComplete() ) + ALERT( at_error, "Schedule stalled!!\n" ); + } +#endif +} + +//========================================================= +// Monster Think - calls out to core AI functions and handles this +// monster's specific animation events +//========================================================= +void CBaseMonster :: MonsterThink ( void ) +{ + SetNextThink( 0.1 );// keep monster thinking. + + RunAI(); + + RunAnimation(); +} + +//========================================================= +// CBaseMonster - USE - will make a monster angry at whomever +// activated it. +//========================================================= +void CBaseMonster :: MonsterUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_IdealMonsterState = MONSTERSTATE_ALERT; +} + +//========================================================= +// Ignore conditions - before a set of conditions is allowed +// to interrupt a monster's schedule, this function removes +// conditions that we have flagged to interrupt the current +// schedule, but may not want to interrupt the schedule every +// time. (Pain, for instance) +//========================================================= +int CBaseMonster :: IgnoreConditions ( void ) +{ + int iIgnoreConditions = 0; + + if ( !FShouldEat() ) + { + // not hungry? Ignore food smell. + iIgnoreConditions |= bits_COND_SMELL_FOOD; + } + + if ( m_MonsterState == MONSTERSTATE_SCRIPT && m_pCine ) + iIgnoreConditions |= m_pCine->IgnoreConditions(); + + return iIgnoreConditions; +} + +//========================================================= +// RouteClear - zeroes out the monster's route array and goal +//========================================================= +void CBaseMonster :: RouteClear ( void ) +{ + RouteNew(); + pev->gaitsequence = 0; // buz + m_movementGoal = MOVEGOAL_NONE; + m_movementActivity = ACT_IDLE; + Forget( bits_MEMORY_MOVE_FAILED ); +} + +//========================================================= +// Route New - clears out a route to be changed, but keeps +// goal intact. +//========================================================= +void CBaseMonster :: RouteNew ( void ) +{ + m_Route[ 0 ].iType = 0; + m_iRouteIndex = 0; +} + +//========================================================= +// FRouteClear - returns TRUE if the Route is cleared out +// ( invalid ) +//========================================================= +BOOL CBaseMonster :: FRouteClear ( void ) +{ + if ( m_Route[ m_iRouteIndex ].iType == 0 || m_movementGoal == MOVEGOAL_NONE ) + return TRUE; + + return FALSE; +} + +//========================================================= +// FRefreshRoute - after calculating a path to the monster's +// target, this function copies as many waypoints as possible +// from that path to the monster's Route array +//========================================================= +BOOL CBaseMonster :: FRefreshRoute ( void ) +{ + CBaseEntity *pPathCorner; + int i; + BOOL returnCode; + + RouteNew(); + + returnCode = FALSE; + + switch( m_movementGoal ) + { + case MOVEGOAL_PATHCORNER: + { + // monster is on a path_corner loop + pPathCorner = m_pGoalEnt; + i = 0; + + while ( pPathCorner && i < ROUTE_SIZE ) + { + m_Route[ i ].iType = bits_MF_TO_PATHCORNER; + m_Route[ i ].vecLocation = pPathCorner->pev->origin; + + pPathCorner = pPathCorner->GetNextTarget(); + + // Last path_corner in list? + if ( !pPathCorner ) + m_Route[i].iType |= bits_MF_IS_GOAL; + + i++; + } + } + returnCode = TRUE; + break; + + case MOVEGOAL_ENEMY: + returnCode = BuildRoute( m_vecEnemyLKP, bits_MF_TO_ENEMY, m_hEnemy ); + break; + + case MOVEGOAL_LOCATION: + returnCode = BuildRoute( m_vecMoveGoal, bits_MF_TO_LOCATION, NULL ); + break; + + case MOVEGOAL_TARGETENT: + if (m_hTargetEnt != NULL) + { + returnCode = BuildRoute( m_hTargetEnt->pev->origin, bits_MF_TO_TARGETENT, m_hTargetEnt ); + } + break; + + case MOVEGOAL_NODE: + returnCode = FGetNodeRoute( m_vecMoveGoal ); +// if ( returnCode ) +// RouteSimplify( NULL ); + break; + } + + return returnCode; +} + + +BOOL CBaseMonster::MoveToEnemy( Activity movementAct, float waitTime ) +{ + m_movementActivity = movementAct; + m_moveWaitTime = waitTime; + + m_movementGoal = MOVEGOAL_ENEMY; + return FRefreshRoute(); +} + + +BOOL CBaseMonster::MoveToLocation( Activity movementAct, float waitTime, const Vector &goal ) +{ + m_movementActivity = movementAct; + m_moveWaitTime = waitTime; + + m_movementGoal = MOVEGOAL_LOCATION; + m_vecMoveGoal = goal; + return FRefreshRoute(); +} + + +BOOL CBaseMonster::MoveToTarget( Activity movementAct, float waitTime ) +{ + m_movementActivity = movementAct; + m_moveWaitTime = waitTime; + + m_movementGoal = MOVEGOAL_TARGETENT; + return FRefreshRoute(); +} + + +BOOL CBaseMonster::MoveToNode( Activity movementAct, float waitTime, const Vector &goal ) +{ + m_movementActivity = movementAct; + m_moveWaitTime = waitTime; + + m_movementGoal = MOVEGOAL_NODE; + m_vecMoveGoal = goal; + return FRefreshRoute(); +} + + +#ifdef _DEBUG +void DrawRoute( entvars_t *pev, WayPoint_t *m_Route, int m_iRouteIndex, int r, int g, int b ) +{ + int i; + + if ( m_Route[m_iRouteIndex].iType == 0 ) + { + ALERT( at_aiconsole, "Can't draw route!\n" ); + return; + } + +// UTIL_ParticleEffect ( m_Route[ m_iRouteIndex ].vecLocation, g_vecZero, 255, 25 ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( m_Route[ m_iRouteIndex ].vecLocation.x ); + WRITE_COORD( m_Route[ m_iRouteIndex ].vecLocation.y ); + WRITE_COORD( m_Route[ m_iRouteIndex ].vecLocation.z ); + + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 1 ); // life + WRITE_BYTE( 16 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( r ); // r, g, b + WRITE_BYTE( g ); // r, g, b + WRITE_BYTE( b ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); + + for ( i = m_iRouteIndex ; i < ROUTE_SIZE - 1; i++ ) + { + if ( (m_Route[ i ].iType & bits_MF_IS_GOAL) || (m_Route[ i+1 ].iType == 0) ) + break; + + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS ); + WRITE_COORD( m_Route[ i ].vecLocation.x ); + WRITE_COORD( m_Route[ i ].vecLocation.y ); + WRITE_COORD( m_Route[ i ].vecLocation.z ); + WRITE_COORD( m_Route[ i + 1 ].vecLocation.x ); + WRITE_COORD( m_Route[ i + 1 ].vecLocation.y ); + WRITE_COORD( m_Route[ i + 1 ].vecLocation.z ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 1 ); // life + WRITE_BYTE( 8 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( r ); // r, g, b + WRITE_BYTE( g ); // r, g, b + WRITE_BYTE( b ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); + +// UTIL_ParticleEffect ( m_Route[ i ].vecLocation, g_vecZero, 255, 25 ); + } +} +#endif + + +int ShouldSimplify( int routeType ) +{ + routeType &= ~bits_MF_IS_GOAL; + + if ( (routeType == bits_MF_TO_PATHCORNER) || (routeType & bits_MF_DONT_SIMPLIFY) ) + return FALSE; + return TRUE; +} + +//========================================================= +// RouteSimplify +// +// Attempts to make the route more direct by cutting out +// unnecessary nodes & cutting corners. +// +//========================================================= +void CBaseMonster :: RouteSimplify( CBaseEntity *pTargetEnt ) +{ + // BUGBUG: this doesn't work 100% yet + int i, count, outCount; + Vector vecStart; + WayPoint_t outRoute[ ROUTE_SIZE * 2 ]; // Any points except the ends can turn into 2 points in the simplified route + + count = 0; + + for ( i = m_iRouteIndex; i < ROUTE_SIZE; i++ ) + { + if ( !m_Route[i].iType ) + break; + else + count++; + if ( m_Route[i].iType & bits_MF_IS_GOAL ) + break; + } + // Can't simplify a direct route! + if ( count < 2 ) + { +// DrawRoute( pev, m_Route, m_iRouteIndex, 0, 0, 255 ); + return; + } + + outCount = 0; + vecStart = pev->origin; + for ( i = 0; i < count-1; i++ ) + { + // Don't eliminate path_corners + if ( !ShouldSimplify( m_Route[m_iRouteIndex+i].iType ) ) + { + outRoute[outCount] = m_Route[ m_iRouteIndex + i ]; + outCount++; + } + else if ( CheckLocalMove ( vecStart, m_Route[m_iRouteIndex+i+1].vecLocation, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + // Skip vert + continue; + } + else + { + Vector vecTest, vecSplit; + + // Halfway between this and next + vecTest = (m_Route[m_iRouteIndex+i+1].vecLocation + m_Route[m_iRouteIndex+i].vecLocation) * 0.5; + + // Halfway between this and previous + vecSplit = (m_Route[m_iRouteIndex+i].vecLocation + vecStart) * 0.5; + + int iType = (m_Route[m_iRouteIndex+i].iType | bits_MF_TO_DETOUR) & ~bits_MF_NOT_TO_MASK; + if ( CheckLocalMove ( vecStart, vecTest, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + outRoute[outCount].iType = iType; + outRoute[outCount].vecLocation = vecTest; + } + else if ( CheckLocalMove ( vecSplit, vecTest, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + outRoute[outCount].iType = iType; + outRoute[outCount].vecLocation = vecSplit; + outRoute[outCount+1].iType = iType; + outRoute[outCount+1].vecLocation = vecTest; + outCount++; // Adding an extra point + } + else + { + outRoute[outCount] = m_Route[ m_iRouteIndex + i ]; + } + } + // Get last point + vecStart = outRoute[ outCount ].vecLocation; + outCount++; + } + ASSERT( i < count ); + outRoute[outCount] = m_Route[ m_iRouteIndex + i ]; + outCount++; + + // Terminate + outRoute[outCount].iType = 0; + ASSERT( outCount < (ROUTE_SIZE*2) ); + +// Copy the simplified route, disable for testing + m_iRouteIndex = 0; + for ( i = 0; i < ROUTE_SIZE && i < outCount; i++ ) + { + m_Route[i] = outRoute[i]; + } + + // Terminate route + if ( i < ROUTE_SIZE ) + m_Route[i].iType = 0; + +// Debug, test movement code +#if 0 +// if ( CVAR_GET_FLOAT( "simplify" ) != 0 ) + DrawRoute( pev, outRoute, 0, 255, 0, 0 ); +// else + DrawRoute( pev, m_Route, m_iRouteIndex, 0, 255, 0 ); +#endif +} + +//========================================================= +// FBecomeProne - tries to send a monster into PRONE state. +// right now only used when a barnacle snatches someone, so +// may have some special case stuff for that. +//========================================================= +BOOL CBaseMonster :: FBecomeProne ( void ) +{ + if ( FBitSet ( pev->flags, FL_ONGROUND ) ) + { + pev->flags -= FL_ONGROUND; + } + + m_IdealMonsterState = MONSTERSTATE_PRONE; + return TRUE; +} + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CBaseMonster :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( flDist > 64 && flDist <= 784 && flDot >= 0.5 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack2 +//========================================================= +BOOL CBaseMonster :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + if ( flDist > 64 && flDist <= 512 && flDot >= 0.5 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckMeleeAttack1 +//========================================================= +BOOL CBaseMonster :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + // Decent fix to keep folks from kicking/punching hornets and snarks is to check the onground flag(sjb) + if ( flDist <= 64 && flDot >= 0.7 && m_hEnemy != NULL && FBitSet ( m_hEnemy->pev->flags, FL_ONGROUND ) ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckMeleeAttack2 +//========================================================= +BOOL CBaseMonster :: CheckMeleeAttack2 ( float flDot, float flDist ) +{ + if ( flDist <= 64 && flDot >= 0.7 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckAttacks - sets all of the bits for attacks that the +// monster is capable of carrying out on the passed entity. +//========================================================= +void CBaseMonster :: CheckAttacks ( CBaseEntity *pTarget, float flDist ) +{ + Vector2D vec2LOS; + float flDot; + + UTIL_MakeVectors ( pev->angles ); + + vec2LOS = ( pTarget->pev->origin - pev->origin ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + flDot = DotProduct (vec2LOS , gpGlobals->v_forward.Make2D() ); + + // we know the enemy is in front now. We'll find which attacks the monster is capable of by + // checking for corresponding Activities in the model file, then do the simple checks to validate + // those attack types. + + // Clear all attack conditions + ClearConditions( bits_COND_CAN_RANGE_ATTACK1 | bits_COND_CAN_RANGE_ATTACK2 | bits_COND_CAN_MELEE_ATTACK1 |bits_COND_CAN_MELEE_ATTACK2 ); + + if ( m_afCapability & bits_CAP_RANGE_ATTACK1 ) + { + if ( CheckRangeAttack1 ( flDot, flDist ) ) + SetConditions( bits_COND_CAN_RANGE_ATTACK1 ); + } + if ( m_afCapability & bits_CAP_RANGE_ATTACK2 ) + { + if ( CheckRangeAttack2 ( flDot, flDist ) ) + SetConditions( bits_COND_CAN_RANGE_ATTACK2 ); + } + if ( m_afCapability & bits_CAP_MELEE_ATTACK1 ) + { + if ( CheckMeleeAttack1 ( flDot, flDist ) ) + SetConditions( bits_COND_CAN_MELEE_ATTACK1 ); + } + if ( m_afCapability & bits_CAP_MELEE_ATTACK2 ) + { + if ( CheckMeleeAttack2 ( flDot, flDist ) ) + SetConditions( bits_COND_CAN_MELEE_ATTACK2 ); + } +} + +//========================================================= +// CanCheckAttacks - prequalifies a monster to do more fine +// checking of potential attacks. +//========================================================= +BOOL CBaseMonster :: FCanCheckAttacks ( void ) +{ + if ( HasConditions(bits_COND_SEE_ENEMY) && !HasConditions( bits_COND_ENEMY_TOOFAR ) ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// CheckEnemy - part of the Condition collection process, +// gets and stores data and conditions pertaining to a monster's +// enemy. Returns TRUE if Enemy LKP was updated. +//========================================================= +int CBaseMonster :: CheckEnemy ( CBaseEntity *pEnemy ) +{ + float flDistToEnemy; + int iUpdatedLKP;// set this to TRUE if you update the EnemyLKP in this function. + + iUpdatedLKP = FALSE; + ClearConditions ( bits_COND_ENEMY_FACING_ME ); + ClearConditions ( bits_COND_CROUCH_NOT_SAFE ); // buz + + if ( !FVisible( pEnemy ) ) + { + ASSERT(!HasConditions(bits_COND_SEE_ENEMY)); + SetConditions( bits_COND_ENEMY_OCCLUDED ); + } + else + ClearConditions( bits_COND_ENEMY_OCCLUDED ); + + if ( !pEnemy->IsAlive() ) + { + SetConditions ( bits_COND_ENEMY_DEAD ); + ClearConditions( bits_COND_SEE_ENEMY | bits_COND_ENEMY_OCCLUDED ); + return FALSE; + } + + // buz: check, is it safe to hide just by crouching? (when monster needs to reload or hurt) + if ( m_afCapability & bits_CAP_CROUCH_COVER ) + { + CBaseMonster *pEnemyMonster = pEnemy->MyMonsterPointer(); + if (pEnemyMonster) + { + TraceResult tr; + Vector vecEnemy = pEnemyMonster->GetGunPosition( ); + Vector vecMeCrouched = pev->origin + Vector(0, 0, 36); + UTIL_TraceLine(vecEnemy, vecMeCrouched, ignore_monsters, ignore_glass, ENT(pev)/*pentIgnore*/, &tr); + if (tr.flFraction == 1.0) // can be shot, not safe + { + SetConditions( bits_COND_CROUCH_NOT_SAFE ); + } + } + } + + Vector vecEnemyPos = pEnemy->pev->origin; + // distance to enemy's origin + flDistToEnemy = ( vecEnemyPos - pev->origin ).Length(); + vecEnemyPos.z += pEnemy->pev->size.z * 0.5; + // distance to enemy's head + float flDistToEnemy2 = (vecEnemyPos - pev->origin).Length(); + if (flDistToEnemy2 < flDistToEnemy) + flDistToEnemy = flDistToEnemy2; + else + { + // distance to enemy's feet + vecEnemyPos.z -= pEnemy->pev->size.z; + float flDistToEnemy2 = (vecEnemyPos - pev->origin).Length(); + if (flDistToEnemy2 < flDistToEnemy) + flDistToEnemy = flDistToEnemy2; + } + + if ( HasConditions( bits_COND_SEE_ENEMY ) ) + { + CBaseMonster *pEnemyMonster; + + iUpdatedLKP = TRUE; + m_vecEnemyLKP = pEnemy->pev->origin; + + pEnemyMonster = pEnemy->MyMonsterPointer(); + + if ( pEnemyMonster ) + { + if ( pEnemyMonster->FInViewCone ( this ) ) + { + SetConditions ( bits_COND_ENEMY_FACING_ME ); + } + else + ClearConditions( bits_COND_ENEMY_FACING_ME ); + } + + if (pEnemy->pev->velocity != Vector( 0, 0, 0)) + { + // trail the enemy a bit + m_vecEnemyLKP = m_vecEnemyLKP - pEnemy->pev->velocity * RANDOM_FLOAT( -0.05, 0 ); + } + else + { + // UNDONE: use pev->oldorigin? + } + } + else if ( !HasConditions(bits_COND_ENEMY_OCCLUDED|bits_COND_SEE_ENEMY) && ( flDistToEnemy <= 256 ) ) + { + // if the enemy is not occluded, and unseen, that means it is behind or beside the monster. + // if the enemy is near enough the monster, we go ahead and let the monster know where the + // enemy is. + iUpdatedLKP = TRUE; + m_vecEnemyLKP = pEnemy->pev->origin; + } + + if ( flDistToEnemy >= m_flDistTooFar ) + { + // enemy is very far away from monster + SetConditions( bits_COND_ENEMY_TOOFAR ); + } + else + ClearConditions( bits_COND_ENEMY_TOOFAR ); + + if ( FCanCheckAttacks() ) + { + CheckAttacks ( m_hEnemy, flDistToEnemy ); + } + + if ( m_movementGoal == MOVEGOAL_ENEMY ) + { + for ( int i = m_iRouteIndex; i < ROUTE_SIZE; i++ ) + { + if ( m_Route[ i ].iType == (bits_MF_IS_GOAL|bits_MF_TO_ENEMY) ) + { + // UNDONE: Should we allow monsters to override this distance (80?) + if ( (m_Route[ i ].vecLocation - m_vecEnemyLKP).Length() > 80 ) + { + // Refresh + FRefreshRoute(); + return iUpdatedLKP; + } + } + } + } + + return iUpdatedLKP; +} + +//========================================================= +// PushEnemy - remember the last few enemies, always remember the player +//========================================================= +void CBaseMonster :: PushEnemy( CBaseEntity *pEnemy, Vector &vecLastKnownPos ) +{ + int i; + + if (pEnemy == NULL) + return; + + // UNDONE: blah, this is bad, we should use a stack but I'm too lazy to code one. + for (i = 0; i < MAX_OLD_ENEMIES; i++) + { + if (m_hOldEnemy[i] == pEnemy) + return; + if (m_hOldEnemy[i] == NULL) // someone died, reuse their slot + break; + } + if (i >= MAX_OLD_ENEMIES) + return; + + m_hOldEnemy[i] = pEnemy; + m_vecOldEnemy[i] = vecLastKnownPos; +} + +//========================================================= +// PopEnemy - try remembering the last few enemies +//========================================================= +BOOL CBaseMonster :: PopEnemy( ) +{ + // UNDONE: blah, this is bad, we should use a stack but I'm too lazy to code one. + for (int i = MAX_OLD_ENEMIES - 1; i >= 0; i--) + { + if (m_hOldEnemy[i] != NULL) + { + if (m_hOldEnemy[i]->IsAlive( )) // cheat and know when they die + { + m_hEnemy = m_hOldEnemy[i]; + m_vecEnemyLKP = m_vecOldEnemy[i]; + // ALERT( at_console, "remembering\n"); + return TRUE; + } + else + { + m_hOldEnemy[i] = NULL; + } + } + } + return FALSE; +} + +//========================================================= +// SetActivity +//========================================================= +void CBaseMonster :: SetActivity ( Activity NewActivity ) +{ + int iSequence; + + iSequence = LookupActivity ( NewActivity ); + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + { + if ( pev->sequence != iSequence || !m_fSequenceLoops ) + { + // don't reset frame between walk and run + if ( !(m_Activity == ACT_WALK || m_Activity == ACT_RUN) || !(NewActivity == ACT_WALK || NewActivity == ACT_RUN)) + pev->frame = 0; + } + + pev->sequence = iSequence; // Set to the reset anim (if it's there) + ResetSequenceInfo( ); + } + else + { + // Not available try to get default anim + ALERT ( at_aiconsole, "%s has no sequence for act:%s\n", STRING(pev->classname), GetNameForActivity( NewActivity )); + pev->sequence = 0; // Set to the reset anim (if it's there) + } + + m_Activity = NewActivity; // Go ahead and set this so it doesn't keep trying when the anim is not present + + // In case someone calls this with something other than the ideal activity + m_IdealActivity = m_Activity; + + // NOTE: set yaw_speed right when activity is changed + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + RecalculateYawSpeed(); +} + +void CBaseMonster :: SetIdealActivity( Activity NewActivity ) +{ + // ignore if it's an ACT_TRANSITION, it means somewhere we're setting IdealActivity with a bogus intermediate value + if( NewActivity == ACT_TRANSITION ) + return; + + if( NewActivity == ACT_RESET ) + { + // They probably meant to call SetActivity(ACT_RESET)... we'll fix it for them. + SetActivity( ACT_RESET ); + return; + } + + m_IdealActivity = NewActivity; + + if( NewActivity == ACT_DO_NOT_DISTURB ) + { + // Don't resolve anything! Leave it the way the user has it right now. + return; + } + + // Perform translation in case we need to change sequences within a single activity, + // such as between a standing idle and a crouching idle. +// ResolveActivityToSequence( m_IdealActivity, m_nIdealSequence, m_IdealTranslatedActivity, m_IdealWeaponActivity ); +} + +//========================================================= +// SetSequenceByName +//========================================================= +void CBaseMonster :: SetSequenceByName ( char *szSequence ) +{ + int iSequence; + + iSequence = LookupSequence ( szSequence ); + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + { + if ( pev->sequence != iSequence || !m_fSequenceLoops ) + { + pev->frame = 0; + } + + pev->sequence = iSequence; // Set to the reset anim (if it's there) + ResetSequenceInfo( ); + RecalculateYawSpeed(); + } + else + { + // Not available try to get default anim + ALERT ( at_aiconsole, "%s has no sequence named:%s\n", STRING(pev->classname), szSequence ); + pev->sequence = 0; // Set to the reset anim (if it's there) + } +} + +//========================================================= +// CheckLocalMove - returns TRUE if the caller can walk a +// straight line from its current origin to the given +// location. If so, don't use the node graph! +// +// if a valid pointer to a int is passed, the function +// will fill that int with the distance that the check +// reached before hitting something. THIS ONLY HAPPENS +// IF THE LOCAL MOVE CHECK FAILS! +// +// !!!PERFORMANCE - should we try to load balance this? +// DON"T USE SETORIGIN! +//========================================================= +#define LOCAL_STEP_SIZE 16 +//#define LOCAL_STEP_SIZE 8 // buz test +int CBaseMonster :: CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ) +{ + Vector vecStartPos;// record monster's position before trying the move + float flYaw; + float flDist; + float flStep, stepSize; + int iReturn; + + vecStartPos = pev->origin; + + + flYaw = UTIL_VecToYaw ( vecEnd - vecStart );// build a yaw that points to the goal. + flDist = ( vecEnd - vecStart ).Length2D();// get the distance. + iReturn = LOCALMOVE_VALID;// assume everything will be ok. + + // move the monster to the start of the local move that's to be checked. + UTIL_SetOrigin( this, vecStart );// !!!BUGBUG - won't this fire triggers? - nope, SetOrigin doesn't fire + + if ( !(pev->flags & (FL_FLY|FL_SWIM)) ) + { + DROP_TO_FLOOR( ENT( pev ) );//make sure monster is on the floor! + } + + //pev->origin.z = vecStartPos.z;//!!!HACKHACK + +// pev->origin = vecStart; + +/* + if ( flDist > 1024 ) + { + // !!!PERFORMANCE - this operation may be too CPU intensive to try checks this large. + // We don't lose much here, because a distance this great is very likely + // to have something in the way. + + // since we've actually moved the monster during the check, undo the move. + pev->origin = vecStartPos; + return FALSE; + } +*/ + // this loop takes single steps to the goal. + for ( flStep = 0 ; flStep < flDist ; flStep += LOCAL_STEP_SIZE ) + { + stepSize = LOCAL_STEP_SIZE; + + if ( (flStep + LOCAL_STEP_SIZE) >= (flDist-1) ) + stepSize = (flDist - flStep) - 1; + +// UTIL_ParticleEffect ( pev->origin, g_vecZero, 255, 25 ); + + if ( !WALK_MOVE( ENT(pev), flYaw, stepSize, WALKMOVE_CHECKONLY ) ) + {// can't take the next step, fail! + + if ( pflDist != NULL ) + { + *pflDist = flStep; + } + if ( pTarget && pTarget->edict() == gpGlobals->trace_ent ) + { + // if this step hits target ent, the move is legal. + iReturn = LOCALMOVE_VALID; + break; + } + else + { + // If we're going toward an entity, and we're almost getting there, it's OK. +// if ( pTarget && fabs( flDist - iStep ) < LOCAL_STEP_SIZE ) +// fReturn = TRUE; +// else + iReturn = LOCALMOVE_INVALID; + break; + } + + } + } + + if ( iReturn == LOCALMOVE_VALID && !(pev->flags & (FL_FLY|FL_SWIM) ) && (!pTarget || (pTarget->pev->flags & FL_ONGROUND)) ) + { + // The monster can move to a spot UNDER the target, but not to it. Don't try to triangulate, go directly to the node graph. + // UNDONE: Magic # 64 -- this used to be pev->size.z but that won't work for small creatures like the headcrab + if ( fabs(vecEnd.z - pev->origin.z) > 64 ) + { + iReturn = LOCALMOVE_INVALID_DONT_TRIANGULATE; + } + } + /* + // uncommenting this block will draw a line representing the nearest legal move. + WRITE_BYTE(MSG_BROADCAST, SVC_TEMPENTITY); + WRITE_BYTE(MSG_BROADCAST, TE_SHOWLINE); + WRITE_COORD(MSG_BROADCAST, pev->origin.x); + WRITE_COORD(MSG_BROADCAST, pev->origin.y); + WRITE_COORD(MSG_BROADCAST, pev->origin.z); + WRITE_COORD(MSG_BROADCAST, vecStart.x); + WRITE_COORD(MSG_BROADCAST, vecStart.y); + WRITE_COORD(MSG_BROADCAST, vecStart.z); + */ + + // since we've actually moved the monster during the check, undo the move. + UTIL_SetOrigin( this, vecStartPos ); + + return iReturn; +} + + +float CBaseMonster :: OpenDoorAndWait( entvars_t *pevDoor ) +{ + float flTravelTime = 0; + + //ALERT(at_aiconsole, "A door. "); + CBaseEntity *pcbeDoor = CBaseEntity::Instance(pevDoor); + if (pcbeDoor && !pcbeDoor->IsLockedByMaster()) + { + //ALERT(at_aiconsole, "unlocked! "); + pcbeDoor->Use(this, this, USE_ON, 0.0); + //ALERT(at_aiconsole, "pevDoor->nextthink = %d ms\n", (int)(1000*pevDoor->nextthink)); + //ALERT(at_aiconsole, "pevDoor->ltime = %d ms\n", (int)(1000*pevDoor->ltime)); + //ALERT(at_aiconsole, "pev-> nextthink = %d ms\n", (int)(1000*pev->nextthink)); + //ALERT(at_aiconsole, "pev->ltime = %d ms\n", (int)(1000*pev->ltime)); + + flTravelTime = pcbeDoor->m_fNextThink - pevDoor->ltime; + + //ALERT(at_aiconsole, "Waiting %d ms\n", (int)(1000*flTravelTime)); + if ( pcbeDoor->pev->targetname ) + { + CBaseEntity *pTarget = NULL; + for (;;) + { + pTarget = UTIL_FindEntityByTargetname( pTarget, STRING(pcbeDoor->pev->targetname)); + if (!pTarget) + break; + + if ( VARS( pTarget->pev ) != pcbeDoor->pev && + FClassnameIs ( pTarget->pev, STRING(pcbeDoor->pev->classname) ) ) + { + pTarget->Use(this, this, USE_ON, 0.0); + } + } + } + } + + return gpGlobals->time + flTravelTime; +} + + +//========================================================= +// AdvanceRoute - poorly named function that advances the +// m_iRouteIndex. If it goes beyond ROUTE_SIZE, the route +// is refreshed. +//========================================================= +void CBaseMonster :: AdvanceRoute ( float distance ) +{ + + if ( m_iRouteIndex == ROUTE_SIZE - 1 ) + { + // time to refresh the route. + if ( !FRefreshRoute() ) + { + ALERT ( at_aiconsole, "Can't Refresh Route!!\n" ); + } + } + else + { + if ( ! (m_Route[ m_iRouteIndex ].iType & bits_MF_IS_GOAL) ) + { + // If we've just passed a path_corner, advance m_pGoalEnt + if ( (m_Route[ m_iRouteIndex ].iType & ~bits_MF_NOT_TO_MASK) == bits_MF_TO_PATHCORNER ) + m_pGoalEnt = m_pGoalEnt->GetNextTarget(); + + // IF both waypoints are nodes, then check for a link for a door and operate it. + // + if ( (m_Route[m_iRouteIndex].iType & bits_MF_TO_NODE) == bits_MF_TO_NODE + && (m_Route[m_iRouteIndex+1].iType & bits_MF_TO_NODE) == bits_MF_TO_NODE) + { + //ALERT(at_aiconsole, "SVD: Two nodes. "); + + int iSrcNode = WorldGraph.FindNearestNode(m_Route[m_iRouteIndex].vecLocation, this ); + int iDestNode = WorldGraph.FindNearestNode(m_Route[m_iRouteIndex+1].vecLocation, this ); + + int iLink; + WorldGraph.HashSearch(iSrcNode, iDestNode, iLink); + + if ( iLink >= 0 && WorldGraph.m_pLinkPool[iLink].m_pLinkEnt != NULL ) + { + //ALERT(at_aiconsole, "A link. "); + if ( WorldGraph.HandleLinkEnt ( iSrcNode, WorldGraph.m_pLinkPool[iLink].m_pLinkEnt, m_afCapability, CGraph::NODEGRAPH_DYNAMIC ) ) + { + //ALERT(at_aiconsole, "usable."); + entvars_t *pevDoor = WorldGraph.m_pLinkPool[iLink].m_pLinkEnt; + if (pevDoor) + { + m_flMoveWaitFinished = OpenDoorAndWait( pevDoor ); +// ALERT( at_aiconsole, "Wating for door %.2f\n", m_flMoveWaitFinished-gpGlobals->time ); + } + } + } + //ALERT(at_aiconsole, "\n"); + } + m_iRouteIndex++; + } + else // At goal!!! + { + if ( distance < m_flGroundSpeed * 0.2 /* FIX */ ) + { + MovementComplete(); + } + } + } +} + + +int CBaseMonster :: RouteClassify( int iMoveFlag ) +{ + int movementGoal; + + movementGoal = MOVEGOAL_NONE; + + if ( iMoveFlag & bits_MF_TO_TARGETENT ) + movementGoal = MOVEGOAL_TARGETENT; + else if ( iMoveFlag & bits_MF_TO_ENEMY ) + movementGoal = MOVEGOAL_ENEMY; + else if ( iMoveFlag & bits_MF_TO_PATHCORNER ) + movementGoal = MOVEGOAL_PATHCORNER; + else if ( iMoveFlag & bits_MF_TO_NODE ) + movementGoal = MOVEGOAL_NODE; + else if ( iMoveFlag & bits_MF_TO_LOCATION ) + movementGoal = MOVEGOAL_LOCATION; + + return movementGoal; +} + +//========================================================= +// BuildRoute +//========================================================= +BOOL CBaseMonster :: BuildRoute ( const Vector &vecGoal, int iMoveFlag, CBaseEntity *pTarget ) +{ + float flDist; + Vector vecApex; + int iLocalMove; + + RouteNew(); + m_movementGoal = RouteClassify( iMoveFlag ); + +// so we don't end up with no moveflags + m_Route[ 0 ].vecLocation = vecGoal; + m_Route[ 0 ].iType = iMoveFlag | bits_MF_IS_GOAL; + +// check simple local move + iLocalMove = CheckLocalMove( pev->origin, vecGoal, pTarget, &flDist ); + + if ( iLocalMove == LOCALMOVE_VALID ) + { + // monster can walk straight there! + return TRUE; + } +// try to triangulate around any obstacles. + else if ( iLocalMove != LOCALMOVE_INVALID_DONT_TRIANGULATE && FTriangulate( pev->origin, vecGoal, flDist, pTarget, &vecApex ) ) + { + // there is a slightly more complicated path that allows the monster to reach vecGoal + m_Route[ 0 ].vecLocation = vecApex; + m_Route[ 0 ].iType = (iMoveFlag | bits_MF_TO_DETOUR); + + m_Route[ 1 ].vecLocation = vecGoal; + m_Route[ 1 ].iType = iMoveFlag | bits_MF_IS_GOAL; + + /* + WRITE_BYTE(MSG_BROADCAST, SVC_TEMPENTITY); + WRITE_BYTE(MSG_BROADCAST, TE_SHOWLINE); + WRITE_COORD(MSG_BROADCAST, vecApex.x ); + WRITE_COORD(MSG_BROADCAST, vecApex.y ); + WRITE_COORD(MSG_BROADCAST, vecApex.z ); + WRITE_COORD(MSG_BROADCAST, vecApex.x ); + WRITE_COORD(MSG_BROADCAST, vecApex.y ); + WRITE_COORD(MSG_BROADCAST, vecApex.z + 128 ); + */ + + RouteSimplify( pTarget ); + return TRUE; + } + +// last ditch, try nodes + if ( FGetNodeRoute( vecGoal ) ) + { +// ALERT ( at_console, "Can get there on nodes\n" ); + m_vecMoveGoal = vecGoal; + RouteSimplify( pTarget ); + return TRUE; + } + + // b0rk + return FALSE; +} + + +//========================================================= +// InsertWaypoint - Rebuilds the existing route so that the +// supplied vector and moveflags are the first waypoint in +// the route, and fills the rest of the route with as much +// of the pre-existing route as possible +//========================================================= +void CBaseMonster :: InsertWaypoint ( Vector vecLocation, int afMoveFlags ) +{ + int i, type; + + + // we have to save some Index and Type information from the real + // path_corner or node waypoint that the monster was trying to reach. This makes sure that data necessary + // to refresh the original path exists even in the new waypoints that don't correspond directy to a path_corner + // or node. + type = afMoveFlags | (m_Route[ m_iRouteIndex ].iType & ~bits_MF_NOT_TO_MASK); + + for ( i = ROUTE_SIZE-1; i > 0; i-- ) + m_Route[i] = m_Route[i-1]; + + m_Route[ m_iRouteIndex ].vecLocation = vecLocation; + m_Route[ m_iRouteIndex ].iType = type; +} + +//========================================================= +// FTriangulate - tries to overcome local obstacles by +// triangulating a path around them. +// +// iApexDist is how far the obstruction that we are trying +// to triangulate around is from the monster. +//========================================================= +BOOL CBaseMonster :: FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, CBaseEntity *pTargetEnt, Vector *pApex ) +{ + Vector vecDir; + Vector vecForward; + Vector vecLeft;// the spot we'll try to triangulate to on the left + Vector vecRight;// the spot we'll try to triangulate to on the right + Vector vecTop;// the spot we'll try to triangulate to on the top + Vector vecBottom;// the spot we'll try to triangulate to on the bottom + Vector vecFarSide;// the spot that we'll move to after hitting the triangulated point, before moving on to our normal goal. + int i; + float sizeX, sizeZ; + + // If the hull width is less than 24, use 24 because CheckLocalMove uses a min of + // 24. + sizeX = pev->size.x; + if (sizeX < 24.0) + sizeX = 24.0; + else if (sizeX > 48.0) + sizeX = 48.0; + sizeZ = pev->size.z; + //if (sizeZ < 24.0) + // sizeZ = 24.0; + + vecForward = ( vecEnd - vecStart ).Normalize(); + + Vector vecDirUp(0,0,1); + vecDir = CrossProduct ( vecForward, vecDirUp); + + // start checking right about where the object is, picking two equidistant starting points, one on + // the left, one on the right. As we progress through the loop, we'll push these away from the obstacle, + // hoping to find a way around on either side. pev->size.x is added to the ApexDist in order to help select + // an apex point that insures that the monster is sufficiently past the obstacle before trying to turn back + // onto its original course. + + vecLeft = pev->origin + ( vecForward * ( flDist + sizeX ) ) - vecDir * ( sizeX * 3 ); + vecRight = pev->origin + ( vecForward * ( flDist + sizeX ) ) + vecDir * ( sizeX * 3 ); + if (pev->movetype == MOVETYPE_FLY) + { + vecTop = pev->origin + (vecForward * flDist) + (vecDirUp * sizeZ * 3); + vecBottom = pev->origin + (vecForward * flDist) - (vecDirUp * sizeZ * 3); + } + + vecFarSide = m_Route[ m_iRouteIndex ].vecLocation; + + vecDir = vecDir * sizeX * 2; + if (pev->movetype == MOVETYPE_FLY) + vecDirUp = vecDirUp * sizeZ * 2; + + for ( i = 0 ; i < 8; i++ ) + { +// Debug, Draw the triangulation +#if 0 + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( vecRight.x ); + WRITE_COORD( vecRight.y ); + WRITE_COORD( vecRight.z ); + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( vecLeft.x ); + WRITE_COORD( vecLeft.y ); + WRITE_COORD( vecLeft.z ); + MESSAGE_END(); +#endif + +#if 0 + if (pev->movetype == MOVETYPE_FLY) + { + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( vecTop.x ); + WRITE_COORD( vecTop.y ); + WRITE_COORD( vecTop.z ); + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( vecBottom.x ); + WRITE_COORD( vecBottom.y ); + WRITE_COORD( vecBottom.z ); + MESSAGE_END(); + } +#endif + + if ( CheckLocalMove( pev->origin, vecRight, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( CheckLocalMove ( vecRight, vecFarSide, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( pApex ) + { + *pApex = vecRight; + } + + return TRUE; + } + } + if ( CheckLocalMove( pev->origin, vecLeft, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( CheckLocalMove ( vecLeft, vecFarSide, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( pApex ) + { + *pApex = vecLeft; + } + + return TRUE; + } + } + + if (pev->movetype == MOVETYPE_FLY) + { + if ( CheckLocalMove( pev->origin, vecTop, pTargetEnt, NULL ) == LOCALMOVE_VALID) + { + if ( CheckLocalMove ( vecTop, vecFarSide, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( pApex ) + { + *pApex = vecTop; + //ALERT(at_aiconsole, "triangulate over\n"); + } + + return TRUE; + } + } +#if 1 + if ( CheckLocalMove( pev->origin, vecBottom, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( CheckLocalMove ( vecBottom, vecFarSide, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( pApex ) + { + *pApex = vecBottom; + //ALERT(at_aiconsole, "triangulate under\n"); + } + + return TRUE; + } + } +#endif + } + + vecRight = vecRight + vecDir; + vecLeft = vecLeft - vecDir; + if (pev->movetype == MOVETYPE_FLY) + { + vecTop = vecTop + vecDirUp; + vecBottom = vecBottom - vecDirUp; + } + } + + return FALSE; +} + +//========================================================= +// Move - take a single step towards the next ROUTE location +//========================================================= +#define DIST_TO_CHECK 200 + +void CBaseMonster :: Move ( float flInterval ) +{ + float flWaypointDist; + float flCheckDist; + float flDist;// how far the lookahead check got before hitting an object. + Vector vecDir; + Vector vecApex; + CBaseEntity *pTargetEnt; + + // Don't move if no valid route + if ( FRouteClear() ) + { + // If we still have a movement goal, then this is probably a route truncated by SimplifyRoute() + // so refresh it. + if ( m_movementGoal == MOVEGOAL_NONE || !FRefreshRoute() ) + { + ALERT( at_aiconsole, "Tried to move with no route!\n" ); + TaskFail(); + return; + } + } + + if ( m_flMoveWaitFinished > gpGlobals->time ) + return; + +// Debug, test movement code +#if 0 +// if ( CVAR_GET_FLOAT("stopmove" ) != 0 ) + { + if ( m_movementGoal == MOVEGOAL_ENEMY ) + RouteSimplify( m_hEnemy ); + else + RouteSimplify( m_hTargetEnt ); + FRefreshRoute(); + return; + } +#else +// Debug, draw the route +// DrawRoute( pev, m_Route, m_iRouteIndex, 0, 200, 0 ); +#endif + + // if the monster is moving directly towards an entity (enemy for instance), we'll set this pointer + // to that entity for the CheckLocalMove and Triangulate functions. + pTargetEnt = NULL; + + // local move to waypoint. + vecDir = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Normalize(); + flWaypointDist = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Length2D(); + + if (!pev->gaitsequence) // buz: dont turn strafing monsters + { + SetIdealYawToTargetAndUpdate( m_Route[ m_iRouteIndex ].vecLocation, AI_KEEP_YAW_SPEED ); + } + + // if the waypoint is closer than CheckDist, CheckDist is the dist to waypoint + if ( flWaypointDist < DIST_TO_CHECK ) + { + flCheckDist = flWaypointDist; + } + else + { + flCheckDist = DIST_TO_CHECK; + } + + if ( (m_Route[ m_iRouteIndex ].iType & (~bits_MF_NOT_TO_MASK)) == bits_MF_TO_ENEMY ) + { + // only on a PURE move to enemy ( i.e., ONLY MF_TO_ENEMY set, not MF_TO_ENEMY and DETOUR ) + pTargetEnt = m_hEnemy; + } + else if ( (m_Route[ m_iRouteIndex ].iType & ~bits_MF_NOT_TO_MASK) == bits_MF_TO_TARGETENT ) + { + pTargetEnt = m_hTargetEnt; + } + + // !!!BUGBUG - CheckDist should be derived from ground speed. + // If this fails, it should be because of some dynamic entity blocking this guy. + // We've already checked this path, so we should wait and time out if the entity doesn't move + flDist = 0; + if ( CheckLocalMove ( pev->origin, pev->origin + vecDir * flCheckDist, pTargetEnt, &flDist ) != LOCALMOVE_VALID ) + { + CBaseEntity *pBlocker; + + // Can't move, stop + Stop(); + // Blocking entity is in global trace_ent + pBlocker = CBaseEntity::Instance( gpGlobals->trace_ent ); + if (pBlocker) + { + DispatchBlocked( edict(), pBlocker->edict() ); + } + + if ( pBlocker && m_moveWaitTime > 0 && pBlocker->IsMoving() && !pBlocker->IsPlayer() && (gpGlobals->time-m_flMoveWaitFinished) > 3.0 ) + { + // Can we still move toward our target? + if ( flDist < m_flGroundSpeed ) + { + // No, Wait for a second + m_flMoveWaitFinished = gpGlobals->time + m_moveWaitTime; + return; + } + // Ok, still enough room to take a step + } + else + { + // try to triangulate around whatever is in the way. + if ( FTriangulate( pev->origin, m_Route[ m_iRouteIndex ].vecLocation, flDist, pTargetEnt, &vecApex ) ) + { + InsertWaypoint( vecApex, bits_MF_TO_DETOUR ); + RouteSimplify( pTargetEnt ); + } + else + { +// ALERT ( at_aiconsole, "Couldn't Triangulate\n" ); + Stop(); + // Only do this once until your route is cleared + if ( m_moveWaitTime > 0 && !(m_afMemory & bits_MEMORY_MOVE_FAILED) ) + { + FRefreshRoute(); + if ( FRouteClear() ) + { + TaskFail(); + } + else + { + // Don't get stuck + if ( (gpGlobals->time - m_flMoveWaitFinished) < 0.2 ) + Remember( bits_MEMORY_MOVE_FAILED ); + + m_flMoveWaitFinished = gpGlobals->time + 0.1; + } + } + else + { + TaskFail(); + ALERT( at_aiconsole, "%s Failed to move (%d)!\n", STRING(pev->classname), HasMemory( bits_MEMORY_MOVE_FAILED ) ); + //ALERT( at_aiconsole, "%f, %f, %f\n", pev->origin.z, (pev->origin + (vecDir * flCheckDist)).z, m_Route[m_iRouteIndex].vecLocation.z ); + + // buz: if blocker is player, tell him to move away from my path! + if (pBlocker->IsPlayer()) + BlockedByPlayer((CBasePlayer*)pBlocker); + } + return; + } + } + } + + // close enough to the target, now advance to the next target. This is done before actually reaching + // the target so that we get a nice natural turn while moving. + if ( ShouldAdvanceRoute( flWaypointDist ) )///!!!BUGBUG- magic number + { + AdvanceRoute( flWaypointDist ); + } + + // Might be waiting for a door + if ( m_flMoveWaitFinished > gpGlobals->time ) + { + Stop(); + return; + } + + // UNDONE: this is a hack to quit moving farther than it has looked ahead. + if (flCheckDist < m_flGroundSpeed * flInterval) + { + flInterval = flCheckDist / m_flGroundSpeed; + // ALERT( at_console, "%.02f\n", flInterval ); + } + MoveExecute( pTargetEnt, vecDir, flInterval ); + + if ( MovementIsComplete() ) + { + Stop(); + RouteClear(); + } +} + + +BOOL CBaseMonster:: ShouldAdvanceRoute( float flWaypointDist ) +{ + if ( flWaypointDist <= MONSTER_CUT_CORNER_DIST ) + { + // ALERT( at_console, "cut %f\n", flWaypointDist ); + return TRUE; + } + + return FALSE; +} + + +void CBaseMonster::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ) +{ +// float flYaw = UTIL_VecToYaw ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin );// build a yaw that points to the goal. +// WALK_MOVE( ENT(pev), flYaw, m_flGroundSpeed * flInterval, WALKMOVE_NORMAL ); + + // buz: strafing monsters movement + if (pev->gaitsequence) + { + float flTotal = m_flGroundSpeed * flInterval; + float flStep; + while (flTotal > 0.001) + { + // don't walk more than 16 units or stairs stop working + flStep = min( 16.0, flTotal ); + UTIL_MoveToOrigin ( ENT(pev), m_Route[ m_iRouteIndex ].vecLocation, flStep, MOVE_STRAFE ); + flTotal -= flStep; + } + } + else + { + if ( m_IdealActivity != m_movementActivity) + m_IdealActivity = m_movementActivity; + + float flTotal = m_flGroundSpeed * pev->framerate * flInterval; + float flStep; + while (flTotal > 0.001) + { + // don't walk more than 16 units or stairs stop working + flStep = min( 16.0, flTotal ); + UTIL_MoveToOrigin ( ENT(pev), m_Route[ m_iRouteIndex ].vecLocation, flStep, MOVE_NORMAL ); + flTotal -= flStep; + } + } + + CalcFacing( flInterval ); + + MoveFacing(); + + // ALERT( at_console, "dist %f\n", m_flGroundSpeed * pev->framerate * flInterval ); +} + + +//========================================================= +// MonsterInit - after a monster is spawned, it needs to +// be dropped into the world, checked for mobility problems, +// and put on the proper path, if any. This function does +// all of those things after the monster spawns. Any +// initialization that should take place for all monsters +// goes here. +//========================================================= +void CBaseMonster :: MonsterInit ( void ) +{ + if (!g_pGameRules->FAllowMonsters()) + { + pev->flags |= FL_KILLME; // Post this because some monster code modifies class data after calling this function +// REMOVE_ENTITY(ENT(pev)); + return; + } + + // buz + pev->takedamage = DAMAGE_AIM; + + pev->ideal_yaw = pev->angles.y; + pev->max_health = pev->health; + pev->deadflag = DEAD_NO; + m_IdealMonsterState = MONSTERSTATE_IDLE;// Assume monster will be idle, until proven otherwise + +// if (m_iStartAlert) +// m_IdealMonsterState = MONSTERSTATE_ALERT; // buz + + m_IdealActivity = ACT_IDLE; + + SetBits (pev->flags, FL_MONSTER); + if ( pev->spawnflags & SF_MONSTER_HITMONSTERCLIP ) + pev->flags |= FL_MONSTERCLIP; + + ClearSchedule(); + RouteClear(); + InitBoneControllers( ); // FIX: should be done in Spawn + + m_iHintNode = NO_NODE; + + m_afMemory = MEMORY_CLEAR; + + m_hEnemy = NULL; + + m_flDistTooFar = 1024.0; + m_flDistLook = 2048.0; + + // set eye position + SetEyePosition(); + + SetThink(&CBaseMonster :: MonsterInitThink ); + SetNextThink( 0.1 ); + SetUse(&CBaseMonster :: MonsterUse ); + + // buz: force body selection + if (pev->impulse) + { + pev->body = pev->impulse; + pev->impulse = 0; + } +} + +//========================================================= +// MonsterInitThink - Calls StartMonster. Startmonster is +// virtual, but this function cannot be +//========================================================= +void CBaseMonster :: MonsterInitThink ( void ) +{ + StartMonster(); +} + + +void CBaseMonster :: StartPatrol ( CBaseEntity* path ) +{ + m_pGoalEnt = path; + + if ( !m_pGoalEnt ) + { + ALERT(at_error, "ReadyMonster()--%s couldn't find target \"%s\"\n", STRING(pev->classname), STRING(pev->target)); + } + else + { + // Monster will start turning towards his destination +// SetIdealYawToTargetAndUpdate( m_pGoalEnt->pev->origin ); + + // set the monster up to walk a path corner path. + // !!!BUGBUG - this is a minor bit of a hack. + // JAYJAY + m_movementGoal = MOVEGOAL_PATHCORNER; + + if ( pev->movetype == MOVETYPE_FLY ) + m_movementActivity = ACT_FLY; + else + m_movementActivity = ACT_WALK; + + if ( !FRefreshRoute() ) + { + ALERT ( at_aiconsole, "Can't Create Route!\n" ); + } + SetState( MONSTERSTATE_IDLE ); + ChangeSchedule( GetScheduleOfType( SCHED_IDLE_WALK ) ); + } +} + +//========================================================= +// StartMonster - final bit of initization before a monster +// is turned over to the AI. +//========================================================= +void CBaseMonster :: StartMonster ( void ) +{ + // update capabilities + if ( LookupActivity ( ACT_RANGE_ATTACK1 ) != ACTIVITY_NOT_AVAILABLE ) + { + m_afCapability |= bits_CAP_RANGE_ATTACK1; + } + if ( LookupActivity ( ACT_RANGE_ATTACK2 ) != ACTIVITY_NOT_AVAILABLE ) + { + m_afCapability |= bits_CAP_RANGE_ATTACK2; + } + if ( LookupActivity ( ACT_MELEE_ATTACK1 ) != ACTIVITY_NOT_AVAILABLE ) + { + m_afCapability |= bits_CAP_MELEE_ATTACK1; + } + if ( LookupActivity ( ACT_MELEE_ATTACK2 ) != ACTIVITY_NOT_AVAILABLE ) + { + m_afCapability |= bits_CAP_MELEE_ATTACK2; + } + + // buz: ACT_TWITCH is cover animation + if ((LookupActivity ( ACT_TWITCH ) != ACTIVITY_NOT_AVAILABLE) && + !FClassnameIs(pev,"monster_leech")) // hack: leech uses ACT_TWITCH animation + { + m_afCapability |= bits_CAP_CROUCH_COVER; + } + + + // Raise monster off the floor one unit, then drop to floor + if ( pev->movetype != MOVETYPE_FLY && !FBitSet( pev->spawnflags, SF_MONSTER_FALL_TO_GROUND ) ) + { + pev->origin.z += 1; + DROP_TO_FLOOR ( ENT(pev) ); + // Try to move the monster to make sure it's not stuck in a brush. + //LRC- there are perfectly good reasons for making a monster stuck, so it shouldn't always be an error. + if (!WALK_MOVE ( ENT(pev), 0, 0, WALKMOVE_NORMAL ) && !FBitSet( pev->spawnflags, SF_MONSTER_NO_YELLOW_BLOBS)) + { + ALERT(at_debug, "%s \"%s\" stuck in wall--level design error\n", STRING(pev->classname), STRING(pev->targetname)); + pev->effects |= EF_BRIGHTFIELD; + } + } + else + { + pev->flags &= ~FL_ONGROUND; + } + + if ( !FStringNull(pev->target) )// this monster has a target + { + StartPatrol(UTIL_FindEntityByTargetname( NULL, STRING( pev->target ))); + } + + //SetState ( m_IdealMonsterState ); + //SetActivity ( m_IdealActivity ); + + // Delay drop to floor to make sure each door in the level has had its chance to spawn + // Spread think times so that they don't all happen at the same time (Carmack) + SetThink(&CBaseMonster :: CallMonsterThink ); + AbsoluteNextThink( m_fNextThink + RANDOM_FLOAT(0.1, 0.4) ); // spread think times. + + if ( !FStringNull(pev->targetname) )// wait until triggered + { + SetState( MONSTERSTATE_IDLE ); + // UNDONE: Some scripted sequence monsters don't have an idle? + SetActivity( ACT_IDLE ); + ChangeSchedule( GetScheduleOfType( SCHED_WAIT_TRIGGER ) ); + } +} + +void CBaseMonster :: MovementComplete( void ) +{ + switch( m_iTaskStatus ) + { + case TASKSTATUS_NEW: + case TASKSTATUS_RUNNING: + m_iTaskStatus = TASKSTATUS_RUNNING_TASK; + break; + + case TASKSTATUS_RUNNING_MOVEMENT: + TaskComplete(); + break; + + case TASKSTATUS_RUNNING_TASK: + ALERT( at_error, "Movement completed twice!\n" ); + break; + + case TASKSTATUS_COMPLETE: + break; + } + m_movementGoal = MOVEGOAL_NONE; +} + + +int CBaseMonster::TaskIsRunning( void ) +{ + if ( m_iTaskStatus != TASKSTATUS_COMPLETE && + m_iTaskStatus != TASKSTATUS_RUNNING_MOVEMENT ) + return 1; + + return 0; +} + +//========================================================= +// IRelationship - returns an integer that describes the +// relationship between two types of monster. +//========================================================= +int CBaseMonster::IRelationship ( CBaseEntity *pTarget ) +{ + static int iEnemy[18][18] = + { // NONE MACH PLYR HPASS HMIL AMIL APASS AMONST APREY APRED INSECT PLRALY PBWPN ABWPN FACT_A FACT_B FACT_C TERR + /*NONE*/ { R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO}, + /*MACHINE*/ { R_NO, R_NO, R_DL, R_DL, R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL}, + /*PLAYER*/ { R_NO, R_DL, R_NO, R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_NO, R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL}, + /*HUMANPASSIVE*/{ R_NO, R_NO, R_AL, R_AL, R_HT, R_FR, R_NO, R_HT, R_DL, R_FR, R_NO, R_AL, R_NO, R_NO, R_DL, R_DL, R_DL, R_DL}, + /*HUMANMILITAR*/{ R_NO, R_NO, R_HT, R_DL, R_NO, R_HT, R_DL, R_DL, R_DL, R_DL, R_NO, R_HT, R_NO, R_NO, R_DL, R_DL, R_DL, R_DL}, + /*ALIENMILITAR*/{ R_NO, R_DL, R_HT, R_DL, R_HT, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_DL, R_NO, R_NO, R_DL, R_DL, R_DL, R_DL}, + /*ALIENPASSIVE*/{ R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_DL, R_DL, R_DL, R_DL}, + /*ALIENMONSTER*/{ R_NO, R_DL, R_DL, R_DL, R_DL, R_NO, R_NO, R_NO, R_NO, R_NO, R_NO, R_DL, R_NO, R_NO, R_DL, R_DL, R_DL, R_DL}, + /*ALIENPREY */{ R_NO, R_NO, R_DL, R_DL, R_DL, R_NO, R_NO, R_NO, R_NO, R_FR, R_NO, R_DL, R_NO, R_NO, R_DL, R_DL, R_DL, R_DL}, + /*ALIENPREDATO*/{ R_NO, R_NO, R_DL, R_DL, R_DL, R_NO, R_NO, R_NO, R_HT, R_DL, R_NO, R_DL, R_NO, R_NO, R_DL, R_DL, R_DL, R_DL}, + /*INSECT*/ { R_FR, R_FR, R_FR, R_FR, R_FR, R_NO, R_FR, R_FR, R_FR, R_FR, R_NO, R_FR, R_NO, R_NO, R_FR, R_FR, R_FR, R_DL}, + /*PLAYERALLY*/ { R_NO, R_DL, R_AL, R_AL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_NO, R_NO, R_NO, R_NO, R_DL, R_DL, R_DL, R_DL}, + /*PBIOWEAPON*/ { R_NO, R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_NO, R_DL, R_NO, R_DL, R_DL, R_DL, R_DL, R_DL}, + /*ABIOWEAPON*/ { R_NO, R_NO, R_DL, R_DL, R_DL, R_AL, R_NO, R_DL, R_DL, R_NO, R_NO, R_DL, R_DL, R_NO, R_DL, R_DL, R_DL, R_DL}, + /*FACTION_A*/ { R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_NO, R_DL, R_DL, R_DL, R_AL, R_DL, R_DL, R_DL}, + /*FACTION_B*/ { R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_NO, R_DL, R_DL, R_DL, R_DL, R_AL, R_DL, R_DL}, + /*FACTION_C*/ { R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_AL, R_DL}, + /*TERRORISTS*/ { R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_AL} // buz + }; + + int iTargClass = pTarget->Classify(); + + if (iTargClass == CLASS_PLAYER && m_iPlayerReact) //LRC + { + if (m_iPlayerReact == 1) // Ignore player + return R_NO; + else if (m_iPlayerReact == 4) + return R_HT; + else if (m_afMemory & bits_MEMORY_PROVOKED) + return R_HT; + else + return R_NO; + } + + return iEnemy[ Classify() ][ iTargClass ]; +} + +//========================================================= +// FindCover - tries to find a nearby node that will hide +// the caller from its enemy. +// +// If supplied, search will return a node at least as far +// away as MinDist, but no farther than MaxDist. +// if MaxDist isn't supplied, it defaults to a reasonable +// value +//========================================================= +// UNDONE: Should this find the nearest node? + +//float CGraph::PathLength( int iStart, int iDest, int iHull, int afCapMask ) + +BOOL CBaseMonster :: FindCover ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ) +{ + int i; + int iMyHullIndex; + int iMyNode; + int iThreatNode; + float flDist; + Vector vecLookersOffset; + TraceResult tr; + + if ( !flMaxDist ) + { + // user didn't supply a MaxDist, so work up a crazy one. + flMaxDist = 784; + } + + if ( flMinDist > 0.5 * flMaxDist) + { +#if _DEBUG + ALERT ( at_debug, "FindCover MinDist (%.0f) too close to MaxDist (%.0f)\n", flMinDist, flMaxDist ); +#endif + flMinDist = 0.5 * flMaxDist; + } + + if ( !WorldGraph.m_fGraphPresent || !WorldGraph.m_fGraphPointersSet ) + { + ALERT ( at_aiconsole, "Graph not ready for findcover!\n" ); + return FALSE; + } + + iMyNode = WorldGraph.FindNearestNode( pev->origin, this ); + iThreatNode = WorldGraph.FindNearestNode ( vecThreat, this ); + iMyHullIndex = WorldGraph.HullIndex( this ); + + if ( iMyNode == NO_NODE ) + { + ALERT ( at_aiconsole, "FindCover() - %s has no nearest node!\n", STRING(pev->classname)); + return FALSE; + } + if ( iThreatNode == NO_NODE ) + { + // ALERT ( at_aiconsole, "FindCover() - Threat has no nearest node!\n" ); + iThreatNode = iMyNode; + // return FALSE; + } + + vecLookersOffset = vecThreat + vecViewOffset;// calculate location of enemy's eyes + + // we'll do a rough sample to find nodes that are relatively nearby + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + int nodeNumber = (i + WorldGraph.m_iLastCoverSearch) % WorldGraph.m_cNodes; + + CNode &node = WorldGraph.Node( nodeNumber ); + WorldGraph.m_iLastCoverSearch = nodeNumber + 1; // next monster that searches for cover node will start where we left off here. + + // could use an optimization here!! + flDist = ( pev->origin - node.m_vecOrigin ).Length(); + + // DON'T do the trace check on a node that is farther away than a node that we've already found to + // provide cover! Also make sure the node is within the mins/maxs of the search. + if ( flDist >= flMinDist && flDist < flMaxDist ) + { + UTIL_TraceLine ( node.m_vecOrigin + vecViewOffset, vecLookersOffset, ignore_monsters, ignore_glass, ENT(pev), &tr ); + + // if this node will block the threat's line of sight to me... + if ( tr.flFraction != 1.0 ) + { + // ..and is also closer to me than the threat, or the same distance from myself and the threat the node is good. + if ( ( iMyNode == iThreatNode ) || WorldGraph.PathLength( iMyNode, nodeNumber, iMyHullIndex, m_afCapability ) <= WorldGraph.PathLength( iThreatNode, nodeNumber, iMyHullIndex, m_afCapability ) ) + { + if ( FValidateCover ( node.m_vecOrigin ) && MoveToLocation( ACT_RUN, 0, node.m_vecOrigin ) ) + { + /* + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( node.m_vecOrigin.x ); + WRITE_COORD( node.m_vecOrigin.y ); + WRITE_COORD( node.m_vecOrigin.z ); + + WRITE_COORD( vecLookersOffset.x ); + WRITE_COORD( vecLookersOffset.y ); + WRITE_COORD( vecLookersOffset.z ); + MESSAGE_END(); + */ + + return TRUE; + } + } + } + } + } + return FALSE; +} + + +//========================================================= +// BuildNearestRoute - tries to build a route as close to the target +// as possible, even if there isn't a path to the final point. +// +// If supplied, search will return a node at least as far +// away as MinDist from vecThreat, but no farther than MaxDist. +// if MaxDist isn't supplied, it defaults to a reasonable +// value +//========================================================= +BOOL CBaseMonster :: BuildNearestRoute ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ) +{ + int i; + int iMyHullIndex; + int iMyNode; + float flDist; + Vector vecLookersOffset; + TraceResult tr; + + if ( !flMaxDist ) + { + // user didn't supply a MaxDist, so work up a crazy one. + flMaxDist = 784; + } + + if ( flMinDist > 0.5 * flMaxDist) + { +#if _DEBUG + ALERT ( at_debug, "FindCover MinDist (%.0f) too close to MaxDist (%.0f)\n", flMinDist, flMaxDist ); +#endif + flMinDist = 0.5 * flMaxDist; + } + + if ( !WorldGraph.m_fGraphPresent || !WorldGraph.m_fGraphPointersSet ) + { + ALERT ( at_aiconsole, "Graph not ready for BuildNearestRoute!\n" ); + return FALSE; + } + + iMyNode = WorldGraph.FindNearestNode( pev->origin, this ); + iMyHullIndex = WorldGraph.HullIndex( this ); + + if ( iMyNode == NO_NODE ) + { + ALERT ( at_aiconsole, "BuildNearestRoute() - %s has no nearest node!\n", STRING(pev->classname)); + return FALSE; + } + + vecLookersOffset = vecThreat + vecViewOffset;// calculate location of enemy's eyes + + // we'll do a rough sample to find nodes that are relatively nearby + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + int nodeNumber = (i + WorldGraph.m_iLastCoverSearch) % WorldGraph.m_cNodes; + + CNode &node = WorldGraph.Node( nodeNumber ); + WorldGraph.m_iLastCoverSearch = nodeNumber + 1; // next monster that searches for cover node will start where we left off here. + + // can I get there? + if (WorldGraph.NextNodeInRoute( iMyNode, nodeNumber, iMyHullIndex, 0 ) != iMyNode) + { + flDist = ( vecThreat - node.m_vecOrigin ).Length(); + + // is it close? + if ( flDist > flMinDist && flDist < flMaxDist) + { + // can I see where I want to be from there? + UTIL_TraceLine( node.m_vecOrigin + pev->view_ofs, vecLookersOffset, ignore_monsters, edict(), &tr ); + + if (tr.flFraction == 1.0) + { + // try to actually get there + if ( BuildRoute ( node.m_vecOrigin, bits_MF_TO_LOCATION, NULL ) ) + { + flMaxDist = flDist; + m_vecMoveGoal = node.m_vecOrigin; + return TRUE; // UNDONE: keep looking for something closer! + } + } + } + } + } + + return FALSE; +} + + + +//========================================================= +// BestVisibleEnemy - this functions searches the link +// list whose head is the caller's m_pLink field, and returns +// a pointer to the enemy entity in that list that is nearest the +// caller. +// +// !!!UNDONE - currently, this only returns the closest enemy. +// we'll want to consider distance, relationship, attack types, back turned, etc. +//========================================================= +CBaseEntity *CBaseMonster :: BestVisibleEnemy ( void ) +{ + CBaseEntity *pReturn; + CBaseEntity *pNextEnt; + int iNearest; + int iDist; + int iBestRelationship; + + iNearest = 8192;// so first visible entity will become the closest. + pNextEnt = m_pLink; + pReturn = NULL; + iBestRelationship = R_NO; + + while ( pNextEnt != NULL ) + { + if ( pNextEnt->IsAlive() ) + { + if ( IRelationship( pNextEnt) > iBestRelationship ) + { + // this entity is disliked MORE than the entity that we + // currently think is the best visible enemy. No need to do + // a distance check, just get mad at this one for now. + iBestRelationship = IRelationship ( pNextEnt ); + iNearest = ( pNextEnt->pev->origin - pev->origin ).Length(); + pReturn = pNextEnt; + } + else if ( IRelationship( pNextEnt) == iBestRelationship ) + { + // this entity is disliked just as much as the entity that + // we currently think is the best visible enemy, so we only + // get mad at it if it is closer. + iDist = ( pNextEnt->pev->origin - pev->origin ).Length(); + + if ( iDist <= iNearest ) + { + iNearest = iDist; + iBestRelationship = IRelationship ( pNextEnt ); + pReturn = pNextEnt; + } + } + } + + pNextEnt = pNextEnt->m_pLink; + } + + return pReturn; +} + +//----------------------------------------------------------------------------- +// Purpose: Keep track of multiple objects that the npc is interested in facing +//----------------------------------------------------------------------------- +void CBaseMonster :: AddFacingTarget( CBaseEntity *pTarget, float flImportance, float flDuration, float flRamp ) +{ + m_facingQueue.Add( pTarget, flImportance, flDuration, flRamp ); +} + +void CBaseMonster :: AddFacingTarget( const Vector &vecPosition, float flImportance, float flDuration, float flRamp ) +{ + m_facingQueue.Add( vecPosition, flImportance, flDuration, flRamp ); +} + +void CBaseMonster :: AddFacingTarget( CBaseEntity *pTarget, const Vector &vecPosition, float flImportance, float flDuration, float flRamp ) +{ + m_facingQueue.Add( pTarget, vecPosition, flImportance, flDuration, flRamp ); +} + +float CBaseMonster :: GetFacingDirection( Vector &vecDir ) +{ + float flTotalInterest = 0.0; + vecDir = g_vecZero; + + int i; + + // clean up facing targets + for( i = 0; i < m_facingQueue.Count(); ) + { + if( !m_facingQueue[i].IsActive( )) + { + m_facingQueue.Remove( i ); + } + else + { + i++; + } + } + + for( i = 0; i < m_facingQueue.Count(); i++ ) + { + float flInterest = m_facingQueue[i].Interest(); + Vector tmp = (m_facingQueue[i].GetPosition() - pev->origin).Normalize(); + + vecDir = vecDir * (1.0f - flInterest) + tmp * flInterest; + + flTotalInterest = (1.0f - (1.0f - flTotalInterest) * (1.0f - flInterest)); + + vecDir = vecDir.Normalize(); + } + + return flTotalInterest; +} + +//========================================================= +// CalcIdealYaw - gets a yaw value for the caller that would +// face the supplied vector. Value is stuffed into the npc's +// ideal_yaw +//========================================================= +float CBaseMonster :: CalcIdealYaw( const Vector &vecTarget ) +{ + Vector vecProjection = g_vecZero; + + // strafing npc needs to face 90 degrees away from its goal + if ( m_movementActivity == ACT_STRAFE_LEFT ) + { + vecProjection.x = -vecTarget.y; + vecProjection.y = vecTarget.x; + } + else if ( m_movementActivity == ACT_STRAFE_RIGHT ) + { + vecProjection.x = vecTarget.y; + vecProjection.y = vecTarget.x; + + return UTIL_VecToYaw( vecProjection - pev->origin ); + } + else + { + vecProjection = vecTarget; + } + + m_vecDirection = (vecProjection - pev->origin); + m_vecFacingDir = m_vecDirection; + + return UTIL_VecToYaw( m_vecDirection ); +} + +//========================================================= +// DeltaIdealYaw - returns the difference ( in degrees ) between +// monster's current yaw and ideal_yaw +// +// Positive result is left turn, negative is right turn +//========================================================= +float CBaseMonster :: DeltaIdealYaw( void ) +{ + float flCurrentYaw = UTIL_AngleMod( pev->angles.y ); + + if( flCurrentYaw == GetIdealYaw() ) + { + return 0.0f; + } + + return UTIL_AngleDiff( GetIdealYaw(), flCurrentYaw ); +} + +//========================================================= +// ClampYaw - turns a monster towards its ideal_yaw +//========================================================= +float CBaseMonster :: ClampYaw( float yawSpeedPerSec, float current, float target, float time ) +{ + if( current != target ) + { + float speed = yawSpeedPerSec * time; + float move = target - current; + + if( target > current ) + { + if( move >= 180 ) + move = move - 360; + } + else + { + if( move <= -180 ) + move = move + 360; + } + + if( move > 0 ) + { + // turning to the monster's left + if( move > speed ) + move = speed; + } + else + { + // turning to the monster's right + if( move < -speed ) + move = -speed; + } + + return UTIL_AngleMod( current + move ); + } + + return target; +} + +void CBaseMonster :: UpdateYaw( int yawSpeed ) +{ + float ideal, current, newYaw; + + if( yawSpeed = -1 ) + yawSpeed = pev->yaw_speed; + + // NOTE: GetIdealYaw() will never exactly be reached because UTIL_AngleMod + // also truncates the angle to 16 bits of resolution. So lets truncate it here. + current = UTIL_AngleMod( pev->angles.y ); + ideal = UTIL_AngleMod( pev->ideal_yaw ); + + newYaw = ClampYaw( (float)yawSpeed * 10.0, current, ideal, gpGlobals->frametime ); + + if( newYaw != current ) + { + pev->angles.y = newYaw; + + // turn head in desired direction only if they have a turnable head + if( m_afCapability & bits_CAP_TURN_HEAD ) + { + float yaw = pev->ideal_yaw - pev->angles.y; + if( yaw > 180 ) yaw -= 360; + if( yaw < -180 ) yaw += 360; + // yaw *= 0.8; + SetBoneController( 0, yaw ); + } + } +} + +float CBaseMonster :: MaxYawSpeed( void ) +{ + switch( m_Activity ) + { + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + return 60; + break; + case ACT_RUN: + case ACT_RUN_HURT: + case ACT_WALK_HURT: + return 15; + break; + case ACT_WALK: + return 15; + break; + default: + if( IsMoving( )) + { + return 15; + } + return 45; // too fast? + break; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Returns yaw speed based on what they're doing. +//----------------------------------------------------------------------------- +float CBaseMonster :: CalcYawSpeed( void ) +{ + float maxYaw = MaxYawSpeed(); + + if( m_movementGoal != MOVEGOAL_NONE && m_flGroundSpeed != 0.0 ) + { + // --------------------------------------------------- + // If not moving to a waypoint use a base turing speed + // --------------------------------------------------- + if( FRouteClear( )) + { + return maxYaw; + } + // -------------------------------------------------------------- + // If moving towards a waypoint, set the turn speed based on the + // distance of the waypoint and my forward velocity + // -------------------------------------------------------------- + if( m_flGroundSpeed > 0 ) + { + // ----------------------------------------------------------------- + // Get the projection of npc's heading direction on the waypoint dir + // ----------------------------------------------------------------- + float waypointDist = (m_Route[m_iRouteIndex].vecLocation - pev->origin).Length(); + + // If waypoint is close, aim for the waypoint + if (waypointDist < 100) + { + float scale = 1.0f + (0.01f * (100.0f - waypointDist)); + return (maxYaw * scale); + } + } + } + + return maxYaw; +} + +void CBaseMonster :: RecalculateYawSpeed( void ) +{ + SetYawSpeed( CalcYawSpeed() ); +} + +void CBaseMonster :: SetIdealYawToTarget( const Vector &target ) +{ + SetIdealYaw( CalcIdealYaw( target )); +} + +void CBaseMonster :: SetIdealYawToTargetAndUpdate( const Vector &target, float yawSpeed ) +{ + SetIdealYawAndUpdate( CalcIdealYaw( target ), yawSpeed ); +} + +void CBaseMonster :: SetIdealYawAndUpdate( float idealYaw, float yawSpeed ) +{ + SetIdealYaw( idealYaw ); + if( yawSpeed == AI_CALC_YAW_SPEED ) + RecalculateYawSpeed(); + else if( yawSpeed != AI_KEEP_YAW_SPEED ) + SetYawSpeed( yawSpeed ); + UpdateYaw(); +} + +void CBaseMonster :: CalcFacing( float flInterval ) +{ + float currentYaw = UTIL_AngleMod( pev->angles.y ); + float goalYaw = UTIL_VecToYaw( m_vecFacingDir ); + float deltaYaw = fabs( UTIL_AngleDiff( goalYaw, currentYaw ) ); + + if( deltaYaw > 15 ) + { + float speed = deltaYaw * 4.0f; // i.e., any maneuver takes a quarter a second + float clampedYaw = ClampYaw( speed, currentYaw, goalYaw, flInterval ); + + if( clampedYaw != goalYaw ) + { + m_vecFacingDir = UTIL_YawToVector( clampedYaw ); + } + } +} + +void CBaseMonster :: MoveFacing( void ) +{ + // required movement direction + float flMoveYaw = UTIL_VecToYaw( m_vecDirection ); + + float fSequenceMoveYaw = GetSequenceMoveYaw( pev->sequence ); + if( fSequenceMoveYaw == NOMOTION ) + fSequenceMoveYaw = 0; + + if( !HasPoseParameter( pev->sequence, "move_yaw" )) + { + SetIdealYawAndUpdate( UTIL_AngleMod( flMoveYaw - fSequenceMoveYaw )); + } + else + { + Vector dir; + float flInfluence = GetFacingDirection( dir ); + dir = m_vecFacingDir * (1.0f - flInfluence) + dir * flInfluence; + dir = dir.Normalize(); + + // ideal facing direction + float idealYaw = UTIL_AngleMod( UTIL_VecToYaw( dir )); + + // FIXME: facing has important max velocity issues + SetIdealYawAndUpdate( idealYaw ); + + // find movement direction to compensate for not being turned far enough + float flDiff = UTIL_AngleDiff( flMoveYaw, pev->angles.y ); + + // HACKHACK: temporary fixup negative oscillations until this will do properly + if( pev->ideal_yaw == pev->angles.y ) + flDiff = 0.0f; +#ifdef _DEBUG + Msg( "ideal_yaw %g angles.y: %g, move_yaw %g\n", GetIdealYaw(), pev->angles.y, flDiff ); +#endif + SetPoseParameter( "move_yaw", flDiff ); + } +} + +//========================================================= +// Changeyaw - turns a monster towards its ideal_yaw +//========================================================= +float CBaseMonster::ChangeYaw ( int yawSpeed ) +{ +#if 0 + float ideal, current, move, speed; + + current = UTIL_AngleMod( pev->angles.y ); + ideal = pev->ideal_yaw; + if (current != ideal) + { + speed = (float)yawSpeed * gpGlobals->frametime * 10.0f; + move = ideal - current; + + if (ideal > current) + { + if (move >= 180) + move = move - 360; + } + else + { + if (move <= -180) + move = move + 360; + } + + if (move > 0) + {// turning to the monster's left + if (move > speed) + move = speed; + } + else + {// turning to the monster's right + if (move < -speed) + move = -speed; + } + + pev->angles.y = UTIL_AngleMod (current + move); + + // turn head in desired direction only if they have a turnable head + if (m_afCapability & bits_CAP_TURN_HEAD) + { + float yaw = pev->ideal_yaw - pev->angles.y; + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + // yaw *= 0.8; + SetBoneController( 0, yaw ); + } + } + else + move = 0; + + return move; +#endif + return 0.0f; +} + +//========================================================= +// VecToYaw - turns a directional vector into a yaw value +// that points down that vector. +//========================================================= +float CBaseMonster :: VecToYaw( Vector vecDir ) +{ + if (vecDir.x == 0 && vecDir.y == 0 && vecDir.z == 0) + return pev->angles.y; + + return UTIL_VecToYaw( vecDir ); +} + +//========================================================= +// SetEyePosition +// +// queries the monster's model for $eyeposition and copies +// that vector to the monster's view_ofs +// +//========================================================= +void CBaseMonster :: SetEyePosition ( void ) +{ + Vector vecEyePosition; + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + GetEyePosition( pmodel, vecEyePosition ); + + pev->view_ofs = vecEyePosition; + + if ( pev->view_ofs == g_vecZero ) + { + ALERT ( at_aiconsole, "%s has no view_ofs!\n", STRING ( pev->classname ) ); + } +} + +//========================================================= +// StepSound +// +// Play step sounds with material type +//========================================================= + +void CBaseMonster :: StepSound( void ) +{ + char *rgsz[MAX_MAT_SOUNDS]; + msurface_t *surf = NULL; + matdef_t *pMaterial = NULL; + TraceResult ptr; + int fWalking; + int cnt = 0; + + UTIL_TraceLine( pev->origin + Vector( 0, 0, 8 ), pev->origin - Vector( 0, 0, 16 ), ignore_monsters, ENT( pev ), &ptr ); + CBaseEntity *pEntity = CBaseEntity::Instance(ptr.pHit); + memset( rgsz, 0, sizeof( rgsz )); + + if( !pEntity ) return; + + if( pEntity->pev->solid == SOLID_BSP || pEntity->pev->movetype == MOVETYPE_PUSHSTEP ) + { + surf = TRACE_SURFACE( ENT( pEntity->pev ), pev->origin + Vector( 0, 0, 8 ), pev->origin - Vector( 0, 0, 16 )); + pMaterial = COM_MatDefFromSurface( surf, ptr.vecEndPos ); + } + else if( pEntity->pev->solid == SOLID_CUSTOM ) + { + if( ptr.pMat ) pMaterial = ptr.pMat->effects; + } + + if( pMaterial ) + { + // fill sound array + for( cnt = 0; pMaterial->step_sounds[cnt] != NULL; cnt++ ) + rgsz[cnt] = (char *)pMaterial->step_sounds[cnt]; + } + + if ( m_Activity == ACT_WALK ) + fWalking = true; + else fWalking = false; + + float fvol = fWalking ? 0.2f : 0.5f; + + if( pev->waterlevel == 1 ) + { + // wading in the water + rgsz[0] = "player/pl_wade1.wav"; + rgsz[1] = "player/pl_wade3.wav"; + rgsz[2] = "player/pl_wade2.wav"; + rgsz[3] = "player/pl_wade4.wav"; + cnt = 4; + } + else if( cnt == 0 ) + { + // defaulting to NPC sound + rgsz[0] = "common/npc_step1.wav"; + rgsz[1] = "common/npc_step2.wav"; + rgsz[2] = "common/npc_step3.wav"; + rgsz[3] = "common/npc_step4.wav"; + cnt = 4; + } + + EMIT_SOUND_DYN( ENT( pev ), CHAN_BODY, rgsz[RANDOM_LONG(0, cnt - 1)], fvol, ATTN_NORM, 0, 96 + RANDOM_LONG( 0, 0xf )); +} + +void CBaseMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case SCRIPT_EVENT_DEAD: + if ( m_MonsterState == MONSTERSTATE_SCRIPT ) + { + pev->deadflag = DEAD_DYING; + // Kill me now! (and fade out when CineCleanup() is called) +#if _DEBUG + ALERT( at_aiconsole, "Death event: %s\n", STRING(pev->classname) ); +#endif + pev->health = 0; + } +#if _DEBUG + else + ALERT( at_aiconsole, "INVALID death event:%s\n", STRING(pev->classname) ); +#endif + break; + case SCRIPT_EVENT_NOT_DEAD: + if ( m_MonsterState == MONSTERSTATE_SCRIPT ) + { + pev->deadflag = DEAD_NO; + // This is for life/death sequences where the player can determine whether a character is dead or alive after the script + pev->health = pev->max_health; + } + break; + + case SCRIPT_EVENT_SOUND: // Play a named wave file + if ( !(pev->spawnflags & SF_MONSTER_GAG) || m_MonsterState != MONSTERSTATE_IDLE) + { + if( !strnicmp( pEvent->options, "common/npc_step", 15 )) StepSound(); + else EMIT_SOUND( edict(), CHAN_BODY, pEvent->options, 1.0, ATTN_IDLE ); + } + break; + + case SCRIPT_EVENT_SOUND_VOICE: + if ( !(pev->spawnflags & SF_MONSTER_GAG) || m_MonsterState != MONSTERSTATE_IDLE) + EMIT_SOUND( edict(), CHAN_VOICE, pEvent->options, 1.0, ATTN_IDLE ); + break; + + case SCRIPT_EVENT_SENTENCE_RND1: // Play a named sentence group 33% of the time + if (RANDOM_LONG(0,2) == 0) + break; + // fall through... + case SCRIPT_EVENT_SENTENCE: // Play a named sentence group + SENTENCEG_PlayRndSz( edict(), pEvent->options, 1.0, ATTN_IDLE, 0, 100 ); + break; + + case SCRIPT_EVENT_FIREEVENT: // Fire a trigger + FireTargets( pEvent->options, this, this, USE_TOGGLE, 0 ); + break; + + case SCRIPT_EVENT_NOINTERRUPT: // Can't be interrupted from now on + if ( m_pCine ) + m_pCine->AllowInterrupt( FALSE ); + break; + + case SCRIPT_EVENT_CANINTERRUPT: // OK to interrupt now + if ( m_pCine ) + m_pCine->AllowInterrupt( TRUE ); + break; + +#if 0 + case SCRIPT_EVENT_INAIR: // Don't DROP_TO_FLOOR() + case SCRIPT_EVENT_ENDANIMATION: // Set ending animation sequence to + break; +#endif + + case MONSTER_EVENT_BODYDROP_HEAVY: + if ( pev->flags & FL_ONGROUND ) + { + if ( RANDOM_LONG( 0, 1 ) == 0 ) + { + EMIT_SOUND_DYN( ENT(pev), CHAN_BODY, "common/bodydrop3.wav", 1, ATTN_NORM, 0, 90 ); + } + else + { + EMIT_SOUND_DYN( ENT(pev), CHAN_BODY, "common/bodydrop4.wav", 1, ATTN_NORM, 0, 90 ); + } + } + break; + + case MONSTER_EVENT_BODYDROP_LIGHT: + if ( pev->flags & FL_ONGROUND ) + { + if ( RANDOM_LONG( 0, 1 ) == 0 ) + { + EMIT_SOUND( ENT(pev), CHAN_BODY, "common/bodydrop3.wav", 1, ATTN_NORM ); + } + else + { + EMIT_SOUND( ENT(pev), CHAN_BODY, "common/bodydrop4.wav", 1, ATTN_NORM ); + } + } + break; + + case MONSTER_EVENT_SWISHSOUND: + { + // NO MONSTER may use this anim event unless that monster's precache precaches this sound!!! + EMIT_SOUND( ENT(pev), CHAN_BODY, "zombie/claw_miss2.wav", 1, ATTN_NORM ); + break; + } + + default: + ALERT( at_aiconsole, "Unhandled animation event %d for %s\n", pEvent->event, STRING(pev->classname) ); + break; + + } +} + + +// Combat + +Vector CBaseMonster :: GetGunPosition( ) +{ + UTIL_MakeVectors(pev->angles); + + // Vector vecSrc = pev->origin + gpGlobals->v_forward * 10; + //vecSrc.z = pevShooter->absmin.z + pevShooter->size.z * 0.7; + //vecSrc.z = pev->origin.z + (pev->view_ofs.z - 4); + Vector vecSrc = pev->origin + + gpGlobals->v_forward * m_HackedGunPos.y + + gpGlobals->v_right * m_HackedGunPos.x + + gpGlobals->v_up * m_HackedGunPos.z; + + return vecSrc; +} + + + + + +//========================================================= +// NODE GRAPH +//========================================================= + + + + + +//========================================================= +// FGetNodeRoute - tries to build an entire node path from +// the callers origin to the passed vector. If this is +// possible, ROUTE_SIZE waypoints will be copied into the +// callers m_Route. TRUE is returned if the operation +// succeeds (path is valid) or FALSE if failed (no path +// exists ) +//========================================================= +BOOL CBaseMonster :: FGetNodeRoute ( Vector vecDest ) +{ + int iPath[ MAX_PATH_SIZE ]; + int iSrcNode, iDestNode; + int iResult; + int i; + int iNumToCopy; + + iSrcNode = WorldGraph.FindNearestNode ( pev->origin, this ); + iDestNode = WorldGraph.FindNearestNode ( vecDest, this ); + + if ( iSrcNode == -1 ) + { + // no node nearest self +// ALERT ( at_aiconsole, "FGetNodeRoute: No valid node near self!\n" ); + return FALSE; + } + else if ( iDestNode == -1 ) + { + // no node nearest target +// ALERT ( at_aiconsole, "FGetNodeRoute: No valid node near target!\n" ); + return FALSE; + } + + // valid src and dest nodes were found, so it's safe to proceed with + // find shortest path + int iNodeHull = WorldGraph.HullIndex( this ); // make this a monster virtual function + iResult = WorldGraph.FindShortestPath ( iPath, iSrcNode, iDestNode, iNodeHull, m_afCapability ); + + if ( !iResult ) + { +#if 1 + ALERT ( at_aiconsole, "No Path from %d to %d!\n", iSrcNode, iDestNode ); + return FALSE; +#else + BOOL bRoutingSave = WorldGraph.m_fRoutingComplete; + WorldGraph.m_fRoutingComplete = FALSE; + iResult = WorldGraph.FindShortestPath(iPath, iSrcNode, iDestNode, iNodeHull, m_afCapability); + WorldGraph.m_fRoutingComplete = bRoutingSave; + if ( !iResult ) + { + ALERT ( at_aiconsole, "No Path from %d to %d!\n", iSrcNode, iDestNode ); + return FALSE; + } + else + { + ALERT ( at_aiconsole, "Routing is inconsistent!" ); + } +#endif + } + + // there's a valid path within iPath now, so now we will fill the route array + // up with as many of the waypoints as it will hold. + + // don't copy ROUTE_SIZE entries if the path returned is shorter + // than ROUTE_SIZE!!! + if ( iResult < ROUTE_SIZE ) + { + iNumToCopy = iResult; + } + else + { + iNumToCopy = ROUTE_SIZE; + } + + for ( i = 0 ; i < iNumToCopy; i++ ) + { + m_Route[ i ].vecLocation = WorldGraph.m_pNodes[ iPath[ i ] ].m_vecOrigin; + m_Route[ i ].iType = bits_MF_TO_NODE; + } + + if ( iNumToCopy < ROUTE_SIZE ) + { + m_Route[ iNumToCopy ].vecLocation = vecDest; + m_Route[ iNumToCopy ].iType |= bits_MF_IS_GOAL; + } + + return TRUE; +} + +//========================================================= +// FindHintNode +//========================================================= +int CBaseMonster :: FindHintNode ( void ) +{ + int i; + TraceResult tr; + + if ( !WorldGraph.m_fGraphPresent ) + { + ALERT ( at_aiconsole, "find_hintnode: graph not ready!\n" ); + return NO_NODE; + } + + if ( WorldGraph.m_iLastActiveIdleSearch >= WorldGraph.m_cNodes ) + { + WorldGraph.m_iLastActiveIdleSearch = 0; + } + + for ( i = 0; i < WorldGraph.m_cNodes ; i++ ) + { + int nodeNumber = (i + WorldGraph.m_iLastActiveIdleSearch) % WorldGraph.m_cNodes; + CNode &node = WorldGraph.Node( nodeNumber ); + + if ( node.m_sHintType ) + { + // this node has a hint. Take it if it is visible, the monster likes it, and the monster has an animation to match the hint's activity. + if ( FValidateHintType ( node.m_sHintType ) ) + { + if ( !node.m_sHintActivity || LookupActivity ( node.m_sHintActivity ) != ACTIVITY_NOT_AVAILABLE ) + { + UTIL_TraceLine ( pev->origin + pev->view_ofs, node.m_vecOrigin + pev->view_ofs, ignore_monsters, ENT(pev), &tr ); + + if ( tr.flFraction == 1.0 ) + { + WorldGraph.m_iLastActiveIdleSearch = nodeNumber + 1; // next monster that searches for hint nodes will start where we left off. + return nodeNumber;// take it! + } + } + } + } + } + + WorldGraph.m_iLastActiveIdleSearch = 0;// start at the top of the list for the next search. + + return NO_NODE; +} + + +void CBaseMonster::ReportAIState( void ) +{ + ALERT_TYPE level = at_console; + + static const char *pStateNames[] = { "None", "Idle", "Combat", "Alert", "Hunt", "Prone", "Scripted", "Dead" }; + + ALERT( level, "%s: ", STRING(pev->classname) ); + if ( (int)m_MonsterState < ARRAYSIZE(pStateNames) ) + ALERT( level, "State: %s, ", pStateNames[m_MonsterState] ); + int i = 0; + while ( activity_map[i].type != 0 ) + { + if ( activity_map[i].type == (int)m_Activity ) + { + ALERT( level, "Activity %s, ", activity_map[i].name ); + break; + } + i++; + } + + if ( m_pSchedule ) + { + const char *pName = NULL; + pName = m_pSchedule->pName; + if ( !pName ) + pName = "Unknown"; + ALERT( level, "Schedule %s, ", pName ); + Task_t *pTask = GetTask(); + if ( pTask ) + ALERT( level, "Task %d (#%d), ", pTask->iTask, m_iScheduleIndex ); + } + else + ALERT( level, "No Schedule, " ); + + if ( m_hEnemy != NULL ) + ALERT( level, "\nEnemy is %s", STRING(m_hEnemy->pev->classname) ); + else + ALERT( level, "No enemy" ); + + if ( IsMoving() ) + { + ALERT( level, " Moving " ); + if ( m_flMoveWaitFinished > gpGlobals->time ) + ALERT( level, ": Stopped for %.2f. ", m_flMoveWaitFinished - gpGlobals->time ); + else if ( m_IdealActivity == GetStoppedActivity() ) + ALERT( level, ": In stopped anim. " ); + } + + CSquadMonster *pSquadMonster = MySquadMonsterPointer(); + + if ( pSquadMonster ) + { + if ( !pSquadMonster->InSquad() ) + { + ALERT ( level, "not " ); + } + + ALERT ( level, "In Squad, " ); + + if ( !pSquadMonster->IsLeader() ) + { + ALERT ( level, "not " ); + } + + ALERT ( level, "Leader." ); + } + + ALERT( level, "\n" ); + ALERT( level, "Yaw speed:%3.1f,Health: %3.1f\n", pev->yaw_speed, pev->health ); + if ( pev->spawnflags & SF_MONSTER_PRISONER ) + ALERT( level, " PRISONER! " ); + if ( pev->spawnflags & SF_MONSTER_PREDISASTER ) + ALERT( level, " Pre-Disaster! " ); + ALERT( level, "\n" ); +} + +//========================================================= +// KeyValue +// +// !!! netname entvar field is used in squadmonster for groupname!!! +//========================================================= +void CBaseMonster :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "TriggerTarget")) + { + m_iszTriggerTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "TriggerCondition") ) + { + m_iTriggerCondition = atoi( pkvd->szValue ); +// ALERT(at_console, "trigger cond set to %d\n", m_iTriggerCondition); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iClass") ) //LRC + { + m_iClass = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iPlayerReact") ) //LRC + { + m_iPlayerReact = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iStartAlert") ) //buz + { + m_iUseAlertAnims = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + { + CBaseToggle::KeyValue( pkvd ); + } +} + +//========================================================= +// FCheckAITrigger - checks the monster's AI Trigger Conditions, +// if there is a condition, then checks to see if condition is +// met. If yes, the monster's TriggerTarget is fired. +// +// Returns TRUE if the target is fired. +//========================================================= +BOOL CBaseMonster :: FCheckAITrigger ( void ) +{ + BOOL fFireTarget; + + if ( m_iTriggerCondition == AITRIGGER_NONE ) + { + // no conditions, so this trigger is never fired. + return FALSE; + } + + fFireTarget = FALSE; + + switch ( m_iTriggerCondition ) + { + case AITRIGGER_SEEPLAYER_ANGRY_AT_PLAYER: + if ( m_hEnemy != NULL && m_hEnemy->IsPlayer() && HasConditions ( bits_COND_SEE_ENEMY ) ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_SEEPLAYER_UNCONDITIONAL: + if ( HasConditions ( bits_COND_SEE_CLIENT ) ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_SEEPLAYER_NOT_IN_COMBAT: + if ( HasConditions ( bits_COND_SEE_CLIENT ) && + m_MonsterState != MONSTERSTATE_COMBAT && + m_MonsterState != MONSTERSTATE_PRONE && + m_MonsterState != MONSTERSTATE_SCRIPT) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_TAKEDAMAGE: + if ( m_afConditions & ( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_DEATH: + if ( pev->deadflag != DEAD_NO ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_HALFHEALTH: + if ( IsAlive() && pev->health <= ( pev->max_health / 2 ) ) + { + fFireTarget = TRUE; + } + break; +/* + + // !!!UNDONE - no persistant game state that allows us to track these two. + + case AITRIGGER_SQUADMEMBERDIE: + break; + case AITRIGGER_SQUADLEADERDIE: + break; +*/ + case AITRIGGER_HEARWORLD: + if ( m_afConditions & bits_COND_HEAR_SOUND && m_afSoundTypes & bits_SOUND_WORLD ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_HEARPLAYER: + if ( m_afConditions & bits_COND_HEAR_SOUND && m_afSoundTypes & bits_SOUND_PLAYER ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_HEARCOMBAT: + if ( m_afConditions & bits_COND_HEAR_SOUND && m_afSoundTypes & bits_SOUND_COMBAT ) + { + fFireTarget = TRUE; + } + break; + } + + if ( fFireTarget ) + { + // fire the target, then set the trigger conditions to NONE so we don't fire again + ALERT ( at_aiconsole, "AI Trigger Fire Target\n" ); + FireTargets( STRING( m_iszTriggerTarget ), this, this, USE_TOGGLE, 0 ); + m_iTriggerCondition = AITRIGGER_NONE; + return TRUE; + } + + return FALSE; +} + +//========================================================= +// CanPlaySequence - determines whether or not the monster +// can play the scripted sequence or AI sequence that is +// trying to possess it. If DisregardState is set, the monster +// will be sucked into the script no matter what state it is +// in. ONLY Scripted AI ents should allow this. +//========================================================= + +//LRC - to help debug when sequences won't play... +//#define DEBUG_CANTPLAY + +int CBaseMonster :: CanPlaySequence( int interruptFlags ) +{ + if ( m_pCine ) + { + if ( interruptFlags & SS_INTERRUPT_SCRIPTS ) + { + return true; + } + else + { +#ifdef DEBUG_CANTPLAY + ALERT(at_debug, "CANTPLAY: Already playing %s \"%s\"!\n", STRING(m_pCine->pev->classname), STRING(m_pCine->pev->targetname)); +#endif + return false; + } + } + else if ( !IsAlive() || m_MonsterState == MONSTERSTATE_PRONE ) + { +#ifdef DEBUG_CANTPLAY + ALERT(at_debug, "CANTPLAY: Dead/Barnacled!\n"); +#endif + // monster is already running a scripted sequence or dead! + return FALSE; + } + + if ( interruptFlags & SS_INTERRUPT_ANYSTATE ) + { + // ok to go, no matter what the monster state. (scripted AI) + return TRUE; + } + + if ( m_MonsterState == MONSTERSTATE_NONE || m_MonsterState == MONSTERSTATE_IDLE || m_IdealMonsterState == MONSTERSTATE_IDLE ) + { + // ok to go, but only in these states + return TRUE; + } + + if ( m_MonsterState == MONSTERSTATE_ALERT && interruptFlags & SS_INTERRUPT_ALERT ) + return TRUE; + + // unknown situation +#ifdef DEBUG_CANTPLAY + ALERT(at_debug, "CANTPLAY: non-interruptable state.\n"); +#endif + return FALSE; +} + + +//========================================================= +// FindLateralCover - attempts to locate a spot in the world +// directly to the left or right of the caller that will +// conceal them from view of pSightEnt +//========================================================= +#define COVER_CHECKS 5// how many checks are made +#define COVER_DELTA 48// distance between checks + +BOOL CBaseMonster :: FindLateralCover ( const Vector &vecThreat, const Vector &vecViewOffset ) +{ + TraceResult tr; + Vector vecBestOnLeft; + Vector vecBestOnRight; + Vector vecLeftTest; + Vector vecRightTest; + Vector vecStepRight; + int i; + + UTIL_MakeVectors ( pev->angles ); + vecStepRight = gpGlobals->v_right * COVER_DELTA; + vecStepRight.z = 0; + + vecLeftTest = vecRightTest = pev->origin; + + for ( i = 0 ; i < COVER_CHECKS ; i++ ) + { + vecLeftTest = vecLeftTest - vecStepRight; + vecRightTest = vecRightTest + vecStepRight; + + // it's faster to check the SightEnt's visibility to the potential spot than to check the local move, so we do that first. + UTIL_TraceLine( vecThreat + vecViewOffset, vecLeftTest + pev->view_ofs, ignore_monsters, ignore_glass, ENT(pev)/*pentIgnore*/, &tr); + + if (tr.flFraction != 1.0) + { + if ( FValidateCover ( vecLeftTest ) && CheckLocalMove( pev->origin, vecLeftTest, NULL, NULL ) == LOCALMOVE_VALID ) + { + if ( MoveToLocation( ACT_RUN, 0, vecLeftTest ) ) + { + return TRUE; + } + } + } + + // it's faster to check the SightEnt's visibility to the potential spot than to check the local move, so we do that first. + UTIL_TraceLine(vecThreat + vecViewOffset, vecRightTest + pev->view_ofs, ignore_monsters, ignore_glass, ENT(pev)/*pentIgnore*/, &tr); + + if ( tr.flFraction != 1.0 ) + { + if ( FValidateCover ( vecRightTest ) && CheckLocalMove( pev->origin, vecRightTest, NULL, NULL ) == LOCALMOVE_VALID ) + { + if ( MoveToLocation( ACT_RUN, 0, vecRightTest ) ) + { + return TRUE; + } + } + } + } + + return FALSE; +} + + +Vector CBaseMonster :: ShootAtEnemy( const Vector &shootOrigin ) +{ + if (m_pCine != NULL && m_hTargetEnt != NULL && (m_pCine->m_fTurnType == 1)) + { + Vector vecDest = ( m_hTargetEnt->pev->absmin + m_hTargetEnt->pev->absmax ) / 2; + return ( vecDest - shootOrigin ).Normalize(); + } + else if ( m_hEnemy ) + { + return ( (m_hEnemy->BodyTarget( shootOrigin ) - m_hEnemy->pev->origin) + m_vecEnemyLKP - shootOrigin ).Normalize(); + } + else return gpGlobals->v_forward; +} + + + +//========================================================= +// FacingIdeal - tells us if a monster is facing its ideal +// yaw. Created this function because many spots in the +// code were checking the yawdiff against this magic +// number. Nicer to have it in one place if we're gonna +// be stuck with it. +//========================================================= +BOOL CBaseMonster :: FacingIdeal( void ) +{ + if ( fabs( DeltaIdealYaw() ) <= 0.006 )//!!!BUGBUG - no magic numbers!!! + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// FCanActiveIdle +//========================================================= +BOOL CBaseMonster :: FCanActiveIdle ( void ) +{ + /* + if ( m_MonsterState == MONSTERSTATE_IDLE && m_IdealMonsterState == MONSTERSTATE_IDLE && !IsMoving() ) + { + return TRUE; + } + */ + return FALSE; +} + + +void CBaseMonster::PlaySentence( const char *pszSentence, float duration, float volume, float attenuation ) +{ + if ( pszSentence && IsAlive() ) + { + if ( pszSentence[0] == '!' ) + EMIT_SOUND_DYN( edict(), CHAN_VOICE, pszSentence, volume, attenuation, 0, PITCH_NORM ); + else + SENTENCEG_PlayRndSz( edict(), pszSentence, volume, attenuation, 0, PITCH_NORM ); + } +} + + +void CBaseMonster::PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ) +{ + PlaySentence( pszSentence, duration, volume, attenuation ); +} + + +void CBaseMonster::SentenceStop( void ) +{ + EMIT_SOUND( edict(), CHAN_VOICE, "common/null.wav", 1.0, ATTN_IDLE ); +} + + +void CBaseMonster::CorpseFallThink( void ) +{ + if ( pev->flags & FL_ONGROUND ) + { + SetThink ( NULL ); + + SetSequenceBox( ); + UTIL_SetOrigin( this, pev->origin );// link into world. + } + else + SetNextThink( 0.1 ); +} + +// Call after animation/pose is set up +void CBaseMonster :: MonsterInitDead( void ) +{ + InitBoneControllers(); + + pev->solid = SOLID_BBOX; + pev->movetype = MOVETYPE_TOSS;// so he'll fall to ground + + pev->frame = 0; + ResetSequenceInfo( ); + pev->framerate = 0; + + // Copy health + pev->max_health = pev->health; + pev->deadflag = DEAD_DEAD; + + UTIL_SetSize(pev, g_vecZero, g_vecZero ); + UTIL_SetOrigin( this, pev->origin ); + + // Setup health counters, etc. + BecomeDead(); + SetThink(&CBaseMonster :: CorpseFallThink ); + SetNextThink( 0.5 ); +} + +//========================================================= +// BBoxIsFlat - check to see if the monster's bounding box +// is lying flat on a surface (traces from all four corners +// are same length.) +//========================================================= +BOOL CBaseMonster :: BBoxFlat ( void ) +{ + TraceResult tr; + Vector vecPoint; + float flXSize, flYSize; + float flLength; + float flLength2; + + flXSize = pev->size.x / 2; + flYSize = pev->size.y / 2; + + vecPoint.x = pev->origin.x + flXSize; + vecPoint.y = pev->origin.y + flYSize; + vecPoint.z = pev->origin.z; + + UTIL_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), ignore_monsters, ENT(pev), &tr ); + flLength = (vecPoint - tr.vecEndPos).Length(); + + vecPoint.x = pev->origin.x - flXSize; + vecPoint.y = pev->origin.y - flYSize; + + UTIL_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), ignore_monsters, ENT(pev), &tr ); + flLength2 = (vecPoint - tr.vecEndPos).Length(); + if ( flLength2 > flLength ) + { + return FALSE; + } + flLength = flLength2; + + vecPoint.x = pev->origin.x - flXSize; + vecPoint.y = pev->origin.y + flYSize; + UTIL_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), ignore_monsters, ENT(pev), &tr ); + flLength2 = (vecPoint - tr.vecEndPos).Length(); + if ( flLength2 > flLength ) + { + return FALSE; + } + flLength = flLength2; + + vecPoint.x = pev->origin.x + flXSize; + vecPoint.y = pev->origin.y - flYSize; + UTIL_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), ignore_monsters, ENT(pev), &tr ); + flLength2 = (vecPoint - tr.vecEndPos).Length(); + if ( flLength2 > flLength ) + { + return FALSE; + } + flLength = flLength2; + + return TRUE; +} + +//========================================================= +// Get Enemy - tries to find the best suitable enemy for the monster. +//========================================================= +BOOL CBaseMonster :: GetEnemy ( void ) +{ + CBaseEntity *pNewEnemy; + + if ( HasConditions(bits_COND_SEE_HATE | bits_COND_SEE_DISLIKE | bits_COND_SEE_NEMESIS) ) + { + pNewEnemy = BestVisibleEnemy(); + + if ( pNewEnemy != m_hEnemy && pNewEnemy != NULL) + { + // DO NOT mess with the monster's m_hEnemy pointer unless the schedule the monster is currently running will be interrupted + // by COND_NEW_ENEMY. This will eliminate the problem of monsters getting a new enemy while they are in a schedule that doesn't care, + // and then not realizing it by the time they get to a schedule that does. I don't feel this is a good permanent fix. + + if ( m_pSchedule ) + { + if ( m_pSchedule->iInterruptMask & bits_COND_NEW_ENEMY ) + { + PushEnemy( m_hEnemy, m_vecEnemyLKP ); + SetConditions(bits_COND_NEW_ENEMY); + m_hEnemy = pNewEnemy; + m_vecEnemyLKP = m_hEnemy->pev->origin; + } + // if the new enemy has an owner, take that one as well + if (pNewEnemy->pev->owner != NULL) + { + CBaseEntity *pOwner = GetMonsterPointer( pNewEnemy->pev->owner ); + if ( pOwner && (pOwner->pev->flags & FL_MONSTER) && IRelationship( pOwner ) != R_NO ) + PushEnemy( pOwner, m_vecEnemyLKP ); + } + } + } + } + + // remember old enemies + if (m_hEnemy == NULL && PopEnemy( )) + { + if ( m_pSchedule ) + { + if ( m_pSchedule->iInterruptMask & bits_COND_NEW_ENEMY ) + { + SetConditions(bits_COND_NEW_ENEMY); + } + } + } + + if ( m_hEnemy != NULL ) + { + // buz: if enemy is player, store myself in his data, so player's allies will attack me + if (m_hEnemy->IsPlayer()) + { + CBasePlayer *pplayer = (CBasePlayer*)((CBaseEntity*)m_hEnemy); + pplayer->m_hLastEnemy = this; + } + + // monster has an enemy. + return TRUE; + } + + return FALSE;// monster has no enemy +} + + +//========================================================= +// DropItem - dead monster drops named item +//========================================================= +CBaseEntity* CBaseMonster :: DropItem ( char *pszItemName, const Vector &vecPos, const Vector &vecAng ) +{ + if ( !pszItemName ) + { + ALERT ( at_debug, "DropItem() - No item name!\n" ); + return NULL; + } + + CBaseEntity *pItem = CBaseEntity::Create( pszItemName, vecPos, vecAng, edict() ); + + if ( pItem ) + { + // do we want this behavior to be default?! (sjb) + pItem->pev->velocity = pev->velocity; + pItem->pev->avelocity = Vector ( 0, RANDOM_FLOAT( 0, 100 ), 0 ); + return pItem; + } + else + { + ALERT ( at_debug, "DropItem() - Didn't create!\n" ); + return FALSE; + } + +} + + +BOOL CBaseMonster :: ShouldFadeOnDeath( void ) +{ + // if flagged to fade out or I have an owner (I came from a monster spawner) + if ( (pev->spawnflags & SF_MONSTER_FADECORPSE) || !FNullEnt( pev->owner ) ) + return TRUE; + + return FALSE; +} + + +//LRC - an entity for monsters to shoot at. +#define SF_MONSTERTARGET_OFF 1 +class CTargetMonster : public CBaseEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int Classify( void ) { return pev->frags; }; + STATE GetState( void ) + { + return pev->health?STATE_ON:STATE_OFF; + }; +}; +LINK_ENTITY_TO_CLASS( monster_target, CTargetMonster ); + +void CTargetMonster :: Spawn ( void ) +{ + if (pev->spawnflags & SF_MONSTERTARGET_OFF) + pev->health = 0; + else + pev->health = 1; // Don't ignore me, I'm not dead. I'm quite well really. I think I'll go for a walk... + SetBits (pev->flags, FL_MONSTER); +} + +void CTargetMonster::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (ShouldToggle( useType )) + { + if (pev->health) + pev->health = 0; + else + pev->health = 1; + } +} \ No newline at end of file diff --git a/dlls/monsters.h b/dlls/monsters.h new file mode 100644 index 0000000..a86d214 --- /dev/null +++ b/dlls/monsters.h @@ -0,0 +1,190 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef MONSTERS_H +#include "skill.h" +#define MONSTERS_H + +/* + +===== monsters.h ======================================================== + + Header file for monster-related utility code + +*/ + +// CHECKLOCALMOVE result types +#define LOCALMOVE_INVALID 0 // move is not possible +#define LOCALMOVE_INVALID_DONT_TRIANGULATE 1 // move is not possible, don't try to triangulate +#define LOCALMOVE_VALID 2 // move is possible + +// Hit Group standards +#define HITGROUP_GENERIC 0 +#define HITGROUP_HEAD 1 +#define HITGROUP_CHEST 2 +#define HITGROUP_STOMACH 3 +#define HITGROUP_LEFTARM 4 +#define HITGROUP_RIGHTARM 5 +#define HITGROUP_LEFTLEG 6 +#define HITGROUP_RIGHTLEG 7 + + +// Monster Spawnflags +#define SF_MONSTER_WAIT_TILL_SEEN 1// spawnflag that makes monsters wait until player can see them before attacking. +#define SF_MONSTER_GAG 2 // no idle noises from this monster +#define SF_MONSTER_HITMONSTERCLIP 4 +// 8 +#define SF_MONSTER_PRISONER 16 // monster won't attack anyone, no one will attacke him. +// 32 +#define SF_MONSTER_INVINCIBLE 64 // buz - dont take damage from player +#define SF_MONSTER_NO_YELLOW_BLOBS 128 //LRC- if the monster is stuck, don't give errors or show yellow blobs. +//LRC- wasn't implemented. #define SF_MONSTER_WAIT_FOR_SCRIPT 128 //spawnflag that makes monsters wait to check for attacking until the script is done or they've been attacked +#define SF_MONSTER_PREDISASTER 256 //this is a predisaster scientist or barney. Influences how they speak. +#define SF_MONSTER_FADECORPSE 512 // Fade out corpse after death +#define SF_MONSTER_NO_WPN_DROP 1024 //LRC- never drop your weapon (player can't pick it up.) +//LRC - this clashes with 'not in deathmatch'. Replaced with m_iPlayerReact. +//#define SF_MONSTER_INVERT_PLAYERREACT 2048 //LRC- if this monster would usually attack the player, don't attack unless provoked. If you would usually NOT attack the player, attack him. +#define SF_MONSTER_FALL_TO_GROUND 0x80000000 + +// specialty spawnflags +#define SF_MONSTER_TURRET_AUTOACTIVATE 32 +#define SF_MONSTER_TURRET_STARTINACTIVE 64 +#define SF_MONSTER_WAIT_UNTIL_PROVOKED 64 // don't attack the player unless provoked + + + +// MoveToOrigin stuff +#define MOVE_START_TURN_DIST 64 // when this far away from moveGoal, start turning to face next goal +#define MOVE_STUCK_DIST 32 // if a monster can't step this far, it is stuck. + + +// MoveToOrigin stuff +#define MOVE_NORMAL 0// normal move in the direction monster is facing +#define MOVE_STRAFE 1// moves in direction specified, no matter which way monster is facing + +// spawn flags 256 and above are already taken by the engine +extern void UTIL_MoveToOrigin( edict_t* pent, const Vector &vecGoal, float flDist, int iMoveType ); + +Vector VecCheckToss ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flGravityAdj = 1.0 ); +Vector VecCheckThrow ( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float flSpeed, float flGravityAdj = 1.0 ); +extern DLL_GLOBAL Vector g_vecAttackDir; +extern DLL_GLOBAL CONSTANT float g_flMeleeRange; +extern DLL_GLOBAL CONSTANT float g_flMediumRange; +extern DLL_GLOBAL CONSTANT float g_flLongRange; +extern void EjectBrass (const Vector &vecOrigin, const Vector &vecVelocity, float rotation, int model, int soundtype ); + +BOOL FBoxVisible ( entvars_t *pevLooker, entvars_t *pevTarget ); +BOOL FBoxVisible ( entvars_t *pevLooker, entvars_t *pevTarget, Vector &vecTargetOrigin, float flSize = 0.0 ); + +// monster to monster relationship types +#define R_AL -2 // (ALLY) pals. Good alternative to R_NO when applicable. +#define R_FR -1// (FEAR)will run +#define R_NO 0// (NO RELATIONSHIP) disregard +#define R_DL 1// (DISLIKE) will attack +#define R_HT 2// (HATE)will attack this character instead of any visible DISLIKEd characters +#define R_NM 3// (NEMESIS) A monster Will ALWAYS attack its nemsis, no matter what + + +// these bits represent the monster's memory +#define MEMORY_CLEAR 0 +#define bits_MEMORY_PROVOKED ( 1 << 0 )// right now only used for houndeyes. +#define bits_MEMORY_INCOVER ( 1 << 1 )// monster knows it is in a covered position. +#define bits_MEMORY_SUSPICIOUS ( 1 << 2 )// Ally is suspicious of the player, and will move to provoked more easily +#define bits_MEMORY_PATH_FINISHED ( 1 << 3 )// Finished monster path (just used by big momma for now) +#define bits_MEMORY_ON_PATH ( 1 << 4 )// Moving on a path +#define bits_MEMORY_MOVE_FAILED ( 1 << 5 )// Movement has already failed +#define bits_MEMORY_FLINCHED ( 1 << 6 )// Has already flinched +#define bits_MEMORY_KILLED ( 1 << 7 )// HACKHACK -- remember that I've already called my Killed() +#define bits_MEMORY_TURNING ( 1 << 13 ) // Turning, don't interrupt me. + +#define bits_MEMORY_CUSTOM4 ( 1 << 28 ) // Monster-specific memory +#define bits_MEMORY_CUSTOM3 ( 1 << 29 ) // Monster-specific memory +#define bits_MEMORY_CUSTOM2 ( 1 << 30 ) // Monster-specific memory +#define bits_MEMORY_CUSTOM1 ( 1 << 31 ) // Monster-specific memory + +// trigger conditions for scripted AI +// these MUST match the CHOICES interface in halflife.fgd for the base monster +enum +{ + AITRIGGER_NONE = 0, + AITRIGGER_SEEPLAYER_ANGRY_AT_PLAYER, + AITRIGGER_TAKEDAMAGE, + AITRIGGER_HALFHEALTH, + AITRIGGER_DEATH, + AITRIGGER_SQUADMEMBERDIE, + AITRIGGER_SQUADLEADERDIE, + AITRIGGER_HEARWORLD, + AITRIGGER_HEARPLAYER, + AITRIGGER_HEARCOMBAT, + AITRIGGER_SEEPLAYER_UNCONDITIONAL, + AITRIGGER_SEEPLAYER_NOT_IN_COMBAT, +}; +/* + 0 : "No Trigger" + 1 : "See Player" + 2 : "Take Damage" + 3 : "50% Health Remaining" + 4 : "Death" + 5 : "Squad Member Dead" + 6 : "Squad Leader Dead" + 7 : "Hear World" + 8 : "Hear Player" + 9 : "Hear Combat" +*/ + +// +// A gib is a chunk of a body, or a piece of wood/metal/rocks/etc. +// +class CGib : public CBaseEntity +{ +public: + void Spawn( const char *szGibModel ); + void EXPORT BounceGibTouch ( CBaseEntity *pOther ); + void EXPORT StickyGibTouch ( CBaseEntity *pOther ); + void EXPORT WaitTillLand( void ); + void LimitVelocity( void ); + + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DONT_SAVE; } + static void SpawnHeadGib( entvars_t *pevVictim ); + static void SpawnHeadGib( entvars_t *pevVictim, const char *szGibModel ); + static void SpawnRandomGibs( entvars_t *pevVictim, int cGibs, int human ); + static void SpawnRandomGibs( entvars_t *pevVictim, int cGibs, int notfirst, const char *szGibModel ); //LRC + static void SpawnStickyGibs( entvars_t *pevVictim, Vector vecOrigin, int cGibs ); + + int m_bloodColor; + int m_cBloodDecals; + int m_material; + float m_lifeTime; +}; + + +#define CUSTOM_SCHEDULES\ + virtual Schedule_t *ScheduleFromName( const char *pName );\ + static Schedule_t *m_scheduleList[]; + +#define DEFINE_CUSTOM_SCHEDULES(derivedClass)\ + Schedule_t *derivedClass::m_scheduleList[] = + +#define IMPLEMENT_CUSTOM_SCHEDULES(derivedClass, baseClass)\ + Schedule_t *derivedClass::ScheduleFromName( const char *pName )\ + {\ + Schedule_t *pSchedule = ScheduleInList( pName, m_scheduleList, ARRAYSIZE(m_scheduleList) );\ + if ( !pSchedule )\ + return baseClass::ScheduleFromName(pName);\ + return pSchedule;\ + } + + + +#endif //MONSTERS_H diff --git a/dlls/monsterstate.cpp b/dlls/monsterstate.cpp new file mode 100644 index 0000000..fb96034 --- /dev/null +++ b/dlls/monsterstate.cpp @@ -0,0 +1,241 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// monsterstate.cpp - base class monster functions for +// controlling core AI. +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "nodes.h" +#include "monsters.h" +#include "animation.h" +#include "saverestore.h" +#include "soundent.h" + +//========================================================= +// SetState +//========================================================= +void CBaseMonster :: SetState ( MONSTERSTATE State ) +{ +/* + if ( State != m_MonsterState ) + { + ALERT ( at_aiconsole, "State Changed to %d\n", State ); + } +*/ + + switch( State ) + { + + // Drop enemy pointers when going to idle + case MONSTERSTATE_IDLE: + + if ( m_hEnemy != NULL ) + { + m_hEnemy = NULL;// not allowed to have an enemy anymore. + ALERT ( at_aiconsole, "Stripped\n" ); + } + break; + } + + m_MonsterState = State; + m_IdealMonsterState = State; +} + +//========================================================= +// RunAI +//========================================================= +void CBaseMonster :: RunAI ( void ) +{ + // to test model's eye height + //UTIL_ParticleEffect ( pev->origin + pev->view_ofs, g_vecZero, 255, 10 ); + + // IDLE sound permitted in ALERT state is because monsters were silent in ALERT state. Only play IDLE sound in IDLE state + // once we have sounds for that state. + if ( ( m_MonsterState == MONSTERSTATE_IDLE || m_MonsterState == MONSTERSTATE_ALERT ) && RANDOM_LONG(0,99) == 0 && !(pev->flags & SF_MONSTER_GAG) ) + { + IdleSound(); + } + + if ( m_MonsterState != MONSTERSTATE_NONE && + m_MonsterState != MONSTERSTATE_PRONE && + m_MonsterState != MONSTERSTATE_DEAD )// don't bother with this crap if monster is prone. + { + // collect some sensory Condition information. + // don't let monsters outside of the player's PVS act up, or most of the interesting + // things will happen before the player gets there! + // UPDATE: We now let COMBAT state monsters think and act fully outside of player PVS. This allows the player to leave + // an area where monsters are fighting, and the fight will continue. +// if ( !FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) || ( m_MonsterState == MONSTERSTATE_COMBAT ) ) +// { + Look( m_flDistLook ); + Listen();// check for audible sounds. + + // now filter conditions. + ClearConditions( IgnoreConditions() ); + + GetEnemy(); +// } buz: monsters can act anytime - for rush scripts + + // do these calculations if monster has an enemy. + if ( m_hEnemy != NULL ) + { + CheckEnemy( m_hEnemy ); + } + + CheckAmmo(); + } + + FCheckAITrigger(); + + PrescheduleThink(); + + MaintainSchedule(); + + // if the monster didn't use these conditions during the above call to MaintainSchedule() or CheckAITrigger() + // we throw them out cause we don't want them sitting around through the lifespan of a schedule + // that doesn't use them. + m_afConditions &= ~( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ); + + // buz + if (m_MonsterState == MONSTERSTATE_COMBAT || + m_MonsterState == MONSTERSTATE_ALERT || + m_MonsterState == MONSTERSTATE_HUNT) + { + m_iUseAlertAnims = 1; + } +} + +//========================================================= +// GetIdealState - surveys the Conditions information available +// and finds the best new state for a monster. +//========================================================= +MONSTERSTATE CBaseMonster :: GetIdealState ( void ) +{ + int iConditions; + + iConditions = IScheduleFlags(); + + // If no schedule conditions, the new ideal state is probably the reason we're in here. + switch ( m_MonsterState ) + { + case MONSTERSTATE_IDLE: + + /* + IDLE goes to ALERT upon hearing a sound + -IDLE goes to ALERT upon being injured + IDLE goes to ALERT upon seeing food + -IDLE goes to COMBAT upon sighting an enemy + IDLE goes to HUNT upon smelling food + */ + { + if ( iConditions & bits_COND_NEW_ENEMY ) + { + // new enemy! This means an idle monster has seen someone it dislikes, or + // that a monster in combat has found a more suitable target to attack + m_IdealMonsterState = MONSTERSTATE_COMBAT; + } + else if ( iConditions & bits_COND_LIGHT_DAMAGE ) + { + SetIdealYawToTargetAndUpdate( m_vecEnemyLKP ); + m_IdealMonsterState = MONSTERSTATE_ALERT; + } + else if ( iConditions & bits_COND_HEAVY_DAMAGE ) + { + SetIdealYawToTargetAndUpdate( m_vecEnemyLKP ); + m_IdealMonsterState = MONSTERSTATE_ALERT; + } + else if ( iConditions & bits_COND_HEAR_SOUND ) + { + CSound *pSound; + + pSound = PBestSound(); + ASSERT( pSound != NULL ); + if ( pSound ) + { + SetIdealYawToTargetAndUpdate( pSound->m_vecOrigin ); + if ( pSound->m_iType & (bits_SOUND_COMBAT|bits_SOUND_DANGER) ) + m_IdealMonsterState = MONSTERSTATE_ALERT; + } + } + else if ( iConditions & (bits_COND_SMELL | bits_COND_SMELL_FOOD) ) + { + m_IdealMonsterState = MONSTERSTATE_ALERT; + } + + break; + } + case MONSTERSTATE_ALERT: + /* + ALERT goes to IDLE upon becoming bored + -ALERT goes to COMBAT upon sighting an enemy + ALERT goes to HUNT upon hearing a noise + */ + { + if ( iConditions & (bits_COND_NEW_ENEMY|bits_COND_SEE_ENEMY) ) + { + // see an enemy we MUST attack + m_IdealMonsterState = MONSTERSTATE_COMBAT; + } + else if ( iConditions & bits_COND_HEAR_SOUND ) + { + m_IdealMonsterState = MONSTERSTATE_ALERT; + CSound *pSound = PBestSound(); + ASSERT( pSound != NULL ); + if ( pSound ) + SetIdealYawToTargetAndUpdate( pSound->m_vecOrigin ); + } + break; + } + case MONSTERSTATE_COMBAT: + /* + COMBAT goes to HUNT upon losing sight of enemy + COMBAT goes to ALERT upon death of enemy + */ + { + if ( m_hEnemy == NULL ) + { + m_IdealMonsterState = MONSTERSTATE_ALERT; + ALERT ( at_aiconsole, "***Combat state with no enemy!\n" ); + } + break; + } + case MONSTERSTATE_HUNT: + /* + HUNT goes to ALERT upon seeing food + HUNT goes to ALERT upon being injured + HUNT goes to IDLE if goal touched + HUNT goes to COMBAT upon seeing enemy + */ + { + break; + } + case MONSTERSTATE_SCRIPT: + if ( iConditions & (bits_COND_TASK_FAILED|bits_COND_LIGHT_DAMAGE|bits_COND_HEAVY_DAMAGE) ) + { + ExitScriptedSequence(); // This will set the ideal state + } + break; + + case MONSTERSTATE_DEAD: + m_IdealMonsterState = MONSTERSTATE_DEAD; + break; + } + + return m_IdealMonsterState; +} + diff --git a/dlls/mortar.cpp b/dlls/mortar.cpp new file mode 100644 index 0000000..576d187 --- /dev/null +++ b/dlls/mortar.cpp @@ -0,0 +1,255 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== mortar.cpp ======================================================== + + the "LaBuznik" mortar device + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "weapons.h" +#include "decals.h" +#include "soundent.h" + +class CFuncMortarField : public CBaseToggle +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + + // Bmodels don't go across transitions + virtual int ObjectCaps( void ) { return CBaseToggle :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + void EXPORT FieldUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int m_iszXController; + int m_iszYController; + float m_flSpread; + float m_flDelay; + int m_iCount; + int m_fControl; +}; + +LINK_ENTITY_TO_CLASS( func_mortar_field, CFuncMortarField ); + +TYPEDESCRIPTION CFuncMortarField::m_SaveData[] = +{ + DEFINE_FIELD( CFuncMortarField, m_iszXController, FIELD_STRING ), + DEFINE_FIELD( CFuncMortarField, m_iszYController, FIELD_STRING ), + DEFINE_FIELD( CFuncMortarField, m_flSpread, FIELD_FLOAT ), + DEFINE_FIELD( CFuncMortarField, m_flDelay, FIELD_FLOAT ), + DEFINE_FIELD( CFuncMortarField, m_iCount, FIELD_INTEGER ), + DEFINE_FIELD( CFuncMortarField, m_fControl, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CFuncMortarField, CBaseToggle ); + + +void CFuncMortarField :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iszXController")) + { + m_iszXController = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszYController")) + { + m_iszYController = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flSpread")) + { + m_flSpread = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fControl")) + { + m_fControl = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iCount")) + { + m_iCount = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } +} + + +// Drop bombs from above +void CFuncMortarField :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + pev->movetype = MOVETYPE_NONE; + SetBits( pev->effects, EF_NODRAW ); + SetUse(&CFuncMortarField :: FieldUse ); + Precache(); +} + + +void CFuncMortarField :: Precache( void ) +{ + PRECACHE_SOUND ("weapons/mortar.wav"); + PRECACHE_SOUND ("weapons/mortarhit.wav"); + PRECACHE_MODEL( "sprites/lgtning.spr" ); +} + + +// If connected to a table, then use the table controllers, else hit where the trigger is. +void CFuncMortarField :: FieldUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + Vector vecStart; + + vecStart.x = RANDOM_FLOAT( pev->mins.x, pev->maxs.x ); + vecStart.y = RANDOM_FLOAT( pev->mins.y, pev->maxs.y ); + vecStart.z = pev->maxs.z; + + switch( m_fControl ) + { + case 0: // random + break; + case 1: // Trigger Activator + if (pActivator != NULL) + { + vecStart.x = pActivator->pev->origin.x; + vecStart.y = pActivator->pev->origin.y; + } + break; + case 2: // table + { + CBaseEntity *pController; + + if (!FStringNull(m_iszXController)) + { + pController = UTIL_FindEntityByTargetname( NULL, STRING(m_iszXController)); + if (pController != NULL) + { + vecStart.x = pev->mins.x + pController->pev->ideal_yaw * (pev->size.x); + } + } + if (!FStringNull(m_iszYController)) + { + pController = UTIL_FindEntityByTargetname( NULL, STRING(m_iszYController)); + if (pController != NULL) + { + vecStart.y = pev->mins.y + pController->pev->ideal_yaw * (pev->size.y); + } + } + } + break; + } + + int pitch = RANDOM_LONG(95,124); + + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "weapons/mortar.wav", 1.0, ATTN_NONE, 0, pitch); + + float t = 2.5; + for (int i = 0; i < m_iCount; i++) + { + Vector vecSpot = vecStart; + vecSpot.x += RANDOM_FLOAT( -m_flSpread, m_flSpread ); + vecSpot.y += RANDOM_FLOAT( -m_flSpread, m_flSpread ); + + TraceResult tr; + UTIL_TraceLine( vecSpot, vecSpot + Vector( 0, 0, -1 ) * 4096, ignore_monsters, ENT(pev), &tr ); + + edict_t *pentOwner = NULL; + if (pActivator) pentOwner = pActivator->edict(); + + CBaseEntity *pMortar = Create("monster_mortar", tr.vecEndPos, Vector( 0, 0, 0 ), pentOwner ); + pMortar->SetNextThink( t ); + t += RANDOM_FLOAT( 0.2, 0.5 ); + + if (i == 0) + CSoundEnt::InsertSound ( bits_SOUND_DANGER, tr.vecEndPos, 400, 0.3 ); + } +} + + +class CMortar : public CGrenade +{ +public: + void Spawn( void ); + void Precache( void ); + + void EXPORT MortarExplode( void ); + + int m_spriteTexture; +}; + +LINK_ENTITY_TO_CLASS( monster_mortar, CMortar ); + +void CMortar::Spawn( ) +{ + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_NOT; + + pev->dmg = 200; + + SetThink(&CMortar:: MortarExplode ); + DontThink(); + + Precache( ); + + +} + + +void CMortar::Precache( ) +{ + m_spriteTexture = PRECACHE_MODEL( "sprites/lgtning.spr" ); +} + +void CMortar::MortarExplode( void ) +{ + // mortar beam + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS ); + 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 + 1024); + WRITE_SHORT(m_spriteTexture ); + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 1 ); // life + WRITE_BYTE( 40 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 160 ); // r, g, b + WRITE_BYTE( 100 ); // r, g, b + WRITE_BYTE( 128 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + TraceResult tr; + UTIL_TraceLine( pev->origin + Vector( 0, 0, 1024 ), pev->origin - Vector( 0, 0, 1024 ), dont_ignore_monsters, ENT(pev), &tr ); + + Explode( &tr, DMG_BLAST | DMG_MORTAR ); + UTIL_ScreenShake( tr.vecEndPos, 25.0, 150.0, 1.0, 750 ); +} \ No newline at end of file diff --git a/dlls/movewith.cpp b/dlls/movewith.cpp new file mode 100644 index 0000000..36845a5 --- /dev/null +++ b/dlls/movewith.cpp @@ -0,0 +1,691 @@ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "movewith.h" +#include "saverestore.h" +#include "player.h" + +CWorld *g_pWorld = NULL; //LRC + +BOOL g_doingDesired = FALSE; //LRC - marks whether the Desired functions are currently + // being processed. + +void UTIL_AddToAssistList( CBaseEntity *pEnt ) +{ +// ALERT(at_console, "Add %s \"%s\" to AssistList\n", STRING(pEnt->pev->classname), STRING(pEnt->pev->targetname)); + + if (pEnt->m_pAssistLink) + { +// ALERT(at_console, "Ignored AddToAssistList for %s \"%s\"\n", STRING(pEnt->pev->classname), STRING(pEnt->pev->targetname)); + return; // is pEnt already in the body of the list? + } + + if ( !g_pWorld ) + { + ALERT(at_debug, "AddToAssistList %s \"%s\" has no AssistList!\n", STRING(pEnt->pev->classname), STRING(pEnt->pev->targetname)); + return; + } + + CBaseEntity *pListMember = g_pWorld; + + // find the last entry in the list... + while (pListMember->m_pAssistLink != NULL) + pListMember = pListMember->m_pAssistLink; + + if (pListMember == pEnt) + { +// ALERT(at_console, "(end)Ignored AddToAssistList for %s \"%s\"\n", STRING(pEnt->pev->classname), STRING(pEnt->pev->targetname)); + return; // pEnt is the last entry in the list. + } + + pListMember->m_pAssistLink = pEnt; // it's not in the list already, so add pEnt to the list. + +// int count = 0; +// for (pListMember = g_pWorld->m_pAssistLink; pListMember; pListMember = pListMember->m_pAssistLink) +// { +// count++; +// } +// ALERT(at_console, "Added %s \"%s\" to AssistList, length now %d\n", STRING(pEnt->pev->classname), STRING(pEnt->pev->targetname), count); +} + +void HandlePostAssist( CBaseEntity *pEnt ) +{ + if (pEnt->m_iLFlags & LF_POSTASSISTVEL) + { +// ALERT(at_console, "RestoreVel %s: orign %f %f %f, velocity was %f %f %f, back to %f %f %f\n", +// STRING(pEnt->pev->targetname), +// pEnt->pev->origin.x, pEnt->pev->origin.y, pEnt->pev->origin.z, +// pEnt->pev->velocity.x, pEnt->pev->velocity.y, pEnt->pev->velocity.z, +// pEnt->m_vecPostAssistVel.x, pEnt->m_vecPostAssistVel.y, pEnt->m_vecPostAssistVel.z +// ); + pEnt->pev->velocity = pEnt->m_vecPostAssistVel; + pEnt->m_vecPostAssistVel = g_vecZero; + pEnt->m_iLFlags &= ~LF_POSTASSISTVEL; + } + if (pEnt->m_iLFlags & LF_POSTASSISTAVEL) + { +// ALERT(at_console, "RestoreVel %s: orign %f %f %f, velocity was %f %f %f, back to %f %f %f\n", +// STRING(pEnt->pev->targetname), +// pEnt->pev->origin.x, pEnt->pev->origin.y, pEnt->pev->origin.z, +// pEnt->pev->velocity.x, pEnt->pev->velocity.y, pEnt->pev->velocity.z, +// pEnt->m_vecPostAssistVel.x, pEnt->m_vecPostAssistVel.y, pEnt->m_vecPostAssistVel.z +// ); + pEnt->pev->avelocity = pEnt->m_vecPostAssistAVel; + pEnt->m_vecPostAssistAVel = g_vecZero; + pEnt->m_iLFlags &= ~LF_POSTASSISTAVEL; + } + CBaseEntity *pChild; + for (pChild = pEnt->m_pChildMoveWith; pChild != NULL; pChild = pChild->m_pSiblingMoveWith) + HandlePostAssist( pChild ); +} + +// returns 0 if no desired settings needed, else returns 1 +int ApplyDesiredSettings( CBaseEntity *pListMember ) +{ + if (pListMember->m_iLFlags & LF_DODESIRED) + { + pListMember->m_iLFlags &= ~LF_DODESIRED; + } + else + { + // don't need to apply any desired settings for this entity. + return 0; + } +// ALERT(at_console, "ApplyDesiredSettings for %s \"%s\", pevnt %f, ltime %f, mfnt %f, mpevnt %f, %f\n", STRING(pListMember->pev->classname), STRING(pListMember->pev->targetname), pListMember->pev->nextthink, pListMember->pev->ltime, pListMember->m_fNextThink, pListMember->m_fPevNextThink, pListMember->pev->origin.x); + + if (pListMember->m_iLFlags & LF_DESIRED_ACTION) + { + pListMember->m_iLFlags &= ~LF_DESIRED_ACTION; + pListMember->DesiredAction(); + } + + if (pListMember->m_iLFlags & LF_DESIRED_INFO) + { + pListMember->m_iLFlags &= ~LF_DESIRED_INFO; + ALERT(at_debug, "DesiredInfo: pos %f %f %f, vel %f %f %f. Child pos %f %f %f, vel %f %f %f\n\n", pListMember->pev->origin.x, pListMember->pev->origin.y, pListMember->pev->origin.z, pListMember->pev->velocity.x, pListMember->pev->velocity.y, pListMember->pev->velocity.z, pListMember->m_pChildMoveWith->pev->origin.x, pListMember->m_pChildMoveWith->pev->origin.y, pListMember->m_pChildMoveWith->pev->origin.z, pListMember->m_pChildMoveWith->pev->velocity.x, pListMember->m_pChildMoveWith->pev->velocity.y, pListMember->m_pChildMoveWith->pev->velocity.z); + } + + if (pListMember->m_iLFlags & LF_DESIRED_POSTASSIST) + { + pListMember->m_iLFlags &= ~LF_DESIRED_POSTASSIST; + HandlePostAssist( pListMember ); + } + + if (pListMember->m_iLFlags & LF_DESIRED_THINK) + { + pListMember->m_iLFlags &= ~LF_DESIRED_THINK; + //ALERT(at_console, "DesiredThink %s\n", STRING(pListMember->pev->targetname)); + pListMember->Think(); + } + + return 1; +} + +void AssistChildren( CBaseEntity *pEnt, Vector vecAdjustVel, Vector vecAdjustAVel ) +{ + CBaseEntity *pChild; + for(pChild = pEnt->m_pChildMoveWith; pChild != NULL; pChild = pChild->m_pSiblingMoveWith) + { + if (!(pChild->m_iLFlags & LF_POSTASSISTVEL)) + { + pChild->m_vecPostAssistVel = pChild->pev->velocity; + pChild->m_iLFlags |= LF_POSTASSISTVEL; + } + if (!(pChild->m_iLFlags & LF_POSTASSISTAVEL)) + { + pChild->m_vecPostAssistAVel = pChild->pev->avelocity; + pChild->m_iLFlags |= LF_POSTASSISTAVEL; + } + pChild->pev->velocity = pChild->pev->velocity - vecAdjustVel;// (pChild->pev->velocity - pEnt->m_vecPostAssistVel) + pEnt->m_vecPostAssistVel*fFraction; + pChild->pev->avelocity = pChild->pev->avelocity - vecAdjustAVel;// (pChild->pev->avelocity - pEnt->m_vecPostAssistAVel) + pEnt->m_vecPostAssistAVel*fFraction; + + //ALERT(at_console, "AssistChild %s: origin %f %f %f, old vel %f %f %f. fraction %f, new vel %f %f %f, dest %f %f %f\n", STRING(pChild->pev->targetname), pChild->pev->origin.x, pChild->pev->origin.y, pChild->pev->origin.z, pChild->m_vecPostAssistVel.x, pChild->m_vecPostAssistVel.y, pChild->m_vecPostAssistVel.z, fFraction, pChild->pev->velocity.x, pChild->pev->velocity.y, pChild->pev->velocity.z, pChild->pev->origin.x + pChild->pev->velocity.x*gpGlobals->frametime, pChild->pev->origin.y + pChild->pev->velocity.y*gpGlobals->frametime, pChild->pev->origin.z + pChild->pev->velocity.z*gpGlobals->frametime ); + //ALERT(at_console, "AssistChild %s: origin %f %f %f. velocity was %f %f %f, now %f %f %f\n", STRING(pChild->pev->targetname), pChild->pev->origin.x, pChild->pev->origin.y, pChild->pev->origin.z, pChild->m_vecPostAssistVel.x, pChild->m_vecPostAssistVel.y, pChild->m_vecPostAssistVel.z, pChild->pev->velocity.x, pChild->pev->velocity.y, pChild->pev->velocity.z); + + AssistChildren( pChild, vecAdjustVel, vecAdjustAVel ); + } +} + +// pEnt is a PUSH entity. Before physics is applied in a frame, we need to assist: +// 1) this entity, if it will stop moving this turn but its parent will continue. +// 2) this entity's PUSH children, if 1) was applied. +// 3) this entity's non-PUSH children, if this entity will stop moving this turn. +// +// returns 0 if the entity wasn't flagged for DoAssist. +int TryAssistEntity( CBaseEntity *pEnt ) +{ + if (gpGlobals->frametime == 0) + { +// ALERT(at_console, "frametime 0, don't assist\n"); + return 0; + } + + // not flagged as needing assistance? + if (!(pEnt->m_iLFlags & LF_DOASSIST)) + return 0; + +// ALERT(at_console, "AssistList: %s\n", STRING(pEnt->pev->classname)); + + if (pEnt->m_fNextThink <= 0) + { +// ALERT(at_console, "Cancelling assist for %s, %f\n", STRING(pEnt->pev->targetname), pEnt->pev->origin.x); + pEnt->m_iLFlags &= ~LF_DOASSIST; + return 0; // the think has been cancelled. Oh well... + } + +// ALERT(at_console, "Assist, mfNT %f, pevNT %f\n", pEnt->m_fNextThink, pEnt->pev->nextthink); +// pEnt->ThinkCorrection(); + + // a fraction of the current velocity. (the part of it that the engine should see.) + float fFraction = 0; + + // is this the frame when the entity will stop? + if (pEnt->pev->movetype == MOVETYPE_PUSH) + { + if (pEnt->m_fNextThink <= pEnt->pev->ltime + gpGlobals->frametime) + fFraction = (pEnt->m_fNextThink - pEnt->pev->ltime)/gpGlobals->frametime; + } + else if (pEnt->m_fNextThink <= gpGlobals->time + gpGlobals->frametime) + { + fFraction = (pEnt->m_fNextThink - gpGlobals->time)/gpGlobals->frametime; +// ALERT(at_console, "Setting fFraction\n"); + } + + if (fFraction) + { +// ALERT(at_console, "Assisting %s \"%s\", %f <= %f + %f\n", STRING(pEnt->pev->classname), STRING(pEnt->pev->targetname), pEnt->m_fNextThink, pEnt->pev->ltime, gpGlobals->frametime); + + if (pEnt->m_iLFlags & LF_CORRECTSPEED) + { + if (!(pEnt->m_iLFlags & LF_POSTASSISTVEL)) + { + pEnt->m_vecPostAssistVel = pEnt->pev->velocity; + pEnt->m_iLFlags |= LF_POSTASSISTVEL; + } + + if (!(pEnt->m_iLFlags & LF_POSTASSISTAVEL)) + { + pEnt->m_vecPostAssistAVel = pEnt->pev->avelocity; + pEnt->m_iLFlags |= LF_POSTASSISTAVEL; + } + + Vector vecVelTemp = pEnt->pev->velocity; + Vector vecAVelTemp = pEnt->pev->avelocity; + + if (pEnt->m_pMoveWith) + { + pEnt->pev->velocity = (pEnt->pev->velocity - pEnt->m_pMoveWith->pev->velocity)*fFraction + pEnt->m_pMoveWith->pev->velocity; + pEnt->pev->avelocity = (pEnt->pev->avelocity - pEnt->m_pMoveWith->pev->avelocity)*fFraction + pEnt->m_pMoveWith->pev->avelocity; + } + else + { + pEnt->pev->velocity = pEnt->pev->velocity*fFraction; + pEnt->pev->avelocity = pEnt->pev->avelocity*fFraction; + } + + //ALERT(at_console, "Assist %s: origin %f %f %f, old vel %f %f %f. fraction %f, new vel %f %f %f, dest %f %f %f\n", STRING(pEnt->pev->targetname), pEnt->pev->origin.x, pEnt->pev->origin.y, pEnt->pev->origin.z, pEnt->m_vecPostAssistVel.x, pEnt->m_vecPostAssistVel.y, pEnt->m_vecPostAssistVel.z, fFraction, pEnt->pev->velocity.x, pEnt->pev->velocity.y, pEnt->pev->velocity.z, pEnt->pev->origin.x + pEnt->pev->velocity.x*gpGlobals->frametime, pEnt->pev->origin.y + pEnt->pev->velocity.y*gpGlobals->frametime, pEnt->pev->origin.z + pEnt->pev->velocity.z*gpGlobals->frametime); + + AssistChildren( pEnt, vecVelTemp - pEnt->pev->velocity, vecAVelTemp - pEnt->pev->avelocity ); + UTIL_DesiredPostAssist( pEnt ); + } + + UTIL_DesiredThink( pEnt ); +// ALERT(at_console, "Assist sets DesiredThink for %s\n", STRING(pEnt->pev->classname)); + + pEnt->m_iLFlags &= ~LF_DOASSIST; + } + +#if 0 + else + { +// float fLeft = (pEnt->m_fNextThink - pEnt->pev->ltime); +// ALERT(at_console, "NoAssist: origin %f %f %f, ontrack %f %f %f, mfNT %f, should be %f\n", pEnt->pev->origin.x, pEnt->pev->origin.y, pEnt->pev->origin.z, pEnt->pev->origin.x + pEnt->pev->velocity.x*fLeft, pEnt->pev->origin.y + pEnt->pev->velocity.y*fLeft, pEnt->pev->origin.z + pEnt->pev->velocity.z*fLeft, pEnt->m_fNextThink, ((int)(pEnt->pev->origin.x+pEnt->pev->velocity.x*fLeft)-pEnt->pev->origin.x)/pEnt->pev->velocity.x+pEnt->pev->ltime); + static Vector staticPos; + static float staticLtime; + static float staticGtime; + static float staticFtime; +// ALERT(at_console, "No assist %s; diff %f %f %f, ediff %f %f %f, ltdiff %f, gtdiff %f, ftime %f, nthink %f, mfNT %f\n", +// STRING(pEnt->pev->targetname), +// pEnt->pev->origin.x - staticPos.x, pEnt->pev->origin.y - staticPos.y, pEnt->pev->origin.z - staticPos.z, +// pEnt->pev->velocity.x*staticFtime, pEnt->pev->velocity.y*staticFtime, pEnt->pev->velocity.z*staticFtime, +// pEnt->pev->ltime - staticLtime, gpGlobals->time - staticGtime, gpGlobals->frametime, +// pEnt->pev->nextthink, pEnt->m_fNextThink +// ); + staticPos = pEnt->pev->origin; + staticLtime = pEnt->pev->ltime; + staticGtime = gpGlobals->time; + staticFtime = gpGlobals->frametime; + } +#endif +// ALERT(at_console, "TryAssist %s \"%s\", lflags %f, pevnt %f, ltime %f, mfnt %f, mpevnt %f, %f\n", STRING(pEnt->pev->classname), STRING(pEnt->pev->targetname), pEnt->m_iLFlags, pEnt->pev->nextthink, pEnt->pev->ltime, pEnt->m_fNextThink, pEnt->m_fPevNextThink, pEnt->pev->origin.x); +// ALERT(at_console, "TryAssist %s \"%s\", pevnt %f, ltime %f, mfnt %f, mpevnt %f, lfl %d, %f\n", STRING(pEnt->pev->classname), STRING(pEnt->pev->targetname), pEnt->pev->nextthink, pEnt->pev->ltime, pEnt->m_fNextThink, pEnt->m_fPevNextThink, pEnt->m_iLFlags, pEnt->pev->origin.x); + return 1; +} + +// called every frame, by StartFrame +void CheckAssistList( void ) +{ + CBaseEntity *pListMember; + + if ( !g_pWorld ) + { + ALERT(at_error, "CheckAssistList has no AssistList!\n"); + return; + } + + int count = 0; + + for (pListMember = g_pWorld; pListMember; pListMember = pListMember->m_pAssistLink) + { + if (pListMember->m_iLFlags & LF_DOASSIST) + count++; + } +// if (count) +// ALERT(at_console, "CheckAssistList begins, length is %d\n", count); + count = 0; + pListMember = g_pWorld; + + while ( pListMember->m_pAssistLink ) // handle the remaining entries in the list + { + TryAssistEntity(pListMember->m_pAssistLink); + if (!(pListMember->m_pAssistLink->m_iLFlags & LF_ASSISTLIST)) + { +// ALERT(at_console, "Removing %s \"%s\" from assistList\n", STRING(pListMember->m_pAssistLink->pev->classname), STRING(pListMember->m_pAssistLink->pev->targetname)); + CBaseEntity *pTemp = pListMember->m_pAssistLink; + pListMember->m_pAssistLink = pListMember->m_pAssistLink->m_pAssistLink; + pTemp->m_pAssistLink = NULL; + } + else + { + pListMember = pListMember->m_pAssistLink; + } + } + +// if (count) +// ALERT(at_console, "CheckAssistList complete, %d ents checked\n", count); +} + +// called every frame, by PostThink +void CheckDesiredList( void ) +{ + CBaseEntity *pListMember; + int loopbreaker = 1000; //assume this is the max. number of entities which will use DesiredList in any one frame +// BOOL liststart = FALSE; + if (g_doingDesired) + ALERT(at_debug, "CheckDesiredList: doingDesired is already set!?\n"); + g_doingDesired = TRUE; + + if (!g_pWorld) + { + ALERT(at_console, "CheckDesiredList has no AssistList!\n"); + return; + } + pListMember = g_pWorld; + CBaseEntity *pNext; + +// int count = 0; +// int all = 0; +// for (pListMember = g_pWorld->m_pAssistLink; pListMember; pListMember = pListMember->m_pAssistLink) +// { +// all++; +// if (pListMember->m_iLFlags & LF_DODESIRED) +// count++; +// } +// if (count) +// ALERT(at_console, "CheckDesiredList begins, length is %d, total %d\n", count, all); +// count = 0; + pListMember = g_pWorld->m_pAssistLink; + + while (pListMember) + { + // cache this, in case ApplyDesiredSettings does a SUB_Remove. + pNext = pListMember->m_pAssistLink; + ApplyDesiredSettings( pListMember ); + pListMember = pNext; + loopbreaker--; + if (loopbreaker <= 0) + { + ALERT(at_error, "Infinite(?) loop in DesiredList!"); + break; + } + } +// if (count) +// ALERT(at_console, "CheckDesiredList complete, %d ents checked\n", count); + +// if (liststart) ALERT(at_console, "-- DesiredList ends\n"); + g_doingDesired = FALSE; +} + + + +void UTIL_MarkForAssist( CBaseEntity *pEnt, BOOL correctSpeed ) +{ + pEnt->m_iLFlags |= LF_DOASSIST; + + if (correctSpeed) + pEnt->m_iLFlags |= LF_CORRECTSPEED; + else + pEnt->m_iLFlags &= ~LF_CORRECTSPEED; + + UTIL_AddToAssistList( pEnt ); +} + +void UTIL_MarkForDesired ( CBaseEntity *pEnt ) +{ + pEnt->m_iLFlags |= LF_DODESIRED; + + if (g_doingDesired) + { + //ALERT(at_console, "doingDesired is true, start immediately\n"); + ApplyDesiredSettings( pEnt ); + return; + } + + UTIL_AddToAssistList( pEnt ); +} + +void UTIL_DesiredAction ( CBaseEntity *pEnt ) +{ + pEnt->m_iLFlags |= LF_DESIRED_ACTION; + UTIL_MarkForDesired( pEnt ); +} + +void UTIL_DesiredInfo ( CBaseEntity *pEnt ) +{ + pEnt->m_iLFlags |= LF_DESIRED_INFO; + UTIL_MarkForDesired( pEnt ); +} + +void UTIL_DesiredPostAssist ( CBaseEntity *pEnt ) +{ + pEnt->m_iLFlags |= LF_DESIRED_POSTASSIST; + UTIL_MarkForDesired( pEnt ); +} + +void UTIL_DesiredThink ( CBaseEntity *pEnt ) +{ +// ALERT(at_console, "Setting DesiredThink %s\n", STRING(pEnt->pev->targetname)); + pEnt->m_iLFlags |= LF_DESIRED_THINK; + pEnt->DontThink(); + UTIL_MarkForDesired( pEnt ); +} + + + +// LRC- change the origin to the given position, and bring any movewiths along too. +void UTIL_AssignOrigin( CBaseEntity *pEntity, const Vector vecOrigin ) +{ + UTIL_AssignOrigin( pEntity, vecOrigin, TRUE); +} + +// LRC- bInitiator is true if this is being called directly, rather than because pEntity is moving with something else. +void UTIL_AssignOrigin( CBaseEntity *pEntity, const Vector vecOrigin, BOOL bInitiator) +{ +// ALERT(at_console, "AssignOrigin before %f, after %f\n", pEntity->pev->origin.x, vecOrigin.x); + Vector vecDiff = vecOrigin - pEntity->pev->origin; + if (vecDiff.Length() > 0.01 && CVAR_GET_FLOAT("sohl_mwdebug")) + ALERT(at_debug,"AssignOrigin %s %s: (%f %f %f) goes to (%f %f %f)\n",STRING(pEntity->pev->classname), STRING(pEntity->pev->targetname), pEntity->pev->origin.x, pEntity->pev->origin.y, pEntity->pev->origin.z, vecOrigin.x, vecOrigin.y, vecOrigin.z); + +// UTIL_SetDesiredPos(pEntity, vecOrigin); +// pEntity->pev->origin = vecOrigin; + UTIL_SetOrigin(pEntity, vecOrigin); + +// if (pEntity->m_vecDesiredVel != g_vecZero) +// { +// pEntity->pev->velocity = pEntity->m_vecDesiredVel; +// } + if (bInitiator && pEntity->m_pMoveWith) + { +// UTIL_DesiredMWOffset( pEntity ); +// if (pEntity->m_vecMoveWithOffset != (pEntity->pev->origin - pEntity->m_pMoveWith->pev->origin)) +// ALERT(at_console, "Changing MWOffset for %s \"%s\"\n", STRING(pEntity->pev->classname), STRING(pEntity->pev->targetname)); + pEntity->m_vecMoveWithOffset = pEntity->pev->origin - pEntity->m_pMoveWith->pev->origin; +// ALERT(at_console,"set m_vecMoveWithOffset = %f %f %f\n",pEntity->m_vecMoveWithOffset.x,pEntity->m_vecMoveWithOffset.y,pEntity->m_vecMoveWithOffset.z); + } + if (pEntity->m_pChildMoveWith) // now I've moved pEntity, does anything else have to move with it? + { + CBaseEntity* pChild = pEntity->m_pChildMoveWith; +// if (vecDiff != g_vecZero) +// { + Vector vecTemp; + while (pChild) + { + //ALERT(at_console," pre: parent origin is (%f %f %f), child origin is (%f %f %f)\n", + // pEntity->pev->origin.x,pEntity->pev->origin.y,pEntity->pev->origin.z, + // pChild->pev->origin.x,pChild->pev->origin.y,pChild->pev->origin.z + //); + if (pChild->pev->movetype != MOVETYPE_PUSH || pChild->pev->velocity == pEntity->pev->velocity) // if the child isn't moving under its own power + { + UTIL_AssignOrigin( pChild, vecOrigin + pChild->m_vecMoveWithOffset, FALSE ); +// ALERT(at_console,"used m_vecMoveWithOffset based on %f %f %f to set %f %f %f\n",pEntity->pev->origin.x,pEntity->pev->origin.y,pEntity->pev->origin.z,pChild->pev->origin.x,pChild->pev->origin.y,pChild->pev->origin.z); + } + else + { + vecTemp = vecDiff + pChild->pev->origin; + UTIL_AssignOrigin( pChild, vecTemp, FALSE ); + } + //ALERT(at_console," child origin becomes (%f %f %f)\n",pChild->pev->origin.x,pChild->pev->origin.y,pChild->pev->origin.z); + //ALERT(at_console,"ent %p has sibling %p\n",pChild,pChild->m_pSiblingMoveWith); + pChild = pChild->m_pSiblingMoveWith; + } +// } + } +} + +void UTIL_SetAngles( CBaseEntity *pEntity, const Vector vecAngles ) +{ + UTIL_SetAngles( pEntity, vecAngles, TRUE ); +} + +void UTIL_SetAngles( CBaseEntity *pEntity, const Vector vecAngles, BOOL bInitiator) +{ + Vector vecDiff = vecAngles - pEntity->pev->angles; + if (vecDiff.Length() > 0.01 && CVAR_GET_FLOAT("sohl_mwdebug")) + ALERT(at_debug,"SetAngles %s %s: (%f %f %f) goes to (%f %f %f)\n",STRING(pEntity->pev->classname), STRING(pEntity->pev->targetname), pEntity->pev->angles.x, pEntity->pev->angles.y, pEntity->pev->angles.z, vecAngles.x, vecAngles.y, vecAngles.z); + +// UTIL_SetDesiredAngles(pEntity, vecAngles); + pEntity->pev->angles = vecAngles; + + if (bInitiator && pEntity->m_pMoveWith) + { + pEntity->m_vecRotWithOffset = vecAngles - pEntity->m_pMoveWith->pev->angles; + } + if (pEntity->m_pChildMoveWith) // now I've moved pEntity, does anything else have to move with it? + { + CBaseEntity* pChild = pEntity->m_pChildMoveWith; + Vector vecTemp; + while (pChild) + { + if (pChild->pev->avelocity == pEntity->pev->avelocity) // if the child isn't turning under its own power + { + UTIL_SetAngles( pChild, vecAngles + pChild->m_vecRotWithOffset, FALSE ); + } + else + { + vecTemp = vecDiff + pChild->pev->angles; + UTIL_SetAngles( pChild, vecTemp, FALSE ); + } + //ALERT(at_console," child origin becomes (%f %f %f)\n",pChild->pev->origin.x,pChild->pev->origin.y,pChild->pev->origin.z); + //ALERT(at_console,"ent %p has sibling %p\n",pChild,pChild->m_pSiblingMoveWith); + pChild = pChild->m_pSiblingMoveWith; + } + } +} + +//LRC- an arbitrary limit. If this number is exceeded we assume there's an infinite loop, and abort. +#define MAX_MOVEWITH_DEPTH 100 + +//LRC- for use in supporting movewith. Tell the entity that whatever it's moving with is about to change velocity. +// loopbreaker is there to prevent the game from hanging... +void UTIL_SetMoveWithVelocity ( CBaseEntity *pEnt, const Vector vecSet, int loopbreaker ) +{ + if (loopbreaker <= 0) + { + ALERT(at_error, "Infinite child list for MoveWith!"); + return; + } + + if (!pEnt->m_pMoveWith) + { + ALERT(at_error,"SetMoveWithVelocity: no MoveWith entity!?\n"); + return; + } + + Vector vecNew; +// if (pEnt->pev->movetype == MOVETYPE_PUSH) +// { +// float fFraction = + vecNew = (pEnt->pev->velocity - pEnt->m_pMoveWith->pev->velocity) + vecSet; +// } +// else +// vecNew = vecSet; // lazy assumption: non-push entities don't move on their own. + +// ALERT(at_console,"SetMoveWithVelocity: %s changes to (%f %f %f) (based on %f %f %f)\n", +// STRING(pEnt->pev->classname), vecNew.x, vecNew.y, vecNew.z, +// pEnt->m_pMoveWith->pev->velocity.x, pEnt->m_pMoveWith->pev->velocity.y, pEnt->m_pMoveWith->pev->velocity.z +// ); + +// ALERT(at_console,"SMWV: %s is sent (%f,%f,%f) - goes from (%f,%f,%f) to (%f,%f,%f)\n", +// STRING(pEnt->pev->targetname), vecSet.x, vecSet.y, vecSet.z, +// pEnt->pev->velocity.x, pEnt->pev->velocity.y, pEnt->pev->velocity.z, +// vecNew.x, vecNew.y, vecNew.z +// ); + + //LRC- velocity is ignored on entities that aren't thinking! (Aargh...) +// if (fThinkTime) +// { +// UTIL_DesiredNextThink( pEnt, fThinkTime ); +// } +// else if (pEnt->pev->nextthink < 1) +// { +// UTIL_DesiredNextThink( pEnt, 1E6 ); +// //pEnt->pev->nextthink = gpGlobals->time + 1E6; +// } + + if ( pEnt->m_pChildMoveWith ) + { + CBaseEntity *pMoving = pEnt->m_pChildMoveWith; + int sloopbreaker = MAX_MOVEWITH_DEPTH; // to prevent the game hanging... + while (pMoving) + { + UTIL_SetMoveWithVelocity(pMoving, vecNew, loopbreaker - 1 ); + pMoving = pMoving->m_pSiblingMoveWith; + sloopbreaker--; + if (sloopbreaker <= 0) + { + ALERT(at_error, "SetMoveWithVelocity: Infinite sibling list for MoveWith!"); + break; + } + } + } + +// if (bImmediate) + pEnt->pev->velocity = vecNew; +// else +// UTIL_SetDesiredVel(pEnt, vecNew); +} + +//LRC +void UTIL_SetVelocity ( CBaseEntity *pEnt, const Vector vecSet ) +{ + Vector vecNew; + if (pEnt->m_pMoveWith) + vecNew = vecSet + pEnt->m_pMoveWith->pev->velocity; + else + vecNew = vecSet; + +// ALERT(at_console,"SetV: %s is sent (%f,%f,%f) - goes from (%f,%f,%f) to (%f,%f,%f)\n", +// STRING(pEnt->pev->targetname), vecSet.x, vecSet.y, vecSet.z, +// pEnt->pev->velocity.x, pEnt->pev->velocity.y, pEnt->pev->velocity.z, +// vecNew.x, vecNew.y, vecNew.z +// ); + + if ( pEnt->m_pChildMoveWith ) + { + CBaseEntity *pMoving = pEnt->m_pChildMoveWith; + int sloopbreaker = MAX_MOVEWITH_DEPTH; // LRC - to save us from infinite loops + while (pMoving) + { + UTIL_SetMoveWithVelocity(pMoving, vecNew, MAX_MOVEWITH_DEPTH ); + pMoving = pMoving->m_pSiblingMoveWith; + sloopbreaker--; + if (sloopbreaker <= 0) + { + ALERT(at_error, "SetVelocity: Infinite sibling list for MoveWith!\n"); + break; + } + } + } + + pEnt->pev->velocity = vecNew; +} + +//LRC - one more MoveWith utility. This one's for the simple version of RotWith. +void UTIL_SetMoveWithAvelocity ( CBaseEntity *pEnt, const Vector vecSet, int loopbreaker ) +{ + if (loopbreaker <= 0) + { + ALERT(at_error, "Infinite child list for MoveWith!"); + return; + } + + if (!pEnt->m_pMoveWith) + { + ALERT(at_error,"SetMoveWithAvelocity: no MoveWith entity!?\n"); + return; + } + + Vector vecNew = (pEnt->pev->avelocity - pEnt->m_pMoveWith->pev->avelocity) + vecSet; + +// ALERT(at_console, "Setting Child AVelocity %f %f %f, was %f %f %f mw %f %f %f\n", vecNew.x, vecNew.y, vecNew.z, pEnt->pev->avelocity.x, pEnt->pev->avelocity.y, pEnt->pev->avelocity.z, pEnt->m_pMoveWith->pev->avelocity.x, pEnt->m_pMoveWith->pev->avelocity.y, pEnt->m_pMoveWith->pev->avelocity.z); + + if ( pEnt->m_pChildMoveWith ) + { + CBaseEntity *pMoving = pEnt->m_pChildMoveWith; + int sloopbreaker = MAX_MOVEWITH_DEPTH; // to prevent the game hanging... + while (pMoving) + { + UTIL_SetMoveWithAvelocity(pMoving, vecNew, loopbreaker - 1 ); + pMoving = pMoving->m_pSiblingMoveWith; + sloopbreaker--; + if (sloopbreaker <= 0) + { + ALERT(at_error, "SetMoveWithVelocity: Infinite sibling list for MoveWith!"); + break; + } + } + } + + //UTIL_SetDesiredAvelocity(pEnt, vecNew); + pEnt->pev->avelocity = vecNew; +} + +void UTIL_SetAvelocity ( CBaseEntity *pEnt, const Vector vecSet ) +{ + Vector vecNew; + if (pEnt->m_pMoveWith) + vecNew = vecSet + pEnt->m_pMoveWith->pev->avelocity; + else + vecNew = vecSet; + +// ALERT(at_console, "Setting AVelocity %f %f %f\n", vecNew.x, vecNew.y, vecNew.z); + + if ( pEnt->m_pChildMoveWith ) + { + CBaseEntity *pMoving = pEnt->m_pChildMoveWith; + int sloopbreaker = MAX_MOVEWITH_DEPTH; // LRC - to save us from infinite loops + while (pMoving) + { + UTIL_SetMoveWithAvelocity(pMoving, vecNew, MAX_MOVEWITH_DEPTH ); + pMoving = pMoving->m_pSiblingMoveWith; + sloopbreaker--; + if (sloopbreaker <= 0) + { + ALERT(at_error, "SetAvelocity: Infinite sibling list for MoveWith!\n"); + break; + } + } + } + //UTIL_SetDesiredAvelocity(pEnt, vecNew); + pEnt->pev->avelocity = vecNew; +} diff --git a/dlls/movewith.h b/dlls/movewith.h new file mode 100644 index 0000000..7f6961d --- /dev/null +++ b/dlls/movewith.h @@ -0,0 +1,44 @@ +#define LF_NOTEASY (1<<0) +#define LF_NOTMEDIUM (1<<1) +#define LF_NOTHARD (1<<2) + +#define LF_POSTASSISTVEL (1<<3) +#define LF_POSTASSISTAVEL (1<<4) + +#define LF_DOASSIST (1<<5) +#define LF_CORRECTSPEED (1<<6) + +#define LF_DODESIRED (1<<7) +#define LF_DESIRED_THINK (1<<8) +#define LF_DESIRED_POSTASSIST (1<<9) + +#define LF_DESIRED_INFO (1<<10) +#define LF_DESIRED_ACTION (1<<11) + +#define LF_ALIASLIST (1<<12) + +// an entity must have one of these flags set in order to be in the AssistList +#define LF_ASSISTLIST (LF_DOASSIST|LF_DODESIRED) + +extern void CheckDesiredList( void ); +extern void CheckAssistList ( void ); + +extern void UTIL_DesiredAction ( CBaseEntity *pEnt ); +extern void UTIL_DesiredThink ( CBaseEntity *pEnt ); +extern void UTIL_DesiredInfo ( CBaseEntity *pEnt ); +extern void UTIL_DesiredPostAssist ( CBaseEntity *pEnt ); + +extern void UTIL_AddToAssistList ( CBaseEntity *pEnt ); + + +extern void UTIL_MarkForAssist ( CBaseEntity *pEnt, BOOL correctSpeed ); + +extern void UTIL_AssignOrigin ( CBaseEntity* pEntity, const Vector vecOrigin ); +extern void UTIL_AssignOrigin ( CBaseEntity* pEntity, const Vector vecOrigin, BOOL bInitiator ); +extern void UTIL_SetVelocity ( CBaseEntity *pEnt, const Vector vecSet ); +extern void UTIL_SetAngles ( CBaseEntity* pEntity, const Vector vecAngles ); +extern void UTIL_SetAngles ( CBaseEntity* pEntity, const Vector vecAngles, BOOL bInitiator ); +extern void UTIL_SetAvelocity ( CBaseEntity *pEnt, const Vector vecSet ); + +extern void UTIL_SetMoveWithVelocity( CBaseEntity *pEnt, const Vector vecSet, int loopbreaker ); +extern void UTIL_SetMoveWithAvelocity( CBaseEntity *pEnt, const Vector vecSet, int loopbreaker ); diff --git a/dlls/multiplay_gamerules.cpp b/dlls/multiplay_gamerules.cpp new file mode 100644 index 0000000..b8db623 --- /dev/null +++ b/dlls/multiplay_gamerules.cpp @@ -0,0 +1,1667 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// teamplay_gamerules.cpp +// +#include + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" + +#include "skill.h" +#include "game.h" +#include "items.h" +#include "voice_gamemgr.h" +#include "hltv.h" + +extern DLL_GLOBAL CGameRules *g_pGameRules; +extern DLL_GLOBAL BOOL g_fGameOver; +extern int gmsgDeathMsg; // client dll messages +extern int gmsgScoreInfo; +extern int gmsgMOTD; +extern int gmsgServerName; + +extern int g_teamplay; + +#define ITEM_RESPAWN_TIME 30 +#define WEAPON_RESPAWN_TIME 20 +#define AMMO_RESPAWN_TIME 20 + +float g_flIntermissionStartTime = 0; + +CVoiceGameMgr g_VoiceGameMgr; + +class CMultiplayGameMgrHelper : public IVoiceGameMgrHelper +{ +public: + virtual bool CanPlayerHearPlayer(CBasePlayer *pListener, CBasePlayer *pTalker) + { + if ( g_teamplay ) + { + if ( g_pGameRules->PlayerRelationship( pListener, pTalker ) != GR_TEAMMATE ) + { + return false; + } + } + + return true; + } +}; +static CMultiplayGameMgrHelper g_GameMgrHelper; + +//********************************************************* +// Rules for the half-life multiplayer game. +//********************************************************* + +CHalfLifeMultiplay :: CHalfLifeMultiplay() +{ + g_VoiceGameMgr.Init(&g_GameMgrHelper, gpGlobals->maxClients); + + RefreshSkillData(); + m_flIntermissionEndTime = 0; + g_flIntermissionStartTime = 0; + + // 11/8/98 + // Modified by YWB: Server .cfg file is now a cvar, so that + // server ops can run multiple game servers, with different server .cfg files, + // from a single installed directory. + // Mapcyclefile is already a cvar. + + // 3/31/99 + // Added lservercfg file cvar, since listen and dedicated servers should not + // share a single config file. (sjb) + if ( IS_DEDICATED_SERVER() ) + { + // dedicated server + char *servercfgfile = (char *)CVAR_GET_STRING( "servercfgfile" ); + + if ( servercfgfile && servercfgfile[0] ) + { + char szCommand[256]; + + ALERT( at_debug, "Executing dedicated server config file\n" ); + sprintf( szCommand, "exec %s\n", servercfgfile ); + SERVER_COMMAND( szCommand ); + } + } + else + { + // listen server + char *lservercfgfile = (char *)CVAR_GET_STRING( "lservercfgfile" ); + + if ( lservercfgfile && lservercfgfile[0] ) + { + char szCommand[256]; + + ALERT( at_debug, "Executing listen server config file\n" ); + sprintf( szCommand, "exec %s\n", lservercfgfile ); + SERVER_COMMAND( szCommand ); + } + } +} + +BOOL CHalfLifeMultiplay::ClientCommand( CBasePlayer *pPlayer, const char *pcmd ) +{ + if(g_VoiceGameMgr.ClientCommand(pPlayer, pcmd)) + return TRUE; + + return CGameRules::ClientCommand(pPlayer, pcmd); +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay::RefreshSkillData( void ) +{ +// load all default values + CGameRules::RefreshSkillData(); + +// override some values for multiplay. + + // suitcharger + gSkillData.suitchargerCapacity = 30; + + // Crowbar whack + gSkillData.plrDmgCrowbar = 25; + + // Glock Round + gSkillData.plrDmg9MM = 12; + + // MP5 Round + gSkillData.plrDmgMP5 = 12; + + // M203 grenade + gSkillData.plrDmgM203Grenade = 100; + + // Shotgun buckshot + gSkillData.plrDmgBuckshot = 20;// fewer pellets in deathmatch + + // RPG + gSkillData.plrDmgRPG = 120; + + // Hand Grendade + gSkillData.plrDmgHandGrenade = 100; +} + +// longest the intermission can last, in seconds +#define MAX_INTERMISSION_TIME 120 + +extern cvar_t timeleft, fragsleft; + +extern cvar_t mp_chattime; + +//========================================================= +//========================================================= +void CHalfLifeMultiplay :: Think ( void ) +{ + g_VoiceGameMgr.Update(gpGlobals->frametime); + + ///// Check game rules ///// + static int last_frags; + static int last_time; + + int frags_remaining = 0; + int time_remaining = 0; + + if ( g_fGameOver ) // someone else quit the game already + { + // bounds check + int time = (int)CVAR_GET_FLOAT( "mp_chattime" ); + if ( time < 1 ) + CVAR_SET_STRING( "mp_chattime", "1" ); + else if ( time > MAX_INTERMISSION_TIME ) + CVAR_SET_STRING( "mp_chattime", UTIL_dtos1( MAX_INTERMISSION_TIME ) ); + + m_flIntermissionEndTime = g_flIntermissionStartTime + mp_chattime.value; + + // check to see if we should change levels now + if ( m_flIntermissionEndTime < gpGlobals->time ) + { + if ( m_iEndIntermissionButtonHit // check that someone has pressed a key, or the max intermission time is over + || ( ( g_flIntermissionStartTime + MAX_INTERMISSION_TIME ) < gpGlobals->time) ) + ChangeLevel(); // intermission is over + } + + return; + } + + float flTimeLimit = timelimit.value * 60; + float flFragLimit = fraglimit.value; + + time_remaining = (int)(flTimeLimit ? ( flTimeLimit - gpGlobals->time ) : 0); + + if ( flTimeLimit != 0 && gpGlobals->time >= flTimeLimit ) + { + GoToIntermission(); + return; + } + + if ( flFragLimit ) + { + int bestfrags = 9999; + int remain; + + // check if any player is over the frag limit + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + if ( pPlayer && pPlayer->pev->frags >= flFragLimit ) + { + GoToIntermission(); + return; + } + + + if ( pPlayer ) + { + remain = flFragLimit - pPlayer->pev->frags; + if ( remain < bestfrags ) + { + bestfrags = remain; + } + } + + } + frags_remaining = bestfrags; + } + + // Updates when frags change + if ( frags_remaining != last_frags ) + { + g_engfuncs.pfnCvar_DirectSet( &fragsleft, UTIL_VarArgs( "%i", frags_remaining ) ); + } + + // Updates once per second + if ( timeleft.value != last_time ) + { + g_engfuncs.pfnCvar_DirectSet( &timeleft, UTIL_VarArgs( "%i", time_remaining ) ); + } + + last_frags = frags_remaining; + last_time = time_remaining; +} + + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsMultiplayer( void ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsDeathmatch( void ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsCoOp( void ) +{ + return gpGlobals->coop; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ + if ( !pWeapon->CanDeploy() ) + { + // that weapon can't deploy anyway. + return FALSE; + } + + if ( !pPlayer->m_pActiveItem ) + { + // player doesn't have an active item! + return TRUE; + } + + if ( !pPlayer->m_pActiveItem->CanHolster() ) + { + // can't put away the active item. + return FALSE; + } + + if ( pWeapon->iWeight() > pPlayer->m_pActiveItem->iWeight() ) + { + return TRUE; + } + + return FALSE; +} + +BOOL CHalfLifeMultiplay :: GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) +{ + + CBasePlayerItem *pCheck; + CBasePlayerItem *pBest;// this will be used in the event that we don't find a weapon in the same category. + int iBestWeight; + int i; + + iBestWeight = -1;// no weapon lower than -1 can be autoswitched to + pBest = NULL; + + if ( !pCurrentWeapon->CanHolster() ) + { + // can't put this gun away right now, so can't switch. + return FALSE; + } + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + pCheck = pPlayer->m_rgpPlayerItems[ i ]; + + while ( pCheck ) + { + if ( pCheck->iWeight() > -1 && pCheck->iWeight() == pCurrentWeapon->iWeight() && pCheck != pCurrentWeapon ) + { + // this weapon is from the same category. + if ( pCheck->CanDeploy() ) + { + if ( pPlayer->SwitchWeapon( pCheck ) ) + { + return TRUE; + } + } + } + else if ( pCheck->iWeight() > iBestWeight && pCheck != pCurrentWeapon )// don't reselect the weapon we're trying to get rid of + { + //ALERT ( at_console, "Considering %s\n", STRING( pCheck->pev->classname ) ); + // we keep updating the 'best' weapon just in case we can't find a weapon of the same weight + // that the player was using. This will end up leaving the player with his heaviest-weighted + // weapon. + if ( pCheck->CanDeploy() ) + { + // if this weapon is useable, flag it as the best + iBestWeight = pCheck->iWeight(); + pBest = pCheck; + } + } + + pCheck = pCheck->m_pNext; + } + } + + // if we make it here, we've checked all the weapons and found no useable + // weapon in the same catagory as the current weapon. + + // if pBest is null, we didn't find ANYTHING. Shouldn't be possible- should always + // at least get the crowbar, but ya never know. + if ( !pBest ) + { + return FALSE; + } + + pPlayer->SwitchWeapon( pBest ); + + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay :: ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) +{ + g_VoiceGameMgr.ClientConnected(pEntity); + return TRUE; +} + +extern int gmsgSayText; +extern int gmsgGameMode; + +void CHalfLifeMultiplay :: UpdateGameMode( CBasePlayer *pPlayer ) +{ + MESSAGE_BEGIN( MSG_ONE, gmsgGameMode, NULL, pPlayer->edict() ); + WRITE_BYTE( 0 ); // game mode none + MESSAGE_END(); +} + +void CHalfLifeMultiplay :: InitHUD( CBasePlayer *pl ) +{ + // notify other clients of player joining the game + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, UTIL_VarArgs( "%s has joined the game\n", + ( pl->pev->netname && STRING(pl->pev->netname)[0] != 0 ) ? STRING(pl->pev->netname) : "unconnected" ) ); + + // team match? + if ( g_teamplay ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" entered the game\n", + STRING( pl->pev->netname ), + GETPLAYERUSERID( pl->edict() ), + GETPLAYERAUTHID( pl->edict() ), + g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pl->edict() ), "model" ) ); + } + else + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" entered the game\n", + STRING( pl->pev->netname ), + GETPLAYERUSERID( pl->edict() ), + GETPLAYERAUTHID( pl->edict() ), + GETPLAYERUSERID( pl->edict() ) ); + } + + UpdateGameMode( pl ); + + // sending just one score makes the hud scoreboard active; otherwise + // it is just disabled for single play + MESSAGE_BEGIN( MSG_ONE, gmsgScoreInfo, NULL, pl->edict() ); + WRITE_BYTE( ENTINDEX(pl->edict()) ); + WRITE_SHORT( 0 ); + WRITE_SHORT( 0 ); + WRITE_SHORT( 0 ); + WRITE_SHORT( 0 ); + MESSAGE_END(); + + SendMOTDToClient( pl->edict() ); + + // loop through all active players and send their score info to the new client + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + // FIXME: Probably don't need to cast this just to read m_iDeaths + CBasePlayer *plr = (CBasePlayer *)UTIL_PlayerByIndex( i ); + + if ( plr ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgScoreInfo, NULL, pl->edict() ); + WRITE_BYTE( i ); // client number + WRITE_SHORT( plr->pev->frags ); + WRITE_SHORT( plr->m_iDeaths ); + WRITE_SHORT( 0 ); + WRITE_SHORT( GetTeamIndex( plr->m_szTeamName ) + 1 ); + MESSAGE_END(); + } + } + + if ( g_fGameOver ) + { + MESSAGE_BEGIN( MSG_ONE, SVC_INTERMISSION, NULL, pl->edict() ); + MESSAGE_END(); + } +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay :: ClientDisconnected( edict_t *pClient ) +{ + if ( pClient ) + { + CBasePlayer *pPlayer = (CBasePlayer *)CBaseEntity::Instance( pClient ); + + if ( pPlayer ) + { + FireTargets( "game_playerleave", pPlayer, pPlayer, USE_TOGGLE, 0 ); + + // team match? + if ( g_teamplay ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" disconnected\n", + STRING( pPlayer->pev->netname ), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model" ) ); + } + else + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" disconnected\n", + STRING( pPlayer->pev->netname ), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + GETPLAYERUSERID( pPlayer->edict() ) ); + } + + pPlayer->RemoveAllItems( TRUE );// destroy all of the players weapons and items + } + } +} + +//========================================================= +//========================================================= +float CHalfLifeMultiplay :: FlPlayerFallDamage( CBasePlayer *pPlayer ) +{ + int iFallDamage = (int)falldamage.value; + + switch ( iFallDamage ) + { + case 1://progressive + pPlayer->m_flFallVelocity -= PLAYER_MAX_SAFE_FALL_SPEED; + return pPlayer->m_flFallVelocity * DAMAGE_FOR_FALL_SPEED; + break; + default: + case 0:// fixed + return 10; + break; + } +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay :: PlayerThink( CBasePlayer *pPlayer ) +{ + if ( g_fGameOver ) + { + // check for button presses + if ( pPlayer->m_afButtonPressed & ( IN_DUCK | IN_ATTACK | IN_ATTACK2 | IN_USE | IN_JUMP ) ) + m_iEndIntermissionButtonHit = TRUE; + + // clear attack/use commands from player + pPlayer->m_afButtonPressed = 0; + pPlayer->pev->button = 0; + pPlayer->m_afButtonReleased = 0; + } +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay :: PlayerSpawn( CBasePlayer *pPlayer ) +{ + BOOL addDefault; + CBaseEntity *pWeaponEntity = NULL; + + pPlayer->m_iHideHUD |= ITEM_SUIT; + + addDefault = TRUE; + + while ( pWeaponEntity = UTIL_FindEntityByClassname( pWeaponEntity, "game_player_equip" )) + { + pWeaponEntity->Touch( pPlayer ); + addDefault = FALSE; + } + + if ( addDefault ) + { + pPlayer->GiveNamedItem( "weapon_knife" ); + pPlayer->GiveNamedItem( "weapon_glock" ); + pPlayer->GiveAmmo( 68, "barret" );// 4 full reloads + } +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay :: FPlayerCanRespawn( CBasePlayer *pPlayer ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +float CHalfLifeMultiplay :: FlPlayerSpawnTime( CBasePlayer *pPlayer ) +{ + return gpGlobals->time;//now! +} + +BOOL CHalfLifeMultiplay :: AllowAutoTargetCrosshair( void ) +{ + return ( aimcrosshair.value != 0 ); +} + +//========================================================= +// IPointsForKill - how many points awarded to anyone +// that kills this player? +//========================================================= +int CHalfLifeMultiplay :: IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) +{ + return 1; +} + + +//========================================================= +// PlayerKilled - someone/something killed this player +//========================================================= +void CHalfLifeMultiplay :: PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) +{ + DeathNotice( pVictim, pKiller, pInflictor ); + + pVictim->m_iDeaths += 1; + + + FireTargets( "game_playerdie", pVictim, pVictim, USE_TOGGLE, 0 ); + CBasePlayer *peKiller = NULL; + CBaseEntity *ktmp = CBaseEntity::Instance( pKiller ); + if ( ktmp && (ktmp->Classify() == CLASS_PLAYER) ) + peKiller = (CBasePlayer*)ktmp; + + if ( pVictim->pev == pKiller ) + { // killed self + pKiller->frags -= 1; + } + else if ( ktmp && ktmp->IsPlayer() ) + { + // if a player dies in a deathmatch game and the killer is a client, award the killer some points + pKiller->frags += IPointsForKill( peKiller, pVictim ); + + FireTargets( "game_playerkill", ktmp, ktmp, USE_TOGGLE, 0 ); + } + else + { // killed by the world +//MH pKiller->frags -= 1; this should be victim (we don't want to give the world frags) + pVictim->pev->frags -= 1; +//END + } + + // update the scores + // killed scores + MESSAGE_BEGIN( MSG_ALL, gmsgScoreInfo ); + WRITE_BYTE( ENTINDEX(pVictim->edict()) ); + WRITE_SHORT( pVictim->pev->frags ); + WRITE_SHORT( pVictim->m_iDeaths ); + WRITE_SHORT( 0 ); + WRITE_SHORT( GetTeamIndex( pVictim->m_szTeamName ) + 1 ); + MESSAGE_END(); + + // killers score, if it's a player + CBaseEntity *ep = CBaseEntity::Instance( pKiller ); + if ( ep && ep->Classify() == CLASS_PLAYER ) + { + CBasePlayer *PK = (CBasePlayer*)ep; + + MESSAGE_BEGIN( MSG_ALL, gmsgScoreInfo ); + WRITE_BYTE( ENTINDEX(PK->edict()) ); + WRITE_SHORT( PK->pev->frags ); + WRITE_SHORT( PK->m_iDeaths ); + WRITE_SHORT( 0 ); + WRITE_SHORT( GetTeamIndex( PK->m_szTeamName) + 1 ); + MESSAGE_END(); + + // let the killer paint another decal as soon as he'd like. + PK->m_flNextDecalTime = gpGlobals->time; + } +} + +//========================================================= +// Deathnotice. +//========================================================= +void CHalfLifeMultiplay::DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pevInflictor ) +{ + // Work out what killed the player, and send a message to all clients about it + CBaseEntity *Killer = CBaseEntity::Instance( pKiller ); + + const char *killer_weapon_name = "world"; // by default, the player is killed by the world + int killer_index = 0; + + if ( pKiller->flags & FL_CLIENT ) + { + killer_index = ENTINDEX(ENT(pKiller)); + + if ( pevInflictor ) + { + if ( pevInflictor == pKiller ) + { + // If the inflictor is the killer, then it must be their current weapon doing the damage + CBasePlayer *pPlayer = (CBasePlayer*)CBaseEntity::Instance( pKiller ); + + if ( pPlayer->m_pActiveItem ) + { + killer_weapon_name = pPlayer->m_pActiveItem->pszName(); + } + } + else + { + killer_weapon_name = STRING( pevInflictor->classname ); // it's just that easy + } + } + } + else + { + killer_weapon_name = STRING( pevInflictor->classname ); + } + + // strip the monster_* or weapon_* from the inflictor's classname + if ( strncmp( killer_weapon_name, "weapon_", 7 ) == 0 ) + killer_weapon_name += 7; + else if ( strncmp( killer_weapon_name, "monster_", 8 ) == 0 ) + killer_weapon_name += 8; + else if ( strncmp( killer_weapon_name, "func_", 5 ) == 0 ) + killer_weapon_name += 5; + + MESSAGE_BEGIN( MSG_ALL, gmsgDeathMsg ); + WRITE_BYTE( killer_index ); // the killer + WRITE_BYTE( ENTINDEX(pVictim->edict()) ); // the victim + WRITE_STRING( killer_weapon_name ); // what they were killed by (should this be a string?) + MESSAGE_END(); + + if ( pVictim->pev == pKiller ) + { + // killed self + + // team match? + if ( g_teamplay ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" committed suicide with \"%s\"\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pVictim->edict() ), "model" ), + killer_weapon_name ); + } + else + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" committed suicide with \"%s\"\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name ); + } + } + else if ( pKiller->flags & FL_CLIENT ) + { + // team match? + if ( g_teamplay ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" killed \"%s<%i><%s><%s>\" with \"%s\"\n", + STRING( pKiller->netname ), + GETPLAYERUSERID( ENT(pKiller) ), + GETPLAYERAUTHID( ENT(pKiller) ), + g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( ENT(pKiller) ), "model" ), + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pVictim->edict() ), "model" ), + killer_weapon_name ); + } + else + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" killed \"%s<%i><%s><%i>\" with \"%s\"\n", + STRING( pKiller->netname ), + GETPLAYERUSERID( ENT(pKiller) ), + GETPLAYERAUTHID( ENT(pKiller) ), + GETPLAYERUSERID( ENT(pKiller) ), + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name ); + } + } + else + { + // killed by the world + + // team match? + if ( g_teamplay ) + { + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" committed suicide with \"%s\" (world)\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pVictim->edict() ), "model" ), + killer_weapon_name ); + } + else + { + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" committed suicide with \"%s\" (world)\n", + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name ); + } + } + + MESSAGE_BEGIN( MSG_SPEC, SVC_DIRECTOR ); + WRITE_BYTE ( 9 ); // command length in bytes + WRITE_BYTE ( DRC_CMD_EVENT ); // player killed + WRITE_SHORT( ENTINDEX(pVictim->edict()) ); // index number of primary entity + if (pevInflictor) + WRITE_SHORT( ENTINDEX(ENT(pevInflictor)) ); // index number of secondary entity + else + WRITE_SHORT( ENTINDEX(ENT(pKiller)) ); // index number of secondary entity + WRITE_LONG( 7 | DRC_FLAG_DRAMATIC); // eventflags (priority and flags) + MESSAGE_END(); + +// Print a standard message + // TODO: make this go direct to console + return; // just remove for now +/* + char szText[ 128 ]; + + if ( pKiller->flags & FL_MONSTER ) + { + // killed by a monster + strcpy ( szText, STRING( pVictim->pev->netname ) ); + strcat ( szText, " was killed by a monster.\n" ); + return; + } + + if ( pKiller == pVictim->pev ) + { + strcpy ( szText, STRING( pVictim->pev->netname ) ); + strcat ( szText, " commited suicide.\n" ); + } + else if ( pKiller->flags & FL_CLIENT ) + { + strcpy ( szText, STRING( pKiller->netname ) ); + + strcat( szText, " : " ); + strcat( szText, killer_weapon_name ); + strcat( szText, " : " ); + + strcat ( szText, STRING( pVictim->pev->netname ) ); + strcat ( szText, "\n" ); + } + else if ( FClassnameIs ( pKiller, "worldspawn" ) ) + { + strcpy ( szText, STRING( pVictim->pev->netname ) ); + strcat ( szText, " fell or drowned or something.\n" ); + } + else if ( pKiller->solid == SOLID_BSP ) + { + strcpy ( szText, STRING( pVictim->pev->netname ) ); + strcat ( szText, " was mooshed.\n" ); + } + else + { + strcpy ( szText, STRING( pVictim->pev->netname ) ); + strcat ( szText, " died mysteriously.\n" ); + } + + UTIL_ClientPrintAll( szText ); +*/ +} + +//========================================================= +// PlayerGotWeapon - player has grabbed a weapon that was +// sitting in the world +//========================================================= +void CHalfLifeMultiplay :: PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ +} + +//========================================================= +// FlWeaponRespawnTime - what is the time in the future +// at which this weapon may spawn? +//========================================================= +float CHalfLifeMultiplay :: FlWeaponRespawnTime( CBasePlayerItem *pWeapon ) +{ + if ( weaponstay.value > 0 ) + { + // make sure it's only certain weapons + if ( !(pWeapon->iFlags() & ITEM_FLAG_LIMITINWORLD) ) + { + return gpGlobals->time + 0; // weapon respawns almost instantly + } + } + + return gpGlobals->time + WEAPON_RESPAWN_TIME; +} + +// when we are within this close to running out of entities, items +// marked with the ITEM_FLAG_LIMITINWORLD will delay their respawn +#define ENTITY_INTOLERANCE 100 + +//========================================================= +// FlWeaponRespawnTime - Returns 0 if the weapon can respawn +// now, otherwise it returns the time at which it can try +// to spawn again. +//========================================================= +float CHalfLifeMultiplay :: FlWeaponTryRespawn( CBasePlayerItem *pWeapon ) +{ + if ( pWeapon && pWeapon->m_iId && (pWeapon->iFlags() & ITEM_FLAG_LIMITINWORLD) ) + { + if ( NUMBER_OF_ENTITIES() < (gpGlobals->maxEntities - ENTITY_INTOLERANCE) ) + return 0; + + // we're past the entity tolerance level, so delay the respawn + return FlWeaponRespawnTime( pWeapon ); + } + + return 0; +} + +//========================================================= +// VecWeaponRespawnSpot - where should this weapon spawn? +// Some game variations may choose to randomize spawn locations +//========================================================= +Vector CHalfLifeMultiplay :: VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ) +{ + return pWeapon->pev->origin; +} + +//========================================================= +// WeaponShouldRespawn - any conditions inhibiting the +// respawning of this weapon? +//========================================================= +int CHalfLifeMultiplay :: WeaponShouldRespawn( CBasePlayerItem *pWeapon ) +{ + if ( pWeapon->pev->spawnflags & SF_NORESPAWN ) + { + return GR_WEAPON_RESPAWN_NO; + } + + return GR_WEAPON_RESPAWN_YES; +} + +//========================================================= +// CanHaveWeapon - returns FALSE if the player is not allowed +// to pick up this weapon +//========================================================= +BOOL CHalfLifeMultiplay::CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pItem ) +{ + if ( weaponstay.value > 0 ) + { + if ( pItem->iFlags() & ITEM_FLAG_LIMITINWORLD ) + return CGameRules::CanHavePlayerItem( pPlayer, pItem ); + + // check if the player already has this weapon + for ( int i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + CBasePlayerItem *it = pPlayer->m_rgpPlayerItems[i]; + + while ( it != NULL ) + { + if ( it->m_iId == pItem->m_iId ) + { + return FALSE; + } + + it = it->m_pNext; + } + } + } + + return CGameRules::CanHavePlayerItem( pPlayer, pItem ); +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay::PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ) +{ +} + +//========================================================= +//========================================================= +int CHalfLifeMultiplay::ItemShouldRespawn( CItem *pItem ) +{ + if ( pItem->pev->spawnflags & SF_NORESPAWN ) + { + return GR_ITEM_RESPAWN_NO; + } + + return GR_ITEM_RESPAWN_YES; +} + + +//========================================================= +// At what time in the future may this Item respawn? +//========================================================= +float CHalfLifeMultiplay::FlItemRespawnTime( CItem *pItem ) +{ + return gpGlobals->time + ITEM_RESPAWN_TIME; +} + +//========================================================= +// Where should this item respawn? +// Some game variations may choose to randomize spawn locations +//========================================================= +Vector CHalfLifeMultiplay::VecItemRespawnSpot( CItem *pItem ) +{ + return pItem->pev->origin; +} + +//========================================================= +//========================================================= +void CHalfLifeMultiplay::PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ) +{ +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay::IsAllowedToSpawn( CBaseEntity *pEntity ) +{ +// if ( pEntity->pev->flags & FL_MONSTER ) +// return FALSE; + + return TRUE; +} + +//========================================================= +//========================================================= +int CHalfLifeMultiplay::AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ) +{ + if ( pAmmo->pev->spawnflags & SF_NORESPAWN ) + { + return GR_AMMO_RESPAWN_NO; + } + + return GR_AMMO_RESPAWN_YES; +} + +//========================================================= +//========================================================= +float CHalfLifeMultiplay::FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ) +{ + return gpGlobals->time + AMMO_RESPAWN_TIME; +} + +//========================================================= +//========================================================= +Vector CHalfLifeMultiplay::VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ) +{ + return pAmmo->pev->origin; +} + +//========================================================= +//========================================================= +float CHalfLifeMultiplay::FlHealthChargerRechargeTime( void ) +{ + return 60; +} + + +float CHalfLifeMultiplay::FlHEVChargerRechargeTime( void ) +{ + return 30; +} + +//========================================================= +//========================================================= +int CHalfLifeMultiplay::DeadPlayerWeapons( CBasePlayer *pPlayer ) +{ + return GR_PLR_DROP_GUN_ACTIVE; +} + +//========================================================= +//========================================================= +int CHalfLifeMultiplay::DeadPlayerAmmo( CBasePlayer *pPlayer ) +{ + return GR_PLR_DROP_AMMO_ACTIVE; +} + +edict_t *CHalfLifeMultiplay::GetPlayerSpawnSpot( CBasePlayer *pPlayer ) +{ + edict_t *pentSpawnSpot = CGameRules::GetPlayerSpawnSpot( pPlayer ); + if ( IsMultiplayer() && pentSpawnSpot->v.target ) + { + FireTargets( STRING(pentSpawnSpot->v.target), pPlayer, pPlayer, USE_TOGGLE, 0 ); + } + + return pentSpawnSpot; +} + + +//========================================================= +//========================================================= +int CHalfLifeMultiplay::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) +{ + // half life deathmatch has only enemies + return GR_NOTTEAMMATE; +} + +BOOL CHalfLifeMultiplay :: PlayFootstepSounds( CBasePlayer *pl, float fvol ) +{ + if ( g_footsteps && g_footsteps->value == 0 ) + return FALSE; + + if ( pl->IsOnLadder() || pl->pev->velocity.Length2D() > 220 ) + return TRUE; // only make step sounds in multiplayer if the player is moving fast enough + + return FALSE; +} + +BOOL CHalfLifeMultiplay :: FAllowFlashlight( void ) +{ + return flashlight.value != 0; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeMultiplay :: FAllowMonsters( void ) +{ + return ( allowmonsters.value != 0 ); +} + +//========================================================= +//======== CHalfLifeMultiplay private functions =========== +#define INTERMISSION_TIME 6 + +void CHalfLifeMultiplay :: GoToIntermission( void ) +{ + if ( g_fGameOver ) + return; // intermission has already been triggered, so ignore. + + MESSAGE_BEGIN(MSG_ALL, SVC_INTERMISSION); + MESSAGE_END(); + + // bounds check + int time = (int)CVAR_GET_FLOAT( "mp_chattime" ); + if ( time < 1 ) + CVAR_SET_STRING( "mp_chattime", "1" ); + else if ( time > MAX_INTERMISSION_TIME ) + CVAR_SET_STRING( "mp_chattime", UTIL_dtos1( MAX_INTERMISSION_TIME ) ); + + m_flIntermissionEndTime = gpGlobals->time + ( (int)mp_chattime.value ); + g_flIntermissionStartTime = gpGlobals->time; + + g_fGameOver = TRUE; + m_iEndIntermissionButtonHit = FALSE; +} + +#define MAX_RULE_BUFFER 1024 + +typedef struct mapcycle_item_s +{ + struct mapcycle_item_s *next; + + char mapname[ 32 ]; + int minplayers, maxplayers; + char rulebuffer[ MAX_RULE_BUFFER ]; +} mapcycle_item_t; + +typedef struct mapcycle_s +{ + struct mapcycle_item_s *items; + struct mapcycle_item_s *next_item; +} mapcycle_t; + +/* +============== +DestroyMapCycle + +Clean up memory used by mapcycle when switching it +============== +*/ +void DestroyMapCycle( mapcycle_t *cycle ) +{ + mapcycle_item_t *p, *n, *start; + p = cycle->items; + if ( p ) + { + start = p; + p = p->next; + while ( p != start ) + { + n = p->next; + delete p; + p = n; + } + + delete cycle->items; + } + cycle->items = NULL; + cycle->next_item = NULL; +} + +char com_token[ 1500 ]; + +/* +============== +COM_Parse + +Parse a token out of a string +============== +*/ +char *COM_Parse (char *data) +{ + int c; + int len; + + len = 0; + com_token[0] = 0; + + if (!data) + return NULL; + +// skip whitespace +skipwhite: + while ( (c = *data) <= ' ') + { + if (c == 0) + return NULL; // end of file; + data++; + } + +// skip // comments + if (c=='/' && data[1] == '/') + { + while (*data && *data != '\n') + data++; + goto skipwhite; + } + + +// handle quoted strings specially + if (c == '\"') + { + data++; + while (1) + { + c = *data++; + if (c=='\"' || !c) + { + com_token[len] = 0; + return data; + } + com_token[len] = c; + len++; + } + } + +// parse single characters + if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c == ',' ) + { + com_token[len] = c; + len++; + com_token[len] = 0; + return data+1; + } + +// parse a regular word + do + { + com_token[len] = c; + data++; + len++; + c = *data; + if (c=='{' || c=='}'|| c==')'|| c=='(' || c=='\'' || c == ',' ) + break; + } while (c>32); + + com_token[len] = 0; + return data; +} + +/* +============== +COM_TokenWaiting + +Returns 1 if additional data is waiting to be processed on this line +============== +*/ +int COM_TokenWaiting( char *buffer ) +{ + char *p; + + p = buffer; + while ( *p && *p!='\n') + { + if ( !isspace( *p ) || isalnum( *p ) ) + return 1; + + p++; + } + + return 0; +} + + + +/* +============== +ReloadMapCycleFile + + +Parses mapcycle.txt file into mapcycle_t structure +============== +*/ +int ReloadMapCycleFile( char *filename, mapcycle_t *cycle ) +{ + char szBuffer[ MAX_RULE_BUFFER ]; + char szMap[ 32 ]; + int length; + char *pFileList; + char *aFileList = pFileList = (char*)LOAD_FILE( filename, &length ); + int hasbuffer; + mapcycle_item_s *item, *newlist = NULL, *next; + + if ( pFileList && length ) + { + // the first map name in the file becomes the default + while ( 1 ) + { + hasbuffer = 0; + memset( szBuffer, 0, MAX_RULE_BUFFER ); + + pFileList = COM_Parse( pFileList ); + if ( strlen( com_token ) <= 0 ) + break; + + strcpy( szMap, com_token ); + + // Any more tokens on this line? + if ( COM_TokenWaiting( pFileList ) ) + { + pFileList = COM_Parse( pFileList ); + if ( strlen( com_token ) > 0 ) + { + hasbuffer = 1; + strcpy( szBuffer, com_token ); + } + } + + // Check map + if ( IS_MAP_VALID( szMap ) ) + { + // Create entry + char *s; + + item = new mapcycle_item_s; + + strcpy( item->mapname, szMap ); + + item->minplayers = 0; + item->maxplayers = 0; + + memset( item->rulebuffer, 0, MAX_RULE_BUFFER ); + + if ( hasbuffer ) + { + s = g_engfuncs.pfnInfoKeyValue( szBuffer, "minplayers" ); + if ( s && s[0] ) + { + item->minplayers = atoi( s ); + item->minplayers = max( item->minplayers, 0 ); + item->minplayers = min( item->minplayers, gpGlobals->maxClients ); + } + s = g_engfuncs.pfnInfoKeyValue( szBuffer, "maxplayers" ); + if ( s && s[0] ) + { + item->maxplayers = atoi( s ); + item->maxplayers = max( item->maxplayers, 0 ); + item->maxplayers = min( item->maxplayers, gpGlobals->maxClients ); + } + + // Remove keys + // + g_engfuncs.pfnInfo_RemoveKey( szBuffer, "minplayers" ); + g_engfuncs.pfnInfo_RemoveKey( szBuffer, "maxplayers" ); + + strcpy( item->rulebuffer, szBuffer ); + } + + item->next = cycle->items; + cycle->items = item; + } + else + { + ALERT( at_console, "Skipping %s from mapcycle, not a valid map\n", szMap ); + } + + } + + FREE_FILE( aFileList ); + } + + // Fixup circular list pointer + item = cycle->items; + + // Reverse it to get original order + while ( item ) + { + next = item->next; + item->next = newlist; + newlist = item; + item = next; + } + cycle->items = newlist; + item = cycle->items; + + // Didn't parse anything + if ( !item ) + { + return 0; + } + + while ( item->next ) + { + item = item->next; + } + item->next = cycle->items; + + cycle->next_item = item->next; + + return 1; +} + +/* +============== +CountPlayers + +Determine the current # of active players on the server for map cycling logic +============== +*/ +int CountPlayers( void ) +{ + int num = 0; + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pEnt = UTIL_PlayerByIndex( i ); + + if ( pEnt ) + { + num = num + 1; + } + } + + return num; +} + +/* +============== +ExtractCommandString + +Parse commands/key value pairs to issue right after map xxx command is issued on server + level transition +============== +*/ +void ExtractCommandString( char *s, char *szCommand ) +{ + // Now make rules happen + char pkey[512]; + char value[512]; // use two buffers so compares + // work without stomping on each other + char *o; + + if ( *s == '\\' ) + s++; + + while (1) + { + o = pkey; + while ( *s != '\\' ) + { + if ( !*s ) + return; + *o++ = *s++; + } + *o = 0; + s++; + + o = value; + + while (*s != '\\' && *s) + { + if (!*s) + return; + *o++ = *s++; + } + *o = 0; + + strcat( szCommand, pkey ); + if ( strlen( value ) > 0 ) + { + strcat( szCommand, " " ); + strcat( szCommand, value ); + } + strcat( szCommand, "\n" ); + + if (!*s) + return; + s++; + } +} + +/* +============== +ChangeLevel + +Server is changing to a new level, check mapcycle.txt for map name and setup info +============== +*/ +void CHalfLifeMultiplay :: ChangeLevel( void ) +{ + static char szPreviousMapCycleFile[ 256 ]; + static mapcycle_t mapcycle; + + char szNextMap[32]; + char szFirstMapInList[32]; + char szCommands[ 1500 ]; + char szRules[ 1500 ]; + int minplayers = 0, maxplayers = 0; + strcpy( szFirstMapInList, "hldm1" ); // the absolute default level is hldm1 + + int curplayers; + BOOL do_cycle = TRUE; + + // find the map to change to + char *mapcfile = (char*)CVAR_GET_STRING( "mapcyclefile" ); + ASSERT( mapcfile != NULL ); + + szCommands[ 0 ] = '\0'; + szRules[ 0 ] = '\0'; + + curplayers = CountPlayers(); + + // Has the map cycle filename changed? + if ( stricmp( mapcfile, szPreviousMapCycleFile ) ) + { + strcpy( szPreviousMapCycleFile, mapcfile ); + + DestroyMapCycle( &mapcycle ); + + if ( !ReloadMapCycleFile( mapcfile, &mapcycle ) || ( !mapcycle.items ) ) + { + ALERT( at_console, "Unable to load map cycle file %s\n", mapcfile ); + do_cycle = FALSE; + } + } + + if ( do_cycle && mapcycle.items ) + { + BOOL keeplooking = FALSE; + BOOL found = FALSE; + mapcycle_item_s *item; + + // Assume current map + strcpy( szNextMap, STRING(gpGlobals->mapname) ); + strcpy( szFirstMapInList, STRING(gpGlobals->mapname) ); + + // Traverse list + for ( item = mapcycle.next_item; item->next != mapcycle.next_item; item = item->next ) + { + keeplooking = FALSE; + + ASSERT( item != NULL ); + + if ( item->minplayers != 0 ) + { + if ( curplayers >= item->minplayers ) + { + found = TRUE; + minplayers = item->minplayers; + } + else + { + keeplooking = TRUE; + } + } + + if ( item->maxplayers != 0 ) + { + if ( curplayers <= item->maxplayers ) + { + found = TRUE; + maxplayers = item->maxplayers; + } + else + { + keeplooking = TRUE; + } + } + + if ( keeplooking ) + continue; + + found = TRUE; + break; + } + + if ( !found ) + { + item = mapcycle.next_item; + } + + // Increment next item pointer + mapcycle.next_item = item->next; + + // Perform logic on current item + strcpy( szNextMap, item->mapname ); + + ExtractCommandString( item->rulebuffer, szCommands ); + strcpy( szRules, item->rulebuffer ); + } + + if ( !IS_MAP_VALID(szNextMap) ) + { + strcpy( szNextMap, szFirstMapInList ); + } + + g_fGameOver = TRUE; + + ALERT( at_console, "CHANGE LEVEL: %s\n", szNextMap ); + if ( minplayers || maxplayers ) + { + ALERT( at_console, "PLAYER COUNT: min %i max %i current %i\n", minplayers, maxplayers, curplayers ); + } + if ( strlen( szRules ) > 0 ) + { + ALERT( at_console, "RULES: %s\n", szRules ); + } + + CHANGE_LEVEL( szNextMap, NULL ); + if ( strlen( szCommands ) > 0 ) + { + SERVER_COMMAND( szCommands ); + } +} + +#define MAX_MOTD_CHUNK 60 +#define MAX_MOTD_LENGTH 1536 // (MAX_MOTD_CHUNK * 4) + +void CHalfLifeMultiplay :: SendMOTDToClient( edict_t *client ) +{ + // read from the MOTD.txt file + int length, char_count = 0; + char *pFileList; + char *aFileList = pFileList = (char*)LOAD_FILE( (char *)CVAR_GET_STRING( "motdfile" ), &length ); + + // send the server name + MESSAGE_BEGIN( MSG_ONE, gmsgServerName, NULL, client ); + WRITE_STRING( CVAR_GET_STRING("hostname") ); + MESSAGE_END(); + + // Send the message of the day + // read it chunk-by-chunk, and send it in parts + + while ( pFileList && *pFileList && char_count < MAX_MOTD_LENGTH ) + { + char chunk[MAX_MOTD_CHUNK+1]; + + if ( strlen( pFileList ) < MAX_MOTD_CHUNK ) + { + strcpy( chunk, pFileList ); + } + else + { + strncpy( chunk, pFileList, MAX_MOTD_CHUNK ); + chunk[MAX_MOTD_CHUNK] = 0; // strncpy doesn't always append the null terminator + } + + char_count += strlen( chunk ); + if ( char_count < MAX_MOTD_LENGTH ) + pFileList = aFileList + char_count; + else + *pFileList = 0; + + MESSAGE_BEGIN( MSG_ONE, gmsgMOTD, NULL, client ); + WRITE_BYTE( *pFileList ? FALSE : TRUE ); // FALSE means there is still more message to come + WRITE_STRING( chunk ); + MESSAGE_END(); + } + + FREE_FILE( aFileList ); +} + + diff --git a/dlls/nihilanth.cpp b/dlls/nihilanth.cpp new file mode 100644 index 0000000..b134736 --- /dev/null +++ b/dlls/nihilanth.cpp @@ -0,0 +1,1845 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "effects.h" + +#define N_SCALE 15 +#define N_SPHERES 20 + +class CNihilanth : public CBaseMonster +{ +public: + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + int Classify( void ) { return CLASS_ALIEN_MILITARY; }; + int BloodColor( void ) { return BLOOD_COLOR_YELLOW; } + void Killed( entvars_t *pevAttacker, int iGib ); + void GibMonster( void ); + + void SetObjectCollisionBox( void ) + { + pev->absmin = pev->origin + Vector( -16 * N_SCALE, -16 * N_SCALE, -48 * N_SCALE ); + pev->absmax = pev->origin + Vector( 16 * N_SCALE, 16 * N_SCALE, 28 * N_SCALE ); + } + + void HandleAnimEvent( MonsterEvent_t *pEvent ); + + void EXPORT StartupThink( void ); + void EXPORT HuntThink( void ); + void EXPORT CrashTouch( CBaseEntity *pOther ); + void EXPORT DyingThink( void ); + void EXPORT StartupUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT NullThink( void ); + void EXPORT CommandUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + void FloatSequence( void ); + void NextActivity( void ); + + void Flight( void ); + + BOOL AbsorbSphere( void ); + BOOL EmitSphere( void ); + void TargetSphere( USE_TYPE useType, float value ); + CBaseEntity *RandomTargetname( const char *szName ); + void ShootBalls( void ); + void MakeFriend( Vector vecPos ); + + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + + void PainSound( void ); + void DeathSound( void ); + + static const char *pAttackSounds[]; // vocalization: play sometimes when he launches an attack + static const char *pBallSounds[]; // the sound of the lightening ball launch + static const char *pShootSounds[]; // grunting vocalization: play sometimes when he launches an attack + static const char *pRechargeSounds[]; // vocalization: play when he recharges + static const char *pLaughSounds[]; // vocalization: play sometimes when hit and still has lots of health + static const char *pPainSounds[]; // vocalization: play sometimes when hit and has much less health and no more chargers + static const char *pDeathSounds[]; // vocalization: play as he dies + + // x_teleattack1.wav the looping sound of the teleport attack ball. + + float m_flForce; + + float m_flNextPainSound; + + Vector m_velocity; + Vector m_avelocity; + + Vector m_vecTarget; + Vector m_posTarget; + + Vector m_vecDesired; + Vector m_posDesired; + + float m_flMinZ; + float m_flMaxZ; + + Vector m_vecGoal; + + float m_flLastSeen; + float m_flPrevSeen; + + int m_irritation; + + int m_iLevel; + int m_iTeleport; + + EHANDLE m_hRecharger; + + EHANDLE m_hSphere[N_SPHERES]; + int m_iActiveSpheres; + + float m_flAdj; + + CSprite *m_pBall; + + char m_szRechargerTarget[64]; + char m_szDrawUse[64]; + char m_szTeleportUse[64]; + char m_szTeleportTouch[64]; + char m_szDeadUse[64]; + char m_szDeadTouch[64]; + + float m_flShootEnd; + float m_flShootTime; + + EHANDLE m_hFriend[3]; +}; + +LINK_ENTITY_TO_CLASS( monster_nihilanth, CNihilanth ); + +TYPEDESCRIPTION CNihilanth::m_SaveData[] = +{ + DEFINE_FIELD( CNihilanth, m_flForce, FIELD_FLOAT ), + DEFINE_FIELD( CNihilanth, m_flNextPainSound, FIELD_TIME ), + DEFINE_FIELD( CNihilanth, m_velocity, FIELD_VECTOR ), + DEFINE_FIELD( CNihilanth, m_avelocity, FIELD_VECTOR ), + DEFINE_FIELD( CNihilanth, m_vecTarget, FIELD_VECTOR ), + DEFINE_FIELD( CNihilanth, m_posTarget, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CNihilanth, m_vecDesired, FIELD_VECTOR ), + DEFINE_FIELD( CNihilanth, m_posDesired, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CNihilanth, m_flMinZ, FIELD_FLOAT ), + DEFINE_FIELD( CNihilanth, m_flMaxZ, FIELD_FLOAT ), + DEFINE_FIELD( CNihilanth, m_vecGoal, FIELD_VECTOR ), + DEFINE_FIELD( CNihilanth, m_flLastSeen, FIELD_TIME ), + DEFINE_FIELD( CNihilanth, m_flPrevSeen, FIELD_TIME ), + DEFINE_FIELD( CNihilanth, m_irritation, FIELD_INTEGER ), + DEFINE_FIELD( CNihilanth, m_iLevel, FIELD_INTEGER ), + DEFINE_FIELD( CNihilanth, m_iTeleport, FIELD_INTEGER ), + DEFINE_FIELD( CNihilanth, m_hRecharger, FIELD_EHANDLE ), + DEFINE_ARRAY( CNihilanth, m_hSphere, FIELD_EHANDLE, N_SPHERES ), + DEFINE_FIELD( CNihilanth, m_iActiveSpheres, FIELD_INTEGER ), + DEFINE_FIELD( CNihilanth, m_flAdj, FIELD_FLOAT ), + DEFINE_FIELD( CNihilanth, m_pBall, FIELD_CLASSPTR ), + DEFINE_ARRAY( CNihilanth, m_szRechargerTarget, FIELD_CHARACTER, 64 ), + DEFINE_ARRAY( CNihilanth, m_szDrawUse, FIELD_CHARACTER, 64 ), + DEFINE_ARRAY( CNihilanth, m_szTeleportUse, FIELD_CHARACTER, 64 ), + DEFINE_ARRAY( CNihilanth, m_szTeleportTouch, FIELD_CHARACTER, 64 ), + DEFINE_ARRAY( CNihilanth, m_szDeadUse, FIELD_CHARACTER, 64 ), + DEFINE_ARRAY( CNihilanth, m_szDeadTouch, FIELD_CHARACTER, 64 ), + DEFINE_FIELD( CNihilanth, m_flShootEnd, FIELD_TIME ), + DEFINE_FIELD( CNihilanth, m_flShootTime, FIELD_TIME ), + DEFINE_ARRAY( CNihilanth, m_hFriend, FIELD_EHANDLE, 3 ), +}; + +IMPLEMENT_SAVERESTORE( CNihilanth, CBaseMonster ); + +class CNihilanthHVR : public CBaseMonster +{ +public: + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + + void CircleInit( CBaseEntity *pTarget ); + void AbsorbInit( void ); + void TeleportInit( CNihilanth *pOwner, CBaseEntity *pEnemy, CBaseEntity *pTarget, CBaseEntity *pTouch ); + void GreenBallInit( void ); + void ZapInit( CBaseEntity *pEnemy ); + + void EXPORT HoverThink( void ); + BOOL CircleTarget( Vector vecTarget ); + void EXPORT DissipateThink( void ); + + void EXPORT ZapThink( void ); + void EXPORT TeleportThink( void ); + void EXPORT TeleportTouch( CBaseEntity *pOther ); + + void EXPORT RemoveTouch( CBaseEntity *pOther ); + void EXPORT BounceTouch( CBaseEntity *pOther ); + void EXPORT ZapTouch( CBaseEntity *pOther ); + + CBaseEntity *RandomClassname( const char *szName ); + + // void EXPORT SphereUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + void MovetoTarget( Vector vecTarget ); + virtual void Crawl( void ); + + void Zap( void ); + void Teleport( void ); + + float m_flIdealVel; + Vector m_vecIdeal; + CNihilanth *m_pNihilanth; + EHANDLE m_hTouch; + int m_nFrames; +}; + +LINK_ENTITY_TO_CLASS( nihilanth_energy_ball, CNihilanthHVR ); + + +TYPEDESCRIPTION CNihilanthHVR::m_SaveData[] = +{ + DEFINE_FIELD( CNihilanthHVR, m_flIdealVel, FIELD_FLOAT ), + DEFINE_FIELD( CNihilanthHVR, m_vecIdeal, FIELD_VECTOR ), + DEFINE_FIELD( CNihilanthHVR, m_pNihilanth, FIELD_CLASSPTR ), + DEFINE_FIELD( CNihilanthHVR, m_hTouch, FIELD_EHANDLE ), + DEFINE_FIELD( CNihilanthHVR, m_nFrames, FIELD_INTEGER ), +}; + + +IMPLEMENT_SAVERESTORE( CNihilanthHVR, CBaseMonster ); + + +//========================================================= +// Nihilanth, final Boss monster +//========================================================= + +const char *CNihilanth::pAttackSounds[] = +{ + "X/x_attack1.wav", + "X/x_attack2.wav", + "X/x_attack3.wav", +}; + +const char *CNihilanth::pBallSounds[] = +{ + "X/x_ballattack1.wav", +}; + +const char *CNihilanth::pShootSounds[] = +{ + "X/x_shoot1.wav", +}; + +const char *CNihilanth::pRechargeSounds[] = +{ + "X/x_recharge1.wav", + "X/x_recharge2.wav", + "X/x_recharge3.wav", +}; + +const char *CNihilanth::pLaughSounds[] = +{ + "X/x_laugh1.wav", + "X/x_laugh2.wav", +}; + +const char *CNihilanth::pPainSounds[] = +{ + "X/x_pain1.wav", + "X/x_pain2.wav", +}; + +const char *CNihilanth::pDeathSounds[] = +{ + "X/x_die1.wav", +}; + + +void CNihilanth :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(edict(), "models/nihilanth.mdl"); + // UTIL_SetSize(pev, Vector( -300, -300, 0), Vector(300, 300, 512)); + UTIL_SetSize(pev, Vector( -32, -32, 0), Vector(32, 32, 64)); + UTIL_SetOrigin( this, pev->origin ); + + pev->flags |= FL_MONSTER; + pev->takedamage = DAMAGE_AIM; + if (pev->health == 0) + pev->health = gSkillData.nihilanthHealth; + pev->view_ofs = Vector( 0, 0, 300 ); + + m_flFieldOfView = -1; // 360 degrees + + pev->sequence = 0; + ResetSequenceInfo( ); + + InitBoneControllers(); + + SetThink(&CNihilanth :: StartupThink ); + SetNextThink( 0.1 ); + + m_vecDesired = Vector( 1, 0, 0 ); + m_posDesired = Vector( pev->origin.x, pev->origin.y, 512 ); + + m_iLevel = 1; + m_iTeleport = 1; + + if (m_szRechargerTarget[0] == '\0') strcpy( m_szRechargerTarget, "n_recharger" ); + if (m_szDrawUse[0] == '\0') strcpy( m_szDrawUse, "n_draw" ); + if (m_szTeleportUse[0] == '\0') strcpy( m_szTeleportUse, "n_leaving" ); + if (m_szTeleportTouch[0] == '\0') strcpy( m_szTeleportTouch, "n_teleport" ); + if (m_szDeadUse[0] == '\0') strcpy( m_szDeadUse, "n_dead" ); + if (m_szDeadTouch[0] == '\0') strcpy( m_szDeadTouch, "n_ending" ); + + // near death + /* + m_iTeleport = 10; + m_iLevel = 10; + m_irritation = 2; + pev->health = 100; + */ +} + + +void CNihilanth::Precache( void ) +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/nihilanth.mdl"); + PRECACHE_MODEL("sprites/lgtning.spr"); + UTIL_PrecacheOther( "nihilanth_energy_ball" ); + UTIL_PrecacheOther( "monster_alien_controller" ); + UTIL_PrecacheOther( "monster_alien_slave" ); + + PRECACHE_SOUND_ARRAY( pAttackSounds ); + PRECACHE_SOUND_ARRAY( pBallSounds ); + PRECACHE_SOUND_ARRAY( pShootSounds ); + PRECACHE_SOUND_ARRAY( pRechargeSounds ); + PRECACHE_SOUND_ARRAY( pLaughSounds ); + PRECACHE_SOUND_ARRAY( pPainSounds ); + PRECACHE_SOUND_ARRAY( pDeathSounds ); + PRECACHE_SOUND("debris/beamstart7.wav"); +} + + + +void CNihilanth :: PainSound( void ) +{ + if (m_flNextPainSound > gpGlobals->time) + return; + + m_flNextPainSound = gpGlobals->time + RANDOM_FLOAT( 2, 5 ); + + if (pev->health > gSkillData.nihilanthHealth / 2) + { + EMIT_SOUND( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY( pLaughSounds ), 1.0, 0.2 ); + } + else if (m_irritation >= 2) + { + EMIT_SOUND( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY( pPainSounds ), 1.0, 0.2 ); + } +} + +void CNihilanth :: DeathSound( void ) +{ + EMIT_SOUND( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY( pDeathSounds ), 1.0, 0.1 ); +} + + +void CNihilanth::NullThink( void ) +{ + StudioFrameAdvance( ); + SetNextThink( 0.5 ); +} + + +void CNihilanth::StartupUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink(&CNihilanth:: HuntThink ); + SetNextThink( 0.1 ); + SetUse(&CNihilanth:: CommandUse ); +} + + +void CNihilanth::StartupThink( void ) +{ + m_irritation = 0; + m_flAdj = 512; + + CBaseEntity *pEntity; + + pEntity = UTIL_FindEntityByTargetname( NULL, "n_min"); + if (pEntity) + m_flMinZ = pEntity->pev->origin.z; + else + m_flMinZ = -4096; + + pEntity = UTIL_FindEntityByTargetname( NULL, "n_max"); + if (pEntity) + m_flMaxZ = pEntity->pev->origin.z; + else + m_flMaxZ = 4096; + + m_hRecharger = this; + for (int i = 0; i < N_SPHERES; i++) + { + EmitSphere( ); + } + m_hRecharger = NULL; + + SetThink(&CNihilanth:: HuntThink); + SetUse(&CNihilanth:: CommandUse ); + SetNextThink( 0.1 ); +} + + +void CNihilanth :: Killed( entvars_t *pevAttacker, int iGib ) +{ + CBaseMonster::Killed( pevAttacker, iGib ); +} + +void CNihilanth :: DyingThink( void ) +{ + SetNextThink( 0.1 ); + DispatchAnimEvents( ); + StudioFrameAdvance( ); + + if (pev->deadflag == DEAD_NO) + { + DeathSound( ); + pev->deadflag = DEAD_DYING; + + m_posDesired.z = m_flMaxZ; + } + + if (pev->deadflag == DEAD_DYING) + { + Flight( ); + + if (fabs( pev->origin.z - m_flMaxZ ) < 16) + { + pev->velocity = Vector( 0, 0, 0 ); + FireTargets( m_szDeadUse, this, this, USE_ON, 1.0 ); + pev->deadflag = DEAD_DEAD; + } + } + + if (m_fSequenceFinished) + { + pev->avelocity.y += RANDOM_FLOAT( -100, 100 ); + if (pev->avelocity.y < -100) + pev->avelocity.y = -100; + if (pev->avelocity.y > 100) + pev->avelocity.y = 100; + + pev->sequence = LookupSequence( "die1" ); + } + + if (m_pBall) + { + if (m_pBall->pev->renderamt > 0) + { + m_pBall->pev->renderamt = max( 0, m_pBall->pev->renderamt - 2); + } + else + { + UTIL_Remove( m_pBall ); + m_pBall = NULL; + } + } + + Vector vecDir, vecSrc, vecAngles; + + UTIL_MakeAimVectors( pev->angles ); + int iAttachment = RANDOM_LONG( 1, 4 ); + + do { + vecDir = Vector( RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 )); + } while (DotProduct( vecDir, vecDir) > 1.0); + + switch( RANDOM_LONG( 1, 4 )) + { + case 1: // head + vecDir.z = fabs( vecDir.z ) * 0.5; + vecDir = vecDir + 2 * gpGlobals->v_up; + break; + case 2: // eyes + if (DotProduct( vecDir, gpGlobals->v_forward ) < 0) + vecDir = vecDir * -1; + + vecDir = vecDir + 2 * gpGlobals->v_forward; + break; + case 3: // left hand + if (DotProduct( vecDir, gpGlobals->v_right ) > 0) + vecDir = vecDir * -1; + vecDir = vecDir - 2 * gpGlobals->v_right; + break; + case 4: // right hand + if (DotProduct( vecDir, gpGlobals->v_right ) < 0) + vecDir = vecDir * -1; + vecDir = vecDir + 2 * gpGlobals->v_right; + break; + } + + GetAttachment( iAttachment - 1, vecSrc, vecAngles ); + + TraceResult tr; + + UTIL_TraceLine( vecSrc, vecSrc + vecDir * 4096, ignore_monsters, ENT(pev), &tr ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( entindex() + 0x1000 * iAttachment ); + WRITE_COORD( tr.vecEndPos.x); + WRITE_COORD( tr.vecEndPos.y); + WRITE_COORD( tr.vecEndPos.z); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 5 ); // life + WRITE_BYTE( 100 ); // width + WRITE_BYTE( 120 ); // noise + WRITE_BYTE( 64 ); // r, g, b + WRITE_BYTE( 128 ); // r, g, b + WRITE_BYTE( 255); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); + + GetAttachment( 0, vecSrc, vecAngles ); + CNihilanthHVR *pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); + pEntity->pev->velocity = Vector ( RANDOM_FLOAT( -0.7, 0.7 ), RANDOM_FLOAT( -0.7, 0.7 ), 1.0 ) * 600.0; + pEntity->GreenBallInit( ); + + return; +} + + + +void CNihilanth::CrashTouch( CBaseEntity *pOther ) +{ + // only crash if we hit something solid + if ( pOther->pev->solid == SOLID_BSP) + { + SetTouch( NULL ); + SetNextThink( 0 ); + } +} + + + +void CNihilanth :: GibMonster( void ) +{ + // EMIT_SOUND_DYN(edict(), CHAN_VOICE, "common/bodysplat.wav", 0.75, ATTN_NORM, 0, 200); +} + + + +void CNihilanth :: FloatSequence( void ) +{ + if (m_irritation >= 2) + { + pev->sequence = LookupSequence( "float_open" ); + } + else if (m_avelocity.y > 30) + { + pev->sequence = LookupSequence( "walk_r" ); + } + else if (m_avelocity.y < -30) + { + pev->sequence = LookupSequence( "walk_l" ); + } + else if (m_velocity.z > 30) + { + pev->sequence = LookupSequence( "walk_u" ); + } + else if (m_velocity.z < -30) + { + pev->sequence = LookupSequence( "walk_d" ); + } + else + { + pev->sequence = LookupSequence( "float" ); + } +} + + +void CNihilanth :: ShootBalls( void ) +{ + if (m_flShootEnd > gpGlobals->time) + { + Vector vecHand, vecAngle; + + while (m_flShootTime < m_flShootEnd && m_flShootTime < gpGlobals->time) + { + if (m_hEnemy != NULL) + { + Vector vecSrc, vecDir; + CNihilanthHVR *pEntity; + + GetAttachment( 2, vecHand, vecAngle ); + vecSrc = vecHand + pev->velocity * (m_flShootTime - gpGlobals->time); + // vecDir = (m_posTarget - vecSrc).Normalize( ); + vecDir = (m_posTarget - pev->origin).Normalize( ); + vecSrc = vecSrc + vecDir * (gpGlobals->time - m_flShootTime); + pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); + pEntity->pev->velocity = vecDir * 200.0; + pEntity->ZapInit( m_hEnemy ); + + GetAttachment( 3, vecHand, vecAngle ); + vecSrc = vecHand + pev->velocity * (m_flShootTime - gpGlobals->time); + // vecDir = (m_posTarget - vecSrc).Normalize( ); + vecDir = (m_posTarget - pev->origin).Normalize( ); + vecSrc = vecSrc + vecDir * (gpGlobals->time - m_flShootTime); + pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); + pEntity->pev->velocity = vecDir * 200.0; + pEntity->ZapInit( m_hEnemy ); + } + m_flShootTime += 0.2; + } + } +} + + +void CNihilanth :: MakeFriend( Vector vecStart ) +{ + int i; + + for (i = 0; i < 3; i++) + { + if (m_hFriend[i] != NULL && !m_hFriend[i]->IsAlive()) + { + if (pev->rendermode == kRenderNormal) // don't do it if they are already fading + m_hFriend[i]->MyMonsterPointer()->FadeMonster( ); + m_hFriend[i] = NULL; + } + + if (m_hFriend[i] == NULL) + { + if (RANDOM_LONG(0, 1) == 0) + { + int iNode = WorldGraph.FindNearestNode ( vecStart, bits_NODE_AIR ); + if (iNode != NO_NODE) + { + CNode &node = WorldGraph.Node( iNode ); + TraceResult tr; + UTIL_TraceHull( node.m_vecOrigin + Vector( 0, 0, 32 ), node.m_vecOrigin + Vector( 0, 0, 32 ), dont_ignore_monsters, large_hull, NULL, &tr ); + if (tr.fStartSolid == 0) + m_hFriend[i] = Create("monster_alien_controller", node.m_vecOrigin, pev->angles ); + } + } + else + { + int iNode = WorldGraph.FindNearestNode ( vecStart, bits_NODE_LAND | bits_NODE_WATER ); + if (iNode != NO_NODE) + { + CNode &node = WorldGraph.Node( iNode ); + TraceResult tr; + UTIL_TraceHull( node.m_vecOrigin + Vector( 0, 0, 36 ), node.m_vecOrigin + Vector( 0, 0, 36 ), dont_ignore_monsters, human_hull, NULL, &tr ); + if (tr.fStartSolid == 0) + m_hFriend[i] = Create("monster_alien_slave", node.m_vecOrigin, pev->angles ); + } + } + if (m_hFriend[i] != NULL) + { + EMIT_SOUND( m_hFriend[i]->edict(), CHAN_WEAPON, "debris/beamstart7.wav", 1.0, ATTN_NORM ); + } + + return; + } + } +} + + +void CNihilanth :: NextActivity( ) +{ + UTIL_MakeAimVectors( pev->angles ); + + if (m_irritation >= 2) + { + if (m_pBall == NULL) + { + m_pBall = CSprite::SpriteCreate( "sprites/tele1.spr", pev->origin, TRUE ); + if (m_pBall) + { + m_pBall->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation ); + m_pBall->SetAttachment( edict(), 1 ); + m_pBall->SetScale( 4.0 ); + m_pBall->pev->framerate = 10.0; + m_pBall->TurnOn( ); + } + } + + if (m_pBall) + { + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x1000 ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( 256 ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 192 ); // G + WRITE_BYTE( 64 ); // B + WRITE_BYTE( 200 ); // life * 10 + WRITE_COORD( 0 ); // decay + MESSAGE_END(); + } + } + + if ((pev->health < gSkillData.nihilanthHealth / 2 || m_iActiveSpheres < N_SPHERES / 2) && m_hRecharger == NULL && m_iLevel <= 9) + { + char szName[64]; + + CBaseEntity *pEnt = NULL; + CBaseEntity *pRecharger = NULL; + float flDist = 8192; + + sprintf(szName, "%s%d", m_szRechargerTarget, m_iLevel ); + + while ((pEnt = UTIL_FindEntityByTargetname( pEnt, szName )) != NULL) + { + float flLocal = (pEnt->pev->origin - pev->origin).Length(); + if (flLocal < flDist) + { + flDist = flLocal; + pRecharger = pEnt; + } + } + + if (pRecharger) + { + m_hRecharger = pRecharger; + m_posDesired = Vector( pev->origin.x, pev->origin.y, pRecharger->pev->origin.z ); + m_vecDesired = (pRecharger->pev->origin - m_posDesired).Normalize( ); + m_vecDesired.z = 0; + m_vecDesired = m_vecDesired.Normalize(); + } + else + { + m_hRecharger = NULL; + ALERT( at_aiconsole, "nihilanth can't find %s\n", szName ); + m_iLevel++; + if (m_iLevel > 9) + m_irritation = 2; + } + } + + float flDist = (m_posDesired - pev->origin).Length(); + float flDot = DotProduct( m_vecDesired, gpGlobals->v_forward ); + + if (m_hRecharger != NULL) + { + // at we at power up yet? + if (flDist < 128.0) + { + int iseq = LookupSequence( "recharge" ); + + if (iseq != pev->sequence) + { + char szText[64]; + + sprintf( szText, "%s%d", m_szDrawUse, m_iLevel ); + FireTargets( szText, this, this, USE_ON, 1.0 ); + + ALERT( at_debug, "fireing %s\n", szText ); + } + pev->sequence = LookupSequence( "recharge" ); + } + else + { + FloatSequence( ); + } + return; + } + + if (m_hEnemy != NULL && !m_hEnemy->IsAlive()) + { + m_hEnemy = NULL; + } + + if (m_flLastSeen + 15 < gpGlobals->time) + { + m_hEnemy = NULL; + } + + if (m_hEnemy == NULL) + { + Look( 4096 ); + m_hEnemy = BestVisibleEnemy( ); + } + + if (m_hEnemy != NULL && m_irritation != 0) + { + if (m_flLastSeen + 5 > gpGlobals->time && flDist < 256 && flDot > 0) + { + if (m_irritation >= 2 && pev->health < gSkillData.nihilanthHealth / 2.0) + { + pev->sequence = LookupSequence( "attack1_open" ); + } + else + { + if (RANDOM_LONG(0, 1 ) == 0) + { + pev->sequence = LookupSequence( "attack1" ); // zap + } + else + { + char szText[64]; + + sprintf( szText, "%s%d", m_szTeleportTouch, m_iTeleport ); + CBaseEntity *pTouch = UTIL_FindEntityByTargetname( NULL, szText ); + + sprintf( szText, "%s%d", m_szTeleportUse, m_iTeleport ); + CBaseEntity *pTrigger = UTIL_FindEntityByTargetname( NULL, szText ); + + if (pTrigger != NULL || pTouch != NULL) + { + pev->sequence = LookupSequence( "attack2" ); // teleport + } + else + { + m_iTeleport++; + pev->sequence = LookupSequence( "attack1" ); // zap + } + } + } + return; + } + } + + FloatSequence( ); +} + +void CNihilanth :: HuntThink( void ) +{ + SetNextThink( 0.1 ); + DispatchAnimEvents( ); + StudioFrameAdvance( ); + + ShootBalls( ); + + // if dead, force cancelation of current animation + if (pev->health <= 0) + { + SetThink(&CNihilanth :: DyingThink ); + m_fSequenceFinished = TRUE; + return; + } + + // ALERT( at_console, "health %.0f\n", pev->health ); + + // if damaged, try to abosorb some spheres + if (pev->health < gSkillData.nihilanthHealth && AbsorbSphere( )) + { + pev->health += gSkillData.nihilanthHealth / N_SPHERES; + } + + // get new sequence + if (m_fSequenceFinished) + { + // if (!m_fSequenceLoops) + pev->frame = 0; + NextActivity( ); + ResetSequenceInfo( ); + pev->framerate = 2.0 - 1.0 * (pev->health / gSkillData.nihilanthHealth); + } + + // look for current enemy + if (m_hEnemy != NULL && m_hRecharger == NULL) + { + if (FVisible( m_hEnemy )) + { + if (m_flLastSeen < gpGlobals->time - 5) + m_flPrevSeen = gpGlobals->time; + m_flLastSeen = gpGlobals->time; + m_posTarget = m_hEnemy->pev->origin; + m_vecTarget = (m_posTarget - pev->origin).Normalize(); + m_vecDesired = m_vecTarget; + m_posDesired = Vector( pev->origin.x, pev->origin.y, m_posTarget.z + m_flAdj ); + } + else + { + m_flAdj = min( m_flAdj + 10, 1000 ); + } + } + + // don't go too high + if (m_posDesired.z > m_flMaxZ) + m_posDesired.z = m_flMaxZ; + + // don't go too low + if (m_posDesired.z < m_flMinZ) + m_posDesired.z = m_flMinZ; + + Flight( ); +} + + + +void CNihilanth :: Flight( void ) +{ + // estimate where I'll be facing in one seconds + UTIL_MakeAimVectors( pev->angles + m_avelocity ); + // Vector vecEst1 = pev->origin + m_velocity + gpGlobals->v_up * m_flForce - Vector( 0, 0, 384 ); + // float flSide = DotProduct( m_posDesired - vecEst1, gpGlobals->v_right ); + + float flSide = DotProduct( m_vecDesired, gpGlobals->v_right ); + + if (flSide < 0) + { + if (m_avelocity.y < 180) + { + m_avelocity.y += 6; // 9 * (3.0/2.0); + } + } + else + { + if (m_avelocity.y > -180) + { + m_avelocity.y -= 6; // 9 * (3.0/2.0); + } + } + m_avelocity.y *= 0.98; + + // estimate where I'll be in two seconds + Vector vecEst = pev->origin + m_velocity * 2.0 + gpGlobals->v_up * m_flForce * 20; + + // add immediate force + UTIL_MakeAimVectors( pev->angles ); + m_velocity.x += gpGlobals->v_up.x * m_flForce; + m_velocity.y += gpGlobals->v_up.y * m_flForce; + m_velocity.z += gpGlobals->v_up.z * m_flForce; + + + float flSpeed = m_velocity.Length(); + float flDir = DotProduct( Vector( gpGlobals->v_forward.x, gpGlobals->v_forward.y, 0 ), Vector( m_velocity.x, m_velocity.y, 0 ) ); + if (flDir < 0) + flSpeed = -flSpeed; + + float flDist = DotProduct( m_posDesired - vecEst, gpGlobals->v_forward ); + + // sideways drag + m_velocity.x = m_velocity.x * (1.0 - fabs( gpGlobals->v_right.x ) * 0.05); + m_velocity.y = m_velocity.y * (1.0 - fabs( gpGlobals->v_right.y ) * 0.05); + m_velocity.z = m_velocity.z * (1.0 - fabs( gpGlobals->v_right.z ) * 0.05); + + // general drag + m_velocity = m_velocity * 0.995; + + // apply power to stay correct height + if (m_flForce < 100 && vecEst.z < m_posDesired.z) + { + m_flForce += 10; + } + else if (m_flForce > -100 && vecEst.z > m_posDesired.z) + { + if (vecEst.z > m_posDesired.z) + m_flForce -= 10; + } + + UTIL_SetOrigin( this, pev->origin + m_velocity * 0.1 ); + pev->angles = pev->angles + m_avelocity * 0.1; + + // ALERT( at_console, "%5.0f %5.0f : %4.0f : %3.0f : %2.0f\n", m_posDesired.z, pev->origin.z, m_velocity.z, m_avelocity.y, m_flForce ); +} + + +BOOL CNihilanth :: AbsorbSphere( void ) +{ + for (int i = 0; i < N_SPHERES; i++) + { + if (m_hSphere[i] != NULL) + { + CNihilanthHVR *pSphere = (CNihilanthHVR *)((CBaseEntity *)m_hSphere[i]); + pSphere->AbsorbInit( ); + m_hSphere[i] = NULL; + m_iActiveSpheres--; + return TRUE; + } + } + return FALSE; +} + + +BOOL CNihilanth :: EmitSphere( void ) +{ + m_iActiveSpheres = 0; + int empty = 0; + + for (int i = 0; i < N_SPHERES; i++) + { + if (m_hSphere[i] != NULL) + { + m_iActiveSpheres++; + } + else + { + empty = i; + } + } + + if (m_iActiveSpheres >= N_SPHERES) + return FALSE; + + Vector vecSrc = m_hRecharger->pev->origin; + CNihilanthHVR *pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); + pEntity->pev->velocity = pev->origin - vecSrc; + pEntity->CircleInit( this ); + + m_hSphere[empty] = pEntity; + return TRUE; +} + + +//LRC- never called(?) No. --PC. +void CNihilanth :: TargetSphere( USE_TYPE useType, float value ) +{ + CBaseMonster *pSphere; + int i = 0; + for (i = 0; i < N_SPHERES; i++) + { + if (m_hSphere[i] != NULL) + { + pSphere = m_hSphere[i]->MyMonsterPointer(); + if (pSphere->m_hEnemy == NULL) + break; + } + } + if (i == N_SPHERES) + { + return; + } + + Vector vecSrc, vecAngles; + GetAttachment( 2, vecSrc, vecAngles ); + UTIL_SetOrigin( pSphere, vecSrc ); + pSphere->Use( this, this, useType, value ); + pSphere->pev->velocity = m_vecDesired * RANDOM_FLOAT( 50, 100 ) + Vector( RANDOM_FLOAT( -50, 50 ), RANDOM_FLOAT( -50, 50 ), RANDOM_FLOAT( -50, 50 ) ); +} + + + +void CNihilanth :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case 1: // shoot + break; + case 2: // zen + if (m_hEnemy != NULL) + { + if (RANDOM_LONG(0,4) == 0) + EMIT_SOUND( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY( pAttackSounds ), 1.0, 0.2 ); + + EMIT_SOUND( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY( pBallSounds ), 1.0, 0.2 ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x3000 ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( 256 ); // radius + WRITE_BYTE( 128 ); // R + WRITE_BYTE( 128 ); // G + WRITE_BYTE( 255 ); // B + WRITE_BYTE( 10 ); // life * 10 + WRITE_COORD( 128 ); // decay + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x4000 ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( 256 ); // radius + WRITE_BYTE( 128 ); // R + WRITE_BYTE( 128 ); // G + WRITE_BYTE( 255 ); // B + WRITE_BYTE( 10 ); // life * 10 + WRITE_COORD( 128 ); // decay + MESSAGE_END(); + + m_flShootTime = gpGlobals->time; + m_flShootEnd = gpGlobals->time + 1.0; + } + break; + case 3: // prayer + if (m_hEnemy != NULL) + { + char szText[32]; + + sprintf( szText, "%s%d", m_szTeleportTouch, m_iTeleport ); + CBaseEntity *pTouch = UTIL_FindEntityByTargetname( NULL, szText ); + + sprintf( szText, "%s%d", m_szTeleportUse, m_iTeleport ); + CBaseEntity *pTrigger = UTIL_FindEntityByTargetname( NULL, szText ); + + if (pTrigger != NULL || pTouch != NULL) + { + EMIT_SOUND( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY( pAttackSounds ), 1.0, 0.2 ); + + Vector vecSrc, vecAngles; + GetAttachment( 2, vecSrc, vecAngles ); + CNihilanthHVR *pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); + pEntity->pev->velocity = pev->origin - vecSrc; + pEntity->TeleportInit( this, m_hEnemy, pTrigger, pTouch ); + } + else + { + m_iTeleport++; // unexpected failure + + EMIT_SOUND( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY( pBallSounds ), 1.0, 0.2 ); + + ALERT( at_aiconsole, "nihilanth can't target %s\n", szText ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x3000 ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( 256 ); // radius + WRITE_BYTE( 128 ); // R + WRITE_BYTE( 128 ); // G + WRITE_BYTE( 255 ); // B + WRITE_BYTE( 10 ); // life * 10 + WRITE_COORD( 128 ); // decay + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x4000 ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( 256 ); // radius + WRITE_BYTE( 128 ); // R + WRITE_BYTE( 128 ); // G + WRITE_BYTE( 255 ); // B + WRITE_BYTE( 10 ); // life * 10 + WRITE_COORD( 128 ); // decay + MESSAGE_END(); + + m_flShootTime = gpGlobals->time; + m_flShootEnd = gpGlobals->time + 1.0; + } + } + break; + case 4: // get a sphere + { + if (m_hRecharger != NULL) + { + if (!EmitSphere( )) + { + m_hRecharger = NULL; + } + } + } + break; + case 5: // start up sphere machine + { + EMIT_SOUND( edict(), CHAN_VOICE , RANDOM_SOUND_ARRAY( pRechargeSounds ), 1.0, 0.2 ); + } + break; + case 6: + if (m_hEnemy != NULL) + { + Vector vecSrc, vecAngles; + GetAttachment( 2, vecSrc, vecAngles ); + CNihilanthHVR *pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); + pEntity->pev->velocity = pev->origin - vecSrc; + pEntity->ZapInit( m_hEnemy ); + } + break; + case 7: + /* + Vector vecSrc, vecAngles; + GetAttachment( 0, vecSrc, vecAngles ); + CNihilanthHVR *pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); + pEntity->pev->velocity = Vector ( RANDOM_FLOAT( -0.7, 0.7 ), RANDOM_FLOAT( -0.7, 0.7 ), 1.0 ) * 600.0; + pEntity->GreenBallInit( ); + */ + break; + } +} + + + +void CNihilanth::CommandUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + switch (useType) + { + case USE_OFF: + { + CBaseEntity *pTouch = UTIL_FindEntityByTargetname( NULL, m_szDeadTouch ); + if ( pTouch && m_hEnemy != NULL ) + pTouch->Touch( m_hEnemy ); + } + break; + case USE_ON: + if (m_irritation == 0) + { + m_irritation = 1; + } + break; + case USE_SET: + break; + case USE_TOGGLE: + break; + } +} + + +int CNihilanth :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if (pevInflictor->owner == edict()) + return 0; + + if (flDamage >= pev->health) + { + pev->health = 1; + if (m_irritation != 3) + return 0; + } + + PainSound( ); + + pev->health -= flDamage; + return 0; +} + + + +void CNihilanth::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if (m_irritation == 3) + m_irritation = 2; + + if (m_irritation == 2 && ptr->iHitgroup == 2 && flDamage > 2) + m_irritation = 3; + + if (m_irritation != 3) + { + Vector vecBlood = (ptr->vecEndPos - pev->origin).Normalize( ); + + UTIL_BloodStream( ptr->vecEndPos, vecBlood, BloodColor(), flDamage + (100 - 100 * (pev->health / gSkillData.nihilanthHealth))); + } + + // SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage * 5.0);// a little surface blood. + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); +} + + + +CBaseEntity *CNihilanth::RandomTargetname( const char *szName ) +{ + int total = 0; + + CBaseEntity *pEntity = NULL; + CBaseEntity *pNewEntity = NULL; + while ((pNewEntity = UTIL_FindEntityByTargetname( pNewEntity, szName )) != NULL) + { + total++; + if (RANDOM_LONG(0,total-1) < 1) + pEntity = pNewEntity; + } + return pEntity; +} + + + + + + + + + +//========================================================= +// Controller bouncy ball attack +//========================================================= + + + +void CNihilanthHVR :: Spawn( void ) +{ + Precache( ); + + pev->rendermode = kRenderTransAdd; + pev->renderamt = 255; + pev->scale = 3.0; +} + + +void CNihilanthHVR :: Precache( void ) +{ + PRECACHE_MODEL("sprites/flare6.spr"); + PRECACHE_MODEL("sprites/nhth1.spr"); + PRECACHE_MODEL("sprites/exit1.spr"); + PRECACHE_MODEL("sprites/tele1.spr"); + PRECACHE_MODEL("sprites/animglow01.spr"); + PRECACHE_MODEL("sprites/xspark4.spr"); + PRECACHE_MODEL("sprites/muzzleflash3.spr"); + PRECACHE_SOUND("debris/zap4.wav"); + PRECACHE_SOUND("weapons/electro4.wav"); + PRECACHE_SOUND("x/x_teleattack1.wav"); +} + + + +void CNihilanthHVR :: CircleInit( CBaseEntity *pTarget ) +{ + pev->movetype = MOVETYPE_NOCLIP; + pev->solid = SOLID_NOT; + + // SET_MODEL(edict(), "sprites/flare6.spr"); + // pev->scale = 3.0; + // SET_MODEL(edict(), "sprites/xspark4.spr"); + SET_MODEL(edict(), "sprites/muzzleflash3.spr"); + pev->rendercolor.x = 255; + pev->rendercolor.y = 224; + pev->rendercolor.z = 192; + pev->scale = 2.0; + m_nFrames = 1; + pev->renderamt = 255; + + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + UTIL_SetOrigin( this, pev->origin ); + + SetThink(&CNihilanthHVR :: HoverThink ); + SetTouch(&CNihilanthHVR :: BounceTouch ); + SetNextThink( 0.1 ); + + m_hTargetEnt = pTarget; +} + + +CBaseEntity *CNihilanthHVR::RandomClassname( const char *szName ) +{ + int total = 0; + + CBaseEntity *pEntity = NULL; + CBaseEntity *pNewEntity = NULL; + while ((pNewEntity = UTIL_FindEntityByClassname( pNewEntity, szName )) != NULL) + { + total++; + if (RANDOM_LONG(0,total-1) < 1) + pEntity = pNewEntity; + } + return pEntity; +} + +void CNihilanthHVR :: HoverThink( void ) +{ + SetNextThink( 0.1 ); + + if (m_hTargetEnt != NULL) + { + CircleTarget( m_hTargetEnt->pev->origin + Vector( 0, 0, 16 * N_SCALE ) ); + } + else + { + UTIL_Remove( this ); + } + + + if (RANDOM_LONG( 0, 99 ) < 5) + { +/* + CBaseEntity *pOther = RandomClassname( STRING(pev->classname) ); + + if (pOther && pOther != this) + { + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTS ); + WRITE_SHORT( this->entindex() ); + WRITE_SHORT( pOther->entindex() ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // framestart + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 10 ); // life + WRITE_BYTE( 80 ); // width + WRITE_BYTE( 80 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 128 ); // r, g, b + WRITE_BYTE( 64 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 30 ); // speed + MESSAGE_END(); + } +*/ +/* + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTS ); + WRITE_SHORT( this->entindex() ); + WRITE_SHORT( m_hTargetEnt->entindex() + 0x1000 ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // framestart + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 10 ); // life + WRITE_BYTE( 80 ); // width + WRITE_BYTE( 80 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 128 ); // r, g, b + WRITE_BYTE( 64 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 30 ); // speed + MESSAGE_END(); +*/ + } + + pev->frame = ((int)pev->frame + 1) % m_nFrames; +} + + + + +void CNihilanthHVR :: ZapInit( CBaseEntity *pEnemy ) +{ + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(edict(), "sprites/nhth1.spr"); + + pev->rendercolor.x = 255; + pev->rendercolor.y = 255; + pev->rendercolor.z = 255; + pev->scale = 2.0; + + pev->velocity = (pEnemy->pev->origin - pev->origin).Normalize() * 200; + + m_hEnemy = pEnemy; + SetThink(&CNihilanthHVR :: ZapThink ); + SetTouch(&CNihilanthHVR :: ZapTouch ); + SetNextThink( 0.1 ); + + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, "debris/zap4.wav", 1, ATTN_NORM, 0, 100 ); +} + +void CNihilanthHVR :: ZapThink( void ) +{ + SetNextThink( 0.05 ); + + // check world boundaries + if (m_hEnemy == NULL || pev->origin.x < -4096 || pev->origin.x > 4096 || pev->origin.y < -4096 || pev->origin.y > 4096 || pev->origin.z < -4096 || pev->origin.z > 4096) + { + SetTouch( NULL ); + UTIL_Remove( this ); + return; + } + + if (pev->velocity.Length() < 2000) + { + pev->velocity = pev->velocity * 1.2; + } + + + // MovetoTarget( m_hEnemy->Center( ) ); + + if ((m_hEnemy->Center() - pev->origin).Length() < 256) + { + TraceResult tr; + + UTIL_TraceLine( pev->origin, m_hEnemy->Center(), dont_ignore_monsters, edict(), &tr ); + + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + if (pEntity != NULL && pEntity->pev->takedamage) + { + ClearMultiDamage( ); + pEntity->TraceAttack( pev, gSkillData.nihilanthZap, pev->velocity, &tr, DMG_SHOCK ); + ApplyMultiDamage( pev, pev ); + } + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( entindex() ); + WRITE_COORD( tr.vecEndPos.x ); + WRITE_COORD( tr.vecEndPos.y ); + WRITE_COORD( tr.vecEndPos.z ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 3 ); // life + WRITE_BYTE( 20 ); // width + WRITE_BYTE( 20 ); // noise + WRITE_BYTE( 64 ); // r, g, b + WRITE_BYTE( 196 ); // r, g, b + WRITE_BYTE( 255); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); + + UTIL_EmitAmbientSound( edict(), tr.vecEndPos, "weapons/electro4.wav", 0.5, ATTN_NORM, 0, RANDOM_LONG( 140, 160 ) ); + + SetTouch( NULL ); + UTIL_Remove( this ); + SetNextThink( 0.2 ); + return; + } + + pev->frame = (int)(pev->frame + 1) % 11; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( 128 ); // radius + WRITE_BYTE( 128 ); // R + WRITE_BYTE( 128 ); // G + WRITE_BYTE( 255 ); // B + WRITE_BYTE( 10 ); // life * 10 + WRITE_COORD( 128 ); // decay + MESSAGE_END(); + + // Crawl( ); +} + + +void CNihilanthHVR::ZapTouch( CBaseEntity *pOther ) +{ + UTIL_EmitAmbientSound( edict(), pev->origin, "weapons/electro4.wav", 1.0, ATTN_NORM, 0, RANDOM_LONG( 90, 95 ) ); + + RadiusDamage( pev, pev, 50, CLASS_NONE, DMG_SHOCK ); + pev->velocity = pev->velocity * 0; + + /* + for (int i = 0; i < 10; i++) + { + Crawl( ); + } + */ + + SetTouch( NULL ); + UTIL_Remove( this ); + SetNextThink( 0.2 ); +} + + + +void CNihilanthHVR :: TeleportInit( CNihilanth *pOwner, CBaseEntity *pEnemy, CBaseEntity *pTarget, CBaseEntity *pTouch ) +{ + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + pev->rendercolor.x = 255; + pev->rendercolor.y = 255; + pev->rendercolor.z = 255; + pev->velocity.z *= 0.2; + + SET_MODEL(edict(), "sprites/exit1.spr"); + + m_pNihilanth = pOwner; + m_hEnemy = pEnemy; + m_hTargetEnt = pTarget; + m_hTouch = pTouch; + + SetThink(&CNihilanthHVR :: TeleportThink ); + SetTouch(&CNihilanthHVR :: TeleportTouch ); + SetNextThink( 0.1 ); + + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, "x/x_teleattack1.wav", 1, 0.2, 0, 100 ); +} + + +void CNihilanthHVR :: GreenBallInit( ) +{ + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + pev->rendercolor.x = 255; + pev->rendercolor.y = 255; + pev->rendercolor.z = 255; + pev->scale = 1.0; + + SET_MODEL(edict(), "sprites/exit1.spr"); + + SetTouch(&CNihilanthHVR :: RemoveTouch ); +} + + +void CNihilanthHVR :: TeleportThink( void ) +{ + SetNextThink( 0.1 ); + + // check world boundaries + if (m_hEnemy == NULL || !m_hEnemy->IsAlive() || pev->origin.x < -4096 || pev->origin.x > 4096 || pev->origin.y < -4096 || pev->origin.y > 4096 || pev->origin.z < -4096 || pev->origin.z > 4096) + { + STOP_SOUND(edict(), CHAN_WEAPON, "x/x_teleattack1.wav" ); + UTIL_Remove( this ); + return; + } + + if ((m_hEnemy->Center() - pev->origin).Length() < 128) + { + STOP_SOUND(edict(), CHAN_WEAPON, "x/x_teleattack1.wav" ); + UTIL_Remove( this ); + + if (m_hTargetEnt != NULL) + m_hTargetEnt->Use( m_hEnemy, m_hEnemy, USE_ON, 1.0 ); + + if ( m_hTouch != NULL && m_hEnemy != NULL ) + m_hTouch->Touch( m_hEnemy ); + } + else + { + MovetoTarget( m_hEnemy->Center( ) ); + } + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( 256 ); // radius + WRITE_BYTE( 0 ); // R + WRITE_BYTE( 255 ); // G + WRITE_BYTE( 0 ); // B + WRITE_BYTE( 10 ); // life * 10 + WRITE_COORD( 256 ); // decay + MESSAGE_END(); + + pev->frame = (int)(pev->frame + 1) % 20; +} + + +void CNihilanthHVR :: AbsorbInit( void ) +{ + SetThink(&CNihilanthHVR :: DissipateThink ); + pev->renderamt = 255; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTS ); + WRITE_SHORT( this->entindex() ); + WRITE_SHORT( m_hTargetEnt->entindex() + 0x1000 ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // framestart + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 50 ); // life + WRITE_BYTE( 80 ); // width + WRITE_BYTE( 80 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 128 ); // r, g, b + WRITE_BYTE( 64 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 30 ); // speed + MESSAGE_END(); +} + +void CNihilanthHVR::TeleportTouch( CBaseEntity *pOther ) +{ + CBaseEntity *pEnemy = m_hEnemy; + + if (pOther == pEnemy) + { + if (m_hTargetEnt != NULL) + m_hTargetEnt->Use( pEnemy, pEnemy, USE_ON, 1.0 ); + + if (m_hTouch != NULL && pEnemy != NULL ) + m_hTouch->Touch( pEnemy ); + } + else + { + m_pNihilanth->MakeFriend( pev->origin ); + } + + SetTouch( NULL ); + STOP_SOUND(edict(), CHAN_WEAPON, "x/x_teleattack1.wav" ); + UTIL_Remove( this ); +} + + +void CNihilanthHVR :: DissipateThink( void ) +{ + SetNextThink( 0.1 ); + + if (pev->scale > 5.0) + UTIL_Remove( this ); + + pev->renderamt -= 2; + pev->scale += 0.1; + + if (m_hTargetEnt != NULL) + { + CircleTarget( m_hTargetEnt->pev->origin + Vector( 0, 0, 4096 ) ); + } + else + { + UTIL_Remove( this ); + } + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( pev->renderamt ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 192 ); // G + WRITE_BYTE( 64 ); // B + WRITE_BYTE( 2 ); // life * 10 + WRITE_COORD( 0 ); // decay + MESSAGE_END(); +} + + +BOOL CNihilanthHVR :: CircleTarget( Vector vecTarget ) +{ + BOOL fClose = FALSE; + + Vector vecDest = vecTarget; + Vector vecEst = pev->origin + pev->velocity * 0.5; + Vector vecSrc = pev->origin; + vecDest.z = 0; + vecEst.z = 0; + vecSrc.z = 0; + float d1 = (vecDest - vecSrc).Length() - 24 * N_SCALE; + float d2 = (vecDest - vecEst).Length() - 24 * N_SCALE; + + if (m_vecIdeal == Vector( 0, 0, 0 )) + { + m_vecIdeal = pev->velocity; + } + + if (d1 < 0 && d2 <= d1) + { + // ALERT( at_console, "too close\n"); + m_vecIdeal = m_vecIdeal - (vecDest - vecSrc).Normalize() * 50; + } + else if (d1 > 0 && d2 >= d1) + { + // ALERT( at_console, "too far\n"); + m_vecIdeal = m_vecIdeal + (vecDest - vecSrc).Normalize() * 50; + } + pev->avelocity.z = d1 * 20; + + if (d1 < 32) + { + fClose = TRUE; + } + + m_vecIdeal = m_vecIdeal + Vector( RANDOM_FLOAT( -2, 2 ), RANDOM_FLOAT( -2, 2 ), RANDOM_FLOAT( -2, 2 )); + m_vecIdeal = Vector( m_vecIdeal.x, m_vecIdeal.y, 0 ).Normalize( ) * 200 + /* + Vector( -m_vecIdeal.y, m_vecIdeal.x, 0 ).Normalize( ) * 32 */ + + Vector( 0, 0, m_vecIdeal.z ); + // m_vecIdeal = m_vecIdeal + Vector( -m_vecIdeal.y, m_vecIdeal.x, 0 ).Normalize( ) * 2; + + // move up/down + d1 = vecTarget.z - pev->origin.z; + if (d1 > 0 && m_vecIdeal.z < 200) + m_vecIdeal.z += 20; + else if (d1 < 0 && m_vecIdeal.z > -200) + m_vecIdeal.z -= 20; + + pev->velocity = m_vecIdeal; + + // ALERT( at_console, "%.0f %.0f %.0f\n", m_vecIdeal.x, m_vecIdeal.y, m_vecIdeal.z ); + return fClose; +} + + +void CNihilanthHVR :: MovetoTarget( Vector vecTarget ) +{ + if (m_vecIdeal == Vector( 0, 0, 0 )) + { + m_vecIdeal = pev->velocity; + } + + // accelerate + float flSpeed = m_vecIdeal.Length(); + if (flSpeed > 300) + { + m_vecIdeal = m_vecIdeal.Normalize( ) * 300; + } + m_vecIdeal = m_vecIdeal + (vecTarget - pev->origin).Normalize() * 300; + pev->velocity = m_vecIdeal; +} + + + + +void CNihilanthHVR :: Crawl( void ) +{ + + Vector vecAim = Vector( RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 ) ).Normalize( ); + Vector vecPnt = pev->origin + pev->velocity * 0.2 + vecAim * 128; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( entindex() ); + WRITE_COORD( vecPnt.x); + WRITE_COORD( vecPnt.y); + WRITE_COORD( vecPnt.z); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 3 ); // life + WRITE_BYTE( 20 ); // width + WRITE_BYTE( 80 ); // noise + WRITE_BYTE( 64 ); // r, g, b + WRITE_BYTE( 128 ); // r, g, b + WRITE_BYTE( 255); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); +} + + +void CNihilanthHVR::RemoveTouch( CBaseEntity *pOther ) +{ + STOP_SOUND(edict(), CHAN_WEAPON, "x/x_teleattack1.wav" ); + UTIL_Remove( this ); +} + +void CNihilanthHVR::BounceTouch( CBaseEntity *pOther ) +{ + Vector vecDir = m_vecIdeal.Normalize( ); + + TraceResult tr = UTIL_GetGlobalTrace( ); + + float n = -DotProduct(tr.vecPlaneNormal, vecDir); + + vecDir = 2.0 * tr.vecPlaneNormal * n + vecDir; + + m_vecIdeal = vecDir * m_vecIdeal.Length(); +} + + + +#endif \ No newline at end of file diff --git a/dlls/nodes.cpp b/dlls/nodes.cpp new file mode 100644 index 0000000..e0db471 --- /dev/null +++ b/dlls/nodes.cpp @@ -0,0 +1,3627 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// nodes.cpp - AI node tree stuff. +//========================================================= + +#include +#include + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "virtualfs.h" +#include "monsters.h" +#include "nodes.h" +#include "animation.h" +#include "doors.h" + +#define HULL_STEP_SIZE 16// how far the test hull moves on each step +#define NODE_HEIGHT 8 // how high to lift nodes off the ground after we drop them all (make stair/ramp mapping easier) + +// to help eliminate node clutter by level designers, this is used to cap how many other nodes +// any given node is allowed to 'see' in the first stage of graph creation "LinkVisibleNodes()". +#define MAX_NODE_INITIAL_LINKS 128 +#define MAX_NODES 1024 + +extern DLL_GLOBAL edict_t *g_pBodyQueueHead; + +Vector VecBModelOrigin( entvars_t* pevBModel ); + +CGraph WorldGraph; + +LINK_ENTITY_TO_CLASS( info_node, CNodeEnt ); +LINK_ENTITY_TO_CLASS( info_node_air, CNodeEnt ); + +//========================================================= +// CGraph - InitGraph - prepares the graph for use. Frees any +// memory currently in use by the world graph, NULLs +// all pointers, and zeros the node count. +//========================================================= +void CGraph :: InitGraph( void) +{ + + // Make the graph unavailable + // + m_fGraphPresent = FALSE; + m_fGraphPointersSet = FALSE; + m_fRoutingComplete = FALSE; + + // Free the link pool + // + if ( m_pLinkPool ) + { + free ( m_pLinkPool ); + m_pLinkPool = NULL; + } + + // Free the node info + // + if ( m_pNodes ) + { + free ( m_pNodes ); + m_pNodes = NULL; + } + + if ( m_di ) + { + free ( m_di ); + m_di = NULL; + } + + // Free the routing info. + // + if ( m_pRouteInfo ) + { + free ( m_pRouteInfo ); + m_pRouteInfo = NULL; + } + + if (m_pHashLinks) + { + free(m_pHashLinks); + m_pHashLinks = NULL; + } + + // Zero node and link counts + // + m_cNodes = 0; + m_cLinks = 0; + m_nRouteInfo = 0; + + m_iLastActiveIdleSearch = 0; + m_iLastCoverSearch = 0; +} + +//========================================================= +// CGraph - AllocNodes - temporary function that mallocs a +// reasonable number of nodes so we can build the path which +// will be saved to disk. +//========================================================= +int CGraph :: AllocNodes ( void ) +{ +// malloc all of the nodes + WorldGraph.m_pNodes = (CNode *)calloc ( sizeof ( CNode ), MAX_NODES ); + +// could not malloc space for all the nodes! + if ( !WorldGraph.m_pNodes ) + { + ALERT ( at_aiconsole, "**ERROR**\nCouldn't malloc %d nodes!\n", WorldGraph.m_cNodes ); + return FALSE; + } + + return TRUE; +} + +//========================================================= +// CGraph - LinkEntForLink - sometimes the ent that blocks +// a path is a usable door, in which case the monster just +// needs to face the door and fire it. In other cases, the +// monster needs to operate a button or lever to get the +// door to open. This function will return a pointer to the +// button if the monster needs to hit a button to open the +// door, or returns a pointer to the door if the monster +// need only use the door. +// +// pNode is the node the monster will be standing on when it +// will need to stop and trigger the ent. +//========================================================= +entvars_t* CGraph :: LinkEntForLink ( CLink *pLink, CNode *pNode ) +{ + CBaseEntity *pSearch; + CBaseEntity *pTrigger; + entvars_t *pevTrigger; + entvars_t *pevLinkEnt; + TraceResult tr; + + pevLinkEnt = pLink->m_pLinkEnt; + if ( !pevLinkEnt ) + return NULL; + + pSearch = NULL;// start search at the top of the ent list. + + if ( FClassnameIs ( pevLinkEnt, "func_door" ) || FClassnameIs ( pevLinkEnt, "func_door_rotating" ) ) + { + + ///!!!UNDONE - check for TOGGLE or STAY open doors here. If a door is in the way, and is + // TOGGLE or STAY OPEN, even monsters that can't open doors can go that way. + + if ( ( pevLinkEnt->spawnflags & SF_DOOR_USE_ONLY ) ) + {// door is use only, so the door is all the monster has to worry about + return pevLinkEnt; + } + + while ( 1 ) + { + pTrigger = UTIL_FindEntityByTarget ( pSearch, STRING( pevLinkEnt->targetname ) );// find the button or trigger + + if ( !pTrigger ) + {// no trigger found + + // right now this is a problem among auto-open doors, or any door that opens through the use + // of a trigger brush. Trigger brushes have no models, and don't show up in searches. Just allow + // monsters to open these sorts of doors for now. + return pevLinkEnt; + } + + pSearch = pTrigger; + pevTrigger = pTrigger->pev; + + if ( FClassnameIs(pevTrigger, "func_button") || FClassnameIs(pevTrigger, "func_rot_button" ) ) + {// only buttons are handled right now. + + // trace from the node to the trigger, make sure it's one we can see from the node. + // !!!HACKHACK Use bodyqueue here cause there are no ents we really wish to ignore! + UTIL_TraceLine ( pNode->m_vecOrigin, VecBModelOrigin( pevTrigger ), ignore_monsters, g_pBodyQueueHead, &tr ); + + + if ( VARS(tr.pHit) == pevTrigger ) + {// good to go! + return VARS( tr.pHit ); + } + } + } + } + else + { + ALERT ( at_aiconsole, "Unsupported PathEnt:\n'%s'\n", STRING ( pevLinkEnt->classname ) ); + return NULL; + } +} + +//========================================================= +// CGraph - HandleLinkEnt - a brush ent is between two +// nodes that would otherwise be able to see each other. +// Given the monster's capability, determine whether +// or not the monster can go this way. +//========================================================= +int CGraph :: HandleLinkEnt ( int iNode, entvars_t *pevLinkEnt, int afCapMask, NODEQUERY queryType ) +{ + edict_t *pentWorld; + CBaseEntity *pDoor; + TraceResult tr; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return FALSE; + } + + if ( FNullEnt ( pevLinkEnt ) ) + { + ALERT ( at_aiconsole, "dead path ent!\n" ); + return TRUE; + } + pentWorld = NULL; + +// func_door + if ( FClassnameIs( pevLinkEnt, "func_door" ) || FClassnameIs( pevLinkEnt, "func_door_rotating" ) ) + {// ent is a door. + + pDoor = ( CBaseEntity::Instance( pevLinkEnt ) ); + + if ( ( pevLinkEnt->spawnflags & SF_DOOR_USE_ONLY ) ) + {// door is use only. + + if ( ( afCapMask & bits_CAP_OPEN_DOORS ) ) + {// let monster right through if he can open doors + return TRUE; + } + else + { + // monster should try for it if the door is open and looks as if it will stay that way + if ( pDoor->GetToggleState()== TS_AT_TOP && ( pevLinkEnt->spawnflags & SF_DOOR_NO_AUTO_RETURN ) ) + { + return TRUE; + } + + return FALSE; + } + } + else + {// door must be opened with a button or trigger field. + + // monster should try for it if the door is open and looks as if it will stay that way + if ( pDoor->GetToggleState() == TS_AT_TOP && ( pevLinkEnt->spawnflags & SF_DOOR_NO_AUTO_RETURN ) ) + { + return TRUE; + } + if ( ( afCapMask & bits_CAP_OPEN_DOORS ) ) + { + if ( !( pevLinkEnt->spawnflags & SF_DOOR_NOMONSTERS ) || queryType == NODEGRAPH_STATIC ) + return TRUE; + } + + return FALSE; + } + } +// func_breakable + else if ( FClassnameIs( pevLinkEnt, "func_breakable" ) && queryType == NODEGRAPH_STATIC ) + { + return TRUE; + } + else + { + ALERT ( at_aiconsole, "Unhandled Ent in Path %s\n", STRING( pevLinkEnt->classname ) ); + return FALSE; + } + + return FALSE; +} + +#if 0 +//========================================================= +// FindNearestLink - finds the connection (line) nearest +// the given point. Returns FALSE if fails, or TRUE if it +// has stuffed the index into the nearest link pool connection +// into the passed int pointer, and a BOOL telling whether or +// not the point is along the line into the passed BOOL pointer. +//========================================================= +int CGraph :: FindNearestLink ( const Vector &vecTestPoint, int *piNearestLink, BOOL *pfAlongLine ) +{ + int i, j;// loops + + int iNearestLink;// index into the link pool, this is the nearest node at any time. + float flMinDist;// the distance of of the nearest case so far + float flDistToLine;// the distance of the current test case + + BOOL fCurrentAlongLine; + BOOL fSuccess; + + //float flConstant;// line constant + Vector vecSpot1, vecSpot2; + Vector2D vec2Spot1, vec2Spot2, vec2TestPoint; + Vector2D vec2Normal;// line normal + Vector2D vec2Line; + + TraceResult tr; + + iNearestLink = -1;// prepare for failure + fSuccess = FALSE; + + flMinDist = 9999;// anything will be closer than this + +// go through all of the nodes, and each node's connections + int cSkip = 0;// how many links proper pairing allowed us to skip + int cChecked = 0;// how many links were checked + + for ( i = 0 ; i < m_cNodes ; i++ ) + { + vecSpot1 = m_pNodes[ i ].m_vecOrigin; + + if ( m_pNodes[ i ].m_cNumLinks <= 0 ) + {// this shouldn't happen! + ALERT ( at_aiconsole, "**Node %d has no links\n", i ); + continue; + } + + for ( j = 0 ; j < m_pNodes[ i ].m_cNumLinks ; j++ ) + { + /* + !!!This optimization only works when the node graph consists of properly linked pairs. + if ( INodeLink ( i, j ) <= i ) + { + // since we're going through the nodes in order, don't check + // any connections whose second node is lower in the list + // than the node we're currently working with. This eliminates + // redundant checks. + cSkip++; + continue; + } + */ + + vecSpot2 = PNodeLink ( i, j )->m_vecOrigin; + + // these values need a little attention now and then, or sometimes ramps cause trouble. + if ( fabs ( vecSpot1.z - vecTestPoint.z ) > 48 && fabs ( vecSpot2.z - vecTestPoint.z ) > 48 ) + { + // if both endpoints of the line are 32 units or more above or below the monster, + // the monster won't be able to get to them, so we do a bit of trivial rejection here. + // this may change if monsters are allowed to jump down. + // + // !!!LATER: some kind of clever X/Y hashing should be used here, too + continue; + } + +// now we have two endpoints for a line segment that we've not already checked. +// since all lines that make it this far are within -/+ 32 units of the test point's +// Z Plane, we can get away with doing the point->line check in 2d. + + cChecked++; + + vec2Spot1 = vecSpot1.Make2D(); + vec2Spot2 = vecSpot2.Make2D(); + vec2TestPoint = vecTestPoint.Make2D(); + + // get the line normal. + vec2Line = ( vec2Spot1 - vec2Spot2 ).Normalize(); + vec2Normal.x = -vec2Line.y; + vec2Normal.y = vec2Line.x; + + if ( DotProduct ( vec2Line, ( vec2TestPoint - vec2Spot1 ) ) > 0 ) + {// point outside of line + flDistToLine = ( vec2TestPoint - vec2Spot1 ).Length(); + fCurrentAlongLine = FALSE; + } + else if ( DotProduct ( vec2Line, ( vec2TestPoint - vec2Spot2 ) ) < 0 ) + {// point outside of line + flDistToLine = ( vec2TestPoint - vec2Spot2 ).Length(); + fCurrentAlongLine = FALSE; + } + else + {// point inside line + flDistToLine = fabs( DotProduct ( vec2TestPoint - vec2Spot2, vec2Normal ) ); + fCurrentAlongLine = TRUE; + } + + if ( flDistToLine < flMinDist ) + {// just found a line nearer than any other so far + + UTIL_TraceLine ( vecTestPoint, SourceNode( i, j ).m_vecOrigin, ignore_monsters, g_pBodyQueueHead, &tr ); + + if ( tr.flFraction != 1.0 ) + {// crap. can't see the first node of this link, try to see the other + + UTIL_TraceLine ( vecTestPoint, DestNode( i, j ).m_vecOrigin, ignore_monsters, g_pBodyQueueHead, &tr ); + if ( tr.flFraction != 1.0 ) + {// can't use this link, cause can't see either node! + continue; + } + + } + + fSuccess = TRUE;// we know there will be something to return. + flMinDist = flDistToLine; + iNearestLink = m_pNodes [ i ].m_iFirstLink + j; + *piNearestLink = m_pNodes[ i ].m_iFirstLink + j; + *pfAlongLine = fCurrentAlongLine; + } + } + } + +/* + if ( fSuccess ) + { + WRITE_BYTE(MSG_BROADCAST, SVC_TEMPENTITY); + WRITE_BYTE(MSG_BROADCAST, TE_SHOWLINE); + + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iSrcNode ].m_vecOrigin.x ); + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iSrcNode ].m_vecOrigin.y ); + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iSrcNode ].m_vecOrigin.z + NODE_HEIGHT); + + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iDestNode ].m_vecOrigin.x ); + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iDestNode ].m_vecOrigin.y ); + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iDestNode ].m_vecOrigin.z + NODE_HEIGHT); + } +*/ + + ALERT ( at_aiconsole, "%d Checked\n", cChecked ); + return fSuccess; +} + +#endif + +int CGraph::HullIndex( const CBaseEntity *pEntity ) +{ + if ( pEntity->pev->movetype == MOVETYPE_FLY) + return NODE_FLY_HULL; + + if ( pEntity->pev->mins == Vector( -12, -12, 0 ) ) + return NODE_SMALL_HULL; + else if ( pEntity->pev->mins == VEC_HUMAN_HULL_MIN ) + return NODE_HUMAN_HULL; + else if ( pEntity->pev->mins == Vector ( -32, -32, 0 ) ) + return NODE_LARGE_HULL; + +// ALERT ( at_aiconsole, "Unknown Hull Mins!\n" ); + return NODE_HUMAN_HULL; +} + + +int CGraph::NodeType( const CBaseEntity *pEntity ) +{ + if ( pEntity->pev->movetype == MOVETYPE_FLY) + { + if (pEntity->pev->waterlevel != 0 && pEntity->pev->watertype != CONTENTS_FOG) + { + return bits_NODE_WATER; + } + else + { + return bits_NODE_AIR; + } + } + return bits_NODE_LAND; +} + + +// Sum up graph weights on the path from iStart to iDest to determine path length +float CGraph::PathLength( int iStart, int iDest, int iHull, int afCapMask ) +{ + float distance = 0; + int iNext; + + int iMaxLoop = m_cNodes; + + int iCurrentNode = iStart; + int iCap = CapIndex( afCapMask ); + + while (iCurrentNode != iDest) + { + if (iMaxLoop-- <= 0) + { + ALERT( at_debug, "Route Failure\n" ); + return 0; + } + + iNext = NextNodeInRoute( iCurrentNode, iDest, iHull, iCap ); + if (iCurrentNode == iNext) + { + //ALERT(at_aiconsole, "SVD: Can't get there from here..\n"); + return 0; + } + + int iLink; + HashSearch(iCurrentNode, iNext, iLink); + if (iLink < 0) + { + ALERT(at_debug, "HashLinks is broken from %d to %d.\n", iCurrentNode, iDest); + return 0; + } + CLink &link = Link(iLink); + distance += link.m_flWeight; + + iCurrentNode = iNext; + } + + return distance; +} + + +// Parse the routing table at iCurrentNode for the next node on the shortest path to iDest +int CGraph::NextNodeInRoute( int iCurrentNode, int iDest, int iHull, int iCap ) +{ + int iNext = iCurrentNode; + int nCount = iDest+1; + char *pRoute = m_pRouteInfo + m_pNodes[ iCurrentNode ].m_pNextBestNode[iHull][iCap]; + + // Until we decode the next best node + // + while (nCount > 0) + { + char ch = *pRoute++; + //ALERT(at_aiconsole, "C(%d)", ch); + if (ch < 0) + { + // Sequence phrase + // + ch = -ch; + if (nCount <= ch) + { + iNext = iDest; + nCount = 0; + //ALERT(at_aiconsole, "SEQ: iNext/iDest=%d\n", iNext); + } + else + { + //ALERT(at_aiconsole, "SEQ: nCount + ch (%d + %d)\n", nCount, ch); + nCount = nCount - ch; + } + } + else + { + //ALERT(at_aiconsole, "C(%d)", *pRoute); + + // Repeat phrase + // + if (nCount <= ch+1) + { + iNext = iCurrentNode + *pRoute; + if (iNext >= m_cNodes) iNext -= m_cNodes; + else if (iNext < 0) iNext += m_cNodes; + nCount = 0; + //ALERT(at_aiconsole, "REP: iNext=%d\n", iNext); + } + else + { + //ALERT(at_aiconsole, "REP: nCount - ch+1 (%d - %d+1)\n", nCount, ch); + nCount = nCount - ch - 1; + } + pRoute++; + } + } + + return iNext; +} + + +//========================================================= +// CGraph - FindShortestPath +// +// accepts a capability mask (afCapMask), and will only +// find a path usable by a monster with those capabilities +// returns the number of nodes copied into supplied array +//========================================================= +int CGraph :: FindShortestPath ( int *piPath, int iStart, int iDest, int iHull, int afCapMask) +{ + int iVisitNode; + int iCurrentNode; + int iNumPathNodes; + int iHullMask; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available or built + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return FALSE; + } + + if ( iStart < 0 || iStart > m_cNodes ) + {// The start node is bad? + ALERT ( at_aiconsole, "Can't build a path, iStart is %d!\n", iStart ); + return FALSE; + } + + if (iStart == iDest) + { + piPath[0] = iStart; + piPath[1] = iDest; + return 2; + } + + // Is routing information present. + // + if (m_fRoutingComplete) + { + int iCap = CapIndex( afCapMask ); + + iNumPathNodes = 0; + piPath[iNumPathNodes++] = iStart; + iCurrentNode = iStart; + int iNext; + + //ALERT(at_aiconsole, "GOAL: %d to %d\n", iStart, iDest); + + // Until we arrive at the destination + // + while (iCurrentNode != iDest) + { + iNext = NextNodeInRoute( iCurrentNode, iDest, iHull, iCap ); + if (iCurrentNode == iNext) + { + //ALERT(at_aiconsole, "SVD: Can't get there from here..\n"); + return 0; + break; + } + if (iNumPathNodes >= MAX_PATH_SIZE) + { + //ALERT(at_aiconsole, "SVD: Don't return the entire path.\n"); + break; + } + piPath[iNumPathNodes++] = iNext; + iCurrentNode = iNext; + } + //ALERT( at_aiconsole, "SVD: Path with %d nodes.\n", iNumPathNodes); + } + else + { + CQueuePriority queue; + + switch( iHull ) + { + case NODE_SMALL_HULL: + iHullMask = bits_LINK_SMALL_HULL; + break; + case NODE_HUMAN_HULL: + iHullMask = bits_LINK_HUMAN_HULL; + break; + case NODE_LARGE_HULL: + iHullMask = bits_LINK_LARGE_HULL; + break; + case NODE_FLY_HULL: + iHullMask = bits_LINK_FLY_HULL; + break; + } + + // Mark all the nodes as unvisited. + // + int i = 0; + for ( i = 0; i < m_cNodes; i++) + { + m_pNodes[ i ].m_flClosestSoFar = -1.0; + } + + m_pNodes[ iStart ].m_flClosestSoFar = 0.0; + m_pNodes[ iStart ].m_iPreviousNode = iStart;// tag this as the origin node + queue.Insert( iStart, 0.0 );// insert start node + + while ( !queue.Empty() ) + { + // now pull a node out of the queue + float flCurrentDistance; + iCurrentNode = queue.Remove(flCurrentDistance); + + // For straight-line weights, the following Shortcut works. For arbitrary weights, + // it doesn't. + // + if (iCurrentNode == iDest) break; + + CNode *pCurrentNode = &m_pNodes[ iCurrentNode ]; + + for ( i = 0 ; i < pCurrentNode->m_cNumLinks ; i++ ) + {// run through all of this node's neighbors + + iVisitNode = INodeLink ( iCurrentNode, i ); + if ( ( m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i ].m_afLinkInfo & iHullMask ) != iHullMask ) + {// monster is too large to walk this connection + //ALERT ( at_aiconsole, "fat ass %d/%d\n",m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i ].m_afLinkInfo, iMonsterHull ); + continue; + } + // check the connection from the current node to the node we're about to mark visited and push into the queue + if ( m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i ].m_pLinkEnt != NULL ) + {// there's a brush ent in the way! Don't mark this node or put it into the queue unless the monster can negotiate it + + if ( !HandleLinkEnt ( iCurrentNode, m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i ].m_pLinkEnt, afCapMask, NODEGRAPH_STATIC ) ) + {// monster should not try to go this way. + continue; + } + } + float flOurDistance = flCurrentDistance + m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i].m_flWeight; + if ( m_pNodes[ iVisitNode ].m_flClosestSoFar < -0.5 + || flOurDistance < m_pNodes[ iVisitNode ].m_flClosestSoFar - 0.001 ) + { + m_pNodes[iVisitNode].m_flClosestSoFar = flOurDistance; + m_pNodes[iVisitNode].m_iPreviousNode = iCurrentNode; + + queue.Insert ( iVisitNode, flOurDistance ); + } + } + } + if ( m_pNodes[iDest].m_flClosestSoFar < -0.5 ) + {// Destination is unreachable, no path found. + return 0; + } + + // the queue is not empty + + // now we must walk backwards through the m_iPreviousNode field, and count how many connections there are in the path + iCurrentNode = iDest; + iNumPathNodes = 1;// count the dest + + while ( iCurrentNode != iStart ) + { + iNumPathNodes++; + iCurrentNode = m_pNodes[ iCurrentNode ].m_iPreviousNode; + } + + iCurrentNode = iDest; + for ( i = iNumPathNodes - 1 ; i >= 0 ; i-- ) + { + piPath[ i ] = iCurrentNode; + iCurrentNode = m_pNodes [ iCurrentNode ].m_iPreviousNode; + } + } + +#if 0 + + if (m_fRoutingComplete) + { + // This will draw the entire path that was generated for the monster. + + for ( int i = 0 ; i < iNumPathNodes - 1 ; i++ ) + { + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( m_pNodes[ piPath[ i ] ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ piPath[ i ] ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ piPath[ i ] ].m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( m_pNodes[ piPath[ i + 1 ] ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ piPath[ i + 1 ] ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ piPath[ i + 1 ] ].m_vecOrigin.z + NODE_HEIGHT ); + MESSAGE_END(); + } + } + +#endif +#if 0 // MAZE map + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( m_pNodes[ 4 ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ 4 ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ 4 ].m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( m_pNodes[ 9 ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ 9 ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ 9 ].m_vecOrigin.z + NODE_HEIGHT ); + MESSAGE_END(); +#endif + + return iNumPathNodes; +} + +inline ULONG Hash(void *p, int len) +{ + CRC32_t ulCrc; + CRC32_INIT(&ulCrc); + CRC32_PROCESS_BUFFER(&ulCrc, p, len); + return CRC32_FINAL(ulCrc); +} + +void inline CalcBounds(int &Lower, int &Upper, int Goal, int Best) +{ + int Temp = 2*Goal - Best; + if (Best > Goal) + { + Lower = max(0, Temp); + Upper = Best; + } + else + { + Upper = min(255, Temp); + Lower = Best; + } +} + +// Convert from [-8192,8192] to [0, 255] +// +inline int CALC_RANGE(int x, int lower, int upper) +{ + return NUM_RANGES*(x-lower)/((upper-lower+1)); +} + + +void inline UpdateRange(int &minValue, int &maxValue, int Goal, int Best) +{ + int Lower, Upper; + CalcBounds(Lower, Upper, Goal, Best); + if (Upper < maxValue) maxValue = Upper; + if (minValue < Lower) minValue = Lower; +} + +void CGraph :: CheckNode(Vector vecOrigin, int iNode) +{ + // Have we already seen this point before?. + // + if (m_di[iNode].m_CheckedEvent == m_CheckedCounter) return; + m_di[iNode].m_CheckedEvent = m_CheckedCounter; + + float flDist = ( vecOrigin - m_pNodes[ iNode ].m_vecOriginPeek ).Length(); + + if ( flDist < m_flShortest ) + { + TraceResult tr; + + // make sure that vecOrigin can trace to this node! + UTIL_TraceLine ( vecOrigin, m_pNodes[ iNode ].m_vecOriginPeek, ignore_monsters, 0, &tr ); + + if ( tr.flFraction == 1.0 ) + { + m_iNearest = iNode; + m_flShortest = flDist; + + UpdateRange(m_minX, m_maxX, CALC_RANGE(vecOrigin.x, m_RegionMin[0], m_RegionMax[0]), m_pNodes[iNode].m_Region[0]); + UpdateRange(m_minY, m_maxY, CALC_RANGE(vecOrigin.y, m_RegionMin[1], m_RegionMax[1]), m_pNodes[iNode].m_Region[1]); + UpdateRange(m_minZ, m_maxZ, CALC_RANGE(vecOrigin.z, m_RegionMin[2], m_RegionMax[2]), m_pNodes[iNode].m_Region[2]); + + // From maxCircle, calculate maximum bounds box. All points must be + // simultaneously inside all bounds of the box. + // + m_minBoxX = CALC_RANGE(vecOrigin.x - flDist, m_RegionMin[0], m_RegionMax[0]); + m_maxBoxX = CALC_RANGE(vecOrigin.x + flDist, m_RegionMin[0], m_RegionMax[0]); + m_minBoxY = CALC_RANGE(vecOrigin.y - flDist, m_RegionMin[1], m_RegionMax[1]); + m_maxBoxY = CALC_RANGE(vecOrigin.y + flDist, m_RegionMin[1], m_RegionMax[1]); + m_minBoxZ = CALC_RANGE(vecOrigin.z - flDist, m_RegionMin[2], m_RegionMax[2]); + m_maxBoxZ = CALC_RANGE(vecOrigin.z + flDist, m_RegionMin[2], m_RegionMax[2]); + } + } +} + +//========================================================= +// CGraph - FindNearestNode - returns the index of the node nearest +// the given vector -1 is failure (couldn't find a valid +// near node ) +//========================================================= +int CGraph :: FindNearestNode ( const Vector &vecOrigin, CBaseEntity *pEntity ) +{ + return FindNearestNode( vecOrigin, NodeType( pEntity ) ); +} + +int CGraph :: FindNearestNode ( const Vector &vecOrigin, int afNodeTypes ) +{ + int i; + TraceResult tr; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return -1; + } + + // Check with the cache + // + ULONG iHash = (CACHE_SIZE-1) & Hash((void *)(const float *)vecOrigin, sizeof(vecOrigin)); + if (m_Cache[iHash].v == vecOrigin) + { + //ALERT(at_aiconsole, "Cache Hit.\n"); + return m_Cache[iHash].n; + } + else + { + //ALERT(at_aiconsole, "Cache Miss.\n"); + } + + // Mark all points as unchecked. + // + m_CheckedCounter++; + if (m_CheckedCounter == 0) + { + for (int i = 0; i < m_cNodes; i++) + { + m_di[i].m_CheckedEvent = 0; + } + m_CheckedCounter++; + } + + m_iNearest = -1; + m_flShortest = 999999.0; // just a big number. + + // If we can find a visible point, then let CalcBounds set the limits, but if + // we have no visible point at all to start with, then don't restrict the limits. + // +#if 1 + m_minX = 0; m_maxX = 255; + m_minY = 0; m_maxY = 255; + m_minZ = 0; m_maxZ = 255; + m_minBoxX = 0; m_maxBoxX = 255; + m_minBoxY = 0; m_maxBoxY = 255; + m_minBoxZ = 0; m_maxBoxZ = 255; +#else + m_minBoxX = CALC_RANGE(vecOrigin.x - flDist, m_RegionMin[0], m_RegionMax[0]); + m_maxBoxX = CALC_RANGE(vecOrigin.x + flDist, m_RegionMin[0], m_RegionMax[0]); + m_minBoxY = CALC_RANGE(vecOrigin.y - flDist, m_RegionMin[1], m_RegionMax[1]); + m_maxBoxY = CALC_RANGE(vecOrigin.y + flDist, m_RegionMin[1], m_RegionMax[1]); + m_minBoxZ = CALC_RANGE(vecOrigin.z - flDist, m_RegionMin[2], m_RegionMax[2]); + m_maxBoxZ = CALC_RANGE(vecOrigin.z + flDist, m_RegionMin[2], m_RegionMax[2]) + CalcBounds(m_minX, m_maxX, CALC_RANGE(vecOrigin.x, m_RegionMin[0], m_RegionMax[0]), m_pNodes[m_iNearest].m_Region[0]); + CalcBounds(m_minY, m_maxY, CALC_RANGE(vecOrigin.y, m_RegionMin[1], m_RegionMax[1]), m_pNodes[m_iNearest].m_Region[1]); + CalcBounds(m_minZ, m_maxZ, CALC_RANGE(vecOrigin.z, m_RegionMin[2], m_RegionMax[2]), m_pNodes[m_iNearest].m_Region[2]); +#endif + + int halfX = (m_minX+m_maxX)/2; + int halfY = (m_minY+m_maxY)/2; + int halfZ = (m_minZ+m_maxZ)/2; + + int j; + + for (i = halfX; i >= m_minX; i--) + { + for (j = m_RangeStart[0][i]; j <= m_RangeEnd[0][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[0]].m_afNodeInfo & afNodeTypes)) continue; + + int rgY = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[1]; + if (rgY > m_maxBoxY) break; + if (rgY < m_minBoxY) continue; + + int rgZ = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[2]; + if (rgZ < m_minBoxZ) continue; + if (rgZ > m_maxBoxZ) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[0]); + } + } + + for (i = max(m_minY,halfY+1); i <= m_maxY; i++) + { + for (j = m_RangeStart[1][i]; j <= m_RangeEnd[1][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[1]].m_afNodeInfo & afNodeTypes)) continue; + + int rgZ = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[2]; + if (rgZ > m_maxBoxZ) break; + if (rgZ < m_minBoxZ) continue; + int rgX = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[0]; + if (rgX < m_minBoxX) continue; + if (rgX > m_maxBoxX) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[1]); + } + } + + for (i = min(m_maxZ,halfZ); i >= m_minZ; i--) + { + for (j = m_RangeStart[2][i]; j <= m_RangeEnd[2][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[2]].m_afNodeInfo & afNodeTypes)) continue; + + int rgX = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[0]; + if (rgX > m_maxBoxX) break; + if (rgX < m_minBoxX) continue; + int rgY = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[1]; + if (rgY < m_minBoxY) continue; + if (rgY > m_maxBoxY) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[2]); + } + } + + for (i = max(m_minX,halfX+1); i <= m_maxX; i++) + { + for (j = m_RangeStart[0][i]; j <= m_RangeEnd[0][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[0]].m_afNodeInfo & afNodeTypes)) continue; + + int rgY = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[1]; + if (rgY > m_maxBoxY) break; + if (rgY < m_minBoxY) continue; + + int rgZ = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[2]; + if (rgZ < m_minBoxZ) continue; + if (rgZ > m_maxBoxZ) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[0]); + } + } + + for (i = min(m_maxY,halfY); i >= m_minY; i--) + { + for (j = m_RangeStart[1][i]; j <= m_RangeEnd[1][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[1]].m_afNodeInfo & afNodeTypes)) continue; + + int rgZ = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[2]; + if (rgZ > m_maxBoxZ) break; + if (rgZ < m_minBoxZ) continue; + int rgX = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[0]; + if (rgX < m_minBoxX) continue; + if (rgX > m_maxBoxX) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[1]); + } + } + + for (i = max(m_minZ,halfZ+1); i <= m_maxZ; i++) + { + for (j = m_RangeStart[2][i]; j <= m_RangeEnd[2][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[2]].m_afNodeInfo & afNodeTypes)) continue; + + int rgX = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[0]; + if (rgX > m_maxBoxX) break; + if (rgX < m_minBoxX) continue; + int rgY = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[1]; + if (rgY < m_minBoxY) continue; + if (rgY > m_maxBoxY) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[2]); + } + } + +#if 0 + // Verify our answers. + // + int iNearestCheck = -1; + m_flShortest = 8192;// find nodes within this radius + + for ( i = 0 ; i < m_cNodes ; i++ ) + { + float flDist = ( vecOrigin - m_pNodes[ i ].m_vecOriginPeek ).Length(); + + if ( flDist < m_flShortest ) + { + // make sure that vecOrigin can trace to this node! + UTIL_TraceLine ( vecOrigin, m_pNodes[ i ].m_vecOriginPeek, ignore_monsters, 0, &tr ); + + if ( tr.flFraction == 1.0 ) + { + iNearestCheck = i; + m_flShortest = flDist; + } + } + } + + if (iNearestCheck != m_iNearest) + { + ALERT( at_aiconsole, "NOT closest %d(%f,%f,%f) %d(%f,%f,%f).\n", + iNearestCheck, + m_pNodes[iNearestCheck].m_vecOriginPeek.x, + m_pNodes[iNearestCheck].m_vecOriginPeek.y, + m_pNodes[iNearestCheck].m_vecOriginPeek.z, + m_iNearest, + (m_iNearest == -1?0.0:m_pNodes[m_iNearest].m_vecOriginPeek.x), + (m_iNearest == -1?0.0:m_pNodes[m_iNearest].m_vecOriginPeek.y), + (m_iNearest == -1?0.0:m_pNodes[m_iNearest].m_vecOriginPeek.z)); + } + if (m_iNearest == -1) + { + ALERT(at_aiconsole, "All that work for nothing.\n"); + } +#endif + m_Cache[iHash].v = vecOrigin; + m_Cache[iHash].n = m_iNearest; + return m_iNearest; +} + +//========================================================= +// CGraph - ShowNodeConnections - draws a line from the given node +// to all connected nodes +//========================================================= +void CGraph :: ShowNodeConnections ( int iNode ) +{ + Vector vecSpot; + CNode *pNode; + CNode *pLinkNode; + int i; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available or built + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return; + } + + if ( iNode < 0 ) + { + ALERT( at_aiconsole, "Can't show connections for node %d\n", iNode ); + return; + } + + pNode = &m_pNodes[ iNode ]; + + UTIL_ParticleEffect( pNode->m_vecOrigin, g_vecZero, 255, 20 );// show node position + + if ( pNode->m_cNumLinks <= 0 ) + {// no connections! + ALERT ( at_aiconsole, "**No Connections!\n" ); + } + + for ( i = 0 ; i < pNode->m_cNumLinks ; i++ ) + { + + pLinkNode = &Node( NodeLink( iNode, i).m_iDestNode ); + vecSpot = pLinkNode->m_vecOrigin; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( m_pNodes[ iNode ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ iNode ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ iNode ].m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + NODE_HEIGHT ); + MESSAGE_END(); + + } +} + +//========================================================= +// CGraph - LinkVisibleNodes - the first, most basic +// function of node graph creation, this connects every +// node to every other node that it can see. Expects a +// pointer to an empty connection pool and a file pointer +// to write progress to. Returns the total number of initial +// links. +// +// If there's a problem with this process, the index +// of the offending node will be written to piBadNode +//========================================================= +int CGraph :: LinkVisibleNodes ( CLink *pLinkPool, CVirtualFS *file, int *piBadNode ) +{ + int i,j,z; + edict_t *pTraceEnt; + int cTotalLinks, cLinksThisNode, cMaxInitialLinks; + TraceResult tr; + + // !!!BUGBUG - this function returns 0 if there is a problem in the middle of connecting the graph + // it also returns 0 if none of the nodes in a level can see each other. piBadNode is ALWAYS read + // by BuildNodeGraph() if this function returns a 0, so make sure that it doesn't get some random + // number back. + *piBadNode = 0; + + if ( m_cNodes <= 0 ) + { + ALERT ( at_aiconsole, "No Nodes!\n" ); + return FALSE; + } + + // if the file pointer is bad, don't blow up, just don't write the + // file. + if ( !file ) + { + ALERT ( at_aiconsole, "**LinkVisibleNodes:\ncan't write to file." ); + } + else + { + file->Printf( "----------------------------------------------------------------------------\n" ); + file->Printf( "LinkVisibleNodes - Initial Connections\n" ); + file->Printf( "----------------------------------------------------------------------------\n" ); + } + + cTotalLinks = 0;// start with no connections + + // to keep track of the maximum number of initial links any node had so far. + // this lets us keep an eye on MAX_NODE_INITIAL_LINKS to ensure that we are + // being generous enough. + cMaxInitialLinks = 0; + + for ( i = 0 ; i < m_cNodes ; i++ ) + { + cLinksThisNode = 0;// reset this count for each node. + + if ( file ) + { + file->Printf( "Node #%4d:\n\n", i ); + } + + for ( z = 0 ; z < MAX_NODE_INITIAL_LINKS ; z++ ) + { + // clear out the important fields in the link pool for this node + pLinkPool [ cTotalLinks + z ].m_iSrcNode = i;// so each link knows which node it originates from + pLinkPool [ cTotalLinks + z ].m_iDestNode = 0; + pLinkPool [ cTotalLinks + z ].m_pLinkEnt = NULL; + } + + m_pNodes [ i ].m_iFirstLink = cTotalLinks; + + // now build a list of every other node that this node can see + for ( j = 0 ; j < m_cNodes ; j++ ) + { + if ( j == i ) + {// don't connect to self! + continue; + } + +#if 0 + if ( (m_pNodes[ i ].m_afNodeInfo & bits_NODE_WATER) != (m_pNodes[ j ].m_afNodeInfo & bits_NODE_WATER) ) + { + // don't connect water nodes to air nodes or land nodes. It just wouldn't be prudent at this juncture. + continue; + } +#else + if ( (m_pNodes[ i ].m_afNodeInfo & bits_NODE_GROUP_REALM) != (m_pNodes[ j ].m_afNodeInfo & bits_NODE_GROUP_REALM) ) + { + // don't connect air nodes to water nodes to land nodes. It just wouldn't be prudent at this juncture. + continue; + } +#endif + + tr.pHit = NULL;// clear every time so we don't get stuck with last trace's hit ent + pTraceEnt = 0; + + UTIL_TraceLine ( m_pNodes[ i ].m_vecOrigin, + m_pNodes[ j ].m_vecOrigin, + ignore_monsters, + g_pBodyQueueHead,//!!!HACKHACK no real ent to supply here, using a global we don't care about + &tr ); + + + if ( tr.fStartSolid ) + continue; + + if ( tr.flFraction != 1.0 ) + {// trace hit a brush ent, trace backwards to make sure that this ent is the only thing in the way. + + pTraceEnt = tr.pHit;// store the ent that the trace hit, for comparison + + UTIL_TraceLine ( m_pNodes[ j ].m_vecOrigin, + m_pNodes[ i ].m_vecOrigin, + ignore_monsters, + g_pBodyQueueHead,//!!!HACKHACK no real ent to supply here, using a global we don't care about + &tr ); + + +// there is a solid_bsp ent in the way of these two nodes, so we must record several things about in order to keep +// track of it in the pathfinding code, as well as through save and restore of the node graph. ANY data that is manipulated +// as part of the process of adding a LINKENT to a connection here must also be done in CGraph::SetGraphPointers, where reloaded +// graphs are prepared for use. + if ( tr.pHit == pTraceEnt && !FClassnameIs( tr.pHit, "worldspawn" ) ) + { + // get a pointer + pLinkPool [ cTotalLinks ].m_pLinkEnt = VARS( tr.pHit ); + + // record the modelname, so that we can save/load node trees + memcpy( pLinkPool [ cTotalLinks ].m_szLinkEntModelname, STRING( VARS(tr.pHit)->model ), 4 ); + + // set the flag for this ent that indicates that it is attached to the world graph + // if this ent is removed from the world, it must also be removed from the connections + // that it formerly blocked. + if ( !FBitSet( VARS( tr.pHit )->flags, FL_GRAPHED ) ) + { + VARS( tr.pHit )->flags += FL_GRAPHED; + } + } + else + {// even if the ent wasn't there, these nodes couldn't be connected. Skip. + continue; + } + } + + if ( file ) + { + file->Printf( "%4d", j ); + + if ( !FNullEnt( pLinkPool[ cTotalLinks ].m_pLinkEnt ) ) + { + // record info about the ent in the way, if any. + file->Printf( " Entity on connection: %s, name: %s Model: %s", STRING( VARS( pTraceEnt )->classname ), STRING ( VARS( pTraceEnt )->targetname ), STRING ( VARS(tr.pHit)->model ) ); + } + + file->Printf( "\n", j ); + } + + pLinkPool [ cTotalLinks ].m_iDestNode = j; + cLinksThisNode++; + cTotalLinks++; + + // If we hit this, either a level designer is placing too many nodes in the same area, or + // we need to allow for a larger initial link pool. + if ( cLinksThisNode == MAX_NODE_INITIAL_LINKS ) + { + ALERT ( at_aiconsole, "**LinkVisibleNodes:\nNode %d has NodeLinks > MAX_NODE_INITIAL_LINKS", i ); + if( file ) file->Printf( "** NODE %d HAS NodeLinks > MAX_NODE_INITIAL_LINKS **\n", i ); + *piBadNode = i; + return FALSE; + } + else if ( cTotalLinks > MAX_NODE_INITIAL_LINKS * m_cNodes ) + { + // this is paranoia + ALERT ( at_aiconsole, "**LinkVisibleNodes:\nTotalLinks > MAX_NODE_INITIAL_LINKS * NUMNODES" ); + *piBadNode = i; + return FALSE; + } + + if ( cLinksThisNode == 0 && file ) + { + file->Printf( "**NO INITIAL LINKS**\n" ); + } + + // record the connection info in the link pool + WorldGraph.m_pNodes [ i ].m_cNumLinks = cLinksThisNode; + + // keep track of the most initial links ANY node had, so we can figure out + // if we have a large enough default link pool + if ( cLinksThisNode > cMaxInitialLinks ) + { + cMaxInitialLinks = cLinksThisNode; + } + } + + + if ( file ) + { + file->Printf( "----------------------------------------------------------------------------\n" ); + } + } + + if ( file ) + { + file->Printf( "\n%4d Total Initial Connections - %4d Maximum connections for a single node.\n", cTotalLinks, cMaxInitialLinks ); + file->Printf( "----------------------------------------------------------------------------\n\n\n" ); + } + + return cTotalLinks; +} + +//========================================================= +// CGraph - RejectInlineLinks - expects a pointer to a link +// pool, and a pointer to and already-open file ( if you +// want status reports written to disk ). RETURNS the number +// of connections that were rejected +//========================================================= +int CGraph :: RejectInlineLinks ( CLink *pLinkPool, CVirtualFS *file ) +{ + int i,j,k; + + int cRejectedLinks; + + BOOL fRestartLoop;// have to restart the J loop if we eliminate a link. + + CNode *pSrcNode; + CNode *pCheckNode;// the node we are testing for (one of pSrcNode's connections) + CNode *pTestNode;// the node we are checking against ( also one of pSrcNode's connections) + + float flDistToTestNode, flDistToCheckNode; + + Vector2D vec2DirToTestNode, vec2DirToCheckNode; + + if ( file ) + { + file->Printf( "----------------------------------------------------------------------------\n" ); + file->Printf( "InLine Rejection:\n" ); + file->Printf( "----------------------------------------------------------------------------\n" ); + } + + cRejectedLinks = 0; + + for ( i = 0 ; i < m_cNodes ; i++ ) + { + pSrcNode = &m_pNodes[ i ]; + + if ( file ) + { + file->Printf( "Node %3d:\n", i ); + } + + for ( j = 0 ; j < pSrcNode->m_cNumLinks ; j++ ) + { + pCheckNode = &m_pNodes[ pLinkPool[ pSrcNode->m_iFirstLink + j ].m_iDestNode ]; + + vec2DirToCheckNode = ( pCheckNode->m_vecOrigin - pSrcNode->m_vecOrigin ).Make2D(); + flDistToCheckNode = vec2DirToCheckNode.Length(); + vec2DirToCheckNode = vec2DirToCheckNode.Normalize(); + + pLinkPool[ pSrcNode->m_iFirstLink + j ].m_flWeight = flDistToCheckNode; + + fRestartLoop = FALSE; + for ( k = 0 ; k < pSrcNode->m_cNumLinks && !fRestartLoop ; k++ ) + { + if ( k == j ) + { + // don't check against same node + continue; + } + + pTestNode = &m_pNodes [ pLinkPool[ pSrcNode->m_iFirstLink + k ].m_iDestNode ]; + + vec2DirToTestNode = ( pTestNode->m_vecOrigin - pSrcNode->m_vecOrigin ).Make2D(); + + flDistToTestNode = vec2DirToTestNode.Length(); + vec2DirToTestNode = vec2DirToTestNode.Normalize(); + + if ( DotProduct ( vec2DirToCheckNode, vec2DirToTestNode ) >= 0.998 ) + { + // there's a chance that TestNode intersects the line to CheckNode. If so, we should disconnect the link to CheckNode. + if ( flDistToTestNode < flDistToCheckNode ) + { + if ( file ) + { + file->Printf( "REJECTED NODE %3d through Node %3d, Dot = %8f\n", pLinkPool[ pSrcNode->m_iFirstLink + j ].m_iDestNode, pLinkPool[ pSrcNode->m_iFirstLink + k ].m_iDestNode, DotProduct ( vec2DirToCheckNode, vec2DirToTestNode ) ); + } + + pLinkPool[ pSrcNode->m_iFirstLink + j ] = pLinkPool[ pSrcNode->m_iFirstLink + ( pSrcNode->m_cNumLinks - 1 ) ]; + pSrcNode->m_cNumLinks--; + j--; + + cRejectedLinks++;// keeping track of how many links are cut, so that we can return that value. + + fRestartLoop = TRUE; + } + } + } + } + + if ( file ) + { + file->Printf( "----------------------------------------------------------------------------\n\n" ); + } + } + + return cRejectedLinks; +} + +//========================================================= +// TestHull is a modelless clip hull that verifies reachable +// nodes by walking from every node to each of it's connections +//========================================================= +class CTestHull : public CBaseMonster +{ + +public: + void Spawn( entvars_t *pevMasterNode ); + virtual int ObjectCaps( void ) { return CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + float MaxYawSpeed( void ) { return 8.0f; } + void EXPORT CallBuildNodeGraph ( void ); + void BuildNodeGraph ( void ); + void EXPORT ShowBadNode ( void ); + void EXPORT DropDelay ( void ); + void EXPORT PathFind ( void ); + + Vector vecBadNodeOrigin; +}; + +LINK_ENTITY_TO_CLASS( testhull, CTestHull ); + +//========================================================= +// CTestHull::Spawn +//========================================================= +void CTestHull :: Spawn( entvars_t *pevMasterNode ) +{ + SET_MODEL(ENT(pev), "models/player.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + pev->health = 50; + + if ( WorldGraph.m_fGraphPresent ) + { + // graph loaded from disk, so we don't need the test hull + SetThink(&CTestHull :: SUB_Remove ); + SetNextThink( 0 ); + } + else + { + SetThink(&CTestHull :: DropDelay ); + SetNextThink( 1 ); + } + + // Make this invisible + // UNDONE: Shouldn't we just use EF_NODRAW? This doesn't need to go to the client. + pev->rendermode = kRenderTransTexture; + pev->renderamt = 0; +} + +//========================================================= +// TestHull::DropDelay - spawns TestHull on top of +// the 0th node and drops it to the ground. +//========================================================= +void CTestHull::DropDelay ( void ) +{ + UTIL_CenterPrintAll( "Node Graph out of Date. Rebuilding..." ); + + UTIL_SetOrigin ( this, WorldGraph.m_pNodes[ 0 ].m_vecOrigin ); + + SetThink(&CTestHull:: CallBuildNodeGraph ); + + SetNextThink( 1 ); +} + +//========================================================= +// nodes start out as ents in the world. As they are spawned, +// the node info is recorded then the ents are discarded. +//========================================================= +void CNodeEnt :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "hinttype")) + { + m_sHintType = (short)atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + + if (FStrEq(pkvd->szKeyName, "activity")) + { + m_sHintActivity = (short)atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +//========================================================= +//========================================================= +void CNodeEnt :: Spawn( void ) +{ + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_NOT;// always solid_not + + if ( WorldGraph.m_fGraphPresent ) + { + // graph loaded from disk, so discard all these node ents as soon as they spawn + REMOVE_ENTITY( edict() ); + return; + } + + if ( WorldGraph.m_cNodes == 0 ) + { + // this is the first node to spawn, spawn the test hull entity that builds and walks the node tree + CTestHull *pHull = GetClassPtr((CTestHull *)NULL); + pHull->Spawn( pev ); + } + + if ( WorldGraph.m_cNodes >= MAX_NODES ) + { + ALERT ( at_aiconsole, "cNodes > MAX_NODES\n" ); + return; + } + + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_vecOriginPeek = + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_vecOrigin = pev->origin; + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_flHintYaw = pev->angles.y; + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_sHintType = m_sHintType; + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_sHintActivity = m_sHintActivity; + + if (FClassnameIs( pev, "info_node_air" )) + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_afNodeInfo = bits_NODE_AIR; + else + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_afNodeInfo = 0; + + WorldGraph.m_cNodes++; + + REMOVE_ENTITY( edict() ); +} + +//========================================================= +// CTestHull - ShowBadNode - makes a bad node fizzle. When +// there's a problem with node graph generation, the test +// hull will be placed up the bad node's location and will generate +// particles +//========================================================= +void CTestHull :: ShowBadNode( void ) +{ + pev->movetype = MOVETYPE_FLY; + pev->angles.y = pev->angles.y + 4; + + UTIL_MakeVectors ( pev->angles ); + + UTIL_ParticleEffect ( pev->origin, g_vecZero, 255, 25 ); + UTIL_ParticleEffect ( pev->origin + gpGlobals->v_forward * 64, g_vecZero, 255, 25 ); + UTIL_ParticleEffect ( pev->origin - gpGlobals->v_forward * 64, g_vecZero, 255, 25 ); + UTIL_ParticleEffect ( pev->origin + gpGlobals->v_right * 64, g_vecZero, 255, 25 ); + UTIL_ParticleEffect ( pev->origin - gpGlobals->v_right * 64, g_vecZero, 255, 25 ); + + SetNextThink( 0.1 ); +} + +extern BOOL gTouchDisabled; +void CTestHull::CallBuildNodeGraph( void ) +{ + // TOUCH HACK -- Don't allow this entity to call anyone's "touch" function + gTouchDisabled = TRUE; + BuildNodeGraph(); + gTouchDisabled = FALSE; + // Undo TOUCH HACK +} + +//========================================================= +// BuildNodeGraph - think function called by the empty walk +// hull that is spawned by the first node to spawn. This +// function links all nodes that can see each other, then +// eliminates all inline links, then uses a monster-sized +// hull that walks between each node and each of its links +// to ensure that a monster can actually fit through the space +//========================================================= +void CTestHull :: BuildNodeGraph( void ) +{ + TraceResult tr; + CVirtualFS file; + + char szNrpFilename [MAX_PATH];// text node report filename + + CLink *pTempPool; // temporary link pool + + CNode *pSrcNode;// node we're currently working with + CNode *pDestNode;// the other node in comparison operations + + BOOL fSkipRemainingHulls;//if smallest hull can't fit, don't check any others + BOOL fPairsValid;// are all links in the graph evenly paired? + + int i, j, hull; + + int iBadNode;// this is the node that caused graph generation to fail + + int cMaxInitialLinks = 0; + int cMaxValidLinks = 0; + + int iPoolIndex = 0; + int cPoolLinks;// number of links in the pool. + + Vector vecDirToCheckNode; + Vector vecDirToTestNode; + Vector vecStepCheckDir; + Vector vecTraceSpot; + Vector vecSpot; + + Vector2D vec2DirToCheckNode; + Vector2D vec2DirToTestNode; + Vector2D vec2StepCheckDir; + Vector2D vec2TraceSpot; + Vector2D vec2Spot; + + float flYaw;// use this stuff to walk the hull between nodes + float flDist; + int step; + + SetThink(&CTestHull :: SUB_Remove );// no matter what happens, the hull gets rid of itself. + SetNextThink( 0 ); + + // malloc a swollen temporary connection pool that we trim down after we know exactly how many connections there are. + pTempPool = (CLink *)calloc ( sizeof ( CLink ) , ( WorldGraph.m_cNodes * MAX_NODE_INITIAL_LINKS ) ); + if ( !pTempPool ) + { + ALERT ( at_aiconsole, "**Could not malloc TempPool!\n" ); + return; + } + + Q_snprintf( szNrpFilename, sizeof( szNrpFilename ), "maps/%s.nrp", STRING( gpGlobals->mapname )); + + file.Printf( "Node Graph Report for map: %s.bsp\n", STRING(gpGlobals->mapname) ); + file.Printf( "%d Total Nodes\n\n", WorldGraph.m_cNodes ); + + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + // print all node numbers and their locations to the file. + WorldGraph.m_pNodes[ i ].m_cNumLinks = 0; + WorldGraph.m_pNodes[ i ].m_iFirstLink = 0; + memset(WorldGraph.m_pNodes[ i ].m_pNextBestNode, 0, sizeof(WorldGraph.m_pNodes[ i ].m_pNextBestNode)); + + file.Printf( "Node# %4d\n", i ); + file.Printf( "Location %4d,%4d,%4d\n",(int)WorldGraph.m_pNodes[ i ].m_vecOrigin.x, (int)WorldGraph.m_pNodes[ i ].m_vecOrigin.y, (int)WorldGraph.m_pNodes[ i ].m_vecOrigin.z ); + file.Printf( "HintType: %4d\n", WorldGraph.m_pNodes[ i ].m_sHintType ); + file.Printf( "HintActivity: %4d\n", WorldGraph.m_pNodes[ i ].m_sHintActivity ); + file.Printf( "HintYaw: %4f\n", WorldGraph.m_pNodes[ i ].m_flHintYaw ); + file.Printf( "-------------------------------------------------------------------------------\n" ); + } + + file.Printf( "\n\n" ); + + // Automatically recognize WATER nodes and drop the LAND nodes to the floor. + // + for ( i = 0; i < WorldGraph.m_cNodes; i++) + { + if (WorldGraph.m_pNodes[ i ].m_afNodeInfo & bits_NODE_AIR) + { + // do nothing + } + else if (UTIL_PointContents(WorldGraph.m_pNodes[ i ].m_vecOrigin) == CONTENTS_WATER) + { + WorldGraph.m_pNodes[ i ].m_afNodeInfo |= bits_NODE_WATER; + } + else + { + WorldGraph.m_pNodes[ i ].m_afNodeInfo |= bits_NODE_LAND; + + // trace to the ground, then pop up 8 units and place node there to make it + // easier for them to connect (think stairs, chairs, and bumps in the floor). + // After the routing is done, push them back down. + // + TraceResult tr; + + UTIL_TraceLine ( WorldGraph.m_pNodes[i].m_vecOrigin, + WorldGraph.m_pNodes[i].m_vecOrigin - Vector ( 0, 0, 384 ), + ignore_monsters, + g_pBodyQueueHead,//!!!HACKHACK no real ent to supply here, using a global we don't care about + &tr ); + + // This trace is ONLY used if we hit an entity flagged with FL_WORLDBRUSH + TraceResult trEnt; + UTIL_TraceLine ( WorldGraph.m_pNodes[i].m_vecOrigin, + WorldGraph.m_pNodes[i].m_vecOrigin - Vector ( 0, 0, 384 ), + dont_ignore_monsters, + g_pBodyQueueHead,//!!!HACKHACK no real ent to supply here, using a global we don't care about + &trEnt ); + + + // Did we hit something closer than the floor? + if ( trEnt.flFraction < tr.flFraction ) + { + // If it was a world brush entity, copy the node location + if ( trEnt.pHit && (trEnt.pHit->v.flags & FL_WORLDBRUSH) ) + tr.vecEndPos = trEnt.vecEndPos; + } + + WorldGraph.m_pNodes[i].m_vecOriginPeek.z = + WorldGraph.m_pNodes[i].m_vecOrigin.z = tr.vecEndPos.z + NODE_HEIGHT; + } + } + + cPoolLinks = WorldGraph.LinkVisibleNodes( pTempPool, &file, &iBadNode ); + + if ( !cPoolLinks ) + { + ALERT ( at_aiconsole, "**ConnectVisibleNodes FAILED!\n" ); + + SetThink(&CTestHull :: ShowBadNode );// send the hull off to show the offending node. + //pev->solid = SOLID_NOT; + pev->origin = WorldGraph.m_pNodes[ iBadNode ].m_vecOrigin; + + if ( pTempPool ) + { + free ( pTempPool ); + } + + // dump the report onto disk + SAVE_FILE( szNrpFilename, file.GetBuffer(), file.GetSize( )); + + return; + } + +// send the walkhull to all of this node's connections now. We'll do this here since +// so much of it relies on being able to control the test hull. + file.Printf( "----------------------------------------------------------------------------\n" ); + file.Printf( "Walk Rejection:\n"); + + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + pSrcNode = &WorldGraph.m_pNodes[ i ]; + + file.Printf( "-------------------------------------------------------------------------------\n"); + file.Printf( "Node %4d:\n\n", i ); + + for ( j = 0 ; j < pSrcNode->m_cNumLinks ; j++ ) + { + // assume that all hulls can walk this link, then eliminate the ones that can't. + pTempPool [ pSrcNode->m_iFirstLink + j ].m_afLinkInfo = bits_LINK_SMALL_HULL | bits_LINK_HUMAN_HULL | bits_LINK_LARGE_HULL | bits_LINK_FLY_HULL; + + + // do a check for each hull size. + + // if we can't fit a tiny hull through a connection, no other hulls with fit either, so we + // should just fall out of the loop. Do so by setting the SkipRemainingHulls flag. + fSkipRemainingHulls = FALSE; + for ( hull = 0 ; hull < MAX_NODE_HULLS; hull++ ) + { + if (fSkipRemainingHulls && (hull == NODE_HUMAN_HULL || hull == NODE_LARGE_HULL)) // skip the remaining walk hulls + continue; + + switch ( hull ) + { + case NODE_SMALL_HULL: + UTIL_SetSize(pev, Vector(-12, -12, 0), Vector(12, 12, 24)); + break; + case NODE_HUMAN_HULL: + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX ); + break; + case NODE_LARGE_HULL: + UTIL_SetSize(pev, Vector(-32, -32, 0), Vector(32, 32, 64)); + break; + case NODE_FLY_HULL: + UTIL_SetSize(pev, Vector(-32, -32, 0), Vector(32, 32, 64)); + // UTIL_SetSize(pev, Vector(0, 0, 0), Vector(0, 0, 0)); + break; + } + + UTIL_SetOrigin ( this, pSrcNode->m_vecOrigin );// place the hull on the node + + if ( !FBitSet ( pev->flags, FL_ONGROUND ) ) + { + ALERT ( at_aiconsole, "OFFGROUND!\n" ); + } + + // now build a yaw that points to the dest node, and get the distance. + if ( j < 0 ) + { + ALERT ( at_aiconsole, "**** j = %d ****\n", j ); + if ( pTempPool ) + { + free ( pTempPool ); + } + + // dump the report onto disk + SAVE_FILE( szNrpFilename, file.GetBuffer(), file.GetSize( )); + return; + } + + pDestNode = &WorldGraph.m_pNodes [ pTempPool[ pSrcNode->m_iFirstLink + j ].m_iDestNode ]; + + vecSpot = pDestNode->m_vecOrigin; + + if (hull < NODE_FLY_HULL) + { + int SaveFlags = pev->flags; + int MoveMode = WALKMOVE_WORLDONLY; + if (pSrcNode->m_afNodeInfo & bits_NODE_WATER) + { + pev->flags |= FL_SWIM; + MoveMode = WALKMOVE_NORMAL; + } + + flYaw = UTIL_VecToYaw ( pDestNode->m_vecOrigin - pev->origin ); + + flDist = ( vecSpot - pev->origin ).Length2D(); + + int fWalkFailed = FALSE; + + // in this loop we take tiny steps from the current node to the nodes that it links to, one at a time. + for ( step = 0 ; step < flDist && !fWalkFailed ; step += HULL_STEP_SIZE ) + { + float stepSize = HULL_STEP_SIZE; + + if ( (step + stepSize) >= (flDist-1) ) + stepSize = (flDist - step) - 1; + + if ( !WALK_MOVE( ENT(pev), flYaw, stepSize, MoveMode ) ) + {// can't take the next step + + fWalkFailed = TRUE; + break; + } + } + + if (!fWalkFailed && (pev->origin - vecSpot).Length() > 64) + { + // ALERT( at_console, "bogus walk\n"); + // we thought we + fWalkFailed = TRUE; + } + + if (fWalkFailed) + { + + //pTempPool[ pSrcNode->m_iFirstLink + j ] = pTempPool [ pSrcNode->m_iFirstLink + ( pSrcNode->m_cNumLinks - 1 ) ]; + + // now me must eliminate the hull that couldn't walk this connection + switch ( hull ) + { + case NODE_SMALL_HULL: // if this hull can't fit, nothing can, so drop the connection + file.Printf( "NODE_SMALL_HULL step %f\n", step ); + pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo &= ~(bits_LINK_SMALL_HULL | bits_LINK_HUMAN_HULL | bits_LINK_LARGE_HULL); + fSkipRemainingHulls = TRUE;// don't bother checking larger hulls + break; + case NODE_HUMAN_HULL: + file.Printf( "NODE_HUMAN_HULL step %f\n", step ); + pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo &= ~(bits_LINK_HUMAN_HULL | bits_LINK_LARGE_HULL); + fSkipRemainingHulls = TRUE;// don't bother checking larger hulls + break; + case NODE_LARGE_HULL: + file.Printf( "NODE_LARGE_HULL step %f\n", step ); + pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo &= ~bits_LINK_LARGE_HULL; + break; + } + } + pev->flags = SaveFlags; + } + else + { + TraceResult tr; + + UTIL_TraceHull( pSrcNode->m_vecOrigin + Vector( 0, 0, 32 ), pDestNode->m_vecOriginPeek + Vector( 0, 0, 32 ), ignore_monsters, large_hull, ENT( pev ), &tr ); + if (tr.fStartSolid || tr.flFraction < 1.0) + { + pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo &= ~bits_LINK_FLY_HULL; + } + } + } + + if (pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo == 0) + { + file.Printf( "Rejected Node %3d - Unreachable by ", pTempPool [ pSrcNode->m_iFirstLink + j ].m_iDestNode ); + pTempPool[ pSrcNode->m_iFirstLink + j ] = pTempPool [ pSrcNode->m_iFirstLink + ( pSrcNode->m_cNumLinks - 1 ) ]; + file.Printf( "Any Hull\n" ); + + pSrcNode->m_cNumLinks--; + cPoolLinks--;// we just removed a link, so decrement the total number of links in the pool. + j--; + } + + } + } + file.Printf( "-------------------------------------------------------------------------------\n\n\n"); + + cPoolLinks -= WorldGraph.RejectInlineLinks ( pTempPool, &file ); + + // now malloc a pool just large enough to hold the links that are actually used + WorldGraph.m_pLinkPool = (CLink *) calloc ( sizeof ( CLink ), cPoolLinks ); + + if ( !WorldGraph.m_pLinkPool ) + { + // couldn't make the link pool! + ALERT ( at_aiconsole, "Couldn't malloc LinkPool!\n" ); + if ( pTempPool ) + { + free ( pTempPool ); + } + + // dump the report onto disk + SAVE_FILE( szNrpFilename, file.GetBuffer(), file.GetSize( )); + return; + } + WorldGraph.m_cLinks = cPoolLinks; + + // copy only the used portions of the TempPool into the graph's link pool + int iFinalPoolIndex = 0; + int iOldFirstLink; + + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + iOldFirstLink = WorldGraph.m_pNodes[ i ].m_iFirstLink;// store this, because we have to re-assign it before entering the copy loop + + WorldGraph.m_pNodes[ i ].m_iFirstLink = iFinalPoolIndex; + + for ( j = 0 ; j < WorldGraph.m_pNodes[ i ].m_cNumLinks ; j++ ) + { + WorldGraph.m_pLinkPool[ iFinalPoolIndex++ ] = pTempPool[ iOldFirstLink + j ]; + } + } + + // Node sorting numbers linked nodes close to each other + // + WorldGraph.SortNodes(); + + // This is used for HashSearch + // + WorldGraph.BuildLinkLookups(); + + fPairsValid = TRUE; // assume that the connection pairs are all valid to start + + file.Printf( "\n\n-------------------------------------------------------------------------------\n"); + file.Printf( "Link Pairings:\n"); + +// link integrity check. The idea here is that if Node A links to Node B, node B should +// link to node A. If not, we have a situation that prevents us from using a basic +// optimization in the FindNearestLink function. + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + for ( j = 0 ; j < WorldGraph.m_pNodes[ i ].m_cNumLinks ; j++ ) + { + int iLink; + WorldGraph.HashSearch(WorldGraph.INodeLink(i,j), i, iLink); + if (iLink < 0) + { + fPairsValid = FALSE;// unmatched link pair. + file.Printf( "WARNING: Node %3d does not connect back to Node %3d\n", WorldGraph.INodeLink(i, j), i); + } + } + } + + // !!!LATER - if all connections are properly paired, when can enable an optimization in the pathfinding code + // (in the find nearest line function) + if ( fPairsValid ) + { + file.Printf( "\nAll Connections are Paired!\n"); + } + + file.Printf( "-------------------------------------------------------------------------------\n"); + file.Printf( "\n\n-------------------------------------------------------------------------------\n"); + file.Printf( "Total Number of Connections in Pool: %d\n", cPoolLinks ); + file.Printf( "-------------------------------------------------------------------------------\n"); + file.Printf( "Connection Pool: %d bytes\n", sizeof ( CLink ) * cPoolLinks ); + file.Printf( "-------------------------------------------------------------------------------\n"); + + + ALERT ( at_aiconsole, "%d Nodes, %d Connections\n", WorldGraph.m_cNodes, cPoolLinks ); + + // This is used for FindNearestNode + // + WorldGraph.BuildRegionTables(); + + + // Push all of the LAND nodes down to the ground now. Leave the water and air nodes alone. + // + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + if ((WorldGraph.m_pNodes[ i ].m_afNodeInfo & bits_NODE_LAND)) + { + WorldGraph.m_pNodes[ i ].m_vecOrigin.z -= NODE_HEIGHT; + } + } + + if ( pTempPool ) + { + // free the temp pool + free ( pTempPool ); + } + + // dump the report onto disk + SAVE_FILE( szNrpFilename, file.GetBuffer(), file.GetSize( )); + + // We now have some graphing capabilities. + // + WorldGraph.m_fGraphPresent = TRUE;//graph is in memory. + WorldGraph.m_fGraphPointersSet = TRUE;// since the graph was generated, the pointers are ready + WorldGraph.m_fRoutingComplete = FALSE; // Optimal routes aren't computed, yet. + + // Compute and compress the routing information. + // + WorldGraph.ComputeStaticRoutingTables(); + + // save the node graph for this level + WorldGraph.FSaveGraph( (char *)STRING( gpGlobals->mapname ) ); + ALERT( at_debug, "Done.\n"); +} + + +//========================================================= +// returns a hardcoded path. +//========================================================= +void CTestHull :: PathFind ( void ) +{ + int iPath[ 50 ]; + int iPathSize; + int i; + CNode *pNode, *pNextNode; + + if ( !WorldGraph.m_fGraphPresent || !WorldGraph.m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return; + } + + iPathSize = WorldGraph.FindShortestPath ( iPath, 0, 19, 0, 0 ); // UNDONE use hull constant + + if ( !iPathSize ) + { + ALERT ( at_aiconsole, "No Path!\n" ); + return; + } + + ALERT ( at_aiconsole, "%d\n", iPathSize ); + + pNode = &WorldGraph.m_pNodes[ iPath [ 0 ] ]; + + for ( i = 0 ; i < iPathSize - 1 ; i++ ) + { + + pNextNode = &WorldGraph.m_pNodes[ iPath [ i + 1 ] ]; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( pNode->m_vecOrigin.x ); + WRITE_COORD( pNode->m_vecOrigin.y ); + WRITE_COORD( pNode->m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( pNextNode->m_vecOrigin.x); + WRITE_COORD( pNextNode->m_vecOrigin.y); + WRITE_COORD( pNextNode->m_vecOrigin.z + NODE_HEIGHT); + MESSAGE_END(); + + pNode = pNextNode; + } + +} + + +//========================================================= +// CStack Constructor +//========================================================= +CStack :: CStack( void ) +{ + m_level = 0; +} + +//========================================================= +// pushes a value onto the stack +//========================================================= +void CStack :: Push( int value ) +{ + if ( m_level >= MAX_STACK_NODES ) + { + printf("Error!\n"); + return; + } + m_stack[m_level] = value; + m_level++; +} + +//========================================================= +// pops a value off of the stack +//========================================================= +int CStack :: Pop( void ) +{ + if ( m_level <= 0 ) + return -1; + + m_level--; + return m_stack[ m_level ]; +} + +//========================================================= +// returns the value on the top of the stack +//========================================================= +int CStack :: Top ( void ) +{ + return m_stack[ m_level - 1 ]; +} + +//========================================================= +// copies every element on the stack into an array LIFO +//========================================================= +void CStack :: CopyToArray ( int *piArray ) +{ + int i; + + for ( i = 0 ; i < m_level ; i++ ) + { + piArray[ i ] = m_stack[ i ]; + } +} + +//========================================================= +// CQueue constructor +//========================================================= +CQueue :: CQueue( void ) +{ + m_cSize = 0; + m_head = 0; + m_tail = -1; +} + +//========================================================= +// inserts a value into the queue +//========================================================= +void CQueue :: Insert ( int iValue, float fPriority ) +{ + + if ( Full() ) + { + printf ( "Queue is full!\n" ); + return; + } + + m_tail++; + + if ( m_tail == MAX_STACK_NODES ) + {//wrap around + m_tail = 0; + } + + m_queue[ m_tail ].Id = iValue; + m_queue[ m_tail ].Priority = fPriority; + m_cSize++; +} + +//========================================================= +// removes a value from the queue (FIFO) +//========================================================= +int CQueue :: Remove ( float &fPriority ) +{ + if ( m_head == MAX_STACK_NODES ) + {// wrap + m_head = 0; + } + + m_cSize--; + fPriority = m_queue[ m_head ].Priority; + return m_queue[ m_head++ ].Id; +} + +//========================================================= +// CQueue constructor +//========================================================= +CQueuePriority :: CQueuePriority( void ) +{ + m_cSize = 0; +} + +//========================================================= +// inserts a value into the priority queue +//========================================================= +void CQueuePriority :: Insert( int iValue, float fPriority ) +{ + + if ( Full() ) + { + printf ( "Queue is full!\n" ); + return; + } + + m_heap[ m_cSize ].Priority = fPriority; + m_heap[ m_cSize ].Id = iValue; + m_cSize++; + Heap_SiftUp(); +} + +//========================================================= +// removes the smallest item from the priority queue +// +//========================================================= +int CQueuePriority :: Remove( float &fPriority ) +{ + int iReturn = m_heap[ 0 ].Id; + fPriority = m_heap[ 0 ].Priority; + + m_cSize--; + + m_heap[ 0 ] = m_heap[ m_cSize ]; + + Heap_SiftDown(0); + return iReturn; +} + +#define HEAP_LEFT_CHILD(x) (2*(x)+1) +#define HEAP_RIGHT_CHILD(x) (2*(x)+2) +#define HEAP_PARENT(x) (((x)-1)/2) + +void CQueuePriority::Heap_SiftDown(int iSubRoot) +{ + int parent = iSubRoot; + int child = HEAP_LEFT_CHILD(parent); + + struct tag_HEAP_NODE Ref = m_heap[ parent ]; + + while (child < m_cSize) + { + int rightchild = HEAP_RIGHT_CHILD(parent); + if (rightchild < m_cSize) + { + if ( m_heap[ rightchild ].Priority < m_heap[ child ].Priority ) + { + child = rightchild; + } + } + if ( Ref.Priority <= m_heap[ child ].Priority ) + break; + + m_heap[ parent ] = m_heap[ child ]; + parent = child; + child = HEAP_LEFT_CHILD(parent); + } + m_heap[ parent ] = Ref; +} + +void CQueuePriority::Heap_SiftUp(void) +{ + int child = m_cSize-1; + while (child) + { + int parent = HEAP_PARENT(child); + if ( m_heap[ parent ].Priority <= m_heap[ child ].Priority ) + break; + + struct tag_HEAP_NODE Tmp; + Tmp = m_heap[ child ]; + m_heap[ child ] = m_heap[ parent ]; + m_heap[ parent ] = Tmp; + + child = parent; + } +} + +//========================================================= +// CGraph - FLoadGraph - attempts to load a node graph from disk. +// if the current level is maps/snar.bsp, maps/graphs/snar.nod +// will be loaded. If file cannot be loaded, the node tree +// will be created and saved to disk. +//========================================================= +int CGraph :: FLoadGraph ( char *szMapName ) +{ + char szFilename[MAX_PATH]; + int iVersion; + int iResult; + int length; + byte *aMemFile; + byte *pMemFile; + + Q_snprintf( szFilename, sizeof( szFilename ), "maps/%s.bsp", szMapName ); + + iResult = MAP_READ_LUMP( szFilename, LUMP_AINODEGRAPH, (void **)&aMemFile, &length ); + + if( iResult == LUMP_LOAD_NO_EXTRADATA ) + { + // this map doesn't support including new lumps. fallback to old method + Q_snprintf( szFilename, sizeof( szFilename ), "maps/graphs/%s.nod", szMapName ); + + aMemFile = LOAD_FILE( szFilename, &length ); + } + else if( iResult != LUMP_LOAD_OK ) + { + return FALSE; + } + + pMemFile = aMemFile; + + if( !aMemFile ) + { + // nodegraph is completely missed + return FALSE; + } + else + { + // Read the graph version number + // + length -= sizeof(int); + if (length < 0) goto ShortFile; + memcpy(&iVersion, pMemFile, sizeof(int)); + pMemFile += sizeof(int); + + if ( iVersion != GRAPH_VERSION ) + { + // This file was written by a different build of the dll! + // + ALERT ( at_aiconsole, "**ERROR** Graph version is %d, expected %d\n",iVersion, GRAPH_VERSION ); + goto ShortFile; + } + + // Read the graph class + // + length -= sizeof(CGraph); + if (length < 0) goto ShortFile; + memcpy(this, pMemFile, sizeof(CGraph)); + pMemFile += sizeof(CGraph); + + // Set the pointers to zero, just in case we run out of memory. + // + m_pNodes = NULL; + m_pLinkPool = NULL; + m_di = NULL; + m_pRouteInfo = NULL; + m_pHashLinks = NULL; + + + // Malloc for the nodes + // + m_pNodes = ( CNode * )calloc ( sizeof ( CNode ), m_cNodes ); + + if ( !m_pNodes ) + { + ALERT ( at_aiconsole, "**ERROR**\nCouldn't malloc %d nodes!\n", m_cNodes ); + goto NoMemory; + } + + // Read in all the nodes + // + length -= sizeof(CNode) * m_cNodes; + if (length < 0) goto ShortFile; + memcpy(m_pNodes, pMemFile, sizeof(CNode)*m_cNodes); + pMemFile += sizeof(CNode) * m_cNodes; + + + // Malloc for the link pool + // + m_pLinkPool = ( CLink * )calloc ( sizeof ( CLink ), m_cLinks ); + + if ( !m_pLinkPool ) + { + ALERT ( at_aiconsole, "**ERROR**\nCouldn't malloc %d link!\n", m_cLinks ); + goto NoMemory; + } + + // Read in all the links + // + length -= sizeof(CLink)*m_cLinks; + if (length < 0) goto ShortFile; + memcpy(m_pLinkPool, pMemFile, sizeof(CLink)*m_cLinks); + pMemFile += sizeof(CLink)*m_cLinks; + + // Malloc for the sorting info. + // + m_di = (DIST_INFO *)calloc( sizeof(DIST_INFO), m_cNodes ); + if ( !m_di ) + { + ALERT ( at_aiconsole, "***ERROR**\nCouldn't malloc %d entries sorting nodes!\n", m_cNodes ); + goto NoMemory; + } + + // Read it in. + // + length -= sizeof(DIST_INFO)*m_cNodes; + if (length < 0) goto ShortFile; + memcpy(m_di, pMemFile, sizeof(DIST_INFO)*m_cNodes); + pMemFile += sizeof(DIST_INFO)*m_cNodes; + + // Malloc for the routing info. + // + m_fRoutingComplete = FALSE; + m_pRouteInfo = (char *)calloc( sizeof(char), m_nRouteInfo ); + if ( !m_pRouteInfo ) + { + ALERT ( at_aiconsole, "***ERROR**\nCounldn't malloc %d route bytes!\n", m_nRouteInfo ); + goto NoMemory; + } + m_CheckedCounter = 0; + for (int i = 0; i < m_cNodes; i++) + { + m_di[i].m_CheckedEvent = 0; + } + + // Read in the route information. + // + length -= sizeof(char)*m_nRouteInfo; + if (length < 0) goto ShortFile; + memcpy(m_pRouteInfo, pMemFile, sizeof(char)*m_nRouteInfo); + pMemFile += sizeof(char)*m_nRouteInfo; + m_fRoutingComplete = TRUE;; + + // malloc for the hash links + // + m_pHashLinks = (short *)calloc(sizeof(short), m_nHashLinks); + if (!m_pHashLinks) + { + ALERT ( at_aiconsole, "***ERROR**\nCounldn't malloc %d hash link bytes!\n", m_nHashLinks ); + goto NoMemory; + } + + // Read in the hash link information + // + length -= sizeof(short)*m_nHashLinks; + if (length < 0) goto ShortFile; + memcpy(m_pHashLinks, pMemFile, sizeof(short)*m_nHashLinks); + pMemFile += sizeof(short)*m_nHashLinks; + + // Set the graph present flag, clear the pointers set flag + // + m_fGraphPresent = TRUE; + m_fGraphPointersSet = FALSE; + + FREE_FILE(aMemFile); + + if (length != 0) + { + ALERT ( at_aiconsole, "***WARNING***:Node graph was longer than expected by %d bytes.!\n", length); + } + + return TRUE; + } + +ShortFile: +NoMemory: + FREE_FILE(aMemFile); + return FALSE; +} + +//========================================================= +// CGraph - FSaveGraph - It's not rocket science. +// this WILL overwrite existing files. +//========================================================= +int CGraph :: FSaveGraph ( char *szMapName ) +{ + int iVersion = GRAPH_VERSION; + char szFilename[MAX_PATH]; + CVirtualFS file; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + { + // protect us in the case that the node graph isn't available or built + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return FALSE; + } + + Q_snprintf( szFilename, sizeof( szFilename ), "maps/%s.bsp", szMapName ); + ALERT ( at_aiconsole, "Write LUMP_AINODEGRAPH to %s\n", szFilename ); + + // write the version + file.Write( &iVersion, sizeof( int )); + + // write the CGraph class + file.Write( this, sizeof( CGraph )); + + // write the nodes + file.Write( m_pNodes, sizeof( CNode ) * m_cNodes ); + + // write the links + file.Write( m_pLinkPool, sizeof( CLink ) * m_cLinks ); + + file.Write( m_di, sizeof( DIST_INFO ) * m_cNodes ); + + // Write the route info. + if( m_pRouteInfo && m_nRouteInfo ) + { + file.Write( m_pRouteInfo, m_nRouteInfo ); + } + + if( m_pHashLinks && m_nHashLinks ) + { + file.Write( m_pHashLinks, sizeof( short ) * m_nHashLinks ); + } + + // dump into real file + int iResult = MAP_SAVE_LUMP( szFilename, LUMP_AINODEGRAPH, file.GetBuffer(), file.GetSize( )); + + if( iResult == LUMP_SAVE_NO_EXTRADATA ) + { + // this map doesn't support including new lumps. fallback to old method + Q_snprintf( szFilename, sizeof( szFilename ), "maps/graphs/%s.nod", szMapName ); + + if( SAVE_FILE( szFilename, file.GetBuffer(), file.GetSize( ))) + return true; + return false; + } + + return (iResult == LUMP_SAVE_OK) ? true : false; +} + +//========================================================= +// CGraph - FSetGraphPointers - Takes the modelnames of +// all of the brush ents that block connections in the node +// graph and resolves them into pointers to those entities. +// this is done after loading the graph from disk, whereupon +// the pointers are not valid. +//========================================================= +int CGraph :: FSetGraphPointers ( void ) +{ + int i; + CBaseEntity *pLinkEnt; + + for ( i = 0 ; i < m_cLinks ; i++ ) + {// go through all of the links + + if ( m_pLinkPool[ i ].m_pLinkEnt != NULL ) + { + char name[5]; + // when graphs are saved, any valid pointers are will be non-zero, signifying that we should + // reset those pointers upon reloading. Any pointers that were NULL when the graph was saved + // will be NULL when reloaded, and will ignored by this function. + + // m_szLinkEntModelname is not necessarily NULL terminated (so we can store it in a more alignment-friendly 4 bytes) + memcpy( name, m_pLinkPool[ i ].m_szLinkEntModelname, 4 ); + name[4] = 0; + pLinkEnt = UTIL_FindEntityByString( NULL, "model", name ); + + if ( !pLinkEnt ) + { + // the ent isn't around anymore? Either there is a major problem, or it was removed from the world + // ( like a func_breakable that's been destroyed or something ). Make sure that LinkEnt is null. + ALERT ( at_aiconsole, "**Could not find model %s\n", name ); + m_pLinkPool[ i ].m_pLinkEnt = NULL; + } + else + { + m_pLinkPool[ i ].m_pLinkEnt = pLinkEnt->pev; + + if ( !FBitSet( m_pLinkPool[ i ].m_pLinkEnt->flags, FL_GRAPHED ) ) + { + m_pLinkPool[ i ].m_pLinkEnt->flags += FL_GRAPHED; + } + } + } + } + + // the pointers are now set. + m_fGraphPointersSet = TRUE; + return TRUE; +} + +//========================================================= +// CGraph - CheckNODFile - this function checks the date of +// the BSP file that was just loaded and the date of the a +// ssociated .NOD file. If the NOD file is not present, or +// is older than the BSP file, we rebuild it. +// +// returns FALSE if the .NOD file doesn't qualify and needs +// to be rebuilt. +// +// !!!BUGBUG - the file times we get back are 20 hours ahead! +// since this happens consistantly, we can still correctly +// determine which of the 2 files is newer. This needs fixed, +// though. ( I now suspect that we are getting GMT back from +// these functions and must compensate for local time ) (sjb) +//========================================================= +int CGraph :: CheckNODFile ( char *szMapName ) +{ + int retValue; + + char szBspFilename[MAX_PATH]; + char szGraphFilename[MAX_PATH]; + + Q_snprintf( szBspFilename, sizeof( szBspFilename ), "maps/%s.bsp", szMapName ); + retValue = MAP_CHECK_LUMP( szBspFilename, LUMP_AINODEGRAPH, NULL ); + + if( retValue == LUMP_LOAD_OK ) + return true; + else if( retValue == LUMP_LOAD_NOT_EXIST ) + return false; + + Q_snprintf( szBspFilename, sizeof( szBspFilename ), "maps/%s.bsp", szMapName ); + Q_snprintf( szGraphFilename, sizeof( szBspFilename ), "maps/graphs/%s.nod", szMapName ); + + retValue = TRUE; + + int iCompare; + + if (COMPARE_FILE_TIME(szBspFilename, szGraphFilename, &iCompare)) + { + if ( iCompare > 0 ) + { + // BSP file is newer. + ALERT ( at_aiconsole, ".NOD File will be updated\n\n" ); + retValue = FALSE; + } + } + else + { + retValue = FALSE; + } + + return retValue; +} + +#define ENTRY_STATE_EMPTY -1 + +struct tagNodePair +{ + short iSrc; + short iDest; +}; + +void CGraph::HashInsert(int iSrcNode, int iDestNode, int iKey) +{ + struct tagNodePair np; + + np.iSrc = iSrcNode; + np.iDest = iDestNode; + CRC32_t dwHash; + CRC32_INIT(&dwHash); + CRC32_PROCESS_BUFFER(&dwHash, &np, sizeof(np)); + dwHash = CRC32_FINAL(dwHash); + + int di = m_HashPrimes[dwHash&15]; + int i = (dwHash >> 4) % m_nHashLinks; + while (m_pHashLinks[i] != ENTRY_STATE_EMPTY) + { + i += di; + if (i >= m_nHashLinks) i -= m_nHashLinks; + } + m_pHashLinks[i] = iKey; +} + +void CGraph::HashSearch(int iSrcNode, int iDestNode, int &iKey) +{ + struct tagNodePair np; + + np.iSrc = iSrcNode; + np.iDest = iDestNode; + CRC32_t dwHash; + CRC32_INIT(&dwHash); + CRC32_PROCESS_BUFFER(&dwHash, &np, sizeof(np)); + dwHash = CRC32_FINAL(dwHash); + + int di = m_HashPrimes[dwHash&15]; + int i = (dwHash >> 4) % m_nHashLinks; + while (m_pHashLinks[i] != ENTRY_STATE_EMPTY) + { + CLink &link = Link(m_pHashLinks[i]); + if (iSrcNode == link.m_iSrcNode && iDestNode == link.m_iDestNode) + { + break; + } + else + { + i += di; + if (i >= m_nHashLinks) i -= m_nHashLinks; + } + } + iKey = m_pHashLinks[i]; +} + +#define NUMBER_OF_PRIMES 177 + +int Primes[NUMBER_OF_PRIMES] = +{ 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, +71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, +157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, +241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, +347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, +439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, +547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, +643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, +751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, +859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, +977, 983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 0 }; + +void CGraph::HashChoosePrimes(int TableSize) +{ + int LargestPrime = TableSize/2; + if (LargestPrime > Primes[NUMBER_OF_PRIMES-2]) + { + LargestPrime = Primes[NUMBER_OF_PRIMES-2]; + } + int Spacing = LargestPrime/16; + + // Pick a set primes that are evenly spaced from (0 to LargestPrime) + // We divide this interval into 16 equal sized zones. We want to find + // one prime number that best represents that zone. + // + int iZone = 0, iPrime = 0; + for (iZone = 1, iPrime = 0; iPrime < 16; iZone += Spacing) + { + // Search for a prime number that is less than the target zone + // number given by iZone. + // + int Lower = Primes[0]; + for (int jPrime = 0; Primes[jPrime] != 0; jPrime++) + { + if (jPrime != 0 && TableSize % Primes[jPrime] == 0) continue; + int Upper = Primes[jPrime]; + if (Lower <= iZone && iZone <= Upper) + { + // Choose the closest lower prime number. + // + if (iZone - Lower <= Upper - iZone) + { + m_HashPrimes[iPrime++] = Lower; + } + else + { + m_HashPrimes[iPrime++] = Upper; + } + break; + } + Lower = Upper; + } + } + + // Alternate negative and positive numbers + // + for (iPrime = 0; iPrime < 16; iPrime += 2) + { + m_HashPrimes[iPrime] = TableSize-m_HashPrimes[iPrime]; + } + + // Shuffle the set of primes to reduce correlation with bits in + // hash key. + // + for (iPrime = 0; iPrime < 16-1; iPrime++) + { + int Pick = RANDOM_LONG(0, 15-iPrime); + int Temp = m_HashPrimes[Pick]; + m_HashPrimes[Pick] = m_HashPrimes[15-iPrime]; + m_HashPrimes[15-iPrime] = Temp; + } +} + +// Renumber nodes so that nodes that link together are together. +// +#define UNNUMBERED_NODE -1 +void CGraph::SortNodes(void) +{ + // We are using m_iPreviousNode to be the new node number. + // After assigning new node numbers to everything, we move + // things and patchup the links. + // + int iNodeCnt = 0; + m_pNodes[0].m_iPreviousNode = iNodeCnt++; + int i = 0; + for (i = 1; i < m_cNodes; i++) + { + m_pNodes[i].m_iPreviousNode = UNNUMBERED_NODE; + } + + for (i = 0; i < m_cNodes; i++) + { + // Run through all of this node's neighbors + // + for (int j = 0 ; j < m_pNodes[i].m_cNumLinks; j++ ) + { + int iDestNode = INodeLink(i, j); + if (m_pNodes[iDestNode].m_iPreviousNode == UNNUMBERED_NODE) + { + m_pNodes[iDestNode].m_iPreviousNode = iNodeCnt++; + } + } + } + + // Assign remaining node numbers to unlinked nodes. + // + for (i = 0; i < m_cNodes; i++) + { + if (m_pNodes[i].m_iPreviousNode == UNNUMBERED_NODE) + { + m_pNodes[i].m_iPreviousNode = iNodeCnt++; + } + } + + // Alter links to reflect new node numbers. + // + for (i = 0; i < m_cLinks; i++) + { + m_pLinkPool[i].m_iSrcNode = m_pNodes[m_pLinkPool[i].m_iSrcNode].m_iPreviousNode; + m_pLinkPool[i].m_iDestNode = m_pNodes[m_pLinkPool[i].m_iDestNode].m_iPreviousNode; + } + + // Rearrange nodes to reflect new node numbering. + // + for (i = 0; i < m_cNodes; i++) + { + while (m_pNodes[i].m_iPreviousNode != i) + { + // Move current node off to where it should be, and bring + // that other node back into the current slot. + // + int iDestNode = m_pNodes[i].m_iPreviousNode; + CNode TempNode = m_pNodes[iDestNode]; + m_pNodes[iDestNode] = m_pNodes[i]; + m_pNodes[i] = TempNode; + } + } +} + +void CGraph::BuildLinkLookups(void) +{ + m_nHashLinks = 3*m_cLinks/2 + 3; + + HashChoosePrimes(m_nHashLinks); + m_pHashLinks = (short *)calloc(sizeof(short), m_nHashLinks); + if (!m_pHashLinks) + { + ALERT(at_aiconsole, "Couldn't allocated Link Lookup Table.\n"); + return; + } + int i = 0; + for (i = 0; i < m_nHashLinks; i++) + { + m_pHashLinks[i] = ENTRY_STATE_EMPTY; + } + + for (i = 0; i < m_cLinks; i++) + { + CLink &link = Link(i); + HashInsert(link.m_iSrcNode, link.m_iDestNode, i); + } +#if 0 + for (i = 0; i < m_cLinks; i++) + { + CLink &link = Link(i); + int iKey; + HashSearch(link.m_iSrcNode, link.m_iDestNode, iKey); + if (iKey != i) + { + ALERT(at_aiconsole, "HashLinks don't match (%d versus %d)\n", i, iKey); + } + } +#endif +} + +void CGraph::BuildRegionTables(void) +{ + if (m_di) free(m_di); + + // Go ahead and setup for range searching the nodes for FindNearestNodes + // + m_di = (DIST_INFO *)calloc(sizeof(DIST_INFO), m_cNodes); + if (!m_di) + { + ALERT(at_aiconsole, "Couldn't allocated node ordering array.\n"); + return; + } + + // Calculate regions for all the nodes. + // + // + int i = 0; + for (i = 0; i < 3; i++) + { + m_RegionMin[i] = 999999999.0; // just a big number out there; + m_RegionMax[i] = -999999999.0; // just a big number out there; + } + for (i = 0; i < m_cNodes; i++) + { + if (m_pNodes[i].m_vecOrigin.x < m_RegionMin[0]) + m_RegionMin[0] = m_pNodes[i].m_vecOrigin.x; + if (m_pNodes[i].m_vecOrigin.y < m_RegionMin[1]) + m_RegionMin[1] = m_pNodes[i].m_vecOrigin.y; + if (m_pNodes[i].m_vecOrigin.z < m_RegionMin[2]) + m_RegionMin[2] = m_pNodes[i].m_vecOrigin.z; + + if (m_pNodes[i].m_vecOrigin.x > m_RegionMax[0]) + m_RegionMax[0] = m_pNodes[i].m_vecOrigin.x; + if (m_pNodes[i].m_vecOrigin.y > m_RegionMax[1]) + m_RegionMax[1] = m_pNodes[i].m_vecOrigin.y; + if (m_pNodes[i].m_vecOrigin.z > m_RegionMax[2]) + m_RegionMax[2] = m_pNodes[i].m_vecOrigin.z; + } + for (i = 0; i < m_cNodes; i++) + { + m_pNodes[i].m_Region[0] = CALC_RANGE(m_pNodes[i].m_vecOrigin.x, m_RegionMin[0], m_RegionMax[0]); + m_pNodes[i].m_Region[1] = CALC_RANGE(m_pNodes[i].m_vecOrigin.y, m_RegionMin[1], m_RegionMax[1]); + m_pNodes[i].m_Region[2] = CALC_RANGE(m_pNodes[i].m_vecOrigin.z, m_RegionMin[2], m_RegionMax[2]); + } + + for (i = 0; i < 3; i++) + { + int j = 0; + for ( j = 0; j < NUM_RANGES; j++) + { + m_RangeStart[i][j] = 255; + m_RangeEnd[i][j] = 0; + } + for (j = 0; j < m_cNodes; j++) + { + m_di[j].m_SortedBy[i] = j; + } + + for (j = 0; j < m_cNodes - 1; j++) + { + int jNode = m_di[j].m_SortedBy[i]; + int jCodeX = m_pNodes[jNode].m_Region[0]; + int jCodeY = m_pNodes[jNode].m_Region[1]; + int jCodeZ = m_pNodes[jNode].m_Region[2]; + int jCode; + switch (i) + { + case 0: + jCode = (jCodeX << 16) + (jCodeY << 8) + jCodeZ; + break; + case 1: + jCode = (jCodeY << 16) + (jCodeZ << 8) + jCodeX; + break; + case 2: + jCode = (jCodeZ << 16) + (jCodeX << 8) + jCodeY; + break; + } + + for (int k = j+1; k < m_cNodes; k++) + { + int kNode = m_di[k].m_SortedBy[i]; + int kCodeX = m_pNodes[kNode].m_Region[0]; + int kCodeY = m_pNodes[kNode].m_Region[1]; + int kCodeZ = m_pNodes[kNode].m_Region[2]; + int kCode; + switch (i) + { + case 0: + kCode = (kCodeX << 16) + (kCodeY << 8) + kCodeZ; + break; + case 1: + kCode = (kCodeY << 16) + (kCodeZ << 8) + kCodeX; + break; + case 2: + kCode = (kCodeZ << 16) + (kCodeX << 8) + kCodeY; + break; + } + + if (kCode < jCode) + { + // Swap j and k entries. + // + int Tmp = m_di[j].m_SortedBy[i]; + m_di[j].m_SortedBy[i] = m_di[k].m_SortedBy[i]; + m_di[k].m_SortedBy[i] = Tmp; + } + } + } + } + + // Generate lookup tables. + // + for (i = 0; i < m_cNodes; i++) + { + int CodeX = m_pNodes[m_di[i].m_SortedBy[0]].m_Region[0]; + int CodeY = m_pNodes[m_di[i].m_SortedBy[1]].m_Region[1]; + int CodeZ = m_pNodes[m_di[i].m_SortedBy[2]].m_Region[2]; + + if (i < m_RangeStart[0][CodeX]) + { + m_RangeStart[0][CodeX] = i; + } + if (i < m_RangeStart[1][CodeY]) + { + m_RangeStart[1][CodeY] = i; + } + if (i < m_RangeStart[2][CodeZ]) + { + m_RangeStart[2][CodeZ] = i; + } + if (m_RangeEnd[0][CodeX] < i) + { + m_RangeEnd[0][CodeX] = i; + } + if (m_RangeEnd[1][CodeY] < i) + { + m_RangeEnd[1][CodeY] = i; + } + if (m_RangeEnd[2][CodeZ] < i) + { + m_RangeEnd[2][CodeZ] = i; + } + } + + // Initialize the cache. + // + memset(m_Cache, 0, sizeof(m_Cache)); +} + +void CGraph :: ComputeStaticRoutingTables( void ) +{ + int nRoutes = m_cNodes*m_cNodes; +#define FROM_TO(x,y) ((x)*m_cNodes+(y)) + short *Routes = new short[nRoutes]; + + int *pMyPath = new int[m_cNodes]; + unsigned short *BestNextNodes = new unsigned short[m_cNodes]; + char *pRoute = new char[m_cNodes*2]; + + + if (Routes && pMyPath && BestNextNodes && pRoute) + { + int nTotalCompressedSize = 0; + for (int iHull = 0; iHull < MAX_NODE_HULLS; iHull++) + { + for (int iCap = 0; iCap < 2; iCap++) + { + int iCapMask; + switch (iCap) + { + case 0: + iCapMask = 0; + break; + + case 1: + iCapMask = bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE; + break; + } + + + // Initialize Routing table to uncalculated. + // + int iFrom = 0; + for (iFrom = 0; iFrom < m_cNodes; iFrom++) + { + for (int iTo = 0; iTo < m_cNodes; iTo++) + { + Routes[FROM_TO(iFrom, iTo)] = -1; + } + } + + for (iFrom = 0; iFrom < m_cNodes; iFrom++) + { + for (int iTo = m_cNodes-1; iTo >= 0; iTo--) + { + if (Routes[FROM_TO(iFrom, iTo)] != -1) continue; + + int cPathSize = FindShortestPath(pMyPath, iFrom, iTo, iHull, iCapMask); + + // Use the computed path to update the routing table. + // + if (cPathSize > 1) + { + for (int iNode = 0; iNode < cPathSize-1; iNode++) + { + int iStart = pMyPath[iNode]; + int iNext = pMyPath[iNode+1]; + for (int iNode1 = iNode+1; iNode1 < cPathSize; iNode1++) + { + int iEnd = pMyPath[iNode1]; + Routes[FROM_TO(iStart, iEnd)] = iNext; + } + } +#if 0 + // Well, at first glance, this should work, but actually it's safer + // to be told explictly that you can take a series of node in a + // particular direction. Some links don't appear to have links in + // the opposite direction. + // + for (iNode = cPathSize-1; iNode >= 1; iNode--) + { + int iStart = pMyPath[iNode]; + int iNext = pMyPath[iNode-1]; + for (int iNode1 = iNode-1; iNode1 >= 0; iNode1--) + { + int iEnd = pMyPath[iNode1]; + Routes[FROM_TO(iStart, iEnd)] = iNext; + } + } +#endif + } + else + { + Routes[FROM_TO(iFrom, iTo)] = iFrom; + Routes[FROM_TO(iTo, iFrom)] = iTo; + } + } + } + + for (iFrom = 0; iFrom < m_cNodes; iFrom++) + { + for (int iTo = 0; iTo < m_cNodes; iTo++) + { + BestNextNodes[iTo] = Routes[FROM_TO(iFrom, iTo)]; + } + + // Compress this node's routing table. + // + int iLastNode = 9999999; // just really big. + int cSequence = 0; + int cRepeats = 0; + int CompressedSize = 0; + char *p = pRoute; + for (int i = 0; i < m_cNodes; i++) + { + BOOL CanRepeat = ((BestNextNodes[i] == iLastNode) && cRepeats < 127); + BOOL CanSequence = (BestNextNodes[i] == i && cSequence < 128); + + if (cRepeats) + { + if (CanRepeat) + { + cRepeats++; + } + else + { + // Emit the repeat phrase. + // + CompressedSize += 2; // (count-1, iLastNode-i) + *p++ = cRepeats - 1; + int a = iLastNode - iFrom; + int b = iLastNode - iFrom + m_cNodes; + int c = iLastNode - iFrom - m_cNodes; + if (-128 <= a && a <= 127) + { + *p++ = a; + } + else if (-128 <= b && b <= 127) + { + *p++ = b; + } + else if (-128 <= c && c <= 127) + { + *p++ = c; + } + else + { + ALERT( at_aiconsole, "Nodes need sorting (%d,%d)!\n", iLastNode, iFrom); + } + cRepeats = 0; + + if (CanSequence) + { + // Start a sequence. + // + cSequence++; + } + else + { + // Start another repeat. + // + cRepeats++; + } + } + } + else if (cSequence) + { + if (CanSequence) + { + cSequence++; + } + else + { + // It may be advantageous to combine + // a single-entry sequence phrase with the + // next repeat phrase. + // + if (cSequence == 1 && CanRepeat) + { + // Combine with repeat phrase. + // + cRepeats = 2; + cSequence = 0; + } + else + { + // Emit the sequence phrase. + // + CompressedSize += 1; // (-count) + *p++ = -cSequence; + cSequence = 0; + + // Start a repeat sequence. + // + cRepeats++; + } + } + } + else + { + if (CanSequence) + { + // Start a sequence phrase. + // + cSequence++; + } + else + { + // Start a repeat sequence. + // + cRepeats++; + } + } + iLastNode = BestNextNodes[i]; + } + if (cRepeats) + { + // Emit the repeat phrase. + // + CompressedSize += 2; + *p++ = cRepeats - 1; +#if 0 + iLastNode = iFrom + *pRoute; + if (iLastNode >= m_cNodes) iLastNode -= m_cNodes; + else if (iLastNode < 0) iLastNode += m_cNodes; +#endif + int a = iLastNode - iFrom; + int b = iLastNode - iFrom + m_cNodes; + int c = iLastNode - iFrom - m_cNodes; + if (-128 <= a && a <= 127) + { + *p++ = a; + } + else if (-128 <= b && b <= 127) + { + *p++ = b; + } + else if (-128 <= c && c <= 127) + { + *p++ = c; + } + else + { + ALERT( at_aiconsole, "Nodes need sorting (%d,%d)!\n", iLastNode, iFrom); + } + } + if (cSequence) + { + // Emit the Sequence phrase. + // + CompressedSize += 1; + *p++ = -cSequence; + } + + // Go find a place to store this thing and point to it. + // + int nRoute = p - pRoute; + if (m_pRouteInfo) + { + int i = 0; + for (i = 0; i < m_nRouteInfo - nRoute; i++) + { + if (memcmp(m_pRouteInfo + i, pRoute, nRoute) == 0) + { + break; + } + } + if (i < m_nRouteInfo - nRoute) + { + m_pNodes[ iFrom ].m_pNextBestNode[iHull][iCap] = i; + } + else + { + char *Tmp = (char *)calloc(sizeof(char), (m_nRouteInfo + nRoute)); + memcpy(Tmp, m_pRouteInfo, m_nRouteInfo); + free(m_pRouteInfo); + m_pRouteInfo = Tmp; + memcpy(m_pRouteInfo + m_nRouteInfo, pRoute, nRoute); + m_pNodes[ iFrom ].m_pNextBestNode[iHull][iCap] = m_nRouteInfo; + m_nRouteInfo += nRoute; + nTotalCompressedSize += CompressedSize; + } + } + else + { + m_nRouteInfo = nRoute; + m_pRouteInfo = (char *)calloc(sizeof(char), nRoute); + memcpy(m_pRouteInfo, pRoute, nRoute); + m_pNodes[ iFrom ].m_pNextBestNode[iHull][iCap] = 0; + nTotalCompressedSize += CompressedSize; + } + } + } + } + ALERT( at_aiconsole, "Size of Routes = %d\n", nTotalCompressedSize); + } + if (Routes) delete Routes; + if (BestNextNodes) delete BestNextNodes; + if (pRoute) delete pRoute; + if (pMyPath) delete pMyPath; + Routes = 0; + BestNextNodes = 0; + pRoute = 0; + pMyPath = 0; + +#if 0 + TestRoutingTables(); +#endif + m_fRoutingComplete = TRUE; +} + +// Test those routing tables. Doesn't really work, yet. +// +void CGraph :: TestRoutingTables( void ) +{ + int *pMyPath = new int[m_cNodes]; + int *pMyPath2 = new int[m_cNodes]; + if (pMyPath && pMyPath2) + { + for (int iHull = 0; iHull < MAX_NODE_HULLS; iHull++) + { + for (int iCap = 0; iCap < 2; iCap++) + { + int iCapMask; + switch (iCap) + { + case 0: + iCapMask = 0; + break; + + case 1: + iCapMask = bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE; + break; + } + + for (int iFrom = 0; iFrom < m_cNodes; iFrom++) + { + for (int iTo = 0; iTo < m_cNodes; iTo++) + { + m_fRoutingComplete = FALSE; + int cPathSize1 = FindShortestPath(pMyPath, iFrom, iTo, iHull, iCapMask); + m_fRoutingComplete = TRUE; + int cPathSize2 = FindShortestPath(pMyPath2, iFrom, iTo, iHull, iCapMask); + + // Unless we can look at the entire path, we can verify that it's correct. + // + if (cPathSize2 == MAX_PATH_SIZE) continue; + + // Compare distances. + // +#if 1 + float flDistance1 = 0.0; + int i = 0; + for (i = 0; i < cPathSize1-1; i++) + { + // Find the link from pMyPath[i] to pMyPath[i+1] + // + if (pMyPath[i] == pMyPath[i+1]) continue; + int iVisitNode; + BOOL bFound = FALSE; + for (int iLink = 0; iLink < m_pNodes[pMyPath[i]].m_cNumLinks; iLink++) + { + iVisitNode = INodeLink ( pMyPath[i], iLink ); + if (iVisitNode == pMyPath[i+1]) + { + flDistance1 += m_pLinkPool[ m_pNodes[ pMyPath[i] ].m_iFirstLink + iLink].m_flWeight; + bFound = TRUE; + break; + } + } + if (!bFound) + { + ALERT(at_aiconsole, "No link.\n"); + } + } + + float flDistance2 = 0.0; + for (i = 0; i < cPathSize2-1; i++) + { + // Find the link from pMyPath2[i] to pMyPath2[i+1] + // + if (pMyPath2[i] == pMyPath2[i+1]) continue; + int iVisitNode; + BOOL bFound = FALSE; + for (int iLink = 0; iLink < m_pNodes[pMyPath2[i]].m_cNumLinks; iLink++) + { + iVisitNode = INodeLink ( pMyPath2[i], iLink ); + if (iVisitNode == pMyPath2[i+1]) + { + flDistance2 += m_pLinkPool[ m_pNodes[ pMyPath2[i] ].m_iFirstLink + iLink].m_flWeight; + bFound = TRUE; + break; + } + } + if (!bFound) + { + ALERT(at_aiconsole, "No link.\n"); + } + } + if (fabs(flDistance1 - flDistance2) > 0.10) + { +#else + if (cPathSize1 != cPathSize2 || memcmp(pMyPath, pMyPath2, sizeof(int)*cPathSize1) != 0) + { +#endif + ALERT(at_aiconsole, "Routing is inconsistent!!!\n"); + ALERT(at_aiconsole, "(%d to %d |%d/%d)1:", iFrom, iTo, iHull, iCap); + int i = 0; + for (i = 0; i < cPathSize1; i++) + { + ALERT(at_aiconsole, "%d ", pMyPath[i]); + } + ALERT(at_aiconsole, "\n(%d to %d |%d/%d)2:", iFrom, iTo, iHull, iCap); + for (i = 0; i < cPathSize2; i++) + { + ALERT(at_aiconsole, "%d ", pMyPath2[i]); + } + ALERT(at_aiconsole, "\n"); + m_fRoutingComplete = FALSE; + cPathSize1 = FindShortestPath(pMyPath, iFrom, iTo, iHull, iCapMask); + m_fRoutingComplete = TRUE; + cPathSize2 = FindShortestPath(pMyPath2, iFrom, iTo, iHull, iCapMask); + goto EnoughSaid; + } + } + } + } + } + } + +EnoughSaid: + + if (pMyPath) delete pMyPath; + if (pMyPath2) delete pMyPath2; + pMyPath = 0; + pMyPath2 = 0; +} + + + + + + + + + +//========================================================= +// CNodeViewer - Draws a graph of the shorted path from all nodes +// to current location (typically the player). It then draws +// as many connects as it can per frame, trying not to overflow the buffer +//========================================================= +class CNodeViewer : public CBaseEntity +{ +public: + void Spawn( void ); + + int m_iBaseNode; + int m_iDraw; + int m_nVisited; + int m_aFrom[128]; + int m_aTo[128]; + int m_iHull; + int m_afNodeType; + Vector m_vecColor; + + void FindNodeConnections( int iNode ); + void AddNode( int iFrom, int iTo ); + void EXPORT DrawThink( void ); + +}; +LINK_ENTITY_TO_CLASS( node_viewer, CNodeViewer ); +LINK_ENTITY_TO_CLASS( node_viewer_human, CNodeViewer ); +LINK_ENTITY_TO_CLASS( node_viewer_fly, CNodeViewer ); +LINK_ENTITY_TO_CLASS( node_viewer_large, CNodeViewer ); + +void CNodeViewer::Spawn( ) +{ + if ( !WorldGraph.m_fGraphPresent || !WorldGraph.m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available or built + ALERT ( at_debug, "Graph not ready!\n" ); + UTIL_Remove( this ); + return; + } + + + if (FClassnameIs( pev, "node_viewer_fly")) + { + m_iHull = NODE_FLY_HULL; + m_afNodeType = bits_NODE_AIR; + m_vecColor = Vector( 160, 100, 255 ); + } + else if (FClassnameIs( pev, "node_viewer_large")) + { + m_iHull = NODE_LARGE_HULL; + m_afNodeType = bits_NODE_LAND | bits_NODE_WATER; + m_vecColor = Vector( 100, 255, 160 ); + } + else + { + m_iHull = NODE_HUMAN_HULL; + m_afNodeType = bits_NODE_LAND | bits_NODE_WATER; + m_vecColor = Vector( 255, 160, 100 ); + } + + + m_iBaseNode = WorldGraph.FindNearestNode ( pev->origin, m_afNodeType ); + + if ( m_iBaseNode < 0 ) + { + ALERT( at_debug, "No nearby node\n" ); + return; + } + + m_nVisited = 0; + + ALERT( at_aiconsole, "basenode %d\n", m_iBaseNode ); + + if (WorldGraph.m_cNodes < 128) + { + for (int i = 0; i < WorldGraph.m_cNodes; i++) + { + AddNode( i, WorldGraph.NextNodeInRoute( i, m_iBaseNode, m_iHull, 0 )); + } + } + else + { + // do a depth traversal + FindNodeConnections( m_iBaseNode ); + + int start = 0; + int end; + do { + end = m_nVisited; + // ALERT( at_console, "%d :", m_nVisited ); + for (end = m_nVisited; start < end; start++) + { + FindNodeConnections( m_aFrom[start] ); + FindNodeConnections( m_aTo[start] ); + } + } while (end != m_nVisited); + } + + ALERT( at_aiconsole, "%d nodes\n", m_nVisited ); + + m_iDraw = 0; + SetThink(&CNodeViewer:: DrawThink ); + SetNextThink( 0 ); +} + + +void CNodeViewer :: FindNodeConnections ( int iNode ) +{ + AddNode( iNode, WorldGraph.NextNodeInRoute( iNode, m_iBaseNode, m_iHull, 0 )); + for ( int i = 0 ; i < WorldGraph.m_pNodes[ iNode ].m_cNumLinks ; i++ ) + { + CLink *pToLink = &WorldGraph.NodeLink( iNode, i); + AddNode( pToLink->m_iDestNode, WorldGraph.NextNodeInRoute( pToLink->m_iDestNode, m_iBaseNode, m_iHull, 0 )); + } +} + +void CNodeViewer::AddNode( int iFrom, int iTo ) +{ + if (m_nVisited >= 128) + { + return; + } + else + { + if (iFrom == iTo) + return; + + for (int i = 0; i < m_nVisited; i++) + { + if (m_aFrom[i] == iFrom && m_aTo[i] == iTo) + return; + if (m_aFrom[i] == iTo && m_aTo[i] == iFrom) + return; + } + m_aFrom[m_nVisited] = iFrom; + m_aTo[m_nVisited] = iTo; + m_nVisited++; + } +} + + +void CNodeViewer :: DrawThink( void ) +{ + SetNextThink( 0 ); + + for (int i = 0; i < 10; i++) + { + if (m_iDraw == m_nVisited) + { + UTIL_Remove( this ); + return; + } + + extern short g_sModelIndexLaser; + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aFrom[m_iDraw] ].m_vecOrigin.x ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aFrom[m_iDraw] ].m_vecOrigin.y ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aFrom[m_iDraw] ].m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( WorldGraph.m_pNodes[ m_aTo[m_iDraw] ].m_vecOrigin.x ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aTo[m_iDraw] ].m_vecOrigin.y ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aTo[m_iDraw] ].m_vecOrigin.z + NODE_HEIGHT ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 0 ); // g-cont. set infinite life + WRITE_BYTE( 40 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( m_vecColor.x ); // r, g, b + WRITE_BYTE( m_vecColor.y ); // r, g, b + WRITE_BYTE( m_vecColor.z ); // r, g, b + WRITE_BYTE( 128 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + m_iDraw++; + } +} + + diff --git a/dlls/nodes.h b/dlls/nodes.h new file mode 100644 index 0000000..a4bd906 --- /dev/null +++ b/dlls/nodes.h @@ -0,0 +1,376 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// nodes.h +//========================================================= + +//========================================================= +// DEFINE +//========================================================= +#define MAX_STACK_NODES 100 +#define NO_NODE -1 +#define MAX_NODE_HULLS 4 + +#define bits_NODE_LAND ( 1 << 0 ) // Land node, so nudge if necessary. +#define bits_NODE_AIR ( 1 << 1 ) // Air node, don't nudge. +#define bits_NODE_WATER ( 1 << 2 ) // Water node, don't nudge. +#define bits_NODE_GROUP_REALM (bits_NODE_LAND | bits_NODE_AIR | bits_NODE_WATER) + +//========================================================= +// Instance of a node. +//========================================================= +class CNode +{ +public: + Vector m_vecOrigin;// location of this node in space + Vector m_vecOriginPeek; // location of this node (LAND nodes are NODE_HEIGHT higher). + BYTE m_Region[3]; // Which of 256 regions do each of the coordinate belong? + int m_afNodeInfo;// bits that tell us more about this location + + int m_cNumLinks; // how many links this node has + int m_iFirstLink;// index of this node's first link in the link pool. + + // Where to start looking in the compressed routing table (offset into m_pRouteInfo). + // (4 hull sizes -- smallest to largest + fly/swim), and secondly, door capability. + // + int m_pNextBestNode[MAX_NODE_HULLS][2]; + + // Used in finding the shortest path. m_fClosestSoFar is -1 if not visited. + // Then it is the distance to the source. If another path uses this node + // and has a closer distance, then m_iPreviousNode is also updated. + // + float m_flClosestSoFar; // Used in finding the shortest path. + int m_iPreviousNode; + + short m_sHintType;// there is something interesting in the world at this node's position + short m_sHintActivity;// there is something interesting in the world at this node's position + float m_flHintYaw;// monster on this node should face this yaw to face the hint. +}; + +//========================================================= +// CLink - A link between 2 nodes +//========================================================= +#define bits_LINK_SMALL_HULL ( 1 << 0 )// headcrab box can fit through this connection +#define bits_LINK_HUMAN_HULL ( 1 << 1 )// player box can fit through this connection +#define bits_LINK_LARGE_HULL ( 1 << 2 )// big box can fit through this connection +#define bits_LINK_FLY_HULL ( 1 << 3 )// a flying big box can fit through this connection +#define bits_LINK_DISABLED ( 1 << 4 )// link is not valid when the set + +#define NODE_SMALL_HULL 0 +#define NODE_HUMAN_HULL 1 +#define NODE_LARGE_HULL 2 +#define NODE_FLY_HULL 3 + +class CVirtualFS; + +class CLink +{ +public: + int m_iSrcNode;// the node that 'owns' this link ( keeps us from having to make reverse lookups ) + int m_iDestNode;// the node on the other end of the link. + + entvars_t *m_pLinkEnt;// the entity that blocks this connection (doors, etc) + + // m_szLinkEntModelname is not necessarily NULL terminated (so we can store it in a more alignment-friendly 4 bytes) + char m_szLinkEntModelname[ 4 ];// the unique name of the brush model that blocks the connection (this is kept for save/restore) + + int m_afLinkInfo;// information about this link + float m_flWeight;// length of the link line segment +}; + + +typedef struct +{ + int m_SortedBy[3]; + int m_CheckedEvent; +} DIST_INFO; + +typedef struct +{ + Vector v; + short n; // Nearest node or -1 if no node found. +} CACHE_ENTRY; + +//========================================================= +// CGraph +//========================================================= +#define GRAPH_VERSION (int)16// !!!increment this whever graph/node/link classes change, to obsolesce older disk files. +class CGraph +{ +public: + +// the graph has two flags, and should not be accessed unless both flags are TRUE! + BOOL m_fGraphPresent;// is the graph in memory? + BOOL m_fGraphPointersSet;// are the entity pointers for the graph all set? + BOOL m_fRoutingComplete; // are the optimal routes computed, yet? + + CNode *m_pNodes;// pointer to the memory block that contains all node info + CLink *m_pLinkPool;// big list of all node connections + char *m_pRouteInfo; // compressed routing information the nodes use. + + int m_cNodes;// total number of nodes + int m_cLinks;// total number of links + int m_nRouteInfo; // size of m_pRouteInfo in bytes. + + // Tables for making nearest node lookup faster. SortedBy provided nodes in a + // order of a particular coordinate. Instead of doing a binary search, RangeStart + // and RangeEnd let you get to the part of SortedBy that you are interested in. + // + // Once you have a point of interest, the only way you'll find a closer point is + // if at least one of the coordinates is closer than the ones you have now. So we + // search each range. After the search is exhausted, we know we have the closest + // node. + // +#define CACHE_SIZE 128 +#define NUM_RANGES 256 + DIST_INFO *m_di; // This is m_cNodes long, but the entries don't correspond to CNode entries. + int m_RangeStart[3][NUM_RANGES]; + int m_RangeEnd[3][NUM_RANGES]; + float m_flShortest; + int m_iNearest; + int m_minX, m_minY, m_minZ, m_maxX, m_maxY, m_maxZ; + int m_minBoxX, m_minBoxY, m_minBoxZ, m_maxBoxX, m_maxBoxY, m_maxBoxZ; + int m_CheckedCounter; + float m_RegionMin[3], m_RegionMax[3]; // The range of nodes. + CACHE_ENTRY m_Cache[CACHE_SIZE]; + + + int m_HashPrimes[16]; + short *m_pHashLinks; + int m_nHashLinks; + + + // kinda sleazy. In order to allow variety in active idles for monster groups in a room with more than one node, + // we keep track of the last node we searched from and store it here. Subsequent searches by other monsters will pick + // up where the last search stopped. + int m_iLastActiveIdleSearch; + + // another such system used to track the search for cover nodes, helps greatly with two monsters trying to get to the same node. + int m_iLastCoverSearch; + + // functions to create the graph + int LinkVisibleNodes ( CLink *pLinkPool, CVirtualFS *file, int *piBadNode ); + int RejectInlineLinks ( CLink *pLinkPool, CVirtualFS *file ); + int FindShortestPath ( int *piPath, int iStart, int iDest, int iHull, int afCapMask); + int FindNearestNode ( const Vector &vecOrigin, CBaseEntity *pEntity ); + int FindNearestNode ( const Vector &vecOrigin, int afNodeTypes ); + //int FindNearestLink ( const Vector &vecTestPoint, int *piNearestLink, BOOL *pfAlongLine ); + float PathLength( int iStart, int iDest, int iHull, int afCapMask ); + int NextNodeInRoute( int iCurrentNode, int iDest, int iHull, int iCap ); + + enum NODEQUERY { NODEGRAPH_DYNAMIC, NODEGRAPH_STATIC }; + // A static query means we're asking about the possiblity of handling this entity at ANY time + // A dynamic query means we're asking about it RIGHT NOW. So we should query the current state + int HandleLinkEnt ( int iNode, entvars_t *pevLinkEnt, int afCapMask, NODEQUERY queryType ); + entvars_t* LinkEntForLink ( CLink *pLink, CNode *pNode ); + void ShowNodeConnections ( int iNode ); + void InitGraph( void ); + int AllocNodes ( void ); + + int CheckNODFile(char *szMapName); + int FLoadGraph(char *szMapName); + int FSaveGraph(char *szMapName); + int FSetGraphPointers(void); + void CheckNode(Vector vecOrigin, int iNode); + + void BuildRegionTables(void); + void ComputeStaticRoutingTables(void); + void TestRoutingTables(void); + + void HashInsert(int iSrcNode, int iDestNode, int iKey); + void HashSearch(int iSrcNode, int iDestNode, int &iKey); + void HashChoosePrimes(int TableSize); + void BuildLinkLookups(void); + + void SortNodes(void); + + int HullIndex( const CBaseEntity *pEntity ); // what hull the monster uses + int NodeType( const CBaseEntity *pEntity ); // what node type the monster uses + inline int CapIndex( int afCapMask ) + { + if (afCapMask & (bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE)) + return 1; + return 0; + } + + + inline CNode &Node( int i ) + { +#ifdef _DEBUG + if ( !m_pNodes || i < 0 || i > m_cNodes ) + ALERT( at_error, "Bad Node!\n" ); +#endif + return m_pNodes[i]; + } + + inline CLink &Link( int i ) + { +#ifdef _DEBUG + if ( !m_pLinkPool || i < 0 || i > m_cLinks ) + ALERT( at_error, "Bad link!\n" ); +#endif + return m_pLinkPool[i]; + } + + inline CLink &NodeLink( int iNode, int iLink ) + { + return Link( Node( iNode ).m_iFirstLink + iLink ); + } + + inline CLink &NodeLink( const CNode &node, int iLink ) + { + return Link( node.m_iFirstLink + iLink ); + } + + inline int INodeLink ( int iNode, int iLink ) + { + return NodeLink( iNode, iLink ).m_iDestNode; + } + +#if 0 + inline CNode &SourceNode( int iNode, int iLink ) + { + return Node( NodeLink( iNode, iLink ).m_iSrcNode ); + } + + inline CNode &DestNode( int iNode, int iLink ) + { + return Node( NodeLink( iNode, iLink ).m_iDestNode ); + } + + inline CNode *PNodeLink ( int iNode, int iLink ) + { + return &DestNode( iNode, iLink ); + } +#endif +}; + +//========================================================= +// Nodes start out as ents in the level. The node graph +// is built, then these ents are discarded. +//========================================================= +class CNodeEnt : public CBaseEntity +{ + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + short m_sHintType; + short m_sHintActivity; +}; + + +//========================================================= +// CStack - last in, first out. +//========================================================= +class CStack +{ +public: + CStack( void ); + void Push( int value ); + int Pop( void ); + int Top( void ); + int Empty( void ) { return m_level==0; } + int Size( void ) { return m_level; } + void CopyToArray ( int *piArray ); + +private: + int m_stack[ MAX_STACK_NODES ]; + int m_level; +}; + + +//========================================================= +// CQueue - first in, first out. +//========================================================= +class CQueue +{ +public: + + CQueue( void );// constructor + inline int Full ( void ) { return ( m_cSize == MAX_STACK_NODES ); } + inline int Empty ( void ) { return ( m_cSize == 0 ); } + //inline int Tail ( void ) { return ( m_queue[ m_tail ] ); } + inline int Size ( void ) { return ( m_cSize ); } + void Insert( int, float ); + int Remove( float & ); + +private: + int m_cSize; + struct tag_QUEUE_NODE + { + int Id; + float Priority; + } m_queue[ MAX_STACK_NODES ]; + int m_head; + int m_tail; +}; + +//========================================================= +// CQueuePriority - Priority queue (smallest item out first). +// +//========================================================= +class CQueuePriority +{ +public: + + CQueuePriority( void );// constructor + inline int Full ( void ) { return ( m_cSize == MAX_STACK_NODES ); } + inline int Empty ( void ) { return ( m_cSize == 0 ); } + //inline int Tail ( float & ) { return ( m_queue[ m_tail ].Id ); } + inline int Size ( void ) { return ( m_cSize ); } + void Insert( int, float ); + int Remove( float &); + +private: + int m_cSize; + struct tag_HEAP_NODE + { + int Id; + float Priority; + } m_heap[ MAX_STACK_NODES ]; + void Heap_SiftDown(int); + void Heap_SiftUp(void); + +}; + +//========================================================= +// hints - these MUST coincide with the HINTS listed under +// info_node in the FGD file! +//========================================================= +enum +{ + HINT_NONE = 0, + HINT_WORLD_DOOR, + HINT_WORLD_WINDOW, + HINT_WORLD_BUTTON, + HINT_WORLD_MACHINERY, + HINT_WORLD_LEDGE, + HINT_WORLD_LIGHT_SOURCE, + HINT_WORLD_HEAT_SOURCE, + HINT_WORLD_BLINKING_LIGHT, + HINT_WORLD_BRIGHT_COLORS, + HINT_WORLD_HUMAN_BLOOD, + HINT_WORLD_ALIEN_BLOOD, + + HINT_TACTICAL_EXIT = 100, + HINT_TACTICAL_VANTAGE, + HINT_TACTICAL_AMBUSH, + + HINT_STUKA_PERCH = 300, + HINT_STUKA_LANDING, +}; + +extern CGraph WorldGraph; diff --git a/dlls/osprey.cpp b/dlls/osprey.cpp new file mode 100644 index 0000000..d373e07 --- /dev/null +++ b/dlls/osprey.cpp @@ -0,0 +1,827 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "soundent.h" +#include "effects.h" +#include "customentity.h" + +typedef struct +{ + int isValid; + EHANDLE hGrunt; + Vector vecOrigin; + Vector vecAngles; +} t_ospreygrunt; + + + +#define SF_WAITFORTRIGGER 0x40 + + +#define MAX_CARRY 24 + +class COsprey : public CBaseMonster +{ +public: + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + int ObjectCaps( void ) { return CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + void Spawn( void ); + void Precache( void ); + int Classify( void ) { return CLASS_MACHINE; }; + int BloodColor( void ) { return DONT_BLEED; } + void Killed( entvars_t *pevAttacker, int iGib ); + + void UpdateGoal( void ); + BOOL HasDead( void ); + void EXPORT FlyThink( void ); + void EXPORT DeployThink( void ); + void Flight( void ); + void EXPORT HitTouch( CBaseEntity *pOther ); + void EXPORT FindAllThink( void ); + void EXPORT HoverThink( void ); + CBaseMonster *MakeGrunt( Vector vecSrc ); + void EXPORT CrashTouch( CBaseEntity *pOther ); + void EXPORT DyingThink( void ); + void EXPORT CommandUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + // int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + void ShowDamage( void ); + + CBaseEntity *m_pGoalEnt; + Vector m_vel1; + Vector m_vel2; + Vector m_pos1; + Vector m_pos2; + Vector m_ang1; + Vector m_ang2; + float m_startTime; + float m_dTime; + + Vector m_velocity; + + float m_flIdealtilt; + float m_flRotortilt; + + float m_flRightHealth; + float m_flLeftHealth; + + int m_iUnits; + EHANDLE m_hGrunt[MAX_CARRY]; + Vector m_vecOrigin[MAX_CARRY]; + EHANDLE m_hRepel[4]; + + int m_iSoundState; + int m_iSpriteTexture; + + int m_iPitch; + + int m_iExplode; + int m_iTailGibs; + int m_iBodyGibs; + int m_iEngineGibs; + + int m_iDoLeftSmokePuff; + int m_iDoRightSmokePuff; +}; + +LINK_ENTITY_TO_CLASS( monster_osprey, COsprey ); + +TYPEDESCRIPTION COsprey::m_SaveData[] = +{ + DEFINE_FIELD( COsprey, m_pGoalEnt, FIELD_CLASSPTR ), + DEFINE_FIELD( COsprey, m_vel1, FIELD_VECTOR ), + DEFINE_FIELD( COsprey, m_vel2, FIELD_VECTOR ), + DEFINE_FIELD( COsprey, m_pos1, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( COsprey, m_pos2, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( COsprey, m_ang1, FIELD_VECTOR ), + DEFINE_FIELD( COsprey, m_ang2, FIELD_VECTOR ), + + DEFINE_FIELD( COsprey, m_startTime, FIELD_TIME ), + DEFINE_FIELD( COsprey, m_dTime, FIELD_FLOAT ), + DEFINE_FIELD( COsprey, m_velocity, FIELD_VECTOR ), + + DEFINE_FIELD( COsprey, m_flIdealtilt, FIELD_FLOAT ), + DEFINE_FIELD( COsprey, m_flRotortilt, FIELD_FLOAT ), + + DEFINE_FIELD( COsprey, m_flRightHealth, FIELD_FLOAT ), + DEFINE_FIELD( COsprey, m_flLeftHealth, FIELD_FLOAT ), + + DEFINE_FIELD( COsprey, m_iUnits, FIELD_INTEGER ), + DEFINE_ARRAY( COsprey, m_hGrunt, FIELD_EHANDLE, MAX_CARRY ), + DEFINE_ARRAY( COsprey, m_vecOrigin, FIELD_POSITION_VECTOR, MAX_CARRY ), + DEFINE_ARRAY( COsprey, m_hRepel, FIELD_EHANDLE, 4 ), + + // DEFINE_FIELD( COsprey, m_iSoundState, FIELD_INTEGER ), + // DEFINE_FIELD( COsprey, m_iSpriteTexture, FIELD_INTEGER ), + // DEFINE_FIELD( COsprey, m_iPitch, FIELD_INTEGER ), + + DEFINE_FIELD( COsprey, m_iDoLeftSmokePuff, FIELD_INTEGER ), + DEFINE_FIELD( COsprey, m_iDoRightSmokePuff, FIELD_INTEGER ), +}; +IMPLEMENT_SAVERESTORE( COsprey, CBaseMonster ); + +void COsprey :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/osprey.mdl"); + UTIL_SetSize(pev, Vector( -400, -400, -100), Vector(400, 400, 32)); + UTIL_SetOrigin( this, pev->origin ); + + //ALERT(at_console, "Osprey origin %f %f %f\n", pev->origin.x, pev->origin.y, pev->origin.z); + + pev->flags |= FL_MONSTER; + pev->takedamage = DAMAGE_YES; + m_flRightHealth = 200; + m_flLeftHealth = 200; + pev->health = 400; + + pev->speed = 80; //LRC - default speed, in case path corners don't give a speed. + + m_flFieldOfView = 0; // 180 degrees + + pev->sequence = 0; + ResetSequenceInfo( ); + pev->frame = RANDOM_LONG(0,0xFF); + + InitBoneControllers(); + + SetThink(&COsprey :: FindAllThink ); + SetUse(&COsprey :: CommandUse ); + + if (!(pev->spawnflags & SF_WAITFORTRIGGER)) + { + SetNextThink( 1.0 ); + } + + m_pos2 = pev->origin; + m_ang2 = pev->angles; + m_vel2 = pev->velocity; +} + + +void COsprey::Precache( void ) +{ + UTIL_PrecacheOther( "monster_human_grunt" ); + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/osprey.mdl"); + PRECACHE_MODEL("models/HVR.mdl"); + + PRECACHE_SOUND("apache/ap_rotor4.wav"); + PRECACHE_SOUND("weapons/mortarhit.wav"); + + m_iSpriteTexture = PRECACHE_MODEL( "sprites/rope.spr" ); + + m_iExplode = PRECACHE_MODEL( "sprites/fexplo.spr" ); + m_iTailGibs = PRECACHE_MODEL( "models/osprey_tailgibs.mdl" ); + m_iBodyGibs = PRECACHE_MODEL( "models/osprey_bodygibs.mdl" ); + m_iEngineGibs = PRECACHE_MODEL( "models/osprey_enginegibs.mdl" ); +} + +void COsprey::CommandUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetNextThink( 0.1 ); +} + +void COsprey :: FindAllThink( void ) +{ + CBaseEntity *pEntity = NULL; + + m_iUnits = 0; + while (m_iUnits < MAX_CARRY && (pEntity = UTIL_FindEntityByClassname( pEntity, "monster_human_grunt" )) != NULL) + { + if (pEntity->IsAlive()) + { + m_hGrunt[m_iUnits] = pEntity; + m_vecOrigin[m_iUnits] = pEntity->pev->origin; + m_iUnits++; + } + } + + if (m_iUnits == 0) + { + m_iUnits = 4; //LRC - stop whining, just make the damn grunts... + +// ALERT( at_console, "osprey error: no grunts to resupply\n"); +// UTIL_Remove( this ); +// return; + } + SetThink(&COsprey :: FlyThink ); + SetNextThink( 0.1 ); + m_startTime = gpGlobals->time; +} + + +void COsprey :: DeployThink( void ) +{ + UTIL_MakeAimVectors( pev->angles ); + + Vector vecForward = gpGlobals->v_forward; + Vector vecRight = gpGlobals->v_right; + Vector vecUp = gpGlobals->v_up; + + Vector vecSrc; + + TraceResult tr; + UTIL_TraceLine( pev->origin, pev->origin + Vector( 0, 0, -4096.0), ignore_monsters, ENT(pev), &tr); + CSoundEnt::InsertSound ( bits_SOUND_DANGER, tr.vecEndPos, 400, 0.3 ); + + vecSrc = pev->origin + vecForward * 32 + vecRight * 100 + vecUp * -96; + m_hRepel[0] = MakeGrunt( vecSrc ); + + vecSrc = pev->origin + vecForward * -64 + vecRight * 100 + vecUp * -96; + m_hRepel[1] = MakeGrunt( vecSrc ); + + vecSrc = pev->origin + vecForward * 32 + vecRight * -100 + vecUp * -96; + m_hRepel[2] = MakeGrunt( vecSrc ); + + vecSrc = pev->origin + vecForward * -64 + vecRight * -100 + vecUp * -96; + m_hRepel[3] = MakeGrunt( vecSrc ); + + SetThink(&COsprey :: HoverThink ); + SetNextThink( 0.1 ); +} + + + +BOOL COsprey :: HasDead( ) +{ + for (int i = 0; i < m_iUnits; i++) + { + if (m_hGrunt[i] == NULL || !m_hGrunt[i]->IsAlive()) + { + return TRUE; + } + else + { + m_vecOrigin[i] = m_hGrunt[i]->pev->origin; // send them to where they died + } + } + return FALSE; +} + + +CBaseMonster *COsprey :: MakeGrunt( Vector vecSrc ) +{ + CBaseEntity *pEntity; + CBaseMonster *pGrunt; + + TraceResult tr; + UTIL_TraceLine( vecSrc, vecSrc + Vector( 0, 0, -4096.0), dont_ignore_monsters, ENT(pev), &tr); + if ( tr.pHit && Instance( tr.pHit )->pev->solid != SOLID_BSP) + return NULL; + + for (int i = 0; i < m_iUnits; i++) + { + if (m_hGrunt[i] == NULL || !m_hGrunt[i]->IsAlive()) + { + if (m_hGrunt[i] != NULL && m_hGrunt[i]->pev->rendermode == kRenderNormal) + { + m_hGrunt[i]->SUB_StartFadeOut( ); + } + pEntity = Create( "monster_human_grunt", vecSrc, pev->angles ); + pGrunt = pEntity->MyMonsterPointer( ); + pGrunt->pev->movetype = MOVETYPE_FLY; + pGrunt->pev->velocity = Vector( 0, 0, RANDOM_FLOAT( -196, -128 ) ); + pGrunt->SetActivity( ACT_GLIDE ); + + CBeam *pBeam = CBeam::BeamCreate( "sprites/rope.spr", 10 ); + pBeam->PointEntInit( vecSrc + Vector(0,0,112), pGrunt->entindex() ); + pBeam->SetFlags( BEAM_FSOLID ); + pBeam->SetColor( 255, 255, 255 ); + pBeam->SetThink(&CBeam:: SUB_Remove ); + pBeam->SetNextThink( -4096.0 * tr.flFraction / pGrunt->pev->velocity.z + 0.5 ); + + // ALERT( at_console, "%d at %.0f %.0f %.0f\n", i, m_vecOrigin[i].x, m_vecOrigin[i].y, m_vecOrigin[i].z ); + pGrunt->m_vecLastPosition = m_vecOrigin[i]; + m_hGrunt[i] = pGrunt; + return pGrunt; + } + } + // ALERT( at_console, "none dead\n"); + return NULL; +} + + +void COsprey :: HoverThink( void ) +{ + int i; + for (i = 0; i < 4; i++) + { + if (m_hRepel[i] != NULL && m_hRepel[i]->pev->health > 0 && !(m_hRepel[i]->pev->flags & FL_ONGROUND)) + { + break; + } + } + + if (i == 4) + { + m_startTime = gpGlobals->time; + SetThink(&COsprey :: FlyThink ); + } + + SetNextThink( 0.1 ); + UTIL_MakeAimVectors( pev->angles ); + ShowDamage( ); +} + + +void COsprey::UpdateGoal( ) +{ + if (m_pGoalEnt) + { + m_pos1 = m_pos2; + m_ang1 = m_ang2; + m_vel1 = m_vel2; + m_pos2 = m_pGoalEnt->pev->origin; + m_ang2 = m_pGoalEnt->pev->angles; + UTIL_MakeAimVectors( Vector( 0, m_ang2.y, 0 ) ); + + //LRC - ugh. we shouldn't require our path corners to specify a speed! + if (m_pGoalEnt->pev->speed) + pev->speed = m_pGoalEnt->pev->speed; + + m_vel2 = gpGlobals->v_forward * pev->speed; //LRC + + m_startTime = m_startTime + m_dTime; + m_dTime = 2.0 * (m_pos1 - m_pos2).Length() / (m_vel1.Length() + pev->speed); + + //ALERT(at_console, "osprey m_dTime = %f / %f + %f\n", (m_pos1 - m_pos2).Length(), m_vel1.Length(), m_pGoalEnt->pev->speed); + + if (m_ang1.y - m_ang2.y < -180) + { + m_ang1.y += 360; + } + else if (m_ang1.y - m_ang2.y > 180) + { + m_ang1.y -= 360; + } + + if (pev->speed < 400) + m_flIdealtilt = 0; + else + m_flIdealtilt = -90; + } + else + { + ALERT( at_debug, "osprey missing target"); + } +} + + +void COsprey::FlyThink( void ) +{ + StudioFrameAdvance( ); + SetNextThink( 0.1 ); + + if ( m_pGoalEnt == NULL && !FStringNull(pev->target) )// this monster has a target + { + m_pGoalEnt = UTIL_FindEntityByTargetname( NULL, STRING( pev->target ) ); + UpdateGoal( ); + } + + if (gpGlobals->time > m_startTime + m_dTime) + { + if (m_pGoalEnt->pev->speed == 0) + { + SetThink(&COsprey:: DeployThink ); + } + int loopbreaker = 100; //LRC - don't loop indefinitely! + do { + m_pGoalEnt = UTIL_FindEntityByTargetname( NULL, STRING( m_pGoalEnt->pev->target ) ); + loopbreaker--; //LRC + } while (m_pGoalEnt->pev->speed < 400 && !HasDead() && loopbreaker > 0); + UpdateGoal( ); + } + + Flight( ); + ShowDamage( ); +} + + +void COsprey::Flight( ) +{ + float t = (gpGlobals->time - m_startTime); + float scale = 1.0 / m_dTime; + + float f = UTIL_SplineFraction( t * scale, 1.0 ); + +// ALERT(at_console, "Osprey setorigin m_pos1 %f, m_vel1 %f, m_pos2 %f, m_vel2 %f, m_dTime %f, t %f, f %f\n", m_pos1.x, m_vel1.x, m_pos2.x, m_vel2.x, m_dTime, t, f); + + Vector pos = (m_pos1 + m_vel1 * t) * (1.0 - f) + (m_pos2 - m_vel2 * (m_dTime - t)) * f; + Vector ang = (m_ang1) * (1.0 - f) + (m_ang2) * f; + m_velocity = m_vel1 * (1.0 - f) + m_vel2 * f; + + UTIL_SetOrigin( this, pos ); + pev->angles = ang; + UTIL_MakeAimVectors( pev->angles ); + float flSpeed = DotProduct( gpGlobals->v_forward, m_velocity ); + + // float flSpeed = DotProduct( gpGlobals->v_forward, pev->velocity ); + + float m_flIdealtilt = (160 - flSpeed) / 10.0; + + // ALERT( at_console, "%f %f\n", flSpeed, flIdealtilt ); + if (m_flRotortilt < m_flIdealtilt) + { + m_flRotortilt += 0.5; + if (m_flRotortilt > 0) + m_flRotortilt = 0; + } + if (m_flRotortilt > m_flIdealtilt) + { + m_flRotortilt -= 0.5; + if (m_flRotortilt < -90) + m_flRotortilt = -90; + } + SetBoneController( 0, m_flRotortilt ); + + + if (m_iSoundState == 0) + { + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_rotor4.wav", 1.0, 0.15, 0, 110 ); + // EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_whine1.wav", 0.5, 0.2, 0, 110 ); + + m_iSoundState = SND_CHANGE_PITCH; // hack for going through level transitions + } + else + { + CBaseEntity *pPlayer = NULL; + + pPlayer = UTIL_FindEntityByClassname( NULL, "player" ); + // UNDONE: this needs to send different sounds to every player for multiplayer. + if (pPlayer) + { + float pitch = DotProduct( m_velocity - pPlayer->pev->velocity, (pPlayer->pev->origin - pev->origin).Normalize() ); + + pitch = (int)(100 + pitch / 75.0); + + if (pitch > 250) + pitch = 250; + if (pitch < 50) + pitch = 50; + + if (pitch == 100) + pitch = 101; + + if (pitch != m_iPitch) + { + m_iPitch = pitch; + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_rotor4.wav", 1.0, 0.15, SND_CHANGE_PITCH | SND_CHANGE_VOL, pitch); + // ALERT( at_console, "%.0f\n", pitch ); + } + } + // EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_whine1.wav", flVol, 0.2, SND_CHANGE_PITCH | SND_CHANGE_VOL, pitch); + } + +} + + +void COsprey::HitTouch( CBaseEntity *pOther ) +{ + SetNextThink( 2.0 ); +} + + +/* +int COsprey::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + if (m_flRotortilt <= -90) + { + m_flRotortilt = 0; + } + else + { + m_flRotortilt -= 45; + } + SetBoneController( 0, m_flRotortilt ); + return 0; +} +*/ + + + +void COsprey :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->gravity = 0.3; + pev->velocity = m_velocity; + pev->avelocity = Vector( RANDOM_FLOAT( -20, 20 ), 0, RANDOM_FLOAT( -50, 50 ) ); + STOP_SOUND( ENT(pev), CHAN_STATIC, "apache/ap_rotor4.wav" ); + + UTIL_SetSize( pev, Vector( -32, -32, -64), Vector( 32, 32, 0) ); + SetThink(&COsprey :: DyingThink ); + SetTouch(&COsprey :: CrashTouch ); + SetNextThink( 0.1 ); + pev->health = 0; + pev->takedamage = DAMAGE_NO; + + m_startTime = gpGlobals->time + 4.0; +} + +void COsprey::CrashTouch( CBaseEntity *pOther ) +{ + // only crash if we hit something solid + if ( pOther->pev->solid == SOLID_BSP) + { + SetTouch( NULL ); + m_startTime = gpGlobals->time; + SetNextThink( 0 ); + m_velocity = pev->velocity; + } +} + + +void COsprey :: DyingThink( void ) +{ + StudioFrameAdvance( ); + SetNextThink( 0.1 ); + + pev->avelocity = pev->avelocity * 1.02; + + // still falling? + if (m_startTime > gpGlobals->time ) + { + UTIL_MakeAimVectors( pev->angles ); + ShowDamage( ); + + Vector vecSpot = pev->origin + pev->velocity * 0.2; + + // random explosions + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_EXPLOSION); // This just makes a dynamic light now + WRITE_COORD( vecSpot.x + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( vecSpot.y + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( vecSpot.z + RANDOM_FLOAT( -150, -50 )); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( RANDOM_LONG(0,29) + 30 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + + // lots of smoke + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSpot.x + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( vecSpot.y + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( vecSpot.z + RANDOM_FLOAT( -150, -50 )); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 100 ); // scale * 10 + WRITE_BYTE( 10 ); // framerate + MESSAGE_END(); + + + vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z ); + + // size + WRITE_COORD( 800 ); + WRITE_COORD( 800 ); + WRITE_COORD( 132 ); + + // velocity + WRITE_COORD( pev->velocity.x ); + WRITE_COORD( pev->velocity.y ); + WRITE_COORD( pev->velocity.z ); + + // randomization + WRITE_BYTE( 50 ); + + // Model + WRITE_SHORT( m_iTailGibs ); //model id# + + // # of shards + WRITE_BYTE( 8 ); // let client decide + + // duration + WRITE_BYTE( 200 );// 10.0 seconds + + // flags + + WRITE_BYTE( BREAK_METAL ); + MESSAGE_END(); + + + + // don't stop it we touch a entity + pev->flags &= ~FL_ONGROUND; + SetNextThink( 0.2 ); + return; + } + else + { + Vector vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + + /* + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_EXPLOSION); // This just makes a dynamic light now + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 512 ); + WRITE_SHORT( m_iExplode ); + WRITE_BYTE( 250 ); // scale * 10 + WRITE_BYTE( 10 ); // framerate + MESSAGE_END(); + */ + + // gibs + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_SPRITE ); + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 512 ); + WRITE_SHORT( m_iExplode ); + WRITE_BYTE( 250 ); // scale * 10 + WRITE_BYTE( 255 ); // brightness + MESSAGE_END(); + + /* + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 300 ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 250 ); // scale * 10 + WRITE_BYTE( 6 ); // framerate + MESSAGE_END(); + */ + + // blast circle + MESSAGE_BEGIN( MSG_PAS, 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 + 2000 ); // reach damage radius over .2 seconds + WRITE_SHORT( m_iSpriteTexture ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 4 ); // life + WRITE_BYTE( 32 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 192 ); // r, g, b + WRITE_BYTE( 128 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + EMIT_SOUND(ENT(pev), CHAN_STATIC, "weapons/mortarhit.wav", 1.0, 0.3); + + RadiusDamage( pev->origin, pev, pev, 300, CLASS_NONE, DMG_BLAST ); + + // gibs + vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 64); + + // size + WRITE_COORD( 800 ); + WRITE_COORD( 800 ); + WRITE_COORD( 128 ); + + // velocity + WRITE_COORD( m_velocity.x ); + WRITE_COORD( m_velocity.y ); + WRITE_COORD( fabs( m_velocity.z ) * 0.25 ); + + // randomization + WRITE_BYTE( 40 ); + + // Model + WRITE_SHORT( m_iBodyGibs ); //model id# + + // # of shards + WRITE_BYTE( 128 ); + + // duration + WRITE_BYTE( 200 );// 10.0 seconds + + // flags + + WRITE_BYTE( BREAK_METAL ); + MESSAGE_END(); + + UTIL_Remove( this ); + } +} + + +void COsprey :: ShowDamage( void ) +{ + if (m_iDoLeftSmokePuff > 0 || RANDOM_LONG(0,99) > m_flLeftHealth) + { + Vector vecSrc = pev->origin + gpGlobals->v_right * -340; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSrc ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSrc.x ); + WRITE_COORD( vecSrc.y ); + WRITE_COORD( vecSrc.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( RANDOM_LONG(0,9) + 20 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + if (m_iDoLeftSmokePuff > 0) + m_iDoLeftSmokePuff--; + } + if (m_iDoRightSmokePuff > 0 || RANDOM_LONG(0,99) > m_flRightHealth) + { + Vector vecSrc = pev->origin + gpGlobals->v_right * 340; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSrc ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSrc.x ); + WRITE_COORD( vecSrc.y ); + WRITE_COORD( vecSrc.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( RANDOM_LONG(0,9) + 20 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + if (m_iDoRightSmokePuff > 0) + m_iDoRightSmokePuff--; + } +} + + +void COsprey::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + // ALERT( at_console, "%d %.0f\n", ptr->iHitgroup, flDamage ); + + // only so much per engine + if (ptr->iHitgroup == 3) + { + if (m_flRightHealth < 0) + return; + else + m_flRightHealth -= flDamage; + m_iDoLeftSmokePuff = 3 + (flDamage / 5.0); + } + + if (ptr->iHitgroup == 2) + { + if (m_flLeftHealth < 0) + return; + else + m_flLeftHealth -= flDamage; + m_iDoRightSmokePuff = 3 + (flDamage / 5.0); + } + + // hit hard, hits cockpit, hits engines + if (flDamage > 50 || ptr->iHitgroup == 1 || ptr->iHitgroup == 2 || ptr->iHitgroup == 3) + { + // ALERT( at_console, "%.0f\n", flDamage ); + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + } + else + { + UTIL_Sparks( ptr->vecEndPos ); + } +} + + + + + diff --git a/dlls/paranoia_military.cpp b/dlls/paranoia_military.cpp new file mode 100644 index 0000000..d6fb46a --- /dev/null +++ b/dlls/paranoia_military.cpp @@ -0,0 +1,5019 @@ +/*** +* +* Copyright (c) 2006. All rights reserved. +* Paranoia military human class, based on valve's hgrunt class +* Written by BUzer +* +****/ + + +//========================================================= +// Hit groups! +//========================================================= +/* + + 1 - Head + 2 - Stomach + 3 - Gun + +*/ + + +#include "extdll.h" +#include "plane.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "animation.h" +#include "weapons.h" +#include "talkmonster.h" +#include "soundent.h" +#include "effects.h" +#include "customentity.h" +#include "scripted.h" //LRC +#include "rushscript.h" // buz +#include "player.h" // buz +#include "monster_head_controller.h"//MaSTeR + +extern DLL_GLOBAL int g_iSkillLevel; + +#define SF_HAS_FLASHLIGHT 4096 +#define SF_TOGGLE_FLASHLIGHT 8192 +#define SF_FLASHLIGHT_ON 16384 +//========================================================= +// monster-specific DEFINE's +//========================================================= +#define MIL_CLIP_SIZE 36 // how many bullets in a clip? - NOTE: 3 round burst sound, so keep as 3 * x! +#define MIL_VOL 0.35 // volume of grunt sounds +#define MIL_ATTN ATTN_NORM // attenutation of grunt sentences +#define MIL_LIMP_HEALTH 20 +#define MIL_DMG_HEADSHOT ( DMG_BULLET | DMG_CLUB ) // damage types that can kill a grunt with a single headshot. + +// ammunition types +#define MIL_AKONLY 0 +#define MIL_GRENADES 1 + +#define MIL_GUN_GROUP 2 +#define MIL_GUN_AK 0 +#define MIL_GUN_NONE 1 + +#define MIL_HEAD_GROUP 0 +#define MIL_HEAD_GASMASK 2 + +#define MIL_GASMASK_GROUP 3 + +#define MIL_STUFF_GROUP 4 + +TYPEDESCRIPTION CHeadController::m_SaveData[] = +{ + DEFINE_FIELD( CHeadController, m_iLightLevel, FIELD_INTEGER ), + DEFINE_FIELD( CHeadController, m_iBackwardLen, FIELD_INTEGER ), + DEFINE_FIELD( CHeadController, m_hOwner, FIELD_EHANDLE ), +}; IMPLEMENT_SAVERESTORE( CHeadController, CBaseEntity ); + +LINK_ENTITY_TO_CLASS( head_flashlight, CFlashlight ); +LINK_ENTITY_TO_CLASS( head_controller, CHeadController ); + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define MIL_AE_RELOAD ( 2 ) +#define MIL_AE_KICK ( 3 ) +#define MIL_AE_BURST1 ( 4 ) +#define MIL_AE_BURST2 ( 5 ) +#define MIL_AE_BURST3 ( 6 ) +#define MIL_AE_GREN_TOSS ( 7 ) +//#define MIL_AE_GREN_LAUNCH ( 8 ) +#define MIL_AE_GREN_DROP ( 9 ) +#define MIL_AE_CAUGHT_ENEMY ( 10) // grunt established sight with an enemy (player only) that had previously eluded the squad. +#define MIL_AE_DROP_GUN ( 11) // grunt (probably dead) is dropping his mp5. +#define MIL_AE_FLASHLIGHT ( 12) +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_MIL_SUPPRESS = LAST_TALKMONSTER_SCHEDULE + 1, + SCHED_MIL_ESTABLISH_LINE_OF_FIRE,// move to a location to set up an attack against the enemy. (usually when a friendly is in the way). + SCHED_MIL_COVER_AND_RELOAD, + SCHED_MIL_SWEEP, + SCHED_MIL_FOUND_ENEMY, + SCHED_MIL_REPEL, + SCHED_MIL_REPEL_ATTACK, + SCHED_MIL_REPEL_LAND, + SCHED_MIL_WAIT_FACE_ENEMY, + SCHED_MIL_TAKECOVER_FAILED,// special schedule type that forces analysis of conditions and picks the best possible schedule to recover from this type of failure. + SCHED_MIL_ELOF_FAIL, + SCHED_MIL_DUCK_COVER_WAIT, // buz + SCHED_MIL_FLASHLIGHT, //Toggle flashlight + SCHED_MIL_WALKBACK_FIRE, //Walk backward and fire + SCHED_INFECTED_FIRINGWALK, +}; + +//========================================================= +// monster-specific tasks +//========================================================= +enum +{ + TASK_MIL_FACE_TOSS_DIR = LAST_TALKMONSTER_TASK + 1, + TASK_MIL_SPEAK_SENTENCE, + TASK_MIL_CHECK_FIRE, +}; + +//========================================================= +// monster-specific conditions +//========================================================= +#define bits_COND_MIL_NOFIRE ( bits_COND_SPECIAL1 ) + +class CMilitary : public CTalkMonster +{ +public: + void Spawn( void ); + void Precache( void ); + float MaxYawSpeed ( void ); + int Classify ( void ); + int ISoundMask ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + + void InitFlashlight( void ); + void InitHeadController( void ); + void ToggleFlashlight ( void ); + + BOOL FCanCheckAttacks ( void ); + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack2 ( float flDot, float flDist ); + void CheckAmmo ( void ); + void SetActivity ( Activity NewActivity ); + void StartTask ( Task_t *pTask ); + void RunTask ( Task_t *pTask ); + void DeathSound( void ); + void PainSound( void ); +// void IdleSound ( void ); + Vector GetGunPosition( void ); + virtual void Shoot ( void ); +// void Shotgun ( void ); +// void PrescheduleThink ( void ); + void GibMonster( void ); + void SpeakSentence( void ); + BOOL NoFriendlyFire(void); // buz + void TalkInit(); // buz + void DeclineFollowing(); //buz + virtual BOOL GetEnemy ( void ); // buz + void BlockedByPlayer ( CBasePlayer *pBlocker ); // buz + void TalkAboutDeadFriend( CTalkMonster *pfriend ); + + // buz: overriden for grunts to fix model's bugs... + virtual void SetEyePosition ( void ) + { + Vector vecEyePosition; + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + GetEyePosition( pmodel, vecEyePosition ); + pev->view_ofs = vecEyePosition; + if ( pev->view_ofs == g_vecZero ) + { + pev->view_ofs = Vector(0, 0 ,73); + // ALERT ( at_aiconsole, "using default view ofs for %s\n", STRING ( pev->classname ) ); + } + } + + // buz: overriden for soldiers - eye position is differs when monster is crouching + virtual Vector EyePosition( ) + { + if (m_Activity == ACT_TWITCH) + return pev->origin + Vector(0, 0, 36); + return pev->origin + pev->view_ofs; + } + + // Wargon: Þçàòü ìîíñòðà ìîæíî òîëüêî åñëè îí æèâ. Ýòî íóæíî ÷òîáû èêîíêà þçà íå ïîêàçûâàëàñü íà ìåðòâûõ ìîíñòðàõ. + virtual int ObjectCaps( void ) { if (pev->deadflag == DEAD_NO) return CTalkMonster :: ObjectCaps() | FCAP_IMPULSE_USE | FCAP_DISTANCE_USE; else return CTalkMonster::ObjectCaps(); } + + int Save( CSave &save ); + int Restore( CRestore &restore ); + + CBaseEntity *Kick( void ); + Schedule_t *GetSchedule( void ); + Schedule_t *GetScheduleOfType ( int Type ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + +// int IRelationship ( CBaseEntity *pTarget ); // buz: use talkmoster's relationship + + BOOL FOkToSpeak( void ); + void JustSpoke( void ); + + CUSTOM_SCHEDULES; + static TYPEDESCRIPTION m_SaveData[]; + + // checking the feasibility of a grenade toss is kind of costly, so we do it every couple of seconds, + // not every server frame. + float m_flNextGrenadeCheck; + float m_flNextPainTime; + float m_flLastEnemySightTime; + + //MaSTeR: Flashlight and head controller + CFlashlight *pFlashlight; + CHeadController *pHeadController; + + Vector m_vecTossVelocity; + + // Wargon: Åñëè âðàã âçÿò èç äàííûõ èãðîêà, òî TRUE. Èíà÷å FALSE. (1.1) + BOOL m_fEnemyFromPlayer; + + BOOL m_fThrowGrenade; + BOOL m_fStanding; +// BOOL m_fFirstEncounter;// only put on the handsign show in the squad's first encounter. + int m_cClipSize; + + int m_voicePitch; + + int m_iBrassShell; +// int m_iShotgunShell; + + int m_iSentence; + + // buz + int m_iNoGasDamage; + + int m_iLastFireCheckResult; // buz. 1-only crouch, 2-only standing, 0-any + static const char *pGruntSentences[]; +}; + +LINK_ENTITY_TO_CLASS( monster_human_military, CMilitary ); + +TYPEDESCRIPTION CMilitary::m_SaveData[] = +{ + DEFINE_FIELD( CMilitary, m_flNextGrenadeCheck, FIELD_TIME ), + DEFINE_FIELD( CMilitary, m_flNextPainTime, FIELD_TIME ), + DEFINE_FIELD( CMilitary, m_vecTossVelocity, FIELD_VECTOR ), + DEFINE_FIELD( CMilitary, m_fThrowGrenade, FIELD_BOOLEAN ), + DEFINE_FIELD( CMilitary, m_fStanding, FIELD_BOOLEAN ), + DEFINE_FIELD( CMilitary, m_cClipSize, FIELD_INTEGER ), + DEFINE_FIELD( CMilitary, m_voicePitch, FIELD_INTEGER ), + DEFINE_FIELD( CMilitary, m_iSentence, FIELD_INTEGER ), + DEFINE_FIELD( CMilitary, m_iNoGasDamage, FIELD_INTEGER ), + DEFINE_FIELD( CMilitary, pFlashlight, FIELD_EHANDLE ), + DEFINE_FIELD( CMilitary, pHeadController, FIELD_EHANDLE ), +}; IMPLEMENT_SAVERESTORE( CMilitary, CTalkMonster ); + +const char *CMilitary::pGruntSentences[] = +{ + "VV_GREN", // grenade scared grunt + "VV_ALERT", // sees player + "VV_MONSTER", // sees monster + "VV_COVER", // running to cover + "VV_THROW", // about to throw grenade + "VV_CHARGE", // running out to get the enemy + "VV_TAUNT", // say rude things +}; + +enum +{ + MIL_SENT_NONE = -1, + MIL_SENT_GREN = 0, + MIL_SENT_ALERT, + MIL_SENT_MONSTER, + MIL_SENT_COVER, + MIL_SENT_THROW, + MIL_SENT_CHARGE, + MIL_SENT_TAUNT, +} MIL_SENTENCE_TYPES; + + +void CMilitary :: BlockedByPlayer ( CBasePlayer *pBlocker ) +{ + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_BLOCKED"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "VV_BLOCKED", 4, VOL_NORM, ATTN_NORM ); + } +} + +void CMilitary :: TalkAboutDeadFriend( CTalkMonster *pfriend ) +{ + if (FClassnameIs(pfriend->pev, STRING(pev->classname))) + { + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_TMDOWN"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "VV_TMDOWN", 4, VOL_NORM, ATTN_NORM ); + } + } +} + + +//========================================================= +// buz: overriden for allied soldiers - they can get enemy from player's data +//========================================================= +BOOL CMilitary :: GetEnemy ( void ) +{ + // Wargon: Åñëè âðàãà íåò, òî ïåðåìåííàÿ ñáðàñûâàåòñÿ. (1.1) + if (m_hEnemy == NULL) + { + m_fEnemyFromPlayer = FALSE; + } + if (!CBaseMonster::GetEnemy()) + { + // buz: cant get enemy using normal way, try player's data + if (IsFollowing() && m_hTargetEnt->IsPlayer()) + { + CBasePlayer *pMyMaster = (CBasePlayer*)((CBaseEntity*)m_hTargetEnt); + + // big bunch of checks.. + if ((pMyMaster->m_hLastEnemy != NULL) && + (pMyMaster->m_hLastEnemy->MyMonsterPointer()) && + (pMyMaster->m_hLastEnemy != this) && + (pMyMaster->m_hLastEnemy->pev->health > 0) && + (pMyMaster->m_hLastEnemy->IsAlive()) && + !FBitSet(pMyMaster->m_hLastEnemy->pev->spawnflags, SF_MONSTER_PRISONER) && + !FBitSet(pMyMaster->m_hLastEnemy->pev->flags, FL_NOTARGET) && + (IRelationship( pMyMaster->m_hLastEnemy ) > 0)) + { + m_hEnemy = pMyMaster->m_hLastEnemy; + m_vecEnemyLKP = m_hEnemy->pev->origin; + + // Wargon: Âðàã âçÿò èç äàííûõ èãðîêà. (1.1) + m_fEnemyFromPlayer = TRUE; + + if ( m_pSchedule ) + { + if ( m_pSchedule->iInterruptMask & bits_COND_NEW_ENEMY ) + { + SetConditions(bits_COND_NEW_ENEMY); + } + } + +// ALERT(at_console, "get enemy from player!\n"); + return TRUE; + } + } + } + + return FALSE; +// return CBaseMonster::GetEnemy(); +} + + + +//========================================================= +// Speak Sentence - say your cued up sentence. +// +// Some grunt sentences (take cover and charge) rely on actually +// being able to execute the intended action. It's really lame +// when a grunt says 'COVER ME' and then doesn't move. The problem +// is that the sentences were played when the decision to TRY +// to move to cover was made. Now the sentence is played after +// we know for sure that there is a valid path. The schedule +// may still fail but in most cases, well after the grunt has +// started moving. +//========================================================= +void CMilitary :: SpeakSentence( void ) +{ + if ( m_iSentence == MIL_SENT_NONE ) + { + // no sentence cued up. + return; + } + +// if (FOkToSpeak()) +// { + SENTENCEG_PlayRndSz( ENT(pev), pGruntSentences[ m_iSentence ], MIL_VOL, MIL_ATTN, 0, m_voicePitch); + JustSpoke(); +// } +} + +//========================================================= +//MaSTeR: Èíèöèàëèçàöèÿ ôîíàðÿ (ñîçäàíèå ýíòèòè è ïðîâåðêà ñïàóíôëàãîâ) +//========================================================= +void CMilitary :: InitHeadController( void ) +{ + if( !pHeadController ) + { + pHeadController = GetClassPtr((CHeadController *)NULL ); + pHeadController->Spawn( this ); + } +} + +void CMilitary :: InitFlashlight( void ) +{ + if ( FBitSet( pev->spawnflags, SF_HAS_FLASHLIGHT )) + { + if( !pFlashlight ) + { + pFlashlight = GetClassPtr((CFlashlight *)NULL ); + pFlashlight->Spawn( pev ); + } + + if( FBitSet( pev->spawnflags, SF_FLASHLIGHT_ON )) + pFlashlight->On(); + } +} + +//========================================================= +//MaSTeR: Ïåðåêëþ÷åíèå ôîíàðèêà (âêë\âûêë) +//========================================================= +void CMilitary :: ToggleFlashlight( void ) +{ + // if we suppose to toglle flashlight + if ( pFlashlight && FBitSet( pev->spawnflags, SF_TOGGLE_FLASHLIGHT )) + { + if( pFlashlight->GetState() == STATE_ON ) + pFlashlight->Off(); + else pFlashlight->On(); + } +} + +//========================================================= +// GibMonster - make gun fly through the air. +//========================================================= +void CMilitary :: GibMonster ( void ) +{ + Vector vecGunPos; + Vector vecGunAngles; + + if (GetBodygroup(MIL_GUN_GROUP) != MIL_GUN_NONE && !(pev->spawnflags & SF_MONSTER_NO_WPN_DROP)) + { + GetAttachment( 0, vecGunPos, vecGunAngles ); + + CBaseEntity *pGun = DropItem( "weapon_aks", vecGunPos, vecGunAngles ); + + if ( pGun ) + { + pGun->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + pGun->pev->avelocity = Vector ( 0, RANDOM_FLOAT( 200, 400 ), 0 ); + } + + if (pev->weapons == MIL_GRENADES ) + { + pGun = DropItem( "weapon_handgrenade", vecGunPos, vecGunAngles ); + if ( pGun ) + { + pGun->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + pGun->pev->avelocity = Vector ( 0, RANDOM_FLOAT( 200, 400 ), 0 ); + } + } + } + + CBaseMonster :: GibMonster(); +} + +//========================================================= +// ISoundMask - Overidden for human grunts because they +// hear the DANGER sound that is made by hand grenades and +// other dangerous items. +//========================================================= +int CMilitary :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER | + bits_SOUND_DANGER; +} + +//========================================================= +// buz: ó ãðóíòîâ FOkToSpeak î÷åíü ïðîñòîé - âñåãî-ëèøü ïðîâåðêà íà Gag. +// íî ó äðóæåñòâåííûõ èãðîêó ìîíñòðîâ ýòà ôóíêöèÿ îçíà÷àåò âîçìîæíîñòü +// áàçàðèòü î âñÿêîé ôèãíå - çàäàâàòü äðóã äðóãó âîïðîñû, ðàçãîâàðèâàòü î òîì î ñåì.. +//========================================================= +BOOL CMilitary :: FOkToSpeak( void ) +{ + if ( pev->spawnflags & SF_MONSTER_GAG ) + return FALSE; + + if ( m_MonsterState == MONSTERSTATE_PRONE || m_IdealMonsterState == MONSTERSTATE_PRONE ) + return FALSE; + + // if not alive, certainly don't speak + if ( pev->deadflag != DEAD_NO ) + return FALSE; + + // if player is not in pvs, don't speak + if (!IsAlive() || FNullEnt(FIND_CLIENT_IN_PVS(edict()))) + return FALSE; + + // don't talk if you're in combat + if (m_hEnemy != NULL && FVisible( m_hEnemy )) + return FALSE; + + return TRUE; +} + +//========================================================= +//========================================================= +void CMilitary :: JustSpoke( void ) +{ + CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(1.5, 2.0); + m_iSentence = MIL_SENT_NONE; +} + + +//========================================================= +// FCanCheckAttacks - this is overridden for human grunts +// because they can throw/shoot grenades when they can't see their +// target and the base class doesn't check attacks if the monster +// cannot see its enemy. +// +// !!!BUGBUG - this gets called before a 3-round burst is fired +// which means that a friendly can still be hit with up to 2 rounds. +// ALSO, grenades will not be tossed if there is a friendly in front, +// this is a bad bug. Friendly machine gun fire avoidance +// will unecessarily prevent the throwing of a grenade as well. +//========================================================= +BOOL CMilitary :: FCanCheckAttacks ( void ) +{ + if ( !HasConditions( bits_COND_ENEMY_TOOFAR ) ) + { + return TRUE; + } + else + { + return FALSE; + } +} + + +//========================================================= +// CheckMeleeAttack1 +//========================================================= +BOOL CMilitary :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + CBaseMonster *pEnemy; + + if ( m_hEnemy != NULL ) + { + pEnemy = m_hEnemy->MyMonsterPointer(); + + if ( !pEnemy ) + { + return FALSE; + } + } + + // Wargon: Ðàññòîÿíèå flDist óìåíüøåíî ñ 64 äî 48. (1.1) + if ( flDist <= 48 && flDot >= 0.7 && + pEnemy->Classify() != CLASS_ALIEN_BIOWEAPON && + pEnemy->Classify() != CLASS_PLAYER_BIOWEAPON ) + { + return TRUE; + } + return FALSE; +} + +//============================================== +// buz: +// NoFriendlyFire - basically copied from squad monsters +// true - shoot, false - dont shoot +// ============================================= +BOOL CMilitary :: NoFriendlyFire(void) +{ + CPlane backPlane; + CPlane leftPlane; + CPlane rightPlane; + + Vector vecLeftSide; + Vector vecRightSide; + Vector v_left; + + //!!!BUGBUG - to fix this, the planes must be aligned to where the monster will be firing its gun, not the direction it is facing!!! + + if ( m_hEnemy != NULL ) + { + UTIL_MakeVectors ( UTIL_VecToAngles( m_hEnemy->Center() - pev->origin ) ); + } + else + { + // if there's no enemy, pretend there's a friendly in the way, so the grunt won't shoot. + return FALSE; + } + + // buz: simply check by traceline for other monster_human_military +/* TraceResult tr; + UTIL_TraceLine(pev->origin, pev->origin + (gpGlobals->v_forward * 1024), dont_ignore_monsters, ENT(pev), &tr); + if (tr.pHit && (FClassnameIs(tr.pHit, "monster_human_military") || + FClassnameIs(tr.pHit, "monster_scientist") || + FClassnameIs(tr.pHit, "monster_barney") || + FClassnameIs(tr.pHit, "monster_human_alpha") || + FClassnameIs(tr.pHit, "monster_alpha_pistol"))) + return FALSE;*/ + + vecLeftSide = pev->origin - ( gpGlobals->v_right * ( pev->size.x * 1.5 ) ); + vecRightSide = pev->origin + ( gpGlobals->v_right * ( pev->size.x * 1.5 ) ); + v_left = gpGlobals->v_right * -1; + + leftPlane.InitializePlane ( gpGlobals->v_right, vecLeftSide ); + rightPlane.InitializePlane ( v_left, vecRightSide ); + backPlane.InitializePlane ( gpGlobals->v_forward, pev->origin ); + + // search all entities around + Vector mins = pev->origin - Vector(1024, 1024, 1024); + Vector maxs = pev->origin + Vector(1024, 1024, 1024); + CBaseEntity *pList[256]; + int count = UTIL_EntitiesInBox( pList, 256, mins, maxs, FL_MONSTER ); + for ( int i = 0; i < count; i++ ) + { + CBaseMonster *pMonster = pList[i]->MyMonsterPointer(); + if (pMonster && (pMonster != this) && (IRelationship(pMonster) <= R_NO)) + { + if ( backPlane.PointInFront ( pMonster->pev->origin ) && + leftPlane.PointInFront ( pMonster->pev->origin ) && + rightPlane.PointInFront ( pMonster->pev->origin) ) + { + // this guy is in the check volume! Don't shoot! + // ALERT(at_console, "dont shoot!\n"); + return FALSE; + } + } + } + + // check for player + edict_t *pentPlayer = FIND_CLIENT_IN_PVS( edict() ); + if (!FNullEnt(pentPlayer) && (pentPlayer != m_hEnemy->edict()) && + backPlane.PointInFront ( pentPlayer->v.origin ) && + leftPlane.PointInFront ( pentPlayer->v.origin ) && + rightPlane.PointInFront ( pentPlayer->v.origin ) ) + { + // the player is in the check volume! Don't shoot! + return FALSE; + } + + return TRUE; +} + + + +//========================================================= +// CheckRangeAttack1 - overridden for HGrunt, cause +// FCanCheckAttacks() doesn't disqualify all attacks based +// on whether or not the enemy is occluded because unlike +// the base class, the HGrunt can attack when the enemy is +// occluded (throw grenade over wall, etc). We must +// disqualify the machine gun attack if the enemy is occluded. +//========================================================= +BOOL CMilitary :: CheckRangeAttack1 ( float flDot, float flDist ) +{ +/* if ( !HasConditions( bits_COND_ENEMY_OCCLUDED ) && flDist <= 2048 && flDot >= 0.5 && NoFriendlyFire() ) + { + TraceResult tr; + + if ( flDist <= 64 ) + { + // kick nonclients who are close enough, but don't shoot at them. + return FALSE; + } + + Vector vecSrc = GetGunPosition(); + + // verify that a bullet fired from the gun will hit the enemy before the world. + UTIL_TraceLine( vecSrc, m_hEnemy->BodyTarget(vecSrc), ignore_monsters, ignore_glass, ENT(pev), &tr); + + if ( tr.flFraction == 1.0 ) + { + return TRUE; + } + } + + return FALSE;*/ + + if ( !HasConditions( bits_COND_ENEMY_OCCLUDED ) && flDist <= 2048 && flDot >= 0.5 && NoFriendlyFire() ) + { + TraceResult tr; + + if ( !m_hEnemy->IsPlayer() && flDist <= 48 ) // Wargon: Ðàññòîÿíèå flDist óìåíüøåíî ñ 64 äî 48. (1.1) + { + // kick nonclients who are close enough, but don't shoot at them. + return FALSE; + } + + BOOL savedStanding = m_fStanding; + m_fStanding = FALSE; // buz: check chrouched fire first + Vector vecSrc = GetGunPosition(); + + // verify that a bullet fired from the gun will hit the enemy before the world. + UTIL_TraceLine( vecSrc, m_hEnemy->BodyTarget(vecSrc), ignore_monsters, ignore_glass, ENT(pev), &tr); + if ( tr.flFraction == 1.0 && !pev->gaitsequence) // buz: cant fire crouched when moving + { + // buz: we can fire crouched, now check for standing + m_fStanding = TRUE; + vecSrc = GetGunPosition(); + UTIL_TraceLine( vecSrc, m_hEnemy->BodyTarget(vecSrc), ignore_monsters, ignore_glass, ENT(pev), &tr); + m_fStanding = savedStanding; + if ( tr.flFraction == 1.0 ) + { + // ALERT(at_aiconsole, "== mil shoot as wish\n"); + m_iLastFireCheckResult = 0; // shoot as you wish + } + else + { + // ALERT(at_aiconsole, "== mil shoot crouched\n"); + m_iLastFireCheckResult = 1; // only chrouched + } + return TRUE; + } + else + { + // buz: cant fire crouching, maybe me or enemy in some kind of cover (or running). Check standing. + m_fStanding = TRUE; + vecSrc = GetGunPosition(); + UTIL_TraceLine( vecSrc, m_hEnemy->BodyTarget(vecSrc), ignore_monsters, ignore_glass, ENT(pev), &tr); + m_fStanding = savedStanding; + if ( tr.flFraction == 1.0 ) + { + // ALERT(at_aiconsole, "== mil shoot standing\n"); + m_iLastFireCheckResult = 2; // buz: standing is our only one choice + return TRUE; + } + else + { + // ALERT(at_aiconsole, "== mil cant shoot\n"); + m_iLastFireCheckResult = 0; + return FALSE; // cant fire + } + } + } + + return FALSE; +} + +//========================================================= +// CheckRangeAttack2 - this checks the Grunt's grenade +// attack. +//========================================================= +BOOL CMilitary :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + if (pev->weapons != MIL_GRENADES) + { + return FALSE; + } + + // if the grunt isn't moving, it's ok to check. + if ( m_flGroundSpeed != 0 ) + { + m_fThrowGrenade = FALSE; + return m_fThrowGrenade; + } + + // assume things haven't changed too much since last time + if (gpGlobals->time < m_flNextGrenadeCheck ) + { + return m_fThrowGrenade; + } + + if ( !FBitSet ( m_hEnemy->pev->flags, FL_ONGROUND ) && (m_hEnemy->pev->waterlevel == 0 || m_hEnemy->pev->watertype==CONTENTS_FOG) && m_vecEnemyLKP.z > pev->absmax.z ) + { + //!!!BUGBUG - we should make this check movetype and make sure it isn't FLY? Players who jump a lot are unlikely to + // be grenaded. + // don't throw grenades at anything that isn't on the ground! + m_fThrowGrenade = FALSE; + return m_fThrowGrenade; + } + + Vector vecTarget; + +// if (FBitSet( pev->weapons, HGRUNT_HANDGRENADE)) +// { + // find feet + if (RANDOM_LONG(0,1)) + { + // magically know where they are + vecTarget = Vector( m_hEnemy->pev->origin.x, m_hEnemy->pev->origin.y, m_hEnemy->pev->absmin.z ); + } + else + { + // toss it to where you last saw them + vecTarget = m_vecEnemyLKP; + } + // vecTarget = m_vecEnemyLKP + (m_hEnemy->BodyTarget( pev->origin ) - m_hEnemy->pev->origin); + // estimate position + // vecTarget = vecTarget + m_hEnemy->pev->velocity * 2; +/* } + else + { + // find target + // vecTarget = m_hEnemy->BodyTarget( pev->origin ); + vecTarget = m_vecEnemyLKP + (m_hEnemy->BodyTarget( pev->origin ) - m_hEnemy->pev->origin); + // estimate position + if (HasConditions( bits_COND_SEE_ENEMY)) + vecTarget = vecTarget + ((vecTarget - pev->origin).Length() / gSkillData.hgruntGrenadeSpeed) * m_hEnemy->pev->velocity; + }*/ + + // are any of my squad members near the intended grenade impact area? +/* if ( InSquad() ) + { + if (SquadMemberInRange( vecTarget, 256 )) + { + // crap, I might blow my own guy up. Don't throw a grenade and don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + m_fThrowGrenade = FALSE; + } + }*/ + + // buz: check for allies in target area: + CBaseEntity *pTarget = NULL; + while ((pTarget = UTIL_FindEntityInSphere( pTarget, vecTarget, 256 )) != NULL) + { + if (FClassnameIs( pTarget->pev, "monster_human_military") || FClassnameIs( pTarget->pev, "player")) + { + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + m_fThrowGrenade = FALSE; + } + } + + if ( ( vecTarget - pev->origin ).Length2D() <= 256 ) + { + // crap, I don't want to blow myself up + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + m_fThrowGrenade = FALSE; + return m_fThrowGrenade; + } + + +// if (FBitSet( pev->weapons, HGRUNT_HANDGRENADE)) +// { + Vector vecToss = VecCheckToss( pev, GetGunPosition(), vecTarget, 0.5 ); + + if ( vecToss != g_vecZero ) + { + m_vecTossVelocity = vecToss; + + // throw a hand grenade + m_fThrowGrenade = TRUE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time; // 1/3 second. + } + else + { + // don't throw + m_fThrowGrenade = FALSE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + } +/* } + else + { + Vector vecToss = VecCheckThrow( pev, GetGunPosition(), vecTarget, gSkillData.hgruntGrenadeSpeed, 0.5 ); + + if ( vecToss != g_vecZero ) + { + m_vecTossVelocity = vecToss; + + // throw a hand grenade + m_fThrowGrenade = TRUE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 0.3; // 1/3 second. + } + else + { + // don't throw + m_fThrowGrenade = FALSE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + } + }*/ + + return m_fThrowGrenade; +} + + +//========================================================= +// TraceAttack - make sure we're not taking it in the helmet +//========================================================= +void CMilitary :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ +// ALERT(at_console, "mil hitgr: %d\n", ptr->iHitgroup); + // check for helmet shot + if ((ptr->iHitgroup == 8) || (ptr->iHitgroup == 1)) + { + // make sure we're wearing one +/* if (GetBodygroup( 1 ) == HEAD_GRUNT && (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_BLAST | DMG_CLUB))) + { + // absorb damage + flDamage -= 20; + if (flDamage <= 0) + { + UTIL_Ricochet( ptr->vecEndPos, 1.0 ); + flDamage = 0.01; + } + }*/ // buz + // it's head shot anyways + ptr->iHitgroup = HITGROUP_HEAD; + } + CTalkMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + +// buz: basically just like barney +int CMilitary :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // buz: refuse gas damage while wearing gasmask + if (m_iNoGasDamage && ( bitsDamageType & DMG_NERVEGAS )) + return 0; + + Forget( bits_MEMORY_INCOVER ); + + // Wargon: Ñîþçíèêè íå äîëæíû âîññòàâàòü ïðîòèâ èãðîêà. + return CBaseMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); + +/* // make sure friends talk about it if player hurts talkmonsters... + int ret = CTalkMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); + if ( !IsAlive() || pev->deadflag == DEAD_DYING ) + return ret; + + // LRC - if my reaction to the player has been overridden, don't do this stuff + if (m_iPlayerReact) return ret; + + if ( m_MonsterState != MONSTERSTATE_PRONE && (pevAttacker->flags & FL_CLIENT) ) + { + // This is a heurstic to determine if the player intended to harm me + // If I have an enemy, we can't establish intent (may just be crossfire) + if ( m_hEnemy == NULL ) + { + // If the player was facing directly at me, or I'm already suspicious, get mad + if ( (m_afMemory & bits_MEMORY_SUSPICIOUS) ) + { + // Alright, now I'm pissed! + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_MAD"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "VV_MAD", 4, VOL_NORM, ATTN_NORM ); + } + + Remember( bits_MEMORY_PROVOKED ); + StopFollowing( TRUE ); + } + else + { + // Hey, be careful with that + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_SHOT"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "VV_SHOT", 4, VOL_NORM, ATTN_NORM ); + } + Remember( bits_MEMORY_SUSPICIOUS ); + } + } + else if ( !(m_hEnemy->IsPlayer()) && pev->deadflag == DEAD_NO ) + { + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_SHOT"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "VV_SHOT", 4, VOL_NORM, ATTN_NORM ); + } + } + } + + return ret; */ +} + +//========================================================= +// MaxYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +float CMilitary :: MaxYawSpeed( void ) +{ + float ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + ys = 150; + break; + case ACT_RUN: + ys = 150; + break; + case ACT_WALK: + ys = 180; + break; + case ACT_RANGE_ATTACK1: + ys = 120; + break; + case ACT_RANGE_ATTACK2: + ys = 120; + break; + case ACT_MELEE_ATTACK1: + ys = 120; + break; + case ACT_MELEE_ATTACK2: + ys = 120; + break; + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 180; + break; + case ACT_GLIDE: + case ACT_FLY: + ys = 30; + break; + default: + ys = 90; + break; + } + + return ys; +} + + +//========================================================= +// CheckAmmo - overridden for the grunt because he actually +// uses ammo! (base class doesn't) +//========================================================= +void CMilitary :: CheckAmmo ( void ) +{ + if ( m_cAmmoLoaded <= 0 ) + { + SetConditions(bits_COND_NO_AMMO_LOADED); + } +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CMilitary :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_PLAYER_ALLY;//CLASS_HUMAN_MILITARY; +} + +//========================================================= +//========================================================= +CBaseEntity *CMilitary :: Kick( void ) +{ + TraceResult tr; + + UTIL_MakeVectors( pev->angles ); + Vector vecStart = pev->origin; + vecStart.z += pev->size.z * 0.5; + Vector vecEnd = vecStart + (gpGlobals->v_forward * 48); // Wargon: Ðàññòîÿíèå óìåíüøåíî ñ 70 äî 48. (1.1) + + UTIL_TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_hull, ENT(pev), &tr ); + + if ( tr.pHit ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + return pEntity; + } + + return NULL; +} + +//========================================================= +// GetGunPosition return the end of the barrel +//========================================================= + +Vector CMilitary :: GetGunPosition( ) +{ + if (m_fStanding ) + { + return pev->origin + Vector( 0, 0, 60 ); + } + else + { + // return pev->origin + Vector( 0, 0, 48 ); + return pev->origin + Vector( 0, 0, 40 ); + } +} + +//========================================================= +// Shoot +//========================================================= +void CMilitary :: Shoot ( void ) +{ +// if (m_hEnemy == NULL && m_pCine == NULL) //LRC - scripts may fire when you have no enemy +// { +// return; +// } + + UTIL_MakeVectors ( pev->angles ); + Vector vecShootOrigin = GetGunPosition(); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); + + if (m_cAmmoLoaded > 0) + { + Vector vecBrassPos, vecBrassDir; + GetAttachment(3, vecBrassPos, vecBrassDir); + Vector vecShellVelocity = gpGlobals->v_right * RANDOM_FLOAT(40,90) + gpGlobals->v_up * RANDOM_FLOAT(75,200) + gpGlobals->v_forward * RANDOM_FLOAT(-40, 40); + EjectBrass( vecBrassPos, vecShellVelocity, pev->angles.y, m_iBrassShell, TE_BOUNCE_SHELL); + FireBullets( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_7DEGREES, 2048.0f, BULLET_NORMAL, gSkillData.monDmgAK ); // shoot +-5 degrees + + pev->effects |= EF_MUZZLEFLASH; + + m_cAmmoLoaded--;// take away a bullet! + // ALERT(at_console, "mil ammo has %d\n", m_cAmmoLoaded); + } + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CMilitary :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + Vector vecShootDir; + Vector vecShootOrigin; + + switch( pEvent->event ) + { + case MIL_AE_FLASHLIGHT: + { + ToggleFlashlight(); + break; + } + case MIL_AE_DROP_GUN: + { + if (pev->spawnflags & SF_MONSTER_NO_WPN_DROP) break; //LRC + + Vector vecGunPos; + Vector vecGunAngles; + + GetAttachment( 0, vecGunPos, vecGunAngles ); + + // switch to body group with no gun. + SetBodygroup( MIL_GUN_GROUP, MIL_GUN_NONE ); + + // now spawn a gun. + DropItem( "weapon_ak74", vecGunPos, vecGunAngles ); + + if (pev->weapons == MIL_GRENADES) + { + DropItem( "weapon_handgrenade", BodyTarget( pev->origin ), vecGunAngles ); + } + + break; + } + case MIL_AE_RELOAD: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "military/mil_reload.wav", 1, ATTN_NORM ); + m_cAmmoLoaded = m_cClipSize; + ClearConditions(bits_COND_NO_AMMO_LOADED); + break; + + case MIL_AE_GREN_TOSS: + { + UTIL_MakeVectors( pev->angles ); + // CGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 34 + Vector (0, 0, 32), m_vecTossVelocity, 3.5 ); + //LRC - a bit of a hack. Ideally the grunts would work out in advance whether it's ok to throw. + if (m_pCine) + { + Vector vecToss = g_vecZero; + if (m_hTargetEnt != NULL && m_pCine->PreciseAttack()) + { + vecToss = VecCheckToss( pev, GetGunPosition(), m_hTargetEnt->pev->origin, 0.5 ); + } + if (vecToss == g_vecZero) + { + vecToss = (gpGlobals->v_forward*0.5+gpGlobals->v_up*0.5).Normalize()*gSkillData.hgruntGrenadeSpeed; + } + CGrenade::ShootTimed( pev, GetGunPosition(), vecToss, 3.5 ); + } + else + CGrenade::ShootTimed( pev, GetGunPosition(), m_vecTossVelocity, 3.5 ); + + m_fThrowGrenade = FALSE; + m_flNextGrenadeCheck = gpGlobals->time + 6;// wait six seconds before even looking again to see if a grenade can be thrown. + // !!!LATER - when in a group, only try to throw grenade if ordered. + } + break; + + // buz: no grenade laucher +/* case HGRUNT_AE_GREN_LAUNCH: + { + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/glauncher.wav", 0.8, ATTN_NORM); + //LRC: firing due to a script? + if (m_pCine) + { + Vector vecToss; + if (m_hTargetEnt != NULL && m_pCine->PreciseAttack()) + vecToss = VecCheckThrow( pev, GetGunPosition(), m_hTargetEnt->pev->origin, gSkillData.hgruntGrenadeSpeed, 0.5 ); + else + { + // just shoot diagonally up+forwards + UTIL_MakeVectors(pev->angles); + vecToss = (gpGlobals->v_forward*0.5 + gpGlobals->v_up*0.5).Normalize() * gSkillData.hgruntGrenadeSpeed; + } + CGrenade::ShootContact( pev, GetGunPosition(), vecToss ); + } + else + CGrenade::ShootContact( pev, GetGunPosition(), m_vecTossVelocity ); + m_fThrowGrenade = FALSE; + if (g_iSkillLevel == SKILL_HARD) + m_flNextGrenadeCheck = gpGlobals->time + RANDOM_FLOAT( 2, 5 );// wait a random amount of time before shooting again + else + m_flNextGrenadeCheck = gpGlobals->time + 6;// wait six seconds before even looking again to see if a grenade can be thrown. + } + break;*/ + + case MIL_AE_GREN_DROP: + { + UTIL_MakeVectors( pev->angles ); + CGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 17 - gpGlobals->v_right * 27 + gpGlobals->v_up * 6, g_vecZero, 3 ); + } + break; + + case MIL_AE_BURST1: + { + // ALERT(at_console, "*-------- mil burst 1\n"); + // the first round of the three round burst plays the sound and puts a sound in the world sound list. + if (m_cAmmoLoaded > 0) + { + if ( RANDOM_LONG(0,1) ) + { + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "military/mil_mgun1.wav", 1, ATTN_NORM ); + } + else + { + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "military/mil_mgun2.wav", 1, ATTN_NORM ); + } + } + else + { + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/dryfire1.wav", 1, ATTN_NORM ); + } + + Shoot(); + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 384, 0.3 ); + } + break; + + case MIL_AE_BURST2: + case MIL_AE_BURST3: + Shoot(); + break; + + case MIL_AE_KICK: + { + CBaseEntity *pHurt = Kick(); + + if ( pHurt ) + { + // buz: move only if it is a monster! + if (pHurt->MyMonsterPointer()) + { + UTIL_MakeVectors( pev->angles ); + pHurt->pev->punchangle.x = 15; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 100 + gpGlobals->v_up * 50; + } + pHurt->TakeDamage( pev, pev, gSkillData.MilDmgKick, DMG_CLUB ); + } + } + break; + + case MIL_AE_CAUGHT_ENEMY: + { + // if ( FOkToSpeak() ) + // { + SENTENCEG_PlayRndSz(ENT(pev), "VV_ALERT", MIL_VOL, MIL_ATTN, 0, m_voicePitch); + JustSpoke(); + // } + } + + default: + CTalkMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CMilitary :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/soldier.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + if (pev->health == 0) + pev->health = gSkillData.milHealth; + m_flFieldOfView = VIEW_FIELD_FULL;//0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_flNextGrenadeCheck = gpGlobals->time + 1; + m_flNextPainTime = gpGlobals->time; + m_iSentence = MIL_SENT_NONE; + + m_afCapability = bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP; + +// m_fEnemyEluded = FALSE; +// m_fFirstEncounter = TRUE;// this is true when the grunt spawns, because he hasn't encountered an enemy yet. + + m_HackedGunPos = Vector ( 0, 0, 55 ); + + m_cClipSize = MIL_CLIP_SIZE; + m_cAmmoLoaded = m_cClipSize; + + // buz: pev->body is head number + int head = pev->body; + pev->body = 0; + SetBodygroup( MIL_HEAD_GROUP, head ); + + if (head == MIL_HEAD_GASMASK) + { + SetBodygroup( MIL_GASMASK_GROUP, 1 ); + m_iNoGasDamage = 1; + } + else + { + SetBodygroup( MIL_GASMASK_GROUP, 0 ); + m_iNoGasDamage = 0; + } + + // buz: pev->effects is additional stuff number + SetBodygroup( MIL_STUFF_GROUP, pev->effects ); + + MonsterInit(); + InitFlashlight(); + InitHeadController(); + + SetUse(&CMilitary :: FollowerUse ); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CMilitary :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/soldier.mdl"); + + PRECACHE_SOUND( "weapons/dryfire1.wav" ); //LRC + + PRECACHE_SOUND( "military/mil_mgun1.wav" ); + PRECACHE_SOUND( "military/mil_mgun2.wav" ); + + PRECACHE_SOUND( "military/mil_die1.wav" ); + PRECACHE_SOUND( "military/mil_die2.wav" ); + PRECACHE_SOUND( "military/mil_die3.wav" ); + + PRECACHE_SOUND( "military/mil_pain1.wav" ); + PRECACHE_SOUND( "military/mil_pain2.wav" ); + PRECACHE_SOUND( "military/mil_pain3.wav" ); + PRECACHE_SOUND( "military/mil_pain4.wav" ); + PRECACHE_SOUND( "military/mil_pain5.wav" ); + + PRECACHE_SOUND( "military/mil_reload.wav" ); + + PRECACHE_SOUND("zombie/claw_miss2.wav");// because we use the basemonster SWIPE animation event + + // get voice pitch + if (RANDOM_LONG(0,1)) + m_voicePitch = 109 + RANDOM_LONG(0,7); + else + m_voicePitch = 100; + + m_iBrassShell = PRECACHE_MODEL ("models/ak74_shell.mdl");// brass shell + + TalkInit(); + CTalkMonster::Precache(); +} + + +// talk init +void CMilitary :: TalkInit() +{ + CTalkMonster::TalkInit(); + + // military human speech group names (group names are in sentences.txt) + + if (!m_iszSpeakAs) + { + m_szGrp[TLK_ANSWER] = "VV_ANSWER"; + m_szGrp[TLK_QUESTION] = "VV_QUESTION"; + m_szGrp[TLK_IDLE] = "VV_IDLE"; + m_szGrp[TLK_STARE] = "VV_STARE"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) //LRC + m_szGrp[TLK_USE] = "VV_PFOLLOW"; + else + m_szGrp[TLK_USE] = "VV_OK"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) + m_szGrp[TLK_UNUSE] = "VV_PWAIT"; + else + m_szGrp[TLK_UNUSE] = "VV_WAIT"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) + m_szGrp[TLK_DECLINE] = "VV_POK"; + else + m_szGrp[TLK_DECLINE] = "VV_NOTOK"; + m_szGrp[TLK_STOP] = "VV_STOP"; + + m_szGrp[TLK_NOSHOOT] = "VV_SCARED"; + m_szGrp[TLK_HELLO] = "VV_HELLO"; + + m_szGrp[TLK_PLHURT1] = "!VV_CUREA"; + m_szGrp[TLK_PLHURT2] = "!VV_CUREB"; + m_szGrp[TLK_PLHURT3] = "!VV_CUREC"; + + m_szGrp[TLK_PHELLO] = NULL; //"BA_PHELLO"; // UNDONE + m_szGrp[TLK_PIDLE] = NULL; //"BA_PIDLE"; // UNDONE + m_szGrp[TLK_PQUESTION] = "VV_PQUEST"; // UNDONE + + m_szGrp[TLK_SMELL] = "VV_SMELL"; + + m_szGrp[TLK_WOUND] = "VV_WOUND"; + m_szGrp[TLK_MORTAL] = "VV_MORTAL"; + } +} + + +void CMilitary::DeclineFollowing( void ) +{ + PlaySentence( m_szGrp[TLK_DECLINE], 2, VOL_NORM, ATTN_NORM ); //LRC +} + + + +//========================================================= +// start task +//========================================================= +void CMilitary :: StartTask ( Task_t *pTask ) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch ( pTask->iTask ) + { + case TASK_MIL_CHECK_FIRE: + if ( !NoFriendlyFire() ) + { + SetConditions( bits_COND_MIL_NOFIRE ); + } + TaskComplete(); + break; + + case TASK_MIL_SPEAK_SENTENCE: + SpeakSentence(); + TaskComplete(); + break; + + case TASK_WALK_PATH: + case TASK_RUN_PATH: + // grunt no longer assumes he is covered if he moves + Forget( bits_MEMORY_INCOVER ); + CTalkMonster ::StartTask( pTask ); + break; + + case TASK_RELOAD: + m_IdealActivity = ACT_RELOAD; + break; + + case TASK_MIL_FACE_TOSS_DIR: + break; + + case TASK_FACE_IDEAL: + case TASK_FACE_ENEMY: + CTalkMonster :: StartTask( pTask ); + if (pev->movetype == MOVETYPE_FLY) + { + m_IdealActivity = ACT_GLIDE; + } + break; + + default: + CTalkMonster :: StartTask( pTask ); + break; + } +} + +//========================================================= +// RunTask +//========================================================= +void CMilitary :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_MIL_FACE_TOSS_DIR: + { + // project a point along the toss vector and turn to face that point. + SetIdealYawToTargetAndUpdate( pev->origin + m_vecTossVelocity * 64, AI_KEEP_YAW_SPEED ); + + if ( FacingIdeal() ) + { + m_iTaskStatus = TASKSTATUS_COMPLETE; + } + break; + } + default: + { + CTalkMonster :: RunTask( pTask ); + break; + } + } +} + +//========================================================= +// PainSound +//========================================================= +void CMilitary :: PainSound ( void ) +{ + if ( gpGlobals->time > m_flNextPainTime ) + { + switch ( RANDOM_LONG(0,6) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "military/mil_pain3.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "military/mil_pain4.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "military/mil_pain5.wav", 1, ATTN_NORM ); + break; + case 3: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "military/mil_pain1.wav", 1, ATTN_NORM ); + break; + case 4: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "military/mil_pain2.wav", 1, ATTN_NORM ); + break; + } + + m_flNextPainTime = gpGlobals->time + 1; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CMilitary :: DeathSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "military/mil_die1.wav", 1, ATTN_IDLE ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "military/mil_die2.wav", 1, ATTN_IDLE ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "military/mil_die3.wav", 1, ATTN_IDLE ); + break; + } +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +/* +extern Schedule_t slMilFail[]; +extern Schedule_t slMilCombatFail[]; +extern Schedule_t slMilVictoryDance[]; +extern Schedule_t slMilEstablishLineOfFire[]; +extern Schedule_t slMilFoundEnemy[]; +extern Schedule_t slMilCombatFace[]; +extern Schedule_t slMilSignalSuppress[]; +extern Schedule_t slMilSuppress[]; +extern Schedule_t slMilWaitInCover[]; +extern Schedule_t slMilTakeCover[]; +extern Schedule_t slMilGrenadeCover[]; +extern Schedule_t slMilTossGrenadeCover[]; +extern Schedule_t slMilTakeCoverFromBestSound[]; +extern Schedule_t slMilHideReload[]; +extern Schedule_t slMilSweep[]; +extern Schedule_t slMilRangeAttack1A[]; +extern Schedule_t slMilRangeAttack1B[]; +extern Schedule_t slMilRangeAttack2[]; +extern Schedule_t slMilRepel[]; +extern Schedule_t slMilRepelAttack[]; +extern Schedule_t slMilRepelLand[];*/ + +// ============================= base grunt schedules ================ + +Task_t tlMilFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slMilFail[] = +{ + { + tlMilFail, + ARRAYSIZE ( tlMilFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2, + 0, + "Grunt Fail" + }, +}; + +//========================================================= +// Grunt Combat Fail +//========================================================= +Task_t tlMilCombatFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slMilCombatFail[] = +{ + { + tlMilCombatFail, + ARRAYSIZE ( tlMilCombatFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2, + 0, + "Grunt Combat Fail" + }, +}; +//========================================================= +//Move back and fire +//========================================================= +Task_t tlMilWalkBackFire[] = +{ + {TASK_FACE_ENEMY, 0 }, + {TASK_SET_ACTIVITY, (float)ACT_WALKBACK_FIRE}, +}; + +Schedule_t slMilWalkBackFire[]= +{ + { + tlMilWalkBackFire, + ARRAYSIZE ( tlMilWalkBackFire ), + bits_COND_ENEMY_DEAD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2, + 0, + "Walk backward and fire" + }, +}; + +//========================================================= +// MaSTeR: toggle flashlight +//========================================================= +Task_t tlMilToggleFlashlight[] = +{ + {TASK_STOP_MOVING, 0 }, + {TASK_PLAY_SEQUENCE, (float)ACT_FLASHLIGHT}, + +}; +Schedule_t slMilToggleFlashlight[] = +{ + { + tlMilToggleFlashlight, + ARRAYSIZE ( tlMilToggleFlashlight ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "Toggle Flashlight" + }, +}; + +//========================================================= +// MaSTeR: walk and fire +//========================================================= +Task_t tlInfFiringWalk [] = +{ + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_FIRINGWALK }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slInfFiringWalk[] = +{ + { + tlInfFiringWalk, + ARRAYSIZE ( tlInfFiringWalk ), + bits_COND_ENEMY_DEAD | + bits_COND_CAN_MELEE_ATTACK1 | + bits_SOUND_DANGER, + 0, + "Infected Soldier Walk and Fire" + }, +}; +//========================================================= +// Victory dance! +//========================================================= +Task_t tlMilVictoryDance[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_WAIT, (float)1.5 }, + { TASK_GET_PATH_TO_ENEMY_CORPSE, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, +}; + +Schedule_t slMilVictoryDance[] = +{ + { + tlMilVictoryDance, + ARRAYSIZE ( tlMilVictoryDance ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "GruntVictoryDance" + }, +}; + +//========================================================= +// Establish line of fire - move to a position that allows +// the grunt to attack. +//========================================================= +Task_t tlMilEstablishLineOfFire[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_MIL_ELOF_FAIL }, + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_MIL_SPEAK_SENTENCE,(float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slMilEstablishLineOfFire[] = +{ + { + tlMilEstablishLineOfFire, + ARRAYSIZE ( tlMilEstablishLineOfFire ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "GruntEstablishLineOfFire" + }, +}; + +//========================================================= +// GruntFoundEnemy - grunt established sight with an enemy +// that was hiding from the squad. +//========================================================= +Task_t tlMilFoundEnemy[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY,(float)ACT_SIGNAL1 }, +}; + +Schedule_t slMilFoundEnemy[] = +{ + { + tlMilFoundEnemy, + ARRAYSIZE ( tlMilFoundEnemy ), + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "GruntFoundEnemy" + }, +}; + +//========================================================= +// GruntCombatFace Schedule +//========================================================= +Task_t tlMilCombatFace1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_WAIT, (float)1.5 }, + { TASK_SET_SCHEDULE, (float)SCHED_MIL_SWEEP }, +}; + +Schedule_t slMilCombatFace[] = +{ + { + tlMilCombatFace1, + ARRAYSIZE ( tlMilCombatFace1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2, + 0, + "Combat Face" + }, +}; + +//========================================================= +// Suppressing fire - don't stop shooting until the clip is +// empty or grunt gets hurt. +//========================================================= +Task_t tlMilSignalSuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_SIGNAL2 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MIL_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MIL_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MIL_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MIL_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MIL_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slMilSignalSuppress[] = +{ + { + tlMilSignalSuppress, + ARRAYSIZE ( tlMilSignalSuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_MIL_NOFIRE | + bits_COND_NO_AMMO_LOADED, + + bits_SOUND_DANGER, + "SignalSuppress" + }, +}; + +Task_t tlMilSuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MIL_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MIL_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MIL_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MIL_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MIL_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slMilSuppress[] = +{ + { + tlMilSuppress, + ARRAYSIZE ( tlMilSuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_MIL_NOFIRE | + bits_COND_NO_AMMO_LOADED, + + bits_SOUND_DANGER, + "Suppress" + }, +}; + + +//========================================================= +// grunt wait in cover - we don't allow danger or the ability +// to attack to break a grunt's run to cover schedule, but +// when a grunt is in cover, we do want them to attack if they can. +//========================================================= +Task_t tlMilWaitInCover[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)1 }, +}; + +Schedule_t slMilWaitInCover[] = +{ + { + tlMilWaitInCover, + ARRAYSIZE ( tlMilWaitInCover ), + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2, + + bits_SOUND_DANGER, + "GruntWaitInCover" + }, +}; + +//========================================================= +// run to cover. +// !!!BUGBUG - set a decent fail schedule here. +//========================================================= +Task_t tlMilTakeCover1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_MIL_TAKECOVER_FAILED }, + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_MIL_SPEAK_SENTENCE, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_SET_SCHEDULE, (float)SCHED_MIL_WAIT_FACE_ENEMY }, +}; + +Schedule_t slMilTakeCover[] = +{ + { + tlMilTakeCover1, + ARRAYSIZE ( tlMilTakeCover1 ), + 0, + 0, + "TakeCover" + }, +}; + +//========================================================= +// drop grenade then run to cover. +//========================================================= +Task_t tlMilGrenadeCover1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)99 }, + { TASK_FIND_FAR_NODE_COVER_FROM_ENEMY, (float)384 }, + { TASK_PLAY_SEQUENCE, (float)ACT_SPECIAL_ATTACK1 }, + { TASK_CLEAR_MOVE_WAIT, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_MIL_WAIT_FACE_ENEMY }, +}; + +Schedule_t slMilGrenadeCover[] = +{ + { + tlMilGrenadeCover1, + ARRAYSIZE ( tlMilGrenadeCover1 ), + 0, + 0, + "GrenadeCover" + }, +}; + + +//========================================================= +// drop grenade then run to cover. +//========================================================= +Task_t tlMilTossGrenadeCover1[] = +{ + { TASK_FACE_ENEMY, (float)0 }, + { TASK_RANGE_ATTACK2, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_TAKE_COVER_FROM_ENEMY }, +}; + +Schedule_t slMilTossGrenadeCover[] = +{ + { + tlMilTossGrenadeCover1, + ARRAYSIZE ( tlMilTossGrenadeCover1 ), + 0, + 0, + "TossGrenadeCover" + }, +}; + +//========================================================= +// hide from the loudest sound source (to run from grenade) +//========================================================= +Task_t tlMilTakeCoverFromBestSound[] = +{ +// Wargon: Òåïåðü ñîþçíèêè íå èùóò óêðûòèÿ îò âðàãîâ. (1.1) +// { TASK_SET_FAIL_SCHEDULE, (float)SCHED_COWER },// duck and cover if cannot move from explosion + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_BEST_SOUND, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, +// Wargon: Òåïåðü ñîþçíèêè íå èùóò óêðûòèÿ îò âðàãîâ. (1.1) +// { TASK_TURN_LEFT, (float)179 }, +}; + +Schedule_t slMilTakeCoverFromBestSound[] = +{ + { + tlMilTakeCoverFromBestSound, + ARRAYSIZE ( tlMilTakeCoverFromBestSound ), + 0, + 0, + "GruntTakeCoverFromBestSound" + }, +}; + +//========================================================= +// Grunt reload schedule +//========================================================= +Task_t tlMilHideReload[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RELOAD }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_RELOAD }, +}; + +Schedule_t slMilHideReload[] = +{ + { + tlMilHideReload, + ARRAYSIZE ( tlMilHideReload ), + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "GruntHideReload" + } +}; + +//========================================================= +// Do a turning sweep of the area +//========================================================= +Task_t tlMilSweep[] = +{ + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slMilSweep[] = +{ + { + tlMilSweep, + ARRAYSIZE ( tlMilSweep ), + + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_HEAR_SOUND, + + bits_SOUND_WORLD |// sound flags + bits_SOUND_DANGER | + bits_SOUND_PLAYER, + + "Grunt Sweep" + }, +}; + +//========================================================= +// primary range attack. Overriden because base class stops attacking when the enemy is occluded. +// grunt's grenade toss requires the enemy be occluded. +//========================================================= +Task_t tlMilRangeAttack1A[] = +{ + { TASK_STOP_MOVING, (float)0 }, +// { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCH }, buz + { TASK_FACE_ENEMY, (float)0 }, // buz + { TASK_MIL_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MIL_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MIL_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MIL_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slMilRangeAttack1A[] = +{ + { + tlMilRangeAttack1A, + ARRAYSIZE ( tlMilRangeAttack1A ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_HEAR_SOUND | + bits_COND_MIL_NOFIRE | + bits_COND_NO_AMMO_LOADED, + + bits_SOUND_DANGER, + "Range Attack1A" + }, +}; + + +//========================================================= +// primary range attack. Overriden because base class stops attacking when the enemy is occluded. +// grunt's grenade toss requires the enemy be occluded. +//========================================================= +Task_t tlMilRangeAttack1B[] = +{ + { TASK_STOP_MOVING, (float)0 }, +// { TASK_PLAY_SEQUENCE_FACE_ENEMY,(float)ACT_IDLE_ANGRY }, buz + { TASK_FACE_ENEMY, (float)0 }, // buz + { TASK_MIL_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MIL_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MIL_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MIL_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slMilRangeAttack1B[] = +{ + { + tlMilRangeAttack1B, + ARRAYSIZE ( tlMilRangeAttack1B ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_LIGHT_DAMAGE | // buz: interruptable by light damage + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED | + bits_COND_MIL_NOFIRE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Range Attack1B" + }, +}; + +//========================================================= +// secondary range attack. Overriden because base class stops attacking when the enemy is occluded. +// grunt's grenade toss requires the enemy be occluded. +//========================================================= +Task_t tlMilRangeAttack2[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_MIL_FACE_TOSS_DIR, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_RANGE_ATTACK2 }, + { TASK_SET_SCHEDULE, (float)SCHED_MIL_WAIT_FACE_ENEMY },// don't run immediately after throwing grenade. +}; + +Schedule_t slMilRangeAttack2[] = +{ + { + tlMilRangeAttack2, + ARRAYSIZE ( tlMilRangeAttack2 ), + 0, + 0, + "RangeAttack2" + }, +}; + + +//========================================================= +// repel +//========================================================= +Task_t tlMilRepel[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_GLIDE }, +}; + +Schedule_t slMilRepel[] = +{ + { + tlMilRepel, + ARRAYSIZE ( tlMilRepel ), + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER, + "Repel" + }, +}; + + +//========================================================= +// repel +//========================================================= +Task_t tlMilRepelAttack[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_FLY }, +}; + +Schedule_t slMilRepelAttack[] = +{ + { + tlMilRepelAttack, + ARRAYSIZE ( tlMilRepelAttack ), + bits_COND_ENEMY_OCCLUDED, + 0, + "Repel Attack" + }, +}; + +//========================================================= +// repel land +//========================================================= +Task_t tlMilRepelLand[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_LAND }, + { TASK_GET_PATH_TO_LASTPOSITION,(float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_CLEAR_LASTPOSITION, (float)0 }, +}; + +Schedule_t slMilRepelLand[] = +{ + { + tlMilRepelLand, + ARRAYSIZE ( tlMilRepelLand ), + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER, + "Repel Land" + }, +}; + + + +// ======================= end base grunt schedules ================= + + +Task_t tlMilFollow[] = +{ + { TASK_MOVE_TO_TARGET_RANGE,(float)128 }, // Move within 256 of target ent (client) + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE }, +}; + +Schedule_t slMilFollow[] = +{ + { + tlMilFollow, + ARRAYSIZE ( tlMilFollow ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_PROVOKED, + bits_SOUND_DANGER, + "Follow" + }, +}; + +Task_t tlMilFaceTarget[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_TARGET, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_CHASE }, +}; + +Schedule_t slMilFaceTarget[] = +{ + { + tlMilFaceTarget, + ARRAYSIZE ( tlMilFaceTarget ), + bits_COND_CLIENT_PUSH | + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_PROVOKED, + bits_SOUND_DANGER, + "FaceTarget" + }, +}; + +//========================================================= +// buz: duck and wait couple seconds behind the barrel, crate, etc.. +//========================================================= +Task_t tlMilDuckAndCoverWait[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_MIL_SPEAK_SENTENCE, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_TWITCH }, + { TASK_WAIT, (float)3 }, // randomize a bit? +}; + +Schedule_t slMilDuckAndCoverWait[] = +{ + { + tlMilDuckAndCoverWait, + ARRAYSIZE ( tlMilDuckAndCoverWait ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + // bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_CROUCH_NOT_SAFE | // buz: terminate, if crouching is not safe more + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "DuckAndCover!" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CMilitary ) +{ + slMilFail, + slMilCombatFail, + slMilVictoryDance, + slMilEstablishLineOfFire, + slMilFoundEnemy, + slMilCombatFace, + slMilSignalSuppress, + slMilSuppress, + slMilWaitInCover, + slMilTakeCover, + slMilGrenadeCover, + slMilTossGrenadeCover, + slMilTakeCoverFromBestSound, + slMilHideReload, + slMilSweep, + slMilRangeAttack1A, + slMilRangeAttack1B, + slMilRangeAttack2, + slMilRepel, + slMilRepelAttack, + slMilRepelLand, + slMilFollow, + slMilFaceTarget, + slMilDuckAndCoverWait, + slMilToggleFlashlight, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CMilitary, CTalkMonster ); + +//========================================================= +// SetActivity +//========================================================= +void CMilitary :: SetActivity ( Activity NewActivity ) +{ + int iSequence = ACTIVITY_NOT_AVAILABLE; + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + switch ( NewActivity) + { + case ACT_WALKBACK_FIRE: + iSequence = LookupSequence("walkback"); + break; + case ACT_FIRINGWALK: + iSequence = LookupSequence("firingwalk"); + break; + case ACT_DIESIMPLE: + iSequence = LookupSequence("die-simple"); + break; + case ACT_RANGE_ATTACK1: + // grunt is either shooting standing or shooting crouched + if ( m_fStanding ) + { + // get aimable sequence +// ALERT(at_console, "MIL STANDING\n"); + iSequence = LookupSequence( "standing_mp5" ); + } + else + { +// ALERT(at_console, "MIL CROUCHING\n"); + // get crouching shoot + iSequence = LookupSequence( "crouching_mp5" ); + } + + break; + case ACT_RANGE_ATTACK2: + // get toss anim + iSequence = LookupSequence( "throwgrenade" ); + + break; + case ACT_RUN: + if ( pev->health <= MIL_LIMP_HEALTH ) + { + // limp! + iSequence = LookupActivity ( ACT_RUN_HURT ); + } + else + { + // buz: get combat movement animation in combat state + // if (m_MonsterState == MONSTERSTATE_COMBAT || m_MonsterState == MONSTERSTATE_ALERT) + if (m_iUseAlertAnims) + { + iSequence = LookupSequence("combat_run_primary"); + if (iSequence != -1) + break; + } + + iSequence = LookupActivity ( NewActivity ); + } + break; + case ACT_WALK: + if ( pev->health <= MIL_LIMP_HEALTH ) + { + // limp! + iSequence = LookupActivity ( ACT_WALK_HURT ); + } + else + { + // buz: get combat movement animation in combat state + // if (m_MonsterState == MONSTERSTATE_COMBAT || m_MonsterState == MONSTERSTATE_ALERT) + if (m_iUseAlertAnims) + { + iSequence = LookupSequence("combat_walk_primary"); + if (iSequence != -1) + break; + } + + iSequence = LookupActivity ( NewActivity ); + } + break; + case ACT_IDLE: + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + { + NewActivity = ACT_IDLE_ANGRY; + } + iSequence = LookupActivity ( NewActivity ); + break; + default: + iSequence = LookupActivity ( NewActivity ); + break; + } + + m_Activity = NewActivity; // Go ahead and set this so it doesn't keep trying when the anim is not present + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + { + if ( pev->sequence != iSequence || !m_fSequenceLoops ) + { + pev->frame = 0; + } + + pev->sequence = iSequence; // Set to the reset anim (if it's there) + ResetSequenceInfo( ); + RecalculateYawSpeed(); + } + else + { + // Not available try to get default anim + ALERT ( at_debug, "%s has no sequence for act:%s\n", STRING(pev->classname), GetNameForActivity( NewActivity )); + pev->sequence = 0; // Set to the reset anim (if it's there) + } +} + +//========================================================= +// Get Schedule! +//========================================================= +Schedule_t *CMilitary :: GetSchedule( void ) +{ + + // clear old sentence + m_iSentence = MIL_SENT_NONE; + + // flying? If PRONE, barnacle has me. IF not, it's assumed I am rapelling. + if ( pev->movetype == MOVETYPE_FLY && m_MonsterState != MONSTERSTATE_PRONE ) + { + if (pev->flags & FL_ONGROUND) + { + // just landed + pev->movetype = MOVETYPE_STEP; + return GetScheduleOfType ( SCHED_MIL_REPEL_LAND ); + } + else + { + // repel down a rope, + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + return GetScheduleOfType ( SCHED_MIL_REPEL_ATTACK ); + else + return GetScheduleOfType ( SCHED_MIL_REPEL ); + } + } + + // grunts place HIGH priority on running away from danger sounds. + if ( HasConditions(bits_COND_HEAR_SOUND) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound) + { + if (pSound->m_iType & bits_SOUND_DANGER) + { + // dangerous sound nearby! + + //!!!KELLY - currently, this is the grunt's signal that a grenade has landed nearby, + // and the grunt should find cover from the blast + // good place for "SHIT!" or some other colorful verbal indicator of dismay. + // It's not safe to play a verbal order here "Scatter", etc cause + // this may only affect a single individual in a squad. + + // if (FOkToSpeak()) + // { + SENTENCEG_PlayRndSz( ENT(pev), "VV_GREN", MIL_VOL, MIL_ATTN, 0, m_voicePitch); + JustSpoke(); + // } + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + /* + if (!HasConditions( bits_COND_SEE_ENEMY ) && ( pSound->m_iType & (bits_SOUND_PLAYER | bits_SOUND_COMBAT) )) + { + SetIdealYawToTargetAndUpdate( pSound->m_vecOrigin ); + } + */ + } + } + switch ( m_MonsterState ) + { + case MONSTERSTATE_IDLE: + case MONSTERSTATE_ALERT: + if ( pFlashlight && pHeadController && FBitSet( pev->spawnflags, SF_TOGGLE_FLASHLIGHT )) + { + if (( pHeadController->ShouldUseLights() && pFlashlight->GetState() != STATE_ON ) || ( !pHeadController->ShouldUseLights() && pFlashlight->GetState() != STATE_OFF )) + { + return GetScheduleOfType(SCHED_MIL_FLASHLIGHT); + } + } + // buz: ïåðåçàðÿäèòüñÿ, åñëè âðàãà íåò è ìàãàçèí ïîëóïóñò + if (m_cAmmoLoaded < m_cClipSize / 2) + { + return GetScheduleOfType ( SCHED_RELOAD ); + } + + if (!FStringNull(m_hRushEntity) && (gpGlobals->time > m_flRushNextTime) && (m_flRushNextTime != -1)) + { + CBaseEntity *pRushEntity = UTIL_FindEntityByTargetname( NULL, STRING( m_hRushEntity ) ); + if (pRushEntity) + { + CStartRush* pRush = (CStartRush*)pRushEntity; + m_hTargetEnt = pRush->GetDestinationEntity(); + + return GetScheduleOfType( SCHED_RUSH_TARGET ); + } + else + // rush entity not found on this map. + // try again after next changelevel + m_flRushNextTime = -1; + } + + // Behavior for following the player + if ( IsFollowing() ) + { + if ( !m_hTargetEnt->IsAlive() ) + { + // UNDONE: Comment about the recently dead player here? + StopFollowing( FALSE ); + break; + } + + // If I'm already close enough to my target + if ( TargetDistance() <= 128 ) + { + if ( HasConditions( bits_COND_CLIENT_PUSH ) ) // Player wants me to move + return GetScheduleOfType( SCHED_MOVE_AWAY_FOLLOW ); + } + + return GetScheduleOfType( SCHED_TARGET_FACE ); // Just face and follow. + } + + if ( HasConditions( bits_COND_CLIENT_PUSH ) ) // Player wants me to move + return GetScheduleOfType( SCHED_MOVE_AWAY ); + + // try to say something about smells + TrySmellTalk(); + break; + + case MONSTERSTATE_COMBAT: + { +// dead enemy + // Wargon: Êðîìå ñëó÷àåâ, êîãäà âðàã âçÿò èç äàííûõ èãðîêà. (1.1) + if ( HasConditions( bits_COND_ENEMY_DEAD ) && !m_fEnemyFromPlayer ) + { + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_KILL"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "VV_KILL", 4, VOL_NORM, ATTN_NORM ); + } + + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + +// new enemy + if ( HasConditions(bits_COND_NEW_ENEMY) ) + { + // if (FOkToSpeak())// && RANDOM_LONG(0,1)) + // { + if (m_hEnemy != NULL) // buz: rewritten + { + // american spy + if (m_hEnemy->Classify() == CLASS_HUMAN_MILITARY) + SENTENCEG_PlayRndSz( ENT(pev), "VV_AMER", MIL_VOL, MIL_ATTN, 0, m_voicePitch); + + // monster + else if ((m_hEnemy->Classify() == CLASS_ALIEN_MILITARY) || + (m_hEnemy->Classify() == CLASS_ALIEN_MONSTER) || + (m_hEnemy->Classify() == CLASS_ALIEN_PREY) || + (m_hEnemy->Classify() == CLASS_ALIEN_PREDATOR)) + SENTENCEG_PlayRndSz( ENT(pev), "VV_MONST", MIL_VOL, MIL_ATTN, 0, m_voicePitch); + } + JustSpoke(); + // } + + if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_MIL_SUPPRESS ); + } + else + { + return GetScheduleOfType ( SCHED_MIL_ESTABLISH_LINE_OF_FIRE ); + } + } +// no ammo + else if ( HasConditions ( bits_COND_NO_AMMO_LOADED ) ) + { + if ((m_afCapability & bits_CAP_CROUCH_COVER) && !HasConditions(bits_COND_CROUCH_NOT_SAFE) ) // buz: reload here, if safe + return GetScheduleOfType ( SCHED_RELOAD ); + else + return GetScheduleOfType ( SCHED_MIL_COVER_AND_RELOAD ); + } + +// damaged just a little + else if ( HasConditions( bits_COND_LIGHT_DAMAGE ) ) + { + /* // if hurt: + // 90% chance of taking cover + // 10% chance of flinch. + int iPercent = RANDOM_LONG(0,99); + + if ( iPercent <= 90 && m_hEnemy != NULL ) + { + // only try to take cover if we actually have an enemy! + + //!!!KELLY - this grunt was hit and is going to run to cover. + if (FOkToSpeak()) // && RANDOM_LONG(0,1)) + { + //SENTENCEG_PlayRndSz( ENT(pev), "HG_COVER", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + m_iSentence = MIL_SENT_COVER; + //JustSpoke(); + } + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + else + { + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + }*/ + + int iPercent; + + // buz: 90% to duck and cover, if can + if ((m_afCapability & bits_CAP_CROUCH_COVER) && !HasConditions(bits_COND_CROUCH_NOT_SAFE) && m_hEnemy != NULL ) + { + iPercent = RANDOM_LONG(0,99); + if (iPercent <= 90) + return GetScheduleOfType( SCHED_MIL_DUCK_COVER_WAIT ); // wait some time in cover + } + + // buz: now 50% to try normal way of taking cover + iPercent = RANDOM_LONG(0,99); + if ( iPercent <= 50 && m_hEnemy != NULL ) + { + //!!!KELLY - this grunt was hit and is going to run to cover. + // if (FOkToSpeak()) // && RANDOM_LONG(0,1)) + // { + //SENTENCEG_PlayRndSz( ENT(pev), "HG_COVER", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + m_iSentence = MIL_SENT_COVER; + //JustSpoke(); + // } + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + else + { + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + } +// can kick + else if ( HasConditions ( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + if( pHeadController && pHeadController->GetBackTrace() < 32.0f ) + return GetScheduleOfType ( SCHED_MELEE_ATTACK1 ); + else + return GetScheduleOfType ( SCHED_MIL_WALKBACK_FIRE ); + } +// can grenade launch + + else if (( pev->weapons == MIL_GRENADES ) && HasConditions ( bits_COND_CAN_RANGE_ATTACK2 )) + { + // shoot a grenade if you can + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } +// can shoot + else if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } +// can't see enemy + else if ( HasConditions( bits_COND_ENEMY_OCCLUDED ) ) + { + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) ) + { + //!!!KELLY - this grunt is about to throw or fire a grenade at the player. Great place for "fire in the hole" "frag out" etc + // if (FOkToSpeak()) + // { + SENTENCEG_PlayRndSz( ENT(pev), "VV_THROW", MIL_VOL, MIL_ATTN, 0, m_voicePitch); + JustSpoke(); + // } + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + + //!!!KELLY - grunt cannot see the enemy and has just decided to + // charge the enemy's position. + // if (FOkToSpeak())// && RANDOM_LONG(0,1)) + // { + //SENTENCEG_PlayRndSz( ENT(pev), "HG_CHARGE", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + m_iSentence = MIL_SENT_CHARGE; + //JustSpoke(); + // } + + return GetScheduleOfType( SCHED_MIL_ESTABLISH_LINE_OF_FIRE ); + } + + if ( HasConditions( bits_COND_SEE_ENEMY ) && !HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_MIL_ESTABLISH_LINE_OF_FIRE ); + } + } + } + + // no special cases here, call the base class + return CTalkMonster :: GetSchedule(); +} + + +extern Schedule_t slIdleStand[]; +//========================================================= +//========================================================= +Schedule_t* CMilitary :: GetScheduleOfType ( int Type ) +{ + Schedule_t *psched; + + switch ( Type ) + { + case SCHED_TARGET_CHASE: + return slMilFollow; + + case SCHED_TARGET_FACE: + psched = CTalkMonster::GetScheduleOfType(Type); + + if (psched == slIdleStand) + return slMilFaceTarget; // override this for different target face behavior + else + return psched; + + case SCHED_MIL_FLASHLIGHT: + { + return &slMilToggleFlashlight[ 0 ]; + } + break; + + case SCHED_TAKE_COVER_FROM_ENEMY: + { + // if ( RANDOM_LONG(0,1) ) - buz - this is bad trick for player allied monsters.. + // { + // return &slMilGrenadeCover[ 0 ]; + // } + // else + // { + // Wargon: Òåïåðü ñîþçíèêè íå èùóò óêðûòèÿ îò âðàãîâ. (1.1) + // return &slMilTakeCover[ 0 ]; + // } + } + case SCHED_TAKE_COVER_FROM_BEST_SOUND: + { + return &slMilTakeCoverFromBestSound[ 0 ]; + } + case SCHED_MIL_DUCK_COVER_WAIT: // buz + { + return &slMilDuckAndCoverWait[ 0 ]; + } + case SCHED_MIL_TAKECOVER_FAILED: + { + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + + return GetScheduleOfType ( SCHED_FAIL ); + } + break; + case SCHED_MIL_ELOF_FAIL: + { + // human grunt is unable to move to a position that allows him to attack the enemy. + return GetScheduleOfType ( SCHED_TAKE_COVER_FROM_ENEMY ); + } + break; + case SCHED_MIL_ESTABLISH_LINE_OF_FIRE: + { + return &slMilEstablishLineOfFire[ 0 ]; + } + break; + case SCHED_RANGE_ATTACK1: + { + // randomly stand or crouch + // if (RANDOM_LONG(0,9) == 0) + // m_fStanding = RANDOM_LONG(0,1); + + /* m_fStanding = RANDOM_LONG(0,1); // buz + + if (m_fStanding) + return &slMilRangeAttack1B[ 0 ]; + else + return &slMilRangeAttack1A[ 0 ];*/ + + + // buz: use CheckRangedAttack1's recommendations + switch (m_iLastFireCheckResult) + { + case 0: // fire any + default: + if (RANDOM_LONG(0,5) == 0) + m_fStanding = RANDOM_LONG(0,1); + break; + case 1: // only crouched + // ALERT(at_console, "MIL SET CROUCHING\n"); + m_fStanding = FALSE; + break; + case 2: // only standing + // ALERT(at_console, "MIL SET STANDING\n"); + m_fStanding = TRUE; + break; + } + + // buz: 1B is interruptable - use it if grunt can fast take cover when hit + if ((m_afCapability & bits_CAP_CROUCH_COVER) && !HasConditions(bits_COND_CROUCH_NOT_SAFE) ) + return &slMilRangeAttack1B[ 0 ]; + else + return &slMilRangeAttack1A[ 0 ]; + } + case SCHED_RANGE_ATTACK2: + { + return &slMilRangeAttack2[ 0 ]; + } + case SCHED_COMBAT_FACE: + { + return &slMilCombatFace[ 0 ]; + } + case SCHED_MIL_WAIT_FACE_ENEMY: + { + return &slMilWaitInCover[ 0 ]; + } + case SCHED_MIL_WALKBACK_FIRE: + { + return &slMilWalkBackFire[ 0 ]; + } + case SCHED_MIL_SWEEP: + { + return &slMilSweep[ 0 ]; + } + case SCHED_MIL_COVER_AND_RELOAD: + { + return &slMilHideReload[ 0 ]; + } + case SCHED_MIL_FOUND_ENEMY: + { + return &slMilFoundEnemy[ 0 ]; + } + case SCHED_VICTORY_DANCE: + { + // buz + if (RANDOM_LONG(0,4) == 0) + return &slMilVictoryDance[ 0 ]; + else + return &slMilFail[ 0 ]; + } + case SCHED_MIL_SUPPRESS: + { + // buz: use CheckRangedAttack1's recommendations + switch (m_iLastFireCheckResult) + { + case 0: // fire any + default: + if (RANDOM_LONG(0,5) == 0) + m_fStanding = RANDOM_LONG(0,1); + break; + case 1: // only crouched +// ALERT(at_console, "MIL SET CROUCHING\n"); + m_fStanding = FALSE; + break; + case 2: // only standing +// ALERT(at_console, "MIL SET STANDING\n"); + m_fStanding = TRUE; + break; + } + + return &slMilSuppress[ 0 ]; + } + case SCHED_FAIL: + { + if ( m_hEnemy != NULL ) + { + // grunt has an enemy, so pick a different default fail schedule most likely to help recover. + return &slMilCombatFail[ 0 ]; + } + + return &slMilFail[ 0 ]; + } + case SCHED_MIL_REPEL: + { + if (pev->velocity.z > -128) + pev->velocity.z -= 32; + return &slMilRepel[ 0 ]; + } + case SCHED_MIL_REPEL_ATTACK: + { + if (pev->velocity.z > -128) + pev->velocity.z -= 32; + return &slMilRepelAttack[ 0 ]; + } + case SCHED_MIL_REPEL_LAND: + { + return &slMilRepelLand[ 0 ]; + } + default: + { + return CTalkMonster :: GetScheduleOfType ( Type ); + } + } +} + + +//========================================================= +// CHGruntRepel - when triggered, spawns a monster_human_grunt +// repelling down a line. +//========================================================= + +class CMilitaryRepel : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void EXPORT RepelUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int m_iSpriteTexture; // Don't save, precache +}; + +LINK_ENTITY_TO_CLASS( monster_military_repel, CMilitaryRepel ); + +void CMilitaryRepel::Spawn( void ) +{ + Precache( ); + pev->solid = SOLID_NOT; + + SetUse(&CMilitaryRepel:: RepelUse ); +} + +void CMilitaryRepel::Precache( void ) +{ + UTIL_PrecacheOther( "monster_human_military" ); + m_iSpriteTexture = PRECACHE_MODEL( "sprites/rope.spr" ); +} + +void CMilitaryRepel::RepelUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + TraceResult tr; + UTIL_TraceLine( pev->origin, pev->origin + Vector( 0, 0, -4096.0), dont_ignore_monsters, ENT(pev), &tr); + /* + if ( tr.pHit && Instance( tr.pHit )->pev->solid != SOLID_BSP) + return NULL; + */ + + CBaseEntity *pEntity = Create( "monster_human_military", pev->origin, pev->angles ); + CBaseMonster *pGrunt = pEntity->MyMonsterPointer( ); + pGrunt->pev->movetype = MOVETYPE_FLY; + pGrunt->pev->velocity = Vector( 0, 0, RANDOM_FLOAT( -196, -128 ) ); + pGrunt->SetActivity( ACT_GLIDE ); + // UNDONE: position? + pGrunt->m_vecLastPosition = tr.vecEndPos; + + CBeam *pBeam = CBeam::BeamCreate( "sprites/rope.spr", 10 ); + pBeam->PointEntInit( pev->origin + Vector(0,0,112), pGrunt->entindex() ); + pBeam->SetFlags( BEAM_FSOLID ); + pBeam->SetColor( 255, 255, 255 ); + pBeam->SetThink(&CBeam:: SUB_Remove ); + pBeam->SetNextThink( -4096.0 * tr.flFraction / pGrunt->pev->velocity.z + 0.5 ); + + UTIL_Remove( this ); +} + + + +//========================================================= +// DEAD HGRUNT PROP +//========================================================= +class CDeadMilitary : public CBaseMonster +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_PLAYER_ALLY; } + + void KeyValue( KeyValueData *pkvd ); + float MaxYawSpeed( void ) { return 8.0f; } + int m_iPose;// which sequence to display -- temporary, don't need to save + static char *m_szPoses[3]; +}; + +char *CDeadMilitary::m_szPoses[] = { "deadstomach", "deadside", "deadsitting" }; + +void CDeadMilitary::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "pose")) + { + m_iPose = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + +LINK_ENTITY_TO_CLASS( monster_military_dead, CDeadMilitary ); + +//========================================================= +// ********** DeadHGrunt SPAWN ********** +//========================================================= +void CDeadMilitary :: Spawn( void ) +{ + int oldBody; + + PRECACHE_MODEL("models/soldier.mdl"); + SET_MODEL(ENT(pev), "models/soldier.mdl"); + + pev->sequence = 0; + m_bloodColor = BLOOD_COLOR_RED; + + pev->sequence = LookupSequence( m_szPoses[m_iPose] ); + + if (pev->sequence == -1) + { + ALERT ( at_debug, "Dead hgrunt with bad pose\n" ); + } + + // Corpses have less health + pev->health = 20; + + oldBody = pev->body; + pev->body = 0; + + MonsterInitDead(); +} + + + +#define SPETSNAZ_HEAD_GROUP 1 +#define SPETSNAZ_GUN_GROUP 2 +#define SPETSNAZ_HEAD_SHIELD 2 + +#define SPETSNAZ_WEAPON_AKS 0 +#define SPETSNAZ_WEAPON_ASVAL 1 +#define SPETSNAZ_WEAPON_GROZA 2 +#define SPETSNAZ_WEAPON_EMPTY 3 + +#define SF_SPETSNAZ_RADIO 8 + +/*************************************** + Spetsnaz class +***************************************/ + +class CSpetsnaz : public CMilitary +{ +public: + void Spawn( void ); + void Precache( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + BOOL CheckRangeAttack2 ( float flDot, float flDist ); + void DeathSound( void ); + void PainSound( void ); + void Shoot ( void ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + void RunAI ( void ); // only to check radio sound + void BlockedByPlayer ( CBasePlayer *pBlocker ); + void TalkAboutDeadFriend( CTalkMonster *pfriend ); + + void TalkInit(); // buz + void SpeakSentence( void ); + + void KeyValue( KeyValueData *pkvd ); + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + Schedule_t *GetSchedule( void ); + Schedule_t *GetScheduleOfType(int Type); + // èìååò øëåì + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + // äðóãèå ñåíòåíñû + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + void GibMonster( void ); + + int m_iHasHeadShield; // ignores headshots + int m_iHasGrenades; + float m_fNextRadioNoise; // 0 - no noise + static const char *pSpetsnazSentences[]; + + // Wargon: Ïðè ñìåðòè ñïåöíàçîâöà èìèòèðóåòñÿ äåéñòâèå ýíòèòè player_loadsaved. (1.1) + void RunTask( Task_t *pTask ); + void EXPORT MonsterDeadThink( void ); +}; + +const char *CSpetsnaz::pSpetsnazSentences[] = +{ + "AL_GREN", // grenade scared grunt + "AL_ALERT", // sees player + "AL_MONSTER", // sees monster + "AL_COVER", // running to cover + "AL_THROW", // about to throw grenade + "AL_CHARGE", // running out to get the enemy + "AL_TAUNT", // say rude things +}; + +enum +{ + AL_SENT_NONE = -1, + AL_SENT_GREN = 0, + AL_SENT_ALERT, + AL_SENT_MONSTER, + AL_SENT_COVER, + AL_SENT_THROW, + AL_SENT_CHARGE, + AL_SENT_TAUNT, +} AL_SENTENCE_TYPES; + +TYPEDESCRIPTION CSpetsnaz::m_SaveData[] = +{ + DEFINE_FIELD( CSpetsnaz, m_iHasHeadShield, FIELD_INTEGER), + DEFINE_FIELD( CSpetsnaz, m_iHasGrenades, FIELD_INTEGER), +}; + +LINK_ENTITY_TO_CLASS( monster_human_alpha, CSpetsnaz ); +IMPLEMENT_SAVERESTORE(CSpetsnaz,CMilitary); + + +void CSpetsnaz :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iHasGrenades")) + { + m_iHasGrenades = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CMilitary::KeyValue( pkvd ); +} + +void CSpetsnaz :: BlockedByPlayer ( CBasePlayer *pBlocker ) +{ + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_BLOCKED"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "AL_BLOCKED", 4, VOL_NORM, ATTN_NORM ); + } +} + +void CSpetsnaz :: TalkAboutDeadFriend( CTalkMonster *pfriend ) +{ + if (FClassnameIs(pfriend->pev, "monster_human_alpha") || + FClassnameIs(pfriend->pev, "monster_alpha_pistol")) + { + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_TMDOWN"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "AL_TMDOWN", 4, VOL_NORM, ATTN_NORM ); + } + } +} + +//========================================================= +// Spawn +//========================================================= +void CSpetsnaz :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/soldier_alpha.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + // Wargon: Õåëñû ñïåöíàçîâöåâ, çàäàííûå â ïàðàìåòðàõ ýíòèòè, èãíîðèðóþòñÿ. (1.1) + // if (pev->health == 0) + pev->health = gSkillData.alphaHealth; + m_flFieldOfView = VIEW_FIELD_FULL;//0.2; + m_MonsterState = MONSTERSTATE_NONE; + m_flNextGrenadeCheck = gpGlobals->time + 1; + m_flNextPainTime = gpGlobals->time; + m_iSentence = MIL_SENT_NONE; + m_afCapability = bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP; + m_HackedGunPos = Vector ( 0, 0, 55 ); + + // buz: pev->body is head number + int head = pev->body; + pev->body = 0; + SetBodygroup( SPETSNAZ_HEAD_GROUP, head ); + m_iHasHeadShield = (head == SPETSNAZ_HEAD_SHIELD) ? 1 : 0; + m_iNoGasDamage = 0; + + switch (pev->weapons) + { + case SPETSNAZ_WEAPON_AKS: + default: + SetBodygroup( SPETSNAZ_GUN_GROUP, SPETSNAZ_WEAPON_AKS ); + // ALERT(at_console, "********* AKS\n"); + m_cClipSize = 36; + break; + case SPETSNAZ_WEAPON_GROZA: + SetBodygroup( SPETSNAZ_GUN_GROUP, SPETSNAZ_WEAPON_GROZA ); + // ALERT(at_console, "********* GROZA\n"); + m_cClipSize = 36; + break; + case SPETSNAZ_WEAPON_ASVAL: + SetBodygroup( SPETSNAZ_GUN_GROUP, SPETSNAZ_WEAPON_ASVAL ); + // ALERT(at_console, "********* ASVAL\n"); + m_cClipSize = 24; + break; + } + + //MaSTeR: Flashlight and head controller + m_cAmmoLoaded = m_cClipSize; +// ALERT(at_console, "** spec start ammo %d\n", m_cAmmoLoaded); + MonsterInit(); + InitFlashlight(); + InitHeadController(); + SetUse(&CSpetsnaz :: FollowerUse ); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CSpetsnaz :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/soldier_alpha.mdl"); + + PRECACHE_SOUND( "weapons/dryfire1.wav" ); //LRC + + switch (pev->weapons) + { + case SPETSNAZ_WEAPON_AKS: + PRECACHE_SOUND ("weapons/aks_fire1.wav"); + PRECACHE_SOUND ("weapons/aks_fire2.wav"); + PRECACHE_SOUND ("weapons/aks_fire3.wav"); + m_iBrassShell = PRECACHE_MODEL ("models/aks_shell.mdl"); + break; + + case SPETSNAZ_WEAPON_GROZA: + PRECACHE_SOUND ("weapons/groza_fire1.wav"); + PRECACHE_SOUND ("weapons/groza_fire2.wav"); + PRECACHE_SOUND ("weapons/groza_fire3.wav"); + m_iBrassShell = PRECACHE_MODEL ("models/groza_shell.mdl"); + break; + + case SPETSNAZ_WEAPON_ASVAL: + PRECACHE_SOUND ("weapons/val_fire1.wav"); + PRECACHE_SOUND ("weapons/val_fire2.wav"); + PRECACHE_SOUND ("weapons/val_fire3.wav"); + m_iBrassShell = PRECACHE_MODEL ("models/val_shell.mdl"); + break; + } + + PRECACHE_SOUND( "alpha/alpha_die1.wav" ); + PRECACHE_SOUND( "alpha/alpha_die2.wav" ); + PRECACHE_SOUND( "alpha/alpha_die3.wav" ); + + PRECACHE_SOUND( "alpha/alpha_pain1.wav" ); + PRECACHE_SOUND( "alpha/alpha_pain2.wav" ); + PRECACHE_SOUND( "alpha/alpha_pain3.wav" ); + PRECACHE_SOUND( "alpha/alpha_pain4.wav" ); + PRECACHE_SOUND( "alpha/alpha_pain5.wav" ); + + PRECACHE_SOUND( "alpha/alpha_reload.wav" ); + + PRECACHE_SOUND("zombie/claw_miss2.wav");// because we use the basemonster SWIPE animation event + + // get voice pitch + if (RANDOM_LONG(0,1)) + m_voicePitch = 109 + RANDOM_LONG(0,7); + else + m_voicePitch = 100; + + if (pev->spawnflags & SF_SPETSNAZ_RADIO) + { + m_fNextRadioNoise = gpGlobals->time + RANDOM_FLOAT( 20, 60 ); + } + + TalkInit(); + CTalkMonster::Precache(); +} + + +// talk init +void CSpetsnaz :: TalkInit() +{ + CTalkMonster::TalkInit(); + + if (!m_iszSpeakAs) + { + m_szGrp[TLK_ANSWER] = "AL_ANSWER"; + m_szGrp[TLK_QUESTION] = "AL_QUESTION"; + m_szGrp[TLK_IDLE] = "AL_IDLE"; + m_szGrp[TLK_STARE] = "AL_STARE"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) //LRC + m_szGrp[TLK_USE] = "AL_PFOLLOW"; + else + m_szGrp[TLK_USE] = "AL_OK"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) + m_szGrp[TLK_UNUSE] = "AL_PWAIT"; + else + m_szGrp[TLK_UNUSE] = "AL_WAIT"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) + m_szGrp[TLK_DECLINE] = "AL_POK"; + else + m_szGrp[TLK_DECLINE] = "AL_NOTOK"; + m_szGrp[TLK_STOP] = "AL_STOP"; + + m_szGrp[TLK_NOSHOOT] = "AL_SCARED"; + m_szGrp[TLK_HELLO] = "AL_HELLO"; + + m_szGrp[TLK_PLHURT1] = "!AL_CUREA"; + m_szGrp[TLK_PLHURT2] = "!AL_CUREB"; + m_szGrp[TLK_PLHURT3] = "!AL_CUREC"; + + m_szGrp[TLK_PHELLO] = NULL; + m_szGrp[TLK_PIDLE] = NULL; + m_szGrp[TLK_PQUESTION] = "AL_PQUEST"; + + m_szGrp[TLK_SMELL] = "AL_SMELL"; + + m_szGrp[TLK_WOUND] = "AL_WOUND"; + m_szGrp[TLK_MORTAL] = "AL_MORTAL"; + } +} + + + +//========================================================= +// PainSound +//========================================================= +void CSpetsnaz :: PainSound ( void ) +{ + if ( gpGlobals->time > m_flNextPainTime ) + { + switch ( RANDOM_LONG(0,6) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "alpha/alpha_pain3.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "alpha/alpha_pain4.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "alpha/alpha_pain5.wav", 1, ATTN_NORM ); + break; + case 3: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "alpha/alpha_pain1.wav", 1, ATTN_NORM ); + break; + case 4: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "alpha/alpha_pain2.wav", 1, ATTN_NORM ); + break; + } + + m_flNextPainTime = gpGlobals->time + 1; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CSpetsnaz :: DeathSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "alpha/alpha_die1.wav", 1, ATTN_IDLE ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "alpha/alpha_die2.wav", 1, ATTN_IDLE ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "alpha/alpha_die3.wav", 1, ATTN_IDLE ); + break; + } +} + +//========================================================= +// Shoot +//========================================================= +void CSpetsnaz :: Shoot ( void ) +{ +// if (m_hEnemy == NULL && m_pCine == NULL) //LRC - scripts may fire when you have no enemy +// { +// return; +// } + + UTIL_MakeVectors ( pev->angles ); + Vector vecShootOrigin = GetGunPosition(); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); + + if (m_cAmmoLoaded > 0) + { + switch (pev->weapons) + { + case SPETSNAZ_WEAPON_AKS: + switch(RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/aks_fire1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/aks_fire2.wav", 1, ATTN_NORM ); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/aks_fire3.wav", 1, ATTN_NORM ); break; + } + break; + + case SPETSNAZ_WEAPON_GROZA: + switch(RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/groza_fire1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/groza_fire2.wav", 1, ATTN_NORM ); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/groza_fire3.wav", 1, ATTN_NORM ); break; + } + break; + + case SPETSNAZ_WEAPON_ASVAL: + switch(RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/val_fire1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/val_fire2.wav", 1, ATTN_NORM ); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/val_fire3.wav", 1, ATTN_NORM ); break; + } + break; + } + + Vector vecBrassPos, vecBrassDir; + GetAttachment(3, vecBrassPos, vecBrassDir); + Vector vecShellVelocity = gpGlobals->v_right * RANDOM_FLOAT(40,90) + gpGlobals->v_up * RANDOM_FLOAT(75,200) + gpGlobals->v_forward * RANDOM_FLOAT(-40, 40); + EjectBrass ( vecBrassPos, vecShellVelocity, pev->angles.y, m_iBrassShell, TE_BOUNCE_SHELL); + + switch (pev->weapons) + { + case SPETSNAZ_WEAPON_AKS: + FireBullets( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_7DEGREES, 2048.0f, BULLET_NORMAL, gSkillData.monDmgAK ); + break; + case SPETSNAZ_WEAPON_GROZA: + FireBullets( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_5DEGREES, 2048.0f, BULLET_NORMAL, gSkillData.monDmgGroza ); + break; + case SPETSNAZ_WEAPON_ASVAL: + FireBullets( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_5DEGREES, 2048.0f, BULLET_NORMAL, gSkillData.monDmgAsval ); + break; + default: + ALERT(at_error, "ERROR: trying to fire without a gun!\n"); + } + + pev->effects |= EF_MUZZLEFLASH; + + m_cAmmoLoaded--;// take away a bullet! + // ALERT(at_console, "spcnazz ammo has %d\n", m_cAmmoLoaded); + } + else + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/dryfire1.wav", 1, ATTN_NORM ); + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); +} + + + +//========================================================= +// TraceAttack - make sure we're not taking it in the helmet +//========================================================= +void CSpetsnaz :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ +// ALERT(at_console, "specnaz hitgr: %d\n", ptr->iHitgroup); + // check for helmet shot + if ((ptr->iHitgroup == 8) || (ptr->iHitgroup == 1)) + { + // make sure we're wearing one + if (m_iHasHeadShield && (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_BLAST | DMG_CLUB))) + { + // absorb damage + flDamage -= 40; + if (flDamage <= 0) + { + UTIL_Ricochet( ptr->vecEndPos, 1.0 ); + flDamage = 0.01; + } + } + // it's head shot anyways + ptr->iHitgroup = HITGROUP_HEAD; + } + CTalkMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + +int CSpetsnaz :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + Forget( bits_MEMORY_INCOVER ); + + // Wargon: Ñîþçíèêè íå äîëæíû âîññòàâàòü ïðîòèâ èãðîêà. È èñêëþ÷åíà âîçìîæíîñòü ãèáàíèÿ ñïåöíàçîâöåâ - ýòîãî òðåáóåò àâòîçàãðóçêà ïðè ñìåðòè. (1.1) + return CBaseMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType | DMG_NEVERGIB); + +/* // make sure friends talk about it if player hurts talkmonsters... + int ret = CTalkMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); + if ( !IsAlive() || pev->deadflag == DEAD_DYING ) + return ret; + + // LRC - if my reaction to the player has been overridden, don't do this stuff + if (m_iPlayerReact) return ret; + + if ( m_MonsterState != MONSTERSTATE_PRONE && (pevAttacker->flags & FL_CLIENT) ) + { + // This is a heurstic to determine if the player intended to harm me + // If I have an enemy, we can't establish intent (may just be crossfire) + if ( m_hEnemy == NULL ) + { + // If the player was facing directly at me, or I'm already suspicious, get mad + if ( (m_afMemory & bits_MEMORY_SUSPICIOUS) ) + { + // Alright, now I'm pissed! + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_MAD"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "AL_MAD", 4, VOL_NORM, ATTN_NORM ); + } + + Remember( bits_MEMORY_PROVOKED ); + StopFollowing( TRUE ); + } + else + { + // Hey, be careful with that + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_SHOT"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "AL_SHOT", 4, VOL_NORM, ATTN_NORM ); + } + Remember( bits_MEMORY_SUSPICIOUS ); + } + } + else if ( !(m_hEnemy->IsPlayer()) && pev->deadflag == DEAD_NO ) + { + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_SHOT"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "AL_SHOT", 4, VOL_NORM, ATTN_NORM ); + } + } + } + return ret; */ +} + + + +//========================================================= +// CheckRangeAttack2 - this checks the Grunt's grenade +// attack. +//========================================================= +BOOL CSpetsnaz :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + if (!m_iHasGrenades) + return FALSE; + + // if the grunt isn't moving, it's ok to check. + if ( m_flGroundSpeed != 0 ) + { + m_fThrowGrenade = FALSE; + return m_fThrowGrenade; + } + + // assume things haven't changed too much since last time + if (gpGlobals->time < m_flNextGrenadeCheck ) + { + return m_fThrowGrenade; + } + + if ( !FBitSet ( m_hEnemy->pev->flags, FL_ONGROUND ) && (m_hEnemy->pev->waterlevel == 0 || m_hEnemy->pev->watertype==CONTENTS_FOG) && m_vecEnemyLKP.z > pev->absmax.z ) + { + //!!!BUGBUG - we should make this check movetype and make sure it isn't FLY? Players who jump a lot are unlikely to + // be grenaded. + // don't throw grenades at anything that isn't on the ground! + m_fThrowGrenade = FALSE; + return m_fThrowGrenade; + } + + Vector vecTarget; + + // find feet + if (RANDOM_LONG(0,1)) + { + // magically know where they are + vecTarget = Vector( m_hEnemy->pev->origin.x, m_hEnemy->pev->origin.y, m_hEnemy->pev->absmin.z ); + } + else + { + // toss it to where you last saw them + vecTarget = m_vecEnemyLKP; + } + + // buz: check for allies in target area: + CBaseEntity *pTarget = NULL; + while ((pTarget = UTIL_FindEntityInSphere( pTarget, vecTarget, 256 )) != NULL) + { + if (FClassnameIs( pTarget->pev, "monster_human_alpha") || FClassnameIs( pTarget->pev, "player")) + { + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + m_fThrowGrenade = FALSE; + } + } + + if ( ( vecTarget - pev->origin ).Length2D() <= 256 ) + { + // crap, I don't want to blow myself up + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + m_fThrowGrenade = FALSE; + return m_fThrowGrenade; + } + + Vector vecToss = VecCheckToss( pev, GetGunPosition(), vecTarget, 0.5 ); + + if ( vecToss != g_vecZero ) + { + m_vecTossVelocity = vecToss; + + // throw a hand grenade + m_fThrowGrenade = TRUE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time; // 1/3 second. + } + else + { + // don't throw + m_fThrowGrenade = FALSE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + } + + return m_fThrowGrenade; +} + + + +void CSpetsnaz :: GibMonster ( void ) +{ + Vector vecGunPos; + Vector vecGunAngles; + + if (GetBodygroup(SPETSNAZ_GUN_GROUP) != SPETSNAZ_WEAPON_EMPTY && !(pev->spawnflags & SF_MONSTER_NO_WPN_DROP)) + { + GetAttachment( 0, vecGunPos, vecGunAngles ); + + CBaseEntity *pGun = NULL; + switch (pev->weapons) + { + case SPETSNAZ_WEAPON_AKS: + pGun = DropItem( "weapon_aks", vecGunPos, vecGunAngles ); + break; + case SPETSNAZ_WEAPON_GROZA: + pGun = DropItem( "weapon_groza", vecGunPos, vecGunAngles ); + break; + case SPETSNAZ_WEAPON_ASVAL: + pGun = DropItem( "weapon_val", vecGunPos, vecGunAngles ); + break; + } + + if ( pGun ) + { + pGun->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + pGun->pev->avelocity = Vector ( 0, RANDOM_FLOAT( 200, 400 ), 0 ); + } + + if (m_iHasGrenades) + { + pGun = DropItem( "weapon_handgrenade", BodyTarget( pev->origin ), vecGunAngles ); + if ( pGun ) + { + pGun->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + pGun->pev->avelocity = Vector ( 0, RANDOM_FLOAT( 200, 400 ), 0 ); + } + } + } + CBaseMonster :: GibMonster(); +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CSpetsnaz :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + Vector vecShootDir; + Vector vecShootOrigin; + + switch( pEvent->event ) + { + case MIL_AE_DROP_GUN: + { + if (pev->spawnflags & SF_MONSTER_NO_WPN_DROP) break; //LRC + + Vector vecGunPos; + Vector vecGunAngles; + GetAttachment( 0, vecGunPos, vecGunAngles ); + // switch to body group with no gun. + SetBodygroup( SPETSNAZ_GUN_GROUP, SPETSNAZ_WEAPON_EMPTY ); + + switch (pev->weapons) + { + case SPETSNAZ_WEAPON_AKS: + DropItem( "weapon_aks", vecGunPos, vecGunAngles ); + break; + case SPETSNAZ_WEAPON_GROZA: + DropItem( "weapon_groza", vecGunPos, vecGunAngles ); + break; + case SPETSNAZ_WEAPON_ASVAL: + DropItem( "weapon_val", vecGunPos, vecGunAngles ); + break; + } + + if (m_iHasGrenades) + DropItem( "weapon_handgrenade", BodyTarget( pev->origin ), vecGunAngles ); + + break; + } + + case MIL_AE_RELOAD: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "alpha/alpha_reload.wav", 1, ATTN_NORM ); + m_cAmmoLoaded = m_cClipSize; + pev->health = gSkillData.alphaHealth; // Wargon: Ïîïîëíåíèå õåëñîâ ïðè ïåðåçàðÿäêå. (1.1) + ClearConditions(bits_COND_NO_AMMO_LOADED); + break; + + case MIL_AE_BURST1: + { + // the first round of the three round burst plays the sound and puts a sound in the world sound list. + // buz: sound moved to Shoot + // ALERT(at_console, "*-------- spec burst 1\n"); + Shoot(); + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 384, 0.3 ); + } + break; + + case MIL_AE_CAUGHT_ENEMY: + { + // if ( FOkToSpeak() ) + // { + SENTENCEG_PlayRndSz(ENT(pev), "AL_ALERT", MIL_VOL, MIL_ATTN, 0, m_voicePitch); + JustSpoke(); + // } + } + + default: + CMilitary::HandleAnimEvent( pEvent ); + break; + } +} + + + + +//========================================================= +// Get Schedule! +//========================================================= +Schedule_t *CSpetsnaz :: GetSchedule( void ) +{ + + // clear old sentence + m_iSentence = MIL_SENT_NONE; + + // flying? If PRONE, barnacle has me. IF not, it's assumed I am rapelling. + if ( pev->movetype == MOVETYPE_FLY && m_MonsterState != MONSTERSTATE_PRONE ) + { + if (pev->flags & FL_ONGROUND) + { + // just landed + pev->movetype = MOVETYPE_STEP; + return GetScheduleOfType ( SCHED_MIL_REPEL_LAND ); + } + else + { + // repel down a rope, + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + return GetScheduleOfType ( SCHED_MIL_REPEL_ATTACK ); + else + return GetScheduleOfType ( SCHED_MIL_REPEL ); + } + } + + // grunts place HIGH priority on running away from danger sounds. + if ( HasConditions(bits_COND_HEAR_SOUND) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound) + { + if (pSound->m_iType & bits_SOUND_DANGER) + { + // dangerous sound nearby! + + //!!!KELLY - currently, this is the grunt's signal that a grenade has landed nearby, + // and the grunt should find cover from the blast + // good place for "SHIT!" or some other colorful verbal indicator of dismay. + // It's not safe to play a verbal order here "Scatter", etc cause + // this may only affect a single individual in a squad. + + // if (FOkToSpeak()) + // { + SENTENCEG_PlayRndSz( ENT(pev), "AL_GREN", MIL_VOL, MIL_ATTN, 0, m_voicePitch); + JustSpoke(); + // } + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + /* + if (!HasConditions( bits_COND_SEE_ENEMY ) && ( pSound->m_iType & (bits_SOUND_PLAYER | bits_SOUND_COMBAT) )) + { + SetIdealYawToTargetAndUpdate( pSound->m_vecOrigin ); + } + */ + } + } + switch ( m_MonsterState ) + { + case MONSTERSTATE_IDLE: + case MONSTERSTATE_ALERT: + if ( pFlashlight && pHeadController && FBitSet( pev->spawnflags, SF_TOGGLE_FLASHLIGHT )) + { + if (( pHeadController->ShouldUseLights() && pFlashlight->GetState() != STATE_ON ) || ( !pHeadController->ShouldUseLights() && pFlashlight->GetState() != STATE_OFF )) + { + return GetScheduleOfType(SCHED_MIL_FLASHLIGHT); + } + } + // buz: ïåðåçàðÿäèòüñÿ, åñëè âðàãà íåò è ìàãàçèí ïîëóïóñò + if (m_cAmmoLoaded < m_cClipSize / 2) + { + return GetScheduleOfType ( SCHED_RELOAD ); + } + + if (!FStringNull(m_hRushEntity) && (gpGlobals->time > m_flRushNextTime) && (m_flRushNextTime != -1)) + { + CBaseEntity *pRushEntity = UTIL_FindEntityByTargetname( NULL, STRING( m_hRushEntity ) ); + if (pRushEntity) + { + CStartRush* pRush = (CStartRush*)pRushEntity; + m_hTargetEnt = pRush->GetDestinationEntity(); + + return GetScheduleOfType( SCHED_RUSH_TARGET ); + } + else + // rush entity not found on this map. + // try again after next changelevel + m_flRushNextTime = -1; + } + + // Behavior for following the player + if ( IsFollowing() ) + { + if ( !m_hTargetEnt->IsAlive() ) + { + // UNDONE: Comment about the recently dead player here? + StopFollowing( FALSE ); + break; + } + + // If I'm already close enough to my target + if ( TargetDistance() <= 128 ) + { + if ( HasConditions( bits_COND_CLIENT_PUSH ) ) // Player wants me to move + return GetScheduleOfType( SCHED_MOVE_AWAY_FOLLOW ); + } + + return GetScheduleOfType( SCHED_TARGET_FACE ); // Just face and follow. + } + + if ( HasConditions( bits_COND_CLIENT_PUSH ) ) // Player wants me to move + return GetScheduleOfType( SCHED_MOVE_AWAY ); + + // try to say something about smells + TrySmellTalk(); + break; + + case MONSTERSTATE_COMBAT: + { +// dead enemy + // Wargon: Êðîìå ñëó÷àåâ, êîãäà âðàã âçÿò èç äàííûõ èãðîêà. (1.1) + if ( HasConditions( bits_COND_ENEMY_DEAD ) && !m_fEnemyFromPlayer ) + { + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_KILL"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "AL_KILL", 4, VOL_NORM, ATTN_NORM ); + } + + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + +// new enemy + if ( HasConditions(bits_COND_NEW_ENEMY) ) + { + // if (FOkToSpeak())// && RANDOM_LONG(0,1)) + // { + if (m_hEnemy != NULL) // buz: rewritten + { + // american spy + if (m_hEnemy->Classify() == CLASS_HUMAN_MILITARY) + SENTENCEG_PlayRndSz( ENT(pev), "AL_AMER", MIL_VOL, MIL_ATTN, 0, m_voicePitch); + + // monster + else if ((m_hEnemy->Classify() == CLASS_ALIEN_MILITARY) || + (m_hEnemy->Classify() == CLASS_ALIEN_MONSTER) || + (m_hEnemy->Classify() == CLASS_ALIEN_PREY) || + (m_hEnemy->Classify() == CLASS_ALIEN_PREDATOR)) + SENTENCEG_PlayRndSz( ENT(pev), "AL_MONST", MIL_VOL, MIL_ATTN, 0, m_voicePitch); + } + JustSpoke(); + // } + + if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_MIL_SUPPRESS ); + } + else + { + return GetScheduleOfType ( SCHED_MIL_ESTABLISH_LINE_OF_FIRE ); + } + } +// no ammo + else if ( HasConditions ( bits_COND_NO_AMMO_LOADED ) ) + { + // Wargon: Ñïåöíàçîâåö ÷àñòî áåãàåò â óêðûòèå ÷åðåç âñþ êàðòó. Íàì ýòî íå íóæíî. (1.1) +// if ((m_afCapability & bits_CAP_CROUCH_COVER) && !HasConditions(bits_COND_CROUCH_NOT_SAFE) ) // buz: reload here, if safe + return GetScheduleOfType ( SCHED_RELOAD ); +// else +// return GetScheduleOfType ( SCHED_MIL_COVER_AND_RELOAD ); + } + +// damaged just a little + else if ( HasConditions( bits_COND_LIGHT_DAMAGE ) ) + { + /* // if hurt: + // 90% chance of taking cover + // 10% chance of flinch. + int iPercent = RANDOM_LONG(0,99); + + if ( iPercent <= 90 && m_hEnemy != NULL ) + { + // only try to take cover if we actually have an enemy! + + //!!!KELLY - this grunt was hit and is going to run to cover. + // if (FOkToSpeak()) // && RANDOM_LONG(0,1)) + // { + //SENTENCEG_PlayRndSz( ENT(pev), "HG_COVER", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + m_iSentence = AL_SENT_COVER; + //JustSpoke(); + // } + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + else + { + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + }*/ + + int iPercent; + + // buz: 90% to duck and cover, if can + if ((m_afCapability & bits_CAP_CROUCH_COVER) && !HasConditions(bits_COND_CROUCH_NOT_SAFE) && m_hEnemy != NULL ) + { + iPercent = RANDOM_LONG(0,99); + if (iPercent <= 90) + return GetScheduleOfType( SCHED_MIL_DUCK_COVER_WAIT ); // wait some time in cover + } + + // buz: now 50% to try normal way of taking cover + iPercent = RANDOM_LONG(0,99); + if ( iPercent <= 50 && m_hEnemy != NULL ) + { + //!!!KELLY - this grunt was hit and is going to run to cover. + // if (FOkToSpeak()) // && RANDOM_LONG(0,1)) + // { + //SENTENCEG_PlayRndSz( ENT(pev), "HG_COVER", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + m_iSentence = MIL_SENT_COVER; + //JustSpoke(); + // } + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + else + { + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + } +// can kick + else if ( HasConditions ( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + if( pHeadController && pHeadController->GetBackTrace() < 32.0f ) + return GetScheduleOfType ( SCHED_MELEE_ATTACK1 ); + else + { + //ALERT(at_console, "BACK! \n"); + return GetScheduleOfType ( SCHED_MIL_WALKBACK_FIRE ); + } + } +// can grenade launch + + else if ((m_iHasGrenades) && HasConditions ( bits_COND_CAN_RANGE_ATTACK2 )) + { + // shoot a grenade if you can + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } +// can shoot + else if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } +// can't see enemy + else if ( HasConditions( bits_COND_ENEMY_OCCLUDED ) ) + { + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) ) + { + //!!!KELLY - this grunt is about to throw or fire a grenade at the player. Great place for "fire in the hole" "frag out" etc + // if (FOkToSpeak()) + // { + SENTENCEG_PlayRndSz( ENT(pev), "AL_THROW", MIL_VOL, MIL_ATTN, 0, m_voicePitch); + JustSpoke(); + // } + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + + //!!!KELLY - grunt cannot see the enemy and has just decided to + // charge the enemy's position. + // if (FOkToSpeak())// && RANDOM_LONG(0,1)) + // { + //SENTENCEG_PlayRndSz( ENT(pev), "HG_CHARGE", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + m_iSentence = AL_SENT_CHARGE; + //JustSpoke(); + // } + + return GetScheduleOfType( SCHED_MIL_ESTABLISH_LINE_OF_FIRE ); + } + + if ( HasConditions( bits_COND_SEE_ENEMY ) && !HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_MIL_ESTABLISH_LINE_OF_FIRE ); + } + } + } + + // no special cases here, call the base class + return CTalkMonster :: GetSchedule(); +} + +Schedule_t *CSpetsnaz :: GetScheduleOfType(int Type) +{ + + return CMilitary::GetScheduleOfType(Type); + +} + + +void CSpetsnaz :: SpeakSentence( void ) +{ + if ( m_iSentence == AL_SENT_NONE ) + { + // no sentence cued up. + return; + } + +// if (FOkToSpeak()) +// { + SENTENCEG_PlayRndSz( ENT(pev), pSpetsnazSentences[ m_iSentence ], MIL_VOL, MIL_ATTN, 0, m_voicePitch); + JustSpoke(); +// } +} + + +BOOL CSpetsnaz :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if (pev->weapons == SPETSNAZ_WEAPON_EMPTY) + return false; + + return CMilitary :: CheckRangeAttack1 ( flDot, flDist ); +} + + +void CSpetsnaz :: RunAI ( void ) +{ +// ALERT(at_console, "*** time %f\n", gpGlobals->time); + if (m_fNextRadioNoise && (m_fNextRadioNoise < gpGlobals->time)) + { + m_fNextRadioNoise = gpGlobals->time + RANDOM_FLOAT( 20, 60 ); + SENTENCEG_PlayRndSz( ENT(pev), "AL_RADIONOISE", 0.2, ATTN_STATIC, 0, m_voicePitch, CHAN_BODY); + } + CMilitary::RunAI(); +} + +// Wargon: Äåôîëòíûé TASK_DIE îâåððàéäåí äëÿ èìèòàöèè äåéñòâèÿ ýíòèòè player_loadsaved. (1.1) +void CSpetsnaz :: RunTask ( Task_t *pTask ) +{ + switch (pTask->iTask) + { + case TASK_DIE: + { + if (m_fSequenceFinished && pev->frame >= 255) + { + pev->deadflag = DEAD_DEAD; + StopAnimation(); + if (!BBoxFlat()) + UTIL_SetSize(pev, Vector(-4, -4, 0), Vector(4, 4, 1)); + else + UTIL_SetSize(pev, Vector(pev->mins.x, pev->mins.y, pev->mins.z), Vector(pev->maxs.x, pev->maxs.y, pev->mins.z + 1)); + if (ShouldFadeOnDeath()) + SUB_StartFadeOut(); + else + CSoundEnt::InsertSound(bits_SOUND_CARCASS, pev->origin, 384, 30); + if (!gpGlobals->deathmatch) + { + CBasePlayer *pPlayer = (CBasePlayer *)CBaseEntity::Instance(g_engfuncs.pfnPEntityOfEntIndex(1)); + if (pPlayer) + { + ClearBits( pPlayer->m_iHideHUD, ITEM_SUIT ); + if (pPlayer->m_iGasMaskOn) + pPlayer->ToggleGasMask(); + ClearBits( pPlayer->m_iHideHUD, ITEM_GASMASK ); + if (pPlayer->m_iHeadShieldOn) + pPlayer->ToggleHeadShield(); + ClearBits( pPlayer->m_iHideHUD, ITEM_HEADSHIELD ); + } + UTIL_ScreenFadeAll(Vector(0, 0, 0), 1, 6, 255, 0x0001); // Wargon: FFADE_OUT. (1.1) + UTIL_ShowMessageAll("#ALPHA_DIED"); + SetNextThink(5); + SetThink(&CSpetsnaz::MonsterDeadThink); + } + else + SetThink(NULL); + } + break; + } + default: + { + CMilitary::RunTask(pTask); + break; + } + } +} + +// Wargon: Àâòîçàãðóçêà ñðàáàòûâàåò ñ çàäåðæêîé ïîñëå ñìåðòè ñïåöíàçîâöà. (1.1) +void CSpetsnaz :: MonsterDeadThink ( void ) +{ + SERVER_COMMAND("reload\n"); +} + + +/*************************************** + Spetsnaz with aps class +***************************************/ + +#define SPETSNAZ2_HEAD_GROUP 1 +#define SPETSNAZ2_HEAD_SHIELD 1 + +#define SPETSNAZ2_GUN_GROUP 2 +#define SPETSNAZ2_WEAPON_APS 0 +#define SPETSNAZ2_WEAPON_EMPTY 1 + +class CSpetsnazAPS : public CSpetsnaz +{ +public: + void Spawn( void ); + void Precache( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void Shoot ( void ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + void SetActivity ( Activity NewActivity ); + void GibMonster( void ); +}; + +LINK_ENTITY_TO_CLASS( monster_alpha_pistol, CSpetsnazAPS ); + +void CSpetsnazAPS :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/soldier_alpha_pistol.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + // Wargon: Õåëñû ñïåöíàçîâöåâ, çàäàííûå â ïàðàìåòðàõ ýíòèòè, èãíîðèðóþòñÿ. (1.1) + // if (pev->health == 0) + pev->health = gSkillData.alphaHealth; + m_flFieldOfView = VIEW_FIELD_FULL; //0.2; + m_MonsterState = MONSTERSTATE_NONE; + m_flNextGrenadeCheck = gpGlobals->time + 1; + m_flNextPainTime = gpGlobals->time; + m_iSentence = MIL_SENT_NONE; + m_afCapability = bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP; + m_HackedGunPos = Vector ( 0, 0, 55 ); + + m_cClipSize = 12; + m_cAmmoLoaded = m_cClipSize; + + // buz: pev->body is head number + int head = pev->body; + pev->body = 0; + SetBodygroup( SPETSNAZ2_HEAD_GROUP, head ); + m_iHasHeadShield = (head == SPETSNAZ2_HEAD_SHIELD) ? 1 : 0; + m_iNoGasDamage = 0; + + MonsterInit(); + SetUse(&CSpetsnazAPS :: FollowerUse ); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CSpetsnazAPS :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/soldier_alpha_pistol.mdl"); + + PRECACHE_SOUND( "weapons/dryfire1.wav" ); //LRC + + PRECACHE_SOUND ("weapons/aps_fire.wav"); + + PRECACHE_SOUND( "alpha/alpha_die1.wav" ); + PRECACHE_SOUND( "alpha/alpha_die2.wav" ); + PRECACHE_SOUND( "alpha/alpha_die3.wav" ); + + PRECACHE_SOUND( "alpha/alpha_pain1.wav" ); + PRECACHE_SOUND( "alpha/alpha_pain2.wav" ); + PRECACHE_SOUND( "alpha/alpha_pain3.wav" ); + PRECACHE_SOUND( "alpha/alpha_pain4.wav" ); + PRECACHE_SOUND( "alpha/alpha_pain5.wav" ); + + PRECACHE_SOUND( "alpha/alpha_reload_aps.wav" ); + + PRECACHE_SOUND("zombie/claw_miss2.wav");// because we use the basemonster SWIPE animation event + + m_iBrassShell = PRECACHE_MODEL ("models/aps_shell.mdl"); + + // get voice pitch + if (RANDOM_LONG(0,1)) + m_voicePitch = 109 + RANDOM_LONG(0,7); + else + m_voicePitch = 100; + + if (pev->spawnflags & SF_SPETSNAZ_RADIO) + { + m_fNextRadioNoise = gpGlobals->time + RANDOM_FLOAT( 20, 60 ); + } + + TalkInit(); + CTalkMonster::Precache(); +} + +BOOL CSpetsnazAPS :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + return CMilitary :: CheckRangeAttack1 ( flDot, flDist ); +} + + + +void CSpetsnazAPS :: GibMonster ( void ) +{ + Vector vecGunPos; + Vector vecGunAngles; + + if (GetBodygroup(SPETSNAZ2_GUN_GROUP) != SPETSNAZ2_WEAPON_EMPTY && !(pev->spawnflags & SF_MONSTER_NO_WPN_DROP)) + { + GetAttachment( 0, vecGunPos, vecGunAngles ); + + CBaseEntity *pGun = DropItem( "weapon_aps", vecGunPos, vecGunAngles ); + if ( pGun ) + { + pGun->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + pGun->pev->avelocity = Vector ( 0, RANDOM_FLOAT( 200, 400 ), 0 ); + } + + if (m_iHasGrenades) + { + pGun = DropItem( "weapon_handgrenade", BodyTarget( pev->origin ), vecGunAngles ); + if ( pGun ) + { + pGun->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + pGun->pev->avelocity = Vector ( 0, RANDOM_FLOAT( 200, 400 ), 0 ); + } + } + } + + CBaseMonster :: GibMonster(); +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CSpetsnazAPS :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + Vector vecShootDir; + Vector vecShootOrigin; + + switch( pEvent->event ) + { + case MIL_AE_DROP_GUN: + { + if (pev->spawnflags & SF_MONSTER_NO_WPN_DROP) break; //LRC + + Vector vecGunPos; + Vector vecGunAngles; + GetAttachment( 0, vecGunPos, vecGunAngles ); + // switch to body group with no gun. + SetBodygroup( SPETSNAZ2_GUN_GROUP, SPETSNAZ2_WEAPON_EMPTY ); + DropItem( "weapon_aps", vecGunPos, vecGunAngles ); + + if (m_iHasGrenades) + DropItem( "weapon_handgrenade", BodyTarget( pev->origin ), vecGunAngles ); + + break; + } + + case MIL_AE_RELOAD: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "alpha/alpha_reload_aps.wav", 1, ATTN_NORM ); + m_cAmmoLoaded = m_cClipSize; + ClearConditions(bits_COND_NO_AMMO_LOADED); + break; + + default: + CSpetsnaz::HandleAnimEvent( pEvent ); + break; + } +} + + +//========================================================= +// Shoot +//========================================================= +void CSpetsnazAPS :: Shoot ( void ) +{ +// if (m_hEnemy == NULL && m_pCine == NULL) //LRC - scripts may fire when you have no enemy +// { +// return; +// } + + UTIL_MakeVectors ( pev->angles ); + Vector vecShootOrigin = GetGunPosition(); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); + + if (m_cAmmoLoaded > 0) + { + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/aps_fire.wav", 1, ATTN_NORM ); + + Vector vecBrassPos, vecBrassDir; + GetAttachment(3, vecBrassPos, vecBrassDir); + Vector vecShellVelocity = gpGlobals->v_right * RANDOM_FLOAT(40,90) + gpGlobals->v_up * RANDOM_FLOAT(75,200) + gpGlobals->v_forward * RANDOM_FLOAT(-40, 40); + EjectBrass( vecBrassPos, vecShellVelocity, pev->angles.y, m_iBrassShell, TE_BOUNCE_SHELL); + FireBullets( 1, vecShootOrigin, vecShootDir, VECTOR_CONE_5DEGREES, 2048.0f, BULLET_NORMAL, gSkillData.monDmg9MM ); + + pev->effects |= EF_MUZZLEFLASH; + + m_cAmmoLoaded--;// take away a bullet! + } + else + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/dryfire1.wav", 1, ATTN_NORM ); + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); +} + + +void CSpetsnazAPS :: SetActivity ( Activity NewActivity ) +{ + int iSequence = ACTIVITY_NOT_AVAILABLE; + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + switch ( NewActivity) + { + case ACT_RANGE_ATTACK1: + //iSequence = LookupActivity ( NewActivity ); + if ( m_fStanding ) + { + // get aimable sequence + iSequence = LookupSequence( "standing" ); + } + else + { + // get crouching shoot + iSequence = LookupSequence( "crouching" ); + } + + break; + case ACT_RANGE_ATTACK2: + // get toss anim + iSequence = LookupSequence( "throwgrenade" ); + + break; + case ACT_RUN: + if ( pev->health <= MIL_LIMP_HEALTH ) + { + // limp! + iSequence = LookupActivity ( ACT_RUN_HURT ); + } + else + { + iSequence = LookupActivity ( NewActivity ); + } + break; + case ACT_WALK: + if ( pev->health <= MIL_LIMP_HEALTH ) + { + // limp! + iSequence = LookupActivity ( ACT_WALK_HURT ); + } + else + { + iSequence = LookupActivity ( NewActivity ); + } + break; + case ACT_IDLE: + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + { + NewActivity = ACT_IDLE_ANGRY; + } + iSequence = LookupActivity ( NewActivity ); + break; + default: + iSequence = LookupActivity ( NewActivity ); + break; + } + + m_Activity = NewActivity; // Go ahead and set this so it doesn't keep trying when the anim is not present + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + { + if ( pev->sequence != iSequence || !m_fSequenceLoops ) + { + pev->frame = 0; + } + + pev->sequence = iSequence; // Set to the reset anim (if it's there) + ResetSequenceInfo( ); + RecalculateYawSpeed(); + } + else + { + // Not available try to get default anim + ALERT ( at_debug, "%s has no sequence for act:%s\n", STRING(pev->classname), GetNameForActivity( NewActivity )); + pev->sequence = 0; // Set to the reset anim (if it's there) + } +} + +#define INFECTED_WEAPON_AKS 0 +#define INFECTED_WEAPON_ASVAL 1 +#define INFECTED_WEAPON_GROZA 2 +#define INFECTED_WEAPON_EMPTY 3 + +/*************************************** + Infected soldier class +****************************************/ + +class CSoldierInfected : public CMilitary +{ +public: + void Spawn( void ); + void Precache( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void DeathSound( void ); + void PainSound( void ); + void Shoot ( void ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); + + int Classify(void); + + Schedule_t *GetSchedule( void ); + Schedule_t *GetScheduleOfType(int Type); + void GibMonster( void ); + +}; + +LINK_ENTITY_TO_CLASS( monster_soldier_infected, CSoldierInfected ); + +void CSoldierInfected::Spawn( ) +{ + Precache( ); + CMilitary::Spawn(); + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/monsters/monster_soldiershooter.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + + //pev->health = gSkillData.infSoldierHealth; + m_flFieldOfView = VIEW_FIELD_FULL; //0.2; + m_MonsterState = MONSTERSTATE_NONE; + m_flNextPainTime = gpGlobals->time; + m_afCapability = bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP; + m_HackedGunPos = Vector ( 0, 0, 55 ); + + m_cClipSize = 30; + m_cAmmoLoaded = m_cClipSize; + + // buz: pev->body is head number + int head = pev->body; + pev->body = 0; + + MonsterInit(); + InitFlashlight(); + InitHeadController(); +} + +void CSoldierInfected :: Precache( ) +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/monsters/monster_soldiershooter.mdl"); + + PRECACHE_SOUND( "weapons/dryfire1.wav" ); //LRC + + PRECACHE_SOUND ("weapons/aps_fire.wav"); + + PRECACHE_SOUND( "InfectedSoldier/die1.wav" ); + PRECACHE_SOUND( "InfectedSoldier/die2.wav" ); + PRECACHE_SOUND( "InfectedSoldier/die3.wav" ); + + PRECACHE_SOUND( "InfectedSoldier/pain1.wav" ); + PRECACHE_SOUND( "InfectedSoldier/pain2.wav" ); + PRECACHE_SOUND( "InfectedSoldier/pain3.wav" ); + PRECACHE_SOUND( "InfectedSoldier/pain4.wav" ); + PRECACHE_SOUND( "InfectedSoldier/pain5.wav" ); + + PRECACHE_SOUND( "alpha/alpha_reload_aps.wav" ); + + PRECACHE_SOUND("zombie/claw_miss2.wav");// because we use the basemonster SWIPE animation event + + m_iBrassShell = PRECACHE_MODEL ("models/aps_shell.mdl"); + + // get voice pitch + if (RANDOM_LONG(0,1)) + m_voicePitch = 109 + RANDOM_LONG(0,7); + else + m_voicePitch = 100; + +} +void CSoldierInfected::HandleAnimEvent( MonsterEvent_t *pEvent ) +{ +Vector vecShootDir; +Vector vecShootOrigin; + + switch( pEvent->event ) + { + + case MIL_AE_DROP_GUN: + { + if (pev->spawnflags & SF_MONSTER_NO_WPN_DROP) break; //LRC + + Vector vecGunPos; + Vector vecGunAngles; + + GetAttachment( 0, vecGunPos, vecGunAngles ); + + // switch to body group with no gun. + SetBodygroup( MIL_GUN_GROUP, MIL_GUN_NONE ); + + // now spawn a gun. + DropItem( "weapon_ak74", vecGunPos, vecGunAngles ); + + if (pev->weapons == MIL_GRENADES) + { + DropItem( "weapon_handgrenade", BodyTarget( pev->origin ), vecGunAngles ); + } + + break; + } + case MIL_AE_RELOAD: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "military/mil_reload.wav", 1, ATTN_NORM ); + m_cAmmoLoaded = m_cClipSize; + ClearConditions(bits_COND_NO_AMMO_LOADED); + break; + + + case MIL_AE_BURST1: + { + // ALERT(at_console, "*-------- mil burst 1\n"); + // the first round of the three round burst plays the sound and puts a sound in the world sound list. + if (m_cAmmoLoaded > 0) + { + if ( RANDOM_LONG(0,1) ) + { + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "military/mil_mgun1.wav", 1, ATTN_NORM ); + } + else + { + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "military/mil_mgun2.wav", 1, ATTN_NORM ); + } + } + else + { + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/dryfire1.wav", 1, ATTN_NORM ); + } + + Shoot(); + + } + break; + + case MIL_AE_BURST2: + case MIL_AE_BURST3: + Shoot(); + break; + + case MIL_AE_KICK: + { + CBaseEntity *pHurt = Kick(); + + if ( pHurt ) + { + // buz: move only if it is a monster! + if (pHurt->MyMonsterPointer()) + { + UTIL_MakeVectors( pev->angles ); + pHurt->pev->punchangle.x = 15; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 100 + gpGlobals->v_up * 50; + } + pHurt->TakeDamage( pev, pev, gSkillData.MilDmgKick, DMG_CLUB ); + } + } + break; + } +} + +//========================================================= +// Get Schedule! +//========================================================= +Schedule_t *CSoldierInfected :: GetSchedule( void ) +{ + + // clear old sentence + m_iSentence = MIL_SENT_NONE; + + if (m_cAmmoLoaded < m_cClipSize / 2) + { + return GetScheduleOfType ( SCHED_RELOAD ); + } + switch ( m_MonsterState ) + { + case MONSTERSTATE_IDLE: + case MONSTERSTATE_ALERT: + + + case MONSTERSTATE_COMBAT: + { + +// no ammo + if ( HasConditions ( bits_COND_NO_AMMO_LOADED ) ) + { + // Wargon: Ñïåöíàçîâåö ÷àñòî áåãàåò â óêðûòèå ÷åðåç âñþ êàðòó. Íàì ýòî íå íóæíî. (1.1) +// if ((m_afCapability & bits_CAP_CROUCH_COVER) && !HasConditions(bits_COND_CROUCH_NOT_SAFE) ) // buz: reload here, if safe + return GetScheduleOfType ( SCHED_RELOAD ); +// else +// return GetScheduleOfType ( SCHED_MIL_COVER_AND_RELOAD ); + } + + +// can kick + else if ( HasConditions ( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_MELEE_ATTACK1 ); + } + +// can't see enemy + else if ( HasConditions( bits_COND_ENEMY_OCCLUDED ) ) + { + return GetScheduleOfType( SCHED_MIL_ESTABLISH_LINE_OF_FIRE ); + } + + if ( HasConditions( bits_COND_SEE_ENEMY ) && !HasConditions ( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_INFECTED_FIRINGWALK ); + } + } + } + + // no special cases here, call the base class + return CBaseMonster :: GetSchedule(); +} + +Schedule_t* CSoldierInfected :: GetScheduleOfType ( int Type ) +{ + + switch ( Type ) + { + case SCHED_INFECTED_FIRINGWALK: + { + return slInfFiringWalk; + } + break; + default: + { + return CMilitary :: GetScheduleOfType ( Type ); + } + } +} +//========================================================= +// PainSound +//========================================================= +void CSoldierInfected :: PainSound ( void ) +{ + if ( gpGlobals->time > m_flNextPainTime ) + { + switch ( RANDOM_LONG(0,6) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "InfectedSoldier/pain1.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "InfectedSoldier/pain2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "InfectedSoldier/pain3.wav", 1, ATTN_NORM ); + break; + case 3: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "InfectedSoldier/pain4.wav", 1, ATTN_NORM ); + break; + case 4: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "InfectedSoldier/pain5.wav", 1, ATTN_NORM ); + break; + } + + m_flNextPainTime = gpGlobals->time + 1; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CSoldierInfected :: DeathSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "InfectedSoldier/die1.wav", 1, ATTN_IDLE ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "InfectedSoldier/die2.wav", 1, ATTN_IDLE ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "InfectedSoldier/die3.wav", 1, ATTN_IDLE ); + break; + } +} +void CSoldierInfected::Shoot ( void ) +{ + UTIL_MakeVectors ( pev->angles ); + Vector vecShootOrigin = GetGunPosition(); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); + + if (m_cAmmoLoaded > 0) + { + switch (pev->weapons) + { + case INFECTED_WEAPON_AKS: + switch(RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/aks_fire1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/aks_fire2.wav", 1, ATTN_NORM ); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/aks_fire3.wav", 1, ATTN_NORM ); break; + } + break; + + case INFECTED_WEAPON_GROZA: + switch(RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/groza_fire1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/groza_fire2.wav", 1, ATTN_NORM ); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/groza_fire3.wav", 1, ATTN_NORM ); break; + } + break; + + case INFECTED_WEAPON_ASVAL: + switch(RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/val_fire1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/val_fire2.wav", 1, ATTN_NORM ); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/val_fire3.wav", 1, ATTN_NORM ); break; + } + break; + } + + Vector vecBrassPos, vecBrassDir; + GetAttachment(3, vecBrassPos, vecBrassDir); + Vector vecShellVelocity = gpGlobals->v_right * RANDOM_FLOAT(40,90) + gpGlobals->v_up * RANDOM_FLOAT(75,200) + gpGlobals->v_forward * RANDOM_FLOAT(-40, 40); + EjectBrass ( vecBrassPos, vecShellVelocity, pev->angles.y, m_iBrassShell, TE_BOUNCE_SHELL); + + switch (pev->weapons) + { + case SPETSNAZ_WEAPON_AKS: + FireBullets(1, vecShootOrigin, vecShootDir, VECTOR_CONE_7DEGREES, 2048, BULLET_NORMAL, gSkillData.monDmgAK ); + break; + case SPETSNAZ_WEAPON_GROZA: + FireBullets(1, vecShootOrigin, vecShootDir, VECTOR_CONE_5DEGREES, 2048, BULLET_NORMAL, gSkillData.monDmgGroza ); + break; + case SPETSNAZ_WEAPON_ASVAL: + FireBullets(1, vecShootOrigin, vecShootDir, VECTOR_CONE_5DEGREES, 2048, BULLET_NORMAL, gSkillData.monDmgAsval ); + break; + default: + ALERT(at_error, "ERROR: trying to fire without a gun!\n"); + } + + pev->effects |= EF_MUZZLEFLASH; + + m_cAmmoLoaded--;// take away a bullet! + // ALERT(at_console, "spcnazz ammo has %d\n", m_cAmmoLoaded); + } + else + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/dryfire1.wav", 1, ATTN_NORM ); + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); + +} + +BOOL CSoldierInfected :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + return FALSE; +} + +void CSoldierInfected :: GibMonster( void ) +{ + Vector vecGunPos; + Vector vecGunAngles; + + if (GetBodygroup(SPETSNAZ_GUN_GROUP) != SPETSNAZ_WEAPON_EMPTY && !(pev->spawnflags & SF_MONSTER_NO_WPN_DROP)) + { + GetAttachment( 0, vecGunPos, vecGunAngles ); + + CBaseEntity *pGun = NULL; + switch (pev->weapons) + { + case INFECTED_WEAPON_AKS: + pGun = DropItem( "weapon_aks", vecGunPos, vecGunAngles ); + break; + case INFECTED_WEAPON_GROZA: + pGun = DropItem( "weapon_groza", vecGunPos, vecGunAngles ); + break; + case INFECTED_WEAPON_ASVAL: + pGun = DropItem( "weapon_val", vecGunPos, vecGunAngles ); + break; + } + + if ( pGun ) + { + pGun->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + pGun->pev->avelocity = Vector ( 0, RANDOM_FLOAT( 200, 400 ), 0 ); + } + + + } + CBaseMonster :: GibMonster(); +} + +BOOL CSoldierInfected :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + CBaseMonster *pEnemy; + + if ( m_hEnemy != NULL ) + { + pEnemy = m_hEnemy->MyMonsterPointer(); + + if ( !pEnemy ) + { + return FALSE; + } + } + + if ( flDist <= 64 && flDot >= 0.7) + { + return TRUE; + } + return FALSE; +} + +int CSoldierInfected :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_PREY; +} \ No newline at end of file diff --git a/dlls/paranoia_turret.cpp b/dlls/paranoia_turret.cpp new file mode 100644 index 0000000..ea8e83e --- /dev/null +++ b/dlls/paranoia_turret.cpp @@ -0,0 +1,318 @@ +/*************************************** +* +* Writen by BUzer for Half-Life: Paranoia modification +* +* Static machineguns code +* +****************************************/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "effects.h" +#include "weapons.h" +#include "explode.h" +#include "monsters.h" +#include "movewith.h" +#include "animation.h" + +#include "player.h" + +extern int gmsgSpecTank; + + +class CFuncMachinegun : public CBaseAnimating +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return FCAP_IMPULSE_USE; } + BOOL OnControls( entvars_t *pevTest ); + void PostFrame( CBaseEntity *pActivator ); + void UpdateClientData( CBasePlayer *client ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +// short m_usEvent; + float m_flPointHeight; + float m_flDistUp, m_flDistFwd; +// float m_flConeHor, m_flConeVer; + float m_flNextAttack; + float m_fireRate; + float m_flDamage; +// Vector savedPlayerAngles; + int m_iShouldUpdate; +// float m_flLastAnimTime; + int m_iAmmo; +}; + +LINK_ENTITY_TO_CLASS( func_machinegun, CFuncMachinegun ); + +TYPEDESCRIPTION CFuncMachinegun::m_SaveData[] = +{ + DEFINE_FIELD( CFuncMachinegun, m_flPointHeight, FIELD_FLOAT ), + DEFINE_FIELD( CFuncMachinegun, m_flDistUp, FIELD_FLOAT ), + DEFINE_FIELD( CFuncMachinegun, m_flDistFwd, FIELD_FLOAT ), +// DEFINE_FIELD( CFuncMachinegun, m_flConeHor, FIELD_FLOAT ), +// DEFINE_FIELD( CFuncMachinegun, m_flConeVer, FIELD_FLOAT ), + DEFINE_FIELD( CFuncMachinegun, m_flNextAttack, FIELD_TIME ), + DEFINE_FIELD( CFuncMachinegun, m_fireRate, FIELD_FLOAT ), + DEFINE_FIELD( CFuncMachinegun, m_flDamage, FIELD_FLOAT ), +// DEFINE_FIELD( CFuncMachinegun, savedPlayerAngles, FIELD_VECTOR ), +// DEFINE_FIELD( CFuncMachinegun, m_flLastAnimTime, FIELD_TIME ), + DEFINE_FIELD( CFuncMachinegun, m_iAmmo, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CFuncMachinegun, CBaseAnimating ); + + +void CFuncMachinegun::Precache() +{ + PRECACHE_MODEL( (char *)STRING(pev->model) ); + PRECACHE_MODEL ("models/shell.mdl"); + + pev->sequence = 0; + pev->frame = 0; + pev->framerate = 1; + ResetSequenceInfo(); + m_fSequenceLoops = TRUE; + + m_iShouldUpdate = 1; +} + + +void CFuncMachinegun::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "baseheight")) + { + m_flPointHeight = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "distfwd")) + { + m_flDistFwd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "distup")) + { + m_flDistUp = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } +/* else if (FStrEq(pkvd->szKeyName, "conehor")) + { + m_flConeHor = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "conever")) + { + m_flConeVer = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + }*/ + else if (FStrEq(pkvd->szKeyName, "firerate")) + { + m_fireRate = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damage")) + { + m_flDamage = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "ammo")) + { + m_iAmmo = atoi(pkvd->szValue); + if (m_iAmmo > 999) m_iAmmo = 999; + if (m_iAmmo < -1) m_iAmmo = -1; + pkvd->fHandled = TRUE; + } + else + CBaseAnimating::KeyValue( pkvd ); +} + + +void CFuncMachinegun::Spawn() +{ + Precache(); + + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_BBOX; + pev->angles.x = 0; // remove pitch +// m_flLastAnimTime = gpGlobals->time; + pev->renderfx = 51; // õàê äëÿ ðåíäåðåðà, ÷òîáû îí íå èíòåðïîëèðîâàë êîíòðîëëåðû + + SET_MODEL( ENT(pev), STRING(pev->model) ); + UTIL_SetSize( pev, Vector(-25,-25,0), Vector(25,25,60)); + + ResetSequenceInfo(); + m_fSequenceLoops = TRUE; + + InitBoneControllers(); +} + + +BOOL CFuncMachinegun::OnControls( entvars_t *pevTest ) +{ + Vector vecToPlayer = pevTest->origin - pev->origin; + vecToPlayer.z = 0; + + if ( vecToPlayer.Length() > 50 ) + return FALSE; + + UTIL_MakeVectors(pev->angles); + vecToPlayer = vecToPlayer.Normalize(); + + if (DotProduct(vecToPlayer, gpGlobals->v_forward) > -0.7) + return FALSE; + + return TRUE; +} + + +void CFuncMachinegun::PostFrame( CBaseEntity *pActivator ) +{ + Vector plAngles = pActivator->pev->angles; + while (plAngles.y < 0) plAngles.y += 360; + + float yawAngle = plAngles.y - pev->angles.y; + float pitchAngle = pActivator->pev->angles.x * -3; + SetBoneController( 0, yawAngle ); + SetBoneController( 1, pitchAngle ); + + StudioFrameAdvance(); // ^%&*(#%$%^@# !!!! + + // return to idle after fire anim + if (m_fSequenceFinished) + { + pev->sequence = 0; + pev->frame = 0; + ResetSequenceInfo(); +// ALERT(at_console, "return to idle\n"); + m_fSequenceLoops = TRUE; + } + + if ( gpGlobals->time < m_flNextAttack ) + return; + +// ALERT(at_console, "current ammo: %d\n", m_iAmmo); + + if ( pActivator->pev->button & IN_ATTACK && (m_iAmmo > 0 || m_iAmmo == -1)) + { + // fire + Vector vecForward, vecSrc, vecAngles; + vecAngles = pActivator->pev->angles; + vecAngles.x = vecAngles.x * -3; // invert anf scale pitch + UTIL_MakeVectorsPrivate( vecAngles, vecForward, NULL, NULL ); + GetAttachment(0, vecSrc, vecAngles); + + pev->sequence = 1; // sounds, muzzleflashes, and shells will go by anim event + pev->frame = 0; + ResetSequenceInfo(); + m_fSequenceLoops = FALSE; + + if( !m_flDamage ) m_flDamage = gSkillData.monDmg12MM; + FireBullets( 1, vecSrc, vecForward, Vector( 0, 0, 0 ), 4096, BULLET_NORMAL, m_flDamage, pActivator->pev ); + if (m_iAmmo > 0) m_iAmmo--; + + // update ammo counter + MESSAGE_BEGIN( MSG_ONE, gmsgSpecTank, NULL, pActivator->pev ); + WRITE_BYTE( 2 ); // ammo update + WRITE_LONG(m_iAmmo); + MESSAGE_END(); + + // HACKHACK -- make some noise (that the AI can hear) + if ( pActivator->IsPlayer() ) + ((CBasePlayer *)pActivator)->m_iWeaponVolume = LOUD_GUN_VOLUME; + + m_flNextAttack = gpGlobals->time + ( 1.0f / m_fireRate ); + } + +// ALERT(at_console, "current sequence: %d\n", pev->sequence); +} + + +void CFuncMachinegun::UpdateClientData( CBasePlayer *client ) +{ + if (m_iShouldUpdate) + { + Vector vecPoint = pev->origin + Vector(0, 0, m_flPointHeight); + float coneHor = GetControllerBound( 0 ); + float coneVer = GetControllerBound( 1 ); + + MESSAGE_BEGIN( MSG_ONE, gmsgSpecTank, NULL, client->pev ); + WRITE_BYTE( 1 ); // tank is on + WRITE_COORD(vecPoint.x); + WRITE_COORD(vecPoint.y); + WRITE_COORD(vecPoint.z); + WRITE_COORD(pev->angles.y); // write default yaw + WRITE_COORD(coneHor); // write cone + WRITE_COORD(coneVer); // + WRITE_COORD(m_flDistFwd); + WRITE_COORD(m_flDistUp); + WRITE_LONG(m_iAmmo); + MESSAGE_END(); + + m_iShouldUpdate = 0; + } +} + +void CFuncMachinegun::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (!pActivator->IsPlayer()) + { + ALERT(at_console, "non player activating func_machinegun!\n"); + return; + } + + if (useType == USE_SET && value == 2) // called by playerthink, update position + { + PostFrame( pActivator ); + } + else if (useType == USE_SET && value == 3) // update client data + { + UpdateClientData( (CBasePlayer*)pActivator ); + } + else if (useType == USE_OFF) // turn off + { + CBasePlayer *player = (CBasePlayer *)pActivator; + // player->pev->angles = savedPlayerAngles; + MESSAGE_BEGIN( MSG_ONE, gmsgSpecTank, NULL, player->pev ); + WRITE_BYTE( 0 ); // tank is off + MESSAGE_END(); + + pev->sequence = 0; + pev->frame = 0; + ResetSequenceInfo(); + m_fSequenceLoops = TRUE; + + if ( player->m_pActiveItem ) + player->m_pActiveItem->Deploy(); + + player->m_iHideHUD &= ~HIDEHUD_WEAPONS; + } + else // turn on + { + // is player in valid zone + if ( OnControls(pActivator->pev) ) + { + CBasePlayer *player = (CBasePlayer *)pActivator; + player->m_pSpecTank = this; + m_iShouldUpdate = 1; + // savedPlayerAngles = player->pev->angles; + // player->pev->angles = pev->angles; + player->m_iHideHUD |= HIDEHUD_WEAPONS; + + if ( player->m_pActiveItem ) + { + player->m_pActiveItem->Holster( true ); + player->pev->weaponmodel = 0; + player->pev->viewmodel = 0; + } + } + // else + // ALERT(at_console, "player is not in valid zone\n"); + } +} diff --git a/dlls/pathcorner.cpp b/dlls/pathcorner.cpp new file mode 100644 index 0000000..0939f79 --- /dev/null +++ b/dlls/pathcorner.cpp @@ -0,0 +1,440 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// ========================== PATH_CORNER =========================== +// + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "trains.h" +#include "saverestore.h" + +class CPathCorner : public CPointEntity +{ +public: + void Spawn( ); + void KeyValue( KeyValueData* pkvd ); + float GetDelay( void ) { return m_flWait; } +// void Touch( CBaseEntity *pOther ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + float m_flWait; +}; + +LINK_ENTITY_TO_CLASS( path_corner, CPathCorner ); + +// Global Savedata for Delay +TYPEDESCRIPTION CPathCorner::m_SaveData[] = +{ + DEFINE_FIELD( CPathCorner, m_flWait, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CPathCorner, CPointEntity ); + +// +// Cache user-entity-field values until spawn is called. +// +void CPathCorner :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "turnspeed")) //LRC + { + if (pkvd->szValue[0]) // if the field is blank, don't set the spawnflag. + { + pev->spawnflags |= SF_CORNER_AVELOCITY; + UTIL_StringToVector( (float*)pev->avelocity, pkvd->szValue); + } + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + + +void CPathCorner :: Spawn( ) +{ + ASSERTSZ(!FStringNull(pev->targetname), "path_corner without a targetname"); +} + +#if 0 +void CPathCorner :: Touch( CBaseEntity *pOther ) +{ + entvars_t* pevToucher = pOther->pev; + + if ( FBitSet ( pevToucher->flags, FL_MONSTER ) ) + {// monsters don't navigate path corners based on touch anymore + return; + } + + // If OTHER isn't explicitly looking for this path_corner, bail out + if ( pOther->m_pGoalEnt != this ) + { + return; + } + + // If OTHER has an enemy, this touch is incidental, ignore + if ( !FNullEnt(pevToucher->enemy) ) + { + return; // fighting, not following a path + } + + // UNDONE: support non-zero flWait + /* + if (m_flWait != 0) + ALERT(at_warning, "Non-zero path-cornder waits NYI"); + */ + + // Find the next "stop" on the path, make it the goal of the "toucher". + if (FStringNull(pev->target)) + { + ALERT(at_warning, "PathCornerTouch: no next stop specified"); + } + + pOther->m_pGoalEnt = UTIL_FindEntityByTargetname ( NULL, STRING(pev->target) ); + + // If "next spot" was not found (does not exist - level design error) + if ( !pOther->m_pGoalEnt ) + { + ALERT(at_debug, "PathCornerTouch--%s couldn't find next stop in path: %s", STRING(pev->classname), STRING(pev->target)); + return; + } + + // Turn towards the next stop in the path. + pevToucher->ideal_yaw = UTIL_VecToYaw ( pOther->m_pGoalEnt->pev->origin - pevToucher->origin ); +} +#endif + + + +TYPEDESCRIPTION CPathTrack::m_SaveData[] = +{ + DEFINE_FIELD( CPathTrack, m_length, FIELD_FLOAT ), + DEFINE_FIELD( CPathTrack, m_pnext, FIELD_CLASSPTR ), + DEFINE_FIELD( CPathTrack, m_paltpath, FIELD_CLASSPTR ), + DEFINE_FIELD( CPathTrack, m_pprevious, FIELD_CLASSPTR ), + DEFINE_FIELD( CPathTrack, m_altName, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CPathTrack, CBaseEntity ); +LINK_ENTITY_TO_CLASS( path_track, CPathTrack ); + +// +// Cache user-entity-field values until spawn is called. +// +void CPathTrack :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "altpath")) + { + m_altName = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "turnspeed")) //LRC + { + if (pkvd->szValue[0]) // if the field is blank, don't set the spawnflag. + { + pev->spawnflags |= SF_PATH_AVELOCITY; + UTIL_StringToVector( (float*)pev->avelocity, pkvd->szValue); + } + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +void CPathTrack :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int on; + + // Use toggles between two paths + if ( m_paltpath ) + { + on = !FBitSet( pev->spawnflags, SF_PATH_ALTERNATE ); + if ( ShouldToggle( useType, on ) ) + { + if ( on ) + SetBits( pev->spawnflags, SF_PATH_ALTERNATE ); + else + ClearBits( pev->spawnflags, SF_PATH_ALTERNATE ); + } + } + else // Use toggles between enabled/disabled + { + on = !FBitSet( pev->spawnflags, SF_PATH_DISABLED ); + + if ( ShouldToggle( useType, on ) ) + { + if ( on ) + SetBits( pev->spawnflags, SF_PATH_DISABLED ); + else + ClearBits( pev->spawnflags, SF_PATH_DISABLED ); + } + } +} + + +void CPathTrack :: Link( void ) +{ + CBaseEntity *pTarget; + + if ( !FStringNull(pev->target) ) + { + pTarget = UTIL_FindEntityByTargetname( NULL, STRING(pev->target) ); + if ( pTarget ) + { + m_pnext = (CPathTrack*)pTarget; + m_pnext->SetPrevious( this ); + } + else + ALERT( at_debug, "Dead end link %s\n", STRING(pev->target) ); + } + + // Find "alternate" path + if ( m_altName ) + { + pTarget = UTIL_FindEntityByTargetname( NULL, STRING(m_altName) ); + if ( pTarget ) // If no next pointer, this is the end of a path + { + m_paltpath = (CPathTrack*)pTarget; + m_paltpath->SetPrevious( this ); + } + } +} + + +void CPathTrack :: Spawn( void ) +{ + pev->solid = SOLID_TRIGGER; + UTIL_SetSize(pev, Vector(-8, -8, -8), Vector(8, 8, 8)); + + m_pnext = NULL; + m_pprevious = NULL; +// DEBUGGING CODE +#if PATH_SPARKLE_DEBUG + SetThink(&CPathTrack :: Sparkle ); + SetNextThink( 0.5 ); +#endif +} + + +void CPathTrack::Activate( void ) +{ + if ( !FStringNull( pev->targetname ) ) // Link to next, and back-link + Link(); + + CPointEntity::Activate(); +} + +CPathTrack *CPathTrack :: ValidPath( CPathTrack *ppath, int testFlag ) +{ + if ( !ppath ) + return NULL; + + if ( testFlag && FBitSet( ppath->pev->spawnflags, SF_PATH_DISABLED ) ) + return NULL; + + return ppath; +} + + +void CPathTrack :: Project( CPathTrack *pstart, CPathTrack *pend, Vector *origin, float dist ) +{ + if ( pstart && pend ) + { + Vector dir = (pend->pev->origin - pstart->pev->origin); + dir = dir.Normalize(); + *origin = pend->pev->origin + dir * dist; + } +} + +CPathTrack *CPathTrack::GetNext( void ) +{ + if ( m_paltpath && FBitSet( pev->spawnflags, SF_PATH_ALTERNATE ) && !FBitSet( pev->spawnflags, SF_PATH_ALTREVERSE ) ) + return m_paltpath; + + return m_pnext; +} + + + +CPathTrack *CPathTrack::GetPrevious( void ) +{ + if ( m_paltpath && FBitSet( pev->spawnflags, SF_PATH_ALTERNATE ) && FBitSet( pev->spawnflags, SF_PATH_ALTREVERSE ) ) + return m_paltpath; + + return m_pprevious; +} + + + +void CPathTrack::SetPrevious( CPathTrack *pprev ) +{ + // Only set previous if this isn't my alternate path + if ( pprev && !FStrEq( STRING(pprev->pev->targetname), STRING(m_altName) ) ) + m_pprevious = pprev; +} + + +// Assumes this is ALWAYS enabled +CPathTrack *CPathTrack :: LookAhead( Vector *origin, float dist, int move ) +{ + CPathTrack *pcurrent; + float originalDist = dist; + + pcurrent = this; + Vector currentPos = *origin; + + if ( dist < 0 ) // Travelling backwards through path + { + dist = -dist; + while ( dist > 0 ) + { + Vector dir = pcurrent->pev->origin - currentPos; + float length = dir.Length(); + if ( !length ) + { + if ( !ValidPath(pcurrent->GetPrevious(), move) ) // If there is no previous node, or it's disabled, return now. + { + if ( !move ) + Project( pcurrent->GetNext(), pcurrent, origin, dist ); + return NULL; + } + pcurrent = pcurrent->GetPrevious(); + } + else if ( length > dist ) // enough left in this path to move + { + *origin = currentPos + (dir * (dist / length)); + return pcurrent; + } + else + { + dist -= length; + currentPos = pcurrent->pev->origin; + *origin = currentPos; + if ( !ValidPath(pcurrent->GetPrevious(), move) ) // If there is no previous node, or it's disabled, return now. + return NULL; + + pcurrent = pcurrent->GetPrevious(); + } + } + *origin = currentPos; + return pcurrent; + } + else + { + while ( dist > 0 ) + { + if ( !ValidPath(pcurrent->GetNext(), move) ) // If there is no next node, or it's disabled, return now. + { + if ( !move ) + Project( pcurrent->GetPrevious(), pcurrent, origin, dist ); + return NULL; + } + Vector dir = pcurrent->GetNext()->pev->origin - currentPos; + float length = dir.Length(); + if ( !length && !ValidPath( pcurrent->GetNext()->GetNext(), move ) ) + { + if ( dist == originalDist ) // HACK -- up against a dead end + return NULL; + return pcurrent; + } + if ( length > dist ) // enough left in this path to move + { + *origin = currentPos + (dir * (dist / length)); + return pcurrent; + } + else + { + dist -= length; + currentPos = pcurrent->GetNext()->pev->origin; + pcurrent = pcurrent->GetNext(); + *origin = currentPos; + } + } + *origin = currentPos; + } + + return pcurrent; +} + + +// Assumes this is ALWAYS enabled +CPathTrack *CPathTrack :: Nearest( Vector origin ) +{ + int deadCount; + float minDist, dist; + Vector delta; + CPathTrack *ppath, *pnearest; + + + delta = origin - pev->origin; + delta.z = 0; + minDist = delta.Length(); + pnearest = this; + ppath = GetNext(); + + // Hey, I could use the old 2 racing pointers solution to this, but I'm lazy :) + deadCount = 0; + while ( ppath && ppath != this ) + { + deadCount++; + if ( deadCount > 9999 ) + { + ALERT( at_error, "Bad sequence of path_tracks from %s", STRING(pev->targetname) ); + return NULL; + } + delta = origin - ppath->pev->origin; + delta.z = 0; + dist = delta.Length(); + if ( dist < minDist ) + { + minDist = dist; + pnearest = ppath; + } + ppath = ppath->GetNext(); + } + return pnearest; +} + + +CPathTrack *CPathTrack::Instance( edict_t *pent ) +{ + if ( FClassnameIs( pent, "path_track" ) ) + return (CPathTrack *)GET_PRIVATE(pent); + return NULL; +} + + + // DEBUGGING CODE +#if PATH_SPARKLE_DEBUG +void CPathTrack :: Sparkle( void ) +{ + + SetNextThink( 0.2 ); + if ( FBitSet( pev->spawnflags, SF_PATH_DISABLED ) ) + UTIL_ParticleEffect(pev->origin, Vector(0,0,100), 210, 10); + else + UTIL_ParticleEffect(pev->origin, Vector(0,0,100), 84, 10); +} +#endif + diff --git a/dlls/physcallback.h b/dlls/physcallback.h new file mode 100644 index 0000000..15a5d88 --- /dev/null +++ b/dlls/physcallback.h @@ -0,0 +1,56 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PHYSCALLBACK_H +#define PHYSCALLBACK_H +#pragma once + +#include "physint.h" + +// Must be provided by user of this code +extern server_physics_api_t g_physfuncs; + +// The actual physic callbacks +#define LINK_ENTITY (*g_physfuncs.pfnLinkEdict) +#define PHYSICS_TIME (*g_physfuncs.pfnGetServerTime) +#define HOST_FRAMETIME (*g_physfuncs.pfnGetFrameTime) +#define MODEL_HANDLE (*g_physfuncs.pfnGetModel) +#define GET_AREANODE (*g_physfuncs.pfnGetHeadnode) +#define GET_SERVER_STATE (*g_physfuncs.pfnServerState) +#define HOST_ERROR (*g_physfuncs.pfnHost_Error) +#define Tri (g_physfuncs.pTriAPI) +#define DrawConsoleString (*g_physfuncs.pfnDrawConsoleString) +#define DrawSetTextColor (*g_physfuncs.pfnDrawSetTextColor) +#define DrawConsoleStringLen (*g_physfuncs.pfnDrawConsoleStringLen) +#define WORLD_HANDLE() MODEL_HANDLE( 1 ) +#define GET_LIGHT_STYLE (*g_physfuncs.pfnGetLightStyle) +#define UPDATE_PACKED_FOG (*g_physfuncs.pfnUpdateFogSettings) +#define GET_FILES_LIST (*g_physfuncs.pfnGetFilesList) +#define TRACE_SURFACE (*g_physfuncs.pfnTraceSurface) +#define GET_TEXTURE_DATA (*g_physfuncs.pfnGetTextureData) + +// built-in memory manager +#define Mem_Alloc( x ) (*g_physfuncs.pfnMemAlloc)( x, __FILE__, __LINE__ ) +#define Mem_Free( x ) (*g_physfuncs.pfnMemFree)( x, __FILE__, __LINE__ ) +#define _Mem_Alloc (*g_physfuncs.pfnMemAlloc) +#define _Mem_Free (*g_physfuncs.pfnMemFree) + +#define MAP_CHECK_LUMP (*g_physfuncs.pfnCheckLump) +#define MAP_READ_LUMP (*g_physfuncs.pfnReadLump) +#define MAP_SAVE_LUMP (*g_physfuncs.pfnSaveLump) + +#define SAVE_FILE (*g_physfuncs.pfnSaveFile) +#define LOAD_IMAGE_PIXELS (*g_physfuncs.pfnLoadImagePixels) + +#endif //PHYSCALLBACK_H \ No newline at end of file diff --git a/dlls/physic.cpp b/dlls/physic.cpp new file mode 100644 index 0000000..e2659bb --- /dev/null +++ b/dlls/physic.cpp @@ -0,0 +1,328 @@ +/* +physic.cpp - common physics code +Copyright (C) 2011 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "client.h" +#include "nodes.h" +#include "decals.h" +#include "gamerules.h" +#include "game.h" +#include "com_model.h" +#include "features.h" +#include "triangleapi.h" +#include "render_api.h" +#include "pm_shared.h" +#include "pm_defs.h" +#include "trace.h" +#include "stringlib.h" +#include "weapons.h" + +unsigned int EngineSetFeatures( void ) +{ + unsigned int flags = (ENGINE_WRITE_LARGE_COORD|ENGINE_COMPUTE_STUDIO_LERP|ENGINE_LOAD_DELUXEDATA); + + if( g_iXashEngineBuildNumber >= 2148 ) + flags |= ENGINE_LARGE_LIGHTMAPS|ENGINE_DISABLE_HDTEXTURES; // required for right trace alpha-surfaces on studiomodels + + return flags; +} + +void Physic_SweepTest( CBaseEntity *pTouch, const Vector &start, const Vector &mins, const Vector &maxs, const Vector &end, trace_t *tr ) +{ + if( !pTouch->pev->modelindex || !GET_MODEL_PTR( pTouch->edict() )) + { + // bad model? + tr->allsolid = false; + return; + } + + Vector trace_mins, trace_maxs; + UTIL_MoveBounds( start, mins, maxs, end, trace_mins, trace_maxs ); + + // NOTE: pmove code completely ignore a bounds checking. So we need to do it here + if( !BoundsIntersect( trace_mins, trace_maxs, pTouch->pev->absmin, pTouch->pev->absmax )) + { + tr->allsolid = false; + return; + } + + CMeshDesc *bodyMesh = UTIL_GetCollisionMesh( pTouch->pev->modelindex ); + + if( !bodyMesh ) + { + // failed to build mesh for some reasons, so skip them + tr->allsolid = false; + return; + } + + mmesh_t *pMesh = bodyMesh->GetMesh(); + areanode_t *pHeadNode = bodyMesh->GetHeadNode(); + entvars_t *pev = pTouch->pev; + + if( !pMesh ) + { + // failed to build mesh for some reasons, so skip them + tr->allsolid = false; + return; + } + + TraceMesh trm; // a name like Doom3 :-) + + trm.SetTraceMesh( pMesh, pHeadNode, pTouch->pev->modelindex ); + trm.SetMeshOrientation( pev->origin, pev->angles, pev->startpos ); + trm.SetupTrace( start, mins, maxs, end, tr ); + + if( trm.DoTrace( )) + { + if( tr->fraction < 1.0f || tr->startsolid ) + tr->ent = pTouch->edict(); + tr->surf = trm.GetLastHitSurface(); + } +} + +void SV_ClipMoveToEntity( edict_t *ent, const float *start, float *mins, float *maxs, const float *end, trace_t *trace ) +{ + // convert edict_t to base entity + CBaseEntity *pTouch = CBaseEntity::Instance( ent ); + + if( !pTouch ) + { + // removed entity? + trace->allsolid = false; + return; + } + + Physic_SweepTest( pTouch, start, mins, maxs, end, trace ); +} + +void SV_ClipPMoveToEntity( physent_t *pe, const float *start, float *mins, float *maxs, const float *end, pmtrace_t *tr ) +{ + // convert physent_t to base entity + CBaseEntity *pTouch = CBaseEntity::Instance( INDEXENT( pe->info )); + trace_t trace; + + if( !pTouch ) + { + // removed entity? + tr->allsolid = false; + return; + } + + // make trace default + memset( &trace, 0, sizeof( trace )); + trace.allsolid = true; + trace.fraction = 1.0f; + trace.endpos = end; + + Physic_SweepTest( pTouch, start, mins, maxs, end, &trace ); + + // convert trace_t into pmtrace_t + memcpy( tr, &trace, 48 ); + tr->surf = trace.surf; + + if( trace.ent != NULL && PM_GetPlayerMove( )) + tr->ent = pe - PM_GetPlayerMove()->physents; + else tr->ent = -1; +} + +int SV_RestoreDecal( decallist_t *entry, edict_t *pEdict, qboolean adjacent ) +{ + int flags = entry->flags; + int entityIndex = ENTINDEX( pEdict ); + int cacheID = 0, modelIndex = 0; + Vector scale = g_vecZero; + + if( flags & FDECAL_STUDIO ) + { + if( FBitSet( pEdict->v.iuser1, CF_STATIC_ENTITY )) + { + cacheID = pEdict->v.colormap; + scale = pEdict->v.startpos; + } + + UTIL_RestoreStudioDecal( entry->position, entry->impactPlaneNormal, entityIndex, + pEdict->v.modelindex, entry->name, flags, &entry->studio_state, cacheID, scale ); + return TRUE; + } + + if( adjacent && entry->entityIndex != 0 && ( !pEdict || pEdict->free )) + { + TraceResult tr; + + ALERT( at_error, "couldn't restore entity index %i\n", entry->entityIndex ); + + Vector testspot = entry->position + entry->impactPlaneNormal * 5.0f; + Vector testend = entry->position + entry->impactPlaneNormal * -5.0f; + + UTIL_TraceLine( testspot, testend, ignore_monsters, NULL, &tr ); + + // NOTE: this code may does wrong result on moving brushes e.g. func_tracktrain + if( tr.flFraction != 1.0f && !tr.fAllSolid ) + { + // check impact plane normal + float dot = DotProduct( entry->impactPlaneNormal, tr.vecPlaneNormal ); + + if( dot >= 0.95f ) + { + entityIndex = ENTINDEX( tr.pHit ); + if( entityIndex > 0 ) modelIndex = tr.pHit->v.modelindex; + UTIL_RestoreCustomDecal( tr.vecEndPos, entry->impactPlaneNormal, entityIndex, modelIndex, entry->name, flags, entry->scale ); + } + } + } + else + { + // global entity is exist on new level so we can apply decal in local space + // NOTE: this case also used for transition world decals + UTIL_RestoreCustomDecal( entry->position, entry->impactPlaneNormal, entityIndex, pEdict->v.modelindex, entry->name, flags, entry->scale ); + } + + return TRUE; +} + +// point to create generic entities +int SV_CreateEntity( edict_t *pent, const char *szName ) +{ + if( !Q_strnicmp( szName, "ammo_", 5 )) + { + GetClassPtr(( CBasePlayerAmmo *)&pent->v ); // alloc private data + pent->v.netname = pent->v.classname; // member virtual name + pent->v.classname = MAKE_STRING( "ammo_generic" ); // to allow save\restore + + return 0; + } + else if( !Q_strnicmp( szName, "weapon_", 7 )) + { + GetClassPtr(( CBasePlayerItem *)&pent->v ); // alloc private data + pent->v.netname = pent->v.classname; // member virtual name + pent->v.classname = MAKE_STRING( "weapon_generic" ); // to allow save\restore + + return 0; + } + + return -1; +} + +int EngineAllowSaveGame( void ) +{ + return g_fAllowSaves; +} + +void SV_ProcessModelData( model_t *mod, qboolean create, const byte *buffer ) +{ + CRC32_t ulCrc; + + // g-cont. probably this is redundant :-) + if( !IS_DEDICATED_SERVER( )) + return; + + if( FBitSet( mod->flags, MODEL_WORLD )) + SV_ProcessWorldData( mod, create, buffer ); + + if( mod->type == mod_studio ) + { + if( create ) + { + studiohdr_t *src = (studiohdr_t *)buffer; + CRC32_INIT( &ulCrc ); + CRC32_PROCESS_BUFFER( &ulCrc, (byte *)buffer, src->length ); + mod->modelCRC = CRC32_FINAL( ulCrc ); + } + else + { + // release collision mesh + if( mod->bodymesh != NULL ) + { + mod->bodymesh->CMeshDesc::~CMeshDesc(); + Mem_Free( mod->bodymesh ); + mod->bodymesh = NULL; + } + } + } +} + +// +// Xash3D physics interface +// +static physics_interface_t gPhysicsInterface = +{ + SV_PHYSICS_INTERFACE_VERSION, + SV_CreateEntity, + NULL, + NULL, + NULL, + EngineAllowSaveGame, + NULL, // not needs + EngineSetFeatures, + NULL, + NULL, + NULL, + SV_ClipMoveToEntity, + SV_ClipPMoveToEntity, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + SV_RestoreDecal, + NULL, + SV_ProcessModelData, +}; + +int Server_GetPhysicsInterface( int iVersion, server_physics_api_t *pfuncsFromEngine, physics_interface_t *pFunctionTable ) +{ + if ( !pFunctionTable || !pfuncsFromEngine || iVersion != SV_PHYSICS_INTERFACE_VERSION ) + { + return FALSE; + } + + size_t iExportSize = sizeof( server_physics_api_t ); + size_t iImportSize = sizeof( physics_interface_t ); + + // NOTE: the very old versions NOT have information about current build in any case + if( g_iXashEngineBuildNumber <= 1910 ) + { + if( g_fXashEngine ) + ALERT( at_console, "very oldest version of Xash3D was detected. Engine features was disabled.\n" ); + + // interface sizes for build 1905 and older + iExportSize = 28; + iImportSize = 24; + } + + if( g_iXashEngineBuildNumber <= 3584 ) + { + if( g_fXashEngine ) + ALERT( at_console, "old version of Xash3D was detected. Some engine features was disabled.\n" ); + + // interface sizes for build 3584 and older + iExportSize = 80; + iImportSize = 80; + } + + // copy new physics interface + memcpy( &g_physfuncs, pfuncsFromEngine, iExportSize ); + + // fill engine callbacks + memcpy( pFunctionTable, &gPhysicsInterface, iImportSize ); + + g_fPhysicInitialized = TRUE; + + return TRUE; +} \ No newline at end of file diff --git a/dlls/plane.cpp b/dlls/plane.cpp new file mode 100644 index 0000000..39a91c3 --- /dev/null +++ b/dlls/plane.cpp @@ -0,0 +1,60 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "plane.h" + +//========================================================= +// Plane +//========================================================= +CPlane :: CPlane ( void ) +{ + m_fInitialized = FALSE; +} + +//========================================================= +// InitializePlane - Takes a normal for the plane and a +// point on the plane and +//========================================================= +void CPlane :: InitializePlane ( const Vector &vecNormal, const Vector &vecPoint ) +{ + m_vecNormal = vecNormal; + m_flDist = DotProduct ( m_vecNormal, vecPoint ); + m_fInitialized = TRUE; +} + + +//========================================================= +// PointInFront - determines whether the given vector is +// in front of the plane. +//========================================================= +BOOL CPlane :: PointInFront ( const Vector &vecPoint ) +{ + float flFace; + + if ( !m_fInitialized ) + { + return FALSE; + } + + flFace = DotProduct ( m_vecNormal, vecPoint ) - m_flDist; + + if ( flFace >= 0 ) + { + return TRUE; + } + + return FALSE; +} + diff --git a/dlls/plane.h b/dlls/plane.h new file mode 100644 index 0000000..af70f1c --- /dev/null +++ b/dlls/plane.h @@ -0,0 +1,43 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PLANE_H +#define PLANE_H + +//========================================================= +// Plane +//========================================================= +class CPlane +{ +public: + CPlane ( void ); + + //========================================================= + // InitializePlane - Takes a normal for the plane and a + // point on the plane and + //========================================================= + void InitializePlane ( const Vector &vecNormal, const Vector &vecPoint ); + + //========================================================= + // PointInFront - determines whether the given vector is + // in front of the plane. + //========================================================= + BOOL PointInFront ( const Vector &vecPoint ); + + Vector m_vecNormal; + float m_flDist; + BOOL m_fInitialized; +}; + +#endif // PLANE_H diff --git a/dlls/plats.cpp b/dlls/plats.cpp new file mode 100644 index 0000000..58618a6 --- /dev/null +++ b/dlls/plats.cpp @@ -0,0 +1,3286 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== plats.cpp ======================================================== + + spawn, think, and touch functions for trains, etc + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "trains.h" +#include "saverestore.h" +#include "movewith.h" + +static void PlatSpawnInsideTrigger(entvars_t* pevPlatform); + +static float Fix( float angle ) +{ + if ( IS_NAN(angle) ) + { + ALERT(at_debug, "NaN error during Fix!\n"); + return angle; + } + while ( angle < 0 ) + angle += 360; + while ( angle > 360 ) + angle -= 360; + + return angle; +} + + +static void FixupAngles( Vector &v ) +{ + v.x = Fix( v.x ); + v.y = Fix( v.y ); + v.z = Fix( v.z ); +} + +class CFuncTrain; + +//LRC - scripted_trainsequence +class CTrainSequence : public CBaseEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EndThink( void ); + void TimeOutThink( void ); + void KeyValue( KeyValueData *pkvd ); + STATE GetState( void ) { return (m_pTrain||m_pTrackTrain)?STATE_ON:STATE_OFF; } + virtual int ObjectCaps( void ); + + void StopSequence( void ); + void ArrivalNotify( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + string_t m_iszEntity; + string_t m_iszDestination; + string_t m_iszTerminate; + int m_iDirection; + int m_iPostDirection; + float m_fDuration; +// at any given time, at most one of these pointers will be set. + CFuncTrain *m_pTrain; + CFuncTrackTrain *m_pTrackTrain; + + CBaseEntity *m_pDestination; +}; + + + +#define SF_PLAT_TOGGLE 0x0001 + +class CBasePlatTrain : public CBaseToggle +{ +public: + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + void KeyValue( KeyValueData* pkvd); + void Precache( void ); + + // This is done to fix spawn flag collisions between this class and a derived class + virtual BOOL IsTogglePlat( void ) { return (pev->spawnflags & SF_PLAT_TOGGLE) ? TRUE : FALSE; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + BYTE m_bMoveSnd; // sound a plat makes while moving + BYTE m_bStopSnd; // sound a plat makes when it stops + float m_volume; // Sound volume +}; + +TYPEDESCRIPTION CBasePlatTrain::m_SaveData[] = +{ + DEFINE_FIELD( CBasePlatTrain, m_bMoveSnd, FIELD_CHARACTER ), + DEFINE_FIELD( CBasePlatTrain, m_bStopSnd, FIELD_CHARACTER ), + DEFINE_FIELD( CBasePlatTrain, m_volume, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CBasePlatTrain, CBaseToggle ); + +void CBasePlatTrain :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "lip")) + { + m_flLip = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "height")) + { + m_flHeight = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "rotation")) + { + m_vecFinalAngle.x = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "movesnd")) + { + m_bMoveSnd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "stopsnd")) + { + m_bStopSnd = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "custommovesnd")) + { + pev->noise = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "customstopsnd")) + { + pev->noise1 = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "volume")) + { + m_volume = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +#define noiseMoving noise +#define noiseArrived noise1 + +void CBasePlatTrain::Precache( void ) +{ +// set the plat's "in-motion" sound + if (FStringNull(pev->noiseMoving)) + { + switch (m_bMoveSnd) + { + case 1: + pev->noiseMoving = MAKE_STRING("plats/bigmove1.wav"); + break; + case 2: + pev->noiseMoving = MAKE_STRING("plats/bigmove2.wav"); + break; + case 3: + pev->noiseMoving = MAKE_STRING("plats/elevmove1.wav"); + break; + case 4: + pev->noiseMoving = MAKE_STRING("plats/elevmove2.wav"); + break; + case 5: + pev->noiseMoving = MAKE_STRING("plats/elevmove3.wav"); + break; + case 6: + pev->noiseMoving = MAKE_STRING("plats/freightmove1.wav"); + break; + case 7: + pev->noiseMoving = MAKE_STRING("plats/freightmove2.wav"); + break; + case 8: + pev->noiseMoving = MAKE_STRING("plats/heavymove1.wav"); + break; + case 9: + pev->noiseMoving = MAKE_STRING("plats/rackmove1.wav"); + break; + case 10: + pev->noiseMoving = MAKE_STRING("plats/railmove1.wav"); + break; + case 11: + pev->noiseMoving = MAKE_STRING("plats/squeekmove1.wav"); + break; + case 12: + pev->noiseMoving = MAKE_STRING("plats/talkmove1.wav"); + break; + case 13: + pev->noiseMoving = MAKE_STRING("plats/talkmove2.wav"); + break; + default: + pev->noiseMoving = MAKE_STRING("common/null.wav"); + break; + } + } + PRECACHE_SOUND ((char*)STRING(pev->noiseMoving)); + +// set the plat's 'reached destination' stop sound + if (FStringNull(pev->noiseArrived)) + { + switch (m_bStopSnd) + { + case 1: + pev->noiseArrived = MAKE_STRING("plats/bigstop1.wav"); + break; + case 2: + pev->noiseArrived = MAKE_STRING("plats/bigstop2.wav"); + break; + case 3: + pev->noiseArrived = MAKE_STRING("plats/freightstop1.wav"); + break; + case 4: + pev->noiseArrived = MAKE_STRING("plats/heavystop2.wav"); + break; + case 5: + pev->noiseArrived = MAKE_STRING("plats/rackstop1.wav"); + break; + case 6: + pev->noiseArrived = MAKE_STRING("plats/railstop1.wav"); + break; + case 7: + pev->noiseArrived = MAKE_STRING("plats/squeekstop1.wav"); + break; + case 8: + pev->noiseArrived = MAKE_STRING("plats/talkstop1.wav"); + break; + default: + pev->noiseArrived = MAKE_STRING("common/null.wav"); + break; + } + } + PRECACHE_SOUND ((char*)STRING(pev->noiseArrived)); +} + +// +//====================== PLAT code ==================================================== +// + + +#define noiseMovement noise +#define noiseStopMoving noise1 + +class CFuncPlat : public CBasePlatTrain +{ +public: + void Spawn( void ); + void Precache( void ); + void Setup( void ); + + virtual void Blocked( CBaseEntity *pOther ); + + void EXPORT PlatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + void EXPORT CallGoUp( void ) { GoUp(); } + void EXPORT CallGoDown( void ) { GoDown(); } + void EXPORT CallHitTop( void ) { HitTop(); } + void EXPORT CallHitBottom( void ) { HitBottom(); } + + virtual void GoUp( void ); + virtual void GoDown( void ); + virtual void HitTop( void ); + virtual void HitBottom( void ); +}; +LINK_ENTITY_TO_CLASS( func_plat, CFuncPlat ); + + +// UNDONE: Need to save this!!! It needs class & linkage +class CPlatTrigger : public CBaseEntity +{ +public: + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DONT_SAVE; } + void SpawnInsideTrigger( CFuncPlat *pPlatform ); + void Touch( CBaseEntity *pOther ); + CFuncPlat *m_pPlatform; +}; + + + +/*QUAKED func_plat (0 .5 .8) ? PLAT_LOW_TRIGGER +speed default 150 + +Plats are always drawn in the extended position, so they will light correctly. + +If the plat is the target of another trigger or button, it will start out disabled in +the extended position until it is trigger, when it will lower and become a normal plat. + +If the "height" key is set, that will determine the amount the plat moves, instead of +being implicitly determined by the model's height. + +Set "sounds" to one of the following: +1) base fast +2) chain slow +*/ + +void CFuncPlat :: Setup( void ) +{ + //pev->noiseMovement = MAKE_STRING("plats/platmove1.wav"); + //pev->noiseStopMoving = MAKE_STRING("plats/platstop1.wav"); + + if (m_flTLength == 0) + m_flTLength = 80; + if (m_flTWidth == 0) + m_flTWidth = 10; + + pev->angles = g_vecZero; + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(this, pev->origin); // set size and link into world + UTIL_SetSize(pev, pev->mins, pev->maxs); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + // vecPosition1 is the top position, vecPosition2 is the bottom + if (m_pMoveWith) + m_vecPosition1 = pev->origin - m_pMoveWith->pev->origin; + else + m_vecPosition1 = pev->origin; + m_vecPosition2 = m_vecPosition1; + if (m_flHeight != 0) + m_vecPosition2.z = m_vecPosition2.z - m_flHeight; + else + m_vecPosition2.z = m_vecPosition2.z - pev->size.z + 8; + if (pev->speed == 0) + pev->speed = 150; + + if ( m_volume == 0 ) + m_volume = 0.85; +} + + +void CFuncPlat :: Precache( ) +{ + CBasePlatTrain::Precache(); + //PRECACHE_SOUND("plats/platmove1.wav"); + //PRECACHE_SOUND("plats/platstop1.wav"); + if ( !IsTogglePlat() ) + PlatSpawnInsideTrigger( pev ); // the "start moving" trigger +} + + +void CFuncPlat :: Spawn( ) +{ + Setup(); + + Precache(); + + // If this platform is the target of some button, it starts at the TOP position, + // and is brought down by that button. Otherwise, it starts at BOTTOM. + if ( !FStringNull(pev->targetname) ) + { + if (m_pMoveWith) + UTIL_AssignOrigin (this, m_vecPosition1 + m_pMoveWith->pev->origin); + else + UTIL_AssignOrigin (this, m_vecPosition1); + m_toggle_state = TS_AT_TOP; + SetUse(&CFuncPlat :: PlatUse ); + } + else + { + if (m_pMoveWith) + UTIL_AssignOrigin (this, m_vecPosition2 + m_pMoveWith->pev->origin); + else + UTIL_AssignOrigin (this, m_vecPosition2); + m_toggle_state = TS_AT_BOTTOM; + } +} + + + +static void PlatSpawnInsideTrigger(entvars_t* pevPlatform) +{ + GetClassPtr( (CPlatTrigger *)NULL)->SpawnInsideTrigger( GetClassPtr( (CFuncPlat *)pevPlatform ) ); +} + + +// +// Create a trigger entity for a platform. +// +void CPlatTrigger :: SpawnInsideTrigger( CFuncPlat *pPlatform ) +{ + m_pPlatform = pPlatform; + // Create trigger entity, "point" it at the owning platform, give it a touch method + pev->solid = SOLID_TRIGGER; + pev->movetype = MOVETYPE_NONE; + pev->origin = pPlatform->pev->origin; + + // Establish the trigger field's size + Vector vecTMin = m_pPlatform->pev->mins + Vector ( 25 , 25 , 0 ); + Vector vecTMax = m_pPlatform->pev->maxs + Vector ( 25 , 25 , 8 ); + vecTMin.z = vecTMax.z - ( m_pPlatform->m_vecPosition1.z - m_pPlatform->m_vecPosition2.z + 8 ); + if (m_pPlatform->pev->size.x <= 50) + { + vecTMin.x = (m_pPlatform->pev->mins.x + m_pPlatform->pev->maxs.x) / 2; + vecTMax.x = vecTMin.x + 1; + } + if (m_pPlatform->pev->size.y <= 50) + { + vecTMin.y = (m_pPlatform->pev->mins.y + m_pPlatform->pev->maxs.y) / 2; + vecTMax.y = vecTMin.y + 1; + } + UTIL_SetSize ( pev, vecTMin, vecTMax ); +} + + +// +// When the platform's trigger field is touched, the platform ??? +// +void CPlatTrigger :: Touch( CBaseEntity *pOther ) +{ + // Ignore touches by non-players + entvars_t* pevToucher = pOther->pev; + if ( !FClassnameIs (pevToucher, "player") ) + return; + + // Ignore touches by corpses + if (!pOther->IsAlive()) + return; + + // Make linked platform go up/down. + if (m_pPlatform->m_toggle_state == TS_AT_BOTTOM) + m_pPlatform->GoUp(); + else if (m_pPlatform->m_toggle_state == TS_AT_TOP) + m_pPlatform->SetNextThink( 1 );// delay going down +} + + +// +// Used by SUB_UseTargets, when a platform is the target of a button. +// Start bringing platform down. +// +void CFuncPlat :: PlatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( IsTogglePlat() ) + { + // Top is off, bottom is on + BOOL on = (m_toggle_state == TS_AT_BOTTOM) ? TRUE : FALSE; + + if ( !ShouldToggle( useType, on ) ) + return; + + if (m_toggle_state == TS_AT_TOP) + { + SetNextThink( 0.01 ); + SetThink(&CFuncPlat :: CallGoDown ); + } + else if ( m_toggle_state == TS_AT_BOTTOM ) + { + SetNextThink( 0.01 ); + SetThink(&CFuncPlat :: CallGoUp ); + } + } + else + { + SetUse( NULL ); + + if (m_toggle_state == TS_AT_TOP) + { + SetNextThink( 0.01 ); + SetThink(&CFuncPlat :: CallGoDown ); + } + } +} + + +// +// Platform is at top, now starts moving down. +// +void CFuncPlat :: GoDown( void ) +{ + if(pev->noiseMovement) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_AT_TOP || m_toggle_state == TS_GOING_UP); + m_toggle_state = TS_GOING_DOWN; + SetMoveDone(&CFuncPlat ::CallHitBottom); + LinearMove(m_vecPosition2, pev->speed); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncPlat :: HitBottom( void ) +{ + if(pev->noiseMovement) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement)); + + if (pev->noiseStopMoving) + EMIT_SOUND(ENT(pev), CHAN_WEAPON, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_AT_BOTTOM; +} + + +// +// Platform is at bottom, now starts moving up +// +void CFuncPlat :: GoUp( void ) +{ + if (pev->noiseMovement) + EMIT_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_AT_BOTTOM || m_toggle_state == TS_GOING_DOWN); + m_toggle_state = TS_GOING_UP; + SetMoveDone(&CFuncPlat ::CallHitTop); + LinearMove(m_vecPosition1, pev->speed); +} + + +// +// Platform has hit top. Pauses, then starts back down again. +// +void CFuncPlat :: HitTop( void ) +{ + if(pev->noiseMovement) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement)); + + if (pev->noiseStopMoving) + EMIT_SOUND(ENT(pev), CHAN_WEAPON, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + + ASSERT(m_toggle_state == TS_GOING_UP); + m_toggle_state = TS_AT_TOP; + + if ( !IsTogglePlat() ) + { + // After a delay, the platform will automatically start going down again. + SetThink(&CFuncPlat :: CallGoDown ); + SetNextThink( 3 ); + } +} + + +void CFuncPlat :: Blocked( CBaseEntity *pOther ) +{ + ALERT( at_aiconsole, "%s Blocked by %s\n", STRING(pev->classname), STRING(pOther->pev->classname) ); + // Hurt the blocker a little + pOther->TakeDamage(pev, pev, 1, DMG_CRUSH); + + if(pev->noiseMovement) + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement)); + + // Send the platform back where it came from + ASSERT(m_toggle_state == TS_GOING_UP || m_toggle_state == TS_GOING_DOWN); + if (m_toggle_state == TS_GOING_UP) + { + SetNextThink( 0 ); + SetThink(&CFuncPlat :: GoDown ); + } + else if (m_toggle_state == TS_GOING_DOWN) + { + SetNextThink( 0 ); + SetThink(&CFuncPlat :: GoUp ); + } +} + + +class CFuncPlatRot : public CFuncPlat +{ +public: + void Spawn( void ); + void SetupRotation( void ); + virtual void KeyValue( KeyValueData *pkvd ); + + virtual void GoUp( void ); + virtual void GoDown( void ); + virtual void HitTop( void ); + virtual void HitBottom( void ); + + void RotMove( Vector &destAngle, float time ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + Vector m_end, m_start; +}; +LINK_ENTITY_TO_CLASS( func_platrot, CFuncPlatRot ); +TYPEDESCRIPTION CFuncPlatRot::m_SaveData[] = +{ + DEFINE_FIELD( CFuncPlatRot, m_end, FIELD_VECTOR ), + DEFINE_FIELD( CFuncPlatRot, m_start, FIELD_VECTOR ), +}; + +IMPLEMENT_SAVERESTORE( CFuncPlatRot, CFuncPlat ); + +void CFuncPlatRot::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "axes")) + { + UTIL_StringToVector((float*)(pev->movedir), pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CFuncPlat::KeyValue( pkvd ); +} + +void CFuncPlatRot :: SetupRotation( void ) +{ + if ( m_vecFinalAngle.x != 0 ) // This plat rotates too! + { + CBaseToggle :: AxisDir( pev ); + m_start = pev->angles; + m_end = pev->angles + pev->movedir * m_vecFinalAngle.x; + } + else + { + m_start = g_vecZero; + m_end = g_vecZero; + } + if ( !FStringNull(pev->targetname) ) // Start at top + { + UTIL_SetAngles(this, m_end); + //pev->angles = m_end; + } +} + + +void CFuncPlatRot :: Spawn( void ) +{ + CFuncPlat :: Spawn(); + SetupRotation(); +} + +void CFuncPlatRot :: GoDown( void ) +{ + CFuncPlat :: GoDown(); + + Vector vecDest; + if (m_pMoveWith) + { + vecDest = m_vecFinalDest + m_pMoveWith->pev->origin; + } + else + vecDest = m_vecFinalDest; + Vector vecDestDelta = vecDest - pev->origin; + float flTravelTime = vecDestDelta.Length() / m_flLinearMoveSpeed; + + RotMove( m_start, flTravelTime ); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncPlatRot :: HitBottom( void ) +{ + CFuncPlat :: HitBottom(); + UTIL_SetAvelocity(this, g_vecZero); + //pev->avelocity = g_vecZero; + UTIL_SetAngles(this, m_start); + //pev->angles = m_start; +} + + +// +// Platform is at bottom, now starts moving up +// +void CFuncPlatRot :: GoUp( void ) +{ + CFuncPlat :: GoUp(); + + Vector vecDest; + if (m_pMoveWith) + { + vecDest = m_vecFinalDest + m_pMoveWith->pev->origin; + } + else + vecDest = m_vecFinalDest; + Vector vecDestDelta = vecDest - pev->origin; + float flTravelTime = vecDestDelta.Length() / m_flLinearMoveSpeed; + + RotMove( m_end, flTravelTime ); +} + + +// +// Platform has hit top. Pauses, then starts back down again. +// +void CFuncPlatRot :: HitTop( void ) +{ + CFuncPlat :: HitTop(); + UTIL_SetAvelocity(this, g_vecZero); + //pev->avelocity = g_vecZero; + UTIL_SetAngles(this, m_end); + //pev->angles = m_end; +} + + +void CFuncPlatRot :: RotMove( Vector &destAngle, float time ) +{ + // set destdelta to the vector needed to move + Vector vecDestDelta = destAngle - pev->angles; + + // Travel time is so short, we're practically there already; make it so. + if ( time >= 0.1) + { + UTIL_SetAvelocity(this, vecDestDelta / time); + //pev->avelocity = vecDestDelta / time; + } + else + { + UTIL_SetAvelocity(this, vecDestDelta); + //pev->avelocity = vecDestDelta; + SetNextThink( 1 ); + } +} + + +// +//====================== TRAIN code ================================================== +// + +//the others are defined in const.h +// SF_TRAIN_WAIT_RETRIGGER 1 +#define SF_TRAIN_SETORIGIN 2 +// SF_TRAIN_START_ON 4 // Train is initially moving +// SF_TRAIN_PASSABLE 8 // Train is not solid -- used to make water trains +#define SF_TRAIN_REVERSE 0x800000 + +class CFuncTrain : public CBasePlatTrain +{ +public: + void Spawn( void ); + void Precache( void ); + void PostSpawn( void ); + void OverrideReset( void ); + + void Blocked( CBaseEntity *pOther ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + //LRC + void StartSequence(CTrainSequence *pSequence); + void StopSequence( ); + CTrainSequence *m_pSequence; + + void EXPORT Wait( void ); + void EXPORT Next( void ); + void EXPORT ThinkDoNext( void ); + void EXPORT SoundSetup( void ); + + STATE GetState( void ) { return m_iState; } + + virtual void ThinkCorrection( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + entvars_t *m_pevCurrentTarget; + int m_sounds; +//LRC - now part of CBaseEntity: BOOL m_activated; + STATE m_iState; + float m_fStoredThink; + Vector m_vecAvelocity; +}; + +LINK_ENTITY_TO_CLASS( func_train, CFuncTrain ); +TYPEDESCRIPTION CFuncTrain::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTrain, m_sounds, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrain, m_pevCurrentTarget, FIELD_EVARS ), +//LRC - now part of CBaseEntity: DEFINE_FIELD( CFuncTrain, m_activated, FIELD_BOOLEAN ), + DEFINE_FIELD( CFuncTrain, m_iState, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrain, m_fStoredThink, FIELD_TIME ), + DEFINE_FIELD( CFuncTrain, m_pSequence, FIELD_CLASSPTR ), //LRC + DEFINE_FIELD( CFuncTrain, m_vecAvelocity, FIELD_VECTOR ), //LRC +}; + +IMPLEMENT_SAVERESTORE( CFuncTrain, CBasePlatTrain ); + + +void CFuncTrain :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBasePlatTrain::KeyValue( pkvd ); +} + + +void CFuncTrain :: Blocked( CBaseEntity *pOther ) +{ + // Keep "movewith" entities in line + UTIL_AssignOrigin(this, pev->origin); + + if ( gpGlobals->time < m_flActivateFinished) + return; + + m_flActivateFinished = gpGlobals->time + 0.5; + + if (pev->dmg) + pOther->TakeDamage(pev, pev, pev->dmg, DMG_CRUSH); +} + + +void CFuncTrain :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( ShouldToggle( useType ) ) + { + if (pev->spawnflags & SF_TRAIN_WAIT_RETRIGGER) + { + // Move toward my target +// ALERT(at_console, "Unset Retrigger (use)\n"); + pev->spawnflags &= ~SF_TRAIN_WAIT_RETRIGGER; + Next(); + } + else + { +// ALERT(at_console, "Set Retrigger (use)\n"); + pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER; + // Pop back to last target if it's available + if ( pev->enemy ) + pev->target = pev->enemy->v.targetname; + + DontThink(); + UTIL_SetVelocity(this, g_vecZero); + UTIL_SetAvelocity(this, g_vecZero); + m_iState = STATE_OFF; +// pev->velocity = g_vecZero; + + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + } + } +} + + +void CFuncTrain :: Wait( void ) +{ +// ALERT(at_console, "Wait t %s, m %s\n", STRING(pev->target), STRING(pev->message)); + if (m_pSequence) + m_pSequence->ArrivalNotify(); + + // Fire the pass target if there is one + if ( m_pevCurrentTarget->message ) + { + FireTargets( STRING(m_pevCurrentTarget->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( m_pevCurrentTarget->spawnflags, SF_CORNER_FIREONCE ) ) + m_pevCurrentTarget->message = 0; + } + + // need pointer to LAST target. + if ( FBitSet (m_pevCurrentTarget->spawnflags , SF_TRAIN_WAIT_RETRIGGER ) || ( pev->spawnflags & SF_TRAIN_WAIT_RETRIGGER ) ) + { +// if (FBitSet (m_pevCurrentTarget->spawnflags , SF_TRAIN_WAIT_RETRIGGER )) +// ALERT(at_console, "Wait: wait for retrigger from path %s\n", STRING(m_pevCurrentTarget->targetname)); +// else +// ALERT(at_console, "Wait: wait for retrigger from train\n"); + pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER; + m_iState = STATE_OFF; + // clear the sound channel. + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + + UTIL_SetVelocity(this, g_vecZero); + UTIL_SetAvelocity(this, g_vecZero); + DontThink(); + return; + } + + // ALERT ( at_console, "%f\n", m_flWait ); + + if (m_flWait != 0) + {// -1 wait will wait forever! + m_iState = STATE_OFF; + UTIL_SetAvelocity(this, g_vecZero); + UTIL_SetVelocity(this, g_vecZero); + SetNextThink( m_flWait ); + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + SetThink(&CFuncTrain :: Next ); +// ALERT(at_console, "Wait: doing Next in %f\n", m_flWait); + } + else + { +// ALERT(at_console, "Wait: doing Next now\n"); + Next();// do it right now! + } +} + + +// +// Train next - path corner needs to change to next target +// +void CFuncTrain :: Next( void ) +{ + CBaseEntity *pTarg; + + // now find our next target + pTarg = GetNextTarget(); + + if ( !pTarg ) + { + // no destination, just come to a halt + m_iState = STATE_OFF; + UTIL_SetVelocity(this, g_vecZero); + UTIL_SetAvelocity(this, g_vecZero); + + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + // Play stop sound + if ( pev->noiseStopMoving ) + EMIT_SOUND (ENT(pev), CHAN_VOICE, (char*)STRING(pev->noiseStopMoving), m_volume, ATTN_NORM); + + return; + } + + // Save last target in case we need to find it again + pev->message = pev->target; + +// if (m_pevCurrentTarget) +// ALERT(at_console, "Next, pTarg %s, pevTarg %s\n", STRING(pTarg->pev->targetname), STRING(m_pevCurrentTarget->targetname)); +// else +// ALERT(at_console, "Next, pTarg %s, pevTarg null\n", STRING(pTarg->pev->targetname)); + + if (pev->spawnflags & SF_TRAIN_REVERSE && m_pSequence) + { + //LRC - search backwards + CBaseEntity *pSearch = m_pSequence->m_pDestination; + while (pSearch) + { + if (FStrEq(STRING(pSearch->pev->target), STRING(pev->target))) + { + // pSearch leads to the current corner, so it's the next thing we're moving to. + pev->target = pSearch->pev->targetname; +// ALERT(at_console, "Next, pSearch %s\n", STRING(pSearch->pev->targetname)); + break; + } + pSearch = pSearch->GetNextTarget(); + } + } + else + { + pev->target = pTarg->pev->target; + } + +// ALERT(at_console, "Next, new pevtarget %s, new message %s\n", STRING(pev->target), STRING(pev->message)); + + m_flWait = pTarg->GetDelay(); + + // don't copy speed from target if it is 0 (uninitialized) + if ( m_pevCurrentTarget ) + { + if ( m_pevCurrentTarget->speed != 0 ) + { + switch ((int)(m_pevCurrentTarget->armortype)) + { + case PATHSPEED_SET: + pev->speed = m_pevCurrentTarget->speed; + ALERT( at_aiconsole, "Train %s speed set to %4.2f\n", STRING(pev->targetname), pev->speed ); + break; + case PATHSPEED_ACCEL: + pev->speed += m_pevCurrentTarget->speed; + ALERT( at_aiconsole, "Train %s speed accel to %4.2f\n", STRING(pev->targetname), pev->speed ); + break; + case PATHSPEED_TIME: + float distance; + if (pev->spawnflags & SF_TRAIN_SETORIGIN) + distance = (pev->origin - pTarg->pev->origin).Length(); + else + distance = (pev->origin - (pTarg->pev->origin - (pev->mins + pev->maxs) * 0.5)).Length(); + + pev->speed = distance / m_pevCurrentTarget->speed; + ALERT( at_aiconsole, "Train %s speed to %4.2f (timed)\n", STRING(pev->targetname), pev->speed ); + break; + } + } + + if (m_pevCurrentTarget->spawnflags & SF_CORNER_AVELOCITY) + { + m_vecAvelocity = pTarg->pev->avelocity; + UTIL_SetAvelocity(this, m_vecAvelocity); + //pev->avelocity = pTarg->pev->avelocity; //LRC + } + + if (m_pevCurrentTarget->armorvalue) + { + UTIL_SetAngles(this, m_pevCurrentTarget->angles); + //pev->angles = m_pevCurrentTarget->angles; //LRC - if we just passed a "turn to face" corner, set angle exactly. + } + } + + m_pevCurrentTarget = pTarg->pev;// keep track of this since path corners change our target for us. + + pev->enemy = pTarg->edict();//hack + + if( FBitSet(pTarg->pev->spawnflags, SF_CORNER_TELEPORT) ) //LRC - cosmetic change to use pTarg + { + // Path corner has indicated a teleport to the next corner. + SetBits(pev->effects, EF_NOINTERP); + if (m_pMoveWith) + { + if (pev->spawnflags & SF_TRAIN_SETORIGIN) + UTIL_AssignOrigin(this, pTarg->pev->origin - m_pMoveWith->pev->origin ); + else + UTIL_AssignOrigin(this, pTarg->pev->origin - (pev->mins + pev->maxs) * 0.5 - m_pMoveWith->pev->origin ); + } + else + { + if (pev->spawnflags & SF_TRAIN_SETORIGIN) + UTIL_AssignOrigin(this, pTarg->pev->origin ); + else + UTIL_AssignOrigin(this, pTarg->pev->origin - (pev->mins + pev->maxs) * 0.5 ); + } + + if (pTarg->pev->armorvalue) //LRC - "teleport and turn to face" means you set an angle as you teleport. + { + UTIL_SetAngles(this, pTarg->pev->angles); + //pev->angles = pTarg->pev->angles; + } + + Wait(); // Get on with doing the next path corner. + } + else + { + // Normal linear move. + + // CHANGED this from CHAN_VOICE to CHAN_STATIC around OEM beta time because trains should + // use CHAN_STATIC for their movement sounds to prevent sound field problems. + // this is not a hack or temporary fix, this is how things should be. (sjb). + if ( pev->noiseMovement ) + STOP_SOUND( edict(), CHAN_STATIC, (char*)STRING(pev->noiseMovement) ); + if ( pev->noiseMovement ) + EMIT_SOUND (ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement), m_volume, ATTN_NORM); + + ClearBits(pev->effects, EF_NOINTERP); + SetMoveDone(&CFuncTrain :: Wait ); + + if (pTarg->pev->armorvalue) //LRC - "turn to face" the next corner + { + Vector vTemp = pev->angles; + FixupAngles( vTemp ); + UTIL_SetAngles(this, vTemp); + Vector oDelta = pTarg->pev->origin - pev->origin; + Vector aDelta = pTarg->pev->angles - pev->angles; + float timeTaken = oDelta.Length() / pev->speed; + m_vecAvelocity = aDelta / timeTaken; + //pev->avelocity = aDelta / timeTaken; + } + + UTIL_SetAvelocity(this, m_vecAvelocity); + + m_iState = STATE_ON; + + if (m_pMoveWith) + { + if (pev->spawnflags & SF_TRAIN_SETORIGIN) + LinearMove( pTarg->pev->origin - m_pMoveWith->pev->origin, pev->speed ); + else + LinearMove (pTarg->pev->origin - (pev->mins + pev->maxs)* 0.5 - m_pMoveWith->pev->origin, pev->speed); + } + else + { + if (pev->spawnflags & SF_TRAIN_SETORIGIN) + LinearMove( pTarg->pev->origin, pev->speed ); + else + LinearMove (pTarg->pev->origin - (pev->mins + pev->maxs)* 0.5, pev->speed); + } + +// ALERT(at_console, "Next: LMove done\n"); +// ALERT(at_console, "Next ends, nextthink %f, flags %f\n", pev->nextthink, m_iLFlags); + } +} + +//LRC- called by Activate. (but not when a game is loaded.) +void CFuncTrain :: PostSpawn( void ) +{ + CBaseEntity *pTarget = UTIL_FindEntityByTargetname (NULL, STRING(pev->target) ); + entvars_t *pevTarg; + + m_iState = STATE_OFF; + + if (pTarget) + { + pevTarg = pTarget->pev; + } + else + { + ALERT(at_debug, "Missing train target \"%s\"\n", STRING(pev->target)); + return; + } + + pev->message = pevTarg->targetname; //LRC - record the old target so that we can find it again + pev->target = pevTarg->target; + m_pevCurrentTarget = pevTarg;// keep track of this since path corners change our target for us. + + if (pev->avelocity != g_vecZero) + { + m_vecAvelocity = pev->avelocity; + UTIL_SetAvelocity(this, g_vecZero); + } + + if (pev->spawnflags & SF_TRAIN_SETORIGIN) + { + m_vecSpawnOffset = m_vecSpawnOffset + pevTarg->origin - pev->origin; + if (m_pMoveWith) + UTIL_AssignOrigin (this, pevTarg->origin - m_pMoveWith->pev->origin ); + else + UTIL_AssignOrigin (this, pevTarg->origin ); + } + else + { + m_vecSpawnOffset = m_vecSpawnOffset + (pevTarg->origin - (pev->mins + pev->maxs) * 0.5) - pev->origin; + if (m_pMoveWith) + UTIL_AssignOrigin (this, pevTarg->origin - (pev->mins + pev->maxs) * 0.5 - m_pMoveWith->pev->origin ); + else + UTIL_AssignOrigin (this, pevTarg->origin - (pev->mins + pev->maxs) * 0.5 ); + } + + if ( FStringNull(pev->targetname) || pev->spawnflags & SF_TRAIN_START_ON) + { // not triggered, so start immediately + SetNextThink( 1.5 ); +// SetThink( Next ); + SetThink(&CFuncTrain :: ThinkDoNext ); + } + else + { +// ALERT(at_console, "Set Retrigger (postspawn)\n"); + pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER; + } + +// ALERT(at_console, "func_train postspawn: origin %f %f %f\n", pev->origin.x, pev->origin.y, pev->origin.z); +} + +void CFuncTrain :: ThinkDoNext( void ) +{ + SetNextThink( 0.1 ); +// ALERT(at_console, "TDN "); + if (gpGlobals->time != 1.0) // only go on if the game has properly started yet + SetThink(&CFuncTrain :: Next ); +} + +//LRC +void CFuncTrain :: StartSequence(CTrainSequence *pSequence) +{ + m_pSequence = pSequence; +// ALERT(at_console, "Unset Retrigger (startsequence)\n"); + pev->spawnflags &= ~SF_TRAIN_WAIT_RETRIGGER; +// m_iState = STATE_ON; + //... +} + +//LRC +void CFuncTrain :: StopSequence( ) +{ + m_pSequence = NULL; +// pev->spawnflags &= ~SF_TRAIN_WAIT_RETRIGGER; + pev->spawnflags &= ~SF_TRAIN_REVERSE; + Use(this, this, USE_OFF, 0); + //... +} + +/*QUAKED func_train (0 .5 .8) ? +Trains are moving platforms that players can ride. +The targets origin specifies the min point of the train at each corner. +The train spawns at the first target it is pointing at. +If the train is the target of a button or trigger, it will not begin moving until activated. +speed default 100 +dmg default 2 +sounds +1) ratchet metal +*/ + +void CFuncTrain :: Spawn( void ) +{ + Precache(); + if (pev->speed == 0) + pev->speed = 100; + +// if (!(pev->origin == g_vecZero)) +// { +// pev->spawnflags |= SF_TRAIN_SETORIGIN; +// m_vecSpawnOffset = pev->origin; +// } + + if ( FStringNull(pev->target) ) + ALERT(at_debug, "func_train \"%s\" has no target\n", STRING(pev->targetname)); + + if (pev->dmg == 0) + pev->dmg = 2; + else if (pev->dmg == -1) //LRC- a train that doesn't crush people! + pev->dmg = 0; + + pev->movetype = MOVETYPE_PUSH; + + if ( FBitSet (pev->spawnflags, SF_TRACKTRAIN_PASSABLE) ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + + SET_MODEL( ENT(pev), STRING(pev->model) ); + UTIL_SetSize (pev, pev->mins, pev->maxs); + UTIL_SetOrigin(this, pev->origin); + + m_iState = STATE_OFF; + + if ( m_volume == 0 ) + m_volume = 0.85; +} + +//LRC - making movement sounds which continue after a game is loaded. +void CFuncTrain :: SoundSetup( void ) +{ + EMIT_SOUND (ENT(pev), CHAN_STATIC, (char*)STRING(pev->noiseMovement), m_volume, ATTN_NORM); + SetNextThink( m_fStoredThink - pev->ltime ); +// ALERT(at_console, "SoundSetup: mfNT %f, pevNT %f, stored was %f, time %f", m_fNextThink, pev->nextthink, m_fStoredThink, pev->ltime ); + m_fStoredThink = 0; + SetThink(&CFuncTrain :: LinearMoveDone ); +} + +//LRC +void CFuncTrain :: ThinkCorrection( void ) +{ + if (m_fStoredThink && pev->nextthink != m_fPevNextThink) + { +// ALERT(at_console, "StoredThink Correction for train \"%s\", %f -> %f\n", STRING(pev->targetname), m_fStoredThink, m_fStoredThink + pev->nextthink - m_fPevNextThink); + m_fStoredThink += pev->nextthink - m_fPevNextThink; + } + + CBasePlatTrain::ThinkCorrection(); +} + +void CFuncTrain :: Precache( void ) +{ + CBasePlatTrain::Precache(); + + //LRC - continue the movement sound after loading a game + if (m_iState == STATE_ON && pev->noiseMovement) + { + // we can't set up SFX during precache, so get a think to do it. + // Unfortunately, since we're moving, we must be already thinking. + // So we store the current think time, and will restore it after SFX are done. + if (!m_fStoredThink) + m_fStoredThink = m_fNextThink; + SetNextThink( 0.1 ); +// ALERT(at_console, "preparing SoundSetup: stored %f, mfNT %f, pevNT %f, ltime %f", m_fStoredThink, m_fNextThink, pev->nextthink, pev->ltime); + SetThink(&CFuncTrain :: SoundSetup ); + } + +#if 0 // obsolete + // otherwise use preset sound + switch (m_sounds) + { + case 0: + pev->noise = 0; + pev->noise1 = 0; + break; + + case 1: + PRECACHE_SOUND ("plats/train2.wav"); + PRECACHE_SOUND ("plats/train1.wav"); + pev->noise = MAKE_STRING("plats/train2.wav"); + pev->noise1 = MAKE_STRING("plats/train1.wav"); + break; + + case 2: + PRECACHE_SOUND ("plats/platmove1.wav"); + PRECACHE_SOUND ("plats/platstop1.wav"); + pev->noise = MAKE_STRING("plats/platstop1.wav"); + pev->noise1 = MAKE_STRING("plats/platmove1.wav"); + break; + } +#endif +} + +void CFuncTrain::OverrideReset( void ) +{ + CBaseEntity *pTarg; + + // Are we moving? + if ( m_iState == STATE_ON ) //pev->velocity != g_vecZero && pev->nextthink != 0 ) + { + pev->target = pev->message; + // now find our next target + pTarg = GetNextTarget(); + if ( !pTarg ) + { + DontThink(); + UTIL_SetVelocity(this, g_vecZero); + m_iState = STATE_OFF; + } + else // Keep moving for 0.1 secs, then find path_corner again and restart + { + SetThink(&CFuncTrain:: Next ); + SetNextThink( 0.1 ); + } + } +} + + + + +// --------------------------------------------------------------------- +// +// Track Train +// +// --------------------------------------------------------------------- +void CFuncTrackTrain :: Spawn( void ) +{ + if ( pev->speed == 0 ) + m_speed = 100; + else + m_speed = pev->speed; + + pev->speed = 0; + pev->velocity = g_vecZero; // why do they set this stuff? --LRC + m_vecBaseAvel = pev->avelocity; //LRC - save it for later + pev->avelocity = g_vecZero; + pev->impulse = m_speed; + + m_dir = 1; + + if ( FStringNull(pev->target) ) + { + if ( FStringNull(pev->targetname) ) + ALERT( at_debug, "func_tracktrain with no target\n" ); + else + ALERT( at_debug, "func_tracktrain %s has no target\n", STRING(pev->targetname)); + } + + if ( pev->spawnflags & SF_TRACKTRAIN_PASSABLE ) + pev->solid = SOLID_NOT; + else + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + SET_MODEL( ENT(pev), STRING(pev->model) ); + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( this, pev->origin ); +// ALERT(at_console, "SpawnOrigin %f %f %f\n", pev->origin.x, pev->origin.y, pev->origin.z); + + // Cache off placed origin for train controls + pev->oldorigin = pev->origin; + + m_controlMins = pev->mins; + m_controlMaxs = pev->maxs; + m_controlMaxs.z += 72; +// start trains on the next frame, to make sure their targets have had +// a chance to spawn/activate + NextThink( 0.1, FALSE ); + SetThink(&CFuncTrackTrain :: Find ); + Precache(); +} + +void CFuncTrackTrain :: Precache( void ) +{ + if (m_flVolume == 0.0) + m_flVolume = 1.0; + + switch (m_sounds) + { + default: + //pev->noise = 0; LRC - allow custom sounds to be set in worldcraft. + break; + case 1: pev->noise = MAKE_STRING("plats/ttrain1.wav");break; + case 2: pev->noise = MAKE_STRING("plats/ttrain2.wav");break; + case 3: pev->noise = MAKE_STRING("plats/ttrain3.wav");break; + case 4: pev->noise = MAKE_STRING("plats/ttrain4.wav");break; + case 5: pev->noise = MAKE_STRING("plats/ttrain6.wav");break; + case 6: pev->noise = MAKE_STRING("plats/ttrain7.wav");break; + } + + if (FStringNull(pev->noise1)) + pev->noise1 = MAKE_STRING("plats/ttrain_brake1.wav"); + + if (FStringNull(pev->noise2)) + pev->noise2 = MAKE_STRING("plats/ttrain_start1.wav"); + + if (pev->noise) + PRECACHE_SOUND((char*)STRING(pev->noise)); //LRC + PRECACHE_SOUND((char*)STRING(pev->noise1)); + PRECACHE_SOUND((char*)STRING(pev->noise2)); + + m_usAdjustPitch = PRECACHE_EVENT( 1, "evTrainSound" ); +} + +TYPEDESCRIPTION CFuncTrackTrain::m_SaveData[] = +{ + DEFINE_FIELD( CFuncTrackTrain, m_ppath, FIELD_CLASSPTR ), + DEFINE_FIELD( CFuncTrackTrain, m_length, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_height, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_speed, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_dir, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_startSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_controlMins, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTrackTrain, m_controlMaxs, FIELD_VECTOR ), + DEFINE_FIELD( CFuncTrackTrain, m_sounds, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrackTrain, m_flVolume, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_flBank, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_oldSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CFuncTrackTrain, m_vecMasterAvel, FIELD_VECTOR ), //LRC + DEFINE_FIELD( CFuncTrackTrain, m_vecBaseAvel, FIELD_VECTOR ), //LRC + DEFINE_FIELD( CFuncTrackTrain, m_pSequence, FIELD_CLASSPTR ), //LRC +}; + +IMPLEMENT_SAVERESTORE( CFuncTrackTrain, CBaseEntity ); +LINK_ENTITY_TO_CLASS( func_tracktrain, CFuncTrackTrain ); + +void CFuncTrackTrain :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "wheels")) + { + m_length = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "height")) + { + m_height = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "startspeed")) + { + m_startSpeed = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "sounds")) + { + m_sounds = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "custommovesound")) + { + pev->noise = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "custombrakesound")) + { + pev->noise1 = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "customstartsound")) + { + pev->noise2 = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "volume")) + { + m_flVolume = (float) (atoi(pkvd->szValue)); + m_flVolume *= 0.1; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "bank")) + { + m_flBank = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +void CFuncTrackTrain :: NextThink( float thinkTime, BOOL alwaysThink ) +{ + if ( alwaysThink ) +// m_iLFlags |= LF_ALWAYSTHINK; + pev->flags |= FL_ALWAYSTHINK; + else +// m_iLFlags &= ~LF_ALWAYSTHINK; + pev->flags &= ~FL_ALWAYSTHINK; + + SetNextThink( thinkTime, TRUE ); +} + + +void CFuncTrackTrain :: Blocked( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + // Blocker is on-ground on the train + if ( FBitSet( pevOther->flags, FL_ONGROUND ) && VARS(pevOther->groundentity) == pev ) + { + float deltaSpeed = fabs(pev->speed); + if ( deltaSpeed > 50 ) + deltaSpeed = 50; + if ( !pevOther->velocity.z ) + pevOther->velocity.z += deltaSpeed; + return; + } + else + pevOther->velocity = (pevOther->origin - pev->origin ).Normalize() * pev->dmg; + + ALERT( at_aiconsole, "TRAIN(%s): Blocked by %s (dmg:%.2f)\n", STRING(pev->targetname), STRING(pOther->pev->classname), pev->dmg ); + if ( pev->dmg <= 0 ) + return; + // we can't hurt this thing, so we're not concerned with it + pOther->TakeDamage(pev, pev, pev->dmg, DMG_CRUSH); +} + + +void CFuncTrackTrain :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ +// ALERT(at_debug, "TRAIN: use\n"); + + if ( useType != USE_SET ) + { + if ( !ShouldToggle( useType, (pev->speed != 0) ) ) + return; + + if ( pev->speed == 0 ) + { + pev->speed = m_speed * m_dir; + + PostponeNext(); + } + else + { + pev->speed = 0; + UTIL_SetVelocity(this, g_vecZero); //LRC + //pev->velocity = g_vecZero; + if (!FBitSet(pev->spawnflags, SF_TRACKTRAIN_AVELOCITY)) + UTIL_SetAvelocity(this, g_vecZero); //LRC + //pev->avelocity = g_vecZero; + StopSound(); + SetThink( NULL ); + } + } + else + { + float delta = value; + + delta = ((int)(pev->speed * 4) / (int)m_speed)*0.25 + 0.25 * delta; + if ( delta > 1 ) + delta = 1; + else if ( delta < -1 ) + delta = -1; + if ( pev->spawnflags & SF_TRACKTRAIN_FORWARDONLY ) + { + if ( delta < 0 ) + delta = 0; + } + pev->speed = m_speed * delta; + + if ( pev->spawnflags & SF_TRACKTRAIN_AVEL_GEARS ) + { + UTIL_SetAvelocity(this, m_vecMasterAvel * delta); + //pev->avelocity = m_vecMasterAvel * delta; //LRC + } + + PostponeNext(); + ALERT( at_aiconsole, "TRAIN(%s), speed to %.2f\n", STRING(pev->targetname), pev->speed ); + } +} + +#define TRAIN_STARTPITCH 60 +#define TRAIN_MAXPITCH 200 +#define TRAIN_MAXSPEED 1000 // approx max speed for sound pitch calculation + +void CFuncTrackTrain :: StopSound( void ) +{ + // if sound playing, stop it + if (m_soundPlaying && pev->noise) + { + if (m_sounds) //LRC - flashy event-based method, for normal sounds. + { + unsigned short us_encode; + unsigned short us_sound = ( ( unsigned short )( m_sounds ) & 0x0007 ) << 12; + + us_encode = us_sound; + + PLAYBACK_EVENT_FULL( FEV_RELIABLE | FEV_UPDATE, edict(), m_usAdjustPitch, 0.0, + (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, us_encode, 0, 1, 0 ); + } + else + { + //LRC - down-to-earth method, for custom sounds. + STOP_SOUND(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noise)); + } + + EMIT_SOUND_DYN(ENT(pev), CHAN_ITEM, (char*)STRING(pev->noise1), m_flVolume, ATTN_NORM, 0, 100); + } + + m_soundPlaying = 0; +} + +// update pitch based on speed, start sound if not playing +// NOTE: when train goes through transition, m_soundPlaying should go to 0, +// which will cause the looped sound to restart. + +void CFuncTrackTrain :: UpdateSound( void ) +{ + float flpitch; + + if (!pev->noise) + return; + + flpitch = TRAIN_STARTPITCH + (abs(pev->speed) * (TRAIN_MAXPITCH - TRAIN_STARTPITCH) / TRAIN_MAXSPEED); + + if (!m_soundPlaying) + { + // play startup sound for train + EMIT_SOUND_DYN(ENT(pev), CHAN_ITEM, (char*)STRING(pev->noise2), m_flVolume, ATTN_NORM, 0, 100); + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noise), m_flVolume, ATTN_NORM, 0, (int) flpitch); + m_soundPlaying = 1; + } + else + { + if (m_sounds) //LRC - flashy event-based method, for normal sounds. + { + // volume 0.0 - 1.0 - 6 bits + // m_sounds 3 bits + // flpitch = 6 bits + // 15 bits total + + unsigned short us_encode; + unsigned short us_sound = ( ( unsigned short )( m_sounds ) & 0x0007 ) << 12; + unsigned short us_pitch = ( ( unsigned short )( flpitch / 10.0 ) & 0x003f ) << 6; + unsigned short us_volume = ( ( unsigned short )( m_flVolume * 40.0 ) & 0x003f ); + + us_encode = us_sound | us_pitch | us_volume; + + PLAYBACK_EVENT_FULL( FEV_RELIABLE | FEV_UPDATE, edict(), m_usAdjustPitch, 0.0, + (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, us_encode, 0, 0, 0 ); + } + else + { + //LRC - down-to-earth method, for custom sounds. + // update pitch + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, (char*)STRING(pev->noise), m_flVolume, ATTN_NORM, SND_CHANGE_PITCH, (int) flpitch); + } + } +} + +void CFuncTrackTrain :: PostponeNext( void ) +{ + UTIL_DesiredAction(this); +} + +void CFuncTrackTrain :: DesiredAction( void ) // Next( void ) +{ + float time = 0.5; + +// ALERT(at_console, "Next: pos %f %f %f, vel %f %f %f. Child pos %f %f %f, vel %f %f %f\n", pev->origin.x, pev->origin.y, pev->origin.z, pev->velocity.x, pev->velocity.y, pev->velocity.z, m_pChildMoveWith->pev->origin.x, m_pChildMoveWith->pev->origin.y, m_pChildMoveWith->pev->origin.z, m_pChildMoveWith->pev->velocity.x, m_pChildMoveWith->pev->velocity.y, m_pChildMoveWith->pev->velocity.z); +// UTIL_DesiredInfo(this); + +// static float stime; +// ALERT(at_console, "TRAIN: think delay = %f\n", gpGlobals->time - stime); +// stime = gpGlobals->time; + + if ( !pev->speed ) + { +// ALERT(at_console, "TRAIN: no speed\n"); + UTIL_SetVelocity(this, g_vecZero); + DontThink(); + ALERT( at_aiconsole, "TRAIN(%s): Speed is 0\n", STRING(pev->targetname) ); + StopSound(); + return; + } + +// if ( !m_ppath ) +// m_ppath = UTIL_FindEntityByTargetname( NULL, STRING(pev->target) ); + if ( !m_ppath ) + { +// ALERT(at_debug, "TRAIN: no path\n"); + UTIL_SetVelocity(this, g_vecZero); + DontThink(); + ALERT( at_aiconsole, "TRAIN(%s): Lost path\n", STRING(pev->targetname) ); + StopSound(); + return; + } + + UpdateSound(); + + Vector nextPos = pev->origin; + + nextPos.z -= m_height; + CPathTrack *pnext = m_ppath->LookAhead( &nextPos, pev->speed * 0.1, 1 ); + nextPos.z += m_height; + + UTIL_SetVelocity( this, (nextPos - pev->origin) * 10 ); //LRC +// Vector vD = (nextPos - pev->origin) * 10; +// ALERT(at_debug, "TRAIN: Set vel to (%f %f %f)\n", vD.x, vD.y, vD.z); + //pev->velocity = (nextPos - pev->origin) * 10; + Vector nextFront = pev->origin; + + nextFront.z -= m_height; + if ( m_length > 0 ) + m_ppath->LookAhead( &nextFront, m_length, 0 ); + else + m_ppath->LookAhead( &nextFront, 100, 0 ); + nextFront.z += m_height; + + if (!FBitSet(pev->spawnflags, SF_TRACKTRAIN_AVELOCITY)) //LRC + { + Vector delta = nextFront - pev->origin; + + Vector angles = UTIL_VecToAngles( delta ); + // The train actually points west + angles.y += 180; //LRC, FIXME: add a 'built facing' field. + + // !!! All of this crap has to be done to make the angles not wrap around, revisit this. + FixupAngles( angles ); + FixupAngles( pev->angles ); + + if ( !pnext || (delta.x == 0 && delta.y == 0) ) + angles = pev->angles; + + float vy, vx, vz; + if ( !(pev->spawnflags & SF_TRACKTRAIN_NOPITCH) ) + vx = 10*UTIL_AngleDistance( angles.x, pev->angles.x ); + else + vx = m_vecBaseAvel.x; + + if ( !(pev->spawnflags & SF_TRACKTRAIN_NOYAW) ) //LRC + vy = 10*UTIL_AngleDistance( angles.y, pev->angles.y ); + else + vy = m_vecBaseAvel.y; + + if ( m_flBank != 0 ) + { + if ( pev->avelocity.y < -5 ) + vz = UTIL_AngleDistance( UTIL_ApproachAngle( -m_flBank, pev->angles.z, m_flBank*2 ), pev->angles.z); + else if ( pev->avelocity.y > 5 ) + vz = UTIL_AngleDistance( UTIL_ApproachAngle( m_flBank, pev->angles.z, m_flBank*2 ), pev->angles.z); + else + vz = UTIL_AngleDistance( UTIL_ApproachAngle( 0, pev->angles.z, m_flBank*4 ), pev->angles.z) * 4; + } + else + { + vz = m_vecBaseAvel.z; + } + + UTIL_SetAvelocity(this, Vector(vx, vy, vz)); + //pev->avelocity.y = vy; + //pev->avelocity.x = vx; + } + + if ( pnext ) + { + if ( pnext != m_ppath ) + { +// ALERT(at_debug, "TRAIN: new m_ppath %s, was %s. Origin %f %f %f\n", STRING(pnext->pev->targetname), STRING(m_ppath->pev->targetname), pev->origin.x, pev->origin.y, pev->origin.z); + CPathTrack *pFire; + if ( pev->speed >= 0 ) // check whether we're going forwards or backwards + pFire = pnext; + else + pFire = m_ppath; + + m_ppath = pnext; + // Fire the pass target if there is one + if ( pFire->pev->message ) + { + FireTargets( STRING(pFire->pev->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( pFire->pev->spawnflags, SF_PATH_FIREONCE ) ) + pFire->pev->message = 0; + } + + if ( pFire->pev->spawnflags & SF_PATH_DISABLE_TRAIN ) + pev->spawnflags |= SF_TRACKTRAIN_NOCONTROL; + + //LRC is "match angle" set to "Yes"? If so, set the angle exactly, because we've reached the corner. + if ( pFire->pev->armorvalue == PATHMATCH_YES && pev->spawnflags & SF_TRACKTRAIN_AVELOCITY ) + { + Vector vTemp = pFire->pev->angles; + vTemp.y -= 180; //the train is actually built facing west. + UTIL_SetAngles(this, vTemp); + //pev->angles = pFire->pev->angles; + //pev->angles.y -= 180; //the train is actually built facing west. + } + + float setting = ((int)(pev->speed*4) / (int)m_speed) / 4.0; //LRC - one of { 1, 0.75, 0.5, 0.25, 0, ... -1 } + + //LRC + if ( pFire->pev->frags == PATHTURN_RESET ) + { + pev->spawnflags &= ~(SF_TRACKTRAIN_AVEL_GEARS | SF_TRACKTRAIN_AVELOCITY); + } + else if ( pFire->pev->spawnflags & SF_PATH_AVELOCITY) + { + if ( pFire->pev->frags == PATHTURN_SET_MASTER ) + { + m_vecMasterAvel = pFire->pev->avelocity; + UTIL_SetAvelocity(this, m_vecMasterAvel * setting); + //pev->avelocity = m_vecMasterAvel * setting; + pev->spawnflags |= (SF_TRACKTRAIN_AVEL_GEARS | SF_TRACKTRAIN_AVELOCITY); + } + else if ( pFire->pev->frags == PATHTURN_SET ) + { + UTIL_SetAvelocity(this, pFire->pev->avelocity); + //pev->avelocity = pFire->pev->avelocity; + pev->spawnflags |= SF_TRACKTRAIN_AVELOCITY; + pev->spawnflags &= ~SF_TRACKTRAIN_AVEL_GEARS; + } + } + + CPathTrack* pDest; //LRC - the path_track we're heading for, after pFire. + if (pev->speed > 0) + pDest = pFire->GetNext(); + else + pDest = pFire->GetPrevious(); +// ALERT(at_debug, "and pDest is %s\n", STRING(pDest->pev->targetname)); + + //LRC + // don't look at speed from target if it is 0 (uninitialized) + if ( pFire->pev->speed != 0) + { + //ALERT( at_console, "TrackTrain setting is %d / %d = %.2f\n", (int)(pev->speed*4), (int)m_speed, setting ); + + switch ( (int)(pFire->pev->armortype) ) + { + case PATHSPEED_SET: + // Don't override speed if under user control + if (pev->spawnflags & SF_TRACKTRAIN_NOCONTROL) + pev->speed = pFire->pev->speed; + ALERT( at_aiconsole, "TrackTrain %s speed set to %4.2f\n", STRING(pev->targetname), pev->speed ); + break; + case PATHSPEED_SET_MASTER: + m_speed = pFire->pev->speed; + pev->impulse = m_speed; + pev->speed = setting * m_speed; + ALERT( at_aiconsole, "TrackTrain %s master speed set to %4.2f\n", STRING(pev->targetname), pev->speed ); + break; + case PATHSPEED_ACCEL: + m_speed += pFire->pev->speed; + pev->impulse = m_speed; + pev->speed = setting * m_speed; + ALERT( at_aiconsole, "TrackTrain %s speed accel to %4.2f\n", STRING(pev->targetname), pev->speed ); + break; + case PATHSPEED_TIME: + float distance = (pev->origin - pDest->pev->origin).Length(); + //ALERT(at_debug, "pFire=%s, distance=%.2f, ospeed=%.2f, nspeed=%.2f\n", STRING(pFire->pev->targetname), distance, pev->speed, distance / pFire->pev->speed); + m_speed = distance / pFire->pev->speed; + pev->impulse = m_speed; + pev->speed = setting * m_speed; + ALERT( at_aiconsole, "TrackTrain %s speed to %4.2f (timed)\n", STRING(pev->targetname), pev->speed ); + break; + } + } + + //LRC + if (pDest->pev->armorvalue == PATHMATCH_YES) + { + pev->spawnflags |= SF_TRACKTRAIN_AVELOCITY | SF_TRACKTRAIN_AVEL_GEARS; + Vector vTemp = pev->angles; + FixupAngles( vTemp ); + UTIL_SetAngles(this, vTemp ); + Vector oDelta = pDest->pev->origin - pev->origin; + Vector aDelta; + if (setting > 0) + { + aDelta.x = UTIL_AngleDistance(pDest->pev->angles.x, pev->angles.x); + aDelta.y = UTIL_AngleDistance(pDest->pev->angles.y, pev->angles.y); + aDelta.z = UTIL_AngleDistance(pDest->pev->angles.z, pev->angles.z); + } + else + { + aDelta.x = UTIL_AngleDistance(pev->angles.x, pDest->pev->angles.x); + aDelta.y = UTIL_AngleDistance(pev->angles.y, pDest->pev->angles.y); + aDelta.z = UTIL_AngleDistance(pev->angles.z, pDest->pev->angles.z); + } + if (aDelta.y > 0) // the train is actually built facing west. + aDelta.y -= 180; + else + aDelta.y += 180; + float timeTaken = oDelta.Length() / m_speed; + m_vecMasterAvel = aDelta / timeTaken; + UTIL_SetAvelocity(this, setting * m_vecMasterAvel); + //pev->avelocity = setting * m_vecMasterAvel; + } + //LRC- FIXME: add support, here, for a Teleport flag. + } +// else +// { +// ALERT(at_debug, "TRAIN: same pnext\n"); +// } + SetThink(&CFuncTrackTrain :: PostponeNext ); + NextThink( time, TRUE ); + } + else // end of path, stop + { + Vector vecTemp; //LRC + StopSound(); + vecTemp = (nextPos - pev->origin); //LRC + +// ALERT(at_debug, "TRAIN: path end\n"); + +// UTIL_SetVelocity( this, (nextPos - pev->origin) * 10 ); //LRC +// pev->velocity = (nextPos - pev->origin); + if (!FBitSet(pev->spawnflags, SF_TRACKTRAIN_AVELOCITY)) //LRC + UTIL_SetAvelocity(this, g_vecZero); + //pev->avelocity = g_vecZero; + float distance = vecTemp.Length(); //LRC + //float distance = pev->velocity.Length(); + m_oldSpeed = pev->speed; + + + pev->speed = 0; + + // Move to the dead end + + // Are we there yet? + if ( distance > 0 ) + { + // no, how long to get there? + time = distance / m_oldSpeed; + UTIL_SetVelocity( this, vecTemp * (m_oldSpeed / distance) ); //LRC + //pev->velocity = pev->velocity * (m_oldSpeed / distance); + SetThink(&CFuncTrackTrain :: DeadEnd ); + NextThink( time, FALSE ); + } + else + { + UTIL_SetVelocity( this, vecTemp ); //LRC + DeadEnd(); + } + } +} + + +void CFuncTrackTrain::DeadEnd( void ) +{ + // Fire the dead-end target if there is one + CPathTrack *pTrack, *pNext; + + pTrack = m_ppath; + + ALERT( at_aiconsole, "TRAIN(%s): Dead end ", STRING(pev->targetname) ); + // Find the dead end path node + // HACKHACK -- This is bugly, but the train can actually stop moving at a different node depending on it's speed + // so we have to traverse the list to it's end. + if ( pTrack ) + { + if ( m_oldSpeed < 0 ) + { + do + { + pNext = pTrack->ValidPath( pTrack->GetPrevious(), TRUE ); + if ( pNext ) + pTrack = pNext; + } while ( pNext ); + } + else + { + do + { + pNext = pTrack->ValidPath( pTrack->GetNext(), TRUE ); + if ( pNext ) + pTrack = pNext; + } while ( pNext ); + } + } + + UTIL_SetVelocity( this, g_vecZero ); //LRC +// pev->velocity = g_vecZero; + if (!FBitSet(pev->spawnflags, SF_TRACKTRAIN_AVELOCITY)) //LRC + UTIL_SetAvelocity(this, g_vecZero ); + //pev->avelocity = g_vecZero; + if ( pTrack ) + { + ALERT( at_aiconsole, "at %s\n", STRING(pTrack->pev->targetname) ); + if ( pTrack->pev->netname ) + FireTargets( STRING(pTrack->pev->netname), this, this, USE_TOGGLE, 0 ); + } + else + ALERT( at_aiconsole, "\n" ); +} + + +void CFuncTrackTrain :: SetControls( entvars_t *pevControls ) +{ + Vector offset = pevControls->origin - pev->oldorigin; + + m_controlMins = pevControls->mins + offset; + m_controlMaxs = pevControls->maxs + offset; +} + + +BOOL CFuncTrackTrain :: OnControls( entvars_t *pevTest ) +{ + Vector offset = pevTest->origin - pev->origin; + + if ( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL ) + return FALSE; + + // Transform offset into local coordinates + UTIL_MakeVectors( pev->angles ); + Vector local; + local.x = DotProduct( offset, gpGlobals->v_forward ); + local.y = -DotProduct( offset, gpGlobals->v_right ); + local.z = DotProduct( offset, gpGlobals->v_up ); + + if ( local.x >= m_controlMins.x && local.y >= m_controlMins.y && local.z >= m_controlMins.z && + local.x <= m_controlMaxs.x && local.y <= m_controlMaxs.y && local.z <= m_controlMaxs.z ) + return TRUE; + + return FALSE; +} + + +void CFuncTrackTrain :: Find( void ) +{ + m_ppath = (CPathTrack*)UTIL_FindEntityByTargetname( NULL, STRING(pev->target) ); + if ( !m_ppath ) + return; + + entvars_t *pevTarget = m_ppath->pev; + if ( !FClassnameIs( pevTarget, "path_track" ) ) + { + ALERT( at_error, "func_track_train must be on a path of path_track\n" ); + m_ppath = NULL; + return; + } + + Vector nextPos = pevTarget->origin; + nextPos.z += m_height; + + Vector look = nextPos; + look.z -= m_height; + m_ppath->LookAhead( &look, m_length, 0 ); + look.z += m_height; + + Vector vTemp = UTIL_VecToAngles( look - nextPos ); + vTemp.y += 180; + // The train actually points west + //pev->angles.y += 180; + + if ( pev->spawnflags & SF_TRACKTRAIN_NOPITCH ) + { + vTemp.x = 0; + //pev->angles.x = 0; + } + + UTIL_SetAngles(this, vTemp); //LRC + + UTIL_AssignOrigin ( this, nextPos ); //LRC +// ALERT(at_console, "Train Find; origin %f %f %f\n", pev->origin.x, pev->origin.y, pev->origin.z); + //UTIL_SetOrigin( this, nextPos ); + NextThink( 0.1, FALSE ); +// NextThink( 8, FALSE ); //LRC - What was this for?! +// SetThink( Next ); + SetThink(&CFuncTrackTrain :: PostponeNext ); + pev->speed = m_startSpeed; + + UpdateSound(); +} + +void CFuncTrackTrain :: NearestPath( void ) +{ + CBaseEntity *pTrack = NULL; + CBaseEntity *pNearest = NULL; + float dist, closest; + + closest = 1024; + + while ((pTrack = UTIL_FindEntityInSphere( pTrack, pev->origin, 1024 )) != NULL) + { + // filter out non-tracks + if ( !(pTrack->pev->flags & (FL_CLIENT|FL_MONSTER)) && FClassnameIs( pTrack->pev, "path_track" ) ) + { + dist = (pev->origin - pTrack->pev->origin).Length(); + if ( dist < closest ) + { + closest = dist; + pNearest = pTrack; + } + } + } + + if ( !pNearest ) + { + ALERT( at_debug, "Can't find a nearby track !!!\n" ); + SetThink(NULL); + return; + } + + ALERT( at_aiconsole, "TRAIN: %s, Nearest track is %s\n", STRING(pev->targetname), STRING(pNearest->pev->targetname) ); + // If I'm closer to the next path_track on this path, then it's my real path + pTrack = ((CPathTrack *)pNearest)->GetNext(); + if ( pTrack ) + { + if ( (pev->origin - pTrack->pev->origin).Length() < (pev->origin - pNearest->pev->origin).Length() ) + pNearest = pTrack; + } + + m_ppath = (CPathTrack *)pNearest; + + if ( pev->speed != 0 ) + { + NextThink( 0.1, FALSE ); + SetThink(&CFuncTrackTrain :: PostponeNext ); + } +} + + +void CFuncTrackTrain::OverrideReset( void ) +{ + NextThink( 0.1, FALSE ); + SetThink(&CFuncTrackTrain:: NearestPath ); +} + + +CFuncTrackTrain *CFuncTrackTrain::Instance( edict_t *pent ) +{ + if ( FClassnameIs( pent, "func_tracktrain" ) ) + return (CFuncTrackTrain *)GET_PRIVATE(pent); + return NULL; +} + +//LRC +void CFuncTrackTrain :: StartSequence(CTrainSequence *pSequence) +{ + m_pSequence = pSequence; +// ALERT(at_console, "Unset Retrigger (startsequence)\n"); + pev->spawnflags &= ~SF_TRAIN_WAIT_RETRIGGER; + //... +} + +//LRC +void CFuncTrackTrain :: StopSequence( ) +{ + DontThink(); + m_pSequence = NULL; + //... +} + +// This class defines the volume of space that the player must stand in to control the train +class CFuncTrainControls : public CBaseEntity +{ +public: + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + void Spawn( void ); + void EXPORT Find( void ); +}; +LINK_ENTITY_TO_CLASS( func_traincontrols, CFuncTrainControls ); + + +void CFuncTrainControls :: Find( void ) +{ + CBaseEntity *pTarget = NULL; + + do + { + pTarget = UTIL_FindEntityByTargetname( pTarget, STRING(pev->target) ); + } while ( pTarget && !FClassnameIs(pTarget->pev, "func_tracktrain") ); + + if ( !pTarget ) + { + ALERT( at_debug, "TrackTrainControls: No train %s\n", STRING(pev->target) ); + return; + } + + CFuncTrackTrain *ptrain = (CFuncTrackTrain*)pTarget; + ptrain->SetControls( pev ); + UTIL_Remove( this ); +} + + +void CFuncTrainControls :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + UTIL_SetSize( pev, pev->mins, pev->maxs ); + UTIL_SetOrigin( this, pev->origin ); + + SetThink(&CFuncTrainControls :: Find ); + SetNextThink( 0 ); +} + + + +// ---------------------------------------------------------------------------- +// +// Track changer / Train elevator +// +// ---------------------------------------------------------------------------- + +#define SF_TRACK_ACTIVATETRAIN 0x00000001 +#define SF_TRACK_RELINK 0x00000002 +#define SF_TRACK_ROTMOVE 0x00000004 +#define SF_TRACK_STARTBOTTOM 0x00000008 +#define SF_TRACK_DONT_MOVE 0x00000010 + +// +// This entity is a rotating/moving platform that will carry a train to a new track. +// It must be larger in X-Y planar area than the train, since it must contain the +// train within these dimensions in order to operate when the train is near it. +// + +typedef enum { TRAIN_SAFE, TRAIN_BLOCKING, TRAIN_FOLLOWING } TRAIN_CODE; + +class CFuncTrackChange : public CFuncPlatRot +{ +public: + void Spawn( void ); + void Precache( void ); + +// virtual void Blocked( void ); + virtual void EXPORT GoUp( void ); + virtual void EXPORT GoDown( void ); + + void KeyValue( KeyValueData* pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Find( void ); + TRAIN_CODE EvaluateTrain( CPathTrack *pcurrent ); + void UpdateTrain( Vector &dest ); + virtual void HitBottom( void ); + virtual void HitTop( void ); + void Touch( CBaseEntity *pOther ); + virtual void UpdateAutoTargets( int toggleState ); + virtual BOOL IsTogglePlat( void ) { return TRUE; } + + void DisableUse( void ) { m_use = 0; } + void EnableUse( void ) { m_use = 1; } + int UseEnabled( void ) { return m_use; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + virtual void OverrideReset( void ); + + + CPathTrack *m_trackTop; + CPathTrack *m_trackBottom; + + CFuncTrackTrain *m_train; + + int m_trackTopName; + int m_trackBottomName; + int m_trainName; + TRAIN_CODE m_code; + int m_targetState; + int m_use; +}; +LINK_ENTITY_TO_CLASS( func_trackchange, CFuncTrackChange ); + +TYPEDESCRIPTION CFuncTrackChange::m_SaveData[] = +{ + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackTop, FIELD_CLASSPTR ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackBottom, FIELD_CLASSPTR ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_train, FIELD_CLASSPTR ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackTopName, FIELD_STRING ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trackBottomName, FIELD_STRING ), + DEFINE_GLOBAL_FIELD( CFuncTrackChange, m_trainName, FIELD_STRING ), + DEFINE_FIELD( CFuncTrackChange, m_code, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrackChange, m_targetState, FIELD_INTEGER ), + DEFINE_FIELD( CFuncTrackChange, m_use, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CFuncTrackChange, CFuncPlatRot ); + +void CFuncTrackChange :: Spawn( void ) +{ + Setup(); + if ( FBitSet( pev->spawnflags, SF_TRACK_DONT_MOVE ) ) + m_vecPosition2.z = pev->origin.z; + + SetupRotation(); + + if ( FBitSet( pev->spawnflags, SF_TRACK_STARTBOTTOM ) ) + { + UTIL_SetOrigin (this, m_vecPosition2); + m_toggle_state = TS_AT_BOTTOM; + pev->angles = m_start; + m_targetState = TS_AT_TOP; + } + else + { + UTIL_SetOrigin (this, m_vecPosition1); + m_toggle_state = TS_AT_TOP; + pev->angles = m_end; + m_targetState = TS_AT_BOTTOM; + } + + EnableUse(); + pev->nextthink = pev->ltime + 2.0; + SetThink(&CFuncTrackChange :: Find ); + Precache(); +} + +void CFuncTrackChange :: Precache( void ) +{ + // Can't trigger sound + PRECACHE_SOUND( "buttons/button11.wav" ); + + CFuncPlatRot::Precache(); +} + + +// UNDONE: Filter touches before re-evaluating the train. +void CFuncTrackChange :: Touch( CBaseEntity *pOther ) +{ +#if 0 + TRAIN_CODE code; + entvars_t *pevToucher = pOther->pev; +#endif +} + + + +void CFuncTrackChange :: KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "train") ) + { + m_trainName = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "toptrack") ) + { + m_trackTopName = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "bottomtrack") ) + { + m_trackBottomName = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + { + CFuncPlatRot::KeyValue( pkvd ); // Pass up to base class + } +} + + +void CFuncTrackChange::OverrideReset( void ) +{ + pev->nextthink = pev->ltime + 1.0; + SetThink(&CFuncTrackChange:: Find ); +} + +void CFuncTrackChange :: Find( void ) +{ + // Find track entities + CBaseEntity *pTarget; + + pTarget = UTIL_FindEntityByTargetname( NULL, STRING(m_trackTopName) ); + if ( pTarget && FClassnameIs(pTarget->pev, "path_track")) + { + m_trackTop = (CPathTrack*)pTarget; + pTarget = UTIL_FindEntityByTargetname( NULL, STRING(m_trackBottomName) ); + if ( pTarget && FClassnameIs(pTarget->pev, "path_track")) + { + m_trackBottom = (CPathTrack*)pTarget; + pTarget = UTIL_FindEntityByTargetname( NULL, STRING(m_trainName) ); + if ( pTarget && FClassnameIs(pTarget->pev, "func_tracktrain")) + { + m_train = (CFuncTrackTrain*)pTarget; + Vector center = (pev->absmin + pev->absmax) * 0.5; + m_trackBottom = m_trackBottom->Nearest( center ); + m_trackTop = m_trackTop->Nearest( center ); + UpdateAutoTargets( m_toggle_state ); + SetThink( NULL ); + return; + } + else + ALERT( at_error, "Can't find train for track change! %s\n", STRING(m_trainName) ); + } + else + ALERT( at_error, "Can't find bottom track for track change! %s\n", STRING(m_trackBottomName) ); + } + else + ALERT( at_error, "Can't find top track for track change! %s\n", STRING(m_trackTopName) ); +} + + + +TRAIN_CODE CFuncTrackChange :: EvaluateTrain( CPathTrack *pcurrent ) +{ + // Go ahead and work, we don't have anything to switch, so just be an elevator + if ( !pcurrent || !m_train ) + return TRAIN_SAFE; + + if ( m_train->m_ppath == pcurrent || (pcurrent->m_pprevious && m_train->m_ppath == pcurrent->m_pprevious) || + (pcurrent->m_pnext && m_train->m_ppath == pcurrent->m_pnext) ) + { + if ( m_train->pev->speed != 0 ) + return TRAIN_BLOCKING; + + Vector dist = pev->origin - m_train->pev->origin; + float length = dist.Length2D(); + if ( length < m_train->m_length ) // Empirically determined close distance + return TRAIN_FOLLOWING; + else if ( length > (150 + m_train->m_length) ) + return TRAIN_SAFE; + + return TRAIN_BLOCKING; + } + + return TRAIN_SAFE; +} + +void CFuncTrackChange :: UpdateTrain( Vector &dest ) +{ + float time; + Vector vel = pev->velocity; + + if (m_pfnThink == LinearMoveNow) + { + // we're going to do a LinearMoveNow: calculate the velocity it'll have + Vector vecDest; + if (m_pMoveWith) + vecDest = m_vecFinalDest + m_pMoveWith->pev->origin; + else + vecDest = m_vecFinalDest; + Vector vecDestDelta = vecDest - pev->origin; + time = vecDestDelta.Length() / m_flLinearMoveSpeed; + vel = vecDestDelta / time; + } + else + { + time = (pev->nextthink - pev->ltime); + } + + m_train->pev->velocity = vel; + m_train->pev->avelocity = pev->avelocity; + m_train->NextThink( m_train->pev->ltime + time, FALSE ); + + // Attempt at getting the train to rotate properly around the origin of the trackchange + if ( time <= 0 ) + { +// ALERT(at_console, "no time, set trainvel %f %f %f\n", m_train->pev->velocity.x, m_train->pev->velocity.y, m_train->pev->velocity.z); + return; + } + + Vector offset = m_train->pev->origin - pev->origin; + Vector delta = dest - pev->angles; + // Transform offset into local coordinates + UTIL_MakeInvVectors( delta, gpGlobals ); + Vector local; + local.x = DotProduct( offset, gpGlobals->v_forward ); + local.y = DotProduct( offset, gpGlobals->v_right ); + local.z = DotProduct( offset, gpGlobals->v_up ); + + local = local - offset; + m_train->pev->velocity = vel + (local * (1.0/time)); + +// ALERT(at_console, "set trainvel %f %f %f\n", m_train->pev->velocity.x, m_train->pev->velocity.y, m_train->pev->velocity.z); +} + +void CFuncTrackChange :: GoDown( void ) +{ + if ( m_code == TRAIN_BLOCKING ) + return; + + // HitBottom may get called during CFuncPlat::GoDown(), so set up for that + // before you call GoDown() + + UpdateAutoTargets( TS_GOING_DOWN ); + // If ROTMOVE, move & rotate + if ( FBitSet( pev->spawnflags, SF_TRACK_DONT_MOVE ) ) + { + SetMoveDone(&CFuncTrackChange :: CallHitBottom ); + m_toggle_state = TS_GOING_DOWN; + AngularMove( m_start, pev->speed ); + } + else + { + CFuncPlat :: GoDown(); + SetMoveDone(&CFuncTrackChange :: CallHitBottom ); + + Vector vecDest; + if (m_pMoveWith) + { + vecDest = m_vecFinalDest + m_pMoveWith->pev->origin; + } + else + vecDest = m_vecFinalDest; + Vector vecDestDelta = vecDest - pev->origin; + float flTravelTime = vecDestDelta.Length() / m_flLinearMoveSpeed; + + RotMove( m_start, flTravelTime ); +// RotMove( m_start, pev->nextthink - pev->ltime ); + } + // Otherwise, rotate first, move second + + // If the train is moving with the platform, update it + if ( m_code == TRAIN_FOLLOWING ) + { + UpdateTrain( m_start ); + m_train->m_ppath = NULL; + } +} + + +// +// Platform is at bottom, now starts moving up +// +void CFuncTrackChange :: GoUp( void ) +{ + if ( m_code == TRAIN_BLOCKING ) + return; + + // HitTop may get called during CFuncPlat::GoUp(), so set up for that + // before you call GoUp(); + + UpdateAutoTargets( TS_GOING_UP ); + if ( FBitSet( pev->spawnflags, SF_TRACK_DONT_MOVE ) ) + { + m_toggle_state = TS_GOING_UP; + SetMoveDone(&CFuncTrackChange :: CallHitTop ); + AngularMove( m_end, pev->speed ); + } + else + { + // If ROTMOVE, move & rotate + CFuncPlat :: GoUp(); + SetMoveDone(&CFuncTrackChange :: CallHitTop ); + + Vector vecDest; + if (m_pMoveWith) + { + vecDest = m_vecFinalDest + m_pMoveWith->pev->origin; + } + else + vecDest = m_vecFinalDest; + Vector vecDestDelta = vecDest - pev->origin; + float flTravelTime = vecDestDelta.Length() / m_flLinearMoveSpeed; + + RotMove( m_end, flTravelTime ); +// RotMove( m_end, pev->nextthink - pev->ltime ); + } + + // Otherwise, move first, rotate second + + // If the train is moving with the platform, update it + if ( m_code == TRAIN_FOLLOWING ) + { + UpdateTrain( m_end ); + m_train->m_ppath = NULL; + } +} + + +// Normal track change +void CFuncTrackChange :: UpdateAutoTargets( int toggleState ) +{ + if ( !m_trackTop || !m_trackBottom ) + return; + + if ( toggleState == TS_AT_TOP ) + ClearBits( m_trackTop->pev->spawnflags, SF_PATH_DISABLED ); + else + SetBits( m_trackTop->pev->spawnflags, SF_PATH_DISABLED ); + + if ( toggleState == TS_AT_BOTTOM ) + ClearBits( m_trackBottom->pev->spawnflags, SF_PATH_DISABLED ); + else + SetBits( m_trackBottom->pev->spawnflags, SF_PATH_DISABLED ); +} + + +void CFuncTrackChange :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( m_toggle_state != TS_AT_TOP && m_toggle_state != TS_AT_BOTTOM ) + return; + + // If train is in "safe" area, but not on the elevator, play alarm sound + if ( m_toggle_state == TS_AT_TOP ) + m_code = EvaluateTrain( m_trackTop ); + else if ( m_toggle_state == TS_AT_BOTTOM ) + m_code = EvaluateTrain( m_trackBottom ); + else + m_code = TRAIN_BLOCKING; + if ( m_code == TRAIN_BLOCKING ) + { + // Play alarm and return + EMIT_SOUND(ENT(pev), CHAN_VOICE, "buttons/button11.wav", 1, ATTN_NORM); + return; + } + + // Otherwise, it's safe to move + // If at top, go down + // at bottom, go up + + DisableUse(); + if (m_toggle_state == TS_AT_TOP) + GoDown(); + else + GoUp(); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncTrackChange :: HitBottom( void ) +{ + CFuncPlatRot :: HitBottom(); + if ( m_code == TRAIN_FOLLOWING ) + { +// UpdateTrain(); + m_train->SetTrack( m_trackBottom ); + } + SetThink( NULL ); + pev->nextthink = -1; + + UpdateAutoTargets( m_toggle_state ); + + EnableUse(); +} + + +// +// Platform has hit bottom. Stops and waits forever. +// +void CFuncTrackChange :: HitTop( void ) +{ + CFuncPlatRot :: HitTop(); + if ( m_code == TRAIN_FOLLOWING ) + { +// UpdateTrain(); + m_train->SetTrack( m_trackTop ); + } + + // Don't let the plat go back down + SetThink( NULL ); + pev->nextthink = -1; + UpdateAutoTargets( m_toggle_state ); + EnableUse(); +} + + + +class CFuncTrackAuto : public CFuncTrackChange +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual void UpdateAutoTargets( int toggleState ); +}; + +LINK_ENTITY_TO_CLASS( func_trackautochange, CFuncTrackAuto ); + +// Auto track change +void CFuncTrackAuto :: UpdateAutoTargets( int toggleState ) +{ + CPathTrack *pTarget, *pNextTarget; + + if ( !m_trackTop || !m_trackBottom ) + return; + + if ( m_targetState == TS_AT_TOP ) + { + pTarget = m_trackTop->GetNext(); + pNextTarget = m_trackBottom->GetNext(); + } + else + { + pTarget = m_trackBottom->GetNext(); + pNextTarget = m_trackTop->GetNext(); + } + if ( pTarget ) + { + ClearBits( pTarget->pev->spawnflags, SF_PATH_DISABLED ); + if ( m_code == TRAIN_FOLLOWING && m_train && m_train->pev->speed == 0 ) + m_train->Use( this, this, USE_ON, 0 ); + } + + if ( pNextTarget ) + SetBits( pNextTarget->pev->spawnflags, SF_PATH_DISABLED ); + +} + + +void CFuncTrackAuto :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CPathTrack *pTarget; + + if ( !UseEnabled() ) + return; + + if ( m_toggle_state == TS_AT_TOP ) + pTarget = m_trackTop; + else if ( m_toggle_state == TS_AT_BOTTOM ) + pTarget = m_trackBottom; + else + pTarget = NULL; + + if ( FClassnameIs( pActivator->pev, "func_tracktrain" ) ) + { + m_code = EvaluateTrain( pTarget ); + // Safe to fire? + if ( m_code == TRAIN_FOLLOWING && m_toggle_state != m_targetState ) + { + DisableUse(); + if (m_toggle_state == TS_AT_TOP) + GoDown(); + else + GoUp(); + } + } + else + { + if ( pTarget ) + pTarget = pTarget->GetNext(); + if ( pTarget && m_train->m_ppath != pTarget && ShouldToggle( useType, m_targetState ) ) + { + if ( m_targetState == TS_AT_TOP ) + m_targetState = TS_AT_BOTTOM; + else + m_targetState = TS_AT_TOP; + } + + UpdateAutoTargets( m_targetState ); + } +} + + +// ---------------------------------------------------------- +// +// +// pev->speed is the travel speed +// pev->health is current health +// pev->max_health is the amount to reset to each time it starts + +#define FGUNTARGET_START_ON 0x0001 + +class CGunTarget : public CBaseMonster +{ +public: + void Spawn( void ); + void Activate( void ); + void EXPORT Next( void ); + void EXPORT Start( void ); + void EXPORT Wait( void ); + void Stop( void ); + + int BloodColor( void ) { return DONT_BLEED; } + int Classify( void ) { return CLASS_MACHINE; } + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + Vector BodyTarget( const Vector &posSrc ) { return pev->origin; } + + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + BOOL m_on; +}; + + +LINK_ENTITY_TO_CLASS( func_guntarget, CGunTarget ); + +TYPEDESCRIPTION CGunTarget::m_SaveData[] = +{ + DEFINE_FIELD( CGunTarget, m_on, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE( CGunTarget, CBaseMonster ); + + +void CGunTarget::Spawn( void ) +{ + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(this, pev->origin); + SET_MODEL(ENT(pev), STRING(pev->model) ); + + if ( pev->speed == 0 ) + pev->speed = 100; + + // Don't take damage until "on" + pev->takedamage = DAMAGE_NO; + pev->flags |= FL_MONSTER; + + m_on = FALSE; + pev->max_health = pev->health; + + if ( pev->spawnflags & FGUNTARGET_START_ON ) + { + SetThink(&CGunTarget:: Start ); + SetNextThink( 0.3 ); + } +} + + +void CGunTarget::Activate( void ) +{ + CBaseEntity *pTarg; + + // now find our next target + pTarg = GetNextTarget(); + if ( pTarg ) + { + m_hTargetEnt = pTarg; + UTIL_SetOrigin( this, pTarg->pev->origin - (pev->mins + pev->maxs) * 0.5 ); + } + CBaseMonster::Activate(); +} + + +void CGunTarget::Start( void ) +{ + Use( this, this, USE_ON, 0 ); +} + + +void CGunTarget::Next( void ) +{ + SetThink( NULL ); + + m_hTargetEnt = GetNextTarget(); + CBaseEntity *pTarget = m_hTargetEnt; + + if ( !pTarget ) + { + Stop(); + return; + } + SetMoveDone(&CGunTarget:: Wait ); + LinearMove( pTarget->pev->origin - (pev->mins + pev->maxs) * 0.5, pev->speed ); +} + + +void CGunTarget::Wait( void ) +{ + CBaseEntity *pTarget = m_hTargetEnt; + + if ( !pTarget ) + { + Stop(); + return; + } + + // Fire the pass target if there is one + if ( pTarget->pev->message ) + { + FireTargets( STRING(pTarget->pev->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( pTarget->pev->spawnflags, SF_CORNER_FIREONCE ) ) + pTarget->pev->message = 0; + } + + m_flWait = pTarget->GetDelay(); + + pev->target = pTarget->pev->target; + SetThink(&CGunTarget:: Next ); + if (m_flWait != 0) + {// -1 wait will wait forever! + SetNextThink( m_flWait ); + } + else + { + Next();// do it RIGHT now! + } +} + + +void CGunTarget::Stop( void ) +{ + pev->velocity = g_vecZero; + DontThink(); + pev->takedamage = DAMAGE_NO; +} + + +int CGunTarget::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( pev->health > 0 ) + { + pev->health -= flDamage; + if ( pev->health <= 0 ) + { + pev->health = 0; + Stop(); + if ( pev->message ) + FireTargets( STRING(pev->message), this, this, USE_TOGGLE, 0 ); + } + } + return 0; +} + + +void CGunTarget::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_on ) ) + return; + + if ( m_on ) + { + Stop(); + } + else + { + pev->takedamage = DAMAGE_AIM; + m_hTargetEnt = GetNextTarget(); + if ( m_hTargetEnt == NULL ) + return; + pev->health = pev->max_health; + Next(); + } +} + +//============================================================================ +//LRC - Scripted Train Sequence +//============================================================================ + +#define DIRECTION_NONE 0 +#define DIRECTION_FORWARDS 1 +#define DIRECTION_BACKWARDS 2 +#define DIRECTION_STOP 3 +#define DIRECTION_DESTINATION 4 + +#define SF_TRAINSEQ_REMOVE 2 +#define SF_TRAINSEQ_DIRECT 4 +#define SF_TRAINSEQ_DEBUG 8 + +LINK_ENTITY_TO_CLASS( scripted_trainsequence, CTrainSequence ); + +TYPEDESCRIPTION CTrainSequence::m_SaveData[] = +{ + DEFINE_FIELD( CTrainSequence, m_iszEntity, FIELD_STRING ), + DEFINE_FIELD( CTrainSequence, m_iszDestination, FIELD_STRING ), + DEFINE_FIELD( CTrainSequence, m_pDestination, FIELD_CLASSPTR), + DEFINE_FIELD( CTrainSequence, m_iszTerminate, FIELD_STRING ), + DEFINE_FIELD( CTrainSequence, m_fDuration, FIELD_FLOAT ), + DEFINE_FIELD( CTrainSequence, m_iDirection, FIELD_INTEGER ), + DEFINE_FIELD( CTrainSequence, m_iPostDirection, FIELD_INTEGER ), + DEFINE_FIELD( CTrainSequence, m_pTrain, FIELD_CLASSPTR), + DEFINE_FIELD( CTrainSequence, m_pTrackTrain, FIELD_CLASSPTR), +}; + +IMPLEMENT_SAVERESTORE( CTrainSequence, CBaseEntity ); + +int CTrainSequence :: ObjectCaps( void ) +{ + return (CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION); +} + +void CTrainSequence :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iDirection")) + { + m_iDirection = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iPostDirection")) + { + m_iPostDirection = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszEntity")) + { + m_iszEntity = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszDestination")) + { + m_iszDestination = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszTerminate")) + { + m_iszTerminate = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CTrainSequence :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ +// ALERT(at_console, "SeqUse\n"); + if (!ShouldToggle(useType)) + { +// ALERT(at_console, "SeqUse, don't toggle\n"); + return; + } + else + { +// ALERT(at_console, "SeqUse ok\n"); + } + + if (GetState() == STATE_OFF) + { + // start the sequence, take control of the train + + CBaseEntity* pEnt = UTIL_FindEntityByTargetname(NULL, STRING(m_iszEntity), pActivator); + if (pEnt) + { + m_pDestination = UTIL_FindEntityByTargetname(NULL, STRING(m_iszDestination), pActivator); + + if (pev->spawnflags & SF_TRAINSEQ_DEBUG) + { + ALERT(at_console, "trainsequence \"%s\" found train \"%s\"", STRING(pev->targetname), STRING(pEnt->pev->targetname)); + if (m_pDestination) + ALERT(at_console, "found destination %s\n", STRING(m_pDestination->pev->targetname)); + else + ALERT(at_console, "missing destination\n"); + } + + if (FStrEq(STRING(pEnt->pev->classname), "func_train")) + { + CFuncTrain *pTrain = (CFuncTrain*)pEnt; + + // check whether it's being controlled by another sequence + if (pTrain->m_pSequence) + { +// ALERT(at_console, "SeqUse: Train sequence already set\n"); + return; + } +// ALERT(at_console, "SeqUse: Train takecontrol\n"); + + //ok, we can now take control of it. + pTrain->StartSequence(this); + m_pTrain = pTrain; + + if (pev->spawnflags & SF_TRAINSEQ_DIRECT) + { + pTrain->pev->target = m_pDestination->pev->targetname; + pTrain->Next(); + } + else + { + int iDir = DIRECTION_NONE; + + switch (m_iDirection) + { + case DIRECTION_DESTINATION: + if (m_pDestination) + { + Vector vecFTemp, vecBTemp; + CBaseEntity *pTrainDest = UTIL_FindEntityByTargetname(NULL, STRING(pTrain->pev->message)); + float fForward; + if (pTrain->pev->spawnflags & SF_TRAIN_SETORIGIN) + fForward = (pTrainDest->pev->origin - pTrain->pev->origin).Length(); + else + fForward = (pTrainDest->pev->origin - (pTrain->pev->origin + (pTrain->pev->maxs + pTrain->pev->mins)*0.5)).Length(); + float fBackward = -fForward; // the further back from the TrainDest entity we are, the shorter the backward distance. + CBaseEntity *pCurForward = pTrainDest; + CBaseEntity *pCurBackward = m_pDestination; + vecFTemp = pCurForward->pev->origin; + vecBTemp = pCurBackward->pev->origin; + int loopbreaker = 10; + while(iDir == DIRECTION_NONE) + { + if (pCurForward) + { + fForward += (pCurForward->pev->origin - vecFTemp).Length(); + vecFTemp = pCurForward->pev->origin; + + // ALERT(at_console, "SeqUse: Forward %f %s (%p == %p)\n", fForward, STRING(pCurForward->pev->targetname), pCurForward, m_pDestination); + // if we've finished tracing the forward line + if (pCurForward == m_pDestination) + { + // if the backward line is longest + if (fBackward >= fForward || pCurBackward == NULL) + iDir = DIRECTION_FORWARDS; + } + else + { + pCurForward = pCurForward->GetNextTarget(); + } + } + if (pCurBackward) + { + fBackward += (pCurBackward->pev->origin - vecBTemp).Length(); + vecBTemp = pCurBackward->pev->origin; + + // ALERT(at_console, "SeqUse: Backward %f %s (%p == %p)\n", fBackward, STRING(pCurBackward->pev->targetname), pCurBackward, pTrainDest); + // if we've finished tracng the backward line + if (pCurBackward == pTrainDest) + { + // if the forward line is shorter + if (fBackward < fForward || pCurForward == NULL) + iDir = DIRECTION_BACKWARDS; + } + else + { + pCurBackward = pCurBackward->GetNextTarget(); + } + } + loopbreaker--; + if (loopbreaker <= 0) + iDir = DIRECTION_STOP; + } + } + else + { + iDir = DIRECTION_STOP; + } + break; + case DIRECTION_FORWARDS: iDir = DIRECTION_FORWARDS; break; + case DIRECTION_BACKWARDS: iDir = DIRECTION_BACKWARDS; break; + case DIRECTION_STOP: iDir = DIRECTION_STOP; break; + } + + // ALERT(at_console, "SeqUse: iDir is %d\n", iDir); + + if (iDir == DIRECTION_BACKWARDS && !(pTrain->pev->spawnflags & SF_TRAIN_REVERSE)) + { +// ALERT(at_console, "Reversing from \"%s\" \"%s\"\n", STRING(pTrain->pev->target), STRING(pTrain->pev->message)); + // change direction + pTrain->pev->spawnflags |= SF_TRAIN_REVERSE; + + CBaseEntity *pSearch = m_pDestination; + while (pSearch) + { + if (FStrEq(STRING(pSearch->pev->target), STRING(pTrain->pev->message))) + { + // ALERT(at_console, "SeqUse reverse: pSearch is %s\n", STRING(pSearch->pev->targetname)); + CBaseEntity *pTrainTarg = pSearch->GetNextTarget(); + if (pTrainTarg) + pTrain->pev->enemy = pTrainTarg->edict(); + else + pTrain->pev->enemy = NULL; + pTrain->pev->target = pSearch->pev->targetname; + break; + } + pSearch = pSearch->GetNextTarget(); + } + + if (!pSearch) + { + // this shouldn't happen. + ALERT(at_error, "Found no path to reach destination! (train has t %s, m %s; dest is %s)\n", STRING(pTrain->pev->target), STRING(pTrain->pev->message), STRING(m_pDestination->pev->targetname)); + return; + } + pTrain->m_pevCurrentTarget = NULL; // we haven't reached the corner, so don't use its settings +// if (pTrain->pev->enemy) +// ALERT(at_console, "SeqUse: pTrain target %s, enemy %s\n", STRING(pTrain->pev->target), STRING(pTrain->pev->enemy->v.targetname)); +// else +// ALERT(at_console, "SeqUse: pTrain target %s, no enemy\n", STRING(pTrain->pev->target)); + pTrain->Next(); + } + else if (iDir == DIRECTION_FORWARDS) + { +// ALERT(at_console, "Dir_Forwards targ %s\n", STRING(pTrain->pev->target)); + pTrain->pev->target = pTrain->pev->message; + pTrain->Next(); + } + else if (iDir == DIRECTION_STOP) + { + SetNextThink(0.1); + SetThink(&CTrainSequence ::EndThink); + return; + } + } + } + else if (FStrEq(STRING(pEnt->pev->classname), "func_tracktrain")) + { + CFuncTrackTrain *pTrackTrain = (CFuncTrackTrain*)pEnt; + + // check whether it's being controlled by another sequence + if (pTrackTrain->m_pSequence) + return; + + //ok, we can now take control of it. + pTrackTrain->StartSequence(this); + m_pTrackTrain = pTrackTrain; + } + else + { + ALERT(at_error, "scripted_trainsequence %s can't affect %s \"%s\": not a train!\n", STRING(pev->targetname), STRING(pEnt->pev->classname), STRING(pEnt->pev->targetname)); + return; + } + } + else // no entity with that name + { + ALERT(at_error, "Missing train \"%s\" for scripted_trainsequence %s!\n", STRING(m_iszEntity), STRING(pev->targetname)); + return; + } + + // if we got here, we've set up a sequence successfully. + // do the rest of the setup. + if (m_fDuration) + { + SetThink(&CTrainSequence :: TimeOutThink ); + SetNextThink( m_fDuration ); + } + +// if (m_pTrain) +// ALERT(at_console, "m_pTrain nextthink %f, flags %f\n", STRING(m_pTrain->pev->nextthink), m_pTrain->m_iLFlags); + } + else // prematurely end the sequence + { + //disable the other end conditions + DontThink(); + + // release control of the train + StopSequence(); + } +} + +void CTrainSequence :: ArrivalNotify() +{ +// ALERT(at_console, "ArrivalNotify\n"); + // check whether the current path is our destination, + // and end the sequence if it is. + if (m_pTrain) + { + if (m_pTrain->m_pevCurrentTarget == m_pDestination->pev) + { + // we've reached the destination. Stop now. +// ALERT(at_console, "ArrivalNotify %s stop\n", STRING(pev->targetname)); + EndThink(); + } + else + { +// ALERT(at_console, "ArrivalNotify %s continue\n", STRING(pev->targetname)); + } + } + else if (m_pTrackTrain) + { + //... + } + else + { + ALERT(at_error, "scripted_trainsequence: ArrivalNotify without a train!?\n"); + return; // this shouldn't happen. + } +} + +void CTrainSequence :: EndThink() +{ + //the sequence has expired. Release control. + StopSequence(); + FireTargets(STRING(pev->target), this, this, USE_TOGGLE, 0); +} + +void CTrainSequence :: TimeOutThink() +{ + //the sequence has timed out. Release control. + StopSequence(); + FireTargets(STRING(pev->netname), this, this, USE_TOGGLE, 0); +} + +void CTrainSequence :: StopSequence() +{ + if (m_pTrain) + { +// ALERT(at_console, "StopSequence called\n"); + //stuff... + m_pTrain->StopSequence(); + m_pTrain = NULL; + + if (FBitSet(pev->spawnflags, SF_TRAINSEQ_REMOVE)) + UTIL_Remove( this ); + } + else if (m_pTrackTrain) + { + //stuff... + } + else + { + ALERT(at_error, "scripted_trainsequence: StopSequence without a train!?\n"); + return; // this shouldn't happen. + } + FireTargets(STRING(m_iszTerminate), this, this, USE_TOGGLE, 0); +} diff --git a/dlls/player.cpp b/dlls/player.cpp new file mode 100644 index 0000000..a7862c2 --- /dev/null +++ b/dlls/player.cpp @@ -0,0 +1,5834 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== player.cpp ======================================================== + + functions dealing with the player + +*/ + +#include "extdll.h" +#include "util.h" + +#include "cbase.h" +#include "player.h" +#include "trains.h" +#include "nodes.h" +#include "weapons.h" +#include "soundent.h" +#include "monsters.h" +#include "shake.h" +#include "decals.h" +#include "gamerules.h" +#include "game.h" +#include "hltv.h" +#include "effects.h" //LRC +#include "movewith.h" //LRC +#include + +// #define DUCKFIX + +extern DLL_GLOBAL ULONG g_ulModelIndexPlayer; +extern DLL_GLOBAL BOOL g_fGameOver; +extern DLL_GLOBAL BOOL g_fDrawLines; +int gEvilImpulse101; +BOOL g_markFrameBounds = 0; //LRC +extern DLL_GLOBAL int g_iSkillLevel, gDisplayTitle; + + +BOOL gInitHUD = TRUE; + +extern void CopyToBodyQue(entvars_t* pev); +extern void respawn(entvars_t *pev, BOOL fCopyCorpse); +extern Vector VecBModelOrigin(entvars_t *pevBModel ); +extern edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ); + +// the world node graph +extern CGraph WorldGraph; + +#define GLOBAL_TIME_STEP 0.002f // ~ one minute is equal 10 seconds like in S.T.A.L.K.E.R + +// buz: flags for goal panel +#define TP_FL_ENABLE 1 +#define TP_FL_TITLE 2 +#define TP_FL_IMAGE 4 +#define TP_FL_POPUP 8 + + +#define TRAIN_ACTIVE 0x80 +#define TRAIN_NEW 0xc0 +#define TRAIN_OFF 0x00 +#define TRAIN_NEUTRAL 0x01 +#define TRAIN_SLOW 0x02 +#define TRAIN_MEDIUM 0x03 +#define TRAIN_FAST 0x04 +#define TRAIN_BACK 0x05 + +#define FLASH_DRAIN_TIME 1.2 //100 units/3 minutes +#define FLASH_CHARGE_TIME 0.2 // 100 units/20 seconds (seconds per unit) + +#define GAS_DAMAGE_LENGTH 45.0f; + +// Global Savedata for player +TYPEDESCRIPTION CBasePlayer::m_playerSaveData[] = +{ + DEFINE_FIELD( CBasePlayer, m_flFlashLightTime, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_iFlashBattery, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_flStaminaValue, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, m_afButtonLast, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_afButtonPressed, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_afButtonReleased, FIELD_INTEGER ), + + DEFINE_ARRAY( CBasePlayer, m_rgItems, FIELD_INTEGER, MAX_ITEMS ), + DEFINE_FIELD( CBasePlayer, m_afPhysicsFlags, FIELD_INTEGER ), + + DEFINE_FIELD( CBasePlayer, m_flTimeStepSound, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flTimeWeaponIdle, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flSwimTime, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flDuckTime, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, m_flWallJumpTime, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, m_flSuitUpdate, FIELD_TIME ), + DEFINE_ARRAY( CBasePlayer, m_rgSuitPlayList, FIELD_INTEGER, CSUITPLAYLIST ), + DEFINE_FIELD( CBasePlayer, m_iSuitPlayNext, FIELD_INTEGER ), + DEFINE_ARRAY( CBasePlayer, m_rgiSuitNoRepeat, FIELD_INTEGER, CSUITNOREPEAT ), + DEFINE_ARRAY( CBasePlayer, m_rgflSuitNoRepeatTime, FIELD_TIME, CSUITNOREPEAT ), + DEFINE_FIELD( CBasePlayer, m_lastDamageAmount, FIELD_INTEGER ), + + DEFINE_ARRAY( CBasePlayer, m_rgpPlayerItems, FIELD_CLASSPTR, MAX_ITEM_TYPES ), + DEFINE_FIELD( CBasePlayer, m_pActiveItem, FIELD_CLASSPTR ), + DEFINE_FIELD( CBasePlayer, m_pLastItem, FIELD_CLASSPTR ), + DEFINE_FIELD( CBasePlayer, m_pNextItem, FIELD_CLASSPTR ), + + DEFINE_ARRAY( CBasePlayer, m_rgAmmo, FIELD_INTEGER, MAX_AMMO_SLOTS ), + DEFINE_FIELD( CBasePlayer, m_idrowndmg, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_idrownrestored, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_tSneaking, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, m_iTrain, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_bitsHUDDamage, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_flFallVelocity, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayer, m_iTargetVolume, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iWeaponVolume, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iExtraSoundTypes, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iWeaponFlash, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_fLongJump, FIELD_BOOLEAN ), + DEFINE_FIELD( CBasePlayer, m_fInitHUD, FIELD_BOOLEAN ), + DEFINE_FIELD( CBasePlayer, m_tbdPrev, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, m_pTank, FIELD_EHANDLE ), // NB: this points to a CFuncTank*Controls* now. --LRC + DEFINE_FIELD( CBasePlayer, m_iHideHUD, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_iFOV, FIELD_INTEGER ), + + // buz + DEFINE_FIELD( CBasePlayer, m_iGasMaskOn, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, m_flGasMaskTime, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, m_iHeadShieldOn, FIELD_INTEGER ), + + DEFINE_FIELD( CBasePlayer, m_pSpecTank, FIELD_CLASSPTR ), + DEFINE_FIELD( CBasePlayer, m_strCurrentGoalName, FIELD_STRING ), + DEFINE_FIELD( CBasePlayer, m_strCurrentGoalImageName, FIELD_STRING ), + DEFINE_FIELD( CBasePlayer, m_strCurrentGoalTitleName, FIELD_STRING ), + + DEFINE_FIELD( CBasePlayer, m_iJumpHeight, FIELD_INTEGER ), + + // buz: rain stuff + DEFINE_FIELD( CBasePlayer, Rain_dripsPerSecond, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, Rain_windX, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayer, Rain_windY, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayer, Rain_randX, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayer, Rain_randY, FIELD_FLOAT ), + + DEFINE_FIELD( CBasePlayer, Rain_ideal_dripsPerSecond, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayer, Rain_ideal_windX, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayer, Rain_ideal_windY, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayer, Rain_ideal_randX, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayer, Rain_ideal_randY, FIELD_FLOAT ), + + DEFINE_FIELD( CBasePlayer, Rain_endFade, FIELD_TIME ), + DEFINE_FIELD( CBasePlayer, Rain_nextFadeUpdate, FIELD_TIME ), + + DEFINE_FIELD( CBasePlayer, m_flBlurAmount, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayer, m_flCurBlurAmount, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayer, m_flLastBlurAmount, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayer, m_flBlurFadeTime, FIELD_TIME ), + + // buz: real lightlevel + DEFINE_FIELD( CBasePlayer, m_fLightlevel, FIELD_FLOAT ), + + DEFINE_FIELD( CBasePlayer, m_nCustomSprayFrames, FIELD_INTEGER ), + + //DEFINE_FIELD( CBasePlayer, m_fDeadTime, FIELD_FLOAT ), // only used in multiplayer games + //DEFINE_FIELD( CBasePlayer, m_fGameHUDInitialized, FIELD_INTEGER ), // only used in multiplayer games + //DEFINE_FIELD( CBasePlayer, m_flStopExtraSoundTime, FIELD_TIME ), + //DEFINE_FIELD( CBasePlayer, m_fKnownItem, FIELD_INTEGER ), // reset to zero on load + //DEFINE_FIELD( CBasePlayer, m_iPlayerSound, FIELD_INTEGER ), // Don't restore, set in Precache() + //DEFINE_FIELD( CBasePlayer, m_pentSndLast, FIELD_EDICT ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_flSndRoomtype, FIELD_FLOAT ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_flSndRange, FIELD_FLOAT ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_fNewAmmo, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_flgeigerRange, FIELD_FLOAT ), // Don't restore, reset in Precache() + //DEFINE_FIELD( CBasePlayer, m_flgeigerDelay, FIELD_FLOAT ), // Don't restore, reset in Precache() + //DEFINE_FIELD( CBasePlayer, m_igeigerRangePrev, FIELD_FLOAT ), // Don't restore, reset in Precache() + //DEFINE_FIELD( CBasePlayer, m_iStepLeft, FIELD_INTEGER ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_fNoPlayerSound, FIELD_BOOLEAN ), // Don't need to restore, debug + //DEFINE_FIELD( CBasePlayer, m_iUpdateTime, FIELD_INTEGER ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_iClientHealth, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_iClientBattery, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_iClientHideHUD, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_fWeapon, FIELD_BOOLEAN ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_nCustomSprayFrames, FIELD_INTEGER ), // Don't restore, depends on server message after spawning and only matters in multiplayer + //DEFINE_FIELD( CBasePlayer, m_vecAutoAim, FIELD_VECTOR ), // Don't save/restore - this is recomputed + //DEFINE_ARRAY( CBasePlayer, m_rgAmmoLast, FIELD_INTEGER, MAX_AMMO_SLOTS ), // Don't need to restore + //DEFINE_FIELD( CBasePlayer, m_fOnTarget, FIELD_BOOLEAN ), // Don't need to restore + + DEFINE_FIELD( CBasePlayer, m_nCustomSprayFrames, FIELD_INTEGER ), + DEFINE_ARRAY( CBasePlayer, m_szAnimExtention, FIELD_CHARACTER, 32 ), +}; + +int giPrecacheGrunt = 0; +int gmsgShake = 0; +int gmsgFade = 0; +int gmsgSelAmmo = 0; +int gmsgFlashlight = 0; +int gmsgFlashBattery = 0; +int gmsgResetHUD = 0; +int gmsgInitHUD = 0; +int gmsgSetFog = 0; //LRC +int gmsgKeyedDLight = 0;//LRC +int gmsgSetSky = 0; //LRC +int gmsgHUDColor = 0; //LRC +int gmsgParticle = 0; // LRC +int gmsgDelParticle = 0; // buz +int gmsgShowGameTitle = 0; +int gmsgCurWeapon = 0; +int gmsgHealth = 0; +int gmsgStamina = 0; +int gmsgDamage = 0; +int gmsgBattery = 0; +int gmsgTrain = 0; +int gmsgLogo = 0; +int gmsgWeaponList = 0; +int gmsgAmmoX = 0; +int gmsgHudText = 0; +int gmsgDeathMsg = 0; +int gmsgScoreInfo = 0; +int gmsgTeamInfo = 0; +int gmsgTeamScore = 0; +int gmsgGameMode = 0; +int gmsgMOTD = 0; +int gmsgServerName = 0; +int gmsgAmmoPickup = 0; +int gmsgWeapPickup = 0; +int gmsgItemPickup = 0; +int gmsgHideWeapon = 0; +int gmsgSetCurWeap = 0; +int gmsgSayText = 0; +int gmsgTextMsg = 0; +int gmsgSetFOV = 0; +int gmsgShowMenu = 0; +int gmsgGeigerRange = 0; +int gmsgTeamNames = 0; +int gmsgStatusIcon = 0; //LRC +int gmsgStatusText = 0; +int gmsgStatusValue = 0; +int gmsgGasMask = 0; // buz +int gmsgSpecTank = 0; // buz +int gmsgTextWindow = 0; // buz +int gmsgRainData = 0; // rain +int gmsgHeadShield = 0; // buz +int gmsgRadioIcon = 0; // buz +int gmsgTabPanel = 0; // buz +int gmsgCustomDecal = 0; // buz +int gmsgStudioDecal = 0; // g-cont +int gmsgCustomDLight = 0; // buz +int gmsgSkymarker_Sky = 0; // buz +int gmsgSkymarker_World = 0; // buz +int gmsgWaterSplash = 0; // lev +int gmsgNewExplode = 0; // lev +int gmsgMusicFade = 0; +int gmsgWeaponAnim = 0; +int gmsgWeaponBody = 0; +int gmsgWeaponSkin = 0; +int gmsgPartEffect = 0; +int gmsgBlurEffect = 0; +int gmsgLevelTime = 0; +// Wargon: Èêîíêà þçà. +int gmsgCanUse = 0; + +void LinkUserMessages( void ) +{ + // Already taken care of? + if ( gmsgSelAmmo ) + { + return; + } + + gmsgSelAmmo = REG_USER_MSG("SelAmmo", sizeof(SelAmmo)); + gmsgCurWeapon = REG_USER_MSG("CurWeapon", 3); + gmsgGeigerRange = REG_USER_MSG("Geiger", 1); + gmsgFlashlight = REG_USER_MSG("Flashlight", 2); + gmsgFlashBattery = REG_USER_MSG("FlashBat", 1); + gmsgStamina = REG_USER_MSG( "Stamina", 2); + gmsgHealth = REG_USER_MSG( "Health", 1 ); + gmsgDamage = REG_USER_MSG( "Damage", 12 ); + gmsgBattery = REG_USER_MSG( "Battery", 2); + gmsgTrain = REG_USER_MSG( "Train", 1); + gmsgHudText = REG_USER_MSG( "HudText", -1 ); + gmsgSayText = REG_USER_MSG( "SayText", -1 ); + gmsgTextMsg = REG_USER_MSG( "TextMsg", -1 ); + gmsgWeaponList = REG_USER_MSG("WeaponList", -1); + gmsgResetHUD = REG_USER_MSG("ResetHUD", 1); // called every respawn + gmsgInitHUD = REG_USER_MSG("InitHUD", 0 ); // called every time a new player joins the server + + gmsgSetFog = REG_USER_MSG("SetFog", 9 ); //LRC + gmsgKeyedDLight = REG_USER_MSG("KeyedDLight", -1 ); //LRC + gmsgSetSky = REG_USER_MSG( "SetSky", 7 ); //LRC + gmsgHUDColor = REG_USER_MSG( "HUDColor", 4 ); //LRC + gmsgParticle = REG_USER_MSG( "Particle", -1); //LRC + gmsgDelParticle = REG_USER_MSG( "DelParticle", -1); //buz + + gmsgShowGameTitle = REG_USER_MSG("GameTitle", 1); + gmsgDeathMsg = REG_USER_MSG( "DeathMsg", -1 ); + gmsgScoreInfo = REG_USER_MSG( "ScoreInfo", 9 ); + gmsgTeamInfo = REG_USER_MSG( "TeamInfo", -1 ); // sets the name of a player's team + gmsgTeamScore = REG_USER_MSG( "TeamScore", -1 ); // sets the score of a team on the scoreboard + gmsgGameMode = REG_USER_MSG( "GameMode", 1 ); + gmsgMOTD = REG_USER_MSG( "MOTD", -1 ); + gmsgServerName = REG_USER_MSG( "ServerName", -1 ); + gmsgAmmoPickup = REG_USER_MSG( "AmmoPickup", -1); //2 ); buz - ammo name string also sended + gmsgWeapPickup = REG_USER_MSG( "WeapPickup", 1 ); + gmsgItemPickup = REG_USER_MSG( "ItemPickup", -1 ); + gmsgHideWeapon = REG_USER_MSG( "HideWeapon", 1 ); + gmsgSetFOV = REG_USER_MSG( "SetFOV", 1 ); + gmsgShowMenu = REG_USER_MSG( "ShowMenu", -1 ); + gmsgShake = REG_USER_MSG("ScreenShake", sizeof(ScreenShake)); + gmsgFade = REG_USER_MSG("ScreenFade", sizeof(ScreenFade)); + gmsgAmmoX = REG_USER_MSG("AmmoX", 2); + gmsgTeamNames = REG_USER_MSG( "TeamNames", -1 ); + gmsgStatusIcon = REG_USER_MSG( "StatusIcon", -1 ); + + gmsgStatusText = REG_USER_MSG("StatusText", -1); + gmsgStatusValue = REG_USER_MSG("StatusValue", 3); + + gmsgGasMask = REG_USER_MSG("GasMask", 1); // buz + gmsgSpecTank = REG_USER_MSG("SpecTank", -1); // buz + gmsgTextWindow = REG_USER_MSG("TextWindow", -1); // buz + gmsgRainData = REG_USER_MSG("RainData", 16); // buz rain + gmsgHeadShield = REG_USER_MSG("HeadShield", 1); // buz + gmsgRadioIcon = REG_USER_MSG("RadioIcon", -1); // buz + gmsgTabPanel = REG_USER_MSG("TabPanel", -1); // buz + gmsgCustomDecal = REG_USER_MSG("CustomDecal", -1); // buz + gmsgStudioDecal = REG_USER_MSG("StudioDecal", -1); // g-cont + gmsgCustomDLight = REG_USER_MSG("mydlight", -1); // buz + gmsgSkymarker_Sky = REG_USER_MSG( "SkyMarker", -1 ); // buz + gmsgSkymarker_World = REG_USER_MSG( "WorldMarker", -1 ); // buz + gmsgWaterSplash = REG_USER_MSG( "WaterSplash", -1 ); // lev + gmsgNewExplode = REG_USER_MSG( "NewExplode", -1 ); // lev + gmsgMusicFade = REG_USER_MSG( "MusicFade", 2 ); + gmsgWeaponAnim = REG_USER_MSG( "WeaponAnim", 2 ); // anim + framerate + gmsgWeaponBody = REG_USER_MSG( "WeaponBody", 1 ); + gmsgWeaponSkin = REG_USER_MSG( "WeaponSkin", 1 ); + gmsgPartEffect = REG_USER_MSG( "PartEffect", -1 ); + gmsgBlurEffect = REG_USER_MSG( "BlurEffect", 2 ); + gmsgLevelTime = REG_USER_MSG( "LevelTime", 4 ); + + // Wargon: Èêîíêà þçà. + gmsgCanUse = REG_USER_MSG("CanUse", -1); +} + +LINK_ENTITY_TO_CLASS( player, CBasePlayer ); + +void CBasePlayer :: Pain( void ) +{ + float flRndSound;//sound randomizer + + flRndSound = RANDOM_FLOAT ( 0 , 1 ); + + if ( flRndSound <= 0.33 ) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain5.wav", 1, ATTN_NORM); + else if ( flRndSound <= 0.66 ) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain6.wav", 1, ATTN_NORM); + else + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain7.wav", 1, ATTN_NORM); +} + +/* + * + */ +Vector VecVelocityForDamage(float flDamage) +{ + Vector vec(RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + + if (flDamage > -50) + vec = vec * 0.7; + else if (flDamage > -200) + vec = vec * 2; + else + vec = vec * 10; + + return vec; +} + +#if 0 /* +static void ThrowGib(entvars_t *pev, char *szGibModel, float flDamage) +{ + edict_t *pentNew = CREATE_ENTITY(); + entvars_t *pevNew = VARS(pentNew); + + pevNew->origin = pev->origin; + SET_MODEL(ENT(pevNew), szGibModel); + UTIL_SetSize(pevNew, g_vecZero, g_vecZero); + + pevNew->velocity = VecVelocityForDamage(flDamage); + pevNew->movetype = MOVETYPE_BOUNCE; + pevNew->solid = SOLID_NOT; + pevNew->avelocity.x = RANDOM_FLOAT(0,600); + pevNew->avelocity.y = RANDOM_FLOAT(0,600); + pevNew->avelocity.z = RANDOM_FLOAT(0,600); + CHANGE_METHOD(ENT(pevNew), em_think, SUB_Remove); + pevNew->ltime = gpGlobals->time; + pevNew->nextthink = gpGlobals->time + RANDOM_FLOAT(10,20); + pevNew->frame = 0; + pevNew->flags = 0; +} + + +static void ThrowHead(entvars_t *pev, char *szGibModel, floatflDamage) +{ + SET_MODEL(ENT(pev), szGibModel); + pev->frame = 0; + pev->nextthink = -1; + pev->movetype = MOVETYPE_BOUNCE; + pev->takedamage = DAMAGE_NO; + pev->solid = SOLID_NOT; + pev->view_ofs = Vector(0,0,8); + UTIL_SetSize(pev, Vector(-16,-16,0), Vector(16,16,56)); + pev->velocity = VecVelocityForDamage(flDamage); + pev->avelocity = RANDOM_FLOAT(-1,1) * Vector(0,600,0); + pev->origin.z -= 24; + ClearBits(pev->flags, FL_ONGROUND); +} + + +*/ +#endif + +int TrainSpeed(int iSpeed, int iMax) +{ + float fSpeed, fMax; + int iRet = 0; + + fMax = (float)iMax; + fSpeed = iSpeed; + + fSpeed = fSpeed/fMax; + + if (iSpeed < 0) + iRet = TRAIN_BACK; + else if (iSpeed == 0) + iRet = TRAIN_NEUTRAL; + else if (fSpeed < 0.33) + iRet = TRAIN_SLOW; + else if (fSpeed < 0.66) + iRet = TRAIN_MEDIUM; + else + iRet = TRAIN_FAST; + + return iRet; +} + +void CBasePlayer :: DeathSound( void ) +{ + // temporarily using pain sounds for death sounds + switch (RANDOM_LONG(1,5)) + { + case 1: + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain5.wav", 1, ATTN_NORM); + break; + case 2: + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain6.wav", 1, ATTN_NORM); + break; + case 3: + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain7.wav", 1, ATTN_NORM); + break; + } +} + +// override takehealth +// bitsDamageType indicates type of damage healed. + +int CBasePlayer :: TakeHealth( float flHealth, int bitsDamageType ) +{ + return CBaseMonster :: TakeHealth (flHealth, bitsDamageType); +} + +Vector CBasePlayer :: GetGunPosition( ) +{ + return pev->origin + pev->view_ofs; +} + +//========================================================= +// TraceAttack +//========================================================= +void CBasePlayer :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if ( pev->takedamage ) + { + m_LastHitGroup = ptr->iHitgroup; + + switch ( ptr->iHitgroup ) + { + case HITGROUP_GENERIC: + break; + case HITGROUP_HEAD: + // buz: no head damage if wearing shield + if (m_iHeadShieldOn) + { + UTIL_Ricochet( ptr->vecEndPos, 1.0 ); + pev->punchangle.x -= 2; + return; + } + flDamage *= gSkillData.plrHead; + break; + case HITGROUP_CHEST: + flDamage *= gSkillData.plrChest; + break; + case HITGROUP_STOMACH: + flDamage *= gSkillData.plrStomach; + break; + case HITGROUP_LEFTARM: + case HITGROUP_RIGHTARM: + flDamage *= gSkillData.plrArm; + break; + case HITGROUP_LEFTLEG: + case HITGROUP_RIGHTLEG: + flDamage *= gSkillData.plrLeg; + break; + default: + break; + } + + // buz: player dont bleeds + // SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage);// a little surface blood. + // TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + } +} + +/* + Take some damage. + NOTE: each call to TakeDamage with bitsDamageType set to a time-based damage + type will cause the damage time countdown to be reset. Thus the ongoing effects of poison, radiation + etc are implemented with subsequent calls to TakeDamage using DMG_GENERIC. +*/ + +#define ARMOR_RATIO 0.2 // Armor Takes 80% of the damage +#define ARMOR_BONUS 0.5 // Each Point of Armor is work 1/x points of health + +int CBasePlayer :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // have suit diagnose the problem - ie: report damage type + int bitsDamage = bitsDamageType; + int ffound = TRUE; + int fmajor; + int fcritical; + int fTookDamage; + int ftrivial; + float flRatio; + float flBonus; + float flHealthPrev = pev->health; + + flBonus = ARMOR_BONUS; + flRatio = ARMOR_RATIO; + + UTIL_ScreenFade((CBaseEntity*)this,Vector(150,0,0),flDamage/16,0.1,25,0); + + if ( ( bitsDamageType & DMG_BLAST ) && g_pGameRules->IsMultiplayer() ) + { + // blasts damage armor more. + flBonus *= 2; + } + + // buz: refuse gas damage while wearing gasmask + if (m_iGasMaskOn && ( bitsDamageType & DMG_NERVEGAS )) + return 0; + + // Already dead + if ( !IsAlive() ) + return 0; + // go take the damage first + + + CBaseEntity *pAttacker = CBaseEntity::Instance(pevAttacker); + + if ( !g_pGameRules->FPlayerCanTakeDamage( this, pAttacker ) ) + { + // Refuse the damage + return 0; + } + + // keep track of amount of damage last sustained + m_lastDamageAmount = flDamage; + + if( bitsDamageType & DMG_NERVEGAS && !m_flBlurAmount ) + { + m_flBlurAmount += flDamage * 0.03f; + m_flCurBlurAmount = -m_flBlurAmount; // start pos + m_flLastBlurAmount = m_flBlurAmount; + + if( !m_flBlurFadeTime ) // fire once + EMIT_SOUND( ENT(pev), CHAN_VOICE, "player/pl_gas_hard.wav", 1, ATTN_NORM ); + m_flBlurFadeTime = gpGlobals->time; + } + + // Armor. + // buz: áðîíÿ íå ñïàñàåò îò óäàðîâ âïëîòíóþ + // Wargon: Áðîíÿ òàêæå íå çàùèùàåò îò ãàçà. (1.1) + if (pev->armorvalue && !(bitsDamageType & (DMG_FALL | DMG_DROWN | DMG_CLUB | DMG_SLASH | DMG_NERVEGAS)) )// armor doesn't protect against fall or drown damage! + { + float flNew = flDamage * flRatio; + + float flArmor; + + flArmor = (flDamage - flNew) * flBonus; + + // Does this use more armor than we have? + if (flArmor > pev->armorvalue) + { + flArmor = pev->armorvalue; + flArmor *= (1/flBonus); + flNew = flDamage - flArmor; + pev->armorvalue = 0; + } + else + pev->armorvalue -= flArmor; + + flDamage = flNew; + } + + // this cast to INT is critical!!! If a player ends up with 0.5 health, the engine will get that + // as an int (zero) and think the player is dead! (this will incite a clientside screentilt, etc) + fTookDamage = CBaseMonster::TakeDamage(pevInflictor, pevAttacker, (int)flDamage, bitsDamageType); + + // reset damage time countdown for each type of time based damage player just sustained + + { + for (int i = 0; i < CDMG_TIMEBASED; i++) + if (bitsDamageType & (DMG_PARALYZE << i)) + m_rgbTimeBasedDamage[i] = 0; + } + + // tell director about it + MESSAGE_BEGIN( MSG_SPEC, SVC_DIRECTOR ); + WRITE_BYTE ( 9 ); // command length in bytes + WRITE_BYTE ( DRC_CMD_EVENT ); // take damage event + WRITE_SHORT( ENTINDEX(this->edict()) ); // index number of primary entity + WRITE_SHORT( ENTINDEX(ENT(pevInflictor)) ); // index number of secondary entity + WRITE_LONG( 5 ); // eventflags (priority and flags) + MESSAGE_END(); + + + // how bad is it, doc? + + ftrivial = (pev->health > 75 || m_lastDamageAmount < 5); + fmajor = (m_lastDamageAmount > 25); + fcritical = (pev->health < 30); + + // g-cont. dropping active item if damage is too much + if( fmajor && (bitsDamage & (DMG_FALL | DMG_CRUSH | DMG_SLASH))) + DropPlayerItem( NULL ); + + // handle all bits set in this damage message, + // let the suit give player the diagnosis + + // UNDONE: add sounds for types of damage sustained (ie: burn, shock, slash ) + + // UNDONE: still need to record damage and heal messages for the following types + + // DMG_BURN + // DMG_FREEZE + // DMG_BLAST + // DMG_SHOCK + + m_bitsDamageType |= bitsDamage; // Save this so we can report it to the client + m_bitsHUDDamage = -1; // make sure the damage bits get resent + + // buz: new punch system + if (bitsDamage & (DMG_BULLET | DMG_BLAST) && pevInflictor && pevInflictor != pev) + { + float punch, pmax, divide; + + if (bitsDamage & DMG_BLAST ) + { + pmax = CVAR_GET_FLOAT("blast_punch_max"); + divide = CVAR_GET_FLOAT("blast_punch_divide"); + } + else + { + pmax = CVAR_GET_FLOAT("bullet_punch_max"); + divide = CVAR_GET_FLOAT("bullet_punch_divide"); + } + + if (divide == 0) divide = 1; + + punch = (flDamage > pmax ? pmax : flDamage) / divide; + Vector to = pevInflictor->origin - pev->origin; + to.z = 0; + to = to.Normalize(); + Vector tempAngle = Vector(0, pev->v_angle.y, 0); + UTIL_MakeVectors(tempAngle); + + ViewPunch(DotProduct(gpGlobals->v_forward, to) * punch, + DotProduct(gpGlobals->v_right, to) * punch, + DotProduct(gpGlobals->v_right, to) * punch); + } + else + { + // pev->punchangle.x = -2; // old punchangle + ViewPunch(1, 0, 0); + } + + return fTookDamage; // buz" no hev sounds + + while (fTookDamage && (!ftrivial || (bitsDamage & DMG_TIMEBASED)) && ffound && bitsDamage) + { + ffound = FALSE; + + if (bitsDamage & DMG_CLUB) + { + if (fmajor) + SetSuitUpdate("!HEV_DMG4", FALSE, SUIT_NEXT_IN_30SEC); // minor fracture + bitsDamage &= ~DMG_CLUB; + ffound = TRUE; + } + if (bitsDamage & (DMG_FALL | DMG_CRUSH)) + { + if (fmajor) + SetSuitUpdate("!HEV_DMG5", FALSE, SUIT_NEXT_IN_30SEC); // major fracture + else + SetSuitUpdate("!HEV_DMG4", FALSE, SUIT_NEXT_IN_30SEC); // minor fracture + + bitsDamage &= ~(DMG_FALL | DMG_CRUSH); + ffound = TRUE; + } + + if (bitsDamage & DMG_BULLET) + { + if (m_lastDamageAmount > 5) + SetSuitUpdate("!HEV_DMG6", FALSE, SUIT_NEXT_IN_30SEC); // blood loss detected + //else + // SetSuitUpdate("!HEV_DMG0", FALSE, SUIT_NEXT_IN_30SEC); // minor laceration + + bitsDamage &= ~DMG_BULLET; + ffound = TRUE; + } + + if (bitsDamage & DMG_SLASH) + { + if (fmajor) + SetSuitUpdate("!HEV_DMG1", FALSE, SUIT_NEXT_IN_30SEC); // major laceration + else + SetSuitUpdate("!HEV_DMG0", FALSE, SUIT_NEXT_IN_30SEC); // minor laceration + + bitsDamage &= ~DMG_SLASH; + ffound = TRUE; + } + + if (bitsDamage & DMG_SONIC) + { + if (fmajor) + SetSuitUpdate("!HEV_DMG2", FALSE, SUIT_NEXT_IN_1MIN); // internal bleeding + bitsDamage &= ~DMG_SONIC; + ffound = TRUE; + } + + if (bitsDamage & (DMG_POISON | DMG_PARALYZE)) + { + SetSuitUpdate("!HEV_DMG3", FALSE, SUIT_NEXT_IN_1MIN); // blood toxins detected + bitsDamage &= ~(DMG_POISON | DMG_PARALYZE); + ffound = TRUE; + } + + if (bitsDamage & DMG_ACID) + { + SetSuitUpdate("!HEV_DET1", FALSE, SUIT_NEXT_IN_1MIN); // hazardous chemicals detected + bitsDamage &= ~DMG_ACID; + ffound = TRUE; + } + + if (bitsDamage & DMG_NERVEGAS) + { + SetSuitUpdate("!HEV_DET0", FALSE, SUIT_NEXT_IN_1MIN); // biohazard detected + bitsDamage &= ~DMG_NERVEGAS; + ffound = TRUE; + } + + if (bitsDamage & DMG_RADIATION) + { + SetSuitUpdate("!HEV_DET2", FALSE, SUIT_NEXT_IN_1MIN); // radiation detected + bitsDamage &= ~DMG_RADIATION; + ffound = TRUE; + } + if (bitsDamage & DMG_SHOCK) + { + bitsDamage &= ~DMG_SHOCK; + ffound = TRUE; + } + } + + if (fTookDamage && !ftrivial && fmajor && flHealthPrev >= 75) + { + // first time we take major damage... + // turn automedic on if not on + SetSuitUpdate("!HEV_MED1", FALSE, SUIT_NEXT_IN_30MIN); // automedic on + + // give morphine shot if not given recently + SetSuitUpdate("!HEV_HEAL7", FALSE, SUIT_NEXT_IN_30MIN); // morphine shot + } + + if (fTookDamage && !ftrivial && fcritical && flHealthPrev < 75) + { + + // already took major damage, now it's critical... + if (pev->health < 6) + SetSuitUpdate("!HEV_HLTH3", FALSE, SUIT_NEXT_IN_10MIN); // near death + else if (pev->health < 20) + SetSuitUpdate("!HEV_HLTH2", FALSE, SUIT_NEXT_IN_10MIN); // health critical + + // give critical health warnings + if (!RANDOM_LONG(0,3) && flHealthPrev < 50) + SetSuitUpdate("!HEV_DMG7", FALSE, SUIT_NEXT_IN_5MIN); //seek medical attention + } + + // if we're taking time based damage, warn about its continuing effects + if (fTookDamage && (bitsDamageType & DMG_TIMEBASED) && flHealthPrev < 75) + { + if (flHealthPrev < 50) + { + if (!RANDOM_LONG(0,3)) + SetSuitUpdate("!HEV_DMG7", FALSE, SUIT_NEXT_IN_5MIN); //seek medical attention + } + else + SetSuitUpdate("!HEV_HLTH1", FALSE, SUIT_NEXT_IN_10MIN); // health dropping + } + + return fTookDamage; +} + +//========================================================= +// PackDeadPlayerItems - call this when a player dies to +// pack up the appropriate weapons and ammo items, and to +// destroy anything that shouldn't be packed. +// +// This is pretty brute force :( +//========================================================= +void CBasePlayer :: PackDeadPlayerItems( void ) +{ + int iWeaponRules; + int iAmmoRules; + int i; + CBasePlayerItem *rgpPackWeapons[MAX_WEAPONS]; + int iPackAmmo[ MAX_AMMO_SLOTS + 1]; + int iPW = 0;// index into packweapons array + int iPA = 0;// index into packammo array + + memset(rgpPackWeapons, NULL, sizeof(rgpPackWeapons) ); + memset(iPackAmmo, -1, sizeof(iPackAmmo) ); + + // get the game rules + iWeaponRules = g_pGameRules->DeadPlayerWeapons( this ); + iAmmoRules = g_pGameRules->DeadPlayerAmmo( this ); + + if ( iWeaponRules == GR_PLR_DROP_GUN_NO && iAmmoRules == GR_PLR_DROP_AMMO_NO ) + { + // nothing to pack. Remove the weapons and return. Don't call create on the box! + RemoveAllItems( TRUE ); + return; + } + +// go through all of the weapons and make a list of the ones to pack + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + // there's a weapon here. Should I pack it? + CBasePlayerItem *pPlayerItem = m_rgpPlayerItems[ i ]; + + while ( pPlayerItem ) + { + switch( iWeaponRules ) + { + case GR_PLR_DROP_GUN_ACTIVE: + if ( m_pActiveItem && pPlayerItem == m_pActiveItem ) + { + // this is the active item. Pack it. + rgpPackWeapons[ iPW++ ] = pPlayerItem; + } + break; + + case GR_PLR_DROP_GUN_ALL: + rgpPackWeapons[ iPW++ ] = pPlayerItem; + break; + + default: + break; + } + + pPlayerItem = pPlayerItem->m_pNext; + } + } + } + +// now go through ammo and make a list of which types to pack. + if ( iAmmoRules != GR_PLR_DROP_AMMO_NO ) + { + for ( i = 0 ; i < MAX_AMMO_SLOTS ; i++ ) + { + if ( m_rgAmmo[ i ] > 0 ) + { + // player has some ammo of this type. + switch ( iAmmoRules ) + { + case GR_PLR_DROP_AMMO_ALL: + iPackAmmo[ iPA++ ] = i; + break; + + case GR_PLR_DROP_AMMO_ACTIVE: + if ( m_pActiveItem && i == m_pActiveItem->PrimaryAmmoIndex() ) + { + // this is the primary ammo type for the active weapon + iPackAmmo[ iPA++ ] = i; + } + else if ( m_pActiveItem && i == m_pActiveItem->SecondaryAmmoIndex() ) + { + // this is the secondary ammo type for the active weapon + iPackAmmo[ iPA++ ] = i; + } + break; + + default: + break; + } + } + } + } + +// create a box to pack the stuff into. + CWeaponBox *pWeaponBox = (CWeaponBox *)CBaseEntity::Create( "weaponbox", pev->origin, pev->angles, edict() ); + + pWeaponBox->pev->angles.x = 0;// don't let weaponbox tilt. + pWeaponBox->pev->angles.z = 0; + + pWeaponBox->SetThink(& CWeaponBox::Kill ); + pWeaponBox->SetNextThink( 120 ); + +// back these two lists up to their first elements + iPA = 0; + iPW = 0; + +// pack the ammo + while ( iPackAmmo[ iPA ] != -1 ) + { + pWeaponBox->PackAmmo( MAKE_STRING( CBasePlayerAmmo::AmmoInfoArray[ iPackAmmo[ iPA ] ].pszName ), m_rgAmmo[ iPackAmmo[ iPA ] ] ); + iPA++; + } + +// now pack all of the items in the lists + while ( rgpPackWeapons[ iPW ] ) + { + // weapon unhooked from the player. Pack it into der box. + pWeaponBox->PackWeapon( rgpPackWeapons[ iPW ] ); + + iPW++; + } + + pWeaponBox->pev->velocity = pev->velocity * 1.2;// weaponbox has player's velocity, then some. + + RemoveAllItems( TRUE );// now strip off everything that wasn't handled by the code above. +} + +void CBasePlayer :: RemoveAllItems( BOOL removeSuit ) +{ + if (m_pActiveItem) + { + ResetAutoaim( ); + m_pActiveItem->Holster( true ); + m_pActiveItem = NULL; + } + + m_pLastItem = NULL; + + int i; + CBasePlayerItem *pPendingItem; + for (i = 0; i < MAX_ITEM_TYPES; i++) + { + m_pActiveItem = m_rgpPlayerItems[i]; + while (m_pActiveItem) + { + pPendingItem = m_pActiveItem->m_pNext; + m_pActiveItem->Drop( ); + m_pActiveItem = pPendingItem; + } + m_rgpPlayerItems[i] = NULL; + } + m_pActiveItem = NULL; + + pev->viewmodel = 0; + pev->weaponmodel = 0; + + if ( removeSuit ) + ClearBits( m_iHideHUD, ITEM_SUIT ); + + ClearBits( pev->weapons, WEAPON_ALLWEAPONS ); + + for ( i = 0; i < MAX_AMMO_SLOTS;i++) + m_rgAmmo[i] = 0; + + UpdateClientData(); + + // send Selected Weapon Message to our client + MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pev ); + WRITE_BYTE( 0 ); + WRITE_BYTE( 0 ); + WRITE_BYTE( 0 ); + MESSAGE_END(); +} + +/* + * GLOBALS ASSUMED SET: g_ulModelIndexPlayer + * + * ENTITY_METHOD(PlayerDie) + */ +entvars_t *g_pevLastInflictor; // Set in combat.cpp. Used to pass the damage inflictor for death messages. + // Better solution: Add as parameter to all Killed() functions. + +void CBasePlayer::Killed( entvars_t *pevAttacker, int iGib ) +{ + // Wargon: Åñëè èãðîêà óáèëè õåäøîòîì, òî âûâîäèòñÿ ñîîòâåòñòâóþùàÿ ìåññàãà. + if (m_LastHitGroup == HITGROUP_HEAD) + UTIL_ShowMessage("#TIPS_HEADSHOOT", this ); + + CSound *pSound; + + // Holster weapon immediately, to allow it to cleanup + if ( m_pActiveItem ) + m_pActiveItem->Holster( true ); + + m_pNextItem = NULL; + + g_pGameRules->PlayerKilled( this, pevAttacker, g_pevLastInflictor ); + + if ( m_pTank != NULL ) + { + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + } + + // buz: spec tank + if (m_pSpecTank) + { + m_pSpecTank->Use( this, this, USE_OFF, 0 ); + m_pSpecTank = NULL; + } + + // this client isn't going to be thinking for a while, so reset the sound until they respawn + pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex( edict() ) ); + { + if ( pSound ) + { + pSound->Reset(); + } + } + + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/gameover.wav", 1, ATTN_NORM); + + SetAnimation( PLAYER_DIE ); + + m_iRespawnFrames = 0; + + pev->modelindex = g_ulModelIndexPlayer; // don't use eyes + + pev->deadflag = DEAD_DYING; + pev->movetype = MOVETYPE_TOSS; + ClearBits( pev->flags, FL_ONGROUND ); + if (pev->velocity.z < 10) + pev->velocity.z += RANDOM_FLOAT(0,300); + + // clear out the suit message cache so we don't keep chattering + SetSuitUpdate(NULL, FALSE, 0); + + // send "health" update message to zero + m_iClientHealth = 0; + MESSAGE_BEGIN( MSG_ONE, gmsgHealth, NULL, pev ); + WRITE_BYTE( m_iClientHealth ); + MESSAGE_END(); + + m_iClientStamina = 0; + MESSAGE_BEGIN( MSG_ONE, gmsgStamina, NULL, pev ); + WRITE_SHORT( m_iClientStamina ); + MESSAGE_END(); + // Tell Ammo Hud that the player is dead + MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pev ); + WRITE_BYTE(0); + WRITE_BYTE(0XFF); + WRITE_BYTE(0xFF); + MESSAGE_END(); + + // reset FOV + pev->fov = m_iFOV = m_iClientFOV = 0; + + MESSAGE_BEGIN( MSG_ONE, gmsgSetFOV, NULL, pev ); + WRITE_BYTE(0); + MESSAGE_END(); + + m_flBlurAmount = 0.0f; // reset blur + + //I don't _think_ we need to anything about fog here. - LRC + + // UNDONE: Put this in, but add FFADE_PERMANENT and make fade time 8.8 instead of 4.12 + // UTIL_ScreenFade( edict(), Vector(128,0,0), 6, 15, 255, FFADE_OUT | FFADE_MODULATE ); + + if ( ( pev->health < -40 && iGib != GIB_NEVER ) || iGib == GIB_ALWAYS ) + { + pev->solid = SOLID_NOT; + GibMonster(); // This clears pev->model + pev->effects |= EF_NODRAW; + return; + } + + DeathSound(); + + pev->angles.x = 0; + pev->angles.z = 0; + + SetThink(&CBasePlayer::PlayerDeathThink); + SetNextThink( 0.1 ); +} + + +// Set the activity based on an event or current state +void CBasePlayer::SetAnimation( PLAYER_ANIM playerAnim ) +{ + int animDesired; + float speed; + char szAnim[64]; + + speed = pev->velocity.Length2D(); + + if (pev->flags & FL_FROZEN) + { + speed = 0; + playerAnim = PLAYER_IDLE; + } + + switch (playerAnim) + { + case PLAYER_JUMP: + m_IdealActivity = ACT_HOP; + pev->fuser1 = 0.0f; + break; + + case PLAYER_SUPERJUMP: + m_IdealActivity = ACT_LEAP; + pev->fuser1 = 0.0f; + break; + + case PLAYER_DIE: + m_IdealActivity = ACT_DIESIMPLE; + m_IdealActivity = GetDeathActivity( ); + break; + + case PLAYER_ATTACK1: + switch( m_Activity ) + { + case ACT_HOVER: + case ACT_SWIM: + case ACT_HOP: + case ACT_LEAP: + case ACT_DIESIMPLE: + m_IdealActivity = m_Activity; + break; + default: + m_IdealActivity = ACT_RANGE_ATTACK1; + break; + } + break; + case PLAYER_IDLE: + case PLAYER_WALK: + if ( !FBitSet( pev->flags, FL_ONGROUND ) && (m_Activity == ACT_HOP || m_Activity == ACT_LEAP) ) // Still jumping + { + m_IdealActivity = m_Activity; + } + else if ( pev->waterlevel > 1 && pev->watertype != CONTENTS_FOG) + { + if ( speed == 0 ) + m_IdealActivity = ACT_HOVER; + else + m_IdealActivity = ACT_SWIM; + } + else + { + m_IdealActivity = ACT_WALK; + } + break; + } + + switch (m_IdealActivity) + { + case ACT_HOVER: + case ACT_LEAP: + case ACT_SWIM: + case ACT_HOP: + case ACT_DIESIMPLE: + default: + if ( m_Activity == m_IdealActivity) + return; + m_Activity = m_IdealActivity; + + animDesired = LookupActivity( m_Activity ); + // Already using the desired animation? + if (pev->sequence == animDesired) + return; + + pev->gaitsequence = 0; + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); + return; + + case ACT_RANGE_ATTACK1: + if ( FBitSet( pev->flags, FL_DUCKING ) ) // crouching + strcpy( szAnim, "crouch_shoot_" ); + else + strcpy( szAnim, "ref_shoot_" ); + strcat( szAnim, m_szAnimExtention ); + animDesired = LookupSequence( szAnim ); + if (animDesired == -1) + animDesired = 0; + + if ( pev->sequence != animDesired || !m_fSequenceLoops ) + { + pev->frame = 0; + } + + if (!m_fSequenceLoops) + { + pev->effects |= EF_NOINTERP; + } + + m_Activity = m_IdealActivity; + + pev->sequence = animDesired; + ResetSequenceInfo( ); + break; + + case ACT_WALK: + if (m_Activity != ACT_RANGE_ATTACK1 || m_fSequenceFinished) + { + if ( FBitSet( pev->flags, FL_DUCKING ) ) // crouching + strcpy( szAnim, "crouch_aim_" ); + else + strcpy( szAnim, "ref_aim_" ); + strcat( szAnim, m_szAnimExtention ); + animDesired = LookupSequence( szAnim ); + if (animDesired == -1) + animDesired = 0; + m_Activity = ACT_WALK; + } + else + { + animDesired = pev->sequence; + } + } + + if ( FBitSet( pev->flags, FL_DUCKING ) ) + { + if ( speed == 0) + { + pev->gaitsequence = LookupActivity( ACT_CROUCHIDLE ); + // pev->gaitsequence = LookupActivity( ACT_CROUCH ); + } + else + { + pev->gaitsequence = LookupActivity( ACT_CROUCH ); + } + } + else if ( speed > 200 ) + { + pev->gaitsequence = LookupActivity( ACT_RUN ); + + if (m_pActiveItem) + m_pActiveItem->PlayerRun(); + } + else if (speed > 0) + { + pev->gaitsequence = LookupActivity( ACT_WALK ); + + if (m_pActiveItem) + m_pActiveItem->PlayerWalk(); + } + else + { + // pev->gaitsequence = LookupActivity( ACT_WALK ); + pev->gaitsequence = LookupSequence( "deep_idle" ); + } + + + // Already using the desired animation? + if (pev->sequence == animDesired) + return; + + //ALERT( at_console, "Set animation to %d\n", animDesired ); + // Reset to first frame of desired animation + pev->sequence = animDesired; + pev->frame = 0; + ResetSequenceInfo( ); +} + +/* +=========== +WaterMove +============ +*/ +#define AIRTIME 12 // lung full of air lasts this many seconds + +void CBasePlayer::WaterMove() +{ + int air; + + if (pev->movetype == MOVETYPE_NOCLIP) + return; + + if (pev->health < 0) + return; + + // waterlevel 0 - not in water + // waterlevel 1 - feet in water + // waterlevel 2 - waist in water + // waterlevel 3 - head in water + + if (pev->waterlevel != 3 || pev->watertype <= CONTENTS_FLYFIELD) + { + // not underwater + + // play 'up for air' sound + if (pev->air_finished < gpGlobals->time) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_wade1.wav", 1, ATTN_NORM); + else if (pev->air_finished < gpGlobals->time + 9) + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_wade2.wav", 1, ATTN_NORM); + + pev->air_finished = gpGlobals->time + AIRTIME; + pev->dmg = 2; + + // if we took drowning damage, give it back slowly + if (m_idrowndmg > m_idrownrestored) + { + // set drowning damage bit. hack - dmg_drownrecover actually + // makes the time based damage code 'give back' health over time. + // make sure counter is cleared so we start count correctly. + + // NOTE: this actually causes the count to continue restarting + // until all drowning damage is healed. + + m_bitsDamageType |= DMG_DROWNRECOVER; + m_bitsDamageType &= ~DMG_DROWN; + m_rgbTimeBasedDamage[itbd_DrownRecover] = 0; + } + + } + else if (pev->watertype > CONTENTS_FLYFIELD) // FLYFIELD, FLYFIELD_GRAVITY & FOG aren't really water... + { // fully under water + // stop restoring damage while underwater + m_bitsDamageType &= ~DMG_DROWNRECOVER; + m_rgbTimeBasedDamage[itbd_DrownRecover] = 0; + + if (pev->air_finished < gpGlobals->time) // drown! + { + if (pev->pain_finished < gpGlobals->time) + { + // take drowning damage + pev->dmg += 1; + if (pev->dmg > 5) + pev->dmg = 5; + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), pev->dmg, DMG_DROWN); + pev->pain_finished = gpGlobals->time + 1; + + // track drowning damage, give it back when + // player finally takes a breath + + m_idrowndmg += pev->dmg; + } + } + else + { + m_bitsDamageType &= ~DMG_DROWN; + } + } + + if (!pev->waterlevel || pev->watertype <= CONTENTS_FLYFIELD ) + { + if (FBitSet(pev->flags, FL_INWATER)) + { + ClearBits(pev->flags, FL_INWATER); + } + return; + } + + // make bubbles + + air = (int)(pev->air_finished - gpGlobals->time); + if (!RANDOM_LONG(0,0x1f) && RANDOM_LONG(0,AIRTIME-1) >= air) + { + switch (RANDOM_LONG(0,3)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim1.wav", 0.8, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim2.wav", 0.8, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim3.wav", 0.8, ATTN_NORM); break; + case 3: EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_swim4.wav", 0.8, ATTN_NORM); break; + } + + UTIL_MakeVectors( pev->v_angle ); + + // g-cont. add some bubbles + CBaseEntity::Create( "player_bubbles", EyePosition() + gpGlobals->v_forward * 2.0f, pev->v_angle, edict() ); + } + + if (pev->watertype == CONTENTS_LAVA) // do damage + { + if (pev->dmgtime < gpGlobals->time) + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), 10 * pev->waterlevel, DMG_BURN); + } + else if (pev->watertype == CONTENTS_SLIME) // do damage + { + pev->dmgtime = gpGlobals->time + 1; + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), 4 * pev->waterlevel, DMG_ACID); + } + + if (!FBitSet(pev->flags, FL_INWATER)) + { + SetBits(pev->flags, FL_INWATER); + pev->dmgtime = 0; + } +} + + +// TRUE if the player is attached to a ladder +BOOL CBasePlayer::IsOnLadder( void ) +{ + return ( pev->movetype == MOVETYPE_FLY ); +} + +void CBasePlayer::PlayerDeathThink(void) +{ + float flForward; + + if (FBitSet(pev->flags, FL_ONGROUND)) + { + flForward = pev->velocity.Length() - 20; + if (flForward <= 0) + pev->velocity = g_vecZero; + else + pev->velocity = flForward * pev->velocity.Normalize(); + } + + if ( HasWeapons() ) + { + // we drop the guns here because weapons that have an area effect and can kill their user + // will sometimes crash coming back from CBasePlayer::Killed() if they kill their owner because the + // player class sometimes is freed. It's safer to manipulate the weapons once we know + // we aren't calling into any of their code anymore through the player pointer. + PackDeadPlayerItems(); + } + + + if (pev->modelindex && (!m_fSequenceFinished) && (pev->deadflag == DEAD_DYING)) + { + StudioFrameAdvance( ); + + m_iRespawnFrames++; // Note, these aren't necessarily real "frames", so behavior is dependent on # of client movement commands + if ( m_iRespawnFrames < 120 ) // Animations should be no longer than this + return; + } + + // once we're done animating our death and we're on the ground, we want to set movetype to None so our dead body won't do collisions and stuff anymore + // this prevents a bug where the dead body would go to a player's head if he walked over it while the dead player was clicking their button to respawn + if ( pev->movetype != MOVETYPE_NONE && FBitSet(pev->flags, FL_ONGROUND) ) + pev->movetype = MOVETYPE_NONE; + + if (pev->deadflag == DEAD_DYING) + pev->deadflag = DEAD_DEAD; + + StopAnimation(); + + pev->effects |= EF_NOINTERP; + pev->framerate = 0.0; + + BOOL fAnyButtonDown = (pev->button & ~IN_SCORE ); + + // wait for all buttons released + if (pev->deadflag == DEAD_DEAD) + { + if (fAnyButtonDown) + return; + + if ( g_pGameRules->FPlayerCanRespawn( this ) ) + { + m_fDeadTime = gpGlobals->time; + pev->deadflag = DEAD_RESPAWNABLE; + } + + return; + } + +// if the player has been dead for one second longer than allowed by forcerespawn, +// forcerespawn isn't on. Send the player off to an intermission camera until they +// choose to respawn. + if ( g_pGameRules->IsMultiplayer() && ( gpGlobals->time > (m_fDeadTime + 6) ) && !(m_afPhysicsFlags & PFLAG_OBSERVER) ) + { + // go to dead camera. + StartDeathCam(); + } + +// wait for any button down, or mp_forcerespawn is set and the respawn time is up + if (!fAnyButtonDown + && !( g_pGameRules->IsMultiplayer() && forcerespawn.value > 0 && (gpGlobals->time > (m_fDeadTime + 5))) ) + return; + + pev->button = 0; + m_iRespawnFrames = 0; + + //ALERT(at_console, "Respawn\n"); + + respawn(pev, !(m_afPhysicsFlags & PFLAG_OBSERVER) );// don't copy a corpse if we're in deathcam. + DontThink(); +} + +//========================================================= +// StartDeathCam - find an intermission spot and send the +// player off into observer mode +//========================================================= +void CBasePlayer::StartDeathCam( void ) +{ + CBaseEntity *pSpot, *pNewSpot; + int iRand; + + if ( pev->view_ofs == g_vecZero ) + { + // don't accept subsequent attempts to StartDeathCam() + return; + } + + pSpot = UTIL_FindEntityByClassname( NULL, "info_intermission"); + + if ( pSpot ) + { + // at least one intermission spot in the world. + iRand = RANDOM_LONG( 0, 3 ); + + while ( iRand > 0 ) + { + pNewSpot = UTIL_FindEntityByTargetname( pSpot, "info_intermission"); + + if ( pNewSpot ) + { + pSpot = pNewSpot; + } + + iRand--; + } + + CopyToBodyQue( pev ); + StartObserver( pSpot->pev->origin, pSpot->pev->v_angle ); + } + else + { + // no intermission spot. Push them up in the air, looking down at their corpse + TraceResult tr; + CopyToBodyQue( pev ); + UTIL_TraceLine( pev->origin, pev->origin + Vector( 0, 0, 128 ), ignore_monsters, edict(), &tr ); + StartObserver( tr.vecEndPos, UTIL_VecToAngles( tr.vecEndPos - pev->origin ) ); + return; + } +} + +void CBasePlayer::StartObserver( Vector vecPosition, Vector vecViewAngle ) +{ + m_afPhysicsFlags |= PFLAG_OBSERVER; + + pev->view_ofs = g_vecZero; + pev->angles = pev->v_angle = vecViewAngle; + pev->fixangle = TRUE; + pev->solid = SOLID_NOT; + pev->takedamage = DAMAGE_NO; + pev->movetype = MOVETYPE_NONE; + pev->modelindex = 0; + UTIL_SetOrigin( this, vecPosition ); +} + +// +// PlayerUse - handles USE keypress +// +#define PLAYER_SEARCH_RADIUS (float)64 +#define PLAYER_DISTUSE_RADIUS (float)128 // Wargon: Ðàññòîÿíèå äàëüíåãî þçà óìåíüøåíî. + +void CBasePlayer::PlayerUse ( void ) +{ + // Was use pressed or released? + if ( ! ((pev->button | m_afButtonPressed | m_afButtonReleased) & IN_USE) ) + return; + + // Hit Use on a train? + if ( m_afButtonPressed & IN_USE ) + { + if ( m_pTank != NULL ) + { + // Stop controlling the tank + // TODO: Send HUD Update + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + return; + } + // buz: spec tank + else if (m_pSpecTank) + { + m_pSpecTank->Use( this, this, USE_OFF, 0 ); + m_pSpecTank = NULL; + return; + } + else + { + if ( m_afPhysicsFlags & PFLAG_ONTRAIN ) + { + m_afPhysicsFlags &= ~PFLAG_ONTRAIN; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + else + { // Start controlling the train! + CBaseEntity *pTrain = CBaseEntity::Instance( pev->groundentity ); + + if ( pTrain && !(pev->button & IN_JUMP) && FBitSet(pev->flags, FL_ONGROUND) && + (pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) && pTrain->OnControls(pev) ) + { + m_afPhysicsFlags |= PFLAG_ONTRAIN; + m_iTrain = TrainSpeed(pTrain->pev->speed, pTrain->pev->impulse); + m_iTrain |= TRAIN_NEW; +// EMIT_SOUND( ENT(pev), CHAN_ITEM, "plats/train_use1.wav", 0.8, ATTN_NORM); + return; + } + } + } + } + + CBaseEntity *pObject = NULL; + CBaseEntity *pClosest = NULL; + Vector vecLOS; + float flMaxDot = VIEW_FIELD_NARROW; + float flDot; + TraceResult tr; + int caps; + + UTIL_MakeVectors ( pev->v_angle );// so we know which way we are facing + + //LRC- try to get an exact entity to use. + // (is this causing "use-buttons-through-walls" problems? Surely not!) + UTIL_TraceLine( pev->origin + pev->view_ofs, + pev->origin + pev->view_ofs + (gpGlobals->v_forward * PLAYER_SEARCH_RADIUS), + dont_ignore_monsters, ENT(pev), &tr ); + if (tr.pHit) + { + pObject = CBaseEntity::Instance(tr.pHit); + if (!pObject || !(pObject->ObjectCaps() & (FCAP_IMPULSE_USE | FCAP_CONTINUOUS_USE | FCAP_ONOFF_USE))) + { + pObject = NULL; + } + } + + if (!pObject) //LRC- couldn't find a direct solid object to use, try the normal method + { + while ((pObject = UTIL_FindEntityInSphere( pObject, pev->origin, PLAYER_SEARCH_RADIUS )) != NULL) + { + caps = pObject->ObjectCaps(); + if (caps & (FCAP_IMPULSE_USE | FCAP_CONTINUOUS_USE | FCAP_ONOFF_USE) && !(caps & FCAP_ONLYDIRECT_USE)) //LRC - we can't see 'direct use' entities in this section + { + // buz: test special usability by sequence bbox for monsters + CBaseMonster *pMonster = pObject->MyMonsterPointer(); + if (pMonster) + { + Vector mins, maxs; + pMonster->ExtractBbox(pMonster->pev->sequence, mins, maxs); + vecLOS = (((mins + maxs) * 0.5) + pMonster->pev->origin - (pev->origin + pev->view_ofs)); + vecLOS = UTIL_ClampVectorToBox( vecLOS, ((mins + maxs) * 0.5) ); + } + else + { + vecLOS = (VecBModelOrigin( pObject->pev ) - (pev->origin + pev->view_ofs)); + + // ALERT(at_console, "absmin %f %f %f, absmax %f %f %f, mins %f %f %f, maxs %f %f %f, size %f %f %f\n", pObject->pev->absmin.x, pObject->pev->absmin.y, pObject->pev->absmin.z, pObject->pev->absmax.x, pObject->pev->absmax.y, pObject->pev->absmax.z, pObject->pev->mins.x, pObject->pev->mins.y, pObject->pev->mins.z, pObject->pev->maxs.x, pObject->pev->maxs.y, pObject->pev->maxs.z, pObject->pev->size.x, pObject->pev->size.y, pObject->pev->size.z);//LRCTEMP + // This essentially moves the origin of the target to the corner nearest the player to test to see + // if it's "hull" is in the view cone + vecLOS = UTIL_ClampVectorToBox( vecLOS, pObject->pev->size * 0.5 ); + } + + flDot = DotProduct (vecLOS , gpGlobals->v_forward); + if (flDot > flMaxDot || vecLOS == g_vecZero ) // LRC - if the player is standing inside this entity, it's also ok to use it. + {// only if the item is in front of the user + pClosest = pObject; + flMaxDot = flDot; +// ALERT( at_console, "%s : %f\n", STRING( pObject->pev->classname ), flDot ); + } +// ALERT( at_console, "%s : %f\n", STRING( pObject->pev->classname ), flDot ); + } + } + pObject = pClosest; + + if( pObject && pObject->ObjectCaps() & FCAP_USE_ONLY ) + { + // make sure what item not blocked by entity + UTIL_TraceLine( pev->origin + pev->view_ofs, pObject->pev->origin, dont_ignore_monsters, ENT(pev), &tr ); + if( tr.flFraction < 1.0f ) pObject = NULL; // blocked + } + } + + if (!pObject) // buz - try distance use + { + UTIL_TraceLine( pev->origin + pev->view_ofs, + pev->origin + pev->view_ofs + (gpGlobals->v_forward * PLAYER_DISTUSE_RADIUS), + dont_ignore_monsters, ENT(pev), &tr ); + if (tr.pHit) + { + pObject = CBaseEntity::Instance(tr.pHit); + if (!pObject || !(pObject->ObjectCaps() & FCAP_DISTANCE_USE)) + { + pObject = NULL; + } + } + } + + // Found an object + if (pObject ) + { + //!!!UNDONE: traceline here to prevent USEing buttons through walls + caps = pObject->ObjectCaps(); + + if ( m_afButtonPressed & IN_USE ) + EMIT_SOUND( ENT(pev), CHAN_ITEM, "common/wpn_select.wav", 0.4, ATTN_NORM); + + if ( ( (pev->button & IN_USE) && (caps & FCAP_CONTINUOUS_USE) ) || + ( (m_afButtonPressed & IN_USE) && (caps & (FCAP_IMPULSE_USE|FCAP_ONOFF_USE)) ) ) + { + if ( caps & FCAP_CONTINUOUS_USE ) + m_afPhysicsFlags |= PFLAG_USING; + + pObject->Use( this, this, USE_SET, 1 ); + } + // UNDONE: Send different USE codes for ON/OFF. Cache last ONOFF_USE object to send 'off' if you turn away + // (actually, nothing uses on/off. They're either continuous - rechargers and momentary + // buttons - or they're impulse - buttons, doors, tanks, trains, etc.) --LRC + else if ( (m_afButtonReleased & IN_USE) && (pObject->ObjectCaps() & FCAP_ONOFF_USE) ) // BUGBUG This is an "off" use + { + pObject->Use( this, this, USE_SET, 0 ); + } + } + else + { + if ( m_afButtonPressed & IN_USE ) + EMIT_SOUND( ENT(pev), CHAN_ITEM, "common/wpn_denyselect.wav", 0.4, ATTN_NORM); + } +} + + + +void CBasePlayer::Jump() +{ + Vector vecWallCheckDir;// direction we're tracing a line to find a wall when walljumping + Vector vecAdjustedVelocity; + Vector vecSpot; + TraceResult tr; + + if (FBitSet(pev->flags, FL_WATERJUMP)) + return; + + if (pev->waterlevel >= 2 && pev->watertype != CONTENTS_FOG) + { + return; + } + + // jump velocity is sqrt( height * gravity * 2) + + // If this isn't the first frame pressing the jump button, break out. + if ( !FBitSet( m_afButtonPressed, IN_JUMP ) ) + return; // don't pogo stick + + if ( !(pev->flags & FL_ONGROUND) || !pev->groundentity ) + { + return; + } + +// many features in this function use v_forward, so makevectors now. + UTIL_MakeVectors (pev->angles); + + // ClearBits(pev->flags, FL_ONGROUND); // don't stairwalk + + SetAnimation( PLAYER_JUMP ); + + if ( m_fLongJump && + (pev->button & IN_DUCK) && + ( pev->flDuckTime > 0 ) && + pev->velocity.Length() > 50 ) + { + SetAnimation( PLAYER_SUPERJUMP ); + } + + // buz: report active item about jumping + if (m_pActiveItem) + m_pActiveItem->PlayerJump(); + + // If you're standing on a conveyor, add its velocity to yours (for momentum) + entvars_t *pevGround = VARS(pev->groundentity); + if ( pevGround && (pevGround->flags & FL_CONVEYOR) ) + { + pev->velocity = pev->velocity + pev->basevelocity; + } +} + + + +// This is a glorious hack to find free space when you've crouched into some solid space +// Our crouching collisions do not work correctly for some reason and this is easier +// than fixing the problem :( +void FixPlayerCrouchStuck( edict_t *pPlayer ) +{ + TraceResult trace; + + // Move up as many as 18 pixels if the player is stuck. + for ( int i = 0; i < 18; i++ ) + { + UTIL_TraceHull( pPlayer->v.origin, pPlayer->v.origin, dont_ignore_monsters, head_hull, pPlayer, &trace ); + if ( trace.fStartSolid ) + pPlayer->v.origin.z ++; + else + break; + } +} + +void CBasePlayer::Duck( ) +{ + if (pev->button & IN_DUCK) + { + if ( m_IdealActivity != ACT_LEAP ) + { + SetAnimation( PLAYER_WALK ); + } + } +} + +// +// ID's player as such. +// +int CBasePlayer::Classify ( void ) +{ + return CLASS_PLAYER; +} + + +void CBasePlayer::AddPoints( int score, BOOL bAllowNegativeScore ) +{ + // Positive score always adds + if ( score < 0 ) + { + if ( !bAllowNegativeScore ) + { + if ( pev->frags < 0 ) // Can't go more negative + return; + + if ( -score > pev->frags ) // Will this go negative? + { + score = -pev->frags; // Sum will be 0 + } + } + } + + pev->frags += score; + + MESSAGE_BEGIN( MSG_ALL, gmsgScoreInfo ); + WRITE_BYTE( ENTINDEX(edict()) ); + WRITE_SHORT( pev->frags ); + WRITE_SHORT( m_iDeaths ); + WRITE_SHORT( 0 ); + WRITE_SHORT( g_pGameRules->GetTeamIndex( m_szTeamName ) + 1 ); + MESSAGE_END(); +} + + +void CBasePlayer::AddPointsToTeam( int score, BOOL bAllowNegativeScore ) +{ + int index = entindex(); + + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + if ( pPlayer && i != index ) + { + if ( g_pGameRules->PlayerRelationship( this, pPlayer ) == GR_TEAMMATE ) + { + pPlayer->AddPoints( score, bAllowNegativeScore ); + } + } + } +} + +//Player ID +void CBasePlayer::InitStatusBar() +{ + m_flStatusBarDisappearDelay = 0; + m_SbarString1[0] = m_SbarString0[0] = 0; +} + +void CBasePlayer::UpdateStatusBar() +{ + int newSBarState[ SBAR_END ]; + char sbuf0[ SBAR_STRING_SIZE ]; + char sbuf1[ SBAR_STRING_SIZE ]; + + memset( newSBarState, 0, sizeof(newSBarState) ); + strcpy( sbuf0, m_SbarString0 ); + strcpy( sbuf1, m_SbarString1 ); + + // Find an ID Target + TraceResult tr; + UTIL_MakeVectors( pev->v_angle + pev->punchangle ); + Vector vecSrc = EyePosition(); + Vector vecEnd = vecSrc + (gpGlobals->v_forward * MAX_ID_RANGE); + UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, edict(), &tr); + + if (tr.flFraction != 1.0) + { + if ( !FNullEnt( tr.pHit ) ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + + if (pEntity->Classify() == CLASS_PLAYER ) + { + newSBarState[ SBAR_ID_TARGETNAME ] = ENTINDEX( pEntity->edict() ); + strcpy( sbuf1, "1 %p1\n2 Health: %i2%%\n3 Armor: %i3%%" ); + + // allies and medics get to see the targets health + if ( g_pGameRules->PlayerRelationship( this, pEntity ) == GR_TEAMMATE ) + { + newSBarState[ SBAR_ID_TARGETHEALTH ] = 100 * (pEntity->pev->health / pEntity->pev->max_health); + newSBarState[ SBAR_ID_TARGETARMOR ] = pEntity->pev->armorvalue; //No need to get it % based since 100 it's the max. + } + + m_flStatusBarDisappearDelay = gpGlobals->time + 1.0; + } + } + else if ( m_flStatusBarDisappearDelay > gpGlobals->time ) + { + // hold the values for a short amount of time after viewing the object + newSBarState[ SBAR_ID_TARGETNAME ] = m_izSBarState[ SBAR_ID_TARGETNAME ]; + newSBarState[ SBAR_ID_TARGETHEALTH ] = m_izSBarState[ SBAR_ID_TARGETHEALTH ]; + newSBarState[ SBAR_ID_TARGETARMOR ] = m_izSBarState[ SBAR_ID_TARGETARMOR ]; + } + } + + BOOL bForceResend = FALSE; + + if ( strcmp( sbuf0, m_SbarString0 ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgStatusText, NULL, pev ); + WRITE_BYTE( 0 ); + WRITE_STRING( sbuf0 ); + MESSAGE_END(); + + strcpy( m_SbarString0, sbuf0 ); + + // make sure everything's resent + bForceResend = TRUE; + } + + if ( strcmp( sbuf1, m_SbarString1 ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgStatusText, NULL, pev ); + WRITE_BYTE( 1 ); + WRITE_STRING( sbuf1 ); + MESSAGE_END(); + + strcpy( m_SbarString1, sbuf1 ); + + // make sure everything's resent + bForceResend = TRUE; + } + + // Check values and send if they don't match + for (int i = 1; i < SBAR_END; i++) + { + if ( newSBarState[i] != m_izSBarState[i] || bForceResend ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgStatusValue, NULL, pev ); + WRITE_BYTE( i ); + WRITE_SHORT( newSBarState[i] ); + MESSAGE_END(); + + m_izSBarState[i] = newSBarState[i]; + } + } +} + + + + + + + + + +#define CLIMB_SHAKE_FREQUENCY 22 // how many frames in between screen shakes when climbing +#define MAX_CLIMB_SPEED 200 // fastest vertical climbing speed possible +#define CLIMB_SPEED_DEC 15 // climbing deceleration rate +#define CLIMB_PUNCH_X -7 // how far to 'punch' client X axis when climbing +#define CLIMB_PUNCH_Z 7 // how far to 'punch' client Z axis when climbing + +void CBasePlayer::PreThink(void) +{ + int buttonsChanged = (m_afButtonLast ^ pev->button); // These buttons have changed this frame + + // Debounced button codes for pressed/released + // UNDONE: Do we need auto-repeat? + m_afButtonPressed = buttonsChanged & pev->button; // The ones that changed and are now down are "pressed" + m_afButtonReleased = buttonsChanged & (~pev->button); // The ones that changed and aren't down are "released" + + g_pGameRules->PlayerThink( this ); + +// CheckDesiredList( ); //LRC + //CheckAssistList(); //LRC + + if ( g_fGameOver ) + return; // intermission or finale + + UTIL_MakeVectors(pev->v_angle); // is this still used? + + ItemPreFrame( ); + WaterMove(); + + if ( g_pGameRules && g_pGameRules->FAllowFlashlight() ) + m_iHideHUD &= ~HIDEHUD_FLASHLIGHT; + else + m_iHideHUD |= HIDEHUD_FLASHLIGHT; + + + // JOHN: checks if new client data (for HUD and view control) needs to be sent to the client + UpdateClientData(); + + CheckTimeBasedDamage(); + + CheckSuitUpdate(); + + if (pev->deadflag >= DEAD_DYING) + { + PlayerDeathThink(); + return; + } + + // So the correct flags get sent to client asap. + // + if ( m_afPhysicsFlags & PFLAG_ONTRAIN ) + pev->flags |= FL_ONTRAIN; + else + pev->flags &= ~FL_ONTRAIN; + + // Train speed control + if ( m_afPhysicsFlags & PFLAG_ONTRAIN ) + { + CBaseEntity *pTrain = CBaseEntity::Instance( pev->groundentity ); + float vel; + + if ( !pTrain ) + { + TraceResult trainTrace; + // Maybe this is on the other side of a level transition + UTIL_TraceLine( pev->origin, pev->origin + Vector(0,0,-38), ignore_monsters, ENT(pev), &trainTrace ); + + // HACKHACK - Just look for the func_tracktrain classname + if ( trainTrace.flFraction != 1.0 && trainTrace.pHit ) + pTrain = CBaseEntity::Instance( trainTrace.pHit ); + + + if ( !pTrain || !(pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) || !pTrain->OnControls(pev) ) + { + //ALERT( at_error, "In train mode with no train!\n" ); + m_afPhysicsFlags &= ~PFLAG_ONTRAIN; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + } + else if ( !FBitSet( pev->flags, FL_ONGROUND ) || FBitSet( pTrain->pev->spawnflags, SF_TRACKTRAIN_NOCONTROL ) || (pev->button & (IN_MOVELEFT|IN_MOVERIGHT) ) ) + { + // Turn off the train if you jump, strafe, or the train controls go dead + m_afPhysicsFlags &= ~PFLAG_ONTRAIN; + m_iTrain = TRAIN_NEW|TRAIN_OFF; + return; + } + + pev->velocity = g_vecZero; + vel = 0; + if ( m_afButtonPressed & IN_FORWARD ) + { + vel = 1; + pTrain->Use( this, this, USE_SET, (float)vel ); + } + else if ( m_afButtonPressed & IN_BACK ) + { + vel = -1; + pTrain->Use( this, this, USE_SET, (float)vel ); + } + + if (vel) + { + m_iTrain = TrainSpeed(pTrain->pev->speed, pTrain->pev->impulse); + m_iTrain |= TRAIN_ACTIVE|TRAIN_NEW; + } + + } else if (m_iTrain & TRAIN_ACTIVE) + m_iTrain = TRAIN_NEW; // turn off train + + if (pev->button & IN_JUMP) + { + // If on a ladder, jump off the ladder + // else Jump + Jump(); + } + + + // If trying to duck, already ducked, or in the process of ducking + if ((pev->button & IN_DUCK) || FBitSet(pev->flags,FL_DUCKING) || (m_afPhysicsFlags & PFLAG_DUCKING) ) + Duck(); + + if ( !FBitSet ( pev->flags, FL_ONGROUND ) ) + { + m_flFallVelocity = -pev->velocity.z; + } + + // StudioFrameAdvance( );//!!!HACKHACK!!! Can't be hit by traceline when not animating? + + // Clear out ladder pointer + m_hEnemy = NULL; + + if ( m_afPhysicsFlags & PFLAG_ONBARNACLE ) + { + pev->velocity = g_vecZero; + } + + if (m_flStaminaValue < 1 && pev->button & IN_RUN) + { + CLIENT_COMMAND(ENT(pev), "-sprint\n"); + //ALERT(at_console, "low stamina!\n"); + return; + } + + if ( pev->button & IN_RUN && m_flStaminaValue > 0 && pev->velocity.Length2D() > 100) + { + m_flStaminaValue -= 0.25; + } + else if (m_flStaminaValue < 100) + { + if (pev->velocity.Length2D() < 100) + m_flStaminaValue += 0.25; + else if (pev->velocity.Length2D() < 290) + m_flStaminaValue += 0.1; + } + + pev->skin = (int)m_flStaminaValue; +} +/* Time based Damage works as follows: + 1) There are several types of timebased damage: + + #define DMG_PARALYZE (1 << 14) // slows affected creature down + #define DMG_NERVEGAS (1 << 15) // nerve toxins, very bad + #define DMG_POISON (1 << 16) // blood poisioning + #define DMG_RADIATION (1 << 17) // radiation exposure + #define DMG_DROWNRECOVER (1 << 18) // drown recovery + #define DMG_ACID (1 << 19) // toxic chemicals or acid burns + #define DMG_SLOWBURN (1 << 20) // in an oven + #define DMG_SLOWFREEZE (1 << 21) // in a subzero freezer + + 2) A new hit inflicting tbd restarts the tbd counter - each monster has an 8bit counter, + per damage type. The counter is decremented every second, so the maximum time + an effect will last is 255/60 = 4.25 minutes. Of course, staying within the radius + of a damaging effect like fire, nervegas, radiation will continually reset the counter to max. + + 3) Every second that a tbd counter is running, the player takes damage. The damage + is determined by the type of tdb. + Paralyze - 1/2 movement rate, 30 second duration. + Nervegas - 5 points per second, 16 second duration = 80 points max dose. + Poison - 2 points per second, 25 second duration = 50 points max dose. + Radiation - 1 point per second, 50 second duration = 50 points max dose. + Drown - 5 points per second, 2 second duration. + Acid/Chemical - 5 points per second, 10 second duration = 50 points max. + Burn - 10 points per second, 2 second duration. + Freeze - 3 points per second, 10 second duration = 30 points max. + + 4) Certain actions or countermeasures counteract the damaging effects of tbds: + + Armor/Heater/Cooler - Chemical(acid),burn, freeze all do damage to armor power, then to body + - recharged by suit recharger + Air In Lungs - drowning damage is done to air in lungs first, then to body + - recharged by poking head out of water + - 10 seconds if swiming fast + Air In SCUBA - drowning damage is done to air in tanks first, then to body + - 2 minutes in tanks. Need new tank once empty. + Radiation Syringe - Each syringe full provides protection vs one radiation dosage + Antitoxin Syringe - Each syringe full provides protection vs one poisoning (nervegas or poison). + Health kit - Immediate stop to acid/chemical, fire or freeze damage. + Radiation Shower - Immediate stop to radiation damage, acid/chemical or fire damage. + + +*/ + +// If player is taking time based damage, continue doing damage to player - +// this simulates the effect of being poisoned, gassed, dosed with radiation etc - +// anything that continues to do damage even after the initial contact stops. +// Update all time based damage counters, and shut off any that are done. + +// The m_bitsDamageType bit MUST be set if any damage is to be taken. +// This routine will detect the initial on value of the m_bitsDamageType +// and init the appropriate counter. Only processes damage every second. + +//#define PARALYZE_DURATION 30 // number of 2 second intervals to take damage +//#define PARALYZE_DAMAGE 0.0 // damage to take each 2 second interval + +//#define NERVEGAS_DURATION 16 +//#define NERVEGAS_DAMAGE 5.0 + +//#define POISON_DURATION 25 +//#define POISON_DAMAGE 2.0 + +//#define RADIATION_DURATION 50 +//#define RADIATION_DAMAGE 1.0 + +//#define ACID_DURATION 10 +//#define ACID_DAMAGE 5.0 + +//#define SLOWBURN_DURATION 2 +//#define SLOWBURN_DAMAGE 1.0 + +//#define SLOWFREEZE_DURATION 1.0 +//#define SLOWFREEZE_DAMAGE 3.0 + +/* */ + + +void CBasePlayer::CheckTimeBasedDamage() +{ + int i; + BYTE bDuration = 0; + + static float gtbdPrev = 0.0; + + if (!(m_bitsDamageType & DMG_TIMEBASED)) + return; + + // only check for time based damage approx. every 2 seconds + if (abs(gpGlobals->time - m_tbdPrev) < 2.0) + return; + + m_tbdPrev = gpGlobals->time; + + for (i = 0; i < CDMG_TIMEBASED; i++) + { + // make sure bit is set for damage type + if (m_bitsDamageType & (DMG_PARALYZE << i)) + { + switch (i) + { + case itbd_Paralyze: + // UNDONE - flag movement as half-speed + bDuration = PARALYZE_DURATION; + break; + case itbd_NerveGas: +// TakeDamage(pev, pev, NERVEGAS_DAMAGE, DMG_GENERIC); + bDuration = NERVEGAS_DURATION; + break; + case itbd_Poison: + TakeDamage(pev, pev, POISON_DAMAGE, DMG_GENERIC); + bDuration = POISON_DURATION; + break; + case itbd_Radiation: +// TakeDamage(pev, pev, RADIATION_DAMAGE, DMG_GENERIC); + bDuration = RADIATION_DURATION; + break; + case itbd_DrownRecover: + // NOTE: this hack is actually used to RESTORE health + // after the player has been drowning and finally takes a breath + if (m_idrowndmg > m_idrownrestored) + { + int idif = min(m_idrowndmg - m_idrownrestored, 10); + + TakeHealth(idif, DMG_GENERIC); + m_idrownrestored += idif; + } + bDuration = 4; // get up to 5*10 = 50 points back + break; + case itbd_Acid: +// TakeDamage(pev, pev, ACID_DAMAGE, DMG_GENERIC); + bDuration = ACID_DURATION; + break; + case itbd_SlowBurn: +// TakeDamage(pev, pev, SLOWBURN_DAMAGE, DMG_GENERIC); + bDuration = SLOWBURN_DURATION; + break; + case itbd_SlowFreeze: +// TakeDamage(pev, pev, SLOWFREEZE_DAMAGE, DMG_GENERIC); + bDuration = SLOWFREEZE_DURATION; + break; + default: + bDuration = 0; + } + + if (m_rgbTimeBasedDamage[i]) + { + // use up an antitoxin on poison or nervegas after a few seconds of damage + if (((i == itbd_NerveGas) && (m_rgbTimeBasedDamage[i] < NERVEGAS_DURATION)) || + ((i == itbd_Poison) && (m_rgbTimeBasedDamage[i] < POISON_DURATION))) + { + if (m_rgItems[ITEM_ANTIDOTE]) + { + m_rgbTimeBasedDamage[i] = 0; + m_rgItems[ITEM_ANTIDOTE]--; + // SetSuitUpdate("!HEV_HEAL4", FALSE, SUIT_REPEAT_OK); // buz: no hev sounds + } + } + + + // decrement damage duration, detect when done. + if (!m_rgbTimeBasedDamage[i] || --m_rgbTimeBasedDamage[i] == 0) + { + m_rgbTimeBasedDamage[i] = 0; + // if we're done, clear damage bits + m_bitsDamageType &= ~(DMG_PARALYZE << i); + } + } + else + // first time taking this damage type - init damage duration + m_rgbTimeBasedDamage[i] = bDuration; + } + } +} + +/* +THE POWER SUIT + +The Suit provides 3 main functions: Protection, Notification and Augmentation. +Some functions are automatic, some require power. +The player gets the suit shortly after getting off the train in C1A0 and it stays +with him for the entire game. + +Protection + + Heat/Cold + When the player enters a hot/cold area, the heating/cooling indicator on the suit + will come on and the battery will drain while the player stays in the area. + After the battery is dead, the player starts to take damage. + This feature is built into the suit and is automatically engaged. + Radiation Syringe + This will cause the player to be immune from the effects of radiation for N seconds. Single use item. + Anti-Toxin Syringe + This will cure the player from being poisoned. Single use item. + Health + Small (1st aid kits, food, etc.) + Large (boxes on walls) + Armor + The armor works using energy to create a protective field that deflects a + percentage of damage projectile and explosive attacks. After the armor has been deployed, + it will attempt to recharge itself to full capacity with the energy reserves from the battery. + It takes the armor N seconds to fully charge. + +Notification (via the HUD) + +x Health +x Ammo +x Automatic Health Care + Notifies the player when automatic healing has been engaged. +x Geiger counter + Classic Geiger counter sound and status bar at top of HUD + alerts player to dangerous levels of radiation. This is not visible when radiation levels are normal. +x Poison + Armor + Displays the current level of armor. + +Augmentation + + Reanimation (w/adrenaline) + Causes the player to come back to life after he has been dead for 3 seconds. + Will not work if player was gibbed. Single use. + Long Jump + Used by hitting the ??? key(s). Caused the player to further than normal. + SCUBA + Used automatically after picked up and after player enters the water. + Works for N seconds. Single use. + +Things powered by the battery + + Armor + Uses N watts for every M units of damage. + Heat/Cool + Uses N watts for every second in hot/cold area. + Long Jump + Uses N watts for every jump. + Alien Cloak + Uses N watts for each use. Each use lasts M seconds. + Alien Shield + Augments armor. Reduces Armor drain by one half + +*/ + +// if in range of radiation source, ping geiger counter + +#define GEIGERDELAY 0.25 + +void CBasePlayer :: UpdateGeigerCounter( void ) +{ + BYTE range; + + // delay per update ie: don't flood net with these msgs + if (gpGlobals->time < m_flgeigerDelay) + return; + + m_flgeigerDelay = gpGlobals->time + GEIGERDELAY; + + // send range to radition source to client + + range = (BYTE) (m_flgeigerRange / 4); + + if (range != m_igeigerRangePrev) + { + m_igeigerRangePrev = range; + + MESSAGE_BEGIN( MSG_ONE, gmsgGeigerRange, NULL, pev ); + WRITE_BYTE( range ); + MESSAGE_END(); + } + + // reset counter and semaphore + if (!RANDOM_LONG(0,3)) + m_flgeigerRange = 1000; + +} + +/* +================ +CheckSuitUpdate + +Play suit update if it's time +================ +*/ + +#define SUITUPDATETIME 3.5 +#define SUITFIRSTUPDATETIME 0.1 + +void CBasePlayer :: CheckSuitUpdate() +{ + // Ignore suit updates if no suit + if ( !FBitSet( m_iHideHUD, ITEM_SUIT )) + return; + + // if in range of radiation source, ping geiger counter + UpdateGeigerCounter(); +} + +// add sentence to suit playlist queue. if fgroup is true, then +// name is a sentence group (HEV_AA), otherwise name is a specific +// sentence name ie: !HEV_AA0. If iNoRepeat is specified in +// seconds, then we won't repeat playback of this word or sentence +// for at least that number of seconds. + +void CBasePlayer::SetSuitUpdate(char *name, int fgroup, int iNoRepeatTime) +{ + int i; + int isentence; + int iempty = -1; + + + // Ignore suit updates if no suit + if ( !FBitSet( m_iHideHUD, ITEM_SUIT ) ) + return; + + if ( g_pGameRules->IsMultiplayer() ) + { + // due to static channel design, etc. We don't play HEV sounds in multiplayer right now. + return; + } + + // if name == NULL, then clear out the queue + + if (!name) + { + for (i = 0; i < CSUITPLAYLIST; i++) + m_rgSuitPlayList[i] = 0; + return; + } + // get sentence or group number + if (!fgroup) + { + isentence = SENTENCEG_Lookup(name, NULL); + if (isentence < 0) + { + ALERT(at_debug,"HEV couldn't find sentence %s\n",name); + return; + } + } + else + // mark group number as negative + isentence = -SENTENCEG_GetIndex(name); + + // check norepeat list - this list lets us cancel + // the playback of words or sentences that have already + // been played within a certain time. + + for (i = 0; i < CSUITNOREPEAT; i++) + { + if (isentence == m_rgiSuitNoRepeat[i]) + { + // this sentence or group is already in + // the norepeat list + + if (m_rgflSuitNoRepeatTime[i] < gpGlobals->time) + { + // norepeat time has expired, clear it out + m_rgiSuitNoRepeat[i] = 0; + m_rgflSuitNoRepeatTime[i] = 0.0; + iempty = i; + break; + } + else + { + // don't play, still marked as norepeat + return; + } + } + // keep track of empty slot + if (!m_rgiSuitNoRepeat[i]) + iempty = i; + } + + // sentence is not in norepeat list, save if norepeat time was given + + if (iNoRepeatTime) + { + if (iempty < 0) + iempty = RANDOM_LONG(0, CSUITNOREPEAT-1); // pick random slot to take over + m_rgiSuitNoRepeat[iempty] = isentence; + m_rgflSuitNoRepeatTime[iempty] = iNoRepeatTime + gpGlobals->time; + } + + // find empty spot in queue, or overwrite last spot + + m_rgSuitPlayList[m_iSuitPlayNext++] = isentence; + if (m_iSuitPlayNext == CSUITPLAYLIST) + m_iSuitPlayNext = 0; + + if (m_flSuitUpdate <= gpGlobals->time) + { + if (m_flSuitUpdate == 0) + // play queue is empty, don't delay too long before playback + m_flSuitUpdate = gpGlobals->time + SUITFIRSTUPDATETIME; + else + m_flSuitUpdate = gpGlobals->time + SUITUPDATETIME; + } + +} + +/* +================ +CheckPowerups + +Check for turning off powerups + +GLOBALS ASSUMED SET: g_ulModelIndexPlayer +================ +*/ + static void +CheckPowerups(entvars_t *pev) +{ + if (pev->health <= 0) + return; + + pev->modelindex = g_ulModelIndexPlayer; // don't use eyes +} + + +//========================================================= +// UpdatePlayerSound - updates the position of the player's +// reserved sound slot in the sound list. +//========================================================= +void CBasePlayer :: UpdatePlayerSound ( void ) +{ + int iBodyVolume; + int iVolume; + CSound *pSound; + + // blur fadeout code + if( m_flBlurFadeTime > 0.0f ) + { + float elapsed = gpGlobals->time - m_flBlurFadeTime; + + float f = elapsed / GAS_DAMAGE_LENGTH; // fadeout time + f = bound( 0.0f, f, 1.0f ); + + m_flBlurAmount = m_flLastBlurAmount + m_flCurBlurAmount * f; + if( f == 1.0f ) m_flBlurFadeTime = 0.0f; // hit 100% fade + } + else if( m_flLastBlurAmount && m_flCurBlurAmount ) + { + // fixup changelevel issues + m_flBlurAmount = m_flLastBlurAmount = m_flCurBlurAmount = 0.0f; + } + + pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt :: ClientSoundIndex( edict() ) ); + + if ( !pSound ) + { + ALERT ( at_debug, "Client lost reserved sound!\n" ); + return; + } + + pSound->m_iType = bits_SOUND_NONE; + + // now calculate the best target volume for the sound. If the player's weapon + // is louder than his body/movement, use the weapon volume, else, use the body volume. + + if ( FBitSet ( pev->flags, FL_ONGROUND ) ) + { + iBodyVolume = pev->velocity.Length(); + + // clamp the noise that can be made by the body, in case a push trigger, + // weapon recoil, or anything shoves the player abnormally fast. + if ( iBodyVolume > 512 ) + { + iBodyVolume = 512; + } + } + else + { + iBodyVolume = 0; + } + + if ( pev->button & IN_JUMP ) + { + iBodyVolume += 100; + } + +// convert player move speed and actions into sound audible by monsters. + if ( m_iWeaponVolume > iBodyVolume ) + { + m_iTargetVolume = m_iWeaponVolume; + + // OR in the bits for COMBAT sound if the weapon is being louder than the player. + pSound->m_iType |= bits_SOUND_COMBAT; + } + else + { + m_iTargetVolume = iBodyVolume; + } + + // decay weapon volume over time so bits_SOUND_COMBAT stays set for a while + m_iWeaponVolume -= 250 * gpGlobals->frametime; + if ( m_iWeaponVolume < 0 ) + { + iVolume = 0; + } + + + // if target volume is greater than the player sound's current volume, we paste the new volume in + // immediately. If target is less than the current volume, current volume is not set immediately to the + // lower volume, rather works itself towards target volume over time. This gives monsters a much better chance + // to hear a sound, especially if they don't listen every frame. + iVolume = pSound->m_iVolume; + + if ( m_iTargetVolume > iVolume ) + { + iVolume = m_iTargetVolume; + } + else if ( iVolume > m_iTargetVolume ) + { + iVolume -= 250 * gpGlobals->frametime; + + if ( iVolume < m_iTargetVolume ) + { + iVolume = 0; + } + } + + if ( m_fNoPlayerSound ) + { + // debugging flag, lets players move around and shoot without monsters hearing. + iVolume = 0; + } + + if ( gpGlobals->time > m_flStopExtraSoundTime ) + { + // since the extra sound that a weapon emits only lasts for one client frame, we keep that sound around for a server frame or two + // after actual emission to make sure it gets heard. + m_iExtraSoundTypes = 0; + } + + if ( pSound ) + { + pSound->m_vecOrigin = pev->origin; + pSound->m_iType |= ( bits_SOUND_PLAYER | m_iExtraSoundTypes ); + pSound->m_iVolume = iVolume; + } + + // keep track of virtual muzzle flash + m_iWeaponFlash -= 256 * gpGlobals->frametime; + if (m_iWeaponFlash < 0) + m_iWeaponFlash = 0; + + //UTIL_MakeVectors ( pev->angles ); + //gpGlobals->v_forward.z = 0; + + // Below are a couple of useful little bits that make it easier to determine just how much noise the + // player is making. + // UTIL_ParticleEffect ( pev->origin + gpGlobals->v_forward * iVolume, g_vecZero, 255, 25 ); + //ALERT ( at_console, "%d/%d\n", iVolume, m_iTargetVolume ); +} + + +void CBasePlayer::PostThink() +{ + if ( g_fGameOver ) return; // intermission or finale + + if ( !IsAlive( )) return; + + // buz test +// ALERT(at_console, "lighting is: %d, m_iWeaponFlash is: %d\n", Illumination(), m_iWeaponFlash); + + // Handle Tank controlling + if ( m_pTank != NULL ) + { // if they've moved too far from the gun, or selected a weapon, unuse the gun + if ( m_pTank->OnControls( pev ) && !pev->weaponmodel ) + { +//LRC - This is now handled with the Think function, by TrackTarget +// m_pTank->Use( this, this, USE_SET, 2 ); // try fire the gun + } + else + { // they've moved off the platform + m_pTank->Use( this, this, USE_OFF, 0 ); + m_pTank = NULL; + } + } + + // buz: spec tank + if (m_pSpecTank) + { + if ( m_pSpecTank->OnControls( pev ) && !pev->weaponmodel ) + { + m_pSpecTank->Use( this, this, USE_SET, 2 ); // try fire the gun + } + else + { // they've moved off the platform + m_pSpecTank->Use( this, this, USE_OFF, 0 ); + m_pSpecTank = NULL; + } + } + + +// do weapon stuff + ItemPostFrame( ); + + // buz: turn off gasmask when underwater + if (m_iGasMaskOn && pev->waterlevel == 3) + { + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, SOUND_GASMASK_OFF, 1.0, ATTN_NORM, 0, PITCH_NORM ); + m_iGasMaskOn = 0; + m_iUpdateGasMask = 1; + + // stop last sound + char sz[128]; + sprintf(sz, "items/gasm_breath%i.wav", m_iLastGasMaskSound); + STOP_SOUND( ENT(pev), CHAN_VOICE, sz ); + } + + // buz: play gasmask sounds + if (gpGlobals->time > m_flNextBreathTime && m_iGasMaskOn) + { + m_iLastGasMaskSound = RANDOM_LONG( 1, 4 ); + char sz[128]; + sprintf(sz, "items/gasm_breath%i.wav", m_iLastGasMaskSound); + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, sz, 1.0, RANDOM_FLOAT( 0.7, 1 ), 0, PITCH_NORM ); + m_flNextBreathTime = gpGlobals->time + RANDOM_FLOAT( 5, 7 ); + } + +// check to see if player landed hard enough to make a sound +// falling farther than half of the maximum safe distance, but not as far a max safe distance will +// play a bootscrape sound, and no damage will be inflicted. Fallling a distance shorter than half +// of maximum safe distance will make no sound. Falling farther than max safe distance will play a +// fallpain sound, and damage will be inflicted based on how far the player fell + + if ( (FBitSet(pev->flags, FL_ONGROUND)) && (pev->health > 0) && m_flFallVelocity >= PLAYER_FALL_PUNCH_THRESHHOLD ) + { + // ALERT ( at_console, "%f\n", m_flFallVelocity ); + + if (pev->watertype == CONTENTS_WATER) + { + // Did he hit the world or a non-moving entity? + // BUG - this happens all the time in water, especially when + // BUG - water has current force + // if ( !pev->groundentity || VARS(pev->groundentity)->velocity.z == 0 ) + // EMIT_SOUND(ENT(pev), CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM); + } + else if ( m_flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED ) + {// after this point, we start doing damage + + float flFallDamage = g_pGameRules->FlPlayerFallDamage( this ); + + if ( flFallDamage > pev->health ) + {//splat + // note: play on item channel because we play footstep landing on body channel + EMIT_SOUND(ENT(pev), CHAN_ITEM, "common/bodysplat.wav", 1, ATTN_NORM); + } + + if ( flFallDamage > 0 ) + { + TakeDamage(VARS(eoNullEntity), VARS(eoNullEntity), flFallDamage, DMG_FALL ); + pev->punchangle.x = 0; + } + } + + if ( IsAlive() ) + { + SetAnimation( PLAYER_WALK ); + } + } + + if (FBitSet(pev->flags, FL_ONGROUND)) + { + if (m_flFallVelocity > 64 && !g_pGameRules->IsMultiplayer()) + { + CSoundEnt::InsertSound ( bits_SOUND_PLAYER, pev->origin, m_flFallVelocity, 0.2 ); + // ALERT( at_console, "fall %f\n", m_flFallVelocity ); + } + m_flFallVelocity = 0; + } + + // select the proper animation for the player character + if ( IsAlive() ) + { + if (!pev->velocity.x && !pev->velocity.y) + SetAnimation( PLAYER_IDLE ); + else if ((pev->velocity.x || pev->velocity.y) && (FBitSet(pev->flags, FL_ONGROUND))) + SetAnimation( PLAYER_WALK ); + else if (pev->waterlevel > 1) + SetAnimation( PLAYER_WALK ); + } + + // calc gait animation + StudioGaitFrameAdvance( ); + + // calc player animation + StudioFrameAdvance( ); + + CheckPowerups(pev); + + UpdatePlayerSound(); + + // Track button info so we can detect 'pressed' and 'released' buttons next frame + m_afButtonLast = pev->button; +} + + +// checks if the spot is clear of players +BOOL IsSpawnPointValid( CBaseEntity *pPlayer, CBaseEntity *pSpot ) +{ + CBaseEntity *ent = NULL; + + if ( pSpot->GetState( pPlayer ) != STATE_ON ) + { + return FALSE; + } + + while ( (ent = UTIL_FindEntityInSphere( ent, pSpot->pev->origin, 128 )) != NULL ) + { + // if ent is a client, don't spawn on 'em + if ( ent->IsPlayer() && ent != pPlayer ) + return FALSE; + } + + return TRUE; +} + + +DLL_GLOBAL CBaseEntity *g_pLastSpawn; +//LRC- moved to cbase.h +//inline int FNullEnt( CBaseEntity *ent ) { return (ent == NULL) || FNullEnt( ent->edict() ); } + +/* +============ +EntSelectSpawnPoint + +Returns the entity to spawn at + +USES AND SETS GLOBAL g_pLastSpawn +============ +*/ +edict_t *EntSelectSpawnPoint( CBaseEntity *pPlayer ) +{ + CBaseEntity *pSpot; + edict_t *player; + + player = pPlayer->edict(); + +// choose a info_player_deathmatch point + if (g_pGameRules->IsCoOp()) + { + pSpot = UTIL_FindEntityByClassname( g_pLastSpawn, "info_player_coop"); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + pSpot = UTIL_FindEntityByClassname( g_pLastSpawn, "info_player_start"); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + } + else if ( g_pGameRules->IsDeathmatch() ) + { + pSpot = g_pLastSpawn; + // Randomize the start spot + for ( int i = RANDOM_LONG(1,5); i > 0; i-- ) + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + if ( FNullEnt( pSpot ) ) // skip over the null point + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + + CBaseEntity *pFirstSpot = pSpot; + + do + { + if ( pSpot ) + { + // check if pSpot is valid + if ( IsSpawnPointValid( pPlayer, pSpot ) ) + { + if ( pSpot->pev->origin == Vector( 0, 0, 0 ) ) + { + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + continue; + } + + // if so, go to pSpot + goto ReturnSpot; + } + } + // increment pSpot + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + } while ( pSpot != pFirstSpot ); // loop if we're not back to the start + + // we haven't found a place to spawn yet, so kill any guy at the first spawn point and spawn there + if ( !FNullEnt( pSpot ) ) + { + CBaseEntity *ent = NULL; + while ( (ent = UTIL_FindEntityInSphere( ent, pSpot->pev->origin, 128 )) != NULL ) + { + // if ent is a client, kill em (unless they are ourselves) + if ( ent->IsPlayer() && !(ent->edict() == player) ) + ent->TakeDamage( VARS(INDEXENT(0)), VARS(INDEXENT(0)), 300, DMG_GENERIC ); + } + goto ReturnSpot; + } + } + + // If startspot is set, (re)spawn there. + if ( FStringNull( gpGlobals->startspot ) || !strlen(STRING(gpGlobals->startspot))) + { + pSpot = UTIL_FindEntityByClassname(NULL, "info_player_start"); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + } + else + { + pSpot = UTIL_FindEntityByTargetname( NULL, STRING(gpGlobals->startspot) ); + if ( !FNullEnt(pSpot) ) + goto ReturnSpot; + } + +ReturnSpot: + if ( FNullEnt( pSpot ) ) + { + ALERT(at_error, "PutClientInServer: no info_player_start on level"); + return INDEXENT(0); + } + + g_pLastSpawn = pSpot; + return pSpot->edict(); +} + +void CBasePlayer::Spawn( void ) +{ +// ALERT(at_console, "PLAYER spawns at time %f\n", gpGlobals->time); + +// ALERT(at_console, "KTyJIxy DaeT MHe CuJIbI!\n"); + + pev->classname = MAKE_STRING("player"); + pev->health = 100; + pev->armorvalue = 0; + pev->takedamage = DAMAGE_AIM; + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_WALK; + pev->max_health = pev->health; + pev->flags &= FL_PROXY; // keep proxy flag sey by engine + pev->flags |= FL_CLIENT; + pev->air_finished = gpGlobals->time + 12; + pev->dmg = 2; // initial water damage + pev->deadflag = DEAD_NO; + pev->dmg_take = 0; + pev->dmg_save = 0; + pev->friction = 1.0; + pev->gravity = 1.0; + m_bitsHUDDamage = -1; + m_bitsDamageType = 0; + m_afPhysicsFlags = 0; + m_fLongJump = FALSE;// no longjump module. + + m_flStaminaValue = 100; + + // buz + m_iGasMaskOn = 0; + m_flGasMaskTime = 0; +// m_iJumpHeight = 100; +// g_engfuncs.pfnSetPhysicsKeyValue( edict(), "jh", "100" ); + SetJumpHeight(100); + + // buz rain + Rain_dripsPerSecond = 0; + Rain_windX = 0; + Rain_windY = 0; + Rain_randX = 0; + Rain_randY = 0; + Rain_ideal_dripsPerSecond = 0; + Rain_ideal_windX = 0; + Rain_ideal_windY = 0; + Rain_ideal_randX = 0; + Rain_ideal_randY = 0; + Rain_endFade = 0; + Rain_nextFadeUpdate = 0; + + g_engfuncs.pfnSetPhysicsKeyValue( edict(), "slj", "0" ); + g_engfuncs.pfnSetPhysicsKeyValue( edict(), "hl", "1" ); + + pev->fov = m_iFOV = 0;// init field of view. + m_iClientFOV = -1; // make sure fov reset is sent + + m_flClientBlurAmount = -1.0f; + m_iClientStamina = -1; + m_flNextDecalTime = 0;// let this player decal as soon as he spawns. + m_flClientLevelTime = 0; + m_flgeigerDelay = gpGlobals->time + 2.0; // wait a few seconds until user-defined message registrations + // are recieved by all clients + + m_flTimeStepSound = 0; + m_iStepLeft = 0; + m_flFieldOfView = 0.5;// some monsters use this to determine whether or not the player is looking at them. + + m_bloodColor = BLOOD_COLOR_RED; + m_flNextAttack = UTIL_WeaponTimeBase(); + StartSneaking(); + + m_iFlashBattery = 99; + m_flFlashLightTime = 1; // force first message + + // dont let uninitialized value here hurt the player + m_flFallVelocity = 0; + + g_pGameRules->SetDefaultPlayerTeam( this ); + edict_t* startent = g_pGameRules->GetPlayerSpawnSpot( this ); + + //LRC- support the new "start with HEV" flag... // buz - moved here + if (startent->v.spawnflags & 1) // the START WITH SUIT flag + SetBits( m_iHideHUD, ITEM_SUIT ); + + if (startent->v.spawnflags & 2) // buz - the START WITH head shield flag + SetBits( m_iHideHUD, ITEM_HEADSHIELD ); + + SET_MODEL(ENT(pev), "models/player.mdl"); + g_ulModelIndexPlayer = pev->modelindex; + pev->sequence = LookupActivity( ACT_IDLE ); + + if ( FBitSet(pev->flags, FL_DUCKING) ) + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); + else + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + + // buz: Paranoia's speed adjustment + pev->maxspeed = gSkillData.plrPrimaryMaxSpeed; + + pev->view_ofs = VEC_VIEW; + Precache(); + m_HackedGunPos = Vector( 0, 32, 0 ); + + if ( m_iPlayerSound == SOUNDLIST_EMPTY ) + { + ALERT ( at_debug, "Couldn't alloc player sound slot!\n" ); + } + + m_fNoPlayerSound = FALSE;// normal sound behavior. + + m_pLastItem = NULL; + m_fInitHUD = TRUE; + m_iClientHideHUD = -1; // force this to be recalculated + m_fWeapon = FALSE; + m_pClientActiveItem = NULL; + m_iClientBattery = -1; + + // reset all ammo values to 0 + for ( int i = 0; i < MAX_AMMO_SLOTS; i++ ) + { + m_rgAmmo[i] = 0; + m_rgAmmoLast[i] = 0; // client ammo values also have to be reset (the death hud clear messages does on the client side) + } + + m_lastx = m_lasty = 0; + + m_flNextChatTime = gpGlobals->time; + + g_pGameRules->PlayerSpawn( this ); +} + + +void CBasePlayer :: Precache( void ) +{ + // in the event that the player JUST spawned, and the level node graph + // was loaded, fix all of the node graph pointers before the game starts. + + // !!!BUGBUG - now that we have multiplayer, this needs to be moved! + if ( WorldGraph.m_fGraphPresent && !WorldGraph.m_fGraphPointersSet ) + { + if ( !WorldGraph.FSetGraphPointers() ) + { + ALERT ( at_debug, "**Graph pointers were not set!\n"); + } + else + { + ALERT ( at_debug, "**Graph Pointers Set!\n" ); + } + } + + // SOUNDS / MODELS ARE PRECACHED in ClientPrecache() (game specific) + // because they need to precache before any clients have connected + + // init geiger counter vars during spawn and each time + // we cross a level transition + + m_flgeigerRange = 1000; + m_igeigerRangePrev = 1000; + + m_bitsDamageType = 0; + m_bitsHUDDamage = -1; + + m_iClientBattery = -1; + + m_flClientBlurAmount = -1.0f; + m_flClientLevelTime = -1.0f; + + m_iTrain = TRAIN_NEW; + + m_iGoalNeedsUpdate = 2; // buz + m_iUpdateHeadShield = 2; // buz - 2 is turn on immediatly + m_iUpdateGasMask = 2; // g-cont - 2 is turn on immediatly + m_iInitMessagesSent = 0; // buz + + // Make sure any necessary user messages have been registered + LinkUserMessages(); + + m_iUpdateTime = 5; // won't update for 1/2 a second + + Rain_needsUpdate = 1; // buz rain + + m_iLastGasMaskSound = 1; + + if ( gInitHUD ) + m_fInitHUD = TRUE; +} + +int CBasePlayer::Save( CSave &save ) +{ + if ( !CBaseMonster::Save(save) ) + return 0; + + return save.WriteFields( "cPLAYER", "PLAYER", this, m_playerSaveData, ARRAYSIZE(m_playerSaveData) ); +} + + +// +// Marks everything as new so the player will resend this to the hud. +// +void CBasePlayer::RenewItems(void) +{ + +} + + +int CBasePlayer::Restore( CRestore &restore ) +{ + if ( !CBaseMonster::Restore(restore) ) + return 0; + + int status = restore.ReadFields( "PLAYER", this, m_playerSaveData, ARRAYSIZE(m_playerSaveData) ); + + SAVERESTOREDATA *pSaveData = (SAVERESTOREDATA *)gpGlobals->pSaveData; + // landmark isn't present. + if ( !pSaveData->fUseLandmark ) + { + ALERT( at_debug, "No Landmark:%s\n", pSaveData->szLandmarkName ); + + // default to normal spawn + edict_t* pentSpawnSpot = EntSelectSpawnPoint( this ); + pev->origin = VARS(pentSpawnSpot)->origin + Vector(0,0,1); + pev->angles = VARS(pentSpawnSpot)->angles; + } + pev->v_angle.z = 0; // Clear out roll + pev->angles = pev->v_angle; + + pev->fixangle = TRUE; // turn this way immediately + +// Copied from spawn() for now + m_bloodColor = BLOOD_COLOR_RED; + + g_ulModelIndexPlayer = pev->modelindex; + + if ( FBitSet(pev->flags, FL_DUCKING) ) + { + // Use the crouch HACK + //FixPlayerCrouchStuck( edict() ); + // Don't need to do this with new player prediction code. + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); + } + else + { + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + } + + g_engfuncs.pfnSetPhysicsKeyValue( edict(), "hl", "1" ); + + if ( m_fLongJump ) + { + g_engfuncs.pfnSetPhysicsKeyValue( edict(), "slj", "1" ); + } + else + { + g_engfuncs.pfnSetPhysicsKeyValue( edict(), "slj", "0" ); + } + + // buz: restore jump height + SetJumpHeight(m_iJumpHeight); + + RenewItems(); + + return status; +} + +void CBasePlayer::SelectNextItem( int iItem ) +{ + CBasePlayerItem *pItem; + + pItem = m_rgpPlayerItems[ iItem ]; + + if (!pItem) + return; + + if (pItem == m_pActiveItem) + { + // select the next one in the chain + pItem = m_pActiveItem->m_pNext; + if (! pItem) + { + return; + } + + CBasePlayerItem *pLast; + pLast = pItem; + while (pLast->m_pNext) + pLast = pLast->m_pNext; + + // relink chain + pLast->m_pNext = m_pActiveItem; + m_pActiveItem->m_pNext = NULL; + m_rgpPlayerItems[ iItem ] = pItem; + } + + ResetAutoaim( ); + + if ( m_pActiveItem ) + m_pActiveItem->Holster( ); + + QueueItem( pItem ); + + if ( m_pActiveItem ) + m_pActiveItem->Deploy( ); +} + +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 :: SelectItem( const char *pstr ) +{ + if ( !pstr ) return; + + if( m_pActiveItem && m_pActiveItem->m_fInReload ) + return; + + CBasePlayerItem *pItem = NULL; + + for( int i = 0; i < MAX_ITEM_TYPES; i++ ) + { + if( m_rgpPlayerItems[i] ) + { + pItem = m_rgpPlayerItems[i]; + + while( pItem ) + { + if(FStrEq( STRING( pItem->pev->netname ), pstr )) + break; + pItem = pItem->m_pNext; + } + } + + if (pItem) + break; + } + + if (!pItem) + return; + + + if (pItem == m_pActiveItem) + return; + + ResetAutoaim( ); + + if ( m_pActiveItem ) + m_pActiveItem->Holster( ); + + QueueItem( pItem ); + + if ( m_pActiveItem ) + m_pActiveItem->Deploy( ); +} + + +void CBasePlayer::SelectLastItem( void ) +{ + if ( !m_pLastItem ) + { + return; + } + + // buz fix: dont allow to switch to guns with no bullets + if ( !m_pLastItem->CanDeploy( )) + return; + + if ( m_pActiveItem && !m_pActiveItem->CanHolster() ) + { + return; + } + + if( m_pActiveItem && m_pActiveItem->m_fInReload ) + return; + + ResetAutoaim( ); + + if ( m_pActiveItem ) + m_pActiveItem->Holster( ); + + QueueItem( m_pLastItem ); + + if ( m_pActiveItem ) + m_pActiveItem->Deploy( ); +} + +//============================================== +// HasWeapons - do I have any weapons at all? +//============================================== +BOOL CBasePlayer::HasWeapons( void ) +{ + int i; + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + return TRUE; + } + } + + return FALSE; +} + +void CBasePlayer::SelectPrevItem( int iItem ) +{ +} + + +const char *CBasePlayer::TeamID( void ) +{ + if ( pev == NULL ) // Not fully connected yet + return ""; + + // return their team name + return m_szTeamName; +} + + +//============================================== +// !!!UNDONE:ultra temporary SprayCan entity to apply +// decal frame at a time. For PreAlpha CD +//============================================== +class CSprayCan : public CBaseEntity +{ +public: + void Spawn ( entvars_t *pevOwner ); + void Think( void ); + + virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; } +}; + +void CSprayCan::Spawn ( entvars_t *pevOwner ) +{ + pev->origin = pevOwner->origin + Vector ( 0 , 0 , 32 ); + pev->angles = pevOwner->v_angle; + pev->owner = ENT(pevOwner); + pev->frame = 0; + + SetNextThink( 0.1 ); + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/sprayer.wav", 1, ATTN_NORM); +} + +void CSprayCan::Think( void ) +{ + TraceResult tr; + float angle = 0.0; + + UTIL_MakeVectors( pev->angles ); + gpGlobals->trace_flags = FTRACE_IGNORE_ALPHATEST; + UTIL_TraceLine( pev->origin, pev->origin + gpGlobals->v_forward * 128, dont_ignore_monsters, pev->owner, &tr ); + + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + if( fabs( tr.vecPlaneNormal.z ) > 0.7f ) + angle = pev->angles.y; + + if( pEntity && GET_MODEL_PTR( pEntity->edict() )) + UTIL_StudioDecalTrace( &tr, "decal_ban4" ); + else + { + UTIL_TraceCustomDecal( &tr, "decal_ban4", angle ); + UTIL_DecalTrace( &tr, "decal_ban4" ); + } + UTIL_Remove( this ); +} + +class CBloodSplat : public CBaseEntity +{ +public: + void Spawn ( entvars_t *pevOwner ); + void Spray ( void ); +}; + +void CBloodSplat::Spawn ( entvars_t *pevOwner ) +{ + pev->origin = pevOwner->origin + Vector ( 0 , 0 , 32 ); + pev->angles = pevOwner->v_angle; + pev->owner = ENT(pevOwner); + + SetThink(&CBloodSplat:: Spray ); + SetNextThink( 0.1 ); +} + +void CBloodSplat::Spray ( void ) +{ + TraceResult tr; + + if ( g_Language != LANGUAGE_GERMAN ) + { + UTIL_MakeVectors(pev->angles); + gpGlobals->trace_flags = FTRACE_IGNORE_ALPHATEST; + UTIL_TraceLine ( pev->origin, pev->origin + gpGlobals->v_forward * 256, dont_ignore_monsters, pev->owner, & tr); + + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + + if( pEntity && GET_MODEL_PTR( pEntity->edict() )) + UTIL_BloodStudioDecalTrace( &tr, BLOOD_COLOR_RED ); + else UTIL_BloodDecalTrace( &tr, BLOOD_COLOR_RED ); + } + SetThink(&CBloodSplat:: SUB_Remove ); + SetNextThink( 0.1 ); +} + +//============================================== +void CBasePlayer :: GiveNamedItem( const char *pszName ) +{ + edict_t *pent = CREATE_NAMED_ENTITY( ALLOC_STRING( pszName )); + + if ( FNullEnt( pent )) + { + ALERT ( at_debug, "NULL Ent in GiveNamedItem!\n" ); + return; + } + + VARS( pent )->origin = pev->origin; + pent->v.spawnflags |= SF_NORESPAWN; + + DispatchSpawn( pent ); + DispatchTouch( pent, ENT( pev )); +} + +CBaseEntity *FindEntityForward( CBaseEntity *pMe ) +{ + TraceResult tr; + + UTIL_MakeVectors(pMe->pev->v_angle); + UTIL_TraceLine(pMe->pev->origin + pMe->pev->view_ofs,pMe->pev->origin + pMe->pev->view_ofs + gpGlobals->v_forward * 8192,dont_ignore_monsters, pMe->edict(), &tr ); + if ( tr.flFraction != 1.0 && !FNullEnt( tr.pHit) ) + { + CBaseEntity *pHit = CBaseEntity::Instance( tr.pHit ); + return pHit; + } + return NULL; +} + +BOOL CBasePlayer :: FlashlightIsOn( void ) +{ + return FBitSet(pev->effects, EF_DIMLIGHT); +} + +void CBasePlayer :: FlashlightTurnOn( void ) +{ + if ( !g_pGameRules->FAllowFlashlight() ) + { + return; + } + + if ( FBitSet( m_iHideHUD, ITEM_SUIT ) ) + { + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, SOUND_FLASHLIGHT_ON, 1.0, ATTN_NORM, 0, PITCH_NORM ); + + SetBits(pev->effects, EF_DIMLIGHT); + MESSAGE_BEGIN( MSG_ONE, gmsgFlashlight, NULL, pev ); + WRITE_BYTE(1); + WRITE_BYTE(m_iFlashBattery); + MESSAGE_END(); + + m_flFlashLightTime = FLASH_DRAIN_TIME + gpGlobals->time; + + } +} + +void CBasePlayer :: FlashlightTurnOff( void ) +{ + if (FlashlightIsOn()) + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, SOUND_FLASHLIGHT_OFF, 1.0, ATTN_NORM, 0, PITCH_NORM ); + + ClearBits(pev->effects, EF_DIMLIGHT); + MESSAGE_BEGIN( MSG_ONE, gmsgFlashlight, NULL, pev ); + WRITE_BYTE(0); + WRITE_BYTE(m_iFlashBattery); + MESSAGE_END(); + + m_flFlashLightTime = FLASH_CHARGE_TIME + gpGlobals->time; +} + +// buz: gasmask turn on/off +void CBasePlayer :: ToggleGasMask( void ) +{ + if (!FBitSet( m_iHideHUD, ITEM_GASMASK ) || pev->waterlevel == 3) + return; // no gasmask + + if (gpGlobals->time < m_flGasMaskTime) + return; // not too fast + + if (m_iGasMaskOn) + { + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, SOUND_GASMASK_OFF, 1.0, ATTN_NORM, 0, PITCH_NORM ); + m_iGasMaskOn = 0; + + // stop last sound + char sz[128]; + sprintf(sz, "items/gasm_breath%i.wav", m_iLastGasMaskSound); + STOP_SOUND( ENT(pev), CHAN_VOICE, sz ); +// ALERT(at_console, "gasmask off\n"); + } + else + { + if (m_pActiveItem && m_pActiveItem->GetMode() == 3) + { + UTIL_ShowMessage("#GAS_AND_SCOPE", this ); + return; + } + if (m_iHeadShieldOn) + { + UTIL_ShowMessage("#GAS_AND_SHIELD", this ); + return; + } + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, SOUND_GASMASK_ON, 1.0, ATTN_NORM, 0, PITCH_NORM ); + m_iGasMaskOn = 1; + m_flNextBreathTime = gpGlobals->time + 3; + +// ALERT(at_console, "gasmask on\n"); + } + + m_flGasMaskTime = gpGlobals->time + 0.5; + m_iUpdateGasMask = 1; +} + +// buz: head shield turn on/off +void CBasePlayer :: ToggleHeadShield( void ) +{ + if (!FBitSet( m_iHideHUD, ITEM_HEADSHIELD )) + return; // no shield + + if (gpGlobals->time < m_flHeadShieldTime) + return; // not too fast + + if (m_iHeadShieldOn) + { + m_iHeadShieldOn = 0; + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, SOUND_SHIELD_OFF, 1.0, ATTN_NORM, 0, PITCH_NORM ); +// ALERT(at_console, "head shield off\n"); + } + else + { + if (m_pActiveItem && m_pActiveItem->GetMode() == 3) + { + UTIL_ShowMessage("#SCOPE_AND_SHIELD", this ); + return; + } + if (m_iGasMaskOn) + { + UTIL_ShowMessage("#GAS_AND_SHIELD", this ); + return; + } + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, SOUND_SHIELD_ON, 1.0, ATTN_NORM, 0, PITCH_NORM ); + m_iHeadShieldOn = 1; +// ALERT(at_console, "head shield on\n"); + } + + m_flHeadShieldTime = gpGlobals->time + 0.5; + m_iUpdateHeadShield = 1; +} + + +// buz +void CBasePlayer :: SendInitMessages( void ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + CBaseEntity *pEntity; + + if ( !pEdict ) + return; + + for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) + { + if ( pEdict->free ) // Not in use + continue; + + pEntity = CBaseEntity::Instance(pEdict); + if ( !pEntity ) + continue; + + pEntity->SendInitMessage( this ); + } +} + +/* +=============== +ForceClientDllUpdate + +When recording a demo, we need to have the server tell us the entire client state +so that the client side .dll can behave correctly. +Reset stuff so that the state is transmitted. +=============== +*/ +void CBasePlayer :: ForceClientDllUpdate( void ) +{ + m_iClientHealth = -1; + m_iClientStamina = -1; + m_iClientBattery = -1; + m_flClientBlurAmount = -1.0f; + m_iTrain |= TRAIN_NEW; // Force new train message. + m_fWeapon = FALSE; // Force weapon send + m_fKnownItem = FALSE; // Force weaponinit messages. + m_fInitHUD = TRUE; // Force HUD gmsgResetHUD message + + // Now force all the necessary messages + // to be sent. + UpdateClientData(); +} + +/* +============ +ImpulseCommands +============ +*/ +extern float g_flWeaponCheat; + +int firemode; + +void CBasePlayer::ImpulseCommands( ) +{ + TraceResult tr;// UNDONE: kill me! This is temporary for PreAlpha CDs + + // Handle use events + PlayerUse(); + + int iImpulse = (int)pev->impulse; + switch (iImpulse) + { + case 99: + { + + int iOn; + + if (!gmsgLogo) + { + iOn = 1; + gmsgLogo = REG_USER_MSG("Logo", 1); + } + else + { + iOn = 0; + } + + ASSERT( gmsgLogo > 0 ); + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsgLogo, NULL, pev ); + WRITE_BYTE(iOn); + MESSAGE_END(); + + if(!iOn) + gmsgLogo = 0; + break; + } + case 100: + // temporary flashlight for level designers + if ( FlashlightIsOn()) + { + FlashlightTurnOff(); + } + else + { + FlashlightTurnOn(); + } + break; + case 45: + if( m_pActiveItem ) + m_pActiveItem->WeaponToggleMode(); + break; + case 201:// paint decal + if( gpGlobals->time < m_flNextDecalTime ) + { + // too early! + break; + } + + UTIL_MakeVectors( pev->v_angle ); + gpGlobals->trace_flags = FTRACE_IGNORE_ALPHATEST; + UTIL_TraceLine( pev->origin + pev->view_ofs, pev->origin + pev->view_ofs + gpGlobals->v_forward * 128, dont_ignore_monsters, ENT(pev), & tr); + + if ( tr.flFraction != 1.0 ) + { + // line hit something, so paint a decal + m_flNextDecalTime = gpGlobals->time + decalfrequency.value; + CSprayCan *pCan = GetClassPtr((CSprayCan *)NULL); + pCan->Spawn( pev ); + } + break; + case 198: // gasmask + ToggleGasMask(); + break; + + case 204: // Demo recording, update client dll specific data again. + ForceClientDllUpdate(); + break; + default: + // check all of the cheat impulse commands now + CheatImpulseCommands( iImpulse ); + break; + } + + pev->impulse = 0; +} + +const char *pHitboxNames[] = +{ +"Generic", +"Head", +"Chest", +"Stomach", +"Left Arm", +"Right Arm", +"Left Leg", +"Right Leg", +"Unknown8", +"Unknown9", +"Unknown10", +"Unknown11", +"Unknown12", +"Unknown13", +"Unknown14", +"Unknown15", +"", +}; + +//========================================================= +//========================================================= +void CBasePlayer::CheatImpulseCommands( int iImpulse ) +{ +#if !defined( HLDEMO_BUILD ) + if ( g_flWeaponCheat == 0.0 ) + { + return; + } + + CBaseEntity *pEntity; + TraceResult tr; + + switch ( iImpulse ) + { + case 76: + { + if (!giPrecacheGrunt) + { + giPrecacheGrunt = 1; + ALERT(at_debug, "You must now restart to use Grunt-o-matic.\n"); + } + else + { + UTIL_MakeVectors( Vector( 0, pev->v_angle.y, 0 ) ); + Create("monster_human_grunt", pev->origin + gpGlobals->v_forward * 128, pev->angles); + } + break; + } + + case 90: //LRC - send USE_TOGGLE + { + char *impulsetarget = (char *)CVAR_GET_STRING( "sohl_impulsetarget" ); + if (impulsetarget) + FireTargets(impulsetarget, this, this, USE_TOGGLE, 0); + break; + } + case 91: //LRC - send USE_ON + { + char *impulsetarget = (char *)CVAR_GET_STRING( "sohl_impulsetarget" ); + if (impulsetarget) + FireTargets(impulsetarget, this, this, USE_ON, 0); + break; + } + case 92: //LRC - send USE_OFF + { + char *impulsetarget = (char *)CVAR_GET_STRING( "sohl_impulsetarget" ); + if (impulsetarget) + FireTargets(impulsetarget, this, this, USE_OFF, 0); + break; + } + + case 101: + { + gEvilImpulse101 = TRUE; + char *afile = (char *)LOAD_FILE( "scripts/weapons/impulse101.txt", NULL ); + char *pfile = afile; + char token[256]; + + while( pfile != NULL ) + { + // parsing impulse101.txt + pfile = COM_ParseFile( pfile, token ); + if( !Q_strlen( token )) continue; + GiveNamedItem( token ); + } + + if( afile ) FREE_FILE( afile ); + gEvilImpulse101 = FALSE; + } + break; + case 102: + // Gibbage!!! + CGib::SpawnRandomGibs( pev, 1, 1 ); + break; + + case 103: + // What the hell are you doing? + pEntity = FindEntityForward( this ); + if ( pEntity ) + { + CBaseMonster *pMonster = pEntity->MyMonsterPointer(); + if ( pMonster ) + pMonster->ReportAIState(); + } + break; + + case 104: + // Dump all of the global state varaibles (and global entity names) + gGlobalState.DumpGlobals(); + break; + + case 105:// player makes no sound for monsters to hear. + { + if ( m_fNoPlayerSound ) + { + ALERT ( at_debug, "Player is audible\n" ); + m_fNoPlayerSound = FALSE; + } + else + { + ALERT ( at_debug, "Player is silent\n" ); + m_fNoPlayerSound = TRUE; + } + break; + } + + case 106: + // Give me the classname and targetname of this entity. + pEntity = FindEntityForward( this ); + if ( pEntity ) + { + ALERT ( at_debug, "Classname: %s", STRING( pEntity->pev->classname ) ); + + if ( !FStringNull ( pEntity->pev->targetname ) ) + { + ALERT ( at_debug, " - Targetname: %s\n", STRING( pEntity->pev->targetname ) ); + } + else + { + ALERT ( at_debug, " - TargetName: No Targetname\n" ); + } + + ALERT ( at_debug, "Model: %s\n", STRING( pEntity->pev->model ) ); + if ( pEntity->pev->globalname ) + ALERT ( at_debug, "Globalname: %s\n", STRING( pEntity->pev->globalname ) ); + ALERT(at_debug, "State: %s\n", GetStringForState( pEntity->GetState() )); //LRC + ALERT ( at_debug, "rendermode: %d\n", pEntity->pev->rendermode ); + ALERT ( at_debug, "renderamt: %g\n", pEntity->pev->renderamt ); + ALERT ( at_debug, "renderfx: %d\n", pEntity->pev->renderfx ); + ALERT ( at_debug, "origin: %g %g %g\n", pEntity->pev->origin.x, pEntity->pev->origin.y, pEntity->pev->origin.z ); + ALERT ( at_debug, "angles: %g %g %g\n", pEntity->pev->angles.x, pEntity->pev->angles.y, pEntity->pev->angles.z ); + } + break; + + case 107: + { + TraceResult tr; + + edict_t *pWorld = g_engfuncs.pfnPEntityOfEntIndex( 0 ); + + Vector start = pev->origin + pev->view_ofs; + Vector end = start + gpGlobals->v_forward * 1024; + UTIL_TraceLine( start, end, dont_ignore_monsters, edict(), &tr ); + + if( tr.pHit ) pWorld = tr.pHit; + + if( pWorld->v.solid == SOLID_CUSTOM ) + { + // hitting env_static, so tr.surf may be valid + if( tr.pMat ) + { + char basename[64], texname[64]; + + COM_FileBase( STRING( pWorld->v.model ), basename ); + COM_FileBase( tr.pMat->pSource->name, texname ); + + ALERT( at_debug, "Material: %s/%s\n", basename, texname ); + } + } + else + { + const char *pTextureName = TRACE_TEXTURE( pWorld, start, end ); + if( pTextureName ) ALERT( at_debug, "Material: %s\n", pTextureName ); + } + } + break; + case 195:// show shortest paths for entire level to nearest node + { + Create("node_viewer_fly", pev->origin, pev->angles); + } + break; + case 196:// show shortest paths for entire level to nearest node + { + Create("node_viewer_large", pev->origin, pev->angles); + } + break; + case 197:// show shortest paths for entire level to nearest node + { + Create("node_viewer_human", pev->origin, pev->angles); + } + break; + case 199:// show nearest node and all connections + { + ALERT ( at_debug, "%d\n", WorldGraph.FindNearestNode ( pev->origin, bits_NODE_GROUP_REALM ) ); + WorldGraph.ShowNodeConnections ( WorldGraph.FindNearestNode ( pev->origin, bits_NODE_GROUP_REALM ) ); + } + break; + case 202:// Random blood splatter + UTIL_MakeVectors(pev->v_angle); + gpGlobals->trace_flags = FTRACE_IGNORE_ALPHATEST; + UTIL_TraceLine ( pev->origin + pev->view_ofs, pev->origin + pev->view_ofs + gpGlobals->v_forward * 256, dont_ignore_monsters, ENT(pev), & tr); + + if ( tr.flFraction != 1.0 ) + {// line hit something, so paint a decal + CBloodSplat *pBlood = GetClassPtr((CBloodSplat *)NULL); + pBlood->Spawn( pev ); + } + break; + case 203:// remove creature. + pEntity = FindEntityForward( this ); + if ( pEntity ) + { + if ( pEntity->pev->takedamage ) + { + pEntity->SetThink(&CBaseEntity::SUB_Remove); + pEntity->SetNextThink( 0.1 ); + } + else UTIL_Remove( pEntity ); + } + break; + } +#endif // HLDEMO_BUILD +} + +// +// Add a weapon to the player (Item == Weapon == Selectable Object) +// +int CBasePlayer :: AddPlayerItem( CBasePlayerItem *pItem ) +{ + CBasePlayerItem *pInsert; + + pInsert = m_rgpPlayerItems[pItem->iItemSlot()]; + + while( pInsert ) + { + if ( FStrEq( STRING( pInsert->pev->netname ), STRING( pItem->pev->netname ) )) + { + if (pItem->AddDuplicate( pInsert )) + { + g_pGameRules->PlayerGotWeapon ( this, pItem ); + pItem->CheckRespawn(); + + // ugly hack to update clip w/o an update clip message + pInsert->UpdateItemInfo( ); + if (m_pActiveItem) + m_pActiveItem->UpdateItemInfo( ); + + pItem->Kill( ); + } + else if (gEvilImpulse101) + { + // FIXME: remove anyway for deathmatch testing + pItem->Kill( ); + } + return FALSE; + } + pInsert = pInsert->m_pNext; + } + + + if (pItem->AddToPlayer( this )) + { + g_pGameRules->PlayerGotWeapon ( this, pItem ); + pItem->CheckRespawn(); + + pItem->m_pNext = m_rgpPlayerItems[pItem->iItemSlot()]; + m_rgpPlayerItems[pItem->iItemSlot()] = pItem; + + // should we switch to this item? + if ( g_pGameRules->FShouldSwitchWeapon( this, pItem ) ) + { + SwitchWeapon( pItem ); + } + + return TRUE; + } + else if (gEvilImpulse101) + { + // FIXME: remove anyway for deathmatch testing + pItem->Kill( ); + } + return FALSE; +} + + + +int CBasePlayer::RemovePlayerItem( CBasePlayerItem *pItem ) +{ + if (m_pActiveItem == pItem) + { + ResetAutoaim( ); + pItem->Holster( true ); + pItem->DontThink();// crowbar may be trying to swing again, etc. + pItem->SetThink( NULL ); + m_pActiveItem = NULL; + pev->viewmodel = 0; + pev->weaponmodel = 0; + } + else if ( m_pLastItem == pItem ) + m_pLastItem = NULL; + + CBasePlayerItem *pPrev = m_rgpPlayerItems[pItem->iItemSlot()]; + + if (pPrev == pItem) + { + m_rgpPlayerItems[pItem->iItemSlot()] = pItem->m_pNext; + return TRUE; + } + else + { + while (pPrev && pPrev->m_pNext != pItem) + { + pPrev = pPrev->m_pNext; + } + if (pPrev) + { + pPrev->m_pNext = pItem->m_pNext; + return TRUE; + } + } + return FALSE; +} + + +// +// Returns the unique ID for the ammo, or -1 if error +// +int CBasePlayer :: GiveAmmo( int iCount, const char *szAmmoName ) +{ + if( !szAmmoName || !*szAmmoName ) + { + // no ammo. + return -1; + } + + AmmoInfo *pAmmo = UTIL_FindAmmoType( szAmmoName ); + + if( !pAmmo || !g_pGameRules->CanHaveAmmo( this, szAmmoName, pAmmo->iMaxCarry )) + { + // game rules say I can't have any more of this ammo type. + return -1; + } + + int i = 0; + + i = GetAmmoIndex( szAmmoName ); + + if ( i < 0 || i >= MAX_AMMO_SLOTS ) + return -1; + + int iAdd = Q_min( iCount, pAmmo->iMaxCarry - m_rgAmmo[i] ); + if ( iAdd < 1 ) + return i; + + m_rgAmmo[ i ] += iAdd; + + + if ( gmsgAmmoPickup ) // make sure the ammo messages have been linked first + { + // Send the message that ammo has been picked up + MESSAGE_BEGIN( MSG_ONE, gmsgAmmoPickup, NULL, pev ); + WRITE_BYTE( GetAmmoIndex( szAmmoName )); // ammo ID + WRITE_BYTE( iAdd ); // amount + WRITE_STRING( szAmmoName ); // buz: send also ammo name to display in hud.. + MESSAGE_END(); + } + + return i; +} + +/* +============ +ItemPreFrame + +Called every frame by the player PreThink +============ +*/ +void CBasePlayer::ItemPreFrame() +{ + if ( gpGlobals->time < m_flNextAttack ) + return; + + if ( !m_pActiveItem ) + { + // last item needs to playing holster again + if( m_pLastItem && m_pLastItem->WaitForHolster( )) + { + m_pLastItem->Holster(); + } + else if( m_pNextItem ) + { + m_pActiveItem = m_pNextItem; + m_pActiveItem->Deploy(); + m_pNextItem = NULL; + } + } + + if ( !m_pActiveItem ) + return; + + m_pActiveItem->ItemPreFrame( ); +} + + +/* +============ +ItemPostFrame + +Called every frame by the player PostThink +============ +*/ +void CBasePlayer::ItemPostFrame() +{ + // buz test +// ALERT(at_console, "pl speed: %f\n", pev->maxspeed); + + // check if the player is using a tank + if ( m_pTank != NULL ) + return; + + // buz: spec tank + if ( m_pSpecTank ) + return; + + if ( gpGlobals->time < m_flNextAttack ) + return; + + ImpulseCommands(); + + if (!m_pActiveItem) + return; + + m_pActiveItem->ItemPostFrame( ); +} + +int CBasePlayer::AmmoInventory( int iAmmoIndex ) +{ + if (iAmmoIndex == -1) + { + return -1; + } + + return m_rgAmmo[ iAmmoIndex ]; +} + +int CBasePlayer :: GetAmmoIndex( const char *psz ) +{ + if( !psz ) return -1; + + for( int i = 1; i < MAX_AMMO_SLOTS; i++ ) + { + if( !CBasePlayerAmmo::AmmoInfoArray[i].pszName ) + continue; + + if( !Q_stricmp( CBasePlayerAmmo::AmmoInfoArray[i].pszName, psz )) + return i; + } + + return -1; +} + +const char *CBasePlayer :: GetAmmoName(int index ) +{ + if( index == -1 ) + return NULL; + + return CBasePlayerAmmo :: AmmoInfoArray[index].pszName; +} + +// Called from UpdateClientData +// makes sure the client has all the necessary ammo info, if values have changed +void CBasePlayer::SendAmmoUpdate(void) +{ + for (int i=0; i < MAX_AMMO_SLOTS;i++) + { + if (m_rgAmmo[i] != m_rgAmmoLast[i]) + { + m_rgAmmoLast[i] = m_rgAmmo[i]; + + ASSERT( m_rgAmmo[i] >= 0 ); + ASSERT( m_rgAmmo[i] < 255 ); + + // send "Ammo" update message + MESSAGE_BEGIN( MSG_ONE, gmsgAmmoX, NULL, pev ); + WRITE_BYTE( i ); + WRITE_BYTE( max( min( m_rgAmmo[i], 254 ), 0 ) ); // clamp the value to one byte + MESSAGE_END(); + } + } +} + +/* +========================================================= + UpdateClientData + +resends any changed player HUD info to the client. +Called every frame by Player::PreThink +Also called at start of demo recording and playback by +ForceClientDllUpdate to ensure the demo gets messages +reflecting all of the HUD state info. +========================================================= +*/ +void CBasePlayer :: UpdateClientData( void ) +{ + if (m_fInitHUD) + { + m_fInitHUD = FALSE; + gInitHUD = FALSE; + + MESSAGE_BEGIN( MSG_ONE, gmsgResetHUD, NULL, pev ); + WRITE_BYTE( 0 ); + MESSAGE_END(); + + if ( !m_fGameHUDInitialized ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgInitHUD, NULL, pev ); + MESSAGE_END(); + + g_pGameRules->InitHUD( this ); + m_fGameHUDInitialized = TRUE; + if ( g_pGameRules->IsMultiplayer() ) + { + FireTargets( "game_playerjoin", this, this, USE_TOGGLE, 0 ); + } + } + + FireTargets( "game_playerspawn", this, this, USE_TOGGLE, 0 ); + + InitStatusBar(); + } + + if ( m_iHideHUD != m_iClientHideHUD ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgHideWeapon, NULL, pev ); + WRITE_BYTE( m_iHideHUD ); + MESSAGE_END(); + + m_iClientHideHUD = m_iHideHUD; + } + + if ( m_iFOV != m_iClientFOV ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgSetFOV, NULL, pev ); + WRITE_BYTE( m_iFOV ); + MESSAGE_END(); + + // cache FOV change at end of function, so weapon updates can see that FOV has changed + } + + // HACKHACK -- send the message to display the game title + if (gDisplayTitle) + { + MESSAGE_BEGIN( MSG_ONE, gmsgShowGameTitle, NULL, pev ); + WRITE_BYTE( 0 ); + MESSAGE_END(); + gDisplayTitle = 0; + } + + if (pev->health != m_iClientHealth) + { + int iHealth = max( pev->health, 0 ); // make sure that no negative health values are sent + + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsgHealth, NULL, pev ); + WRITE_BYTE( iHealth ); + MESSAGE_END(); + + m_iClientHealth = pev->health; + } + if ((int)m_flStaminaValue != m_iClientStamina) + { + int iStamina = (int)m_flStaminaValue; + + // send "stamina" update message + MESSAGE_BEGIN( MSG_ONE, gmsgStamina, NULL, pev ); + WRITE_SHORT(iStamina ); + MESSAGE_END(); + + m_iClientStamina = iStamina; + } + + if( m_flBlurAmount != m_flClientBlurAmount ) + { + m_flClientBlurAmount = m_flBlurAmount; + + MESSAGE_BEGIN( MSG_ONE, gmsgBlurEffect, NULL, pev ); + WRITE_SHORT( m_flBlurAmount * 10000 ); + MESSAGE_END(); + } + + if (pev->armorvalue != m_iClientBattery) + { + m_iClientBattery = pev->armorvalue; + + ASSERT( gmsgBattery > 0 ); + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsgBattery, NULL, pev ); + WRITE_SHORT( (int)pev->armorvalue); + MESSAGE_END(); + } + + if (pev->dmg_take || pev->dmg_save || m_bitsHUDDamage != m_bitsDamageType) + { + // Comes from inside me if not set + Vector damageOrigin = pev->origin; + // send "damage" message + // causes screen to flash, and pain compass to show direction of damage + edict_t *other = pev->dmg_inflictor; + if ( other ) + { + CBaseEntity *pEntity = CBaseEntity::Instance(other); + if ( pEntity ) + damageOrigin = pEntity->Center(); + } + + // only send down damage type that have hud art + int visibleDamageBits = m_bitsDamageType & DMG_SHOWNHUD; + + MESSAGE_BEGIN( MSG_ONE, gmsgDamage, NULL, pev ); + WRITE_BYTE( pev->dmg_save ); + WRITE_BYTE( pev->dmg_take ); + WRITE_LONG( visibleDamageBits ); + WRITE_COORD( damageOrigin.x ); + WRITE_COORD( damageOrigin.y ); + WRITE_COORD( damageOrigin.z ); + MESSAGE_END(); + + pev->dmg_take = 0; + pev->dmg_save = 0; + m_bitsHUDDamage = m_bitsDamageType; + + // Clear off non-time-based damage indicators + m_bitsDamageType &= DMG_TIMEBASED; + } + + + // RAIN START + // calculate and update rain fading + if (Rain_endFade > 0) + { + if (gpGlobals->time < Rain_endFade) + { // we're in fading process + if (Rain_nextFadeUpdate <= gpGlobals->time) + { + int secondsLeft = Rain_endFade - gpGlobals->time + 1; + + Rain_dripsPerSecond += (Rain_ideal_dripsPerSecond - Rain_dripsPerSecond) / secondsLeft; + Rain_windX += (Rain_ideal_windX - Rain_windX) / (float)secondsLeft; + Rain_windY += (Rain_ideal_windY - Rain_windY) / (float)secondsLeft; + Rain_randX += (Rain_ideal_randX - Rain_randX) / (float)secondsLeft; + Rain_randY += (Rain_ideal_randY - Rain_randY) / (float)secondsLeft; + + Rain_nextFadeUpdate = gpGlobals->time + 1; // update once per second + Rain_needsUpdate = 1; + + ALERT(at_aiconsole, "Rain fading: curdrips: %i, idealdrips %i\n", Rain_dripsPerSecond, Rain_ideal_dripsPerSecond); + } + } + else + { // finish fading process + Rain_nextFadeUpdate = 0; + Rain_endFade = 0; + + Rain_dripsPerSecond = Rain_ideal_dripsPerSecond; + Rain_windX = Rain_ideal_windX; + Rain_windY = Rain_ideal_windY; + Rain_randX = Rain_ideal_randX; + Rain_randY = Rain_ideal_randY; + Rain_needsUpdate = 1; + + ALERT(at_aiconsole, "Rain fading finished at %i drips\n", Rain_dripsPerSecond); + } + } + + + // send rain message + if (Rain_needsUpdate) + { + //search for rain_settings entity + edict_t *pFind; + pFind = FIND_ENTITY_BY_CLASSNAME( NULL, "rain_settings" ); + if (!FNullEnt( pFind )) + { + // rain allowed on this map + CBaseEntity *pEnt = CBaseEntity::Instance( pFind ); + CRainSettings *pRainSettings = (CRainSettings *)pEnt; + + float raindistance = pRainSettings->Rain_Distance; + float rainheight = pRainSettings->pev->origin[2]; + int rainmode = pRainSettings->Rain_Mode; + + // search for constant rain_modifies + pFind = FIND_ENTITY_BY_CLASSNAME( NULL, "rain_modify" ); + while ( !FNullEnt( pFind ) ) + { + if (pFind->v.spawnflags & 1) + { + // copy settings to player's data and clear fading + CBaseEntity *pEnt = CBaseEntity::Instance( pFind ); + CRainModify *pRainModify = (CRainModify *)pEnt; + + Rain_dripsPerSecond = pRainModify->Rain_Drips; + Rain_windX = pRainModify->Rain_windX; + Rain_windY = pRainModify->Rain_windY; + Rain_randX = pRainModify->Rain_randX; + Rain_randY = pRainModify->Rain_randY; + + Rain_endFade = 0; + break; + } + pFind = FIND_ENTITY_BY_CLASSNAME( pFind, "rain_modify" ); + } + + MESSAGE_BEGIN(MSG_ONE, gmsgRainData, NULL, pev); + WRITE_SHORT(Rain_dripsPerSecond); + WRITE_COORD(raindistance); + WRITE_COORD(Rain_windX); + WRITE_COORD(Rain_windY); + WRITE_COORD(Rain_randX); + WRITE_COORD(Rain_randY); + WRITE_SHORT(rainmode); + WRITE_COORD(rainheight); + MESSAGE_END(); + + if (Rain_dripsPerSecond) + ALERT(at_aiconsole, "Sending enabling rain message\n"); + else + ALERT(at_aiconsole, "Sending disabling rain message\n"); + } + else + { // no rain on this map + Rain_dripsPerSecond = 0; + Rain_windX = 0; + Rain_windY = 0; + Rain_randX = 0; + Rain_randY = 0; + Rain_ideal_dripsPerSecond = 0; + Rain_ideal_windX = 0; + Rain_ideal_windY = 0; + Rain_ideal_randX = 0; + Rain_ideal_randY = 0; + Rain_endFade = 0; + Rain_nextFadeUpdate = 0; + + ALERT(at_aiconsole, "Clearing rain data\n"); + } + + Rain_needsUpdate = 0; + } + // RAIN END + + + // Update Flashlight + // buz: infinite flashlight +/* if ((m_flFlashLightTime) && (m_flFlashLightTime <= gpGlobals->time)) + { + if (FlashlightIsOn()) + { + if (m_iFlashBattery) + { + m_flFlashLightTime = FLASH_DRAIN_TIME + gpGlobals->time; + m_iFlashBattery--; + + if (!m_iFlashBattery) + FlashlightTurnOff(); + } + } + else + { + if (m_iFlashBattery < 100) + { + m_flFlashLightTime = FLASH_CHARGE_TIME + gpGlobals->time; + m_iFlashBattery++; + } + else + m_flFlashLightTime = 0; + } + + MESSAGE_BEGIN( MSG_ONE, gmsgFlashBattery, NULL, pev ); + WRITE_BYTE(m_iFlashBattery); + MESSAGE_END(); + }*/ + + + // buz: update gasmask + if (m_iUpdateGasMask) + { + if (m_iUpdateGasMask == 2) + { + if (m_iGasMaskOn) + { + MESSAGE_BEGIN( MSG_ONE, gmsgGasMask, NULL, pev ); + WRITE_BYTE(2); + MESSAGE_END(); + } + // dont send "fast off" update, because client sets it by default + } + else + { + if (m_iGasMaskOn) + { + MESSAGE_BEGIN( MSG_ONE, gmsgGasMask, NULL, pev ); + WRITE_BYTE(1); + MESSAGE_END(); + } + else + { + MESSAGE_BEGIN( MSG_ONE, gmsgGasMask, NULL, pev ); + WRITE_BYTE(0); + MESSAGE_END(); + } + } + m_iUpdateGasMask = 0; + } + + // buz: update head shield + // 0 off, 1 on, 2 fast on + if (m_iUpdateHeadShield) + { + if (m_iUpdateHeadShield == 2) + { + if (m_iHeadShieldOn) + { + MESSAGE_BEGIN( MSG_ONE, gmsgHeadShield, NULL, pev ); + WRITE_BYTE(2); + MESSAGE_END(); + } + // dont send "fast off" update, because client sets it by default + } + else + { + if (m_iHeadShieldOn) + { + MESSAGE_BEGIN( MSG_ONE, gmsgHeadShield, NULL, pev ); + WRITE_BYTE(1); + MESSAGE_END(); + } + else + { + MESSAGE_BEGIN( MSG_ONE, gmsgHeadShield, NULL, pev ); + WRITE_BYTE(0); + MESSAGE_END(); + } + } + m_iUpdateHeadShield = 0; + } + + // buz: update goal window + if (m_iGoalNeedsUpdate) + { + if (FStringNull(m_strCurrentGoalName)) + { + MESSAGE_BEGIN( MSG_ONE, gmsgTabPanel, NULL, pev ); + WRITE_BYTE(0); // turn off + MESSAGE_END(); + ALERT(at_aiconsole, "Player goal window cleared\n"); + } + else + { + int flags = 1; + if (!FStringNull(m_strCurrentGoalTitleName)) + flags |= TP_FL_TITLE; + + if (!FStringNull(m_strCurrentGoalImageName)) + flags |= TP_FL_IMAGE; + + if (m_iGoalNeedsUpdate != 2) + flags |= TP_FL_POPUP; + + MESSAGE_BEGIN( MSG_ONE, gmsgTabPanel, NULL, pev ); + WRITE_BYTE(flags); // show + WRITE_STRING(STRING(m_strCurrentGoalName)); + + if (flags & TP_FL_TITLE) + WRITE_STRING(STRING(m_strCurrentGoalTitleName)); + + if (flags & TP_FL_IMAGE) + WRITE_STRING(STRING(m_strCurrentGoalImageName)); + MESSAGE_END(); + ALERT(at_aiconsole, "Player goal description set\n"); + } + + m_iGoalNeedsUpdate = 0; + } + + // buz: send particle systems + if (!m_iInitMessagesSent) + { + SendInitMessages(); + m_iInitMessagesSent = 1; + + // initialize level-time here + m_flLevelTime = gGlobalState.EntityGetTime( MAKE_STRING( "GLOBAL_TIME" )); + } + + if (m_iTrain & TRAIN_NEW) + { + ASSERT( gmsgTrain > 0 ); + // send "health" update message + MESSAGE_BEGIN( MSG_ONE, gmsgTrain, NULL, pev ); + WRITE_BYTE(m_iTrain & 0xF); + MESSAGE_END(); + + m_iTrain &= ~TRAIN_NEW; + } + + // if time is used on a map + if( m_flLevelTime != -1.0f ) + { + m_flLevelTime += gpGlobals->frametime * GLOBAL_TIME_STEP; // evaluate level time + if( m_flLevelTime > 24.0f ) m_flLevelTime = 0.0f; // clamp the time + + // probably it will be think every frame + if( fabs( m_flClientLevelTime - m_flLevelTime ) > GLOBAL_TIME_STEP * GLOBAL_TIME_STEP ) + { +// ALERT( at_console, "CurTime: %g\n", m_flLevelTime ); + + MESSAGE_BEGIN( MSG_ONE, gmsgLevelTime, NULL, pev ); + WRITE_LONG( *(int *)&m_flLevelTime ); // convert float to long + MESSAGE_END(); + + gGlobalState.EntitySetTime( MAKE_STRING( "GLOBAL_TIME" ), m_flLevelTime ); + m_flClientLevelTime = m_flLevelTime; + } + } + + // + // New Weapon? + // + if (!m_fKnownItem) + { + m_fKnownItem = TRUE; + + // WeaponInit Message + // byte = # of weapons + // + // for each weapon: + // byte name str length (not including null) + // bytes... name + // byte Ammo Type + // byte Ammo2 Type + // byte bucket + // byte bucket pos + // byte flags + // ???? Icons + + // Send ALL the weapon info now + for (int i = 0; i < MAX_WEAPONS; i++) + { + ItemInfo& II = CBasePlayerItem::ItemInfoArray[i]; + + if ( !II.iId ) + continue; + + const char *pszName; + if (!II.pszName) + pszName = "Empty"; + else pszName = II.pszName; + + MESSAGE_BEGIN( MSG_ONE, gmsgWeaponList, NULL, pev ); + WRITE_STRING(pszName); // string weapon name + WRITE_BYTE(GetAmmoIndex(II.pszAmmo1)); // byte Ammo Type + WRITE_BYTE(II.iMaxAmmo1); // byte Max Ammo 1 + WRITE_BYTE(GetAmmoIndex(II.pszAmmo2)); // byte Ammo2 Type + WRITE_BYTE(II.iMaxAmmo2); // byte Max Ammo 2 + WRITE_BYTE(II.iSlot); // byte bucket + WRITE_BYTE(II.iPosition); // byte bucket pos + WRITE_BYTE(II.iId); // byte id (bit index into pev->weapons) + WRITE_SHORT( II.iFlags ); // short Flags + MESSAGE_END(); + } + } + + + SendAmmoUpdate(); + + // buz: update spec machinegun state + if (m_pSpecTank) + { + m_pSpecTank->Use( this, this, USE_SET, 3 ); // update client data + } + + // Update all the items + for ( int i = 0; i < MAX_ITEM_TYPES; i++ ) + { + if ( m_rgpPlayerItems[i] ) // each item updates it's successors + m_rgpPlayerItems[i]->UpdateClientData( this ); + } + + // buz: fix bug with crosshairs: send weapon update message if + // no one item didnt sent it before + if ( !m_fWeapon ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pev ); + WRITE_BYTE( 0 ); + WRITE_BYTE( 0 ); + WRITE_BYTE( 0 ); + MESSAGE_END(); + + // m_iClientClip = m_iClip; + // m_iClientWeaponState = state; + m_fWeapon = TRUE; + } + + // Cache and client weapon change + m_pClientActiveItem = m_pActiveItem; + m_iClientFOV = m_iFOV; + + // Update Status Bar + if ( m_flNextSBarUpdateTime < gpGlobals->time ) + { + UpdateStatusBar(); + m_flNextSBarUpdateTime = gpGlobals->time + 0.2; + } + + // Wargon: Åñëè êîñòþìà íåò, òî ôîíàðèê âûêëþ÷àåòñÿ. + if (!FBitSet( m_iHideHUD, ITEM_SUIT ) && FlashlightIsOn()) + FlashlightTurnOff(); + +// if ( pev->waterlevel == 3 && FlashlightIsOn()) +// FlashlightTurnOff(); + + // Wargon: Ñåðâåðíûé êîä èêîíêè þçà. + CBaseEntity *pObject = NULL; + CBaseEntity *pClosest = NULL; + Vector vecLOS; + float flMaxDot = VIEW_FIELD_NARROW; + float flDot; + int caps; + TraceResult tr; + UTIL_MakeVectors( pev->v_angle ); + UTIL_TraceLine( pev->origin + pev->view_ofs, pev->origin + pev->view_ofs + (gpGlobals->v_forward * PLAYER_SEARCH_RADIUS), dont_ignore_monsters, ENT(pev), &tr ); + if (tr.pHit) + { + pObject = CBaseEntity::Instance(tr.pHit); + if (!pObject || !(pObject->ObjectCaps() & (FCAP_IMPULSE_USE | FCAP_CONTINUOUS_USE | FCAP_ONOFF_USE)) || (pObject->ObjectCaps() & FCAP_HIDE_USE)) + pObject = NULL; + } + if (!pObject) + { + while ((pObject = UTIL_FindEntityInSphere( pObject, pev->origin, PLAYER_SEARCH_RADIUS )) != NULL) + { + caps = pObject->ObjectCaps(); + if (caps & (FCAP_IMPULSE_USE | FCAP_CONTINUOUS_USE | FCAP_ONOFF_USE) && !(caps & FCAP_ONLYDIRECT_USE) && !(pObject->ObjectCaps() & FCAP_HIDE_USE)) + { + CBaseMonster *pMonster = pObject->MyMonsterPointer(); + if (pMonster) + { + Vector mins, maxs; + pMonster->ExtractBbox(pMonster->pev->sequence, mins, maxs); + vecLOS = (((mins + maxs) * 0.5) + pMonster->pev->origin - (pev->origin + pev->view_ofs)); + vecLOS = UTIL_ClampVectorToBox( vecLOS, ((mins + maxs) * 0.5) ); + } + else + { + vecLOS = (VecBModelOrigin( pObject->pev ) - (pev->origin + pev->view_ofs)); + vecLOS = UTIL_ClampVectorToBox( vecLOS, pObject->pev->size * 0.5 ); + } + flDot = DotProduct (vecLOS, gpGlobals->v_forward); + if (flDot > flMaxDot || vecLOS == g_vecZero ) + { + pClosest = pObject; + flMaxDot = flDot; + } + } + } + pObject = pClosest; + + if( pObject && pObject->ObjectCaps() & FCAP_USE_ONLY ) + { + // make sure what item not blocked by entity + UTIL_TraceLine( pev->origin + pev->view_ofs, pObject->pev->origin, dont_ignore_monsters, ENT(pev), &tr ); + if( tr.flFraction < 1.0f ) pObject = NULL; // blocked + } + } + if (!pObject) + { + UTIL_TraceLine( pev->origin + pev->view_ofs, pev->origin + pev->view_ofs + (gpGlobals->v_forward * PLAYER_DISTUSE_RADIUS), dont_ignore_monsters, ENT(pev), &tr ); + if (tr.pHit) + { + pObject = CBaseEntity::Instance(tr.pHit); + if (!pObject || !(pObject->ObjectCaps() & FCAP_DISTANCE_USE) || (pObject->ObjectCaps() & FCAP_HIDE_USE)) + pObject = NULL; + } + } + if ((pObject) && !((m_pTank != NULL) || (m_pSpecTank))) + { + MESSAGE_BEGIN( MSG_ONE, gmsgCanUse, NULL, pev ); + WRITE_BYTE(1); + MESSAGE_END(); + } + else + { + MESSAGE_BEGIN( MSG_ONE, gmsgCanUse, NULL, pev ); + WRITE_BYTE(0); + MESSAGE_END(); + } +} + +//========================================================= +// buz: PlayerSetGoalDesc - sets goal description for this client +//========================================================= +void CBasePlayer::PlayerSetGoalDesc( string_t strindex, string_t title, string_t imgname ) +{ + m_strCurrentGoalName = strindex; + m_strCurrentGoalTitleName = title; + m_strCurrentGoalImageName = imgname; + m_iGoalNeedsUpdate = 1; +} + +//========================================================= +// FBecomeProne - Overridden for the player to set the proper +// physics flags when a barnacle grabs player. +//========================================================= +BOOL CBasePlayer :: FBecomeProne ( void ) +{ + m_afPhysicsFlags |= PFLAG_ONBARNACLE; + return TRUE; +} + +//========================================================= +// BarnacleVictimBitten - bad name for a function that is called +// by Barnacle victims when the barnacle pulls their head +// into its mouth. For the player, just die. +//========================================================= +void CBasePlayer :: BarnacleVictimBitten ( entvars_t *pevBarnacle ) +{ + TakeDamage ( pevBarnacle, pevBarnacle, pev->health + pev->armorvalue, DMG_SLASH | DMG_ALWAYSGIB ); +} + +//========================================================= +// BarnacleVictimReleased - overridden for player who has +// physics flags concerns. +//========================================================= +void CBasePlayer :: BarnacleVictimReleased ( void ) +{ + m_afPhysicsFlags &= ~PFLAG_ONBARNACLE; +} + + +//========================================================= +// Illumination +// return player light level plus virtual muzzle flash +//========================================================= +int CBasePlayer :: Illumination( void ) +{ + int iIllum = CBaseEntity::Illumination( ); + + iIllum += m_iWeaponFlash; + if (iIllum > 255) + return 255; + return iIllum; +} + + +void CBasePlayer :: EnableControl(BOOL fControl) +{ + if (!fControl) + { + pev->flags |= FL_FROZEN; + pev->velocity = g_vecZero; //LRC - stop view bobbing + } + else + pev->flags &= ~FL_FROZEN; +} + + +#define DOT_1DEGREE 0.9998476951564 +#define DOT_2DEGREE 0.9993908270191 +#define DOT_3DEGREE 0.9986295347546 +#define DOT_4DEGREE 0.9975640502598 +#define DOT_5DEGREE 0.9961946980917 +#define DOT_6DEGREE 0.9945218953683 +#define DOT_7DEGREE 0.9925461516413 +#define DOT_8DEGREE 0.9902680687416 +#define DOT_9DEGREE 0.9876883405951 +#define DOT_10DEGREE 0.9848077530122 +#define DOT_15DEGREE 0.9659258262891 +#define DOT_20DEGREE 0.9396926207859 +#define DOT_25DEGREE 0.9063077870367 + +//========================================================= +// Autoaim +// set crosshair position to point to enemey +//========================================================= +Vector CBasePlayer :: GetAutoaimVector( float flDelta ) +{ + if (g_iSkillLevel == SKILL_HARD) + { + UTIL_MakeVectors( pev->v_angle + pev->punchangle ); + return gpGlobals->v_forward; + } + + Vector vecSrc = GetGunPosition( ); + float flDist = 8192; + + // always use non-sticky autoaim + // UNDONE: use sever variable to chose! + if (1 || g_iSkillLevel == SKILL_MEDIUM) + { + m_vecAutoAim = Vector( 0, 0, 0 ); + // flDelta *= 0.5; + } + + BOOL m_fOldTargeting = m_fOnTarget; + Vector angles = AutoaimDeflection(vecSrc, flDist, flDelta ); + + // update ontarget if changed + if ( !g_pGameRules->AllowAutoTargetCrosshair() ) + m_fOnTarget = 0; + else if (m_fOldTargeting != m_fOnTarget) + { + m_pActiveItem->UpdateItemInfo( ); + } + + if (angles.x > 180) + angles.x -= 360; + if (angles.x < -180) + angles.x += 360; + if (angles.y > 180) + angles.y -= 360; + if (angles.y < -180) + angles.y += 360; + + if (angles.x > 25) + angles.x = 25; + if (angles.x < -25) + angles.x = -25; + if (angles.y > 12) + angles.y = 12; + if (angles.y < -12) + angles.y = -12; + + + // always use non-sticky autoaim + // UNDONE: use sever variable to chose! + if (0 || g_iSkillLevel == SKILL_EASY) + { + m_vecAutoAim = m_vecAutoAim * 0.67 + angles * 0.33; + } + else + { + m_vecAutoAim = angles * 0.9; + } + + // m_vecAutoAim = m_vecAutoAim * 0.99; + + // Don't send across network if sv_aim is 0 + if ( g_psv_aim->value != 0 ) + { + if ( m_vecAutoAim.x != m_lastx || + m_vecAutoAim.y != m_lasty ) + { + SET_CROSSHAIRANGLE( edict(), -m_vecAutoAim.x, m_vecAutoAim.y ); + + m_lastx = m_vecAutoAim.x; + m_lasty = m_vecAutoAim.y; + } + } + + // ALERT( at_console, "%f %f\n", angles.x, angles.y ); + + UTIL_MakeVectors( pev->v_angle + pev->punchangle + m_vecAutoAim ); + return gpGlobals->v_forward; +} + + +Vector CBasePlayer :: AutoaimDeflection( Vector &vecSrc, float flDist, float flDelta ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + CBaseEntity *pEntity; + float bestdot; + Vector bestdir; + edict_t *bestent; + TraceResult tr; + + if ( g_psv_aim->value == 0 ) + { + m_fOnTarget = FALSE; + return g_vecZero; + } + + UTIL_MakeVectors( pev->v_angle + pev->punchangle + m_vecAutoAim ); + + // try all possible entities + bestdir = gpGlobals->v_forward; + bestdot = flDelta; // +- 10 degrees + bestent = NULL; + + m_fOnTarget = FALSE; + + UTIL_TraceLine( vecSrc, vecSrc + bestdir * flDist, dont_ignore_monsters, edict(), &tr ); + + + if ( tr.pHit && tr.pHit->v.takedamage != DAMAGE_NO) + { + // don't look through water + if (!((pev->waterlevel != 3 && tr.pHit->v.waterlevel == 3) + || (pev->waterlevel == 3 && tr.pHit->v.waterlevel == 0))) + { + if (tr.pHit->v.takedamage == DAMAGE_AIM) + m_fOnTarget = TRUE; + + return m_vecAutoAim; + } + } + + for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) + { + Vector center; + Vector dir; + float dot; + + if ( pEdict->free ) // Not in use + continue; + + if (pEdict->v.takedamage != DAMAGE_AIM) + continue; + if (pEdict == edict()) + continue; +// if (pev->team > 0 && pEdict->v.team == pev->team) +// continue; // don't aim at teammate + if ( !g_pGameRules->ShouldAutoAim( this, pEdict ) ) + continue; + + pEntity = Instance( pEdict ); + if (pEntity == NULL) + continue; + + if (!pEntity->IsAlive()) + continue; + + // don't look through water + if ((pev->waterlevel != 3 && pEntity->pev->waterlevel == 3) + || (pev->waterlevel == 3 && pEntity->pev->waterlevel == 0)) + continue; + + center = pEntity->BodyTarget( vecSrc ); + + dir = (center - vecSrc).Normalize( ); + + // make sure it's in front of the player + if (DotProduct (dir, gpGlobals->v_forward ) < 0) + continue; + + dot = fabs( DotProduct (dir, gpGlobals->v_right ) ) + + fabs( DotProduct (dir, gpGlobals->v_up ) ) * 0.5; + + // tweek for distance + dot *= 1.0 + 0.2 * ((center - vecSrc).Length() / flDist); + + if (dot > bestdot) + continue; // to far to turn + + UTIL_TraceLine( vecSrc, center, dont_ignore_monsters, edict(), &tr ); + if (tr.flFraction != 1.0 && tr.pHit != pEdict) + { + // ALERT( at_console, "hit %s, can't see %s\n", STRING( tr.pHit->v.classname ), STRING( pEdict->v.classname ) ); + continue; + } + + // don't shoot at friends + if (IRelationship( pEntity ) < 0) + { + if ( !pEntity->IsPlayer() && !g_pGameRules->IsDeathmatch()) + // ALERT( at_console, "friend\n"); + continue; + } + + // can shoot at this one + bestdot = dot; + bestent = pEdict; + bestdir = dir; + } + + if (bestent) + { + bestdir = UTIL_VecToAngles (bestdir); + bestdir.x = -bestdir.x; + bestdir = bestdir - pev->v_angle - pev->punchangle; + + if (bestent->v.takedamage == DAMAGE_AIM) + m_fOnTarget = TRUE; + + return bestdir; + } + + return Vector( 0, 0, 0 ); +} + + +void CBasePlayer :: ResetAutoaim( ) +{ + if (m_vecAutoAim.x != 0 || m_vecAutoAim.y != 0) + { + m_vecAutoAim = Vector( 0, 0, 0 ); + SET_CROSSHAIRANGLE( edict(), 0, 0 ); + } + m_fOnTarget = FALSE; +} + +/* +============= +SetCustomDecalFrames + + UNDONE: Determine real frame limit, 8 is a placeholder. + Note: -1 means no custom frames present. +============= +*/ +void CBasePlayer :: SetCustomDecalFrames( int nFrames ) +{ + if (nFrames > 0 && + nFrames < 8) + m_nCustomSprayFrames = nFrames; + else + m_nCustomSprayFrames = -1; +} + +/* +============= +GetCustomDecalFrames + + Returns the # of custom frames this player's custom clan logo contains. +============= +*/ +int CBasePlayer :: GetCustomDecalFrames( void ) +{ + return m_nCustomSprayFrames; +} + + +//========================================================= +// DropPlayerItem - drop the named item, or if no name, +// the active item. +//========================================================= +void CBasePlayer::DropPlayerItem ( char *pszItemName ) +{ +/* + if ( !g_pGameRules->IsMultiplayer() || (weaponstay.value > 0) ) + { + // no dropping in single player. + return; + } +*/ + if ( !Q_strlen( pszItemName ) ) + { + // if this string has no length, the client didn't type a name! + // assume player wants to drop the active item. + // make the string null to make future operations in this function easier + pszItemName = NULL; + } + + CBasePlayerItem *pWeapon; + int i; + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + pWeapon = m_rgpPlayerItems[ i ]; + + while ( pWeapon ) + { + if ( pszItemName ) + { + // try to match by name. + if ( FStrEq( pszItemName, STRING( pWeapon->pev->netname ) ) ) + { + // match! + break; + } + } + else + { + // trying to drop active item + if ( pWeapon == m_pActiveItem ) + { + // active item! + break; + } + } + + pWeapon = pWeapon->m_pNext; + } + + + // if we land here with a valid pWeapon pointer, that's because we found the + // item we want to drop and hit a BREAK; pWeapon is the item. + if ( pWeapon && pWeapon->AllowToDrop( )) + { + if( pszItemName != NULL ) + g_pGameRules->GetNextBestWeapon( this, pWeapon ); + Vector dropAngle = pev->angles; + dropAngle.x = dropAngle.z = 0; + UTIL_MakeVectors ( dropAngle ); + + pev->weapons &= ~(1<m_iId);// take item off hud + + CWeaponBox *pWeaponBox = (CWeaponBox *)CBaseEntity::Create( "weaponbox", pev->origin + gpGlobals->v_forward * 10, pev->angles, edict() ); + SET_MODEL( ENT(pWeaponBox->pev), STRING( pWeapon->iWorldModel() )); + pWeaponBox->pev->angles.x = 0; + pWeaponBox->pev->angles.z = 0; + if( pWeaponBox->GetSequenceCount() > 1 ) + pWeaponBox->pev->sequence = 1; + pWeaponBox->PackWeapon( pWeapon ); + pWeaponBox->pev->velocity = gpGlobals->v_forward * 300 + gpGlobals->v_forward * 100; + + // drop half of the ammo for this weapon. + int iAmmoIndex; + + iAmmoIndex = GetAmmoIndex ( pWeapon->pszAmmo1() ); // ??? + + if ( iAmmoIndex != -1 ) + { + // this weapon weapon uses ammo, so pack an appropriate amount. + if ( pWeapon->iFlags() & ITEM_FLAG_EXHAUSTIBLE ) + { + // pack up all the ammo, this weapon is its own ammo type + pWeaponBox->PackAmmo( MAKE_STRING(pWeapon->pszAmmo1()), m_rgAmmo[ iAmmoIndex ] ); + m_rgAmmo[ iAmmoIndex ] = 0; + + } + else + { + // pack half of the ammo + pWeaponBox->PackAmmo( MAKE_STRING(pWeapon->pszAmmo1()), m_rgAmmo[ iAmmoIndex ] / 2 ); + m_rgAmmo[ iAmmoIndex ] /= 2; + } + + } + + return;// we're done, so stop searching with the FOR loop. + } + } +} + +//========================================================= +// HasPlayerItem Does the player already have this item? +//========================================================= +BOOL CBasePlayer::HasPlayerItem( CBasePlayerItem *pCheckItem ) +{ + CBasePlayerItem *pItem = m_rgpPlayerItems[pCheckItem->iItemSlot()]; + + while (pItem) + { + if (FStrEq( STRING( pItem->pev->netname ), STRING( pCheckItem->pev->netname ) )) + { + return TRUE; + } + pItem = pItem->m_pNext; + } + + return FALSE; +} + +//========================================================= +// HasNamedPlayerItem Does the player already have this item? +//========================================================= +BOOL CBasePlayer::HasNamedPlayerItem( const char *pszItemName ) +{ + CBasePlayerItem *pItem; + int i; + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + pItem = m_rgpPlayerItems[ i ]; + + while (pItem) + { + if ( !Q_stricmp( pszItemName, STRING( pItem->pev->netname ) ) ) + { + return TRUE; + } + pItem = pItem->m_pNext; + } + } + + return FALSE; +} + +//========================================================= +// +//========================================================= +BOOL CBasePlayer :: SwitchWeapon( CBasePlayerItem *pWeapon ) +{ + if ( !pWeapon->CanDeploy() ) + { + return FALSE; + } + + ResetAutoaim( ); + + if ( m_pActiveItem ) + m_pActiveItem->Holster( ); + + QueueItem( pWeapon ); + + if ( m_pActiveItem ) + m_pActiveItem->Deploy( ); + + return TRUE; +} + +//========================================================= +// Dead HEV suit prop +// LRC- i.e. the dead blokes you see in Xen. +//========================================================= +class CDeadHEV : public CBaseMonster +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_HUMAN_MILITARY; } + + void KeyValue( KeyValueData *pkvd ); + float MaxYawSpeed( void ) { return 8.0f; } + int m_iPose;// which sequence to display -- temporary, don't need to save + static char *m_szPoses[4]; +}; + +char *CDeadHEV::m_szPoses[] = { "deadback", "deadsitting", "deadstomach", "deadtable" }; + +void CDeadHEV::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "pose")) + { + m_iPose = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + +LINK_ENTITY_TO_CLASS( monster_hevsuit_dead, CDeadHEV ); + +//========================================================= +// ********** DeadHEV SPAWN ********** +//========================================================= +void CDeadHEV :: Spawn( void ) +{ + PRECACHE_MODEL("models/player.mdl"); + SET_MODEL(ENT(pev), "models/player.mdl"); + + pev->sequence = 0; + pev->body = 1; + m_bloodColor = BLOOD_COLOR_RED; + + pev->sequence = LookupSequence( m_szPoses[m_iPose] ); + + if (pev->sequence == -1) + { + ALERT ( at_debug, "Dead hevsuit with bad pose\n" ); + pev->sequence = 0; + pev->effects |= EF_BRIGHTFIELD; + } + + // Corpses have less health + pev->health = 8; + + MonsterInitDead(); +} + + +class CStripWeapons : public CPointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + int m_iRemoveGasMask; + int m_iRemoveShield; +}; + +LINK_ENTITY_TO_CLASS( player_weaponstrip, CStripWeapons ); + +TYPEDESCRIPTION CStripWeapons::m_SaveData[] = +{ + DEFINE_FIELD( CStripWeapons, m_iRemoveGasMask, FIELD_INTEGER ), + DEFINE_FIELD( CStripWeapons, m_iRemoveShield, FIELD_INTEGER ), +}; IMPLEMENT_SAVERESTORE( CStripWeapons, CPointEntity ); + +void CStripWeapons :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "gasmask")) + { + m_iRemoveGasMask = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "shield")) + { + m_iRemoveShield = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CStripWeapons :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBasePlayer *pPlayer = NULL; + + if ( pActivator && pActivator->IsPlayer() ) + { + pPlayer = (CBasePlayer *)pActivator; + } + else if ( !g_pGameRules->IsDeathmatch() ) + { + pPlayer = (CBasePlayer *)CBaseEntity::Instance( g_engfuncs.pfnPEntityOfEntIndex( 1 ) ); + } + + if ( pPlayer ) + { + // buz + if (m_iRemoveGasMask) + { + if (pPlayer->m_iGasMaskOn) + pPlayer->ToggleGasMask(); + + ClearBits( pPlayer->m_iHideHUD, ITEM_GASMASK ); + } + if (m_iRemoveShield) + { + if (pPlayer->m_iHeadShieldOn) + pPlayer->ToggleHeadShield(); + + ClearBits( pPlayer->m_iHideHUD, ITEM_HEADSHIELD ); + } + pPlayer->RemoveAllItems( FBitSet( pev->spawnflags, 1 )); + } +} + + +class CRevertSaved : public CPointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT MessageThink( void ); + void EXPORT LoadThink( void ); + void KeyValue( KeyValueData *pkvd ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + inline float Duration( void ) { return pev->dmg_take; } + inline float HoldTime( void ) { return pev->dmg_save; } + inline float MessageTime( void ) { return m_messageTime; } + inline float LoadTime( void ) { return m_loadTime; } + + inline void SetDuration( float duration ) { pev->dmg_take = duration; } + inline void SetHoldTime( float hold ) { pev->dmg_save = hold; } + inline void SetMessageTime( float time ) { m_messageTime = time; } + inline void SetLoadTime( float time ) { m_loadTime = time; } + +private: + float m_messageTime; + float m_loadTime; +}; + +LINK_ENTITY_TO_CLASS( player_loadsaved, CRevertSaved ); + +TYPEDESCRIPTION CRevertSaved::m_SaveData[] = +{ + DEFINE_FIELD( CRevertSaved, m_messageTime, FIELD_FLOAT ), // These are not actual times, but durations, so save as floats + DEFINE_FIELD( CRevertSaved, m_loadTime, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CRevertSaved, CPointEntity ); + +void CRevertSaved :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "duration")) + { + SetDuration( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "holdtime")) + { + SetHoldTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "messagetime")) + { + SetMessageTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "loadtime")) + { + SetLoadTime( atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +void CRevertSaved :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + UTIL_ScreenFadeAll( pev->rendercolor, Duration(), HoldTime(), pev->renderamt, FFADE_OUT ); + SetNextThink( MessageTime() ); + SetThink(&CRevertSaved :: MessageThink ); +} + + +void CRevertSaved :: MessageThink( void ) +{ + UTIL_ShowMessageAll( STRING(pev->message) ); + float nextThink = LoadTime() - MessageTime(); + if ( nextThink > 0 ) + { + SetNextThink( nextThink ); + SetThink(&CRevertSaved :: LoadThink ); + } + else + LoadThink(); +} + + +void CRevertSaved :: LoadThink( void ) +{ + if ( !gpGlobals->deathmatch ) + { + SERVER_COMMAND("reload\n"); + } +} + +//========================================================= +// Trigger to disable a player +//========================================================= +#define SF_FREEZE_LOCUS 1 + +class CPlayerFreeze:public CBaseDelay +{ + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Think( void ); + STATE GetState( void ) { return m_hActivator == NULL? STATE_OFF: STATE_ON; } +}; + +void CPlayerFreeze::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (!(pev->spawnflags & SF_FREEZE_LOCUS)) + { + pActivator = UTIL_FindEntityByClassname(NULL, "player"); + } + + if (pActivator && pActivator->pev->flags & FL_CLIENT) + { + if (!ShouldToggle(useType, pActivator->pev->flags & FL_FROZEN)) + return; + + if (pActivator->pev->flags & FL_FROZEN) + { + // unfreeze him + ((CBasePlayer *)((CBaseEntity *)pActivator))->EnableControl(TRUE); + m_hActivator = NULL; + DontThink(); + } + else + { + // freeze him + ((CBasePlayer *)((CBaseEntity *)pActivator))->EnableControl(FALSE); + if (m_flDelay) + { + m_hActivator = pActivator; + SetNextThink(m_flDelay); + } + } + } +} + +void CPlayerFreeze::Think ( void ) +{ + Use(m_hActivator, this, USE_ON, 0); +} + +LINK_ENTITY_TO_CLASS( player_freeze, CPlayerFreeze ); + +// underwater bubbles effect +class CPlayerBubbles : public CPointEntity +{ + void Spawn( void ); + void Think( void ); + virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; } +}; + +LINK_ENTITY_TO_CLASS( player_bubbles, CPlayerBubbles ); + +void CPlayerBubbles :: Spawn( void ) +{ + // when spawned in-game + if( GET_SERVER_STATE() == SERVER_ACTIVE ) + { + UTIL_SetOrigin( this, pev->origin ); + SET_MODEL( edict(), "sprites/null.spr" ); + pev->body = 1; + + pev->movetype = MOVETYPE_COMPOUND; + pev->aiment = pev->owner; + + MESSAGE_BEGIN( MSG_BROADCAST, gmsgParticle ); + WRITE_ENTITY( entindex() ); + WRITE_STRING( "particles/p_underwater.aur" ); + WRITE_BYTE( 0 ); // attachment + MESSAGE_END(); + } + + SetNextThink( 1 ); // time to auto remove +} + +void CPlayerBubbles :: Think ( void ) +{ + // kill the aurora too + UTIL_Remove( this ); +} + +//========================================================= +// Multiplayer intermission spots. +//========================================================= +class CInfoIntermission:public CPointEntity +{ + void Spawn( void ); + void Think( void ); +}; + +void CInfoIntermission::Spawn( void ) +{ + UTIL_SetOrigin( this, pev->origin ); + pev->solid = SOLID_NOT; + pev->effects |= EF_NODRAW; + pev->v_angle = g_vecZero; + + SetNextThink( 2 );// let targets spawn! + +} + +void CInfoIntermission::Think ( void ) +{ + CBaseEntity *pTarget; + + // find my target + pTarget = UTIL_FindEntityByTargetname( NULL, STRING(pev->target) ); + + if ( pTarget ) + { + pev->v_angle = UTIL_VecToAngles( (pTarget->pev->origin - pev->origin).Normalize() ); + pev->v_angle.x = -pev->v_angle.x; + } +} + +LINK_ENTITY_TO_CLASS( info_intermission, CInfoIntermission ); + + +// buz: set jump height function +void CBasePlayer::SetJumpHeight(int value) +{ + m_iJumpHeight = value; + char buf[16]; + itoa(m_iJumpHeight, buf, 10); + g_engfuncs.pfnSetPhysicsKeyValue( edict(), "jh", buf ); +// ALERT(at_aiconsole, "SETTING JUMP: %s\n", buf); +} + + + +//============================================================== +// Hud sprite displayer +//============================================================== +#define SF_HUDSPR_ACTIVE 1 + +class CHudSprite:public CBaseEntity +{ + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + STATE GetState( void ) { return pev->spawnflags & SF_HUDSPR_ACTIVE? STATE_ON:STATE_OFF; } + void Think( void ); +}; + +void CHudSprite::Spawn( void ) +{ + if (FStringNull(pev->targetname)) + { + pev->spawnflags |= SF_HUDSPR_ACTIVE; + } + + if (pev->spawnflags & SF_HUDSPR_ACTIVE) + { + SetNextThink(2); + } +} + +void CHudSprite::Think( void ) +{ + Use(this, this, USE_ON, 0); +} + +void CHudSprite::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !pActivator || !pActivator->IsPlayer() ) + { + pActivator = CBaseEntity::Instance(g_engfuncs.pfnPEntityOfEntIndex( 1 )); + } + + if (ShouldToggle(useType)) + { + if (pev->spawnflags & SF_HUDSPR_ACTIVE) + pev->spawnflags &= ~SF_HUDSPR_ACTIVE; + else + pev->spawnflags |= SF_HUDSPR_ACTIVE; + } + +// byte : TRUE = ENABLE icon, FALSE = DISABLE icon +// string : the sprite name to display +// byte : red +// byte : green +// byte : blue + MESSAGE_BEGIN( MSG_ONE, gmsgStatusIcon, NULL, pActivator->pev ); + WRITE_BYTE(pev->spawnflags & SF_HUDSPR_ACTIVE); + WRITE_STRING(STRING(pev->model)); + WRITE_BYTE(pev->rendercolor.x); + WRITE_BYTE(pev->rendercolor.y); + WRITE_BYTE(pev->rendercolor.z); + MESSAGE_END(); +} + +LINK_ENTITY_TO_CLASS( hud_sprite, CHudSprite ); + + + +// buz +void CBasePlayer::ViewPunch( float p, float y, float r ) +{ + // fuser2-4 is punch speed + pev->fuser2 -= p * 20; + pev->fuser3 += y * 20; + pev->fuser4 += r * 20; +} \ No newline at end of file diff --git a/dlls/player.h b/dlls/player.h new file mode 100644 index 0000000..4ebb908 --- /dev/null +++ b/dlls/player.h @@ -0,0 +1,405 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PLAYER_H +#define PLAYER_H + +#define PLAYER_FATAL_FALL_SPEED 1024// approx 60 feet +#define PLAYER_MAX_SAFE_FALL_SPEED 580// approx 20 feet +#define DAMAGE_FOR_FALL_SPEED (float) 100 / ( PLAYER_FATAL_FALL_SPEED - PLAYER_MAX_SAFE_FALL_SPEED )// damage per unit per second. +#define PLAYER_MIN_BOUNCE_SPEED 200 +#define PLAYER_FALL_PUNCH_THRESHHOLD (float)350 // won't punch player's screen/make scrape noise unless player falling at least this fast. + +// +// Player PHYSICS FLAGS bits +// +#define PFLAG_ONLADDER ( 1<<0 ) +#define PFLAG_ONSWING ( 1<<0 ) +#define PFLAG_ONTRAIN ( 1<<1 ) +#define PFLAG_ONBARNACLE ( 1<<2 ) +#define PFLAG_DUCKING ( 1<<3 ) // In the process of ducking, but totally squatted yet +#define PFLAG_USING ( 1<<4 ) // Using a continuous entity +#define PFLAG_OBSERVER ( 1<<5 ) // player is locked in stationary cam mode. Spectators can move, observers can't. + +// +// generic player +// +//----------------------------------------------------- +//This is Half-Life player entity +//----------------------------------------------------- +#define CSUITPLAYLIST 4 // max of 4 suit sentences queued up at any time + +#define SUIT_GROUP TRUE +#define SUIT_SENTENCE FALSE + +#define SUIT_REPEAT_OK 0 +#define SUIT_NEXT_IN_30SEC 30 +#define SUIT_NEXT_IN_1MIN 60 +#define SUIT_NEXT_IN_5MIN 300 +#define SUIT_NEXT_IN_10MIN 600 +#define SUIT_NEXT_IN_30MIN 1800 +#define SUIT_NEXT_IN_1HOUR 3600 + +#define CSUITNOREPEAT 32 + +#define SOUND_FLASHLIGHT_ON "items/flashlight1.wav" +#define SOUND_FLASHLIGHT_OFF "items/flashlight1.wav" + +// buz: gasmask sounds +#define SOUND_GASMASK_ON "items/gasmask1.wav" +#define SOUND_GASMASK_OFF "items/gasmask2.wav" +// buz: headshield sounds +#define SOUND_SHIELD_ON "items/headshield1.wav" +#define SOUND_SHIELD_OFF "items/headshield2.wav" + +#define TEAM_NAME_LENGTH 16 + +typedef enum +{ + PLAYER_IDLE, + PLAYER_WALK, + PLAYER_JUMP, + PLAYER_SUPERJUMP, + PLAYER_DIE, + PLAYER_ATTACK1, +} PLAYER_ANIM; + + +//NB: changing this structure will cause problems! --LRC + +#define MAX_ID_RANGE 2048 +#define SBAR_STRING_SIZE 128 + +enum sbar_data +{ + SBAR_ID_TARGETNAME = 1, + SBAR_ID_TARGETHEALTH, + SBAR_ID_TARGETARMOR, + SBAR_END, +}; + +#define CHAT_INTERVAL 1.0f + +class CBasePlayer : public CBaseMonster +{ +public: + float m_flStaminaValue; + + int random_seed; // See that is shared between client & server for shared weapons code + + int m_iPlayerSound;// the index of the sound list slot reserved for this player + int m_iTargetVolume;// ideal sound volume. + int m_iWeaponVolume;// how loud the player's weapon is right now. + int m_iExtraSoundTypes;// additional classification for this weapon's sound + int m_iWeaponFlash;// brightness of the weapon flash + float m_flStopExtraSoundTime; + + float m_flFlashLightTime; // Time until next battery draw/Recharge + int m_iFlashBattery; // Flashlight Battery Draw + + int m_afButtonLast; + int m_afButtonPressed; + int m_afButtonReleased; + + edict_t *m_pentSndLast; // last sound entity to modify player room type + float m_flSndRoomtype; // last roomtype set by sound entity + float m_flSndRange; // dist from player to sound entity + float m_flBlurAmount; + float m_flLastBlurAmount; // blur amount at damage moment + float m_flCurBlurAmount; // applied blur amount + float m_flBlurFadeTime; // blur auto-expired time + + float m_flFallVelocity; + + int m_rgItems[MAX_ITEMS]; + int m_fKnownItem; // True when a new item needs to be added + int m_fNewAmmo; // True when a new item has been added + + unsigned int m_afPhysicsFlags; // physics flags - set when 'normal' physics should be revisited or overriden + float m_fNextSuicideTime; // the time after which the player can next use the suicide command + + // buz: gasmask + int m_iGasMaskOn; + int m_iUpdateGasMask; + float m_flGasMaskTime; + float m_flNextBreathTime; + int m_iLastGasMaskSound; + + // buz: head shield + int m_iHeadShieldOn; + int m_iUpdateHeadShield; + float m_flHeadShieldTime; + + // buz: special tank + CBaseEntity *m_pSpecTank; + + // buz: current goal description + string_t m_strCurrentGoalName; + string_t m_strCurrentGoalImageName; + string_t m_strCurrentGoalTitleName; + int m_iGoalNeedsUpdate; + + void PlayerSetGoalDesc( string_t strindex, string_t title, string_t imgname ); + + // buz: jumping height + int m_iJumpHeight; // in percents, 0 - 100 + void SetJumpHeight( int value ); + + // buz: real lightlevel (from 0 to 1) + float m_fLightlevel; + + // buz: last monster, who consider this player his enemy (allies will attack him) + EHANDLE m_hLastEnemy; + +// these are time-sensitive things that we keep track of + float m_flTimeStepSound; // when the last stepping sound was made + float m_flTimeWeaponIdle; // when to play another weapon idle animation. + float m_flSwimTime; // how long player has been underwater + float m_flDuckTime; // how long we've been ducking + float m_flWallJumpTime; // how long until next walljump + + float m_flSuitUpdate; // when to play next suit update + int m_rgSuitPlayList[CSUITPLAYLIST];// next sentencenum to play for suit update + int m_iSuitPlayNext; // next sentence slot for queue storage; + int m_rgiSuitNoRepeat[CSUITNOREPEAT]; // suit sentence no repeat list + float m_rgflSuitNoRepeatTime[CSUITNOREPEAT]; // how long to wait before allowing repeat + int m_lastDamageAmount; // Last damage taken + float m_tbdPrev; // Time-based damage timer + + float m_flgeigerRange; // range to nearest radiation source + float m_flgeigerDelay; // delay per update of range msg to client + int m_igeigerRangePrev; + int m_iStepLeft; // alternate left/right foot stepping sound + int m_idrowndmg; // track drowning damage taken + int m_idrownrestored; // track drowning damage restored + + int m_bitsHUDDamage; // Damage bits for the current fame. These get sent to + // the hude via the DAMAGE message + BOOL m_fInitHUD; // True when deferred HUD restart msg needs to be sent + BOOL m_fGameHUDInitialized; + int m_iTrain; // Train control position + BOOL m_fWeapon; // Set this to FALSE to force a reset of the current weapon HUD info + + EHANDLE m_pTank; // the tank which the player is currently controlling, NULL if no tank + float m_fDeadTime; // the time at which the player died (used in PlayerDeathThink()) + + BOOL m_fNoPlayerSound; // a debugging feature. Player makes no sound if this is true. + BOOL m_fLongJump; // does this player have the longjump module? + + float m_tSneaking; + int m_iUpdateTime; // stores the number of frame ticks before sending HUD update messages + int m_iClientHealth; // the health currently known by the client. If this changes, send a new + int m_iClientBattery; // the Battery currently known by the client. If this changes, send a new + int m_iHideHUD; // the players hud weapon info is to be hidden + int m_iClientHideHUD; + int m_iFOV; // field of view + int m_iClientFOV; // client's known FOV + int m_iClientStamina; + float m_flClientBlurAmount; + float m_flClientLevelTime; + + void ViewPunch( float p, float y, float r ); // buz - íàãëî ñïåð èç õë2 + + // usable player items + CBasePlayerItem *m_rgpPlayerItems[MAX_ITEM_TYPES]; + CBasePlayerItem *m_pActiveItem; + CBasePlayerItem *m_pClientActiveItem; // client version of the active item + CBasePlayerItem *m_pLastItem; + CBasePlayerItem *m_pNextItem; + // shared ammo slots + int m_rgAmmo[MAX_AMMO_SLOTS]; + int m_rgAmmoLast[MAX_AMMO_SLOTS]; + + Vector m_vecAutoAim; + BOOL m_fOnTarget; + int m_iDeaths; + float m_iRespawnFrames; // used in PlayerDeathThink() to make sure players can always respawn + + int m_lastx, m_lasty; // These are the previous update's crosshair angles, DON"T SAVE/RESTORE + + int m_nCustomSprayFrames;// Custom clan logo frames for this player + float m_flNextDecalTime;// next time this player can spray a decal + + char m_szTeamName[TEAM_NAME_LENGTH]; + + virtual void Spawn( void ); + void Pain( void ); + +// virtual void Think( void ); + virtual void Jump( void ); + virtual void Duck( void ); + virtual void PreThink( void ); + virtual void PostThink( void ); + virtual Vector GetGunPosition( void ); + virtual int TakeHealth( float flHealth, int bitsDamageType ); + virtual void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); + virtual void Killed( entvars_t *pevAttacker, int iGib ); + virtual Vector BodyTarget( const Vector &posSrc ) { return Center( ) + pev->view_ofs * RANDOM_FLOAT( 0.5, 1.1 ); }; // position to shoot at + virtual void StartSneaking( void ) { m_tSneaking = gpGlobals->time - 1; } + virtual void StopSneaking( void ) { m_tSneaking = gpGlobals->time + 30; } + virtual BOOL IsSneaking( void ) { return m_tSneaking <= gpGlobals->time; } + virtual BOOL IsAlive( void ) { return (pev->deadflag == DEAD_NO) && pev->health > 0; } + virtual BOOL ShouldFadeOnDeath( void ) { return FALSE; } + virtual BOOL IsPlayer( void ) { return TRUE; } // Spectators should return FALSE for this, they aren't "players" as far as game logic is concerned + + virtual BOOL IsNetClient( void ) { return TRUE; } // Bots should return FALSE for this, they can't receive NET messages + // Spectators should return TRUE for this + virtual const char *TeamID( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + void RenewItems(void); + void PackDeadPlayerItems( void ); + void RemoveAllItems( BOOL removeSuit ); + BOOL SwitchWeapon( CBasePlayerItem *pWeapon ); + + // JOHN: sends custom messages if player HUD data has changed (eg health, ammo) + virtual void UpdateClientData( void ); + + static TYPEDESCRIPTION m_playerSaveData[]; + + // Player is moved across the transition by other means + virtual int ObjectCaps( void ) { return CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual void Precache( void ); + BOOL IsOnLadder( void ); + BOOL FlashlightIsOn( void ); + void FlashlightTurnOn( void ); + void FlashlightTurnOff( void ); + + // buz + void ToggleGasMask( void ); + void ToggleHeadShield( void ); + + void UpdatePlayerSound ( void ); + void DeathSound ( void ); + + int Classify ( void ); + void SetAnimation( PLAYER_ANIM playerAnim ); + void SetWeaponAnimType( const char *szExtention ); + char m_szAnimExtention[32]; + + // custom player functions + virtual void ImpulseCommands( void ); + void ChangeModeMessage( void ); + void CheatImpulseCommands( int iImpulse ); + + void StartDeathCam( void ); + void StartObserver( Vector vecPosition, Vector vecViewAngle ); + + void AddPoints( int score, BOOL bAllowNegativeScore ); + void AddPointsToTeam( int score, BOOL bAllowNegativeScore ); + BOOL AddPlayerItem( CBasePlayerItem *pItem ); + BOOL RemovePlayerItem( CBasePlayerItem *pItem ); + void DropPlayerItem ( char *pszItemName ); + BOOL HasPlayerItem( CBasePlayerItem *pCheckItem ); + BOOL HasNamedPlayerItem( const char *pszItemName ); + BOOL HasWeapons( void );// do I have ANY weapons? + void SelectPrevItem( int iItem ); + void SelectNextItem( int iItem ); + void SelectLastItem(void); + void SelectItem(const char *pstr); + void QueueItem(CBasePlayerItem *pItem); + void ItemPreFrame( void ); + void ItemPostFrame( void ); + void GiveNamedItem( const char *szName ); + void EnableControl(BOOL fControl); + + int GiveAmmo( int iAmount, const char *szAmmoName ); + void SendAmmoUpdate(void); + + void WaterMove( void ); + void EXPORT PlayerDeathThink( void ); + void PlayerUse( void ); + + void CheckSuitUpdate(); + void SetSuitUpdate(char *name, int fgroup, int iNoRepeat); + void UpdateGeigerCounter( void ); + void CheckTimeBasedDamage( void ); + + BOOL FBecomeProne ( void ); + void BarnacleVictimBitten ( entvars_t *pevBarnacle ); + void BarnacleVictimReleased ( void ); + static int GetAmmoIndex(const char *psz); + int AmmoInventory( int iAmmoIndex ); + const char *GetAmmoName(int index ); + int Illumination( void ); + + void ResetAutoaim( void ); + Vector GetAutoaimVector( float flDelta ); + Vector AutoaimDeflection( Vector &vecSrc, float flDist, float flDelta ); + + void ForceClientDllUpdate( void ); // Forces all client .dll specific data to be resent to client. + + void DeathMessage( entvars_t *pevKiller ); + + void SetCustomDecalFrames( int nFrames ); + int GetCustomDecalFrames( void ); + + float m_flStartCharge; + float m_flAmmoStartCharge; + float m_flPlayAftershock; + float m_flNextAmmoBurn;// while charging, when to absorb another unit of player's ammo? + + //Player ID + void InitStatusBar( void ); + void UpdateStatusBar( void ); + int m_izSBarState[ SBAR_END ]; + float m_flNextSBarUpdateTime; + float m_flStatusBarDisappearDelay; + char m_SbarString0[ SBAR_STRING_SIZE ]; + char m_SbarString1[ SBAR_STRING_SIZE ]; + + float m_flNextChatTime; + + // rain + int Rain_dripsPerSecond; + float Rain_windX, Rain_windY; + float Rain_randX, Rain_randY; + + int Rain_ideal_dripsPerSecond; + float Rain_ideal_windX, Rain_ideal_windY; + float Rain_ideal_randX, Rain_ideal_randY; + + float Rain_endFade; // 0 means off + float Rain_nextFadeUpdate; + + int Rain_needsUpdate; + + void SendInitMessages(); // buz + int m_iInitMessagesSent; // buz + + float m_flLevelTime; +}; + +#define AUTOAIM_2DEGREES 0.0348994967025 +#define AUTOAIM_5DEGREES 0.08715574274766 +#define AUTOAIM_8DEGREES 0.1391731009601 +#define AUTOAIM_10DEGREES 0.1736481776669 + + +extern int gmsgHudText; +extern int gmsgParticle; // LRC +extern int gmsgCustomDecal; +extern int gmsgStudioDecal; +extern int gmsgMusicFade; +extern int gmsgWeaponAnim; +extern int gmsgWeaponBody; +extern int gmsgWeaponSkin; +extern int gmsgPartEffect; +extern int gmsgBlurEffect; +extern int gmsgLevelTime; +extern BOOL gInitHUD; + +#endif // PLAYER_H diff --git a/dlls/playermonster.cpp b/dlls/playermonster.cpp new file mode 100644 index 0000000..59e2907 --- /dev/null +++ b/dlls/playermonster.cpp @@ -0,0 +1,122 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: New version of the slider bar +// +// $NoKeywords: $ +//============================================================================= + +//========================================================= +// playermonster - for scripted sequence use. +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" + +// For holograms, make them not solid so the player can walk through them +#define SF_MONSTERPLAYER_NOTSOLID 4 + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= + +class CPlayerMonster : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + float MaxYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int ISoundMask ( void ); +}; +LINK_ENTITY_TO_CLASS( monster_player, CPlayerMonster ); + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CPlayerMonster :: Classify ( void ) +{ + return CLASS_PLAYER_ALLY; +} + +//========================================================= +// MaxYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +float CPlayerMonster :: MaxYawSpeed( void ) +{ + float ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + default: + ys = 90; + } + + return ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CPlayerMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case 0: + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// ISoundMask - player monster can't hear. +//========================================================= +int CPlayerMonster :: ISoundMask ( void ) +{ + return NULL; +} + +//========================================================= +// Spawn +//========================================================= +void CPlayerMonster :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/player.mdl"); + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->health = 8; + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + + MonsterInit(); + if ( pev->spawnflags & SF_MONSTERPLAYER_NOTSOLID ) + { + pev->solid = SOLID_NOT; + pev->takedamage = DAMAGE_NO; + } +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CPlayerMonster :: Precache() +{ + PRECACHE_MODEL("models/player.mdl"); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= diff --git a/dlls/prop_explosion.cpp b/dlls/prop_explosion.cpp new file mode 100644 index 0000000..ebea5c8 --- /dev/null +++ b/dlls/prop_explosion.cpp @@ -0,0 +1,270 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "animation.h" +#include "effects.h" +#include "nodes.h" +#include "explode.h" +#include "player.h" + +#define FIRE_SPRITE_NAME "sprites/flame2.spr" +#define BURNIND_SOUND_NAME "ambience/burning1.wav" + +// must match definition in modelgen.h +enum synctype_t +{ + ST_SYNC=0, + ST_RAND +}; + +// TODO: shorten these? +typedef struct { + int ident; + int version; + int type; + int texFormat; + float boundingradius; + int width; + int height; + int numframes; + float beamlength; + synctype_t synctype; +} dsprite_t; + +class CPropExplosion : public CGrenade +{ + void Spawn( void ); + void Precache( void ); + int BloodColor( void ) { return DONT_BLEED; }; + + void EXPORT PropThink( void ); + + void RunFire( void ); + void StartExplode( void ); + + float CalculationSpriteScale( ); + + void KeyValue( KeyValueData *pkvd ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + string_t m_iszParentName; + + int m_startHealth; + float m_firetime; + float m_explosiontime; + float m_dmgradius; + bool m_isfire; +}; + +LINK_ENTITY_TO_CLASS( prop_explosion, CPropExplosion ); + +TYPEDESCRIPTION CPropExplosion::m_SaveData[] = +{ + DEFINE_FIELD( CPropExplosion, m_startHealth, FIELD_INTEGER ), + DEFINE_FIELD( CPropExplosion, m_firetime, FIELD_FLOAT ), + DEFINE_FIELD( CPropExplosion, m_explosiontime, FIELD_FLOAT ), + DEFINE_FIELD( CPropExplosion, m_dmgradius, FIELD_FLOAT ), + DEFINE_FIELD( CPropExplosion, m_isfire, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CPropExplosion, CGrenade ); + +void CPropExplosion :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "health"))//skin is used for content type + { + m_startHealth = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "firetime")) + { + m_firetime = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damageradius")) + { + m_dmgradius = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CGrenade::KeyValue( pkvd ); +} + +void CPropExplosion :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL( ENT(pev), STRING(pev->model) ); + + Vector mins; + Vector maxs; + + ExtractBbox( pev->sequence, mins, maxs ); + + UTIL_SetSize(pev, mins, maxs ); //Whi it is not using for other entities ? + UTIL_SetOrigin( this, pev->origin ); + + DROP_TO_FLOOR( edict() ); + + pev->nextthink = gpGlobals->time + 0.1; + +// SetTouch( PropTouch ); //maybe later + SetThink( PropThink ); + + //If not defined, add standart value + if(!m_startHealth ) + { + m_startHealth = 50; + } + + if(!m_firetime) + { + m_firetime = 4; + } + + if(!m_dmgradius ) + { + m_dmgradius = 100; + } + + m_isfire = false; + + pev->flags |= FL_MONSTER; + pev->takedamage = DAMAGE_YES; + pev->health = m_startHealth; + pev->dmg = 50; +} + +void CPropExplosion::Precache( void ) +{ + PRECACHE_MODEL((char *)STRING(pev->model)); + PRECACHE_MODEL( FIRE_SPRITE_NAME ); + PRECACHE_SOUND( BURNIND_SOUND_NAME ); +} + +void CPropExplosion::PropThink( void ) +{ + if( pev->health < m_startHealth ) + { + if(!m_explosiontime) + { + m_explosiontime = gpGlobals->time + m_firetime; + } + else + { + if( m_explosiontime < gpGlobals->time ) + { + StartExplode( ); + } + else + { + RunFire( ); + } + } + } + + pev->nextthink = gpGlobals->time + 0.1; +} + +void CPropExplosion::RunFire( void ) +{ + if(!m_isfire ) + { + CSprite *pSprite = CSprite::SpriteCreate( FIRE_SPRITE_NAME, pev->origin, TRUE ); + + pSprite->AnimateAndDie( m_firetime ); + pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation ); + pSprite->SetScale( CalculationSpriteScale() ); + + EMIT_SOUND(ENT(pev), CHAN_WEAPON, BURNIND_SOUND_NAME, 1, ATTN_NORM); + + m_isfire = true; + } +} + +void CPropExplosion::StartExplode( void ) +{ + STOP_SOUND( ENT(pev), CHAN_WEAPON, BURNIND_SOUND_NAME ); + + UTIL_ScreenShake( pev->origin, 16, 200, 5, m_dmgradius ); + + ExplosionCreate( pev->origin, pev->angles, edict(), (int)m_dmgradius, true ); + + UTIL_Remove( this ); +} + +float CPropExplosion::CalculationSpriteScale( ) +{ + dsprite_t *szSprite = NULL; + + float SpriteX = 0; + float SpriteY = 0; + + float ModelX = 0; + float ModelY = 0; + + szSprite = (dsprite_t *)g_engfuncs.pfnLoadFileForMe( FIRE_SPRITE_NAME, NULL ); + + if (!szSprite) + { + ALERT( at_aiconsole,"Couldn't open fire sprites !\n"); + + return 1.0; + } + + SpriteX = szSprite->width; + SpriteY = szSprite->height; + + g_engfuncs.pfnFreeFile( szSprite ); + + Vector mins; + Vector maxs; + + ExtractBbox( pev->sequence, mins, maxs ); + + ModelX = maxs.x - mins.x; + ModelY = maxs.z - mins.z; + + float FullSprSizeX = ModelX * 3; //pixel to unit ~ 2 ??? + float FullSprSizeY = ModelY * 3; + + float ScaleX = FullSprSizeX / SpriteX; + float ScaleY = FullSprSizeY / SpriteY; + + float Averagescale = ( ScaleX + ScaleY ) / 2; + + //Debug + /* + ALERT( at_console, "Sprite: x = %f, y = %f\n", SpriteX, SpriteY ); + ALERT( at_console, "Model: x = %f, y = %f\n", ModelX, ModelY ); + + ALERT( at_console, "Full Sprite size: x = %f, y = %f\n", SpriteX * Averagescale, SpriteY * Averagescale ); + ALERT( at_console, "Scale: x = %f, y = %f\n", ScaleX, ScaleY ); + ALERT( at_console, "Average Scale: %f", Averagescale ); + */ + + return Averagescale; +} \ No newline at end of file diff --git a/dlls/roach.cpp b/dlls/roach.cpp new file mode 100644 index 0000000..4ded5d9 --- /dev/null +++ b/dlls/roach.cpp @@ -0,0 +1,301 @@ +// Wargon: AI äëÿ êðûñ. Îñíîâàí íà îðèãèíàëüíîì êîäå monster_cockroach. +// Òàðàêàíû è êðûñû ïåðåæèâóò è íàñ, è íàøèõ ïîòîìêîâ, è òåáÿ òîæå. ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "soundent.h" + +#define RAT_IDLE 0 +#define RAT_BORED 1 +#define RAT_SCARED_BY_ENT 2 +#define RAT_SCARED_BY_LIGHT 3 +#define RAT_SMELL_FOOD 4 +#define RAT_EAT 5 + +class CRat : public CBaseMonster +{ +public: + void Spawn ( void ); + void Precache ( void ); + float MaxYawSpeed( void ); + void EXPORT MonsterThink ( void ); + void Move ( float flInterval ); + void PickNewDest ( int iCondition ); + void EXPORT Touch ( CBaseEntity *pOther ); + void Killed ( entvars_t *pevAttacker, int iGib ); + float m_flLastLightLevel; + float m_flNextSmellTime; + int Classify ( void ) { return m_iClass?m_iClass:CLASS_INSECT; } + void Look ( int iDistance ); + int ISoundMask ( void ) { return bits_SOUND_CARCASS | bits_SOUND_MEAT; } + BOOL m_fLightHacked; + int m_iMode; +}; + +LINK_ENTITY_TO_CLASS( monster_cockroach, CRat ); + +void CRat::Touch( CBaseEntity *pOther ) +{ + if ( pOther->pev->velocity == g_vecZero || !pOther->IsPlayer() ) + return; + Vector vecSpot; + TraceResult tr; + vecSpot = pev->origin + Vector ( 0, 0, 8 ); + UTIL_TraceLine( vecSpot, vecSpot + Vector ( 0, 0, -24 ), ignore_monsters, ENT( pev ), &tr ); + UTIL_TraceCustomDecal( &tr, "brains", RANDOM_FLOAT( 0.0f, 360.0f )); + TakeDamage( pOther->pev, pOther->pev, pev->health, DMG_CRUSH ); +} + +float CRat :: MaxYawSpeed( void ) +{ + return 120.0f; +} + +void CRat::Spawn() +{ + Precache(); + if ( pev->model ) + SET_MODEL( ENT( pev ), STRING( pev->model ) ); + else + SET_MODEL( ENT( pev ), "models/krisa.mdl" ); + UTIL_SetSize( pev, Vector( -4, -4, 0 ), Vector( 4, 4, 2 ) ); + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->health = 1; + m_flFieldOfView = 0.5; + m_MonsterState = MONSTERSTATE_NONE; + MonsterInit(); + SetActivity( ACT_IDLE ); + pev->takedamage = DAMAGE_YES; + m_fLightHacked = FALSE; + m_flLastLightLevel = -1; + m_iMode = RAT_IDLE; + m_flNextSmellTime = gpGlobals->time; +} + +void CRat::Precache() +{ + if ( pev->model ) + PRECACHE_MODEL( (char*)STRING( pev->model ) ); + else + PRECACHE_MODEL( "models/krisa.mdl" ); + PRECACHE_SOUND( "roach/rch_die.wav" ); + PRECACHE_SOUND( "roach/rch_walk.wav" ); + PRECACHE_SOUND( "roach/rch_smash.wav" ); +} + +void CRat::Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->solid = SOLID_NOT; + pev->takedamage = DAMAGE_NO; + pev->deadflag = DEAD_DEAD; + if ( RANDOM_LONG( 0, 4 ) == 1 ) + EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, "roach/rch_die.wav", 0.8, ATTN_NORM, 0, 80 + RANDOM_LONG( 0, 39 ) ); + else + EMIT_SOUND_DYN( ENT( pev ), CHAN_BODY, "roach/rch_smash.wav", 0.7, ATTN_NORM, 0, 80 + RANDOM_LONG( 0, 39 ) ); + CSoundEnt::InsertSound( bits_SOUND_WORLD, pev->origin, 128, 1 ); + CBaseEntity *pOwner = CBaseEntity::Instance( pev->owner ); + if ( pOwner ) + pOwner->DeathNotice( pev ); + SetActivity( ACT_DIESIMPLE ); + if ( pev->health < 0 ) + { + switch ( RANDOM_LONG( 0, 3 ) ) + { + case 0: + { + SetActivity( ACT_DIEFORWARD ); + break; + } + case 1: + { + SetActivity( ACT_DIEBACKWARD ); + break; + } + case 2: + { + SetActivity( ACT_DIE_CHESTSHOT ); + break; + } + case 3: + { + SetActivity( ACT_DIE_GUTSHOT ); + break; + } + } + } +} + +void CRat::MonsterThink( void ) +{ + if ( FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) + SetNextThink( RANDOM_FLOAT( 1, 1.5 ) ); + else + SetNextThink( 0.1 ); + float flInterval = StudioFrameAdvance(); + if ( pev->health > 0 ) + { + if ( !m_fLightHacked ) + { + SetNextThink( 1 ); + m_fLightHacked = TRUE; + return; + } + else if ( m_flLastLightLevel < 0 ) + m_flLastLightLevel = GETENTITYILLUM( ENT( pev ) ); + switch ( m_iMode ) + { + case RAT_IDLE: + case RAT_EAT: + { + if ( RANDOM_LONG( 0, 2 ) == 1 ) + { + Look( 150 ); + if ( HasConditions( bits_COND_SEE_FEAR ) ) + { + Eat( 30 + ( RANDOM_LONG( 0, 14 ) ) ); + PickNewDest( RAT_SCARED_BY_ENT ); + SetActivity( ACT_WALK ); + } + else if ( RANDOM_LONG( 0, 128 ) == 1 ) + { + PickNewDest( RAT_BORED ); + SetActivity( ACT_WALK ); + if ( m_iMode == RAT_EAT ) + Eat( 30 + ( RANDOM_LONG ( 0, 14 ) ) ); + } + } + if ( m_iMode == RAT_IDLE ) + { + if ( FShouldEat() ) + Listen(); + if ( GETENTITYILLUM( ENT( pev ) ) > m_flLastLightLevel ) + { + PickNewDest( RAT_SCARED_BY_LIGHT ); + SetActivity( ACT_WALK ); + } + else if ( HasConditions( bits_COND_SMELL_FOOD ) ) + { + CSound *pSound; + pSound = CSoundEnt::SoundPointerForIndex( m_iAudibleList ); + if ( pSound && abs( pSound->m_vecOrigin.z - pev->origin.z ) <= 3 ) + { + PickNewDest( RAT_SMELL_FOOD ); + SetActivity( ACT_WALK ); + } + } + } + break; + } + case RAT_SCARED_BY_LIGHT: + { + if ( GETENTITYILLUM( ENT( pev ) ) <= m_flLastLightLevel ) + { + m_flLastLightLevel = GETENTITYILLUM( ENT( pev ) ); + SetActivity( ACT_IDLE ); + } + break; + } + } + } + if ( m_flGroundSpeed != 0 ) + Move( flInterval ); +} + +void CRat::PickNewDest( int iCondition ) +{ + Vector vecNewDir; + Vector vecDest; + float flDist; + m_iMode = iCondition; + if ( m_iMode == RAT_SMELL_FOOD ) + { + CSound *pSound; + pSound = CSoundEnt::SoundPointerForIndex( m_iAudibleList ); + if ( pSound ) + { + m_Route[ 0 ].vecLocation.x = pSound->m_vecOrigin.x + ( 3 - RANDOM_LONG( 0, 5 ) ); + m_Route[ 0 ].vecLocation.y = pSound->m_vecOrigin.y + ( 3 - RANDOM_LONG( 0, 5 ) ); + m_Route[ 0 ].vecLocation.z = pSound->m_vecOrigin.z; + m_Route[ 0 ].iType = bits_MF_TO_LOCATION; + m_movementGoal = RouteClassify( m_Route[ 0 ].iType ); + return; + } + } + do + { + vecNewDir.x = RANDOM_FLOAT( -1, 1 ); + vecNewDir.y = RANDOM_FLOAT( -1, 1 ); + flDist = 256 + ( RANDOM_LONG( 0, 255 ) ); + vecDest = pev->origin + vecNewDir * flDist; + } while ( ( vecDest - pev->origin ).Length2D() < 128 ); + m_Route[ 0 ].vecLocation.x = vecDest.x; + m_Route[ 0 ].vecLocation.y = vecDest.y; + m_Route[ 0 ].vecLocation.z = pev->origin.z; + m_Route[ 0 ].iType = bits_MF_TO_LOCATION; + m_movementGoal = RouteClassify( m_Route[ 0 ].iType ); + if ( RANDOM_LONG( 0, 8 ) == 1 ) + EMIT_SOUND_DYN( ENT( pev ), CHAN_BODY, "roach/rch_walk.wav", 1, ATTN_NORM, 0, 80 + RANDOM_LONG( 0, 39 ) ); +} + +void CRat::Move( float flInterval ) +{ + float flWaypointDist; + Vector vecApex; + flWaypointDist = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Length2D(); + SetIdealYawToTargetAndUpdate( m_Route[ m_iRouteIndex ].vecLocation, AI_KEEP_YAW_SPEED ); + UTIL_MakeVectors( pev->angles ); + if ( !WALK_MOVE( ENT( pev ), pev->ideal_yaw, 4, WALKMOVE_NORMAL ) ) + PickNewDest( m_iMode ); + WALK_MOVE( ENT( pev ), pev->ideal_yaw, m_flGroundSpeed * flInterval, WALKMOVE_NORMAL ); + if ( flWaypointDist <= m_flGroundSpeed * flInterval ) + { + SetActivity( ACT_IDLE ); + m_flLastLightLevel = GETENTITYILLUM( ENT( pev ) ); + if ( m_iMode == RAT_SMELL_FOOD ) + m_iMode = RAT_EAT; + else + m_iMode = RAT_IDLE; + } + if ( RANDOM_LONG(0, 128) == 1 && m_iMode != RAT_SCARED_BY_LIGHT && m_iMode != RAT_SMELL_FOOD ) + PickNewDest( FALSE ); +} + +void CRat::Look( int iDistance ) +{ + CBaseEntity *pSightEnt = NULL; + CBaseEntity *pPreviousEnt; + int iSighted = 0; + ClearConditions( bits_COND_SEE_HATE | bits_COND_SEE_DISLIKE | bits_COND_SEE_ENEMY | bits_COND_SEE_FEAR ); + if ( FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) + return; + m_pLink = NULL; + pPreviousEnt = this; + while ( ( pSightEnt = UTIL_FindEntityInSphere( pSightEnt, pev->origin, iDistance ) ) != NULL ) + { + if ( pSightEnt->IsPlayer() || FBitSet( pSightEnt->pev->flags, FL_MONSTER ) ) + { + if ( !FBitSet( pSightEnt->pev->flags, FL_NOTARGET ) && pSightEnt->pev->health > 0 ) + { + pPreviousEnt->m_pLink = pSightEnt; + pSightEnt->m_pLink = NULL; + pPreviousEnt = pSightEnt; + switch ( IRelationship( pSightEnt ) ) + { + case R_FR: + iSighted |= bits_COND_SEE_FEAR; + break; + case R_NO: + break; + default: + break; + } + } + } + } + SetConditions( iSighted ); +} diff --git a/dlls/rpg_rocket.cpp b/dlls/rpg_rocket.cpp new file mode 100644 index 0000000..8247659 --- /dev/null +++ b/dlls/rpg_rocket.cpp @@ -0,0 +1,320 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "gamerules.h" + +LINK_ENTITY_TO_CLASS( laser_spot, CLaserSpot ); + +//========================================================= +//========================================================= +CLaserSpot *CLaserSpot :: CreateSpot( void ) +{ + CLaserSpot *pSpot = GetClassPtr((CLaserSpot *)NULL ); + pSpot->Spawn(); + + pSpot->pev->classname = MAKE_STRING( "laser_spot" ); + + return pSpot; +} + +//========================================================= +//========================================================= +CLaserSpot *CLaserSpot :: CreateSpot( const char *spritename ) +{ + CLaserSpot *pSpot = CreateSpot(); + SET_MODEL( pSpot->edict(), spritename ); + + return pSpot; +} + +//========================================================= +//========================================================= +void CLaserSpot :: Spawn( void ) +{ + Precache( ); + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_NOT; + + pev->rendermode = kRenderGlow; + pev->renderfx = kRenderFxNoDissipation; + pev->renderamt = 255; + + SET_MODEL(ENT(pev), "sprites/laserdot.spr"); + UTIL_SetOrigin( this, pev->origin ); +} + +//========================================================= +// Suspend- make the laser sight invisible. +//========================================================= +void CLaserSpot :: Suspend( float flSuspendTime ) +{ + pev->effects |= EF_NODRAW; + + if( flSuspendTime == -1.0f ) + { + SetThink( NULL ); + } + else + { + SetThink( &CLaserSpot:: Revive ); + SetNextThink( flSuspendTime ); + } +} + +void CLaserSpot :: Update( CBasePlayer *pPlayer ) +{ + UTIL_MakeVectors( pPlayer->pev->v_angle ); + Vector vecSrc = pPlayer->GetGunPosition( ); + Vector vecAiming = gpGlobals->v_forward; + + TraceResult tr; + UTIL_TraceLine ( vecSrc, vecSrc + vecAiming * 8192, dont_ignore_monsters, pPlayer->edict(), &tr ); + + UTIL_SetOrigin( this, tr.vecEndPos ); +} + +//========================================================= +// Revive - bring a suspended laser sight back. +//========================================================= +void CLaserSpot :: Revive( void ) +{ + pev->effects &= ~EF_NODRAW; + + SetThink( NULL ); +} + +void CLaserSpot::Precache( void ) +{ + PRECACHE_MODEL( "sprites/laserdot.spr" ); +} + +LINK_ENTITY_TO_CLASS( rpg_rocket, CRpgRocket ); + +// Wargon: SaveData ïåðåìåùåí èç weapons.cpp. +TYPEDESCRIPTION CRpgRocket :: m_SaveData[] = +{ + DEFINE_FIELD( CRpgRocket, m_flIgniteTime, FIELD_TIME ), + DEFINE_FIELD( CRpgRocket, m_pLauncher, FIELD_CLASSPTR ), +}; IMPLEMENT_SAVERESTORE( CRpgRocket, CGrenade ); + +//========================================================= +//========================================================= +CRpgRocket *CRpgRocket :: CreateRpgRocket( Vector vecOrigin, Vector vecAngles, CBaseEntity *pOwner, CBasePlayerItem *pLauncher ) +{ + CRpgRocket *pRocket = GetClassPtr((CRpgRocket *)NULL ); + + EMIT_SOUND( pLauncher->m_pPlayer->edict(), CHAN_ITEM, "weapons/rocketfire1.wav", 1, 0.9 ); + + UTIL_SetOrigin( pRocket, vecOrigin ); + pRocket->pev->angles = vecAngles; + pRocket->Spawn(); + pRocket->SetTouch(& CRpgRocket::RocketTouch ); + pRocket->m_pLauncher = pLauncher;// remember what RPG fired me. + pRocket->m_pLauncher->m_cActiveRockets++;// register this missile as active for the launcher + pRocket->pev->owner = pOwner->edict(); + + return pRocket; +} + +//========================================================= +//========================================================= +void CRpgRocket :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_BBOX; + + SET_MODEL( edict(), "models/rpgrocket.mdl" ); + UTIL_SetSize( pev, g_vecZero, g_vecZero ); + UTIL_SetOrigin( this, pev->origin ); + + pev->classname = MAKE_STRING( "rpg_rocket" ); + + SetThink( &CRpgRocket :: IgniteThink ); + SetTouch( &CRpgRocket :: ExplodeTouch ); + + UTIL_MakeVectors( pev->angles ); + pev->angles.x = -pev->angles.x; + pev->gravity = 0.5; + + SetNextThink( 0.05 ); + + pev->dmg = gSkillData.plrDmgRPG; +} + +//========================================================= +//========================================================= +void CRpgRocket :: RocketTouch ( CBaseEntity *pOther ) +{ + // my launcher is still around, tell it I'm dead. + if( m_pLauncher ) m_pLauncher->m_cActiveRockets--; + STOP_SOUND( edict(), CHAN_VOICE, "weapons/rocket1.wav" ); + ExplodeTouch( pOther ); +} + +//========================================================= +//========================================================= +void CRpgRocket :: Precache( void ) +{ + PRECACHE_MODEL( "models/rpgrocket.mdl" ); + m_iTrail = PRECACHE_MODEL("sprites/smoke.spr"); + m_iFireTrail = PRECACHE_MODEL( "sprites/muz3.spr" ); // Wargon: Ñïðàéò äëÿ îãíåííîãî ñëåäà ó ðàêåò. + PRECACHE_SOUND ("weapons/rocket1.wav"); +} + +void CRpgRocket :: IgniteThink( void ) +{ + pev->movetype = MOVETYPE_FLY; + pev->effects |= EF_LIGHT; + + pev->body = 1; + + // make rocket sound + EMIT_SOUND( ENT(pev), CHAN_VOICE, "weapons/rocket1.wav", 1, 0.5 ); + + // rocket trail + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMFOLLOW ); + WRITE_SHORT(entindex()); // entity + WRITE_SHORT( m_iTrail ); // model + WRITE_BYTE( 30 ); // life + WRITE_BYTE( 5 ); // width + WRITE_BYTE( 160 ); // r, g, b + WRITE_BYTE( 170 ); // r, g, b + WRITE_BYTE( 170 ); // r, g, b + WRITE_BYTE( 200 ); // brightness + MESSAGE_END(); //move PHS/PVS data sending into here (SEND_ALL, SEND_PVS, SEND_PHS) + m_flIgniteTime = gpGlobals->time; + + // set to follow laser spot + SetThink(&CRpgRocket :: FollowThink ); + SetNextThink( 0 ); +} + +void CRpgRocket :: FollowThink( void ) +{ + CBaseEntity *pOther = NULL; + Vector vecTarget; + Vector vecDir; + float flDist, flMax, flDot; + TraceResult tr; + + UTIL_MakeAimVectors( pev->angles ); + + vecTarget = gpGlobals->v_forward; + flMax = 4096; + + // Examine all entities within a reasonable radius + while ((pOther = UTIL_FindEntityByClassname( pOther, "laser_spot" )) != NULL) + { + UTIL_TraceLine ( pev->origin, pOther->pev->origin, dont_ignore_monsters, ENT(pev), &tr ); + + if( tr.flFraction >= 0.90f ) + { + vecDir = pOther->pev->origin - pev->origin; + flDist = vecDir.Length( ); + vecDir = vecDir.Normalize( ); + flDot = DotProduct( gpGlobals->v_forward, vecDir ); + if(( flDot > 0.0f ) && ( flDist * ( 1.0f - flDot ) < flMax )) + { + flMax = flDist * ( 1.0f - flDot ); + vecTarget = vecDir; + } + } + } + + pev->angles = UTIL_VecToAngles( vecTarget ); + + // this acceleration and turning math is totally wrong, but it seems to respond well so don't change it. + float flSpeed = pev->velocity.Length(); + + if( gpGlobals->time - m_flIgniteTime < 1.0f ) + { + pev->velocity = pev->velocity * 0.2f + vecTarget * ( flSpeed * 0.8f + 400.0f ); + if( pev->waterlevel == 3 && pev->watertype > CONTENTS_FLYFIELD ) + { + // go slow underwater + if( pev->velocity.Length() > 300.0f ) + { + pev->velocity = pev->velocity.Normalize() * 300.0f; + } + UTIL_BubbleTrail( pev->origin - pev->velocity * 0.1, pev->origin, 4 ); + } + else + { + if( pev->velocity.Length() > 2000.0f ) + { + pev->velocity = pev->velocity.Normalize() * 2000.0f; + } + } + } + else + { + if( pev->effects & EF_LIGHT ) + { + pev->effects &= ~EF_LIGHT; + STOP_SOUND( ENT(pev), CHAN_VOICE, "weapons/rocket1.wav" ); + } + + pev->velocity = pev->velocity * 0.2 + vecTarget * flSpeed * 0.798; + + if(( pev->waterlevel == 0 || pev->watertype == CONTENTS_FOG ) && pev->velocity.Length() < 1500 ) + { + // my launcher is still around, tell it I'm dead. + if( m_pLauncher ) m_pLauncher->m_cActiveRockets--; + Detonate( ); + } + } + + // Wargon: Íåïîíÿòíî, ïî÷åìó ó ðàêåò íå ðèñóåòñÿ glow-ñïðàéò. Èñïðàâèë ïîëîæåíèå, ñäåëàâ ñâîé îãîíü äëÿ ðàêåòû ÷åðåç ìåññàãó. + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_SPRITE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( m_iFireTrail ); + WRITE_BYTE( 4 ); + WRITE_BYTE( 150 ); + MESSAGE_END(); + + // lev + extern int gmsgCustomDLight; + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_SPARKS ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_PAS, gmsgCustomDLight, pev->origin ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_BYTE( 10 ); // radius / 10 + WRITE_BYTE( 1 ); // life * 10 + WRITE_BYTE( 10 ); // decay / 10 + MESSAGE_END(); + + SetNextThink( 0.02 ); +} \ No newline at end of file diff --git a/dlls/rushscript.h b/dlls/rushscript.h new file mode 100644 index 0000000..29f3eae --- /dev/null +++ b/dlls/rushscript.h @@ -0,0 +1,11 @@ + +class CStartRush : public CBaseEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + CBaseEntity* GetDestinationEntity( void ); + void ReportSuccess( CBaseEntity* who ); + void Spawn( void ); + + int ObjectCaps( void ) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; \ No newline at end of file diff --git a/dlls/saverestore.h b/dlls/saverestore.h new file mode 100644 index 0000000..2355570 --- /dev/null +++ b/dlls/saverestore.h @@ -0,0 +1,182 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Implementation in UTIL.CPP +#ifndef SAVERESTORE_H +#define SAVERESTORE_H + +class CBaseEntity; + +class CSaveRestoreBuffer +{ +public: + CSaveRestoreBuffer( void ); + CSaveRestoreBuffer( SAVERESTOREDATA *pdata ); + ~CSaveRestoreBuffer( void ); + + int EntityIndex( entvars_t *pevLookup ); + int EntityIndex( edict_t *pentLookup ); + int EntityIndex( EOFFSET eoLookup ); + int EntityIndex( CBaseEntity *pEntity ); + + int EntityFlags( int entityIndex, int flags ) { return EntityFlagsSet( entityIndex, 0 ); } + int EntityFlagsSet( int entityIndex, int flags ); + + edict_t *EntityFromIndex( int entityIndex ); + + unsigned short TokenHash( const char *pszToken ); + +protected: + SAVERESTOREDATA *m_pdata; + void BufferRewind( int size ); + unsigned int HashString( const char *pszToken ); +}; + + +class CSave : public CSaveRestoreBuffer +{ +public: + CSave( SAVERESTOREDATA *pdata ) : CSaveRestoreBuffer( pdata ) {}; + + void WriteShort( const char *pname, const short *value, int count ); + void WriteInt( const char *pname, const int *value, int count ); // Save an int + void WriteFloat( const char *pname, const float *value, int count ); // Save a float + void WriteTime( const char *pname, const float *value, int count ); // Save a float (timevalue) + void WriteData( const char *pname, int size, const char *pdata ); // Save a binary data block + void WriteString( const char *pname, const char *pstring ); // Save a null-terminated string + void WriteString( const char *pname, const int *stringId, int count ); // Save a null-terminated string (engine string) + void WriteRange( const char *pname, const RandomRange &value ); + void WriteRange( const char *pname, const float *value, int count ); + void WriteVector( const char *pname, const Vector &value ); // Save a vector + void WriteVector( const char *pname, const float *value, int count ); // Save a vector + void WritePositionVector( const char *pname, const Vector &value ); // Offset for landmark if necessary + void WritePositionVector( const char *pname, const float *value, int count ); // array of pos vectors + void WriteFunction( const char *pname, const int *value, int count ); // Save a function pointer + // Save a function pointer. (LRC- also pass the classname to allow better error messages) + void WriteFunction( const char* cname, const char *pname, const int *value, int count ); + + int WriteEntVars( const char *pname, entvars_t *pev ); // Save entvars_t (entvars_t) + int WriteFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); + int WriteFields( const char *cname, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); + +private: + int DataEmpty( const char *pdata, int size ); + void BufferField( const char *pname, int size, const char *pdata ); + void BufferString( char *pdata, int len ); + void BufferData( const char *pdata, int size ); + void BufferHeader( const char *pname, int size ); +}; + +typedef struct +{ + unsigned short size; + unsigned short token; + char *pData; +} HEADER; + +class CRestore : public CSaveRestoreBuffer +{ +public: + CRestore( SAVERESTOREDATA *pdata ) : CSaveRestoreBuffer( pdata ) { m_global = 0; m_precache = TRUE; } + + int ReadEntVars( const char *pname, entvars_t *pev ); // entvars_t + int ReadFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ); + int ReadField( void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount, int startField, int size, char *pName, void *pData ); + int ReadInt( void ); + short ReadShort( void ); + int ReadNamedInt( const char *pName ); + char *ReadNamedString( const char *pName ); + int Empty( void ) { return (m_pdata == NULL) || ((m_pdata->pCurrentData-m_pdata->pBaseData)>=m_pdata->bufferSize); } + inline void SetGlobalMode( int global ) { m_global = global; } + void PrecacheMode( BOOL mode ) { m_precache = mode; } + +private: + char *BufferPointer( void ); + void BufferReadBytes( char *pOutput, int size ); + void BufferSkipBytes( int bytes ); + int BufferSkipZString( void ); + int BufferCheckZString( const char *string ); + + void BufferReadHeader( HEADER *pheader ); + + int m_global; // Restoring a global entity? + BOOL m_precache; +}; + +#define MAX_ENTITYARRAY 64 + +//#define ARRAYSIZE(p) (sizeof(p)/sizeof(p[0])) + +#define IMPLEMENT_SAVERESTORE(derivedClass,baseClass) \ + int derivedClass::Save( CSave &save )\ + {\ + if ( !baseClass::Save(save) )\ + return 0;\ + if (pev->targetname)\ + return save.WriteFields( STRING(pev->targetname), #derivedClass, this, m_SaveData, ARRAYSIZE(m_SaveData) );\ + else\ + return save.WriteFields( STRING(pev->classname), #derivedClass, this, m_SaveData, ARRAYSIZE(m_SaveData) );\ + }\ + int derivedClass::Restore( CRestore &restore )\ + {\ + if ( !baseClass::Restore(restore) )\ + return 0;\ + return restore.ReadFields( #derivedClass, this, m_SaveData, ARRAYSIZE(m_SaveData) );\ + } + + +typedef enum { GLOBAL_OFF = 0, GLOBAL_ON = 1, GLOBAL_DEAD = 2 } GLOBALESTATE; + +typedef struct globalentity_s globalentity_t; + +struct globalentity_s +{ + char name[64]; + char levelName[32]; + GLOBALESTATE state; + float global_time; + globalentity_t *pNext; +}; + +class CGlobalState +{ +public: + CGlobalState(); + void Reset( void ); + void ClearStates( void ); + void EntityAdd( string_t globalname, string_t mapName, GLOBALESTATE state, float time = 0.0f ); + void EntitySetState( string_t globalname, GLOBALESTATE state ); + void EntitySetTime( string_t globalname, float time ); + void EntityUpdate( string_t globalname, string_t mapname ); + const globalentity_t *EntityFromTable( string_t globalname ); + GLOBALESTATE EntityGetState( string_t globalname ); + float EntityGetTime( string_t globalname ); + int EntityInTable( string_t globalname ) { return (Find( globalname ) != NULL) ? 1 : 0; } + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +//#ifdef _DEBUG + void DumpGlobals( void ); +//#endif + +private: + globalentity_t *Find( string_t globalname ); + globalentity_t *m_pList; + int m_listCount; +}; + +extern CGlobalState gGlobalState; + +#endif //SAVERESTORE_H diff --git a/dlls/schedule.cpp b/dlls/schedule.cpp new file mode 100644 index 0000000..5e7a244 --- /dev/null +++ b/dlls/schedule.cpp @@ -0,0 +1,1776 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// schedule.cpp - functions and data pertaining to the +// monsters' AI scheduling system. +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "animation.h" +#include "scripted.h" +#include "nodes.h" +#include "defaultai.h" +#include "soundent.h" +#include "rushscript.h" // buz + +extern CGraph WorldGraph; + +//========================================================= +// FHaveSchedule - Returns TRUE if monster's m_pSchedule +// is anything other than NULL. +//========================================================= +BOOL CBaseMonster :: FHaveSchedule( void ) +{ + if ( m_pSchedule == NULL ) + { + return FALSE; + } + + return TRUE; +} + +//========================================================= +// ClearSchedule - blanks out the caller's schedule pointer +// and index. +//========================================================= +void CBaseMonster :: ClearSchedule( void ) +{ + m_iTaskStatus = TASKSTATUS_NEW; + m_pSchedule = NULL; + m_iScheduleIndex = 0; +} + +//========================================================= +// FScheduleDone - Returns TRUE if the caller is on the +// last task in the schedule +//========================================================= +BOOL CBaseMonster :: FScheduleDone ( void ) +{ + ASSERT( m_pSchedule != NULL ); + + if ( m_iScheduleIndex == m_pSchedule->cTasks ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// ChangeSchedule - replaces the monster's schedule pointer +// with the passed pointer, and sets the ScheduleIndex back +// to 0 +//========================================================= +void CBaseMonster :: ChangeSchedule ( Schedule_t *pNewSchedule ) +{ + ASSERT( pNewSchedule != NULL ); + + m_pSchedule = pNewSchedule; + m_iScheduleIndex = 0; + m_iTaskStatus = TASKSTATUS_NEW; + m_afConditions = 0;// clear all of the conditions + m_failSchedule = SCHED_NONE; + + if ( m_pSchedule->iInterruptMask & bits_COND_HEAR_SOUND && !(m_pSchedule->iSoundMask) ) + { + ALERT ( at_aiconsole, "COND_HEAR_SOUND with no sound mask!\n" ); + } + else if ( m_pSchedule->iSoundMask && !(m_pSchedule->iInterruptMask & bits_COND_HEAR_SOUND) ) + { + ALERT ( at_aiconsole, "Sound mask without COND_HEAR_SOUND!\n" ); + } + +#if _DEBUG + if ( !ScheduleFromName( pNewSchedule->pName ) ) + { + ALERT( at_debug, "Schedule %s not in table!!!\n", pNewSchedule->pName ); + } +#endif + +// this is very useful code if you can isolate a test case in a level with a single monster. It will notify +// you of every schedule selection the monster makes. +#if 0 + if ( FClassnameIs( pev, "monster_human_grunt" ) ) + { + Task_t *pTask = GetTask(); + + if ( pTask ) + { + const char *pName = NULL; + + if ( m_pSchedule ) + { + pName = m_pSchedule->pName; + } + else + { + pName = "No Schedule"; + } + + if ( !pName ) + { + pName = "Unknown"; + } + + ALERT( at_aiconsole, "%s: picked schedule %s\n", STRING( pev->classname ), pName ); + } + } +#endif// 0 + +} + +//========================================================= +// NextScheduledTask - increments the ScheduleIndex +//========================================================= +void CBaseMonster :: NextScheduledTask ( void ) +{ + ASSERT( m_pSchedule != NULL ); + + m_iTaskStatus = TASKSTATUS_NEW; + m_iScheduleIndex++; + + if ( FScheduleDone() ) + { + // just completed last task in schedule, so make it invalid by clearing it. + SetConditions( bits_COND_SCHEDULE_DONE ); + //ClearSchedule(); + } +} + +//========================================================= +// IScheduleFlags - returns an integer with all Conditions +// bits that are currently set and also set in the current +// schedule's Interrupt mask. +//========================================================= +int CBaseMonster :: IScheduleFlags ( void ) +{ + if( !m_pSchedule ) + { + return 0; + } + + // strip off all bits excepts the ones capable of breaking this schedule. + return m_afConditions & m_pSchedule->iInterruptMask; +} + +//========================================================= +// FScheduleValid - returns TRUE as long as the current +// schedule is still the proper schedule to be executing, +// taking into account all conditions +//========================================================= +BOOL CBaseMonster :: FScheduleValid ( void ) +{ + if ( m_pSchedule == NULL ) + { + // schedule is empty, and therefore not valid. + return FALSE; + } + + if ( HasConditions( m_pSchedule->iInterruptMask | bits_COND_SCHEDULE_DONE | bits_COND_TASK_FAILED ) ) + { +#ifdef DEBUG + if ( HasConditions ( bits_COND_TASK_FAILED ) && m_failSchedule == SCHED_NONE ) + { +// ALERT(at_console, "failed: %s\n", m_pSchedule->pName); + // fail! Send a visual indicator. + Vector tmp = pev->origin; + tmp.z = pev->absmax.z + 16; + UTIL_Sparks( tmp ); + } +#endif // DEBUG + + // some condition has interrupted the schedule, or the schedule is done + return FALSE; + } + + return TRUE; +} + +//========================================================= +// MaintainSchedule - does all the per-think schedule maintenance. +// ensures that the monster leaves this function with a valid +// schedule! +//========================================================= +void CBaseMonster :: MaintainSchedule ( void ) +{ + Schedule_t *pNewSchedule; + int i; + + // UNDONE: Tune/fix this 10... This is just here so infinite loops are impossible + for ( i = 0; i < 10; i++ ) + { + if ( m_pSchedule != NULL && TaskIsComplete() ) + { + NextScheduledTask(); + } + + // validate existing schedule + if ( !FScheduleValid() || m_MonsterState != m_IdealMonsterState ) + { + // if we come into this block of code, the schedule is going to have to be changed. + // if the previous schedule was interrupted by a condition, GetIdealState will be + // called. Else, a schedule finished normally. + + // Notify the monster that his schedule is changing + ScheduleChange(); + + // Call GetIdealState if we're not dead and one or more of the following... + // - in COMBAT state with no enemy (it died?) + // - conditions bits (excluding SCHEDULE_DONE) indicate interruption, + // - schedule is done but schedule indicates it wants GetIdealState called + // after successful completion (by setting bits_COND_SCHEDULE_DONE in iInterruptMask) + // DEAD & SCRIPT are not suggestions, they are commands! + if ( m_IdealMonsterState != MONSTERSTATE_DEAD && + (m_IdealMonsterState != MONSTERSTATE_SCRIPT || m_IdealMonsterState == m_MonsterState) ) + { + // if we're here, then either we're being told to do something (besides dying or playing a script) + // or our current schedule (besides dying) is invalid. -- LRC + if ( (m_afConditions && !HasConditions(bits_COND_SCHEDULE_DONE)) || + (m_pSchedule && (m_pSchedule->iInterruptMask & bits_COND_SCHEDULE_DONE)) || + ((m_MonsterState == MONSTERSTATE_COMBAT) && (m_hEnemy == NULL)) ) + { + GetIdealState(); + } + } + if ( HasConditions( bits_COND_TASK_FAILED ) && m_MonsterState == m_IdealMonsterState ) + { + if ( m_failSchedule != SCHED_NONE ) + pNewSchedule = GetScheduleOfType( m_failSchedule ); + else + pNewSchedule = GetScheduleOfType( SCHED_FAIL ); + // schedule was invalid because the current task failed to start or complete + ALERT ( at_aiconsole, "Schedule Failed at %d!\n", m_iScheduleIndex ); + ChangeSchedule( pNewSchedule ); + } + else + { + SetState( m_IdealMonsterState ); + if ( m_MonsterState == MONSTERSTATE_SCRIPT || m_MonsterState == MONSTERSTATE_DEAD ) + { + pNewSchedule = CBaseMonster::GetSchedule(); + } + else + pNewSchedule = GetSchedule(); + ChangeSchedule( pNewSchedule ); + } + } + + if ( m_iTaskStatus == TASKSTATUS_NEW ) + { + Task_t *pTask = GetTask(); + ASSERT( pTask != NULL ); + TaskBegin(); + StartTask( pTask ); + } + + // UNDONE: Twice?!!! + if ( m_Activity != m_IdealActivity ) + { + SetActivity ( m_IdealActivity ); + } + + if ( !TaskIsComplete() && m_iTaskStatus != TASKSTATUS_NEW ) + break; + } + + if ( TaskIsRunning() ) + { + Task_t *pTask = GetTask(); + ASSERT( pTask != NULL ); + RunTask( pTask ); + } + + // UNDONE: We have to do this so that we have an animation set to blend to if RunTask changes the animation + // RunTask() will always change animations at the end of a script! + // Don't do this twice + if ( m_Activity != m_IdealActivity ) + { + SetActivity ( m_IdealActivity ); + } +} + +//========================================================= +// RunTask +//========================================================= +void CBaseMonster :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_TURN_RIGHT: + case TASK_TURN_LEFT: + { + UpdateYaw(); + + if ( FacingIdeal() ) + { + TaskComplete(); + } + break; + } + + case TASK_PLAY_SEQUENCE_FACE_ENEMY: + case TASK_PLAY_SEQUENCE_FACE_TARGET: + { + CBaseEntity *pTarget; + + if ( pTask->iTask == TASK_PLAY_SEQUENCE_FACE_TARGET ) + pTarget = m_hTargetEnt; + else + pTarget = m_hEnemy; + if ( pTarget ) + { + SetIdealYawAndUpdate( pTarget->pev->origin - pev->origin , AI_KEEP_YAW_SPEED ); + } + if ( m_fSequenceFinished ) + TaskComplete(); + } + break; + + case TASK_PLAY_SEQUENCE: + case TASK_PLAY_ACTIVE_IDLE: + { + if ( m_fSequenceFinished ) + { + TaskComplete(); + } + break; + } + + + case TASK_FACE_ENEMY: + { + SetIdealYawToTargetAndUpdate( m_vecEnemyLKP ); + + if ( FacingIdeal() ) + { + TaskComplete(); + } + break; + } + case TASK_FACE_HINTNODE: + case TASK_FACE_LASTPOSITION: + case TASK_FACE_TARGET: + case TASK_FACE_IDEAL: + case TASK_FACE_ROUTE: + { + UpdateYaw(); + + if ( FacingIdeal() ) + { + TaskComplete(); + } + break; + } + case TASK_WAIT_PVS: + { + // g-cont. TESTTEST. completely ignore PVS + if ( 1 )// !FNullEnt(FIND_CLIENT_IN_PVS(edict())) ) + { + TaskComplete(); + } + break; + } + case TASK_WAIT_INDEFINITE: + { + // don't do anything. + break; + } + case TASK_WAIT: + case TASK_WAIT_RANDOM: + { + if ( gpGlobals->time >= m_flWaitFinished ) + { + TaskComplete(); + } + break; + } + case TASK_WAIT_FACE_ENEMY: + { + SetIdealYawToTargetAndUpdate( m_vecEnemyLKP , AI_KEEP_YAW_SPEED ); + + if ( gpGlobals->time >= m_flWaitFinished ) + { + TaskComplete(); + } + break; + } + case TASK_MOVE_TO_TARGET_RANGE: + { + float distance; + + if ( m_hTargetEnt == NULL ) + TaskFail(); + else + { + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + // Re-evaluate when you think your finished, or the target has moved too far + if ( (distance < pTask->flData) || (m_vecMoveGoal - m_hTargetEnt->pev->origin).Length() > pTask->flData * 0.5 ) + { + m_vecMoveGoal = m_hTargetEnt->pev->origin; + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + FRefreshRoute(); + } + + // Set the appropriate activity based on an overlapping range + // overlap the range to prevent oscillation + if ( distance < pTask->flData ) + { + TaskComplete(); + RouteClear(); // Stop moving + } + else if ( distance < 190 && m_movementActivity != ACT_WALK ) + m_movementActivity = ACT_WALK; + else if ( distance >= 270 && m_movementActivity != ACT_RUN ) + m_movementActivity = ACT_RUN; + } + + break; + } + + case TASK_MOVE_TO_TARGET_RANGE2: // buz + { + float distance; + + if ( m_hTargetEnt == NULL ) + TaskFail(); + else + { + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + // Re-evaluate when you think your finished, or the target has moved too far + if ( (distance < m_flRushDistance) || (m_vecMoveGoal - m_hTargetEnt->pev->origin).Length() > m_flRushDistance * 0.5 ) + { + // buz: çà òî âðåìÿ, ïîêà ìû áåæàëè ê öåëè, îíà ìîãëà îáíîâèòüñÿ. + // ñïðàøèâàåì öåëü åùå ðàç. + CStartRush* pRush = (CStartRush*)UTIL_FindEntityByTargetname(NULL, STRING( m_hRushEntity )); + if (pRush) + m_hTargetEnt = pRush->GetDestinationEntity(); + + m_vecMoveGoal = m_hTargetEnt->pev->origin; + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + FRefreshRoute(); + } + + if ( distance < m_flRushDistance ) + { + TaskComplete(); + RouteClear(); // Stop moving + + CBaseEntity *pRushEntity = UTIL_FindEntityByTargetname( NULL, STRING( m_hRushEntity ) ); + m_hRushEntity = iStringNull; + m_hTargetEnt = NULL; + + if (pRushEntity) + { + CStartRush* pRush = (CStartRush*)pRushEntity; + pRush->ReportSuccess(this); + } + else + ALERT(at_console, "ERROR: reached target, but no rush entity!\n"); + } + // else if ( distance < 190 && m_movementActivity != ACT_WALK ) + // m_movementActivity = ACT_WALK; + // else if ( distance >= 270 && m_movementActivity != ACT_RUN ) + // m_movementActivity = ACT_RUN; + } + + break; + } + + case TASK_WAIT_FOR_MOVEMENT: + { + if (MovementIsComplete()) + { + TaskComplete(); + RouteClear(); // Stop moving + } + break; + } + case TASK_DIE: + { + if ( m_fSequenceFinished && pev->frame >= 255 ) + { + pev->deadflag = DEAD_DEAD; + + SetThink ( NULL ); + StopAnimation(); + + SetThink(&CBaseMonster :: CorpseFallThink ); + + if ( ShouldFadeOnDeath() ) + { + // this monster was created by a monstermaker... fade the corpse out. + SUB_StartFadeOut(); + } + else + { + // body is gonna be around for a while, so have it stink for a bit. + CSoundEnt::InsertSound ( bits_SOUND_CARCASS, pev->origin, 384, 30 ); + } + } + break; + } + case TASK_RANGE_ATTACK1_NOTURN: + case TASK_MELEE_ATTACK1_NOTURN: + case TASK_MELEE_ATTACK2_NOTURN: + case TASK_RANGE_ATTACK2_NOTURN: + case TASK_RELOAD_NOTURN: + { + if ( m_fSequenceFinished ) + { + m_Activity = ACT_RESET; + TaskComplete(); + } + break; + } + case TASK_RANGE_ATTACK1: + case TASK_MELEE_ATTACK1: + case TASK_MELEE_ATTACK2: + case TASK_RANGE_ATTACK2: + case TASK_SPECIAL_ATTACK1: + case TASK_SPECIAL_ATTACK2: + case TASK_RELOAD: + { + SetIdealYawToTargetAndUpdate( m_vecEnemyLKP, AI_KEEP_YAW_SPEED ); + + if ( m_fSequenceFinished ) + { + m_Activity = ACT_RESET; + TaskComplete(); + } + break; + } + case TASK_SMALL_FLINCH: + { + if ( m_fSequenceFinished ) + { + TaskComplete(); + } + } + break; + case TASK_WAIT_FOR_SCRIPT: + { + if ( m_pCine->m_iDelay <= 0 && gpGlobals->time >= m_pCine->m_startTime ) + { + TaskComplete(); + } + break; + } + case TASK_PLAY_SCRIPT: + { +// ALERT(at_console, "Play Script\n"); + if (m_fSequenceFinished) + { +// ALERT(at_console, "Anim Finished\n"); + if (m_pCine->m_iRepeatsLeft > 0) + { +// ALERT(at_console, "Frame %f; Repeat %d from %f\n", pev->frame, m_pCine->m_iRepeatsLeft, m_pCine->m_fRepeatFrame); + m_pCine->m_iRepeatsLeft--; + pev->frame = m_pCine->m_fRepeatFrame; + ResetSequenceInfo( ); + } + else + { + TaskComplete(); + } + } + break; + } + } +} + +//========================================================= +// SetTurnActivity - measures the difference between the way +// the monster is facing and determines whether or not to +// select one of the 180 turn animations. +//========================================================= +void CBaseMonster :: SetTurnActivity ( void ) +{ + float flYD; + flYD = DeltaIdealYaw(); + + if( flYD <= -80 && flYD >= -100 && LookupActivity( ACT_90_RIGHT ) != ACTIVITY_NOT_AVAILABLE ) + { + // 90 degree right. + Remember( bits_MEMORY_TURNING ); + SetIdealActivity( ACT_90_RIGHT ); + return; + } + + if( flYD >= 80 && flYD <= 100 && LookupActivity( ACT_90_LEFT ) != ACTIVITY_NOT_AVAILABLE ) + { + // 90 degree left. + Remember( bits_MEMORY_TURNING ); + SetIdealActivity( ACT_90_LEFT ); + return; + } + + if( fabs( flYD ) >= 160 && LookupActivity ( ACT_180_LEFT ) != ACTIVITY_NOT_AVAILABLE ) + { + Remember( bits_MEMORY_TURNING ); + SetIdealActivity( ACT_180_LEFT ); + return; + } + + if( flYD <= -45 && LookupActivity ( ACT_TURN_RIGHT ) != ACTIVITY_NOT_AVAILABLE ) + { + // big right turn + SetIdealActivity( ACT_TURN_RIGHT ); + } + else if ( flYD > 45 && LookupActivity ( ACT_TURN_LEFT ) != ACTIVITY_NOT_AVAILABLE ) + { + // big left turn + SetIdealActivity( ACT_TURN_LEFT ); + } +} + +//========================================================= +// Start task - selects the correct activity and performs +// any necessary calculations to start the next task on the +// schedule. +//========================================================= +void CBaseMonster :: StartTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_TURN_RIGHT: + { + float flCurrentYaw; + + flCurrentYaw = UTIL_AngleMod( pev->angles.y ); + SetIdealYaw( UTIL_AngleMod( flCurrentYaw - pTask->flData )); + SetTurnActivity(); + break; + } + case TASK_TURN_LEFT: + { + float flCurrentYaw; + + flCurrentYaw = UTIL_AngleMod( pev->angles.y ); + SetIdealYaw( UTIL_AngleMod( flCurrentYaw + pTask->flData )); + SetTurnActivity(); + break; + } + case TASK_REMEMBER: + { + Remember ( (int)pTask->flData ); + TaskComplete(); + break; + } + case TASK_FORGET: + { + Forget ( (int)pTask->flData ); + TaskComplete(); + break; + } + case TASK_FIND_HINTNODE: + { + m_iHintNode = FindHintNode(); + + if ( m_iHintNode != NO_NODE ) + { + TaskComplete(); + } + else + { + TaskFail(); + } + break; + } + case TASK_STORE_LASTPOSITION: + { + m_vecLastPosition = pev->origin; + TaskComplete(); + break; + } + case TASK_CLEAR_LASTPOSITION: + { + m_vecLastPosition = g_vecZero; + TaskComplete(); + break; + } + case TASK_CLEAR_HINTNODE: + { + m_iHintNode = NO_NODE; + TaskComplete(); + break; + } + case TASK_STOP_MOVING: + { + if ( m_IdealActivity == m_movementActivity ) + { + m_IdealActivity = GetStoppedActivity(); + } + + if( LookupPoseParameter( "move_yaw") >= 0 ) + SetPoseParameter( "move_yaw", 0 ); + RouteClear(); + TaskComplete(); + break; + } + case TASK_PLAY_SEQUENCE_FACE_ENEMY: + case TASK_PLAY_SEQUENCE_FACE_TARGET: + case TASK_PLAY_SEQUENCE: + { + m_IdealActivity = ( Activity )( int )pTask->flData; + break; + } + case TASK_PLAY_ACTIVE_IDLE: + { + // monsters verify that they have a sequence for the node's activity BEFORE + // moving towards the node, so it's ok to just set the activity without checking here. + m_IdealActivity = ( Activity )WorldGraph.m_pNodes[ m_iHintNode ].m_sHintActivity; + break; + } + case TASK_SET_SCHEDULE: + { + Schedule_t *pNewSchedule; + + pNewSchedule = GetScheduleOfType( (int)pTask->flData ); + + if ( pNewSchedule ) + { + ChangeSchedule( pNewSchedule ); + } + else + { + TaskFail(); + } + + break; + } + case TASK_FIND_NEAR_NODE_COVER_FROM_ENEMY: + { + if ( m_hEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( FindCover( m_hEnemy->pev->origin, m_hEnemy->pev->view_ofs, 0, pTask->flData ) ) + { + // try for cover farther than the FLData from the schedule. + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_FAR_NODE_COVER_FROM_ENEMY: + { + if ( m_hEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( FindCover( m_hEnemy->pev->origin, m_hEnemy->pev->view_ofs, pTask->flData, CoverRadius() ) ) + { + // try for cover farther than the FLData from the schedule. + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_NODE_COVER_FROM_ENEMY: + { + if ( m_hEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( FindCover( m_hEnemy->pev->origin, m_hEnemy->pev->view_ofs, 0, CoverRadius() ) ) + { + // try for cover farther than the FLData from the schedule. + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_COVER_FROM_ENEMY: + { + entvars_t *pevCover; + + if ( m_hEnemy == NULL ) + { + // Find cover from self if no enemy available + pevCover = pev; +// TaskFail(); +// return; + } + else + pevCover = m_hEnemy->pev; + + if ( FindLateralCover( pevCover->origin, pevCover->view_ofs ) ) + { + // try lateral first + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else if ( FindCover( pevCover->origin, pevCover->view_ofs, 0, CoverRadius() ) ) + { + // then try for plain ole cover + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_COVER_FROM_ORIGIN: + { + if ( FindCover( pev->origin, pev->view_ofs, 0, CoverRadius() ) ) + { + // then try for plain ole cover + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else + { + // no cover! + TaskFail(); + } + } + break; + case TASK_FIND_COVER_FROM_BEST_SOUND: + { + CSound *pBestSound; + + pBestSound = PBestSound(); + + ASSERT( pBestSound != NULL ); + /* + if ( pBestSound && FindLateralCover( pBestSound->m_vecOrigin, g_vecZero ) ) + { + // try lateral first + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + */ + + if ( pBestSound && FindCover( pBestSound->m_vecOrigin, g_vecZero, pBestSound->m_iVolume, CoverRadius() ) ) + { + // then try for plain ole cover + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else + { + // no coverwhatsoever. or no sound in list + TaskFail(); + } + break; + } + case TASK_FACE_HINTNODE: + { + SetIdealYaw( WorldGraph.m_pNodes[ m_iHintNode ].m_flHintYaw ); + SetTurnActivity(); + break; + } + + case TASK_FACE_LASTPOSITION: + SetIdealYawToTarget( m_vecLastPosition ); + SetTurnActivity(); + break; + + case TASK_FACE_TARGET: + if ( m_hTargetEnt != NULL ) + { + SetIdealYawToTarget( m_hTargetEnt->pev->origin ); + SetTurnActivity(); + } + else + TaskFail(); + break; + case TASK_FACE_ENEMY: + { + SetIdealYawToTarget( m_vecEnemyLKP ); + SetTurnActivity(); + break; + } + case TASK_FACE_IDEAL: + { + SetTurnActivity(); + break; + } + case TASK_FACE_ROUTE: + { + if (FRouteClear()) + { + ALERT(at_aiconsole, "No route to face!\n"); + TaskFail(); + } + else + { + SetIdealYawToTarget( m_Route[m_iRouteIndex].vecLocation ); + + if( fabs( DeltaIdealYaw() ) <= 15.0f ) + { + // This character is already facing the path well enough that + // moving will look fairly natural. Don't bother with a transitional + // turn animation. + TaskComplete(); + break; + } + + SetTurnActivity(); + } + break; + } + case TASK_WAIT_PVS: + case TASK_WAIT_INDEFINITE: + { + // don't do anything. + break; + } + case TASK_WAIT: + case TASK_WAIT_FACE_ENEMY: + {// set a future time that tells us when the wait is over. + m_flWaitFinished = gpGlobals->time + pTask->flData; + break; + } + case TASK_WAIT_RANDOM: + {// set a future time that tells us when the wait is over. + m_flWaitFinished = gpGlobals->time + RANDOM_FLOAT( 0.1, pTask->flData ); + break; + } + case TASK_MOVE_TO_TARGET_RANGE: + { + if ( (m_hTargetEnt->pev->origin - pev->origin).Length() < 1 ) + TaskComplete(); + else + { + m_vecMoveGoal = m_hTargetEnt->pev->origin; + if ( !MoveToTarget( ACT_WALK, 0.5 ) ) + TaskFail(); + } + break; + } + case TASK_MOVE_TO_TARGET_RANGE2: // buz + { + float distance = ( m_hTargetEnt->pev->origin - pev->origin ).Length2D(); + + if (distance < m_flRushDistance) + { + TaskComplete(); + + CBaseEntity *pRushEntity = UTIL_FindEntityByTargetname( NULL, STRING( m_hRushEntity ) ); + m_hRushEntity = iStringNull; + m_hTargetEnt = NULL; + if (pRushEntity) + { + CStartRush* pRush = (CStartRush*)pRushEntity; + pRush->ReportSuccess(this); + } + else + ALERT(at_console, "ERROR: reached target, but no rush entity!\n"); + + // ALERT(at_console, "already there! rushdist: %f, mydist: %f\n", m_flRushDistance, distance); + } + else + { + m_vecMoveGoal = m_hTargetEnt->pev->origin; + + Activity act = m_iRushMovetype ? ACT_WALK : ACT_RUN; + if (LookupActivity ( act ) == ACTIVITY_NOT_AVAILABLE) + { + act = m_iRushMovetype ? ACT_RUN : ACT_WALK; // get another movement activity + ALERT(at_aiconsole, "Rush target: monster %s doesnt support this movement activity\n", STRING(pev->classname)); + } + + if ( !MoveToTarget( act, 0.5 ) ) + { + m_flRushNextTime = gpGlobals->time + 3;// ïîïðîáóåì ÷åðåç òðè ñåêóíäû åùå ðàç + TaskFail(); + // ALERT(at_console, "NO PATH!!!\n"); + } + } + break; + } + case TASK_RUN_TO_SCRIPT: + case TASK_WALK_TO_SCRIPT: + { + Activity newActivity; + + if ( !m_pGoalEnt || (m_pGoalEnt->pev->origin - pev->origin).Length() < 1 ) + TaskComplete(); + else + { + if ( pTask->iTask == TASK_WALK_TO_SCRIPT ) + newActivity = ACT_WALK; + else + newActivity = ACT_RUN; + // This monster can't do this! + if ( LookupActivity( newActivity ) == ACTIVITY_NOT_AVAILABLE ) + TaskComplete(); + else + { + if ( m_pGoalEnt != NULL ) + { + Vector vecDest; + vecDest = m_pGoalEnt->pev->origin; + + if ( !MoveToLocation( newActivity, 2, vecDest ) ) + { + TaskFail(); + ALERT( at_aiconsole, "%s Failed to reach script!!!\n", STRING(pev->classname) ); + RouteClear(); + } + } + else + { + TaskFail(); + ALERT( at_aiconsole, "%s: MoveTarget is missing!?!\n", STRING(pev->classname) ); + RouteClear(); + } + } + } + TaskComplete(); + break; + } + case TASK_CLEAR_MOVE_WAIT: + { + m_flMoveWaitFinished = gpGlobals->time; + TaskComplete(); + break; + } + case TASK_MELEE_ATTACK1_NOTURN: + case TASK_MELEE_ATTACK1: + { + m_IdealActivity = ACT_MELEE_ATTACK1; + break; + } + case TASK_MELEE_ATTACK2_NOTURN: + case TASK_MELEE_ATTACK2: + { + m_IdealActivity = ACT_MELEE_ATTACK2; + break; + } + case TASK_RANGE_ATTACK1_NOTURN: + case TASK_RANGE_ATTACK1: + { + m_IdealActivity = ACT_RANGE_ATTACK1; + break; + } + case TASK_RANGE_ATTACK2_NOTURN: + case TASK_RANGE_ATTACK2: + { + m_IdealActivity = ACT_RANGE_ATTACK2; + break; + } + case TASK_RELOAD_NOTURN: + case TASK_RELOAD: + { + m_IdealActivity = ACT_RELOAD; + break; + } + case TASK_SPECIAL_ATTACK1: + { + m_IdealActivity = ACT_SPECIAL_ATTACK1; + break; + } + case TASK_SPECIAL_ATTACK2: + { + m_IdealActivity = ACT_SPECIAL_ATTACK2; + break; + } + case TASK_SET_ACTIVITY: + { + m_IdealActivity = (Activity)(int)pTask->flData; + TaskComplete(); + break; + } + case TASK_GET_PATH_TO_ENEMY_LKP: + { + if ( BuildRoute ( m_vecEnemyLKP, bits_MF_TO_LOCATION, NULL ) ) + { + TaskComplete(); + } + else if (BuildNearestRoute( m_vecEnemyLKP, pev->view_ofs, 0, (m_vecEnemyLKP - pev->origin).Length() )) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToEnemyLKP failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_ENEMY: + { + CBaseEntity *pEnemy = m_hEnemy; + + if ( pEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( BuildRoute ( pEnemy->pev->origin, bits_MF_TO_ENEMY, pEnemy ) ) + { + TaskComplete(); + } + else if (BuildNearestRoute( pEnemy->pev->origin, pEnemy->pev->view_ofs, 0, (pEnemy->pev->origin - pev->origin).Length() )) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToEnemy failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_ENEMY_CORPSE: + { + UTIL_MakeVectors( pev->angles ); + if ( BuildRoute ( m_vecEnemyLKP - gpGlobals->v_forward * 64, bits_MF_TO_LOCATION, NULL ) ) + { + TaskComplete(); + } + else + { + ALERT ( at_aiconsole, "GetPathToEnemyCorpse failed!!\n" ); + TaskFail(); + } + } + break; + case TASK_GET_PATH_TO_SPOT: + { + CBaseEntity *pPlayer = UTIL_FindEntityByClassname( NULL, "player" ); + if ( BuildRoute ( m_vecMoveGoal, bits_MF_TO_LOCATION, pPlayer ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToSpot failed!!\n" ); + TaskFail(); + } + break; + } + + case TASK_GET_PATH_TO_TARGET: + { + RouteClear(); + if ( m_hTargetEnt != NULL && MoveToTarget( m_movementActivity, 1 ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToSpot failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_SCRIPT: + { + RouteClear(); + if ( m_pCine != NULL && MoveToLocation( m_movementActivity, 1, m_pCine->pev->origin ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToSpot failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_HINTNODE:// for active idles! + { + if ( MoveToLocation( m_movementActivity, 2, WorldGraph.m_pNodes[ m_iHintNode ].m_vecOrigin ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToHintNode failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_LASTPOSITION: + { + m_vecMoveGoal = m_vecLastPosition; + + if ( MoveToLocation( m_movementActivity, 2, m_vecMoveGoal ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToLastPosition failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_BESTSOUND: + { + CSound *pSound; + + pSound = PBestSound(); + + if ( pSound && MoveToLocation( m_movementActivity, 2, pSound->m_vecOrigin ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToBestSound failed!!\n" ); + TaskFail(); + } + break; + } +case TASK_GET_PATH_TO_BESTSCENT: + { + CSound *pScent; + + pScent = PBestScent(); + + if ( pScent && MoveToLocation( m_movementActivity, 2, pScent->m_vecOrigin ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToBestScent failed!!\n" ); + + TaskFail(); + } + break; + } + case TASK_RUN_PATH: + { + // UNDONE: This is in some default AI and some monsters can't run? -- walk instead? + if ( LookupActivity( ACT_RUN ) != ACTIVITY_NOT_AVAILABLE ) + { + m_movementActivity = ACT_RUN; + } + else + { + m_movementActivity = ACT_WALK; + } + TaskComplete(); + break; + } + case TASK_WALK_PATH: + { + if ( pev->movetype == MOVETYPE_FLY ) + { + m_movementActivity = ACT_FLY; + } + if ( LookupActivity( ACT_WALK ) != ACTIVITY_NOT_AVAILABLE ) + { + m_movementActivity = ACT_WALK; + } + else + { + m_movementActivity = ACT_RUN; + } + TaskComplete(); + break; + } + case TASK_STRAFE_PATH: + { + Vector2D vec2DirToPoint; + Vector2D vec2RightSide; + + // to start strafing, we have to first figure out if the target is on the left side or right side + UTIL_MakeVectors ( pev->angles ); + + vec2DirToPoint = ( m_Route[ 0 ].vecLocation - pev->origin ).Make2D().Normalize(); + vec2RightSide = gpGlobals->v_right.Make2D().Normalize(); + + if ( DotProduct ( vec2DirToPoint, vec2RightSide ) > 0 ) + { + // strafe right + m_movementActivity = ACT_STRAFE_RIGHT; + } + else + { + // strafe left + m_movementActivity = ACT_STRAFE_LEFT; + } + TaskComplete(); + break; + } + + + case TASK_WAIT_FOR_MOVEMENT: + { + if (FRouteClear()) + { + TaskComplete(); + } + break; + } + + case TASK_EAT: + { + Eat( pTask->flData ); + TaskComplete(); + break; + } + case TASK_SMALL_FLINCH: + { + m_IdealActivity = GetSmallFlinchActivity(); + break; + } + case TASK_DIE: + { + RouteClear(); + + m_IdealActivity = GetDeathActivity(); + + pev->deadflag = DEAD_DYING; + break; + } + case TASK_SOUND_WAKE: + { + AlertSound(); + TaskComplete(); + break; + } + case TASK_SOUND_DIE: + { + DeathSound(); + TaskComplete(); + break; + } + case TASK_SOUND_IDLE: + { + IdleSound(); + TaskComplete(); + break; + } + case TASK_SOUND_PAIN: + { + PainSound(); + TaskComplete(); + break; + } + case TASK_SOUND_DEATH: + { + DeathSound(); + TaskComplete(); + break; + } + case TASK_SOUND_ANGRY: + { + // sounds are complete as soon as we get here, cause we've already played them. + ALERT ( at_aiconsole, "SOUND\n" ); + TaskComplete(); + break; + } + case TASK_WAIT_FOR_SCRIPT: + { + if ( m_pCine->m_iDelay <= 0 && gpGlobals->time >= m_pCine->m_startTime ) + { + TaskComplete(); //LRC - start playing immediately + } + else if (!m_pCine->IsAction() && m_pCine->m_iszIdle) + { + m_pCine->StartSequence( (CBaseMonster *)this, m_pCine->m_iszIdle, FALSE ); + if (FStrEq( STRING(m_pCine->m_iszIdle), STRING(m_pCine->m_iszPlay))) + { + pev->framerate = 0; + } + } + else + m_IdealActivity = ACT_IDLE; + + break; + } + case TASK_PLAY_SCRIPT: + { + if (m_pCine->IsAction()) + { + //ALERT(at_console,"PlayScript: setting idealactivity %d\n",m_pCine->m_fAction); + switch(m_pCine->m_fAction) + { + case 0: + m_IdealActivity = ACT_RANGE_ATTACK1; break; + case 1: + m_IdealActivity = ACT_RANGE_ATTACK2; break; + case 2: + m_IdealActivity = ACT_MELEE_ATTACK1; break; + case 3: + m_IdealActivity = ACT_MELEE_ATTACK2; break; + case 4: + m_IdealActivity = ACT_SPECIAL_ATTACK1; break; + case 5: + m_IdealActivity = ACT_SPECIAL_ATTACK2; break; + case 6: + m_IdealActivity = ACT_RELOAD; break; + case 7: + m_IdealActivity = ACT_HOP; break; + } + pev->framerate = 1.0; // shouldn't be needed, but just in case + pev->movetype = MOVETYPE_FLY; + ClearBits(pev->flags, FL_ONGROUND); + } + else + { + m_pCine->StartSequence( (CBaseMonster *)this, m_pCine->m_iszPlay, TRUE ); + if ( m_fSequenceFinished ) + ClearSchedule(); + pev->framerate = 1.0; + //ALERT( at_aiconsole, "Script %s has begun for %s\n", STRING( m_pCine->m_iszPlay ), STRING(pev->classname) ); + } + m_scriptState = SCRIPT_PLAYING; + break; + } + case TASK_ENABLE_SCRIPT: + { + m_pCine->DelayStart( 0 ); + TaskComplete(); + break; + } +//LRC + case TASK_END_SCRIPT: + { + m_pCine->SequenceDone( this ); + TaskComplete(); + break; + } + case TASK_PLANT_ON_SCRIPT: + { + if ( m_pCine != NULL ) + { + // Plant on script + // LRC - if it's a teleport script, do the turn too + if (m_pCine->m_fMoveTo == 4 || m_pCine->m_fMoveTo == 6) + { + if (m_pCine->m_fTurnType == 0) //LRC + pev->angles.y = m_hTargetEnt->pev->angles.y; + else if (m_pCine->m_fTurnType == 1) + pev->angles.y = UTIL_VecToYaw(m_hTargetEnt->pev->origin - pev->origin); + pev->ideal_yaw = pev->angles.y; + pev->avelocity = Vector( 0, 0, 0 ); + pev->velocity = Vector( 0, 0, 0 ); + pev->effects |= EF_NOINTERP; + } + + if (m_pCine->m_fMoveTo != 6) + pev->origin = m_pGoalEnt->pev->origin; + } + + TaskComplete(); + break; + } + case TASK_FACE_SCRIPT: + { + if ( m_pCine != NULL && m_pCine->m_fMoveTo != 0) // movetype "no move" makes us ignore turntype + { + switch (m_pCine->m_fTurnType) + { + case 0: + SetIdealYaw( UTIL_AngleMod( m_pCine->pev->angles.y )); + break; + case 1: + // yes, this is inconsistent- turn to face uses the "target" and turn to angle uses the "cine". + if (m_hTargetEnt) + SetIdealYawToTarget( m_hTargetEnt->pev->origin ); + else + SetIdealYawToTarget( m_pCine->pev->origin ); + break; + // default: don't turn + } + } + + TaskComplete(); + m_IdealActivity = ACT_IDLE; + RouteClear(); + break; + } + case TASK_SUGGEST_STATE: + { + m_IdealMonsterState = (MONSTERSTATE)(int)pTask->flData; + TaskComplete(); + break; + } + + case TASK_SET_FAIL_SCHEDULE: + m_failSchedule = (int)pTask->flData; + TaskComplete(); + break; + + case TASK_CLEAR_FAIL_SCHEDULE: + m_failSchedule = SCHED_NONE; + TaskComplete(); + break; + + default: + { + ALERT ( at_aiconsole, "No StartTask entry for %d\n", (SHARED_TASKS)pTask->iTask ); + break; + } + } +} + +//========================================================= +// GetTask - returns a pointer to the current +// scheduled task. NULL if there's a problem. +//========================================================= +Task_t *CBaseMonster :: GetTask ( void ) +{ + if ( m_iScheduleIndex < 0 || m_iScheduleIndex >= m_pSchedule->cTasks ) + { + // m_iScheduleIndex is not within valid range for the monster's current schedule. + return NULL; + } + else + { + return &m_pSchedule->pTasklist[ m_iScheduleIndex ]; + } +} + +//========================================================= +// GetSchedule - Decides which type of schedule best suits +// the monster's current state and conditions. Then calls +// monster's member function to get a pointer to a schedule +// of the proper type. +//========================================================= +Schedule_t *CBaseMonster :: GetSchedule ( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_PRONE: + { + return GetScheduleOfType( SCHED_BARNACLE_VICTIM_GRAB ); + break; + } + case MONSTERSTATE_NONE: + { + ALERT ( at_aiconsole, "MONSTERSTATE IS NONE!\n" ); + break; + } + case MONSTERSTATE_IDLE: + { + // buz: rush target + if (!FStringNull(m_hRushEntity) && (gpGlobals->time > m_flRushNextTime) && (m_flRushNextTime != -1)) + { + CBaseEntity *pRushEntity = UTIL_FindEntityByTargetname( NULL, STRING( m_hRushEntity ) ); + if (pRushEntity) + { + CStartRush* pRush = (CStartRush*)pRushEntity; + m_hTargetEnt = pRush->GetDestinationEntity(); + + return GetScheduleOfType( SCHED_RUSH_TARGET ); + } + else + // rush entity not found on this map. + // try again after next changelevel + m_flRushNextTime = -1; + } + + if ( HasConditions ( bits_COND_HEAR_SOUND ) ) + { + return GetScheduleOfType( SCHED_ALERT_FACE ); + } + else if ( FRouteClear() ) + { + // no valid route! + return GetScheduleOfType( SCHED_IDLE_STAND ); + } + else + { + // valid route. Get moving + return GetScheduleOfType( SCHED_IDLE_WALK ); + } + break; + } + case MONSTERSTATE_ALERT: + { + // buz: rush target + if (!FStringNull(m_hRushEntity) && (gpGlobals->time > m_flRushNextTime) && (m_flRushNextTime != -1)) + { + CBaseEntity *pRushEntity = UTIL_FindEntityByTargetname( NULL, STRING( m_hRushEntity ) ); + if (pRushEntity) + { + CStartRush* pRush = (CStartRush*)pRushEntity; + m_hTargetEnt = pRush->GetDestinationEntity(); + + return GetScheduleOfType( SCHED_RUSH_TARGET ); + } + else + // rush entity not found on this map. + // try again after next changelevel + m_flRushNextTime = -1; + } + + if ( HasConditions( bits_COND_ENEMY_DEAD ) && LookupActivity( ACT_VICTORY_DANCE ) != ACTIVITY_NOT_AVAILABLE ) + { + return GetScheduleOfType ( SCHED_VICTORY_DANCE ); + } + + if ( HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE) ) + { + if ( fabs( DeltaIdealYaw() ) < (1.0 - m_flFieldOfView) * 60 ) // roughly in the correct direction + { + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ORIGIN ); + } + else if ( LookupActivity( ACT_SMALL_FLINCH ) != -1 ) + { + return GetScheduleOfType( SCHED_ALERT_SMALL_FLINCH ); + } + + // Not facing the correct dir, and I have no flinch animation + return GetScheduleOfType( SCHED_ALERT_FACE ); + } + + else if ( HasConditions ( bits_COND_HEAR_SOUND ) ) + { + return GetScheduleOfType( SCHED_ALERT_FACE ); + } + else + { + return GetScheduleOfType( SCHED_ALERT_STAND ); + } + break; + } + case MONSTERSTATE_COMBAT: + { + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // clear the current (dead) enemy and try to find another. + m_hEnemy = NULL; + + if ( GetEnemy() ) + { + ClearConditions( bits_COND_ENEMY_DEAD ); + return GetSchedule(); + } + else + { + SetState( MONSTERSTATE_ALERT ); + return GetSchedule(); + } + } + + if ( HasConditions(bits_COND_NEW_ENEMY) ) + { + return GetScheduleOfType ( SCHED_WAKE_ANGRY ); + } + else if (HasConditions(bits_COND_LIGHT_DAMAGE) && !HasMemory( bits_MEMORY_FLINCHED) ) + { + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + else if ( !HasConditions(bits_COND_SEE_ENEMY) ) + { + // we can't see the enemy + if ( !HasConditions(bits_COND_ENEMY_OCCLUDED) ) + { + // enemy is unseen, but not occluded! + // turn to face enemy + return GetScheduleOfType( SCHED_COMBAT_FACE ); + } + else + { + // chase! + return GetScheduleOfType( SCHED_CHASE_ENEMY ); + } + } + else + { + // we can see the enemy + if ( HasConditions(bits_COND_CAN_RANGE_ATTACK1) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + if ( HasConditions(bits_COND_CAN_RANGE_ATTACK2) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + if ( HasConditions(bits_COND_CAN_MELEE_ATTACK1) ) + { + return GetScheduleOfType( SCHED_MELEE_ATTACK1 ); + } + if ( HasConditions(bits_COND_CAN_MELEE_ATTACK2) ) + { + return GetScheduleOfType( SCHED_MELEE_ATTACK2 ); + } + if ( !HasConditions(bits_COND_CAN_RANGE_ATTACK1 | bits_COND_CAN_MELEE_ATTACK1) ) + { + // if we can see enemy but can't use either attack type, we must need to get closer to enemy + return GetScheduleOfType( SCHED_CHASE_ENEMY ); + } + else if ( !FacingIdeal() ) + { + //turn + return GetScheduleOfType( SCHED_COMBAT_FACE ); + } + else + { + ALERT ( at_aiconsole, "No suitable combat schedule!\n" ); + } + } + break; + } + case MONSTERSTATE_DEAD: + { + return GetScheduleOfType( SCHED_DIE ); + break; + } + case MONSTERSTATE_SCRIPT: + { + ASSERT( m_pCine != NULL ); + if ( !m_pCine ) + { + ALERT( at_aiconsole, "Script failed for %s\n", STRING(pev->classname) ); +// ALERT( at_console, "Script failed for %s\n", STRING(pev->classname) ); + CineCleanup(); + return GetScheduleOfType( SCHED_IDLE_STAND ); + } + + return GetScheduleOfType( SCHED_AISCRIPT ); + } + default: + { + ALERT ( at_aiconsole, "Invalid State for GetSchedule!\n" ); + break; + } + } + + return &slError[ 0 ]; +} diff --git a/dlls/schedule.h b/dlls/schedule.h new file mode 100644 index 0000000..e416956 --- /dev/null +++ b/dlls/schedule.h @@ -0,0 +1,295 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Scheduling +//========================================================= + +#ifndef SCHEDULE_H +#define SCHEDULE_H + +#define TASKSTATUS_NEW 0 // Just started +#define TASKSTATUS_RUNNING 1 // Running task & movement +#define TASKSTATUS_RUNNING_MOVEMENT 2 // Just running movement +#define TASKSTATUS_RUNNING_TASK 3 // Just running task +#define TASKSTATUS_COMPLETE 4 // Completed, get next task + + +//========================================================= +// These are the schedule types +//========================================================= +typedef enum +{ + SCHED_NONE = 0, + SCHED_IDLE_STAND, + SCHED_IDLE_WALK, + SCHED_WAKE_ANGRY, + SCHED_WAKE_CALLED, + SCHED_ALERT_FACE, + SCHED_ALERT_SMALL_FLINCH, + SCHED_ALERT_BIG_FLINCH, + SCHED_ALERT_STAND, + SCHED_INVESTIGATE_SOUND, + SCHED_COMBAT_FACE, + SCHED_COMBAT_STAND, + SCHED_CHASE_ENEMY, + SCHED_CHASE_ENEMY_FAILED, + SCHED_VICTORY_DANCE, + SCHED_TARGET_FACE, + SCHED_TARGET_CHASE, + SCHED_SMALL_FLINCH, + SCHED_TAKE_COVER_FROM_ENEMY, + SCHED_TAKE_COVER_FROM_BEST_SOUND, + SCHED_TAKE_COVER_FROM_ORIGIN, + SCHED_COWER, // usually a last resort! + SCHED_MELEE_ATTACK1, + SCHED_MELEE_ATTACK2, + SCHED_RANGE_ATTACK1, + SCHED_RANGE_ATTACK2, + SCHED_SPECIAL_ATTACK1, + SCHED_SPECIAL_ATTACK2, + SCHED_STANDOFF, + SCHED_ARM_WEAPON, + SCHED_RELOAD, + SCHED_GUARD, + SCHED_AMBUSH, + SCHED_DIE, + SCHED_WAIT_TRIGGER, + SCHED_FOLLOW, + SCHED_SLEEP, + SCHED_WAKE, + SCHED_BARNACLE_VICTIM_GRAB, + SCHED_BARNACLE_VICTIM_CHOMP, + SCHED_AISCRIPT, + SCHED_FAIL, + SCHED_RUSH_TARGET, // buz + + LAST_COMMON_SCHEDULE // Leave this at the bottom +} SCHEDULE_TYPE; + +//========================================================= +// These are the shared tasks +//========================================================= +typedef enum +{ + TASK_INVALID = 0, + TASK_WAIT, + TASK_WAIT_FACE_ENEMY, + TASK_WAIT_PVS, + TASK_SUGGEST_STATE, + TASK_WALK_TO_SCRIPT, + TASK_RUN_TO_SCRIPT, + TASK_MOVE_TO_TARGET_RANGE, + TASK_MOVE_TO_TARGET_RANGE2, // buz + TASK_GET_PATH_TO_ENEMY, + TASK_GET_PATH_TO_ENEMY_LKP, + TASK_GET_PATH_TO_ENEMY_CORPSE, + TASK_GET_PATH_TO_LEADER, + TASK_GET_PATH_TO_SPOT, + TASK_GET_PATH_TO_TARGET, + TASK_GET_PATH_TO_SCRIPT, + TASK_GET_PATH_TO_HINTNODE, + TASK_GET_PATH_TO_LASTPOSITION, + TASK_GET_PATH_TO_BESTSOUND, + TASK_GET_PATH_TO_BESTSCENT, + TASK_RUN_PATH, + TASK_WALK_PATH, + TASK_STRAFE_PATH, + TASK_CLEAR_MOVE_WAIT, + TASK_STORE_LASTPOSITION, + TASK_CLEAR_LASTPOSITION, + TASK_PLAY_ACTIVE_IDLE, + TASK_FIND_HINTNODE, + TASK_CLEAR_HINTNODE, + TASK_SMALL_FLINCH, + TASK_FACE_IDEAL, + TASK_FACE_ROUTE, + TASK_FACE_ENEMY, + TASK_FACE_HINTNODE, + TASK_FACE_TARGET, + TASK_FACE_LASTPOSITION, + TASK_RANGE_ATTACK1, + TASK_RANGE_ATTACK2, + TASK_MELEE_ATTACK1, + TASK_MELEE_ATTACK2, + TASK_RELOAD, + TASK_RANGE_ATTACK1_NOTURN, + TASK_RANGE_ATTACK2_NOTURN, + TASK_MELEE_ATTACK1_NOTURN, + TASK_MELEE_ATTACK2_NOTURN, + TASK_RELOAD_NOTURN, + TASK_SPECIAL_ATTACK1, + TASK_SPECIAL_ATTACK2, + TASK_CROUCH, + TASK_STAND, + TASK_GUARD, + TASK_STEP_LEFT, + TASK_STEP_RIGHT, + TASK_STEP_FORWARD, + TASK_STEP_BACK, + TASK_DODGE_LEFT, + TASK_DODGE_RIGHT, + TASK_SOUND_ANGRY, + TASK_SOUND_DEATH, + TASK_SET_ACTIVITY, + TASK_SET_SCHEDULE, + TASK_SET_FAIL_SCHEDULE, + TASK_CLEAR_FAIL_SCHEDULE, + TASK_PLAY_SEQUENCE, + TASK_PLAY_SEQUENCE_FACE_ENEMY, + TASK_PLAY_SEQUENCE_FACE_TARGET, + TASK_SOUND_IDLE, + TASK_SOUND_WAKE, + TASK_SOUND_PAIN, + TASK_SOUND_DIE, + TASK_FIND_COVER_FROM_BEST_SOUND,// tries lateral cover first, then node cover + TASK_FIND_COVER_FROM_ENEMY,// tries lateral cover first, then node cover + TASK_FIND_LATERAL_COVER_FROM_ENEMY, + TASK_FIND_NODE_COVER_FROM_ENEMY, + TASK_FIND_NEAR_NODE_COVER_FROM_ENEMY,// data for this one is the MAXIMUM acceptable distance to the cover. + TASK_FIND_FAR_NODE_COVER_FROM_ENEMY,// data for this one is there MINIMUM aceptable distance to the cover. + TASK_FIND_COVER_FROM_ORIGIN, + TASK_EAT, + TASK_DIE, + TASK_WAIT_FOR_SCRIPT, + TASK_PLAY_SCRIPT, + TASK_ENABLE_SCRIPT, + TASK_PLANT_ON_SCRIPT, + TASK_FACE_SCRIPT, + TASK_END_SCRIPT, //LRC + TASK_WAIT_RANDOM, + TASK_WAIT_INDEFINITE, + TASK_STOP_MOVING, + TASK_TURN_LEFT, + TASK_TURN_RIGHT, + TASK_REMEMBER, + TASK_FORGET, + TASK_WAIT_FOR_MOVEMENT, // wait until MovementIsComplete() + LAST_COMMON_TASK, // LEAVE THIS AT THE BOTTOM!! (sjb) +} SHARED_TASKS; + + +// These go in the flData member of the TASK_WALK_TO_TARGET, TASK_RUN_TO_TARGET +enum +{ + TARGET_MOVE_NORMAL = 0, + TARGET_MOVE_SCRIPTED = 1, +}; + + +// A goal should be used for a task that requires several schedules to complete. +// The goal index should indicate which schedule (ordinally) the monster is running. +// That way, when tasks fail, the AI can make decisions based on the context of the +// current goal and sequence rather than just the current schedule. +enum +{ + GOAL_ATTACK_ENEMY, + GOAL_MOVE, + GOAL_TAKE_COVER, + GOAL_MOVE_TARGET, + GOAL_EAT, +}; + +// an array of tasks is a task list +// an array of schedules is a schedule list +struct Task_t +{ + + int iTask; + float flData; +}; + +struct Schedule_t +{ + + Task_t *pTasklist; + int cTasks; + int iInterruptMask;// a bit mask of conditions that can interrupt this schedule + + // a more specific mask that indicates which TYPES of sounds will interrupt the schedule in the + // event that the schedule is broken by COND_HEAR_SOUND + int iSoundMask; + const char *pName; +}; + +// an array of waypoints makes up the monster's route. +// !!!LATER- this declaration doesn't belong in this file. +struct WayPoint_t +{ + Vector vecLocation; + int iType; +}; + +// these MoveFlag values are assigned to a WayPoint's TYPE in order to demonstrate the +// type of movement the monster should use to get there. +#define bits_MF_TO_TARGETENT ( 1 << 0 ) // local move to targetent. +#define bits_MF_TO_ENEMY ( 1 << 1 ) // local move to enemy +#define bits_MF_TO_COVER ( 1 << 2 ) // local move to a hiding place +#define bits_MF_TO_DETOUR ( 1 << 3 ) // local move to detour point. +#define bits_MF_TO_PATHCORNER ( 1 << 4 ) // local move to a path corner +#define bits_MF_TO_NODE ( 1 << 5 ) // local move to a node +#define bits_MF_TO_LOCATION ( 1 << 6 ) // local move to an arbitrary point +#define bits_MF_IS_GOAL ( 1 << 7 ) // this waypoint is the goal of the whole move. +#define bits_MF_DONT_SIMPLIFY ( 1 << 8 ) // Don't let the route code simplify this waypoint + +// If you define any flags that aren't _TO_ flags, add them here so we can mask +// them off when doing compares. +#define bits_MF_NOT_TO_MASK (bits_MF_IS_GOAL | bits_MF_DONT_SIMPLIFY) + +#define MOVEGOAL_NONE (0) +#define MOVEGOAL_TARGETENT (bits_MF_TO_TARGETENT) +#define MOVEGOAL_ENEMY (bits_MF_TO_ENEMY) +#define MOVEGOAL_PATHCORNER (bits_MF_TO_PATHCORNER) +#define MOVEGOAL_LOCATION (bits_MF_TO_LOCATION) +#define MOVEGOAL_NODE (bits_MF_TO_NODE) + +// these bits represent conditions that may befall the monster, of which some are allowed +// to interrupt certain schedules. +#define bits_COND_NO_AMMO_LOADED ( 1 << 0 ) // weapon needs to be reloaded! +#define bits_COND_SEE_HATE ( 1 << 1 ) // see something that you hate +#define bits_COND_SEE_FEAR ( 1 << 2 ) // see something that you are afraid of +#define bits_COND_SEE_DISLIKE ( 1 << 3 ) // see something that you dislike +#define bits_COND_SEE_ENEMY ( 1 << 4 ) // target entity is in full view. +#define bits_COND_ENEMY_OCCLUDED ( 1 << 5 ) // target entity occluded by the world +#define bits_COND_SMELL_FOOD ( 1 << 6 ) +#define bits_COND_ENEMY_TOOFAR ( 1 << 7 ) +#define bits_COND_LIGHT_DAMAGE ( 1 << 8 ) // hurt a little +#define bits_COND_HEAVY_DAMAGE ( 1 << 9 ) // hurt a lot +#define bits_COND_CAN_RANGE_ATTACK1 ( 1 << 10) +#define bits_COND_CAN_MELEE_ATTACK1 ( 1 << 11) +#define bits_COND_CAN_RANGE_ATTACK2 ( 1 << 12) +#define bits_COND_CAN_MELEE_ATTACK2 ( 1 << 13) +// #define bits_COND_CAN_RANGE_ATTACK3 ( 1 << 14) +#define bits_COND_CROUCH_NOT_SAFE ( 1 << 14) // buz: monster will be not safe if he crouch +#define bits_COND_PROVOKED ( 1 << 15) +#define bits_COND_NEW_ENEMY ( 1 << 16) +#define bits_COND_HEAR_SOUND ( 1 << 17) // there is an interesting sound +#define bits_COND_SMELL ( 1 << 18) // there is an interesting scent +#define bits_COND_ENEMY_FACING_ME ( 1 << 19) // enemy is facing me +#define bits_COND_ENEMY_DEAD ( 1 << 20) // enemy was killed. If you get this in combat, try to find another enemy. If you get it in alert, victory dance. +#define bits_COND_SEE_CLIENT ( 1 << 21) // see a client +#define bits_COND_SEE_NEMESIS ( 1 << 22) // see my nemesis + +#define bits_COND_SPECIAL1 ( 1 << 28) // Defined by individual monster +#define bits_COND_SPECIAL2 ( 1 << 29) // Defined by individual monster + +#define bits_COND_TASK_FAILED ( 1 << 30) +#define bits_COND_SCHEDULE_DONE ( 1 << 31) + + +#define bits_COND_ALL_SPECIAL (bits_COND_SPECIAL1 | bits_COND_SPECIAL2) + +#define bits_COND_CAN_ATTACK (bits_COND_CAN_RANGE_ATTACK1 | bits_COND_CAN_MELEE_ATTACK1 | bits_COND_CAN_RANGE_ATTACK2 | bits_COND_CAN_MELEE_ATTACK2) + +#endif // SCHEDULE_H diff --git a/dlls/scientist.cpp b/dlls/scientist.cpp new file mode 100644 index 0000000..59a30fa --- /dev/null +++ b/dlls/scientist.cpp @@ -0,0 +1,1483 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// human scientist (passive lab worker) +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "talkmonster.h" +#include "schedule.h" +#include "defaultai.h" +#include "scripted.h" +#include "animation.h" +#include "soundent.h" + + +//#define NUM_SCIENTIST_HEADS 4 // four heads available for scientist model +//enum { HEAD_GLASSES = 0, HEAD_EINSTEIN = 1, HEAD_LUTHER = 2, HEAD_SLICK = 3 }; + +enum +{ + SCHED_HIDE = LAST_TALKMONSTER_SCHEDULE + 1, + SCHED_FEAR, + SCHED_PANIC, + SCHED_STARTLE, + SCHED_TARGET_CHASE_SCARED, + SCHED_TARGET_FACE_SCARED, +}; + +enum +{ + TASK_SAY_HEAL = LAST_TALKMONSTER_TASK + 1, + TASK_HEAL, + TASK_SAY_FEAR, + TASK_RUN_PATH_SCARED, + TASK_SCREAM, + TASK_RANDOM_SCREAM, + TASK_MOVE_TO_TARGET_RANGE_SCARED, +}; + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define SCIENTIST_AE_HEAL ( 1 ) +#define SCIENTIST_AE_NEEDLEON ( 2 ) +#define SCIENTIST_AE_NEEDLEOFF ( 3 ) + +//======================================================= +// Scientist +//======================================================= + +class CScientist : public CTalkMonster +{ +public: + void Spawn( void ); + void Precache( void ); + + float MaxYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void RunTask( Task_t *pTask ); + void StartTask( Task_t *pTask ); + + // Wargon: Þçàòü ìîíñòðà ìîæíî òîëüêî åñëè îí æèâ. Ýòî íóæíî ÷òîáû èêîíêà þçà íå ïîêàçûâàëàñü íà ìåðòâûõ ìîíñòðàõ. + int ObjectCaps( void ) { if (pev->deadflag == DEAD_NO) return CTalkMonster :: ObjectCaps() | FCAP_IMPULSE_USE | FCAP_DISTANCE_USE; else return CTalkMonster::ObjectCaps(); } + + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); + virtual int FriendNumber( int arrayNumber ); + void SetActivity ( Activity newActivity ); + Activity GetStoppedActivity( void ); + int ISoundMask( void ); + void DeclineFollowing( void ); + void BlockedByPlayer ( CBasePlayer *pBlocker ); // buz + + float CoverRadius( void ) { return 1200; } // Need more room for cover because scientists want to get far away! + BOOL DisregardEnemy( CBaseEntity *pEnemy ) { return !pEnemy->IsAlive() || (gpGlobals->time - m_fearTime) > 15; } + + BOOL CanHeal( void ); + void Heal( void ); + void Scream( void ); + + // Override these to set behavior + Schedule_t *GetScheduleOfType ( int Type ); + Schedule_t *GetSchedule ( void ); + MONSTERSTATE GetIdealState ( void ); + + void DeathSound( void ); + void PainSound( void ); + + void TalkInit( void ); + + void Killed( entvars_t *pevAttacker, int iGib ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + CUSTOM_SCHEDULES; + +private: + float m_painTime; + float m_healTime; + float m_fearTime; +}; + +LINK_ENTITY_TO_CLASS( monster_scientist, CScientist ); + +TYPEDESCRIPTION CScientist::m_SaveData[] = +{ + DEFINE_FIELD( CScientist, m_painTime, FIELD_TIME ), + DEFINE_FIELD( CScientist, m_healTime, FIELD_TIME ), + DEFINE_FIELD( CScientist, m_fearTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CScientist, CTalkMonster ); + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +Task_t tlFollow[] = +{ +// { TASK_SET_FAIL_SCHEDULE, (float)SCHED_CANT_FOLLOW }, // If you fail, bail out of follow + { TASK_MOVE_TO_TARGET_RANGE,(float)128 }, // Move within 128 of target ent (client) +// { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE }, +}; + +Schedule_t slFollow[] = +{ + { + tlFollow, + ARRAYSIZE ( tlFollow ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + bits_SOUND_COMBAT | + bits_SOUND_DANGER, + "Follow" + }, +}; + +Task_t tlFollowScared[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_TARGET_CHASE },// If you fail, follow normally + { TASK_MOVE_TO_TARGET_RANGE_SCARED,(float)128 }, // Move within 128 of target ent (client) +// { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE_SCARED }, +}; + +Schedule_t slFollowScared[] = +{ + { + tlFollowScared, + ARRAYSIZE ( tlFollowScared ), + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + bits_SOUND_DANGER, + "FollowScared" + }, +}; + +Task_t tlFaceTargetScared[] = +{ + { TASK_FACE_TARGET, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_CROUCHIDLE }, + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_CHASE_SCARED }, +}; + +Schedule_t slFaceTargetScared[] = +{ + { + tlFaceTargetScared, + ARRAYSIZE ( tlFaceTargetScared ), + bits_COND_HEAR_SOUND | + bits_COND_NEW_ENEMY, + bits_SOUND_DANGER, + "FaceTargetScared" + }, +}; + +Task_t tlStopFollowing[] = +{ + { TASK_CANT_FOLLOW, (float)0 }, +}; + +Schedule_t slStopFollowing[] = +{ + { + tlStopFollowing, + ARRAYSIZE ( tlStopFollowing ), + 0, + 0, + "StopFollowing" + }, +}; + + +Task_t tlHeal[] = +{ + { TASK_MOVE_TO_TARGET_RANGE,(float)50 }, // Move within 60 of target ent (client) + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_TARGET_CHASE }, // If you fail, catch up with that guy! (change this to put syringe away and then chase) + { TASK_FACE_IDEAL, (float)0 }, + { TASK_SAY_HEAL, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_TARGET, (float)ACT_ARM }, // Whip out the needle + { TASK_HEAL, (float)0 }, // Put it in the player + { TASK_PLAY_SEQUENCE_FACE_TARGET, (float)ACT_DISARM }, // Put away the needle +}; + +Schedule_t slHeal[] = +{ + { + tlHeal, + ARRAYSIZE ( tlHeal ), + 0, // Don't interrupt or he'll end up running around with a needle all the time + 0, + "Heal" + }, +}; + + +Task_t tlFaceTarget[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_TARGET, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_CHASE }, +}; + +Schedule_t slFaceTarget[] = +{ + { + tlFaceTarget, + ARRAYSIZE ( tlFaceTarget ), + bits_COND_CLIENT_PUSH | + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND, + bits_SOUND_COMBAT | + bits_SOUND_DANGER, + "FaceTarget" + }, +}; + + +Task_t tlSciPanic[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_SCREAM, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_EXCITED }, // This is really fear-stricken excitement + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slSciPanic[] = +{ + { + tlSciPanic, + ARRAYSIZE ( tlSciPanic ), + 0, + 0, + "SciPanic" + }, +}; + + +Task_t tlIdleSciStand[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, // repick IDLESTAND every two seconds. + { TASK_TLK_HEADRESET, (float)0 }, // reset head position +}; + +Schedule_t slIdleSciStand[] = +{ + { + tlIdleSciStand, + ARRAYSIZE ( tlIdleSciStand ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_SMELL | + bits_COND_CLIENT_PUSH | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT |// sound flags + //bits_SOUND_PLAYER | + //bits_SOUND_WORLD | + bits_SOUND_DANGER | + bits_SOUND_MEAT |// scents + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "IdleSciStand" + + }, +}; + + +Task_t tlScientistCover[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_PANIC }, // If you fail, just panic! + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH_SCARED, (float)0 }, + { TASK_TURN_LEFT, (float)179 }, + { TASK_SET_SCHEDULE, (float)SCHED_HIDE }, +}; + +Schedule_t slScientistCover[] = +{ + { + tlScientistCover, + ARRAYSIZE ( tlScientistCover ), + bits_COND_NEW_ENEMY, + 0, + "ScientistCover" + }, +}; + + + +Task_t tlScientistHide[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_PANIC }, // If you fail, just panic! + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_CROUCH }, + { TASK_SET_ACTIVITY, (float)ACT_CROUCHIDLE }, // FIXME: This looks lame + { TASK_WAIT_RANDOM, (float)10.0 }, +}; + +Schedule_t slScientistHide[] = +{ + { + tlScientistHide, + ARRAYSIZE ( tlScientistHide ), + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND | + bits_COND_SEE_ENEMY | + bits_COND_SEE_HATE | + bits_COND_SEE_FEAR | + bits_COND_SEE_DISLIKE, + bits_SOUND_DANGER, + "ScientistHide" + }, +}; + + +Task_t tlScientistStartle[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_PANIC }, // If you fail, just panic! + { TASK_RANDOM_SCREAM, (float)0.3 }, // Scream 30% of the time + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCH }, + { TASK_RANDOM_SCREAM, (float)0.1 }, // Scream again 10% of the time + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCHIDLE }, + { TASK_WAIT_RANDOM, (float)1.0 }, +}; + +Schedule_t slScientistStartle[] = +{ + { + tlScientistStartle, + ARRAYSIZE ( tlScientistStartle ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_ENEMY | + bits_COND_SEE_HATE | + bits_COND_SEE_FEAR | + bits_COND_SEE_DISLIKE, + 0, + "ScientistStartle" + }, +}; + + + +Task_t tlFear[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_SAY_FEAR, (float)0 }, +// { TASK_PLAY_SEQUENCE, (float)ACT_FEAR_DISPLAY }, +}; + +Schedule_t slFear[] = +{ + { + tlFear, + ARRAYSIZE ( tlFear ), + bits_COND_NEW_ENEMY, + 0, + "Fear" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CScientist ) +{ + slFollow, + slFaceTarget, + slIdleSciStand, + slFear, + slScientistCover, + slScientistHide, + slScientistStartle, + slHeal, + slStopFollowing, + slSciPanic, + slFollowScared, + slFaceTargetScared, +}; + + +IMPLEMENT_CUSTOM_SCHEDULES( CScientist, CTalkMonster ); + + +void CScientist::BlockedByPlayer ( CBasePlayer *pBlocker ) +{ + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_BLOCKED"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "SC_BLOCKED", 4, VOL_NORM, ATTN_NORM ); + } +} + +void CScientist::DeclineFollowing( void ) +{ + Talk( 10 ); + m_hTalkTarget = m_hEnemy; + PlaySentence( m_szGrp[TLK_DECLINE], 2, VOL_NORM, ATTN_NORM ); //LRC +} + + +void CScientist :: Scream( void ) +{ + if ( FOkToSpeak() ) + { + Talk( 10 ); + m_hTalkTarget = m_hEnemy; + PlaySentence( "SC_SCREAM", RANDOM_FLOAT(3, 6), VOL_NORM, ATTN_NORM ); + } +} + + +Activity CScientist::GetStoppedActivity( void ) +{ + if ( m_hEnemy != NULL ) + return ACT_EXCITED; + return CTalkMonster::GetStoppedActivity(); +} + + +void CScientist :: StartTask( Task_t *pTask ) +{ + switch( pTask->iTask ) + { + case TASK_SAY_HEAL: +// if ( FOkToSpeak() ) + Talk( 2 ); + m_hTalkTarget = m_hTargetEnt; + PlaySentence( "SC_HEAL", 2, VOL_NORM, ATTN_IDLE ); + + TaskComplete(); + break; + + case TASK_SCREAM: + Scream(); + TaskComplete(); + break; + + case TASK_RANDOM_SCREAM: + if ( RANDOM_FLOAT( 0, 1 ) < pTask->flData ) + Scream(); + TaskComplete(); + break; + + case TASK_SAY_FEAR: + if ( FOkToSpeak() ) + { + Talk( 2 ); + m_hTalkTarget = m_hEnemy; + if ( m_hEnemy->IsPlayer() ) + PlaySentence( "SC_PLFEAR", 5, VOL_NORM, ATTN_NORM ); + else + PlaySentence( "SC_FEAR", 5, VOL_NORM, ATTN_NORM ); + } + TaskComplete(); + break; + + case TASK_HEAL: + m_IdealActivity = ACT_MELEE_ATTACK1; + break; + + case TASK_RUN_PATH_SCARED: + m_movementActivity = ACT_RUN_SCARED; + break; + + case TASK_MOVE_TO_TARGET_RANGE_SCARED: + { + if ( (m_hTargetEnt->pev->origin - pev->origin).Length() < 1 ) + TaskComplete(); + else + { + m_vecMoveGoal = m_hTargetEnt->pev->origin; + if ( !MoveToTarget( ACT_WALK_SCARED, 0.5 ) ) + TaskFail(); + } + } + break; + + default: + CTalkMonster::StartTask( pTask ); + break; + } +} + +void CScientist :: RunTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_RUN_PATH_SCARED: + if ( MovementIsComplete() ) + TaskComplete(); + if ( RANDOM_LONG(0,31) < 8 ) + Scream(); + break; + + case TASK_MOVE_TO_TARGET_RANGE_SCARED: + { + if ( RANDOM_LONG(0,63)< 8 ) + Scream(); + + if ( m_hEnemy == NULL ) + { + TaskFail(); + } + else + { + float distance; + + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + // Re-evaluate when you think your finished, or the target has moved too far + if ( (distance < pTask->flData) || (m_vecMoveGoal - m_hTargetEnt->pev->origin).Length() > pTask->flData * 0.5 ) + { + m_vecMoveGoal = m_hTargetEnt->pev->origin; + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + FRefreshRoute(); + } + + // Set the appropriate activity based on an overlapping range + // overlap the range to prevent oscillation + if ( distance < pTask->flData ) + { + TaskComplete(); + RouteClear(); // Stop moving + } + else if ( distance < 190 && m_movementActivity != ACT_WALK_SCARED ) + m_movementActivity = ACT_WALK_SCARED; + else if ( distance >= 270 && m_movementActivity != ACT_RUN_SCARED ) + m_movementActivity = ACT_RUN_SCARED; + } + } + break; + + case TASK_HEAL: + if ( m_fSequenceFinished ) + { + TaskComplete(); + } + else + { + if ( TargetDistance() > 90 ) + TaskComplete(); + SetIdealYawAndUpdate( m_hTargetEnt->pev->origin - pev->origin, AI_KEEP_YAW_SPEED ); + } + break; + default: + CTalkMonster::RunTask( pTask ); + break; + } +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CScientist :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_HUMAN_PASSIVE; +} + + +//========================================================= +// MaxYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +float CScientist :: MaxYawSpeed( void ) +{ + float ys; + + ys = 90; + + switch ( m_Activity ) + { + case ACT_IDLE: + ys = 120; + break; + case ACT_WALK: + ys = 180; + break; + case ACT_RUN: + ys = 150; + break; + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 120; + break; + } + + return ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CScientist :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case SCIENTIST_AE_HEAL: // Heal my target (if within range) + Heal(); + break; + case SCIENTIST_AE_NEEDLEON: + { + // buz: no model change in heal. Maybe later will be added +// int oldBody = pev->body; +// pev->body = (oldBody % NUM_SCIENTIST_HEADS) + NUM_SCIENTIST_HEADS * 1; + } + break; + case SCIENTIST_AE_NEEDLEOFF: + { +// int oldBody = pev->body; +// pev->body = (oldBody % NUM_SCIENTIST_HEADS) + NUM_SCIENTIST_HEADS * 0; + } + break; + + default: + CTalkMonster::HandleAnimEvent( pEvent ); + } +} + +//========================================================= +// Spawn +//========================================================= +void CScientist :: Spawn( void ) +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/scientist.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + if (pev->health == 0) + pev->health = gSkillData.scientistHealth; + pev->view_ofs = Vector ( 0, 0, 50 );// position of the eyes relative to monster's origin. + m_flFieldOfView = VIEW_FIELD_WIDE; // NOTE: we need a wide field of view so scientists will notice player and say hello + m_MonsterState = MONSTERSTATE_NONE; + +// m_flDistTooFar = 256.0; + + m_afCapability = bits_CAP_HEAR | bits_CAP_TURN_HEAD | bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE; + + // buz: remove this scientist skin mess etc + // White hands +/* pev->skin = 0; + + if ( pev->body == -1 ) + {// -1 chooses a random head + pev->body = RANDOM_LONG(0, NUM_SCIENTIST_HEADS-1);// pick a head, any head + } + + // Luther is black, make his hands black + if ( pev->body == HEAD_LUTHER ) + pev->skin = 1;*/ + + MonsterInit(); + SetUse(&CScientist :: FollowerUse ); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CScientist :: Precache( void ) +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/scientist.mdl"); + PRECACHE_SOUND("scientist/sci_pain1.wav"); + PRECACHE_SOUND("scientist/sci_pain2.wav"); + PRECACHE_SOUND("scientist/sci_pain3.wav"); + PRECACHE_SOUND("scientist/sci_pain4.wav"); + PRECACHE_SOUND("scientist/sci_pain5.wav"); + + // every new scientist must call this, otherwise + // when a level is loaded, nobody will talk (time is reset to 0) + TalkInit(); + + CTalkMonster::Precache(); +} + +// Init talk data +void CScientist :: TalkInit() +{ + + CTalkMonster::TalkInit(); + + // scientist will try to talk to friends in this order: + + m_szFriends[0] = "monster_scientist"; + m_szFriends[1] = "monster_sitting_scientist"; + m_szFriends[2] = "monster_barney"; + m_szFriends[3] = "monster_human_military"; // buz + m_szFriends[4] = "monster_human_alpha"; // buz + m_szFriends[5] = "monster_alpha_pistol"; // buz + + // scientists speach group names (group names are in sentences.txt) + + if (!m_iszSpeakAs) + { + m_szGrp[TLK_ANSWER] = "SC_ANSWER"; + m_szGrp[TLK_QUESTION] = "SC_QUESTION"; + m_szGrp[TLK_IDLE] = "SC_IDLE"; + m_szGrp[TLK_STARE] = "SC_STARE"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) + m_szGrp[TLK_USE] = "SC_PFOLLOW"; + else + m_szGrp[TLK_USE] = "SC_OK"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) + m_szGrp[TLK_UNUSE] = "SC_PWAIT"; + else + m_szGrp[TLK_UNUSE] = "SC_WAIT"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) + m_szGrp[TLK_DECLINE] = "SC_POK"; + else + m_szGrp[TLK_DECLINE] = "SC_NOTOK"; + m_szGrp[TLK_STOP] = "SC_STOP"; + m_szGrp[TLK_NOSHOOT] = "SC_SCARED"; + m_szGrp[TLK_HELLO] = "SC_HELLO"; + + m_szGrp[TLK_PLHURT1] = "!SC_CUREA"; + m_szGrp[TLK_PLHURT2] = "!SC_CUREB"; + m_szGrp[TLK_PLHURT3] = "!SC_CUREC"; + + m_szGrp[TLK_PHELLO] = "SC_PHELLO"; + m_szGrp[TLK_PIDLE] = "SC_PIDLE"; + m_szGrp[TLK_PQUESTION] = "SC_PQUEST"; + m_szGrp[TLK_SMELL] = "SC_SMELL"; + + m_szGrp[TLK_WOUND] = "SC_WOUND"; + m_szGrp[TLK_MORTAL] = "SC_MORTAL"; + } + +/* // get voice for head + switch (pev->body % 3) + { + default: + case HEAD_GLASSES: m_voicePitch = 105; break; //glasses + case HEAD_EINSTEIN: m_voicePitch = 100; break; //einstein + case HEAD_LUTHER: m_voicePitch = 95; break; //luther + case HEAD_SLICK: m_voicePitch = 100; break;//slick + }*/ // buz + m_voicePitch = 100; // buz +} + +int CScientist :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType) +{ + + if ( pevInflictor && pevInflictor->flags & FL_CLIENT ) + { + Remember( bits_MEMORY_PROVOKED ); + StopFollowing( TRUE ); + } + + // make sure friends talk about it if player hurts scientist... + return CTalkMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); +} + + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. In the base class implementation, +// monsters care about all sounds, but no scents. +//========================================================= +int CScientist :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_DANGER | + bits_SOUND_PLAYER; +} + +//========================================================= +// PainSound +//========================================================= +void CScientist :: PainSound ( void ) +{ + if (gpGlobals->time < m_painTime ) + return; + + m_painTime = gpGlobals->time + RANDOM_FLOAT(0.5, 0.75); + + switch (RANDOM_LONG(0,4)) + { + case 0: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain1.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 1: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain2.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 2: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain3.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 3: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain4.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 4: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain5.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CScientist :: DeathSound ( void ) +{ + PainSound(); +} + + +void CScientist::Killed( entvars_t *pevAttacker, int iGib ) +{ + SetUse( NULL ); + CTalkMonster::Killed( pevAttacker, iGib ); +} + + +void CScientist :: SetActivity ( Activity newActivity ) +{ + // buz +/* if ((newActivity == ACT_WALK) && m_iUseAlertAnims) + newActivity = ACT_WALK_SCARED; + else if ((newActivity == ACT_RUN) && m_iUseAlertAnims) + newActivity = ACT_RUN_SCARED;*/ + + int iSequence = LookupActivity ( newActivity ); + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence == ACTIVITY_NOT_AVAILABLE ) + newActivity = ACT_IDLE; + CTalkMonster::SetActivity( newActivity ); +} + + +Schedule_t* CScientist :: GetScheduleOfType ( int Type ) +{ + Schedule_t *psched; + + switch( Type ) + { + // Hook these to make a looping schedule + case SCHED_TARGET_FACE: + // call base class default so that scientist will talk + // when 'used' + psched = CTalkMonster::GetScheduleOfType(Type); + + if (psched == slIdleStand) + return slFaceTarget; // override this for different target face behavior + else + return psched; + + case SCHED_TARGET_CHASE: + return slFollow; + + case SCHED_CANT_FOLLOW: + return slStopFollowing; + + case SCHED_PANIC: + return slSciPanic; + + case SCHED_TARGET_CHASE_SCARED: + return slFollowScared; + + case SCHED_TARGET_FACE_SCARED: + return slFaceTargetScared; + + case SCHED_IDLE_STAND: + // call base class default so that scientist will talk + // when standing during idle + psched = CTalkMonster::GetScheduleOfType(Type); + + if (psched == slIdleStand) + return slIdleSciStand; + else + return psched; + + case SCHED_HIDE: + return slScientistHide; + + case SCHED_STARTLE: + return slScientistStartle; + + case SCHED_FEAR: + return slFear; + } + + return CTalkMonster::GetScheduleOfType( Type ); +} + +Schedule_t *CScientist :: GetSchedule ( void ) +{ + // so we don't keep calling through the EHANDLE stuff + CBaseEntity *pEnemy = m_hEnemy; + + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + + switch( m_MonsterState ) + { + case MONSTERSTATE_ALERT: + case MONSTERSTATE_IDLE: + if ( pEnemy ) + { + if ( HasConditions( bits_COND_SEE_ENEMY ) ) + m_fearTime = gpGlobals->time; + else if ( DisregardEnemy( pEnemy ) ) // After 15 seconds of being hidden, return to alert + { + m_hEnemy = NULL; + pEnemy = NULL; + } + } + + if ( HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE)) + { + // flinch if hurt + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + + // Cower when you hear something scary + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound ) + { + if ( pSound->m_iType & (bits_SOUND_DANGER | bits_SOUND_COMBAT) ) + { + if ( gpGlobals->time - m_fearTime > 3 ) // Only cower every 3 seconds or so + { + m_fearTime = gpGlobals->time; // Update last fear + return GetScheduleOfType( SCHED_STARTLE ); // This will just duck for a second + } + } + } + } + + // Behavior for following the player + if ( IsFollowing() ) + { + if ( !m_hTargetEnt->IsAlive() ) + { + // UNDONE: Comment about the recently dead player here? + StopFollowing( FALSE ); + break; + } + + int relationship = R_NO; + + // Nothing scary, just me and the player + if ( pEnemy != NULL ) + relationship = IRelationship( pEnemy ); + + // UNDONE: Model fear properly, fix R_FR and add multiple levels of fear + if ( relationship != R_DL && relationship != R_HT ) + { + // If I'm already close enough to my target + if ( TargetDistance() <= 128 ) + { + if ( CanHeal() ) // Heal opportunistically + return slHeal; + if ( HasConditions( bits_COND_CLIENT_PUSH ) ) // Player wants me to move + return GetScheduleOfType( SCHED_MOVE_AWAY_FOLLOW ); + } + return GetScheduleOfType( SCHED_TARGET_FACE ); // Just face and follow. + } + else // UNDONE: When afraid, scientist won't move out of your way. Keep This? If not, write move away scared + { + if ( HasConditions( bits_COND_NEW_ENEMY ) ) // I just saw something new and scary, react + return GetScheduleOfType( SCHED_FEAR ); // React to something scary + return GetScheduleOfType( SCHED_TARGET_FACE_SCARED ); // face and follow, but I'm scared! + } + } + + if ( HasConditions( bits_COND_CLIENT_PUSH ) ) // Player wants me to move + return GetScheduleOfType( SCHED_MOVE_AWAY ); + + // try to say something about smells + TrySmellTalk(); + break; + case MONSTERSTATE_COMBAT: + if ( HasConditions( bits_COND_NEW_ENEMY ) ) + return slFear; // Point and scream! + if ( HasConditions( bits_COND_SEE_ENEMY ) ) + return slScientistCover; // Take Cover + + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + return slTakeCoverFromBestSound; // Cower and panic from the scary sound! + + return slScientistCover; // Run & Cower + break; + } + + return CTalkMonster::GetSchedule(); +} + +MONSTERSTATE CScientist :: GetIdealState ( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_ALERT: + case MONSTERSTATE_IDLE: + if ( HasConditions( bits_COND_NEW_ENEMY ) ) + { + if ( IsFollowing() ) + { + int relationship = IRelationship( m_hEnemy ); + if ( relationship != R_FR || relationship != R_HT && !HasConditions( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) ) + { + // Don't go to combat if you're following the player + m_IdealMonsterState = MONSTERSTATE_ALERT; + return m_IdealMonsterState; + } + StopFollowing( TRUE ); + } + } + else if ( HasConditions( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) ) + { + // Stop following if you take damage + if ( IsFollowing() ) + StopFollowing( TRUE ); + } + break; + + case MONSTERSTATE_COMBAT: + { + CBaseEntity *pEnemy = m_hEnemy; + if ( pEnemy != NULL ) + { + if ( DisregardEnemy( pEnemy ) ) // After 15 seconds of being hidden, return to alert + { + // Strip enemy when going to alert + m_IdealMonsterState = MONSTERSTATE_ALERT; + m_hEnemy = NULL; + return m_IdealMonsterState; + } + // Follow if only scared a little + if ( m_hTargetEnt != NULL ) + { + m_IdealMonsterState = MONSTERSTATE_ALERT; + return m_IdealMonsterState; + } + + if ( HasConditions ( bits_COND_SEE_ENEMY ) ) + { + m_fearTime = gpGlobals->time; + m_IdealMonsterState = MONSTERSTATE_COMBAT; + return m_IdealMonsterState; + } + + } + } + break; + } + + return CTalkMonster::GetIdealState(); +} + + +BOOL CScientist::CanHeal( void ) +{ + if ( (m_healTime > gpGlobals->time) || (m_hTargetEnt == NULL) || (m_hTargetEnt->pev->health > (m_hTargetEnt->pev->max_health * 0.5)) ) + return FALSE; + + return TRUE; +} + +void CScientist::Heal( void ) +{ + if ( !CanHeal() ) + return; + + Vector target = m_hTargetEnt->pev->origin - pev->origin; + if ( target.Length() > 100 ) + return; + + m_hTargetEnt->TakeHealth( gSkillData.scientistHeal, DMG_GENERIC ); + // Don't heal again for 1 minute + m_healTime = gpGlobals->time + 60; +} + +int CScientist::FriendNumber( int arrayNumber ) +{ + static int array[3] = { 1, 2, 0 }; + if ( arrayNumber < 3 ) + return array[ arrayNumber ]; + return arrayNumber; +} + + +//========================================================= +// Dead Scientist PROP +//========================================================= +class CDeadScientist : public CBaseMonster +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_HUMAN_PASSIVE; } + + void KeyValue( KeyValueData *pkvd ); + int m_iPose;// which sequence to display + static char *m_szPoses[7]; +}; +char *CDeadScientist::m_szPoses[] = { "lying_on_back", "lying_on_stomach", "dead_sitting", "dead_hang", "dead_table1", "dead_table2", "dead_table3" }; + +void CDeadScientist::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "pose")) + { + m_iPose = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} +LINK_ENTITY_TO_CLASS( monster_scientist_dead, CDeadScientist ); + +// +// ********** DeadScientist SPAWN ********** +// +void CDeadScientist :: Spawn( ) +{ + PRECACHE_MODEL("models/scientist.mdl"); + SET_MODEL(ENT(pev), "models/scientist.mdl"); + + pev->sequence = 0; + // Corpses have less health + pev->health = 8;//gSkillData.scientistHealth; + + m_bloodColor = BLOOD_COLOR_RED; + +/* if ( pev->body == -1 ) + {// -1 chooses a random head + pev->body = RANDOM_LONG(0, NUM_SCIENTIST_HEADS-1);// pick a head, any head + } + // Luther is black, make his hands black + if ( pev->body == HEAD_LUTHER ) + pev->skin = 1; + else + pev->skin = 0;*/ // buz + + pev->sequence = LookupSequence( m_szPoses[m_iPose] ); + if (pev->sequence == -1) + { + ALERT ( at_debug, "Dead scientist with bad pose\n" ); + } + + // pev->skin += 2; // use bloody skin -- UNDONE: Turn this back on when we have a bloody skin again! + MonsterInitDead(); +} + + +//========================================================= +// Sitting Scientist PROP +//========================================================= + +class CSittingScientist : public CScientist // kdb: changed from public CBaseMonster so he can speak +{ +public: + void Spawn( void ); + void Precache( void ); + + void EXPORT SittingThink( void ); + int Classify ( void ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + virtual void SetAnswerQuestion( CTalkMonster *pSpeaker ); + int FriendNumber( int arrayNumber ); + + int FIdleSpeak ( void ); + int m_baseSequence; + int m_headTurn; + float m_flResponseDelay; +}; + +LINK_ENTITY_TO_CLASS( monster_sitting_scientist, CSittingScientist ); +TYPEDESCRIPTION CSittingScientist::m_SaveData[] = +{ + // Don't need to save/restore m_baseSequence (recalced) + DEFINE_FIELD( CSittingScientist, m_headTurn, FIELD_INTEGER ), + DEFINE_FIELD( CSittingScientist, m_flResponseDelay, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CSittingScientist, CScientist ); + +// animation sequence aliases +typedef enum +{ +SITTING_ANIM_sitlookleft, +SITTING_ANIM_sitlookright, +SITTING_ANIM_sitscared, +SITTING_ANIM_sitting2, +SITTING_ANIM_sitting3 +} SITTING_ANIM; + + +#define SF_SITTINGSCI_POSTDISASTER 1024 + +// +// ********** Scientist SPAWN ********** +// +void CSittingScientist :: Spawn( ) +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/scientist.mdl"); + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/scientist.mdl"); + Precache(); + InitBoneControllers(); + + UTIL_SetSize(pev, Vector(-14, -14, 0), Vector(14, 14, 36)); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + pev->health = 50; + + m_bloodColor = BLOOD_COLOR_RED; + m_flFieldOfView = VIEW_FIELD_WIDE; // indicates the width of this monster's forward view cone ( as a dotproduct result ) + + m_afCapability = bits_CAP_HEAR | bits_CAP_TURN_HEAD; + + if (!FBitSet(pev->spawnflags, SF_SITTINGSCI_POSTDISASTER)) //LRC- allow a sitter to be postdisaster. + SetBits(pev->spawnflags, SF_MONSTER_PREDISASTER); // predisaster only! + +/* if ( pev->body == -1 ) + {// -1 chooses a random head + pev->body = RANDOM_LONG(0, NUM_SCIENTIST_HEADS-1);// pick a head, any head + } + // Luther is black, make his hands black + if ( pev->body == HEAD_LUTHER ) + pev->skin = 1;*/ // buz + + m_baseSequence = LookupSequence( "sitlookleft" ); + pev->sequence = m_baseSequence + RANDOM_LONG(0,4); + ResetSequenceInfo( ); + + SetThink(&CSittingScientist ::SittingThink); + SetNextThink( 0.1 ); + + DROP_TO_FLOOR ( ENT(pev) ); +} + +void CSittingScientist :: Precache( void ) +{ + m_baseSequence = LookupSequence( "sitlookleft" ); + TalkInit(); +} + +//========================================================= +// ID as a passive human +//========================================================= +int CSittingScientist :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_HUMAN_PASSIVE; +} + + +int CSittingScientist::FriendNumber( int arrayNumber ) +{ + static int array[3] = { 2, 1, 0 }; + if ( arrayNumber < 3 ) + return array[ arrayNumber ]; + return arrayNumber; +} + + + +//========================================================= +// sit, do stuff +//========================================================= +void CSittingScientist :: SittingThink( void ) +{ + CBaseEntity *pent; + + StudioFrameAdvance( ); + + // try to greet player + if (FIdleHello()) + { + pent = FindNearestFriend(TRUE); + if (pent) + { + float yaw = VecToYaw(pent->pev->origin - pev->origin) - pev->angles.y; + + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + + if (yaw > 0) + pev->sequence = m_baseSequence + SITTING_ANIM_sitlookleft; + else + pev->sequence = m_baseSequence + SITTING_ANIM_sitlookright; + + ResetSequenceInfo( ); + pev->frame = 0; + SetBoneController( 0, 0 ); + } + } + else if (m_fSequenceFinished) + { + int i = RANDOM_LONG(0,99); + m_headTurn = 0; + + if (m_flResponseDelay && gpGlobals->time > m_flResponseDelay) + { + // respond to question + IdleRespond(); + pev->sequence = m_baseSequence + SITTING_ANIM_sitscared; + m_flResponseDelay = 0; + } + else if (i < 30) + { + pev->sequence = m_baseSequence + SITTING_ANIM_sitting3; + + // turn towards player or nearest friend and speak + + if (!FBitSet(m_bitsSaid, bit_saidHelloPlayer)) + pent = FindNearestFriend(TRUE); + else + pent = FindNearestFriend(FALSE); + + if (!FIdleSpeak() || !pent) + { + m_headTurn = RANDOM_LONG(0,8) * 10 - 40; + pev->sequence = m_baseSequence + SITTING_ANIM_sitting3; + } + else + { + // only turn head if we spoke + float yaw = VecToYaw(pent->pev->origin - pev->origin) - pev->angles.y; + + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + + if (yaw > 0) + pev->sequence = m_baseSequence + SITTING_ANIM_sitlookleft; + else + pev->sequence = m_baseSequence + SITTING_ANIM_sitlookright; + + //ALERT(at_console, "sitting speak\n"); + } + } + else if (i < 60) + { + pev->sequence = m_baseSequence + SITTING_ANIM_sitting3; + m_headTurn = RANDOM_LONG(0,8) * 10 - 40; + if (RANDOM_LONG(0,99) < 5) + { + //ALERT(at_console, "sitting speak2\n"); + FIdleSpeak(); + } + } + else if (i < 80) + { + pev->sequence = m_baseSequence + SITTING_ANIM_sitting2; + } + else if (i < 100) + { + pev->sequence = m_baseSequence + SITTING_ANIM_sitscared; + } + + ResetSequenceInfo( ); + pev->frame = 0; + SetBoneController( 0, m_headTurn ); + } + SetNextThink( 0.1 ); +} + +// prepare sitting scientist to answer a question +void CSittingScientist :: SetAnswerQuestion( CTalkMonster *pSpeaker ) +{ + m_flResponseDelay = gpGlobals->time + RANDOM_FLOAT(3, 4); + m_hTalkTarget = (CBaseMonster *)pSpeaker; +} + + +//========================================================= +// FIdleSpeak +// ask question of nearby friend, or make statement +//========================================================= +int CSittingScientist :: FIdleSpeak ( void ) +{ + // try to start a conversation, or make statement + int pitch; + + if (!FOkToSpeak()) + return FALSE; + + // set global min delay for next conversation + CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(4.8, 5.2); + + pitch = GetVoicePitch(); + + // if there is a friend nearby to speak to, play sentence, set friend's response time, return + + // try to talk to any standing or sitting scientists nearby + CBaseEntity *pentFriend = FindNearestFriend(FALSE); + + if (pentFriend && RANDOM_LONG(0,1)) + { + CTalkMonster *pTalkMonster = GetClassPtr((CTalkMonster *)pentFriend->pev); + pTalkMonster->SetAnswerQuestion( this ); + + IdleHeadTurn(pentFriend->pev->origin); + SENTENCEG_PlayRndSz( ENT(pev), m_szGrp[TLK_PQUESTION], 1.0, ATTN_IDLE, 0, pitch ); + // set global min delay for next conversation + CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(4.8, 5.2); + return TRUE; + } + + // otherwise, play an idle statement + if (RANDOM_LONG(0,1)) + { + SENTENCEG_PlayRndSz( ENT(pev), m_szGrp[TLK_PIDLE], 1.0, ATTN_IDLE, 0, pitch ); + // set global min delay for next conversation + CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(4.8, 5.2); + return TRUE; + } + + // never spoke + CTalkMonster::g_talkWaitTime = 0; + return FALSE; +} diff --git a/dlls/scripted.cpp b/dlls/scripted.cpp new file mode 100644 index 0000000..a4ae0c3 --- /dev/null +++ b/dlls/scripted.cpp @@ -0,0 +1,1277 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +/* + + +===== scripted.cpp ======================================================== + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "player.h" + +#ifndef ANIMATION_H +#include "animation.h" +#endif + +#ifndef SAVERESTORE_H +#include "saverestore.h" +#endif + +#include "schedule.h" +#include "scripted.h" +#include "defaultai.h" +#include "movewith.h" + + + +/* +classname "scripted_sequence" +targetname "me" - there can be more than one with the same name, and they act in concert +target "the_entity_I_want_to_start_playing" or "class entity_classname" will pick the closest inactive scientist +play "name_of_sequence" +idle "name of idle sequence to play before starting" +donetrigger "whatever" - can be any other triggerable entity such as another sequence, train, door, or a special case like "die" or "remove" +moveto - if set the monster first moves to this nodes position +range # - only search this far to find the target +spawnflags - (stop if blocked, stop if player seen) +*/ + + +// +// Cache user-entity-field values until spawn is called. +// + +void CCineMonster :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iszIdle")) + { + m_iszIdle = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszPlay")) + { + m_iszPlay = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszEntity")) + { + m_iszEntity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszAttack")) + { + m_iszAttack = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszMoveTarget")) + { + m_iszMoveTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszFireOnBegin")) + { + m_iszFireOnBegin = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fMoveTo")) + { + m_fMoveTo = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fTurnType")) + { + m_fTurnType = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fAction")) + { + m_fAction = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } +// LRC else if (FStrEq(pkvd->szKeyName, "m_flRepeat")) +// { +// m_flRepeat = atof( pkvd->szValue ); +// pkvd->fHandled = TRUE; +// } + else if (FStrEq(pkvd->szKeyName, "m_flRadius")) + { + m_flRadius = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iRepeats")) + { + m_iRepeats = atoi( pkvd->szValue ); + m_iRepeatsLeft = m_iRepeats; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fRepeatFrame")) + { + m_fRepeatFrame = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iFinishSchedule")) + { + m_iFinishSchedule = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iPriority")) + { + m_iPriority = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + { + CBaseMonster::KeyValue( pkvd ); + } +} + +TYPEDESCRIPTION CCineMonster::m_SaveData[] = +{ + DEFINE_FIELD( CCineMonster, m_iState, FIELD_INTEGER ), //LRC + DEFINE_FIELD( CCineMonster, m_iszIdle, FIELD_STRING ), + DEFINE_FIELD( CCineMonster, m_iszPlay, FIELD_STRING ), + DEFINE_FIELD( CCineMonster, m_iszEntity, FIELD_STRING ), + DEFINE_FIELD( CCineMonster, m_iszAttack, FIELD_STRING ), //LRC + DEFINE_FIELD( CCineMonster, m_iszMoveTarget, FIELD_STRING ), //LRC + DEFINE_FIELD( CCineMonster, m_iszFireOnBegin, FIELD_STRING ), + DEFINE_FIELD( CCineMonster, m_fMoveTo, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_fTurnType, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_fAction, FIELD_INTEGER ), +//LRC- this is unused DEFINE_FIELD( CCineMonster, m_flRepeat, FIELD_FLOAT ), + DEFINE_FIELD( CCineMonster, m_flRadius, FIELD_FLOAT ), + + DEFINE_FIELD( CCineMonster, m_iDelay, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_startTime, FIELD_TIME ), + + DEFINE_FIELD( CCineMonster, m_saved_movetype, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_saved_solid, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_saved_effects, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_iFinishSchedule, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_interruptable, FIELD_BOOLEAN ), + + //LRC + DEFINE_FIELD( CCineMonster, m_iRepeats, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_iRepeatsLeft, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_fRepeatFrame, FIELD_FLOAT ), + DEFINE_FIELD( CCineMonster, m_iPriority, FIELD_INTEGER ), +}; + + +IMPLEMENT_SAVERESTORE( CCineMonster, CBaseMonster ); + +LINK_ENTITY_TO_CLASS( scripted_sequence, CCineMonster ); +LINK_ENTITY_TO_CLASS( scripted_action, CCineMonster ); //LRC + +LINK_ENTITY_TO_CLASS( aiscripted_sequence, CCineMonster ); //LRC - aiscripted sequences don't need to be seperate + + +void CCineMonster :: Spawn( void ) +{ + // pev->solid = SOLID_TRIGGER; + // UTIL_SetSize(pev, Vector(-8, -8, -8), Vector(8, 8, 8)); + pev->solid = SOLID_NOT; + + m_iState = STATE_OFF; //LRC + + if ( FStringNull(m_iszIdle) && FStringNull(pev->targetname) ) // if no targetname, start now + { + SetThink(&CCineMonster :: CineThink ); + SetNextThink( 1.0 ); + } + else if ( m_iszIdle ) + { + SetThink(&CCineMonster :: InitIdleThink ); + SetNextThink( 1.0 ); + } + if ( pev->spawnflags & SF_SCRIPT_NOINTERRUPT ) + m_interruptable = FALSE; + else + m_interruptable = TRUE; + + //LRC - the only difference between AI and normal sequences + if ( FClassnameIs(pev, "aiscripted_sequence") || pev->spawnflags & SF_SCRIPT_OVERRIDESTATE ) + { + m_iPriority |= SS_INTERRUPT_ANYSTATE; + } +} + +// +// CineStart +// +void CCineMonster :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // do I already know who I should use + CBaseEntity *pEntity = m_hTargetEnt; + CBaseMonster *pTarget = NULL; + + if ( pEntity ) + pTarget = pEntity->MyMonsterPointer(); + + if ( pTarget ) + { +// ALERT(at_console, "Sequence \"%s\" triggered, already has a target.\n", STRING(pev->targetname)); + // am I already playing the script? + if ( pTarget->m_scriptState == SCRIPT_PLAYING ) + return; + + m_startTime = gpGlobals->time + 0.05; //why the delay? -- LRC + } + else + { +// ALERT(at_console, "Sequence \"%s\" triggered, can't find target; searching\n", STRING(pev->targetname)); + m_hActivator = pActivator; + // if not, try finding them + SetThink(&CCineMonster :: CineThink ); +// SetNextThink( 0 ); + CineThink(); //LRC + } +} + + +// This doesn't really make sense since only MOVETYPE_PUSH get 'Blocked' events +void CCineMonster :: Blocked( CBaseEntity *pOther ) +{ + +} + +void CCineMonster :: Touch( CBaseEntity *pOther ) +{ +/* + ALERT( at_aiconsole, "Cine Touch\n" ); + if (m_pentTarget && OFFSET(pOther->pev) == OFFSET(m_pentTarget)) + { + CBaseMonster *pTarget = GetClassPtr((CBaseMonster *)VARS(m_pentTarget)); + pTarget->m_monsterState == MONSTERSTATE_SCRIPT; + } +*/ +} + + +/* + entvars_t *pevOther = VARS( gpGlobals->other ); + + if ( !FBitSet ( pevOther->flags , FL_MONSTER ) ) + {// touched by a non-monster. + return; + } + + pevOther->origin.z += 1; + + if ( FBitSet ( pevOther->flags, FL_ONGROUND ) ) + {// clear the onground so physics don't bitch + pevOther->flags -= FL_ONGROUND; + } + + // toss the monster! + pevOther->velocity = pev->movedir * pev->speed; + pevOther->velocity.z += m_flHeight; + + + pev->solid = SOLID_NOT;// kill the trigger for now !!!UNDONE +} +*/ + + +// +// ********** Cinematic DIE ********** +// +void CCineMonster :: Die( void ) +{ + SetThink(&CCineMonster :: SUB_Remove ); +} + +// +// ********** Cinematic PAIN ********** +// +void CCineMonster :: Pain( void ) +{ + +} + +// +// ********** Cinematic Think ********** +// + +//LRC: now redefined... find a viable entity with the given name, and return it (or NULL if not found). +CBaseMonster* CCineMonster :: FindEntity( const char* sName, CBaseEntity *pActivator ) +{ + CBaseEntity *pEntity; + + pEntity = UTIL_FindEntityByTargetname(NULL, sName, pActivator); + //m_hTargetEnt = NULL; + CBaseMonster *pMonster = NULL; + + while (pEntity) + { + if ( FBitSet( pEntity->pev->flags, FL_MONSTER )) + { + pMonster = pEntity->MyMonsterPointer( ); + if ( pMonster && pMonster->CanPlaySequence( m_iPriority | SS_INTERRUPT_ALERT ) ) + { + return pMonster; + } + ALERT( at_aiconsole, "Found %s, but can't play!\n", sName ); + } + pEntity = UTIL_FindEntityByTargetname(pEntity, sName, pActivator); + pMonster = NULL; + } + + // couldn't find something with the given targetname; assume it's a classname instead. + if ( !pMonster ) + { + pEntity = NULL; + while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, m_flRadius )) != NULL) + { + if (FClassnameIs( pEntity->pev, sName)) + { + if ( FBitSet( pEntity->pev->flags, FL_MONSTER )) + { + pMonster = pEntity->MyMonsterPointer( ); + if ( pMonster && pMonster->CanPlaySequence( m_iPriority ) ) + { + return pMonster; + } + } + } + } + } + return NULL; +} + +// make the entity enter a scripted sequence +void CCineMonster :: PossessEntity( void ) +{ + CBaseEntity *pEntity = m_hTargetEnt; + CBaseMonster *pTarget = NULL; + if ( pEntity ) + pTarget = pEntity->MyMonsterPointer(); + +// ALERT( at_console, "Possess: pEntity %s, pTarget %s\n", STRING(pEntity->pev->targetname), STRING(pTarget->pev->targetname)); + + if ( pTarget ) + { + if (pTarget->m_pCine) + { + pTarget->m_pCine->CancelScript(); + } + + pTarget->m_pCine = this; + if (m_iszAttack) + { + // anything with that name? + pTarget->m_hTargetEnt = UTIL_FindEntityByTargetname(NULL, STRING(m_iszAttack), m_hActivator); + if ( pTarget->m_hTargetEnt == NULL ) + { // nothing. Anything with that classname? + while ((pTarget->m_hTargetEnt = UTIL_FindEntityInSphere( pTarget->m_hTargetEnt, pev->origin, m_flRadius )) != NULL) + { + if (FClassnameIs( pTarget->m_hTargetEnt->pev, STRING(m_iszAttack))) break; + } + } + if (pTarget->m_hTargetEnt == NULL) + { // nothing. Oh well. + ALERT(at_debug,"%s %s has a missing \"turn target\": %s\n",STRING(pev->classname),STRING(pev->targetname),STRING(m_iszAttack)); + pTarget->m_hTargetEnt = this; + } + } + else + { + pTarget->m_hTargetEnt = this; + } + + if (m_iszMoveTarget) + { + // anything with that name? + pTarget->m_pGoalEnt = UTIL_FindEntityByTargetname(NULL, STRING(m_iszMoveTarget), m_hActivator); + if (pTarget->m_pGoalEnt == NULL) + { // nothing. Oh well. + ALERT(at_debug,"%s %s has a missing \"move target\": %s\n",STRING(pev->classname),STRING(pev->targetname),STRING(m_iszMoveTarget)); + pTarget->m_pGoalEnt = this; + } + } + else + { + pTarget->m_pGoalEnt = this; + } +// if (IsAction()) +// pTarget->PushEnemy(this,pev->origin); + + m_saved_movetype = pTarget->pev->movetype; + m_saved_solid = pTarget->pev->solid; + m_saved_effects = pTarget->pev->effects; + pTarget->pev->effects |= pev->effects; + +// ALERT(at_console, "script. IsAction = %d",IsAction()); + + m_iState = STATE_ON; // LRC: assume we'll set it to 'on', unless proven otherwise... + switch (m_fMoveTo) + { + case 1: + case 2: + DelayStart( 1 ); + m_iState = STATE_TURN_ON; + // fall through... + case 0: + case 4: + case 5: + case 6: + pTarget->m_scriptState = SCRIPT_WAIT; + break; + } +// ALERT( at_aiconsole, "\"%s\" found and used (INT: %s)\n", STRING( pTarget->pev->targetname ), FBitSet(pev->spawnflags, SF_SCRIPT_NOINTERRUPT)?"No":"Yes" ); + + pTarget->m_IdealMonsterState = MONSTERSTATE_SCRIPT; +// if (m_iszIdle) +// { +// ALERT(at_console, "Possess: Play idle sequence\n"); +// StartSequence( pTarget, m_iszIdle, FALSE ); +// if (FStrEq( STRING(m_iszIdle), STRING(m_iszPlay))) +// { +// pTarget->pev->framerate = 0; +// } +// } +// ALERT(at_console, "Finished PossessEntity, ms %d, ims %d\n", pTarget->m_MonsterState, pTarget->m_IdealMonsterState); + } + +} + +// at the beginning of the level, set up the idle animation. --LRC +void CCineMonster :: InitIdleThink( void ) +{ + if ((m_hTargetEnt = FindEntity(STRING(m_iszEntity), NULL)) != NULL) + { + PossessEntity( ); + m_startTime = gpGlobals->time + 1E6; + ALERT( at_aiconsole, "script \"%s\" using monster \"%s\"\n", STRING( pev->targetname ), STRING( m_iszEntity ) ); + } + else + { + CancelScript( ); + ALERT( at_aiconsole, "script \"%s\" can't find monster \"%s\"\n", STRING( pev->targetname ), STRING( m_iszEntity ) ); + SetNextThink( 1.0 ); + } +} + +void CCineMonster :: CineThink( void ) +{ +// ALERT(at_console, "Sequence think, activator %s\n", STRING(m_hActivator->pev->targetname)); + if ((m_hTargetEnt = FindEntity(STRING(m_iszEntity),m_hActivator)) != NULL) + { +// ALERT(at_console, "Sequence found %s \"%s\"\n", STRING(m_hTargetEnt->pev->classname), STRING(m_hTargetEnt->pev->targetname)); + PossessEntity( ); + ALERT( at_aiconsole, "script \"%s\" using monster \"%s\"\n", STRING( pev->targetname ), STRING( m_iszEntity ) ); + } + else + { +// ALERT(at_console, "Sequence found nothing called %s\n", STRING(m_iszEntity)); + CancelScript( ); + ALERT( at_aiconsole, "script \"%s\" can't find monster \"%s\"\n", STRING( pev->targetname ), STRING( m_iszEntity ) ); + SetNextThink( 1.0 ); + } +} + + +// lookup a sequence name and setup the target monster to play it +BOOL CCineMonster :: StartSequence( CBaseMonster *pTarget, int iszSeq, BOOL completeOnEmpty ) +{ +// ALERT( at_console, "StartSequence %s \"%s\"\n", STRING(pev->classname), STRING(pev->targetname)); + + if ( !iszSeq && completeOnEmpty ) + { + SequenceDone( pTarget ); + return FALSE; + } + + pTarget->pev->sequence = pTarget->LookupSequence( STRING( iszSeq ) ); + if (pTarget->pev->sequence == -1) + { + ALERT( at_error, "%s: unknown scripted sequence \"%s\"\n", STRING( pTarget->pev->targetname ), STRING( iszSeq) ); + pTarget->pev->sequence = 0; + // return FALSE; + } + +#if 0 + char *s; + if ( pev->spawnflags & SF_SCRIPT_NOINTERRUPT ) + s = "No"; + else + s = "Yes"; + + ALERT( at_debug, "%s (%s): started \"%s\":INT:%s\n", STRING( pTarget->pev->targetname ), STRING( pTarget->pev->classname ), STRING( iszSeq), s ); +#endif + + pTarget->pev->frame = 0; + pTarget->ResetSequenceInfo( ); + return TRUE; +} + +//========================================================= +// SequenceDone - called when a scripted sequence animation +// sequence is done playing ( or when an AI Scripted Sequence +// doesn't supply an animation sequence to play ). Expects +// the CBaseMonster pointer to the monster that the sequence +// possesses. +//========================================================= +void CCineMonster :: SequenceDone ( CBaseMonster *pMonster ) +{ + m_iRepeatsLeft = m_iRepeats; //LRC - reset the repeater count + m_iState = STATE_OFF; // we've finished. +// ALERT( at_console, "Sequence %s finished\n", STRING(pev->targetname));//STRING( m_pCine->m_iszPlay ) ); + + if ( !( pev->spawnflags & SF_SCRIPT_REPEATABLE ) ) + { + SetThink(&CCineMonster :: SUB_Remove ); + SetNextThink( 0.1 ); + } + + // This is done so that another sequence can take over the monster when triggered by the first + + pMonster->CineCleanup(); + + FixScriptMonsterSchedule( pMonster ); + + // This may cause a sequence to attempt to grab this guy NOW, so we have to clear him out + // of the existing sequence + SUB_UseTargets( NULL, USE_TOGGLE, 0 ); +} + +//========================================================= +// When a monster finishes a scripted sequence, we have to +// fix up its state and schedule for it to return to a +// normal AI monster. +// +// Scripted sequences just dirty the Schedule and drop the +// monster in Idle State. +// +// or select a specific AMBUSH schedule, regardless of state. //LRC +//========================================================= +void CCineMonster :: FixScriptMonsterSchedule( CBaseMonster *pMonster ) +{ + if ( pMonster->m_IdealMonsterState != MONSTERSTATE_DEAD ) + pMonster->m_IdealMonsterState = MONSTERSTATE_IDLE; +// pMonster->ClearSchedule(); + + switch ( m_iFinishSchedule ) + { + case SCRIPT_FINISHSCHED_DEFAULT: + pMonster->ClearSchedule(); + break; + case SCRIPT_FINISHSCHED_AMBUSH: + pMonster->ChangeSchedule( pMonster->GetScheduleOfType( SCHED_AMBUSH ) ); + break; + default: + ALERT ( at_aiconsole, "FixScriptMonsterSchedule - no case!\n" ); + pMonster->ClearSchedule(); + break; + } +} + +BOOL CBaseMonster :: ExitScriptedSequence( ) +{ + if ( pev->deadflag == DEAD_DYING ) + { + // is this legal? + // BUGBUG -- This doesn't call Killed() + m_IdealMonsterState = MONSTERSTATE_DEAD; + return FALSE; + } + + // buz test +// ALERT(at_console, "in exit sequence, x is %f, y: %f, z: %f\n", pev->origin.x, pev->origin.y, pev->origin.z); + + if (m_pCine) + { + m_pCine->CancelScript( ); + } + + return TRUE; +} + + +void CCineMonster::AllowInterrupt( BOOL fAllow ) +{ + if ( pev->spawnflags & SF_SCRIPT_NOINTERRUPT ) + return; + m_interruptable = fAllow; +} + + +BOOL CCineMonster::CanInterrupt( void ) +{ + if ( !m_interruptable ) + return FALSE; + + CBaseEntity *pTarget = m_hTargetEnt; + + if ( pTarget != NULL && pTarget->pev->deadflag == DEAD_NO ) + return TRUE; + + return FALSE; +} + + +int CCineMonster::IgnoreConditions( void ) +{ + if ( CanInterrupt() ) + return 0; + + // Big fat BUG: This is an IgnoreConditions function - we need to return the conditions + // that _shouldn't_ be able to break the script, instead of the conditions that _should_!! + return SCRIPT_BREAK_CONDITIONS; +} + + +void ScriptEntityCancel( edict_t *pentCine ) +{ + // make sure they are a scripted_sequence + if (FClassnameIs( pentCine, "scripted_sequence" ) || FClassnameIs( pentCine, "scripted_action" )) + { + GetClassPtr((CCineMonster *)VARS(pentCine))->m_iState = STATE_OFF; // buz: fixed evil Laurie's bug!!!! added GetClassPtr :)) + CCineMonster *pCineTarget = GetClassPtr((CCineMonster *)VARS(pentCine)); + // make sure they have a monster in mind for the script + CBaseEntity *pEntity = pCineTarget->m_hTargetEnt; + CBaseMonster *pTarget = NULL; + if ( pEntity ) + pTarget = pEntity->MyMonsterPointer(); + + if (pTarget) + { + // ALERT(at_console, "in scriptentitycancel, x is %f, y: %f, z: %f\n", pTarget->pev->origin.x, pTarget->pev->origin.y, pTarget->pev->origin.z); + + // make sure their monster is actually playing a script + if ( pTarget->m_MonsterState == MONSTERSTATE_SCRIPT ) + { + // tell them do die + pTarget->m_scriptState = CCineMonster::SCRIPT_CLEANUP; + // do it now + pTarget->CineCleanup( ); + //LRC - clean up so that if another script is starting immediately, the monster will notice it. + pTarget->ClearSchedule( ); + } + } + } +} + + +// find all the cinematic entities with my targetname and stop them from playing +void CCineMonster :: CancelScript( void ) +{ +// ALERT( at_aiconsole, "Cancelling script: %s\n", STRING(m_iszPlay) ); +// ALERT(at_console, "in cancelling script, x is %f, y: %f, z: %f\n", m_hTargetEnt->pev->origin.x, m_hTargetEnt->pev->origin.y, m_hTargetEnt->pev->origin.z); + + if ( !pev->targetname ) + { + ScriptEntityCancel( edict() ); + return; + } + + CBaseEntity *pCineTarget = UTIL_FindEntityByTargetname(NULL, STRING(pev->targetname)); + + while (pCineTarget) + { + ScriptEntityCancel( ENT(pCineTarget->pev) ); + pCineTarget = UTIL_FindEntityByTargetname(pCineTarget, STRING(pev->targetname)); + } +} + + +// find all the cinematic entities with my targetname and tell them whether to wait before starting +void CCineMonster :: DelayStart( int state ) +{ + CBaseEntity *pCine = UTIL_FindEntityByTargetname(NULL, STRING(pev->targetname)); + + while ( pCine ) + { + if (FClassnameIs( pCine->pev, "scripted_sequence" ) || FClassnameIs( pCine->pev, "scripted_action" )) + { + CCineMonster *pTarget = GetClassPtr((CCineMonster *)(pCine->pev)); + if (state) + { +// ALERT(at_console, "Delaying start\n"); + pTarget->m_iDelay++; + } + else + { +// ALERT(at_console, "Undelaying start\n"); + pTarget->m_iDelay--; + if (pTarget->m_iDelay <= 0) + { + pTarget->m_iState = STATE_ON; //LRC + FireTargets(STRING(m_iszFireOnBegin), this, this, USE_TOGGLE, 0); //LRC + pTarget->m_startTime = gpGlobals->time + 0.05; // why the delay? -- LRC + } + } + } + pCine = UTIL_FindEntityByTargetname(pCine, STRING(pev->targetname)); + } +} + + + +// Find an entity that I'm interested in and precache the sounds he'll need in the sequence. +void CCineMonster :: Activate( void ) +{ + CBaseEntity *pEntity; + CBaseMonster *pTarget; + + // The entity name could be a target name or a classname + // Check the targetname + pEntity = UTIL_FindEntityByTargetname(NULL, STRING(m_iszEntity)); + pTarget = NULL; + + while (!pTarget && pEntity) + { + if ( FBitSet( pEntity->pev->flags, FL_MONSTER )) + { + pTarget = pEntity->MyMonsterPointer( ); + } + pEntity = UTIL_FindEntityByTargetname(pEntity, STRING(m_iszEntity)); + } + + // If no entity with that targetname, check the classname + if ( !pTarget ) + { + pEntity = UTIL_FindEntityByClassname(NULL, STRING(m_iszEntity)); + while (!pTarget && pEntity) + { + pTarget = pEntity->MyMonsterPointer( ); + pEntity = UTIL_FindEntityByClassname(pEntity, STRING(m_iszEntity)); + } + } + // Found a compatible entity + if ( pTarget ) + { + void *pmodel; + pmodel = GET_MODEL_PTR( pTarget->edict() ); + if ( pmodel ) + { + // Look through the event list for stuff to precache + SequencePrecache( pmodel, STRING( m_iszIdle ) ); + SequencePrecache( pmodel, STRING( m_iszPlay ) ); + } + } + + CBaseMonster::Activate(); +} + + +BOOL CBaseMonster :: CineCleanup( ) +{ + CCineMonster *pOldCine = m_pCine; + + // am I linked to a cinematic? + if (m_pCine) + { + // okay, reset me to what it thought I was before + m_pCine->m_hTargetEnt = NULL; + + pev->movetype = m_pCine->m_saved_movetype; + +// LRC - why mess around with this? Solidity isn't changed by sequences! +// pev->solid = m_pCine->m_saved_solid; + + if (m_pCine->pev->spawnflags & SF_SCRIPT_STAYDEAD) + pev->deadflag = DEAD_DYING; + } + else + { + // arg, punt + pev->movetype = MOVETYPE_STEP;// this is evil + pev->solid = SOLID_SLIDEBOX; + } + m_pCine = NULL; + m_hTargetEnt = NULL; + m_pGoalEnt = NULL; + if (pev->deadflag == DEAD_DYING) + { + // last frame of death animation? + pev->health = 0; + pev->framerate = 0.0; + pev->solid = SOLID_NOT; + SetState( MONSTERSTATE_DEAD ); + pev->deadflag = DEAD_DEAD; + UTIL_SetSize( pev, pev->mins, Vector(pev->maxs.x, pev->maxs.y, pev->mins.z + 2) ); + + if ( pOldCine && FBitSet( pOldCine->pev->spawnflags, SF_SCRIPT_LEAVECORPSE ) ) + { + SetUse( NULL ); // BUGBUG -- This doesn't call Killed() + SetThink( NULL ); // This will probably break some stuff + SetTouch( NULL ); + } + else + SUB_StartFadeOut(); // SetThink( SUB_DoNothing ); + // This turns off animation & physics in case their origin ends up stuck in the world or something + StopAnimation(); + pev->movetype = MOVETYPE_NONE; + pev->effects |= EF_NOINTERP; // Don't interpolate either, assume the corpse is positioned in its final resting place + return FALSE; + } + + // If we actually played a sequence + if ( pOldCine && pOldCine->m_iszPlay ) + { + if ( !(pOldCine->pev->spawnflags & SF_SCRIPT_NOSCRIPTMOVEMENT) ) + { + // reset position + Vector new_origin, new_angle; + GetBonePosition( 0, new_origin, new_angle ); + + // Figure out how far they have moved + // We can't really solve this problem because we can't query the movement of the origin relative + // to the sequence. We can get the root bone's position as we do here, but there are + // cases where the root bone is in a different relative position to the entity's origin + // before/after the sequence plays. So we are stuck doing this: + + // !!!HACKHACK: Float the origin up and drop to floor because some sequences have + // irregular motion that can't be properly accounted for. + + // UNDONE: THIS SHOULD ONLY HAPPEN IF WE ACTUALLY PLAYED THE SEQUENCE. + Vector oldOrigin = pev->origin; + + // UNDONE: ugly hack. Don't move monster if they don't "seem" to move + // this really needs to be done with the AX,AY,etc. flags, but that aren't consistantly + // being set, so animations that really do move won't be caught. + if ((oldOrigin - new_origin).Length2D() < 8.0) + new_origin = oldOrigin; + + pev->origin.x = new_origin.x; + pev->origin.y = new_origin.y; + pev->origin.z += 1; + + pev->flags |= FL_ONGROUND; + int drop = DROP_TO_FLOOR( ENT(pev) ); + + // Origin in solid? Set to org at the end of the sequence + if ( drop < 0 ) + pev->origin = oldOrigin; + else if ( drop == 0 ) // Hanging in air? + { + pev->origin.z = new_origin.z; + pev->flags &= ~FL_ONGROUND; + } + // else entity hit floor, leave there + + // pEntity->pev->origin.z = new_origin.z + 5.0; // damn, got to fix this + + UTIL_SetOrigin( this, pev->origin ); + pev->effects |= EF_NOINTERP; + } + + // We should have some animation to put these guys in, but for now it's idle. + // Due to NOINTERP above, there won't be any blending between this anim & the sequence + m_Activity = ACT_RESET; + } + // set them back into a normal state + pev->enemy = NULL; + if ( pev->health > 0 ) + m_IdealMonsterState = MONSTERSTATE_IDLE; // m_previousState; + else + { + // Dropping out because he got killed + // Can't call killed() no attacker and weirdness (late gibbing) may result + m_IdealMonsterState = MONSTERSTATE_DEAD; + SetConditions( bits_COND_LIGHT_DAMAGE ); + pev->deadflag = DEAD_DYING; + FCheckAITrigger(); + pev->deadflag = DEAD_NO; + } + + + // SetAnimation( m_MonsterState ); + //LRC- removed, was never implemented. ClearBits(pev->spawnflags, SF_MONSTER_WAIT_FOR_SCRIPT ); + + return TRUE; +} + + + + +class CScriptedSentence : public CBaseToggle +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT FindThink( void ); + void EXPORT DelayThink( void ); + void EXPORT DurationThink( void ); + int ObjectCaps( void ) { return (CBaseToggle :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + + STATE GetState() { return m_playing?STATE_ON:STATE_OFF; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + CBaseMonster *FindEntity( CBaseEntity *pActivator ); + BOOL AcceptableSpeaker( CBaseMonster *pMonster ); + BOOL StartSentence( CBaseMonster *pTarget ); + + +private: + int m_iszSentence; // string index for idle animation + int m_iszEntity; // entity that is wanted for this sentence + float m_flRadius; // range to search + float m_flDuration; // How long the sentence lasts + float m_flRepeat; // maximum repeat rate + float m_flAttenuation; + float m_flVolume; + BOOL m_active; // is the sentence enabled? (for m_flRepeat) + BOOL m_playing; //LRC- is the sentence playing? (for GetState) + int m_iszListener; // name of entity to look at while talking +}; + +#define SF_SENTENCE_ONCE 0x0001 +#define SF_SENTENCE_FOLLOWERS 0x0002 // only say if following player +#define SF_SENTENCE_INTERRUPT 0x0004 // force talking except when dead +#define SF_SENTENCE_CONCURRENT 0x0008 // allow other people to keep talking + +TYPEDESCRIPTION CScriptedSentence::m_SaveData[] = +{ + DEFINE_FIELD( CScriptedSentence, m_iszSentence, FIELD_STRING ), + DEFINE_FIELD( CScriptedSentence, m_iszEntity, FIELD_STRING ), + DEFINE_FIELD( CScriptedSentence, m_flRadius, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_flDuration, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_flRepeat, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_flAttenuation, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_flVolume, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_active, FIELD_BOOLEAN ), + DEFINE_FIELD( CScriptedSentence, m_playing, FIELD_BOOLEAN ), + DEFINE_FIELD( CScriptedSentence, m_iszListener, FIELD_STRING ), +}; + + +IMPLEMENT_SAVERESTORE( CScriptedSentence, CBaseToggle ); + +LINK_ENTITY_TO_CLASS( scripted_sentence, CScriptedSentence ); + +void CScriptedSentence :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "sentence")) + { + m_iszSentence = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "entity")) + { + m_iszEntity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "duration")) + { + m_flDuration = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "radius")) + { + m_flRadius = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "refire")) + { + m_flRepeat = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if(FStrEq(pkvd->szKeyName, "attenuation")) + { + pev->impulse = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if(FStrEq(pkvd->szKeyName, "volume")) + { + m_flVolume = atof( pkvd->szValue ) * 0.1; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "listener")) + { + m_iszListener = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + + +void CScriptedSentence :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !m_active ) + return; +// ALERT( at_console, "Firing sentence: %s\n", STRING(m_iszSentence) ); + m_hActivator = pActivator; + SetThink(&CScriptedSentence :: FindThink ); + SetNextThink( 0 ); +} + + +void CScriptedSentence :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + + m_active = TRUE; + m_playing = FALSE; //LRC + // if no targetname, start now + if ( !pev->targetname ) + { + SetThink(&CScriptedSentence :: FindThink ); + SetNextThink( 1.0 ); + } + + switch( pev->impulse ) + { + case 1: // Medium radius + m_flAttenuation = ATTN_STATIC; + break; + + case 2: // Large radius + m_flAttenuation = ATTN_NORM; + break; + + case 3: //EVERYWHERE + m_flAttenuation = ATTN_NONE; + break; + + default: + case 0: // Small radius + m_flAttenuation = ATTN_IDLE; + break; + } + pev->impulse = 0; + + // No volume, use normal + if ( m_flVolume <= 0 ) + m_flVolume = 1.0; +} + + +void CScriptedSentence :: FindThink( void ) +{ + if (!m_iszEntity) //LRC- no target monster given: speak through HEV + { + CBasePlayer* pPlayer = (CBasePlayer*)UTIL_FindEntityByClassname( NULL, "player" ); + if (pPlayer) + { + m_playing = TRUE; + if ((STRING(m_iszSentence))[0] == '!') + pPlayer->SetSuitUpdate((char*)STRING(m_iszSentence),FALSE,0); + else + pPlayer->SetSuitUpdate((char*)STRING(m_iszSentence),TRUE,0); + if ( pev->spawnflags & SF_SENTENCE_ONCE ) + UTIL_Remove( this ); + SetThink(&CScriptedSentence :: DurationThink ); + SetNextThink( m_flDuration ); + m_active = FALSE; + } + else + ALERT( at_debug, "ScriptedSentence: can't find \"player\" to play HEV sentence!?\n"); + return; + } + + CBaseMonster *pMonster = FindEntity( m_hActivator ); + if ( pMonster ) + { + m_playing = TRUE; + StartSentence( pMonster ); + if ( pev->spawnflags & SF_SENTENCE_ONCE ) + UTIL_Remove( this ); + SetThink(&CScriptedSentence :: DurationThink ); + SetNextThink( m_flDuration ); + m_active = FALSE; +// ALERT( at_console, "%s: found monster %s\n", STRING(m_iszSentence), STRING(m_iszEntity) ); + } + else + { +// ALERT( at_console, "%s: can't find monster %s\n", STRING(m_iszSentence), STRING(m_iszEntity) ); + SetNextThink( m_flRepeat + 0.5 ); + } +} + +//LRC +void CScriptedSentence :: DurationThink( void ) +{ + m_playing = FALSE; + SetNextThink( m_flRepeat ); + SetThink(&CScriptedSentence :: DelayThink ); +} + +void CScriptedSentence :: DelayThink( void ) +{ + m_active = TRUE; + if ( !pev->targetname ) + SetNextThink( 0.1 ); + SetThink(&CScriptedSentence :: FindThink ); +} + + +BOOL CScriptedSentence :: AcceptableSpeaker( CBaseMonster *pMonster ) +{ + if ( pMonster ) + { + if ( pev->spawnflags & SF_SENTENCE_FOLLOWERS ) + { + if ( pMonster->m_hTargetEnt == NULL || !FClassnameIs(pMonster->m_hTargetEnt->pev, "player") ) + return FALSE; + } + BOOL override; + if ( pev->spawnflags & SF_SENTENCE_INTERRUPT ) + override = TRUE; + else + override = FALSE; + if ( pMonster->CanPlaySentence( override ) ) + return TRUE; + } + return FALSE; +} + + +CBaseMonster *CScriptedSentence :: FindEntity( CBaseEntity *pActivator ) +{ + CBaseEntity *pTarget; + CBaseMonster *pMonster; + + pTarget = UTIL_FindEntityByTargetname(NULL, STRING(m_iszEntity), pActivator); + pMonster = NULL; + + while ( pTarget ) + { + pMonster = pTarget->MyMonsterPointer( ); + if ( pMonster != NULL ) + { + if ( AcceptableSpeaker( pMonster ) ) + return pMonster; +// ALERT( at_console, "%s (%s), not acceptable\n", STRING(pMonster->pev->classname), STRING(pMonster->pev->targetname) ); + } + pTarget = UTIL_FindEntityByTargetname(pTarget, STRING(m_iszEntity), pActivator); + } + + pTarget = NULL; + while ((pTarget = UTIL_FindEntityInSphere( pTarget, pev->origin, m_flRadius )) != NULL) + { + if (FClassnameIs( pTarget->pev, STRING(m_iszEntity))) + { + if ( FBitSet( pTarget->pev->flags, FL_MONSTER )) + { + pMonster = pTarget->MyMonsterPointer( ); + if ( AcceptableSpeaker( pMonster ) ) + return pMonster; + } + } + } + + return NULL; +} + + +BOOL CScriptedSentence :: StartSentence( CBaseMonster *pTarget ) +{ + if ( !pTarget ) + { + ALERT( at_aiconsole, "Not Playing sentence %s\n", STRING(m_iszSentence) ); + return NULL; + } + + BOOL bConcurrent = FALSE; + //LRC: Er... if the "concurrent" flag is NOT set, we make bConcurrent true!? + if ( !(pev->spawnflags & SF_SENTENCE_CONCURRENT) ) + bConcurrent = TRUE; + + CBaseEntity *pListener = NULL; + if (!FStringNull(m_iszListener)) + { + float radius = m_flRadius; + + if ( FStrEq( STRING(m_iszListener ), "player" ) ) + radius = 4096; // Always find the player + + pListener = UTIL_FindEntityGeneric( STRING( m_iszListener ), pTarget->pev->origin, radius ); + } + + pTarget->PlayScriptedSentence( STRING(m_iszSentence), m_flDuration, m_flVolume, m_flAttenuation, bConcurrent, pListener ); + ALERT( at_aiconsole, "Playing sentence %s (%.1f)\n", STRING(m_iszSentence), m_flDuration ); + SUB_UseTargets( NULL, USE_TOGGLE, 0 ); + return TRUE; +} + + + + + +/* + +*/ + + +//========================================================= +// Furniture - this is the cool comment I cut-and-pasted +//========================================================= +class CFurniture : public CBaseMonster +{ +public: + void Spawn ( void ); + void Die( void ); + int Classify ( void ); + virtual int ObjectCaps( void ) { return (CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + float MaxYawSpeed( void ) { return 0.0f; } +}; + + +LINK_ENTITY_TO_CLASS( monster_furniture, CFurniture ); + + +//========================================================= +// Furniture is killed +//========================================================= +void CFurniture :: Die ( void ) +{ + SetThink(&CFurniture :: SUB_Remove ); + SetNextThink( 0 ); +} + +//========================================================= +// This used to have something to do with bees flying, but +// now it only initializes moving furniture in scripted sequences +//========================================================= +void CFurniture :: Spawn( ) +{ + PRECACHE_MODEL((char *)STRING(pev->model)); + SET_MODEL(ENT(pev), STRING(pev->model)); + + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_BBOX; + pev->health = 80000; + pev->takedamage = DAMAGE_AIM; + pev->sequence = 0; + pev->frame = 0; + +// pev->nextthink += 1.0; +// SetThink (WalkMonsterDelay); + + ResetSequenceInfo( ); + pev->frame = 0; + MonsterInit(); +} + +//========================================================= +// ID's Furniture as neutral (noone will attack it) +//========================================================= +int CFurniture::Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_NONE; +} + + diff --git a/dlls/scripted.h b/dlls/scripted.h new file mode 100644 index 0000000..a2245a1 --- /dev/null +++ b/dlls/scripted.h @@ -0,0 +1,128 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef SCRIPTED_H +#define SCRIPTED_H + +#ifndef SCRIPTEVENT_H +#include "scriptevent.h" +#endif + +#define SF_SCRIPT_WAITTILLSEEN 1 +#define SF_SCRIPT_EXITAGITATED 2 +#define SF_SCRIPT_REPEATABLE 4 +#define SF_SCRIPT_LEAVECORPSE 8 +//#define SF_SCRIPT_INTERPOLATE 16 // don't use, old bug +#define SF_SCRIPT_NOINTERRUPT 32 +#define SF_SCRIPT_OVERRIDESTATE 64 +#define SF_SCRIPT_NOSCRIPTMOVEMENT 128 +#define SF_SCRIPT_STAYDEAD 256 // LRC- signifies that the animation kills the monster + // (needed because the monster animations don't use AnimEvent 1000 properly) + +#define SCRIPT_BREAK_CONDITIONS (bits_COND_LIGHT_DAMAGE|bits_COND_HEAVY_DAMAGE) + +//LRC - rearranged into flags +#define SS_INTERRUPT_IDLE 0x0 +#define SS_INTERRUPT_ALERT 0x1 +#define SS_INTERRUPT_ANYSTATE 0x2 +#define SS_INTERRUPT_SCRIPTS 0x4 + +// when a monster finishes an AI scripted sequence, we can choose +// a schedule to place them in. These defines are the aliases to +// resolve worldcraft input to real schedules (sjb) +#define SCRIPT_FINISHSCHED_DEFAULT 0 +#define SCRIPT_FINISHSCHED_AMBUSH 1 + +class CCineMonster : public CBaseMonster +{ +public: + void Spawn( void ); + virtual void KeyValue( KeyValueData *pkvd ); + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual void Blocked( CBaseEntity *pOther ); + virtual void Touch( CBaseEntity *pOther ); + virtual int ObjectCaps( void ) { return (CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + virtual void Activate( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + //LRC: states for script entities + virtual STATE GetState( void ) { return m_iState; }; + STATE m_iState; + + // void EXPORT CineSpawnThink( void ); + void EXPORT CineThink( void ); + void EXPORT InitIdleThink( void ); //LRC + void Pain( void ); + void Die( void ); + void DelayStart( int state ); + CBaseMonster* FindEntity( const char* sName, CBaseEntity *pActivator ); + virtual void PossessEntity( void ); + + inline BOOL IsAction( void ) { return FClassnameIs(pev,"scripted_action"); }; //LRC + + //LRC: Should the monster do a precise attack for this scripted_action? + // (Do a precise attack if we'll be turning to face the target, but we haven't just walked to the target.) + BOOL PreciseAttack( void ) + { + // if (m_fTurnType != 1) { ALERT(at_console,"preciseattack fails check 1\n"); return FALSE; } + // if (m_fMoveTo == 0) { ALERT(at_console,"preciseattack fails check 2\n"); return FALSE; } + // if (m_fMoveTo != 5 && m_iszAttack == 0) { ALERT(at_console,"preciseattack fails check 3\n"); return FALSE; } + // ALERT(at_console,"preciseattack passes!\n"); + // return TRUE; + return m_fTurnType == 1 && ( m_fMoveTo == 5 || (m_fMoveTo != 0 && !FStrEq(STRING(m_iszAttack), STRING(m_iszMoveTarget)) )); + }; + + void ReleaseEntity( CBaseMonster *pEntity ); + void CancelScript( void ); + virtual BOOL StartSequence( CBaseMonster *pTarget, int iszSeq, BOOL completeOnEmpty ); + void SequenceDone ( CBaseMonster *pMonster ); + virtual void FixScriptMonsterSchedule( CBaseMonster *pMonster ); + BOOL CanInterrupt( void ); + void AllowInterrupt( BOOL fAllow ); + int IgnoreConditions( void ); + + int m_iszIdle; // string index for idle animation + int m_iszPlay; // string index for scripted animation + int m_iszEntity; // entity that is wanted for this script + int m_iszAttack; // entity to attack + int m_iszMoveTarget; // entity to move to + int m_iszFireOnBegin; // entity to fire when the sequence _starts_. + int m_fMoveTo; + int m_fTurnType; + int m_fAction; + int m_iFinishSchedule; + float m_flRadius; // range to search +//LRC- this does nothing!! float m_flRepeat; // repeat rate + int m_iRepeats; //LRC - number of times to repeat the animation + int m_iRepeatsLeft; //LRC + float m_fRepeatFrame; //LRC + int m_iPriority; //LRC + + int m_iDelay; + float m_startTime; + + int m_saved_movetype; + int m_saved_solid; + int m_saved_effects; +// Vector m_vecOrigOrigin; + BOOL m_interruptable; +}; + +//LRC - removed CCineAI, obsolete + +#endif //SCRIPTED_H diff --git a/dlls/scriptevent.h b/dlls/scriptevent.h new file mode 100644 index 0000000..42377cf --- /dev/null +++ b/dlls/scriptevent.h @@ -0,0 +1,29 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef SCRIPTEVENT_H +#define SCRIPTEVENT_H + +#define SCRIPT_EVENT_DEAD 1000 // character is now dead +#define SCRIPT_EVENT_NOINTERRUPT 1001 // does not allow interrupt +#define SCRIPT_EVENT_CANINTERRUPT 1002 // will allow interrupt +#define SCRIPT_EVENT_FIREEVENT 1003 // event now fires +#define SCRIPT_EVENT_SOUND 1004 // Play named wave file (on CHAN_BODY) +#define SCRIPT_EVENT_SENTENCE 1005 // Play named sentence +#define SCRIPT_EVENT_INAIR 1006 // Leave the character in air at the end of the sequence (don't find the floor) +#define SCRIPT_EVENT_ENDANIMATION 1007 // Set the animation by name after the sequence completes +#define SCRIPT_EVENT_SOUND_VOICE 1008 // Play named wave file (on CHAN_VOICE) +#define SCRIPT_EVENT_SENTENCE_RND1 1009 // Play sentence group 25% of the time +#define SCRIPT_EVENT_NOT_DEAD 1010 // Bring back to life (for life/death sequences) +#endif //SCRIPTEVENT_H diff --git a/dlls/singleplay_gamerules.cpp b/dlls/singleplay_gamerules.cpp new file mode 100644 index 0000000..e55c63a --- /dev/null +++ b/dlls/singleplay_gamerules.cpp @@ -0,0 +1,398 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// teamplay_gamerules.cpp +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "skill.h" +#include "items.h" + +extern DLL_GLOBAL CGameRules *g_pGameRules; +extern DLL_GLOBAL BOOL g_fGameOver; +extern int gmsgDeathMsg; // client dll messages +extern int gmsgScoreInfo; +extern int gmsgMOTD; + +//========================================================= +//========================================================= +CHalfLifeRules::CHalfLifeRules( void ) +{ + RefreshSkillData(); +} + +//========================================================= +//========================================================= +void CHalfLifeRules::Think ( void ) +{ +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::IsMultiplayer( void ) +{ + return FALSE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::IsDeathmatch ( void ) +{ + return FALSE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::IsCoOp( void ) +{ + return FALSE; +} + + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ + if ( !pPlayer->m_pActiveItem ) + { + // player doesn't have an active item! + return TRUE; + } + + if ( !pPlayer->m_pActiveItem->CanHolster() ) + { + return FALSE; + } + + return TRUE; +} + +//========================================================= +//========================================================= +// buz: ñêîïèðîâàíî èç MultiplayerRules +BOOL CHalfLifeRules :: GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) +{ + CBasePlayerItem *pCheck; + CBasePlayerItem *pBest;// this will be used in the event that we don't find a weapon in the same category. + int iBestWeight; + int i; + + iBestWeight = -1;// no weapon lower than -1 can be autoswitched to + pBest = NULL; + + if ( !pCurrentWeapon->CanHolster() ) + { + // can't put this gun away right now, so can't switch. + return FALSE; + } + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + pCheck = pPlayer->m_rgpPlayerItems[ i ]; + + while ( pCheck ) + { + if ( pCheck->iWeight() > -1 && pCheck->iWeight() == pCurrentWeapon->iWeight() && pCheck != pCurrentWeapon ) + { + // this weapon is from the same category. + if ( pCheck->CanDeploy() ) + { + if ( pPlayer->SwitchWeapon( pCheck ) ) + { + return TRUE; + } + } + } + else if ( pCheck->iWeight() > iBestWeight && pCheck != pCurrentWeapon )// don't reselect the weapon we're trying to get rid of + { + //ALERT ( at_console, "Considering %s\n", STRING( pCheck->pev->classname ) ); + // we keep updating the 'best' weapon just in case we can't find a weapon of the same weight + // that the player was using. This will end up leaving the player with his heaviest-weighted + // weapon. + if ( pCheck->CanDeploy() ) + { + // if this weapon is useable, flag it as the best + iBestWeight = pCheck->iWeight(); + pBest = pCheck; + } + } + + pCheck = pCheck->m_pNext; + } + } + + // if we make it here, we've checked all the weapons and found no useable + // weapon in the same catagory as the current weapon. + + // if pBest is null, we didn't find ANYTHING. Shouldn't be possible- should always + // at least get the crowbar, but ya never know. + if ( !pBest ) + { + return FALSE; + } + + pPlayer->SwitchWeapon( pBest ); + + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules :: ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) +{ + return TRUE; +} + +void CHalfLifeRules :: InitHUD( CBasePlayer *pl ) +{ +} + +//========================================================= +//========================================================= +void CHalfLifeRules :: ClientDisconnected( edict_t *pClient ) +{ +} + +//========================================================= +//========================================================= +float CHalfLifeRules::FlPlayerFallDamage( CBasePlayer *pPlayer ) +{ + // subtract off the speed at which a player is allowed to fall without being hurt, + // so damage will be based on speed beyond that, not the entire fall + pPlayer->m_flFallVelocity -= PLAYER_MAX_SAFE_FALL_SPEED; + return pPlayer->m_flFallVelocity * DAMAGE_FOR_FALL_SPEED; +} + +//========================================================= +//========================================================= +void CHalfLifeRules :: PlayerSpawn( CBasePlayer *pPlayer ) +{ + CBaseEntity *pWeaponEntity = NULL; + +// LRC what's wrong with allowing "game_player_equip" entities in single player? (The +// level designer is God: if he wants the player to start with a weapon, we should allow it!) + while ( pWeaponEntity = UTIL_FindEntityByClassname( pWeaponEntity, "game_player_equip" )) + { + pWeaponEntity->Touch( pPlayer ); + } +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules :: AllowAutoTargetCrosshair( void ) +{ + return ( g_iSkillLevel == SKILL_EASY ); +} + +//========================================================= +//========================================================= +void CHalfLifeRules :: PlayerThink( CBasePlayer *pPlayer ) +{ +} + + +//========================================================= +//========================================================= +BOOL CHalfLifeRules :: FPlayerCanRespawn( CBasePlayer *pPlayer ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +float CHalfLifeRules :: FlPlayerSpawnTime( CBasePlayer *pPlayer ) +{ + return gpGlobals->time;//now! +} + +//========================================================= +// IPointsForKill - how many points awarded to anyone +// that kills this player? +//========================================================= +int CHalfLifeRules :: IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) +{ + return 1; +} + +//========================================================= +// PlayerKilled - someone/something killed this player +//========================================================= +void CHalfLifeRules :: PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) +{ +} + +//========================================================= +// Deathnotice +//========================================================= +void CHalfLifeRules::DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) +{ +} + +//========================================================= +// PlayerGotWeapon - player has grabbed a weapon that was +// sitting in the world +//========================================================= +void CHalfLifeRules :: PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ +} + +//========================================================= +// FlWeaponRespawnTime - what is the time in the future +// at which this weapon may spawn? +//========================================================= +float CHalfLifeRules :: FlWeaponRespawnTime( CBasePlayerItem *pWeapon ) +{ + return -1; +} + +//========================================================= +// FlWeaponRespawnTime - Returns 0 if the weapon can respawn +// now, otherwise it returns the time at which it can try +// to spawn again. +//========================================================= +float CHalfLifeRules :: FlWeaponTryRespawn( CBasePlayerItem *pWeapon ) +{ + return 0; +} + +//========================================================= +// VecWeaponRespawnSpot - where should this weapon spawn? +// Some game variations may choose to randomize spawn locations +//========================================================= +Vector CHalfLifeRules :: VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ) +{ + return pWeapon->pev->origin; +} + +//========================================================= +// WeaponShouldRespawn - any conditions inhibiting the +// respawning of this weapon? +//========================================================= +int CHalfLifeRules :: WeaponShouldRespawn( CBasePlayerItem *pWeapon ) +{ + return GR_WEAPON_RESPAWN_NO; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +void CHalfLifeRules::PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ) +{ +} + +//========================================================= +//========================================================= +int CHalfLifeRules::ItemShouldRespawn( CItem *pItem ) +{ + return GR_ITEM_RESPAWN_NO; +} + + +//========================================================= +// At what time in the future may this Item respawn? +//========================================================= +float CHalfLifeRules::FlItemRespawnTime( CItem *pItem ) +{ + return -1; +} + +//========================================================= +// Where should this item respawn? +// Some game variations may choose to randomize spawn locations +//========================================================= +Vector CHalfLifeRules::VecItemRespawnSpot( CItem *pItem ) +{ + return pItem->pev->origin; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules::IsAllowedToSpawn( CBaseEntity *pEntity ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +void CHalfLifeRules::PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ) +{ +} + +//========================================================= +//========================================================= +int CHalfLifeRules::AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ) +{ + return GR_AMMO_RESPAWN_NO; +} + +//========================================================= +//========================================================= +float CHalfLifeRules::FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ) +{ + return -1; +} + +//========================================================= +//========================================================= +Vector CHalfLifeRules::VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ) +{ + return pAmmo->pev->origin; +} + +//========================================================= +//========================================================= +float CHalfLifeRules::FlHealthChargerRechargeTime( void ) +{ + return 0;// don't recharge +} + +//========================================================= +//========================================================= +int CHalfLifeRules::DeadPlayerWeapons( CBasePlayer *pPlayer ) +{ + return GR_PLR_DROP_GUN_NO; +} + +//========================================================= +//========================================================= +int CHalfLifeRules::DeadPlayerAmmo( CBasePlayer *pPlayer ) +{ + return GR_PLR_DROP_AMMO_NO; +} + +//========================================================= +//========================================================= +int CHalfLifeRules::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) +{ + // why would a single player in half life need this? + return GR_NOTTEAMMATE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeRules :: FAllowMonsters( void ) +{ + return TRUE; +} diff --git a/dlls/skill.cpp b/dlls/skill.cpp new file mode 100644 index 0000000..4fd5f62 --- /dev/null +++ b/dlls/skill.cpp @@ -0,0 +1,48 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// skill.cpp - code for skill level concerns +//========================================================= +#include "extdll.h" +#include "util.h" +#include "skill.h" + + +skilldata_t gSkillData; + + +//========================================================= +// take the name of a cvar, tack a digit for the skill level +// on, and return the value.of that Cvar +//========================================================= +float GetSkillCvar( char *pName ) +{ + int iCount; + float flValue; + char szBuffer[ 64 ]; + + iCount = sprintf( szBuffer, "%s%d",pName, gSkillData.iSkillLevel ); + + flValue = CVAR_GET_FLOAT ( szBuffer ); + +// Wargon: Ýòî ñîîáùåíèå íå íóæíî.  skill.cfg íóëåâûå çíà÷åíèÿ äîïóñòèìû. +// if ( flValue <= 0 ) +// { +// ALERT ( at_debug, "\n\n** GetSkillCVar Got a zero for %s **\n\n", szBuffer ); +// } + + return flValue; +} + diff --git a/dlls/skill.h b/dlls/skill.h new file mode 100644 index 0000000..0ed0a02 --- /dev/null +++ b/dlls/skill.h @@ -0,0 +1,182 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// skill.h - skill level concerns +//========================================================= + +struct skilldata_t +{ + + int iSkillLevel; // game skill level + +// Monster Health & Damage + float agruntHealth; + float agruntDmgPunch; + + float apacheHealth; + + float barneyHealth; + + float bigmommaHealthFactor; // Multiply each node's health by this + float bigmommaDmgSlash; // melee attack damage + float bigmommaDmgBlast; // mortar attack damage + float bigmommaRadiusBlast; // mortar attack radius + + float bullsquidHealth; + float bullsquidDmgBite; + float bullsquidDmgWhip; + float bullsquidDmgSpit; + + float gargantuaHealth; + float gargantuaDmgSlash; + float gargantuaDmgFire; + float gargantuaDmgStomp; + + float hassassinHealth; + + float headcrabHealth; + float headcrabDmgBite; + + float hgruntHealth; + float hgruntDmgKick; + float hgruntShotgunPellets; + float hgruntGrenadeSpeed; + + float houndeyeHealth; + float houndeyeDmgBlast; + + float slaveHealth; + float slaveDmgClaw; + float slaveDmgClawrake; + float slaveDmgZap; + + float ichthyosaurHealth; + float ichthyosaurDmgShake; + + float leechHealth; + float leechDmgBite; + + float controllerHealth; + float controllerDmgZap; + float controllerSpeedBall; + float controllerDmgBall; + + float nihilanthHealth; + float nihilanthZap; + + float scientistHealth; + + float snarkHealth; + float snarkDmgBite; + float snarkDmgPop; + + float zombieHealth; + float zombieDmgOneSlash; + float zombieDmgBothSlash; + + float turretHealth; + float miniturretHealth; + float sentryHealth; + + +// Player Weapons + float plrDmgCrowbar; + float plrDmgCrowbarSec; // buz + float plrDmg9MM; + float plrDmgMP5; // used in paranoia + float plrDmgM203Grenade; + float plrDmgBuckshot; // used in paranoia + float plrDmgRPG; + float plrDmgHandGrenade; + // buz: paranoia weapons + float plrDmgAps; + float plrDmgBarret; + float plrDmgAks; + float plrDmgAk47; + float plrDmgAsval; + float plrDmgRpk; + float plrDmgGroza; + +// weapons shared by monsters + float monDmg9MM; + float monDmgMP5; + float monDmg12MM; + float monDmgHornet; + // buz: paranoia monsters: + float monDmgAK; + float monDmgAsval; + float monDmgGroza; + float monTerDmgAK; + float monTerDmgRPK; + float monDmgGlock; + + float MilDmgKick; + float milHealth; + float alphaHealth; + float terrorHealth; + float cloneHealth; + float cloneHealthHeavy; + +// health/suit charge + float suitchargerCapacity; + float batteryCapacity; + float healthchargerCapacity; + float healthkitCapacity; + float bighealthkitCapacity; // buz + float painkillerCapacity; // buz + float scientistHeal; + +// monster damage adj + float monHead; + float monChest; + float monStomach; + float monLeg; + float monArm; + +// player damage adj + float plrHead; + float plrChest; + float plrStomach; + float plrLeg; + float plrArm; + +// Wargon: Ìíîæèòåëè äàìàãè äëÿ monster_zombie. (1.1) + float zomHead; + float zomChest; + float zomStomach; + float zomLeg; + float zomArm; + + float superofficerHealth; + float superofficerDmgKick; + float superofficerDmgPunch; + float superofficerDmgBlood; + +// buz: maxspeeds + float plrPrimaryMaxSpeed; // in primary weapon mode + float plrSecondaryMaxSpeed; // in secondary weapon mode + +// Wargon: Ïàòðîíû èç ìåðòâûõ âðàæèí. (1.1) + float maxDeadEnemyAmmo; +}; + +extern DLL_GLOBAL skilldata_t gSkillData; +float GetSkillCvar( char *pName ); + +extern DLL_GLOBAL int g_iSkillLevel; + +#define SKILL_EASY 1 +#define SKILL_MEDIUM 2 +#define SKILL_HARD 3 diff --git a/dlls/sound.cpp b/dlls/sound.cpp new file mode 100644 index 0000000..120ad00 --- /dev/null +++ b/dlls/sound.cpp @@ -0,0 +1,2084 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// sound.cpp +//========================================================= + +#include + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "player.h" +#include "talkmonster.h" +#include "gamerules.h" +#include "material.h" + +static char *memfgets( byte *pMemFile, int fileSize, int &filePos, char *pBuffer, int bufferSize ) +{ + // Bullet-proofing + if ( !pMemFile || !pBuffer ) + return NULL; + + if ( filePos >= fileSize ) + return NULL; + + int i = filePos; + int last = fileSize; + + // fgets always NULL terminates, so only read bufferSize-1 characters + if ( last - filePos > (bufferSize-1) ) + last = filePos + (bufferSize-1); + + int stop = 0; + + // Stop at the next newline (inclusive) or end of buffer + while ( i < last && !stop ) + { + if ( pMemFile[i] == '\n' ) + stop = 1; + i++; + } + + + // If we actually advanced the pointer, copy it over + if ( i != filePos ) + { + // We read in size bytes + int size = i - filePos; + // copy it out + memcpy( pBuffer, pMemFile + filePos, sizeof(byte)*size ); + + // If the buffer isn't full, terminate (this is always true) + if ( size < bufferSize ) + pBuffer[size] = 0; + + // Update file pointer + filePos = i; + return pBuffer; + } + + // No data read, bail + return NULL; +} + +// ==================== GENERIC AMBIENT SOUND ====================================== + +// runtime pitch shift and volume fadein/out structure + +// NOTE: IF YOU CHANGE THIS STRUCT YOU MUST CHANGE THE SAVE/RESTORE VERSION NUMBER +// SEE BELOW (in the typedescription for the class) +typedef struct dynpitchvol +{ + // NOTE: do not change the order of these parameters + // NOTE: unless you also change order of rgdpvpreset array elements! + int preset; + + int pitchrun; // pitch shift % when sound is running 0 - 255 + int pitchstart; // pitch shift % when sound stops or starts 0 - 255 + int spinup; // spinup time 0 - 100 + int spindown; // spindown time 0 - 100 + + int volrun; // volume change % when sound is running 0 - 10 + int volstart; // volume change % when sound stops or starts 0 - 10 + int fadein; // volume fade in time 0 - 100 + int fadeout; // volume fade out time 0 - 100 + + // Low Frequency Oscillator + int lfotype; // 0) off 1) square 2) triangle 3) random + int lforate; // 0 - 1000, how fast lfo osciallates + + int lfomodpitch; // 0-100 mod of current pitch. 0 is off. + int lfomodvol; // 0-100 mod of current volume. 0 is off. + + int cspinup; // each trigger hit increments counter and spinup pitch + + + int cspincount; + + int pitch; + int spinupsav; + int spindownsav; + int pitchfrac; + + int vol; + int fadeinsav; + int fadeoutsav; + int volfrac; + + int lfofrac; + int lfomult; + + +} dynpitchvol_t; + +#define CDPVPRESETMAX 27 + +// presets for runtime pitch and vol modulation of ambient sounds + +dynpitchvol_t rgdpvpreset[CDPVPRESETMAX] = +{ +// pitch pstart spinup spindwn volrun volstrt fadein fadeout lfotype lforate modptch modvol cspnup +{1, 255, 75, 95, 95, 10, 1, 50, 95, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{2, 255, 85, 70, 88, 10, 1, 20, 88, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{3, 255, 100, 50, 75, 10, 1, 10, 75, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{4, 100, 100, 0, 0, 10, 1, 90, 90, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{5, 100, 100, 0, 0, 10, 1, 80, 80, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{6, 100, 100, 0, 0, 10, 1, 50, 70, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{7, 100, 100, 0, 0, 5, 1, 40, 50, 1, 50, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0}, +{8, 100, 100, 0, 0, 5, 1, 40, 50, 1, 150, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0}, +{9, 100, 100, 0, 0, 5, 1, 40, 50, 1, 750, 0, 10, 0, 0,0,0,0,0,0,0,0,0,0}, +{10,128, 100, 50, 75, 10, 1, 30, 40, 2, 8, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{11,128, 100, 50, 75, 10, 1, 30, 40, 2, 25, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{12,128, 100, 50, 75, 10, 1, 30, 40, 2, 70, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{13,50, 50, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{14,70, 70, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{15,90, 90, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{16,120, 120, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{17,180, 180, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{18,255, 255, 0, 0, 10, 1, 20, 50, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{19,200, 75, 90, 90, 10, 1, 50, 90, 2, 100, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{20,255, 75, 97, 90, 10, 1, 50, 90, 1, 40, 50, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{21,100, 100, 0, 0, 10, 1, 30, 50, 3, 15, 20, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{22,160, 160, 0, 0, 10, 1, 50, 50, 3, 500, 25, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{23,255, 75, 88, 0, 10, 1, 40, 0, 0, 0, 0, 0, 5, 0,0,0,0,0,0,0,0,0,0}, +{24,200, 20, 95, 70, 10, 1, 70, 70, 3, 20, 50, 0, 0, 0,0,0,0,0,0,0,0,0,0}, +{25,180, 100, 50, 60, 10, 1, 40, 60, 2, 90, 100, 100, 0, 0,0,0,0,0,0,0,0,0,0}, +{26,60, 60, 0, 0, 10, 1, 40, 70, 3, 80, 20, 50, 0, 0,0,0,0,0,0,0,0,0,0}, +{27,128, 90, 10, 10, 10, 1, 20, 40, 1, 5, 10, 20, 0, 0,0,0,0,0,0,0,0,0,0} +}; + +class CAmbientGeneric : public CBaseEntity +{ +public: + void KeyValue( KeyValueData* pkvd); + void Spawn( void ); +// void PostSpawn( void ); + void Precache( void ); + void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT StartPlayFrom( void ); + void EXPORT RampThink( void ); + void InitModulationParms(void); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + + float m_flAttenuation; // attenuation value + dynpitchvol_t m_dpv; + + BOOL m_fActive; // only TRUE when the entity is playing a looping sound + BOOL m_fLooping; // TRUE when the sound played will loop + edict_t *m_pPlayFrom; //LRC - the entity to play from + int m_iChannel; //LRC - the channel to play from, for "play from X" sounds +}; + +LINK_ENTITY_TO_CLASS( ambient_generic, CAmbientGeneric ); +TYPEDESCRIPTION CAmbientGeneric::m_SaveData[] = +{ + DEFINE_FIELD( CAmbientGeneric, m_flAttenuation, FIELD_FLOAT ), + DEFINE_FIELD( CAmbientGeneric, m_fActive, FIELD_BOOLEAN ), + DEFINE_FIELD( CAmbientGeneric, m_fLooping, FIELD_BOOLEAN ), + DEFINE_FIELD( CAmbientGeneric, m_iChannel, FIELD_INTEGER ), //LRC + DEFINE_FIELD( CAmbientGeneric, m_pPlayFrom, FIELD_EDICT ), //LRC + + // HACKHACK - This is not really in the spirit of the save/restore design, but save this + // out as a binary data block. If the dynpitchvol_t is changed, old saved games will NOT + // load these correctly, so bump the save/restore version if you change the size of the struct + // The right way to do this is to split the input parms (read in keyvalue) into members and re-init this + // struct in Precache(), but it's unlikely that the struct will change, so it's not worth the time right now. + DEFINE_ARRAY( CAmbientGeneric, m_dpv, FIELD_CHARACTER, sizeof(dynpitchvol_t) ), +}; + +IMPLEMENT_SAVERESTORE( CAmbientGeneric, CBaseEntity ); + +// +// ambient_generic - general-purpose user-defined static sound +// +void CAmbientGeneric :: Spawn( void ) +{ +/* + -1 : "Default" + 0 : "Everywhere" + 200 : "Small Radius" + 125 : "Medium Radius" + 80 : "Large Radius" +*/ + + if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_EVERYWHERE) ) + { + m_flAttenuation = ATTN_NONE; + } + else if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_SMALLRADIUS) ) + { + m_flAttenuation = ATTN_IDLE; + } + else if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_MEDIUMRADIUS) ) + { + m_flAttenuation = ATTN_STATIC; + } + else if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_LARGERADIUS) ) + { + m_flAttenuation = ATTN_NORM; + } + else + {// if the designer didn't set a sound attenuation, default to one. + m_flAttenuation = ATTN_STATIC; + } + + char* szSoundFile = (char*) STRING(pev->message); + + if ( FStringNull( pev->message ) || strlen( szSoundFile ) < 1 ) + { + ALERT( at_error, "ambient_generic \"%s\" at (%f, %f, %f) has no sound file\n", + STRING(pev->targetname), pev->origin.x, pev->origin.y, pev->origin.z ); + SetNextThink( 0.1 ); + SetThink(&CAmbientGeneric :: SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + // Set up think function for dynamic modification + // of ambient sound's pitch or volume. Don't + // start thinking yet. + + SetThink(&CAmbientGeneric ::RampThink); + DontThink(); + + // allow on/off switching via 'use' function. + + SetUse(&CAmbientGeneric :: ToggleUse ); + + m_fActive = FALSE; + + if ( FBitSet ( pev->spawnflags, AMBIENT_SOUND_NOT_LOOPING ) ) + m_fLooping = FALSE; + else + m_fLooping = TRUE; + Precache( ); +} + +// this function needs to be called when the game is loaded, not just when the entity spawns. +// Don't make this a PostSpawn function. +void CAmbientGeneric :: Precache( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + + if ( !FStringNull( pev->message ) && strlen( szSoundFile ) > 1 ) + { + if (*szSoundFile != '!') + PRECACHE_SOUND(szSoundFile); + } + // init all dynamic modulation parms + InitModulationParms(); + + if ( !FBitSet (pev->spawnflags, AMBIENT_SOUND_START_SILENT ) ) + { + // start the sound ASAP + if (m_fLooping) + m_fActive = TRUE; + } + + if (pev->target) + { + CBaseEntity *pTarget = UTIL_FindEntityByTargetname( NULL, STRING(pev->target)); + if (!pTarget) + { + ALERT(at_debug, "WARNING: ambient_generic \"%s\" can't find \"%s\", its entity to play from.\n", + STRING(pev->targetname), STRING(pev->target)); + } + else + m_pPlayFrom = ENT(pTarget->pev); + } + + if ( m_fActive ) + { + if (m_pPlayFrom || FBitSet(pev->spawnflags, AMBIENT_SOUND_FROM_PLAYER)) + { + SetThink(&CAmbientGeneric ::StartPlayFrom); //LRC +// EMIT_SOUND_DYN( m_pPlayFrom, m_iChannel, szSoundFile, //LRC +// (m_dpv.vol * 0.01), m_flAttenuation, SND_SPAWNING, m_dpv.pitch); + +// ALERT(at_console, "AMBGEN: spawn start\n"); + } + else + { + UTIL_EmitAmbientSound ( ENT(pev), pev->origin, szSoundFile, + (m_dpv.vol * 0.01), m_flAttenuation, SND_SPAWNING, m_dpv.pitch); + } + SetNextThink( 0.1 ); + } +} + +//LRC - for some reason, I can't get other entities to start playing sounds during Activate; +// this function is used to delay the effect until the first Think, which seems to fix the problem. +void CAmbientGeneric :: StartPlayFrom( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + + if (FBitSet(pev->spawnflags, AMBIENT_SOUND_FROM_PLAYER)) // buz + { + CBaseEntity *pPlayer = CBaseEntity::Instance(g_engfuncs.pfnPEntityOfEntIndex(1)); + EMIT_SOUND_SUIT( pPlayer->edict(), szSoundFile); + } + else + { + EMIT_SOUND_DYN( m_pPlayFrom, m_iChannel, szSoundFile, //LRC + (m_dpv.vol * 0.01), m_flAttenuation, SND_SPAWNING, m_dpv.pitch); + } + + SetThink(&CAmbientGeneric ::RampThink); + SetNextThink( 0.1 ); +} + +// RampThink - Think at 5hz if we are dynamically modifying +// pitch or volume of the playing sound. This function will +// ramp pitch and/or volume up or down, modify pitch/volume +// with lfo if active. + +void CAmbientGeneric :: RampThink( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + int pitch = m_dpv.pitch; + int vol = m_dpv.vol; + int flags = 0; + int fChanged = 0; // FALSE if pitch and vol remain unchanged this round + int prev; + + if (!m_dpv.spinup && !m_dpv.spindown && !m_dpv.fadein && !m_dpv.fadeout && !m_dpv.lfotype) + return; // no ramps or lfo, stop thinking + + // ============== + // pitch envelope + // ============== + if (m_dpv.spinup || m_dpv.spindown) + { + prev = m_dpv.pitchfrac >> 8; + + if (m_dpv.spinup > 0) + m_dpv.pitchfrac += m_dpv.spinup; + else if (m_dpv.spindown > 0) + m_dpv.pitchfrac -= m_dpv.spindown; + + pitch = m_dpv.pitchfrac >> 8; + + if (pitch > m_dpv.pitchrun) + { + pitch = m_dpv.pitchrun; + m_dpv.spinup = 0; // done with ramp up + } + + if (pitch < m_dpv.pitchstart) + { + pitch = m_dpv.pitchstart; + m_dpv.spindown = 0; // done with ramp down + + // shut sound off + if (m_pPlayFrom) + { + STOP_SOUND( m_pPlayFrom, m_iChannel, szSoundFile); //LRC + } + else if (FBitSet(pev->spawnflags, AMBIENT_SOUND_FROM_PLAYER)) // buz + { + CBaseEntity *pPlayer = CBaseEntity::Instance(g_engfuncs.pfnPEntityOfEntIndex(1)); + STOP_SOUND( pPlayer->edict(), CHAN_STATIC, szSoundFile); + } + else + { + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + } + + // return without setting nextthink + return; + } + + if (pitch > 255) pitch = 255; + if (pitch < 1) pitch = 1; + + m_dpv.pitch = pitch; + + fChanged |= (prev != pitch); + flags |= SND_CHANGE_PITCH; + } + + // ================== + // amplitude envelope + // ================== + if (m_dpv.fadein || m_dpv.fadeout) + { + prev = m_dpv.volfrac >> 8; + + if (m_dpv.fadein > 0) + m_dpv.volfrac += m_dpv.fadein; + else if (m_dpv.fadeout > 0) + m_dpv.volfrac -= m_dpv.fadeout; + + vol = m_dpv.volfrac >> 8; + + if (vol > m_dpv.volrun) + { + vol = m_dpv.volrun; + m_dpv.fadein = 0; // done with ramp up + } + + if (vol < m_dpv.volstart) + { + vol = m_dpv.volstart; + m_dpv.fadeout = 0; // done with ramp down + + // shut sound off + if (m_pPlayFrom) + { + STOP_SOUND( m_pPlayFrom, m_iChannel, szSoundFile); //LRC + } + else if (FBitSet(pev->spawnflags, AMBIENT_SOUND_FROM_PLAYER)) // buz + { + CBaseEntity *pPlayer = CBaseEntity::Instance(g_engfuncs.pfnPEntityOfEntIndex(1)); + STOP_SOUND( pPlayer->edict(), CHAN_STATIC, szSoundFile); + } + else + { + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + } + + // return without setting nextthink + return; + } + + if (vol > 100) vol = 100; + if (vol < 1) vol = 1; + + m_dpv.vol = vol; + + fChanged |= (prev != vol); + flags |= SND_CHANGE_VOL; + } + + // =================== + // pitch/amplitude LFO + // =================== + if (m_dpv.lfotype) + { + int pos; + + if (m_dpv.lfofrac > 0x6fffffff) + m_dpv.lfofrac = 0; + + // update lfo, lfofrac/255 makes a triangle wave 0-255 + m_dpv.lfofrac += m_dpv.lforate; + pos = m_dpv.lfofrac >> 8; + + if (m_dpv.lfofrac < 0) + { + m_dpv.lfofrac = 0; + m_dpv.lforate = abs(m_dpv.lforate); + pos = 0; + } + else if (pos > 255) + { + pos = 255; + m_dpv.lfofrac = (255 << 8); + m_dpv.lforate = -abs(m_dpv.lforate); + } + + switch(m_dpv.lfotype) + { + case LFO_SQUARE: + if (pos < 128) + m_dpv.lfomult = 255; + else + m_dpv.lfomult = 0; + + break; + case LFO_RANDOM: + if (pos == 255) + m_dpv.lfomult = RANDOM_LONG(0, 255); + break; + case LFO_TRIANGLE: + default: + m_dpv.lfomult = pos; + break; + } + + if (m_dpv.lfomodpitch) + { + prev = pitch; + + // pitch 0-255 + pitch += ((m_dpv.lfomult - 128) * m_dpv.lfomodpitch) / 100; + + if (pitch > 255) pitch = 255; + if (pitch < 1) pitch = 1; + + + fChanged |= (prev != pitch); + flags |= SND_CHANGE_PITCH; + } + + if (m_dpv.lfomodvol) + { + // vol 0-100 + prev = vol; + + vol += ((m_dpv.lfomult - 128) * m_dpv.lfomodvol) / 100; + + if (vol > 100) vol = 100; + if (vol < 0) vol = 0; + + fChanged |= (prev != vol); + flags |= SND_CHANGE_VOL; + } + + } + + // Send update to playing sound only if we actually changed + // pitch or volume in this routine. + + if (flags && fChanged) + { + if (pitch == PITCH_NORM) + pitch = PITCH_NORM + 1; // don't send 'no pitch' ! + + if (m_pPlayFrom) + { + EMIT_SOUND_DYN( m_pPlayFrom, m_iChannel, szSoundFile, (vol * 0.01), //LRC + m_flAttenuation, flags, pitch); + } + else if (FBitSet(pev->spawnflags, AMBIENT_SOUND_FROM_PLAYER)) // buz + { + CBaseEntity *pPlayer = CBaseEntity::Instance(g_engfuncs.pfnPEntityOfEntIndex(1)); + EMIT_SOUND_SUIT( pPlayer->edict(), szSoundFile); + } + else + { + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + (vol * 0.01), m_flAttenuation, flags, pitch); + } + } + + // update ramps at 5hz + SetNextThink( 0.2 ); + return; +} + +// Init all ramp params in preparation to +// play a new sound + +void CAmbientGeneric :: InitModulationParms(void) +{ + int pitchinc; + + m_dpv.volrun = pev->health * 10; // 0 - 100 + if (m_dpv.volrun > 100) m_dpv.volrun = 100; + if (m_dpv.volrun < 0) m_dpv.volrun = 0; + + // get presets + if (m_dpv.preset != 0 && m_dpv.preset <= CDPVPRESETMAX) + { + // load preset values + m_dpv = rgdpvpreset[m_dpv.preset - 1]; + + // fixup preset values, just like + // fixups in KeyValue routine. + if (m_dpv.spindown > 0) + m_dpv.spindown = (101 - m_dpv.spindown) * 64; + if (m_dpv.spinup > 0) + m_dpv.spinup = (101 - m_dpv.spinup) * 64; + + m_dpv.volstart *= 10; + m_dpv.volrun *= 10; + + if (m_dpv.fadein > 0) + m_dpv.fadein = (101 - m_dpv.fadein) * 64; + if (m_dpv.fadeout > 0) + m_dpv.fadeout = (101 - m_dpv.fadeout) * 64; + + m_dpv.lforate *= 256; + + m_dpv.fadeinsav = m_dpv.fadein; + m_dpv.fadeoutsav = m_dpv.fadeout; + m_dpv.spinupsav = m_dpv.spinup; + m_dpv.spindownsav = m_dpv.spindown; + } + + m_dpv.fadein = m_dpv.fadeinsav; + m_dpv.fadeout = 0; + + if (m_dpv.fadein) + m_dpv.vol = m_dpv.volstart; + else + m_dpv.vol = m_dpv.volrun; + + m_dpv.spinup = m_dpv.spinupsav; + m_dpv.spindown = 0; + + if (m_dpv.spinup) + m_dpv.pitch = m_dpv.pitchstart; + else + m_dpv.pitch = m_dpv.pitchrun; + + if (m_dpv.pitch == 0) + m_dpv.pitch = PITCH_NORM; + + m_dpv.pitchfrac = m_dpv.pitch << 8; + m_dpv.volfrac = m_dpv.vol << 8; + + m_dpv.lfofrac = 0; + m_dpv.lforate = abs(m_dpv.lforate); + + m_dpv.cspincount = 1; + + if (m_dpv.cspinup) + { + pitchinc = (255 - m_dpv.pitchstart) / m_dpv.cspinup; + + m_dpv.pitchrun = m_dpv.pitchstart + pitchinc; + if (m_dpv.pitchrun > 255) m_dpv.pitchrun = 255; + } + + if ((m_dpv.spinupsav || m_dpv.spindownsav || (m_dpv.lfotype && m_dpv.lfomodpitch)) + && (m_dpv.pitch == PITCH_NORM)) + m_dpv.pitch = PITCH_NORM + 1; // must never send 'no pitch' as first pitch + // if we intend to pitch shift later! +} + +// +// ToggleUse - turns an ambient sound on or off. If the +// ambient is a looping sound, mark sound as active (m_fActive) +// if it's playing, innactive if not. If the sound is not +// a looping sound, never mark it as active. +// +void CAmbientGeneric :: ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + char* szSoundFile = (char*) STRING(pev->message); + float fraction; + + if ( useType != USE_TOGGLE ) + { + if ( (m_fActive && useType == USE_ON) || (!m_fActive && useType == USE_OFF) ) + return; + } + // Directly change pitch if arg passed. Only works if sound is already playing. + + if (useType == USE_SET && m_fActive) // Momentary buttons will pass down a float in here + { + + fraction = value; + + if ( fraction > 1.0 ) + fraction = 1.0; + if (fraction < 0.0) + fraction = 0.01; + + m_dpv.pitch = fraction * 255; + + if (m_pPlayFrom) + { + EMIT_SOUND_DYN( m_pPlayFrom, m_iChannel, szSoundFile, 0, 0, SND_CHANGE_PITCH, m_dpv.pitch); + } + else if (FBitSet(pev->spawnflags, AMBIENT_SOUND_FROM_PLAYER)) // buz + { + CBaseEntity *pPlayer = CBaseEntity::Instance(g_engfuncs.pfnPEntityOfEntIndex(1)); + EMIT_SOUND_SUIT( pPlayer->edict(), szSoundFile); + } + else + { + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_CHANGE_PITCH, m_dpv.pitch); + } + + return; + } + + // Toggle + + // m_fActive is TRUE only if a looping sound is playing. + + if ( m_fActive ) + {// turn sound off + + if (m_dpv.cspinup) + { + // Don't actually shut off. Each toggle causes + // incremental spinup to max pitch + + if (m_dpv.cspincount <= m_dpv.cspinup) + { + int pitchinc; + + // start a new spinup + m_dpv.cspincount++; + + pitchinc = (255 - m_dpv.pitchstart) / m_dpv.cspinup; + + m_dpv.spinup = m_dpv.spinupsav; + m_dpv.spindown = 0; + + m_dpv.pitchrun = m_dpv.pitchstart + pitchinc * m_dpv.cspincount; + if (m_dpv.pitchrun > 255) m_dpv.pitchrun = 255; + + SetNextThink( 0.1 ); + } + + } + else + { + m_fActive = FALSE; + + // HACKHACK - this makes the code in Precache() work properly after a save/restore + pev->spawnflags |= AMBIENT_SOUND_START_SILENT; + + if (m_dpv.spindownsav || m_dpv.fadeoutsav) + { + // spin it down (or fade it) before shutoff if spindown is set + m_dpv.spindown = m_dpv.spindownsav; + m_dpv.spinup = 0; + + m_dpv.fadeout = m_dpv.fadeoutsav; + m_dpv.fadein = 0; + SetNextThink( 0.1 ); + } + else if (m_pPlayFrom) + { + STOP_SOUND( m_pPlayFrom, m_iChannel, szSoundFile); + } + else if (FBitSet(pev->spawnflags, AMBIENT_SOUND_FROM_PLAYER)) // buz + { + CBaseEntity *pPlayer = CBaseEntity::Instance(g_engfuncs.pfnPEntityOfEntIndex(1)); + STOP_SOUND( pPlayer->edict(), CHAN_STATIC, szSoundFile); + } + else + { + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + } + } + } + else + {// turn sound on + + // only toggle if this is a looping sound. If not looping, each + // trigger will cause the sound to play. If the sound is still + // playing from a previous trigger press, it will be shut off + // and then restarted. + + if (m_fLooping) + { + m_fActive = TRUE; + } + else if (m_pPlayFrom) + { + STOP_SOUND( m_pPlayFrom, m_iChannel, szSoundFile); //LRC + } + else if (FBitSet(pev->spawnflags, AMBIENT_SOUND_FROM_PLAYER)) // buz + { + CBaseEntity *pPlayer = CBaseEntity::Instance(g_engfuncs.pfnPEntityOfEntIndex(1)); + STOP_SOUND( pPlayer->edict(), CHAN_STATIC, szSoundFile); + } + else + { + // shut sound off now - may be interrupting a long non-looping sound + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + 0, 0, SND_STOP, 0); + } + + // init all ramp params for startup + + InitModulationParms(); + + if (m_pPlayFrom) + { + EMIT_SOUND_DYN( m_pPlayFrom, m_iChannel, szSoundFile, //LRC + (m_dpv.vol * 0.01), m_flAttenuation, 0, m_dpv.pitch); + } + else if (FBitSet(pev->spawnflags, AMBIENT_SOUND_FROM_PLAYER)) // buz + { + CBaseEntity *pPlayer = CBaseEntity::Instance(g_engfuncs.pfnPEntityOfEntIndex(1)); + EMIT_SOUND_SUIT( pPlayer->edict(), szSoundFile); + } + else + { + UTIL_EmitAmbientSound(ENT(pev), pev->origin, szSoundFile, + (m_dpv.vol * 0.01), m_flAttenuation, 0, m_dpv.pitch); + } + + SetNextThink( 0.1 ); + } +} +// KeyValue - load keyvalue pairs into member data of the +// ambient generic. NOTE: called BEFORE spawn! + +void CAmbientGeneric :: KeyValue( KeyValueData *pkvd ) +{ + // NOTE: changing any of the modifiers in this code + // NOTE: also requires changing InitModulationParms code. + + // channel (e.g. CHAN_STATIC, etc) + if (FStrEq(pkvd->szKeyName, "channel")) + { + m_iChannel = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + + // preset + else if (FStrEq(pkvd->szKeyName, "preset")) + { + m_dpv.preset = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + + // pitchrun + else if (FStrEq(pkvd->szKeyName, "pitch")) + { + m_dpv.pitchrun = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + if (m_dpv.pitchrun > 255) m_dpv.pitchrun = 255; + if (m_dpv.pitchrun < 0) m_dpv.pitchrun = 0; + } + + // pitchstart + else if (FStrEq(pkvd->szKeyName, "pitchstart")) + { + m_dpv.pitchstart = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + if (m_dpv.pitchstart > 255) m_dpv.pitchstart = 255; + if (m_dpv.pitchstart < 0) m_dpv.pitchstart = 0; + } + + // spinup + else if (FStrEq(pkvd->szKeyName, "spinup")) + { + m_dpv.spinup = atoi(pkvd->szValue); + + if (m_dpv.spinup > 100) m_dpv.spinup = 100; + if (m_dpv.spinup < 0) m_dpv.spinup = 0; + + if (m_dpv.spinup > 0) + m_dpv.spinup = (101 - m_dpv.spinup) * 64; + m_dpv.spinupsav = m_dpv.spinup; + pkvd->fHandled = TRUE; + } + + // spindown + else if (FStrEq(pkvd->szKeyName, "spindown")) + { + m_dpv.spindown = atoi(pkvd->szValue); + + if (m_dpv.spindown > 100) m_dpv.spindown = 100; + if (m_dpv.spindown < 0) m_dpv.spindown = 0; + + if (m_dpv.spindown > 0) + m_dpv.spindown = (101 - m_dpv.spindown) * 64; + m_dpv.spindownsav = m_dpv.spindown; + pkvd->fHandled = TRUE; + } + + // volstart + else if (FStrEq(pkvd->szKeyName, "volstart")) + { + m_dpv.volstart = atoi(pkvd->szValue); + + if (m_dpv.volstart > 10) m_dpv.volstart = 10; + if (m_dpv.volstart < 0) m_dpv.volstart = 0; + + m_dpv.volstart *= 10; // 0 - 100 + + pkvd->fHandled = TRUE; + } + + // fadein + else if (FStrEq(pkvd->szKeyName, "fadein")) + { + m_dpv.fadein = atoi(pkvd->szValue); + + if (m_dpv.fadein > 100) m_dpv.fadein = 100; + if (m_dpv.fadein < 0) m_dpv.fadein = 0; + + if (m_dpv.fadein > 0) + m_dpv.fadein = (101 - m_dpv.fadein) * 64; + m_dpv.fadeinsav = m_dpv.fadein; + pkvd->fHandled = TRUE; + } + + // fadeout + else if (FStrEq(pkvd->szKeyName, "fadeout")) + { + m_dpv.fadeout = atoi(pkvd->szValue); + + if (m_dpv.fadeout > 100) m_dpv.fadeout = 100; + if (m_dpv.fadeout < 0) m_dpv.fadeout = 0; + + if (m_dpv.fadeout > 0) + m_dpv.fadeout = (101 - m_dpv.fadeout) * 64; + m_dpv.fadeoutsav = m_dpv.fadeout; + pkvd->fHandled = TRUE; + } + + // lfotype + else if (FStrEq(pkvd->szKeyName, "lfotype")) + { + m_dpv.lfotype = atoi(pkvd->szValue); + if (m_dpv.lfotype > 4) m_dpv.lfotype = LFO_TRIANGLE; + pkvd->fHandled = TRUE; + } + + // lforate + else if (FStrEq(pkvd->szKeyName, "lforate")) + { + m_dpv.lforate = atoi(pkvd->szValue); + + if (m_dpv.lforate > 1000) m_dpv.lforate = 1000; + if (m_dpv.lforate < 0) m_dpv.lforate = 0; + + m_dpv.lforate *= 256; + + pkvd->fHandled = TRUE; + } + // lfomodpitch + else if (FStrEq(pkvd->szKeyName, "lfomodpitch")) + { + m_dpv.lfomodpitch = atoi(pkvd->szValue); + if (m_dpv.lfomodpitch > 100) m_dpv.lfomodpitch = 100; + if (m_dpv.lfomodpitch < 0) m_dpv.lfomodpitch = 0; + + + pkvd->fHandled = TRUE; + } + + // lfomodvol + else if (FStrEq(pkvd->szKeyName, "lfomodvol")) + { + m_dpv.lfomodvol = atoi(pkvd->szValue); + if (m_dpv.lfomodvol > 100) m_dpv.lfomodvol = 100; + if (m_dpv.lfomodvol < 0) m_dpv.lfomodvol = 0; + + pkvd->fHandled = TRUE; + } + + // cspinup + else if (FStrEq(pkvd->szKeyName, "cspinup")) + { + m_dpv.cspinup = atoi(pkvd->szValue); + if (m_dpv.cspinup > 100) m_dpv.cspinup = 100; + if (m_dpv.cspinup < 0) m_dpv.cspinup = 0; + + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +// =================== ROOM SOUND FX ========================================== + +class CEnvSound : public CPointEntity +{ +public: + void KeyValue( KeyValueData* pkvd); + void Spawn( void ); + + void Think( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + float m_flRadius; + float m_flRoomtype; +}; + +LINK_ENTITY_TO_CLASS( env_sound, CEnvSound ); +TYPEDESCRIPTION CEnvSound::m_SaveData[] = +{ + DEFINE_FIELD( CEnvSound, m_flRadius, FIELD_FLOAT ), + DEFINE_FIELD( CEnvSound, m_flRoomtype, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CEnvSound, CBaseEntity ); + + +void CEnvSound :: KeyValue( KeyValueData *pkvd ) +{ + + if (FStrEq(pkvd->szKeyName, "radius")) + { + m_flRadius = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + if (FStrEq(pkvd->szKeyName, "roomtype")) + { + m_flRoomtype = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } +} + +// returns TRUE if the given sound entity (pev) is in range +// and can see the given player entity (pevTarget) + +BOOL FEnvSoundInRange(entvars_t *pev, entvars_t *pevTarget, float *pflRange) +{ + CEnvSound *pSound = GetClassPtr( (CEnvSound *)pev ); + Vector vecSpot1 = pev->origin + pev->view_ofs; + Vector vecSpot2 = pevTarget->origin + pevTarget->view_ofs; + Vector vecRange; + float flRange; + TraceResult tr; + + UTIL_TraceLine(vecSpot1, vecSpot2, ignore_monsters, ENT(pev), &tr); + + // check if line of sight crosses water boundary, or is blocked + + if ((tr.fInOpen && tr.fInWater) || tr.flFraction != 1) + return FALSE; + + // calc range from sound entity to player + + vecRange = tr.vecEndPos - vecSpot1; + flRange = vecRange.Length(); + + if (pSound->m_flRadius < flRange) + return FALSE; + + if (pflRange) + *pflRange = flRange; + + return TRUE; +} + +// +// A client that is visible and in range of a sound entity will +// have its room_type set by that sound entity. If two or more +// sound entities are contending for a client, then the nearest +// sound entity to the client will set the client's room_type. +// A client's room_type will remain set to its prior value until +// a new in-range, visible sound entity resets a new room_type. +// + +// CONSIDER: if player in water state, autoset roomtype to 14,15 or 16. + +void CEnvSound :: Think( void ) +{ + // get pointer to client if visible; FIND_CLIENT_IN_PVS will + // cycle through visible clients on consecutive calls. + + edict_t *pentPlayer = FIND_CLIENT_IN_PVS(edict()); + CBasePlayer *pPlayer = NULL; + + if (FNullEnt(pentPlayer)) + goto env_sound_Think_slow; // no player in pvs of sound entity, slow it down + + pPlayer = GetClassPtr( (CBasePlayer *)VARS(pentPlayer)); + float flRange; + + // check to see if this is the sound entity that is + // currently affecting this player + + if(!FNullEnt(pPlayer->m_pentSndLast) && (pPlayer->m_pentSndLast == ENT(pev))) { + + // this is the entity currently affecting player, check + // for validity + + if (pPlayer->m_flSndRoomtype != 0 && pPlayer->m_flSndRange != 0) { + + // we're looking at a valid sound entity affecting + // player, make sure it's still valid, update range + + if (FEnvSoundInRange(pev, VARS(pentPlayer), &flRange)) { + pPlayer->m_flSndRange = flRange; + goto env_sound_Think_fast; + } else { + + // current sound entity affecting player is no longer valid, + // flag this state by clearing room_type and range. + // NOTE: we do not actually change the player's room_type + // NOTE: until we have a new valid room_type to change it to. + + pPlayer->m_flSndRange = 0; + pPlayer->m_flSndRoomtype = 0; + goto env_sound_Think_slow; + } + } else { + // entity is affecting player but is out of range, + // wait passively for another entity to usurp it... + goto env_sound_Think_slow; + } + } + + // if we got this far, we're looking at an entity that is contending + // for current player sound. the closest entity to player wins. + + if (FEnvSoundInRange(pev, VARS(pentPlayer), &flRange)) + { + if (flRange < pPlayer->m_flSndRange || pPlayer->m_flSndRange == 0) + { + // new entity is closer to player, so it wins. + pPlayer->m_pentSndLast = ENT(pev); + pPlayer->m_flSndRoomtype = m_flRoomtype; + pPlayer->m_flSndRange = flRange; + + // send room_type command to player's server. + // this should be a rare event - once per change of room_type + // only! + + //CLIENT_COMMAND(pentPlayer, "room_type %f", m_flRoomtype); + + MESSAGE_BEGIN( MSG_ONE, SVC_ROOMTYPE, NULL, pentPlayer ); // use the magic #1 for "one client" + WRITE_SHORT( (short)m_flRoomtype ); // sequence number + MESSAGE_END(); + + // crank up nextthink rate for new active sound entity + // by falling through to think_fast... + } + // player is not closer to the contending sound entity, + // just fall through to think_fast. this effectively + // cranks up the think_rate of entities near the player. + } + + // player is in pvs of sound entity, but either not visible or + // not in range. do nothing, fall through to think_fast... + +env_sound_Think_fast: + SetNextThink( 0.25 ); + return; + +env_sound_Think_slow: + SetNextThink( 0.75 ); + return; +} + +// +// env_sound - spawn a sound entity that will set player roomtype +// when player moves in range and sight. +// +// +void CEnvSound :: Spawn( ) +{ + // spread think times + SetNextThink( RANDOM_FLOAT(0.0, 0.5) ); +} + +//===================== +//LRC - trigger_sound +//===================== +class CTriggerSound : public CBaseDelay +{ +public: + void KeyValue( KeyValueData* pkvd); + void Spawn( void ); + void Touch( CBaseEntity *pOther ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + virtual int ObjectCaps( void ) { return CBaseDelay :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + float m_flRoomtype; + string_t m_iszMaster; +}; + +LINK_ENTITY_TO_CLASS( trigger_sound, CTriggerSound ); +TYPEDESCRIPTION CTriggerSound::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerSound, m_flRoomtype, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerSound, m_iszMaster, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CTriggerSound, CBaseDelay ); + +void CTriggerSound :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "roomtype")) + { + m_flRoomtype = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "master")) + { + m_iszMaster = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CTriggerSound :: Touch( CBaseEntity *pOther ) +{ + if (!UTIL_IsMasterTriggered(m_iszMaster, pOther)) return; + + if (pOther->IsPlayer()) + { + CBasePlayer *pPlayer = (CBasePlayer*)pOther; + if (pPlayer->m_pentSndLast != this->edict()) + { + pPlayer->m_pentSndLast = ENT(pev); + pPlayer->m_flSndRoomtype = m_flRoomtype; + pPlayer->m_flSndRange = 0; + + MESSAGE_BEGIN( MSG_ONE, SVC_ROOMTYPE, NULL, pPlayer->edict() ); // use the magic #1 for "one client" + WRITE_SHORT( (short)m_flRoomtype ); // sequence number + MESSAGE_END(); + + SUB_UseTargets(pPlayer, USE_TOGGLE, 0); + } + } +} + +void CTriggerSound :: Spawn( ) +{ + pev->solid = SOLID_TRIGGER; + pev->movetype = MOVETYPE_NONE; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + SetBits( pev->effects, EF_NODRAW ); +} + +// ==================== SENTENCE GROUPS, UTILITY FUNCTIONS ====================================== + +#define CSENTENCE_LRU_MAX 32 // max number of elements per sentence group + +// group of related sentences + +typedef struct sentenceg +{ + char szgroupname[CBSENTENCENAME_MAX]; + int count; + unsigned char rgblru[CSENTENCE_LRU_MAX]; + +} SENTENCEG; + +#define CSENTENCEG_MAX 512 // max number of sentence groups +// globals + +SENTENCEG rgsentenceg[CSENTENCEG_MAX]; +int fSentencesInit = FALSE; + +char gszallsentencenames[CVOXFILESENTENCEMAX][CBSENTENCENAME_MAX]; +int gcallsentences = 0; + +// randomize list of sentence name indices + +void USENTENCEG_InitLRU(unsigned char *plru, int count) +{ + int i, j, k; + unsigned char temp; + + if (!fSentencesInit) + return; + + if (count > CSENTENCE_LRU_MAX) + count = CSENTENCE_LRU_MAX; + + for (i = 0; i < count; i++) + plru[i] = (unsigned char) i; + + // randomize array + for (i = 0; i < (count * 4); i++) + { + j = RANDOM_LONG(0,count-1); + k = RANDOM_LONG(0,count-1); + temp = plru[j]; + plru[j] = plru[k]; + plru[k] = temp; + } +} + +// ignore lru. pick next sentence from sentence group. Go in order until we hit the last sentence, +// then repeat list if freset is true. If freset is false, then repeat last sentence. +// ipick is passed in as the requested sentence ordinal. +// ipick 'next' is returned. +// return of -1 indicates an error. + +int USENTENCEG_PickSequential(int isentenceg, char *szfound, int ipick, int freset) +{ + char *szgroupname; + unsigned char count; + char sznum[8]; + + if (!fSentencesInit) + return -1; + + if (isentenceg < 0) + return -1; + + szgroupname = rgsentenceg[isentenceg].szgroupname; + count = rgsentenceg[isentenceg].count; + + if (count == 0) + return -1; + + if (ipick >= count) + ipick = count-1; + + strcpy(szfound, "!"); + strcat(szfound, szgroupname); + sprintf(sznum, "%d", ipick); + strcat(szfound, sznum); + + if (ipick >= count) + { + if (freset) + // reset at end of list + return 0; + else + return count; + } + + return ipick + 1; +} + + + +// pick a random sentence from rootname0 to rootnameX. +// picks from the rgsentenceg[isentenceg] least +// recently used, modifies lru array. returns the sentencename. +// note, lru must be seeded with 0-n randomized sentence numbers, with the +// rest of the lru filled with -1. The first integer in the lru is +// actually the size of the list. Returns ipick, the ordinal +// of the picked sentence within the group. + +int USENTENCEG_Pick(int isentenceg, char *szfound) +{ + char *szgroupname; + unsigned char *plru; + unsigned char i; + unsigned char count; + char sznum[8]; + unsigned char ipick; + int ffound = FALSE; + + if (!fSentencesInit) + return -1; + + if (isentenceg < 0) + return -1; + + szgroupname = rgsentenceg[isentenceg].szgroupname; + count = rgsentenceg[isentenceg].count; + plru = rgsentenceg[isentenceg].rgblru; + + while (!ffound) + { + for (i = 0; i < count; i++) + if (plru[i] != 0xFF) + { + ipick = plru[i]; + plru[i] = 0xFF; + ffound = TRUE; + break; + } + + if (!ffound) + USENTENCEG_InitLRU(plru, count); + else + { + strcpy(szfound, "!"); + strcat(szfound, szgroupname); + sprintf(sznum, "%d", ipick); + strcat(szfound, sznum); + return ipick; + } + } + return -1; +} + +// ===================== SENTENCE GROUPS, MAIN ROUTINES ======================== + +// Given sentence group rootname (name without number suffix), +// get sentence group index (isentenceg). Returns -1 if no such name. + +int SENTENCEG_GetIndex(const char *szgroupname) +{ + int i; + + if (!fSentencesInit || !szgroupname) + return -1; + + // search rgsentenceg for match on szgroupname + + i = 0; + while (rgsentenceg[i].count) + { + if (!strcmp(szgroupname, rgsentenceg[i].szgroupname)) + return i; + i++; + } + + return -1; +} + +// given sentence group index, play random sentence for given entity. +// returns ipick - which sentence was picked to +// play from the group. Ipick is only needed if you plan on stopping +// the sound before playback is done (see SENTENCEG_Stop). + +int SENTENCEG_PlayRndI(edict_t *entity, int isentenceg, + float volume, float attenuation, int flags, int pitch) +{ + char name[64]; + int ipick; + + if (!fSentencesInit) + return -1; + + name[0] = 0; + + ipick = USENTENCEG_Pick(isentenceg, name); + if (ipick > 0 && name) + EMIT_SOUND_DYN(entity, CHAN_VOICE, name, volume, attenuation, flags, pitch); + return ipick; +} + +// same as above, but takes sentence group name instead of index + +int SENTENCEG_PlayRndSz(edict_t *entity, const char *szgroupname, + float volume, float attenuation, int flags, int pitch, int channel) +{ + char name[64]; + int ipick; + int isentenceg; + + if (!fSentencesInit) + return -1; + + name[0] = 0; + + isentenceg = SENTENCEG_GetIndex(szgroupname); + if (isentenceg < 0) + { + ALERT( at_debug, "No such sentence group %s\n", szgroupname ); + return -1; + } + + ipick = USENTENCEG_Pick(isentenceg, name); + if (ipick >= 0 && name[0]) + EMIT_SOUND_DYN(entity, (channel == -1) ? CHAN_VOICE : channel, name, volume, attenuation, flags, pitch); + + return ipick; +} + +// play sentences in sequential order from sentence group. Reset after last sentence. + +int SENTENCEG_PlaySequentialSz(edict_t *entity, const char *szgroupname, + float volume, float attenuation, int flags, int pitch, int ipick, int freset) +{ + char name[64]; + int ipicknext; + int isentenceg; + + if (!fSentencesInit) + return -1; + + name[0] = 0; + + isentenceg = SENTENCEG_GetIndex(szgroupname); + if (isentenceg < 0) + return -1; + + ipicknext = USENTENCEG_PickSequential(isentenceg, name, ipick, freset); + if (ipicknext >= 0 && name[0]) + EMIT_SOUND_DYN(entity, CHAN_VOICE, name, volume, attenuation, flags, pitch); + return ipicknext; +} + + +// for this entity, for the given sentence within the sentence group, stop +// the sentence. + +void SENTENCEG_Stop(edict_t *entity, int isentenceg, int ipick) +{ + char buffer[64]; + char sznum[8]; + + if (!fSentencesInit) + return; + + if (isentenceg < 0 || ipick < 0) + return; + + strcpy(buffer, "!"); + strcat(buffer, rgsentenceg[isentenceg].szgroupname); + sprintf(sznum, "%d", ipick); + strcat(buffer, sznum); + + STOP_SOUND(entity, CHAN_VOICE, buffer); +} + +// open sentences.txt, scan for groups, build rgsentenceg +// Should be called from world spawn, only works on the +// first call and is ignored subsequently. + +void SENTENCEG_Init() +{ + char buffer[512]; + char szgroup[64]; + int i, j; + int isentencegs; + + if (fSentencesInit) + return; + + memset(gszallsentencenames, 0, CVOXFILESENTENCEMAX * CBSENTENCENAME_MAX); + gcallsentences = 0; + + memset(rgsentenceg, 0, CSENTENCEG_MAX * sizeof(SENTENCEG)); + memset(buffer, 0, 512); + memset(szgroup, 0, 64); + isentencegs = -1; + + + int filePos = 0, fileSize; + byte *pMemFile = g_engfuncs.pfnLoadFileForMe( "sound/sentences.txt", &fileSize ); + if ( !pMemFile ) + return; + + // for each line in the file... + while ( memfgets(pMemFile, fileSize, filePos, buffer, 511) != NULL ) + { + // skip whitespace + i = 0; + while(buffer[i] && buffer[i] == ' ') + i++; + + if (!buffer[i]) + continue; + + if (buffer[i] == '/' || !isalpha(buffer[i])) + continue; + + // get sentence name + j = i; + while (buffer[j] && buffer[j] != ' ') + j++; + + if (!buffer[j]) + continue; + + if (gcallsentences > CVOXFILESENTENCEMAX) + { + ALERT (at_error, "Too many sentences in sentences.txt!\n"); + break; + } + + // null-terminate name and save in sentences array + buffer[j] = 0; + const char *pString = buffer + i; + + if ( strlen( pString ) >= CBSENTENCENAME_MAX ) + ALERT( at_warning, "Sentence %s longer than %d letters\n", pString, CBSENTENCENAME_MAX-1 ); + + strcpy( gszallsentencenames[gcallsentences++], pString ); + + j--; + if (j <= i) + continue; + if (!isdigit(buffer[j])) + continue; + + // cut out suffix numbers + while (j > i && isdigit(buffer[j])) + j--; + + if (j <= i) + continue; + + buffer[j+1] = 0; + + // if new name doesn't match previous group name, + // make a new group. + + if (strcmp(szgroup, &(buffer[i]))) + { + // name doesn't match with prev name, + // copy name into group, init count to 1 + isentencegs++; + if (isentencegs >= CSENTENCEG_MAX) + { + ALERT (at_error, "Too many sentence groups in sentences.txt!\n"); + break; + } + + strcpy(rgsentenceg[isentencegs].szgroupname, &(buffer[i])); + rgsentenceg[isentencegs].count = 1; + + strcpy(szgroup, &(buffer[i])); + + continue; + } + else + { + //name matches with previous, increment group count + if (isentencegs >= 0) + rgsentenceg[isentencegs].count++; + } + } + + g_engfuncs.pfnFreeFile( pMemFile ); + + fSentencesInit = TRUE; + + // init lru lists + + i = 0; + + while (rgsentenceg[i].count && i < CSENTENCEG_MAX) + { + USENTENCEG_InitLRU(&(rgsentenceg[i].rgblru[0]), rgsentenceg[i].count); + i++; + } + +} + +// convert sentence (sample) name to !sentencenum, return !sentencenum + +int SENTENCEG_Lookup(const char *sample, char *sentencenum) +{ + char sznum[8]; + + int i; + // this is a sentence name; lookup sentence number + // and give to engine as string. + for (i = 0; i < gcallsentences; i++) + if (!stricmp(gszallsentencenames[i], sample+1)) + { + if (sentencenum) + { + strcpy(sentencenum, "!"); + sprintf(sznum, "%d", i); + strcat(sentencenum, sznum); + } + return i; + } + // sentence name not found! + return -1; +} + +void EMIT_SOUND_DYN(edict_t *entity, int channel, const char *sample, float volume, float attenuation, + int flags, int pitch) +{ + if (sample && *sample == '!') + { + char name[32]; + if (SENTENCEG_Lookup(sample, name) >= 0) + { + EMIT_SOUND_DYN2(entity, channel, name, volume, attenuation, flags, pitch); + // ALERT(at_console, "play sentence %s, output %s\n", sample, name); + } + else + ALERT( at_aiconsole, "Unable to find %s in sentences.txt\n", sample ); + + // buz: send sencences as text messages to lookup subtitles in titles.txt + // UTIL_ShowMessageAll( sample ); + UTIL_ShowMessagePVS( sample, entity->v.origin ); + } + else + { + EMIT_SOUND_DYN2(entity, channel, sample, volume, attenuation, flags, pitch); + // ALERT(at_console, "play sound %s\n", sample); + } +} + +// play a specific sentence over the HEV suit speaker - just pass player entity, and !sentencename + +void EMIT_SOUND_SUIT(edict_t *entity, const char *sample) +{ + float fvol; + int pitch = PITCH_NORM; + + fvol = CVAR_GET_FLOAT("suitvolume"); + if (RANDOM_LONG(0,1)) + pitch = RANDOM_LONG(0,6) + 98; + + if (fvol > 0.05) + EMIT_SOUND_DYN(entity, CHAN_STATIC, sample, fvol, ATTN_NORM, 0, pitch); +} + +// play a sentence, randomly selected from the passed in group id, over the HEV suit speaker + +void EMIT_GROUPID_SUIT(edict_t *entity, int isentenceg) +{ + float fvol; + int pitch = PITCH_NORM; + + fvol = CVAR_GET_FLOAT("suitvolume"); + if (RANDOM_LONG(0,1)) + pitch = RANDOM_LONG(0,6) + 98; + + if (fvol > 0.05) + SENTENCEG_PlayRndI(entity, isentenceg, fvol, ATTN_NORM, 0, pitch); +} + +// play a sentence, randomly selected from the passed in groupname + +void EMIT_GROUPNAME_SUIT(edict_t *entity, const char *groupname) +{ + float fvol; + int pitch = PITCH_NORM; + + fvol = CVAR_GET_FLOAT("suitvolume"); + if (RANDOM_LONG(0,1)) + pitch = RANDOM_LONG(0,6) + 98; + + if (fvol > 0.05) + SENTENCEG_PlayRndSz(entity, groupname, fvol, ATTN_NORM, 0, pitch); +} + +// ===================== MATERIAL TYPE DETECTION, MAIN ROUTINES ======================== +// play a strike sound based on the texture that was hit by the attack traceline. VecSrc/VecEnd are the +// original traceline endpoints used by the attacker, iBulletType is the type of bullet that hit the texture. +// returns volume of strike instrument (crowbar) to play +// (this is not used for footsteps, only attack sound effects. --LRC) + +void TEXTURETYPE_Init( void ) +{ + // TODO: parse matspec.txt here, precache sounds etc +} + +float TEXTURETYPE_PlaySound( TraceResult *ptr, Vector vecSrc, Vector vecEnd, int iBulletType ) +{ + // hit the world, try to play sound based on texture material type + int impactType = IMPACT_NONE; + char *rgsz[MAX_MAT_SOUNDS]; + float fattn = ATTN_NORM; + matdef_t *pMat = NULL; + float fvol, fvolbar; + int cnt; + + if ( !g_pGameRules->PlayTextureSounds() ) + return 0.0f; + + CBaseEntity *pEntity = CBaseEntity :: Instance( ptr->pHit ); + + if( !pEntity ) return 0.0f; // noting to hit? + + if( pEntity->Classify() != CLASS_NONE && pEntity->Classify() != CLASS_MACHINE ) + { + // hit body + impactType = IMPACT_BODY; + } + else if( pEntity->pev->solid == SOLID_BSP || pEntity->pev->movetype == MOVETYPE_PUSHSTEP ) + { + msurface_t *surf = TRACE_SURFACE( pEntity->edict(), vecSrc, vecEnd ); + + if( !surf || !surf->texinfo || !surf->texinfo->texture || !surf->texinfo->texture->material ) + return 0.0f; + + impactType = IMPACT_MATERIAL; + + pMat = surf->texinfo->texture->material->effects; // epic chain! + } + else if( pEntity->pev->solid == SOLID_CUSTOM ) + { + if( !ptr->pMat ) return 0.0f; + + impactType = IMPACT_MATERIAL; + + pMat = ptr->pMat->effects; + } + + if ( !pMat && !impactType ) + return 0.0f; + + switch ( impactType ) + { + case IMPACT_BODY: + if ( iBulletType == BULLET_STAB ) + return 0.0f; // knife already makes this sound + rgsz[0] = "weapons/bullet_hit1.wav"; + rgsz[1] = "weapons/bullet_hit2.wav"; + fvol = 1.0f; fvolbar = 0.2f; + fattn = 1.0f; + cnt = 2; + break; + case IMPACT_MATERIAL: + if( !pMat ) return 0.0f; + fvol = 1.0f; fvolbar = 0.2f; + fattn = 1.0f; + + // count sounds + for( cnt = 0; pMat->impact_sounds[cnt] != NULL; cnt++ ) + rgsz[cnt] = (char *)pMat->impact_sounds[cnt]; + break; + default: + return 0.0f; + } + + // did we hit a breakable? + if( pEntity && FClassnameIs(pEntity->pev, "func_breakable" )) + { + // drop volumes, the object will already play a damaged sound + fvol /= 1.5f; + fvolbar /= 2.0f; + } + + // play material hit sound + UTIL_EmitAmbientSound( ENT( 0 ), ptr->vecEndPos, rgsz[RANDOM_LONG( 0, cnt - 1 )], fvol, fattn, 0, 96 + RANDOM_LONG( 0, 0xf )); + + return fvolbar; +} + +// =================================================================================== +// +// Speaker class. Used for announcements per level, for door lock/unlock spoken voice. +// + +class CSpeaker : public CBaseEntity +{ +public: + void KeyValue( KeyValueData* pkvd); + void Spawn( void ); + void Precache( void ); + void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT SpeakerThink( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + + int m_preset; // preset number +}; + +LINK_ENTITY_TO_CLASS( speaker, CSpeaker ); +TYPEDESCRIPTION CSpeaker::m_SaveData[] = +{ + DEFINE_FIELD( CSpeaker, m_preset, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CSpeaker, CBaseEntity ); + +// +// ambient_generic - general-purpose user-defined static sound +// +void CSpeaker :: Spawn( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + + if ( !m_preset && (FStringNull( pev->message ) || strlen( szSoundFile ) < 1 )) + { + ALERT( at_error, "SPEAKER with no Level/Sentence! at: %f, %f, %f\n", pev->origin.x, pev->origin.y, pev->origin.z ); + SetNextThink( 0.1 ); + SetThink(&CSpeaker :: SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + + SetThink(&CSpeaker ::SpeakerThink); + DontThink(); + + // allow on/off switching via 'use' function. + + SetUse(&CSpeaker :: ToggleUse ); + + Precache( ); +} + +#define ANNOUNCE_MINUTES_MIN 0.25 +#define ANNOUNCE_MINUTES_MAX 2.25 + +void CSpeaker :: Precache( void ) +{ + if ( !FBitSet (pev->spawnflags, SPEAKER_START_SILENT ) ) + // set first announcement time for random n second + SetNextThink( RANDOM_FLOAT(5.0, 15.0) ); +} +void CSpeaker :: SpeakerThink( void ) +{ + char* szSoundFile; + float flvolume = pev->health * 0.1; + float flattenuation = 0.3; + int flags = 0; + int pitch = 100; + + + // Wait for the talkmonster to finish first. + if (gpGlobals->time <= CTalkMonster::g_talkWaitTime) + { + AbsoluteNextThink( CTalkMonster::g_talkWaitTime + RANDOM_FLOAT( 5, 10 ) ); + return; + } + + if (m_preset) + { + // go lookup preset text, assign szSoundFile + switch (m_preset) + { + case 1: szSoundFile = "C1A0_"; break; + case 2: szSoundFile = "C1A1_"; break; + case 3: szSoundFile = "C1A2_"; break; + case 4: szSoundFile = "C1A3_"; break; + case 5: szSoundFile = "C1A4_"; break; + case 6: szSoundFile = "C2A1_"; break; + case 7: szSoundFile = "C2A2_"; break; + case 8: szSoundFile = "C2A3_"; break; + case 9: szSoundFile = "C2A4_"; break; + case 10: szSoundFile = "C2A5_"; break; + case 11: szSoundFile = "C3A1_"; break; + case 12: szSoundFile = "C3A2_"; break; + } + } else + szSoundFile = (char*) STRING(pev->message); + + if (szSoundFile[0] == '!') + { + // play single sentence, one shot + UTIL_EmitAmbientSound ( ENT(pev), pev->origin, szSoundFile, + flvolume, flattenuation, flags, pitch); + + // shut off and reset + DontThink(); + } + else + { + // make random announcement from sentence group + + if (SENTENCEG_PlayRndSz(ENT(pev), szSoundFile, flvolume, flattenuation, flags, pitch) < 0) + ALERT(at_debug, "Level Design Error!\nSPEAKER has bad sentence group name: %s\n",szSoundFile); + + // set next announcement time for random 5 to 10 minute delay + SetNextThink( RANDOM_FLOAT(ANNOUNCE_MINUTES_MIN * 60.0, ANNOUNCE_MINUTES_MAX * 60.0) ); + + CTalkMonster::g_talkWaitTime = gpGlobals->time + 5; // time delay until it's ok to speak: used so that two NPCs don't talk at once + } + + return; +} + + +// +// ToggleUse - if an announcement is pending, cancel it. If no announcement is pending, start one. +// +void CSpeaker :: ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int fActive = (m_fNextThink > 0.0); + + // fActive is TRUE only if an announcement is pending + + if ( useType != USE_TOGGLE ) + { + // ignore if we're just turning something on that's already on, or + // turning something off that's already off. + if ( (fActive && useType == USE_ON) || (!fActive && useType == USE_OFF) ) + return; + } + + if ( useType == USE_ON ) + { + // turn on announcements + SetNextThink( 0.1 ); + return; + } + + if ( useType == USE_OFF ) + { + // turn off announcements + DontThink(); + return; + + } + + // Toggle announcements + + + if ( fActive ) + { + // turn off announcements + DontThink(); + } + else + { + // turn on announcements + SetNextThink( 0.1 ); + } +} + +// KeyValue - load keyvalue pairs into member data +// NOTE: called BEFORE spawn! + +void CSpeaker :: KeyValue( KeyValueData *pkvd ) +{ + + // preset + if (FStrEq(pkvd->szKeyName, "preset")) + { + m_preset = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +// buz +extern int gmsgRadioIcon; + +class CRadio : public CPointEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; + +LINK_ENTITY_TO_CLASS( radio_sentence, CRadio ); + +void CRadio::Spawn() +{ + Precache(); + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + pev->effects |= EF_NODRAW; +} + +void CRadio::Precache() +{ + char* szSoundFile = (char*) STRING(pev->message); + + if ( !FStringNull( pev->message ) && strlen( szSoundFile ) > 1 ) + { + if (*szSoundFile != '!') + PRECACHE_SOUND(szSoundFile); + } +} + +void CRadio::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + char* szSoundFile = (char*) STRING(pev->message); + CBaseEntity *pPlayer = CBaseEntity::Instance(g_engfuncs.pfnPEntityOfEntIndex(1)); + if ( !FStringNull( pev->message ) && strlen( szSoundFile ) > 1 ) + { + EMIT_SOUND_SUIT( pPlayer->edict(), szSoundFile); + } + + char* szTitle = (char*) STRING(pev->netname); + if ( !FStringNull( pev->netname ) && strlen( szTitle ) > 1 ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgRadioIcon, NULL, pPlayer->pev ); + WRITE_STRING( szTitle ); + WRITE_COORD( pev->health ); + WRITE_BYTE( pev->rendercolor.x ); + WRITE_BYTE( pev->rendercolor.y ); + WRITE_BYTE( pev->rendercolor.z ); + WRITE_BYTE( pev->renderamt ); + MESSAGE_END(); + } +} \ No newline at end of file diff --git a/dlls/soundent.cpp b/dlls/soundent.cpp new file mode 100644 index 0000000..726f74f --- /dev/null +++ b/dlls/soundent.cpp @@ -0,0 +1,379 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "soundent.h" + + +LINK_ENTITY_TO_CLASS( soundent, CSoundEnt ); + +CSoundEnt *pSoundEnt; + +//========================================================= +// CSound - Clear - zeros all fields for a sound +//========================================================= +void CSound :: Clear ( void ) +{ + m_vecOrigin = g_vecZero; + m_iType = 0; + m_iVolume = 0; + m_flExpireTime = 0; + m_iNext = SOUNDLIST_EMPTY; + m_iNextAudible = 0; +} + +//========================================================= +// Reset - clears the volume, origin, and type for a sound, +// but doesn't expire or unlink it. +//========================================================= +void CSound :: Reset ( void ) +{ + m_vecOrigin = g_vecZero; + m_iType = 0; + m_iVolume = 0; + m_iNext = SOUNDLIST_EMPTY; +} + +//========================================================= +// FIsSound - returns TRUE if the sound is an Audible sound +//========================================================= +BOOL CSound :: FIsSound ( void ) +{ + if ( m_iType & ( bits_SOUND_COMBAT | bits_SOUND_WORLD | bits_SOUND_PLAYER | bits_SOUND_DANGER ) ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// FIsScent - returns TRUE if the sound is actually a scent +//========================================================= +BOOL CSound :: FIsScent ( void ) +{ + if ( m_iType & ( bits_SOUND_CARCASS | bits_SOUND_MEAT | bits_SOUND_GARBAGE ) ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// Spawn +//========================================================= +void CSoundEnt :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + Initialize(); + + SetNextThink( 1 ); +} + +//========================================================= +// Think - at interval, the entire active sound list is checked +// for sounds that have ExpireTimes less than or equal +// to the current world time, and these sounds are deallocated. +//========================================================= +void CSoundEnt :: Think ( void ) +{ + int iSound; + int iPreviousSound; + + SetNextThink( 0.3 );// how often to check the sound list. + + iPreviousSound = SOUNDLIST_EMPTY; + iSound = m_iActiveSound; + + while ( iSound != SOUNDLIST_EMPTY ) + { + if ( m_SoundPool[ iSound ].m_flExpireTime <= gpGlobals->time && m_SoundPool[ iSound ].m_flExpireTime != SOUND_NEVER_EXPIRE ) + { + int iNext = m_SoundPool[ iSound ].m_iNext; + + // move this sound back into the free list + FreeSound( iSound, iPreviousSound ); + + iSound = iNext; + } + else + { + iPreviousSound = iSound; + iSound = m_SoundPool[ iSound ].m_iNext; + } + } + + if ( m_fShowReport ) + { + ALERT ( at_aiconsole, "Soundlist: %d / %d (%d)\n", ISoundsInList( SOUNDLISTTYPE_ACTIVE ),ISoundsInList( SOUNDLISTTYPE_FREE ), ISoundsInList( SOUNDLISTTYPE_ACTIVE ) - m_cLastActiveSounds ); + m_cLastActiveSounds = ISoundsInList ( SOUNDLISTTYPE_ACTIVE ); + } + +} + +//========================================================= +// Precache - dummy function +//========================================================= +void CSoundEnt :: Precache ( void ) +{ +} + +//========================================================= +// FreeSound - clears the passed active sound and moves it +// to the top of the free list. TAKE CARE to only call this +// function for sounds in the Active list!! +//========================================================= +void CSoundEnt :: FreeSound ( int iSound, int iPrevious ) +{ + if ( !pSoundEnt ) + { + // no sound ent! + return; + } + + if ( iPrevious != SOUNDLIST_EMPTY ) + { + // iSound is not the head of the active list, so + // must fix the index for the Previous sound +// pSoundEnt->m_SoundPool[ iPrevious ].m_iNext = m_SoundPool[ iSound ].m_iNext; + pSoundEnt->m_SoundPool[ iPrevious ].m_iNext = pSoundEnt->m_SoundPool[ iSound ].m_iNext; + } + else + { + // the sound we're freeing IS the head of the active list. + pSoundEnt->m_iActiveSound = pSoundEnt->m_SoundPool [ iSound ].m_iNext; + } + + // make iSound the head of the Free list. + pSoundEnt->m_SoundPool[ iSound ].m_iNext = pSoundEnt->m_iFreeSound; + pSoundEnt->m_iFreeSound = iSound; +} + +//========================================================= +// IAllocSound - moves a sound from the Free list to the +// Active list returns the index of the alloc'd sound +//========================================================= +int CSoundEnt :: IAllocSound( void ) +{ + int iNewSound; + + if ( m_iFreeSound == SOUNDLIST_EMPTY ) + { + // no free sound! + ALERT ( at_debug, "Free Sound List is full!\n" ); + return SOUNDLIST_EMPTY; + } + + // there is at least one sound available, so move it to the + // Active sound list, and return its SoundPool index. + + iNewSound = m_iFreeSound;// copy the index of the next free sound + + m_iFreeSound = m_SoundPool[ m_iFreeSound ].m_iNext;// move the index down into the free list. + + m_SoundPool[ iNewSound ].m_iNext = m_iActiveSound;// point the new sound at the top of the active list. + + m_iActiveSound = iNewSound;// now make the new sound the top of the active list. You're done. + + return iNewSound; +} + +//========================================================= +// InsertSound - Allocates a free sound and fills it with +// sound info. +//========================================================= +void CSoundEnt :: InsertSound ( int iType, const Vector &vecOrigin, int iVolume, float flDuration ) +{ + int iThisSound; + + if ( !pSoundEnt ) + { + // no sound ent! + return; + } + + iThisSound = pSoundEnt->IAllocSound(); + + if ( iThisSound == SOUNDLIST_EMPTY ) + { + ALERT ( at_debug, "Could not AllocSound() for InsertSound() (DLL)\n" ); + return; + } + + pSoundEnt->m_SoundPool[ iThisSound ].m_vecOrigin = vecOrigin; + pSoundEnt->m_SoundPool[ iThisSound ].m_iType = iType; + pSoundEnt->m_SoundPool[ iThisSound ].m_iVolume = iVolume; + pSoundEnt->m_SoundPool[ iThisSound ].m_flExpireTime = gpGlobals->time + flDuration; +} + +//========================================================= +// Initialize - clears all sounds and moves them into the +// free sound list. +//========================================================= +void CSoundEnt :: Initialize ( void ) +{ + int i; + int iSound; + + m_cLastActiveSounds; + m_iFreeSound = 0; + m_iActiveSound = SOUNDLIST_EMPTY; + + for ( i = 0 ; i < MAX_WORLD_SOUNDS ; i++ ) + {// clear all sounds, and link them into the free sound list. + m_SoundPool[ i ].Clear(); + m_SoundPool[ i ].m_iNext = i + 1; + } + + m_SoundPool[ i - 1 ].m_iNext = SOUNDLIST_EMPTY;// terminate the list here. + + + // now reserve enough sounds for each client + for ( i = 0 ; i < gpGlobals->maxClients ; i++ ) + { + iSound = pSoundEnt->IAllocSound(); + + if ( iSound == SOUNDLIST_EMPTY ) + { + ALERT ( at_debug, "Could not AllocSound() for Client Reserve! (DLL)\n" ); + return; + } + + pSoundEnt->m_SoundPool[ iSound ].m_flExpireTime = SOUND_NEVER_EXPIRE; + } + + if ( CVAR_GET_FLOAT("displaysoundlist") == 1 ) + { + m_fShowReport = TRUE; + } + else + { + m_fShowReport = FALSE; + } +} + +//========================================================= +// ISoundsInList - returns the number of sounds in the desired +// sound list. +//========================================================= +int CSoundEnt :: ISoundsInList ( int iListType ) +{ + int i; + int iThisSound; + + if ( iListType == SOUNDLISTTYPE_FREE ) + { + iThisSound = m_iFreeSound; + } + else if ( iListType == SOUNDLISTTYPE_ACTIVE ) + { + iThisSound = m_iActiveSound; + } + else + { + ALERT ( at_debug, "Unknown Sound List Type!\n" ); + } + + if ( iThisSound == SOUNDLIST_EMPTY ) + { + return 0; + } + + i = 0; + + while ( iThisSound != SOUNDLIST_EMPTY ) + { + i++; + + iThisSound = m_SoundPool[ iThisSound ].m_iNext; + } + + return i; +} + +//========================================================= +// ActiveList - returns the head of the active sound list +//========================================================= +int CSoundEnt :: ActiveList ( void ) +{ + if ( !pSoundEnt ) + { + return SOUNDLIST_EMPTY; + } + + return pSoundEnt->m_iActiveSound; +} + +//========================================================= +// FreeList - returns the head of the free sound list +//========================================================= +int CSoundEnt :: FreeList ( void ) +{ + if ( !pSoundEnt ) + { + return SOUNDLIST_EMPTY; + } + + return pSoundEnt->m_iFreeSound; +} + +//========================================================= +// SoundPointerForIndex - returns a pointer to the instance +// of CSound at index's position in the sound pool. +//========================================================= +CSound* CSoundEnt :: SoundPointerForIndex( int iIndex ) +{ + if ( !pSoundEnt ) + { + return NULL; + } + + if ( iIndex > ( MAX_WORLD_SOUNDS - 1 ) ) + { + ALERT ( at_debug, "SoundPointerForIndex() - Index too large!\n" ); + return NULL; + } + + if ( iIndex < 0 ) + { + ALERT ( at_debug, "SoundPointerForIndex() - Index < 0!\n" ); + return NULL; + } + + return &pSoundEnt->m_SoundPool[ iIndex ]; +} + +//========================================================= +// Clients are numbered from 1 to MAXCLIENTS, but the client +// reserved sounds in the soundlist are from 0 to MAXCLIENTS - 1, +// so this function ensures that a client gets the proper index +// to his reserved sound in the soundlist. +//========================================================= +int CSoundEnt :: ClientSoundIndex ( edict_t *pClient ) +{ + int iReturn = ENTINDEX( pClient ) - 1; + +#ifdef _DEBUG + if ( iReturn < 0 || iReturn > gpGlobals->maxClients ) + { + ALERT ( at_debug, "** ClientSoundIndex returning a bogus value! **\n" ); + } +#endif // _DEBUG + + return iReturn; +} diff --git a/dlls/soundent.h b/dlls/soundent.h new file mode 100644 index 0000000..150daac --- /dev/null +++ b/dlls/soundent.h @@ -0,0 +1,95 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// Soundent.h - the entity that spawns when the world +// spawns, and handles the world's active and free sound +// lists. +//========================================================= + +#define MAX_WORLD_SOUNDS 64 // maximum number of sounds handled by the world at one time. + +#define bits_SOUND_NONE 0 +#define bits_SOUND_COMBAT ( 1 << 0 )// gunshots, explosions +#define bits_SOUND_WORLD ( 1 << 1 )// door opening/closing, glass breaking +#define bits_SOUND_PLAYER ( 1 << 2 )// all noises generated by player. walking, shooting, falling, splashing +#define bits_SOUND_CARCASS ( 1 << 3 )// dead body +#define bits_SOUND_MEAT ( 1 << 4 )// gib or pork chop +#define bits_SOUND_DANGER ( 1 << 5 )// pending danger. Grenade that is about to explode, explosive barrel that is damaged, falling crate +#define bits_SOUND_GARBAGE ( 1 << 6 )// trash cans, banana peels, old fast food bags. + +#define bits_ALL_SOUNDS 0xFFFFFFFF + +#define SOUNDLIST_EMPTY -1 + +#define SOUNDLISTTYPE_FREE 1// identifiers passed to functions that can operate on either list, to indicate which list to operate on. +#define SOUNDLISTTYPE_ACTIVE 2 + +#define SOUND_NEVER_EXPIRE -1 // with this set as a sound's ExpireTime, the sound will never expire. + +//========================================================= +// CSound - an instance of a sound in the world. +//========================================================= +class CSound +{ +public: + + void Clear ( void ); + void Reset ( void ); + + Vector m_vecOrigin; // sound's location in space + int m_iType; // what type of sound this is + int m_iVolume; // how loud the sound is + float m_flExpireTime; // when the sound should be purged from the list + int m_iNext; // index of next sound in this list ( Active or Free ) + int m_iNextAudible; // temporary link that monsters use to build a list of audible sounds + + BOOL FIsSound( void ); + BOOL FIsScent( void ); +}; + +//========================================================= +// CSoundEnt - a single instance of this entity spawns when +// the world spawns. The SoundEnt's job is to update the +// world's Free and Active sound lists. +//========================================================= +class CSoundEnt : public CBaseEntity +{ +public: + + void Precache ( void ); + void Spawn( void ); + void Think( void ); + void Initialize ( void ); + + static void InsertSound ( int iType, const Vector &vecOrigin, int iVolume, float flDuration ); + static void FreeSound ( int iSound, int iPrevious ); + static int ActiveList( void );// return the head of the active list + static int FreeList( void );// return the head of the free list + static CSound* SoundPointerForIndex( int iIndex );// return a pointer for this index in the sound list + static int ClientSoundIndex ( edict_t *pClient ); + + BOOL IsEmpty( void ) { return m_iActiveSound == SOUNDLIST_EMPTY; } + int ISoundsInList ( int iListType ); + int IAllocSound ( void ); + virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; } + + int m_iFreeSound; // index of the first sound in the free sound list + int m_iActiveSound; // indes of the first sound in the active sound list + int m_cLastActiveSounds; // keeps track of the number of active sounds at the last update. (for diagnostic work) + BOOL m_fShowReport; // if true, dump information about free/active sounds. + +private: + CSound m_SoundPool[ MAX_WORLD_SOUNDS ]; +}; diff --git a/dlls/spectator.cpp b/dlls/spectator.cpp new file mode 100644 index 0000000..c99edfe --- /dev/null +++ b/dlls/spectator.cpp @@ -0,0 +1,147 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// CBaseSpectator + +// YWB: UNDONE + +// Spectator functions +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "spectator.h" + +/* +=========== +SpectatorConnect + +called when a spectator connects to a server +============ +*/ +void CBaseSpectator::SpectatorConnect(void) +{ + pev->flags = FL_SPECTATOR; + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NOCLIP; + + m_pGoalEnt = NULL; +} + +/* +=========== +SpectatorDisconnect + +called when a spectator disconnects from a server +============ +*/ +void CBaseSpectator::SpectatorDisconnect(void) +{ +} + +/* +================ +SpectatorImpulseCommand + +Called by SpectatorThink if the spectator entered an impulse +================ +*/ +void CBaseSpectator::SpectatorImpulseCommand(void) +{ + static edict_t *pGoal = NULL; + CBaseEntity *pPreviousGoal; + CBaseEntity *pCurrentGoal; + BOOL bFound; + + switch (pev->impulse) + { + case 1: + // teleport the spectator to the next spawn point; note that if the spectator is + // tracking, this doesn't do much + pPreviousGoal = (CBaseEntity*)GET_PRIVATE(pGoal); + pCurrentGoal = (CBaseEntity*)GET_PRIVATE(pGoal); + // Start at the current goal, skip the world, and stop if we looped back around + + bFound = FALSE; + while (1) + { + pCurrentGoal = UTIL_FindEntityByClassname(pCurrentGoal, "info_player_deathmatch"); + // Looped around, failure + if (pCurrentGoal == pPreviousGoal) + { + ALERT(at_debug, "Could not find a spawn spot.\n"); + break; + } + // Found a non-world entity, set success, otherwise, look for the next one. + if ( pCurrentGoal ) + { + bFound = TRUE; + break; + } + } + + if (!bFound) // Didn't find a good spot. + break; + + pGoal = ENT(pCurrentGoal->pev); + UTIL_SetOrigin( this, pGoal->v.origin ); + pev->angles = pGoal->v.angles; + pev->fixangle = FALSE; + break; + default: + ALERT(at_debug, "Unknown spectator impulse\n"); + break; + } + + pev->impulse = 0; +} + +/* +================ +SpectatorThink + +Called every frame after physics are run +================ +*/ +void CBaseSpectator::SpectatorThink(void) +{ + if (!(pev->flags & FL_SPECTATOR)) + { + pev->flags = FL_SPECTATOR; + } + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NOCLIP; + + if (pev->impulse) + SpectatorImpulseCommand(); +} + +/* +=========== +Spawn + + Called when spectator is initialized: + UNDONE: Is this actually being called because spectators are not allocated in normal fashion? +============ +*/ +void CBaseSpectator::Spawn() +{ + pev->flags = FL_SPECTATOR; + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NOCLIP; + + m_pGoalEnt = NULL; +} diff --git a/dlls/spectator.h b/dlls/spectator.h new file mode 100644 index 0000000..d459a38 --- /dev/null +++ b/dlls/spectator.h @@ -0,0 +1,27 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Spectator.h + +class CBaseSpectator : public CBaseEntity +{ +public: + void Spawn(); + void SpectatorConnect(void); + void SpectatorDisconnect(void); + void SpectatorThink(void); + +private: + void SpectatorImpulseCommand(void); +}; \ No newline at end of file diff --git a/dlls/spider.cpp b/dlls/spider.cpp new file mode 100644 index 0000000..9891531 --- /dev/null +++ b/dlls/spider.cpp @@ -0,0 +1,478 @@ +/*** +* +* PARANOIA 2: SAVIOR +* MaSTeR +* +****/ +//========================================================= +// headcrab.cpp - tiny, jumpy alien parasite +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "game.h" + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define SPIDER_AE_JUMPATTACK ( 2 ) + +Task_t tlSpiderRangeAttack1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_WAIT_RANDOM, (float)0.5 }, +}; + +Schedule_t slSpiderRangeAttack1[] = +{ + { + tlSpiderRangeAttack1, + ARRAYSIZE ( tlSpiderRangeAttack1 ), + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED, + 0, + "HCRangeAttack1" + }, +}; + +Task_t tlSpiderRangeAttack1Fast[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slSpiderRangeAttack1Fast[] = +{ + { + tlSpiderRangeAttack1Fast, + ARRAYSIZE ( tlSpiderRangeAttack1Fast ), + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED, + 0, + "HCRAFast" + }, +}; + +class CSpider : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void RunTask ( Task_t *pTask ); + void StartTask ( Task_t *pTask ); + float MaxYawSpeed( void ); + void EXPORT LeapTouch ( CBaseEntity *pOther ); + Vector Center( void ); + Vector BodyTarget( const Vector &posSrc ); + void PainSound( void ); + void DeathSound( void ); + void IdleSound( void ); + void AlertSound( void ); + void PrescheduleThink( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack2 ( float flDot, float flDist ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + virtual float GetDamageAmount( void ) { return gSkillData.headcrabDmgBite; } + virtual int GetVoicePitch( void ) { return 100; } + virtual float GetSoundVolue( void ) { return 1.0; } + Schedule_t* GetScheduleOfType ( int Type ); + + CUSTOM_SCHEDULES; + + static const char *pIdleSounds[]; + static const char *pAlertSounds[]; + static const char *pPainSounds[]; + static const char *pAttackSounds[]; + static const char *pDeathSounds[]; + static const char *pBiteSounds[]; +}; +LINK_ENTITY_TO_CLASS( monster_spider, CSpider ); + +DEFINE_CUSTOM_SCHEDULES( CSpider ) +{ + slSpiderRangeAttack1, + slSpiderRangeAttack1Fast, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CSpider, CBaseMonster ); + +const char *CSpider::pIdleSounds[] = +{ + "spider/idle1.wav", + "spider/idle2.wav", + "spider/idle3.wav", +}; +const char *CSpider::pAlertSounds[] = +{ + "spider/alert1.wav", +}; +const char *CSpider::pPainSounds[] = +{ + "spider/pain1.wav", + "spider/pain2.wav", + "spider/pain3.wav", +}; +const char *CSpider::pAttackSounds[] = +{ + "spider/attack1.wav", + "spider/attack2.wav", + "spider/attack3.wav", +}; + +const char *CSpider::pDeathSounds[] = +{ + "spider/die1.wav", + "spider/die2.wav", +}; + +const char *CSpider::pBiteSounds[] = +{ + "spider/headbite.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CSpider :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_PREY; +} + +//========================================================= +// Center - returns the real center of the spider. The +// bounding box is much larger than the actual creature so +// this is needed for targeting +//========================================================= +Vector CSpider :: Center ( void ) +{ + return Vector( pev->origin.x, pev->origin.y, pev->origin.z + 6 ); +} + + +Vector CSpider :: BodyTarget( const Vector &posSrc ) +{ + return Center( ); +} + +//========================================================= +// MaxYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +float CSpider :: MaxYawSpeed( void ) +{ + float ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + ys = 30; + break; + case ACT_RUN: + case ACT_WALK: + ys = 20; + break; + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 60; + break; + case ACT_RANGE_ATTACK1: + ys = 30; + break; + default: + ys = 30; + break; + } + + return ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CSpider :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case SPIDER_AE_JUMPATTACK: + { + ClearBits( pev->flags, FL_ONGROUND ); + + UTIL_SetOrigin (this, pev->origin + Vector ( 0 , 0 , 1) );// take him off ground so engine doesn't instantly reset onground + UTIL_MakeVectors ( pev->angles ); + + Vector vecJumpDir; + if (m_hEnemy != NULL) + { + float gravity = g_psv_gravity->value; + if (gravity <= 1) + gravity = 1; + + // How fast does the spider need to travel to reach that height given gravity? + float height = (m_hEnemy->pev->origin.z + m_hEnemy->pev->view_ofs.z - pev->origin.z); + if (height < 16) + height = 16; + float speed = sqrt( 2 * gravity * height ); + float time = speed / gravity; + + // Scale the sideways velocity to get there at the right time + vecJumpDir = (m_hEnemy->pev->origin + m_hEnemy->pev->view_ofs - pev->origin); + vecJumpDir = vecJumpDir * ( 1.0 / time ); + + // Speed to offset gravity at the desired height + vecJumpDir.z = speed; + + // Don't jump too far/fast + float distance = vecJumpDir.Length(); + + if (distance > 650) + { + vecJumpDir = vecJumpDir * ( 650.0 / distance ); + } + } + else + { + // jump hop, don't care where + vecJumpDir = Vector( gpGlobals->v_forward.x, gpGlobals->v_forward.y, gpGlobals->v_up.z ) * 350; + } + + int iSound = RANDOM_LONG(0,2); + if ( iSound != 0 ) + EMIT_SOUND_DYN( edict(), CHAN_VOICE, pAttackSounds[iSound], GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); + + pev->velocity = vecJumpDir; + m_flNextAttack = gpGlobals->time + 2; + } + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CSpider :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/monsters/monster_spider.mdl"); + UTIL_SetSize(pev, Vector(-12, -12, 0), Vector(12, 12, 24)); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + if (pev->health == 0) + pev->health = gSkillData.headcrabHealth; + pev->view_ofs = Vector ( 0, 0, 20 );// position of the eyes relative to monster's origin. + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CSpider :: Precache() +{ + PRECACHE_SOUND_ARRAY(pIdleSounds); + PRECACHE_SOUND_ARRAY(pAlertSounds); + PRECACHE_SOUND_ARRAY(pPainSounds); + PRECACHE_SOUND_ARRAY(pAttackSounds); + PRECACHE_SOUND_ARRAY(pDeathSounds); + PRECACHE_SOUND_ARRAY(pBiteSounds); + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/monsters/monster_spider.mdl"); +} + + +//========================================================= +// RunTask +//========================================================= +void CSpider :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_RANGE_ATTACK1: + case TASK_RANGE_ATTACK2: + { + if ( m_fSequenceFinished ) + { + TaskComplete(); + SetTouch( NULL ); + m_IdealActivity = ACT_IDLE; + } + break; + } + default: + { + CBaseMonster :: RunTask(pTask); + } + } +} + +//========================================================= +// LeapTouch - this is the spider's touch function when it +// is in the air +//========================================================= +void CSpider :: LeapTouch ( CBaseEntity *pOther ) +{ + if ( !pOther->pev->takedamage ) + { + return; + } + + if ( pOther->Classify() == Classify() ) + { + return; + } + + // Don't hit if back on ground + if ( !FBitSet( pev->flags, FL_ONGROUND ) ) + { + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pBiteSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); + + pOther->TakeDamage( pev, pev, GetDamageAmount(), DMG_SLASH ); + } + + SetTouch( NULL ); +} + +//========================================================= +// PrescheduleThink +//========================================================= +void CSpider :: PrescheduleThink ( void ) +{ + // make the crab coo a little bit in combat state + if ( m_MonsterState == MONSTERSTATE_COMBAT && RANDOM_FLOAT( 0, 5 ) < 0.1 ) + { + IdleSound(); + } +} + +void CSpider :: StartTask ( Task_t *pTask ) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch ( pTask->iTask ) + { + case TASK_RANGE_ATTACK1: + { + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, pAttackSounds[0], GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); + m_IdealActivity = ACT_RANGE_ATTACK1; + SetTouch(&CSpider :: LeapTouch ); + break; + } + default: + { + CBaseMonster :: StartTask( pTask ); + } + } +} + + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CSpider :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( FBitSet( pev->flags, FL_ONGROUND ) && flDist <= 256 && flDot >= 0.65 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack2 +//========================================================= +BOOL CSpider :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + return FALSE; + // BUGBUG: Why is this code here? There is no ACT_RANGE_ATTACK2 animation. I've disabled it for now. +#if 0 + if ( FBitSet( pev->flags, FL_ONGROUND ) && flDist > 64 && flDist <= 256 && flDot >= 0.5 ) + { + return TRUE; + } + return FALSE; +#endif +} + +int CSpider :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // Don't take any acid damage -- BigMomma's mortar is acid + if ( bitsDamageType & DMG_ACID ) + flDamage = 0; + + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +//========================================================= +// IdleSound +//========================================================= +#define SPIDER_ATTN_IDLE (float)1.5 +void CSpider :: IdleSound ( void ) +{ + EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pIdleSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); +} + +//========================================================= +// AlertSound +//========================================================= +void CSpider :: AlertSound ( void ) +{ + EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pAlertSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); +} + +//========================================================= +// AlertSound +//========================================================= +void CSpider :: PainSound ( void ) +{ + EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pPainSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); +} + +//========================================================= +// DeathSound +//========================================================= +void CSpider :: DeathSound ( void ) +{ + EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pDeathSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); +} + +Schedule_t* CSpider :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_RANGE_ATTACK1: + { + return &slSpiderRangeAttack1[ 0 ]; + } + break; + } + + return CBaseMonster::GetScheduleOfType( Type ); +} diff --git a/dlls/squad.h b/dlls/squad.h new file mode 100644 index 0000000..9acfccb --- /dev/null +++ b/dlls/squad.h @@ -0,0 +1,20 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: New version of the slider bar +// +// $NoKeywords: $ +//============================================================================= + +//========================================================= +// squad.h +//========================================================= + +// these are special group roles that are assigned to members when the group is formed. +// the reason these are explicitly assigned and tasks like throwing grenades to flush out +// enemies is that it's bad to have two members trying to flank left at the same time, but +// ok to have two throwing grenades at the same time. When a squad member cannot attack the +// enemy, it will choose to execute its special role. +#define bits_SQUAD_FLANK_LEFT ( 1 << 0 ) +#define bits_SQUAD_FLANK_RIGHT ( 1 << 1 ) +#define bits_SQUAD_ADVANCE ( 1 << 2 ) +#define bits_SQUAD_FLUSH_ATTACK ( 1 << 3 ) \ No newline at end of file diff --git a/dlls/squadmonster.cpp b/dlls/squadmonster.cpp new file mode 100644 index 0000000..4cf6a35 --- /dev/null +++ b/dlls/squadmonster.cpp @@ -0,0 +1,643 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Squadmonster functions +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "nodes.h" +#include "monsters.h" +#include "animation.h" +#include "saverestore.h" +#include "squadmonster.h" +#include "plane.h" + +//========================================================= +// Save/Restore +//========================================================= +TYPEDESCRIPTION CSquadMonster::m_SaveData[] = +{ + DEFINE_FIELD( CSquadMonster, m_hSquadLeader, FIELD_EHANDLE ), + DEFINE_ARRAY( CSquadMonster, m_hSquadMember, FIELD_EHANDLE, MAX_SQUAD_MEMBERS - 1 ), + + // DEFINE_FIELD( CSquadMonster, m_afSquadSlots, FIELD_INTEGER ), // these need to be reset after transitions! + DEFINE_FIELD( CSquadMonster, m_fEnemyEluded, FIELD_BOOLEAN ), + DEFINE_FIELD( CSquadMonster, m_flLastEnemySightTime, FIELD_TIME ), + + DEFINE_FIELD( CSquadMonster, m_iMySlot, FIELD_INTEGER ), + + +}; + +IMPLEMENT_SAVERESTORE( CSquadMonster, CBaseMonster ); + + +//========================================================= +// OccupySlot - if any slots of the passed slots are +// available, the monster will be assigned to one. +//========================================================= +BOOL CSquadMonster :: OccupySlot( int iDesiredSlots ) +{ + int i; + int iMask; + int iSquadSlots; + + if ( !InSquad() ) + { + return TRUE; + } + + if ( SquadEnemySplit() ) + { + // if the squad members aren't all fighting the same enemy, slots are disabled + // so that a squad member doesn't get stranded unable to engage his enemy because + // all of the attack slots are taken by squad members fighting other enemies. + m_iMySlot = bits_SLOT_SQUAD_SPLIT; + return TRUE; + } + + CSquadMonster *pSquadLeader = MySquadLeader(); + + if ( !( iDesiredSlots ^ pSquadLeader->m_afSquadSlots ) ) + { + // none of the desired slots are available. + return FALSE; + } + + iSquadSlots = pSquadLeader->m_afSquadSlots; + + for ( i = 0; i < NUM_SLOTS; i++ ) + { + iMask = 1<m_afSquadSlots |= iMask; + m_iMySlot = iMask; +// ALERT ( at_aiconsole, "Took slot %d - %d\n", i, m_hSquadLeader->m_afSquadSlots ); + return TRUE; + } + } + } + + return FALSE; +} + +//========================================================= +// VacateSlot +//========================================================= +void CSquadMonster :: VacateSlot() +{ + if ( m_iMySlot != bits_NO_SLOT && InSquad() ) + { +// ALERT ( at_aiconsole, "Vacated Slot %d - %d\n", m_iMySlot, m_hSquadLeader->m_afSquadSlots ); + MySquadLeader()->m_afSquadSlots &= ~m_iMySlot; + m_iMySlot = bits_NO_SLOT; + } +} + +//========================================================= +// ScheduleChange +//========================================================= +void CSquadMonster :: ScheduleChange ( void ) +{ + VacateSlot(); +} + +//========================================================= +// Killed +//========================================================= +void CSquadMonster :: Killed( entvars_t *pevAttacker, int iGib ) +{ + VacateSlot(); + + if ( InSquad() ) + { + MySquadLeader()->SquadRemove( this ); + } + + CBaseMonster :: Killed ( pevAttacker, iGib ); +} + +// These functions are still awaiting conversion to CSquadMonster + + +//========================================================= +// +// SquadRemove(), remove pRemove from my squad. +// If I am pRemove, promote m_pSquadNext to leader +// +//========================================================= +void CSquadMonster :: SquadRemove( CSquadMonster *pRemove ) +{ + ASSERT( pRemove!=NULL ); + ASSERT( this->IsLeader() ); + ASSERT( pRemove->m_hSquadLeader == this ); + + // If I'm the leader, get rid of my squad + if (pRemove == MySquadLeader()) + { + for (int i = 0; i < MAX_SQUAD_MEMBERS-1;i++) + { + CSquadMonster *pMember = MySquadMember(i); + if (pMember) + { + pMember->m_hSquadLeader = NULL; + m_hSquadMember[i] = NULL; + } + } + } + else + { + CSquadMonster *pSquadLeader = MySquadLeader(); + if (pSquadLeader) + { + for (int i = 0; i < MAX_SQUAD_MEMBERS-1;i++) + { + if (pSquadLeader->m_hSquadMember[i] == this) + { + pSquadLeader->m_hSquadMember[i] = NULL; + break; + } + } + } + } + + pRemove->m_hSquadLeader = NULL; +} + +//========================================================= +// +// SquadAdd(), add pAdd to my squad +// +//========================================================= +BOOL CSquadMonster :: SquadAdd( CSquadMonster *pAdd ) +{ + ASSERT( pAdd!=NULL ); + ASSERT( !pAdd->InSquad() ); + ASSERT( this->IsLeader() ); + + for (int i = 0; i < MAX_SQUAD_MEMBERS-1; i++) + { + if (m_hSquadMember[i] == NULL) + { + m_hSquadMember[i] = pAdd; + pAdd->m_hSquadLeader = this; + return TRUE; + } + } + return FALSE; + // should complain here +} + + +//========================================================= +// +// SquadPasteEnemyInfo - called by squad members that have +// current info on the enemy so that it can be stored for +// members who don't have current info. +// +//========================================================= +void CSquadMonster :: SquadPasteEnemyInfo ( void ) +{ + CSquadMonster *pSquadLeader = MySquadLeader( ); + if (pSquadLeader) + pSquadLeader->m_vecEnemyLKP = m_vecEnemyLKP; +} + +//========================================================= +// +// SquadCopyEnemyInfo - called by squad members who don't +// have current info on the enemy. Reads from the same fields +// in the leader's data that other squad members write to, +// so the most recent data is always available here. +// +//========================================================= +void CSquadMonster :: SquadCopyEnemyInfo ( void ) +{ + CSquadMonster *pSquadLeader = MySquadLeader( ); + if (pSquadLeader) + m_vecEnemyLKP = pSquadLeader->m_vecEnemyLKP; +} + +//========================================================= +// +// SquadMakeEnemy - makes everyone in the squad angry at +// the same entity. +// +//========================================================= +void CSquadMonster :: SquadMakeEnemy ( CBaseEntity *pEnemy ) +{ + if (!InSquad()) + return; + + if ( !pEnemy ) + { + ALERT ( at_debug, "ERROR: SquadMakeEnemy() - pEnemy is NULL!\n" ); + return; + } + + CSquadMonster *pSquadLeader = MySquadLeader( ); + for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) + { + CSquadMonster *pMember = pSquadLeader->MySquadMember(i); + if (pMember) + { + // reset members who aren't activly engaged in fighting + if (pMember->m_hEnemy != pEnemy && !pMember->HasConditions( bits_COND_SEE_ENEMY)) + { + if ( pMember->m_hEnemy != NULL) + { + // remember their current enemy + pMember->PushEnemy( pMember->m_hEnemy, pMember->m_vecEnemyLKP ); + } + // give them a new enemy + pMember->m_hEnemy = pEnemy; + pMember->m_vecEnemyLKP = pEnemy->pev->origin; + pMember->SetConditions ( bits_COND_NEW_ENEMY ); + } + } + } +} + + +//========================================================= +// +// SquadCount(), return the number of members of this squad +// callable from leaders & followers +// +//========================================================= +int CSquadMonster :: SquadCount( void ) +{ + if (!InSquad()) + return 0; + + CSquadMonster *pSquadLeader = MySquadLeader(); + int squadCount = 0; + for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) + { + if (pSquadLeader->MySquadMember(i) != NULL) + squadCount++; + } + + return squadCount; +} + + +//========================================================= +// +// SquadRecruit(), get some monsters of my classification and +// link them as a group. returns the group size +// +//========================================================= +int CSquadMonster :: SquadRecruit( int searchRadius, int maxMembers ) +{ + int squadCount; + int iMyClass = Classify();// cache this monster's class + + + // Don't recruit if I'm already in a group + if ( InSquad() ) + return 0; + + if ( maxMembers < 2 ) + return 0; + + // I am my own leader + m_hSquadLeader = this; + squadCount = 1; + + CBaseEntity *pEntity = NULL; + + if ( !FStringNull( pev->netname ) ) + { + // I have a netname, so unconditionally recruit everyone else with that name. + pEntity = UTIL_FindEntityByString( pEntity, "netname", STRING( pev->netname ) ); + while ( pEntity ) + { + CSquadMonster *pRecruit = pEntity->MySquadMonsterPointer(); + + if ( pRecruit ) + { + if ( !pRecruit->InSquad() && pRecruit->Classify() == iMyClass && pRecruit != this ) + { + // minimum protection here against user error.in worldcraft. + if (!SquadAdd( pRecruit )) + break; + squadCount++; + } + } + + pEntity = UTIL_FindEntityByString( pEntity, "netname", STRING( pev->netname ) ); + } + } + else + { + while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, searchRadius )) != NULL) + { + CSquadMonster *pRecruit = pEntity->MySquadMonsterPointer( ); + + if ( pRecruit && pRecruit != this && pRecruit->IsAlive() && !pRecruit->m_pCine ) + { + // Can we recruit this guy? + if ( !pRecruit->InSquad() && pRecruit->Classify() == iMyClass && + ( (iMyClass != CLASS_ALIEN_MONSTER) || FStrEq(STRING(pev->classname), STRING(pRecruit->pev->classname))) && + FStringNull( pRecruit->pev->netname ) ) + { + TraceResult tr; + UTIL_TraceLine( pev->origin + pev->view_ofs, pRecruit->pev->origin + pev->view_ofs, ignore_monsters, pRecruit->edict(), &tr );// try to hit recruit with a traceline. + if ( tr.flFraction == 1.0 ) + { + if (!SquadAdd( pRecruit )) + break; + + squadCount++; + } + } + } + } + } + + // no single member squads + if (squadCount == 1) + { + m_hSquadLeader = NULL; + } + + return squadCount; +} + +//========================================================= +// CheckEnemy +//========================================================= +int CSquadMonster :: CheckEnemy ( CBaseEntity *pEnemy ) +{ + int iUpdatedLKP; + + iUpdatedLKP = CBaseMonster :: CheckEnemy ( m_hEnemy ); + + // communicate with squad members about the enemy IF this individual has the same enemy as the squad leader. + if ( InSquad() && (CBaseEntity *)m_hEnemy == MySquadLeader()->m_hEnemy ) + { + if ( iUpdatedLKP ) + { + // have new enemy information, so paste to the squad. + SquadPasteEnemyInfo(); + } + else + { + // enemy unseen, copy from the squad knowledge. + SquadCopyEnemyInfo(); + } + } + + return iUpdatedLKP; +} + +//========================================================= +// StartMonster +//========================================================= +void CSquadMonster :: StartMonster( void ) +{ + CBaseMonster :: StartMonster(); + + if ( ( m_afCapability & bits_CAP_SQUAD ) && !InSquad() ) + { + if ( !FStringNull( pev->netname ) ) + { + // if I have a groupname, I can only recruit if I'm flagged as leader + if ( !( pev->spawnflags & SF_SQUADMONSTER_LEADER ) ) + { + return; + } + } + + // try to form squads now. + int iSquadSize = SquadRecruit( 1024, 4 ); + + if ( iSquadSize ) + { + ALERT ( at_aiconsole, "Squad of %d %s formed\n", iSquadSize, STRING( pev->classname ) ); + } + + /* if ( IsLeader() && FClassnameIs ( pev, "monster_human_grunt" ) ) + { + SetBodygroup( 1, 1 ); // UNDONE: truly ugly hack + pev->skin = 0; + }*/ // buz: remove this hack + + } +} + +BOOL CSquadMonster :: NoFriendlyFire( void ) +{ + return NoFriendlyFire( FALSE ); //default: don't like the player +} + +//========================================================= +// NoFriendlyFire - checks for possibility of friendly fire +// +// Builds a large box in front of the grunt and checks to see +// if any squad members are in that box. +// +// Can now, also, check whether the player is in the box. LRC +//========================================================= +BOOL CSquadMonster :: NoFriendlyFire( BOOL playerAlly ) +{ + if ( !playerAlly && !InSquad() ) + { + return TRUE; + } + + CPlane backPlane; + CPlane leftPlane; + CPlane rightPlane; + + Vector vecLeftSide; + Vector vecRightSide; + Vector v_left; + + //!!!BUGBUG - to fix this, the planes must be aligned to where the monster will be firing its gun, not the direction it is facing!!! + + if ( m_hEnemy != NULL ) + { + UTIL_MakeVectors ( UTIL_VecToAngles( m_hEnemy->Center() - pev->origin ) ); + } + else + { + // if there's no enemy, pretend there's a friendly in the way, so the grunt won't shoot. + return FALSE; + } + + //UTIL_MakeVectors ( pev->angles ); + + vecLeftSide = pev->origin - ( gpGlobals->v_right * ( pev->size.x * 1.5 ) ); + vecRightSide = pev->origin + ( gpGlobals->v_right * ( pev->size.x * 1.5 ) ); + v_left = gpGlobals->v_right * -1; + + leftPlane.InitializePlane ( gpGlobals->v_right, vecLeftSide ); + rightPlane.InitializePlane ( v_left, vecRightSide ); + backPlane.InitializePlane ( gpGlobals->v_forward, pev->origin ); + +/* + ALERT ( at_console, "LeftPlane: %f %f %f : %f\n", leftPlane.m_vecNormal.x, leftPlane.m_vecNormal.y, leftPlane.m_vecNormal.z, leftPlane.m_flDist ); + ALERT ( at_console, "RightPlane: %f %f %f : %f\n", rightPlane.m_vecNormal.x, rightPlane.m_vecNormal.y, rightPlane.m_vecNormal.z, rightPlane.m_flDist ); + ALERT ( at_console, "BackPlane: %f %f %f : %f\n", backPlane.m_vecNormal.x, backPlane.m_vecNormal.y, backPlane.m_vecNormal.z, backPlane.m_flDist ); +*/ + + CSquadMonster *pSquadLeader = MySquadLeader(); + for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) + { + CSquadMonster *pMember = pSquadLeader->MySquadMember(i); + if (pMember && pMember != this) + { + + if ( backPlane.PointInFront ( pMember->pev->origin ) && + leftPlane.PointInFront ( pMember->pev->origin ) && + rightPlane.PointInFront ( pMember->pev->origin) ) + { + // this guy is in the check volume! Don't shoot! + return FALSE; + } + } + } + + if (playerAlly) + { + edict_t *pentPlayer = FIND_CLIENT_IN_PVS( edict() ); + if (!FNullEnt(pentPlayer) && + backPlane.PointInFront ( pentPlayer->v.origin ) && + leftPlane.PointInFront ( pentPlayer->v.origin ) && + rightPlane.PointInFront ( pentPlayer->v.origin ) ) + { + // the player is in the check volume! Don't shoot! + return FALSE; + } + } + + return TRUE; +} + +//========================================================= +// GetIdealState - surveys the Conditions information available +// and finds the best new state for a monster. +//========================================================= +MONSTERSTATE CSquadMonster :: GetIdealState ( void ) +{ + int iConditions; + + iConditions = IScheduleFlags(); + + // If no schedule conditions, the new ideal state is probably the reason we're in here. + switch ( m_MonsterState ) + { + case MONSTERSTATE_IDLE: + case MONSTERSTATE_ALERT: + if ( HasConditions ( bits_COND_NEW_ENEMY ) && InSquad() ) + { + SquadMakeEnemy ( m_hEnemy ); + } + break; + } + + return CBaseMonster :: GetIdealState(); +} + +//========================================================= +// FValidateCover - determines whether or not the chosen +// cover location is a good one to move to. (currently based +// on proximity to others in the squad) +//========================================================= +BOOL CSquadMonster :: FValidateCover ( const Vector &vecCoverLocation ) +{ + if ( !InSquad() ) + { + return TRUE; + } + + if (SquadMemberInRange( vecCoverLocation, 128 )) + { + // another squad member is too close to this piece of cover. + return FALSE; + } + + return TRUE; +} + +//========================================================= +// SquadEnemySplit- returns TRUE if not all squad members +// are fighting the same enemy. +//========================================================= +BOOL CSquadMonster :: SquadEnemySplit ( void ) +{ + if (!InSquad()) + return FALSE; + + CSquadMonster *pSquadLeader = MySquadLeader(); + CBaseEntity *pEnemy = pSquadLeader->m_hEnemy; + + for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) + { + CSquadMonster *pMember = pSquadLeader->MySquadMember(i); + if (pMember != NULL && pMember->m_hEnemy != NULL && pMember->m_hEnemy != pEnemy) + { + return TRUE; + } + } + return FALSE; +} + +//========================================================= +// FValidateCover - determines whether or not the chosen +// cover location is a good one to move to. (currently based +// on proximity to others in the squad) +//========================================================= +BOOL CSquadMonster :: SquadMemberInRange ( const Vector &vecLocation, float flDist ) +{ + if (!InSquad()) + return FALSE; + + CSquadMonster *pSquadLeader = MySquadLeader(); + + for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) + { + CSquadMonster *pSquadMember = pSquadLeader->MySquadMember(i); + if (pSquadMember && (vecLocation - pSquadMember->pev->origin ).Length2D() <= flDist) + return TRUE; + } + return FALSE; +} + + +extern Schedule_t slChaseEnemyFailed[]; + +Schedule_t *CSquadMonster::GetScheduleOfType( int iType ) +{ + switch ( iType ) + { + + case SCHED_CHASE_ENEMY_FAILED: + { + return &slChaseEnemyFailed[ 0 ]; + } + + default: + return CBaseMonster::GetScheduleOfType( iType ); + } +} + diff --git a/dlls/squadmonster.h b/dlls/squadmonster.h new file mode 100644 index 0000000..8519bdd --- /dev/null +++ b/dlls/squadmonster.h @@ -0,0 +1,121 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// CSquadMonster - all the extra data for monsters that +// form squads. +//========================================================= + +#define SF_SQUADMONSTER_LEADER 32 + + +#define bits_NO_SLOT 0 + +// HUMAN GRUNT SLOTS +#define bits_SLOT_HGRUNT_ENGAGE1 ( 1 << 0 ) +#define bits_SLOT_HGRUNT_ENGAGE2 ( 1 << 1 ) +#define bits_SLOTS_HGRUNT_ENGAGE ( bits_SLOT_HGRUNT_ENGAGE1 | bits_SLOT_HGRUNT_ENGAGE2 ) + +#define bits_SLOT_HGRUNT_GRENADE1 ( 1 << 2 ) +#define bits_SLOT_HGRUNT_GRENADE2 ( 1 << 3 ) +#define bits_SLOTS_HGRUNT_GRENADE ( bits_SLOT_HGRUNT_GRENADE1 | bits_SLOT_HGRUNT_GRENADE2 ) + +// ALIEN GRUNT SLOTS +#define bits_SLOT_AGRUNT_HORNET1 ( 1 << 4 ) +#define bits_SLOT_AGRUNT_HORNET2 ( 1 << 5 ) +#define bits_SLOT_AGRUNT_CHASE ( 1 << 6 ) +#define bits_SLOTS_AGRUNT_HORNET ( bits_SLOT_AGRUNT_HORNET1 | bits_SLOT_AGRUNT_HORNET2 ) + +// HOUNDEYE SLOTS +#define bits_SLOT_HOUND_ATTACK1 ( 1 << 7 ) +#define bits_SLOT_HOUND_ATTACK2 ( 1 << 8 ) +#define bits_SLOT_HOUND_ATTACK3 ( 1 << 9 ) +#define bits_SLOTS_HOUND_ATTACK ( bits_SLOT_HOUND_ATTACK1 | bits_SLOT_HOUND_ATTACK2 | bits_SLOT_HOUND_ATTACK3 ) + +// global slots +#define bits_SLOT_SQUAD_SPLIT ( 1 << 10 )// squad members don't all have the same enemy + +#define NUM_SLOTS 11// update this every time you add/remove a slot. + +#define MAX_SQUAD_MEMBERS 5 + +//========================================================= +// CSquadMonster - for any monster that forms squads. +//========================================================= +class CSquadMonster : public CBaseMonster +{ +public: + // squad leader info + EHANDLE m_hSquadLeader; // who is my leader + EHANDLE m_hSquadMember[MAX_SQUAD_MEMBERS-1]; // valid only for leader + int m_afSquadSlots; + float m_flLastEnemySightTime; // last time anyone in the squad saw the enemy + BOOL m_fEnemyEluded; + + // squad member info + int m_iMySlot;// this is the behaviour slot that the monster currently holds in the squad. + + int CheckEnemy ( CBaseEntity *pEnemy ); + void StartMonster ( void ); + void VacateSlot( void ); + void ScheduleChange( void ); + void Killed( entvars_t *pevAttacker, int iGib ); + BOOL OccupySlot( int iDesiredSlot ); + BOOL NoFriendlyFire( void ); + BOOL NoFriendlyFire( BOOL playerAlly ); + + // squad functions still left in base class + CSquadMonster *MySquadLeader( ) + { + CSquadMonster *pSquadLeader = (CSquadMonster *)((CBaseEntity *)m_hSquadLeader); + if (pSquadLeader != NULL) + return pSquadLeader; + return this; + } + CSquadMonster *MySquadMember( int i ) + { + if (i >= MAX_SQUAD_MEMBERS-1) + return this; + else + return (CSquadMonster *)((CBaseEntity *)m_hSquadMember[i]); + } + int InSquad ( void ) { return m_hSquadLeader != NULL; } + int IsLeader ( void ) { return m_hSquadLeader == this; } + int SquadJoin ( int searchRadius ); + int SquadRecruit ( int searchRadius, int maxMembers ); + int SquadCount( void ); + void SquadRemove( CSquadMonster *pRemove ); + void SquadUnlink( void ); + BOOL SquadAdd( CSquadMonster *pAdd ); + void SquadDisband( void ); + void SquadAddConditions ( int iConditions ); + void SquadMakeEnemy ( CBaseEntity *pEnemy ); + void SquadPasteEnemyInfo ( void ); + void SquadCopyEnemyInfo ( void ); + BOOL SquadEnemySplit ( void ); + BOOL SquadMemberInRange( const Vector &vecLocation, float flDist ); + + virtual CSquadMonster *MySquadMonsterPointer( void ) { return this; } + + static TYPEDESCRIPTION m_SaveData[]; + + int Save( CSave &save ); + int Restore( CRestore &restore ); + + BOOL FValidateCover ( const Vector &vecCoverLocation ); + + MONSTERSTATE GetIdealState ( void ); + Schedule_t *GetScheduleOfType ( int iType ); +}; + diff --git a/dlls/squeakgrenade.cpp b/dlls/squeakgrenade.cpp new file mode 100644 index 0000000..3f2bb08 --- /dev/null +++ b/dlls/squeakgrenade.cpp @@ -0,0 +1,399 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "soundent.h" +#include "gamerules.h" + +enum w_squeak_e { + WSQUEAK_IDLE1 = 0, + WSQUEAK_FIDGET, + WSQUEAK_JUMP, + WSQUEAK_RUN, +}; + +class CSqueakGrenade : public CGrenade +{ + void Spawn( void ); + void Precache( void ); + int Classify( void ); + void EXPORT SuperBounceTouch( CBaseEntity *pOther ); + void EXPORT HuntThink( void ); + int BloodColor( void ) { return BLOOD_COLOR_YELLOW; } + void Killed( entvars_t *pevAttacker, int iGib ); + void GibMonster( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + static float m_flNextBounceSoundTime; + + // CBaseEntity *m_pTarget; + float m_flDie; + Vector m_vecTarget; + float m_flNextHunt; + float m_flNextHit; + Vector m_posPrev; + EHANDLE m_hOwner; + int m_iMyClass; +}; + +float CSqueakGrenade::m_flNextBounceSoundTime = 0; + +LINK_ENTITY_TO_CLASS( monster_snark, CSqueakGrenade ); +TYPEDESCRIPTION CSqueakGrenade::m_SaveData[] = +{ + DEFINE_FIELD( CSqueakGrenade, m_flDie, FIELD_TIME ), + DEFINE_FIELD( CSqueakGrenade, m_vecTarget, FIELD_VECTOR ), + DEFINE_FIELD( CSqueakGrenade, m_flNextHunt, FIELD_TIME ), + DEFINE_FIELD( CSqueakGrenade, m_flNextHit, FIELD_TIME ), + DEFINE_FIELD( CSqueakGrenade, m_posPrev, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CSqueakGrenade, m_hOwner, FIELD_EHANDLE ), +}; + +IMPLEMENT_SAVERESTORE( CSqueakGrenade, CGrenade ); + +#define SQUEEK_DETONATE_DELAY 15.0 + +int CSqueakGrenade :: Classify ( void ) +{ + if (m_iClass) return m_iClass; + + if (m_iMyClass != 0) + return m_iMyClass; // protect against recursion + + if (m_hEnemy != NULL) + { + m_iMyClass = CLASS_INSECT; // no one cares about it + switch( m_hEnemy->Classify( ) ) + { + case CLASS_PLAYER: + case CLASS_HUMAN_PASSIVE: + case CLASS_HUMAN_MILITARY: + m_iMyClass = 0; + return CLASS_ALIEN_MILITARY; // barney's get mad, grunts get mad at it + } + m_iMyClass = 0; + } + + return CLASS_ALIEN_BIOWEAPON; +} + +void CSqueakGrenade :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_BOUNCE; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/w_squeak.mdl"); + UTIL_SetSize(pev, Vector( -4, -4, 0), Vector(4, 4, 8)); + UTIL_SetOrigin( this, pev->origin ); + + SetTouch(&CSqueakGrenade :: SuperBounceTouch ); + SetThink(&CSqueakGrenade :: HuntThink ); + SetNextThink( 0.1 ); + m_flNextHunt = gpGlobals->time + 1E6; + + pev->flags |= FL_MONSTER; + pev->takedamage = DAMAGE_AIM; + if (pev->health == 0) + pev->health = gSkillData.snarkHealth; + pev->gravity = 0.5; + pev->friction = 0.5; + + pev->dmg = gSkillData.snarkDmgPop; + + m_flDie = gpGlobals->time + SQUEEK_DETONATE_DELAY; + + m_flFieldOfView = 0; // 180 degrees + + if ( pev->owner ) + m_hOwner = Instance( pev->owner ); + + m_flNextBounceSoundTime = gpGlobals->time;// reset each time a snark is spawned. + + pev->sequence = WSQUEAK_RUN; + ResetSequenceInfo( ); +} + +void CSqueakGrenade::Precache( void ) +{ + PRECACHE_MODEL("models/w_squeak.mdl"); + PRECACHE_SOUND("squeek/sqk_blast1.wav"); + PRECACHE_SOUND("common/bodysplat.wav"); + PRECACHE_SOUND("squeek/sqk_die1.wav"); + PRECACHE_SOUND("squeek/sqk_hunt1.wav"); + PRECACHE_SOUND("squeek/sqk_hunt2.wav"); + PRECACHE_SOUND("squeek/sqk_hunt3.wav"); + PRECACHE_SOUND("squeek/sqk_deploy1.wav"); +} + + +void CSqueakGrenade :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->model = iStringNull;// make invisible + SetThink(&CSqueakGrenade :: SUB_Remove ); + SetTouch( NULL ); + SetNextThink( 0.1 ); + + // since squeak grenades never leave a body behind, clear out their takedamage now. + // Squeaks do a bit of radius damage when they pop, and that radius damage will + // continue to call this function unless we acknowledge the Squeak's death now. (sjb) + pev->takedamage = DAMAGE_NO; + + // play squeek blast + EMIT_SOUND_DYN(ENT(pev), CHAN_ITEM, "squeek/sqk_blast1.wav", 1, 0.5, 0, PITCH_NORM); + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, SMALL_EXPLOSION_VOLUME, 3.0 ); + + UTIL_BloodDrips( pev->origin, g_vecZero, BloodColor(), 80 ); + + if (m_hOwner != NULL) + RadiusDamage ( pev, m_hOwner->pev, pev->dmg, CLASS_NONE, DMG_BLAST ); + else + RadiusDamage ( pev, pev, pev->dmg, CLASS_NONE, DMG_BLAST ); + + // reset owner so death message happens + if (m_hOwner != NULL) + pev->owner = m_hOwner->edict(); + + CBaseMonster :: Killed( pevAttacker, GIB_ALWAYS ); +} + +void CSqueakGrenade :: GibMonster( void ) +{ + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "common/bodysplat.wav", 0.75, ATTN_NORM, 0, 200); +} + + + +void CSqueakGrenade::HuntThink( void ) +{ + // ALERT( at_console, "think\n" ); + + if (!IsInWorld()) + { + SetTouch( NULL ); + UTIL_Remove( this ); + return; + } + + StudioFrameAdvance( ); + SetNextThink( 0.1 ); + + // explode when ready + if (gpGlobals->time >= m_flDie) + { + g_vecAttackDir = pev->velocity.Normalize( ); + pev->health = -1; + Killed( pev, 0 ); + return; + } + + // float + if (pev->waterlevel != 0 && pev->watertype != CONTENTS_FOG) + { + if (pev->movetype == MOVETYPE_BOUNCE) + { + pev->movetype = MOVETYPE_FLY; + } + pev->velocity = pev->velocity * 0.9; + pev->velocity.z += 8.0; + } + else if (pev->movetype = MOVETYPE_FLY) + { + pev->movetype = MOVETYPE_BOUNCE; + } + + // return if not time to hunt + if (m_flNextHunt > gpGlobals->time) + return; + + m_flNextHunt = gpGlobals->time + 2.0; + + CBaseEntity *pOther = NULL; + Vector vecDir; + TraceResult tr; + + Vector vecFlat = pev->velocity; + vecFlat.z = 0; + vecFlat = vecFlat.Normalize( ); + + UTIL_MakeVectors( pev->angles ); + + if (m_hEnemy == NULL || !m_hEnemy->IsAlive()) + { + // find target, bounce a bit towards it. + Look( 512 ); + m_hEnemy = BestVisibleEnemy( ); + } + + // squeek if it's about time blow up + if ((m_flDie - gpGlobals->time <= 0.5) && (m_flDie - gpGlobals->time >= 0.3)) + { + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "squeek/sqk_die1.wav", 1, ATTN_NORM, 0, 100 + RANDOM_LONG(0,0x3F)); + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 256, 0.25 ); + } + + // higher pitch as squeeker gets closer to detonation time + float flpitch = 155.0 - 60.0 * ((m_flDie - gpGlobals->time) / SQUEEK_DETONATE_DELAY); + if (flpitch < 80) + flpitch = 80; + + if (m_hEnemy != NULL) + { + if (FVisible( m_hEnemy )) + { + vecDir = m_hEnemy->EyePosition() - pev->origin; + m_vecTarget = vecDir.Normalize( ); + } + + float flVel = pev->velocity.Length(); + float flAdj = 50.0 / (flVel + 10.0); + + if (flAdj > 1.2) + flAdj = 1.2; + + // ALERT( at_console, "think : enemy\n"); + + // ALERT( at_console, "%.0f %.2f %.2f %.2f\n", flVel, m_vecTarget.x, m_vecTarget.y, m_vecTarget.z ); + + pev->velocity = pev->velocity * flAdj + m_vecTarget * 300; + } + + if (pev->flags & FL_ONGROUND) + { + pev->avelocity = Vector( 0, 0, 0 ); + } + else + { + if (pev->avelocity == Vector( 0, 0, 0)) + { + pev->avelocity.x = RANDOM_FLOAT( -100, 100 ); + pev->avelocity.z = RANDOM_FLOAT( -100, 100 ); + } + } + + if ((pev->origin - m_posPrev).Length() < 1.0) + { + pev->velocity.x = RANDOM_FLOAT( -100, 100 ); + pev->velocity.y = RANDOM_FLOAT( -100, 100 ); + } + m_posPrev = pev->origin; + + pev->angles = UTIL_VecToAngles( pev->velocity ); + pev->angles.z = 0; + pev->angles.x = 0; +} + + +void CSqueakGrenade::SuperBounceTouch( CBaseEntity *pOther ) +{ + float flpitch; + + TraceResult tr = UTIL_GetGlobalTrace( ); + + // don't hit the guy that launched this grenade + if ( pev->owner && pOther->edict() == pev->owner ) + return; + + // at least until we've bounced once + pev->owner = NULL; + + pev->angles.x = 0; + pev->angles.z = 0; + + // avoid bouncing too much + if (m_flNextHit > gpGlobals->time) + return; + + // higher pitch as squeeker gets closer to detonation time + flpitch = 155.0 - 60.0 * ((m_flDie - gpGlobals->time) / SQUEEK_DETONATE_DELAY); + + if ( pOther->pev->takedamage && m_flNextAttack < gpGlobals->time ) + { + // attack! + + // make sure it's me who has touched them + if (tr.pHit == pOther->edict()) + { + // and it's not another squeakgrenade + if (tr.pHit->v.modelindex != pev->modelindex) + { + // ALERT( at_console, "hit enemy\n"); + ClearMultiDamage( ); + pOther->TraceAttack(pev, gSkillData.snarkDmgBite, gpGlobals->v_forward, &tr, DMG_SLASH ); + if (m_hOwner != NULL) + ApplyMultiDamage( pev, m_hOwner->pev ); + else + ApplyMultiDamage( pev, pev ); + + pev->dmg += gSkillData.snarkDmgPop; // add more explosion damage + // m_flDie += 2.0; // add more life + + // make bite sound + EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, "squeek/sqk_deploy1.wav", 1.0, ATTN_NORM, 0, (int)flpitch); + m_flNextAttack = gpGlobals->time + 0.5; + } + } + else + { + // ALERT( at_console, "been hit\n"); + } + } + + m_flNextHit = gpGlobals->time + 0.1; + m_flNextHunt = gpGlobals->time; + + if ( g_pGameRules->IsMultiplayer() ) + { + // in multiplayer, we limit how often snarks can make their bounce sounds to prevent overflows. + if ( gpGlobals->time < m_flNextBounceSoundTime ) + { + // too soon! + return; + } + } + + if (!(pev->flags & FL_ONGROUND)) + { + // play bounce sound + float flRndSound = RANDOM_FLOAT ( 0 , 1 ); + + if ( flRndSound <= 0.33 ) + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "squeek/sqk_hunt1.wav", 1, ATTN_NORM, 0, (int)flpitch); + else if (flRndSound <= 0.66) + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "squeek/sqk_hunt2.wav", 1, ATTN_NORM, 0, (int)flpitch); + else + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "squeek/sqk_hunt3.wav", 1, ATTN_NORM, 0, (int)flpitch); + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 256, 0.25 ); + } + else + { + // skittering sound + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 100, 0.1 ); + } + + m_flNextBounceSoundTime = gpGlobals->time + 0.5;// half second. +} \ No newline at end of file diff --git a/dlls/stats.cpp b/dlls/stats.cpp new file mode 100644 index 0000000..038d7d3 --- /dev/null +++ b/dlls/stats.cpp @@ -0,0 +1,146 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: New version of the slider bar +// +// $NoKeywords: $ +//============================================================================= + +#include "extdll.h" +#include "util.h" + +#include "cbase.h" +#include "player.h" +#include "trains.h" +#include "nodes.h" +#include "weapons.h" +#include "soundent.h" +#include "monsters.h" +//#include "..\engine\shake.h" +#include "shake.h" +#include "decals.h" +#include "gamerules.h" + + +float AmmoDamage( const char *pName ) +{ + if ( !pName ) + return 0; + + if ( !strcmp( pName, "9mm" ) ) + return gSkillData.plrDmg9MM; + if ( !strcmp( pName, "ARgrenades" ) ) + return gSkillData.plrDmgM203Grenade; + if ( !strcmp( pName, "buckshot" ) ) + return gSkillData.plrDmgBuckshot; + if ( !strcmp( pName, "rockets") ) + return gSkillData.plrDmgRPG; + if ( !strcmp( pName, "Hand Grenade") ) + return gSkillData.plrDmgHandGrenade; + return 0; +} + + +void UpdateStatsFile( float dataTime, char *pMapname, float health, float ammo, int skillLevel ) +{ + FILE *fp; + + fp = fopen( "stats.txt", "a" ); + if ( !fp ) + return; + fprintf( fp, "%6.2f, %6.2f, %6.2f, %s, %2d\n", dataTime, health, ammo, pMapname, skillLevel ); + fclose( fp ); +} + + +#define AMMO_THRESHOLD 10 // This much ammo goes by before it is "interesting" +#define HEALTH_THRESHOLD 10 // Same for health +#define OUTPUT_LATENCY 3 // This many seconds for ammo/health to settle + +typedef struct +{ + int lastAmmo; + float lastHealth; + float lastOutputTime; // NOTE: These times are in "game" time -- a running total of elapsed time since the game started + float nextOutputTime; + float dataTime; + float gameTime; + float lastGameTime; +} TESTSTATS; + +TESTSTATS gStats = {0,0,0,0,0,0,0}; + +void UpdateStats( CBasePlayer *pPlayer ) +{ + int i; + + int ammoCount[ MAX_AMMO_SLOTS ]; + memcpy( ammoCount, pPlayer->m_rgAmmo, MAX_AMMO_SLOTS * sizeof(int) ); + + // Keep a running time, so the graph doesn't overlap + + if ( gpGlobals->time < gStats.lastGameTime ) // Changed level or died, don't b0rk + { + gStats.lastGameTime = gpGlobals->time; + gStats.dataTime = gStats.gameTime; + } + + gStats.gameTime += gpGlobals->time - gStats.lastGameTime; + gStats.lastGameTime = gpGlobals->time; + + for (i = 0; i < MAX_ITEM_TYPES; i++) + { + CBasePlayerItem *p = pPlayer->m_rgpPlayerItems[i]; + while (p) + { + ItemInfo II; + + memset(&II, 0, sizeof(II)); + p->GetItemInfo(&II); + + int index = pPlayer->GetAmmoIndex(II.pszAmmo1); + if ( index >= 0 ) + ammoCount[ index ] += p->m_iClip; + + p = p->m_pNext; + } + } + + float ammo = 0; + for (i = 1; i < MAX_AMMO_SLOTS; i++) + { + ammo += ammoCount[i] * AmmoDamage( CBasePlayerItem::AmmoInfoArray[i].pszName ); + } + + float health = pPlayer->pev->health + pPlayer->pev->armorvalue * 2; // Armor is 2X health + float ammoDelta = fabs( ammo - gStats.lastAmmo ); + float healthDelta = fabs( health - gStats.lastHealth ); + int forceWrite = 0; + if ( health <= 0 && gStats.lastHealth > 0 ) + forceWrite = 1; + + if ( (ammoDelta > AMMO_THRESHOLD || healthDelta > HEALTH_THRESHOLD) && !forceWrite ) + { + if ( gStats.nextOutputTime == 0 ) + gStats.dataTime = gStats.gameTime; + + gStats.lastAmmo = ammo; + gStats.lastHealth = health; + + gStats.nextOutputTime = gStats.gameTime + OUTPUT_LATENCY; + } + else if ( (gStats.nextOutputTime != 0 && gStats.nextOutputTime < gStats.gameTime) || forceWrite ) + { + UpdateStatsFile( gStats.dataTime, (char *)STRING(gpGlobals->mapname), health, ammo, (int)CVAR_GET_FLOAT("skill") ); + + gStats.lastAmmo = ammo; + gStats.lastHealth = health; + gStats.lastOutputTime = gStats.gameTime; + gStats.nextOutputTime = 0; + } +} + +void InitStats( CBasePlayer *pPlayer ) +{ + gStats.lastGameTime = gpGlobals->time; // Fixup stats time +} + diff --git a/dlls/subs.cpp b/dlls/subs.cpp new file mode 100644 index 0000000..f73b6a4 --- /dev/null +++ b/dlls/subs.cpp @@ -0,0 +1,936 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== subs.cpp ======================================================== + + frequently used global functions + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "nodes.h" +#include "doors.h" +#include "movewith.h" +#include "player.h" + +extern CGraph WorldGraph; +extern int gmsgDelParticle; + +extern BOOL FEntIsVisible(entvars_t* pev, entvars_t* pevTarget); + +extern DLL_GLOBAL int g_iSkillLevel; + +// Landmark class +void CPointEntity :: Spawn( void ) +{ + pev->solid = SOLID_NOT; +} + +class CNullEntity : public CBaseEntity +{ +public: + void Spawn( void ); +}; + + +// Null Entity, remove on startup +void CNullEntity :: Spawn( void ) +{ + REMOVE_ENTITY(ENT(pev)); +} +LINK_ENTITY_TO_CLASS(info_null,CNullEntity); +LINK_ENTITY_TO_CLASS(info_texlights,CNullEntity); // don't complain about Merl's new info entities +LINK_ENTITY_TO_CLASS(info_compile_parameters,CNullEntity); + +class CBaseDMStart : public CPointEntity +{ +public: + void KeyValue( KeyValueData *pkvd ); + STATE GetState( CBaseEntity *pEntity ); + +private: +}; + +// These are the new entry points to entities. +LINK_ENTITY_TO_CLASS(info_player_deathmatch,CBaseDMStart); +LINK_ENTITY_TO_CLASS(info_player_start,CPointEntity); +LINK_ENTITY_TO_CLASS(info_landmark,CPointEntity); + +void CBaseDMStart::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "master")) + { + pev->netname = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +STATE CBaseDMStart::GetState( CBaseEntity *pEntity ) +{ + if (UTIL_IsMasterTriggered( pev->netname, pEntity )) + return STATE_ON; + else + return STATE_OFF; +} + +// This updates global tables that need to know about entities being removed +void CBaseEntity::UpdateOnRemove( void ) +{ + int i; + CBaseEntity* pTemp; + + if( pev->modelindex ) + { + // remove all the particle trails, attached to this entity + MESSAGE_BEGIN( MSG_ALL, gmsgDelParticle ); + WRITE_ENTITY( entindex() ); + MESSAGE_END(); + } + + if (!g_pWorld) + { + ALERT(at_debug, "UpdateOnRemove has no AssistList!\n"); + return; + } + + //LRC - remove this from the AssistList. + for (pTemp = g_pWorld; pTemp->m_pAssistLink != NULL; pTemp = pTemp->m_pAssistLink) + { + if (this == pTemp->m_pAssistLink) + { +// ALERT(at_console,"REMOVE: %s removed from the Assist List.\n", STRING(pev->classname)); + pTemp->m_pAssistLink = this->m_pAssistLink; + this->m_pAssistLink = NULL; + break; + } + } + + //LRC + if (m_pMoveWith) + { + // if I'm moving with another entity, take me out of the list. (otherwise things crash!) + pTemp = m_pMoveWith->m_pChildMoveWith; + if (pTemp == this) + { + m_pMoveWith->m_pChildMoveWith = this->m_pSiblingMoveWith; + } + else + { + while (pTemp->m_pSiblingMoveWith) + { + if (pTemp->m_pSiblingMoveWith == this) + { + pTemp->m_pSiblingMoveWith = this->m_pSiblingMoveWith; + break; + } + pTemp = pTemp->m_pSiblingMoveWith; + } + + } +// ALERT(at_console,"REMOVE: %s removed from the %s ChildMoveWith list.\n", STRING(pev->classname), STRING(m_pMoveWith->pev->targetname)); + } + + //LRC - do the same thing if another entity is moving with _me_. + if (m_pChildMoveWith) + { + CBaseEntity* pCur = m_pChildMoveWith; + CBaseEntity* pNext; + while (pCur != NULL) + { + pNext = pCur->m_pSiblingMoveWith; + // bring children to a stop + UTIL_SetMoveWithVelocity(pCur, g_vecZero, 100); + UTIL_SetMoveWithAvelocity(pCur, g_vecZero, 100); + pCur->m_pMoveWith = NULL; + pCur->m_pSiblingMoveWith = NULL; + pCur = pNext; + } + } + + if ( FBitSet( pev->flags, FL_GRAPHED ) ) + { + // this entity was a LinkEnt in the world node graph, so we must remove it from + // the graph since we are removing it from the world. + for ( i = 0 ; i < WorldGraph.m_cLinks ; i++ ) + { + if ( WorldGraph.m_pLinkPool [ i ].m_pLinkEnt == pev ) + { + // if this link has a link ent which is the same ent that is removing itself, remove it! + WorldGraph.m_pLinkPool [ i ].m_pLinkEnt = NULL; + } + } + } + if ( pev->globalname ) + gGlobalState.EntitySetState( pev->globalname, GLOBAL_DEAD ); +} + +// Convenient way to delay removing oneself +void CBaseEntity :: SUB_Remove( void ) +{ + UpdateOnRemove(); + if (pev->health > 0) + { + // this situation can screw up monsters who can't tell their entity pointers are invalid. + pev->health = 0; + ALERT( at_aiconsole, "SUB_Remove called on entity with health > 0\n"); + } + + REMOVE_ENTITY(ENT(pev)); +} + + +// Convenient way to explicitly do nothing (passed to functions that require a method) +void CBaseEntity :: SUB_DoNothing( void ) +{ +// if (pev->ltime) +// ALERT(at_console, "Doing Nothing %f\n", pev->ltime); +// else +// ALERT(at_console, "Doing Nothing %f\n", gpGlobals->time); +} + + +// Global Savedata for Delay +TYPEDESCRIPTION CBaseDelay::m_SaveData[] = +{ + DEFINE_FIELD( CBaseDelay, m_flDelay, FIELD_FLOAT ), + DEFINE_FIELD( CBaseDelay, m_iszKillTarget, FIELD_STRING ), + DEFINE_FIELD( CBaseDelay, m_hActivator, FIELD_EHANDLE ), //LRC +}; + +IMPLEMENT_SAVERESTORE( CBaseDelay, CBaseEntity ); + +void CBaseDelay :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "delay")) + { + m_flDelay = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "killtarget")) + { + m_iszKillTarget = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + { + CBaseEntity::KeyValue( pkvd ); + } +} + + +/* +============================== +SUB_UseTargets + +If self.delay is set, a DelayedUse entity will be created that will actually +do the SUB_UseTargets after that many seconds have passed. + +Removes all entities with a targetname that match self.killtarget, +and removes them, so some events can remove other triggers. + +Search for (string)targetname in all entities that +match (string)self.target and call their .use function (if they have one) + +============================== +*/ +void CBaseEntity :: SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ) +{ + // + // fire targets + // + if (!FStringNull(pev->target)) + { + FireTargets( STRING(pev->target), pActivator, this, useType, value ); + } +} + + +void FireTargets( const char *targetName, CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + const char *inputTargetName = targetName; + CBaseEntity *inputActivator = pActivator; + CBaseEntity *pTarget = NULL; + int i,j, found = false; + char szBuf[80]; + + if ( !targetName ) + return; + if (useType == USE_NOT) + return; + + //LRC - allow changing of usetype + if (targetName[0] == '+') + { + targetName++; + useType = USE_ON; + } + else if (targetName[0] == '-') + { + targetName++; + useType = USE_OFF; + } + + ALERT( at_aiconsole, "Firing: (%s)\n", targetName ); + + pTarget = UTIL_FindEntityByTargetname(pTarget, targetName, pActivator); + if( !pTarget ) + { + // it's not an entity name; check for a locus specifier, e.g: "fadein(mywall)" + for (i = 0; targetName[i]; i++) + { + if (targetName[i] == '(') + { + i++; + for (j = i; targetName[j]; j++) + { + if (targetName[j] == ')') + { + strncpy(szBuf, targetName+i, j-i); + szBuf[j-i] = 0; + pActivator = UTIL_FindEntityByTargetname(NULL, szBuf, inputActivator); + if (!pActivator) + { + //ALERT(at_console, "Missing activator \"%s\"\n", szBuf); + return; // it's a locus specifier, but the locus is invalid. + } + //ALERT(at_console, "Found activator \"%s\"\n", STRING(pActivator->pev->targetname)); + found = true; + break; + } + } + if (!found) ALERT(at_error, "Missing ')' in target value \"%s\"", inputTargetName); + break; + } + } + if (!found) return; // no, it's not a locus specifier. + + strncpy(szBuf, targetName, i-1); + szBuf[i-1] = 0; + targetName = szBuf; + pTarget = UTIL_FindEntityByTargetname(NULL, targetName, inputActivator); + + if (!pTarget) return; // it's a locus specifier all right, but the target's invalid. + } + + do // start firing targets + { + if ( !(pTarget->pev->flags & FL_KILLME) ) // Don't use dying ents + { + if (useType == USE_KILL) + { + ALERT( at_aiconsole, "Use_kill on %s\n", STRING( pTarget->pev->classname ) ); + UTIL_Remove( pTarget ); + } + else + { + ALERT( at_aiconsole, "Found: %s, firing (%s)\n", STRING(pTarget->pev->classname), targetName ); + pTarget->Use( pActivator, pCaller, useType, value ); + } + } + pTarget = UTIL_FindEntityByTargetname(pTarget, targetName, inputActivator); + } while (pTarget); + + //LRC- Firing has finished, aliases can now reflect their new values. + UTIL_FlushAliases(); +} + +LINK_ENTITY_TO_CLASS( DelayedUse, CBaseDelay ); + + +void CBaseDelay :: SUB_UseTargets( CBaseEntity *pActivator, USE_TYPE useType, float value ) +{ + // + // exit immediatly if we don't have a target or kill target + // + if (FStringNull(pev->target) && !m_iszKillTarget) + return; + + // + // check for a delay + // + if (m_flDelay != 0) + { + // create a temp object to fire at a later time + CBaseDelay *pTemp = GetClassPtr( (CBaseDelay *)NULL); + pTemp->pev->classname = MAKE_STRING("DelayedUse"); + + pTemp->SetNextThink( m_flDelay ); + + pTemp->SetThink(&CBaseDelay:: DelayThink ); + + // Save the useType + pTemp->pev->button = (int)useType; + pTemp->m_iszKillTarget = m_iszKillTarget; + pTemp->m_flDelay = 0; // prevent "recursion" + pTemp->pev->target = pev->target; + + //LRC - Valve had a hacked thing here to avoid breaking + // save/restore. In Spirit that's not a problem. + // I've moved m_hActivator into this class, for the "elegant" fix. + pTemp->m_hActivator = pActivator; + + return; + } + + // + // kill the killtargets + // + + if ( m_iszKillTarget ) + { + edict_t *pentKillTarget = NULL; + + ALERT( at_aiconsole, "KillTarget: %s\n", STRING(m_iszKillTarget) ); + //LRC- now just USE_KILLs its killtarget, for consistency. + FireTargets( STRING(m_iszKillTarget), pActivator, this, USE_KILL, 0); + } + + // + // fire targets + // + if (!FStringNull(pev->target)) + { + FireTargets( STRING(pev->target), pActivator, this, useType, value ); + } +} + + +/* +void CBaseDelay :: SUB_UseTargetsEntMethod( void ) +{ + SUB_UseTargets(pev); +} +*/ + +/* +QuakeEd only writes a single float for angles (bad idea), so up and down are +just constant angles. +*/ +void SetMovedir( entvars_t *pev ) +{ + pev->movedir = GetMovedir(pev->angles); + pev->angles = g_vecZero; + } + +Vector GetMovedir( Vector vecAngles ) + { + if (vecAngles == Vector(0, -1, 0)) + { + return Vector(0, 0, 1); + } + else if (vecAngles == Vector(0, -2, 0)) + { + return Vector(0, 0, -1); + } + else + { + UTIL_MakeVectors(vecAngles); + return gpGlobals->v_forward; + } +} + + + +void CBaseDelay::DelayThink( void ) +{ + CBaseEntity *pActivator = NULL; + + // The use type is cached (and stashed) in pev->button + //LRC - now using m_hActivator. + SUB_UseTargets( m_hActivator, (USE_TYPE)pev->button, 0 ); + REMOVE_ENTITY(ENT(pev)); +} + + +// Global Savedata for Toggle +TYPEDESCRIPTION CBaseToggle::m_SaveData[] = +{ + DEFINE_FIELD( CBaseToggle, m_toggle_state, FIELD_INTEGER ), + DEFINE_FIELD( CBaseToggle, m_flActivateFinished, FIELD_TIME ), + DEFINE_FIELD( CBaseToggle, m_flMoveDistance, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flWait, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flLip, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flTWidth, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flTLength, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_vecPosition1, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_vecPosition2, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_vecAngle1, FIELD_VECTOR ), // UNDONE: Position could go through transition, but also angle? + DEFINE_FIELD( CBaseToggle, m_vecAngle2, FIELD_VECTOR ), // UNDONE: Position could go through transition, but also angle? + DEFINE_FIELD( CBaseToggle, m_cTriggersLeft, FIELD_INTEGER ), + DEFINE_FIELD( CBaseToggle, m_flHeight, FIELD_FLOAT ), +// DEFINE_FIELD( CBaseToggle, m_hActivator, FIELD_EHANDLE ), + DEFINE_FIELD( CBaseToggle, m_pfnCallWhenMoveDone, FIELD_FUNCTION ), + DEFINE_FIELD( CBaseToggle, m_vecFinalDest, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_flLinearMoveSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CBaseToggle, m_flAngularMoveSpeed, FIELD_FLOAT ), //LRC + DEFINE_FIELD( CBaseToggle, m_vecFinalAngle, FIELD_VECTOR ), + DEFINE_FIELD( CBaseToggle, m_sMaster, FIELD_STRING), + DEFINE_FIELD( CBaseToggle, m_bitsDamageInflict, FIELD_INTEGER ), // damage type inflicted +}; +IMPLEMENT_SAVERESTORE( CBaseToggle, CBaseAnimating ); + + +void CBaseToggle::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "lip")) + { + m_flLip = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "master")) + { + m_sMaster = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "distance")) + { + m_flMoveDistance = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + +/* +//void CBaseToggle :: LinearMove( Vector vecInput, float flSpeed) +//{ +// LinearMove(vecInput, flSpeed); +//} + +/* +============= +LinearMove + +calculate pev->velocity and pev->nextthink to reach vecDest from +pev->origin traveling at flSpeed +=============== +*/ +void CBaseToggle :: LinearMove( Vector vecInput, float flSpeed )//, BOOL bNow ) +{ +// ALERT(at_console, "LMove %s: %f %f %f, speed %f\n", STRING(pev->targetname), vecInput.x, vecInput.y, vecInput.z, flSpeed); + ASSERTSZ(flSpeed != 0, "LinearMove: no speed is defined!"); +// ASSERTSZ(m_pfnCallWhenMoveDone != NULL, "LinearMove: no post-move function defined"); + + m_flLinearMoveSpeed = flSpeed; + m_vecFinalDest = vecInput; + +// if ((m_pMoveWith || m_pChildMoveWith))// && !bNow) +// { +// ALERT(at_console,"Setting LinearMoveNow to happen after %f\n",gpGlobals->time); + SetThink(&CBaseToggle :: LinearMoveNow ); + UTIL_DesiredThink( this ); + //pev->nextthink = pev->ltime + 0.01; +// } +// else +// { +// LinearMoveNow(); // starring Martin Sheen and Marlon Brando +// } +} + +void CBaseToggle :: LinearMoveNow( void ) +{ +// ALERT(at_console, "LMNow %s\n", STRING(pev->targetname)); + + Vector vecDest; +// if (m_pMoveWith || m_pChildMoveWith ) +// ALERT(at_console,"THINK: LinearMoveNow happens at %f, speed %f\n",gpGlobals->time, m_flLinearMoveSpeed); + + if (m_pMoveWith) + { + vecDest = m_vecFinalDest + m_pMoveWith->pev->origin; + } + else + vecDest = m_vecFinalDest; + +// ALERT(at_console,"LinearMoveNow: Destination is (%f %f %f), finalDest was (%f %f %f)\n", +// vecDest.x,vecDest.y,vecDest.z, +// m_vecFinalDest.x,m_vecFinalDest.y,m_vecFinalDest.z +// ); + + // Already there? + if (vecDest == pev->origin) + { + LinearMoveDone(); + return; + } + + // set destdelta to the vector needed to move + Vector vecDestDelta = vecDest - pev->origin; + + // divide vector length by speed to get time to reach dest + float flTravelTime = vecDestDelta.Length() / m_flLinearMoveSpeed; + + // set nextthink to trigger a call to LinearMoveDone when dest is reached + SetNextThink( flTravelTime, TRUE ); + SetThink(&CBaseToggle :: LinearMoveDone ); + + // scale the destdelta vector by the time spent traveling to get velocity +// pev->velocity = vecDestDelta / flTravelTime; + UTIL_SetVelocity( this, vecDestDelta / flTravelTime ); + +// ALERT(at_console, "LMNow \"%s\": Vel %f %f %f, think %f\n", STRING(pev->targetname), pev->velocity.x, pev->velocity.y, pev->velocity.z, pev->nextthink); +} + + +/* +============ +After moving, set origin to exact final destination, call "move done" function +============ +*/ +/*void CBaseToggle :: LinearMoveDone( void ) +{ + Vector vecDiff; + if (m_pMoveWith) + vecDiff = (m_vecFinalDest + m_pMoveWith->pev->origin) - pev->origin; + else + vecDiff = m_vecFinalDest - pev->origin; + if (vecDiff.Length() > 0.05) //pev->velocity.Length()) + { + // HACK: not there yet, try waiting one more frame. + ALERT(at_console,"Rejecting difference %f\n",vecDiff.Length()); + SetThink(&CBaseToggle ::LinearMoveFinalDone); + pev->nextthink = gpGlobals->time + 0.01; + } + else + { + LinearMoveFinalDone(); + } +}*/ + +void CBaseToggle :: LinearMoveDone( void ) +{ + SetThink(&CBaseToggle ::LinearMoveDoneNow); +// ALERT(at_console, "LMD: desiredThink %s\n", STRING(pev->targetname)); + UTIL_DesiredThink( this ); +} + +void CBaseToggle :: LinearMoveDoneNow( void ) +{ + Vector vecDest; + +// ALERT(at_console, "LMDone %s\n", STRING(pev->targetname)); + + UTIL_SetVelocity(this, g_vecZero);//, TRUE); +// pev->velocity = g_vecZero; + if (m_pMoveWith) + { + vecDest = m_vecFinalDest + m_pMoveWith->pev->origin; +// ALERT(at_console, "LMDone %s: p.origin = %f %f %f, origin = %f %f %f. Set it to %f %f %f\n", STRING(pev->targetname), m_pMoveWith->pev->origin.x, m_pMoveWith->pev->origin.y, m_pMoveWith->pev->origin.z, pev->origin.x, pev->origin.y, pev->origin.z, vecDest.x, vecDest.y, vecDest.z); + } + else + { + vecDest = m_vecFinalDest; +// ALERT(at_console, "LMDone %s: origin = %f %f %f. Set it to %f %f %f\n", STRING(pev->targetname), pev->origin.x, pev->origin.y, pev->origin.z, vecDest.x, vecDest.y, vecDest.z); + } + UTIL_AssignOrigin(this, vecDest); + DontThink(); //LRC + //pev->nextthink = -1; + if ( m_pfnCallWhenMoveDone ) + (this->*m_pfnCallWhenMoveDone)(); +} + +BOOL CBaseToggle :: IsLockedByMaster( void ) +{ + if (UTIL_IsMasterTriggered(m_sMaster, m_hActivator)) + return FALSE; + else + return TRUE; +} + +//LRC- mapping toggle-states to global states +STATE CBaseToggle :: GetState ( void ) +{ + switch (m_toggle_state) + { + case TS_AT_TOP: return STATE_ON; + case TS_AT_BOTTOM: return STATE_OFF; + case TS_GOING_UP: return STATE_TURN_ON; + case TS_GOING_DOWN: return STATE_TURN_OFF; + default: return STATE_OFF; // This should never happen. + } +}; + +/* +============= +AngularMove + +calculate pev->velocity and pev->nextthink to reach vecDest from +pev->origin traveling at flSpeed +Just like LinearMove, but rotational. +=============== +*/ +void CBaseToggle :: AngularMove( Vector vecDestAngle, float flSpeed ) +{ + ASSERTSZ(flSpeed != 0, "AngularMove: no speed is defined!"); +// ASSERTSZ(m_pfnCallWhenMoveDone != NULL, "AngularMove: no post-move function defined"); + + m_vecFinalAngle = vecDestAngle; + m_flAngularMoveSpeed = flSpeed; + +// if ((m_pMoveWith || m_pChildMoveWith))// && !bNow) +// { +// ALERT(at_console,"Setting AngularMoveNow to happen after %f\n",gpGlobals->time); + SetThink(&CBaseToggle :: AngularMoveNow ); + UTIL_DesiredThink( this ); +// ExternalThink( 0.01 ); +// pev->nextthink = pev->ltime + 0.01; +// } +// else +// { +// AngularMoveNow(); // starring Martin Sheen and Marlon Brando +// } +} + +void CBaseToggle :: AngularMoveNow() +{ +// ALERT(at_console, "AngularMoveNow %f\n", pev->ltime); + Vector vecDestAngle; + + if (m_pMoveWith) + vecDestAngle = m_vecFinalAngle + m_pMoveWith->pev->angles; + else + vecDestAngle = m_vecFinalAngle; + + // Already there? + if (vecDestAngle == pev->angles) + { + AngularMoveDone(); + return; + } + + // set destdelta to the vector needed to move + Vector vecDestDelta = vecDestAngle - pev->angles; + + // divide by speed to get time to reach dest + float flTravelTime = vecDestDelta.Length() / m_flAngularMoveSpeed; + + // set nextthink to trigger a call to AngularMoveDone when dest is reached + SetNextThink( flTravelTime, TRUE ); + SetThink(&CBaseToggle :: AngularMoveDone ); + + // scale the destdelta vector by the time spent traveling to get velocity + UTIL_SetAvelocity(this, vecDestDelta / flTravelTime ); +} + +void CBaseToggle :: AngularMoveDone( void ) +{ + SetThink(&CBaseToggle ::AngularMoveDoneNow); +// ALERT(at_console, "LMD: desiredThink %s\n", STRING(pev->targetname)); + UTIL_DesiredThink( this ); +} + +/* +============ +After rotating, set angle to exact final angle, call "move done" function +============ +*/ +void CBaseToggle :: AngularMoveDoneNow( void ) +{ +// ALERT(at_console, "AngularMoveDone %f\n", pev->ltime); + UTIL_SetAvelocity(this, g_vecZero); + if (m_pMoveWith) + { + UTIL_SetAngles(this, m_vecFinalAngle + m_pMoveWith->pev->angles); + } + else + { + UTIL_SetAngles(this, m_vecFinalAngle); + } + DontThink(); + if ( m_pfnCallWhenMoveDone ) + (this->*m_pfnCallWhenMoveDone)(); +} + +// this isn't currently used. Otherwise I'd fix it to use movedir the way it should... +float CBaseToggle :: AxisValue( int flags, const Vector &angles ) +{ + if ( FBitSet(flags, SF_DOOR_ROTATE_Z) ) + return angles.z; + if ( FBitSet(flags, SF_DOOR_ROTATE_X) ) + return angles.x; + + return angles.y; +} + + +void CBaseToggle :: AxisDir( entvars_t *pev ) +{ + if ( pev->movedir != g_vecZero) //LRC + return; + + if ( FBitSet(pev->spawnflags, SF_DOOR_ROTATE_Z) ) + pev->movedir = Vector ( 0, 0, 1 ); // around z-axis + else if ( FBitSet(pev->spawnflags, SF_DOOR_ROTATE_X) ) + pev->movedir = Vector ( 1, 0, 0 ); // around x-axis + else + pev->movedir = Vector ( 0, 1, 0 ); // around y-axis +} + + +float CBaseToggle :: AxisDelta( int flags, const Vector &angle1, const Vector &angle2 ) +{ + if ( FBitSet (flags, SF_DOOR_ROTATE_Z) ) + return angle1.z - angle2.z; + + if ( FBitSet (flags, SF_DOOR_ROTATE_X) ) + return angle1.x - angle2.x; + + return angle1.y - angle2.y; +} + + +/* +============= +FEntIsVisible + +returns TRUE if the passed entity is visible to caller, even if not infront () +============= +*/ + BOOL +FEntIsVisible( + entvars_t* pev, + entvars_t* pevTarget) + { + Vector vecSpot1 = pev->origin + pev->view_ofs; + Vector vecSpot2 = pevTarget->origin + pevTarget->view_ofs; + TraceResult tr; + + UTIL_TraceLine(vecSpot1, vecSpot2, ignore_monsters, ENT(pev), &tr); + + if (tr.fInOpen && tr.fInWater) + return FALSE; // sight line crossed contents + + if (tr.flFraction == 1) + return TRUE; + + return FALSE; + } + + +//========================================================= +// LRC - info_movewith, the first entity I've made which +// truly doesn't fit ANY preexisting category. +//========================================================= +#define SF_IMW_INACTIVE 1 +#define SF_IMW_BLOCKABLE 2 + +class CInfoMoveWith : public CBaseEntity +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + STATE GetState() { return (pev->spawnflags & SF_IMW_INACTIVE)?STATE_OFF:STATE_ON; } +}; + +LINK_ENTITY_TO_CLASS(info_movewith, CInfoMoveWith); + +void CInfoMoveWith :: Spawn( void ) +{ + if (pev->spawnflags & SF_IMW_INACTIVE) + m_MoveWith = pev->netname; + else + m_MoveWith = pev->target; + + if (pev->spawnflags & SF_IMW_BLOCKABLE) + { + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_FLY; + } + // and allow InitMoveWith to set things up as usual. +} + +void CInfoMoveWith :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBaseEntity *pSibling; + + if (!ShouldToggle(useType)) return; + + if (m_pMoveWith) + { + // remove this from the old parent's list of children + pSibling = m_pMoveWith->m_pChildMoveWith; + if (pSibling == this) + m_pMoveWith->m_pChildMoveWith = this->m_pSiblingMoveWith; + else + { + while (pSibling->m_pSiblingMoveWith && pSibling->m_pSiblingMoveWith != this) + { pSibling = pSibling->m_pSiblingMoveWith; } + + if (pSibling->m_pSiblingMoveWith == this) + { + pSibling->m_pSiblingMoveWith = this->m_pSiblingMoveWith; + } + else + { + // failed to find myself in the list, complain + ALERT(at_error, "info_movewith can't find itself\n"); + return; + } + } + m_pMoveWith = NULL; + m_pSiblingMoveWith = NULL; + } + + if (pev->spawnflags & SF_IMW_INACTIVE) + { + pev->spawnflags &= ~SF_IMW_INACTIVE; + m_MoveWith = pev->target; + } + else + { + pev->spawnflags |= SF_IMW_INACTIVE; + m_MoveWith = pev->netname; + } + + // set things up for the new m_MoveWith value + if (!m_MoveWith) + { + UTIL_SetVelocity(this, g_vecZero); // come to a stop + return; + } + + m_pMoveWith = UTIL_FindEntityByTargetname(NULL, STRING(m_MoveWith)); + if (!m_pMoveWith) + { + ALERT(at_debug,"Missing movewith entity %s\n", STRING(m_MoveWith)); + return; + } + + pSibling = m_pMoveWith->m_pChildMoveWith; + while (pSibling) // check that this entity isn't already in the list of children + { + if (pSibling == this) return; + pSibling = pSibling->m_pSiblingMoveWith; + } + + // add this entity to the list of children + m_pSiblingMoveWith = m_pMoveWith->m_pChildMoveWith; // may be null: that's fine by me. + m_pMoveWith->m_pChildMoveWith = this; + m_vecMoveWithOffset = pev->origin - m_pMoveWith->pev->origin; + UTIL_SetVelocity(this, g_vecZero); // match speed with the new entity +} diff --git a/dlls/sv_materials.cpp b/dlls/sv_materials.cpp new file mode 100644 index 0000000..e01024e --- /dev/null +++ b/dlls/sv_materials.cpp @@ -0,0 +1,543 @@ +/* +sv_materials.cpp - materials handling and processing at the server-side +Copyright (C) 2016 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "extdll.h" +#include "util.h" +#include "com_model.h" +#include "material.h" +#include "triangleapi.h" + +sv_matdesc_t *sv_materials; +int sv_matcount; + +terrain_t *sv_terrains; +int sv_numterrains; + +/* +================== +SV_LoadMaterials + +parse material from a given file +================== +*/ +void SV_LoadMaterials( const char *path ) +{ + ALERT( at_aiconsole, "loading %s\n", path ); + + char *afile = (char *)LOAD_FILE( (char *)path, NULL ); + + if( !afile ) + { + ALERT( at_error, "Cannot open file \"%s\"\n", path ); + return; + } + + sv_matdesc_t *oldmaterials = sv_materials; + int oldcount = sv_matcount; + char *pfile = afile; + char token[256]; + int depth = 0; + + // count materials + while( pfile != NULL ) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + + if( Q_strlen( token ) > 1 ) + continue; + + if( token[0] == '{' ) + { + depth++; + } + else if( token[0] == '}' ) + { + sv_matcount++; + depth--; + } + } + + if( depth > 0 ) ALERT( at_warning, "%s: EOF reached without closing brace\n", path ); + if( depth < 0 ) ALERT( at_warning, "%s: EOF reached without opening brace\n", path ); + + sv_materials = (sv_matdesc_t *)Mem_Alloc( sizeof( sv_matdesc_t ) * sv_matcount ); + memcpy( sv_materials, oldmaterials, oldcount * sizeof( sv_matdesc_t )); + Mem_Free( oldmaterials ); + pfile = afile; // start real parsing + + int current = oldcount; // starts from + + while( pfile != NULL ) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + + if( current >= sv_matcount ) + { + ALERT ( at_error, "material parse is overrun %d > %d\n", current, sv_matcount ); + break; + } + + sv_matdesc_t *mat = &sv_materials[current]; + + // read the material name + Q_strncpy( mat->name, token, sizeof( mat->name )); + COM_StripExtension( mat->name ); + + // read opening brace + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + + if( token[0] != '{' ) + { + ALERT( at_error, "found %s when expecting {\n", token ); + break; + } + + while( pfile != NULL ) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "EOF without closing brace\n" ); + goto getout; + } + + // description end goto next material + if( token[0] == '}' ) + { + current++; + break; + } + else if( !Q_stricmp( token, "material" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'material'\n" ); + goto getout; + } + + mat->effects = COM_FindMatdef( token ); + } + else continue; // skip all other tokens on server-side + } + + // apply default values + if( !mat->effects ) mat->effects = COM_DefaultMatdef(); + } +getout: + FREE_FILE( afile ); + ALERT( at_aiconsole, "%d materials parsed\n", current ); +} + +/* +================== +SV_InitMaterials + +parse global material settings +================== +*/ +void SV_InitMaterials( void ) +{ + int count = 0; + char **filenames = GET_FILES_LIST( "scripts/*.mat", &count, 0 ); + + sv_matcount = 0; + + // sequentially load materials + for( int i = 0; i < count; i++ ) + SV_LoadMaterials( filenames[i] ); +} + +/* +================== +SV_FindMaterial + +This function never failed +================== +*/ +sv_matdesc_t *SV_FindMaterial( const char *name ) +{ + static sv_matdesc_t defmat; + + if( !defmat.name[0] ) + { + // initialize default material + Q_strncpy( defmat.name, "*default", sizeof( defmat.name )); + defmat.effects = COM_DefaultMatdef(); + } + + for( int i = 0; i < sv_matcount; i++ ) + { + if( !Q_stricmp( name, sv_materials[i].name )) + return &sv_materials[i]; + } + + return &defmat; +} + +/* +======================== +LoadHeightMap + +parse heightmap pixels and remap it +to real layer count +======================== +*/ +static bool LoadHeightMap( sv_indexMap_t *im, int numLayers ) +{ + unsigned int *src; + byte *buffer; + int i, width, height; + int depth = 1; + + if( numLayers <= 0 ) return false; + + // loading heightmap and keep the source pixels + if(( buffer = (byte *)LOAD_IMAGE_PIXELS( im->name, &width, &height )) == NULL ) + { + ALERT( at_error, "LoadHeightMap: couldn't get source pixels for %s\n", im->name ); + return false; + } + + im->pixels = (byte *)Mem_Alloc( width * height ); + im->numLayers = bound( 1, numLayers, 255 ); + im->height = height; + im->width = width; + + src = (unsigned int *)buffer; + + for( i = 0; i < ( im->width * im->height ); i++ ) + { + byte rawHeight = ( src[i] & 0xFF ); + im->maxHeight = Q_max(( 16 * (int)ceil( rawHeight / 16 )), im->maxHeight ); + } + + // merge layers count + im->numLayers = (im->maxHeight / 16) + 1; + depth = Q_max((int)Q_ceil((float)im->numLayers / 4.0f ), 1 ); + + // clamp to layers count + for( i = 0; i < ( im->width * im->height ); i++ ) + im->pixels[i] = (( src[i] & 0xFF ) * ( im->numLayers - 1 )) / im->maxHeight; + + Mem_Free( buffer ); // no reason to keep this data + + return true; +} + +/* +======================== +LoadTerrainLayers + +loading all the landscape layers +into texture arrays +======================== +*/ +static bool LoadTerrainLayers( sv_layerMap_t *lm, int numLayers ) +{ + // setup materials + for( int i = 0; i < numLayers; i++ ) + lm->effects[i] = SV_FindMaterial( lm->names[i] )->effects; + + return true; +} + +/* +======================== +SV_FreeLandscapes + +free the landscape definitions +======================== +*/ +void SV_FreeLandscapes( void ) +{ + for( int i = 0; i < sv_numterrains; i++ ) + { + terrain_t *terra = &sv_terrains[i]; + sv_indexMap_t *im = &terra->indexmap; + if( im->pixels ) Mem_Free( im->pixels ); + } + + if( sv_terrains ) + Mem_Free( sv_terrains ); + sv_numterrains = 0; + sv_terrains = NULL; +} + +/* +======================== +SV_LoadLandscapes + +load the landscape definitions +======================== +*/ +void SV_LoadLandscapes( const char *filename ) +{ + char filepath[256]; + + Q_snprintf( filepath, sizeof( filepath ), "maps/%s_land.txt", filename ); + + char *afile = (char *)LOAD_FILE( filepath, NULL ); + if( !afile ) return; + + ALERT( at_aiconsole, "loading %s\n", filepath ); + + char *pfile = afile; + char token[256]; + int depth = 0; + + // count materials + while( pfile != NULL ) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + + if( Q_strlen( token ) > 1 ) + continue; + + if( token[0] == '{' ) + { + depth++; + } + else if( token[0] == '}' ) + { + sv_numterrains++; + depth--; + } + } + + if( depth > 0 ) ALERT( at_warning, "%s: EOF reached without closing brace\n", filepath ); + if( depth < 0 ) ALERT( at_warning, "%s: EOF reached without opening brace\n", filepath ); + + sv_terrains = (terrain_t *)Mem_Alloc( sizeof( terrain_t ) * sv_numterrains ); + pfile = afile; // start real parsing + + int current = 0; + + while( pfile != NULL ) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + + if( current >= sv_numterrains ) + { + ALERT ( at_error, "landscape parse is overrun %d > %d\n", current, sv_numterrains ); + break; + } + + terrain_t *terra = &sv_terrains[current]; + + // read the landscape name + Q_strncpy( terra->name, token, sizeof( terra->name )); + + // read opening brace + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + + if( token[0] != '{' ) + { + ALERT( at_error, "found %s when expecting {\n", token ); + break; + } + + while( pfile != NULL ) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "EOF without closing brace\n" ); + goto land_getout; + } + + // description end goto next material + if( token[0] == '}' ) + { + current++; + break; + } + else if( !Q_stricmp( token, "indexMap" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'indexMap'\n" ); + goto land_getout; + } + + Q_strncpy( terra->indexmap.name, token, sizeof( terra->indexmap.name )); + } + else if( !Q_strnicmp( token, "layer", 5 )) + { + int layerNum = Q_atoi( token + 5 ); + + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'layer'\n" ); + goto land_getout; + } + + if( layerNum < 0 || layerNum > ( MAX_LANDSCAPE_LAYERS - 1 )) + { + ALERT( at_error, "%s is out of range. Ignored\n", token ); + } + else + { + COM_FileBase( token, terra->layermap.names[layerNum] ); + } + + terra->numLayers = Q_max( terra->numLayers, layerNum + 1 ); + } + else continue; // skip all other tokens on server-side + } + + if( LoadHeightMap( &terra->indexmap, terra->numLayers )) + { + if( LoadTerrainLayers( &terra->layermap, terra->numLayers )) + terra->valid = true; // all done + } + } + +land_getout: + FREE_FILE( afile ); + ALERT( at_console, "server: %d landscapes parsed\n", current ); +} + +/* +======================== +SV_FindTerrain + +find the terrain description +======================== +*/ +terrain_t *SV_FindTerrain( const char *texname ) +{ + for( int i = 0; i < sv_numterrains; i++ ) + { + if( !Q_stricmp( texname, sv_terrains[i].name ) && sv_terrains[i].valid ) + return &sv_terrains[i]; + } + + return NULL; +} + +/* +================= +Mod_ProcessLandscapes + +handle all the landscapes per level +================= +*/ +static void Mod_ProcessLandscapes( msurface_t *surf, mextrasurf_t *esrf ) +{ + mtexinfo_t *tx = surf->texinfo; + mfaceinfo_t *land = tx->faceinfo; + + if( !land || land->groupid == 0 || !land->landname[0] ) + return; // no landscape specified, just lightmap resolution + + if( !land->terrain ) + { + land->terrain = SV_FindTerrain( land->landname ); + + if( !land->terrain ) + { + // land name was specified in bsp but not declared in script file + ALERT( at_error, "Mod_ProcessLandscapes: %s missing description\n", land->landname ); + land->landname[0] = '\0'; // clear name to avoid trying to find invalid terrain + return; + } + + // prepare new landscape params + ClearBounds( land->mins, land->maxs ); + + // setup shared pointers + for( int i = 0; i < land->terrain->numLayers; i++ ) + land->effects[i] = land->terrain->layermap.effects[i]; + + land->heightmap = land->terrain->indexmap.pixels; + land->heightmap_width = land->terrain->indexmap.width; + land->heightmap_height = land->terrain->indexmap.height; + } + + // update terrain bounds + AddPointToBounds( esrf->mins, land->mins, land->maxs ); + AddPointToBounds( esrf->maxs, land->mins, land->maxs ); +} + +/* +================= +Mod_LoadWorld + + +================= +*/ +void Mod_LoadWorld( model_t *mod, const byte *buf ) +{ + dheader_t *header; + dextrahdr_t *extrahdr; + char barename[64]; + int i; + + header = (dheader_t *)buf; + extrahdr = (dextrahdr_t *)((byte *)buf + sizeof( dheader_t )); + + COM_FileBase( mod->name, barename ); + + // process landscapes first + SV_LoadLandscapes( barename ); + + // apply materials to right get them on dedicated server + for( i = 0; i < mod->numtextures; i++ ) + { + texture_t *tx = mod->textures[i]; + + // bad texture? + if( !tx || !tx->name[0] ) continue; + + sv_matdesc_t *desc = SV_FindMaterial( tx->name ); + + tx->effects = desc->effects; + } + + // mark surfaces for world features + for( i = 0; i < mod->numsurfaces; i++ ) + { + msurface_t *surf = &mod->surfaces[i]; + Mod_ProcessLandscapes( surf, surf->info ); + } +} + +void Mod_FreeWorld( model_t *mod ) +{ + // free landscapes + SV_FreeLandscapes(); +} + +/* +================== +SV_ProcessWorldData + +resource management +================== +*/ +void SV_ProcessWorldData( model_t *mod, qboolean create, const byte *buffer ) +{ + if( create ) Mod_LoadWorld( mod, buffer ); + else Mod_FreeWorld( mod ); +} \ No newline at end of file diff --git a/dlls/talkmonster.cpp b/dlls/talkmonster.cpp new file mode 100644 index 0000000..b3f285b --- /dev/null +++ b/dlls/talkmonster.cpp @@ -0,0 +1,1596 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "talkmonster.h" +#include "defaultai.h" +#include "scripted.h" +#include "soundent.h" +#include "animation.h" + +//========================================================= +// Talking monster base class +// Used for scientists and barneys +//========================================================= +float CTalkMonster::g_talkWaitTime = 0; // time delay until it's ok to speak: used so that two NPCs don't talk at once + +// NOTE: m_voicePitch & m_szGrp should be fixed up by precache each save/restore + +TYPEDESCRIPTION CTalkMonster::m_SaveData[] = +{ + DEFINE_FIELD( CTalkMonster, m_bitsSaid, FIELD_INTEGER ), + DEFINE_FIELD( CTalkMonster, m_nSpeak, FIELD_INTEGER ), + + // Recalc'ed in Precache() + // DEFINE_FIELD( CTalkMonster, m_voicePitch, FIELD_INTEGER ), + // DEFINE_FIELD( CTalkMonster, m_szGrp, FIELD_??? ), + DEFINE_FIELD( CTalkMonster, m_useTime, FIELD_TIME ), + DEFINE_FIELD( CTalkMonster, m_iszUse, FIELD_STRING ), + DEFINE_FIELD( CTalkMonster, m_iszUnUse, FIELD_STRING ), + DEFINE_FIELD( CTalkMonster, m_iszDecline, FIELD_STRING ), //LRC + DEFINE_FIELD( CTalkMonster, m_iszSpeakAs, FIELD_STRING ), //LRC + DEFINE_FIELD( CTalkMonster, m_flLastSaidSmelled, FIELD_TIME ), + DEFINE_FIELD( CTalkMonster, m_flStopTalkTime, FIELD_TIME ), + DEFINE_FIELD( CTalkMonster, m_hTalkTarget, FIELD_EHANDLE ), + DEFINE_FIELD( CTalkMonster, m_deathNoticed, FIELD_INTEGER ), // buz +}; + +IMPLEMENT_SAVERESTORE( CTalkMonster, CBaseMonster ); + +// array of friend names +char *CTalkMonster::m_szFriends[TLK_CFRIENDS] = +{ + "monster_human_alpha", // buz + "monster_alpha_pistol", // buz + "monster_human_military", // buz + "monster_barney", + "monster_scientist", + "monster_sitting_scientist", +}; + + +//========================================================= +// AI Schedules Specific to talking monsters +//========================================================= + +Task_t tlIdleResponse[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_IDLE },// Stop and listen + { TASK_WAIT, (float)0.5 },// Wait until sure it's me they are talking to + { TASK_TLK_EYECONTACT, (float)0 },// Wait until speaker is done + { TASK_TLK_RESPOND, (float)0 },// Wait and then say my response + { TASK_TLK_IDEALYAW, (float)0 },// look at who I'm talking to + { TASK_FACE_IDEAL, (float)0 }, +// { TASK_SET_ACTIVITY, (float)//ACT_SIGNAL3 }, + { TASK_TLK_EYECONTACT, (float)0 },// Wait until speaker is done +}; + +Schedule_t slIdleResponse[] = +{ + { + tlIdleResponse, + ARRAYSIZE ( tlIdleResponse ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "Idle Response" + + }, +}; + +Task_t tlIdleSpeak[] = +{ + { TASK_TLK_SPEAK, (float)0 },// question or remark + { TASK_TLK_IDEALYAW, (float)0 },// look at who I'm talking to + { TASK_FACE_IDEAL, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE},//ACT_SIGNAL3 }, + { TASK_TLK_EYECONTACT, (float)0 }, + { TASK_WAIT_RANDOM, (float)0.5 }, +}; + +Schedule_t slIdleSpeak[] = +{ + { + tlIdleSpeak, + ARRAYSIZE ( tlIdleSpeak ), + bits_COND_NEW_ENEMY | + bits_COND_CLIENT_PUSH | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "Idle Speak" + }, +}; + +Task_t tlIdleSpeakWait[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_IDLE},//ACT_SIGNAL3 },// Stop and talk + { TASK_TLK_SPEAK, (float)0 },// question or remark + { TASK_TLK_EYECONTACT, (float)0 },// + { TASK_WAIT, (float)2 },// wait - used when sci is in 'use' mode to keep head turned +}; + +Schedule_t slIdleSpeakWait[] = +{ + { + tlIdleSpeakWait, + ARRAYSIZE ( tlIdleSpeakWait ), + bits_COND_NEW_ENEMY | + bits_COND_CLIENT_PUSH | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "Idle Speak Wait" + }, +}; + +Task_t tlIdleHello[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_IDLE},//ACT_SIGNAL3 },// Stop and talk + { TASK_TLK_HELLO, (float)0 },// Try to say hello to player + { TASK_TLK_EYECONTACT, (float)0 }, + { TASK_WAIT, (float)0.5 },// wait a bit + { TASK_TLK_HELLO, (float)0 },// Try to say hello to player + { TASK_TLK_EYECONTACT, (float)0 }, + { TASK_WAIT, (float)0.5 },// wait a bit + { TASK_TLK_HELLO, (float)0 },// Try to say hello to player + { TASK_TLK_EYECONTACT, (float)0 }, + { TASK_WAIT, (float)0.5 },// wait a bit + { TASK_TLK_HELLO, (float)0 },// Try to say hello to player + { TASK_TLK_EYECONTACT, (float)0 }, + { TASK_WAIT, (float)0.5 },// wait a bit + +}; + +Schedule_t slIdleHello[] = +{ + { + tlIdleHello, + ARRAYSIZE ( tlIdleHello ), + bits_COND_NEW_ENEMY | + bits_COND_CLIENT_PUSH | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT, + "Idle Hello" + }, +}; + +Task_t tlIdleStopShooting[] = +{ + { TASK_TLK_STOPSHOOTING, (float)0 },// tell player to stop shooting friend + // { TASK_TLK_EYECONTACT, (float)0 },// look at the player +}; + +Schedule_t slIdleStopShooting[] = +{ + { + tlIdleStopShooting, + ARRAYSIZE ( tlIdleStopShooting ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + 0, + "Idle Stop Shooting" + }, +}; + +Task_t tlMoveAway[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_MOVE_AWAY_FAIL }, + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_MOVE_AWAY_PATH, (float)100 }, + { TASK_WALK_PATH_FOR_UNITS, (float)100 }, + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_PLAYER, (float)0.5 }, +}; + +Schedule_t slMoveAway[] = +{ + { + tlMoveAway, + ARRAYSIZE ( tlMoveAway ), + 0, + 0, + "MoveAway" + }, +}; + + +Task_t tlMoveAwayFail[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_PLAYER, (float)0.5 }, +}; + +Schedule_t slMoveAwayFail[] = +{ + { + tlMoveAwayFail, + ARRAYSIZE ( tlMoveAwayFail ), + 0, + 0, + "MoveAwayFail" + }, +}; + + + +Task_t tlMoveAwayFollow[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_TARGET_FACE }, + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_MOVE_AWAY_PATH, (float)100 }, + { TASK_WALK_PATH_FOR_UNITS, (float)100 }, + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE }, +}; + +Schedule_t slMoveAwayFollow[] = +{ + { + tlMoveAwayFollow, + ARRAYSIZE ( tlMoveAwayFollow ), + 0, + 0, + "MoveAwayFollow" + }, +}; + +Task_t tlTlkIdleWatchClient[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_TLK_LOOK_AT_CLIENT, (float)6 }, +}; + +Task_t tlTlkIdleWatchClientStare[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_TLK_CLIENT_STARE, (float)6 }, + { TASK_TLK_STARE, (float)0 }, + { TASK_TLK_IDEALYAW, (float)0 },// look at who I'm talking to + { TASK_FACE_IDEAL, (float)0 }, +// { TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 }, + { TASK_TLK_EYECONTACT, (float)0 }, +}; + +Schedule_t slTlkIdleWatchClient[] = +{ + { + tlTlkIdleWatchClient, + ARRAYSIZE ( tlTlkIdleWatchClient ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_SMELL | + bits_COND_CLIENT_PUSH | + bits_COND_CLIENT_UNSEEN | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT |// sound flags - change these, and you'll break the talking code. + //bits_SOUND_PLAYER | + //bits_SOUND_WORLD | + + bits_SOUND_DANGER | + bits_SOUND_MEAT |// scents + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "TlkIdleWatchClient" + }, + + { + tlTlkIdleWatchClientStare, + ARRAYSIZE ( tlTlkIdleWatchClientStare ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_SMELL | + bits_COND_CLIENT_PUSH | + bits_COND_CLIENT_UNSEEN | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT |// sound flags - change these, and you'll break the talking code. + //bits_SOUND_PLAYER | + //bits_SOUND_WORLD | + + bits_SOUND_DANGER | + bits_SOUND_MEAT |// scents + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "TlkIdleWatchClientStare" + }, +}; + + +Task_t tlTlkIdleEyecontact[] = +{ + { TASK_TLK_IDEALYAW, (float)0 },// look at who I'm talking to + { TASK_FACE_IDEAL, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE},//ACT_SIGNAL3 }, + { TASK_TLK_EYECONTACT, (float)0 },// Wait until speaker is done +}; + +Schedule_t slTlkIdleEyecontact[] = +{ + { + tlTlkIdleEyecontact, + ARRAYSIZE ( tlTlkIdleEyecontact ), + bits_COND_NEW_ENEMY | + bits_COND_CLIENT_PUSH | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "TlkIdleEyecontact" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CTalkMonster ) +{ + slIdleResponse, + slIdleSpeak, + slIdleHello, + slIdleSpeakWait, + slIdleStopShooting, + slMoveAway, + slMoveAwayFollow, + slMoveAwayFail, + slTlkIdleWatchClient, + &slTlkIdleWatchClient[ 1 ], + slTlkIdleEyecontact, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CTalkMonster, CBaseMonster ); + + +void CTalkMonster :: SetActivity ( Activity newActivity ) +{ +/* if (newActivity == ACT_IDLE && IsTalking() ) + newActivity = ACT_SIGNAL3; + + if ( newActivity == ACT_SIGNAL3 && (LookupActivity ( ACT_SIGNAL3 ) == ACTIVITY_NOT_AVAILABLE)) + newActivity = ACT_IDLE; */// buz: remove this signal shit + + CBaseMonster::SetActivity( newActivity ); +} + + +void CTalkMonster :: StartTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_TLK_SPEAK: + // ask question or make statement + FIdleSpeak(); + TaskComplete(); + break; + + case TASK_TLK_RESPOND: + // respond to question + IdleRespond(); + TaskComplete(); + break; + + case TASK_TLK_HELLO: + // greet player + FIdleHello(); + TaskComplete(); + break; + + + case TASK_TLK_STARE: + // let the player know I know he's staring at me. + FIdleStare(); + TaskComplete(); + break; + + case TASK_FACE_PLAYER: + case TASK_TLK_LOOK_AT_CLIENT: + case TASK_TLK_CLIENT_STARE: + // track head to the client for a while. + m_flWaitFinished = gpGlobals->time + pTask->flData; + break; + + case TASK_TLK_EYECONTACT: + break; + + case TASK_TLK_IDEALYAW: + if (m_hTalkTarget != NULL) + { + SetYawSpeed( 60.0f ); + float yaw = VecToYaw(m_hTalkTarget->pev->origin - pev->origin) - pev->angles.y; + + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + + if (yaw < 0) + { + SetIdealYaw( min( yaw + 45, 0 ) + pev->angles.y ); + } + else + { + SetIdealYaw( max( yaw - 45, 0 ) + pev->angles.y ); + } + } + TaskComplete(); + break; + + case TASK_TLK_HEADRESET: + // reset head position after looking at something + m_hTalkTarget = NULL; + TaskComplete(); + break; + + case TASK_TLK_STOPSHOOTING: + // tell player to stop shooting + PlaySentence( m_szGrp[TLK_NOSHOOT], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_NORM ); + TaskComplete(); + break; + + case TASK_CANT_FOLLOW: + StopFollowing( FALSE ); + PlaySentence( m_szGrp[TLK_STOP], RANDOM_FLOAT(2, 2.5), VOL_NORM, ATTN_NORM ); + TaskComplete(); + break; + + case TASK_WALK_PATH_FOR_UNITS: + m_movementActivity = ACT_WALK; + break; + + case TASK_MOVE_AWAY_PATH: + { + Vector dir = pev->angles; + dir.y = pev->ideal_yaw + 180; + Vector move; + + UTIL_MakeVectorsPrivate( dir, move, NULL, NULL ); + dir = pev->origin + move * pTask->flData; + if ( MoveToLocation( ACT_WALK, 2, dir ) ) + { + TaskComplete(); + } + else if ( FindCover( pev->origin, pev->view_ofs, 0, CoverRadius() ) ) + { + // then try for plain ole cover + m_flMoveWaitFinished = gpGlobals->time + 2; + TaskComplete(); + } + else + { + // nowhere to go? + TaskFail(); + } + } + break; + + case TASK_PLAY_SCRIPT: + m_hTalkTarget = NULL; + CBaseMonster::StartTask( pTask ); + break; + + default: + CBaseMonster::StartTask( pTask ); + } +} + + +void CTalkMonster :: RunTask( Task_t *pTask ) +{ + switch( pTask->iTask ) + { + case TASK_TLK_CLIENT_STARE: + case TASK_TLK_LOOK_AT_CLIENT: + + edict_t *pPlayer; + + // track head to the client for a while. + if ( m_MonsterState == MONSTERSTATE_IDLE && + !IsMoving() && + !IsTalking() ) + { + // Get edict for one player + pPlayer = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + + if ( pPlayer ) + { + IdleHeadTurn( pPlayer->v.origin ); + } + } + else + { + // started moving or talking + TaskFail(); + return; + } + + if ( pTask->iTask == TASK_TLK_CLIENT_STARE ) + { + // fail out if the player looks away or moves away. + if ( ( pPlayer->v.origin - pev->origin ).Length2D() > TLK_STARE_DIST ) + { + // player moved away. + TaskFail(); + } + + UTIL_MakeVectors( pPlayer->v.angles ); + if ( UTIL_DotPoints( pPlayer->v.origin, pev->origin, gpGlobals->v_forward ) < m_flFieldOfView ) + { + // player looked away + TaskFail(); + } + } + + if ( gpGlobals->time > m_flWaitFinished ) + { + TaskComplete(); + } + break; + + case TASK_FACE_PLAYER: + { + // Get edict for one player + edict_t *pPlayer = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + + if ( pPlayer ) + { + SetIdealYawToTargetAndUpdate( pPlayer->v.origin, AI_KEEP_YAW_SPEED ); + IdleHeadTurn( pPlayer->v.origin ); + if ( gpGlobals->time > m_flWaitFinished && DeltaIdealYaw() < 10 ) + { + TaskComplete(); + } + } + else + { + TaskFail(); + } + } + break; + + case TASK_TLK_EYECONTACT: + if (!IsMoving() && IsTalking() && m_hTalkTarget != NULL) + { + // ALERT( at_console, "waiting %f\n", m_flStopTalkTime - gpGlobals->time ); + IdleHeadTurn( m_hTalkTarget->pev->origin ); + } + else + { + TaskComplete(); + } + break; + + case TASK_WALK_PATH_FOR_UNITS: + { + float distance; + + distance = (m_vecLastPosition - pev->origin).Length2D(); + + // Walk path until far enough away + if ( distance > pTask->flData || MovementIsComplete() ) + { + TaskComplete(); + RouteClear(); // Stop moving + } + } + break; + case TASK_WAIT_FOR_MOVEMENT: + if (IsTalking() && m_hTalkTarget != NULL) + { + // ALERT(at_console, "walking, talking\n"); + IdleHeadTurn( m_hTalkTarget->pev->origin ); + } + else + { + IdleHeadTurn( pev->origin ); + // override so that during walk, a scientist may talk and greet player + FIdleHello(); + if (RANDOM_LONG(0,m_nSpeak * 20) == 0) + { + FIdleSpeak(); + } + } + + CBaseMonster::RunTask( pTask ); + if (TaskIsComplete()) + IdleHeadTurn( pev->origin ); + break; + + default: + if (IsTalking() && m_hTalkTarget != NULL) + { + IdleHeadTurn( m_hTalkTarget->pev->origin ); + } + else + { + SetBoneController( 0, 0 ); + } + CBaseMonster::RunTask( pTask ); + } +} + + +void CTalkMonster :: Killed( entvars_t *pevAttacker, int iGib ) +{ + // If a client killed me (unless I was already Barnacle'd), make everyone else mad/afraid of him + if ( pevAttacker && m_MonsterState != MONSTERSTATE_PRONE ) + { + if (pevAttacker->flags & FL_CLIENT) + { + AlertFriends(); + LimitFollowers( CBaseEntity::Instance(pevAttacker), 0 ); + } + else if (!m_deathNoticed) + { + // buz: make nearest friend talk about my death + m_deathNoticed = 1; + CBaseEntity *pFriend = FindNearestFriend(FALSE); + if (pFriend && pFriend->IsAlive()) + { + CTalkMonster *pTalkMonster = (CTalkMonster *)pFriend; + pTalkMonster->TalkAboutDeadFriend(this); + } + } + } + + m_hTargetEnt = NULL; + // Don't finish that sentence + StopTalking(); + SetUse( NULL ); + CBaseMonster::Killed( pevAttacker, iGib ); +} + + + +CBaseEntity *CTalkMonster::EnumFriends( CBaseEntity *pPrevious, int listNumber, BOOL bTrace ) +{ + CBaseEntity *pFriend = pPrevious; + char *pszFriend; + TraceResult tr; + Vector vecCheck; + + pszFriend = m_szFriends[ FriendNumber(listNumber) ]; + while (pFriend = UTIL_FindEntityByClassname( pFriend, pszFriend )) + { + if (pFriend == this || !pFriend->IsAlive()) + // don't talk to self or dead people + continue; + if ( bTrace ) + { + vecCheck = pFriend->pev->origin; + vecCheck.z = pFriend->pev->absmax.z; + + UTIL_TraceLine( pev->origin, vecCheck, ignore_monsters, ENT(pev), &tr); + } + else + tr.flFraction = 1.0; + + if (tr.flFraction == 1.0) + { + return pFriend; + } + } + + return NULL; +} + + +void CTalkMonster::AlertFriends( void ) +{ + CBaseEntity *pFriend = NULL; + int i; + + // for each friend in this bsp... + for ( i = 0; i < TLK_CFRIENDS; i++ ) + { + while (pFriend = EnumFriends( pFriend, i, TRUE )) + { + CBaseMonster *pMonster = pFriend->MyMonsterPointer(); + if ( pMonster->IsAlive() ) + { + // don't provoke a friend that's playing a death animation. They're a goner + pMonster->m_afMemory |= bits_MEMORY_PROVOKED; + } + } + } +} + + + +void CTalkMonster::ShutUpFriends( void ) +{ + CBaseEntity *pFriend = NULL; + int i; + + // for each friend in this bsp... + for ( i = 0; i < TLK_CFRIENDS; i++ ) + { + while (pFriend = EnumFriends( pFriend, i, TRUE )) + { + CBaseMonster *pMonster = pFriend->MyMonsterPointer(); + if ( pMonster ) + { + pMonster->SentenceStop(); + } + } + } +} + + +// UNDONE: Keep a follow time in each follower, make a list of followers in this function and do LRU +// UNDONE: Check this in Restore to keep restored monsters from joining a full list of followers +void CTalkMonster::LimitFollowers( CBaseEntity *pPlayer, int maxFollowers ) +{ + CBaseEntity *pFriend = NULL; + int i, count; + + count = 0; + // for each friend in this bsp... + for ( i = 0; i < TLK_CFRIENDS; i++ ) + { + while (pFriend = EnumFriends( pFriend, i, FALSE )) + { + CBaseMonster *pMonster = pFriend->MyMonsterPointer(); + if ( pMonster ) + { + if ( pMonster->m_hTargetEnt == pPlayer ) + { + count++; + if ( count > maxFollowers ) + pMonster->StopFollowing( TRUE ); + } + } + } + } +} + + +float CTalkMonster::TargetDistance( void ) +{ + // If we lose the player, or he dies, return a really large distance + if ( m_hTargetEnt == NULL || !m_hTargetEnt->IsAlive() ) + return 1e6; + + return (m_hTargetEnt->pev->origin - pev->origin).Length(); +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CTalkMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case SCRIPT_EVENT_SENTENCE_RND1: // Play a named sentence group 25% of the time + if (RANDOM_LONG(0,99) < 75) + break; + // fall through... + case SCRIPT_EVENT_SENTENCE: // Play a named sentence group + ShutUpFriends(); + PlaySentence( pEvent->options, RANDOM_FLOAT(2.8, 3.4), VOL_NORM, ATTN_IDLE ); + //ALERT(at_console, "script event speak\n"); + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +// monsters derived from ctalkmonster should call this in precache() + +void CTalkMonster :: TalkInit( void ) +{ + // every new talking monster must reset this global, otherwise + // when a level is loaded, nobody will talk (time is reset to 0) + + CTalkMonster::g_talkWaitTime = 0; + + if (m_iszSpeakAs) //LRC: changing voice groups for monsters + { + char szBuf[64]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_"); + char *szAssign = &(szBuf[strlen(szBuf)]); + + //LRC - this is pretty dodgy; test with save/restore. + strcpy(szAssign,"ANSWER"); + m_szGrp[TLK_ANSWER] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"QUESTION"); + m_szGrp[TLK_QUESTION] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"IDLE"); + m_szGrp[TLK_IDLE] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"STARE"); + m_szGrp[TLK_STARE] = STRING(ALLOC_STRING(szBuf)); + if (pev->spawnflags & SF_MONSTER_PREDISASTER) //LRC + strcpy(szAssign,"PFOLLOW"); + else + strcpy(szAssign,"OK"); + m_szGrp[TLK_USE] = STRING(ALLOC_STRING(szBuf)); + if (pev->spawnflags & SF_MONSTER_PREDISASTER) //LRC + strcpy(szAssign,"PWAIT"); + else + strcpy(szAssign,"WAIT"); + m_szGrp[TLK_UNUSE] = STRING(ALLOC_STRING(szBuf)); + if (pev->spawnflags & SF_MONSTER_PREDISASTER) //LRC + strcpy(szAssign,"POK"); + else + strcpy(szAssign,"NOTOK"); + m_szGrp[TLK_DECLINE] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"STOP"); + m_szGrp[TLK_STOP] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"NOSHOOT"); + m_szGrp[TLK_NOSHOOT] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"HELLO"); + m_szGrp[TLK_HELLO] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"PLHURT1"); + m_szGrp[TLK_PLHURT1] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"PLHURT2"); + m_szGrp[TLK_PLHURT2] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"PLHURT3"); + m_szGrp[TLK_PLHURT3] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"PHELLO"); + m_szGrp[TLK_PHELLO] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"PIDLE"); + m_szGrp[TLK_PIDLE] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"PQUESTION"); + m_szGrp[TLK_PQUESTION] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"SMELL"); + m_szGrp[TLK_SMELL] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"WOUND"); + m_szGrp[TLK_WOUND] = STRING(ALLOC_STRING(szBuf)); + strcpy(szAssign,"MORTAL"); + m_szGrp[TLK_MORTAL] = STRING(ALLOC_STRING(szBuf)); + } + + m_deathNoticed = 0; // buz + m_voicePitch = 100; +} + +//========================================================= +// FindNearestFriend +// Scan for nearest, visible friend. If fPlayer is true, look for +// nearest player +//========================================================= +CBaseEntity *CTalkMonster :: FindNearestFriend(BOOL fPlayer) +{ + CBaseEntity *pFriend = NULL; + CBaseEntity *pNearest = NULL; + float range = 10000000.0; + TraceResult tr; + Vector vecStart = pev->origin; + Vector vecCheck; + int i; + char *pszFriend; + int cfriends; + + vecStart.z = pev->absmax.z; + + if (fPlayer) + cfriends = 1; + else + cfriends = TLK_CFRIENDS; + + // for each type of friend... + + for (i = cfriends-1; i > -1; i--) + { + if (fPlayer) + pszFriend = "player"; + else + pszFriend = m_szFriends[FriendNumber(i)]; + + if (!pszFriend) + continue; + + // for each friend in this bsp... + while (pFriend = UTIL_FindEntityByClassname( pFriend, pszFriend )) + { + if (pFriend == this || !pFriend->IsAlive()) + // don't talk to self or dead people + continue; + + CBaseMonster *pMonster = pFriend->MyMonsterPointer(); + + // If not a monster for some reason, or in a script, or prone + if ( !pMonster || pMonster->m_MonsterState == MONSTERSTATE_SCRIPT || pMonster->m_MonsterState == MONSTERSTATE_PRONE ) + continue; + + vecCheck = pFriend->pev->origin; + vecCheck.z = pFriend->pev->absmax.z; + + // if closer than previous friend, and in range, see if he's visible + + if (range > (vecStart - vecCheck).Length()) + { + UTIL_TraceLine(vecStart, vecCheck, ignore_monsters, ENT(pev), &tr); + + if (tr.flFraction == 1.0) + { + // visible and in range, this is the new nearest scientist + if ((vecStart - vecCheck).Length() < TALKRANGE_MIN) + { + pNearest = pFriend; + range = (vecStart - vecCheck).Length(); + } + } + } + } + } + return pNearest; +} + +int CTalkMonster :: GetVoicePitch( void ) +{ + return m_voicePitch + RANDOM_LONG(0,3); +} + + +void CTalkMonster :: Touch( CBaseEntity *pOther ) +{ + // Did the player touch me? + if ( pOther->IsPlayer() ) + { + // Ignore if pissed at player + if ( m_afMemory & bits_MEMORY_PROVOKED ) + return; + + // Stay put during speech + if ( IsTalking() ) + return; + + // Heuristic for determining if the player is pushing me away + float speed = fabs(pOther->pev->velocity.x) + fabs(pOther->pev->velocity.y); + if ( speed > 50 ) + { + SetConditions( bits_COND_CLIENT_PUSH ); + SetIdealYawToTargetAndUpdate( pOther->pev->origin ); + } + } +} + + + +//========================================================= +// IdleRespond +// Respond to a previous question +//========================================================= +void CTalkMonster :: IdleRespond( void ) +{ + int pitch = GetVoicePitch(); + + // play response + PlaySentence( m_szGrp[TLK_ANSWER], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); +} + +int CTalkMonster :: FOkToSpeak( void ) +{ + // if in the grip of a barnacle, don't speak + if ( m_MonsterState == MONSTERSTATE_PRONE || m_IdealMonsterState == MONSTERSTATE_PRONE ) + { + return FALSE; + } + + // if not alive, certainly don't speak + if ( pev->deadflag != DEAD_NO ) + { + return FALSE; + } + + // if someone else is talking, don't speak + if (gpGlobals->time <= CTalkMonster::g_talkWaitTime) + return FALSE; + + if ( pev->spawnflags & SF_MONSTER_GAG ) + return FALSE; + + if ( m_MonsterState == MONSTERSTATE_PRONE ) + return FALSE; + + // if player is not in pvs, don't speak + if (!IsAlive() || FNullEnt(FIND_CLIENT_IN_PVS(edict()))) + return FALSE; + + // don't talk if you're in combat + if (m_hEnemy != NULL && FVisible( m_hEnemy )) + return FALSE; + + return TRUE; +} + + +int CTalkMonster::CanPlaySentence( BOOL fDisregardState ) +{ + if ( fDisregardState ) + return CBaseMonster::CanPlaySentence( fDisregardState ); + return FOkToSpeak(); +} + +//========================================================= +// FIdleStare +//========================================================= +int CTalkMonster :: FIdleStare( void ) +{ + if (!FOkToSpeak()) + return FALSE; + + PlaySentence( m_szGrp[TLK_STARE], RANDOM_FLOAT(5, 7.5), VOL_NORM, ATTN_IDLE ); + + m_hTalkTarget = FindNearestFriend( TRUE ); + return TRUE; +} + +//========================================================= +// IdleHello +// Try to greet player first time he's seen +//========================================================= +int CTalkMonster :: FIdleHello( void ) +{ + if (!FOkToSpeak()) + return FALSE; + + // if this is first time scientist has seen player, greet him + if (!FBitSet(m_bitsSaid, bit_saidHelloPlayer)) + { + // get a player + CBaseEntity *pPlayer = FindNearestFriend(TRUE); + + if (pPlayer) + { + if (FInViewCone(pPlayer) && FVisible(pPlayer)) + { + m_hTalkTarget = pPlayer; + + if (FBitSet(pev->spawnflags, SF_MONSTER_PREDISASTER)) + PlaySentence( m_szGrp[TLK_PHELLO], RANDOM_FLOAT(3, 3.5), VOL_NORM, ATTN_IDLE ); + else + PlaySentence( m_szGrp[TLK_HELLO], RANDOM_FLOAT(3, 3.5), VOL_NORM, ATTN_IDLE ); + + SetBits(m_bitsSaid, bit_saidHelloPlayer); + + return TRUE; + } + } + } + return FALSE; +} + + +// turn head towards supplied origin +void CTalkMonster :: IdleHeadTurn( Vector &vecFriend ) +{ + // turn head in desired direction only if ent has a turnable head + if (m_afCapability & bits_CAP_TURN_HEAD) + { + float yaw = VecToYaw(vecFriend - pev->origin) - pev->angles.y; + + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + + // turn towards vector + SetBoneController( 0, yaw ); + } +} + +//========================================================= +// FIdleSpeak +// ask question of nearby friend, or make statement +//========================================================= +int CTalkMonster :: FIdleSpeak ( void ) +{ + // try to start a conversation, or make statement + int pitch; + const char *szIdleGroup; + const char *szQuestionGroup; + float duration; + + if (!FOkToSpeak()) + return FALSE; + + // set idle groups based on pre/post disaster + if (FBitSet(pev->spawnflags, SF_MONSTER_PREDISASTER)) + { + szIdleGroup = m_szGrp[TLK_PIDLE]; + szQuestionGroup = m_szGrp[TLK_PQUESTION]; + // set global min delay for next conversation + duration = RANDOM_FLOAT(4.8, 5.2); + } + else + { + szIdleGroup = m_szGrp[TLK_IDLE]; + szQuestionGroup = m_szGrp[TLK_QUESTION]; + // set global min delay for next conversation + duration = RANDOM_FLOAT(2.8, 3.2); + + } + + pitch = GetVoicePitch(); + + // player using this entity is alive and wounded? + CBaseEntity *pTarget = m_hTargetEnt; + + if ( pTarget != NULL ) + { + if ( pTarget->IsPlayer() ) + { + if ( pTarget->IsAlive() ) + { + m_hTalkTarget = m_hTargetEnt; + if (!FBitSet(m_bitsSaid, bit_saidDamageHeavy) && + (m_hTargetEnt->pev->health <= m_hTargetEnt->pev->max_health / 8)) + { + //EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, m_szGrp[TLK_PLHURT3], 1.0, ATTN_IDLE, 0, pitch); + PlaySentence( m_szGrp[TLK_PLHURT3], duration, VOL_NORM, ATTN_IDLE ); + SetBits(m_bitsSaid, bit_saidDamageHeavy); + return TRUE; + } + else if (!FBitSet(m_bitsSaid, bit_saidDamageMedium) && + (m_hTargetEnt->pev->health <= m_hTargetEnt->pev->max_health / 4)) + { + //EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, m_szGrp[TLK_PLHURT2], 1.0, ATTN_IDLE, 0, pitch); + PlaySentence( m_szGrp[TLK_PLHURT2], duration, VOL_NORM, ATTN_IDLE ); + SetBits(m_bitsSaid, bit_saidDamageMedium); + return TRUE; + } + else if (!FBitSet(m_bitsSaid, bit_saidDamageLight) && + (m_hTargetEnt->pev->health <= m_hTargetEnt->pev->max_health / 2)) + { + //EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, m_szGrp[TLK_PLHURT1], 1.0, ATTN_IDLE, 0, pitch); + PlaySentence( m_szGrp[TLK_PLHURT1], duration, VOL_NORM, ATTN_IDLE ); + SetBits(m_bitsSaid, bit_saidDamageLight); + return TRUE; + } + } + else + { + //!!!KELLY - here's a cool spot to have the talkmonster talk about the dead player if we want. + // "Oh dear, Gordon Freeman is dead!" -Scientist + // "Damn, I can't do this without you." -Barney + } + } + } + + // if there is a friend nearby to speak to, play sentence, set friend's response time, return + CBaseEntity *pFriend = FindNearestFriend(FALSE); + + if (pFriend && !(pFriend->IsMoving()) && (RANDOM_LONG(0,99) < 75)) + { + PlaySentence( szQuestionGroup, duration, VOL_NORM, ATTN_IDLE ); + //SENTENCEG_PlayRndSz( ENT(pev), szQuestionGroup, 1.0, ATTN_IDLE, 0, pitch ); + + // force friend to answer + CTalkMonster *pTalkMonster = (CTalkMonster *)pFriend; + m_hTalkTarget = pFriend; + pTalkMonster->SetAnswerQuestion( this ); // UNDONE: This is EVIL!!! + pTalkMonster->m_flStopTalkTime = m_flStopTalkTime; + + m_nSpeak++; + return TRUE; + } + + // otherwise, play an idle statement, try to face client when making a statement. + if ( RANDOM_LONG(0,1) ) + { + //SENTENCEG_PlayRndSz( ENT(pev), szIdleGroup, 1.0, ATTN_IDLE, 0, pitch ); + CBaseEntity *pFriend = FindNearestFriend(TRUE); + + if ( pFriend ) + { + m_hTalkTarget = pFriend; + PlaySentence( szIdleGroup, duration, VOL_NORM, ATTN_IDLE ); + m_nSpeak++; + return TRUE; + } + } + + // didn't speak + Talk( 0 ); + CTalkMonster::g_talkWaitTime = 0; + return FALSE; +} + +void CTalkMonster::PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ) +{ + if ( !bConcurrent ) + ShutUpFriends(); + + // buz - clear this shit +// ClearConditions( bits_COND_CLIENT_PUSH ); // Forget about moving! I've got something to say! + m_useTime = gpGlobals->time + duration; + PlaySentence( pszSentence, duration, volume, attenuation ); + + m_hTalkTarget = pListener; +} + +void CTalkMonster::PlaySentence( const char *pszSentence, float duration, float volume, float attenuation ) +{ + if ( !pszSentence ) + return; + + Talk ( duration ); + + CTalkMonster::g_talkWaitTime = gpGlobals->time + duration + 2.0; + if ( pszSentence[0] == '!' ) + EMIT_SOUND_DYN( edict(), CHAN_VOICE, pszSentence, volume, attenuation, 0, GetVoicePitch()); + else + SENTENCEG_PlayRndSz( edict(), pszSentence, volume, attenuation, 0, GetVoicePitch() ); + + // If you say anything, don't greet the player - you may have already spoken to them + SetBits(m_bitsSaid, bit_saidHelloPlayer); +} + +//========================================================= +// Talk - set a timer that tells us when the monster is done +// talking. +//========================================================= +void CTalkMonster :: Talk( float flDuration ) +{ + if ( flDuration <= 0 ) + { + // no duration :( + m_flStopTalkTime = gpGlobals->time + 3; + } + else + { + m_flStopTalkTime = gpGlobals->time + flDuration; + } +} + +// Prepare this talking monster to answer question +void CTalkMonster :: SetAnswerQuestion( CTalkMonster *pSpeaker ) +{ + if ( !m_pCine ) + ChangeSchedule( slIdleResponse ); + m_hTalkTarget = (CBaseMonster *)pSpeaker; +} + +int CTalkMonster :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType) +{ + if ( IsAlive() ) + { + // if player damaged this entity, have other friends talk about it + if (pevAttacker && m_MonsterState != MONSTERSTATE_PRONE && FBitSet(pevAttacker->flags, FL_CLIENT)) + { + CBaseEntity *pFriend = FindNearestFriend(FALSE); + + if (pFriend && pFriend->IsAlive()) + { + // only if not dead or dying! + CTalkMonster *pTalkMonster = (CTalkMonster *)pFriend; + pTalkMonster->ChangeSchedule( slIdleStopShooting ); + } + } + } + return CBaseMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); +} + + +Schedule_t* CTalkMonster :: GetScheduleOfType ( int Type ) +{ + switch( Type ) + { + case SCHED_MOVE_AWAY: + return slMoveAway; + + case SCHED_MOVE_AWAY_FOLLOW: + return slMoveAwayFollow; + + case SCHED_MOVE_AWAY_FAIL: + return slMoveAwayFail; + + case SCHED_TARGET_FACE: + // speak during 'use' + if (RANDOM_LONG(0,99) < 2) + //ALERT ( at_console, "target chase speak\n" ); + return slIdleSpeakWait; + else + return slIdleStand; + + case SCHED_IDLE_STAND: + { +// ALERT(at_console, "AAAA IDLE STAND!!!!\n"); + // if never seen player, try to greet him + if (!FBitSet(m_bitsSaid, bit_saidHelloPlayer)) + { + return slIdleHello; + } + + // sustained light wounds? + if (!FBitSet(m_bitsSaid, bit_saidWoundLight) && (pev->health <= (pev->max_health * 0.75))) + { + //SENTENCEG_PlayRndSz( ENT(pev), m_szGrp[TLK_WOUND], 1.0, ATTN_IDLE, 0, GetVoicePitch() ); + //CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(2.8, 3.2); + PlaySentence( m_szGrp[TLK_WOUND], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + SetBits(m_bitsSaid, bit_saidWoundLight); + return slIdleStand; + } + // sustained heavy wounds? + else if (!FBitSet(m_bitsSaid, bit_saidWoundHeavy) && (pev->health <= (pev->max_health * 0.5))) + { + //SENTENCEG_PlayRndSz( ENT(pev), m_szGrp[TLK_MORTAL], 1.0, ATTN_IDLE, 0, GetVoicePitch() ); + //CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(2.8, 3.2); + PlaySentence( m_szGrp[TLK_MORTAL], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + SetBits(m_bitsSaid, bit_saidWoundHeavy); + return slIdleStand; + } + + // talk about world + if (FOkToSpeak() && RANDOM_LONG(0,m_nSpeak * 2) == 0) + { + return slIdleSpeak; + } + + if ( !IsTalking() && HasConditions ( bits_COND_SEE_CLIENT ) && RANDOM_LONG( 0, 6 ) == 0 ) + { + edict_t *pPlayer = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + + if ( pPlayer ) + { + // watch the client. + UTIL_MakeVectors ( pPlayer->v.angles ); + if ( ( pPlayer->v.origin - pev->origin ).Length2D() < TLK_STARE_DIST && + UTIL_DotPoints( pPlayer->v.origin, pev->origin, gpGlobals->v_forward ) >= m_flFieldOfView ) + { + // go into the special STARE schedule if the player is close, and looking at me too. + return &slTlkIdleWatchClient[ 1 ]; + } + + return slTlkIdleWatchClient; + } + } + else + { + if (IsTalking()) + // look at who we're talking to + return slTlkIdleEyecontact; + else + // regular standing idle + return slIdleStand; + } + + + // NOTE - caller must first CTalkMonster::GetScheduleOfType, + // then check result and decide what to return ie: if sci gets back + // slIdleStand, return slIdleSciStand + } + break; + } + + return CBaseMonster::GetScheduleOfType( Type ); +} + +//========================================================= +// IsTalking - am I saying a sentence right now? +//========================================================= +BOOL CTalkMonster :: IsTalking( void ) +{ + if ( m_flStopTalkTime > gpGlobals->time ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// If there's a player around, watch him. +//========================================================= +void CTalkMonster :: PrescheduleThink ( void ) +{ + if ( !HasConditions ( bits_COND_SEE_CLIENT ) ) + { + SetConditions ( bits_COND_CLIENT_UNSEEN ); + } +} + +// try to smell something +void CTalkMonster :: TrySmellTalk( void ) +{ + if ( !FOkToSpeak() ) + return; + + // clear smell bits periodically + if ( gpGlobals->time > m_flLastSaidSmelled ) + { +// ALERT ( at_aiconsole, "Clear smell bits\n" ); + ClearBits(m_bitsSaid, bit_saidSmelled); + } + // smelled something? + if (!FBitSet(m_bitsSaid, bit_saidSmelled) && HasConditions ( bits_COND_SMELL )) + { + PlaySentence( m_szGrp[TLK_SMELL], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + m_flLastSaidSmelled = gpGlobals->time + 60;// don't talk about the stinky for a while. + SetBits(m_bitsSaid, bit_saidSmelled); + } +} + + + +int CTalkMonster::IRelationship( CBaseEntity *pTarget ) +{ + if ( pTarget->IsPlayer() ) + if ( m_afMemory & bits_MEMORY_PROVOKED ) + return R_HT; + return CBaseMonster::IRelationship( pTarget ); +} + + +void CTalkMonster::StopFollowing( BOOL clearSchedule, int speakSentence ) +{ + if ( IsFollowing() ) + { + if ( !(m_afMemory & bits_MEMORY_PROVOKED) ) + { + if (speakSentence) + { + PlaySentence( m_szGrp[TLK_UNUSE], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + m_hTalkTarget = m_hTargetEnt; + } + } + + if ( m_movementGoal == MOVEGOAL_TARGETENT ) + RouteClear(); // Stop him from walking toward the player + m_hTargetEnt = NULL; + if ( clearSchedule ) + ClearSchedule(); + if ( m_hEnemy != NULL ) + m_IdealMonsterState = MONSTERSTATE_COMBAT; + } +} + + +void CTalkMonster::StartFollowing( CBaseEntity *pLeader, int speakSentence ) +{ + if ( m_pCine ) + m_pCine->CancelScript(); + + if ( m_hEnemy != NULL ) + m_IdealMonsterState = MONSTERSTATE_ALERT; + + m_hTargetEnt = pLeader; + if (speakSentence) // buz + { + PlaySentence( m_szGrp[TLK_USE], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + m_hTalkTarget = m_hTargetEnt; + } + ClearConditions( bits_COND_CLIENT_PUSH ); + ClearSchedule(); +} + +//LRC- redefined, now returns true if following would be physically possible +BOOL CTalkMonster::CanFollow( void ) +{ + if ( m_MonsterState == MONSTERSTATE_SCRIPT ) + { + if ( !m_pCine->CanInterrupt() ) + return FALSE; + } + + if ( !IsAlive() ) + return FALSE; + + return TRUE; +} + + +//LRC- rewritten +void CTalkMonster :: FollowerUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Don't allow use during a scripted_sentence + if ( (m_useTime > gpGlobals->time) && useType == USE_SET ) + return; + + //ALERT(at_console,"Talkmonster was Used: "); + + // buz: use types - + // player sends USE_SET when pushes +use, and following can be declined by flag or master. + // triggers sends USE_ON (start following), USE_OFF (stop), and USE_TOGGLE. Hlag and master are ignored + + // CanFollow is now true if the monster could physically follow anyone + if ( pCaller != NULL && pCaller->IsPlayer() && CanFollow() ) + { + if ( !IsFollowing() ) + { + // Pre-disaster followers can't be used unless they've got a master to override their behaviour... + if (useType == USE_SET && (IsLockedByMaster() || (pev->spawnflags & SF_MONSTER_PREDISASTER && !m_sMaster))) + { + //ALERT(at_console,"Decline\n"); + DeclineFollowing(); + m_useTime = gpGlobals->time + 3;// buz + } + else if (useType != USE_OFF) + { + LimitFollowers( pCaller , 1 ); + if ( m_afMemory & bits_MEMORY_PROVOKED ) + { + //ALERT(at_console,"Fail\n"); + ALERT( at_aiconsole, "I'm not following you, you evil person!\n" ); + } + else + { + //ALERT(at_console,"Start\n"); + StartFollowing( pCaller, useType == USE_SET ); // buz: say sentence only if directly used by player + SetBits(m_bitsSaid, bit_saidHelloPlayer); // Don't say hi after you've started following + } + } + } + else + { + // buz: speak also decline sentence if player tries to stop blocked monster + //ALERT(at_console,"Stop\n"); + if (useType == USE_SET && (IsLockedByMaster() || (pev->spawnflags & SF_MONSTER_PREDISASTER && !m_sMaster))) + { + //ALERT(at_console,"Decline\n"); + DeclineFollowing(); + m_useTime = gpGlobals->time + 3;// buz + } + else if (useType != USE_ON) + { + StopFollowing( TRUE, useType == USE_SET ); // buz: say sentence only if directly used by player + } + } + } +} + +void CTalkMonster::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "UseSentence")) + { + m_iszUse = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "UnUseSentence")) + { + m_iszUnUse = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "RefusalSentence")) //LRC + { + m_iszDecline = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "SpeakAs")) //LRC + { + m_iszSpeakAs = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + + +void CTalkMonster::Precache( void ) +{ + if ( m_iszUse ) + m_szGrp[TLK_USE] = STRING( m_iszUse ); + if ( m_iszUnUse ) + m_szGrp[TLK_UNUSE] = STRING( m_iszUnUse ); + if ( m_iszDecline ) //LRC + m_szGrp[TLK_DECLINE] = STRING( m_iszDecline ); +} + diff --git a/dlls/talkmonster.h b/dlls/talkmonster.h new file mode 100644 index 0000000..92276b4 --- /dev/null +++ b/dlls/talkmonster.h @@ -0,0 +1,190 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef TALKMONSTER_H +#define TALKMONSTER_H + +#ifndef MONSTERS_H +#include "monsters.h" +#endif + +//========================================================= +// Talking monster base class +// Used for scientists and barneys +//========================================================= + +#define TALKRANGE_MIN 500.0 // don't talk to anyone farther away than this + +#define TLK_STARE_DIST 128 // anyone closer than this and looking at me is probably staring at me. + +#define bit_saidDamageLight (1<<0) // bits so we don't repeat key sentences +#define bit_saidDamageMedium (1<<1) +#define bit_saidDamageHeavy (1<<2) +#define bit_saidHelloPlayer (1<<3) +#define bit_saidWoundLight (1<<4) +#define bit_saidWoundHeavy (1<<5) +#define bit_saidHeard (1<<6) +#define bit_saidSmelled (1<<7) + +#define TLK_CFRIENDS 6 + +typedef enum +{ + TLK_ANSWER = 0, + TLK_QUESTION, + TLK_IDLE, + TLK_STARE, + TLK_USE, + TLK_UNUSE, + TLK_DECLINE, //LRC- refuse to accompany + TLK_STOP, + TLK_NOSHOOT, + TLK_HELLO, + TLK_PHELLO, + TLK_PIDLE, + TLK_PQUESTION, + TLK_PLHURT1, + TLK_PLHURT2, + TLK_PLHURT3, + TLK_SMELL, + TLK_WOUND, + TLK_MORTAL, + + TLK_CGROUPS, // MUST be last entry +} TALKGROUPNAMES; + + +enum +{ + SCHED_CANT_FOLLOW = LAST_COMMON_SCHEDULE + 1, + SCHED_MOVE_AWAY, // Try to get out of the player's way + SCHED_MOVE_AWAY_FOLLOW, // same, but follow afterward + SCHED_MOVE_AWAY_FAIL, // Turn back toward player + + LAST_TALKMONSTER_SCHEDULE, // MUST be last +}; + +enum +{ + TASK_CANT_FOLLOW = LAST_COMMON_TASK + 1, + TASK_MOVE_AWAY_PATH, + TASK_WALK_PATH_FOR_UNITS, + + TASK_TLK_RESPOND, // say my response + TASK_TLK_SPEAK, // question or remark + TASK_TLK_HELLO, // Try to say hello to player + TASK_TLK_HEADRESET, // reset head position + TASK_TLK_STOPSHOOTING, // tell player to stop shooting friend + TASK_TLK_STARE, // let the player know I know he's staring at me. + TASK_TLK_LOOK_AT_CLIENT,// faces player if not moving and not talking and in idle. + TASK_TLK_CLIENT_STARE, // same as look at client, but says something if the player stares. + TASK_TLK_EYECONTACT, // maintain eyecontact with person who I'm talking to + TASK_TLK_IDEALYAW, // set ideal yaw to face who I'm talking to + TASK_FACE_PLAYER, // Face the player + + LAST_TALKMONSTER_TASK, // MUST be last +}; + +class CTalkMonster : public CBaseMonster +{ +public: + void TalkInit( void ); + CBaseEntity *FindNearestFriend(BOOL fPlayer); + float TargetDistance( void ); + void StopTalking( void ) { SentenceStop(); } + + // Base Monster functions + void Precache( void ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType); + void Touch( CBaseEntity *pOther ); + void Killed( entvars_t *pevAttacker, int iGib ); + int IRelationship ( CBaseEntity *pTarget ); + virtual int CanPlaySentence( BOOL fDisregardState ); + virtual void PlaySentence( const char *pszSentence, float duration, float volume, float attenuation ); + void PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ); + void KeyValue( KeyValueData *pkvd ); + + // AI functions + void SetActivity ( Activity newActivity ); + Schedule_t *GetScheduleOfType ( int Type ); + void StartTask( Task_t *pTask ); + void RunTask( Task_t *pTask ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void PrescheduleThink( void ); + + + // Conversations / communication + int GetVoicePitch( void ); + void IdleRespond( void ); + int FIdleSpeak( void ); + int FIdleStare( void ); + int FIdleHello( void ); + void IdleHeadTurn( Vector &vecFriend ); + virtual int FOkToSpeak( void ); // buz: military overrides it + void TrySmellTalk( void ); + CBaseEntity *EnumFriends( CBaseEntity *pentPrevious, int listNumber, BOOL bTrace ); + void AlertFriends( void ); + void ShutUpFriends( void ); + BOOL IsTalking( void ); + void Talk( float flDuration ); + // For following + BOOL CanFollow( void ); + BOOL IsFollowing( void ) { return m_hTargetEnt != NULL && m_hTargetEnt->IsPlayer(); } + void StopFollowing( BOOL clearSchedule, int speakSentence = TRUE ); + void StartFollowing( CBaseEntity *pLeader, int speakSentence = TRUE ); + virtual void DeclineFollowing( void ) {} + void LimitFollowers( CBaseEntity *pPlayer, int maxFollowers ); + + void EXPORT FollowerUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual void SetAnswerQuestion( CTalkMonster *pSpeaker ); + virtual int FriendNumber( int arrayNumber ) { return arrayNumber; } + + virtual void TalkAboutDeadFriend( CTalkMonster *pfriend ) {}; // buz + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + + static char *m_szFriends[TLK_CFRIENDS]; // array of friend names + static float g_talkWaitTime; + + int m_bitsSaid; // set bits for sentences we don't want repeated + int m_nSpeak; // number of times initiated talking + int m_voicePitch; // pitch of voice for this head + const char *m_szGrp[TLK_CGROUPS]; // sentence group names + float m_useTime; // Don't allow +USE until this time + int m_iszUse; // Custom +USE sentence group (follow) + int m_iszUnUse; // Custom +USE sentence group (stop following) + int m_iszDecline; // Custom +USE sentence group (refuse to follow) LRC + int m_iszSpeakAs; // Change the prefix for all this monster's speeches LRC + + int m_deathNoticed; // buz: friends are noticed about my death + + float m_flLastSaidSmelled;// last time we talked about something that stinks + float m_flStopTalkTime;// when in the future that I'll be done saying this sentence. + + EHANDLE m_hTalkTarget; // who to look at while talking + CUSTOM_SCHEDULES; +}; + + +// Clients can push talkmonsters out of their way +#define bits_COND_CLIENT_PUSH ( bits_COND_SPECIAL1 ) +// Don't see a client right now. +#define bits_COND_CLIENT_UNSEEN ( bits_COND_SPECIAL2 ) + + +#endif //TALKMONSTER_H diff --git a/dlls/teamplay_gamerules.cpp b/dlls/teamplay_gamerules.cpp new file mode 100644 index 0000000..9bc7830 --- /dev/null +++ b/dlls/teamplay_gamerules.cpp @@ -0,0 +1,630 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// teamplay_gamerules.cpp +// +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "teamplay_gamerules.h" +#include "game.h" + +static char team_names[MAX_TEAMS][MAX_TEAMNAME_LENGTH]; +static int team_scores[MAX_TEAMS]; +static int num_teams = 0; + +extern DLL_GLOBAL BOOL g_fGameOver; + +CHalfLifeTeamplay :: CHalfLifeTeamplay() +{ + m_DisableDeathMessages = FALSE; + m_DisableDeathPenalty = FALSE; + + memset( team_names, 0, sizeof(team_names) ); + memset( team_scores, 0, sizeof(team_scores) ); + num_teams = 0; + + // Copy over the team from the server config + m_szTeamList[0] = 0; + + // Cache this because the team code doesn't want to deal with changing this in the middle of a game + strncpy( m_szTeamList, teamlist.string, TEAMPLAY_TEAMLISTLENGTH ); + + edict_t *pWorld = INDEXENT(0); + if ( pWorld && pWorld->v.team ) + { + if ( teamoverride.value ) + { + const char *pTeamList = STRING(pWorld->v.team); + if ( pTeamList && strlen(pTeamList) ) + { + strncpy( m_szTeamList, pTeamList, TEAMPLAY_TEAMLISTLENGTH ); + } + } + } + // Has the server set teams + if ( strlen( m_szTeamList ) ) + m_teamLimit = TRUE; + else + m_teamLimit = FALSE; + + RecountTeams(); +} + +extern cvar_t timeleft, fragsleft; + +#include "voice_gamemgr.h" +extern CVoiceGameMgr g_VoiceGameMgr; + +void CHalfLifeTeamplay :: Think ( void ) +{ + ///// Check game rules ///// + static int last_frags; + static int last_time; + + int frags_remaining = 0; + int time_remaining = 0; + + g_VoiceGameMgr.Update(gpGlobals->frametime); + + if ( g_fGameOver ) // someone else quit the game already + { + CHalfLifeMultiplay::Think(); + return; + } + + float flTimeLimit = CVAR_GET_FLOAT("mp_timelimit") * 60; + + time_remaining = (int)(flTimeLimit ? ( flTimeLimit - gpGlobals->time ) : 0); + + if ( flTimeLimit != 0 && gpGlobals->time >= flTimeLimit ) + { + GoToIntermission(); + return; + } + + float flFragLimit = fraglimit.value; + if ( flFragLimit ) + { + int bestfrags = 9999; + int remain; + + // check if any team is over the frag limit + for ( int i = 0; i < num_teams; i++ ) + { + if ( team_scores[i] >= flFragLimit ) + { + GoToIntermission(); + return; + } + + remain = flFragLimit - team_scores[i]; + if ( remain < bestfrags ) + { + bestfrags = remain; + } + } + frags_remaining = bestfrags; + } + + // Updates when frags change + if ( frags_remaining != last_frags ) + { + g_engfuncs.pfnCvar_DirectSet( &fragsleft, UTIL_VarArgs( "%i", frags_remaining ) ); + } + + // Updates once per second + if ( timeleft.value != last_time ) + { + g_engfuncs.pfnCvar_DirectSet( &timeleft, UTIL_VarArgs( "%i", time_remaining ) ); + } + + last_frags = frags_remaining; + last_time = time_remaining; +} + +//========================================================= +// ClientCommand +// the user has typed a command which is unrecognized by everything else; +// this check to see if the gamerules knows anything about the command +//========================================================= +BOOL CHalfLifeTeamplay :: ClientCommand( CBasePlayer *pPlayer, const char *pcmd ) +{ + if(g_VoiceGameMgr.ClientCommand(pPlayer, pcmd)) + return TRUE; + + if ( FStrEq( pcmd, "menuselect" ) ) + { + if ( CMD_ARGC() < 2 ) + return TRUE; + + int slot = atoi( CMD_ARGV(1) ); + + // select the item from the current menu + + return TRUE; + } + + return FALSE; +} + +extern int gmsgGameMode; +extern int gmsgSayText; +extern int gmsgTeamInfo; +extern int gmsgTeamNames; +extern int gmsgScoreInfo; + +void CHalfLifeTeamplay :: UpdateGameMode( CBasePlayer *pPlayer ) +{ + MESSAGE_BEGIN( MSG_ONE, gmsgGameMode, NULL, pPlayer->edict() ); + WRITE_BYTE( 1 ); // game mode teamplay + MESSAGE_END(); +} + + +const char *CHalfLifeTeamplay::SetDefaultPlayerTeam( CBasePlayer *pPlayer ) +{ + // copy out the team name from the model + char *mdls = g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model" ); + strncpy( pPlayer->m_szTeamName, mdls, TEAM_NAME_LENGTH ); + + RecountTeams(); + + // update the current player of the team he is joining + if ( pPlayer->m_szTeamName[0] == '\0' || !IsValidTeam( pPlayer->m_szTeamName ) || defaultteam.value ) + { + const char *pTeamName = NULL; + + if ( defaultteam.value ) + { + pTeamName = team_names[0]; + } + else + { + pTeamName = TeamWithFewestPlayers(); + } + strncpy( pPlayer->m_szTeamName, pTeamName, TEAM_NAME_LENGTH ); + } + + return pPlayer->m_szTeamName; +} + + +//========================================================= +// InitHUD +//========================================================= +void CHalfLifeTeamplay::InitHUD( CBasePlayer *pPlayer ) +{ + int i; + + SetDefaultPlayerTeam( pPlayer ); + CHalfLifeMultiplay::InitHUD( pPlayer ); + + // Send down the team names + MESSAGE_BEGIN( MSG_ONE, gmsgTeamNames, NULL, pPlayer->edict() ); + WRITE_BYTE( num_teams ); + for ( i = 0; i < num_teams; i++ ) + { + WRITE_STRING( team_names[ i ] ); + } + MESSAGE_END(); + + RecountTeams(); + + char *mdls = g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model" ); + // update the current player of the team he is joining + char text[1024]; + if ( !strcmp( mdls, pPlayer->m_szTeamName ) ) + { + sprintf( text, "* you are on team \'%s\'\n", pPlayer->m_szTeamName ); + } + else + { + sprintf( text, "* assigned to team %s\n", pPlayer->m_szTeamName ); + } + + ChangePlayerTeam( pPlayer, pPlayer->m_szTeamName, FALSE, FALSE ); + UTIL_SayText( text, pPlayer ); + int clientIndex = pPlayer->entindex(); + RecountTeams(); + // update this player with all the other players team info + // loop through all active players and send their team info to the new client + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *plr = UTIL_PlayerByIndex( i ); + if ( plr && IsValidTeam( plr->TeamID() ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgTeamInfo, NULL, pPlayer->edict() ); + WRITE_BYTE( plr->entindex() ); + WRITE_STRING( plr->TeamID() ); + MESSAGE_END(); + } + } +} + + +void CHalfLifeTeamplay::ChangePlayerTeam( CBasePlayer *pPlayer, const char *pTeamName, BOOL bKill, BOOL bGib ) +{ + int damageFlags = DMG_GENERIC; + int clientIndex = pPlayer->entindex(); + + if ( !bGib ) + { + damageFlags |= DMG_NEVERGIB; + } + else + { + damageFlags |= DMG_ALWAYSGIB; + } + + if ( bKill ) + { + // kill the player, remove a death, and let them start on the new team + m_DisableDeathMessages = TRUE; + m_DisableDeathPenalty = TRUE; + + entvars_t *pevWorld = VARS( INDEXENT(0) ); + pPlayer->TakeDamage( pevWorld, pevWorld, 900, damageFlags ); + + m_DisableDeathMessages = FALSE; + m_DisableDeathPenalty = FALSE; + } + + // copy out the team name from the model + strncpy( pPlayer->m_szTeamName, pTeamName, TEAM_NAME_LENGTH ); + + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model", pPlayer->m_szTeamName ); + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "team", pPlayer->m_szTeamName ); + + // notify everyone's HUD of the team change + MESSAGE_BEGIN( MSG_ALL, gmsgTeamInfo ); + WRITE_BYTE( clientIndex ); + WRITE_STRING( pPlayer->m_szTeamName ); + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_ALL, gmsgScoreInfo ); + WRITE_BYTE( clientIndex ); + WRITE_SHORT( pPlayer->pev->frags ); + WRITE_SHORT( pPlayer->m_iDeaths ); + WRITE_SHORT( 0 ); + WRITE_SHORT( g_pGameRules->GetTeamIndex( pPlayer->m_szTeamName ) + 1 ); + MESSAGE_END(); +} + + +//========================================================= +// ClientUserInfoChanged +//========================================================= +void CHalfLifeTeamplay::ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ) +{ + char text[1024]; + + // prevent skin/color/model changes + char *mdls = g_engfuncs.pfnInfoKeyValue( infobuffer, "model" ); + + if ( !stricmp( mdls, pPlayer->m_szTeamName ) ) + return; + + if ( defaultteam.value ) + { + int clientIndex = pPlayer->entindex(); + + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model", pPlayer->m_szTeamName ); + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "team", pPlayer->m_szTeamName ); + sprintf( text, "* Not allowed to change teams in this game!\n" ); + UTIL_SayText( text, pPlayer ); + return; + } + + if ( defaultteam.value || !IsValidTeam( mdls ) ) + { + int clientIndex = pPlayer->entindex(); + + g_engfuncs.pfnSetClientKeyValue( clientIndex, g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model", pPlayer->m_szTeamName ); + sprintf( text, "* Can't change team to \'%s\'\n", mdls ); + UTIL_SayText( text, pPlayer ); + sprintf( text, "* Server limits teams to \'%s\'\n", m_szTeamList ); + UTIL_SayText( text, pPlayer ); + return; + } + // notify everyone of the team change + sprintf( text, "* %s has changed to team \'%s\'\n", STRING(pPlayer->pev->netname), mdls ); + UTIL_SayTextAll( text, pPlayer ); + + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" joined team \"%s\"\n", + STRING(pPlayer->pev->netname), + GETPLAYERUSERID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), + pPlayer->m_szTeamName, + mdls ); + + ChangePlayerTeam( pPlayer, mdls, TRUE, TRUE ); + // recound stuff + RecountTeams( TRUE ); +} + +extern int gmsgDeathMsg; + +//========================================================= +// Deathnotice. +//========================================================= +void CHalfLifeTeamplay::DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pevInflictor ) +{ + if ( m_DisableDeathMessages ) + return; + + if ( pVictim && pKiller && pKiller->flags & FL_CLIENT ) + { + CBasePlayer *pk = (CBasePlayer*) CBaseEntity::Instance( pKiller ); + + if ( pk ) + { + if ( (pk != pVictim) && (PlayerRelationship( pVictim, pk ) == GR_TEAMMATE) ) + { + MESSAGE_BEGIN( MSG_ALL, gmsgDeathMsg ); + WRITE_BYTE( ENTINDEX(ENT(pKiller)) ); // the killer + WRITE_BYTE( ENTINDEX(pVictim->edict()) ); // the victim + WRITE_STRING( "teammate" ); // flag this as a teammate kill + MESSAGE_END(); + return; + } + } + } + + CHalfLifeMultiplay::DeathNotice( pVictim, pKiller, pevInflictor ); +} + +//========================================================= +//========================================================= +void CHalfLifeTeamplay :: PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) +{ + if ( !m_DisableDeathPenalty ) + { + CHalfLifeMultiplay::PlayerKilled( pVictim, pKiller, pInflictor ); + RecountTeams(); + } +} + + +//========================================================= +// IsTeamplay +//========================================================= +BOOL CHalfLifeTeamplay::IsTeamplay( void ) +{ + return TRUE; +} + +BOOL CHalfLifeTeamplay::FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ) +{ + if ( pAttacker && PlayerRelationship( pPlayer, pAttacker ) == GR_TEAMMATE ) + { + // my teammate hit me. + if ( (friendlyfire.value == 0) && (pAttacker != pPlayer) ) + { + // friendly fire is off, and this hit came from someone other than myself, then don't get hurt + return FALSE; + } + } + + return CHalfLifeMultiplay::FPlayerCanTakeDamage( pPlayer, pAttacker ); +} + +//========================================================= +//========================================================= +int CHalfLifeTeamplay::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) +{ + // half life multiplay has a simple concept of Player Relationships. + // you are either on another player's team, or you are not. + if ( !pPlayer || !pTarget || !pTarget->IsPlayer() ) + return GR_NOTTEAMMATE; + + if ( (*GetTeamID(pPlayer) != '\0') && (*GetTeamID(pTarget) != '\0') && !stricmp( GetTeamID(pPlayer), GetTeamID(pTarget) ) ) + { + return GR_TEAMMATE; + } + + return GR_NOTTEAMMATE; +} + +//========================================================= +//========================================================= +BOOL CHalfLifeTeamplay::ShouldAutoAim( CBasePlayer *pPlayer, edict_t *target ) +{ + // always autoaim, unless target is a teammate + CBaseEntity *pTgt = CBaseEntity::Instance( target ); + if ( pTgt && pTgt->IsPlayer() ) + { + if ( PlayerRelationship( pPlayer, pTgt ) == GR_TEAMMATE ) + return FALSE; // don't autoaim at teammates + } + + return CHalfLifeMultiplay::ShouldAutoAim( pPlayer, target ); +} + +//========================================================= +//========================================================= +int CHalfLifeTeamplay::IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) +{ + if ( !pKilled ) + return 0; + + if ( !pAttacker ) + return 1; + + if ( pAttacker != pKilled && PlayerRelationship( pAttacker, pKilled ) == GR_TEAMMATE ) + return -1; + + return 1; +} + +//========================================================= +//========================================================= +const char *CHalfLifeTeamplay::GetTeamID( CBaseEntity *pEntity ) +{ + if ( pEntity == NULL || pEntity->pev == NULL ) + return ""; + + // return their team name + return pEntity->TeamID(); +} + + +int CHalfLifeTeamplay::GetTeamIndex( const char *pTeamName ) +{ + if ( pTeamName && *pTeamName != 0 ) + { + // try to find existing team + for ( int tm = 0; tm < num_teams; tm++ ) + { + if ( !stricmp( team_names[tm], pTeamName ) ) + return tm; + } + } + + return -1; // No match +} + + +const char *CHalfLifeTeamplay::GetIndexedTeamName( int teamIndex ) +{ + if ( teamIndex < 0 || teamIndex >= num_teams ) + return ""; + + return team_names[ teamIndex ]; +} + + +BOOL CHalfLifeTeamplay::IsValidTeam( const char *pTeamName ) +{ + if ( !m_teamLimit ) // Any team is valid if the teamlist isn't set + return TRUE; + + return ( GetTeamIndex( pTeamName ) != -1 ) ? TRUE : FALSE; +} + +const char *CHalfLifeTeamplay::TeamWithFewestPlayers( void ) +{ + int i; + int minPlayers = MAX_TEAMS; + int teamCount[ MAX_TEAMS ]; + char *pTeamName = NULL; + + memset( teamCount, 0, MAX_TEAMS * sizeof(int) ); + + // loop through all clients, count number of players on each team + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *plr = UTIL_PlayerByIndex( i ); + + if ( plr ) + { + int team = GetTeamIndex( plr->TeamID() ); + if ( team >= 0 ) + teamCount[team] ++; + } + } + + // Find team with least players + for ( i = 0; i < num_teams; i++ ) + { + if ( teamCount[i] < minPlayers ) + { + minPlayers = teamCount[i]; + pTeamName = team_names[i]; + } + } + + return pTeamName; +} + + +//========================================================= +//========================================================= +void CHalfLifeTeamplay::RecountTeams( bool bResendInfo ) +{ + char *pName; + char teamlist[TEAMPLAY_TEAMLISTLENGTH]; + + // loop through all teams, recounting everything + num_teams = 0; + + // Copy all of the teams from the teamlist + // make a copy because strtok is destructive + strcpy( teamlist, m_szTeamList ); + pName = teamlist; + pName = strtok( pName, ";" ); + while ( pName != NULL && *pName ) + { + if ( GetTeamIndex( pName ) < 0 ) + { + strcpy( team_names[num_teams], pName ); + num_teams++; + } + pName = strtok( NULL, ";" ); + } + + if ( num_teams < 2 ) + { + num_teams = 0; + m_teamLimit = FALSE; + } + + // Sanity check + memset( team_scores, 0, sizeof(team_scores) ); + + // loop through all clients + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *plr = UTIL_PlayerByIndex( i ); + + if ( plr ) + { + const char *pTeamName = plr->TeamID(); + // try add to existing team + int tm = GetTeamIndex( pTeamName ); + + if ( tm < 0 ) // no team match found + { + if ( !m_teamLimit ) + { + // add to new team + tm = num_teams; + num_teams++; + team_scores[tm] = 0; + strncpy( team_names[tm], pTeamName, MAX_TEAMNAME_LENGTH ); + } + } + + if ( tm >= 0 ) + { + team_scores[tm] += plr->pev->frags; + } + + if ( bResendInfo ) //Someone's info changed, let's send the team info again. + { + if ( plr && IsValidTeam( plr->TeamID() ) ) + { + MESSAGE_BEGIN( MSG_ALL, gmsgTeamInfo, NULL ); + WRITE_BYTE( plr->entindex() ); + WRITE_STRING( plr->TeamID() ); + MESSAGE_END(); + } + } + } + } +} diff --git a/dlls/teamplay_gamerules.h b/dlls/teamplay_gamerules.h new file mode 100644 index 0000000..5d4246b --- /dev/null +++ b/dlls/teamplay_gamerules.h @@ -0,0 +1,57 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// teamplay_gamerules.h +// + +#define MAX_TEAMNAME_LENGTH 16 +#define MAX_TEAMS 32 + +#define TEAMPLAY_TEAMLISTLENGTH MAX_TEAMS*MAX_TEAMNAME_LENGTH + +class CHalfLifeTeamplay : public CHalfLifeMultiplay +{ +public: + CHalfLifeTeamplay(); + + virtual BOOL ClientCommand( CBasePlayer *pPlayer, const char *pcmd ); + virtual void ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ); + virtual BOOL IsTeamplay( void ); + virtual BOOL FPlayerCanTakeDamage( CBasePlayer *pPlayer, CBaseEntity *pAttacker ); + virtual int PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ); + virtual const char *GetTeamID( CBaseEntity *pEntity ); + virtual BOOL ShouldAutoAim( CBasePlayer *pPlayer, edict_t *target ); + virtual int IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ); + virtual void InitHUD( CBasePlayer *pl ); + virtual void DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pevInflictor ); + virtual const char *GetGameDescription( void ) { return "HL Teamplay"; } // this is the game name that gets seen in the server browser + virtual void UpdateGameMode( CBasePlayer *pPlayer ); // the client needs to be informed of the current game mode + virtual void PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ); + virtual void Think ( void ); + virtual int GetTeamIndex( const char *pTeamName ); + virtual const char *GetIndexedTeamName( int teamIndex ); + virtual BOOL IsValidTeam( const char *pTeamName ); + const char *SetDefaultPlayerTeam( CBasePlayer *pPlayer ); + virtual void ChangePlayerTeam( CBasePlayer *pPlayer, const char *pTeamName, BOOL bKill, BOOL bGib ); + +private: + void RecountTeams( bool bResendInfo = FALSE ); + const char *TeamWithFewestPlayers( void ); + + BOOL m_DisableDeathMessages; + BOOL m_DisableDeathPenalty; + BOOL m_teamLimit; // This means the server set only some teams as valid + char m_szTeamList[TEAMPLAY_TEAMLISTLENGTH]; +}; diff --git a/dlls/tempmonster.cpp b/dlls/tempmonster.cpp new file mode 100644 index 0000000..45f2e01 --- /dev/null +++ b/dlls/tempmonster.cpp @@ -0,0 +1,121 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// monster template +//========================================================= +#if 0 + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= + +class CMyMonster : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + float MaxYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); +}; +LINK_ENTITY_TO_CLASS( my_monster, CMyMonster ); + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CMyMonster :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_MY_MONSTER; +} + +//========================================================= +// MaxYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +float CMyMonster :: MaxYawSpeed( void ) +{ + float ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + default: + ys = 90; + } + + return ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CMyMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case 0: + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CMyMonster :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/mymodel.mdl"); + UTIL_SetSize( pev, Vector( -12, -12, 0 ), Vector( 12, 12, 24 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + if (pev->health == 0) + pev->health = 8; + pev->view_ofs = Vector ( 0, 0, 0 );// position of the eyes relative to monster's origin. + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CMyMonster :: Precache() +{ + PRECACHE_SOUND("mysound.wav"); + + PRECACHE_MODEL("models/mymodel.mdl"); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +#endif 0 diff --git a/dlls/tentacle.cpp b/dlls/tentacle.cpp new file mode 100644 index 0000000..a6b0794 --- /dev/null +++ b/dlls/tentacle.cpp @@ -0,0 +1,1044 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +/* + + h_tentacle.cpp - silo of death tentacle monster (half life) + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "soundent.h" + + +#define ACT_T_IDLE 1010 +#define ACT_T_TAP 1020 +#define ACT_T_STRIKE 1030 +#define ACT_T_REARIDLE 1040 + +class CTentacle : public CBaseMonster +{ +public: + CTentacle( void ); + + void Spawn( ); + void Precache( ); + void KeyValue( KeyValueData *pkvd ); + + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + // Don't allow the tentacle to go across transitions!!! + virtual int ObjectCaps( void ) { return CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + void SetObjectCollisionBox( void ) + { + pev->absmin = pev->origin + Vector(-400, -400, 0); + pev->absmax = pev->origin + Vector(400, 400, 850); + } + + void EXPORT Cycle( void ); + void EXPORT CommandUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Start( void ); + void EXPORT DieThink( void ); + + void EXPORT Test( void ); + + void EXPORT HitTouch( CBaseEntity *pOther ); + + float HearingSensitivity( void ) { return 2.0; }; + + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void Killed( entvars_t *pevAttacker, int iGib ); + + MONSTERSTATE GetIdealState ( void ) { return MONSTERSTATE_IDLE; }; +// int CanPlaySequence( BOOL fDisregardState ) { return TRUE; }; + int CanPlaySequence( int interruptFlags ) { return TRUE; }; + + int Classify( void ); + + int Level( float dz ); + int MyLevel( void ); + float MyHeight( void ); + + float m_flInitialYaw; + int m_iGoalAnim; + int m_iLevel; + int m_iDir; + float m_flFramerateAdj; + float m_flSoundYaw; + int m_iSoundLevel; + float m_flSoundTime; + float m_flSoundRadius; + int m_iHitDmg; + float m_flHitTime; + + float m_flTapRadius; + + float m_flNextSong; + static int g_fFlySound; + static int g_fSquirmSound; + + float m_flMaxYaw; + int m_iTapSound; + + Vector m_vecPrevSound; + float m_flPrevSoundTime; + + static const char *pHitSilo[]; + static const char *pHitDirt[]; + static const char *pHitWater[]; + + float MaxYawSpeed( void ) { return 18.0f; } +}; + + + +int CTentacle :: g_fFlySound; +int CTentacle :: g_fSquirmSound; + +LINK_ENTITY_TO_CLASS( monster_tentacle, CTentacle ); + +// stike sounds +#define TE_NONE -1 +#define TE_SILO 0 +#define TE_DIRT 1 +#define TE_WATER 2 + +const char *CTentacle::pHitSilo[] = +{ + "tentacle/te_strike1.wav", + "tentacle/te_strike2.wav", +}; + +const char *CTentacle::pHitDirt[] = +{ + "player/pl_dirt1.wav", + "player/pl_dirt2.wav", + "player/pl_dirt3.wav", + "player/pl_dirt4.wav", +}; + +const char *CTentacle::pHitWater[] = +{ + "player/pl_slosh1.wav", + "player/pl_slosh2.wav", + "player/pl_slosh3.wav", + "player/pl_slosh4.wav", +}; + + +TYPEDESCRIPTION CTentacle::m_SaveData[] = +{ + DEFINE_FIELD( CTentacle, m_flInitialYaw, FIELD_FLOAT ), + DEFINE_FIELD( CTentacle, m_iGoalAnim, FIELD_INTEGER ), + DEFINE_FIELD( CTentacle, m_iLevel, FIELD_INTEGER ), + DEFINE_FIELD( CTentacle, m_iDir, FIELD_INTEGER ), + DEFINE_FIELD( CTentacle, m_flFramerateAdj, FIELD_FLOAT ), + DEFINE_FIELD( CTentacle, m_flSoundYaw, FIELD_FLOAT ), + DEFINE_FIELD( CTentacle, m_iSoundLevel, FIELD_INTEGER ), + DEFINE_FIELD( CTentacle, m_flSoundTime, FIELD_TIME ), + DEFINE_FIELD( CTentacle, m_flSoundRadius, FIELD_FLOAT ), + DEFINE_FIELD( CTentacle, m_iHitDmg, FIELD_INTEGER ), + DEFINE_FIELD( CTentacle, m_flHitTime, FIELD_TIME ), + DEFINE_FIELD( CTentacle, m_flTapRadius, FIELD_FLOAT ), + DEFINE_FIELD( CTentacle, m_flNextSong, FIELD_TIME ), + DEFINE_FIELD( CTentacle, m_iTapSound, FIELD_INTEGER ), + DEFINE_FIELD( CTentacle, m_flMaxYaw, FIELD_FLOAT ), + DEFINE_FIELD( CTentacle, m_vecPrevSound, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CTentacle, m_flPrevSoundTime, FIELD_TIME ), +}; +IMPLEMENT_SAVERESTORE( CTentacle, CBaseMonster ); + + +// animation sequence aliases +typedef enum +{ + TENTACLE_ANIM_Pit_Idle, + + TENTACLE_ANIM_rise_to_Temp1, + TENTACLE_ANIM_Temp1_to_Floor, + TENTACLE_ANIM_Floor_Idle, + TENTACLE_ANIM_Floor_Fidget_Pissed, + TENTACLE_ANIM_Floor_Fidget_SmallRise, + TENTACLE_ANIM_Floor_Fidget_Wave, + TENTACLE_ANIM_Floor_Strike, + TENTACLE_ANIM_Floor_Tap, + TENTACLE_ANIM_Floor_Rotate, + TENTACLE_ANIM_Floor_Rear, + TENTACLE_ANIM_Floor_Rear_Idle, + TENTACLE_ANIM_Floor_to_Lev1, + + TENTACLE_ANIM_Lev1_Idle, + TENTACLE_ANIM_Lev1_Fidget_Claw, + TENTACLE_ANIM_Lev1_Fidget_Shake, + TENTACLE_ANIM_Lev1_Fidget_Snap, + TENTACLE_ANIM_Lev1_Strike, + TENTACLE_ANIM_Lev1_Tap, + TENTACLE_ANIM_Lev1_Rotate, + TENTACLE_ANIM_Lev1_Rear, + TENTACLE_ANIM_Lev1_Rear_Idle, + TENTACLE_ANIM_Lev1_to_Lev2, + + TENTACLE_ANIM_Lev2_Idle, + TENTACLE_ANIM_Lev2_Fidget_Shake, + TENTACLE_ANIM_Lev2_Fidget_Swing, + TENTACLE_ANIM_Lev2_Fidget_Tut, + TENTACLE_ANIM_Lev2_Strike, + TENTACLE_ANIM_Lev2_Tap, + TENTACLE_ANIM_Lev2_Rotate, + TENTACLE_ANIM_Lev2_Rear, + TENTACLE_ANIM_Lev2_Rear_Idle, + TENTACLE_ANIM_Lev2_to_Lev3, + + TENTACLE_ANIM_Lev3_Idle, + TENTACLE_ANIM_Lev3_Fidget_Shake, + TENTACLE_ANIM_Lev3_Fidget_Side, + TENTACLE_ANIM_Lev3_Fidget_Swipe, + TENTACLE_ANIM_Lev3_Strike, + TENTACLE_ANIM_Lev3_Tap, + TENTACLE_ANIM_Lev3_Rotate, + TENTACLE_ANIM_Lev3_Rear, + TENTACLE_ANIM_Lev3_Rear_Idle, + + TENTACLE_ANIM_Lev1_Door_reach, + + TENTACLE_ANIM_Lev3_to_Engine, + TENTACLE_ANIM_Engine_Idle, + TENTACLE_ANIM_Engine_Sway, + TENTACLE_ANIM_Engine_Swat, + TENTACLE_ANIM_Engine_Bob, + TENTACLE_ANIM_Engine_Death1, + TENTACLE_ANIM_Engine_Death2, + TENTACLE_ANIM_Engine_Death3, + + TENTACLE_ANIM_none +} TENTACLE_ANIM; + + + + + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CTentacle :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MONSTER; +} + +// +// Tentacle Spawn +// +void CTentacle :: Spawn( ) +{ + Precache( ); + + pev->solid = SOLID_BBOX; + pev->movetype = MOVETYPE_FLY; + pev->health = 75; + pev->sequence = 0; + + SET_MODEL(ENT(pev), "models/tentacle2.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, 0 ), Vector( 32, 32, 64 ) ); + + pev->takedamage = DAMAGE_AIM; + pev->flags |= FL_MONSTER; + + m_bloodColor = BLOOD_COLOR_GREEN; + + SetThink(&CTentacle :: Start ); + SetTouch(&CTentacle :: HitTouch ); + SetUse(&CTentacle :: CommandUse ); + + SetNextThink( 0.2 ); + + ResetSequenceInfo( ); + m_iDir = 1; + + m_flInitialYaw = pev->angles.y; + SetIdealYawAndUpdate( m_flInitialYaw ); + + g_fFlySound = FALSE; + g_fSquirmSound = FALSE; + + m_iHitDmg = 20; + + if (m_flMaxYaw <= 0) + m_flMaxYaw = 65; + + m_MonsterState = MONSTERSTATE_IDLE; + + // SetThink( Test ); + UTIL_SetOrigin( this, pev->origin ); +} + +void CTentacle :: Precache( ) +{ + PRECACHE_MODEL("models/tentacle2.mdl"); + + PRECACHE_SOUND("ambience/flies.wav"); + PRECACHE_SOUND("ambience/squirm2.wav"); + + PRECACHE_SOUND("tentacle/te_alert1.wav"); + PRECACHE_SOUND("tentacle/te_alert2.wav"); + PRECACHE_SOUND("tentacle/te_flies1.wav"); + PRECACHE_SOUND("tentacle/te_move1.wav"); + PRECACHE_SOUND("tentacle/te_move2.wav"); + PRECACHE_SOUND("tentacle/te_roar1.wav"); + PRECACHE_SOUND("tentacle/te_roar2.wav"); + PRECACHE_SOUND("tentacle/te_search1.wav"); + PRECACHE_SOUND("tentacle/te_search2.wav"); + PRECACHE_SOUND("tentacle/te_sing1.wav"); + PRECACHE_SOUND("tentacle/te_sing2.wav"); + PRECACHE_SOUND("tentacle/te_squirm2.wav"); + PRECACHE_SOUND("tentacle/te_strike1.wav"); + PRECACHE_SOUND("tentacle/te_strike2.wav"); + PRECACHE_SOUND("tentacle/te_swing1.wav"); + PRECACHE_SOUND("tentacle/te_swing2.wav"); + + PRECACHE_SOUND_ARRAY( pHitSilo ); + PRECACHE_SOUND_ARRAY( pHitDirt ); + PRECACHE_SOUND_ARRAY( pHitWater ); +} + + +CTentacle::CTentacle( ) +{ + m_flMaxYaw = 65; + m_iTapSound = 0; +} + +void CTentacle::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "sweeparc")) + { + m_flMaxYaw = atof(pkvd->szValue) / 2.0; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "sound")) + { + m_iTapSound = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + } + else + CBaseMonster::KeyValue( pkvd ); +} + + + +int CTentacle :: Level( float dz ) +{ + if (dz < 216) + return 0; + if (dz < 408) + return 1; + if (dz < 600) + return 2; + return 3; +} + + +float CTentacle :: MyHeight( ) +{ + switch ( MyLevel( ) ) + { + case 1: + return 256; + case 2: + return 448; + case 3: + return 640; + } + return 0; +} + + +int CTentacle :: MyLevel( ) +{ + switch( pev->sequence ) + { + case TENTACLE_ANIM_Pit_Idle: + return -1; + + case TENTACLE_ANIM_rise_to_Temp1: + case TENTACLE_ANIM_Temp1_to_Floor: + case TENTACLE_ANIM_Floor_to_Lev1: + return 0; + + case TENTACLE_ANIM_Floor_Idle: + case TENTACLE_ANIM_Floor_Fidget_Pissed: + case TENTACLE_ANIM_Floor_Fidget_SmallRise: + case TENTACLE_ANIM_Floor_Fidget_Wave: + case TENTACLE_ANIM_Floor_Strike: + case TENTACLE_ANIM_Floor_Tap: + case TENTACLE_ANIM_Floor_Rotate: + case TENTACLE_ANIM_Floor_Rear: + case TENTACLE_ANIM_Floor_Rear_Idle: + return 0; + + case TENTACLE_ANIM_Lev1_Idle: + case TENTACLE_ANIM_Lev1_Fidget_Claw: + case TENTACLE_ANIM_Lev1_Fidget_Shake: + case TENTACLE_ANIM_Lev1_Fidget_Snap: + case TENTACLE_ANIM_Lev1_Strike: + case TENTACLE_ANIM_Lev1_Tap: + case TENTACLE_ANIM_Lev1_Rotate: + case TENTACLE_ANIM_Lev1_Rear: + case TENTACLE_ANIM_Lev1_Rear_Idle: + return 1; + + case TENTACLE_ANIM_Lev1_to_Lev2: + return 1; + + case TENTACLE_ANIM_Lev2_Idle: + case TENTACLE_ANIM_Lev2_Fidget_Shake: + case TENTACLE_ANIM_Lev2_Fidget_Swing: + case TENTACLE_ANIM_Lev2_Fidget_Tut: + case TENTACLE_ANIM_Lev2_Strike: + case TENTACLE_ANIM_Lev2_Tap: + case TENTACLE_ANIM_Lev2_Rotate: + case TENTACLE_ANIM_Lev2_Rear: + case TENTACLE_ANIM_Lev2_Rear_Idle: + return 2; + + case TENTACLE_ANIM_Lev2_to_Lev3: + return 2; + + case TENTACLE_ANIM_Lev3_Idle: + case TENTACLE_ANIM_Lev3_Fidget_Shake: + case TENTACLE_ANIM_Lev3_Fidget_Side: + case TENTACLE_ANIM_Lev3_Fidget_Swipe: + case TENTACLE_ANIM_Lev3_Strike: + case TENTACLE_ANIM_Lev3_Tap: + case TENTACLE_ANIM_Lev3_Rotate: + case TENTACLE_ANIM_Lev3_Rear: + case TENTACLE_ANIM_Lev3_Rear_Idle: + return 3; + + case TENTACLE_ANIM_Lev1_Door_reach: + return -1; + } + return -1; +} + + +void CTentacle :: Test( void ) +{ + pev->sequence = TENTACLE_ANIM_Floor_Strike; + pev->framerate = 0; + StudioFrameAdvance( ); + SetNextThink( 0.1 ); +} + + + +// +// TentacleThink +// +void CTentacle :: Cycle( void ) +{ + // ALERT( at_console, "%s %.2f %d %d\n", STRING( pev->targetname ), pev->origin.z, m_MonsterState, m_IdealMonsterState ); + SetNextThink( 0.1 ); + + // ALERT( at_console, "%s %d %d %d %f %f\n", STRING( pev->targetname ), pev->sequence, m_iGoalAnim, m_iDir, pev->framerate, pev->health ); + + if (m_MonsterState == MONSTERSTATE_SCRIPT || m_IdealMonsterState == MONSTERSTATE_SCRIPT) + { + pev->angles.y = m_flInitialYaw; + SetIdealYaw( m_flInitialYaw ); + ClearConditions( IgnoreConditions() ); + MonsterThink( ); + m_iGoalAnim = TENTACLE_ANIM_Pit_Idle; + return; + } + + DispatchAnimEvents( ); + StudioFrameAdvance( ); + + UpdateYaw( MaxYawSpeed() ); + + CSound *pSound; + + Listen( ); + + // Listen will set this if there's something in my sound list + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + pSound = PBestSound(); + else + pSound = NULL; + + if ( pSound ) + { + Vector vecDir; + if (gpGlobals->time - m_flPrevSoundTime < 0.5) + { + float dt = gpGlobals->time - m_flPrevSoundTime; + vecDir = pSound->m_vecOrigin + (pSound->m_vecOrigin - m_vecPrevSound) / dt - pev->origin; + } + else + { + vecDir = pSound->m_vecOrigin - pev->origin; + } + m_flPrevSoundTime = gpGlobals->time; + m_vecPrevSound = pSound->m_vecOrigin; + + m_flSoundYaw = UTIL_VecToYaw ( vecDir ) - m_flInitialYaw; + m_iSoundLevel = Level( vecDir.z ); + + if (m_flSoundYaw < -180) + m_flSoundYaw += 360; + if (m_flSoundYaw > 180) + m_flSoundYaw -= 360; + + // ALERT( at_console, "sound %d %.0f\n", m_iSoundLevel, m_flSoundYaw ); + if (m_flSoundTime < gpGlobals->time) + { + // play "I hear new something" sound + char *sound; + + switch( RANDOM_LONG(0,1) ) + { + case 0: sound = "tentacle/te_alert1.wav"; break; + case 1: sound = "tentacle/te_alert2.wav"; break; + } + + // UTIL_EmitAmbientSound(ENT(pev), pev->origin + Vector( 0, 0, MyHeight()), sound, 1.0, ATTN_NORM, 0, 100); + } + m_flSoundTime = gpGlobals->time + RANDOM_FLOAT( 5.0, 10.0 ); + } + + // clip ideal_yaw + float dy = m_flSoundYaw; + switch( pev->sequence ) + { + case TENTACLE_ANIM_Floor_Rear: + case TENTACLE_ANIM_Floor_Rear_Idle: + case TENTACLE_ANIM_Lev1_Rear: + case TENTACLE_ANIM_Lev1_Rear_Idle: + case TENTACLE_ANIM_Lev2_Rear: + case TENTACLE_ANIM_Lev2_Rear_Idle: + case TENTACLE_ANIM_Lev3_Rear: + case TENTACLE_ANIM_Lev3_Rear_Idle: + if (dy < 0 && dy > -m_flMaxYaw) + dy = -m_flMaxYaw; + if (dy > 0 && dy < m_flMaxYaw) + dy = m_flMaxYaw; + break; + default: + if (dy < -m_flMaxYaw) + dy = -m_flMaxYaw; + if (dy > m_flMaxYaw) + dy = m_flMaxYaw; + } + SetIdealYaw( m_flInitialYaw + dy ); + + if (m_fSequenceFinished) + { + // ALERT( at_console, "%s done %d %d\n", STRING( pev->targetname ), pev->sequence, m_iGoalAnim ); + if (pev->health <= 1) + { + m_iGoalAnim = TENTACLE_ANIM_Pit_Idle; + if (pev->sequence == TENTACLE_ANIM_Pit_Idle) + { + pev->health = 75; + } + } + else if ( m_flSoundTime > gpGlobals->time ) + { + if (m_flSoundYaw >= -(m_flMaxYaw + 30) && m_flSoundYaw <= (m_flMaxYaw + 30)) + { + // strike + m_iGoalAnim = LookupActivity( ACT_T_STRIKE + m_iSoundLevel ); + } + else if (m_flSoundYaw >= -m_flMaxYaw * 2 && m_flSoundYaw <= m_flMaxYaw * 2) + { + // tap + m_iGoalAnim = LookupActivity( ACT_T_TAP + m_iSoundLevel ); + } + else + { + // go into rear idle + m_iGoalAnim = LookupActivity( ACT_T_REARIDLE + m_iSoundLevel ); + } + } + else if (pev->sequence == TENTACLE_ANIM_Pit_Idle) + { + // stay in pit until hear noise + m_iGoalAnim = TENTACLE_ANIM_Pit_Idle; + } + else if (pev->sequence == m_iGoalAnim) + { + if (MyLevel() >= 0 && gpGlobals->time < m_flSoundTime) + { + if (RANDOM_LONG(0,9) < m_flSoundTime - gpGlobals->time) + { + // continue stike + m_iGoalAnim = LookupActivity( ACT_T_STRIKE + m_iSoundLevel ); + } + else + { + // tap + m_iGoalAnim = LookupActivity( ACT_T_TAP + m_iSoundLevel ); + } + } + else if (MyLevel( ) < 0) + { + m_iGoalAnim = LookupActivity( ACT_T_IDLE + 0 ); + } + else + { + if (m_flNextSong < gpGlobals->time) + { + // play "I hear new something" sound + char *sound; + + switch( RANDOM_LONG(0,1) ) + { + case 0: sound = "tentacle/te_sing1.wav"; break; + case 1: sound = "tentacle/te_sing2.wav"; break; + } + + EMIT_SOUND(ENT(pev), CHAN_VOICE, sound, 1.0, ATTN_NORM); + + m_flNextSong = gpGlobals->time + RANDOM_FLOAT( 10, 20 ); + } + + if (RANDOM_LONG(0,15) == 0) + { + // idle on new level + m_iGoalAnim = LookupActivity( ACT_T_IDLE + RANDOM_LONG(0,3) ); + } + else if (RANDOM_LONG(0,3) == 0) + { + // tap + m_iGoalAnim = LookupActivity( ACT_T_TAP + MyLevel( ) ); + } + else + { + // idle + m_iGoalAnim = LookupActivity( ACT_T_IDLE + MyLevel( ) ); + } + } + if (m_flSoundYaw < 0) + m_flSoundYaw += RANDOM_FLOAT( 2, 8 ); + else + m_flSoundYaw -= RANDOM_FLOAT( 2, 8 ); + } + + pev->sequence = FindTransition( pev->sequence, m_iGoalAnim, &m_iDir ); + + if (m_iDir > 0) + { + pev->frame = 0; + } + else + { + m_iDir = -1; // just to safe + pev->frame = 255; + } + ResetSequenceInfo( ); + + m_flFramerateAdj = RANDOM_FLOAT( -0.2, 0.2 ); + pev->framerate = m_iDir * 1.0 + m_flFramerateAdj; + + switch( pev->sequence) + { + case TENTACLE_ANIM_Floor_Tap: + case TENTACLE_ANIM_Lev1_Tap: + case TENTACLE_ANIM_Lev2_Tap: + case TENTACLE_ANIM_Lev3_Tap: + { + Vector vecSrc; + UTIL_MakeVectors( pev->angles ); + + TraceResult tr1, tr2; + + vecSrc = pev->origin + Vector( 0, 0, MyHeight() - 4); + UTIL_TraceLine( vecSrc, vecSrc + gpGlobals->v_forward * 512, ignore_monsters, ENT( pev ), &tr1 ); + + vecSrc = pev->origin + Vector( 0, 0, MyHeight() + 8); + UTIL_TraceLine( vecSrc, vecSrc + gpGlobals->v_forward * 512, ignore_monsters, ENT( pev ), &tr2 ); + + // ALERT( at_console, "%f %f\n", tr1.flFraction * 512, tr2.flFraction * 512 ); + + m_flTapRadius = SetBlending( 0, RANDOM_FLOAT( tr1.flFraction * 512, tr2.flFraction * 512 ) ); + } + break; + default: + m_flTapRadius = 336; // 400 - 64 + break; + } + pev->view_ofs.z = MyHeight( ); + // ALERT( at_console, "seq %d\n", pev->sequence ); + } + + if (m_flPrevSoundTime + 2.0 > gpGlobals->time) + { + // 1.5 normal speed if hears sounds + pev->framerate = m_iDir * 1.5 + m_flFramerateAdj; + } + else if (m_flPrevSoundTime + 5.0 > gpGlobals->time) + { + // slowdown to normal + pev->framerate = m_iDir + m_iDir * (5 - (gpGlobals->time - m_flPrevSoundTime)) / 2 + m_flFramerateAdj; + } +} + + + +void CTentacle::CommandUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // ALERT( at_console, "%s triggered %d\n", STRING( pev->targetname ), useType ); + switch( useType ) + { + case USE_OFF: + pev->takedamage = DAMAGE_NO; + SetThink(&CTentacle:: DieThink ); + m_iGoalAnim = TENTACLE_ANIM_Engine_Death1; + break; + case USE_ON: + if (pActivator) + { + // ALERT( at_console, "insert sound\n"); + CSoundEnt::InsertSound ( bits_SOUND_WORLD, pActivator->pev->origin, 1024, 1.0 ); + } + break; + case USE_SET: + break; + case USE_TOGGLE: + pev->takedamage = DAMAGE_NO; + SetThink(&CTentacle:: DieThink ); + m_iGoalAnim = TENTACLE_ANIM_Engine_Idle; + break; + } + +} + + + +void CTentacle :: DieThink( void ) +{ + SetNextThink( 0.1 ); + + DispatchAnimEvents( ); + StudioFrameAdvance( ); + + SetIdealYawAndUpdate( 24, AI_KEEP_YAW_SPEED ); + + if (m_fSequenceFinished) + { + if (pev->sequence == m_iGoalAnim) + { + switch( m_iGoalAnim ) + { + case TENTACLE_ANIM_Engine_Idle: + case TENTACLE_ANIM_Engine_Sway: + case TENTACLE_ANIM_Engine_Swat: + case TENTACLE_ANIM_Engine_Bob: + m_iGoalAnim = TENTACLE_ANIM_Engine_Sway + RANDOM_LONG( 0, 2 ); + break; + case TENTACLE_ANIM_Engine_Death1: + case TENTACLE_ANIM_Engine_Death2: + case TENTACLE_ANIM_Engine_Death3: + UTIL_Remove( this ); + return; + } + } + + // ALERT( at_console, "%d : %d => ", pev->sequence, m_iGoalAnim ); + pev->sequence = FindTransition( pev->sequence, m_iGoalAnim, &m_iDir ); + // ALERT( at_console, "%d\n", pev->sequence ); + + if (m_iDir > 0) + { + pev->frame = 0; + } + else + { + pev->frame = 255; + } + ResetSequenceInfo( ); + + float dy; + switch( pev->sequence ) + { + case TENTACLE_ANIM_Floor_Rear: + case TENTACLE_ANIM_Floor_Rear_Idle: + case TENTACLE_ANIM_Lev1_Rear: + case TENTACLE_ANIM_Lev1_Rear_Idle: + case TENTACLE_ANIM_Lev2_Rear: + case TENTACLE_ANIM_Lev2_Rear_Idle: + case TENTACLE_ANIM_Lev3_Rear: + case TENTACLE_ANIM_Lev3_Rear_Idle: + case TENTACLE_ANIM_Engine_Idle: + case TENTACLE_ANIM_Engine_Sway: + case TENTACLE_ANIM_Engine_Swat: + case TENTACLE_ANIM_Engine_Bob: + case TENTACLE_ANIM_Engine_Death1: + case TENTACLE_ANIM_Engine_Death2: + case TENTACLE_ANIM_Engine_Death3: + pev->framerate = RANDOM_FLOAT( m_iDir - 0.2, m_iDir + 0.2 ); + dy = 180; + break; + default: + pev->framerate = 1.5; + dy = 0; + break; + } + pev->ideal_yaw = m_flInitialYaw + dy; + } +} + + +void CTentacle :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + char *sound; + + switch( pEvent->event ) + { + case 1: // bang + { + Vector vecSrc, vecAngles; + GetAttachment( 0, vecSrc, vecAngles ); + + // Vector vecSrc = pev->origin + m_flTapRadius * Vector( cos( pev->angles.y * (3.14192653 / 180.0) ), sin( pev->angles.y * (M_PI / 180.0) ), 0.0 ); + + // vecSrc.z += MyHeight( ); + + switch( m_iTapSound ) + { + case TE_SILO: + UTIL_EmitAmbientSound(ENT(pev), vecSrc, RANDOM_SOUND_ARRAY( pHitSilo ), 1.0, ATTN_NORM, 0, 100); + break; + case TE_NONE: + break; + case TE_DIRT: + UTIL_EmitAmbientSound(ENT(pev), vecSrc, RANDOM_SOUND_ARRAY( pHitDirt ), 1.0, ATTN_NORM, 0, 100); + break; + case TE_WATER: + UTIL_EmitAmbientSound(ENT(pev), vecSrc, RANDOM_SOUND_ARRAY( pHitWater ), 1.0, ATTN_NORM, 0, 100); + break; + } + gpGlobals->force_retouch++; + } + break; + + case 3: // start killing swing + m_iHitDmg = 200; + // UTIL_EmitAmbientSound(ENT(pev), pev->origin + Vector( 0, 0, MyHeight()), "tentacle/te_swing1.wav", 1.0, ATTN_NORM, 0, 100); + break; + + case 4: // end killing swing + m_iHitDmg = 25; + break; + + case 5: // just "whoosh" sound + // UTIL_EmitAmbientSound(ENT(pev), pev->origin + Vector( 0, 0, MyHeight()), "tentacle/te_swing2.wav", 1.0, ATTN_NORM, 0, 100); + break; + + case 2: // tap scrape + case 6: // light tap + { + Vector vecSrc = pev->origin + m_flTapRadius * Vector( cos( pev->angles.y * (M_PI / 180.0) ), sin( pev->angles.y * (M_PI / 180.0) ), 0.0 ); + + vecSrc.z += MyHeight( ); + + float flVol = RANDOM_FLOAT( 0.3, 0.5 ); + + switch( m_iTapSound ) + { + case TE_SILO: + UTIL_EmitAmbientSound(ENT(pev), vecSrc, RANDOM_SOUND_ARRAY( pHitSilo ), flVol, ATTN_NORM, 0, 100); + break; + case TE_NONE: + break; + case TE_DIRT: + UTIL_EmitAmbientSound(ENT(pev), vecSrc, RANDOM_SOUND_ARRAY( pHitDirt ), flVol, ATTN_NORM, 0, 100); + break; + case TE_WATER: + UTIL_EmitAmbientSound(ENT(pev), vecSrc, RANDOM_SOUND_ARRAY( pHitWater ), flVol, ATTN_NORM, 0, 100); + break; + } + } + break; + + + case 7: // roar + switch( RANDOM_LONG(0,1) ) + { + case 0: sound = "tentacle/te_roar1.wav"; break; + case 1: sound = "tentacle/te_roar2.wav"; break; + } + + UTIL_EmitAmbientSound(ENT(pev), pev->origin + Vector( 0, 0, MyHeight()), sound, 1.0, ATTN_NORM, 0, 100); + break; + + case 8: // search + switch( RANDOM_LONG(0,1) ) + { + case 0: sound = "tentacle/te_search1.wav"; break; + case 1: sound = "tentacle/te_search2.wav"; break; + } + + UTIL_EmitAmbientSound(ENT(pev), pev->origin + Vector( 0, 0, MyHeight()), sound, 1.0, ATTN_NORM, 0, 100); + break; + + case 9: // swing + switch( RANDOM_LONG(0,1) ) + { + case 0: sound = "tentacle/te_move1.wav"; break; + case 1: sound = "tentacle/te_move2.wav"; break; + } + + UTIL_EmitAmbientSound(ENT(pev), pev->origin + Vector( 0, 0, MyHeight()), sound, 1.0, ATTN_NORM, 0, 100); + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + } +} + + +// +// TentacleStart +// +// void CTentacle :: Start( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +void CTentacle :: Start( void ) +{ + SetThink(&CTentacle :: Cycle ); + + if ( !g_fFlySound ) + { + EMIT_SOUND (ENT(pev), CHAN_BODY, "ambience/flies.wav", 1, ATTN_NORM ); + g_fFlySound = TRUE; +// pev->nextthink = gpGlobals-> time + 0.1; + } + else if ( !g_fSquirmSound ) + { + EMIT_SOUND (ENT(pev), CHAN_BODY, "ambience/squirm2.wav", 1, ATTN_NORM ); + g_fSquirmSound = TRUE; + } + + SetNextThink( 0.1 ); +} + + + + +void CTentacle :: HitTouch( CBaseEntity *pOther ) +{ + TraceResult tr = UTIL_GetGlobalTrace( ); + + if (pOther->pev->modelindex == pev->modelindex) + return; + + if (m_flHitTime > gpGlobals->time) + return; + + // only look at the ones where the player hit me + if (tr.pHit == NULL || tr.pHit->v.modelindex != pev->modelindex) + return; + + if (tr.iHitgroup >= 3) + { + pOther->TakeDamage( pev, pev, m_iHitDmg, DMG_CRUSH ); + // ALERT( at_console, "wack %3d : ", m_iHitDmg ); + } + else if (tr.iHitgroup != 0) + { + pOther->TakeDamage( pev, pev, 20, DMG_CRUSH ); + // ALERT( at_console, "tap %3d : ", 20 ); + } + else + { + return; // Huh? + } + + m_flHitTime = gpGlobals->time + 0.5; + + // ALERT( at_console, "%s : ", STRING( tr.pHit->v.classname ) ); + + // ALERT( at_console, "%.0f : %s : %d\n", pev->angles.y, STRING( pOther->pev->classname ), tr.iHitgroup ); +} + + +int CTentacle::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if (flDamage > pev->health) + { + pev->health = 1; + } + else + { + pev->health -= flDamage; + } + return 1; +} + + + + +void CTentacle :: Killed( entvars_t *pevAttacker, int iGib ) +{ + m_iGoalAnim = TENTACLE_ANIM_Pit_Idle; + return; +} + + + +class CTentacleMaw : public CBaseMonster +{ +public: + void Spawn( ); + void Precache( ); + float MaxYawSpeed( void ) { return 8.0f; } +}; + +LINK_ENTITY_TO_CLASS( monster_tentaclemaw, CTentacleMaw ); + +// +// Tentacle Spawn +// +void CTentacleMaw :: Spawn( ) +{ + Precache( ); + SET_MODEL(ENT(pev), "models/maw.mdl"); + UTIL_SetSize(pev, Vector(-32, -32, 0), Vector(32, 32, 64)); + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_STEP; + pev->health = 75; + pev->sequence = 0; + + pev->angles.x = 90; + // ResetSequenceInfo( ); +} + +void CTentacleMaw :: Precache( ) +{ + PRECACHE_MODEL("models/maw.mdl"); +} + +#endif diff --git a/dlls/trains.h b/dlls/trains.h new file mode 100644 index 0000000..e3e433b --- /dev/null +++ b/dlls/trains.h @@ -0,0 +1,162 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef TRAINS_H +#define TRAINS_H + +// Tracktrain spawn flags +#define SF_TRACKTRAIN_NOPITCH 0x0001 +#define SF_TRACKTRAIN_NOCONTROL 0x0002 +#define SF_TRACKTRAIN_FORWARDONLY 0x0004 +#define SF_TRACKTRAIN_PASSABLE 0x0008 +#define SF_TRACKTRAIN_NOYAW 0x0010 //LRC +#define SF_TRACKTRAIN_AVELOCITY 0x800000 //LRC - avelocity has been set manually, don't turn. +#define SF_TRACKTRAIN_AVEL_GEARS 0x400000 //LRC - avelocity should be scaled up/down when the train changes gear. + +// Spawnflag for CPathTrack +#define SF_PATH_DISABLED 0x00000001 +#define SF_PATH_FIREONCE 0x00000002 +#define SF_PATH_ALTREVERSE 0x00000004 +#define SF_PATH_DISABLE_TRAIN 0x00000008 +#define SF_PATH_ALTERNATE 0x00008000 +#define SF_PATH_AVELOCITY 0x00080000 //LRC + +// Spawnflags of CPathCorner +#define SF_CORNER_WAITFORTRIG 0x001 +#define SF_CORNER_TELEPORT 0x002 +#define SF_CORNER_FIREONCE 0x004 +#define SF_CORNER_AVELOCITY 0x800000 + +//LRC - values in 'armortype' +#define PATHSPEED_SET 0 +#define PATHSPEED_ACCEL 1 +#define PATHSPEED_TIME 2 +#define PATHSPEED_SET_MASTER 3 + +//LRC - values in 'frags' +#define PATHTURN_SET 0 +#define PATHTURN_SET_MASTER 1 +#define PATHTURN_RESET 2 + +//LRC - values in 'armorvalue' +#define PATHMATCH_NO 0 +#define PATHMATCH_YES 1 +#define PATHMATCH_TRACK 2 + +//#define PATH_SPARKLE_DEBUG 1 // This makes a particle effect around path_track entities for debugging +class CPathTrack : public CPointEntity +{ +public: + void Spawn( void ); + void Activate( void ); + void KeyValue( KeyValueData* pkvd); + + void SetPrevious( CPathTrack *pprevious ); + void Link( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + CPathTrack *ValidPath( CPathTrack *ppath, int testFlag ); // Returns ppath if enabled, NULL otherwise + void Project( CPathTrack *pstart, CPathTrack *pend, Vector *origin, float dist ); + + static CPathTrack *Instance( edict_t *pent ); + + CPathTrack *LookAhead( Vector *origin, float dist, int move ); + CPathTrack *Nearest( Vector origin ); + + CPathTrack *GetNext( void ); + CPathTrack *GetPrevious( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; +#if PATH_SPARKLE_DEBUG + void EXPORT Sparkle(void); +#endif + + float m_length; + string_t m_altName; + CPathTrack *m_pnext; + CPathTrack *m_pprevious; + CPathTrack *m_paltpath; +}; + +class CTrainSequence; + +class CFuncTrackTrain : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + + void Blocked( CBaseEntity *pOther ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData* pkvd ); + + //LRC + void StartSequence(CTrainSequence *pSequence); + void StopSequence( ); + CTrainSequence *m_pSequence; + + void DesiredAction( void ); //LRC - used to be called Next! + +// void EXPORT Next( void ); + void EXPORT PostponeNext( void ); + void EXPORT Find( void ); + void EXPORT NearestPath( void ); + void EXPORT DeadEnd( void ); + + void NextThink( float thinkTime, BOOL alwaysThink ); + + void SetTrack( CPathTrack *track ) { m_ppath = track->Nearest(pev->origin); } + void SetControls( entvars_t *pevControls ); + BOOL OnControls( entvars_t *pev ); + + void StopSound ( void ); + void UpdateSound ( void ); + + static CFuncTrackTrain *Instance( edict_t *pent ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DIRECTIONAL_USE; } + + virtual void OverrideReset( void ); + + CPathTrack *m_ppath; + float m_length; + float m_height; + // I get it... this records the train's max speed (as set by the level designer), whereas + // pev->speed records the current speed (as set by the player). --LRC + // m_speed is also stored, as an int, in pev->impulse. + float m_speed; + float m_dir; + float m_startSpeed; + Vector m_controlMins; + Vector m_controlMaxs; + int m_soundPlaying; + int m_sounds; + float m_flVolume; + float m_flBank; + float m_oldSpeed; + Vector m_vecMasterAvel; //LRC - masterAvel is to avelocity as m_speed is to speed. + Vector m_vecBaseAvel; // LRC - the underlying avelocity, superceded by normal turning behaviour where applicable + +private: + unsigned short m_usAdjustPitch; +}; + +#endif diff --git a/dlls/triggers.cpp b/dlls/triggers.cpp new file mode 100644 index 0000000..98d1bdf --- /dev/null +++ b/dlls/triggers.cpp @@ -0,0 +1,5768 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== triggers.cpp ======================================================== + + spawn and use functions for editor-placed triggers + +*/ +//CODIAC - Linux needs this for tolower +#include + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "saverestore.h" +#include "trains.h" // trigger_camera has train functionality +#include "gamerules.h" +#include "talkmonster.h" +#include "weapons.h" //LRC, for trigger_hevcharge +#include "movewith.h" //LRC +#include "locus.h" //LRC +//#include "hgrunt.h" +//#include "islave.h" +#include "rushscript.h" // buz + +#define SF_TRIGGER_PUSH_START_OFF 2//spawnflag that makes trigger_push spawn turned OFF +#define SF_TRIGGER_HURT_TARGETONCE 1// Only fire hurt target once +#define SF_TRIGGER_HURT_START_OFF 2//spawnflag that makes trigger_hurt spawn turned OFF +#define SF_TRIGGER_HURT_NO_CLIENTS 8// clients may not touch this trigger. +#define SF_TRIGGER_HURT_CLIENTONLYFIRE 16// trigger hurt will only fire its target if it is hurting a client +#define SF_TRIGGER_HURT_CLIENTONLYTOUCH 32// only clients may touch this trigger. + +extern DLL_GLOBAL BOOL g_fGameOver; + +extern void SetMovedir(entvars_t* pev); +extern Vector VecBModelOrigin( entvars_t* pevBModel ); + +extern int gmsgTextWindow; // buz + +class CFrictionModifier : public CBaseEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT ChangeFriction( CBaseEntity *pOther ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + static TYPEDESCRIPTION m_SaveData[]; + + float m_frictionFraction; // Sorry, couldn't resist this name :) +}; + +LINK_ENTITY_TO_CLASS( func_friction, CFrictionModifier ); + +// Global Savedata for changelevel friction modifier +TYPEDESCRIPTION CFrictionModifier::m_SaveData[] = +{ + DEFINE_FIELD( CFrictionModifier, m_frictionFraction, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE(CFrictionModifier,CBaseEntity); + + +// Modify an entity's friction +void CFrictionModifier :: Spawn( void ) +{ + pev->solid = SOLID_TRIGGER; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + pev->movetype = MOVETYPE_NONE; + SetTouch(&CFrictionModifier :: ChangeFriction ); +} + + +// Sets toucher's friction to m_frictionFraction (1.0 = normal friction) +void CFrictionModifier :: ChangeFriction( CBaseEntity *pOther ) +{ + if ( pOther->pev->movetype != MOVETYPE_BOUNCEMISSILE && pOther->pev->movetype != MOVETYPE_BOUNCE ) + pOther->pev->friction = m_frictionFraction; +} + + + +// Sets toucher's friction to m_frictionFraction (1.0 = normal friction) +void CFrictionModifier :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "modifier")) + { + m_frictionFraction = atof(pkvd->szValue) / 100.0; + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +class CTriggerNoFlashlightZone : public CBaseEntity +{ +public: + void Spawn( void ); + void Touch( CBaseEntity *pOther ) { ClearBits( pOther->pev->effects, EF_DIMLIGHT ); } +}; + +LINK_ENTITY_TO_CLASS( trigger_noflash, CTriggerNoFlashlightZone ); + +// Modify an entity's friction +void CTriggerNoFlashlightZone :: Spawn( void ) +{ + pev->solid = SOLID_TRIGGER; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + pev->movetype = MOVETYPE_NONE; + SetBits( pev->effects, EF_NODRAW ); + SetTouch(&CFrictionModifier :: ChangeFriction ); +} + +// This trigger will fire when the level spawns (or respawns if not fire once) +// It will check a global state before firing. It supports delay and killtargets + +#define SF_AUTO_FIREONCE 0x0001 +#define SF_AUTO_FROMPLAYER 0x0002 + +class CAutoTrigger : public CBaseDelay +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Activate( void ); + void DesiredAction( void ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + int m_globalstate; + USE_TYPE triggerType; +}; +LINK_ENTITY_TO_CLASS( trigger_auto, CAutoTrigger ); + +TYPEDESCRIPTION CAutoTrigger::m_SaveData[] = +{ + DEFINE_FIELD( CAutoTrigger, m_globalstate, FIELD_STRING ), + DEFINE_FIELD( CAutoTrigger, triggerType, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE(CAutoTrigger,CBaseDelay); + +void CAutoTrigger::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "globalstate")) + { + m_globalstate = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "triggerstate")) + { + int type = atoi( pkvd->szValue ); + switch( type ) + { + case 0: + triggerType = USE_OFF; + break; + case 2: + triggerType = USE_TOGGLE; + break; + default: + triggerType = USE_ON; + break; + } + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + +void CAutoTrigger::Activate( void ) +{ +// ALERT(at_console, "trigger_auto targetting \"%s\": activate\n", STRING(pev->target)); + UTIL_DesiredAction( this ); //LRC - don't think until the player has spawned. + + CBaseDelay::Activate(); +} + +void CAutoTrigger::DesiredAction( void ) +{ +// ALERT(at_console, "trigger_auto targetting \"%s\": Fire at time %f\n", STRING(pev->target), gpGlobals->time); + if ( !m_globalstate || gGlobalState.EntityGetState( m_globalstate ) == GLOBAL_ON ) + { + if (pev->spawnflags & SF_AUTO_FROMPLAYER) + { + CBaseEntity* pPlayer = UTIL_FindEntityByClassname(NULL, "player"); + if (pPlayer) + SUB_UseTargets( pPlayer, triggerType, 0 ); + else + ALERT(at_error,"trigger_auto: \"From Player\" is ticked, but no player found!\n"); + } + else + { + SUB_UseTargets( this, triggerType, 0 ); + } + if ( pev->spawnflags & SF_AUTO_FIREONCE ) + UTIL_Remove( this ); + } +} + +#define SF_RELAY_FIREONCE 0x00000001 +#define SF_RELAY_DEBUG 0x00000002 +#define SF_RELAY_USESAME 0x80000000 + +class CTriggerRelay : public CBaseDelay +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + USE_TYPE m_triggerType; + int m_sMaster; + string_t m_iszAltTarget; +}; +LINK_ENTITY_TO_CLASS( trigger_relay, CTriggerRelay ); + +TYPEDESCRIPTION CTriggerRelay::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerRelay, m_triggerType, FIELD_INTEGER ), + DEFINE_FIELD( CTriggerRelay, m_sMaster, FIELD_STRING ), + DEFINE_FIELD( CTriggerRelay, m_iszAltTarget, FIELD_STRING ),//G-Cont. in garagedoordemo code without this stuff, demo don't work with save\load ;) +}; + +IMPLEMENT_SAVERESTORE(CTriggerRelay,CBaseDelay); + +void CTriggerRelay::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "master")) + { + m_sMaster = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszAltTarget")) + { + m_iszAltTarget = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "triggerstate")) + { + int type = atoi( pkvd->szValue ); + switch( type ) + { + case 0: + m_triggerType = (USE_TYPE)-1; // will be changed in spawn + break; + case 2: + m_triggerType = USE_TOGGLE; + break; + case 4: + m_triggerType = USE_KILL; + break; + case 5: + m_triggerType = USE_SAME; + break; + case 7: + m_triggerType = USE_SET; + break; + default: + m_triggerType = USE_ON; + break; + } + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + + +void CTriggerRelay::Spawn( void ) +{ + if( m_triggerType == -1 ) m_triggerType = USE_OFF;// "triggerstate" is present and set to 'OFF' + else if( !m_triggerType ) m_triggerType = USE_ON; // "triggerstate" is missing - defaulting to 'ON' +} + +void CTriggerRelay::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (!UTIL_IsMasterTriggered(m_sMaster,pActivator)) + { + if (m_iszAltTarget) + { + //FIXME: the alternate target should really use m_flDelay. + if (pev->spawnflags & SF_RELAY_USESAME) + FireTargets( STRING(m_iszAltTarget), pActivator, this, useType, 0 ); + else + FireTargets( STRING(m_iszAltTarget), pActivator, this, m_triggerType, 0 ); + if (pev->spawnflags & SF_RELAY_DEBUG) + ALERT(at_debug,"DEBUG: trigger_relay \"%s\" locked by master %s - fired alternate target %s\n",STRING(pev->targetname), STRING(m_sMaster), STRING(m_iszAltTarget)); + if ( pev->spawnflags & SF_RELAY_FIREONCE ) + { + if (pev->spawnflags & SF_RELAY_DEBUG) + ALERT(at_debug, "trigger_relay \"%s\" removes itself.\n"); + UTIL_Remove( this ); + } + } + else if (pev->spawnflags & SF_RELAY_DEBUG) + ALERT(at_debug,"DEBUG: trigger_relay \"%s\" wasn't activated: locked by master %s\n",STRING(pev->targetname), STRING(m_sMaster)); + return; + } + if (pev->spawnflags & SF_RELAY_DEBUG) + { + ALERT(at_debug,"DEBUG: trigger_relay \"%s\" was sent %s",STRING(pev->targetname), GetStringForUseType(useType)); + if (pActivator) + { + if (FStringNull(pActivator->pev->targetname)) + ALERT(at_debug," from \"%s\"", STRING(pActivator->pev->classname)); + else + ALERT(at_debug," from \"%s\"", STRING(pActivator->pev->targetname)); + } + else + ALERT(at_debug," (no locus)"); + } + + if (FStringNull(pev->target) && !m_iszKillTarget) + { + if (pev->spawnflags & SF_RELAY_DEBUG) ALERT(at_debug, ".\n"); + return; + } + + if (pev->message) + value = CalcLocus_Ratio(pActivator, STRING(pev->message)); + + if (m_triggerType == USE_SAME) + { + if (pev->spawnflags & SF_RELAY_DEBUG) + { + if (m_flDelay) + ALERT(at_debug,": will send %s(same) in %f seconds.\n",GetStringForUseType(useType),m_flDelay); + else + ALERT(at_debug,": sending %s(same) now.\n",GetStringForUseType(useType)); + } + SUB_UseTargets( pActivator, useType, value ); + } + else if (m_triggerType == USE_SET) + { + if (pev->spawnflags & SF_RELAY_DEBUG) + { + if (m_flDelay) + ALERT(at_debug,": will send ratio %f in %f seconds.\n",value,m_flDelay); + else + ALERT(at_debug,": sending ratio %f now.\n",value); + } + SUB_UseTargets( pActivator, m_triggerType, value ); + } + else + { + if (pev->spawnflags & SF_RELAY_DEBUG) + { + if (m_flDelay) + ALERT(at_debug,": will send %s in %f seconds.\n",GetStringForUseType(m_triggerType),m_flDelay); + else + ALERT(at_debug,": sending %s now.\n",GetStringForUseType(m_triggerType)); + } + SUB_UseTargets( pActivator, m_triggerType, 0 ); + } + if ( pev->spawnflags & SF_RELAY_FIREONCE ) + { + if (pev->spawnflags & SF_RELAY_DEBUG) + ALERT(at_debug, "trigger_relay \"%s\" removes itself.\n"); + UTIL_Remove( this ); + } +} + +//=========================================== +//LRC - trigger_rottest, temporary new entity +//=========================================== +class CTriggerRotTest : public CBaseDelay +{ +public: + void PostSpawn( void ); +// void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Think( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + CBaseEntity* m_pMarker; + CBaseEntity* m_pReference; + CBaseEntity* m_pBridge; + CBaseEntity* m_pHinge; +}; +LINK_ENTITY_TO_CLASS( trigger_rottest, CTriggerRotTest ); + +TYPEDESCRIPTION CTriggerRotTest::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerRotTest, m_pMarker, FIELD_CLASSPTR ), + DEFINE_FIELD( CTriggerRotTest, m_pReference, FIELD_CLASSPTR ), + DEFINE_FIELD( CTriggerRotTest, m_pBridge, FIELD_CLASSPTR ), + DEFINE_FIELD( CTriggerRotTest, m_pHinge, FIELD_CLASSPTR ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerRotTest,CBaseDelay); + +void CTriggerRotTest::PostSpawn( void ) +{ + m_pMarker = UTIL_FindEntityByTargetname(NULL, STRING(pev->target)); + m_pReference = UTIL_FindEntityByTargetname(NULL, STRING(pev->netname)); + m_pBridge = UTIL_FindEntityByTargetname(NULL, STRING(pev->noise1)); + m_pHinge = UTIL_FindEntityByTargetname(NULL, STRING(pev->message)); + pev->armorvalue = 0; // initial angle + if (pev->armortype == 0) //angle offset + pev->armortype = 30; + SetNextThink( 1 ); +} + +void CTriggerRotTest::Think( void ) +{ +// ALERT(at_console, "Using angle = %.2f\n", pev->armorvalue); + if (m_pReference) + { + m_pReference->pev->origin = pev->origin; + m_pReference->pev->origin.x = m_pReference->pev->origin.x + pev->health; +// ALERT(at_console, "Set Reference = %.2f %.2f %.2f\n", m_pReference->pev->origin.x, m_pReference->pev->origin.y, m_pReference->pev->origin.z); + } + if (m_pMarker) + { + Vector vecTemp = UTIL_AxisRotationToVec( (m_pHinge->pev->origin - pev->origin).Normalize(), pev->armorvalue ); + m_pMarker->pev->origin = pev->origin + pev->health * vecTemp; + +// ALERT(at_console, "vecTemp = %.2f %.2f %.2f\n", vecTemp.x, vecTemp.y, vecTemp.z); +// ALERT(at_console, "Set Marker = %.2f %.2f %.2f\n", m_pMarker->pev->origin.x, m_pMarker->pev->origin.y, m_pMarker->pev->origin.z); + } + if (m_pBridge) + { + Vector vecTemp = UTIL_AxisRotationToAngles( (m_pHinge->pev->origin - pev->origin).Normalize(), pev->armorvalue ); + m_pBridge->pev->origin = pev->origin; + m_pBridge->pev->angles = vecTemp; + +// ALERT(at_console, "vecTemp = %.2f %.2f %.2f\n", vecTemp.x, vecTemp.y, vecTemp.z); +// ALERT(at_console, "Set Marker = %.2f %.2f %.2f\n", m_pMarker->pev->origin.x, m_pMarker->pev->origin.y, m_pMarker->pev->origin.z); + } + pev->armorvalue += pev->armortype * 0.1; + SetNextThink( 0.1 ); +} + +//********************************************************** +// The Multimanager Entity - when fired, will fire up to 16 targets +// at specified times. +// FLAG: THREAD (create clones when triggered) +// FLAG: CLONE (this is a clone for a threaded execution) + +#define SF_MULTIMAN_CLONE 0x80000000 +#define SF_MULTIMAN_SAMETRIG 0x40000000 +#define SF_MULTIMAN_TRIGCHOSEN 0x20000000 + +#define SF_MULTIMAN_THREAD 0x00000001 +#define SF_MULTIMAN_LOOP 0x00000004 +#define SF_MULTIMAN_ONLYONCE 0x00000008 +#define SF_MULTIMAN_SPAWNFIRE 0x00000010 +#define SF_MULTIMAN_DEBUG 0x00000020 + +#define MM_MODE_CHOOSE 1 +#define MM_MODE_PERCENT 2 +#define MM_MODE_SIMULTANEOUS 3 + +class CMultiManager : public CBaseEntity//Toggle +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn ( void ); + void EXPORT UseThink ( void ); + void EXPORT ManagerThink ( void ); + void EXPORT ManagerUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +#if _DEBUG + void EXPORT ManagerReport( void ); +#endif + + BOOL HasTarget( string_t targetname ); + + int ObjectCaps( void ) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + STATE m_iState; + virtual STATE GetState( void ) { return m_iState; }; + + int m_cTargets; // the total number of targets in this manager's fire list. + int m_index; // Current target + float m_startTime;// Time we started firing + int m_iTargetName [ MAX_MULTI_TARGETS ];// list if indexes into global string array + float m_flTargetDelay [ MAX_MULTI_TARGETS ];// delay (in seconds) from time of manager fire to target fire + + float m_flWait; //LRC- minimum length of time to wait + float m_flMaxWait; //LRC- random, maximum length of time to wait + string_t m_sMaster; //LRC- master + int m_iMode; //LRC- 0 = timed, 1 = pick random, 2 = each random + int m_iszThreadName; //LRC + int m_iszLocusThread; //LRC + + EHANDLE m_hActivator; +private: + USE_TYPE m_triggerType; //LRC + inline BOOL IsClone( void ) { return (pev->spawnflags & SF_MULTIMAN_CLONE) ? TRUE : FALSE; } + inline BOOL ShouldClone( void ) + { + if ( IsClone() ) + return FALSE; + + return (pev->spawnflags & SF_MULTIMAN_THREAD) ? TRUE : FALSE; + } + + CMultiManager *Clone( void ); +}; +LINK_ENTITY_TO_CLASS( multi_manager, CMultiManager ); + +// Global Savedata for multi_manager +TYPEDESCRIPTION CMultiManager::m_SaveData[] = +{ + DEFINE_FIELD( CMultiManager, m_cTargets, FIELD_INTEGER ), + DEFINE_FIELD( CMultiManager, m_index, FIELD_INTEGER ), + DEFINE_FIELD( CMultiManager, m_iState, FIELD_INTEGER ), //LRC + DEFINE_FIELD( CMultiManager, m_iMode, FIELD_INTEGER ), //LRC + DEFINE_FIELD( CMultiManager, m_startTime, FIELD_TIME ), + DEFINE_FIELD( CMultiManager, m_triggerType, FIELD_INTEGER ), //LRC + DEFINE_ARRAY( CMultiManager, m_iTargetName, FIELD_STRING, MAX_MULTI_TARGETS ), + DEFINE_ARRAY( CMultiManager, m_flTargetDelay, FIELD_FLOAT, MAX_MULTI_TARGETS ), + DEFINE_FIELD( CMultiManager, m_sMaster, FIELD_STRING ), //LRC + DEFINE_FIELD( CMultiManager, m_hActivator, FIELD_EHANDLE ), + DEFINE_FIELD( CMultiManager, m_flWait, FIELD_FLOAT ), //LRC + DEFINE_FIELD( CMultiManager, m_flMaxWait, FIELD_FLOAT ), //LRC + DEFINE_FIELD( CMultiManager, m_iszThreadName, FIELD_STRING ), //LRC + DEFINE_FIELD( CMultiManager, m_iszLocusThread, FIELD_STRING ), //LRC +}; + +IMPLEMENT_SAVERESTORE(CMultiManager,CBaseEntity); + +void CMultiManager :: KeyValue( KeyValueData *pkvd ) +{ + + // UNDONE: Maybe this should do something like this: + //CBaseToggle::KeyValue( pkvd ); + // if ( !pkvd->fHandled ) + // ... etc. + // + //LRC- that would support Delay, Killtarget, Lip, Distance, Wait and Master. + // Wait is already supported. I've added master here. To hell with the others. + + if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "maxwait")) + { + m_flMaxWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "master")) //LRC + { + m_sMaster = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszThreadName")) //LRC + { + m_iszThreadName = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszLocusThread")) //LRC + { + m_iszLocusThread = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "mode")) //LRC + { + m_iMode = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "triggerstate")) //LRC + { + switch( atoi( pkvd->szValue ) ) + { + case 4: pev->spawnflags |= SF_MULTIMAN_SAMETRIG; break; + case 1: m_triggerType = USE_ON; break; //LRC- yes, this algorithm is different + case 2: m_triggerType = USE_OFF; break; //from the trigger_relay equivalent- + case 3: m_triggerType = USE_KILL; break; //trigger_relay's got to stay backwards + default: m_triggerType = USE_TOGGLE; break; //compatible. + } + pev->spawnflags |= SF_MULTIMAN_TRIGCHOSEN; + pkvd->fHandled = TRUE; + } + else // add this field to the target list + { + // this assumes that additional fields are targetnames and their values are delay values. + if ( m_cTargets < MAX_MULTI_TARGETS ) + { + char tmp[128]; + + UTIL_StripToken( pkvd->szKeyName, tmp ); + m_iTargetName [ m_cTargets ] = ALLOC_STRING( tmp ); + m_flTargetDelay [ m_cTargets ] = atof (pkvd->szValue); + m_cTargets++; + pkvd->fHandled = TRUE; + } + else //LRC - keep a count of how many targets, for the error message + { + m_cTargets++; + } + } +} + + +void CMultiManager :: Spawn( void ) +{ + //LRC + if( m_cTargets > MAX_MULTI_TARGETS ) + { + ALERT(at_warning, "multi_manager \"%s\" has too many targets (limit is %d, it has %d)\n", STRING( pev->targetname ), MAX_MULTI_TARGETS, m_cTargets ); + m_cTargets = MAX_MULTI_TARGETS; + } + + // check for invalid multi_managers + for ( int i = 0; i < m_cTargets; i++ ) + { + if ( FStrEq( STRING( m_iTargetName[i] ), STRING( pev->targetname )) && m_flTargetDelay[i] == 0.0f ) + { + ALERT( at_error, "infinite loop multi_manager with name %s removed from map\n", STRING( pev->targetname )); + UTIL_Remove( this ); + return; + } + } + + pev->solid = SOLID_NOT; + SetUse(&CMultiManager :: ManagerUse ); + SetThink(&CMultiManager :: ManagerThink); + + m_iState = STATE_OFF; + + if (!FBitSet(pev->spawnflags,SF_MULTIMAN_TRIGCHOSEN)) + m_triggerType = USE_TOGGLE; + + // Sort targets + // Quick and dirty bubble sort + int swapped = 1; + + while ( swapped ) + { + swapped = 0; + for ( int i = 1; i < m_cTargets; i++ ) + { + if ( m_flTargetDelay[i] < m_flTargetDelay[i-1] ) + { + // Swap out of order elements + int name = m_iTargetName[i]; + float delay = m_flTargetDelay[i]; + m_iTargetName[i] = m_iTargetName[i-1]; + m_flTargetDelay[i] = m_flTargetDelay[i-1]; + m_iTargetName[i-1] = name; + m_flTargetDelay[i-1] = delay; + swapped = 1; + } + } + } + + if ( pev->spawnflags & SF_MULTIMAN_SPAWNFIRE) + { + SetThink(&CMultiManager :: UseThink ); + SetUse( NULL ); + UTIL_DesiredThink( this ); + } +} + + +BOOL CMultiManager::HasTarget( string_t targetname ) +{ + for ( int i = 0; i < m_cTargets; i++ ) + if ( FStrEq(STRING(targetname), STRING(m_iTargetName[i])) ) + return TRUE; + + return FALSE; +} + +void CMultiManager :: UseThink ( void ) +{ + SetThink(&CMultiManager :: ManagerThink ); + SetUse(&CMultiManager :: ManagerUse ); + Use( this, this, USE_TOGGLE, 0 ); +} + +// Designers were using this to fire targets that may or may not exist -- +// so I changed it to use the standard target fire code, made it a little simpler. +void CMultiManager :: ManagerThink ( void ) +{ + //LRC- different manager modes + if (m_iMode) + { + // special triggers have no time delay, so we can clean up before firing + if (pev->spawnflags & SF_MULTIMAN_LOOP) + { +// ALERT(at_console,"Manager loops back\n"); + // if it's a loop, start again! + if (m_flMaxWait) //LRC- random time to wait? + m_startTime = RANDOM_FLOAT( m_flWait, m_flMaxWait ); + else if (m_flWait) //LRC- constant time to wait? + m_startTime = m_flWait; + else //LRC- just start immediately. + m_startTime = 0; + if (pev->spawnflags & SF_MULTIMAN_DEBUG) + ALERT(at_debug, "DEBUG: multi_manager \"%s\": restarting loop.\n", STRING(pev->targetname)); + SetNextThink( m_startTime ); + m_startTime = m_fNextThink; + m_iState = STATE_TURN_ON; +// ALERT(at_console, "MM loops, nextthink %f\n", m_fNextThink); + } + else if ( IsClone() || pev->spawnflags & SF_MULTIMAN_ONLYONCE ) + { + if (pev->spawnflags & SF_MULTIMAN_DEBUG) + ALERT(at_debug, "DEBUG: multi_manager \"%s\": killed.\n", STRING(pev->targetname)); + SetThink(&CMultiManager :: SUB_Remove ); + SetNextThink( 0.1 ); + SetUse( NULL ); + } + else + { + if (pev->spawnflags & SF_MULTIMAN_DEBUG) + ALERT(at_debug, "DEBUG: multi_manager \"%s\": last burst.\n", STRING(pev->targetname)); + m_iState = STATE_OFF; + SetThink( NULL ); + SetUse(&CMultiManager :: ManagerUse );// allow manager re-use + } + + int i = 0; + if (m_iMode == MM_MODE_CHOOSE) // choose one of the members, and fire it + { + float total = 0; + for (i = 0; i < m_cTargets; i++) { total += m_flTargetDelay[i]; } + + // no weightings given, so just pick one. + if (total == 0) + { + const char *sTarg = STRING(m_iTargetName[RANDOM_LONG(0, m_cTargets-1)]); + if (pev->spawnflags & SF_MULTIMAN_DEBUG) + ALERT(at_debug, "DEBUG: multi_manager \"%s\": firing \"%s\" (random choice).\n", STRING(pev->targetname), sTarg); + FireTargets(sTarg,m_hActivator,this,m_triggerType,0); + } + else // pick one by weighting + { + float chosen = RANDOM_FLOAT(0,total); + float curpos = 0; + for (i = 0; i < m_cTargets; i++) + { + curpos += m_flTargetDelay[i]; + if (curpos >= chosen) + { + if (pev->spawnflags & SF_MULTIMAN_DEBUG) + ALERT(at_debug, "DEBUG: multi_manager \"%s\": firing \"%s\" (weighted random choice).\n", STRING(pev->targetname), STRING(m_iTargetName[i])); + FireTargets(STRING(m_iTargetName[i]),m_hActivator,this,m_triggerType,0); + break; + } + } + } + } + else if (m_iMode == MM_MODE_PERCENT) // try to call each member + { + for (i = 0; i < m_cTargets; i++) + { + if ( RANDOM_LONG( 0, 100 ) <= m_flTargetDelay[i] ) + { + if (pev->spawnflags & SF_MULTIMAN_DEBUG) + ALERT(at_debug, "DEBUG: multi_manager \"%s\": firing \"%s\" (%f%% chance).\n", STRING(pev->targetname), STRING(m_iTargetName[i]), m_flTargetDelay[i]); + FireTargets(STRING(m_iTargetName[i]),m_hActivator,this,m_triggerType,0); + } + } + } + else if (m_iMode == MM_MODE_SIMULTANEOUS) + { + for (i = 0; i < m_cTargets; i++) + { + if (pev->spawnflags & SF_MULTIMAN_DEBUG) + ALERT(at_debug, "DEBUG: multi_manager \"%s\": firing \"%s\" (simultaneous).\n", STRING(pev->targetname), STRING(m_iTargetName[i])); + FireTargets(STRING(m_iTargetName[i]),m_hActivator,this,m_triggerType,0); + } + } + + return; + } + +// ok, so m_iMode is 0; we're doing normal time-based stuff. + + float time; + int finalidx; + int index = m_index; // store the current index + + time = gpGlobals->time - m_startTime; + +// ALERT(at_console,"Manager think for time %f\n",time); + + // find the last index we're going to fire this time + finalidx = m_index; + while (finalidx < m_cTargets && m_flTargetDelay[ finalidx ] <= time) + finalidx++; + + if ( finalidx >= m_cTargets )// will we finish firing targets this time? + { + if (pev->spawnflags & SF_MULTIMAN_LOOP) + { +// ALERT(at_console,"Manager loops back\n"); + // if it's a loop, start again! + m_index = 0; + if (m_flMaxWait) //LRC- random time to wait? + { + m_startTime = RANDOM_FLOAT( m_flWait, m_flMaxWait ); + m_iState = STATE_TURN_ON; // while we're waiting, we're in state TURN_ON + } + else if (m_flWait) //LRC- constant time to wait? + { + m_startTime = m_flWait; + m_iState = STATE_TURN_ON; + } + else //LRC- just start immediately. + { + m_startTime = 0; + m_iState = STATE_ON; + } + if (pev->spawnflags & SF_MULTIMAN_DEBUG) + ALERT(at_debug, "DEBUG: multi_manager \"%s\": restarting loop.\n", STRING(pev->targetname)); + SetNextThink( m_startTime ); + m_startTime += gpGlobals->time; + } + else + { + m_iState = STATE_OFF; //LRC- STATE_OFF means "yes, we've finished". + if ( IsClone() || pev->spawnflags & SF_MULTIMAN_ONLYONCE ) + { + SetThink(&CMultiManager :: SUB_Remove ); + SetNextThink( 0.1 ); + SetUse( NULL ); + if (pev->spawnflags & SF_MULTIMAN_DEBUG) + ALERT(at_debug, "DEBUG: multi_manager \"%s\": killed.\n", STRING(pev->targetname)); + } + else + { + SetThink( NULL ); + SetUse(&CMultiManager :: ManagerUse );// allow manager re-use + if (pev->spawnflags & SF_MULTIMAN_DEBUG) + ALERT(at_debug, "DEBUG: multi_manager \"%s\": last burst.\n", STRING(pev->targetname)); + } + } + } + else + { + m_index = finalidx; + m_iState = STATE_ON; //LRC- while we're in STATE_ON we're firing targets, and haven't finished yet. + AbsoluteNextThink( m_startTime + m_flTargetDelay[ m_index ] ); + } + + while ( index < m_cTargets && m_flTargetDelay[ index ] <= time ) + { +// ALERT(at_console,"Manager sends %d to %s\n",m_triggerType,STRING(m_iTargetName[m_index])); + if (pev->spawnflags & SF_MULTIMAN_DEBUG) + ALERT(at_debug, "DEBUG: multi_manager \"%s\": firing \"%s\".\n", STRING(pev->targetname), STRING( m_iTargetName[ index ] )); + FireTargets( STRING( m_iTargetName[ index ] ), m_hActivator, this, m_triggerType, 0 ); + index++; + } +} + +CMultiManager *CMultiManager::Clone( void ) +{ + CMultiManager *pMulti = GetClassPtr( (CMultiManager *)NULL ); + + edict_t *pEdict = pMulti->pev->pContainingEntity; + memcpy( pMulti->pev, pev, sizeof(*pev) ); + pMulti->pev->pContainingEntity = pEdict; + + pMulti->pev->spawnflags |= SF_MULTIMAN_CLONE; + pMulti->m_cTargets = m_cTargets; + if (m_iszThreadName) pMulti->pev->targetname = m_iszThreadName; //LRC + pMulti->m_triggerType = m_triggerType; //LRC + pMulti->m_iMode = m_iMode; //LRC + pMulti->m_flWait = m_flWait; //LRC + pMulti->m_flMaxWait = m_flMaxWait; //LRC + memcpy( pMulti->m_iTargetName, m_iTargetName, sizeof( m_iTargetName ) ); + memcpy( pMulti->m_flTargetDelay, m_flTargetDelay, sizeof( m_flTargetDelay ) ); + + return pMulti; +} + + +// The USE function builds the time table and starts the entity thinking. +void CMultiManager :: ManagerUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (pev->spawnflags & SF_MULTIMAN_LOOP) + { + if (m_iState != STATE_OFF) // if we're on, or turning on... + { + if (useType != USE_ON) // ...then turn it off if we're asked to. + { + if (pev->spawnflags & SF_MULTIMAN_DEBUG) + ALERT(at_debug, "DEBUG: multi_manager \"%s\": Loop halted on request.\n", STRING(pev->targetname)); + m_iState = STATE_OFF; + if ( IsClone() || pev->spawnflags & SF_MULTIMAN_ONLYONCE ) + { + SetThink(&CMultiManager :: SUB_Remove ); + SetNextThink( 0.1 ); + SetUse( NULL ); + if (pev->spawnflags & SF_MULTIMAN_DEBUG) + ALERT(at_debug, "DEBUG: multi_manager \"%s\": loop halted (removing).\n", STRING(pev->targetname)); + } + else + { + SetThink( NULL ); + if (pev->spawnflags & SF_MULTIMAN_DEBUG) + ALERT(at_debug, "DEBUG: multi_manager \"%s\": loop halted.\n", STRING(pev->targetname)); + } + } + // else we're already on and being told to turn on, so do nothing. + else if (pev->spawnflags & SF_MULTIMAN_DEBUG) + ALERT(at_debug, "DEBUG: multi_manager \"%s\": Loop already active.\n", STRING(pev->targetname)); + return; + } + else if (useType == USE_OFF) // it's already off + { + if (pev->spawnflags & SF_MULTIMAN_DEBUG) + ALERT(at_debug, "DEBUG: multi_manager \"%s\": Loop already inactive.\n", STRING(pev->targetname)); + return; + } + // otherwise, start firing targets as normal. + } +// ALERT(at_console,"Manager used, targetting ["); +// for (int i = 0; i < m_cTargets; i++) +// { +// ALERT(at_console," %s(%f)",STRING(m_iTargetName[i]),m_flTargetDelay[i]); +// } +// ALERT(at_console," ]\n"); + + //LRC- "master" support + if (!UTIL_IsMasterTriggered(m_sMaster,pActivator)) + { + if (pev->spawnflags & SF_MULTIMAN_DEBUG) + ALERT(at_debug, "DEBUG: multi_manager \"%s\": Can't trigger, locked by master \"%s\".\n", STRING(pev->targetname), STRING(m_sMaster)); + return; + } + + // In multiplayer games, clone the MM and execute in the clone (like a thread) + // to allow multiple players to trigger the same multimanager + if ( ShouldClone() ) + { + CMultiManager *pClone = Clone(); + if (pev->spawnflags & SF_MULTIMAN_DEBUG) + ALERT(at_debug, "DEBUG: multi_manager \"%s\": Creating clone.\n", STRING(pev->targetname)); + pClone->ManagerUse( pActivator, pCaller, useType, value ); + if (m_iszLocusThread) + FireTargets( STRING(m_iszLocusThread), pClone, this, USE_TOGGLE, 0 ); + return; + } + + m_hActivator = pActivator; + m_index = 0; + float timeOffset; + + if (m_flMaxWait) //LRC- random time to wait? + { + timeOffset = RANDOM_FLOAT( m_flWait, m_flMaxWait ); + m_iState = STATE_TURN_ON; // while we're waiting, we're in state TURN_ON + } + else if (m_flWait) //LRC- constant time to wait? + { + timeOffset = m_flWait; + m_iState = STATE_TURN_ON; + } + else //LRC- just start immediately. + { + timeOffset = 0; + m_iState = STATE_ON; + } + + m_startTime = timeOffset + gpGlobals->time; + + // startTime should not be affected by this next bit + if (m_cTargets > 0 && !m_iMode && m_flTargetDelay[0] < 0) + { + // negative wait on the first target? + timeOffset += m_flTargetDelay[0]; + } + + if (pev->spawnflags & SF_MULTIMAN_SAMETRIG) //LRC + m_triggerType = useType; + + if (pev->spawnflags & SF_MULTIMAN_LOOP) + SetUse(&CMultiManager :: ManagerUse ); // clones won't already have this set + else + SetUse( NULL );// disable use until all targets have fired + + if (timeOffset > 0) + { + if (pev->spawnflags & SF_MULTIMAN_DEBUG) + ALERT(at_debug, "DEBUG: multi_manager \"%s\": Begin in %f seconds.\n", STRING(pev->targetname), timeOffset); + SetThink(&CMultiManager :: ManagerThink ); + SetNextThink( timeOffset ); + } + else + { + SetThink(&CMultiManager :: ManagerThink ); + ManagerThink(); + } +} + +#if _DEBUG +void CMultiManager :: ManagerReport ( void ) +{ + int cIndex; + + for ( cIndex = 0 ; cIndex < m_cTargets ; cIndex++ ) + { + ALERT ( at_debug, "%s %f\n", STRING(m_iTargetName[cIndex]), m_flTargetDelay[cIndex] ); + } +} +#endif + +//*********************************************************** +//LRC- multi_watcher entity: useful? Well, I think it is. And I'm worth it. :) +//*********************************************************** + +#define SF_SWATCHER_SENDTOGGLE 0x1 +#define SF_SWATCHER_DONTSEND_ON 0x2 +#define SF_SWATCHER_DONTSEND_OFF 0x4 +#define SF_SWATCHER_NOTON 0x8 +#define SF_SWATCHER_OFF 0x10 +#define SF_SWATCHER_TURN_ON 0x20 +#define SF_SWATCHER_TURN_OFF 0x40 +#define SF_SWATCHER_IN_USE 0x80 +#define SF_SWATCHER_VALID 0x200 + +#define SWATCHER_LOGIC_AND 0 +#define SWATCHER_LOGIC_OR 1 +#define SWATCHER_LOGIC_NAND 2 +#define SWATCHER_LOGIC_NOR 3 +#define SWATCHER_LOGIC_XOR 4 +#define SWATCHER_LOGIC_XNOR 5 + +class CStateWatcher : public CBaseToggle +{ +public: + void Spawn ( void ); + void EXPORT Think ( void ); + void KeyValue( KeyValueData *pkvd ); + virtual STATE GetState( void ); + virtual STATE GetState( CBaseEntity *pActivator ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int m_fLogic; // Logic by which to combine the targets + int m_cTargets; // the total number of targets in this manager's fire list. + int m_iTargetName [ MAX_MULTI_TARGETS ];// list of indexes into global string array +// CBaseEntity* m_pTargetEnt [ MAX_MULTI_TARGETS ]; + + BOOL EvalLogic ( CBaseEntity *pEntity ); +}; + +LINK_ENTITY_TO_CLASS( multi_watcher, CStateWatcher ); +LINK_ENTITY_TO_CLASS( watcher, CStateWatcher ); + +TYPEDESCRIPTION CStateWatcher::m_SaveData[] = +{ + DEFINE_FIELD( CStateWatcher, m_fLogic, FIELD_INTEGER ), + DEFINE_FIELD( CStateWatcher, m_cTargets, FIELD_INTEGER ), + DEFINE_ARRAY( CStateWatcher, m_iTargetName, FIELD_STRING, MAX_MULTI_TARGETS ), +// DEFINE_ARRAY( CStateWatcher, m_pTargetEnt, FIELD_CLASSPTR, MAX_MULTI_TARGETS ), +}; + +IMPLEMENT_SAVERESTORE(CStateWatcher,CBaseToggle); + +void CStateWatcher :: KeyValue( KeyValueData *pkvd ) +{ + char tmp[128]; + if (FStrEq(pkvd->szKeyName, "m_fLogic")) + { + m_fLogic = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszWatch")) + { + if ( m_cTargets < MAX_MULTI_TARGETS ) + { + m_iTargetName [ m_cTargets ] = ALLOC_STRING(pkvd->szValue); + m_cTargets++; + pkvd->fHandled = TRUE; + } + else + { + ALERT(at_debug,"%s: Too many targets for %s \"%s\" (limit is %d)\n",pkvd->szKeyName,STRING(pev->classname),STRING(pev->targetname), MAX_MULTI_TARGETS); + } + } + else // add this field to the target list + { + // this assumes that additional fields are targetnames and their values are delay values. + if ( m_cTargets < MAX_MULTI_TARGETS ) + { + UTIL_StripToken( pkvd->szKeyName, tmp ); + m_iTargetName [ m_cTargets ] = ALLOC_STRING( tmp ); + m_cTargets++; + pkvd->fHandled = TRUE; + } + else + { + ALERT(at_debug,"WARNING - %s: Too many targets for %s \"%s\" (limit is %d)\n",pkvd->szKeyName,STRING(pev->classname),STRING(pev->targetname), MAX_MULTI_TARGETS); + } + } +} + +void CStateWatcher :: Spawn ( void ) +{ + pev->solid = SOLID_NOT; + if (pev->target) + SetNextThink( 0.5 ); +} + +STATE CStateWatcher :: GetState( void ) +{ + if (EvalLogic( NULL )) + return STATE_ON; + else + return STATE_OFF; +} + +STATE CStateWatcher :: GetState( CBaseEntity *pActivator ) +{ +// if (pActivator) +// ALERT(at_console, "GetState( %s \"%s\" )\n", STRING(pActivator->pev->classname), STRING(pActivator->pev->targetname)); +// else +// ALERT(at_console, "GetState( NULL )\n"); + if (EvalLogic( pActivator )) + return STATE_ON; + else + return STATE_OFF; +} + +void CStateWatcher :: Think ( void ) +{ + SetNextThink( 0.1 ); + int oldflag = pev->spawnflags & SF_SWATCHER_VALID; + + if (EvalLogic( NULL )) + pev->spawnflags |= SF_SWATCHER_VALID; + else + pev->spawnflags &= ~SF_SWATCHER_VALID; + + if ((pev->spawnflags & SF_SWATCHER_VALID) != oldflag) + { + // the update changed my state... + + if (oldflag) + { + // ...to off. Send "off". +// ALERT(at_console,"%s turns off\n",STRING(pev->classname)); + if (!FBitSet(pev->spawnflags, SF_SWATCHER_DONTSEND_OFF)) + { + if (pev->spawnflags & SF_SWATCHER_SENDTOGGLE) + SUB_UseTargets(this, USE_TOGGLE, 0); + else + SUB_UseTargets(this, USE_OFF, 0); + } + } + else + { + // ...to on. Send "on". +// ALERT(at_console,"%s turns on\n",STRING(pev->classname)); + if (!FBitSet(pev->spawnflags, SF_SWATCHER_DONTSEND_ON)) + { + if (pev->spawnflags & SF_SWATCHER_SENDTOGGLE) + SUB_UseTargets(this, USE_TOGGLE, 0); + else + SUB_UseTargets(this, USE_ON, 0); + } + } + } +} + +BOOL CStateWatcher :: EvalLogic ( CBaseEntity *pActivator ) +{ + int i; + BOOL b; + BOOL xorgot = FALSE; + + CBaseEntity* pEntity; + + for (i = 0; i < m_cTargets; i++) + { +// if (m_pTargetEnt[i] == NULL) +// { +// pEntity = m_pTargetEnt[i]; +// } +// else +// { + pEntity = UTIL_FindEntityByTargetname(NULL,STRING(m_iTargetName[i]), pActivator); + if (pEntity != NULL) + { +// if ((STRING(m_iTargetName[i]))[0] != '*') // don't cache alias values +// { +// //ALERT(at_console,"Watcher: entity %s cached\n",STRING(m_iTargetName[i])); +// m_pTargetEnt[i] = pEntity; +// } + //else + //ALERT(at_console,"Watcher: aliased entity %s not cached\n",STRING(m_iTargetName[i])); + } + else + { + //ALERT(at_console,"Watcher: missing entity %s\n",STRING(m_iTargetName[i])); + continue; // couldn't find this entity; don't do the test. + } +// } + + b = FALSE; + switch (pEntity->GetState()) + { + case STATE_ON: if (!(pev->spawnflags & SF_SWATCHER_NOTON)) b = TRUE; break; + case STATE_OFF: if (pev->spawnflags & SF_SWATCHER_OFF) b = TRUE; break; + case STATE_TURN_ON: if (pev->spawnflags & SF_SWATCHER_TURN_ON) b = TRUE; break; + case STATE_TURN_OFF: if (pev->spawnflags & SF_SWATCHER_TURN_ON) b = TRUE; break; + case STATE_IN_USE: if (pev->spawnflags & SF_SWATCHER_IN_USE) b = TRUE; break; + } + // handle the states for this logic mode + if (b) + { + switch (m_fLogic) + { + case SWATCHER_LOGIC_OR: +// ALERT(at_console,"b is TRUE, OR returns true\n"); + return TRUE; + case SWATCHER_LOGIC_NOR: +// ALERT(at_console,"b is TRUE, NOR returns false\n"); + return FALSE; + case SWATCHER_LOGIC_XOR: +// ALERT(at_console,"b is TRUE, XOR\n"); + if (xorgot) return FALSE; + xorgot = TRUE; + break; + case SWATCHER_LOGIC_XNOR: +// ALERT(at_console,"b is TRUE, XNOR\n"); + if (xorgot) return TRUE; + xorgot = TRUE; + break; + } + } + else // b is false + { + switch (m_fLogic) + { + case SWATCHER_LOGIC_AND: +// ALERT(at_console,"b is FALSE, AND returns false\n"); + return FALSE; + case SWATCHER_LOGIC_NAND: +// ALERT(at_console,"b is FALSE, NAND returns true\n"); + return TRUE; + } + } + } +// handle the default cases for each logic mode + switch (m_fLogic) + { + case SWATCHER_LOGIC_AND: + case SWATCHER_LOGIC_NOR: +// ALERT(at_console,"final, AND/NOR returns true\n"); + return TRUE; + case SWATCHER_LOGIC_XOR: +// ALERT(at_console,"final, XOR\n"); + return xorgot; + case SWATCHER_LOGIC_XNOR: +// ALERT(at_console,"final, XNOR\n"); + return !xorgot; + default: // NAND, OR +// ALERT(at_console,"final, NAND/OR returns false\n"); + return FALSE; + } +} + +//*********************************************************** +#define SF_WRCOUNT_FIRESTART 0x0001 +#define SF_WRCOUNT_STARTED 0x8000 +class CWatcherCount : public CBaseToggle +{ +public: + void Spawn ( void ); + void EXPORT Think ( void ); + virtual STATE GetState( void ) { return (pev->spawnflags & SF_SWATCHER_VALID)?STATE_ON:STATE_OFF; }; + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + +LINK_ENTITY_TO_CLASS( watcher_count, CWatcherCount ); + +void CWatcherCount :: Spawn ( void ) +{ + pev->solid = SOLID_NOT; + SetNextThink( 0.5 ); +} + +void CWatcherCount :: Think ( void ) +{ + SetNextThink( 0.1 ); + int iCount = 0; + CBaseEntity *pCurrent = NULL; + + pCurrent = UTIL_FindEntityByTargetname( NULL, STRING(pev->noise) ); + while (pCurrent != NULL) + { + iCount++; + pCurrent = UTIL_FindEntityByTargetname( pCurrent, STRING(pev->noise) ); + } + + if (pev->spawnflags & SF_WRCOUNT_STARTED) + { + if (iCount > pev->frags) + { + if (iCount < pev->impulse && pev->frags >= pev->impulse) + FireTargets( STRING(pev->netname), this, this, USE_TOGGLE, 0 ); + FireTargets( STRING(pev->noise1), this, this, USE_TOGGLE, 0 ); + } + else if (iCount < pev->frags) + { + if (iCount >= pev->impulse && pev->frags < pev->impulse) + FireTargets( STRING(pev->message), this, this, USE_TOGGLE, 0 ); + FireTargets( STRING(pev->noise2), this, this, USE_TOGGLE, 0 ); + } + } + else + { + pev->spawnflags |= SF_WRCOUNT_STARTED; + if (pev->spawnflags & SF_WRCOUNT_FIRESTART) + { + if (iCount < pev->impulse) + FireTargets( STRING(pev->netname), this, this, USE_TOGGLE, 0 ); + else + FireTargets( STRING(pev->message), this, this, USE_TOGGLE, 0 ); + } + } + pev->frags = iCount; +} + +//*********************************************************** + +// +// Render parameters trigger +// +// This entity will copy its render parameters (renderfx, rendermode, rendercolor, renderamt) +// to its targets when triggered. +// + +// Flags to indicate masking off various render parameters that are normally copied to the targets +#define SF_RENDER_MASKFX (1<<0) +#define SF_RENDER_MASKAMT (1<<1) +#define SF_RENDER_MASKMODE (1<<2) +#define SF_RENDER_MASKCOLOR (1<<3) +//LRC +#define SF_RENDER_KILLTARGET (1<<5) +#define SF_RENDER_ONLYONCE (1<<6) + + +//LRC- RenderFxFader, a subsidiary entity for RenderFxManager +class CRenderFxFader : public CBaseEntity +{ +public: + void Spawn ( void ); + void EXPORT FadeThink ( void ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flStartTime; + float m_flDuration; + float m_flCoarseness; + int m_iStartAmt; + int m_iOffsetAmt; + Vector m_vecStartColor; + Vector m_vecOffsetColor; + float m_fStartScale; + float m_fOffsetScale; + EHANDLE m_hTarget; + + int m_iszAmtFactor; +}; + +TYPEDESCRIPTION CRenderFxFader::m_SaveData[] = +{ + DEFINE_FIELD( CRenderFxFader, m_flStartTime, FIELD_FLOAT), + DEFINE_FIELD( CRenderFxFader, m_flDuration, FIELD_FLOAT), + DEFINE_FIELD( CRenderFxFader, m_flCoarseness, FIELD_FLOAT), + DEFINE_FIELD( CRenderFxFader, m_iStartAmt, FIELD_INTEGER), + DEFINE_FIELD( CRenderFxFader, m_iOffsetAmt, FIELD_INTEGER ), + DEFINE_FIELD( CRenderFxFader, m_vecStartColor, FIELD_VECTOR ), + DEFINE_FIELD( CRenderFxFader, m_vecOffsetColor, FIELD_VECTOR ), + DEFINE_FIELD( CRenderFxFader, m_fStartScale, FIELD_FLOAT), + DEFINE_FIELD( CRenderFxFader, m_fOffsetScale, FIELD_FLOAT ), + DEFINE_FIELD( CRenderFxFader, m_hTarget, FIELD_EHANDLE ), +}; + +IMPLEMENT_SAVERESTORE(CRenderFxFader,CBaseEntity); + +void CRenderFxFader :: Spawn( void ) +{ + SetThink(&CRenderFxFader :: FadeThink ); +} + +void CRenderFxFader :: FadeThink( void ) +{ + if (((CBaseEntity*)m_hTarget) == NULL) + { +// ALERT(at_console, "render_fader removed\n"); + SUB_Remove(); + return; + } + + float flDegree = (gpGlobals->time - m_flStartTime)/m_flDuration; + + if (flDegree >= 1) + { +// ALERT(at_console, "render_fader removes self\n"); + + m_hTarget->pev->renderamt = m_iStartAmt + m_iOffsetAmt; + m_hTarget->pev->rendercolor = m_vecStartColor + m_vecOffsetColor; + m_hTarget->pev->scale = m_fStartScale + m_fOffsetScale; + + SUB_UseTargets( m_hTarget, USE_TOGGLE, 0 ); + + if (pev->spawnflags & SF_RENDER_KILLTARGET) + { + m_hTarget->SetThink(&CRenderFxFader::SUB_Remove); + m_hTarget->SetNextThink(0.1); + } + + m_hTarget = NULL; + + SetNextThink( 0.1 ); + SetThink(&CRenderFxFader ::SUB_Remove); + } + else + { + m_hTarget->pev->renderamt = m_iStartAmt + m_iOffsetAmt * flDegree; + + m_hTarget->pev->rendercolor.x = m_vecStartColor.x + m_vecOffsetColor.x * flDegree; + m_hTarget->pev->rendercolor.y = m_vecStartColor.y + m_vecOffsetColor.y * flDegree; + m_hTarget->pev->rendercolor.z = m_vecStartColor.z + m_vecOffsetColor.z * flDegree; + + m_hTarget->pev->scale = m_fStartScale + m_fOffsetScale * flDegree; + + SetNextThink( m_flCoarseness ); //? + } +} + + +// RenderFxManager itself +class CRenderFxManager : public CPointEntity +{ +public: + void Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Affect( CBaseEntity *pEntity, BOOL bIsLocus, CBaseEntity *pActivator ); + + void KeyValue( KeyValueData *pkvd ); +}; + +LINK_ENTITY_TO_CLASS( env_render, CRenderFxManager ); + +void CRenderFxManager :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_fScale")) + { + pev->scale = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +void CRenderFxManager :: Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (!FStringNull(pev->target)) + { + CBaseEntity* pTarget = UTIL_FindEntityByTargetname( NULL, STRING(pev->target), pActivator); + BOOL first = TRUE; + while ( pTarget != NULL ) + { + Affect( pTarget, first, pActivator ); + first = FALSE; + pTarget = UTIL_FindEntityByTargetname( pTarget, STRING(pev->target), pActivator ); + } + } + + if (pev->spawnflags & SF_RENDER_ONLYONCE) + { + SetThink(&CRenderFxManager ::SUB_Remove); + SetNextThink(0.1); + } +} + +void CRenderFxManager::Affect( CBaseEntity *pTarget, BOOL bIsFirst, CBaseEntity *pActivator ) +{ + entvars_t *pevTarget = pTarget->pev; + + float fAmtFactor = 1; + if ( pev->message && !FBitSet( pev->spawnflags, SF_RENDER_MASKAMT ) ) + fAmtFactor = CalcLocus_Ratio(pActivator, STRING(pev->message)); + + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKFX ) ) + pevTarget->renderfx = pev->renderfx; + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKMODE ) ) + { + //LRC - amt is often 0 when mode is normal. Set it to be fully visible, for fade purposes. + if (pev->frags && pevTarget->renderamt == 0 && pevTarget->rendermode == kRenderNormal) + pevTarget->renderamt = 255; + pevTarget->rendermode = pev->rendermode; + } + if (pev->frags == 0) // not fading? + { + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKAMT ) ) + pevTarget->renderamt = pev->renderamt * fAmtFactor; + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKCOLOR ) ) + pevTarget->rendercolor = pev->rendercolor; + if ( pev->scale ) + pevTarget->scale = pev->scale; + + if (bIsFirst) + FireTargets( STRING(pev->netname), pTarget, this, USE_TOGGLE, 0 ); + } + else + { + //LRC - fade the entity in/out! + // (We create seperate fader entities to do this, one for each entity that needs fading.) + CRenderFxFader *pFader = GetClassPtr( (CRenderFxFader *)NULL ); + pFader->m_hTarget = pTarget; + pFader->m_iStartAmt = pevTarget->renderamt; + pFader->m_vecStartColor = pevTarget->rendercolor; + pFader->m_fStartScale = pevTarget->scale; + if (pFader->m_fStartScale == 0) + pFader->m_fStartScale = 1; // When we're scaling, 0 is treated as 1. Use 1 as the number to fade from. + pFader->pev->spawnflags = pev->spawnflags; + + if (bIsFirst) + pFader->pev->target = pev->netname; + + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKAMT ) ) + pFader->m_iOffsetAmt = (pev->renderamt * fAmtFactor) - pevTarget->renderamt; + else + pFader->m_iOffsetAmt = 0; + + if ( !FBitSet( pev->spawnflags, SF_RENDER_MASKCOLOR ) ) + { + pFader->m_vecOffsetColor.x = pev->rendercolor.x - pevTarget->rendercolor.x; + pFader->m_vecOffsetColor.y = pev->rendercolor.y - pevTarget->rendercolor.y; + pFader->m_vecOffsetColor.z = pev->rendercolor.z - pevTarget->rendercolor.z; + } + else + { + pFader->m_vecOffsetColor = g_vecZero; + } + + if ( pev->scale ) + pFader->m_fOffsetScale = pev->scale - pevTarget->scale; + else + pFader->m_fOffsetScale = 0; + + pFader->m_flStartTime = gpGlobals->time; + pFader->m_flDuration = pev->frags; + pFader->m_flCoarseness = pev->armorvalue; + pFader->SetNextThink( 0 ); + pFader->Spawn(); + } +} + +//*********************************************************** +// +// EnvCustomize +// +// Changes various properties of an entity (some properties only apply to monsters.) +// + +#define SF_CUSTOM_AFFECTDEAD 1 +#define SF_CUSTOM_ONCE 2 +#define SF_CUSTOM_DEBUG 4 + +#define CUSTOM_FLAG_NOCHANGE 0 +#define CUSTOM_FLAG_ON 1 +#define CUSTOM_FLAG_OFF 2 +#define CUSTOM_FLAG_TOGGLE 3 +#define CUSTOM_FLAG_USETYPE 4 +#define CUSTOM_FLAG_INVUSETYPE 5 + +class CEnvCustomize : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void PostSpawn( void ); + void DesiredAction( void ); + void Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + void Affect (CBaseEntity *pTarget, USE_TYPE useType); + int GetActionFor( int iField, int iActive, USE_TYPE useType, char *szDebug ); + void SetBoneController (float fController, int cnum, CBaseEntity *pTarget); + + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + void KeyValue( KeyValueData *pkvd ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flRadius; + int m_iszModel; + int m_iClass; + int m_iPlayerReact; + int m_iPrisoner; + int m_iMonsterClip; + int m_iVisible; + int m_iSolid; + int m_iProvoked; + int m_voicePitch; + int m_iBloodColor; + float m_fFramerate; + float m_fController0; + float m_fController1; + float m_fController2; + float m_fController3; + int m_iReflection; // reflection style +}; + +LINK_ENTITY_TO_CLASS( env_customize, CEnvCustomize ); + +TYPEDESCRIPTION CEnvCustomize::m_SaveData[] = +{ + DEFINE_FIELD( CEnvCustomize, m_flRadius, FIELD_FLOAT), + DEFINE_FIELD( CEnvCustomize, m_iszModel, FIELD_STRING), + DEFINE_FIELD( CEnvCustomize, m_iClass, FIELD_INTEGER), + DEFINE_FIELD( CEnvCustomize, m_iPlayerReact, FIELD_INTEGER ), + DEFINE_FIELD( CEnvCustomize, m_iPrisoner, FIELD_INTEGER ), + DEFINE_FIELD( CEnvCustomize, m_iMonsterClip, FIELD_INTEGER ), + DEFINE_FIELD( CEnvCustomize, m_iVisible, FIELD_INTEGER ), + DEFINE_FIELD( CEnvCustomize, m_iSolid, FIELD_INTEGER ), + DEFINE_FIELD( CEnvCustomize, m_iProvoked, FIELD_INTEGER ), + DEFINE_FIELD( CEnvCustomize, m_voicePitch, FIELD_INTEGER ), + DEFINE_FIELD( CEnvCustomize, m_iBloodColor, FIELD_INTEGER ), + DEFINE_FIELD( CEnvCustomize, m_fFramerate, FIELD_FLOAT ), + DEFINE_FIELD( CEnvCustomize, m_fController0, FIELD_FLOAT ), + DEFINE_FIELD( CEnvCustomize, m_fController1, FIELD_FLOAT ), + DEFINE_FIELD( CEnvCustomize, m_fController2, FIELD_FLOAT ), + DEFINE_FIELD( CEnvCustomize, m_fController3, FIELD_FLOAT ), + DEFINE_FIELD( CEnvCustomize, m_iReflection, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE(CEnvCustomize,CBaseEntity); + +void CEnvCustomize :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iVisible")) + { + m_iVisible = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iSolid")) + { + m_iSolid = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszModel")) + { + m_iszModel = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_voicePitch")) + { + m_voicePitch = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iPrisoner")) + { + m_iPrisoner = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iMonsterClip")) + { + m_iMonsterClip = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iClass")) + { + m_iClass = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iPlayerReact")) + { + m_iPlayerReact = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flRadius")) + { + m_flRadius = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iProvoked")) + { + m_iProvoked = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_bloodColor") || FStrEq(pkvd->szKeyName, "m_iBloodColor")) + { + m_iBloodColor = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fFramerate")) + { + m_fFramerate = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fController0")) + { + m_fController0 = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fController1")) + { + m_fController1 = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fController2")) + { + m_fController2 = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fController3")) + { + m_fController3 = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iReflection")) + { + m_iReflection = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CEnvCustomize :: Spawn ( void ) +{ + Precache(); +} + +void CEnvCustomize :: Precache( void ) +{ + if (m_iszModel) + PRECACHE_MODEL((char*)STRING(m_iszModel)); +} + +void CEnvCustomize :: PostSpawn( void ) +{ + if (!pev->targetname) + { + // no name - just take effect when everything's spawned. + UTIL_DesiredAction( this ); + } +} + +void CEnvCustomize :: DesiredAction ( void ) +{ + Use(this, this, USE_TOGGLE, 0); +} + +void CEnvCustomize :: Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( FStringNull(pev->target) ) + { + if ( pActivator ) + Affect(pActivator, useType); + else if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, "DEBUG: env_customize \"%s\" was fired without a locus!\n", STRING(pev->targetname)); + } + else + { + BOOL fail = TRUE; + CBaseEntity *pTarget = UTIL_FindEntityByTargetname(NULL, STRING(pev->target), pActivator); + while (pTarget) + { + Affect(pTarget, useType); + fail = FALSE; + pTarget = UTIL_FindEntityByTargetname(pTarget, STRING(pev->target), pActivator); + } + pTarget = UTIL_FindEntityByClassname(NULL, STRING(pev->target)); + while (pTarget) + { + Affect(pTarget, useType); + fail = FALSE; + pTarget = UTIL_FindEntityByClassname(pTarget, STRING(pev->target)); + } + if (fail && pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, "DEBUG: env_customize \"%s\" does nothing; can't find any entity with name or class \"%s\".\n", STRING(pev->target)); + } + + if (pev->spawnflags & SF_CUSTOM_ONCE) + { + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, "DEBUG: env_customize \"%s\" removes itself.\n", STRING(pev->targetname)); + UTIL_Remove(this); + } +} + +//LRCT +extern DLL_GLOBAL ULONG g_ulModelIndexPlayer; + +void CEnvCustomize :: Affect (CBaseEntity *pTarget, USE_TYPE useType) +{ + CBaseMonster* pMonster = pTarget->MyMonsterPointer(); + if (!FBitSet(pev->spawnflags, SF_CUSTOM_AFFECTDEAD) && pMonster && pMonster->m_MonsterState == MONSTERSTATE_DEAD) + { + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, "DEBUG: env_customize %s does nothing; can't apply to a corpse.\n", STRING(pev->targetname)); + return; + } + + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, "DEBUG: env_customize \"%s\" affects %s \"%s\": [", STRING(pev->targetname), STRING(pTarget->pev->classname), STRING(pTarget->pev->targetname)); + + if (m_iszModel) + { + Vector vecMins, vecMaxs; + vecMins = pTarget->pev->mins; + vecMaxs = pTarget->pev->maxs; + SET_MODEL(pTarget->edict(),STRING(m_iszModel)); +// if (pTarget->pev->flags & FL_CLIENT) +// g_ulModelIndexPlayer = pTarget->pev->modelindex; + UTIL_SetSize(pTarget->pev,vecMins,vecMaxs); + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " model=%s", STRING(m_iszModel)); + } + SetBoneController( m_fController0, 0, pTarget ); + SetBoneController( m_fController1, 1, pTarget ); + SetBoneController( m_fController2, 2, pTarget ); + SetBoneController( m_fController3, 3, pTarget ); + if (m_fFramerate != -1) + { + //FIXME: check for env_model, stop it from changing its own framerate + pTarget->pev->framerate = m_fFramerate; + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " framerate=%f", m_fFramerate); + } + if (pev->body != -1) + { + pTarget->pev->body = pev->body; + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " body = %d", pev->body); + } + if (pev->skin != -1) + { + if (pev->skin == -2) + { + if (pTarget->pev->skin) + { + pTarget->pev->skin = 0; + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " skin=0"); + } + else + { + pTarget->pev->skin = 1; + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " skin=1"); + } + } + else if (pev->skin == -99) // special option to set CONTENTS_EMPTY + { + pTarget->pev->skin = -1; + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " skin=-1"); + } + else + { + pTarget->pev->skin = pev->skin; + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " skin=%d", pTarget->pev->skin); + } + } + + if( m_iReflection != -1 ) + { + switch( m_iReflection ) + { + case 0: + pTarget->pev->effects &= ~EF_NOREFLECT; + pTarget->pev->effects &= ~EF_REFLECTONLY; + break; + case 1: + pTarget->pev->effects |= EF_NOREFLECT; + pTarget->pev->effects &= ~EF_REFLECTONLY; + break; + case 2: + pTarget->pev->effects |= EF_REFLECTONLY; + pTarget->pev->effects &= ~EF_NOREFLECT; + break; + } + } + + switch ( GetActionFor(m_iVisible, !(pTarget->pev->effects & EF_NODRAW), useType, "visible")) + { + case CUSTOM_FLAG_ON: pTarget->pev->effects &= ~EF_NODRAW; break; + case CUSTOM_FLAG_OFF: pTarget->pev->effects |= EF_NODRAW; break; + } + + switch ( GetActionFor(m_iSolid, pTarget->pev->solid != SOLID_NOT, useType, "solid")) + { + case CUSTOM_FLAG_ON: + if (*(STRING(pTarget->pev->model)) == '*') + pTarget->pev->solid = SOLID_BSP; + else + pTarget->pev->solid = SOLID_SLIDEBOX; + break; + case CUSTOM_FLAG_OFF: pTarget->pev->solid = SOLID_NOT; break; + } +/* if (m_iVisible != CUSTOM_FLAG_NOCHANGE) + { + if (pTarget->pev->effects & EF_NODRAW && (m_iVisible == CUSTOM_FLAG_TOGGLE || m_iVisible == CUSTOM_FLAG_ON)) + { + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " visible=YES"); + } + else if (m_iVisible != CUSTOM_FLAG_ON) + { + pTarget->pev->effects |= EF_NODRAW; + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " visible=NO"); + } + } + + if (m_iSolid != CUSTOM_FLAG_NOCHANGE) + { + if (pTarget->pev->solid == SOLID_NOT && (m_iSolid == CUSTOM_FLAG_TOGGLE || m_iSolid == CUSTOM_FLAG_ON)) + { + if (*(STRING(pTarget->pev->model)) == '*') + { + pTarget->pev->solid = SOLID_BSP; + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " solid=YES(bsp)"); + } + else + { + pTarget->pev->solid = SOLID_SLIDEBOX; + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " solid=YES(point)"); + } + } + else if (m_iSolid != CUSTOM_FLAG_ON) + { + pTarget->pev->solid = SOLID_NOT; + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " solid=NO"); + } + else if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " solid=unchanged"); + } +*/ + if (!pMonster) + { + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " ]\n"); + return; + } + + if (m_iBloodColor != 0) + { + pMonster->m_bloodColor = m_iBloodColor; + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " bloodcolor=%d", m_iBloodColor); + } + if (m_voicePitch > 0) + { + if (FClassnameIs(pTarget->pev,"monster_barney") || FClassnameIs(pTarget->pev,"monster_scientist") || FClassnameIs(pTarget->pev,"monster_sitting_scientist")) + { + ((CTalkMonster*)pTarget)->m_voicePitch = m_voicePitch; + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " voicePitch(talk)=%d", m_voicePitch); + } +// else if (FClassnameIs(pTarget->pev,"monster_human_grunt") || FClassnameIs(pTarget->pev,"monster_human_grunt_repel")) +// ((CHGrunt*)pTarget)->m_voicePitch = m_voicePitch; +// else if (FClassnameIs(pTarget->pev,"monster_alien_slave")) +// ((CISlave*)pTarget)->m_voicePitch = m_voicePitch; + else if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " voicePitch=unchanged"); + } + + if (m_iClass != 0) + { + pMonster->m_iClass = m_iClass; + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " class=%d", m_iClass); + if (pMonster->m_hEnemy) + { + pMonster->m_hEnemy = NULL; + // make 'em stop attacking... might be better to use a different signal? + pMonster->SetConditions( bits_COND_NEW_ENEMY ); + } + } + if (m_iPlayerReact != -1) + { + pMonster->m_iPlayerReact = m_iPlayerReact; + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " playerreact=%d", m_iPlayerReact); + } + +// SetCustomFlag( m_iPrisoner, pMonster->pev->spawnflags, SF_MONSTER_PRISONER, useType, "prisoner"); + switch (GetActionFor(m_iPrisoner, pMonster->pev->spawnflags & SF_MONSTER_PRISONER, useType, "prisoner") ) + { + case CUSTOM_FLAG_ON: + pMonster->pev->spawnflags |= SF_MONSTER_PRISONER; + if (pMonster->m_hEnemy) + { + pMonster->m_hEnemy = NULL; + // make 'em stop attacking... might be better to use a different signal? + pMonster->SetConditions( bits_COND_NEW_ENEMY ); + } + break; + case CUSTOM_FLAG_OFF: + pMonster->pev->spawnflags &= ~SF_MONSTER_PRISONER; + break; + } +/* if (m_iPrisoner != CUSTOM_FLAG_NOCHANGE) + { + if (pMonster->pev->spawnflags & SF_MONSTER_PRISONER && (m_iPrisoner == CUSTOM_FLAG_TOGGLE || m_iPrisoner == CUSTOM_FLAG_OFF)) + { + pMonster->pev->spawnflags &= ~SF_MONSTER_PRISONER; + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " prisoner=NO"); + } + else if (m_iPrisoner != CUSTOM_FLAG_OFF) + { + pMonster->pev->spawnflags |= SF_MONSTER_PRISONER; + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " prisoner=YES"); + if (pMonster->m_hEnemy) + { + pMonster->m_hEnemy = NULL; + // make 'em stop attacking... might be better to use a different signal? + pMonster->SetConditions( bits_COND_NEW_ENEMY ); + } + } + else if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " prisoner=unchanged"); + } +*/ + switch (GetActionFor(m_iMonsterClip, pMonster->pev->flags & FL_MONSTERCLIP, useType, "monsterclip") ) + { + case CUSTOM_FLAG_ON: pMonster->pev->flags |= FL_MONSTERCLIP; break; + case CUSTOM_FLAG_OFF: pMonster->pev->flags &= ~FL_MONSTERCLIP; break; + } +/* if (m_iMonsterClip != CUSTOM_FLAG_NOCHANGE) + { + if (pMonster->pev->flags & FL_MONSTERCLIP && (m_iMonsterClip == CUSTOM_FLAG_TOGGLE || m_iMonsterClip == CUSTOM_FLAG_OFF)) + { + pMonster->pev->flags &= ~FL_MONSTERCLIP; + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " monsterclip=NO"); + } + else if (m_iMonsterClip != CUSTOM_FLAG_OFF) + { + pMonster->pev->flags |= FL_MONSTERCLIP; + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " monsterclip=YES"); + } + else if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " monsterclip=unchanged"); + } +*/ + switch (GetActionFor(m_iProvoked, pMonster->m_afMemory & bits_MEMORY_PROVOKED, useType, "provoked") ) + { + case CUSTOM_FLAG_ON: pMonster->Remember(bits_MEMORY_PROVOKED); break; + case CUSTOM_FLAG_OFF: pMonster->Forget(bits_MEMORY_PROVOKED); break; + } +/* if (m_iProvoked != CUSTOM_FLAG_NOCHANGE) + { + if (pMonster->m_afMemory & bits_MEMORY_PROVOKED && (m_iProvoked == CUSTOM_FLAG_TOGGLE || m_iProvoked == CUSTOM_FLAG_OFF)) + { + pMonster->Forget(bits_MEMORY_PROVOKED); + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " provoked=NO"); + } + else if (m_iProvoked != CUSTOM_FLAG_OFF) + { + pMonster->Remember(bits_MEMORY_PROVOKED); + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " provoked=YES"); + } + else if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " provoked=unchanged"); + } +*/ + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " ]\n"); +} + +int CEnvCustomize::GetActionFor( int iField, int iActive, USE_TYPE useType, char* szDebug) +{ + int iAction = iField; + + if (iAction == CUSTOM_FLAG_USETYPE) + { + if (useType == USE_ON) + iAction = CUSTOM_FLAG_ON; + else if (useType == USE_OFF) + iAction = CUSTOM_FLAG_OFF; + else + iAction = CUSTOM_FLAG_TOGGLE; + } + else if (iAction == CUSTOM_FLAG_INVUSETYPE) + { + if (useType == USE_ON) + iAction = CUSTOM_FLAG_OFF; + else if (useType == USE_OFF) + iAction = CUSTOM_FLAG_ON; + else + iAction = CUSTOM_FLAG_TOGGLE; + } + + if (iAction == CUSTOM_FLAG_TOGGLE) + { + if (iActive) + iAction = CUSTOM_FLAG_OFF; + else + iAction = CUSTOM_FLAG_ON; + } + + if (pev->spawnflags & SF_CUSTOM_DEBUG) + { + if (iAction == CUSTOM_FLAG_ON) + ALERT(at_debug, " %s=YES", szDebug); + else if (iAction == CUSTOM_FLAG_OFF) + ALERT(at_debug, " %s=NO", szDebug); + } + return iAction; +} + +void CEnvCustomize::SetBoneController( float fController, int cnum, CBaseEntity *pTarget) +{ + if (fController) //FIXME: the pTarget isn't necessarily a CBaseAnimating. + { + if (fController == 1024) + { + ((CBaseAnimating*)pTarget)->SetBoneController( cnum, 0 ); + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " bone%d=0", cnum); + } + else + { + ((CBaseAnimating*)pTarget)->SetBoneController( cnum, fController ); + if (pev->spawnflags & SF_CUSTOM_DEBUG) + ALERT(at_debug, " bone%d=%f", cnum, fController); + } + } +} + +//===================================== +// trigger_x entities + +class CBaseTrigger : public CBaseToggle +{ +public: + //LRC - this was very bloated. I moved lots of methods into the + // subclasses where they belonged. + void InitTrigger( void ); + void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + BOOL CanTouch( entvars_t *pevToucher ); + + virtual int ObjectCaps( void ) { return CBaseToggle :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + +LINK_ENTITY_TO_CLASS( trigger, CBaseTrigger ); + +BOOL CBaseTrigger :: CanTouch( entvars_t *pevToucher ) +{ + if ( !pev->netname ) + { + // Only touch clients, monsters, or pushables (depending on flags) + if (pevToucher->flags & FL_CLIENT) + return !(pev->spawnflags & SF_TRIGGER_NOCLIENTS); + else if (pevToucher->flags & FL_MONSTER) + return pev->spawnflags & SF_TRIGGER_ALLOWMONSTERS; + else if (FClassnameIs(pevToucher,"func_pushable")) + return pev->spawnflags & SF_TRIGGER_PUSHABLES; + else + return pev->spawnflags & SF_TRIGGER_EVERYTHING; + } + else + { + // If netname is set, it's an entity-specific trigger; we ignore the spawnflags. + if (!FClassnameIs(pevToucher, STRING(pev->netname)) && + (!pevToucher->targetname || !FStrEq(STRING(pevToucher->targetname), STRING(pev->netname)))) + return FALSE; + } + return TRUE; +} + +// +// ToggleUse - If this is the USE function for a trigger, its state will toggle every time it's fired +// +void CBaseTrigger :: ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (pev->solid == SOLID_NOT) + {// if the trigger is off, turn it on + pev->solid = SOLID_TRIGGER; + + // Force retouch + gpGlobals->force_retouch++; + } + else + {// turn the trigger off + pev->solid = SOLID_NOT; + } + UTIL_SetOrigin( this, pev->origin ); +} + +/* +================ +InitTrigger +================ +*/ +void CBaseTrigger::InitTrigger( ) +{ + // trigger angles are used for one-way touches. An angle of 0 is assumed + // to mean no restrictions, so use a yaw of 360 instead. + if (pev->angles != g_vecZero) + SetMovedir(pev); + pev->solid = SOLID_TRIGGER; + pev->movetype = MOVETYPE_NONE; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + if ( CVAR_GET_FLOAT("showtriggers") == 0 ) + SetBits( pev->effects, EF_NODRAW ); +} + +//===================================== +// +// trigger_hurt - hurts anything that touches it. if the trigger has a targetname, firing it will toggle state +// +class CTriggerHurt : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT RadiationThink( void ); + void EXPORT HurtTouch ( CBaseEntity *pOther ); + virtual void KeyValue( KeyValueData *pkvd ); +}; + +LINK_ENTITY_TO_CLASS( trigger_hurt, CTriggerHurt ); + +void CTriggerHurt :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "damage")) + { + pev->dmg = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "damagetype")) + { + m_bitsDamageInflict |= atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "cangib")) + { + switch (atoi(pkvd->szValue)) + { + case 1: + m_bitsDamageInflict |= DMG_ALWAYSGIB; + case 2: + m_bitsDamageInflict |= DMG_NEVERGIB; + } + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CTriggerHurt :: Spawn( void ) +{ + InitTrigger(); + SetTouch(&CTriggerHurt :: HurtTouch ); + + if ( !FStringNull ( pev->targetname ) ) + { + SetUse(&CTriggerHurt :: ToggleUse ); + } + else + { + SetUse ( NULL ); + } + + if (m_bitsDamageInflict & DMG_RADIATION) + { + SetThink(&CTriggerHurt :: RadiationThink ); + SetNextThink( RANDOM_FLOAT(0.0, 0.5) ); + } + + if ( FBitSet (pev->spawnflags, SF_TRIGGER_HURT_START_OFF) )// if flagged to Start Turned Off, make trigger nonsolid. + pev->solid = SOLID_NOT; + + UTIL_SetOrigin( this, pev->origin ); // Link into the list +} + +// When touched, a hurt trigger does DMG points of damage each half-second +void CTriggerHurt :: HurtTouch ( CBaseEntity *pOther ) +{ + float fldmg; + + if ( !pOther->pev->takedamage ) + return; + + if ( (pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYTOUCH) && !pOther->IsPlayer() ) + { + // this trigger is only allowed to touch clients, and this ain't a client. + return; + } + + if ( (pev->spawnflags & SF_TRIGGER_HURT_NO_CLIENTS) && pOther->IsPlayer() ) + return; + + // HACKHACK -- In multiplayer, players touch this based on packet receipt. + // So the players who send packets later aren't always hurt. Keep track of + // how much time has passed and whether or not you've touched that player + if ( g_pGameRules->IsMultiplayer() ) + { + if ( pev->dmgtime > gpGlobals->time ) + { + if ( gpGlobals->time != pev->pain_finished ) + {// too early to hurt again, and not same frame with a different entity + if ( pOther->IsPlayer() ) + { + int playerMask = 1 << (pOther->entindex() - 1); + + // If I've already touched this player (this time), then bail out + if ( pev->impulse & playerMask ) + return; + + // Mark this player as touched + // BUGBUG - There can be only 32 players! + pev->impulse |= playerMask; + } + else + { + return; + } + } + } + else + { + // New clock, "un-touch" all players + pev->impulse = 0; + if ( pOther->IsPlayer() ) + { + int playerMask = 1 << (pOther->entindex() - 1); + + // Mark this player as touched + // BUGBUG - There can be only 32 players! + pev->impulse |= playerMask; + } + } + } + else // Original code -- single player + { + if ( pev->dmgtime > gpGlobals->time && gpGlobals->time != pev->pain_finished ) + {// too early to hurt again, and not same frame with a different entity + return; + } + } + + + + // If this is time_based damage (poison, radiation), override the pev->dmg with a + // default for the given damage type. Monsters only take time-based damage + // while touching the trigger. Player continues taking damage for a while after + // leaving the trigger + + fldmg = pev->dmg * 0.5; // 0.5 seconds worth of damage, pev->dmg is damage/second + + + // JAY: Cut this because it wasn't fully realized. Damage is simpler now. +#if 0 + switch (m_bitsDamageInflict) + { + default: break; + case DMG_POISON: fldmg = POISON_DAMAGE/4; break; + case DMG_NERVEGAS: fldmg = NERVEGAS_DAMAGE/4; break; + case DMG_RADIATION: fldmg = RADIATION_DAMAGE/4; break; + case DMG_PARALYZE: fldmg = PARALYZE_DAMAGE/4; break; // UNDONE: cut this? should slow movement to 50% + case DMG_ACID: fldmg = ACID_DAMAGE/4; break; + case DMG_SLOWBURN: fldmg = SLOWBURN_DAMAGE/4; break; + case DMG_SLOWFREEZE: fldmg = SLOWFREEZE_DAMAGE/4; break; + } +#endif + + if ( fldmg < 0 ) + pOther->TakeHealth( -fldmg, m_bitsDamageInflict ); + else + pOther->TakeDamage( pev, pev, fldmg, m_bitsDamageInflict ); + + // Store pain time so we can get all of the other entities on this frame + pev->pain_finished = gpGlobals->time; + + // Apply damage every half second + pev->dmgtime = gpGlobals->time + 0.5;// half second delay until this trigger can hurt toucher again + + + + if ( pev->target ) + { + // trigger has a target it wants to fire. + if ( pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYFIRE ) + { + // if the toucher isn't a client, don't fire the target! + if ( !pOther->IsPlayer() ) + { + return; + } + } + + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); + if ( pev->spawnflags & SF_TRIGGER_HURT_TARGETONCE ) + pev->target = 0; + } +} + +// trigger hurt that causes radiation will do a radius +// check and set the player's geiger counter level +// according to distance from center of trigger + +void CTriggerHurt :: RadiationThink( void ) +{ + + edict_t *pentPlayer; + CBasePlayer *pPlayer = NULL; + float flRange; + entvars_t *pevTarget; + Vector vecSpot1; + Vector vecSpot2; + Vector vecRange; + Vector origin; + Vector view_ofs; + + // check to see if a player is in pvs + // if not, continue + + // set origin to center of trigger so that this check works + origin = pev->origin; + view_ofs = pev->view_ofs; + + pev->origin = (pev->absmin + pev->absmax) * 0.5; + pev->view_ofs = pev->view_ofs * 0.0; + + pentPlayer = FIND_CLIENT_IN_PVS(edict()); + + pev->origin = origin; + pev->view_ofs = view_ofs; + + // reset origin + + if (!FNullEnt(pentPlayer)) + { + + pPlayer = GetClassPtr( (CBasePlayer *)VARS(pentPlayer)); + + pevTarget = VARS(pentPlayer); + + // get range to player; + + vecSpot1 = (pev->absmin + pev->absmax) * 0.5; + vecSpot2 = (pevTarget->absmin + pevTarget->absmax) * 0.5; + + vecRange = vecSpot1 - vecSpot2; + flRange = vecRange.Length(); + + // if player's current geiger counter range is larger + // than range to this trigger hurt, reset player's + // geiger counter range + + if (pPlayer->m_flgeigerRange >= flRange) + pPlayer->m_flgeigerRange = flRange; + } + + SetNextThink( 0.25 ); +} + +//===================================== +// +// trigger_hevcharge +// charges/discharges the player's suit. if the trigger has a targetname, firing it will toggle state +//LRC +#define SF_HEVCHARGE_NOANNOUNCE 0x04 + +class CTriggerHevCharge : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT ChargeTouch ( CBaseEntity *pOther ); + void EXPORT AnnounceThink( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_hevcharge, CTriggerHevCharge ); + +void CTriggerHevCharge :: Spawn( void ) +{ + InitTrigger(); + SetTouch(&CTriggerHevCharge :: ChargeTouch ); + SetThink(&CTriggerHevCharge :: AnnounceThink ); + + if ( !FStringNull ( pev->targetname ) ) + { + SetUse(&CTriggerHevCharge :: ToggleUse ); + } + else + { + SetUse ( NULL ); + } + + if ( FBitSet (pev->spawnflags, SF_TRIGGER_HURT_START_OFF) )// if flagged to Start Turned Off, make trigger nonsolid. + pev->solid = SOLID_NOT; + + UTIL_SetOrigin( this, pev->origin ); // Link into the list +} + +void CTriggerHevCharge :: ChargeTouch ( CBaseEntity *pOther ) +{ + if (IsLockedByMaster()) + return; + + // check that it's a player with an HEV suit + if ( !pOther->IsPlayer() ) + return; + + CBasePlayer *pPlayer = (CBasePlayer *)pOther; + + if( !FBitSet( pPlayer->m_iHideHUD, ITEM_SUIT )) + return; + + // FIXME: add in the multiplayer fix, from trigger_hurt? + if ( pev->dmgtime > gpGlobals->time ) + return; + pev->dmgtime = gpGlobals->time + 0.5;// half second delay until this trigger can hurt toucher again + + int iNewArmor = pOther->pev->armorvalue + pev->frags; + if (iNewArmor > MAX_NORMAL_BATTERY) iNewArmor = MAX_NORMAL_BATTERY; + if (iNewArmor < 0) + iNewArmor = 0; + if (iNewArmor == pOther->pev->armorvalue) // if no change, we've finished. + return; + + pOther->pev->armorvalue = iNewArmor; + + //FIXME: support the 'target once' flag + if ( pev->target ) + { + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); + } + + // The suit doesn't say much in multiplayer. Which is convenient, since it lets me be lazy here. + if ( g_pGameRules->IsMultiplayer() || pev->spawnflags & SF_HEVCHARGE_NOANNOUNCE) + return; + + // as long as the suit is charging, this think will never actually happen. + //ALERT(at_debug, "%s ", STRING(pev->targetname)); + pev->aiment = ENT(pOther->pev); + SetNextThink( 1 ); +} + +void CTriggerHevCharge :: AnnounceThink ( ) +{ + CBasePlayer *pPlayer = (CBasePlayer*)(CBaseEntity::Instance(pev->aiment)); + + if (!pPlayer || !pPlayer->IsPlayer()) + { + ALERT(at_error, "trigger_hevcharge: invalid player in Announce!\n"); + return; + } + //copied from item_battery... + + int pct; + char szcharge[64]; + + // Suit reports new power level + // For some reason this wasn't working in release build -- round it. + pct = (int)( (float)(pPlayer->pev->armorvalue * 100.0) * (1.0/MAX_NORMAL_BATTERY) + 0.5); + pct = (pct / 5); + if (pct > 0) + pct--; + + sprintf( szcharge,"!HEV_%1dP", pct ); + //ALERT(at_debug, "Announce %s\n", szcharge); + + ((CBasePlayer*)pPlayer)->SetSuitUpdate(szcharge, FALSE, SUIT_REPEAT_OK); +} + + +//===================================== +// +// trigger_monsterjump +// +class CTriggerMonsterJump : public CBaseTrigger +{ +public: + void Spawn( void ); + void Touch( CBaseEntity *pOther ); + void Think( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_monsterjump, CTriggerMonsterJump ); + + +void CTriggerMonsterJump :: Spawn ( void ) +{ + SetMovedir ( pev ); + + InitTrigger (); + + DontThink(); + pev->speed = 200; + m_flHeight = 150; + + if ( !FStringNull ( pev->targetname ) ) + {// if targetted, spawn turned off + pev->solid = SOLID_NOT; + UTIL_SetOrigin( this, pev->origin ); // Unlink from trigger list + SetUse(&CTriggerMonsterJump :: ToggleUse ); + } +} + + +void CTriggerMonsterJump :: Think( void ) +{ + pev->solid = SOLID_NOT;// kill the trigger for now !!!UNDONE + UTIL_SetOrigin( this, pev->origin ); // Unlink from trigger list + SetThink( NULL ); +} + +void CTriggerMonsterJump :: Touch( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + if ( !FBitSet ( pevOther->flags , FL_MONSTER ) ) + {// touched by a non-monster. + return; + } + + pevOther->origin.z += 1; + + if ( FBitSet ( pevOther->flags, FL_ONGROUND ) ) + {// clear the onground so physics don't bitch + pevOther->flags &= ~FL_ONGROUND; + } + + // toss the monster! + pevOther->velocity = pev->movedir * pev->speed; + pevOther->velocity.z += m_flHeight; + SetNextThink( 0 ); +} + + +//===================================== +// +// trigger_cdaudio - starts/stops cd audio tracks +// +class CTriggerCDAudio : public CBaseTrigger +{ +public: + void Spawn( void ); + + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void PlayTrack( void ); + void Touch ( CBaseEntity *pOther ); +}; + +LINK_ENTITY_TO_CLASS( trigger_cdaudio, CTriggerCDAudio ); + +// +// Changes tracks or stops CD when player touches +// +// !!!HACK - overloaded HEALTH to avoid adding new field +void CTriggerCDAudio :: Touch ( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() ) + {// only clients may trigger these events + return; + } + + PlayTrack(); +} + +void CTriggerCDAudio :: Spawn( void ) +{ + InitTrigger(); +} + +void CTriggerCDAudio::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + PlayTrack(); +} + +void PlayCDTrack( int iTrack ) +{ + edict_t *pClient; + + // manually find the single player. + pClient = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + + // Can't play if the client is not connected! + if ( !pClient ) + return; + + if ( iTrack < -1 || iTrack > 30 ) + { + ALERT ( at_debug, "TriggerCDAudio - Track %d out of range\n" ); + return; + } + + if ( iTrack == -1 ) + { + CLIENT_COMMAND ( pClient, "cd pause\n"); + } + else + { + char string [ 64 ]; + + sprintf( string, "cd play %3d\n", iTrack ); + CLIENT_COMMAND ( pClient, string); + } +} + + +// only plays for ONE client, so only use in single play! +void CTriggerCDAudio :: PlayTrack( void ) +{ + PlayCDTrack( (int)pev->health ); + + SetTouch( NULL ); + UTIL_Remove( this ); +} + + +// This plays a CD track when fired or when the player enters it's radius +class CTargetCDAudio : public CPointEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Think( void ); + void Play( void ); +}; + +LINK_ENTITY_TO_CLASS( target_cdaudio, CTargetCDAudio ); + +void CTargetCDAudio :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "radius")) + { + pev->scale = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +void CTargetCDAudio :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + if ( pev->scale > 0 ) + SetNextThink( 1.0 ); +} + +void CTargetCDAudio::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + Play(); +} + +// only plays for ONE client, so only use in single play! +void CTargetCDAudio::Think( void ) +{ + edict_t *pClient; + + // manually find the single player. + pClient = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + + // Can't play if the client is not connected! + if ( !pClient ) + return; + + SetNextThink( 0.5 ); + + if ( (pClient->v.origin - pev->origin).Length() <= pev->scale ) + Play(); + +} + +void CTargetCDAudio::Play( void ) +{ + PlayCDTrack( (int)pev->health ); + UTIL_Remove(this); +} + +//===================================== +//trigger_multiple + +class CTriggerMultiple : public CBaseTrigger +{ +public: + void Spawn( void ); + void Precache( void ) + { + if (!FStringNull(pev->noise)) + PRECACHE_SOUND((char*)STRING(pev->noise)); + } + void EXPORT MultiTouch( CBaseEntity *pOther ); + void EXPORT MultiWaitOver( void ); + void ActivateMultiTrigger( CBaseEntity *pActivator ); +}; + +LINK_ENTITY_TO_CLASS( trigger_multiple, CTriggerMultiple ); + + +void CTriggerMultiple :: Spawn( void ) +{ + if (m_flWait == 0) + m_flWait = 0.2; + + InitTrigger(); + + ASSERTSZ(pev->health == 0, "trigger_multiple with health"); + SetTouch(&CTriggerMultiple :: MultiTouch ); + Precache(); +} + +void CTriggerMultiple :: MultiTouch( CBaseEntity *pOther ) +{ + entvars_t *pevToucher; + + pevToucher = pOther->pev; + + if (!CanTouch(pevToucher)) return; + +#if 0 + // if the trigger has an angles field, check player's facing direction + if (pev->movedir != g_vecZero) + { + UTIL_MakeVectors( pevToucher->angles ); + if ( DotProduct( gpGlobals->v_forward, pev->movedir ) < 0 ) + return; // not facing the right way + } +#endif + + ActivateMultiTrigger( pOther ); +} + + +// +// the trigger was just touched/killed/used +// m_hActivator gets set to the activator so it can be held through a delay +// so wait for the delay time before firing +// +void CTriggerMultiple :: ActivateMultiTrigger( CBaseEntity *pActivator ) +{ + if (m_fNextThink > gpGlobals->time) + return; // still waiting for reset time + + if (!UTIL_IsMasterTriggered(m_sMaster,pActivator)) + return; + + if (FClassnameIs(pev, "trigger_secret")) + { + if ( pev->enemy == NULL || !FClassnameIs(pev->enemy, "player")) + return; + gpGlobals->found_secrets++; + } + + if (!FStringNull(pev->noise)) + EMIT_SOUND(ENT(pev), CHAN_VOICE, (char*)STRING(pev->noise), 1, ATTN_NORM); + +// don't trigger again until reset +// pev->takedamage = DAMAGE_NO; + + m_hActivator = pActivator; + SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); + + if ( pev->message && pActivator->IsPlayer() ) + { + UTIL_ShowMessage( STRING(pev->message), pActivator ); +// CLIENT_PRINTF( ENT( pActivator->pev ), print_center, STRING(pev->message) ); + } + + if (m_flWait > 0) + { + SetThink(&CTriggerMultiple :: MultiWaitOver ); + SetNextThink( m_flWait ); + } + else + { + // we can't just remove (self) here, because this is a touch function + // called while C code is looping through area links... + SetTouch( NULL ); + SetNextThink( 0.1 ); + SetThink(&CTriggerMultiple :: SUB_Remove ); + } +} + +// the wait time has passed, so set back up for another activation +void CTriggerMultiple :: MultiWaitOver( void ) +{ +// if (pev->max_health) +// { +// pev->health = pev->max_health; +// pev->takedamage = DAMAGE_YES; +// pev->solid = SOLID_BBOX; +// } + SetThink( NULL ); +} + +//===================================== +//trigger_once + +class CTriggerOnce : public CTriggerMultiple +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_once, CTriggerOnce ); +void CTriggerOnce::Spawn( void ) +{ + m_flWait = -1; + + CTriggerMultiple :: Spawn(); +} + + +//=========================================================== +//LRC - trigger_inout, a trigger which fires _only_ when +// the player enters or leaves it. +// If there's more than one entity it can trigger off, then +// it will trigger for each one that enters and leaves. +//=========================================================== +class CTriggerInOut; + +class CInOutRegister : public CPointEntity +{ +public: + // returns true if found in the list + BOOL IsRegistered ( CBaseEntity *pValue ); + // remove all invalid entries from the list, trigger their targets as appropriate + // returns the new list + CInOutRegister *Prune( void ); + // adds a new entry to the list + CInOutRegister *Add( CBaseEntity *pValue ); + BOOL IsEmpty( void ) { return m_pNext?FALSE:TRUE; }; + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + CTriggerInOut *m_pField; + CInOutRegister *m_pNext; + EHANDLE m_hValue; +}; + +class CTriggerInOut : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT Touch( CBaseEntity *pOther ); + void EXPORT Think( void ); + virtual void FireOnEntry( CBaseEntity *pOther ); + virtual void FireOnLeaving( CBaseEntity *pOther ); + + void KeyValue( KeyValueData *pkvd ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + STATE GetState() { return m_pRegister->IsEmpty()?STATE_OFF:STATE_ON; } + + string_t m_iszAltTarget; + string_t m_iszBothTarget; + CInOutRegister *m_pRegister; +}; + + +// CInOutRegister method bodies: + +TYPEDESCRIPTION CInOutRegister::m_SaveData[] = +{ + DEFINE_FIELD( CInOutRegister, m_pField, FIELD_CLASSPTR ), + DEFINE_FIELD( CInOutRegister, m_pNext, FIELD_CLASSPTR ), + DEFINE_FIELD( CInOutRegister, m_hValue, FIELD_EHANDLE ), +}; + +IMPLEMENT_SAVERESTORE(CInOutRegister,CPointEntity); +LINK_ENTITY_TO_CLASS( inout_register, CInOutRegister ); + +BOOL CInOutRegister::IsRegistered ( CBaseEntity *pValue ) +{ + if (m_hValue == pValue) + return TRUE; + else if (m_pNext) + return m_pNext->IsRegistered( pValue ); + else + return FALSE; +} + +CInOutRegister *CInOutRegister::Add( CBaseEntity *pValue ) +{ + if (m_hValue == pValue) + { + // it's already in the list, don't need to do anything + return this; + } + else if (m_pNext) + { + // keep looking + m_pNext = m_pNext->Add( pValue ); + return this; + } + else + { + // reached the end of the list; add the new entry, and trigger + CInOutRegister *pResult = GetClassPtr( (CInOutRegister*)NULL ); + pResult->m_hValue = pValue; + pResult->m_pNext = this; + pResult->m_pField = m_pField; + pResult->pev->classname = MAKE_STRING("inout_register"); + +// ALERT(at_console, "adding; max %.2f %.2f %.2f, min %.2f %.2f %.2f is inside max %.2f %.2f %.2f, min %.2f %.2f %.2f\n", pResult->m_hValue->pev->absmax.x, pResult->m_hValue->pev->absmax.y, pResult->m_hValue->pev->absmax.z, pResult->m_hValue->pev->absmin.x, pResult->m_hValue->pev->absmin.y, pResult->m_hValue->pev->absmin.z, pResult->m_pField->pev->absmax.x, pResult->m_pField->pev->absmax.y, pResult->m_pField->pev->absmax.z, pResult->m_pField->pev->absmin.x, pResult->m_pField->pev->absmin.y, pResult->m_pField->pev->absmin.z); //LRCT + + m_pField->FireOnEntry( pValue ); + return pResult; + } +} + +CInOutRegister *CInOutRegister::Prune( void ) +{ + if ( m_hValue ) + { + ASSERTSZ(m_pNext != NULL, "invalid InOut registry terminator\n"); + if ( m_pField->Intersects(m_hValue) ) + { + // this entity is still inside the field, do nothing + m_pNext = m_pNext->Prune(); + return this; + } + else + { +// ALERT(at_console, "removing; max %.2f %.2f %.2f, min %.2f %.2f %.2f is outside max %.2f %.2f %.2f, min %.2f %.2f %.2f\n", m_hValue->pev->absmax.x, m_hValue->pev->absmax.y, m_hValue->pev->absmax.z, m_hValue->pev->absmin.x, m_hValue->pev->absmin.y, m_hValue->pev->absmin.z, m_pField->pev->absmax.x, m_pField->pev->absmax.y, m_pField->pev->absmax.z, m_pField->pev->absmin.x, m_pField->pev->absmin.y, m_pField->pev->absmin.z); //LRCT + + // this entity has just left the field, trigger + m_pField->FireOnLeaving( m_hValue ); + SetThink(&CInOutRegister:: SUB_Remove ); + SetNextThink( 0.1 ); + return m_pNext->Prune(); + } + } + else + { // this register has a missing or null value + if (m_pNext) + { + // this is an invalid list entry, remove it + SetThink(&CInOutRegister:: SUB_Remove ); + SetNextThink( 0.1 ); + return m_pNext->Prune(); + } + else + { + // this is the list terminator, leave it. + return this; + } + } +} + + + +// CTriggerInOut method bodies: + +LINK_ENTITY_TO_CLASS( trigger_inout, CTriggerInOut ); + +TYPEDESCRIPTION CTriggerInOut::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerInOut, m_iszAltTarget, FIELD_STRING ), + DEFINE_FIELD( CTriggerInOut, m_iszBothTarget, FIELD_STRING ), + DEFINE_FIELD( CTriggerInOut, m_pRegister, FIELD_CLASSPTR ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerInOut,CBaseTrigger); + +void CTriggerInOut::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iszAltTarget")) + { + m_iszAltTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszBothTarget")) + { + m_iszBothTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseTrigger::KeyValue( pkvd ); +} + +void CTriggerInOut :: Spawn( void ) +{ + InitTrigger(); + // create a null-terminator for the registry + m_pRegister = GetClassPtr( (CInOutRegister*)NULL ); + m_pRegister->m_hValue = NULL; + m_pRegister->m_pNext = NULL; + m_pRegister->m_pField = this; + m_pRegister->pev->classname = MAKE_STRING("inout_register"); +} + +void CTriggerInOut :: Touch( CBaseEntity *pOther ) +{ + if (!CanTouch(pOther->pev)) return; + + m_pRegister = m_pRegister->Add(pOther); + + if (m_fNextThink <= 0 && !m_pRegister->IsEmpty()) + SetNextThink( 0.1 ); +} + +void CTriggerInOut :: Think( void ) +{ + // Prune handles all Intersects tests and fires targets as appropriate + m_pRegister = m_pRegister->Prune(); + + if (m_pRegister->IsEmpty()) + DontThink(); + else + SetNextThink( 0.1 ); +} + +void CTriggerInOut :: FireOnEntry( CBaseEntity *pOther ) +{ + if (UTIL_IsMasterTriggered(m_sMaster,pOther)) + { + FireTargets(STRING(m_iszBothTarget), pOther, this, USE_ON, 0); + FireTargets(STRING(pev->target), pOther, this, USE_TOGGLE, 0); + } +} + +void CTriggerInOut :: FireOnLeaving( CBaseEntity *pEnt ) +{ + if ( UTIL_IsMasterTriggered(m_sMaster, pEnt) ) + { + FireTargets(STRING(m_iszBothTarget), pEnt, this, USE_OFF, 0); + FireTargets(STRING(m_iszAltTarget), pEnt, this, USE_TOGGLE, 0); + } +} + +class CTriggerNoSave : public CTriggerInOut +{ +public: + virtual void FireOnEntry( CBaseEntity *pOther ); + virtual void FireOnLeaving( CBaseEntity *pOther ); +}; + +LINK_ENTITY_TO_CLASS( trigger_nosave, CTriggerNoSave ); + +void CTriggerNoSave :: FireOnEntry( CBaseEntity *pOther ) +{ + if (UTIL_IsMasterTriggered(m_sMaster,pOther)) + { + g_fAllowSaves = FALSE; + } +} + +void CTriggerNoSave :: FireOnLeaving( CBaseEntity *pEnt ) +{ + if ( UTIL_IsMasterTriggered(m_sMaster, pEnt) ) + { + g_fAllowSaves = TRUE; + } +} + +// ============================== +// trigger_counter + +//After the counter has been triggered "cTriggersLeft" +//times (default 2), it will fire all of it's targets and remove itself. +class CTriggerCounter : public CTriggerMultiple +{ +public: + void Spawn( void ); + void EXPORT CounterUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); +}; +LINK_ENTITY_TO_CLASS( trigger_counter, CTriggerCounter ); + +void CTriggerCounter :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "count")) + { + m_cTriggersLeft = (int) atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CTriggerMultiple::KeyValue( pkvd ); +} + +void CTriggerCounter :: Spawn( void ) +{ + // By making the flWait be -1, this counter-trigger will disappear after it's activated + // (but of course it needs cTriggersLeft "uses" before that happens). + m_flWait = -1; + + if (m_cTriggersLeft == 0) + m_cTriggersLeft = 2; + SetUse(&CTriggerCounter :: CounterUse ); +} + +void CTriggerCounter::CounterUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_cTriggersLeft--; + m_hActivator = pActivator; + + if (m_cTriggersLeft < 0) + return; + + BOOL fTellActivator = + (FClassnameIs(m_hActivator->pev, "player") && + !FBitSet(pev->spawnflags, SPAWNFLAG_NOMESSAGE)); + if (m_cTriggersLeft != 0) + { + if (fTellActivator) + { + // UNDONE: I don't think we want these Quakesque messages + switch (m_cTriggersLeft) + { + case 1: ALERT(at_debug, "Only 1 more to go..."); break; + case 2: ALERT(at_debug, "Only 2 more to go..."); break; + case 3: ALERT(at_debug, "Only 3 more to go..."); break; + default: ALERT(at_debug, "There are more to go..."); break; + } + } + return; + } + + // !!!UNDONE: I don't think we want these Quakesque messages + if (fTellActivator) + ALERT(at_debug, "Sequence completed!"); + + ActivateMultiTrigger( m_hActivator ); +} + +// ====================== TRIGGER_CHANGELEVEL ================================ + +class CTriggerVolume : public CPointEntity // Derive from point entity so this doesn't move across levels +{ +public: + void Spawn( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_transition, CTriggerVolume ); + +// Define space that travels across a level transition +void CTriggerVolume :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + pev->model = NULL; + pev->modelindex = 0; +} + + +// Fires a target after level transition and then dies +class CFireAndDie : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void Think( void ); + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() | FCAP_FORCE_TRANSITION; } // Always go across transitions +}; +LINK_ENTITY_TO_CLASS( fireanddie, CFireAndDie ); + +void CFireAndDie::Spawn( void ) +{ + pev->classname = MAKE_STRING("fireanddie"); + // Don't call Precache() - it should be called on restore +} + + +void CFireAndDie::Precache( void ) +{ + // This gets called on restore + SetNextThink( m_flDelay ); +} + + +void CFireAndDie::Think( void ) +{ + SUB_UseTargets( this, USE_TOGGLE, 0 ); + UTIL_Remove( this ); +} + + +#define SF_CHANGELEVEL_USEONLY 0x0002 +class CChangeLevel : public CBaseTrigger +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT UseChangeLevel ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT TriggerChangeLevel( void ); + void EXPORT ExecuteChangeLevel( void ); + void EXPORT TouchChangeLevel( CBaseEntity *pOther ); + void ChangeLevelNow( CBaseEntity *pActivator ); + + static edict_t *FindLandmark( const char *pLandmarkName ); + static int ChangeList( LEVELLIST *pLevelList, int maxList ); + static int AddTransitionToList( LEVELLIST *pLevelList, int listCount, const char *pMapName, const char *pLandmarkName, edict_t *pentLandmark ); + static int InTransitionVolume( CBaseEntity *pEntity, char *pVolumeName ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + char m_szMapName[cchMapNameMost]; // trigger_changelevel only: next map + char m_szLandmarkName[cchMapNameMost]; // trigger_changelevel only: landmark on next map + int m_changeTarget; + float m_changeTargetDelay; +}; +LINK_ENTITY_TO_CLASS( trigger_changelevel, CChangeLevel ); + +// Global Savedata for changelevel trigger +TYPEDESCRIPTION CChangeLevel::m_SaveData[] = +{ + DEFINE_ARRAY( CChangeLevel, m_szMapName, FIELD_CHARACTER, cchMapNameMost ), + DEFINE_ARRAY( CChangeLevel, m_szLandmarkName, FIELD_CHARACTER, cchMapNameMost ), + DEFINE_FIELD( CChangeLevel, m_changeTarget, FIELD_STRING ), + DEFINE_FIELD( CChangeLevel, m_changeTargetDelay, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE(CChangeLevel,CBaseTrigger); + +// +// Cache user-entity-field values until spawn is called. +// + +void CChangeLevel :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "map")) + { + if (strlen(pkvd->szValue) >= cchMapNameMost) + ALERT( at_error, "Map name '%s' too long (32 chars)\n", pkvd->szValue ); + + strcpy(m_szMapName, pkvd->szValue); + + //LRC -- don't allow changelevels to contain capital letters; it causes problems +// ALERT(at_console, "MapName %s ", m_szMapName); + for (int i = 0; m_szMapName[i]; i++) { m_szMapName[i] = tolower(m_szMapName[i]); } +// ALERT(at_console, "changed to %s\n", m_szMapName); + + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "landmark")) + { + if (strlen(pkvd->szValue) >= cchMapNameMost) + ALERT( at_error, "Landmark name '%s' too long (32 chars)\n", pkvd->szValue ); + strcpy(m_szLandmarkName, pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "changetarget")) + { + m_changeTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "changedelay")) + { + m_changeTargetDelay = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseTrigger::KeyValue( pkvd ); +} + + +/*QUAKED trigger_changelevel (0.5 0.5 0.5) ? NO_INTERMISSION +When the player touches this, he gets sent to the map listed in the "map" variable. Unless the NO_INTERMISSION flag is set, the view will go to the info_intermission spot and display stats. +*/ + +void CChangeLevel :: Spawn( void ) +{ + if ( FStrEq( m_szMapName, "" ) ) + ALERT( at_debug, "a trigger_changelevel doesn't have a map" ); + + if ( FStrEq( m_szLandmarkName, "" ) ) + ALERT( at_debug, "trigger_changelevel to %s doesn't have a landmark", m_szMapName ); + + if (!FStringNull ( pev->targetname ) ) + { + SetUse(&CChangeLevel :: UseChangeLevel ); + } + InitTrigger(); + if ( !(pev->spawnflags & SF_CHANGELEVEL_USEONLY) ) + SetTouch(&CChangeLevel :: TouchChangeLevel ); +// ALERT( at_console, "TRANSITION: %s (%s)\n", m_szMapName, m_szLandmarkName ); +} + + +void CChangeLevel :: ExecuteChangeLevel( void ) +{ + MESSAGE_BEGIN( MSG_ALL, SVC_CDTRACK ); + WRITE_BYTE( 3 ); + WRITE_BYTE( 3 ); + MESSAGE_END(); + + MESSAGE_BEGIN(MSG_ALL, SVC_INTERMISSION); + MESSAGE_END(); +} + + +FILE_GLOBAL char st_szNextMap[cchMapNameMost]; +FILE_GLOBAL char st_szNextSpot[cchMapNameMost]; + +edict_t *CChangeLevel :: FindLandmark( const char *pLandmarkName ) +{ + CBaseEntity *pLandmark; + + pLandmark = UTIL_FindEntityByTargetname( NULL, pLandmarkName ); + while ( pLandmark ) + { + // Found the landmark + if ( FClassnameIs( pLandmark->pev, "info_landmark" ) ) + return ENT(pLandmark->pev); + else + pLandmark = UTIL_FindEntityByTargetname( pLandmark, pLandmarkName ); + } + ALERT( at_error, "Can't find landmark %s\n", pLandmarkName ); + return NULL; +} + + +//========================================================= +// CChangeLevel :: Use - allows level transitions to be +// triggered by buttons, etc. +// +//========================================================= +void CChangeLevel :: UseChangeLevel ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + ChangeLevelNow( pActivator ); +} + +void CChangeLevel :: ChangeLevelNow( CBaseEntity *pActivator ) +{ + edict_t *pentLandmark; + LEVELLIST levels[16]; + + ASSERT(!FStrEq(m_szMapName, "")); + + // Don't work in deathmatch + if ( g_pGameRules->IsDeathmatch() ) + return; + + // Some people are firing these multiple times in a frame, disable + if ( gpGlobals->time == pev->dmgtime ) + return; + + pev->dmgtime = gpGlobals->time; + + + CBaseEntity *pPlayer = CBaseEntity::Instance( g_engfuncs.pfnPEntityOfEntIndex( 1 ) ); + if ( !InTransitionVolume( pPlayer, m_szLandmarkName ) ) + { + ALERT( at_aiconsole, "Player isn't in the transition volume %s, aborting\n", m_szLandmarkName ); + return; + } + + // Create an entity to fire the changetarget + if ( m_changeTarget ) + { + CFireAndDie *pFireAndDie = GetClassPtr( (CFireAndDie *)NULL ); + if ( pFireAndDie ) + { + // Set target and delay + pFireAndDie->pev->target = m_changeTarget; + pFireAndDie->m_flDelay = m_changeTargetDelay; + pFireAndDie->pev->origin = pPlayer->pev->origin; + // Call spawn + DispatchSpawn( pFireAndDie->edict() ); + } + } + // This object will get removed in the call to CHANGE_LEVEL, copy the params into "safe" memory + strcpy(st_szNextMap, m_szMapName); + + m_hActivator = pActivator; + SUB_UseTargets( pActivator, USE_TOGGLE, 0 ); + st_szNextSpot[0] = 0; // Init landmark to NULL + + // look for a landmark entity + pentLandmark = FindLandmark( m_szLandmarkName ); + if ( !FNullEnt( pentLandmark ) ) + { + strcpy(st_szNextSpot, m_szLandmarkName); + gpGlobals->vecLandmarkOffset = VARS(pentLandmark)->origin; + } +// ALERT( at_console, "Level touches %d levels\n", ChangeList( levels, 16 ) ); + ALERT( at_debug, "CHANGE LEVEL: %s %s\n", st_szNextMap, st_szNextSpot ); + CHANGE_LEVEL( st_szNextMap, st_szNextSpot ); +} + +// +// GLOBALS ASSUMED SET: st_szNextMap +// +void CChangeLevel :: TouchChangeLevel( CBaseEntity *pOther ) +{ + if (!FClassnameIs(pOther->pev, "player")) + return; + + ChangeLevelNow( pOther ); +} + + +// Add a transition to the list, but ignore duplicates +// (a designer may have placed multiple trigger_changelevels with the same landmark) +int CChangeLevel::AddTransitionToList( LEVELLIST *pLevelList, int listCount, const char *pMapName, const char *pLandmarkName, edict_t *pentLandmark ) +{ + int i; + + if ( !pLevelList || !pMapName || !pLandmarkName || !pentLandmark ) + return 0; + + for ( i = 0; i < listCount; i++ ) + { + if ( pLevelList[i].pentLandmark == pentLandmark && strcmp( pLevelList[i].mapName, pMapName ) == 0 ) + return 0; + } + strcpy( pLevelList[listCount].mapName, pMapName ); + strcpy( pLevelList[listCount].landmarkName, pLandmarkName ); + pLevelList[listCount].pentLandmark = pentLandmark; + pLevelList[listCount].vecLandmarkOrigin = VARS(pentLandmark)->origin; + + return 1; +} + +int BuildChangeList( LEVELLIST *pLevelList, int maxList ) +{ + return CChangeLevel::ChangeList( pLevelList, maxList ); +} + + +int CChangeLevel::InTransitionVolume( CBaseEntity *pEntity, char *pVolumeName ) +{ + CBaseEntity *pVolume; + + + if ( pEntity->ObjectCaps() & FCAP_FORCE_TRANSITION ) + return 1; + + // If you're following another entity, follow it through the transition (weapons follow the player) + if ( pEntity->pev->movetype == MOVETYPE_FOLLOW ) + { + if ( pEntity->pev->aiment != NULL ) + pEntity = CBaseEntity::Instance( pEntity->pev->aiment ); + } + + int inVolume = 1; // Unless we find a trigger_transition, everything is in the volume + + pVolume = UTIL_FindEntityByTargetname( NULL, pVolumeName ); + while ( pVolume ) + { + if ( FClassnameIs( pVolume->pev, "trigger_transition" ) ) + { + if ( pVolume->Intersects( pEntity ) ) // It touches one, it's in the volume + return 1; + else + inVolume = 0; // Found a trigger_transition, but I don't intersect it -- if I don't find another, don't go! + } + pVolume = UTIL_FindEntityByTargetname( pVolume, pVolumeName ); + } + + return inVolume; +} + + +// We can only ever move 512 entities across a transition +#define MAX_ENTITY 512 + +// This has grown into a complicated beast +// Can we make this more elegant? +// This builds the list of all transitions on this level and which entities are in their PVS's and +// can / should be moved across. +int CChangeLevel::ChangeList( LEVELLIST *pLevelList, int maxList ) +{ + edict_t *pentLandmark; + int i, count; + + count = 0; + + // Find all of the possible level changes on this BSP + CBaseEntity *pChangelevel = UTIL_FindEntityByClassname( NULL, "trigger_changelevel" ); + + if ( !pChangelevel ) + return NULL; + + while ( pChangelevel ) + { + CChangeLevel *pTrigger; + + pTrigger = GetClassPtr((CChangeLevel *)pChangelevel->pev); + if ( pTrigger ) + { + // Find the corresponding landmark + pentLandmark = FindLandmark( pTrigger->m_szLandmarkName ); + if ( pentLandmark ) + { + // Build a list of unique transitions + if ( AddTransitionToList( pLevelList, count, pTrigger->m_szMapName, pTrigger->m_szLandmarkName, pentLandmark ) ) + { + count++; + if ( count >= maxList ) // FULL!! + break; + } + } + } + pChangelevel = UTIL_FindEntityByClassname( pChangelevel, "trigger_changelevel" ); + } + + if ( gpGlobals->pSaveData && ((SAVERESTOREDATA *)gpGlobals->pSaveData)->pTable ) + { + CSave saveHelper( (SAVERESTOREDATA *)gpGlobals->pSaveData ); + + for ( i = 0; i < count; i++ ) + { + int j, entityCount = 0; + CBaseEntity *pEntList[ MAX_ENTITY ]; + int entityFlags[ MAX_ENTITY ]; + + // Follow the linked list of entities in the PVS of the transition landmark + edict_t *pent = UTIL_EntitiesInPVS( pLevelList[i].pentLandmark ); + + // Build a list of valid entities in this linked list (we're going to use pent->v.chain again) + while ( !FNullEnt( pent ) ) + { + CBaseEntity *pEntity = CBaseEntity::Instance(pent); + if ( pEntity ) + { +// ALERT( at_console, "Trying %s\n", STRING(pEntity->pev->classname) ); + int caps = pEntity->ObjectCaps(); + if ( !(caps & FCAP_DONT_SAVE) ) + { + int flags = 0; + + // If this entity can be moved or is global, mark it + if ( caps & FCAP_ACROSS_TRANSITION ) + flags |= FENTTABLE_MOVEABLE; + if ( pEntity->pev->globalname && !pEntity->IsDormant() ) + flags |= FENTTABLE_GLOBAL; + if ( flags ) + { + pEntList[ entityCount ] = pEntity; + entityFlags[ entityCount ] = flags; + entityCount++; + if ( entityCount > MAX_ENTITY ) + ALERT( at_error, "Too many entities across a transition!" ); + } +// else +// ALERT( at_console, "Failed %s\n", STRING(pEntity->pev->classname) ); + } +// else +// ALERT( at_console, "DON'T SAVE %s\n", STRING(pEntity->pev->classname) ); + } + pent = pent->v.chain; + } + + for ( j = 0; j < entityCount; j++ ) + { + // Check to make sure the entity isn't screened out by a trigger_transition + if ( entityFlags[j] && InTransitionVolume( pEntList[j], pLevelList[i].landmarkName ) ) + { + // Mark entity table with 1<pev->classname) ); + + } + } + } + + return count; +} + +/* +go to the next level for deathmatch +only called if a time or frag limit has expired +*/ +void NextLevel( void ) +{ + CBaseEntity* pEnt; + CChangeLevel *pChange; + + // find a trigger_changelevel + pEnt = UTIL_FindEntityByClassname(NULL, "trigger_changelevel"); + + // go back to start if no trigger_changelevel + if ( !pEnt ) + { + gpGlobals->mapname = MAKE_STRING("start"); + pChange = GetClassPtr( (CChangeLevel *)NULL ); + strcpy(pChange->m_szMapName, "start"); + } + else + pChange = GetClassPtr( (CChangeLevel *)pEnt->pev ); + + strcpy(st_szNextMap, pChange->m_szMapName); + g_fGameOver = TRUE; + + pChange->SetNextThink( 0 ); + if (pChange->m_fNextThink) + { + pChange->SetThink(& CChangeLevel::ExecuteChangeLevel ); + pChange->SetNextThink( 0.1 ); + } +} + + +// ============================== LADDER ======================================= + +#define SF_LADDER_VISIBLE 1 + +class CLadder : public CBaseTrigger +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Precache( void ); +}; +LINK_ENTITY_TO_CLASS( func_ladder, CLadder ); + + +void CLadder :: KeyValue( KeyValueData *pkvd ) +{ + CBaseTrigger::KeyValue( pkvd ); +} + + +//========================================================= +// func_ladder - makes an area vertically negotiable +//========================================================= +void CLadder :: Precache( void ) +{ + // Do all of this in here because we need to 'convert' old saved games + pev->solid = SOLID_NOT; + pev->skin = CONTENTS_LADDER; + if ( CVAR_GET_FLOAT("showtriggers") == 0 && !(pev->spawnflags & SF_LADDER_VISIBLE)) + { + pev->effects |= EF_NODRAW; + //LRC- NODRAW is a better-performance way to stop things being drawn. + // (unless... would it prevent client-side movement algorithms from working?) +// pev->rendermode = kRenderTransTexture; +// pev->renderamt = 0; + } + else + pev->effects &= ~EF_NODRAW; +} + + +void CLadder :: Spawn( void ) +{ + Precache(); + + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + pev->movetype = MOVETYPE_PUSH; +} + + +// ========================== A TRIGGER THAT PUSHES YOU =============================== + +class CTriggerPush : public CBaseTrigger +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Touch( CBaseEntity *pOther ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_iszPushVel; + int m_iszPushSpeed; +}; +LINK_ENTITY_TO_CLASS( trigger_push, CTriggerPush ); + +TYPEDESCRIPTION CTriggerPush::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerPush, m_iszPushVel, FIELD_STRING ), + DEFINE_FIELD( CTriggerPush, m_iszPushSpeed, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerPush,CBaseTrigger); + +void CTriggerPush :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iszPushSpeed")) + { + m_iszPushSpeed = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszPushVel")) + { + m_iszPushVel = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseTrigger::KeyValue( pkvd ); +} + + +/*QUAKED trigger_push (.5 .5 .5) ? TRIG_PUSH_ONCE +Pushes the player +*/ + +void CTriggerPush :: Spawn( ) +{ + if ( pev->angles == g_vecZero ) + pev->angles.y = 360; + InitTrigger(); + + if ( FBitSet (pev->spawnflags, SF_TRIGGER_PUSH_START_OFF) )// if flagged to Start Turned Off, make trigger nonsolid. + pev->solid = SOLID_NOT; + + SetUse(&CTriggerPush :: ToggleUse ); + + UTIL_SetOrigin( this, pev->origin ); // Link into the list +} + + +void CTriggerPush :: Touch( CBaseEntity *pOther ) +{ + entvars_t* pevToucher = pOther->pev; + + // UNDONE: Is there a better way than health to detect things that have physics? (clients/monsters) + switch( pevToucher->movetype ) + { + case MOVETYPE_NONE: + case MOVETYPE_PUSH: + case MOVETYPE_NOCLIP: + case MOVETYPE_FOLLOW: + return; + } + + Vector vecPush; + if (!FStringNull(m_iszPushVel)) + vecPush = CalcLocus_Velocity( this, pOther, STRING(m_iszPushVel) ); + else + vecPush = pev->movedir; + + if (!FStringNull(m_iszPushSpeed)) + vecPush = vecPush * CalcLocus_Ratio( pOther, STRING(m_iszPushSpeed) ); + + if (pev->speed) + vecPush = vecPush * pev->speed; + else + vecPush = vecPush * 100; + + if ( pevToucher->solid != SOLID_NOT && pevToucher->solid != SOLID_BSP ) + { + // Instant trigger, just transfer velocity and remove + if (FBitSet(pev->spawnflags, SF_TRIG_PUSH_ONCE)) + { + pevToucher->velocity = pevToucher->velocity + vecPush; + if ( pevToucher->velocity.z > 0 ) + pevToucher->flags &= ~FL_ONGROUND; + UTIL_Remove( this ); + } + else + { // Push field, transfer to base velocity + if ( pevToucher->flags & FL_BASEVELOCITY ) + vecPush = vecPush + pevToucher->basevelocity; + + pevToucher->basevelocity = vecPush; + + pevToucher->flags |= FL_BASEVELOCITY; +// ALERT( at_console, "Vel %f, base %f\n", pevToucher->velocity.z, pevToucher->basevelocity.z ); + } + } +} + + +//=========================================================== +//LRC- trigger_bounce +//=========================================================== +#define SF_BOUNCE_CUTOFF 16 + +class CTriggerBounce : public CBaseTrigger +{ +public: + void Spawn( void ); + void Touch( CBaseEntity *pOther ); +}; + +LINK_ENTITY_TO_CLASS( trigger_bounce, CTriggerBounce ); + + +void CTriggerBounce :: Spawn( void ) +{ + SetMovedir(pev); + InitTrigger(); +} + +void CTriggerBounce :: Touch( CBaseEntity *pOther ) +{ + if (!UTIL_IsMasterTriggered(m_sMaster, pOther)) + return; + if (!CanTouch(pOther->pev)) + return; + + float dot = DotProduct(pev->movedir, pOther->pev->velocity); + if (dot < -pev->armorvalue) + { + if (pev->spawnflags & SF_BOUNCE_CUTOFF) + pOther->pev->velocity = pOther->pev->velocity - (dot + pev->frags*(dot+pev->armorvalue))*pev->movedir; + else + pOther->pev->velocity = pOther->pev->velocity - (dot + pev->frags*dot)*pev->movedir; + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); + } +} + + +//=========================================================== +//LRC- trigger_onsight +//=========================================================== +#define SF_ONSIGHT_NOLOS 0x00001 +#define SF_ONSIGHT_NOGLASS 0x00002 +#define SF_ONSIGHT_ACTIVE 0x08000 +#define SF_ONSIGHT_DEMAND 0x10000 + +class CTriggerOnSight : public CBaseDelay +{ +public: + void Spawn( void ); + void Think( void ); + BOOL VisionCheck( void ); + BOOL CanSee(CBaseEntity *pLooker, CBaseEntity *pSeen); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + STATE GetState(); +}; + +LINK_ENTITY_TO_CLASS( trigger_onsight, CTriggerOnSight ); + +void CTriggerOnSight :: Spawn( void ) +{ + if (pev->target || pev->noise) + // if we're going to have to trigger stuff, start thinking + SetNextThink( 1 ); + else + // otherwise, just check whenever someone asks about our state. + pev->spawnflags |= SF_ONSIGHT_DEMAND; + + if (pev->max_health > 0) + { + pev->health = cos(pev->max_health/2 * M_PI/180.0); +// ALERT(at_debug, "Cosine is %f\n", pev->health); + } +} + +STATE CTriggerOnSight :: GetState( void ) +{ + if (pev->spawnflags & SF_ONSIGHT_DEMAND) + return VisionCheck()?STATE_ON:STATE_OFF; + else + return (pev->spawnflags & SF_ONSIGHT_ACTIVE)?STATE_ON:STATE_OFF; +} + +void CTriggerOnSight :: Think( void ) +{ + // is this a sensible rate? + SetNextThink( 0.1 ); + +// if (!UTIL_IsMasterTriggered(m_sMaster, NULL)) +// { +// pev->spawnflags &= ~SF_ONSIGHT_ACTIVE; +// return; +// } + + if (VisionCheck()) + { + if (!FBitSet(pev->spawnflags, SF_ONSIGHT_ACTIVE)) + { + FireTargets(STRING(pev->target), this, this, USE_TOGGLE, 0); + FireTargets(STRING(pev->noise1), this, this, USE_ON, 0); + pev->spawnflags |= SF_ONSIGHT_ACTIVE; + } + } + else + { + if (pev->spawnflags & SF_ONSIGHT_ACTIVE) + { + FireTargets(STRING(pev->noise), this, this, USE_TOGGLE, 0); + FireTargets(STRING(pev->noise1), this, this, USE_OFF, 0); + pev->spawnflags &= ~SF_ONSIGHT_ACTIVE; + } + } +} + +BOOL CTriggerOnSight :: VisionCheck( void ) +{ + CBaseEntity *pLooker; + if (pev->netname) + { + pLooker = UTIL_FindEntityByTargetname(NULL, STRING(pev->netname)); + if (!pLooker) + return FALSE; // if we can't find the eye entity, give up + } + else + { + pLooker = UTIL_FindEntityByClassname(NULL, "player"); + if (!pLooker) + { + ALERT(at_error, "trigger_onsight can't find player!?\n"); + return FALSE; + } + } + + CBaseEntity *pSeen; + if (pev->message) + pSeen = UTIL_FindEntityByTargetname(NULL, STRING(pev->message)); + else + return CanSee(pLooker, this); + + if (!pSeen) + { + // must be a classname. + pSeen = UTIL_FindEntityByClassname(pSeen, STRING(pev->message)); + while (pSeen != NULL) + { + if (CanSee(pLooker, pSeen)) + return TRUE; + pSeen = UTIL_FindEntityByClassname(pSeen, STRING(pev->message)); + } + return FALSE; + } + else + { + while (pSeen != NULL) + { + if (CanSee(pLooker, pSeen)) + return TRUE; + pSeen = UTIL_FindEntityByTargetname(pSeen, STRING(pev->message)); + } + return FALSE; + } +} + +// by the criteria we're using, can the Looker see the Seen entity? +BOOL CTriggerOnSight :: CanSee(CBaseEntity *pLooker, CBaseEntity *pSeen) +{ + // out of range? + if (pev->frags && (pLooker->pev->origin - pSeen->pev->origin).Length() > pev->frags) + return FALSE; + + // check FOV if appropriate + if (pev->max_health < 360) + { + // copied from CBaseMonster's FInViewCone function + Vector2D vec2LOS; + float flDot; + float flComp = pev->health; + UTIL_MakeVectors ( pLooker->pev->angles ); + vec2LOS = ( pSeen->pev->origin - pLooker->pev->origin ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + flDot = DotProduct (vec2LOS , gpGlobals->v_forward.Make2D() ); + +// ALERT(at_debug, "flDot is %f\n", flDot); + + if ( pev->max_health == -1 ) + { + CBaseMonster *pMonst = pLooker->MyMonsterPointer(); + if (pMonst) + flComp = pMonst->m_flFieldOfView; + else + return FALSE; // not a monster, can't use M-M-M-MonsterVision + } + + // outside field of view + if (flDot <= flComp) + return FALSE; + } + + // check LOS if appropriate + if (!FBitSet(pev->spawnflags, SF_ONSIGHT_NOLOS)) + { + TraceResult tr; + if (SF_ONSIGHT_NOGLASS) + UTIL_TraceLine( pLooker->EyePosition(), pSeen->pev->origin, ignore_monsters, ignore_glass, pLooker->edict(), &tr ); + else + UTIL_TraceLine( pLooker->EyePosition(), pSeen->pev->origin, ignore_monsters, dont_ignore_glass, pLooker->edict(), &tr ); + if (tr.flFraction < 1.0 && tr.pHit != pSeen->edict()) + return FALSE; + } + + return TRUE; +} + +//====================================== +// teleport trigger +// +// +class CTriggerTeleport : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT TeleportTouch ( CBaseEntity *pOther ); +}; +LINK_ENTITY_TO_CLASS( trigger_teleport, CTriggerTeleport ); + +void CTriggerTeleport :: Spawn( void ) +{ + InitTrigger(); + + SetTouch(&CTriggerTeleport :: TeleportTouch ); +} + +void CTriggerTeleport :: TeleportTouch( CBaseEntity *pOther ) +{ + entvars_t* pevToucher = pOther->pev; + CBaseEntity *pTarget = NULL; + + // Only teleport monsters or clients + if ( !FBitSet( pevToucher->flags, FL_CLIENT|FL_MONSTER ) ) + return; + + if (!UTIL_IsMasterTriggered(m_sMaster, pOther)) + return; + + if (!CanTouch(pevToucher)) + return; + + pTarget = UTIL_FindEntityByTargetname( pTarget, STRING(pev->target) ); + if ( !pTarget ) + return; + + //LRC - landmark based teleports + CBaseEntity *pLandmark = UTIL_FindEntityByTargetname( NULL, STRING(pev->message) ); + if ( pLandmark ) + { + Vector vecOriginOffs = pTarget->pev->origin - pLandmark->pev->origin; + //ALERT(at_console, "Offs initially: %f %f %f\n", vecOriginOffs.x, vecOriginOffs.y, vecOriginOffs.z); + + // do we need to rotate the entity? + if ( pLandmark->pev->angles != pTarget->pev->angles ) + { + Vector vecVA; + float ydiff = pTarget->pev->angles.y - pLandmark->pev->angles.y; + + // set new angle to face +// ALERT(at_console, "angles = %f %f %f\n", pOther->pev->angles.x, pOther->pev->angles.y, pOther->pev->angles.z); + pOther->pev->angles.y += ydiff; + if (pOther->IsPlayer()) + { +// ALERT(at_console, "v_angle = %f %f %f\n", pOther->pev->v_angle.x, pOther->pev->v_angle.y, pOther->pev->v_angle.z); + pOther->pev->angles.x = pOther->pev->v_angle.x; +// pOther->pev->v_angle.y += ydiff; + pOther->pev->fixangle = TRUE; + } + + // set new velocity + vecVA = UTIL_VecToAngles(pOther->pev->velocity); + vecVA.y += ydiff; + UTIL_MakeVectors(vecVA); + pOther->pev->velocity = gpGlobals->v_forward * pOther->pev->velocity.Length(); + // fix the ugly "angle to vector" behaviour - a legacy from Quake + pOther->pev->velocity.z = -pOther->pev->velocity.z; + + // set new origin + Vector vecPlayerOffs = pOther->pev->origin - pLandmark->pev->origin; + //ALERT(at_console, "PlayerOffs: %f %f %f\n", vecPlayerOffs.x, vecPlayerOffs.y, vecPlayerOffs.z); + vecVA = UTIL_VecToAngles(vecPlayerOffs); + UTIL_MakeVectors(vecVA); + vecVA.y += ydiff; + UTIL_MakeVectors(vecVA); + Vector vecPlayerOffsNew = gpGlobals->v_forward * vecPlayerOffs.Length(); + vecPlayerOffsNew.z = -vecPlayerOffsNew.z; + //ALERT(at_console, "PlayerOffsNew: %f %f %f\n", vecPlayerOffsNew.x, vecPlayerOffsNew.y, vecPlayerOffsNew.z); + + vecOriginOffs = vecOriginOffs + vecPlayerOffsNew - vecPlayerOffs; + //ALERT(at_console, "vecOriginOffs: %f %f %f\n", vecOriginOffs.x, vecOriginOffs.y, vecOriginOffs.z); +// vecOriginOffs.y++; + } + + UTIL_SetOrigin( pOther, pOther->pev->origin + vecOriginOffs ); + } + else + { + Vector tmp = pTarget->pev->origin; + + if ( pOther->IsPlayer() ) + { + tmp.z -= pOther->pev->mins.z;// make origin adjustments in case the teleportee is a player. (origin in center, not at feet) + } + tmp.z++; + UTIL_SetOrigin( pOther, tmp ); + + pOther->pev->angles = pTarget->pev->angles; + pOther->pev->velocity = pOther->pev->basevelocity = g_vecZero; + if ( pOther->IsPlayer() ) + { + pOther->pev->v_angle = pTarget->pev->angles; //LRC + pOther->pev->fixangle = TRUE; + } + } + + pevToucher->flags &= ~FL_ONGROUND; + pevToucher->fixangle = TRUE; + + FireTargets(STRING(pev->noise), pOther, this, USE_TOGGLE, 0); +} + + +LINK_ENTITY_TO_CLASS( info_teleport_destination, CPointEntity ); + + + +class CTriggerSave : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT SaveTouch( CBaseEntity *pOther ); +}; +LINK_ENTITY_TO_CLASS( trigger_autosave, CTriggerSave ); + +void CTriggerSave::Spawn( void ) +{ + if ( g_pGameRules->IsDeathmatch() ) + { + REMOVE_ENTITY( ENT(pev) ); + return; + } + + InitTrigger(); + SetTouch(&CTriggerSave:: SaveTouch ); +} + +void CTriggerSave::SaveTouch( CBaseEntity *pOther ) +{ + if ( !UTIL_IsMasterTriggered( m_sMaster, pOther ) ) + return; + + // Only save on clients + if ( !pOther->IsPlayer() ) + return; + + SetTouch( NULL ); + UTIL_Remove( this ); + SERVER_COMMAND( "autosave\n" ); +} + +#define SF_ENDSECTION_USEONLY 0x0001 + +class CTriggerEndSection : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT EndSectionTouch( CBaseEntity *pOther ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT EndSectionUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; +LINK_ENTITY_TO_CLASS( trigger_endsection, CTriggerEndSection ); + + +void CTriggerEndSection::EndSectionUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Only save on clients + if ( pActivator && !pActivator->IsNetClient() ) + return; + + SetUse( NULL ); + + if ( pev->message ) + { + g_engfuncs.pfnEndSection(STRING(pev->message)); + } + UTIL_Remove( this ); +} + +void CTriggerEndSection::Spawn( void ) +{ + if ( g_pGameRules->IsDeathmatch() ) + { + REMOVE_ENTITY( ENT(pev) ); + return; + } + + InitTrigger(); + + SetUse(&CTriggerEndSection:: EndSectionUse ); + // If it is a "use only" trigger, then don't set the touch function. + if ( ! (pev->spawnflags & SF_ENDSECTION_USEONLY) ) + SetTouch(&CTriggerEndSection:: EndSectionTouch ); +} + +void CTriggerEndSection::EndSectionTouch( CBaseEntity *pOther ) +{ + // Only save on clients + if ( !pOther->IsNetClient() ) + return; + + SetTouch( NULL ); + + if (pev->message) + { + g_engfuncs.pfnEndSection(STRING(pev->message)); + } + UTIL_Remove( this ); +} + +void CTriggerEndSection :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "section")) + { +// m_iszSectionName = ALLOC_STRING( pkvd->szValue ); + // Store this in message so we don't have to write save/restore for this ent + pev->message = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseTrigger::KeyValue( pkvd ); +} + + +class CTriggerGravity : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT GravityTouch( CBaseEntity *pOther ); +}; +LINK_ENTITY_TO_CLASS( trigger_gravity, CTriggerGravity ); + +void CTriggerGravity::Spawn( void ) +{ + InitTrigger(); + SetTouch(&CTriggerGravity:: GravityTouch ); +} + +void CTriggerGravity::GravityTouch( CBaseEntity *pOther ) +{ + // Only save on clients + if ( !pOther->IsPlayer() ) + return; + + pOther->pev->gravity = pev->gravity; +} + + + + +//=========================================================== +//LRC- trigger_startpatrol +//=========================================================== +class CTriggerSetPatrol : public CBaseDelay +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + int m_iszPath; +}; +LINK_ENTITY_TO_CLASS( trigger_startpatrol, CTriggerSetPatrol ); + +TYPEDESCRIPTION CTriggerSetPatrol::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerSetPatrol, m_iszPath, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerSetPatrol,CBaseDelay); + +void CTriggerSetPatrol::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iszPath")) + { + m_iszPath = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + +void CTriggerSetPatrol::Spawn( void ) +{ +} + + +void CTriggerSetPatrol::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBaseEntity *pTarget = UTIL_FindEntityByTargetname( NULL, STRING( pev->target ), pActivator ); + CBaseEntity *pPath = UTIL_FindEntityByTargetname( NULL, STRING( m_iszPath ), pActivator ); + + if (pTarget && pPath) + { + CBaseMonster *pMonster = pTarget->MyMonsterPointer(); + if (pMonster) pMonster->StartPatrol(pPath); + } +} + + +//=========================================================== +//LRC- trigger_motion +//=========================================================== +#define SF_MOTION_DEBUG 1 +class CTriggerMotion : public CPointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void KeyValue( KeyValueData *pkvd ); + + int m_iszPosition; + int m_iPosMode; + int m_iszAngles; + int m_iAngMode; + int m_iszVelocity; + int m_iVelMode; + int m_iszAVelocity; + int m_iAVelMode; +}; +LINK_ENTITY_TO_CLASS( trigger_motion, CTriggerMotion ); + +TYPEDESCRIPTION CTriggerMotion::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerMotion, m_iszPosition, FIELD_STRING ), + DEFINE_FIELD( CTriggerMotion, m_iPosMode, FIELD_INTEGER ), + DEFINE_FIELD( CTriggerMotion, m_iszAngles, FIELD_STRING ), + DEFINE_FIELD( CTriggerMotion, m_iAngMode, FIELD_INTEGER ), + DEFINE_FIELD( CTriggerMotion, m_iszVelocity, FIELD_STRING ), + DEFINE_FIELD( CTriggerMotion, m_iVelMode, FIELD_INTEGER ), + DEFINE_FIELD( CTriggerMotion, m_iszAVelocity, FIELD_STRING ), + DEFINE_FIELD( CTriggerMotion, m_iAVelMode, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerMotion,CPointEntity); + +void CTriggerMotion::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iszPosition")) + { + m_iszPosition = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iPosMode")) + { + m_iPosMode = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszAngles")) + { + m_iszAngles = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iAngMode")) + { + m_iAngMode = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszVelocity")) + { + m_iszVelocity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iVelMode")) + { + m_iVelMode = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszAVelocity")) + { + m_iszAVelocity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iAVelMode")) + { + m_iAVelMode = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +void CTriggerMotion::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBaseEntity *pTarget = UTIL_FindEntityByTargetname( NULL, STRING(pev->target), pActivator ); + if (pTarget == NULL || pActivator == NULL) return; + + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "DEBUG: trigger_motion affects %s \"%s\":\n", STRING(pTarget->pev->classname), STRING(pTarget->pev->targetname)); + + if (m_iszPosition) + { + switch (m_iPosMode) + { + case 0: + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "DEBUG: Set origin from %f %f %f ", pTarget->pev->origin.x, pTarget->pev->origin.y, pTarget->pev->origin.z); + pTarget->pev->origin = CalcLocus_Position( this, pActivator, STRING(m_iszPosition) ); + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "to %f %f %f\n", pTarget->pev->origin.x, pTarget->pev->origin.y, pTarget->pev->origin.z); + pTarget->pev->flags &= ~FL_ONGROUND; + break; + case 1: + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "DEBUG: Set origin from %f %f %f ", pTarget->pev->origin.x, pTarget->pev->origin.y, pTarget->pev->origin.z); + pTarget->pev->origin = pTarget->pev->origin + CalcLocus_Velocity( this, pActivator, STRING(m_iszPosition) ); + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "to %f %f %f\n", pTarget->pev->origin.x, pTarget->pev->origin.y, pTarget->pev->origin.z); + pTarget->pev->flags &= ~FL_ONGROUND; + break; + } + } + + Vector vecTemp; + Vector vecVelAngles; + if (m_iszAngles) + { + switch (m_iAngMode) + { + case 0: + vecTemp = CalcLocus_Velocity( this, pActivator, STRING(m_iszAngles) ); + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "DEBUG: Set angles from %f %f %f ", pTarget->pev->angles.x, pTarget->pev->angles.y, pTarget->pev->angles.z); + pTarget->pev->angles = UTIL_VecToAngles( vecTemp ); + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "to %f %f %f\n", pTarget->pev->angles.x, pTarget->pev->angles.y, pTarget->pev->angles.z); + break; + case 1: + vecTemp = CalcLocus_Velocity( this, pActivator, STRING(m_iszVelocity) ); + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "DEBUG: Rotate angles from %f %f %f ", pTarget->pev->angles.x, pTarget->pev->angles.y, pTarget->pev->angles.z); + pTarget->pev->angles = pTarget->pev->angles + UTIL_VecToAngles( vecTemp ); + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "to %f %f %f\n", pTarget->pev->angles.x, pTarget->pev->angles.y, pTarget->pev->angles.z); + break; + case 2: + UTIL_StringToRandomVector( vecTemp, STRING(m_iszAngles) ); + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "DEBUG: Rotate angles from %f %f %f ", pTarget->pev->angles.x, pTarget->pev->angles.y, pTarget->pev->angles.z); + pTarget->pev->angles = pTarget->pev->angles + vecTemp; + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "to %f %f %f\n", pTarget->pev->angles.x, pTarget->pev->angles.y, pTarget->pev->angles.z); + break; + } + } + + if (m_iszVelocity) + { + switch (m_iVelMode) + { + case 0: + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "DEBUG: Set velocity from %f %f %f ", pTarget->pev->velocity.x, pTarget->pev->velocity.y, pTarget->pev->velocity.z); + pTarget->pev->velocity = CalcLocus_Velocity( this, pActivator, STRING(m_iszVelocity) ); + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "to %f %f %f\n", pTarget->pev->velocity.x, pTarget->pev->velocity.y, pTarget->pev->velocity.z); + break; + case 1: + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "DEBUG: Set velocity from %f %f %f ", pTarget->pev->velocity.x, pTarget->pev->velocity.y, pTarget->pev->velocity.z); + pTarget->pev->velocity = pTarget->pev->velocity + CalcLocus_Velocity( this, pActivator, STRING(m_iszVelocity) ); + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "to %f %f %f\n", pTarget->pev->velocity.x, pTarget->pev->velocity.y, pTarget->pev->velocity.z); + break; + case 2: + vecTemp = CalcLocus_Velocity( this, pActivator, STRING(m_iszVelocity) ); + vecVelAngles = UTIL_VecToAngles( vecTemp ) + UTIL_VecToAngles( pTarget->pev->velocity ); + UTIL_MakeVectors( vecVelAngles ); + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "DEBUG: Rotate velocity from %f %f %f ", pTarget->pev->velocity.x, pTarget->pev->velocity.y, pTarget->pev->velocity.z); + pTarget->pev->velocity = pTarget->pev->velocity.Length() * gpGlobals->v_forward; + pTarget->pev->velocity.z = -pTarget->pev->velocity.z; //vecToAngles reverses the z angle + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "to %f %f %f\n", pTarget->pev->velocity.x, pTarget->pev->velocity.y, pTarget->pev->velocity.z); + break; + case 3: + UTIL_StringToRandomVector( vecTemp, STRING(m_iszVelocity) ); + vecVelAngles = vecTemp + UTIL_VecToAngles( pTarget->pev->velocity ); + UTIL_MakeVectors( vecVelAngles ); + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "DEBUG: Rotate velocity from %f %f %f ", pTarget->pev->velocity.x, pTarget->pev->velocity.y, pTarget->pev->velocity.z); + pTarget->pev->velocity = pTarget->pev->velocity.Length() * gpGlobals->v_forward; + pTarget->pev->velocity.z = -pTarget->pev->velocity.z; //vecToAngles reverses the z angle + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "to %f %f %f\n", pTarget->pev->velocity.x, pTarget->pev->velocity.y, pTarget->pev->velocity.z); + break; + } + } + + if( m_iszAVelocity ) + { + switch (m_iAVelMode) + { + case 0: + UTIL_StringToRandomVector( vecTemp, STRING(m_iszAVelocity) ); + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "DEBUG: Set avelocity from %f %f %f ", pTarget->pev->avelocity.x, pTarget->pev->avelocity.y, pTarget->pev->avelocity.z); + pTarget->pev->avelocity = vecTemp; + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "to %f %f %f\n", pTarget->pev->avelocity.x, pTarget->pev->avelocity.y, pTarget->pev->avelocity.z); + break; + case 1: + UTIL_StringToRandomVector( vecTemp, STRING(m_iszAVelocity) ); + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "DEBUG: Set avelocity from %f %f %f ", pTarget->pev->avelocity.x, pTarget->pev->avelocity.y, pTarget->pev->avelocity.z); + pTarget->pev->avelocity = pTarget->pev->avelocity + vecTemp; + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "to %f %f %f\n", pTarget->pev->avelocity.x, pTarget->pev->avelocity.y, pTarget->pev->avelocity.z); + break; + } + } +} + + +//=========================================================== +//LRC- motion_manager +//=========================================================== +class CMotionThread : public CBaseEntity +{ +public: + void Think( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_iszPosition; + int m_iPosMode; + int m_iszFacing; + int m_iFaceMode; + EHANDLE m_hLocus; + EHANDLE m_hTarget; +}; +LINK_ENTITY_TO_CLASS( motion_thread, CMotionThread ); + +TYPEDESCRIPTION CMotionThread::m_SaveData[] = +{ + DEFINE_FIELD( CMotionThread, m_iszPosition, FIELD_STRING ), + DEFINE_FIELD( CMotionThread, m_iPosMode, FIELD_INTEGER ), + DEFINE_FIELD( CMotionThread, m_iszFacing, FIELD_STRING ), + DEFINE_FIELD( CMotionThread, m_iFaceMode, FIELD_INTEGER ), + DEFINE_FIELD( CMotionThread, m_hLocus, FIELD_EHANDLE ), + DEFINE_FIELD( CMotionThread, m_hTarget, FIELD_EHANDLE ), +};IMPLEMENT_SAVERESTORE(CMotionThread,CBaseEntity); + +void CMotionThread::Think( void ) +{ + if (m_hLocus == NULL || m_hTarget == NULL) + { + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "motion_thread expires\n"); + SetThink(&CMotionThread:: SUB_Remove ); + SetNextThink( 0.1 ); + return; + } + else + { + SetNextThink( 0 ); // think every frame + } + + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "motion_thread affects %s \"%s\":\n", STRING(m_hTarget->pev->classname), STRING(m_hTarget->pev->targetname)); + + Vector vecTemp; + + if (m_iszPosition) + { + switch (m_iPosMode) + { + case 0: // set position + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "DEBUG: Set origin from %f %f %f ", m_hTarget->pev->origin.x, m_hTarget->pev->origin.y, m_hTarget->pev->origin.z); + UTIL_AssignOrigin(m_hTarget, CalcLocus_Position( this, m_hLocus, STRING(m_iszPosition) )); + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "to %f %f %f\n", m_hTarget->pev->origin.x, m_hTarget->pev->origin.y, m_hTarget->pev->origin.z); + m_hTarget->pev->flags &= ~FL_ONGROUND; + break; + case 1: // offset position (= fake velocity) + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "DEBUG: Offset origin from %f %f %f ", m_hTarget->pev->origin.x, m_hTarget->pev->origin.y, m_hTarget->pev->origin.z); + UTIL_AssignOrigin(m_hTarget, m_hTarget->pev->origin + gpGlobals->frametime * CalcLocus_Velocity( this, m_hLocus, STRING(m_iszPosition) )); + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "to %f %f %f\n", m_hTarget->pev->origin.x, m_hTarget->pev->origin.y, m_hTarget->pev->origin.z); + m_hTarget->pev->flags &= ~FL_ONGROUND; + break; + case 2: // set velocity + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "DEBUG: Set velocity from %f %f %f ", m_hTarget->pev->velocity.x, m_hTarget->pev->velocity.y, m_hTarget->pev->velocity.z); + UTIL_SetVelocity(m_hTarget, CalcLocus_Velocity( this, m_hLocus, STRING(m_iszPosition) )); + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "to %f %f %f\n", m_hTarget->pev->velocity.x, m_hTarget->pev->velocity.y, m_hTarget->pev->velocity.z); + break; + case 3: // accelerate + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "DEBUG: Accelerate from %f %f %f ", m_hTarget->pev->velocity.x, m_hTarget->pev->velocity.y, m_hTarget->pev->velocity.z); + UTIL_SetVelocity(m_hTarget, m_hTarget->pev->velocity + gpGlobals->frametime * CalcLocus_Velocity( this, m_hLocus, STRING(m_iszPosition) )); + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "to %f %f %f\n", m_hTarget->pev->velocity.x, m_hTarget->pev->velocity.y, m_hTarget->pev->velocity.z); + break; + case 4: // follow position + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "DEBUG: Set velocity (path) from %f %f %f ", m_hTarget->pev->velocity.x, m_hTarget->pev->velocity.y, m_hTarget->pev->velocity.z); + UTIL_SetVelocity(m_hTarget, CalcLocus_Position( this, m_hLocus, STRING(m_iszPosition) ) - m_hTarget->pev->origin); + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "to %f %f %f\n", m_hTarget->pev->velocity.x, m_hTarget->pev->velocity.y, m_hTarget->pev->velocity.z); + break; + } + } + + Vector vecVelAngles; + + if (m_iszFacing) + { + switch (m_iFaceMode) + { + case 0: // set angles + vecTemp = CalcLocus_Velocity( this, m_hLocus, STRING(m_iszFacing) ); + if (vecTemp != g_vecZero) // if the vector is 0 0 0, don't use it + { + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "DEBUG: Set angles from %f %f %f ", m_hTarget->pev->angles.x, m_hTarget->pev->angles.y, m_hTarget->pev->angles.z); + UTIL_SetAngles(m_hTarget, UTIL_VecToAngles( vecTemp )); + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "to %f %f %f\n", m_hTarget->pev->angles.x, m_hTarget->pev->angles.y, m_hTarget->pev->angles.z); + } + else if (pev->spawnflags & SF_MOTION_DEBUG) + { + ALERT(at_debug, "Zero velocity, don't change angles\n"); + } + break; + case 1: // offset angles (= fake avelocity) + vecTemp = CalcLocus_Velocity( this, m_hLocus, STRING(m_iszFacing) ); + if (vecTemp != g_vecZero) // if the vector is 0 0 0, don't use it + { + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "DEBUG: Offset angles from %f %f %f ", m_hTarget->pev->angles.x, m_hTarget->pev->angles.y, m_hTarget->pev->angles.z); + UTIL_SetAngles(m_hTarget, m_hTarget->pev->angles + gpGlobals->frametime * UTIL_VecToAngles( vecTemp )); + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "to %f %f %f\n", m_hTarget->pev->angles.x, m_hTarget->pev->angles.y, m_hTarget->pev->angles.z); + } + else if (pev->spawnflags & SF_MOTION_DEBUG) + { + ALERT(at_debug, "Zero velocity, don't change angles\n"); + } + break; + case 2: // offset angles (= fake avelocity) + UTIL_StringToRandomVector( vecVelAngles, STRING(m_iszFacing) ); + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "DEBUG: Rotate angles from %f %f %f ", m_hTarget->pev->angles.x, m_hTarget->pev->angles.y, m_hTarget->pev->angles.z); + UTIL_SetAngles(m_hTarget, m_hTarget->pev->angles + gpGlobals->frametime * vecVelAngles); + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "to %f %f %f\n", m_hTarget->pev->angles.x, m_hTarget->pev->angles.y, m_hTarget->pev->angles.z); + break; + case 3: // set avelocity + UTIL_StringToRandomVector( vecTemp, STRING(m_iszFacing) ); + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "DEBUG: Set avelocity from %f %f %f ", m_hTarget->pev->avelocity.x, m_hTarget->pev->avelocity.y, m_hTarget->pev->avelocity.z); + UTIL_SetAvelocity(m_hTarget, vecTemp); + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "to %f %f %f\n", m_hTarget->pev->avelocity.x, m_hTarget->pev->avelocity.y, m_hTarget->pev->avelocity.z); + break; + } + } +} + + +class CMotionManager : public CPointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + void Affect( CBaseEntity *pTarget, CBaseEntity *pActivator ); + void PostSpawn( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_iszPosition; + int m_iPosMode; + int m_iszFacing; + int m_iFaceMode; +}; +LINK_ENTITY_TO_CLASS( motion_manager, CMotionManager ); + +TYPEDESCRIPTION CMotionManager::m_SaveData[] = +{ + DEFINE_FIELD( CMotionManager, m_iszPosition, FIELD_STRING ), + DEFINE_FIELD( CMotionManager, m_iPosMode, FIELD_INTEGER ), + DEFINE_FIELD( CMotionManager, m_iszFacing, FIELD_STRING ), + DEFINE_FIELD( CMotionManager, m_iFaceMode, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE(CMotionManager,CPointEntity); + +void CMotionManager::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iszPosition")) + { + m_iszPosition = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iPosMode")) + { + m_iPosMode = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszFacing")) + { + m_iszFacing = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iFaceMode")) + { + m_iFaceMode = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +void CMotionManager::PostSpawn( void ) +{ + if (FStringNull(pev->targetname)) + Use( this, this, USE_ON, 0 ); +} + +void CMotionManager::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBaseEntity *pTarget = pActivator; + if (pev->target) + { + pTarget = UTIL_FindEntityByTargetname(NULL, STRING(pev->target), pActivator); + if (pTarget == NULL) + ALERT(at_error, "motion_manager \"%s\" can't find entity \"%s\" to affect\n", STRING(pev->targetname), STRING(pev->target)); + else + { + do + { + Affect( pTarget, pActivator ); + pTarget = UTIL_FindEntityByTargetname(pTarget, STRING(pev->target), pActivator); + } while ( pTarget ); + } + } +} + +void CMotionManager::Affect( CBaseEntity *pTarget, CBaseEntity *pActivator ) +{ + if (pev->spawnflags & SF_MOTION_DEBUG) + ALERT(at_debug, "DEBUG: Creating MotionThread for %s \"%s\"\n", STRING(pTarget->pev->classname), STRING(pTarget->pev->targetname)); + + CMotionThread *pThread = GetClassPtr( (CMotionThread*)NULL ); + if (pThread == NULL) return; //error? + pThread->pev->classname = MAKE_STRING( "motion_thread" ); // allow save\restore + pThread->m_hLocus = pActivator; + pThread->m_hTarget = pTarget; + pThread->m_iszPosition = m_iszPosition; + pThread->m_iPosMode = m_iPosMode; + pThread->m_iszFacing = m_iszFacing; + pThread->m_iFaceMode = m_iFaceMode; + pThread->pev->spawnflags = pev->spawnflags; + pThread->SetNextThink( 0 ); +} + + +// this is a really bad idea. +class CTriggerChangeTarget : public CBaseDelay +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + int m_iszNewTarget; +}; +LINK_ENTITY_TO_CLASS( trigger_changetarget, CTriggerChangeTarget ); + +TYPEDESCRIPTION CTriggerChangeTarget::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerChangeTarget, m_iszNewTarget, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerChangeTarget,CBaseDelay); + +void CTriggerChangeTarget::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iszNewTarget")) + { + m_iszNewTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + +void CTriggerChangeTarget::Spawn( void ) +{ +} + + +void CTriggerChangeTarget::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBaseEntity *pTarget = UTIL_FindEntityByTargetname( NULL, STRING( pev->target ), pActivator ); + + if (pTarget) + { + if (FStrEq(STRING(m_iszNewTarget), "*locus")) + { + if (pActivator) + pTarget->pev->target = pActivator->pev->targetname; + else + ALERT(at_error, "trigger_changetarget \"%s\" requires a locus!\n", STRING(pev->targetname)); + } + else + pTarget->pev->target = m_iszNewTarget; + CBaseMonster *pMonster = pTarget->MyMonsterPointer( ); + if (pMonster) + { + pMonster->m_pGoalEnt = NULL; + } + } +} + + +//LRC - you thought _that_ was a bad idea? Check this baby out... +class CTriggerChangeValue : public CBaseDelay +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + int m_iszNewValue; +}; +LINK_ENTITY_TO_CLASS( trigger_changevalue, CTriggerChangeValue ); + +TYPEDESCRIPTION CTriggerChangeValue::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerChangeValue, m_iszNewValue, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerChangeValue,CBaseDelay); + +void CTriggerChangeValue::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iszNewValue")) + { + m_iszNewValue = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + +void CTriggerChangeValue::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBaseEntity *pTarget = UTIL_FindEntityByTargetname( NULL, STRING( pev->target ), pActivator ); + + if (pTarget) + { + KeyValueData mypkvd; + mypkvd.szKeyName = (char*)STRING(pev->netname); + mypkvd.szValue = (char*)STRING(m_iszNewValue); + mypkvd.fHandled = FALSE; + pTarget->KeyValue(&mypkvd); + //Error if not handled? + } +} + +//===================================================== +// trigger_command: activate a console command +//===================================================== +class CTriggerCommand : public CBaseEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; +LINK_ENTITY_TO_CLASS( trigger_command, CTriggerCommand ); + +void CTriggerCommand::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + char szCommand[256]; + + if (pev->netname) + { + sprintf( szCommand, "%s\n", STRING(pev->netname) ); + SERVER_COMMAND( szCommand ); + } +} + +//========================================================= +// trigger_changecvar: temporarily set a console variable +//========================================================= +#define SF_CVAR_ACTIVE 0x80000 + +class CTriggerChangeCVar : public CBaseEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Think( void ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + static TYPEDESCRIPTION m_SaveData[]; + + char m_szStoredString[256]; +}; +LINK_ENTITY_TO_CLASS( trigger_changecvar, CTriggerChangeCVar ); + +TYPEDESCRIPTION CTriggerChangeCVar::m_SaveData[] = +{ + DEFINE_ARRAY( CTriggerChangeCVar, m_szStoredString, FIELD_CHARACTER, 256 ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerChangeCVar,CBaseEntity); + +void CTriggerChangeCVar::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + char szCommand[256]; + + if (!(pev->netname)) return; + + if (ShouldToggle(useType, pev->spawnflags & SF_CVAR_ACTIVE)) + { + if (pev->spawnflags & SF_CVAR_ACTIVE) + { + sprintf( szCommand, "%s \"%s\"\n", STRING(pev->netname), m_szStoredString ); + pev->spawnflags &= ~SF_CVAR_ACTIVE; + } + else + { + strncpy(m_szStoredString, CVAR_GET_STRING(STRING(pev->netname)), 256); + sprintf( szCommand, "%s \"%s\"\n", STRING(pev->netname), STRING(pev->message) ); + pev->spawnflags |= SF_CVAR_ACTIVE; + + if (pev->armorvalue >= 0) + { + SetNextThink( pev->armorvalue ); + } + } + SERVER_COMMAND( szCommand ); + } +} + +void CTriggerChangeCVar::Think( void ) +{ + char szCommand[256]; + + if (pev->spawnflags & SF_CVAR_ACTIVE) + { + sprintf( szCommand, "%s %s\n", STRING(pev->netname), m_szStoredString ); + SERVER_COMMAND( szCommand ); + pev->spawnflags &= ~SF_CVAR_ACTIVE; + } +} + + + +#define SF_CAMERA_PLAYER_POSITION 1 +#define SF_CAMERA_PLAYER_TARGET 2 +#define SF_CAMERA_PLAYER_TAKECONTROL 4 + +class CTriggerCamera : public CBaseDelay +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT FollowTarget( void ); + void Move(void); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + static TYPEDESCRIPTION m_SaveData[]; + + EHANDLE m_hPlayer; + EHANDLE m_hTarget; + CBaseEntity *m_pentPath; + int m_sPath; + float m_flWait; + float m_flReturnTime; + float m_flStopTime; + float m_moveDistance; + float m_targetSpeed; + float m_initialSpeed; + float m_acceleration; + float m_deceleration; + int m_state; + int m_iszViewEntity; + +}; +LINK_ENTITY_TO_CLASS( trigger_camera, CTriggerCamera ); + +// Global Savedata for changelevel friction modifier +TYPEDESCRIPTION CTriggerCamera::m_SaveData[] = +{ + DEFINE_FIELD( CTriggerCamera, m_hPlayer, FIELD_EHANDLE ), + DEFINE_FIELD( CTriggerCamera, m_hTarget, FIELD_EHANDLE ), + DEFINE_FIELD( CTriggerCamera, m_pentPath, FIELD_CLASSPTR ), + DEFINE_FIELD( CTriggerCamera, m_sPath, FIELD_STRING ), + DEFINE_FIELD( CTriggerCamera, m_flWait, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_flReturnTime, FIELD_TIME ), + DEFINE_FIELD( CTriggerCamera, m_flStopTime, FIELD_TIME ), + DEFINE_FIELD( CTriggerCamera, m_moveDistance, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_targetSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_initialSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_acceleration, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_deceleration, FIELD_FLOAT ), + DEFINE_FIELD( CTriggerCamera, m_state, FIELD_INTEGER ), + DEFINE_FIELD( CTriggerCamera, m_iszViewEntity, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE(CTriggerCamera,CBaseDelay); + +void CTriggerCamera::Spawn( void ) +{ + pev->movetype = MOVETYPE_NOCLIP; + pev->solid = SOLID_NOT; // Remove model & collisions + pev->renderamt = 0; // The engine won't draw this model if this is set to 0 and blending is on + pev->rendermode = kRenderTransTexture; + + m_initialSpeed = pev->speed; + if ( m_acceleration == 0 ) + m_acceleration = 500; + if ( m_deceleration == 0 ) + m_deceleration = 500; +} + + +void CTriggerCamera :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "wait")) + { + m_flWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "moveto")) + { + m_sPath = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "acceleration")) + { + m_acceleration = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "deceleration")) + { + m_deceleration = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszViewEntity")) + { + m_iszViewEntity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + + + +void CTriggerCamera::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_state ) ) + return; + + // Toggle state + m_state = !m_state; + if (m_state == 0) + { + m_flReturnTime = gpGlobals->time; + return; + } + if ( !pActivator || !pActivator->IsPlayer() ) + { + pActivator = CBaseEntity::Instance(g_engfuncs.pfnPEntityOfEntIndex( 1 )); + } + + m_hPlayer = pActivator; + + m_flReturnTime = gpGlobals->time + m_flWait; + pev->speed = m_initialSpeed; + m_targetSpeed = m_initialSpeed; + + if (FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_TARGET ) ) + { + m_hTarget = m_hPlayer; + } + else + { + m_hTarget = GetNextTarget(); + } + + // Nothing to look at! + if ( m_hTarget == NULL ) + { + return; + } + + + if (FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_TAKECONTROL ) ) + { + ((CBasePlayer *)pActivator)->EnableControl(FALSE); + } + + if ( m_sPath ) + { + m_pentPath = UTIL_FindEntityByTargetname( NULL, STRING(m_sPath) ); + } + else + { + m_pentPath = NULL; + } + + m_flStopTime = gpGlobals->time; + if ( m_pentPath ) + { + if ( m_pentPath->pev->speed != 0 ) + m_targetSpeed = m_pentPath->pev->speed; + + m_flStopTime += m_pentPath->GetDelay(); + } + + // copy over player information + if (FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_POSITION ) ) + { + UTIL_SetOrigin( this, pActivator->pev->origin + pActivator->pev->view_ofs ); + pev->angles.x = -pActivator->pev->angles.x; + pev->angles.y = pActivator->pev->angles.y; + pev->angles.z = 0; + pev->velocity = pActivator->pev->velocity; + } + else + { + pev->velocity = Vector( 0, 0, 0 ); + } + + //LRC + if (m_iszViewEntity) + { + CBaseEntity *pEntity = UTIL_FindEntityByTargetname(NULL, STRING(m_iszViewEntity)); + if (pEntity) + { + SET_VIEW( pActivator->edict(), pEntity->edict() ); + } + } + else + { + SET_VIEW( pActivator->edict(), edict() ); + } + + SET_MODEL(ENT(pev), STRING(pActivator->pev->model) ); + + // follow the player down + SetThink(&CTriggerCamera:: FollowTarget ); + SetNextThink( 0 ); + + m_moveDistance = 0; + Move(); +} + + +void CTriggerCamera::FollowTarget( ) +{ + if (m_hPlayer == NULL) + return; + + if (m_hTarget == NULL || m_flReturnTime < gpGlobals->time) + { + if (m_hPlayer->IsAlive( )) + { + SET_VIEW( m_hPlayer->edict(), m_hPlayer->edict() ); + ((CBasePlayer *)((CBaseEntity *)m_hPlayer))->EnableControl(TRUE); + } + SUB_UseTargets( this, USE_TOGGLE, 0 ); + pev->avelocity = Vector( 0, 0, 0 ); + m_state = 0; + return; + } + + Vector vecGoal = UTIL_VecToAngles( m_hTarget->pev->origin - pev->origin ); + vecGoal.x = -vecGoal.x; + + if (pev->angles.y > 360) + pev->angles.y -= 360; + + if (pev->angles.y < 0) + pev->angles.y += 360; + + float dx = vecGoal.x - pev->angles.x; + float dy = vecGoal.y - pev->angles.y; + + if (dx < -180) + dx += 360; + if (dx > 180) + dx = dx - 360; + + if (dy < -180) + dy += 360; + if (dy > 180) + dy = dy - 360; + + pev->avelocity.x = dx * 40 * gpGlobals->frametime; + pev->avelocity.y = dy * 40 * gpGlobals->frametime; + + + if (!(FBitSet (pev->spawnflags, SF_CAMERA_PLAYER_TAKECONTROL))) + { + pev->velocity = pev->velocity * 0.8; + if (pev->velocity.Length( ) < 10.0) //LRC- whyyyyyy??? + pev->velocity = g_vecZero; + } + + SetNextThink( 0 ); + + Move(); +} + +void CTriggerCamera::Move() +{ + // Not moving on a path, return + if (!m_pentPath) + return; + + // Subtract movement from the previous frame + m_moveDistance -= pev->speed * gpGlobals->frametime; + + // Have we moved enough to reach the target? + if ( m_moveDistance <= 0 ) + { + // Fire the passtarget if there is one + if ( m_pentPath->pev->message ) + { + FireTargets( STRING(m_pentPath->pev->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( m_pentPath->pev->spawnflags, SF_CORNER_FIREONCE ) ) + m_pentPath->pev->message = 0; + } + // Time to go to the next target + m_pentPath = m_pentPath->GetNextTarget(); + + // Set up next corner + if ( !m_pentPath ) + { + pev->velocity = g_vecZero; + } + else + { + if ( m_pentPath->pev->speed != 0 ) + m_targetSpeed = m_pentPath->pev->speed; + + Vector delta = m_pentPath->pev->origin - pev->origin; + m_moveDistance = delta.Length(); + pev->movedir = delta.Normalize(); + m_flStopTime = gpGlobals->time + m_pentPath->GetDelay(); + } + } + + if ( m_flStopTime > gpGlobals->time ) + pev->speed = UTIL_Approach( 0, pev->speed, m_deceleration * gpGlobals->frametime ); + else + pev->speed = UTIL_Approach( m_targetSpeed, pev->speed, m_acceleration * gpGlobals->frametime ); + + float fraction = 2 * gpGlobals->frametime; + pev->velocity = ((pev->movedir * pev->speed) * fraction) + (pev->velocity * (1-fraction)); +} + + + + + +// ========= buz: paranoia vgui text messages: ============= + +class CTriggerTextWindow : public CBaseEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) + { + if ( !pActivator || !pActivator->IsPlayer() ) + { + pActivator = CBaseEntity::Instance(g_engfuncs.pfnPEntityOfEntIndex( 1 )); + } + + MESSAGE_BEGIN( MSG_ONE, gmsgTextWindow, NULL, pActivator->pev ); + WRITE_STRING(STRING(pev->message)); + MESSAGE_END(); + } + + int ObjectCaps( void ) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + + +LINK_ENTITY_TO_CLASS( trigger_textwindow, CTriggerTextWindow ); + + +//========== buz: mp3 control =========================== +class CAmbientMusic : public CBaseDelay +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void PlayStream( CBasePlayer *pPlayer ); + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Think( void ); +}; + +LINK_ENTITY_TO_CLASS( trigger_mp3, CAmbientMusic ); + +void CAmbientMusic :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "fadetime")) + { + pev->frags = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseDelay::KeyValue( pkvd ); +} + +void CAmbientMusic :: Spawn( void ) +{ + if( !pev->message ) + pev->frags = 5.0f; // Paranoia default fade +} + +void CAmbientMusic :: PlayStream( CBasePlayer *pPlayer ) +{ + if( !pPlayer ) return; // not spawned? + + if( !pev->message ) + { + if( pev->frags ) + { + // run fading out like in Paranoia + pev->dmgtime = gpGlobals->time; + SetNextThink( 0.01 ); + } + else + { + CLIENT_COMMAND( pPlayer->edict(), "music\n" ); // stop + } + } + else + { + CLIENT_COMMAND( pPlayer->edict(), "music \"%s\" \"%s\"\n", STRING( pev->message ), STRING( pev->message )); // loop + } +} + +void CAmbientMusic :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if( pev->dmgtime ) return; // soundtrack is fading out + + // send to all the clients + for( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + PlayStream( (CBasePlayer *)UTIL_PlayerByIndex( i )); + } +} + +void CAmbientMusic :: Think( void ) +{ + float elapsed = gpGlobals->time - pev->dmgtime; + float f = elapsed / pev->frags; + f = bound( 0.0f, f, 1.0f ); + + MESSAGE_BEGIN( MSG_ALL, gmsgMusicFade ); + WRITE_SHORT( f * 10000 ); + MESSAGE_END(); + + if( f == 1.0f ) // hit 100% fade + { + // send stop to all the clients + for( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( i ); + if( pPlayer ) CLIENT_COMMAND( pPlayer->edict(), "music\n" ); + } + + pev->dmgtime = 0.0f; + return; + } + + SetNextThink( 0.01 ); +} + +//========== buz: goal window =========================== + +class CTriggerGoal : public CBaseEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) + { + if ( !pActivator || !pActivator->IsPlayer() ) + { + pActivator = CBaseEntity::Instance(g_engfuncs.pfnPEntityOfEntIndex( 1 )); + } + + CBasePlayer* pPlayer = (CBasePlayer*)pActivator; + pPlayer->PlayerSetGoalDesc(pev->message, pev->netname, pev->noise); + } + + int ObjectCaps( void ) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + + +LINK_ENTITY_TO_CLASS( trigger_goal, CTriggerGoal ); + + +//======== buz: buz: rush script ====================== + + +void CStartRush :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBaseEntity* pTarget = NULL; + pTarget = UTIL_FindEntityByTargetname(pTarget, STRING(pev->target), pActivator); + if (!pTarget) + { + ALERT(at_console, "scripted_startrush cant find monster %s\n", STRING(pev->target)); + return; + } + + CBaseMonster* pMonster = pTarget->MyMonsterPointer(); + if (!pMonster) + { + ALERT(at_console, "%s is not a monster!\n", STRING(pev->target)); + return; + } + + pMonster->m_hRushEntity = pev->targetname; + pMonster->m_iRushMovetype = pev->weapons; + pMonster->m_flRushDistance = pev->frags; + pMonster->m_flRushNextTime = 0; + +// ALERT(at_console, "RUSH use org: %f, %f, %f\n", pev->origin.x, pev->origin.y, pev->origin.z); +} + +CBaseEntity* CStartRush :: GetDestinationEntity( void ) +{ + if (!FStringNull(pev->message)) + { + CBaseEntity* pTarget = NULL; + pTarget = UTIL_FindEntityByTargetname(pTarget, STRING(pev->message)); + if (!pTarget) + { + ALERT(at_console, "scripted_startrush cant find entity %s\n", STRING(pev->message)); + return this; + } + return pTarget; + } +// ALERT(at_console, "RUSH: RETURNING org: %f, %f, %f\n", pev->origin.x, pev->origin.y, pev->origin.z); + + return this; +} + +void CStartRush :: ReportSuccess( CBaseEntity* who ) +{ +// ALERT(at_console, " CStartRush got succes report\n"); + + if (!FStringNull(pev->netname)) + { + FireTargets( STRING(pev->netname), who, this, USE_TOGGLE, 0 ); + // ALERT(at_console, " CStartRush firing: %s\n", STRING(pev->netname)); + } +} + + +void CStartRush :: Spawn( void ) +{ + pev->solid = SOLID_NOT; +} + + +LINK_ENTITY_TO_CLASS( scripted_startrush, CStartRush ); + + + +//================== buz: gas area ============================ + +#define SF_GASAREA_STARTOFF 1 +#define GASAREA_THINKTIME 0.3 + +class CGasArea : public CBaseEntity +{ +public: + void Spawn() + { + pev->solid = SOLID_TRIGGER; + pev->movetype = MOVETYPE_NONE; + SET_MODEL(ENT(pev), STRING(pev->model)); // set size and link into world + if ( CVAR_GET_FLOAT("showtriggers") == 0 ) + SetBits( pev->effects, EF_NODRAW ); + + if ( FBitSet(pev->spawnflags, SF_GASAREA_STARTOFF) ) + { + m_iEnabled = FALSE; + SetThink(NULL); + pev->nextthink = -1; + } + else + { + m_iEnabled = TRUE; + SetThink(&CGasArea::DamageThink); + pev->nextthink = gpGlobals->time + GASAREA_THINKTIME; + } + } + + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) + { + // just toggle on/off + if (m_iEnabled) + { + m_iEnabled = FALSE; + SetThink(NULL); + pev->nextthink = -1; + } + else + { + m_iEnabled = TRUE; + SetThink(&CGasArea::DamageThink); + pev->nextthink = gpGlobals->time + GASAREA_THINKTIME; + } + } + + void EXPORT DamageThink(void) + { + Vector warnmin = pev->absmin - Vector(m_fWarningDist, m_fWarningDist, 0); + Vector warnmax = pev->absmax + Vector(m_fWarningDist, m_fWarningDist, 0); + + // find all monsters in volume + CBaseEntity *pList[32]; + int count = UTIL_EntitiesInBox( pList, 32, warnmin, warnmax, FL_MONSTER|FL_CLIENT ); + + for (int i = 0; i < count; i++) + { + CBaseMonster* pMonster = pList[i]->MyMonsterPointer(); + if ( pMonster && pMonster->pev->takedamage != DAMAGE_NO && pMonster->IsAlive() ) + { + // is monster in gas area, or just walking nearby? + if (pMonster->pev->origin.x > pev->absmin.x && + pMonster->pev->origin.y > pev->absmin.y && + pMonster->pev->origin.z > pev->absmin.z && + pMonster->pev->origin.x < pev->absmax.x && + pMonster->pev->origin.y < pev->absmax.y && + pMonster->pev->origin.z < pev->absmax.z ) + { + // inflict damage + pMonster->TakeDamage( pev, pev, m_fDamage, DMG_NERVEGAS ); + pMonster->GasWarning(1); + } + else + { + // just warn him + pMonster->GasWarning(0); + } + } + } + + pev->nextthink = gpGlobals->time + GASAREA_THINKTIME; + } + + void KeyValue( KeyValueData *pkvd ) + { + if (FStrEq(pkvd->szKeyName, "warndist")) + { + m_fWarningDist = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + if (FStrEq(pkvd->szKeyName, "gasdamage")) + { + m_fDamage = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); + } + + int ObjectCaps( void ) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + int m_iEnabled; + float m_fWarningDist; + float m_fDamage; +}; + +LINK_ENTITY_TO_CLASS( trigger_gasarea, CGasArea ); + +TYPEDESCRIPTION CGasArea::m_SaveData[] = +{ + DEFINE_FIELD( CGasArea, m_iEnabled, FIELD_INTEGER ), + DEFINE_FIELD( CGasArea, m_fWarningDist, FIELD_FLOAT ), + DEFINE_FIELD( CGasArea, m_fDamage, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE(CGasArea,CBaseEntity); \ No newline at end of file diff --git a/dlls/turret.cpp b/dlls/turret.cpp new file mode 100644 index 0000000..390eca3 --- /dev/null +++ b/dlls/turret.cpp @@ -0,0 +1,1336 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +/* + +===== turret.cpp ======================================================== + +*/ + +// +// TODO: +// Take advantage of new monster fields like m_hEnemy and get rid of that OFFSET() stuff +// Revisit enemy validation stuff, maybe it's not necessary with the newest monster code +// + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "effects.h" + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); + +#define TURRET_SHOTS 2 +#define TURRET_RANGE (100 * 12) +#define TURRET_SPREAD Vector( 0, 0, 0 ) +#define TURRET_TURNRATE 30 //angles per 0.1 second +#define TURRET_MAXWAIT 15 // seconds turret will stay active w/o a target +#define TURRET_MAXSPIN 5 // seconds turret barrel will spin w/o a target +#define TURRET_MACHINE_VOLUME 0.5 + +typedef enum +{ + TURRET_ANIM_NONE = 0, + TURRET_ANIM_FIRE, + TURRET_ANIM_SPIN, + TURRET_ANIM_DEPLOY, + TURRET_ANIM_RETIRE, + TURRET_ANIM_DIE, +} TURRET_ANIM; + +class CBaseTurret : public CBaseMonster +{ +public: + void Spawn(void); + virtual void Precache(void); + void KeyValue( KeyValueData *pkvd ); + void EXPORT TurretUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + virtual int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + virtual int Classify(void); + + int BloodColor( void ) { return DONT_BLEED; } + void GibMonster( void ) {} // UNDONE: Throw turret gibs? + + // Think functions + + void EXPORT ActiveThink(void); + void EXPORT SearchThink(void); + void EXPORT AutoSearchThink(void); + void EXPORT TurretDeath(void); + + virtual void EXPORT SpinDownCall(void) { m_iSpin = 0; } + virtual void EXPORT SpinUpCall(void) { m_iSpin = 1; } + + // void SpinDown(void); + // float EXPORT SpinDownCall( void ) { return SpinDown(); } + + // virtual float SpinDown(void) { return 0;} + // virtual float Retire(void) { return 0;} + + void EXPORT Deploy(void); + void EXPORT Retire(void); + + void EXPORT Initialize(void); + + virtual void Ping(void); + virtual void EyeOn(void); + virtual void EyeOff(void); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + // other functions + void SetTurretAnim(TURRET_ANIM anim); + int MoveTurret(void); + virtual void Shoot(Vector &vecSrc, Vector &vecDirToEnemy) { }; + + float m_flMaxSpin; // Max time to spin the barrel w/o a target + int m_iSpin; + + CSprite *m_pEyeGlow; + int m_eyeBrightness; + + int m_iDeployHeight; + int m_iRetractHeight; + int m_iMinPitch; + + int m_iBaseTurnRate; // angles per second + float m_fTurnRate; // actual turn rate + int m_iOrientation; // 0 = floor, 1 = Ceiling + int m_iOn; + virtual STATE getState() { if (m_iOn) { return STATE_ON; } else { return STATE_OFF; } } + + int m_fBeserk; // Sometimes this bitch will just freak out + int m_iAutoStart; // true if the turret auto deploys when a target + // enters its range + + Vector m_vecLastSight; + float m_flLastSight; // Last time we saw a target + float m_flMaxWait; // Max time to seach w/o a target + int m_iSearchSpeed; // Not Used! + + // movement + float m_flStartYaw; + Vector m_vecCurAngles; + Vector m_vecGoalAngles; + + + float m_flPingTime; // Time until the next ping, used when searching + float m_flSpinUpTime; // Amount of time until the barrel should spin down when searching +}; + + +TYPEDESCRIPTION CBaseTurret::m_SaveData[] = +{ + DEFINE_FIELD( CBaseTurret, m_flMaxSpin, FIELD_FLOAT ), + DEFINE_FIELD( CBaseTurret, m_iSpin, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseTurret, m_pEyeGlow, FIELD_CLASSPTR ), + DEFINE_FIELD( CBaseTurret, m_eyeBrightness, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_iDeployHeight, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_iRetractHeight, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_iMinPitch, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseTurret, m_iBaseTurnRate, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_fTurnRate, FIELD_FLOAT ), + DEFINE_FIELD( CBaseTurret, m_iOrientation, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_iOn, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_fBeserk, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_iAutoStart, FIELD_INTEGER ), + + + DEFINE_FIELD( CBaseTurret, m_vecLastSight, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseTurret, m_flLastSight, FIELD_TIME ), + DEFINE_FIELD( CBaseTurret, m_flMaxWait, FIELD_FLOAT ), + DEFINE_FIELD( CBaseTurret, m_iSearchSpeed, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseTurret, m_flStartYaw, FIELD_FLOAT ), + DEFINE_FIELD( CBaseTurret, m_vecCurAngles, FIELD_VECTOR ), + DEFINE_FIELD( CBaseTurret, m_vecGoalAngles, FIELD_VECTOR ), + + DEFINE_FIELD( CBaseTurret, m_flPingTime, FIELD_TIME ), + DEFINE_FIELD( CBaseTurret, m_flSpinUpTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CBaseTurret, CBaseMonster ); + +class CTurret : public CBaseTurret +{ +public: + void Spawn(void); + void Precache(void); + // Think functions + void SpinUpCall(void); + void SpinDownCall(void); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + // other functions + void Shoot(Vector &vecSrc, Vector &vecDirToEnemy); + +private: + int m_iStartSpin; + +}; +TYPEDESCRIPTION CTurret::m_SaveData[] = +{ + DEFINE_FIELD( CTurret, m_iStartSpin, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CTurret, CBaseTurret ); + + +class CMiniTurret : public CBaseTurret +{ +public: + void Spawn( ); + void Precache(void); + // other functions + void Shoot(Vector &vecSrc, Vector &vecDirToEnemy); +}; + + +LINK_ENTITY_TO_CLASS( monster_turret, CTurret ); +LINK_ENTITY_TO_CLASS( monster_miniturret, CMiniTurret ); + +void CBaseTurret::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "maxsleep")) + { + m_flMaxWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "orientation")) + { + m_iOrientation = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + } + else if (FStrEq(pkvd->szKeyName, "searchspeed")) + { + m_iSearchSpeed = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + } + else if (FStrEq(pkvd->szKeyName, "turnrate")) + { + m_iBaseTurnRate = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "style") || + FStrEq(pkvd->szKeyName, "height") || + FStrEq(pkvd->szKeyName, "value1") || + FStrEq(pkvd->szKeyName, "value2") || + FStrEq(pkvd->szKeyName, "value3")) + pkvd->fHandled = TRUE; + else + CBaseMonster::KeyValue( pkvd ); +} + + +void CBaseTurret::Spawn() +{ + Precache( ); + SetNextThink( 1 ); + pev->movetype = MOVETYPE_FLY; + pev->sequence = 0; + pev->frame = 0; + pev->solid = SOLID_SLIDEBOX; + pev->takedamage = DAMAGE_AIM; + + SetBits (pev->flags, FL_MONSTER); + SetUse(&CBaseTurret:: TurretUse ); + + if (( pev->spawnflags & SF_MONSTER_TURRET_AUTOACTIVATE ) + && !( pev->spawnflags & SF_MONSTER_TURRET_STARTINACTIVE )) + { + m_iAutoStart = TRUE; + } + + if (m_iOrientation == 1) + { + pev->idealpitch = 180; + pev->angles.x = 180; + } + + ResetSequenceInfo( ); + SetBoneController( 0, 0 ); + SetBoneController( 1, 0 ); + m_flFieldOfView = VIEW_FIELD_FULL; + // m_flSightRange = TURRET_RANGE; +} + + +void CBaseTurret::Precache( ) +{ + PRECACHE_SOUND ("turret/tu_fire1.wav"); + PRECACHE_SOUND ("turret/tu_ping.wav"); + PRECACHE_SOUND ("turret/tu_active2.wav"); + PRECACHE_SOUND ("turret/tu_die.wav"); + PRECACHE_SOUND ("turret/tu_die2.wav"); + PRECACHE_SOUND ("turret/tu_die3.wav"); + // PRECACHE_SOUND ("turret/tu_retract.wav"); // just use deploy sound to save memory + PRECACHE_SOUND ("turret/tu_deploy.wav"); + PRECACHE_SOUND ("turret/tu_spinup.wav"); + PRECACHE_SOUND ("turret/tu_spindown.wav"); + PRECACHE_SOUND ("turret/tu_search.wav"); + PRECACHE_SOUND ("turret/tu_alert.wav"); +} + +#define TURRET_GLOW_SPRITE "sprites/flare3.spr" + +void CTurret::Spawn() +{ + Precache( ); + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/turret.mdl"); + if (!pev->health) + pev->health = gSkillData.turretHealth; + m_HackedGunPos = Vector( 0, 0, 12.75 ); + m_flMaxSpin = TURRET_MAXSPIN; + pev->view_ofs.z = 12.75; + + CBaseTurret::Spawn( ); + + m_iRetractHeight = 16; + m_iDeployHeight = 32; + m_iMinPitch = -15; + UTIL_SetSize(pev, Vector(-32, -32, -m_iRetractHeight), Vector(32, 32, m_iRetractHeight)); + + SetThink(&CTurret::Initialize); + + m_pEyeGlow = CSprite::SpriteCreate( TURRET_GLOW_SPRITE, pev->origin, FALSE ); + m_pEyeGlow->SetTransparency( kRenderGlow, 255, 0, 0, 0, kRenderFxNoDissipation ); + m_pEyeGlow->SetAttachment( edict(), 2 ); + m_eyeBrightness = 0; + + SetNextThink( 0.3 ); +} + +void CTurret::Precache() +{ + CBaseTurret::Precache( ); + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL ("models/turret.mdl"); + PRECACHE_MODEL (TURRET_GLOW_SPRITE); +} + +void CMiniTurret::Spawn() +{ + Precache( ); + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/miniturret.mdl"); + if (!pev->health) + pev->health = gSkillData.miniturretHealth; + m_HackedGunPos = Vector( 0, 0, 12.75 ); + m_flMaxSpin = 0; + pev->view_ofs.z = 12.75; + + CBaseTurret::Spawn( ); + m_iRetractHeight = 16; + m_iDeployHeight = 32; + m_iMinPitch = -15; + UTIL_SetSize(pev, Vector(-16, -16, -m_iRetractHeight), Vector(16, 16, m_iRetractHeight)); + + SetThink(&CMiniTurret::Initialize); + SetNextThink( 0.3 ); +} + + +void CMiniTurret::Precache() +{ + CBaseTurret::Precache( ); + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL ("models/miniturret.mdl"); + PRECACHE_SOUND("weapons/hks1.wav"); + PRECACHE_SOUND("weapons/hks2.wav"); + PRECACHE_SOUND("weapons/hks3.wav"); +} + +void CBaseTurret::Initialize(void) +{ + m_iOn = 0; + m_fBeserk = 0; + m_iSpin = 0; + + SetBoneController( 0, 0 ); + SetBoneController( 1, 0 ); + + if (m_iBaseTurnRate == 0) m_iBaseTurnRate = TURRET_TURNRATE; + if (m_flMaxWait == 0) m_flMaxWait = TURRET_MAXWAIT; + m_flStartYaw = pev->angles.y; + if (m_iOrientation == 1) + { +// pev->idealpitch = 180; //This is moved to CBaseTurret::Spawn for fix old bug in original HL. G-Cont. +// pev->angles.x = 180; + pev->view_ofs.z = -pev->view_ofs.z; + pev->effects |= EF_INVLIGHT; + pev->angles.y = pev->angles.y + 180; + if (pev->angles.y > 360) + pev->angles.y = pev->angles.y - 360; + } + + m_vecGoalAngles.x = 0; + + if (m_iAutoStart) + { + m_flLastSight = gpGlobals->time + m_flMaxWait; + SetThink(&CBaseTurret::AutoSearchThink); + SetNextThink( 0.1 ); + } + else + SetThink(&CBaseTurret::SUB_DoNothing); +} + +void CBaseTurret::TurretUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_iOn ) ) + return; + + if (m_iOn) + { + m_hEnemy = NULL; + SetNextThink( 0.1 ); + m_iAutoStart = FALSE;// switching off a turret disables autostart + //!!!! this should spin down first!!BUGBUG + SetThink(&CBaseTurret::Retire); + } + else + { + SetNextThink( 0.1 ); // turn on delay + + // if the turret is flagged as an autoactivate turret, re-enable it's ability open self. + if ( pev->spawnflags & SF_MONSTER_TURRET_AUTOACTIVATE ) + { + m_iAutoStart = TRUE; + } + + SetThink(&CBaseTurret::Deploy); + } +} + + +void CBaseTurret::Ping( void ) +{ + // make the pinging noise every second while searching + if (m_flPingTime == 0) + m_flPingTime = gpGlobals->time + 1; + else if (m_flPingTime <= gpGlobals->time) + { + m_flPingTime = gpGlobals->time + 1; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "turret/tu_ping.wav", 1, ATTN_NORM); + EyeOn( ); + } + else if (m_eyeBrightness > 0) + { + EyeOff( ); + } +} + + +void CBaseTurret::EyeOn( ) +{ + if (m_pEyeGlow) + { + if (m_eyeBrightness != 255) + { + m_eyeBrightness = 255; + } + m_pEyeGlow->SetBrightness( m_eyeBrightness ); + } +} + + +void CBaseTurret::EyeOff( ) +{ + if (m_pEyeGlow) + { + if (m_eyeBrightness > 0) + { + m_eyeBrightness = max( 0, m_eyeBrightness - 30 ); + m_pEyeGlow->SetBrightness( m_eyeBrightness ); + } + } +} + + +void CBaseTurret::ActiveThink(void) +{ + int fAttack = 0; + Vector vecDirToEnemy; + + SetNextThink( 0.1 ); + StudioFrameAdvance( ); + + if ((!m_iOn) || (m_hEnemy == NULL)) + { + m_hEnemy = NULL; + m_flLastSight = gpGlobals->time + m_flMaxWait; + SetThink(&CBaseTurret::SearchThink); + return; + } + + // if it's dead, look for something new + if ( !m_hEnemy->IsAlive() ) + { + if (!m_flLastSight) + { + m_flLastSight = gpGlobals->time + 0.5; // continue-shooting timeout + } + else + { + if (gpGlobals->time > m_flLastSight) + { + m_hEnemy = NULL; + m_flLastSight = gpGlobals->time + m_flMaxWait; + SetThink(&CBaseTurret::SearchThink); + return; + } + } + } + + Vector vecMid = pev->origin + pev->view_ofs; + Vector vecMidEnemy = m_hEnemy->BodyTarget( vecMid ); + + // Look for our current enemy + int fEnemyVisible = FBoxVisible(pev, m_hEnemy->pev, vecMidEnemy ); + + vecDirToEnemy = vecMidEnemy - vecMid; // calculate dir and dist to enemy + float flDistToEnemy = vecDirToEnemy.Length(); + + Vector vec = UTIL_VecToAngles(vecMidEnemy - vecMid); + + // Current enmey is not visible. + if (!fEnemyVisible || (flDistToEnemy > TURRET_RANGE)) + { + if (!m_flLastSight) + m_flLastSight = gpGlobals->time + 0.5; + else + { + // Should we look for a new target? + if (gpGlobals->time > m_flLastSight) + { + m_hEnemy = NULL; + m_flLastSight = gpGlobals->time + m_flMaxWait; + SetThink(&CBaseTurret::SearchThink); + return; + } + } + fEnemyVisible = 0; + } + else + { + m_vecLastSight = vecMidEnemy; + } + + UTIL_MakeAimVectors(m_vecCurAngles); + + /* + ALERT( at_console, "%.0f %.0f : %.2f %.2f %.2f\n", + m_vecCurAngles.x, m_vecCurAngles.y, + gpGlobals->v_forward.x, gpGlobals->v_forward.y, gpGlobals->v_forward.z ); + */ + + Vector vecLOS = vecDirToEnemy; //vecMid - m_vecLastSight; + vecLOS = vecLOS.Normalize(); + + // Is the Gun looking at the target + if (DotProduct(vecLOS, gpGlobals->v_forward) <= 0.866) // 30 degree slop + fAttack = FALSE; + else + fAttack = TRUE; + + // fire the gun + if (m_iSpin && ((fAttack) || (m_fBeserk))) + { + Vector vecSrc, vecAng; + GetAttachment( 0, vecSrc, vecAng ); + SetTurretAnim(TURRET_ANIM_FIRE); + Shoot(vecSrc, gpGlobals->v_forward ); + } + else + { + SetTurretAnim(TURRET_ANIM_SPIN); + } + + //move the gun + if (m_fBeserk) + { + if (RANDOM_LONG(0,9) == 0) + { + m_vecGoalAngles.y = RANDOM_FLOAT(0,360); + m_vecGoalAngles.x = RANDOM_FLOAT(0,90) - 90 * m_iOrientation; + TakeDamage(pev,pev,1, DMG_GENERIC); // don't beserk forever + return; + } + } + else if (fEnemyVisible) + { + if (vec.y > 360) + vec.y -= 360; + + if (vec.y < 0) + vec.y += 360; + + //ALERT(at_console, "[%.2f]", vec.x); + + if (vec.x < -180) + vec.x += 360; + + if (vec.x > 180) + vec.x -= 360; + + // now all numbers should be in [1...360] + // pin to turret limitations to [-90...15] + + if (m_iOrientation == 0) + { + if (vec.x > 90) + vec.x = 90; + else if (vec.x < m_iMinPitch) + vec.x = m_iMinPitch; + } + else + { + if (vec.x < -90) + vec.x = -90; + else if (vec.x > -m_iMinPitch) + vec.x = -m_iMinPitch; + } + + // ALERT(at_console, "->[%.2f]\n", vec.x); + + m_vecGoalAngles.y = vec.y; + m_vecGoalAngles.x = vec.x; + + } + + SpinUpCall(); + MoveTurret(); +} + + +void CTurret::Shoot(Vector &vecSrc, Vector &vecDirToEnemy) +{ + FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, BULLET_NORMAL, gSkillData.monDmg12MM ); + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "turret/tu_fire1.wav", 1, 0.6); + pev->effects = pev->effects | EF_MUZZLEFLASH; +} + + +void CMiniTurret::Shoot(Vector &vecSrc, Vector &vecDirToEnemy) +{ + FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, BULLET_NORMAL, gSkillData.monDmg9MM ); + + switch(RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks3.wav", 1, ATTN_NORM); break; + } + pev->effects = pev->effects | EF_MUZZLEFLASH; +} + + +void CBaseTurret::Deploy(void) +{ + SetNextThink( 0.1 ); + StudioFrameAdvance( ); + + if (pev->sequence != TURRET_ANIM_DEPLOY) + { + m_iOn = 1; + SetTurretAnim(TURRET_ANIM_DEPLOY); + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_deploy.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + SUB_UseTargets( this, USE_ON, 0 ); + } + + if (m_fSequenceFinished) + { + pev->maxs.z = m_iDeployHeight; + pev->mins.z = -m_iDeployHeight; + UTIL_SetSize(pev, pev->mins, pev->maxs); + + m_vecCurAngles.x = 0; + + if (m_iOrientation == 1) + { + m_vecCurAngles.y = UTIL_AngleMod( pev->angles.y + 180 ); + } + else + { + m_vecCurAngles.y = UTIL_AngleMod( pev->angles.y ); + } + + SetTurretAnim(TURRET_ANIM_SPIN); + pev->framerate = 0; + SetThink(&CBaseTurret::SearchThink); + } + + m_flLastSight = gpGlobals->time + m_flMaxWait; +} + +void CBaseTurret::Retire(void) +{ + // make the turret level + m_vecGoalAngles.x = 0; + m_vecGoalAngles.y = m_flStartYaw; + + SetNextThink( 0.1 ); + + StudioFrameAdvance( ); + + EyeOff( ); + + if (!MoveTurret()) + { + if (m_iSpin) + { + SpinDownCall(); + } + else if (pev->sequence != TURRET_ANIM_RETIRE) + { + SetTurretAnim(TURRET_ANIM_RETIRE); + EMIT_SOUND_DYN(ENT(pev), CHAN_BODY, "turret/tu_deploy.wav", TURRET_MACHINE_VOLUME, ATTN_NORM, 0, 120); + SUB_UseTargets( this, USE_OFF, 0 ); + } + else if (m_fSequenceFinished) + { + m_iOn = 0; + m_flLastSight = 0; + SetTurretAnim(TURRET_ANIM_NONE); + pev->maxs.z = m_iRetractHeight; + pev->mins.z = -m_iRetractHeight; + UTIL_SetSize(pev, pev->mins, pev->maxs); + if (m_iAutoStart) + { + SetThink(&CBaseTurret::AutoSearchThink); + SetNextThink( 0.1 ); + } + else + SetThink(&CBaseTurret::SUB_DoNothing); + } + } + else + { + SetTurretAnim(TURRET_ANIM_SPIN); + } +} + + +void CTurret::SpinUpCall(void) +{ + StudioFrameAdvance( ); + SetNextThink( 0.1 ); + + // Are we already spun up? If not start the two stage process. + if (!m_iSpin) + { + SetTurretAnim( TURRET_ANIM_SPIN ); + // for the first pass, spin up the the barrel + if (!m_iStartSpin) + { + SetNextThink( 1.0 ); // spinup delay + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_spinup.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + m_iStartSpin = 1; + pev->framerate = 0.1; + } + // after the barrel is spun up, turn on the hum + else if (pev->framerate >= 1.0) + { + SetNextThink( 0.1 ); // retarget delay + EMIT_SOUND(ENT(pev), CHAN_STATIC, "turret/tu_active2.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + SetThink(&CTurret::ActiveThink); + m_iStartSpin = 0; + m_iSpin = 1; + } + else + { + pev->framerate += 0.075; + } + } + + if (m_iSpin) + { + SetThink(&CTurret::ActiveThink); + } +} + + +void CTurret::SpinDownCall(void) +{ + if (m_iSpin) + { + SetTurretAnim( TURRET_ANIM_SPIN ); + if (pev->framerate == 1.0) + { + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "turret/tu_active2.wav", 0, 0, SND_STOP, 100); + EMIT_SOUND(ENT(pev), CHAN_ITEM, "turret/tu_spindown.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + } + pev->framerate -= 0.02; + if (pev->framerate <= 0) + { + pev->framerate = 0; + m_iSpin = 0; + } + } +} + + +void CBaseTurret::SetTurretAnim(TURRET_ANIM anim) +{ + if (pev->sequence != anim) + { + switch(anim) + { + case TURRET_ANIM_FIRE: + case TURRET_ANIM_SPIN: + if (pev->sequence != TURRET_ANIM_FIRE && pev->sequence != TURRET_ANIM_SPIN) + { + pev->frame = 0; + } + break; + default: + pev->frame = 0; + break; + } + + pev->sequence = anim; + ResetSequenceInfo( ); + + switch(anim) + { + case TURRET_ANIM_RETIRE: + pev->frame = 255; + pev->framerate = -1.0; + break; + case TURRET_ANIM_DIE: + pev->framerate = 1.0; + break; + } + //ALERT(at_console, "Turret anim #%d\n", anim); + } +} + + +// +// This search function will sit with the turret deployed and look for a new target. +// After a set amount of time, the barrel will spin down. After m_flMaxWait, the turret will +// retact. +// +void CBaseTurret::SearchThink(void) +{ + // ensure rethink + SetTurretAnim(TURRET_ANIM_SPIN); + StudioFrameAdvance( ); + SetNextThink( 0.1 ); + + if (m_flSpinUpTime == 0 && m_flMaxSpin) + m_flSpinUpTime = gpGlobals->time + m_flMaxSpin; + + Ping( ); + + // If we have a target and we're still healthy + if (m_hEnemy != NULL) + { + if (!m_hEnemy->IsAlive() ) + m_hEnemy = NULL;// Dead enemy forces a search for new one + } + + + // Acquire Target + if (m_hEnemy == NULL) + { + Look(TURRET_RANGE); + m_hEnemy = BestVisibleEnemy(); + } + + // If we've found a target, spin up the barrel and start to attack + if (m_hEnemy != NULL) + { + m_flLastSight = 0; + m_flSpinUpTime = 0; + SetThink(&CBaseTurret::ActiveThink); + } + else + { + // Are we out of time, do we need to retract? + if (gpGlobals->time > m_flLastSight) + { + //Before we retrace, make sure that we are spun down. + m_flLastSight = 0; + m_flSpinUpTime = 0; + SetThink(&CBaseTurret::Retire); + } + // should we stop the spin? + else if ((m_flSpinUpTime) && (gpGlobals->time > m_flSpinUpTime)) + { + SpinDownCall(); + } + + // generic hunt for new victims + m_vecGoalAngles.y = (m_vecGoalAngles.y + 0.1 * m_fTurnRate); + if (m_vecGoalAngles.y >= 360) + m_vecGoalAngles.y -= 360; + MoveTurret(); + } +} + + +// +// This think function will deploy the turret when something comes into range. This is for +// automatically activated turrets. +// +void CBaseTurret::AutoSearchThink(void) +{ + // ensure rethink + StudioFrameAdvance( ); + SetNextThink( 0.3 ); + + // If we have a target and we're still healthy + + if (m_hEnemy != NULL) + { + if (!m_hEnemy->IsAlive() ) + m_hEnemy = NULL;// Dead enemy forces a search for new one + } + + // Acquire Target + + if (m_hEnemy == NULL) + { + Look( TURRET_RANGE ); + m_hEnemy = BestVisibleEnemy(); + } + + if (m_hEnemy != NULL) + { + SetThink(&CBaseTurret::Deploy); + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_alert.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + } +} + + +void CBaseTurret :: TurretDeath( void ) +{ + BOOL iActive = FALSE; + + StudioFrameAdvance( ); + SetNextThink( 0.1 ); + + if (pev->deadflag != DEAD_DEAD) + { + pev->deadflag = DEAD_DEAD; + + float flRndSound = RANDOM_FLOAT ( 0 , 1 ); + + if ( flRndSound <= 0.33 ) + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die.wav", 1.0, ATTN_NORM); + else if ( flRndSound <= 0.66 ) + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die2.wav", 1.0, ATTN_NORM); + else + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die3.wav", 1.0, ATTN_NORM); + + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "turret/tu_active2.wav", 0, 0, SND_STOP, 100); + + if (m_iOrientation == 0) + m_vecGoalAngles.x = -15; + else + m_vecGoalAngles.x = -90; + + SetTurretAnim(TURRET_ANIM_DIE); + + EyeOn( ); + } + + EyeOff( ); + + if (pev->dmgtime + RANDOM_FLOAT( 0, 2 ) > gpGlobals->time) + { + // lots of smoke + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( RANDOM_FLOAT( pev->absmin.x, pev->absmax.x ) ); + WRITE_COORD( RANDOM_FLOAT( pev->absmin.y, pev->absmax.y ) ); + WRITE_COORD( pev->origin.z - m_iOrientation * 64 ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 25 ); // scale * 10 + WRITE_BYTE( 10 - m_iOrientation * 5); // framerate + MESSAGE_END(); + } + + if (pev->dmgtime + RANDOM_FLOAT( 0, 5 ) > gpGlobals->time) + { + Vector vecSrc = Vector( RANDOM_FLOAT( pev->absmin.x, pev->absmax.x ), RANDOM_FLOAT( pev->absmin.y, pev->absmax.y ), 0 ); + if (m_iOrientation == 0) + vecSrc = vecSrc + Vector( 0, 0, RANDOM_FLOAT( pev->origin.z, pev->absmax.z ) ); + else + vecSrc = vecSrc + Vector( 0, 0, RANDOM_FLOAT( pev->absmin.z, pev->origin.z ) ); + + UTIL_Sparks( vecSrc ); + } + + if (m_fSequenceFinished && !MoveTurret( ) && pev->dmgtime + 5 < gpGlobals->time) + { + pev->framerate = 0; + SetThink( NULL ); + } +} + + + +void CBaseTurret :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if ( ptr->iHitgroup == 10 ) + { + // hit armor + if ( pev->dmgtime != gpGlobals->time || (RANDOM_LONG(0,10) < 1) ) + { + UTIL_Ricochet( ptr->vecEndPos, RANDOM_FLOAT( 1, 2) ); + pev->dmgtime = gpGlobals->time; + } + + flDamage = 0.1;// don't hurt the monster much, but allow bits_COND_LIGHT_DAMAGE to be generated + } + + if ( !pev->takedamage ) + return; + + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); +} + +// take damage. bitsDamageType indicates type of damage sustained, ie: DMG_BULLET + +int CBaseTurret::TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType) +{ + if ( !pev->takedamage ) + return 0; + + if (!m_iOn) + flDamage /= 10.0; + + pev->health -= flDamage; + if (pev->health <= 0) + { + pev->health = 0; + pev->takedamage = DAMAGE_NO; + pev->dmgtime = gpGlobals->time; + + ClearBits (pev->flags, FL_MONSTER); // why are they set in the first place??? + + SetUse(NULL); + SetThink(&CBaseTurret::TurretDeath); + SUB_UseTargets( this, USE_ON, 0 ); // wake up others + SetNextThink( 0.1 ); + + return 0; + } + + if (pev->health <= 10) + { + if (m_iOn && (1 || RANDOM_LONG(0, 0x7FFF) > 800)) + { + m_fBeserk = 1; + SetThink(&CBaseTurret::SearchThink); + } + } + + return 1; +} + +int CBaseTurret::MoveTurret(void) +{ + int state = 0; + // any x movement? + + if (m_vecCurAngles.x != m_vecGoalAngles.x) + { + float flDir = m_vecGoalAngles.x > m_vecCurAngles.x ? 1 : -1 ; + + m_vecCurAngles.x += 0.1 * m_fTurnRate * flDir; + + // if we started below the goal, and now we're past, peg to goal + if (flDir == 1) + { + if (m_vecCurAngles.x > m_vecGoalAngles.x) + m_vecCurAngles.x = m_vecGoalAngles.x; + } + else + { + if (m_vecCurAngles.x < m_vecGoalAngles.x) + m_vecCurAngles.x = m_vecGoalAngles.x; + } + + if (m_iOrientation == 0) + SetBoneController(1, -m_vecCurAngles.x); + else + SetBoneController(1, m_vecCurAngles.x); + state = 1; + } + + if (m_vecCurAngles.y != m_vecGoalAngles.y) + { + float flDir = m_vecGoalAngles.y > m_vecCurAngles.y ? 1 : -1 ; + float flDist = fabs(m_vecGoalAngles.y - m_vecCurAngles.y); + + if (flDist > 180) + { + flDist = 360 - flDist; + flDir = -flDir; + } + if (flDist > 30) + { + if (m_fTurnRate < m_iBaseTurnRate * 10) + { + m_fTurnRate += m_iBaseTurnRate; + } + } + else if (m_fTurnRate > 45) + { + m_fTurnRate -= m_iBaseTurnRate; + } + else + { + m_fTurnRate += m_iBaseTurnRate; + } + + m_vecCurAngles.y += 0.1 * m_fTurnRate * flDir; + + if (m_vecCurAngles.y < 0) + m_vecCurAngles.y += 360; + else if (m_vecCurAngles.y >= 360) + m_vecCurAngles.y -= 360; + + if (flDist < (0.05 * m_iBaseTurnRate)) + m_vecCurAngles.y = m_vecGoalAngles.y; + + //ALERT(at_console, "%.2f -> %.2f\n", m_vecCurAngles.y, y); + if (m_iOrientation == 0) + SetBoneController(0, m_vecCurAngles.y - pev->angles.y ); + else + SetBoneController(0, pev->angles.y - 180 - m_vecCurAngles.y ); + state = 1; + } + + if (!state) + m_fTurnRate = m_iBaseTurnRate; + + //ALERT(at_console, "(%.2f, %.2f)->(%.2f, %.2f)\n", m_vecCurAngles.x, + // m_vecCurAngles.y, m_vecGoalAngles.x, m_vecGoalAngles.y); + return state; +} + +// +// ID as a machine +// +int CBaseTurret::Classify ( void ) +{ + if (m_iClass) return m_iClass; + if (m_iOn || m_iAutoStart) + return CLASS_MACHINE; + return CLASS_NONE; +} + + + + +//========================================================= +// Sentry gun - smallest turret, placed near grunt entrenchments +//========================================================= +class CSentry : public CBaseTurret +{ +public: + void Spawn( ); + void Precache(void); + // other functions + void Shoot(Vector &vecSrc, Vector &vecDirToEnemy); + int TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType); + void EXPORT SentryTouch( CBaseEntity *pOther ); + void EXPORT SentryDeath( void ); + +}; + +LINK_ENTITY_TO_CLASS( monster_sentry, CSentry ); + +void CSentry::Precache() +{ + CBaseTurret::Precache( ); + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL ("models/sentry.mdl"); +} + +void CSentry::Spawn() +{ + Precache( ); + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/sentry.mdl"); + if (!pev->health) //LRC + pev->health = gSkillData.sentryHealth; + m_HackedGunPos = Vector( 0, 0, 48 ); + pev->view_ofs.z = 48; + //m_flMaxWait = 1E6; + if (m_flMaxWait == 0) m_flMaxWait = TURRET_MAXWAIT;//G-Cont. now sentry can deployed + m_flMaxSpin = 1E6; + + CBaseTurret::Spawn(); + m_iRetractHeight = 64; + m_iDeployHeight = 64; + m_iMinPitch = -60; + UTIL_SetSize(pev, Vector(-16, -16, -m_iRetractHeight), Vector(16, 16, m_iRetractHeight)); + + SetTouch(&CSentry::SentryTouch); + SetThink(&CSentry::Initialize); + SetNextThink( 0.3 ); +} + +void CSentry::Shoot(Vector &vecSrc, Vector &vecDirToEnemy) +{ + FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, BULLET_NORMAL, gSkillData.monDmgMP5 ); + + switch(RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks3.wav", 1, ATTN_NORM); break; + } + pev->effects = pev->effects | EF_MUZZLEFLASH; +} + +int CSentry::TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType) +{ + if ( !pev->takedamage ) + return 0; + + if (!m_iOn) + { + SetThink(&CSentry:: Deploy ); + SetUse( NULL ); + SetNextThink( 0.1 ); + } + + pev->health -= flDamage; + if (pev->health <= 0) + { + pev->health = 0; + pev->takedamage = DAMAGE_NO; + pev->dmgtime = gpGlobals->time; + + ClearBits (pev->flags, FL_MONSTER); // why are they set in the first place??? + + SetUse(NULL); + SetThink(&CSentry::SentryDeath); + SUB_UseTargets( this, USE_ON, 0 ); // wake up others + SetNextThink( 0.1 ); + + return 0; + } + + return 1; +} + + +void CSentry::SentryTouch( CBaseEntity *pOther ) +{ + if ( pOther && (pOther->IsPlayer() || (pOther->pev->flags & FL_MONSTER)) ) + { + TakeDamage(pOther->pev, pOther->pev, 0, 0 ); + } +} + + +void CSentry :: SentryDeath( void ) +{ + BOOL iActive = FALSE; + + StudioFrameAdvance( ); + SetNextThink( 0.1 ); + + if (pev->deadflag != DEAD_DEAD) + { + pev->deadflag = DEAD_DEAD; + + float flRndSound = RANDOM_FLOAT ( 0 , 1 ); + + if ( flRndSound <= 0.33 ) + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die.wav", 1.0, ATTN_NORM); + else if ( flRndSound <= 0.66 ) + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die2.wav", 1.0, ATTN_NORM); + else + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die3.wav", 1.0, ATTN_NORM); + + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "turret/tu_active2.wav", 0, 0, SND_STOP, 100); + + SetBoneController( 0, 0 ); + SetBoneController( 1, 0 ); + + SetTurretAnim(TURRET_ANIM_DIE); + + pev->solid = SOLID_NOT; + pev->angles.y = UTIL_AngleMod( pev->angles.y + RANDOM_LONG( 0, 2 ) * 120 ); + + EyeOn( ); + } + + EyeOff( ); + + Vector vecSrc, vecAng; + GetAttachment( 1, vecSrc, vecAng ); + + if (pev->dmgtime + RANDOM_FLOAT( 0, 2 ) > gpGlobals->time) + { + // lots of smoke + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSrc.x + RANDOM_FLOAT( -16, 16 ) ); + WRITE_COORD( vecSrc.y + RANDOM_FLOAT( -16, 16 ) ); + WRITE_COORD( vecSrc.z - 32 ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 15 ); // scale * 10 + WRITE_BYTE( 8 ); // framerate + MESSAGE_END(); + } + + if (pev->dmgtime + RANDOM_FLOAT( 0, 8 ) > gpGlobals->time) + { + UTIL_Sparks( vecSrc ); + } + + if (m_fSequenceFinished && pev->dmgtime + 5 < gpGlobals->time) + { + pev->framerate = 0; + SetThink( NULL ); + } +} + diff --git a/dlls/util.cpp b/dlls/util.cpp new file mode 100644 index 0000000..708c234 --- /dev/null +++ b/dlls/util.cpp @@ -0,0 +1,3261 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== util.cpp ======================================================== + + Utility code. Really not optional after all. + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include +#include "shake.h" +#include "decals.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "movewith.h" +#include "locus.h" +#include +#include "render_api.h" + +void Msg( const char *szText, ... ) +{ + va_list szCommand; + static char value[1024]; + + va_start( szCommand, szText ); + Q_vsnprintf( value, sizeof( value ), szText, szCommand ); + va_end( szCommand ); + + g_engfuncs.pfnAlertMessage( at_console, value ); +} + +float UTIL_WeaponTimeBase( void ) +{ + return gpGlobals->time; +} + +static unsigned int glSeed = 0; + +unsigned int seed_table[ 256 ] = +{ + 28985, 27138, 26457, 9451, 17764, 10909, 28790, 8716, 6361, 4853, 17798, 21977, 19643, 20662, 10834, 20103, + 27067, 28634, 18623, 25849, 8576, 26234, 23887, 18228, 32587, 4836, 3306, 1811, 3035, 24559, 18399, 315, + 26766, 907, 24102, 12370, 9674, 2972, 10472, 16492, 22683, 11529, 27968, 30406, 13213, 2319, 23620, 16823, + 10013, 23772, 21567, 1251, 19579, 20313, 18241, 30130, 8402, 20807, 27354, 7169, 21211, 17293, 5410, 19223, + 10255, 22480, 27388, 9946, 15628, 24389, 17308, 2370, 9530, 31683, 25927, 23567, 11694, 26397, 32602, 15031, + 18255, 17582, 1422, 28835, 23607, 12597, 20602, 10138, 5212, 1252, 10074, 23166, 19823, 31667, 5902, 24630, + 18948, 14330, 14950, 8939, 23540, 21311, 22428, 22391, 3583, 29004, 30498, 18714, 4278, 2437, 22430, 3439, + 28313, 23161, 25396, 13471, 19324, 15287, 2563, 18901, 13103, 16867, 9714, 14322, 15197, 26889, 19372, 26241, + 31925, 14640, 11497, 8941, 10056, 6451, 28656, 10737, 13874, 17356, 8281, 25937, 1661, 4850, 7448, 12744, + 21826, 5477, 10167, 16705, 26897, 8839, 30947, 27978, 27283, 24685, 32298, 3525, 12398, 28726, 9475, 10208, + 617, 13467, 22287, 2376, 6097, 26312, 2974, 9114, 21787, 28010, 4725, 15387, 3274, 10762, 31695, 17320, + 18324, 12441, 16801, 27376, 22464, 7500, 5666, 18144, 15314, 31914, 31627, 6495, 5226, 31203, 2331, 4668, + 12650, 18275, 351, 7268, 31319, 30119, 7600, 2905, 13826, 11343, 13053, 15583, 30055, 31093, 5067, 761, + 9685, 11070, 21369, 27155, 3663, 26542, 20169, 12161, 15411, 30401, 7580, 31784, 8985, 29367, 20989, 14203, + 29694, 21167, 10337, 1706, 28578, 887, 3373, 19477, 14382, 675, 7033, 15111, 26138, 12252, 30996, 21409, + 25678, 18555, 13256, 23316, 22407, 16727, 991, 9236, 5373, 29402, 6117, 15241, 27715, 19291, 19888, 19847 +}; + +unsigned int U_Random( void ) +{ + glSeed *= 69069; + glSeed += seed_table[ glSeed & 0xff ]; + + return ( ++glSeed & 0x0fffffff ); +} + +void U_Srand( unsigned int seed ) +{ + glSeed = seed_table[ seed & 0xff ]; +} + +/* +===================== +UTIL_SharedRandomLong +===================== +*/ +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ) +{ + unsigned int range; + + U_Srand( (int)seed + low + high ); + + range = high - low + 1; + if ( !(range - 1) ) + { + return low; + } + else + { + int offset; + int rnum; + + rnum = U_Random(); + + offset = rnum % range; + + return (low + offset); + } +} + +/* +===================== +UTIL_SharedRandomFloat +===================== +*/ +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ) +{ + // + unsigned int range; + + U_Srand( (int)seed + *(int *)&low + *(int *)&high ); + + U_Random(); + U_Random(); + + range = high - low; + if ( !range ) + { + return low; + } + else + { + int tensixrand; + float offset; + + tensixrand = U_Random() & 65535; + + offset = (float)tensixrand / 65536.0; + + return (low + offset * range ); + } +} + +void UTIL_ParametricRocket( entvars_t *pev, Vector vecOrigin, Vector vecAngles, edict_t *owner ) +{ + pev->startpos = vecOrigin; + // Trace out line to end pos + TraceResult tr; + UTIL_MakeVectors( vecAngles ); + UTIL_TraceLine( pev->startpos, pev->startpos + gpGlobals->v_forward * 8192, ignore_monsters, owner, &tr); + pev->endpos = tr.vecEndPos; + + // Now compute how long it will take based on current velocity + Vector vecTravel = pev->endpos - pev->startpos; + float travelTime = 0.0; + if ( pev->velocity.Length() > 0 ) + { + travelTime = vecTravel.Length() / pev->velocity.Length(); + } + pev->starttime = gpGlobals->time; + pev->impacttime = gpGlobals->time + travelTime; +} + +int g_groupmask = 0; +int g_groupop = 0; + +// Normal overrides +void UTIL_SetGroupTrace( int groupmask, int op ) +{ + g_groupmask = groupmask; + g_groupop = op; + + ENGINE_SETGROUPMASK( g_groupmask, g_groupop ); +} + +void UTIL_UnsetGroupTrace( void ) +{ + g_groupmask = 0; + g_groupop = 0; + + ENGINE_SETGROUPMASK( 0, 0 ); +} + +// Smart version, it'll clean itself up when it pops off stack +UTIL_GroupTrace::UTIL_GroupTrace( int groupmask, int op ) +{ + m_oldgroupmask = g_groupmask; + m_oldgroupop = g_groupop; + + g_groupmask = groupmask; + g_groupop = op; + + ENGINE_SETGROUPMASK( g_groupmask, g_groupop ); +} + +UTIL_GroupTrace::~UTIL_GroupTrace( void ) +{ + g_groupmask = m_oldgroupmask; + g_groupop = m_oldgroupop; + + ENGINE_SETGROUPMASK( g_groupmask, g_groupop ); +} + +TYPEDESCRIPTION gEntvarsDescription[] = +{ + DEFINE_ENTITY_FIELD( classname, FIELD_STRING ), + DEFINE_ENTITY_GLOBAL_FIELD( globalname, FIELD_STRING ), + + DEFINE_ENTITY_FIELD( origin, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_FIELD( oldorigin, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_FIELD( velocity, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( basevelocity, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( movedir, FIELD_VECTOR ), + + DEFINE_ENTITY_FIELD( angles, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( avelocity, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( punchangle, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( v_angle, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( fixangle, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( idealpitch, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( pitch_speed, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( ideal_yaw, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( yaw_speed, FIELD_FLOAT ), + + DEFINE_ENTITY_FIELD( modelindex, FIELD_INTEGER ), + DEFINE_ENTITY_GLOBAL_FIELD( model, FIELD_MODELNAME ), + + DEFINE_ENTITY_FIELD( viewmodel, FIELD_MODELNAME ), + DEFINE_ENTITY_FIELD( weaponmodel, FIELD_MODELNAME ), + DEFINE_ENTITY_FIELD( weaponanim, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( absmin, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_FIELD( absmax, FIELD_POSITION_VECTOR ), + DEFINE_ENTITY_GLOBAL_FIELD( mins, FIELD_VECTOR ), + DEFINE_ENTITY_GLOBAL_FIELD( maxs, FIELD_VECTOR ), + DEFINE_ENTITY_GLOBAL_FIELD( size, FIELD_VECTOR ), + + DEFINE_ENTITY_FIELD( ltime, FIELD_TIME ), + DEFINE_ENTITY_FIELD( nextthink, FIELD_TIME ), + + DEFINE_ENTITY_FIELD( solid, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( movetype, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( skin, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( body, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( effects, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( gravity, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( friction, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( light_level, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( frame, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( scale, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( sequence, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( animtime, FIELD_TIME ), + DEFINE_ENTITY_FIELD( framerate, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( controller, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( blending, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( rendermode, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( renderamt, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( rendercolor, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( renderfx, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( health, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( frags, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( weapons, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( takedamage, FIELD_FLOAT ), + + DEFINE_ENTITY_FIELD( deadflag, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( view_ofs, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( button, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( impulse, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( chain, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( dmg_inflictor, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( enemy, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( aiment, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( owner, FIELD_EDICT ), + DEFINE_ENTITY_FIELD( groundentity, FIELD_EDICT ), + + DEFINE_ENTITY_FIELD( spawnflags, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( flags, FIELD_FLOAT ), + + DEFINE_ENTITY_FIELD( colormap, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( team, FIELD_INTEGER ), + + DEFINE_ENTITY_FIELD( max_health, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( teleport_time, FIELD_TIME ), + DEFINE_ENTITY_FIELD( armortype, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( armorvalue, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( waterlevel, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( watertype, FIELD_INTEGER ), + + // Having these fields be local to the individual levels makes it easier to test those levels individually. + DEFINE_ENTITY_GLOBAL_FIELD( target, FIELD_STRING ), + DEFINE_ENTITY_GLOBAL_FIELD( targetname, FIELD_STRING ), + DEFINE_ENTITY_FIELD( netname, FIELD_STRING ), + DEFINE_ENTITY_FIELD( message, FIELD_STRING ), + + DEFINE_ENTITY_FIELD( dmg_take, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( dmg_save, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( dmg, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( dmgtime, FIELD_TIME ), + + DEFINE_ENTITY_FIELD( noise, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( noise1, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( noise2, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( noise3, FIELD_SOUNDNAME ), + DEFINE_ENTITY_FIELD( speed, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( air_finished, FIELD_TIME ), + DEFINE_ENTITY_FIELD( pain_finished, FIELD_TIME ), + DEFINE_ENTITY_FIELD( radsuit_finished, FIELD_TIME ), + DEFINE_ENTITY_FIELD( fuser1, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( fuser2, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( fuser3, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( fuser4, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( iuser1, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( iuser2, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( iuser3, FIELD_STRING ), // because we store player->handmodel + DEFINE_ENTITY_FIELD( iuser4, FIELD_INTEGER ), + DEFINE_ENTITY_FIELD( vuser1, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( vuser2, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( vuser3, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( vuser4, FIELD_VECTOR ), + DEFINE_ENTITY_FIELD( maxspeed, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( startpos, FIELD_VECTOR ), +}; + +#define ENTVARS_COUNT (sizeof(gEntvarsDescription)/sizeof(gEntvarsDescription[0])) + + +#ifdef DEBUG +edict_t *DBG_EntOfVars( const entvars_t *pev ) +{ + if (pev->pContainingEntity != NULL) + return pev->pContainingEntity; + ALERT(at_debug, "entvars_t pContainingEntity is NULL, calling into engine"); + edict_t* pent = (*g_engfuncs.pfnFindEntityByVars)((entvars_t*)pev); + if (pent == NULL) + ALERT(at_debug, "DAMN! Even the engine couldn't FindEntityByVars!"); + ((entvars_t *)pev)->pContainingEntity = pent; + return pent; +} +#endif //DEBUG + + +#ifdef DEBUG + void +DBG_AssertFunction( + BOOL fExpr, + const char* szExpr, + const char* szFile, + int szLine, + const char* szMessage) + { + if (fExpr) + return; + char szOut[512]; + if (szMessage != NULL) + sprintf(szOut, "ASSERT FAILED:\n %s \n(%s@%d)\n%s", szExpr, szFile, szLine, szMessage); + else + sprintf(szOut, "ASSERT FAILED:\n %s \n(%s@%d)", szExpr, szFile, szLine); + ALERT(at_debug, szOut); + } +#endif // DEBUG + +BOOL UTIL_GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) +{ + return g_pGameRules->GetNextBestWeapon( pPlayer, pCurrentWeapon ); +} + +// ripped this out of the engine +float UTIL_AngleMod(float a) +{ + if (a < 0) + { + a = a + 360 * ((int)(a / 360) + 1); + } + else if (a >= 360) + { + a = a - 360 * ((int)(a / 360)); + } + // a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); + return a; +} + +float UTIL_AngleDiff( float destAngle, float srcAngle ) +{ + float delta; + + delta = destAngle - srcAngle; + if ( destAngle > srcAngle ) + { + if ( delta >= 180 ) + delta -= 360; + } + else + { + if ( delta <= -180 ) + delta += 360; + } + return delta; +} + +Vector UTIL_VecToAngles( const Vector &vec ) +{ + float rgflVecOut[3]; + VEC_TO_ANGLES(vec, rgflVecOut); + return Vector(rgflVecOut); +} + +Vector UTIL_YawToVector( float yaw ) +{ + Vector ret; + + ret.z = 0; + float angle = DEG2RAD( yaw ); + SinCos( angle, &ret.y, &ret.x ); + + return ret; +} + +//LRC - pass in a normalised axis vector and a number of degrees, and this returns the corresponding +// angles value for an entity. +inline Vector UTIL_AxisRotationToAngles( const Vector &vecAxis, float flDegs ) +{ + Vector vecTemp = UTIL_AxisRotationToVec( vecAxis, flDegs ); + float rgflVecOut[3]; + //ugh, mathsy. + rgflVecOut[0] = asin(vecTemp.z) * (-180.0 / M_PI); + rgflVecOut[1] = acos(vecTemp.x) * (180.0 / M_PI); + if (vecTemp.y < 0) + rgflVecOut[1] = -rgflVecOut[1]; + rgflVecOut[2] = 0; //for now + return Vector(rgflVecOut); +} + +//LRC - as above, but returns the position of point 1 0 0 under the given rotation +Vector UTIL_AxisRotationToVec( const Vector &vecAxis, float flDegs ) +{ + float rgflVecOut[3]; + float flRads = flDegs * (M_PI / 180.0); + float c = cos(flRads); + float s = sin(flRads); + float v = vecAxis.x * (1-c); + //ugh, more maths. Thank goodness for internet geometry sites... + rgflVecOut[0] = vecAxis.x*v + c; + rgflVecOut[1] = vecAxis.y*v + vecAxis.z*s; + rgflVecOut[2] = vecAxis.z*v - vecAxis.y*s; + return Vector(rgflVecOut); +} + +// float UTIL_MoveToOrigin( edict_t *pent, const Vector vecGoal, float flDist, int iMoveType ) +void UTIL_MoveToOrigin( edict_t *pent, const Vector &vecGoal, float flDist, int iMoveType ) +{ + float rgfl[3]; + vecGoal.CopyToArray(rgfl); +// return MOVE_TO_ORIGIN ( pent, rgfl, flDist, iMoveType ); + MOVE_TO_ORIGIN ( pent, rgfl, flDist, iMoveType ); +} + + +int UTIL_EntitiesInBox( CBaseEntity **pList, int listMax, const Vector &mins, const Vector &maxs, int flagMask ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + CBaseEntity *pEntity; + int count; + + count = 0; + + if ( !pEdict ) + return count; + + for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) + { + if ( pEdict->free ) // Not in use + continue; + + if ( flagMask && !(pEdict->v.flags & flagMask) ) // Does it meet the criteria? + continue; + + if ( mins.x > pEdict->v.absmax.x || + mins.y > pEdict->v.absmax.y || + mins.z > pEdict->v.absmax.z || + maxs.x < pEdict->v.absmin.x || + maxs.y < pEdict->v.absmin.y || + maxs.z < pEdict->v.absmin.z ) + continue; + + pEntity = CBaseEntity::Instance(pEdict); + if ( !pEntity ) + continue; + + pList[ count ] = pEntity; + count++; + + if ( count >= listMax ) + return count; + } + + return count; +} + + +int UTIL_MonstersInSphere( CBaseEntity **pList, int listMax, const Vector ¢er, float radius ) +{ + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + CBaseEntity *pEntity; + int count; + float distance, delta; + + count = 0; + float radiusSquared = radius * radius; + + if ( !pEdict ) + return count; + + for ( int i = 1; i < gpGlobals->maxEntities; i++, pEdict++ ) + { + if ( pEdict->free ) // Not in use + continue; + + if ( !(pEdict->v.flags & (FL_CLIENT|FL_MONSTER)) ) // Not a client/monster ? + continue; + + // Use origin for X & Y since they are centered for all monsters + // Now X + delta = center.x - pEdict->v.origin.x;//(pEdict->v.absmin.x + pEdict->v.absmax.x)*0.5; + delta *= delta; + + if ( delta > radiusSquared ) + continue; + distance = delta; + + // Now Y + delta = center.y - pEdict->v.origin.y;//(pEdict->v.absmin.y + pEdict->v.absmax.y)*0.5; + delta *= delta; + + distance += delta; + if ( distance > radiusSquared ) + continue; + + // Now Z + delta = center.z - (pEdict->v.absmin.z + pEdict->v.absmax.z)*0.5; + delta *= delta; + + distance += delta; + if ( distance > radiusSquared ) + continue; + + pEntity = CBaseEntity::Instance(pEdict); + if ( !pEntity ) + continue; + + pList[ count ] = pEntity; + count++; + + if ( count >= listMax ) + return count; + } + + + return count; +} + + +CBaseEntity *UTIL_FindEntityInSphere( CBaseEntity *pStartEntity, const Vector &vecCenter, float flRadius ) +{ + edict_t *pentEntity; + + if (pStartEntity) + pentEntity = pStartEntity->edict(); + else + pentEntity = NULL; + + pentEntity = FIND_ENTITY_IN_SPHERE( pentEntity, vecCenter, flRadius); + + if (!FNullEnt(pentEntity)) + return CBaseEntity::Instance(pentEntity); + return NULL; +} + + +CBaseEntity *UTIL_FindEntityByString( CBaseEntity *pStartEntity, const char *szKeyword, const char *szValue ) +{ + edict_t *pentEntity; + CBaseEntity *pEntity; + + if (pStartEntity) + pentEntity = pStartEntity->edict(); + else + pentEntity = NULL; + + for (;;) + { + // Don't change this to use UTIL_FindEntityByString! + pentEntity = FIND_ENTITY_BY_STRING( pentEntity, szKeyword, szValue ); + + // if pentEntity (the edict) is null, we're at the end of the entities. Give up. + if (FNullEnt(pentEntity)) + { + return NULL; + } + else + { + // ...but if only pEntity (the classptr) is null, we've just got one dud, so we try again. + pEntity = CBaseEntity::Instance(pentEntity); + if (pEntity) + return pEntity; + } + } +} + +CBaseEntity *UTIL_FindEntityByClassname( CBaseEntity *pStartEntity, const char *szName ) +{ + return UTIL_FindEntityByString( pStartEntity, "classname", szName ); +} + +#define MAX_ALIASNAME_LEN 80 + +//LRC - things get messed up if aliases change in the middle of an entity traversal. +// so instead, they record what changes should be made, and wait until this function gets +// called. +void UTIL_FlushAliases( void ) +{ +// ALERT(at_console, "Flushing alias list\n"); + if (!g_pWorld) + { + ALERT(at_debug, "FlushAliases has no AliasList!\n"); + return; + } + + while (g_pWorld->m_pFirstAlias) + { + if (g_pWorld->m_pFirstAlias->m_iLFlags & LF_ALIASLIST) + { +// ALERT(at_console, "call FlushChanges for %s \"%s\"\n", STRING(g_pWorld->m_pFirstAlias->pev->classname), STRING(g_pWorld->m_pFirstAlias->pev->targetname)); + g_pWorld->m_pFirstAlias->FlushChanges(); + g_pWorld->m_pFirstAlias->m_iLFlags &= ~LF_ALIASLIST; + } + g_pWorld->m_pFirstAlias = g_pWorld->m_pFirstAlias->m_pNextAlias; + } +} + +void UTIL_AddToAliasList( CBaseAlias *pAlias ) +{ + if (!g_pWorld) + { + ALERT(at_debug, "AddToAliasList has no AliasList!\n"); + return; + } + + pAlias->m_iLFlags |= LF_ALIASLIST; + +// ALERT(at_console, "Adding %s \"%s\" to alias list\n", STRING(pAlias->pev->classname), STRING(pAlias->pev->targetname)); + if (g_pWorld->m_pFirstAlias == NULL) + { + g_pWorld->m_pFirstAlias = pAlias; + pAlias->m_pNextAlias = NULL; + } + else if (g_pWorld->m_pFirstAlias == pAlias) + { + // already in the list + return; + } + else + { + CBaseAlias *pCurrent = g_pWorld->m_pFirstAlias; + while (pCurrent->m_pNextAlias != NULL) + { + if (pCurrent->m_pNextAlias == pAlias) + { + // already in the list + return; + } + pCurrent = pCurrent->m_pNextAlias; + } + pCurrent->m_pNextAlias = pAlias; + pAlias->m_pNextAlias = NULL; + } +} + +// for every alias which has the given name, find the earliest entity which any of them refers to +// and which is later than pStartEntity. +CBaseEntity *UTIL_FollowAliasReference(CBaseEntity *pStartEntity, const char* szValue) +{ + CBaseEntity* pEntity; + CBaseEntity* pBestEntity = NULL; // the entity we're currently planning to return. + int iBestOffset = -1; // the offset of that entity. + CBaseEntity* pTempEntity; + int iTempOffset; + + pEntity = UTIL_FindEntityByTargetname(NULL,szValue); + + while ( pEntity ) + { + if (pEntity->IsAlias()) + { + pTempEntity = ((CBaseAlias*)pEntity)->FollowAlias( pStartEntity ); + if ( pTempEntity ) + { + // We've found an entity; only use it if its offset is lower than the offset we've currently got. + iTempOffset = OFFSET(pTempEntity->pev); + if (iBestOffset == -1 || iTempOffset < iBestOffset) + { + iBestOffset = iTempOffset; + pBestEntity = pTempEntity; + } + } + } + pEntity = UTIL_FindEntityByTargetname(pEntity,szValue); + } + + return pBestEntity; +} + +// for every info_group which has the given groupname, find the earliest entity which is referred to by its member +// with the given membername and which is later than pStartEntity. +CBaseEntity *UTIL_FollowGroupReference(CBaseEntity *pStartEntity, char* szGroupName, char* szMemberName) +{ + CBaseEntity* pEntity; + CBaseEntity* pBestEntity = NULL; // the entity we're currently planning to return. + int iBestOffset = -1; // the offset of that entity. + CBaseEntity* pTempEntity; + int iTempOffset; + char szBuf[MAX_ALIASNAME_LEN]; + char* szThisMember = szMemberName; + char* szTail = NULL; + int iszMemberValue; + int i; + + // find the first '.' in the membername and if there is one, split the string at that point. + for (i = 0; szMemberName[i]; i++) + { + if (szMemberName[i] == '.') + { + // recursive member-reference + // FIXME: we should probably check that i < MAX_ALIASNAME_LEN. + strncpy(szBuf,szMemberName,i); + szBuf[i] = 0; + szTail = &(szMemberName[i+1]); + szThisMember = szBuf; + break; + } + } + + pEntity = UTIL_FindEntityByTargetname(NULL,szGroupName); + while ( pEntity ) + { + if (FStrEq(STRING(pEntity->pev->classname), "info_group")) + { + iszMemberValue = ((CInfoGroup*)pEntity)->GetMember(szThisMember); +// ALERT(at_console,"survived getMember\n"); +// return NULL; + if (!FStringNull(iszMemberValue)) + { + if (szTail) // do we have more references to follow? + pTempEntity = UTIL_FollowGroupReference(pStartEntity, (char*)STRING(iszMemberValue), szTail); + else + pTempEntity = UTIL_FindEntityByTargetname(pStartEntity,STRING(iszMemberValue)); + + if ( pTempEntity ) + { + iTempOffset = OFFSET(pTempEntity->pev); + if (iBestOffset == -1 || iTempOffset < iBestOffset) + { + iBestOffset = iTempOffset; + pBestEntity = pTempEntity; + } + } + } + } + pEntity = UTIL_FindEntityByTargetname(pEntity,szGroupName); + } + + if (pBestEntity) + { +// ALERT(at_console,"\"%s\".\"%s\" returns %s\n",szGroupName,szMemberName,STRING(pBestEntity->pev->targetname)); + return pBestEntity; + } + return NULL; +} + +// Returns the first entity which szName refers to and which is after pStartEntity. +CBaseEntity *UTIL_FollowReference( CBaseEntity *pStartEntity, const char* szName ) +{ + char szRoot[MAX_ALIASNAME_LEN+1]; // allow room for null-terminator + char* szMember; + int i; + CBaseEntity *pResult; + + if (!szName || szName[0] == 0) return NULL; + + // reference through an info_group? + for (i = 0; szName[i]; i++) + { + if (szName[i] == '.') + { + // yes, it looks like a reference through an info_group... + // FIXME: we should probably check that i < MAX_ALIASNAME_LEN. + strncpy(szRoot,szName,i); + szRoot[i] = 0; + szMember = (char*)&szName[i+1]; + //ALERT(at_console,"Following reference- group %s with member %s\n",szRoot,szMember); + pResult = UTIL_FollowGroupReference(pStartEntity, szRoot, szMember); +// if (pResult) +// ALERT(at_console,"\"%s\".\"%s\" = %s\n",szRoot,szMember,STRING(pResult->pev->targetname)); + return pResult; + } + } + // reference through an info_alias? + if ( szName[0] == '*' ) + { + if (FStrEq(szName, "*player")) + { + CBaseEntity* pPlayer = UTIL_FindEntityByClassname(NULL, "player"); + if (pPlayer && (pStartEntity == NULL || pPlayer->eoffset() > pStartEntity->eoffset())) + return pPlayer; + else + return NULL; + } + //ALERT(at_console,"Following alias %s\n",szName+1); + pResult = UTIL_FollowAliasReference( pStartEntity, szName+1 ); +// if (pResult) +// ALERT(at_console,"alias \"%s\" = %s\n",szName+1,STRING(pResult->pev->targetname)); + return pResult; + } + // not a reference +// ALERT(at_console,"%s is not a reference\n",szName); + return NULL; +} + +CBaseEntity *UTIL_FindEntityByTargetname( CBaseEntity *pStartEntity, const char *szName ) +{ + CBaseEntity *pFound = UTIL_FollowReference( pStartEntity, szName ); + if (pFound) + return pFound; + else + return UTIL_FindEntityByString( pStartEntity, "targetname", szName ); +} + +CBaseEntity *UTIL_FindEntityByTargetname( CBaseEntity *pStartEntity, const char *szName, CBaseEntity *pActivator ) +{ + if (FStrEq(szName, "*locus")) + { + if (pActivator && (pStartEntity == NULL || pActivator->eoffset() > pStartEntity->eoffset())) + return pActivator; + else + return NULL; + } + else + return UTIL_FindEntityByTargetname( pStartEntity, szName ); +} + +CBaseEntity *UTIL_FindEntityByTarget( CBaseEntity *pStartEntity, const char *szName ) +{ + return UTIL_FindEntityByString( pStartEntity, "target", szName ); +} + +CBaseEntity *UTIL_FindEntityGeneric( const char *szWhatever, Vector &vecSrc, float flRadius ) +{ + CBaseEntity *pEntity = NULL; + + pEntity = UTIL_FindEntityByTargetname( NULL, szWhatever ); + if (pEntity) + return pEntity; + + CBaseEntity *pSearch = NULL; + float flMaxDist2 = flRadius * flRadius; + while ((pSearch = UTIL_FindEntityByClassname( pSearch, szWhatever )) != NULL) + { + float flDist2 = (pSearch->pev->origin - vecSrc).Length(); + flDist2 = flDist2 * flDist2; + if (flMaxDist2 > flDist2) + { + pEntity = pSearch; + flMaxDist2 = flDist2; + } + } + return pEntity; +} + + +// returns a CBaseEntity pointer to a player by index. Only returns if the player is spawned and connected +// otherwise returns NULL +// Index is 1 based +CBaseEntity *UTIL_PlayerByIndex( int playerIndex ) +{ + CBaseEntity *pPlayer = NULL; + + if ( playerIndex > 0 && playerIndex <= gpGlobals->maxClients ) + { + edict_t *pPlayerEdict = INDEXENT( playerIndex ); + if ( pPlayerEdict && !pPlayerEdict->free ) + { + pPlayer = CBaseEntity::Instance( pPlayerEdict ); + } + } + + return pPlayer; +} + + +void UTIL_MakeVectors( const Vector &vecAngles ) +{ + MAKE_VECTORS( vecAngles ); +} + + +void UTIL_MakeAimVectors( const Vector &vecAngles ) +{ + float rgflVec[3]; + vecAngles.CopyToArray(rgflVec); + rgflVec[0] = -rgflVec[0]; + MAKE_VECTORS(rgflVec); +} + + +#define SWAP(a,b,temp) ((temp)=(a),(a)=(b),(b)=(temp)) + +void UTIL_MakeInvVectors( const Vector &vec, globalvars_t *pgv ) +{ + MAKE_VECTORS(vec); + + float tmp; + pgv->v_right = pgv->v_right * -1; + + SWAP(pgv->v_forward.y, pgv->v_right.x, tmp); + SWAP(pgv->v_forward.z, pgv->v_up.x, tmp); + SWAP(pgv->v_right.z, pgv->v_up.y, tmp); +} + + +void UTIL_EmitAmbientSound( edict_t *entity, const Vector &vecOrigin, const char *samp, float vol, float attenuation, int fFlags, int pitch ) +{ + float rgfl[3]; + vecOrigin.CopyToArray(rgfl); + + if (samp && *samp == '!') + { + char name[32]; + if (SENTENCEG_Lookup(samp, name) >= 0) + EMIT_AMBIENT_SOUND(entity, rgfl, name, vol, attenuation, fFlags, pitch); + + // buz: send sencences as text messages to lookup subtitles in titles.txt + UTIL_ShowMessagePVS( samp, vecOrigin ); + } + else + EMIT_AMBIENT_SOUND(entity, rgfl, samp, vol, attenuation, fFlags, pitch); +} + +static unsigned short FixedUnsigned16( float value, float scale ) +{ + int output; + + output = value * scale; + if ( output < 0 ) + output = 0; + if ( output > 0xFFFF ) + output = 0xFFFF; + + return (unsigned short)output; +} + +static short FixedSigned16( float value, float scale ) +{ + int output; + + output = value * scale; + + if ( output > 32767 ) + output = 32767; + + if ( output < -32768 ) + output = -32768; + + return (short)output; +} + +// Shake the screen of all clients within radius +// radius == 0, shake all clients +// UNDONE: Allow caller to shake clients not ONGROUND? +// UNDONE: Fix falloff model (disabled)? +// UNDONE: Affect user controls? +//LRC UNDONE: Work during trigger_camera? +void UTIL_ScreenShake( const Vector ¢er, float amplitude, float frequency, float duration, float radius ) +{ + int i; + float localAmplitude; + ScreenShake shake; + + shake.duration = FixedUnsigned16( duration, 1<<12 ); // 4.12 fixed + shake.frequency = FixedUnsigned16( frequency, 1<<8 ); // 8.8 fixed + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + if ( !pPlayer || !(pPlayer->pev->flags & FL_ONGROUND) ) // Don't shake if not onground + continue; + + localAmplitude = 0; + + if ( radius <= 0 ) + localAmplitude = amplitude; + else + { + Vector delta = center - pPlayer->pev->origin; + float distance = delta.Length(); + + // Had to get rid of this falloff - it didn't work well + if ( distance < radius ) + localAmplitude = amplitude;//radius - distance; + } + if ( localAmplitude ) + { + shake.amplitude = FixedUnsigned16( localAmplitude, 1<<12 ); // 4.12 fixed + + MESSAGE_BEGIN( MSG_ONE, gmsgShake, NULL, pPlayer->edict() ); // use the magic #1 for "one client" + + WRITE_SHORT( shake.amplitude ); // shake amount + WRITE_SHORT( shake.duration ); // shake lasts this long + WRITE_SHORT( shake.frequency ); // shake noise frequency + + MESSAGE_END(); + } + } +} + + + +void UTIL_ScreenShakeAll( const Vector ¢er, float amplitude, float frequency, float duration ) +{ + UTIL_ScreenShake( center, amplitude, frequency, duration, 0 ); +} + + +void UTIL_ScreenFadeBuild( ScreenFade &fade, const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) +{ + fade.duration = FixedUnsigned16( fadeTime, 1<<12 ); // 4.12 fixed + fade.holdTime = FixedUnsigned16( fadeHold, 1<<12 ); // 4.12 fixed + fade.r = (int)color.x; + fade.g = (int)color.y; + fade.b = (int)color.z; + fade.a = alpha; + fade.fadeFlags = flags; +} + + +void UTIL_ScreenFadeWrite( const ScreenFade &fade, CBaseEntity *pEntity ) +{ + if ( !pEntity || !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, gmsgFade, NULL, pEntity->edict() ); // use the magic #1 for "one client" + + WRITE_SHORT( fade.duration ); // fade lasts this long + WRITE_SHORT( fade.holdTime ); // fade lasts this long + WRITE_SHORT( fade.fadeFlags ); // fade type (in / out) + WRITE_BYTE( fade.r ); // fade red + WRITE_BYTE( fade.g ); // fade green + WRITE_BYTE( fade.b ); // fade blue + WRITE_BYTE( fade.a ); // fade blue + + MESSAGE_END(); +} + + +void UTIL_ScreenFadeAll( const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) +{ + int i; + ScreenFade fade; + + UTIL_ScreenFadeBuild( fade, color, fadeTime, fadeHold, alpha, flags ); + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + UTIL_ScreenFadeWrite( fade, pPlayer ); + } +} + + +void UTIL_ScreenFade( CBaseEntity *pEntity, const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ) +{ + ScreenFade fade; + + UTIL_ScreenFadeBuild( fade, color, fadeTime, fadeHold, alpha, flags ); + UTIL_ScreenFadeWrite( fade, pEntity ); +} + + +void UTIL_HudMessage( CBaseEntity *pEntity, const hudtextparms_t &textparms, const char *pMessage ) +{ + if ( !pEntity || !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, SVC_TEMPENTITY, NULL, pEntity->edict() ); + WRITE_BYTE( TE_TEXTMESSAGE ); + WRITE_BYTE( textparms.channel & 0xFF ); + + WRITE_SHORT( FixedSigned16( textparms.x, 1<<13 ) ); + WRITE_SHORT( FixedSigned16( textparms.y, 1<<13 ) ); + WRITE_BYTE( textparms.effect ); + + WRITE_BYTE( textparms.r1 ); + WRITE_BYTE( textparms.g1 ); + WRITE_BYTE( textparms.b1 ); + WRITE_BYTE( textparms.a1 ); + + WRITE_BYTE( textparms.r2 ); + WRITE_BYTE( textparms.g2 ); + WRITE_BYTE( textparms.b2 ); + WRITE_BYTE( textparms.a2 ); + + WRITE_SHORT( FixedUnsigned16( textparms.fadeinTime, 1<<8 ) ); + WRITE_SHORT( FixedUnsigned16( textparms.fadeoutTime, 1<<8 ) ); + WRITE_SHORT( FixedUnsigned16( textparms.holdTime, 1<<8 ) ); + + if ( textparms.effect == 2 ) + WRITE_SHORT( FixedUnsigned16( textparms.fxTime, 1<<8 ) ); + + if ( strlen( pMessage ) < 512 ) + { + WRITE_STRING( pMessage ); + } + else + { + char tmp[512]; + strncpy( tmp, pMessage, 511 ); + tmp[511] = 0; + WRITE_STRING( tmp ); + } + MESSAGE_END(); +} + +void UTIL_HudMessageAll( const hudtextparms_t &textparms, const char *pMessage ) +{ + int i; + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + UTIL_HudMessage( pPlayer, textparms, pMessage ); + } +} + + +extern int gmsgTextMsg, gmsgSayText; +void UTIL_ClientPrintAll( int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) +{ + MESSAGE_BEGIN( MSG_ALL, gmsgTextMsg ); + WRITE_BYTE( msg_dest ); + WRITE_STRING( msg_name ); + + if ( param1 ) + WRITE_STRING( param1 ); + if ( param2 ) + WRITE_STRING( param2 ); + if ( param3 ) + WRITE_STRING( param3 ); + if ( param4 ) + WRITE_STRING( param4 ); + + MESSAGE_END(); +} + +void ClientPrint( entvars_t *client, int msg_dest, const char *msg_name, const char *param1, const char *param2, const char *param3, const char *param4 ) +{ + MESSAGE_BEGIN( MSG_ONE, gmsgTextMsg, NULL, client ); + WRITE_BYTE( msg_dest ); + WRITE_STRING( msg_name ); + + if ( param1 ) + WRITE_STRING( param1 ); + if ( param2 ) + WRITE_STRING( param2 ); + if ( param3 ) + WRITE_STRING( param3 ); + if ( param4 ) + WRITE_STRING( param4 ); + + MESSAGE_END(); +} + +void UTIL_SayText( const char *pText, CBaseEntity *pEntity ) +{ + if ( !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, gmsgSayText, NULL, pEntity->edict() ); + WRITE_BYTE( pEntity->entindex() ); + WRITE_STRING( pText ); + MESSAGE_END(); +} + +void UTIL_SayTextAll( const char *pText, CBaseEntity *pEntity ) +{ + MESSAGE_BEGIN( MSG_ALL, gmsgSayText, NULL ); + WRITE_BYTE( pEntity->entindex() ); + WRITE_STRING( pText ); + MESSAGE_END(); +} + + +char *UTIL_dtos1( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +char *UTIL_dtos2( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +char *UTIL_dtos3( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +char *UTIL_dtos4( int d ) +{ + static char buf[8]; + sprintf( buf, "%d", d ); + return buf; +} + +void UTIL_ShowMessage( const char *pString, CBaseEntity *pEntity ) +{ + if ( !pEntity || !pEntity->IsNetClient() ) + return; + + MESSAGE_BEGIN( MSG_ONE, gmsgHudText, NULL, pEntity->edict() ); + WRITE_STRING( pString ); + MESSAGE_END(); +} + + +void UTIL_ShowMessageAll( const char *pString ) +{ + int i; + + // loop through all players + + for ( i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + UTIL_ShowMessage( pString, pPlayer ); + } +} + +void UTIL_ShowMessagePVS( const char *pString, const Vector &org ) // buz +{ + // loop through all players + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer && pPlayer->IsNetClient() ) + { + // MESSAGE_BEGIN( MSG_ONE, gmsgHudText, NULL, pEntity->edict() ); + MESSAGE_BEGIN( MSG_PAS, gmsgHudText, org ); + WRITE_STRING( pString ); + MESSAGE_END(); + } + } +} + +// Overloaded to add IGNORE_GLASS +void UTIL_TraceLine( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, IGNORE_GLASS ignoreGlass, edict_t *pentIgnore, TraceResult *ptr ) +{ + TRACE_LINE( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE) | (ignoreGlass?0x100:0), pentIgnore, ptr ); +} + + +void UTIL_TraceLine( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, edict_t *pentIgnore, TraceResult *ptr ) +{ + TRACE_LINE( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE), pentIgnore, ptr ); +} + + +void UTIL_TraceHull( const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, int hullNumber, edict_t *pentIgnore, TraceResult *ptr ) +{ + TRACE_HULL( vecStart, vecEnd, (igmon == ignore_monsters ? TRUE : FALSE), hullNumber, pentIgnore, ptr ); +} + +void UTIL_TraceModel( const Vector &vecStart, const Vector &vecEnd, int hullNumber, edict_t *pentModel, TraceResult *ptr ) +{ + g_engfuncs.pfnTraceModel( vecStart, vecEnd, hullNumber, pentModel, ptr ); +} + + +TraceResult UTIL_GetGlobalTrace( ) +{ + TraceResult tr; + + tr.fAllSolid = gpGlobals->trace_allsolid; + tr.fStartSolid = gpGlobals->trace_startsolid; + tr.fInOpen = gpGlobals->trace_inopen; + tr.fInWater = gpGlobals->trace_inwater; + tr.flFraction = gpGlobals->trace_fraction; + tr.flPlaneDist = gpGlobals->trace_plane_dist; + tr.pHit = gpGlobals->trace_ent; + tr.vecEndPos = gpGlobals->trace_endpos; + tr.vecPlaneNormal = gpGlobals->trace_plane_normal; + tr.iHitgroup = gpGlobals->trace_hitgroup; + return tr; +} + + +void UTIL_SetSize( entvars_t *pev, const Vector &vecMin, const Vector &vecMax ) +{ + SET_SIZE( ENT(pev), vecMin, vecMax ); +} + + +float UTIL_VecToYaw( const Vector &vec ) +{ + return VEC_TO_YAW(vec); +} + +void UTIL_SetEdictOrigin( edict_t *pEdict, const Vector &vecOrigin ) +{ + SET_ORIGIN(pEdict, vecOrigin ); +} + +// 'links' the entity into the world +void UTIL_SetOrigin( CBaseEntity *pEntity, const Vector &vecOrigin ) +{ + SET_ORIGIN(ENT(pEntity->pev), vecOrigin ); +} + +void UTIL_ParticleEffect( const Vector &vecOrigin, const Vector &vecDirection, ULONG ulColor, ULONG ulCount ) +{ + PARTICLE_EFFECT( vecOrigin, vecDirection, (float)ulColor, (float)ulCount ); +} + +float UTIL_Approach( float target, float value, float speed ) +{ + float delta = target - value; + + if ( delta > speed ) + value += speed; + else if ( delta < -speed ) + value -= speed; + else + value = target; + + return value; +} + + +float UTIL_ApproachAngle( float target, float value, float speed ) +{ + target = UTIL_AngleMod( target ); + value = UTIL_AngleMod( target ); + + float delta = target - value; + + // Speed is assumed to be positive + if ( speed < 0 ) + speed = -speed; + + if ( delta < -180 ) + delta += 360; + else if ( delta > 180 ) + delta -= 360; + + if ( delta > speed ) + value += speed; + else if ( delta < -speed ) + value -= speed; + else + value = target; + + return value; +} + + +float UTIL_AngleDistance( float next, float cur ) +{ + float delta = next - cur; + +// LRC- correct for deltas > 360 + while ( delta < -180 ) + delta += 360; + while ( delta > 180 ) + delta -= 360; + + return delta; +} + + +float UTIL_SplineFraction( float value, float scale ) +{ + value = scale * value; + float valueSquared = value * value; + + // Nice little ease-in, ease-out spline-like curve + return 3 * valueSquared - 2 * valueSquared * value; +} + + +char* UTIL_VarArgs( char *format, ... ) +{ + va_list argptr; + static char string[1024]; + + va_start (argptr, format); + vsprintf (string, format,argptr); + va_end (argptr); + + return string; +} + +Vector UTIL_GetAimVector( edict_t *pent, float flSpeed ) +{ + Vector tmp; + GET_AIM_VECTOR(pent, flSpeed, tmp); + return tmp; +} + +BOOL UTIL_IsMasterTriggered(string_t iszMaster, CBaseEntity *pActivator) +{ + int i, j, found = false; + const char *szMaster; + char szBuf[80]; + CBaseEntity *pMaster; + int reverse = false; + + + if (iszMaster) + { +// ALERT(at_console, "IsMasterTriggered(%s, %s \"%s\")\n", STRING(iszMaster), STRING(pActivator->pev->classname), STRING(pActivator->pev->targetname)); + szMaster = STRING(iszMaster); + if (szMaster[0] == '~') //inverse master + { + reverse = true; + szMaster++; + } + + pMaster = UTIL_FindEntityByTargetname( NULL, szMaster ); + if ( !pMaster ) + { + for (i = 0; szMaster[i]; i++) + { + if (szMaster[i] == '(') + { + for (j = i+1; szMaster[j]; j++) + { + if (szMaster[j] == ')') + { + strncpy(szBuf, szMaster+i+1, (j-i)-1); + szBuf[(j-i)-1] = 0; + pActivator = UTIL_FindEntityByTargetname( NULL, szBuf ); + found = true; + break; + } + } + if (!found) // no ) found + { + ALERT(at_error, "Missing ')' in master \"%s\"\n", szMaster); + return FALSE; + } + break; + } + } + if (!found) // no ( found + { + ALERT(at_debug, "Master \"%s\" not found!\n",szMaster); + return TRUE; + } + + strncpy(szBuf, szMaster, i); + szBuf[i] = 0; + pMaster = UTIL_FindEntityByTargetname( NULL, szBuf ); + } + + if (pMaster) + { + if (reverse) + return (pMaster->GetState( pActivator ) != STATE_ON); + else + return (pMaster->GetState( pActivator ) == STATE_ON); + } + } + + // if the entity has no master (or the master is missing), just say yes. + return TRUE; +} + +BOOL UTIL_ShouldShowBlood( int color ) +{ + if ( color != DONT_BLEED ) + { + if ( color == BLOOD_COLOR_RED ) + { + if ( CVAR_GET_FLOAT("violence_hblood") != 0 ) + return TRUE; + } + else + { + if ( CVAR_GET_FLOAT("violence_ablood") != 0 ) + return TRUE; + } + } + return FALSE; +} + +int UTIL_PointContents( const Vector &vec ) +{ + return POINT_CONTENTS(vec); +} + +void UTIL_BloodStream( const Vector &origin, const Vector &direction, int color, int amount ) +{ + if ( !UTIL_ShouldShowBlood( color ) ) + return; + + if ( g_Language == LANGUAGE_GERMAN && color == BLOOD_COLOR_RED ) + color = 0; + + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, origin ); + WRITE_BYTE( TE_BLOODSTREAM ); + WRITE_COORD( origin.x ); + WRITE_COORD( origin.y ); + WRITE_COORD( origin.z ); + WRITE_COORD( direction.x ); + WRITE_COORD( direction.y ); + WRITE_COORD( direction.z ); + WRITE_BYTE( color ); + WRITE_BYTE( min( amount, 255 ) ); + MESSAGE_END(); +} + +void UTIL_BloodDrips( const Vector &origin, const Vector &direction, int color, int amount ) +{ + if ( !UTIL_ShouldShowBlood( color ) ) + return; + + if ( color == DONT_BLEED || amount == 0 ) + return; + + if ( g_Language == LANGUAGE_GERMAN && color == BLOOD_COLOR_RED ) + color = 0; + + if ( g_pGameRules->IsMultiplayer() ) + { + // scale up blood effect in multiplayer for better visibility + amount *= 2; + } + + if ( amount > 255 ) + amount = 255; + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, origin ); + WRITE_BYTE( TE_BLOODSPRITE ); + WRITE_COORD( origin.x); // pos + WRITE_COORD( origin.y); + WRITE_COORD( origin.z); + WRITE_SHORT( g_sModelIndexBloodSpray ); // initial sprite model + WRITE_SHORT( g_sModelIndexBloodDrop ); // droplet sprite models + WRITE_BYTE( color ); // color index into host_basepal + WRITE_BYTE( min( max( 3, amount / 10 ), 16 ) ); // size + MESSAGE_END(); +} + +Vector UTIL_RandomBloodVector( void ) +{ + Vector direction; + + direction.x = RANDOM_FLOAT ( -1, 1 ); + direction.y = RANDOM_FLOAT ( -1, 1 ); + direction.z = RANDOM_FLOAT ( 0, 1 ); + + return direction; +} + +void UTIL_DecalTrace( TraceResult *pTrace, const char *decalName ) +{ + short entityIndex; + int index; + int message; + + index = DECAL_INDEX( decalName ); + + if( index < 0 ) + return; + + if (pTrace->flFraction == 1.0) + return; + + // Only decal BSP models + if ( pTrace->pHit ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( pTrace->pHit ); + if ( pEntity && !pEntity->IsBSPModel() ) + return; + entityIndex = ENTINDEX( pTrace->pHit ); + } + else + entityIndex = 0; + + message = TE_DECAL; + if ( entityIndex != 0 ) + { + if ( index > 255 ) + { + message = TE_DECALHIGH; + index -= 256; + } + } + else + { + message = TE_WORLDDECAL; + if ( index > 255 ) + { + message = TE_WORLDDECALHIGH; + index -= 256; + } + } + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( message ); + WRITE_COORD( pTrace->vecEndPos.x ); + WRITE_COORD( pTrace->vecEndPos.y ); + WRITE_COORD( pTrace->vecEndPos.z ); + WRITE_BYTE( index ); + if ( entityIndex ) + WRITE_SHORT( entityIndex ); + MESSAGE_END(); +} + +// buz +BOOL UTIL_TraceCustomDecal( TraceResult *pTrace, const char *name, float angle, int persistent ) // Wargon: Çíà÷åíèå ïî óìîë÷àíèþ ïðîïèñàíî â util.h. +{ + short entityIndex; + short modelIndex = 0; + byte flags = 0; + + if( !name || !*name ) + return FALSE; + + if (pTrace->flFraction == 1.0) + return FALSE; + + // Only decal BSP models + if ( pTrace->pHit ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( pTrace->pHit ); + if ( pEntity && !pEntity->IsBSPModel() ) + return FALSE; + entityIndex = ENTINDEX( pTrace->pHit ); + } + else + entityIndex = 0; + + edict_t *pEdict = INDEXENT( entityIndex ); + if( pEdict ) modelIndex = pEdict->v.modelindex; + + if( persistent ) + flags |= FDECAL_PERMANENT; + + angle = anglemod( angle ); + + MESSAGE_BEGIN( persistent ? MSG_INIT : MSG_BROADCAST, gmsgCustomDecal ); + WRITE_COORD( pTrace->vecEndPos.x ); + WRITE_COORD( pTrace->vecEndPos.y ); + WRITE_COORD( pTrace->vecEndPos.z ); + WRITE_COORD( pTrace->vecPlaneNormal.x * 8192 ); + WRITE_COORD( pTrace->vecPlaneNormal.y * 8192 ); + WRITE_COORD( pTrace->vecPlaneNormal.z * 8192 ); + WRITE_SHORT( entityIndex ); + WRITE_SHORT( modelIndex ); + WRITE_STRING( name ); + WRITE_BYTE( flags ); + WRITE_ANGLE( angle ); + MESSAGE_END(); + + return TRUE; +} + +void UTIL_RestoreCustomDecal( const Vector &vecPos, const Vector &vecNormal, int entityIndex, int modelIndex, const char *name, int flags, float angle ) +{ + MESSAGE_BEGIN( MSG_INIT, gmsgCustomDecal ); + WRITE_COORD( vecPos.x ); + WRITE_COORD( vecPos.y ); + WRITE_COORD( vecPos.z ); + WRITE_COORD( vecNormal.x * 8192 ); + WRITE_COORD( vecNormal.y * 8192 ); + WRITE_COORD( vecNormal.z * 8192 ); + WRITE_SHORT( entityIndex ); + WRITE_SHORT( modelIndex ); + WRITE_STRING( name ); + WRITE_BYTE( flags ); + WRITE_ANGLE( angle ); + MESSAGE_END(); +} + +BOOL UTIL_StudioDecalTrace( TraceResult *pTrace, const char *name, int flags ) +{ + short entityIndex; + + if( !name || !*name ) + return FALSE; + + if( pTrace->flFraction == 1.0f ) + return FALSE; + + CBaseEntity *pEntity; + + // Only decal Studio models + if( pTrace->pHit ) + { + pEntity = CBaseEntity::Instance( pTrace->pHit ); + + if( !pEntity || !GET_MODEL_PTR( pEntity->edict() )) + return FALSE; // not a studiomodel? + + entityIndex = ENTINDEX( pTrace->pHit ); + } + else + { + return FALSE; + } + + Vector vecNormal = pTrace->vecPlaneNormal; + Vector vecEnd = pTrace->vecEndPos; + Vector scale = Vector( 1.0f, 1.0f, 1.0f ); + + if( FBitSet( pEntity->pev->iuser1, CF_STATIC_ENTITY )) + { + if( pEntity->pev->startpos != g_vecZero ) + scale = Vector( 1.0f / pEntity->pev->startpos.x, 1.0f / pEntity->pev->startpos.y, 1.0f / pEntity->pev->startpos.z ); + } + + // studio decals is always converted into local space to avoid troubles with precision and modelscale + matrix4x4 mat = matrix4x4( pEntity->pev->origin, pEntity->pev->angles, scale ); + vecNormal = mat.VectorIRotate( vecNormal ); + vecEnd = mat.VectorITransform( vecEnd ); + SetBits( flags, FDECAL_LOCAL_SPACE ); // now it's in local space + + MESSAGE_BEGIN( MSG_BROADCAST, gmsgStudioDecal ); + WRITE_COORD( vecEnd.x ); // write pos + WRITE_COORD( vecEnd.y ); + WRITE_COORD( vecEnd.z ); + WRITE_COORD( vecNormal.x * 1000.0f ); // write normal + WRITE_COORD( vecNormal.y * 1000.0f ); + WRITE_COORD( vecNormal.z * 1000.0f ); + WRITE_SHORT( entityIndex ); + WRITE_SHORT( pEntity->pev->modelindex ); + WRITE_STRING( name ); // decal texture + WRITE_BYTE( flags ); + + // write model state for correct restore + WRITE_SHORT( pEntity->pev->sequence ); + WRITE_SHORT( (int)( pEntity->pev->frame * 8.0f )); + WRITE_BYTE( pEntity->pev->blending[0] ); + WRITE_BYTE( pEntity->pev->blending[1] ); + WRITE_BYTE( pEntity->pev->controller[0] ); + WRITE_BYTE( pEntity->pev->controller[1] ); + WRITE_BYTE( pEntity->pev->controller[2] ); + WRITE_BYTE( pEntity->pev->controller[3] ); + + WRITE_BYTE( pEntity->pev->body ); + WRITE_BYTE( pEntity->pev->skin ); + + if( FBitSet( pEntity->pev->iuser1, CF_STATIC_ENTITY )) + WRITE_SHORT( pEntity->pev->colormap ); + else WRITE_SHORT( 0 ); + MESSAGE_END(); + + return TRUE; +} + +void UTIL_RestoreStudioDecal( const Vector &vecEnd, const Vector &vecNormal, int entityIndex, int modelIndex, const char *name, int flags, modelstate_t *state, int lightcache, const Vector &scale ) +{ + MESSAGE_BEGIN( MSG_INIT, gmsgStudioDecal ); + WRITE_COORD( vecEnd.x ); // write pos + WRITE_COORD( vecEnd.y ); + WRITE_COORD( vecEnd.z ); + WRITE_COORD( vecNormal.x * 1000.0f ); // write normal + WRITE_COORD( vecNormal.y * 1000.0f ); + WRITE_COORD( vecNormal.z * 1000.0f ); + WRITE_SHORT( entityIndex ); + WRITE_SHORT( modelIndex ); + WRITE_STRING( name ); // decal texture + WRITE_BYTE( flags ); + + // write model state for correct restore + WRITE_SHORT( state->sequence ); + WRITE_SHORT( state->frame ); // already premultiplied by 8 + WRITE_BYTE( state->blending[0] ); + WRITE_BYTE( state->blending[1] ); + WRITE_BYTE( state->controller[0] ); + WRITE_BYTE( state->controller[1] ); + WRITE_BYTE( state->controller[2] ); + WRITE_BYTE( state->controller[3] ); + WRITE_BYTE( state->body ); + WRITE_BYTE( state->skin ); + WRITE_SHORT( lightcache ); + + // model scale + WRITE_COORD( scale.x * 1000.0f ); + WRITE_COORD( scale.y * 1000.0f ); + WRITE_COORD( scale.z * 1000.0f ); + MESSAGE_END(); +} + +void UTIL_BloodDecalTrace( TraceResult *pTrace, int bloodColor ) +{ + if ( UTIL_ShouldShowBlood( bloodColor ) ) + { + if ( bloodColor == BLOOD_COLOR_RED ) + UTIL_TraceCustomDecal( pTrace, "redblood", RANDOM_FLOAT( 0.0f, 360.0f )); + else + UTIL_TraceCustomDecal( pTrace, "yellowblood", RANDOM_FLOAT( 0.0f, 360.0f )); + } +} + +void UTIL_BloodStudioDecalTrace( TraceResult *pTrace, int bloodColor ) +{ + if ( UTIL_ShouldShowBlood( bloodColor ) ) + { + if ( bloodColor == BLOOD_COLOR_RED ) + UTIL_StudioDecalTrace( pTrace, "redblood" ); + else + UTIL_StudioDecalTrace( pTrace, "yellowblood" ); + } +} + +void UTIL_GunshotDecalTrace( TraceResult *pTrace, const char *name ) +{ + if (pTrace->flFraction == 1.0) + return; + + UTIL_TraceCustomDecal( pTrace, name ); +} + +void UTIL_Sparks( const Vector &position ) +{ +#if 0 + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, position ); + WRITE_BYTE( TE_SPARKS ); + WRITE_COORD( position.x ); + WRITE_COORD( position.y ); + WRITE_COORD( position.z ); + MESSAGE_END(); +#else + MESSAGE_BEGIN( MSG_PVS, gmsgPartEffect, position ); + WRITE_COORD( position.x ); + WRITE_COORD( position.y ); + WRITE_COORD( position.z ); + WRITE_COORD( 0.0f ); + WRITE_COORD( 0.0f ); + WRITE_COORD( 0.0f ); + WRITE_STRING( "env_spark.part1" ); + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_PVS, gmsgPartEffect, position ); + WRITE_COORD( position.x ); + WRITE_COORD( position.y ); + WRITE_COORD( position.z ); + WRITE_COORD( 0.0f ); + WRITE_COORD( 0.0f ); + WRITE_COORD( 0.0f ); + WRITE_STRING( "env_spark.part2" ); + MESSAGE_END(); +#endif +} + +void UTIL_Ricochet( const Vector &position, float scale ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, position ); + WRITE_BYTE( TE_ARMOR_RICOCHET ); + WRITE_COORD( position.x ); + WRITE_COORD( position.y ); + WRITE_COORD( position.z ); + WRITE_BYTE( (int)(scale*10) ); + MESSAGE_END(); +} + + +BOOL UTIL_TeamsMatch( const char *pTeamName1, const char *pTeamName2 ) +{ + // Everyone matches unless it's teamplay + if ( !g_pGameRules->IsTeamplay() ) + return TRUE; + + // Both on a team? + if ( *pTeamName1 != 0 && *pTeamName2 != 0 ) + { + if ( !stricmp( pTeamName1, pTeamName2 ) ) // Same Team? + return TRUE; + } + + return FALSE; +} + +//LRC - moved here from barney.cpp +BOOL UTIL_IsFacing( entvars_t *pevTest, const Vector &reference ) +{ + Vector vecDir = (reference - pevTest->origin); + vecDir.z = 0; + vecDir = vecDir.Normalize(); + Vector forward, angle; + angle = pevTest->v_angle; + angle.x = 0; + UTIL_MakeVectorsPrivate( angle, forward, NULL, NULL ); + // He's facing me, he meant it + if ( DotProduct( forward, vecDir ) > 0.96 ) // +/- 15 degrees or so + { + return TRUE; + } + return FALSE; +} + +void UTIL_StringToVector( float *pVector, const char *pString ) +{ + char *pstr, *pfront, tempString[128]; + int j; + + strcpy( tempString, pString ); + pstr = pfront = tempString; + + for ( j = 0; j < 3; j++ ) // lifted from pr_edict.c + { + pVector[j] = atof( pfront ); + + while ( *pstr && *pstr != ' ' ) + pstr++; + if (!*pstr) + break; + pstr++; + pfront = pstr; + } + if (j < 2) + { + /* + ALERT( at_error, "Bad field in entity!! %s:%s == \"%s\"\n", + pkvd->szClassName, pkvd->szKeyName, pkvd->szValue ); + */ + for (j = j+1;j < 3; j++) + pVector[j] = 0; + } +} + + +//LRC - randomized vectors of the form "0 0 0 .. 1 0 0" +void UTIL_StringToRandomVector( float *pVector, const char *pString ) +{ + char *pstr, *pfront, tempString[128]; + int j; + float pAltVec[3]; + + strcpy( tempString, pString ); + pstr = pfront = tempString; + + for ( j = 0; j < 3; j++ ) // lifted from pr_edict.c + { + pVector[j] = atof( pfront ); + + while ( *pstr && *pstr != ' ' ) pstr++; + if (!*pstr) break; + pstr++; + pfront = pstr; + } + if (j < 2) + { + /* + ALERT( at_error, "Bad field in entity!! %s:%s == \"%s\"\n", + pkvd->szClassName, pkvd->szKeyName, pkvd->szValue ); + */ + for (j = j+1;j < 3; j++) + pVector[j] = 0; + } + else if (*pstr == '.') + { + pstr++; + if (*pstr != '.') return; + pstr++; + if (*pstr != ' ') return; + + UTIL_StringToVector(pAltVec, pstr); + + pVector[0] = RANDOM_FLOAT( pVector[0], pAltVec[0] ); + pVector[1] = RANDOM_FLOAT( pVector[1], pAltVec[1] ); + pVector[2] = RANDOM_FLOAT( pVector[2], pAltVec[2] ); + } +} + + +void UTIL_StringToIntArray( int *pVector, int count, const char *pString ) +{ + char *pstr, *pfront, tempString[128]; + int j; + + strcpy( tempString, pString ); + pstr = pfront = tempString; + + for ( j = 0; j < count; j++ ) // lifted from pr_edict.c + { + pVector[j] = atoi( pfront ); + + while ( *pstr && *pstr != ' ' ) + pstr++; + if (!*pstr) + break; + pstr++; + pfront = pstr; + } + + for ( j++; j < count; j++ ) + { + pVector[j] = 0; + } +} + +Vector UTIL_ClampVectorToBox( const Vector &input, const Vector &clampSize ) +{ + Vector sourceVector = input; + + if ( sourceVector.x > clampSize.x ) + sourceVector.x -= clampSize.x; + else if ( sourceVector.x < -clampSize.x ) + sourceVector.x += clampSize.x; + else + sourceVector.x = 0; + + if ( sourceVector.y > clampSize.y ) + sourceVector.y -= clampSize.y; + else if ( sourceVector.y < -clampSize.y ) + sourceVector.y += clampSize.y; + else + sourceVector.y = 0; + + if ( sourceVector.z > clampSize.z ) + sourceVector.z -= clampSize.z; + else if ( sourceVector.z < -clampSize.z ) + sourceVector.z += clampSize.z; + else + sourceVector.z = 0; + + return sourceVector.Normalize(); +} + + +float UTIL_WaterLevel( const Vector &position, float minz, float maxz ) +{ + Vector midUp = position; + midUp.z = minz; + + if (UTIL_PointContents(midUp) != CONTENTS_WATER) + return minz; + + midUp.z = maxz; + if (UTIL_PointContents(midUp) == CONTENTS_WATER) + return maxz; + + float diff = maxz - minz; + while (diff > 1.0) + { + midUp.z = minz + diff/2.0; + if (UTIL_PointContents(midUp) == CONTENTS_WATER) + { + minz = midUp.z; + } + else + { + maxz = midUp.z; + } + diff = maxz - minz; + } + + return midUp.z; +} + + +extern DLL_GLOBAL short g_sModelIndexBubbles;// holds the index for the bubbles model + +void UTIL_Bubbles( Vector mins, Vector maxs, int count ) +{ + Vector mid = (mins + maxs) * 0.5; + + float flHeight = UTIL_WaterLevel( mid, mid.z, mid.z + 1024 ); + flHeight = flHeight - mins.z; + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, mid ); + WRITE_BYTE( TE_BUBBLES ); + WRITE_COORD( mins.x ); // mins + WRITE_COORD( mins.y ); + WRITE_COORD( mins.z ); + WRITE_COORD( maxs.x ); // maxz + WRITE_COORD( maxs.y ); + WRITE_COORD( maxs.z ); + WRITE_COORD( flHeight ); // height + WRITE_SHORT( g_sModelIndexBubbles ); + WRITE_BYTE( count ); // count + WRITE_COORD( 8 ); // speed + MESSAGE_END(); +} + +void UTIL_BubbleTrail( Vector from, Vector to, int count ) +{ + float flHeight = UTIL_WaterLevel( from, from.z, from.z + 256 ); + flHeight = flHeight - from.z; + + if (flHeight < 8) + { + flHeight = UTIL_WaterLevel( to, to.z, to.z + 256 ); + flHeight = flHeight - to.z; + if (flHeight < 8) + return; + + // UNDONE: do a ploink sound + flHeight = flHeight + to.z - from.z; + } + + if (count > 255) + count = 255; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BUBBLETRAIL ); + WRITE_COORD( from.x ); // mins + WRITE_COORD( from.y ); + WRITE_COORD( from.z ); + WRITE_COORD( to.x ); // maxz + WRITE_COORD( to.y ); + WRITE_COORD( to.z ); + WRITE_COORD( flHeight ); // height + WRITE_SHORT( g_sModelIndexBubbles ); + WRITE_BYTE( count ); // count + WRITE_COORD( 8 ); // speed + MESSAGE_END(); +} + + +void UTIL_Remove( CBaseEntity *pEntity ) +{ + if ( !pEntity ) + return; + + pEntity->UpdateOnRemove(); + pEntity->pev->flags |= FL_KILLME; + pEntity->pev->targetname = 0; +} + + +BOOL UTIL_IsValidEntity( edict_t *pent ) +{ + if ( !pent || pent->free || (pent->v.flags & FL_KILLME) ) + return FALSE; + return TRUE; +} + +void UTIL_PrecacheOther( const char *szClassname ) +{ + edict_t *pent; + + pent = CREATE_NAMED_ENTITY( MAKE_STRING( szClassname ) ); + if ( FNullEnt( pent ) ) + { + ALERT ( at_debug, "NULL Ent in UTIL_PrecacheOther\n" ); + return; + } + + CBaseEntity *pEntity = CBaseEntity::Instance (VARS( pent )); + if (pEntity) + pEntity->Precache( ); + REMOVE_ENTITY(pent); +} + +//========================================================= +// UTIL_LogPrintf - Prints a logged message to console. +// Preceded by LOG: ( timestamp ) < message > +//========================================================= +void UTIL_LogPrintf( char *fmt, ... ) +{ + va_list argptr; + static char string[1024]; + + va_start ( argptr, fmt ); + vsprintf ( string, fmt, argptr ); + va_end ( argptr ); + + // Print to server console + ALERT( at_logged, "%s", string ); +} + +//========================================================= +// UTIL_DotPoints - returns the dot product of a line from +// src to check and vecdir. +//========================================================= +float UTIL_DotPoints ( const Vector &vecSrc, const Vector &vecCheck, const Vector &vecDir ) +{ + Vector2D vec2LOS; + + vec2LOS = ( vecCheck - vecSrc ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + return DotProduct (vec2LOS , ( vecDir.Make2D() ) ); +} + + +//========================================================= +// UTIL_StripToken - for redundant keynames +//========================================================= +void UTIL_StripToken( const char *pKey, char *pDest ) +{ + int i = 0; + + while ( pKey[i] && pKey[i] != '#' ) + { + pDest[i] = pKey[i]; + i++; + } + pDest[i] = 0; +} + + +char* GetStringForUseType( USE_TYPE useType ) +{ + switch(useType) + { + case USE_ON: return "USE_ON"; + case USE_OFF: return "USE_OFF"; + case USE_TOGGLE: return "USE_TOGGLE"; + case USE_KILL: return "USE_KILL"; + case USE_NOT: return "USE_NOT"; + default: + return "USE_UNKNOWN!?"; + } +} + +char* GetStringForState( STATE state ) +{ + switch(state) + { + case STATE_ON: return "ON"; + case STATE_OFF: return "OFF"; + case STATE_TURN_ON: return "TURN ON"; + case STATE_TURN_OFF: return "TURN OFF"; + case STATE_IN_USE: return "IN USE"; + default: + return "STATE_UNKNOWN!?"; + } +} + + +// -------------------------------------------------------------- +// +// CSave +// +// -------------------------------------------------------------- +static int gSizes[FIELD_TYPECOUNT] = +{ + sizeof(float), // FIELD_FLOAT + sizeof(int), // FIELD_STRING + sizeof(int), // FIELD_ENTITY + sizeof(int), // FIELD_CLASSPTR + sizeof(int), // FIELD_EHANDLE + sizeof(int), // FIELD_entvars_t + sizeof(int), // FIELD_EDICT + sizeof(float)*3, // FIELD_VECTOR + sizeof(float)*3, // FIELD_POSITION_VECTOR + sizeof(int *), // FIELD_POINTER + sizeof(int), // FIELD_INTEGER + sizeof(int *), // FIELD_FUNCTION + sizeof(int), // FIELD_BOOLEAN + sizeof(short), // FIELD_SHORT + sizeof(char), // FIELD_CHARACTER + sizeof(float), // FIELD_TIME + sizeof(int), // FIELD_MODELNAME + sizeof(int), // FIELD_SOUNDNAME + sizeof(float)*2, // FIELD_RANGE +}; + +// Base class includes common SAVERESTOREDATA pointer, and manages the entity table +CSaveRestoreBuffer :: CSaveRestoreBuffer( void ) +{ + m_pdata = NULL; +} + + +CSaveRestoreBuffer :: CSaveRestoreBuffer( SAVERESTOREDATA *pdata ) +{ + m_pdata = pdata; +} + + +CSaveRestoreBuffer :: ~CSaveRestoreBuffer( void ) +{ +} + +int CSaveRestoreBuffer :: EntityIndex( CBaseEntity *pEntity ) +{ + if ( pEntity == NULL ) + return -1; + return EntityIndex( pEntity->pev ); +} + + +int CSaveRestoreBuffer :: EntityIndex( entvars_t *pevLookup ) +{ + if ( pevLookup == NULL ) + return -1; + return EntityIndex( ENT( pevLookup ) ); +} + +int CSaveRestoreBuffer :: EntityIndex( EOFFSET eoLookup ) +{ + return EntityIndex( ENT( eoLookup ) ); +} + + +int CSaveRestoreBuffer :: EntityIndex( edict_t *pentLookup ) +{ + if ( !m_pdata || pentLookup == NULL ) + return -1; + + int i; + ENTITYTABLE *pTable; + + for ( i = 0; i < m_pdata->tableCount; i++ ) + { + pTable = m_pdata->pTable + i; + if ( pTable->pent == pentLookup ) + return i; + } + return -1; +} + + +edict_t *CSaveRestoreBuffer :: EntityFromIndex( int entityIndex ) +{ + if ( !m_pdata || entityIndex < 0 ) + return NULL; + + int i; + ENTITYTABLE *pTable; + + for ( i = 0; i < m_pdata->tableCount; i++ ) + { + pTable = m_pdata->pTable + i; + if ( pTable->id == entityIndex ) + return pTable->pent; + } + return NULL; +} + + +int CSaveRestoreBuffer :: EntityFlagsSet( int entityIndex, int flags ) +{ + if ( !m_pdata || entityIndex < 0 ) + return 0; + if ( entityIndex > m_pdata->tableCount ) + return 0; + + m_pdata->pTable[ entityIndex ].flags |= flags; + + return m_pdata->pTable[ entityIndex ].flags; +} + + +void CSaveRestoreBuffer :: BufferRewind( int size ) +{ + if ( !m_pdata ) + return; + + if ( m_pdata->size < size ) + size = m_pdata->size; + + m_pdata->pCurrentData -= size; + m_pdata->size -= size; +} + +#ifndef _WIN32 +extern "C" { +unsigned _rotr ( unsigned val, int shift) +{ + register unsigned lobit; /* non-zero means lo bit set */ + register unsigned num = val; /* number to rotate */ + + shift &= 0x1f; /* modulo 32 -- this will also make + negative shifts work */ + + while (shift--) { + lobit = num & 1; /* get high bit */ + num >>= 1; /* shift right one bit */ + if (lobit) + num |= 0x80000000; /* set hi bit if lo bit was set */ + } + + return num; +} +} +#endif + +unsigned int CSaveRestoreBuffer :: HashString( const char *pszToken ) +{ + unsigned int hash = 0; + + while ( *pszToken ) + hash = _rotr( hash, 4 ) ^ *pszToken++; + + return hash; +} + +unsigned short CSaveRestoreBuffer :: TokenHash( const char *pszToken ) +{ + unsigned short hash = (unsigned short)(HashString( pszToken ) % (unsigned)m_pdata->tokenCount ); + +#if _DEBUG + static int tokensparsed = 0; + tokensparsed++; + if ( !m_pdata->tokenCount || !m_pdata->pTokens ) + ALERT( at_error, "No token table array in TokenHash()!" ); +#endif + + for ( int i=0; itokenCount; i++ ) + { +#if _DEBUG + static qboolean beentheredonethat = FALSE; + if ( i > 50 && !beentheredonethat ) + { + beentheredonethat = TRUE; + ALERT( at_error, "CSaveRestoreBuffer :: TokenHash() is getting too full!" ); + } +#endif + + int index = hash + i; + if ( index >= m_pdata->tokenCount ) + index -= m_pdata->tokenCount; + + if ( !m_pdata->pTokens[index] || strcmp( pszToken, m_pdata->pTokens[index] ) == 0 ) + { + m_pdata->pTokens[index] = (char *)pszToken; + return index; + } + } + + // Token hash table full!!! + // [Consider doing overflow table(s) after the main table & limiting linear hash table search] + ALERT( at_error, "CSaveRestoreBuffer :: TokenHash() is COMPLETELY FULL!" ); + return 0; +} + +void CSave :: WriteData( const char *pname, int size, const char *pdata ) +{ + BufferField( pname, size, pdata ); +} + + +void CSave :: WriteShort( const char *pname, const short *data, int count ) +{ + BufferField( pname, sizeof(short) * count, (const char *)data ); +} + + +void CSave :: WriteInt( const char *pname, const int *data, int count ) +{ + BufferField( pname, sizeof(int) * count, (const char *)data ); +} + + +void CSave :: WriteFloat( const char *pname, const float *data, int count ) +{ + BufferField( pname, sizeof(float) * count, (const char *)data ); +} + + +void CSave :: WriteTime( const char *pname, const float *data, int count ) +{ + int i; + Vector tmp, input; + + BufferHeader( pname, sizeof(float) * count ); + for ( i = 0; i < count; i++ ) + { + float tmp = data[0]; + + // Always encode time as a delta from the current time so it can be re-based if loaded in a new level + // Times of 0 are never written to the file, so they will be restored as 0, not a relative time + if ( m_pdata ) + tmp -= m_pdata->time; + + BufferData( (const char *)&tmp, sizeof(float) ); + data ++; + } +} + + +void CSave :: WriteString( const char *pname, const char *pdata ) +{ +#ifdef TOKENIZE + short token = (short)TokenHash( pdata ); + WriteShort( pname, &token, 1 ); +#else + BufferField( pname, strlen(pdata) + 1, pdata ); +#endif +} + + +void CSave :: WriteString( const char *pname, const int *stringId, int count ) +{ + int i, size; + +#ifdef TOKENIZE + short token = (short)TokenHash( STRING( *stringId ) ); + WriteShort( pname, &token, 1 ); +#else +#if 0 + if ( count != 1 ) + ALERT( at_error, "No string arrays!\n" ); + WriteString( pname, (char *)STRING(*stringId) ); +#endif + + size = 0; + for ( i = 0; i < count; i++ ) + size += strlen( STRING( stringId[i] ) ) + 1; + + BufferHeader( pname, size ); + for ( i = 0; i < count; i++ ) + { + const char *pString = STRING(stringId[i]); + BufferData( pString, strlen(pString)+1 ); + } +#endif +} + + +void CSave :: WriteVector( const char *pname, const Vector &value ) +{ + WriteVector( pname, &value.x, 1 ); +} + + +void CSave :: WriteVector( const char *pname, const float *value, int count ) +{ + BufferHeader( pname, sizeof(float) * 3 * count ); + BufferData( (const char *)value, sizeof(float) * 3 * count ); +} + +void CSave :: WriteRange( const char *pname, const RandomRange &value ) +{ + WriteRange( pname, &value.m_flMin, 1 ); +} + +void CSave :: WriteRange( const char *pname, const float *value, int count ) +{ + BufferHeader( pname, sizeof(float) * 2 * count ); + BufferData( (const char *)value, sizeof(float) * 2 * count ); +} + +void CSave :: WritePositionVector( const char *pname, const Vector &value ) +{ + + if ( m_pdata && m_pdata->fUseLandmark ) + { + Vector tmp = value - m_pdata->vecLandmarkOffset; + WriteVector( pname, tmp ); + } + + WriteVector( pname, value ); +} + + +void CSave :: WritePositionVector( const char *pname, const float *value, int count ) +{ + int i; + Vector tmp, input; + + BufferHeader( pname, sizeof(float) * 3 * count ); + for ( i = 0; i < count; i++ ) + { + Vector tmp( value[0], value[1], value[2] ); + + if ( m_pdata && m_pdata->fUseLandmark ) + tmp = tmp - m_pdata->vecLandmarkOffset; + + BufferData( (const char *)&tmp.x, sizeof(float) * 3 ); + value += 3; + } +} + + +void CSave :: WriteFunction( const char* cname, const char *pname, const int *data, int count ) +{ + const char *functionName; + + functionName = NAME_FOR_FUNCTION( *data ); + if ( functionName ) + BufferField( pname, strlen(functionName) + 1, functionName ); + else + ALERT( at_error, "Member \"%s\" of \"%s\" contains an invalid function pointer %p!", pname, cname, *data ); +} + + +void EntvarsKeyvalue( entvars_t *pev, KeyValueData *pkvd ) +{ + int i; + TYPEDESCRIPTION *pField; + + for ( i = 0; i < ENTVARS_COUNT; i++ ) + { + pField = &gEntvarsDescription[i]; + + if ( !stricmp( pField->fieldName, pkvd->szKeyName ) ) + { + switch( pField->fieldType ) + { + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + case FIELD_STRING: + (*(int *)((char *)pev + pField->fieldOffset)) = ALLOC_STRING( pkvd->szValue ); + break; + + case FIELD_TIME: + case FIELD_FLOAT: + (*(float *)((char *)pev + pField->fieldOffset)) = atof( pkvd->szValue ); + break; + + case FIELD_INTEGER: + (*(int *)((char *)pev + pField->fieldOffset)) = atoi( pkvd->szValue ); + break; + + case FIELD_POSITION_VECTOR: + case FIELD_VECTOR: + UTIL_StringToVector( (float *)((char *)pev + pField->fieldOffset), pkvd->szValue ); + break; + + default: + case FIELD_EVARS: + case FIELD_CLASSPTR: + case FIELD_EDICT: + case FIELD_ENTITY: + case FIELD_POINTER: + ALERT( at_error, "Bad field in entity!!\n" ); + break; + } + pkvd->fHandled = TRUE; + return; + } + } +} + + + +int CSave :: WriteEntVars( const char *pname, entvars_t *pev ) +{ + if (pev->targetname) + return WriteFields( STRING(pev->targetname), pname, pev, gEntvarsDescription, ENTVARS_COUNT ); + else + return WriteFields( STRING(pev->classname), pname, pev, gEntvarsDescription, ENTVARS_COUNT ); +} + + + +int CSave :: WriteFields( const char *cname, const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + int i, j, actualCount, emptyCount; + TYPEDESCRIPTION *pTest; + int entityArray[MAX_ENTITYARRAY]; + + // Precalculate the number of empty fields + emptyCount = 0; + for ( i = 0; i < fieldCount; i++ ) + { + void *pOutputData; + pOutputData = ((char *)pBaseData + pFields[i].fieldOffset ); + if ( DataEmpty( (const char *)pOutputData, pFields[i].fieldSize * gSizes[pFields[i].fieldType] ) ) + emptyCount++; + } + + // Empty fields will not be written, write out the actual number of fields to be written + actualCount = fieldCount - emptyCount; + WriteInt( pname, &actualCount, 1 ); + + for ( i = 0; i < fieldCount; i++ ) + { + void *pOutputData; + pTest = &pFields[ i ]; + pOutputData = ((char *)pBaseData + pTest->fieldOffset ); + + // UNDONE: Must we do this twice? + if ( DataEmpty( (const char *)pOutputData, pTest->fieldSize * gSizes[pTest->fieldType] ) ) + continue; + + switch( pTest->fieldType ) + { + case FIELD_FLOAT: + WriteFloat( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + case FIELD_TIME: + WriteTime( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + case FIELD_STRING: + WriteString( pTest->fieldName, (int *)pOutputData, pTest->fieldSize ); + break; + case FIELD_CLASSPTR: + case FIELD_EVARS: + case FIELD_EDICT: + case FIELD_ENTITY: + case FIELD_EHANDLE: + if ( pTest->fieldSize > MAX_ENTITYARRAY ) + ALERT( at_error, "Can't save more than %d entities in an array!!!\n", MAX_ENTITYARRAY ); + for ( j = 0; j < pTest->fieldSize; j++ ) + { + switch( pTest->fieldType ) + { + case FIELD_EVARS: + entityArray[j] = EntityIndex( ((entvars_t **)pOutputData)[j] ); + break; + case FIELD_CLASSPTR: + entityArray[j] = EntityIndex( ((CBaseEntity **)pOutputData)[j] ); + break; + case FIELD_EDICT: + entityArray[j] = EntityIndex( ((edict_t **)pOutputData)[j] ); + break; + case FIELD_ENTITY: + entityArray[j] = EntityIndex( ((EOFFSET *)pOutputData)[j] ); + break; + case FIELD_EHANDLE: + entityArray[j] = EntityIndex( (CBaseEntity *)(((EHANDLE *)pOutputData)[j]) ); + break; + } + } + WriteInt( pTest->fieldName, entityArray, pTest->fieldSize ); + break; + case FIELD_POSITION_VECTOR: + WritePositionVector( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + case FIELD_VECTOR: + WriteVector( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + case FIELD_RANGE: + WriteRange( pTest->fieldName, (float *)pOutputData, pTest->fieldSize ); + break; + case FIELD_BOOLEAN: + case FIELD_INTEGER: + WriteInt( pTest->fieldName, (int *)pOutputData, pTest->fieldSize ); + break; + + case FIELD_SHORT: + WriteData( pTest->fieldName, 2 * pTest->fieldSize, ((char *)pOutputData) ); + break; + + case FIELD_CHARACTER: + WriteData( pTest->fieldName, pTest->fieldSize, ((char *)pOutputData) ); + break; + + // For now, just write the address out, we're not going to change memory while doing this yet! + case FIELD_POINTER: + WriteInt( pTest->fieldName, (int *)(char *)pOutputData, pTest->fieldSize ); + break; + + case FIELD_FUNCTION: + WriteFunction( cname, pTest->fieldName, (int *)(char *)pOutputData, pTest->fieldSize ); + break; + default: + ALERT( at_error, "Bad field type\n" ); + } + } + + return 1; +} + + +void CSave :: BufferString( char *pdata, int len ) +{ + char c = 0; + + BufferData( pdata, len ); // Write the string + BufferData( &c, 1 ); // Write a null terminator +} + + +int CSave :: DataEmpty( const char *pdata, int size ) +{ + for ( int i = 0; i < size; i++ ) + { + if ( pdata[i] ) + return 0; + } + return 1; +} + + +void CSave :: BufferField( const char *pname, int size, const char *pdata ) +{ + BufferHeader( pname, size ); + BufferData( pdata, size ); +} + + +void CSave :: BufferHeader( const char *pname, int size ) +{ + short hashvalue = TokenHash( pname ); + if ( size > 1<<(sizeof(short)*8) ) + ALERT( at_error, "CSave :: BufferHeader() size parameter exceeds 'short'!" ); + BufferData( (const char *)&size, sizeof(short) ); + BufferData( (const char *)&hashvalue, sizeof(short) ); +} + + +void CSave :: BufferData( const char *pdata, int size ) +{ + if ( !m_pdata ) + return; + + if ( m_pdata->size + size > m_pdata->bufferSize ) + { + ALERT( at_error, "Save/Restore overflow!" ); + m_pdata->size = m_pdata->bufferSize; + return; + } + + memcpy( m_pdata->pCurrentData, pdata, size ); + m_pdata->pCurrentData += size; + m_pdata->size += size; +} + + + +// -------------------------------------------------------------- +// +// CRestore +// +// -------------------------------------------------------------- + +int CRestore::ReadField( void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount, int startField, int size, char *pName, void *pData ) +{ + int i, j, stringCount, fieldNumber, entityIndex; + TYPEDESCRIPTION *pTest; + float time, timeData; + Vector position; + edict_t *pent; + char *pString; + + time = 0; + position = Vector(0,0,0); + + if ( m_pdata ) + { + time = m_pdata->time; + if ( m_pdata->fUseLandmark ) + position = m_pdata->vecLandmarkOffset; + } + + for ( i = 0; i < fieldCount; i++ ) + { + fieldNumber = (i+startField)%fieldCount; + pTest = &pFields[ fieldNumber ]; + if ( !stricmp( pTest->fieldName, pName ) ) + { + if ( !m_global || !(pTest->flags & FTYPEDESC_GLOBAL) ) + { + for ( j = 0; j < pTest->fieldSize; j++ ) + { + void *pOutputData = ((char *)pBaseData + pTest->fieldOffset + (j*gSizes[pTest->fieldType]) ); + void *pInputData = (char *)pData + j * gSizes[pTest->fieldType]; + + switch( pTest->fieldType ) + { + case FIELD_TIME: + timeData = *(float *)pInputData; + // Re-base time variables + timeData += time; + *((float *)pOutputData) = timeData; + break; + case FIELD_FLOAT: + *((float *)pOutputData) = *(float *)pInputData; + break; + case FIELD_MODELNAME: + case FIELD_SOUNDNAME: + case FIELD_STRING: + // Skip over j strings + pString = (char *)pData; + for ( stringCount = 0; stringCount < j; stringCount++ ) + { + while (*pString) + pString++; + pString++; + } + pInputData = pString; + if ( strlen( (char *)pInputData ) == 0 ) + *((int *)pOutputData) = 0; + else + { + int string; + + string = ALLOC_STRING( (char *)pInputData ); + + *((int *)pOutputData) = string; + + if ( !FStringNull( string ) && m_precache ) + { + if ( pTest->fieldType == FIELD_MODELNAME ) + PRECACHE_MODEL( (char *)STRING( string ) ); + else if ( pTest->fieldType == FIELD_SOUNDNAME ) + PRECACHE_SOUND( (char *)STRING( string ) ); + } + } + break; + case FIELD_EVARS: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((entvars_t **)pOutputData) = VARS(pent); + else + *((entvars_t **)pOutputData) = NULL; + break; + case FIELD_CLASSPTR: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((CBaseEntity **)pOutputData) = CBaseEntity::Instance(pent); + else + { + *((CBaseEntity **)pOutputData) = NULL; + if (entityIndex != -1) ALERT(at_console, "## Restore: invalid entitynum %d\n", entityIndex); + } + break; + case FIELD_EDICT: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + *((edict_t **)pOutputData) = pent; + break; + case FIELD_EHANDLE: + // Input and Output sizes are different! + pOutputData = (char *)pOutputData + j*(sizeof(EHANDLE) - gSizes[pTest->fieldType]); + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((EHANDLE *)pOutputData) = CBaseEntity::Instance(pent); + else + *((EHANDLE *)pOutputData) = NULL; + break; + case FIELD_ENTITY: + entityIndex = *( int *)pInputData; + pent = EntityFromIndex( entityIndex ); + if ( pent ) + *((EOFFSET *)pOutputData) = OFFSET(pent); + else + *((EOFFSET *)pOutputData) = 0; + break; + case FIELD_RANGE: + ((float *)pOutputData)[0] = ((float *)pInputData)[0]; + ((float *)pOutputData)[1] = ((float *)pInputData)[1]; + break; + case FIELD_VECTOR: + ((float *)pOutputData)[0] = ((float *)pInputData)[0]; + ((float *)pOutputData)[1] = ((float *)pInputData)[1]; + ((float *)pOutputData)[2] = ((float *)pInputData)[2]; + break; + case FIELD_POSITION_VECTOR: + ((float *)pOutputData)[0] = ((float *)pInputData)[0] + position.x; + ((float *)pOutputData)[1] = ((float *)pInputData)[1] + position.y; + ((float *)pOutputData)[2] = ((float *)pInputData)[2] + position.z; + break; + + case FIELD_BOOLEAN: + case FIELD_INTEGER: + *((int *)pOutputData) = *( int *)pInputData; + break; + + case FIELD_SHORT: + *((short *)pOutputData) = *( short *)pInputData; + break; + + case FIELD_CHARACTER: + *((char *)pOutputData) = *( char *)pInputData; + break; + + case FIELD_POINTER: + *((int *)pOutputData) = *( int *)pInputData; + break; + case FIELD_FUNCTION: + if ( strlen( (char *)pInputData ) == 0 ) + *((int *)pOutputData) = 0; + else + *((int *)pOutputData) = FUNCTION_FROM_NAME( (char *)pInputData ); + break; + + default: + ALERT( at_error, "Bad field type\n" ); + } + } + } +#if 0 + else + { + ALERT( at_debug, "Skipping global field %s\n", pName ); + } +#endif + return fieldNumber; + } + } + + return -1; +} + + +int CRestore::ReadEntVars( const char *pname, entvars_t *pev ) +{ + return ReadFields( pname, pev, gEntvarsDescription, ENTVARS_COUNT ); +} + + +int CRestore::ReadFields( const char *pname, void *pBaseData, TYPEDESCRIPTION *pFields, int fieldCount ) +{ + unsigned short i, token; + int lastField, fileCount; + HEADER header; + + i = ReadShort(); + ASSERT( i == sizeof(int) ); // First entry should be an int + + token = ReadShort(); + + // Check the struct name + if ( token != TokenHash(pname) ) // Field Set marker + { +// ALERT( at_error, "Expected %s found %s!\n", pname, BufferPointer() ); + BufferRewind( 2*sizeof(short) ); + return 0; + } + + // Skip over the struct name + fileCount = ReadInt(); // Read field count + + lastField = 0; // Make searches faster, most data is read/written in the same order + + // Clear out base data + for ( i = 0; i < fieldCount; i++ ) + { + // Don't clear global fields + if ( !m_global || !(pFields[i].flags & FTYPEDESC_GLOBAL) ) + memset( ((char *)pBaseData + pFields[i].fieldOffset), 0, pFields[i].fieldSize * gSizes[pFields[i].fieldType] ); + } + + for ( i = 0; i < fileCount; i++ ) + { + BufferReadHeader( &header ); + lastField = ReadField( pBaseData, pFields, fieldCount, lastField, header.size, m_pdata->pTokens[header.token], header.pData ); + lastField++; + } + + return 1; +} + + +void CRestore::BufferReadHeader( HEADER *pheader ) +{ + ASSERT( pheader!=NULL ); + pheader->size = ReadShort(); // Read field size + pheader->token = ReadShort(); // Read field name token + pheader->pData = BufferPointer(); // Field Data is next + BufferSkipBytes( pheader->size ); // Advance to next field +} + + +short CRestore::ReadShort( void ) +{ + short tmp = 0; + + BufferReadBytes( (char *)&tmp, sizeof(short) ); + + return tmp; +} + +int CRestore::ReadInt( void ) +{ + int tmp = 0; + + BufferReadBytes( (char *)&tmp, sizeof(int) ); + + return tmp; +} + +int CRestore::ReadNamedInt( const char *pName ) +{ + HEADER header; + + BufferReadHeader( &header ); + return ((int *)header.pData)[0]; +} + +char *CRestore::ReadNamedString( const char *pName ) +{ + HEADER header; + + BufferReadHeader( &header ); +#ifdef TOKENIZE + return (char *)(m_pdata->pTokens[*(short *)header.pData]); +#else + return (char *)header.pData; +#endif +} + + +char *CRestore::BufferPointer( void ) +{ + if ( !m_pdata ) + return NULL; + + return m_pdata->pCurrentData; +} + +void CRestore::BufferReadBytes( char *pOutput, int size ) +{ + ASSERT( m_pdata !=NULL ); + + if ( !m_pdata || Empty() ) + return; + + if ( (m_pdata->size + size) > m_pdata->bufferSize ) + { + ALERT( at_error, "Restore overflow!" ); + m_pdata->size = m_pdata->bufferSize; + return; + } + + if ( pOutput ) + memcpy( pOutput, m_pdata->pCurrentData, size ); + m_pdata->pCurrentData += size; + m_pdata->size += size; +} + + +void CRestore::BufferSkipBytes( int bytes ) +{ + BufferReadBytes( NULL, bytes ); +} + +int CRestore::BufferSkipZString( void ) +{ + char *pszSearch; + int len; + + if ( !m_pdata ) + return 0; + + int maxLen = m_pdata->bufferSize - m_pdata->size; + + len = 0; + pszSearch = m_pdata->pCurrentData; + while ( *pszSearch++ && len < maxLen ) + len++; + + len++; + + BufferSkipBytes( len ); + + return len; +} + +int CRestore::BufferCheckZString( const char *string ) +{ + if ( !m_pdata ) + return 0; + + int maxLen = m_pdata->bufferSize - m_pdata->size; + int len = strlen( string ); + if ( len <= maxLen ) + { + if ( !strncmp( string, m_pdata->pCurrentData, len ) ) + return 1; + } + return 0; +} + +void UTIL_FindHullIntersection( const Vector &vecSrc, TraceResult &tr, float *mins, float *maxs, edict_t *pEntity ) +{ + float distance; + TraceResult tmpTrace; + float *minmaxs[2] = {mins, maxs}; + Vector vecHullEnd = tr.vecEndPos; + Vector vecEnd; + + distance = 1e6f; + + vecHullEnd = vecSrc + ((vecHullEnd - vecSrc) * 2); + UTIL_TraceLine( vecSrc, vecHullEnd, dont_ignore_monsters, pEntity, &tmpTrace ); + + if ( tmpTrace.flFraction < 1.0 ) + { + tr = tmpTrace; + return; + } + + for ( int i = 0; i < 2; i++ ) + { + for ( int j = 0; j < 2; j++ ) + { + for ( int k = 0; k < 2; k++ ) + { + vecEnd.x = vecHullEnd.x + minmaxs[i][0]; + vecEnd.y = vecHullEnd.y + minmaxs[j][1]; + vecEnd.z = vecHullEnd.z + minmaxs[k][2]; + + UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, pEntity, &tmpTrace ); + + if ( tmpTrace.flFraction < 1.0 ) + { + float thisDistance = (tmpTrace.vecEndPos - vecSrc).Length(); + + if ( thisDistance < distance ) + { + tr = tmpTrace; + distance = thisDistance; + } + } + } + } + } +} + +unsigned short UTIL_PrecacheMovie( string_t iString, int allow_sound ) +{ + return UTIL_PrecacheMovie( STRING( iString ), allow_sound ); +} + +unsigned short UTIL_PrecacheMovie( const char *s, int allow_sound ) +{ + int iCompare; + char path[64], temp[64]; + + Q_snprintf( path, sizeof( path ), "media/%s", s ); + Q_snprintf( temp, sizeof( temp ), "%s%s", allow_sound ? "*" : "", s ); + + // verify file exists + // g-cont. idea! use COMPARE_FILE_TIME instead of LOAD_FILE_FOR_ME + if( COMPARE_FILE_TIME( path, path, &iCompare )) + { + return g_engfuncs.pfnPrecacheGeneric( temp ); + } + + ALERT( at_console, "Warning: video (%s) not found!\n", path ); + + return 0; +} \ No newline at end of file diff --git a/dlls/util.h b/dlls/util.h new file mode 100644 index 0000000..0188d87 --- /dev/null +++ b/dlls/util.h @@ -0,0 +1,628 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// Misc utility code +// +#include + +#ifndef ACTIVITY_H +#include "activity.h" +#endif + +#ifndef ENGINECALLBACK_H +#include "enginecallback.h" +#endif + +#ifndef PHYSCALLBACK_H +#include "physcallback.h" +#endif + +#include "com_model.h" + +#include "randomrange.h" + +inline void MESSAGE_BEGIN( int msg_dest, int msg_type, const float *pOrigin, entvars_t *ent ); // implementation later in this file + +// Use this instead of ALLOC_STRING on constant strings +#define STRING(offset) (const char *)(gpGlobals->pStringBase + (int)offset) +#define MAKE_STRING(str) ((int)str - (int)STRING(0)) + +inline edict_t *FIND_ENTITY_BY_CLASSNAME(edict_t *entStart, const char *pszName) +{ + return FIND_ENTITY_BY_STRING(entStart, "classname", pszName); +} + +inline edict_t *FIND_ENTITY_BY_TARGETNAME(edict_t *entStart, const char *pszName) +{ + return FIND_ENTITY_BY_STRING(entStart, "targetname", pszName); +} + +// for doing a reverse lookup. Say you have a door, and want to find its button. +inline edict_t *FIND_ENTITY_BY_TARGET(edict_t *entStart, const char *pszName) +{ + return FIND_ENTITY_BY_STRING(entStart, "target", pszName); +} + +// Keeps clutter down a bit, when writing key-value pairs +#define WRITEKEY_INT(pf, szKeyName, iKeyValue) ENGINE_FPRINTF(pf, "\"%s\" \"%d\"\n", szKeyName, iKeyValue) +#define WRITEKEY_FLOAT(pf, szKeyName, flKeyValue) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%f\"\n", szKeyName, flKeyValue) +#define WRITEKEY_STRING(pf, szKeyName, szKeyValue) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%s\"\n", szKeyName, szKeyValue) +#define WRITEKEY_VECTOR(pf, szKeyName, flX, flY, flZ) \ + ENGINE_FPRINTF(pf, "\"%s\" \"%f %f %f\"\n", szKeyName, flX, flY, flZ) + +// Makes these more explicit, and easier to find +#define FILE_GLOBAL static +#define DLL_GLOBAL + +// Until we figure out why "const" gives the compiler problems, we'll just have to use +// this bogus "empty" define to mark things as constant. +#define CONSTANT + +// More explicit than "int" +typedef int EOFFSET; + +// In case it's not alread defined +typedef int BOOL; + +// Keeps clutter down a bit, when declaring external entity/global method prototypes +#define DECLARE_GLOBAL_METHOD(MethodName) extern void DLLEXPORT MethodName( void ) +#define GLOBAL_METHOD(funcname) void DLLEXPORT funcname(void) + +// This is the glue that hooks .MAP entity class names to our CPP classes +// The _declspec forces them to be exported by name so we can do a lookup with GetProcAddress() +// The function is used to intialize / allocate the object for the entity +#ifdef _WIN32 +#define LINK_ENTITY_TO_CLASS(mapClassName,DLLClassName) \ + extern "C" _declspec( dllexport ) void mapClassName( entvars_t *pev ); \ + void mapClassName( entvars_t *pev ) { GetClassPtr( (DLLClassName *)pev ); } +#else +#define LINK_ENTITY_TO_CLASS(mapClassName,DLLClassName) extern "C" void mapClassName( entvars_t *pev ); void mapClassName( entvars_t *pev ) { GetClassPtr( (DLLClassName *)pev ); } +#endif + + +// +// Conversion among the three types of "entity", including identity-conversions. +// +#ifdef DEBUG + extern edict_t *DBG_EntOfVars(const entvars_t *pev); + inline edict_t *ENT(const entvars_t *pev) { return DBG_EntOfVars(pev); } +#else + inline edict_t *ENT(const entvars_t *pev) { return pev->pContainingEntity; } +#endif +inline edict_t *ENT(edict_t *pent) { return pent; } +inline edict_t *ENT(EOFFSET eoffset) { return (*g_engfuncs.pfnPEntityOfEntOffset)(eoffset); } +inline EOFFSET OFFSET(EOFFSET eoffset) { return eoffset; } +inline EOFFSET OFFSET(const edict_t *pent) +{ +#if _DEBUG + if ( !pent ) + ALERT( at_error, "Bad ent in OFFSET()\n" ); +#endif + return (*g_engfuncs.pfnEntOffsetOfPEntity)(pent); +} +inline EOFFSET OFFSET(entvars_t *pev) +{ +#if _DEBUG + if ( !pev ) + ALERT( at_error, "Bad pev in OFFSET()\n" ); +#endif + return OFFSET(ENT(pev)); +} +inline entvars_t *VARS(entvars_t *pev) { return pev; } + +inline entvars_t *VARS(edict_t *pent) +{ + if ( !pent ) + return NULL; + + return &pent->v; +} + +inline entvars_t* VARS(EOFFSET eoffset) { return VARS(ENT(eoffset)); } +inline int ENTINDEX(edict_t *pEdict) { return (*g_engfuncs.pfnIndexOfEdict)(pEdict); } +inline edict_t* INDEXENT( int iEdictNum ) { return (*g_engfuncs.pfnPEntityOfEntIndex)(iEdictNum); } +inline void MESSAGE_BEGIN( int msg_dest, int msg_type, const float *pOrigin, entvars_t *ent ) { + (*g_engfuncs.pfnMessageBegin)(msg_dest, msg_type, pOrigin, ENT(ent)); +} + +// Testing the three types of "entity" for nullity +//LRC- four types, rather; see cbase.h +#define eoNullEntity 0 +inline BOOL FNullEnt(EOFFSET eoffset) { return eoffset == 0; } +inline BOOL FNullEnt(const edict_t* pent) { return pent == NULL || FNullEnt(OFFSET(pent)); } +inline BOOL FNullEnt(entvars_t* pev) { return pev == NULL || FNullEnt(OFFSET(pev)); } + +// Testing strings for nullity +#define iStringNull 0 +inline BOOL FStringNull(int iString) { return iString == iStringNull; } + +#define cchMapNameMost 32 + +// Dot products for view cone checking +#define VIEW_FIELD_FULL (float)-1.0 // +-180 degrees +#define VIEW_FIELD_WIDE (float)-0.7 // +-135 degrees 0.1 // +-85 degrees, used for full FOV checks +#define VIEW_FIELD_NARROW (float)0.7 // +-45 degrees, more narrow check used to set up ranged attacks +#define VIEW_FIELD_ULTRA_NARROW (float)0.9 // +-25 degrees, more narrow check used to set up ranged attacks + +// All monsters need this data +#define DONT_BLEED -1 +#define BLOOD_COLOR_RED (BYTE)247 +#define BLOOD_COLOR_YELLOW (BYTE)195 +#define BLOOD_COLOR_GREEN BLOOD_COLOR_YELLOW + +typedef enum +{ + MONSTERSTATE_NONE = 0, + MONSTERSTATE_IDLE, + MONSTERSTATE_COMBAT, + MONSTERSTATE_ALERT, + MONSTERSTATE_HUNT, + MONSTERSTATE_PRONE, + MONSTERSTATE_SCRIPT, + MONSTERSTATE_PLAYDEAD, + MONSTERSTATE_DEAD + +} MONSTERSTATE; + +//LRC- the values used for the new "global states" mechanism. +typedef enum +{ + STATE_OFF = 0, // disabled, inactive, invisible, closed, or stateless. Or non-alert monster. + STATE_TURN_ON, // door opening, env_fade fading in, etc. + STATE_ON, // enabled, active, visisble, or open. Or alert monster. + STATE_TURN_OFF, // door closing, monster dying (?). + STATE_IN_USE, // player is in control (train/tank/barney/scientist). + // In_Use isn't very useful, I'll probably remove it. +} STATE; + +extern char* GetStringForState( STATE state ); + +// Things that toggle (buttons/triggers/doors) need this +typedef enum + { + TS_AT_TOP, + TS_AT_BOTTOM, + TS_GOING_UP, + TS_GOING_DOWN + } TOGGLE_STATE; + +// Misc useful +inline BOOL FStrEq(const char*sz1, const char*sz2) + { return (Q_strcmp(sz1, sz2) == 0); } +inline BOOL FClassnameIs(edict_t* pent, const char* szClassname) + { return FStrEq(STRING(VARS(pent)->classname), szClassname); } +inline BOOL FClassnameIs(entvars_t* pev, const char* szClassname) + { return FStrEq(STRING(pev->classname), szClassname); } + +class CBaseEntity; + +// Misc. Prototypes +extern void UTIL_SetSize (entvars_t* pev, const Vector &vecMin, const Vector &vecMax); +extern float UTIL_VecToYaw (const Vector &vec); +extern Vector UTIL_VecToAngles (const Vector &vec); +extern float UTIL_AngleMod (float a); +extern float UTIL_AngleDiff ( float destAngle, float srcAngle ); +extern Vector UTIL_YawToVector( float yaw ); + +extern Vector UTIL_AxisRotationToAngles (const Vector &vec, float angle); //LRC +extern Vector UTIL_AxisRotationToVec (const Vector &vec, float angle); //LRC + +//LRC +class CBaseAlias; +extern void UTIL_AddToAliasList( CBaseAlias *pAlias ); +extern void UTIL_FlushAliases( void ); + +extern CBaseEntity *UTIL_FindEntityInSphere(CBaseEntity *pStartEntity, const Vector &vecCenter, float flRadius); +extern CBaseEntity *UTIL_FindEntityByString(CBaseEntity *pStartEntity, const char *szKeyword, const char *szValue ); +extern CBaseEntity *UTIL_FindEntityByClassname(CBaseEntity *pStartEntity, const char *szName ); +extern CBaseEntity *UTIL_FindEntityByTargetname(CBaseEntity *pStartEntity, const char *szName ); +extern CBaseEntity *UTIL_FindEntityByTargetname(CBaseEntity *pStartEntity, const char *szName, CBaseEntity *pActivator ); //LRC - for $locus references +extern CBaseEntity *UTIL_FindEntityByTarget(CBaseEntity *pStartEntity, const char *szName ); +extern CBaseEntity *UTIL_FindEntityGeneric(const char *szName, Vector &vecSrc, float flRadius ); + +// returns a CBaseEntity pointer to a player by index. Only returns if the player is spawned and connected +// otherwise returns NULL +// Index is 1 based +extern CBaseEntity *UTIL_PlayerByIndex( int playerIndex ); + +#define UTIL_EntitiesInPVS(pent) (*g_engfuncs.pfnEntitiesInPVS)(pent) +extern void UTIL_MakeVectors (const Vector &vecAngles); + +// Pass in an array of pointers and an array size, it fills the array and returns the number inserted +extern int UTIL_MonstersInSphere( CBaseEntity **pList, int listMax, const Vector ¢er, float radius ); +extern int UTIL_EntitiesInBox( CBaseEntity **pList, int listMax, const Vector &mins, const Vector &maxs, int flagMask ); + +inline void UTIL_MakeVectorsPrivate( const Vector &vecAngles, float *p_vForward, float *p_vRight, float *p_vUp ) +{ + g_engfuncs.pfnAngleVectors( vecAngles, p_vForward, p_vRight, p_vUp ); +} + +extern void UTIL_MakeAimVectors ( const Vector &vecAngles ); // like MakeVectors, but assumes pitch isn't inverted +extern void UTIL_MakeInvVectors ( const Vector &vec, globalvars_t *pgv ); + +extern void UTIL_SetEdictOrigin ( edict_t *pEdict, const Vector &vecOrigin ); +extern void UTIL_SetOrigin ( CBaseEntity* pEntity, const Vector &vecOrigin ); +extern void UTIL_EmitAmbientSound ( edict_t *entity, const Vector &vecOrigin, const char *samp, float vol, float attenuation, int fFlags, int pitch ); +extern void UTIL_ParticleEffect ( const Vector &vecOrigin, const Vector &vecDirection, ULONG ulColor, ULONG ulCount ); +extern void UTIL_ScreenShake ( const Vector ¢er, float amplitude, float frequency, float duration, float radius ); +extern void UTIL_ScreenShakeAll ( const Vector ¢er, float amplitude, float frequency, float duration ); +extern void UTIL_ShowMessage ( const char *pString, CBaseEntity *pPlayer ); +extern void UTIL_ShowMessageAll ( const char *pString ); +extern void UTIL_ShowMessagePVS( const char *pString, const Vector &org ); // buz +extern void UTIL_ScreenFadeAll ( const Vector &color, float fadeTime, float holdTime, int alpha, int flags ); +extern void UTIL_ScreenFade ( CBaseEntity *pEntity, const Vector &color, float fadeTime, float fadeHold, int alpha, int flags ); + +typedef enum { ignore_monsters=1, dont_ignore_monsters=0, missile=2 } IGNORE_MONSTERS; +typedef enum { ignore_glass=1, dont_ignore_glass=0 } IGNORE_GLASS; +extern void UTIL_TraceLine (const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, edict_t *pentIgnore, TraceResult *ptr); +extern void UTIL_TraceLine (const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, IGNORE_GLASS ignoreGlass, edict_t *pentIgnore, TraceResult *ptr); +typedef enum { point_hull=0, human_hull=1, large_hull=2, head_hull=3 }; +extern void UTIL_TraceHull (const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, int hullNumber, edict_t *pentIgnore, TraceResult *ptr); +extern TraceResult UTIL_GetGlobalTrace (void); +extern void UTIL_TraceModel (const Vector &vecStart, const Vector &vecEnd, int hullNumber, edict_t *pentModel, TraceResult *ptr); +extern Vector UTIL_GetAimVector (edict_t* pent, float flSpeed); +extern int UTIL_PointContents (const Vector &vec); + +extern int UTIL_IsMasterTriggered (string_t sMaster, CBaseEntity *pActivator); +extern void UTIL_BloodStream( const Vector &origin, const Vector &direction, int color, int amount ); +extern void UTIL_BloodDrips( const Vector &origin, const Vector &direction, int color, int amount ); +extern Vector UTIL_RandomBloodVector( void ); +extern BOOL UTIL_TraceCustomDecal( TraceResult *pTrace, const char *name, float angle = 0.0f, int persistent = 0 ); // Wargon: Ýòî äîëæíî áûòü îáúÿâëåíî çäåñü ÷òîáû ìîæíî áûëî ðèñîâàòü êàñòîìíå äåêàëè èç êîäà ìîíñòðèêîâ. +extern void UTIL_RestoreCustomDecal( const Vector &vecPos, const Vector &vecNormal, int entityIndex, int modelIndex, const char *name, int flags, float angle ); +extern BOOL UTIL_StudioDecalTrace( TraceResult *pTrace, const char *name, int flags = 0 ); +extern void UTIL_RestoreStudioDecal( const Vector &vecEnd, const Vector &vecNormal, int entityIndex, int modelIndex, const char *name, int flags, struct modelstate_s *state, int lightcache, const Vector &scale ); +extern void UTIL_DecalTrace( TraceResult *pTrace, const char *decalName ); + +// +extern BOOL UTIL_ShouldShowBlood( int bloodColor ); +extern void UTIL_BloodDecalTrace( TraceResult *pTrace, int bloodColor ); +extern void UTIL_BloodStudioDecalTrace( TraceResult *pTrace, int bloodColor ); +extern void UTIL_GunshotDecalTrace( TraceResult *pTrace, const char *name ); +extern void UTIL_Sparks( const Vector &position ); +extern void UTIL_Ricochet( const Vector &position, float scale ); +extern void UTIL_StringToVector( float *pVector, const char *pString ); +extern void UTIL_StringToRandomVector( float *pVector, const char *pString ); //LRC +extern void UTIL_StringToIntArray( int *pVector, int count, const char *pString ); +extern Vector UTIL_ClampVectorToBox( const Vector &input, const Vector &clampSize ); +extern float UTIL_Approach( float target, float value, float speed ); +extern float UTIL_ApproachAngle( float target, float value, float speed ); +extern float UTIL_AngleDistance( float next, float cur ); + +extern char *UTIL_VarArgs( char *format, ... ); +extern void UTIL_Remove( CBaseEntity *pEntity ); +extern BOOL UTIL_IsValidEntity( edict_t *pent ); +extern BOOL UTIL_TeamsMatch( const char *pTeamName1, const char *pTeamName2 ); +extern BOOL UTIL_IsFacing( entvars_t *pevTest, const Vector &reference ); //LRC + +// Use for ease-in, ease-out style interpolation (accel/decel) +extern float UTIL_SplineFraction( float value, float scale ); + +// Search for water transition along a vertical line +extern float UTIL_WaterLevel( const Vector &position, float minz, float maxz ); +extern void UTIL_Bubbles( Vector mins, Vector maxs, int count ); +extern void UTIL_BubbleTrail( Vector from, Vector to, int count ); + +// allows precacheing of other entities +extern void UTIL_PrecacheOther( const char *szClassname ); + +// prints a message to each client +extern void UTIL_ClientPrintAll( int msg_dest, const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ); +inline void UTIL_CenterPrintAll( const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ) +{ + UTIL_ClientPrintAll( HUD_PRINTCENTER, msg_name, param1, param2, param3, param4 ); +} + +class CBasePlayerItem; +class CBasePlayer; +extern BOOL UTIL_GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ); + +// prints messages through the HUD +extern void ClientPrint( entvars_t *client, int msg_dest, const char *msg_name, const char *param1 = NULL, const char *param2 = NULL, const char *param3 = NULL, const char *param4 = NULL ); + +// prints a message to the HUD say (chat) +extern void UTIL_SayText( const char *pText, CBaseEntity *pEntity ); +extern void UTIL_SayTextAll( const char *pText, CBaseEntity *pEntity ); + + +typedef struct hudtextparms_s +{ + float x; + float y; + int effect; + byte r1, g1, b1, a1; + byte r2, g2, b2, a2; + float fadeinTime; + float fadeoutTime; + float holdTime; + float fxTime; + int channel; +} hudtextparms_t; + +// prints as transparent 'title' to the HUD +extern void UTIL_HudMessageAll( const hudtextparms_t &textparms, const char *pMessage ); +extern void UTIL_HudMessage( CBaseEntity *pEntity, const hudtextparms_t &textparms, const char *pMessage ); + +// for handy use with ClientPrint params +extern char *UTIL_dtos1( int d ); +extern char *UTIL_dtos2( int d ); +extern char *UTIL_dtos3( int d ); +extern char *UTIL_dtos4( int d ); + +// Writes message to console with timestamp and FragLog header. +extern void UTIL_LogPrintf( char *fmt, ... ); + +// Sorta like FInViewCone, but for nonmonsters. +extern float UTIL_DotPoints ( const Vector &vecSrc, const Vector &vecCheck, const Vector &vecDir ); + +extern void UTIL_StripToken( const char *pKey, char *pDest );// for redundant keynames + +extern void UTIL_FindHullIntersection( const Vector &vecSrc, TraceResult &tr, float *mins, float *maxs, edict_t *pEntity ); + +// Misc functions +extern void SetMovedir(entvars_t* pev); +extern Vector GetMovedir( Vector vecAngles ); +extern Vector VecBModelOrigin( entvars_t* pevBModel ); +extern int BuildChangeList( LEVELLIST *pLevelList, int maxList ); + +// +// How did I ever live without ASSERT? +// +#ifdef DEBUG +void DBG_AssertFunction(BOOL fExpr, const char* szExpr, const char* szFile, int szLine, const char* szMessage); +#define ASSERT(f) DBG_AssertFunction(f, #f, __FILE__, __LINE__, NULL) +#define ASSERTSZ(f, sz) DBG_AssertFunction(f, #f, __FILE__, __LINE__, sz) +#else // !DEBUG +#define ASSERT(f) +#define ASSERTSZ(f, sz) +#endif // !DEBUG + + +extern DLL_GLOBAL const Vector g_vecZero; + +// +// Constants that were used only by QC (maybe not used at all now) +// +// Un-comment only as needed +// +#define LANGUAGE_ENGLISH 0 +#define LANGUAGE_GERMAN 1 +#define LANGUAGE_FRENCH 2 +#define LANGUAGE_BRITISH 3 + +extern DLL_GLOBAL int g_Language; +extern DLL_GLOBAL BOOL g_fXashEngine; +extern DLL_GLOBAL BOOL g_fPhysicInitialized; +extern DLL_GLOBAL int g_iXashEngineBuildNumber; // may be 0 for old versions or GoldSource +extern DLL_GLOBAL BOOL g_fAllowSaves; + +#define AMBIENT_SOUND_STATIC 0 // medium radius attenuation +#define AMBIENT_SOUND_EVERYWHERE 1 +#define AMBIENT_SOUND_SMALLRADIUS 2 +#define AMBIENT_SOUND_MEDIUMRADIUS 4 +#define AMBIENT_SOUND_LARGERADIUS 8 +#define AMBIENT_SOUND_START_SILENT 16 +#define AMBIENT_SOUND_NOT_LOOPING 32 +#define AMBIENT_SOUND_FROM_PLAYER 64 + +#define SPEAKER_START_SILENT 1 // wait for trigger 'on' to start announcements + +#define SND_SPAWNING (1<<8) // duplicated in protocol.h we're spawing, used in some cases for ambients +#define SND_STOP (1<<5) // duplicated in protocol.h stop sound +#define SND_CHANGE_VOL (1<<6) // duplicated in protocol.h change sound vol +#define SND_CHANGE_PITCH (1<<7) // duplicated in protocol.h change sound pitch + +#define LFO_SQUARE 1 +#define LFO_TRIANGLE 2 +#define LFO_RANDOM 3 + +// func_rotating +#define SF_BRUSH_ROTATE_Y_AXIS 0 //!?! (LRC) +#define SF_BRUSH_ROTATE_INSTANT 1 +#define SF_BRUSH_ROTATE_BACKWARDS 2 +#define SF_BRUSH_ROTATE_Z_AXIS 4 +#define SF_BRUSH_ROTATE_X_AXIS 8 +#define SF_PENDULUM_AUTO_RETURN 16 +#define SF_PENDULUM_PASSABLE 32 + +#define SF_BRUSH_ROTATE_SMALLRADIUS 128 +#define SF_BRUSH_ROTATE_MEDIUMRADIUS 256 +#define SF_BRUSH_ROTATE_LARGERADIUS 512 + +#define PUSH_BLOCK_ONLY_X 1 +#define PUSH_BLOCK_ONLY_Y 2 + +#define VEC_HULL_MIN Vector(-16, -16, -36) +#define VEC_HULL_MAX Vector( 16, 16, 36) +#define VEC_HUMAN_HULL_MIN Vector( -16, -16, 0 ) +#define VEC_HUMAN_HULL_MAX Vector( 16, 16, 72 ) +#define VEC_HUMAN_HULL_DUCK Vector( 16, 16, 36 ) + +#define VEC_VIEW Vector( 0, 0, 28 ) + +#define VEC_DUCK_HULL_MIN Vector(-16, -16, -18 ) +#define VEC_DUCK_HULL_MAX Vector( 16, 16, 18) +#define VEC_DUCK_VIEW Vector( 0, 0, 12 ) + +#define CIN_FRAMETIME (1.0f / 30.0f) + +#define SVC_TEMPENTITY 23 +#define SVC_INTERMISSION 30 +#define SVC_CDTRACK 32 +#define SVC_WEAPONANIM 35 +#define SVC_ROOMTYPE 37 +#define SVC_DIRECTOR 51 + + +// triggers +#define SF_TRIGGER_ALLOWMONSTERS 1// monsters allowed to fire this trigger +#define SF_TRIGGER_NOCLIENTS 2// players not allowed to fire this trigger +#define SF_TRIGGER_PUSHABLES 4// only pushables can fire this trigger +#define SF_TRIGGER_EVERYTHING 8// everything else can fire this trigger (e.g. gibs, rockets) + +// func breakable +#define SF_BREAK_TRIGGER_ONLY 1// may only be broken by trigger +#define SF_BREAK_TOUCH 2// can be 'crashed through' by running player (plate glass) +#define SF_BREAK_PRESSURE 4// can be broken by a player standing on it +#define SF_BREAK_FADE_RESPAWN 8// LRC- fades in gradually when respawned +#define SF_BREAK_CROWBAR 256// instant break if hit with crowbar + +// func_pushable (it's also func_breakable, so don't collide with those flags) +#define SF_PUSH_BREAKABLE 128 +#define SF_PUSH_NOPULL 512//LRC +#define SF_PUSH_USECUSTOMSIZE 0x800000 //LRC, not yet used + +#define SF_LIGHT_START_OFF 1 + +#define SPAWNFLAG_NOMESSAGE 1 +#define SPAWNFLAG_NOTOUCH 1 +#define SPAWNFLAG_DROIDONLY 4 + +#define SPAWNFLAG_USEONLY 1 // can't be touched, must be used (buttons) + +#define TELE_PLAYER_ONLY 1 +#define TELE_SILENT 2 + +#define SF_TRIG_PUSH_ONCE 1 + + +// Sound Utilities + +// sentence groups +#define CBSENTENCENAME_MAX 32 // g-cont. was 16 +#define CVOXFILESENTENCEMAX 1536 // max number of sentences in game. NOTE: this must match + // CVOXFILESENTENCEMAX in engine\sound.h!!! + +extern char gszallsentencenames[CVOXFILESENTENCEMAX][CBSENTENCENAME_MAX]; +extern int gcallsentences; + +int USENTENCEG_Pick(int isentenceg, char *szfound); +int USENTENCEG_PickSequential(int isentenceg, char *szfound, int ipick, int freset); +void USENTENCEG_InitLRU(unsigned char *plru, int count); + +void SENTENCEG_Init(); +void SENTENCEG_Stop(edict_t *entity, int isentenceg, int ipick); +int SENTENCEG_PlayRndI(edict_t *entity, int isentenceg, float volume, float attenuation, int flags, int pitch); +int SENTENCEG_PlayRndSz(edict_t *entity, const char *szrootname, float volume, float attenuation, int flags, int pitch, int channel = -1); // buz: added channel parameter +int SENTENCEG_PlaySequentialSz(edict_t *entity, const char *szrootname, float volume, float attenuation, int flags, int pitch, int ipick, int freset); +int SENTENCEG_GetIndex(const char *szrootname); +int SENTENCEG_Lookup(const char *sample, char *sentencenum); + +void TEXTURETYPE_Init( void ); +float TEXTURETYPE_PlaySound(TraceResult *ptr, Vector vecSrc, Vector vecEnd, int iBulletType ); + +// NOTE: use EMIT_SOUND_DYN to set the pitch of a sound. Pitch of 100 +// is no pitch shift. Pitch > 100 up to 255 is a higher pitch, pitch < 100 +// down to 1 is a lower pitch. 150 to 70 is the realistic range. +// EMIT_SOUND_DYN with pitch != 100 should be used sparingly, as it's not quite as +// fast as EMIT_SOUND (the pitchshift mixer is not native coded). + +void EMIT_SOUND_DYN(edict_t *entity, int channel, const char *sample, float volume, float attenuation, + int flags, int pitch); + + +inline void EMIT_SOUND(edict_t *entity, int channel, const char *sample, float volume, float attenuation) +{ + EMIT_SOUND_DYN(entity, channel, sample, volume, attenuation, 0, PITCH_NORM); +} + +inline void STOP_SOUND(edict_t *entity, int channel, const char *sample) +{ + EMIT_SOUND_DYN(entity, channel, sample, 0, 0, SND_STOP, PITCH_NORM); +} + +void EMIT_SOUND_SUIT(edict_t *entity, const char *sample); +void EMIT_GROUPID_SUIT(edict_t *entity, int isentenceg); +void EMIT_GROUPNAME_SUIT(edict_t *entity, const char *groupname); + +#define PRECACHE_SOUND_ARRAY( a ) \ + { for (int i = 0; i < ARRAYSIZE( a ); i++ ) PRECACHE_SOUND((char *) a [i]); } + +#define EMIT_SOUND_ARRAY_DYN( chan, array ) \ + EMIT_SOUND_DYN ( ENT(pev), chan , array [ RANDOM_LONG(0,ARRAYSIZE( array )-1) ], 1.0, ATTN_NORM, 0, RANDOM_LONG(95,105) ); + +#define RANDOM_SOUND_ARRAY( array ) (array) [ RANDOM_LONG(0,ARRAYSIZE( (array) )-1) ] + +#define PLAYBACK_EVENT( flags, who, index ) PLAYBACK_EVENT_FULL( flags, who, index, 0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0 ); +#define PLAYBACK_EVENT_DELAY( flags, who, index, delay ) PLAYBACK_EVENT_FULL( flags, who, index, delay, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, 0, 0 ); + +#define GROUP_OP_AND 0 +#define GROUP_OP_NAND 1 + +extern int g_groupmask; +extern int g_groupop; + +class UTIL_GroupTrace +{ +public: + UTIL_GroupTrace( int groupmask, int op ); + ~UTIL_GroupTrace( void ); + +private: + int m_oldgroupmask, m_oldgroupop; +}; + +void UTIL_SetGroupTrace( int groupmask, int op ); +void UTIL_UnsetGroupTrace( void ); + +int UTIL_SharedRandomLong( unsigned int seed, int low, int high ); +float UTIL_SharedRandomFloat( unsigned int seed, float low, float high ); + +float UTIL_WeaponTimeBase( void ); +int GetStdLightStyle (int iStyle); //LRC- declared here so it can be used by everything that + // needs to deal with the standard lightstyles. +// LRC- for aliases and groups +CBaseEntity* UTIL_FollowReference( CBaseEntity* pStartEntity, const char* szName ); + +extern unsigned short UTIL_PrecacheMovie( const char *s, int allow_sound = 0 ); +extern unsigned short UTIL_PrecacheMovie( string_t iString, int allow_sound = 0 ); + +// material system +typedef struct +{ + struct matdef_s *effects; // hit, impact, particle effects etc + char name[64]; +} sv_matdesc_t; + +typedef struct +{ + char name[64]; + unsigned short width, height; + byte maxHeight; + byte numLayers; // layers that specified on heightmap + byte *pixels; // pixels are immediately goes here +} sv_indexMap_t; + +typedef struct +{ + char names[MAX_LANDSCAPE_LAYERS][64]; // basenames + struct matdef_s *effects[MAX_LANDSCAPE_LAYERS]; // layer settings +} sv_layerMap_t; + +typedef struct terrain_s +{ + char name[16]; + sv_indexMap_t indexmap; + sv_layerMap_t layermap; + int numLayers; // count of array textures + bool valid; // if heightmap was actual +} terrain_t; + +extern void SV_InitMaterials( void ); +extern void COM_InitMatdef( void ); +extern sv_matdesc_t *SV_FindMaterial( const char *name ); +extern terrain_t *SV_FindTerrain( const char *texname ); +extern void SV_ProcessWorldData( model_t *mod, qboolean create, const byte *buffer ); \ No newline at end of file diff --git a/dlls/weapons.cpp b/dlls/weapons.cpp new file mode 100644 index 0000000..f8e1c93 --- /dev/null +++ b/dlls/weapons.cpp @@ -0,0 +1,3069 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== weapons.cpp ======================================================== + + functions governing the selection/use of weapons for players + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "soundent.h" +#include "decals.h" +#include "gamerules.h" +#include "animation.h" +#include "game.h" + +extern int gEvilImpulse101; + +DLL_GLOBAL unsigned short g_usShootEvent; +DLL_GLOBAL short g_sModelIndexLaser;// holds the index for the laser beam +DLL_GLOBAL const char *g_pModelNameLaser = "sprites/laserbeam.spr"; +DLL_GLOBAL short g_sModelIndexLaserDot;// holds the index for the laser beam dot +DLL_GLOBAL short g_sModelIndexFireball;// holds the index for the fireball +DLL_GLOBAL short g_sModelIndexSmoke;// holds the index for the smoke cloud +DLL_GLOBAL short g_sModelIndexWExplosion;// holds the index for the underwater explosion +DLL_GLOBAL short g_sModelIndexBubbles;// holds the index for the bubbles model +DLL_GLOBAL short g_sModelIndexBloodDrop;// holds the sprite index for the initial blood +DLL_GLOBAL short g_sModelIndexBloodSpray;// holds the sprite index for splattered blood +DLL_GLOBAL short g_sModelIndexWaterSplash; +DLL_GLOBAL short g_sModelIndexSmokeTrail; +DLL_GLOBAL short g_sModelIndexNull; + +ItemInfo CBasePlayerItem :: ItemInfoArray[MAX_WEAPONS]; +int CBasePlayerItem :: m_iGlobalID; + +extern int gmsgCurWeapon; + +// Precaches the weapon and queues the weapon info for sending to clients +void UTIL_PrecacheWeapon( const char *szClassname ) +{ + edict_t *pent = CREATE_NAMED_ENTITY( ALLOC_STRING( szClassname ) ); + + if ( FNullEnt( pent )) + { + ALERT( at_error, "NULL Ent in UTIL_PrecacheOtherWeapon\n" ); + return; + } + + CBaseEntity *pEntity = CBaseEntity::Instance (VARS( pent )); + + if (pEntity) + { + ItemInfo II; + + memset( &II, 0, sizeof II ); + + if((( CBasePlayerItem *)pEntity)->GetItemInfo( &II )) + { + CBasePlayerItem::ItemInfoArray[II.iId] = II; + } + + // g-cont. reduce loading time because v_models is too heaviliy ~ 35 Mb +// pEntity->Precache( ); + } + + REMOVE_ENTITY( pent ); +} + +void UTIL_PrecacheSpecialItems( void ) // buz +{ + ItemInfo II; + + // add painkiller to weapons list + memset( &II, 0, sizeof II ); + II.pszName = "painkiller"; + II.pszAmmo1 = "painkillers"; + II.iMaxAmmo1 = 10; + II.iSlot = 0; + II.iPosition = 1; + II.pszAmmo2 = "none"; + II.iMaxAmmo2 = -1; + II.iId = WEAPON_PAINKILLER; + CBasePlayerItem::ItemInfoArray[II.iId] = II; +} + +void UTIL_InitWeaponDescription( const char *pattern ) +{ + int numFiles = 0; + + char **filenames = GET_FILES_LIST( pattern, &numFiles, FALSE ); + char classname[256]; // convert path into name + + for( int i = 0; i < numFiles; i++ ) + { + COM_FileBase( filenames[i], classname ); + UTIL_PrecacheWeapon( classname ); + } + + UTIL_PrecacheSpecialItems(); +} + +TYPEDESCRIPTION CBasePlayerItem :: m_SaveData[] = +{ + DEFINE_FIELD( CBasePlayerItem, m_pPlayer, FIELD_CLASSPTR ), + DEFINE_FIELD( CBasePlayerItem, m_pNext, FIELD_CLASSPTR ), + DEFINE_FIELD( CBasePlayerItem, m_iId, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerItem, m_iItemCaps, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerItem, m_iDefaultAmmo1, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerItem, m_iDefaultAmmo2, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerItem, m_flNextPrimaryAttack, FIELD_TIME ), + DEFINE_FIELD( CBasePlayerItem, m_flNextSecondaryAttack, FIELD_TIME ), + DEFINE_FIELD( CBasePlayerItem, m_flTimeWeaponIdle, FIELD_TIME ), + DEFINE_FIELD( CBasePlayerItem, m_flTimeUpdate, FIELD_TIME ), + DEFINE_FIELD( CBasePlayerItem, m_flSpreadTime, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayerItem, m_flLastShotTime, FIELD_TIME ), + DEFINE_FIELD( CBasePlayerItem, m_flLastSpreadPower, FIELD_FLOAT ), + DEFINE_FIELD( CBasePlayerItem, m_iPrimaryAmmoType, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerItem, m_iSecondaryAmmoType, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerItem, m_iWeaponAutoFire, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerItem, m_cActiveRockets, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerItem, m_iStepReload, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerItem, m_iIronSight, FIELD_BOOLEAN ), + DEFINE_FIELD( CBasePlayerItem, m_fWaitForHolster, FIELD_BOOLEAN ), + DEFINE_FIELD( CBasePlayerItem, m_iZoom, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerItem, m_iClip, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerItem, m_iBody, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerItem, m_iSkin, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerItem, m_iSpot, FIELD_INTEGER ), + DEFINE_FIELD( CBasePlayerItem, m_iHandModel, FIELD_MODELNAME ), +}; IMPLEMENT_SAVERESTORE( CBasePlayerItem, CBaseAnimating ); + +LINK_ENTITY_TO_CLASS( weapon_generic, CBasePlayerItem ); + +void CBasePlayerItem :: SetObjectCollisionBox( void ) +{ + pev->absmin = pev->origin + Vector( -24.0f, -24.0f, 0.0f ); + pev->absmax = pev->origin + Vector( 24.0f, 24.0f, 16.0f ); +} + +//========================================================= +// Sets up movetype, size, solidtype for a new weapon. +//========================================================= +void CBasePlayerItem :: FallInit( void ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_BBOX; + + UTIL_SetOrigin( this, pev->origin ); + UTIL_SetSize( pev, g_vecZero, g_vecZero ); // pointsize until it lands on the ground. + + // Wargon: Îðóæèå þçàáåëüíî. + SetUse( &CBasePlayerItem::DefaultUse ); + m_iItemCaps = CBaseEntity::ObjectCaps() | FCAP_IMPULSE_USE; + + if( !FBitSet( ObjectCaps(), FCAP_USE_ONLY ) || FBitSet( pev->spawnflags, SF_NORESPAWN )) + SetTouch( &CBasePlayerItem :: DefaultTouch ); + SetThink( &CBasePlayerItem :: FallThink ); + + SetNextThink( 0.1 ); +} + +//========================================================= +// AttemptToMaterialize - the item is trying to rematerialize, +// should it do so now or wait longer? +//========================================================= +void CBasePlayerItem :: AttemptToMaterialize( void ) +{ + float time = g_pGameRules->FlWeaponTryRespawn( this ); + + if ( time == 0.0f ) + { + Materialize(); + return; + } + + SetNextThink( time ); +} + +//========================================================= +// Materialize - make a CBasePlayerItem visible and tangible +//========================================================= +void CBasePlayerItem :: Materialize( void ) +{ + if ( pev->effects & EF_NODRAW ) + { + // changing from invisible state to visible. + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, "items/suitchargeok1.wav", 1, ATTN_NORM, 0, 150 ); + pev->effects &= ~EF_NODRAW; + pev->effects |= EF_MUZZLEFLASH; + } + + pev->solid = SOLID_TRIGGER; + + UTIL_SetOrigin( this, pev->origin ); // link into world. + + // Wargon: Îðóæèå þçàáåëüíî. + SetUse( &CBasePlayerItem::DefaultUse ); + m_iItemCaps = CBaseEntity::ObjectCaps() | FCAP_IMPULSE_USE; + + if( !FBitSet( ObjectCaps(), FCAP_USE_ONLY )) + SetTouch( &CBasePlayerItem :: DefaultTouch ); + SetThink( NULL ); + +} + +//========================================================= +// CheckRespawn - a player is taking this weapon, should +// it respawn? +//========================================================= +void CBasePlayerItem :: CheckRespawn ( void ) +{ + switch ( g_pGameRules->WeaponShouldRespawn( this ) ) + { + case GR_WEAPON_RESPAWN_YES: + Respawn(); + break; + case GR_WEAPON_RESPAWN_NO: + break; + } +} + +//========================================================= +// Respawn- this item is already in the world, but it is +// invisible and intangible. Make it visible and tangible. +//========================================================= +CBaseEntity* CBasePlayerItem :: Respawn( void ) +{ + // make a copy of this weapon that is invisible and inaccessible to players (no touch function). The weapon spawn/respawn code + // will decide when to make the weapon visible and touchable. + const char *pszClassname = STRING( pev->classname ); + CBaseEntity *pNewWeapon = CBaseEntity :: Create( pszClassname, g_pGameRules->VecWeaponRespawnSpot( this ), pev->angles, pev->owner ); + + if ( pNewWeapon ) + { + pNewWeapon->pev->effects |= EF_NODRAW;// invisible for now + + // Wargon: Îðóæèå íåþçàáåëüíî. + pNewWeapon->SetUse( NULL ); + m_iItemCaps = CBaseEntity :: ObjectCaps(); + + pNewWeapon->SetTouch( NULL );// no touch + pNewWeapon->SetThink( &CBasePlayerItem :: AttemptToMaterialize ); + + DROP_TO_FLOOR( edict() ); + + // not a typo! We want to know when the weapon the player just picked up should respawn! + // this new entity we created is the replacement, + // but when it should respawn is based on conditions belonging to the weapon that was taken. + pNewWeapon->AbsoluteNextThink( g_pGameRules->FlWeaponRespawnTime( this )); + } + else + { + ALERT ( at_debug, "Respawn failed to create %s!\n", STRING( pev->classname ) ); + } + + return pNewWeapon; +} + + +//========================================================= +// FallThink - Items that have just spawned run this think +// to catch them when they hit the ground. Once we're sure +// that the object is grounded, we change its solid type +// to trigger and set it in a large box that helps the +// player get it. +//========================================================= +void CBasePlayerItem :: FallThink ( void ) +{ + SetNextThink( 0.1 ); + + if ( pev->flags & FL_ONGROUND ) + { + // clatter if we have an owner (i.e., dropped by someone) + // don't clatter if the gun is waiting to respawn (if it's waiting, it is invisible!) + if ( !FNullEnt( pev->owner ) ) + { + int pitch = 95 + RANDOM_LONG( 0, 29 ); + EMIT_SOUND_DYN( edict(), CHAN_VOICE, "items/weapondrop1.wav", 1, ATTN_NORM, 0, pitch ); + } + + // lie flat + pev->angles.x = 0; + pev->angles.z = 0; + + Materialize(); + } +} + +void CBasePlayerItem :: KnifeDecal1( void ) +{ + UTIL_StudioDecalTrace( &m_trHit, pszDecalName( 0 )); + UTIL_TraceCustomDecal( &m_trHit, pszDecalName( 0 )); +} + +void CBasePlayerItem :: KnifeDecal2( void ) +{ + UTIL_StudioDecalTrace( &m_trHit, pszDecalName( 1 )); + UTIL_TraceCustomDecal( &m_trHit, pszDecalName( 1 )); +} + +BOOL CBasePlayerItem :: HasAmmo( void ) +{ + BOOL bHasAmmo = 0; + + if ( pszAmmo1() ) + bHasAmmo |= (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] != 0); + + if ( pszAmmo2() ) + bHasAmmo |= (m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] != 0); + + if (m_iClip > 0) + bHasAmmo |= 1; + + return bHasAmmo; +} + +BOOL CBasePlayerItem :: CanDeploy( void ) +{ + if( FBitSet( iFlags(), ITEM_FLAG_SELECTONEMPTY )) + return TRUE; + + if ( !Q_stricmp( pszAmmo1(), "none" ) && !Q_stricmp( pszAmmo2(), "none" )) + { + // this weapon doesn't use ammo, can always deploy. + return TRUE; + } + + return HasAmmo(); +} + +BOOL CBasePlayerItem :: CanHolster( void ) +{ + // can't put away while guiding a missile. + if( m_iSpot && m_cActiveRockets ) + return FALSE; + return TRUE; +} + +void CBasePlayerItem :: Precache( void ) +{ + int i; + + m_iClientAnim = -1; + m_iClientSkin = -1; + m_iClientBody = -1; + + if( iViewModel() != iStringNull ) + PRECACHE_MODEL( STRING( iViewModel() )); + + if( iHandModel() != iStringNull ) + PRECACHE_MODEL( STRING( iHandModel() )); + + if( iWorldModel() != iStringNull ) + PRECACHE_MODEL( STRING( iWorldModel() )); + + for( i = 0; i < sndcnt1(); i++ ) + PRECACHE_SOUND( STRING( ItemInfoArray[m_iId].shootsound1[i] )); + + for( i = 0; i < sndcnt2(); i++ ) + PRECACHE_SOUND( STRING( ItemInfoArray[m_iId].shootsound2[i] )); + + for( i = 0; i < emptycnt(); i++ ) + PRECACHE_SOUND( STRING( ItemInfoArray[m_iId].emptysounds[i] )); + + // FIXME: add reload sounds +} + +int CBasePlayerItem :: GetItemInfo( ItemInfo *p ) +{ + // support for half-virtual weapons + if( FStringNull( pev->netname )) + pev->netname = pev->classname; + + if( ParseWeaponFile( p, STRING( pev->netname ))) + { + GenerateID(); + ALERT( at_aiconsole, "ID %i for %s\n", m_iId, STRING( pev->netname )); + p->iId = m_iId; + return 1; + } + + return 0; +} + +void CBasePlayerItem :: Spawn( void ) +{ + // support for half-virtual weapons + if( FStringNull( pev->netname )) + pev->netname = pev->classname; + + if( !FindWeaponID( )) // get actual ID + { + ALERT( at_error, "No spawn function for %s\n", STRING( pev->netname )); + UTIL_Remove( this ); + return; + } + + Precache(); + + // init default ammo + m_iDefaultAmmo1 = iDefaultAmmo1(); + m_iDefaultAmmo2 = iDefaultAmmo2(); + m_iHandModel = iHandModel(); + + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_BBOX; + pev->framerate = 1.0f; + + UTIL_SetOrigin( this, pev->origin ); + UTIL_SetSize( pev, g_vecZero, g_vecZero ); // pointsize until it lands on the ground. + + if( !FBitSet( ObjectCaps(), FCAP_USE_ONLY ) || FBitSet( pev->spawnflags, SF_NORESPAWN )) + SetTouch( DefaultTouch ); + SetThink( FallThink ); + + SET_MODEL( edict(), STRING( iWorldModel( ))); + + if( GetSequenceCount( ) > 1 ) + pev->sequence = 1; // set world animation + + pev->animtime = gpGlobals->time + 0.1; + m_iSpot = 0; + + SetNextThink( 0.1 ); +} + +bool CBasePlayerItem :: FindWeaponID( void ) +{ + for( int i = 0; i < m_iGlobalID; i++ ) + { + if( FStrEq( STRING( pev->netname ), ItemInfoArray[i].pszName )) + { + // already exist + m_iId = ItemInfoArray[i].iId; + return true; + } + } + + return false; +} + +void CBasePlayerItem :: GenerateID( void ) +{ + if( FindWeaponID() ) + return; // already exist + + if( m_iGlobalID >= WEAPON_CUSTOM_COUNT ) + { + ALERT( at_error, "GenerateID: unique weapon ID's is out. Limit is %i weapons\n", WEAPON_CUSTOM_COUNT ); + m_iId = 0; + return; + } + + m_iId = m_iGlobalID++; +} + +void CBasePlayerItem :: DefaultTouch( CBaseEntity *pOther ) +{ + // if it's not a player, ignore + if ( !pOther->IsPlayer( )) + return; + + CBasePlayer *pPlayer = (CBasePlayer *)pOther; + + // can I have this? + if ( !g_pGameRules->CanHavePlayerItem( pPlayer, this ) ) + { + if ( gEvilImpulse101 ) + { + UTIL_Remove( this ); + } + return; + } + + if ( pOther->AddPlayerItem( this )) + { + EMIT_SOUND( edict(), CHAN_ITEM, "items/gunpickup2.wav", 1, ATTN_NORM ); + AttachToPlayer( pPlayer ); + } + + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); // UNDONE: when should this happen? +} + +void CBasePlayerItem :: SetDefaultParams( ItemInfo *II ) +{ + II->iSlot = II->iPosition = 0; + II->iViewModel = MAKE_STRING( "models/view_glock.mdl" ); + II->iHandModel = iStringNull; + II->iWorldModel = MAKE_STRING( "models/world_glock.mdl" ); + Q_strcpy( II->szAnimExt, "onehanded" ); + II->pszAmmo1 = II->pszAmmo2 = "none"; + II->iMaxAmmo1 = II->iMaxAmmo2 = -1; + II->iMaxClip = -1; + II->iFlags = II->iWeight = 0; + II->attack1 = ATTACK_NONE; + II->attack2 = ATTACK_NONE; + II->fNextAttack1 = II->fNextAttack2 = 0.5f; + memset( II->shootsound1, 0, sizeof( II->shootsound1 )); + memset( II->shootsound2, 0, sizeof( II->shootsound2 )); + memset( II->emptysounds, 0, sizeof( II->emptysounds )); + II->sndcount1 = II->sndcount2 = II->emptysndcount = 0; + II->feedback1[0].punchangle[0] = II->feedback1[0].punchangle[2] = II->feedback1[0].punchangle[2] = RandomRange( 0.0f ); + II->feedback1[1].punchangle[0] = II->feedback1[1].punchangle[2] = II->feedback1[1].punchangle[2] = RandomRange( 0.0f ); + II->feedback2[0].punchangle[0] = II->feedback2[0].punchangle[2] = II->feedback2[0].punchangle[2] = RandomRange( 0.0f ); + II->feedback2[1].punchangle[0] = II->feedback2[1].punchangle[2] = II->feedback2[1].punchangle[2] = RandomRange( 0.0f ); + II->feedback1[0].recoil = II->feedback1[1].recoil = RandomRange( 0.0f ); + II->feedback2[0].recoil = II->feedback2[1].recoil = RandomRange( 0.0f ); + II->plr_settings[0].jumpHeight = -1.0f; + II->plr_settings[1].jumpHeight = -1.0f; + II->plr_settings[0].maxSpeed = 0; + II->plr_settings[1].maxSpeed = 0; + II->iVolume = NORMAL_GUN_VOLUME; + II->iFlash = NORMAL_GUN_FLASH; + II->recoil1 = RandomRange( 0.0f ); + II->recoil2 = RandomRange( 0.0f ); + II->vecThrowOffset = g_vecZero; + II->spread1[0].range = RandomRange( 6.0f, 6.0f ); + II->spread1[0].type = SPREAD_LINEAR; + II->spread1[0].expand = 0.25f; + II->spread1[1].range = RandomRange( 2.0f, 4.0f ); + II->spread1[1].type = SPREAD_LINEAR; + II->spread1[1].expand = 0.2f; + II->spread2[0].range = RandomRange( 6.0f, 6.0f ); + II->spread2[0].type = SPREAD_LINEAR; + II->spread2[0].expand = 0.25f; + II->spread2[1].range = RandomRange( 2.0f, 4.0f ); + II->spread2[1].type = SPREAD_LINEAR; + II->spread2[1].expand = 0.2f; + II->spreadtime = 1.5f; + m_iDefaultAmmo1 = 0; + m_iDefaultAmmo2 = 0; + m_iHandModel = 0; + m_iId = 0; // will be overwritten with GenerateID() +} + +int CBasePlayerItem :: ParseWeaponFile( ItemInfo *II, const char *filename ) +{ + char path[256]; + int iResult = 0; + + Q_snprintf( path, sizeof( path ), "scripts/weapons/%s", filename ); + COM_DefaultExtension( path, ".txt" ); + + char *afile = (char *)LOAD_FILE( path, NULL ); + SetDefaultParams( II ); + + if( !afile ) + { + ALERT( at_warning, "weapon info file for %s not found! Entity removed from map.\n", STRING( pev->netname )); + UTIL_Remove( this ); + return 0; + } + else + { + II->pszName = STRING( pev->netname ); + ALERT( at_aiconsole, "parse %s.txt\n", II->pszName ); + // parses the type, moves the file pointer + iResult = ParseWeaponData( II, afile ); + ALERT( at_aiconsole, "Parsing: WeaponData{} %s\n", iResult ? "OK" : "ERROR" ); + iResult = ParsePrimaryAttack( II, afile ); + ALERT( at_aiconsole, "Parsing: PrimaryAttack{} %s\n", iResult ? "OK" : "ERROR" ); + iResult = ParseSecondaryAttack( II, afile ); + ALERT( at_aiconsole, "Parsing: SecondaryAttack{} %s\n", iResult ? "OK" : "ERROR" ); + iResult = ParseSoundData( II, afile ); + ALERT( at_aiconsole, "Parsing: SoundData{} %s\n", iResult ? "OK" : "ERROR" ); + FREE_FILE( afile ); + + return 1; + } +} + +int CBasePlayerItem :: ParseItemFlags( char *pfile ) +{ + char token[256]; + int iFlags = 0; + + if( !pfile || !*pfile ) + return iFlags; + + while( pfile != NULL ) + { + pfile = COM_ParseLine( pfile, token ); + + if( !Q_stricmp( token, "SelectOnEmpty" )) + iFlags |= ITEM_FLAG_SELECTONEMPTY; + else if( !Q_stricmp( token, "NoAutoReload" )) + iFlags |= ITEM_FLAG_NOAUTORELOAD; + else if( !Q_stricmp( token, "NoAutoSwitch" )) + iFlags |= ITEM_FLAG_NOAUTOSWITCHEMPTY; + else if( !Q_stricmp( token, "LimitInWorld" )) + iFlags |= ITEM_FLAG_LIMITINWORLD; + else if( !Q_stricmp( token, "Exhaustible" )) + iFlags |= ITEM_FLAG_EXHAUSTIBLE; + else if( !Q_stricmp( token, "NoDuplicate" )) + iFlags |= ITEM_FLAG_NODUPLICATE; + else if( !Q_stricmp( token, "AutoAim" )) + iFlags |= ITEM_FLAG_USEAUTOAIM; + else if( !Q_stricmp( token, "AllowFireMode" )) + iFlags |= ITEM_FLAG_ALLOW_FIREMODE; + else if( !Q_stricmp( token, "UnderWater" )) + iFlags |= ITEM_FLAG_SHOOT_UNDERWATER; + else if( !Q_stricmp( token, "IronSight" )) + iFlags |= ITEM_FLAG_IRONSIGHT; + else if( !Q_stricmp( token, "AutoFire" )) + iFlags |= ITEM_FLAG_AUTOFIRE; + else if( !Q_stricmp( token, "Scope" )) + iFlags |= ITEM_FLAG_SCOPE; + else if( !Q_stricmp( token, "NoDrop" )) + iFlags |= ITEM_FLAG_NODROP; + else if( pfile && token[0] != '|' ) + ALERT( at_warning, "unknown value %s for 'item_flags'\n", token ); + } + + return iFlags; +} + +char *CBasePlayerItem :: ParseViewPunch( char *pfile, feedback_t *pFeed ) +{ + char token[256]; + + for( int i = 0; i < 3 && pfile != NULL; i++ ) + { + pfile = COM_ParseLine( pfile, token ); + pFeed->punchangle[i] = RandomRange( token ); + } + + return pfile; +} + +int CBasePlayerItem :: ParseWeaponData( ItemInfo *II, char *pfile ) +{ + char token[2048]; + + while( Q_stricmp( token, "WeaponData" )) + { + if( !pfile ) return 0; + pfile = COM_ParseFile( pfile, token ); + } + + while( Q_stricmp( token, "{" )) + { + if( !pfile ) return 0; + pfile = COM_ParseFile( pfile, token ); + } + + pfile = COM_ParseFile( pfile, token ); + + while( Q_stricmp( token, "}" )) + { + if( !pfile ) return 0; + + if ( !Q_stricmp( token, "viewmodel" )) + { + pfile = COM_ParseFile( pfile, token ); + II->iViewModel = ALLOC_STRING( token ); + } + else if ( !Q_stricmp( token, "handmodel" )) + { + pfile = COM_ParseFile( pfile, token ); + II->iHandModel = ALLOC_STRING( token ); + } + else if( !Q_stricmp( token, "playermodel" )) + { + pfile = COM_ParseFile( pfile, token ); + II->iWorldModel = ALLOC_STRING( token ); + } + else if( !Q_stricmp( token, "anim_prefix" )) + { + pfile = COM_ParseFile( pfile, token ); + Q_strncpy( II->szAnimExt, token, sizeof( II->szAnimExt )); + } + else if( !Q_stricmp( token, "bucket" )) + { + pfile = COM_ParseFile( pfile, token ); + II->iSlot = Q_atoi( token ); + } + else if( !Q_stricmp( token, "bucket_position" )) + { + pfile = COM_ParseFile( pfile, token ); + II->iPosition = Q_atoi( token ); + } + else if( !Q_stricmp( token, "clip_size" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !Q_stricmp( token, "noclip" )) + II->iMaxClip = WEAPON_NOCLIP; + else II->iMaxClip = Q_atoi( token ); + } + else if( !Q_stricmp( token, "primary_ammo" )) + { + pfile = COM_ParseFile( pfile, token ); + + if( Q_stricmp( token, "none" )) + { + AmmoInfo *pAmmo = UTIL_FindAmmoType( token ); + if( pAmmo ) II->iMaxAmmo1 = pAmmo->iMaxCarry; + else ALERT( at_error, "ParseWeaponData: unknown ammo type %s in 'primary_ammo'\n", token ); + } + else II->iMaxAmmo1 = -1; + + II->pszAmmo1 = STRING( ALLOC_STRING( token )); + } + else if( !Q_stricmp( token, "secondary_ammo" )) + { + pfile = COM_ParseFile( pfile, token ); + + if( Q_stricmp( token, "none" )) + { + AmmoInfo *pAmmo = UTIL_FindAmmoType( token ); + if( pAmmo ) II->iMaxAmmo2 = pAmmo->iMaxCarry; + else ALERT( at_error, "ParseWeaponData: unknown ammo type %s in 'secondary_ammo'\n", token ); + } + else II->iMaxAmmo2 = -1; + + II->pszAmmo2 = STRING( ALLOC_STRING( token )); + } + else if( !Q_stricmp( token, "defaultammo" ) || !Q_stricmp( token, "defaultammo1" )) + { + pfile = COM_ParseFile( pfile, token ); + II->iDefaultAmmo1 = RandomRange( token ); + } + else if( !Q_stricmp( token, "defaultammo2" )) + { + pfile = COM_ParseFile( pfile, token ); + II->iDefaultAmmo2 = RandomRange( token ); + } + else if( !Q_stricmp( token, "weight" )) + { + pfile = COM_ParseFile( pfile, token ); + II->iWeight = Q_atoi( token ); + } + else if( !Q_stricmp( token, "ThrowOffset" )) + { + pfile = COM_ParseFile( pfile, token ); + UTIL_StringToVector( II->vecThrowOffset, token ); + } + else if( !Q_stricmp( token, "volume" )) + { + pfile = COM_ParseFile( pfile, token ); + + if( !Q_stricmp( token, "none" )) + II->iVolume = NO_GUN_VOLUME; + else if( !Q_stricmp( token, "quiet" )) + II->iVolume = QUIET_GUN_VOLUME; + else if( !Q_stricmp( token, "normal" )) + II->iVolume = NORMAL_GUN_VOLUME; + else if( !Q_stricmp( token, "loud" )) + II->iVolume = LOUD_GUN_VOLUME; + } + else if( !Q_stricmp( token, "flash" )) + { + pfile = COM_ParseFile( pfile, token ); + + if( !Q_stricmp( token, "none" )) + II->iVolume = NO_GUN_FLASH; + else if( !Q_stricmp( token, "dim" )) + II->iVolume = DIM_GUN_FLASH; + else if( !Q_stricmp( token, "normal" )) + II->iVolume = NORMAL_GUN_FLASH; + else if( !Q_stricmp( token, "bright" )) + II->iVolume = BRIGHT_GUN_FLASH; + } + else if( !Q_stricmp( token, "SpreadTime" )) + { + pfile = COM_ParseFile( pfile, token ); + II->spreadtime = Q_atof( token ); + } + else if( !Q_stricmp( token, "MaxSpeed" )) + { + pfile = COM_ParseFile( pfile, token ); + II->plr_settings[0].maxSpeed = Q_atof( token ); + } + else if( !Q_stricmp( token, "JumpHeight" )) + { + pfile = COM_ParseFile( pfile, token ); + II->plr_settings[0].jumpHeight = Q_atof( token ); + } + else if( !Q_stricmp( token, "MaxSpeedIS" )) + { + pfile = COM_ParseFile( pfile, token ); + II->plr_settings[1].maxSpeed = Q_atof( token ); + } + else if( !Q_stricmp( token, "JumpHeightIS" )) + { + pfile = COM_ParseFile( pfile, token ); + II->plr_settings[1].jumpHeight = Q_atof( token ); + } + else if( !Q_stricmp( token, "item_flags" )) + { + pfile = COM_ParseFile( pfile, token ); + II->iFlags = ParseItemFlags( token ); + } + + pfile = COM_ParseFile( pfile, token ); + } + + return 1; +} + +int CBasePlayerItem :: ParsePrimaryAttack( ItemInfo *II, char *pfile ) +{ + char token[256]; + + while( Q_stricmp( token, "PrimaryAttack" )) + { + if( !pfile ) return 0; + pfile = COM_ParseFile( pfile, token ); + } + + while( Q_stricmp( token, "{" )) + { + if( !pfile ) return 0; + pfile = COM_ParseFile( pfile, token ); + } + + pfile = COM_ParseFile( pfile, token ); + + while( Q_stricmp( token, "}" )) + { + if( !pfile ) return 0; + + if( !Q_stricmp( token, "action" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !Q_stricmp( token, "none" )) + II->attack1 = ATTACK_NONE; + else if( !Q_stricmp( token, "ammo1" )) + II->attack1 = ATTACK_AMMO1; + else if( !Q_stricmp( token, "ammo2" )) + II->attack1 = ATTACK_AMMO2; + else if( !Q_stricmp( token, "laserdot" )) + II->attack1 = ATTACK_LASER_DOT; + else if( !Q_stricmp( token, "zoom" )) + II->attack1 = ATTACK_ZOOM; + else if( !Q_stricmp( token, "flashlight" )) + II->attack1 = ATTACK_FLASHLIGHT; + else if( !Q_stricmp( token, "switchmode" )) + II->attack1 = ATTACK_SWITCHMODE; + else if( !Q_stricmp( token, "swing" )) + II->attack1 = ATTACK_SWING; + else if( !Q_stricmp( token, "ironsight" )) + II->attack1 = ATTACK_IRONSIGHT; + else if( !Q_stricmp( token, "scope" )) + II->attack1 = ATTACK_SCOPE; + else II->attack1 = ALLOC_STRING( token ); // client command + } + else if( !Q_stricmp( token, "SpreadRange" )) + { + pfile = COM_ParseFile( pfile, token ); + II->spread1[0].range = RandomRange( token ); + } + else if( !Q_stricmp( token, "SpreadRangeIS" )) + { + pfile = COM_ParseFile( pfile, token ); + II->spread1[1].range = RandomRange( token ); + } + else if( !Q_stricmp( token, "SpreadType" )) + { + pfile = COM_ParseFile( pfile, token ); + + if( !Q_stricmp( "E_LINEAR", token )) + II->spread1[0].type = SPREAD_LINEAR; + else if( !Q_stricmp( "E_QUAD", token )) + II->spread1[0].type = SPREAD_QUAD; + else if( !Q_stricmp( "E_CUBE", token )) + II->spread1[0].type = SPREAD_CUBE; + else if( !Q_stricmp( "E_SQRT", token )) + II->spread1[0].type = SPREAD_SQRT; + else ALERT( at_warning, "ParsePrimaryAttack: unknown spread equalize type '%s'\n", token ); + } + else if( !Q_stricmp( token, "SpreadTypeIS" )) + { + pfile = COM_ParseFile( pfile, token ); + + if( !Q_stricmp( "E_LINEAR", token )) + II->spread1[1].type = SPREAD_LINEAR; + else if( !Q_stricmp( "E_QUAD", token )) + II->spread1[1].type = SPREAD_QUAD; + else if( !Q_stricmp( "E_CUBE", token )) + II->spread1[1].type = SPREAD_CUBE; + else if( !Q_stricmp( "E_SQRT", token )) + II->spread1[1].type = SPREAD_SQRT; + else ALERT( at_warning, "ParsePrimaryAttack: unknown spread equalize type '%s'\n", token ); + } + else if( !Q_stricmp( token, "SpreadExpand" )) + { + pfile = COM_ParseFile( pfile, token ); + II->spread1[0].expand = Q_atof( token ); + } + else if( !Q_stricmp( token, "SpreadExpandIS" )) + { + pfile = COM_ParseFile( pfile, token ); + II->spread1[1].expand = Q_atof( token ); + } + else if( !Q_stricmp( token, "PunchAngle" )) + { + pfile = ParseViewPunch( pfile, &II->feedback1[0] ); + } + else if( !Q_stricmp( token, "PunchAngleIS" )) + { + pfile = ParseViewPunch( pfile, &II->feedback1[1] ); + } + else if( !Q_stricmp( token, "recoil" )) + { + pfile = COM_ParseFile( pfile, token ); + II->recoil1 = RandomRange( token ); + } + else if( !Q_stricmp( token, "nextattack" )) + { + pfile = COM_ParseFile( pfile, token ); + II->fNextAttack1 = Q_atof( token ); + } + else if( !Q_stricmp( token, "SmashDecal" )) + { + pfile = COM_ParseFile( pfile, token ); + II->smashDecals[0] = ALLOC_STRING( token ); + } + + pfile = COM_ParseFile( pfile, token ); + } + + return 1; +} + +int CBasePlayerItem :: ParseSecondaryAttack( ItemInfo *II, char *pfile ) +{ + char token[256]; + + while( Q_stricmp( token, "SecondaryAttack" )) + { + if( !pfile ) return 0; + pfile = COM_ParseFile( pfile, token ); + } + + while( Q_stricmp( token, "{" )) + { + if( !pfile ) return 0; + pfile = COM_ParseFile( pfile, token ); + } + + pfile = COM_ParseFile( pfile, token ); + + while( Q_stricmp( token, "}" )) + { + if( !pfile ) return 0; + + if( !Q_stricmp( token, "action" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !Q_stricmp( token, "none" )) + II->attack2 = ATTACK_NONE; + else if( !Q_stricmp( token, "ammo1" )) + II->attack2 = ATTACK_AMMO1; + else if( !Q_stricmp( token, "ammo2" )) + II->attack2 = ATTACK_AMMO2; + else if( !Q_stricmp( token, "laserdot" )) + II->attack2 = ATTACK_LASER_DOT; + else if( !Q_stricmp( token, "zoom" )) + II->attack2 = ATTACK_ZOOM; + else if( !Q_stricmp( token, "flashlight" )) + II->attack2 = ATTACK_FLASHLIGHT; + else if( !Q_stricmp( token, "switchmode" )) + II->attack2 = ATTACK_SWITCHMODE; + else if( !Q_stricmp( token, "swing" )) + II->attack2 = ATTACK_SWING; + else if( !Q_stricmp( token, "ironsight" )) + II->attack2 = ATTACK_IRONSIGHT; + else if( !Q_stricmp( token, "scope" )) + II->attack2 = ATTACK_SCOPE; + else II->attack1 = ALLOC_STRING( token ); // client command + } + else if( !Q_stricmp( token, "SpreadRange" )) + { + pfile = COM_ParseFile( pfile, token ); + II->spread2[0].range = RandomRange( token ); + } + else if( !Q_stricmp( token, "SpreadRangeIS" )) + { + pfile = COM_ParseFile( pfile, token ); + II->spread2[1].range = RandomRange( token ); + } + else if( !Q_stricmp( token, "SpreadType" )) + { + pfile = COM_ParseFile( pfile, token ); + + if( !Q_stricmp( "E_LINEAR", token )) + II->spread2[0].type = SPREAD_LINEAR; + else if( !Q_stricmp( "E_QUAD", token )) + II->spread2[0].type = SPREAD_QUAD; + else if( !Q_stricmp( "E_CUBE", token )) + II->spread2[0].type = SPREAD_CUBE; + else if( !Q_stricmp( "E_SQRT", token )) + II->spread2[0].type = SPREAD_SQRT; + else ALERT( at_warning, "ParseSecondaryAttack: unknown spread equalize type '%s'\n", token ); + } + else if( !Q_stricmp( token, "SpreadTypeIS" )) + { + pfile = COM_ParseFile( pfile, token ); + + if( !Q_stricmp( "E_LINEAR", token )) + II->spread2[1].type = SPREAD_LINEAR; + else if( !Q_stricmp( "E_QUAD", token )) + II->spread2[1].type = SPREAD_QUAD; + else if( !Q_stricmp( "E_CUBE", token )) + II->spread2[1].type = SPREAD_CUBE; + else if( !Q_stricmp( "E_SQRT", token )) + II->spread2[1].type = SPREAD_SQRT; + else ALERT( at_warning, "ParseSecondaryAttack: unknown spread equalize type '%s'\n", token ); + } + else if( !Q_stricmp( token, "SpreadExpand" )) + { + pfile = COM_ParseFile( pfile, token ); + II->spread2[0].expand = Q_atof( token ); + } + else if( !Q_stricmp( token, "SpreadExpandIS" )) + { + pfile = COM_ParseFile( pfile, token ); + II->spread2[1].expand = Q_atof( token ); + } + else if( !Q_stricmp( token, "PunchAngle" )) + { + pfile = ParseViewPunch( pfile, &II->feedback2[0] ); + } + else if( !Q_stricmp( token, "PunchAngleIS" )) + { + pfile = ParseViewPunch( pfile, &II->feedback2[1] ); + } + else if( !Q_stricmp( token, "recoil" )) + { + pfile = COM_ParseFile( pfile, token ); + II->recoil2 = RandomRange( token ); + } + else if( !Q_stricmp( token, "nextattack" )) + { + pfile = COM_ParseFile( pfile, token ); + II->fNextAttack2 = Q_atof( token ); + } + else if( !Q_stricmp( token, "SmashDecal" )) + { + pfile = COM_ParseFile( pfile, token ); + II->smashDecals[1] = ALLOC_STRING( token ); + } + + pfile = COM_ParseFile( pfile, token ); + } + + return 1; +} + +int CBasePlayerItem :: ParseSoundData( ItemInfo *II, char *pfile ) +{ + char token[256]; + int i = 0; + int j = 0; + + while( Q_stricmp( token, "SoundData" )) + { + if( !pfile ) return 0; + pfile = COM_ParseFile( pfile, token ); + } + + while( Q_stricmp( token, "{" )) + { + if( !pfile ) return 0; + pfile = COM_ParseFile( pfile, token ); + } + + pfile = COM_ParseFile( pfile, token ); + + while( Q_stricmp( token, "}" )) + { + if( !pfile ) return 0; + + if( !Q_stricmp( token, "shootsound1" )) + { + pfile = COM_ParseFile( pfile, token ); + if( II->sndcount1 < MAX_SHOOTSOUNDS ) + { + II->shootsound1[II->sndcount1] = ALLOC_STRING( token ); + PRECACHE_SOUND( STRING( II->shootsound1[II->sndcount1] )); + II->sndcount1++; + } + else ALERT( at_warning, "Too many shoot sounds for %s\n", STRING( pev->netname )); + } + else if ( !stricmp( token, "shootsound2" )) + { + pfile = COM_ParseFile( pfile, token ); + if( II->sndcount2 < MAX_SHOOTSOUNDS ) + { + II->shootsound2[II->sndcount2] = ALLOC_STRING( token ); + PRECACHE_SOUND( STRING( II->shootsound2[II->sndcount2] )); + II->sndcount2++; + } + else ALERT( at_warning, "Too many shoot sounds for %s\n", STRING( pev->netname )); + } + else if( !stricmp( token, "emptysound" )) + { + pfile = COM_ParseFile( pfile, token ); + if( II->emptysndcount < MAX_SHOOTSOUNDS ) + { + II->emptysounds[II->emptysndcount] = ALLOC_STRING( token ); + PRECACHE_SOUND( STRING( II->emptysounds[II->emptysndcount] )); + II->emptysndcount++; + } + else ALERT( at_warning, "Too many empty sounds for %s\n", STRING( pev->netname )); + } + + pfile = COM_ParseFile( pfile, token ); + } + + return 1; +} + +int CBasePlayerItem :: GetAnimation( Activity activity ) +{ + if( !FStrEq( STRING( pev->model ), STRING( iViewModel( )))) + return -1; + + if( m_iIronSight ) + { + // translate activity in case Iron Sight is enabled + Activity new_activity = GetIronSightActivity( activity ); + + // make sure what iron sight activity is found + int iAnim = LookupActivity( new_activity ); + + if( iAnim != -1 ) return iAnim; + // fallback to normal activity + } + + return LookupActivity( activity ); +} + +int CBasePlayerItem :: SetAnimation( Activity activity, float fps ) +{ + int iSequence = GetAnimation( activity ); + + if( iSequence != -1 ) SendWeaponAnim( iSequence, fps ); + else ALERT( at_aiconsole, "%s not found\n", activity_map[activity - 1].name ); + + return iSequence; +} + +int CBasePlayerItem :: SetAnimation( char *name, float fps ) +{ + if( !FStrEq( STRING( pev->model ), STRING( iViewModel( )))) + return -1; + + int iSequence = LookupSequence( name ); + + if( iSequence != -1 ) SendWeaponAnim( iSequence, fps ); + else ALERT( at_aiconsole, "sequence \"%s\" not found\n", name ); + + return iSequence; +} + +void CBasePlayerItem :: SendWeaponAnim( int iAnim, float framerate ) +{ + if( iAnim == -1 ) return; // sequence not found + + m_pPlayer->pev->weaponanim = iAnim; + pev->framerate = framerate; + SetNextIdle( SequenceDuration( )); // auto-update idle activity + m_iClientAnim = -1; // force to send new sequence +} + +Activity CBasePlayerItem :: GetIronSightActivity( Activity act ) +{ + switch( act ) + { + case ACT_VM_IDLE: return ACT_VM_IDLE_IS; + case ACT_VM_IDLE_EMPTY: return ACT_VM_IDLE_EMPTY_IS; + case ACT_VM_RANGE_ATTACK: return ACT_VM_RANGE_ATTACK_IS; + case ACT_VM_MELEE_ATTACK: return ACT_VM_MELEE_ATTACK_IS; + case ACT_VM_SHOOT_LAST: return ACT_VM_SHOOT_LAST_IS; + case ACT_VM_LAST_MELEE_ATTACK: return ACT_VM_LAST_MELEE_ATTACK_IS; + case ACT_VM_SHOOT_EMPTY: return ACT_VM_SHOOT_EMPTY_IS; + case ACT_VM_RELOAD_EMPTY: return ACT_VM_RELOAD_EMPTY_IS; + case ACT_VM_PUMP_EMPTY: return ACT_VM_PUMP_EMPTY_IS; + case ACT_VM_RELOAD: return ACT_VM_RELOAD_IS; + case ACT_VM_PUMP: return ACT_VM_PUMP_IS; + } + + return act; // default +} + +void CBasePlayerItem :: ApplyPlayerSettings( bool bReset ) +{ + if (bReset) + { + // buz: Paranoia's speed adjustment + // return player speed to normal + m_pPlayer->pev->maxspeed = gSkillData.plrPrimaryMaxSpeed; + + // buz: set jump force + m_pPlayer->SetJumpHeight( 100.0f ); + } + else + { + // buz: Paranoia's speed adjustment + if (ClientMaxSpeed( )) m_pPlayer->pev->maxspeed = ClientMaxSpeed(); + else m_pPlayer->pev->maxspeed = gSkillData.plrPrimaryMaxSpeed; + + // buz: set jump force + if (ClientJumpHeight() != -1.0f) + m_pPlayer->SetJumpHeight( ClientJumpHeight( )); + else m_pPlayer->SetJumpHeight( 100.0f ); + } +} + +//========================================================= +// Spread system from Paranoia +//========================================================= +Vector CBasePlayerItem :: GetConeVectorForDegree( int degree ) +{ + switch( degree ) + { + case 0: return g_vecZero; + case 1: return VECTOR_CONE_1DEGREES; + case 2: return VECTOR_CONE_2DEGREES; + case 3: return VECTOR_CONE_3DEGREES; + case 4: return VECTOR_CONE_4DEGREES; + case 5: return VECTOR_CONE_5DEGREES; + case 6: return VECTOR_CONE_6DEGREES; + case 7: return VECTOR_CONE_7DEGREES; + case 8: return VECTOR_CONE_8DEGREES; + case 9: return VECTOR_CONE_9DEGREES; + case 10: return VECTOR_CONE_10DEGREES; + case 15: return VECTOR_CONE_15DEGREES; + case 20: return VECTOR_CONE_20DEGREES; + } + + return g_vecZero; +} + +void CBasePlayerItem :: DoEqualizeSpread( int type, float &spread ) +{ + switch( type ) + { + case SPREAD_QUAD: + spread = spread * spread; + break; + case SPREAD_CUBE: + spread = spread * spread * spread; + break; + case SPREAD_SQRT: + spread = sqrt( spread ); + break; + } +} + +// returns spread expand power [0..1] based on current time +float CBasePlayerItem :: CalcSpread( void ) +{ + float decay = ( gpGlobals->time - m_flLastShotTime ) / ( m_flSpreadTime * m_flLastSpreadPower ); + + if( decay > 1.0f ) return 0.0f; + + return ( 1.0f - decay ) * m_flLastSpreadPower; +} + +float CBasePlayerItem :: ExpandSpread( float expandPower ) +{ + // buz: in ducking more accuracy + // g-cont. make sure what is not a duck in the jump + if( FBitSet( m_pPlayer->pev->flags, FL_DUCKING ) && FBitSet( pev->flags, FL_ONGROUND )) + expandPower *= 0.7f; + + float curspread = CalcSpread(); + + m_flLastShotTime = gpGlobals->time; + m_flLastSpreadPower = curspread + expandPower; + + if( m_flLastSpreadPower > 2.0f ) + m_flLastSpreadPower = 2.0f; + + return curspread; +} + +Vector CBasePlayerItem :: GetSpreadVec( void ) +{ + const spread_t *info = pSpread1(); // auto-select between normal and IronSight settings + + float spread = CalcSpread(); + DoEqualizeSpread( info->type, spread ); + + Vector minspread = GetConeVectorForDegree( (int)info->range.m_flMin ); + Vector addspread = GetConeVectorForDegree( (int)info->range.m_flMax ); + Vector vecSpread = minspread + ( addspread * spread ); + + vecSpread.z = spread; // scale + + return vecSpread; +} + +Vector CBasePlayerItem :: CalcSpreadVec( const spread_t *info, float &spread ) +{ + spread = ExpandSpread( info->expand ); + + DoEqualizeSpread( info->type, spread ); + Vector minspread = GetConeVectorForDegree( (int)info->range.m_flMin ); + Vector addspread = GetConeVectorForDegree( (int)info->range.m_flMax ); + + return ( minspread + ( addspread * spread )); +} + +void CBasePlayerItem :: PlayerJump( void ) +{ + ExpandSpread( 0.7f ); +} + +void CBasePlayerItem :: PlayerRun( void ) +{ + ExpandSpread( 0.1f ); +} + +void CBasePlayerItem :: PlayerWalk( void ) +{ + ExpandSpread( 0.03f ); +} + +//========================================================= +// Zoom In\Out +//========================================================= +void CBasePlayerItem :: ZoomUpdate( void ) +{ + BOOL m_bUseZoom = FALSE; + + if( iAttack1() == ATTACK_ZOOM && FBitSet( m_pPlayer->pev->button, IN_ATTACK )) + m_bUseZoom = TRUE; + + if( iAttack2() == ATTACK_ZOOM && FBitSet( m_pPlayer->pev->button, IN_ATTACK2 )) + m_bUseZoom = TRUE; + + if( m_bUseZoom ) + { + if( m_iZoom == NOT_IN_ZOOM ) + { + if( m_flHoldTime > UTIL_WeaponTimeBase( )) + return; + + EMIT_SOUND( ENT( m_pPlayer->pev ), CHAN_ITEM, "weapons/zoom.wav", 1, ATTN_NORM ); + m_flTimeUpdate = UTIL_WeaponTimeBase() + 0.8; + m_iZoom = NORMAL_ZOOM; + } + + if( m_iZoom == NORMAL_ZOOM ) + { + m_pPlayer->m_iFOV = ZOOM_DEFAULT; // FIXME: get zoom values from weapon settings + m_pPlayer->pev->viewmodel = iStringNull; + m_iZoom = UPDATE_ZOOM; // ready to zooming, wait for 0.8 secs + } + + if( m_iZoom == UPDATE_ZOOM && m_pPlayer->m_iFOV > ZOOM_MAXIMUM ) + { + if( m_flTimeUpdate < UTIL_WeaponTimeBase( )) + { + m_flTimeUpdate = UTIL_WeaponTimeBase() + 0.02f; + m_pPlayer->m_iFOV--; + } + } + + if( m_iZoom == RESET_ZOOM ) + ZoomReset(); + } + else if( m_iZoom > NORMAL_ZOOM ) + { + m_iZoom = RESET_ZOOM; + } + +#if 0 + MESSAGE_BEGIN( MSG_ONE, gmsgZoomHUD, NULL, m_pPlayer->pev ); + WRITE_BYTE( m_iZoom ); + MESSAGE_END(); +#endif +} + +void CBasePlayerItem :: ZoomReset( void ) +{ + // return viewmodel + if( m_iZoom != NOT_IN_ZOOM ) + { + m_pPlayer->pev->viewmodel = iViewModel(); + m_pPlayer->pev->iuser3 = iHandModel(); + m_flHoldTime = UTIL_WeaponTimeBase() + 0.5; + m_pPlayer->m_iFOV = 90; + m_iZoom = NOT_IN_ZOOM; // clear zoom +#if 0 + MESSAGE_BEGIN( MSG_ONE, gmsgZoomHUD, NULL, m_pPlayer->pev ); + WRITE_BYTE( m_iZoom ); + MESSAGE_END(); +#endif + // update client data manually + m_pPlayer->UpdateClientData(); + } +} + +//========================================================= +// generic base functions +//========================================================= +BOOL CBasePlayerItem :: DefaultDeploy( Activity sequence ) +{ + m_iClientAnim = -1; + m_iClientSkin = -1; + m_iClientBody = -1; + + // init spread system + m_flSpreadTime = fSpreadTime(); + m_flLastSpreadPower = 0.0f; + m_flLastShotTime = 0.0f; + + if( PLAYER_HAS_SUIT ) + pev->body |= MILITARY_SUIT; + else pev->body &= ~MILITARY_SUIT; + + m_pPlayer->pev->viewmodel = iViewModel(); + m_pPlayer->pev->iuser3 = iHandModel(); + m_pPlayer->pev->weaponmodel = iWorldModel(); + Q_strncpy( m_pPlayer->m_szAnimExtention, szAnimExt(), sizeof( m_pPlayer->m_szAnimExtention )); + + SET_MODEL( edict(), STRING( iViewModel( ))); + + float fps = 1.0f; + + if( g_pGameRules->IsMultiplayer( )) + fps *= 1.5f; // speed up 1.5x + + if ( SetAnimation( sequence, fps ) != -1 ) + { + // make some delay before idle playing + SetNextAttack( SequenceDuration() - 0.1f ); + return TRUE; + } + + // animation missed + return FALSE; +} + +BOOL CBasePlayerItem :: DefaultHolster( Activity sequence, bool force ) +{ + m_fInReload = FALSE; + int iResult = 0; + + if( m_pSpot ) + { + // disable laser dot + EMIT_SOUND( m_pPlayer->edict(), CHAN_ITEM, "weapons/spot_off.wav", 1, ATTN_NORM ); + m_pSpot->Killed( NULL, GIB_NEVER ); + m_pSpot = NULL; + } + + m_iSkin = 0; // reset screen + ZoomReset(); + + if( iAttack1() == ATTACK_FLASHLIGHT || iAttack2() == ATTACK_FLASHLIGHT ) + ClearBits( m_pPlayer->pev->effects, EF_DIMLIGHT ); // FIXME: create new flag for weapon flashlight + + // disbale IronSight before switching to next weapon + if( m_iIronSight ) + { + m_iIronSight = 0; + + // can play full animation + if( !force ) + { + int iAnim = -1; + if( !HasAmmo( )) iAnim = GetAnimation( ACT_VM_IRONSIGHT_OFF_EMPTY ); + if( iAnim == -1 ) iAnim = GetAnimation( ACT_VM_IRONSIGHT_OFF ); + SendWeaponAnim( iAnim ); + SetNextAttack( SequenceDuration() + 0.1f ); + m_fWaitForHolster = TRUE; // queue enabled + return 0; + } + } + + // disable queue + m_fWaitForHolster = FALSE; + + float fps = 1.0f; + + if( g_pGameRules->IsMultiplayer( )) + fps *= 2.5f; + + iResult = SetAnimation( sequence, fps ); + if( iResult != -1 ) + { + // delay before switching + SetNextAttack( SequenceDuration() + 0.1f ); + iResult = 1; + } + + if( FBitSet( iFlags(), ITEM_FLAG_EXHAUSTIBLE ) && !m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] && !m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] ) + { + // no more ammo! + ClearBits( m_pPlayer->pev->weapons, BIT( m_iId )); + m_pPlayer->pev->viewmodel = iStringNull; + m_pPlayer->pev->weaponmodel = iStringNull; + SetThink( DestroyItem ); + SetNextThink( 0.5 ); + } + + // animation not found + return iResult; +} + +void CBasePlayerItem :: DefaultIdle( void ) +{ + // weapon have clip and ammo or just have ammo + if(( iMaxClip() && m_iClip ) || m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] > 0 || iMaxAmmo1() == -1 ) + { + // play random idle animation + float flRand = RANDOM_FLOAT( 0, 1.0f ); + if( flRand < 0.5f ) SetAnimation( ACT_VM_IDLE ); + else m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + RANDOM_FLOAT( 10.0f, 15.0f ); + } + else + { + SetAnimation( ACT_VM_IDLE_EMPTY ); + } +} + +BOOL CBasePlayerItem :: DefaultReload( Activity sequence ) +{ + if( m_flNextPrimaryAttack > UTIL_WeaponTimeBase( )) + return FALSE; + + if( m_flNextSecondaryAttack > UTIL_WeaponTimeBase( )) + return FALSE; + + if( m_cActiveRockets && m_iSpot ) + { + // no reloading when there are active missiles tracking the designator. + // ward off future autoreload attempts by setting next attack time into the future for a bit. + SetNextAttack( 0.5f ); + return FALSE; + } + + if( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] > 0 ) // have ammo? + { + if( iMaxClip() == m_iClip ) + return FALSE; + + if( m_iStepReload == NOT_IN_RELOAD ) + { + if( SetAnimation( ACT_VM_START_RELOAD ) != -1 ) + { + // found anim, continue + m_iStepReload = START_RELOAD; + m_fInReload = TRUE; // disable reload button + return TRUE; // start reload cycle. See also ItemPostFrame + } + else // init default reload + { + int i = Q_min( iMaxClip() - m_iClip, m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] ); + if( i == 0 ) return FALSE; + int iResult = -1; + + ZoomReset(); // reset zoom + + if( m_iClip <= 0 ) // empty clip ? + { + // iResult is error code + iResult = SetAnimation( ACT_VM_RELOAD_EMPTY ); + m_iStepReload = EMPTY_RELOAD; // it's empty reload + } + + if( iResult == -1 ) + { + SetAnimation( sequence ); + m_iStepReload = NORMAL_RELOAD; // not empty reload or sequence not found + } + + if( m_pSpot ) m_pSpot->Suspend( SequenceDuration( )); // suspend laserdot + SetNextAttack( SequenceDuration( )); + m_fInReload = TRUE; // disable reload button + + return TRUE; + } + } + else if( m_iStepReload == START_RELOAD ) + { + // continue step reload + if( m_flTimeWeaponIdle > UTIL_WeaponTimeBase( )) + return FALSE; + + // was waiting for gun to move to side + SetAnimation( sequence ); + m_iStepReload = CONTINUE_RELOAD; + } + else if( m_iStepReload == CONTINUE_RELOAD ) + { + // Add them to the clip + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + m_iStepReload = START_RELOAD; + m_iClip++; + + if( m_iClip == iMaxClip( )) + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.1; + } + + return TRUE; + } + + return FALSE; +} + +BOOL CBasePlayerItem :: DefaultSwing( int primary ) +{ + int fDidHit = FALSE; + + TraceResult tr; + + UTIL_MakeVectors( m_pPlayer->pev->v_angle ); + Vector vecSrc = m_pPlayer->GetGunPosition(); + Vector vecEnd = vecSrc + gpGlobals->v_forward * 32.0f; + + UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, ENT( m_pPlayer->pev ), &tr ); + + if ( tr.flFraction >= 1.0 ) + { + UTIL_TraceHull( vecSrc, vecEnd, dont_ignore_monsters, head_hull, m_pPlayer->edict(), &tr ); + + if ( tr.flFraction < 1.0 ) + { + // Calculate the point of intersection of the line (or hull) and the object we hit + // This is and approximation of the "best" intersection + CBaseEntity *pHit = CBaseEntity::Instance( tr.pHit ); + if ( !pHit || pHit->IsBSPModel() ) + UTIL_FindHullIntersection( vecSrc, tr, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX, m_pPlayer->edict() ); + vecEnd = tr.vecEndPos; // This is the point on the actual surface (the hull could have hit space) + } + } + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + if ( tr.flFraction >= 1.0f ) + { + if( emptycnt( )) + EMIT_SOUND( m_pPlayer->edict(), CHAN_WEAPON, STRING( EmptySnd( )), 1.0, ATTN_NORM ); + + if (primary) + { + // HACKHACK: ACT_VM_SHOOT_EMPTY_IS as miss attack for knife + if( LookupActivity( ACT_VM_SHOOT_EMPTY_IS ) != ACTIVITY_NOT_AVAILABLE ) + SetAnimation( ACT_VM_SHOOT_EMPTY_IS ); + else SetAnimation( ACT_VM_RANGE_ATTACK ); + } + else SetAnimation( ACT_VM_SHOOT_EMPTY ); + } + else + { + if (primary) SetAnimation( ACT_VM_RANGE_ATTACK ); + else SetAnimation( ACT_VM_MELEE_ATTACK ); + + // hit + fDidHit = TRUE; + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + + ClearMultiDamage( ); + + // Wargon: Èñïðàâëåíî ãèáàíèå íîæåì. + if( primary ) pEntity->TraceAttack(m_pPlayer->pev, gSkillData.plrDmgCrowbar, gpGlobals->v_forward, &tr, DMG_CLUB | DMG_NEVERGIB ); + else pEntity->TraceAttack(m_pPlayer->pev, gSkillData.plrDmgCrowbarSec, gpGlobals->v_forward, &tr, DMG_CLUB | DMG_NEVERGIB ); + + ApplyMultiDamage( m_pPlayer->pev, m_pPlayer->pev ); + + // play thwack, smack, or dong sound + float flVol = 1.0f; + int fHitWorld = TRUE; + + if (pEntity) + { + if ( pEntity->Classify() != CLASS_NONE && pEntity->Classify() != CLASS_MACHINE ) + { + if( primary ) + { + if( sndcnt1( )) + EMIT_SOUND( m_pPlayer->edict(), CHAN_WEAPON, STRING( ShootSnd1( )), 1.0, ATTN_NORM ); + } + else + { + // FIXME: hardcoded sound + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/knife_stab.wav", 1, ATTN_NORM); + } + + m_pPlayer->m_iWeaponVolume = KNIFE_BODYHIT_VOLUME; + + if ( !pEntity->IsAlive() ) + return TRUE; + else flVol = 0.1; + + fHitWorld = FALSE; + } + } + + // play texture hit sound + // UNDONE: Calculate the correct point of intersection when we hit with the hull instead of the line + if (fHitWorld) + { + float fvolbar = TEXTURETYPE_PlaySound( &tr, vecSrc, vecSrc + (vecEnd-vecSrc) * 2.0f, BULLET_STAB ); + + if( sndcnt2( )) + { + const char *pszSound = STRING( ShootSnd2()); + + EMIT_SOUND_DYN( m_pPlayer->edict(), CHAN_WEAPON, pszSound, fvolbar, ATTN_NORM, 0, RANDOM_LONG( 98, 102 )); + } + } + + // delay the decal a bit + m_trHit = tr; + + m_pPlayer->m_iWeaponVolume = flVol * KNIFE_WALLHIT_VOLUME; + + if (primary) + { + SetThink( &CBasePlayerItem :: KnifeDecal1 ); + SetNextThink( 0.1f ); + } + else + { + SetThink( &CBasePlayerItem :: KnifeDecal2 ); + SetNextThink( 0.15f ); + } + } + + return fDidHit; +} + +BOOL CBasePlayerItem :: PlayEmptySound( void ) +{ + if( m_iPlayEmptySound ) + { + if( emptycnt( )) + EMIT_SOUND( m_pPlayer->edict(), CHAN_WEAPON, STRING( EmptySnd( )), 0.8, ATTN_NORM ); + m_iPlayEmptySound = 0; + return TRUE; + } + + return FALSE; +} + +void CBasePlayerItem :: ResetEmptySound( void ) +{ + m_iPlayEmptySound = 1; +} + +void CBasePlayerItem :: PlayAttackSound( int primary ) +{ + if( primary ) + { + if( sndcnt1( )) EMIT_SOUND( m_pPlayer->edict(), CHAN_WEAPON, STRING( ShootSnd1( )), 1.0, ATTN_NORM ); + } + else + { + if( sndcnt2( )) EMIT_SOUND( m_pPlayer->edict(), CHAN_WEAPON, STRING( ShootSnd2( )), 1.0, ATTN_NORM ); + } +} + +int CBasePlayerItem :: GetCurrentAttack( const char *ammo, int primary ) +{ + AmmoInfo *pAmmo = UTIL_FindAmmoType( ammo ); + + int iResult; + + if( !pAmmo || !Q_stricmp( ammo, "none" )) // no ammo or ammo not found + { + // just play animation and sound + if( primary ) iResult = ( SetAnimation( ACT_VM_RANGE_ATTACK ) == -1 ) ? 0 : 1; + else iResult = ( SetAnimation( ACT_VM_MELEE_ATTACK ) == -1 ) ? 0 : 1; + + PlayAttackSound( primary ); + SetPlayerEffects(); + } + else if( pAmmo->iMissileClassName != iStringNull ) + { + // missile attack (throw entity) + iResult = ThrowGeneric( ammo, primary ); + } + else + { + // shoot bullets (default case) + iResult = ShootGeneric( ammo, primary ); + } + + return iResult; +} + +int CBasePlayerItem :: PlayCurrentAttack( int action, int primary ) +{ + int iResult = 0; + + if( action == ATTACK_ZOOM ) + iResult = 1; // See void ZoomUpdate( void ); for details + else if( action == ATTACK_AMMO1 ) + iResult = GetCurrentAttack( pszAmmo1(), primary ); + else if( action == ATTACK_AMMO2 ) + iResult = GetCurrentAttack( pszAmmo2(), primary ); + else if( action == ATTACK_LASER_DOT ) + { + m_iSpot = !m_iSpot; + if( !m_iSpot && m_pSpot ) + { + // disable laser dot + EMIT_SOUND( ENT( m_pPlayer->pev ), CHAN_ITEM, "weapons/spot_off.wav", 1, ATTN_NORM ); + m_pSpot->Killed( NULL, GIB_NEVER ); + m_pSpot = NULL; + m_iSkin = 0; // disable screen + } + iResult = 1; + } + else if( action == ATTACK_FLASHLIGHT ) + { + if( FBitSet( m_pPlayer->pev->effects, EF_DIMLIGHT )) + { + ClearBits( m_pPlayer->pev->effects, EF_DIMLIGHT ); + EMIT_SOUND_DYN( ENT( m_pPlayer->pev ), CHAN_WEAPON, SOUND_FLASHLIGHT_OFF, 1.0, ATTN_NORM, 0, PITCH_NORM ); + } + else + { + SetBits( m_pPlayer->pev->effects, EF_DIMLIGHT ); + EMIT_SOUND_DYN( ENT( m_pPlayer->pev ), CHAN_WEAPON, SOUND_FLASHLIGHT_ON, 1.0, ATTN_NORM, 0, PITCH_NORM ); + } + iResult = 1; + } + else if( action == ATTACK_SWITCHMODE ) + { + if( !m_iBody ) + { + m_iBody = 1; + pev->button = 1; // enable custom firemode + SetAnimation( ACT_VM_TURNON ); + } + else + { + SetAnimation( ACT_VM_TURNOFF ); + pev->button = 0; // disable custom firemode + m_iClientBody = m_iBody = 0; // make delay before change + } + iResult = 1; + } + else if( action == ATTACK_SWING ) + { + iResult = DefaultSwing( primary ); + } + else if( action == ATTACK_IRONSIGHT ) + { + int iAnim = -1; + + if( !m_iIronSight ) + { + if( !HasAmmo( )) iAnim = GetAnimation( ACT_VM_IRONSIGHT_ON_EMPTY ); + if( iAnim == -1 ) iAnim = GetAnimation( ACT_VM_IRONSIGHT_ON ); + m_iIronSight = 1; + } + else + { + if( !HasAmmo( )) iAnim = GetAnimation( ACT_VM_IRONSIGHT_OFF_EMPTY ); + if( iAnim == -1 ) iAnim = GetAnimation( ACT_VM_IRONSIGHT_OFF ); + m_iIronSight = 0; + } + + ApplyPlayerSettings( FALSE ); + SendWeaponAnim( iAnim ); + iResult = 1; + } + else if( action == ATTACK_SCOPE ) + { + int iAnim = -1; + + if( m_pPlayer->m_iGasMaskOn ) + { + UTIL_ShowMessage( "#GAS_AND_SCOPE", m_pPlayer ); + SetNextAttack( 0.5f ); + return -1; + } + else if( m_pPlayer->m_iHeadShieldOn ) + { + UTIL_ShowMessage( "#SCOPE_AND_SHIELD", m_pPlayer ); + SetNextAttack( 0.5f ); + return -1; + } + + if( !m_iIronSight ) + { + if( !HasAmmo( )) iAnim = GetAnimation( ACT_VM_IRONSIGHT_ON_EMPTY ); + if( iAnim == -1 ) iAnim = GetAnimation( ACT_VM_IRONSIGHT_ON ); + m_iIronSight = 1; + } + else + { + if( !HasAmmo( )) iAnim = GetAnimation( ACT_VM_IRONSIGHT_OFF_EMPTY ); + if( iAnim == -1 ) iAnim = GetAnimation( ACT_VM_IRONSIGHT_OFF ); + m_iIronSight = 0; + } + + ApplyPlayerSettings( FALSE ); + SendWeaponAnim( iAnim ); + iResult = 1; + } + else if( action == ATTACK_NONE ) + { + return -1; + } + else + { + // just command + char command[64]; + + if( !strncmp( STRING( action ), "fire ", 5 )) + { + char *target = (char *)STRING( action ); + target = target + 5; // remove "fire " + FireTargets( target, m_pPlayer, this, USE_TOGGLE, 1.0f ); // activate target + } + else + { + Q_snprintf( command, sizeof( command ), "%s\n", STRING( action )); + SERVER_COMMAND( command ); + } + + // just play animation and sound + if( primary ) SetAnimation( ACT_VM_RANGE_ATTACK ); + else SetAnimation( ACT_VM_MELEE_ATTACK ); + + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + PlayAttackSound( primary ); + } + + return iResult; +} + +//========================================================= +// Get Atatck Info by AmmoInfo +//========================================================= +int CBasePlayerItem :: GetAmmoType( const char *ammo ) +{ + if( !Q_stricmp( ammo, pszAmmo1() )) + return AMMO_PRIMARY; // primary ammo + + if( !Q_stricmp( ammo, pszAmmo2() )) + return AMMO_SECONDARY; // secondary ammo + + return AMMO_UNKNOWN; // no ammo +} + +int CBasePlayerItem :: UseAmmo( const char *ammo, int count ) +{ + int ammoType = GetAmmoType( ammo ); + + if( ammoType == AMMO_UNKNOWN ) + { + return -1; + } + else if( ammoType == AMMO_PRIMARY ) + { + if( iMaxClip() == -1 && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] > 0 ) + { + // noclip + if( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] < count ) + count = 1; + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= count; + } + else if( m_iClip > 0 ) + { + // have clip + if( m_iClip < count ) + count = 1; + m_iClip -= count; + } + else + { + // ammo is out + return 0; + } + } + else if( ammoType == AMMO_SECONDARY ) + { + if( m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] > 0 ) + { + if( m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] < count ) + count = 1; + m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] -= count; + } + else + { + // ammo is out + return 0; + } + } + + return 1; +} + +// client effects (muzzleflash, decals, shell, etc) +void CBasePlayerItem :: PlayClientFire( const Vector &vecDir, float spread, int iAnim, int shellidx, string_t shootsnd, int cShots ) +{ + int soundidx = 0; + + // params sent table: + // args->fparam1 = vecDir.x + // args->fparam2 = vecDir.y + // args->iparam1 = iAnim + // args->iparam2 = spread * 255 + // args->bparam1 = shellindex (bool expanded up to 16 bits) + // args->bparam2 = soundindex (bool expanded up to 16 bits) + + if( shootsnd != iStringNull ) + soundidx = PRECACHE_SOUND( STRING( shootsnd )); + + PLAYBACK_EVENT_FULL( 0, m_pPlayer->edict(), g_usShootEvent, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, + vecDir.x, vecDir.y, (iAnim & 0x0FFF)|((cShots & 0xF )<<12 ), (int)( spread * 255 ), shellidx, soundidx ); +} + +void CBasePlayerItem :: SetPlayerEffects( void ) +{ + m_pPlayer->m_iWeaponVolume = iVolume(); + m_pPlayer->m_iWeaponFlash = iFlash(); + + if( m_pPlayer->m_iWeaponFlash ) + SetBits( m_pPlayer->pev->effects, EF_MUZZLEFLASH ); + + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); // player animation +} + +float CBasePlayerItem :: AutoAimDelta( int primary ) +{ + const spread_t *info = (primary) ? pSpread1() : pSpread2(); + + return GetConeVectorForDegree( (int)info->range.m_flMax ).x; +} + +int CBasePlayerItem :: ShootGeneric( const char *ammo, int primary, int cShots ) +{ + if( m_pPlayer->pev->waterlevel != 3 || FBitSet( iFlags(), ITEM_FLAG_SHOOT_UNDERWATER )) + { + // have ammo and player not underwater + if( !UseAmmo( ammo, cShots )) + return 0; + + SetPlayerEffects(); + + // viewmodel animation + ZoomReset(); + + int iAnim = -1; + + if( primary ) + { + if( iMaxClip() && !m_iClip ) + iAnim = GetAnimation( ACT_VM_SHOOT_LAST ); + if( iAnim == -1 ) + iAnim = GetAnimation( ACT_VM_RANGE_ATTACK ); + } + else + { + if( !m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] ) + iAnim = GetAnimation( ACT_VM_LAST_MELEE_ATTACK ); + if( iAnim == -1 ) + iAnim = GetAnimation( ACT_VM_MELEE_ATTACK ); + } + + float spread, flDistance = 8192.0f; // set max distance + Vector vecSrc = m_pPlayer->GetGunPosition(); + Vector vecAiming = gpGlobals->v_forward; + Vector vecSpread = CalcSpreadVec((primary) ? pSpread1() : pSpread2(), spread ); + + if( FBitSet( iFlags(), ITEM_FLAG_USEAUTOAIM )) + { + vecAiming = m_pPlayer->GetAutoaimVector( AutoAimDelta( primary )); + } + else + { + UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); + vecAiming = gpGlobals->v_forward; + } + + AmmoInfo *pInfo = UTIL_FindAmmoType( ammo ); + int shellIndex = 0; + float flDamage = 0.0f; + int numShots = 1; + + if( pInfo != NULL ) + { + shellIndex = pInfo->iShellIndex; + flDamage = pInfo->flPlayerDamage; + flDistance = pInfo->flDistance; + numShots = pInfo->iNumShots; + } + + Vector vecDir = m_pPlayer->FireBulletsPlayer( cShots * numShots, vecSrc, vecAiming, vecSpread, flDistance, flDamage, + m_pPlayer->pev, m_pPlayer->random_seed ); + + PlayClientFire( vecDir, spread, iAnim, shellIndex, (primary) ? ShootSnd1() : ShootSnd2( ), cShots * numShots ); + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + RANDOM_FLOAT ( 10, 15 ); + + return 1; + } + + return 0; +} + +int CBasePlayerItem :: ThrowGeneric( const char *ammo, int primary, int cShots ) +{ + // have ammo and player not underwater + if( !UseAmmo( ammo, cShots )) + return 0; + + SetPlayerEffects(); + + // viewmodel animation + ZoomReset(); + + int iAnim = -1; + + if( primary ) + { + if( iMaxClip() && !m_iClip ) + iAnim = GetAnimation( ACT_VM_SHOOT_LAST ); + if( iAnim == -1 ) + iAnim = GetAnimation( ACT_VM_RANGE_ATTACK ); + + if( sndcnt1( )) + EMIT_SOUND( m_pPlayer->edict(), CHAN_WEAPON, STRING( ShootSnd1( )), 1.0, ATTN_NORM ); + } + else + { + if( !m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] ) + iAnim = GetAnimation( ACT_VM_LAST_MELEE_ATTACK ); + if( iAnim == -1 ) + iAnim = GetAnimation( ACT_VM_MELEE_ATTACK ); + + if( sndcnt2( )) + EMIT_SOUND( m_pPlayer->edict(), CHAN_WEAPON, STRING( ShootSnd2( )), 1.0, ATTN_NORM ); + } + + SendWeaponAnim( iAnim ); + + AmmoInfo *pInfo = UTIL_FindAmmoType( ammo ); + + if( !pInfo ) return -1; // ammo not found + + float flDamage = pInfo->flPlayerDamage; + float flDistance = pInfo->flDistance; + float spread; + + UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); +#if 1 + Vector vecSrc = m_pPlayer->GetGunPosition() + gpGlobals->v_forward * vecThrowOffset().x + + gpGlobals->v_right * vecThrowOffset().y + gpGlobals->v_up * vecThrowOffset().z; +#else + Vector vecSrc = m_pPlayer->GetGunPosition() + gpGlobals->v_forward * weapon_x.value + + gpGlobals->v_right * weapon_y.value + gpGlobals->v_up * weapon_z.value; +#endif + Vector vecAiming = gpGlobals->v_forward; + Vector vecSpread = CalcSpreadVec((primary) ? pSpread1() : pSpread2(), spread ); + + if( FBitSet( iFlags(), ITEM_FLAG_USEAUTOAIM )) + { + vecAiming = m_pPlayer->GetAutoaimVector( AutoAimDelta( primary )); + } + else + { + UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); + vecAiming = gpGlobals->v_forward; + } + + // FIXME: this is a limitation of weapon system. Make generic tuneable projectile someday... + if( FStrEq( STRING( pInfo->iMissileClassName ), "rpg_rocket" )) + { + CRpgRocket *pRocket = CRpgRocket :: CreateRpgRocket( vecSrc, m_pPlayer->pev->v_angle, m_pPlayer, this ); + pRocket->pev->velocity = vecAiming * flDistance; + pRocket->pev->velocity += vecAiming * DotProduct( m_pPlayer->pev->velocity, vecAiming ); + pRocket->pev->dmg = flDamage; + + return 1; + } + else if( FStrEq( STRING( pInfo->iMissileClassName ), "grenade_vog25" )) + { + CGrenade *pGrenade = CGrenade :: ShootGeneric( m_pPlayer, vecSrc, vecAiming * flDistance, pInfo->iMissileClassName ); + pGrenade->pev->gravity = 0.5;// lower gravity since grenade is aerodynamic and engine doesn't know it. + pGrenade->pev->dmg = flDamage; + + return 1; + } + + return 0; +} + +//========================================================= +// called by the new item with the existing item as parameter +// +// if we call ExtractAmmo(), it's because the player is picking up this type of weapon for +// the first time. If it is spawned by the world, m_iDefaultAmmo will have a default ammo amount in it. +// if this is a weapon dropped by a dying player, has 0 m_iDefaultAmmo, which means only the ammo in +// the weapon clip comes along. +//========================================================= +int CBasePlayerItem :: ExtractAmmo( CBasePlayerItem *pWeapon, BOOL duplicate ) +{ + int iResult = 0; + + if( pszAmmo1( )) + { + iResult |= pWeapon->AddPrimaryAmmo( m_iDefaultAmmo1, pszAmmo1(), iMaxClip(), iMaxAmmo1(), duplicate ); + m_iDefaultAmmo1 = 0; + } + + if( pszAmmo2( )) + { + iResult |= pWeapon->AddSecondaryAmmo( m_iDefaultAmmo2, pszAmmo2(), iMaxAmmo2() ); + m_iDefaultAmmo2 = 0; + } + + return iResult; +} + +//========================================================= +// called by the new item's class with the existing item as parameter +//========================================================= +int CBasePlayerItem :: ExtractClipAmmo( CBasePlayerItem *pWeapon ) +{ + int iAmmo; + + if( m_iClip == WEAPON_NOCLIP ) + iAmmo = 0; // guns with no clips always come empty if they are second-hand + else iAmmo = m_iClip; + + return pWeapon->m_pPlayer->GiveAmmo( iAmmo, pszAmmo1()); // , &m_iPrimaryAmmoType +} + +BOOL CBasePlayerItem :: AddPrimaryAmmo( int iCount, const char *szName, int iMaxClip, int iMaxCarry, BOOL duplicate ) +{ + int iIdAmmo; + + if( iMaxClip < 1 ) + { + m_iClip = WEAPON_NOCLIP; + iIdAmmo = m_pPlayer->GiveAmmo( iCount, szName ); + } + else if( m_iClip == 0 && duplicate == FALSE ) + { + int i = Q_min( m_iClip + iCount, iMaxClip ) - m_iClip; + m_iClip += i; + iIdAmmo = m_pPlayer->GiveAmmo( iCount - i, szName ); + } + else + { + iIdAmmo = m_pPlayer->GiveAmmo( iCount, szName ); + } + + if( iIdAmmo > 0 ) + { + m_iPrimaryAmmoType = iIdAmmo; + + if( m_pPlayer->HasPlayerItem( this )) + { + // play the "got ammo" sound only if we gave some ammo to a player that already had this gun. + // if the player is just getting this gun for the first time, DefaultTouch will play the "picked up gun" sound for us. + EMIT_SOUND( edict(), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM ); + } + } + + return iIdAmmo > 0 ? TRUE : FALSE; +} + + +BOOL CBasePlayerItem :: AddSecondaryAmmo( int iCount, const char *szName, int iMax ) +{ + int iIdAmmo; + + iIdAmmo = m_pPlayer->GiveAmmo( iCount, szName ); + + //m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] = iMax; // hack for testing + + if (iIdAmmo > 0) + { + m_iSecondaryAmmoType = iIdAmmo; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + } + return iIdAmmo > 0 ? TRUE : FALSE; +} + +// CALLED THROUGH the newly-touched weapon's instance. The existing player weapon is pOriginal +int CBasePlayerItem :: AddDuplicate( CBasePlayerItem *pOriginal ) +{ + if( FBitSet( iFlags(), ITEM_FLAG_NODUPLICATE )) + return FALSE; + + if( m_iDefaultAmmo1 || m_iDefaultAmmo2 ) + return ExtractAmmo( pOriginal, TRUE ); + return ExtractClipAmmo( pOriginal ); +} + +void CBasePlayerItem :: ItemPostFrame( void ) +{ + if( m_iSpot && !m_pSpot ) + { + // enable laser dot + m_pSpot = CLaserSpot :: CreateSpot(); + EMIT_SOUND( ENT( m_pPlayer->pev ), CHAN_ITEM, "weapons/spot_on.wav", 1, ATTN_NORM ); + } + + if( !m_iSpot && m_pSpot ) + { + // disable laser dot + EMIT_SOUND( ENT( m_pPlayer->pev ), CHAN_ITEM, "weapons/spot_off.wav", 1, ATTN_NORM ); + m_pSpot->Killed( NULL, GIB_NEVER ); + m_pSpot = NULL; + } + + if( m_pSpot ) + m_pSpot->Update( m_pPlayer ); + + ZoomUpdate(); // update zoom + + if( m_iStepReload == NOT_IN_RELOAD ) + m_fInReload = FALSE; // finished + + if( m_flTimeUpdate < UTIL_WeaponTimeBase( )) + PostIdle(); // catch all + + if( m_fInReload && m_pPlayer->m_flNextAttack <= UTIL_WeaponTimeBase( )) + { + if( m_iStepReload > CONTINUE_RELOAD ) + { + // complete the reload. + int j = Q_min( iMaxClip() - m_iClip, m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]); + + // Add them to the clip + m_iClip += j; + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= j; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.01; // play PostReload + m_fInReload = FALSE; + } + } + + if( FBitSet( m_pPlayer->pev->button, IN_ATTACK2 ) && ( m_flNextSecondaryAttack <= gpGlobals->time )) + { + SecondaryAttack(); +// ClearBits( m_pPlayer->pev->button, IN_ATTACK2 ); + } + else if( FBitSet( m_pPlayer->pev->button, IN_ATTACK ) && ( m_flNextPrimaryAttack <= gpGlobals->time )) + { + PrimaryAttack(); +// ClearBits( m_pPlayer->pev->button, IN_ATTACK ); + } + else if( FBitSet( m_pPlayer->pev->button, IN_RELOAD ) && iMaxClip() != WEAPON_NOCLIP && !m_fInReload ) + { + // reload when reload is pressed, or if no buttons are down and weapon is empty. + Reload(); + } + else if( !FBitSet( m_pPlayer->pev->button, IN_ATTACK | IN_ATTACK2 )) + { + // play sequence holster / deploy if player find or lose suit + if(( PLAYER_HAS_SUIT && !PLAYER_DRAW_SUIT ) || ( !PLAYER_HAS_SUIT && PLAYER_DRAW_SUIT )) + { + m_pPlayer->m_pActiveItem->Holster( true ); + m_pPlayer->QueueItem( this ); + if( m_pPlayer->m_pActiveItem ) + m_pPlayer->m_pActiveItem->Deploy(); + } + + if( !CanDeploy() && m_flNextPrimaryAttack < gpGlobals->time ) + { + // weapon isn't useable, switch. + if( !FBitSet( iFlags(), ITEM_FLAG_NOAUTOSWITCHEMPTY ) && g_pGameRules->GetNextBestWeapon( m_pPlayer, this )) + { + m_flNextPrimaryAttack = gpGlobals->time + 0.3; + return; + } + } + else + { + // weapon is useable. Reload if empty and weapon has waited as long as it has to after firing + if( m_iClip == 0 && !FBitSet( iFlags(), ITEM_FLAG_NOAUTORELOAD ) && m_flNextPrimaryAttack < gpGlobals->time ) + { + Reload(); + return; + } + } + + m_iPlayEmptySound = 1; // reset empty sound + if( FBitSet( iFlags(), ITEM_FLAG_USEAUTOAIM )) + m_pPlayer->GetAutoaimVector( AutoAimDelta( TRUE )); + + if( m_flTimeWeaponIdle < UTIL_WeaponTimeBase( )) + { + // step reload + if( m_iStepReload > NOT_IN_RELOAD ) + { + if( m_iClip != iMaxClip() && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] ) + { + Reload(); + } + else + { + // reload debounce has timed out + if( IsEmptyReload( )) + { + if( SetAnimation( ACT_VM_PUMP_EMPTY ) == -1 ) + SetAnimation( ACT_VM_PUMP ); + } + else SetAnimation( ACT_VM_PUMP ); + PostReload(); // post effects + m_iStepReload = NOT_IN_RELOAD; + } + } + else + { + WeaponIdle(); + } + return; + } + } + + // catch all + if( ShouldWeaponIdle( )) + { + WeaponIdle(); + } +} + +void CBasePlayerItem :: DestroyItem( void ) +{ + if( m_pPlayer ) + { + // if attached to a player, remove. + m_pPlayer->RemovePlayerItem( this ); + } + + Kill( ); +} + +int CBasePlayerItem :: AddToPlayer( CBasePlayer *pPlayer ) +{ + m_pPlayer = pPlayer; + + pPlayer->pev->weapons |= BIT( m_iId ); + + pev->sequence = 0; // to avoid some strange problems + + if( !m_iPrimaryAmmoType ) + m_iPrimaryAmmoType = pPlayer->GetAmmoIndex( pszAmmo1() ); + + if( !m_iSecondaryAmmoType ) + m_iSecondaryAmmoType = pPlayer->GetAmmoIndex( pszAmmo2() ); + + if( FBitSet( iFlags(), ITEM_FLAG_AUTOFIRE )) + m_iWeaponAutoFire = TRUE; // enable auto-fire as default + + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + + return AddWeapon(); +} + +void CBasePlayerItem :: Drop( void ) +{ + // Wargon: Îðóæèå íåþçàáåëüíî. + SetUse( NULL ); + m_iItemCaps = CBaseEntity :: ObjectCaps(); + + SetTouch( NULL ); + SetThink( &CBasePlayerItem :: SUB_Remove ); + SetNextThink( 0.1 ); +} + +void CBasePlayerItem :: Kill( void ) +{ + // Wargon: Îðóæèå íåþçàáåëüíî. + SetUse( NULL ); + m_iItemCaps = CBaseEntity :: ObjectCaps(); + + SetTouch( NULL ); + SetThink( &CBasePlayerItem :: SUB_Remove ); + SetNextThink( 0.1 ); +} + +void CBasePlayerItem :: Deploy( void ) +{ + ApplyPlayerSettings( FALSE ); + + m_iStepReload = 0; + if( iMaxClip() > 0 && m_iClip <= 0 ) // weapon have clip and clip is empty ? + { + if( !DefaultDeploy( ACT_VM_DEPLOY_EMPTY )) // try to playing "deploy_empty" anim + DefaultDeploy( ACT_VM_DEPLOY ); // custom animation not found, play standard animation + } + else DefaultDeploy( ACT_VM_DEPLOY ); // just playing standard anim +} + +void CBasePlayerItem :: Holster( bool force ) +{ + ApplyPlayerSettings( TRUE ); + + if( iMaxClip() > 0 && m_iClip <= 0 ) // weapon have clip and clip is empty ? + { + if( !DefaultHolster( ACT_VM_HOLSTER_EMPTY, force )) // try to playing "holster_empty" anim + DefaultHolster( ACT_VM_HOLSTER, force ); + } + else DefaultHolster( ACT_VM_HOLSTER, force ); // just playing standard anim +} + +void CBasePlayerItem :: WeaponToggleMode( void ) +{ + if( !FBitSet( iFlags(), ITEM_FLAG_ALLOW_FIREMODE )) + return; + + if( m_iWeaponAutoFire ) + { + UTIL_ShowMessage( "#WEAPON_SEMI_AUTO", m_pPlayer ); + m_iWeaponAutoFire = 0; + } + else + { + UTIL_ShowMessage( "#WEAPON_FULL_AUTO", m_pPlayer ); + m_iWeaponAutoFire = 1; + } + + EMIT_SOUND( m_pPlayer->edict(), CHAN_WEAPON, "weapons/weapon_chengemode.wav", 0.8, ATTN_NORM ); + + // FIXME: play anim here +} + +void CBasePlayerItem :: AttachToPlayer ( CBasePlayer *pPlayer ) +{ + pev->movetype = MOVETYPE_FOLLOW; + pev->aiment = pPlayer->edict(); + pev->effects = EF_NODRAW; + pev->solid = SOLID_NOT; + pev->targetname = iStringNull; + pev->owner = pPlayer->edict(); + + SetNextThink( 0.1 ); + SetTouch( NULL ); + SetThink( NULL ); + SetUse( NULL ); + + m_iItemCaps = CBaseEntity :: ObjectCaps(); +} + +int CBasePlayerItem :: UpdateClientData( CBasePlayer *pPlayer ) +{ + BOOL bSend = FALSE; + int state = 0; + + // update weapon body + if( m_iClientBody != m_iBody ) + { + pev->body = (pev->body % NUM_HANDS) + NUM_HANDS * m_iBody; + MESSAGE_BEGIN( MSG_ONE, gmsgWeaponBody, NULL, m_pPlayer->pev ); + WRITE_BYTE( pev->body ); + MESSAGE_END(); + m_iClientBody = m_iBody; + } + + // update weapon skin + if( m_iClientSkin != m_iSkin ) + { + pev->skin = m_iSkin; + MESSAGE_BEGIN( MSG_ONE, gmsgWeaponSkin, NULL, m_pPlayer->pev ); + WRITE_BYTE( pev->skin ); + MESSAGE_END(); + m_iClientSkin = m_iSkin; + } + + // update weapon anim + if( pPlayer->pev->weaponanim != m_iClientAnim ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeaponAnim, NULL, m_pPlayer->pev ); + WRITE_BYTE( pPlayer->pev->weaponanim ); // sequence number + WRITE_BYTE( pev->framerate * 8 ); // playing speed + MESSAGE_END(); + + m_iClientAnim = pPlayer->pev->weaponanim; + } + + if( pPlayer->m_pActiveItem == this ) + { + if( pPlayer->m_fOnTarget ) + state = WEAPON_IS_ONTARGET; + else state = 1; + } + + // forcing send of all data! + if( !pPlayer->m_fWeapon ) + bSend = TRUE; + + // This is the current or last weapon, so the state will need to be updated + if( this == pPlayer->m_pActiveItem || this == pPlayer->m_pClientActiveItem ) + { + if( pPlayer->m_pActiveItem != pPlayer->m_pClientActiveItem ) + { + bSend = TRUE; + } + } + + // if the ammo, state, or fov has changed, update the weapon + if( m_iClip != m_iClientClip || state != m_iClientWeaponState || pPlayer->m_iFOV != pPlayer->m_iClientFOV ) + { + bSend = TRUE; + } + + if( bSend ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pPlayer->pev ); + WRITE_BYTE( state ); + WRITE_BYTE( m_iId ); + WRITE_BYTE( m_iClip ); + MESSAGE_END(); + + m_iClientClip = m_iClip; + m_iClientWeaponState = state; + pPlayer->m_fWeapon = TRUE; + } + + if( m_pNext ) + m_pNext->UpdateClientData( pPlayer ); + + return 1; +} + +//========================================================= +// IsUseable - this function determines whether or not a +// weapon is useable by the player in its current state. +// (does it have ammo loaded? do I have any ammo for the +// weapon?, etc) +//========================================================= +BOOL CBasePlayerItem :: IsUseable( void ) +{ + if( m_iClip <= 0 && ( m_pPlayer->m_rgAmmo[PrimaryAmmoIndex()] <= 0 && iMaxAmmo1() != -1 )) + { + return FALSE; + } + return TRUE; +} + +//========================================================= +//========================================================= +int CBasePlayerItem :: PrimaryAmmoIndex( void ) +{ + return m_iPrimaryAmmoType; +} + +//========================================================= +//========================================================= +int CBasePlayerItem :: SecondaryAmmoIndex( void ) +{ + return -1; +} + +//========================================================= +// RetireWeapon - no more ammo for this gun, put it away. +//========================================================= +void CBasePlayerItem :: RetireWeapon( void ) +{ + // first, no viewmodel at all. + m_pPlayer->pev->viewmodel = iStringNull; + m_pPlayer->pev->weaponmodel = iStringNull; + //m_pPlayer->pev->viewmodelindex = NULL; + + g_pGameRules->GetNextBestWeapon( m_pPlayer, this ); +} + +//********************************************************* +// weaponbox code: +//********************************************************* +LINK_ENTITY_TO_CLASS( weaponbox, CWeaponBox ); + +TYPEDESCRIPTION CWeaponBox :: m_SaveData[] = +{ + DEFINE_ARRAY( CWeaponBox, m_rgAmmo, FIELD_INTEGER, MAX_AMMO_SLOTS ), + DEFINE_ARRAY( CWeaponBox, m_rgiszAmmo, FIELD_STRING, MAX_AMMO_SLOTS ), + DEFINE_ARRAY( CWeaponBox, m_rgpPlayerItems, FIELD_CLASSPTR, MAX_ITEM_TYPES ), + DEFINE_FIELD( CWeaponBox, m_cAmmoTypes, FIELD_INTEGER ), +}; IMPLEMENT_SAVERESTORE( CWeaponBox, CBaseEntity ); + +//========================================================= +// +//========================================================= +void CWeaponBox :: Precache( void ) +{ + if( pev->model != iStringNull ) + PRECACHE_MODEL( STRING( pev->model )); +} + +//========================================================= +//========================================================= +void CWeaponBox :: KeyValue( KeyValueData *pkvd ) +{ + if( FStrEq( pkvd->szKeyName, "reflection" )) + { + switch(atoi(pkvd->szValue)) + { + case 1: pev->effects |= EF_NOREFLECT; break; + case 2: pev->effects |= EF_REFLECTONLY; break; + } + } + else if ( m_cAmmoTypes < MAX_AMMO_SLOTS ) + { + PackAmmo( ALLOC_STRING(pkvd->szKeyName), atoi(pkvd->szValue) ); + m_cAmmoTypes++;// count this new ammo type. + + pkvd->fHandled = TRUE; + } + else + { + ALERT( at_error, "WeaponBox too full! only %d ammotypes allowed\n", MAX_AMMO_SLOTS ); + } +} + +//========================================================= +// CWeaponBox - Spawn +//========================================================= +void CWeaponBox :: Spawn( void ) +{ + if( GET_SERVER_STATE() == SERVER_LOADING ) + pev->model = MAKE_STRING( "models/w_weaponbox.mdl" ); + + Precache( ); + + pev->movetype = MOVETYPE_TOSS; + pev->solid = SOLID_TRIGGER; + + UTIL_SetSize( pev, g_vecZero, g_vecZero ); + + if( pev->model != iStringNull ) + SET_MODEL( ENT(pev), STRING( pev->model )); +} + +//========================================================= +// CWeaponBox - Kill - the think function that removes the +// box from the world. +//========================================================= +void CWeaponBox :: Kill( void ) +{ + CBasePlayerItem *pWeapon; + int i; + + // destroy the weapons + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + pWeapon = m_rgpPlayerItems[ i ]; + + while ( pWeapon ) + { + pWeapon->SetThink(&CBasePlayerItem::SUB_Remove); + pWeapon->SetNextThink( 0.1 ); + pWeapon = pWeapon->m_pNext; + } + } + + // remove the box + UTIL_Remove( this ); +} + +//========================================================= +// CWeaponBox - Touch: try to add my contents to the toucher +// if the toucher is a player. +//========================================================= +void CWeaponBox :: Touch( CBaseEntity *pOther ) +{ + if ( !(pev->flags & FL_ONGROUND ) ) + { + return; + } + + if ( !pOther->IsPlayer() ) + { + // only players may touch a weaponbox. + return; + } + + if ( !pOther->IsAlive() ) + { + // no dead guys. + return; + } + + CBasePlayer *pPlayer = (CBasePlayer *)pOther; + int i; + + // dole out ammo + for ( i = 0 ; i < MAX_AMMO_SLOTS ; i++ ) + { + if ( !FStringNull( m_rgiszAmmo[ i ] ) ) + { + // there's some ammo of this type. + pPlayer->GiveAmmo( m_rgAmmo[ i ], STRING( m_rgiszAmmo[ i ] )); + + // now empty the ammo from the weaponbox since we just gave it to the player + m_rgiszAmmo[ i ] = iStringNull; + m_rgAmmo[ i ] = 0; + } + } + +// go through my weapons and try to give the usable ones to the player. +// it's important the the player be given ammo first, so the weapons code doesn't refuse +// to deploy a better weapon that the player may pick up because he has no ammo for it. + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + CBasePlayerItem *pItem; + + // have at least one weapon in this slot + while ( m_rgpPlayerItems[ i ] ) + { + pItem = m_rgpPlayerItems[ i ]; + m_rgpPlayerItems[ i ] = m_rgpPlayerItems[ i ]->m_pNext;// unlink this weapon from the box + + if ( pPlayer->AddPlayerItem( pItem ) ) + { + pItem->AttachToPlayer( pPlayer ); + } + } + } + } + + EMIT_SOUND( pOther->edict(), CHAN_ITEM, "items/gunpickup2.wav", 1, ATTN_NORM ); + SetTouch(NULL); + UTIL_Remove(this); +} + +//========================================================= +// CWeaponBox - PackWeapon: Add this weapon to the box +//========================================================= +BOOL CWeaponBox :: PackWeapon( CBasePlayerItem *pWeapon ) +{ + // is one of these weapons already packed in this box? + if ( HasWeapon( pWeapon ) ) + { + return FALSE;// box can only hold one of each weapon type + } + + if ( pWeapon->m_pPlayer ) + { + if ( !pWeapon->m_pPlayer->RemovePlayerItem( pWeapon ) ) + { + // failed to unhook the weapon from the player! + return FALSE; + } + } + + int iWeaponSlot = pWeapon->iItemSlot(); + + if ( m_rgpPlayerItems[ iWeaponSlot ] ) + { + // there's already one weapon in this slot, so link this into the slot's column + pWeapon->m_pNext = m_rgpPlayerItems[ iWeaponSlot ]; + m_rgpPlayerItems[ iWeaponSlot ] = pWeapon; + } + else + { + // first weapon we have for this slot + m_rgpPlayerItems[ iWeaponSlot ] = pWeapon; + pWeapon->m_pNext = NULL; + } + + pWeapon->pev->spawnflags |= SF_NORESPAWN;// never respawn + pWeapon->pev->movetype = MOVETYPE_NONE; + pWeapon->pev->solid = SOLID_NOT; + pWeapon->pev->effects |= EF_NODRAW; + pWeapon->pev->modelindex = 0; + pWeapon->pev->model = iStringNull; + pWeapon->pev->owner = edict(); + pWeapon->SetThink( NULL );// crowbar may be trying to swing again, etc. + pWeapon->SetTouch( NULL ); + pWeapon->m_pPlayer = NULL; + + return TRUE; +} + +//========================================================= +// MaxAmmoCarry - pass in a name and this function will tell +// you the maximum amount of that type of ammunition that a +// player can carry. +//========================================================= +int CWeaponBox :: MaxAmmoCarry( int iszName ) +{ + for ( int i = 0; i < MAX_WEAPONS; i++ ) + { + if ( CBasePlayerItem::ItemInfoArray[i].pszAmmo1 && !Q_strcmp( STRING(iszName), CBasePlayerItem::ItemInfoArray[i].pszAmmo1 ) ) + return CBasePlayerItem::ItemInfoArray[i].iMaxAmmo1; + if ( CBasePlayerItem::ItemInfoArray[i].pszAmmo2 && !Q_strcmp( STRING(iszName), CBasePlayerItem::ItemInfoArray[i].pszAmmo2 ) ) + return CBasePlayerItem::ItemInfoArray[i].iMaxAmmo2; + } + + ALERT( at_error, "MaxAmmoCarry() doesn't recognize '%s'!\n", STRING( iszName ) ); + + return -1; +} + +//========================================================= +// CWeaponBox - PackAmmo +//========================================================= +BOOL CWeaponBox :: PackAmmo( int iszName, int iCount ) +{ + int iMaxCarry; + + if ( FStringNull( iszName ) ) + { + // error here + ALERT ( at_debug, "NULL String in PackAmmo!\n" ); + return FALSE; + } + + iMaxCarry = MaxAmmoCarry( iszName ); + + if ( iMaxCarry != -1 && iCount > 0 ) + { + GiveAmmo( iCount, STRING( iszName )); + return TRUE; + } + + return FALSE; +} + +//========================================================= +// CWeaponBox - GiveAmmo +//========================================================= +int CWeaponBox :: GiveAmmo( int iCount, const char *szName ) +{ + int i; + + for (i = 1; i < MAX_AMMO_SLOTS && !FStringNull( m_rgiszAmmo[i] ); i++) + { + if (!Q_stricmp( szName, STRING( m_rgiszAmmo[i] ))) + { + AmmoInfo *pAmmo = UTIL_FindAmmoType( szName ); + + if( !pAmmo ) return -1; + + int iAdd = Q_min( iCount, pAmmo->iMaxCarry - m_rgAmmo[i] ); + if ( iCount == 0 || iAdd > 0 ) + { + m_rgAmmo[i] += iAdd; + return i; + } + + return -1; + } + } + + if (i < MAX_AMMO_SLOTS) + { + m_rgiszAmmo[i] = MAKE_STRING( szName ); + m_rgAmmo[i] = iCount; + return i; + } + + ALERT( at_debug, "out of named ammo slots\n"); + + return i; +} + +//========================================================= +// CWeaponBox::HasWeapon - is a weapon of this type already +// packed in this box? +//========================================================= +BOOL CWeaponBox :: HasWeapon( CBasePlayerItem *pCheckItem ) +{ + CBasePlayerItem *pItem = m_rgpPlayerItems[pCheckItem->iItemSlot()]; + + while (pItem) + { + if (FClassnameIs( pItem->pev, STRING( pCheckItem->pev->classname) )) + { + return TRUE; + } + pItem = pItem->m_pNext; + } + + return FALSE; +} + +//========================================================= +// CWeaponBox::IsEmpty - is there anything in this box? +//========================================================= +BOOL CWeaponBox :: IsEmpty( void ) +{ + int i; + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( m_rgpPlayerItems[ i ] ) + { + return FALSE; + } + } + + for ( i = 0 ; i < MAX_AMMO_SLOTS ; i++ ) + { + if ( !FStringNull( m_rgiszAmmo[ i ] ) ) + { + // still have a bit of this type of ammo + return FALSE; + } + } + + return TRUE; +} + +//========================================================= +//========================================================= +void CWeaponBox :: SetObjectCollisionBox( void ) +{ + if( !Q_stricmp( STRING( pev->model ), "models/w_weaponbox.mdl" )) + { + pev->absmin = pev->origin + Vector(-16, -16, 0); + pev->absmax = pev->origin + Vector(16, 16, 16); + } + else + { + pev->absmin = pev->origin + Vector(-24, -24, 0); + pev->absmax = pev->origin + Vector(24, 24, 16); + } +} + +// buz: painkiller item +class CPainkiller : public CBasePlayerAmmo +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_painkiller.mdl"); + CBasePlayerAmmo::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_painkiller.mdl"); + PRECACHE_SOUND("items/painkiller_pickup.wav"); + } + BOOL AddAmmo( CBaseEntity *pOther ) + { + int bResult = (pOther->GiveAmmo( 1, "painkillers" ) != -1 ); + if (bResult) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/painkiller_pickup.wav", 1, ATTN_NORM); + pOther->pev->weapons |= (1< +#include "effects.h" +#include "player.h" + +class CBasePlayer; +class CBasePlayerItem; +extern int gmsgWeapPickup; + +// Contact Grenade / Timed grenade +class CGrenade : public CBaseMonster +{ +public: + void Spawn( void ); + + static CGrenade *ShootTimed( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity, float time, float damage = 100.0f ); + static CGrenade *ShootContact( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ); + static CGrenade *ShootGeneric( CBaseEntity *pOwner, Vector vecStart, Vector vecVelocity, string_t classname ); + + void Explode( Vector vecSrc, Vector vecAim ); + void Explode( TraceResult *pTrace, int bitsDamageType ); + void EXPORT Smoke( void ); + + void EXPORT BounceTouch( CBaseEntity *pOther ); + void EXPORT SlideTouch( CBaseEntity *pOther ); + void EXPORT ExplodeTouch( CBaseEntity *pOther ); + void EXPORT DangerSoundThink( void ); + void EXPORT PreDetonate( void ); + void EXPORT Detonate( void ); + void EXPORT DetonateUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT TumbleThink( void ); + + virtual void BounceSound( void ); + virtual int BloodColor( void ) { return DONT_BLEED; } + virtual void Killed( entvars_t *pevAttacker, int iGib ); + + BOOL m_fRegisteredSound;// whether or not this grenade has issued its DANGER sound to the world sound list yet. +}; + +class CRpgRocket : public CGrenade +{ +public: + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + void EXPORT FollowThink( void ); + void EXPORT IgniteThink( void ); + void EXPORT RocketTouch( CBaseEntity *pOther ); + static CRpgRocket *CreateRpgRocket( Vector vecOrigin, Vector vecAngles, CBaseEntity *pOwner, CBasePlayerItem *pLauncher ); + + int m_iFireTrail; // Wargon: ïåðåìåííàÿ äëÿ îãíåííîãî ñëåäà ó ðàêåò. + int m_iTrail; + float m_flIgniteTime; + CBasePlayerItem *m_pLauncher;// pointer back to the launcher that fired me. +}; + +class CLaserSpot : public CBaseEntity +{ + void Spawn( void ); + void Precache( void ); + int ObjectCaps( void ) { return FCAP_DONT_SAVE; } +public: + void Update( CBasePlayer *m_pPlayer ); + void Suspend( float flSuspendTime ); + void EXPORT Revive( void ); + + static CLaserSpot *CreateSpot( void ); + static CLaserSpot *CreateSpot( const char* spritename ); +}; + +// hardcoded ID's +#define WEAPON_NONE 0 +#define WEAPON_PAINKILLER 31 +#define WEAPON_CUSTOM_COUNT WEAPON_PAINKILLER +#define WEAPON_ALLWEAPONS (~BIT( WEAPON_PAINKILLER )) + +#define MAX_WEAPONS 32 // a 32-bit integer limit +#define MAX_AMMO_DESC 256 // various combinations of entities that contain ammo +#define MAX_SHOOTSOUNDS 8 // max of four random shoot sounds +#define MAX_NORMAL_BATTERY 100 + +// suit definitions +#define BAREHAND_SUIT 0 // just in case +#define MILITARY_SUIT 1 // gordon suit +#define NUM_HANDS 2 // number of hands: barney and gordon + +#define PLAYER_HAS_SUIT ( FBitSet( m_pPlayer->m_iHideHUD, ITEM_SUIT )) +#define PLAYER_DRAW_SUIT ( FBitSet( pev->body, MILITARY_SUIT )) + +// the maximum amount of ammo each weapon's clip can hold +#define WEAPON_NOCLIP -1 + +// reload code +#define NOT_IN_RELOAD 0 +#define START_RELOAD 1 +#define CONTINUE_RELOAD 2 // for shotgun +#define EMPTY_RELOAD 3 +#define NORMAL_RELOAD 4 + +// zoom code +#define NOT_IN_ZOOM 0 // zoom not used +#define NORMAL_ZOOM 1 // default zooming (button one shot) +#define UPDATE_ZOOM 2 // increase zoom (button holding down) +#define RESET_ZOOM 3 // disable zoom + +#define ZOOM_MAXIMUM 20 +#define ZOOM_DEFAULT 50 + +// ammo code +#define AMMO_UNKNOWN 0 +#define AMMO_PRIMARY 1 +#define AMMO_SECONDARY 2 + +#define ITEM_FLAG_SELECTONEMPTY BIT( 0 ) // can select while ammo is out +#define ITEM_FLAG_NOAUTORELOAD BIT( 1 ) // manual reload only +#define ITEM_FLAG_NOAUTOSWITCHEMPTY BIT( 2 ) // don't switch to another gun while ammo is out +#define ITEM_FLAG_LIMITINWORLD BIT( 3 ) // limit count in world (e.g. explodables) +#define ITEM_FLAG_EXHAUSTIBLE BIT( 4 ) // A player can totally exhaust their ammo supply and lose this weapon +#define ITEM_FLAG_NODUPLICATE BIT( 5 ) // player can't give this weapon again (e.g. knife, crowbar) +#define ITEM_FLAG_USEAUTOAIM BIT( 6 ) // weapon uses autoaim +#define ITEM_FLAG_ALLOW_FIREMODE BIT( 7 ) // allow to change firemode +#define ITEM_FLAG_SHOOT_UNDERWATER BIT( 8 ) // weapon can be shoot underwater (e.g. glock) +#define ITEM_FLAG_IRONSIGHT BIT( 9 ) // enable client effects: DOF, breathing etc +#define ITEM_FLAG_AUTOFIRE BIT( 10 ) // hold attack for auto-fire +#define ITEM_FLAG_SCOPE BIT( 11 ) // using scope instead of IronSight +#define ITEM_FLAG_NODROP BIT( 12 ) // don't drop this weapon + +#define WEAPON_IS_ONTARGET 0x40 + +typedef enum +{ + ATTACK_NONE = 0, // no action + ATTACK_AMMO1, // fire primary ammo + ATTACK_AMMO2, // fire secondary ammo + ATTACK_LASER_DOT, // enable laser dot + ATTACK_ZOOM, // enable zoom + ATTACK_FLASHLIGHT, // enable flashlight + ATTACK_SWITCHMODE, // play two beetwen anims and change body + ATTACK_SWING, // knife swing + ATTACK_IRONSIGHT, // iron sight on and off + ATTACK_SCOPE // iron sight & scope +} e_attack; + +enum +{ + SPREAD_LINEAR = 0, // èçìåíåíèå ðàçáðîñà ëèíåéíî âî âðåìåíè + SPREAD_QUAD, // ïî ïàðàáîëå (âíà÷àëå óçêèé, ïîòîì ðåçêî ðàñøèðÿåòñÿ) + SPREAD_CUBE, // êóáè÷åñêàÿ ïàðàáîëà + SPREAD_SQRT, // íàîáîðîò (áûñòðî ðàñøèðÿåòñÿ è ïëàâíî ïåðåõîäèò â ìàêñèìàëüíûé) +}; + +typedef struct +{ + RandomRange range; // min spread .. max spread + int type; // spread equalize + float expand; // spread expand +} spread_t; + +typedef struct +{ + float maxSpeed; // clamp at maximum + float jumpHeight; // clamp at maximum +} player_settings_t; + +// client feedback +typedef struct +{ + RandomRange punchangle[3]; // range of x,y,z + RandomRange recoil; +} feedback_t; + +typedef struct +{ + int iSlot; // hud slot + int iPosition; // hud position + string_t iViewModel; // path to viewmodel + string_t iHandModel; // path to optional hands model + string_t iWorldModel; // path to worldmodel + char szAnimExt[16]; // player anim postfix + const char *pszAmmo1; // ammo 1 type + int iMaxAmmo1; // max ammo 1 + const char *pszAmmo2; // ammo 2 type + int iMaxAmmo2; // max ammo 2 + RandomRange iDefaultAmmo1; // default primary ammo + RandomRange iDefaultAmmo2; // default secondary ammo + const char *pszName; // unique weapon name + int iMaxClip; // clip size + int iId; // unique weapon ID + int iFlags; // misc flags + int iWeight; // this value used to determine this weapon's importance in autoselection. + int attack1; // attack1 type + int attack2; // attack2 type + float fNextAttack1; // nextattack + float fNextAttack2; // next secondary attack + Vector vecThrowOffset; // throw view offset for melee grenades + string_t shootsound1[MAX_SHOOTSOUNDS]; // primary attack sounds + string_t shootsound2[MAX_SHOOTSOUNDS]; // secondary attack sounds + string_t emptysounds[MAX_SHOOTSOUNDS]; // empty sound + int sndcount1; // primary attack sound count + int sndcount2; // secondary attack sound count + int emptysndcount; // empty sounds count + string_t smashDecals[2]; // decal groups for swing weapon (crowbar, knife) + spread_t spread1[2]; // customizable spread from Paranoia + spread_t spread2[2]; // same but for secondary attack + feedback_t feedback1[2]; // client feedback for primary attack (recoil, punch, speed etc) + feedback_t feedback2[2]; // same but for secondary attack + player_settings_t plr_settings[2]; // player speed and player jumpheight + float spreadtime; // time to return to normal spread + int iVolume; // weapon volume + int iFlash; // weapon flash + RandomRange recoil1; // recoil 1 attack + RandomRange recoil2; // recoil 2 attack +} ItemInfo; + +typedef struct +{ + const char *pszName; + int iMaxCarry; + int iShellIndex; // precached shell model index + int iNumShots; // > 1 is fraction + string_t iMissileClassName; // it's a projectile ammo (grenades etc) + float flDistance; // typically 2048 for fractions + float flPlayerDamage; + float flMonsterDamage; + int iId; +} AmmoInfo; + +// ammo_entity description +typedef struct +{ + string_t classname; // to link with real entity + string_t ammomodel; // model to show + string_t clipsound; // sound when touch entity + AmmoInfo *type; // pointer to ammo specification + RandomRange count; +} AmmoDesc; + +// Items that the player has in their inventory that they can use +class CBasePlayerItem : public CBaseAnimating +{ +public: + // buz: get advance spread vec to send it to client + // gun dont use advanced spread by default + virtual Vector GetSpreadVec( void ); + + // buz: gun jumping actions + virtual void PlayerJump( void ); + virtual void PlayerWalk( void ); + virtual void PlayerRun( void ); + + // buz: get current weapon mode (for toggleable weapons) + virtual int GetMode( void ) + { + if( FBitSet( iFlags(), ITEM_FLAG_SCOPE|ITEM_FLAG_IRONSIGHT ) && !m_fInReload ) + { + if( FBitSet( iFlags(), ITEM_FLAG_SCOPE )) + return (m_iIronSight) ? 3 : 1; + return (m_iIronSight + 1); + } + return 0; // 0 means gun dont use mode + } + + virtual void WeaponToggleMode( void ); + + virtual void SetObjectCollisionBox( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + virtual int AddToPlayer( CBasePlayer *pPlayer ); // return TRUE if the item you want the item added to the player inventory + virtual int AddDuplicate( CBasePlayerItem *pItem ); // return TRUE if you want your duplicate removed from world + void EXPORT DestroyItem( void ); + void EXPORT DefaultTouch( CBaseEntity *pOther ); // default weapon touch + void EXPORT KnifeDecal1( void ); + void EXPORT KnifeDecal2( void ); + void Precache( void ); + void Spawn( void ); + + // Wargon: Ïåðåìåííûå äëÿ þçàáåëüíîñòè îðóæèé. + void EXPORT DefaultUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { DefaultTouch( pActivator ); } + virtual int ObjectCaps( void ) { return m_iItemCaps | FCAP_ACROSS_TRANSITION | FCAP_USE_ONLY; } + + // generic "shared" ammo handlers + BOOL AddPrimaryAmmo( int iCount, const char *szName, int iMaxClip, int iMaxCarry, BOOL duplicate ); + BOOL AddSecondaryAmmo( int iCount, const char *szName, int iMaxCarry ); + + void EXPORT FallThink ( void );// when an item is first spawned, this think is run to determine when the object has hit the ground. + void EXPORT Materialize( void );// make a weapon visible and tangible + void EXPORT AttemptToMaterialize( void ); // the weapon desires to become visible and tangible, if the game rules allow for it + CBaseEntity* Respawn ( void );// copy a weapon + void FallInit( void ); + void CheckRespawn( void ); + virtual int GetItemInfo( ItemInfo *p ); + virtual BOOL CanDeploy( void ); + virtual BOOL CanHolster( void ); // can this weapon be put away right now? + virtual BOOL IsUseable( void ); + virtual BOOL HasAmmo( void ); + virtual BOOL WaitForHolster( void ) { return m_fWaitForHolster; } + virtual BOOL AllowToDrop( void ) { return !FBitSet( iFlags(), ITEM_FLAG_NODROP ); } + + virtual void ItemPreFrame( void ) { return; } // called each frame by the player PreThink + virtual void ItemPostFrame( void ); // called each frame by the player PostThink + + virtual int ExtractAmmo( CBasePlayerItem *pWeapon, BOOL duplicate ); // Return TRUE if you can add ammo to yourself when picked up + virtual int ExtractClipAmmo( CBasePlayerItem *pWeapon ); // Return TRUE if you can add ammo to yourself when picked up + + virtual int AddWeapon( void ) { ExtractAmmo( this, FALSE ); return TRUE; }; // Return TRUE if you want to add yourself to the player + + virtual void PlayClientFire( const Vector &vecDir, float spread, int iAnim, int shellidx, string_t shootsnd, int cShots = 1 ); + virtual void SendWeaponAnim( int iAnim, float framerate = 1.0f ); + + virtual void Drop( void ); + virtual void Kill( void ); + virtual void AttachToPlayer ( CBasePlayer *pPlayer ); + + virtual int PrimaryAmmoIndex( void ); + virtual int SecondaryAmmoIndex( void ); + + virtual int UpdateClientData( CBasePlayer *pPlayer ); + virtual void UpdateItemInfo( void ) {}; // updates HUD state + + Vector GetConeVectorForDegree( int degree ); + Vector CalcSpreadVec( const spread_t *info, float &spread ); + void DoEqualizeSpread( int type, float &spread ); + float ExpandSpread( float expandPower ); + float CalcSpread( void ); + + void SetDefaultParams( ItemInfo *II ); + + int ParseWeaponFile( ItemInfo *II, const char *filename ); // parse weapon_*.txt + int ParseWeaponData( ItemInfo *II, char *file ); // parse WeaponData {} + int ParsePrimaryAttack( ItemInfo *II, char *pfile ); // parse PrimaryAttack {} + int ParseSecondaryAttack( ItemInfo *II, char *pfile ); // parse SeconadryAttack {} + int ParseSoundData( ItemInfo *II, char *pfile ); // parse SoundData {} + char *ParseViewPunch( char *pfile, feedback_t *pFeed ); + int ParseItemFlags( char *pfile ); + // HudData will be parsed on the client side + virtual void GenerateID( void ); // generate unique ID number for each weapon + virtual bool FindWeaponID( void ); + + int GetAnimation( Activity activity ); + int SetAnimation( Activity activity, float fps = 1.0f ); + int SetAnimation( char *name, float fps = 1.0f ); + Activity GetIronSightActivity( Activity act ); + int UseAmmo( const char *ammo, int count ); + int GetAmmoType( const char *ammo ); // incredible stupid way... + + inline int IsEmptyReload( void ) { return m_iStepReload == EMPTY_RELOAD ? TRUE : FALSE; } + int ShootGeneric( const char *ammo, int primary, int cShots = 1 ); + int ThrowGeneric( const char *ammo, int primary, int cShots = 1 ); + int GetCurrentAttack( const char *ammo, int primary ); + int PlayCurrentAttack( int action, int primary ); + void PlayAttackSound( int primary ); + void ApplyPlayerSettings( bool bReset ); + void SetPlayerEffects( void ); + float AutoAimDelta( int primary ); + + float SetNextAttack( float delay ) { return m_pPlayer->m_flNextAttack = gpGlobals->time + delay; } + float SetNextIdle( float delay ) { return m_flTimeWeaponIdle = gpGlobals->time + delay; } + float SequenceDuration( void ) { return CBaseAnimating :: SequenceDuration( m_pPlayer->pev->weaponanim ); } + + static ItemInfo ItemInfoArray[MAX_WEAPONS]; + static int m_iGlobalID; // unique ID for each weapon + + // weapon routines + void ZoomUpdate( void ); + void ZoomReset( void ); + + CBasePlayer *m_pPlayer; + CBasePlayerItem *m_pNext; + CLaserSpot *m_pSpot; // LTD spot (don't save this, becase spot have FCAP_DONT_SAVE flag) + int m_iId; // WEAPON_??? + int m_iItemCaps; + TraceResult m_trHit; // for knife + BOOL m_fWaitForHolster; // weapon waiting for holster + string_t m_iHandModel; // in case it present + + // virtual methods for ItemInfo acess + int iItemPosition( void ) { return ItemInfoArray[m_iId].iPosition; } + int iItemSlot( void ) { return ItemInfoArray[m_iId].iSlot + 1; } + int iViewModel( void ) { return ItemInfoArray[m_iId].iViewModel; } + int iHandModel( void ) { return ItemInfoArray[m_iId].iHandModel; } + int iWorldModel( void ) { return ItemInfoArray[m_iId].iWorldModel; } + int iDefaultAmmo1( void ) { return (int)ItemInfoArray[m_iId].iDefaultAmmo1.Random(); } + int iDefaultAmmo2( void ) { return (int)ItemInfoArray[m_iId].iDefaultAmmo2.Random(); } + int iMaxAmmo1( void ) { return ItemInfoArray[m_iId].iMaxAmmo1; } + int iMaxAmmo2( void ) { return ItemInfoArray[m_iId].iMaxAmmo2; } + int iMaxClip( void ) { return ItemInfoArray[m_iId].iMaxClip; } + int iWeight( void ) { return ItemInfoArray[m_iId].iWeight; } + int iFlags( void ) { return ItemInfoArray[m_iId].iFlags; } + int iAttack1( void ) { return ItemInfoArray[m_iId].attack1; } + int sndcnt1( void ) { return ItemInfoArray[m_iId].sndcount1; } + int sndcnt2( void ) { return ItemInfoArray[m_iId].sndcount2; } + int emptycnt( void ) { return ItemInfoArray[m_iId].emptysndcount; } + string_t ShootSnd1( void ) { return ItemInfoArray[m_iId].shootsound1[RANDOM_LONG( 0, sndcnt1( ) - 1)]; } + string_t ShootSnd2( void ) { return ItemInfoArray[m_iId].shootsound2[RANDOM_LONG( 0, sndcnt2( ) - 1)]; } + string_t EmptySnd( void ) { return ItemInfoArray[m_iId].emptysounds[RANDOM_LONG( 0, emptycnt() - 1)]; } + int iAttack2( void ) { return ItemInfoArray[m_iId].attack2; } + char *szAnimExt( void ) { return ItemInfoArray[m_iId].szAnimExt; } + float fNextAttack1( void ) { return ItemInfoArray[m_iId].fNextAttack1; } + float fNextAttack2( void ) { return ItemInfoArray[m_iId].fNextAttack2; } + float fRecoil1( void ) { return ItemInfoArray[m_iId].recoil1.Random(); } + float fRecoil2( void ) { return ItemInfoArray[m_iId].recoil2.Random(); } + const char *pszAmmo1( void ) { return ItemInfoArray[m_iId].pszAmmo1; } + const char *pszName( void ) { return ItemInfoArray[m_iId].pszName; } + const char *pszAmmo2( void ) { return ItemInfoArray[m_iId].pszAmmo2; } + const spread_t *pSpread1( void ) { return &ItemInfoArray[m_iId].spread1[m_iIronSight]; } + const spread_t *pSpread2( void ) { return &ItemInfoArray[m_iId].spread2[m_iIronSight]; } + const feedback_t *pFeedback1( void ) { return &ItemInfoArray[m_iId].feedback1[m_iIronSight]; } + const feedback_t *pFeedback2( void ) { return &ItemInfoArray[m_iId].feedback2[m_iIronSight]; } + const char *pszDecalName( int type ) { return STRING( ItemInfoArray[m_iId].smashDecals[type] ); } + float ClientMaxSpeed( void ) { return ItemInfoArray[m_iId].plr_settings[m_iIronSight].maxSpeed; } + float ClientJumpHeight( void ) { return ItemInfoArray[m_iId].plr_settings[m_iIronSight].jumpHeight; } + Vector vecThrowOffset( void ) { return ItemInfoArray[m_iId].vecThrowOffset; } + float fSpreadTime( void ) { return ItemInfoArray[m_iId].spreadtime; } + int iVolume( void ) { return ItemInfoArray[m_iId].iVolume; } + int iFlash( void ) { return ItemInfoArray[m_iId].iFlash; } + + int m_iWeaponAutoFire; // 0 - semi-auto, 1 - full auto + + int m_iDefaultAmmo1; // how much ammo you get when you pick up this weapon as placed by a level designer. + int m_iDefaultAmmo2; // how much ammo you get when you pick up this weapon as placed by a level designer. + + float m_flNextPrimaryAttack; // soonest time ItemPostFrame will call PrimaryAttack + float m_flNextSecondaryAttack; // soonest time ItemPostFrame will call SecondaryAttack + float m_flTimeWeaponIdle; // soonest time ItemPostFrame will call WeaponIdle + int m_iPrimaryAmmoType; // "primary" ammo index into players m_rgAmmo[] + int m_iSecondaryAmmoType; // "secondary" ammo index into players m_rgAmmo[] + int m_fFireOnEmpty; // True when the gun is empty and the player is still holding down the attack key(s) + float m_flTimeUpdate; // special time for additional effects + int m_iStepReload; // reload state (e.g. for shotgun) + int m_iIronSight; // iron sight is enabled + float m_flSpreadTime; // time to return from full spread + float m_flLastShotTime; // time from last shot + float m_flLastSpreadPower; // [0..1] range value + int m_cActiveRockets; // stuff for rpg with LTD + int m_iClip; // number of shots left in the primary weapon clip, -1 it not used + int m_iSpot; // enable laser dot + int m_iZoom; // zoom current level + int m_iBody; // viewmodel body + int m_iSkin; // viewmodel skin + + int m_fInReload; // Are we in the middle of a reload; + int m_iPlayEmptySound; // trigger to playing empty sound once + int m_iClientAnim; // used to resend anim on save\restore + int m_iClientClip; // the last version of m_iClip sent to hud dll + int m_iClientWeaponState; // the last version of the weapon state sent to hud dll (is current weapon, is on target) + int m_iClientSkin; // the last version of m_iSkin sent to hud dll + int m_iClientBody; // the last version of m_iBody sent to hud dll + float m_flHoldTime; // button holdtime + + // this methods can be overloaded + virtual void RetireWeapon( void ); + virtual BOOL ShouldWeaponIdle( void ) { return FALSE; }; + virtual BOOL PlayEmptySound( void ); + virtual void ResetEmptySound( void ); + + // default methods + BOOL DefaultDeploy( Activity sequence ); + BOOL DefaultHolster( Activity sequence, bool force = false ); + BOOL DefaultReload( Activity sequence ); + BOOL DefaultSwing( int primary ); + void DefaultIdle( void ); + + virtual void PrimaryAttack( void ) // do "+ATTACK" + { + if( !m_iWeaponAutoFire && !FBitSet( m_pPlayer->m_afButtonPressed, IN_ATTACK )) + return; // no effect for hold button + + int iResult = PlayCurrentAttack( iAttack1(), true ); + + if( iResult == 1 ) + { + const feedback_t *pFB = pFeedback1(); + m_pPlayer->ViewPunch( pFB->punchangle[0].Random(), pFB->punchangle[1].Random(), pFB->punchangle[2].Random() ); + m_pPlayer->pev->velocity = m_pPlayer->pev->velocity - gpGlobals->v_forward * fRecoil1(); + + PrimaryPostAttack(); // run post effects + + float flNextAttack = fNextAttack1(); + if( flNextAttack == -1.0f ) + flNextAttack = SequenceDuration(); + + if( m_flNextPrimaryAttack < UTIL_WeaponTimeBase( )) + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + flNextAttack + 0.02f; + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + flNextAttack; + if( HasAmmo( )) m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + RANDOM_FLOAT( 10.0f, 15.0f ); + } + else if( iResult == 0 ) + { + float flNextAttack = 0.1f; + + if( GetAnimation( ACT_VM_SHOOT_EMPTY ) != -1 ) + { + SetAnimation( ACT_VM_SHOOT_EMPTY ); + flNextAttack = SequenceDuration(); + } + + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + flNextAttack; + PlayEmptySound(); + } + + m_iStepReload = NOT_IN_RELOAD; // reset reload + } + + virtual void SecondaryAttack( void ) // do "+ATTACK2" + { + if( !FBitSet( m_pPlayer->m_afButtonPressed, IN_ATTACK2 )) + return; // no effect for hold button + + int iResult = PlayCurrentAttack( iAttack2(), false ); + + if( iResult == 1 ) + { + const feedback_t *pFB = pFeedback2(); + m_pPlayer->ViewPunch( pFB->punchangle[0].Random(), pFB->punchangle[1].Random(), pFB->punchangle[2].Random() ); + m_pPlayer->pev->velocity = m_pPlayer->pev->velocity - gpGlobals->v_forward * fRecoil2(); + + SecondaryPostAttack(); // run post effects + + float flNextAttack = fNextAttack2(); + if( flNextAttack == -1.0f ) + flNextAttack = SequenceDuration(); + + if( m_flNextSecondaryAttack < UTIL_WeaponTimeBase() ) + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + flNextAttack + 0.02f; + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + flNextAttack; + if( HasAmmo( )) m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + RANDOM_FLOAT( 10.0f, 15.0f ); + } + else if( iResult == 0 ) + { + float flNextAttack = 0.1f; + + if( GetAnimation( ACT_VM_SHOOT_EMPTY ) != -1 ) + { + SetAnimation( ACT_VM_SHOOT_EMPTY ); + flNextAttack = SequenceDuration(); + } + + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + flNextAttack; + PlayEmptySound(); + } + + m_iStepReload = NOT_IN_RELOAD; // reset reload + } + + virtual void Reload( void ){ DefaultReload( ACT_VM_RELOAD ); } // do "+RELOAD" + virtual void PrimaryPostAttack( void ) {} + virtual void SecondaryPostAttack( void ) {} + virtual void PostReload( void ) {} + virtual void PostIdle( void ) {} // calling every frame + virtual void WeaponIdle( void ){ DefaultIdle(); } // called when no buttons pressed + virtual void Deploy( void ); // deploy function + virtual void Holster( bool force = false ); // holster function +}; + +class CBasePlayerAmmo : public CBaseEntity +{ +public: + void Precache( void ); + void Spawn( void ); + + // Wargon: Ïåðåìåííûå äëÿ þçàáåëüíîñòè ïàòðîíîâ. + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + void EXPORT DefaultUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { DefaultTouch( pActivator ); } + virtual int ObjectCaps( void ) { return m_iAmmoCaps | FCAP_ACROSS_TRANSITION | FCAP_USE_ONLY; } + virtual BOOL IsGenericAmmo( void ) { return ( FStrEq( STRING( pev->classname ), "ammo_generic" ) && pev->netname != iStringNull ); } + void KeyValue( KeyValueData *pkvd ); + BOOL InitGenericAmmo( void ); + + RandomRange m_rAmmoCount; + BOOL m_bCustomAmmo; // it's a virtual entity + string_t m_iAmmoType; // just store name of ammo so we can find them again + int m_iAmmoCaps; + + static AmmoInfo AmmoInfoArray[MAX_AMMO_SLOTS]; + static AmmoDesc AmmoDescArray[MAX_AMMO_DESC]; + + void EXPORT DefaultTouch( CBaseEntity *pOther ); // default weapon touch + virtual BOOL AddAmmo( CBaseEntity *pOther ); + + CBaseEntity* Respawn( void ); + void EXPORT Materialize( void ); +}; + + +extern DLL_GLOBAL short g_sModelIndexLaser;// holds the index for the laser beam +extern DLL_GLOBAL const char *g_pModelNameLaser; +extern DLL_GLOBAL short g_sModelIndexLaserDot;// holds the index for the laser beam dot +extern DLL_GLOBAL short g_sModelIndexFireball;// holds the index for the fireball +extern DLL_GLOBAL short g_sModelIndexSmoke;// holds the index for the smoke cloud +extern DLL_GLOBAL short g_sModelIndexWExplosion;// holds the index for the underwater explosion +extern DLL_GLOBAL short g_sModelIndexBubbles;// holds the index for the bubbles model +extern DLL_GLOBAL short g_sModelIndexBloodDrop;// holds the sprite index for blood drops +extern DLL_GLOBAL short g_sModelIndexBloodSpray;// holds the sprite index for blood spray (bigger) +extern DLL_GLOBAL short g_sModelIndexWaterSplash; +extern DLL_GLOBAL short g_sModelIndexSmokeTrail; +extern DLL_GLOBAL short g_sModelIndexNull; + +extern void ClearMultiDamage( void ); +extern void ApplyMultiDamage( entvars_t* pevInflictor, entvars_t* pevAttacker ); +extern void AddMultiDamage( entvars_t *pevInflictor, CBaseEntity *pEntity, float flDamage, int bitsDamageType ); + +extern void DecalGunshot( TraceResult *pTrace, int iBulletType, const Vector &vecSrc, bool fromPlayer = false ); +extern void SpawnBlood(Vector vecSpot, int bloodColor, float flDamage); +extern const char *DamageDecal( CBaseEntity *pEntity, int bitsDamageType ); +extern void RadiusDamage( Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, float flRadius, int iClassIgnore, int bitsDamageType ); +extern void UTIL_InitAmmoDescription( const char *filename ); +extern void UTIL_InitWeaponDescription( const char *pattern ); +extern void AddAmmoNameToAmmoRegistry( const char *szAmmoname ); +extern AmmoInfo *UTIL_FindAmmoType( const char *szAmmoname ); + +typedef struct +{ + CBaseEntity *pEntity; + float amount; + int type; +} MULTIDAMAGE; + +extern MULTIDAMAGE gMultiDamage; + +#define LOUD_GUN_VOLUME 1000 +#define NORMAL_GUN_VOLUME 600 +#define QUIET_GUN_VOLUME 200 +#define NO_GUN_VOLUME 0 + +#define BRIGHT_GUN_FLASH 512 +#define NORMAL_GUN_FLASH 256 +#define DIM_GUN_FLASH 128 +#define NO_GUN_FLASH 0 + +#define KNIFE_BODYHIT_VOLUME 128 +#define KNIFE_WALLHIT_VOLUME 512 + +#define BIG_EXPLOSION_VOLUME 2048 +#define NORMAL_EXPLOSION_VOLUME 1024 +#define SMALL_EXPLOSION_VOLUME 512 + +#define WEAPON_ACTIVITY_VOLUME 64 + +#define VECTOR_CONE_1DEGREES Vector( 0.00873, 0.00873, 0.00873 ) +#define VECTOR_CONE_2DEGREES Vector( 0.01745, 0.01745, 0.01745 ) +#define VECTOR_CONE_3DEGREES Vector( 0.02618, 0.02618, 0.02618 ) +#define VECTOR_CONE_4DEGREES Vector( 0.03490, 0.03490, 0.03490 ) +#define VECTOR_CONE_5DEGREES Vector( 0.04362, 0.04362, 0.04362 ) +#define VECTOR_CONE_6DEGREES Vector( 0.05234, 0.05234, 0.05234 ) +#define VECTOR_CONE_7DEGREES Vector( 0.06105, 0.06105, 0.06105 ) +#define VECTOR_CONE_8DEGREES Vector( 0.06976, 0.06976, 0.06976 ) +#define VECTOR_CONE_9DEGREES Vector( 0.07846, 0.07846, 0.07846 ) +#define VECTOR_CONE_10DEGREES Vector( 0.08716, 0.08716, 0.08716 ) +#define VECTOR_CONE_15DEGREES Vector( 0.13053, 0.13053, 0.13053 ) +#define VECTOR_CONE_20DEGREES Vector( 0.17365, 0.17365, 0.17365 ) + +//========================================================= +// CWeaponBox - a single entity that can store weapons +// and ammo. +//========================================================= +class CWeaponBox : public CBaseAnimating +{ + void Precache( void ); + void Spawn( void ); + void Touch( CBaseEntity *pOther ); + void KeyValue( KeyValueData *pkvd ); + BOOL IsEmpty( void ); + int GiveAmmo( int iCount, const char *szName ); + int MaxAmmoCarry( int iszName ); + void SetObjectCollisionBox( void ); +public: + void EXPORT Kill( void ); + + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + BOOL HasWeapon( CBasePlayerItem *pCheckItem ); + BOOL PackWeapon( CBasePlayerItem *pWeapon ); + BOOL PackAmmo( int iszName, int iCount ); + + CBasePlayerItem *m_rgpPlayerItems[MAX_ITEM_TYPES];// one slot for each + + int m_rgiszAmmo[MAX_AMMO_SLOTS];// ammo names + int m_rgAmmo[MAX_AMMO_SLOTS];// ammo quantities + + int m_cAmmoTypes;// how many ammo types packed into this box (if packed by a level designer) +}; + +#endif // WEAPONS_H \ No newline at end of file diff --git a/dlls/world.cpp b/dlls/world.cpp new file mode 100644 index 0000000..48c1575 --- /dev/null +++ b/dlls/world.cpp @@ -0,0 +1,778 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== world.cpp ======================================================== + + precaches and defs for entities and other data that must always be available. + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "nodes.h" +#include "soundent.h" +#include "client.h" +#include "decals.h" +#include "skill.h" +#include "effects.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "teamplay_gamerules.h" +#include "movewith.h" //LRC + +extern CGraph WorldGraph; +extern CSoundEnt *pSoundEnt; + +extern CBaseEntity *g_pLastSpawn; +DLL_GLOBAL edict_t *g_pBodyQueueHead; +CGlobalState gGlobalState; +extern DLL_GLOBAL int gDisplayTitle; +DLL_GLOBAL unsigned short m_usSmokePuff; // buz - smoke event +extern unsigned short g_usShootEvent; + +// +// This must match the list in util.h +// +DLL_DECALLIST gDecals[] = { + { "{shot1", 0 }, // DECAL_GUNSHOT1 + { "{shot2", 0 }, // DECAL_GUNSHOT2 + { "{shot3",0 }, // DECAL_GUNSHOT3 + { "{shot4", 0 }, // DECAL_GUNSHOT4 + { "{shot5", 0 }, // DECAL_GUNSHOT5 + { "{lambda01", 0 }, // DECAL_LAMBDA1 + { "{lambda02", 0 }, // DECAL_LAMBDA2 + { "{lambda03", 0 }, // DECAL_LAMBDA3 + { "{lambda04", 0 }, // DECAL_LAMBDA4 + { "{lambda05", 0 }, // DECAL_LAMBDA5 + { "{lambda06", 0 }, // DECAL_LAMBDA6 + { "{scorch1", 0 }, // DECAL_SCORCH1 + { "{scorch2", 0 }, // DECAL_SCORCH2 + { "{blood1", 0 }, // DECAL_BLOOD1 + { "{blood2", 0 }, // DECAL_BLOOD2 + { "{blood3", 0 }, // DECAL_BLOOD3 + { "{blood4", 0 }, // DECAL_BLOOD4 + { "{blood5", 0 }, // DECAL_BLOOD5 + { "{blood6", 0 }, // DECAL_BLOOD6 + { "{yblood1", 0 }, // DECAL_YBLOOD1 + { "{yblood2", 0 }, // DECAL_YBLOOD2 + { "{yblood3", 0 }, // DECAL_YBLOOD3 + { "{yblood4", 0 }, // DECAL_YBLOOD4 + { "{yblood5", 0 }, // DECAL_YBLOOD5 + { "{yblood6", 0 }, // DECAL_YBLOOD6 + { "{break1", 0 }, // DECAL_GLASSBREAK1 + { "{break2", 0 }, // DECAL_GLASSBREAK2 + { "{break3", 0 }, // DECAL_GLASSBREAK3 + { "{bigshot1", 0 }, // DECAL_BIGSHOT1 + { "{bigshot2", 0 }, // DECAL_BIGSHOT2 + { "{bigshot3", 0 }, // DECAL_BIGSHOT3 + { "{bigshot4", 0 }, // DECAL_BIGSHOT4 + { "{bigshot5", 0 }, // DECAL_BIGSHOT5 + { "{spit1", 0 }, // DECAL_SPIT1 + { "{spit2", 0 }, // DECAL_SPIT2 + { "{bproof1", 0 }, // DECAL_BPROOF1 + { "{gargstomp", 0 }, // DECAL_GARGSTOMP1, // Gargantua stomp crack + { "{smscorch1", 0 }, // DECAL_SMALLSCORCH1, // Small scorch mark + { "{smscorch2", 0 }, // DECAL_SMALLSCORCH2, // Small scorch mark + { "{smscorch3", 0 }, // DECAL_SMALLSCORCH3, // Small scorch mark + { "{mommablob", 0 }, // DECAL_MOMMABIRTH // BM Birth spray + { "{mommablob", 0 }, // DECAL_MOMMASPLAT // BM Mortar spray?? need decal +}; + +/* +============================================================================== + +BODY QUE + +============================================================================== +*/ +// Body queue class here.... It's really just CBaseEntity +class CCorpse : public CBaseEntity +{ + virtual int ObjectCaps( void ) { return FCAP_DONT_SAVE; } +}; + +LINK_ENTITY_TO_CLASS( bodyque, CCorpse ); + +static void InitBodyQue(void) +{ + string_t istrClassname = MAKE_STRING("bodyque"); + + g_pBodyQueueHead = CREATE_NAMED_ENTITY( istrClassname ); + entvars_t *pev = VARS(g_pBodyQueueHead); + + // Reserve 3 more slots for dead bodies + for ( int i = 0; i < 3; i++ ) + { + pev->owner = CREATE_NAMED_ENTITY( istrClassname ); + pev = VARS(pev->owner); + } + + pev->owner = g_pBodyQueueHead; +} + + +// +// make a body que entry for the given ent so the ent can be respawned elsewhere +// +// GLOBALS ASSUMED SET: g_eoBodyQueueHead +// +void CopyToBodyQue(entvars_t *pev) +{ + if (pev->effects & EF_NODRAW) + return; + + entvars_t *pevHead = VARS(g_pBodyQueueHead); + + pevHead->angles = pev->angles; + pevHead->model = pev->model; + pevHead->modelindex = pev->modelindex; + pevHead->frame = pev->frame; + pevHead->colormap = pev->colormap; + pevHead->movetype = MOVETYPE_TOSS; + pevHead->velocity = pev->velocity; + pevHead->flags = 0; + pevHead->deadflag = pev->deadflag; + pevHead->renderfx = kRenderFxDeadPlayer; + pevHead->renderamt = ENTINDEX( ENT( pev ) ); + + pevHead->effects = pev->effects | EF_NOINTERP; + //pevHead->goalstarttime = pev->goalstarttime; + //pevHead->goalframe = pev->goalframe; + //pevHead->goalendtime = pev->goalendtime ; + + pevHead->sequence = pev->sequence; + pevHead->animtime = pev->animtime; + + UTIL_SetEdictOrigin(g_pBodyQueueHead, pev->origin); + UTIL_SetSize(pevHead, pev->mins, pev->maxs); + g_pBodyQueueHead = pevHead->owner; +} + + +CGlobalState::CGlobalState( void ) +{ + Reset(); +} + +void CGlobalState::Reset( void ) +{ + m_pList = NULL; + m_listCount = 0; +} + +globalentity_t *CGlobalState :: Find( string_t globalname ) +{ + if ( !globalname ) + return NULL; + + globalentity_t *pTest; + const char *pEntityName = STRING(globalname); + + + pTest = m_pList; + while ( pTest ) + { + if ( FStrEq( pEntityName, pTest->name ) ) + break; + + pTest = pTest->pNext; + } + + return pTest; +} + + +// This is available all the time now on impulse 104, remove later +//#ifdef _DEBUG +void CGlobalState :: DumpGlobals( void ) +{ + static char *estates[] = { "Off", "On", "Dead" }; + globalentity_t *pTest; + + ALERT( at_debug, "-- Globals --\n" ); + pTest = m_pList; + while ( pTest ) + { + ALERT( at_debug, "%s: %s (%s)\n", pTest->name, pTest->levelName, estates[pTest->state] ); + pTest = pTest->pNext; + } +} +//#endif + + +void CGlobalState :: EntityAdd( string_t globalname, string_t mapName, GLOBALESTATE state, float time ) +{ + ASSERT( !Find(globalname) ); + + globalentity_t *pNewEntity = (globalentity_t *)calloc( sizeof( globalentity_t ), 1 ); + ASSERT( pNewEntity != NULL ); + pNewEntity->pNext = m_pList; + m_pList = pNewEntity; + strcpy( pNewEntity->name, STRING( globalname ) ); + strcpy( pNewEntity->levelName, STRING(mapName) ); + pNewEntity->global_time = time; + pNewEntity->state = state; + m_listCount++; +} + + +void CGlobalState :: EntitySetState( string_t globalname, GLOBALESTATE state ) +{ + globalentity_t *pEnt = Find( globalname ); + + if ( !pEnt ) return; + + pEnt->state = state; +} + +void CGlobalState :: EntitySetTime( string_t globalname, float time ) +{ + globalentity_t *pEnt = Find( globalname ); + + if ( !pEnt ) return; + + pEnt->global_time = time; +} + +const globalentity_t *CGlobalState :: EntityFromTable( string_t globalname ) +{ + globalentity_t *pEnt = Find( globalname ); + + return pEnt; +} + + +GLOBALESTATE CGlobalState :: EntityGetState( string_t globalname ) +{ + globalentity_t *pEnt = Find( globalname ); + if ( pEnt ) + return pEnt->state; + + return GLOBAL_OFF; +} + +float CGlobalState :: EntityGetTime( string_t globalname ) +{ + globalentity_t *pEnt = Find( globalname ); + if ( pEnt ) + return pEnt->global_time; + + return -1.0f; +} + + +// Global Savedata for Delay +TYPEDESCRIPTION CGlobalState::m_SaveData[] = +{ + DEFINE_FIELD( CGlobalState, m_listCount, FIELD_INTEGER ), +}; + +// Global Savedata for Delay +TYPEDESCRIPTION gGlobalEntitySaveData[] = +{ + DEFINE_ARRAY( globalentity_t, name, FIELD_CHARACTER, 64 ), + DEFINE_ARRAY( globalentity_t, levelName, FIELD_CHARACTER, 32 ), + DEFINE_FIELD( globalentity_t, state, FIELD_INTEGER ), + DEFINE_FIELD( globalentity_t, global_time, FIELD_FLOAT ), // to save global time instead of state +}; + + +int CGlobalState::Save( CSave &save ) +{ + int i; + globalentity_t *pEntity; + + if ( !save.WriteFields( "cGLOBAL", "GLOBAL", this, m_SaveData, ARRAYSIZE(m_SaveData) ) ) + return 0; + + pEntity = m_pList; + for ( i = 0; i < m_listCount && pEntity; i++ ) + { + if ( !save.WriteFields( "cGENT", "GENT", pEntity, gGlobalEntitySaveData, ARRAYSIZE(gGlobalEntitySaveData) ) ) + return 0; + + pEntity = pEntity->pNext; + } + + return 1; +} + +int CGlobalState::Restore( CRestore &restore ) +{ + int i, listCount; + globalentity_t tmpEntity; + + ClearStates(); + if ( !restore.ReadFields( "GLOBAL", this, m_SaveData, ARRAYSIZE(m_SaveData) ) ) + return 0; + + listCount = m_listCount; // Get new list count + m_listCount = 0; // Clear loaded data + + for ( i = 0; i < listCount; i++ ) + { + if ( !restore.ReadFields( "GENT", &tmpEntity, gGlobalEntitySaveData, ARRAYSIZE(gGlobalEntitySaveData) ) ) + return 0; + EntityAdd( MAKE_STRING(tmpEntity.name), MAKE_STRING(tmpEntity.levelName), tmpEntity.state, tmpEntity.global_time ); + } + return 1; +} + +void CGlobalState::EntityUpdate( string_t globalname, string_t mapname ) +{ + globalentity_t *pEnt = Find( globalname ); + + if ( pEnt ) + strcpy( pEnt->levelName, STRING(mapname) ); +} + + +void CGlobalState::ClearStates( void ) +{ + globalentity_t *pFree = m_pList; + while ( pFree ) + { + globalentity_t *pNext = pFree->pNext; + free( pFree ); + pFree = pNext; + } + Reset(); +} + + +void SaveGlobalState( SAVERESTOREDATA *pSaveData ) +{ + CSave saveHelper( pSaveData ); + gGlobalState.Save( saveHelper ); +} + + +void RestoreGlobalState( SAVERESTOREDATA *pSaveData ) +{ + CRestore restoreHelper( pSaveData ); + gGlobalState.Restore( restoreHelper ); +} + + +void ResetGlobalState( void ) +{ + gGlobalState.ClearStates(); + gInitHUD = TRUE; // Init the HUD on a new game / load game +} + +// called by worldspawn +void W_Precache( void ) +{ + memset( CBasePlayerItem::ItemInfoArray, 0, sizeof(CBasePlayerItem::ItemInfoArray) ); + memset( CBasePlayerAmmo::AmmoInfoArray, 0, sizeof(CBasePlayerAmmo::AmmoInfoArray) ); + memset( CBasePlayerAmmo::AmmoDescArray, 0, sizeof(CBasePlayerAmmo::AmmoDescArray) ); + CBasePlayerItem::m_iGlobalID = 1; // weapon id's starts from 1 + + // custom items... + UTIL_InitAmmoDescription( "scripts/weapons/ammodesc.txt" ); + + UTIL_InitWeaponDescription( "scripts/weapons/weapon_*.txt" ); // lookup all the weapon_???.txt files + + // common world objects + UTIL_PrecacheOther( "item_suit" ); + UTIL_PrecacheOther( "item_gasmask" ); // buz + UTIL_PrecacheOther( "item_headshield" ); // buz + + UTIL_PrecacheOther( "laser_spot" ); + UTIL_PrecacheOther( "rpg_rocket" ); + + if ( g_pGameRules->IsDeathmatch() ) + { + PRECACHE_SOUND( "items/suitchargeok1.wav" );//!!! temporary sound for respawning weapons. + UTIL_PrecacheOther( "weaponbox" );// container for dropped deathmatch weapons + } + + g_sModelIndexFireball = PRECACHE_MODEL ("sprites/zerogxplode.spr");// fireball + g_sModelIndexWExplosion = PRECACHE_MODEL ("sprites/WXplo1.spr");// underwater fireball + g_sModelIndexSmoke = PRECACHE_MODEL ("sprites/steam1.spr");// smoke + g_sModelIndexWaterSplash = PRECACHE_MODEL ("sprites/wsplash_x.spr");//water splash + g_sModelIndexBubbles = PRECACHE_MODEL ("sprites/bubble.spr");//bubbles + g_sModelIndexBloodSpray = PRECACHE_MODEL ("sprites/bloodspray.spr"); // initial blood + g_sModelIndexBloodDrop = PRECACHE_MODEL ("sprites/blood.spr"); // splattered blood + g_sModelIndexSmokeTrail = PRECACHE_MODEL ("sprites/smokeball.spr"); + g_sModelIndexNull = PRECACHE_MODEL ("sprites/null.spr"); + + g_sModelIndexLaser = PRECACHE_MODEL( (char *)g_pModelNameLaser ); + g_sModelIndexLaserDot = PRECACHE_MODEL("sprites/laserdot.spr"); + + m_usSmokePuff = PRECACHE_EVENT( 1, "evSmokePuff" ); // buz + g_usShootEvent = PRECACHE_EVENT( 1, "evFireGeneric" ); // g-cont + + // used by explosions + PRECACHE_MODEL ("sprites/null.spr"); + + PRECACHE_MODEL ("models/grenade.mdl"); + PRECACHE_MODEL ("sprites/explode1.spr"); + PRECACHE_MODEL ("models/m_flash1.mdl"); + + PRECACHE_SOUND ("weapons/debris1.wav");// explosion aftermaths + PRECACHE_SOUND ("weapons/debris2.wav");// explosion aftermaths + PRECACHE_SOUND ("weapons/debris3.wav");// explosion aftermaths + + PRECACHE_SOUND ("weapons/grenade_hit1.wav");//grenade + PRECACHE_SOUND ("weapons/grenade_hit2.wav");//grenade + PRECACHE_SOUND ("weapons/grenade_hit3.wav");//grenade + + PRECACHE_SOUND ("weapons/bullet_hit1.wav"); // hit by bullet + PRECACHE_SOUND ("weapons/bullet_hit2.wav"); // hit by bullet + + PRECACHE_SOUND ("items/weapondrop1.wav");// weapon falls to the ground + PRECACHE_SOUND ("weapons/weapon_chengemode.wav"); + + // buz + PRECACHE_MODEL ("sprites/wsplash_x.spr"); + PRECACHE_MODEL ("sprites/smokeball.spr"); + PRECACHE_MODEL ("sprites/eexplo.spr"); + PRECACHE_MODEL ("sprites/fexplo.spr"); + PRECACHE_MODEL ("sprites/dexplo.spr"); + PRECACHE_MODEL ("sprites/richo1.spr"); +} + +// moved CWorld class definition to cbase.h +//======================= +// CWorld +// +// This spawns first when each level begins. +//======================= + +LINK_ENTITY_TO_CLASS( worldspawn, CWorld ); + +#define SF_WORLD_DARK 0x0001 // Fade from black at startup +#define SF_WORLD_TITLE 0x0002 // Display game title at startup +#define SF_WORLD_FORCETEAM 0x0004 // Force teams +//#define SF_WORLD_STARTSUIT 0x0008 // LRC- Start this level with an HEV suit! + +extern DLL_GLOBAL BOOL g_fGameOver; +float g_flWeaponCheat; + +//BOOL g_startSuit; //LRC + +void CWorld :: Spawn( void ) +{ + // if time was set + if( pev->button && m_vecTime.x != -1.0f && m_vecTime.y != -1.0f ) + { + int name = MAKE_STRING( "GLOBAL_TIME" ); + float global_time = bound( 0.0f, m_vecTime.x, 24.0f ) + bound( 0.0f, m_vecTime.y, 60.0f ) * (1.0f/60.0f); + + if ( !gGlobalState.EntityInTable( name ) ) // first initialize ? + gGlobalState.EntityAdd( name, gpGlobals->mapname, (GLOBALESTATE)GLOBAL_ON, global_time ); + } + + LIGHT_STYLE( 20, "m" ); + g_fGameOver = FALSE; + Precache( ); +} + +void CWorld :: Precache( void ) +{ + // LRC - set up the world lists + g_pWorld = this; + m_pAssistLink = NULL; + m_pFirstAlias = NULL; + g_pLastSpawn = NULL; + + CVAR_SET_STRING( "sv_gravity", "800" ); // 67ft/sec + CVAR_SET_STRING( "sv_stepsize", "18" ); + CVAR_SET_STRING( "room_type", "0" ); // clear DSP + + // Set up game rules + if ( g_pGameRules ) + { + delete g_pGameRules; + } + + g_pGameRules = InstallGameRules( ); + + //!!!UNDONE why is there so much Spawn code in the Precache function? I'll just keep it here + + ///!!!LATER - do we want a sound ent in deathmatch? (sjb) + //pSoundEnt = CBaseEntity::Create( "soundent", g_vecZero, g_vecZero, edict() ); + pSoundEnt = GetClassPtr(( CSoundEnt *)NULL ); + pSoundEnt->Spawn(); + + if ( !pSoundEnt ) + { + ALERT ( at_debug, "**COULD NOT CREATE SOUNDENT**\n" ); + } + + InitBodyQue(); + +// init sentence group playback stuff from sentences.txt. +// ok to call this multiple times, calls after first are ignored. + + SENTENCEG_Init(); + +// init texture type array from materials.txt + + TEXTURETYPE_Init(); + + +// the area based ambient sounds MUST be the first precache_sounds + +// player precaches + W_Precache (); // get weapon precaches + + ClientPrecache(); + +// sounds used from C physics code + PRECACHE_SOUND("common/null.wav"); // clears sound channels + + PRECACHE_SOUND( "items/gunpickup2.wav" );// player picks up a gun. + + PRECACHE_SOUND( "common/bodydrop3.wav" );// dead bodies hitting the ground (animation events) + PRECACHE_SOUND( "common/bodydrop4.wav" ); + + g_Language = (int)CVAR_GET_FLOAT( "sv_language" ); + if ( g_Language == LANGUAGE_GERMAN ) + { + PRECACHE_MODEL( "models/germangibs.mdl" ); + } + else + { + PRECACHE_MODEL( "models/hgibs.mdl" ); + PRECACHE_MODEL( "models/agibs.mdl" ); + } + + PRECACHE_SOUND ("weapons/ric1.wav"); + PRECACHE_SOUND ("weapons/ric2.wav"); + PRECACHE_SOUND ("weapons/ric3.wav"); + PRECACHE_SOUND ("weapons/ric4.wav"); + PRECACHE_SOUND ("weapons/ric5.wav"); + + PRECACHE_SOUND ("debris/wood1.wav"); // hit wood texture + PRECACHE_SOUND ("debris/wood2.wav"); + PRECACHE_SOUND ("debris/wood3.wav"); + PRECACHE_SOUND ("debris/wood4.wav"); + + PRECACHE_SOUND ("debris/metal1.wav"); // hit metal texture + PRECACHE_SOUND ("debris/metal2.wav"); + PRECACHE_SOUND ("debris/metal3.wav"); + PRECACHE_SOUND ("debris/metal4.wav"); + + PRECACHE_SOUND ("debris/glass1.wav"); // hit metal texture + PRECACHE_SOUND ("debris/glass2.wav"); + PRECACHE_SOUND ("debris/glass3.wav"); + + PRECACHE_SOUND( "player/water_splash1.wav" ); + PRECACHE_SOUND( "player/water_splash2.wav" ); + PRECACHE_SOUND( "player/water_splash3.wav" ); + + PRECACHE_MODEL( "sprites/null.spr" ); //LRC + + PRECACHE_MODEL( "sprites/splash1.spr" ); + PRECACHE_MODEL( "sprites/splash2.spr" ); + + PRECACHE_MODEL( "sprites/bexplo.spr" ); + PRECACHE_MODEL( "sprites/cexplo.spr" ); + PRECACHE_MODEL( "sprites/dexplo.spr" ); + PRECACHE_MODEL( "sprites/eexplo.spr" ); + PRECACHE_MODEL( "sprites/fexplo.spr" ); + PRECACHE_MODEL( "sprites/smokeball.spr" ); + + PRECACHE_MODEL( "sprites/gunsmoke.spr" ); + +// +// Setup light animation tables. 'a' is total darkness, 'z' is maxbright. +// + int i; + + // 0 normal + for (i = 0; i <= 13; i++) + { + LIGHT_STYLE(i, (char*)STRING(GetStdLightStyle(i))); + } + + // styles 32-62 are assigned by the light program for switchable lights + + // 63 testing + LIGHT_STYLE(63, "a"); + +#if 0 // Paranoia has fully seperate decal system + for (i = 0; i < ARRAYSIZE(gDecals); i++ ) + gDecals[i].index = DECAL_INDEX( gDecals[i].name ); +#endif + +// init the WorldGraph. + WorldGraph.InitGraph(); + +// make sure the .NOD file is newer than the .BSP file. + if ( !WorldGraph.CheckNODFile ( ( char * )STRING( gpGlobals->mapname ) ) ) + {// NOD file is not present, or is older than the BSP file. + WorldGraph.AllocNodes (); + } + else + {// Load the node graph for this level + if ( !WorldGraph.FLoadGraph ( (char *)STRING( gpGlobals->mapname ) ) ) + {// couldn't load, so alloc and prepare to build a graph. + ALERT ( at_debug, "*Error opening .NOD file\n" ); + WorldGraph.AllocNodes (); + } + else + { + ALERT ( at_debug, "\n*Graph Loaded!\n" ); + } + } + + if ( pev->speed > 0 ) + CVAR_SET_FLOAT( "sv_zmax", pev->speed ); + else + CVAR_SET_FLOAT( "sv_zmax", 4096 ); + + // g-cont. moved here to right restore global WaveHeight on save\restore level + CVAR_SET_FLOAT( "sv_wateramp", pev->scale ); + + if ( pev->netname ) + { + ALERT( at_aiconsole, "Chapter title: %s\n", STRING(pev->netname) ); + CBaseEntity *pEntity = CBaseEntity::Create( "env_message", g_vecZero, g_vecZero, NULL ); + if ( pEntity ) + { + pEntity->SetThink(&CWorld::SUB_CallUseToggle ); + pEntity->pev->message = pev->netname; + pev->netname = 0; + pEntity->SetNextThink( 0.3 ); + pEntity->pev->spawnflags = SF_MESSAGE_ONCE; + } + } + + if ( pev->spawnflags & SF_WORLD_DARK ) + CVAR_SET_FLOAT( "v_dark", 1.0 ); + else + CVAR_SET_FLOAT( "v_dark", 0.0 ); + + pev->spawnflags &= ~SF_WORLD_DARK; // g-cont. don't apply fade after save\restore + + if ( pev->spawnflags & SF_WORLD_TITLE ) + gDisplayTitle = TRUE; // display the game title if this key is set + else + gDisplayTitle = FALSE; + + pev->spawnflags &= ~SF_WORLD_TITLE; // g-cont. don't show logo after save\restore + + if ( pev->spawnflags & SF_WORLD_FORCETEAM ) + { + CVAR_SET_FLOAT( "mp_defaultteam", 1 ); + } + else + { + CVAR_SET_FLOAT( "mp_defaultteam", 0 ); + } + + // g-cont. moved here so cheats will working on restore level + g_flWeaponCheat = CVAR_GET_FLOAT( "sv_cheats" ); // Is the impulse 101 command allowed? + + if( g_iXashEngineBuildNumber >= 2009 ) + UPDATE_PACKED_FOG( pev->impulse ); +} + + +// +// Just to ignore the "wad" field. +// +void CWorld :: KeyValue( KeyValueData *pkvd ) +{ + if ( FStrEq(pkvd->szKeyName, "skyname") ) + { + // Sent over net now. + CVAR_SET_STRING( "sv_skyname", pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "sounds") ) + { + gpGlobals->cdAudioTrack = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "WaveHeight") ) + { + // Sent over net now. + pev->scale = atof(pkvd->szValue) * (1.0/8.0); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "MaxRange") ) + { + pev->speed = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "chaptertitle") ) + { + pev->netname = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "fog") ) + { + int fog_settings[4]; + UTIL_StringToIntArray( fog_settings, 4, pkvd->szValue ); + pev->impulse = (fog_settings[0]<<24)|(fog_settings[1]<<16)|(fog_settings[2]<<8)|fog_settings[3]; + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "startdark") ) + { + // UNDONE: This is a gross hack!!! The CVAR is NOT sent over the client/sever link + // but it will work for single player + int flag = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + if ( flag ) + pev->spawnflags |= SF_WORLD_DARK; + } + else if ( FStrEq(pkvd->szKeyName, "newunit") ) + { + // Single player only. Clear save directory if set + if ( atoi(pkvd->szValue) ) + CVAR_SET_FLOAT( "sv_newunit", 1 ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "gametitle") ) + { + if ( atoi(pkvd->szValue) ) + pev->spawnflags |= SF_WORLD_TITLE; + + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "mapteams") ) + { + pev->team = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "defaultteam") ) + { + if ( atoi(pkvd->szValue) ) + { + pev->spawnflags |= SF_WORLD_FORCETEAM; + } + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "allowmonsters") ) + { + CVAR_SET_FLOAT( "mp_allowmonsters", atof(pkvd->szValue) ); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "time") ) + { + UTIL_StringToVector( (float*)(m_vecTime), pkvd->szValue ); + pkvd->fHandled = TRUE; + pev->button = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} diff --git a/dlls/xen.cpp b/dlls/xen.cpp new file mode 100644 index 0000000..bf3c227 --- /dev/null +++ b/dlls/xen.cpp @@ -0,0 +1,583 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "animation.h" +#include "effects.h" + + +#define XEN_PLANT_GLOW_SPRITE "sprites/flare3.spr" +#define XEN_PLANT_HIDE_TIME 5 + + +class CActAnimating : public CBaseAnimating +{ +public: + void SetActivity( Activity act ); + inline Activity GetActivity( void ) { return m_Activity; } + + virtual int ObjectCaps( void ) { return CBaseAnimating :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + Activity m_Activity; +}; + +TYPEDESCRIPTION CActAnimating::m_SaveData[] = +{ + DEFINE_FIELD( CActAnimating, m_Activity, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CActAnimating, CBaseAnimating ); + +void CActAnimating :: SetActivity( Activity act ) +{ + int sequence = LookupActivity( act ); + if ( sequence != ACTIVITY_NOT_AVAILABLE ) + { + pev->sequence = sequence; + m_Activity = act; + pev->frame = 0; + ResetSequenceInfo( ); + } +} + + + + +class CXenPLight : public CActAnimating +{ +public: + void Spawn( void ); + void Precache( void ); + void Touch( CBaseEntity *pOther ); + void Think( void ); + + void LightOn( void ); + void LightOff( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + CSprite *m_pGlow; +}; + +LINK_ENTITY_TO_CLASS( xen_plantlight, CXenPLight ); + +TYPEDESCRIPTION CXenPLight::m_SaveData[] = +{ + DEFINE_FIELD( CXenPLight, m_pGlow, FIELD_CLASSPTR ), +}; + +IMPLEMENT_SAVERESTORE( CXenPLight, CActAnimating ); + +void CXenPLight :: Spawn( void ) +{ + Precache(); + + SET_MODEL( ENT(pev), "models/light.mdl" ); + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_TRIGGER; + + UTIL_SetSize( pev, Vector(-80,-80,0), Vector(80,80,32)); + SetActivity( ACT_IDLE ); + SetNextThink( 0.1 ); + pev->frame = RANDOM_FLOAT(0,255); + + m_pGlow = CSprite::SpriteCreate( XEN_PLANT_GLOW_SPRITE, pev->origin + Vector(0,0,(pev->mins.z+pev->maxs.z)*0.5), FALSE ); + m_pGlow->SetTransparency( kRenderGlow, pev->rendercolor.x, pev->rendercolor.y, pev->rendercolor.z, pev->renderamt, pev->renderfx ); + m_pGlow->SetAttachment( edict(), 1 ); +} + + +void CXenPLight :: Precache( void ) +{ + PRECACHE_MODEL( "models/light.mdl" ); + PRECACHE_MODEL( XEN_PLANT_GLOW_SPRITE ); +} + + +void CXenPLight :: Think( void ) +{ + StudioFrameAdvance(); + SetNextThink( 0.1 ); + + switch( GetActivity() ) + { + case ACT_CROUCH: + if ( m_fSequenceFinished ) + { + SetActivity( ACT_CROUCHIDLE ); + LightOff(); + } + break; + + case ACT_CROUCHIDLE: + if ( gpGlobals->time > pev->dmgtime ) + { + SetActivity( ACT_STAND ); + LightOn(); + } + break; + + case ACT_STAND: + if ( m_fSequenceFinished ) + SetActivity( ACT_IDLE ); + break; + + case ACT_IDLE: + default: + break; + } +} + + +void CXenPLight :: Touch( CBaseEntity *pOther ) +{ + if ( pOther->IsPlayer() ) + { + pev->dmgtime = gpGlobals->time + XEN_PLANT_HIDE_TIME; + if ( GetActivity() == ACT_IDLE || GetActivity() == ACT_STAND ) + { + SetActivity( ACT_CROUCH ); + } + } +} + + +void CXenPLight :: LightOn( void ) +{ + SUB_UseTargets( this, USE_ON, 0 ); + if ( m_pGlow ) + m_pGlow->pev->effects &= ~EF_NODRAW; +} + + +void CXenPLight :: LightOff( void ) +{ + SUB_UseTargets( this, USE_OFF, 0 ); + if ( m_pGlow ) + m_pGlow->pev->effects |= EF_NODRAW; +} + + + +class CXenHair : public CActAnimating +{ +public: + void Spawn( void ); + void Precache( void ); + void Think( void ); +}; + +LINK_ENTITY_TO_CLASS( xen_hair, CXenHair ); + +#define SF_HAIR_SYNC 0x0001 + +void CXenHair::Spawn( void ) +{ + Precache(); + SET_MODEL( edict(), "models/hair.mdl" ); + UTIL_SetSize( pev, Vector(-4,-4,0), Vector(4,4,32)); + pev->sequence = 0; + + if ( !(pev->spawnflags & SF_HAIR_SYNC) ) + { + pev->frame = RANDOM_FLOAT(0,255); + pev->framerate = RANDOM_FLOAT( 0.7, 1.4 ); + } + ResetSequenceInfo( ); + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + SetNextThink( RANDOM_FLOAT( 0.1, 0.4 ) ); // Load balance these a bit +} + + +void CXenHair::Think( void ) +{ + StudioFrameAdvance(); + SetNextThink( 0.5 ); +} + + +void CXenHair::Precache( void ) +{ + PRECACHE_MODEL( "models/hair.mdl" ); +} + + +class CXenTreeTrigger : public CBaseEntity +{ +public: + void Touch( CBaseEntity *pOther ); + static CXenTreeTrigger *TriggerCreate( edict_t *pOwner, const Vector &position ); +}; +LINK_ENTITY_TO_CLASS( xen_ttrigger, CXenTreeTrigger ); + +CXenTreeTrigger *CXenTreeTrigger :: TriggerCreate( edict_t *pOwner, const Vector &position ) +{ + CXenTreeTrigger *pTrigger = GetClassPtr( (CXenTreeTrigger *)NULL ); + pTrigger->pev->origin = position; + pTrigger->pev->classname = MAKE_STRING("xen_ttrigger"); + pTrigger->pev->solid = SOLID_TRIGGER; + pTrigger->pev->movetype = MOVETYPE_NONE; + pTrigger->pev->owner = pOwner; + + return pTrigger; +} + + +void CXenTreeTrigger::Touch( CBaseEntity *pOther ) +{ + if ( pev->owner ) + { + CBaseEntity *pEntity = CBaseEntity::Instance(pev->owner); + pEntity->Touch( pOther ); + } +} + + +#define TREE_AE_ATTACK 1 + +class CXenTree : public CActAnimating +{ +public: + void Spawn( void ); + void Precache( void ); + void Touch( CBaseEntity *pOther ); + void Think( void ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { Attack(); return 0; } + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void Attack( void ); + int Classify( void ) { return CLASS_BARNACLE; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + static const char *pAttackHitSounds[]; + static const char *pAttackMissSounds[]; + +private: + CXenTreeTrigger *m_pTrigger; +}; + +LINK_ENTITY_TO_CLASS( xen_tree, CXenTree ); + +TYPEDESCRIPTION CXenTree::m_SaveData[] = +{ + DEFINE_FIELD( CXenTree, m_pTrigger, FIELD_CLASSPTR ), +}; + +IMPLEMENT_SAVERESTORE( CXenTree, CActAnimating ); + +void CXenTree :: Spawn( void ) +{ + Precache(); + + SET_MODEL( ENT(pev), "models/tree.mdl" ); + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_BBOX; + + pev->takedamage = DAMAGE_YES; + + UTIL_SetSize( pev, Vector(-30,-30,0), Vector(30,30,188)); + SetActivity( ACT_IDLE ); + SetNextThink( 0.1 ); + pev->frame = RANDOM_FLOAT(0,255); + pev->framerate = RANDOM_FLOAT( 0.7, 1.4 ); + + Vector triggerPosition; + UTIL_MakeVectorsPrivate( pev->angles, triggerPosition, NULL, NULL ); + triggerPosition = pev->origin + (triggerPosition * 64); + // Create the trigger + m_pTrigger = CXenTreeTrigger::TriggerCreate( edict(), triggerPosition ); + UTIL_SetSize( m_pTrigger->pev, Vector( -24, -24, 0 ), Vector( 24, 24, 128 ) ); +} + +const char *CXenTree::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CXenTree::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +void CXenTree :: Precache( void ) +{ + PRECACHE_MODEL( "models/tree.mdl" ); + PRECACHE_MODEL( XEN_PLANT_GLOW_SPRITE ); + PRECACHE_SOUND_ARRAY( pAttackHitSounds ); + PRECACHE_SOUND_ARRAY( pAttackMissSounds ); +} + + +void CXenTree :: Touch( CBaseEntity *pOther ) +{ + if ( !pOther->IsPlayer() && FClassnameIs( pOther->pev, "monster_bigmomma" ) ) + return; + + Attack(); +} + + +void CXenTree :: Attack( void ) +{ + if ( GetActivity() == ACT_IDLE ) + { + SetActivity( ACT_MELEE_ATTACK1 ); + pev->framerate = RANDOM_FLOAT( 1.0, 1.4 ); + EMIT_SOUND_ARRAY_DYN( CHAN_WEAPON, pAttackMissSounds ); + } +} + + +void CXenTree :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case TREE_AE_ATTACK: + { + CBaseEntity *pList[8]; + BOOL sound = FALSE; + int count = UTIL_EntitiesInBox( pList, 8, m_pTrigger->pev->absmin, m_pTrigger->pev->absmax, FL_MONSTER|FL_CLIENT ); + Vector forward; + + UTIL_MakeVectorsPrivate( pev->angles, forward, NULL, NULL ); + + for ( int i = 0; i < count; i++ ) + { + if ( pList[i] != this ) + { + if ( pList[i]->pev->owner != edict() ) + { + sound = TRUE; + pList[i]->TakeDamage( pev, pev, 25, DMG_CRUSH | DMG_SLASH ); + pList[i]->pev->punchangle.x = 15; + pList[i]->pev->velocity = pList[i]->pev->velocity + forward * 100; + } + } + } + + if ( sound ) + { + EMIT_SOUND_ARRAY_DYN( CHAN_WEAPON, pAttackHitSounds ); + } + } + return; + } + + CActAnimating::HandleAnimEvent( pEvent ); +} + +void CXenTree :: Think( void ) +{ + float flInterval = StudioFrameAdvance(); + SetNextThink( 0.1 ); + DispatchAnimEvents( flInterval ); + + switch( GetActivity() ) + { + case ACT_MELEE_ATTACK1: + if ( m_fSequenceFinished ) + { + SetActivity( ACT_IDLE ); + pev->framerate = RANDOM_FLOAT( 0.6, 1.4 ); + } + break; + + default: + case ACT_IDLE: + break; + + } +} + + +// UNDONE: These need to smoke somehow when they take damage +// Touch behavior? +// Cause damage in smoke area + +// +// Spores +// +class CXenSpore : public CActAnimating +{ +public: + void Spawn( void ); + void Precache( void ); + void Touch( CBaseEntity *pOther ); + void Think( void ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { Attack(); return 0; } +// void HandleAnimEvent( MonsterEvent_t *pEvent ); + void Attack( void ) {} + + static const char *pModelNames[]; +}; + +class CXenSporeSmall : public CXenSpore +{ + void Spawn( void ); +}; + +class CXenSporeMed : public CXenSpore +{ + void Spawn( void ); +}; + +class CXenSporeLarge : public CXenSpore +{ + void Spawn( void ); + + static const Vector m_hullSizes[]; +}; + +// Fake collision box for big spores +class CXenHull : public CPointEntity +{ +public: + static CXenHull *CreateHull( CBaseEntity *source, const Vector &mins, const Vector &maxs, const Vector &offset ); + int Classify( void ) { return CLASS_BARNACLE; } +}; + +CXenHull *CXenHull :: CreateHull( CBaseEntity *source, const Vector &mins, const Vector &maxs, const Vector &offset ) +{ + CXenHull *pHull = GetClassPtr( (CXenHull *)NULL ); + + UTIL_SetOrigin( pHull, source->pev->origin + offset ); + SET_MODEL( pHull->edict(), STRING(source->pev->model) ); + pHull->pev->solid = SOLID_BBOX; + pHull->pev->classname = MAKE_STRING("xen_hull"); + pHull->pev->movetype = MOVETYPE_NONE; + pHull->pev->owner = source->edict(); + UTIL_SetSize( pHull->pev, mins, maxs ); + pHull->pev->renderamt = 0; + pHull->pev->rendermode = kRenderTransTexture; + + return pHull; +} + + +LINK_ENTITY_TO_CLASS( xen_spore_small, CXenSporeSmall ); +LINK_ENTITY_TO_CLASS( xen_spore_medium, CXenSporeMed ); +LINK_ENTITY_TO_CLASS( xen_spore_large, CXenSporeLarge ); +LINK_ENTITY_TO_CLASS( xen_hull, CXenHull ); + +void CXenSporeSmall::Spawn( void ) +{ + pev->skin = 0; + CXenSpore::Spawn(); + UTIL_SetSize( pev, Vector(-16,-16,0), Vector(16,16,64)); +} +void CXenSporeMed::Spawn( void ) +{ + pev->skin = 1; + CXenSpore::Spawn(); + UTIL_SetSize( pev, Vector(-40,-40,0), Vector(40,40,120)); +} + + +// I just eyeballed these -- fill in hulls for the legs +const Vector CXenSporeLarge::m_hullSizes[] = +{ + Vector( 90, -25, 0 ), + Vector( 25, 75, 0 ), + Vector( -15, -100, 0 ), + Vector( -90, -35, 0 ), + Vector( -90, 60, 0 ), +}; + +void CXenSporeLarge::Spawn( void ) +{ + pev->skin = 2; + CXenSpore::Spawn(); + UTIL_SetSize( pev, Vector(-48,-48,110), Vector(48,48,240)); + + Vector forward, right; + + UTIL_MakeVectorsPrivate( pev->angles, forward, right, NULL ); + + // Rotate the leg hulls into position + for ( int i = 0; i < ARRAYSIZE(m_hullSizes); i++ ) + CXenHull :: CreateHull( this, Vector(-12, -12, 0 ), Vector( 12, 12, 120 ), (m_hullSizes[i].x * forward) + (m_hullSizes[i].y * right) ); +} + +void CXenSpore :: Spawn( void ) +{ + Precache(); + + SET_MODEL( ENT(pev), pModelNames[pev->skin] ); + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_BBOX; + pev->takedamage = DAMAGE_YES; + +// SetActivity( ACT_IDLE ); + pev->sequence = 0; + pev->frame = RANDOM_FLOAT(0,255); + pev->framerate = RANDOM_FLOAT( 0.7, 1.4 ); + ResetSequenceInfo( ); + SetNextThink( RANDOM_FLOAT( 0.1, 0.4 ) ); // Load balance these a bit +} + +const char *CXenSpore::pModelNames[] = +{ + "models/fungus(small).mdl", + "models/fungus.mdl", + "models/fungus(large).mdl", +}; + + +void CXenSpore :: Precache( void ) +{ + PRECACHE_MODEL( (char *)pModelNames[pev->skin] ); +} + + +void CXenSpore :: Touch( CBaseEntity *pOther ) +{ +} + + +void CXenSpore :: Think( void ) +{ + float flInterval = StudioFrameAdvance(); + SetNextThink( 0.1 ); + +#if 0 + DispatchAnimEvents( flInterval ); + + switch( GetActivity() ) + { + default: + case ACT_IDLE: + break; + + } +#endif +} + + diff --git a/dlls/zombie.cpp b/dlls/zombie.cpp new file mode 100644 index 0000000..d41d2dd --- /dev/null +++ b/dlls/zombie.cpp @@ -0,0 +1,589 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Zombie +//========================================================= + +// UNDONE: Don't flinch every time you get hit + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "player.h" +// Wargon: ×òîáû ðàáîòàëè SpawnBlood è AddMultiDamage. (1.1) +#include "weapons.h" + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define ZOMBIE_AE_ATTACK_RIGHT 0x01 +#define ZOMBIE_AE_ATTACK_LEFT 0x02 +#define ZOMBIE_AE_ATTACK_BOTH 0x03 + +#define ZOMBIE_FLINCH_DELAY 2 // at most one flinch every n secs + +class CZombie : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + float MaxYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int IgnoreConditions ( void ); + + float m_flNextFlinch; + + void PainSound( void ); + void AlertSound( void ); + void IdleSound( void ); + void AttackSound( void ); + + static const char *pAttackSounds[]; + static const char *pIdleSounds[]; + static const char *pAlertSounds[]; + static const char *pPainSounds[]; + static const char *pAttackHitSounds[]; + static const char *pAttackMissSounds[]; + + // Wargon: Îñîáûå çâóêè äëÿ ïîòîëî÷íèêà è ïàóêà. + static const char *pCeilingAlertSounds[]; + static const char *pCeilingAttackSounds[]; + static const char *pCeilingPainSounds[]; + static const char *pSpiderAlertSounds[]; + static const char *pSpiderAttackSounds[]; + static const char *pSpiderPainSounds[]; + + //Lev: Åù¸ çâóêè äëÿ òîëñòÿêà + static const char *pStrikerAlertSounds[]; + static const char *pStrikerAttackSounds[]; + static const char *pStrikerPainSounds[]; + + // No range attacks + BOOL CheckRangeAttack1 ( float flDot, float flDist ) { return FALSE; } + BOOL CheckRangeAttack2 ( float flDot, float flDist ) { return FALSE; } + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + virtual Vector BodyTarget( const Vector &posSrc ) { return Center() + Vector( 0.0f, 0.0f, RANDOM_FLOAT( 1.0, 20.0f )); }; // position to shoot at + + // Wargon: Îòäåëüíûå ìíîæèòåëè ïîâðåæäåíèé ïî õèòãðóïïàì äëÿ monster_zombie. (1.1) + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ); + +// void RunAI( void ); +}; + +LINK_ENTITY_TO_CLASS( monster_zombie, CZombie ); + +const char *CZombie::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CZombie::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CZombie::pAttackSounds[] = +{ + "zombie/zo_attack1.wav", + "zombie/zo_attack2.wav", +}; + +const char *CZombie::pIdleSounds[] = +{ + "zombie/zo_idle1.wav", + "zombie/zo_idle2.wav", + "zombie/zo_idle3.wav", + "zombie/zo_idle4.wav", +}; + +const char *CZombie::pAlertSounds[] = +{ + "zombie/zo_alert10.wav", + "zombie/zo_alert20.wav", + "zombie/zo_alert30.wav", +}; + +const char *CZombie::pPainSounds[] = +{ + "zombie/zo_pain1.wav", + "zombie/zo_pain2.wav", +}; + +// Wargon: Îñîáûå çâóêè äëÿ ïîòîëî÷íèêà è ïàóêà. +const char *CZombie::pCeilingAlertSounds[] = +{ + "potolo4nik/zo_alert10.wav", + "potolo4nik/zo_alert20.wav", + "potolo4nik/zo_alert30.wav", +}; + +const char *CZombie::pCeilingAttackSounds[] = +{ + "potolo4nik/zo_attack1.wav", + "potolo4nik/zo_attack2.wav", +}; + +const char *CZombie::pCeilingPainSounds[] = +{ + "potolo4nik/zo_pain1.wav", + "potolo4nik/zo_pain2.wav", +}; + +const char *CZombie::pSpiderAlertSounds[] = +{ + "spider/zo_alert10.wav", + "spider/zo_alert20.wav", + "spider/zo_alert30.wav", +}; + +const char *CZombie::pSpiderAttackSounds[] = +{ + "spider/zo_attack1.wav", + "spider/zo_attack2.wav", +}; + +const char *CZombie::pSpiderPainSounds[] = +{ + "spider/zo_pain1.wav", + "spider/zo_pain2.wav", +}; + +const char *CZombie::pStrikerAlertSounds[] = +{ + "striker/striker_alert1.wav", + "striker/striker_alert2.wav", +}; + +const char *CZombie::pStrikerAttackSounds[] = +{ + "striker/striker_attack1.wav", + "striker/striker_attack2.wav", +}; + +const char *CZombie::pStrikerPainSounds[] = +{ + "striker/striker_pain1.wav", + "striker/striker_pain2.wav", + "striker/striker_pain3.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CZombie :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MONSTER; +} + +//========================================================= +// MaxYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +float CZombie :: MaxYawSpeed( void ) +{ + float ys; + + ys = 120; + +#if 0 + switch ( m_Activity ) + { + } +#endif + + return ys; +} + +int CZombie :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // buz: refuse gas damage + if (bitsDamageType & DMG_NERVEGAS ) + return 0; + + // Take 30% damage from bullets + if ( bitsDamageType == DMG_BULLET ) + { + Vector vecDir = pev->origin - (pevInflictor->absmin + pevInflictor->absmax) * 0.5; + vecDir = vecDir.Normalize(); + float flForce = DamageForce( flDamage ); + pev->velocity = pev->velocity + vecDir * flForce; + flDamage *= 0.3; + } + + // HACK HACK -- until we fix this. + if ( IsAlive() ) + PainSound(); + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +// Wargon: Îòäåëüíûå ìíîæèòåëè ïîâðåæäåíèé ïî õèòãðóïïàì äëÿ monster_zombie. (1.1) +void CZombie :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + if (pev->takedamage) + { + if (pev->spawnflags & SF_MONSTER_INVINCIBLE) + { + CBaseEntity *pEnt = CBaseEntity::Instance(pevAttacker); + if (pEnt->IsPlayer()) + return; + if (pevAttacker->owner) + { + pEnt = CBaseEntity::Instance(pevAttacker->owner); + if (pEnt->IsPlayer()) + return; + } + } + m_LastHitGroup = ptr->iHitgroup; + TraceBleed(flDamage, vecDir, ptr, bitsDamageType); + TraceResult btr; + switch (ptr->iHitgroup) + { + case HITGROUP_GENERIC: + break; + case HITGROUP_HEAD: + UTIL_TraceLine(ptr->vecEndPos, ptr->vecEndPos + vecDir * 172, ignore_monsters, ENT(pev), &btr); + UTIL_TraceCustomDecal( &btr, "brains", RANDOM_FLOAT( 0.0f, 360.0f )); + SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage * 4); + flDamage *= gSkillData.zomHead; + break; + case HITGROUP_CHEST: + flDamage *= gSkillData.zomChest; + break; + case HITGROUP_STOMACH: + flDamage *= gSkillData.zomStomach; + break; + case HITGROUP_LEFTARM: + case HITGROUP_RIGHTARM: + flDamage *= gSkillData.zomArm; + break; + case HITGROUP_LEFTLEG: + case HITGROUP_RIGHTLEG: + flDamage *= gSkillData.zomLeg; + break; + default: + break; + } + SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage * 2); + AddMultiDamage(pevAttacker, this, flDamage, bitsDamageType); + } +} + +// Wargon: Äîáàâëåíû îñîáûå çâóêè â Alert, Attack è Pain äëÿ ïîòîëî÷íèêà è ïàóêà. +void CZombie :: AlertSound( void ) +{ + int pitch = 95 + RANDOM_LONG(0,9); + if ( FStrEq( STRING(pev->model), "models/zombie_c.mdl" ) ) + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pCeilingAlertSounds[ RANDOM_LONG(0,ARRAYSIZE(pCeilingAlertSounds)-1) ], 1.0, ATTN_NORM, 0, pitch ); + else if ( FStrEq( STRING(pev->model), "models/spider.mdl" ) ) + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pSpiderAlertSounds[ RANDOM_LONG(0,ARRAYSIZE(pSpiderAlertSounds)-1) ], 1.0, ATTN_NORM, 0, pitch ); + else if ( FStrEq( STRING(pev->model), "models/zombie_striker.mdl" ) ) + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pStrikerAlertSounds[ RANDOM_LONG(0,ARRAYSIZE(pSpiderAlertSounds)-1) ], 1.0, ATTN_NORM, 0, pitch ); + else + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pAlertSounds[ RANDOM_LONG(0,ARRAYSIZE(pAlertSounds)-1) ], 1.0, ATTN_NORM, 0, pitch ); +} + +void CZombie :: AttackSound( void ) +{ + if ( FStrEq( STRING(pev->model), "models/zombie_c.mdl" ) ) + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pCeilingAttackSounds[ RANDOM_LONG(0,ARRAYSIZE(pCeilingAttackSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + else if ( FStrEq( STRING(pev->model), "models/spider.mdl" ) ) + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pSpiderAttackSounds[ RANDOM_LONG(0,ARRAYSIZE(pSpiderAttackSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + else if ( FStrEq( STRING(pev->model), "models/zombie_striker.mdl" ) ) + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pStrikerAttackSounds[ RANDOM_LONG(0,ARRAYSIZE(pSpiderAlertSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + else + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pAttackSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); +} + +void CZombie :: PainSound( void ) +{ + int pitch = 95 + RANDOM_LONG(0,9); + if (RANDOM_LONG(0,5) < 2) + { + if ( FStrEq( STRING(pev->model), "models/zombie_c.mdl" ) ) + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pCeilingPainSounds[ RANDOM_LONG(0,ARRAYSIZE(pCeilingPainSounds)-1) ], 1.0, ATTN_NORM, 0, pitch ); + else if ( FStrEq( STRING(pev->model), "models/spider.mdl" ) ) + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pSpiderPainSounds[ RANDOM_LONG(0,ARRAYSIZE(pSpiderPainSounds)-1) ], 1.0, ATTN_NORM, 0, pitch ); + else if ( FStrEq( STRING(pev->model), "models/zombie_striker.mdl" ) ) + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pStrikerPainSounds[ RANDOM_LONG(0,ARRAYSIZE(pSpiderAlertSounds)-1) ], 1.0, ATTN_NORM, 0, pitch ); + else + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pPainSounds[ RANDOM_LONG(0,ARRAYSIZE(pPainSounds)-1) ], 1.0, ATTN_NORM, 0, pitch ); + } +} + +void CZombie :: IdleSound( void ) +{ + int pitch = 95 + RANDOM_LONG(0,9); + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pIdleSounds[ RANDOM_LONG(0,ARRAYSIZE(pIdleSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); +} + +// buz: update player's geiger counter +/*void CZombie :: RunAI() +{ + if (!m_pPlayer2) + { + edict_t *pent = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + if (!FNullEnt(pent)) + { + m_pPlayer2 = GetClassPtr( (CBasePlayer *)VARS(pent)); + } + else + ALERT(at_console, "ERROR: zombie cant find the player entity!!\n"); + } + else + { + Vector vecSpot1 = (pev->absmin + pev->absmax) * 0.5; + Vector vecSpot2 = (m_pPlayer2->pev->absmin + m_pPlayer2->pev->absmax) * 0.5; + Vector vecRange = vecSpot1 - vecSpot2; + float flRange = vecRange.Length(); + + if (m_pPlayer2->m_flgeigerRange >= flRange) + m_pPlayer2->m_flgeigerRange = flRange; + } + + CBaseMonster::RunAI(); +}*/ + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CZombie :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case ZOMBIE_AE_ATTACK_RIGHT: + { + // do stuff for this event. + // ALERT( at_console, "Slash right!\n" ); + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.zombieDmgOneSlash, DMG_SLASH ); + if ( pHurt ) + { + if ( pHurt->pev->flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->pev->punchangle.z = -18; + pHurt->pev->punchangle.x = 5; + pHurt->pev->velocity = pHurt->pev->velocity - gpGlobals->v_right * 100; + } + // Play a random attack hit sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + } + else // Play a random attack miss sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + + if (RANDOM_LONG(0,1)) + AttackSound(); + } + break; + + case ZOMBIE_AE_ATTACK_LEFT: + { + // do stuff for this event. + // ALERT( at_console, "Slash left!\n" ); + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.zombieDmgOneSlash, DMG_SLASH ); + if ( pHurt ) + { + if ( pHurt->pev->flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->pev->punchangle.z = 18; + pHurt->pev->punchangle.x = 5; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_right * 100; + } + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + } + else + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + + if (RANDOM_LONG(0,1)) + AttackSound(); + } + break; + + case ZOMBIE_AE_ATTACK_BOTH: + { + // do stuff for this event. + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.zombieDmgBothSlash, DMG_SLASH ); + if ( pHurt ) + { + if ( pHurt->pev->flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->pev->punchangle.x = 5; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * -100; + } + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + } + else + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + + if (RANDOM_LONG(0,1)) + AttackSound(); + } + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CZombie :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/zombie.mdl"); + + // Wargon: Îñîáûå ðàçìåðû äëÿ ïîòîëî÷íèêà è ïàóêà. + if ( FStrEq( STRING(pev->model), "models/zombie_c.mdl" ) ) + UTIL_SetSize( pev, Vector(-16, -16, 0), Vector(16, 16, 128) ); + else if ( FStrEq( STRING(pev->model), "models/spider.mdl" ) ) + UTIL_SetSize( pev, Vector(-32, -32, 0), Vector(32, 32, 64) ); + else + UTIL_SetSize( pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + if ( pev->health == 0.0f || pev->health == 999.0f ) + pev->health = gSkillData.zombieHealth; + pev->view_ofs = VEC_VIEW;// position of the eyes relative to monster's origin. + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_afCapability = bits_CAP_DOORS_GROUP; + +// pev->renderfx = 49; // buz: hack for studiorenderer to draw red eyes + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CZombie :: Precache() // Wargon: Îñîáûå çâóêè äëÿ ïîòîëî÷íèêà è ïàóêà. +{ + int i; + + if ( FStrEq( STRING(pev->model), "models/zombie_c.mdl" ) ) + { + PRECACHE_MODEL("models/zombie_c.mdl"); + + for ( i = 0; i < ARRAYSIZE( pCeilingAlertSounds ); i++ ) + PRECACHE_SOUND((char *)pCeilingAlertSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pCeilingAttackSounds ); i++ ) + PRECACHE_SOUND((char *)pCeilingAttackSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pCeilingPainSounds ); i++ ) + PRECACHE_SOUND((char *)pCeilingPainSounds[i]); + } + else if ( FStrEq( STRING(pev->model), "models/spider.mdl" ) ) + { + PRECACHE_MODEL("models/spider.mdl"); + + for ( i = 0; i < ARRAYSIZE( pSpiderAlertSounds ); i++ ) + PRECACHE_SOUND((char *)pSpiderAlertSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pSpiderAttackSounds ); i++ ) + PRECACHE_SOUND((char *)pSpiderAttackSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pSpiderPainSounds ); i++ ) + PRECACHE_SOUND((char *)pSpiderPainSounds[i]); + } + else if ( FStrEq( STRING(pev->model), "models/zombie_striker.mdl" ) ) + { + PRECACHE_MODEL("models/zombie_striker.mdl"); + + for ( i = 0; i < ARRAYSIZE( pStrikerAlertSounds ); i++ ) + PRECACHE_SOUND((char *)pStrikerAlertSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pStrikerAttackSounds ); i++ ) + PRECACHE_SOUND((char *)pStrikerAttackSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pStrikerPainSounds ); i++ ) + PRECACHE_SOUND((char *)pStrikerPainSounds[i]); + } + else + { + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); + else + PRECACHE_MODEL("models/zombie.mdl"); + + for ( i = 0; i < ARRAYSIZE( pAlertSounds ); i++ ) + PRECACHE_SOUND((char *)pAlertSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAttackSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pPainSounds ); i++ ) + PRECACHE_SOUND((char *)pPainSounds[i]); + } + + for ( i = 0; i < ARRAYSIZE( pIdleSounds ); i++ ) + PRECACHE_SOUND((char *)pIdleSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAttackHitSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackHitSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAttackMissSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackMissSounds[i]); + +// PRECACHE_MODEL("models/zombie_eyes.mdl"); // buz +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + + + +int CZombie::IgnoreConditions ( void ) +{ + int iIgnore = CBaseMonster::IgnoreConditions(); + + if ((m_Activity == ACT_MELEE_ATTACK1) || (m_Activity == ACT_MELEE_ATTACK1)) + { +#if 0 + if (pev->health < 20) + iIgnore |= (bits_COND_LIGHT_DAMAGE|bits_COND_HEAVY_DAMAGE); + else +#endif + if (m_flNextFlinch >= gpGlobals->time) + iIgnore |= (bits_COND_LIGHT_DAMAGE|bits_COND_HEAVY_DAMAGE); + } + + if ((m_Activity == ACT_SMALL_FLINCH) || (m_Activity == ACT_BIG_FLINCH)) + { + if (m_flNextFlinch < gpGlobals->time) + m_flNextFlinch = gpGlobals->time + ZOMBIE_FLINCH_DELAY; + } + + return iIgnore; + +} diff --git a/dof_bokeh.cpp b/dof_bokeh.cpp new file mode 100644 index 0000000..735ed39 --- /dev/null +++ b/dof_bokeh.cpp @@ -0,0 +1,299 @@ +uniform sampler2D bgl_RenderedTexture; +uniform sampler2D bgl_DepthTexture; +uniform float bgl_RenderedTextureWidth; +uniform float bgl_RenderedTextureHeight; + +#define PI 3.14159265 + +float width = bgl_RenderedTextureWidth; //texture width +float height = bgl_RenderedTextureHeight; //texture height + +vec2 texel = vec2(1.0/width,1.0/height); + +//uniform variables from external script + +uniform float focalDepth; //focal distance value in meters, but you may use autofocus option below +uniform float focalLength; //focal length in mm +uniform float fstop; //f-stop value +uniform bool showFocus; //show debug focus point and focal range (red = focal point, green = focal range) + +/* +make sure that these two values are the same for your camera, otherwise distances will be wrong. +*/ + +float znear = 0.1; //camera clipping start +float zfar = 100.0; //camera clipping end + +//------------------------------------------ +//user variables + +int samples = 3; //samples on the first ring +int rings = 3; //ring count + +bool manualdof = false; //manual dof calculation +float ndofstart = 1.0; //near dof blur start +float ndofdist = 2.0; //near dof blur falloff distance +float fdofstart = 1.0; //far dof blur start +float fdofdist = 3.0; //far dof blur falloff distance + +float CoC = 0.03;//circle of confusion size in mm (35mm film = 0.03mm) + +bool vignetting = true; //use optical lens vignetting? +float vignout = 1.3; //vignetting outer border +float vignin = 0.0; //vignetting inner border +float vignfade = 22.0; //f-stops till vignete fades + +bool autofocus = false; //use autofocus in shader? disable if you use external focalDepth value +vec2 focus = vec2(0.5,0.5); // autofocus point on screen (0.0,0.0 - left lower corner, 1.0,1.0 - upper right) +float maxblur = 1.0; //clamp value of max blur (0.0 = no blur,1.0 default) + +float threshold = 0.5; //highlight threshold; +float gain = 2.0; //highlight gain; + +float bias = 0.5; //bokeh edge bias +float fringe = 0.7; //bokeh chromatic aberration/fringing + +bool noise = true; //use noise instead of pattern for sample dithering +float namount = 0.0001; //dither amount + +bool depthblur = false; //blur the depth buffer? +float dbsize = 1.25; //depthblursize + +/* +next part is experimental +not looking good with small sample and ring count +looks okay starting from samples = 4, rings = 4 +*/ + +bool pentagon = false; //use pentagon as bokeh shape? +float feather = 0.4; //pentagon shape feather + +//------------------------------------------ + + +float penta(vec2 coords) //pentagonal shape +{ + float scale = float(rings) - 1.3; + vec4 HS0 = vec4( 1.0, 0.0, 0.0, 1.0); + vec4 HS1 = vec4( 0.309016994, 0.951056516, 0.0, 1.0); + vec4 HS2 = vec4(-0.809016994, 0.587785252, 0.0, 1.0); + vec4 HS3 = vec4(-0.809016994,-0.587785252, 0.0, 1.0); + vec4 HS4 = vec4( 0.309016994,-0.951056516, 0.0, 1.0); + vec4 HS5 = vec4( 0.0 ,0.0 , 1.0, 1.0); + + vec4 one = vec4( 1.0 ); + + vec4 P = vec4((coords),vec2(scale, scale)); + + vec4 dist = vec4(0.0); + float inorout = -4.0; + + dist.x = dot( P, HS0 ); + dist.y = dot( P, HS1 ); + dist.z = dot( P, HS2 ); + dist.w = dot( P, HS3 ); + + dist = smoothstep( -feather, feather, dist ); + + inorout += dot( dist, one ); + + dist.x = dot( P, HS4 ); + dist.y = HS5.w - abs( P.z ); + + dist = smoothstep( -feather, feather, dist ); + inorout += dist.x; + + return clamp( inorout, 0.0, 1.0 ); +} + +float bdepth(vec2 coords) //blurring depth +{ + float d = 0.0; + float kernel[9]; + vec2 offset[9]; + + vec2 wh = vec2(texel.x, texel.y) * dbsize; + + offset[0] = vec2(-wh.x,-wh.y); + offset[1] = vec2( 0.0, -wh.y); + offset[2] = vec2( wh.x -wh.y); + + offset[3] = vec2(-wh.x, 0.0); + offset[4] = vec2( 0.0, 0.0); + offset[5] = vec2( wh.x, 0.0); + + offset[6] = vec2(-wh.x, wh.y); + offset[7] = vec2( 0.0, wh.y); + offset[8] = vec2( wh.x, wh.y); + + kernel[0] = 1.0/16.0; kernel[1] = 2.0/16.0; kernel[2] = 1.0/16.0; + kernel[3] = 2.0/16.0; kernel[4] = 4.0/16.0; kernel[5] = 2.0/16.0; + kernel[6] = 1.0/16.0; kernel[7] = 2.0/16.0; kernel[8] = 1.0/16.0; + + + for( int i=0; i<9; i++ ) + { + float tmp = texture2D(bgl_DepthTexture, coords + offset[i]).r; + d += tmp * kernel[i]; + } + + return d; +} + + +vec3 color(vec2 coords,float blur) //processing the sample +{ + vec3 col = vec3(0.0); + + col.r = texture2D(bgl_RenderedTexture,coords + vec2(0.0,1.0)*texel*fringe*blur).r; + col.g = texture2D(bgl_RenderedTexture,coords + vec2(-0.866,-0.5)*texel*fringe*blur).g; + col.b = texture2D(bgl_RenderedTexture,coords + vec2(0.866,-0.5)*texel*fringe*blur).b; + + vec3 lumcoeff = vec3(0.299,0.587,0.114); + float lum = dot(col.rgb, lumcoeff); + float thresh = max((lum-threshold)*gain, 0.0); + return col+mix(vec3(0.0),col,thresh*blur); +} + +vec2 rand(vec2 coord) //generating noise/pattern texture for dithering +{ + float noiseX = ((fract(1.0-coord.s*(width/2.0))*0.25)+(fract(coord.t*(height/2.0))*0.75))*2.0-1.0; + float noiseY = ((fract(1.0-coord.s*(width/2.0))*0.75)+(fract(coord.t*(height/2.0))*0.25))*2.0-1.0; + + if (noise) + { + noiseX = clamp(fract(sin(dot(coord ,vec2(12.9898,78.233))) * 43758.5453),0.0,1.0)*2.0-1.0; + noiseY = clamp(fract(sin(dot(coord ,vec2(12.9898,78.233)*2.0)) * 43758.5453),0.0,1.0)*2.0-1.0; + } + return vec2(noiseX,noiseY); +} + +vec3 debugFocus(vec3 col, float blur, float depth) +{ + float edge = 0.002*depth; //distance based edge smoothing + float m = clamp(smoothstep(0.0,edge,blur),0.0,1.0); + float e = clamp(smoothstep(1.0-edge,1.0,blur),0.0,1.0); + + col = mix(col,vec3(1.0,0.5,0.0),(1.0-m)*0.6); + col = mix(col,vec3(0.0,0.5,1.0),((1.0-e)-(1.0-m))*0.2); + + return col; +} + +float linearize(float depth) +{ + return -zfar * znear / (depth * (zfar - znear) - zfar); +} + +float vignette() +{ + float dist = distance(gl_TexCoord[3].xy, vec2(0.5,0.5)); + dist = smoothstep(vignout+(fstop/vignfade), vignin+(fstop/vignfade), dist); + return clamp(dist,0.0,1.0); +} + +void main() +{ + //scene depth calculation + + float depth = linearize(texture2D(bgl_DepthTexture,gl_TexCoord[0].xy).x); + + if (depthblur) + { + depth = linearize(bdepth(gl_TexCoord[0].xy)); + } + + //focal plane calculation + + float fDepth = focalDepth; + + if (autofocus) + { + fDepth = linearize(texture2D(bgl_DepthTexture,focus).x); + } + + //dof blur factor calculation + + float blur = 0.0; + + if (manualdof) + { + float a = depth-fDepth; //focal plane + float b = (a-fdofstart)/fdofdist; //far DoF + float c = (-a-ndofstart)/ndofdist; //near Dof + blur = (a>0.0)?b:c; + } + + else + { + float f = focalLength; //focal length in mm + float d = fDepth*1000.0; //focal plane in mm + float o = depth*1000.0; //depth in mm + + float a = (o*f)/(o-f); + float b = (d*f)/(d-f); + float c = (d-f)/(d*fstop*CoC); + + blur = abs(a-b)*c; + } + + blur = clamp(blur,0.0,1.0); + + // calculation of pattern for ditering + + vec2 noise = rand(gl_TexCoord[0].xy)*namount*blur; + + // getting blur x and y step factor + + float w = (1.0/width)*blur*maxblur+noise.x; + float h = (1.0/height)*blur*maxblur+noise.y; + + // calculation of final color + + vec3 col = vec3(0.0); + + if(blur < 0.05) //some optimization thingy + { + col = texture2D(bgl_RenderedTexture, gl_TexCoord[0].xy).rgb; + } + + else + { + col = texture2D(bgl_RenderedTexture, gl_TexCoord[0].xy).rgb; + float s = 1.0; + int ringsamples; + + for (int i = 1; i <= rings; i += 1) + { + ringsamples = i * samples; + + for (int j = 0 ; j < ringsamples ; j += 1) + { + float step = PI*2.0 / float(ringsamples); + float pw = (cos(float(j)*step)*float(i)); + float ph = (sin(float(j)*step)*float(i)); + float p = 1.0; + if (pentagon) + { + p = penta(vec2(pw,ph)); + } + col += color(gl_TexCoord[0].xy + vec2(pw*w,ph*h),blur)*mix(1.0,(float(i))/(float(rings)),bias)*p; + s += 1.0*mix(1.0,(float(i))/(float(rings)),bias)*p; + } + } + col /= s; //divide by sample count + } + + if (showFocus) + { + col = debugFocus(col, blur, depth); + } + + if (vignetting) + { + col *= vignette(); + } + + gl_FragColor.rgb = col; + gl_FragColor.a = 1.0; +} \ No newline at end of file diff --git a/engine/alert.h b/engine/alert.h new file mode 100644 index 0000000..91e0377 --- /dev/null +++ b/engine/alert.h @@ -0,0 +1,31 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef ALERT_H +#define ALERT_H + +#define ARRAYSIZE( p ) (sizeof( p ) / sizeof( p[0] )) + +typedef enum +{ + at_notice, + at_console, // same as at_notice, but forces a ConPrintf, not a message box + at_aiconsole, // same as at_console, but only shown if developer level is 2! + at_warning, + at_error, + at_logged // Server print to console ( only in multiplayer games ). +} ALERT_TYPE; + +#endif//ALERT_H \ No newline at end of file diff --git a/engine/alias.h b/engine/alias.h new file mode 100644 index 0000000..c78f161 --- /dev/null +++ b/engine/alias.h @@ -0,0 +1,152 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef ALIAS_H +#define ALIAS_H + +/* +============================================================================== + +ALIAS MODELS + +Alias models are position independent, so the cache manager can move them. +============================================================================== +*/ + +#define IDALIASHEADER (('O'<<24)+('P'<<16)+('D'<<8)+'I') // little-endian "IDPO" + +#define ALIAS_VERSION 6 + +#define MAXALIASVERTS 2048 +#define MAXALIASFRAMES 256 +#define MAXALIASTRIS 4096 +#define MAX_SKINS 32 + +// client-side model flags +#define ALIAS_ROCKET 0x0001 // leave a trail +#define ALIAS_GRENADE 0x0002 // leave a trail +#define ALIAS_GIB 0x0004 // leave a trail +#define ALIAS_ROTATE 0x0008 // rotate (bonus items) +#define ALIAS_TRACER 0x0010 // green split trail +#define ALIAS_ZOMGIB 0x0020 // small blood trail +#define ALIAS_TRACER2 0x0040 // orange split trail + rotate +#define ALIAS_TRACER3 0x0080 // purple trail + +// must match definition in sprite.h +#ifndef SYNCTYPE_T +#define SYNCTYPE_T +typedef enum +{ + ST_SYNC = 0, + ST_RAND +} synctype_t; +#endif + +typedef enum +{ + ALIAS_SINGLE = 0, + ALIAS_GROUP +} aliasframetype_t; + +typedef enum +{ + ALIAS_SKIN_SINGLE = 0, + ALIAS_SKIN_GROUP +} aliasskintype_t; + +typedef struct +{ + int ident; + int version; + vec3_t scale; + vec3_t scale_origin; + float boundingradius; + vec3_t eyeposition; + int numskins; + int skinwidth; + int skinheight; + int numverts; + int numtris; + int numframes; + synctype_t synctype; + int flags; + float size; +} daliashdr_t; + +// TODO: could be shorts +typedef struct +{ + int onseam; + int s; + int t; +} stvert_t; + +// This mirrors trivert_t in trilib.h, is present so Quake knows how to +// load this data +typedef struct +{ + byte v[3]; + byte lightnormalindex; +} trivertex_t; + +typedef struct dtriangle_s +{ + int facesfront; + int vertindex[3]; +} dtriangle_t; + +#define DT_FACES_FRONT 0x0010 +#define ALIAS_ONSEAM 0x0020 + +typedef struct +{ + trivertex_t bboxmin; // lightnormal isn't used + trivertex_t bboxmax; // lightnormal isn't used + char name[16]; // frame name from grabbing +} daliasframe_t; + +typedef struct +{ + int numframes; + trivertex_t bboxmin; // lightnormal isn't used + trivertex_t bboxmax; // lightnormal isn't used +} daliasgroup_t; + +typedef struct +{ + int numskins; +} daliasskingroup_t; + +typedef struct +{ + float interval; +} daliasinterval_t; + +typedef struct +{ + float interval; +} daliasskininterval_t; + +typedef struct +{ + aliasframetype_t type; +} daliasframetype_t; + +typedef struct +{ + aliasskintype_t type; +} daliasskintype_t; + +#endif//ALIAS_H \ No newline at end of file diff --git a/engine/anorms.h b/engine/anorms.h new file mode 100644 index 0000000..c90f8d6 --- /dev/null +++ b/engine/anorms.h @@ -0,0 +1,177 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +{-0.525731, 0.000000, 0.850651}, +{-0.442863, 0.238856, 0.864188}, +{-0.295242, 0.000000, 0.955423}, +{-0.309017, 0.500000, 0.809017}, +{-0.162460, 0.262866, 0.951056}, +{0.000000, 0.000000, 1.000000}, +{0.000000, 0.850651, 0.525731}, +{-0.147621, 0.716567, 0.681718}, +{0.147621, 0.716567, 0.681718}, +{0.000000, 0.525731, 0.850651}, +{0.309017, 0.500000, 0.809017}, +{0.525731, 0.000000, 0.850651}, +{0.295242, 0.000000, 0.955423}, +{0.442863, 0.238856, 0.864188}, +{0.162460, 0.262866, 0.951056}, +{-0.681718, 0.147621, 0.716567}, +{-0.809017, 0.309017, 0.500000}, +{-0.587785, 0.425325, 0.688191}, +{-0.850651, 0.525731, 0.000000}, +{-0.864188, 0.442863, 0.238856}, +{-0.716567, 0.681718, 0.147621}, +{-0.688191, 0.587785, 0.425325}, +{-0.500000, 0.809017, 0.309017}, +{-0.238856, 0.864188, 0.442863}, +{-0.425325, 0.688191, 0.587785}, +{-0.716567, 0.681718, -0.147621}, +{-0.500000, 0.809017, -0.309017}, +{-0.525731, 0.850651, 0.000000}, +{0.000000, 0.850651, -0.525731}, +{-0.238856, 0.864188, -0.442863}, +{0.000000, 0.955423, -0.295242}, +{-0.262866, 0.951056, -0.162460}, +{0.000000, 1.000000, 0.000000}, +{0.000000, 0.955423, 0.295242}, +{-0.262866, 0.951056, 0.162460}, +{0.238856, 0.864188, 0.442863}, +{0.262866, 0.951056, 0.162460}, +{0.500000, 0.809017, 0.309017}, +{0.238856, 0.864188, -0.442863}, +{0.262866, 0.951056, -0.162460}, +{0.500000, 0.809017, -0.309017}, +{0.850651, 0.525731, 0.000000}, +{0.716567, 0.681718, 0.147621}, +{0.716567, 0.681718, -0.147621}, +{0.525731, 0.850651, 0.000000}, +{0.425325, 0.688191, 0.587785}, +{0.864188, 0.442863, 0.238856}, +{0.688191, 0.587785, 0.425325}, +{0.809017, 0.309017, 0.500000}, +{0.681718, 0.147621, 0.716567}, +{0.587785, 0.425325, 0.688191}, +{0.955423, 0.295242, 0.000000}, +{1.000000, 0.000000, 0.000000}, +{0.951056, 0.162460, 0.262866}, +{0.850651, -0.525731, 0.000000}, +{0.955423, -0.295242, 0.000000}, +{0.864188, -0.442863, 0.238856}, +{0.951056, -0.162460, 0.262866}, +{0.809017, -0.309017, 0.500000}, +{0.681718, -0.147621, 0.716567}, +{0.850651, 0.000000, 0.525731}, +{0.864188, 0.442863, -0.238856}, +{0.809017, 0.309017, -0.500000}, +{0.951056, 0.162460, -0.262866}, +{0.525731, 0.000000, -0.850651}, +{0.681718, 0.147621, -0.716567}, +{0.681718, -0.147621, -0.716567}, +{0.850651, 0.000000, -0.525731}, +{0.809017, -0.309017, -0.500000}, +{0.864188, -0.442863, -0.238856}, +{0.951056, -0.162460, -0.262866}, +{0.147621, 0.716567, -0.681718}, +{0.309017, 0.500000, -0.809017}, +{0.425325, 0.688191, -0.587785}, +{0.442863, 0.238856, -0.864188}, +{0.587785, 0.425325, -0.688191}, +{0.688191, 0.587785, -0.425325}, +{-0.147621, 0.716567, -0.681718}, +{-0.309017, 0.500000, -0.809017}, +{0.000000, 0.525731, -0.850651}, +{-0.525731, 0.000000, -0.850651}, +{-0.442863, 0.238856, -0.864188}, +{-0.295242, 0.000000, -0.955423}, +{-0.162460, 0.262866, -0.951056}, +{0.000000, 0.000000, -1.000000}, +{0.295242, 0.000000, -0.955423}, +{0.162460, 0.262866, -0.951056}, +{-0.442863, -0.238856, -0.864188}, +{-0.309017, -0.500000, -0.809017}, +{-0.162460, -0.262866, -0.951056}, +{0.000000, -0.850651, -0.525731}, +{-0.147621, -0.716567, -0.681718}, +{0.147621, -0.716567, -0.681718}, +{0.000000, -0.525731, -0.850651}, +{0.309017, -0.500000, -0.809017}, +{0.442863, -0.238856, -0.864188}, +{0.162460, -0.262866, -0.951056}, +{0.238856, -0.864188, -0.442863}, +{0.500000, -0.809017, -0.309017}, +{0.425325, -0.688191, -0.587785}, +{0.716567, -0.681718, -0.147621}, +{0.688191, -0.587785, -0.425325}, +{0.587785, -0.425325, -0.688191}, +{0.000000, -0.955423, -0.295242}, +{0.000000, -1.000000, 0.000000}, +{0.262866, -0.951056, -0.162460}, +{0.000000, -0.850651, 0.525731}, +{0.000000, -0.955423, 0.295242}, +{0.238856, -0.864188, 0.442863}, +{0.262866, -0.951056, 0.162460}, +{0.500000, -0.809017, 0.309017}, +{0.716567, -0.681718, 0.147621}, +{0.525731, -0.850651, 0.000000}, +{-0.238856, -0.864188, -0.442863}, +{-0.500000, -0.809017, -0.309017}, +{-0.262866, -0.951056, -0.162460}, +{-0.850651, -0.525731, 0.000000}, +{-0.716567, -0.681718, -0.147621}, +{-0.716567, -0.681718, 0.147621}, +{-0.525731, -0.850651, 0.000000}, +{-0.500000, -0.809017, 0.309017}, +{-0.238856, -0.864188, 0.442863}, +{-0.262866, -0.951056, 0.162460}, +{-0.864188, -0.442863, 0.238856}, +{-0.809017, -0.309017, 0.500000}, +{-0.688191, -0.587785, 0.425325}, +{-0.681718, -0.147621, 0.716567}, +{-0.442863, -0.238856, 0.864188}, +{-0.587785, -0.425325, 0.688191}, +{-0.309017, -0.500000, 0.809017}, +{-0.147621, -0.716567, 0.681718}, +{-0.425325, -0.688191, 0.587785}, +{-0.162460, -0.262866, 0.951056}, +{0.442863, -0.238856, 0.864188}, +{0.162460, -0.262866, 0.951056}, +{0.309017, -0.500000, 0.809017}, +{0.147621, -0.716567, 0.681718}, +{0.000000, -0.525731, 0.850651}, +{0.425325, -0.688191, 0.587785}, +{0.587785, -0.425325, 0.688191}, +{0.688191, -0.587785, 0.425325}, +{-0.955423, 0.295242, 0.000000}, +{-0.951056, 0.162460, 0.262866}, +{-1.000000, 0.000000, 0.000000}, +{-0.850651, 0.000000, 0.525731}, +{-0.955423, -0.295242, 0.000000}, +{-0.951056, -0.162460, 0.262866}, +{-0.864188, 0.442863, -0.238856}, +{-0.951056, 0.162460, -0.262866}, +{-0.809017, 0.309017, -0.500000}, +{-0.864188, -0.442863, -0.238856}, +{-0.951056, -0.162460, -0.262866}, +{-0.809017, -0.309017, -0.500000}, +{-0.681718, 0.147621, -0.716567}, +{-0.681718, -0.147621, -0.716567}, +{-0.850651, 0.000000, -0.525731}, +{-0.688191, 0.587785, -0.425325}, +{-0.587785, 0.425325, -0.688191}, +{-0.425325, 0.688191, -0.587785}, +{-0.425325, -0.688191, -0.587785}, +{-0.587785, -0.425325, -0.688191}, +{-0.688191, -0.587785, -0.425325}, diff --git a/engine/cdll_exp.h b/engine/cdll_exp.h new file mode 100644 index 0000000..e4622cb --- /dev/null +++ b/engine/cdll_exp.h @@ -0,0 +1,67 @@ +/* +cdll_exp.h - exports for client +Copyright (C) 2013 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ +#ifndef CDLL_EXP_H +#define CDLL_EXP_H + +// NOTE: ordering is important! +typedef struct cldll_func_s +{ + int (*pfnInitialize)( cl_enginefunc_t *pEnginefuncs, int iVersion ); + void (*pfnInit)( void ); + int (*pfnVidInit)( void ); + int (*pfnRedraw)( float flTime, int intermission ); + int (*pfnUpdateClientData)( client_data_t *cdata, float flTime ); + void (*pfnReset)( void ); + void (*pfnPlayerMove)( struct playermove_s *ppmove, int server ); + void (*pfnPlayerMoveInit)( struct playermove_s *ppmove ); + char (*pfnPlayerMoveTexture)( char *name ); + void (*IN_ActivateMouse)( void ); + void (*IN_DeactivateMouse)( void ); + void (*IN_MouseEvent)( int mstate ); + void (*IN_ClearStates)( void ); + void (*IN_Accumulate)( void ); + void (*CL_CreateMove)( float frametime, struct usercmd_s *cmd, int active ); + int (*CL_IsThirdPerson)( void ); + void (*CL_CameraOffset)( float *ofs ); // unused + void *(*KB_Find)( const char *name ); + void (*CAM_Think)( void ); // camera stuff + void (*pfnCalcRefdef)( ref_params_t *pparams ); + int (*pfnAddEntity)( int type, cl_entity_t *ent, const char *modelname ); + void (*pfnCreateEntities)( void ); + void (*pfnDrawNormalTriangles)( void ); + void (*pfnDrawTransparentTriangles)( void ); + void (*pfnStudioEvent)( const struct mstudioevent_s *event, const cl_entity_t *entity ); + void (*pfnPostRunCmd)( struct local_state_s *from, struct local_state_s *to, usercmd_t *cmd, int runfuncs, double time, unsigned int random_seed ); + void (*pfnShutdown)( void ); + void (*pfnTxferLocalOverrides)( entity_state_t *state, const clientdata_t *client ); + void (*pfnProcessPlayerState)( entity_state_t *dst, const entity_state_t *src ); + void (*pfnTxferPredictionData)( entity_state_t *ps, const entity_state_t *pps, clientdata_t *pcd, const clientdata_t *ppcd, weapon_data_t *wd, const weapon_data_t *pwd ); + void (*pfnDemo_ReadBuffer)( int size, byte *buffer ); + int (*pfnConnectionlessPacket)( const struct netadr_s *net_from, const char *args, char *buffer, int *size ); + int (*pfnGetHullBounds)( int hullnumber, float *mins, float *maxs ); + void (*pfnFrame)( double time ); + int (*pfnKey_Event)( int eventcode, int keynum, const char *pszCurrentBinding ); + void (*pfnTempEntUpdate)( double frametime, double client_time, double cl_gravity, struct tempent_s **ppTempEntFree, struct tempent_s **ppTempEntActive, int ( *Callback_AddVisibleEntity )( cl_entity_t *pEntity ), void ( *Callback_TempEntPlaySound )( struct tempent_s *pTemp, float damp )); + cl_entity_t *(*pfnGetUserEntity)( int index ); + void (*pfnVoiceStatus)( int entindex, qboolean bTalking ); + void (*pfnDirectorMessage)( int iSize, void *pbuf ); + int (*pfnGetStudioModelInterface)( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio ); + void (*pfnChatInputPosition)( int *x, int *y ); + // Xash3D extension + int (*pfnGetRenderInterface)( int version, render_api_t *renderfuncs, render_interface_t *callback ); + void (*pfnClipMoveToEntity)( struct physent_s *pe, const vec3_t start, vec3_t mins, vec3_t maxs, const vec3_t end, struct pmtrace_s *tr ); +} cldll_func_t; + +#endif//CDLL_EXP_H \ No newline at end of file diff --git a/engine/cdll_int.h b/engine/cdll_int.h new file mode 100644 index 0000000..6741269 --- /dev/null +++ b/engine/cdll_int.h @@ -0,0 +1,297 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cdll_int.h +// +// 4-23-98 +// JOHN: client dll interface declarations +// + +#ifndef CDLL_INT_H +#define CDLL_INT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "const.h" +#include + +#ifndef offsetof +#define offsetof(s,m) (size_t)&(((s *)0)->m) +#endif + +// this file is included by both the engine and the client-dll, +// so make sure engine declarations aren't done twice + +typedef int HSPRITE; // handle to a graphic +typedef int (*pfnUserMsgHook)( const char *pszName, int iSize, void *pbuf ); + +#include "wrect.h" + +#define SCRINFO_SCREENFLASH 1 +#define SCRINFO_STRETCHED 2 + +typedef struct SCREENINFO_s +{ + int iSize; + int iWidth; + int iHeight; + int iFlags; + int iCharHeight; + short charWidths[256]; +} SCREENINFO; + +typedef struct client_data_s +{ + // fields that cannot be modified (ie. have no effect if changed) + vec3_t origin; + + // fields that can be changed by the cldll + vec3_t viewangles; + int iWeaponBits; + float fov; // field of view +} client_data_t; + +typedef struct client_sprite_s +{ + char szName[64]; + char szSprite[64]; + int hspr; + int iRes; + wrect_t rc; +} client_sprite_t; + +typedef struct client_textmessage_s +{ + int effect; + byte r1, g1, b1, a1; // 2 colors for effects + byte r2, g2, b2, a2; + float x; + float y; + float fadein; + float fadeout; + float holdtime; + float fxtime; + const char *pName; + const char *pMessage; +} client_textmessage_t; + +typedef struct hud_player_info_s +{ + char *name; + short ping; + byte thisplayer; // TRUE if this is the calling player + + // stuff that's unused at the moment, but should be done + byte spectator; + byte packetloss; + char *model; + short topcolor; + short bottomcolor; +} hud_player_info_t; + +typedef struct cl_enginefuncs_s +{ + // sprite handlers + HSPRITE (*pfnSPR_Load)( const char *szPicName ); + int (*pfnSPR_Frames)( HSPRITE hPic ); + int (*pfnSPR_Height)( HSPRITE hPic, int frame ); + int (*pfnSPR_Width)( HSPRITE hPic, int frame ); + void (*pfnSPR_Set)( HSPRITE hPic, int r, int g, int b ); + void (*pfnSPR_Draw)( int frame, int x, int y, const wrect_t *prc ); + void (*pfnSPR_DrawHoles)( int frame, int x, int y, const wrect_t *prc ); + void (*pfnSPR_DrawAdditive)( int frame, int x, int y, const wrect_t *prc ); + void (*pfnSPR_EnableScissor)( int x, int y, int width, int height ); + void (*pfnSPR_DisableScissor)( void ); + client_sprite_t *(*pfnSPR_GetList)( char *psz, int *piCount ); + + // screen handlers + void (*pfnFillRGBA)( int x, int y, int width, int height, int r, int g, int b, int a ); + int (*pfnGetScreenInfo)( SCREENINFO *pscrinfo ); + void (*pfnSetCrosshair)( HSPRITE hspr, wrect_t rc, int r, int g, int b ); + + // cvar handlers + struct cvar_s *(*pfnRegisterVariable)( char *szName, char *szValue, int flags ); + float (*pfnGetCvarFloat)( char *szName ); + char* (*pfnGetCvarString)( char *szName ); + + // command handlers + int (*pfnAddCommand)( char *cmd_name, void (*function)(void) ); + int (*pfnHookUserMsg)( char *szMsgName, pfnUserMsgHook pfn ); + int (*pfnServerCmd)( char *szCmdString ); + int (*pfnClientCmd)( char *szCmdString ); + + void (*pfnGetPlayerInfo)( int ent_num, hud_player_info_t *pinfo ); + + // sound handlers + void (*pfnPlaySoundByName)( char *szSound, float volume ); + void (*pfnPlaySoundByIndex)( int iSound, float volume ); + + // vector helpers + void (*pfnAngleVectors)( const float *vecAngles, float *forward, float *right, float *up ); + + // text message system + client_textmessage_t *(*pfnTextMessageGet)( const char *pName ); + int (*pfnDrawCharacter)( int x, int y, int number, int r, int g, int b ); + int (*pfnDrawConsoleString)( int x, int y, char *string ); + void (*pfnDrawSetTextColor)( float r, float g, float b ); + void (*pfnDrawConsoleStringLen)( const char *string, int *length, int *height ); + + void (*pfnConsolePrint)( const char *string ); + void (*pfnCenterPrint)( const char *string ); + + // Added for user input processing + int (*GetWindowCenterX)( void ); + int (*GetWindowCenterY)( void ); + void (*GetViewAngles)( float * ); + void (*SetViewAngles)( float * ); + int (*GetMaxClients)( void ); + void (*Cvar_SetValue)( char *cvar, float value ); + + int (*Cmd_Argc)( void ); + char *(*Cmd_Argv)( int arg ); + void (*Con_Printf)( char *fmt, ... ); + void (*Con_DPrintf)( char *fmt, ... ); + void (*Con_NPrintf)( int pos, char *fmt, ... ); + void (*Con_NXPrintf)( struct con_nprint_s *info, char *fmt, ... ); + + const char* (*PhysInfo_ValueForKey)( const char *key ); + const char* (*ServerInfo_ValueForKey)( const char *key ); + float (*GetClientMaxspeed)( void ); + int (*CheckParm)( char *parm, char **ppnext ); + + void (*Key_Event)( int key, int down ); + void (*GetMousePosition)( int *mx, int *my ); + int (*IsNoClipping)( void ); + + struct cl_entity_s *(*GetLocalPlayer)( void ); + struct cl_entity_s *(*GetViewModel)( void ); + struct cl_entity_s *(*GetEntityByIndex)( int idx ); + + float (*GetClientTime)( void ); + void (*V_CalcShake)( void ); + void (*V_ApplyShake)( float *origin, float *angles, float factor ); + + int (*PM_PointContents)( float *point, int *truecontents ); + int (*PM_WaterEntity)( float *p ); + struct pmtrace_s *(*PM_TraceLine)( float *start, float *end, int flags, int usehull, int ignore_pe ); + + struct model_s *(*CL_LoadModel)( const char *modelname, int *index ); + int (*CL_CreateVisibleEntity)( int type, struct cl_entity_s *ent ); + + const struct model_s* (*GetSpritePointer)( HSPRITE hSprite ); + void (*pfnPlaySoundByNameAtLocation)( char *szSound, float volume, float *origin ); + + unsigned short (*pfnPrecacheEvent)( int type, const char* psz ); + void (*pfnPlaybackEvent)( int flags, const struct edict_s *pInvoker, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); + void (*pfnWeaponAnim)( int iAnim, int body ); + float (*pfnRandomFloat)( float flLow, float flHigh ); + long (*pfnRandomLong)( long lLow, long lHigh ); + void (*pfnHookEvent)( char *name, void ( *pfnEvent )( struct event_args_s *args )); + int (*Con_IsVisible) (); + const char *(*pfnGetGameDirectory)( void ); + struct cvar_s *(*pfnGetCvarPointer)( const char *szName ); + const char *(*Key_LookupBinding)( const char *pBinding ); + const char *(*pfnGetLevelName)( void ); + void (*pfnGetScreenFade)( struct screenfade_s *fade ); + void (*pfnSetScreenFade)( struct screenfade_s *fade ); + void* (*VGui_GetPanel)( ); + void (*VGui_ViewportPaintBackground)( int extents[4] ); + + byte* (*COM_LoadFile)( char *path, int usehunk, int *pLength ); + char* (*COM_ParseFile)( char *data, char *token ); + void (*COM_FreeFile)( void *buffer ); + + struct triangleapi_s *pTriAPI; + struct efx_api_s *pEfxAPI; + struct event_api_s *pEventAPI; + struct demo_api_s *pDemoAPI; + struct net_api_s *pNetAPI; + struct IVoiceTweak_s *pVoiceTweak; + + // returns 1 if the client is a spectator only (connected to a proxy), 0 otherwise or 2 if in dev_overview mode + int (*IsSpectateOnly)( void ); + struct model_s *(*LoadMapSprite)( const char *filename ); + + // file search functions + void (*COM_AddAppDirectoryToSearchPath)( const char *pszBaseDir, const char *appName ); + int (*COM_ExpandFilename)( const char *fileName, char *nameOutBuffer, int nameOutBufferSize ); + + // User info + // playerNum is in the range (1, MaxClients) + // returns NULL if player doesn't exit + // returns "" if no value is set + const char *( *PlayerInfo_ValueForKey )( int playerNum, const char *key ); + void (*PlayerInfo_SetValueForKey )( const char *key, const char *value ); + + // Gets a unique ID for the specified player. This is the same even if you see the player on a different server. + // iPlayer is an entity index, so client 0 would use iPlayer=1. + // Returns false if there is no player on the server in the specified slot. + qboolean (*GetPlayerUniqueID)(int iPlayer, char playerID[16]); + + // TrackerID access + int (*GetTrackerIDForPlayer)(int playerSlot); + int (*GetPlayerForTrackerID)(int trackerID); + + // Same as pfnServerCmd, but the message goes in the unreliable stream so it can't clog the net stream + // (but it might not get there). + int ( *pfnServerCmdUnreliable )( char *szCmdString ); + + void (*pfnGetMousePos)( struct tagPOINT *ppt ); + void (*pfnSetMousePos)( int x, int y ); + void (*pfnSetMouseEnable)( qboolean fEnable ); + void (*pfnUnused1)( void ); + void (*pfnUnused2)( void ); + void (*pfnUnused3)( void ); + void (*pfnUnused4)( void ); + float (*GetClientOldTime)( void ); + float (*pfnGetGravity)( void ); + struct model_s*(*pfnGetModelByIndex)( int index ); + void (*pfnUnused5)( void ); + void (*pfnUnused6)( void ); + void (*pfnUnused7)( void ); + void (*pfnUnused8)( void ); + void (*pfnUnused9)( void ); + void (*pfnUnused10)( void ); + void (*pfnUnused11)( void ); + void (*pfnUnused12)( void ); + const char*(*LocalPlayerInfo_ValueForKey)( const char* key ); + void (*pfnUnused13)( void ); + void (*pfnUnused14)( void ); + void (*pfnUnused15)( void ); + void (*pfnUnused16)( void ); + void (*Cvar_Set)( char *name, char *value ); + void (*pfnUnused17)( void ); + void (*pfnUnused18)( void ); + void (*pfnUnused19)( void ); + double (*pfnSys_FloatTime)( void ); + void (*pfnUnused20)( void ); + void (*pfnUnused21)( void ); + void (*pfnUnused22)( void ); + void (*pfnUnused23)( void ); + void (*pfnFillRGBABlend)( int x, int y, int width, int height, int r, int g, int b, int a ); + int (*pfnGetAppID)( void ); + void (*pfnUnused24)( void ); + void (*pfnUnused25)( void ); +} cl_enginefunc_t; + +#define CLDLL_INTERFACE_VERSION 7 + +#ifdef __cplusplus +} +#endif + +#endif//CDLL_INT_H \ No newline at end of file diff --git a/engine/custom.h b/engine/custom.h new file mode 100644 index 0000000..a5a8da9 --- /dev/null +++ b/engine/custom.h @@ -0,0 +1,101 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Customization.h + +#ifndef CUSTOM_H +#define CUSTOM_H +#ifdef _WIN32 +#pragma once +#endif + +#include "const.h" + +#define MAX_QPATH 64 // Must match value in quakedefs.h + +///////////////// +// Customization +// passed to pfnPlayerCustomization +// For automatic downloading. +typedef enum +{ + t_sound = 0, + t_skin, + t_model, + t_decal, + t_generic, + t_eventscript, + t_world, // Fake type for world, is really t_model +} resourcetype_t; + + +typedef struct +{ + int size; +} _resourceinfo_t; + +typedef struct resourceinfo_s +{ + _resourceinfo_t info[ 8 ]; +} resourceinfo_t; + +#define RES_FATALIFMISSING (1<<0) // Disconnect if we can't get this file. +#define RES_WASMISSING (1<<1) // Do we have the file locally, did we get it ok? +#define RES_CUSTOM (1<<2) // Is this resource one that corresponds to another player's customization + // or is it a server startup resource. +#define RES_REQUESTED (1<<3) // Already requested a download of this one +#define RES_PRECACHED (1<<4) // Already precached + +#include "crc.h" + +typedef struct resource_s +{ + char szFileName[MAX_QPATH]; // File name to download/precache. + resourcetype_t type; // t_sound, t_skin, t_model, t_decal. + int nIndex; // For t_decals + int nDownloadSize; // Size in Bytes if this must be downloaded. + unsigned char ucFlags; + +// For handling client to client resource propagation + unsigned char rgucMD5_hash[16]; // To determine if we already have it. + unsigned char playernum; // Which player index this resource is associated with, if it's a custom resource. + + unsigned char rguc_reserved[ 32 ]; // For future expansion + struct resource_s *pNext; // Next in chain. + struct resource_s *pPrev; +} resource_t; + +typedef struct customization_s +{ + qboolean bInUse; // Is this customization in use; + resource_t resource; // The resource_t for this customization + qboolean bTranslated; // Has the raw data been translated into a useable format? + // (e.g., raw decal .wad make into texture_t *) + int nUserData1; // Customization specific data + int nUserData2; // Customization specific data + void *pInfo; // Buffer that holds the data structure that references the data (e.g., the cachewad_t) + void *pBuffer; // Buffer that holds the data for the customization (the raw .wad data) + struct customization_s *pNext; // Next in chain +} customization_t; + +#define FCUST_FROMHPAK ( 1<<0 ) +#define FCUST_WIPEDATA ( 1<<1 ) +#define FCUST_IGNOREINIT ( 1<<2 ) + +void COM_ClearCustomizationList( struct customization_s *pHead, qboolean bCleanDecals); +qboolean COM_CreateCustomization( struct customization_s *pListHead, struct resource_s *pResource, int playernumber, int flags, + struct customization_s **pCustomization, int *nLumps ); +int COM_SizeofResourceList ( struct resource_s *pList, struct resourceinfo_s *ri ); + +#endif // CUSTOM_H diff --git a/engine/customentity.h b/engine/customentity.h new file mode 100644 index 0000000..2c4eb5e --- /dev/null +++ b/engine/customentity.h @@ -0,0 +1,38 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef CUSTOMENTITY_H +#define CUSTOMENTITY_H + +// Custom Entities + +// Start/End Entity is encoded as 12 bits of entity index, and 4 bits of attachment (4:12) +#define BEAMENT_ENTITY(x) ((x)&0xFFF) +#define BEAMENT_ATTACHMENT(x) (((x)>>12)&0xF) + +// Beam types, encoded as a byte +enum +{ + BEAM_POINTS = 0, + BEAM_ENTPOINT, + BEAM_ENTS, + BEAM_HOSE, +}; + +#define BEAM_FSINE 0x10 +#define BEAM_FSOLID 0x20 +#define BEAM_FSHADEIN 0x40 +#define BEAM_FSHADEOUT 0x80 + +#endif //CUSTOMENTITY_H diff --git a/engine/edict.h b/engine/edict.h new file mode 100644 index 0000000..6bb109d --- /dev/null +++ b/engine/edict.h @@ -0,0 +1,36 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#if !defined EDICT_H +#define EDICT_H +#ifdef _WIN32 +#pragma once +#endif +#define MAX_ENT_LEAFS 48 + +#include "progdefs.h" + +struct edict_s +{ + qboolean free; + int serialnumber; + link_t area; // linked to a division node or leaf + + int headnode; // -1 to use normal leaf check + int num_leafs; + short leafnums[MAX_ENT_LEAFS]; + + float freetime; // sv.time when the object was freed + + void* pvPrivateData; // Alloced and freed by engine, used by DLLs + + entvars_t v; // C exported fields from progs + + // other fields from progs come immediately after +}; + +#endif diff --git a/engine/eiface.h b/engine/eiface.h new file mode 100644 index 0000000..46fec0c --- /dev/null +++ b/engine/eiface.h @@ -0,0 +1,478 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef EIFACE_H +#define EIFACE_H + +#ifdef HLDEMO_BUILD +#define INTERFACE_VERSION 001 +#else // !HLDEMO_BUILD, i.e., regular version of HL +#define INTERFACE_VERSION 140 +#endif // !HLDEMO_BUILD + +#include +#include "custom.h" +#include "cvardef.h" +// +// Defines entity interface between engine and DLLs. +// This header file included by engine files and DLL files. +// +// Before including this header, DLLs must: +// include progdefs.h +// This is conveniently done for them in extdll.h +// + +#ifdef _WIN32 +#define DLLEXPORT __stdcall +#else +#define DLLEXPORT /* */ +#endif + +#include + +#define at_debug at_console + +// 4-22-98 JOHN: added for use in pfnClientPrintf +typedef enum +{ + print_console, + print_center, + print_chat, +} PRINT_TYPE; + +// For integrity checking of content on clients +typedef enum +{ + force_exactfile, // File on client must exactly match server's file + force_model_samebounds, // For model files only, the geometry must fit in the same bbox + force_model_specifybounds, // For model files only, the geometry must fit in the specified bbox +} FORCE_TYPE; + +// Returned by TraceLine +typedef struct +{ + int fAllSolid; // if true, plane is not valid + int fStartSolid; // if true, the initial point was in a solid area + int fInOpen; + int fInWater; + float flFraction; // time completed, 1.0 = didn't hit anything + vec3_t vecEndPos; // final position + float flPlaneDist; + vec3_t vecPlaneNormal; // surface normal at impact + edict_t *pHit; // entity the surface is on + + union + { + int iHitgroup;// 0 == generic, non zero is specific body part + struct mstudiomat_s *pMat; // env_static hitting surface + }; +} TraceResult; + +// CD audio status +typedef struct +{ + int fPlaying;// is sound playing right now? + int fWasPlaying;// if not, CD is paused if WasPlaying is true. + int fInitialized; + int fEnabled; + int fPlayLooping; + float cdvolume; + int fCDRom; + int fPlayTrack; +} CDStatus; + +typedef unsigned long CRC32_t; + +// Engine hands this to DLLs for functionality callbacks +typedef struct enginefuncs_s +{ + int (*pfnPrecacheModel)( const char* s ); + int (*pfnPrecacheSound)( const char* s ); + void (*pfnSetModel)( edict_t *e, const char *m ); + int (*pfnModelIndex)( const char *m ); + int (*pfnModelFrames)( int modelIndex ); + void (*pfnSetSize)( edict_t *e, const float *rgflMin, const float *rgflMax ); + void (*pfnChangeLevel)( char* s1, char* s2 ); + void (*pfnGetSpawnParms)( edict_t *ent ); + void (*pfnSaveSpawnParms)( edict_t *ent ); + float (*pfnVecToYaw)( const float *rgflVector ); + void (*pfnVecToAngles)( const float *rgflVectorIn, float *rgflVectorOut ); + void (*pfnMoveToOrigin)( edict_t *ent, const float *pflGoal, float dist, int iMoveType ); + void (*pfnChangeYaw)( edict_t* ent ); + void (*pfnChangePitch)( edict_t* ent ); + edict_t* (*pfnFindEntityByString)( edict_t *pEdictStartSearchAfter, const char *pszField, const char *pszValue ); + int (*pfnGetEntityIllum)( edict_t* pEnt ); + edict_t* (*pfnFindEntityInSphere)( edict_t *pEdictStartSearchAfter, const float *org, float rad ); + edict_t* (*pfnFindClientInPVS)( edict_t *pEdict ); + edict_t* (*pfnEntitiesInPVS)( edict_t *pplayer ); + void (*pfnMakeVectors)( const float *rgflVector ); + void (*pfnAngleVectors)( const float *rgflVector, float *forward, float *right, float *up ); + edict_t* (*pfnCreateEntity)( void ); + void (*pfnRemoveEntity)( edict_t* e ); + edict_t* (*pfnCreateNamedEntity)( int className ); + void (*pfnMakeStatic)( edict_t *ent ); + int (*pfnEntIsOnFloor)( edict_t *e ); + int (*pfnDropToFloor)( edict_t* e ); + int (*pfnWalkMove)( edict_t *ent, float yaw, float dist, int iMode ); + void (*pfnSetOrigin)( edict_t *e, const float *rgflOrigin ); + void (*pfnEmitSound)( edict_t *entity, int channel, const char *sample, /*int*/float volume, float attenuation, int fFlags, int pitch ); + void (*pfnEmitAmbientSound)( edict_t *entity, float *pos, const char *samp, float vol, float attenuation, int fFlags, int pitch ); + void (*pfnTraceLine)( const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr ); + void (*pfnTraceToss)( edict_t* pent, edict_t* pentToIgnore, TraceResult *ptr ); + int (*pfnTraceMonsterHull)( edict_t *pEdict, const float *v1, const float *v2, int fNoMonsters, edict_t *pentToSkip, TraceResult *ptr ); + void (*pfnTraceHull)( const float *v1, const float *v2, int fNoMonsters, int hullNumber, edict_t *pentToSkip, TraceResult *ptr ); + void (*pfnTraceModel)( const float *v1, const float *v2, int hullNumber, edict_t *pent, TraceResult *ptr ); + const char *(*pfnTraceTexture)( edict_t *pTextureEntity, const float *v1, const float *v2 ); + void (*pfnTraceSphere)( const float *v1, const float *v2, int fNoMonsters, float radius, edict_t *pentToSkip, TraceResult *ptr ); + void (*pfnGetAimVector)( edict_t* ent, float speed, float *rgflReturn ); + void (*pfnServerCommand)( char* str ); + void (*pfnServerExecute)( void ); + void (*pfnClientCommand)( edict_t* pEdict, char* szFmt, ... ); + void (*pfnParticleEffect)( const float *org, const float *dir, float color, float count ); + void (*pfnLightStyle)( int style, char* val ); + int (*pfnDecalIndex)( const char *name ); + int (*pfnPointContents)( const float *rgflVector ); + void (*pfnMessageBegin)( int msg_dest, int msg_type, const float *pOrigin, edict_t *ed ); + void (*pfnMessageEnd)( void ); + void (*pfnWriteByte)( int iValue ); + void (*pfnWriteChar)( int iValue ); + void (*pfnWriteShort)( int iValue ); + void (*pfnWriteLong)( int iValue ); + void (*pfnWriteAngle)( float flValue ); + void (*pfnWriteCoord)( float flValue ); + void (*pfnWriteString)( const char *sz ); + void (*pfnWriteEntity)( int iValue ); + void (*pfnCVarRegister)( cvar_t *pCvar ); + float (*pfnCVarGetFloat)( const char *szVarName ); + const char* (*pfnCVarGetString)( const char *szVarName ); + void (*pfnCVarSetFloat)( const char *szVarName, float flValue ); + void (*pfnCVarSetString)( const char *szVarName, const char *szValue ); + void (*pfnAlertMessage)( ALERT_TYPE atype, char *szFmt, ... ); + void (*pfnEngineFprintf)( FILE *pfile, char *szFmt, ... ); + void* (*pfnPvAllocEntPrivateData)( edict_t *pEdict, long cb ); + void* (*pfnPvEntPrivateData)( edict_t *pEdict ); + void (*pfnFreeEntPrivateData)( edict_t *pEdict ); + const char *(*pfnSzFromIndex)( int iString ); + int (*pfnAllocString)( const char *szValue ); + struct entvars_s *(*pfnGetVarsOfEnt)( edict_t *pEdict ); + edict_t* (*pfnPEntityOfEntOffset)( int iEntOffset ); + int (*pfnEntOffsetOfPEntity)( const edict_t *pEdict ); + int (*pfnIndexOfEdict)( const edict_t *pEdict ); + edict_t* (*pfnPEntityOfEntIndex)( int iEntIndex ); + edict_t* (*pfnFindEntityByVars)( struct entvars_s* pvars ); + void* (*pfnGetModelPtr)( edict_t* pEdict ); + int (*pfnRegUserMsg)( const char *pszName, int iSize ); + void (*pfnAnimationAutomove)( const edict_t* pEdict, float flTime ); + void (*pfnGetBonePosition)( const edict_t* pEdict, int iBone, float *rgflOrigin, float *rgflAngles ); + unsigned long (*pfnFunctionFromName)( const char *pName ); + const char *(*pfnNameForFunction)( unsigned long function ); + void (*pfnClientPrintf)( edict_t* pEdict, PRINT_TYPE ptype, const char *szMsg ); // JOHN: engine callbacks so game DLL can print messages to individual clients + void (*pfnServerPrint)( const char *szMsg ); + const char *(*pfnCmd_Args)( void ); // these 3 added + const char *(*pfnCmd_Argv)( int argc ); // so game DLL can easily + int (*pfnCmd_Argc)( void ); // access client 'cmd' strings + void (*pfnGetAttachment)( const edict_t *pEdict, int iAttachment, float *rgflOrigin, float *rgflAngles ); + void (*pfnCRC32_Init)( CRC32_t *pulCRC ); + void (*pfnCRC32_ProcessBuffer)( CRC32_t *pulCRC, void *p, int len ); + void (*pfnCRC32_ProcessByte)( CRC32_t *pulCRC, unsigned char ch ); + CRC32_t (*pfnCRC32_Final)( CRC32_t pulCRC ); + long (*pfnRandomLong)( long lLow, long lHigh ); + float (*pfnRandomFloat)( float flLow, float flHigh ); + void (*pfnSetView)( const edict_t *pClient, const edict_t *pViewent ); + float (*pfnTime)( void ); + void (*pfnCrosshairAngle)( const edict_t *pClient, float pitch, float yaw ); + byte* (*pfnLoadFileForMe)( const char *filename, int *pLength ); + void (*pfnFreeFile)( void *buffer ); + void (*pfnEndSection)( const char *pszSectionName ); // trigger_endsection + int (*pfnCompareFileTime)( char *filename1, char *filename2, int *iCompare ); + void (*pfnGetGameDir)( char *szGetGameDir ); + void (*pfnCvar_RegisterVariable)( cvar_t *variable ); + void (*pfnFadeClientVolume)( const edict_t *pEdict, int fadePercent, int fadeOutSeconds, int holdTime, int fadeInSeconds ); + void (*pfnSetClientMaxspeed)( const edict_t *pEdict, float fNewMaxspeed ); + edict_t *(*pfnCreateFakeClient)( const char *netname ); // returns NULL if fake client can't be created + void (*pfnRunPlayerMove)( edict_t *fakeclient, const float *viewangles, float forwardmove, float sidemove, float upmove, unsigned short buttons, byte impulse, byte msec ); + int (*pfnNumberOfEntities)( void ); + char* (*pfnGetInfoKeyBuffer)( edict_t *e ); // passing in NULL gets the serverinfo + char* (*pfnInfoKeyValue)( char *infobuffer, char *key ); + void (*pfnSetKeyValue)( char *infobuffer, char *key, char *value ); + void (*pfnSetClientKeyValue)( int clientIndex, char *infobuffer, char *key, char *value ); + int (*pfnIsMapValid)( char *filename ); + void (*pfnStaticDecal)( const float *origin, int decalIndex, int entityIndex, int modelIndex ); + int (*pfnPrecacheGeneric)( const char *s ); + int (*pfnGetPlayerUserId)( edict_t *e ); // returns the server assigned userid for this player. useful for logging frags, etc. returns -1 if the edict couldn't be found in the list of clients + void (*pfnBuildSoundMsg)( edict_t *entity, int channel, const char *sample, /*int*/float volume, float attenuation, int fFlags, int pitch, int msg_dest, int msg_type, const float *pOrigin, edict_t *ed ); + int (*pfnIsDedicatedServer)( void ); // is this a dedicated server? + cvar_t *(*pfnCVarGetPointer)( const char *szVarName ); + unsigned int (*pfnGetPlayerWONId)( edict_t *e ); // returns the server assigned WONid for this player. useful for logging frags, etc. returns -1 if the edict couldn't be found in the list of clients + + // YWB 8/1/99 TFF Physics additions + void (*pfnInfo_RemoveKey)( char *s, const char *key ); + const char *(*pfnGetPhysicsKeyValue)( const edict_t *pClient, const char *key ); + void (*pfnSetPhysicsKeyValue)( const edict_t *pClient, const char *key, const char *value ); + const char *(*pfnGetPhysicsInfoString)( const edict_t *pClient ); + unsigned short (*pfnPrecacheEvent)( int type, const char*psz ); + void (*pfnPlaybackEvent)( int flags, const edict_t *pInvoker, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); + + unsigned char *(*pfnSetFatPVS)( float *org ); + unsigned char *(*pfnSetFatPAS)( float *org ); + + int (*pfnCheckVisibility )( const edict_t *entity, unsigned char *pset ); + + void (*pfnDeltaSetField) ( struct delta_s *pFields, const char *fieldname ); + void (*pfnDeltaUnsetField)( struct delta_s *pFields, const char *fieldname ); + void (*pfnDeltaAddEncoder)( char *name, void (*conditionalencode)( struct delta_s *pFields, const unsigned char *from, const unsigned char *to ) ); + int (*pfnGetCurrentPlayer)( void ); + int (*pfnCanSkipPlayer)( const edict_t *player ); + int (*pfnDeltaFindField)( struct delta_s *pFields, const char *fieldname ); + void (*pfnDeltaSetFieldByIndex)( struct delta_s *pFields, int fieldNumber ); + void (*pfnDeltaUnsetFieldByIndex)( struct delta_s *pFields, int fieldNumber ); + void (*pfnSetGroupMask)( int mask, int op ); + int (*pfnCreateInstancedBaseline)( int classname, struct entity_state_s *baseline ); + void (*pfnCvar_DirectSet)( struct cvar_s *var, char *value ); + + // Forces the client and server to be running with the same version of the specified file + // ( e.g., a player model ). + // Calling this has no effect in single player + void (*pfnForceUnmodified)( FORCE_TYPE type, float *mins, float *maxs, const char *filename ); + + void (*pfnGetPlayerStats)( const edict_t *pClient, int *ping, int *packet_loss ); + + void (*pfnAddServerCommand)( char *cmd_name, void (*function) (void) ); + + // For voice communications, set which clients hear eachother. + // NOTE: these functions take player entity indices (starting at 1). + qboolean (*pfnVoice_GetClientListening)(int iReceiver, int iSender); + qboolean (*pfnVoice_SetClientListening)(int iReceiver, int iSender, qboolean bListen); + + const char *(*pfnGetPlayerAuthId) ( edict_t *e ); +} enginefuncs_t; +// ONLY ADD NEW FUNCTIONS TO THE END OF THIS STRUCT. INTERFACE VERSION IS FROZEN AT 138 + +// Passed to pfnKeyValue +typedef struct KeyValueData_s +{ + char *szClassName; // in: entity classname + char *szKeyName; // in: name of key + char *szValue; // in: value of key + long fHandled; // out: DLL sets to true if key-value pair was understood +} KeyValueData; + + +typedef struct +{ + char mapName[32]; + char landmarkName[32]; + edict_t *pentLandmark; + vec3_t vecLandmarkOrigin; +} LEVELLIST; + +typedef struct +{ + int id; // Ordinal ID of this entity (used for entity <--> pointer conversions) + edict_t *pent; // Pointer to the in-game entity + + int location; // Offset from the base data of this entity + int size; // Byte size of this entity's data + int flags; // This could be a short -- bit mask of transitions that this entity is in the PVS of + string_t classname; // entity class name + +} ENTITYTABLE; + +#define MAX_LEVEL_CONNECTIONS 16 // These are encoded in the lower 16bits of ENTITYTABLE->flags + +#define FENTTABLE_PLAYER 0x80000000 +#define FENTTABLE_REMOVED 0x40000000 +#define FENTTABLE_MOVEABLE 0x20000000 +#define FENTTABLE_GLOBAL 0x10000000 + +typedef struct saverestore_s +{ + char *pBaseData; // Start of all entity save data + char *pCurrentData; // Current buffer pointer for sequential access + int size; // Current data size + int bufferSize; // Total space for data + int tokenSize; // Size of the linear list of tokens + int tokenCount; // Number of elements in the pTokens table + char **pTokens; // Hash table of entity strings (sparse) + int currentIndex; // Holds a global entity table ID + int tableCount; // Number of elements in the entity table + int connectionCount; // Number of elements in the levelList[] + ENTITYTABLE *pTable; // Array of ENTITYTABLE elements (1 for each entity) + LEVELLIST levelList[MAX_LEVEL_CONNECTIONS]; // List of connections from this level + + // smooth transition + int fUseLandmark; + char szLandmarkName[20]; // landmark we'll spawn near in next level + vec3_t vecLandmarkOffset; // for landmark transitions + float time; + char szCurrentMapName[32]; // To check global entities +} SAVERESTOREDATA; + +typedef enum _fieldtypes +{ + FIELD_FLOAT = 0, // Any floating point value + FIELD_STRING, // A string ID (return from ALLOC_STRING) + FIELD_ENTITY, // An entity offset (EOFFSET) + FIELD_CLASSPTR, // CBaseEntity * + FIELD_EHANDLE, // Entity handle + FIELD_EVARS, // EVARS * + FIELD_EDICT, // edict_t *, or edict_t * (same thing) + FIELD_VECTOR, // Any vector + FIELD_POSITION_VECTOR, // A world coordinate (these are fixed up across level transitions automagically) + FIELD_POINTER, // Arbitrary data pointer... to be removed, use an array of FIELD_CHARACTER + FIELD_INTEGER, // Any integer or enum + FIELD_FUNCTION, // A class function pointer (Think, Use, etc) + FIELD_BOOLEAN, // boolean, implemented as an int, I may use this as a hint for compression + FIELD_SHORT, // 2 byte integer + FIELD_CHARACTER, // a byte + FIELD_TIME, // a floating point time (these are fixed up automatically too!) + FIELD_MODELNAME, // Engine string that is a model name (needs precache) + FIELD_SOUNDNAME, // Engine string that is a sound name (needs precache) + FIELD_RANGE, // Min and Max range for generate random value + + FIELD_TYPECOUNT, // MUST BE LAST +} FIELDTYPE; + +#ifndef offsetof +#define offsetof(s,m) (size_t)&(((s *)0)->m) +#endif + +#define _FIELD(type,name,fieldtype,count,flags) { fieldtype, #name, offsetof(type, name), count, flags } +#define DEFINE_FIELD(type,name,fieldtype) _FIELD(type, name, fieldtype, 1, 0) +#define DEFINE_ARRAY(type,name,fieldtype,count) _FIELD(type, name, fieldtype, count, 0) +#define DEFINE_ENTITY_FIELD(name,fieldtype) _FIELD(entvars_t, name, fieldtype, 1, 0 ) +#define DEFINE_ENTITY_GLOBAL_FIELD(name,fieldtype) _FIELD(entvars_t, name, fieldtype, 1, FTYPEDESC_GLOBAL ) +#define DEFINE_GLOBAL_FIELD(type,name,fieldtype) _FIELD(type, name, fieldtype, 1, FTYPEDESC_GLOBAL ) + +#define FTYPEDESC_GLOBAL 0x0001 // This field is masked for global entity save/restore +#define FTYPEDESC_SAVE 0x0002 // This field is saved to disk +#define FTYPEDESC_KEY 0x0004 // This field can be requested and written to by string name at load time +#define FTYPEDESC_FUNCTIONTABLE 0x0008 // This is a table entry for a member function pointer + +typedef struct +{ + FIELDTYPE fieldType; + char *fieldName; + int fieldOffset; + short fieldSize; + short flags; +} TYPEDESCRIPTION; + +typedef struct +{ + // Initialize/shutdown the game (one-time call after loading of game .dll ) + void (*pfnGameInit)( void ); + int (*pfnSpawn)( edict_t *pent ); + void (*pfnThink)( edict_t *pent ); + void (*pfnUse)( edict_t *pentUsed, edict_t *pentOther ); + void (*pfnTouch)( edict_t *pentTouched, edict_t *pentOther ); + void (*pfnBlocked)( edict_t *pentBlocked, edict_t *pentOther ); + void (*pfnKeyValue)( edict_t *pentKeyvalue, KeyValueData *pkvd ); + void (*pfnSave)( edict_t *pent, SAVERESTOREDATA *pSaveData ); + int (*pfnRestore)( edict_t *pent, SAVERESTOREDATA *pSaveData, int globalEntity ); + void (*pfnSetAbsBox)( edict_t *pent ); + + void (*pfnSaveWriteFields)( SAVERESTOREDATA*, const char*, void*, TYPEDESCRIPTION*, int ); + void (*pfnSaveReadFields)( SAVERESTOREDATA*, const char*, void*, TYPEDESCRIPTION*, int ); + void (*pfnSaveGlobalState)( SAVERESTOREDATA * ); + void (*pfnRestoreGlobalState)( SAVERESTOREDATA * ); + void (*pfnResetGlobalState)( void ); + + qboolean (*pfnClientConnect)( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[128] ); + + void (*pfnClientDisconnect)( edict_t *pEntity ); + void (*pfnClientKill)( edict_t *pEntity ); + void (*pfnClientPutInServer)( edict_t *pEntity ); + void (*pfnClientCommand)( edict_t *pEntity ); + void (*pfnClientUserInfoChanged)( edict_t *pEntity, char *infobuffer ); + void (*pfnServerActivate)( edict_t *pEdictList, int edictCount, int clientMax ); + void (*pfnServerDeactivate)( void ); + void (*pfnPlayerPreThink)( edict_t *pEntity ); + void (*pfnPlayerPostThink)( edict_t *pEntity ); + + void (*pfnStartFrame)( void ); + void (*pfnParmsNewLevel)( void ); + void (*pfnParmsChangeLevel)( void ); + + // Returns string describing current .dll. E.g., TeamFotrress 2, Half-Life + const char *(*pfnGetGameDescription)( void ); + + // Notify dll about a player customization. + void (*pfnPlayerCustomization)( edict_t *pEntity, customization_t *pCustom ); + + // Spectator funcs + void (*pfnSpectatorConnect)( edict_t *pEntity ); + void (*pfnSpectatorDisconnect)( edict_t *pEntity ); + void (*pfnSpectatorThink)( edict_t *pEntity ); + + // Notify game .dll that engine is going to shut down. Allows mod authors to set a breakpoint. + void (*pfnSys_Error)( const char *error_string ); + + void (*pfnPM_Move)( struct playermove_s *ppmove, qboolean server ); + void (*pfnPM_Init)( struct playermove_s *ppmove ); + char (*pfnPM_FindTextureType)( char *name ); + void (*pfnSetupVisibility)( struct edict_s *pViewEntity, struct edict_s *pClient, unsigned char **pvs, unsigned char **pas ); + void (*pfnUpdateClientData) ( const struct edict_s *ent, int sendweapons, struct clientdata_s *cd ); + int (*pfnAddToFullPack)( struct entity_state_s *state, int e, edict_t *ent, edict_t *host, int hostflags, int player, unsigned char *pSet ); + void (*pfnCreateBaseline)( int player, int eindex, struct entity_state_s *baseline, struct edict_s *entity, int playermodelindex, vec3_t player_mins, vec3_t player_maxs ); + void (*pfnRegisterEncoders)( void ); + int (*pfnGetWeaponData)( struct edict_s *player, struct weapon_data_s *info ); + + void (*pfnCmdStart)( const edict_t *player, const struct usercmd_s *cmd, unsigned int random_seed ); + void (*pfnCmdEnd)( const edict_t *player ); + + // Return 1 if the packet is valid. Set response_buffer_size if you want to send a response packet. Incoming, it holds the max + // size of the response_buffer, so you must zero it out if you choose not to respond. + int (*pfnConnectionlessPacket )( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ); + + // Enumerates player hulls. Returns 0 if the hull number doesn't exist, 1 otherwise + int (*pfnGetHullBounds) ( int hullnumber, float *mins, float *maxs ); + + // Create baselines for certain "unplaced" items. + void (*pfnCreateInstancedBaselines) ( void ); + + // One of the pfnForceUnmodified files failed the consistency check for the specified player + // Return 0 to allow the client to continue, 1 to force immediate disconnection ( with an optional disconnect message of up to 256 characters ) + int (*pfnInconsistentFile)( const struct edict_s *player, const char *filename, char *disconnect_message ); + + // The game .dll should return 1 if lag compensation should be allowed ( could also just set + // the sv_unlag cvar. + // Most games right now should return 0, until client-side weapon prediction code is written + // and tested for them. + int (*pfnAllowLagCompensation)( void ); +} DLL_FUNCTIONS; + +extern DLL_FUNCTIONS gEntityInterface; + +// Current version. +#define NEW_DLL_FUNCTIONS_VERSION 1 + +typedef struct +{ + // Called right before the object's memory is freed. + // Calls its destructor. + void (*pfnOnFreeEntPrivateData)( edict_t *pEnt ); + void (*pfnGameShutdown)(void); + int (*pfnShouldCollide)( edict_t *pentTouched, edict_t *pentOther ); +} NEW_DLL_FUNCTIONS; +typedef int (*NEW_DLL_FUNCTIONS_FN)( NEW_DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ); + +// Pointers will be null if the game DLL doesn't support this API. +extern NEW_DLL_FUNCTIONS gNewDLLFunctions; + +typedef int (*APIFUNCTION)( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion ); +typedef int (*APIFUNCTION2)( DLL_FUNCTIONS *pFunctionTable, int *interfaceVersion ); + +#endif//EIFACE_H \ No newline at end of file diff --git a/engine/keydefs.h b/engine/keydefs.h new file mode 100644 index 0000000..aca2c09 --- /dev/null +++ b/engine/keydefs.h @@ -0,0 +1,129 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// keydefs.h +#ifndef KEYDEFS_H +#define KEYDEFS_H +#ifdef _WIN32 +#pragma once +#endif + +// +// these are the key numbers that should be passed to Key_Event +// +#define K_TAB 9 +#define K_ENTER 13 +#define K_ESCAPE 27 +#define K_SPACE 32 + +// normal keys should be passed as lowercased ascii + +#define K_BACKSPACE 127 +#define K_UPARROW 128 +#define K_DOWNARROW 129 +#define K_LEFTARROW 130 +#define K_RIGHTARROW 131 + +#define K_ALT 132 +#define K_CTRL 133 +#define K_SHIFT 134 +#define K_F1 135 +#define K_F2 136 +#define K_F3 137 +#define K_F4 138 +#define K_F5 139 +#define K_F6 140 +#define K_F7 141 +#define K_F8 142 +#define K_F9 143 +#define K_F10 144 +#define K_F11 145 +#define K_F12 146 +#define K_INS 147 +#define K_DEL 148 +#define K_PGDN 149 +#define K_PGUP 150 +#define K_HOME 151 +#define K_END 152 + +#define K_KP_HOME 160 +#define K_KP_UPARROW 161 +#define K_KP_PGUP 162 +#define K_KP_LEFTARROW 163 +#define K_KP_5 164 +#define K_KP_RIGHTARROW 165 +#define K_KP_END 166 +#define K_KP_DOWNARROW 167 +#define K_KP_PGDN 168 +#define K_KP_ENTER 169 +#define K_KP_INS 170 +#define K_KP_DEL 171 +#define K_KP_SLASH 172 +#define K_KP_MINUS 173 +#define K_KP_PLUS 174 +#define K_CAPSLOCK 175 + + +// +// joystick buttons +// +#define K_JOY1 203 +#define K_JOY2 204 +#define K_JOY3 205 +#define K_JOY4 206 + +// +// aux keys are for multi-buttoned joysticks to generate so they can use +// the normal binding process +// +#define K_AUX1 207 +#define K_AUX2 208 +#define K_AUX3 209 +#define K_AUX4 210 +#define K_AUX5 211 +#define K_AUX6 212 +#define K_AUX7 213 +#define K_AUX8 214 +#define K_AUX9 215 +#define K_AUX10 216 +#define K_AUX11 217 +#define K_AUX12 218 +#define K_AUX13 219 +#define K_AUX14 220 +#define K_AUX15 221 +#define K_AUX16 222 +#define K_AUX17 223 +#define K_AUX18 224 +#define K_AUX19 225 +#define K_AUX20 226 +#define K_AUX21 227 +#define K_AUX22 228 +#define K_AUX23 229 +#define K_AUX24 230 +#define K_AUX25 231 +#define K_AUX26 232 +#define K_AUX27 233 +#define K_AUX28 234 +#define K_AUX29 235 +#define K_AUX30 236 +#define K_AUX31 237 +#define K_AUX32 238 +#define K_MWHEELDOWN 239 +#define K_MWHEELUP 240 + +#define K_PAUSE 255 + +// +// mouse buttons generate virtual keys +// +#define K_MOUSE1 241 +#define K_MOUSE2 242 +#define K_MOUSE3 243 +#define K_MOUSE4 244 +#define K_MOUSE5 245 + +#endif // KEYDEFS_H diff --git a/engine/menu_int.h b/engine/menu_int.h new file mode 100644 index 0000000..66a85bc --- /dev/null +++ b/engine/menu_int.h @@ -0,0 +1,189 @@ +/* +menu_int.h - interface between engine and menu +Copyright (C) 2010 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef MENU_INT_H +#define MENU_INT_H + +#include "cvardef.h" +#include "gameinfo.h" +#include "wrect.h" + +typedef int HIMAGE; // handle to a graphic + +// flags for PIC_Load +#define PIC_NEAREST (1<<0) // disable texfilter +#define PIC_KEEP_SOURCE (1<<1) // some images keep source +#define PIC_NOFLIP_TGA (1<<2) // Steam background completely ignore tga attribute 0x20 + +typedef struct ui_globalvars_s +{ + float time; // unclamped host.realtime + float frametime; + + int scrWidth; // actual values + int scrHeight; + + int maxClients; + int developer; + int demoplayback; + int demorecording; + char demoname[64]; // name of currently playing demo + char maptitle[64]; // title of active map +} ui_globalvars_t; + +typedef struct ui_enginefuncs_s +{ + // image handlers + HIMAGE (*pfnPIC_Load)( const char *szPicName, const byte *ucRawImage, long ulRawImageSize, long flags ); + void (*pfnPIC_Free)( const char *szPicName ); + int (*pfnPIC_Width)( HIMAGE hPic ); + int (*pfnPIC_Height)( HIMAGE hPic ); + void (*pfnPIC_Set)( HIMAGE hPic, int r, int g, int b, int a ); + void (*pfnPIC_Draw)( int x, int y, int width, int height, const wrect_t *prc ); + void (*pfnPIC_DrawHoles)( int x, int y, int width, int height, const wrect_t *prc ); + void (*pfnPIC_DrawTrans)( int x, int y, int width, int height, const wrect_t *prc ); + void (*pfnPIC_DrawAdditive)( int x, int y, int width, int height, const wrect_t *prc ); + void (*pfnPIC_EnableScissor)( int x, int y, int width, int height ); + void (*pfnPIC_DisableScissor)( void ); + + // screen handlers + void (*pfnFillRGBA)( int x, int y, int width, int height, int r, int g, int b, int a ); + + // cvar handlers + cvar_t* (*pfnRegisterVariable)( const char *szName, const char *szValue, int flags ); + float (*pfnGetCvarFloat)( const char *szName ); + char* (*pfnGetCvarString)( const char *szName ); + void (*pfnCvarSetString)( const char *szName, const char *szValue ); + void (*pfnCvarSetValue)( const char *szName, float flValue ); + + // command handlers + int (*pfnAddCommand)( const char *cmd_name, void (*function)(void) ); + void (*pfnClientCmd)( int execute_now, const char *szCmdString ); + void (*pfnDelCommand)( const char *cmd_name ); + int (*pfnCmdArgc)( void ); + char* (*pfnCmdArgv)( int argc ); + char* (*pfnCmd_Args)( void ); + + // debug messages (in-menu shows only notify) + void (*Con_Printf)( char *fmt, ... ); + void (*Con_DPrintf)( char *fmt, ... ); + void (*Con_NPrintf)( int pos, char *fmt, ... ); + void (*Con_NXPrintf)( struct con_nprint_s *info, char *fmt, ... ); + + // sound handlers + void (*pfnPlayLocalSound)( const char *szSound ); + + // cinematic handlers + void (*pfnDrawLogo)( const char *filename, float x, float y, float width, float height ); + int (*pfnGetLogoWidth)( void ); + int (*pfnGetLogoHeight)( void ); + float (*pfnGetLogoLength)( void ); // cinematic duration in seconds + + // text message system + void (*pfnDrawCharacter)( int x, int y, int width, int height, int ch, int ulRGBA, HIMAGE hFont ); + int (*pfnDrawConsoleString)( int x, int y, const char *string ); + void (*pfnDrawSetTextColor)( int r, int g, int b, int alpha ); + void (*pfnDrawConsoleStringLen)( const char *string, int *length, int *height ); + void (*pfnSetConsoleDefaultColor)( int r, int g, int b ); // color must came from colors.lst + + // custom rendering (for playermodel preview) + struct cl_entity_s* (*pfnGetPlayerModel)( void ); // for drawing playermodel previews + void (*pfnSetModel)( struct cl_entity_s *ed, const char *path ); + void (*pfnClearScene)( void ); + void (*pfnRenderScene)( const struct ref_viewpass_s *rvp ); + int (*CL_CreateVisibleEntity)( int type, struct cl_entity_s *ent ); + + // misc handlers + void (*pfnHostError)( const char *szFmt, ... ); + int (*pfnFileExists)( const char *filename, int gamedironly ); + void (*pfnGetGameDir)( char *szGetGameDir ); + + // gameinfo handlers + int (*pfnCreateMapsList)( int fRefresh ); + int (*pfnClientInGame)( void ); + void (*pfnClientJoin)( const struct netadr_s adr ); + + // parse txt files + byte* (*COM_LoadFile)( const char *filename, int *pLength ); + char* (*COM_ParseFile)( char *data, char *token ); + void (*COM_FreeFile)( void *buffer ); + + // keyfuncs + void (*pfnKeyClearStates)( void ); // call when menu open or close + void (*pfnSetKeyDest)( int dest ); + const char *(*pfnKeynumToString)( int keynum ); + const char *(*pfnKeyGetBinding)( int keynum ); + void (*pfnKeySetBinding)( int keynum, const char *binding ); + int (*pfnKeyIsDown)( int keynum ); + int (*pfnKeyGetOverstrikeMode)( void ); + void (*pfnKeySetOverstrikeMode)( int fActive ); + void *(*pfnKeyGetState)( const char *name ); // for mlook, klook etc + + // engine memory manager + void* (*pfnMemAlloc)( size_t cb, const char *filename, const int fileline ); + void (*pfnMemFree)( void *mem, const char *filename, const int fileline ); + + // collect info from engine + int (*pfnGetGameInfo)( GAMEINFO *pgameinfo ); + GAMEINFO **(*pfnGetGamesList)( int *numGames ); // collect info about all mods + char **(*pfnGetFilesList)( const char *pattern, int *numFiles, int gamedironly ); // find in files + int (*pfnGetSaveComment)( const char *savename, char *comment ); + int (*pfnGetDemoComment)( const char *demoname, char *comment ); + int (*pfnCheckGameDll)( void ); // returns false if hl.dll is missed or invalid + char *(*pfnGetClipboardData)( void ); + + // engine launcher + void (*pfnShellExecute)( const char *name, const char *args, int closeEngine ); + void (*pfnWriteServerConfig)( const char *name ); + void (*pfnChangeInstance)( const char *newInstance, const char *szFinalMessage ); + void (*pfnPlayBackgroundTrack)( const char *introName, const char *loopName ); + void (*pfnHostEndGame)( const char *szFinalMessage ); + + // menu interface is freezed at version 0.75 + // new functions starts here + float (*pfnRandomFloat)( float flLow, float flHigh ); + long (*pfnRandomLong)( long lLow, long lHigh ); + + void (*pfnSetCursor)( void *hCursor ); // change cursor + int (*pfnIsMapValid)( char *filename ); + void (*pfnProcessImage)( int texnum, float gamma, int topColor, int bottomColor ); + int (*pfnCompareFileTime)( char *filename1, char *filename2, int *iCompare ); + + const char *(*pfnGetModeString)( int vid_mode ); +} ui_enginefuncs_t; + +typedef struct +{ + int (*pfnVidInit)( void ); + void (*pfnInit)( void ); + void (*pfnShutdown)( void ); + void (*pfnRedraw)( float flTime ); + void (*pfnKeyEvent)( int key, int down ); + void (*pfnMouseMove)( int x, int y ); + void (*pfnSetActiveMenu)( int active ); + void (*pfnAddServerToList)( struct netadr_s adr, const char *info ); + void (*pfnGetCursorPos)( int *pos_x, int *pos_y ); + void (*pfnSetCursorPos)( int pos_x, int pos_y ); + void (*pfnShowCursor)( int show ); + void (*pfnCharEvent)( int key ); + int (*pfnMouseInRect)( void ); // mouse entering\leave game window + int (*pfnIsVisible)( void ); + int (*pfnCreditsActive)( void ); // unused + void (*pfnFinalCredits)( void ); // show credits + game end +} UI_FUNCTIONS; + +typedef int (*MENUAPI)( UI_FUNCTIONS *pFunctionTable, ui_enginefuncs_t* engfuncs, ui_globalvars_t *pGlobals ); + +#endif//MENU_INT_H \ No newline at end of file diff --git a/engine/physint.h b/engine/physint.h new file mode 100644 index 0000000..fc4cd5b --- /dev/null +++ b/engine/physint.h @@ -0,0 +1,153 @@ +/* +physint.h - Server Physics Interface +Copyright (C) 2011 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef PHYSINT_H +#define PHYSINT_H + +#define SV_PHYSICS_INTERFACE_VERSION 6 + +#include "areanode.h" + +// values that can be returned with pfnServerState +#define SERVER_DEAD 0 +#define SERVER_LOADING 1 +#define SERVER_ACTIVE 2 + +// LUMP reading errors +#define LUMP_LOAD_OK 0 +#define LUMP_LOAD_COULDNT_OPEN 1 +#define LUMP_LOAD_BAD_HEADER 2 +#define LUMP_LOAD_BAD_VERSION 3 +#define LUMP_LOAD_NO_EXTRADATA 4 +#define LUMP_LOAD_INVALID_NUM 5 +#define LUMP_LOAD_NOT_EXIST 6 +#define LUMP_LOAD_MEM_FAILED 7 +#define LUMP_LOAD_CORRUPTED 8 + +// LUMP saving errors +#define LUMP_SAVE_OK 0 +#define LUMP_SAVE_COULDNT_OPEN 1 +#define LUMP_SAVE_BAD_HEADER 2 +#define LUMP_SAVE_BAD_VERSION 3 +#define LUMP_SAVE_NO_EXTRADATA 4 +#define LUMP_SAVE_INVALID_NUM 5 +#define LUMP_SAVE_ALREADY_EXIST 6 +#define LUMP_SAVE_NO_DATA 7 +#define LUMP_SAVE_CORRUPTED 8 + +typedef struct server_physics_api_s +{ + // unlink edict from old position and link onto new + void ( *pfnLinkEdict) ( edict_t *ent, qboolean touch_triggers ); + double ( *pfnGetServerTime )( void ); // unclamped + double ( *pfnGetFrameTime )( void ); // unclamped + void* ( *pfnGetModel )( int modelindex ); + areanode_t* ( *pfnGetHeadnode )( void ); // AABB tree for all physic entities + int ( *pfnServerState )( void ); + void ( *pfnHost_Error )( const char *error, ... ); // cause Host Error +// ONLY ADD NEW FUNCTIONS TO THE END OF THIS STRUCT. INTERFACE VERSION IS FROZEN AT 6 + struct triangleapi_s *pTriAPI; // draw coliisions etc. Only for local system + + // draw debug messages (must be called from DrawOrthoTriangles). Only for local system + int ( *pfnDrawConsoleString )( int x, int y, char *string ); + void ( *pfnDrawSetTextColor )( float r, float g, float b ); + void ( *pfnDrawConsoleStringLen )( const char *string, int *length, int *height ); + void ( *Con_NPrintf )( int pos, char *fmt, ... ); + void ( *Con_NXPrintf )( struct con_nprint_s *info, char *fmt, ... ); + const char *( *pfnGetLightStyle )( int style ); // read custom appreance for selected lightstyle + void ( *pfnUpdateFogSettings )( unsigned int packed_fog ); + char **(*pfnGetFilesList)( const char *pattern, int *numFiles, int gamedironly ); + struct msurface_s *(*pfnTraceSurface)( edict_t *pTextureEntity, const float *v1, const float *v2 ); + const byte *(*pfnGetTextureData)( unsigned int texnum ); + + // static allocations + void *(*pfnMemAlloc)( size_t cb, const char *filename, const int fileline ); + void (*pfnMemFree)( void *mem, const char *filename, const int fileline ); + + // trace & contents + int (*pfnMaskPointContents)( const float *pos, int groupmask ); + trace_t (*pfnTrace)( const float *p0, float *mins, float *maxs, const float *p1, int type, edict_t *e ); + trace_t (*pfnTraceNoEnts)( const float *p0, float *mins, float *maxs, const float *p1, int type, edict_t *e ); + int (*pfnBoxInPVS)( const float *org, const float *boxmins, const float *boxmaxs ); + + // message handler (missed function to write raw bytes) + void (*pfnWriteBytes)( byte *bytes, int count ); + + // BSP lump management + int (*pfnCheckLump)( const char *filename, const int lump, int *lumpsize ); + int (*pfnReadLump)( const char *filename, const int lump, void **lumpdata, int *lumpsize ); + int (*pfnSaveLump)( const char *filename, const int lump, void *lumpdata, int lumpsize ); + + // FS tools + int (*pfnSaveFile)( const char *filename, const void *data, long len ); + const byte *(*pfnLoadImagePixels)( const char *filename, int *width, int *height ); + + const char* (*pfnGetModelName)( int modelindex ); +} server_physics_api_t; + +// physic callbacks +typedef struct physics_interface_s +{ + int version; + // passed through pfnCreate (0 is attempt to create, -1 is reject) + int ( *SV_CreateEntity )( edict_t *pent, const char *szName ); + // run custom physics for each entity (return 0 to use built-in engine physic) + int ( *SV_PhysicsEntity )( edict_t *pEntity ); + // spawn entities with internal mod function e.g. for re-arrange spawn order (0 - use engine parser, 1 - use mod parser) + int ( *SV_LoadEntities )( const char *mapname, char *entities ); + // update conveyor belt for clients + void ( *SV_UpdatePlayerBaseVelocity )( edict_t *ent ); + // The game .dll should return 1 if save game should be allowed + int ( *SV_AllowSaveGame )( void ); +// ONLY ADD NEW FUNCTIONS TO THE END OF THIS STRUCT. INTERFACE VERSION IS FROZEN AT 6 + // override trigger area checking and touching + int ( *SV_TriggerTouch )( edict_t *pent, edict_t *trigger ); + // some engine features can be enabled only through this function + unsigned int ( *SV_CheckFeatures )( void ); + // used for draw debug collisions for custom physic engine etc + void ( *DrawDebugTriangles )( void ); + // used for draw debug overlay (textured) + void ( *DrawNormalTriangles )( void ); + // used for draw debug messages (2d mode) + void ( *DrawOrthoTriangles )( void ); + // tracing entities with SOLID_CUSTOM mode on a server (not used by pmove code) + void ( *ClipMoveToEntity)( edict_t *ent, const float *start, float *mins, float *maxs, const float *end, trace_t *trace ); + // tracing entities with SOLID_CUSTOM mode on a server (only used by pmove code) + void ( *ClipPMoveToEntity)( struct physent_s *pe, const float *start, float *mins, float *maxs, const float *end, struct pmtrace_s *tr ); + // called at end the frame of SV_Physics call + void ( *SV_EndFrame )( void ); + // obsolete + void (*pfnReserved)( void ); + // called through save\restore process + void (*pfnCreateEntitiesInRestoreList)( SAVERESTOREDATA *pSaveData, int levelMask, qboolean create_world ); + // allocate custom string (e.g. using user implementation of stringtable, not engine strings) + string_t (*pfnAllocString)( const char *szValue ); + // make custom string (e.g. using user implementation of stringtable, not engine strings) + string_t (*pfnMakeString)( const char *szValue ); + // read custom string (e.g. using user implementation of stringtable, not engine strings) + const char* (*pfnGetString)( string_t iString ); + // helper for restore custom decals that have custom message (e.g. Paranoia) + int (*pfnRestoreDecal)( struct decallist_s *entry, edict_t *pEdict, qboolean adjacent ); + // handle custom trigger touching for player + void (*PM_PlayerTouch)( struct playermove_s *ppmove, edict_t *client ); + // alloc or destroy model custom data (called only for dedicated servers, otherwise using an client version) + void (*Mod_ProcessUserData)( struct model_s *mod, qboolean create, const byte *buffer ); + // select BSP-hull for trace with specified mins\maxs + void *(*SV_HullForBsp)( edict_t *ent, const float *mins, const float *maxs, float *offset ); + // handle player custom think function + int (*SV_PlayerThink)( edict_t *ent, float frametime, double time ); +} physics_interface_t; + +#endif//PHYSINT_H \ No newline at end of file diff --git a/engine/progdefs.h b/engine/progdefs.h new file mode 100644 index 0000000..b8c58ad --- /dev/null +++ b/engine/progdefs.h @@ -0,0 +1,224 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PROGDEFS_H +#define PROGDEFS_H +#ifdef _WIN32 +#pragma once +#endif + +typedef struct +{ + float time; + float frametime; + float force_retouch; + string_t mapname; + string_t startspot; + float deathmatch; + float coop; + float teamplay; + float serverflags; + float found_secrets; + vec3_t v_forward; + vec3_t v_up; + vec3_t v_right; + float trace_allsolid; + float trace_startsolid; + float trace_fraction; + vec3_t trace_endpos; + vec3_t trace_plane_normal; + float trace_plane_dist; + edict_t *trace_ent; + float trace_inopen; + float trace_inwater; + int trace_hitgroup; + int trace_flags; + int msg_entity; + int cdAudioTrack; + int maxClients; + int maxEntities; + const char *pStringBase; + + void *pSaveData; + vec3_t vecLandmarkOffset; +} globalvars_t; + + +typedef struct entvars_s +{ + string_t classname; + string_t globalname; + + vec3_t origin; + vec3_t oldorigin; + vec3_t velocity; + vec3_t basevelocity; + vec3_t clbasevelocity; // Base velocity that was passed in to server physics so + // client can predict conveyors correctly. Server zeroes it, so we need to store here, too. + vec3_t movedir; + + vec3_t angles; // Model angles + vec3_t avelocity; // angle velocity (degrees per second) + vec3_t punchangle; // auto-decaying view angle adjustment + vec3_t v_angle; // Viewing angle (player only) + + // For parametric entities + vec3_t endpos; + vec3_t startpos; + float impacttime; + float starttime; + + int fixangle; // 0:nothing, 1:force view angles, 2:add avelocity + float idealpitch; + float pitch_speed; + float ideal_yaw; + float yaw_speed; + + int modelindex; + string_t model; + + int viewmodel; // player's viewmodel + int weaponmodel; // what other players see + + vec3_t absmin; // BB max translated to world coord + vec3_t absmax; // BB max translated to world coord + vec3_t mins; // local BB min + vec3_t maxs; // local BB max + vec3_t size; // maxs - mins + + float ltime; + float nextthink; + + int movetype; + int solid; + + int skin; + int body; // sub-model selection for studiomodels + int effects; + + float gravity; // % of "normal" gravity + float friction; // inverse elasticity of MOVETYPE_BOUNCE + + int light_level; + + int sequence; // animation sequence + int gaitsequence; // movement animation sequence for player (0 for none) + float frame; // % playback position in animation sequences (0..255) + float animtime; // world time when frame was set + float framerate; // animation playback rate (-8x to 8x) + byte controller[4]; // bone controller setting (0..255) + byte blending[2]; // blending amount between sub-sequences (0..255) + + float scale; // sprite rendering scale (0..255) + + int rendermode; + float renderamt; + vec3_t rendercolor; + int renderfx; + + float health; + float frags; + int weapons; // bit mask for available weapons + float takedamage; + + int deadflag; + vec3_t view_ofs; // eye position + + int button; + int impulse; + + edict_t *chain; // Entity pointer when linked into a linked list + edict_t *dmg_inflictor; + edict_t *enemy; + edict_t *aiment; // entity pointer when MOVETYPE_FOLLOW + edict_t *owner; + edict_t *groundentity; + + int spawnflags; + int flags; + + int colormap; // lowbyte topcolor, highbyte bottomcolor + int team; + + float max_health; + float teleport_time; + float armortype; + float armorvalue; + int waterlevel; + int watertype; + + string_t target; + string_t targetname; + string_t netname; + string_t message; + + float dmg_take; + float dmg_save; + float dmg; + float dmgtime; + + string_t noise; + string_t noise1; + string_t noise2; + string_t noise3; + + float speed; + float air_finished; + float pain_finished; + float radsuit_finished; + + edict_t *pContainingEntity; + + int playerclass; + float maxspeed; + + float fov; + int weaponanim; + + int pushmsec; + + int bInDuck; + int flTimeStepSound; + int flSwimTime; + int flDuckTime; + int iStepLeft; + float flFallVelocity; + + int gamestate; + + int oldbuttons; + + int groupinfo; + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; + edict_t *euser1; + edict_t *euser2; + edict_t *euser3; + edict_t *euser4; +} entvars_t; + + +#endif // PROGDEFS_H \ No newline at end of file diff --git a/engine/progs.h b/engine/progs.h new file mode 100644 index 0000000..6115fcb --- /dev/null +++ b/engine/progs.h @@ -0,0 +1,82 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PROGS_H +#define PROGS_H + +#include "progdefs.h" + +// 16 simultaneous events, max +#define MAX_EVENT_QUEUE 64 + +#define DEFAULT_EVENT_RESENDS 1 + +#include "event_flags.h" + +typedef struct event_info_s event_info_t; + +#include "event_args.h" + +struct event_info_s +{ + unsigned short index; // 0 implies not in use + + short packet_index; // Use data from state info for entity in delta_packet . -1 implies separate info based on event + // parameter signature + short entity_index; // The edict this event is associated with + + float fire_time; // if non-zero, the time when the event should be fired ( fixed up on the client ) + + event_args_t args; + +// CLIENT ONLY + int flags; // Reliable or not, etc. + +}; + +typedef struct event_state_s event_state_t; + +struct event_state_s +{ + struct event_info_s ei[ MAX_EVENT_QUEUE ]; +}; + +#if !defined( ENTITY_STATEH ) +#include "entity_state.h" +#endif + +#if !defined( EDICT_H ) +#include "edict.h" +#endif + +#define STRUCT_FROM_LINK(l,t,m) ((t *)((byte *)l - (int)&(((t *)0)->m))) +#define EDICT_FROM_AREA(l) STRUCT_FROM_LINK(l,edict_t,area) + +//============================================================================ + +extern char *pr_strings; +extern globalvars_t gGlobalVariables; + +//============================================================================ + +edict_t *ED_Alloc (void); +void ED_Free (edict_t *ed); +void ED_LoadFromFile (char *data); + +edict_t *EDICT_NUM(int n); +int NUM_FOR_EDICT(const edict_t *e); + +#define PROG_TO_EDICT(e) ((edict_t *)((byte *)sv.edicts + e)) + +#endif // PROGS_H \ No newline at end of file diff --git a/engine/shake.h b/engine/shake.h new file mode 100644 index 0000000..8e405db --- /dev/null +++ b/engine/shake.h @@ -0,0 +1,55 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef SHAKE_H +#define SHAKE_H + +// Screen / View effects + +// screen shake +extern int gmsgShake; + +// This structure is sent over the net to describe a screen shake event +typedef struct +{ + unsigned short amplitude; // FIXED 4.12 amount of shake + unsigned short duration; // FIXED 4.12 seconds duration + unsigned short frequency; // FIXED 8.8 noise frequency (low frequency is a jerk,high frequency is a rumble) +} ScreenShake; + +extern void V_ApplyShake( float *origin, float *angles, float factor ); +extern void V_CalcShake( void ); +extern int V_ScreenShake( const char *pszName, int iSize, void *pbuf ); +extern int V_ScreenFade( const char *pszName, int iSize, void *pbuf ); + + +// Fade in/out +extern int gmsgFade; + +#define FFADE_IN 0x0000 // Just here so we don't pass 0 into the function +#define FFADE_OUT 0x0001 // Fade out (not in) +#define FFADE_MODULATE 0x0002 // Modulate (don't blend) +#define FFADE_STAYOUT 0x0004 // ignores the duration, stays faded out until new ScreenFade message received + +// This structure is sent over the net to describe a screen fade event +typedef struct +{ + unsigned short duration; // FIXED 4.12 seconds duration + unsigned short holdTime; // FIXED 4.12 seconds duration until reset (fade & hold) + short fadeFlags; // flags + byte r, g, b, a; // fade to color ( max alpha ) +} ScreenFade; + +#endif // SHAKE_H + diff --git a/engine/sprite.h b/engine/sprite.h new file mode 100644 index 0000000..991be35 --- /dev/null +++ b/engine/sprite.h @@ -0,0 +1,96 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef SPRITE_H +#define SPRITE_H + +/* +============================================================================== + +SPRITE MODELS + +.spr extended version (Half-Life compatible sprites with some Xash3D extensions) +============================================================================== +*/ + +#define IDSPRITEHEADER (('P'<<24)+('S'<<16)+('D'<<8)+'I') // little-endian "IDSP" +#define SPRITE_VERSION 2 // Half-Life sprites + +typedef enum +{ + ST_SYNC = 0, + ST_RAND +} synctype_t; + +typedef enum +{ + FRAME_SINGLE = 0, + FRAME_GROUP, + FRAME_ANGLED // Xash3D ext +} frametype_t; + +typedef enum +{ + SPR_NORMAL = 0, + SPR_ADDITIVE, + SPR_INDEXALPHA, + SPR_ALPHTEST, +} drawtype_t; + +typedef enum +{ + SPR_FWD_PARALLEL_UPRIGHT = 0, + SPR_FACING_UPRIGHT, + SPR_FWD_PARALLEL, + SPR_ORIENTED, + SPR_FWD_PARALLEL_ORIENTED, +} angletype_t; + +typedef struct +{ + int ident; // LittleLong 'ISPR' + int version; // current version 2 + angletype_t type; // camera align + drawtype_t texFormat; // rendering mode + int boundingradius; // quick face culling + int bounds[2]; // mins\maxs + int numframes; // including groups + float beamlength; // unused + synctype_t synctype; // animation synctype +} dsprite_t; + +typedef struct +{ + int origin[2]; + int width; + int height; +} dspriteframe_t; + +typedef struct +{ + int numframes; +} dspritegroup_t; + +typedef struct +{ + float interval; +} dspriteinterval_t; + +typedef struct +{ + frametype_t type; +} dframetype_t; + +#endif//SPRITE_H \ No newline at end of file diff --git a/engine/studio.h b/engine/studio.h new file mode 100644 index 0000000..e776cca --- /dev/null +++ b/engine/studio.h @@ -0,0 +1,795 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef STUDIO_H +#define STUDIO_H + +#include "shader.h" +#include "color24.h" +#include "lightlimits.h" + +/* +============================================================================== + +STUDIO MODELS + +Studio models are position independent, so the cache manager can move them. +============================================================================== +*/ + +// header +#define STUDIO_VERSION 10 +#define IDSTUDIOHEADER (('T'<<24)+('S'<<16)+('D'<<8)+'I') // little-endian "IDST" +#define IDSEQGRPHEADER (('Q'<<24)+('S'<<16)+('D'<<8)+'I') // little-endian "IDSQ" + +// studio limits +#define MAXSTUDIOVERTS 16384 // max vertices per submodel +#define MAXSTUDIOSKINS 256 // total textures +#define MAXSTUDIOBONES 128 // total bones actually used +#define MAXSTUDIOMODELS 32 // sub-models per model +#define MAXSTUDIOBODYPARTS 32 // body parts per submodel +#define MAXSTUDIOGROUPS 16 // sequence groups (e.g. barney01.mdl, barney02.mdl, e.t.c) +#define MAXSTUDIOCONTROLLERS 32 // max controllers per model +#define MAXSTUDIOATTACHMENTS 64 // max attachments per model +#define MAXSTUDIOBONEWEIGHTS 4 // absolute hardware limit! +#define MAXSTUDIONAME 32 // a part of specs +#define MAXSTUDIOPOSEPARAM 24 +#define MAXEVENTSTRING 64 +#define MAX_STUDIO_LIGHTMAP_SIZE 256 // must match with engine const!!! + +// client-side model flags +#define STUDIO_ROCKET (1<<0) // leave a trail +#define STUDIO_GRENADE (1<<1) // leave a trail +#define STUDIO_GIB (1<<2) // leave a trail +#define STUDIO_ROTATE (1<<3) // rotate (bonus items) +#define STUDIO_TRACER (1<<4) // green split trail +#define STUDIO_ZOMGIB (1<<5) // small blood trail +#define STUDIO_TRACER2 (1<<6) // orange split trail + rotate +#define STUDIO_TRACER3 (1<<7) // purple trail +#define STUDIO_AMBIENT_LIGHT (1<<8) // force to use ambient shading +#define STUDIO_TRACE_HITBOX (1<<9) // always use hitbox trace instead of bbox +#define STUDIO_FORCE_SKYLIGHT (1<<10) // always grab lightvalues from the sky settings (even if sky is invisible) + +#define STUDIO_HAS_BUMP (1<<16) // loadtime set +#define STUDIO_STATIC_PROP (1<<29) // hint for engine +#define STUDIO_HAS_BONEINFO (1<<30) // extra info about bones (pose matrix, procedural index etc) +#define STUDIO_HAS_BONEWEIGHTS (1<<31) // yes we got support of bone weighting + +// lighting & rendermode options +#define STUDIO_NF_FLATSHADE 0x0001 +#define STUDIO_NF_CHROME 0x0002 +#define STUDIO_NF_FULLBRIGHT 0x0004 +#define STUDIO_NF_NOMIPS 0x0008 // ignore mip-maps +#define STUDIO_NF_SMOOTH 0x0010 // smooth tangent space +#define STUDIO_NF_ADDITIVE 0x0020 // rendering with additive mode +#define STUDIO_NF_MASKED 0x0040 // use texture with alpha channel +#define STUDIO_NF_NORMALMAP 0x0080 // indexed normalmap +#define STUDIO_NF_GLOSSMAP 0x0100 // glossmap +#define STUDIO_NF_GLOSSPOWER 0x0200 +#define STUDIO_NF_LUMA 0x0400 // self-illuminate parts +#define STUDIO_NF_ALPHASOLID 0x0800 // use with STUDIO_NF_MASKED to have solid alphatest surfaces for env_static +#define STUDIO_NF_TWOSIDE 0x1000 // render mesh as twosided +#define STUDIO_NF_HEIGHTMAP 0x2000 + +#define STUDIO_NF_NODRAW (1<<16) // failed to create shader for this mesh +#define STUDIO_NF_NODLIGHT (1<<17) // failed to create dlight shader for this mesh +#define STUDIO_NF_NOSUNLIGHT (1<<18) // failed to create sun light shader for this mesh + +#define STUDIO_NF_HAS_ALPHA (1<<20) // external texture has alpha-channel +#define STUDIO_NF_HAS_DETAIL (1<<21) // studiomodels has detail textures + +#define STUDIO_NF_COLORMAP (1<<30) // can changed by colormap command +#define STUDIO_NF_UV_COORDS (1<<31) // using half-float coords instead of ST + +// motion flags +#define STUDIO_X 0x0001 +#define STUDIO_Y 0x0002 +#define STUDIO_Z 0x0004 +#define STUDIO_XR 0x0008 +#define STUDIO_YR 0x0010 +#define STUDIO_ZR 0x0020 + +#define STUDIO_LX 0x0040 +#define STUDIO_LY 0x0080 +#define STUDIO_LZ 0x0100 +#define STUDIO_LXR 0x0200 +#define STUDIO_LYR 0x0400 +#define STUDIO_LZR 0x0800 +#define STUDIO_LINEAR 0x1000 +#define STUDIO_QUADRATIC_MOTION 0x2000 +#define STUDIO_RESERVED 0x4000 // g-cont. reserved one bit for me +#define STUDIO_TYPES 0x7FFF +#define STUDIO_RLOOP 0x8000 // controller that wraps shortest distance + +// bonecontroller types +#define STUDIO_MOUTH 4 + +// sequence flags +#define STUDIO_LOOPING 0x0001 // ending frame should be the same as the starting frame +#define STUDIO_SNAP 0x0002 // do not interpolate between previous animation and this one +#define STUDIO_DELTA 0x0004 // this sequence "adds" to the base sequences, not slerp blends +#define STUDIO_AUTOPLAY 0x0008 // temporary flag that forces the sequence to always play +#define STUDIO_POST 0x0010 // +#define STUDIO_ALLZEROS 0x0020 // this animation/sequence has no real animation data +#define STUDIO_BLENDPOSE 0x0040 // to differentiate GoldSrc style blending from Source style blending (with pose parameters) +#define STUDIO_CYCLEPOSE 0x0080 // cycle index is taken from a pose parameter index +#define STUDIO_REALTIME 0x0100 // cycle index is taken from a real-time clock, not the animations cycle index +#define STUDIO_LOCAL 0x0200 // sequence has a local context sequence +#define STUDIO_HIDDEN 0x0400 // don't show in default selection views +#define STUDIO_IKRULES 0x0800 // sequence has IK-rules +#define STUDIO_ACTIVITY 0x1000 // Has been updated at runtime to activity index +#define STUDIO_EVENT 0x2000 // Has been updated at runtime to event index +#define STUDIO_WORLD 0x4000 // sequence blends in worldspace +#define STUDIO_LIGHT_FROM_ROOT 0x8000 // get lighting point from root bonepos not from entity origin + +// autolayer flags +#define STUDIO_AL_POST 0x0001 // +#define STUDIO_AL_SPLINE 0x0002 // convert layer ramp in/out curve is a spline instead of linear +#define STUDIO_AL_XFADE 0x0004 // pre-bias the ramp curve to compense for a non-1 weight, + // assuming a second layer is also going to accumulate +#define STUDIO_AL_NOBLEND 0x0008 // animation always blends at 1.0 (ignores weight) +#define STUDIO_AL_LOCAL 0x0010 // layer is a local context sequence +#define STUDIO_AL_POSE 0x0020 // layer blends using a pose parameter instead of parent cycle + +typedef struct +{ + int ident; + int version; + + char name[64]; + int length; + + vec3_t eyeposition; // ideal eye position + vec3_t min; // ideal movement hull size + vec3_t max; + + vec3_t bbmin; // clipping bounding box + vec3_t bbmax; + + int flags; + + int numbones; // bones + int boneindex; + + int numbonecontrollers; // bone controllers + int bonecontrollerindex; + + int numhitboxes; // complex bounding boxes + int hitboxindex; + + int numseq; // animation sequences + int seqindex; + + int numseqgroups; // demand loaded sequences + int seqgroupindex; + + int numtextures; // raw textures + int textureindex; + int texturedataindex; + + int numskinref; // replaceable textures + int numskinfamilies; + int skinindex; + + int numbodyparts; + int bodypartindex; + + int numattachments; // queryable attachable points + int attachmentindex; + + int studiohdr2index; + int soundindex; // UNUSED + + int soundgroups; // UNUSED + int soundgroupindex; // UNUSED + + int numtransitions; // animation node to animation node transition graph + int transitionindex; +} studiohdr_t; + +// extra header to hold more offsets +typedef struct +{ + int numposeparameters; + int poseparamindex; + + int numikautoplaylocks; + int ikautoplaylockindex; + + int numikchains; + int ikchainindex; + + int keyvalueindex; + int keyvaluesize; + + int numhitboxsets; + int hitboxsetindex; + + int unused[6]; // for future expansions +} studiohdr2_t; + +// header for demand loaded sequence group data +typedef struct +{ + int id; + int version; + + char name[64]; + int length; +} studioseqhdr_t; + +// bone flags +#define BONE_ALWAYS_PROCEDURAL 0x0001 // bone is always procedurally animated +#define BONE_SCREEN_ALIGN_SPHERE 0x0002 // bone aligns to the screen, not constrained in motion. +#define BONE_SCREEN_ALIGN_CYLINDER 0x0004 // bone aligns to the screen, constrained by it's own axis. +#define BONE_JIGGLE_PROCEDURAL 0x0008 +#define BONE_FIXED_ALIGNMENT 0x0010 // bone can't spin 360 degrees, all interpolation is normalized around a fixed orientation + +#define BONE_USED_MASK (BONE_USED_BY_HITBOX|BONE_USED_BY_ATTACHMENT|BONE_USED_BY_VERTEX|BONE_USED_BY_BONE_MERGE) +#define BONE_USED_BY_ANYTHING BONE_USED_MASK +#define BONE_USED_BY_HITBOX 0x00000100// bone (or child) is used by a hit box +#define BONE_USED_BY_ATTACHMENT 0x00000200// bone (or child) is used by an attachment point +#define BONE_USED_BY_VERTEX 0x00000400// bone (or child) is used by the toplevel model via skinned vertex +#define BONE_USED_BY_BONE_MERGE 0x00000800 + +// bones +typedef struct +{ + char name[MAXSTUDIONAME];// bone name for symbolic links + int parent; // parent bone + int flags; // bone flags + int bonecontroller[6]; // bone controller index, -1 == none + float value[6]; // default DoF values + float scale[6]; // scale for delta DoF values +} mstudiobone_t; + +#define STUDIO_PROC_AXISINTERP 1 +#define STUDIO_PROC_QUATINTERP 2 +#define STUDIO_PROC_AIMATBONE 3 +#define STUDIO_PROC_AIMATATTACH 4 +#define STUDIO_PROC_JIGGLE 5 + +typedef struct +{ + int control; // local transformation of this bone used to calc 3 point blend + int axis; // axis to check + vec3_t pos[6]; // X+, X-, Y+, Y-, Z+, Z- + vec4_t quat[6]; // X+, X-, Y+, Y-, Z+, Z- +} mstudioaxisinterpbone_t; + +typedef struct +{ + float inv_tolerance; // 1.0f / radian angle of trigger influence + vec4_t trigger; // angle to match + vec3_t pos; // new position + vec4_t quat; // new angle +} mstudioquatinterpinfo_t; + +typedef struct +{ + int control; // local transformation to check + int numtriggers; + int triggerindex; +} mstudioquatinterpbone_t; + +// extra info for bones +typedef struct +{ + float poseToBone[3][4]; // boneweighting reqiures + vec4_t qAlignment; + int proctype; + int procindex; // procedural rule + vec4_t quat; // aligned bone rotation + int reserved[10]; // for future expansions +} mstudioboneinfo_t; + +// JIGGLEBONES +#define JIGGLE_IS_FLEXIBLE 0x01 +#define JIGGLE_IS_RIGID 0x02 +#define JIGGLE_HAS_YAW_CONSTRAINT 0x04 +#define JIGGLE_HAS_PITCH_CONSTRAINT 0x08 +#define JIGGLE_HAS_ANGLE_CONSTRAINT 0x10 +#define JIGGLE_HAS_LENGTH_CONSTRAINT 0x20 +#define JIGGLE_HAS_BASE_SPRING 0x40 +#define JIGGLE_IS_BOING 0x80 // simple squash and stretch sinusoid "boing" + +typedef struct +{ + int flags; + + // general params + float length; // how from from bone base, along bone, is tip + float tipMass; + + // flexible params + float yawStiffness; + float yawDamping; + float pitchStiffness; + float pitchDamping; + float alongStiffness; + float alongDamping; + + // angle constraint + float angleLimit; // maximum deflection of tip in radians + + // yaw constraint + float minYaw; // in radians + float maxYaw; // in radians + float yawFriction; + float yawBounce; + + // pitch constraint + float minPitch; // in radians + float maxPitch; // in radians + float pitchFriction; + float pitchBounce; + + // base spring + float baseMass; + float baseStiffness; + float baseDamping; + float baseMinLeft; + float baseMaxLeft; + float baseLeftFriction; + float baseMinUp; + float baseMaxUp; + float baseUpFriction; + float baseMinForward; + float baseMaxForward; + float baseForwardFriction; + + // boing + float boingImpactSpeed; + float boingImpactAngle; + float boingDampingRate; + float boingFrequency; + float boingAmplitude; +} mstudiojigglebone_t; + +typedef struct +{ + int parent; + int aim; // might be bone or attach + vec3_t aimvector; + vec3_t upvector; + vec3_t basepos; +} mstudioaimatbone_t; + +// bone controllers +typedef struct +{ + int bone; // -1 == 0 + int type; // X, Y, Z, XR, YR, ZR, M + float start; + float end; + int rest; // byte index value at rest + int index; // 0-3 user set controller, 4 mouth +} mstudiobonecontroller_t; + +// intersection boxes +typedef struct +{ + int bone; + int group; // intersection group + vec3_t bbmin; // bounding box + vec3_t bbmax; +} mstudiobbox_t; + +typedef struct +{ + char name[MAXSTUDIONAME]; + int numhitboxes; + int hitboxindex; +} mstudiohitboxset_t; + +#ifndef CACHE_USER +#define CACHE_USER +typedef struct cache_user_s +{ + void *data; // extradata +} cache_user_t; +#endif + +// demand loaded sequence groups +typedef struct +{ + char label[MAXSTUDIONAME]; // textual name + char name[64]; // file name + cache_user_t cache; // cache index pointer + int data; // hack for group 0 +} mstudioseqgroup_t; + +// events +typedef struct mstudioevent_s +{ + int frame; + int event; + int type; + char options[MAXEVENTSTRING]; +} mstudioevent_t; + +#define STUDIO_ATTACHMENT_LOCAL (1<<0) // vectors are filled + +// attachment +typedef struct +{ + char name[MAXSTUDIONAME]; + int flags; + int bone; + vec3_t org; // attachment position + vec3_t vectors[3]; // attachment vectors +} mstudioattachment_t; + +#define IK_SELF 1 +#define IK_WORLD 2 +#define IK_GROUND 3 +#define IK_RELEASE 4 +#define IK_ATTACHMENT 5 +#define IK_UNLATCH 6 + +typedef struct +{ + float scale[6]; + unsigned short offset[6]; +} mstudioikerror_t; + +typedef struct +{ + int index; + + int type; + int chain; + + int bone; + int attachment; // attachment index + + int slot; // iktarget slot. Usually same as chain. + float height; + float radius; + float floor; + vec3_t pos; + vec4_t quat; + + int ikerrorindex; // compressed IK error + + int iStart; + float start; // beginning of influence + float peak; // start of full influence + float tail; // end of full influence + float end; // end of all influence + float contact; // frame footstep makes ground concact + float drop; // how far down the foot should drop when reaching for IK + float top; // top of the foot box + + int unused[4]; // for future expansions +} mstudioikrule_t; + +typedef struct +{ + int chain; + float flPosWeight; + float flLocalQWeight; + int flags; + + int unused[4]; // for future expansions +} mstudioiklock_t; + +typedef struct +{ + int endframe; + int motionflags; + float v0; // velocity at start of block + float v1; // velocity at end of block + float angle; // YAW rotation at end of this blocks movement + vec3_t vector; // movement vector relative to this blocks initial angle + vec3_t position; // relative to start of animation??? +} mstudiomovement_t; + +// additional info for each animation in sequence blend group or single sequence +typedef struct +{ + char label[MAXSTUDIONAME]; // animation label (may be matched with sequence label) + float fps; // frames per second (match with sequence fps or be different) + int flags; // looping/non-looping flags + int numframes; // frames per animation + + // piecewise movement + int nummovements; // piecewise movement + int movementindex; + + int numikrules; + int ikruleindex; // non-zero when IK data is stored in the mdl + + int unused[8]; // for future expansions +} mstudioanimdesc_t; + +// autoplaying sequences +typedef struct +{ + short iSequence; + short iPose; + int flags; + float start; // beginning of influence + float peak; // start of full influence + float tail; // end of full influence + float end; // end of all influence +} mstudioautolayer_t; + +// sequence descriptions +typedef struct +{ + char label[MAXSTUDIONAME]; // sequence label + + float fps; // frames per second + int flags; // looping/non-looping flags + + int activity; + int actweight; + + int numevents; + int eventindex; + + int numframes; // number of frames per sequence + + int weightlistindex; // weightlists + int iklockindex; // IK locks + + int motiontype; + int posekeyindex; // index of pose parameter + vec3_t linearmovement; + int autolayerindex; // autolayer descriptions + int keyvalueindex; // local key-values + + vec3_t bbmin; // per sequence bounding box + vec3_t bbmax; + + int numblends; + int animindex; // mstudioanim_t pointer relative to start of sequence group data + // [blend][bone][X, Y, Z, XR, YR, ZR] + + int blendtype[2]; // X, Y, Z, XR, YR, ZR (same as paramindex) + float blendstart[2]; // starting value (same as paramstart) + float blendend[2]; // ending value (same as paramend) + byte groupsize[2]; // 255 x 255 blends should be enough + byte numautolayers; // count of autoplaying layers + byte numiklocks; // IK-locks per sequence + + int seqgroup; // sequence group for demand loading + + int entrynode; // transition node at entry + int exitnode; // transition node at exit + byte nodeflags; // transition rules (really this is bool) + byte cycleposeindex; // index of pose parameter to use as cycle index + byte fadeintime; // ideal cross fade in time (0.2 secs default) time = (fadeintime / 100) + byte fadeouttime; // ideal cross fade out time (0.2 msecs default) time = (fadeouttime / 100) + + int animdescindex; // mstudioanimdesc_t [blend] +} mstudioseqdesc_t; + +typedef struct +{ + char name[MAXSTUDIONAME]; + int flags; // ???? + float start; // starting value + float end; // ending value + float loop; // looping range, 0 for no looping, 360 for rotations, etc. +} mstudioposeparamdesc_t; + +typedef struct +{ + unsigned short offset[6]; +} mstudioanim_t; + +// animation frames +typedef union +{ + struct + { + byte valid; + byte total; + } num; + short value; +} mstudioanimvalue_t; + +// body part index +typedef struct +{ + char name[64]; + int nummodels; + int base; + int modelindex; // index into models array +} mstudiobodyparts_t; + +// skin info +typedef struct mstudiotex_s +{ + char name[64]; + int flags; + int width; + int height; + int index; +} mstudiotexture_t; + +// ikinfo +typedef struct +{ + int bone; + vec3_t kneeDir; // ideal bending direction (per link, if applicable) + vec3_t unused0; // unused +} mstudioiklink_t; + +typedef struct +{ + char name[MAXSTUDIONAME]; + int linktype; + int numlinks; + int linkindex; +} mstudioikchain_t; + +typedef struct +{ + byte weight[4]; + char bone[4]; +} mstudioboneweight_t; + +// skin families +// short index[skinfamilies][skinref] + +// studio models +typedef struct +{ + char name[64]; + + int type; // UNUSED + float boundingradius; // UNUSED + + int nummesh; + int meshindex; + + int numverts; // number of unique vertices + int vertinfoindex; // vertex bone info + int vertindex; // vertex vec3_t + int numnorms; // number of unique surface normals + int norminfoindex; // normal bone info + int normindex; // normal vec3_t + + int blendvertinfoindex; // boneweighted vertex info + int blendnorminfoindex; // boneweighted normal info +} mstudiomodel_t; + +// vec3_t boundingbox[model][bone][2]; // complex intersection info + +// meshes +typedef struct +{ + int numtris; + int triindex; + int skinref; + int numnorms; // per mesh normals + int normindex; // UNUSED! +} mstudiomesh_t; + +typedef struct +{ + short vertindex; // index into vertex array + short normindex; // index into normal array + short s,t; // s,t position on skin +} mstudiotrivert_t; + +/* +=========================== + +USER-DEFINED DATA + +=========================== +*/ +// this struct may be expaned by user request +typedef struct vbomesh_s +{ + unsigned int skinref; // skin reference + unsigned int numVerts; // trifan vertices count + unsigned int numElems; // trifan elements count + int lightmapnum; // each mesh should use only once atlas page! + + unsigned int vbo, vao, ibo; // buffer objects + vec3_t mins, maxs; // right transform to get screencopy + int parentbone; // parent bone to transform AABB + unsigned short uniqueID; // to reject decal drawing + unsigned int cacheSize; // debug info: uploaded cache size for this buffer +} vbomesh_t; + +// each mstudiotexture_t has a material +typedef struct mstudiomat_s +{ + mstudiotexture_t *pSource; // pointer to original texture + + unsigned short gl_diffuse_id; // diffuse texture + unsigned short gl_detailmap_id; // detail texture + unsigned short gl_normalmap_id; // normalmap + unsigned short gl_specular_id; // specular + unsigned short gl_glowmap_id; // self-illuminate parts + unsigned short gl_heightmap_id; // parallax stuff + + // this part is shared with matdesc_t + float smoothness; // smoothness factor + float detailScale[2]; // detail texture scales x, y + float reflectScale; // reflection scale for translucent water + float refractScale; // refraction scale for mirrors, windows, water + float aberrationScale; // chromatic abberation + float reliefScale; // relief-mapping + struct matdef_s *effects; // hit, impact, particle effects etc + int flags; // mstudiotexture_t->flags + + // cached shadernums + shader_t forwardScene; + shader_t forwardLightSpot; + shader_t forwardLightOmni; + shader_t forwardLightProj; + shader_t deferredScene; + shader_t deferredLight; + shader_t forwardDepth; + + unsigned short lastRenderMode; // for catch change render modes +} mstudiomaterial_t; + +typedef struct mstudiosurface_s +{ + int flags; // match with msurface_t->flags + int texture_step; + + short lightextents[2]; + unsigned short light_s[MAXLIGHTMAPS]; + unsigned short light_t[MAXLIGHTMAPS]; + byte lights[MAXDYNLIGHTS];// static lights that affected this face (255 = no lights) + + int lightmaptexturenum; + byte styles[MAXLIGHTMAPS]; + + color24 *samples; // note: this is the actual lightmap data for this surface + color24 *normals; // note: this is the actual deluxemap data for this surface + byte *shadows; // note: occlusion map for this surface +} mstudiosurface_t; + +typedef struct +{ + vbomesh_t *meshes; // meshes per submodel + int nummesh; // mstudiomodel_t->nummesh +} msubmodel_t; + +// triangles +typedef struct mbodypart_s +{ + int base; // mstudiobodyparts_t->base + msubmodel_t *models[MAXSTUDIOBODYPARTS]; // submodels per body part + int nummodels; // mstudiobodyparts_t->nummodels +} mbodypart_t; + +typedef struct mvbocache_s +{ + mstudiosurface_t *surfaces; + int numsurfaces; + + mbodypart_t *bodyparts; + int numbodyparts; + + bool update_light; // gamma or brightness was changed so we need to reload lightmaps +} mstudiocache_t; + +typedef struct mposebone_s +{ + matrix3x4 posetobone[MAXSTUDIOBONES]; +} mposetobone_t; + +#endif//STUDIO_H \ No newline at end of file diff --git a/game_shared/_virtualfs.h b/game_shared/_virtualfs.h new file mode 100644 index 0000000..8deffcc --- /dev/null +++ b/game_shared/_virtualfs.h @@ -0,0 +1,300 @@ +//======================================================================= +// Copyright (C) XashXT Group 2014 +// virtualfs.h - Virtual FileSystem that writes into memory +//======================================================================= +#ifndef VIRTUALFS_H +#define VIRTUALFS_H + +#define FS_MEM_BLOCK 65535 +#define FS_MSG_BLOCK 8192 + +class CVirtualFS +{ +public: + CVirtualFS(); + CVirtualFS( const byte *file, size_t size ); + ~CVirtualFS(); + + size_t Read( char *out, size_t size ); + size_t Write( const void *in, size_t size ); + size_t Insert( const void *in, size_t size ); + size_t Print( const char *message ); + size_t IPrint( const char *message ); + size_t Printf( const char *fmt, ... ); + size_t IPrintf( const char *fmt, ... ); + size_t VPrintf( const char *fmt, va_list ap ); + size_t IVPrintf( const char *fmt, va_list ap ); + char *GetBuffer( void ) { return (char *)m_pBuffer; }; + size_t GetSize( void ) { return m_iLength; }; + size_t Tell( void ) { return m_iOffset; } + bool Eof( void ) { return (m_iOffset == m_iLength) ? true : false; } + int Seek( size_t offset, int whence ); + int Gets( char *string, size_t size ); + int Getc( void ); +private: + byte *m_pBuffer; // file buffer + size_t m_iBuffSize; // real buffer size + size_t m_iOffset; // buffer position + size_t m_iLength; // buffer current size +}; + +CVirtualFS :: CVirtualFS() +{ + m_iBuffSize = FS_MEM_BLOCK; // can be resized later + m_pBuffer = new byte[m_iBuffSize]; + memset( m_pBuffer, 0, m_iBuffSize ); + m_iLength = m_iOffset = 0; +} + +CVirtualFS :: CVirtualFS( const byte *file, size_t size ) +{ + if( !file || size <= 0 ) + { + m_iBuffSize = m_iOffset = m_iLength = 0; + m_pBuffer = NULL; + return; + } + + m_iLength = m_iBuffSize = size; + m_pBuffer = new byte[m_iBuffSize]; + memcpy( m_pBuffer, file, m_iBuffSize ); + m_iOffset = 0; +} + +CVirtualFS :: ~CVirtualFS() +{ + delete [] m_pBuffer; +} + +size_t CVirtualFS :: Read( char *out, size_t size ) +{ + if( !m_pBuffer || !out || size <= 0 ) + return 0; + + // check for enough room + if( m_iOffset >= m_iLength ) + return 0; // hit EOF + + size_t read_size = 0; + + if( m_iOffset + size <= m_iLength ) + { + memcpy( out, m_pBuffer + m_iOffset, size ); + m_iOffset += size; + read_size = size; + } + else + { + int reduced_size = m_iLength - m_iOffset; + memcpy( out, m_pBuffer + m_iOffset, reduced_size ); + m_iOffset += reduced_size; + read_size = reduced_size; + } + + return read_size; +} + +size_t CVirtualFS :: Write( const void *in, size_t size ) +{ + if( !m_pBuffer ) return -1; + + if( m_iOffset + size >= m_iBuffSize ) + { + size_t newsize = m_iOffset + size + FS_MEM_BLOCK; + + if( m_iBuffSize < newsize ) + { + // reallocate buffer now + m_pBuffer = (byte *)realloc( m_pBuffer, newsize ); + memset( m_pBuffer + m_iBuffSize, 0, newsize - m_iBuffSize ); + m_iBuffSize = newsize; // update buffsize + } + } + + // write into buffer + memcpy( m_pBuffer + m_iOffset, in, size ); + m_iOffset += size; + + if( m_iOffset > m_iLength ) + m_iLength = m_iOffset; + + return m_iLength; +} + +size_t CVirtualFS :: Insert( const void *in, size_t size ) +{ + if( !m_pBuffer ) return -1; + + if( m_iLength + size >= m_iBuffSize ) + { + size_t newsize = m_iLength + size + FS_MEM_BLOCK; + + if( m_iBuffSize < newsize ) + { + // reallocate buffer now + m_pBuffer = (byte *)realloc( m_pBuffer, newsize ); + memset( m_pBuffer + m_iBuffSize, 0, newsize - m_iBuffSize ); + m_iBuffSize = newsize; // update buffsize + } + } + + // backup right part + size_t rp_size = m_iLength - m_iOffset; + byte *backup = new byte[rp_size]; + memcpy( backup, m_pBuffer + m_iOffset, rp_size ); + + // insert into buffer + memcpy( m_pBuffer + m_iOffset, in, size ); + m_iOffset += size; + + // write right part buffer + memcpy( m_pBuffer + m_iOffset, backup, rp_size ); + delete [] backup; + + if(( m_iOffset + rp_size ) > m_iLength ) + m_iLength = m_iOffset + rp_size; + + return m_iLength; +} + +size_t CVirtualFS :: Print( const char *message ) +{ + return Write( message, Q_strlen( message )); +} + +size_t CVirtualFS :: IPrint( const char *message ) +{ + return Insert( message, Q_strlen( message )); +} + +size_t CVirtualFS :: Printf( const char *fmt, ... ) +{ + size_t result; + va_list args; + + va_start( args, fmt ); + result = VPrintf( fmt, args ); + va_end( args ); + + return result; +} + +size_t CVirtualFS :: IPrintf( const char *fmt, ... ) +{ + size_t result; + va_list args; + + va_start( args, fmt ); + result = IVPrintf( fmt, args ); + va_end( args ); + + return result; +} + +size_t CVirtualFS :: VPrintf( const char *fmt, va_list ap ) +{ + size_t buff_size = FS_MSG_BLOCK; + char *tempbuff; + size_t len; + + while( 1 ) + { + tempbuff = new char[buff_size]; + len = Q_vsprintf( tempbuff, fmt, ap ); + if( len >= 0 && len < buff_size ) + break; + delete [] tempbuff; + buff_size <<= 1; + } + + len = Write( tempbuff, len ); + delete [] tempbuff; + + return len; +} + +size_t CVirtualFS :: IVPrintf( const char *fmt, va_list ap ) +{ + size_t buff_size = FS_MSG_BLOCK; + char *tempbuff; + size_t len; + + while( 1 ) + { + tempbuff = new char[buff_size]; + len = Q_vsprintf( tempbuff, fmt, ap ); + if( len >= 0 && len < buff_size ) + break; + delete [] tempbuff; + buff_size <<= 1; + } + + len = Insert( tempbuff, len ); + delete [] tempbuff; + + return len; +} + +int CVirtualFS :: Getc( void ) +{ + char c; + + if( !Read( &c, 1 )) + return EOF; + return (byte)c; +} + +int CVirtualFS :: Gets( char *string, size_t size ) +{ + size_t end = 0; + int c; + + while( 1 ) + { + c = Getc(); + + if( c == '\r' || c == '\n' || c < 0 ) + break; + + if( end < ( size - 1 )) + string[end++] = c; + } + + string[end] = 0; + + // remove \n following \r + if( c == '\r' ) + { + c = Getc(); + if( c != '\n' ) Seek( -1, SEEK_CUR ); // rewind + } + + return c; +} + +int CVirtualFS :: Seek( size_t offset, int whence ) +{ + // Compute the file offset + switch( whence ) + { + case SEEK_CUR: + offset += m_iOffset; + break; + case SEEK_SET: + break; + case SEEK_END: + offset += m_iLength; + break; + default: + return -1; + } + + if(( offset < 0 ) || ( offset > m_iLength )) + return -1; + + m_iOffset = offset; + + return 0; +} + +#endif//VIRTUALFS_H \ No newline at end of file diff --git a/game_shared/bitvec.h b/game_shared/bitvec.h new file mode 100644 index 0000000..0271a11 --- /dev/null +++ b/game_shared/bitvec.h @@ -0,0 +1,179 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef BITVEC_H +#define BITVEC_H +#ifdef _WIN32 +#pragma once +#endif + + +#include + + +class CBitVecAccessor +{ +public: + CBitVecAccessor(unsigned long *pDWords, int iBit); + + void operator=(int val); + operator unsigned long(); + +private: + unsigned long *m_pDWords; + int m_iBit; +}; + + +// CBitVec allows you to store a list of bits and do operations on them like they were +// an atomic type. +template +class CBitVec +{ +public: + + CBitVec(); + + // Set all values to the specified value (0 or 1..) + void Init(int val = 0); + + // Access the bits like an array. + CBitVecAccessor operator[](int i); + + // Operations on other bit vectors. + CBitVec& operator=(CBitVec const &other); + bool operator==(CBitVec const &other); + bool operator!=(CBitVec const &other); + + // Get underlying dword representations of the bits. + int GetNumDWords(); + unsigned long GetDWord(int i); + void SetDWord(int i, unsigned long val); + + int GetNumBits(); + +private: + + enum {NUM_DWORDS = NUM_BITS/32 + !!(NUM_BITS & 31)}; + unsigned long m_DWords[NUM_DWORDS]; +}; + + + +// ------------------------------------------------------------------------ // +// CBitVecAccessor inlines. +// ------------------------------------------------------------------------ // + +inline CBitVecAccessor::CBitVecAccessor(unsigned long *pDWords, int iBit) +{ + m_pDWords = pDWords; + m_iBit = iBit; +} + + +inline void CBitVecAccessor::operator=(int val) +{ + if(val) + m_pDWords[m_iBit >> 5] |= (1 << (m_iBit & 31)); + else + m_pDWords[m_iBit >> 5] &= ~(unsigned long)(1 << (m_iBit & 31)); +} + +inline CBitVecAccessor::operator unsigned long() +{ + return m_pDWords[m_iBit >> 5] & (1 << (m_iBit & 31)); +} + + + +// ------------------------------------------------------------------------ // +// CBitVec inlines. +// ------------------------------------------------------------------------ // + +template +inline int CBitVec::GetNumBits() +{ + return NUM_BITS; +} + + +template +inline CBitVec::CBitVec() +{ + for(int i=0; i < NUM_DWORDS; i++) + m_DWords[i] = 0; +} + + +template +inline void CBitVec::Init(int val) +{ + for(int i=0; i < GetNumBits(); i++) + { + (*this)[i] = val; + } +} + + +template +inline CBitVec& CBitVec::operator=(CBitVec const &other) +{ + memcpy(m_DWords, other.m_DWords, sizeof(m_DWords)); + return *this; +} + + +template +inline CBitVecAccessor CBitVec::operator[](int i) +{ + assert(i >= 0 && i < GetNumBits()); + return CBitVecAccessor(m_DWords, i); +} + + +template +inline bool CBitVec::operator==(CBitVec const &other) +{ + for(int i=0; i < NUM_DWORDS; i++) + if(m_DWords[i] != other.m_DWords[i]) + return false; + + return true; +} + + +template +inline bool CBitVec::operator!=(CBitVec const &other) +{ + return !(*this == other); +} + + +template +inline int CBitVec::GetNumDWords() +{ + return NUM_DWORDS; +} + +template +inline unsigned long CBitVec::GetDWord(int i) +{ + assert(i >= 0 && i < NUM_DWORDS); + return m_DWords[i]; +} + + +template +inline void CBitVec::SetDWord(int i, unsigned long val) +{ + assert(i >= 0 && i < NUM_DWORDS); + m_DWords[i] = val; +} + + +#endif // BITVEC_H + diff --git a/game_shared/bone_setup.cpp b/game_shared/bone_setup.cpp new file mode 100644 index 0000000..3538b34 --- /dev/null +++ b/game_shared/bone_setup.cpp @@ -0,0 +1,2381 @@ +/* +bone_setup.cpp - shared code for setup studio bones +This file is part of XashNT engine +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "mathlib.h" +#include "const.h" +#include +#include "com_model.h" +#include "stringlib.h" +#include "bs_defs.h" +#include "ikcontext.h" +#include "iksolver.h" + +//----------------------------------------------------------------------------- +// Purpose: return a sub frame rotation for a single bone +//----------------------------------------------------------------------------- +void CStudioBoneSetup :: ExtractAnimValue( int frame, const mstudioanimvalue_t *panimvalue, float scale, float &v1, float &v2 ) +{ + if( !panimvalue ) + { + v1 = v2 = 0.0f; + return; + } + + // avoids a crash reading off the end of the data + // g-cont. this solution is coming from Source 2007 and has no changes in Source 2013 + if(( panimvalue->num.total == 1 ) && ( panimvalue->num.valid == 1 )) + { + v1 = v2 = panimvalue[1].value * scale; + return; + } + + int k = frame; + + // find the data list that has the frame + while( panimvalue->num.total <= k ) + { + k -= panimvalue->num.total; + panimvalue += panimvalue->num.valid + 1; + + if( panimvalue->num.total == 0 ) + { + debugMsg( "^3Error:^7 ExtractAnimValue: num.total == 0!\n" ); + v1 = v2 = 0.0f; + return; + } + } + + // Bah, missing blend! + if( panimvalue->num.valid > k ) + { + // has valid animation data + v1 = panimvalue[k+1].value * scale; + + if( panimvalue->num.valid > k + 1 ) + { + // has valid animation blend data + v2 = panimvalue[k+2].value * scale; + } + else + { + if( panimvalue->num.total > k + 1 ) v2 = v1; // data repeats, no blend + else v2 = panimvalue[panimvalue->num.valid+2].value * scale; // pull blend from first data block in next list + } + } + else + { + // get last valid data block + v1 = panimvalue[panimvalue->num.valid].value * scale; + + if( panimvalue->num.total > k + 1 ) v2 = v1; // data repeats, no blend + else v2 = panimvalue[panimvalue->num.valid + 2].value * scale; // pull blend from first data block in next list + } +} + +//----------------------------------------------------------------------------- +// Purpose: return a sub frame rotation for a single bone (usefully to decompress IK errors) +//----------------------------------------------------------------------------- +void CStudioBoneSetup :: ExtractAnimValue( int frame, const mstudioanimvalue_t *panimvalue, float scale, float &v1 ) +{ + if( !panimvalue ) + { + v1 = 0.0f; + return; + } + + int k = frame; + + while( panimvalue->num.total <= k ) + { + k -= panimvalue->num.total; + panimvalue += panimvalue->num.valid + 1; + + if( panimvalue->num.total == 0 ) + { + debugMsg( "^3Error:^7 ExtractAnimValue: num.total == 0!\n" ); + v1 = 0.0f; + return; + } + } + + // Bah, missing blend! + if( panimvalue->num.valid > k ) + { + v1 = panimvalue[k+1].value * scale; + } + else + { + // get last valid data block + v1 = panimvalue[panimvalue->num.valid].value * scale; + } +} + +void CStudioBoneSetup :: AdjustBoneAngles( mstudiobone_t *pbone, Radian &angles1, Radian &angles2 ) +{ + if( m_flBoneControllers == NULL ) + return; + + for( int j = 0; j < 3; j++ ) + { + if( pbone->bonecontroller[j+3] != -1 ) + { + angles1[j] += m_flBoneControllers[pbone->bonecontroller[j+3]]; + angles2[j] += m_flBoneControllers[pbone->bonecontroller[j+3]]; + } + } +} + +void CStudioBoneSetup :: AdjustBoneOrigin( mstudiobone_t *pbone, Vector &origin ) +{ + if( m_flBoneControllers == NULL ) + return; + + for( int j = 0; j < 3; j++ ) + { + if( pbone->bonecontroller[j] != -1 ) + { + origin[j] += m_flBoneControllers[pbone->bonecontroller[j]]; + } + } +} + +Vector4D CStudioBoneSetup :: CalcBoneQuaternion( int frame, float s, int flags, mstudiobone_t *pbone, mstudioboneinfo_t *pinfo, mstudioanim_t *panim ) +{ + Radian angles1, angles2; + Vector4D q1, q2, q; + + if( !FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEINFO )) + pinfo = NULL; + + if( s > 0.001f ) + { + ExtractAnimValue( frame, pAnimvalue( panim, 3 ), pbone->scale[3], angles1.x, angles2.x ); + ExtractAnimValue( frame, pAnimvalue( panim, 4 ), pbone->scale[4], angles1.y, angles2.y ); + ExtractAnimValue( frame, pAnimvalue( panim, 5 ), pbone->scale[5], angles1.z, angles2.z ); + + if( !FBitSet( flags, STUDIO_DELTA )) + { + angles1.x = angles1.x + pbone->value[3]; + angles1.y = angles1.y + pbone->value[4]; + angles1.z = angles1.z + pbone->value[5]; + angles2.x = angles2.x + pbone->value[3]; + angles2.y = angles2.y + pbone->value[4]; + angles2.z = angles2.z + pbone->value[5]; + } + + AdjustBoneAngles( pbone, angles1, angles2 ); + + if( angles1 != angles2 ) + { + AngleQuaternion( angles1, q1 ); + AngleQuaternion( angles2, q2 ); + QuaternionBlend( q1, q2, s, q ); + } + else AngleQuaternion( angles1, q ); + } + else + { + ExtractAnimValue( frame, pAnimvalue( panim, 3 ), pbone->scale[3], angles1.x ); + ExtractAnimValue( frame, pAnimvalue( panim, 4 ), pbone->scale[4], angles1.y ); + ExtractAnimValue( frame, pAnimvalue( panim, 5 ), pbone->scale[5], angles1.z ); + angles2 = g_radZero; // dummy + + if( !FBitSet( flags, STUDIO_DELTA )) + { + angles1.x = angles1.x + pbone->value[3]; + angles1.y = angles1.y + pbone->value[4]; + angles1.z = angles1.z + pbone->value[5]; + } + + AdjustBoneAngles( pbone, angles1, angles2 ); + + AngleQuaternion( angles1, q ); + } + + // align to unified bone + if( !FBitSet( flags, STUDIO_DELTA ) && FBitSet( pbone->flags, BONE_FIXED_ALIGNMENT ) && ( pinfo != NULL )) + { + QuaternionAlign( pinfo->qAlignment, q, q ); + } + + return q; +} + +//----------------------------------------------------------------------------- +// Purpose: return a sub frame position for a single bone +//----------------------------------------------------------------------------- +Vector CStudioBoneSetup :: CalcBonePosition( int frame, float s, int flags, mstudiobone_t *pbone, mstudioanim_t *panim ) +{ + Vector origin1, origin2; + Vector pos; + + if( s > 0.001f ) + { + ExtractAnimValue( frame, pAnimvalue( panim, 0 ), pbone->scale[0], origin1.x, origin2.x ); + ExtractAnimValue( frame, pAnimvalue( panim, 1 ), pbone->scale[1], origin1.y, origin2.y ); + ExtractAnimValue( frame, pAnimvalue( panim, 2 ), pbone->scale[2], origin1.z, origin2.z ); + + if( origin1 != origin2 ) + { + InterpolateOrigin( origin1, origin2, pos, s ); + } + else pos = origin1; + } + else + { + ExtractAnimValue( frame, pAnimvalue( panim, 0 ), pbone->scale[0], origin1.x ); + ExtractAnimValue( frame, pAnimvalue( panim, 1 ), pbone->scale[1], origin1.y ); + ExtractAnimValue( frame, pAnimvalue( panim, 2 ), pbone->scale[2], origin1.z ); + + pos = origin1; + } + + if( !FBitSet( flags, STUDIO_DELTA )) + { + pos.x = pos.x + pbone->value[0]; + pos.y = pos.y + pbone->value[1]; + pos.z = pos.z + pbone->value[2]; + } + + AdjustBoneOrigin( pbone, pos ); + + return pos; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CStudioBoneSetup :: CalcIKError( const mstudioikerror_t *panim, int frame, float s, Vector &pos, Vector4D &q ) +{ + Radian angles1, angles2; + Vector origin1, origin2; + Vector4D q1, q2; + + if( s > 0.0001f ) + { + ExtractAnimValue( frame, pAnimvalue( panim, 0 ), panim->scale[0], origin1.x, origin2.x ); + ExtractAnimValue( frame, pAnimvalue( panim, 1 ), panim->scale[1], origin1.y, origin2.y ); + ExtractAnimValue( frame, pAnimvalue( panim, 2 ), panim->scale[2], origin1.z, origin2.z ); + + if( origin1 != origin2 ) + { + InterpolateOrigin( origin1, origin2, pos, s ); + } + else pos = origin1; + + ExtractAnimValue( frame, pAnimvalue( panim, 3 ), panim->scale[3], angles1.x, angles2.x ); + ExtractAnimValue( frame, pAnimvalue( panim, 4 ), panim->scale[4], angles1.y, angles2.y ); + ExtractAnimValue( frame, pAnimvalue( panim, 5 ), panim->scale[5], angles1.z, angles2.z ); + + if( angles1 != angles2 ) + { + AngleQuaternion( angles1, q1 ); + AngleQuaternion( angles2, q2 ); + QuaternionBlend( q1, q2, s, q ); + } + else AngleQuaternion( angles1, q ); + } + else + { + ExtractAnimValue( frame, pAnimvalue( panim, 0 ), panim->scale[0], origin1.x ); + ExtractAnimValue( frame, pAnimvalue( panim, 1 ), panim->scale[1], origin1.y ); + ExtractAnimValue( frame, pAnimvalue( panim, 2 ), panim->scale[2], origin1.z ); + + pos = origin1; + + ExtractAnimValue( frame, pAnimvalue( panim, 3 ), panim->scale[3], angles1.x ); + ExtractAnimValue( frame, pAnimvalue( panim, 4 ), panim->scale[4], angles1.y ); + ExtractAnimValue( frame, pAnimvalue( panim, 5 ), panim->scale[5], angles1.z ); + + AngleQuaternion( angles1, q ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +mstudioanim_t *CStudioBoneSetup :: GetAnimSourceData( mstudioseqdesc_t *pseqdesc ) +{ + mstudioseqgroup_t *pseqgroup = (mstudioseqgroup_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqgroupindex) + pseqdesc->seqgroup; + + if( pseqdesc->seqgroup == 0 ) + return (mstudioanim_t *)((byte *)m_pStudioHeader + pseqgroup->data + pseqdesc->animindex); + + return NULL; // base implementation can't lookup for sequence groups +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +mstudioanim_t *CStudioBoneSetup :: FetchAnimation( mstudioseqdesc_t *pseqdesc, int animation ) +{ + mstudioanim_t *panim = (mstudioanim_t *)GetAnimSourceData( pseqdesc ); + + if( animation < 0 || animation > ( pseqdesc->numblends - 1 )) + return panim; + + panim += animation * m_pStudioHeader->numbones; + + return panim; +} + +//----------------------------------------------------------------------------- +// Purpose: returns animation description. Notice: may return NULL +//----------------------------------------------------------------------------- +mstudioanimdesc_t *CStudioBoneSetup :: FetchAnimDesc( mstudioseqdesc_t *pseqdesc, int animation ) +{ + static mstudioanimdesc_t baseDesc; // for backward compatibility + + if( pseqdesc->animdescindex <= 0 || pseqdesc->animdescindex >= m_pStudioHeader->length ) + { + Q_strncpy( baseDesc.label, pseqdesc->label, sizeof( baseDesc.label )); + baseDesc.numframes = pseqdesc->numframes; + baseDesc.flags = pseqdesc->flags; + baseDesc.fps = pseqdesc->fps; + + return &baseDesc; + } + + mstudioanimdesc_t *panimdesc = (mstudioanimdesc_t *)((byte *)m_pStudioHeader + pseqdesc->animdescindex); + + if( animation < 0 || animation > ( pseqdesc->numblends - 1 )) + return panimdesc; // pointer to first anim description + + panimdesc += animation; + + return panimdesc; +} + +//----------------------------------------------------------------------------- +// Purpose: Find and decode a sub-frame of animation +//----------------------------------------------------------------------------- +void CStudioBoneSetup :: CalcAnimation( Vector pos[], Vector4D q[], mstudioseqdesc_t *pseqdesc, int animation, float cycle ) +{ + mstudiobone_t *pbone = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + mstudioanimdesc_t *animdesc = FetchAnimDesc( pseqdesc, animation ); + mstudioanim_t *panim = FetchAnimation( pseqdesc, animation ); + mstudioboneinfo_t *pboneinfo = NULL; + + if( FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEINFO )) + pboneinfo = (mstudioboneinfo_t *)((byte *)pbone + m_pStudioHeader->numbones * sizeof( mstudiobone_t )); + + float fFrame = cycle * (animdesc->numframes - 1); + int iFrame = (int)fFrame; + float s = (fFrame - iFrame); // cut fractional part + + const float *pweight = pBoneweight( pseqdesc ); + + // BUGBUG: the sequence, the anim, and the model can have all different bone mappings. + for( int i = 0; i < m_pStudioHeader->numbones; i++, pbone++, pboneinfo++, panim++ ) + { + if( pweight[i] <= 0.0f || !IsBoneUsed( pbone )) + continue; + + q[i] = CalcBoneQuaternion( iFrame, s, animdesc->flags, pbone, pboneinfo, panim ); + pos[i] = CalcBonePosition( iFrame, s, animdesc->flags, pbone, panim ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Inter-animation blend. Assumes both types are identical. +// blend together q1,pos1 with q2,pos2. Return result in q1,pos1. +// 0 returns q1, pos1. 1 returns q2, pos2 +//----------------------------------------------------------------------------- +void CStudioBoneSetup :: BlendBones( Vector4D q1[], Vector pos1[], mstudioseqdesc_t *pseqdesc, const Vector4D q2[], const Vector pos2[], float s ) +{ + mstudiobone_t *pbone = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + const float *pweight = pBoneweight( pseqdesc ); + int i; + + if( s <= 0.0f ) + { + return; + } + else if( s >= 1.0 ) + { + for( i = 0; i < m_pStudioHeader->numbones; i++ ) + { + if( pweight[i] <= 0.0f || !IsBoneUsed( pbone + i )) + continue; + + pos1[i] = pos2[i]; + q1[i] = q2[i]; + } + return; + } + + for( i = 0; i < m_pStudioHeader->numbones; i++ ) + { + if( pweight[i] > 0.0f ) + { + if( FBitSet( pbone[i].flags, BONE_FIXED_ALIGNMENT )) + QuaternionBlendNoAlign( q1[i], q2[i], s, q1[i] ); + else QuaternionBlend( q1[i], q2[i], s, q1[i] ); + InterpolateOrigin( pos1[i], pos2[i], pos1[i], s ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: blend together q1,pos1 with q2,pos2. Return result in q1,pos1. +// 0 returns q1, pos1. 1 returns q2, pos2 +//----------------------------------------------------------------------------- +void CStudioBoneSetup :: SlerpBones( Vector4D q1[], Vector pos1[], mstudioseqdesc_t *pseqdesc, const Vector4D q2[], const Vector pos2[], float s ) +{ + mstudiobone_t *pbone = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + const float *pweight = pBoneweight( pseqdesc ); + float s2; + + if( s <= 0.0f ) + { + return; + } + else if( s > 1.0f ) + { + s = 1.0f; + } + + if( FBitSet( pseqdesc->flags, STUDIO_WORLD )) + { + WorldSpaceSlerp( q1, pos1, pseqdesc, q2, pos2, s ); + return; + } + + if( FBitSet( pseqdesc->flags, STUDIO_DELTA )) + { + for( int i = 0; i < m_pStudioHeader->numbones; i++ ) + { + // skip unused bones + if( !IsBoneUsed( pbone + i )) + continue; + + s2 = s * pweight[i]; // blend in based on this bones weight + if( s2 <= 0.0f ) continue; + + if( FBitSet( pseqdesc->flags, STUDIO_POST )) + { + QuaternionMA( q1[i], s2, q2[i], q1[i] ); + // FIXME: are these correct? + pos1[i] = pos1[i] + pos2[i] * s2; + } + else + { + QuaternionSM( s2, q2[i], q1[i], q1[i] ); + // FIXME: are these correct? + pos1[i] = pos1[i] + pos2[i] * s2; + } + } + } + else + { + for( int i = 0; i < m_pStudioHeader->numbones; i++ ) + { + // skip unused bones + if( !IsBoneUsed( pbone + i )) + continue; + + s2 = s * pweight[i]; // blend in based on this bones weight + if( s2 <= 0.0f ) continue; + + if( FBitSet( pbone[i].flags, BONE_FIXED_ALIGNMENT )) + QuaternionSlerpNoAlign( q1[i], q2[i], s2, q1[i] ); + else QuaternionSlerp( q1[i], q2[i], s2, q1[i] ); + InterpolateOrigin( pos1[i], pos2[i], pos1[i], s2 ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: blend together in world space q1,pos1 with q2,pos2. Return result in q1,pos1. +// 0 returns q1, pos1. 1 returns q2, pos2 +//----------------------------------------------------------------------------- +void CStudioBoneSetup :: WorldSpaceSlerp( Vector4D q1[], Vector pos1[], mstudioseqdesc_t *pseqdesc, const Vector4D q2[], const Vector pos2[], float s ) +{ + mstudiobone_t *pbone = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + const float *pweight = pBoneweight( pseqdesc ); + float s1; // weight of parent for q2, pos2 + float s2; // weight for q2, pos2 + + // make fake root transform + matrix3x4 rootXform; + + rootXform.Identity(); + + for( int i = 0; i < m_pStudioHeader->numbones; i++ ) + { + // skip unused bones + if( !IsBoneUsed( pbone + i )) + continue; + + int n = pbone[i].parent; + s1 = 0.0f; + + s2 = s * pweight[i]; // blend in based on this bones weight + if( n != -1 ) s1 = s * pweight[n]; + + if( s1 == 1.0f && s2 == 1.0f ) + { + pos1[i] = pos2[i]; + q1[i] = q2[i]; + } + else if( s2 > 0.0f ) + { + Vector4D srcQ, dstQ; + Vector srcPos, dstPos; + Vector4D targetQ; + Vector targetPos; + + BuildBoneChain( rootXform, pos1, q1, i, dstBoneToWorld ); + BuildBoneChain( rootXform, pos2, q2, i, srcBoneToWorld ); + + srcQ = srcBoneToWorld[i].GetQuaternion(); + dstQ = dstBoneToWorld[i].GetQuaternion(); + srcPos = srcBoneToWorld[i].GetOrigin(); + dstPos = dstBoneToWorld[i].GetOrigin(); + + QuaternionSlerp( dstQ, srcQ, s2, targetQ ); + targetBoneToWorld[i] = matrix3x4( dstPos, targetQ ); + + // back solve + if( n == -1 ) + { + q1[i] = targetBoneToWorld[i].GetQuaternion(); + } + else + { + matrix3x4 worldToBone = targetBoneToWorld[n].Invert(); + matrix3x4 local = worldToBone.ConcatTransforms( targetBoneToWorld[i] ); + q1[i] = local.GetQuaternion(); + + // blend bone lengths (local space) + InterpolateOrigin( pos1[i], pos2[i], pos1[i], s2 ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: build boneToWorld transforms for a specific bone +//----------------------------------------------------------------------------- +void CStudioBoneSetup :: BuildBoneChain( const matrix3x4 &rootxform, const Vector pos[], const Vector4D q[], int iBone, matrix3x4 *pBoneToWorld, byte *pBoneSet ) +{ + mstudiobone_t *pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + matrix3x4 bonematrix = matrix3x4( pos[iBone], q[iBone] ); + int iParent = pbones[iBone].parent; + + if( pBoneSet && pBoneSet[iBone] ) + return; + + if( iParent == -1 ) + { + pBoneToWorld[iBone] = rootxform.ConcatTransforms( bonematrix ); + } + else + { + // evil recursive!!! + BuildBoneChain( rootxform, pos, q, iParent, pBoneToWorld, pBoneSet ); + pBoneToWorld[iBone] = pBoneToWorld[iParent].ConcatTransforms( bonematrix ); + } + + if( pBoneSet ) pBoneSet[iBone] = 1; +} + +//----------------------------------------------------------------------------- +// Purpose: turn a specific bones boneToWorld transform into a pos and q in parents bonespace +//----------------------------------------------------------------------------- +void CStudioBoneSetup :: SolveBone( int iBone, matrix3x4 *pBoneToWorld, Vector pos[], Vector4D q[] ) +{ + mstudiobone_t *pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + int iParent = pbones[iBone].parent; + + matrix3x4 worldToBone = pBoneToWorld[iParent].Invert(); + matrix3x4 local = worldToBone.ConcatTransforms( pBoneToWorld[iBone] ); + + q[iBone] = local.GetQuaternion(); + pos[iBone] = local.GetOrigin(); +} + +//----------------------------------------------------------------------------- +// Purpose: for a 2 bone chain, find the IK solution and reset the matrices +//----------------------------------------------------------------------------- +bool CStudioBoneSetup :: SolveIK( const mstudioikchain_t *pikchain, Vector &targetFoot, matrix3x4 *pBoneToWorld ) +{ + const mstudioiklink_t *link0 = pIKLink( pikchain, 0 ); + const mstudioiklink_t *link1 = pIKLink( pikchain, 1 ); + const mstudioiklink_t *link2 = pIKLink( pikchain, 2 ); + + if( link0->kneeDir.LengthSqr() > 0.0f ) + { + Vector targetKneeDir, targetKneePos; + // FIXME: knee length should be as long as the legs + targetKneeDir = pBoneToWorld[link0->bone].VectorRotate( link0->kneeDir ); + targetKneePos = pBoneToWorld[link1->bone].GetOrigin(); + + return SolveIK( link0->bone, link1->bone, link2->bone, targetFoot, targetKneePos, targetKneeDir, pBoneToWorld ); + } + + return SolveIK( link0->bone, link1->bone, link2->bone, targetFoot, pBoneToWorld ); +} + +//----------------------------------------------------------------------------- +// Purpose: Solve Knee position for a known hip and foot location, but no specific knee direction preference +//----------------------------------------------------------------------------- +bool CStudioBoneSetup :: SolveIK( int iThigh, int iKnee, int iFoot, Vector &targetFoot, matrix3x4 *pBoneToWorld ) +{ + Vector worldFoot, worldKnee, worldThigh; + + worldThigh = pBoneToWorld[iThigh].GetOrigin(); + worldKnee = pBoneToWorld[iKnee].GetOrigin(); + worldFoot = pBoneToWorld[iFoot].GetOrigin(); + + // debugLine( worldThigh, worldKnee, 0, 0, 255, true, 0 ); + // debugLine( worldKnee, worldFoot, 0, 0, 255, true, 0 ); + + Vector ikFoot, ikKnee; + + ikFoot = targetFoot - worldThigh; + ikKnee = worldKnee - worldThigh; + + float l1 = (worldKnee - worldThigh).Length(); + float l2 = (worldFoot - worldKnee).Length(); + float l3 = (worldFoot - worldThigh).Length(); + + // leg too straight to figure out knee? + if( l3 > (l1 + l2) * KNEEMAX_EPSILON ) + { + return false; + } + + Vector ikHalf = (worldFoot-worldThigh) * (l1 / l3); + + // FIXME: what to do when the knee completely straight? + Vector ikKneeDir = (ikKnee - ikHalf).Normalize(); + + return SolveIK( iThigh, iKnee, iFoot, targetFoot, worldKnee, ikKneeDir, pBoneToWorld ); +} + +//----------------------------------------------------------------------------- +// Purpose: Realign the matrix so that its X axis points along the desired axis. +//----------------------------------------------------------------------------- +void CStudioBoneSetup :: AlignIKMatrix( matrix3x4 &mMat, const Vector &vAlignTo ) +{ + Vector tmp1, tmp2, tmp3; + + // Column 0 (X) becomes the vector. + tmp1 = vAlignTo.Normalize(); + mMat.SetForward( tmp1 ); + + // Column 1 (Y) is the cross of the vector and column 2 (Z). + tmp3 = mMat.GetUp(); + tmp2 = CrossProduct( tmp3, tmp1 ).Normalize(); + + // FIXME: check for X being too near to Z + mMat.SetRight( tmp2 ); + + // Column 2 (Z) is the cross of columns 0 (X) and 1 (Y). + tmp3 = CrossProduct( tmp1, tmp2 ); + mMat.SetUp( tmp3 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Solve Knee position for a known hip and foot location, and a known knee direction +//----------------------------------------------------------------------------- +bool CStudioBoneSetup :: SolveIK( int iThigh, int iKnee, int iFoot, Vector &targetFoot, Vector &targetKneePos, Vector &targetKneeDir, matrix3x4 *pBoneToWorld ) +{ + Vector worldFoot, worldKnee, worldThigh; + + worldThigh = pBoneToWorld[iThigh].GetOrigin(); + worldKnee = pBoneToWorld[iKnee].GetOrigin(); + worldFoot = pBoneToWorld[iFoot].GetOrigin(); + + // debugLine( worldThigh, worldKnee, 0, 0, 255, true, 0 ); + // debugLine( worldThigh, worldThigh + targetKneeDir, 0, 0, 255, true, 0 ); + // debugLine( worldKnee, targetKnee, 0, 0, 255, true, 0 ); + + Vector ikFoot, ikTargetKnee, ikKnee; + + ikFoot = targetFoot - worldThigh; + ikKnee = targetKneePos - worldThigh; + + float l1 = (worldKnee-worldThigh).Length(); + float l2 = (worldFoot-worldKnee).Length(); + + // exaggerate knee targets for legs that are nearly straight + // FIXME: should be configurable, and the ikKnee should be from the original animation, not modifed + float d = (targetFoot - worldThigh).Length() - Q_min( l1, l2 ); + d = Q_max( l1 + l2, d ); + // FIXME: too short knee directions cause trouble + d = d * 100.0f; + + ikTargetKnee = ikKnee + targetKneeDir * d; + + // debugLine( worldKnee, worldThigh + ikTargetKnee, 0, 0, 255, true, 0 ); + + int color[3] = { 0, 255, 0 }; + + // too far away? (0.9998 is about 1 degree) + if( ikFoot.Length() > ( l1 + l2 ) * KNEEMAX_EPSILON ) + { + ikFoot = ikFoot.Normalize(); + ikFoot *= (l1 + l2) * KNEEMAX_EPSILON; + color[0] = 255; color[1] = 0; color[2] = 0; + } + + // too close? + // limit distance to about an 80 degree knee bend + float minDist = Q_max( fabs( l1 - l2 ) * 1.15f, Q_min( l1, l2 ) * 0.15f ); + + if( ikFoot.Length() < minDist ) + { + // too close to get an accurate vector, just use original vector + ikFoot = (worldFoot - worldThigh); + ikFoot = ikFoot.Normalize(); + ikFoot *= minDist; + } + + CIKSolver ik; // heart of all inverse kinematics + + if( ik.solve( l1, l2, ikFoot, ikTargetKnee, ikKnee )) + { + matrix3x4& mWorldThigh = pBoneToWorld[iThigh]; + matrix3x4& mWorldKnee = pBoneToWorld[iKnee]; + matrix3x4& mWorldFoot = pBoneToWorld[iFoot]; + + // debugLine( worldThigh, ikKnee + worldThigh, 255, 0, 0, true, 0 ); + // debugLine( ikKnee + worldThigh, ikFoot + worldThigh, 255, 0, 0, true,0 ); + + // debugLine( worldThigh, ikKnee + worldThigh, color[0], color[1], color[2], true, 0 ); + // debugLine( ikKnee + worldThigh, ikFoot + worldThigh, color[0], color[1], color[2], true,0 ); + + // build transformation matrix for thigh + AlignIKMatrix( mWorldThigh, ikKnee ); + AlignIKMatrix( mWorldKnee, ikFoot - ikKnee ); + + mWorldKnee[3][0] = ikKnee.x + worldThigh.x; + mWorldKnee[3][1] = ikKnee.y + worldThigh.y; + mWorldKnee[3][2] = ikKnee.z + worldThigh.z; + + mWorldFoot[3][0] = ikFoot.x + worldThigh.x; + mWorldFoot[3][1] = ikFoot.y + worldThigh.y; + mWorldFoot[3][2] = ikFoot.z + worldThigh.z; + + return true; + } + else + { +#if 0 + debugLine( worldThigh, worldThigh + ikKnee, 255, 0, 0, true, 0 ); + debugLine( worldThigh + ikKnee, worldThigh + ikFoot, 255, 0, 0, true, 0 ); + debugLine( worldThigh + ikFoot, worldThigh, 255, 0, 0, true, 0 ); + debugLine( worldThigh + ikKnee, worldThigh + ikTargetKnee, 255, 0, 0, true, 0 ); +#endif + return false; + } +} + +//----------------------------------------------------------------------------- +// Purpose: calculate a pose for a single sequence +//----------------------------------------------------------------------------- +void CStudioBoneSetup :: InitPose( Vector pos[], Vector4D q[] ) +{ + mstudiobone_t *pbone = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + mstudioboneinfo_t *pboneinfo = (mstudioboneinfo_t *)((byte *)pbone + m_pStudioHeader->numbones * sizeof( mstudiobone_t )); + + for( int i = 0; i < m_pStudioHeader->numbones; i++, pbone++ ) + { + // skip unused bones + if( !IsBoneUsed( pbone + i )) + continue; + + // check if we can use aligned quaternion instead of euler angles + if( FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEINFO )) q[i] = pboneinfo[i].quat; + else AngleQuaternion( Radian( pbone->value[3], pbone->value[4], pbone->value[5] ), q[i] ); + pos[i] = Vector( pbone->value ); // grab three first values + } +} + +//----------------------------------------------------------------------------- +// Purpose: turn a 2x2 blend into a 3 way triangle blend +// Returns: returns the animination indices and barycentric coordinates of a triangle +// the triangle is a right triangle, and the diagonal is between elements [0] and [2] +//----------------------------------------------------------------------------- +void CStudioBoneSetup :: Calc9WayBlendIndices( int i0, int i1, float s0, float s1, const mstudioseqdesc_t *pseqdesc, int *pAnimIndices, float *pWeight ) +{ + // figure out which bi-section direction we are using to make triangles. + bool bEven = ((( i0 + i1 ) & 0x1 ) == 0 ); + int x1, y1; + int x2, y2; + int x3, y3; + + // diagonal is between elements 1 & 3 + + if( bEven ) + { + // TL to BR + if( s0 > s1 ) + { + // B + x1 = 0; y1 = 0; + x2 = 1; y2 = 0; + x3 = 1; y3 = 1; + pWeight[0] = (1.0f - s0); + pWeight[1] = s0 - s1; + } + else + { + // C + x1 = 1; y1 = 1; + x2 = 0; y2 = 1; + x3 = 0; y3 = 0; + pWeight[0] = s0; + pWeight[1] = s1 - s0; + } + } + else + { + float flTotal = s0 + s1; + + // BL to TR + if( flTotal > 1.0f ) + { + // D + x1 = 1; y1 = 0; + x2 = 1; y2 = 1; + x3 = 0; y3 = 1; + pWeight[0] = (1.0f - s1); + pWeight[1] = s0 - 1.0f + s1; + } + else + { + // A + x1 = 0; y1 = 1; + x2 = 0; y2 = 0; + x3 = 1; y3 = 0; + pWeight[0] = s1; + pWeight[1] = 1.0f - s0 - s1; + } + } + + pAnimIndices[0] = iAnimBlend( pseqdesc, i0 + x1, i1 + y1 ); + pAnimIndices[1] = iAnimBlend( pseqdesc, i0 + x2, i1 + y2 ); + pAnimIndices[2] = iAnimBlend( pseqdesc, i0 + x3, i1 + y3 ); + + // clamp the diagonal + if( pWeight[1] < 0.001f ) pWeight[1] = 0.0f; + pWeight[2] = 1.0f - pWeight[0] - pWeight[1]; +} + +//----------------------------------------------------------------------------- +// Purpose: Calculates default values for the pose parameters +// Output: fills in an array +//----------------------------------------------------------------------------- +void CStudioBoneSetup :: CalcDefaultPoseParameters( float flPoseParams[] ) +{ + int nPoseCount = CountPoseParameters(); + + for( int i = 0; i < MAXSTUDIOPOSEPARAM; i++ ) + { + // default to middle of the pose parameter range + flPoseParams[i] = 0.5f; + + if( i < nPoseCount ) + { + const mstudioposeparamdesc_t *pPose = pPoseParameter( i ); + + // want to try for a zero state. If one doesn't exist set it to .5 by default. + if( pPose->start < 0.0f && pPose->end > 0.0f ) + { + float flPoseDelta = pPose->end - pPose->start; + flPoseParams[i] = -pPose->start / flPoseDelta; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: resolve a global pose parameter to the specific setting for this sequence +//----------------------------------------------------------------------------- +void CStudioBoneSetup :: LocalPoseParameter( mstudioseqdesc_t *pseqdesc, int iLocalIndex, float &flSetting, int &index ) +{ + // first check for traditional GoldSource blenders + if( !FBitSet( pseqdesc->flags, STUDIO_BLENDPOSE )) + { + flSetting = m_flPoseParams[iLocalIndex]; + index = 0; // unused + return; + } + else + { + int iPose = pseqdesc->blendtype[iLocalIndex]; + + if( iPose == -1 ) + { + flSetting = 0; + index = 0; + return; + } + + const mstudioposeparamdesc_t *pPose = pPoseParameter( iPose ); + + if( pPose == NULL ) + { + flSetting = 0; + index = 0; + return; + } + + float flValue = m_flPoseParams[iPose]; + + if( pPose->loop ) + { + float wrap = (pPose->start + pPose->end) / 2.0 + pPose->loop / 2.0; + float shift = pPose->loop - wrap; + flValue = flValue - pPose->loop * floor((flValue + shift) / pPose->loop); + } + + if( pseqdesc->posekeyindex == 0 ) + { + float flLocalStart = ((float)pseqdesc->blendstart[iLocalIndex] - pPose->start) / (pPose->end - pPose->start); + float flLocalEnd = ((float)pseqdesc->blendend[iLocalIndex] - pPose->start) / (pPose->end - pPose->start); + + // convert into local range + flSetting = (flValue - flLocalStart) / (flLocalEnd - flLocalStart); + + // clamp. This shouldn't ever need to happen if it's looping. + flSetting = bound( 0.0f, flSetting, 1.0f ); + + index = 0; + if( pseqdesc->groupsize[iLocalIndex] > 2 ) + { + // estimate index + index = (int)(flSetting * (pseqdesc->groupsize[iLocalIndex] - 1)); + if( index == pseqdesc->groupsize[iLocalIndex] - 1 ) + index = pseqdesc->groupsize[iLocalIndex] - 2; + flSetting = flSetting * (pseqdesc->groupsize[iLocalIndex] - 1) - index; + } + } + else + { + flValue = flValue * (pPose->end - pPose->start) + pPose->start; + index = 0; + + // FIXME: this shouldn't be a linear search + while( 1 ) + { + flSetting = (flValue - flPoseKey( pseqdesc, iLocalIndex, index )); + flSetting /= (flPoseKey( pseqdesc, iLocalIndex, index + 1 ) - flPoseKey( pseqdesc, iLocalIndex, index )); + + if( index < pseqdesc->groupsize[iLocalIndex] - 2 && flSetting > 1.0f ) + { + index++; + continue; + } + break; + } + + // clamp. + flSetting = bound( 0.0f, flSetting, 1.0f ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: returns array of animations and weightings for a sequence based on current pose parameters +//----------------------------------------------------------------------------- + +void CStudioBoneSetup :: LocalSeqAnims( int sequence, mstudioanimdesc_t *panim[4], float *weight ) +{ + if( !m_pStudioHeader || sequence < 0 || sequence >= m_pStudioHeader->numseq ) + { + weight[0] = weight[1] = 0.0f; + weight[2] = weight[3] = 0.0f; + return; + } + + mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + sequence; + float s0 = 0.0f, s1 = 0.0f; + int i0 = 0, i1 = 0; + + LocalPoseParameter( pseqdesc, 0, s0, i0 ); + LocalPoseParameter( pseqdesc, 1, s1, i1 ); + + panim[0] = FetchAnimDesc( pseqdesc, iAnimBlend( pseqdesc, i0+0, i1+0 )); + weight[0] = (1.0f - s0) * (1.0f - s1); + + panim[1] = FetchAnimDesc( pseqdesc, iAnimBlend( pseqdesc, i0+1, i1+0 )); + weight[1] = (s0) * (1.0f - s1); + + panim[2] = FetchAnimDesc( pseqdesc, iAnimBlend( pseqdesc, i0+0, i1+1 )); + weight[2] = (1.0f - s0) * (s1); + + panim[3] = FetchAnimDesc( pseqdesc, iAnimBlend( pseqdesc, i0+1, i1+1 )); + weight[3] = (s0) * (s1); +} + +//----------------------------------------------------------------------------- +// Purpose: returns max frame number for a sequence +//----------------------------------------------------------------------------- +int CStudioBoneSetup :: LocalMaxFrame( int sequence ) +{ + mstudioanimdesc_t *panim[4]; + float weight[4]; + float maxFrame = 0; + + LocalSeqAnims( sequence, panim, weight ); + + for( int i = 0; i < 4; i++ ) + { + if( weight[i] > 0.0f ) + { + maxFrame += panim[i]->numframes * weight[i]; + } + } + + if( maxFrame > 1 ) + maxFrame -= 1; + + // FIXME: why does the weights sometimes not exactly add it 1.0 and this sometimes rounds down? + return (maxFrame + 0.01); +} + +//----------------------------------------------------------------------------- +// Purpose: returns frames per second of a sequence +//----------------------------------------------------------------------------- +float CStudioBoneSetup :: LocalFPS( int sequence ) +{ + mstudioanimdesc_t *panim[4]; + float weight[4]; + float t = 0.0f; + + LocalSeqAnims( sequence, panim, weight ); + + for( int i = 0; i < 4; i++ ) + { + if( weight[i] > 0.0f ) + { + t += panim[i]->fps * weight[i]; + } + } + + return t; +} + +//----------------------------------------------------------------------------- +// Purpose: returns cycles per second of a sequence (cycles/second) +//----------------------------------------------------------------------------- +float CStudioBoneSetup :: LocalCPS( int sequence ) +{ + mstudioanimdesc_t *panim[4]; + float weight[4]; + float t = 0.0f; + + LocalSeqAnims( sequence, panim, weight ); + + for( int i = 0; i < 4; i++ ) + { + if( weight[i] > 0.0f && panim[i]->numframes > 1 ) + { + t += (panim[i]->fps / (panim[i]->numframes - 1)) * weight[i]; + } + } + + return t; +} + +//----------------------------------------------------------------------------- +// Purpose: converts a ranged bone controller value into a 0..1 encoded value +// Output: ctlValue contains 0..1 encoding. +// returns clamped ranged value +//----------------------------------------------------------------------------- +float CStudioBoneSetup :: SetController( int iController, float flValue, float &ctlValue ) +{ + if( !m_pStudioHeader ) + return flValue; + + mstudiobonecontroller_t *pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bonecontrollerindex); + + // find first controller that matches the index + for( int i = 0; i < m_pStudioHeader->numbonecontrollers; i++, pbonecontroller++ ) + { + if( pbonecontroller->index == iController ) + break; + } + + if( i >= m_pStudioHeader->numbonecontrollers ) + { + ctlValue = 0.0f; + return flValue; + } + + // wrap 0..360 if it's a rotational controller + if( FBitSet( pbonecontroller->type, STUDIO_XR|STUDIO_YR|STUDIO_ZR )) + { + // ugly hack, invert value if end < start + if( pbonecontroller->end < pbonecontroller->start ) + flValue = -flValue; + + // does the controller not wrap? + if( pbonecontroller->start + 359.0f >= pbonecontroller->end ) + { + if( flValue > (( pbonecontroller->start + pbonecontroller->end) / 2.0f ) + 180.0f ) + flValue = flValue - 360.0f; + if( flValue < (( pbonecontroller->start + pbonecontroller->end) / 2.0f ) - 180.0f ) + flValue = flValue + 360.0f; + } + else + { + if( flValue > 360.0f ) + flValue = flValue - (int)(flValue / 360.0f) * 360.0f; + else if( flValue < 0.0f ) + flValue = flValue + (int)((flValue / -360.0f) + 1.0f) * 360.0f; + } + } + + ctlValue = (flValue - pbonecontroller->start) / (pbonecontroller->end - pbonecontroller->start); + ctlValue = bound( 0.0f, ctlValue, 1.0f ); + + float flReturnVal = ((1.0f - ctlValue) * pbonecontroller->start + ctlValue * pbonecontroller->end); + + // ugly hack, invert value if a rotational controller and end < start + if( FBitSet( pbonecontroller->type, STUDIO_XR | STUDIO_YR | STUDIO_ZR ) && pbonecontroller->end < pbonecontroller->start ) + { + flReturnVal *= -1.0f; + } + + return flReturnVal; +} + +//----------------------------------------------------------------------------- +// Purpose: converts a 0..1 encoded bone controller value into a ranged value +// Output: returns ranged value +//----------------------------------------------------------------------------- +float CStudioBoneSetup :: GetController( int iController, float ctlValue ) +{ + if( !m_pStudioHeader ) + return 0.0f; + + mstudiobonecontroller_t *pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bonecontrollerindex); + + // find first controller that matches the index + for( int i = 0; i < m_pStudioHeader->numbonecontrollers; i++, pbonecontroller++ ) + { + if( pbonecontroller->index == iController ) + break; + } + + if( i >= m_pStudioHeader->numbonecontrollers ) + return 0.0f; + + return ctlValue * (pbonecontroller->end - pbonecontroller->start) + pbonecontroller->start; +} + + +//----------------------------------------------------------------------------- +// Purpose: converts a ranged pose parameter value into a 0..1 encoded value +// Output: ctlValue contains 0..1 encoding. +// returns clamped ranged value +//----------------------------------------------------------------------------- +float CStudioBoneSetup :: SetPoseParameter( int iParameter, float flValue, float &ctlValue ) +{ + if( iParameter < 0 || iParameter >= CountPoseParameters( )) + return 0.0f; + + const mstudioposeparamdesc_t *pPose = pPoseParameter( iParameter ); + + if( pPose->loop ) + { + float wrap = (pPose->start + pPose->end) / 2.0f + pPose->loop / 2.0f; + float shift = pPose->loop - wrap; + flValue = flValue - pPose->loop * floor(( flValue + shift ) / pPose->loop ); + } + + ctlValue = (flValue - pPose->start) / (pPose->end - pPose->start); + ctlValue = bound( 0.0f, ctlValue, 1.0f ); + + return ctlValue * (pPose->end - pPose->start) + pPose->start; +} + + +//----------------------------------------------------------------------------- +// Purpose: converts a 0..1 encoded pose parameter value into a ranged value +// Output: returns ranged value +//----------------------------------------------------------------------------- +float CStudioBoneSetup :: GetPoseParameter( int iParameter, float ctlValue ) +{ + if( iParameter < 0 || iParameter >= CountPoseParameters( )) + return 0.0f; + + const mstudioposeparamdesc_t *pPose = pPoseParameter( iParameter ); + + return ctlValue * (pPose->end - pPose->start) + pPose->start; +} + +//----------------------------------------------------------------------------- +// Purpose: returns length (in seconds) of a sequence (seconds/cycle) +//----------------------------------------------------------------------------- +float CStudioBoneSetup :: LocalDuration( int sequence ) +{ + mstudioanimdesc_t *panim[4]; + float weight[4]; + float t = 0.0f; + + LocalSeqAnims( sequence, panim, weight ); + + for( int i = 0; i < 4; i++ ) + { + if( weight[i] > 0 && panim[i]->fps != 0.0f ) + { + t += ((panim[i]->numframes - 1) / panim[i]->fps) * weight[i]; + } + } + + return t; +} + +//----------------------------------------------------------------------------- +// Purpose: calculate changes in position and angle relative to the start of an animations cycle +// Output: updated position and angle, relative to the origin +// returns false if animation is not a movement animation +//----------------------------------------------------------------------------- +bool CStudioBoneSetup :: AnimPosition( mstudioanimdesc_t *panim, float flCycle, Vector &vecPos, Vector &vecAngle ) +{ + float prevframe = 0; + int iLoops = 0; + + vecPos.Init( ); + vecAngle.Init( ); + + if( panim->nummovements == 0 ) + return false; + + if( flCycle > 1.0f ) + iLoops = (int)flCycle; + else if( flCycle < 0.0f ) + iLoops = (int)flCycle - 1; + + flCycle = flCycle - iLoops; + + float flFrame = flCycle * (panim->numframes - 1); + + for( int i = 0; i < panim->nummovements; i++ ) + { + const mstudiomovement_t *pmove = pMovement( panim, i ); + + if( pmove->endframe >= flFrame ) + { + float f = (flFrame - prevframe) / (pmove->endframe - prevframe); + float d = pmove->v0 * f + 0.5 * (pmove->v1 - pmove->v0) * f * f; + + vecPos = vecPos + d * pmove->vector; + vecAngle.y = vecAngle.y * (1.0f - f) + pmove->angle * f; + + if( iLoops != 0 ) + { + const mstudiomovement_t *pmove = pMovement( panim, panim->nummovements - 1 ); + vecPos = vecPos + iLoops * pmove->position; + vecAngle.y = vecAngle.y + iLoops * pmove->angle; + } + return true; + } + else + { + prevframe = pmove->endframe; + vecPos = pmove->position; + vecAngle.y = pmove->angle; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: calculate instantaneous velocity in ips at a given point +// in the animations cycle +// Output: velocity vector, relative to identity orientation +// returns false if animation is not a movement animation +//----------------------------------------------------------------------------- +bool CStudioBoneSetup :: AnimVelocity( mstudioanimdesc_t *panim, float flCycle, Vector &vecVelocity ) +{ + float flFrame = flCycle * (panim->numframes - 1); + float prevframe = 0.0f; + + flFrame = flFrame - (int)(flFrame / (panim->numframes - 1)); + + for( int i = 0; i < panim->nummovements; i++ ) + { + const mstudiomovement_t *pmove = pMovement( panim, i ); + + if( pmove->endframe >= flFrame ) + { + float f = (flFrame - prevframe) / (pmove->endframe - prevframe); + float vel = pmove->v0 * (1 - f) + pmove->v1 * f; + + // scale from per block to per sec velocity + vel = vel * panim->fps / (pmove->endframe - prevframe); + vecVelocity = pmove->vector * vel; + + return true; + } + else + { + prevframe = pmove->endframe; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: calculate changes in position and angle between two points in an animation cycle +// Output: updated position and angle, relative to CycleFrom being at the origin +// returns false if animation is not a movement animation +//----------------------------------------------------------------------------- +bool CStudioBoneSetup :: AnimMovement( mstudioanimdesc_t *panim, float flCycleFrom, float flCycleTo, Vector &deltaPos, Vector &deltaAngle ) +{ + if( panim->nummovements == 0 ) + return false; + + Vector startPos; + Vector startA; + + AnimPosition( panim, flCycleFrom, startPos, startA ); + + Vector endPos; + Vector endA; + + AnimPosition( panim, flCycleTo, endPos, endA ); + + Vector tmp = endPos - startPos; + deltaAngle.y = endA.y - startA.y; + + deltaPos = VectorYawRotate( tmp, -startA.y ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: finds how much of an animation to play to move given linear distance +//----------------------------------------------------------------------------- +float CStudioBoneSetup :: FindAnimDistance( mstudioanimdesc_t *panim, float flDist ) +{ + float prevframe = 0; + + if( flDist <= 0 ) + return 0.0; + + for( int i = 0; i < panim->nummovements; i++ ) + { + const mstudiomovement_t *pmove = pMovement( panim, i ); + float flMove = (pmove->v0 + pmove->v1) * 0.5f; + + if( flMove >= flDist ) + { + float root1, root2; + + // d = V0 * t + 1/2 (V1-V0) * t^2 + if( SolveQuadratic( 0.5f * ( pmove->v1 - pmove->v0 ), pmove->v0, -flDist, root1, root2 )) + { + float cpf = 1.0f / (panim->numframes - 1); // cycles per frame + return (prevframe + root1 * (pmove->endframe - prevframe)) * cpf; + } + return 0.0f; + } + else + { + flDist -= flMove; + prevframe = pmove->endframe; + } + } + + return 1.0f; +} + +//----------------------------------------------------------------------------- +// Purpose: calculate changes in position and angle between two points in a sequences cycle +// Output: updated position and angle, relative to CycleFrom being at the origin +// returns false if sequence is not a movement sequence +//----------------------------------------------------------------------------- +bool CStudioBoneSetup :: SeqMovement( int sequence, float flCycleFrom, float flCycleTo, Vector &deltaPos, Vector &deltaAngles ) +{ + mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + sequence; + mstudioanimdesc_t *panim[4]; + float weight[4]; + + LocalSeqAnims( sequence, panim, weight ); + + deltaPos = g_vecZero; + deltaAngles = g_vecZero; + + bool found = false; + + for( int i = 0; i < 4; i++ ) + { + if( weight[i] ) + { + Vector localPos = g_vecZero; + Vector localAngles = g_vecZero; + + const float *pweight = pBoneweight( pseqdesc ); + + if( AnimMovement( panim[i], flCycleFrom, flCycleTo, localPos, localAngles )) + { + found = true; + deltaPos = deltaPos + localPos * weight[i]; + // FIXME: this makes no sense + deltaAngles = deltaAngles + localAngles * weight[i]; + } + else if( !FBitSet( panim[i]->flags, STUDIO_DELTA ) && panim[i]->nummovements == 0 && pweight[0] > 0.0f ) + { +// found = true; + } + } + } + + // simple movement from GoldSource + if( !found && pseqdesc->linearmovement != g_vecZero ) + { + Vector startPos = pseqdesc->linearmovement * flCycleFrom; + Vector endPos = pseqdesc->linearmovement * flCycleTo; + + deltaPos = endPos - startPos; + found = true; + } + + return found; +} + +//----------------------------------------------------------------------------- +// Purpose: calculate instantaneous velocity in ips at a given point in the sequence's cycle +// Output: velocity vector, relative to identity orientation +// returns false if sequence is not a movement sequence +//----------------------------------------------------------------------------- +bool CStudioBoneSetup :: SeqVelocity( int sequence, float flCycle, Vector &vecVelocity ) +{ + mstudioanimdesc_t *panim[4]; + float weight[4]; + + LocalSeqAnims( sequence, panim, weight ); + + vecVelocity = g_vecZero; + bool found = false; + + for( int i = 0; i < 4; i++ ) + { + if( weight[i] ) + { + Vector vecLocalVelocity; + + if( AnimVelocity( panim[i], flCycle, vecLocalVelocity )) + { + vecVelocity = vecVelocity + vecLocalVelocity * weight[i]; + found = true; + } + } + } + + return found; +} + +//----------------------------------------------------------------------------- +// Purpose: finds how much of an sequence to play to move given linear distance +//----------------------------------------------------------------------------- +float CStudioBoneSetup :: FindSeqDistance( int sequence, float flDist ) +{ + mstudioanimdesc_t *panim[4]; + float weight[4]; + + LocalSeqAnims( sequence, panim, weight ); + + float flCycle = 0; + + for( int i = 0; i < 4; i++ ) + { + if( weight[i] ) + { + float flLocalCycle = FindAnimDistance( panim[i], flDist ); + flCycle = flCycle + flLocalCycle * weight[i]; + } + } + + return flCycle; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CStudioBoneSetup :: IKRuleWeight( const mstudioikrule_t *ikRule, const mstudioanimdesc_t *panim, float flCycle, int &iFrame, float &fraq ) +{ + if( ikRule->end > 1.0f && flCycle < ikRule->start ) + { + flCycle = flCycle + 1.0f; + } + + float value = 0.0f; + fraq = (panim->numframes - 1) * (flCycle - ikRule->start) + ikRule->iStart; + iFrame = (int)fraq; + fraq = fraq - iFrame; + + if( flCycle < ikRule->start ) + { + iFrame = ikRule->iStart; + fraq = 0.0f; + return 0.0f; + } + else if( flCycle < ikRule->peak ) + { + value = ( flCycle - ikRule->start ) / ( ikRule->peak - ikRule->start ); + } + else if( flCycle < ikRule->tail ) + { + return 1.0f; + } + else if( flCycle < ikRule->end ) + { + value = 1.0f - (( flCycle - ikRule->tail ) / ( ikRule->end - ikRule->tail )); + } + else + { + fraq = (panim->numframes - 1) * (ikRule->end - ikRule->start) + ikRule->iStart; + iFrame = (int)fraq; + fraq = fraq - iFrame; + } + + return SimpleSpline( value ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CStudioBoneSetup :: IKRuleWeight( ikcontextikrule_t *ikRule, float flCycle ) +{ + if( ikRule->end > 1.0f && flCycle < ikRule->start ) + { + flCycle = flCycle + 1.0f; + } + + float value = 0.0f; + + if( flCycle < ikRule->start ) + { + return 0.0f; + } + else if( flCycle < ikRule->peak ) + { + value = ( flCycle - ikRule->start ) / ( ikRule->peak - ikRule->start ); + } + else if( flCycle < ikRule->tail ) + { + return 1.0f; + } + else if( flCycle < ikRule->end ) + { + value = 1.0f - ((flCycle - ikRule->tail) / (ikRule->end - ikRule->tail)); + } + + return SimpleSpline( value ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CStudioBoneSetup :: IKShouldLatch( ikcontextikrule_t *ikRule, float flCycle ) +{ + if( ikRule->end > 1.0f && flCycle < ikRule->start ) + { + flCycle = flCycle + 1.0f; + } + + if( flCycle < ikRule->peak ) + { + return false; + } + else if( flCycle < ikRule->end ) + { + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +float CStudioBoneSetup :: IKTail( ikcontextikrule_t *ikRule, float flCycle ) +{ + if( ikRule->end > 1.0f && flCycle < ikRule->start ) + { + flCycle = flCycle + 1.0f; + } + + if( flCycle <= ikRule->tail ) + { + return 0.0f; + } + else if( flCycle < ikRule->end ) + { + return (( flCycle - ikRule->tail ) / ( ikRule->end - ikRule->tail )); + } + + return 0.0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CStudioBoneSetup :: IKAnimError( const mstudioikrule_t *pRule, mstudioanimdesc_t *panim, float flCycle, Vector &pos, Vector4D &q, float &flWeight ) +{ + float fraq; + int iFrame; + + flWeight = IKRuleWeight( pRule, panim, flCycle, iFrame, fraq ); + flWeight = bound( 0.0f, flWeight, 1.0f ); + + if( pRule->type != IK_GROUND && flWeight < 0.0001f ) + return false; + + const mstudioikerror_t *pError = pCompressedError( pRule ); + + if( pError != NULL ) + { + CalcIKError( pError, iFrame - pRule->iStart, fraq, pos, q ); + return true; + } + + // no data, disable IK rule + flWeight = 0.0f; + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: For a specific sequence:rule, find where it starts, stops, and what +// the estimated offset from the connection point is. +// return true if the rule is within bounds. +//----------------------------------------------------------------------------- +bool CStudioBoneSetup :: IKSequenceError( int iSeq, float flCycle, int iRule, mstudioanimdesc_t *panim[4], float weight[4], ikcontextikrule_t *ikRule ) +{ + int i; + + memset( ikRule, 0, sizeof( ikcontextikrule_t )); + ikRule->start = ikRule->peak = ikRule->tail = ikRule->end = 0; + + const mstudioikrule_t *prevRule = NULL; + + // find overall influence + for( i = 0; i < 4; i++ ) + { + if( weight[i] ) + { + if( iRule >= panim[i]->numikrules || panim[i]->numikrules != panim[0]->numikrules ) + return false; + + const mstudioikrule_t *pRule = pIKRule( panim[i], iRule ); + + if( pRule == NULL ) + return false; + + float dt = 0.0f; + + if( prevRule != NULL ) + { + if( pRule->start - prevRule->start > 0.5f ) + { + dt = -1.0f; + } + else if( pRule->start - prevRule->start < -0.5f ) + { + dt = 1.0; + } + } + else + { + prevRule = pRule; + } + + ikRule->start += (pRule->start + dt) * weight[i]; + ikRule->peak += (pRule->peak + dt) * weight[i]; + ikRule->tail += (pRule->tail + dt) * weight[i]; + ikRule->end += (pRule->end + dt) * weight[i]; + } + } + + if( ikRule->start > 1.0f ) + { + ikRule->start -= 1.0f; + ikRule->peak -= 1.0f; + ikRule->tail -= 1.0f; + ikRule->end -= 1.0f; + } + else if( ikRule->start < 0.0f ) + { + ikRule->start += 1.0f; + ikRule->peak += 1.0f; + ikRule->tail += 1.0f; + ikRule->end += 1.0f; + } + + ikRule->flWeight = IKRuleWeight( ikRule, flCycle ); + + if( ikRule->flWeight <= 0.001f ) + { + // go ahead and allow IK_GROUND rules a virtual looping section + if( pIKRule( panim[0], iRule ) == NULL ) + return false; + + if(( panim[0]->flags & STUDIO_LOOPING ) && pIKRule( panim[0], iRule )->type == IK_GROUND && ikRule->end - ikRule->start > 0.75f ) + { + ikRule->flWeight = 0.001f; + flCycle = ikRule->end - 0.001f; + } + else + { + return false; + } + } + + ikRule->pos.Init(); + ikRule->q.Init(); + + // find target error + float total = 0.0f; + for( i = 0; i < 4; i++ ) + { + if( weight[i] ) + { + Vector pos1; + Vector4D q1; + float w; + + const mstudioikrule_t *pRule = pIKRule( panim[i], iRule ); + if( pRule == NULL ) + return false; + + ikRule->chain = pRule->chain; // FIXME: this is anim local + ikRule->bone = pRule->bone; // FIXME: this is anim local + ikRule->type = pRule->type; + ikRule->slot = pRule->slot; + + ikRule->height += pRule->height * weight[i]; + ikRule->floor += pRule->floor * weight[i]; + ikRule->radius += pRule->radius * weight[i]; + ikRule->drop += pRule->drop * weight[i]; + ikRule->top += pRule->top * weight[i]; + + // keep track of tail condition + ikRule->release += IKTail( ikRule, flCycle ) * weight[i]; + + // only check rules with error values + switch( ikRule->type ) + { + case IK_SELF: + case IK_WORLD: + case IK_GROUND: + case IK_ATTACHMENT: + if( IKAnimError( pRule, panim[i], flCycle, pos1, q1, w )) + { + ikRule->pos = ikRule->pos + pos1 * weight[i]; + QuaternionAccumulate( ikRule->q, weight[i], q1, ikRule->q ); + total += weight[i]; + } + break; + default: + total += weight[i]; + break; + } + + ikRule->latched = IKShouldLatch( ikRule, flCycle ) * ikRule->flWeight; + + if( ikRule->type == IK_ATTACHMENT ) + { + ikRule->iAttachment = pRule->attachment; + } + } + } + + if( total <= 0.0001f ) + { + return false; + } + + if( total < 0.999f ) + { + QuaternionScale( ikRule->q, 1.0f / total, ikRule->q ); + ikRule->pos *= ( 1.0f / total ); + } + + ikRule->q = ikRule->q.Normalize(); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: calculate a pose for a single sequence +//----------------------------------------------------------------------------- +void CStudioBoneSetup :: CalcPoseSingle( Vector pos[], Vector4D q[], int sequence, float cycle ) +{ + static Vector pos2[MAXSTUDIOBONES]; + static Vector4D q2[MAXSTUDIOBONES]; + static Vector pos3[MAXSTUDIOBONES]; + static Vector4D q3[MAXSTUDIOBONES]; + static Vector pos4[MAXSTUDIOBONES]; + static Vector4D q4[MAXSTUDIOBONES]; + bool anim_4wayblend = true; // FIXME: get 9-way for gold-source + mstudioseqdesc_t *pseqdesc; + + if( sequence < 0 || sequence >= m_pStudioHeader->numseq ) + { + debugMsg( "^2Warning:^7 sequence %i/%i out of range for model %s\n", sequence, m_pStudioHeader->numseq, m_pStudioHeader->name ); + sequence = 0; + } + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + sequence; + + int i0 = 0, i1 = 0; + float s0 = 0, s1 = 0; + + LocalPoseParameter( pseqdesc, 0, s0, i0 ); + LocalPoseParameter( pseqdesc, 1, s1, i1 ); + + if( FBitSet( pseqdesc->flags, STUDIO_REALTIME )) + { + float cps = LocalCPS( sequence ); + cycle = m_flTime * cps; + cycle = cycle - (int)cycle; + } + else if( FBitSet( pseqdesc->flags, STUDIO_CYCLEPOSE )) + { + int iPose = bound( 0, pseqdesc->cycleposeindex, MAXSTUDIOPOSEPARAM - 1 ); + cycle = m_flPoseParams[iPose]; + } + else if( cycle < 0.0f || cycle >= 1.0f ) + { + if( FBitSet( pseqdesc->flags, STUDIO_LOOPING )) + { + cycle = cycle - (int)cycle; + if( cycle < 0.0f ) cycle += 1.0f; + } + else + { + cycle = bound( 0.0f, cycle, 1.0f ); + } + } + + // GoldSource blending + if( !FBitSet( pseqdesc->flags, STUDIO_BLENDPOSE ) && ( pseqdesc->numblends > 1 )) + { + if( pseqdesc->numblends == 9 ) + { + // blending is 0 - 0.5 == Left to Middle, 0.5 to 1.0 == Middle to Right + if( s1 <= 0.5f ) + { + // Scale 0-0.5 blending up to 0-1.0 + s1 = ( s1 * 2.0f ); + + if( s0 <= 0.5f ) + { + // Blending is 0-127 == Top to Middle, 128 to 255 == Middle to Bottom + s0 = ( s0 * 2.0f ); + + // need to blend 0 - 1 - 3 - 4 + CalcAnimation( pos, q, pseqdesc, 0, cycle ); + CalcAnimation( pos2, q2, pseqdesc, 1, cycle ); + CalcAnimation( pos3, q3, pseqdesc, 3, cycle ); + CalcAnimation( pos4, q4, pseqdesc, 4, cycle ); + } + else + { + // Scale 0.5-1.0 blending up to 0.0-1.0 + s0 = 2.0f * ( s0 - 0.5f ); + + // need to blend 3 - 4 - 6 - 7 + CalcAnimation( pos, q, pseqdesc, 3, cycle ); + CalcAnimation( pos2, q2, pseqdesc, 4, cycle ); + CalcAnimation( pos3, q3, pseqdesc, 6, cycle ); + CalcAnimation( pos4, q4, pseqdesc, 7, cycle ); + } + } + else + { + // Scale 0.5-1.0 blending up to 0-1.0 + s1 = 2.0f * ( s1 - 0.5f ); + + if ( s0 <= 0.5f ) + { + // Blending is 0-0.5 == Top to Middle, 0.5 to 1.0 == Middle to Bottom + s0 = ( s0 * 2.0f ); + + // need to blend 1 - 2 - 4 - 5 + CalcAnimation( pos, q, pseqdesc, 1, cycle ); + CalcAnimation( pos2, q2, pseqdesc, 2, cycle ); + CalcAnimation( pos3, q3, pseqdesc, 4, cycle ); + CalcAnimation( pos4, q4, pseqdesc, 5, cycle ); + } + else + { + // Scale 0.5-1.0 blending up to 0-1.0 + s0 = 2.0 * ( s0 - 0.5 ); + + // need to blend 4 - 5 - 7 - 8 + CalcAnimation( pos, q, pseqdesc, 4, cycle ); + CalcAnimation( pos2, q2, pseqdesc, 5, cycle ); + CalcAnimation( pos3, q3, pseqdesc, 7, cycle ); + CalcAnimation( pos4, q4, pseqdesc, 8, cycle ); + } + } + + // Spherically interpolate the bones + SlerpBones( q, pos, pseqdesc, q2, pos2, s1 ); + SlerpBones( q3, pos3, pseqdesc, q4, pos4, s1 ); + SlerpBones( q, pos, pseqdesc, q3, pos3, s0 ); + } + else + { + CalcAnimation( pos, q, pseqdesc, 0, cycle ); + CalcAnimation( pos2, q2, pseqdesc, 1, cycle ); + BlendBones( q, pos, pseqdesc, q2, pos2, s0 ); + + if( pseqdesc->numblends == 4 ) + { + CalcAnimation( pos3, q3, pseqdesc, 2, cycle ); + CalcAnimation( pos4, q4, pseqdesc, 3, cycle ); + BlendBones( q3, pos3, pseqdesc, q4, pos4, s0 ); + BlendBones( q, pos, pseqdesc, q3, pos3, s1 ); + } + } + return; + } + + if( s0 < 0.001f ) + { + if( s1 < 0.001f ) + { + CalcAnimation( pos, q, pseqdesc, iAnimBlend( pseqdesc, i0+0, i1+0 ), cycle ); + } + else if( s1 > 0.999f ) + { + CalcAnimation( pos, q, pseqdesc, iAnimBlend( pseqdesc, i0+0, i1+1 ), cycle ); + } + else + { + CalcAnimation( pos, q, pseqdesc, iAnimBlend( pseqdesc, i0+0, i1+0 ), cycle ); + CalcAnimation( pos2, q2, pseqdesc, iAnimBlend( pseqdesc, i0+0, i1+1 ), cycle ); + BlendBones( q, pos, pseqdesc, q2, pos2, s1 ); + } + } + else if( s0 > 0.999f ) + { + if( s1 < 0.001f ) + { + CalcAnimation( pos, q, pseqdesc, iAnimBlend( pseqdesc, i0+1, i1+0 ), cycle ); + } + else if( s1 > 0.999f ) + { + CalcAnimation( pos, q, pseqdesc, iAnimBlend( pseqdesc, i0+1, i1+1 ), cycle ); + } + else + { + CalcAnimation( pos, q, pseqdesc, iAnimBlend( pseqdesc, i0+1, i1+0 ), cycle ); + CalcAnimation( pos2, q2, pseqdesc, iAnimBlend( pseqdesc, i0+1, i1+1 ), cycle ); + BlendBones( q, pos, pseqdesc, q2, pos2, s1 ); + } + } + else + { + if( s1 < 0.001f ) + { + CalcAnimation( pos, q, pseqdesc, iAnimBlend( pseqdesc, i0+0, i1+0 ), cycle ); + CalcAnimation( pos2, q2, pseqdesc, iAnimBlend( pseqdesc, i0+1, i1+0 ), cycle ); + BlendBones( q, pos, pseqdesc, q2, pos2, s0 ); + } + else if( s1 > 0.999f ) + { + CalcAnimation( pos, q, pseqdesc, iAnimBlend( pseqdesc, i0+0, i1+1 ), cycle ); + CalcAnimation( pos2, q2, pseqdesc, iAnimBlend( pseqdesc, i0+1, i1+1 ), cycle ); + BlendBones( q, pos, pseqdesc, q2, pos2, s0 ); + } + else if( anim_4wayblend ) + { + CalcAnimation( pos, q, pseqdesc, iAnimBlend( pseqdesc, i0+0, i1+0 ), cycle ); + CalcAnimation( pos2, q2, pseqdesc, iAnimBlend( pseqdesc, i0+1, i1+0 ), cycle ); + BlendBones( q, pos, pseqdesc, q2, pos2, s0 ); + + CalcAnimation( pos2, q2, pseqdesc, iAnimBlend( pseqdesc, i0+0, i1+1 ), cycle ); + CalcAnimation( pos3, q3, pseqdesc, iAnimBlend( pseqdesc, i0+1, i1+1 ), cycle ); + BlendBones( q2, pos2, pseqdesc, q3, pos3, s0 ); + + BlendBones( q, pos, pseqdesc, q2, pos2, s1 ); + } + else + { + int iAnimIndices[3]; + float weight[3]; + + Calc9WayBlendIndices( i0, i1, s0, s1, pseqdesc, iAnimIndices, weight ); + + if( weight[1] < 0.001f ) + { + // on diagonal + CalcAnimation( pos, q, pseqdesc, iAnimIndices[0], cycle ); + CalcAnimation( pos2, q2, pseqdesc, iAnimIndices[2], cycle ); + BlendBones( q, pos, pseqdesc, q2, pos2, weight[2] / ( weight[0] + weight[2] )); + } + else + { + CalcAnimation( pos, q, pseqdesc, iAnimIndices[0], cycle ); + CalcAnimation( pos2, q2, pseqdesc, iAnimIndices[1], cycle ); + BlendBones( q, pos, pseqdesc, q2, pos2, weight[1] / ( weight[0] + weight[1] )); + + CalcAnimation( pos3, q3, pseqdesc, iAnimIndices[2], cycle ); + BlendBones( q, pos, pseqdesc, q3, pos3, weight[2] ); + } + } + } + + // list is cleared + SetBoneControllers( NULL ); +} + +//----------------------------------------------------------------------------- +// Purpose: calculate a pose for a single sequence +// adds autolayers, runs local ik rukes +//----------------------------------------------------------------------------- +void CStudioBoneSetup :: AddSequenceLayers( CIKContext *pIKContext, Vector pos[], Vector4D q[], int sequence, float cycle, float flWeight ) +{ + mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + sequence; + + for( int i = 0; i < pseqdesc->numautolayers; i++ ) + { + mstudioautolayer_t *pLayer = (mstudioautolayer_t *)((byte *)m_pStudioHeader + pseqdesc->autolayerindex) + i; + + if( FBitSet( pLayer->flags, STUDIO_AL_LOCAL )) + continue; + + float layerCycle = cycle; + float layerWeight = flWeight; + + if( pLayer->start != pLayer->end ) + { + float s = 1.0; + float index; + + if( !FBitSet( pLayer->flags, STUDIO_AL_POSE )) + { + index = cycle; + } + else + { + int iSequence = pLayer->iSequence; + int iPose = pLayer->iPose; + + if( iPose != -1 ) + { + const mstudioposeparamdesc_t *pPose = pPoseParameter( iPose ); + index = m_flPoseParams[iPose] * (pPose->end - pPose->start) + pPose->start; + } + else + { + index = 0; + } + } + + if( index < pLayer->start ) + continue; + if( index >= pLayer->end ) + continue; + + if( index < pLayer->peak && pLayer->start != pLayer->peak ) + { + s = (index - pLayer->start) / (pLayer->peak - pLayer->start); + } + else if( index > pLayer->tail && pLayer->end != pLayer->tail ) + { + s = (pLayer->end - index) / (pLayer->end - pLayer->tail); + } + + if( FBitSet( pLayer->flags, STUDIO_AL_SPLINE )) + { + s = SimpleSpline( s ); + } + + if( FBitSet( pLayer->flags, STUDIO_AL_XFADE ) && ( index > pLayer->tail )) + { + layerWeight = ( s * flWeight ) / ( 1.0f - flWeight + s * flWeight ); + } + else if( FBitSet( pLayer->flags, STUDIO_AL_NOBLEND )) + { + layerWeight = s; + } + else + { + layerWeight = flWeight * s; + } + + if( !FBitSet( pLayer->flags, STUDIO_AL_POSE )) + { + layerCycle = (cycle - pLayer->start) / (pLayer->end - pLayer->start); + } + } + + AccumulatePose( pIKContext, pos, q, pLayer->iSequence, layerCycle, layerWeight ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: calculate a pose for a single sequence +// adds autolayers, runs local ik rukes +//----------------------------------------------------------------------------- +void CStudioBoneSetup :: AddLocalLayers( CIKContext *pIKContext, Vector pos[], Vector4D q[], int sequence, float cycle, float flWeight ) +{ + mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + sequence; + + if( !FBitSet( pseqdesc->flags, STUDIO_LOCAL )) + { + return; + } + + for( int i = 0; i < pseqdesc->numautolayers; i++ ) + { + mstudioautolayer_t *pLayer = (mstudioautolayer_t *)((byte *)m_pStudioHeader + pseqdesc->autolayerindex) + i; + + if( !FBitSet( pLayer->flags, STUDIO_AL_LOCAL )) + continue; + + float layerCycle = cycle; + float layerWeight = flWeight; + + if( pLayer->start != pLayer->end ) + { + float s = 1.0f; + + if( cycle < pLayer->start ) + continue; + + if( cycle >= pLayer->end ) + continue; + + if( cycle < pLayer->peak && pLayer->start != pLayer->peak ) + { + s = (cycle - pLayer->start) / (pLayer->peak - pLayer->start); + } + else if( cycle > pLayer->tail && pLayer->end != pLayer->tail ) + { + s = (pLayer->end - cycle) / (pLayer->end - pLayer->tail); + } + + if( FBitSet( pLayer->flags, STUDIO_AL_SPLINE )) + { + s = SimpleSpline( s ); + } + + if( FBitSet( pLayer->flags, STUDIO_AL_XFADE ) && ( cycle > pLayer->tail )) + { + layerWeight = ( s * flWeight ) / ( 1.0f - flWeight + s * flWeight ); + } + else if( FBitSet( pLayer->flags, STUDIO_AL_NOBLEND )) + { + layerWeight = s; + } + else + { + layerWeight = flWeight * s; + } + + layerCycle = (cycle - pLayer->start) / (pLayer->end - pLayer->start); + } + + AccumulatePose( pIKContext, pos, q, pLayer->iSequence, layerCycle, layerWeight ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: accumulate a pose for a single sequence on top of existing animation +// adds autolayers, runs local ik rukes +//----------------------------------------------------------------------------- +void CStudioBoneSetup :: AccumulatePose( CIKContext *pIKContext, Vector pos[], Vector4D q[], int sequence, float cycle, float flWeight ) +{ + Vector pos2[MAXSTUDIOBONES]; + Vector4D q2[MAXSTUDIOBONES]; + + flWeight = bound( 0.0f, flWeight, 1.0f ); + if( sequence < 0 ) return; + + mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + sequence; + + // add any IK locks to prevent extremities from moving + CIKContext seq_ik; + + if( pseqdesc->numiklocks ) + { + // local space relative so absolute position doesn't mater + seq_ik.Init( this, g_vecZero, g_vecZero, 0.0f, 0 ); + seq_ik.AddSequenceLocks( pseqdesc, pos, q ); + } + + if( FBitSet( pseqdesc->flags, STUDIO_LOCAL )) + { + InitPose( pos2, q2 ); + } + + CalcPoseSingle( pos2, q2, sequence, cycle ); + + // this weight is wrong, the IK rules won't composite at the correct intensity + AddLocalLayers( pIKContext, pos2, q2, sequence, cycle, 1.0 ); + SlerpBones( q, pos, pseqdesc, q2, pos2, flWeight ); + + if( pIKContext ) + { + pIKContext->AddDependencies( pseqdesc, sequence, cycle, flWeight ); + } + + AddSequenceLayers( pIKContext, pos, q, sequence, cycle, flWeight ); + + if( pseqdesc->numiklocks ) + { + seq_ik.SolveSequenceLocks( pseqdesc, pos, q ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: blend together q1,pos1 with q2,pos2. Return result in q1,pos1. +// 0 returns q1, pos1. 1 returns q2, pos2 +//----------------------------------------------------------------------------- +void CStudioBoneSetup :: CalcBoneAdj( Vector pos[], Vector4D q[], const byte controllers[], byte mouthopen ) +{ + mstudiobonecontroller_t *pbonecontroller; + int i, j, k; + float value; + Vector p0; + Radian a0; + Vector4D q0; + + for( j = 0; j < m_pStudioHeader->numbonecontrollers; j++ ) + { + pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bonecontrollerindex) + j; + k = pbonecontroller->bone; + + if( IsBoneUsed( k )) + { + i = pbonecontroller->index; + + if( i == STUDIO_MOUTH ) + value = bound( 0.0f, ( mouthopen / 64.0f ), 1.0f ); + else value = bound( 0.0f, (float)controllers[i] / 255.0f, 1.0f ); + value = (1.0f - value) * pbonecontroller->start + value * pbonecontroller->end; + + switch( pbonecontroller->type & STUDIO_TYPES ) + { + case STUDIO_XR: + a0.Init( DEG2RAD( value ), 0.0f, 0.0f ); + AngleQuaternion( a0, q0 ); + QuaternionSM( 1.0f, q0, q[k], q[k] ); + break; + case STUDIO_YR: + a0.Init( 0.0f, DEG2RAD( value ), 0.0f ); + AngleQuaternion( a0, q0 ); + QuaternionSM( 1.0f, q0, q[k], q[k] ); + break; + case STUDIO_ZR: + a0.Init( 0.0f, 0.0f, DEG2RAD( value )); + AngleQuaternion( a0, q0 ); + QuaternionSM( 1.0f, q0, q[k], q[k] ); + break; + case STUDIO_X: + pos[k].x += value; + break; + case STUDIO_Y: + pos[k].y += value; + break; + case STUDIO_Z: + pos[k].z += value; + break; + } + } + } +} + +void CStudioBoneSetup :: CalcBoneAdj( float adj[], const byte controllers[], byte mouthopen ) +{ + mstudiobonecontroller_t *pbonecontroller; + int i, j, k; + float value; + Vector p0; + Radian a0; + Vector4D q0; + + for( j = 0; j < m_pStudioHeader->numbonecontrollers; j++ ) + { + pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bonecontrollerindex) + j; + k = pbonecontroller->bone; + + if( IsBoneUsed( k )) + { + i = pbonecontroller->index; + + if( i == STUDIO_MOUTH ) + value = bound( 0.0f, ( mouthopen / 64.0f ), 1.0f ); + else value = bound( 0.0f, (float)controllers[i] / 255.0f, 1.0f ); + value = (1.0f - value) * pbonecontroller->start + value * pbonecontroller->end; + + switch( pbonecontroller->type & STUDIO_TYPES ) + { + case STUDIO_YR: + case STUDIO_ZR: + case STUDIO_XR: + adj[j] = DEG2RAD( value ); + break; + case STUDIO_X: + case STUDIO_Y: + case STUDIO_Z: + adj[j] = value; + break; + } + } + } + + // list is installed + SetBoneControllers( adj ); +} + +//----------------------------------------------------------------------------- +// Purpose: run all animations that automatically play and are driven off of poseParameters +//----------------------------------------------------------------------------- +void CStudioBoneSetup :: CalcAutoplaySequences( CIKContext *pIKContext, Vector pos[], Vector4D q[] ) +{ + if( pIKContext ) + { + pIKContext->AddAutoplayLocks( pos, q ); + } + + for( int i = 0; i < m_pStudioHeader->numseq; i++ ) + { + mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + i; + + if( !FBitSet( pseqdesc->flags, STUDIO_AUTOPLAY )) + continue; + + float cps = LocalCPS( i ); + float cycle = m_flTime * cps; + cycle = cycle - (int)cycle; + + AccumulatePose( NULL, pos, q, i, cycle, 1.0 ); + } + + if( pIKContext ) + { + pIKContext->SolveAutoplayLocks( pos, q ); + } +} \ No newline at end of file diff --git a/game_shared/bs_defs.h b/game_shared/bs_defs.h new file mode 100644 index 0000000..87f6dd1 --- /dev/null +++ b/game_shared/bs_defs.h @@ -0,0 +1,378 @@ +/* +bs_desf.h - Bone Setup defines +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef BS_DEFS_H +#define BS_DEFS_H + +#include + +struct ikcontextikrule_t; +class CIKContext; + +/* +==================== +CStudioBoneSetup + +==================== +*/ +class CStudioBoneSetup +{ +public: + CStudioBoneSetup() + { + InitBoneWeights(); + m_pStudioHeader = NULL; + m_flBoneControllers = NULL; + m_flPoseParams = NULL; + m_iBoneMask = 0; + } +//protected: + const mstudioanimvalue_t *pAnimvalue( const mstudioanim_t *panim, int dof ) + { + if( !panim || panim->offset[dof] == 0 ) + return NULL; + return (mstudioanimvalue_t *)((byte *)panim + panim->offset[dof]); + } + + const mstudioanimvalue_t *pAnimvalue( const mstudioikerror_t *panim, int dof ) + { + if( !panim || panim->offset[dof] == 0 ) + return NULL; + return (mstudioanimvalue_t *)((byte *)panim + panim->offset[dof]); + } + + const mstudiomovement_t *pMovement( const mstudioanimdesc_t *panim, int movement ) + { + if( !panim || panim->nummovements <= 0 ) + return NULL; + return (mstudiomovement_t *)((byte *)m_pStudioHeader + panim->movementindex) + movement; + } + + const mstudioikrule_t *pIKRule( const mstudioanimdesc_t *panim, int iRule ) + { + if( !panim || panim->numikrules <= 0 ) + return NULL; + return (mstudioikrule_t *)((byte *)m_pStudioHeader + panim->ikruleindex) + iRule; + } + + const mstudioikchain_t *pIKChain( int chain ) + { + studiohdr2_t *phdr2 = NULL; + + if( m_pStudioHeader->studiohdr2index > 0 && m_pStudioHeader->studiohdr2index < m_pStudioHeader->length ) + phdr2 = (studiohdr2_t *)((byte *)m_pStudioHeader + m_pStudioHeader->studiohdr2index); + + if( phdr2 && chain >= 0 && chain < phdr2->numikchains ) + return (mstudioikchain_t *)((byte *)m_pStudioHeader + phdr2->ikchainindex) + chain; + + return NULL; + } + + const mstudioiklock_t *pIKAutoplayLock( int lock ) + { + studiohdr2_t *phdr2 = NULL; + + if( m_pStudioHeader->studiohdr2index > 0 && m_pStudioHeader->studiohdr2index < m_pStudioHeader->length ) + phdr2 = (studiohdr2_t *)((byte *)m_pStudioHeader + m_pStudioHeader->studiohdr2index); + + if( phdr2 && lock >= 0 && lock < phdr2->numikautoplaylocks ) + return (mstudioiklock_t *)((byte *)m_pStudioHeader + phdr2->ikautoplaylockindex) + lock; + + return NULL; + } + + const mstudioiklink_t *pIKLink( const mstudioikchain_t *pchain, int link ) + { + studiohdr2_t *phdr2 = NULL; + + if( m_pStudioHeader->studiohdr2index > 0 && m_pStudioHeader->studiohdr2index < m_pStudioHeader->length ) + phdr2 = (studiohdr2_t *)((byte *)m_pStudioHeader + m_pStudioHeader->studiohdr2index); + + if( phdr2 && link >= 0 && link < pchain->numlinks ) + return (mstudioiklink_t *)((byte *)m_pStudioHeader + pchain->linkindex) + link; + + return NULL; + } + + const mstudioiklock_t *pIKLock( const mstudioseqdesc_t *pseqdesc, int lock ) + { + if( !pseqdesc || pseqdesc->iklockindex <= 0 ) + return NULL; + return (mstudioiklock_t *)((byte *)m_pStudioHeader + pseqdesc->iklockindex) + lock; + } + + const mstudioikerror_t *pCompressedError( const mstudioikrule_t *pRule ) + { + if( !pRule || pRule->ikerrorindex <= 0 ) + return NULL; + return (mstudioikerror_t *)((byte *)m_pStudioHeader + pRule->ikerrorindex); + } + + const float *pBoneweight( const mstudioseqdesc_t *pseqdesc ) + { + if( !pseqdesc || pseqdesc->weightlistindex <= 0 ) + { + if( m_flCustomBoneWeight != NULL ) + return m_flCustomBoneWeight; + return m_flDefaultBoneWeight; + } + return (float *)((byte *)m_pStudioHeader + pseqdesc->weightlistindex); + } + + const mstudioposeparamdesc_t *pPoseParameter( int iPose ) + { + studiohdr2_t *phdr2 = NULL; + + if( m_pStudioHeader->studiohdr2index > 0 && m_pStudioHeader->studiohdr2index < m_pStudioHeader->length ) + phdr2 = (studiohdr2_t *)((byte *)m_pStudioHeader + m_pStudioHeader->studiohdr2index); + + if( phdr2 && phdr2->numposeparameters > iPose ) + return (mstudioposeparamdesc_t *)((byte *)m_pStudioHeader + phdr2->poseparamindex) + iPose; + + return NULL; // poseparams is missed + } + + // look up hitbox set by index + mstudiohitboxset_t *pHitboxSet( int i ) const + { + studiohdr2_t *phdr2 = NULL; + + if( m_pStudioHeader->studiohdr2index > 0 && m_pStudioHeader->studiohdr2index < m_pStudioHeader->length ) + phdr2 = (studiohdr2_t *)((byte *)m_pStudioHeader + m_pStudioHeader->studiohdr2index); + + if( phdr2 && phdr2->numhitboxsets > i ) + return (mstudiohitboxset_t *)((byte *)m_pStudioHeader + phdr2->hitboxsetindex) + i; + + return NULL; // hitbox set is missed + } + + // calls through to hitbox to determine size of specified set + inline mstudiobbox_t *pHitbox( int set, int i ) const + { + mstudiohitboxset_t const *s = pHitboxSet( set ); + if( !s ) return NULL; + + if( s->numhitboxes > i ) + return (mstudiobbox_t *)((byte *)m_pStudioHeader + s->hitboxindex) + i; + return NULL; + } + + inline mstudiobbox_t *pHitbox( int i ) const + { + if( m_pStudioHeader->numhitboxes > i ) + return (mstudiobbox_t *)((byte *)m_pStudioHeader + m_pStudioHeader->hitboxindex) + i; + return NULL; + } + + const char *pKeyValuesBuffer( size_t *size = NULL ) const + { + studiohdr2_t *phdr2 = NULL; + + if( m_pStudioHeader->studiohdr2index > 0 && m_pStudioHeader->studiohdr2index < m_pStudioHeader->length ) + phdr2 = (studiohdr2_t *)((byte *)m_pStudioHeader + m_pStudioHeader->studiohdr2index); + if( size ) *size = 0; + + if( phdr2 && phdr2->keyvaluesize > 0 ) + { + if( size ) *size = phdr2->keyvaluesize; + return (const char *)((byte *)m_pStudioHeader + phdr2->keyvalueindex); + } + + return NULL; + } + + int FindAttachment( const char *pAttachmentName ) + { + mstudioattachment_t *pattachment = (mstudioattachment_t *)((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex); + + for( int i = 0; i < m_pStudioHeader->numattachments; i++, pattachment++ ) + { + if( !Q_stricmp( pAttachmentName, pattachment->name )) + return i + 1; + } + + return 0; + } + + int CountPoseParameters( void ) + { + studiohdr2_t *phdr2 = NULL; + + if( m_pStudioHeader->studiohdr2index > 0 && m_pStudioHeader->studiohdr2index < m_pStudioHeader->length ) + phdr2 = (studiohdr2_t *)((byte *)m_pStudioHeader + m_pStudioHeader->studiohdr2index); + + if( phdr2 && phdr2->numposeparameters > 0 ) + return phdr2->numposeparameters; + return 0; // poseparams is missed + } + + int GetNumIKChains( void ) + { + studiohdr2_t *phdr2 = NULL; + + if( m_pStudioHeader->studiohdr2index > 0 && m_pStudioHeader->studiohdr2index < m_pStudioHeader->length ) + phdr2 = (studiohdr2_t *)((byte *)m_pStudioHeader + m_pStudioHeader->studiohdr2index); + + if( phdr2 && phdr2->numikchains > 0 ) + return phdr2->numikchains; + return 0; // no IK chains + } + + int GetNumIKAutoplayLocks( void ) + { + studiohdr2_t *phdr2 = NULL; + + if( m_pStudioHeader->studiohdr2index > 0 && m_pStudioHeader->studiohdr2index < m_pStudioHeader->length ) + phdr2 = (studiohdr2_t *)((byte *)m_pStudioHeader + m_pStudioHeader->studiohdr2index); + + if( phdr2 && phdr2->numikautoplaylocks > 0 ) + return phdr2->numikautoplaylocks; + return 0; // no IK autoplay locks + } + + int GetNumHitboxSets( void ) + { + studiohdr2_t *phdr2 = NULL; + + if( m_pStudioHeader->studiohdr2index > 0 && m_pStudioHeader->studiohdr2index < m_pStudioHeader->length ) + phdr2 = (studiohdr2_t *)((byte *)m_pStudioHeader + m_pStudioHeader->studiohdr2index); + + if( phdr2 && phdr2->numhitboxsets > 0 ) + return phdr2->numhitboxsets; + return 0; // no hitbox sets + } + + const float flPoseKey( const mstudioseqdesc_t *pseqdesc, int iParam, int iAnim ) + { + float *poseKey = (float *)((byte *)m_pStudioHeader + pseqdesc->posekeyindex); + return poseKey[iParam * pseqdesc->groupsize[0] + iAnim]; + } + + int iAnimBlend( const mstudioseqdesc_t *pseqdesc, int x, int y ) + { + if( x >= pseqdesc->groupsize[0] ) + x = pseqdesc->groupsize[0] - 1; + + if( y >= pseqdesc->groupsize[1] ) + y = pseqdesc->groupsize[1] - 1; + + return (x + pseqdesc->groupsize[0] * y); // animations[blend] + } + + bool IsBoneUsed( mstudiobone_t *pbone ) + { + if( m_iBoneMask ) + return (FBitSet( pbone->flags, m_iBoneMask )) ? true : false; + return true; + } + + bool IsBoneUsed( int iBone ) + { + mstudiobone_t *pbone = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); + + if( iBone != -1 && m_iBoneMask != 0 ) + return (FBitSet( pbone[iBone].flags, m_iBoneMask )) ? true : false; + return true; + } +private: + void InitBoneWeights( void ) { for( int i = 0; i < MAXSTUDIOBONES; i++ ) m_flDefaultBoneWeight[i] = 1.0f; } + void ExtractAnimValue( int frame, const mstudioanimvalue_t *panimvalue, float scale, float &v1, float &v2 ); + void ExtractAnimValue( int frame, const mstudioanimvalue_t *panimvalue, float scale, float &v1 ); + Vector4D CalcBoneQuaternion( int frame, float s, int flags, mstudiobone_t *pbone, mstudioboneinfo_t *pboneinfo, mstudioanim_t *panim ); + Vector CalcBonePosition( int frame, float s, int flags, mstudiobone_t *pbone, mstudioanim_t *panim ); + void AdjustBoneAngles( mstudiobone_t *pbone, Radian &angles1, Radian &angles2 ); + void AdjustBoneOrigin( mstudiobone_t *pbone, Vector &origin ); + void CalcIKError( const mstudioikerror_t *pIKError, int frame, float s, Vector &pos, Vector4D &q ); + mstudioanim_t *FetchAnimation( mstudioseqdesc_t *pseqdesc, int animation ); + mstudioanimdesc_t *FetchAnimDesc( mstudioseqdesc_t *pseqdesc, int animation ); + void CalcAnimation( Vector pos[], Vector4D q[], mstudioseqdesc_t *seqdesc, int animation, float cycle ); + void BlendBones( Vector4D q1[], Vector pos1[], mstudioseqdesc_t *pseqdesc, const Vector4D q2[], const Vector pos2[], float s ); + void SlerpBones( Vector4D q1[], Vector pos1[], mstudioseqdesc_t *pseqdesc, const Vector4D q2[], const Vector pos2[], float s ); + void BuildBoneChain( const matrix3x4 &root, const Vector pos[], const Vector4D q[], int iBone, matrix3x4 *pBoneToWorld, byte *pSet = NULL ); + void WorldSpaceSlerp( Vector4D q1[], Vector pos1[], mstudioseqdesc_t *pseqdesc, const Vector4D q2[], const Vector pos2[], float s ); + void Calc9WayBlendIndices( int i0, int i1, float s0, float s1, const mstudioseqdesc_t *pseqdesc, int *pAnimIndices, float *pWeight ); + void AddSequenceLayers( CIKContext *pContext, Vector pos[], Vector4D q[], int sequence, float cycle, float flWeight ); + void AddLocalLayers( CIKContext *pContext, Vector pos[], Vector4D q[], int sequence, float cycle, float flWeight ); + void SolveBone( int iBone, matrix3x4 *pBoneToWorld, Vector pos[], Vector4D q[] ); + bool SolveIK( const mstudioikchain_t *pikchain, Vector &targetFoot, matrix3x4 *pBoneToWorld ); + bool SolveIK( int iThigh, int iKnee, int iFoot, Vector &targetFoot, matrix3x4 *pBoneToWorld ); + bool SolveIK( int iThigh, int iKnee, int iFoot, Vector &targetFoot, Vector &targetKneePos, Vector &targetKneeDir, matrix3x4 *pBoneToWorld ); + void CalcPoseSingle( Vector pos[], Vector4D q[], int sequence, float cycle ); + void AlignIKMatrix( matrix3x4 &mMat, const Vector &vAlignTo ); + + // private routines + void LocalPoseParameter( mstudioseqdesc_t *pseqdesc, int iLocalIndex, float &flSetting, int &index ); + bool IKAnimError( const mstudioikrule_t *pRule, mstudioanimdesc_t *panim, float flCycle, Vector &pos, Vector4D &q, float &flWeight ); + float IKTail( ikcontextikrule_t *ikRule, float flCycle ); + bool IKSequenceError( int iSequence, float flCycle, int iRule, mstudioanimdesc_t *panim[4], float weight[4], ikcontextikrule_t *ikRule ); + float IKRuleWeight( const mstudioikrule_t *ikRule, const mstudioanimdesc_t *panim, float flCycle, int &iFrame, float &fraq ); + float IKRuleWeight( ikcontextikrule_t *ikRule, float flCycle ); + bool IKShouldLatch( ikcontextikrule_t *ikRule, float flCycle ); + + float m_flDefaultBoneWeight[MAXSTUDIOBONES]; // compatibility issues + float *m_flCustomBoneWeight; // user boneweights + const float *m_flBoneControllers; + const float *m_flPoseParams; + int m_iBoneMask; + float m_flTime; // realtime + + // intermediate matrices + matrix3x4 srcBoneToWorld[MAXSTUDIOBONES]; + matrix3x4 dstBoneToWorld[MAXSTUDIOBONES]; + matrix3x4 targetBoneToWorld[MAXSTUDIOBONES]; +public: + // import table + virtual void debugMsg( char *szFmt, ... ) {} + virtual mstudioanim_t *GetAnimSourceData( mstudioseqdesc_t *pseqdesc ); + virtual void debugLine( const Vector& origin, const Vector& dest, int r, int g, int b, bool noDepthTest = false, float duration = 0.0f ) {} + + // export table + void InitPose( Vector pos[], Vector4D q[] ); + void AccumulatePose( CIKContext *pContext, Vector pos[], Vector4D q[], int sequence, float cycle, float flWeight ); + void CalcBoneAdj( Vector pos[], Vector4D q[], const byte controllers[], byte mouthopen ); + void CalcBoneAdj( float adj[], const byte controllers[], byte mouthopen ); + void CalcAutoplaySequences( CIKContext *pContext, Vector pos[], Vector4D q[] ); + void SetStudioPointers( studiohdr_t *pStudioHdr, const float *pPoseParams ) { m_pStudioHeader = pStudioHdr; m_flPoseParams = pPoseParams; } + void SetBoneControllers( float *pNewList ) { m_flBoneControllers = pNewList; } + void SetBoneWeights( float *pNewList ) { m_flCustomBoneWeight = pNewList; } + void SetBoneMask( int iBoneMask ) { m_iBoneMask = iBoneMask; } + void UpdateRealTime( float flTime ) { m_flTime = flTime; } + void CalcDefaultPoseParameters( float flPoseParams[] ); + + // shared routines + float GetController( int iController, float ctlValue ); + float SetController( int iController, float flValue, float &ctlValue ); + float GetPoseParameter( int iParameter, float ctlValue ); + float SetPoseParameter( int iParameter, float flValue, float &ctlValue ); + void LocalSeqAnims( int sequence, mstudioanimdesc_t *panim[4], float *weight ); + int LocalMaxFrame( int iSequence ); + float LocalDuration( int sequence ); + float LocalFPS( int sequence ); + float LocalCPS( int sequence ); + + // movement routines + bool AnimPosition( mstudioanimdesc_t *panim, float flCycle, Vector &vecPos, Vector &vecAngle ); + bool AnimVelocity( mstudioanimdesc_t *panim, float flCycle, Vector &vecVelocity ); + bool AnimMovement( mstudioanimdesc_t *panim, float flCycleFrom, float flCycleTo, Vector &deltaPos, Vector &deltaAngle ); + float FindAnimDistance( mstudioanimdesc_t *panim, float flDist ); + bool SeqMovement( int sequence, float flCycleFrom, float flCycleTo, Vector &deltaPos, Vector &deltaAngles ); + bool SeqVelocity( int iSequence, float flCycle, Vector &vecVelocity ); + float FindSeqDistance( int sequence, float flDist ); + + studiohdr_t *m_pStudioHeader; + friend class CIKContext; +}; + +#endif//BS_DEFS_H \ No newline at end of file diff --git a/game_shared/bullets.h b/game_shared/bullets.h new file mode 100644 index 0000000..0bf0323 --- /dev/null +++ b/game_shared/bullets.h @@ -0,0 +1,16 @@ +//======================================================================= +// Copyright (C) XashXT Group 2015 +// bullets.h - shared bullet types +//======================================================================= +#ifndef BULLETS_H +#define BULLETS_H + +// bullet types +typedef enum +{ + BULLET_NORMAL = 0, // single bullet + BULLET_BUCKSHOT, // multiple parts + BULLET_STAB, // hack for knife +} Bullet; + +#endif//BULLETS_H \ No newline at end of file diff --git a/game_shared/cdll_dll.h b/game_shared/cdll_dll.h new file mode 100644 index 0000000..0a981c2 --- /dev/null +++ b/game_shared/cdll_dll.h @@ -0,0 +1,57 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// cdll_dll.h + +// this file is included by both the game-dll and the client-dll, + +#ifndef CDLL_DLL_H +#define CDLL_DLL_H + +#define MAX_WEAPONS 32 // ??? + +#define MAX_WEAPON_SLOTS 10 // hud item selection slots +#define MAX_ITEM_TYPES 6 // hud item selection slots + +#define MAX_ITEMS 5 // hard coded item types +#define ITEM_ANTIDOTE 0 +#define ITEM_SECURITY 1 + +#define HIDEHUD_WEAPONS ( 1<<0 ) +#define HIDEHUD_FLASHLIGHT ( 1<<1 ) +#define HIDEHUD_ALL ( 1<<2 ) +#define HIDEHUD_HEALTH ( 1<<3 ) +#define ITEM_SUIT ( 1<<4 ) +#define ITEM_HEADSHIELD ( 1<<5 ) +#define ITEM_GASMASK ( 1<<6 ) + +#define MAX_AMMO_SLOTS 32 // not really slots + +#define HUD_PRINTNOTIFY 1 +#define HUD_PRINTCONSOLE 2 +#define HUD_PRINTTALK 3 +#define HUD_PRINTCENTER 4 + +// decal flags +#define FDECAL_PERMANENT 0x01 // This decal should not be removed in favor of any new decals +#define FDECAL_USE_LANDMARK 0x02 // This is a decal applied on a bmodel without origin-brush so we done in absoulute pos +#define FDECAL_CUSTOM 0x04 // This is a custom clan logo and should not be saved/restored +#define FDECAL_PUDDLE 0x08 // Decal is a puddle (use special shader) +#define FDECAL_NORANDOM 0x10 // Decal came from save\restore, so we don't use random select +#define FDECAL_DONTSAVE 0x20 // Decal was loaded from adjacent level, don't save it for this level +#define FDECAL_STUDIO 0x40 // Indicates a studio decal +#define FDECAL_LOCAL_SPACE 0x80 // Decal is in local space (any decal after serialization) + +#endif \ No newline at end of file diff --git a/game_shared/common.cpp b/game_shared/common.cpp new file mode 100644 index 0000000..62015af --- /dev/null +++ b/game_shared/common.cpp @@ -0,0 +1,338 @@ +/* +common.cpp - common game routines +Copyright (C) 2011 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#define NOMINMAX +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* +============ +COM_FileBase + +Extracts the base name of a file (no path, no extension, assumes '/' as path separator) +============ +*/ +void COM_FileBase( const char *in, char *out ) +{ + int len, start, end; + + len = Q_strlen( in ); + if( !len ) return; + + // scan backward for '.' + end = len - 1; + + while( end && in[end] != '.' && in[end] != '/' && in[end] != '\\' ) + end--; + + if( in[end] != '.' ) + end = len-1; // no '.', copy to end + else end--; // found ',', copy to left of '.' + + + // scan backward for '/' + start = len - 1; + + while( start >= 0 && in[start] != '/' && in[start] != '\\' ) + start--; + + if( start < 0 || ( in[start] != '/' && in[start] != '\\' )) + start = 0; + else start++; + + // length of new sting + len = end - start + 1; + + // Copy partial string + Q_strncpy( out, &in[start], len + 1 ); + out[len] = 0; +} + +/* +============ +COM_ExtractFilePath +============ +*/ +void COM_ExtractFilePath( const char *path, char *dest ) +{ + const char *src; + src = path + Q_strlen( path ) - 1; + + // back up until a \ or the start + while( src != path && !(*(src - 1) == '\\' || *(src - 1) == '/' )) + src--; + + if( src != path ) + { + memcpy( dest, path, src - path ); + dest[src - path - 1] = 0; // cutoff backslash + } + else Q_strcpy( dest, "" ); // file without path +} + +/* +============ +COM_StripExtension +============ +*/ +void COM_StripExtension( char *path ) +{ + size_t length; + + length = Q_strlen( path ) - 1; + while( length > 0 && path[length] != '.' ) + { + length--; + if( path[length] == '/' || path[length] == '\\' || path[length] == ':' ) + return; // no extension + } + if( length ) path[length] = 0; +} + +/* +================== +COM_DefaultExtension +================== +*/ +void COM_DefaultExtension( char *path, const char *extension ) +{ + const char *src; + + // if path doesn't have a .EXT, append extension + // (extension should include the .) + src = path + Q_strlen( path ) - 1; + + while( *src != '/' && src != path ) + { + // it has an extension + if( *src == '.' ) return; + src--; + } + Q_strcat( path, extension ); +} + +/* +============ +COM_FixSlashes + +Changes all '/' characters into '\' characters, in place. +============ +*/ +void COM_FixSlashes( char *pname ) +{ + while( *pname ) + { + if( *pname == '\\' ) + *pname = '/'; + pname++; + } +} + +/* +============== +COM_ParseFile + +safe version text parser +============== +*/ +char *COM_ParseFileExt( char *data, char *token, long token_size, bool allowNewLines ) +{ + bool newline = false; + int c, len; + + if( !token || !token_size ) + return NULL; + + len = 0; + token[0] = 0; + + if( !data ) + return NULL; + +// skip whitespace +skipwhite: + while(( c = ((byte)*data)) <= ' ' ) + { + if( c == 0 ) + return NULL; // end of file; + if( c == '\n' ) + newline = true; + data++; + } + + if( newline && !allowNewLines ) + return data; + + newline = false; + + // skip // comments + if( c == '/' && data[1] == '/' ) + { + while( *data && *data != '\n' ) + data++; + goto skipwhite; + } + + // handle quoted strings specially + if( c == '\"' ) + { + data++; + while( 1 ) + { + c = (byte)*data++; + if( c == '\"' || !c ) + { + if( len < token_size ) + token[len] = 0; + return data; + } + + if( len < token_size ) + token[len] = c; + len++; + } + } + + // parse single characters + if( c == '{' || c == '}' || c == ')' || c == '(' || c == '\'' || c == ',' || c == '|' ) + { + if( len < token_size ) + token[len] = c; + len++; + + if( len < token_size ) + token[len] = 0; + else token[0] = 0; // string is too long + + return data + 1; + } + + // parse a regular word + do + { + if( len < token_size ) + token[len] = c; + data++; + len++; + c = ((byte)*data); + + if( c == '{' || c == '}' || c == ')' || c == '(' || c == '\'' || c == ',' || c == '|' ) + break; + } while( c > 32 ); + + if( len < token_size ) + token[len] = 0; + else token[0] = 0; // string is too long + + return data; +} + +/* +================= +COM_SkipBracedSection + +The next token should be an open brace. +Skips until a matching close brace is found. +Internal brace depths are properly skipped. +================= +*/ +char *COM_SkipBracedSection( char *pfile ) +{ + char token[256]; + int depth = 0; + + do { + pfile = COM_ParseFile( pfile, token ); + + if( token[1] == 0 ) + { + if( token[0] == '{' ) + depth++; + else if( token[0] == '}' ) + depth--; + } + } while( depth && pfile != NULL ); + + return pfile; +} + +/* +============ +COM_FileExtension +============ +*/ +const char *COM_FileExtension( const char *in ) +{ + const char *separator, *backslash, *colon, *dot; + + separator = Q_strrchr( in, '/' ); + backslash = Q_strrchr( in, '\\' ); + + if( !separator || separator < backslash ) + separator = backslash; + + colon = Q_strrchr( in, ':' ); + + if( !separator || separator < colon ) + separator = colon; + + dot = Q_strrchr( in, '.' ); + + if( dot == NULL || ( separator && ( dot < separator ))) + return ""; + + return dot + 1; +} + +/* +================= +COM_HashKey + +returns hash key for string +================= +*/ +unsigned int COM_HashKey( const char *string, unsigned int hashSize ) +{ + unsigned int hashKey = 0; + + for( int i = 0; string[i]; i++ ) + hashKey = (hashKey + i) * 37 + Q_tolower( string[i] ); + + return (hashKey % hashSize); +} + +/* +================== +COM_FileExists +================== +*/ +bool COM_FileExists( const char *path ) +{ + int desc; + + if(( desc = open( path, O_RDONLY|O_BINARY )) < 0 ) + return false; + + close( desc ); + return true; +} \ No newline at end of file diff --git a/game_shared/cubemap.h b/game_shared/cubemap.h new file mode 100644 index 0000000..794e4fe --- /dev/null +++ b/game_shared/cubemap.h @@ -0,0 +1,64 @@ +//===== Copyright © 1996-2007, Valve Corporation, All rights reserved. ======// +// +// Purpose: a class for performing cube-mapped spherical sample lookups. +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//===========================================================================// + +#ifndef CUBEMAP_H +#define CUBEMAP_H + +#include "mathlib.h" + +template struct CCubeMap +{ + T m_Samples[6][RES][RES]; + +public: + _forceinline void GetCoords( Vector const &vecNormalizedDirection, int &nX, int &nY, int &nFace ) + { + int nLargest = 0; + int nAxis0 = 1; + int nAxis1 = 2; + + // find largest magnitude component + if( fabs( vecNormalizedDirection[1] ) > fabs( vecNormalizedDirection[0] )) + { + nLargest = 1; + nAxis0 = 0; + nAxis1 = 2; + } + + if( fabs( vecNormalizedDirection[2] ) > fabs( vecNormalizedDirection[nLargest] )) + { + nLargest = 2; + nAxis0 = 0; + nAxis1 = 1; + } + + float flZ = vecNormalizedDirection[nLargest]; + + if( flZ < 0 ) + { + flZ = - flZ; + nLargest += 3; + } + + nFace = nLargest; + flZ = 1.0 / flZ; + + nX = RemapValClamped( vecNormalizedDirection[nAxis0] * flZ, -1, 1, 0, RES - 1 ); + nY = RemapValClamped( vecNormalizedDirection[nAxis1] * flZ, -1, 1, 0, RES - 1 ); + } + + _forceinline T & GetSample( Vector const &vecNormalizedDirection ) + { + int nX, nY, nFace; + GetCoords( vecNormalizedDirection, nX, nY, nFace ); + return m_Samples[nFace][nX][nY]; + } +}; + +#endif// CUBEMAP_H \ No newline at end of file diff --git a/game_shared/ikcontext.cpp b/game_shared/ikcontext.cpp new file mode 100644 index 0000000..04d6c2d --- /dev/null +++ b/game_shared/ikcontext.cpp @@ -0,0 +1,1057 @@ +/* +ikcontext.cpp - Inverse Kinematic implementation +This file is part of XashNT engine +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "mathlib.h" +#include "const.h" +#include +#include "com_model.h" +#include "stringlib.h" +#include "bs_defs.h" +#include "ikcontext.h" + +matrix3x4 CIKContext :: m_boneToWorld[MAXSTUDIOBONES]; + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CIKContext :: CIKContext() +{ + m_target.EnsureCapacity( 12 ); // FIXME: this sucks, shouldn't it be grown? + m_iFramecounter = -1; + m_pBoneSetup = NULL; + m_flTime = -1.0f; + m_target.SetSize( 0 ); +} + +void CIKContext :: Init( const CStudioBoneSetup *pBoneSetup, const Vector &angles, const Vector &pos, float flTime, int iFramecounter ) +{ + m_ikChainRule.RemoveAll(); // m_numikrules = 0; + m_pBoneSetup = (CStudioBoneSetup *)pBoneSetup; + + if( m_pBoneSetup->GetNumIKChains( )) + { + m_ikChainRule.SetSize( m_pBoneSetup->GetNumIKChains() ); + + // FIXME: Brutal hackery to prevent a crash + if( m_target.Count() == 0 ) + { + m_target.SetSize( 12 ); + memset( m_target.Base(), 0, sizeof( m_target[0] ) * m_target.Count() ); + ClearTargets(); + } + } + else + { + m_target.SetSize( 0 ); + } + + m_rootxform = matrix3x4( pos, angles ); + m_iFramecounter = iFramecounter; + m_flTime = flTime; +} + +void CIKContext :: AddDependencies( mstudioseqdesc_t *pseqdesc, int iSequence, float flCycle, float flWeight ) +{ + int i; + + if( m_pBoneSetup->GetNumIKChains() == 0 ) + return; + + if( !FBitSet( pseqdesc->flags, STUDIO_IKRULES )) + return; + + ikcontextikrule_t ikrule; + + // this shouldn't be necessary, but the Assert should help us catch whoever is screwing this up + flWeight = bound( 0.0f, flWeight, 1.0f ); + + // unify this + if( FBitSet( pseqdesc->flags, STUDIO_REALTIME )) + { + float cps = m_pBoneSetup->LocalCPS( iSequence ); + flCycle = m_flTime * cps; + flCycle = flCycle - (int)flCycle; + } + else if( flCycle < 0 || flCycle >= 1 ) + { + if( FBitSet( pseqdesc->flags, STUDIO_LOOPING )) + { + flCycle = flCycle - (int)flCycle; + if( flCycle < 0.0f ) flCycle += 1.0f; + } + else + { + flCycle = bound( 0.0f, flCycle, 0.9999f ); + } + } + + mstudioanimdesc_t *panim[4]; + float weight[4]; + + m_pBoneSetup->LocalSeqAnims( iSequence, panim, weight ); + + // g-cont. all the animations of current blend has equal set of ikrules and chains. see studiomdl->simplify.cpp for details + for( i = 0; i < panim[0]->numikrules; i++ ) + { + if( !m_pBoneSetup->IKSequenceError( iSequence, flCycle, i, panim, weight, &ikrule )) + continue; + + // don't add rule if the bone isn't going to be calculated + const mstudioikchain_t *pchain = m_pBoneSetup->pIKChain( ikrule.chain ); + int bone = m_pBoneSetup->pIKLink( pchain, 2 )->bone; + + if( !m_pBoneSetup->IsBoneUsed( bone )) + continue; + + // or if its relative bone isn't going to be calculated + if( ikrule.bone >= 0 && !m_pBoneSetup->IsBoneUsed( ikrule.bone )) + continue; + + // FIXME: Brutal hackery to prevent a crash + if( m_target.Count() == 0 ) + { + m_target.SetSize( 12 ); + memset( m_target.Base(), 0, sizeof( m_target[0] ) * m_target.Count()); + ClearTargets(); + } + + ikrule.flRuleWeight = flWeight; + + if( ikrule.flRuleWeight * ikrule.flWeight > 0.999f ) + { + if( ikrule.type != IK_UNLATCH ) + { + // clear out chain if rule is 100% + m_ikChainRule.Element( ikrule.chain ).RemoveAll( ); + if( ikrule.type == IK_RELEASE ) + continue; + } + } + + int nIndex = m_ikChainRule.Element( ikrule.chain ).AddToTail( ); + m_ikChainRule.Element( ikrule.chain ).Element( nIndex ) = ikrule; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CIKContext :: AddAutoplayLocks( Vector pos[], Vector4D q[] ) +{ + // skip all array access if no autoplay locks. + if( m_pBoneSetup->GetNumIKAutoplayLocks() == 0 ) + { + return; + } + + int ikOffset = m_ikLock.AddMultipleToTail( m_pBoneSetup->GetNumIKAutoplayLocks() ); + memset( &m_ikLock[ikOffset], 0, sizeof( ikcontextikrule_t ) * m_pBoneSetup->GetNumIKAutoplayLocks() ); + + for( int i = 0; i < m_pBoneSetup->GetNumIKAutoplayLocks(); i++ ) + { + const mstudioiklock_t *lock = m_pBoneSetup->pIKAutoplayLock( i ); + const mstudioikchain_t *pchain = m_pBoneSetup->pIKChain( lock->chain ); + int bone = m_pBoneSetup->pIKLink( pchain, 2 )->bone; + + // don't bother with iklock if the bone isn't going to be calculated + if( !m_pBoneSetup->IsBoneUsed( bone )) + continue; + + // eval current ik'd bone + BuildBoneChain( pos, q, bone, m_boneToWorld ); + + ikcontextikrule_t *ikrule = &m_ikLock[ikOffset + i]; + + ikrule->chain = lock->chain; + ikrule->type = IK_WORLD; + ikrule->slot = i; + + ikrule->q = m_boneToWorld[bone].GetQuaternion(); + ikrule->pos = m_boneToWorld[bone].GetOrigin(); + + // save off current knee direction + if( m_pBoneSetup->pIKLink( pchain, 0 )->kneeDir.LengthSqr() > 0.0f ) + { + const mstudioiklink_t *link0 = m_pBoneSetup->pIKLink( pchain, 0 ); + const mstudioiklink_t *link1 = m_pBoneSetup->pIKLink( pchain, 1 ); + + ikrule->kneeDir = m_boneToWorld[link0->bone].VectorRotate( link0->kneeDir ); + ikrule->kneePos = m_boneToWorld[link1->bone].GetOrigin(); + } + else + { + ikrule->kneeDir.Init( ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CIKContext :: AddSequenceLocks( mstudioseqdesc_t *pseqdesc, Vector pos[], Vector4D q[] ) +{ + if( m_pBoneSetup->GetNumIKChains() == 0 ) + { + return; + } + + if( pseqdesc->numiklocks == 0 ) + { + return; + } + + int ikOffset = m_ikLock.AddMultipleToTail( pseqdesc->numiklocks ); + memset( &m_ikLock[ikOffset], 0, sizeof( ikcontextikrule_t ) * pseqdesc->numiklocks ); + + for( int i = 0; i < pseqdesc->numiklocks; i++ ) + { + const mstudioiklock_t *plock = m_pBoneSetup->pIKLock( pseqdesc, i ); + const mstudioikchain_t *pchain = m_pBoneSetup->pIKChain( plock->chain ); + int bone = m_pBoneSetup->pIKLink( pchain, 2 )->bone; + + // don't bother with iklock if the bone isn't going to be calculated + if( !m_pBoneSetup->IsBoneUsed( bone )) + continue; + + // eval current ik'd bone + BuildBoneChain( pos, q, bone, m_boneToWorld ); + + ikcontextikrule_t *ikrule = &m_ikLock[ikOffset+i]; + ikrule->chain = i; + ikrule->slot = i; + ikrule->type = IK_WORLD; + + ikrule->q = m_boneToWorld[bone].GetQuaternion(); + ikrule->pos = m_boneToWorld[bone].GetOrigin(); + + // save off current knee direction + if( m_pBoneSetup->pIKLink( pchain, 0 )->kneeDir.LengthSqr() > 0.0f ) + { + const mstudioiklink_t *link0 = m_pBoneSetup->pIKLink( pchain, 0 ); + ikrule->kneeDir = m_boneToWorld[link0->bone].VectorRotate( link0->kneeDir ); + } + else + { + ikrule->kneeDir.Init( ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: build boneToWorld transforms for a specific bone +//----------------------------------------------------------------------------- +void CIKContext :: BuildBoneChain( const Vector pos[], const Vector4D q[], int iBone, matrix3x4 *pBoneToWorld, byte *pBoneSet ) +{ + m_pBoneSetup->BuildBoneChain( m_rootxform, pos, q, iBone, pBoneToWorld, pBoneSet ); +} + +//----------------------------------------------------------------------------- +// Purpose: Invalidate any IK locks. +//----------------------------------------------------------------------------- +void CIKContext :: ClearTargets( void ) +{ + for( int i = 0; i < m_target.Count(); i++ ) + { + m_target[i].latched.iFramecounter = -9999; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Run through the rules that survived and turn a specific bones boneToWorld +// transform into a pos and q in parents bonespace +//----------------------------------------------------------------------------- +void CIKContext :: UpdateTargets( Vector pos[], Vector4D q[], matrix3x4 boneToWorld[], byte *pBoneSet ) +{ + int i, j; + + for( i = 0; i < m_target.Count(); i++ ) + { + m_target[i].est.flWeight = 0.0f; + m_target[i].est.latched = 1.0f; + m_target[i].est.release = 1.0f; + m_target[i].est.height = 0.0f; + m_target[i].est.floor = 0.0f; + m_target[i].est.radius = 0.0f; + m_target[i].offset.pos.Init(); + m_target[i].offset.q.Init(); + } + + AutoIKRelease( ); + + for( j = 0; j < m_ikChainRule.Count(); j++ ) + { + for( i = 0; i < m_ikChainRule.Element( j ).Count(); i++ ) + { + ikcontextikrule_t *pRule = &m_ikChainRule.Element( j ).Element( i ); + + switch( pRule->type ) + { + case IK_ATTACHMENT: + case IK_GROUND: + { + matrix3x4 footTarget; + CIKTarget *pTarget = &m_target[pRule->slot]; + pTarget->chain = pRule->chain; + pTarget->type = pRule->type; + + if( pRule->type == IK_ATTACHMENT ) + pTarget->offset.attachmentIndex = pRule->iAttachment; + else pTarget->offset.attachmentIndex = 0; + + if( pRule->flRuleWeight == 1.0f || pTarget->est.flWeight == 0.0f ) + { + pTarget->offset.q = pRule->q; + pTarget->offset.pos = pRule->pos; + pTarget->est.height = pRule->height; + pTarget->est.floor = pRule->floor; + pTarget->est.radius = pRule->radius; + pTarget->est.latched = pRule->latched * pRule->flRuleWeight; + pTarget->est.release = pRule->release; + pTarget->est.flWeight = pRule->flWeight * pRule->flRuleWeight; + } + else + { + QuaternionSlerp( pTarget->offset.q, pRule->q, pRule->flRuleWeight, pTarget->offset.q ); + pTarget->offset.pos = Lerp( pRule->flRuleWeight, pTarget->offset.pos, pRule->pos ); + pTarget->est.height = Lerp( pRule->flRuleWeight, pTarget->est.height, pRule->height ); + pTarget->est.floor = Lerp( pRule->flRuleWeight, pTarget->est.floor, pRule->floor ); + pTarget->est.radius = Lerp( pRule->flRuleWeight, pTarget->est.radius, pRule->radius ); + //pTarget->est.latched = Lerp( pRule->flRuleWeight, pTarget->est.latched, pRule->latched ); + pTarget->est.latched = Q_min( pTarget->est.latched, pRule->latched ); + pTarget->est.release = Lerp( pRule->flRuleWeight, pTarget->est.release, pRule->release ); + pTarget->est.flWeight = Lerp( pRule->flRuleWeight, pTarget->est.flWeight, pRule->flWeight ); + } + + if( pRule->type == IK_GROUND ) + { + pTarget->latched.deltaPos.z = 0.0f; + pTarget->est.pos.z = pTarget->est.floor + m_rootxform[3][2]; + } + } + break; + case IK_UNLATCH: + { + CIKTarget *pTarget = &m_target[pRule->slot]; + if( pRule->latched > 0.0 ) pTarget->est.latched = 0.0f; + else pTarget->est.latched = Q_min( pTarget->est.latched, 1.0f - pRule->flWeight ); + } + break; + case IK_RELEASE: + { + CIKTarget *pTarget = &m_target[pRule->slot]; + if( pRule->latched > 0.0f ) pTarget->est.latched = 0.0f; + else pTarget->est.latched = Q_min( pTarget->est.latched, 1.0f - pRule->flWeight ); + pTarget->est.flWeight = (pTarget->est.flWeight) * (1.0f - pRule->flWeight * pRule->flRuleWeight); + } + break; + } + } + } + + for( i = 0; i < m_target.Count(); i++ ) + { + CIKTarget *pTarget = &m_target[i]; + + if( pTarget->est.flWeight > 0.0 ) + { + const mstudioikchain_t *pchain = m_pBoneSetup->pIKChain( pTarget->chain ); + int bone = m_pBoneSetup->pIKLink( pchain, 2 )->bone; + + // eval current ik'd bone + BuildBoneChain( pos, q, bone, boneToWorld, pBoneSet ); + + // xform IK target error into world space + matrix3x4 local = matrix3x4( pTarget->offset.pos, pTarget->offset.q ).Invert(); + matrix3x4 worldFootpad = boneToWorld[bone].ConcatTransforms( local ); + + if( pTarget->est.latched == 1.0f ) + pTarget->latched.bNeedsLatch = true; + else pTarget->latched.bNeedsLatch = false; + + // disable latched position if it looks invalid + if( m_iFramecounter < 0 || pTarget->latched.iFramecounter < m_iFramecounter - 1 + || pTarget->latched.iFramecounter > m_iFramecounter ) + { + pTarget->latched.bHasLatch = false; + pTarget->latched.influence = 0.0; + } + + pTarget->latched.iFramecounter = m_iFramecounter; + + // find ideal contact position + pTarget->est.q = pTarget->ideal.q = worldFootpad.GetQuaternion(); + pTarget->est.pos = pTarget->ideal.pos = worldFootpad.GetOrigin(); + + float latched = pTarget->est.latched; + + if( pTarget->latched.bHasLatch ) + { + if( pTarget->est.latched == 1.0f ) + { + // keep track of latch position error from ideal contact position + pTarget->latched.deltaPos = pTarget->latched.pos - pTarget->est.pos; + QuaternionSM( -1.0f, pTarget->est.q, pTarget->latched.q, pTarget->latched.deltaQ ); + pTarget->est.q = pTarget->latched.q; + pTarget->est.pos = pTarget->latched.pos; + } + else if( pTarget->est.latched > 0.0f ) + { + // ramp out latch differences during decay phase of rule + if( latched > 0 && latched < pTarget->latched.influence ) + { + // latching has decreased + float dt = pTarget->latched.influence - latched; + if( pTarget->latched.influence > 0.0 ) + dt = dt / pTarget->latched.influence; + + QuaternionScale( pTarget->latched.deltaQ, (1.0f - dt), pTarget->latched.deltaQ ); + pTarget->latched.deltaPos *= (1.0f - dt); + } + + // move ideal contact position by latched error factor + pTarget->est.pos = pTarget->est.pos + pTarget->latched.deltaPos; + QuaternionMA( pTarget->est.q, 1, pTarget->latched.deltaQ, pTarget->est.q ); + pTarget->latched.q = pTarget->est.q; + pTarget->latched.pos = pTarget->est.pos; + } + else + { + pTarget->latched.bHasLatch = false; + pTarget->latched.q = pTarget->est.q; + pTarget->latched.pos = pTarget->est.pos; + pTarget->latched.deltaPos.Init(); + pTarget->latched.deltaQ.Init(); + } + pTarget->latched.influence = latched; + } + + // check for illegal requests + Vector p1, p2, p3; + + p1 = boneToWorld[m_pBoneSetup->pIKLink( pchain, 0 )->bone].GetOrigin(); // hip + p2 = boneToWorld[m_pBoneSetup->pIKLink( pchain, 1 )->bone].GetOrigin(); // knee + p3 = boneToWorld[m_pBoneSetup->pIKLink( pchain, 2 )->bone].GetOrigin(); // foot + + float d1 = (p2 - p1).Length(); + float d2 = (p3 - p2).Length(); + + if( pTarget->latched.bHasLatch ) + { + float d4 = (p3 + pTarget->latched.deltaPos - p1).Length(); + + // unstick feet when distance is too great + if(( d4 < fabs( d1 - d2 ) || d4 * 0.95f > d1 + d2 ) && pTarget->est.latched > 0.2f ) + { + pTarget->error.flTime = m_flTime; + } + + // unstick feet when angle is too great + if( pTarget->est.latched > 0.2f ) + { + float d = fabs( pTarget->latched.deltaQ.w ) * 2.0f - 1.0f; + + // FIXME: cos(45), make property of chain + if( d < 0.707f ) + { + pTarget->error.flTime = m_flTime; + } + } + } + + Vector dt = pTarget->est.pos - p1; + pTarget->trace.hipToFoot = dt.Length(); + pTarget->trace.hipToKnee = d1; + pTarget->trace.kneeToFoot = d2; + pTarget->trace.hip = p1; + pTarget->trace.knee = p2; + dt = dt.Normalize(); + pTarget->trace.closest = p1 + dt * ( fabs( d1 - d2 ) * 1.01f); + pTarget->trace.farthest = p1 + dt * (d1 + d2) * 0.99; + pTarget->trace.lowest = p1 + Vector( 0, 0, -1.0f ) * (d1 + d2) * 0.99f; + // pTarget->trace.endpos = pTarget->est.pos; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: insert release rules if the ik rules were in error +//----------------------------------------------------------------------------- +void CIKContext :: AutoIKRelease( void ) +{ + int i; + + for( i = 0; i < m_target.Count(); i++ ) + { + CIKTarget *pTarget = &m_target[i]; + + float dt = m_flTime - pTarget->error.flTime; + + if( pTarget->error.bInError || dt < 0.5f ) + { + if( !pTarget->error.bInError ) + { + pTarget->error.ramp = 0.0; + pTarget->error.flErrorTime = pTarget->error.flTime; + pTarget->error.bInError = true; + } + + float ft = m_flTime - pTarget->error.flErrorTime; + + if( dt < 0.25f ) + { + pTarget->error.ramp = Q_min( pTarget->error.ramp + ft * 4.0f, 1.0f ); + } + else + { + pTarget->error.ramp = Q_max( pTarget->error.ramp - ft * 4.0f, 0.0f ); + } + + if( pTarget->error.ramp > 0.0f ) + { + ikcontextikrule_t ikrule; + + ikrule.chain = pTarget->chain; + ikrule.bone = 0; + ikrule.type = IK_RELEASE; + ikrule.slot = i; + ikrule.flWeight = SimpleSpline( pTarget->error.ramp ); + ikrule.flRuleWeight = 1.0; + ikrule.latched = dt < 0.25 ? 0.0 : ikrule.flWeight; + + // don't bother with AutoIKRelease if the bone isn't going to be calculated + // this code is crashing for some unknown reason. + if( pTarget->chain >= 0 && pTarget->chain < m_pBoneSetup->GetNumIKChains( )) + { + const mstudioikchain_t *pchain = m_pBoneSetup->pIKChain( pTarget->chain ); + + if( pchain != NULL ) + { + int bone = m_pBoneSetup->pIKLink( pchain, 2 )->bone; + if( bone >= 0 && bone < m_pBoneSetup->m_pStudioHeader->numbones ) + { + if( !m_pBoneSetup->IsBoneUsed( bone )) + { + pTarget->error.bInError = false; + continue; + } + + int nIndex = m_ikChainRule.Element( ikrule.chain ).AddToTail( ); + m_ikChainRule.Element( ikrule.chain ).Element( nIndex ) = ikrule; + } + else + { + m_pBoneSetup->debugMsg( "^2Warning:^7 AutoIKRelease (%s) out of range bone %d (%d)\n", + m_pBoneSetup->m_pStudioHeader->name, bone, m_pBoneSetup->m_pStudioHeader->numbones ); + } + } + else + { + m_pBoneSetup->debugMsg( "^2Warning:^7 AutoIKRelease (%s) got a NULL pchain %d\n", + m_pBoneSetup->m_pStudioHeader->name, pTarget->chain ); + } + } + else + { + m_pBoneSetup->debugMsg( "^2Warning:^7 AutoIKRelease (%s) got an out of range chain %d (%d)\n", + m_pBoneSetup->m_pStudioHeader->name, pTarget->chain, m_pBoneSetup->GetNumIKChains( )); + } + } + else + { + pTarget->error.bInError = false; + } + + pTarget->error.flErrorTime = m_flTime; + } + } +} + +void CIKContext :: SolveDependencies( Vector pos[], Vector4D q[], matrix3x4 boneToWorld[], byte *pBoneSet ) +{ + matrix3x4 worldTarget; + int i, j; + + ikchainresult_t chainResult[32]; // allocate!!! + + // init chain rules + for( i = 0; i < m_pBoneSetup->GetNumIKChains(); i++ ) + { + const mstudioikchain_t *pchain = m_pBoneSetup->pIKChain( i ); + ikchainresult_t *pChainResult = &chainResult[i]; + int bone = m_pBoneSetup->pIKLink( pchain, 2 )->bone; + + pChainResult->target = -1; + pChainResult->flWeight = 0.0; + + // don't bother with chain if the bone isn't going to be calculated + if( !m_pBoneSetup->IsBoneUsed( bone )) + continue; + + // eval current ik'd bone + BuildBoneChain( pos, q, bone, boneToWorld, pBoneSet ); + + pChainResult->q = boneToWorld[bone].GetQuaternion(); + pChainResult->pos = boneToWorld[bone].GetOrigin(); + } + + for( j = 0; j < m_ikChainRule.Count(); j++ ) + { + for( i = 0; i < m_ikChainRule.Element( j ).Count(); i++ ) + { + ikcontextikrule_t *pRule = &m_ikChainRule.Element( j ).Element( i ); + ikchainresult_t *pChainResult = &chainResult[pRule->chain]; + pChainResult->target = -1; + + switch( pRule->type ) + { + case IK_SELF: + { + // xform IK target error into world space + matrix3x4 local = matrix3x4( pRule->pos, pRule->q ); + + // eval target bone space + if( pRule->bone != -1 ) + { + BuildBoneChain( pos, q, pRule->bone, boneToWorld, pBoneSet ); + worldTarget = boneToWorld[pRule->bone].ConcatTransforms( local ); + } + else + { + worldTarget = m_rootxform.ConcatTransforms( local ); + } + + float flWeight = pRule->flWeight * pRule->flRuleWeight; + pChainResult->flWeight = pChainResult->flWeight * (1 - flWeight) + flWeight; + + Vector p2; + Vector4D q2; + + // target p and q + q2 = worldTarget.GetQuaternion(); + p2 = worldTarget.GetOrigin(); + + // m_pBoneSetup->debugLine( pChainResult->pos, p2, 0, 0, 255, true, 0.1 ); + + // blend in position and angles + pChainResult->pos = pChainResult->pos * (1.0f - flWeight) + p2 * flWeight; + QuaternionSlerp( pChainResult->q, q2, flWeight, pChainResult->q ); + } + break; + case IK_WORLD: + break; + case IK_ATTACHMENT: + break; + case IK_GROUND: + break; + case IK_RELEASE: + { + // move target back towards original location + float flWeight = pRule->flWeight * pRule->flRuleWeight; + const mstudioikchain_t *pchain = m_pBoneSetup->pIKChain( pRule->chain ); + int bone = m_pBoneSetup->pIKLink( pchain, 2 )->bone; + + Vector p2; + Vector4D q2; + + BuildBoneChain( pos, q, bone, boneToWorld, pBoneSet ); + q2 = boneToWorld[bone].GetQuaternion(); + p2 = boneToWorld[bone].GetOrigin(); + + // blend in position and angles + pChainResult->pos = pChainResult->pos * (1.0 - flWeight) + p2 * flWeight; + QuaternionSlerp( pChainResult->q, q2, flWeight, pChainResult->q ); + } + break; + case IK_UNLATCH: + { + /* + pChainResult->flWeight = pChainResult->flWeight * (1 - pRule->flWeight) + pRule->flWeight; + + pChainResult->pos = pChainResult->pos * (1.0 - pRule->flWeight ) + pChainResult->local.pos * pRule->flWeight; + QuaternionSlerp( pChainResult->q, pChainResult->local.q, pRule->flWeight, pChainResult->q ); + */ + } + break; + } + } + } + + for (i = 0; i < m_target.Count(); i++) + { + CIKTarget *pTarget = &m_target[i]; + + if( m_target[i].est.flWeight > 0.0f ) + { + ikchainresult_t *pChainResult = &chainResult[ pTarget->chain ]; + matrix3x4 local = matrix3x4( pTarget->offset.pos, pTarget->offset.q ); + matrix3x4 worldFootpad = matrix3x4( pTarget->est.pos, pTarget->est.q ); + worldTarget = worldFootpad.ConcatTransforms( local ); + + Vector p2; + Vector4D q2; + + // target p and q + q2 = worldTarget.GetQuaternion(); + p2 = worldTarget.GetOrigin(); + + // blend in position and angles + pChainResult->flWeight = pTarget->est.flWeight; + pChainResult->pos = pChainResult->pos * (1.0 - pChainResult->flWeight ) + p2 * pChainResult->flWeight; + QuaternionSlerp( pChainResult->q, q2, pChainResult->flWeight, pChainResult->q ); + } + + if( pTarget->latched.bNeedsLatch ) + { + // keep track of latch position + pTarget->latched.bHasLatch = true; + pTarget->latched.q = pTarget->est.q; + pTarget->latched.pos = pTarget->est.pos; + } + } + + for( i = 0; i < m_pBoneSetup->GetNumIKChains(); i++ ) + { + ikchainresult_t *pChainResult = &chainResult[ i ]; + const mstudioikchain_t *pchain = m_pBoneSetup->pIKChain( i ); + + if( pChainResult->flWeight > 0.0f ) + { + int bone0 = m_pBoneSetup->pIKLink( pchain, 0 )->bone; + int bone1 = m_pBoneSetup->pIKLink( pchain, 1 )->bone; + int bone2 = m_pBoneSetup->pIKLink( pchain, 2 )->bone; + + Vector tmp = boneToWorld[bone2].GetOrigin(); + // m_pBoneSetup->debugLine( pChainResult->pos, tmp, 255, 255, 255, true, 0.1 ); + + // do exact IK solution + // FIXME: once per link! + if( m_pBoneSetup->SolveIK( pchain, pChainResult->pos, boneToWorld )) + { + Vector p3 = boneToWorld[bone2].GetOrigin(); + // replace rotational component with IK result + boneToWorld[bone2] = matrix3x4( p3, pChainResult->q ); + + // rebuild chain + // FIXME: is this needed if everyone past this uses the boneToWorld array? + m_pBoneSetup->SolveBone( bone2, boneToWorld, pos, q ); + m_pBoneSetup->SolveBone( bone1, boneToWorld, pos, q ); + m_pBoneSetup->SolveBone( bone0, boneToWorld, pos, q ); + } + else + { + // FIXME: need to invalidate the targets that forced this... + if( pChainResult->target != -1 ) + { + CIKTarget *pTarget = &m_target[pChainResult->target]; + QuaternionScale( pTarget->latched.deltaQ, 0.8f, pTarget->latched.deltaQ ); + pTarget->latched.deltaPos *= 0.8f; + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CIKContext :: SolveAutoplayLocks( Vector pos[], Vector4D q[] ) +{ + for( int i = 0; i < m_ikLock.Count(); i++ ) + { + const mstudioiklock_t *lock = m_pBoneSetup->pIKAutoplayLock( i ); + SolveLock( lock, i, pos, q, m_boneToWorld ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CIKContext :: SolveSequenceLocks( mstudioseqdesc_t *pseqdesc, Vector pos[], Vector4D q[] ) +{ + for( int i = 0; i < m_ikLock.Count(); i++ ) + { + const mstudioiklock_t *plock = m_pBoneSetup->pIKLock( pseqdesc, i ); + SolveLock( plock, i, pos, q, m_boneToWorld ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CIKContext :: AddAllLocks( Vector pos[], Vector4D q[] ) +{ + // skip all array access if no autoplay locks. + if( m_pBoneSetup->GetNumIKChains() == 0 ) + { + return; + } + + int ikOffset = m_ikLock.AddMultipleToTail( m_pBoneSetup->GetNumIKChains() ); + memset( &m_ikLock[ikOffset], 0, sizeof( ikcontextikrule_t ) * m_pBoneSetup->GetNumIKChains() ); + + for( int i = 0; i < m_pBoneSetup->GetNumIKChains(); i++ ) + { + const mstudioikchain_t *pchain = m_pBoneSetup->pIKChain( i ); + int bone = m_pBoneSetup->pIKLink( pchain, 2 )->bone; + + // don't bother with iklock if the bone isn't going to be calculated + if( !m_pBoneSetup->IsBoneUsed( bone )) + continue; + + // eval current ik'd bone + BuildBoneChain( pos, q, bone, m_boneToWorld ); + + ikcontextikrule_t *ikrule = &m_ikLock[ikOffset + i]; + + ikrule->type = IK_WORLD; + ikrule->chain = i; + ikrule->slot = i; + + ikrule->q = m_boneToWorld[bone].GetQuaternion(); + ikrule->pos = m_boneToWorld[bone].GetOrigin(); + + // save off current knee direction + if( m_pBoneSetup->pIKLink( pchain, 0 )->kneeDir.LengthSqr() > 0.0f ) + { + const mstudioiklink_t *link0 = m_pBoneSetup->pIKLink( pchain, 0 ); + ikrule->kneeDir = m_boneToWorld[link0->bone].VectorRotate( link0->kneeDir ); + ikrule->kneePos = m_boneToWorld[link0->bone].GetOrigin(); + + } + else + { + ikrule->kneeDir.Init( ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CIKContext :: SolveAllLocks( Vector pos[], Vector4D q[] ) +{ + mstudioiklock_t lock; + + for( int i = 0; i < m_ikLock.Count(); i++ ) + { + lock.chain = i; + lock.flPosWeight = 1.0f; + lock.flLocalQWeight = 0.0f; + lock.flags = 0; + + SolveLock( &lock, i, pos, q, m_boneToWorld ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CIKContext :: SolveLock( const mstudioiklock_t *plock, int i, Vector pos[], Vector4D q[], matrix3x4 boneToWorld[], byte *pBoneSet ) +{ + const mstudioikchain_t *pchain = m_pBoneSetup->pIKChain( plock->chain ); + int bone0 = m_pBoneSetup->pIKLink( pchain, 0 )->bone; + int bone1 = m_pBoneSetup->pIKLink( pchain, 1 )->bone; + int bone2 = m_pBoneSetup->pIKLink( pchain, 2 )->bone; + + // don't bother with iklock if the bone isn't going to be calculated + if( !m_pBoneSetup->IsBoneUsed( bone2 )) + return; + + // eval current ik'd bone + BuildBoneChain( pos, q, bone2, boneToWorld, pBoneSet ); + + Vector p1, p2, p3; + Vector4D q2, q3; + + // current p and q + p1 = boneToWorld[bone2].GetOrigin(); + + // blend in position + p3 = p1 * (1.0 - plock->flPosWeight ) + m_ikLock[i].pos * plock->flPosWeight; + + // do exact IK solution + if( m_ikLock[i].kneeDir.LengthSqr() > 0.0f ) + m_pBoneSetup->SolveIK( bone0, bone1, bone2, p3, m_ikLock[i].kneePos, m_ikLock[i].kneeDir, boneToWorld ); + else m_pBoneSetup->SolveIK(pchain, p3, boneToWorld ); + + // slam orientation + p3 = boneToWorld[bone2].GetOrigin(); + boneToWorld[bone2] = matrix3x4( p3, m_ikLock[i].q ); + + // rebuild chain + q2 = q[bone2]; + m_pBoneSetup->SolveBone( bone2, boneToWorld, pos, q ); + QuaternionSlerp( q[bone2], q2, plock->flLocalQWeight, q[bone2] ); + m_pBoneSetup->SolveBone( bone1, boneToWorld, pos, q ); + m_pBoneSetup->SolveBone( bone0, boneToWorld, pos, q ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CIKTarget :: SetOwner( int entindex, const Vector &pos, const Vector &angles ) +{ + latched.owner = entindex; + latched.absOrigin = pos; + latched.absAngles = angles; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CIKTarget :: ClearOwner( void ) +{ + latched.owner = -1; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CIKTarget :: GetOwner( void ) +{ + return latched.owner; +} + +//----------------------------------------------------------------------------- +// Purpose: update the latched IK values that are in a moving frame of reference +//----------------------------------------------------------------------------- +void CIKTarget :: UpdateOwner( int entindex, const Vector &pos, const Vector &angles ) +{ + if( pos == latched.absOrigin && angles == latched.absAngles ) + return; + + matrix3x4 in = matrix3x4( pos, angles ); + matrix3x4 out = matrix3x4( latched.absOrigin, latched.absAngles ).Invert(); + + matrix3x4 tmp1 = matrix3x4( latched.pos, latched.q ); + matrix3x4 tmp2 = out.ConcatTransforms( tmp1 ); + tmp1 = in.ConcatTransforms( tmp2 ); + + latched.q = tmp1.GetQuaternion(); + latched.pos = tmp1.GetOrigin(); +} + +//----------------------------------------------------------------------------- +// Purpose: sets the ground position of an ik target +//----------------------------------------------------------------------------- +void CIKTarget :: SetPos( const Vector &pos ) +{ + est.pos = pos; +} + +//----------------------------------------------------------------------------- +// Purpose: sets the ground "identity" orientation of an ik target +//----------------------------------------------------------------------------- +void CIKTarget :: SetAngles( const Vector &angles ) +{ + AngleQuaternion( angles, est.q ); +} + +//----------------------------------------------------------------------------- +// Purpose: sets the ground "identity" orientation of an ik target +//----------------------------------------------------------------------------- +void CIKTarget :: SetQuaternion( const Vector4D &q ) +{ + est.q = q; +} + +//----------------------------------------------------------------------------- +// Purpose: calculates a ground "identity" orientation based on the surface +// normal of the ground and the desired ground identity orientation +//----------------------------------------------------------------------------- +void CIKTarget :: SetNormal( const Vector &normal ) +{ + // recalculate foot angle based on slope of surface + matrix3x4 m1 = matrix3x4( g_vecZero, est.q ); + Vector forward, right; + + right = m1.GetRight(); + forward = CrossProduct( right, normal ); + right = CrossProduct( normal, forward ); + + m1.SetForward( forward ); + m1.SetRight( right ); + m1.SetUp( normal ); + + est.q = m1.GetQuaternion(); +} + +//----------------------------------------------------------------------------- +// Purpose: estimates the ground impact at the center location assuming a the edge of +// an Z axis aligned disc collided with it the surface. +//----------------------------------------------------------------------------- +void CIKTarget :: SetPosWithNormalOffset( const Vector &pos, const Vector &normal ) +{ + // assume it's a disc edge intersecting with the floor, so try to estimate the z location of the center + est.pos = pos; + + if( normal.z > 0.9999f ) + { + return; + } + else if( normal.z > 0.707 ) + { + // clamp at 45 degrees + // tan == sin / cos + float tan = sqrt( 1.0f - normal.z * normal.z ) / normal.z; + est.pos.z = est.pos.z - est.radius * tan; + } + else + { + est.pos.z = est.pos.z - est.radius; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CIKTarget :: SetOnWorld( bool bOnWorld ) +{ + est.onWorld = bOnWorld; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CIKTarget :: IsActive( void ) +{ + return (est.flWeight > 0.0f); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CIKTarget :: IKFailed( void ) +{ + latched.deltaPos.Init(); + latched.deltaQ.Init(); + latched.pos = ideal.pos; + latched.q = ideal.q; + est.latched = 0.0; + est.flWeight = 0.0; + est.onWorld = false; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CIKTarget :: MoveReferenceFrame( Vector &deltaPos, Vector &deltaAngles ) +{ + est.pos -= deltaPos; + latched.pos -= deltaPos; + offset.pos -= deltaPos; + ideal.pos -= deltaPos; +} \ No newline at end of file diff --git a/game_shared/ikcontext.h b/game_shared/ikcontext.h new file mode 100644 index 0000000..e65c641 --- /dev/null +++ b/game_shared/ikcontext.h @@ -0,0 +1,209 @@ +/* +ikcontext.h - Inverse Kinematic implementation +Copyright (C) 2017 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef IKCONTEXT_H +#define IKCONTEXT_H + +#include +#include + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +class CIKContext; + +// ik info +class CIKTarget +{ +public: + void SetOwner( int entindex, const Vector &pos, const Vector &angles ); + void ClearOwner( void ); + int GetOwner( void ); + void UpdateOwner( int entindex, const Vector &pos, const Vector &angles ); + void SetPos( const Vector &pos ); + void SetAngles( const Vector &angles ); + void SetQuaternion( const Vector4D &q ); + void SetNormal( const Vector &normal ); + void SetPosWithNormalOffset( const Vector &pos, const Vector &normal ); + void SetOnWorld( bool bOnWorld = true ); + + bool IsActive( void ); + void IKFailed( void ); + int chain; + int type; + void MoveReferenceFrame( Vector &deltaPos, Vector &deltaAngles ); + // accumulated offset from ideal footplant location +public: + struct x2 + { + int attachmentIndex; + Vector pos; + Vector4D q; + } offset; +private: + struct x3 + { + Vector pos; + Vector4D q; + } ideal; +public: + struct x4 + { + float latched; + float release; + float height; + float floor; + float radius; + float flWeight; + Vector pos; + Vector4D q; + bool onWorld; + } est; // estimate contact position + + struct x5 + { + float hipToFoot; // distance from hip + float hipToKnee; // distance from hip to knee + float kneeToFoot; // distance from knee to foot + Vector hip; // location of hip + Vector closest; // closest valid location from hip to foot that the foot can move to + Vector knee; // pre-ik location of knee + Vector farthest; // farthest valid location from hip to foot that the foot can move to + Vector lowest; // lowest position directly below hip that the foot can drop to + } trace; +private: + // internally latched footset, position + struct x1 + { + bool bNeedsLatch; + bool bHasLatch; + float influence; + int iFramecounter; + int owner; + Vector absOrigin; + Vector absAngles; + Vector pos; + Vector4D q; + Vector deltaPos; // acculated error + Vector4D deltaQ; + Vector debouncePos; + Vector4D debounceQ; + } latched; + + struct x6 + { + float flTime; // time last error was detected + float flErrorTime; + float ramp; + bool bInError; + } error; + + friend class CIKContext; +}; + +typedef struct +{ + // accumulated offset from ideal footplant location + int target; + Vector pos; + Vector4D q; + float flWeight; +} ikchainresult_t; + +struct ikcontextikrule_t +{ + int index; + + int type; + int chain; + + int bone; + + int slot; // iktarget slot. Usually same as chain. + float height; + float radius; + float floor; + Vector pos; + Vector4D q; + + float start; // beginning of influence + float peak; // start of full influence + float tail; // end of full influence + float end; // end of all influence + + float top; + float drop; + + float commit; // frame footstep target should be committed + float release; // frame ankle should end rotation from latched orientation + + float flWeight; // processed version of start-end cycle + float flRuleWeight; // blending weight + float latched; // does the IK rule use a latched value? + int iAttachment; + + Vector kneeDir; + Vector kneePos; + + ikcontextikrule_t() {} + +private: + // No copy constructors allowed + ikcontextikrule_t(const ikcontextikrule_t& vOther); +}; + +class CIKContext +{ +public: + CIKContext( ); + void Init( const CStudioBoneSetup *pBoneSetup, const Vector &angles, const Vector &pos, float flTime, int iFramecounter ); + void AddDependencies( mstudioseqdesc_t *pseqdesc, int iSequence, float flCycle, float flWeight = 1.0f ); + + void ClearTargets( void ); + void UpdateTargets( Vector pos[], Vector4D q[], matrix3x4 boneToWorld[], byte *pBoneSet = NULL ); + void AutoIKRelease( void ); + void SolveDependencies( Vector pos[], Vector4D q[], matrix3x4 boneToWorld[], byte *pBoneSet = NULL ); + + void AddAutoplayLocks( Vector pos[], Vector4D q[] ); + void SolveAutoplayLocks( Vector pos[], Vector4D q[] ); + + void AddSequenceLocks( mstudioseqdesc_t *pseqdesc, Vector pos[], Vector4D q[] ); + void SolveSequenceLocks( mstudioseqdesc_t *pseqdesc, Vector pos[], Vector4D q[] ); + + void AddAllLocks( Vector pos[], Vector4D q[] ); + void SolveAllLocks( Vector pos[], Vector4D q[] ); + + void SolveLock( const mstudioiklock_t *plock, int i, Vector pos[], Vector4D q[], matrix3x4 boneToWorld[], byte *pBoneSet = NULL ); + + CUtlArrayFixed< CIKTarget, 12 > m_target; + +private: + CStudioBoneSetup *m_pBoneSetup; + + bool Estimate( int iSequence, float flCycle, int iTarget, const float poseParameter[], float flWeight = 1.0f ); + void BuildBoneChain( const Vector pos[], const Vector4D q[], int iBone, matrix3x4 *pBoneToWorld, byte *pBoneSet = NULL ); + + // virtual IK rules, filtered and combined from each sequence + CUtlArray< CUtlArray< ikcontextikrule_t > > m_ikChainRule; + CUtlArray< ikcontextikrule_t > m_ikLock; + + static matrix3x4 m_boneToWorld[MAXSTUDIOBONES]; + matrix3x4 m_rootxform; + + int m_iFramecounter; + float m_flTime; +}; + +#endif//IKCONTEXT \ No newline at end of file diff --git a/game_shared/iksolver.h b/game_shared/iksolver.h new file mode 100644 index 0000000..4871771 --- /dev/null +++ b/game_shared/iksolver.h @@ -0,0 +1,89 @@ +/* +iksolver.h - Ken Perlin' inverse kinematic solver +Copyright (C) 2017 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef IKSOLVER_H +#define IKSOLVER_H + +class CIKSolver +{ +public: + //------------ GENERAL VECTOR MATH SUPPORT ----------- + static float dot( float const a[], float const b[] ) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; } + static float length( float const v[] ) { return sqrt( dot( v, v )); } + static void normalize( float v[] ) { float norm = length( v ); v[0] /= norm; v[1] /= norm; v[2] /= norm; } + static float findD( float a, float b, float c ) { return (c + (a * a - b * b) / c) / 2; } + static float findE( float a, float d ) { return sqrt(a * a - d * d); } + + static void cross( float const a[], float const b[], float c[] ) + { + c[0] = a[1] * b[2] - a[2] * b[1]; + c[1] = a[2] * b[0] - a[0] * b[2]; + c[2] = a[0] * b[1] - a[1] * b[0]; + } + + static void rot( float const M[3][3], float const src[], float dst[] ) + { + dst[0] = dot( M[0], src ); + dst[1] = dot( M[1], src ); + dst[2] = dot( M[2], src ); + } + + void defineM( float const P[], float const D[] ) + { + float *X = Minv[0], *Y = Minv[1], *Z = Minv[2]; + int i; + + for( i = 0; i < 3; i++ ) + X[i] = P[i]; + normalize( X ); + + // Its y axis is perpendicular to P, so Y = unit( E - X(E·X) ). + float dDOTx = dot( D, X ); + + for( i = 0; i < 3; i++ ) + Y[i] = D[i] - dDOTx * X[i]; + normalize( Y ); + + // Its z axis is perpendicular to both X and Y, so Z = X×Y. + cross( X, Y, Z ); + + // Mfwd = (Minv)T, since transposing inverts a rotation matrix. + for( i = 0; i < 3; i++ ) + { + Mfwd[i][0] = Minv[0][i]; + Mfwd[i][1] = Minv[1][i]; + Mfwd[i][2] = Minv[2][i]; + } + } + + bool solve( float A, float B, float const P[], float const D[], float Q[] ) + { + float R[3]; + + defineM( P, D ); + rot( Minv, P, R ); + float r = length( R ); + float d = findD( A, B, r ); + float e = findE( A, d ); + float S[3] = { d, e, 0 }; + rot( Mfwd, S, Q ); + return d > (r - B) && d < A; + } + + float Mfwd[3][3]; + float Minv[3][3]; +}; + +#endif//IKSOLVER_H \ No newline at end of file diff --git a/game_shared/jigglebones.cpp b/game_shared/jigglebones.cpp new file mode 100644 index 0000000..728aeb1 --- /dev/null +++ b/game_shared/jigglebones.cpp @@ -0,0 +1,506 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#include "jigglebones.h" + +//----------------------------------------------------------------------------- +JiggleData * CJiggleBones::GetJiggleData( int bone, float currenttime, const Vector &initBasePos, const Vector &initTipPos ) +{ + FOR_EACH_LL( m_jiggleBoneState, it ) + { + if ( m_jiggleBoneState[it].bone == bone ) + { + return &m_jiggleBoneState[it]; + } + } + + JiggleData data; + data.Init( bone, currenttime, initBasePos, initTipPos ); + + int idx = m_jiggleBoneState.AddToHead( data ); + if ( idx == m_jiggleBoneState.InvalidIndex() ) + return NULL; + + return &m_jiggleBoneState[idx]; +} + + +//----------------------------------------------------------------------------- +/** + * Do spring physics calculations and update "jiggle bone" matrix + * (Michael Booth, Turtle Rock Studios) + */ +void CJiggleBones::BuildJiggleTransformations( int boneIndex, float currenttime, const mstudiojigglebone_t *jiggleInfo, const matrix3x4 &goalMX, matrix3x4 &boneMX ) +{ + Vector goalBasePosition = goalMX[3]; + + Vector goalForward = goalMX[2]; + Vector goalUp = goalMX[1]; + Vector goalLeft = goalMX[0]; + + // compute goal tip position + Vector goalTip = goalBasePosition + jiggleInfo->length * goalForward; + + JiggleData *data = GetJiggleData( boneIndex, currenttime, goalBasePosition, goalTip ); + if( !data ) return; + + if( currenttime - data->lastUpdate > 0.5f ) + { + data->Init( boneIndex, currenttime, goalBasePosition, goalTip ); + } + + // limit maximum deltaT to avoid simulation blowups + // if framerate gets very low, jiggle will run in slow motion + const float thirtyHZ = 0.0333f; + const float thousandHZ = 0.001f; + float deltaT = bound( thousandHZ, currenttime - data->lastUpdate, thirtyHZ ); + data->lastUpdate = currenttime; + + // + // Bone tip flex + // + if (jiggleInfo->flags & (JIGGLE_IS_FLEXIBLE | JIGGLE_IS_RIGID)) + { + // apply gravity in global space + data->tipAccel.z -= jiggleInfo->tipMass; + + if (jiggleInfo->flags & JIGGLE_IS_FLEXIBLE) + { + // decompose into local coordinates + Vector error = goalTip - data->tipPos; + + Vector localError; + localError.x = DotProduct( goalLeft, error ); + localError.y = DotProduct( goalUp, error ); + localError.z = DotProduct( goalForward, error ); + + Vector localVel; + localVel.x = DotProduct( goalLeft, data->tipVel ); + localVel.y = DotProduct( goalUp, data->tipVel ); + + // yaw spring + float yawAccel = jiggleInfo->yawStiffness * localError.x - jiggleInfo->yawDamping * localVel.x; + + // pitch spring + float pitchAccel = jiggleInfo->pitchStiffness * localError.y - jiggleInfo->pitchDamping * localVel.y; + + if (jiggleInfo->flags & JIGGLE_HAS_LENGTH_CONSTRAINT) + { + // drive tip towards goal tip position + data->tipAccel += yawAccel * goalLeft + pitchAccel * goalUp; + } + else + { + // allow flex along length of spring + localVel.z = DotProduct( goalForward, data->tipVel ); + + // along spring + float alongAccel = jiggleInfo->alongStiffness * localError.z - jiggleInfo->alongDamping * localVel.z; + + // drive tip towards goal tip position + data->tipAccel += yawAccel * goalLeft + pitchAccel * goalUp + alongAccel * goalForward; + } + } + + + // simple euler integration + data->tipVel += data->tipAccel * deltaT; + data->tipPos += data->tipVel * deltaT; + + // clear this timestep's accumulated accelerations + data->tipAccel = g_vecZero; + + // + // Apply optional constraints + // + if (jiggleInfo->flags & (JIGGLE_HAS_YAW_CONSTRAINT | JIGGLE_HAS_PITCH_CONSTRAINT)) + { + // find components of spring vector in local coordinate system + Vector along = data->tipPos - goalBasePosition; + Vector localAlong; + localAlong.x = DotProduct( goalLeft, along ); + localAlong.y = DotProduct( goalUp, along ); + localAlong.z = DotProduct( goalForward, along ); + + Vector localVel; + localVel.x = DotProduct( goalLeft, data->tipVel ); + localVel.y = DotProduct( goalUp, data->tipVel ); + localVel.z = DotProduct( goalForward, data->tipVel ); + + if (jiggleInfo->flags & JIGGLE_HAS_YAW_CONSTRAINT) + { + // enforce yaw constraints in local XZ plane + float yawError = atan2( localAlong.x, localAlong.z ); + + bool isAtLimit = false; + float yaw = 0.0f; + + if (yawError < jiggleInfo->minYaw) + { + // at angular limit + isAtLimit = true; + yaw = jiggleInfo->minYaw; + } + else if (yawError > jiggleInfo->maxYaw) + { + // at angular limit + isAtLimit = true; + yaw = jiggleInfo->maxYaw; + } + + if (isAtLimit) + { + float sy, cy; + SinCos( yaw, &sy, &cy ); + + // yaw matrix + matrix3x4 yawMatrix; + + yawMatrix.SetForward( Vector( cy, 0, -sy )); + yawMatrix.SetRight( Vector( 0.0f, 1.0f, 0.0f )); + yawMatrix.SetUp( Vector( sy, 0.0f, cy )); + yawMatrix.SetOrigin( g_vecZero ); + + // global coordinates of limit + matrix3x4 limitMatrix = goalMX.ConcatTransforms( yawMatrix ); + + Vector limitLeft( limitMatrix[0] ); + Vector limitUp( limitMatrix[1] ); + Vector limitForward( limitMatrix[2] ); + Vector limitAlong( DotProduct( limitLeft, along ), DotProduct( limitUp, along ), DotProduct( limitForward, along )); + + // clip to limit plane + data->tipPos = goalBasePosition + limitAlong.y * limitUp + limitAlong.z * limitForward; + + // yaw friction - rubbing along limit plane + Vector limitVel; + limitVel.y = DotProduct( limitUp, data->tipVel ); + limitVel.z = DotProduct( limitForward, data->tipVel ); + + data->tipAccel -= jiggleInfo->yawFriction * (limitVel.y * limitUp + limitVel.z * limitForward); + + // update velocity reaction to hitting constraint + data->tipVel = -jiggleInfo->yawBounce * limitVel.x * limitLeft + limitVel.y * limitUp + limitVel.z * limitForward; + + // update along vectors for use by pitch constraint + along = data->tipPos - goalBasePosition; + localAlong.x = DotProduct( goalLeft, along ); + localAlong.y = DotProduct( goalUp, along ); + localAlong.z = DotProduct( goalForward, along ); + + localVel.x = DotProduct( goalLeft, data->tipVel ); + localVel.y = DotProduct( goalUp, data->tipVel ); + localVel.z = DotProduct( goalForward, data->tipVel ); + } + } + + + if (jiggleInfo->flags & JIGGLE_HAS_PITCH_CONSTRAINT) + { + // enforce pitch constraints in local YZ plane + float pitchError = atan2( localAlong.y, localAlong.z ); + + bool isAtLimit = false; + float pitch = 0.0f; + + if (pitchError < jiggleInfo->minPitch) + { + // at angular limit + isAtLimit = true; + pitch = jiggleInfo->minPitch; + } + else if (pitchError > jiggleInfo->maxPitch) + { + // at angular limit + isAtLimit = true; + pitch = jiggleInfo->maxPitch; + } + + if (isAtLimit) + { + float sp, cp; + SinCos( pitch, &sp, &cp ); + + // pitch matrix + matrix3x4 pitchMatrix; + + pitchMatrix.SetForward( Vector( 1.0f, 0.0, 0.0f )); + pitchMatrix.SetRight( Vector( 0, cp, -sp )); + pitchMatrix.SetUp( Vector( 0, sp, cp )); + pitchMatrix.SetOrigin( g_vecZero ); + + // global coordinates of limit + matrix3x4 limitMatrix = goalMX.ConcatTransforms( pitchMatrix ); + + Vector limitLeft( limitMatrix[0] ); + Vector limitUp( limitMatrix[1] ); + Vector limitForward( limitMatrix[2] ); + + Vector limitAlong( DotProduct( limitLeft, along ), DotProduct( limitUp, along ), DotProduct( limitForward, along )); + + // clip to limit plane + data->tipPos = goalBasePosition + limitAlong.x * limitLeft + limitAlong.z * limitForward; + + // pitch friction - rubbing along limit plane + Vector limitVel; + limitVel.y = DotProduct( limitUp, data->tipVel ); + limitVel.z = DotProduct( limitForward, data->tipVel ); + + data->tipAccel -= jiggleInfo->pitchFriction * (limitVel.x * limitLeft + limitVel.z * limitForward); + + // update velocity reaction to hitting constraint + data->tipVel = limitVel.x * limitLeft - jiggleInfo->pitchBounce * limitVel.y * limitUp + limitVel.z * limitForward; + } + } + } + + // needed for matrix assembly below + Vector forward = (data->tipPos - goalBasePosition).Normalize(); + + if (jiggleInfo->flags & JIGGLE_HAS_ANGLE_CONSTRAINT) + { + // enforce max angular error + Vector error = goalTip - data->tipPos; + float dot = DotProduct( forward, goalForward ); + float angleBetween = acos( dot ); + if (dot < 0.0f) + { + angleBetween = 2.0f * M_PI - angleBetween; + } + + if (angleBetween > jiggleInfo->angleLimit) + { + // at angular limit + float maxBetween = jiggleInfo->length * sin( jiggleInfo->angleLimit ); + + Vector delta = (goalTip - data->tipPos).Normalize(); + + data->tipPos = goalTip - maxBetween * delta; + + forward = (data->tipPos - goalBasePosition).Normalize(); + } + } + + if (jiggleInfo->flags & JIGGLE_HAS_LENGTH_CONSTRAINT) + { + // enforce spring length + data->tipPos = goalBasePosition + jiggleInfo->length * forward; + + // zero velocity along forward bone axis + data->tipVel -= DotProduct( data->tipVel, forward ) * forward; + } + + // + // Build bone matrix to align along current tip direction + // + Vector left = CrossProduct( goalUp, forward ).Normalize(); + + Vector up = CrossProduct( forward, left ); + + boneMX.SetForward( left ); + boneMX.SetRight( up ); + boneMX.SetUp( forward ); + boneMX.SetOrigin( goalBasePosition ); + } + + // + // Bone base flex + // + if (jiggleInfo->flags & JIGGLE_HAS_BASE_SPRING) + { + // gravity + data->baseAccel.z -= jiggleInfo->baseMass; + + // simple spring + Vector error = goalBasePosition - data->basePos; + data->baseAccel += jiggleInfo->baseStiffness * error - jiggleInfo->baseDamping * data->baseVel; + + data->baseVel += data->baseAccel * deltaT; + data->basePos += data->baseVel * deltaT; + + // clear this timestep's accumulated accelerations + data->baseAccel = g_vecZero; + + // constrain to limits + error = data->basePos - goalBasePosition; + Vector localError; + localError.x = DotProduct( goalLeft, error ); + localError.y = DotProduct( goalUp, error ); + localError.z = DotProduct( goalForward, error ); + + Vector localVel; + localVel.x = DotProduct( goalLeft, data->baseVel ); + localVel.y = DotProduct( goalUp, data->baseVel ); + localVel.z = DotProduct( goalForward, data->baseVel ); + + // horizontal constraint + if (localError.x < jiggleInfo->baseMinLeft) + { + localError.x = jiggleInfo->baseMinLeft; + + // friction + data->baseAccel -= jiggleInfo->baseLeftFriction * (localVel.y * goalUp + localVel.z * goalForward); + } + else if (localError.x > jiggleInfo->baseMaxLeft) + { + localError.x = jiggleInfo->baseMaxLeft; + + // friction + data->baseAccel -= jiggleInfo->baseLeftFriction * (localVel.y * goalUp + localVel.z * goalForward); + } + + if (localError.y < jiggleInfo->baseMinUp) + { + localError.y = jiggleInfo->baseMinUp; + + // friction + data->baseAccel -= jiggleInfo->baseUpFriction * (localVel.x * goalLeft + localVel.z * goalForward); + } + else if (localError.y > jiggleInfo->baseMaxUp) + { + localError.y = jiggleInfo->baseMaxUp; + + // friction + data->baseAccel -= jiggleInfo->baseUpFriction * (localVel.x * goalLeft + localVel.z * goalForward); + } + + if (localError.z < jiggleInfo->baseMinForward) + { + localError.z = jiggleInfo->baseMinForward; + + // friction + data->baseAccel -= jiggleInfo->baseForwardFriction * (localVel.x * goalLeft + localVel.y * goalUp); + } + else if (localError.z > jiggleInfo->baseMaxForward) + { + localError.z = jiggleInfo->baseMaxForward; + + // friction + data->baseAccel -= jiggleInfo->baseForwardFriction * (localVel.x * goalLeft + localVel.y * goalUp); + } + + data->basePos = goalBasePosition + localError.x * goalLeft + localError.y * goalUp + localError.z * goalForward; + + + // fix up velocity + data->baseVel = (data->basePos - data->baseLastPos) / deltaT; + data->baseLastPos = data->basePos; + + + if (!(jiggleInfo->flags & (JIGGLE_IS_FLEXIBLE | JIGGLE_IS_RIGID))) + { + // no tip flex - use bone's goal orientation + boneMX = goalMX; + } + + // update bone position + boneMX.SetOrigin( data->basePos ); + } + else if ( jiggleInfo->flags & JIGGLE_IS_BOING ) + { + // estimate velocity + Vector vel = goalBasePosition - data->lastBoingPos; + + data->lastBoingPos = goalBasePosition; + + float speed = vel.Length(); + vel = vel.Normalize(); + + if( speed < 0.00001f ) + { + vel = Vector( 0.0f, 0.0f, 1.0f ); + speed = 0.0f; + } + else + { + speed /= deltaT; + } + + data->boingTime += deltaT; + + // if velocity changed a lot, we impacted and should *boing* + const float minSpeed = 5.0f; // 15.0f; + const float minReBoingTime = 0.5f; + + if(( speed > minSpeed || data->boingSpeed > minSpeed ) && data->boingTime > minReBoingTime ) + { + if( fabs( data->boingSpeed - speed ) > jiggleInfo->boingImpactSpeed || DotProduct( vel, data->boingVelDir ) < jiggleInfo->boingImpactAngle ) + { + data->boingTime = 0.0f; + data->boingDir = -vel; + } + } + + data->boingVelDir = vel; + data->boingSpeed = speed; + + float damping = 1.0f - ( jiggleInfo->boingDampingRate * data->boingTime ); + if ( damping < 0.01f ) + { + // boing has entirely damped out + boneMX = goalMX; + } + else + { + damping *= damping; + damping *= damping; + + float flex = jiggleInfo->boingAmplitude * cos( jiggleInfo->boingFrequency * data->boingTime ) * damping; + float squash = 1.0f + flex; + float stretch = 1.0f - flex; + + boneMX.SetForward( goalLeft ); + boneMX.SetRight( goalUp ); + boneMX.SetUp( goalForward ); + boneMX.SetOrigin( g_vecZero ); + + // build transform into "boing space", where Z is along primary boing axis + Vector boingSide; + if( fabs( data->boingDir.x ) < 0.9f ) + { + boingSide = CrossProduct( data->boingDir, Vector( 1.0f, 0.0f, 0.0f )).Normalize(); + } + else + { + boingSide = CrossProduct( data->boingDir, Vector( 0.0f, 0.0f, 1.0f )).Normalize(); + } + + Vector boingOtherSide = CrossProduct( data->boingDir, boingSide ); + + matrix3x4 xfrmToBoingCoordsMX, xfrmFromBoingCoordsMX; + + xfrmToBoingCoordsMX.SetForward( boingSide ); + xfrmToBoingCoordsMX.SetRight( boingOtherSide ); + xfrmToBoingCoordsMX.SetUp( data->boingDir ); + xfrmToBoingCoordsMX.SetOrigin( g_vecZero ); + + // transform back from boing space (inverse is transpose since orthogonal) + xfrmFromBoingCoordsMX = xfrmToBoingCoordsMX; + xfrmToBoingCoordsMX = xfrmToBoingCoordsMX.Transpose(); + + // build squash and stretch transform in "boing space" + matrix3x4 boingMX; + + boingMX.SetForward( Vector( squash, 0.0f, 0.0f )); + boingMX.SetRight( Vector( 0.0f, squash, 0.0f )); + boingMX.SetUp( Vector( 0.0f, 0.0f, stretch )); + boingMX.SetOrigin( g_vecZero ); + + // put it all together + matrix3x4 xfrmMX; + xfrmMX = xfrmToBoingCoordsMX.ConcatTransforms( boingMX ); + xfrmMX = xfrmMX.ConcatTransforms( xfrmFromBoingCoordsMX ); + boneMX = boneMX.ConcatTransforms( xfrmMX ); + boneMX.SetOrigin( goalBasePosition ); + } + } + else if (!(jiggleInfo->flags & (JIGGLE_IS_FLEXIBLE | JIGGLE_IS_RIGID))) + { + // no flex at all - just use goal matrix + boneMX = goalMX; + } +} \ No newline at end of file diff --git a/game_shared/jigglebones.h b/game_shared/jigglebones.h new file mode 100644 index 0000000..72a408c --- /dev/null +++ b/game_shared/jigglebones.h @@ -0,0 +1,81 @@ +//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $NoKeywords: $ +//=============================================================================// +#ifndef JIGGLEBONES_H +#define JIGGLEBONES_H + +#include +#include "mathlib.h" +#include "studio.h" +#include "utllinkedlist.h" + +//----------------------------------------------------------------------------- +/** + * JiggleData is the instance-specific data for a jiggle bone + */ +struct JiggleData +{ + void Init( int initBone, float currenttime, const Vector &initBasePos, const Vector &initTipPos ) + { + bone = initBone; + + lastUpdate = currenttime; + + basePos = initBasePos; + baseLastPos = basePos; + baseVel.Init(); + baseAccel.Init(); + + tipPos = initTipPos; + tipVel.Init(); + tipAccel.Init(); + + lastLeft = Vector( 0, 0, 0 ); + + lastBoingPos = initBasePos; + boingDir = Vector( 0.0f, 0.0f, 1.0f ); + boingVelDir.Init(); + boingSpeed = 0.0f; + boingTime = 0.0f; + } + + int bone; + + float lastUpdate; // based on gpGlobals->realtime + + Vector basePos; // position of the base of the jiggle bone + Vector baseLastPos; + Vector baseVel; + Vector baseAccel; + + Vector tipPos; // position of the tip of the jiggle bone + Vector tipVel; + Vector tipAccel; + Vector lastLeft; // previous up vector + + Vector lastBoingPos; // position of base of jiggle bone last update for tracking velocity + Vector boingDir; // current direction along which the boing effect is occurring + Vector boingVelDir; // current estimation of jiggle bone unit velocity vector for boing effect + float boingSpeed; // current estimation of jiggle bone speed for boing effect + float boingTime; +}; + +class CJiggleBones +{ +public: + CJiggleBones() {} + ~CJiggleBones() { m_jiggleBoneState.RemoveAll(); } + + JiggleData *GetJiggleData( int bone, float currenttime, const Vector &initBasePos, const Vector &initTipPos ); + void BuildJiggleTransformations( int boneIndex, float currentime, const mstudiojigglebone_t *jiggleParams, const matrix3x4 &goalMX, matrix3x4 &boneMX ); +private: + CUtlLinkedList< JiggleData > m_jiggleBoneState; +}; + +extern bool CalcProceduralBone( const studiohdr_t *pStudioHdr, int iBone, matrix3x4 *bonetransform ); + +#endif // C_JIGGLEBONES_H \ No newline at end of file diff --git a/game_shared/material.cpp b/game_shared/material.cpp new file mode 100644 index 0000000..f579aa6 --- /dev/null +++ b/game_shared/material.cpp @@ -0,0 +1,342 @@ +/* +material.cpp - material description for both client and server +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef CLIENT_DLL +#include"extdll.h" +#include "util.h" +#else +#include "hud.h" +#include "cl_util.h" +#endif + +#include "material.h" +#include "stringlib.h" +#include "com_model.h" + +static matdef_t *com_matdef; // pointer to global materials array +static int com_matcount; +static matdef_t *com_defmat; + +char *_COM_CopyString( const char *s, const char *file, const int line ) +{ + if( !s ) return NULL; + + char *b = (char *)_Mem_Alloc( Q_strlen( s ) + 1, file, line ); + Q_strcpy( b, s ); + + return b; +} + +matdef_t *COM_CreateDefaultMatdef( void ) +{ + static matdef_t mat; + + if( mat.name[0] ) + return &mat; // already created + + Q_strcpy( mat.name, "default" ); + mat.detailName[0] = '\0'; + + mat.impact_decal = COM_CopyString( "shot" ); // default Paranoia decal + mat.impact_sounds[0] = COM_CopyString( "debris/concrete1.wav" ); + mat.impact_sounds[1] = COM_CopyString( "debris/concrete2.wav" ); + mat.impact_sounds[0] = COM_CopyString( "debris/concrete3.wav" ); + mat.impact_sounds[1] = COM_CopyString( "debris/concrete4.wav" ); + mat.step_sounds[0] = COM_CopyString( "player/pl_beton1.wav" ); + mat.step_sounds[1] = COM_CopyString( "player/pl_beton2.wav" ); + mat.step_sounds[2] = COM_CopyString( "player/pl_beton3.wav" ); + mat.step_sounds[3] = COM_CopyString( "player/pl_beton4.wav" ); + + return &mat; +} + +/* +================== +COM_FindMatdef + +This function never failed +================== +*/ +matdef_t *COM_FindMatdef( const char *name ) +{ + for( int i = 0; i < com_matcount; i++ ) + { + if( !Q_stricmp( name, com_matdef[i].name )) + return &com_matdef[i]; + } + + ALERT( at_error, "material specific '%s' not found. forcing to defualt\n", name ); + + return com_defmat; +} + +/* +================== +GetLayerIndexForPoint + +this function came from q3map2 +================== +*/ +static char COM_GetLayerIndexForPoint( mfaceinfo_t *land, const Vector &point ) +{ + Vector size; + + if( !land || !land->heightmap ) + return -1; + + for( int i = 0; i < 3; i++ ) + size[i] = ( land->maxs[i] - land->mins[i] ); + + float s = ( point[0] - land->mins[0] ) / size[0]; + float t = ( land->maxs[1] - point[1] ) / size[1]; + + int x = s * land->heightmap_width; + int y = t * land->heightmap_height; + + x = bound( 0, x, ( land->heightmap_width - 1 )); + y = bound( 0, y, ( land->heightmap_height - 1 )); + + return land->heightmap[y * land->heightmap_width + x]; +} + +/* +================= +COM_MatDefFromSurface + +safe version to get matdef_t +================= +*/ +matdef_t *COM_MatDefFromSurface( msurface_t *surf, const Vector &vecPoint ) +{ + if( !surf || !surf->texinfo ) + return NULL; + + mtexinfo_t *tx = surf->texinfo; + int layerNum = COM_GetLayerIndexForPoint( tx->faceinfo, vecPoint ); + + if( layerNum != -1 && layerNum < MAX_LANDSCAPE_LAYERS ) + { + // landscape support + return tx->faceinfo->effects[layerNum]; + } + + if( !tx->texture ) + return NULL; + + return surf->texinfo->texture->effects; // epic chain! +} + +matdef_t *COM_DefaultMatdef( void ) +{ + return com_defmat; +} + +void COM_InitMatdef( void ) +{ + ALERT( at_aiconsole, "loading materials.def\n" ); + + char *afile = (char *)LOAD_FILE( "scripts/materials.def", NULL ); + + if( !afile ) + { + ALERT( at_error, "Cannot open file \"scripts/materials.def\"\n" ); + return; + } + + char *pfile = afile; + char token[256]; + int depth = 0; + com_matcount = 0; + + // count materials + while( pfile != NULL ) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + + if( Q_strlen( token ) > 1 ) + continue; + + if( token[0] == '{' ) + { + depth++; + } + else if( token[0] == '}' ) + { + com_matcount++; + depth--; + } + } + + if( depth > 0 ) ALERT( at_warning, "materials.def: EOF reached without closing brace\n" ); + if( depth < 0 ) ALERT( at_warning, "materials.def: EOF reached without opening brace\n" ); + + com_matdef = (matdef_t *)Mem_Alloc( sizeof( matdef_t ) * com_matcount ); + pfile = afile; // start real parsing + + int current = 0; + + while( pfile != NULL ) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + + if( current >= com_matcount ) + { + ALERT ( at_error, "material parse is overrun %d > %d\n", current, com_matcount ); + break; + } + + matdef_t *mat = &com_matdef[current]; + + // read the material name + Q_strncpy( mat->name, token, sizeof( mat->name )); + + // detail scale default values + mat->detailScale[0] = 10.0f; + mat->detailScale[1] = 10.0f; + + // read opening brace + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + + if( token[0] != '{' ) + { + ALERT( at_error, "found %s when expecting {\n", token ); + break; + } + + while( pfile != NULL ) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "EOF without closing brace\n" ); + goto getout; + } + + // description end goto next material + if( token[0] == '}' ) + { + current++; + break; + } + else if( !Q_stricmp( token, "impact_decal" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'impact_decal'\n" ); + goto getout; + } + + mat->impact_decal = COM_CopyString( token ); + } + else if( !Q_stricmp( token, "impact_parts" )) + { + while(( pfile = COM_ParseLine( pfile, token )) != NULL ) + { + if( !Q_strlen( token )) break; // end of line + + // find the free sound slot + for( int i = 0; mat->impact_parts[i] != NULL && i < MAX_MAT_SOUNDS; i++ ); + + if( i < MAX_MAT_SOUNDS ) + { + mat->impact_parts[i] = COM_CopyString( token ); + } + } + } + else if( !Q_stricmp( token, "impact_sound" )) + { + while(( pfile = COM_ParseLine( pfile, token )) != NULL ) + { + if( !Q_strlen( token )) break; // end of line + + // find the free sound slot + for( int i = 0; mat->impact_sounds[i] != NULL && i < MAX_MAT_SOUNDS; i++ ); + + if( i < MAX_MAT_SOUNDS ) + { + mat->impact_sounds[i] = COM_CopyString( token ); + } + } + } + else if( !Q_stricmp( token, "step_sound" )) + { + while(( pfile = COM_ParseLine( pfile, token )) != NULL ) + { + if( !Q_strlen( token )) break; // end of line + + // find the free sound slot + for( int i = 0; mat->step_sounds[i] != NULL && i < MAX_MAT_SOUNDS; i++ ); + + if( i < MAX_MAT_SOUNDS ) + { + mat->step_sounds[i] = COM_CopyString( token ); + } + } + } + else if( !Q_stricmp( token, "detailScale" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'detailScale'\n" ); + goto getout; + } + + Q_atov( mat->detailScale, token, 2 ); + mat->detailScale[0] = bound( 0.01f, mat->detailScale[0], 100.0f ); + mat->detailScale[1] = bound( 0.01f, mat->detailScale[0], 100.0f ); + } + else if( !Q_stricmp( token, "detailmap" )) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) + { + ALERT( at_error, "hit EOF while parsing 'detailmap'\n" ); + goto getout; + } + + COM_FixSlashes( token ); + Q_strncpy( mat->detailName, token, sizeof( mat->detailName )); + } + else ALERT( at_warning, "Unknown material token %s\n", token ); + } + } +getout: + FREE_FILE( afile ); + ALERT( at_aiconsole, "%d matdefs parsed\n", current ); + + com_defmat = NULL; + + // search for default material + for( int i = 0; i < com_matcount; i++ ) + { + if( !Q_stricmp( "default", com_matdef[i].name )) + { + com_defmat = &com_matdef[i]; + break; + } + } + + // if default material was not specified by mod-maker - create internal + if( !com_defmat ) + { + ALERT( at_warning, "Default material was not specified. Create internal\n" ); + com_defmat = COM_CreateDefaultMatdef(); + } +} \ No newline at end of file diff --git a/game_shared/material.h b/game_shared/material.h new file mode 100644 index 0000000..ccf3259 --- /dev/null +++ b/game_shared/material.h @@ -0,0 +1,42 @@ +/* +material.h - material description for both client and server +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef MATERIAL_H +#define MATERIAL_H + +#define MAX_MAT_SOUNDS 8 + +typedef struct matdef_s +{ + char name[32]; // material name + const char *impact_decal; + const char *impact_parts[MAX_MAT_SOUNDS+1]; // for terminator + const char *impact_sounds[MAX_MAT_SOUNDS+1]; + const char *step_sounds[MAX_MAT_SOUNDS+1]; + char detailName[64]; // shared detail texture for materials + float detailScale[2]; // detail texture scales x, y +} matdef_t; + +#define IMPACT_NONE 0 +#define IMPACT_BODY 1 +#define IMPACT_MATERIAL 2 + +#define COM_CopyString( s ) _COM_CopyString( s, __FILE__, __LINE__ ) +matdef_t *COM_MatDefFromSurface( struct msurface_s *surf, const Vector &vecPoint ); +matdef_t *COM_FindMatdef( const char *name ); +matdef_t *COM_DefaultMatdef( void ); +void COM_InitMatdef( void ); + +#endif//MATERIAL_H \ No newline at end of file diff --git a/game_shared/mathlib.cpp b/game_shared/mathlib.cpp new file mode 100644 index 0000000..413999d --- /dev/null +++ b/game_shared/mathlib.cpp @@ -0,0 +1,1737 @@ +//======================================================================= +// Copyright (C) Shambler Team 2005 +// mathlib.cpp - shared math library +//======================================================================= + +#include "mathlib.h" +#include "const.h" +#include "com_model.h" +#include + +const Vector g_vecZero( 0, 0, 0 ); +const Radian g_radZero( 0, 0, 0 ); + +float g_hullcolor[8][3] = +{ +{ 1.0f, 1.0f, 1.0f }, +{ 1.0f, 0.5f, 0.5f }, +{ 0.5f, 1.0f, 0.5f }, +{ 1.0f, 1.0f, 0.5f }, +{ 0.5f, 0.5f, 1.0f }, +{ 1.0f, 0.5f, 1.0f }, +{ 0.5f, 1.0f, 1.0f }, +{ 1.0f, 1.0f, 1.0f }, +}; + +int g_boxpnt[6][4] = +{ +{ 0, 4, 6, 2 }, // +X +{ 0, 1, 5, 4 }, // +Y +{ 0, 2, 3, 1 }, // +Z +{ 7, 5, 1, 3 }, // -X +{ 7, 3, 2, 6 }, // -Y +{ 7, 6, 4, 5 }, // -Z +}; + +/* +================= +SignbitsForPlane + +fast box on planeside test +================= +*/ +int SignbitsForPlane( const Vector &normal ) +{ + for( int bits = 0, i = 0; i < 3; i++ ) + if( normal[i] < 0.0f ) + bits |= 1<type = PlaneTypeForNormal( vecNormal ); + else plane->type = type; + plane->signbits = SignbitsForPlane( vecNormal ); + plane->normal = vecNormal; + plane->dist = flDist; +} + +/* +================ +PlaneFromPoints +================ +*/ +void PlaneFromPoints( const Vector &p0, const Vector &p1, const Vector &p2, mplane_t *plane ) +{ + Vector t1 = p0 - p1; + Vector t2 = p2 - p1; + Vector normal = CrossProduct( t1, t2 ).Normalize(); + float dist = DotProduct( p0, normal ); + + SetPlane( plane, normal, dist ); +} + +/* +================= +PlanesGetIntersectionPoint + +================= +*/ +bool PlanesGetIntersectionPoint( const mplane_t *plane1, const mplane_t *plane2, const mplane_t *plane3, Vector &out ) +{ + Vector n1 = plane1->normal.Normalize(); + Vector n2 = plane2->normal.Normalize(); + Vector n3 = plane3->normal.Normalize(); + + Vector n1n2 = CrossProduct( n1, n2 ); + Vector n2n3 = CrossProduct( n2, n3 ); + Vector n3n1 = CrossProduct( n3, n1 ); + + float denom = DotProduct( n1, n2n3 ); + out = g_vecZero; + + // check if the denominator is zero (which would mean that no intersection is to be found + if( denom == 0.0f ) + { + // no intersection could be found, return <0,0,0> + return false; + } + + // compute intersection point + out += n2n3 * plane1->dist; + out += n3n1 * plane2->dist; + out += n1n2 * plane3->dist; + out *= (1.0f / denom ); + + return true; +} + +/* +================= +PlaneIntersect + +find point where ray +was intersect with plane +================= +*/ +Vector PlaneIntersect( mplane_t *plane, const Vector& p0, const Vector& p1 ) +{ + float distToPlane = PlaneDiff( p0, plane ); + float planeDotRay = DotProduct( plane->normal, p1 ); + float sect = -(distToPlane) / planeDotRay; + + return p0 + p1 * sect; +} + +void PerpendicularVector( Vector &dst, const Vector &src ) +{ + // LordHavoc: optimized to death and beyond + int pos; + float minelem; + + if( src.x ) + { + dst.x = 0; + if( src.y ) + { + dst.y = 0; + if( src.z ) + { + dst.z = 0; + pos = 0; + minelem = fabs( src.x ); + if( fabs( src.y ) < minelem ) + { + pos = 1; + minelem = fabs( src.y ); + } + if( fabs( src.y ) < minelem ) + pos = 2; + + dst[pos] = 1; + dst.x -= src[pos] * src.x; + dst.y -= src[pos] * src.y; + dst.z -= src[pos] * src.z; + + // normalize the result + dst = dst.Normalize(); + } + else dst.z = 1; + } + else + { + dst.y = 1; + dst.z = 0; + } + } + else + { + dst.x = 1; + dst.y = 0; + dst.z = 0; + } +} + +/* +================= +VectorAngles + +================= +*/ +void VectorAngles( const Vector &forward, Vector &angles ) +{ + angles[ROLL] = 0.0f; + + if( forward.x || forward.y ) + { + float tmp; + + angles[YAW] = RAD2DEG( atan2( forward.y, forward.x )); + if( angles[YAW] < 0 ) angles[YAW] += 360; + + tmp = sqrt( forward.x * forward.x + forward.y * forward.y ); + angles[PITCH] = RAD2DEG( atan2( forward.z, tmp )); + if( angles[PITCH] < 0 ) angles[PITCH] += 360; + } + else + { + // fast case + angles[YAW] = 0.0f; + if( forward.z > 0 ) + angles[PITCH] = 90.0f; + else angles[PITCH] = 270.0f; + } +} + +/* +================= +VectorAngles2 + +this version without stupid quake bug +================= +*/ +void VectorAngles2( const Vector &forward, Vector &angles ) +{ + angles[ROLL] = 0.0f; + + if( forward.x || forward.y ) + { + float tmp; + + angles[YAW] = RAD2DEG( atan2( forward.y, forward.x )); + if( angles[YAW] < 0.0f ) angles[YAW] += 360.0f; + + tmp = sqrt( forward.x * forward.x + forward.y * forward.y ); + angles[PITCH] = RAD2DEG( atan2( -forward.z, tmp )); + if( angles[PITCH] < 0.0f ) angles[PITCH] += 360.0f; + } + else + { + // fast case + angles[YAW] = 0.0f; + if( forward.z > 0.0f ) + angles[PITCH] = 270.0f; + else angles[PITCH] = 90.0f; + } +} + +/* +================= +NearestPOW +================= +*/ +int NearestPOW( int value, bool roundDown ) +{ + int n = 1; + + if( value <= 0 ) return 1; + while( n < value ) n <<= 1; + + if( roundDown ) + { + if( n > value ) n >>= 1; + } + return n; +} + +void MakeAxial( float normal[3] ) +{ + int i, type; + + for( type = 0; type < 3; type++ ) + { + if( fabs( normal[type] ) > 0.9999f ) + break; + } + + // make positive and pure axial + for( i = 0; i < 3 && type != 3; i++ ) + { + if( i == type ) + normal[i] = 1.0f; + else normal[i] = 0.0f; + } +} + +//----------------------------------------------------------------------------- +// Transforms a AABB into another space; which will inherently grow the box. +//----------------------------------------------------------------------------- +void TransformAABB( const matrix4x4& world, const Vector &mins, const Vector &maxs, Vector &absmin, Vector &absmax ) +{ + Vector localCenter = (mins + maxs) * 0.5f; + Vector localExtents = maxs - localCenter; + Vector worldCenter = world.VectorTransform( localCenter ); + matrix4x4 iworld = world.Transpose(); + Vector worldExtents; + + worldExtents.x = DotProductAbs( localExtents, iworld.GetForward( )); + worldExtents.y = DotProductAbs( localExtents, iworld.GetRight( )); + worldExtents.z = DotProductAbs( localExtents, iworld.GetUp( )); + + absmin = worldCenter - worldExtents; + absmax = worldCenter + worldExtents; +} + +/* +================== +TransformAABBLocal +================== +*/ +void TransformAABBLocal( const matrix4x4& world, const Vector &mins, const Vector &maxs, Vector &outmins, Vector &outmaxs ) +{ + matrix4x4 im = world.Invert(); + ClearBounds( outmins, outmaxs ); + Vector p1, p2; + int i; + + // compute a full bounding box + for( i = 0; i < 8; i++ ) + { + p1.x = ( i & 1 ) ? mins.x : maxs.x; + p1.y = ( i & 2 ) ? mins.y : maxs.y; + p1.z = ( i & 4 ) ? mins.z : maxs.z; + + p2.x = DotProduct( p1, im[0] ); + p2.y = DotProduct( p1, im[1] ); + p2.z = DotProduct( p1, im[2] ); + + if( p2.x < outmins.x ) outmins.x = p2.x; + if( p2.x > outmaxs.x ) outmaxs.x = p2.x; + if( p2.y < outmins.y ) outmins.y = p2.y; + if( p2.y > outmaxs.y ) outmaxs.y = p2.y; + if( p2.z < outmins.z ) outmins.z = p2.z; + if( p2.z > outmaxs.z ) outmaxs.z = p2.z; + } + + // sanity check + for( i = 0; i < 3; i++ ) + { + if( outmins[i] > outmaxs[i] ) + { + outmins = g_vecZero; + outmaxs = g_vecZero; + return; + } + } +} + +float CalcSqrDistanceToAABB( const Vector &mins, const Vector &maxs, const Vector &point ) +{ + float flDelta; + float flDistSqr = 0.0f; + + if( point.x < mins.x ) + { + flDelta = (mins.x - point.x); + flDistSqr += flDelta * flDelta; + } + else if( point.x > maxs.x ) + { + flDelta = (point.x - maxs.x); + flDistSqr += flDelta * flDelta; + } + + if( point.y < mins.y ) + { + flDelta = (mins.y - point.y); + flDistSqr += flDelta * flDelta; + } + else if( point.y > maxs.y ) + { + flDelta = (point.y - maxs.y); + flDistSqr += flDelta * flDelta; + } + + if( point.z < mins.z ) + { + flDelta = (mins.z - point.z); + flDistSqr += flDelta * flDelta; + } + else if( point.z > maxs.z ) + { + flDelta = (point.z - maxs.z); + flDistSqr += flDelta * flDelta; + } + + return flDistSqr; +} + +// returns true if the sphere and cone intersect +// NOTE: cone sine/cosine are the half angle of the cone +bool IsSphereIntersectingCone( const Vector &sphereCenter, float sphereRadius, const Vector &coneOrigin, const Vector &coneNormal, float coneSine, float coneCosine ) +{ + Vector backCenter = coneOrigin - (sphereRadius / coneSine) * coneNormal; + Vector delta = sphereCenter - backCenter; + float deltaLen = delta.Length(); + + if( DotProduct( coneNormal, delta ) >= deltaLen * coneCosine ) + { + delta = sphereCenter - coneOrigin; + deltaLen = delta.Length(); + if( -DotProduct(coneNormal, delta) >= deltaLen * coneSine ) + return ( deltaLen <= sphereRadius ) ? true : false; + return true; + } + + return false; +} + +void CalcClosestPointOnAABB( const Vector &mins, const Vector &maxs, const Vector &point, Vector &closestOut ) +{ + closestOut.x = bound( mins.x, point.x, maxs.x ); + closestOut.y = bound( mins.y, point.y, maxs.y ); + closestOut.z = bound( mins.z, point.z, maxs.z ); +} + +// +// bounds operations +// +/* +================= +ClearBounds +================= +*/ +void ClearBounds( Vector &mins, Vector &maxs ) +{ + // make bogus range + mins.x = mins.y = mins.z = 999999; + maxs.x = maxs.y = maxs.z = -999999; +} + +/* +================= +ClearBounds +================= +*/ +void ClearBounds( Vector2D &mins, Vector2D &maxs ) +{ + // make bogus range + mins.x = mins.y = 999999; + maxs.x = maxs.y = -999999; +} + +bool BoundsIsCleared( const Vector &mins, const Vector &maxs ) +{ + if( mins.x <= maxs.x || mins.y <= maxs.y || mins.z <= maxs.z ) + return false; + return true; +} + +bool BoundsIsNull( const Vector &mins, const Vector &maxs ) +{ + if( mins.x != maxs.x || mins.y != maxs.y || mins.z != maxs.z ) + return false; + return true; +} + +/* +================= +ExpandBounds +================= +*/ +void ExpandBounds( Vector &mins, Vector &maxs, float offset ) +{ + mins[0] -= offset; + mins[1] -= offset; + mins[2] -= offset; + maxs[0] += offset; + maxs[1] += offset; + maxs[2] += offset; +} + +/* +================= +AddPointToBounds +================= +*/ +void AddPointToBounds( const Vector &v, Vector &mins, Vector &maxs, float limit ) +{ + if( limit ) + { + for( int i = 0; i < 3; i++ ) + { + if( v[i] < mins[i] ) mins[i] = Q_max( v[i], mins[i] - limit ); + if( v[i] > maxs[i] ) maxs[i] = Q_min( v[i], maxs[i] + limit ); + } + } + else + { + for( int i = 0; i < 3; i++ ) + { + if( v[i] < mins[i] ) mins[i] = v[i]; + if( v[i] > maxs[i] ) maxs[i] = v[i]; + } + } +} + +/* +================= +AddPointToBounds +================= +*/ +void AddPointToBounds( const Vector2D &v, Vector2D &mins, Vector2D &maxs ) +{ + for( int i = 0; i < 2; i++ ) + { + if( v[i] < mins[i] ) mins[i] = v[i]; + if( v[i] > maxs[i] ) maxs[i] = v[i]; + } +} + +/* +================= +BoundsIntersect +================= +*/ +bool BoundsIntersect( const Vector &mins1, const Vector &maxs1, const Vector &mins2, const Vector &maxs2 ) +{ + if( mins1.x > maxs2.x || mins1.y > maxs2.y || mins1.z > maxs2.z ) + return false; + if( maxs1.x < mins2.x || maxs1.y < mins2.y || maxs1.z < mins2.z ) + return false; + return true; +} + +/* +================= +BoundsIntersect +================= +*/ +bool BoundsIntersect( const Vector2D &mins1, const Vector2D &maxs1, const Vector2D &mins2, const Vector2D &maxs2 ) +{ + if( mins1.x > maxs2.x || mins1.y > maxs2.y ) + return false; + if( maxs1.x < mins2.x || maxs1.y < mins2.y ) + return false; + return true; +} + +/* +================= +BoundsAndSphereIntersect +================= +*/ +bool BoundsAndSphereIntersect( const Vector &mins, const Vector &maxs, const Vector &origin, float radius ) +{ + if( mins.x > origin.x + radius || mins.y > origin.y + radius || mins.z > origin.z + radius ) + return false; + if( maxs.x < origin.x - radius || maxs.y < origin.y - radius || maxs.z < origin.z - radius ) + return false; + return true; +} + +/* +================= +BoundsAndSphereIntersect +================= +*/ +bool BoundsAndSphereIntersect( const Vector2D &mins, const Vector2D &maxs, const Vector2D &origin, float radius ) +{ + if( mins.x > origin.x + radius || mins.y > origin.y + radius ) + return false; + if( maxs.x < origin.x - radius || maxs.y < origin.y - radius ) + return false; + return true; +} + +/* +================= +RadiusFromBounds +================= +*/ +float RadiusFromBounds( const Vector &mins, const Vector &maxs ) +{ + Vector corner; + + for( int i = 0; i < 3; i++ ) + { + corner[i] = fabs( mins[i] ) > fabs( maxs[i] ) ? fabs( mins[i] ) : fabs( maxs[i] ); + } + return corner.Length(); +} + +// +// quaternion operations +// + +/* +==================== +AngleQuaternion + +degrees euler XYZ version +==================== +*/ +void AngleQuaternion( const Vector &angles, Vector4D &quat ) +{ + float sr, sp, sy, cr, cp, cy; + + SinCos( DEG2RAD( angles.y ) * 0.5f, &sy, &cy ); + SinCos( DEG2RAD( angles.x ) * 0.5f, &sp, &cp ); + SinCos( DEG2RAD( angles.z ) * 0.5f, &sr, &cr ); + + float srXcp = sr * cp, crXsp = cr * sp; + quat.x = srXcp * cy - crXsp * sy; // X + quat.y = crXsp * cy + srXcp * sy; // Y + + float crXcp = cr * cp, srXsp = sr * sp; + quat.z = crXcp * sy - srXsp * cy; // Z + quat.w = crXcp * cy + srXsp * sy; // W (real component) +} + +/* +==================== +AngleQuaternion + +radian euler YZX version +==================== +*/ +void AngleQuaternion( const Radian &angles, Vector4D &quat ) +{ + float sr, sp, sy, cr, cp, cy; + + SinCos( angles.z * 0.5f, &sy, &cy ); + SinCos( angles.y * 0.5f, &sp, &cp ); + SinCos( angles.x * 0.5f, &sr, &cr ); + + float srXcp = sr * cp, crXsp = cr * sp; + quat.x = srXcp * cy - crXsp * sy; // X + quat.y = crXsp * cy + srXcp * sy; // Y + + float crXcp = cr * cp, srXsp = sr * sp; + quat.z = crXcp * sy - srXsp * cy; // Z + quat.w = crXcp * cy + srXsp * sy; // W (real component) +} + +/* +==================== +QuaternionAngle + +==================== +*/ +void QuaternionAngle( const Vector4D &quat, Vector &angles ) +{ + // g-cont. it's incredible stupid way but... + matrix3x3 temp( quat ); + temp.GetAngles( angles ); +} + +/* +==================== +QuaternionAngle + +==================== +*/ +void QuaternionAngle( const Vector4D &quat, Radian &angles ) +{ + // g-cont. it's incredible stupid way but... + matrix3x3 temp( quat ); + temp.GetAngles( angles ); +} + +/* +==================== +QuaternionAlign + +make sure quaternions are within 180 degrees of one another, +if not, reverse q +==================== +*/ +void QuaternionAlign( const Vector4D &p, const Vector4D &q, Vector4D &qt ) +{ + // decide if one of the quaternions is backwards + float a = 0; + float b = 0; + int i; + + for( i = 0; i < 4; i++ ) + { + a += (p[i] - q[i]) * (p[i] - q[i]); + b += (p[i] + q[i]) * (p[i] + q[i]); + } + + if( a > b ) + { + for( i = 0; i < 4; i++ ) + { + qt[i] = -q[i]; + } + } + else if( &qt != &q ) + { + for( i = 0; i < 4; i++ ) + { + qt[i] = q[i]; + } + } +} + +/* +==================== +QuaternionSlerpNoAlign +==================== +*/ +void QuaternionSlerpNoAlign( const Vector4D &p, const Vector4D &q, float t, Vector4D &qt ) +{ + float omega, cosom, sinom, sclp, sclq; + + // 0.0 returns p, 1.0 return q. + cosom = p[0] * q[0] + p[1] * q[1] + p[2] * q[2] + p[3] * q[3]; + + if(( 1.0f + cosom ) > 0.000001f ) + { + if(( 1.0f - cosom ) > 0.000001f ) + { + omega = acos( cosom ); + sinom = sin( omega ); + sclp = sin( (1.0f - t) * omega) / sinom; + sclq = sin( t * omega ) / sinom; + } + else + { + // TODO: add short circuit for cosom == 1.0f? + sclp = 1.0f - t; + sclq = t; + } + + for( int i = 0; i < 4; i++ ) + { + qt[i] = sclp * p[i] + sclq * q[i]; + } + } + else + { + qt[0] = -q[1]; + qt[1] = q[0]; + qt[2] = -q[3]; + qt[3] = q[2]; + sclp = sin(( 1.0f - t ) * ( 0.5f * M_PI )); + sclq = sin( t * ( 0.5f * M_PI )); + + for( int i = 0; i < 3; i++ ) + { + qt[i] = sclp * p[i] + sclq * qt[i]; + } + } +} + +/* +==================== +QuaternionSlerp + +Quaternion sphereical linear interpolation +==================== +*/ +void QuaternionSlerp( const Vector4D &p, const Vector4D &q, float t, Vector4D &qt ) +{ + Vector4D q2; + + // 0.0 returns p, 1.0 return q. + // decide if one of the quaternions is backwards + QuaternionAlign( p, q, q2 ); + + QuaternionSlerpNoAlign( p, q2, t, qt ); +} + +/* +==================== +QuaternionSlerp + +Quaternion sphereical linear interpolation +==================== +*/ +void QuaternionSlerp( const Radian &r0, const Radian &r1, float t, Radian &r2 ) +{ + Vector4D q0, q1, q2; + + AngleQuaternion( r0, q0 ); + AngleQuaternion( r1, q1 ); + QuaternionSlerp( q0, q1, t, q2 ); + QuaternionAngle( q2, r2 ); +} + +/* +==================== +QuaternionBlend +==================== +*/ +void QuaternionBlend( const Vector4D &p, const Vector4D &q, float t, Vector4D &qt ) +{ + // decide if one of the quaternions is backwards + Vector4D q2; + + QuaternionAlign( p, q, q2 ); + QuaternionBlendNoAlign( p, q2, t, qt ); +} + +/* +==================== +QuaternionBlendNoAlign +==================== +*/ +void QuaternionBlendNoAlign( const Vector4D &p, const Vector4D &q, float t, Vector4D &qt ) +{ + float sclp, sclq; + + // 0.0 returns p, 1.0 return q. + sclp = 1.0f - t; + sclq = t; + + for( int i = 0; i < 4; i++ ) + { + qt[i] = sclp * p[i] + sclq * q[i]; + } + + qt = qt.Normalize(); +} + +void QuaternionAdd( const Vector4D &p, const Vector4D &q, Vector4D &qt ) +{ + Vector4D q2; + QuaternionAlign( p, q, q2 ); + qt = p + q2; +} + +/* +==================== +QuaternionMultiply + +multiply two quaternions +==================== +*/ +void QuaternionMultiply( const Vector4D &q1, const Vector4D &q2, Vector4D &out ) +{ + out[0] = q1[3] * q2[0] + q1[0] * q2[3] + q1[1] * q2[2] - q1[2] * q2[1]; + out[1] = q1[3] * q2[1] + q1[1] * q2[3] + q1[2] * q2[0] - q1[0] * q2[2]; + out[2] = q1[3] * q2[2] + q1[2] * q2[3] + q1[0] * q2[1] - q1[1] * q2[0]; + out[3] = q1[3] * q2[3] - q1[0] * q2[0] - q1[1] * q2[1] - q1[2] * q2[2]; +} + +/* +==================== +QuaternionVectorTransform + +transform vector by quaternion +==================== +*/ +void QuaternionVectorTransform( const Vector4D &q, const Vector &v, Vector &out ) +{ + float wx, wy, wz, xx, yy, yz, xy, xz, zz, x2, y2, z2; + + // 9 muls, 3 adds + x2 = q[0] + q[0]; y2 = q[1] + q[1]; z2 = q[2] + q[2]; + xx = q[0] * x2; xy = q[0] * y2; xz = q[0] * z2; + yy = q[1] * y2; yz = q[1] * z2; zz = q[2] * z2; + wx = q[3] * x2; wy = q[3] * y2; wz = q[3] * z2; + + // 9 muls, 9 subs, 9 adds + out[0] = ( 1.0f - yy - zz ) * v[0] + ( xy - wz ) * v[1] + ( xz + wy ) * v[2]; + out[1] = ( xy + wz ) * v[0] + ( 1.0f - xx - zz ) * v[1] + ( yz - wx ) * v[2]; + out[2] = ( xz - wy ) * v[0] + ( yz + wx ) * v[1] + ( 1.0f - xx - yy ) * v[2]; +} + +/* +==================== +QuaternionConcatTransforms + +transform quat\vector by another quat\vector +==================== +*/ +void QuaternionConcatTransforms( const Vector4D &q1, const Vector &v1, const Vector4D &q2, const Vector &v2, Vector4D &q, Vector &v ) +{ + QuaternionMultiply( q1, q2, q ); + QuaternionVectorTransform( q1, v2, v ); + v += v1; +} + +void QuaternionMult( const Vector4D &p, const Vector4D &q, Vector4D &qt ) +{ + if( &p == &qt ) + { + Vector4D p2 = p; + QuaternionMult( p2, q, qt ); + return; + } + + // decide if one of the quaternions is backwards + Vector4D q2; + + QuaternionAlign( p, q, q2 ); + qt.x = p.x * q2.w + p.y * q2.z - p.z * q2.y + p.w * q2.x; + qt.y = -p.x * q2.z + p.y * q2.w + p.z * q2.x + p.w * q2.y; + qt.z = p.x * q2.y - p.y * q2.x + p.z * q2.w + p.w * q2.z; + qt.w = -p.x * q2.x - p.y * q2.y - p.z * q2.z + p.w * q2.w; +} + +void QuaternionScale( const Vector4D &p, float t, Vector4D &q ) +{ + float r; + + // FIXME: nick, this isn't overly sensitive to accuracy, and it may be faster to + // use the cos part (w) of the quaternion (sin(omega)*N,cos(omega)) to figure the new scale. +#if 1 + Vector ps = Vector( p.x, p.y, p.z ); + float sinom = ps.Length(); // !!! +#else + float sinom = p.Length(); // !!! +#endif + sinom = Q_min( sinom, 1.0f ); + float sinsom = sin( asin( sinom ) * t ); + + t = sinsom / (sinom + FLT_EPSILON); + q.x = p.x * t; + q.y = p.y * t; + q.z = p.z * t; + + // rescale rotation + r = 1.0f - sinsom * sinsom; + + if( r < 0.0f ) + r = 0.0f; + r = sqrt( r ); + + // keep sign of rotation + if( p.w < 0 ) q.w = -r; + else q.w = r; +} + +//----------------------------------------------------------------------------- +// Purpose: Converts an exponential map (ang/axis) to a quaternion +//----------------------------------------------------------------------------- +void AxisAngleQuaternion( const Vector &axis, float angle, Vector4D &q ) +{ + float sa, ca; + + SinCos( DEG2RAD( angle ) * 0.5f, &sa, &ca ); + + q.x = axis.x * sa; + q.y = axis.y * sa; + q.z = axis.z * sa; + q.w = ca; +} + +//----------------------------------------------------------------------------- +// Purpose: qt = ( s * p ) * q +//----------------------------------------------------------------------------- +void QuaternionSM( float s, const Vector4D &p, const Vector4D &q, Vector4D &qt ) +{ + Vector4D p1, q1; + + QuaternionScale( p, s, p1 ); + QuaternionMult( p1, q, q1 ); + qt = q1.Normalize(); +} + +//----------------------------------------------------------------------------- +// Purpose: qt = p * ( s * q ) +//----------------------------------------------------------------------------- +void QuaternionMA( const Vector4D &p, float s, const Vector4D &q, Vector4D &qt ) +{ + Vector4D p1, q1; + + QuaternionScale( q, s, q1 ); + QuaternionMult( p, q1, p1 ); + qt = p1.Normalize(); +} + +//----------------------------------------------------------------------------- +// Purpose: qt = p + s * q +//----------------------------------------------------------------------------- +void QuaternionAccumulate( const Vector4D &p, float s, const Vector4D &q, Vector4D &qt ) +{ + Vector4D q2; + + QuaternionAlign( p, q, q2 ); + qt[0] = p[0] + s * q2[0]; + qt[1] = p[1] + s * q2[1]; + qt[2] = p[2] + s * q2[2]; + qt[3] = p[3] + s * q2[3]; +} + +// +// lerping stuff +// + +/* +=================== +InterpolateOrigin + +Interpolate position. +Frac is 0.0 to 1.0 +=================== +*/ +void InterpolateOrigin( const Vector& start, const Vector& end, Vector& output, float frac, bool back ) +{ + if( back ) output += frac * ( end - start ); + else output = start + frac * ( end - start ); +} + +/* +=================== +InterpolateAngles + +Interpolate Euler angles. +Frac is 0.0 to 1.0 ( i.e., should probably be clamped, but doesn't have to be ) +=================== +*/ +void InterpolateAngles( const Vector& start, const Vector& end, Vector& output, float frac, bool back ) +{ +#if 0 + Vector4D src, dest; + + // convert to quaternions + AngleQuaternion( start, src ); + AngleQuaternion( end, dest ); + + Vector4D result; + Vector out; + + // slerp + QuaternionSlerp( src, dest, frac, result ); + + // convert to euler + QuaternionAngle( result, out ); + + if( back ) output += out; + else output = out; +#else + for( int i = 0; i < 3; i++ ) + { + float ang1, ang2; + + ang1 = end[i]; + ang2 = start[i]; + + float d = ang1 - ang2; + + if( d > 180 ) d -= 360; + else if( d < -180 ) d += 360; + + output[i] += d * frac; + } +#endif +} + +/* +==================== +RotatePointAroundVector +==================== +*/ +void RotatePointAroundVector( Vector &dst, const Vector &dir, const Vector &point, float degrees ) +{ + float t0, t1; + float angle, c, s; + Vector vr, vu, vf = dir; + + angle = DEG2RAD( degrees ); + SinCos( angle, &s, &c ); + VectorMatrix( vf, vr, vu ); + + t0 = vr.x * c + vu.x * -s; + t1 = vr.x * s + vu.x * c; + dst.x = (t0 * vr.x + t1 * vu.x + vf.x * vf.x) * point.x + + (t0 * vr.y + t1 * vu.y + vf.x * vf.y) * point.y + + (t0 * vr.z + t1 * vu.z + vf.x * vf.z) * point.z; + + t0 = vr.y * c + vu.y * -s; + t1 = vr.y * s + vu.y * c; + dst.y = (t0 * vr.x + t1 * vu.x + vf.y * vf.x) * point.x + + (t0 * vr.y + t1 * vu.y + vf.y * vf.y) * point.y + + (t0 * vr.z + t1 * vu.z + vf.y * vf.z) * point.z; + + t0 = vr.z * c + vu.z * -s; + t1 = vr.z * s + vu.z * c; + dst.z = (t0 * vr.x + t1 * vu.x + vf.z * vf.x) * point.x + + (t0 * vr.y + t1 * vu.y + vf.z * vf.y) * point.y + + (t0 * vr.z + t1 * vu.z + vf.z * vf.z) * point.z; +} + +void NormalizeAngles( Vector &angles ) +{ + for( int i = 0; i < 3; i++ ) + { + if( angles[i] > 180.0f ) + { + angles[i] -= 360.0f; + } + else if( angles[i] < -180.0f ) + { + angles[i] += 360.0f; + } + } +} + +/* +=================== +AngleBetweenVectors + +=================== +*/ +float AngleBetweenVectors( const Vector v1, const Vector v2 ) +{ + float l1 = v1.Length(); + float l2 = v2.Length(); + + if( !l1 || !l2 ) + return 0.0f; + + float angle = acos( DotProduct( v1, v2 )) / (l1 * l2); + + return RAD2DEG( angle ); +} + +void VectorMatrix( Vector &forward, Vector &right, Vector &up ) +{ + if( forward.x || forward.y ) + { + right = Vector( forward.y, -forward.x, 0 ); + right = right.Normalize(); + up = CrossProduct( forward, right ); + } + else + { + right = Vector( 1.0f, 0.0f, 0.0f ); + up = Vector( 0.0f, 1.0f, 0.0f ); + } +} + +/* +================== +BoxOnPlaneSide + +Returns 1, 2, or 1 + 2 +================== +*/ +int BoxOnPlaneSide( const Vector &emins, const Vector &emaxs, const mplane_t *p ) +{ + float dist1, dist2; + int sides = 0; + + // general case + switch( p->signbits ) + { + case 0: + dist1 = p->normal.x * emaxs.x + p->normal.y * emaxs.y + p->normal.z * emaxs.z; + dist2 = p->normal.x * emins.x + p->normal.y * emins.y + p->normal.z * emins.z; + break; + case 1: + dist1 = p->normal.x * emins.x + p->normal.y * emaxs.y + p->normal.z * emaxs.z; + dist2 = p->normal.x * emaxs.x + p->normal.y * emins.y + p->normal.z * emins.z; + break; + case 2: + dist1 = p->normal.x * emaxs.x + p->normal.y * emins.y + p->normal.z * emaxs.z; + dist2 = p->normal.x * emins.x + p->normal.y * emaxs.y + p->normal.z * emins.z; + break; + case 3: + dist1 = p->normal.x * emins.x + p->normal.y * emins.y + p->normal.z * emaxs.z; + dist2 = p->normal.x * emaxs.x + p->normal.y * emaxs.y + p->normal.z * emins.z; + break; + case 4: + dist1 = p->normal.x * emaxs.x + p->normal.y * emaxs.y + p->normal.z * emins.z; + dist2 = p->normal.x * emins.x + p->normal.y * emins.y + p->normal.z * emaxs.z; + break; + case 5: + dist1 = p->normal.x * emins.x + p->normal.y * emaxs.y + p->normal.z * emins.z; + dist2 = p->normal.x * emaxs.x + p->normal.y * emins.y + p->normal.z * emaxs.z; + break; + case 6: + dist1 = p->normal.x * emaxs.x + p->normal.y * emins.y + p->normal.z * emins.z; + dist2 = p->normal.x * emins.x + p->normal.y * emaxs.y + p->normal.z * emaxs.z; + break; + case 7: + dist1 = p->normal.x * emins.x + p->normal.y * emins.y + p->normal.z * emins.z; + dist2 = p->normal.x * emaxs.x + p->normal.y * emaxs.y + p->normal.z * emaxs.z; + break; + default: + // shut up compiler + dist1 = dist2 = 0; + break; + } + + if( dist1 >= p->dist ) + sides = 1; + if( dist2 < p->dist ) + sides |= 2; + + return sides; +} + +/* +===================== +PlaneFromPoints + +Returns false if the triangle is degenrate. +The normal will point out of the clock for clockwise ordered points +===================== +*/ +bool PlaneFromPoints( const Vector triangle[3], mplane_t *plane ) +{ + Vector v1 = triangle[1] - triangle[0]; + Vector v2 = triangle[2] - triangle[0]; + plane->normal = CrossProduct( v2, v1 ); + + if( plane->normal.Length() == 0.0f ) + { + plane->normal = g_vecZero; + return false; + } + + plane->normal = plane->normal.Normalize(); + + plane->dist = DotProduct( triangle[0], plane->normal ); + return true; +} + +/* +================= +CategorizePlane + +A slightly more complex version of SignbitsForPlane and PlaneTypeForNormal, +which also tries to fix possible floating point glitches (like -0.00000 cases) +================= +*/ +void CategorizePlane( mplane_t *plane ) +{ + plane->signbits = 0; + plane->type = PLANE_NONAXIAL; + + for( int i = 0; i < 3; i++ ) + { + if( plane->normal[i] < 0 ) + { + plane->signbits |= (1<normal[i] == -1.0f ) + { + plane->signbits = (1<normal = g_vecZero; + plane->normal[i] = -1.0f; + break; + } + } + else if( plane->normal[i] == 1.0f ) + { + plane->type = i; + plane->signbits = 0; + plane->normal = g_vecZero; + plane->normal[i] = 1.0f; + break; + } + } +} + +/* +================= +ComparePlanes +================= +*/ +bool ComparePlanes( mplane_t *plane, const Vector &normal, float dist ) +{ + if( fabs( plane->normal.x - normal.x ) < PLANE_NORMAL_EPSILON + && fabs( plane->normal.y - normal.y ) < PLANE_NORMAL_EPSILON + && fabs( plane->normal.z - normal.z ) < PLANE_NORMAL_EPSILON + && fabs( plane->dist - dist ) < PLANE_DIST_EPSILON ) + return true; + return false; +} + +/* +================== +SnapVectorToGrid + +================== +*/ +void SnapVectorToGrid( Vector &normal ) +{ + for( int i = 0; i < 3; i++ ) + { + if( fabs( normal[i] - 1.0f ) < PLANE_NORMAL_EPSILON ) + { + normal = g_vecZero; + normal[i] = 1.0f; + break; + } + + if( fabs( normal[i] - -1.0f ) < PLANE_NORMAL_EPSILON ) + { + normal = g_vecZero; + normal[i] = -1.0f; + break; + } + } +} + +/* +============== +SnapPlaneToGrid + +============== +*/ +void SnapPlaneToGrid( mplane_t *plane ) +{ + SnapVectorToGrid( plane->normal ); + + if( fabs( plane->dist - Q_rint( plane->dist )) < PLANE_DIST_EPSILON ) + plane->dist = Q_rint( plane->dist ); +} + +/* +================ +VectorIsOnAxis +================ +*/ +bool VectorIsOnAxis( const Vector &v ) +{ + int count = 0; + + for( int i = 0; i < 3; i++ ) + { + if( v[i] == 0.0 ) + count++; + } + + // the zero vector will be on axis. + return (count > 1) ? true : false; +} + +/* +============== +VectorCompareEpsilon + +============== +*/ +bool VectorCompareEpsilon( const Vector &vec1, const Vector &vec2, float epsilon ) +{ + float ax, ay, az; + + ax = fabs( vec1.x - vec2.x ); + ay = fabs( vec1.y - vec2.y ); + az = fabs( vec1.z - vec2.z ); + + if(( ax < epsilon ) && ( ay < epsilon ) && ( az < epsilon )) + return true; + return false; +} + +bool RadianCompareEpsilon( const Radian &vec1, const Radian &vec2, float epsilon ) +{ + for( int i = 0; i < 3; i++ ) + { + // clamp to 2pi + float a1 = fmod( vec1[i], (float)(M_PI * 2)); + float a2 = fmod( vec2[i], (float)(M_PI * 2)); + float delta = fabs( a1 - a2 ); + + // use the smaller angle (359 == 1 degree off) + if( delta > M_PI ) + { + delta = 2 * M_PI - delta; + } + + if( delta > epsilon ) + return 0; + } + return 1; +} + +/* +================== +VertexHashKey +================== +*/ +unsigned int VertexHashKey( const vec3_t point, unsigned int hashSize ) +{ + unsigned int hashKey = 0; + + hashKey ^= int( fabs( point[0] )); + hashKey ^= int( fabs( point[1] )); + hashKey ^= int( fabs( point[2] )); + + hashKey &= (hashSize - 1); + + return hashKey; +} + +// rotate a vector around the Z axis (YAW) +Vector VectorYawRotate( const Vector &in, float flYaw ) +{ + Vector out; + float sy, cy; + + SinCos( DEG2RAD(flYaw), &sy, &cy ); + + out.x = in.x * cy - in.y * sy; + out.y = in.x * sy + in.y * cy; + out.z = in.z; + + return out; +} + +// solve a x^2 + b x + c = 0 +bool SolveQuadratic( float a, float b, float c, float &root1, float &root2 ) +{ + if( a == 0 ) + { + if( b != 0 ) + { + // no x^2 component, it's a linear system + root1 = root2 = -c / b; + return true; + } + + if( c == 0 ) + { + // all zero's + root1 = root2 = 0; + return true; + } + return false; + } + + float tmp = b * b - 4.0f * a * c; + + if( tmp < 0 ) + { + // imaginary number, bah, no solution. + return false; + } + + tmp = sqrt( tmp ); + root1 = (-b + tmp) / (2.0f * a); + root2 = (-b - tmp) / (2.0f * a); + + return true; +} + +// solves for "a, b, c" where "a x^2 + b x + c = y", return true if solution exists +bool SolveInverseQuadratic( float x1, float y1, float x2, float y2, float x3, float y3, float &a, float &b, float &c ) +{ + float det = (x1 - x2) * (x1 - x3) * (x2 - x3); + + // FIXME: check with some sort of epsilon + if( det == 0.0 ) return false; + + a = (x3*(-y1 + y2) + x2*(y1 - y3) + x1*(-y2 + y3)) / det; + + b = (x3*x3*(y1 - y2) + x1*x1*(y2 - y3) + x2*x2*(-y1 + y3)) / det; + + c = (x1*x3*(-x1 + x3)*y2 + x2*x2*(x3*y1 - x1*y3) + x2*(-(x3*x3*y1) + x1*x1*y3)) / det; + + return true; +} + +float ColorNormalize( const Vector &in, Vector &out ) +{ + float max, scale; + + max = in.x; + if( in.y > max ) + max = in.y; + if( in.z > max ) + max = in.z; + + if( max == 0.0f ) + return 0.0f; + + scale = 1.0f / max; + out = in * scale; + + return max; +} + +void CalcTBN( const Vector &p0, const Vector &p1, const Vector &p2, const Vector2D &t0, const Vector2D &t1, const Vector2D& t2, Vector &s, Vector &t, bool areaweight ) +{ + // Compute the partial derivatives of X, Y, and Z with respect to S and T. + s.Init( 0.0f, 0.0f, 0.0f ); + t.Init( 0.0f, 0.0f, 0.0f ); + + // x, s, t + Vector edge01( p1.x - p0.x, t1.x - t0.x, t1.y - t0.y ); + Vector edge02( p2.x - p0.x, t2.x - t0.x, t2.y - t0.y ); + + Vector cross = CrossProduct( edge01, edge02 ); + + if( fabs( cross.x ) > SMALL_FLOAT ) + { + s.x += -cross.y / cross.x; + t.x += -cross.z / cross.x; + } + + // y, s, t + edge01.Init( p1.y - p0.y, t1.x - t0.x, t1.y - t0.y ); + edge02.Init( p2.y - p0.y, t2.x - t0.x, t2.y - t0.y ); + + cross = CrossProduct( edge01, edge02 ); + + if( fabs( cross.x ) > SMALL_FLOAT ) + { + s.y += -cross.y / cross.x; + t.y += -cross.z / cross.x; + } + + // z, s, t + edge01.Init( p1.z - p0.z, t1.x - t0.x, t1.y - t0.y ); + edge02.Init( p2.z - p0.z, t2.x - t0.x, t2.y - t0.y ); + + cross = CrossProduct( edge01, edge02 ); + + if( fabs( cross.x ) > SMALL_FLOAT ) + { + s.z += -cross.y / cross.x; + t.z += -cross.z / cross.x; + } + + if( !areaweight ) + { + // Normalize s and t + s = s.Normalize(); + t = t.Normalize(); + } +} + +/* +================== +UTIL_MoveBounds +================== +*/ +void UTIL_MoveBounds( const Vector &start, const Vector &mins, const Vector &maxs, const Vector &end, Vector &outmins, Vector &outmaxs ) +{ + for( int i = 0; i < 3; i++ ) + { + if( end[i] > start[i] ) + { + outmins[i] = start[i] + mins[i] - 1; + outmaxs[i] = end[i] + maxs[i] + 1; + } + else + { + outmins[i] = end[i] + mins[i] - 1; + outmaxs[i] = start[i] + maxs[i] + 1; + } + } +} + +unsigned short FloatToHalf( float v ) +{ + unsigned int i = *((unsigned int *)&v); + unsigned int e = (i >> 23) & 0x00ff; + unsigned int m = i & 0x007fffff; + unsigned short h; + + if( e <= 127 - 15 ) + h = ((m | 0x00800000) >> (127 - 14 - e)) >> 13; + else h = (i >> 13) & 0x3fff; + + h |= (i >> 16) & 0xc000; + + return h; +} + +float HalfToFloat( unsigned short h ) +{ + unsigned int f = (h << 16) & 0x80000000; + unsigned int em = h & 0x7fff; + + if( em > 0x03ff ) + { + f |= (em << 13) + ((127 - 15) << 23); + } + else + { + unsigned int m = em & 0x03ff; + + if( m != 0 ) + { + unsigned int e = (em >> 10) & 0x1f; + + while(( m & 0x0400 ) == 0 ) + { + m <<= 1; + e--; + } + + m &= 0x3ff; + f |= ((e + (127 - 14)) << 23) | (m << 13); + } + } + + return *((float *)&f); +} + +signed char FloatToChar( float v ) +{ + float in = v * 127.0f; + float out = bound( -128.0f, in, 127.0f ); + return (char)out; +} + +/* +==================== +V_CalcFov +==================== +*/ +float V_CalcFov( float &fov_x, float width, float height ) +{ + float x, half_fov_y; + + if( fov_x < 1 || fov_x > 170 ) + fov_x = 90; + + x = width / tan( DEG2RAD( fov_x ) * 0.5f ); + half_fov_y = atan( height / x ); + + return RAD2DEG( half_fov_y ) * 2; +} + +/* +==================== +V_AdjustFov +==================== +*/ +void V_AdjustFov( float &fov_x, float &fov_y, float width, float height, bool lock_x ) +{ + float x, y; + + if(( width * 3 ) == ( 4 * height ) || ( width * 4 ) == ( height * 5 )) + { + // 4:3 or 5:4 ratio + return; + } + + if( lock_x ) + { + fov_y = 2 * atan(( width * 3 ) / ( height * 4 ) * tan( fov_y * M_PI / 360.0 * 0.5 )) * 360 / M_PI; + return; + } + + y = V_CalcFov( fov_x, 640, 480 ); + x = fov_x; + + fov_x = V_CalcFov( y, height, width ); + + if( fov_x < x ) + fov_x = x; + else fov_y = y; +} + +/* +==================== +ColorToFloat + +pack RGB into single float +==================== +*/ +float ColorToFloat( const Vector &color ) +{ + int icolor[3]; + + icolor[0] = color.x * 255; + icolor[1] = color.y * 255; + icolor[2] = color.z * 255; + + int pack = (icolor[0] << 16) | (icolor[1] << 8) | icolor[2]; + return (float)((double)pack / (double)(1 << 24)); +} + +/* +==================== +ColorToNormal + +pack XYZ into single float +==================== +*/ +float NormalToFloat( const Vector &normal ) +{ + int inormal[3]; + Vector n = normal.Normalize(); + + inormal[0] = n.x * 127 + 128; + inormal[1] = n.y * 127 + 128; + inormal[2] = n.z * 127 + 128; + + int pack = (inormal[0] << 16) | (inormal[1] << 8) | inormal[2]; + return (float)((double)pack / (double)(1 << 24)); +} + +/* +==================== +ColorToFloat + +pack local coords into single float +==================== +*/ +float OriginToFloat( const Vector &pos, const Vector ¢er ) +{ + Vector local = (pos - center) * 0.5f; // expand the range to -256\+256 units + int ilocal[3]; + + // clamp to -128\127 + ilocal[0] = bound( -128, local.x, 127 ) + 128; + ilocal[1] = bound( -128, local.y, 127 ) + 128; + ilocal[2] = bound( -128, local.z, 127 ) + 128; + + int pack = (ilocal[0] << 16) | (ilocal[1] << 8) | ilocal[2]; + return (float)((double)pack / (double)(1 << 24)); +} + +float PackColor( const Vector &color ) +{ + byte r = color.x, g = color.y, b = color.z; + return (float)((double)((r << 16) | (g << 8) | b) / (double)(1 << 24)); +} + +float PackNormal( const Vector &normal ) +{ + Vector lb; + + for( int i = 0; i < 3; i++ ) + { + lb[i] = normal[i] * 127.0f + 128.0f; + lb[i] = bound( 0, lb[i], 255.0 ); + } + + byte x = lb.x, y = lb.y, z = lb.z; + return (float)((double)((x << 16) | (y << 8) | z) / (double)(1 << 24)); +} \ No newline at end of file diff --git a/game_shared/mathlib.h b/game_shared/mathlib.h new file mode 100644 index 0000000..3fcff63 --- /dev/null +++ b/game_shared/mathlib.h @@ -0,0 +1,265 @@ +//======================================================================= +// Copyright (C) Shambler Team 2005 +// mathlib.h - shared mathlib header +//======================================================================= +#ifndef MATHLIB_H +#define MATHLIB_H + +#ifndef M_PI +#define M_PI (float)3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +#ifndef M_PI2 +#define M_PI2 (float)6.28318530717958647692 +#endif + +#ifndef byte +typedef unsigned char byte; +#endif // byte + +#ifndef vec_t +typedef float vec_t; +#endif + +#include +#include +#include "matrix.h" + +// NOTE: PhysX mathlib is conflicted with standard min\max +#define Q_min( a, b ) (((a) < (b)) ? (a) : (b)) +#define Q_max( a, b ) (((a) > (b)) ? (a) : (b)) +#define Q_recip( a ) ((float)(1.0f / (float)(a))) +#define Q_floor( a ) ((float)(long)(a)) +#define Q_ceil( a ) ((float)(long)((a) + 1)) +#define Q_round( x, y ) (floor( x / y + 0.5 ) * y ) +#define Q_square( a ) ((a) * (a)) +#define Q_sign( x ) ( x >= 0 ? 1.0 : -1.0 ) + +#define bound( min, num, max ) ((num) >= (min) ? ((num) < (max) ? (num) : (max)) : (min)) +#define saturate( val ) ((val) >= 0 ? ((val) < 1 ? (val) : 1) : 0) +#define IS_NAN(x) (((*(int *)&x) & (255<<23)) == (255<<23)) + +// some Physic Engine declarations +#define METERS_PER_INCH 0.0254f +#define METER2INCH( x ) (float)( x * ( 1.0f / METERS_PER_INCH )) +#define INCH2METER( x ) (float)( x * ( METERS_PER_INCH / 1.0f )) + +// Keeps clutter down a bit, when using a float as a bit-vector +#define SetBits( iBitVector, bits ) ((iBitVector) = (iBitVector) | (bits)) +#define ClearBits( iBitVector, bits ) ((iBitVector) = (iBitVector) & ~(bits)) +#define FBitSet( iBitVector, bit ) ((iBitVector) & (bit)) + +#define CHARSIGNBITSET( c ) (((const uint)(c)) >> 7) +#define CHARSIGNBITNOTSET( c ) ((~((const uint)(c))) >> 7) + +#define SHORTSIGNBITSET( s ) (((const uint)(s)) >> 15) +#define SHORTSIGNBITNOTSET( s ) ((~((const uint)(s))) >> 15) + +#define INTSIGNBITSET( i ) (((const uint)(i)) >> 31) +#define INTSIGNBITNOTSET( i ) ((~((const uint)(i))) >> 31) + +#define FLOATSIGNBITSET( f ) ((*(const uint *)&(f)) >> 31) +#define FLOATSIGNBITNOTSET( f ) ((~(*(const uint *)&(f))) >> 31) + +#define KNEEMAX_EPSILON 0.9998f // (0.9998 is about 1 degree) + +// Used to represent sides of things like planes. +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 + +#define PLANE_X 0 // 0 - 2 are axial planes +#define PLANE_Y 1 // 3 needs alternate calc +#define PLANE_Z 2 +#define PLANE_LAST_AXIAL 2 +#define PLANE_NONAXIAL 3 + +#define PLANE_NORMAL_EPSILON 1e-5 +#define PLANE_DIR_EPSILON 1e-4 +#define PLANE_DIST_EPSILON 4e-2 + +#define SMALL_FLOAT 1e-12 + +inline float Q_fabs( float f ) { int tmp = *( int *)&f; tmp &= 0x7FFFFFFF; return *( float *)&tmp; } +inline float anglemod( float a ) { return (360.0f / 65536) * ((int)(a * (65536 / 360.0f)) & 65535); } +void TransformAABB( const matrix4x4& world, const Vector &mins, const Vector &maxs, Vector &absmin, Vector &absmax ); +void TransformAABBLocal( const matrix4x4& world, const Vector &mins, const Vector &maxs, Vector &outmins, Vector &outmaxs ); +void CalcClosestPointOnAABB( const Vector &mins, const Vector &maxs, const Vector &point, Vector &closestOut ); +float CalcSqrDistanceToAABB( const Vector &mins, const Vector &maxs, const Vector &point ); +void RotatePointAroundVector( Vector &dst, const Vector &dir, const Vector &point, float degrees ); +bool PlanesGetIntersectionPoint( const struct mplane_s *plane1, const struct mplane_s *plane2, const struct mplane_s *plane3, Vector &out ); +bool IsSphereIntersectingCone( const Vector &sphereCenter, float sphereRadius, const Vector &coneOrigin, const Vector &coneNormal, float coneSine, float coneCosine ); +void PlaneFromPoints( const Vector &p0, const Vector &p1, const Vector &p2, struct mplane_s *plane ); +Vector PlaneIntersect( struct mplane_s *plane, const Vector& p0, const Vector& p1 ); +void VectorAngles( const Vector &forward, Vector &angles ); +void VectorAngles2( const Vector &forward, Vector &angles ); +void MakeAxial( float normal[3] ); + +// Remap a value in the range [A,B] to [C,D]. +inline float RemapVal( float val, float A, float B, float C, float D) +{ + if( A == B ) return val >= B ? D : C; + return C + (D - C) * (val - A) / (B - A); +} + +inline float RemapValClamped( float val, float A, float B, float C, float D) +{ + if( A == B ) return val >= B ? D : C; + float cVal = (val - A) / (B - A); + cVal = bound( 0.0f, cVal, 1.0f ); + + return C + (D - C) * cVal; +} + +// Returns A + (B-A)*flPercent. +// float Lerp( float flPercent, float A, float B ); +template +_forceinline T Lerp( float flPercent, T const &A, T const &B ) +{ + return A + (B - A) * flPercent; +} + +int PlaneTypeForNormal( const Vector &normal ); +int SignbitsForPlane( const Vector &normal ); +int NearestPOW( int value, bool roundDown ); +void SetPlane( struct mplane_s *plane, const Vector &vecNormal, float flDist, int type = -1 ); + +// +// bounds operations +// +void ClearBounds( Vector &mins, Vector &maxs ); +void ClearBounds( Vector2D &mins, Vector2D &maxs ); +bool BoundsIsCleared( const Vector &mins, const Vector &maxs ); +bool BoundsIsNull( const Vector &mins, const Vector &maxs ); +void ExpandBounds( Vector &mins, Vector &maxs, float offset ); +void AddPointToBounds( const Vector &v, Vector &mins, Vector &maxs, float limit = 0.0f ); +void AddPointToBounds( const Vector2D &v, Vector2D &mins, Vector2D &maxs ); +bool BoundsIntersect( const Vector &mins1, const Vector &maxs1, const Vector &mins2, const Vector &maxs2 ); +bool BoundsIntersect( const Vector2D &mins1, const Vector2D &maxs1, const Vector2D &mins2, const Vector2D &maxs2 ); +bool BoundsAndSphereIntersect( const Vector &mins, const Vector &maxs, const Vector &origin, float radius ); +bool BoundsAndSphereIntersect( const Vector2D &mins, const Vector2D &maxs, const Vector2D &origin, float radius ); +void UTIL_MoveBounds( const Vector &start, const Vector &mins, const Vector &maxs, const Vector &end, Vector &outmins, Vector &outmaxs ); +float RadiusFromBounds( const Vector &mins, const Vector &maxs ); +void PerpendicularVector( Vector &dst, const Vector &src ); + +// +// quaternion operations +// +void AngleQuaternion( const Vector &angles, Vector4D &quat ); +void AngleQuaternion( const Radian &angles, Vector4D &quat ); +void AxisAngleQuaternion( const Vector &axis, float angle, Vector4D &q ); +void QuaternionAngle( const Vector4D &quat, Vector &angles ); +void QuaternionAngle( const Vector4D &quat, Radian &angles ); +void QuaternionAlign( const Vector4D &p, const Vector4D &q, Vector4D &qt ); +void QuaternionSlerp( const Vector4D &p, const Vector4D &q, float t, Vector4D &qt ); +void QuaternionSlerp( const Radian &r0, const Radian &r1, float t, Radian &r2 ); +void QuaternionBlend( const Vector4D &p, const Vector4D &q, float t, Vector4D &qt ); +void QuaternionSlerpNoAlign( const Vector4D &p, const Vector4D &q, float t, Vector4D &qt ); +void QuaternionBlendNoAlign( const Vector4D &p, const Vector4D &q, float t, Vector4D &qt ); +void QuaternionMultiply( const Vector4D &q1, const Vector4D &q2, Vector4D &out ); +void QuaternionVectorTransform( const Vector4D &q, const Vector &v, Vector &out ); +void QuaternionConcatTransforms( const Vector4D &q1, const Vector &v1, const Vector4D &q2, const Vector &v2, Vector4D &q, Vector &v ); +void QuaternionSM( float s, const Vector4D &p, const Vector4D &q, Vector4D &qt ); +void QuaternionMA( const Vector4D &p, float s, const Vector4D &q, Vector4D &qt ); +void QuaternionAccumulate( const Vector4D &p, float s, const Vector4D &q, Vector4D &qt ); +void QuaternionMult( const Vector4D &p, const Vector4D &q, Vector4D &qt ); +void QuaternionAdd( const Vector4D &p, const Vector4D &q, Vector4D &qt ); +void QuaternionScale( const Vector4D &p, float t, Vector4D &q ); + +// +// lerping stuff +// +void InterpolateOrigin( const Vector& start, const Vector& end, Vector& output, float frac, bool back = false ); +void InterpolateAngles( const Vector& start, const Vector& end, Vector& output, float frac, bool back = false ); +void NormalizeAngles( Vector &angles ); + +// +// FOV computing +// +float V_CalcFov( float &fov_x, float width, float height ); +void V_AdjustFov( float &fov_x, float &fov_y, float width, float height, bool lock_x ); + +struct mplane_s; + +void VectorMatrix( Vector &forward, Vector &right, Vector &up ); +float ColorNormalize( const Vector &in, Vector &out ); +// solve for "x" where "a x^2 + b x + c = 0", return true if solution exists +bool SolveQuadratic( float a, float b, float c, float &root1, float &root2 ); +// solves for "a, b, c" where "a x^2 + b x + c = y", return true if solution exists +bool SolveInverseQuadratic( float x1, float y1, float x2, float y2, float x3, float y3, float &a, float &b, float &c ); +bool PlaneFromPoints( const Vector triangle[3], struct mplane_s *plane ); +bool ComparePlanes( struct mplane_s *plane, const Vector &normal, float dist ); +bool VectorCompareEpsilon( const Vector &vec1, const Vector &vec2, float epsilon ); +bool RadianCompareEpsilon( const Radian &vec1, const Radian &vec2, float epsilon ); +unsigned int VertexHashKey( const vec3_t point, unsigned int hashSize ); +void CategorizePlane( struct mplane_s *plane ); +void SnapPlaneToGrid( struct mplane_s *plane ); +void SnapVectorToGrid( Vector &normal ); +bool VectorIsOnAxis( const Vector &v ); + +void CalcTBN( const Vector &p0, const Vector &p1, const Vector &p2, const Vector2D &t0, const Vector2D &t1, const Vector2D& t2, Vector &s, Vector &t, bool areaweight = false ); + +int BoxOnPlaneSide( const Vector &emins, const Vector &emaxs, const struct mplane_s *plane ); +#define BOX_ON_PLANE_SIDE(emins, emaxs, p) (((p)->type < 3)?(((p)->dist <= (emins)[(p)->type])? 1 : (((p)->dist >= (emaxs)[(p)->type])? 2 : 3)):BoxOnPlaneSide( (emins), (emaxs), (p))) + +#define PlaneDist(point,plane) ((plane)->type < 3 ? (point)[(plane)->type] : DotProduct((point), (plane)->normal)) +#define PlaneDiff(point,plane) (((plane)->type < 3 ? (point)[(plane)->type] : DotProduct((point), (plane)->normal)) - (plane)->dist) +#define VectorDistance(a, b) (sqrt( VectorDistance2( a, b ))) +#define VectorDistance2(a, b) (((a)[0] - (b)[0]) * ((a)[0] - (b)[0]) + ((a)[1] - (b)[1]) * ((a)[1] - (b)[1]) + ((a)[2] - (b)[2]) * ((a)[2] - (b)[2])) +Vector VectorYawRotate( const Vector &in, float flYaw ); + +// FIXME: get rid of this +#define VectorCopy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2]) +#define VectorMax(a) ( Q_max((a)[0], Q_max((a)[1], (a)[2])) ) + +#define TriangleNormal(a,b,c,n) \ +( \ + (n)[0] = ((a)[1] - (b)[1]) * ((c)[2] - (b)[2]) - ((a)[2] - (b)[2]) * ((c)[1] - (b)[1]), \ + (n)[1] = ((a)[2] - (b)[2]) * ((c)[0] - (b)[0]) - ((a)[0] - (b)[0]) * ((c)[2] - (b)[2]), \ + (n)[2] = ((a)[0] - (b)[0]) * ((c)[1] - (b)[1]) - ((a)[1] - (b)[1]) * ((c)[0] - (b)[0]) \ +) + +/* +================= +rsqrt +================= +*/ +inline float rsqrt( float number ) +{ + if( number == 0.0f ) + return 0.0f; + + int x = number * 0.5f; + int i = *(int *)&number; // evil floating point bit level hacking + i = 0x5f3759df - (i >> 1); // what the fuck? + int y = *(float *)&i; + y = y * (1.5f - (x * y * y)); // first iteration + + return y; +} + +// hermite basis function for smooth interpolation +// Similar to Gain() above, but very cheap to call +// value should be between 0 & 1 inclusive +inline float SimpleSpline( float value ) +{ + float valueSquared = value * value; + + // Nice little ease-in, ease-out spline-like curve + return (3 * valueSquared - 2 * valueSquared * value); +} + +unsigned short FloatToHalf( float v ); +float HalfToFloat( unsigned short h ); +signed char FloatToChar( float v ); + +float ColorToFloat( const Vector &color ); +float NormalToFloat( const Vector &normal ); +float OriginToFloat( const Vector &pos, const Vector ¢er ); +float PackColor( const Vector &color ); +float PackNormal( const Vector &normal ); + +extern float g_hullcolor[8][3]; +extern int g_boxpnt[6][4]; + +#endif//MATHLIB_H \ No newline at end of file diff --git a/game_shared/matrix.cpp b/game_shared/matrix.cpp new file mode 100644 index 0000000..838bd78 --- /dev/null +++ b/game_shared/matrix.cpp @@ -0,0 +1,1170 @@ +//======================================================================= +// Copyright (C) XashXT Group 2011 +//======================================================================= + +#include +#include +#include +#include + +matrix3x3::matrix3x3( void ) +{ +} + +void matrix3x3::Identity( void ) +{ + mat[0] = Vector( 1, 0, 0 ); + mat[1] = Vector( 0, 1, 0 ); + mat[2] = Vector( 0, 0, 1 ); +} + +void matrix3x3 :: FromVector( const Vector &forward ) +{ + if( forward.x || forward.y ) + { + mat[0] = forward; + mat[1] = Vector( forward.y, -forward.x, 0.0f ).Normalize(); + mat[2] = CrossProduct( mat[0], mat[1] ); + } + else + { + mat[0] = forward; + mat[1] = Vector( 1.0f, 0.0f, 0.0f ); + mat[2] = Vector( 0.0f, 1.0f, 0.0f ); + } +} + +// from class matrix3x4 to class matrix3x3 +matrix3x3& matrix3x3 :: operator=(const matrix3x4 &vOther) +{ + mat[0][0] = vOther[0][0]; + mat[1][1] = vOther[1][1]; + mat[2][2] = vOther[2][2]; + mat[0][1] = vOther[1][0]; + mat[0][2] = vOther[1][0]; + mat[1][0] = vOther[0][1]; + mat[1][2] = vOther[2][1]; + mat[2][0] = vOther[0][2]; + mat[2][1] = vOther[1][2]; + + return *this; +} + +// from class matrix4x4 to class matrix3x3 +matrix3x3& matrix3x3 :: operator=(const matrix4x4 &vOther) +{ + mat[0][0] = vOther[0][0]; + mat[1][1] = vOther[1][1]; + mat[2][2] = vOther[2][2]; + mat[0][1] = vOther[1][0]; + mat[0][2] = vOther[1][0]; + mat[1][0] = vOther[0][1]; + mat[1][2] = vOther[2][1]; + mat[2][0] = vOther[0][2]; + mat[2][1] = vOther[1][2]; + return *this; +} + +Vector matrix3x3::VectorRotate( const Vector &v ) const +{ + Vector out; + + out[0] = v[0] * mat[0][0] + v[1] * mat[1][0] + v[2] * mat[2][0]; + out[1] = v[0] * mat[0][1] + v[1] * mat[1][1] + v[2] * mat[2][1]; + out[2] = v[0] * mat[0][2] + v[1] * mat[1][2] + v[2] * mat[2][2]; + + return out; +} + +Vector matrix3x3::VectorIRotate( const Vector &v ) const +{ + Vector out; + + out[0] = v[0] * mat[0][0] + v[1] * mat[0][1] + v[2] * mat[0][2]; + out[1] = v[0] * mat[1][0] + v[1] * mat[1][1] + v[2] * mat[1][2]; + out[2] = v[0] * mat[2][0] + v[1] * mat[2][1] + v[2] * mat[2][2]; + + return out; +} + +Vector4D matrix3x3 :: GetQuaternion( void ) +{ + float trace = mat[0][0] + mat[1][1] + mat[2][2]; + Vector4D quat; + + if(trace > 0.0f) + { + float r = sqrt(1.0f + trace), inv = 0.5f / r; + quat[0] = (mat[1][2] - mat[2][1]) * inv; + quat[1] = (mat[2][0] - mat[0][2]) * inv; + quat[2] = (mat[0][1] - mat[1][0]) * inv; + quat[3] = 0.5f * r; + } + else if(mat[0][0] > mat[1][1] && mat[0][0] > mat[2][2]) + { + float r = sqrt(1.0f + mat[0][0] - mat[1][1] - mat[2][2]), inv = 0.5f / r; + quat[0] = 0.5f * r; + quat[1] = (mat[0][1] + mat[1][0]) * inv; + quat[2] = (mat[2][0] + mat[0][2]) * inv; + quat[3] = (mat[1][2] - mat[2][1]) * inv; + } + else if(mat[1][1] > mat[2][2]) + { + float r = sqrt(1.0f + mat[1][1] - mat[0][0] - mat[2][2]), inv = 0.5f / r; + quat[0] = (mat[0][1] + mat[1][0]) * inv; + quat[1] = 0.5f * r; + quat[2] = (mat[1][2] + mat[2][1]) * inv; + quat[3] = (mat[2][0] - mat[0][2]) * inv; + } + else + { + float r = sqrt(1.0f + mat[2][2] - mat[0][0] - mat[1][1]), inv = 0.5f / r; + quat[0] = (mat[2][0] + mat[0][2]) * inv; + quat[1] = (mat[1][2] + mat[2][1]) * inv; + quat[2] = 0.5f * r; + quat[3] = (mat[0][1] - mat[1][0]) * inv; + } + + return quat; +} + +matrix3x3 matrix3x3 :: Concat( const matrix3x3 mat2 ) +{ + matrix3x3 out; + + out[0][0] = mat[0][0] * mat2[0][0] + mat[0][1] * mat2[1][0] + mat[0][2] * mat2[2][0]; + out[0][1] = mat[0][0] * mat2[0][1] + mat[0][1] * mat2[1][1] + mat[0][2] * mat2[2][1]; + out[0][2] = mat[0][0] * mat2[0][2] + mat[0][1] * mat2[1][2] + mat[0][2] * mat2[2][2]; + out[1][0] = mat[1][0] * mat2[0][0] + mat[1][1] * mat2[1][0] + mat[1][2] * mat2[2][0]; + out[1][1] = mat[1][0] * mat2[0][1] + mat[1][1] * mat2[1][1] + mat[1][2] * mat2[2][1]; + out[1][2] = mat[1][0] * mat2[0][2] + mat[1][1] * mat2[1][2] + mat[1][2] * mat2[2][2]; + out[2][0] = mat[2][0] * mat2[0][0] + mat[2][1] * mat2[1][0] + mat[2][2] * mat2[2][0]; + out[2][1] = mat[2][0] * mat2[0][1] + mat[2][1] * mat2[1][1] + mat[2][2] * mat2[2][1]; + out[2][2] = mat[2][0] * mat2[0][2] + mat[2][1] * mat2[1][2] + mat[2][2] * mat2[2][2]; + + return out; +} + +matrix3x4::matrix3x4( void ) +{ +} + +void matrix3x4::Identity( void ) +{ + mat[0] = Vector( 1, 0, 0 ); + mat[1] = Vector( 0, 1, 0 ); + mat[2] = Vector( 0, 0, 1 ); + mat[3] = Vector( 0, 0, 0 ); +} + +Vector matrix3x4::VectorTransform( const Vector &v ) const +{ + Vector out; + + out[0] = v[0] * mat[0][0] + v[1] * mat[1][0] + v[2] * mat[2][0] + mat[3][0]; + out[1] = v[0] * mat[0][1] + v[1] * mat[1][1] + v[2] * mat[2][1] + mat[3][1]; + out[2] = v[0] * mat[0][2] + v[1] * mat[1][2] + v[2] * mat[2][2] + mat[3][2]; + + return out; +} + +Vector matrix3x4::VectorITransform( const Vector &v ) const +{ + Vector iv, out; + + iv[0] = v[0] - mat[3][0]; + iv[1] = v[1] - mat[3][1]; + iv[2] = v[2] - mat[3][2]; + + out[0] = iv[0] * mat[0][0] + iv[1] * mat[0][1] + iv[2] * mat[0][2]; + out[1] = iv[0] * mat[1][0] + iv[1] * mat[1][1] + iv[2] * mat[1][2]; + out[2] = iv[0] * mat[2][0] + iv[1] * mat[2][1] + iv[2] * mat[2][2]; + + return out; +} + +Vector matrix3x4::VectorRotate( const Vector &v ) const +{ + Vector out; + + out[0] = v[0] * mat[0][0] + v[1] * mat[1][0] + v[2] * mat[2][0]; + out[1] = v[0] * mat[0][1] + v[1] * mat[1][1] + v[2] * mat[2][1]; + out[2] = v[0] * mat[0][2] + v[1] * mat[1][2] + v[2] * mat[2][2]; + + return out; +} + +Vector matrix3x4::VectorIRotate( const Vector &v ) const +{ + Vector out; + + out[0] = v[0] * mat[0][0] + v[1] * mat[0][1] + v[2] * mat[0][2]; + out[1] = v[0] * mat[1][0] + v[1] * mat[1][1] + v[2] * mat[1][2]; + out[2] = v[0] * mat[2][0] + v[1] * mat[2][1] + v[2] * mat[2][2]; + + return out; +} + +Vector4D matrix3x4 :: GetQuaternion( void ) +{ + float trace = mat[0][0] + mat[1][1] + mat[2][2]; + Vector4D quat; + + if( trace > 0.0f ) + { + float r = sqrt(1.0f + trace), inv = 0.5f / r; + quat[0] = (mat[1][2] - mat[2][1]) * inv; + quat[1] = (mat[2][0] - mat[0][2]) * inv; + quat[2] = (mat[0][1] - mat[1][0]) * inv; + quat[3] = 0.5f * r; + } + else if( mat[0][0] > mat[1][1] && mat[0][0] > mat[2][2] ) + { + float r = sqrt(1.0f + mat[0][0] - mat[1][1] - mat[2][2]), inv = 0.5f / r; + quat[0] = 0.5f * r; + quat[1] = (mat[0][1] + mat[1][0]) * inv; + quat[2] = (mat[2][0] + mat[0][2]) * inv; + quat[3] = (mat[1][2] - mat[2][1]) * inv; + } + else if( mat[1][1] > mat[2][2] ) + { + float r = sqrt(1.0f + mat[1][1] - mat[0][0] - mat[2][2]), inv = 0.5f / r; + quat[0] = (mat[0][1] + mat[1][0]) * inv; + quat[1] = 0.5f * r; + quat[2] = (mat[1][2] + mat[2][1]) * inv; + quat[3] = (mat[2][0] - mat[0][2]) * inv; + } + else + { + float r = sqrt(1.0f + mat[2][2] - mat[0][0] - mat[1][1]), inv = 0.5f / r; + quat[0] = (mat[2][0] + mat[0][2]) * inv; + quat[1] = (mat[1][2] + mat[2][1]) * inv; + quat[2] = 0.5f * r; + quat[3] = (mat[0][1] - mat[1][0]) * inv; + } + + return quat; +} + +matrix3x4 matrix3x4 :: Invert( void ) const +{ + // we only support uniform scaling, so assume the first row is enough + // (note the lack of sqrt here, because we're trying to undo the scaling, + // this means multiplying by the inverse scale twice - squaring it, which + // makes the sqrt a waste of time) + float scale = 1.0 / (mat[0][0] * mat[0][0] + mat[0][1] * mat[0][1] + mat[0][2] * mat[0][2]); + + matrix3x4 out; + + // invert the rotation by transposing and multiplying by the squared + // recipricol of the input matrix scale as described above + out[0][0] = mat[0][0] * scale; + out[0][1] = mat[1][0] * scale; + out[0][2] = mat[2][0] * scale; + out[1][0] = mat[0][1] * scale; + out[1][1] = mat[1][1] * scale; + out[1][2] = mat[2][1] * scale; + out[2][0] = mat[0][2] * scale; + out[2][1] = mat[1][2] * scale; + out[2][2] = mat[2][2] * scale; + + // invert the translate + out[3][0] = -(mat[3][0] * out[0][0] + mat[3][1] * out[1][0] + mat[3][2] * out[2][0]); + out[3][1] = -(mat[3][0] * out[0][1] + mat[3][1] * out[1][1] + mat[3][2] * out[2][1]); + out[3][2] = -(mat[3][0] * out[0][2] + mat[3][1] * out[1][2] + mat[3][2] * out[2][2]); + + return out; +} + +matrix3x4 matrix3x4 :: ConcatTransforms( const matrix3x4 mat2 ) +{ + matrix3x4 out; + + out[0][0] = mat[0][0] * mat2[0][0] + mat[1][0] * mat2[0][1] + mat[2][0] * mat2[0][2]; + out[1][0] = mat[0][0] * mat2[1][0] + mat[1][0] * mat2[1][1] + mat[2][0] * mat2[1][2]; + out[2][0] = mat[0][0] * mat2[2][0] + mat[1][0] * mat2[2][1] + mat[2][0] * mat2[2][2]; + out[3][0] = mat[0][0] * mat2[3][0] + mat[1][0] * mat2[3][1] + mat[2][0] * mat2[3][2] + mat[3][0]; + out[0][1] = mat[0][1] * mat2[0][0] + mat[1][1] * mat2[0][1] + mat[2][1] * mat2[0][2]; + out[1][1] = mat[0][1] * mat2[1][0] + mat[1][1] * mat2[1][1] + mat[2][1] * mat2[1][2]; + out[2][1] = mat[0][1] * mat2[2][0] + mat[1][1] * mat2[2][1] + mat[2][1] * mat2[2][2]; + out[3][1] = mat[0][1] * mat2[3][0] + mat[1][1] * mat2[3][1] + mat[2][1] * mat2[3][2] + mat[3][1]; + out[0][2] = mat[0][2] * mat2[0][0] + mat[1][2] * mat2[0][1] + mat[2][2] * mat2[0][2]; + out[1][2] = mat[0][2] * mat2[1][0] + mat[1][2] * mat2[1][1] + mat[2][2] * mat2[1][2]; + out[2][2] = mat[0][2] * mat2[2][0] + mat[1][2] * mat2[2][1] + mat[2][2] * mat2[2][2]; + out[3][2] = mat[0][2] * mat2[3][0] + mat[1][2] * mat2[3][1] + mat[2][2] * mat2[3][2] + mat[3][2]; + + return out; +} + +matrix3x4 matrix3x4 :: ConcatTransforms( const matrix3x4 mat2 ) const +{ + matrix3x4 out; + + out[0][0] = mat[0][0] * mat2[0][0] + mat[1][0] * mat2[0][1] + mat[2][0] * mat2[0][2]; + out[1][0] = mat[0][0] * mat2[1][0] + mat[1][0] * mat2[1][1] + mat[2][0] * mat2[1][2]; + out[2][0] = mat[0][0] * mat2[2][0] + mat[1][0] * mat2[2][1] + mat[2][0] * mat2[2][2]; + out[3][0] = mat[0][0] * mat2[3][0] + mat[1][0] * mat2[3][1] + mat[2][0] * mat2[3][2] + mat[3][0]; + out[0][1] = mat[0][1] * mat2[0][0] + mat[1][1] * mat2[0][1] + mat[2][1] * mat2[0][2]; + out[1][1] = mat[0][1] * mat2[1][0] + mat[1][1] * mat2[1][1] + mat[2][1] * mat2[1][2]; + out[2][1] = mat[0][1] * mat2[2][0] + mat[1][1] * mat2[2][1] + mat[2][1] * mat2[2][2]; + out[3][1] = mat[0][1] * mat2[3][0] + mat[1][1] * mat2[3][1] + mat[2][1] * mat2[3][2] + mat[3][1]; + out[0][2] = mat[0][2] * mat2[0][0] + mat[1][2] * mat2[0][1] + mat[2][2] * mat2[0][2]; + out[1][2] = mat[0][2] * mat2[1][0] + mat[1][2] * mat2[1][1] + mat[2][2] * mat2[1][2]; + out[2][2] = mat[0][2] * mat2[2][0] + mat[1][2] * mat2[2][1] + mat[2][2] * mat2[2][2]; + out[3][2] = mat[0][2] * mat2[3][0] + mat[1][2] * mat2[3][1] + mat[2][2] * mat2[3][2] + mat[3][2]; + + return out; +} + +// from class matrix3x3 to class matrix3x4 +matrix3x4& matrix3x4 :: operator=(const matrix3x3 &vOther) +{ + mat[0][0] = vOther[0][0]; + mat[1][0] = vOther[1][0]; + mat[2][0] = vOther[2][0]; + mat[3][0] = 0.0f; + mat[0][1] = vOther[0][1]; + mat[1][1] = vOther[1][1]; + mat[2][1] = vOther[2][1]; + mat[3][1] = 0.0f; + mat[0][2] = vOther[0][2]; + mat[1][2] = vOther[1][2]; + mat[2][2] = vOther[2][2]; + mat[3][2] = 0.0f; + + return *this; +} + +// from class matrix4x4 to class matrix3x4 +matrix3x4& matrix3x4 :: operator=(const matrix4x4 &vOther) +{ + mat[0][0] = vOther[0][0]; + mat[1][0] = vOther[1][0]; + mat[2][0] = vOther[2][0]; + mat[3][0] = vOther[3][0]; + mat[0][1] = vOther[0][1]; + mat[1][1] = vOther[1][1]; + mat[2][1] = vOther[2][1]; + mat[3][1] = vOther[3][1]; + mat[0][2] = vOther[0][2]; + mat[1][2] = vOther[1][2]; + mat[2][2] = vOther[2][2]; + mat[3][2] = vOther[3][2]; + + return *this; +} + +matrix4x4::matrix4x4( void ) +{ +} + +void matrix4x4::Identity( void ) +{ + mat[0] = Vector4D( 1, 0, 0, 0 ); + mat[1] = Vector4D( 0, 1, 0, 0 ); + mat[2] = Vector4D( 0, 0, 1, 0 ); + mat[3] = Vector4D( 0, 0, 0, 1 ); +} + +Vector matrix4x4::VectorTransform( const Vector &v ) const +{ + Vector out; + + out[0] = v[0] * mat[0][0] + v[1] * mat[1][0] + v[2] * mat[2][0] + mat[3][0]; + out[1] = v[0] * mat[0][1] + v[1] * mat[1][1] + v[2] * mat[2][1] + mat[3][1]; + out[2] = v[0] * mat[0][2] + v[1] * mat[1][2] + v[2] * mat[2][2] + mat[3][2]; + + return out; +} + +Vector4D matrix4x4::VectorTransform( const Vector4D &v ) const +{ + Vector4D out; + + out[0] = v[0] * mat[0][0] + v[1] * mat[1][0] + v[2] * mat[2][0] + v[3] * mat[3][0]; + out[1] = v[0] * mat[0][1] + v[1] * mat[1][1] + v[2] * mat[2][1] + v[3] * mat[3][1]; + out[2] = v[0] * mat[0][2] + v[1] * mat[1][2] + v[2] * mat[2][2] + v[3] * mat[3][2]; + out[3] = v[0] * mat[0][3] + v[1] * mat[1][3] + v[2] * mat[2][3] + v[3] * mat[3][3]; + + return out; +} + +Vector matrix4x4::VectorITransform( const Vector &v ) const +{ + Vector iv, out; + + iv[0] = v[0] - mat[3][0]; + iv[1] = v[1] - mat[3][1]; + iv[2] = v[2] - mat[3][2]; + + out[0] = iv[0] * mat[0][0] + iv[1] * mat[0][1] + iv[2] * mat[0][2]; + out[1] = iv[0] * mat[1][0] + iv[1] * mat[1][1] + iv[2] * mat[1][2]; + out[2] = iv[0] * mat[2][0] + iv[1] * mat[2][1] + iv[2] * mat[2][2]; + + return out; +} + +Vector matrix4x4::VectorRotate( const Vector &v ) const +{ + Vector out; + + out[0] = v[0] * mat[0][0] + v[1] * mat[1][0] + v[2] * mat[2][0]; + out[1] = v[0] * mat[0][1] + v[1] * mat[1][1] + v[2] * mat[2][1]; + out[2] = v[0] * mat[0][2] + v[1] * mat[1][2] + v[2] * mat[2][2]; + + return out; +} + +Vector matrix4x4::VectorIRotate( const Vector &v ) const +{ + Vector out; + + out[0] = v[0] * mat[0][0] + v[1] * mat[0][1] + v[2] * mat[0][2]; + out[1] = v[0] * mat[1][0] + v[1] * mat[1][1] + v[2] * mat[1][2]; + out[2] = v[0] * mat[2][0] + v[1] * mat[2][1] + v[2] * mat[2][2]; + + return out; +} + +void matrix4x4::TransformPositivePlane( const mplane_t &in, mplane_t &out ) +{ + float scale = sqrt( mat[0][0] * mat[0][0] + mat[1][0] * mat[1][0] + mat[2][0] * mat[2][0] ); + float iscale = 1.0f / scale; + mplane_t tmp = in; + + tmp.normal.x = (in.normal.x * mat[0][0] + in.normal.y * mat[1][0] + in.normal.z * mat[2][0]) * iscale; + tmp.normal.y = (in.normal.x * mat[0][1] + in.normal.y * mat[1][1] + in.normal.z * mat[2][1]) * iscale; + tmp.normal.z = (in.normal.x * mat[0][2] + in.normal.y * mat[1][2] + in.normal.z * mat[2][2]) * iscale; + tmp.dist = in.dist * scale + ( tmp.normal.x * mat[3][0] + tmp.normal.y * mat[3][1] + tmp.normal.z * mat[3][2] ); + + out = tmp; +} + +void matrix4x4::TransformPositivePlane( const plane_t &in, plane_t &out ) +{ + float scale = sqrt( mat[0][0] * mat[0][0] + mat[1][0] * mat[1][0] + mat[2][0] * mat[2][0] ); + float iscale = 1.0f / scale; + plane_t tmp = in; + + tmp.normal.x = (in.normal.x * mat[0][0] + in.normal.y * mat[1][0] + in.normal.z * mat[2][0]) * iscale; + tmp.normal.y = (in.normal.x * mat[0][1] + in.normal.y * mat[1][1] + in.normal.z * mat[2][1]) * iscale; + tmp.normal.z = (in.normal.x * mat[0][2] + in.normal.y * mat[1][2] + in.normal.z * mat[2][2]) * iscale; + tmp.dist = in.dist * scale + ( tmp.normal.x * mat[3][0] + tmp.normal.y * mat[3][1] + tmp.normal.z * mat[3][2] ); + + out = tmp; +} + +void matrix4x4::TransformStandardPlane( const mplane_t &in, mplane_t &out ) +{ + float scale = sqrt( mat[0][0] * mat[0][0] + mat[1][0] * mat[1][0] + mat[2][0] * mat[2][0] ); + float iscale = 1.0f / scale; + mplane_t tmp = in; + + tmp.normal.x = (in.normal.x * mat[0][0] + in.normal.y * mat[1][0] + in.normal.z * mat[2][0]) * iscale; + tmp.normal.y = (in.normal.x * mat[0][1] + in.normal.y * mat[1][1] + in.normal.z * mat[2][1]) * iscale; + tmp.normal.z = (in.normal.x * mat[0][2] + in.normal.y * mat[1][2] + in.normal.z * mat[2][2]) * iscale; + tmp.dist = in.dist * scale - ( tmp.normal.x * mat[3][0] + tmp.normal.y * mat[3][1] + tmp.normal.z * mat[3][2] ); + + out = tmp; +} + +void matrix4x4::TransformStandardPlane( const plane_t &in, plane_t &out ) +{ + float scale = sqrt( mat[0][0] * mat[0][0] + mat[1][0] * mat[1][0] + mat[2][0] * mat[2][0] ); + float iscale = 1.0f / scale; + plane_t tmp = in; + + tmp.normal.x = (in.normal.x * mat[0][0] + in.normal.y * mat[1][0] + in.normal.z * mat[2][0]) * iscale; + tmp.normal.y = (in.normal.x * mat[0][1] + in.normal.y * mat[1][1] + in.normal.z * mat[2][1]) * iscale; + tmp.normal.z = (in.normal.x * mat[0][2] + in.normal.y * mat[1][2] + in.normal.z * mat[2][2]) * iscale; + tmp.dist = in.dist * scale - ( tmp.normal.x * mat[3][0] + tmp.normal.y * mat[3][1] + tmp.normal.z * mat[3][2] ); + + out = tmp; +} + +Vector4D matrix4x4 :: GetQuaternion( void ) +{ + float trace = mat[0][0] + mat[1][1] + mat[2][2]; + Vector4D quat; + + if( trace > 0.0f ) + { + float r = sqrt(1.0f + trace), inv = 0.5f / r; + quat[0] = (mat[1][2] - mat[2][1]) * inv; + quat[1] = (mat[2][0] - mat[0][2]) * inv; + quat[2] = (mat[0][1] - mat[1][0]) * inv; + quat[3] = 0.5f * r; + } + else if( mat[0][0] > mat[1][1] && mat[0][0] > mat[2][2] ) + { + float r = sqrt(1.0f + mat[0][0] - mat[1][1] - mat[2][2]), inv = 0.5f / r; + quat[0] = 0.5f * r; + quat[1] = (mat[0][1] + mat[1][0]) * inv; + quat[2] = (mat[2][0] + mat[0][2]) * inv; + quat[3] = (mat[1][2] - mat[2][1]) * inv; + } + else if( mat[1][1] > mat[2][2] ) + { + float r = sqrt(1.0f + mat[1][1] - mat[0][0] - mat[2][2]), inv = 0.5f / r; + quat[0] = (mat[0][1] + mat[1][0]) * inv; + quat[1] = 0.5f * r; + quat[2] = (mat[1][2] + mat[2][1]) * inv; + quat[3] = (mat[2][0] - mat[0][2]) * inv; + } + else + { + float r = sqrt(1.0f + mat[2][2] - mat[0][0] - mat[1][1]), inv = 0.5f / r; + quat[0] = (mat[2][0] + mat[0][2]) * inv; + quat[1] = (mat[1][2] + mat[2][1]) * inv; + quat[2] = 0.5f * r; + quat[3] = (mat[0][1] - mat[1][0]) * inv; + } + + return quat; +} + +matrix4x4 matrix4x4 :: Invert( void ) const +{ + // we only support uniform scaling, so assume the first row is enough + // (note the lack of sqrt here, because we're trying to undo the scaling, + // this means multiplying by the inverse scale twice - squaring it, which + // makes the sqrt a waste of time) + float scale = 1.0 / (mat[0][0] * mat[0][0] + mat[0][1] * mat[0][1] + mat[0][2] * mat[0][2]); + + matrix4x4 out; + + // invert the rotation by transposing and multiplying by the squared + // recipricol of the input matrix scale as described above + out[0][0] = mat[0][0] * scale; + out[1][0] = mat[0][1] * scale; + out[2][0] = mat[0][2] * scale; + out[0][1] = mat[1][0] * scale; + out[1][1] = mat[1][1] * scale; + out[2][1] = mat[1][2] * scale; + out[0][2] = mat[2][0] * scale; + out[1][2] = mat[2][1] * scale; + out[2][2] = mat[2][2] * scale; + + // invert the translate + out[3][0] = -(mat[3][0] * out[0][0] + mat[3][1] * out[1][0] + mat[3][2] * out[2][0]); + out[3][1] = -(mat[3][0] * out[0][1] + mat[3][1] * out[1][1] + mat[3][2] * out[2][1]); + out[3][2] = -(mat[3][0] * out[0][2] + mat[3][1] * out[1][2] + mat[3][2] * out[2][2]); + + // don't know if there's anything worth doing here + out[0][3] = 0.0f; + out[1][3] = 0.0f; + out[2][3] = 0.0f; + out[3][3] = 1.0f; + + return out; +} + +matrix4x4 matrix4x4 :: InvertFull( void ) const +{ + float *temp, *r[4]; + float rtemp[4][8]; + float s, m[4]; + matrix4x4 out; + + r[0] = rtemp[0]; + r[1] = rtemp[1]; + r[2] = rtemp[2]; + r[3] = rtemp[3]; + + r[0][0] = mat[0][0]; + r[0][1] = mat[1][0]; + r[0][2] = mat[2][0]; + r[0][3] = mat[3][0]; + r[0][4] = 1.0; + r[0][5] = 0.0; + r[0][6] = 0.0; + r[0][7] = 0.0; + + r[1][0] = mat[0][1]; + r[1][1] = mat[1][1]; + r[1][2] = mat[2][1]; + r[1][3] = mat[3][1]; + r[1][5] = 1.0; + r[1][4] = 0.0; + r[1][6] = 0.0; + r[1][7] = 0.0; + + r[2][0] = mat[0][2]; + r[2][1] = mat[1][2]; + r[2][2] = mat[2][2]; + r[2][3] = mat[3][2]; + r[2][6] = 1.0; + r[2][4] = 0.0; + r[2][5] = 0.0; + r[2][7] = 0.0; + + r[3][0] = mat[0][3]; + r[3][1] = mat[1][3]; + r[3][2] = mat[2][3]; + r[3][3] = mat[3][3]; + r[3][4] = 0.0; + r[3][5] = 0.0; + r[3][6] = 0.0; + r[3][7] = 1.0; + + if( fabs( r[3][0] ) > fabs( r[2][0] )) + { + temp = r[3]; + r[3] = r[2]; + r[2] = temp; + } + + if( fabs( r[2][0] ) > fabs( r[1][0] )) + { + temp = r[2]; + r[2] = r[1]; + r[1] = temp; + } + + if( fabs( r[1][0] ) > fabs( r[0][0] )) + { + temp = r[1]; + r[1] = r[0]; + r[0] = temp; + } + + if( r[0][0] ) + { + m[1] = r[1][0] / r[0][0]; + m[2] = r[2][0] / r[0][0]; + m[3] = r[3][0] / r[0][0]; + + s = r[0][1]; + r[1][1] -= m[1] * s; + r[2][1] -= m[2] * s; + r[3][1] -= m[3] * s; + + s = r[0][2]; + r[1][2] -= m[1] * s; + r[2][2] -= m[2] * s; + r[3][2] -= m[3] * s; + + s = r[0][3]; + r[1][3] -= m[1] * s; + r[2][3] -= m[2] * s; + r[3][3] -= m[3] * s; + + s = r[0][4]; + if( s ) + { + r[1][4] -= m[1] * s; + r[2][4] -= m[2] * s; + r[3][4] -= m[3] * s; + } + + s = r[0][5]; + if( s ) + { + r[1][5] -= m[1] * s; + r[2][5] -= m[2] * s; + r[3][5] -= m[3] * s; + } + + s = r[0][6]; + if( s ) + { + r[1][6] -= m[1] * s; + r[2][6] -= m[2] * s; + r[3][6] -= m[3] * s; + } + + s = r[0][7]; + if( s ) + { + r[1][7] -= m[1] * s; + r[2][7] -= m[2] * s; + r[3][7] -= m[3] * s; + } + + if( fabs( r[3][1] ) > fabs( r[2][1] )) + { + temp = r[3]; + r[3] = r[2]; + r[2] = temp; + } + if( fabs( r[2][1] ) > fabs( r[1][1] )) + { + temp = r[2]; + r[2] = r[1]; + r[1] = temp; + } + + if( r[1][1] ) + { + m[2] = r[2][1] / r[1][1]; + m[3] = r[3][1] / r[1][1]; + r[2][2] -= m[2] * r[1][2]; + r[3][2] -= m[3] * r[1][2]; + r[2][3] -= m[2] * r[1][3]; + r[3][3] -= m[3] * r[1][3]; + + s = r[1][4]; + if( s ) + { + r[2][4] -= m[2] * s; + r[3][4] -= m[3] * s; + } + + s = r[1][5]; + if( s ) + { + r[2][5] -= m[2] * s; + r[3][5] -= m[3] * s; + } + + s = r[1][6]; + if( s ) + { + r[2][6] -= m[2] * s; + r[3][6] -= m[3] * s; + } + + s = r[1][7]; + if( s ) + { + r[2][7] -= m[2] * s; + r[3][7] -= m[3] * s; + } + + if( fabs( r[3][2] ) > fabs( r[2][2] )) + { + temp = r[3]; + r[3] = r[2]; + r[2] = temp; + } + + if( r[2][2] ) + { + m[3] = r[3][2] / r[2][2]; + r[3][3] -= m[3] * r[2][3]; + r[3][4] -= m[3] * r[2][4]; + r[3][5] -= m[3] * r[2][5]; + r[3][6] -= m[3] * r[2][6]; + r[3][7] -= m[3] * r[2][7]; + + if( r[3][3] ) + { + s = 1.0 / r[3][3]; + r[3][4] *= s; + r[3][5] *= s; + r[3][6] *= s; + r[3][7] *= s; + + m[2] = r[2][3]; + s = 1.0 / r[2][2]; + r[2][4] = s * (r[2][4] - r[3][4] * m[2]); + r[2][5] = s * (r[2][5] - r[3][5] * m[2]); + r[2][6] = s * (r[2][6] - r[3][6] * m[2]); + r[2][7] = s * (r[2][7] - r[3][7] * m[2]); + + m[1] = r[1][3]; + r[1][4] -= r[3][4] * m[1]; + r[1][5] -= r[3][5] * m[1]; + r[1][6] -= r[3][6] * m[1]; + r[1][7] -= r[3][7] * m[1]; + + m[0] = r[0][3]; + r[0][4] -= r[3][4] * m[0]; + r[0][5] -= r[3][5] * m[0]; + r[0][6] -= r[3][6] * m[0]; + r[0][7] -= r[3][7] * m[0]; + + m[1] = r[1][2]; + s = 1.0 / r[1][1]; + r[1][4] = s * (r[1][4] - r[2][4] * m[1]); + r[1][5] = s * (r[1][5] - r[2][5] * m[1]); + r[1][6] = s * (r[1][6] - r[2][6] * m[1]); + r[1][7] = s * (r[1][7] - r[2][7] * m[1]); + + m[0] = r[0][2]; + r[0][4] -= r[2][4] * m[0]; + r[0][5] -= r[2][5] * m[0]; + r[0][6] -= r[2][6] * m[0]; + r[0][7] -= r[2][7] * m[0]; + + m[0] = r[0][1]; + s = 1.0 / r[0][0]; + r[0][4] = s * (r[0][4] - r[1][4] * m[0]); + r[0][5] = s * (r[0][5] - r[1][5] * m[0]); + r[0][6] = s * (r[0][6] - r[1][6] * m[0]); + r[0][7] = s * (r[0][7] - r[1][7] * m[0]); + + out[0][0] = r[0][4]; + out[0][1] = r[1][4]; + out[0][2] = r[2][4]; + out[0][3] = r[3][4]; + out[1][0] = r[0][5]; + out[1][1] = r[1][5]; + out[1][2] = r[2][5]; + out[1][3] = r[3][5]; + out[2][0] = r[0][6]; + out[2][1] = r[1][6]; + out[2][2] = r[2][6]; + out[2][3] = r[3][6]; + out[3][0] = r[0][7]; + out[3][1] = r[1][7]; + out[3][2] = r[2][7]; + out[3][3] = r[3][7]; + + return out; + } + } + } + } + + // failed + return *this; +} + +matrix4x4 matrix4x4 :: ConcatTransforms( const matrix4x4 mat2 ) +{ + matrix4x4 out; + + out[0][0] = mat[0][0] * mat2[0][0] + mat[1][0] * mat2[0][1] + mat[2][0] * mat2[0][2]; + out[1][0] = mat[0][0] * mat2[1][0] + mat[1][0] * mat2[1][1] + mat[2][0] * mat2[1][2]; + out[2][0] = mat[0][0] * mat2[2][0] + mat[1][0] * mat2[2][1] + mat[2][0] * mat2[2][2]; + out[3][0] = mat[0][0] * mat2[3][0] + mat[1][0] * mat2[3][1] + mat[2][0] * mat2[3][2] + mat[3][0]; + out[0][1] = mat[0][1] * mat2[0][0] + mat[1][1] * mat2[0][1] + mat[2][1] * mat2[0][2]; + out[1][1] = mat[0][1] * mat2[1][0] + mat[1][1] * mat2[1][1] + mat[2][1] * mat2[1][2]; + out[2][1] = mat[0][1] * mat2[2][0] + mat[1][1] * mat2[2][1] + mat[2][1] * mat2[2][2]; + out[3][1] = mat[0][1] * mat2[3][0] + mat[1][1] * mat2[3][1] + mat[2][1] * mat2[3][2] + mat[3][1]; + out[0][2] = mat[0][2] * mat2[0][0] + mat[1][2] * mat2[0][1] + mat[2][2] * mat2[0][2]; + out[1][2] = mat[0][2] * mat2[1][0] + mat[1][2] * mat2[1][1] + mat[2][2] * mat2[1][2]; + out[2][2] = mat[0][2] * mat2[2][0] + mat[1][2] * mat2[2][1] + mat[2][2] * mat2[2][2]; + out[3][2] = mat[0][2] * mat2[3][0] + mat[1][2] * mat2[3][1] + mat[2][2] * mat2[3][2] + mat[3][2]; + + // not used for concat transforms + out[0][3] = 0.0f; + out[1][3] = 0.0f; + out[2][3] = 0.0f; + out[3][3] = 1.0f; + + return out; +} + +matrix4x4 matrix4x4 :: Concat( const matrix4x4 mat2 ) +{ + matrix4x4 out; + + out[0][0] = mat[0][0] * mat2[0][0] + mat[1][0] * mat2[0][1] + mat[2][0] * mat2[0][2] + mat[3][0] * mat2[0][3]; + out[1][0] = mat[0][0] * mat2[1][0] + mat[1][0] * mat2[1][1] + mat[2][0] * mat2[1][2] + mat[3][0] * mat2[1][3]; + out[2][0] = mat[0][0] * mat2[2][0] + mat[1][0] * mat2[2][1] + mat[2][0] * mat2[2][2] + mat[3][0] * mat2[2][3]; + out[3][0] = mat[0][0] * mat2[3][0] + mat[1][0] * mat2[3][1] + mat[2][0] * mat2[3][2] + mat[3][0] * mat2[3][3]; + out[0][1] = mat[0][1] * mat2[0][0] + mat[1][1] * mat2[0][1] + mat[2][1] * mat2[0][2] + mat[3][1] * mat2[0][3]; + out[1][1] = mat[0][1] * mat2[1][0] + mat[1][1] * mat2[1][1] + mat[2][1] * mat2[1][2] + mat[3][1] * mat2[1][3]; + out[2][1] = mat[0][1] * mat2[2][0] + mat[1][1] * mat2[2][1] + mat[2][1] * mat2[2][2] + mat[3][1] * mat2[2][3]; + out[3][1] = mat[0][1] * mat2[3][0] + mat[1][1] * mat2[3][1] + mat[2][1] * mat2[3][2] + mat[3][1] * mat2[3][3]; + out[0][2] = mat[0][2] * mat2[0][0] + mat[1][2] * mat2[0][1] + mat[2][2] * mat2[0][2] + mat[3][2] * mat2[0][3]; + out[1][2] = mat[0][2] * mat2[1][0] + mat[1][2] * mat2[1][1] + mat[2][2] * mat2[1][2] + mat[3][2] * mat2[1][3]; + out[2][2] = mat[0][2] * mat2[2][0] + mat[1][2] * mat2[2][1] + mat[2][2] * mat2[2][2] + mat[3][2] * mat2[2][3]; + out[3][2] = mat[0][2] * mat2[3][0] + mat[1][2] * mat2[3][1] + mat[2][2] * mat2[3][2] + mat[3][2] * mat2[3][3]; + out[0][3] = mat[0][3] * mat2[0][0] + mat[1][3] * mat2[0][1] + mat[2][3] * mat2[0][2] + mat[3][3] * mat2[0][3]; + out[1][3] = mat[0][3] * mat2[1][0] + mat[1][3] * mat2[1][1] + mat[2][3] * mat2[1][2] + mat[3][3] * mat2[1][3]; + out[2][3] = mat[0][3] * mat2[2][0] + mat[1][3] * mat2[2][1] + mat[2][3] * mat2[2][2] + mat[3][3] * mat2[2][3]; + out[3][3] = mat[0][3] * mat2[3][0] + mat[1][3] * mat2[3][1] + mat[2][3] * mat2[3][2] + mat[3][3] * mat2[3][3]; + + return out; +} + +// from class matrix3x3 to class matrix4x4 +matrix4x4& matrix4x4 :: operator=(const matrix3x3 &vOther) +{ + mat[0][0] = vOther[0][0]; + mat[1][0] = vOther[1][0]; + mat[2][0] = vOther[2][0]; + mat[3][0] = 0.0f; + mat[0][1] = vOther[0][1]; + mat[1][1] = vOther[1][1]; + mat[2][1] = vOther[2][1]; + mat[3][1] = 0.0f; + mat[0][2] = vOther[0][2]; + mat[1][2] = vOther[1][2]; + mat[2][2] = vOther[2][2]; + mat[3][2] = 0.0f; + mat[0][3] = 0.0f; + mat[1][3] = 0.0f; + mat[2][3] = 0.0f; + mat[3][3] = 1.0f; + + return *this; +} + +// from class matrix3x4 to class matrix4x4 +matrix4x4& matrix4x4 :: operator=(const matrix3x4 &vOther) +{ + mat[0][0] = vOther[0][0]; + mat[1][0] = vOther[1][0]; + mat[2][0] = vOther[2][0]; + mat[3][0] = vOther[3][0]; + mat[0][1] = vOther[0][1]; + mat[1][1] = vOther[1][1]; + mat[2][1] = vOther[2][1]; + mat[3][1] = vOther[3][1]; + mat[0][2] = vOther[0][2]; + mat[1][2] = vOther[1][2]; + mat[2][2] = vOther[2][2]; + mat[3][2] = vOther[3][2]; + mat[0][3] = 0.0f; + mat[1][3] = 0.0f; + mat[2][3] = 0.0f; + mat[3][3] = 1.0f; + + return *this; +} + +// from class matrix3x4 to class matrix4x4 +matrix4x4& matrix4x4 :: operator=(const matrix4x4 &vOther) +{ + mat[0][0] = vOther[0][0]; + mat[1][0] = vOther[1][0]; + mat[2][0] = vOther[2][0]; + mat[3][0] = vOther[3][0]; + mat[0][1] = vOther[0][1]; + mat[1][1] = vOther[1][1]; + mat[2][1] = vOther[2][1]; + mat[3][1] = vOther[3][1]; + mat[0][2] = vOther[0][2]; + mat[1][2] = vOther[1][2]; + mat[2][2] = vOther[2][2]; + mat[3][2] = vOther[3][2]; + mat[0][3] = vOther[0][3]; + mat[1][3] = vOther[1][3]; + mat[2][3] = vOther[2][3]; + mat[3][3] = vOther[3][3]; + + return *this; +} + +void matrix4x4 :: CreateProjection( float xMax, float xMin, float yMax, float yMin, float zNear, float zFar ) +{ + mat[0][0] = ( 2.0f * zNear ) / ( xMax - xMin ); + mat[1][1] = ( 2.0f * zNear ) / ( yMax - yMin ); + mat[2][2] = -( zFar + zNear ) / ( zFar - zNear ); + mat[3][3] = mat[0][1] = mat[1][0] = mat[3][0] = mat[0][3] = mat[3][1] = mat[1][3] = 0.0f; + + mat[0][2] = 0.0f; + mat[1][2] = 0.0f; + mat[2][0] = ( xMax + xMin ) / ( xMax - xMin ); + mat[2][1] = ( yMax + yMin ) / ( yMax - yMin ); + mat[2][3] = -1.0f; + mat[3][2] = -( 2.0f * zFar * zNear ) / ( zFar - zNear ); +} + +void matrix4x4 :: CreateProjection( float fov_x, float fov_y, float zNear, float zFar ) +{ + mat[0][0] = 1.0f / tan( fov_x * M_PI / 360.0f ); + mat[1][1] = 1.0f / tan( fov_y * M_PI / 360.0f ); + mat[2][2] = -( zFar + zNear ) / ( zFar - zNear ); + mat[3][2] = -( 2.0 * zFar * zNear ) / ( zFar - zNear ); + mat[2][3] = -1.0f; + + mat[0][1] = mat[1][0] = mat[3][0] = mat[0][3] = mat[3][1] = mat[1][3] = 0.0f; + mat[0][2] = mat[2][0] = mat[2][1] = mat[1][2] = mat[3][3] = 0.0f; +} + +void matrix4x4 :: CreateOrtho( float xLeft, float xRight, float yBottom, float yTop, float zNear, float zFar ) +{ + mat[0][0] = 2.0f / (xRight - xLeft); + mat[1][1] = 2.0f / (yTop - yBottom); + mat[2][2] = -2.0f / (zFar - zNear); + mat[3][3] = 1.0f; + mat[0][1] = mat[1][0] = mat[2][0] = mat[2][1] = mat[0][3] = mat[1][3] = mat[2][3] = 0.0f; + + mat[0][2] = 0.0f; + mat[1][2] = 0.0f; + mat[3][0] = -(xRight + xLeft) / (xRight - xLeft); + mat[3][1] = -(yTop + yBottom) / (yTop - yBottom); + mat[3][2] = -(zFar + zNear) / (zFar - zNear); +} + +void matrix4x4 :: CreateOrthoRH( float xLeft, float xRight, float yBottom, float yTop, float zNear, float zFar ) +{ + mat[0][0] = 2.0f / (xRight - xLeft); + mat[1][1] = 2.0f / (yTop - yBottom); + mat[2][2] = 1.0f / (zNear - zFar); + mat[3][3] = 1.0f; + mat[0][1] = mat[1][0] = mat[2][0] = mat[2][1] = mat[0][3] = mat[1][3] = mat[2][3] = 0.0f; + + mat[0][2] = 0.0f; + mat[1][2] = 0.0f; + mat[3][0] = (xLeft + xRight) / (xLeft - xRight); + mat[3][1] = (yTop + yBottom) / (yBottom - yTop); + mat[3][2] = zNear / (zNear - zFar); +} + +void matrix4x4 :: LookAt( const Vector &eye, const Vector &dir, const Vector &up ) +{ + Vector sideN = CrossProduct( dir, up ).Normalize(); + Vector upN = CrossProduct( sideN, dir ).Normalize(); + Vector dirN = dir.Normalize(); + + mat[0][0] = sideN[0]; + mat[1][0] = sideN[1]; + mat[2][0] = sideN[2]; + mat[3][0] = -DotProduct( sideN, eye ); + mat[0][1] = upN[0]; + mat[1][1] = upN[1]; + mat[2][1] = upN[2]; + mat[3][1] = -DotProduct( upN, eye ); + mat[0][2] = -dirN[0]; + mat[1][2] = -dirN[1]; + mat[2][2] = -dirN[2]; + mat[3][2] = DotProduct( dirN, eye ); + + mat[0][3] = mat[1][3] = mat[2][3] = 0.0f; + mat[3][3] = 1.0f; +} + +void matrix4x4 :: Crop( const Vector &mins, const Vector &maxs ) +{ + float scaleX = 2.0f / ( maxs.x - mins.x ); + float scaleY = 2.0f / ( maxs.y - mins.y ); + + float offsetX = -0.5f * ( maxs.x + mins.x ) * scaleX; + float offsetY = -0.5f * ( maxs.y + mins.y ) * scaleY; + + float scaleZ = 1.0f / ( maxs.z - mins.z ); + float offsetZ = -mins.z * scaleZ; + + mat[0][1] = mat[0][2] = mat[0][3] = 0.0f; + mat[1][0] = mat[1][2] = mat[1][3] = 0.0f; + mat[2][0] = mat[2][1] = mat[2][3] = 0.0f; + mat[3][3] = 1.0f; + + mat[0][0] = scaleX; + mat[1][1] = scaleY; + mat[2][2] = scaleZ; + + mat[3][0] = offsetX; + mat[3][1] = offsetY; + mat[3][2] = offsetZ; +} + +void matrix4x4::CreateModelview( void ) +{ + mat[0][0] = mat[1][1] = mat[2][2] = 0.0f; + mat[3][0] = mat[0][3] = 0.0f; + mat[3][1] = mat[1][3] = 0.0f; + mat[3][2] = mat[2][3] = 0.0f; + mat[3][3] = 1.0f; + mat[0][1] = mat[2][0] = mat[1][2] = 0.0f; + mat[0][2] = mat[1][0] = -1.0f; + mat[2][1] = 1.0f; +} + +void matrix4x4::CreateTexture( void ) +{ + mat[0][0] = 0.5f; + mat[1][0] = 0.0f; + mat[2][0] = 0.0f; + mat[3][0] = 0.5f; + mat[0][1] = 0.0f; + mat[1][1] = 0.5f; + mat[2][1] = 0.0f; + mat[3][1] = 0.5f; + mat[0][2] = 0.0f; + mat[1][2] = 0.0f; + mat[2][2] = 0.5f; + mat[3][2] = 0.5f; + mat[0][3] = 0.0f; + mat[1][3] = 0.0f; + mat[2][3] = 0.0f; + mat[3][3] = 1.0f; +} + +void matrix4x4::CreateTranslate( float x, float y, float z ) +{ + mat[0][0] = 1.0f; + mat[1][0] = 0.0f; + mat[2][0] = 0.0f; + mat[3][0] = x; + mat[0][1] = 0.0f; + mat[1][1] = 1.0f; + mat[2][1] = 0.0f; + mat[3][1] = y; + mat[0][2] = 0.0f; + mat[1][2] = 0.0f; + mat[2][2] = 1.0f; + mat[3][2] = z; + mat[0][3] = 0.0f; + mat[1][3] = 0.0f; + mat[2][3] = 0.0f; + mat[3][3] = 1.0f; +} + +void matrix4x4::CreateRotate( float angle, float x, float y, float z ) +{ + float len, c, s; + + len = x * x + y * y + z * z; + if( len != 0.0f ) len = 1.0f / sqrt( len ); + + x *= len; + y *= len; + z *= len; + + angle *= (float)(-M_PI / 180.0f); + SinCos( angle, &s, &c ); + + mat[0][0] = x * x + c * (1 - x * x); + mat[1][0] = x * y * (1 - c) + z * s; + mat[2][0] = z * x * (1 - c) - y * s; + mat[3][0] = 0.0f; + mat[0][1] = x * y * (1 - c) - z * s; + mat[1][1] = y * y + c * (1 - y * y); + mat[2][1] = y * z * (1 - c) + x * s; + mat[3][1] = 0.0f; + mat[0][2] = z * x * (1 - c) + y * s; + mat[1][2] = y * z * (1 - c) - x * s; + mat[2][2] = z * z + c * (1 - z * z); + mat[3][2] = 0.0f; + mat[0][3] = 0.0f; + mat[1][3] = 0.0f; + mat[2][3] = 0.0f; + mat[3][3] = 1.0f; +} + +void matrix4x4::CreateScale( float scale ) +{ + mat[0][0] = scale; + mat[1][0] = 0.0f; + mat[2][0] = 0.0f; + mat[3][0] = 0.0f; + mat[0][1] = 0.0f; + mat[1][1] = scale; + mat[2][1] = 0.0f; + mat[3][1] = 0.0f; + mat[0][2] = 0.0f; + mat[1][2] = 0.0f; + mat[2][2] = scale; + mat[3][2] = 0.0f; + mat[0][3] = 0.0f; + mat[1][3] = 0.0f; + mat[2][3] = 0.0f; + mat[3][3] = 1.0f; +} + +void matrix4x4::CreateScale( float x, float y, float z ) +{ + mat[0][0] = x; + mat[1][0] = 0.0f; + mat[2][0] = 0.0f; + mat[3][0] = 0.0f; + mat[0][1] = 0.0f; + mat[1][1] = y; + mat[2][1] = 0.0f; + mat[3][1] = 0.0f; + mat[0][2] = 0.0f; + mat[1][2] = 0.0f; + mat[2][2] = z; + mat[3][2] = 0.0f; + mat[0][3] = 0.0f; + mat[1][3] = 0.0f; + mat[2][3] = 0.0f; + mat[3][3] = 1.0f; +} \ No newline at end of file diff --git a/game_shared/matrix.h b/game_shared/matrix.h new file mode 100644 index 0000000..fc12344 --- /dev/null +++ b/game_shared/matrix.h @@ -0,0 +1,1117 @@ +//======================================================================= +// Copyright (C) XashXT Group 2011 +//======================================================================= + +#ifndef MATRIX_H +#define MATRIX_H + +#include + +#ifndef M_PI +#define M_PI 3.14159265358979323846 // matches value in gcc v2 math.h +#endif + +class matrix3x4; +class matrix4x4; +struct mplane_s; + +class matrix3x3 +{ +public: + matrix3x3(); + matrix3x3( float m00, float m01, float m02, float m10, float m11, float m12, float m20, float m21, float m22 ) + { + mat[0][0] = m00; + mat[0][1] = m01; + mat[0][2] = m02; + mat[1][0] = m10; + mat[1][1] = m11; + mat[1][2] = m12; + mat[2][0] = m20; + mat[2][1] = m21; + mat[2][2] = m22; + } + + // init from quaternion + _forceinline matrix3x3( const Vector4D &quaternion ) + { + mat[0][0] = 1.0f - 2.0f * (quaternion.y * quaternion.y + quaternion.z * quaternion.z); + mat[1][0] = 2.0f * (quaternion.x * quaternion.y - quaternion.z * quaternion.w); + mat[2][0] = 2.0f * (quaternion.x * quaternion.z + quaternion.y * quaternion.w); + mat[0][1] = 2.0f * (quaternion.x * quaternion.y + quaternion.z * quaternion.w); + mat[1][1] = 1.0f - 2.0f * (quaternion.x * quaternion.x + quaternion.z * quaternion.z); + mat[2][1] = 2.0f * (quaternion.y * quaternion.z - quaternion.x * quaternion.w); + mat[0][2] = 2.0f * (quaternion.x * quaternion.z - quaternion.y * quaternion.w); + mat[1][2] = 2.0f * (quaternion.y * quaternion.z + quaternion.x * quaternion.w); + mat[2][2] = 1.0f - 2.0f * (quaternion.x * quaternion.x + quaternion.y * quaternion.y); + } + + // init from angles + _forceinline matrix3x3( const Vector &angles ) + { + float angle, sr, sp, sy, cr, cp, cy; + + if( angles[ROLL] ) + { + angle = angles[YAW] * (M_PI*2 / 360); + SinCos( angle, &sy, &cy ); + angle = angles[PITCH] * (M_PI*2 / 360); + SinCos( angle, &sp, &cp ); + angle = angles[ROLL] * (M_PI*2 / 360); + SinCos( angle, &sr, &cr ); + + mat[0][0] = (cp*cy); + mat[1][0] = (sr*sp*cy+cr*-sy); + mat[2][0] = (cr*sp*cy+-sr*-sy); + mat[0][1] = (cp*sy); + mat[1][1] = (sr*sp*sy+cr*cy); + mat[2][1] = (cr*sp*sy+-sr*cy); + mat[0][2] = (-sp); + mat[1][2] = (sr*cp); + mat[2][2] = (cr*cp); + } + else if( angles[PITCH] ) + { + angle = angles[YAW] * (M_PI*2 / 360); + SinCos( angle, &sy, &cy ); + angle = angles[PITCH] * (M_PI*2 / 360); + SinCos( angle, &sp, &cp ); + + mat[0][0] = (cp*cy); + mat[1][0] = (-sy); + mat[2][0] = (sp*cy); + mat[0][1] = (cp*sy); + mat[1][1] = (cy); + mat[2][1] = (sp*sy); + mat[0][2] = (-sp); + mat[1][2] = 0; + mat[2][2] = (cp); + } + else if( angles[YAW] ) + { + angle = angles[YAW] * (M_PI*2 / 360); + SinCos( angle, &sy, &cy ); + + mat[0][0] = (cy); + mat[1][0] = (-sy); + mat[2][0] = 0; + mat[0][1] = (sy); + mat[1][1] = (cy); + mat[2][1] = 0; + mat[0][2] = 0; + mat[1][2] = 0; + mat[2][2] = 1; + } + else + { + mat[0][0] = 1; + mat[1][0] = 0; + mat[2][0] = 0; + mat[0][1] = 0; + mat[1][1] = 1; + mat[2][1] = 0; + mat[0][2] = 0; + mat[1][2] = 0; + mat[2][2] = 1; + } + } + + void Identity(); + + // array access + float* operator[]( int i ) { return mat[i]; } + float const* operator[]( int i ) const { return mat[i]; } + operator float *() { return (float *)&mat[0][0]; } + operator const float *() const { return (float *)&mat[0][0]; } + + _forceinline int operator == ( const matrix3x3& mat2 ) const + { + if( mat[0][0] != mat2[0][0] || mat[0][1] != mat2[0][1] || mat[0][2] != mat2[0][2] ) + return false; + if( mat[1][0] != mat2[1][0] || mat[1][1] != mat2[1][1] || mat[1][2] != mat2[1][2] ) + return false; + if( mat[2][0] != mat2[2][0] || mat[2][1] != mat2[2][1] || mat[2][2] != mat2[2][2] ) + return false; + return true; + } + + _forceinline int operator != ( const matrix3x3& mat2 ) const { return !(*this == mat2 ); } + matrix3x3& operator=(const matrix3x4 &vOther); + matrix3x3& operator=(const matrix4x4 &vOther); + + // Access the basis vectors. + Vector GetForward() const { return mat[0]; }; + Vector GetRight() const { return mat[1]; }; + Vector GetUp() const { return mat[2]; }; + Vector GetRow( int i ) const { return mat[i]; } + + void SetForward( const Vector &vForward ) { mat[0] = vForward; }; + void SetRight( const Vector &vRight ) { mat[1] = vRight; }; + void SetUp( const Vector &vUp ) { mat[2] = vUp; }; + + void FromVector( const Vector &forward ); + + Vector GetAngles( void ) + { + float xyDist = sqrt( mat[0][0] * mat[0][0] + mat[0][1] * mat[0][1] ); + Vector angles; + + // enough here to get angles? + if( xyDist > 0.001f ) + { + angles[0] = RAD2DEG( atan2( -mat[0][2], xyDist ) ); + angles[1] = RAD2DEG( atan2( mat[0][1], mat[0][0] ) ); + angles[2] = RAD2DEG( atan2( mat[1][2], mat[2][2] ) ); + } + else // forward is mostly Z, gimbal lock + { + angles[0] = RAD2DEG( atan2( -mat[0][2], xyDist ) ); + angles[1] = RAD2DEG( atan2( -mat[1][0], mat[1][1] ) ); + angles[2] = 0; + } + + return angles; + } + + void GetAngles( Vector &angles ) { angles = GetAngles(); } + + void GetAngles( Radian &angles ) + { + float xyDist = sqrt( mat[0][0] * mat[0][0] + mat[0][1] * mat[0][1] ); + + // enough here to get angles? + if( xyDist > 0.001f ) + { + angles.y = atan2( -mat[0][2], xyDist ); + angles.z = atan2( mat[0][1], mat[0][0] ); + angles.x = atan2( mat[1][2], mat[2][2] ); + } + else // forward is mostly Z, gimbal lock + { + angles.y = atan2( -mat[0][2], xyDist ); + angles.z = atan2( -mat[1][0], mat[1][1] ); + angles.x = 0.0f; + } + } + + Vector4D GetQuaternion( void ); + void GetQuaternion( Vector4D &quat ) { quat = GetQuaternion(); } + + // Transpose. + matrix3x3 Transpose() const + { + return matrix3x3( + mat[0][0], mat[1][0], mat[2][0], + mat[0][1], mat[1][1], mat[2][1], + mat[0][2], mat[1][2], mat[2][2] ); + } + + Vector matrix3x3::VectorRotate( const Vector &v ) const; + Vector matrix3x3::VectorIRotate( const Vector &v ) const; + + // copy as OpenGl matrix + inline void CopyToArray( float *rgfl ) const + { + rgfl[ 0] = mat[0][0]; + rgfl[ 1] = mat[0][1]; + rgfl[ 2] = mat[0][2]; + rgfl[ 3] = 0.0f; + rgfl[ 4] = mat[1][0]; + rgfl[ 5] = mat[1][1]; + rgfl[ 6] = mat[1][2]; + rgfl[ 7] = 0.0f; + rgfl[ 8] = mat[2][0]; + rgfl[ 9] = mat[2][1]; + rgfl[10] = mat[2][2]; + rgfl[11] = 0.0f; + rgfl[12] = 0.0f; + rgfl[13] = 0.0f; + rgfl[14] = 0.0f; + rgfl[15] = 1.0f; + } + + matrix3x3 Concat( const matrix3x3 mat2 ); + + Vector mat[3]; +}; + +class matrix3x4 +{ +public: + matrix3x4(); + matrix3x4( float m00, float m01, float m02, + float m10, float m11, float m12, + float m20, float m21, float m22, + float m30, float m31, float m32 ) + { + mat[0][0] = m00; + mat[0][1] = m01; + mat[0][2] = m02; + mat[1][0] = m10; + mat[1][1] = m11; + mat[1][2] = m12; + mat[2][0] = m20; + mat[2][1] = m21; + mat[2][2] = m22; + mat[3][0] = m30; + mat[3][1] = m31; + mat[3][2] = m32; + } + + // init from entity + _forceinline matrix3x4( const Vector &origin, const Vector &angles, float scale = 1.0f ) + { + float angle, sr, sp, sy, cr, cp, cy; + + if( angles[ROLL] ) + { + angle = angles[YAW] * (M_PI*2 / 360); + SinCos( angle, &sy, &cy ); + angle = angles[PITCH] * (M_PI*2 / 360); + SinCos( angle, &sp, &cp ); + angle = angles[ROLL] * (M_PI*2 / 360); + SinCos( angle, &sr, &cr ); + + mat[0][0] = (cp*cy) * scale; + mat[1][0] = (sr*sp*cy+cr*-sy) * scale; + mat[2][0] = (cr*sp*cy+-sr*-sy) * scale; + mat[3][0] = origin.x; + mat[0][1] = (cp*sy) * scale; + mat[1][1] = (sr*sp*sy+cr*cy) * scale; + mat[2][1] = (cr*sp*sy+-sr*cy) * scale; + mat[3][1] = origin.y; + mat[0][2] = (-sp) * scale; + mat[1][2] = (sr*cp) * scale; + mat[2][2] = (cr*cp) * scale; + mat[3][2] = origin.z; + } + else if( angles[PITCH] ) + { + angle = angles[YAW] * (M_PI*2 / 360); + SinCos( angle, &sy, &cy ); + angle = angles[PITCH] * (M_PI*2 / 360); + SinCos( angle, &sp, &cp ); + + mat[0][0] = (cp*cy) * scale; + mat[1][0] = (-sy) * scale; + mat[2][0] = (sp*cy) * scale; + mat[3][0] = origin.x; + mat[0][1] = (cp*sy) * scale; + mat[1][1] = (cy) * scale; + mat[2][1] = (sp*sy) * scale; + mat[3][1] = origin.y; + mat[0][2] = (-sp) * scale; + mat[1][2] = 0; + mat[2][2] = (cp) * scale; + mat[3][2] = origin.z; + } + else if( angles[YAW] ) + { + angle = angles[YAW] * (M_PI*2 / 360); + SinCos( angle, &sy, &cy ); + + mat[0][0] = (cy) * scale; + mat[1][0] = (-sy) * scale; + mat[2][0] = 0; + mat[3][0] = origin.x; + mat[0][1] = (sy) * scale; + mat[1][1] = (cy) * scale; + mat[2][1] = 0; + mat[3][1] = origin.y; + mat[0][2] = 0; + mat[1][2] = 0; + mat[2][2] = scale; + mat[3][2] = origin.z; + } + else + { + mat[0][0] = scale; + mat[1][0] = 0; + mat[2][0] = 0; + mat[3][0] = origin.x; + mat[0][1] = 0; + mat[1][1] = scale; + mat[2][1] = 0; + mat[3][1] = origin.y; + mat[0][2] = 0; + mat[1][2] = 0; + mat[2][2] = scale; + mat[3][2] = origin.z; + } + } + + // init from entity + _forceinline matrix3x4( const Vector &origin, const Vector &angles, const Vector &scale ) + { + float angle, sr, sp, sy, cr, cp, cy; + + if( angles[ROLL] ) + { + angle = angles[YAW] * (M_PI*2 / 360); + SinCos( angle, &sy, &cy ); + angle = angles[PITCH] * (M_PI*2 / 360); + SinCos( angle, &sp, &cp ); + angle = angles[ROLL] * (M_PI*2 / 360); + SinCos( angle, &sr, &cr ); + + mat[0][0] = (cp*cy) * scale.x; + mat[1][0] = (sr*sp*cy+cr*-sy) * scale.y; + mat[2][0] = (cr*sp*cy+-sr*-sy) * scale.z; + mat[3][0] = origin.x; + mat[0][1] = (cp*sy) * scale.x; + mat[1][1] = (sr*sp*sy+cr*cy) * scale.y; + mat[2][1] = (cr*sp*sy+-sr*cy) * scale.z; + mat[3][1] = origin.y; + mat[0][2] = (-sp) * scale.x; + mat[1][2] = (sr*cp) * scale.y; + mat[2][2] = (cr*cp) * scale.z; + mat[3][2] = origin.z; + } + else if( angles[PITCH] ) + { + angle = angles[YAW] * (M_PI*2 / 360); + SinCos( angle, &sy, &cy ); + angle = angles[PITCH] * (M_PI*2 / 360); + SinCos( angle, &sp, &cp ); + + mat[0][0] = (cp*cy) * scale.x; + mat[1][0] = (-sy) * scale.y; + mat[2][0] = (sp*cy) * scale.z; + mat[3][0] = origin.x; + mat[0][1] = (cp*sy) * scale.x; + mat[1][1] = (cy) * scale.y; + mat[2][1] = (sp*sy) * scale.z; + mat[3][1] = origin.y; + mat[0][2] = (-sp) * scale.x; + mat[1][2] = 0; + mat[2][2] = (cp) * scale.z; + mat[3][2] = origin.z; + } + else if( angles[YAW] ) + { + angle = angles[YAW] * (M_PI*2 / 360); + SinCos( angle, &sy, &cy ); + + mat[0][0] = (cy) * scale.x; + mat[1][0] = (-sy) * scale.y; + mat[2][0] = 0; + mat[3][0] = origin.x; + mat[0][1] = (sy) * scale.x; + mat[1][1] = (cy) * scale.y; + mat[2][1] = 0; + mat[3][1] = origin.y; + mat[0][2] = 0; + mat[1][2] = 0; + mat[2][2] = scale.z; + mat[3][2] = origin.z; + } + else + { + mat[0][0] = scale.x; + mat[1][0] = 0; + mat[2][0] = 0; + mat[3][0] = origin.x; + mat[0][1] = 0; + mat[1][1] = scale.y; + mat[2][1] = 0; + mat[3][1] = origin.y; + mat[0][2] = 0; + mat[1][2] = 0; + mat[2][2] = scale.z; + mat[3][2] = origin.z; + } + } + + _forceinline matrix3x4( const Vector &origin, const Radian &angles, const Vector &scale ) + { + float angle, sr, sp, sy, cr, cp, cy; + + angle = angles[ROLL]; // YAW -> ROLL + SinCos( angle, &sy, &cy ); + angle = angles[YAW]; // PITCH -> YAW + SinCos( angle, &sp, &cp ); + angle = angles[PITCH]; // ROLL -> PITCH + SinCos( angle, &sr, &cr ); + + mat[0][0] = (cp*cy) * scale.x; + mat[0][1] = (cp*sy) * scale.x; + mat[0][2] = (-sp) * scale.x; + mat[1][0] = (sr*sp*cy+cr*-sy) * scale.y; + mat[1][1] = (sr*sp*sy+cr*cy) * scale.y; + mat[1][2] = (sr*cp) * scale.y; + mat[2][0] = (cr*sp*cy+-sr*-sy) * scale.z; + mat[2][1] = (cr*sp*sy+-sr*cy) * scale.z; + mat[2][2] = (cr*cp) * scale.z; + mat[3][0] = origin.x; + mat[3][1] = origin.y; + mat[3][2] = origin.z; + } + + _forceinline matrix3x4( const Vector &origin, const Radian &angles ) + { + float angle, sr, sp, sy, cr, cp, cy; + + angle = angles[ROLL]; // YAW -> ROLL + SinCos( angle, &sy, &cy ); + angle = angles[YAW]; // PITCH -> YAW + SinCos( angle, &sp, &cp ); + angle = angles[PITCH]; // ROLL -> PITCH + SinCos( angle, &sr, &cr ); + + mat[0][0] = (cp*cy); + mat[0][1] = (cp*sy); + mat[0][2] = (-sp); + mat[1][0] = (sr*sp*cy+cr*-sy); + mat[1][1] = (sr*sp*sy+cr*cy); + mat[1][2] = (sr*cp); + mat[2][0] = (cr*sp*cy+-sr*-sy); + mat[2][1] = (cr*sp*sy+-sr*cy); + mat[2][2] = (cr*cp); + mat[3][0] = origin.x; + mat[3][1] = origin.y; + mat[3][2] = origin.z; + } + + // init from quaternion + origin + _forceinline matrix3x4( const Vector &origin, const Vector4D &quaternion ) + { + mat[0][0] = 1.0f - 2.0f * (quaternion.y * quaternion.y + quaternion.z * quaternion.z); + mat[1][0] = 2.0f * (quaternion.x * quaternion.y - quaternion.z * quaternion.w); + mat[2][0] = 2.0f * (quaternion.x * quaternion.z + quaternion.y * quaternion.w); + mat[3][0] = origin[0]; + mat[0][1] = 2.0f * (quaternion.x * quaternion.y + quaternion.z * quaternion.w); + mat[1][1] = 1.0f - 2.0f * (quaternion.x * quaternion.x + quaternion.z * quaternion.z); + mat[2][1] = 2.0f * (quaternion.y * quaternion.z - quaternion.x * quaternion.w); + mat[3][1] = origin[1]; + mat[0][2] = 2.0f * (quaternion.x * quaternion.z - quaternion.y * quaternion.w); + mat[1][2] = 2.0f * (quaternion.y * quaternion.z + quaternion.x * quaternion.w); + mat[2][2] = 1.0f - 2.0f * (quaternion.x * quaternion.x + quaternion.y * quaternion.y); + mat[3][2] = origin[2]; + } + + // init from rotational matrix + _forceinline matrix3x4( const matrix3x3 &in ) + { + mat[0][0] = in[0][0]; + mat[0][1] = in[0][1]; + mat[0][2] = in[0][2]; + mat[1][0] = in[1][0]; + mat[1][1] = in[1][1]; + mat[1][2] = in[1][2]; + mat[2][0] = in[2][0]; + mat[2][1] = in[2][1]; + mat[2][2] = in[2][2]; + mat[3][0] = 0; + mat[3][1] = 0; + mat[3][2] = 0; + } + + void Identity(); + + // array access + float* operator[]( int i ) { return mat[i]; } + float const* operator[]( int i ) const { return mat[i]; } + operator float *() { return (float *)&mat[0][0]; } + operator const float *() const { return (float *)&mat[0][0]; } + + _forceinline int operator == ( const matrix3x4& mat2 ) const + { + if( mat[0][0] != mat2[0][0] || mat[0][1] != mat2[0][1] || mat[0][2] != mat2[0][2] ) + return false; + if( mat[1][0] != mat2[1][0] || mat[1][1] != mat2[1][1] || mat[1][2] != mat2[1][2] ) + return false; + if( mat[2][0] != mat2[2][0] || mat[2][1] != mat2[2][1] || mat[2][2] != mat2[2][2] ) + return false; + if( mat[3][0] != mat2[3][0] || mat[3][1] != mat2[3][1] || mat[3][2] != mat2[3][2] ) + return false; + return true; + } + + _forceinline int operator != ( const matrix3x4& mat2 ) const { return !(*this == mat2 ); } + matrix3x4& operator=( const matrix3x3 &vOther ); + matrix3x4& operator=( const matrix4x4 &vOther ); + + // Access the basis vectors. + Vector GetForward() const { return mat[0]; }; + Vector GetRight() const { return mat[1]; }; + Vector GetUp() const { return mat[2]; }; + Vector GetRow( int i ) const { return mat[i]; } + Vector GetOrigin() const { return mat[3]; }; + + void SetForward( const Vector &vForward ) { mat[0] = vForward; }; + void SetRight( const Vector &vRight ) { mat[1] = vRight; }; + void SetUp( const Vector &vUp ) { mat[2] = vUp; }; + + void SetOrigin( const Vector &vOrigin ) { mat[3] = vOrigin; }; + void GetOrigin( Vector &vOrigin ) { vOrigin = mat[3]; }; + + Vector GetAngles( void ) + { + float xyDist = sqrt( mat[0][0] * mat[0][0] + mat[0][1] * mat[0][1] ); + Vector angles; + + // enough here to get angles? + if( xyDist > 0.001f ) + { + angles[0] = RAD2DEG( atan2( -mat[0][2], xyDist ) ); + angles[1] = RAD2DEG( atan2( mat[0][1], mat[0][0] ) ); + angles[2] = RAD2DEG( atan2( mat[1][2], mat[2][2] ) ); + } + else // forward is mostly Z, gimbal lock + { + angles[0] = RAD2DEG( atan2( -mat[0][2], xyDist ) ); + angles[1] = RAD2DEG( atan2( -mat[1][0], mat[1][1] ) ); + angles[2] = 0.0f; + } + + return angles; + } + + void GetStudioTransform( Vector &position, Radian &angles ) + { + float xyDist = sqrt( mat[0][0] * mat[0][0] + mat[0][1] * mat[0][1] ); + + // enough here to get angles? + if( xyDist > 0.001f ) + { + angles.y = atan2( -mat[0][2], xyDist ); + angles.z = atan2( mat[0][1], mat[0][0] ); + angles.x = atan2( mat[1][2], mat[2][2] ); + } + else // forward is mostly Z, gimbal lock + { + angles.y = atan2( -mat[0][2], xyDist ); + angles.z = atan2( -mat[1][0], mat[1][1] ); + angles.x = 0.0f; + } + + position = mat[3]; + } + + void GetAngles( Vector &angles ) { angles = GetAngles(); } + + void GetAngles( Radian &angles ) + { + float xyDist = sqrt( mat[0][0] * mat[0][0] + mat[0][1] * mat[0][1] ); + + // enough here to get angles? + if( xyDist > 0.001f ) + { + angles.y = atan2( -mat[0][2], xyDist ); + angles.z = atan2( mat[0][1], mat[0][0] ); + angles.x = atan2( mat[1][2], mat[2][2] ); + } + else // forward is mostly Z, gimbal lock + { + angles.y = atan2( -mat[0][2], xyDist ); + angles.z = atan2( -mat[1][0], mat[1][1] ); + angles.x = 0.0f; + } + } + + Vector4D GetQuaternion( void ); + void GetQuaternion( Vector4D &quat ) { quat = GetQuaternion(); } + + // transform point and normal + Vector VectorTransform( const Vector &v ) const; + Vector VectorITransform( const Vector &v ) const; + Vector VectorRotate( const Vector &v ) const; + Vector VectorIRotate( const Vector &v ) const; + + // copy as OpenGl matrix + inline void CopyToArray( float *rgfl ) const + { + rgfl[ 0] = mat[0][0]; + rgfl[ 1] = mat[0][1]; + rgfl[ 2] = mat[0][2]; + rgfl[ 3] = 0.0f; + rgfl[ 4] = mat[1][0]; + rgfl[ 5] = mat[1][1]; + rgfl[ 6] = mat[1][2]; + rgfl[ 7] = 0.0f; + rgfl[ 8] = mat[2][0]; + rgfl[ 9] = mat[2][1]; + rgfl[10] = mat[2][2]; + rgfl[11] = 0.0f; + rgfl[12] = mat[3][0]; + rgfl[13] = mat[3][1]; + rgfl[14] = mat[3][2]; + rgfl[15] = 1.0f; + } + + // copy as Vector4D array + inline void CopyToArray4x3( Vector4D array[] ) const + { + array[0] = Vector4D( mat[0][0], mat[0][1], mat[0][2], mat[3][0] ); + array[1] = Vector4D( mat[1][0], mat[1][1], mat[1][2], mat[3][1] ); + array[2] = Vector4D( mat[2][0], mat[2][1], mat[2][2], mat[3][2] ); + } + + // Transpose. + matrix3x4 Transpose() const + { + return matrix3x4( // transpose 3x3, position is unchanged + mat[0][0], mat[1][0], mat[2][0], + mat[0][1], mat[1][1], mat[2][1], + mat[0][2], mat[1][2], mat[2][2], + mat[3][0], mat[3][1], mat[3][2] ); + } + + matrix3x4 Invert( void ) const; // basic orthonormal invert + matrix3x4 ConcatTransforms( const matrix3x4 mat2 ); + matrix3x4 ConcatTransforms( const matrix3x4 mat2 ) const; + + Vector mat[4]; +}; + +class matrix4x4 +{ +public: + matrix4x4(); + matrix4x4( float m00, float m01, float m02, float m03, + float m10, float m11, float m12, float m13, + float m20, float m21, float m22, float m23, + float m30, float m31, float m32, float m33 ) + { + mat[0][0] = m00; + mat[0][1] = m01; + mat[0][2] = m02; + mat[0][3] = m03; + mat[1][0] = m10; + mat[1][1] = m11; + mat[1][2] = m12; + mat[1][3] = m13; + mat[2][0] = m20; + mat[2][1] = m21; + mat[2][2] = m22; + mat[2][3] = m23; + mat[3][0] = m30; + mat[3][1] = m31; + mat[3][2] = m32; + mat[3][3] = m33; + } + + // init from OpenGl matrix + matrix4x4( float *opengl_matrix ) + { + mat[0][0] = opengl_matrix[0]; + mat[0][1] = opengl_matrix[1]; + mat[0][2] = opengl_matrix[2]; + mat[0][3] = opengl_matrix[3]; + mat[1][0] = opengl_matrix[4]; + mat[1][1] = opengl_matrix[5]; + mat[1][2] = opengl_matrix[6]; + mat[1][3] = opengl_matrix[7]; + mat[2][0] = opengl_matrix[8]; + mat[2][1] = opengl_matrix[9]; + mat[2][2] = opengl_matrix[10]; + mat[2][3] = opengl_matrix[11]; + mat[3][0] = opengl_matrix[12]; + mat[3][1] = opengl_matrix[13]; + mat[3][2] = opengl_matrix[14]; + mat[3][3] = opengl_matrix[15]; + } + + // init from entity + _forceinline matrix4x4( const Vector &origin, const Vector &angles, float scale = 1.0f ) + { + float angle, sr, sp, sy, cr, cp, cy; + + if( angles[ROLL] ) + { + angle = angles[YAW] * (M_PI*2 / 360); + SinCos( angle, &sy, &cy ); + angle = angles[PITCH] * (M_PI*2 / 360); + SinCos( angle, &sp, &cp ); + angle = angles[ROLL] * (M_PI*2 / 360); + SinCos( angle, &sr, &cr ); + + mat[0][0] = (cp*cy) * scale; + mat[1][0] = (sr*sp*cy+cr*-sy) * scale; + mat[2][0] = (cr*sp*cy+-sr*-sy) * scale; + mat[3][0] = origin.x; + mat[0][1] = (cp*sy) * scale; + mat[1][1] = (sr*sp*sy+cr*cy) * scale; + mat[2][1] = (cr*sp*sy+-sr*cy) * scale; + mat[3][1] = origin.y; + mat[0][2] = (-sp) * scale; + mat[1][2] = (sr*cp) * scale; + mat[2][2] = (cr*cp) * scale; + mat[3][2] = origin.z; + mat[0][3] = 0; + mat[1][3] = 0; + mat[2][3] = 0; + mat[3][3] = 1; + } + else if( angles[PITCH] ) + { + angle = angles[YAW] * (M_PI*2 / 360); + SinCos( angle, &sy, &cy ); + angle = angles[PITCH] * (M_PI*2 / 360); + SinCos( angle, &sp, &cp ); + + mat[0][0] = (cp*cy) * scale; + mat[1][0] = (-sy) * scale; + mat[2][0] = (sp*cy) * scale; + mat[3][0] = origin.x; + mat[0][1] = (cp*sy) * scale; + mat[1][1] = (cy) * scale; + mat[2][1] = (sp*sy) * scale; + mat[3][1] = origin.y; + mat[0][2] = (-sp) * scale; + mat[1][2] = 0; + mat[2][2] = (cp) * scale; + mat[3][2] = origin.z; + mat[0][3] = 0; + mat[1][3] = 0; + mat[2][3] = 0; + mat[3][3] = 1; + } + else if( angles[YAW] ) + { + angle = angles[YAW] * (M_PI*2 / 360); + SinCos( angle, &sy, &cy ); + + mat[0][0] = (cy) * scale; + mat[1][0] = (-sy) * scale; + mat[2][0] = 0; + mat[3][0] = origin.x; + mat[0][1] = (sy) * scale; + mat[1][1] = (cy) * scale; + mat[2][1] = 0; + mat[3][1] = origin.y; + mat[0][2] = 0; + mat[1][2] = 0; + mat[2][2] = scale; + mat[3][2] = origin.z; + mat[0][3] = 0; + mat[1][3] = 0; + mat[2][3] = 0; + mat[3][3] = 1; + } + else + { + mat[0][0] = scale; + mat[1][0] = 0; + mat[2][0] = 0; + mat[3][0] = origin.x; + mat[0][1] = 0; + mat[1][1] = scale; + mat[2][1] = 0; + mat[3][1] = origin.y; + mat[0][2] = 0; + mat[1][2] = 0; + mat[2][2] = scale; + mat[3][2] = origin.z; + mat[0][3] = 0; + mat[1][3] = 0; + mat[2][3] = 0; + mat[3][3] = 1; + } + } + + // init from entity + _forceinline matrix4x4( const Vector &origin, const Vector &angles, const Vector &scale ) + { + float angle, sr, sp, sy, cr, cp, cy; + + if( angles[ROLL] ) + { + angle = angles[YAW] * (M_PI*2 / 360); + SinCos( angle, &sy, &cy ); + angle = angles[PITCH] * (M_PI*2 / 360); + SinCos( angle, &sp, &cp ); + angle = angles[ROLL] * (M_PI*2 / 360); + SinCos( angle, &sr, &cr ); + + mat[0][0] = (cp*cy) * scale.x; + mat[1][0] = (sr*sp*cy+cr*-sy) * scale.y; + mat[2][0] = (cr*sp*cy+-sr*-sy) * scale.z; + mat[3][0] = origin.x; + mat[0][1] = (cp*sy) * scale.x; + mat[1][1] = (sr*sp*sy+cr*cy) * scale.y; + mat[2][1] = (cr*sp*sy+-sr*cy) * scale.z; + mat[3][1] = origin.y; + mat[0][2] = (-sp) * scale.x; + mat[1][2] = (sr*cp) * scale.y; + mat[2][2] = (cr*cp) * scale.z; + mat[3][2] = origin.z; + mat[0][3] = 0; + mat[1][3] = 0; + mat[2][3] = 0; + mat[3][3] = 1; + } + else if( angles[PITCH] ) + { + angle = angles[YAW] * (M_PI*2 / 360); + SinCos( angle, &sy, &cy ); + angle = angles[PITCH] * (M_PI*2 / 360); + SinCos( angle, &sp, &cp ); + + mat[0][0] = (cp*cy) * scale.x; + mat[1][0] = (-sy) * scale.y; + mat[2][0] = (sp*cy) * scale.z; + mat[3][0] = origin.x; + mat[0][1] = (cp*sy) * scale.x; + mat[1][1] = (cy) * scale.y; + mat[2][1] = (sp*sy) * scale.z; + mat[3][1] = origin.y; + mat[0][2] = (-sp) * scale.x; + mat[1][2] = 0; + mat[2][2] = (cp) * scale.z; + mat[3][2] = origin.z; + mat[0][3] = 0; + mat[1][3] = 0; + mat[2][3] = 0; + mat[3][3] = 1; + } + else if( angles[YAW] ) + { + angle = angles[YAW] * (M_PI*2 / 360); + SinCos( angle, &sy, &cy ); + + mat[0][0] = (cy) * scale.x; + mat[1][0] = (-sy) * scale.y; + mat[2][0] = 0; + mat[3][0] = origin.x; + mat[0][1] = (sy) * scale.x; + mat[1][1] = (cy) * scale.y; + mat[2][1] = 0; + mat[3][1] = origin.y; + mat[0][2] = 0; + mat[1][2] = 0; + mat[2][2] = scale.z; + mat[3][2] = origin.z; + mat[0][3] = 0; + mat[1][3] = 0; + mat[2][3] = 0; + mat[3][3] = 1; + } + else + { + mat[0][0] = scale.x; + mat[1][0] = 0; + mat[2][0] = 0; + mat[3][0] = origin.x; + mat[0][1] = 0; + mat[1][1] = scale.y; + mat[2][1] = 0; + mat[3][1] = origin.y; + mat[0][2] = 0; + mat[1][2] = 0; + mat[2][2] = scale.z; + mat[3][2] = origin.z; + mat[0][3] = 0; + mat[1][3] = 0; + mat[2][3] = 0; + mat[3][3] = 1; + } + } + + // init from quaternion + origin + _forceinline matrix4x4( const Vector &origin, const Vector4D &quaternion ) + { + mat[0][0] = 1.0f - 2.0f * (quaternion.y * quaternion.y + quaternion.z * quaternion.z); + mat[1][0] = 2.0f * (quaternion.x * quaternion.y - quaternion.z * quaternion.w); + mat[2][0] = 2.0f * (quaternion.x * quaternion.z + quaternion.y * quaternion.w); + mat[3][0] = origin[0]; + mat[0][1] = 2.0f * (quaternion.x * quaternion.y + quaternion.z * quaternion.w); + mat[1][1] = 1.0f - 2.0f * (quaternion.x * quaternion.x + quaternion.z * quaternion.z); + mat[2][1] = 2.0f * (quaternion.y * quaternion.z - quaternion.x * quaternion.w); + mat[3][1] = origin[1]; + mat[0][2] = 2.0f * (quaternion.x * quaternion.z - quaternion.y * quaternion.w); + mat[1][2] = 2.0f * (quaternion.y * quaternion.z + quaternion.x * quaternion.w); + mat[2][2] = 1.0f - 2.0f * (quaternion.x * quaternion.x + quaternion.y * quaternion.y); + mat[3][2] = origin[2]; + mat[0][3] = 0; + mat[1][3] = 0; + mat[2][3] = 0; + mat[3][3] = 1; + } + + // init from matrix3x4 + _forceinline matrix4x4( const matrix3x4 &in ) + { + mat[0][0] = in[0][0]; + mat[0][1] = in[0][1]; + mat[0][2] = in[0][2]; + mat[1][0] = in[1][0]; + mat[1][1] = in[1][1]; + mat[1][2] = in[1][2]; + mat[2][0] = in[2][0]; + mat[2][1] = in[2][1]; + mat[2][2] = in[2][2]; + mat[3][0] = in[3][0]; + mat[3][1] = in[3][1]; + mat[3][2] = in[3][2]; + mat[0][3] = 0; + mat[1][3] = 0; + mat[2][3] = 0; + mat[3][3] = 1; + } + + void Identity(); + + // creates some non-identity states + void CreateModelview( void ); + void CreateTexture( void ); + void Crop( const Vector &mins, const Vector &maxs ); + void LookAt( const Vector &eye, const Vector &dir, const Vector &up ); // like gluLookAt + void CreateProjection( float fov_x, float fov_y, float zNear, float zFar ); + void CreateProjection( float xMax, float xMin, float yMax, float yMin, float zNear, float zFar ); + void CreateOrtho( float xLeft, float xRight, float yBottom, float yTop, float zNear, float zFar ); + void CreateOrthoRH( float xLeft, float xRight, float yBottom, float yTop, float zNear, float zFar ); + void CreateTranslate( float x, float y, float z ); + void CreateRotate( float angle, float x, float y, float z ); + void CreateScale( float scale ); + void CreateScale( float x, float y, float z ); + + // array access + float* operator[]( int i ) { return mat[i]; } + float const* operator[]( int i ) const { return mat[i]; } + operator float *() { return (float *)&mat[0][0]; } + operator const float *() const { return (float *)&mat[0][0]; } + + _forceinline int operator == ( const matrix4x4& mat2 ) const + { + if( mat[0][0] != mat2[0][0] || mat[0][1] != mat2[0][1] || mat[0][2] != mat2[0][2] || mat[0][3] != mat2[0][3] ) + return false; + if( mat[1][0] != mat2[1][0] || mat[1][1] != mat2[1][1] || mat[1][2] != mat2[1][2] || mat[1][3] != mat2[1][3] ) + return false; + if( mat[2][0] != mat2[2][0] || mat[2][1] != mat2[2][1] || mat[2][2] != mat2[2][2] || mat[2][3] != mat2[2][3] ) + return false; + if( mat[3][0] != mat2[3][0] || mat[3][1] != mat2[3][1] || mat[3][2] != mat2[3][2] || mat[3][3] != mat2[3][3] ) + return false; + return true; + } + + _forceinline int operator != ( const matrix4x4& mat2 ) const { return !(*this == mat2 ); } + matrix4x4& operator=( const matrix3x3 &vOther ); + matrix4x4& operator=( const matrix3x4 &vOther ); + matrix4x4& operator=( const matrix4x4 &vOther ); + + // Access the basis vectors. + Vector GetForward() const { return mat[0]; }; + Vector GetRight() const { return mat[1]; }; + Vector GetUp() const { return mat[2]; }; + Vector GetRow( int i ) const { return mat[i]; } + Vector GetOrigin() const { return mat[3]; }; + + void SetForward( const Vector4D &vForward ) { mat[0] = vForward; }; + void SetRight( const Vector4D &vRight ) { mat[1] = vRight; }; + void SetUp( const Vector4D &vUp ) { mat[2] = vUp; }; + void SetForward( const Vector &vForward ) { mat[0] = Vector4D( vForward.x, vForward.y, vForward.z, 1.0f ); }; + void SetRight( const Vector &vRight ) { mat[1] = Vector4D( vRight.x, vRight.y, vRight.z, 1.0f ); }; + void SetUp( const Vector &vUp ) { mat[2] = Vector4D( vUp.x, vUp.y, vUp.z, 1.0f ); }; + + void SetOrigin( const Vector &vOrigin ) { mat[3] = Vector4D( vOrigin.x, vOrigin.y, vOrigin.z, 1.0f ); }; + void GetOrigin( Vector &vOrigin ) { vOrigin = mat[3]; }; + + Vector GetAngles( void ) + { + float xyDist = sqrt( mat[0][0] * mat[0][0] + mat[0][1] * mat[0][1] ); + Vector angles; + + // enough here to get angles? + if( xyDist > 0.001f ) + { + angles[0] = RAD2DEG( atan2( -mat[0][2], xyDist ) ); + angles[1] = RAD2DEG( atan2( mat[0][1], mat[0][0] ) ); + angles[2] = RAD2DEG( atan2( mat[1][2], mat[2][2] ) ); + } + else // forward is mostly Z, gimbal lock + { + angles[0] = RAD2DEG( atan2( -mat[0][2], xyDist ) ); + angles[1] = RAD2DEG( atan2( -mat[1][0], mat[1][1] ) ); + angles[2] = 0; + } + + return angles; + } + + void GetAngles( Vector &angles ) { angles = GetAngles(); } + + Vector4D GetQuaternion( void ); + void GetQuaternion( Vector4D &quat ) { quat = GetQuaternion(); } + + // transform point and normal + Vector VectorTransform( const Vector &v ) const; + Vector4D VectorTransform( const Vector4D &v ) const; + Vector VectorITransform( const Vector &v ) const; + Vector VectorRotate( const Vector &v ) const; + Vector VectorIRotate( const Vector &v ) const; + + // transform plane + void TransformPositivePlane( const struct mplane_s &in, struct mplane_s &out ); + void TransformPositivePlane( const struct plane_s &in, struct plane_s &out ); + void TransformStandardPlane( const struct mplane_s &in, struct mplane_s &out ); + void TransformStandardPlane( const struct plane_s &in, struct plane_s &out ); + + // copy as OpenGl matrix + inline void CopyToArray( float *rgfl ) const + { + rgfl[ 0] = mat[0][0]; + rgfl[ 1] = mat[0][1]; + rgfl[ 2] = mat[0][2]; + rgfl[ 3] = mat[0][3]; + rgfl[ 4] = mat[1][0]; + rgfl[ 5] = mat[1][1]; + rgfl[ 6] = mat[1][2]; + rgfl[ 7] = mat[1][3]; + rgfl[ 8] = mat[2][0]; + rgfl[ 9] = mat[2][1]; + rgfl[10] = mat[2][2]; + rgfl[11] = mat[2][3]; + rgfl[12] = mat[3][0]; + rgfl[13] = mat[3][1]; + rgfl[14] = mat[3][2]; + rgfl[15] = mat[3][3]; + } + + // Transpose. + matrix4x4 Transpose() const + { + return matrix4x4( + mat[0][0], mat[1][0], mat[2][0], mat[3][0], + mat[0][1], mat[1][1], mat[2][1], mat[3][1], + mat[0][2], mat[1][2], mat[2][2], mat[3][2], + mat[0][3], mat[1][3], mat[2][3], mat[3][3] ); + } + + matrix4x4 Invert( void ) const; // basic orthonormal invert + matrix4x4 InvertFull( void ) const; // full invert + matrix4x4 ConcatTransforms( const matrix4x4 mat2 ); + matrix4x4 Concat( const matrix4x4 mat2 ); + + void ConcatTranslate( float x, float y, float z ) + { + matrix4x4 temp; + temp.CreateTranslate( x, y, z ); + *this = Concat( temp ); + } + + void ConcatRotate( float angle, float x, float y, float z ) + { + matrix4x4 temp; + temp.CreateRotate( angle, x, y, z ); + *this = Concat( temp ); + } + + void ConcatScale( float scale ) + { + matrix4x4 temp; + temp.CreateScale( scale ); + *this = Concat( temp ); + } + + void ConcatScale( float x, float y, float z ) + { + matrix4x4 temp; + temp.CreateScale( x, y, z ); + *this = Concat( temp ); + } + + Vector4D mat[4]; +}; + +#endif//MATRIX_H \ No newline at end of file diff --git a/game_shared/procbones.cpp b/game_shared/procbones.cpp new file mode 100644 index 0000000..61761e6 --- /dev/null +++ b/game_shared/procbones.cpp @@ -0,0 +1,303 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// + +#include "mathlib.h" +#include "studio.h" + +//----------------------------------------------------------------------------- +// Purpose: look at single column vector of another bones local transformation +// and generate a procedural transformation based on how that column +// points down the 6 cardinal axis (all negative weights are clamped to 0). +//----------------------------------------------------------------------------- +bool DoAxisInterpBone( mstudioaxisinterpbone_t *pProc, mstudiobone_t *pbones, int iBone, matrix3x4 *bonetransform ) +{ + Vector control; + + if( pbones[pProc->control].parent != -1 ) // invert it back into parent's space. + control = bonetransform[pbones[pProc->control].parent].VectorIRotate( bonetransform[iBone][pProc->axis] ); + else control = bonetransform[iBone][pProc->axis]; + + Vector4D *q1, *q2, *q3; + Vector *p1, *p2, *p3; + + // find axial control inputs + float a1 = control.x; + float a2 = control.y; + float a3 = control.z; + + if( a1 >= 0.0f ) + { + q1 = &pProc->quat[0]; + p1 = &pProc->pos[0]; + } + else + { + a1 = -a1; + q1 = &pProc->quat[1]; + p1 = &pProc->pos[1]; + } + + if( a2 >= 0.0f ) + { + q2 = &pProc->quat[2]; + p2 = &pProc->pos[2]; + } + else + { + a2 = -a2; + q2 = &pProc->quat[3]; + p2 = &pProc->pos[3]; + } + + if( a3 >= 0.0f ) + { + q3 = &pProc->quat[4]; + p3 = &pProc->pos[4]; + } + else + { + a3 = -a3; + q3 = &pProc->quat[5]; + p3 = &pProc->pos[5]; + } + + Vector4D v, tmp; + Vector p = g_vecZero; + + // do a three-way blend + if( a1 + a2 > 0.0f ) + { + float t = 1.0 / (a1 + a2 + a3); + + // FIXME: do a proper 3-way Quat blend! + QuaternionSlerp( *q2, *q1, a1 / (a1 + a2), tmp ); + QuaternionSlerp( tmp, *q3, a3 * t, v ); + p += *p1 * ( a1 * t ); + p += *p2 * ( a2 * t ); + p += *p3 * ( a3 * t ); + + bonetransform[iBone] = bonetransform[pbones[iBone].parent].ConcatTransforms( matrix3x4( p, v )); + + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Generate a procedural transformation based on how that another bones +// local transformation matches a set of target orientations. +//----------------------------------------------------------------------------- +bool DoQuatInterpBone( mstudioquatinterpbone_t *pProc, mstudiobone_t *pbones, mstudioquatinterpinfo_t *pTrigger, int iBone, matrix3x4 *bonetransform ) +{ + matrix3x4 bonematrix; + + if( pbones[pProc->control].parent != -1 ) + { + Vector4D src; + float weight[32]; // !!! MAXSTUDIOBONETRIGGERS + float scale = 0.0f; + Vector4D quat; + Vector pos; + int i; + + matrix3x4 tmpmatrix = bonetransform[pbones[pProc->control].parent].Invert(); + matrix3x4 controlmatrix = tmpmatrix.ConcatTransforms( bonetransform[pProc->control] ); + + src = controlmatrix.GetQuaternion(); + + for( i = 0; i < pProc->numtriggers; i++ ) + { + float dot = fabs( DotProduct( pTrigger[i].trigger, src )); + + // FIXME: a fast acos should be acceptable + dot = bound( -1.0f, dot, 1.0f ); + weight[i] = 1.0f - ( 2.0f * acos( dot ) * pTrigger[i].inv_tolerance ); + weight[i] = Q_max( 0.0f, weight[i] ); + scale += weight[i]; + } + + if( scale <= 0.001f ) // EPSILON? + return false; + + for( i = 0; i < pProc->numtriggers; i++ ) + { + if( weight[i] != 0.0f ) + break; + } + + // triggers are not triggered + if( i == pProc->numtriggers ) + return false; + + scale = 1.0f / scale; + quat.Init(); + pos.Init(); + + for( i = 0; i < pProc->numtriggers; i++ ) + { + if( weight[i] == 0.0f ) + continue; + + float s = weight[i] * scale; + + QuaternionAlign( pTrigger[i].quat, quat, quat ); + + // g-cont. why valve don't use slerp here?.. + quat.x = quat.x + s * pTrigger[i].quat.x; + quat.y = quat.y + s * pTrigger[i].quat.y; + quat.z = quat.z + s * pTrigger[i].quat.z; + quat.w = quat.w + s * pTrigger[i].quat.w; + pos.x = pos.x + s * pTrigger[i].pos.x; + pos.y = pos.y + s * pTrigger[i].pos.y; + pos.z = pos.z + s * pTrigger[i].pos.z; + } + + quat = quat.Normalize(); // g-cont. is this really needs? + bonematrix = matrix3x4( pos, quat ); + bonetransform[iBone] = bonetransform[pbones[iBone].parent].ConcatTransforms( bonematrix ); + + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Generate a procedural transformation so that one bone points at +// another point on the model +//----------------------------------------------------------------------------- +void DoAimAtBone( mstudioaimatbone_t *pProc, Vector4D &q1, mstudiobone_t *pbones, int iBone, matrix3x4 *bonetransform, const studiohdr_t *pStudioHdr ) +{ + // The world matrix of the bone to change + matrix3x4 boneMatrix; + + // Guaranteed to be unit length + const Vector &userAimVector = pProc->aimvector; + + // Guaranteed to be unit length + const Vector &userUpVector = pProc->upvector; + + // Get to get position of bone but also for up reference + matrix3x4 parentSpace = bonetransform[pProc->parent]; + + // World space position of the bone to aim + Vector aimWorldPosition = parentSpace.VectorTransform( pProc->basepos ); + + // The worldspace pos to aim at + Vector aimAtWorldPosition; + + if( pStudioHdr ) + { + // This means it's AIMATATTACH + mstudioattachment_t *pattachment = (mstudioattachment_t *) ((byte *)pStudioHdr + pStudioHdr->attachmentindex) + pProc->aim; + aimAtWorldPosition = bonetransform[pattachment->bone].VectorTransform( pattachment->org ); + } + else + { + aimAtWorldPosition = bonetransform[pProc->aim].GetOrigin(); + } + + // The aim and up data is relative to this bone, not the parent bone + matrix3x4 bonematrix, boneLocalToWorld; + + bonematrix = matrix3x4( pProc->basepos, q1 ); + boneLocalToWorld = bonetransform[pProc->parent].ConcatTransforms( bonematrix ); + + Vector aimVector = (aimAtWorldPosition - aimWorldPosition).Normalize(); + + Vector axis = CrossProduct( userAimVector, aimVector ).Normalize(); + float angle( acosf( DotProduct( userAimVector, aimVector ))); + Vector4D aimRotation; + + AxisAngleQuaternion( axis, RAD2DEG( angle ), aimRotation ); + + if(( 1.0f - fabs( DotProduct( userUpVector, userAimVector ))) > FLT_EPSILON ) + { + matrix3x4 aimRotationMatrix = matrix3x4( g_vecZero, aimRotation ); + Vector tmp_pUp = aimRotationMatrix.VectorRotate( userUpVector ); + Vector tmpV = aimVector * DotProduct( aimVector, tmp_pUp ); + Vector pUp = (tmp_pUp - tmpV).Normalize(); + + Vector tmp_pParentUp = boneLocalToWorld.VectorRotate( userUpVector ); + tmpV = aimVector * DotProduct( aimVector, tmp_pParentUp ); + Vector pParentUp = (tmp_pParentUp - tmpV).Normalize(); + Vector4D upRotation; + + if( 1.0f - fabs( DotProduct( pUp, pParentUp )) > FLT_EPSILON ) + { + angle = acos( DotProduct( pUp, pParentUp )); + axis = CrossProduct( pUp, pParentUp ); + } + else + { + angle = 0; + axis = pUp; + } + + axis = axis.Normalize(); + AxisAngleQuaternion( axis, RAD2DEG( angle ), upRotation ); + Vector4D boneRotation; + + QuaternionMult( upRotation, aimRotation, boneRotation ); + boneMatrix = matrix3x4( aimWorldPosition, boneRotation ); + } + else + { + boneMatrix = matrix3x4( aimWorldPosition, aimRotation ); + } + + bonetransform[iBone] = boneMatrix; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CalcProceduralBone( const studiohdr_t *pStudioHdr, int iBone, matrix3x4 *bonetransform ) +{ + if( !FBitSet( pStudioHdr->flags, STUDIO_HAS_BONEINFO )) + return false; // info about procedural bones is absent + + mstudiobone_t *pbones = (mstudiobone_t *)((byte *)pStudioHdr + pStudioHdr->boneindex); + mstudioboneinfo_t *pinfo = (mstudioboneinfo_t *)((byte *)pbones + pStudioHdr->numbones * sizeof( mstudiobone_t )); + mstudioaxisinterpbone_t *pProcAxis; + mstudioquatinterpbone_t *pProcQuat; + mstudioquatinterpinfo_t *pTrigger; + mstudioaimatbone_t *pProcAimAt; + + if( FBitSet( pbones[iBone].flags, BONE_ALWAYS_PROCEDURAL ) && pinfo[iBone].procindex ) + { + Vector4D quat = pinfo[iBone].quat; + + switch( pinfo[iBone].proctype ) + { + case STUDIO_PROC_AXISINTERP: + pProcAxis = (mstudioaxisinterpbone_t *)((byte *)pStudioHdr + pinfo[iBone].procindex); + if( DoAxisInterpBone( pProcAxis, pbones, iBone, bonetransform )) + return true; + break; + case STUDIO_PROC_QUATINTERP: + pProcQuat = (mstudioquatinterpbone_t *)((byte *)pStudioHdr + pinfo[iBone].procindex); + pTrigger = (mstudioquatinterpinfo_t *)((byte *)pStudioHdr + pProcQuat->triggerindex); + if( DoQuatInterpBone( pProcQuat, pbones, pTrigger, iBone, bonetransform )) + return true; + break; + case STUDIO_PROC_AIMATBONE: + pProcAimAt = (mstudioaimatbone_t *)((byte *)pStudioHdr + pinfo[iBone].procindex); + DoAimAtBone( pProcAimAt, quat, pbones, iBone, bonetransform, NULL ); + return true; + case STUDIO_PROC_AIMATATTACH: + pProcAimAt = (mstudioaimatbone_t *)((byte *)pStudioHdr + pinfo[iBone].procindex); + DoAimAtBone( pProcAimAt, quat, pbones, iBone, bonetransform, pStudioHdr ); + return true; + default: + return false; + } + } + + return false; +} \ No newline at end of file diff --git a/game_shared/randomrange.h b/game_shared/randomrange.h new file mode 100644 index 0000000..f77b86f --- /dev/null +++ b/game_shared/randomrange.h @@ -0,0 +1,73 @@ +/* +randomrange.h - simple class of implementation random values +Copyright (C) 2011 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef RANDOMRANGE_H +#define RANDOMRANGE_H + +#include +#include + +class RandomRange +{ +public: + RandomRange() { m_flMin = m_flMax = 0; m_bDefined = false; } + RandomRange( float fValue ) { m_flMin = m_flMax = fValue; m_bDefined = true; } + RandomRange( float fMin, float fMax) { m_flMin = fMin; m_flMax = fMax; m_bDefined = true; } + RandomRange( char *szToken ) + { + char *cOneDot = NULL; + m_bDefined = true; + + for( char *c = szToken; *c; c++ ) + { + if( *c == '.' ) + { + if( cOneDot != NULL ) + { + // found two dots in a row - it's a range + *cOneDot = 0; // null terminate the first number + m_flMin = Q_atof( szToken ); // parse the first number + *cOneDot = '.'; // change it back, just in case + c++; + m_flMax = Q_atof( c ); // parse the second number + return; + } + else cOneDot = c; + } + else cOneDot = NULL; + } + + // no range, just record the number + m_flMax = m_flMin = Q_atof( szToken ); + } + + // a simple implementation of RANDOM_FLOAT for now + float Random() { return RANDOM_FLOAT(m_flMin, m_flMax); } + float Random() const { return RANDOM_FLOAT(m_flMin, m_flMax); } + + float GetInstance() { return Random(); } + float GetOffset( float fBasis ) { return Random() - fBasis; } + + bool IsDefined() { return m_bDefined; } + + // array access... + operator float *() { return &m_flMin; } + operator const float *() const { return &m_flMin; } + + float m_flMin, m_flMax; // NOTE: should be first so array acess is working correctly + bool m_bDefined; +}; + +#endif//RANDOMRANGE_H \ No newline at end of file diff --git a/game_shared/stringlib.cpp b/game_shared/stringlib.cpp new file mode 100644 index 0000000..a93dbbf --- /dev/null +++ b/game_shared/stringlib.cpp @@ -0,0 +1,581 @@ +//======================================================================= +// Copyright (C) XashXT Group 2011 +// stringlib.cpp - safety string routines +//======================================================================= + +#define NOMINMAX +#include +#include +#include +#include + +#pragma warning(disable : 4244) // MIPS + +//============ +// UTIL_FileExtension +// returns file extension +//============ +const char *UTIL_FileExtension( const char *in ) +{ + const char *separator, *backslash, *colon, *dot; + + separator = Q_strrchr( in, '/' ); + backslash = Q_strrchr( in, '\\' ); + if( !separator || separator < backslash ) + separator = backslash; + colon = Q_strrchr( in, ':' ); + if( !separator || separator < colon ) + separator = colon; + dot = Q_strrchr( in, '.' ); + if( dot == NULL || (separator && ( dot < separator ))) + return ""; + return dot + 1; +} + +void Q_strnupr( const char *in, char *out, size_t size_out ) +{ + if( size_out == 0 ) return; + + while( *in && size_out > 1 ) + { + if( *in >= 'a' && *in <= 'z' ) + *out++ = *in++ + 'A' - 'a'; + else *out++ = *in++; + size_out--; + } + *out = '\0'; +} + +void Q_strnlwr( const char *in, char *out, size_t size_out ) +{ + if( size_out == 0 ) return; + + while( *in && size_out > 1 ) + { + if( *in >= 'A' && *in <= 'Z' ) + *out++ = *in++ + 'a' - 'A'; + else *out++ = *in++; + size_out--; + } + *out = '\0'; +} + +bool Q_isdigit( const char *str ) +{ + if( str && *str ) + { + while( isdigit( *str )) str++; + if( !*str ) return true; + } + return false; +} + +int Q_strlen( const char *string ) +{ + if( !string ) return 0; + + int len = 0; + const char *p = string; + while( *p ) + { + p++; + len++; + } + return len; +} + +char Q_toupper( const char in ) +{ + char out; + + if( in >= 'a' && in <= 'z' ) + out = in + 'A' - 'a'; + else out = in; + + return out; +} + +char Q_tolower( const char in ) +{ + char out; + + if( in >= 'A' && in <= 'Z' ) + out = in + 'a' - 'A'; + else out = in; + + return out; +} + +size_t Q_strncat( char *dst, const char *src, size_t size ) +{ + if( !dst || !src || !size ) + return 0; + + register char *d = dst; + register const char *s = src; + register size_t n = size; + size_t dlen; + + // find the end of dst and adjust bytes left but don't go past end + while( n-- != 0 && *d != '\0' ) d++; + dlen = d - dst; + n = size - dlen; + + if( n == 0 ) return( dlen + Q_strlen( s )); + + while( *s != '\0' ) + { + if( n != 1 ) + { + *d++ = *s; + n--; + } + s++; + } + + *d = '\0'; + return( dlen + ( s - src )); // count does not include NULL +} + +size_t Q_strncpy( char *dst, const char *src, size_t size ) +{ + if( !dst || !src || !size ) + return 0; + + register char *d = dst; + register const char *s = src; + register size_t n = size; + + // copy as many bytes as will fit + if( n != 0 && --n != 0 ) + { + do + { + if(( *d++ = *s++ ) == 0 ) + break; + } while( --n != 0 ); + } + + // not enough room in dst, add NULL and traverse rest of src + if( n == 0 ) + { + if( size != 0 ) + *d = '\0'; // NULL-terminate dst + while( *s++ ); + } + return ( s - src - 1 ); // count does not include NULL +} + +char *copystring( const char *s ) +{ + if( !s ) return NULL; + + char *b = new char[Q_strlen( s ) + 1]; + Q_strcpy( b, s ); + + return b; +} + +int Q_atoi( const char *str ) +{ + int val = 0; + int c, sign; + + if( !str ) return 0; + + // check for empty charachters in string + while( str && *str == ' ' ) + str++; + + if( !str ) return 0; + + if( *str == '-' ) + { + sign = -1; + str++; + } + else sign = 1; + + // check for hex + if( str[0] == '0' && ( str[1] == 'x' || str[1] == 'X' )) + { + str += 2; + while( 1 ) + { + c = *str++; + if( c >= '0' && c <= '9' ) val = (val<<4) + c - '0'; + else if( c >= 'a' && c <= 'f' ) val = (val<<4) + c - 'a' + 10; + else if( c >= 'A' && c <= 'F' ) val = (val<<4) + c - 'A' + 10; + else return val * sign; + } + } + + // check for character + if( str[0] == '\'' ) + return sign * str[1]; + + // assume decimal + while( 1 ) + { + c = *str++; + if( c < '0' || c > '9' ) + return val * sign; + val = val * 10 + c - '0'; + } + return 0; +} + +float Q_atof( const char *str ) +{ + double val = 0; + int c, sign, decimal, total; + + if( !str ) return 0.0f; + + // check for empty charachters in string + while( str && *str == ' ' ) + str++; + + if( !str ) return 0.0f; + + if( *str == '-' ) + { + sign = -1; + str++; + } + else sign = 1; + + // check for hex + if( str[0] == '0' && ( str[1] == 'x' || str[1] == 'X' )) + { + str += 2; + while( 1 ) + { + c = *str++; + if( c >= '0' && c <= '9' ) val = (val * 16) + c - '0'; + else if( c >= 'a' && c <= 'f' ) val = (val * 16) + c - 'a' + 10; + else if( c >= 'A' && c <= 'F' ) val = (val * 16) + c - 'A' + 10; + else return val * sign; + } + } + + // check for character + if( str[0] == '\'' ) return sign * str[1]; + + // assume decimal + decimal = -1; + total = 0; + while( 1 ) + { + c = *str++; + if( c == '.' ) + { + decimal = total; + continue; + } + + if( c < '0' || c > '9' ) + break; + val = val * 10 + c - '0'; + total++; + } + + if( decimal == -1 ) + return val * sign; + + while( total > decimal ) + { + val /= 10; + total--; + } + + return val * sign; +} + +void Q_atov( float *vec, const char *str, size_t siz ) +{ + char buffer[64]; + char *pstr, *pfront; + size_t j; + + Q_strncpy( buffer, str, sizeof( buffer )); + memset( vec, 0, sizeof( float ) * siz ); + pstr = pfront = buffer; + + for( j = 0; j < siz; j++ ) + { + vec[j] = Q_atof( pfront ); + + // valid separator is space + while( *pstr && *pstr != ' ' ) + pstr++; + + if( !*pstr ) break; + pstr++; + pfront = pstr; + } +} + +char *Q_strchr( const char *s, char c ) +{ + int len = Q_strlen( s ); + + while( len-- ) + { + if( *++s == c ) + return (char *)s; + } + return 0; +} + +char *Q_strrchr( const char *s, char c ) +{ + int len = Q_strlen( s ); + + s += len; + + while( len-- ) + { + if( *--s == c ) + return (char *)s; + } + return 0; +} + +int Q_strnicmp( const char *s1, const char *s2, int n ) +{ + int c1, c2; + + if( s1 == NULL ) + { + if( s2 == NULL ) return 0; + else return -1; + } + else if( s2 == NULL ) + return 1; + + do { + c1 = *s1++; + c2 = *s2++; + + if( !n-- ) return 0; // strings are equal until end point + + if( c1 != c2 ) + { + if( c1 >= 'a' && c1 <= 'z' ) c1 -= ('a' - 'A'); + if( c2 >= 'a' && c2 <= 'z' ) c2 -= ('a' - 'A'); + if( c1 != c2 ) return c1 < c2 ? -1 : 1; + } + } while( c1 ); + + // strings are equal + return 0; +} + +int Q_strncmp( const char *s1, const char *s2, int n ) +{ + int c1, c2; + + if( s1 == NULL ) + { + if( s2 == NULL ) return 0; + else return -1; + } + else if( s2 == NULL ) + return 1; + + do { + c1 = *s1++; + c2 = *s2++; + + // strings are equal until end point + if( !n-- ) return 0; + if( c1 != c2 ) return c1 < c2 ? -1 : 1; + + } while( c1 ); + + // strings are equal + return 0; +} + +char *Q_strstr( const char *string, const char *string2 ) +{ + int c, len; + + if( !string || !string2 ) return NULL; + + c = *string2; + len = Q_strlen( string2 ); + + while( string ) + { + for( ; *string && *string != c; string++ ); + + if( *string ) + { + if( !Q_strncmp( string, string2, len )) + break; + string++; + } + else return NULL; + } + return (char *)string; +} + +char *Q_stristr( const char *string, const char *string2 ) +{ + int c, len; + + if( !string || !string2 ) return NULL; + + c = Q_tolower( *string2 ); + len = Q_strlen( string2 ); + + while( string ) + { + for( ; *string && Q_tolower( *string ) != c; string++ ); + + if( *string ) + { + if( !Q_strnicmp( string, string2, len )) + break; + string++; + } + else return NULL; + } + return (char *)string; +} + +int Q_vsnprintf( char *buffer, size_t buffersize, const char *format, va_list args ) +{ + size_t result; + + result = _vsnprintf( buffer, buffersize, format, args ); + + if( result < 0 || result >= buffersize ) + { + buffer[buffersize - 1] = '\0'; + return -1; + } + return result; +} + +int Q_snprintf( char *buffer, size_t buffersize, const char *format, ... ) +{ + va_list args; + int result; + + va_start( args, format ); + result = Q_vsnprintf( buffer, buffersize, format, args ); + va_end( args ); + + return result; +} + +int Q_sprintf( char *buffer, const char *format, ... ) +{ + va_list args; + int result; + + va_start( args, format ); + result = Q_vsnprintf( buffer, 99999, format, args ); + va_end( args ); + + return result; +} + +/* +============ +va + +does a varargs printf into a temp buffer, +so I don't need to have varargs versions +of all text functions. +============ +*/ +char *va( const char *format, ... ) +{ + va_list argptr; + static char string[64][1024], *s; + static int stringindex = 0; + + s = string[stringindex]; + stringindex = (stringindex + 1) & 63; + va_start( argptr, format ); + Q_vsnprintf( s, sizeof( string[0] ), format, argptr ); + va_end( argptr ); + + return s; +} + +char *Q_pretifymem( float value, int digitsafterdecimal ) +{ + static char output[8][32]; + static int current; + float onekb = 1024.0f; + float onemb = onekb * onekb; + char suffix[8]; + char *out = output[current]; + char val[32], *i, *o, *dot; + int pos; + + current = ( current + 1 ) & ( 8 - 1 ); + + // first figure out which bin to use + if( value > onemb ) + { + value /= onemb; + Q_sprintf( suffix, " Mb" ); + } + else if( value > onekb ) + { + value /= onekb; + Q_sprintf( suffix, " Kb" ); + } + else Q_sprintf( suffix, " bytes" ); + + // clamp to >= 0 + digitsafterdecimal = Q_max( digitsafterdecimal, 0 ); + + // if it's basically integral, don't do any decimals + if( fabs( value - (int)value ) < 0.00001 ) + { + Q_sprintf( val, "%i%s", (int)value, suffix ); + } + else + { + char fmt[32]; + + // otherwise, create a format string for the decimals + Q_sprintf( fmt, "%%.%if%s", digitsafterdecimal, suffix ); + Q_sprintf( val, fmt, value ); + } + + // copy from in to out + i = val; + o = out; + + // search for decimal or if it was integral, find the space after the raw number + dot = Q_strstr( i, "." ); + if( !dot ) dot = Q_strstr( i, " " ); + + pos = dot - i; // compute position of dot + pos -= 3; // don't put a comma if it's <= 3 long + + while( *i ) + { + // if pos is still valid then insert a comma every third digit, except if we would be + // putting one in the first spot + if( pos >= 0 && !( pos % 3 )) + { + // never in first spot + if( o != out ) *o++ = ','; + } + + pos--; // count down comma position + *o++ = *i++; // copy rest of data as normal + } + *o = 0; // terminate + + return out; +} \ No newline at end of file diff --git a/game_shared/stringlib.h b/game_shared/stringlib.h new file mode 100644 index 0000000..592e7a4 --- /dev/null +++ b/game_shared/stringlib.h @@ -0,0 +1,63 @@ +//======================================================================= +// Copyright (C) XashXT Group 2011 +// stringlib.h - safety string routines +//======================================================================= +#ifndef STRINGLIB_H +#define STRINGLIB_H + +#include + +extern const char *UTIL_FileExtension( const char *in ); +extern void Q_strnupr( const char *in, char *out, size_t size_out ); +extern void Q_strnlwr( const char *in, char *out, size_t size_out ); +extern bool Q_isdigit( const char *str ); +extern int Q_strlen( const char *string ); +extern char Q_toupper( const char in ); +extern char Q_tolower( const char in ); +extern size_t Q_strncat( char *dst, const char *src, size_t size ); +extern size_t Q_strncpy( char *dst, const char *src, size_t size ); +extern char *copystring( const char *s ); // don't forget release memory after use +#define freestring( a ) (delete [](a)) +int Q_atoi( const char *str ); +float Q_atof( const char *str ); +void Q_atov( float *vec, const char *str, size_t siz ); +char *Q_strchr( const char *s, char c ); +char *Q_strrchr( const char *s, char c ); +int Q_strnicmp( const char *s1, const char *s2, int n ); +int Q_strncmp( const char *s1, const char *s2, int n ); +char *Q_strstr( const char *string, const char *string2 ); +char *Q_stristr( const char *string, const char *string2 ); +int Q_vsnprintf( char *buffer, size_t buffersize, const char *format, va_list args ); +int Q_snprintf( char *buffer, size_t buffersize, const char *format, ... ); +int Q_sprintf( char *buffer, const char *format, ... ); +char *Q_pretifymem( float value, int digitsafterdecimal ); +char *va( const char *format, ... ); + +#define Q_strupr( in, out ) Q_strnupr( in, out, 99999 ) +#define Q_strlwr( int, out ) Q_strnlwr( in, out, 99999 ) +#define Q_strcat( dst, src ) Q_strncat( dst, src, 99999 ) +#define Q_strcpy( dst, src ) Q_strncpy( dst, src, 99999 ) +#define Q_stricmp( s1, s2 ) Q_strnicmp( s1, s2, 99999 ) +#define Q_strcmp( s1, s2 ) Q_strncmp( s1, s2, 99999 ) +#define Q_vsprintf( buffer, format, args ) Q_vsnprintf( buffer, 99999, format, args ) +#define Q_memprint( val) Q_pretifymem( val, 2 ) + +// +// common.cpp +// +void COM_FileBase( const char *in, char *out ); +void COM_ExtractFilePath( const char *path, char *dest ); +void COM_StripExtension( char *path ); +void COM_DefaultExtension( char *path, const char *extension ); +char *COM_MemFgets( unsigned char *pMemFile, long fileSize, long &filePos, char *pBuffer, long bufferSize ); +int COM_TokenWaiting( const char *buffer ); +char *COM_ParseFileExt( char *data, char *token, long token_size, bool allowNewLines ); +#define COM_ParseFile( data, token ) COM_ParseFileExt( data, token, sizeof( token ), true ) +#define COM_ParseLine( data, token ) COM_ParseFileExt( data, token, sizeof( token ), false ) +unsigned int COM_HashKey( const char *string, unsigned int hashSize ); +const char *COM_FileExtension( const char *in ); +char *COM_SkipBracedSection( char *pfile ); +bool COM_FileExists( const char *path ); +void COM_FixSlashes( char *pname ); + +#endif//STRINGLIB_H \ No newline at end of file diff --git a/game_shared/utlarray.h b/game_shared/utlarray.h new file mode 100644 index 0000000..1889222 --- /dev/null +++ b/game_shared/utlarray.h @@ -0,0 +1,771 @@ +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. =======// +// +// Purpose: +// +// $NoKeywords: $ +// +// A growable array class that maintains a free list and keeps elements +// in the same location +//=============================================================================// + +#ifndef UTLVECTOR_H +#define UTLVECTOR_H + +#ifdef _WIN32 +#pragma once +#endif + +#include +#include +#include "utlmemory.h" +#include "utlblockmemory.h" + +#define FOR_EACH_VEC( vecName, iteratorName ) \ + for ( int iteratorName = 0; iteratorName < vecName.Count(); iteratorName++ ) + +//----------------------------------------------------------------------------- +// The CUtlArray class: +// A growable array class which doubles in size by default. +// It will always keep all elements consecutive in memory, and may move the +// elements around in memory (via a PvRealloc) when elements are inserted or +// removed. Clients should therefore refer to the elements of the vector +// by index (they should *never* maintain pointers to elements in the vector). +//----------------------------------------------------------------------------- +template< class T, class A = CUtlMemory > +class CUtlArray +{ + typedef A CAllocator; +public: + typedef T ElemType_t; + + // constructor, destructor + CUtlArray( int growSize = 0, int initSize = 0 ); + CUtlArray( T* pMemory, int allocationCount, int numElements = 0 ); + ~CUtlArray(); + + // Copy the array. + CUtlArray& operator=( const CUtlArray &other ); + + // element access + T& operator[]( int i ); + const T& operator[]( int i ) const; + T& Element( int i ); + const T& Element( int i ) const; + T& Head(); + const T& Head() const; + T& Tail(); + const T& Tail() const; + + // Gets the base address (can change when adding elements!) + T* Base() { return m_Memory.Base(); } + const T* Base() const { return m_Memory.Base(); } + + // Returns the number of elements in the vector + // SIZE IS DEPRECATED! + int Count() const; + int Size() const; // don't use me! + + // Is element index valid? + bool IsValidIndex( int i ) const; + static int InvalidIndex(); + + // Adds an element, uses default constructor + int AddToHead(); + int AddToTail(); + int InsertBefore( int elem ); + int InsertAfter( int elem ); + + // Adds an element, uses copy constructor + int AddToHead( const T& src ); + int AddToTail( const T& src ); + int InsertBefore( int elem, const T& src ); + int InsertAfter( int elem, const T& src ); + + // Adds multiple elements, uses default constructor + int AddMultipleToHead( int num ); + int AddMultipleToTail( int num, const T *pToCopy=NULL ); + int InsertMultipleBefore( int elem, int num, const T *pToCopy=NULL ); // If pToCopy is set, then it's an array of length 'num' and + int InsertMultipleAfter( int elem, int num ); + + // Calls RemoveAll() then AddMultipleToTail. + void SetSize( int size ); + void SetCount( int count ); + + // Calls SetSize and copies each element. + void CopyArray( const T *pArray, int size ); + + // Fast swap + void Swap( CUtlArray< T, A > &vec ); + + // Add the specified array to the tail. + int AddVectorToTail( CUtlArray const &src ); + + // Finds an element (element needs operator== defined) + int Find( const T& src ) const; + + bool HasElement( const T& src ) const; + + // Makes sure we have enough memory allocated to store a requested # of elements + void EnsureCapacity( int num ); + + // Makes sure we have at least this many elements + void EnsureCount( int num ); + + // Element removal + void FastRemove( int elem ); // doesn't preserve order + void Remove( int elem ); // preserves order, shifts elements + bool FindAndRemove( const T& src ); // removes first occurrence of src, preserves order, shifts elements + void RemoveMultiple( int elem, int num ); // preserves order, shifts elements + void RemoveAll(); // doesn't deallocate memory + + // Memory deallocation + void Purge(); + + // Purges the list and calls delete on each element in it. + void PurgeAndDeleteElements(); + + // Compacts the vector to the number of elements actually in use + void Compact(); + + // Set the size by which it grows when it needs to allocate more memory. + void SetGrowSize( int size ) { m_Memory.SetGrowSize( size ); } + + int NumAllocated() const; // Only use this if you really know what you're doing! + + void Sort( int (__cdecl *pfnCompare)(const T *, const T *) ); + +#ifdef DBGFLAG_VALIDATE + void Validate( CValidator &validator, char *pchName ); // Validate our internal structures +#endif // DBGFLAG_VALIDATE + +protected: + // Can't copy this unless we explicitly do it! + CUtlArray( CUtlArray const& vec ) { assert(0); } + + // Grows the vector + void GrowVector( int num = 1 ); + + // Shifts elements.... + void ShiftElementsRight( int elem, int num = 1 ); + void ShiftElementsLeft( int elem, int num = 1 ); + + CAllocator m_Memory; + int m_Size; + + // For easier access to the elements through the debugger + // it's in release builds so this can be used in libraries correctly + T *m_pElements; + + inline void ResetDbgInfo() + { + m_pElements = Base(); + } +}; + + +// this is kind of ugly, but until C++ gets templatized typedefs in C++0x, it's our only choice +template < class T > +class CUtlBlockVector : public CUtlArray< T, CUtlBlockMemory< T, int > > +{ +public: + CUtlBlockVector( int growSize = 0, int initSize = 0 ) + : CUtlArray< T, CUtlBlockMemory< T, int > >( growSize, initSize ) {} +}; + +//----------------------------------------------------------------------------- +// The CUtlArrayFixed class: +// A array class with a fixed allocation scheme +//----------------------------------------------------------------------------- + +template< class BASE_UTLVECTOR, class MUTEX_TYPE = CThreadFastMutex > +class CUtlArrayMT : public BASE_UTLVECTOR, public MUTEX_TYPE +{ + typedef BASE_UTLVECTOR BaseClass; +public: + MUTEX_TYPE Mutex_t; + + // constructor, destructor + CUtlArrayMT( int growSize = 0, int initSize = 0 ) : BaseClass( growSize, initSize ) {} + CUtlArrayMT( typename BaseClass::ElemType_t* pMemory, int numElements ) : BaseClass( pMemory, numElements ) {} +}; + + +//----------------------------------------------------------------------------- +// The CUtlArrayFixed class: +// A array class with a fixed allocation scheme +//----------------------------------------------------------------------------- +template< class T, size_t MAX_SIZE > +class CUtlArrayFixed : public CUtlArray< T, CUtlMemoryFixed > +{ + typedef CUtlArray< T, CUtlMemoryFixed > BaseClass; +public: + + // constructor, destructor + CUtlArrayFixed( int growSize = 0, int initSize = 0 ) : BaseClass( growSize, initSize ) {} + CUtlArrayFixed( T* pMemory, int numElements ) : BaseClass( pMemory, numElements ) {} +}; + + +//----------------------------------------------------------------------------- +// The CUtlArrayFixed class: +// A array class with a fixed allocation scheme +//----------------------------------------------------------------------------- +template< class T, size_t MAX_SIZE > +class CUtlArrayFixedGrowable : public CUtlArray< T, CUtlMemoryFixedGrowable > +{ + typedef CUtlArray< T, CUtlMemoryFixedGrowable > BaseClass; + +public: + // constructor, destructor + CUtlArrayFixedGrowable( int growSize = 0 ) : BaseClass( growSize, MAX_SIZE ) {} +}; + + +//----------------------------------------------------------------------------- +// The CCopyableUtlVector class: +// A array class that allows copy construction (so you can nest a CUtlArray inside of another one of our containers) +// WARNING - this class lets you copy construct which can be an expensive operation if you don't carefully control when it happens +// Only use this when nesting a CUtlArray() inside of another one of our container classes (i.e a CUtlMap) +//----------------------------------------------------------------------------- +template< class T > +class CCopyableUtlVector : public CUtlArray< T, CUtlMemory > +{ + typedef CUtlArray< T, CUtlMemory > BaseClass; +public: + CCopyableUtlVector( int growSize = 0, int initSize = 0 ) : BaseClass( growSize, initSize ) {} + CCopyableUtlVector( T* pMemory, int numElements ) : BaseClass( pMemory, numElements ) {} + virtual ~CCopyableUtlVector() {} + CCopyableUtlVector( CCopyableUtlVector const& vec ) { CopyArray( vec.Base(), vec.Count() ); } +}; + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- +template< typename T, class A > +inline CUtlArray::CUtlArray( int growSize, int initSize ) : + m_Memory(growSize, initSize), m_Size(0) +{ + ResetDbgInfo(); +} + +template< typename T, class A > +inline CUtlArray::CUtlArray( T* pMemory, int allocationCount, int numElements ) : + m_Memory(pMemory, allocationCount), m_Size(numElements) +{ + ResetDbgInfo(); +} + +template< typename T, class A > +inline CUtlArray::~CUtlArray() +{ + Purge(); +} + +template< typename T, class A > +inline CUtlArray& CUtlArray::operator=( const CUtlArray &other ) +{ + int nCount = other.Count(); + SetSize( nCount ); + for ( int i = 0; i < nCount; i++ ) + { + (*this)[ i ] = other[ i ]; + } + return *this; +} + + +//----------------------------------------------------------------------------- +// element access +//----------------------------------------------------------------------------- +template< typename T, class A > +inline T& CUtlArray::operator[]( int i ) +{ + return m_Memory[ i ]; +} + +template< typename T, class A > +inline const T& CUtlArray::operator[]( int i ) const +{ + return m_Memory[ i ]; +} + +template< typename T, class A > +inline T& CUtlArray::Element( int i ) +{ + return m_Memory[ i ]; +} + +template< typename T, class A > +inline const T& CUtlArray::Element( int i ) const +{ + return m_Memory[ i ]; +} + +template< typename T, class A > +inline T& CUtlArray::Head() +{ + assert( m_Size > 0 ); + return m_Memory[ 0 ]; +} + +template< typename T, class A > +inline const T& CUtlArray::Head() const +{ + assert( m_Size > 0 ); + return m_Memory[ 0 ]; +} + +template< typename T, class A > +inline T& CUtlArray::Tail() +{ + assert( m_Size > 0 ); + return m_Memory[ m_Size - 1 ]; +} + +template< typename T, class A > +inline const T& CUtlArray::Tail() const +{ + assert( m_Size > 0 ); + return m_Memory[ m_Size - 1 ]; +} + + +//----------------------------------------------------------------------------- +// Count +//----------------------------------------------------------------------------- +template< typename T, class A > +inline int CUtlArray::Size() const +{ + return m_Size; +} + +template< typename T, class A > +inline int CUtlArray::Count() const +{ + return m_Size; +} + + +//----------------------------------------------------------------------------- +// Is element index valid? +//----------------------------------------------------------------------------- +template< typename T, class A > +inline bool CUtlArray::IsValidIndex( int i ) const +{ + return (i >= 0) && (i < m_Size); +} + + +//----------------------------------------------------------------------------- +// Returns in invalid index +//----------------------------------------------------------------------------- +template< typename T, class A > +inline int CUtlArray::InvalidIndex() +{ + return -1; +} + + +//----------------------------------------------------------------------------- +// Grows the vector +//----------------------------------------------------------------------------- +template< typename T, class A > +void CUtlArray::GrowVector( int num ) +{ + if (m_Size + num > m_Memory.NumAllocated()) + { + m_Memory.Grow( m_Size + num - m_Memory.NumAllocated() ); + } + + m_Size += num; + ResetDbgInfo(); +} + + +//----------------------------------------------------------------------------- +// Sorts the vector +//----------------------------------------------------------------------------- +template< typename T, class A > +void CUtlArray::Sort( int (__cdecl *pfnCompare)(const T *, const T *) ) +{ + typedef int (__cdecl *QSortCompareFunc_t)(const void *, const void *); + if ( Count() <= 1 ) + return; + + if ( Base() ) + { + qsort( Base(), Count(), sizeof(T), (QSortCompareFunc_t)(pfnCompare) ); + } + else + { + assert( 0 ); + // this path is untested + // if you want to sort vectors that use a non-sequential memory allocator, + // you'll probably want to patch in a quicksort algorithm here + // I just threw in this bubble sort to have something just in case... + + for ( int i = m_Size - 1; i >= 0; --i ) + { + for ( int j = 1; j <= i; ++j ) + { + if ( pfnCompare( &Element( j - 1 ), &Element( j ) ) < 0 ) + { + swap( Element( j - 1 ), Element( j ) ); + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Makes sure we have enough memory allocated to store a requested # of elements +//----------------------------------------------------------------------------- +template< typename T, class A > +void CUtlArray::EnsureCapacity( int num ) +{ + m_Memory.EnsureCapacity(num); + ResetDbgInfo(); +} + + +//----------------------------------------------------------------------------- +// Makes sure we have at least this many elements +//----------------------------------------------------------------------------- +template< typename T, class A > +void CUtlArray::EnsureCount( int num ) +{ + if (Count() < num) + AddMultipleToTail( num - Count() ); +} + + +//----------------------------------------------------------------------------- +// Shifts elements +//----------------------------------------------------------------------------- +template< typename T, class A > +void CUtlArray::ShiftElementsRight( int elem, int num ) +{ + assert( IsValidIndex(elem) || ( m_Size == 0 ) || ( num == 0 )); + int numToMove = m_Size - elem - num; + if ((numToMove > 0) && (num > 0)) + memmove( &Element(elem+num), &Element(elem), numToMove * sizeof(T) ); +} + +template< typename T, class A > +void CUtlArray::ShiftElementsLeft( int elem, int num ) +{ + assert( IsValidIndex(elem) || ( m_Size == 0 ) || ( num == 0 )); + int numToMove = m_Size - elem - num; + if ((numToMove > 0) && (num > 0)) + { + memmove( &Element(elem), &Element(elem+num), numToMove * sizeof(T) ); + +#ifdef _DEBUG + memset( &Element(m_Size-num), 0xDD, num * sizeof(T) ); +#endif + } +} + + +//----------------------------------------------------------------------------- +// Adds an element, uses default constructor +//----------------------------------------------------------------------------- +template< typename T, class A > +inline int CUtlArray::AddToHead() +{ + return InsertBefore(0); +} + +template< typename T, class A > +inline int CUtlArray::AddToTail() +{ + return InsertBefore( m_Size ); +} + +template< typename T, class A > +inline int CUtlArray::InsertAfter( int elem ) +{ + return InsertBefore( elem + 1 ); +} + +template< typename T, class A > +int CUtlArray::InsertBefore( int elem ) +{ + // Can insert at the end + assert( (elem == Count()) || IsValidIndex(elem) ); + + GrowVector(); + ShiftElementsRight(elem); + Construct( &Element(elem) ); + return elem; +} + + +//----------------------------------------------------------------------------- +// Adds an element, uses copy constructor +//----------------------------------------------------------------------------- +template< typename T, class A > +inline int CUtlArray::AddToHead( const T& src ) +{ + // Can't insert something that's in the list... reallocation may hose us + assert( (Base() == NULL) || (&src < Base()) || (&src >= (Base() + Count()) ) ); + return InsertBefore( 0, src ); +} + +template< typename T, class A > +inline int CUtlArray::AddToTail( const T& src ) +{ + // Can't insert something that's in the list... reallocation may hose us + assert( (Base() == NULL) || (&src < Base()) || (&src >= (Base() + Count()) ) ); + return InsertBefore( m_Size, src ); +} + +template< typename T, class A > +inline int CUtlArray::InsertAfter( int elem, const T& src ) +{ + // Can't insert something that's in the list... reallocation may hose us + assert( (Base() == NULL) || (&src < Base()) || (&src >= (Base() + Count()) ) ); + return InsertBefore( elem + 1, src ); +} + +template< typename T, class A > +int CUtlArray::InsertBefore( int elem, const T& src ) +{ + // Can't insert something that's in the list... reallocation may hose us + assert( (Base() == NULL) || (&src < Base()) || (&src >= (Base() + Count()) ) ); + + // Can insert at the end + assert( (elem == Count()) || IsValidIndex(elem) ); + + GrowVector(); + ShiftElementsRight(elem); + CopyConstruct( &Element(elem), src ); + return elem; +} + + +//----------------------------------------------------------------------------- +// Adds multiple elements, uses default constructor +//----------------------------------------------------------------------------- +template< typename T, class A > +inline int CUtlArray::AddMultipleToHead( int num ) +{ + return InsertMultipleBefore( 0, num ); +} + +template< typename T, class A > +inline int CUtlArray::AddMultipleToTail( int num, const T *pToCopy ) +{ + // Can't insert something that's in the list... reallocation may hose us + assert( (Base() == NULL) || !pToCopy || (pToCopy + num < Base()) || (pToCopy >= (Base() + Count()) ) ); + + return InsertMultipleBefore( m_Size, num, pToCopy ); +} + +template< typename T, class A > +int CUtlArray::InsertMultipleAfter( int elem, int num ) +{ + return InsertMultipleBefore( elem + 1, num ); +} + + +template< typename T, class A > +void CUtlArray::SetCount( int count ) +{ + RemoveAll(); + AddMultipleToTail( count ); +} + +template< typename T, class A > +inline void CUtlArray::SetSize( int size ) +{ + SetCount( size ); +} + +template< typename T, class A > +void CUtlArray::CopyArray( const T *pArray, int size ) +{ + // Can't insert something that's in the list... reallocation may hose us + assert( (Base() == NULL) || !pArray || (Base() >= (pArray + size)) || (pArray >= (Base() + Count()) ) ); + + SetSize( size ); + for( int i=0; i < size; i++ ) + { + (*this)[i] = pArray[i]; + } +} + +template< typename T, class A > +void CUtlArray::Swap( CUtlArray< T, A > &vec ) +{ + m_Memory.Swap( vec.m_Memory ); + swap( m_Size, vec.m_Size ); + swap( m_pElements, vec.m_pElements ); +} + +template< typename T, class A > +int CUtlArray::AddVectorToTail( CUtlArray const &src ) +{ + assert( &src != this ); + + int base = Count(); + + // Make space. + AddMultipleToTail( src.Count() ); + + // Copy the elements. + for ( int i=0; i < src.Count(); i++ ) + { + (*this)[base + i] = src[i]; + } + + return base; +} + +template< typename T, class A > +inline int CUtlArray::InsertMultipleBefore( int elem, int num, const T *pToInsert ) +{ + if( num == 0 ) + return elem; + + // Can insert at the end + assert( (elem == Count()) || IsValidIndex(elem) ); + + GrowVector(num); + ShiftElementsRight(elem, num); + + // Invoke default constructors + for (int i = 0; i < num; ++i) + Construct( &Element(elem+i) ); + + // Copy stuff in? + if ( pToInsert ) + { + for ( int i=0; i < num; i++ ) + { + Element( elem+i ) = pToInsert[i]; + } + } + + return elem; +} + + +//----------------------------------------------------------------------------- +// Finds an element (element needs operator== defined) +//----------------------------------------------------------------------------- +template< typename T, class A > +int CUtlArray::Find( const T& src ) const +{ + for ( int i = 0; i < Count(); ++i ) + { + if (Element(i) == src) + return i; + } + return -1; +} + +template< typename T, class A > +bool CUtlArray::HasElement( const T& src ) const +{ + return ( Find(src) >= 0 ); +} + + +//----------------------------------------------------------------------------- +// Element removal +//----------------------------------------------------------------------------- +template< typename T, class A > +void CUtlArray::FastRemove( int elem ) +{ + assert( IsValidIndex(elem) ); + + Destruct( &Element(elem) ); + if (m_Size > 0) + { + memcpy( &Element(elem), &Element(m_Size-1), sizeof(T) ); + --m_Size; + } +} + +template< typename T, class A > +void CUtlArray::Remove( int elem ) +{ + Destruct( &Element(elem) ); + ShiftElementsLeft(elem); + --m_Size; +} + +template< typename T, class A > +bool CUtlArray::FindAndRemove( const T& src ) +{ + int elem = Find( src ); + if ( elem != -1 ) + { + Remove( elem ); + return true; + } + return false; +} + +template< typename T, class A > +void CUtlArray::RemoveMultiple( int elem, int num ) +{ + assert( elem >= 0 ); + assert( elem + num <= Count() ); + + for (int i = elem + num; --i >= elem; ) + Destruct(&Element(i)); + + ShiftElementsLeft(elem, num); + m_Size -= num; +} + +template< typename T, class A > +void CUtlArray::RemoveAll() +{ + for (int i = m_Size; --i >= 0; ) + { + Destruct(&Element(i)); + } + + m_Size = 0; +} + + +//----------------------------------------------------------------------------- +// Memory deallocation +//----------------------------------------------------------------------------- + +template< typename T, class A > +inline void CUtlArray::Purge() +{ + RemoveAll(); + m_Memory.Purge(); + ResetDbgInfo(); +} + + +template< typename T, class A > +inline void CUtlArray::PurgeAndDeleteElements() +{ + for( int i=0; i < m_Size; i++ ) + { + delete Element(i); + } + Purge(); +} + +template< typename T, class A > +inline void CUtlArray::Compact() +{ + m_Memory.Purge(m_Size); +} + +template< typename T, class A > +inline int CUtlArray::NumAllocated() const +{ + return m_Memory.NumAllocated(); +} + +#endif // CCVECTOR_H \ No newline at end of file diff --git a/game_shared/utlblockmemory.h b/game_shared/utlblockmemory.h new file mode 100644 index 0000000..b7cb962 --- /dev/null +++ b/game_shared/utlblockmemory.h @@ -0,0 +1,319 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +// +// A growable memory class. +//===========================================================================// + +#ifndef UTLBLOCKMEMORY_H +#define UTLBLOCKMEMORY_H + +#ifdef _WIN32 +#pragma once +#endif + +#pragma warning (disable:4100) +#pragma warning (disable:4514) + +//----------------------------------------------------------------------------- +// The CUtlBlockMemory class: +// A growable memory class that allocates non-sequential blocks, but is indexed sequentially +//----------------------------------------------------------------------------- +template< class T, class I > +class CUtlBlockMemory +{ +public: + // constructor, destructor + CUtlBlockMemory( int nGrowSize = 0, int nInitSize = 0 ); + ~CUtlBlockMemory(); + + // Set the size by which the memory grows - round up to the next power of 2 + void Init( int nGrowSize = 0, int nInitSize = 0 ); + + // here to match CUtlMemory, but only used by ResetDbgInfo, so it can just return NULL + T* Base() { return NULL; } + const T* Base() const { return NULL; } + + class Iterator_t + { + public: + Iterator_t( I i ) : index( i ) {} + I index; + + bool operator==( const Iterator_t it ) const { return index == it.index; } + bool operator!=( const Iterator_t it ) const { return index != it.index; } + }; + Iterator_t First() const { return Iterator_t( IsIdxValid( 0 ) ? 0 : InvalidIndex() ); } + Iterator_t Next( const Iterator_t &it ) const { return Iterator_t( IsIdxValid( it.index + 1 ) ? it.index + 1 : InvalidIndex() ); } + I GetIndex( const Iterator_t &it ) const { return it.index; } + bool IsIdxAfter( I i, const Iterator_t &it ) const { return i > it.index; } + bool IsValidIterator( const Iterator_t &it ) const { return IsIdxValid( it.index ); } + Iterator_t InvalidIterator() const { return Iterator_t( InvalidIndex() ); } + + // element access + T& operator[]( I i ); + const T& operator[]( I i ) const; + T& Element( I i ); + const T& Element( I i ) const; + + // Can we use this index? + bool IsIdxValid( I i ) const; + static I InvalidIndex() { return ( I )-1; } + + void Swap( CUtlBlockMemory< T, I > &mem ); + + // Size + int NumAllocated() const; + int Count() const { return NumAllocated(); } + + // Grows memory by max(num,growsize) rounded up to the next power of 2, and returns the allocation index/ptr + void Grow( int num = 1 ); + + // Makes sure we've got at least this much memory + void EnsureCapacity( int num ); + + // Memory deallocation + void Purge(); + + // Purge all but the given number of elements + void Purge( int numElements ); + +protected: + int Index( int major, int minor ) const { return ( major << m_nIndexShift ) | minor; } + int MajorIndex( int i ) const { return i >> m_nIndexShift; } + int MinorIndex( int i ) const { return i & m_nIndexMask; } + void ChangeSize( int nBlocks ); + int NumElementsInBlock() const { return m_nIndexMask + 1; } + + T** m_pMemory; + int m_nBlocks; + int m_nIndexMask : 27; + int m_nIndexShift : 5; +}; + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- + +template< class T, class I > +CUtlBlockMemory::CUtlBlockMemory( int nGrowSize, int nInitAllocationCount ) +: m_pMemory( 0 ), m_nBlocks( 0 ), m_nIndexMask( 0 ), m_nIndexShift( 0 ) +{ + Init( nGrowSize, nInitAllocationCount ); +} + +template< class T, class I > +CUtlBlockMemory::~CUtlBlockMemory() +{ + Purge(); +} + + +//----------------------------------------------------------------------------- +// Fast swap +//----------------------------------------------------------------------------- +template< class T, class I > +void CUtlBlockMemory::Swap( CUtlBlockMemory< T, I > &mem ) +{ + swap( m_pMemory, mem.m_pMemory ); + swap( m_nBlocks, mem.m_nBlocks ); + swap( m_nIndexMask, mem.m_nIndexMask ); + swap( m_nIndexShift, mem.m_nIndexShift ); +} + + +//----------------------------------------------------------------------------- +// Set the size by which the memory grows - round up to the next power of 2 +//----------------------------------------------------------------------------- +template< class T, class I > +void CUtlBlockMemory::Init( int nGrowSize /* = 0 */, int nInitSize /* = 0 */ ) +{ + Purge(); + + if ( nGrowSize == 0) + { + // default grow size is smallest size s.t. c++ allocation overhead is ~6% of block size + nGrowSize = ( 127 + sizeof( T ) ) / sizeof( T ); + } + nGrowSize = SmallestPowerOfTwoGreaterOrEqual( nGrowSize ); + m_nIndexMask = nGrowSize - 1; + + m_nIndexShift = 0; + while ( nGrowSize > 1 ) + { + nGrowSize >>= 1; + ++m_nIndexShift; + } + assert( m_nIndexMask + 1 == ( 1 << m_nIndexShift ) ); + + Grow( nInitSize ); +} + + +//----------------------------------------------------------------------------- +// element access +//----------------------------------------------------------------------------- +template< class T, class I > +inline T& CUtlBlockMemory::operator[]( I i ) +{ + assert( IsIdxValid(i) ); + T *pBlock = m_pMemory[ MajorIndex( i ) ]; + return pBlock[ MinorIndex( i ) ]; +} + +template< class T, class I > +inline const T& CUtlBlockMemory::operator[]( I i ) const +{ + assert( IsIdxValid(i) ); + const T *pBlock = m_pMemory[ MajorIndex( i ) ]; + return pBlock[ MinorIndex( i ) ]; +} + +template< class T, class I > +inline T& CUtlBlockMemory::Element( I i ) +{ + assert( IsIdxValid(i) ); + T *pBlock = m_pMemory[ MajorIndex( i ) ]; + return pBlock[ MinorIndex( i ) ]; +} + +template< class T, class I > +inline const T& CUtlBlockMemory::Element( I i ) const +{ + assert( IsIdxValid(i) ); + const T *pBlock = m_pMemory[ MajorIndex( i ) ]; + return pBlock[ MinorIndex( i ) ]; +} + + +//----------------------------------------------------------------------------- +// Size +//----------------------------------------------------------------------------- +template< class T, class I > +inline int CUtlBlockMemory::NumAllocated() const +{ + return m_nBlocks * NumElementsInBlock(); +} + + +//----------------------------------------------------------------------------- +// Is element index valid? +//----------------------------------------------------------------------------- +template< class T, class I > +inline bool CUtlBlockMemory::IsIdxValid( I i ) const +{ + return ( i >= 0 ) && ( MajorIndex( i ) < m_nBlocks ); +} + +template< class T, class I > +void CUtlBlockMemory::Grow( int num ) +{ + if ( num <= 0 ) + return; + + int nBlockSize = NumElementsInBlock(); + int nBlocks = ( num + nBlockSize - 1 ) / nBlockSize; + + ChangeSize( m_nBlocks + nBlocks ); +} + +template< class T, class I > +void CUtlBlockMemory::ChangeSize( int nBlocks ) +{ + int nBlocksOld = m_nBlocks; + m_nBlocks = nBlocks; + + // free old blocks if shrinking + for ( int i = m_nBlocks; i < nBlocksOld; ++i ) + { + free( (void*)m_pMemory[ i ] ); + } + + if ( m_pMemory ) + { + m_pMemory = (T**)realloc( m_pMemory, m_nBlocks * sizeof(T*) ); + assert( m_pMemory ); + } + else + { + m_pMemory = (T**)malloc( m_nBlocks * sizeof(T*) ); + assert( m_pMemory ); + } + + if ( !m_pMemory ) + { + Error( "CUtlBlockMemory overflow!\n" ); + } + + // allocate new blocks if growing + int nBlockSize = NumElementsInBlock(); + for ( int i = nBlocksOld; i < m_nBlocks; ++i ) + { + m_pMemory[ i ] = (T*)malloc( nBlockSize * sizeof( T ) ); + assert( m_pMemory[ i ] ); + } +} + + +//----------------------------------------------------------------------------- +// Makes sure we've got at least this much memory +//----------------------------------------------------------------------------- +template< class T, class I > +inline void CUtlBlockMemory::EnsureCapacity( int num ) +{ + Grow( num - NumAllocated() ); +} + + +//----------------------------------------------------------------------------- +// Memory deallocation +//----------------------------------------------------------------------------- +template< class T, class I > +void CUtlBlockMemory::Purge() +{ + if ( !m_pMemory ) + return; + + for ( int i = 0; i < m_nBlocks; ++i ) + { + free( (void*)m_pMemory[ i ] ); + } + m_nBlocks = 0; + + free( (void*)m_pMemory ); + m_pMemory = 0; +} + +template< class T, class I > +void CUtlBlockMemory::Purge( int numElements ) +{ + assert( numElements >= 0 ); + + int nAllocated = NumAllocated(); + if ( numElements > nAllocated ) + { + // Ensure this isn't a grow request in disguise. + assert( numElements <= nAllocated ); + return; + } + + if ( numElements <= 0 ) + { + Purge(); + return; + } + + int nBlockSize = NumElementsInBlock(); + int nBlocksOld = m_nBlocks; + int nBlocks = ( numElements + nBlockSize - 1 ) / nBlockSize; + + // If the number of blocks is the same as the allocated number of blocks, we are done. + if ( nBlocks == m_nBlocks ) + return; + + ChangeSize( nBlocks ); +} + +#endif // UTLBLOCKMEMORY_H \ No newline at end of file diff --git a/game_shared/utllinkedlist.h b/game_shared/utllinkedlist.h new file mode 100644 index 0000000..c306e69 --- /dev/null +++ b/game_shared/utllinkedlist.h @@ -0,0 +1,688 @@ +//======== (C) Copyright 1999, 2000 Valve, L.L.C. All rights reserved. ======== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: Linked list container class +// +// $Revision: $ +// $NoKeywords: $ +//============================================================================= + +#ifndef UTLLINKEDLIST_H +#define UTLLINKEDLIST_H + +#ifdef _WIN32 +#pragma once +#endif + +#include +#include "utlmemory.h" + + +// This is a useful macro to iterate from head to tail in a linked list. +#define FOR_EACH_LL( listName, iteratorName ) \ + for( int iteratorName=listName.Head(); iteratorName != listName.InvalidIndex(); iteratorName = listName.Next( iteratorName ) ) + +#define INVALID_LLIST_IDX ((I)~0) + +//----------------------------------------------------------------------------- +// class CUtlLinkedList: +// description: +// A lovely index-based linked list! T is the class type, I is the index +// type, which usually should be an unsigned short or smaller. +//----------------------------------------------------------------------------- + +template +class CUtlLinkedList +{ +public: + typedef T ElemType_t; + typedef I IndexType_t; + + // constructor, destructor + CUtlLinkedList( int growSize = 0, int initSize = 0 ); + CUtlLinkedList( void *pMemory, int memsize ); + ~CUtlLinkedList( ); + + // gets particular elements + T& Element( I i ); + T const& Element( I i ) const; + T& operator[]( I i ); + T const& operator[]( I i ) const; + + // Make sure we have a particular amount of memory + void EnsureCapacity( int num ); + + // Memory deallocation + void Purge(); + + // Delete all the elements then call Purge. + void PurgeAndDeleteElements(); + + // Insertion methods.... + I InsertBefore( I before ); + I InsertAfter( I after ); + I AddToHead( ); + I AddToTail( ); + + I InsertBefore( I before, T const& src ); + I InsertAfter( I after, T const& src ); + I AddToHead( T const& src ); + I AddToTail( T const& src ); + + // Find an element and return its index or InvalidIndex() if it couldn't be found. + I Find( const T &src ) const; + + // Look for the element. If it exists, remove it and return true. Otherwise, return false. + bool FindAndRemove( const T &src ); + + // Removal methods + void Remove( I elem ); + void RemoveAll(); + + // Allocation/deallocation methods + // If multilist == true, then list list may contain many + // non-connected lists, and IsInList and Head + Tail are meaningless... + I Alloc( bool multilist = false ); + void Free( I elem ); + + // list modification + void LinkBefore( I before, I elem ); + void LinkAfter( I after, I elem ); + void Unlink( I elem ); + void LinkToHead( I elem ); + void LinkToTail( I elem ); + + // invalid index + inline static I InvalidIndex() { return INVALID_LLIST_IDX; } + inline static size_t ElementSize() { return sizeof(ListElem_t); } + + // list statistics + int Count() const; + I MaxElementIndex() const; + + // Traversing the list + I Head() const; + I Tail() const; + I Previous( I i ) const; + I Next( I i ) const; + + // Are nodes in the list or valid? + bool IsValidIndex( I i ) const; + bool IsInList( I i ) const; + +protected: + // What the linked list element looks like + struct ListElem_t + { + T m_Element; + I m_Previous; + I m_Next; + + private: + // No copy constructor for these... + ListElem_t( const ListElem_t& ); + }; + + // constructs the class + I AllocInternal( bool multilist = false ); + void ConstructList(); + + // Gets at the list element.... + ListElem_t& InternalElement( I i ) { return m_Memory[i]; } + ListElem_t const& InternalElement( I i ) const { return m_Memory[i]; } + + void ResetDbgInfo() + { + m_pElements = m_Memory.Base(); + } + + // copy constructors not allowed + CUtlLinkedList( CUtlLinkedList const& list ) { assert(0); } + + CUtlMemory m_Memory; + I m_Head; + I m_Tail; + I m_FirstFree; + I m_ElementCount; // The number actually in the list + I m_TotalElements; // The number allocated + + // For debugging purposes; + // it's in release builds so this can be used in libraries correctly + ListElem_t *m_pElements; +}; + + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- + +template +CUtlLinkedList::CUtlLinkedList( int growSize, int initSize ) : + m_Memory(growSize, initSize) +{ + ConstructList(); + ResetDbgInfo(); +} + +template +CUtlLinkedList::CUtlLinkedList( void* pMemory, int memsize ) : + m_Memory((ListElem_t *)pMemory, memsize/sizeof(ListElem_t)) +{ + ConstructList(); + ResetDbgInfo(); +} + +template +CUtlLinkedList::~CUtlLinkedList( ) +{ + RemoveAll(); +} + +template +void CUtlLinkedList::ConstructList() +{ + m_Head = InvalidIndex(); + m_Tail = InvalidIndex(); + m_FirstFree = InvalidIndex(); + m_ElementCount = m_TotalElements = 0; +} + + +//----------------------------------------------------------------------------- +// gets particular elements +//----------------------------------------------------------------------------- + +template +inline T& CUtlLinkedList::Element( I i ) +{ + return m_Memory[i].m_Element; +} + +template +inline T const& CUtlLinkedList::Element( I i ) const +{ + return m_Memory[i].m_Element; +} + +template +inline T& CUtlLinkedList::operator[]( I i ) +{ + return m_Memory[i].m_Element; +} + +template +inline T const& CUtlLinkedList::operator[]( I i ) const +{ + return m_Memory[i].m_Element; +} + +//----------------------------------------------------------------------------- +// list statistics +//----------------------------------------------------------------------------- + +template +inline int CUtlLinkedList::Count() const +{ + return m_ElementCount; +} + +template +inline I CUtlLinkedList::MaxElementIndex() const +{ + return m_Memory.NumAllocated(); +} + + +//----------------------------------------------------------------------------- +// Traversing the list +//----------------------------------------------------------------------------- + +template +inline I CUtlLinkedList::Head() const +{ + return m_Head; +} + +template +inline I CUtlLinkedList::Tail() const +{ + return m_Tail; +} + +template +inline I CUtlLinkedList::Previous( I i ) const +{ + assert( IsValidIndex(i) ); + return InternalElement(i).m_Previous; +} + +template +inline I CUtlLinkedList::Next( I i ) const +{ + assert( IsValidIndex(i) ); + return InternalElement(i).m_Next; +} + + +//----------------------------------------------------------------------------- +// Are nodes in the list or valid? +//----------------------------------------------------------------------------- + +template +inline bool CUtlLinkedList::IsValidIndex( I i ) const +{ + return (i < m_TotalElements) && (i >= 0) && + ((m_Memory[i].m_Previous != i) || (m_Memory[i].m_Next == i)); +} + +template +inline bool CUtlLinkedList::IsInList( I i ) const +{ + return (i < m_TotalElements) && (i >= 0) && (Previous(i) != i); +} + +//----------------------------------------------------------------------------- +// Makes sure we have enough memory allocated to store a requested # of elements +//----------------------------------------------------------------------------- + +template< class T, class I > +void CUtlLinkedList::EnsureCapacity( int num ) +{ + m_Memory.EnsureCapacity(num); + ResetDbgInfo(); +} + + +//----------------------------------------------------------------------------- +// Deallocate memory +//----------------------------------------------------------------------------- + +template +void CUtlLinkedList::Purge() +{ + RemoveAll(); + m_Memory.Purge( ); + m_FirstFree = InvalidIndex(); + m_TotalElements = 0; + ResetDbgInfo(); + +} + + +template +void CUtlLinkedList::PurgeAndDeleteElements() +{ + int iNext; + for( int i=Head(); i != InvalidIndex(); i=iNext ) + { + iNext = Next(i); + delete Element(i); + } + + Purge(); +} + + +//----------------------------------------------------------------------------- +// Node allocation/deallocation +//----------------------------------------------------------------------------- +template +I CUtlLinkedList::AllocInternal( bool multilist ) +{ + I elem; + if (m_FirstFree == InvalidIndex()) + { + // Nothing in the free list; add. + // Since nothing is in the free list, m_TotalElements == total # of elements + // the list knows about. + if (m_TotalElements == m_Memory.NumAllocated()) + m_Memory.Grow(); + + assert( m_TotalElements != InvalidIndex() ); + + elem = (I)m_TotalElements; + ++m_TotalElements; + + assert( elem != InvalidIndex() ); + } + else + { + elem = m_FirstFree; + m_FirstFree = InternalElement(m_FirstFree).m_Next; + } + + if (!multilist) + InternalElement(elem).m_Next = InternalElement(elem).m_Previous = elem; + else + InternalElement(elem).m_Next = InternalElement(elem).m_Previous = InvalidIndex(); + + ResetDbgInfo(); + + return elem; +} + +template +I CUtlLinkedList::Alloc( bool multilist ) +{ + I elem = AllocInternal( multilist ); + Construct( &Element(elem) ); + + return elem; +} + +template +void CUtlLinkedList::Free( I elem ) +{ + assert( IsValidIndex(elem) ); + Unlink(elem); + + ListElem_t &internalElem = InternalElement(elem); + Destruct( &internalElem.m_Element ); + internalElem.m_Next = m_FirstFree; + m_FirstFree = elem; +} + +//----------------------------------------------------------------------------- +// Insertion methods; allocates and links (uses default constructor) +//----------------------------------------------------------------------------- + +template +I CUtlLinkedList::InsertBefore( I before ) +{ + // Make a new node + I newNode = AllocInternal(); + + // Link it in + LinkBefore( before, newNode ); + + // Construct the data + Construct( &Element(newNode) ); + + return newNode; +} + +template +I CUtlLinkedList::InsertAfter( I after ) +{ + // Make a new node + I newNode = AllocInternal(); + + // Link it in + LinkAfter( after, newNode ); + + // Construct the data + Construct( &Element(newNode) ); + + return newNode; +} + +template +inline I CUtlLinkedList::AddToHead( ) +{ + return InsertAfter( InvalidIndex() ); +} + +template +inline I CUtlLinkedList::AddToTail( ) +{ + return InsertBefore( InvalidIndex() ); +} + + +//----------------------------------------------------------------------------- +// Insertion methods; allocates and links (uses copy constructor) +//----------------------------------------------------------------------------- + +template +I CUtlLinkedList::InsertBefore( I before, T const& src ) +{ + // Make a new node + I newNode = AllocInternal(); + + // Link it in + LinkBefore( before, newNode ); + + // Construct the data + CopyConstruct( &Element(newNode), src ); + + return newNode; +} + +template +I CUtlLinkedList::InsertAfter( I after, T const& src ) +{ + // Make a new node + I newNode = AllocInternal(); + + // Link it in + LinkAfter( after, newNode ); + + // Construct the data + CopyConstruct( &Element(newNode), src ); + + return newNode; +} + +template +inline I CUtlLinkedList::AddToHead( T const& src ) +{ + return InsertAfter( InvalidIndex(), src ); +} + +template +inline I CUtlLinkedList::AddToTail( T const& src ) +{ + return InsertBefore( InvalidIndex(), src ); +} + + +//----------------------------------------------------------------------------- +// Removal methods +//----------------------------------------------------------------------------- + +template +I CUtlLinkedList::Find( const T &src ) const +{ + for ( I i=Head(); i != InvalidIndex(); i = Next( i ) ) + { + if ( Element( i ) == src ) + return i; + } + return InvalidIndex(); +} + + +template +bool CUtlLinkedList::FindAndRemove( const T &src ) +{ + I i = Find( src ); + if ( i == InvalidIndex() ) + { + return false; + } + else + { + Remove( i ); + return true; + } +} + + +template +void CUtlLinkedList::Remove( I elem ) +{ + Free( elem ); +} + +template +void CUtlLinkedList::RemoveAll() +{ + if (m_TotalElements == 0) + return; + + // Put everything into the free list + I prev = InvalidIndex(); + for (int i = (int)m_TotalElements; --i >= 0; ) + { + // Invoke the destructor + if (IsValidIndex((I)i)) + Destruct( &Element((I)i) ); + + // next points to the next free list item + InternalElement((I)i).m_Next = prev; + + // Indicates it's in the free list + InternalElement((I)i).m_Previous = (I)i; + prev = (I)i; + } + + // First free points to the first element + m_FirstFree = 0; + + // Clear everything else out + m_Head = InvalidIndex(); + m_Tail = InvalidIndex(); + m_ElementCount = 0; +} + + +//----------------------------------------------------------------------------- +// list modification +//----------------------------------------------------------------------------- + +template +void CUtlLinkedList::LinkBefore( I before, I elem ) +{ + assert( IsValidIndex(elem) ); + + // Unlink it if it's in the list at the moment + Unlink(elem); + + ListElem_t& newElem = InternalElement(elem); + + // The element *after* our newly linked one is the one we linked before. + newElem.m_Next = before; + + if (before == InvalidIndex()) + { + // In this case, we're linking to the end of the list, so reset the tail + newElem.m_Previous = m_Tail; + m_Tail = elem; + } + else + { + // Here, we're not linking to the end. Set the prev pointer to point to + // the element we're linking. + assert( IsInList(before) ); + ListElem_t& beforeElem = InternalElement(before); + newElem.m_Previous = beforeElem.m_Previous; + beforeElem.m_Previous = elem; + } + + // Reset the head if we linked to the head of the list + if (newElem.m_Previous == InvalidIndex()) + m_Head = elem; + else + InternalElement(newElem.m_Previous).m_Next = elem; + + // one more element baby + ++m_ElementCount; +} + +template +void CUtlLinkedList::LinkAfter( I after, I elem ) +{ + assert( IsValidIndex(elem) ); + + // Unlink it if it's in the list at the moment + if ( IsInList(elem) ) + Unlink(elem); + + ListElem_t& newElem = InternalElement(elem); + + // The element *before* our newly linked one is the one we linked after + newElem.m_Previous = after; + if (after == InvalidIndex()) + { + // In this case, we're linking to the head of the list, reset the head + newElem.m_Next = m_Head; + m_Head = elem; + } + else + { + // Here, we're not linking to the end. Set the next pointer to point to + // the element we're linking. + assert( IsInList(after) ); + ListElem_t& afterElem = InternalElement(after); + newElem.m_Next = afterElem.m_Next; + afterElem.m_Next = elem; + } + + // Reset the tail if we linked to the tail of the list + if (newElem.m_Next == InvalidIndex()) + m_Tail = elem; + else + InternalElement(newElem.m_Next).m_Previous = elem; + + // one more element baby + ++m_ElementCount; +} + +template +void CUtlLinkedList::Unlink( I elem ) +{ + assert( IsValidIndex(elem) ); + if (IsInList(elem)) + { + ListElem_t *pBase = m_Memory.Base(); + ListElem_t *pOldElem = &pBase[elem]; + + // If we're the first guy, reset the head + // otherwise, make our previous node's next pointer = our next + if ( pOldElem->m_Previous != INVALID_LLIST_IDX ) + { + pBase[pOldElem->m_Previous].m_Next = pOldElem->m_Next; + } + else + { + m_Head = pOldElem->m_Next; + } + + // If we're the last guy, reset the tail + // otherwise, make our next node's prev pointer = our prev + if ( pOldElem->m_Next != INVALID_LLIST_IDX ) + { + pBase[pOldElem->m_Next].m_Previous = pOldElem->m_Previous; + } + else + { + m_Tail = pOldElem->m_Previous; + } + + // This marks this node as not in the list, + // but not in the free list either + pOldElem->m_Previous = pOldElem->m_Next = elem; + + // One less puppy + --m_ElementCount; + } +} + +template +inline void CUtlLinkedList::LinkToHead( I elem ) +{ + LinkAfter( InvalidIndex(), elem ); +} + +template +inline void CUtlLinkedList::LinkToTail( I elem ) +{ + LinkBefore( InvalidIndex(), elem ); +} + + +#endif // UTLLINKEDLIST_H diff --git a/game_shared/utlmemory.h b/game_shared/utlmemory.h new file mode 100644 index 0000000..db61524 --- /dev/null +++ b/game_shared/utlmemory.h @@ -0,0 +1,914 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +// +// A growable memory class. +//===========================================================================// + +#ifndef UTLMEMORY_H +#define UTLMEMORY_H + +#ifdef _WIN32 +#pragma once +#endif + +#include +#include +#include + +#define ALIGN_VALUE( val, alignment ) (( val + alignment - 1 ) & ~( alignment - 1 )) +#define stackalloc( _size ) _alloca( ALIGN_VALUE( _size, 16 ) ) +#define stackfree( _p ) 0 + +// Swap two of anything. +template +_forceinline void swap( T& x, T& y ) +{ + T temp = x; + x = y; + y = temp; +} + +template +inline T AlignValue( T val, unsigned alignment ) +{ + return (T)( ( (unsigned int)val + alignment - 1 ) & ~( alignment - 1 ) ); +} + +//----------------------------------------------------------------------------- +// Methods to invoke the constructor, copy constructor, and destructor +//----------------------------------------------------------------------------- + +template +inline void Construct( T* pMemory ) +{ + new( pMemory ) T; +} + +template +inline void CopyConstruct( T* pMemory, T const& src ) +{ + new( pMemory ) T(src); +} + +template +inline void Destruct( T* pMemory ) +{ + pMemory->~T(); + +#ifdef _DEBUG + memset( pMemory, 0xDD, sizeof(T) ); +#endif +} + +#pragma warning (disable:4100) +#pragma warning (disable:4514) +// identifier was truncated to '255' characters in the debug information +#pragma warning(disable: 4786) + +//----------------------------------------------------------------------------- +// The CUtlMemory class: +// A growable memory class which doubles in size by default. +//----------------------------------------------------------------------------- +template< class T, class I = int > +class CUtlMemory +{ +public: + // constructor, destructor + CUtlMemory( int nGrowSize = 0, int nInitSize = 0 ); + CUtlMemory( T* pMemory, int numElements ); + CUtlMemory( const T* pMemory, int numElements ); + ~CUtlMemory(); + + // Set the size by which the memory grows + void Init( int nGrowSize = 0, int nInitSize = 0 ); + + class Iterator_t + { + public: + Iterator_t( I i ) : index( i ) {} + I index; + + bool operator==( const Iterator_t it ) const { return index == it.index; } + bool operator!=( const Iterator_t it ) const { return index != it.index; } + }; + Iterator_t First() const { return Iterator_t( IsIdxValid( 0 ) ? 0 : InvalidIndex() ); } + Iterator_t Next( const Iterator_t &it ) const { return Iterator_t( IsIdxValid( it.index + 1 ) ? it.index + 1 : InvalidIndex() ); } + I GetIndex( const Iterator_t &it ) const { return it.index; } + bool IsIdxAfter( I i, const Iterator_t &it ) const { return i > it.index; } + bool IsValidIterator( const Iterator_t &it ) const { return IsIdxValid( it.index ); } + Iterator_t InvalidIterator() const { return Iterator_t( InvalidIndex() ); } + + // element access + T& operator[]( I i ); + const T& operator[]( I i ) const; + T& Element( I i ); + const T& Element( I i ) const; + + // Can we use this index? + bool IsIdxValid( I i ) const; + static I InvalidIndex() { return ( I )-1; } + + // Gets the base address (can change when adding elements!) + T* Base(); + const T* Base() const; + + // Attaches the buffer to external memory.... + void SetExternalBuffer( T* pMemory, int numElements ); + void SetExternalBuffer( const T* pMemory, int numElements ); + void AssumeMemory( T *pMemory, int nSize ); + + // Fast swap + void Swap( CUtlMemory< T, I > &mem ); + + // Switches the buffer from an external memory buffer to a reallocatable buffer + // Will copy the current contents of the external buffer to the reallocatable buffer + void ConvertToGrowableMemory( int nGrowSize ); + + // Size + int NumAllocated() const; + int Count() const; + + // Grows the memory, so that at least allocated + num elements are allocated + void Grow( int num = 1 ); + + // Makes sure we've got at least this much memory + void EnsureCapacity( int num ); + + // Memory deallocation + void Purge(); + + // Purge all but the given number of elements + void Purge( int numElements ); + + // is the memory externally allocated? + bool IsExternallyAllocated() const; + + // is the memory read only? + bool IsReadOnly() const; + + // Set the size by which the memory grows + void SetGrowSize( int size ); + +protected: + void ValidateGrowSize() + { +#ifdef _X360 + if ( m_nGrowSize && m_nGrowSize != EXTERNAL_BUFFER_MARKER ) + { + // Max grow size at 128 bytes on XBOX + const int MAX_GROW = 128; + if ( m_nGrowSize * sizeof(T) > MAX_GROW ) + { + m_nGrowSize = max( 1, MAX_GROW / sizeof(T) ); + } + } +#endif + } + + enum + { + EXTERNAL_BUFFER_MARKER = -1, + EXTERNAL_CONST_BUFFER_MARKER = -2, + }; + + T* m_pMemory; + int m_nAllocationCount; + int m_nGrowSize; +}; + + +//----------------------------------------------------------------------------- +// The CUtlMemory class: +// A growable memory class which doubles in size by default. +//----------------------------------------------------------------------------- +template< class T, size_t SIZE, class I = int > +class CUtlMemoryFixedGrowable : public CUtlMemory< T, I > +{ + typedef CUtlMemory< T, I > BaseClass; + +public: + CUtlMemoryFixedGrowable( int nGrowSize = 0, int nInitSize = SIZE ) : BaseClass( m_pFixedMemory, SIZE ) + { + assert( nInitSize == 0 || nInitSize == SIZE ); + m_nMallocGrowSize = nGrowSize; + } + + void Grow( int nCount = 1 ) + { + if ( IsExternallyAllocated() ) + { + ConvertToGrowableMemory( m_nMallocGrowSize ); + } + BaseClass::Grow( nCount ); + } + + void EnsureCapacity( int num ) + { + if ( CUtlMemory::m_nAllocationCount >= num ) + return; + + if ( IsExternallyAllocated() ) + { + // Can't grow a buffer whose memory was externally allocated + ConvertToGrowableMemory( m_nMallocGrowSize ); + } + + BaseClass::EnsureCapacity( num ); + } + +private: + int m_nMallocGrowSize; + T m_pFixedMemory[ SIZE ]; +}; + +//----------------------------------------------------------------------------- +// The CUtlMemoryFixed class: +// A fixed memory class +//----------------------------------------------------------------------------- +template< typename T, size_t SIZE, int nAlignment = 0 > +class CUtlMemoryFixed +{ +public: + // constructor, destructor + CUtlMemoryFixed( int nGrowSize = 0, int nInitSize = 0 ) { assert( nInitSize == 0 || nInitSize == SIZE ); } + CUtlMemoryFixed( T* pMemory, int numElements ) { assert( 0 ); } + + // Can we use this index? + bool IsIdxValid( int i ) const { return (i >= 0) && (i < SIZE); } + static int InvalidIndex() { return -1; } + + // Gets the base address + T* Base() { if ( nAlignment == 0 ) return (T*)(&m_Memory[0]); else return (T*)AlignValue( &m_Memory[0], nAlignment ); } + const T* Base() const { if ( nAlignment == 0 ) return (T*)(&m_Memory[0]); else return (T*)AlignValue( &m_Memory[0], nAlignment ); } + + // element access + T& operator[]( int i ) { assert( IsIdxValid(i) ); return Base()[i]; } + const T& operator[]( int i ) const { assert( IsIdxValid(i) ); return Base()[i]; } + T& Element( int i ) { assert( IsIdxValid(i) ); return Base()[i]; } + const T& Element( int i ) const { assert( IsIdxValid(i) ); return Base()[i]; } + + // Attaches the buffer to external memory.... + void SetExternalBuffer( T* pMemory, int numElements ) { assert( 0 ); } + + // Size + int NumAllocated() const { return SIZE; } + int Count() const { return SIZE; } + + // Grows the memory, so that at least allocated + num elements are allocated + void Grow( int num = 1 ) { assert( 0 ); } + + // Makes sure we've got at least this much memory + void EnsureCapacity( int num ) { assert( num <= SIZE ); } + + // Memory deallocation + void Purge() {} + + // Purge all but the given number of elements (NOT IMPLEMENTED IN CUtlMemoryFixed) + void Purge( int numElements ) { assert( 0 ); } + + // is the memory externally allocated? + bool IsExternallyAllocated() const { return false; } + + // Set the size by which the memory grows + void SetGrowSize( int size ) {} + + class Iterator_t + { + public: + Iterator_t( int i ) : index( i ) {} + int index; + bool operator==( const Iterator_t it ) const { return index == it.index; } + bool operator!=( const Iterator_t it ) const { return index != it.index; } + }; + Iterator_t First() const { return Iterator_t( IsIdxValid( 0 ) ? 0 : InvalidIndex() ); } + Iterator_t Next( const Iterator_t &it ) const { return Iterator_t( IsIdxValid( it.index + 1 ) ? it.index + 1 : InvalidIndex() ); } + int GetIndex( const Iterator_t &it ) const { return it.index; } + bool IsIdxAfter( int i, const Iterator_t &it ) const { return i > it.index; } + bool IsValidIterator( const Iterator_t &it ) const { return IsIdxValid( it.index ); } + Iterator_t InvalidIterator() const { return Iterator_t( InvalidIndex() ); } + +private: + char m_Memory[ SIZE*sizeof(T) + nAlignment ]; +}; + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- + +template< class T, class I > +CUtlMemory::CUtlMemory( int nGrowSize, int nInitAllocationCount ) : m_pMemory(0), + m_nAllocationCount( nInitAllocationCount ), m_nGrowSize( nGrowSize ) +{ + ValidateGrowSize(); + assert( nGrowSize >= 0 ); + if (m_nAllocationCount) + { + m_pMemory = (T*)malloc( m_nAllocationCount * sizeof(T) ); + } +} + +template< class T, class I > +CUtlMemory::CUtlMemory( T* pMemory, int numElements ) : m_pMemory(pMemory), + m_nAllocationCount( numElements ) +{ + // Special marker indicating externally supplied modifyable memory + m_nGrowSize = EXTERNAL_BUFFER_MARKER; +} + +template< class T, class I > +CUtlMemory::CUtlMemory( const T* pMemory, int numElements ) : m_pMemory( (T*)pMemory ), + m_nAllocationCount( numElements ) +{ + // Special marker indicating externally supplied modifyable memory + m_nGrowSize = EXTERNAL_CONST_BUFFER_MARKER; +} + +template< class T, class I > +CUtlMemory::~CUtlMemory() +{ + Purge(); +} + +template< class T, class I > +void CUtlMemory::Init( int nGrowSize /*= 0*/, int nInitSize /*= 0*/ ) +{ + Purge(); + + m_nGrowSize = nGrowSize; + m_nAllocationCount = nInitSize; + ValidateGrowSize(); + assert( nGrowSize >= 0 ); + if (m_nAllocationCount) + { + m_pMemory = (T*)malloc( m_nAllocationCount * sizeof(T) ); + } +} + +//----------------------------------------------------------------------------- +// Fast swap +//----------------------------------------------------------------------------- +template< class T, class I > +void CUtlMemory::Swap( CUtlMemory &mem ) +{ + swap( m_nGrowSize, mem.m_nGrowSize ); + swap( m_pMemory, mem.m_pMemory ); + swap( m_nAllocationCount, mem.m_nAllocationCount ); +} + + +//----------------------------------------------------------------------------- +// Switches the buffer from an external memory buffer to a reallocatable buffer +//----------------------------------------------------------------------------- +template< class T, class I > +void CUtlMemory::ConvertToGrowableMemory( int nGrowSize ) +{ + if ( !IsExternallyAllocated() ) + return; + + m_nGrowSize = nGrowSize; + if (m_nAllocationCount) + { + int nNumBytes = m_nAllocationCount * sizeof(T); + T *pMemory = (T*)malloc( nNumBytes ); + memcpy( pMemory, m_pMemory, nNumBytes ); + m_pMemory = pMemory; + } + else + { + m_pMemory = NULL; + } +} + + +//----------------------------------------------------------------------------- +// Attaches the buffer to external memory.... +//----------------------------------------------------------------------------- +template< class T, class I > +void CUtlMemory::SetExternalBuffer( T* pMemory, int numElements ) +{ + // Blow away any existing allocated memory + Purge(); + + m_pMemory = pMemory; + m_nAllocationCount = numElements; + + // Indicate that we don't own the memory + m_nGrowSize = EXTERNAL_BUFFER_MARKER; +} + +template< class T, class I > +void CUtlMemory::SetExternalBuffer( const T* pMemory, int numElements ) +{ + // Blow away any existing allocated memory + Purge(); + + m_pMemory = const_cast( pMemory ); + m_nAllocationCount = numElements; + + // Indicate that we don't own the memory + m_nGrowSize = EXTERNAL_CONST_BUFFER_MARKER; +} + +template< class T, class I > +void CUtlMemory::AssumeMemory( T* pMemory, int numElements ) +{ + // Blow away any existing allocated memory + Purge(); + + // Simply take the pointer but don't mark us as external + m_pMemory = pMemory; + m_nAllocationCount = numElements; +} + + +//----------------------------------------------------------------------------- +// element access +//----------------------------------------------------------------------------- +template< class T, class I > +inline T& CUtlMemory::operator[]( I i ) +{ + assert( !IsReadOnly() ); + assert( IsIdxValid(i) ); + return m_pMemory[i]; +} + +template< class T, class I > +inline const T& CUtlMemory::operator[]( I i ) const +{ + assert( IsIdxValid(i) ); + return m_pMemory[i]; +} + +template< class T, class I > +inline T& CUtlMemory::Element( I i ) +{ + assert( !IsReadOnly() ); + assert( IsIdxValid(i) ); + return m_pMemory[i]; +} + +template< class T, class I > +inline const T& CUtlMemory::Element( I i ) const +{ + assert( IsIdxValid(i) ); + return m_pMemory[i]; +} + + +//----------------------------------------------------------------------------- +// is the memory externally allocated? +//----------------------------------------------------------------------------- +template< class T, class I > +bool CUtlMemory::IsExternallyAllocated() const +{ + return (m_nGrowSize < 0); +} + + +//----------------------------------------------------------------------------- +// is the memory read only? +//----------------------------------------------------------------------------- +template< class T, class I > +bool CUtlMemory::IsReadOnly() const +{ + return (m_nGrowSize == EXTERNAL_CONST_BUFFER_MARKER); +} + + +template< class T, class I > +void CUtlMemory::SetGrowSize( int nSize ) +{ + assert( !IsExternallyAllocated() ); + assert( nSize >= 0 ); + m_nGrowSize = nSize; + ValidateGrowSize(); +} + + +//----------------------------------------------------------------------------- +// Gets the base address (can change when adding elements!) +//----------------------------------------------------------------------------- +template< class T, class I > +inline T* CUtlMemory::Base() +{ + assert( !IsReadOnly() ); + return m_pMemory; +} + +template< class T, class I > +inline const T *CUtlMemory::Base() const +{ + return m_pMemory; +} + + +//----------------------------------------------------------------------------- +// Size +//----------------------------------------------------------------------------- +template< class T, class I > +inline int CUtlMemory::NumAllocated() const +{ + return m_nAllocationCount; +} + +template< class T, class I > +inline int CUtlMemory::Count() const +{ + return m_nAllocationCount; +} + + +//----------------------------------------------------------------------------- +// Is element index valid? +//----------------------------------------------------------------------------- +template< class T, class I > +inline bool CUtlMemory::IsIdxValid( I i ) const +{ + return ( ((int) i) >= 0 ) && ( ((int) i) < m_nAllocationCount ); +} + +//----------------------------------------------------------------------------- +// Grows the memory +//----------------------------------------------------------------------------- +inline int UtlMemory_CalcNewAllocationCount( int nAllocationCount, int nGrowSize, int nNewSize, int nBytesItem ) +{ + if ( nGrowSize ) + { + nAllocationCount = ((1 + ((nNewSize - 1) / nGrowSize)) * nGrowSize); + } + else + { + if ( !nAllocationCount ) + { + // Compute an allocation which is at least as big as a cache line... + nAllocationCount = (31 + nBytesItem) / nBytesItem; + } + + while (nAllocationCount < nNewSize) + { +#ifndef _X360 + nAllocationCount *= 2; +#else + int nNewAllocationCount = ( nAllocationCount * 9) / 8; // 12.5 % + if ( nNewAllocationCount > nAllocationCount ) + nAllocationCount = nNewAllocationCount; + else + nAllocationCount *= 2; +#endif + } + } + + return nAllocationCount; +} + +template< class T, class I > +void CUtlMemory::Grow( int num ) +{ + assert( num > 0 ); + + if ( IsExternallyAllocated() ) + { + // Can't grow a buffer whose memory was externally allocated + assert(0); + return; + } + + // Make sure we have at least numallocated + num allocations. + // Use the grow rules specified for this memory (in m_nGrowSize) + int nAllocationRequested = m_nAllocationCount + num; + + m_nAllocationCount = UtlMemory_CalcNewAllocationCount( m_nAllocationCount, m_nGrowSize, nAllocationRequested, sizeof(T) ); + + // if m_nAllocationRequested wraps index type I, recalculate + if ( ( int )( I )m_nAllocationCount < nAllocationRequested ) + { + if ( ( int )( I )m_nAllocationCount == 0 && ( int )( I )( m_nAllocationCount - 1 ) >= nAllocationRequested ) + { + --m_nAllocationCount; // deal w/ the common case of m_nAllocationCount == MAX_USHORT + 1 + } + else + { + if ( ( int )( I )nAllocationRequested != nAllocationRequested ) + { + // we've been asked to grow memory to a size s.t. the index type can't address the requested amount of memory + assert( 0 ); + return; + } + while ( ( int )( I )m_nAllocationCount < nAllocationRequested ) + { + m_nAllocationCount = ( m_nAllocationCount + nAllocationRequested ) / 2; + } + } + } + + if (m_pMemory) + { + m_pMemory = (T*)realloc( m_pMemory, m_nAllocationCount * sizeof(T) ); + assert( m_pMemory ); + } + else + { + m_pMemory = (T*)malloc( m_nAllocationCount * sizeof(T) ); + assert( m_pMemory ); + } +} + + +//----------------------------------------------------------------------------- +// Makes sure we've got at least this much memory +//----------------------------------------------------------------------------- +template< class T, class I > +inline void CUtlMemory::EnsureCapacity( int num ) +{ + if (m_nAllocationCount >= num) + return; + + if ( IsExternallyAllocated() ) + { + // Can't grow a buffer whose memory was externally allocated + assert(0); + return; + } + + m_nAllocationCount = num; + + if (m_pMemory) + { + m_pMemory = (T*)realloc( m_pMemory, m_nAllocationCount * sizeof(T) ); + } + else + { + m_pMemory = (T*)malloc( m_nAllocationCount * sizeof(T) ); + } +} + + +//----------------------------------------------------------------------------- +// Memory deallocation +//----------------------------------------------------------------------------- +template< class T, class I > +void CUtlMemory::Purge() +{ + if ( !IsExternallyAllocated() ) + { + if (m_pMemory) + { + free( (void*)m_pMemory ); + m_pMemory = 0; + } + m_nAllocationCount = 0; + } +} + +template< class T, class I > +void CUtlMemory::Purge( int numElements ) +{ + assert( numElements >= 0 ); + + if( numElements > m_nAllocationCount ) + { + // Ensure this isn't a grow request in disguise. + assert( numElements <= m_nAllocationCount ); + return; + } + + // If we have zero elements, simply do a purge: + if( numElements == 0 ) + { + Purge(); + return; + } + + if ( IsExternallyAllocated() ) + { + // Can't shrink a buffer whose memory was externally allocated, fail silently like purge + return; + } + + // If the number of elements is the same as the allocation count, we are done. + if( numElements == m_nAllocationCount ) + { + return; + } + + + if( !m_pMemory ) + { + // Allocation count is non zero, but memory is null. + assert( m_pMemory ); + return; + } + + m_nAllocationCount = numElements; + + // Allocation count > 0, shrink it down. + m_pMemory = (T*)realloc( m_pMemory, m_nAllocationCount * sizeof(T) ); +} + +//----------------------------------------------------------------------------- +// The CUtlMemory class: +// A growable memory class which doubles in size by default. +//----------------------------------------------------------------------------- +template< class T, int nAlignment > +class CUtlMemoryAligned : public CUtlMemory +{ +public: + // constructor, destructor + CUtlMemoryAligned( int nGrowSize = 0, int nInitSize = 0 ); + CUtlMemoryAligned( T* pMemory, int numElements ); + CUtlMemoryAligned( const T* pMemory, int numElements ); + ~CUtlMemoryAligned(); + + // Attaches the buffer to external memory.... + void SetExternalBuffer( T* pMemory, int numElements ); + void SetExternalBuffer( const T* pMemory, int numElements ); + + // Grows the memory, so that at least allocated + num elements are allocated + void Grow( int num = 1 ); + + // Makes sure we've got at least this much memory + void EnsureCapacity( int num ); + + // Memory deallocation + void Purge(); + + // Purge all but the given number of elements (NOT IMPLEMENTED IN CUtlMemoryAligned) + void Purge( int numElements ) { assert( 0 ); } + +private: + void *Align( const void *pAddr ); +}; + + +//----------------------------------------------------------------------------- +// Aligns a pointer +//----------------------------------------------------------------------------- +template< class T, int nAlignment > +void *CUtlMemoryAligned::Align( const void *pAddr ) +{ + size_t nAlignmentMask = nAlignment - 1; + return (void*)( ((size_t)pAddr + nAlignmentMask) & (~nAlignmentMask) ); +} + + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- +template< class T, int nAlignment > +CUtlMemoryAligned::CUtlMemoryAligned( int nGrowSize, int nInitAllocationCount ) +{ + CUtlMemory::m_pMemory = 0; + CUtlMemory::m_nAllocationCount = nInitAllocationCount; + CUtlMemory::m_nGrowSize = nGrowSize; + ValidateGrowSize(); + + // Alignment must be a power of two + COMPILE_TIME_ASSERT( (nAlignment & (nAlignment-1)) == 0 ); + assert( (nGrowSize >= 0) && (nGrowSize != CUtlMemory::EXTERNAL_BUFFER_MARKER) ); + if ( CUtlMemory::m_nAllocationCount ) + { + CUtlMemory::m_pMemory = (T*)_aligned_malloc( nInitAllocationCount * sizeof(T), nAlignment ); + } +} + +template< class T, int nAlignment > +CUtlMemoryAligned::CUtlMemoryAligned( T* pMemory, int numElements ) +{ + // Special marker indicating externally supplied memory + CUtlMemory::m_nGrowSize = CUtlMemory::EXTERNAL_BUFFER_MARKER; + + CUtlMemory::m_pMemory = (T*)Align( pMemory ); + CUtlMemory::m_nAllocationCount = ( (int)(pMemory + numElements) - (int)CUtlMemory::m_pMemory ) / sizeof(T); +} + +template< class T, int nAlignment > +CUtlMemoryAligned::CUtlMemoryAligned( const T* pMemory, int numElements ) +{ + // Special marker indicating externally supplied memory + CUtlMemory::m_nGrowSize = CUtlMemory::EXTERNAL_CONST_BUFFER_MARKER; + + CUtlMemory::m_pMemory = (T*)Align( pMemory ); + CUtlMemory::m_nAllocationCount = ( (int)(pMemory + numElements) - (int)CUtlMemory::m_pMemory ) / sizeof(T); +} + +template< class T, int nAlignment > +CUtlMemoryAligned::~CUtlMemoryAligned() +{ + Purge(); +} + + +//----------------------------------------------------------------------------- +// Attaches the buffer to external memory.... +//----------------------------------------------------------------------------- +template< class T, int nAlignment > +void CUtlMemoryAligned::SetExternalBuffer( T* pMemory, int numElements ) +{ + // Blow away any existing allocated memory + Purge(); + + CUtlMemory::m_pMemory = (T*)Align( pMemory ); + CUtlMemory::m_nAllocationCount = ( (int)(pMemory + numElements) - (int)CUtlMemory::m_pMemory ) / sizeof(T); + + // Indicate that we don't own the memory + CUtlMemory::m_nGrowSize = CUtlMemory::EXTERNAL_BUFFER_MARKER; +} + +template< class T, int nAlignment > +void CUtlMemoryAligned::SetExternalBuffer( const T* pMemory, int numElements ) +{ + // Blow away any existing allocated memory + Purge(); + + CUtlMemory::m_pMemory = (T*)Align( pMemory ); + CUtlMemory::m_nAllocationCount = ( (int)(pMemory + numElements) - (int)CUtlMemory::m_pMemory ) / sizeof(T); + + // Indicate that we don't own the memory + CUtlMemory::m_nGrowSize = CUtlMemory::EXTERNAL_CONST_BUFFER_MARKER; +} + + +//----------------------------------------------------------------------------- +// Grows the memory +//----------------------------------------------------------------------------- +template< class T, int nAlignment > +void CUtlMemoryAligned::Grow( int num ) +{ + assert( num > 0 ); + + if ( IsExternallyAllocated() ) + { + // Can't grow a buffer whose memory was externally allocated + assert(0); + return; + } + + // Make sure we have at least numallocated + num allocations. + // Use the grow rules specified for this memory (in m_nGrowSize) + int nAllocationRequested = CUtlMemory::m_nAllocationCount + num; + + CUtlMemory::m_nAllocationCount = UtlMemory_CalcNewAllocationCount( CUtlMemory::m_nAllocationCount, CUtlMemory::m_nGrowSize, nAllocationRequested, sizeof(T) ); + + if ( CUtlMemory::m_pMemory ) + { + CUtlMemory::m_pMemory = (T*)_aligned_realloc( CUtlMemory::m_pMemory, CUtlMemory::m_nAllocationCount * sizeof(T), nAlignment ); + assert( CUtlMemory::m_pMemory ); + } + else + { + CUtlMemory::m_pMemory = (T*)_aligned_malloc( CUtlMemory::m_nAllocationCount * sizeof(T), nAlignment ); + assert( CUtlMemory::m_pMemory ); + } +} + + +//----------------------------------------------------------------------------- +// Makes sure we've got at least this much memory +//----------------------------------------------------------------------------- +template< class T, int nAlignment > +inline void CUtlMemoryAligned::EnsureCapacity( int num ) +{ + if ( CUtlMemory::m_nAllocationCount >= num ) + return; + + if ( IsExternallyAllocated() ) + { + // Can't grow a buffer whose memory was externally allocated + assert(0); + return; + } + + CUtlMemory::m_nAllocationCount = num; + + if ( CUtlMemory::m_pMemory ) + { + CUtlMemory::m_pMemory = (T*)_aligned_realloc( CUtlMemory::m_pMemory, CUtlMemory::m_nAllocationCount * sizeof(T), nAlignment ); + } + else + { + CUtlMemory::m_pMemory = (T*)_aligned_malloc( CUtlMemory::m_nAllocationCount * sizeof(T), nAlignment ); + } +} + + +//----------------------------------------------------------------------------- +// Memory deallocation +//----------------------------------------------------------------------------- +template< class T, int nAlignment > +void CUtlMemoryAligned::Purge() +{ + if ( !IsExternallyAllocated() ) + { + if ( CUtlMemory::m_pMemory ) + { + _aligned_free( CUtlMemory::m_pMemory ); + CUtlMemory::m_pMemory = 0; + } + CUtlMemory::m_nAllocationCount = 0; + } +} + +#endif // UTLMEMORY_H \ No newline at end of file diff --git a/game_shared/vector.h b/game_shared/vector.h new file mode 100644 index 0000000..f9d15ce --- /dev/null +++ b/game_shared/vector.h @@ -0,0 +1,478 @@ +//======================================================================= +// Copyright (C) Shambler Team 2005 +// vector.h - shared vector operations +//======================================================================= +#ifndef VECTOR_H +#define VECTOR_H + +#include +#include +#include +#include +#include + +#define PITCH 0 +#define YAW 1 +#define ROLL 2 + +#define DIST_EPSILON (1.0f / 32.0f) +#define STOP_EPSILON 0.1f +#define ON_EPSILON 0.1f + +#define BIT( n ) (1<<( n )) +#define RAD2DEG( x ) ((float)(x) * (float)(180.f / M_PI)) +#define DEG2RAD( x ) ((float)(x) * (float)(M_PI / 180.f)) +#define IS_NAN( x ) (((*(int *)&x) & (255<<23)) == (255<<23)) +#define Q_rint( x ) ((x) < 0 ? ((int)((x)-0.5f)) : ((int)((x)+0.5f))) +#define Q_floor( a ) ((float)(long)(a)) +#define Q_ceil( a ) ((float)(long)((a) + 1)) + +// global replacement engine <-> game +#define vec2_t Vector2D +#define vec3_t Vector +#define vec4_t Vector4D + +#pragma warning( disable : 4244 ) // disable 'possible loss of data converting float to int' warning message +#pragma warning( disable : 4305 ) // disable 'truncation from 'const double' to 'float' warning message + +class NxVec3; +class Radian; + +inline void SinCos( float angle, float *sine, float *cosine ) +{ + __asm + { + push ecx + fld dword ptr angle + fsincos + mov ecx, dword ptr[cosine] + fstp dword ptr [ecx] + mov ecx, dword ptr[sine] + fstp dword ptr [ecx] + pop ecx + } +} + +inline float Q_rsqrt( float number ) +{ + const float threehalfs = 1.5F; + + float x2 = number * 0.5F; + float y = number; + int i = *(long *)&y; // evil floating point bit level hacking + i = 0x5f3759df - (i>>1); // what the fuck? + y = * (float *)&i; + y = y * (1.5F - ( x2 * y * y )); // 1st iteration + + assert( !IS_NAN( y )); + + return y; +} + +extern float HalfToFloat( unsigned short h ); + +//========================================================= +// 2DVector - used for many pathfinding and many other +// operations that are treated as planar rather than 3d. +//========================================================= +class Vector2D +{ +public: + inline Vector2D(void) { } + inline Vector2D(float X, float Y) { x = X; y = Y; } + inline Vector2D( const float *rgfl ) { x = rgfl[0]; y = rgfl[1]; } + inline Vector2D(float rgfl[2]) { x = rgfl[0]; y = rgfl[1]; } + inline Vector2D operator+(const Vector2D& v) const { return Vector2D(x+v.x, y+v.y); } + inline Vector2D operator-(const Vector2D& v) const { return Vector2D(x-v.x, y-v.y); } + inline Vector2D operator*(float fl) const { return Vector2D(x*fl, y*fl); } + inline Vector2D operator/(float fl) const { return Vector2D(x/fl, y/fl); } + + _forceinline Vector2D& operator+=(const Vector2D &v) + { + x+=v.x; y+=v.y; + return *this; + } + _forceinline Vector2D& operator-=(const Vector2D &v) + { + x-=v.x; y-=v.y; + return *this; + } + _forceinline Vector2D& operator*=(const Vector2D &v) + { + x *= v.x; y *= v.y; + return *this; + } + _forceinline Vector2D& operator*=(float s) + { + x *= s; y *= s; + return *this; + } + _forceinline Vector2D& operator/=(const Vector2D &v) + { + x /= v.x; y /= v.y; + return *this; + } + _forceinline Vector2D& operator/=(float s) + { + float oofl = 1.0f / s; + x *= oofl; y *= oofl; + return *this; + } + + operator float *() { return &x; } // Vectors will now automatically convert to float * when needed + operator const float *() const { return &x; } + + inline float Length(void) const { return sqrt(x*x + y*y ); } + inline Vector2D Normalize ( void ) const + { + Vector2D vec2; + + float flLen = Length(); + if ( flLen == 0 ) + { + return Vector2D( 0, 0 ); + } + else + { + flLen = 1 / flLen; + return Vector2D( x * flLen, y * flLen ); + } + } + float x, y; +}; + +#define IS_NAN(x) (((*(int *)&x) & (255<<23)) == (255<<23)) + +inline float DotProduct(const Vector2D& a, const Vector2D& b) { return( a.x*b.x + a.y*b.y ); } +inline Vector2D operator*(float fl, const Vector2D& v) { return v * fl; } + +class NxVec3; + +//========================================================= +// 3D Vector +//========================================================= +class Vector // same data-layout as engine's vec3_t, +{ // which is a float[3] +public: + // Construction/destruction + inline Vector(void) { } + inline Vector(float X, float Y, float Z) { x = X; y = Y; z = Z; } + inline Vector(const Vector& v) { x = v.x; y = v.y; z = v.z; } + inline Vector( const float *rgfl ) { x = rgfl[0]; y = rgfl[1]; z = rgfl[2]; } + inline Vector(float rgfl[3]) { x = rgfl[0]; y = rgfl[1]; z = rgfl[2]; } + inline Vector( const unsigned short rgus[3] ) { x = HalfToFloat( rgus[0] ); y = HalfToFloat( rgus[1] ); z = HalfToFloat( rgus[2] ); } + inline Vector( char rgch[3] ) + { + x = float( rgch[0] ); + y = float( rgch[1] ); + z = float( rgch[2] ); + + float flLen = Length(); + + if( flLen ) + { + float flInvLen = 1.0f / flLen; + x *= flInvLen, y *= flInvLen, z *= flInvLen; + } + } + + inline Vector( float fill ) { x = fill; y = fill; z = fill; } + Vector(const NxVec3& v); + + // Initialization + void Init(float ix=0.0f, float iy=0.0f, float iz=0.0f){ x = ix; y = iy; z = iz; } + + // Operators + inline Vector operator-(void) const { return Vector(-x,-y,-z); } + inline int operator==(const Vector& v) const { return x==v.x && y==v.y && z==v.z; } + inline int operator!=(const Vector& v) const { return !(*this==v); } + inline Vector operator+(const Vector& v) const { return Vector(x+v.x, y+v.y, z+v.z); } + inline Vector operator-(const Vector& v) const { return Vector(x-v.x, y-v.y, z-v.z); } + inline Vector operator+(float fl) const { return Vector(x+fl, y+fl, z+fl); } + inline Vector operator-(float fl) const { return Vector(x-fl, y-fl, z-fl); } + inline Vector operator*(float fl) const { return Vector(x*fl, y*fl, z*fl); } + inline Vector operator/(float fl) const { return Vector(x/fl, y/fl, z/fl); } + inline Vector operator*(const Vector& v) const { return Vector(x*v.x, y*v.y, z*v.z); } + const Vector& operator=(const NxVec3& v); + + _forceinline Vector& operator+=(const Vector &v) + { + x+=v.x; y+=v.y; z += v.z; + return *this; + } + _forceinline Vector& operator-=(const Vector &v) + { + x-=v.x; y-=v.y; z -= v.z; + return *this; + } + _forceinline Vector& operator*=(const Vector &v) + { + x *= v.x; y *= v.y; z *= v.z; + return *this; + } + _forceinline Vector& operator*=(float s) + { + x *= s; y *= s; z *= s; + return *this; + } + _forceinline Vector& operator/=(const Vector &v) + { + x /= v.x; y /= v.y; z /= v.z; + return *this; + } + _forceinline Vector& operator/=(float s) + { + float oofl = 1.0f / s; + x *= oofl; y *= oofl; z *= oofl; + return *this; + } + + _forceinline Vector& fixangle(void) + { + if (!IS_NAN( x )) + { + while ( x < 0 ) x += 360; + while ( x > 360 ) x -= 360; + } + if (!IS_NAN( y )) + { + while ( y < 0 ) y += 360; + while ( y > 360 ) y -= 360; + } + if (!IS_NAN( z )) + { + while ( z < 0 ) z += 360; + while ( z > 360 ) z -= 360; + } + return *this; + } + + _forceinline Vector MA( float scale, const Vector &start, const Vector &direction ) const + { + return Vector(start.x + scale * direction.x, start.y + scale * direction.y, start.z + scale * direction.z) ; + } + + _forceinline bool Compare( const Vector &vec, const float epsilon ) const + { + if( fabs( x - vec.x ) > epsilon ) return false; + if( fabs( y - vec.y ) > epsilon ) return false; + if( fabs( z - vec.z ) > epsilon ) return false; + + return true; + } + + // Methods + inline void CopyToArray( float *rgfl ) const { rgfl[0] = x, rgfl[1] = y, rgfl[2] = z; } + inline float Length(void) const { return sqrt( x*x + y*y + z*z ); } + inline float LengthSqr(void) const { return (x*x + y*y + z*z); } + operator float *() { return &x; } // Vectors will now automatically convert to float * when needed + operator const float *() const { return &x; } + + inline Vector Normalize( void ) const + { + float flLen = Length(); + + if( flLen ) + { + flLen = 1.0f / flLen; + return Vector( x * flLen, y * flLen, z * flLen ); + } + + return *this; // can't normalize + } + + inline Vector NormalizeFast( void ) const + { + float ilength = Q_rsqrt( x * x + y * y + z * z ); + return Vector( x * ilength, y * ilength, z * ilength ); + } + + inline float NormalizeLength( void ) + { + float flLen = Length(); + + if( flLen ) + { + float flInvLen = 1.0f / flLen; + x *= flInvLen, y *= flInvLen, z *= flInvLen; + } + + return flLen; + } + + inline Vector Abs( void ) const + { + return Vector( fabs( x ), fabs( y ), fabs( z )); + } + + inline float Average( void ) const { return (x + y + z) / 3.0f; } + + float Dot( Vector const& vOther ) const + { + return(x*vOther.x+y*vOther.y+z*vOther.z); + } + + Vector Cross(const Vector &vOther) const + { + return Vector(y*vOther.z - z*vOther.y, z*vOther.x - x*vOther.z, x*vOther.y - y*vOther.x); + } + inline Vector2D Make2D ( void ) const + { + Vector2D Vec2; + Vec2.x = x; + Vec2.y = y; + return Vec2; + } + + inline float Length2D(void) const { return sqrt(x*x + y*y); } + + // Members + float x, y, z; +}; + +inline Vector operator* ( float fl, const Vector& v ) { return v * fl; } +inline float DotProduct(const Vector& a, const Vector& b ) { return( a.x * b.x + a.y * b.y + a.z * b.z ); } +inline float DotProductAbs( const Vector& a, const Vector& b ) { return( fabs( a.x * b.x ) + fabs( a.y * b.y ) + fabs( a.z * b.z )); } +inline Vector CrossProduct( const Vector& a, const Vector& b ) { return Vector( a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x ); } +inline void VectorLerp( const Vector& src1, float t, const Vector& src2, Vector& dest ) +{ + dest.x = src1.x + (src2.x - src1.x) * t; + dest.y = src1.y + (src2.y - src1.y) * t; + dest.z = src1.z + (src2.z - src1.z) * t; +} + +//========================================================= +// 4D Vector - for matrix operations +//========================================================= +class Vector4D +{ +public: + // Members + float x, y, z, w; + + // Construction/destruction + inline Vector4D( void ) { } + inline Vector4D( float X, float Y, float Z, float W ) { x = X; y = Y; z = Z; w = W; } + inline Vector4D( const Vector4D& v ) { x = v.x; y = v.y; z = v.z, w = v.w; } + inline Vector4D( const float *pFloat ) { x = pFloat[0]; y = pFloat[1]; z = pFloat[2]; w = pFloat[3];} + inline Vector4D( const Vector& v ) { x = v.x; y = v.y; z = v.z; w = 1.0f; } + inline Vector4D( Radian const &angle ); // evil auto type promotion!!! + + // Initialization + void Init( float ix = 0.0f, float iy = 0.0f, float iz = 0.0f, float iw = 0.0f ) + { + x = ix; y = iy; z = iz; w = iw; + } + + // Vectors will now automatically convert to float * when needed + operator float *() { return &x; } + operator const float *() const { return &x; } + + // Vectors will now automatically convert to Vector when needed + operator Vector() { return Vector( x, y, z ); } + operator const Vector() const { return Vector( x, y, z ); } + + inline float Length(void) const { return sqrt( x*x + y*y + z*z + w*w); } + inline float LengthSqr(void) const { return (x*x + y*y + z*z + w*w); } + + inline Vector4D Normalize( void ) const + { + float flLen = Length(); + + if( flLen ) + { + flLen = 1.0f / flLen; + return Vector4D( x * flLen, y * flLen, z * flLen, w * flLen ); + } + + return *this; // can't normalize + } + + // equality + bool operator==(const Vector4D& v) const { return v.x==x && v.y==y && v.z==z && v.w==w; } + bool operator!=(const Vector4D& v) const { return !(*this==v); } + inline Vector4D operator+(const Vector4D& v) const { return Vector4D(x+v.x, y+v.y, z+v.z, w+v.w); } + inline Vector4D operator-(const Vector4D& v) const { return Vector4D(x-v.x, y-v.y, z-v.z, w-v.w); } +}; + +inline float DotProduct( const Vector4D& a, const Vector4D& b ) { return( a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w ); } + +class Radian +{ +public: + inline Radian( void ) { } + inline Radian( float X, float Y, float Z ) { x = X; y = Y; z = Z; } + inline Radian( Vector4D const &q ); // evil auto type promotion!!! + + // initialization + inline void Init( float ix = 0.0f, float iy = 0.0f, float iz = 0.0f ) { x = ix; y = iy; z = iz; } + + // Vectors will now automatically convert to float * when needed + operator float *() { return &x; } + operator const float *() const { return &x; } + + // Operators + inline Radian operator-(void) const { return Radian(-x,-y,-z); } + inline int operator==(const Radian& v) const { return x==v.x && y==v.y && z==v.z; } + inline int operator!=(const Radian& v) const { return !(*this==v); } + inline Radian operator+(const Radian& v) const { return Radian(x+v.x, y+v.y, z+v.z); } + inline Radian operator-(const Radian& v) const { return Radian(x-v.x, y-v.y, z-v.z); } + inline Radian operator+(float fl) const { return Radian(x+fl, y+fl, z+fl); } + inline Radian operator-(float fl) const { return Radian(x-fl, y-fl, z-fl); } + inline Radian operator*(float fl) const { return Radian(x*fl, y*fl, z*fl); } + inline Radian operator/(float fl) const { return Radian(x/fl, y/fl, z/fl); } + inline Radian operator*(const Radian& v) const { return Radian(x*v.x, y*v.y, z*v.z); } + const Radian& operator=(const NxVec3& v); + + _forceinline Radian& operator+=(const Radian &v) + { + x+=v.x; y+=v.y; z += v.z; + return *this; + } + _forceinline Radian& operator-=(const Radian &v) + { + x-=v.x; y-=v.y; z -= v.z; + return *this; + } + _forceinline Radian& operator*=(const Radian &v) + { + x *= v.x; y *= v.y; z *= v.z; + return *this; + } + _forceinline Radian& operator*=(float s) + { + x *= s; y *= s; z *= s; + return *this; + } + _forceinline Radian& operator/=(const Radian &v) + { + x /= v.x; y /= v.y; z /= v.z; + return *this; + } + _forceinline Radian& operator/=(float s) + { + float oofl = 1.0f / s; + x *= oofl; y *= oofl; z *= oofl; + return *this; + } + + float x, y, z; +}; + +extern void AngleQuaternion( Radian const &angles, Vector4D &qt ); +extern void QuaternionAngle( Vector4D const &q, Radian &angles ); + +inline Radian :: Radian( Vector4D const &q ) +{ + QuaternionAngle( q, *this ); +} + +inline Vector4D :: Vector4D( Radian const &angle ) +{ + AngleQuaternion( angle, *this ); +} + +extern const Vector g_vecZero; +extern const Radian g_radZero; + +#endif//VECTOR_H \ No newline at end of file diff --git a/game_shared/vgui_checkbutton2.cpp b/game_shared/vgui_checkbutton2.cpp new file mode 100644 index 0000000..511853f --- /dev/null +++ b/game_shared/vgui_checkbutton2.cpp @@ -0,0 +1,197 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include +#include "vgui_checkbutton2.h" +#include "vgui_loadtga.h" + + +#define EXTRA_X 5 + + +using namespace vgui; + + + +CCheckButton2::CCheckButton2() : + m_Label(""), + m_pChecked(NULL), + m_pUnchecked(NULL), + m_pHandler(NULL), + m_CheckboxPanel(NULL) +{ + m_bOwnImages = false; + m_bChecked = false; + m_pChecked = m_pUnchecked = NULL; + m_bCheckboxLeft = true; + + m_Label.setParent(this); + m_Label.setFgColor(255,255,255,0); + m_Label.setBgColor(0,0,0,255); // background is not drawn and foreground is white + m_Label.addInputSignal(this); + + m_CheckboxPanel.setParent(this); + m_CheckboxPanel.addInputSignal(this); + + setPaintBackgroundEnabled(false); +} + + +CCheckButton2::~CCheckButton2() +{ + DeleteImages(); +} + + +void CCheckButton2::SetImages(char const *pChecked, char const *pUnchecked) +{ + DeleteImages(); + + m_pChecked = vgui_LoadTGA(pChecked); + m_pUnchecked = vgui_LoadTGA(pUnchecked); + m_bOwnImages = true; + + SetupControls(); +} + + +void CCheckButton2::SetImages(Image *pChecked, Image *pUnchecked) +{ + DeleteImages(); + + m_pChecked = pChecked; + m_pUnchecked = pUnchecked; + m_bOwnImages = false; + + SetupControls(); +} + + +void CCheckButton2::DeleteImages() +{ + if(m_bOwnImages) + { + delete m_pChecked; + delete m_pUnchecked; + } + + m_pChecked = NULL; + m_pUnchecked = NULL; + m_bOwnImages = false; + + SetupControls(); +} + + +void CCheckButton2::SetCheckboxLeft(bool bLeftAlign) +{ + m_bCheckboxLeft = bLeftAlign; + SetupControls(); +} + + +bool CCheckButton2::GetCheckboxLeft() +{ + return m_bCheckboxLeft; +} + + +void CCheckButton2::SetText(char const *pText, ...) +{ + char str[512]; + + va_list marker; + va_start(marker, pText); + _vsnprintf(str, sizeof(str), pText, marker); + va_end(marker); + + m_Label.setText(str); + SetupControls(); +} + + +void CCheckButton2::SetTextColor(int r, int g, int b, int a) +{ + m_Label.setFgColor(r, g, b, a); + repaint(); +} + + +void CCheckButton2::SetHandler(ICheckButton2Handler *pHandler) +{ + m_pHandler = pHandler; +} + + +bool CCheckButton2::IsChecked() +{ + return m_bChecked; +} + + +void CCheckButton2::SetChecked(bool bChecked) +{ + m_bChecked = bChecked; + SetupControls(); +} + + +void CCheckButton2::internalMousePressed(MouseCode code) +{ + m_bChecked = !m_bChecked; + + if(m_pHandler) + m_pHandler->StateChanged(this); + + SetupControls(); +} + + +void CCheckButton2::SetupControls() +{ + // Initialize the checkbutton bitmap. + Image *pBitmap = m_bChecked ? m_pChecked : m_pUnchecked; + + Panel *controls[2] = {&m_CheckboxPanel, &m_Label}; + int controlSizes[2][2]; + + controlSizes[0][0] = controlSizes[0][1] = 0; + if(pBitmap) + pBitmap->getSize(controlSizes[0][0], controlSizes[0][1]); + + m_CheckboxPanel.setImage(pBitmap); + m_CheckboxPanel.setSize(controlSizes[0][0], controlSizes[0][1]); + + + // Get the label's size. + m_Label.getSize(controlSizes[1][0], controlSizes[1][1]); + m_Label.setContentAlignment(Label::a_west); + + + // Position the controls. + int iLeftControl = !m_bCheckboxLeft; + int iBiggestY = controlSizes[0][1] > controlSizes[1][0] ? 0 : 1; + controls[iLeftControl]->setPos(0, (controlSizes[iBiggestY][1] - controlSizes[iLeftControl][1]) / 2); + controls[!iLeftControl]->setPos(controlSizes[iLeftControl][0] + EXTRA_X, (controlSizes[iBiggestY][1] - controlSizes[!iLeftControl][1]) / 2); + + + // Fit this control to the sizes of the subcontrols. + setSize(controlSizes[0][0] + controlSizes[1][0] + EXTRA_X, (controlSizes[0][1] > controlSizes[1][1]) ? controlSizes[0][1] : controlSizes[1][1]); + repaint(); +} + + +void CCheckButton2::mousePressed(MouseCode code, Panel *panel) +{ + internalMousePressed(code); +} + + + + + diff --git a/game_shared/vgui_checkbutton2.h b/game_shared/vgui_checkbutton2.h new file mode 100644 index 0000000..8f985d9 --- /dev/null +++ b/game_shared/vgui_checkbutton2.h @@ -0,0 +1,101 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_CHECKBUTTON2_H +#define VGUI_CHECKBUTTON2_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "vgui_label.h" +#include "vgui_imagepanel.h" +#include "vgui_defaultinputsignal.h" + + +namespace vgui +{ + + +class CCheckButton2; + + +class ICheckButton2Handler +{ +public: + virtual void StateChanged(CCheckButton2 *pButton) = 0; +}; + + +// VGUI checkbox class. +// - Provides access to the checkbox images. +// - Provides an easy callback mechanism for state changes. +// - Default background is invisible, and default text color is white. +class CCheckButton2 : public Panel, public CDefaultInputSignal +{ +public: + + CCheckButton2(); + ~CCheckButton2(); + + // Initialize the button with these. + void SetImages(char const *pChecked, char const *pUnchecked); + void SetImages(Image *pChecked, Image *pUnchecked); // If you use this, the button will never delete the images. + void DeleteImages(); + + // The checkbox can be to the left or the right of the text (default is left). + void SetCheckboxLeft(bool bLeftAlign); + bool GetCheckboxLeft(); + + // Set the label text. + void SetText(char const *pText, ...); + void SetTextColor(int r, int g, int b, int a); + + // You can register for change notification here. + void SetHandler(ICheckButton2Handler *pHandler); + + // Get/set the check state. + bool IsChecked(); + void SetChecked(bool bChecked); + + + +// Panel overrides. +public: + + virtual void internalMousePressed(MouseCode code); + + +protected: + + void SetupControls(); + + +// InputSignal overrides. +protected: + virtual void mousePressed(MouseCode code,Panel* panel); + + +public: + ICheckButton2Handler *m_pHandler; + + bool m_bCheckboxLeft; + Label m_Label; + ImagePanel m_CheckboxPanel; + + Image *m_pChecked; + Image *m_pUnchecked; + bool m_bOwnImages; + + bool m_bChecked; +}; + + +} + + +#endif // VGUI_CHECKBUTTON2_H diff --git a/game_shared/vgui_defaultinputsignal.h b/game_shared/vgui_defaultinputsignal.h new file mode 100644 index 0000000..94dae8d --- /dev/null +++ b/game_shared/vgui_defaultinputsignal.h @@ -0,0 +1,39 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_DEFAULTINPUTSIGNAL_H +#define VGUI_DEFAULTINPUTSIGNAL_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "vgui_inputsignal.h" + + +namespace vgui +{ + // This class derives from vgui::InputSignal and implements empty defaults for all of its functions. + class CDefaultInputSignal : public vgui::InputSignal + { + public: + virtual void cursorMoved(int x,int y,Panel* panel) {} + virtual void cursorEntered(Panel* panel) {} + virtual void cursorExited(Panel* panel) {} + virtual void mousePressed(MouseCode code,Panel* panel) {} + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {} + virtual void mouseReleased(MouseCode code,Panel* panel) {} + virtual void mouseWheeled(int delta,Panel* panel) {} + virtual void keyPressed(KeyCode code,Panel* panel) {} + virtual void keyTyped(KeyCode code,Panel* panel) {} + virtual void keyReleased(KeyCode code,Panel* panel) {} + virtual void keyFocusTicked(Panel* panel) {} + }; +} + + +#endif // VGUI_DEFAULTINPUTSIGNAL_H diff --git a/game_shared/vgui_grid.cpp b/game_shared/vgui_grid.cpp new file mode 100644 index 0000000..00e9d69 --- /dev/null +++ b/game_shared/vgui_grid.cpp @@ -0,0 +1,398 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include "vgui_grid.h" + + +using namespace vgui; + + +#define AssertCheck(expr, msg) \ + if(!(expr))\ + {\ + assert(!msg);\ + return 0;\ + } + + + +// ------------------------------------------------------------------------------ // +// CGrid::CGridEntry. +// ------------------------------------------------------------------------------ // + +CGrid::CGridEntry::CGridEntry() +{ + m_pPanel = NULL; + m_bUnderline = false; +} + +CGrid::CGridEntry::~CGridEntry() +{ +} + + +// ------------------------------------------------------------------------------ // +// CGrid. +// ------------------------------------------------------------------------------ // + +CGrid::CGrid() +{ + Clear(); +} + + +CGrid::~CGrid() +{ + Term(); +} + + +bool CGrid::SetDimensions(int xCols, int yRows) +{ + Term(); + + m_GridEntries = new CGridEntry[xCols * yRows]; + m_Widths = new int[xCols*2 + yRows*2]; + m_Heights = m_Widths + xCols; + m_ColOffsets = m_Heights + yRows; + m_RowOffsets = m_ColOffsets + xCols; + + if(!m_GridEntries || !m_Widths) + { + Term(); + return false; + } + + memset(m_Widths, 0, sizeof(int) * (xCols*2 + yRows*2)); + + m_xCols = xCols; + m_yRows = yRows; + return true; +} + + +void CGrid::Term() +{ + delete [] m_GridEntries; + delete [] m_Widths; + Clear(); +} + + +Panel* CGrid::GetEntry(int x, int y) +{ + return GridEntry(x, y)->m_pPanel; +} + + +bool CGrid::SetEntry(int x, int y, Panel *pPanel) +{ + CGridEntry *pEntry = GridEntry(x, y); + if(!pEntry) + return false; + + if(pEntry->m_pPanel) + pEntry->m_pPanel->setParent(NULL); + + pEntry->m_pPanel = pPanel; + if(pPanel) + pPanel->setParent(this); + + m_bDirty = true; + return true; +} + + +int CGrid::GetXSpacing() +{ + return m_xSpacing; +} + + +int CGrid::GetYSpacing() +{ + return m_ySpacing; +} + + +void CGrid::SetSpacing(int xSpacing, int ySpacing) +{ + if(xSpacing != m_xSpacing) + { + m_xSpacing = xSpacing; + CalcColOffsets(0); + m_bDirty = true; + } + + if(ySpacing != m_ySpacing) + { + m_ySpacing = ySpacing; + CalcRowOffsets(0); + m_bDirty = true; + } +} + + +bool CGrid::SetColumnWidth(int iColumn, int width) +{ + AssertCheck(iColumn >= 0 && iColumn < m_xCols, "CGrid::SetColumnWidth : invalid location specified"); + m_Widths[iColumn] = width; + CalcColOffsets(iColumn+1); + m_bDirty = true; + return true; +} + + +bool CGrid::SetRowHeight(int iRow, int height) +{ + AssertCheck(iRow >= 0 && iRow < m_yRows, "CGrid::SetColumnWidth : invalid location specified"); + m_Heights[iRow] = height; + CalcRowOffsets(iRow+1); + m_bDirty = true; + return true; +} + + +int CGrid::GetColumnWidth(int iColumn) +{ + AssertCheck(iColumn >= 0 && iColumn < m_xCols, "CGrid::GetColumnWidth: invalid location specified"); + return m_Widths[iColumn]; +} + + +int CGrid::GetRowHeight(int iRow) +{ + AssertCheck(iRow >= 0 && iRow < m_yRows, "CGrid::GetRowHeight: invalid location specified"); + return m_Heights[iRow]; +} + + +int CGrid::CalcFitColumnWidth(int iColumn) +{ + AssertCheck(iColumn >= 0 && iColumn < m_xCols, "CGrid::CalcFitColumnWidth: invalid location specified"); + + int maxSize = 0; + for(int i=0; i < m_yRows; i++) + { + Panel *pPanel = GridEntry(iColumn, i)->m_pPanel; + if(!pPanel) + continue; + + int w, h; + pPanel->getSize(w,h); + if(w > maxSize) + maxSize = w; + } + + return maxSize; +} + + +int CGrid::CalcFitRowHeight(int iRow) +{ + AssertCheck(iRow >= 0 && iRow < m_yRows, "CGrid::CalcFitRowHeight: invalid location specified"); + + int maxSize = 0; + for(int i=0; i < m_xCols; i++) + { + Panel *pPanel = GridEntry(i, iRow)->m_pPanel; + if(!pPanel) + continue; + + int w, h; + pPanel->getSize(w,h); + if(h > maxSize) + maxSize = h; + } + + return maxSize; +} + + +void CGrid::AutoSetRowHeights() +{ + for(int i=0; i < m_yRows; i++) + SetRowHeight(i, CalcFitRowHeight(i)); +} + + +bool CGrid::GetEntryBox( + int col, int row, int &x, int &y, int &w, int &h) +{ + AssertCheck(col >= 0 && col < m_xCols && row >= 0 && row < m_yRows, "CGrid::GetEntryBox: invalid location specified"); + + x = m_ColOffsets[col]; + w = m_Widths[col]; + + y = m_RowOffsets[row]; + h = m_Heights[row]; + return true; +} + + +bool CGrid::CopyColumnWidths(CGrid *pOther) +{ + if(!pOther || pOther->m_xCols != m_xCols) + return false; + + for(int i=0; i < m_xCols; i++) + m_Widths[i] = pOther->m_Widths[i]; + + CalcColOffsets(0); + m_bDirty = true; + return true; +} + + +void CGrid::RepositionContents() +{ + for(int x=0; x < m_xCols; x++) + { + for(int y=0; y < m_yRows; y++) + { + Panel *pPanel = GridEntry(x,y)->m_pPanel; + if(!pPanel) + continue; + + pPanel->setBounds( + m_ColOffsets[x], + m_RowOffsets[y], + m_Widths[x], + m_Heights[y]); + } + } + + m_bDirty = false; +} + + +int CGrid::CalcDrawHeight() +{ + if(m_yRows > 0) + { + return m_RowOffsets[m_yRows-1] + m_Heights[m_yRows - 1] + m_ySpacing; + } + else + { + return 0; + } +} + + +void CGrid::paint() +{ + if(m_bDirty) + RepositionContents(); + + Panel::paint(); + + // walk the grid looking for underlined rows + int x = 0, y = 0; + for (int row = 0; row < m_yRows; row++) + { + CGridEntry *cell = GridEntry(0, row); + + y += cell->m_pPanel->getTall() + m_ySpacing; + if (cell->m_bUnderline) + { + drawSetColor(cell->m_UnderlineColor[0], cell->m_UnderlineColor[1], cell->m_UnderlineColor[2], cell->m_UnderlineColor[3]); + drawFilledRect(0, y - (cell->m_iUnderlineOffset + 1), getWide(), y - cell->m_iUnderlineOffset); + } + } +} + +void CGrid::paintBackground() +{ + Panel::paintBackground(); +} + +//----------------------------------------------------------------------------- +// Purpose: sets underline color for a particular row +//----------------------------------------------------------------------------- +void CGrid::SetRowUnderline(int row, bool enabled, int offset, int r, int g, int b, int a) +{ + CGridEntry *cell = GridEntry(0, row); + cell->m_bUnderline = enabled; + if (enabled) + { + cell->m_iUnderlineOffset = offset; + cell->m_UnderlineColor[0] = r; + cell->m_UnderlineColor[1] = g; + cell->m_UnderlineColor[2] = b; + cell->m_UnderlineColor[3] = a; + } +} + +void CGrid::Clear() +{ + m_xCols = m_yRows = 0; + m_Widths = NULL; + m_GridEntries = NULL; + m_xSpacing = m_ySpacing = 0; + m_bDirty = false; +} + + +CGrid::CGridEntry* CGrid::GridEntry(int x, int y) +{ + AssertCheck(x >= 0 && x < m_xCols && y >= 0 && y < m_yRows, "CGrid::GridEntry: invalid location specified"); + return &m_GridEntries[y*m_xCols + x]; +} + + +void CGrid::CalcColOffsets(int iStart) +{ + int cur = m_xSpacing; + if(iStart != 0) + cur += m_ColOffsets[iStart-1] + m_Widths[iStart-1]; + + for(int i=iStart; i < m_xCols; i++) + { + m_ColOffsets[i] = cur; + cur += m_Widths[i] + m_xSpacing; + } +} + + +void CGrid::CalcRowOffsets(int iStart) +{ + int cur = m_ySpacing; + if(iStart != 0) + cur += m_RowOffsets[iStart-1]; + + for(int i=iStart; i < m_yRows; i++) + { + m_RowOffsets[i] = cur; + cur += m_Heights[i] + m_ySpacing; + } +} + +bool CGrid::getCellAtPoint(int worldX, int worldY, int &row, int &col) +{ + row = -1; col = -1; + for(int x=0; x < m_xCols; x++) + { + for(int y=0; y < m_yRows; y++) + { + Panel *pPanel = GridEntry(x,y)->m_pPanel; + if (!pPanel) + continue; + + if (pPanel->isWithin(worldX, worldY)) + { + col = x; + row = y; + return true; + } + } + } + + return false; +} + + diff --git a/game_shared/vgui_grid.h b/game_shared/vgui_grid.h new file mode 100644 index 0000000..472f509 --- /dev/null +++ b/game_shared/vgui_grid.h @@ -0,0 +1,122 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_GRID_H +#define VGUI_GRID_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "vgui_panel.h" + + +namespace vgui +{ + +// The grid control simply manages a grid of panels. You can adjust column sizes and spacings and +// configure and fill the panels however you want. +// To use this control, call SetDimensions, SetSpacing and fill the controls. +class CGrid : public Panel +{ +public: + CGrid(); + virtual ~CGrid(); + + bool SetDimensions(int xCols, int yRows); // Set how many columns and rows in the grid. + void Term(); + + Panel* GetEntry(int x, int y); // Get the panel associated with a grid entry. + bool SetEntry(int x, int y, Panel *pPanel); + + int GetXSpacing(); + int GetYSpacing(); + void SetSpacing(int xSpacing, int ySpacing); // Set spacing between rows and columns. + + bool SetColumnWidth(int iColumn, int width); // Set a column's width. + bool SetRowHeight(int iRow, int height); // Set a row's height. + + int GetColumnWidth(int iColumn); + int GetRowHeight(int iRow); + + int CalcFitColumnWidth(int iColumn); // Returns the maximum width of all panels in the column. + int CalcFitRowHeight(int iRow); // Returns the maximum height of all panels in the row. + + int CalcDrawHeight(); // Returns how many pixels high the grid control should be + // for all of its contents to be visible (based on its row heights + // and y spacing). + + void AutoSetRowHeights(); // Just does SetRowHeight(iRow, CalcFitRowHeight(iRow)) for all rows. + + bool GetEntryBox( // Returns the bounding box for the specified entry. + int col, int row, int &x, int &y, int &w, int &h); + + bool CopyColumnWidths(CGrid *pOther); // Copy the column widths from the other grid. Fails if the + // column count is different. + + void RepositionContents(); // Sets the size and position of all the grid entries based + // on current spacings and row/column widths. + // You usually only want to call this while setting up the control + // if you want to get the position or dimensions of the child + // controls. This will set them. + + void SetRowUnderline(int row, bool enabled, int offset, int r, int g, int b, int a); // sets underline color for a particular row + + // returns the true if found, false otherwise + bool getCellAtPoint(int worldX, int worldY, int &row, int &col); + +// Panel overrides. +public: + + virtual void paint(); + virtual void paintBackground(); + +protected: + + class CGridEntry + { + public: + CGridEntry(); + ~CGridEntry(); + + Panel *m_pPanel; + + bool m_bUnderline; + short m_UnderlineColor[4]; + int m_iUnderlineOffset; + }; + + void Clear(); + CGridEntry* GridEntry(int x, int y); + + void CalcColOffsets(int iStart); + void CalcRowOffsets(int iStart); + + +protected: + + bool m_bDirty; // Set when controls will need to be repositioned. + + int m_xCols; + int m_yRows; + + int m_xSpacing; + int m_ySpacing; + + int *m_Widths; + int *m_Heights; + int *m_ColOffsets; + int *m_RowOffsets; + + CGridEntry *m_GridEntries; + +}; + +}; + + +#endif // VGUI_GRID_H diff --git a/game_shared/vgui_helpers.cpp b/game_shared/vgui_helpers.cpp new file mode 100644 index 0000000..fbcfeb5 --- /dev/null +++ b/game_shared/vgui_helpers.cpp @@ -0,0 +1,45 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "vgui_helpers.h" + + +using namespace vgui; + + +void AlignPanel(Panel *pChild, Panel *pParent, int alignment) +{ + int w, h, cw, ch; + pParent->getSize(w, h); + pChild->getSize(cw, ch); + + int xCenter = (w - cw) / 2; + int yCenter = (h - ch) / 2; + + if(alignment == Label::a_west) + pChild->setPos(0, yCenter); + else if(alignment == Label::a_northwest) + pChild->setPos(0,0); + else if(alignment == Label::a_north) + pChild->setPos(xCenter, 0); + else if(alignment == Label::a_northeast) + pChild->setPos(w - cw, 0); + else if(alignment == Label::a_east) + pChild->setPos(w - cw, yCenter); + else if(alignment == Label::a_southeast) + pChild->setPos(w - cw, h - ch); + else if(alignment == Label::a_south) + pChild->setPos(xCenter, h - ch); + else if(alignment == Label::a_southwest) + pChild->setPos(0, h - ch); + else if(alignment == Label::a_center) + pChild->setPos(xCenter, yCenter); +} + + + + diff --git a/game_shared/vgui_helpers.h b/game_shared/vgui_helpers.h new file mode 100644 index 0000000..f9ee45e --- /dev/null +++ b/game_shared/vgui_helpers.h @@ -0,0 +1,31 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_HELPERS_H +#define VGUI_HELPERS_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "vgui_panel.h" +#include "vgui_label.h" + + +inline int PanelTop(vgui::Panel *pPanel) {int x,y,w,h; pPanel->getBounds(x,y,w,h); return y;} +inline int PanelLeft(vgui::Panel *pPanel) {int x,y,w,h; pPanel->getBounds(x,y,w,h); return x;} +inline int PanelRight(vgui::Panel *pPanel) {int x,y,w,h; pPanel->getBounds(x,y,w,h); return x+w;} +inline int PanelBottom(vgui::Panel *pPanel) {int x,y,w,h; pPanel->getBounds(x,y,w,h); return y+h;} +inline int PanelWidth(vgui::Panel *pPanel) {int x,y,w,h; pPanel->getBounds(x,y,w,h); return w;} +inline int PanelHeight(vgui::Panel *pPanel) {int x,y,w,h; pPanel->getBounds(x,y,w,h); return h;} + +// Places child at the requested position inside pParent. iAlignment is from Label::Alignment. +void AlignPanel(vgui::Panel *pChild, vgui::Panel *pParent, int alignment); + + +#endif // VGUI_HELPERS_H + diff --git a/game_shared/vgui_listbox.cpp b/game_shared/vgui_listbox.cpp new file mode 100644 index 0000000..972091e --- /dev/null +++ b/game_shared/vgui_listbox.cpp @@ -0,0 +1,207 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "vgui_listbox.h" + + + +using namespace vgui; + + +CListBox::CListBox() : Panel(0, 0, 0, 0), + m_ItemsPanel(0,0,0,0), + m_ScrollBar(0, 0, 0, 0, true), + m_Slider(0, 0, 10, 40, true) +{ + m_Signal.m_pListBox = this; + + m_ItemsPanel.setParent(this); + m_ItemsPanel.setBgColor(0,0,0,255); + + m_Slider.setRangeWindow(50); + m_Slider.setRangeWindowEnabled(true); + + m_ScrollBar.setParent(this); + m_ScrollBar.addIntChangeSignal(&m_Signal); + m_ScrollBar.setSlider(&m_Slider); + m_ScrollBar.setButtonPressedScrollValue(1); + + m_Items.m_pNext = m_Items.m_pPrev = &m_Items; + m_ItemOffset = 0; + m_iScrollMax = -1; +} + +CListBox::~CListBox() +{ + Term(); +} + +void CListBox::Init() +{ + Term(); +} + +void CListBox::Term() +{ + m_ItemOffset = 0; + + // Free the LBItems. + LBItem *pNext; + for(LBItem *pItem=m_Items.m_pNext; pItem != &m_Items; pItem=pNext) + { + pItem->m_pPanel->setParent(NULL); // detach the panel from us + pNext = pItem->m_pNext; + delete pItem; + } + m_Items.m_pPrev = m_Items.m_pNext = &m_Items; +} + +void CListBox::AddItem(Panel* panel) +{ + // Add the item. + LBItem *pItem = new LBItem; + if(!pItem) + return; + + pItem->m_pPanel = panel; + pItem->m_pPanel->setParent(&m_ItemsPanel); + + pItem->m_pPrev = m_Items.m_pPrev; + pItem->m_pNext = &m_Items; + pItem->m_pNext->m_pPrev = pItem->m_pPrev->m_pNext = pItem; + + m_ScrollBar.setRange(0, GetScrollMax()); + m_Slider.setRangeWindow(50); + m_Slider.setRangeWindowEnabled(true); + + InternalLayout(); +} + +int CListBox::GetNumItems() +{ + int count=0; + for(LBItem *pItem=m_Items.m_pNext; pItem != &m_Items; pItem=pItem->m_pNext) + ++count; + + return count; +} + +int CListBox::GetItemWidth() +{ + int wide, tall; + m_ItemsPanel.getSize(wide, tall); + return wide; +} + +int CListBox::GetScrollPos() +{ + return m_ItemOffset; +} + +void CListBox::SetScrollPos(int pos) +{ + int maxItems = GetScrollMax(); + if(maxItems < 0) + return; + + m_ItemOffset = (pos < 0) ? 0 : ((pos > maxItems) ? maxItems : pos); + InternalLayout(); +} + +void CListBox::setPos(int x, int y) +{ + Panel::setPos(x, y); + InternalLayout(); +} + +void CListBox::setSize(int wide,int tall) +{ + Panel::setSize(wide,tall); + InternalLayout(); +} + +void CListBox::setPixelScroll(int value) +{ + m_ItemOffset = m_ScrollBar.getValue(); + InternalLayout(); +} + +void CListBox::InternalLayout() +{ + int x, y, wide, tall; + getBounds(x, y, wide, tall); + + // Reposition the main panel and the scrollbar. + m_ItemsPanel.setBounds(0, 0, wide-15, tall); + m_ScrollBar.setBounds(wide-15, 0, 15, tall); + + bool bNeedScrollbar = false; + + // Reposition the items. + int curItem = 0; + int curY = 0; + int maxItem = GetScrollMax(); + for(LBItem *pItem=m_Items.m_pNext; pItem != &m_Items; pItem=pItem->m_pNext) + { + if(curItem < m_ItemOffset) + { + pItem->m_pPanel->setVisible(false); + bNeedScrollbar = true; + } + else if (curItem >= maxItem) + { + // item is past the end of the items we care about + pItem->m_pPanel->setVisible(false); + } + else + { + pItem->m_pPanel->setVisible(true); + + int itemWidth, itemHeight; + pItem->m_pPanel->getSize(itemWidth, itemHeight); + + // Don't change the item's height but change its width to fit the listbox. + pItem->m_pPanel->setBounds(0, curY, wide, itemHeight); + + curY += itemHeight; + + if (curY > tall) + { + bNeedScrollbar = true; + } + } + + ++curItem; + } + + m_ScrollBar.setVisible(bNeedScrollbar); + + repaint(); +} + +void CListBox::paintBackground() +{ +} + +void CListBox::SetScrollRange(int maxScroll) +{ + m_iScrollMax = maxScroll; + m_ScrollBar.setRange(0, maxScroll); + InternalLayout(); +} + +int CListBox::GetScrollMax() +{ + if (m_iScrollMax < 0) + { + return GetNumItems() - 1; + } + + return m_iScrollMax; +} + + diff --git a/game_shared/vgui_listbox.h b/game_shared/vgui_listbox.h new file mode 100644 index 0000000..a48acef --- /dev/null +++ b/game_shared/vgui_listbox.h @@ -0,0 +1,115 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VOICE_LISTBOX_H +#define VOICE_LISTBOX_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "VGUI_Panel.h" +#include "VGUI_IntChangeSignal.h" + +#include "VGUI_Slider2.h" +#include "VGUI_ScrollBar2.h" + + +namespace vgui +{ + +// Listbox class used by voice code. Based off of vgui's list panel but with some modifications: +// - This listbox clips its child items to its rectangle. +// - You can access things like the scrollbar and find out the item width. +// - The scrollbar scrolls one element at a time and the range is correct. + +// Note: this listbox does not provide notification when items are +class CListBox : public Panel +{ +public: + + CListBox(); + ~CListBox(); + + void Init(); + void Term(); + + // Add an item to the listbox. This automatically sets the item's parent to the listbox + // and resizes the item's width to fit within the listbox. + void AddItem(Panel *pPanel); + + // Get the number of items currently in the listbox. + int GetNumItems(); + + // Get the width that listbox items will be set to (this changes if you resize the listbox). + int GetItemWidth(); + + // Get/set the scrollbar position (position says which element is at the top of the listbox). + int GetScrollPos(); + void SetScrollPos(int pos); + + // sets the last item the listbox should scroll to + // scroll to GetNumItems() if not set + void SetScrollRange(int maxScroll); + + // returns the maximum value the scrollbar can scroll to + int GetScrollMax(); + +// vgui overrides. +public: + + virtual void setPos(int x, int y); + virtual void setSize(int wide,int tall); + virtual void setPixelScroll(int value); + virtual void paintBackground(); + + +protected: + + class LBItem + { + public: + Panel *m_pPanel; + LBItem *m_pPrev, *m_pNext; + }; + + class ListBoxSignal : public IntChangeSignal + { + public: + void intChanged(int value,Panel* panel) + { + m_pListBox->setPixelScroll(-value); + } + + vgui::CListBox *m_pListBox; + }; + + +protected: + + void InternalLayout(); + + +protected: + + // All the items.. + LBItem m_Items; + + Panel m_ItemsPanel; + + int m_ItemOffset; // where we're scrolled to + Slider2 m_Slider; + ScrollBar2 m_ScrollBar; + ListBoxSignal m_Signal; + + int m_iScrollMax; +}; + +} + + +#endif // VOICE_LISTBOX_H diff --git a/game_shared/vgui_loadtga.cpp b/game_shared/vgui_loadtga.cpp new file mode 100644 index 0000000..3e7a7ad --- /dev/null +++ b/game_shared/vgui_loadtga.cpp @@ -0,0 +1,93 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "wrect.h" +#include "../cl_dll/cl_dll.h" +#include "vgui.h" +#include "vgui_loadtga.h" +#include "vgui_inputstream.h" + + +// ---------------------------------------------------------------------- // +// Helper class for loading tga files. +// ---------------------------------------------------------------------- // +class MemoryInputStream : public vgui::InputStream +{ +public: + MemoryInputStream() + { + m_pData = NULL; + m_DataLen = m_ReadPos = 0; + } + + virtual void seekStart(bool& success) {m_ReadPos=0; success=true;} + virtual void seekRelative(int count,bool& success) {m_ReadPos+=count; success=true;} + virtual void seekEnd(bool& success) {m_ReadPos=m_DataLen; success=true;} + virtual int getAvailable(bool& success) {success=false; return 0;} // This is what vgui does for files... + + virtual uchar readUChar(bool& success) + { + if(m_ReadPos>=0 && m_ReadPos +#include +#include +#include + +using namespace vgui; + + +namespace +{ +class FooDefaultScrollBarIntChangeSignal : public IntChangeSignal +{ +public: + FooDefaultScrollBarIntChangeSignal(ScrollBar2* scrollBar) + { + _scrollBar=scrollBar; + } + virtual void intChanged(int value,Panel* panel) + { + _scrollBar->fireIntChangeSignal(); + } +protected: + ScrollBar2* _scrollBar; +}; + +class FooDefaultButtonSignal : public ActionSignal +{ +public: + ScrollBar2* _scrollBar; + int _buttonIndex; +public: + FooDefaultButtonSignal(ScrollBar2* scrollBar,int buttonIndex) + { + _scrollBar=scrollBar; + _buttonIndex=buttonIndex; + } +public: + virtual void actionPerformed(Panel* panel) + { + _scrollBar->doButtonPressed(_buttonIndex); + } +}; + +} + +//----------------------------------------------------------------------------- +// Purpose: Default scrollbar button +// Draws in new scoreboard style +//----------------------------------------------------------------------------- +class ScrollBarButton : public Button +{ +private: + LineBorder m_Border; + +public: + ScrollBarButton(const char *filename, int x, int y, int wide, int tall) : m_Border(Color(60, 60, 60, 0)), Button("", x, y, wide, tall) + { + Image *image = vgui_LoadTGA(filename); + if (image) + { + image->setColor(Color(140, 140, 140, 0)); + setImage(image); + } + + setBorder(&m_Border); + } + + virtual void paintBackground() + { + int wide,tall; + getPaintSize(wide,tall); + + // fill the background + drawSetColor(0, 0, 0, 0); + drawFilledRect(0, 0, wide, tall); + } +}; + + + + +//----------------------------------------------------------------------------- +// Purpose: Constructor +// Input : x - +// y - +// wide - +// tall - +// vertical - +//----------------------------------------------------------------------------- +ScrollBar2::ScrollBar2(int x,int y,int wide,int tall,bool vertical) : Panel(x,y,wide,tall) +{ + _slider=null; + _button[0]=null; + _button[1]=null; + + if(vertical) + { + setSlider(new Slider2(0,wide-1,wide,(tall-(wide*2))+2,true)); + setButton(new ScrollBarButton("gfx/vgui/arrowup.tga",0,0,wide,wide),0); + setButton(new ScrollBarButton("gfx/vgui/arrowdown.tga",0,tall-wide,wide,wide),1); + } + else + { + // untested code + setSlider(new Slider2(tall,0,wide-(tall*2),tall,false)); + setButton(new ScrollBarButton("gfx/vgui/320_arrowlt.tga",0,0,tall+1,tall+1),0); + setButton(new ScrollBarButton("gfx/vgui/320_arrowrt.tga",wide-tall,0,tall+1,tall+1),1); + } + + setPaintBorderEnabled(true); + setPaintBackgroundEnabled(true); + setPaintEnabled(true); + setButtonPressedScrollValue(15); + + validate(); + } + +void ScrollBar2::setSize(int wide,int tall) +{ + Panel::setSize(wide,tall); + + if(_slider==null) + { + return; + } + + if(_button[0]==null) + { + return; + } + + if(_button[1]==null) + { + return; + } + + getPaintSize(wide,tall); + + if(_slider->isVertical()) + { + _slider->setBounds(0,wide,wide,tall-(wide*2)); + //_slider->setBounds(0,0,wide,tall); + _button[0]->setBounds(0,0,wide,wide); + _button[1]->setBounds(0,tall-wide,wide,wide); + } + else + { + _slider->setBounds(tall,0,wide-(tall*2),tall); + //_slider->setBounds(0,0,wide,tall); + _button[0]->setBounds(0,0,tall,tall); + _button[1]->setBounds((wide-tall),0,tall,tall); + } +} + +void ScrollBar2::performLayout() +{ +} + +void ScrollBar2::setValue(int value) +{ + _slider->setValue(value); +} + +int ScrollBar2::getValue() +{ + return _slider->getValue(); +} + +void ScrollBar2::addIntChangeSignal(IntChangeSignal* s) +{ + _intChangeSignalDar.putElement(s); + _slider->addIntChangeSignal(new FooDefaultScrollBarIntChangeSignal(this)); +} + +void ScrollBar2::setRange(int min,int max) +{ + _slider->setRange(min,max); +} + +void ScrollBar2::fireIntChangeSignal() +{ + for(int i=0;i<_intChangeSignalDar.getCount();i++) + { + _intChangeSignalDar[i]->intChanged(_slider->getValue(),this); + } +} + +bool ScrollBar2::isVertical() +{ + return _slider->isVertical(); +} + +bool ScrollBar2::hasFullRange() +{ + return _slider->hasFullRange(); +} + +//LEAK: new and old slider will leak +void ScrollBar2::setButton(Button* button,int index) +{ + if(_button[index]!=null) + { + removeChild(_button[index]); + } + _button[index]=button; + addChild(_button[index]); + + _button[index]->addActionSignal(new FooDefaultButtonSignal(this,index)); + + validate(); + + //_button[index]->setVisible(false); +} + +Button* ScrollBar2::getButton(int index) +{ + return _button[index]; +} + +//LEAK: new and old slider will leak +void ScrollBar2::setSlider(Slider2 *slider) +{ + if(_slider!=null) + { + removeChild(_slider); + } + _slider=slider; + addChild(_slider); + + _slider->addIntChangeSignal(new FooDefaultScrollBarIntChangeSignal(this)); + + validate(); +} + +Slider2 *ScrollBar2::getSlider() +{ + return _slider; +} + +void ScrollBar2::doButtonPressed(int buttonIndex) +{ + if(buttonIndex==0) + { + _slider->setValue(_slider->getValue()-_buttonPressedScrollValue); + } + else + { + _slider->setValue(_slider->getValue()+_buttonPressedScrollValue); + } + +} + +void ScrollBar2::setButtonPressedScrollValue(int value) +{ + _buttonPressedScrollValue=value; +} + +void ScrollBar2::setRangeWindow(int rangeWindow) +{ + _slider->setRangeWindow(rangeWindow); +} + +void ScrollBar2::setRangeWindowEnabled(bool state) +{ + _slider->setRangeWindowEnabled(state); +} + +void ScrollBar2::validate() +{ + if(_slider!=null) + { + int buttonOffset=0; + + for(int i=0;i<2;i++) + { + if(_button[i]!=null) + { + if(_button[i]->isVisible()) + { + if(_slider->isVertical()) + { + buttonOffset+=_button[i]->getTall(); + } + else + { + buttonOffset+=_button[i]->getWide(); + } + } + } + } + + _slider->setButtonOffset(buttonOffset); + } + + int wide,tall; + getSize(wide,tall); + setSize(wide,tall); +} diff --git a/game_shared/vgui_scrollbar2.h b/game_shared/vgui_scrollbar2.h new file mode 100644 index 0000000..b9dfea6 --- /dev/null +++ b/game_shared/vgui_scrollbar2.h @@ -0,0 +1,62 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_SCROLLBAR2_H +#define VGUI_SCROLLBAR2_H +#ifdef _WIN32 +#pragma once +#endif + +#include +#include +#include + +namespace vgui +{ + +class IntChangeSignal; +class Button; +class Slider2; + +//----------------------------------------------------------------------------- +// Purpose: Hacked up version of the vgui scrollbar +//----------------------------------------------------------------------------- +class VGUIAPI ScrollBar2 : public Panel +{ +public: + ScrollBar2(int x,int y,int wide,int tall,bool vertical); +public: + virtual void setValue(int value); + virtual int getValue(); + virtual void addIntChangeSignal(IntChangeSignal* s); + virtual void setRange(int min,int max); + virtual void setRangeWindow(int rangeWindow); + virtual void setRangeWindowEnabled(bool state); + virtual void setSize(int wide,int tall); + virtual bool isVertical(); + virtual bool hasFullRange(); + virtual void setButton(Button *button,int index); + virtual Button* getButton(int index); + virtual void setSlider(Slider2 *slider); + virtual Slider2 *getSlider(); + virtual void doButtonPressed(int buttonIndex); + virtual void setButtonPressedScrollValue(int value); + virtual void validate(); +public: //bullshit public + virtual void fireIntChangeSignal(); +protected: + virtual void performLayout(); +protected: + Button* _button[2]; + Slider2 *_slider; + Dar _intChangeSignalDar; + int _buttonPressedScrollValue; +}; + +} + +#endif // VGUI_SCROLLBAR2_H diff --git a/game_shared/vgui_slider2.cpp b/game_shared/vgui_slider2.cpp new file mode 100644 index 0000000..7a220b5 --- /dev/null +++ b/game_shared/vgui_slider2.cpp @@ -0,0 +1,436 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: New version of the slider bar +// +// $NoKeywords: $ +//============================================================================= + +#include "VGUI_Slider2.h" + +#include +#include +#include +#include + +using namespace vgui; + +namespace +{ +class FooDefaultSliderSignal : public InputSignal +{ +private: + Slider2* _slider; +public: + FooDefaultSliderSignal(Slider2* slider) + { + _slider=slider; + } +public: + void cursorMoved(int x,int y,Panel* panel) + { + _slider->privateCursorMoved(x,y,panel); + } + void cursorEntered(Panel* panel){} + void cursorExited(Panel* panel){} + void mouseDoublePressed(MouseCode code,Panel* panel){} + void mousePressed(MouseCode code,Panel* panel) + { + _slider->privateMousePressed(code,panel); + } + void mouseReleased(MouseCode code,Panel* panel) + { + _slider->privateMouseReleased(code,panel); + } + void mouseWheeled(int delta,Panel* panel){} + void keyPressed(KeyCode code,Panel* panel){} + void keyTyped(KeyCode code,Panel* panel){} + void keyReleased(KeyCode code,Panel* panel){} + void keyFocusTicked(Panel* panel){} +}; +} + +Slider2::Slider2(int x,int y,int wide,int tall,bool vertical) : Panel(x,y,wide,tall) +{ + _vertical=vertical; + _dragging=false; + _value=0; + _range[0]=0; + _range[1]=299; + _rangeWindow=0; + _rangeWindowEnabled=false; + _buttonOffset=0; + recomputeNobPosFromValue(); + addInputSignal(new FooDefaultSliderSignal(this)); +} + +void Slider2::setSize(int wide,int tall) +{ + Panel::setSize(wide,tall); + recomputeNobPosFromValue(); +} + +bool Slider2::isVertical() +{ + return _vertical; +} + +void Slider2::setValue(int value) +{ + int oldValue=_value; + + if(value<_range[0]) + { + value=_range[0]; + } + + if(value>_range[1]) + { + value=_range[1]; + } + + _value=value; + recomputeNobPosFromValue(); + + if(_value!=oldValue) + { + fireIntChangeSignal(); + } +} + +int Slider2::getValue() +{ + return _value; +} + +void Slider2::recomputeNobPosFromValue() +{ + int wide,tall; + + getPaintSize(wide,tall); + + float fwide=(float)wide; + float ftall=(float)tall; + float frange=(float)(_range[1]-_range[0]); + float fvalue=(float)(_value-_range[0]); + float fper=fvalue/frange; + float frangewindow=(float)(_rangeWindow); + + if(frangewindow<0) + { + frangewindow=0; + } + + if(!_rangeWindowEnabled) + { + frangewindow=frange; + } + + if ( frangewindow > 0 ) + { + if(_vertical) + { + float fnobsize=frangewindow; + float freepixels = ftall - fnobsize; + + float firstpixel = freepixels * fper; + + _nobPos[0]=(int)( firstpixel ); + _nobPos[1]=(int)( firstpixel + fnobsize ); + + if(_nobPos[1]>tall) + { + _nobPos[0]=tall-((int)fnobsize); + _nobPos[1]=tall; + } + } + else + { + float fnobsize=frangewindow; + float freepixels = fwide - fnobsize; + + float firstpixel = freepixels * fper; + + _nobPos[0]=(int)( firstpixel ); + _nobPos[1]=(int)( firstpixel + fnobsize ); + + if(_nobPos[1]>wide) + { + _nobPos[0]=wide-((int)fnobsize); + _nobPos[1]=wide; + } + } + } + + repaint(); +} + +void Slider2::recomputeValueFromNobPos() +{ + int wide,tall; + getPaintSize(wide,tall); + + float fwide=(float)wide; + float ftall=(float)tall; + float frange=(float)(_range[1]-_range[0]); + float fvalue=(float)(_value-_range[0]); + float fnob=(float)_nobPos[0]; + float frangewindow=(float)(_rangeWindow); + + if(frangewindow<0) + { + frangewindow=0; + } + + if(!_rangeWindowEnabled) + { + frangewindow=frange; + } + + if ( frangewindow > 0 ) + { + if(_vertical) + { + float fnobsize=frangewindow; + fvalue=frange*(fnob/(ftall-fnobsize)); + } + else + { + float fnobsize=frangewindow; + fvalue=frange*(fnob/(fwide-fnobsize)); + } + } + // Take care of rounding issues. + _value=(int)(fvalue+_range[0]+0.5); + + // Clamp final result + _value = ( _value < _range[1] ) ? _value : _range[1]; +} + +bool Slider2::hasFullRange() +{ + int wide,tall; + getPaintSize(wide,tall); + + float fwide=(float)wide; + float ftall=(float)tall; + float frange=(float)(_range[1]-_range[0]); + float frangewindow=(float)(_rangeWindow); + + if(frangewindow<0) + { + frangewindow=0; + } + + if(!_rangeWindowEnabled) + { + frangewindow=frange; + } + + if ( frangewindow > 0 ) + { + if(_vertical) + { + if( frangewindow <= ( ftall + _buttonOffset ) ) + { + return true; + } + } + else + { + if( frangewindow <= ( fwide + _buttonOffset ) ) + { + return true; + } + } + } + + return false; +} + +void Slider2::addIntChangeSignal(IntChangeSignal* s) +{ + _intChangeSignalDar.putElement(s); +} + +void Slider2::fireIntChangeSignal() +{ + for(int i=0;i<_intChangeSignalDar.getCount();i++) + { + _intChangeSignalDar[i]->intChanged(getValue(),this); + } +} + +void Slider2::paintBackground() +{ + int wide,tall; + getPaintSize(wide,tall); + + if (_vertical) + { + // background behind slider + drawSetColor(40, 40, 40, 0); + drawFilledRect(0, 0, wide, tall); + + // slider front + drawSetColor(0, 0, 0, 0); + drawFilledRect(0,_nobPos[0],wide,_nobPos[1]); + + // slider border + drawSetColor(60, 60, 60, 0); + drawFilledRect(0,_nobPos[0],wide,_nobPos[0]+1); // top + drawFilledRect(0,_nobPos[1],wide,_nobPos[1]+1); // bottom + drawFilledRect(0,_nobPos[0]+1,1,_nobPos[1]); // left + drawFilledRect(wide-1,_nobPos[0]+1,wide,_nobPos[1]); // right + } + else + { + //!! doesn't work + + drawSetColor(Scheme::sc_secondary3); + drawFilledRect(0,0,wide,tall); + + drawSetColor(Scheme::sc_black); + drawOutlinedRect(0,0,wide,tall); + + drawSetColor(Scheme::sc_primary2); + drawFilledRect(_nobPos[0],0,_nobPos[1],tall); + + drawSetColor(Scheme::sc_black); + drawOutlinedRect(_nobPos[0],0,_nobPos[1],tall); + } +} + +void Slider2::setRange(int min,int max) +{ + if(maxmax) + { + min=max; + } + + _range[0]=min; + _range[1]=max; +} + +void Slider2::getRange(int& min,int& max) +{ + min=_range[0]; + max=_range[1]; +} + +void Slider2::privateCursorMoved(int x,int y,Panel* panel) +{ + if(!_dragging) + { + return; + } + + getApp()->getCursorPos(x,y); + screenToLocal(x,y); + + int wide,tall; + getPaintSize(wide,tall); + + if(_vertical) + { + _nobPos[0]=_nobDragStartPos[0]+(y-_dragStartPos[1]); + _nobPos[1]=_nobDragStartPos[1]+(y-_dragStartPos[1]); + + if(_nobPos[1]>tall) + { + _nobPos[0]=tall-(_nobPos[1]-_nobPos[0]); + _nobPos[1]=tall; + } + + if(_nobPos[0]<0) + { + _nobPos[1]=_nobPos[1]-_nobPos[0]; + _nobPos[0]=0; + } + } + else + { + _nobPos[0]=_nobDragStartPos[0]+(x-_dragStartPos[0]); + _nobPos[1]=_nobDragStartPos[1]+(x-_dragStartPos[0]); + + if(_nobPos[1]>wide) + { + _nobPos[0]=wide-(_nobPos[1]-_nobPos[0]); + _nobPos[1]=wide; + } + + if(_nobPos[0]<0) + { + _nobPos[1]=_nobPos[1]-_nobPos[0]; + _nobPos[0]=0; + } + } + + recomputeValueFromNobPos(); + repaint(); + fireIntChangeSignal(); +} + +void Slider2::privateMousePressed(MouseCode code,Panel* panel) +{ + int x,y; + getApp()->getCursorPos(x,y); + screenToLocal(x,y); + + if(_vertical) + { + if((y>=_nobPos[0])&&(y<_nobPos[1])) + { + _dragging=true; + getApp()->setMouseCapture(this); + _nobDragStartPos[0]=_nobPos[0]; + _nobDragStartPos[1]=_nobPos[1]; + _dragStartPos[0]=x; + _dragStartPos[1]=y; + } + } + else + { + if((x>=_nobPos[0])&&(x<_nobPos[1])) + { + _dragging=true; + getApp()->setMouseCapture(this); + _nobDragStartPos[0]=_nobPos[0]; + _nobDragStartPos[1]=_nobPos[1]; + _dragStartPos[0]=x; + _dragStartPos[1]=y; + } + } + +} + +void Slider2::privateMouseReleased(MouseCode code,Panel* panel) +{ + _dragging=false; + getApp()->setMouseCapture(null); +} + +void Slider2::getNobPos(int& min, int& max) +{ + min=_nobPos[0]; + max=_nobPos[1]; +} + +void Slider2::setRangeWindow(int rangeWindow) +{ + _rangeWindow=rangeWindow; +} + +void Slider2::setRangeWindowEnabled(bool state) +{ + _rangeWindowEnabled=state; +} + +void Slider2::setButtonOffset(int buttonOffset) +{ + _buttonOffset=buttonOffset; +} \ No newline at end of file diff --git a/game_shared/vgui_slider2.h b/game_shared/vgui_slider2.h new file mode 100644 index 0000000..ec38421 --- /dev/null +++ b/game_shared/vgui_slider2.h @@ -0,0 +1,67 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_SLIDER2_H +#define VGUI_SLIDER2_H +#ifdef _WIN32 +#pragma once +#endif + +#include +#include +#include + +namespace vgui +{ + +enum MouseCode; +class IntChangeSignal; + +class VGUIAPI Slider2 : public Panel +{ +private: + bool _vertical; + bool _dragging; + int _nobPos[2]; + int _nobDragStartPos[2]; + int _dragStartPos[2]; + Dar _intChangeSignalDar; + int _range[2]; + int _value; + int _rangeWindow; + bool _rangeWindowEnabled; + int _buttonOffset; +public: + Slider2(int x,int y,int wide,int tall,bool vertical); +public: + virtual void setValue(int value); + virtual int getValue(); + virtual bool isVertical(); + virtual void addIntChangeSignal(IntChangeSignal* s); + virtual void setRange(int min,int max); + virtual void getRange(int& min,int& max); + virtual void setRangeWindow(int rangeWindow); + virtual void setRangeWindowEnabled(bool state); + virtual void setSize(int wide,int tall); + virtual void getNobPos(int& min, int& max); + virtual bool hasFullRange(); + virtual void setButtonOffset(int buttonOffset); +private: + virtual void recomputeNobPosFromValue(); + virtual void recomputeValueFromNobPos(); +public: //bullshit public + virtual void privateCursorMoved(int x,int y,Panel* panel); + virtual void privateMousePressed(MouseCode code,Panel* panel); + virtual void privateMouseReleased(MouseCode code,Panel* panel); +protected: + virtual void fireIntChangeSignal(); + virtual void paintBackground(); +}; + +} + +#endif // VGUI_SLIDER2_H diff --git a/game_shared/virtualfs.h b/game_shared/virtualfs.h new file mode 100644 index 0000000..b43d46c --- /dev/null +++ b/game_shared/virtualfs.h @@ -0,0 +1,302 @@ +//======================================================================= +// Copyright (C) XashXT Group 2014 +// virtualfs.h - Virtual FileSystem that writes into memory +//======================================================================= +#ifndef VIRTUALFS_H +#define VIRTUALFS_H + +#include + +#define FS_MEM_BLOCK 65535 +#define FS_MSG_BLOCK 8192 + +class CVirtualFS +{ +public: + CVirtualFS(); + CVirtualFS( const byte *file, size_t size ); + ~CVirtualFS(); + + size_t Read( void *out, size_t size ); + size_t Write( const void *in, size_t size ); + size_t Insert( const void *in, size_t size ); + size_t Print( const char *message ); + size_t IPrint( const char *message ); + size_t Printf( const char *fmt, ... ); + size_t IPrintf( const char *fmt, ... ); + size_t VPrintf( const char *fmt, va_list ap ); + size_t IVPrintf( const char *fmt, va_list ap ); + char *GetBuffer( void ) { return (char *)m_pBuffer; }; + size_t GetSize( void ) { return m_iLength; }; + size_t Tell( void ) { return m_iOffset; } + bool Eof( void ) { return (m_iOffset == m_iLength) ? true : false; } + int Seek( size_t offset, int whence ); + int Gets( char *string, size_t size ); + int Getc( void ); +private: + byte *m_pBuffer; // file buffer + size_t m_iBuffSize; // real buffer size + size_t m_iOffset; // buffer position + size_t m_iLength; // buffer current size +}; + +_forceinline CVirtualFS :: CVirtualFS() +{ + m_iBuffSize = FS_MEM_BLOCK; // can be resized later + m_pBuffer = new byte[m_iBuffSize]; + memset( m_pBuffer, 0, m_iBuffSize ); + m_iLength = m_iOffset = 0; +} + +_forceinline CVirtualFS :: CVirtualFS( const byte *file, size_t size ) +{ + if( !file || size <= 0 ) + { + m_iBuffSize = m_iOffset = m_iLength = 0; + m_pBuffer = NULL; + return; + } + + m_iLength = m_iBuffSize = size; + m_pBuffer = new byte[m_iBuffSize]; + memcpy( m_pBuffer, file, m_iBuffSize ); + m_iOffset = 0; +} + +_forceinline CVirtualFS :: ~CVirtualFS() +{ + delete [] m_pBuffer; +} + +_forceinline size_t CVirtualFS :: Read( void *out, size_t size ) +{ + if( !m_pBuffer || !out || size <= 0 ) + return 0; + + // check for enough room + if( m_iOffset >= m_iLength ) + return 0; // hit EOF + + size_t read_size = 0; + + if( m_iOffset + size <= m_iLength ) + { + memcpy( out, m_pBuffer + m_iOffset, size ); + m_iOffset += size; + read_size = size; + } + else + { + int reduced_size = m_iLength - m_iOffset; + memcpy( out, m_pBuffer + m_iOffset, reduced_size ); + m_iOffset += reduced_size; + read_size = reduced_size; + } + + return read_size; +} + +_forceinline size_t CVirtualFS :: Write( const void *in, size_t size ) +{ + if( !m_pBuffer ) return -1; + + if( m_iOffset + size >= m_iBuffSize ) + { + size_t newsize = m_iOffset + size + FS_MEM_BLOCK; + + if( m_iBuffSize < newsize ) + { + // reallocate buffer now + m_pBuffer = (byte *)realloc( m_pBuffer, newsize ); + memset( m_pBuffer + m_iBuffSize, 0, newsize - m_iBuffSize ); + m_iBuffSize = newsize; // update buffsize + } + } + + // write into buffer + memcpy( m_pBuffer + m_iOffset, in, size ); + m_iOffset += size; + + if( m_iOffset > m_iLength ) + m_iLength = m_iOffset; + + return m_iLength; +} + +_forceinline size_t CVirtualFS :: Insert( const void *in, size_t size ) +{ + if( !m_pBuffer ) return -1; + + if( m_iLength + size >= m_iBuffSize ) + { + size_t newsize = m_iLength + size + FS_MEM_BLOCK; + + if( m_iBuffSize < newsize ) + { + // reallocate buffer now + m_pBuffer = (byte *)realloc( m_pBuffer, newsize ); + memset( m_pBuffer + m_iBuffSize, 0, newsize - m_iBuffSize ); + m_iBuffSize = newsize; // update buffsize + } + } + + // backup right part + size_t rp_size = m_iLength - m_iOffset; + byte *backup = new byte[rp_size]; + memcpy( backup, m_pBuffer + m_iOffset, rp_size ); + + // insert into buffer + memcpy( m_pBuffer + m_iOffset, in, size ); + m_iOffset += size; + + // write right part buffer + memcpy( m_pBuffer + m_iOffset, backup, rp_size ); + delete [] backup; + + if(( m_iOffset + rp_size ) > m_iLength ) + m_iLength = m_iOffset + rp_size; + + return m_iLength; +} + +_forceinline size_t CVirtualFS :: Print( const char *message ) +{ + return Write( message, Q_strlen( message )); +} + +_forceinline size_t CVirtualFS :: IPrint( const char *message ) +{ + return Insert( message, Q_strlen( message )); +} + +_forceinline size_t CVirtualFS :: Printf( const char *fmt, ... ) +{ + size_t result; + va_list args; + + va_start( args, fmt ); + result = VPrintf( fmt, args ); + va_end( args ); + + return result; +} + +_forceinline size_t CVirtualFS :: IPrintf( const char *fmt, ... ) +{ + size_t result; + va_list args; + + va_start( args, fmt ); + result = IVPrintf( fmt, args ); + va_end( args ); + + return result; +} + +_forceinline size_t CVirtualFS :: VPrintf( const char *fmt, va_list ap ) +{ + size_t buff_size = FS_MSG_BLOCK; + char *tempbuff; + size_t len; + + while( 1 ) + { + tempbuff = new char[buff_size]; + len = Q_vsprintf( tempbuff, fmt, ap ); + if( len >= 0 && len < buff_size ) + break; + delete [] tempbuff; + buff_size <<= 1; + } + + len = Write( tempbuff, len ); + delete [] tempbuff; + + return len; +} + +_forceinline size_t CVirtualFS :: IVPrintf( const char *fmt, va_list ap ) +{ + size_t buff_size = FS_MSG_BLOCK; + char *tempbuff; + size_t len; + + while( 1 ) + { + tempbuff = new char[buff_size]; + len = Q_vsprintf( tempbuff, fmt, ap ); + if( len >= 0 && len < buff_size ) + break; + delete [] tempbuff; + buff_size <<= 1; + } + + len = Insert( tempbuff, len ); + delete [] tempbuff; + + return len; +} + +_forceinline int CVirtualFS :: Getc( void ) +{ + char c; + + if( !Read( &c, 1 )) + return EOF; + return (byte)c; +} + +_forceinline int CVirtualFS :: Gets( char *string, size_t size ) +{ + size_t end = 0; + int c; + + while( 1 ) + { + c = Getc(); + + if( c == '\r' || c == '\n' || c < 0 ) + break; + + if( end < ( size - 1 )) + string[end++] = c; + } + + string[end] = 0; + + // remove \n following \r + if( c == '\r' ) + { + c = Getc(); + if( c != '\n' ) Seek( -1, SEEK_CUR ); // rewind + } + + return c; +} + +_forceinline int CVirtualFS :: Seek( size_t offset, int whence ) +{ + // Compute the file offset + switch( whence ) + { + case SEEK_CUR: + offset += m_iOffset; + break; + case SEEK_SET: + break; + case SEEK_END: + offset += m_iLength; + break; + default: + return -1; + } + + if(( offset < 0 ) || ( offset > m_iLength )) + return -1; + + m_iOffset = offset; + + return 0; +} + +#endif//VIRTUALFS_H \ No newline at end of file diff --git a/game_shared/voice_banmgr.cpp b/game_shared/voice_banmgr.cpp new file mode 100644 index 0000000..470e80e --- /dev/null +++ b/game_shared/voice_banmgr.cpp @@ -0,0 +1,197 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include +#include +#include "voice_banmgr.h" + + +#define BANMGR_FILEVERSION 1 +char const *g_pBanMgrFilename = "voice_ban.dt"; + + + +// Hash a player ID to a byte. +unsigned char HashPlayerID(char const playerID[16]) +{ + unsigned char curHash = 0; + + for(int i=0; i < 16; i++) + curHash += (unsigned char)playerID[i]; + + return curHash; +} + + + +CVoiceBanMgr::CVoiceBanMgr() +{ + Clear(); +} + + +CVoiceBanMgr::~CVoiceBanMgr() +{ + Term(); +} + + +bool CVoiceBanMgr::Init(char const *pGameDir) +{ + Term(); + + char filename[512]; + _snprintf(filename, sizeof(filename), "%s/%s", pGameDir, g_pBanMgrFilename); + + // Load in the squelch file. + FILE *fp = fopen(filename, "rb"); + if(fp) + { + int version; + fread(&version, 1, sizeof(version), fp); + if(version == BANMGR_FILEVERSION) + { + fseek(fp, 0, SEEK_END); + int nIDs = (ftell(fp) - sizeof(version)) / 16; + fseek(fp, sizeof(version), SEEK_SET); + + for(int i=0; i < nIDs; i++) + { + char playerID[16]; + fread(playerID, 1, 16, fp); + AddBannedPlayer(playerID); + } + } + + fclose(fp); + } + + return true; +} + + +void CVoiceBanMgr::Term() +{ + // Free all the player structures. + for(int i=0; i < 256; i++) + { + BannedPlayer *pListHead = &m_PlayerHash[i]; + BannedPlayer *pNext; + for(BannedPlayer *pCur=pListHead->m_pNext; pCur != pListHead; pCur=pNext) + { + pNext = pCur->m_pNext; + delete pCur; + } + } + + Clear(); +} + + +void CVoiceBanMgr::SaveState(char const *pGameDir) +{ + // Save the file out. + char filename[512]; + _snprintf(filename, sizeof(filename), "%s/%s", pGameDir, g_pBanMgrFilename); + + FILE *fp = fopen(filename, "wb"); + if(fp) + { + int version = BANMGR_FILEVERSION; + fwrite(&version, 1, sizeof(version), fp); + + for(int i=0; i < 256; i++) + { + BannedPlayer *pListHead = &m_PlayerHash[i]; + for(BannedPlayer *pCur=pListHead->m_pNext; pCur != pListHead; pCur=pCur->m_pNext) + { + fwrite(pCur->m_PlayerID, 1, 16, fp); + } + } + + fclose(fp); + } +} + + +bool CVoiceBanMgr::GetPlayerBan(char const playerID[16]) +{ + return !!InternalFindPlayerSquelch(playerID); +} + + +void CVoiceBanMgr::SetPlayerBan(char const playerID[16], bool bSquelch) +{ + if(bSquelch) + { + // Is this guy already squelched? + if(GetPlayerBan(playerID)) + return; + + AddBannedPlayer(playerID); + } + else + { + BannedPlayer *pPlayer = InternalFindPlayerSquelch(playerID); + if(pPlayer) + { + pPlayer->m_pPrev->m_pNext = pPlayer->m_pNext; + pPlayer->m_pNext->m_pPrev = pPlayer->m_pPrev; + delete pPlayer; + } + } +} + + +void CVoiceBanMgr::ForEachBannedPlayer(void (*callback)(char id[16])) +{ + for(int i=0; i < 256; i++) + { + for(BannedPlayer *pCur=m_PlayerHash[i].m_pNext; pCur != &m_PlayerHash[i]; pCur=pCur->m_pNext) + { + callback(pCur->m_PlayerID); + } + } +} + + +void CVoiceBanMgr::Clear() +{ + // Tie off the hash table entries. + for(int i=0; i < 256; i++) + m_PlayerHash[i].m_pNext = m_PlayerHash[i].m_pPrev = &m_PlayerHash[i]; +} + + +CVoiceBanMgr::BannedPlayer* CVoiceBanMgr::InternalFindPlayerSquelch(char const playerID[16]) +{ + int index = HashPlayerID(playerID); + BannedPlayer *pListHead = &m_PlayerHash[index]; + for(BannedPlayer *pCur=pListHead->m_pNext; pCur != pListHead; pCur=pCur->m_pNext) + { + if(memcmp(playerID, pCur->m_PlayerID, 16) == 0) + return pCur; + } + + return NULL; +} + + +CVoiceBanMgr::BannedPlayer* CVoiceBanMgr::AddBannedPlayer(char const playerID[16]) +{ + BannedPlayer *pNew = new BannedPlayer; + if(!pNew) + return NULL; + + int index = HashPlayerID(playerID); + memcpy(pNew->m_PlayerID, playerID, 16); + pNew->m_pNext = &m_PlayerHash[index]; + pNew->m_pPrev = m_PlayerHash[index].m_pPrev; + pNew->m_pPrev->m_pNext = pNew->m_pNext->m_pPrev = pNew; + return pNew; +} + diff --git a/game_shared/voice_banmgr.h b/game_shared/voice_banmgr.h new file mode 100644 index 0000000..2f14d8b --- /dev/null +++ b/game_shared/voice_banmgr.h @@ -0,0 +1,57 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VOICE_BANMGR_H +#define VOICE_BANMGR_H +#ifdef _WIN32 +#pragma once +#endif + + +// This class manages the (persistent) list of squelched players. +class CVoiceBanMgr +{ +public: + + CVoiceBanMgr(); + ~CVoiceBanMgr(); + + // Init loads the list of squelched players from disk. + bool Init(char const *pGameDir); + void Term(); + + // Saves the state into voice_squelch.dt. + void SaveState(char const *pGameDir); + + bool GetPlayerBan(char const playerID[16]); + void SetPlayerBan(char const playerID[16], bool bSquelch); + + // Call your callback for each banned player. + void ForEachBannedPlayer(void (*callback)(char id[16])); + + +protected: + + class BannedPlayer + { + public: + char m_PlayerID[16]; + BannedPlayer *m_pPrev, *m_pNext; + }; + + void Clear(); + BannedPlayer* InternalFindPlayerSquelch(char const playerID[16]); + BannedPlayer* AddBannedPlayer(char const playerID[16]); + + +protected: + + BannedPlayer m_PlayerHash[256]; +}; + + +#endif // VOICE_BANMGR_H diff --git a/game_shared/voice_common.h b/game_shared/voice_common.h new file mode 100644 index 0000000..93e9353 --- /dev/null +++ b/game_shared/voice_common.h @@ -0,0 +1,24 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VOICE_COMMON_H +#define VOICE_COMMON_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "bitvec.h" + + +#define VOICE_MAX_PLAYERS 32 // (todo: this should just be set to MAX_CLIENTS). +#define VOICE_MAX_PLAYERS_DW ((VOICE_MAX_PLAYERS / 32) + !!(VOICE_MAX_PLAYERS & 31)) + +typedef CBitVec CPlayerBitVec; + + +#endif // VOICE_COMMON_H diff --git a/game_shared/voice_gamemgr.cpp b/game_shared/voice_gamemgr.cpp new file mode 100644 index 0000000..5ba2edf --- /dev/null +++ b/game_shared/voice_gamemgr.cpp @@ -0,0 +1,274 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "voice_gamemgr.h" +#include +#include +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" + + + +#define UPDATE_INTERVAL 0.3 + + +// These are stored off as CVoiceGameMgr is created and deleted. +CPlayerBitVec g_PlayerModEnable; // Set to 1 for each player if the player wants to use voice in this mod. + // (If it's zero, then the server reports that the game rules are saying the + // player can't hear anyone). + +CPlayerBitVec g_BanMasks[VOICE_MAX_PLAYERS]; // Tells which players don't want to hear each other. + // These are indexed as clients and each bit represents a client + // (so player entity is bit+1). + +CPlayerBitVec g_SentGameRulesMasks[VOICE_MAX_PLAYERS]; // These store the masks we last sent to each client so we can determine if +CPlayerBitVec g_SentBanMasks[VOICE_MAX_PLAYERS]; // we need to resend them. +CPlayerBitVec g_bWantModEnable; + +cvar_t voice_serverdebug = {"voice_serverdebug", "0"}; + +// Set game rules to allow all clients to talk to each other. +// Muted players still can't talk to each other. +cvar_t sv_alltalk = {"sv_alltalk", "0"}; + +// ------------------------------------------------------------------------ // +// Static helpers. +// ------------------------------------------------------------------------ // + +// Find a player with a case-insensitive name search. +static CBasePlayer* FindPlayerByName(const char *pTestName) +{ + for(int i=1; i <= gpGlobals->maxClients; i++) + { + edict_t *pEdict = g_engfuncs.pfnPEntityOfEntIndex(i); + if(pEdict) + { + CBaseEntity *pEnt = CBaseEntity::Instance(pEdict); + if(pEnt && pEnt->IsPlayer()) + { + const char *pNetName = STRING(pEnt->pev->netname); + if(stricmp(pNetName, pTestName) == 0) + { + return (CBasePlayer*)pEnt; + } + } + } + } + + return NULL; +} + +static void VoiceServerDebug( char const *pFmt, ... ) +{ + char msg[4096]; + va_list marker; + + if( !voice_serverdebug.value ) + return; + + va_start( marker, pFmt ); + _vsnprintf( msg, sizeof(msg), pFmt, marker ); + va_end( marker ); + + ALERT( at_console, "%s", msg ); +} + + + +// ------------------------------------------------------------------------ // +// CVoiceGameMgr. +// ------------------------------------------------------------------------ // + +CVoiceGameMgr::CVoiceGameMgr() +{ + m_UpdateInterval = 0; + m_nMaxPlayers = 0; +} + + +CVoiceGameMgr::~CVoiceGameMgr() +{ +} + + +bool CVoiceGameMgr::Init( + IVoiceGameMgrHelper *pHelper, + int maxClients) +{ + m_pHelper = pHelper; + m_nMaxPlayers = VOICE_MAX_PLAYERS < maxClients ? VOICE_MAX_PLAYERS : maxClients; + g_engfuncs.pfnPrecacheModel("sprites/voiceicon.spr"); + + m_msgPlayerVoiceMask = REG_USER_MSG( "VoiceMask", VOICE_MAX_PLAYERS_DW*4 * 2 ); + m_msgRequestState = REG_USER_MSG( "ReqState", 0 ); + + // register voice_serverdebug if it hasn't been registered already + if ( !CVAR_GET_POINTER( "voice_serverdebug" ) ) + CVAR_REGISTER( &voice_serverdebug ); + + if( !CVAR_GET_POINTER( "sv_alltalk" ) ) + CVAR_REGISTER( &sv_alltalk ); + + return true; +} + + +void CVoiceGameMgr::SetHelper(IVoiceGameMgrHelper *pHelper) +{ + m_pHelper = pHelper; +} + + +void CVoiceGameMgr::Update(double frametime) +{ + // Only update periodically. + m_UpdateInterval += frametime; + if(m_UpdateInterval < UPDATE_INTERVAL) + return; + + UpdateMasks(); +} + + +void CVoiceGameMgr::ClientConnected(edict_t *pEdict) +{ + int index = ENTINDEX(pEdict) - 1; + + // Clear out everything we use for deltas on this guy. + g_bWantModEnable[index] = true; + g_SentGameRulesMasks[index].Init(0); + g_SentBanMasks[index].Init(0); +} + +// Called to determine if the Receiver has muted (blocked) the Sender +// Returns true if the receiver has blocked the sender +bool CVoiceGameMgr::PlayerHasBlockedPlayer(CBasePlayer *pReceiver, CBasePlayer *pSender) +{ + int iReceiverIndex, iSenderIndex; + + if ( !pReceiver || !pSender ) + return false; + + iReceiverIndex = pReceiver->entindex() - 1; + iSenderIndex = pSender->entindex() - 1; + + if ( iReceiverIndex < 0 || iReceiverIndex >= m_nMaxPlayers || iSenderIndex < 0 || iSenderIndex >= m_nMaxPlayers ) + return false; + + return ( g_BanMasks[iReceiverIndex][iSenderIndex] ? true : false ); +} + +bool CVoiceGameMgr::ClientCommand(CBasePlayer *pPlayer, const char *cmd) +{ + int playerClientIndex = pPlayer->entindex() - 1; + if(playerClientIndex < 0 || playerClientIndex >= m_nMaxPlayers) + { + VoiceServerDebug( "CVoiceGameMgr::ClientCommand: cmd %s from invalid client (%d)\n", cmd, playerClientIndex ); + return true; + } + + bool bBan = stricmp(cmd, "vban") == 0; + if(bBan && CMD_ARGC() >= 2) + { + for(int i=1; i < CMD_ARGC(); i++) + { + unsigned long mask = 0; + sscanf(CMD_ARGV(i), "%x", &mask); + + if(i <= VOICE_MAX_PLAYERS_DW) + { + VoiceServerDebug( "CVoiceGameMgr::ClientCommand: vban (0x%x) from %d\n", mask, playerClientIndex ); + g_BanMasks[playerClientIndex].SetDWord(i-1, mask); + } + else + { + VoiceServerDebug( "CVoiceGameMgr::ClientCommand: invalid index (%d)\n", i ); + } + } + + // Force it to update the masks now. + //UpdateMasks(); + return true; + } + else if(stricmp(cmd, "VModEnable") == 0 && CMD_ARGC() >= 2) + { + VoiceServerDebug( "CVoiceGameMgr::ClientCommand: VModEnable (%d)\n", !!atoi(CMD_ARGV(1)) ); + g_PlayerModEnable[playerClientIndex] = !!atoi(CMD_ARGV(1)); + g_bWantModEnable[playerClientIndex] = false; + //UpdateMasks(); + return true; + } + else + { + return false; + } +} + + +void CVoiceGameMgr::UpdateMasks() +{ + m_UpdateInterval = 0; + + bool bAllTalk = !!g_engfuncs.pfnCVarGetFloat( "sv_alltalk" ); + + for(int iClient=0; iClient < m_nMaxPlayers; iClient++) + { + CBaseEntity *pEnt = UTIL_PlayerByIndex(iClient+1); + if(!pEnt || !pEnt->IsPlayer()) + continue; + + // Request the state of their "VModEnable" cvar. + if(g_bWantModEnable[iClient]) + { + MESSAGE_BEGIN(MSG_ONE, m_msgRequestState, NULL, pEnt->pev); + MESSAGE_END(); + } + + CBasePlayer *pPlayer = (CBasePlayer*)pEnt; + + CPlayerBitVec gameRulesMask; + if( g_PlayerModEnable[iClient] ) + { + // Build a mask of who they can hear based on the game rules. + for(int iOtherClient=0; iOtherClient < m_nMaxPlayers; iOtherClient++) + { + CBaseEntity *pEnt = UTIL_PlayerByIndex(iOtherClient+1); + if(pEnt && pEnt->IsPlayer() && + (bAllTalk || m_pHelper->CanPlayerHearPlayer(pPlayer, (CBasePlayer*)pEnt)) ) + { + gameRulesMask[iOtherClient] = true; + } + } + } + + // If this is different from what the client has, send an update. + if(gameRulesMask != g_SentGameRulesMasks[iClient] || + g_BanMasks[iClient] != g_SentBanMasks[iClient]) + { + g_SentGameRulesMasks[iClient] = gameRulesMask; + g_SentBanMasks[iClient] = g_BanMasks[iClient]; + + MESSAGE_BEGIN(MSG_ONE, m_msgPlayerVoiceMask, NULL, pPlayer->pev); + int dw; + for(dw=0; dw < VOICE_MAX_PLAYERS_DW; dw++) + { + WRITE_LONG(gameRulesMask.GetDWord(dw)); + WRITE_LONG(g_BanMasks[iClient].GetDWord(dw)); + } + MESSAGE_END(); + } + + // Tell the engine. + for(int iOtherClient=0; iOtherClient < m_nMaxPlayers; iOtherClient++) + { + bool bCanHear = gameRulesMask[iOtherClient] && !g_BanMasks[iClient][iOtherClient]; + g_engfuncs.pfnVoice_SetClientListening(iClient+1, iOtherClient+1, bCanHear); + } + } +} diff --git a/game_shared/voice_gamemgr.h b/game_shared/voice_gamemgr.h new file mode 100644 index 0000000..9605c5c --- /dev/null +++ b/game_shared/voice_gamemgr.h @@ -0,0 +1,79 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VOICE_GAMEMGR_H +#define VOICE_GAMEMGR_H +#pragma once + + +#include "voice_common.h" + + +class CGameRules; +class CBasePlayer; + + +class IVoiceGameMgrHelper +{ +public: + virtual ~IVoiceGameMgrHelper() {} + + // Called each frame to determine which players are allowed to hear each other. This overrides + // whatever squelch settings players have. + virtual bool CanPlayerHearPlayer(CBasePlayer *pListener, CBasePlayer *pTalker) = 0; +}; + + +// CVoiceGameMgr manages which clients can hear which other clients. +class CVoiceGameMgr +{ +public: + CVoiceGameMgr(); + virtual ~CVoiceGameMgr(); + + bool Init( + IVoiceGameMgrHelper *m_pHelper, + int maxClients + ); + + void SetHelper(IVoiceGameMgrHelper *pHelper); + + // Updates which players can hear which other players. + // If gameplay mode is DM, then only players within the PVS can hear each other. + // If gameplay mode is teamplay, then only players on the same team can hear each other. + // Player masks are always applied. + void Update(double frametime); + + // Called when a new client connects (unsquelches its entity for everyone). + void ClientConnected(struct edict_s *pEdict); + + // Called on ClientCommand. Checks for the squelch and unsquelch commands. + // Returns true if it handled the command. + bool ClientCommand(CBasePlayer *pPlayer, const char *cmd); + + // Called to determine if the Receiver has muted (blocked) the Sender + // Returns true if the receiver has blocked the sender + bool PlayerHasBlockedPlayer(CBasePlayer *pReceiver, CBasePlayer *pSender); + + +private: + + // Force it to update the client masks. + void UpdateMasks(); + + +private: + int m_msgPlayerVoiceMask; + int m_msgRequestState; + + IVoiceGameMgrHelper *m_pHelper; + int m_nMaxPlayers; + double m_UpdateInterval; // How long since the last update. +}; + + +#endif // VOICE_GAMEMGR_H diff --git a/game_shared/voice_status.cpp b/game_shared/voice_status.cpp new file mode 100644 index 0000000..8edb47b --- /dev/null +++ b/game_shared/voice_status.cpp @@ -0,0 +1,875 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// There are hud.h's coming out of the woodwork so this ensures that we get the right one. +#if defined( DMC_BUILD ) + #include "../dmc/cl_dll/hud.h" + #include "../dmc/cl_dll/cl_util.h" +#elif defined( RICOCHET_BUILD ) + #include "../ricochet/cl_dll/hud.h" + #include "../ricochet/cl_dll/cl_util.h" +#else + #include "../cl_dll/hud.h" + #include "../cl_dll/cl_util.h" +#endif + +#include +#include +#include + +#if defined( DMC_BUILD ) + #include "../dmc/cl_dll/parsemsg.h" + #include "../dmc/cl_dll/hud_servers.h" + #include "../dmc/cl_dll/demo.h" +#elif defined( RICOCHET_BUILD ) + #include "../ricochet/cl_dll/parsemsg.h" + #include "../ricochet/cl_dll/hud_servers.h" + #include "../ricochet/cl_dll/demo.h" +#else + #include "../cl_dll/parsemsg.h" + #include "../cl_dll/hud_servers.h" + #include "../cl_dll/demo.h" +#endif + +#include "demo_api.h" +#include "voice_status.h" +#include "r_efx.h" +#include "entity_types.h" +#include "VGUI_ActionSignal.h" +#include "VGUI_Scheme.h" +#include "VGUI_TextImage.h" +#include "vgui_loadtga.h" +#include "vgui_helpers.h" +#include "vgui_mousecode.h" + + + +using namespace vgui; + + +extern int cam_thirdperson; + + +#define VOICE_MODEL_INTERVAL 0.3 +#define SCOREBOARD_BLINK_FREQUENCY 0.3 // How often to blink the scoreboard icons. +#define SQUELCHOSCILLATE_PER_SECOND 2.0f + + +extern BitmapTGA *LoadTGA( const char* pImageName ); + + + +// ---------------------------------------------------------------------- // +// The voice manager for the client. +// ---------------------------------------------------------------------- // +CVoiceStatus g_VoiceStatus; + +CVoiceStatus* GetClientVoiceMgr() +{ + return &g_VoiceStatus; +} + + + +// ---------------------------------------------------------------------- // +// CVoiceStatus. +// ---------------------------------------------------------------------- // + +static CVoiceStatus *g_pInternalVoiceStatus = NULL; + +int __MsgFunc_VoiceMask(const char *pszName, int iSize, void *pbuf) +{ + if(g_pInternalVoiceStatus) + g_pInternalVoiceStatus->HandleVoiceMaskMsg(iSize, pbuf); + + return 1; +} + +int __MsgFunc_ReqState(const char *pszName, int iSize, void *pbuf) +{ + if(g_pInternalVoiceStatus) + g_pInternalVoiceStatus->HandleReqStateMsg(iSize, pbuf); + + return 1; +} + + +int g_BannedPlayerPrintCount; +void ForEachBannedPlayer(char id[16]) +{ + char str[256]; + sprintf(str, "Ban %d: %2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x%2x\n", + g_BannedPlayerPrintCount++, + id[0], id[1], id[2], id[3], + id[4], id[5], id[6], id[7], + id[8], id[9], id[10], id[11], + id[12], id[13], id[14], id[15] + ); + strupr(str); + gEngfuncs.pfnConsolePrint(str); +} + + +void ShowBannedCallback() +{ + if(g_pInternalVoiceStatus) + { + g_BannedPlayerPrintCount = 0; + gEngfuncs.pfnConsolePrint("------- BANNED PLAYERS -------\n"); + g_pInternalVoiceStatus->m_BanMgr.ForEachBannedPlayer(ForEachBannedPlayer); + gEngfuncs.pfnConsolePrint("------------------------------\n"); + } +} + + +// ---------------------------------------------------------------------- // +// CVoiceStatus. +// ---------------------------------------------------------------------- // + +CVoiceStatus::CVoiceStatus() +{ + m_bBanMgrInitialized = false; + m_LastUpdateServerState = 0; + + m_pSpeakerLabelIcon = NULL; + m_pScoreboardNeverSpoken = NULL; + m_pScoreboardNotSpeaking = NULL; + m_pScoreboardSpeaking = NULL; + m_pScoreboardSpeaking2 = NULL; + m_pScoreboardSquelch = NULL; + m_pScoreboardBanned = NULL; + + m_pLocalBitmap = NULL; + m_pAckBitmap = NULL; + + m_bTalking = m_bServerAcked = false; + + memset(m_pBanButtons, 0, sizeof(m_pBanButtons)); + + m_bServerModEnable = -1; + + m_pchGameDir = NULL; +} + + +CVoiceStatus::~CVoiceStatus() +{ + g_pInternalVoiceStatus = NULL; + + for(int i=0; i < MAX_VOICE_SPEAKERS; i++) + { + delete m_Labels[i].m_pLabel; + m_Labels[i].m_pLabel = NULL; + + delete m_Labels[i].m_pIcon; + m_Labels[i].m_pIcon = NULL; + + delete m_Labels[i].m_pBackground; + m_Labels[i].m_pBackground = NULL; + } + + delete m_pLocalLabel; + m_pLocalLabel = NULL; + + FreeBitmaps(); + + if(m_pchGameDir) + { + if(m_bBanMgrInitialized) + { +// g-cont. kill the voice_ban.dt file +// m_BanMgr.SaveState(m_pchGameDir); + } + + free(m_pchGameDir); + } +} + + +int CVoiceStatus::Init( + IVoiceStatusHelper *pHelper, + Panel **pParentPanel) +{ + // Setup the voice_modenable cvar. + gEngfuncs.pfnRegisterVariable("voice_modenable", "1", FCVAR_ARCHIVE); + + gEngfuncs.pfnRegisterVariable("voice_clientdebug", "0", 0); + + gEngfuncs.pfnAddCommand("voice_showbanned", ShowBannedCallback); + + if(gEngfuncs.pfnGetGameDirectory()) + { + m_BanMgr.Init(gEngfuncs.pfnGetGameDirectory()); + m_bBanMgrInitialized = true; + } + + assert(!g_pInternalVoiceStatus); + g_pInternalVoiceStatus = this; + + m_BlinkTimer = 0; + m_VoiceHeadModel = NULL; + memset(m_Labels, 0, sizeof(m_Labels)); + + for(int i=0; i < MAX_VOICE_SPEAKERS; i++) + { + CVoiceLabel *pLabel = &m_Labels[i]; + + pLabel->m_pBackground = new Label(""); + + if(pLabel->m_pLabel = new Label("")) + { + pLabel->m_pLabel->setVisible( true ); + pLabel->m_pLabel->setFont( Scheme::sf_primary2 ); + pLabel->m_pLabel->setTextAlignment( Label::a_east ); + pLabel->m_pLabel->setContentAlignment( Label::a_east ); + pLabel->m_pLabel->setParent( pLabel->m_pBackground ); + } + + if( pLabel->m_pIcon = new ImagePanel( NULL ) ) + { + pLabel->m_pIcon->setVisible( true ); + pLabel->m_pIcon->setParent( pLabel->m_pBackground ); + } + + pLabel->m_clientindex = -1; + } + + m_pLocalLabel = new ImagePanel(NULL); + + m_bInSquelchMode = false; + + m_pHelper = pHelper; + m_pParentPanel = pParentPanel; + gHUD.AddHudElem(this); + m_iFlags = HUD_ACTIVE; + HOOK_MESSAGE(VoiceMask); + HOOK_MESSAGE(ReqState); + + // Cache the game directory for use when we shut down + const char *pchGameDirT = gEngfuncs.pfnGetGameDirectory(); + m_pchGameDir = (char *)malloc(strlen(pchGameDirT) + 1); + strcpy(m_pchGameDir, pchGameDirT); + + return 1; +} + + +int CVoiceStatus::VidInit() +{ + FreeBitmaps(); + + + if( m_pLocalBitmap = vgui_LoadTGA("gfx/vgui/icntlk_pl.tga") ) + { + m_pLocalBitmap->setColor(Color(255,255,255,135)); + } + + if( m_pAckBitmap = vgui_LoadTGA("gfx/vgui/icntlk_sv.tga") ) + { + m_pAckBitmap->setColor(Color(255,255,255,135)); // Give just a tiny bit of translucency so software draws correctly. + } + + m_pLocalLabel->setImage( m_pLocalBitmap ); + m_pLocalLabel->setVisible( false ); + + + if( m_pSpeakerLabelIcon = vgui_LoadTGANoInvertAlpha("gfx/vgui/speaker4.tga" ) ) + m_pSpeakerLabelIcon->setColor( Color(255,255,255,1) ); // Give just a tiny bit of translucency so software draws correctly. + + if (m_pScoreboardNeverSpoken = vgui_LoadTGANoInvertAlpha("gfx/vgui/640_speaker1.tga")) + m_pScoreboardNeverSpoken->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + if(m_pScoreboardNotSpeaking = vgui_LoadTGANoInvertAlpha("gfx/vgui/640_speaker2.tga")) + m_pScoreboardNotSpeaking->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + if(m_pScoreboardSpeaking = vgui_LoadTGANoInvertAlpha("gfx/vgui/640_speaker3.tga")) + m_pScoreboardSpeaking->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + if(m_pScoreboardSpeaking2 = vgui_LoadTGANoInvertAlpha("gfx/vgui/640_speaker4.tga")) + m_pScoreboardSpeaking2->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + if(m_pScoreboardSquelch = vgui_LoadTGA("gfx/vgui/icntlk_squelch.tga")) + m_pScoreboardSquelch->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + if(m_pScoreboardBanned = vgui_LoadTGA("gfx/vgui/640_voiceblocked.tga")) + m_pScoreboardBanned->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + + // Figure out the voice head model height. + m_VoiceHeadModelHeight = 45; + char *pFile = (char *)gEngfuncs.COM_LoadFile("scripts/voicemodel.txt", 5, NULL); + if(pFile) + { + char token[4096]; + gEngfuncs.COM_ParseFile(pFile, token); + if(token[0] >= '0' && token[0] <= '9') + { + m_VoiceHeadModelHeight = (float)atof(token); + } + + gEngfuncs.COM_FreeFile(pFile); + } + + m_VoiceHeadModel = gEngfuncs.pfnSPR_Load("sprites/voiceicon.spr"); + return TRUE; +} + + +void CVoiceStatus::Frame(double frametime) +{ + // check server banned players once per second + if(gEngfuncs.GetClientTime() - m_LastUpdateServerState > 1) + { + UpdateServerState(false); + } + + m_BlinkTimer += frametime; + + // Update speaker labels. + if( m_pHelper->CanShowSpeakerLabels() ) + { + for( int i=0; i < MAX_VOICE_SPEAKERS; i++ ) + m_Labels[i].m_pBackground->setVisible( m_Labels[i].m_clientindex != -1 ); + } + else + { + for( int i=0; i < MAX_VOICE_SPEAKERS; i++ ) + m_Labels[i].m_pBackground->setVisible( false ); + } + + for(int i=0; i < VOICE_MAX_PLAYERS; i++) + UpdateBanButton(i); +} + + +void CVoiceStatus::CreateEntities() +{ + if(!m_VoiceHeadModel) + return; + + cl_entity_t *localPlayer = gEngfuncs.GetLocalPlayer(); + + int iOutModel = 0; + for(int i=0; i < VOICE_MAX_PLAYERS; i++) + { + if(!m_VoicePlayers[i]) + continue; + + cl_entity_s *pClient = gEngfuncs.GetEntityByIndex(i+1); + + // Don't show an icon if the player is not in our PVS. + if(!pClient || pClient->curstate.messagenum < localPlayer->curstate.messagenum) + continue; + + // Don't show an icon for dead or spectating players (ie: invisible entities). + if(pClient->curstate.effects & EF_NODRAW) + continue; + + // Don't show an icon for the local player unless we're in thirdperson mode. + if(pClient == localPlayer && !cam_thirdperson) + continue; + + cl_entity_s *pEnt = &m_VoiceHeadModels[iOutModel]; + ++iOutModel; + + memset(pEnt, 0, sizeof(*pEnt)); + + pEnt->curstate.rendermode = kRenderTransAdd; + pEnt->curstate.renderamt = 255; + pEnt->baseline.renderamt = 255; + pEnt->curstate.renderfx = kRenderFxNoDissipation; + pEnt->curstate.framerate = 1; + pEnt->curstate.frame = 0; + pEnt->model = (struct model_s*)gEngfuncs.GetSpritePointer(m_VoiceHeadModel); + pEnt->angles[0] = pEnt->angles[1] = pEnt->angles[2] = 0; + pEnt->curstate.scale = 0.5f; + + pEnt->origin[0] = pEnt->origin[1] = 0; + pEnt->origin[2] = 45; + + VectorAdd(pEnt->origin, pClient->origin, pEnt->origin); + + // Tell the engine. + gEngfuncs.CL_CreateVisibleEntity(ET_NORMAL, pEnt); + } +} + + +void CVoiceStatus::UpdateSpeakerStatus(int entindex, qboolean bTalking) +{ + if(!*m_pParentPanel) + return; + + if( gEngfuncs.pfnGetCvarFloat("voice_clientdebug") ) + { + char msg[256]; + _snprintf( msg, sizeof(msg), "CVoiceStatus::UpdateSpeakerStatus: ent %d talking = %d\n", entindex, bTalking ); + gEngfuncs.pfnConsolePrint( msg ); + } + + // Is it the local player talking? + if( entindex == -1 ) + { + m_bTalking = !!bTalking; + if( bTalking ) + { + // Enable voice for them automatically if they try to talk. + gEngfuncs.pfnClientCmd( "voice_modenable 1" ); + } + } + else if( entindex == -2 ) + { + m_bServerAcked = !!bTalking; + } + else if(entindex >= 0 && entindex <= VOICE_MAX_PLAYERS) + { + int iClient = entindex - 1; + if(iClient < 0) + return; + + CVoiceLabel *pLabel = FindVoiceLabel(iClient); + if(bTalking) + { + m_VoicePlayers[iClient] = true; + m_VoiceEnabledPlayers[iClient] = true; + + // If we don't have a label for this guy yet, then create one. + if(!pLabel) + { + if(pLabel = GetFreeVoiceLabel()) + { + // Get the name from the engine. + hud_player_info_t info; + memset(&info, 0, sizeof(info)); + GetPlayerInfo(entindex, &info); + + char paddedName[512]; + _snprintf(paddedName, sizeof(paddedName), "%s ", info.name); + + int color[3]; + m_pHelper->GetPlayerTextColor( entindex, color ); + + if( pLabel->m_pBackground ) + { + pLabel->m_pBackground->setBgColor( color[0], color[1], color[2], 135 ); + pLabel->m_pBackground->setParent( *m_pParentPanel ); + pLabel->m_pBackground->setVisible( m_pHelper->CanShowSpeakerLabels() ); + } + + if( pLabel->m_pLabel ) + { + pLabel->m_pLabel->setFgColor( 255, 255, 255, 0 ); + pLabel->m_pLabel->setBgColor( 0, 0, 0, 255 ); + pLabel->m_pLabel->setText( paddedName ); + } + + pLabel->m_clientindex = iClient; + } + } + } + else + { + m_VoicePlayers[iClient] = false; + + // If we have a label for this guy, kill it. + if(pLabel) + { + pLabel->m_pBackground->setVisible(false); + pLabel->m_clientindex = -1; + } + } + } + + RepositionLabels(); +} + + +void CVoiceStatus::UpdateServerState(bool bForce) +{ + // Can't do anything when we're not in a level. + char const *pLevelName = gEngfuncs.pfnGetLevelName(); + if( pLevelName[0] == 0 ) + { + if( gEngfuncs.pfnGetCvarFloat("voice_clientdebug") ) + { + gEngfuncs.pfnConsolePrint( "CVoiceStatus::UpdateServerState: pLevelName[0]==0\n" ); + } + + return; + } + + int bCVarModEnable = !!gEngfuncs.pfnGetCvarFloat("voice_modenable"); + if(bForce || m_bServerModEnable != bCVarModEnable) + { + m_bServerModEnable = bCVarModEnable; + + char str[256]; + _snprintf(str, sizeof(str), "VModEnable %d", m_bServerModEnable); + ServerCmd(str); + + if(gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + char msg[256]; + sprintf(msg, "CVoiceStatus::UpdateServerState: Sending '%s'\n", str); + gEngfuncs.pfnConsolePrint(msg); + } + } + + char str[2048]; + sprintf(str, "vban"); + bool bChange = false; + + for(unsigned long dw=0; dw < VOICE_MAX_PLAYERS_DW; dw++) + { + unsigned long serverBanMask = 0; + unsigned long banMask = 0; + for(unsigned long i=0; i < 32; i++) + { + char playerID[16]; + if(!gEngfuncs.GetPlayerUniqueID(i+1, playerID)) + continue; + + if(m_BanMgr.GetPlayerBan(playerID)) + banMask |= 1 << i; + + if(m_ServerBannedPlayers[dw*32 + i]) + serverBanMask |= 1 << i; + } + + if(serverBanMask != banMask) + bChange = true; + + // Ok, the server needs to be updated. + char numStr[512]; + sprintf(numStr, " %x", banMask); + strcat(str, numStr); + } + + if(bChange || bForce) + { + if(gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + char msg[256]; + sprintf(msg, "CVoiceStatus::UpdateServerState: Sending '%s'\n", str); + gEngfuncs.pfnConsolePrint(msg); + } + + gEngfuncs.pfnServerCmdUnreliable(str); // Tell the server.. + } + else + { + if (gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + gEngfuncs.pfnConsolePrint( "CVoiceStatus::UpdateServerState: no change\n" ); + } + } + + m_LastUpdateServerState = gEngfuncs.GetClientTime(); +} + +void CVoiceStatus::UpdateSpeakerImage(Label *pLabel, int iPlayer) +{ + m_pBanButtons[iPlayer-1] = pLabel; + UpdateBanButton(iPlayer-1); +} + +void CVoiceStatus::UpdateBanButton(int iClient) +{ + Label *pPanel = m_pBanButtons[iClient]; + + if (!pPanel) + return; + + char playerID[16]; + extern bool HACK_GetPlayerUniqueID( int iPlayer, char playerID[16] ); + if(!HACK_GetPlayerUniqueID(iClient+1, playerID)) + return; + + // Figure out if it's blinking or not. + bool bBlink = fmod(m_BlinkTimer, SCOREBOARD_BLINK_FREQUENCY*2) < SCOREBOARD_BLINK_FREQUENCY; + bool bTalking = !!m_VoicePlayers[iClient]; + bool bBanned = m_BanMgr.GetPlayerBan(playerID); + bool bNeverSpoken = !m_VoiceEnabledPlayers[iClient]; + + // Get the appropriate image to display on the panel. + if (bBanned) + { + pPanel->setImage(m_pScoreboardBanned); + } + else if (bTalking) + { + if (bBlink) + { + pPanel->setImage(m_pScoreboardSpeaking2); + } + else + { + pPanel->setImage(m_pScoreboardSpeaking); + } + pPanel->setFgColor(255, 170, 0, 1); + } + else if (bNeverSpoken) + { + pPanel->setImage(m_pScoreboardNeverSpoken); + pPanel->setFgColor(100, 100, 100, 1); + } + else + { + pPanel->setImage(m_pScoreboardNotSpeaking); + } +} + + +void CVoiceStatus::HandleVoiceMaskMsg(int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + unsigned long dw; + for(dw=0; dw < VOICE_MAX_PLAYERS_DW; dw++) + { + m_AudiblePlayers.SetDWord(dw, (unsigned long)READ_LONG()); + m_ServerBannedPlayers.SetDWord(dw, (unsigned long)READ_LONG()); + + if(gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + char str[256]; + gEngfuncs.pfnConsolePrint("CVoiceStatus::HandleVoiceMaskMsg\n"); + + sprintf(str, " - m_AudiblePlayers[%d] = %lu\n", dw, m_AudiblePlayers.GetDWord(dw)); + gEngfuncs.pfnConsolePrint(str); + + sprintf(str, " - m_ServerBannedPlayers[%d] = %lu\n", dw, m_ServerBannedPlayers.GetDWord(dw)); + gEngfuncs.pfnConsolePrint(str); + } + } + + m_bServerModEnable = READ_BYTE(); +} + +void CVoiceStatus::HandleReqStateMsg(int iSize, void *pbuf) +{ + if(gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + gEngfuncs.pfnConsolePrint("CVoiceStatus::HandleReqStateMsg\n"); + } + + UpdateServerState(true); +} + +void CVoiceStatus::StartSquelchMode() +{ + if(m_bInSquelchMode) + return; + + m_bInSquelchMode = true; + m_pHelper->UpdateCursorState(); +} + +void CVoiceStatus::StopSquelchMode() +{ + m_bInSquelchMode = false; + m_pHelper->UpdateCursorState(); +} + +bool CVoiceStatus::IsInSquelchMode() +{ + return m_bInSquelchMode; +} + +CVoiceLabel* CVoiceStatus::FindVoiceLabel(int clientindex) +{ + for(int i=0; i < MAX_VOICE_SPEAKERS; i++) + { + if(m_Labels[i].m_clientindex == clientindex) + return &m_Labels[i]; + } + + return NULL; +} + + +CVoiceLabel* CVoiceStatus::GetFreeVoiceLabel() +{ + return FindVoiceLabel(-1); +} + + +void CVoiceStatus::RepositionLabels() +{ + // find starting position to draw from, along right-hand side of screen + int y = ScreenHeight / 2; + + int iconWide = 8, iconTall = 8; + if( m_pSpeakerLabelIcon ) + { + m_pSpeakerLabelIcon->getSize( iconWide, iconTall ); + } + + // Reposition active labels. + for(int i = 0; i < MAX_VOICE_SPEAKERS; i++) + { + CVoiceLabel *pLabel = &m_Labels[i]; + + if( pLabel->m_clientindex == -1 || !pLabel->m_pLabel ) + { + if( pLabel->m_pBackground ) + pLabel->m_pBackground->setVisible( false ); + + continue; + } + + int textWide, textTall; + pLabel->m_pLabel->getContentSize( textWide, textTall ); + + // Don't let it stretch too far across their screen. + if( textWide > (ScreenWidth*2)/3 ) + textWide = (ScreenWidth*2)/3; + + // Setup the background label to fit everything in. + int border = 2; + int bgWide = textWide + iconWide + border*3; + int bgTall = max( textTall, iconTall ) + border*2; + pLabel->m_pBackground->setBounds( ScreenWidth - bgWide - 8, y, bgWide, bgTall ); + + // Put the text at the left. + pLabel->m_pLabel->setBounds( border, (bgTall - textTall) / 2, textWide, textTall ); + + // Put the icon at the right. + int iconLeft = border + textWide + border; + int iconTop = (bgTall - iconTall) / 2; + if( pLabel->m_pIcon ) + { + pLabel->m_pIcon->setImage( m_pSpeakerLabelIcon ); + pLabel->m_pIcon->setBounds( iconLeft, iconTop, iconWide, iconTall ); + } + + y += bgTall + 2; + } + + if( m_pLocalBitmap && m_pAckBitmap && m_pLocalLabel && (m_bTalking || m_bServerAcked) ) + { + m_pLocalLabel->setParent(*m_pParentPanel); + m_pLocalLabel->setVisible( true ); + + if( m_bServerAcked && !!gEngfuncs.pfnGetCvarFloat("voice_clientdebug") ) + m_pLocalLabel->setImage( m_pAckBitmap ); + else + m_pLocalLabel->setImage( m_pLocalBitmap ); + + int sizeX, sizeY; + m_pLocalBitmap->getSize(sizeX, sizeY); + + int local_xPos = ScreenWidth - sizeX - 10; + int local_yPos = m_pHelper->GetAckIconHeight() - sizeY; + + m_pLocalLabel->setPos( local_xPos, local_yPos ); + } + else + { + m_pLocalLabel->setVisible( false ); + } +} + + +void CVoiceStatus::FreeBitmaps() +{ + // Delete all the images we have loaded. + delete m_pLocalBitmap; + m_pLocalBitmap = NULL; + + delete m_pAckBitmap; + m_pAckBitmap = NULL; + + delete m_pSpeakerLabelIcon; + m_pSpeakerLabelIcon = NULL; + + delete m_pScoreboardNeverSpoken; + m_pScoreboardNeverSpoken = NULL; + + delete m_pScoreboardNotSpeaking; + m_pScoreboardNotSpeaking = NULL; + + delete m_pScoreboardSpeaking; + m_pScoreboardSpeaking = NULL; + + delete m_pScoreboardSpeaking2; + m_pScoreboardSpeaking2 = NULL; + + delete m_pScoreboardSquelch; + m_pScoreboardSquelch = NULL; + + delete m_pScoreboardBanned; + m_pScoreboardBanned = NULL; + + // Clear references to the images in panels. + for(int i=0; i < VOICE_MAX_PLAYERS; i++) + { + if (m_pBanButtons[i]) + { + m_pBanButtons[i]->setImage(NULL); + } + } + + if(m_pLocalLabel) + m_pLocalLabel->setImage(NULL); +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if the target client has been banned +// Input : playerID - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CVoiceStatus::IsPlayerBlocked(int iPlayer) +{ + char playerID[16]; + if (!gEngfuncs.GetPlayerUniqueID(iPlayer, playerID)) + return false; + + return m_BanMgr.GetPlayerBan(playerID); +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if the player can't hear the other client due to game rules (eg. the other team) +// Input : playerID - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CVoiceStatus::IsPlayerAudible(int iPlayer) +{ + return !!m_AudiblePlayers[iPlayer-1]; +} + +//----------------------------------------------------------------------------- +// Purpose: blocks/unblocks the target client from being heard +// Input : playerID - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +void CVoiceStatus::SetPlayerBlockedState(int iPlayer, bool blocked) +{ + if (gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + gEngfuncs.pfnConsolePrint( "CVoiceStatus::SetPlayerBlockedState part 1\n" ); + } + + char playerID[16]; + if (!gEngfuncs.GetPlayerUniqueID(iPlayer, playerID)) + return; + + if (gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + gEngfuncs.pfnConsolePrint( "CVoiceStatus::SetPlayerBlockedState part 2\n" ); + } + + // Squelch or (try to) unsquelch this player. + if (gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) + { + char str[256]; + sprintf(str, "CVoiceStatus::SetPlayerBlockedState: setting player %d ban to %d\n", iPlayer, !m_BanMgr.GetPlayerBan(playerID)); + gEngfuncs.pfnConsolePrint(str); + } + + m_BanMgr.SetPlayerBan( playerID, blocked ); + UpdateServerState(false); +} diff --git a/game_shared/voice_status.h b/game_shared/voice_status.h new file mode 100644 index 0000000..7825083 --- /dev/null +++ b/game_shared/voice_status.h @@ -0,0 +1,228 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VOICE_STATUS_H +#define VOICE_STATUS_H +#pragma once + + +#include "VGUI_Label.h" +#include "VGUI_LineBorder.h" +#include "VGUI_ImagePanel.h" +#include "VGUI_BitmapTGA.h" +#include "VGUI_InputSignal.h" +#include "VGUI_Button.h" +#include "voice_common.h" +#include "cl_entity.h" +#include "voice_banmgr.h" +#include "vgui_checkbutton2.h" +#include "vgui_defaultinputsignal.h" + + +class CVoiceStatus; + + +class CVoiceLabel +{ +public: + vgui::Label *m_pLabel; + vgui::Label *m_pBackground; + vgui::ImagePanel *m_pIcon; // Voice icon next to player name. + int m_clientindex; // Client index of the speaker. -1 if this label isn't being used. +}; + + +// This is provided by each mod to access data that may not be the same across mods. +class IVoiceStatusHelper +{ +public: + virtual ~IVoiceStatusHelper() {} + + // Get RGB color for voice status text about this player. + virtual void GetPlayerTextColor(int entindex, int color[3]) = 0; + + // Force it to update the cursor state. + virtual void UpdateCursorState() = 0; + + // Return the height above the bottom that the voice ack icons should be drawn at. + virtual int GetAckIconHeight() = 0; + + // Return true if the voice manager is allowed to show speaker labels + // (mods usually return false when the scoreboard is up). + virtual bool CanShowSpeakerLabels() = 0; +}; + +//----------------------------------------------------------------------------- +// Purpose: Holds a color for the shared image +//----------------------------------------------------------------------------- +class VoiceImagePanel : public vgui::ImagePanel +{ + virtual void paintBackground() + { + if (_image!=null) + { + vgui::Color col; + getFgColor(col); + _image->setColor(col); + _image->doPaint(this); + } + } +}; + + +class CVoiceStatus : public CHudBase, public vgui::CDefaultInputSignal +{ +public: + CVoiceStatus(); + virtual ~CVoiceStatus(); + +// CHudBase overrides. +public: + + // Initialize the cl_dll's voice manager. + virtual int Init( + IVoiceStatusHelper *m_pHelper, + vgui::Panel **pParentPanel); + + // ackPosition is the bottom position of where CVoiceStatus will draw the voice acknowledgement labels. + virtual int VidInit(); + + +public: + + // Call from HUD_Frame each frame. + void Frame(double frametime); + + // Called when a player starts or stops talking. + // entindex is -1 to represent the local client talking (before the data comes back from the server). + // When the server acknowledges that the local client is talking, then entindex will be gEngfuncs.GetLocalPlayer(). + // entindex is -2 to represent the local client's voice being acked by the server. + void UpdateSpeakerStatus(int entindex, qboolean bTalking); + + // sets the correct image in the label for the player + void UpdateSpeakerImage(vgui::Label *pLabel, int iPlayer); + + // Call from the HUD_CreateEntities function so it can add sprites above player heads. + void CreateEntities(); + + // Called when the server registers a change to who this client can hear. + void HandleVoiceMaskMsg(int iSize, void *pbuf); + + // The server sends this message initially to tell the client to send their state. + void HandleReqStateMsg(int iSize, void *pbuf); + + +// Squelch mode functions. +public: + + // When you enter squelch mode, pass in + void StartSquelchMode(); + void StopSquelchMode(); + bool IsInSquelchMode(); + + // returns true if the target client has been banned + // playerIndex is of range 1..maxplayers + bool IsPlayerBlocked(int iPlayerIndex); + + // returns false if the player can't hear the other client due to game rules (eg. the other team) + bool IsPlayerAudible(int iPlayerIndex); + + // blocks the target client from being heard + void SetPlayerBlockedState(int iPlayerIndex, bool blocked); + +public: + + CVoiceLabel* FindVoiceLabel(int clientindex); // Find a CVoiceLabel representing the specified speaker. + // Returns NULL if none. + // entindex can be -1 if you want a currently-unused voice label. + CVoiceLabel* GetFreeVoiceLabel(); // Get an unused voice label. Returns NULL if none. + + void RepositionLabels(); + + void FreeBitmaps(); + + void UpdateServerState(bool bForce); + + // Update the button artwork to reflect the client's current state. + void UpdateBanButton(int iClient); + + +public: + + enum {MAX_VOICE_SPEAKERS=7}; + + float m_LastUpdateServerState; // Last time we called this function. + int m_bServerModEnable; // What we've sent to the server about our "voice_modenable" cvar. + + vgui::Panel **m_pParentPanel; + CPlayerBitVec m_VoicePlayers; // Who is currently talking. Indexed by client index. + + // This is the gamerules-defined list of players that you can hear. It is based on what teams people are on + // and is totally separate from the ban list. Indexed by client index. + CPlayerBitVec m_AudiblePlayers; + + // Players who have spoken at least once in the game so far + CPlayerBitVec m_VoiceEnabledPlayers; + + // This is who the server THINKS we have banned (it can become incorrect when a new player arrives on the server). + // It is checked periodically, and the server is told to squelch or unsquelch the appropriate players. + CPlayerBitVec m_ServerBannedPlayers; + + cl_entity_s m_VoiceHeadModels[VOICE_MAX_PLAYERS]; // These aren't necessarily in the order of players. They are just + // a place for it to put data in during CreateEntities. + + IVoiceStatusHelper *m_pHelper; // Each mod provides an implementation of this. + + + // Scoreboard icons. + double m_BlinkTimer; // Blink scoreboard icons.. + vgui::BitmapTGA *m_pScoreboardNeverSpoken; + vgui::BitmapTGA *m_pScoreboardNotSpeaking; + vgui::BitmapTGA *m_pScoreboardSpeaking; + vgui::BitmapTGA *m_pScoreboardSpeaking2; + vgui::BitmapTGA *m_pScoreboardSquelch; + vgui::BitmapTGA *m_pScoreboardBanned; + + vgui::Label *m_pBanButtons[VOICE_MAX_PLAYERS]; // scoreboard buttons. + + // Squelch mode stuff. + bool m_bInSquelchMode; + + HSPRITE m_VoiceHeadModel; // Voice head model (goes above players who are speaking). + float m_VoiceHeadModelHeight; // Height above their head to place the model. + + vgui::Image *m_pSpeakerLabelIcon; // Icon next to speaker labels. + + // Lower-right icons telling when the local player is talking.. + vgui::BitmapTGA *m_pLocalBitmap; // Represents the local client talking. + vgui::BitmapTGA *m_pAckBitmap; // Represents the server ack'ing the client talking. + vgui::ImagePanel *m_pLocalLabel; // Represents the local client talking. + + bool m_bTalking; // Set to true when the client thinks it's talking. + bool m_bServerAcked; // Set to true when the server knows the client is talking. + +public: + + CVoiceBanMgr m_BanMgr; // Tracks which users we have squelched and don't want to hear. + +public: + + bool m_bBanMgrInitialized; + + // Labels telling who is speaking. + CVoiceLabel m_Labels[MAX_VOICE_SPEAKERS]; + + // Cache the game directory for use when we shut down + char * m_pchGameDir; +}; + + +// Get the (global) voice manager. +CVoiceStatus* GetClientVoiceMgr(); + + +#endif // VOICE_STATUS_H diff --git a/game_shared/voice_vgui_tweakdlg.cpp b/game_shared/voice_vgui_tweakdlg.cpp new file mode 100644 index 0000000..b5cde70 --- /dev/null +++ b/game_shared/voice_vgui_tweakdlg.cpp @@ -0,0 +1,289 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#include "../cl_dll/hud.h" +#include "../cl_dll/cl_util.h" +#include "../cl_dll/vgui_teamfortressviewport.h" + + +#include "vgui_actionsignal.h" +#include "voice_vgui_tweakdlg.h" +#include "voice_vgui_tweakdlg.h" +#include "vgui_panel.h" +#include "vgui_scrollbar.h" +#include "vgui_slider.h" +#include "ivoicetweak.h" +#include "vgui_button.h" +#include "vgui_checkbutton2.h" +#include "vgui_helpers.h" + + +#define ITEM_BORDER 40 // Border between text and scrollbars on left and right. +#define VOICETWEAK_TRANSPARENCY 150 + + +class TweakScroller +{ +public: + TweakScroller(); + void Init(Panel *pParent, char *pText, int yPos); + + // Get/set value. Values are 0-1. + float GetValue(); + void SetValue(float val); + +public: + Label m_Label; + ScrollBar m_Scroll; + Slider m_Slider; +}; + + +class CVoiceVGUITweakDlg : public CMenuPanel, public ICheckButton2Handler +{ +typedef CMenuPanel BaseClass; + +public: + CVoiceVGUITweakDlg(); + ~CVoiceVGUITweakDlg(); + +// CMenuPanel overrides. +public: + virtual void Open(); + virtual void Close(); + + +// ICheckButton2Handler overrides. +public: + + virtual void StateChanged(CCheckButton2 *pButton); + + + +// Panel overrides. +public: + virtual void paintBackground(); + + +private: + + int m_DlgWidth; + int m_DlgHeight; + + Label m_Label; + + IVoiceTweak *m_pVoiceTweak; // Engine voice tweak API. + + TweakScroller m_MicVolume; + TweakScroller m_SpeakerVolume; + + CCheckButton2 m_VoiceModEnable; + + Button m_Button_OK; +}; + + + +bool g_bTweakDlgOpen = false; + +bool IsTweakDlgOpen() +{ + return g_bTweakDlgOpen; +} + + + +// ------------------------------------------------------------------------ // +// Global functions. +// ------------------------------------------------------------------------ // + +static CVoiceVGUITweakDlg g_VoiceTweakDlg; +CMenuPanel* GetVoiceTweakDlg() +{ + return &g_VoiceTweakDlg; +} + + +class CVoiceTweakOKButton : public ActionSignal +{ +public: + virtual void actionPerformed(Panel *pPanel) + { + gViewPort->HideVGUIMenu(); + } +}; +CVoiceTweakOKButton g_OKButtonSignal; + + + +// ------------------------------------------------------------------------ // +// TweakScroller +// ------------------------------------------------------------------------ // + +TweakScroller::TweakScroller() : + m_Label(""), + m_Scroll(0,0,0,0,false), + m_Slider(0,0,10,10,false) +{ +} + + +void TweakScroller::Init(Panel *pParent, char *pText, int yPos) +{ + int parentWidth, parentHeight; + pParent->getSize(parentWidth, parentHeight); + + // Setup the volume scroll bar. + m_Label.setParent(pParent); + m_Label.setFont(Scheme::sf_primary1); + m_Label.setContentAlignment(vgui::Label::a_northwest); + m_Label.setBgColor(0, 0, 0, 255); + m_Label.setFgColor(255,255,255,0); + m_Label.setPos(ITEM_BORDER, yPos); + m_Label.setSize(parentWidth/2-ITEM_BORDER, 20); + m_Label.setText(pText); + m_Label.setVisible(true); + + m_Slider.setRangeWindow(10); + m_Slider.setRangeWindowEnabled(true); + + m_Scroll.setPos(parentWidth/2+ITEM_BORDER, yPos); + m_Scroll.setSize(parentWidth/2-ITEM_BORDER*2, 20); + m_Scroll.setSlider(&m_Slider); + m_Scroll.setParent(pParent); + m_Scroll.setRange(0, 100); + m_Scroll.setFgColor(255,255,255,0); + m_Scroll.setBgColor(255,255,255,0); +} + + +float TweakScroller::GetValue() +{ + return m_Scroll.getValue() / 100.0f; +} + + +void TweakScroller::SetValue(float val) +{ + m_Scroll.setValue((int)(val * 100.0f)); +} + + +// ------------------------------------------------------------------------ // +// CVoiceVGUITweakDlg implementation. +// ------------------------------------------------------------------------ // + +CVoiceVGUITweakDlg::CVoiceVGUITweakDlg() + : CMenuPanel(VOICETWEAK_TRANSPARENCY, false, 0, 0, 0, 0), + m_Button_OK("",0,0), + m_Label("") +{ + m_pVoiceTweak = NULL; + m_Button_OK.addActionSignal(&g_OKButtonSignal); + m_Label.setBgColor(255,255,255,200); +} + + +CVoiceVGUITweakDlg::~CVoiceVGUITweakDlg() +{ +} + + +void CVoiceVGUITweakDlg::Open() +{ + if(g_bTweakDlgOpen) + return; + + g_bTweakDlgOpen = true; + + m_DlgWidth = ScreenWidth; + m_DlgHeight = ScreenHeight; + + m_pVoiceTweak = gEngfuncs.pVoiceTweak; + + // Tell the engine to start voice tweak mode (pipe voice output right to speakers). + m_pVoiceTweak->StartVoiceTweakMode(); + + // Set our size. + setPos((ScreenWidth - m_DlgWidth) / 2, (ScreenHeight - m_DlgHeight) / 2); + setSize(m_DlgWidth, m_DlgHeight); + + int curY = ITEM_BORDER; + m_MicVolume.Init(this, gHUD.m_TextMessage.BufferedLocaliseTextString("#Mic_Volume"), curY); + m_MicVolume.SetValue(m_pVoiceTweak->GetControlFloat(MicrophoneVolume)); + curY = PanelBottom(&m_MicVolume.m_Label); + + m_SpeakerVolume.Init(this, gHUD.m_TextMessage.BufferedLocaliseTextString("#Speaker_Volume"), curY); + m_SpeakerVolume.SetValue(m_pVoiceTweak->GetControlFloat(OtherSpeakerScale)); + curY = PanelBottom(&m_SpeakerVolume.m_Label); + + m_VoiceModEnable.setParent(this); + m_VoiceModEnable.SetImages("gfx/vgui/checked.tga", "gfx/vgui/unchecked.tga"); + m_VoiceModEnable.SetText("Enable Voice In This Mod"); + m_VoiceModEnable.setPos(ITEM_BORDER, curY); + m_VoiceModEnable.SetCheckboxLeft(false); + m_VoiceModEnable.SetChecked(!!gEngfuncs.pfnGetCvarFloat("voice_modenable")); + m_VoiceModEnable.SetHandler(this); + + // Setup the OK button. + int buttonWidth, buttonHeight; + m_Button_OK.setText(gHUD.m_TextMessage.BufferedLocaliseTextString("#Menu_OK")); + m_Button_OK.getSize(buttonWidth, buttonHeight); + m_Button_OK.setPos((m_DlgWidth - buttonWidth) / 2, m_DlgHeight - buttonHeight - 3); + m_Button_OK.setParent(this); + + // Put the label on the top. + m_Label.setBgColor(0, 0, 0, 255); + m_Label.setFgColor(255,255,255,0); + m_Label.setText(gHUD.m_TextMessage.BufferedLocaliseTextString("#Voice_Properties")); + int labelWidth, labelHeight; + m_Label.getSize(labelWidth, labelHeight); + m_Label.setPos((m_DlgWidth - labelWidth) / 2, 5); + m_Label.setParent(this); + + BaseClass::Open(); +} + + +void CVoiceVGUITweakDlg::Close() +{ + m_pVoiceTweak->EndVoiceTweakMode(); + g_bTweakDlgOpen = false; + + BaseClass::Close(); +} + + +void CVoiceVGUITweakDlg::paintBackground() +{ + BaseClass::paintBackground(); + + // Draw our border. + int w,h; + getSize(w,h); + + drawSetColor(128,128,128,1); + drawOutlinedRect(0, 0, w, h); + + float volume = m_MicVolume.GetValue(); + m_pVoiceTweak->SetControlFloat(MicrophoneVolume, volume); + + m_pVoiceTweak->SetControlFloat(OtherSpeakerScale, m_SpeakerVolume.GetValue()); +} + + +void CVoiceVGUITweakDlg::StateChanged(CCheckButton2 *pButton) +{ + if(pButton == &m_VoiceModEnable) + { + if(pButton->IsChecked()) + gEngfuncs.pfnClientCmd("voice_modenable 1"); + else + gEngfuncs.pfnClientCmd("voice_modenable 0"); + } +} + diff --git a/game_shared/voice_vgui_tweakdlg.h b/game_shared/voice_vgui_tweakdlg.h new file mode 100644 index 0000000..857ffd2 --- /dev/null +++ b/game_shared/voice_vgui_tweakdlg.h @@ -0,0 +1,25 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VOICE_VGUI_TWEAKDLG_H +#define VOICE_VGUI_TWEAKDLG_H +#ifdef _WIN32 +#pragma once +#endif + + +class CMenuPanel; + + +// Returns true if the tweak dialog is currently up. +bool IsTweakDlgOpen(); + +// Returns a global instance of the tweak dialog. +CMenuPanel* GetVoiceTweakDlg(); + + +#endif // VOICE_VGUI_TWEAKDLG_H diff --git a/global.txt b/global.txt new file mode 100644 index 0000000..54c5335 --- /dev/null +++ b/global.txt @@ -0,0 +1,2 @@ +Bad angles: +0 135 0 diff --git a/mainui/basemenu.cpp b/mainui/basemenu.cpp new file mode 100644 index 0000000..cf10f4d --- /dev/null +++ b/mainui/basemenu.cpp @@ -0,0 +1,1608 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + + +// ui_menu.c -- main menu interface +#define OEMRESOURCE // for OCR_* cursor junk + +#include "extdll.h" +#include "basemenu.h" +#include "keydefs.h" +#include "menufont.h" // built-in menu font +#include "utils.h" +#include "menu_btnsbmp_table.h" +//CR +#include "ui_title_anim.h" + +cvar_t *ui_precache; +cvar_t *ui_showmodels; +cvar_t *ui_videoconfig; + +uiStatic_t uiStatic; + +char uiEmptyString[256]; +const char *uiSoundIn = "media/launch_upmenu1.wav"; +const char *uiSoundOut = "media/launch_dnmenu1.wav"; +const char *uiSoundLaunch = "media/launch_select2.wav"; +const char *uiSoundGlow = "media/launch_glow1.wav"; +const char *uiSoundBuzz = "media/launch_deny2.wav"; +const char *uiSoundKey = "media/launch_select1.wav"; +const char *uiSoundRemoveKey = "media/launch_deny1.wav"; +const char *uiSoundMove = ""; // Xash3D not use movesound +const char *uiSoundNull = ""; + +int uiColorHelp = 0xFFFFFFFF; // 255, 255, 255, 255 // hint letters color +int uiPromptBgColor = 0xFF404040; // 64, 64, 64, 255 // dialog background color +int uiPromptTextColor = 0xFFF0B418; // 255, 160, 0, 255 // dialog or button letters color +int uiPromptFocusColor = 0xFFFFFF00; // 255, 255, 0, 255 // dialog or button focus letters color +int uiInputTextColor = 0xFFC0C0C0; // 192, 192, 192, 255 +int uiInputBgColor = 0xFF404040; // 64, 64, 64, 255 // field, scrollist, checkbox background color +int uiInputFgColor = 0xFF555555; // 85, 85, 85, 255 // field, scrollist, checkbox foreground color +int uiColorWhite = 0xFFFFFFFF; // 255, 255, 255, 255 // useful for bitmaps +int uiColorDkGrey = 0xFF404040; // 64, 64, 64, 255 // shadow and grayed items +int uiColorBlack = 0xFF000000; // 0, 0, 0, 255 // some controls background +int uiColorConsole = 0xFFF0B418; // just for reference + +// color presets (this is nasty hack to allow color presets to part of text) +const int g_iColorTable[8] = +{ +0xFF000000, // black +0xFFFF0000, // red +0xFF00FF00, // green +0xFFFFFF00, // yellow +0xFF0000FF, // blue +0xFF00FFFF, // cyan +0xFFF0B418, // dialog or button letters color +0xFFFFFFFF, // white +}; + +/* +================= +UI_ScaleCoords + +Any parameter can be NULL if you don't want it +================= +*/ +void UI_ScaleCoords( int *x, int *y, int *w, int *h ) +{ + if( x ) *x *= uiStatic.scaleX; + if( y ) *y *= uiStatic.scaleY; + if( w ) *w *= uiStatic.scaleX; + if( h ) *h *= uiStatic.scaleY; +} + +/* +================= +UI_CursorInRect +================= +*/ +int UI_CursorInRect( int x, int y, int w, int h ) +{ + if( uiStatic.cursorX < x ) + return FALSE; + if( uiStatic.cursorX > x + w ) + return FALSE; + if( uiStatic.cursorY < y ) + return FALSE; + if( uiStatic.cursorY > y + h ) + return FALSE; + return TRUE; +} + +/* +================= +UI_DrawPic +================= +*/ +void UI_DrawPic( int x, int y, int width, int height, const int color, const char *pic ) +{ + HIMAGE hPic = PIC_Load( pic ); + + int r, g, b, a; + UnpackRGBA( r, g, b, a, color ); + + PIC_Set( hPic, r, g, b, a ); + PIC_Draw( x, y, width, height ); +} + +/* +================= +UI_DrawPicAdditive +================= +*/ +void UI_DrawPicAdditive( int x, int y, int width, int height, const int color, const char *pic ) +{ + HIMAGE hPic = PIC_Load( pic ); + + int r, g, b, a; + UnpackRGBA( r, g, b, a, color ); + + PIC_Set( hPic, r, g, b, a ); + PIC_DrawAdditive( x, y, width, height ); +} + +/* +================= +UI_FillRect +================= +*/ +void UI_FillRect( int x, int y, int width, int height, const int color ) +{ + int r, g, b, a; + UnpackRGBA( r, g, b, a, color ); + + FillRGBA( x, y, width, height, r, g, b, a ); +} + +/* +================= +UI_DrawRectangleExt +================= +*/ +void UI_DrawRectangleExt( int in_x, int in_y, int in_w, int in_h, const int color, int outlineWidth ) +{ + int x, y, w, h; + + x = in_x - outlineWidth; + y = in_y - outlineWidth; + w = outlineWidth; + h = in_h + outlineWidth + outlineWidth; + + // draw left + UI_FillRect( x, y, w, h, color ); + + x = in_x + in_w; + y = in_y - outlineWidth; + w = outlineWidth; + h = in_h + outlineWidth + outlineWidth; + + // draw right + UI_FillRect( x, y, w, h, color ); + + x = in_x; + y = in_y - outlineWidth; + w = in_w; + h = outlineWidth; + + // draw top + UI_FillRect( x, y, w, h, color ); + + // draw bottom + x = in_x; + y = in_y + in_h; + w = in_w; + h = outlineWidth; + + UI_FillRect( x, y, w, h, color ); +} + +/* +================= +UI_DrawString +================= +*/ +void UI_DrawString( int x, int y, int w, int h, const char *string, const int color, int forceColor, int charW, int charH, int justify, int shadow ) +{ + int modulate, shadowModulate; + char line[1024], *l; + int xx, yy, ofsX, ofsY, len, ch; + + if( !string || !string[0] ) + return; + +#if 0 // g-cont. disabled 29/06/2011 + // this code do a bad things with prompt dialogues + // vertically centered + if( !strchr( string, '\n' )) + y = y + (( h - charH ) / 2 ); +#endif + + if( shadow ) + { + shadowModulate = PackAlpha( uiColorBlack, UnpackAlpha( color )); + + ofsX = charW / 8; + ofsY = charH / 8; + } + + modulate = color; + + yy = y; + while( *string ) + { + // get a line of text + len = 0; + while( *string ) + { + if( *string == '\n' ) + { + string++; + break; + } + + line[len++] = *string++; + if( len == sizeof( line ) - 1 ) + break; + } + line[len] = 0; + + // align the text as appropriate + if( justify == 0 ) xx = x; + if( justify == 1 ) xx = x + ((w - (ColorStrlen( line ) * charW )) / 2); + if( justify == 2 ) xx = x + (w - (ColorStrlen( line ) * charW )); + + // draw it + l = line; + while( *l ) + { + if( IsColorString( l )) + { + int colorNum = ColorIndex( *(l+1) ); + + if( colorNum == 7 && color != 0 ) + { + modulate = color; + } + else if( !forceColor ) + { + modulate = PackAlpha( g_iColorTable[colorNum], UnpackAlpha( color )); + } + + l += 2; + continue; + } + + ch = *l++; + ch &= 255; + + // fix for letter ¸ + if( ch == 0xB8 ) ch = (byte)'å'; + if( ch == 0xA8 ) ch = (byte)'Å'; + + if( ch != ' ' ) + { + if( shadow ) TextMessageDrawChar( xx + ofsX, yy + ofsY, charW, charH, ch, shadowModulate, uiStatic.hFont ); + TextMessageDrawChar( xx, yy, charW, charH, ch, modulate, uiStatic.hFont ); + } + xx += charW; + } + yy += charH; + } +} + +/* +================= +UI_DrawMouseCursor +================= +*/ +void UI_DrawMouseCursor( void ) +{ + menuCommon_s *item; + HICON hCursor = NULL; + int i; + + if( uiStatic.hideCursor ) return; + + for( i = 0; i < uiStatic.menuActive->numItems; i++ ) + { + item = (menuCommon_s *)uiStatic.menuActive->items[i]; + + if ( item->flags & (QMF_INACTIVE|QMF_HIDDEN)) + continue; + + if ( !UI_CursorInRect( item->x, item->y, item->width, item->height )) + continue; + + if ( item->flags & QMF_GRAYED ) + { + hCursor = (HICON)LoadCursor( NULL, (LPCTSTR)OCR_NO ); + } + else + { + if( item->type == QMTYPE_FIELD ) + hCursor = (HICON)LoadCursor( NULL, (LPCTSTR)OCR_IBEAM ); + } + break; + } + + if( !hCursor ) hCursor = (HICON)LoadCursor( NULL, (LPCTSTR)OCR_NORMAL ); + + SET_CURSOR( hCursor ); +} + +/* +================= +UI_DrawBackground_Callback +================= +*/ +void UI_DrawBackground_Callback( void *self ) +{ + if (!uiStatic.m_fHaveSteamBackground) + { + menuCommon_s *item = (menuCommon_s *)self; + UI_DrawPic( item->x, item->y, item->width, item->height, uiColorWhite, ((menuBitmap_s *)self)->pic ); + return; + } + + int xpos, ypos; + float xScale, yScale; + + // work out scaling factors + xScale = ScreenWidth / uiStatic.m_flTotalWidth; + yScale = ScreenHeight / uiStatic.m_flTotalHeight; + + // iterate and draw all the background pieces + ypos = 0; + for (int y = 0; y < BACKGROUND_ROWS; y++) + { + xpos = 0; + for (int x = 0; x < BACKGROUND_COLUMNS; x++) + { + bimage_t &bimage = uiStatic.m_SteamBackground[y][x]; + + int dx = (int)ceil(xpos * xScale); + int dy = (int)ceil(ypos * yScale); + int dw = (int)ceil(bimage.width * xScale); + int dt = (int)ceil(bimage.height * yScale); + + if (x == 0) dx = 0; + if (y == 0) dy = 0; + + PIC_Set( bimage.hImage, 255, 255, 255, 255 ); + PIC_Draw( dx, dy, dw, dt ); + xpos += bimage.width; + } + ypos += uiStatic.m_SteamBackground[y][0].height; + } +} + +/* +================= +UI_LoadBackgroundImage +================= +*/ +void UI_LoadBackgroundImage( void ) +{ + int num_background_images = 0; + char filename[512]; + + for( int y = 0; y < BACKGROUND_ROWS; y++ ) + { + for( int x = 0; x < BACKGROUND_COLUMNS; x++ ) + { + sprintf( filename, "resource/background/800_%d_%c_loading.tga", y + 1, 'a' + x ); + if (g_engfuncs.pfnFileExists( filename, TRUE )) + num_background_images++; + } + } + + if (num_background_images == (BACKGROUND_COLUMNS * BACKGROUND_ROWS)) + uiStatic.m_fHaveSteamBackground = TRUE; + else uiStatic.m_fHaveSteamBackground = FALSE; + + if (uiStatic.m_fHaveSteamBackground) + { + uiStatic.m_flTotalWidth = uiStatic.m_flTotalHeight = 0.0f; + + for( int y = 0; y < BACKGROUND_ROWS; y++ ) + { + for( int x = 0; x < BACKGROUND_COLUMNS; x++ ) + { + bimage_t &bimage = uiStatic.m_SteamBackground[y][x]; + sprintf(filename, "resource/background/800_%d_%c_loading.tga", y + 1, 'a' + x); + bimage.hImage = PIC_Load( filename, PIC_NOFLIP_TGA ); + bimage.width = PIC_Width( bimage.hImage ); + bimage.height = PIC_Height( bimage.hImage ); + + if (y==0) uiStatic.m_flTotalWidth += bimage.width; + if (x==0) uiStatic.m_flTotalHeight += bimage.height; + } + } + } + else + { + if( g_engfuncs.pfnFileExists( "gfx/shell/splash.bmp", TRUE )) + { + // if we doesn't have logo.avi in gamedir we don't want to draw it + if( !g_engfuncs.pfnFileExists( "media/logo.avi", TRUE )) + uiStatic.m_fDisableLogo = TRUE; + } + } +} + +/* +================= +UI_StartSound +================= +*/ +void UI_StartSound( const char *sound ) +{ + PLAY_SOUND( sound ); +} + +// ===================================================================== + + +/* +================= +UI_AddItem +================= +*/ +void UI_AddItem( menuFramework_s *menu, void *item ) +{ + menuCommon_s *generic = (menuCommon_s *)item; + + if( menu->numItems >= UI_MAX_MENUITEMS ) + HOST_ERROR( "UI_AddItem: UI_MAX_MENUITEMS limit exceeded\n" ); + + menu->items[menu->numItems] = item; + ((menuCommon_s *)menu->items[menu->numItems])->parent = menu; + ((menuCommon_s *)menu->items[menu->numItems])->flags &= ~QMF_HASMOUSEFOCUS; + menu->numItems++; + + switch( generic->type ) + { + case QMTYPE_SCROLLLIST: + UI_ScrollList_Init((menuScrollList_s *)item ); + break; + case QMTYPE_SPINCONTROL: + UI_SpinControl_Init((menuSpinControl_s *)item ); + break; + case QMTYPE_CHECKBOX: + UI_CheckBox_Init((menuCheckBox_s *)item ); + break; + case QMTYPE_SLIDER: + UI_Slider_Init((menuSlider_s *)item ); + break; + case QMTYPE_FIELD: + UI_Field_Init((menuField_s *)item ); + break; + case QMTYPE_ACTION: + UI_Action_Init((menuAction_s *)item ); + break; + case QMTYPE_BITMAP: + UI_Bitmap_Init((menuBitmap_s *)item ); + break; + case QMTYPE_BM_BUTTON: + UI_PicButton_Init((menuPicButton_s *)item ); + break; + default: + HOST_ERROR( "UI_AddItem: unknown item type (%i)\n", generic->type ); + } +} + +/* +================= +UI_CursorMoved +================= +*/ +void UI_CursorMoved( menuFramework_s *menu ) +{ + void (*callback)( void *self, int event ); + + if( menu->cursor == menu->cursorPrev ) + return; + + if( menu->cursorPrev >= 0 && menu->cursorPrev < menu->numItems ) + { + callback = ((menuCommon_s *)menu->items[menu->cursorPrev])->callback; + if( callback ) callback( menu->items[menu->cursorPrev], QM_LOSTFOCUS ); + } + + if( menu->cursor >= 0 && menu->cursor < menu->numItems ) + { + callback = ((menuCommon_s *)menu->items[menu->cursor])->callback; + if( callback ) callback( menu->items[menu->cursor], QM_GOTFOCUS ); + } +} + +/* +================= +UI_SetCursor +================= +*/ +void UI_SetCursor( menuFramework_s *menu, int cursor ) +{ + if(((menuCommon_s *)(menu->items[cursor]))->flags & (QMF_GRAYED|QMF_INACTIVE|QMF_HIDDEN)) + return; + + menu->cursorPrev = menu->cursor; + menu->cursor = cursor; + + UI_CursorMoved( menu ); +} + +/* +================= +UI_SetCursorToItem +================= +*/ +void UI_SetCursorToItem( menuFramework_s *menu, void *item ) +{ + for( int i = 0; i < menu->numItems; i++ ) + { + if( menu->items[i] == item ) + { + UI_SetCursor( menu, i ); + return; + } + } +} + +/* +================= +UI_ItemAtCursor +================= +*/ +void *UI_ItemAtCursor( menuFramework_s *menu ) +{ + if( menu->cursor < 0 || menu->cursor >= menu->numItems ) + return 0; + + // inactive items can't be has focus + if( ((menuCommon_s *)menu->items[menu->cursor])->flags & QMF_INACTIVE ) + return 0; + + return menu->items[menu->cursor]; +} + +/* +================= +UI_AdjustCursor + +This functiont takes the given menu, the direction, and attempts to +adjust the menu's cursor so that it's at the next available slot +================= +*/ +void UI_AdjustCursor( menuFramework_s *menu, int dir ) +{ + menuCommon_s *item; + int wrapped = false; +wrap: + while( menu->cursor >= 0 && menu->cursor < menu->numItems ) + { + item = (menuCommon_s *)menu->items[menu->cursor]; + if( item->flags & (QMF_GRAYED|QMF_INACTIVE|QMF_HIDDEN|QMF_MOUSEONLY)) + menu->cursor += dir; + else break; + } + + if( dir == 1 ) + { + if( menu->cursor >= menu->numItems ) + { + if( wrapped ) + { + menu->cursor = menu->cursorPrev; + return; + } + + menu->cursor = 0; + wrapped = true; + goto wrap; + } + } + else if( dir == -1 ) + { + if( menu->cursor < 0 ) + { + if( wrapped ) + { + menu->cursor = menu->cursorPrev; + return; + } + menu->cursor = menu->numItems - 1; + wrapped = true; + goto wrap; + } + } +} + +/* +================= +UI_DrawMenu +================= +*/ +void UI_DrawMenu( menuFramework_s *menu ) +{ + static long statusFadeTime; + static menuCommon_s *lastItem; + menuCommon_s *item; + int i; + + // draw contents + for( i = 0; i < menu->numItems; i++ ) + { + item = (menuCommon_s *)menu->items[i]; + + if( item->flags & QMF_HIDDEN ) + continue; + + if( item->ownerdraw ) + { + // total subclassing, owner draws everything + item->ownerdraw( item ); + continue; + } + + switch( item->type ) + { + case QMTYPE_SCROLLLIST: + UI_ScrollList_Draw((menuScrollList_s *)item ); + break; + case QMTYPE_SPINCONTROL: + UI_SpinControl_Draw((menuSpinControl_s *)item ); + break; + case QMTYPE_CHECKBOX: + UI_CheckBox_Draw((menuCheckBox_s *)item ); + break; + case QMTYPE_SLIDER: + UI_Slider_Draw((menuSlider_s *)item ); + break; + case QMTYPE_FIELD: + UI_Field_Draw((menuField_s *)item ); + break; + case QMTYPE_ACTION: + UI_Action_Draw((menuAction_s *)item ); + break; + case QMTYPE_BITMAP: + UI_Bitmap_Draw((menuBitmap_s *)item ); + break; + case QMTYPE_BM_BUTTON: + UI_PicButton_Draw((menuPicButton_s *)item ); + break; + } + } + + // draw status bar + item = (menuCommon_s *)UI_ItemAtCursor( menu ); + if( item != lastItem ) + { + // flash on selected button (like in GoldSrc) + if( item ) item->lastFocusTime = uiStatic.realTime; + statusFadeTime = uiStatic.realTime; + lastItem = item; + } + + if( item && ( item->flags & QMF_HASMOUSEFOCUS && !( item->flags & QMF_NOTIFY )) && ( item->statusText != NULL )) + { + // fade it in, but wait a second + int alpha = bound( 0, ((( uiStatic.realTime - statusFadeTime ) - 1000 ) * 0.001f ) * 255, 255 ); + int r, g, b, x, len; + + GetConsoleStringSize( item->statusText, &len, NULL ); + + UnpackRGB( r, g, b, uiColorHelp ); + TextMessageSetColor( r, g, b, alpha ); + x = ( ScreenWidth - len ) * 0.5; // centering + + DrawConsoleString( x, 720 * uiStatic.scaleY, item->statusText ); + } + else statusFadeTime = uiStatic.realTime; +} + +/* +================= +UI_DefaultKey +================= +*/ +const char *UI_DefaultKey( menuFramework_s *menu, int key, int down ) +{ + const char *sound = NULL; + menuCommon_s *item; + int cursorPrev; + + // menu system key + if( down && key == K_ESCAPE ) + { + UI_PopMenu(); + return uiSoundOut; + } + + if( !menu || !menu->numItems ) + return 0; + + item = (menuCommon_s *)UI_ItemAtCursor( menu ); + if( item && !(item->flags & (QMF_GRAYED|QMF_INACTIVE|QMF_HIDDEN))) + { + switch( item->type ) + { + case QMTYPE_SCROLLLIST: + sound = UI_ScrollList_Key((menuScrollList_s *)item, key, down ); + break; + case QMTYPE_SPINCONTROL: + sound = UI_SpinControl_Key((menuSpinControl_s *)item, key, down ); + break; + case QMTYPE_CHECKBOX: + sound = UI_CheckBox_Key((menuCheckBox_s *)item, key, down ); + break; + case QMTYPE_SLIDER: + sound = UI_Slider_Key((menuSlider_s *)item, key, down ); + break; + case QMTYPE_FIELD: + sound = UI_Field_Key((menuField_s *)item, key, down ); + break; + case QMTYPE_ACTION: + sound = UI_Action_Key((menuAction_s *)item, key, down ); + break; + case QMTYPE_BITMAP: + sound = UI_Bitmap_Key((menuBitmap_s *)item, key, down ); + break; + case QMTYPE_BM_BUTTON: + sound = UI_PicButton_Key((menuPicButton_s *)item, key, down ); + break; + } + if( sound ) return sound; // key was handled + } + + // system keys are always wait for keys down and never keys up + if( !down ) return 0; + + // default handling + switch( key ) + { + case K_UPARROW: + case K_KP_UPARROW: + case K_LEFTARROW: + case K_KP_LEFTARROW: + cursorPrev = menu->cursor; + menu->cursorPrev = menu->cursor; + menu->cursor--; + + UI_AdjustCursor( menu, -1 ); + if( cursorPrev != menu->cursor ) + { + UI_CursorMoved( menu ); + if( !(((menuCommon_s *)menu->items[menu->cursor])->flags & QMF_SILENT )) + sound = uiSoundMove; + } + break; + case K_DOWNARROW: + case K_KP_DOWNARROW: + case K_RIGHTARROW: + case K_KP_RIGHTARROW: + case K_TAB: + cursorPrev = menu->cursor; + menu->cursorPrev = menu->cursor; + menu->cursor++; + + UI_AdjustCursor(menu, 1); + if( cursorPrev != menu->cursor ) + { + UI_CursorMoved(menu); + if( !(((menuCommon_s *)menu->items[menu->cursor])->flags & QMF_SILENT )) + sound = uiSoundMove; + } + break; + case K_MOUSE1: + if( item ) + { + if((item->flags & QMF_HASMOUSEFOCUS) && !(item->flags & (QMF_GRAYED|QMF_INACTIVE|QMF_HIDDEN))) + return UI_ActivateItem( menu, item ); + } + + break; + case K_ENTER: + case K_KP_ENTER: + if( item ) + { + if( !(item->flags & (QMF_GRAYED|QMF_INACTIVE|QMF_HIDDEN|QMF_MOUSEONLY))) + return UI_ActivateItem( menu, item ); + } + break; + } + return sound; +} + +/* +================= +UI_ActivateItem +================= +*/ +const char *UI_ActivateItem( menuFramework_s *menu, menuCommon_s *item ) +{ + if( item->callback ) + { + item->callback( item, QM_ACTIVATED ); + + if( !( item->flags & QMF_SILENT )) + return uiSoundMove; + } + return 0; +} + +/* +================= +UI_RefreshServerList +================= +*/ +void UI_RefreshServerList( void ) +{ + uiStatic.numServers = 0; + memset( uiStatic.serverAddresses, 0, sizeof( uiStatic.serverAddresses )); + memset( uiStatic.serverNames, 0, sizeof( uiStatic.serverNames )); + + CLIENT_COMMAND( FALSE, "localservers\n" ); +} + +/* +================= +UI_RefreshInternetServerList +================= +*/ +void UI_RefreshInternetServerList( void ) +{ + uiStatic.numServers = 0; + memset( uiStatic.serverAddresses, 0, sizeof( uiStatic.serverAddresses )); + memset( uiStatic.serverNames, 0, sizeof( uiStatic.serverNames )); + + CLIENT_COMMAND( FALSE, "internetservers\n" ); +} + +/* +================= +UI_StartBackGroundMap +================= +*/ +bool UI_StartBackGroundMap( void ) +{ + static bool first = TRUE; + + if( !first ) return FALSE; + + first = FALSE; + + // some map is already running + if( !uiStatic.bgmapcount || CL_IsActive() || gpGlobals->demoplayback ) + return FALSE; + + int bgmapid = RANDOM_LONG( 0, uiStatic.bgmapcount - 1 ); + + char cmd[128]; + sprintf( cmd, "maps/%s.bsp", uiStatic.bgmaps[bgmapid] ); + if( !FILE_EXISTS( cmd )) return FALSE; + + sprintf( cmd, "map_background %s\n", uiStatic.bgmaps[bgmapid] ); + CLIENT_COMMAND( FALSE, cmd ); + + return TRUE; +} + +// ===================================================================== + +/* +================= +UI_CloseMenu +================= +*/ +void UI_CloseMenu( void ) +{ + uiStatic.menuActive = NULL; + uiStatic.menuDepth = 0; + uiStatic.visible = false; + + // clearing serverlist + uiStatic.numServers = 0; + memset( uiStatic.serverAddresses, 0, sizeof( uiStatic.serverAddresses )); + memset( uiStatic.serverNames, 0, sizeof( uiStatic.serverNames )); + + UI_ClearButtonStack (); + +// KEY_ClearStates (); + KEY_SetDest ( KEY_GAME ); +} + +/* +================= +UI_PushMenu +================= +*/ +void UI_PushMenu( menuFramework_s *menu ) +{ + int i; + menuCommon_s *item; + + // if this menu is already present, drop back to that level to avoid stacking menus by hotkeys + for( i = 0; i < uiStatic.menuDepth; i++ ) + { + if( uiStatic.menuStack[i] == menu ) + { + uiStatic.menuDepth = i; + break; + } + } + + if( i == uiStatic.menuDepth ) + { + if( uiStatic.menuDepth >= UI_MAX_MENUDEPTH ) + HOST_ERROR( "UI_PushMenu: menu stack overflow\n" ); + uiStatic.menuStack[uiStatic.menuDepth++] = menu; + } + + uiStatic.menuActive = menu; + uiStatic.firstDraw = true; + uiStatic.enterSound = gpGlobals->time + 0.15; // make some delay + uiStatic.visible = true; + + KEY_SetDest ( KEY_MENU ); + + menu->cursor = 0; + menu->cursorPrev = 0; + + // force first available item to have focus + for( i = 0; i < menu->numItems; i++ ) + { + item = (menuCommon_s *)menu->items[i]; + + if( item->flags & (QMF_GRAYED|QMF_INACTIVE|QMF_HIDDEN|QMF_MOUSEONLY)) + continue; + + menu->cursorPrev = -1; + UI_SetCursor( menu, i ); + break; + } +} + +/* +================= +UI_PopMenu +================= +*/ +void UI_PopMenu( void ) +{ + UI_StartSound( uiSoundOut ); + + uiStatic.menuDepth--; + + if( uiStatic.menuDepth < 0 ) + HOST_ERROR( "UI_PopMenu: menu stack underflow\n" ); + + UI_PopPButtonStack(); + + if( uiStatic.menuDepth ) + { + uiStatic.menuActive = uiStatic.menuStack[uiStatic.menuDepth-1]; + uiStatic.firstDraw = true; + } + else if ( CL_IsActive( )) + { + UI_CloseMenu(); + } + else + { + // never trying the close menu when client isn't connected + KEY_SetDest( KEY_MENU ); + UI_Main_Menu(); + } + + if( uiStatic.m_fDemosPlayed && uiStatic.m_iOldMenuDepth == uiStatic.menuDepth ) + { + CLIENT_COMMAND( FALSE, "demos\n" ); + uiStatic.m_fDemosPlayed = false; + uiStatic.m_iOldMenuDepth = 0; + } +} + +// ===================================================================== + +/* +================= +UI_UpdateMenu +================= +*/ +void UI_UpdateMenu( float flTime ) +{ + if( !uiStatic.initialized ) + return; + + UI_DrawFinalCredits (); + + if( !uiStatic.visible ) + return; + + if( !uiStatic.menuActive ) + return; + + uiStatic.realTime = flTime * 1000; + uiStatic.framecount++; + + if( CVAR_GET_FLOAT( "cl_background" ) && !g_engfuncs.pfnClientInGame()) + return; // don't draw menu while level is loading + + if( uiStatic.firstDraw ) + { + // we loading background so skip SCR_Update + if( UI_StartBackGroundMap( )) return; + + if( uiStatic.menuActive->activateFunc ) + uiStatic.menuActive->activateFunc(); + } + + // draw menu + if( uiStatic.menuActive->drawFunc ) + uiStatic.menuActive->drawFunc(); + else UI_DrawMenu( uiStatic.menuActive ); + + if( uiStatic.firstDraw ) + { + uiStatic.firstDraw = false; + static int first = TRUE; + + if( first ) + { + // if game was launched with commandline e.g. +map or +load ignore the music + if( !CL_IsActive( )) + BACKGROUND_TRACK( "gamestartup", "gamestartup" ); + first = FALSE; + } + } + + //CR + UI_DrawTitleAnim(); + // + + // draw cursor + UI_DrawMouseCursor(); + + // delay playing the enter sound until after the menu has been + // drawn, to avoid delay while caching images + if( uiStatic.enterSound > 0.0f && uiStatic.enterSound <= gpGlobals->time ) + { + UI_StartSound( uiSoundIn ); + uiStatic.enterSound = -1; + } +} + +/* +================= +UI_KeyEvent +================= +*/ +void UI_KeyEvent( int key, int down ) +{ + const char *sound; + + if( !uiStatic.initialized ) + return; + + if( !uiStatic.visible ) + return; + + if( !uiStatic.menuActive ) + return; + + if( uiStatic.menuActive->keyFunc ) + sound = uiStatic.menuActive->keyFunc( key, down ); + else sound = UI_DefaultKey( uiStatic.menuActive, key, down ); + + if( !down ) return; + if( sound && sound != uiSoundNull ) + UI_StartSound( sound ); +} + +/* +================= +UI_CharEvent +================= +*/ +void UI_CharEvent( int key ) +{ + menuFramework_s *menu; + menuCommon_s *item; + + if( !uiStatic.initialized ) + return; + + if( !uiStatic.visible ) + return; + + if( !uiStatic.menuActive ) + return; + + menu = uiStatic.menuActive; + + if( !menu || !menu->numItems ) + return; + + item = (menuCommon_s *)UI_ItemAtCursor( menu ); + + if( item && !(item->flags & (QMF_GRAYED|QMF_INACTIVE|QMF_HIDDEN))) + { + switch( item->type ) + { + case QMTYPE_FIELD: + UI_Field_Char((menuField_s *)item, key ); + break; + } + } +} + +/* +================= +UI_MouseMove +================= +*/ +void UI_MouseMove( int x, int y ) +{ + int i; + menuCommon_s *item; + + if( !uiStatic.initialized ) + return; + + if( !uiStatic.visible ) + return; + + if( !uiStatic.menuActive ) + return; + + // now menu uses absolute coordinates + uiStatic.cursorX = x; + uiStatic.cursorY = y; + + if( UI_CursorInRect( 1, 1, ScreenWidth - 1, ScreenHeight - 1 )) + uiStatic.mouseInRect = true; + else uiStatic.mouseInRect = false; + + uiStatic.cursorX = bound( 0, uiStatic.cursorX, ScreenWidth ); + uiStatic.cursorY = bound( 0, uiStatic.cursorY, ScreenHeight ); + + // region test the active menu items + for( i = 0; i < uiStatic.menuActive->numItems; i++ ) + { + item = (menuCommon_s *)uiStatic.menuActive->items[i]; + + if( item->flags & (QMF_GRAYED|QMF_INACTIVE|QMF_HIDDEN)) + { + if( item->flags & QMF_HASMOUSEFOCUS ) + { + if( !UI_CursorInRect( item->x, item->y, item->width, item->height )) + item->flags &= ~QMF_HASMOUSEFOCUS; + else item->lastFocusTime = uiStatic.realTime; + } + continue; + } + + if( !UI_CursorInRect( item->x, item->y, item->width, item->height )) + { + item->bPressed = false; + continue; + } + + // set focus to item at cursor + if( uiStatic.menuActive->cursor != i ) + { + UI_SetCursor( uiStatic.menuActive, i ); + ((menuCommon_s *)(uiStatic.menuActive->items[uiStatic.menuActive->cursorPrev]))->flags &= ~QMF_HASMOUSEFOCUS; + + if (!(((menuCommon_s *)(uiStatic.menuActive->items[uiStatic.menuActive->cursor]))->flags & QMF_SILENT )) + UI_StartSound( uiSoundMove ); + } + + ((menuCommon_s *)(uiStatic.menuActive->items[uiStatic.menuActive->cursor]))->flags |= QMF_HASMOUSEFOCUS; + ((menuCommon_s *)(uiStatic.menuActive->items[uiStatic.menuActive->cursor]))->lastFocusTime = uiStatic.realTime; + return; + } + + // out of any region + if( uiStatic.menuActive->numItems ) + { + ((menuCommon_s *)(uiStatic.menuActive->items[uiStatic.menuActive->cursor]))->flags &= ~QMF_HASMOUSEFOCUS; + ((menuCommon_s *)(uiStatic.menuActive->items[uiStatic.menuActive->cursor]))->bPressed = false; + + // a mouse only item restores focus to the previous item + if(((menuCommon_s *)(uiStatic.menuActive->items[uiStatic.menuActive->cursor]))->flags & QMF_MOUSEONLY ) + { + if( uiStatic.menuActive->cursorPrev != -1 ) + uiStatic.menuActive->cursor = uiStatic.menuActive->cursorPrev; + } + } +} + +/* +================= +UI_SetActiveMenu +================= +*/ +void UI_SetActiveMenu( int fActive ) +{ + if( !uiStatic.initialized ) + return; + + // don't continue firing if we leave game + KEY_ClearStates(); + uiStatic.framecount = 0; + + if( fActive ) + { + KEY_SetDest( KEY_MENU ); + UI_Main_Menu(); + } + else + { + UI_CloseMenu(); + } +} + +/* +================= +UI_AddServerToList +================= +*/ +void UI_AddServerToList( netadr_t adr, const char *info ) +{ + int i; + + if( !uiStatic.initialized ) + return; + + if( uiStatic.numServers == UI_MAX_SERVERS ) + return; // full + + if( stricmp( gMenu.m_gameinfo.gamefolder, Info_ValueForKey( info, "gamedir" ))) + return; + + // ignore if duplicated + for( i = 0; i < uiStatic.numServers; i++ ) + { + if( !stricmp( uiStatic.serverNames[i], info )) + return; + } + + // add it to the list + uiStatic.updateServers = true; // info has been updated + uiStatic.serverAddresses[uiStatic.numServers] = adr; + strncpy( uiStatic.serverNames[uiStatic.numServers], info, sizeof( uiStatic.serverNames[uiStatic.numServers] )); + uiStatic.numServers++; +} + +/* +================= +UI_IsVisible + +Some systems may need to know if it is visible or not +================= +*/ +int UI_IsVisible( void ) +{ + if( !uiStatic.initialized ) + return false; + return uiStatic.visible; +} + +void UI_GetCursorPos( int *pos_x, int *pos_y ) +{ + if( pos_x ) *pos_x = uiStatic.cursorX; + if( pos_y ) *pos_y = uiStatic.cursorY; +} + +void UI_SetCursorPos( int pos_x, int pos_y ) +{ +// uiStatic.cursorX = bound( 0, pos_x, ScreenWidth ); +// uiStatic.cursorY = bound( 0, pos_y, ScreenHeight ); + uiStatic.mouseInRect = true; +} + +void UI_ShowCursor( int show ) +{ + uiStatic.hideCursor = (show) ? false : true; +} + +int UI_MouseInRect( void ) +{ + return uiStatic.mouseInRect; +} + +/* +================= +UI_Precache +================= +*/ +void UI_Precache( void ) +{ + if( !uiStatic.initialized ) + return; + + if( !ui_precache->value ) + return; + + PIC_Load( UI_LEFTARROW ); + PIC_Load( UI_LEFTARROWFOCUS ); + PIC_Load( UI_RIGHTARROW ); + PIC_Load( UI_RIGHTARROWFOCUS ); + PIC_Load( UI_UPARROW ); + PIC_Load( UI_UPARROWFOCUS ); + PIC_Load( UI_DOWNARROW ); + PIC_Load( UI_DOWNARROWFOCUS ); + + if( ui_precache->value == 1 ) + return; + + UI_Main_Precache(); + UI_NewGame_Precache(); + UI_LoadGame_Precache(); + UI_SaveGame_Precache(); + UI_SaveLoad_Precache(); + UI_MultiPlayer_Precache(); + UI_Options_Precache(); + UI_InternetGames_Precache(); + UI_LanGame_Precache(); + UI_PlayerSetup_Precache(); + UI_Controls_Precache(); + UI_AdvControls_Precache(); + UI_GameOptions_Precache(); + UI_CreateGame_Precache(); + UI_Audio_Precache(); + UI_Video_Precache(); + UI_VidOptions_Precache(); + UI_VidModes_Precache(); + UI_CustomGame_Precache(); + UI_Credits_Precache(); +} + +void UI_ParseColor( char *&pfile, int *outColor ) +{ + int i, color[3]; + char token[1024]; + + memset( color, 0xFF, sizeof( color )); + + for( i = 0; i < 3; i++ ) + { + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; + color[i] = atoi( token ); + } + + *outColor = PackRGB( color[0], color[1], color[2] ); +} + +void UI_ApplyCustomColors( void ) +{ + char *afile = (char *)LOAD_FILE( "gfx/shell/colors.lst", NULL ); + char *pfile = afile; + char token[1024]; + + if( !afile ) + { + // not error, not warning, just notify + Con_Printf( "UI_SetColors: colors.lst not found\n" ); + return; + } + + while(( pfile = COM_ParseFile( pfile, token )) != NULL ) + { + if( !stricmp( token, "HELP_COLOR" )) + { + UI_ParseColor( pfile, &uiColorHelp ); + } + else if( !stricmp( token, "PROMPT_BG_COLOR" )) + { + UI_ParseColor( pfile, &uiPromptBgColor ); + } + else if( !stricmp( token, "PROMPT_TEXT_COLOR" )) + { + UI_ParseColor( pfile, &uiPromptTextColor ); + } + else if( !stricmp( token, "PROMPT_FOCUS_COLOR" )) + { + UI_ParseColor( pfile, &uiPromptFocusColor ); + } + else if( !stricmp( token, "INPUT_TEXT_COLOR" )) + { + UI_ParseColor( pfile, &uiInputTextColor ); + } + else if( !stricmp( token, "INPUT_BG_COLOR" )) + { + UI_ParseColor( pfile, &uiInputBgColor ); + } + else if( !stricmp( token, "INPUT_FG_COLOR" )) + { + UI_ParseColor( pfile, &uiInputFgColor ); + } + else if( !stricmp( token, "CON_TEXT_COLOR" )) + { + UI_ParseColor( pfile, &uiColorConsole ); + } + } + + int r, g, b; + + UnpackRGB( r, g, b, uiColorConsole ); + ConsoleSetColor( r, g, b ); + + FREE_FILE( afile ); +} + +static void UI_LoadBackgroundMapList( void ) +{ + if( !g_engfuncs.pfnFileExists( "scripts/chapterbackgrounds.txt", TRUE )) + return; + + char *afile = (char *)LOAD_FILE( "scripts/chapterbackgrounds.txt", NULL ); + char *pfile = afile; + char token[1024]; + + uiStatic.bgmapcount = 0; + + if( !afile ) + { + Con_Printf( "UI_LoadBackgroundMapList: chapterbackgrounds.txt not found\n" ); + return; + } + + while(( pfile = COM_ParseFile( pfile, token )) != NULL ) + { + // skip the numbers (old format list) + if( isdigit( token[0] )) continue; + + strncpy( uiStatic.bgmaps[uiStatic.bgmapcount], token, sizeof( uiStatic.bgmaps[0] ) - 1 ); + if( ++uiStatic.bgmapcount > UI_MAX_BGMAPS ) + break; // list is full + } + + FREE_FILE( afile ); +} + +/* +================= +UI_VidInit +================= +*/ +int UI_VidInit( void ) +{ + UI_Precache (); + + uiStatic.scaleX = ScreenWidth / 1024.0f; + uiStatic.scaleY = ScreenHeight / 768.0f; + + // move cursor to screen center + uiStatic.cursorX = ScreenWidth >> 1; + uiStatic.cursorY = ScreenHeight >> 1; + uiStatic.outlineWidth = 4; + uiStatic.sliderWidth = 6; + + // all menu buttons have the same view sizes + uiStatic.buttons_draw_width = UI_BUTTONS_WIDTH; + uiStatic.buttons_draw_height = UI_BUTTONS_HEIGHT; + + UI_ScaleCoords( NULL, NULL, &uiStatic.outlineWidth, NULL ); + UI_ScaleCoords( NULL, NULL, &uiStatic.sliderWidth, NULL ); + UI_ScaleCoords( NULL, NULL, &uiStatic.buttons_draw_width, &uiStatic.buttons_draw_height ); + + // trying to load colors.lst + UI_ApplyCustomColors (); + + // trying to load chapterbackgrounds.txt + UI_LoadBackgroundMapList (); + + // register menu font + uiStatic.hFont = PIC_Load( "#XASH_SYSTEMFONT_001.bmp", menufont_bmp, sizeof( menufont_bmp )); + + UI_LoadBackgroundImage (); +#if 0 + FILE *f; + + // dump menufont onto disk + f = fopen( "menufont.bmp", "wb" ); + fwrite( menufont_bmp, sizeof( menufont_bmp ), 1, f ); + fclose( f ); +#endif + + // reload all menu buttons + UI_LoadBmpButtons (); + + // now recalc all the menus in stack + for( int i = 0; i < uiStatic.menuDepth; i++ ) + { + menuFramework_s *item = uiStatic.menuStack[i]; + + // do vid restart for all pushed elements + if( item && item->vidInitFunc ) + item->vidInitFunc(); + } + + return 1; +} + +/* +================= +UI_Init +================= +*/ +void UI_Init( void ) +{ + // register our cvars and commands + ui_precache = CVAR_REGISTER( "ui_precache", "0", FCVAR_ARCHIVE ); + ui_showmodels = CVAR_REGISTER( "ui_showmodels", "0", FCVAR_ARCHIVE ); + ui_videoconfig = CVAR_REGISTER( "video_config", "high", FCVAR_ARCHIVE ); + + Cmd_AddCommand( "menu_main", UI_Main_Menu ); + Cmd_AddCommand( "menu_newgame", UI_NewGame_Menu ); + Cmd_AddCommand( "menu_loadgame", UI_LoadGame_Menu ); + Cmd_AddCommand( "menu_savegame", UI_SaveGame_Menu ); + Cmd_AddCommand( "menu_saveload", UI_SaveLoad_Menu ); + Cmd_AddCommand( "menu_multiplayer", UI_MultiPlayer_Menu ); + Cmd_AddCommand( "menu_options", UI_Options_Menu ); + Cmd_AddCommand( "menu_langame", UI_LanGame_Menu ); + Cmd_AddCommand( "menu_internetgames", UI_InternetGames_Menu ); + Cmd_AddCommand( "menu_playersetup", UI_PlayerSetup_Menu ); + Cmd_AddCommand( "menu_controls", UI_Controls_Menu ); + Cmd_AddCommand( "menu_advcontrols", UI_AdvControls_Menu ); + Cmd_AddCommand( "menu_gameoptions", UI_GameOptions_Menu ); + Cmd_AddCommand( "menu_creategame", UI_CreateGame_Menu ); + Cmd_AddCommand( "menu_audio", UI_Audio_Menu ); + Cmd_AddCommand( "menu_video", UI_Video_Menu ); + Cmd_AddCommand( "menu_vidoptions", UI_VidOptions_Menu ); + Cmd_AddCommand( "menu_vidmodes", UI_VidModes_Menu ); + Cmd_AddCommand( "menu_customgame", UI_CustomGame_Menu ); + + CHECK_MAP_LIST( TRUE ); + + memset( uiEmptyString, ' ', sizeof( uiEmptyString )); // HACKHACK + uiStatic.initialized = true; + + // setup game info + GetGameInfo( &gMenu.m_gameinfo ); + + // load custom strings + UI_LoadCustomStrings(); + + //CR + UI_InitTitleAnim(); +} + +/* +================= +UI_Shutdown +================= +*/ +void UI_Shutdown( void ) +{ + if( !uiStatic.initialized ) + return; + + Cmd_RemoveCommand( "menu_main" ); + Cmd_RemoveCommand( "menu_newgame" ); + Cmd_RemoveCommand( "menu_loadgame" ); + Cmd_RemoveCommand( "menu_savegame" ); + Cmd_RemoveCommand( "menu_saveload" ); + Cmd_RemoveCommand( "menu_multiplayer" ); + Cmd_RemoveCommand( "menu_options" ); + Cmd_RemoveCommand( "menu_internetgames" ); + Cmd_RemoveCommand( "menu_langame" ); + Cmd_RemoveCommand( "menu_playersetup" ); + Cmd_RemoveCommand( "menu_controls" ); + Cmd_RemoveCommand( "menu_advcontrols" ); + Cmd_RemoveCommand( "menu_gameoptions" ); + Cmd_RemoveCommand( "menu_creategame" ); + Cmd_RemoveCommand( "menu_audio" ); + Cmd_RemoveCommand( "menu_video" ); + Cmd_RemoveCommand( "menu_vidoptions" ); + Cmd_RemoveCommand( "menu_vidmodes" ); + Cmd_RemoveCommand( "menu_advanced" ); + Cmd_RemoveCommand( "menu_performance" ); + Cmd_RemoveCommand( "menu_network" ); + Cmd_RemoveCommand( "menu_defaults" ); + Cmd_RemoveCommand( "menu_cinematics" ); + Cmd_RemoveCommand( "menu_customgame" ); + + memset( &uiStatic, 0, sizeof( uiStatic_t )); +} diff --git a/mainui/basemenu.h b/mainui/basemenu.h new file mode 100644 index 0000000..fc82c5a --- /dev/null +++ b/mainui/basemenu.h @@ -0,0 +1,492 @@ +/* +basemenu.h - menu basic header +Copyright (C) 2010 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef BASEMENU_H +#define BASEMENU_H + +// engine constants +#define GAME_NORMAL 0 +#define GAME_SINGLEPLAYER_ONLY 1 +#define GAME_MULTIPLAYER_ONLY 2 + +#define KEY_CONSOLE 0 +#define KEY_GAME 1 +#define KEY_MENU 2 + +#define CS_SIZE 64 // size of one config string +#define CS_TIME 16 // size of time string + +// color strings +#define ColorIndex( c ) ((( c ) - '0' ) & 7 ) +#define IsColorString( p ) ( p && *( p ) == '^' && *(( p ) + 1) && *(( p ) + 1) >= '0' && *(( p ) + 1 ) <= '9' ) + +#include "netadr.h" + +#define ART_BACKGROUND "gfx/shell/splash" +#define UI_SLIDER_MAIN "gfx/shell/slider" +#define UI_LEFTARROW "gfx/shell/larrowdefault" +#define UI_LEFTARROWFOCUS "gfx/shell/larrowflyover" +#define UI_LEFTARROWPRESSED "gfx/shell/larrowpressed" +#define UI_RIGHTARROW "gfx/shell/rarrowdefault" +#define UI_RIGHTARROWFOCUS "gfx/shell/rarrowflyover" +#define UI_RIGHTARROWPRESSED "gfx/shell/rarrowpressed" +#define UI_UPARROW "gfx/shell/uparrowd" +#define UI_UPARROWFOCUS "gfx/shell/uparrowf" +#define UI_UPARROWPRESSED "gfx/shell/uparrowp" +#define UI_DOWNARROW "gfx/shell/dnarrowd" +#define UI_DOWNARROWFOCUS "gfx/shell/dnarrowf" +#define UI_DOWNARROWPRESSED "gfx/shell/dnarrowp" +#define UI_CHECKBOX_EMPTY "gfx/shell/cb_empty" +#define UI_CHECKBOX_GRAYED "gfx/shell/cb_disabled" +#define UI_CHECKBOX_FOCUS "gfx/shell/cb_over" +#define UI_CHECKBOX_PRESSED "gfx/shell/cb_down" +#define UI_CHECKBOX_ENABLED "gfx/shell/cb_checked" + +#define UI_CURSOR_SIZE 40 + +#define UI_MAX_MENUDEPTH 8 +#define UI_MAX_MENUITEMS 64 + +#define UI_PULSE_DIVISOR 75 +#define UI_BLINK_TIME 250 +#define UI_BLINK_MASK 499 + +#define UI_SMALL_CHAR_WIDTH 10 +#define UI_SMALL_CHAR_HEIGHT 20 +#define UI_MED_CHAR_WIDTH 18 +#define UI_MED_CHAR_HEIGHT 26 +#define UI_BIG_CHAR_WIDTH 20 +#define UI_BIG_CHAR_HEIGHT 40 + +#define UI_MAX_FIELD_LINE 256 +#define UI_OUTLINE_WIDTH uiStatic.outlineWidth // outline thickness + +#define UI_MAXGAMES 900 // slots for savegame/demos +#define UI_MAX_SERVERS 64 +#define UI_MAX_BGMAPS 32 + +#define MAX_HINT_TEXT 512 + +// menu banners used fiexed rectangle (virtual screenspace at 640x480) +#define UI_BANNER_POSX 72 +#define UI_BANNER_POSY 72 +#define UI_BANNER_WIDTH 736 +#define UI_BANNER_HEIGHT 128 + +// menu buttons dims +#define UI_BUTTONS_WIDTH 240 +#define UI_BUTTONS_HEIGHT 40 +#define UI_BUTTON_CHARWIDTH 14 // empirically determined value + +#define ID_BACKGROUND 0 // catch warning on change this + +// Generic types +typedef enum +{ + QMTYPE_SCROLLLIST, + QMTYPE_SPINCONTROL, + QMTYPE_CHECKBOX, + QMTYPE_SLIDER, + QMTYPE_FIELD, + QMTYPE_ACTION, + QMTYPE_BITMAP, + // CR: íîâûé òèï êíîïêè + QMTYPE_BM_BUTTON +} menuType_t; + +// Generic flags +#define QMF_LEFT_JUSTIFY (1<<0) +#define QMF_CENTER_JUSTIFY (1<<1) +#define QMF_RIGHT_JUSTIFY (1<<2) +#define QMF_GRAYED (1<<3) // Grays and disables +#define QMF_INACTIVE (1<<4) // Disables any input +#define QMF_HIDDEN (1<<5) // Doesn't draw +#define QMF_NUMBERSONLY (1<<6) // Edit field is only numbers +#define QMF_LOWERCASE (1<<7) // Edit field is all lower case +#define QMF_UPPERCASE (1<<8) // Edit field is all upper case +#define QMF_DRAW_ADDITIVE (1<<9) // enable additive for this bitmap +#define QMF_PULSEIFFOCUS (1<<10) +#define QMF_HIGHLIGHTIFFOCUS (1<<11) +#define QMF_SMALLFONT (1<<12) +#define QMF_BIGFONT (1<<13) +#define QMF_DROPSHADOW (1<<14) +#define QMF_SILENT (1<<15) // Don't play sounds +#define QMF_HASMOUSEFOCUS (1<<16) +#define QMF_MOUSEONLY (1<<17) // Only mouse input allowed +#define QMF_FOCUSBEHIND (1<<18) // Focus draws behind normal item +#define QMF_NOTIFY (1<<19) // draw notify at right screen side +#define QMF_ACT_ONRELEASE (1<<20) // call Key_Event when button is released +#define QMF_ALLOW_COLORSTRINGS (1<<21) // allow colorstring in MENU_FIELD +#define QMF_HIDEINPUT (1<<22) // used for "password" field + +// Callback notifications +#define QM_GOTFOCUS 1 +#define QM_LOSTFOCUS 2 +#define QM_ACTIVATED 3 +#define QM_CHANGED 4 +#define QM_PRESSED 5 + +typedef struct +{ + int cursor; + int cursorPrev; + + void *items[UI_MAX_MENUITEMS]; + int numItems; + + void (*drawFunc)( void ); + const char *(*keyFunc)( int key, int down ); + void (*activateFunc)( void ); + void (*vidInitFunc)( void ); +} menuFramework_s; + +typedef struct +{ + menuType_t type; + const char *name; + int id; + + unsigned int flags; + + int x; + int y; + int width; + int height; + + int x2; + int y2; + int width2; + int height2; + + int color; + int focusColor; + + int charWidth; + int charHeight; + + int lastFocusTime; + bool bPressed; + + const char *statusText; + + menuFramework_s *parent; + + void (*callback) (void *self, int event); + void (*ownerdraw) (void *self); + +} menuCommon_s; + +typedef struct +{ + menuCommon_s generic; + const char *background; + const char *upArrow; + const char *upArrowFocus; + const char *downArrow; + const char *downArrowFocus; + const char **itemNames; + int numItems; + int curItem; + int topItem; + int numRows; +// scrollbar stuff // ADAMIX + int scrollBarX; + int scrollBarY; + int scrollBarWidth; + int scrollBarHeight; + int scrollBarSliding; +} menuScrollList_s; + +typedef struct +{ + menuCommon_s generic; + const char *background; + const char *leftArrow; + const char *rightArrow; + const char *leftArrowFocus; + const char *rightArrowFocus; + float minValue; + float maxValue; + float curValue; + float range; +} menuSpinControl_s; + +typedef struct +{ + menuCommon_s generic; + const char *background; + int maxLength; // can't be more than UI_MAX_FIELD_LINE + char buffer[UI_MAX_FIELD_LINE]; + int widthInChars; + int cursor; + int scroll; +} menuField_s; + +typedef struct +{ + menuCommon_s generic; + const char *background; +} menuAction_s; + +typedef struct +{ + menuCommon_s generic; + HIMAGE pic; + int button_id; +} menuPicButton_s; + +typedef struct +{ + menuCommon_s generic; + const char *pic; + const char *focusPic; +} menuBitmap_s; + +typedef struct +{ + menuCommon_s generic; + int enabled; + const char *emptyPic; + const char *focusPic; // can be replaced with pressPic manually + const char *checkPic; + const char *grayedPic; // when QMF_GRAYED is set +} menuCheckBox_s; + +typedef struct +{ + menuCommon_s generic; + float minValue; + float maxValue; + float curValue; + float drawStep; + int numSteps; + float range; + int keepSlider; // when mouse button is holds +} menuSlider_s; + +void UI_ScrollList_Init( menuScrollList_s *sl ); +const char *UI_ScrollList_Key( menuScrollList_s *sl, int key, int down ); +void UI_ScrollList_Draw( menuScrollList_s *sl ); + +void UI_SpinControl_Init( menuSpinControl_s *sc ); +const char *UI_SpinControl_Key( menuSpinControl_s *sc, int key, int down ); +void UI_SpinControl_Draw( menuSpinControl_s *sc ); + +void UI_Slider_Init( menuSlider_s *sl ); +const char *UI_Slider_Key( menuSlider_s *sl, int key, int down ); +void UI_Slider_Draw( menuSlider_s *sl ); + +void UI_CheckBox_Init( menuCheckBox_s *cb ); +const char *UI_CheckBox_Key( menuCheckBox_s *cb, int key, int down ); +void UI_CheckBox_Draw( menuCheckBox_s *cb ); + +void UI_Field_Init( menuField_s *f ); +const char *UI_Field_Key( menuField_s *f, int key, int down ); +void UI_Field_Char( menuField_s *f, int key ); +void UI_Field_Draw( menuField_s *f ); + +void UI_Action_Init( menuAction_s *t ); +const char *UI_Action_Key( menuAction_s *t, int key, int down ); +void UI_Action_Draw( menuAction_s *t ); + +void UI_Bitmap_Init( menuBitmap_s *b ); +const char *UI_Bitmap_Key( menuBitmap_s *b, int key, int down ); +void UI_Bitmap_Draw( menuBitmap_s *b ); + +void UI_PicButton_Init( menuPicButton_s *b ); +const char *UI_PicButton_Key( menuPicButton_s *b, int key, int down ); +void UI_PicButton_Draw( menuPicButton_s *item ); + +// ===================================================================== +// Main menu interface + +extern cvar_t *ui_precache; +extern cvar_t *ui_showmodels; + +#define BACKGROUND_ROWS 3 +#define BACKGROUND_COLUMNS 4 + +typedef struct +{ + HIMAGE hImage; + int width; + int height; +} bimage_t; + +typedef struct +{ + menuFramework_s *menuActive; + menuFramework_s *menuStack[UI_MAX_MENUDEPTH]; + int menuDepth; + + netadr_t serverAddresses[UI_MAX_SERVERS]; + char serverNames[UI_MAX_SERVERS][256]; + int numServers; + int updateServers; // true is receive new info about servers + + char bgmaps[UI_MAX_BGMAPS][80]; + int bgmapcount; + + HIMAGE hFont; // mainfont + + // handle steam background images + bimage_t m_SteamBackground[BACKGROUND_ROWS][BACKGROUND_COLUMNS]; + float m_flTotalWidth; + float m_flTotalHeight; + bool m_fHaveSteamBackground; + bool m_fDisableLogo; + bool m_fDemosPlayed; + int m_iOldMenuDepth; + + float scaleX; + float scaleY; + int outlineWidth; + int sliderWidth; + + int cursorX; + int cursorY; + int realTime; + int firstDraw; + float enterSound; + int mouseInRect; + int hideCursor; + int visible; + int framecount; // how many frames menu visible + int initialized; + + // btns_main.bmp stuff + HIMAGE buttonsPics[71]; // FIXME: replace with PC_BUTTONCOUNT + + int buttons_width; // btns_main.bmp global width + int buttons_height; // per one button with all states (inactive, focus, pressed) + + int buttons_draw_width; // scaled image what we drawing + int buttons_draw_height; +} uiStatic_t; + +extern uiStatic_t uiStatic; + +extern char uiEmptyString[256]; // HACKHACK +extern const char *uiSoundIn; +extern const char *uiSoundOut; +extern const char *uiSoundKey; +extern const char *uiSoundRemoveKey; +extern const char *uiSoundLaunch; +extern const char *uiSoundBuzz; +extern const char *uiSoundGlow; +extern const char *uiSoundMove; +extern const char *uiSoundNull; + +extern int uiColorHelp; +extern int uiPromptBgColor; +extern int uiPromptTextColor; +extern int uiPromptFocusColor; +extern int uiInputTextColor; +extern int uiInputBgColor; +extern int uiInputFgColor; + +extern int uiColorWhite; +extern int uiColorDkGrey; +extern int uiColorBlack; + +void UI_ScaleCoords( int *x, int *y, int *w, int *h ); +int UI_CursorInRect( int x, int y, int w, int h ); +void UI_UtilSetupPicButton( menuPicButton_s *pic, int ID ); +void UI_DrawPic( int x, int y, int w, int h, const int color, const char *pic ); +void UI_DrawPicAdditive( int x, int y, int w, int h, const int color, const char *pic ); +void UI_FillRect( int x, int y, int w, int h, const int color ); +#define UI_DrawRectangle( x, y, w, h, color ) UI_DrawRectangleExt( x, y, w, h, color, uiStatic.outlineWidth ) +void UI_DrawRectangleExt( int in_x, int in_y, int in_w, int in_h, const int color, int outlineWidth ); +void UI_DrawString( int x, int y, int w, int h, const char *str, const int col, int forceCol, int charW, int charH, int justify, int shadow ); +void UI_StartSound( const char *sound ); +void UI_LoadBmpButtons( void ); + +void UI_DrawBackground_Callback( void *self ); +void UI_AddItem ( menuFramework_s *menu, void *item ); +void UI_CursorMoved( menuFramework_s *menu ); +void UI_SetCursor( menuFramework_s *menu, int cursor ); +void UI_SetCursorToItem( menuFramework_s *menu, void *item ); +void *UI_ItemAtCursor( menuFramework_s *menu ); +void UI_AdjustCursor( menuFramework_s *menu, int dir ); +void UI_DrawMenu( menuFramework_s *menu ); +const char *UI_DefaultKey( menuFramework_s *menu, int key, int down ); +const char *UI_ActivateItem( menuFramework_s *menu, menuCommon_s *item ); +void UI_RefreshInternetServerList( void ); +void UI_RefreshServerList( void ); +int UI_CreditsActive( void ); +void UI_DrawFinalCredits( void ); + +void UI_CloseMenu( void ); +void UI_PushMenu( menuFramework_s *menu ); +void UI_PopMenu( void ); + +// Precache +void UI_Main_Precache( void ); +void UI_NewGame_Precache( void ); +void UI_LoadGame_Precache( void ); +void UI_SaveGame_Precache( void ); +void UI_SaveLoad_Precache( void ); +void UI_MultiPlayer_Precache( void ); +void UI_Options_Precache( void ); +void UI_InternetGames_Precache( void ); +void UI_LanGame_Precache( void ); +void UI_PlayerSetup_Precache( void ); +void UI_Controls_Precache( void ); +void UI_AdvControls_Precache( void ); +void UI_GameOptions_Precache( void ); +void UI_CreateGame_Precache( void ); +void UI_Audio_Precache( void ); +void UI_Video_Precache( void ); +void UI_VidOptions_Precache( void ); +void UI_VidModes_Precache( void ); +void UI_CustomGame_Precache( void ); +void UI_Credits_Precache( void ); +void UI_GoToSite_Precache( void ); + +// Menus +void UI_Main_Menu( void ); +void UI_NewGame_Menu( void ); +void UI_LoadGame_Menu( void ); +void UI_SaveGame_Menu( void ); +void UI_SaveLoad_Menu( void ); +void UI_MultiPlayer_Menu( void ); +void UI_Options_Menu( void ); +void UI_InternetGames_Menu( void ); +void UI_LanGame_Menu( void ); +void UI_PlayerSetup_Menu( void ); +void UI_Controls_Menu( void ); +void UI_AdvControls_Menu( void ); +void UI_GameOptions_Menu( void ); +void UI_CreateGame_Menu( void ); +void UI_Audio_Menu( void ); +void UI_Video_Menu( void ); +void UI_VidOptions_Menu( void ); +void UI_VidModes_Menu( void ); +void UI_CustomGame_Menu( void ); +void UI_Credits_Menu( void ); + +// +//----------------------------------------------------- +// +class CMenu +{ +public: + // Game information + GAMEINFO m_gameinfo; +}; + +extern CMenu gMenu; + +#endif // BASEMENU_H \ No newline at end of file diff --git a/mainui/enginecallback.h b/mainui/enginecallback.h new file mode 100644 index 0000000..9c01019 --- /dev/null +++ b/mainui/enginecallback.h @@ -0,0 +1,185 @@ +/* +enginecallback.h - actual engine callbacks +Copyright (C) 2010 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef ENGINECALLBACKS_H +#define ENGINECALLBACKS_H + +// built-in memory manager +#define MALLOC( x ) (*g_engfuncs.pfnMemAlloc)( x, __FILE__, __LINE__ ) +#define CALLOC( x, y ) (*g_engfuncs.pfnMemAlloc)((x) * (y), __FILE__, __LINE__ ) +#define FREE( x ) (*g_engfuncs.pfnMemFree)( x, __FILE__, __LINE__ ) + +// screen handlers +#define PIC_Width (*g_engfuncs.pfnPIC_Width) +#define PIC_Height (*g_engfuncs.pfnPIC_Height) +#define PIC_EnableScissor (*g_engfuncs.pfnPIC_EnableScissor) +#define PIC_DisableScissor (*g_engfuncs.pfnPIC_DisableScissor) +#define FillRGBA (*g_engfuncs.pfnFillRGBA) +#define GetScreenInfo (*g_engfuncs.pfnGetScreenInfo) +#define GetGameInfo (*g_engfuncs.pfnGetGameInfo) +#define CheckGameDll (*g_engfuncs.pfnCheckGameDll) + +#define PIC_SetGamma( x, y ) (*g_engfuncs.pfnProcessImage)( x, y, -1, -1 ) +#define PIC_Remap( x, y, z ) (*g_engfuncs.pfnProcessImage)( x, -1.0f, y, z ) + +#define DRAW_LOGO (*g_engfuncs.pfnDrawLogo) +#define PRECACHE_LOGO( x ) (*g_engfuncs.pfnDrawLogo)( x, 0, 0, 0, 0 ) +#define GetLogoWidth (*g_engfuncs.pfnGetLogoWidth) +#define GetLogoHeight (*g_engfuncs.pfnGetLogoHeight) +#define GetLogoLength (*g_engfuncs.pfnGetLogoLength) + +inline HIMAGE PIC_Load( const char *szPicName, long flags = 0 ) +{ + return g_engfuncs.pfnPIC_Load( szPicName, NULL, 0, flags ); +} + +inline HIMAGE PIC_Load( const char *szPicName, const byte *ucRawImage, long ulRawImageSize, long flags = 0 ) +{ + return g_engfuncs.pfnPIC_Load( szPicName, ucRawImage, ulRawImageSize, flags ); +} + +#define PIC_Free (*g_engfuncs.pfnPIC_Free) +#define PLAY_SOUND (*g_engfuncs.pfnPlayLocalSound) +#define CVAR_REGISTER (*g_engfuncs.pfnRegisterVariable) +#define CVAR_SET_FLOAT (*g_engfuncs.pfnCvarSetValue) +#define CVAR_GET_FLOAT (*g_engfuncs.pfnGetCvarFloat) +#define CVAR_GET_STRING (*g_engfuncs.pfnGetCvarString) +#define CVAR_SET_STRING (*g_engfuncs.pfnCvarSetString) +#define CLIENT_COMMAND (*g_engfuncs.pfnClientCmd) +#define CLIENT_JOIN (*g_engfuncs.pfnClientJoin) + +#define GET_MENU_EDICT (*g_engfuncs.pfnGetPlayerModel) +#define ENGINE_SET_MODEL (*g_engfuncs.pfnSetModel) +#define R_ClearScene (*g_engfuncs.pfnClearScene) +#define R_AddEntity (*g_engfuncs.CL_CreateVisibleEntity) +#define R_RenderFrame (*g_engfuncs.pfnRenderScene) + +#define LOAD_FILE (*g_engfuncs.COM_LoadFile) +#define FILE_EXISTS( file ) (*g_engfuncs.pfnFileExists)( file, FALSE ) +#define FREE_FILE (*g_engfuncs.COM_FreeFile) +#define GET_GAME_DIR (*g_engfuncs.pfnGetGameDir) +#define HOST_ERROR (*g_engfuncs.pfnHostError) +#define COM_ParseFile (*g_engfuncs.COM_ParseFile) +#define KEY_SetDest (*g_engfuncs.pfnSetKeyDest) +#define KEY_ClearStates (*g_engfuncs.pfnKeyClearStates) +#define KEY_KeynumToString (*g_engfuncs.pfnKeynumToString) +#define KEY_GetBinding (*g_engfuncs.pfnKeyGetBinding) +#define KEY_SetBinding (*g_engfuncs.pfnKeySetBinding) +#define KEY_IsDown (*g_engfuncs.pfnKeyIsDown) +#define KEY_GetOverstrike (*g_engfuncs.pfnKeyGetOverstrikeMode) +#define KEY_SetOverstrike (*g_engfuncs.pfnKeySetOverstrikeMode) +#define Key_GetState (*g_engfuncs.pfnKeyGetState) +#define SET_CURSOR (*g_engfuncs.pfnSetCursor) + +#define Cmd_AddCommand (*g_engfuncs.pfnAddCommand) +#define Cmd_RemoveCommand (*g_engfuncs.pfnDelCommand) +#define CMD_ARGC (*g_engfuncs.pfnCmdArgc) +#define CMD_ARGV (*g_engfuncs.pfnCmdArgv) +#define Con_Printf (*g_engfuncs.Con_Printf) +#define Con_NPrintf (*g_engfuncs.Con_NPrintf) + +#define GET_GAMES_LIST (*g_engfuncs.pfnGetGamesList) +#define BACKGROUND_TRACK (*g_engfuncs.pfnPlayBackgroundTrack) +#define SHELL_EXECUTE (*g_engfuncs.pfnShellExecute) +#define HOST_WRITECONFIG (*g_engfuncs.pfnWriteServerConfig) +#define HOST_CHANGEGAME (*g_engfuncs.pfnChangeInstance) +#define CHECK_MAP_LIST (*g_engfuncs.pfnCreateMapsList) +#define HOST_ENDGAME (*g_engfuncs.pfnHostEndGame) +#define GET_CLIPBOARD (*g_engfuncs.pfnGetClipboardData) +#define FS_SEARCH (*g_engfuncs.pfnGetFilesList) +#define MAP_IS_VALID (*g_engfuncs.pfnIsMapValid) + +#define GET_SAVE_COMMENT (*g_engfuncs.pfnGetSaveComment) +#define GET_DEMO_COMMENT (*g_engfuncs.pfnGetDemoComment) + +#define CL_IsActive() (g_engfuncs.pfnClientInGame() && !CVAR_GET_FLOAT( "cl_background" )) + +inline void PIC_Set( HIMAGE hPic, int r, int g, int b ) +{ + g_engfuncs.pfnPIC_Set( hPic, r, g, b, 255 ); +} + +inline void PIC_Set( HIMAGE hPic, int r, int g, int b, int a ) +{ + g_engfuncs.pfnPIC_Set( hPic, r, g, b, a ); +} + +inline void PIC_Draw( int x, int y, const wrect_t *prc ) +{ + g_engfuncs.pfnPIC_Draw( x, y, -1, -1, prc ); +} + +inline void PIC_Draw( int x, int y, int width, int height ) +{ + g_engfuncs.pfnPIC_Draw( x, y, width, height, NULL ); +} + +inline void PIC_Draw( int x, int y, int width, int height, const wrect_t *prc ) +{ + g_engfuncs.pfnPIC_Draw( x, y, width, height, prc ); +} + +inline void PIC_DrawTrans( int x, int y, const wrect_t *prc ) +{ + g_engfuncs.pfnPIC_DrawTrans( x, y, -1, -1, prc ); +} + +inline void PIC_DrawTrans( int x, int y, int width, int height ) +{ + g_engfuncs.pfnPIC_DrawTrans( x, y, width, height, NULL ); +} + +inline void PIC_DrawHoles( int x, int y, const wrect_t *prc ) +{ + g_engfuncs.pfnPIC_DrawHoles( x, y, -1, -1, prc ); +} + +inline void SPR_DrawHoles( int x, int y, int width, int height ) +{ + g_engfuncs.pfnPIC_DrawHoles( x, y, width, height, NULL ); +} + +inline void PIC_DrawAdditive( int x, int y, int width, int height ) +{ + g_engfuncs.pfnPIC_DrawAdditive( x, y, width, height, NULL ); +} + +inline void PIC_DrawAdditive( int x, int y, const wrect_t *prc ) +{ + g_engfuncs.pfnPIC_DrawAdditive( x, y, -1, -1, prc ); +} + +inline void PIC_DrawAdditive( int x, int y, int w, int h, const wrect_t *prc ) +{ + g_engfuncs.pfnPIC_DrawAdditive( x, y, w, h, prc ); +} + +inline void TextMessageSetColor( int r, int g, int b, int alpha = 255 ) +{ + g_engfuncs.pfnDrawSetTextColor( r, g, b, alpha ); +} + +#define TextMessageDrawChar (*g_engfuncs.pfnDrawCharacter) +#define DrawConsoleString (*g_engfuncs.pfnDrawConsoleString) +#define GetConsoleStringSize (*g_engfuncs.pfnDrawConsoleStringLen) +#define ConsoleSetColor (*g_engfuncs.pfnSetConsoleDefaultColor) + +#define RANDOM_LONG (*g_engfuncs.pfnRandomLong) +#define RANDOM_FLOAT (*g_engfuncs.pfnRandomFloat) + +#define COMPARE_FILE_TIME (*g_engfuncs.pfnCompareFileTime) +#define VID_GET_MODE (*g_engfuncs.pfnGetModeString) + +#endif//ENGINECALLBACKS_H \ No newline at end of file diff --git a/mainui/extdll.h b/mainui/extdll.h new file mode 100644 index 0000000..94721d1 --- /dev/null +++ b/mainui/extdll.h @@ -0,0 +1,43 @@ +/* +extdll.h - must be included into the all ui files +Copyright (C) 2010 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef EXTDLL_H +#define EXTDLL_H + +// shut-up compiler warnings +#pragma warning(disable : 4305) // int or float data truncation +#pragma warning(disable : 4201) // nameless struct/union +#pragma warning(disable : 4514) // unreferenced inline function removed +#pragma warning(disable : 4100) // unreferenced formal parameter +#pragma warning(disable : 4244) // conversion from 'float' to 'int', possible loss of data + +#include "windows.h" + +// Misc C-runtime library headers +#include +#include +#include +#include +#include + +#define bound( min, num, max ) ((num) >= (min) ? ((num) < (max) ? (num) : (max)) : (min)) + +typedef int (*cmpfunc)( const void *a, const void *b ); + +#define ARRAYSIZE( p ) (sizeof( p ) / sizeof( p[0] )) + +#include "menu_int.h" + +#endif//EXTDLL_H \ No newline at end of file diff --git a/mainui/legacy/menu_playdemo.cpp b/mainui/legacy/menu_playdemo.cpp new file mode 100644 index 0000000..1a9b1ba --- /dev/null +++ b/mainui/legacy/menu_playdemo.cpp @@ -0,0 +1,464 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "extdll.h" +#include "basemenu.h" +#include "utils.h" +#include "keydefs.h" + +#define ART_BANNER "gfx/shell/head_load" + +#define ID_BACKGROUND 0 +#define ID_BANNER 1 +#define ID_PLAY 2 +#define ID_DELETE 3 +#define ID_CANCEL 4 +#define ID_DEMOLIST 5 +#define ID_TABLEHINT 6 +#define ID_LEVELSHOT 7 +#define ID_MSGBOX 8 +#define ID_MSGTEXT 9 +#define ID_YES 10 +#define ID_NO 11 + +#define LEVELSHOT_X 72 +#define LEVELSHOT_Y 400 +#define LEVELSHOT_W 192 +#define LEVELSHOT_H 160 + +#define TITLE_LENGTH 32 +#define MAPNAME_LENGTH 24+TITLE_LENGTH +#define MAXCLIENTS_LENGTH 16+MAPNAME_LENGTH + +typedef struct +{ + char demoName[UI_MAXGAMES][CS_SIZE]; + char delName[UI_MAXGAMES][CS_SIZE]; + char demoDescription[UI_MAXGAMES][256]; + char *demoDescriptionPtr[UI_MAXGAMES]; + + menuFramework_s menu; + + menuBitmap_s background; + menuBitmap_s banner; + menuAction_s play; + menuAction_s remove; + menuAction_s cancel; + + menuScrollList_s demosList; + + menuBitmap_s levelShot; + menuAction_s hintMessage; + char hintText[MAX_HINT_TEXT]; + + // prompt dialog + menuAction_s msgBox; + menuAction_s promptMessage; + menuAction_s yes; + menuAction_s no; +} uiPlayDemo_t; + +static uiPlayDemo_t uiPlayDemo; + +/* +================= +UI_MsgBox_Ownerdraw +================= +*/ +static void UI_MsgBox_Ownerdraw( void *self ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + UI_FillRect( item->x, item->y, item->width, item->height, uiPromptBgColor ); +} + +static void UI_DeleteDialog( void ) +{ + // toggle main menu between active\inactive + // show\hide remove dialog + uiPlayDemo.play.generic.flags ^= QMF_INACTIVE; + uiPlayDemo.remove.generic.flags ^= QMF_INACTIVE; + uiPlayDemo.cancel.generic.flags ^= QMF_INACTIVE; + uiPlayDemo.demosList.generic.flags ^= QMF_INACTIVE; + + uiPlayDemo.msgBox.generic.flags ^= QMF_HIDDEN; + uiPlayDemo.promptMessage.generic.flags ^= QMF_HIDDEN; + uiPlayDemo.no.generic.flags ^= QMF_HIDDEN; + uiPlayDemo.yes.generic.flags ^= QMF_HIDDEN; + +} + +/* +================= +UI_PlayDemo_KeyFunc +================= +*/ +static const char *UI_PlayDemo_KeyFunc( int key, int down ) +{ + if( down && key == K_ESCAPE && uiPlayDemo.play.generic.flags & QMF_INACTIVE ) + { + UI_DeleteDialog(); + return uiSoundNull; + } + return UI_DefaultKey( &uiPlayDemo.menu, key, down ); +} + +/* +================= +UI_PlayDemo_GetDemoList +================= +*/ +static void UI_PlayDemo_GetDemoList( void ) +{ + char comment[256]; + char **filenames; + int i, numFiles; + + filenames = FS_SEARCH( "demos/*.dem", &numFiles, TRUE ); + + for( i = 0; i < numFiles; i++ ) + { + if( i >= UI_MAXGAMES ) break; + + if( !GET_DEMO_COMMENT( filenames[i], comment )) + { + if( strlen( comment )) + { + // get name string even if not found - CL_GetComment can be mark demos + // as etc + StringConcat( uiPlayDemo.demoDescription[i], uiEmptyString, TITLE_LENGTH ); + StringConcat( uiPlayDemo.demoDescription[i], comment, MAPNAME_LENGTH ); + StringConcat( uiPlayDemo.demoDescription[i], uiEmptyString, MAXCLIENTS_LENGTH ); + uiPlayDemo.demoDescriptionPtr[i] = uiPlayDemo.demoDescription[i]; + COM_FileBase( filenames[i], uiPlayDemo.delName[i] ); + } + else uiPlayDemo.demoDescriptionPtr[i] = NULL; + continue; + } + + // strip path, leave only filename (empty slots doesn't have savename) + COM_FileBase( filenames[i], uiPlayDemo.demoName[i] ); + COM_FileBase( filenames[i], uiPlayDemo.delName[i] ); + + // fill demo desc + StringConcat( uiPlayDemo.demoDescription[i], comment + CS_SIZE, TITLE_LENGTH ); + StringConcat( uiPlayDemo.demoDescription[i], uiEmptyString, TITLE_LENGTH ); + StringConcat( uiPlayDemo.demoDescription[i], comment, MAPNAME_LENGTH ); + StringConcat( uiPlayDemo.demoDescription[i], uiEmptyString, MAPNAME_LENGTH ); // fill remaining entries + StringConcat( uiPlayDemo.demoDescription[i], comment + CS_SIZE * 2, MAXCLIENTS_LENGTH ); + StringConcat( uiPlayDemo.demoDescription[i], uiEmptyString, MAXCLIENTS_LENGTH ); + uiPlayDemo.demoDescriptionPtr[i] = uiPlayDemo.demoDescription[i]; + } + + for ( ; i < UI_MAXGAMES; i++ ) + uiPlayDemo.demoDescriptionPtr[i] = NULL; + uiPlayDemo.demosList.itemNames = (const char **)uiPlayDemo.demoDescriptionPtr; + + if( strlen( uiPlayDemo.demoName[0] ) == 0 ) + uiPlayDemo.play.generic.flags |= QMF_GRAYED; + else uiPlayDemo.play.generic.flags &= ~QMF_GRAYED; + + if( strlen( uiPlayDemo.delName[0] ) == 0 || !stricmp( gpGlobals->demoname, uiPlayDemo.delName[uiPlayDemo.demosList.curItem] )) + uiPlayDemo.remove.generic.flags |= QMF_GRAYED; + else uiPlayDemo.remove.generic.flags &= ~QMF_GRAYED; +} + +/* +================= +UI_PlayDemo_Callback +================= +*/ +static void UI_PlayDemo_Callback( void *self, int event ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + if( event == QM_CHANGED ) + { + if( strlen( uiPlayDemo.demoName[uiPlayDemo.demosList.curItem] ) == 0 ) + uiPlayDemo.play.generic.flags |= QMF_GRAYED; + else uiPlayDemo.play.generic.flags &= ~QMF_GRAYED; + + if( strlen( uiPlayDemo.delName[uiPlayDemo.demosList.curItem] ) == 0 || !stricmp( gpGlobals->demoname, uiPlayDemo.delName[uiPlayDemo.demosList.curItem] )) + uiPlayDemo.remove.generic.flags |= QMF_GRAYED; + else uiPlayDemo.remove.generic.flags &= ~QMF_GRAYED; + return; + } + + if( event != QM_ACTIVATED ) + return; + + switch( item->id ) + { + case ID_CANCEL: + UI_PopMenu(); + break; + case ID_PLAY: + if( gpGlobals->demoplayback || gpGlobals->demorecording ) + { + CLIENT_COMMAND( FALSE, "stop" ); + uiPlayDemo.play.generic.name = "Play"; + uiPlayDemo.play.generic.statusText = "Play a demo"; + uiPlayDemo.remove.generic.flags &= ~QMF_GRAYED; + } + else if( strlen( uiPlayDemo.demoName[uiPlayDemo.demosList.curItem] )) + { + char cmd[128]; + sprintf( cmd, "playdemo \"%s\"\n", uiPlayDemo.demoName[uiPlayDemo.demosList.curItem] ); + CLIENT_COMMAND( FALSE, cmd ); + } + break; + case ID_NO: + case ID_DELETE: + UI_DeleteDialog(); + break; + case ID_YES: + if( strlen( uiPlayDemo.delName[uiPlayDemo.demosList.curItem] )) + { + char cmd[128]; + sprintf( cmd, "killdemo \"%s\"\n", uiPlayDemo.delName[uiPlayDemo.demosList.curItem] ); + + CLIENT_COMMAND( TRUE, cmd ); + + sprintf( cmd, "demos/%s.bmp", uiPlayDemo.delName[uiPlayDemo.demosList.curItem] ); + PIC_Free( cmd ); + + // restarts the menu + UI_PopMenu(); + UI_PlayDemo_Menu(); + return; + } + UI_DeleteDialog(); + break; + } +} + +/* +================= +UI_PlayDemo_Ownerdraw +================= +*/ +static void UI_PlayDemo_Ownerdraw( void *self ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + if( item->type != QMTYPE_ACTION && item->id == ID_LEVELSHOT ) + { + int x, y, w, h; + + // draw the levelshot + x = LEVELSHOT_X; + y = LEVELSHOT_Y; + w = LEVELSHOT_W; + h = LEVELSHOT_H; + + UI_ScaleCoords( &x, &y, &w, &h ); + + if( strlen( uiPlayDemo.demoName[uiPlayDemo.demosList.curItem] )) + { + char demoshot[128]; + + sprintf( demoshot, "demos/%s.bmp", uiPlayDemo.demoName[uiPlayDemo.demosList.curItem] ); + + if( !FILE_EXISTS( demoshot )) + UI_DrawPicAdditive( x, y, w, h, uiColorWhite, "gfx/empty.tga" ); + else UI_DrawPic( x, y, w, h, uiColorWhite, demoshot ); + } + else UI_DrawPicAdditive( x, y, w, h, uiColorWhite, "gfx/empty.tga" ); + + // draw the rectangle + UI_DrawRectangle( item->x, item->y, item->width, item->height, uiInputFgColor ); + } +} + +/* +================= +UI_PlayDemo_Init +================= +*/ +static void UI_PlayDemo_Init( void ) +{ + memset( &uiPlayDemo, 0, sizeof( uiPlayDemo_t )); + + uiPlayDemo.menu.vidInitFunc = UI_PlayDemo_Init; + uiPlayDemo.menu.keyFunc = UI_PlayDemo_KeyFunc; + + StringConcat( uiPlayDemo.hintText, "Title", TITLE_LENGTH ); + StringConcat( uiPlayDemo.hintText, uiEmptyString, TITLE_LENGTH ); + StringConcat( uiPlayDemo.hintText, "Map", MAPNAME_LENGTH ); + StringConcat( uiPlayDemo.hintText, uiEmptyString, MAPNAME_LENGTH ); + StringConcat( uiPlayDemo.hintText, "Max Clients", MAXCLIENTS_LENGTH ); + StringConcat( uiPlayDemo.hintText, uiEmptyString, MAXCLIENTS_LENGTH ); + + uiPlayDemo.background.generic.id = ID_BACKGROUND; + uiPlayDemo.background.generic.type = QMTYPE_BITMAP; + uiPlayDemo.background.generic.flags = QMF_INACTIVE; + uiPlayDemo.background.generic.x = 0; + uiPlayDemo.background.generic.y = 0; + uiPlayDemo.background.generic.width = 1024; + uiPlayDemo.background.generic.height = 768; + uiPlayDemo.background.pic = ART_BACKGROUND; + + uiPlayDemo.banner.generic.id = ID_BANNER; + uiPlayDemo.banner.generic.type = QMTYPE_BITMAP; + uiPlayDemo.banner.generic.flags = QMF_INACTIVE|QMF_DRAW_ADDITIVE; + uiPlayDemo.banner.generic.x = UI_BANNER_POSX; + uiPlayDemo.banner.generic.y = UI_BANNER_POSY; + uiPlayDemo.banner.generic.width = UI_BANNER_WIDTH; + uiPlayDemo.banner.generic.height = UI_BANNER_HEIGHT; + uiPlayDemo.banner.pic = ART_BANNER; + + uiPlayDemo.play.generic.id = ID_PLAY; + uiPlayDemo.play.generic.type = QMTYPE_ACTION; + uiPlayDemo.play.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiPlayDemo.play.generic.x = 72; + uiPlayDemo.play.generic.y = 230; + + if ( gpGlobals->demoplayback ) + { + uiPlayDemo.play.generic.name = "Stop"; + uiPlayDemo.play.generic.statusText = "Stop a demo playing"; + } + else if ( gpGlobals->demorecording ) + { + uiPlayDemo.play.generic.name = "Stop"; + uiPlayDemo.play.generic.statusText = "Stop a demo recording"; + } + else + { + uiPlayDemo.play.generic.name = "Play"; + uiPlayDemo.play.generic.statusText = "Play a demo"; + } + uiPlayDemo.play.generic.callback = UI_PlayDemo_Callback; + + uiPlayDemo.remove.generic.id = ID_DELETE; + uiPlayDemo.remove.generic.type = QMTYPE_ACTION; + uiPlayDemo.remove.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiPlayDemo.remove.generic.x = 72; + uiPlayDemo.remove.generic.y = 280; + uiPlayDemo.remove.generic.name = "Delete"; + uiPlayDemo.remove.generic.statusText = "Delete a demo"; + uiPlayDemo.remove.generic.callback = UI_PlayDemo_Callback; + + uiPlayDemo.cancel.generic.id = ID_CANCEL; + uiPlayDemo.cancel.generic.type = QMTYPE_ACTION; + uiPlayDemo.cancel.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiPlayDemo.cancel.generic.x = 72; + uiPlayDemo.cancel.generic.y = 330; + uiPlayDemo.cancel.generic.name = "Cancel"; + uiPlayDemo.cancel.generic.statusText = "Return back to main menu"; + uiPlayDemo.cancel.generic.callback = UI_PlayDemo_Callback; + + uiPlayDemo.hintMessage.generic.id = ID_TABLEHINT; + uiPlayDemo.hintMessage.generic.type = QMTYPE_ACTION; + uiPlayDemo.hintMessage.generic.flags = QMF_INACTIVE|QMF_SMALLFONT; + uiPlayDemo.hintMessage.generic.color = uiColorHelp; + uiPlayDemo.hintMessage.generic.name = uiPlayDemo.hintText; + uiPlayDemo.hintMessage.generic.x = 360; + uiPlayDemo.hintMessage.generic.y = 225; + + uiPlayDemo.levelShot.generic.id = ID_LEVELSHOT; + uiPlayDemo.levelShot.generic.type = QMTYPE_BITMAP; + uiPlayDemo.levelShot.generic.flags = QMF_INACTIVE; + uiPlayDemo.levelShot.generic.x = LEVELSHOT_X; + uiPlayDemo.levelShot.generic.y = LEVELSHOT_Y; + uiPlayDemo.levelShot.generic.width = LEVELSHOT_W; + uiPlayDemo.levelShot.generic.height = LEVELSHOT_H; + uiPlayDemo.levelShot.generic.ownerdraw = UI_PlayDemo_Ownerdraw; + + uiPlayDemo.demosList.generic.id = ID_DEMOLIST; + uiPlayDemo.demosList.generic.type = QMTYPE_SCROLLLIST; + uiPlayDemo.demosList.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_SMALLFONT; + uiPlayDemo.demosList.generic.x = 360; + uiPlayDemo.demosList.generic.y = 255; + uiPlayDemo.demosList.generic.width = 640; + uiPlayDemo.demosList.generic.height = 440; + uiPlayDemo.demosList.generic.callback = UI_PlayDemo_Callback; + + uiPlayDemo.msgBox.generic.id = ID_MSGBOX; + uiPlayDemo.msgBox.generic.type = QMTYPE_ACTION; + uiPlayDemo.msgBox.generic.flags = QMF_INACTIVE|QMF_HIDDEN; + uiPlayDemo.msgBox.generic.ownerdraw = UI_MsgBox_Ownerdraw; // just a fill rectangle + uiPlayDemo.msgBox.generic.x = 192; + uiPlayDemo.msgBox.generic.y = 256; + uiPlayDemo.msgBox.generic.width = 640; + uiPlayDemo.msgBox.generic.height = 256; + + uiPlayDemo.promptMessage.generic.id = ID_MSGBOX; + uiPlayDemo.promptMessage.generic.type = QMTYPE_ACTION; + uiPlayDemo.promptMessage.generic.flags = QMF_INACTIVE|QMF_DROPSHADOW|QMF_HIDDEN; + uiPlayDemo.promptMessage.generic.name = "Delete selected demo?"; + uiPlayDemo.promptMessage.generic.x = 315; + uiPlayDemo.promptMessage.generic.y = 280; + + uiPlayDemo.yes.generic.id = ID_YES; + uiPlayDemo.yes.generic.type = QMTYPE_ACTION; + uiPlayDemo.yes.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_HIDDEN; + uiPlayDemo.yes.generic.name = "Ok"; + uiPlayDemo.yes.generic.x = 380; + uiPlayDemo.yes.generic.y = 460; + uiPlayDemo.yes.generic.callback = UI_PlayDemo_Callback; + + uiPlayDemo.no.generic.id = ID_NO; + uiPlayDemo.no.generic.type = QMTYPE_ACTION; + uiPlayDemo.no.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_HIDDEN; + uiPlayDemo.no.generic.name = "Cancel"; + uiPlayDemo.no.generic.x = 530; + uiPlayDemo.no.generic.y = 460; + uiPlayDemo.no.generic.callback = UI_PlayDemo_Callback; + + UI_PlayDemo_GetDemoList(); + + UI_AddItem( &uiPlayDemo.menu, (void *)&uiPlayDemo.background ); + UI_AddItem( &uiPlayDemo.menu, (void *)&uiPlayDemo.banner ); + UI_AddItem( &uiPlayDemo.menu, (void *)&uiPlayDemo.play ); + UI_AddItem( &uiPlayDemo.menu, (void *)&uiPlayDemo.remove ); + UI_AddItem( &uiPlayDemo.menu, (void *)&uiPlayDemo.cancel ); + UI_AddItem( &uiPlayDemo.menu, (void *)&uiPlayDemo.hintMessage ); + UI_AddItem( &uiPlayDemo.menu, (void *)&uiPlayDemo.levelShot ); + UI_AddItem( &uiPlayDemo.menu, (void *)&uiPlayDemo.demosList ); + UI_AddItem( &uiPlayDemo.menu, (void *)&uiPlayDemo.msgBox ); + UI_AddItem( &uiPlayDemo.menu, (void *)&uiPlayDemo.promptMessage ); + UI_AddItem( &uiPlayDemo.menu, (void *)&uiPlayDemo.no ); + UI_AddItem( &uiPlayDemo.menu, (void *)&uiPlayDemo.yes ); +} + +/* +================= +UI_PlayDemo_Precache +================= +*/ +void UI_PlayDemo_Precache( void ) +{ + PIC_Load( ART_BACKGROUND ); + PIC_Load( ART_BANNER ); +} + +/* +================= +UI_PlayDemo_Menu +================= +*/ +void UI_PlayDemo_Menu( void ) +{ + UI_PlayDemo_Precache(); + UI_PlayDemo_Init(); + + UI_PushMenu( &uiPlayDemo.menu ); +} \ No newline at end of file diff --git a/mainui/legacy/menu_playrec.cpp b/mainui/legacy/menu_playrec.cpp new file mode 100644 index 0000000..94b154c --- /dev/null +++ b/mainui/legacy/menu_playrec.cpp @@ -0,0 +1,178 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "extdll.h" +#include "basemenu.h" +#include "utils.h" + +#define ART_BANNER "gfx/shell/head_saveload" + +#define ID_BACKGROUND 0 +#define ID_BANNER 1 + +#define ID_PLAY 2 +#define ID_RECORD 3 +#define ID_DONE 4 + +#define ID_MSGHINT 5 + +typedef struct +{ + menuFramework_s menu; + + menuBitmap_s background; + menuBitmap_s banner; + + menuAction_s record; + menuAction_s play; + menuAction_s done; + + menuAction_s hintMessage; + char hintText[MAX_HINT_TEXT]; +} uiPlayRec_t; + +static uiPlayRec_t uiPlayRec; + +/* +================= +UI_PlayRec_Callback +================= +*/ +static void UI_PlayRec_Callback( void *self, int event ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + if( event != QM_ACTIVATED ) + return; + + switch( item->id ) + { + case ID_PLAY: + UI_PlayDemo_Menu(); + break; + case ID_RECORD: + UI_RecDemo_Menu(); + break; + case ID_DONE: + UI_PopMenu(); + break; + } +} + +/* +================= +UI_PlayRec_Init +================= +*/ +static void UI_PlayRec_Init( void ) +{ + memset( &uiPlayRec, 0, sizeof( uiPlayRec_t )); + + uiPlayRec.menu.vidInitFunc = UI_PlayRec_Init; + + strcat( uiPlayRec.hintText, "During play or record demo, you can quickly stop\n" ); + strcat( uiPlayRec.hintText, "playing/recording demo by pressing " ); + strcat( uiPlayRec.hintText, KEY_KeynumToString( KEY_GetKey( "stop" ))); + strcat( uiPlayRec.hintText, ".\n" ); + + uiPlayRec.background.generic.id = ID_BACKGROUND; + uiPlayRec.background.generic.type = QMTYPE_BITMAP; + uiPlayRec.background.generic.flags = QMF_INACTIVE; + uiPlayRec.background.generic.x = 0; + uiPlayRec.background.generic.y = 0; + uiPlayRec.background.generic.width = 1024; + uiPlayRec.background.generic.height = 768; + uiPlayRec.background.pic = ART_BACKGROUND; + + uiPlayRec.banner.generic.id = ID_BANNER; + uiPlayRec.banner.generic.type = QMTYPE_BITMAP; + uiPlayRec.banner.generic.flags = QMF_INACTIVE|QMF_DRAW_ADDITIVE; + uiPlayRec.banner.generic.x = UI_BANNER_POSX; + uiPlayRec.banner.generic.y = UI_BANNER_POSY; + uiPlayRec.banner.generic.width = UI_BANNER_WIDTH; + uiPlayRec.banner.generic.height = UI_BANNER_HEIGHT; + uiPlayRec.banner.pic = ART_BANNER; + + uiPlayRec.play.generic.id = ID_PLAY; + uiPlayRec.play.generic.type = QMTYPE_ACTION; + uiPlayRec.play.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiPlayRec.play.generic.name = "Play demo"; + uiPlayRec.play.generic.statusText = "Play a specified demo"; + uiPlayRec.play.generic.x = 72; + uiPlayRec.play.generic.y = 230; + uiPlayRec.play.generic.callback = UI_PlayRec_Callback; + + uiPlayRec.record.generic.id = ID_RECORD; + uiPlayRec.record.generic.type = QMTYPE_ACTION; + uiPlayRec.record.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiPlayRec.record.generic.name = "Record demo"; + uiPlayRec.record.generic.statusText = "Record demo at this time"; + uiPlayRec.record.generic.x = 72; + uiPlayRec.record.generic.y = 280; + uiPlayRec.record.generic.callback = UI_PlayRec_Callback; + + uiPlayRec.done.generic.id = ID_DONE; + uiPlayRec.done.generic.type = QMTYPE_ACTION; + uiPlayRec.done.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiPlayRec.done.generic.name = "Done"; + uiPlayRec.done.generic.statusText = "Go back to the Main Menu"; + uiPlayRec.done.generic.x = 72; + uiPlayRec.done.generic.y = 330; + uiPlayRec.done.generic.callback = UI_PlayRec_Callback; + + uiPlayRec.hintMessage.generic.id = ID_MSGHINT; + uiPlayRec.hintMessage.generic.type = QMTYPE_ACTION; + uiPlayRec.hintMessage.generic.flags = QMF_INACTIVE|QMF_SMALLFONT; + uiPlayRec.hintMessage.generic.color = uiColorHelp; + uiPlayRec.hintMessage.generic.name = uiPlayRec.hintText; + uiPlayRec.hintMessage.generic.x = 360; + uiPlayRec.hintMessage.generic.y = 480; + + UI_AddItem( &uiPlayRec.menu, (void *)&uiPlayRec.background ); + UI_AddItem( &uiPlayRec.menu, (void *)&uiPlayRec.banner ); + UI_AddItem( &uiPlayRec.menu, (void *)&uiPlayRec.play ); + UI_AddItem( &uiPlayRec.menu, (void *)&uiPlayRec.record ); + UI_AddItem( &uiPlayRec.menu, (void *)&uiPlayRec.done ); + UI_AddItem( &uiPlayRec.menu, (void *)&uiPlayRec.hintMessage ); +} + +/* +================= +UI_PlayRec_Precache +================= +*/ +void UI_PlayRec_Precache( void ) +{ + PIC_Load( ART_BACKGROUND ); + PIC_Load( ART_BANNER ); +} + +/* +================= +UI_PlayRec_Menu +================= +*/ +void UI_PlayRec_Menu( void ) +{ + UI_PlayRec_Precache(); + UI_PlayRec_Init(); + + UI_PushMenu( &uiPlayRec.menu ); +} \ No newline at end of file diff --git a/mainui/legacy/menu_recdemo.cpp b/mainui/legacy/menu_recdemo.cpp new file mode 100644 index 0000000..f0b5c38 --- /dev/null +++ b/mainui/legacy/menu_recdemo.cpp @@ -0,0 +1,487 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "extdll.h" +#include "basemenu.h" +#include "utils.h" +#include "keydefs.h" + +#define ART_BANNER "gfx/shell/head_save" + +#define ID_BACKGROUND 0 +#define ID_BANNER 1 +#define ID_RECORD 2 +#define ID_DELETE 3 +#define ID_CANCEL 4 +#define ID_DEMOLIST 5 +#define ID_TABLEHINT 6 +#define ID_LEVELSHOT 7 +#define ID_MSGBOX 8 +#define ID_MSGTEXT 9 +#define ID_YES 10 +#define ID_NO 11 + +#define LEVELSHOT_X 72 +#define LEVELSHOT_Y 400 +#define LEVELSHOT_W 192 +#define LEVELSHOT_H 160 + +#define TITLE_LENGTH 32 +#define MAPNAME_LENGTH 24+TITLE_LENGTH +#define MAXCLIENTS_LENGTH 16+MAPNAME_LENGTH + +typedef struct +{ + char demoName[UI_MAXGAMES][CS_SIZE]; + char delName[UI_MAXGAMES][CS_SIZE]; + char demoDescription[UI_MAXGAMES][256]; + char *demoDescriptionPtr[UI_MAXGAMES]; + + menuFramework_s menu; + + menuBitmap_s background; + menuBitmap_s banner; + menuAction_s record; + menuAction_s remove; + menuAction_s cancel; + + menuScrollList_s demosList; + + menuBitmap_s levelShot; + menuAction_s hintMessage; + char hintText[MAX_HINT_TEXT]; + + // prompt dialog + menuAction_s msgBox; + menuAction_s promptMessage; + menuAction_s yes; + menuAction_s no; +} uiRecDemo_t; + +static uiRecDemo_t uiRecDemo; + +/* +================= +UI_MsgBox_Ownerdraw +================= +*/ +static void UI_MsgBox_Ownerdraw( void *self ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + UI_FillRect( item->x, item->y, item->width, item->height, uiPromptBgColor ); +} + +static void UI_DeleteDialog( void ) +{ + // toggle main menu between active\inactive + // show\hide remove dialog + uiRecDemo.record.generic.flags ^= QMF_INACTIVE; + uiRecDemo.remove.generic.flags ^= QMF_INACTIVE; + uiRecDemo.cancel.generic.flags ^= QMF_INACTIVE; + uiRecDemo.demosList.generic.flags ^= QMF_INACTIVE; + + uiRecDemo.msgBox.generic.flags ^= QMF_HIDDEN; + uiRecDemo.promptMessage.generic.flags ^= QMF_HIDDEN; + uiRecDemo.no.generic.flags ^= QMF_HIDDEN; + uiRecDemo.yes.generic.flags ^= QMF_HIDDEN; + +} + +/* +================= +UI_RecDemo_KeyFunc +================= +*/ +static const char *UI_RecDemo_KeyFunc( int key, int down ) +{ + if( down && key == K_ESCAPE && uiRecDemo.record.generic.flags & QMF_INACTIVE ) + { + UI_DeleteDialog(); + return uiSoundNull; + } + return UI_DefaultKey( &uiRecDemo.menu, key, down ); +} + +/* +================= +UI_RecDemo_GetDemoList +================= +*/ +static void UI_RecDemo_GetDemoList( void ) +{ + char comment[256]; + char **filenames; + int i = 0, j, numFiles; + + filenames = FS_SEARCH( "demos/*.dem", &numFiles, TRUE ); + + if ( CL_IsActive () && !gpGlobals->demorecording && !gpGlobals->demoplayback ) + { + char maxClients[32]; + sprintf( maxClients, "%i", gpGlobals->maxClients ); + + // create new entry for current save game + strncpy( uiRecDemo.demoName[i], "new", CS_SIZE ); + StringConcat( uiRecDemo.demoDescription[i], gpGlobals->maptitle, TITLE_LENGTH ); + StringConcat( uiRecDemo.demoDescription[i], uiEmptyString, TITLE_LENGTH ); // fill remaining entries + StringConcat( uiRecDemo.demoDescription[i], "New Demo", MAPNAME_LENGTH ); + StringConcat( uiRecDemo.demoDescription[i], uiEmptyString, MAPNAME_LENGTH ); + StringConcat( uiRecDemo.demoDescription[i], maxClients, MAXCLIENTS_LENGTH ); + StringConcat( uiRecDemo.demoDescription[i], uiEmptyString, MAXCLIENTS_LENGTH ); + uiRecDemo.demoDescriptionPtr[i] = uiRecDemo.demoDescription[i]; + i++; + } + + for( j = 0; j < numFiles; i++, j++ ) + { + if( i >= UI_MAXGAMES ) break; + + if( !GET_DEMO_COMMENT( filenames[j], comment )) + { + if( strlen( comment )) + { + // get name string even if not found - C:_GetComment can be mark demos + // as etc + // get name string even if not found - SV_GetComment can be mark saves + // as etc + StringConcat( uiRecDemo.demoDescription[i], uiEmptyString, TITLE_LENGTH ); + StringConcat( uiRecDemo.demoDescription[i], comment, MAPNAME_LENGTH ); + StringConcat( uiRecDemo.demoDescription[i], uiEmptyString, MAXCLIENTS_LENGTH ); + uiRecDemo.demoDescriptionPtr[i] = uiRecDemo.demoDescription[i]; + COM_FileBase( filenames[j], uiRecDemo.demoName[i] ); + COM_FileBase( filenames[j], uiRecDemo.delName[i] ); + } + else uiRecDemo.demoDescriptionPtr[i] = NULL; + continue; + } + + // strip path, leave only filename (empty slots doesn't have demoname) + COM_FileBase( filenames[j], uiRecDemo.demoName[i] ); + COM_FileBase( filenames[j], uiRecDemo.delName[i] ); + + // fill demo desc + StringConcat( uiRecDemo.demoDescription[i], comment + CS_SIZE, TITLE_LENGTH ); + StringConcat( uiRecDemo.demoDescription[i], uiEmptyString, TITLE_LENGTH ); + StringConcat( uiRecDemo.demoDescription[i], comment, MAPNAME_LENGTH ); + StringConcat( uiRecDemo.demoDescription[i], uiEmptyString, MAPNAME_LENGTH ); // fill remaining entries + StringConcat( uiRecDemo.demoDescription[i], comment + CS_SIZE * 2, MAXCLIENTS_LENGTH ); + StringConcat( uiRecDemo.demoDescription[i], uiEmptyString, MAXCLIENTS_LENGTH ); + uiRecDemo.demoDescriptionPtr[i] = uiRecDemo.demoDescription[i]; + } + + for ( ; i < UI_MAXGAMES; i++ ) + uiRecDemo.demoDescriptionPtr[i] = NULL; + uiRecDemo.demosList.itemNames = (const char **)uiRecDemo.demoDescriptionPtr; + + if( strlen( uiRecDemo.demoName[0] ) == 0 || !CL_IsActive () || gpGlobals->demoplayback ) + uiRecDemo.record.generic.flags |= QMF_GRAYED; + else uiRecDemo.record.generic.flags &= ~QMF_GRAYED; + + if( strlen( uiRecDemo.delName[0] ) == 0 || !stricmp( gpGlobals->demoname, uiRecDemo.delName[uiRecDemo.demosList.curItem] )) + uiRecDemo.remove.generic.flags |= QMF_GRAYED; + else uiRecDemo.remove.generic.flags &= ~QMF_GRAYED; +} + +/* +================= +UI_RecDemo_Callback +================= +*/ +static void UI_RecDemo_Callback( void *self, int event ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + if( event == QM_CHANGED ) + { + // never overwrite existing saves, because their names was never get collision + if( strlen( uiRecDemo.demoName[uiRecDemo.demosList.curItem] ) == 0 || !CL_IsActive() || gpGlobals->demoplayback ) + uiRecDemo.record.generic.flags |= QMF_GRAYED; + else uiRecDemo.record.generic.flags &= ~QMF_GRAYED; + + if( strlen( uiRecDemo.delName[uiRecDemo.demosList.curItem] ) == 0 || !stricmp( gpGlobals->demoname, uiRecDemo.delName[uiRecDemo.demosList.curItem] )) + uiRecDemo.remove.generic.flags |= QMF_GRAYED; + else uiRecDemo.remove.generic.flags &= ~QMF_GRAYED; + return; + } + + if( event != QM_ACTIVATED ) + return; + + switch( item->id ) + { + case ID_CANCEL: + UI_PopMenu(); + break; + case ID_RECORD: + if( gpGlobals->demorecording ) + { + CLIENT_COMMAND( FALSE, "stop" ); + uiRecDemo.record.generic.name = "Record"; + uiRecDemo.record.generic.statusText = "Record a new demo"; + uiRecDemo.remove.generic.flags &= ~QMF_GRAYED; + } + else if( strlen( uiRecDemo.demoName[uiRecDemo.demosList.curItem] )) + { + char cmd[128]; + + sprintf( cmd, "demos/%s.bmp", uiRecDemo.demoName[uiRecDemo.demosList.curItem] ); + PIC_Free( cmd ); + + sprintf( cmd, "record \"%s\"\n", uiRecDemo.demoName[uiRecDemo.demosList.curItem] ); + CLIENT_COMMAND( FALSE, cmd ); + UI_CloseMenu(); + } + break; + case ID_NO: + case ID_DELETE: + UI_DeleteDialog(); + break; + case ID_YES: + if( strlen( uiRecDemo.delName[uiRecDemo.demosList.curItem] )) + { + char cmd[128]; + sprintf( cmd, "killdemo \"%s\"\n", uiRecDemo.delName[uiRecDemo.demosList.curItem] ); + + CLIENT_COMMAND( TRUE, cmd ); + + sprintf( cmd, "demos/%s.bmp", uiRecDemo.delName[uiRecDemo.demosList.curItem] ); + PIC_Free( cmd ); + + // restarts the menu + UI_PopMenu(); + UI_RecDemo_Menu(); + return; + } + UI_DeleteDialog(); + break; + } +} + +/* +================= +UI_RecDemo_Ownerdraw +================= +*/ +static void UI_RecDemo_Ownerdraw( void *self ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + if( item->type != QMTYPE_ACTION && item->id == ID_LEVELSHOT ) + { + int x, y, w, h; + + // draw the levelshot + x = LEVELSHOT_X; + y = LEVELSHOT_Y; + w = LEVELSHOT_W; + h = LEVELSHOT_H; + + UI_ScaleCoords( &x, &y, &w, &h ); + + if( strlen( uiRecDemo.demoName[uiRecDemo.demosList.curItem] )) + { + char demoshot[128]; + + sprintf( demoshot, "demos/%s.bmp", uiRecDemo.demoName[uiRecDemo.demosList.curItem] ); + + if( !FILE_EXISTS( demoshot )) + UI_DrawPicAdditive( x, y, w, h, uiColorWhite, "gfx/empty.tga" ); + else UI_DrawPic( x, y, w, h, uiColorWhite, demoshot ); + } + else UI_DrawPicAdditive( x, y, w, h, uiColorWhite, "gfx/empty.tga" ); + + // draw the rectangle + UI_DrawRectangle( item->x, item->y, item->width, item->height, uiInputFgColor ); + } +} + +/* +================= +UI_RecDemo_Init +================= +*/ +static void UI_RecDemo_Init( void ) +{ + memset( &uiRecDemo, 0, sizeof( uiRecDemo_t )); + + uiRecDemo.menu.vidInitFunc = UI_RecDemo_Init; + uiRecDemo.menu.keyFunc = UI_RecDemo_KeyFunc; + + StringConcat( uiRecDemo.hintText, "Title", TITLE_LENGTH ); + StringConcat( uiRecDemo.hintText, uiEmptyString, TITLE_LENGTH ); + StringConcat( uiRecDemo.hintText, "Map", MAPNAME_LENGTH ); + StringConcat( uiRecDemo.hintText, uiEmptyString, MAPNAME_LENGTH ); + StringConcat( uiRecDemo.hintText, "Max Clients", MAXCLIENTS_LENGTH ); + StringConcat( uiRecDemo.hintText, uiEmptyString, MAXCLIENTS_LENGTH ); + + uiRecDemo.background.generic.id = ID_BACKGROUND; + uiRecDemo.background.generic.type = QMTYPE_BITMAP; + uiRecDemo.background.generic.flags = QMF_INACTIVE; + uiRecDemo.background.generic.x = 0; + uiRecDemo.background.generic.y = 0; + uiRecDemo.background.generic.width = 1024; + uiRecDemo.background.generic.height = 768; + uiRecDemo.background.pic = ART_BACKGROUND; + + uiRecDemo.banner.generic.id = ID_BANNER; + uiRecDemo.banner.generic.type = QMTYPE_BITMAP; + uiRecDemo.banner.generic.flags = QMF_INACTIVE|QMF_DRAW_ADDITIVE; + uiRecDemo.banner.generic.x = UI_BANNER_POSX; + uiRecDemo.banner.generic.y = UI_BANNER_POSY; + uiRecDemo.banner.generic.width = UI_BANNER_WIDTH; + uiRecDemo.banner.generic.height = UI_BANNER_HEIGHT; + uiRecDemo.banner.pic = ART_BANNER; + + uiRecDemo.record.generic.id = ID_RECORD; + uiRecDemo.record.generic.type = QMTYPE_ACTION; + uiRecDemo.record.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiRecDemo.record.generic.x = 72; + uiRecDemo.record.generic.y = 230; + + if( gpGlobals->demorecording ) + { + uiRecDemo.record.generic.name = "Stop"; + uiRecDemo.record.generic.statusText = "Stop a demo recording"; + } + else + { + uiRecDemo.record.generic.name = "Record"; + uiRecDemo.record.generic.statusText = "Record a new demo"; + } + uiRecDemo.record.generic.callback = UI_RecDemo_Callback; + + uiRecDemo.remove.generic.id = ID_DELETE; + uiRecDemo.remove.generic.type = QMTYPE_ACTION; + uiRecDemo.remove.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiRecDemo.remove.generic.x = 72; + uiRecDemo.remove.generic.y = 280; + uiRecDemo.remove.generic.name = "Delete"; + uiRecDemo.remove.generic.statusText = "Delete a demo"; + uiRecDemo.remove.generic.callback = UI_RecDemo_Callback; + + uiRecDemo.cancel.generic.id = ID_CANCEL; + uiRecDemo.cancel.generic.type = QMTYPE_ACTION; + uiRecDemo.cancel.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiRecDemo.cancel.generic.x = 72; + uiRecDemo.cancel.generic.y = 330; + uiRecDemo.cancel.generic.name = "Cancel"; + uiRecDemo.cancel.generic.statusText = "Return back to main menu"; + uiRecDemo.cancel.generic.callback = UI_RecDemo_Callback; + + uiRecDemo.hintMessage.generic.id = ID_TABLEHINT; + uiRecDemo.hintMessage.generic.type = QMTYPE_ACTION; + uiRecDemo.hintMessage.generic.flags = QMF_INACTIVE|QMF_SMALLFONT; + uiRecDemo.hintMessage.generic.color = uiColorHelp; + uiRecDemo.hintMessage.generic.name = uiRecDemo.hintText; + uiRecDemo.hintMessage.generic.x = 360; + uiRecDemo.hintMessage.generic.y = 225; + + uiRecDemo.levelShot.generic.id = ID_LEVELSHOT; + uiRecDemo.levelShot.generic.type = QMTYPE_BITMAP; + uiRecDemo.levelShot.generic.flags = QMF_INACTIVE; + uiRecDemo.levelShot.generic.x = LEVELSHOT_X; + uiRecDemo.levelShot.generic.y = LEVELSHOT_Y; + uiRecDemo.levelShot.generic.width = LEVELSHOT_W; + uiRecDemo.levelShot.generic.height = LEVELSHOT_H; + uiRecDemo.levelShot.generic.ownerdraw = UI_RecDemo_Ownerdraw; + + uiRecDemo.demosList.generic.id = ID_DEMOLIST; + uiRecDemo.demosList.generic.type = QMTYPE_SCROLLLIST; + uiRecDemo.demosList.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_SMALLFONT; + uiRecDemo.demosList.generic.x = 360; + uiRecDemo.demosList.generic.y = 255; + uiRecDemo.demosList.generic.width = 640; + uiRecDemo.demosList.generic.height = 440; + uiRecDemo.demosList.generic.callback = UI_RecDemo_Callback; + + uiRecDemo.msgBox.generic.id = ID_MSGBOX; + uiRecDemo.msgBox.generic.type = QMTYPE_ACTION; + uiRecDemo.msgBox.generic.flags = QMF_INACTIVE|QMF_HIDDEN; + uiRecDemo.msgBox.generic.ownerdraw = UI_MsgBox_Ownerdraw; // just a fill rectangle + uiRecDemo.msgBox.generic.x = 192; + uiRecDemo.msgBox.generic.y = 256; + uiRecDemo.msgBox.generic.width = 640; + uiRecDemo.msgBox.generic.height = 256; + + uiRecDemo.promptMessage.generic.id = ID_MSGBOX; + uiRecDemo.promptMessage.generic.type = QMTYPE_ACTION; + uiRecDemo.promptMessage.generic.flags = QMF_INACTIVE|QMF_DROPSHADOW|QMF_HIDDEN; + uiRecDemo.promptMessage.generic.name = "Delete selected demo?"; + uiRecDemo.promptMessage.generic.x = 315; + uiRecDemo.promptMessage.generic.y = 280; + + uiRecDemo.yes.generic.id = ID_YES; + uiRecDemo.yes.generic.type = QMTYPE_ACTION; + uiRecDemo.yes.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_HIDDEN; + uiRecDemo.yes.generic.name = "Ok"; + uiRecDemo.yes.generic.x = 380; + uiRecDemo.yes.generic.y = 460; + uiRecDemo.yes.generic.callback = UI_RecDemo_Callback; + + uiRecDemo.no.generic.id = ID_NO; + uiRecDemo.no.generic.type = QMTYPE_ACTION; + uiRecDemo.no.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_HIDDEN; + uiRecDemo.no.generic.name = "Cancel"; + uiRecDemo.no.generic.x = 530; + uiRecDemo.no.generic.y = 460; + uiRecDemo.no.generic.callback = UI_RecDemo_Callback; + + UI_RecDemo_GetDemoList(); + + UI_AddItem( &uiRecDemo.menu, (void *)&uiRecDemo.background ); + UI_AddItem( &uiRecDemo.menu, (void *)&uiRecDemo.banner ); + UI_AddItem( &uiRecDemo.menu, (void *)&uiRecDemo.record ); + UI_AddItem( &uiRecDemo.menu, (void *)&uiRecDemo.remove ); + UI_AddItem( &uiRecDemo.menu, (void *)&uiRecDemo.cancel ); + UI_AddItem( &uiRecDemo.menu, (void *)&uiRecDemo.hintMessage ); + UI_AddItem( &uiRecDemo.menu, (void *)&uiRecDemo.levelShot ); + UI_AddItem( &uiRecDemo.menu, (void *)&uiRecDemo.demosList ); + UI_AddItem( &uiRecDemo.menu, (void *)&uiRecDemo.msgBox ); + UI_AddItem( &uiRecDemo.menu, (void *)&uiRecDemo.promptMessage ); + UI_AddItem( &uiRecDemo.menu, (void *)&uiRecDemo.no ); + UI_AddItem( &uiRecDemo.menu, (void *)&uiRecDemo.yes ); +} + +/* +================= +UI_RecDemo_Precache +================= +*/ +void UI_RecDemo_Precache( void ) +{ + PIC_Load( ART_BACKGROUND ); + PIC_Load( ART_BANNER ); +} + +/* +================= +UI_RecDemo_Menu +================= +*/ +void UI_RecDemo_Menu( void ) +{ + if( !CheckGameDll( )) return; + + UI_RecDemo_Precache(); + UI_RecDemo_Init(); + + UI_PushMenu( &uiRecDemo.menu ); +} \ No newline at end of file diff --git a/mainui/mainui.def b/mainui/mainui.def new file mode 100644 index 0000000..5adf527 --- /dev/null +++ b/mainui/mainui.def @@ -0,0 +1,5 @@ +LIBRARY menu +EXPORTS + GetMenuAPI @1 +SECTIONS + .data READ WRITE diff --git a/mainui/mainui.dsp b/mainui/mainui.dsp new file mode 100644 index 0000000..93e3f28 --- /dev/null +++ b/mainui/mainui.dsp @@ -0,0 +1,257 @@ +# Microsoft Developer Studio Project File - Name="mainui" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=mainui - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "mainui.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "mainui.mak" CFG="mainui - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mainui - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "mainui - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "mainui - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\temp\mainui\!release" +# PROP Intermediate_Dir "..\temp\mainui\!release" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PLATFORM_EXPORTS" /YX /FD /c +# ADD CPP /nologo /MD /W3 /GX /O2 /I "./" /I "../common" /I "../pm_shared" /I "../engine" /I "../game_shared" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 /opt:nowin98 +# ADD LINK32 msvcrt.lib user32.lib /nologo /dll /pdb:none /machine:I386 /nodefaultlib:"libc.lib" /def:".\mainui.def" /out:"..\temp\mainui\!release/menu.dll" /opt:nowin98 +# SUBTRACT LINK32 /profile /nodefaultlib +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\mainui\!release +InputPath=\Paranoia2\src_main\temp\mainui\!release\menu.dll +SOURCE="$(InputPath)" + +"D:\Paranoia2\base\bin\menu.dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\menu.dll "D:\Paranoia2\base\bin\menu.dll" + +# End Custom Build + +!ELSEIF "$(CFG)" == "mainui - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\temp\mainui\!debug" +# PROP Intermediate_Dir "..\temp\mainui\!debug" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "PLATFORM_EXPORTS" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "./" /I "../common" /I "../pm_shared" /I "../engine" /I "../game_shared" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FR /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /def:".\mainui.def" /pdbtype:sept +# ADD LINK32 msvcrtd.lib user32.lib /nologo /dll /debug /machine:I386 /nodefaultlib:"msvcrt.lib" /def:".\mainui.def" /out:"..\temp\mainui\!debug/menu.dll" /pdbtype:sept +# SUBTRACT LINK32 /incremental:no /nodefaultlib +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\mainui\!debug +InputPath=\Paranoia2\src_main\temp\mainui\!debug\menu.dll +SOURCE="$(InputPath)" + +"D:\Paranoia2\base\bin\menu.dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\menu.dll "D:\Paranoia2\base\bin\menu.dll" + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "mainui - Win32 Release" +# Name "mainui - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\basemenu.cpp +# End Source File +# Begin Source File + +SOURCE=.\menu_advcontrols.cpp +# End Source File +# Begin Source File + +SOURCE=.\menu_audio.cpp +# End Source File +# Begin Source File + +SOURCE=.\menu_btns.cpp +# End Source File +# Begin Source File + +SOURCE=.\menu_configuration.cpp +# End Source File +# Begin Source File + +SOURCE=.\menu_controls.cpp +# End Source File +# Begin Source File + +SOURCE=.\menu_creategame.cpp +# End Source File +# Begin Source File + +SOURCE=.\menu_credits.cpp +# End Source File +# Begin Source File + +SOURCE=.\menu_customgame.cpp +# End Source File +# Begin Source File + +SOURCE=.\menu_gameoptions.cpp +# End Source File +# Begin Source File + +SOURCE=.\menu_internetgames.cpp +# End Source File +# Begin Source File + +SOURCE=.\menu_langame.cpp +# End Source File +# Begin Source File + +SOURCE=.\menu_loadgame.cpp +# End Source File +# Begin Source File + +SOURCE=.\menu_main.cpp +# End Source File +# Begin Source File + +SOURCE=.\menu_multiplayer.cpp +# End Source File +# Begin Source File + +SOURCE=.\menu_newgame.cpp +# End Source File +# Begin Source File + +SOURCE=.\menu_playersetup.cpp +# End Source File +# Begin Source File + +SOURCE=.\menu_savegame.cpp +# End Source File +# Begin Source File + +SOURCE=.\menu_saveload.cpp +# End Source File +# Begin Source File + +SOURCE=.\menu_strings.cpp +# End Source File +# Begin Source File + +SOURCE=.\menu_video.cpp +# End Source File +# Begin Source File + +SOURCE=.\menu_vidmodes.cpp +# End Source File +# Begin Source File + +SOURCE=.\menu_vidoptions.cpp +# End Source File +# Begin Source File + +SOURCE=.\udll_int.cpp +# End Source File +# Begin Source File + +SOURCE=.\ui_title_anim.cpp +# End Source File +# Begin Source File + +SOURCE=.\utils.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\basemenu.h +# End Source File +# Begin Source File + +SOURCE=.\enginecallback.h +# End Source File +# Begin Source File + +SOURCE=.\extdll.h +# End Source File +# Begin Source File + +SOURCE=.\menu_btnsbmp_table.h +# End Source File +# Begin Source File + +SOURCE=.\menu_strings.h +# End Source File +# Begin Source File + +SOURCE=.\menufont.H +# End Source File +# Begin Source File + +SOURCE=.\ui_title_anim.h +# End Source File +# Begin Source File + +SOURCE=.\utils.h +# End Source File +# End Group +# End Target +# End Project diff --git a/mainui/menu_advcontrols.cpp b/mainui/menu_advcontrols.cpp new file mode 100644 index 0000000..dbb54bd --- /dev/null +++ b/mainui/menu_advcontrols.cpp @@ -0,0 +1,336 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "extdll.h" +#include "basemenu.h" +#include "utils.h" +#include "../cl_dll/kbutton.h" +#include "menu_btnsbmp_table.h" +#include "menu_strings.h" + +#define ART_BANNER "gfx/shell/head_advanced" + +#define ID_BACKGROUND 0 +#define ID_BANNER 1 + +#define ID_DONE 2 +#define ID_SENSITIVITY 3 +#define ID_CROSSHAIR 4 +#define ID_INVERTMOUSE 5 +#define ID_MOUSELOOK 6 +#define ID_LOOKSPRING 7 +#define ID_LOOKSTRAFE 8 +#define ID_MOUSEFILTER 9 +#define ID_AUTOAIM 10 + +typedef struct +{ + menuFramework_s menu; + + menuBitmap_s background; + menuBitmap_s banner; + + menuPicButton_s done; + + menuCheckBox_s crosshair; + menuCheckBox_s invertMouse; + menuCheckBox_s mouseLook; + menuCheckBox_s lookSpring; + menuCheckBox_s lookStrafe; + menuCheckBox_s mouseFilter; + menuCheckBox_s autoaim; + menuSlider_s sensitivity; +} uiAdvControls_t; + +static uiAdvControls_t uiAdvControls; + +/* +================= +UI_AdvControls_UpdateConfig +================= +*/ +static void UI_AdvControls_UpdateConfig( void ) +{ + if( uiAdvControls.invertMouse.enabled && CVAR_GET_FLOAT( "m_pitch" ) > 0 ) + CVAR_SET_FLOAT( "m_pitch", -CVAR_GET_FLOAT( "m_pitch" )); + else if( !uiAdvControls.invertMouse.enabled && CVAR_GET_FLOAT( "m_pitch" ) < 0 ) + CVAR_SET_FLOAT( "m_pitch", fabs( CVAR_GET_FLOAT( "m_pitch" ))); + + CVAR_SET_FLOAT( "crosshair", uiAdvControls.crosshair.enabled ); + CVAR_SET_FLOAT( "lookspring", uiAdvControls.lookSpring.enabled ); + CVAR_SET_FLOAT( "lookstrafe", uiAdvControls.lookStrafe.enabled ); + CVAR_SET_FLOAT( "m_filter", uiAdvControls.mouseFilter.enabled ); + CVAR_SET_FLOAT( "sv_aim", uiAdvControls.autoaim.enabled ); + CVAR_SET_FLOAT( "sensitivity", (uiAdvControls.sensitivity.curValue * 20.0f) + 0.1f ); + + if( uiAdvControls.mouseLook.enabled ) + { + uiAdvControls.lookSpring.generic.flags |= QMF_GRAYED; + uiAdvControls.lookStrafe.generic.flags |= QMF_GRAYED; + CLIENT_COMMAND( TRUE, "+mlook" ); + } + else + { + uiAdvControls.lookSpring.generic.flags &= ~QMF_GRAYED; + uiAdvControls.lookStrafe.generic.flags &= ~QMF_GRAYED; + CLIENT_COMMAND( TRUE, "-mlook" ); + } +} + +/* +================= +UI_AdvControls_GetConfig +================= +*/ +static void UI_AdvControls_GetConfig( void ) +{ + kbutton_t *mlook; + + if( CVAR_GET_FLOAT( "m_pitch" ) < 0 ) + uiAdvControls.invertMouse.enabled = true; + + if( CVAR_GET_FLOAT( "crosshair" )) + uiAdvControls.crosshair.enabled = 1; + + mlook = (kbutton_s *)Key_GetState( "in_mlook" ); + if( mlook && mlook->state & 1 ) + uiAdvControls.mouseLook.enabled = 1; + + if( CVAR_GET_FLOAT( "lookspring" )) + uiAdvControls.lookSpring.enabled = 1; + + if( CVAR_GET_FLOAT( "lookstrafe" )) + uiAdvControls.lookStrafe.enabled = 1; + + if( CVAR_GET_FLOAT( "m_filter" )) + uiAdvControls.mouseFilter.enabled = 1; + + if( CVAR_GET_FLOAT( "sv_aim" )) + uiAdvControls.autoaim.enabled = 1; + + uiAdvControls.sensitivity.curValue = (CVAR_GET_FLOAT( "sensitivity" ) - 0.1f) / 20.0f; + + if( uiAdvControls.mouseLook.enabled ) + { + uiAdvControls.lookSpring.generic.flags |= QMF_GRAYED; + uiAdvControls.lookStrafe.generic.flags |= QMF_GRAYED; + } + else + { + uiAdvControls.lookSpring.generic.flags &= ~QMF_GRAYED; + uiAdvControls.lookStrafe.generic.flags &= ~QMF_GRAYED; + } +} + +/* +================= +UI_AdvControls_Callback +================= +*/ +static void UI_AdvControls_Callback( void *self, int event ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + switch( item->id ) + { + case ID_CROSSHAIR: + case ID_INVERTMOUSE: + case ID_MOUSELOOK: + case ID_LOOKSPRING: + case ID_LOOKSTRAFE: + case ID_MOUSEFILTER: + case ID_AUTOAIM: + if( event == QM_PRESSED ) + ((menuCheckBox_s *)self)->focusPic = UI_CHECKBOX_PRESSED; + else ((menuCheckBox_s *)self)->focusPic = UI_CHECKBOX_FOCUS; + break; + } + + if( event == QM_CHANGED ) + { + UI_AdvControls_UpdateConfig(); + return; + } + + if( event != QM_ACTIVATED ) + return; + + switch( item->id ) + { + case ID_DONE: + UI_PopMenu(); + break; + } +} + +/* +================= +UI_AdvControls_Init +================= +*/ +static void UI_AdvControls_Init( void ) +{ + memset( &uiAdvControls, 0, sizeof( uiAdvControls_t )); + + uiAdvControls.menu.vidInitFunc = UI_AdvControls_Init; + + uiAdvControls.background.generic.id = ID_BACKGROUND; + uiAdvControls.background.generic.type = QMTYPE_BITMAP; + uiAdvControls.background.generic.flags = QMF_INACTIVE; + uiAdvControls.background.generic.x = 0; + uiAdvControls.background.generic.y = 0; + uiAdvControls.background.generic.width = 1024; + uiAdvControls.background.generic.height = 768; + uiAdvControls.background.pic = ART_BACKGROUND; + + uiAdvControls.banner.generic.id = ID_BANNER; + uiAdvControls.banner.generic.type = QMTYPE_BITMAP; + uiAdvControls.banner.generic.flags = QMF_INACTIVE|QMF_DRAW_ADDITIVE; + uiAdvControls.banner.generic.x = UI_BANNER_POSX; + uiAdvControls.banner.generic.y = UI_BANNER_POSY; + uiAdvControls.banner.generic.width = UI_BANNER_WIDTH; + uiAdvControls.banner.generic.height = UI_BANNER_HEIGHT; + uiAdvControls.banner.pic = ART_BANNER; + + uiAdvControls.done.generic.id = ID_DONE; + uiAdvControls.done.generic.type = QMTYPE_BM_BUTTON; + uiAdvControls.done.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiAdvControls.done.generic.x = 72; + uiAdvControls.done.generic.y = 680; + uiAdvControls.done.generic.name = "Done"; + uiAdvControls.done.generic.statusText = "Save changes and go back to the Customize Menu"; + uiAdvControls.done.generic.callback = UI_AdvControls_Callback; + + UI_UtilSetupPicButton( &uiAdvControls.done, PC_DONE ); + + uiAdvControls.crosshair.generic.id = ID_CROSSHAIR; + uiAdvControls.crosshair.generic.type = QMTYPE_CHECKBOX; + uiAdvControls.crosshair.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_NOTIFY|QMF_ACT_ONRELEASE|QMF_MOUSEONLY|QMF_DROPSHADOW; + uiAdvControls.crosshair.generic.x = 72; + uiAdvControls.crosshair.generic.y = 230; + uiAdvControls.crosshair.generic.name = "Crosshair"; + uiAdvControls.crosshair.generic.callback = UI_AdvControls_Callback; + uiAdvControls.crosshair.generic.statusText = "Enable the weapon aiming crosshair"; + + uiAdvControls.invertMouse.generic.id = ID_INVERTMOUSE; + uiAdvControls.invertMouse.generic.type = QMTYPE_CHECKBOX; + uiAdvControls.invertMouse.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_NOTIFY|QMF_ACT_ONRELEASE|QMF_MOUSEONLY|QMF_DROPSHADOW; + uiAdvControls.invertMouse.generic.x = 72; + uiAdvControls.invertMouse.generic.y = 280; + uiAdvControls.invertMouse.generic.name = MenuStrings[HINT_REVERSE_MOUSE]; + uiAdvControls.invertMouse.generic.callback = UI_AdvControls_Callback; + uiAdvControls.invertMouse.generic.statusText = "Reverse mouse up/down axis"; + + uiAdvControls.mouseLook.generic.id = ID_MOUSELOOK; + uiAdvControls.mouseLook.generic.type = QMTYPE_CHECKBOX; + uiAdvControls.mouseLook.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_NOTIFY|QMF_ACT_ONRELEASE|QMF_MOUSEONLY|QMF_DROPSHADOW; + uiAdvControls.mouseLook.generic.x = 72; + uiAdvControls.mouseLook.generic.y = 330; + uiAdvControls.mouseLook.generic.name = "Mouse look"; + uiAdvControls.mouseLook.generic.callback = UI_AdvControls_Callback; + uiAdvControls.mouseLook.generic.statusText = "Use the mouse to look around instead of using the mouse to move"; + + uiAdvControls.lookSpring.generic.id = ID_LOOKSPRING; + uiAdvControls.lookSpring.generic.type = QMTYPE_CHECKBOX; + uiAdvControls.lookSpring.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_NOTIFY|QMF_ACT_ONRELEASE|QMF_MOUSEONLY|QMF_DROPSHADOW; + uiAdvControls.lookSpring.generic.x = 72; + uiAdvControls.lookSpring.generic.y = 380; + uiAdvControls.lookSpring.generic.name = "Look spring"; + uiAdvControls.lookSpring.generic.callback = UI_AdvControls_Callback; + uiAdvControls.lookSpring.generic.statusText = "Causes the screen to 'spring' back to looking straight ahead when you\nmove forward"; + + uiAdvControls.lookStrafe.generic.id = ID_LOOKSTRAFE; + uiAdvControls.lookStrafe.generic.type = QMTYPE_CHECKBOX; + uiAdvControls.lookStrafe.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_NOTIFY|QMF_ACT_ONRELEASE|QMF_MOUSEONLY|QMF_DROPSHADOW; + uiAdvControls.lookStrafe.generic.x = 72; + uiAdvControls.lookStrafe.generic.y = 430; + uiAdvControls.lookStrafe.generic.name = "Look strafe"; + uiAdvControls.lookStrafe.generic.callback = UI_AdvControls_Callback; + uiAdvControls.lookStrafe.generic.statusText = "In combination with your mouse look modifier, causes left-right movements\nto strafe instead of turn"; + + uiAdvControls.mouseFilter.generic.id = ID_MOUSEFILTER; + uiAdvControls.mouseFilter.generic.type = QMTYPE_CHECKBOX; + uiAdvControls.mouseFilter.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_NOTIFY|QMF_ACT_ONRELEASE|QMF_MOUSEONLY|QMF_DROPSHADOW; + uiAdvControls.mouseFilter.generic.x = 72; + uiAdvControls.mouseFilter.generic.y = 480; + uiAdvControls.mouseFilter.generic.name = "Mouse filter"; + uiAdvControls.mouseFilter.generic.callback = UI_AdvControls_Callback; + uiAdvControls.mouseFilter.generic.statusText = "Average mouse inputs over the last two frames to smooth out movements"; + + uiAdvControls.autoaim.generic.id = ID_AUTOAIM; + uiAdvControls.autoaim.generic.type = QMTYPE_CHECKBOX; + uiAdvControls.autoaim.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_NOTIFY|QMF_ACT_ONRELEASE|QMF_MOUSEONLY|QMF_DROPSHADOW; + uiAdvControls.autoaim.generic.x = 72; + uiAdvControls.autoaim.generic.y = 530; + uiAdvControls.autoaim.generic.name = "Autoaim"; + uiAdvControls.autoaim.generic.callback = UI_AdvControls_Callback; + uiAdvControls.autoaim.generic.statusText = "Let game to help you aim at enemies"; + + uiAdvControls.sensitivity.generic.id = ID_SENSITIVITY; + uiAdvControls.sensitivity.generic.type = QMTYPE_SLIDER; + uiAdvControls.sensitivity.generic.flags = QMF_PULSEIFFOCUS|QMF_DROPSHADOW; + uiAdvControls.sensitivity.generic.name = MenuStrings[HINT_MOUSE_SENSE]; + uiAdvControls.sensitivity.generic.x = 72; + uiAdvControls.sensitivity.generic.y = 625; + uiAdvControls.sensitivity.generic.callback = UI_AdvControls_Callback; + uiAdvControls.sensitivity.generic.statusText = "Set in-game mouse sensitivity"; + uiAdvControls.sensitivity.minValue = 0.0; + uiAdvControls.sensitivity.maxValue = 1.0; + uiAdvControls.sensitivity.range = 0.05f; + + UI_AdvControls_GetConfig(); + + UI_AddItem( &uiAdvControls.menu, (void *)&uiAdvControls.background ); + UI_AddItem( &uiAdvControls.menu, (void *)&uiAdvControls.banner ); + UI_AddItem( &uiAdvControls.menu, (void *)&uiAdvControls.done ); + UI_AddItem( &uiAdvControls.menu, (void *)&uiAdvControls.crosshair ); + UI_AddItem( &uiAdvControls.menu, (void *)&uiAdvControls.invertMouse ); + UI_AddItem( &uiAdvControls.menu, (void *)&uiAdvControls.mouseLook ); + UI_AddItem( &uiAdvControls.menu, (void *)&uiAdvControls.lookSpring ); + UI_AddItem( &uiAdvControls.menu, (void *)&uiAdvControls.lookStrafe ); + UI_AddItem( &uiAdvControls.menu, (void *)&uiAdvControls.mouseFilter ); + UI_AddItem( &uiAdvControls.menu, (void *)&uiAdvControls.autoaim ); + UI_AddItem( &uiAdvControls.menu, (void *)&uiAdvControls.sensitivity ); +} + +/* +================= +UI_AdvControls_Precache +================= +*/ +void UI_AdvControls_Precache( void ) +{ + PIC_Load( ART_BACKGROUND ); + PIC_Load( ART_BANNER ); +} + +/* +================= +UI_AdvControls_Menu +================= +*/ +void UI_AdvControls_Menu( void ) +{ + UI_AdvControls_Precache(); + UI_AdvControls_Init(); + + UI_AdvControls_UpdateConfig(); + UI_PushMenu( &uiAdvControls.menu ); +} \ No newline at end of file diff --git a/mainui/menu_audio.cpp b/mainui/menu_audio.cpp new file mode 100644 index 0000000..800af09 --- /dev/null +++ b/mainui/menu_audio.cpp @@ -0,0 +1,281 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "extdll.h" +#include "basemenu.h" +#include "utils.h" +#include "menu_btnsbmp_table.h" + +#define ART_BANNER "gfx/shell/head_audio" + +#define ID_BACKGROUND 0 +#define ID_BANNER 1 +#define ID_DONE 2 +#define ID_SUITVOLUME 3 +#define ID_SOUNDVOLUME 4 +#define ID_MUSICVOLUME 5 +#define ID_INTERP 6 +#define ID_NODSP 7 +#define ID_MSGHINT 8 + +typedef struct +{ + float soundVolume; + float musicVolume; + float suitVolume; +} uiAudioValues_t; + +static uiAudioValues_t uiAudioInitial; + +typedef struct +{ + menuFramework_s menu; + + menuBitmap_s background; + menuBitmap_s banner; + + menuPicButton_s done; + + menuSlider_s soundVolume; + menuSlider_s musicVolume; + menuSlider_s suitVolume; + menuCheckBox_s lerping; + menuCheckBox_s noDSP; +} uiAudio_t; + +static uiAudio_t uiAudio; + +/* +================= +UI_Audio_GetConfig +================= +*/ +static void UI_Audio_GetConfig( void ) +{ + uiAudio.soundVolume.curValue = CVAR_GET_FLOAT( "volume" ); + uiAudio.musicVolume.curValue = CVAR_GET_FLOAT( "MP3Volume" ); + uiAudio.suitVolume.curValue = CVAR_GET_FLOAT( "suitvolume" ); + + if( CVAR_GET_FLOAT( "s_lerping" )) + uiAudio.lerping.enabled = 1; + + if( CVAR_GET_FLOAT( "dsp_off" )) + uiAudio.noDSP.enabled = 1; + + // save initial values + uiAudioInitial.soundVolume = uiAudio.soundVolume.curValue; + uiAudioInitial.musicVolume = uiAudio.musicVolume.curValue; + uiAudioInitial.suitVolume = uiAudio.suitVolume.curValue; +} + +/* +================= +UI_Audio_SetConfig +================= +*/ +static void UI_Audio_SetConfig( void ) +{ + CVAR_SET_FLOAT( "volume", uiAudio.soundVolume.curValue ); + CVAR_SET_FLOAT( "MP3Volume", uiAudio.musicVolume.curValue ); + CVAR_SET_FLOAT( "suitvolume", uiAudio.suitVolume.curValue ); + CVAR_SET_FLOAT( "s_lerping", uiAudio.lerping.enabled ); + CVAR_SET_FLOAT( "dsp_off", uiAudio.noDSP.enabled ); +} + +/* +================= +UI_Audio_UpdateConfig +================= +*/ +static void UI_Audio_UpdateConfig( void ) +{ + CVAR_SET_FLOAT( "volume", uiAudio.soundVolume.curValue ); + CVAR_SET_FLOAT( "MP3Volume", uiAudio.musicVolume.curValue ); + CVAR_SET_FLOAT( "suitvolume", uiAudio.suitVolume.curValue ); + CVAR_SET_FLOAT( "s_lerping", uiAudio.lerping.enabled ); + CVAR_SET_FLOAT( "dsp_off", uiAudio.noDSP.enabled ); +} + +/* +================= +UI_Audio_Callback +================= +*/ +static void UI_Audio_Callback( void *self, int event ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + switch( item->id ) + { + case ID_INTERP: + case ID_NODSP: + if( event == QM_PRESSED ) + ((menuCheckBox_s *)self)->focusPic = UI_CHECKBOX_PRESSED; + else ((menuCheckBox_s *)self)->focusPic = UI_CHECKBOX_FOCUS; + break; + } + + if( event == QM_CHANGED ) + { + UI_Audio_UpdateConfig(); + return; + } + + if( event != QM_ACTIVATED ) + return; + + switch( item->id ) + { + case ID_DONE: + UI_PopMenu(); + break; + } +} + +/* +================= +UI_Audio_Init +================= +*/ +static void UI_Audio_Init( void ) +{ + memset( &uiAudio, 0, sizeof( uiAudio_t )); + + uiAudio.menu.vidInitFunc = UI_Audio_Init; + + uiAudio.background.generic.id = ID_BACKGROUND; + uiAudio.background.generic.type = QMTYPE_BITMAP; + uiAudio.background.generic.flags = QMF_INACTIVE; + uiAudio.background.generic.x = 0; + uiAudio.background.generic.y = 0; + uiAudio.background.generic.width = 1024; + uiAudio.background.generic.height = 768; + uiAudio.background.pic = ART_BACKGROUND; + + uiAudio.banner.generic.id = ID_BANNER; + uiAudio.banner.generic.type = QMTYPE_BITMAP; + uiAudio.banner.generic.flags = QMF_INACTIVE|QMF_DRAW_ADDITIVE; + uiAudio.banner.generic.x = UI_BANNER_POSX; + uiAudio.banner.generic.y = UI_BANNER_POSY; + uiAudio.banner.generic.width = UI_BANNER_WIDTH; + uiAudio.banner.generic.height = UI_BANNER_HEIGHT; + uiAudio.banner.pic = ART_BANNER; + + uiAudio.done.generic.id = ID_DONE; + uiAudio.done.generic.type = QMTYPE_BM_BUTTON; + uiAudio.done.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiAudio.done.generic.x = 72; + uiAudio.done.generic.y = 230; + uiAudio.done.generic.name = "Done"; + uiAudio.done.generic.statusText = "Go back to the Configuration Menu"; + uiAudio.done.generic.callback = UI_Audio_Callback; + + UI_UtilSetupPicButton( &uiAudio.done, PC_DONE ); + + uiAudio.soundVolume.generic.id = ID_SOUNDVOLUME; + uiAudio.soundVolume.generic.type = QMTYPE_SLIDER; + uiAudio.soundVolume.generic.flags = QMF_PULSEIFFOCUS|QMF_DROPSHADOW; + uiAudio.soundVolume.generic.name = "Game sound volume"; + uiAudio.soundVolume.generic.x = 320; + uiAudio.soundVolume.generic.y = 280; + uiAudio.soundVolume.generic.callback = UI_Audio_Callback; + uiAudio.soundVolume.generic.statusText = "Set master volume level"; + uiAudio.soundVolume.minValue = 0.0; + uiAudio.soundVolume.maxValue = 1.0; + uiAudio.soundVolume.range = 0.05f; + + uiAudio.musicVolume.generic.id = ID_MUSICVOLUME; + uiAudio.musicVolume.generic.type = QMTYPE_SLIDER; + uiAudio.musicVolume.generic.flags = QMF_PULSEIFFOCUS|QMF_DROPSHADOW; + uiAudio.musicVolume.generic.name = "Game music volume"; + uiAudio.musicVolume.generic.x = 320; + uiAudio.musicVolume.generic.y = 340; + uiAudio.musicVolume.generic.callback = UI_Audio_Callback; + uiAudio.musicVolume.generic.statusText = "Set background music volume level"; + uiAudio.musicVolume.minValue = 0.0; + uiAudio.musicVolume.maxValue = 1.0; + uiAudio.musicVolume.range = 0.05f; + + uiAudio.suitVolume.generic.id = ID_SUITVOLUME; + uiAudio.suitVolume.generic.type = QMTYPE_SLIDER; + uiAudio.suitVolume.generic.flags = QMF_PULSEIFFOCUS|QMF_DROPSHADOW; + uiAudio.suitVolume.generic.name = "Suit volume"; + uiAudio.suitVolume.generic.x = 320; + uiAudio.suitVolume.generic.y = 400; + uiAudio.suitVolume.generic.callback = UI_Audio_Callback; + uiAudio.suitVolume.generic.statusText = "Singleplayer suit volume"; + uiAudio.suitVolume.minValue = 0.0; + uiAudio.suitVolume.maxValue = 1.0; + uiAudio.suitVolume.range = 0.05f; + + uiAudio.lerping.generic.id = ID_INTERP; + uiAudio.lerping.generic.type = QMTYPE_CHECKBOX; + uiAudio.lerping.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_ACT_ONRELEASE|QMF_MOUSEONLY|QMF_DROPSHADOW; + uiAudio.lerping.generic.name = "Enable sound interpolation"; + uiAudio.lerping.generic.x = 320; + uiAudio.lerping.generic.y = 470; + uiAudio.lerping.generic.callback = UI_Audio_Callback; + uiAudio.lerping.generic.statusText = "enable/disable interpolation on sound output"; + + uiAudio.noDSP.generic.id = ID_NODSP; + uiAudio.noDSP.generic.type = QMTYPE_CHECKBOX; + uiAudio.noDSP.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_ACT_ONRELEASE|QMF_MOUSEONLY|QMF_DROPSHADOW; + uiAudio.noDSP.generic.name = "Disable DSP effects"; + uiAudio.noDSP.generic.x = 320; + uiAudio.noDSP.generic.y = 520; + uiAudio.noDSP.generic.callback = UI_Audio_Callback; + uiAudio.noDSP.generic.statusText = "this disables sound processing (like echo, flanger etc)"; + + UI_Audio_GetConfig(); + + UI_AddItem( &uiAudio.menu, (void *)&uiAudio.background ); + UI_AddItem( &uiAudio.menu, (void *)&uiAudio.banner ); + UI_AddItem( &uiAudio.menu, (void *)&uiAudio.done ); + UI_AddItem( &uiAudio.menu, (void *)&uiAudio.soundVolume ); + UI_AddItem( &uiAudio.menu, (void *)&uiAudio.musicVolume ); + UI_AddItem( &uiAudio.menu, (void *)&uiAudio.suitVolume ); + UI_AddItem( &uiAudio.menu, (void *)&uiAudio.lerping ); + UI_AddItem( &uiAudio.menu, (void *)&uiAudio.noDSP ); +} + +/* +================= +UI_Audio_Precache +================= +*/ +void UI_Audio_Precache( void ) +{ + PIC_Load( ART_BACKGROUND ); + PIC_Load( ART_BANNER ); +} + +/* +================= +UI_Audio_Menu +================= +*/ +void UI_Audio_Menu( void ) +{ + UI_Audio_Precache(); + UI_Audio_Init(); + + UI_Audio_UpdateConfig(); + UI_PushMenu( &uiAudio.menu ); +} \ No newline at end of file diff --git a/mainui/menu_btns.cpp b/mainui/menu_btns.cpp new file mode 100644 index 0000000..c867871 --- /dev/null +++ b/mainui/menu_btns.cpp @@ -0,0 +1,181 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "extdll.h" +#include "basemenu.h" +#include "utils.h" +#include "menu_btnsbmp_table.h" + +#define ART_BUTTONS_MAIN "gfx/shell/btns_main.bmp" // we support bmp only + +const char *MenuButtons[PC_BUTTONCOUNT] = +{ + "New game", + "Resume Game", + "Hazard Course", + "Configuration", + "Load game", + "Save/load game", + "View readme", + "Quit", + "Multiplayer", + "Easy", + "Medium", + "Difficult", + "Save game", + "Load game", + "Cancel", + "Game options", + "Video", + "Audio", + "Controls", + "Done", + "Quickstart", + "Use defaults", + "Ok", + "Video options", + "Video modes", + "Adv controls", + "Order Half-life", + "Delete", + "Internet games", + "Chat rooms", + "Lan games", + "Customize", + "Skip", + "Exit", + "Connect", + "Refresh", + "Filter", + "Filter", + "Create", + "Create game", + "Chat rooms", + "List rooms", + "Search", + "Servers", + "Join", + "Find", + "Create room", + "Join game", + "Search games", + "Find game", + "Start game", + "View game info", + "Update", + "Add server", + "Disconnect", + "Console", + "Content control", + "Update", + "Visit won", + "Previews", + "Adv options", + "3D info site", + "Custom Game", + "Activate", + "Install", + "Visit web site", + "Refresh list", + "Deactivate", + "Adv options", + "Spectate game", + "Spectate games" +}; + +/* +================= +UI_LoadBmpButtons +================= +*/ +void UI_LoadBmpButtons( void ) +{ + memset( uiStatic.buttonsPics, 0, sizeof( uiStatic.buttonsPics )); + + int bmp_filesize; + byte *bmp_buffer = LOAD_FILE( ART_BUTTONS_MAIN, &bmp_filesize ); + + if( !bmp_buffer || !bmp_filesize ) + { + Con_Printf( "UI_LoadBmpButtons: btns_main.bmp not found\n" ); + return; + } + + BITMAPFILEHEADER *pFileHdr = (BITMAPFILEHEADER *)bmp_buffer; + BITMAPINFOHEADER *pInfoHdr = (BITMAPINFOHEADER *)&bmp_buffer[sizeof( BITMAPFILEHEADER )]; + + BITMAPINFOHEADER NewInfoHdr; + BITMAPFILEHEADER NewFileHdr; + + if( pInfoHdr->biBitCount == 8 && pInfoHdr->biClrUsed == 0 ) + pInfoHdr->biClrUsed = 256; // all colors used + + memcpy( &NewFileHdr, pFileHdr, sizeof( BITMAPFILEHEADER )); + memcpy( &NewInfoHdr, pInfoHdr, sizeof( BITMAPINFOHEADER )); + + byte *palette = bmp_buffer + sizeof( BITMAPFILEHEADER ) + sizeof( BITMAPINFOHEADER ); + int palette_sz = pInfoHdr->biClrUsed * sizeof( RGBQUAD ); + uiStatic.buttons_width = pInfoHdr->biWidth; + uiStatic.buttons_height = 78; // fixed height + + // determine buttons count by image height... + int pic_count = ( pInfoHdr->biHeight / uiStatic.buttons_height ); + + int stride = (pInfoHdr->biWidth * pInfoHdr->biBitCount / 8); + int cutted_img_sz = ((stride + 3 ) & ~3) * uiStatic.buttons_height; + int CuttedBmpSize = sizeof( BITMAPFILEHEADER ) + sizeof( BITMAPINFOHEADER ) + palette_sz + cutted_img_sz; + byte *img_data = &bmp_buffer[pFileHdr->bfOffBits + cutted_img_sz * ( pic_count - 1 )]; + + NewFileHdr.bfSize = CuttedBmpSize; + NewFileHdr.bfOffBits = sizeof( BITMAPFILEHEADER ) + sizeof( BITMAPINFOHEADER ) + palette_sz; + NewInfoHdr.biHeight = uiStatic.buttons_height; + NewInfoHdr.biSizeImage = cutted_img_sz; + + char fname[256]; + byte *raw_img_buff = (byte *)MALLOC( CuttedBmpSize ); + + for( int i = 0; i < pic_count; i++ ) + { + sprintf( fname, "#btns_%d.bmp", i ); + + int offset = 0; + memcpy( &raw_img_buff[offset], &NewFileHdr, sizeof( BITMAPFILEHEADER )); + offset += sizeof( BITMAPFILEHEADER ); + + memcpy( &raw_img_buff[offset], &NewInfoHdr, NewInfoHdr.biSize ); + offset += NewInfoHdr.biSize; + + if( NewInfoHdr.biBitCount <= 8 ) + { + memcpy( &raw_img_buff[offset], palette, palette_sz ); + offset += palette_sz; + } + + memcpy( &raw_img_buff[offset], img_data, cutted_img_sz ); + + // upload image into video memory + uiStatic.buttonsPics[i] = PIC_Load( fname, raw_img_buff, CuttedBmpSize ); + + img_data -= cutted_img_sz; + } + + FREE( raw_img_buff ); + FREE_FILE( bmp_buffer ); +} \ No newline at end of file diff --git a/mainui/menu_btnsbmp_table.h b/mainui/menu_btnsbmp_table.h new file mode 100644 index 0000000..5b05092 --- /dev/null +++ b/mainui/menu_btnsbmp_table.h @@ -0,0 +1,109 @@ +/* +menu_btnsbmp_table.h - btns_main layout +Copyright (C) 2011 CrazyRussian + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef MENU_BTNSBMP_TABLE_H +#define MENU_BTNSBMP_TABLE_H + +enum +{ + PC_NEW_GAME = 0, + PC_RESUME_GAME, + PC_HAZARD_COURSE, + PC_CONFIG, + PC_LOAD_GAME, + PC_SAVE_LOAD_GAME, + PC_VIEW_README, + PC_QUIT, + PC_MULTIPLAYER, + PC_EASY, + PC_MEDIUM, + PC_DIFFICULT, + PC_SAVE_GAME, + PC_LOAD_GAME2, + PC_CANCEL, + PC_GAME_OPTIONS, + PC_VIDEO, + PC_AUDIO, + PC_CONTROLS, + PC_DONE, + PC_QUICKSTART, + PC_USE_DEFAULTS, + PC_OK, + PC_VID_OPT, + PC_VID_MODES, + PC_ADV_CONTROLS, + PC_ORDER_HL, + PC_DELETE, + PC_INET_GAME, + PC_CHAT_ROOMS, + PC_LAN_GAME, + PC_CUSTOMIZE, + PC_SKIP, + PC_EXIT, + PC_CONNECT, + PC_REFRESH, + PC_FILTER, + PC_FILTER2, + PC_CREATE, + PC_CREATE_GAME, + PC_CHAT_ROOMS2, + PC_LIST_ROOMS, + PC_SEARCH, + PC_SERVERS, + PC_JOIN, + PC_FIND, + PC_CREATE_ROOM, + PC_JOIN_GAME, + PC_SEARCH_GAMES, + PC_FIND_GAME, + PC_START_GAME, + PC_VIEW_GAME_INFO, + PC_UPDATE, + PC_ADD_SERVER, + PC_DISCONNECT, + PC_CONSOLE, + PC_CONTENT_CONTROL, + PC_UPDATE2, + PC_VISIT_WON, + PC_PREVIEWS, + PC_ADV_OPT, + PC_3DINFO_SITE, + PC_CUSTOM_GAME, + PC_ACTIVATE, + PC_INSTALL, + PC_VISIT_WEB_SITE, + PC_REFRESH_LIST, + PC_DEACTIVATE, + PC_ADV_OPT2, + PC_SPECTATE_GAME, + PC_SPECTATE_GAMES, + PC_BUTTONCOUNT // must be last +}; + +#define BUTTON_NOFOCUS 0 +#define BUTTON_FOCUS 1 +#define BUTTON_PRESSED 2 + +extern const char *MenuButtons[PC_BUTTONCOUNT]; + +inline int PicButtonWidth( int pic_id ) +{ + if( pic_id < 0 || pic_id > PC_BUTTONCOUNT ) + return 0; + + return strlen( MenuButtons[pic_id] ); +} + +#endif//MENU_BTNSBMP_TABLE_H \ No newline at end of file diff --git a/mainui/menu_configuration.cpp b/mainui/menu_configuration.cpp new file mode 100644 index 0000000..3f3efe4 --- /dev/null +++ b/mainui/menu_configuration.cpp @@ -0,0 +1,304 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "extdll.h" +#include "basemenu.h" +#include "utils.h" +#include "keydefs.h" +#include "menu_btnsbmp_table.h" + +#define ART_BANNER "gfx/shell/head_config" + +#define ID_BACKGROUND 0 +#define ID_BANNER 1 + +#define ID_CONTROLS 2 +#define ID_AUDIO 3 +#define ID_VIDEO 4 +#define ID_UPDATE 5 +#define ID_DONE 6 +#define ID_MSGBOX 7 +#define ID_MSGTEXT 8 +#define ID_YES 130 +#define ID_NO 131 + +typedef struct +{ + menuFramework_s menu; + + menuBitmap_s background; + menuBitmap_s banner; + + menuPicButton_s controls; + menuPicButton_s audio; + menuPicButton_s video; + menuPicButton_s update; + menuPicButton_s done; + + // update dialog + menuAction_s msgBox; + menuAction_s updatePrompt; + menuPicButton_s yes; + menuPicButton_s no; +} uiOptions_t; + +static uiOptions_t uiOptions; + +/* +================= +UI_MsgBox_Ownerdraw +================= +*/ +static void UI_MsgBox_Ownerdraw( void *self ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + UI_FillRect( item->x, item->y, item->width, item->height, uiPromptBgColor ); +} + +static void UI_CheckUpdatesDialog( void ) +{ + // toggle configuration menu between active\inactive + // show\hide CheckUpdates dialog + uiOptions.controls.generic.flags ^= QMF_INACTIVE; + uiOptions.audio.generic.flags ^= QMF_INACTIVE; + uiOptions.video.generic.flags ^= QMF_INACTIVE; + uiOptions.update.generic.flags ^= QMF_INACTIVE; + uiOptions.done.generic.flags ^= QMF_INACTIVE; + + uiOptions.msgBox.generic.flags ^= QMF_HIDDEN; + uiOptions.updatePrompt.generic.flags ^= QMF_HIDDEN; + uiOptions.no.generic.flags ^= QMF_HIDDEN; + uiOptions.yes.generic.flags ^= QMF_HIDDEN; + +} + +/* +================= +UI_Options_KeyFunc +================= +*/ +static const char *UI_Options_KeyFunc( int key, int down ) +{ + if( down && key == K_ESCAPE && uiOptions.done.generic.flags & QMF_INACTIVE ) + { + UI_CheckUpdatesDialog (); // cancel 'check updates' dialog + return uiSoundNull; + } + return UI_DefaultKey( &uiOptions.menu, key, down ); +} + +/* +================= +UI_Options_Callback +================= +*/ +static void UI_Options_Callback( void *self, int event ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + if( event != QM_ACTIVATED ) + return; + + switch( item->id ) + { + case ID_DONE: + UI_PopMenu(); + break; + case ID_CONTROLS: + UI_Controls_Menu(); + break; + case ID_AUDIO: + UI_Audio_Menu(); + break; + case ID_VIDEO: + UI_Video_Menu(); + break; + case ID_UPDATE: + UI_CheckUpdatesDialog(); + break; + case ID_YES: + SHELL_EXECUTE( gMenu.m_gameinfo.update_url, NULL, TRUE ); + break; + case ID_NO: + UI_CheckUpdatesDialog(); + break; + } +} + +/* +================= +UI_Options_Init +================= +*/ +static void UI_Options_Init( void ) +{ + memset( &uiOptions, 0, sizeof( uiOptions_t )); + + uiOptions.menu.vidInitFunc = UI_Options_Init; + uiOptions.menu.keyFunc = UI_Options_KeyFunc; + + uiOptions.background.generic.id = ID_BACKGROUND; + uiOptions.background.generic.type = QMTYPE_BITMAP; + uiOptions.background.generic.flags = QMF_INACTIVE; + uiOptions.background.generic.x = 0; + uiOptions.background.generic.y = 0; + uiOptions.background.generic.width = 1024; + uiOptions.background.generic.height = 768; + uiOptions.background.pic = ART_BACKGROUND; + + uiOptions.banner.generic.id = ID_BANNER; + uiOptions.banner.generic.type = QMTYPE_BITMAP; + uiOptions.banner.generic.flags = QMF_INACTIVE|QMF_DRAW_ADDITIVE; + uiOptions.banner.generic.x = UI_BANNER_POSX; + uiOptions.banner.generic.y = UI_BANNER_POSY; + uiOptions.banner.generic.width = UI_BANNER_WIDTH; + uiOptions.banner.generic.height = UI_BANNER_HEIGHT; + uiOptions.banner.pic = ART_BANNER; + + uiOptions.controls.generic.id = ID_CONTROLS; + uiOptions.controls.generic.type = QMTYPE_BM_BUTTON; + uiOptions.controls.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiOptions.controls.generic.x = 72; + uiOptions.controls.generic.y = 230; + uiOptions.controls.generic.name = "Controls"; + uiOptions.controls.generic.statusText = "Change keyboard and mouse settings"; + uiOptions.controls.generic.callback = UI_Options_Callback; + + UI_UtilSetupPicButton( &uiOptions.controls, PC_CONTROLS ); + + uiOptions.audio.generic.id = ID_AUDIO; + uiOptions.audio.generic.type = QMTYPE_BM_BUTTON; + uiOptions.audio.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiOptions.audio.generic.x = 72; + uiOptions.audio.generic.y = 280; + uiOptions.audio.generic.name = "Audio"; + uiOptions.audio.generic.statusText = "Change sound volume and quality"; + uiOptions.audio.generic.callback = UI_Options_Callback; + + UI_UtilSetupPicButton( &uiOptions.audio, PC_AUDIO ); + + uiOptions.video.generic.id = ID_VIDEO; + uiOptions.video.generic.type = QMTYPE_BM_BUTTON; + uiOptions.video.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiOptions.video.generic.x = 72; + uiOptions.video.generic.y = 330; + uiOptions.video.generic.name = "Video"; + uiOptions.video.generic.statusText = "Change screen size, video mode and gamma"; + uiOptions.video.generic.callback = UI_Options_Callback; + + UI_UtilSetupPicButton( &uiOptions.video, PC_VIDEO ); + + uiOptions.update.generic.id = ID_UPDATE; + uiOptions.update.generic.type = QMTYPE_BM_BUTTON; + uiOptions.update.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiOptions.update.generic.x = 72; + uiOptions.update.generic.y = 380; + uiOptions.update.generic.name = "Update"; + uiOptions.update.generic.statusText = "Donwload the latest version of the Xash3D engine"; + uiOptions.update.generic.callback = UI_Options_Callback; + UI_UtilSetupPicButton(&uiOptions.update,PC_UPDATE); + + if( !strlen( gMenu.m_gameinfo.update_url )) + uiOptions.update.generic.flags |= QMF_GRAYED; + + uiOptions.done.generic.id = ID_DONE; + uiOptions.done.generic.type = QMTYPE_BM_BUTTON; + uiOptions.done.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiOptions.done.generic.x = 72; + uiOptions.done.generic.y = 430; + uiOptions.done.generic.name = "Done"; + uiOptions.done.generic.statusText = "Go back to the Main Menu"; + uiOptions.done.generic.callback = UI_Options_Callback; + + UI_UtilSetupPicButton( &uiOptions.done, PC_DONE ); + + uiOptions.msgBox.generic.id = ID_MSGBOX; + uiOptions.msgBox.generic.type = QMTYPE_ACTION; + uiOptions.msgBox.generic.flags = QMF_INACTIVE|QMF_HIDDEN; + uiOptions.msgBox.generic.ownerdraw = UI_MsgBox_Ownerdraw; // just a fill rectangle + uiOptions.msgBox.generic.x = 192; + uiOptions.msgBox.generic.y = 256; + uiOptions.msgBox.generic.width = 640; + uiOptions.msgBox.generic.height = 256; + + uiOptions.updatePrompt.generic.id = ID_MSGBOX; + uiOptions.updatePrompt.generic.type = QMTYPE_ACTION; + uiOptions.updatePrompt.generic.flags = QMF_INACTIVE|QMF_DROPSHADOW|QMF_HIDDEN; + uiOptions.updatePrompt.generic.name = "Check the Internet for updates?"; + uiOptions.updatePrompt.generic.x = 248; + uiOptions.updatePrompt.generic.y = 280; + + uiOptions.yes.generic.id = ID_YES; + uiOptions.yes.generic.type = QMTYPE_BM_BUTTON; + uiOptions.yes.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_HIDDEN; + uiOptions.yes.generic.name = "Ok"; + uiOptions.yes.generic.x = 380; + uiOptions.yes.generic.y = 460; + uiOptions.yes.generic.callback = UI_Options_Callback; + + UI_UtilSetupPicButton( &uiOptions.yes, PC_OK ); + + uiOptions.no.generic.id = ID_NO; + uiOptions.no.generic.type = QMTYPE_BM_BUTTON; + uiOptions.no.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_HIDDEN; + uiOptions.no.generic.name = "Cancel"; + uiOptions.no.generic.x = 530; + uiOptions.no.generic.y = 460; + uiOptions.no.generic.callback = UI_Options_Callback; + + UI_UtilSetupPicButton( &uiOptions.no, PC_CANCEL ); + + UI_AddItem( &uiOptions.menu, (void *)&uiOptions.background ); + UI_AddItem( &uiOptions.menu, (void *)&uiOptions.banner ); + UI_AddItem( &uiOptions.menu, (void *)&uiOptions.done ); + UI_AddItem( &uiOptions.menu, (void *)&uiOptions.controls ); + UI_AddItem( &uiOptions.menu, (void *)&uiOptions.audio ); + UI_AddItem( &uiOptions.menu, (void *)&uiOptions.video ); + UI_AddItem( &uiOptions.menu, (void *)&uiOptions.update ); + UI_AddItem( &uiOptions.menu, (void *)&uiOptions.msgBox ); + UI_AddItem( &uiOptions.menu, (void *)&uiOptions.updatePrompt ); + UI_AddItem( &uiOptions.menu, (void *)&uiOptions.no ); + UI_AddItem( &uiOptions.menu, (void *)&uiOptions.yes ); +} + +/* +================= +UI_Options_Precache +================= +*/ +void UI_Options_Precache( void ) +{ + PIC_Load( ART_BACKGROUND ); + PIC_Load( ART_BANNER ); +} + +/* +================= +UI_Options_Menu +================= +*/ +void UI_Options_Menu( void ) +{ + UI_Options_Precache(); + UI_Options_Init(); + + UI_PushMenu( &uiOptions.menu ); +} \ No newline at end of file diff --git a/mainui/menu_controls.cpp b/mainui/menu_controls.cpp new file mode 100644 index 0000000..54f0efd --- /dev/null +++ b/mainui/menu_controls.cpp @@ -0,0 +1,623 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "extdll.h" +#include "basemenu.h" +#include "utils.h" +#include "keydefs.h" +#include "menu_btnsbmp_table.h" + +#define ART_BANNER "gfx/shell/head_controls" + +#define ID_BACKGROUND 0 +#define ID_BANNER 1 +#define ID_DEFAULTS 2 +#define ID_ADVANCED 3 +#define ID_DONE 4 +#define ID_CANCEL 5 +#define ID_KEYLIST 6 +#define ID_TABLEHINT 7 +#define ID_MSGBOX1 8 +#define ID_MSGBOX2 9 +#define ID_MSGTEXT 10 +#define ID_PROMPT 11 +#define ID_YES 130 +#define ID_NO 131 + +#define MAX_KEYS 256 +#define CMD_LENGTH 38 +#define KEY1_LENGTH 20+CMD_LENGTH +#define KEY2_LENGTH 20+KEY1_LENGTH + +typedef struct +{ + char keysBind[MAX_KEYS][CMD_LENGTH]; + char firstKey[MAX_KEYS][20]; + char secondKey[MAX_KEYS][20]; + char keysDescription[MAX_KEYS][256]; + char *keysDescriptionPtr[MAX_KEYS]; + + menuFramework_s menu; + + menuBitmap_s background; + menuBitmap_s banner; + menuPicButton_s defaults; + menuPicButton_s advanced; + menuPicButton_s done; + menuPicButton_s cancel; + + // redefine key wait dialog + menuAction_s msgBox1; // small msgbox + menuAction_s msgBox2; // large msgbox + menuAction_s dlgMessage; + menuAction_s promptMessage; + menuPicButton_s yes; + menuPicButton_s no; + + menuScrollList_s keysList; + menuAction_s hintMessage; + char hintText[MAX_HINT_TEXT]; + + int bind_grab; // waiting for key input +} uiControls_t; + +static uiControls_t uiControls; +extern bool hold_button_stack; + +static void UI_ResetToDefaultsDialog( void ) +{ + // toggle main menu between active\inactive + // show\hide reset to defaults dialog + uiControls.defaults.generic.flags ^= QMF_INACTIVE; + uiControls.advanced.generic.flags ^= QMF_INACTIVE; + uiControls.done.generic.flags ^= QMF_INACTIVE; + uiControls.cancel.generic.flags ^= QMF_INACTIVE; + + uiControls.keysList.generic.flags ^= QMF_INACTIVE; + + uiControls.msgBox2.generic.flags ^= QMF_HIDDEN; + uiControls.promptMessage.generic.flags ^= QMF_HIDDEN; + uiControls.yes.generic.flags ^= QMF_HIDDEN; + uiControls.no.generic.flags ^= QMF_HIDDEN; +} + +/* +================= +UI_Controls_GetKeyBindings +================= +*/ +static void UI_Controls_GetKeyBindings( const char *command, int *twoKeys ) +{ + int i, count = 0; + const char *b; + + twoKeys[0] = twoKeys[1] = -1; + + for( i = 0; i < 256; i++ ) + { + b = KEY_GetBinding( i ); + if( !b ) continue; + + if( !stricmp( command, b )) + { + twoKeys[count] = i; + count++; + + if( count == 2 ) break; + } + } + + // swap keys if needed + if( twoKeys[0] != -1 && twoKeys[1] != -1 ) + { + int tempKey = twoKeys[1]; + twoKeys[1] = twoKeys[0]; + twoKeys[0] = tempKey; + } +} + +void UI_UnbindCommand( const char *command ) +{ + int i, l; + const char *b; + + l = strlen( command ); + + for( i = 0; i < 256; i++ ) + { + b = KEY_GetBinding( i ); + if( !b ) continue; + + if( !strncmp( b, command, l )) + KEY_SetBinding( i, "" ); + } +} + +static void UI_Controls_ParseKeysList( void ) +{ + char *afile = (char *)LOAD_FILE( "gfx/shell/kb_act.lst", NULL ); + char *pfile = afile; + char token[1024]; + int i = 0; + + if( !afile ) + { + for( ; i < MAX_KEYS; i++ ) uiControls.keysDescriptionPtr[i] = NULL; + uiControls.keysList.itemNames = (const char **)uiControls.keysDescriptionPtr; + + Con_Printf( "UI_Parse_KeysList: kb_act.lst not found\n" ); + return; + } + + while(( pfile = COM_ParseFile( pfile, token )) != NULL ) + { + char str[128]; + + if( !stricmp( token, "blank" )) + { + // seperator + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; // technically an error + + sprintf( str, "^6%s^7", token ); // enable uiPromptTextColor + StringConcat( uiControls.keysDescription[i], str, strlen( str ) + 1 ); + StringConcat( uiControls.keysDescription[i], uiEmptyString, 256 ); // empty + uiControls.keysDescriptionPtr[i] = uiControls.keysDescription[i]; + strcpy( uiControls.keysBind[i], "" ); + strcpy( uiControls.firstKey[i], "" ); + strcpy( uiControls.secondKey[i], "" ); + i++; + } + else + { + // key definition + int keys[2]; + + UI_Controls_GetKeyBindings( token, keys ); + strncpy( uiControls.keysBind[i], token, sizeof( uiControls.keysBind[i] )); + + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; // technically an error + + sprintf( str, "^6%s^7", token ); // enable uiPromptTextColor + + if( keys[0] == -1 ) strcpy( uiControls.firstKey[i], "" ); + else strncpy( uiControls.firstKey[i], KEY_KeynumToString( keys[0] ), sizeof( uiControls.firstKey[i] )); + + if( keys[1] == -1 ) strcpy( uiControls.secondKey[i], "" ); + else strncpy( uiControls.secondKey[i], KEY_KeynumToString( keys[1] ), sizeof( uiControls.secondKey[i] )); + + StringConcat( uiControls.keysDescription[i], str, CMD_LENGTH ); + StringConcat( uiControls.keysDescription[i], uiEmptyString, CMD_LENGTH ); + + // HACKHACK this color should be get from kb_keys.lst + if( !strnicmp( uiControls.firstKey[i], "MOUSE", 5 )) + sprintf( str, "^5%s^7", uiControls.firstKey[i] ); // cyan + else sprintf( str, "^3%s^7", uiControls.firstKey[i] ); // yellow + StringConcat( uiControls.keysDescription[i], str, KEY1_LENGTH ); + StringConcat( uiControls.keysDescription[i], uiEmptyString, KEY1_LENGTH ); + + // HACKHACK this color should be get from kb_keys.lst + if( !strnicmp( uiControls.secondKey[i], "MOUSE", 5 )) + sprintf( str, "^5%s^7", uiControls.secondKey[i] );// cyan + else sprintf( str, "^3%s^7", uiControls.secondKey[i] ); // yellow + + StringConcat( uiControls.keysDescription[i], str, KEY2_LENGTH ); + StringConcat( uiControls.keysDescription[i], uiEmptyString, KEY2_LENGTH ); + uiControls.keysDescriptionPtr[i] = uiControls.keysDescription[i]; + i++; + } + } + + FREE_FILE( afile ); + + for( ; i < MAX_KEYS; i++ ) uiControls.keysDescriptionPtr[i] = NULL; + uiControls.keysList.itemNames = (const char **)uiControls.keysDescriptionPtr; +} + +static void UI_PromptDialog( void ) +{ + // toggle main menu between active\inactive + // show\hide quit dialog + uiControls.defaults.generic.flags ^= QMF_INACTIVE; + uiControls.advanced.generic.flags ^= QMF_INACTIVE; + uiControls.done.generic.flags ^= QMF_INACTIVE; + uiControls.cancel.generic.flags ^= QMF_INACTIVE; + + uiControls.keysList.generic.flags ^= QMF_INACTIVE; + + uiControls.msgBox1.generic.flags ^= QMF_HIDDEN; + uiControls.dlgMessage.generic.flags ^= QMF_HIDDEN; +} + +static void UI_Controls_RestartMenu( void ) +{ + int lastSelectedKey = uiControls.keysList.curItem; + int lastTopItem = uiControls.keysList.topItem; + + // HACK to prevent mismatch anim stack + hold_button_stack = true; + + // restarts the menu + UI_PopMenu(); + UI_Controls_Menu(); + + hold_button_stack = false; + + // restore last key and top item + uiControls.keysList.curItem = lastSelectedKey; + uiControls.keysList.topItem = lastTopItem; +} + +static void UI_Controls_ResetKeysList( void ) +{ + char *afile = (char *)LOAD_FILE( "gfx/shell/kb_def.lst", NULL ); + char *pfile = afile; + char token[1024]; + int i = 0; + + if( !afile ) + { + Con_Printf( "UI_Parse_KeysList: kb_act.lst not found\n" ); + return; + } + + while(( pfile = COM_ParseFile( pfile, token )) != NULL ) + { + char key[32]; + + strncpy( key, token, sizeof( key )); + + pfile = COM_ParseFile( pfile, token ); + if( !pfile ) break; // technically an error + + char cmd[128]; + + if( key[0] == '\\' && key[1] == '\\' ) + { + key[0] = '\\'; + key[1] = '\0'; + } + + UI_UnbindCommand( token ); + + sprintf( cmd, "bind \"%s\" \"%s\"\n", key, token ); + CLIENT_COMMAND( TRUE, cmd ); + } + + FREE_FILE( afile ); + UI_Controls_RestartMenu (); +} + +/* +================= +UI_Controls_KeyFunc +================= +*/ +static const char *UI_Controls_KeyFunc( int key, int down ) +{ + char cmd[128]; + + if( uiControls.msgBox1.generic.flags & QMF_HIDDEN ) + { + if( down && key == K_ESCAPE && uiControls.defaults.generic.flags & QMF_INACTIVE ) + { + UI_ResetToDefaultsDialog(); + return uiSoundNull; + } + } + + if( down ) + { + if( uiControls.bind_grab ) // assume we are in grab-mode + { + // defining a key + if( key == '`' || key == '~' ) + { + return uiSoundBuzz; + } + else if( key != K_ESCAPE ) + { + const char *bindName = uiControls.keysBind[uiControls.keysList.curItem]; + sprintf( cmd, "bind \"%s\" \"%s\"\n", KEY_KeynumToString( key ), bindName ); + CLIENT_COMMAND( TRUE, cmd ); + } + + uiControls.bind_grab = false; + UI_Controls_RestartMenu(); + + return uiSoundLaunch; + } + + if( key == K_ENTER && uiControls.dlgMessage.generic.flags & QMF_HIDDEN ) + { + if( !strlen( uiControls.keysBind[uiControls.keysList.curItem] )) + { + // probably it's a seperator + return uiSoundBuzz; + } + + // entering to grab-mode + const char *bindName = uiControls.keysBind[uiControls.keysList.curItem]; + int keys[2]; + + UI_Controls_GetKeyBindings( bindName, keys ); + if( keys[1] != -1 ) UI_UnbindCommand( bindName ); + uiControls.bind_grab = true; + + UI_PromptDialog(); // show prompt + return uiSoundKey; + } + + if(( key == K_BACKSPACE || key == K_DEL ) && uiControls.dlgMessage.generic.flags & QMF_HIDDEN ) + { + // delete bindings + + if( !strlen( uiControls.keysBind[uiControls.keysList.curItem] )) + { + // probably it's a seperator + return uiSoundNull; + } + + const char *bindName = uiControls.keysBind[uiControls.keysList.curItem]; + UI_UnbindCommand( bindName ); + UI_StartSound( uiSoundRemoveKey ); + UI_Controls_RestartMenu(); + + return uiSoundNull; + } + } + return UI_DefaultKey( &uiControls.menu, key, down ); +} + +/* +================= +UI_MsgBox_Ownerdraw +================= +*/ +static void UI_MsgBox_Ownerdraw( void *self ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + UI_FillRect( item->x, item->y, item->width, item->height, uiPromptBgColor ); +} + +/* +================= +UI_Controls_Callback +================= +*/ +static void UI_Controls_Callback( void *self, int event ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + if( event != QM_ACTIVATED ) + return; + + switch( item->id ) + { + case ID_DONE: + case ID_CANCEL: + UI_PopMenu(); + break; + case ID_DEFAULTS: + case ID_NO: + UI_ResetToDefaultsDialog (); + break; + case ID_YES: + UI_Controls_ResetKeysList (); + break; + case ID_ADVANCED: + UI_AdvControls_Menu(); + break; + } +} + +/* +================= +UI_Controls_Init +================= +*/ +static void UI_Controls_Init( void ) +{ + memset( &uiControls, 0, sizeof( uiControls_t )); + + uiControls.menu.vidInitFunc = UI_Controls_Init; + uiControls.menu.keyFunc = UI_Controls_KeyFunc; + + StringConcat( uiControls.hintText, "Action", CMD_LENGTH ); + StringConcat( uiControls.hintText, uiEmptyString, CMD_LENGTH-4 ); + StringConcat( uiControls.hintText, "Key/Button", KEY1_LENGTH ); + StringConcat( uiControls.hintText, uiEmptyString, KEY1_LENGTH-8 ); + StringConcat( uiControls.hintText, "Alternate", KEY2_LENGTH ); + StringConcat( uiControls.hintText, uiEmptyString, KEY2_LENGTH ); + + uiControls.background.generic.id = ID_BACKGROUND; + uiControls.background.generic.type = QMTYPE_BITMAP; + uiControls.background.generic.flags = QMF_INACTIVE; + uiControls.background.generic.x = 0; + uiControls.background.generic.y = 0; + uiControls.background.generic.width = 1024; + uiControls.background.generic.height = 768; + uiControls.background.pic = ART_BACKGROUND; + + uiControls.banner.generic.id = ID_BANNER; + uiControls.banner.generic.type = QMTYPE_BITMAP; + uiControls.banner.generic.flags = QMF_INACTIVE|QMF_DRAW_ADDITIVE; + uiControls.banner.generic.x = UI_BANNER_POSX; + uiControls.banner.generic.y = UI_BANNER_POSY; + uiControls.banner.generic.width = UI_BANNER_WIDTH; + uiControls.banner.generic.height = UI_BANNER_HEIGHT; + uiControls.banner.pic = ART_BANNER; + + uiControls.defaults.generic.id = ID_DEFAULTS; + uiControls.defaults.generic.type = QMTYPE_BM_BUTTON; + uiControls.defaults.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiControls.defaults.generic.x = 72; + uiControls.defaults.generic.y = 230; + uiControls.defaults.generic.name = "Use defaults"; + uiControls.defaults.generic.statusText = "Reset all buttons binding to their default values"; + uiControls.defaults.generic.callback = UI_Controls_Callback; + + UI_UtilSetupPicButton( &uiControls.defaults, PC_USE_DEFAULTS ); + + uiControls.advanced.generic.id = ID_ADVANCED; + uiControls.advanced.generic.type = QMTYPE_BM_BUTTON; + uiControls.advanced.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiControls.advanced.generic.x = 72; + uiControls.advanced.generic.y = 280; + uiControls.advanced.generic.name = "Adv controls"; + uiControls.advanced.generic.statusText = "Change mouse sensitivity, enable autoaim, mouselook and crosshair"; + uiControls.advanced.generic.callback = UI_Controls_Callback; + + UI_UtilSetupPicButton( &uiControls.advanced, PC_ADV_CONTROLS ); + + uiControls.done.generic.id = ID_DONE; + uiControls.done.generic.type = QMTYPE_BM_BUTTON; + uiControls.done.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiControls.done.generic.x = 72; + uiControls.done.generic.y = 330; + uiControls.done.generic.name = "Ok"; + uiControls.done.generic.statusText = "Save changes and return to configuration menu"; + uiControls.done.generic.callback = UI_Controls_Callback; + + UI_UtilSetupPicButton( &uiControls.done, PC_DONE ); + + uiControls.cancel.generic.id = ID_CANCEL; + uiControls.cancel.generic.type = QMTYPE_BM_BUTTON; + uiControls.cancel.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiControls.cancel.generic.x = 72; + uiControls.cancel.generic.y = 380; + uiControls.cancel.generic.name = "Cancel"; + uiControls.cancel.generic.statusText = "Discard changes and return to configuration menu"; + uiControls.cancel.generic.callback = UI_Controls_Callback; + + UI_UtilSetupPicButton( &uiControls.cancel, PC_CANCEL ); + + uiControls.hintMessage.generic.id = ID_TABLEHINT; + uiControls.hintMessage.generic.type = QMTYPE_ACTION; + uiControls.hintMessage.generic.flags = QMF_INACTIVE|QMF_SMALLFONT; + uiControls.hintMessage.generic.color = uiColorHelp; + uiControls.hintMessage.generic.name = uiControls.hintText; + uiControls.hintMessage.generic.x = 360; + uiControls.hintMessage.generic.y = 225; + + uiControls.keysList.generic.id = ID_KEYLIST; + uiControls.keysList.generic.type = QMTYPE_SCROLLLIST; + uiControls.keysList.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_SMALLFONT; + uiControls.keysList.generic.x = 360; + uiControls.keysList.generic.y = 255; + uiControls.keysList.generic.width = 640; + uiControls.keysList.generic.height = 440; + uiControls.keysList.generic.callback = UI_Controls_Callback; + + UI_Controls_ParseKeysList(); + + uiControls.msgBox1.generic.id = ID_MSGBOX1; + uiControls.msgBox1.generic.type = QMTYPE_ACTION; + uiControls.msgBox1.generic.flags = QMF_INACTIVE|QMF_HIDDEN; + uiControls.msgBox1.generic.ownerdraw = UI_MsgBox_Ownerdraw; // just a fill rectangle + uiControls.msgBox1.generic.x = 192; + uiControls.msgBox1.generic.y = 256; + uiControls.msgBox1.generic.width = 640; + uiControls.msgBox1.generic.height = 128; + + uiControls.msgBox2.generic.id = ID_MSGBOX2; + uiControls.msgBox2.generic.type = QMTYPE_ACTION; + uiControls.msgBox2.generic.flags = QMF_INACTIVE|QMF_HIDDEN; + uiControls.msgBox2.generic.ownerdraw = UI_MsgBox_Ownerdraw; // just a fill rectangle + uiControls.msgBox2.generic.x = 192; + uiControls.msgBox2.generic.y = 256; + uiControls.msgBox2.generic.width = 640; + uiControls.msgBox2.generic.height = 256; + + uiControls.dlgMessage.generic.id = ID_MSGTEXT; + uiControls.dlgMessage.generic.type = QMTYPE_ACTION; + uiControls.dlgMessage.generic.flags = QMF_INACTIVE|QMF_HIDDEN|QMF_DROPSHADOW; + uiControls.dlgMessage.generic.name = "Press a key or button"; + uiControls.dlgMessage.generic.x = 320; + uiControls.dlgMessage.generic.y = 280; + + uiControls.promptMessage.generic.id = ID_PROMPT; + uiControls.promptMessage.generic.type = QMTYPE_ACTION; + uiControls.promptMessage.generic.flags = QMF_INACTIVE|QMF_DROPSHADOW|QMF_HIDDEN; + uiControls.promptMessage.generic.name = "Reset buttons to default?"; + uiControls.promptMessage.generic.x = 290; + uiControls.promptMessage.generic.y = 280; + + uiControls.yes.generic.id = ID_YES; + uiControls.yes.generic.type = QMTYPE_BM_BUTTON; + uiControls.yes.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_HIDDEN; + uiControls.yes.generic.name = "Ok"; + uiControls.yes.generic.x = 380; + uiControls.yes.generic.y = 460; + uiControls.yes.generic.callback = UI_Controls_Callback; + + UI_UtilSetupPicButton( &uiControls.yes, PC_OK ); + + uiControls.no.generic.id = ID_NO; + uiControls.no.generic.type = QMTYPE_BM_BUTTON; + uiControls.no.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_HIDDEN; + uiControls.no.generic.name = "Cancel"; + uiControls.no.generic.x = 530; + uiControls.no.generic.y = 460; + uiControls.no.generic.callback = UI_Controls_Callback; + + UI_UtilSetupPicButton( &uiControls.no, PC_CANCEL ); + + UI_AddItem( &uiControls.menu, (void *)&uiControls.background ); + UI_AddItem( &uiControls.menu, (void *)&uiControls.banner ); + UI_AddItem( &uiControls.menu, (void *)&uiControls.defaults ); + UI_AddItem( &uiControls.menu, (void *)&uiControls.advanced ); + UI_AddItem( &uiControls.menu, (void *)&uiControls.done ); + UI_AddItem( &uiControls.menu, (void *)&uiControls.cancel ); + UI_AddItem( &uiControls.menu, (void *)&uiControls.hintMessage ); + UI_AddItem( &uiControls.menu, (void *)&uiControls.keysList ); + UI_AddItem( &uiControls.menu, (void *)&uiControls.msgBox1 ); + UI_AddItem( &uiControls.menu, (void *)&uiControls.msgBox2 ); + UI_AddItem( &uiControls.menu, (void *)&uiControls.dlgMessage ); + UI_AddItem( &uiControls.menu, (void *)&uiControls.promptMessage ); + UI_AddItem( &uiControls.menu, (void *)&uiControls.no ); + UI_AddItem( &uiControls.menu, (void *)&uiControls.yes ); +} + +/* +================= +UI_Controls_Precache +================= +*/ +void UI_Controls_Precache( void ) +{ + PIC_Load( ART_BACKGROUND ); + PIC_Load( ART_BANNER ); +} + +/* +================= +UI_Controls_Menu +================= +*/ +void UI_Controls_Menu( void ) +{ + UI_Controls_Precache(); + UI_Controls_Init(); + + UI_PushMenu( &uiControls.menu ); +} \ No newline at end of file diff --git a/mainui/menu_creategame.cpp b/mainui/menu_creategame.cpp new file mode 100644 index 0000000..52c14ff --- /dev/null +++ b/mainui/menu_creategame.cpp @@ -0,0 +1,478 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "extdll.h" +#include "basemenu.h" +#include "utils.h" +#include "keydefs.h" +#include "menu_btnsbmp_table.h" + +#define ART_BANNER "gfx/shell/head_creategame" + +#define ID_BACKGROUND 0 +#define ID_BANNER 1 +#define ID_ADVOPTIONS 2 +#define ID_DONE 3 +#define ID_CANCEL 4 +#define ID_MAPLIST 5 +#define ID_TABLEHINT 6 +#define ID_MAXCLIENTS 7 +#define ID_HOSTNAME 8 +#define ID_PASSWORD 9 +#define ID_DEDICATED 10 + +#define ID_MSGBOX 12 +#define ID_MSGTEXT 13 +#define ID_YES 130 +#define ID_NO 131 + +#define MAPNAME_LENGTH 20 +#define TITLE_LENGTH 20+MAPNAME_LENGTH + +typedef struct +{ + char mapName[UI_MAXGAMES][64]; + char mapsDescription[UI_MAXGAMES][256]; + char *mapsDescriptionPtr[UI_MAXGAMES]; + + menuFramework_s menu; + + menuBitmap_s background; + menuBitmap_s banner; + menuPicButton_s advOptions; + menuPicButton_s done; + menuPicButton_s cancel; + + menuField_s maxClients; + menuField_s hostName; + menuField_s password; + menuCheckBox_s dedicatedServer; + + // newgame prompt dialog + menuAction_s msgBox; + menuAction_s dlgMessage1; + menuAction_s dlgMessage2; + menuPicButton_s yes; + menuPicButton_s no; + + menuScrollList_s mapsList; + menuAction_s hintMessage; + char hintText[MAX_HINT_TEXT]; +} uiCreateGame_t; + +static uiCreateGame_t uiCreateGame; + +/* +================= +UI_CreateGame_Begin +================= +*/ +static void UI_CreateGame_Begin( void ) +{ + char *pMapName = uiCreateGame.mapName[uiCreateGame.mapsList.curItem]; + int maxPlayers = atoi( uiCreateGame.maxClients.buffer ); + + if( !MAP_IS_VALID( pMapName )) + return; // bad map + + CVAR_SET_STRING( "hostname", uiCreateGame.hostName.buffer ); + HOST_WRITECONFIG ( "game.cfg" ); + + if( CVAR_GET_FLOAT( "host_serverstate" ) && CVAR_GET_FLOAT( "maxplayers" ) == 1 ) + HOST_ENDGAME( "end of the game" ); + + CVAR_SET_FLOAT( "deathmatch", 1.0f ); // start deathmatch as default + CVAR_SET_FLOAT( "maxplayers", maxPlayers ); + BACKGROUND_TRACK ( NULL, NULL ); + + // all done, start server + if( uiCreateGame.dedicatedServer.enabled ) + { + char cmd[128], msg[128]; + sprintf( cmd, "#%s +maxplayers %i +map %s", gMenu.m_gameinfo.gamefolder, maxPlayers, pMapName ); + sprintf( msg, "startup dedicated server from '%s'", gMenu.m_gameinfo.gamefolder ); + + HOST_CHANGEGAME( cmd, msg ); + } + else + { + char cmd[128]; + sprintf( cmd, "map %s\n", pMapName ); + + CLIENT_COMMAND( FALSE, cmd ); + } +} + +static void UI_PromptDialog( void ) +{ + if( !CVAR_GET_FLOAT( "host_serverstate" ) || CVAR_GET_FLOAT( "cl_background" )) + { + UI_CreateGame_Begin(); + return; + } + + // toggle main menu between active\inactive + // show\hide quit dialog + uiCreateGame.advOptions.generic.flags ^= QMF_INACTIVE; + uiCreateGame.done.generic.flags ^= QMF_INACTIVE; + uiCreateGame.cancel.generic.flags ^= QMF_INACTIVE; + uiCreateGame.maxClients.generic.flags ^= QMF_INACTIVE; + uiCreateGame.hostName.generic.flags ^= QMF_INACTIVE; + uiCreateGame.password.generic.flags ^= QMF_INACTIVE; + uiCreateGame.dedicatedServer.generic.flags ^= QMF_INACTIVE; + uiCreateGame.mapsList.generic.flags ^= QMF_INACTIVE; + + uiCreateGame.msgBox.generic.flags ^= QMF_HIDDEN; + uiCreateGame.dlgMessage1.generic.flags ^= QMF_HIDDEN; + uiCreateGame.dlgMessage2.generic.flags ^= QMF_HIDDEN; + uiCreateGame.no.generic.flags ^= QMF_HIDDEN; + uiCreateGame.yes.generic.flags ^= QMF_HIDDEN; + +} + +/* +================= +UI_CreateGame_KeyFunc +================= +*/ +static const char *UI_CreateGame_KeyFunc( int key, int down ) +{ + if( down && key == K_ESCAPE && !( uiCreateGame.dlgMessage1.generic.flags & QMF_HIDDEN )) + { + UI_PromptDialog(); + return uiSoundNull; + } + return UI_DefaultKey( &uiCreateGame.menu, key, down ); +} + +/* +================= +UI_MsgBox_Ownerdraw +================= +*/ +static void UI_MsgBox_Ownerdraw( void *self ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + UI_FillRect( item->x, item->y, item->width, item->height, uiPromptBgColor ); +} + +/* +================= +UI_CreateGame_GetMapsList +================= +*/ +static void UI_CreateGame_GetMapsList( void ) +{ + char *afile; + + if( !CHECK_MAP_LIST( FALSE ) || (afile = (char *)LOAD_FILE( "maps.lst", NULL )) == NULL ) + { + uiCreateGame.done.generic.flags |= QMF_GRAYED; + uiCreateGame.mapsList.itemNames = (const char **)uiCreateGame.mapsDescriptionPtr; + Con_Printf( "Cmd_GetMapsList: can't open maps.lst\n" ); + return; + } + + char *pfile = afile; + char token[1024]; + int numMaps = 0; + + while(( pfile = COM_ParseFile( pfile, token )) != NULL ) + { + if( numMaps >= UI_MAXGAMES ) break; + StringConcat( uiCreateGame.mapName[numMaps], token, sizeof( uiCreateGame.mapName[0] )); + StringConcat( uiCreateGame.mapsDescription[numMaps], token, MAPNAME_LENGTH ); + StringConcat( uiCreateGame.mapsDescription[numMaps], uiEmptyString, MAPNAME_LENGTH ); + if(( pfile = COM_ParseFile( pfile, token )) == NULL ) break; // unexpected end of file + StringConcat( uiCreateGame.mapsDescription[numMaps], token, TITLE_LENGTH ); + StringConcat( uiCreateGame.mapsDescription[numMaps], uiEmptyString, TITLE_LENGTH ); + uiCreateGame.mapsDescriptionPtr[numMaps] = uiCreateGame.mapsDescription[numMaps]; + numMaps++; + } + + if( !numMaps ) uiCreateGame.done.generic.flags |= QMF_GRAYED; + + for( ; numMaps < UI_MAXGAMES; numMaps++ ) uiCreateGame.mapsDescriptionPtr[numMaps] = NULL; + uiCreateGame.mapsList.itemNames = (const char **)uiCreateGame.mapsDescriptionPtr; + FREE_FILE( afile ); +} + +/* +================= +UI_CreateGame_Callback +================= +*/ +static void UI_CreateGame_Callback( void *self, int event ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + switch( item->id ) + { + case ID_DEDICATED: + if( event == QM_PRESSED ) + ((menuCheckBox_s *)self)->focusPic = UI_CHECKBOX_PRESSED; + else ((menuCheckBox_s *)self)->focusPic = UI_CHECKBOX_FOCUS; + break; + } + + if( event != QM_ACTIVATED ) + return; + + switch( item->id ) + { + case ID_ADVOPTIONS: + // UNDONE: not implemented + break; + case ID_DONE: + UI_PromptDialog(); + break; + case ID_CANCEL: + UI_PopMenu(); + break; + case ID_YES: + UI_CreateGame_Begin(); + break; + case ID_NO: + UI_PromptDialog(); + break; + } +} + +/* +================= +UI_CreateGame_Init +================= +*/ +static void UI_CreateGame_Init( void ) +{ + memset( &uiCreateGame, 0, sizeof( uiCreateGame_t )); + + uiCreateGame.menu.vidInitFunc = UI_CreateGame_Init; + uiCreateGame.menu.keyFunc = UI_CreateGame_KeyFunc; + + StringConcat( uiCreateGame.hintText, "Map", MAPNAME_LENGTH ); + StringConcat( uiCreateGame.hintText, uiEmptyString, MAPNAME_LENGTH ); + StringConcat( uiCreateGame.hintText, "Title", TITLE_LENGTH ); + StringConcat( uiCreateGame.hintText, uiEmptyString, TITLE_LENGTH ); + + uiCreateGame.background.generic.id = ID_BACKGROUND; + uiCreateGame.background.generic.type = QMTYPE_BITMAP; + uiCreateGame.background.generic.flags = QMF_INACTIVE; + uiCreateGame.background.generic.x = 0; + uiCreateGame.background.generic.y = 0; + uiCreateGame.background.generic.width = 1024; + uiCreateGame.background.generic.height = 768; + uiCreateGame.background.pic = ART_BACKGROUND; + + uiCreateGame.banner.generic.id = ID_BANNER; + uiCreateGame.banner.generic.type = QMTYPE_BITMAP; + uiCreateGame.banner.generic.flags = QMF_INACTIVE|QMF_DRAW_ADDITIVE; + uiCreateGame.banner.generic.x = UI_BANNER_POSX; + uiCreateGame.banner.generic.y = UI_BANNER_POSY; + uiCreateGame.banner.generic.width = UI_BANNER_WIDTH; + uiCreateGame.banner.generic.height = UI_BANNER_HEIGHT; + uiCreateGame.banner.pic = ART_BANNER; + + uiCreateGame.advOptions.generic.id = ID_ADVOPTIONS; + uiCreateGame.advOptions.generic.type = QMTYPE_BM_BUTTON; + uiCreateGame.advOptions.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_GRAYED; + uiCreateGame.advOptions.generic.x = 72; + uiCreateGame.advOptions.generic.y = 230; + uiCreateGame.advOptions.generic.name = "Adv. Options"; + uiCreateGame.advOptions.generic.statusText = "Open the LAN game advanced options menu"; + uiCreateGame.advOptions.generic.callback = UI_CreateGame_Callback; + + UI_UtilSetupPicButton( &uiCreateGame.advOptions, PC_ADV_OPT ); + + uiCreateGame.done.generic.id = ID_DONE; + uiCreateGame.done.generic.type = QMTYPE_BM_BUTTON; + uiCreateGame.done.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiCreateGame.done.generic.x = 72; + uiCreateGame.done.generic.y = 280; + uiCreateGame.done.generic.name = "Ok"; + uiCreateGame.done.generic.statusText = "Start the multiplayer game"; + uiCreateGame.done.generic.callback = UI_CreateGame_Callback; + + UI_UtilSetupPicButton( &uiCreateGame.done, PC_OK ); + + uiCreateGame.cancel.generic.id = ID_CANCEL; + uiCreateGame.cancel.generic.type = QMTYPE_BM_BUTTON; + uiCreateGame.cancel.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiCreateGame.cancel.generic.x = 72; + uiCreateGame.cancel.generic.y = 330; + uiCreateGame.cancel.generic.name = "Cancel"; + uiCreateGame.cancel.generic.statusText = "Return to LAN game menu"; + uiCreateGame.cancel.generic.callback = UI_CreateGame_Callback; + + UI_UtilSetupPicButton( &uiCreateGame.cancel, PC_CANCEL ); + + uiCreateGame.dedicatedServer.generic.id = ID_DEDICATED; + uiCreateGame.dedicatedServer.generic.type = QMTYPE_CHECKBOX; + uiCreateGame.dedicatedServer.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_ACT_ONRELEASE|QMF_MOUSEONLY|QMF_DROPSHADOW; + uiCreateGame.dedicatedServer.generic.name = "Dedicated server"; + uiCreateGame.dedicatedServer.generic.x = 72; + uiCreateGame.dedicatedServer.generic.y = 685; + uiCreateGame.dedicatedServer.generic.callback = UI_CreateGame_Callback; + uiCreateGame.dedicatedServer.generic.statusText = "faster, but you can't join the server from this machine"; + + uiCreateGame.hintMessage.generic.id = ID_TABLEHINT; + uiCreateGame.hintMessage.generic.type = QMTYPE_ACTION; + uiCreateGame.hintMessage.generic.flags = QMF_INACTIVE|QMF_SMALLFONT; + uiCreateGame.hintMessage.generic.color = uiColorHelp; + uiCreateGame.hintMessage.generic.name = uiCreateGame.hintText; + uiCreateGame.hintMessage.generic.x = 590; + uiCreateGame.hintMessage.generic.y = 215; + + uiCreateGame.mapsList.generic.id = ID_MAPLIST; + uiCreateGame.mapsList.generic.type = QMTYPE_SCROLLLIST; + uiCreateGame.mapsList.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_SMALLFONT; + uiCreateGame.mapsList.generic.x = 590; + uiCreateGame.mapsList.generic.y = 245; + uiCreateGame.mapsList.generic.width = 410; + uiCreateGame.mapsList.generic.height = 440; + uiCreateGame.mapsList.generic.callback = UI_CreateGame_Callback; + + uiCreateGame.hostName.generic.id = ID_HOSTNAME; + uiCreateGame.hostName.generic.type = QMTYPE_FIELD; + uiCreateGame.hostName.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiCreateGame.hostName.generic.name = "Server Name:"; + uiCreateGame.hostName.generic.x = 350; + uiCreateGame.hostName.generic.y = 260; + uiCreateGame.hostName.generic.width = 205; + uiCreateGame.hostName.generic.height = 32; + uiCreateGame.hostName.generic.callback = UI_CreateGame_Callback; + uiCreateGame.hostName.maxLength = 28; + strcpy( uiCreateGame.hostName.buffer, CVAR_GET_STRING( "hostname" )); + + uiCreateGame.maxClients.generic.id = ID_MAXCLIENTS; + uiCreateGame.maxClients.generic.type = QMTYPE_FIELD; + uiCreateGame.maxClients.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NUMBERSONLY; + uiCreateGame.maxClients.generic.name = "Max Players:"; + uiCreateGame.maxClients.generic.x = 350; + uiCreateGame.maxClients.generic.y = 360; + uiCreateGame.maxClients.generic.width = 205; + uiCreateGame.maxClients.generic.height = 32; + uiCreateGame.maxClients.maxLength = 3; + + if( CVAR_GET_FLOAT( "maxplayers" ) <= 1 ) + strcpy( uiCreateGame.maxClients.buffer, "8" ); + else sprintf( uiCreateGame.maxClients.buffer, "%i", (int)CVAR_GET_FLOAT( "maxplayers" )); + + uiCreateGame.password.generic.id = ID_PASSWORD; + uiCreateGame.password.generic.type = QMTYPE_FIELD; + uiCreateGame.password.generic.flags = QMF_CENTER_JUSTIFY|QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_HIDEINPUT; + uiCreateGame.password.generic.name = "Password:"; + uiCreateGame.password.generic.x = 350; + uiCreateGame.password.generic.y = 460; + uiCreateGame.password.generic.width = 205; + uiCreateGame.password.generic.height = 32; + uiCreateGame.password.generic.callback = UI_CreateGame_Callback; + uiCreateGame.password.maxLength = 16; + + uiCreateGame.msgBox.generic.id = ID_MSGBOX; + uiCreateGame.msgBox.generic.type = QMTYPE_ACTION; + uiCreateGame.msgBox.generic.flags = QMF_INACTIVE|QMF_HIDDEN; + uiCreateGame.msgBox.generic.ownerdraw = UI_MsgBox_Ownerdraw; // just a fill rectangle + uiCreateGame.msgBox.generic.x = 192; + uiCreateGame.msgBox.generic.y = 256; + uiCreateGame.msgBox.generic.width = 640; + uiCreateGame.msgBox.generic.height = 256; + + uiCreateGame.dlgMessage1.generic.id = ID_MSGTEXT; + uiCreateGame.dlgMessage1.generic.type = QMTYPE_ACTION; + uiCreateGame.dlgMessage1.generic.flags = QMF_INACTIVE|QMF_HIDDEN|QMF_DROPSHADOW; + uiCreateGame.dlgMessage1.generic.name = "Starting a new game will exit"; + uiCreateGame.dlgMessage1.generic.x = 248; + uiCreateGame.dlgMessage1.generic.y = 280; + + uiCreateGame.dlgMessage2.generic.id = ID_MSGTEXT; + uiCreateGame.dlgMessage2.generic.type = QMTYPE_ACTION; + uiCreateGame.dlgMessage2.generic.flags = QMF_INACTIVE|QMF_HIDDEN|QMF_DROPSHADOW; + uiCreateGame.dlgMessage2.generic.name = "any current game, OK to exit?"; + uiCreateGame.dlgMessage2.generic.x = 248; + uiCreateGame.dlgMessage2.generic.y = 310; + + uiCreateGame.yes.generic.id = ID_YES; + uiCreateGame.yes.generic.type = QMTYPE_BM_BUTTON; + uiCreateGame.yes.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_HIDDEN|QMF_DROPSHADOW; + uiCreateGame.yes.generic.name = "Ok"; + uiCreateGame.yes.generic.x = 380; + uiCreateGame.yes.generic.y = 460; + uiCreateGame.yes.generic.callback = UI_CreateGame_Callback; + + UI_UtilSetupPicButton( &uiCreateGame.yes, PC_OK ); + + uiCreateGame.no.generic.id = ID_NO; + uiCreateGame.no.generic.type = QMTYPE_BM_BUTTON; + uiCreateGame.no.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_HIDDEN|QMF_DROPSHADOW; + uiCreateGame.no.generic.name = "Cancel"; + uiCreateGame.no.generic.x = 530; + uiCreateGame.no.generic.y = 460; + uiCreateGame.no.generic.callback = UI_CreateGame_Callback; + + UI_UtilSetupPicButton( &uiCreateGame.no, PC_CANCEL ); + + UI_CreateGame_GetMapsList(); + + UI_AddItem( &uiCreateGame.menu, (void *)&uiCreateGame.background ); + UI_AddItem( &uiCreateGame.menu, (void *)&uiCreateGame.banner ); + UI_AddItem( &uiCreateGame.menu, (void *)&uiCreateGame.advOptions ); + UI_AddItem( &uiCreateGame.menu, (void *)&uiCreateGame.done ); + UI_AddItem( &uiCreateGame.menu, (void *)&uiCreateGame.cancel ); + UI_AddItem( &uiCreateGame.menu, (void *)&uiCreateGame.maxClients ); + UI_AddItem( &uiCreateGame.menu, (void *)&uiCreateGame.hostName ); + UI_AddItem( &uiCreateGame.menu, (void *)&uiCreateGame.password ); + UI_AddItem( &uiCreateGame.menu, (void *)&uiCreateGame.dedicatedServer ); + UI_AddItem( &uiCreateGame.menu, (void *)&uiCreateGame.hintMessage ); + UI_AddItem( &uiCreateGame.menu, (void *)&uiCreateGame.mapsList ); + UI_AddItem( &uiCreateGame.menu, (void *)&uiCreateGame.msgBox ); + UI_AddItem( &uiCreateGame.menu, (void *)&uiCreateGame.dlgMessage1 ); + UI_AddItem( &uiCreateGame.menu, (void *)&uiCreateGame.dlgMessage2 ); + UI_AddItem( &uiCreateGame.menu, (void *)&uiCreateGame.no ); + UI_AddItem( &uiCreateGame.menu, (void *)&uiCreateGame.yes ); +} + +/* +================= +UI_CreateGame_Precache +================= +*/ +void UI_CreateGame_Precache( void ) +{ + PIC_Load( ART_BACKGROUND ); + PIC_Load( ART_BANNER ); +} + +/* +================= +UI_CreateGame_Menu +================= +*/ +void UI_CreateGame_Menu( void ) +{ + if ( gMenu.m_gameinfo.gamemode == GAME_SINGLEPLAYER_ONLY ) + return; + + UI_CreateGame_Precache(); + UI_CreateGame_Init(); + + UI_PushMenu( &uiCreateGame.menu ); +} \ No newline at end of file diff --git a/mainui/menu_credits.cpp b/mainui/menu_credits.cpp new file mode 100644 index 0000000..0c364e7 --- /dev/null +++ b/mainui/menu_credits.cpp @@ -0,0 +1,229 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "extdll.h" +#include "basemenu.h" +#include "utils.h" + +#define UI_CREDITS_PATH "credits.txt" +#define UI_CREDITS_MAXLINES 2048 + +static const char *uiCreditsDefault[] = +{ + "", + "Copyright Kunst-Produkt & XashXT Group 2019 (C)", + 0 +}; + +typedef struct +{ + const char **credits; + int startTime; + int showTime; + int fadeTime; + int numLines; + int active; + int finalCredits; + char *index[UI_CREDITS_MAXLINES]; + char *buffer; + + menuFramework_s menu; +} uiCredits_t; + +static uiCredits_t uiCredits; + + +/* +================= +UI_Credits_DrawFunc +================= +*/ +static void UI_Credits_DrawFunc( void ) +{ + int i, y; + float speed = 40.0f; + int w = UI_MED_CHAR_WIDTH; + int h = UI_MED_CHAR_HEIGHT; + int color = 0; + + // draw the background first + if( !uiCredits.finalCredits && !CVAR_GET_FLOAT( "cl_background" )) + UI_DrawPic( 0, 0, 1024 * uiStatic.scaleX, 768 * uiStatic.scaleY, uiColorWhite, ART_BACKGROUND ); + else speed = 45.0f; // syncronize with final background track :-) + + // otherwise running on cutscene + speed = 32.0f * (768.0f / ScreenHeight); + + // now draw the credits + UI_ScaleCoords( NULL, NULL, &w, &h ); + + y = ScreenHeight - (((gpGlobals->time * 1000) - uiCredits.startTime ) / speed ); + + // draw the credits + for ( i = 0; i < uiCredits.numLines && uiCredits.credits[i]; i++, y += h ) + { + // skip not visible lines, but always draw end line + if( y <= -h && i != uiCredits.numLines - 1 ) continue; + + if(( y < ( ScreenHeight - h ) / 2 ) && i == uiCredits.numLines - 1 ) + { + if( !uiCredits.fadeTime ) uiCredits.fadeTime = (gpGlobals->time * 1000); + color = UI_FadeAlpha( uiCredits.fadeTime, uiCredits.showTime ); + if( UnpackAlpha( color )) + UI_DrawString( 0, ( ScreenHeight - h ) / 2, 1024 * uiStatic.scaleX, h, uiCredits.credits[i], color, true, w, h, 1, true ); + } + else UI_DrawString( 0, y, 1024 * uiStatic.scaleX, h, uiCredits.credits[i], uiColorWhite, false, w, h, 1, true ); + } + + if( y < 0 && UnpackAlpha( color ) == 0 ) + { + uiCredits.active = false; // end of credits + if( uiCredits.finalCredits ) + HOST_ENDGAME( gMenu.m_gameinfo.title ); + } + + if( !uiCredits.active ) + UI_PopMenu(); +} + +/* +================= +UI_Credits_KeyFunc +================= +*/ +static const char *UI_Credits_KeyFunc( int key, int down ) +{ + if( !down ) return uiSoundNull; + + // final credits can't be intterupted + if( uiCredits.finalCredits ) + return uiSoundNull; + + uiCredits.active = false; + return uiSoundNull; +} + +/* +================= +UI_Credits_Init +================= +*/ +static void UI_Credits_Init( void ) +{ + uiCredits.menu.drawFunc = UI_Credits_DrawFunc; + uiCredits.menu.keyFunc = UI_Credits_KeyFunc; + + if( !uiCredits.buffer ) + { + int count; + char *p; + + // load credits if needed + uiCredits.buffer = (char *)LOAD_FILE( UI_CREDITS_PATH, &count ); + if( count ) + { + if( uiCredits.buffer[count - 1] != '\n' && uiCredits.buffer[count - 1] != '\r' ) + { + char *tmp = (char *)MALLOC( count + 2 ); + memcpy( tmp, uiCredits.buffer, count ); + FREE_FILE( uiCredits.buffer ); + uiCredits.buffer = tmp; + strncpy( uiCredits.buffer + count, "\r", 1 ); // add terminator + count += 2; // added "\r\0" + } + p = uiCredits.buffer; + + // convert customs credits to 'ideal' strings array + for ( uiCredits.numLines = 0; uiCredits.numLines < UI_CREDITS_MAXLINES; uiCredits.numLines++ ) + { + uiCredits.index[uiCredits.numLines] = p; + while ( *p != '\r' && *p != '\n' ) + { + p++; + if ( --count == 0 ) + break; + } + + if ( *p == '\r' ) + { + *p++ = 0; + if( --count == 0 ) break; + } + + *p++ = 0; + if( --count == 0 ) break; + } + uiCredits.index[++uiCredits.numLines] = 0; + uiCredits.credits = (const char **)uiCredits.index; + } + else + { + // use built-in credits + uiCredits.credits = uiCreditsDefault; + uiCredits.numLines = ( sizeof( uiCreditsDefault ) / sizeof( uiCreditsDefault[0] )) - 1; // skip term + } + } + + // run credits + uiCredits.startTime = (gpGlobals->time * 1000) + 500; // make half-seconds delay + uiCredits.showTime = bound( 1000, strlen( uiCredits.credits[uiCredits.numLines - 1]) * 1000, 10000 ); + uiCredits.fadeTime = 0; // will be determined later + uiCredits.active = true; +} + +void UI_DrawFinalCredits( void ) +{ + if( uiCredits.finalCredits && uiCredits.active ) + UI_Credits_DrawFunc (); +} + +int UI_CreditsActive( void ) +{ + return uiCredits.active && uiCredits.finalCredits; +} + +/* +================= +UI_Credits_Precache +================= +*/ +void UI_Credits_Precache( void ) +{ + PIC_Load( ART_BACKGROUND ); +} + +/* +================= +UI_Credits_Menu +================= +*/ +void UI_Credits_Menu( void ) +{ + UI_Credits_Precache(); + UI_Credits_Init(); + + UI_PushMenu( &uiCredits.menu ); +} + +void UI_FinalCredits( void ) +{ + uiCredits.finalCredits = true; + UI_Credits_Init(); +} \ No newline at end of file diff --git a/mainui/menu_customgame.cpp b/mainui/menu_customgame.cpp new file mode 100644 index 0000000..0da07dc --- /dev/null +++ b/mainui/menu_customgame.cpp @@ -0,0 +1,390 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "extdll.h" +#include "basemenu.h" +#include "utils.h" +#include "keydefs.h" +#include "menu_btnsbmp_table.h" + +#define ART_BANNER "gfx/shell/head_custom" + +#define ID_BACKGROUND 0 +#define ID_BANNER 1 +#define ID_ACTIVATE 2 +#define ID_DONE 3 +#define ID_GOTOSITE 4 +#define ID_MODLIST 5 +#define ID_TABLEHINT 6 +#define ID_MSGBOX 7 +#define ID_MSGTEXT 8 +#define ID_YES 130 +#define ID_NO 131 + +#define MAX_MODS 512 // engine limit + +#define TYPE_LENGTH 16 +#define NAME_SPACE 4 +#define NAME_LENGTH 32+TYPE_LENGTH +#define VER_LENGTH 6+NAME_LENGTH +#define SIZE_LENGTH 10+VER_LENGTH + +typedef struct +{ + char modsDir[MAX_MODS][64]; + char modsWebSites[MAX_MODS][256]; + char modsDescription[MAX_MODS][256]; + char *modsDescriptionPtr[MAX_MODS]; + + menuFramework_s menu; + + menuBitmap_s background; + menuBitmap_s banner; + menuPicButton_s load; + menuPicButton_s go2url; + menuPicButton_s done; + + // prompt dialog + menuAction_s msgBox; + menuAction_s promptMessage; + menuPicButton_s yes; + menuPicButton_s no; + + menuScrollList_s modList; + menuAction_s hintMessage; + char hintText[MAX_HINT_TEXT]; +} uiCustomGame_t; + +static uiCustomGame_t uiCustomGame; + +/* +================= +UI_MsgBox_Ownerdraw +================= +*/ +static void UI_MsgBox_Ownerdraw( void *self ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + UI_FillRect( item->x, item->y, item->width, item->height, uiPromptBgColor ); +} + +static void UI_EndGameDialog( void ) +{ + // toggle main menu between active\inactive + // show\hide delete dialog + uiCustomGame.load.generic.flags ^= QMF_INACTIVE; + uiCustomGame.go2url.generic.flags ^= QMF_INACTIVE; + uiCustomGame.done.generic.flags ^= QMF_INACTIVE; + uiCustomGame.modList.generic.flags ^= QMF_INACTIVE; + + uiCustomGame.msgBox.generic.flags ^= QMF_HIDDEN; + uiCustomGame.promptMessage.generic.flags ^= QMF_HIDDEN; + uiCustomGame.no.generic.flags ^= QMF_HIDDEN; + uiCustomGame.yes.generic.flags ^= QMF_HIDDEN; + +} + +/* +================= +UI_LoadGame_KeyFunc +================= +*/ +static const char *UI_CustomGame_KeyFunc( int key, int down ) +{ + if( down && key == K_ESCAPE && uiCustomGame.load.generic.flags & QMF_INACTIVE ) + { + UI_EndGameDialog(); + return uiSoundNull; + } + return UI_DefaultKey( &uiCustomGame.menu, key, down ); +} + +/* +================= +UI_CustomGame_GetModList +================= +*/ +static void UI_CustomGame_GetModList( void ) +{ + int numGames; + GAMEINFO **games; + + games = GET_GAMES_LIST( &numGames ); + + for( int i = 0; i < numGames; i++ ) + { + strncpy( uiCustomGame.modsDir[i], games[i]->gamefolder, sizeof( uiCustomGame.modsDir[i] )); + strncpy( uiCustomGame.modsWebSites[i], games[i]->game_url, sizeof( uiCustomGame.modsWebSites[i] )); + + if( strlen( games[i]->type )) + StringConcat( uiCustomGame.modsDescription[i], games[i]->type, TYPE_LENGTH ); + StringConcat( uiCustomGame.modsDescription[i], uiEmptyString, TYPE_LENGTH ); + + if( ColorStrlen( games[i]->title ) > 31 ) // NAME_LENGTH + { + StringConcat( uiCustomGame.modsDescription[i], games[i]->title, ( NAME_LENGTH - NAME_SPACE )); + StringConcat( uiCustomGame.modsDescription[i], "...", NAME_LENGTH ); + } + else StringConcat( uiCustomGame.modsDescription[i], games[i]->title, NAME_LENGTH ); + + StringConcat( uiCustomGame.modsDescription[i], uiEmptyString, NAME_LENGTH ); + StringConcat( uiCustomGame.modsDescription[i], games[i]->version, VER_LENGTH ); + StringConcat( uiCustomGame.modsDescription[i], uiEmptyString, VER_LENGTH ); + if( strlen( games[i]->size )) + StringConcat( uiCustomGame.modsDescription[i], games[i]->size, SIZE_LENGTH ); + else StringConcat( uiCustomGame.modsDescription[i], "0.0 Mb", SIZE_LENGTH ); + StringConcat( uiCustomGame.modsDescription[i], uiEmptyString, SIZE_LENGTH ); + uiCustomGame.modsDescriptionPtr[i] = uiCustomGame.modsDescription[i]; + + if( !strcmp( gMenu.m_gameinfo.gamefolder, games[i]->gamefolder )) + uiCustomGame.modList.curItem = i; + } + + for( ; i < MAX_MODS; i++ ) + uiCustomGame.modsDescriptionPtr[i] = NULL; + + uiCustomGame.modList.itemNames = (const char **)uiCustomGame.modsDescriptionPtr; + + // see if the load button should be grayed + if( !stricmp( gMenu.m_gameinfo.gamefolder, uiCustomGame.modsDir[uiCustomGame.modList.curItem] )) + uiCustomGame.load.generic.flags |= QMF_GRAYED; + if( strlen( uiCustomGame.modsWebSites[uiCustomGame.modList.curItem] ) == 0 ) + uiCustomGame.go2url.generic.flags |= QMF_GRAYED; +} + +/* +================= +UI_CustomGame_Callback +================= +*/ +static void UI_CustomGame_Callback( void *self, int event ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + if( event == QM_CHANGED ) + { + // aee if the load button should be grayed + if( !stricmp( gMenu.m_gameinfo.gamefolder, uiCustomGame.modsDir[uiCustomGame.modList.curItem] )) + uiCustomGame.load.generic.flags |= QMF_GRAYED; + else uiCustomGame.load.generic.flags &= ~QMF_GRAYED; + + if( strlen( uiCustomGame.modsWebSites[uiCustomGame.modList.curItem] ) == 0 ) + uiCustomGame.go2url.generic.flags |= QMF_GRAYED; + else uiCustomGame.go2url.generic.flags &= ~QMF_GRAYED; + return; + } + + if( event != QM_ACTIVATED ) + return; + + switch( item->id ) + { + case ID_DONE: + UI_PopMenu(); + break; + case ID_GOTOSITE: + if( strlen( uiCustomGame.modsWebSites[uiCustomGame.modList.curItem] )) + SHELL_EXECUTE( uiCustomGame.modsWebSites[uiCustomGame.modList.curItem], NULL, false ); + break; + case ID_ACTIVATE: + case ID_NO: + if ( CL_IsActive( )) + { + UI_EndGameDialog(); + break; // don't fuck up the game + } + case ID_YES: + // restart all engine systems with new game + char cmd[128]; + sprintf( cmd, "game %s\n", uiCustomGame.modsDir[uiCustomGame.modList.curItem] ); + CLIENT_COMMAND( FALSE, cmd ); + UI_EndGameDialog(); + break; + } +} + +/* +================= +UI_CustomGame_Init +================= +*/ +static void UI_CustomGame_Init( void ) +{ + memset( &uiCustomGame, 0, sizeof( uiCustomGame_t )); + + uiCustomGame.menu.vidInitFunc = UI_CustomGame_Init; + uiCustomGame.menu.keyFunc = UI_CustomGame_KeyFunc; + + StringConcat( uiCustomGame.hintText, "Type", TYPE_LENGTH ); + StringConcat( uiCustomGame.hintText, uiEmptyString, TYPE_LENGTH ); + StringConcat( uiCustomGame.hintText, "Name", NAME_LENGTH ); + StringConcat( uiCustomGame.hintText, uiEmptyString, NAME_LENGTH ); + StringConcat( uiCustomGame.hintText, "Version", VER_LENGTH ); + StringConcat( uiCustomGame.hintText, uiEmptyString, VER_LENGTH ); + StringConcat( uiCustomGame.hintText, "Size", SIZE_LENGTH ); + StringConcat( uiCustomGame.hintText, uiEmptyString, SIZE_LENGTH ); + + uiCustomGame.background.generic.id = ID_BACKGROUND; + uiCustomGame.background.generic.type = QMTYPE_BITMAP; + uiCustomGame.background.generic.flags = QMF_INACTIVE; + uiCustomGame.background.generic.x = 0; + uiCustomGame.background.generic.y = 0; + uiCustomGame.background.generic.width = 1024; + uiCustomGame.background.generic.height = 768; + uiCustomGame.background.pic = ART_BACKGROUND; + + uiCustomGame.banner.generic.id = ID_BANNER; + uiCustomGame.banner.generic.type = QMTYPE_BITMAP; + uiCustomGame.banner.generic.flags = QMF_INACTIVE|QMF_DRAW_ADDITIVE; + uiCustomGame.banner.generic.x = UI_BANNER_POSX; + uiCustomGame.banner.generic.y = UI_BANNER_POSY; + uiCustomGame.banner.generic.width = UI_BANNER_WIDTH; + uiCustomGame.banner.generic.height = UI_BANNER_HEIGHT; + uiCustomGame.banner.pic = ART_BANNER; + + uiCustomGame.load.generic.id = ID_ACTIVATE; + uiCustomGame.load.generic.type = QMTYPE_BM_BUTTON; + uiCustomGame.load.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiCustomGame.load.generic.x = 72; + uiCustomGame.load.generic.y = 230; + uiCustomGame.load.generic.name = "Activate"; + uiCustomGame.load.generic.statusText = "Activate selected custom game"; + uiCustomGame.load.generic.callback = UI_CustomGame_Callback; + + UI_UtilSetupPicButton( &uiCustomGame.load, PC_ACTIVATE ); + + uiCustomGame.go2url.generic.id = ID_GOTOSITE; + uiCustomGame.go2url.generic.type = QMTYPE_BM_BUTTON; + uiCustomGame.go2url.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiCustomGame.go2url.generic.x = 72; + uiCustomGame.go2url.generic.y = 280; + uiCustomGame.go2url.generic.name = "Visit web site"; + uiCustomGame.go2url.generic.statusText = "Visit the web site of game developrs"; + uiCustomGame.go2url.generic.callback = UI_CustomGame_Callback; + + UI_UtilSetupPicButton( &uiCustomGame.go2url, PC_VISIT_WEB_SITE ); + + uiCustomGame.done.generic.id = ID_DONE; + uiCustomGame.done.generic.type = QMTYPE_BM_BUTTON; + uiCustomGame.done.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiCustomGame.done.generic.x = 72; + uiCustomGame.done.generic.y = 330; + uiCustomGame.done.generic.name = "Done"; + uiCustomGame.done.generic.statusText = "Return to main menu"; + uiCustomGame.done.generic.callback = UI_CustomGame_Callback; + + UI_UtilSetupPicButton( &uiCustomGame.done, PC_DONE ); + + uiCustomGame.hintMessage.generic.id = ID_TABLEHINT; + uiCustomGame.hintMessage.generic.type = QMTYPE_ACTION; + uiCustomGame.hintMessage.generic.flags = QMF_INACTIVE|QMF_SMALLFONT; + uiCustomGame.hintMessage.generic.color = uiColorHelp; + uiCustomGame.hintMessage.generic.name = uiCustomGame.hintText; + uiCustomGame.hintMessage.generic.x = 360; + uiCustomGame.hintMessage.generic.y = 225; + + uiCustomGame.modList.generic.id = ID_MODLIST; + uiCustomGame.modList.generic.type = QMTYPE_SCROLLLIST; + uiCustomGame.modList.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_SMALLFONT; + uiCustomGame.modList.generic.x = 360; + uiCustomGame.modList.generic.y = 255; + uiCustomGame.modList.generic.width = 640; + uiCustomGame.modList.generic.height = 440; + uiCustomGame.modList.generic.callback = UI_CustomGame_Callback; + + uiCustomGame.msgBox.generic.id = ID_MSGBOX; + uiCustomGame.msgBox.generic.type = QMTYPE_ACTION; + uiCustomGame.msgBox.generic.flags = QMF_INACTIVE|QMF_HIDDEN; + uiCustomGame.msgBox.generic.ownerdraw = UI_MsgBox_Ownerdraw; // just a fill rectangle + uiCustomGame.msgBox.generic.x = 192; + uiCustomGame.msgBox.generic.y = 256; + uiCustomGame.msgBox.generic.width = 640; + uiCustomGame.msgBox.generic.height = 256; + + uiCustomGame.promptMessage.generic.id = ID_MSGBOX; + uiCustomGame.promptMessage.generic.type = QMTYPE_ACTION; + uiCustomGame.promptMessage.generic.flags = QMF_INACTIVE|QMF_DROPSHADOW|QMF_HIDDEN; + uiCustomGame.promptMessage.generic.name = "Leave current game?"; + uiCustomGame.promptMessage.generic.x = 315; + uiCustomGame.promptMessage.generic.y = 280; + + uiCustomGame.yes.generic.id = ID_YES; + uiCustomGame.yes.generic.type = QMTYPE_BM_BUTTON; + uiCustomGame.yes.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_HIDDEN; + uiCustomGame.yes.generic.name = "Ok"; + uiCustomGame.yes.generic.x = 380; + uiCustomGame.yes.generic.y = 460; + uiCustomGame.yes.generic.callback = UI_CustomGame_Callback; + + UI_UtilSetupPicButton( &uiCustomGame.yes, PC_OK ); + + uiCustomGame.no.generic.id = ID_NO; + uiCustomGame.no.generic.type = QMTYPE_BM_BUTTON; + uiCustomGame.no.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_HIDDEN; + uiCustomGame.no.generic.name = "Cancel"; + uiCustomGame.no.generic.x = 530; + uiCustomGame.no.generic.y = 460; + uiCustomGame.no.generic.callback = UI_CustomGame_Callback; + + UI_UtilSetupPicButton( &uiCustomGame.no, PC_CANCEL ); + + UI_CustomGame_GetModList(); + + UI_AddItem( &uiCustomGame.menu, (void *)&uiCustomGame.background ); + UI_AddItem( &uiCustomGame.menu, (void *)&uiCustomGame.banner ); + UI_AddItem( &uiCustomGame.menu, (void *)&uiCustomGame.load ); + UI_AddItem( &uiCustomGame.menu, (void *)&uiCustomGame.go2url ); + UI_AddItem( &uiCustomGame.menu, (void *)&uiCustomGame.done ); + UI_AddItem( &uiCustomGame.menu, (void *)&uiCustomGame.hintMessage ); + UI_AddItem( &uiCustomGame.menu, (void *)&uiCustomGame.modList ); + UI_AddItem( &uiCustomGame.menu, (void *)&uiCustomGame.msgBox ); + UI_AddItem( &uiCustomGame.menu, (void *)&uiCustomGame.promptMessage ); + UI_AddItem( &uiCustomGame.menu, (void *)&uiCustomGame.no ); + UI_AddItem( &uiCustomGame.menu, (void *)&uiCustomGame.yes ); +} + +/* +================= +UI_CustomGame_Precache +================= +*/ +void UI_CustomGame_Precache( void ) +{ + PIC_Load( ART_BACKGROUND ); + PIC_Load( ART_BANNER ); +} + +/* +================= +UI_CustomGame_Menu +================= +*/ +void UI_CustomGame_Menu( void ) +{ + // current instance is not support game change + if( !CVAR_GET_FLOAT( "host_allow_changegame" )) + return; + + UI_CustomGame_Precache(); + UI_CustomGame_Init(); + + UI_PushMenu( &uiCustomGame.menu ); +} \ No newline at end of file diff --git a/mainui/menu_gameoptions.cpp b/mainui/menu_gameoptions.cpp new file mode 100644 index 0000000..d857d2c --- /dev/null +++ b/mainui/menu_gameoptions.cpp @@ -0,0 +1,310 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "extdll.h" +#include "basemenu.h" +#include "utils.h" +#include "keydefs.h" +#include "menu_btnsbmp_table.h" + +#define ART_BANNER "gfx/shell/head_advoptions" + +#define ID_BACKGROUND 0 +#define ID_BANNER 1 + +#define ID_DONE 2 +#define ID_CANCEL 3 +#define ID_MAXFPS 4 +#define ID_MAXFPSMESSAGE 5 +#define ID_HAND 6 +#define ID_ALLOWDOWNLOAD 7 +#define ID_ALWAYSRUN 8 + +typedef struct +{ + float maxFPS; + int hand; + int allowDownload; + int alwaysRun; +} uiGameValues_t; + +typedef struct +{ + menuFramework_s menu; + + menuBitmap_s background; + menuBitmap_s banner; + + menuPicButton_s done; + menuPicButton_s cancel; + + menuSpinControl_s maxFPS; + menuAction_s maxFPSmessage; + menuCheckBox_s hand; + menuCheckBox_s allowDownload; + menuCheckBox_s alwaysRun; +} uiGameOptions_t; + +static uiGameOptions_t uiGameOptions; +static uiGameValues_t uiGameInitial; + +/* +================= +UI_GameOptions_UpdateConfig +================= +*/ +static void UI_GameOptions_UpdateConfig( void ) +{ + static char fpsText[8]; + + sprintf( fpsText, "%.f", uiGameOptions.maxFPS.curValue ); + uiGameOptions.maxFPS.generic.name = fpsText; + + CVAR_SET_FLOAT( "hand", uiGameOptions.hand.enabled ); + CVAR_SET_FLOAT( "sv_allow_download", uiGameOptions.allowDownload.enabled ); + CVAR_SET_FLOAT( "fps_max", uiGameOptions.maxFPS.curValue ); + CVAR_SET_FLOAT( "cl_run", uiGameOptions.alwaysRun.enabled ); +} + +/* +================= +UI_GameOptions_DiscardChanges +================= +*/ +static void UI_GameOptions_DiscardChanges( void ) +{ + CVAR_SET_FLOAT( "hand", uiGameInitial.hand ); + CVAR_SET_FLOAT( "sv_allow_download", uiGameInitial.allowDownload ); + CVAR_SET_FLOAT( "fps_max", uiGameInitial.maxFPS ); + CVAR_SET_FLOAT( "cl_run", uiGameInitial.alwaysRun ); +} + +/* +================= +UI_GameOptions_KeyFunc +================= +*/ +static const char *UI_GameOptions_KeyFunc( int key, int down ) +{ + if( down && key == K_ESCAPE ) + UI_GameOptions_DiscardChanges (); + return UI_DefaultKey( &uiGameOptions.menu, key, down ); +} + +/* +================= +UI_GameOptions_GetConfig +================= +*/ +static void UI_GameOptions_GetConfig( void ) +{ + uiGameInitial.maxFPS = uiGameOptions.maxFPS.curValue = CVAR_GET_FLOAT( "fps_max" ); + + if( CVAR_GET_FLOAT( "hand" )) + uiGameInitial.hand = uiGameOptions.hand.enabled = 1; + + if( CVAR_GET_FLOAT( "cl_run" )) + uiGameInitial.alwaysRun = uiGameOptions.alwaysRun.enabled = 1; + + if( CVAR_GET_FLOAT( "sv_allow_download" )) + uiGameInitial.allowDownload = uiGameOptions.allowDownload.enabled = 1; + + UI_GameOptions_UpdateConfig (); +} + +/* +================= +UI_GameOptions_Callback +================= +*/ +static void UI_GameOptions_Callback( void *self, int event ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + switch( item->id ) + { + case ID_HAND: + case ID_ALLOWDOWNLOAD: + case ID_ALWAYSRUN: + if( event == QM_PRESSED ) + ((menuCheckBox_s *)self)->focusPic = UI_CHECKBOX_PRESSED; + else ((menuCheckBox_s *)self)->focusPic = UI_CHECKBOX_FOCUS; + break; + } + + if( event == QM_CHANGED ) + { + UI_GameOptions_UpdateConfig(); + return; + } + + if( event != QM_ACTIVATED ) + return; + + switch( item->id ) + { + case ID_DONE: + UI_PopMenu(); + break; + case ID_CANCEL: + UI_GameOptions_DiscardChanges(); + UI_PopMenu(); + break; + } +} + +/* +================= +UI_GameOptions_Init +================= +*/ +static void UI_GameOptions_Init( void ) +{ + memset( &uiGameInitial, 0, sizeof( uiGameValues_t )); + memset( &uiGameOptions, 0, sizeof( uiGameOptions_t )); + + uiGameOptions.menu.vidInitFunc = UI_GameOptions_Init; + uiGameOptions.menu.keyFunc = UI_GameOptions_KeyFunc; + + uiGameOptions.background.generic.id = ID_BACKGROUND; + uiGameOptions.background.generic.type = QMTYPE_BITMAP; + uiGameOptions.background.generic.flags = QMF_INACTIVE; + uiGameOptions.background.generic.x = 0; + uiGameOptions.background.generic.y = 0; + uiGameOptions.background.generic.width = 1024; + uiGameOptions.background.generic.height = 768; + uiGameOptions.background.pic = ART_BACKGROUND; + + uiGameOptions.banner.generic.id = ID_BANNER; + uiGameOptions.banner.generic.type = QMTYPE_BITMAP; + uiGameOptions.banner.generic.flags = QMF_INACTIVE|QMF_DRAW_ADDITIVE; + uiGameOptions.banner.generic.x = UI_BANNER_POSX; + uiGameOptions.banner.generic.y = UI_BANNER_POSY; + uiGameOptions.banner.generic.width = UI_BANNER_WIDTH; + uiGameOptions.banner.generic.height = UI_BANNER_HEIGHT; + uiGameOptions.banner.pic = ART_BANNER; + + uiGameOptions.done.generic.id = ID_DONE; + uiGameOptions.done.generic.type = QMTYPE_BM_BUTTON; + uiGameOptions.done.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiGameOptions.done.generic.x = 72; + uiGameOptions.done.generic.y = 230; + uiGameOptions.done.generic.name = "Done"; + uiGameOptions.done.generic.statusText = "Save changes and go back to the Customize Menu"; + uiGameOptions.done.generic.callback = UI_GameOptions_Callback; + + UI_UtilSetupPicButton( &uiGameOptions.done, PC_DONE ); + + uiGameOptions.cancel.generic.id = ID_CANCEL; + uiGameOptions.cancel.generic.type = QMTYPE_BM_BUTTON; + uiGameOptions.cancel.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiGameOptions.cancel.generic.x = 72; + uiGameOptions.cancel.generic.y = 280; + uiGameOptions.cancel.generic.name = "Cancel"; + uiGameOptions.cancel.generic.statusText = "Go back to the Customize Menu"; + uiGameOptions.cancel.generic.callback = UI_GameOptions_Callback; + + UI_UtilSetupPicButton( &uiGameOptions.cancel, PC_CANCEL ); + + uiGameOptions.maxFPS.generic.id = ID_MAXFPS; + uiGameOptions.maxFPS.generic.type = QMTYPE_SPINCONTROL; + uiGameOptions.maxFPS.generic.flags = QMF_CENTER_JUSTIFY|QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiGameOptions.maxFPS.generic.x = 315; + uiGameOptions.maxFPS.generic.y = 270; + uiGameOptions.maxFPS.generic.width = 168; + uiGameOptions.maxFPS.generic.height = 26; + uiGameOptions.maxFPS.generic.callback = UI_GameOptions_Callback; + uiGameOptions.maxFPS.generic.statusText = "Cap your game frame rate"; + uiGameOptions.maxFPS.minValue = 20; + uiGameOptions.maxFPS.maxValue = 500; + uiGameOptions.maxFPS.range = 20; + + uiGameOptions.maxFPSmessage.generic.id = ID_MAXFPSMESSAGE; + uiGameOptions.maxFPSmessage.generic.type = QMTYPE_ACTION; + uiGameOptions.maxFPSmessage.generic.flags = QMF_SMALLFONT|QMF_INACTIVE|QMF_DROPSHADOW; + uiGameOptions.maxFPSmessage.generic.x = 280; + uiGameOptions.maxFPSmessage.generic.y = 230; + uiGameOptions.maxFPSmessage.generic.name = "Limit game fps"; + uiGameOptions.maxFPSmessage.generic.color = uiColorHelp; + + uiGameOptions.hand.generic.id = ID_HAND; + uiGameOptions.hand.generic.type = QMTYPE_CHECKBOX; + uiGameOptions.hand.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_ACT_ONRELEASE|QMF_MOUSEONLY|QMF_DROPSHADOW; + uiGameOptions.hand.generic.x = 280; + uiGameOptions.hand.generic.y = 330; + uiGameOptions.hand.generic.name = "Use left hand"; + uiGameOptions.hand.generic.callback = UI_GameOptions_Callback; + uiGameOptions.hand.generic.statusText = "Draw gun at left side"; + + uiGameOptions.allowDownload.generic.id = ID_ALLOWDOWNLOAD; + uiGameOptions.allowDownload.generic.type = QMTYPE_CHECKBOX; + uiGameOptions.allowDownload.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_ACT_ONRELEASE|QMF_MOUSEONLY|QMF_DROPSHADOW; + uiGameOptions.allowDownload.generic.x = 280; + uiGameOptions.allowDownload.generic.y = 390; + uiGameOptions.allowDownload.generic.name = "Allow download"; + uiGameOptions.allowDownload.generic.callback = UI_GameOptions_Callback; + uiGameOptions.allowDownload.generic.statusText = "Allow download of files from servers"; + + uiGameOptions.alwaysRun.generic.id = ID_ALWAYSRUN; + uiGameOptions.alwaysRun.generic.type = QMTYPE_CHECKBOX; + uiGameOptions.alwaysRun.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_ACT_ONRELEASE|QMF_MOUSEONLY|QMF_DROPSHADOW; + uiGameOptions.alwaysRun.generic.x = 280; + uiGameOptions.alwaysRun.generic.y = 450; + uiGameOptions.alwaysRun.generic.name = "Always run"; + uiGameOptions.alwaysRun.generic.callback = UI_GameOptions_Callback; + uiGameOptions.alwaysRun.generic.statusText = "Switch between run/step models when pressed 'run' button"; + + UI_GameOptions_GetConfig(); + + UI_AddItem( &uiGameOptions.menu, (void *)&uiGameOptions.background ); + UI_AddItem( &uiGameOptions.menu, (void *)&uiGameOptions.banner ); + UI_AddItem( &uiGameOptions.menu, (void *)&uiGameOptions.done ); + UI_AddItem( &uiGameOptions.menu, (void *)&uiGameOptions.cancel ); + UI_AddItem( &uiGameOptions.menu, (void *)&uiGameOptions.maxFPS ); + UI_AddItem( &uiGameOptions.menu, (void *)&uiGameOptions.maxFPSmessage ); + UI_AddItem( &uiGameOptions.menu, (void *)&uiGameOptions.hand ); + UI_AddItem( &uiGameOptions.menu, (void *)&uiGameOptions.alwaysRun ); + UI_AddItem( &uiGameOptions.menu, (void *)&uiGameOptions.allowDownload ); +} + +/* +================= +UI_GameOptions_Precache +================= +*/ +void UI_GameOptions_Precache( void ) +{ + PIC_Load( ART_BACKGROUND ); + PIC_Load( ART_BANNER ); +} + +/* +================= +UI_GameOptions_Menu +================= +*/ +void UI_GameOptions_Menu( void ) +{ + UI_GameOptions_Precache(); + UI_GameOptions_Init(); + + UI_GameOptions_UpdateConfig(); + UI_PushMenu( &uiGameOptions.menu ); +} \ No newline at end of file diff --git a/mainui/menu_internetgames.cpp b/mainui/menu_internetgames.cpp new file mode 100644 index 0000000..e8b45f4 --- /dev/null +++ b/mainui/menu_internetgames.cpp @@ -0,0 +1,476 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "extdll.h" +#include "basemenu.h" +#include "utils.h" +#include "keydefs.h" +#include "menu_btnsbmp_table.h" + +#define ART_BANNER "gfx/shell/head_inetgames" + +#define ID_BACKGROUND 0 +#define ID_BANNER 1 +#define ID_JOINGAME 2 +#define ID_CREATEGAME 3 +#define ID_GAMEINFO 4 +#define ID_REFRESH 5 +#define ID_DONE 6 +#define ID_SERVERSLIST 7 +#define ID_TABLEHINT 8 + +#define ID_MSGBOX 9 +#define ID_MSGTEXT 10 +#define ID_YES 130 +#define ID_NO 131 + +#define GAME_LENGTH 28 +#define MAPNAME_LENGTH 20+GAME_LENGTH +#define TYPE_LENGTH 10+MAPNAME_LENGTH +#define MAXCL_LENGTH 15+TYPE_LENGTH + +typedef struct +{ + char gameDescription[UI_MAX_SERVERS][256]; + char *gameDescriptionPtr[UI_MAX_SERVERS]; + + menuFramework_s menu; + + menuBitmap_s background; + menuBitmap_s banner; + menuPicButton_s joinGame; + menuPicButton_s createGame; + menuPicButton_s gameInfo; + menuPicButton_s refresh; + menuPicButton_s done; + + // joingame prompt dialog + menuAction_s msgBox; + menuAction_s dlgMessage1; + menuAction_s dlgMessage2; + menuPicButton_s yes; + menuPicButton_s no; + + menuScrollList_s gameList; + menuAction_s hintMessage; + char hintText[MAX_HINT_TEXT]; + int refreshTime; +} uiInternetGames_t; + +static uiInternetGames_t uiInternetGames; + +static void UI_PromptDialog( void ) +{ + // toggle main menu between active\inactive + // show\hide quit dialog + uiInternetGames.joinGame.generic.flags ^= QMF_INACTIVE; + uiInternetGames.createGame.generic.flags ^= QMF_INACTIVE; + uiInternetGames.gameInfo.generic.flags ^= QMF_INACTIVE; + uiInternetGames.refresh.generic.flags ^= QMF_INACTIVE; + uiInternetGames.done.generic.flags ^= QMF_INACTIVE; + uiInternetGames.gameList.generic.flags ^= QMF_INACTIVE; + + uiInternetGames.msgBox.generic.flags ^= QMF_HIDDEN; + uiInternetGames.dlgMessage1.generic.flags ^= QMF_HIDDEN; + uiInternetGames.dlgMessage2.generic.flags ^= QMF_HIDDEN; + uiInternetGames.no.generic.flags ^= QMF_HIDDEN; + uiInternetGames.yes.generic.flags ^= QMF_HIDDEN; + +} + +/* +================= +UI_InternetGames_KeyFunc +================= +*/ +static const char *UI_InternetGames_KeyFunc( int key, int down ) +{ + if( down && key == K_ESCAPE && !( uiInternetGames.dlgMessage1.generic.flags & QMF_HIDDEN )) + { + UI_PromptDialog(); + return uiSoundNull; + } + return UI_DefaultKey( &uiInternetGames.menu, key, down ); +} + +/* +================= +UI_InternetGames_GetGamesList +================= +*/ +static void UI_InternetGames_GetGamesList( void ) +{ + int i; + const char *info, *host, *map; + int colorOffset[2]; + + for( i = 0; i < uiStatic.numServers; i++ ) + { + if( i >= UI_MAX_SERVERS ) break; + info = uiStatic.serverNames[i]; + + host = Info_ValueForKey( info, "host" ); + colorOffset[0] = ColorPrexfixCount( host ); + StringConcat( uiInternetGames.gameDescription[i], host, GAME_LENGTH ); + StringConcat( uiInternetGames.gameDescription[i], uiEmptyString, GAME_LENGTH + colorOffset[0] ); + map = Info_ValueForKey( info, "map" ); + colorOffset[1] = ColorPrexfixCount( map ); + StringConcat( uiInternetGames.gameDescription[i], map, MAPNAME_LENGTH ); + StringConcat( uiInternetGames.gameDescription[i], uiEmptyString, MAPNAME_LENGTH + colorOffset[0] + colorOffset[1] ); + if( !strcmp( Info_ValueForKey( info, "dm" ), "1" )) + StringConcat( uiInternetGames.gameDescription[i], "dm", TYPE_LENGTH ); + else if( !strcmp( Info_ValueForKey( info, "coop" ), "1" )) + StringConcat( uiInternetGames.gameDescription[i], "coop", TYPE_LENGTH ); + else if( !strcmp( Info_ValueForKey( info, "team" ), "1" )) + StringConcat( uiInternetGames.gameDescription[i], "team", TYPE_LENGTH ); + else StringConcat( uiInternetGames.gameDescription[i], "???", TYPE_LENGTH ); + StringConcat( uiInternetGames.gameDescription[i], uiEmptyString, TYPE_LENGTH + colorOffset[0] + colorOffset[1] ); + StringConcat( uiInternetGames.gameDescription[i], Info_ValueForKey( info, "numcl" ), MAXCL_LENGTH ); + StringConcat( uiInternetGames.gameDescription[i], "\\", MAXCL_LENGTH ); + StringConcat( uiInternetGames.gameDescription[i], Info_ValueForKey( info, "maxcl" ), MAXCL_LENGTH ); + StringConcat( uiInternetGames.gameDescription[i], uiEmptyString, MAXCL_LENGTH ); + uiInternetGames.gameDescriptionPtr[i] = uiInternetGames.gameDescription[i]; + } + + for( ; i < UI_MAX_SERVERS; i++ ) + uiInternetGames.gameDescriptionPtr[i] = NULL; + + uiInternetGames.gameList.itemNames = (const char **)uiInternetGames.gameDescriptionPtr; + uiInternetGames.gameList.numItems = 0; // reset it + + if( !uiInternetGames.gameList.generic.charHeight ) + return; // to avoid divide integer by zero + + // count number of items + while( uiInternetGames.gameList.itemNames[uiInternetGames.gameList.numItems] ) + uiInternetGames.gameList.numItems++; + + // calculate number of visible rows + uiInternetGames.gameList.numRows = (uiInternetGames.gameList.generic.height2 / uiInternetGames.gameList.generic.charHeight) - 2; + if( uiInternetGames.gameList.numRows > uiInternetGames.gameList.numItems ) uiInternetGames.gameList.numRows = uiInternetGames.gameList.numItems; + + if( uiStatic.numServers ) + uiInternetGames.joinGame.generic.flags &= ~QMF_GRAYED; +} + +/* +================= +UI_InternetGames_JoinGame +================= +*/ +static void UI_InternetGames_JoinGame( void ) +{ + if( !strlen( uiInternetGames.gameDescription[uiInternetGames.gameList.curItem] )) + return; + + CLIENT_JOIN( uiStatic.serverAddresses[uiInternetGames.gameList.curItem] ); +} + +/* +================= +UI_Background_Ownerdraw +================= +*/ +static void UI_Background_Ownerdraw( void *self ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + if( !CVAR_GET_FLOAT( "cl_background" )) + UI_DrawBackground_Callback( self ); + + if( uiStatic.realTime > uiInternetGames.refreshTime ) + { + uiInternetGames.refreshTime = uiStatic.realTime + 10000; // refresh every 10 secs + UI_RefreshInternetServerList(); + } + + // serverinfo has been changed update display + if( uiStatic.updateServers ) + { + UI_InternetGames_GetGamesList (); + uiStatic.updateServers = false; + } +} + +/* +================= +UI_MsgBox_Ownerdraw +================= +*/ +static void UI_MsgBox_Ownerdraw( void *self ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + UI_FillRect( item->x, item->y, item->width, item->height, uiPromptBgColor ); +} + +/* +================= +UI_InternetGames_Callback +================= +*/ +static void UI_InternetGames_Callback( void *self, int event ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + if( event != QM_ACTIVATED ) + return; + + switch( item->id ) + { + case ID_JOINGAME: + if( CL_IsActive( )) + UI_PromptDialog(); + else UI_InternetGames_JoinGame(); + break; + case ID_CREATEGAME: + CVAR_SET_FLOAT( "public", 1.0f ); + UI_CreateGame_Menu(); + break; + case ID_GAMEINFO: + // UNDONE: not implemented + break; + case ID_REFRESH: + UI_RefreshInternetServerList(); + break; + case ID_DONE: + UI_PopMenu(); + break; + case ID_YES: + UI_InternetGames_JoinGame(); + break; + case ID_NO: + UI_PromptDialog(); + break; + } +} + +/* +================= +UI_InternetGames_Init +================= +*/ +static void UI_InternetGames_Init( void ) +{ + memset( &uiInternetGames, 0, sizeof( uiInternetGames_t )); + + uiInternetGames.menu.vidInitFunc = UI_InternetGames_Init; + uiInternetGames.menu.keyFunc = UI_InternetGames_KeyFunc; + + StringConcat( uiInternetGames.hintText, "Game", GAME_LENGTH ); + StringConcat( uiInternetGames.hintText, uiEmptyString, GAME_LENGTH ); + StringConcat( uiInternetGames.hintText, "Map", MAPNAME_LENGTH ); + StringConcat( uiInternetGames.hintText, uiEmptyString, MAPNAME_LENGTH ); + StringConcat( uiInternetGames.hintText, "Type", TYPE_LENGTH ); + StringConcat( uiInternetGames.hintText, uiEmptyString, TYPE_LENGTH ); + StringConcat( uiInternetGames.hintText, "Clients", MAXCL_LENGTH ); + StringConcat( uiInternetGames.hintText, uiEmptyString, MAXCL_LENGTH ); + + uiInternetGames.background.generic.id = ID_BACKGROUND; + uiInternetGames.background.generic.type = QMTYPE_BITMAP; + uiInternetGames.background.generic.flags = QMF_INACTIVE; + uiInternetGames.background.generic.x = 0; + uiInternetGames.background.generic.y = 0; + uiInternetGames.background.generic.width = 1024; + uiInternetGames.background.generic.height = 768; + uiInternetGames.background.pic = ART_BACKGROUND; + uiInternetGames.background.generic.ownerdraw = UI_Background_Ownerdraw; + + uiInternetGames.banner.generic.id = ID_BANNER; + uiInternetGames.banner.generic.type = QMTYPE_BITMAP; + uiInternetGames.banner.generic.flags = QMF_INACTIVE|QMF_DRAW_ADDITIVE; + uiInternetGames.banner.generic.x = UI_BANNER_POSX; + uiInternetGames.banner.generic.y = UI_BANNER_POSY; + uiInternetGames.banner.generic.width = UI_BANNER_WIDTH; + uiInternetGames.banner.generic.height = UI_BANNER_HEIGHT; + uiInternetGames.banner.pic = ART_BANNER; + + uiInternetGames.joinGame.generic.id = ID_JOINGAME; + uiInternetGames.joinGame.generic.type = QMTYPE_BM_BUTTON; + uiInternetGames.joinGame.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_GRAYED; + uiInternetGames.joinGame.generic.x = 72; + uiInternetGames.joinGame.generic.y = 230; + uiInternetGames.joinGame.generic.name = "Join game"; + uiInternetGames.joinGame.generic.statusText = "Join to selected game"; + uiInternetGames.joinGame.generic.callback = UI_InternetGames_Callback; + + UI_UtilSetupPicButton( &uiInternetGames.joinGame, PC_JOIN_GAME ); + + uiInternetGames.createGame.generic.id = ID_CREATEGAME; + uiInternetGames.createGame.generic.type = QMTYPE_BM_BUTTON; + uiInternetGames.createGame.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiInternetGames.createGame.generic.x = 72; + uiInternetGames.createGame.generic.y = 280; + uiInternetGames.createGame.generic.name = "Create game"; + uiInternetGames.createGame.generic.statusText = "Create new Internet game"; + uiInternetGames.createGame.generic.callback = UI_InternetGames_Callback; + + UI_UtilSetupPicButton( &uiInternetGames.createGame, PC_CREATE_GAME ); + + uiInternetGames.gameInfo.generic.id = ID_GAMEINFO; + uiInternetGames.gameInfo.generic.type = QMTYPE_BM_BUTTON; + uiInternetGames.gameInfo.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_GRAYED; + uiInternetGames.gameInfo.generic.x = 72; + uiInternetGames.gameInfo.generic.y = 330; + uiInternetGames.gameInfo.generic.name = "View game info"; + uiInternetGames.gameInfo.generic.statusText = "Get detail game info"; + uiInternetGames.gameInfo.generic.callback = UI_InternetGames_Callback; + + UI_UtilSetupPicButton( &uiInternetGames.gameInfo, PC_VIEW_GAME_INFO ); + + uiInternetGames.refresh.generic.id = ID_REFRESH; + uiInternetGames.refresh.generic.type = QMTYPE_BM_BUTTON; + uiInternetGames.refresh.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiInternetGames.refresh.generic.x = 72; + uiInternetGames.refresh.generic.y = 380; + uiInternetGames.refresh.generic.name = "Refresh"; + uiInternetGames.refresh.generic.statusText = "Refresh servers list"; + uiInternetGames.refresh.generic.callback = UI_InternetGames_Callback; + + UI_UtilSetupPicButton( &uiInternetGames.refresh, PC_REFRESH ); + + uiInternetGames.done.generic.id = ID_DONE; + uiInternetGames.done.generic.type = QMTYPE_BM_BUTTON; + uiInternetGames.done.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiInternetGames.done.generic.x = 72; + uiInternetGames.done.generic.y = 430; + uiInternetGames.done.generic.name = "Done"; + uiInternetGames.done.generic.statusText = "Return to main menu"; + uiInternetGames.done.generic.callback = UI_InternetGames_Callback; + + UI_UtilSetupPicButton( &uiInternetGames.done, PC_DONE ); + + uiInternetGames.msgBox.generic.id = ID_MSGBOX; + uiInternetGames.msgBox.generic.type = QMTYPE_ACTION; + uiInternetGames.msgBox.generic.flags = QMF_INACTIVE|QMF_HIDDEN; + uiInternetGames.msgBox.generic.ownerdraw = UI_MsgBox_Ownerdraw; // just a fill rectangle + uiInternetGames.msgBox.generic.x = 192; + uiInternetGames.msgBox.generic.y = 256; + uiInternetGames.msgBox.generic.width = 640; + uiInternetGames.msgBox.generic.height = 256; + + uiInternetGames.dlgMessage1.generic.id = ID_MSGTEXT; + uiInternetGames.dlgMessage1.generic.type = QMTYPE_ACTION; + uiInternetGames.dlgMessage1.generic.flags = QMF_INACTIVE|QMF_HIDDEN|QMF_DROPSHADOW; + uiInternetGames.dlgMessage1.generic.name = "Join a network game will exit"; + uiInternetGames.dlgMessage1.generic.x = 248; + uiInternetGames.dlgMessage1.generic.y = 280; + + uiInternetGames.dlgMessage2.generic.id = ID_MSGTEXT; + uiInternetGames.dlgMessage2.generic.type = QMTYPE_ACTION; + uiInternetGames.dlgMessage2.generic.flags = QMF_INACTIVE|QMF_HIDDEN|QMF_DROPSHADOW; + uiInternetGames.dlgMessage2.generic.name = "any current game, OK to exit?"; + uiInternetGames.dlgMessage2.generic.x = 248; + uiInternetGames.dlgMessage2.generic.y = 310; + + uiInternetGames.yes.generic.id = ID_YES; + uiInternetGames.yes.generic.type = QMTYPE_BM_BUTTON; + uiInternetGames.yes.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_HIDDEN|QMF_DROPSHADOW; + uiInternetGames.yes.generic.name = "Ok"; + uiInternetGames.yes.generic.x = 380; + uiInternetGames.yes.generic.y = 460; + uiInternetGames.yes.generic.callback = UI_InternetGames_Callback; + + UI_UtilSetupPicButton( &uiInternetGames.yes, PC_OK ); + + uiInternetGames.no.generic.id = ID_NO; + uiInternetGames.no.generic.type = QMTYPE_BM_BUTTON; + uiInternetGames.no.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_HIDDEN|QMF_DROPSHADOW; + uiInternetGames.no.generic.name = "Cancel"; + uiInternetGames.no.generic.x = 530; + uiInternetGames.no.generic.y = 460; + uiInternetGames.no.generic.callback = UI_InternetGames_Callback; + + UI_UtilSetupPicButton( &uiInternetGames.no, PC_CANCEL ); + + uiInternetGames.hintMessage.generic.id = ID_TABLEHINT; + uiInternetGames.hintMessage.generic.type = QMTYPE_ACTION; + uiInternetGames.hintMessage.generic.flags = QMF_INACTIVE|QMF_SMALLFONT; + uiInternetGames.hintMessage.generic.color = uiColorHelp; + uiInternetGames.hintMessage.generic.name = uiInternetGames.hintText; + uiInternetGames.hintMessage.generic.x = 360; + uiInternetGames.hintMessage.generic.y = 225; + + uiInternetGames.gameList.generic.id = ID_SERVERSLIST; + uiInternetGames.gameList.generic.type = QMTYPE_SCROLLLIST; + uiInternetGames.gameList.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_SMALLFONT; + uiInternetGames.gameList.generic.x = 360; + uiInternetGames.gameList.generic.y = 255; + uiInternetGames.gameList.generic.width = 640; + uiInternetGames.gameList.generic.height = 440; + uiInternetGames.gameList.generic.callback = UI_InternetGames_Callback; + uiInternetGames.gameList.itemNames = (const char **)uiInternetGames.gameDescriptionPtr; + + // server.dll needs for reading savefiles or startup newgame + if( !CheckGameDll( )) + uiInternetGames.createGame.generic.flags |= QMF_GRAYED; // server.dll is missed - remote servers only + + UI_AddItem( &uiInternetGames.menu, (void *)&uiInternetGames.background ); + UI_AddItem( &uiInternetGames.menu, (void *)&uiInternetGames.banner ); + UI_AddItem( &uiInternetGames.menu, (void *)&uiInternetGames.joinGame ); + UI_AddItem( &uiInternetGames.menu, (void *)&uiInternetGames.createGame ); + UI_AddItem( &uiInternetGames.menu, (void *)&uiInternetGames.gameInfo ); + UI_AddItem( &uiInternetGames.menu, (void *)&uiInternetGames.refresh ); + UI_AddItem( &uiInternetGames.menu, (void *)&uiInternetGames.done ); + UI_AddItem( &uiInternetGames.menu, (void *)&uiInternetGames.hintMessage ); + UI_AddItem( &uiInternetGames.menu, (void *)&uiInternetGames.gameList ); + UI_AddItem( &uiInternetGames.menu, (void *)&uiInternetGames.msgBox ); + UI_AddItem( &uiInternetGames.menu, (void *)&uiInternetGames.dlgMessage1 ); + UI_AddItem( &uiInternetGames.menu, (void *)&uiInternetGames.dlgMessage2 ); + UI_AddItem( &uiInternetGames.menu, (void *)&uiInternetGames.no ); + UI_AddItem( &uiInternetGames.menu, (void *)&uiInternetGames.yes ); + + uiInternetGames.refreshTime = uiStatic.realTime + 500; // delay before update 0.5 sec +} + +/* +================= +UI_InternetGames_Precache +================= +*/ +void UI_InternetGames_Precache( void ) +{ + PIC_Load( ART_BACKGROUND ); + PIC_Load( ART_BANNER ); +} + +/* +================= +UI_InternetGames_Menu +================= +*/ +void UI_InternetGames_Menu( void ) +{ + if ( gMenu.m_gameinfo.gamemode == GAME_SINGLEPLAYER_ONLY ) + return; + + // stop demos to allow open network sockets + if ( gpGlobals->demoplayback && CVAR_GET_FLOAT( "cl_background" )) + { + uiStatic.m_iOldMenuDepth = uiStatic.menuDepth; + CLIENT_COMMAND( FALSE, "stop\n" ); + uiStatic.m_fDemosPlayed = true; + } + + UI_InternetGames_Precache(); + UI_InternetGames_Init(); + + UI_PushMenu( &uiInternetGames.menu ); +} \ No newline at end of file diff --git a/mainui/menu_langame.cpp b/mainui/menu_langame.cpp new file mode 100644 index 0000000..1b4b65f --- /dev/null +++ b/mainui/menu_langame.cpp @@ -0,0 +1,476 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "extdll.h" +#include "basemenu.h" +#include "utils.h" +#include "keydefs.h" +#include "menu_btnsbmp_table.h" + +#define ART_BANNER "gfx/shell/head_lan" + +#define ID_BACKGROUND 0 +#define ID_BANNER 1 +#define ID_JOINGAME 2 +#define ID_CREATEGAME 3 +#define ID_GAMEINFO 4 +#define ID_REFRESH 5 +#define ID_DONE 6 +#define ID_SERVERSLIST 7 +#define ID_TABLEHINT 8 + +#define ID_MSGBOX 9 +#define ID_MSGTEXT 10 +#define ID_YES 130 +#define ID_NO 131 + +#define GAME_LENGTH 28 +#define MAPNAME_LENGTH 20+GAME_LENGTH +#define TYPE_LENGTH 10+MAPNAME_LENGTH +#define MAXCL_LENGTH 15+TYPE_LENGTH + +typedef struct +{ + char gameDescription[UI_MAX_SERVERS][256]; + char *gameDescriptionPtr[UI_MAX_SERVERS]; + + menuFramework_s menu; + + menuBitmap_s background; + menuBitmap_s banner; + menuPicButton_s joinGame; + menuPicButton_s createGame; + menuPicButton_s gameInfo; + menuPicButton_s refresh; + menuPicButton_s done; + + // joingame prompt dialog + menuAction_s msgBox; + menuAction_s dlgMessage1; + menuAction_s dlgMessage2; + menuPicButton_s yes; + menuPicButton_s no; + + menuScrollList_s gameList; + menuAction_s hintMessage; + char hintText[MAX_HINT_TEXT]; + int refreshTime; +} uiLanGame_t; + +static uiLanGame_t uiLanGame; + +static void UI_PromptDialog( void ) +{ + // toggle main menu between active\inactive + // show\hide quit dialog + uiLanGame.joinGame.generic.flags ^= QMF_INACTIVE; + uiLanGame.createGame.generic.flags ^= QMF_INACTIVE; + uiLanGame.gameInfo.generic.flags ^= QMF_INACTIVE; + uiLanGame.refresh.generic.flags ^= QMF_INACTIVE; + uiLanGame.done.generic.flags ^= QMF_INACTIVE; + uiLanGame.gameList.generic.flags ^= QMF_INACTIVE; + + uiLanGame.msgBox.generic.flags ^= QMF_HIDDEN; + uiLanGame.dlgMessage1.generic.flags ^= QMF_HIDDEN; + uiLanGame.dlgMessage2.generic.flags ^= QMF_HIDDEN; + uiLanGame.no.generic.flags ^= QMF_HIDDEN; + uiLanGame.yes.generic.flags ^= QMF_HIDDEN; + +} + +/* +================= +UI_LanGame_KeyFunc +================= +*/ +static const char *UI_LanGame_KeyFunc( int key, int down ) +{ + if( down && key == K_ESCAPE && !( uiLanGame.dlgMessage1.generic.flags & QMF_HIDDEN )) + { + UI_PromptDialog(); + return uiSoundNull; + } + return UI_DefaultKey( &uiLanGame.menu, key, down ); +} + +/* +================= +UI_LanGame_GetGamesList +================= +*/ +static void UI_LanGame_GetGamesList( void ) +{ + int i; + const char *info, *host, *map; + int colorOffset[2]; + + for( i = 0; i < uiStatic.numServers; i++ ) + { + if( i >= UI_MAX_SERVERS ) break; + info = uiStatic.serverNames[i]; + + host = Info_ValueForKey( info, "host" ); + colorOffset[0] = ColorPrexfixCount( host ); + StringConcat( uiLanGame.gameDescription[i], host, GAME_LENGTH ); + StringConcat( uiLanGame.gameDescription[i], uiEmptyString, GAME_LENGTH + colorOffset[0] ); + map = Info_ValueForKey( info, "map" ); + colorOffset[1] = ColorPrexfixCount( map ); + StringConcat( uiLanGame.gameDescription[i], map, MAPNAME_LENGTH ); + StringConcat( uiLanGame.gameDescription[i], uiEmptyString, MAPNAME_LENGTH + colorOffset[0] + colorOffset[1] ); + if( !strcmp( Info_ValueForKey( info, "dm" ), "1" )) + StringConcat( uiLanGame.gameDescription[i], "dm", TYPE_LENGTH ); + else if( !strcmp( Info_ValueForKey( info, "coop" ), "1" )) + StringConcat( uiLanGame.gameDescription[i], "coop", TYPE_LENGTH ); + else if( !strcmp( Info_ValueForKey( info, "team" ), "1" )) + StringConcat( uiLanGame.gameDescription[i], "team", TYPE_LENGTH ); + else StringConcat( uiLanGame.gameDescription[i], "???", TYPE_LENGTH ); + StringConcat( uiLanGame.gameDescription[i], uiEmptyString, TYPE_LENGTH + colorOffset[0] + colorOffset[1] ); + StringConcat( uiLanGame.gameDescription[i], Info_ValueForKey( info, "numcl" ), MAXCL_LENGTH ); + StringConcat( uiLanGame.gameDescription[i], "\\", MAXCL_LENGTH ); + StringConcat( uiLanGame.gameDescription[i], Info_ValueForKey( info, "maxcl" ), MAXCL_LENGTH ); + StringConcat( uiLanGame.gameDescription[i], uiEmptyString, MAXCL_LENGTH ); + uiLanGame.gameDescriptionPtr[i] = uiLanGame.gameDescription[i]; + } + + for( ; i < UI_MAX_SERVERS; i++ ) + uiLanGame.gameDescriptionPtr[i] = NULL; + + uiLanGame.gameList.itemNames = (const char **)uiLanGame.gameDescriptionPtr; + uiLanGame.gameList.numItems = 0; // reset it + + if( !uiLanGame.gameList.generic.charHeight ) + return; // to avoid divide integer by zero + + // count number of items + while( uiLanGame.gameList.itemNames[uiLanGame.gameList.numItems] ) + uiLanGame.gameList.numItems++; + + // calculate number of visible rows + uiLanGame.gameList.numRows = (uiLanGame.gameList.generic.height2 / uiLanGame.gameList.generic.charHeight) - 2; + if( uiLanGame.gameList.numRows > uiLanGame.gameList.numItems ) uiLanGame.gameList.numRows = uiLanGame.gameList.numItems; + + if( uiStatic.numServers ) + uiLanGame.joinGame.generic.flags &= ~QMF_GRAYED; +} + +/* +================= +UI_LanGame_JoinGame +================= +*/ +static void UI_LanGame_JoinGame( void ) +{ + if( !strlen( uiLanGame.gameDescription[uiLanGame.gameList.curItem] )) + return; + + CLIENT_JOIN( uiStatic.serverAddresses[uiLanGame.gameList.curItem] ); +} + +/* +================= +UI_Background_Ownerdraw +================= +*/ +static void UI_Background_Ownerdraw( void *self ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + if( !CVAR_GET_FLOAT( "cl_background" )) + UI_DrawBackground_Callback( self ); + + if( uiStatic.realTime > uiLanGame.refreshTime ) + { + uiLanGame.refreshTime = uiStatic.realTime + 10000; // refresh every 10 secs + UI_RefreshServerList(); + } + + // serverinfo has been changed update display + if( uiStatic.updateServers ) + { + UI_LanGame_GetGamesList (); + uiStatic.updateServers = false; + } +} + +/* +================= +UI_MsgBox_Ownerdraw +================= +*/ +static void UI_MsgBox_Ownerdraw( void *self ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + UI_FillRect( item->x, item->y, item->width, item->height, uiPromptBgColor ); +} + +/* +================= +UI_LanGame_Callback +================= +*/ +static void UI_LanGame_Callback( void *self, int event ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + if( event != QM_ACTIVATED ) + return; + + switch( item->id ) + { + case ID_JOINGAME: + if( CL_IsActive( )) + UI_PromptDialog(); + else UI_LanGame_JoinGame(); + break; + case ID_CREATEGAME: + CVAR_SET_FLOAT( "public", 0.0f ); + UI_CreateGame_Menu(); + break; + case ID_GAMEINFO: + // UNDONE: not implemented + break; + case ID_REFRESH: + UI_RefreshServerList(); + break; + case ID_DONE: + UI_PopMenu(); + break; + case ID_YES: + UI_LanGame_JoinGame(); + break; + case ID_NO: + UI_PromptDialog(); + break; + } +} + +/* +================= +UI_LanGame_Init +================= +*/ +static void UI_LanGame_Init( void ) +{ + memset( &uiLanGame, 0, sizeof( uiLanGame_t )); + + uiLanGame.menu.vidInitFunc = UI_LanGame_Init; + uiLanGame.menu.keyFunc = UI_LanGame_KeyFunc; + + StringConcat( uiLanGame.hintText, "Game", GAME_LENGTH ); + StringConcat( uiLanGame.hintText, uiEmptyString, GAME_LENGTH ); + StringConcat( uiLanGame.hintText, "Map", MAPNAME_LENGTH ); + StringConcat( uiLanGame.hintText, uiEmptyString, MAPNAME_LENGTH ); + StringConcat( uiLanGame.hintText, "Type", TYPE_LENGTH ); + StringConcat( uiLanGame.hintText, uiEmptyString, TYPE_LENGTH ); + StringConcat( uiLanGame.hintText, "Clients", MAXCL_LENGTH ); + StringConcat( uiLanGame.hintText, uiEmptyString, MAXCL_LENGTH ); + + uiLanGame.background.generic.id = ID_BACKGROUND; + uiLanGame.background.generic.type = QMTYPE_BITMAP; + uiLanGame.background.generic.flags = QMF_INACTIVE; + uiLanGame.background.generic.x = 0; + uiLanGame.background.generic.y = 0; + uiLanGame.background.generic.width = 1024; + uiLanGame.background.generic.height = 768; + uiLanGame.background.pic = ART_BACKGROUND; + uiLanGame.background.generic.ownerdraw = UI_Background_Ownerdraw; + + uiLanGame.banner.generic.id = ID_BANNER; + uiLanGame.banner.generic.type = QMTYPE_BITMAP; + uiLanGame.banner.generic.flags = QMF_INACTIVE|QMF_DRAW_ADDITIVE; + uiLanGame.banner.generic.x = UI_BANNER_POSX; + uiLanGame.banner.generic.y = UI_BANNER_POSY; + uiLanGame.banner.generic.width = UI_BANNER_WIDTH; + uiLanGame.banner.generic.height = UI_BANNER_HEIGHT; + uiLanGame.banner.pic = ART_BANNER; + + uiLanGame.joinGame.generic.id = ID_JOINGAME; + uiLanGame.joinGame.generic.type = QMTYPE_BM_BUTTON; + uiLanGame.joinGame.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_GRAYED; + uiLanGame.joinGame.generic.x = 72; + uiLanGame.joinGame.generic.y = 230; + uiLanGame.joinGame.generic.name = "Join game"; + uiLanGame.joinGame.generic.statusText = "Join to selected game"; + uiLanGame.joinGame.generic.callback = UI_LanGame_Callback; + + UI_UtilSetupPicButton( &uiLanGame.joinGame, PC_JOIN_GAME ); + + uiLanGame.createGame.generic.id = ID_CREATEGAME; + uiLanGame.createGame.generic.type = QMTYPE_BM_BUTTON; + uiLanGame.createGame.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiLanGame.createGame.generic.x = 72; + uiLanGame.createGame.generic.y = 280; + uiLanGame.createGame.generic.name = "Create game"; + uiLanGame.createGame.generic.statusText = "Create new LAN game"; + uiLanGame.createGame.generic.callback = UI_LanGame_Callback; + + UI_UtilSetupPicButton( &uiLanGame.createGame, PC_CREATE_GAME ); + + uiLanGame.gameInfo.generic.id = ID_GAMEINFO; + uiLanGame.gameInfo.generic.type = QMTYPE_BM_BUTTON; + uiLanGame.gameInfo.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_GRAYED; + uiLanGame.gameInfo.generic.x = 72; + uiLanGame.gameInfo.generic.y = 330; + uiLanGame.gameInfo.generic.name = "View game info"; + uiLanGame.gameInfo.generic.statusText = "Get detail game info"; + uiLanGame.gameInfo.generic.callback = UI_LanGame_Callback; + + UI_UtilSetupPicButton( &uiLanGame.gameInfo, PC_VIEW_GAME_INFO ); + + uiLanGame.refresh.generic.id = ID_REFRESH; + uiLanGame.refresh.generic.type = QMTYPE_BM_BUTTON; + uiLanGame.refresh.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiLanGame.refresh.generic.x = 72; + uiLanGame.refresh.generic.y = 380; + uiLanGame.refresh.generic.name = "Refresh"; + uiLanGame.refresh.generic.statusText = "Refresh servers list"; + uiLanGame.refresh.generic.callback = UI_LanGame_Callback; + + UI_UtilSetupPicButton( &uiLanGame.refresh, PC_REFRESH ); + + uiLanGame.done.generic.id = ID_DONE; + uiLanGame.done.generic.type = QMTYPE_BM_BUTTON; + uiLanGame.done.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiLanGame.done.generic.x = 72; + uiLanGame.done.generic.y = 430; + uiLanGame.done.generic.name = "Done"; + uiLanGame.done.generic.statusText = "Return to main menu"; + uiLanGame.done.generic.callback = UI_LanGame_Callback; + + UI_UtilSetupPicButton( &uiLanGame.done, PC_DONE ); + + uiLanGame.msgBox.generic.id = ID_MSGBOX; + uiLanGame.msgBox.generic.type = QMTYPE_ACTION; + uiLanGame.msgBox.generic.flags = QMF_INACTIVE|QMF_HIDDEN; + uiLanGame.msgBox.generic.ownerdraw = UI_MsgBox_Ownerdraw; // just a fill rectangle + uiLanGame.msgBox.generic.x = 192; + uiLanGame.msgBox.generic.y = 256; + uiLanGame.msgBox.generic.width = 640; + uiLanGame.msgBox.generic.height = 256; + + uiLanGame.dlgMessage1.generic.id = ID_MSGTEXT; + uiLanGame.dlgMessage1.generic.type = QMTYPE_ACTION; + uiLanGame.dlgMessage1.generic.flags = QMF_INACTIVE|QMF_HIDDEN|QMF_DROPSHADOW; + uiLanGame.dlgMessage1.generic.name = "Join a network game will exit"; + uiLanGame.dlgMessage1.generic.x = 248; + uiLanGame.dlgMessage1.generic.y = 280; + + uiLanGame.dlgMessage2.generic.id = ID_MSGTEXT; + uiLanGame.dlgMessage2.generic.type = QMTYPE_ACTION; + uiLanGame.dlgMessage2.generic.flags = QMF_INACTIVE|QMF_HIDDEN|QMF_DROPSHADOW; + uiLanGame.dlgMessage2.generic.name = "any current game, OK to exit?"; + uiLanGame.dlgMessage2.generic.x = 248; + uiLanGame.dlgMessage2.generic.y = 310; + + uiLanGame.yes.generic.id = ID_YES; + uiLanGame.yes.generic.type = QMTYPE_BM_BUTTON; + uiLanGame.yes.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_HIDDEN|QMF_DROPSHADOW; + uiLanGame.yes.generic.name = "Ok"; + uiLanGame.yes.generic.x = 380; + uiLanGame.yes.generic.y = 460; + uiLanGame.yes.generic.callback = UI_LanGame_Callback; + + UI_UtilSetupPicButton( &uiLanGame.yes, PC_OK ); + + uiLanGame.no.generic.id = ID_NO; + uiLanGame.no.generic.type = QMTYPE_BM_BUTTON; + uiLanGame.no.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_HIDDEN|QMF_DROPSHADOW; + uiLanGame.no.generic.name = "Cancel"; + uiLanGame.no.generic.x = 530; + uiLanGame.no.generic.y = 460; + uiLanGame.no.generic.callback = UI_LanGame_Callback; + + UI_UtilSetupPicButton( &uiLanGame.no, PC_CANCEL ); + + uiLanGame.hintMessage.generic.id = ID_TABLEHINT; + uiLanGame.hintMessage.generic.type = QMTYPE_ACTION; + uiLanGame.hintMessage.generic.flags = QMF_INACTIVE|QMF_SMALLFONT; + uiLanGame.hintMessage.generic.color = uiColorHelp; + uiLanGame.hintMessage.generic.name = uiLanGame.hintText; + uiLanGame.hintMessage.generic.x = 360; + uiLanGame.hintMessage.generic.y = 225; + + uiLanGame.gameList.generic.id = ID_SERVERSLIST; + uiLanGame.gameList.generic.type = QMTYPE_SCROLLLIST; + uiLanGame.gameList.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_SMALLFONT; + uiLanGame.gameList.generic.x = 360; + uiLanGame.gameList.generic.y = 255; + uiLanGame.gameList.generic.width = 640; + uiLanGame.gameList.generic.height = 440; + uiLanGame.gameList.generic.callback = UI_LanGame_Callback; + uiLanGame.gameList.itemNames = (const char **)uiLanGame.gameDescriptionPtr; + + // server.dll needs for reading savefiles or startup newgame + if( !CheckGameDll( )) + uiLanGame.createGame.generic.flags |= QMF_GRAYED; // server.dll is missed - remote servers only + + UI_AddItem( &uiLanGame.menu, (void *)&uiLanGame.background ); + UI_AddItem( &uiLanGame.menu, (void *)&uiLanGame.banner ); + UI_AddItem( &uiLanGame.menu, (void *)&uiLanGame.joinGame ); + UI_AddItem( &uiLanGame.menu, (void *)&uiLanGame.createGame ); + UI_AddItem( &uiLanGame.menu, (void *)&uiLanGame.gameInfo ); + UI_AddItem( &uiLanGame.menu, (void *)&uiLanGame.refresh ); + UI_AddItem( &uiLanGame.menu, (void *)&uiLanGame.done ); + UI_AddItem( &uiLanGame.menu, (void *)&uiLanGame.hintMessage ); + UI_AddItem( &uiLanGame.menu, (void *)&uiLanGame.gameList ); + UI_AddItem( &uiLanGame.menu, (void *)&uiLanGame.msgBox ); + UI_AddItem( &uiLanGame.menu, (void *)&uiLanGame.dlgMessage1 ); + UI_AddItem( &uiLanGame.menu, (void *)&uiLanGame.dlgMessage2 ); + UI_AddItem( &uiLanGame.menu, (void *)&uiLanGame.no ); + UI_AddItem( &uiLanGame.menu, (void *)&uiLanGame.yes ); + + uiLanGame.refreshTime = uiStatic.realTime + 500; // delay before update 0.5 sec +} + +/* +================= +UI_LanGame_Precache +================= +*/ +void UI_LanGame_Precache( void ) +{ + PIC_Load( ART_BACKGROUND ); + PIC_Load( ART_BANNER ); +} + +/* +================= +UI_LanGame_Menu +================= +*/ +void UI_LanGame_Menu( void ) +{ + if ( gMenu.m_gameinfo.gamemode == GAME_SINGLEPLAYER_ONLY ) + return; + + // stop demos to allow open network sockets + if ( gpGlobals->demoplayback && CVAR_GET_FLOAT( "cl_background" )) + { + uiStatic.m_iOldMenuDepth = uiStatic.menuDepth; + CLIENT_COMMAND( FALSE, "stop\n" ); + uiStatic.m_fDemosPlayed = true; + } + + UI_LanGame_Precache(); + UI_LanGame_Init(); + + UI_PushMenu( &uiLanGame.menu ); +} \ No newline at end of file diff --git a/mainui/menu_loadgame.cpp b/mainui/menu_loadgame.cpp new file mode 100644 index 0000000..6a9225e --- /dev/null +++ b/mainui/menu_loadgame.cpp @@ -0,0 +1,470 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "extdll.h" +#include "basemenu.h" +#include "utils.h" +#include "keydefs.h" +#include "menu_btnsbmp_table.h" + +#define ART_BANNER "gfx/shell/head_load" + +#define ID_BACKGROUND 0 +#define ID_BANNER 1 +#define ID_LOAD 2 +#define ID_DELETE 3 +#define ID_CANCEL 4 +#define ID_SAVELIST 5 +#define ID_TABLEHINT 6 +#define ID_LEVELSHOT 7 +#define ID_MSGBOX 8 +#define ID_MSGTEXT 9 +#define ID_YES 130 +#define ID_NO 131 + +#define LEVELSHOT_X 72 +#define LEVELSHOT_Y 400 +#define LEVELSHOT_W 192 +#define LEVELSHOT_H 160 + +#define TIME_LENGTH 20 +#define NAME_LENGTH 32+TIME_LENGTH +#define GAMETIME_LENGTH 15+NAME_LENGTH + +typedef struct +{ + char saveName[UI_MAXGAMES][CS_SIZE]; + char delName[UI_MAXGAMES][CS_SIZE]; + char saveDescription[UI_MAXGAMES][95]; + char *saveDescriptionPtr[UI_MAXGAMES]; + + menuFramework_s menu; + + menuBitmap_s background; + menuBitmap_s banner; + menuPicButton_s load; + menuPicButton_s remove; + menuPicButton_s cancel; + + menuScrollList_s savesList; + + menuBitmap_s levelShot; + menuAction_s hintMessage; + char hintText[MAX_HINT_TEXT]; + + // prompt dialog + menuAction_s msgBox; + menuAction_s promptMessage; + menuPicButton_s yes; + menuPicButton_s no; +} uiLoadGame_t; + +static uiLoadGame_t uiLoadGame; + +/* +================= +UI_MsgBox_Ownerdraw +================= +*/ +static void UI_MsgBox_Ownerdraw( void *self ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + UI_FillRect( item->x, item->y, item->width, item->height, uiPromptBgColor ); +} + +static void UI_DeleteDialog( void ) +{ + // toggle main menu between active\inactive + // show\hide remove dialog + uiLoadGame.load.generic.flags ^= QMF_INACTIVE; + uiLoadGame.remove.generic.flags ^= QMF_INACTIVE; + uiLoadGame.cancel.generic.flags ^= QMF_INACTIVE; + uiLoadGame.savesList.generic.flags ^= QMF_INACTIVE; + + uiLoadGame.msgBox.generic.flags ^= QMF_HIDDEN; + uiLoadGame.promptMessage.generic.flags ^= QMF_HIDDEN; + uiLoadGame.no.generic.flags ^= QMF_HIDDEN; + uiLoadGame.yes.generic.flags ^= QMF_HIDDEN; +} + +/* +================= +UI_LoadGame_KeyFunc +================= +*/ +static const char *UI_LoadGame_KeyFunc( int key, int down ) +{ + if( down && key == K_ESCAPE && uiLoadGame.load.generic.flags & QMF_INACTIVE ) + { + UI_DeleteDialog(); + return uiSoundNull; + } + return UI_DefaultKey( &uiLoadGame.menu, key, down ); +} + +/* +================= +UI_LoadGame_GetGameList +================= +*/ +static void UI_LoadGame_GetGameList( void ) +{ + char comment[256]; + char **filenames; + int i, numFiles; + + filenames = FS_SEARCH( "save/*.sav", &numFiles, TRUE ); + + // sort the saves in reverse order (oldest past at the end) + qsort( filenames, numFiles, sizeof( char* ), (cmpfunc)COM_CompareSaves ); + + for ( i = 0; i < numFiles; i++ ) + { + if( i >= UI_MAXGAMES ) break; + + if( !GET_SAVE_COMMENT( filenames[i], comment )) + { + if( strlen( comment )) + { + // get name string even if not found - SV_GetComment can be mark saves + // as etc + StringConcat( uiLoadGame.saveDescription[i], uiEmptyString, TIME_LENGTH ); + StringConcat( uiLoadGame.saveDescription[i], comment, NAME_LENGTH ); + StringConcat( uiLoadGame.saveDescription[i], uiEmptyString, NAME_LENGTH ); + uiLoadGame.saveDescriptionPtr[i] = uiLoadGame.saveDescription[i]; + COM_FileBase( filenames[i], uiLoadGame.delName[i] ); + } + else uiLoadGame.saveDescriptionPtr[i] = NULL; + continue; + } + + // strip path, leave only filename (empty slots doesn't have savename) + COM_FileBase( filenames[i], uiLoadGame.saveName[i] ); + COM_FileBase( filenames[i], uiLoadGame.delName[i] ); + + // fill save desc + StringConcat( uiLoadGame.saveDescription[i], comment + CS_SIZE, TIME_LENGTH ); + StringConcat( uiLoadGame.saveDescription[i], " ", TIME_LENGTH ); + StringConcat( uiLoadGame.saveDescription[i], comment + CS_SIZE + CS_TIME, TIME_LENGTH ); + StringConcat( uiLoadGame.saveDescription[i], uiEmptyString, TIME_LENGTH ); // fill remaining entries + StringConcat( uiLoadGame.saveDescription[i], comment, NAME_LENGTH ); + StringConcat( uiLoadGame.saveDescription[i], uiEmptyString, NAME_LENGTH ); + StringConcat( uiLoadGame.saveDescription[i], comment + CS_SIZE + (CS_TIME * 2), GAMETIME_LENGTH ); + StringConcat( uiLoadGame.saveDescription[i], uiEmptyString, GAMETIME_LENGTH ); + uiLoadGame.saveDescriptionPtr[i] = uiLoadGame.saveDescription[i]; + } + + for ( ; i < UI_MAXGAMES; i++ ) + uiLoadGame.saveDescriptionPtr[i] = NULL; + + uiLoadGame.savesList.itemNames = (const char **)uiLoadGame.saveDescriptionPtr; + + if ( strlen( uiLoadGame.saveName[0] ) == 0 ) + uiLoadGame.load.generic.flags |= QMF_GRAYED; + else uiLoadGame.load.generic.flags &= ~QMF_GRAYED; + + if ( strlen( uiLoadGame.delName[0] ) == 0 ) + uiLoadGame.remove.generic.flags |= QMF_GRAYED; + else uiLoadGame.remove.generic.flags &= ~QMF_GRAYED; +} + +/* +================= +UI_LoadGame_Callback +================= +*/ +static void UI_LoadGame_Callback( void *self, int event ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + if( event == QM_CHANGED ) + { + if( strlen( uiLoadGame.saveName[uiLoadGame.savesList.curItem] ) == 0 ) + uiLoadGame.load.generic.flags |= QMF_GRAYED; + else uiLoadGame.load.generic.flags &= ~QMF_GRAYED; + + if( strlen( uiLoadGame.delName[uiLoadGame.savesList.curItem] ) == 0 ) + uiLoadGame.remove.generic.flags |= QMF_GRAYED; + else uiLoadGame.remove.generic.flags &= ~QMF_GRAYED; + return; + } + + if( event != QM_ACTIVATED ) + return; + + switch( item->id ) + { + case ID_CANCEL: + UI_PopMenu(); + break; + case ID_LOAD: + if( strlen( uiLoadGame.saveName[uiLoadGame.savesList.curItem] )) + { + char cmd[128]; + sprintf( cmd, "load \"%s\"\n", uiLoadGame.saveName[uiLoadGame.savesList.curItem] ); + + BACKGROUND_TRACK( NULL, NULL ); + + CLIENT_COMMAND( FALSE, cmd ); + } + break; + case ID_NO: + case ID_DELETE: + UI_DeleteDialog(); + break; + case ID_YES: + if( strlen( uiLoadGame.delName[uiLoadGame.savesList.curItem] )) + { + char cmd[128]; + sprintf( cmd, "killsave \"%s\"\n", uiLoadGame.delName[uiLoadGame.savesList.curItem] ); + + CLIENT_COMMAND( TRUE, cmd ); + + sprintf( cmd, "save/%s.bmp", uiLoadGame.delName[uiLoadGame.savesList.curItem] ); + PIC_Free( cmd ); + + // restarts the menu + UI_PopMenu(); + UI_LoadGame_Menu(); + return; + } + UI_DeleteDialog(); + break; + } +} + +/* +================= +UI_LoadGame_Ownerdraw +================= +*/ +static void UI_LoadGame_Ownerdraw( void *self ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + if( item->type != QMTYPE_ACTION && item->id == ID_LEVELSHOT ) + { + int x, y, w, h; + + // draw the levelshot + x = LEVELSHOT_X; + y = LEVELSHOT_Y; + w = LEVELSHOT_W; + h = LEVELSHOT_H; + + UI_ScaleCoords( &x, &y, &w, &h ); + + if( strlen( uiLoadGame.saveName[uiLoadGame.savesList.curItem] )) + { + char saveshot[128]; + + sprintf( saveshot, "save/%s.bmp", uiLoadGame.saveName[uiLoadGame.savesList.curItem] ); + + if( !FILE_EXISTS( saveshot )) + UI_DrawPicAdditive( x, y, w, h, uiColorWhite, "gfx/empty.tga" ); + else UI_DrawPic( x, y, w, h, uiColorWhite, saveshot ); + } + else UI_DrawPicAdditive( x, y, w, h, uiColorWhite, "gfx/empty.tga" ); + + // draw the rectangle + UI_DrawRectangle( item->x, item->y, item->width, item->height, uiInputFgColor ); + } +} + +/* +================= +UI_LoadGame_Init +================= +*/ +static void UI_LoadGame_Init( void ) +{ + memset( &uiLoadGame, 0, sizeof( uiLoadGame_t )); + + uiLoadGame.menu.vidInitFunc = UI_LoadGame_Init; + uiLoadGame.menu.keyFunc = UI_LoadGame_KeyFunc; + + StringConcat( uiLoadGame.hintText, "Time", TIME_LENGTH ); + StringConcat( uiLoadGame.hintText, uiEmptyString, TIME_LENGTH ); + StringConcat( uiLoadGame.hintText, "Game", NAME_LENGTH ); + StringConcat( uiLoadGame.hintText, uiEmptyString, NAME_LENGTH ); + StringConcat( uiLoadGame.hintText, "Elapsed time", GAMETIME_LENGTH ); + StringConcat( uiLoadGame.hintText, uiEmptyString, GAMETIME_LENGTH ); + + uiLoadGame.background.generic.id = ID_BACKGROUND; + uiLoadGame.background.generic.type = QMTYPE_BITMAP; + uiLoadGame.background.generic.flags = QMF_INACTIVE; + uiLoadGame.background.generic.x = 0; + uiLoadGame.background.generic.y = 0; + uiLoadGame.background.generic.width = 1024; + uiLoadGame.background.generic.height = 768; + uiLoadGame.background.pic = ART_BACKGROUND; + + uiLoadGame.banner.generic.id = ID_BANNER; + uiLoadGame.banner.generic.type = QMTYPE_BITMAP; + uiLoadGame.banner.generic.flags = QMF_INACTIVE|QMF_DRAW_ADDITIVE; + uiLoadGame.banner.generic.x = UI_BANNER_POSX; + uiLoadGame.banner.generic.y = UI_BANNER_POSY; + uiLoadGame.banner.generic.width = UI_BANNER_WIDTH; + uiLoadGame.banner.generic.height = UI_BANNER_HEIGHT; + uiLoadGame.banner.pic = ART_BANNER; + + uiLoadGame.load.generic.id = ID_LOAD; + uiLoadGame.load.generic.type = QMTYPE_BM_BUTTON; + uiLoadGame.load.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiLoadGame.load.generic.x = 72; + uiLoadGame.load.generic.y = 230; + uiLoadGame.load.generic.name = "Load"; + uiLoadGame.load.generic.statusText = "Load saved game"; + uiLoadGame.load.generic.callback = UI_LoadGame_Callback; + + UI_UtilSetupPicButton( &uiLoadGame.load, PC_LOAD_GAME ); + + uiLoadGame.remove.generic.id = ID_DELETE; + uiLoadGame.remove.generic.type = QMTYPE_BM_BUTTON; + uiLoadGame.remove.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiLoadGame.remove.generic.x = 72; + uiLoadGame.remove.generic.y = 280; + uiLoadGame.remove.generic.name = "Delete"; + uiLoadGame.remove.generic.statusText = "Delete saved game"; + uiLoadGame.remove.generic.callback = UI_LoadGame_Callback; + + UI_UtilSetupPicButton( &uiLoadGame.remove, PC_DELETE ); + + uiLoadGame.cancel.generic.id = ID_CANCEL; + uiLoadGame.cancel.generic.type = QMTYPE_BM_BUTTON; + uiLoadGame.cancel.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiLoadGame.cancel.generic.x = 72; + uiLoadGame.cancel.generic.y = 330; + uiLoadGame.cancel.generic.name = "Cancel"; + uiLoadGame.cancel.generic.statusText = "Return back to main menu"; + uiLoadGame.cancel.generic.callback = UI_LoadGame_Callback; + + UI_UtilSetupPicButton( &uiLoadGame.cancel, PC_CANCEL ); + + uiLoadGame.hintMessage.generic.id = ID_TABLEHINT; + uiLoadGame.hintMessage.generic.type = QMTYPE_ACTION; + uiLoadGame.hintMessage.generic.flags = QMF_INACTIVE|QMF_SMALLFONT; + uiLoadGame.hintMessage.generic.color = uiColorHelp; + uiLoadGame.hintMessage.generic.name = uiLoadGame.hintText; + uiLoadGame.hintMessage.generic.x = 360; + uiLoadGame.hintMessage.generic.y = 225; + + uiLoadGame.levelShot.generic.id = ID_LEVELSHOT; + uiLoadGame.levelShot.generic.type = QMTYPE_BITMAP; + uiLoadGame.levelShot.generic.flags = QMF_INACTIVE; + uiLoadGame.levelShot.generic.x = LEVELSHOT_X; + uiLoadGame.levelShot.generic.y = LEVELSHOT_Y; + uiLoadGame.levelShot.generic.width = LEVELSHOT_W; + uiLoadGame.levelShot.generic.height = LEVELSHOT_H; + uiLoadGame.levelShot.generic.ownerdraw = UI_LoadGame_Ownerdraw; + + uiLoadGame.savesList.generic.id = ID_SAVELIST; + uiLoadGame.savesList.generic.type = QMTYPE_SCROLLLIST; + uiLoadGame.savesList.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_SMALLFONT; + uiLoadGame.savesList.generic.x = 360; + uiLoadGame.savesList.generic.y = 255; + uiLoadGame.savesList.generic.width = 640; + uiLoadGame.savesList.generic.height = 440; + uiLoadGame.savesList.generic.callback = UI_LoadGame_Callback; + + uiLoadGame.msgBox.generic.id = ID_MSGBOX; + uiLoadGame.msgBox.generic.type = QMTYPE_ACTION; + uiLoadGame.msgBox.generic.flags = QMF_INACTIVE|QMF_HIDDEN; + uiLoadGame.msgBox.generic.ownerdraw = UI_MsgBox_Ownerdraw; // just a fill rectangle + uiLoadGame.msgBox.generic.x = 192; + uiLoadGame.msgBox.generic.y = 256; + uiLoadGame.msgBox.generic.width = 640; + uiLoadGame.msgBox.generic.height = 256; + + uiLoadGame.promptMessage.generic.id = ID_MSGBOX; + uiLoadGame.promptMessage.generic.type = QMTYPE_ACTION; + uiLoadGame.promptMessage.generic.flags = QMF_INACTIVE|QMF_DROPSHADOW|QMF_HIDDEN; + uiLoadGame.promptMessage.generic.name = "Delete selected game?"; + uiLoadGame.promptMessage.generic.x = 315; + uiLoadGame.promptMessage.generic.y = 280; + + uiLoadGame.yes.generic.id = ID_YES; + uiLoadGame.yes.generic.type = QMTYPE_BM_BUTTON; + uiLoadGame.yes.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_HIDDEN; + uiLoadGame.yes.generic.name = "Ok"; + uiLoadGame.yes.generic.x = 380; + uiLoadGame.yes.generic.y = 460; + uiLoadGame.yes.generic.callback = UI_LoadGame_Callback; + + UI_UtilSetupPicButton( &uiLoadGame.yes, PC_OK ); + + uiLoadGame.no.generic.id = ID_NO; + uiLoadGame.no.generic.type = QMTYPE_BM_BUTTON; + uiLoadGame.no.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_HIDDEN; + uiLoadGame.no.generic.name = "Cancel"; + uiLoadGame.no.generic.x = 530; + uiLoadGame.no.generic.y = 460; + uiLoadGame.no.generic.callback = UI_LoadGame_Callback; + + UI_UtilSetupPicButton( &uiLoadGame.no, PC_CANCEL ); + + UI_LoadGame_GetGameList(); + + UI_AddItem( &uiLoadGame.menu, (void *)&uiLoadGame.background ); + UI_AddItem( &uiLoadGame.menu, (void *)&uiLoadGame.banner ); + UI_AddItem( &uiLoadGame.menu, (void *)&uiLoadGame.load ); + UI_AddItem( &uiLoadGame.menu, (void *)&uiLoadGame.remove ); + UI_AddItem( &uiLoadGame.menu, (void *)&uiLoadGame.cancel ); + UI_AddItem( &uiLoadGame.menu, (void *)&uiLoadGame.hintMessage ); + UI_AddItem( &uiLoadGame.menu, (void *)&uiLoadGame.levelShot ); + UI_AddItem( &uiLoadGame.menu, (void *)&uiLoadGame.savesList ); + UI_AddItem( &uiLoadGame.menu, (void *)&uiLoadGame.msgBox ); + UI_AddItem( &uiLoadGame.menu, (void *)&uiLoadGame.promptMessage ); + UI_AddItem( &uiLoadGame.menu, (void *)&uiLoadGame.no ); + UI_AddItem( &uiLoadGame.menu, (void *)&uiLoadGame.yes ); +} + +/* +================= +UI_LoadGame_Precache +================= +*/ +void UI_LoadGame_Precache( void ) +{ + PIC_Load( ART_BACKGROUND ); + PIC_Load( ART_BANNER ); +} + +/* +================= +UI_LoadGame_Menu +================= +*/ +void UI_LoadGame_Menu( void ) +{ + if( gMenu.m_gameinfo.gamemode == GAME_MULTIPLAYER_ONLY ) + { + // completely ignore save\load menus for multiplayer_only + return; + } + + if( !CheckGameDll( )) return; + + UI_LoadGame_Precache(); + UI_LoadGame_Init(); + + UI_PushMenu( &uiLoadGame.menu ); +} \ No newline at end of file diff --git a/mainui/menu_main.cpp b/mainui/menu_main.cpp new file mode 100644 index 0000000..1dda0e2 --- /dev/null +++ b/mainui/menu_main.cpp @@ -0,0 +1,627 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "extdll.h" +#include "basemenu.h" +#include "utils.h" +#include "keydefs.h" +#include "menu_btnsbmp_table.h" +#include "menu_strings.h" + +#define ART_MINIMIZE_N "gfx/shell/min_n" +#define ART_MINIMIZE_F "gfx/shell/min_f" +#define ART_MINIMIZE_D "gfx/shell/min_d" +#define ART_CLOSEBTN_N "gfx/shell/cls_n" +#define ART_CLOSEBTN_F "gfx/shell/cls_f" +#define ART_CLOSEBTN_D "gfx/shell/cls_d" + +#define ID_BACKGROUND 0 +#define ID_CONSOLE 1 +#define ID_RESUME 2 +#define ID_NEWGAME 3 +#define ID_HAZARDCOURSE 4 +#define ID_CONFIGURATION 5 +#define ID_SAVERESTORE 6 +#define ID_MULTIPLAYER 7 +#define ID_CUSTOMGAME 8 +#define ID_PREVIEWS 9 +#define ID_QUIT 10 +#define ID_QUIT_BUTTON 11 +#define ID_MINIMIZE 12 +#define ID_MSGBOX 13 +#define ID_MSGTEXT 14 +#define ID_YES 130 +#define ID_NO 131 + +typedef struct +{ + menuFramework_s menu; + + menuBitmap_s background; + menuPicButton_s console; + menuPicButton_s resumeGame; + menuPicButton_s newGame; + menuPicButton_s hazardCourse; + menuPicButton_s configuration; + menuPicButton_s saveRestore; + menuPicButton_s multiPlayer; + menuPicButton_s customGame; + menuPicButton_s previews; + menuPicButton_s quit; + + menuBitmap_s minimizeBtn; + menuBitmap_s quitButton; + + // quit dialog + menuAction_s msgBox; + menuAction_s quitMessage; + menuAction_s dlgMessage1; + menuPicButton_s yes; + menuPicButton_s no; +} uiMain_t; + +static uiMain_t uiMain; + +/* +================= +UI_MsgBox_Ownerdraw +================= +*/ +static void UI_MsgBox_Ownerdraw( void *self ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + UI_FillRect( item->x, item->y, item->width, item->height, uiPromptBgColor ); +} + +/* +================= +UI_Background_Ownerdraw +================= +*/ +static void UI_Background_Ownerdraw( void *self ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + // map has background + if( CVAR_GET_FLOAT( "cl_background" )) + return; + + UI_DrawBackground_Callback( self ); + + if (uiStatic.m_fHaveSteamBackground || uiStatic.m_fDisableLogo) + return; // no logos for steam background + + if( GetLogoLength() <= 0.05f || GetLogoWidth() <= 32 ) + return; // don't draw stub logo (GoldSrc rules) + + float logoWidth, logoHeight, logoPosY; + float scaleX, scaleY; + + scaleX = ScreenWidth / 640.0f; + scaleY = ScreenHeight / 480.0f; + + logoWidth = GetLogoWidth() * scaleX; + logoHeight = GetLogoHeight() * scaleY; + logoPosY = 70 * scaleY; // 70 it's empirically determined value (magic number) + + DRAW_LOGO( "logo.avi", 0, logoPosY, logoWidth, logoHeight ); +} + +static void UI_QuitDialog( void ) +{ + // toggle main menu between active\inactive + // show\hide quit dialog + uiMain.console.generic.flags ^= QMF_INACTIVE; + uiMain.resumeGame.generic.flags ^= QMF_INACTIVE; + uiMain.newGame.generic.flags ^= QMF_INACTIVE; + uiMain.hazardCourse.generic.flags ^= QMF_INACTIVE; + uiMain.saveRestore.generic.flags ^= QMF_INACTIVE; + uiMain.configuration.generic.flags ^= QMF_INACTIVE; + uiMain.multiPlayer.generic.flags ^= QMF_INACTIVE; + uiMain.customGame.generic.flags ^= QMF_INACTIVE; + uiMain.previews.generic.flags ^= QMF_INACTIVE; + uiMain.quit.generic.flags ^= QMF_INACTIVE; + uiMain.minimizeBtn.generic.flags ^= QMF_INACTIVE; + uiMain.quitButton.generic.flags ^= QMF_INACTIVE; + + uiMain.msgBox.generic.flags ^= QMF_HIDDEN; + uiMain.quitMessage.generic.flags ^= QMF_HIDDEN; + uiMain.no.generic.flags ^= QMF_HIDDEN; + uiMain.yes.generic.flags ^= QMF_HIDDEN; + +} + +static void UI_PromptDialog( void ) +{ + // toggle main menu between active\inactive + // show\hide quit dialog + uiMain.console.generic.flags ^= QMF_INACTIVE; + uiMain.resumeGame.generic.flags ^= QMF_INACTIVE; + uiMain.newGame.generic.flags ^= QMF_INACTIVE; + uiMain.hazardCourse.generic.flags ^= QMF_INACTIVE; + uiMain.saveRestore.generic.flags ^= QMF_INACTIVE; + uiMain.configuration.generic.flags ^= QMF_INACTIVE; + uiMain.multiPlayer.generic.flags ^= QMF_INACTIVE; + uiMain.customGame.generic.flags ^= QMF_INACTIVE; + uiMain.previews.generic.flags ^= QMF_INACTIVE; + uiMain.quit.generic.flags ^= QMF_INACTIVE; + uiMain.minimizeBtn.generic.flags ^= QMF_INACTIVE; + uiMain.quitButton.generic.flags ^= QMF_INACTIVE; + + uiMain.msgBox.generic.flags ^= QMF_HIDDEN; + uiMain.dlgMessage1.generic.flags ^= QMF_HIDDEN; + uiMain.no.generic.flags ^= QMF_HIDDEN; + uiMain.yes.generic.flags ^= QMF_HIDDEN; + +} + +/* +================= +UI_Main_KeyFunc +================= +*/ +static const char *UI_Main_KeyFunc( int key, int down ) +{ + if( down && key == K_ESCAPE ) + { + if ( CL_IsActive( )) + { + if(!( uiMain.dlgMessage1.generic.flags & QMF_HIDDEN )) + UI_PromptDialog(); + else if(!( uiMain.quitMessage.generic.flags & QMF_HIDDEN )) + UI_QuitDialog(); + else UI_CloseMenu(); + } + else UI_QuitDialog(); + return uiSoundNull; + } + return UI_DefaultKey( &uiMain.menu, key, down ); +} + +/* +================= +UI_Main_ActivateFunc +================= +*/ +static void UI_Main_ActivateFunc( void ) +{ + if ( !CL_IsActive( )) + uiMain.resumeGame.generic.flags |= QMF_HIDDEN; + + if( gpGlobals->developer ) + { + uiMain.console.generic.y = CL_IsActive() ? 180 : 230; + UI_ScaleCoords( NULL, &uiMain.console.generic.y, NULL, NULL ); + } +} + +/* +================= +UI_Main_HazardCourse +================= +*/ +static void UI_Main_HazardCourse( void ) +{ + if( CVAR_GET_FLOAT( "host_serverstate" ) && CVAR_GET_FLOAT( "maxplayers" ) > 1 ) + HOST_ENDGAME( "end of the game" ); + + CVAR_SET_FLOAT( "skill", 1.0f ); + CVAR_SET_FLOAT( "deathmatch", 0.0f ); + CVAR_SET_FLOAT( "teamplay", 0.0f ); + CVAR_SET_FLOAT( "pausable", 1.0f ); // singleplayer is always allowing pause + CVAR_SET_FLOAT( "coop", 0.0f ); + + BACKGROUND_TRACK( NULL, NULL ); + + CLIENT_COMMAND( FALSE, "hazardcourse\n" ); +} + +/* +================= +UI_Main_Callback +================= +*/ +static void UI_Main_Callback( void *self, int event ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + switch( item->id ) + { + case ID_QUIT_BUTTON: + if( event == QM_PRESSED ) + ((menuBitmap_s *)self)->focusPic = ART_CLOSEBTN_D; + else ((menuBitmap_s *)self)->focusPic = ART_CLOSEBTN_F; + break; + case ID_MINIMIZE: + if( event == QM_PRESSED ) + ((menuBitmap_s *)self)->focusPic = ART_MINIMIZE_D; + else ((menuBitmap_s *)self)->focusPic = ART_MINIMIZE_F; + break; + } + + if( event != QM_ACTIVATED ) + return; + + switch( item->id ) + { + case ID_CONSOLE: + UI_SetActiveMenu( FALSE ); + KEY_SetDest( KEY_CONSOLE ); + break; + case ID_RESUME: + UI_CloseMenu(); + break; + case ID_NEWGAME: + UI_NewGame_Menu(); + break; + case ID_HAZARDCOURSE: + if( CL_IsActive( )) + UI_PromptDialog(); + else UI_Main_HazardCourse(); + break; + case ID_MULTIPLAYER: + UI_MultiPlayer_Menu(); + break; + case ID_CONFIGURATION: + UI_Options_Menu(); + break; + case ID_SAVERESTORE: + if( CL_IsActive( )) + UI_SaveLoad_Menu(); + else UI_LoadGame_Menu(); + break; + case ID_CUSTOMGAME: + UI_CustomGame_Menu(); + break; + case ID_PREVIEWS: + SHELL_EXECUTE( MenuStrings[HINT_PREVIEWS_CMD], NULL, false ); + break; + case ID_QUIT: + case ID_QUIT_BUTTON: + UI_QuitDialog(); + break; + case ID_MINIMIZE: + CLIENT_COMMAND( FALSE, "minimize\n" ); + break; + case ID_YES: + if( !( uiMain.quitMessage.generic.flags & QMF_HIDDEN )) + CLIENT_COMMAND( FALSE, "quit\n" ); + else UI_Main_HazardCourse(); + break; + case ID_NO: + if( !( uiMain.quitMessage.generic.flags & QMF_HIDDEN )) + UI_QuitDialog(); + else UI_PromptDialog(); + break; + } +} + +/* +================= +UI_Main_Init +================= +*/ +static void UI_Main_Init( void ) +{ + bool bTrainMap; + bool bCustomGame; + + memset( &uiMain, 0, sizeof( uiMain_t )); + + // training map is present and not equal to startmap + if( strlen( gMenu.m_gameinfo.trainmap ) && stricmp( gMenu.m_gameinfo.trainmap, gMenu.m_gameinfo.startmap )) + bTrainMap = true; + else bTrainMap = false; + + if( CVAR_GET_FLOAT( "host_allow_changegame" )) + bCustomGame = true; + else bCustomGame = false; + + // precache .avi file and get logo width and height + PRECACHE_LOGO( "logo.avi" ); + + uiMain.menu.vidInitFunc = UI_Main_Init; + uiMain.menu.keyFunc = UI_Main_KeyFunc; + uiMain.menu.activateFunc = UI_Main_ActivateFunc; + + uiMain.background.generic.id = ID_BACKGROUND; + uiMain.background.generic.type = QMTYPE_BITMAP; + uiMain.background.generic.flags = QMF_INACTIVE; + uiMain.background.generic.x = 0; + uiMain.background.generic.y = 0; + uiMain.background.generic.width = 1024; + uiMain.background.generic.height = 768; + uiMain.background.pic = ART_BACKGROUND; + uiMain.background.generic.ownerdraw = UI_Background_Ownerdraw; + + uiMain.console.generic.id = ID_CONSOLE; + uiMain.console.generic.type = QMTYPE_BM_BUTTON; + uiMain.console.generic.name = "Console"; + uiMain.console.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiMain.console.generic.x = 72; + uiMain.console.generic.y = CL_IsActive() ? 180 : 230; + uiMain.console.generic.callback = UI_Main_Callback; + + UI_UtilSetupPicButton( &uiMain.console, PC_CONSOLE ); + + uiMain.resumeGame.generic.id = ID_RESUME; + uiMain.resumeGame.generic.type = QMTYPE_BM_BUTTON; + uiMain.resumeGame.generic.name = "Resume game"; + uiMain.resumeGame.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiMain.resumeGame.generic.statusText = MenuStrings[HINT_RESUME_GAME]; + uiMain.resumeGame.generic.x = 72; + uiMain.resumeGame.generic.y = 230; + uiMain.resumeGame.generic.callback = UI_Main_Callback; + + UI_UtilSetupPicButton( &uiMain.resumeGame, PC_RESUME_GAME ); + + uiMain.newGame.generic.id = ID_NEWGAME; + uiMain.newGame.generic.type = QMTYPE_BM_BUTTON; + uiMain.newGame.generic.name = "New game"; + uiMain.newGame.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiMain.newGame.generic.statusText = MenuStrings[HINT_NEWGAME]; + uiMain.newGame.generic.x = 72; + uiMain.newGame.generic.y = 280; + uiMain.newGame.generic.callback = UI_Main_Callback; + + UI_UtilSetupPicButton( &uiMain.newGame, PC_NEW_GAME ); + + if ( gMenu.m_gameinfo.gamemode == GAME_MULTIPLAYER_ONLY || !strlen( gMenu.m_gameinfo.startmap )) + uiMain.newGame.generic.flags |= QMF_GRAYED; + + uiMain.hazardCourse.generic.id = ID_HAZARDCOURSE; + uiMain.hazardCourse.generic.type = QMTYPE_BM_BUTTON; + uiMain.hazardCourse.generic.name = "Hazard course"; + uiMain.hazardCourse.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiMain.hazardCourse.generic.statusText = MenuStrings[HINT_HAZARD_COURSE]; + uiMain.hazardCourse.generic.x = 72; + uiMain.hazardCourse.generic.y = 330; + uiMain.hazardCourse.generic.callback = UI_Main_Callback; + + UI_UtilSetupPicButton( &uiMain.hazardCourse, PC_HAZARD_COURSE ); + + uiMain.saveRestore.generic.id = ID_SAVERESTORE; + uiMain.saveRestore.generic.type = QMTYPE_BM_BUTTON; + uiMain.saveRestore.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + + // server.dll needs for reading savefiles or startup newgame + if( !CheckGameDll( )) + { + uiMain.saveRestore.generic.flags |= QMF_GRAYED; + uiMain.hazardCourse.generic.flags |= QMF_GRAYED; + uiMain.newGame.generic.flags |= QMF_GRAYED; + } + + if( CL_IsActive( )) + { + uiMain.saveRestore.generic.name = "Save\\Load Game"; + uiMain.saveRestore.generic.statusText = MenuStrings[HINT_SAVELOADGAME]; + UI_UtilSetupPicButton(&uiMain.saveRestore,PC_SAVE_LOAD_GAME); + } + else + { + uiMain.saveRestore.generic.name = "Load Game"; + uiMain.saveRestore.generic.statusText = MenuStrings[HINT_LOADGAME]; + uiMain.resumeGame.generic.flags |= QMF_HIDDEN; + UI_UtilSetupPicButton( &uiMain.saveRestore, PC_LOAD_GAME ); + } + + uiMain.saveRestore.generic.x = 72; + uiMain.saveRestore.generic.y = bTrainMap ? 380 : 330; + uiMain.saveRestore.generic.callback = UI_Main_Callback; + + uiMain.configuration.generic.id = ID_CONFIGURATION; + uiMain.configuration.generic.type = QMTYPE_BM_BUTTON; + uiMain.configuration.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiMain.configuration.generic.name = "Configuration"; + uiMain.configuration.generic.statusText = MenuStrings[HINT_CONFIGURATION]; + uiMain.configuration.generic.x = 72; + uiMain.configuration.generic.y = bTrainMap ? 430 : 380; + uiMain.configuration.generic.callback = UI_Main_Callback; + + UI_UtilSetupPicButton( &uiMain.configuration, PC_CONFIG ); + + uiMain.multiPlayer.generic.id = ID_MULTIPLAYER; + uiMain.multiPlayer.generic.type = QMTYPE_BM_BUTTON; + uiMain.multiPlayer.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiMain.multiPlayer.generic.name = "Multiplayer"; + uiMain.multiPlayer.generic.statusText = MenuStrings[HINT_MULTIPLAYER]; + uiMain.multiPlayer.generic.x = 72; + uiMain.multiPlayer.generic.y = bTrainMap ? 480 : 430; + uiMain.multiPlayer.generic.callback = UI_Main_Callback; + + UI_UtilSetupPicButton( &uiMain.multiPlayer, PC_MULTIPLAYER ); + + if ( gMenu.m_gameinfo.gamemode == GAME_SINGLEPLAYER_ONLY ) + uiMain.multiPlayer.generic.flags |= QMF_GRAYED; + + if ( gMenu.m_gameinfo.gamemode == GAME_MULTIPLAYER_ONLY ) + { + uiMain.saveRestore.generic.flags |= QMF_GRAYED; + uiMain.hazardCourse.generic.flags |= QMF_GRAYED; + } + + uiMain.customGame.generic.id = ID_CUSTOMGAME; + uiMain.customGame.generic.type = QMTYPE_BM_BUTTON; + uiMain.customGame.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiMain.customGame.generic.name = "Custom Game"; + uiMain.customGame.generic.statusText = MenuStrings[HINT_CUSTOM_GAME]; + uiMain.customGame.generic.x = 72; + uiMain.customGame.generic.y = bTrainMap ? 530 : 480; + uiMain.customGame.generic.callback = UI_Main_Callback; + + UI_UtilSetupPicButton( &uiMain.customGame, PC_CUSTOM_GAME ); + + uiMain.previews.generic.id = ID_PREVIEWS; + uiMain.previews.generic.type = QMTYPE_BM_BUTTON; + uiMain.previews.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiMain.previews.generic.name = "Previews"; + uiMain.previews.generic.statusText = MenuStrings[HINT_PREVIEWS_TEXT]; + uiMain.previews.generic.x = 72; + uiMain.previews.generic.y = (bCustomGame) ? (bTrainMap ? 580 : 530) : (bTrainMap ? 530 : 480); + uiMain.previews.generic.callback = UI_Main_Callback; + + // too short execute string - not a real command + if( strlen( MenuStrings[HINT_PREVIEWS_CMD] ) <= 3 ) + uiMain.previews.generic.flags |= QMF_GRAYED; + + UI_UtilSetupPicButton( &uiMain.previews, PC_PREVIEWS ); + + uiMain.quit.generic.id = ID_QUIT; + uiMain.quit.generic.type = QMTYPE_BM_BUTTON; + uiMain.quit.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiMain.quit.generic.name = "Quit"; + uiMain.quit.generic.statusText = MenuStrings[HINT_QUIT_BUTTON]; + uiMain.quit.generic.x = 72; + uiMain.quit.generic.y = (bCustomGame) ? (bTrainMap ? 630 : 580) : (bTrainMap ? 580 : 530); + uiMain.quit.generic.callback = UI_Main_Callback; + + UI_UtilSetupPicButton( &uiMain.quit, PC_QUIT ); + + uiMain.minimizeBtn.generic.id = ID_MINIMIZE; + uiMain.minimizeBtn.generic.type = QMTYPE_BITMAP; + uiMain.minimizeBtn.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_MOUSEONLY|QMF_ACT_ONRELEASE; + uiMain.minimizeBtn.generic.x = 952; + uiMain.minimizeBtn.generic.y = 13; + uiMain.minimizeBtn.generic.width = 32; + uiMain.minimizeBtn.generic.height = 32; + uiMain.minimizeBtn.generic.callback = UI_Main_Callback; + uiMain.minimizeBtn.pic = ART_MINIMIZE_N; + uiMain.minimizeBtn.focusPic = ART_MINIMIZE_F; + + uiMain.quitButton.generic.id = ID_QUIT_BUTTON; + uiMain.quitButton.generic.type = QMTYPE_BITMAP; + uiMain.quitButton.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_MOUSEONLY|QMF_ACT_ONRELEASE; + uiMain.quitButton.generic.x = 984; + uiMain.quitButton.generic.y = 13; + uiMain.quitButton.generic.width = 32; + uiMain.quitButton.generic.height = 32; + uiMain.quitButton.generic.callback = UI_Main_Callback; + uiMain.quitButton.pic = ART_CLOSEBTN_N; + uiMain.quitButton.focusPic = ART_CLOSEBTN_F; + + uiMain.msgBox.generic.id = ID_MSGBOX; + uiMain.msgBox.generic.type = QMTYPE_ACTION; + uiMain.msgBox.generic.flags = QMF_INACTIVE|QMF_HIDDEN; + uiMain.msgBox.generic.ownerdraw = UI_MsgBox_Ownerdraw; // just a fill rectangle + uiMain.msgBox.generic.x = 192; + uiMain.msgBox.generic.y = 256; + uiMain.msgBox.generic.width = 640; + uiMain.msgBox.generic.height = 256; + + uiMain.quitMessage.generic.id = ID_MSGBOX; + uiMain.quitMessage.generic.type = QMTYPE_ACTION; + uiMain.quitMessage.generic.flags = QMF_INACTIVE|QMF_DROPSHADOW|QMF_HIDDEN|QMF_CENTER_JUSTIFY; + uiMain.quitMessage.generic.name = (CL_IsActive( )) ? MenuStrings[HINT_QUIT_ACTIVE] : MenuStrings[HINT_QUIT]; + uiMain.quitMessage.generic.x = 192; + uiMain.quitMessage.generic.y = 280; + uiMain.quitMessage.generic.width = 640; + uiMain.quitMessage.generic.height = 256; + + uiMain.dlgMessage1.generic.id = ID_MSGTEXT; + uiMain.dlgMessage1.generic.type = QMTYPE_ACTION; + uiMain.dlgMessage1.generic.flags = QMF_INACTIVE|QMF_DROPSHADOW|QMF_HIDDEN|QMF_CENTER_JUSTIFY; + uiMain.dlgMessage1.generic.name = MenuStrings[HINT_RESTART_HZ]; + uiMain.dlgMessage1.generic.x = 192; + uiMain.dlgMessage1.generic.y = 280; + uiMain.dlgMessage1.generic.width = 640; + uiMain.dlgMessage1.generic.height = 256; + + uiMain.yes.generic.id = ID_YES; + uiMain.yes.generic.type = QMTYPE_BM_BUTTON; + uiMain.yes.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_HIDDEN; + uiMain.yes.generic.name = "Ok"; + uiMain.yes.generic.x = 380; + uiMain.yes.generic.y = 460; + uiMain.yes.generic.callback = UI_Main_Callback; + + UI_UtilSetupPicButton( &uiMain.yes, PC_OK ); + + uiMain.no.generic.id = ID_NO; + uiMain.no.generic.type = QMTYPE_BM_BUTTON; + uiMain.no.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_HIDDEN; + uiMain.no.generic.name = "Cancel"; + uiMain.no.generic.x = 530; + uiMain.no.generic.y = 460; + uiMain.no.generic.callback = UI_Main_Callback; + + UI_UtilSetupPicButton( &uiMain.no, PC_CANCEL ); + + UI_AddItem( &uiMain.menu, (void *)&uiMain.background ); + + if ( gpGlobals->developer ) + UI_AddItem( &uiMain.menu, (void *)&uiMain.console ); + + UI_AddItem( &uiMain.menu, (void *)&uiMain.resumeGame ); + UI_AddItem( &uiMain.menu, (void *)&uiMain.newGame ); + + if ( bTrainMap ) + UI_AddItem( &uiMain.menu, (void *)&uiMain.hazardCourse ); + + UI_AddItem( &uiMain.menu, (void *)&uiMain.saveRestore ); + UI_AddItem( &uiMain.menu, (void *)&uiMain.configuration ); + UI_AddItem( &uiMain.menu, (void *)&uiMain.multiPlayer ); + + if ( bCustomGame ) + UI_AddItem( &uiMain.menu, (void *)&uiMain.customGame ); + + UI_AddItem( &uiMain.menu, (void *)&uiMain.previews ); + UI_AddItem( &uiMain.menu, (void *)&uiMain.quit ); + UI_AddItem( &uiMain.menu, (void *)&uiMain.minimizeBtn ); + UI_AddItem( &uiMain.menu, (void *)&uiMain.quitButton ); + UI_AddItem( &uiMain.menu, (void *)&uiMain.msgBox ); + UI_AddItem( &uiMain.menu, (void *)&uiMain.quitMessage ); + UI_AddItem( &uiMain.menu, (void *)&uiMain.dlgMessage1 ); + UI_AddItem( &uiMain.menu, (void *)&uiMain.no ); + UI_AddItem( &uiMain.menu, (void *)&uiMain.yes ); +} + +/* +================= +UI_Main_Precache +================= +*/ +void UI_Main_Precache( void ) +{ + PIC_Load( ART_BACKGROUND ); + PIC_Load( ART_MINIMIZE_N ); + PIC_Load( ART_MINIMIZE_F ); + PIC_Load( ART_MINIMIZE_D ); + PIC_Load( ART_CLOSEBTN_N ); + PIC_Load( ART_CLOSEBTN_F ); + PIC_Load( ART_CLOSEBTN_D ); + + // precache .avi file and get logo width and height + PRECACHE_LOGO( "logo.avi" ); +} + +/* +================= +UI_Main_Menu +================= +*/ +void UI_Main_Menu( void ) +{ + UI_Main_Precache(); + UI_Main_Init(); + + UI_PushMenu( &uiMain.menu ); +} \ No newline at end of file diff --git a/mainui/menu_multiplayer.cpp b/mainui/menu_multiplayer.cpp new file mode 100644 index 0000000..681faef --- /dev/null +++ b/mainui/menu_multiplayer.cpp @@ -0,0 +1,220 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "extdll.h" +#include "basemenu.h" +#include "utils.h" +#include "menu_btnsbmp_table.h" + +#define ART_BANNER "gfx/shell/head_multi" + +#define ID_BACKGROUND 0 +#define ID_BANNER 1 + +#define ID_INTERNETGAMES 2 +#define ID_SPECTATEGAMES 3 +#define ID_LANGAME 4 +#define ID_CUSTOMIZE 5 +#define ID_CONTROLS 6 +#define ID_DONE 7 + +typedef struct +{ + menuFramework_s menu; + + menuBitmap_s background; + menuBitmap_s banner; + + menuPicButton_s internetGames; + menuPicButton_s spectateGames; + menuPicButton_s LANGame; + menuPicButton_s Customize; // playersetup + menuPicButton_s Controls; + menuPicButton_s done; +} uiMultiPlayer_t; + +static uiMultiPlayer_t uiMultiPlayer; + +/* +================= +UI_MultiPlayer_Callback +================= +*/ +static void UI_MultiPlayer_Callback( void *self, int event ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + if( event != QM_ACTIVATED ) + return; + + switch( item->id ) + { + case ID_INTERNETGAMES: + UI_InternetGames_Menu(); + break; + case ID_SPECTATEGAMES: + // UNDONE: not implemented + break; + case ID_LANGAME: + UI_LanGame_Menu(); + break; + case ID_CUSTOMIZE: + UI_PlayerSetup_Menu(); + break; + case ID_CONTROLS: + UI_Controls_Menu(); + break; + case ID_DONE: + UI_PopMenu(); + break; + } +} + +/* +================= +UI_MultiPlayer_Init +================= +*/ +static void UI_MultiPlayer_Init( void ) +{ + memset( &uiMultiPlayer, 0, sizeof( uiMultiPlayer_t )); + + uiMultiPlayer.menu.vidInitFunc = UI_MultiPlayer_Init; + + uiMultiPlayer.background.generic.id = ID_BACKGROUND; + uiMultiPlayer.background.generic.type = QMTYPE_BITMAP; + uiMultiPlayer.background.generic.flags = QMF_INACTIVE; + uiMultiPlayer.background.generic.x = 0; + uiMultiPlayer.background.generic.y = 0; + uiMultiPlayer.background.generic.width = 1024; + uiMultiPlayer.background.generic.height = 768; + uiMultiPlayer.background.pic = ART_BACKGROUND; + + uiMultiPlayer.banner.generic.id = ID_BANNER; + uiMultiPlayer.banner.generic.type = QMTYPE_BITMAP; + uiMultiPlayer.banner.generic.flags = QMF_INACTIVE|QMF_DRAW_ADDITIVE; + uiMultiPlayer.banner.generic.x = UI_BANNER_POSX; + uiMultiPlayer.banner.generic.y = UI_BANNER_POSY; + uiMultiPlayer.banner.generic.width = UI_BANNER_WIDTH; + uiMultiPlayer.banner.generic.height = UI_BANNER_HEIGHT; + uiMultiPlayer.banner.pic = ART_BANNER; + + uiMultiPlayer.internetGames.generic.id = ID_INTERNETGAMES; + uiMultiPlayer.internetGames.generic.type = QMTYPE_BM_BUTTON; + uiMultiPlayer.internetGames.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiMultiPlayer.internetGames.generic.x = 72; + uiMultiPlayer.internetGames.generic.y = 230; + uiMultiPlayer.internetGames.generic.name = "Internet games"; + uiMultiPlayer.internetGames.generic.statusText = "View list of a game internet servers and join the one of your choise"; + uiMultiPlayer.internetGames.generic.callback = UI_MultiPlayer_Callback; + + UI_UtilSetupPicButton( &uiMultiPlayer.internetGames, PC_INET_GAME ); + + uiMultiPlayer.spectateGames.generic.id = ID_SPECTATEGAMES; + uiMultiPlayer.spectateGames.generic.type = QMTYPE_BM_BUTTON; + uiMultiPlayer.spectateGames.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY|QMF_GRAYED; + uiMultiPlayer.spectateGames.generic.x = 72; + uiMultiPlayer.spectateGames.generic.y = 280; + uiMultiPlayer.spectateGames.generic.name = "Spectate games"; + uiMultiPlayer.spectateGames.generic.statusText = "Spectate internet games"; + uiMultiPlayer.spectateGames.generic.callback = UI_MultiPlayer_Callback; + + UI_UtilSetupPicButton( &uiMultiPlayer.spectateGames, PC_SPECTATE_GAMES ); + + uiMultiPlayer.LANGame.generic.id = ID_LANGAME; + uiMultiPlayer.LANGame.generic.type = QMTYPE_BM_BUTTON; + uiMultiPlayer.LANGame.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiMultiPlayer.LANGame.generic.x = 72; + uiMultiPlayer.LANGame.generic.y = 330; + uiMultiPlayer.LANGame.generic.name = "LAN game"; + uiMultiPlayer.LANGame.generic.statusText = "Set up the game on the local area network"; + uiMultiPlayer.LANGame.generic.callback = UI_MultiPlayer_Callback; + + UI_UtilSetupPicButton( &uiMultiPlayer.LANGame, PC_LAN_GAME ); + + uiMultiPlayer.Customize.generic.id = ID_CUSTOMIZE; + uiMultiPlayer.Customize.generic.type = QMTYPE_BM_BUTTON; + uiMultiPlayer.Customize.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiMultiPlayer.Customize.generic.x = 72; + uiMultiPlayer.Customize.generic.y = 380; + uiMultiPlayer.Customize.generic.name = "Customize"; + uiMultiPlayer.Customize.generic.statusText = "Choose your player name, and select visual options for your character"; + uiMultiPlayer.Customize.generic.callback = UI_MultiPlayer_Callback; + + UI_UtilSetupPicButton( &uiMultiPlayer.Customize, PC_CUSTOMIZE ); + + uiMultiPlayer.Controls.generic.id = ID_CONTROLS; + uiMultiPlayer.Controls.generic.type = QMTYPE_BM_BUTTON; + uiMultiPlayer.Controls.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiMultiPlayer.Controls.generic.x = 72; + uiMultiPlayer.Controls.generic.y = 430; + uiMultiPlayer.Controls.generic.name = "Controls"; + uiMultiPlayer.Controls.generic.statusText = "Change keyboard and mouse settings"; + uiMultiPlayer.Controls.generic.callback = UI_MultiPlayer_Callback; + + UI_UtilSetupPicButton( &uiMultiPlayer.Controls, PC_CONTROLS ); + + uiMultiPlayer.done.generic.id = ID_DONE; + uiMultiPlayer.done.generic.type = QMTYPE_BM_BUTTON; + uiMultiPlayer.done.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiMultiPlayer.done.generic.x = 72; + uiMultiPlayer.done.generic.y = 480; + uiMultiPlayer.done.generic.name = "Done"; + uiMultiPlayer.done.generic.statusText = "Go back to the Main Menu"; + uiMultiPlayer.done.generic.callback = UI_MultiPlayer_Callback; + + UI_UtilSetupPicButton( &uiMultiPlayer.done, PC_DONE ); + + UI_AddItem( &uiMultiPlayer.menu, (void *)&uiMultiPlayer.background ); + UI_AddItem( &uiMultiPlayer.menu, (void *)&uiMultiPlayer.banner ); + UI_AddItem( &uiMultiPlayer.menu, (void *)&uiMultiPlayer.internetGames ); + UI_AddItem( &uiMultiPlayer.menu, (void *)&uiMultiPlayer.spectateGames ); + UI_AddItem( &uiMultiPlayer.menu, (void *)&uiMultiPlayer.LANGame ); + UI_AddItem( &uiMultiPlayer.menu, (void *)&uiMultiPlayer.Customize ); + UI_AddItem( &uiMultiPlayer.menu, (void *)&uiMultiPlayer.Controls ); + UI_AddItem( &uiMultiPlayer.menu, (void *)&uiMultiPlayer.done ); +} + +/* +================= +UI_MultiPlayer_Precache +================= +*/ +void UI_MultiPlayer_Precache( void ) +{ + PIC_Load( ART_BACKGROUND ); + PIC_Load( ART_BANNER ); +} + +/* +================= +UI_MultiPlayer_Menu +================= +*/ +void UI_MultiPlayer_Menu( void ) +{ + if ( gMenu.m_gameinfo.gamemode == GAME_SINGLEPLAYER_ONLY ) + return; + + UI_MultiPlayer_Precache(); + UI_MultiPlayer_Init(); + + UI_PushMenu( &uiMultiPlayer.menu ); +} \ No newline at end of file diff --git a/mainui/menu_newgame.cpp b/mainui/menu_newgame.cpp new file mode 100644 index 0000000..358ddfb --- /dev/null +++ b/mainui/menu_newgame.cpp @@ -0,0 +1,329 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "extdll.h" +#include "basemenu.h" +#include "utils.h" +#include "keydefs.h" +#include "menu_btnsbmp_table.h" +#include "menu_strings.h" + +#define ART_BANNER "gfx/shell/head_newgame" + +#define ID_BACKGROUND 0 +#define ID_BANNER 1 + +#define ID_EASY 2 +#define ID_MEDIUM 3 +#define ID_DIFFICULT 4 +#define ID_CANCEL 5 + +#define ID_MSGBOX 6 +#define ID_MSGTEXT 7 +#define ID_YES 130 +#define ID_NO 131 + +typedef struct +{ + menuFramework_s menu; + + menuBitmap_s background; + menuBitmap_s banner; + + menuPicButton_s easy; + menuPicButton_s medium; + menuPicButton_s hard; + menuPicButton_s cancel; + + // newgame prompt dialog + menuAction_s msgBox; + menuAction_s dlgMessage1; + menuPicButton_s yes; + menuPicButton_s no; + + float skill; + +} uiNewGame_t; + +static uiNewGame_t uiNewGame; + +/* +================= +UI_NewGame_StartGame +================= +*/ +static void UI_NewGame_StartGame( float skill ) +{ + if( CVAR_GET_FLOAT( "host_serverstate" ) && CVAR_GET_FLOAT( "maxplayers" ) > 1 ) + HOST_ENDGAME( "end of the game" ); + + CVAR_SET_FLOAT( "skill", skill ); + CVAR_SET_FLOAT( "deathmatch", 0.0f ); + CVAR_SET_FLOAT( "teamplay", 0.0f ); + CVAR_SET_FLOAT( "pausable", 1.0f ); // singleplayer is always allowing pause + CVAR_SET_FLOAT( "maxplayers", 1.0f ); + CVAR_SET_FLOAT( "coop", 0.0f ); + + BACKGROUND_TRACK( NULL, NULL ); + + CLIENT_COMMAND( FALSE, "newgame\n" ); +} + +static void UI_PromptDialog( float skill ) +{ + if ( CL_IsActive( ) == FALSE ) + { + UI_NewGame_StartGame( skill ); + return; + } + + uiNewGame.skill = skill; + + // toggle main menu between active\inactive + // show\hide quit dialog + uiNewGame.easy.generic.flags ^= QMF_INACTIVE; + uiNewGame.medium.generic.flags ^= QMF_INACTIVE; + uiNewGame.hard.generic.flags ^= QMF_INACTIVE; + uiNewGame.cancel.generic.flags ^= QMF_INACTIVE; + + uiNewGame.msgBox.generic.flags ^= QMF_HIDDEN; + uiNewGame.dlgMessage1.generic.flags ^= QMF_HIDDEN; + uiNewGame.no.generic.flags ^= QMF_HIDDEN; + uiNewGame.yes.generic.flags ^= QMF_HIDDEN; + +} + +/* +================= +UI_NewGame_KeyFunc +================= +*/ +static const char *UI_NewGame_KeyFunc( int key, int down ) +{ + if( down && key == K_ESCAPE && !( uiNewGame.dlgMessage1.generic.flags & QMF_HIDDEN )) + { + UI_PromptDialog( 0.0f ); // clear skill + return uiSoundNull; + } + return UI_DefaultKey( &uiNewGame.menu, key, down ); +} + +/* +================= +UI_NewGame_Callback +================= +*/ +static void UI_NewGame_Callback( void *self, int event ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + if( event != QM_ACTIVATED ) + return; + + switch( item->id ) + { + case ID_EASY: + UI_PromptDialog( 1.0f ); + break; + case ID_MEDIUM: + UI_PromptDialog( 2.0f ); + break; + case ID_DIFFICULT: + UI_PromptDialog( 3.0f ); + break; + case ID_CANCEL: + UI_PopMenu(); + break; + case ID_YES: + UI_NewGame_StartGame( uiNewGame.skill ); + break; + case ID_NO: + UI_PromptDialog( 1.0f ); // clear skill + break; + } +} + +/* +================= +UI_MsgBox_Ownerdraw +================= +*/ +static void UI_MsgBox_Ownerdraw( void *self ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + UI_FillRect( item->x, item->y, item->width, item->height, uiPromptBgColor ); +} + +/* +================= +UI_NewGame_Init +================= +*/ +static void UI_NewGame_Init( void ) +{ + memset( &uiNewGame, 0, sizeof( uiNewGame_t )); + + uiNewGame.menu.vidInitFunc = UI_NewGame_Init; + uiNewGame.menu.keyFunc = UI_NewGame_KeyFunc; + + uiNewGame.background.generic.id = ID_BACKGROUND; + uiNewGame.background.generic.type = QMTYPE_BITMAP; + uiNewGame.background.generic.flags = QMF_INACTIVE; + uiNewGame.background.generic.x = 0; + uiNewGame.background.generic.y = 0; + uiNewGame.background.generic.width = 1024; + uiNewGame.background.generic.height = 768; + uiNewGame.background.pic = ART_BACKGROUND; + + uiNewGame.banner.generic.id = ID_BANNER; + uiNewGame.banner.generic.type = QMTYPE_BITMAP; + uiNewGame.banner.generic.flags = QMF_INACTIVE|QMF_DRAW_ADDITIVE; + uiNewGame.banner.generic.x = UI_BANNER_POSX; + uiNewGame.banner.generic.y = UI_BANNER_POSY; + uiNewGame.banner.generic.width = UI_BANNER_WIDTH; + uiNewGame.banner.generic.height = UI_BANNER_HEIGHT; + uiNewGame.banner.pic = ART_BANNER; + + uiNewGame.easy.generic.id = ID_EASY; + uiNewGame.easy.generic.type = QMTYPE_BM_BUTTON; + uiNewGame.easy.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiNewGame.easy.generic.name = "Easy"; + uiNewGame.easy.generic.statusText = MenuStrings[HINT_SKILL_EASY]; + uiNewGame.easy.generic.x = 72; + uiNewGame.easy.generic.y = 230; + uiNewGame.easy.generic.callback = UI_NewGame_Callback; + + UI_UtilSetupPicButton( &uiNewGame.easy, PC_EASY ); + + uiNewGame.medium.generic.id = ID_MEDIUM; + uiNewGame.medium.generic.type = QMTYPE_BM_BUTTON; + uiNewGame.medium.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiNewGame.medium.generic.name = "Medium"; + uiNewGame.medium.generic.statusText = MenuStrings[HINT_SKILL_NORMAL]; + uiNewGame.medium.generic.x = 72; + uiNewGame.medium.generic.y = 280; + uiNewGame.medium.generic.callback = UI_NewGame_Callback; + + UI_UtilSetupPicButton( &uiNewGame.medium, PC_MEDIUM ); + + uiNewGame.hard.generic.id = ID_DIFFICULT; + uiNewGame.hard.generic.type = QMTYPE_BM_BUTTON; + uiNewGame.hard.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiNewGame.hard.generic.name = "Difficult"; + uiNewGame.hard.generic.statusText = MenuStrings[HINT_SKILL_HARD]; + uiNewGame.hard.generic.x = 72; + uiNewGame.hard.generic.y = 330; + uiNewGame.hard.generic.callback = UI_NewGame_Callback; + + UI_UtilSetupPicButton( &uiNewGame.hard, PC_DIFFICULT ); + + uiNewGame.cancel.generic.id = ID_CANCEL; + uiNewGame.cancel.generic.type = QMTYPE_BM_BUTTON; + uiNewGame.cancel.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiNewGame.cancel.generic.name = "Cancel"; + uiNewGame.cancel.generic.statusText = "Go back to the main Menu"; + uiNewGame.cancel.generic.x = 72; + uiNewGame.cancel.generic.y = 380; + uiNewGame.cancel.generic.callback = UI_NewGame_Callback; + + UI_UtilSetupPicButton( &uiNewGame.cancel, PC_CANCEL ); + + uiNewGame.msgBox.generic.id = ID_MSGBOX; + uiNewGame.msgBox.generic.type = QMTYPE_ACTION; + uiNewGame.msgBox.generic.flags = QMF_INACTIVE|QMF_HIDDEN; + uiNewGame.msgBox.generic.ownerdraw = UI_MsgBox_Ownerdraw; // just a fill rectangle + uiNewGame.msgBox.generic.x = 192; + uiNewGame.msgBox.generic.y = 256; + uiNewGame.msgBox.generic.width = 640; + uiNewGame.msgBox.generic.height = 256; + + uiNewGame.dlgMessage1.generic.id = ID_MSGTEXT; + uiNewGame.dlgMessage1.generic.type = QMTYPE_ACTION; + uiNewGame.dlgMessage1.generic.flags = QMF_INACTIVE|QMF_HIDDEN|QMF_DROPSHADOW|QMF_CENTER_JUSTIFY; + uiNewGame.dlgMessage1.generic.name = MenuStrings[HINT_RESTART_GAME]; + uiNewGame.dlgMessage1.generic.x = 192; + uiNewGame.dlgMessage1.generic.y = 280; + uiNewGame.dlgMessage1.generic.width = 640; + uiNewGame.dlgMessage1.generic.height = 256; + + uiNewGame.yes.generic.id = ID_YES; + uiNewGame.yes.generic.type = QMTYPE_BM_BUTTON; + uiNewGame.yes.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_HIDDEN|QMF_DROPSHADOW; + uiNewGame.yes.generic.name = "Ok"; + uiNewGame.yes.generic.x = 380; + uiNewGame.yes.generic.y = 460; + uiNewGame.yes.generic.callback = UI_NewGame_Callback; + + UI_UtilSetupPicButton( &uiNewGame.yes, PC_OK ); + + uiNewGame.no.generic.id = ID_NO; + uiNewGame.no.generic.type = QMTYPE_BM_BUTTON; + uiNewGame.no.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_HIDDEN|QMF_DROPSHADOW; + uiNewGame.no.generic.name = "Cancel"; + uiNewGame.no.generic.x = 530; + uiNewGame.no.generic.y = 460; + uiNewGame.no.generic.callback = UI_NewGame_Callback; + + UI_UtilSetupPicButton( &uiNewGame.no, PC_CANCEL ); + + UI_AddItem( &uiNewGame.menu, (void *)&uiNewGame.background ); + UI_AddItem( &uiNewGame.menu, (void *)&uiNewGame.banner ); + UI_AddItem( &uiNewGame.menu, (void *)&uiNewGame.easy ); + UI_AddItem( &uiNewGame.menu, (void *)&uiNewGame.medium ); + UI_AddItem( &uiNewGame.menu, (void *)&uiNewGame.hard ); + UI_AddItem( &uiNewGame.menu, (void *)&uiNewGame.cancel ); + UI_AddItem( &uiNewGame.menu, (void *)&uiNewGame.msgBox ); + UI_AddItem( &uiNewGame.menu, (void *)&uiNewGame.dlgMessage1 ); + UI_AddItem( &uiNewGame.menu, (void *)&uiNewGame.no ); + UI_AddItem( &uiNewGame.menu, (void *)&uiNewGame.yes ); +} + +/* +================= +UI_NewGame_Precache +================= +*/ +void UI_NewGame_Precache( void ) +{ + PIC_Load( ART_BACKGROUND ); + PIC_Load( ART_BANNER ); +} + +/* +================= +UI_NewGame_Menu +================= +*/ +void UI_NewGame_Menu( void ) +{ + if( gMenu.m_gameinfo.gamemode == GAME_MULTIPLAYER_ONLY ) + { + // completely ignore save\load menus for multiplayer_only + return; + } + + if( !CheckGameDll( )) return; + + UI_NewGame_Precache(); + UI_NewGame_Init(); + + UI_PushMenu( &uiNewGame.menu ); +} \ No newline at end of file diff --git a/mainui/menu_playersetup.cpp b/mainui/menu_playersetup.cpp new file mode 100644 index 0000000..96dc0c7 --- /dev/null +++ b/mainui/menu_playersetup.cpp @@ -0,0 +1,550 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "mathlib.h" +#include "extdll.h" +#include "const.h" +#include "basemenu.h" +#include "utils.h" +#include "keydefs.h" +#include "ref_params.h" +#include "cl_entity.h" +#include "entity_types.h" +#include "menu_btnsbmp_table.h" + +#define ART_BANNER "gfx/shell/head_customize" + +#define ID_BACKGROUND 0 +#define ID_BANNER 1 +#define ID_DONE 2 +#define ID_ADVOPTIONS 3 +#define ID_VIEW 4 +#define ID_NAME 5 +#define ID_MODEL 6 +#define ID_TOPCOLOR 7 +#define ID_BOTTOMCOLOR 8 +#define ID_HIMODELS 9 +#define ID_SHOWMODELS 10 + +#define MAX_PLAYERMODELS 100 + +typedef struct +{ + char models[MAX_PLAYERMODELS][CS_SIZE]; + int num_models; + char currentModel[CS_SIZE]; + + ref_viewpass_t rvp; + cl_entity_t *ent; + + menuFramework_s menu; + + menuBitmap_s background; + menuBitmap_s banner; + + menuPicButton_s done; + menuPicButton_s AdvOptions; + menuBitmap_s view; + + menuCheckBox_s showModels; + menuCheckBox_s hiModels; + menuSlider_s topColor; + menuSlider_s bottomColor; + + menuField_s name; + menuSpinControl_s model; +} uiPlayerSetup_t; + +static uiPlayerSetup_t uiPlayerSetup; +static HIMAGE playerImage = 0; // keep actual +static char lastImage[256]; + +/* +================= +UI_PlayerSetup_CalcFov + +assume refdef is valid +================= +*/ +static void UI_PlayerSetup_CalcFov( ref_viewpass_t *rvp ) +{ + rvp->fov_x = 40.0f; + float x = rvp->viewport[2] / tan( DEG2RAD( rvp->fov_x ) * 0.5f ); + float half_fov_y = atan( rvp->viewport[3] / x ); + rvp->fov_y = RAD2DEG( half_fov_y ) * 2; +} + +/* +================= +UI_PlayerSetup_FindModels +================= +*/ +static void UI_PlayerSetup_FindModels( void ) +{ + char name[256], path[256]; + char **filenames; + int i, numFiles; + + uiPlayerSetup.num_models = 0; + + // Get file list + filenames = FS_SEARCH( "models/player/*", &numFiles, TRUE ); + if( !numFiles ) filenames = FS_SEARCH( "models/player/*", &numFiles, FALSE ); +#if 1 + // add default singleplayer model + strcpy( uiPlayerSetup.models[uiPlayerSetup.num_models], "player" ); + uiPlayerSetup.num_models++; +#endif + // build the model list + for( i = 0; i < numFiles; i++ ) + { + COM_FileBase( filenames[i], name ); + sprintf( path, "models/player/%s/%s.mdl", name, name ); + if( !FILE_EXISTS( path )) continue; + + strcpy( uiPlayerSetup.models[uiPlayerSetup.num_models], name ); + uiPlayerSetup.num_models++; + } +} + +/* +================= +UI_PlayerSetup_GetConfig +================= +*/ +static void UI_PlayerSetup_GetConfig( void ) +{ + int i; + + strncpy( uiPlayerSetup.name.buffer, CVAR_GET_STRING( "name" ), sizeof( uiPlayerSetup.name.buffer )); + + // find models + UI_PlayerSetup_FindModels(); + + // select current model + for( i = 0; i < uiPlayerSetup.num_models; i++ ) + { + if( !stricmp( uiPlayerSetup.models[i], CVAR_GET_STRING( "model" ))) + { + uiPlayerSetup.model.curValue = (float)i; + break; + } + } + + if( gMenu.m_gameinfo.flags & GFL_NOMODELS ) + uiPlayerSetup.model.curValue = 0.0f; // force to default + + strcpy( uiPlayerSetup.currentModel, uiPlayerSetup.models[(int)uiPlayerSetup.model.curValue] ); + uiPlayerSetup.model.maxValue = (float)(uiPlayerSetup.num_models - 1); + + uiPlayerSetup.topColor.curValue = CVAR_GET_FLOAT( "topcolor" ) / 255; + uiPlayerSetup.bottomColor.curValue = CVAR_GET_FLOAT( "bottomcolor" ) / 255; + + if( CVAR_GET_FLOAT( "cl_himodels" )) + uiPlayerSetup.hiModels.enabled = 1; + + if( CVAR_GET_FLOAT( "ui_showmodels" )) + uiPlayerSetup.showModels.enabled = 1; +} + +/* +================= +UI_PlayerSetup_SetConfig +================= +*/ +static void UI_PlayerSetup_SetConfig( void ) +{ + CVAR_SET_STRING( "name", uiPlayerSetup.name.buffer ); + CVAR_SET_STRING( "model", uiPlayerSetup.currentModel ); + CVAR_SET_FLOAT( "topcolor", (int)(uiPlayerSetup.topColor.curValue * 255 )); + CVAR_SET_FLOAT( "bottomcolor", (int)(uiPlayerSetup.bottomColor.curValue * 255 )); + CVAR_SET_FLOAT( "cl_himodels", uiPlayerSetup.hiModels.enabled ); + CVAR_SET_FLOAT( "ui_showmodels", uiPlayerSetup.showModels.enabled ); +} + +/* +================= +UI_PlayerSetup_UpdateConfig +================= +*/ +static void UI_PlayerSetup_UpdateConfig( void ) +{ + char path[256], name[256]; + char newImage[256]; + int topColor, bottomColor; + + // see if the model has changed + if( stricmp( uiPlayerSetup.currentModel, uiPlayerSetup.models[(int)uiPlayerSetup.model.curValue] )) + { + strcpy( uiPlayerSetup.currentModel, uiPlayerSetup.models[(int)uiPlayerSetup.model.curValue] ); + } + + uiPlayerSetup.model.generic.name = uiPlayerSetup.models[(int)uiPlayerSetup.model.curValue]; + strcpy( name, uiPlayerSetup.models[(int)uiPlayerSetup.model.curValue] ); + + if( !stricmp( name, "player" )) + { + strcpy( path, "models/player.mdl" ); + newImage[0] = '\0'; + } + else + { + sprintf( path, "models/player/%s/%s.mdl", name, name ); + sprintf( newImage, "models/player/%s/%s.bmp", name, name ); + } + + topColor = (int)(uiPlayerSetup.topColor.curValue * 255 ); + bottomColor = (int)(uiPlayerSetup.bottomColor.curValue * 255 ); + + CVAR_SET_STRING( "model", uiPlayerSetup.currentModel ); + CVAR_SET_FLOAT( "cl_himodels", uiPlayerSetup.hiModels.enabled ); + CVAR_SET_FLOAT( "ui_showmodels", uiPlayerSetup.showModels.enabled ); + CVAR_SET_FLOAT( "topcolor", topColor ); + CVAR_SET_FLOAT( "bottomcolor", bottomColor ); + + // IMPORTANT: always set default model becuase we need to have something valid here + // if you wish draw your playermodel as normal studiomodel please change "models/player.mdl" to path + if( uiPlayerSetup.ent ) + ENGINE_SET_MODEL( uiPlayerSetup.ent, "models/player.mdl" ); + + if( !ui_showmodels->value ) + { + if( stricmp( lastImage, newImage )) + { + if( lastImage[0] && playerImage ) + { + // release old image + PIC_Free( lastImage ); + lastImage[0] = '\0'; + playerImage = 0; + } + + if( stricmp( name, "player" )) + { + sprintf( lastImage, "models/player/%s/%s.bmp", name, name ); + playerImage = PIC_Load( lastImage, PIC_KEEP_SOURCE ); // if present of course + } + else if( lastImage[0] && playerImage ) + { + // release old image + PIC_Free( lastImage ); + lastImage[0] = '\0'; + playerImage = 0; + } + } + + if( playerImage != 0 ) // update remap colors + PIC_Remap( playerImage, topColor, bottomColor ); + } +} + +/* +================= +UI_PlayerSetup_Callback +================= +*/ +static void UI_PlayerSetup_Callback( void *self, int event ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + switch( item->id ) + { + case ID_HIMODELS: + case ID_SHOWMODELS: + if( event == QM_PRESSED ) + ((menuCheckBox_s *)self)->focusPic = UI_CHECKBOX_PRESSED; + else ((menuCheckBox_s *)self)->focusPic = UI_CHECKBOX_FOCUS; + break; + } + + if( event == QM_CHANGED ) + { + UI_PlayerSetup_UpdateConfig(); + return; + } + + if( event != QM_ACTIVATED ) + return; + + switch( item->id ) + { + case ID_DONE: + UI_PlayerSetup_SetConfig(); + UI_PopMenu(); + break; + case ID_ADVOPTIONS: + UI_PlayerSetup_SetConfig(); + UI_GameOptions_Menu(); + break; + } +} + +/* +================= +UI_PlayerSetup_Ownerdraw +================= +*/ +static void UI_PlayerSetup_Ownerdraw( void *self ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + // draw the background + UI_FillRect( item->x, item->y, item->width, item->height, uiPromptBgColor ); + + // draw the rectangle + UI_DrawRectangle( item->x, item->y, item->width, item->height, uiInputFgColor ); + + if( !ui_showmodels->value && playerImage != 0 ) + { + PIC_Set( playerImage, 255, 255, 255, 255 ); + PIC_Draw( item->x, item->y, item->width, item->height ); + } + else + { + R_ClearScene (); + + // clearing body for each frame + uiPlayerSetup.ent->curstate.body = 0; + + // draw the player model + R_AddEntity( ET_NORMAL, uiPlayerSetup.ent ); + R_RenderFrame( &uiPlayerSetup.rvp ); + } +} + +/* +================= +UI_PlayerSetup_Init +================= +*/ +static void UI_PlayerSetup_Init( void ) +{ + bool game_hlRally = FALSE; + int addFlags = 0; + + memset( &uiPlayerSetup, 0, sizeof( uiPlayerSetup_t )); + + // disable playermodel preview for HLRally to prevent crash + if( !stricmp( gMenu.m_gameinfo.gamefolder, "hlrally" )) + game_hlRally = TRUE; + + if( gMenu.m_gameinfo.flags & GFL_NOMODELS ) + addFlags |= QMF_INACTIVE; + + uiPlayerSetup.menu.vidInitFunc = UI_PlayerSetup_Init; + + uiPlayerSetup.background.generic.id = ID_BACKGROUND; + uiPlayerSetup.background.generic.type = QMTYPE_BITMAP; + uiPlayerSetup.background.generic.flags = QMF_INACTIVE; + uiPlayerSetup.background.generic.x = 0; + uiPlayerSetup.background.generic.y = 0; + uiPlayerSetup.background.generic.width = 1024; + uiPlayerSetup.background.generic.height = 768; + uiPlayerSetup.background.pic = ART_BACKGROUND; + + uiPlayerSetup.banner.generic.id = ID_BANNER; + uiPlayerSetup.banner.generic.type = QMTYPE_BITMAP; + uiPlayerSetup.banner.generic.flags = QMF_INACTIVE|QMF_DRAW_ADDITIVE; + uiPlayerSetup.banner.generic.x = UI_BANNER_POSX; + uiPlayerSetup.banner.generic.y = UI_BANNER_POSY; + uiPlayerSetup.banner.generic.width = UI_BANNER_WIDTH; + uiPlayerSetup.banner.generic.height = UI_BANNER_HEIGHT; + uiPlayerSetup.banner.pic = ART_BANNER; + + uiPlayerSetup.done.generic.id = ID_DONE; + uiPlayerSetup.done.generic.type = QMTYPE_BM_BUTTON; + uiPlayerSetup.done.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiPlayerSetup.done.generic.x = 72; + uiPlayerSetup.done.generic.y = 230; + uiPlayerSetup.done.generic.name = "Done"; + uiPlayerSetup.done.generic.statusText = "Go back to the Multiplayer Menu"; + uiPlayerSetup.done.generic.callback = UI_PlayerSetup_Callback; + + UI_UtilSetupPicButton( &uiPlayerSetup.done, PC_DONE ); + + uiPlayerSetup.AdvOptions.generic.id = ID_ADVOPTIONS; + uiPlayerSetup.AdvOptions.generic.type = QMTYPE_BM_BUTTON; + uiPlayerSetup.AdvOptions.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiPlayerSetup.AdvOptions.generic.x = 72; + uiPlayerSetup.AdvOptions.generic.y = 280; + uiPlayerSetup.AdvOptions.generic.name = "Adv. Options"; + uiPlayerSetup.AdvOptions.generic.statusText = "Configure handness, fov and other advanced options"; + uiPlayerSetup.AdvOptions.generic.callback = UI_PlayerSetup_Callback; + + UI_UtilSetupPicButton( &uiPlayerSetup.AdvOptions, PC_ADV_OPT ); + + uiPlayerSetup.view.generic.id = ID_VIEW; + uiPlayerSetup.view.generic.type = QMTYPE_BITMAP; + uiPlayerSetup.view.generic.flags = QMF_INACTIVE; + uiPlayerSetup.view.generic.x = 660; + uiPlayerSetup.view.generic.y = 260; + uiPlayerSetup.view.generic.width = 260; + uiPlayerSetup.view.generic.height = 320; + uiPlayerSetup.view.generic.ownerdraw = UI_PlayerSetup_Ownerdraw; + + uiPlayerSetup.name.generic.id = ID_NAME; + uiPlayerSetup.name.generic.type = QMTYPE_FIELD; + uiPlayerSetup.name.generic.flags = QMF_CENTER_JUSTIFY|QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiPlayerSetup.name.generic.x = 320; + uiPlayerSetup.name.generic.y = 260; + uiPlayerSetup.name.generic.width = 256; + uiPlayerSetup.name.generic.height = 36; + uiPlayerSetup.name.generic.callback = UI_PlayerSetup_Callback; + uiPlayerSetup.name.generic.statusText = "Enter your multiplayer display name"; + uiPlayerSetup.name.maxLength = 32; + + uiPlayerSetup.model.generic.id = ID_MODEL; + uiPlayerSetup.model.generic.type = QMTYPE_SPINCONTROL; + uiPlayerSetup.model.generic.flags = QMF_CENTER_JUSTIFY|QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|addFlags; + uiPlayerSetup.model.generic.x = game_hlRally ? 320 : 702; + uiPlayerSetup.model.generic.y = game_hlRally ? 320 : 590; + uiPlayerSetup.model.generic.width = game_hlRally ? 256 : 176; + uiPlayerSetup.model.generic.height = game_hlRally ? 36 : 32; + uiPlayerSetup.model.generic.callback = UI_PlayerSetup_Callback; + uiPlayerSetup.model.generic.statusText = "Select a model for representation in multiplayer"; + uiPlayerSetup.model.minValue = 0; + uiPlayerSetup.model.maxValue = 1; + uiPlayerSetup.model.range = 1; + + uiPlayerSetup.topColor.generic.id = ID_TOPCOLOR; + uiPlayerSetup.topColor.generic.type = QMTYPE_SLIDER; + uiPlayerSetup.topColor.generic.flags = QMF_PULSEIFFOCUS|QMF_DROPSHADOW|addFlags; + uiPlayerSetup.topColor.generic.name = "Top color"; + uiPlayerSetup.topColor.generic.x = 250; + uiPlayerSetup.topColor.generic.y = 550; + uiPlayerSetup.topColor.generic.width = 300; + uiPlayerSetup.topColor.generic.callback = UI_PlayerSetup_Callback; + uiPlayerSetup.topColor.generic.statusText = "Set a player model top color"; + uiPlayerSetup.topColor.minValue = 0.0; + uiPlayerSetup.topColor.maxValue = 1.0; + uiPlayerSetup.topColor.range = 0.05f; + + uiPlayerSetup.bottomColor.generic.id = ID_BOTTOMCOLOR; + uiPlayerSetup.bottomColor.generic.type = QMTYPE_SLIDER; + uiPlayerSetup.bottomColor.generic.flags = QMF_PULSEIFFOCUS|QMF_DROPSHADOW|addFlags; + uiPlayerSetup.bottomColor.generic.name = "Bottom color"; + uiPlayerSetup.bottomColor.generic.x = 250; + uiPlayerSetup.bottomColor.generic.y = 620; + uiPlayerSetup.bottomColor.generic.width = 300; + uiPlayerSetup.bottomColor.generic.callback = UI_PlayerSetup_Callback; + uiPlayerSetup.bottomColor.generic.statusText = "Set a player model bottom color"; + uiPlayerSetup.bottomColor.minValue = 0.0; + uiPlayerSetup.bottomColor.maxValue = 1.0; + uiPlayerSetup.bottomColor.range = 0.05f; + + uiPlayerSetup.showModels.generic.id = ID_SHOWMODELS; + uiPlayerSetup.showModels.generic.type = QMTYPE_CHECKBOX; + uiPlayerSetup.showModels.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_ACT_ONRELEASE|QMF_MOUSEONLY|QMF_DROPSHADOW|addFlags; + uiPlayerSetup.showModels.generic.name = "Show 3D Preview"; + uiPlayerSetup.showModels.generic.x = 72; + uiPlayerSetup.showModels.generic.y = 380; + uiPlayerSetup.showModels.generic.callback = UI_PlayerSetup_Callback; + uiPlayerSetup.showModels.generic.statusText = "show 3D player models instead of preview thumbnails"; + + uiPlayerSetup.hiModels.generic.id = ID_HIMODELS; + uiPlayerSetup.hiModels.generic.type = QMTYPE_CHECKBOX; + uiPlayerSetup.hiModels.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_ACT_ONRELEASE|QMF_MOUSEONLY|QMF_DROPSHADOW|addFlags; + uiPlayerSetup.hiModels.generic.name = "High quality models"; + uiPlayerSetup.hiModels.generic.x = 72; + uiPlayerSetup.hiModels.generic.y = 430; + uiPlayerSetup.hiModels.generic.callback = UI_PlayerSetup_Callback; + uiPlayerSetup.hiModels.generic.statusText = "show hi-res models in multiplayer"; + + UI_PlayerSetup_GetConfig(); + + UI_AddItem( &uiPlayerSetup.menu, (void *)&uiPlayerSetup.background ); + UI_AddItem( &uiPlayerSetup.menu, (void *)&uiPlayerSetup.banner ); + UI_AddItem( &uiPlayerSetup.menu, (void *)&uiPlayerSetup.done ); + UI_AddItem( &uiPlayerSetup.menu, (void *)&uiPlayerSetup.AdvOptions ); + // disable playermodel preview for HLRally to prevent crash + if( game_hlRally == FALSE ) + UI_AddItem( &uiPlayerSetup.menu, (void *)&uiPlayerSetup.view ); + UI_AddItem( &uiPlayerSetup.menu, (void *)&uiPlayerSetup.name ); + + if( !( gMenu.m_gameinfo.flags & GFL_NOMODELS )) + { + UI_AddItem( &uiPlayerSetup.menu, (void *)&uiPlayerSetup.model ); + UI_AddItem( &uiPlayerSetup.menu, (void *)&uiPlayerSetup.topColor ); + UI_AddItem( &uiPlayerSetup.menu, (void *)&uiPlayerSetup.bottomColor ); + UI_AddItem( &uiPlayerSetup.menu, (void *)&uiPlayerSetup.showModels ); + UI_AddItem( &uiPlayerSetup.menu, (void *)&uiPlayerSetup.hiModels ); + } + + // NOTE: must be called after UI_AddItem whan we sure what UI_ScaleCoords is done + uiPlayerSetup.rvp.viewport[0] = uiPlayerSetup.view.generic.x; + uiPlayerSetup.rvp.viewport[1] = uiPlayerSetup.view.generic.y; + uiPlayerSetup.rvp.viewport[2] = uiPlayerSetup.view.generic.width; + uiPlayerSetup.rvp.viewport[3] = uiPlayerSetup.view.generic.height; + + UI_PlayerSetup_CalcFov( &uiPlayerSetup.rvp ); + uiPlayerSetup.ent = GET_MENU_EDICT (); + + if( !uiPlayerSetup.ent ) + return; + + // adjust entity params + uiPlayerSetup.ent->curstate.number = 1; // IMPORTANT: always set playerindex to 1 + uiPlayerSetup.ent->curstate.animtime = gpGlobals->time; // start animation + uiPlayerSetup.ent->curstate.sequence = 1; + uiPlayerSetup.ent->curstate.scale = 1.0f; + uiPlayerSetup.ent->curstate.frame = 0.0f; + uiPlayerSetup.ent->curstate.framerate = 1.0f; + uiPlayerSetup.ent->curstate.effects |= EF_FULLBRIGHT; + uiPlayerSetup.ent->curstate.controller[0] = 127; + uiPlayerSetup.ent->curstate.controller[1] = 127; + uiPlayerSetup.ent->curstate.controller[2] = 127; + uiPlayerSetup.ent->curstate.controller[3] = 127; + uiPlayerSetup.ent->latched.prevcontroller[0] = 127; + uiPlayerSetup.ent->latched.prevcontroller[1] = 127; + uiPlayerSetup.ent->latched.prevcontroller[2] = 127; + uiPlayerSetup.ent->latched.prevcontroller[3] = 127; + uiPlayerSetup.ent->origin[0] = uiPlayerSetup.ent->curstate.origin[0] = 45.0f / tan( DEG2RAD( uiPlayerSetup.rvp.fov_y / 2.0f )); + uiPlayerSetup.ent->origin[2] = uiPlayerSetup.ent->curstate.origin[2] = 2.0f; + uiPlayerSetup.ent->angles[1] = uiPlayerSetup.ent->curstate.angles[1] = 180.0f; + uiPlayerSetup.ent->player = true; // yes, draw me as playermodel +} + +/* +================= +UI_PlayerSetup_Precache +================= +*/ +void UI_PlayerSetup_Precache( void ) +{ + PIC_Load( ART_BACKGROUND ); + PIC_Load( ART_BANNER ); +} + +/* +================= +UI_PlayerSetup_Menu +================= +*/ +void UI_PlayerSetup_Menu( void ) +{ + if ( gMenu.m_gameinfo.gamemode == GAME_SINGLEPLAYER_ONLY ) + return; + + UI_PlayerSetup_Precache(); + UI_PlayerSetup_Init(); + + UI_PlayerSetup_UpdateConfig(); + UI_PushMenu( &uiPlayerSetup.menu ); +} \ No newline at end of file diff --git a/mainui/menu_savegame.cpp b/mainui/menu_savegame.cpp new file mode 100644 index 0000000..2227500 --- /dev/null +++ b/mainui/menu_savegame.cpp @@ -0,0 +1,490 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "extdll.h" +#include "basemenu.h" +#include "utils.h" +#include "keydefs.h" +#include "menu_btnsbmp_table.h" + +#define ART_BANNER "gfx/shell/head_save" + +#define ID_BACKGROUND 0 +#define ID_BANNER 1 +#define ID_SAVE 2 +#define ID_DELETE 3 +#define ID_CANCEL 4 +#define ID_SAVELIST 5 +#define ID_TABLEHINT 6 +#define ID_LEVELSHOT 7 +#define ID_MSGBOX 8 +#define ID_MSGTEXT 9 +#define ID_YES 130 +#define ID_NO 131 + +#define LEVELSHOT_X 72 +#define LEVELSHOT_Y 400 +#define LEVELSHOT_W 192 +#define LEVELSHOT_H 160 + +#define TIME_LENGTH 20 +#define NAME_LENGTH 32+TIME_LENGTH +#define GAMETIME_LENGTH 15+NAME_LENGTH + +typedef struct +{ + char saveName[UI_MAXGAMES][CS_SIZE]; + char delName[UI_MAXGAMES][CS_SIZE]; + char saveDescription[UI_MAXGAMES][256]; + char *saveDescriptionPtr[UI_MAXGAMES]; + + menuFramework_s menu; + + menuBitmap_s background; + menuBitmap_s banner; + menuPicButton_s save; + menuPicButton_s remove; + menuPicButton_s cancel; + + menuScrollList_s savesList; + + menuBitmap_s levelShot; + menuAction_s hintMessage; + char hintText[MAX_HINT_TEXT]; + + // prompt dialog + menuAction_s msgBox; + menuAction_s promptMessage; + menuPicButton_s yes; + menuPicButton_s no; +} uiSaveGame_t; + +static uiSaveGame_t uiSaveGame; + +/* +================= +UI_MsgBox_Ownerdraw +================= +*/ +static void UI_MsgBox_Ownerdraw( void *self ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + UI_FillRect( item->x, item->y, item->width, item->height, uiPromptBgColor ); +} + +static void UI_DeleteDialog( void ) +{ + // toggle main menu between active\inactive + // show\hide remove dialog + uiSaveGame.save.generic.flags ^= QMF_INACTIVE; + uiSaveGame.remove.generic.flags ^= QMF_INACTIVE; + uiSaveGame.cancel.generic.flags ^= QMF_INACTIVE; + uiSaveGame.savesList.generic.flags ^= QMF_INACTIVE; + + uiSaveGame.msgBox.generic.flags ^= QMF_HIDDEN; + uiSaveGame.promptMessage.generic.flags ^= QMF_HIDDEN; + uiSaveGame.no.generic.flags ^= QMF_HIDDEN; + uiSaveGame.yes.generic.flags ^= QMF_HIDDEN; + +} + +/* +================= +UI_SaveGame_KeyFunc +================= +*/ +static const char *UI_SaveGame_KeyFunc( int key, int down ) +{ + if( down && key == K_ESCAPE && uiSaveGame.save.generic.flags & QMF_INACTIVE ) + { + UI_DeleteDialog(); + return uiSoundNull; + } + return UI_DefaultKey( &uiSaveGame.menu, key, down ); +} + +/* +================= +UI_SaveGame_GetGameList +================= +*/ +static void UI_SaveGame_GetGameList( void ) +{ + char comment[256]; + char **filenames; + int i = 0, j, numFiles; + + filenames = FS_SEARCH( "save/*.sav", &numFiles, TRUE ); + + // sort the saves in reverse order (oldest past at the end) + qsort( filenames, numFiles, sizeof( char* ), (cmpfunc)COM_CompareSaves ); + + if ( CL_IsActive() && !gpGlobals->demoplayback ) + { + // create new entry for current save game + strncpy( uiSaveGame.saveName[i], "new", CS_SIZE ); + StringConcat( uiSaveGame.saveDescription[i], "Current", TIME_LENGTH ); + StringConcat( uiSaveGame.saveDescription[i], uiEmptyString, TIME_LENGTH ); // fill remaining entries + StringConcat( uiSaveGame.saveDescription[i], "New Saved Game", NAME_LENGTH ); + StringConcat( uiSaveGame.saveDescription[i], uiEmptyString, NAME_LENGTH ); + StringConcat( uiSaveGame.saveDescription[i], "New", GAMETIME_LENGTH ); + StringConcat( uiSaveGame.saveDescription[i], uiEmptyString, GAMETIME_LENGTH ); + uiSaveGame.saveDescriptionPtr[i] = uiSaveGame.saveDescription[i]; + i++; + } + + for ( j = 0; j < numFiles; i++, j++ ) + { + if ( i >= UI_MAXGAMES ) + break; + + if ( !GET_SAVE_COMMENT( filenames[j], comment )) + { + if ( strlen( comment )) + { + // get name string even if not found - SV_GetComment can be mark saves + // as etc + StringConcat( uiSaveGame.saveDescription[i], uiEmptyString, TIME_LENGTH ); + StringConcat( uiSaveGame.saveDescription[i], comment, NAME_LENGTH ); + StringConcat( uiSaveGame.saveDescription[i], uiEmptyString, NAME_LENGTH ); + uiSaveGame.saveDescriptionPtr[i] = uiSaveGame.saveDescription[i]; + COM_FileBase( filenames[j], uiSaveGame.saveName[i] ); + COM_FileBase( filenames[j], uiSaveGame.delName[i] ); + } + else uiSaveGame.saveDescriptionPtr[i] = NULL; + continue; + } + + // strip path, leave only filename (empty slots doesn't have savename) + COM_FileBase( filenames[j], uiSaveGame.saveName[i] ); + COM_FileBase( filenames[j], uiSaveGame.delName[i] ); + + // fill save desc + StringConcat( uiSaveGame.saveDescription[i], comment + CS_SIZE, TIME_LENGTH ); + StringConcat( uiSaveGame.saveDescription[i], " ", TIME_LENGTH ); + StringConcat( uiSaveGame.saveDescription[i], comment + CS_SIZE + CS_TIME, TIME_LENGTH ); + StringConcat( uiSaveGame.saveDescription[i], uiEmptyString, TIME_LENGTH ); // fill remaining entries + StringConcat( uiSaveGame.saveDescription[i], comment, NAME_LENGTH ); + StringConcat( uiSaveGame.saveDescription[i], uiEmptyString, NAME_LENGTH ); + StringConcat( uiSaveGame.saveDescription[i], comment + CS_SIZE + (CS_TIME * 2), GAMETIME_LENGTH ); + StringConcat( uiSaveGame.saveDescription[i], uiEmptyString, GAMETIME_LENGTH ); + uiSaveGame.saveDescriptionPtr[i] = uiSaveGame.saveDescription[i]; + } + + for ( ; i < UI_MAXGAMES; i++ ) + uiSaveGame.saveDescriptionPtr[i] = NULL; + + uiSaveGame.savesList.itemNames = (const char **)uiSaveGame.saveDescriptionPtr; + + if ( strlen( uiSaveGame.saveName[0] ) == 0 || CL_IsActive() == FALSE ) + uiSaveGame.save.generic.flags |= QMF_GRAYED; + else uiSaveGame.save.generic.flags &= ~QMF_GRAYED; + + if ( strlen( uiSaveGame.delName[0] ) == 0 ) + uiSaveGame.remove.generic.flags |= QMF_GRAYED; + else uiSaveGame.remove.generic.flags &= ~QMF_GRAYED; +} + +/* +================= +UI_SaveGame_Callback +================= +*/ +static void UI_SaveGame_Callback( void *self, int event ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + if( event == QM_CHANGED ) + { + // never overwrite existing saves, because their names was never get collision + if ( strlen( uiSaveGame.saveName[uiSaveGame.savesList.curItem] ) == 0 || CL_IsActive() == FALSE ) + uiSaveGame.save.generic.flags |= QMF_GRAYED; + else uiSaveGame.save.generic.flags &= ~QMF_GRAYED; + + if ( strlen( uiSaveGame.delName[uiSaveGame.savesList.curItem] ) == 0 ) + uiSaveGame.remove.generic.flags |= QMF_GRAYED; + else uiSaveGame.remove.generic.flags &= ~QMF_GRAYED; + return; + } + + if( event != QM_ACTIVATED ) + return; + + switch( item->id ) + { + case ID_CANCEL: + UI_PopMenu(); + break; + case ID_SAVE: + if( strlen( uiSaveGame.saveName[uiSaveGame.savesList.curItem] )) + { + char cmd[128]; + + sprintf( cmd, "save/%s.bmp", uiSaveGame.saveName[uiSaveGame.savesList.curItem] ); + PIC_Free( cmd ); + + sprintf( cmd, "save \"%s\"\n", uiSaveGame.saveName[uiSaveGame.savesList.curItem] ); + CLIENT_COMMAND( FALSE, cmd ); + UI_CloseMenu(); + } + break; + case ID_NO: + case ID_DELETE: + UI_DeleteDialog(); + break; + case ID_YES: + if( strlen( uiSaveGame.delName[uiSaveGame.savesList.curItem] )) + { + char cmd[128]; + sprintf( cmd, "killsave \"%s\"\n", uiSaveGame.delName[uiSaveGame.savesList.curItem] ); + + CLIENT_COMMAND( TRUE, cmd ); + + sprintf( cmd, "save/%s.bmp", uiSaveGame.delName[uiSaveGame.savesList.curItem] ); + PIC_Free( cmd ); + + // restarts the menu + UI_PopMenu(); + UI_SaveGame_Menu(); + return; + } + UI_DeleteDialog(); + break; + } +} + +/* +================= +UI_SaveGame_Ownerdraw +================= +*/ +static void UI_SaveGame_Ownerdraw( void *self ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + if( item->type != QMTYPE_ACTION && item->id == ID_LEVELSHOT ) + { + int x, y, w, h; + + // draw the levelshot + x = LEVELSHOT_X; + y = LEVELSHOT_Y; + w = LEVELSHOT_W; + h = LEVELSHOT_H; + + UI_ScaleCoords( &x, &y, &w, &h ); + + if( strlen( uiSaveGame.saveName[uiSaveGame.savesList.curItem] )) + { + char saveshot[128]; + + sprintf( saveshot, "save/%s.bmp", uiSaveGame.saveName[uiSaveGame.savesList.curItem] ); + + if( !FILE_EXISTS( saveshot )) + UI_DrawPicAdditive( x, y, w, h, uiColorWhite, "gfx/empty.tga" ); + else UI_DrawPic( x, y, w, h, uiColorWhite, saveshot ); + } + else UI_DrawPicAdditive( x, y, w, h, uiColorWhite, "gfx/empty.tga" ); + + // draw the rectangle + UI_DrawRectangle( item->x, item->y, item->width, item->height, uiInputFgColor ); + } +} + +/* +================= +UI_SaveGame_Init +================= +*/ +static void UI_SaveGame_Init( void ) +{ + memset( &uiSaveGame, 0, sizeof( uiSaveGame_t )); + + uiSaveGame.menu.vidInitFunc = UI_SaveGame_Init; + uiSaveGame.menu.keyFunc = UI_SaveGame_KeyFunc; + + StringConcat( uiSaveGame.hintText, "Time", TIME_LENGTH ); + StringConcat( uiSaveGame.hintText, uiEmptyString, TIME_LENGTH ); + StringConcat( uiSaveGame.hintText, "Game", NAME_LENGTH ); + StringConcat( uiSaveGame.hintText, uiEmptyString, NAME_LENGTH ); + StringConcat( uiSaveGame.hintText, "Elapsed time", GAMETIME_LENGTH ); + StringConcat( uiSaveGame.hintText, uiEmptyString, GAMETIME_LENGTH ); + + uiSaveGame.background.generic.id = ID_BACKGROUND; + uiSaveGame.background.generic.type = QMTYPE_BITMAP; + uiSaveGame.background.generic.flags = QMF_INACTIVE; + uiSaveGame.background.generic.x = 0; + uiSaveGame.background.generic.y = 0; + uiSaveGame.background.generic.width = 1024; + uiSaveGame.background.generic.height = 768; + uiSaveGame.background.pic = ART_BACKGROUND; + + uiSaveGame.banner.generic.id = ID_BANNER; + uiSaveGame.banner.generic.type = QMTYPE_BITMAP; + uiSaveGame.banner.generic.flags = QMF_INACTIVE|QMF_DRAW_ADDITIVE; + uiSaveGame.banner.generic.x = UI_BANNER_POSX; + uiSaveGame.banner.generic.y = UI_BANNER_POSY; + uiSaveGame.banner.generic.width = UI_BANNER_WIDTH; + uiSaveGame.banner.generic.height = UI_BANNER_HEIGHT; + uiSaveGame.banner.pic = ART_BANNER; + + uiSaveGame.save.generic.id = ID_SAVE; + uiSaveGame.save.generic.type = QMTYPE_BM_BUTTON; + uiSaveGame.save.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiSaveGame.save.generic.x = 72; + uiSaveGame.save.generic.y = 230; + uiSaveGame.save.generic.name = "Save"; + uiSaveGame.save.generic.statusText = "Save current game"; + uiSaveGame.save.generic.callback = UI_SaveGame_Callback; + + UI_UtilSetupPicButton( &uiSaveGame.save, PC_SAVE_GAME ); + + uiSaveGame.remove.generic.id = ID_DELETE; + uiSaveGame.remove.generic.type = QMTYPE_BM_BUTTON; + uiSaveGame.remove.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiSaveGame.remove.generic.x = 72; + uiSaveGame.remove.generic.y = 280; + uiSaveGame.remove.generic.name = "Delete"; + uiSaveGame.remove.generic.statusText = "Delete saved game"; + uiSaveGame.remove.generic.callback = UI_SaveGame_Callback; + + UI_UtilSetupPicButton( &uiSaveGame.remove, PC_DELETE ); + + uiSaveGame.cancel.generic.id = ID_CANCEL; + uiSaveGame.cancel.generic.type = QMTYPE_BM_BUTTON; + uiSaveGame.cancel.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiSaveGame.cancel.generic.x = 72; + uiSaveGame.cancel.generic.y = 330; + uiSaveGame.cancel.generic.name = "Cancel"; + uiSaveGame.cancel.generic.statusText = "Return back to main menu"; + uiSaveGame.cancel.generic.callback = UI_SaveGame_Callback; + + UI_UtilSetupPicButton( &uiSaveGame.cancel, PC_CANCEL ); + + uiSaveGame.hintMessage.generic.id = ID_TABLEHINT; + uiSaveGame.hintMessage.generic.type = QMTYPE_ACTION; + uiSaveGame.hintMessage.generic.flags = QMF_INACTIVE|QMF_SMALLFONT; + uiSaveGame.hintMessage.generic.color = uiColorHelp; + uiSaveGame.hintMessage.generic.name = uiSaveGame.hintText; + uiSaveGame.hintMessage.generic.x = 360; + uiSaveGame.hintMessage.generic.y = 225; + + uiSaveGame.levelShot.generic.id = ID_LEVELSHOT; + uiSaveGame.levelShot.generic.type = QMTYPE_BITMAP; + uiSaveGame.levelShot.generic.flags = QMF_INACTIVE; + uiSaveGame.levelShot.generic.x = LEVELSHOT_X; + uiSaveGame.levelShot.generic.y = LEVELSHOT_Y; + uiSaveGame.levelShot.generic.width = LEVELSHOT_W; + uiSaveGame.levelShot.generic.height = LEVELSHOT_H; + uiSaveGame.levelShot.generic.ownerdraw = UI_SaveGame_Ownerdraw; + + uiSaveGame.savesList.generic.id = ID_SAVELIST; + uiSaveGame.savesList.generic.type = QMTYPE_SCROLLLIST; + uiSaveGame.savesList.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_SMALLFONT; + uiSaveGame.savesList.generic.x = 360; + uiSaveGame.savesList.generic.y = 255; + uiSaveGame.savesList.generic.width = 640; + uiSaveGame.savesList.generic.height = 440; + uiSaveGame.savesList.generic.callback = UI_SaveGame_Callback; + + uiSaveGame.msgBox.generic.id = ID_MSGBOX; + uiSaveGame.msgBox.generic.type = QMTYPE_ACTION; + uiSaveGame.msgBox.generic.flags = QMF_INACTIVE|QMF_HIDDEN; + uiSaveGame.msgBox.generic.ownerdraw = UI_MsgBox_Ownerdraw; // just a fill rectangle + uiSaveGame.msgBox.generic.x = 192; + uiSaveGame.msgBox.generic.y = 256; + uiSaveGame.msgBox.generic.width = 640; + uiSaveGame.msgBox.generic.height = 256; + + uiSaveGame.promptMessage.generic.id = ID_MSGBOX; + uiSaveGame.promptMessage.generic.type = QMTYPE_ACTION; + uiSaveGame.promptMessage.generic.flags = QMF_INACTIVE|QMF_DROPSHADOW|QMF_HIDDEN; + uiSaveGame.promptMessage.generic.name = "Delete selected game?"; + uiSaveGame.promptMessage.generic.x = 315; + uiSaveGame.promptMessage.generic.y = 280; + + uiSaveGame.yes.generic.id = ID_YES; + uiSaveGame.yes.generic.type = QMTYPE_BM_BUTTON; + uiSaveGame.yes.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_HIDDEN; + uiSaveGame.yes.generic.name = "Ok"; + uiSaveGame.yes.generic.x = 380; + uiSaveGame.yes.generic.y = 460; + uiSaveGame.yes.generic.callback = UI_SaveGame_Callback; + + UI_UtilSetupPicButton( &uiSaveGame.yes, PC_OK ); + + uiSaveGame.no.generic.id = ID_NO; + uiSaveGame.no.generic.type = QMTYPE_BM_BUTTON; + uiSaveGame.no.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_HIDDEN; + uiSaveGame.no.generic.name = "Cancel"; + uiSaveGame.no.generic.x = 530; + uiSaveGame.no.generic.y = 460; + uiSaveGame.no.generic.callback = UI_SaveGame_Callback; + + UI_UtilSetupPicButton( &uiSaveGame.no, PC_CANCEL ); + + UI_SaveGame_GetGameList(); + + UI_AddItem( &uiSaveGame.menu, (void *)&uiSaveGame.background ); + UI_AddItem( &uiSaveGame.menu, (void *)&uiSaveGame.banner ); + UI_AddItem( &uiSaveGame.menu, (void *)&uiSaveGame.save ); + UI_AddItem( &uiSaveGame.menu, (void *)&uiSaveGame.remove ); + UI_AddItem( &uiSaveGame.menu, (void *)&uiSaveGame.cancel ); + UI_AddItem( &uiSaveGame.menu, (void *)&uiSaveGame.hintMessage ); + UI_AddItem( &uiSaveGame.menu, (void *)&uiSaveGame.levelShot ); + UI_AddItem( &uiSaveGame.menu, (void *)&uiSaveGame.savesList ); + UI_AddItem( &uiSaveGame.menu, (void *)&uiSaveGame.msgBox ); + UI_AddItem( &uiSaveGame.menu, (void *)&uiSaveGame.promptMessage ); + UI_AddItem( &uiSaveGame.menu, (void *)&uiSaveGame.no ); + UI_AddItem( &uiSaveGame.menu, (void *)&uiSaveGame.yes ); +} + +/* +================= +UI_SaveGame_Precache +================= +*/ +void UI_SaveGame_Precache( void ) +{ + PIC_Load( ART_BACKGROUND ); + PIC_Load( ART_BANNER ); +} + +/* +================= +UI_SaveGame_Menu +================= +*/ +void UI_SaveGame_Menu( void ) +{ + if( gMenu.m_gameinfo.gamemode == GAME_MULTIPLAYER_ONLY ) + { + // completely ignore save\load menus for multiplayer_only + return; + } + + if( !CheckGameDll( )) return; + + UI_SaveGame_Precache(); + UI_SaveGame_Init(); + + UI_PushMenu( &uiSaveGame.menu ); +} \ No newline at end of file diff --git a/mainui/menu_saveload.cpp b/mainui/menu_saveload.cpp new file mode 100644 index 0000000..c517f7a --- /dev/null +++ b/mainui/menu_saveload.cpp @@ -0,0 +1,192 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "extdll.h" +#include "basemenu.h" +#include "utils.h" +#include "menu_btnsbmp_table.h" + +#define ART_BANNER "gfx/shell/head_saveload" + +#define ID_BACKGROUND 0 +#define ID_BANNER 1 + +#define ID_LOAD 2 +#define ID_SAVE 3 +#define ID_DONE 4 + +#define ID_MSGHINT 5 + +typedef struct +{ + menuFramework_s menu; + + menuBitmap_s background; + menuBitmap_s banner; + + menuPicButton_s save; + menuPicButton_s load; + menuPicButton_s done; + + menuAction_s hintMessage; + char hintText[MAX_HINT_TEXT]; +} uiSaveLoad_t; + +static uiSaveLoad_t uiSaveLoad; + +/* +================= +UI_SaveLoad_Callback +================= +*/ +static void UI_SaveLoad_Callback( void *self, int event ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + if( event != QM_ACTIVATED ) + return; + + switch( item->id ) + { + case ID_LOAD: + UI_LoadGame_Menu(); + break; + case ID_SAVE: + UI_SaveGame_Menu(); + break; + case ID_DONE: + UI_PopMenu(); + break; + } +} + +/* +================= +UI_SaveLoad_Init +================= +*/ +static void UI_SaveLoad_Init( void ) +{ + memset( &uiSaveLoad, 0, sizeof( uiSaveLoad_t )); + + uiSaveLoad.menu.vidInitFunc = UI_SaveLoad_Init; + + strcat( uiSaveLoad.hintText, "During play, you can quickly save your game by pressing " ); + strcat( uiSaveLoad.hintText, KEY_KeynumToString( KEY_GetKey( "save quick" ))); + strcat( uiSaveLoad.hintText, ".\nLoad this game again by pressing " ); + strcat( uiSaveLoad.hintText, KEY_KeynumToString( KEY_GetKey( "load quick" ))); + strcat( uiSaveLoad.hintText, ".\n" ); + + uiSaveLoad.background.generic.id = ID_BACKGROUND; + uiSaveLoad.background.generic.type = QMTYPE_BITMAP; + uiSaveLoad.background.generic.flags = QMF_INACTIVE; + uiSaveLoad.background.generic.x = 0; + uiSaveLoad.background.generic.y = 0; + uiSaveLoad.background.generic.width = 1024; + uiSaveLoad.background.generic.height = 768; + uiSaveLoad.background.pic = ART_BACKGROUND; + + uiSaveLoad.banner.generic.id = ID_BANNER; + uiSaveLoad.banner.generic.type = QMTYPE_BITMAP; + uiSaveLoad.banner.generic.flags = QMF_INACTIVE|QMF_DRAW_ADDITIVE; + uiSaveLoad.banner.generic.x = UI_BANNER_POSX; + uiSaveLoad.banner.generic.y = UI_BANNER_POSY; + uiSaveLoad.banner.generic.width = UI_BANNER_WIDTH; + uiSaveLoad.banner.generic.height = UI_BANNER_HEIGHT; + uiSaveLoad.banner.pic = ART_BANNER; + + uiSaveLoad.load.generic.id = ID_LOAD; + uiSaveLoad.load.generic.type = QMTYPE_BM_BUTTON; + uiSaveLoad.load.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiSaveLoad.load.generic.name = "Load game"; + uiSaveLoad.load.generic.statusText = "Load a previously saved game"; + uiSaveLoad.load.generic.x = 72; + uiSaveLoad.load.generic.y = 230; + uiSaveLoad.load.generic.callback = UI_SaveLoad_Callback; + + UI_UtilSetupPicButton( &uiSaveLoad.load, PC_LOAD_GAME ); + + uiSaveLoad.save.generic.id = ID_SAVE; + uiSaveLoad.save.generic.type = QMTYPE_BM_BUTTON; + uiSaveLoad.save.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiSaveLoad.save.generic.name = "Save game"; + uiSaveLoad.save.generic.statusText = "Save current game"; + uiSaveLoad.save.generic.x = 72; + uiSaveLoad.save.generic.y = 280; + uiSaveLoad.save.generic.callback = UI_SaveLoad_Callback; + + UI_UtilSetupPicButton( &uiSaveLoad.save, PC_SAVE_GAME ); + + uiSaveLoad.done.generic.id = ID_DONE; + uiSaveLoad.done.generic.type = QMTYPE_BM_BUTTON; + uiSaveLoad.done.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiSaveLoad.done.generic.name = "Done"; + uiSaveLoad.done.generic.statusText = "Go back to the Main Menu"; + uiSaveLoad.done.generic.x = 72; + uiSaveLoad.done.generic.y = 330; + uiSaveLoad.done.generic.callback = UI_SaveLoad_Callback; + + UI_UtilSetupPicButton( &uiSaveLoad.done, PC_DONE ); + + uiSaveLoad.hintMessage.generic.id = ID_MSGHINT; + uiSaveLoad.hintMessage.generic.type = QMTYPE_ACTION; + uiSaveLoad.hintMessage.generic.flags = QMF_INACTIVE|QMF_SMALLFONT; + uiSaveLoad.hintMessage.generic.color = uiColorHelp; + uiSaveLoad.hintMessage.generic.name = uiSaveLoad.hintText; + uiSaveLoad.hintMessage.generic.x = 360; + uiSaveLoad.hintMessage.generic.y = 480; + + UI_AddItem( &uiSaveLoad.menu, (void *)&uiSaveLoad.background ); + UI_AddItem( &uiSaveLoad.menu, (void *)&uiSaveLoad.banner ); + UI_AddItem( &uiSaveLoad.menu, (void *)&uiSaveLoad.load ); + UI_AddItem( &uiSaveLoad.menu, (void *)&uiSaveLoad.save ); + UI_AddItem( &uiSaveLoad.menu, (void *)&uiSaveLoad.done ); + UI_AddItem( &uiSaveLoad.menu, (void *)&uiSaveLoad.hintMessage ); +} + +/* +================= +UI_SaveLoad_Precache +================= +*/ +void UI_SaveLoad_Precache( void ) +{ + PIC_Load( ART_BACKGROUND ); + PIC_Load( ART_BANNER ); +} + +/* +================= +UI_SaveLoad_Menu +================= +*/ +void UI_SaveLoad_Menu( void ) +{ + if( gMenu.m_gameinfo.gamemode == GAME_MULTIPLAYER_ONLY ) + { + // completely ignore save\load menus for multiplayer_only + return; + } + + UI_SaveLoad_Precache(); + UI_SaveLoad_Init(); + + UI_PushMenu( &uiSaveLoad.menu ); +} \ No newline at end of file diff --git a/mainui/menu_strings.cpp b/mainui/menu_strings.cpp new file mode 100644 index 0000000..afc993d --- /dev/null +++ b/mainui/menu_strings.cpp @@ -0,0 +1,634 @@ +/* +menu_strings.cpp - custom menu strings +Copyright (C) 2011 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "extdll.h" +#include "basemenu.h" +#include "utils.h" +#include "menu_strings.h" + +char *MenuStrings[HINT_MAXSTRINGS] = +{ +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 10 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 20 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 30 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 40 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 50 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 60 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 70 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 80 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 90 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 100 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 110 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 120 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 130 +"", +"Display mode", +"", +"", +"", +"", +"", +"", +"", +"", // 140 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 150 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 160 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 170 +"Reverse mouse", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 180 +"", +"", +"", +"Mouse sensitivity", +"", +"", +"", +"Return to game.", +"Start a new game.", +"", // 190 +"Load a previously saved game.", +"Load a saved game, save the current game.", +"Change game settings, configure controls", +"", +"", +"", +"", +"", +"", +"", // 200 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 210 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 220 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 230 +"", +"", +"", +"Starting a Hazard Course will exit\nany current game, OK to exit?", +"", // filled in UI_LoadCustomStrings +"Are you sure you want to quit?", +"", +"", +"", +"Starting a new game will exit\nany current game, OK to exit?", // 240 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 250 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 260 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 270 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 280 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 290 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 300 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 310 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 320 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 330 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 340 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 350 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 360 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 370 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 380 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 390 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"Find more about Kunst-Product product lineup", // 400 +"", +"http://cs-mapping.com.ua/forum/forumdisplay.php?f=191", +"", +"", +"", +"", +"", +"", +"", +"", // 410 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 420 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 430 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 440 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 450 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 460 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 470 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 480 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 490 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 500 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 510 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 520 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"Select a custom game", // 530 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 540 +"", +"", +"", +"", +"", +"", +"", +"", +"", +"", // 550 +}; + +void UI_InitAliasStrings( void ) +{ + char token[1024]; + + // some strings needs to be initialized here + sprintf( token, "Quit %s without\nsaving current game?", gMenu.m_gameinfo.title ); + MenuStrings[HINT_QUIT_ACTIVE] = StringCopy( token ); + + sprintf( token, "Learn how to play %s", gMenu.m_gameinfo.title ); + MenuStrings[HINT_HAZARD_COURSE] = StringCopy( token ); + + sprintf( token, "Play %s on the 'easy' skill setting", gMenu.m_gameinfo.title ); + MenuStrings[HINT_SKILL_EASY] = StringCopy( token ); + + sprintf( token, "Play %s on the 'medium' skill setting", gMenu.m_gameinfo.title ); + MenuStrings[HINT_SKILL_NORMAL] = StringCopy( token ); + + sprintf( token, "Play %s on the 'difficult' skill setting", gMenu.m_gameinfo.title ); + MenuStrings[HINT_SKILL_HARD] = StringCopy( token ); + + sprintf( token, "Quit playing %s", gMenu.m_gameinfo.title ); + MenuStrings[HINT_QUIT_BUTTON] = StringCopy( token ); + + sprintf( token, "Search for %s servers, configure character", gMenu.m_gameinfo.title ); + MenuStrings[HINT_MULTIPLAYER] = StringCopy( token ); +} + +void UI_LoadCustomStrings( void ) +{ + char *afile = (char *)LOAD_FILE( "gfx/shell/strings.lst", NULL ); + char *pfile = afile; + char token[1024]; + int string_num; + + UI_InitAliasStrings (); + + if( !afile ) + return; + + while(( pfile = COM_ParseFile( pfile, token )) != NULL ) + { + if( isdigit( token[0] )) + { + string_num = atoi( token ); + + // check for bad stiringnum + if( string_num < 0 ) continue; + if( string_num > ( HINT_MAXSTRINGS - 1 )) + continue; + } + else continue; // invalid declaration ? + + // parse new string + pfile = COM_ParseFile( pfile, token ); + MenuStrings[string_num] = StringCopy( token ); // replace default string with custom + } + + FREE_FILE( afile ); +} \ No newline at end of file diff --git a/mainui/menu_strings.h b/mainui/menu_strings.h new file mode 100644 index 0000000..882037b --- /dev/null +++ b/mainui/menu_strings.h @@ -0,0 +1,39 @@ +/* +menu_strings.h - custom menu strings +Copyright (C) 2011 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#define HINT_DISPLAYMODE 132 +#define HINT_REVERSE_MOUSE 171 +#define HINT_MOUSE_SENSE 184 +#define HINT_RESUME_GAME 188 +#define HINT_NEWGAME 189 +#define HINT_HAZARD_COURSE 190 +#define HINT_LOADGAME 191 +#define HINT_SAVELOADGAME 192 +#define HINT_CONFIGURATION 193 +#define HINT_QUIT_BUTTON 196 +#define HINT_MULTIPLAYER 198 +#define HINT_SKILL_EASY 200 +#define HINT_SKILL_NORMAL 201 +#define HINT_SKILL_HARD 202 +#define HINT_RESTART_HZ 234 +#define HINT_QUIT_ACTIVE 235 +#define HINT_QUIT 236 +#define HINT_RESTART_GAME 240 +#define HINT_PREVIEWS_TEXT 400 +#define HINT_PREVIEWS_CMD 402 // this buton will execute program or open HTML-window +#define HINT_CUSTOM_GAME 530 +#define HINT_MAXSTRINGS 551 // 550 strings allowed + +extern char *MenuStrings[HINT_MAXSTRINGS]; \ No newline at end of file diff --git a/mainui/menu_video.cpp b/mainui/menu_video.cpp new file mode 100644 index 0000000..08e2bf8 --- /dev/null +++ b/mainui/menu_video.cpp @@ -0,0 +1,166 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "extdll.h" +#include "basemenu.h" +#include "utils.h" +#include "menu_btnsbmp_table.h" + +#define ART_BANNER "gfx/shell/head_video" + +#define ID_BACKGROUND 0 +#define ID_BANNER 1 + +#define ID_VIDOPTIONS 2 +#define ID_VIDMODES 3 +#define ID_DONE 4 + +typedef struct +{ + menuFramework_s menu; + + menuBitmap_s background; + menuBitmap_s banner; + + menuPicButton_s vidOptions; + menuPicButton_s vidModes; + menuPicButton_s done; +} uiVideo_t; + +static uiVideo_t uiVideo; + +/* +================= +UI_Video_Callback +================= +*/ +static void UI_Video_Callback( void *self, int event ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + if( event != QM_ACTIVATED ) + return; + + switch( item->id ) + { + case ID_VIDOPTIONS: + UI_VidOptions_Menu(); + break; + case ID_VIDMODES: + UI_VidModes_Menu(); + break; + case ID_DONE: + UI_PopMenu(); + break; + } +} + +/* +================= +UI_Video_Init +================= +*/ +static void UI_Video_Init( void ) +{ + memset( &uiVideo, 0, sizeof( uiVideo_t )); + + uiVideo.menu.vidInitFunc = UI_Video_Init; + + uiVideo.background.generic.id = ID_BACKGROUND; + uiVideo.background.generic.type = QMTYPE_BITMAP; + uiVideo.background.generic.flags = QMF_INACTIVE; + uiVideo.background.generic.x = 0; + uiVideo.background.generic.y = 0; + uiVideo.background.generic.width = 1024; + uiVideo.background.generic.height = 768; + uiVideo.background.pic = ART_BACKGROUND; + + uiVideo.banner.generic.id = ID_BANNER; + uiVideo.banner.generic.type = QMTYPE_BITMAP; + uiVideo.banner.generic.flags = QMF_INACTIVE|QMF_DRAW_ADDITIVE; + uiVideo.banner.generic.x = UI_BANNER_POSX; + uiVideo.banner.generic.y = UI_BANNER_POSY; + uiVideo.banner.generic.width = UI_BANNER_WIDTH; + uiVideo.banner.generic.height = UI_BANNER_HEIGHT; + uiVideo.banner.pic = ART_BANNER; + + uiVideo.vidOptions.generic.id = ID_VIDOPTIONS; + uiVideo.vidOptions.generic.type = QMTYPE_BM_BUTTON; + uiVideo.vidOptions.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiVideo.vidOptions.generic.name = "Video options"; + uiVideo.vidOptions.generic.statusText = "Set video options such as screen size, gamma and image quality."; + uiVideo.vidOptions.generic.x = 72; + uiVideo.vidOptions.generic.y = 230; + uiVideo.vidOptions.generic.callback = UI_Video_Callback; + + UI_UtilSetupPicButton( &uiVideo.vidOptions, PC_VID_OPT ); + + uiVideo.vidModes.generic.id = ID_VIDMODES; + uiVideo.vidModes.generic.type = QMTYPE_BM_BUTTON; + uiVideo.vidModes.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiVideo.vidModes.generic.name = "Video modes"; + uiVideo.vidModes.generic.statusText = "Set video modes and configure 3D accelerators."; + uiVideo.vidModes.generic.x = 72; + uiVideo.vidModes.generic.y = 280; + uiVideo.vidModes.generic.callback = UI_Video_Callback; + + UI_UtilSetupPicButton( &uiVideo.vidModes, PC_VID_MODES ); + + uiVideo.done.generic.id = ID_DONE; + uiVideo.done.generic.type = QMTYPE_BM_BUTTON; + uiVideo.done.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_NOTIFY; + uiVideo.done.generic.name = "Done"; + uiVideo.done.generic.statusText = "Go back to the previous menu"; + uiVideo.done.generic.x = 72; + uiVideo.done.generic.y = 330; + uiVideo.done.generic.callback = UI_Video_Callback; + + UI_UtilSetupPicButton( &uiVideo.done, PC_DONE ); + + UI_AddItem( &uiVideo.menu, (void *)&uiVideo.background ); + UI_AddItem( &uiVideo.menu, (void *)&uiVideo.banner ); + UI_AddItem( &uiVideo.menu, (void *)&uiVideo.vidOptions ); + UI_AddItem( &uiVideo.menu, (void *)&uiVideo.vidModes ); + UI_AddItem( &uiVideo.menu, (void *)&uiVideo.done ); +} + +/* +================= +UI_Video_Precache +================= +*/ +void UI_Video_Precache( void ) +{ + PIC_Load( ART_BACKGROUND ); + PIC_Load( ART_BANNER ); +} + +/* +================= +UI_Video_Menu +================= +*/ +void UI_Video_Menu( void ) +{ + UI_Video_Precache(); + UI_Video_Init(); + + UI_PushMenu( &uiVideo.menu ); +} \ No newline at end of file diff --git a/mainui/menu_vidmodes.cpp b/mainui/menu_vidmodes.cpp new file mode 100644 index 0000000..f46ba6f --- /dev/null +++ b/mainui/menu_vidmodes.cpp @@ -0,0 +1,267 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "extdll.h" +#include "basemenu.h" +#include "utils.h" +#include "menu_btnsbmp_table.h" +#include "menu_strings.h" + +#define ART_BANNER "gfx/shell/head_vidmodes" + +#define ID_BACKGROUND 0 +#define ID_BANNER 1 +#define ID_APPLY 2 +#define ID_DONE 3 +#define ID_VIDMODELIST 4 +#define ID_FULLSCREEN 5 +#define ID_VERTICALSYNC 6 +#define ID_TABLEHINT 7 + +#define MAX_VIDMODES 65 + +typedef struct +{ + const char *videoModesPtr[MAX_VIDMODES]; + + menuFramework_s menu; + + menuBitmap_s background; + menuBitmap_s banner; + menuPicButton_s ok; + menuPicButton_s cancel; + menuCheckBox_s windowed; + menuCheckBox_s vsync; + + menuScrollList_s vidList; + menuAction_s listCaption; +} uiVidModes_t; + +static uiVidModes_t uiVidModes; + +/* +================= +UI_VidModes_GetModesList +================= +*/ +static void UI_VidModes_GetConfig( void ) +{ + for( int i = 0; i < MAX_VIDMODES-1; i++ ) + { + uiVidModes.videoModesPtr[i] = VID_GET_MODE( i ); + if( !uiVidModes.videoModesPtr[i] ) + break; // end of list + } + + uiVidModes.videoModesPtr[i] = NULL; // terminator + + uiVidModes.vidList.itemNames = uiVidModes.videoModesPtr; + uiVidModes.vidList.curItem = CVAR_GET_FLOAT( "vid_mode" ); + + if( !CVAR_GET_FLOAT( "fullscreen" )) + uiVidModes.windowed.enabled = 1; + + if( CVAR_GET_FLOAT( "gl_vsync" )) + uiVidModes.vsync.enabled = 1; +} + +/* +================= +UI_VidModes_SetConfig +================= +*/ +static void UI_VidOptions_SetConfig( void ) +{ + CVAR_SET_FLOAT( "vid_mode", uiVidModes.vidList.curItem ); + CVAR_SET_FLOAT( "fullscreen", !uiVidModes.windowed.enabled ); + CVAR_SET_FLOAT( "gl_vsync", uiVidModes.vsync.enabled ); +} + +/* +================= +UI_VidModes_UpdateConfig +================= +*/ +static void UI_VidOptions_UpdateConfig( void ) +{ + CVAR_SET_FLOAT( "gl_vsync", uiVidModes.vsync.enabled ); +} + +/* +================= +UI_VidModes_Callback +================= +*/ +static void UI_VidModes_Callback( void *self, int event ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + switch( item->id ) + { + case ID_FULLSCREEN: + case ID_VERTICALSYNC: + if( event == QM_PRESSED ) + ((menuCheckBox_s *)self)->focusPic = UI_CHECKBOX_PRESSED; + else ((menuCheckBox_s *)self)->focusPic = UI_CHECKBOX_FOCUS; + break; + } + + if( event == QM_CHANGED ) + { + UI_VidOptions_UpdateConfig(); + return; + } + + if( event != QM_ACTIVATED ) + return; + + switch( item->id ) + { + case ID_DONE: + UI_PopMenu(); + break; + case ID_APPLY: + UI_VidOptions_SetConfig (); + UI_PopMenu(); + break; + } +} + +/* +================= +UI_VidModes_Init +================= +*/ +static void UI_VidModes_Init( void ) +{ + memset( &uiVidModes, 0, sizeof( uiVidModes_t )); + + uiVidModes.menu.vidInitFunc = UI_VidModes_Init; + + uiVidModes.background.generic.id = ID_BACKGROUND; + uiVidModes.background.generic.type = QMTYPE_BITMAP; + uiVidModes.background.generic.flags = QMF_INACTIVE; + uiVidModes.background.generic.x = 0; + uiVidModes.background.generic.y = 0; + uiVidModes.background.generic.width = 1024; + uiVidModes.background.generic.height = 768; + uiVidModes.background.pic = ART_BACKGROUND; + + uiVidModes.banner.generic.id = ID_BANNER; + uiVidModes.banner.generic.type = QMTYPE_BITMAP; + uiVidModes.banner.generic.flags = QMF_INACTIVE|QMF_DRAW_ADDITIVE; + uiVidModes.banner.generic.x = UI_BANNER_POSX; + uiVidModes.banner.generic.y = UI_BANNER_POSY; + uiVidModes.banner.generic.width = UI_BANNER_WIDTH; + uiVidModes.banner.generic.height = UI_BANNER_HEIGHT; + uiVidModes.banner.pic = ART_BANNER; + + uiVidModes.ok.generic.id = ID_APPLY; + uiVidModes.ok.generic.type = QMTYPE_BM_BUTTON; + uiVidModes.ok.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiVidModes.ok.generic.x = 72; + uiVidModes.ok.generic.y = 230; + uiVidModes.ok.generic.name = "Apply"; + uiVidModes.ok.generic.statusText = "Apply changes"; + uiVidModes.ok.generic.callback = UI_VidModes_Callback; + + UI_UtilSetupPicButton( &uiVidModes.ok, PC_OK ); + + uiVidModes.cancel.generic.id = ID_DONE; + uiVidModes.cancel.generic.type = QMTYPE_BM_BUTTON; + uiVidModes.cancel.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiVidModes.cancel.generic.x = 72; + uiVidModes.cancel.generic.y = 280; + uiVidModes.cancel.generic.name = "Done"; + uiVidModes.cancel.generic.statusText = "Return back to previous menu"; + uiVidModes.cancel.generic.callback = UI_VidModes_Callback; + + UI_UtilSetupPicButton( &uiVidModes.cancel, PC_CANCEL ); + + uiVidModes.listCaption.generic.id = ID_TABLEHINT; + uiVidModes.listCaption.generic.type = QMTYPE_BM_BUTTON; + uiVidModes.listCaption.generic.flags = QMF_INACTIVE|QMF_SMALLFONT; + uiVidModes.listCaption.generic.color = uiColorHelp; + uiVidModes.listCaption.generic.name = MenuStrings[HINT_DISPLAYMODE]; + uiVidModes.listCaption.generic.x = 400; + uiVidModes.listCaption.generic.y = 270; + + uiVidModes.vidList.generic.id = ID_VIDMODELIST; + uiVidModes.vidList.generic.type = QMTYPE_SCROLLLIST; + uiVidModes.vidList.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_SMALLFONT; + uiVidModes.vidList.generic.x = 400; + uiVidModes.vidList.generic.y = 300; + uiVidModes.vidList.generic.width = 560; + uiVidModes.vidList.generic.height = 300; + uiVidModes.vidList.generic.callback = UI_VidModes_Callback; + + uiVidModes.windowed.generic.id = ID_FULLSCREEN; + uiVidModes.windowed.generic.type = QMTYPE_CHECKBOX; + uiVidModes.windowed.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_ACT_ONRELEASE|QMF_MOUSEONLY|QMF_DROPSHADOW; + uiVidModes.windowed.generic.name = "Run in a window"; + uiVidModes.windowed.generic.x = 400; + uiVidModes.windowed.generic.y = 620; + uiVidModes.windowed.generic.callback = UI_VidModes_Callback; + uiVidModes.windowed.generic.statusText = "Run game in window mode"; + + uiVidModes.vsync.generic.id = ID_VERTICALSYNC; + uiVidModes.vsync.generic.type = QMTYPE_CHECKBOX; + uiVidModes.vsync.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_ACT_ONRELEASE|QMF_MOUSEONLY|QMF_DROPSHADOW; + uiVidModes.vsync.generic.name = "Vertical sync"; + uiVidModes.vsync.generic.x = 400; + uiVidModes.vsync.generic.y = 670; + uiVidModes.vsync.generic.callback = UI_VidModes_Callback; + uiVidModes.vsync.generic.statusText = "enable vertical synchronization"; + + UI_VidModes_GetConfig(); + + UI_AddItem( &uiVidModes.menu, (void *)&uiVidModes.background ); + UI_AddItem( &uiVidModes.menu, (void *)&uiVidModes.banner ); + UI_AddItem( &uiVidModes.menu, (void *)&uiVidModes.ok ); + UI_AddItem( &uiVidModes.menu, (void *)&uiVidModes.cancel ); + UI_AddItem( &uiVidModes.menu, (void *)&uiVidModes.windowed ); + UI_AddItem( &uiVidModes.menu, (void *)&uiVidModes.vsync ); + UI_AddItem( &uiVidModes.menu, (void *)&uiVidModes.listCaption ); + UI_AddItem( &uiVidModes.menu, (void *)&uiVidModes.vidList ); +} + +/* +================= +UI_VidModes_Precache +================= +*/ +void UI_VidModes_Precache( void ) +{ + PIC_Load( ART_BACKGROUND ); + PIC_Load( ART_BANNER ); +} + +/* +================= +UI_VidModes_Menu +================= +*/ +void UI_VidModes_Menu( void ) +{ + UI_VidModes_Precache(); + UI_VidModes_Init(); + + UI_PushMenu( &uiVidModes.menu ); +} \ No newline at end of file diff --git a/mainui/menu_vidoptions.cpp b/mainui/menu_vidoptions.cpp new file mode 100644 index 0000000..c27aefa --- /dev/null +++ b/mainui/menu_vidoptions.cpp @@ -0,0 +1,369 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "extdll.h" +#include "basemenu.h" +#include "utils.h" +#include "menu_btnsbmp_table.h" +#include "mathlib.h" + +#define ART_BANNER "gfx/shell/head_vidoptions" +#define ART_GAMMA "gfx/shell/gamma" + +#define ID_BACKGROUND 0 +#define ID_BANNER 1 +#define ID_DONE 2 +#define ID_CONFIGLABEL 3 +#define ID_VIDEOCONFIG 4 +#define ID_GAMMA 5 +#define ID_GLARE_REDUCTION 6 +#define ID_SIMPLE_SKY 7 +#define ID_ALLOW_MATERIALS 8 + +typedef struct +{ + int outlineWidth; + menuFramework_s menu; + + menuBitmap_s background; + menuBitmap_s banner; + menuBitmap_s testImage; + + menuPicButton_s done; + + menuAction_s configLabel; + menuSpinControl_s videoConfig; + menuSlider_s gammaIntensity; + menuSlider_s glareReduction; + menuCheckBox_s fastSky; + menuCheckBox_s hiTextures; + + HIMAGE hTestImage; +} uiVidOptions_t; + +static char *videoConfigs[] = +{ + "safemode", + "ultralow", + "low", + "medium", + "high", + "higher", + "ultrahigh", +}; + +static uiVidOptions_t uiVidOptions; + + +/* +================= +UI_VidOptions_GetConfig +================= +*/ +static void UI_VidOptions_GetConfig( void ) +{ + uiVidOptions.glareReduction.curValue = CVAR_GET_FLOAT( "brightness" ); + uiVidOptions.gammaIntensity.curValue = RemapVal( CVAR_GET_FLOAT( "gamma" ), 1.8f, 3.0f, 0.0f, 1.0f ); + PIC_SetGamma( uiVidOptions.hTestImage, 1.0f ); + + // select current video config + for( int i = 0; i < ARRAYSIZE( videoConfigs ); i++ ) + { + if( !stricmp( videoConfigs[i], CVAR_GET_STRING( "video_config" ))) + { + uiVidOptions.videoConfig.generic.name = videoConfigs[i]; + uiVidOptions.videoConfig.curValue = (float)i; + break; + } + } + + if( CVAR_GET_FLOAT( "r_fastsky" )) + uiVidOptions.fastSky.enabled = 1; + + if( CVAR_GET_FLOAT( "host_allow_materials" )) + uiVidOptions.hiTextures.enabled = 1; + + uiVidOptions.outlineWidth = 2; + UI_ScaleCoords( NULL, NULL, &uiVidOptions.outlineWidth, NULL ); +} + +/* +================= +UI_VidOptions_UpdateConfig +================= +*/ +static void UI_VidOptions_UpdateConfig( void ) +{ + uiVidOptions.videoConfig.generic.name = videoConfigs[(int)uiVidOptions.videoConfig.curValue]; + + CVAR_SET_FLOAT( "brightness", uiVidOptions.glareReduction.curValue ); + CVAR_SET_FLOAT( "r_fastsky", uiVidOptions.fastSky.enabled ); + CVAR_SET_FLOAT( "host_allow_materials", uiVidOptions.hiTextures.enabled ); + CVAR_SET_FLOAT( "gamma", RemapVal( uiVidOptions.gammaIntensity.curValue, 0.0f, 1.0f, 1.8f, 3.0f )); + PIC_SetGamma( uiVidOptions.hTestImage, 1.0f ); +} + +static void UI_VidOptions_SetConfig( void ) +{ + CVAR_SET_FLOAT( "brightness", uiVidOptions.glareReduction.curValue ); + CVAR_SET_FLOAT( "r_fastsky", uiVidOptions.fastSky.enabled ); + CVAR_SET_FLOAT( "host_allow_materials", uiVidOptions.hiTextures.enabled ); + CVAR_SET_FLOAT( "gamma", RemapVal( uiVidOptions.gammaIntensity.curValue, 0.0f, 1.0f, 1.8f, 3.0f )); + CVAR_SET_STRING( "video_config", videoConfigs[(int)uiVidOptions.videoConfig.curValue] ); + + char cmd[128]; + sprintf( cmd, "exec scripts/%s.cfg\n", CVAR_GET_STRING( "video_config" )); + + CLIENT_COMMAND( FALSE, cmd ); +} + +/* +================= +UI_VidOptions_Ownerdraw +================= +*/ +static void UI_VidOptions_Ownerdraw( void *self ) +{ + menuCommon_s *item = (menuCommon_s *)self; + int color = 0xFFFF0000; // 255, 0, 0, 255 + int viewport[4]; + int viewsize, size, sb_lines; + + viewsize = CVAR_GET_FLOAT( "viewsize" ); + + if( viewsize >= 120 ) + sb_lines = 0; // no status bar at all + else if( viewsize >= 110 ) + sb_lines = 24; // no inventory + else sb_lines = 48; + + size = min( viewsize, 100 ); + + viewport[2] = item->width * size / 100; + viewport[3] = item->height * size / 100; + + if( viewport[3] > item->height - sb_lines ) + viewport[3] = item->height - sb_lines; + if( viewport[3] > item->height ) + viewport[3] = item->height; + + viewport[2] &= ~7; + viewport[3] &= ~1; + + viewport[0] = (item->width - viewport[2]) / 2; + viewport[1] = (item->height - sb_lines - viewport[3]) / 2; + + UI_DrawPic( item->x + viewport[0], item->y + viewport[1], viewport[2], viewport[3], uiColorWhite, ((menuBitmap_s *)self)->pic ); + UI_DrawRectangleExt( item->x, item->y, item->width, item->height, color, uiVidOptions.outlineWidth ); +} + +/* +================= +UI_VidOptions_Callback +================= +*/ +static void UI_VidOptions_Callback( void *self, int event ) +{ + menuCommon_s *item = (menuCommon_s *)self; + + switch( item->id ) + { + case ID_SIMPLE_SKY: + case ID_ALLOW_MATERIALS: + if( event == QM_PRESSED ) + ((menuCheckBox_s *)self)->focusPic = UI_CHECKBOX_PRESSED; + else ((menuCheckBox_s *)self)->focusPic = UI_CHECKBOX_FOCUS; + break; + } + + if( event == QM_CHANGED ) + { + UI_VidOptions_UpdateConfig(); + return; + } + + if( event != QM_ACTIVATED ) + return; + + switch( item->id ) + { + case ID_DONE: + UI_VidOptions_SetConfig(); + UI_PopMenu(); + break; + } +} + +/* +================= +UI_VidOptions_Init +================= +*/ +static void UI_VidOptions_Init( void ) +{ + memset( &uiVidOptions, 0, sizeof( uiVidOptions_t )); + + uiVidOptions.hTestImage = PIC_Load( ART_GAMMA, PIC_KEEP_SOURCE ); + + uiVidOptions.menu.vidInitFunc = UI_VidOptions_Init; + + uiVidOptions.background.generic.id = ID_BACKGROUND; + uiVidOptions.background.generic.type = QMTYPE_BITMAP; + uiVidOptions.background.generic.flags = QMF_INACTIVE; + uiVidOptions.background.generic.x = 0; + uiVidOptions.background.generic.y = 0; + uiVidOptions.background.generic.width = 1024; + uiVidOptions.background.generic.height = 768; + uiVidOptions.background.pic = ART_BACKGROUND; + + uiVidOptions.banner.generic.id = ID_BANNER; + uiVidOptions.banner.generic.type = QMTYPE_BITMAP; + uiVidOptions.banner.generic.flags = QMF_INACTIVE|QMF_DRAW_ADDITIVE; + uiVidOptions.banner.generic.x = UI_BANNER_POSX; + uiVidOptions.banner.generic.y = UI_BANNER_POSY; + uiVidOptions.banner.generic.width = UI_BANNER_WIDTH; + uiVidOptions.banner.generic.height = UI_BANNER_HEIGHT; + uiVidOptions.banner.pic = ART_BANNER; + + uiVidOptions.testImage.generic.id = ID_BANNER; + uiVidOptions.testImage.generic.type = QMTYPE_BITMAP; + uiVidOptions.testImage.generic.flags = QMF_INACTIVE; + uiVidOptions.testImage.generic.x = 390; + uiVidOptions.testImage.generic.y = 225; + uiVidOptions.testImage.generic.width = 480; + uiVidOptions.testImage.generic.height = 450; + uiVidOptions.testImage.pic = ART_GAMMA; + uiVidOptions.testImage.generic.ownerdraw = UI_VidOptions_Ownerdraw; + + uiVidOptions.done.generic.id = ID_DONE; + uiVidOptions.done.generic.type = QMTYPE_BM_BUTTON; + uiVidOptions.done.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW; + uiVidOptions.done.generic.x = 72; + uiVidOptions.done.generic.y = 435; + uiVidOptions.done.generic.name = "Done"; + uiVidOptions.done.generic.statusText = "Go back to the Video Menu"; + uiVidOptions.done.generic.callback = UI_VidOptions_Callback; + + UI_UtilSetupPicButton( &uiVidOptions.done, PC_DONE ); + + uiVidOptions.configLabel.generic.id = ID_CONFIGLABEL; + uiVidOptions.configLabel.generic.type = QMTYPE_ACTION; + uiVidOptions.configLabel.generic.flags = QMF_INACTIVE|QMF_DROPSHADOW|QMF_LEFT_JUSTIFY; + uiVidOptions.configLabel.generic.name = "Graphics Quality"; + uiVidOptions.configLabel.generic.color = uiColorHelp; + uiVidOptions.configLabel.generic.charWidth = 12; + uiVidOptions.configLabel.generic.charHeight = 24; + uiVidOptions.configLabel.generic.x = 72; + uiVidOptions.configLabel.generic.y = 218; + uiVidOptions.configLabel.generic.width = 156; + uiVidOptions.configLabel.generic.height = 32; + + uiVidOptions.videoConfig.generic.id = ID_VIDEOCONFIG; + uiVidOptions.videoConfig.generic.type = QMTYPE_SPINCONTROL; + uiVidOptions.videoConfig.generic.flags = QMF_CENTER_JUSTIFY|QMF_HIGHLIGHTIFFOCUS|QMF_DROPSHADOW|QMF_SMALLFONT; + uiVidOptions.videoConfig.generic.x = 98; + uiVidOptions.videoConfig.generic.y = 258; + uiVidOptions.videoConfig.generic.width = 148; + uiVidOptions.videoConfig.generic.height = 24; + uiVidOptions.videoConfig.generic.callback = UI_VidOptions_Callback; + uiVidOptions.videoConfig.generic.statusText = "Select the optimal perfomance config"; + uiVidOptions.videoConfig.minValue = 0; + uiVidOptions.videoConfig.maxValue = ARRAYSIZE( videoConfigs ) - 1; + uiVidOptions.videoConfig.range = 1; + + uiVidOptions.gammaIntensity.generic.id = ID_GAMMA; + uiVidOptions.gammaIntensity.generic.type = QMTYPE_SLIDER; + uiVidOptions.gammaIntensity.generic.flags = QMF_PULSEIFFOCUS|QMF_DROPSHADOW; + uiVidOptions.gammaIntensity.generic.name = "Gamma"; + uiVidOptions.gammaIntensity.generic.x = 72; + uiVidOptions.gammaIntensity.generic.y = 340; + uiVidOptions.gammaIntensity.generic.callback = UI_VidOptions_Callback; + uiVidOptions.gammaIntensity.generic.statusText = "Set gamma value (0.5 - 2.3)"; + uiVidOptions.gammaIntensity.minValue = 0.0; + uiVidOptions.gammaIntensity.maxValue = 1.0; + uiVidOptions.gammaIntensity.range = 0.05f; + + uiVidOptions.glareReduction.generic.id = ID_GLARE_REDUCTION; + uiVidOptions.glareReduction.generic.type = QMTYPE_SLIDER; + uiVidOptions.glareReduction.generic.flags = QMF_PULSEIFFOCUS|QMF_DROPSHADOW; + uiVidOptions.glareReduction.generic.name = "Glare reduction"; + uiVidOptions.glareReduction.generic.x = 72; + uiVidOptions.glareReduction.generic.y = 400; + uiVidOptions.glareReduction.generic.callback = UI_VidOptions_Callback; + uiVidOptions.glareReduction.generic.statusText = "Set glare reduction level"; + uiVidOptions.glareReduction.minValue = 0.0; + uiVidOptions.glareReduction.maxValue = 1.0; + uiVidOptions.glareReduction.range = 0.05f; + + uiVidOptions.fastSky.generic.id = ID_SIMPLE_SKY; + uiVidOptions.fastSky.generic.type = QMTYPE_CHECKBOX; + uiVidOptions.fastSky.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_ACT_ONRELEASE|QMF_MOUSEONLY|QMF_DROPSHADOW; + uiVidOptions.fastSky.generic.name = "Draw simple sky"; + uiVidOptions.fastSky.generic.x = 72; + uiVidOptions.fastSky.generic.y = 615; + uiVidOptions.fastSky.generic.callback = UI_VidOptions_Callback; + uiVidOptions.fastSky.generic.statusText = "enable/disable fast sky rendering (for old computers)"; + + uiVidOptions.hiTextures.generic.id = ID_ALLOW_MATERIALS; + uiVidOptions.hiTextures.generic.type = QMTYPE_CHECKBOX; + uiVidOptions.hiTextures.generic.flags = QMF_HIGHLIGHTIFFOCUS|QMF_ACT_ONRELEASE|QMF_MOUSEONLY|QMF_DROPSHADOW; + uiVidOptions.hiTextures.generic.name = "Allow materials"; + uiVidOptions.hiTextures.generic.x = 72; + uiVidOptions.hiTextures.generic.y = 665; + uiVidOptions.hiTextures.generic.callback = UI_VidOptions_Callback; + uiVidOptions.hiTextures.generic.statusText = "let engine replace 8-bit textures with full color hi-res prototypes (if present)"; + + UI_VidOptions_GetConfig(); + + UI_AddItem( &uiVidOptions.menu, (void *)&uiVidOptions.background ); + UI_AddItem( &uiVidOptions.menu, (void *)&uiVidOptions.banner ); + UI_AddItem( &uiVidOptions.menu, (void *)&uiVidOptions.done ); + UI_AddItem( &uiVidOptions.menu, (void *)&uiVidOptions.configLabel ); + UI_AddItem( &uiVidOptions.menu, (void *)&uiVidOptions.videoConfig ); + UI_AddItem( &uiVidOptions.menu, (void *)&uiVidOptions.gammaIntensity ); + UI_AddItem( &uiVidOptions.menu, (void *)&uiVidOptions.glareReduction ); + UI_AddItem( &uiVidOptions.menu, (void *)&uiVidOptions.fastSky ); + UI_AddItem( &uiVidOptions.menu, (void *)&uiVidOptions.hiTextures ); + UI_AddItem( &uiVidOptions.menu, (void *)&uiVidOptions.testImage ); +} + +/* +================= +UI_VidOptions_Precache +================= +*/ +void UI_VidOptions_Precache( void ) +{ + PIC_Load( ART_BACKGROUND ); + PIC_Load( ART_BANNER ); +} + +/* +================= +UI_VidOptions_Menu +================= +*/ +void UI_VidOptions_Menu( void ) +{ + UI_VidOptions_Precache(); + UI_VidOptions_Init(); + + UI_PushMenu( &uiVidOptions.menu ); +} \ No newline at end of file diff --git a/mainui/menufont.H b/mainui/menufont.H new file mode 100644 index 0000000..af78b22 --- /dev/null +++ b/mainui/menufont.H @@ -0,0 +1,2553 @@ +/* +menufont.h - built-in menu font +Copyright (C) 2011 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef MENUFONT_H +#define MENUFONT_H + +const byte menufont_bmp[] = +{ + 0x42,0x4d,0x78,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x76,0x00,0x00, + 0x00,0x28,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00, + 0x01,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x02,0x80,0x00,0x00,0x12, + 0x0b,0x00,0x00,0x12,0x0b,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x11,0x11,0x11,0x00,0x22,0x22,0x22, + 0x00,0x33,0x33,0x33,0x00,0x44,0x44,0x44,0x00,0x55,0x55,0x55,0x00, + 0x66,0x66,0x66,0x00,0x77,0x77,0x77,0x00,0x88,0x88,0x88,0x00,0x99, + 0x99,0x99,0x00,0xaa,0xaa,0xaa,0x00,0xbb,0xbb,0xbb,0x00,0xcc,0xcc, + 0xcc,0x00,0xdd,0xdd,0xdd,0x00,0xee,0xee,0xee,0x00,0xff,0xff,0xff, + 0x00,0x21,0x1c,0xfb,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x00, + 0x00,0x11,0x11,0x11,0x11,0x11,0x11,0x10,0x01,0x11,0x11,0x11,0x11, + 0x11,0x9f,0xfb,0x41,0x11,0x11,0x11,0x11,0x10,0x01,0x6f,0xf3,0x00, + 0x01,0x11,0x11,0x10,0x00,0x11,0x11,0x00,0x01,0x11,0x11,0x10,0x00, + 0x00,0x00,0x38,0x51,0x11,0x11,0x11,0x11,0x11,0x10,0x00,0x11,0x11, + 0x11,0x00,0x00,0x00,0x00,0x00,0x00,0x11,0x11,0x00,0x00,0x00,0x00, + 0x00,0x06,0x83,0x11,0x10,0x00,0x00,0x00,0x01,0x11,0x11,0x11,0x00, + 0x00,0x00,0x01,0x11,0x00,0x01,0x11,0x10,0x00,0x00,0x00,0x01,0x11, + 0x11,0x11,0x11,0x11,0x00,0x00,0x11,0x11,0x11,0x10,0x00,0x11,0x11, + 0x00,0x00,0x11,0x11,0x11,0x11,0x00,0x01,0x11,0x10,0x01,0x12,0x10, + 0x0c,0xfb,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x57,0x73,0x00, + 0x00,0x00,0x00,0x00,0x00,0x37,0x72,0x00,0x00,0x00,0x00,0x00,0x7a, + 0xef,0xd1,0x00,0x00,0x00,0x00,0x16,0x72,0x5f,0xf2,0x47,0x40,0x00, + 0x00,0x04,0x77,0x10,0x02,0x77,0x40,0x00,0x00,0x05,0x77,0x77,0x77, + 0xbf,0xa0,0x00,0x00,0x00,0x00,0x00,0x05,0x75,0x00,0x00,0x02,0x77, + 0x77,0x77,0x77,0x77,0x77,0x20,0x02,0x77,0x77,0x77,0x77,0x77,0x7e, + 0xf5,0x00,0x04,0x77,0x77,0x77,0x40,0x00,0x00,0x02,0x77,0x77,0x77, + 0x51,0x00,0x57,0x50,0x00,0x05,0x77,0x77,0x77,0x30,0x00,0x00,0x00, + 0x00,0x02,0x67,0x73,0x00,0x00,0x00,0x05,0x75,0x00,0x00,0x47,0x75, + 0x10,0x00,0x00,0x00,0x67,0x60,0x00,0x27,0x72,0x01,0x10,0x0c,0xfa, + 0x15,0x75,0x00,0x00,0x00,0x00,0x00,0x4d,0xff,0xff,0x91,0x00,0x00, + 0x00,0x00,0x00,0x6f,0xf4,0x00,0x00,0x00,0x00,0x00,0x00,0x3f,0xf6, + 0x00,0x00,0x00,0x01,0xcf,0xff,0xbf,0xfb,0xff,0xf8,0x00,0x00,0x04, + 0xff,0xa0,0x0a,0xff,0x40,0x00,0x00,0x0c,0xff,0xff,0xff,0xff,0xa0, + 0x00,0x00,0x00,0x00,0x00,0x0b,0xfc,0x00,0x00,0x05,0xff,0xff,0xff, + 0xff,0xff,0xff,0x50,0x05,0xff,0xff,0xff,0xff,0xff,0xff,0xf5,0x00, + 0x0a,0xff,0xff,0xff,0xfa,0x00,0x00,0x04,0xff,0xff,0xff,0xfd,0x30, + 0xcf,0xb0,0x00,0x0b,0xff,0xff,0xff,0xf9,0x00,0x00,0x00,0x00,0x6e, + 0xff,0xff,0x91,0x00,0x00,0x0b,0xfb,0x00,0x1b,0xff,0xff,0xd3,0x00, + 0x00,0x00,0x7f,0xf6,0x00,0x4f,0xf5,0x01,0x10,0x0c,0xfd,0xdf,0xff, + 0xc2,0x00,0x00,0x00,0x02,0xef,0xd7,0xaf,0xf7,0x00,0x00,0x00,0x00, + 0x00,0x6f,0xf3,0x00,0x00,0x00,0x00,0x00,0x00,0x2f,0xfc,0x00,0x00, + 0x00,0x08,0xff,0x9a,0xff,0xff,0x8b,0xff,0x50,0x00,0x00,0x8f,0xf4, + 0x5f,0xf8,0x00,0x00,0x00,0x0c,0xfd,0x77,0x8e,0xfd,0x50,0x00,0x00, + 0x00,0x00,0x00,0x0a,0xfc,0x00,0x00,0x05,0xff,0x87,0x9f,0xf9,0x78, + 0xff,0x50,0x05,0xff,0x87,0x9f,0xf9,0x78,0xff,0xa2,0x00,0x09,0xfd, + 0x11,0x17,0xff,0x40,0x00,0x04,0xff,0x51,0x13,0xef,0xa0,0xbf,0xb0, + 0x00,0x0a,0xfc,0x11,0x18,0xff,0x30,0x00,0x00,0x03,0xff,0xd7,0xaf, + 0xf7,0x00,0x00,0x0b,0xfb,0x00,0x9f,0xf9,0x8e,0xfd,0x10,0x00,0x00, + 0x0b,0xfe,0x20,0x3f,0xf5,0x01,0x10,0x0c,0xff,0xc5,0x6e,0xfb,0x00, + 0x00,0x00,0x07,0xff,0x30,0x0c,0xfc,0x00,0x00,0x00,0x00,0x00,0x6f, + 0xf3,0x00,0x00,0x00,0x00,0x00,0x00,0x8f,0xff,0x40,0x00,0x00,0x1e, + 0xfa,0x00,0xbf,0xf8,0x01,0xdf,0xa0,0x00,0x00,0x1d,0xfd,0xef,0xd1, + 0x00,0x00,0x00,0x0c,0xfa,0x00,0x0b,0xfa,0x00,0x00,0x00,0x00,0x00, + 0x11,0x0a,0xfc,0x00,0x00,0x05,0xfe,0x00,0x3f,0xf3,0x01,0xff,0x50, + 0x05,0xff,0x10,0x3f,0xf3,0x00,0xef,0x40,0x00,0x09,0xfc,0x00,0x02, + 0xff,0x60,0x00,0x04,0xff,0x40,0x00,0xaf,0xc0,0xbf,0xb0,0x00,0x0a, + 0xfb,0x00,0x03,0xff,0x50,0x00,0x00,0x04,0xbd,0x30,0x0b,0xfd,0x10, + 0x00,0x0b,0xfa,0x01,0xff,0x80,0x04,0xff,0x60,0x00,0x00,0x03,0xef, + 0xa0,0x2f,0xf5,0x01,0x10,0x0c,0xfe,0x10,0x06,0xff,0x30,0x00,0x00, + 0x0a,0xfc,0x00,0x04,0x52,0x00,0x00,0x00,0x00,0x00,0x6f,0xf3,0x00, + 0x00,0x00,0x00,0x00,0x01,0xef,0xbf,0x90,0x00,0x00,0x3f,0xf5,0x00, + 0x6f,0xf3,0x00,0x9f,0xe1,0x00,0x00,0x04,0xff,0xff,0x40,0x00,0x00, + 0x00,0x0c,0xfa,0x00,0x0c,0xfa,0x00,0x00,0x00,0x00,0x2a,0xff,0xce, + 0xfc,0x00,0x00,0x05,0xff,0x10,0x3f,0xf3,0x02,0xff,0x50,0x05,0xff, + 0x20,0x3f,0xf3,0x01,0xff,0x50,0x00,0x09,0xfd,0x11,0x28,0xff,0x30, + 0x00,0x04,0xff,0x51,0x14,0xef,0x90,0xbf,0xb0,0x00,0x0a,0xfc,0x11, + 0x28,0xff,0x30,0x00,0x00,0x00,0x01,0x01,0x17,0xff,0x20,0x00,0x0b, + 0xfd,0x7a,0xff,0x40,0x01,0xdf,0x90,0x00,0x00,0x00,0x8f,0xfa,0x9f, + 0xf5,0x01,0x10,0x0c,0xfa,0x00,0x03,0xff,0x50,0x00,0x00,0x0b,0xfa, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6f,0xf3,0x00,0x00,0x00, + 0x00,0x00,0x06,0xfc,0x3f,0xf2,0x00,0x00,0x4f,0xf4,0x00,0x5f,0xf2, + 0x00,0x7f,0xf1,0x00,0x00,0x00,0xaf,0xf9,0x00,0x00,0x00,0x00,0x0c, + 0xfa,0x00,0x0c,0xfa,0x00,0x00,0x00,0x01,0xdf,0xe8,0x9e,0xfc,0x00, + 0x00,0x05,0xff,0x10,0x3f,0xf3,0x02,0xff,0x50,0x05,0xff,0x20,0x3f, + 0xf3,0x01,0xff,0x50,0x00,0x09,0xff,0xff,0xff,0xf9,0x00,0x00,0x04, + 0xff,0xff,0xff,0xfd,0x20,0xcf,0xb0,0x00,0x0a,0xff,0xff,0xff,0xf9, + 0x00,0x00,0x00,0x00,0x00,0x1f,0xff,0xff,0x30,0x00,0x0b,0xff,0xff, + 0xff,0x40,0x00,0xcf,0x90,0x00,0x00,0x07,0xff,0xff,0xff,0xf5,0x01, + 0x10,0x0c,0xfa,0x00,0x03,0xff,0x50,0x00,0x00,0x0a,0xfc,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x6f,0xf3,0x00,0x00,0x00,0x00,0x00, + 0x0c,0xf8,0x0b,0xf7,0x00,0x00,0x3f,0xf5,0x00,0x6f,0xf3,0x00,0x9f, + 0xd1,0x00,0x00,0x02,0xef,0xfc,0x10,0x00,0x00,0x00,0x0c,0xfa,0x00, + 0x0c,0xfa,0x00,0x00,0x00,0x05,0xff,0x40,0x0a,0xfc,0x00,0x00,0x05, + 0xff,0x10,0x3f,0xf3,0x02,0xff,0x50,0x05,0xff,0x20,0x3f,0xf3,0x01, + 0xff,0x50,0x00,0x09,0xfe,0x88,0x87,0x40,0x00,0x00,0x04,0xff,0xa8, + 0x88,0x61,0x00,0xcf,0xb0,0x00,0x0a,0xfe,0x88,0x87,0x40,0x00,0x00, + 0x00,0x00,0x00,0x07,0x8a,0xff,0x20,0x00,0x0b,0xfd,0x7a,0xff,0x40, + 0x01,0xdf,0x90,0x00,0x00,0x2f,0xf9,0x21,0x4f,0xf5,0x01,0x10,0x0c, + 0xfe,0x20,0x08,0xff,0x30,0x00,0x00,0x07,0xff,0x20,0x0b,0xb6,0x00, + 0x00,0x00,0x00,0x00,0x5f,0xf3,0x00,0x00,0x00,0x00,0x00,0x4f,0xf3, + 0x06,0xfd,0x10,0x00,0x1e,0xfa,0x00,0xbf,0xf8,0x01,0xdf,0xb0,0x00, + 0x00,0x0a,0xff,0xff,0x80,0x00,0x00,0x00,0x0c,0xfa,0x00,0x0c,0xfa, + 0x00,0x00,0x00,0x07,0xff,0x10,0x0a,0xfc,0x00,0x00,0x05,0xff,0x10, + 0x3f,0xf3,0x02,0xff,0x50,0x05,0xff,0x20,0x3f,0xf3,0x01,0xff,0x50, + 0x00,0x09,0xfc,0x00,0x00,0x00,0x00,0x00,0x04,0xff,0x40,0x00,0x00, + 0x00,0xcf,0xb0,0x00,0x0a,0xfb,0x00,0x00,0x00,0x00,0x00,0x00,0x01, + 0x36,0x20,0x08,0xfd,0x00,0x00,0x0b,0xfa,0x01,0xff,0x80,0x03,0xff, + 0x50,0x00,0x00,0x4f,0xf4,0x00,0x3f,0xf5,0x01,0x10,0x0c,0xff,0xd7, + 0x9f,0xfa,0x00,0x00,0x00,0x02,0xef,0xd7,0xbf,0xf7,0x00,0x00,0x00, + 0x02,0x77,0xaf,0xf9,0x77,0x10,0x00,0x00,0x00,0xaf,0xc0,0x02,0xff, + 0x50,0x00,0x07,0xff,0x9a,0xff,0xff,0x9b,0xff,0x50,0x00,0x00,0x6f, + 0xf7,0x8f,0xf3,0x00,0x00,0x00,0x0c,0xfa,0x00,0x0c,0xfa,0x00,0x00, + 0x00,0x08,0xff,0x10,0x0a,0xfc,0x00,0x00,0x05,0xff,0x10,0x3f,0xf3, + 0x02,0xff,0x50,0x05,0xff,0x20,0x3f,0xf3,0x01,0xff,0x50,0x04,0x7c, + 0xfd,0x00,0x00,0x00,0x00,0x00,0x04,0xff,0x50,0x00,0x00,0x00,0xcf, + 0xb0,0x00,0x0a,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0xff,0xd7, + 0x9f,0xf7,0x00,0x00,0x0b,0xfb,0x00,0x9f,0xf9,0x8d,0xfd,0x10,0x00, + 0x00,0x2f,0xf9,0x11,0x4f,0xf5,0x01,0x10,0x0c,0xfc,0xcf,0xff,0xa1, + 0x00,0x00,0x00,0x00,0x4d,0xff,0xff,0x90,0x00,0x00,0x00,0x05,0xff, + 0xff,0xff,0xff,0x30,0x00,0x00,0x03,0xff,0x70,0x00,0xaf,0xc0,0x00, + 0x01,0xaf,0xff,0xbf,0xfb,0xff,0xf9,0x00,0x00,0x03,0xff,0xc0,0x1e, + 0xfd,0x10,0x00,0x00,0x0c,0xfb,0x00,0x0c,0xfb,0x00,0x00,0x00,0x08, + 0xff,0x10,0x0b,0xfc,0x00,0x00,0x05,0xff,0x10,0x4f,0xf4,0x02,0xff, + 0x50,0x05,0xff,0x20,0x4f,0xf4,0x01,0xff,0x50,0x09,0xff,0xfd,0x00, + 0x00,0x00,0x00,0x00,0x04,0xff,0x50,0x00,0x00,0x00,0xcf,0xb0,0x00, + 0x0b,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6f,0xff,0xff,0x90, + 0x00,0x00,0x0b,0xfb,0x00,0x1a,0xff,0xff,0xc2,0x00,0x00,0x00,0x09, + 0xff,0xff,0xff,0xf5,0x01,0x10,0x05,0x73,0x16,0x75,0x00,0x00,0x00, + 0x00,0x00,0x01,0x67,0x73,0x00,0x00,0x00,0x00,0x02,0x77,0x77,0x77, + 0x77,0x10,0x00,0x00,0x03,0x87,0x20,0x00,0x37,0x71,0x00,0x00,0x05, + 0x73,0x5f,0xe2,0x47,0x50,0x00,0x00,0x04,0x87,0x30,0x04,0x78,0x20, + 0x00,0x00,0x06,0x75,0x00,0x06,0x75,0x00,0x00,0x00,0x04,0x77,0x10, + 0x05,0x76,0x00,0x00,0x02,0x77,0x10,0x27,0x72,0x01,0x77,0x20,0x02, + 0x77,0x10,0x27,0x72,0x01,0x77,0x20,0x04,0x77,0x76,0x00,0x00,0x00, + 0x00,0x00,0x02,0x77,0x20,0x00,0x00,0x00,0x67,0x50,0x00,0x05,0x76, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x67,0x73,0x00,0x00,0x00, + 0x05,0x75,0x00,0x00,0x47,0x75,0x10,0x00,0x00,0x00,0x00,0x48,0x88, + 0x87,0x72,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01, + 0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x10, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x10,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x10,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x01,0x76,0x00,0x00,0x00,0x17,0x71,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x01,0x10,0x00,0x27,0x74,0x04,0x76,0x00, + 0x00,0x00,0x00,0x00,0x47,0x74,0x00,0x00,0x00,0x00,0x02,0x77,0x88, + 0x88,0x72,0x00,0x00,0x00,0x00,0x27,0x72,0x00,0x00,0x00,0x00,0x02, + 0xff,0x00,0x00,0x00,0x0f,0xf1,0x00,0x00,0x00,0x00,0x47,0x75,0x10, + 0x00,0x00,0x03,0x87,0x20,0x37,0x71,0x04,0x77,0x10,0x00,0x00,0x00, + 0x47,0x86,0x20,0x00,0x00,0x00,0x02,0x77,0x20,0x01,0x77,0x20,0x00, + 0x00,0x03,0x77,0x10,0x02,0x77,0x20,0x00,0x00,0x02,0x77,0x20,0x37, + 0x83,0x00,0x00,0x00,0x27,0x75,0x00,0x00,0x57,0x50,0x00,0x00,0x27, + 0x70,0x06,0x72,0x04,0x75,0x00,0x00,0x02,0x77,0x20,0x02,0x77,0x20, + 0x00,0x00,0x00,0x00,0x47,0x74,0x00,0x00,0x00,0x00,0x00,0x67,0x50, + 0x01,0x77,0x40,0x01,0x10,0x04,0xef,0xff,0xbc,0xf9,0x00,0x00,0x00, + 0x00,0x2c,0xff,0xff,0xc2,0x00,0x00,0x00,0x04,0xff,0xff,0xff,0xfe, + 0x40,0x00,0x00,0x00,0x5f,0xf4,0x00,0x00,0x00,0x00,0x02,0xfe,0x55, + 0x55,0x55,0x5f,0xf1,0x00,0x00,0x00,0x2c,0xff,0xff,0xd3,0x00,0x00, + 0x02,0xff,0x90,0x6f,0xf3,0x0d,0xfc,0x10,0x00,0x00,0x2b,0xff,0xff, + 0xf6,0x00,0x00,0x00,0x05,0xff,0xa0,0x02,0xff,0x50,0x00,0x00,0x07, + 0xff,0x90,0x04,0xff,0x40,0x00,0x00,0x05,0xff,0x40,0xaf,0xf2,0x00, + 0x00,0x00,0x5f,0xff,0x70,0x00,0xbf,0xb0,0x00,0x00,0x4f,0xf0,0x3f, + 0xf9,0x08,0xfa,0x00,0x00,0x05,0xff,0x40,0x05,0xff,0x40,0x00,0x00, + 0x00,0x3c,0xff,0xff,0xc2,0x00,0x00,0x00,0x00,0xcf,0xb0,0x02,0xff, + 0x80,0x01,0x10,0x0c,0xff,0x99,0xff,0xf6,0x00,0x00,0x00,0x01,0xcf, + 0xf9,0x8e,0xfd,0x10,0x00,0x00,0x03,0xff,0x51,0x13,0xdf,0xb0,0x00, + 0x00,0x00,0x5f,0xf3,0x00,0x00,0x00,0x00,0x02,0xff,0xff,0xff,0xff, + 0xff,0xf1,0x00,0x00,0x00,0xbf,0xf9,0x8f,0xfc,0x10,0x00,0x00,0x7f, + 0xf1,0x5f,0xf2,0x4f,0xf4,0x00,0x00,0x00,0x9f,0xf4,0x1a,0xff,0x20, + 0x00,0x00,0x05,0xff,0xf5,0x02,0xff,0x50,0x00,0x00,0x06,0xff,0xf3, + 0x03,0xff,0x30,0x00,0x00,0x05,0xff,0x31,0xef,0x60,0x00,0x00,0x00, + 0x27,0xdf,0xc0,0x00,0xbf,0xb0,0x00,0x00,0x3f,0xf0,0x7f,0xfe,0x07, + 0xf9,0x00,0x00,0x05,0xff,0x30,0x05,0xff,0x30,0x00,0x00,0x01,0xdf, + 0xf8,0x8e,0xfd,0x10,0x00,0x00,0x00,0xcf,0xa0,0x02,0xff,0x70,0x01, + 0x10,0x1e,0xf9,0x00,0x6f,0xf6,0x00,0x00,0x00,0x05,0xff,0x60,0x04, + 0xff,0x70,0x00,0x00,0x03,0xff,0x40,0x00,0xaf,0xc0,0x00,0x00,0x00, + 0x5f,0xf3,0x00,0x00,0x00,0x00,0x01,0x7e,0xfc,0x77,0x7b,0xff,0x70, + 0x00,0x00,0x03,0xff,0x70,0x04,0x52,0x00,0x00,0x00,0x1e,0xf7,0x4f, + 0xf2,0xaf,0xb0,0x00,0x00,0x00,0x25,0x40,0x03,0xff,0x40,0x00,0x00, + 0x05,0xff,0xfd,0x12,0xff,0x50,0x00,0x00,0x06,0xff,0xfb,0x03,0xff, + 0x30,0x00,0x00,0x05,0xff,0x25,0xfd,0x10,0x00,0x00,0x00,0x00,0x8f, + 0xd1,0x00,0xbf,0xb0,0x00,0x00,0x3f,0xf1,0xcf,0xef,0x37,0xf9,0x00, + 0x00,0x05,0xff,0x30,0x05,0xff,0x30,0x00,0x00,0x07,0xff,0x40,0x03, + 0xff,0x70,0x00,0x00,0x00,0xcf,0xa0,0x02,0xff,0x70,0x01,0x10,0x0a, + 0xfd,0x30,0x3f,0xf6,0x00,0x00,0x00,0x08,0xfd,0x00,0x00,0xbf,0xb0, + 0x00,0x00,0x03,0xff,0x51,0x14,0xdf,0x80,0x00,0x00,0x00,0x5f,0xf3, + 0x00,0x00,0x00,0x00,0x00,0x06,0xfd,0x00,0x06,0xfe,0x00,0x00,0x00, + 0x06,0xff,0x31,0x10,0x01,0x00,0x00,0x00,0x04,0xee,0xbf,0xfb,0xfd, + 0x20,0x00,0x00,0x00,0x00,0x00,0x19,0xfe,0x20,0x00,0x00,0x05,0xff, + 0xdf,0x71,0xff,0x50,0x00,0x00,0x06,0xff,0xdf,0x53,0xff,0x30,0x00, + 0x00,0x05,0xff,0x5c,0xf6,0x00,0x00,0x00,0x00,0x00,0x8f,0xe1,0x00, + 0xbf,0xb0,0x00,0x00,0x3f,0xe4,0xfc,0x7f,0x87,0xf9,0x00,0x00,0x05, + 0xff,0x41,0x15,0xff,0x30,0x00,0x00,0x0a,0xfc,0x00,0x00,0xbf,0xb0, + 0x00,0x00,0x00,0xcf,0xa0,0x02,0xff,0x70,0x01,0x10,0x02,0xbf,0xfd, + 0xbf,0xf6,0x00,0x00,0x00,0x0a,0xfb,0x00,0x00,0xaf,0xc0,0x00,0x00, + 0x03,0xff,0xff,0xff,0xf9,0x10,0x00,0x00,0x00,0x5f,0xf3,0x00,0x00, + 0x00,0x00,0x00,0x01,0xef,0x40,0x06,0xff,0x10,0x00,0x00,0x07,0xff, + 0xff,0xff,0xfe,0x40,0x00,0x00,0x00,0x8f,0xff,0xff,0xf5,0x00,0x00, + 0x00,0x00,0x00,0x03,0xff,0xc3,0x00,0x00,0x00,0x05,0xff,0x5e,0xe4, + 0xef,0x50,0x00,0x00,0x07,0xfe,0x5f,0xd4,0xff,0x30,0x00,0x00,0x05, + 0xff,0xff,0x90,0x00,0x00,0x00,0x00,0x00,0x8f,0xd1,0x00,0xbf,0xb0, + 0x00,0x00,0x3f,0xe8,0xf8,0x2f,0xd9,0xf9,0x00,0x00,0x05,0xff,0xff, + 0xff,0xff,0x30,0x00,0x00,0x0b,0xfa,0x00,0x00,0xaf,0xc0,0x00,0x00, + 0x00,0xcf,0xa0,0x02,0xff,0x70,0x01,0x10,0x00,0x02,0x69,0xef,0xf6, + 0x00,0x00,0x00,0x0a,0xfd,0x00,0x00,0xbf,0xb0,0x00,0x00,0x03,0xff, + 0xa8,0x8c,0xfc,0x20,0x00,0x00,0x00,0x5f,0xf3,0x00,0x00,0x00,0x00, + 0x00,0x00,0xcf,0x60,0x06,0xfe,0x10,0x00,0x00,0x06,0xff,0x98,0x89, + 0xff,0x40,0x00,0x00,0x02,0xfc,0x7f,0xf5,0xfc,0x00,0x00,0x00,0x00, + 0x00,0x02,0x8e,0xd4,0x00,0x00,0x00,0x05,0xff,0x27,0xfc,0xff,0x50, + 0x00,0x00,0x06,0xfe,0x09,0xfb,0xff,0x30,0x00,0x00,0x05,0xff,0xbf, + 0xb1,0x00,0x00,0x00,0x00,0x00,0x8f,0xd1,0x00,0xbf,0xb0,0x00,0x00, + 0x3f,0xfd,0xf4,0x0b,0xfd,0xf9,0x00,0x00,0x05,0xff,0xff,0xff,0xff, + 0x30,0x00,0x00,0x0a,0xfc,0x00,0x00,0xbf,0xb0,0x00,0x00,0x00,0xcf, + 0xa0,0x02,0xff,0x70,0x01,0x10,0x01,0x45,0x00,0x4f,0xf5,0x00,0x00, + 0x00,0x0a,0xff,0x60,0x04,0xff,0x70,0x00,0x00,0x03,0xff,0x40,0x01, + 0xff,0x70,0x00,0x00,0x00,0x5f,0xf3,0x00,0x00,0x00,0x00,0x00,0x00, + 0xaf,0x80,0x06,0xff,0x10,0x00,0x00,0x03,0xff,0x50,0x06,0xfe,0x10, + 0x00,0x00,0x06,0xf6,0x4f,0xf2,0xaf,0x30,0x00,0x00,0x00,0x00,0x00, + 0x07,0xfc,0x00,0x00,0x00,0x05,0xff,0x21,0xdf,0xff,0x50,0x00,0x00, + 0x06,0xff,0x02,0xef,0xff,0x30,0x00,0x00,0x05,0xff,0x39,0xf4,0x00, + 0x00,0x00,0x00,0x00,0x8f,0xd0,0x00,0xaf,0xb0,0x00,0x00,0x3f,0xff, + 0xd1,0x07,0xff,0xf9,0x00,0x00,0x05,0xff,0x41,0x15,0xff,0x30,0x00, + 0x00,0x06,0xff,0x40,0x03,0xff,0x70,0x00,0x00,0x00,0xcf,0xa0,0x01, + 0xff,0x70,0x01,0x10,0x06,0xff,0x97,0xcf,0xf4,0x00,0x00,0x00,0x0a, + 0xfe,0xf9,0x8e,0xfd,0x10,0x00,0x00,0x03,0xff,0x51,0x15,0xff,0x70, + 0x00,0x00,0x00,0x5f,0xf9,0x77,0x71,0x00,0x00,0x00,0x00,0x9f,0xa4, + 0x49,0xfe,0x10,0x00,0x00,0x00,0xaf,0xe8,0x9f,0xf8,0x00,0x00,0x00, + 0x2c,0xf2,0x5f,0xf2,0x5f,0x91,0x00,0x00,0x00,0x2b,0xc3,0x1b,0xfb, + 0x00,0x00,0x00,0x05,0xff,0x30,0x5f,0xff,0x50,0x00,0x00,0x06,0xff, + 0x10,0x7f,0xff,0x30,0x00,0x00,0x05,0xff,0x33,0xfb,0x10,0x00,0x00, + 0x00,0x00,0x8f,0xe8,0x77,0xdf,0xb0,0x00,0x00,0x3f,0xff,0x90,0x02, + 0xff,0xf9,0x00,0x00,0x05,0xff,0x30,0x05,0xff,0x30,0x00,0x00,0x01, + 0xdf,0xf9,0x8e,0xfd,0x10,0x00,0x00,0x00,0xcf,0xd7,0x78,0xff,0x70, + 0x01,0x10,0x01,0xaf,0xff,0xff,0x90,0x00,0x00,0x00,0x08,0xf6,0xdf, + 0xff,0xc2,0x00,0x00,0x00,0x04,0xff,0xff,0xff,0xfd,0x20,0x00,0x00, + 0x00,0x5f,0xff,0xff,0xf1,0x00,0x00,0x00,0x00,0x9f,0xff,0xff,0xff, + 0x10,0x00,0x00,0x00,0x1b,0xff,0xff,0xa1,0x00,0x00,0x03,0xff,0xa0, + 0x6f,0xf3,0x1d,0xff,0x10,0x00,0x00,0x1b,0xff,0xff,0xe4,0x00,0x00, + 0x00,0x05,0xff,0x30,0x0b,0xff,0x50,0x00,0x00,0x07,0xff,0x10,0x0d, + 0xff,0x40,0x00,0x00,0x05,0xff,0x40,0xbf,0xf3,0x00,0x00,0x00,0x00, + 0x9f,0xff,0xff,0xff,0xb0,0x00,0x00,0x4f,0xff,0x40,0x00,0xcf,0xfa, + 0x00,0x00,0x05,0xff,0x40,0x05,0xff,0x40,0x00,0x00,0x00,0x2c,0xff, + 0xff,0xc2,0x00,0x00,0x00,0x00,0xcf,0xff,0xff,0xff,0x80,0x01,0x10, + 0x00,0x04,0x77,0x74,0x00,0x00,0x00,0x00,0x04,0xf8,0x15,0x64,0x00, + 0x00,0x00,0x00,0x02,0x77,0x88,0x88,0x61,0x00,0x00,0x00,0x00,0x27, + 0x77,0x77,0x71,0x00,0x00,0x00,0x00,0x47,0x77,0x77,0x77,0x10,0x00, + 0x00,0x00,0x00,0x57,0x74,0x00,0x00,0x00,0x02,0x86,0x10,0x37,0x71, + 0x02,0x77,0x10,0x00,0x00,0x00,0x37,0x86,0x20,0x00,0x00,0x00,0x02, + 0x77,0x10,0x02,0x77,0x20,0x00,0x00,0x03,0x77,0x38,0x53,0x77,0x20, + 0x00,0x00,0x02,0x77,0x20,0x17,0x81,0x00,0x00,0x00,0x00,0x47,0x77, + 0x77,0x77,0x50,0x00,0x00,0x27,0x77,0x10,0x00,0x47,0x75,0x00,0x00, + 0x02,0x77,0x20,0x02,0x77,0x20,0x00,0x00,0x00,0x00,0x47,0x74,0x00, + 0x00,0x00,0x00,0x00,0x67,0x77,0x77,0x77,0x40,0x01,0x10,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xcf,0xc7,0x67,0x73,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc9,0xd4,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x10,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x2c,0xff,0xff,0xff,0x30,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x01,0x50,0x34,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x37,0x77,0xaf,0x60,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x77,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x03,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01, + 0x10,0x05,0x76,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x26,0x77, + 0x51,0x00,0x00,0x00,0x00,0x00,0x37,0x71,0x00,0x00,0x00,0x00,0x00, + 0x57,0x72,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x17,0x73,0x00,0x00, + 0x00,0x00,0x68,0x72,0x00,0x00,0x37,0x85,0x00,0x00,0x17,0x77,0x77, + 0x77,0x78,0xff,0x30,0x00,0x00,0x00,0x00,0x00,0x67,0x50,0x00,0x03, + 0x77,0x77,0x77,0x77,0x77,0x77,0x40,0x02,0x77,0x77,0x77,0x77,0x77, + 0x7b,0xf6,0x00,0x06,0x77,0x77,0x77,0x73,0x00,0x00,0x04,0x77,0x77, + 0x76,0x30,0x00,0x67,0x40,0x00,0x17,0x77,0x77,0x77,0x62,0x00,0x00, + 0x00,0x00,0x03,0x77,0x74,0x10,0x00,0x00,0x17,0x73,0x00,0x00,0x37, + 0x73,0x00,0x00,0x00,0x67,0x72,0x00,0x00,0x06,0x74,0x01,0x10,0x0b, + 0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x19,0xff,0xff,0xfe,0x60, + 0x00,0x00,0x00,0x00,0x6f,0xf3,0x00,0x00,0x00,0x00,0x00,0xcf,0xfd, + 0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x3f,0xf8,0x10,0x00,0x00,0x00, + 0x6f,0xfb,0x00,0x00,0xcf,0xf5,0x00,0x00,0x3f,0xff,0xff,0xff,0xff, + 0xff,0x30,0x00,0x00,0x00,0x00,0x00,0xcf,0xb0,0x00,0x05,0xff,0xff, + 0xff,0xff,0xff,0xff,0x80,0x05,0xff,0xff,0xff,0xff,0xff,0xff,0xf6, + 0x00,0x0d,0xff,0xff,0xff,0xff,0x80,0x00,0x09,0xff,0xff,0xff,0xf7, + 0x00,0xdf,0x80,0x00,0x2f,0xff,0xff,0xff,0xff,0x60,0x00,0x00,0x02, + 0xbf,0xff,0xff,0xc3,0x00,0x00,0x2f,0xf7,0x00,0x19,0xff,0xff,0xb2, + 0x00,0x00,0x7f,0xfb,0x00,0x00,0x1e,0xf9,0x01,0x10,0x0a,0xfc,0x00, + 0x00,0x00,0x00,0x00,0x00,0x01,0xbf,0xfb,0x79,0xff,0xf5,0x00,0x00, + 0x00,0x00,0x6f,0xf3,0x00,0x00,0x00,0x00,0x00,0x57,0xdf,0xa0,0x00, + 0x00,0x00,0x00,0x04,0x9d,0xff,0xff,0xea,0x60,0x00,0x00,0x0a,0xff, + 0x60,0x07,0xff,0x90,0x00,0x00,0x3f,0xfa,0x77,0x77,0xbf,0xf8,0x10, + 0x00,0x00,0x00,0x00,0x00,0xcf,0xa0,0x00,0x05,0xfd,0x77,0x9f,0xf8, + 0x77,0xdf,0x60,0x05,0xfd,0x77,0x9f,0xf8,0x77,0xdf,0xb3,0x00,0x0c, + 0xfc,0x77,0x78,0xef,0xf5,0x00,0x08,0xfe,0x87,0x9f,0xff,0x30,0xdf, + 0x70,0x00,0x2f,0xfb,0x77,0x79,0xff,0xf3,0x00,0x00,0x2d,0xff,0xb7, + 0x9f,0xfe,0x30,0x00,0x2f,0xf6,0x00,0x9f,0xfb,0xaf,0xfd,0x10,0x00, + 0x0b,0xff,0x60,0x00,0x1d,0xf8,0x01,0x10,0x0a,0xfc,0x00,0x00,0x00, + 0x00,0x00,0x00,0x07,0xff,0x70,0x00,0x3f,0xfd,0x10,0x00,0x00,0x00, + 0x6f,0xf3,0x00,0x00,0x00,0x00,0x00,0x00,0x2e,0xf3,0x00,0x00,0x00, + 0x00,0x6f,0xff,0xff,0xff,0xff,0xfb,0x10,0x00,0x02,0xef,0xe1,0x2f, + 0xfe,0x10,0x00,0x00,0x3f,0xf5,0x00,0x00,0x6f,0xf1,0x00,0x00,0x00, + 0x00,0x00,0x00,0xcf,0xa0,0x00,0x05,0xfb,0x00,0x2f,0xe1,0x00,0xaf, + 0x50,0x05,0xfb,0x00,0x2f,0xe1,0x00,0xaf,0x60,0x00,0x0d,0xf9,0x00, + 0x00,0x3f,0xfb,0x00,0x09,0xfd,0x00,0x05,0xff,0x80,0xcf,0x70,0x00, + 0x2f,0xf6,0x00,0x00,0x5f,0xf8,0x00,0x00,0x8f,0xf7,0x00,0x04,0xff, + 0xc0,0x00,0x2f,0xf7,0x04,0xff,0x80,0x06,0xff,0x80,0x00,0x03,0xff, + 0xe2,0x00,0x1d,0xf9,0x01,0x10,0x0a,0xfb,0x00,0x00,0x00,0x00,0x00, + 0x00,0x1d,0xfc,0x00,0x00,0x08,0xa4,0x00,0x00,0x00,0x00,0x6f,0xf3, + 0x00,0x00,0x00,0x00,0x00,0x00,0x1e,0xf9,0x00,0x00,0x00,0x03,0xff, + 0xc3,0x3f,0xf7,0x28,0xff,0x70,0x00,0x00,0x6f,0xfa,0xaf,0xf5,0x00, + 0x00,0x00,0x3f,0xf6,0x00,0x00,0x6f,0xf2,0x00,0x00,0x00,0x00,0x11, + 0x00,0xbf,0xa0,0x00,0x05,0xfb,0x00,0x2f,0xe2,0x00,0xaf,0x60,0x05, + 0xfb,0x00,0x2f,0xe2,0x00,0xaf,0x60,0x00,0x0d,0xf9,0x00,0x00,0x0c, + 0xfc,0x00,0x08,0xfd,0x10,0x01,0xff,0x90,0xcf,0x70,0x00,0x2f,0xf7, + 0x00,0x00,0x1f,0xf9,0x00,0x00,0x27,0xb1,0x00,0x00,0x8f,0xf3,0x00, + 0x2f,0xf6,0x0a,0xfd,0x10,0x00,0xcf,0xd1,0x00,0x00,0x7f,0xfb,0x00, + 0x1d,0xf9,0x01,0x10,0x0a,0xfe,0x87,0x77,0x40,0x00,0x00,0x00,0x3f, + 0xf9,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6f,0xf3,0x00,0x00, + 0x00,0x00,0x00,0x00,0x6f,0xfe,0x20,0x00,0x00,0x08,0xff,0x20,0x2f, + 0xf6,0x00,0xaf,0xd1,0x00,0x00,0x0b,0xff,0xff,0xa0,0x00,0x00,0x00, + 0x3f,0xf6,0x00,0x00,0x7f,0xf2,0x00,0x00,0x00,0x6b,0xff,0xd9,0xdf, + 0xa0,0x00,0x05,0xfb,0x00,0x2f,0xe2,0x00,0xaf,0x60,0x05,0xfb,0x00, + 0x2f,0xe2,0x00,0xaf,0x60,0x00,0x0d,0xf9,0x00,0x00,0x3f,0xfa,0x00, + 0x09,0xfd,0x00,0x05,0xff,0x80,0xdf,0x70,0x00,0x2f,0xf6,0x00,0x00, + 0x5f,0xf7,0x00,0x00,0x00,0x00,0x01,0x11,0x5f,0xf6,0x00,0x2f,0xf7, + 0x1d,0xf9,0x00,0x00,0x8f,0xf3,0x00,0x00,0x08,0xff,0x92,0x1e,0xf8, + 0x01,0x10,0x0a,0xff,0xff,0xff,0xfc,0x20,0x00,0x00,0x3f,0xf7,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6f,0xf3,0x00,0x00,0x00,0x00, + 0x00,0x01,0xdf,0xff,0x70,0x00,0x00,0x0b,0xfc,0x00,0x2f,0xf6,0x00, + 0x7f,0xf2,0x00,0x00,0x02,0xef,0xfe,0x20,0x00,0x00,0x00,0x3f,0xf6, + 0x00,0x00,0x7f,0xf2,0x00,0x00,0x09,0xff,0xff,0xff,0xff,0xa0,0x00, + 0x05,0xfb,0x00,0x2f,0xe2,0x00,0xaf,0x60,0x05,0xfb,0x00,0x2f,0xe2, + 0x00,0xaf,0x60,0x00,0x0c,0xfc,0x77,0x79,0xef,0xf5,0x00,0x08,0xff, + 0x87,0x9f,0xff,0x20,0xdf,0x70,0x00,0x2f,0xfb,0x77,0x79,0xff,0xf2, + 0x00,0x00,0x00,0x00,0x4f,0xff,0xff,0xf8,0x00,0x2f,0xff,0xff,0xf8, + 0x00,0x00,0x6f,0xf4,0x00,0x00,0x17,0xef,0xff,0xff,0xf9,0x01,0x10, + 0x0a,0xfe,0x87,0x7c,0xff,0xb0,0x00,0x00,0x3f,0xf7,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x6f,0xf3,0x00,0x00,0x00,0x00,0x00,0x07, + 0xfd,0x7f,0xe1,0x00,0x00,0x0b,0xfc,0x00,0x2f,0xf6,0x00,0x7f,0xf2, + 0x00,0x00,0x00,0xbf,0xfa,0x00,0x00,0x00,0x00,0x3f,0xf6,0x00,0x00, + 0x6f,0xf2,0x00,0x00,0x3f,0xfb,0x21,0x16,0xef,0xa0,0x00,0x05,0xfb, + 0x00,0x2f,0xe2,0x00,0xaf,0x60,0x05,0xfb,0x00,0x2f,0xe2,0x00,0xaf, + 0x60,0x00,0x0d,0xff,0xff,0xff,0xff,0x70,0x00,0x08,0xff,0xff,0xff, + 0xe5,0x00,0xdf,0x70,0x00,0x2f,0xff,0xff,0xff,0xfe,0x50,0x00,0x00, + 0x00,0x00,0x4f,0xff,0xff,0xf8,0x00,0x2f,0xff,0xff,0xf7,0x00,0x00, + 0x6f,0xf4,0x00,0x02,0xdf,0xff,0xef,0xff,0xf9,0x01,0x10,0x0a,0xfb, + 0x00,0x00,0xbf,0xf2,0x00,0x00,0x2f,0xf8,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x6f,0xf3,0x00,0x00,0x00,0x00,0x00,0x1e,0xf8,0x1e, + 0xf6,0x00,0x00,0x08,0xff,0x20,0x2f,0xf6,0x00,0xaf,0xd1,0x00,0x00, + 0x04,0xff,0xff,0x40,0x00,0x00,0x00,0x3f,0xf6,0x00,0x00,0x7f,0xf2, + 0x00,0x00,0x5f,0xf3,0x00,0x00,0xbf,0xa0,0x00,0x05,0xfb,0x00,0x2f, + 0xe2,0x00,0xaf,0x60,0x05,0xfb,0x00,0x2f,0xe2,0x00,0xaf,0x60,0x00, + 0x0c,0xfc,0x77,0x77,0x62,0x00,0x00,0x09,0xfe,0x87,0x75,0x20,0x00, + 0xdf,0x70,0x00,0x2f,0xfb,0x77,0x77,0x51,0x00,0x00,0x00,0x00,0x00, + 0x01,0x11,0x5f,0xf6,0x00,0x2f,0xf7,0x1d,0xf9,0x00,0x00,0x8f,0xf3, + 0x00,0x0a,0xff,0x61,0x11,0x2e,0xf8,0x01,0x10,0x0a,0xfc,0x00,0x00, + 0x7f,0xf4,0x00,0x00,0x0c,0xfc,0x00,0x00,0x04,0x52,0x00,0x00,0x00, + 0x00,0x6f,0xf3,0x00,0x00,0x00,0x00,0x00,0x7f,0xf2,0x08,0xfd,0x10, + 0x00,0x03,0xff,0xc3,0x3f,0xf7,0x28,0xff,0x70,0x00,0x00,0x1d,0xff, + 0xff,0xc1,0x00,0x00,0x00,0x3f,0xf6,0x00,0x00,0x6f,0xf2,0x00,0x00, + 0x6f,0xf3,0x00,0x00,0xcf,0xa0,0x00,0x05,0xfb,0x00,0x2f,0xe2,0x00, + 0xaf,0x60,0x05,0xfb,0x00,0x2f,0xe2,0x00,0xaf,0x60,0x00,0x0d,0xf9, + 0x00,0x00,0x00,0x00,0x00,0x08,0xfd,0x00,0x00,0x00,0x00,0xdf,0x70, + 0x00,0x2f,0xf6,0x00,0x00,0x00,0x00,0x00,0x00,0x14,0x60,0x00,0x00, + 0x8f,0xf3,0x00,0x2f,0xf6,0x0a,0xfd,0x00,0x00,0xcf,0xd1,0x00,0x1d, + 0xfb,0x00,0x00,0x0d,0xf9,0x01,0x10,0x0a,0xfb,0x00,0x00,0xbf,0xf3, + 0x00,0x00,0x06,0xff,0x70,0x00,0x2e,0xfc,0x00,0x00,0x00,0x00,0x5f, + 0xf2,0x00,0x00,0x00,0x00,0x02,0xef,0x90,0x03,0xff,0x50,0x00,0x00, + 0x6f,0xff,0xff,0xff,0xff,0xfb,0x10,0x00,0x00,0x8f,0xf7,0x7f,0xf7, + 0x00,0x00,0x00,0x3f,0xf6,0x00,0x00,0x7f,0xf2,0x00,0x00,0x6f,0xf3, + 0x00,0x00,0xcf,0xa0,0x00,0x05,0xfb,0x00,0x2f,0xe2,0x00,0xaf,0x60, + 0x05,0xfb,0x00,0x2f,0xe2,0x00,0xaf,0x60,0x00,0x0c,0xf9,0x00,0x00, + 0x00,0x00,0x00,0x08,0xfd,0x10,0x00,0x00,0x00,0xcf,0x70,0x00,0x2f, + 0xf7,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0xf7,0x00,0x04,0xff,0xb0, + 0x00,0x2f,0xf6,0x04,0xff,0x70,0x07,0xff,0x80,0x00,0x1d,0xfd,0x10, + 0x00,0x0d,0xf8,0x01,0x10,0x0a,0xfe,0x87,0x7b,0xff,0xc0,0x00,0x00, + 0x01,0xbf,0xfb,0x79,0xef,0xf5,0x00,0x00,0x27,0x77,0xaf,0xf9,0x77, + 0x60,0x00,0x00,0x08,0xff,0x30,0x00,0xbf,0xc0,0x00,0x00,0x03,0x9d, + 0xff,0xff,0xea,0x60,0x00,0x00,0x03,0xff,0xc0,0x1d,0xff,0x30,0x00, + 0x00,0x3f,0xf6,0x00,0x00,0x6f,0xf2,0x00,0x00,0x6f,0xf3,0x00,0x00, + 0xcf,0xa0,0x00,0x05,0xfb,0x00,0x2f,0xe2,0x00,0xaf,0x60,0x05,0xfb, + 0x00,0x2f,0xe2,0x00,0xaf,0x60,0x04,0x8e,0xf9,0x00,0x00,0x00,0x00, + 0x00,0x08,0xfd,0x10,0x00,0x00,0x00,0xcf,0x70,0x00,0x2f,0xf6,0x00, + 0x00,0x00,0x00,0x00,0x00,0x2d,0xff,0xa7,0x9f,0xfe,0x20,0x00,0x2f, + 0xf7,0x00,0x9f,0xfb,0xbf,0xfd,0x10,0x00,0x08,0xff,0xb7,0x77,0x8e, + 0xf8,0x01,0x10,0x0b,0xff,0xff,0xff,0xfd,0x20,0x00,0x00,0x00,0x19, + 0xff,0xff,0xfd,0x50,0x00,0x00,0x4f,0xff,0xff,0xff,0xff,0xf1,0x00, + 0x00,0x3f,0xfa,0x00,0x00,0x5f,0xf5,0x00,0x00,0x00,0x01,0x3f,0xf8, + 0x10,0x00,0x00,0x00,0x1d,0xff,0x30,0x04,0xff,0xd1,0x00,0x00,0x3f, + 0xf6,0x00,0x00,0x7f,0xf2,0x00,0x00,0x6f,0xf3,0x00,0x00,0xcf,0xb0, + 0x00,0x05,0xfc,0x00,0x3f,0xf2,0x00,0xbf,0x60,0x05,0xfc,0x00,0x3f, + 0xf2,0x00,0xbf,0x70,0x09,0xff,0xfa,0x00,0x00,0x00,0x00,0x00,0x09, + 0xff,0x10,0x00,0x00,0x00,0xdf,0x80,0x00,0x2f,0xf7,0x00,0x00,0x00, + 0x00,0x00,0x00,0x02,0xbf,0xff,0xff,0xc3,0x00,0x00,0x2f,0xf7,0x00, + 0x19,0xff,0xff,0xb2,0x00,0x00,0x01,0xbf,0xff,0xff,0xff,0xf9,0x01, + 0x10,0x05,0x77,0x77,0x77,0x51,0x00,0x00,0x00,0x00,0x00,0x26,0x77, + 0x51,0x00,0x00,0x00,0x27,0x77,0x77,0x77,0x77,0x60,0x00,0x00,0x38, + 0x72,0x00,0x00,0x17,0x74,0x00,0x00,0x00,0x00,0x17,0x73,0x00,0x00, + 0x00,0x00,0x28,0x75,0x00,0x00,0x57,0x82,0x00,0x00,0x17,0x73,0x00, + 0x00,0x37,0x71,0x00,0x00,0x37,0x71,0x00,0x00,0x67,0x50,0x00,0x03, + 0x75,0x00,0x17,0x71,0x00,0x57,0x30,0x03,0x75,0x00,0x17,0x71,0x00, + 0x57,0x30,0x04,0x77,0x75,0x00,0x00,0x00,0x00,0x00,0x04,0x76,0x00, + 0x00,0x00,0x00,0x67,0x40,0x00,0x17,0x73,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x03,0x77,0x74,0x00,0x00,0x00,0x17,0x73,0x00,0x00,0x37, + 0x73,0x00,0x00,0x00,0x00,0x04,0x77,0x77,0x77,0x74,0x01,0x10,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x10,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x04,0x74,0x00,0x00,0x00,0x02,0x76,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x13,0x87,0x20,0x00,0x00,0x04, + 0x78,0x20,0x00,0x17,0x77,0x77,0x77,0x62,0x00,0x00,0x00,0x27,0x77, + 0x77,0x77,0x62,0x00,0x00,0x00,0x01,0x77,0x40,0x00,0x00,0x00,0x00, + 0x09,0xf9,0x00,0x00,0x00,0x04,0xfe,0x00,0x00,0x05,0x77,0x77,0x77, + 0x77,0x71,0x00,0x28,0x74,0x00,0x27,0x71,0x00,0x67,0x70,0x00,0x00, + 0x03,0x77,0x75,0x20,0x00,0x00,0x00,0x27,0x74,0x00,0x00,0x37,0x71, + 0x00,0x00,0x27,0x73,0x00,0x00,0x37,0x71,0x00,0x00,0x01,0x77,0x30, + 0x00,0x67,0x82,0x00,0x00,0x57,0x73,0x00,0x00,0x27,0x72,0x00,0x02, + 0x77,0x10,0x27,0x72,0x01,0x77,0x30,0x00,0x05,0x76,0x00,0x00,0x17, + 0x74,0x00,0x00,0x00,0x02,0x67,0x76,0x20,0x00,0x00,0x00,0x17,0x73, + 0x00,0x00,0x37,0x71,0x01,0x03,0xff,0x70,0x00,0x00,0x0c,0xff,0x20, + 0x00,0x2f,0xff,0xff,0xff,0xff,0x60,0x00,0x00,0x4f,0xff,0xff,0xff, + 0xff,0x70,0x00,0x00,0x01,0xff,0x80,0x00,0x00,0x00,0x00,0x08,0xfc, + 0x77,0x77,0x77,0x7a,0xfd,0x00,0x00,0x0b,0xff,0xff,0xff,0xff,0xf3, + 0x00,0x1e,0xfd,0x10,0x5f,0xf2,0x03,0xff,0xa0,0x00,0x02,0xbf,0xff, + 0xff,0xf7,0x00,0x00,0x00,0x4f,0xff,0x20,0x00,0x6f,0xf3,0x00,0x00, + 0x4f,0xfd,0x10,0x00,0x7f,0xf1,0x00,0x00,0x02,0xff,0x60,0x03,0xff, + 0xd1,0x00,0x00,0xcf,0xff,0x40,0x00,0x5f,0xf4,0x00,0x05,0xff,0x30, + 0x6f,0xf7,0x02,0xff,0x60,0x00,0x0b,0xfc,0x00,0x00,0x1f,0xf8,0x00, + 0x00,0x01,0x9f,0xff,0xff,0xf9,0x10,0x00,0x00,0x3f,0xf6,0x00,0x00, + 0x7f,0xf2,0x01,0x10,0xcf,0xc0,0x00,0x00,0x2f,0xf9,0x00,0x00,0x2f, + 0xfb,0x77,0x79,0xff,0xf3,0x00,0x00,0x3f,0xfa,0x77,0x78,0xef,0xf4, + 0x00,0x00,0x01,0xef,0x70,0x00,0x00,0x00,0x00,0x09,0xff,0xff,0xff, + 0xff,0xff,0xfd,0x00,0x00,0x0a,0xfe,0x87,0x77,0x77,0x71,0x00,0x06, + 0xff,0x50,0x4f,0xe2,0x09,0xff,0x30,0x00,0x0a,0xff,0xb7,0x8e,0xff, + 0x50,0x00,0x00,0x3f,0xff,0xa0,0x00,0x6f,0xf3,0x00,0x00,0x4f,0xff, + 0x80,0x00,0x7f,0xf1,0x00,0x00,0x02,0xff,0x60,0x09,0xff,0x50,0x00, + 0x00,0x58,0xff,0x90,0x00,0x5f,0xf4,0x00,0x05,0xff,0x30,0xaf,0xfa, + 0x02,0xff,0x60,0x00,0x0a,0xfc,0x00,0x00,0x1e,0xf7,0x00,0x00,0x1b, + 0xff,0xc8,0x7c,0xff,0xb1,0x00,0x00,0x3f,0xf6,0x00,0x00,0x7f,0xf2, + 0x01,0x10,0x6f,0xf8,0x77,0x77,0xbf,0xf4,0x00,0x00,0x2f,0xf6,0x00, + 0x00,0x5f,0xf8,0x00,0x00,0x3f,0xf4,0x00,0x00,0x3f,0xf9,0x00,0x00, + 0x01,0xef,0x70,0x00,0x00,0x00,0x00,0x04,0xaf,0xfa,0x77,0x77,0xdf, + 0xd6,0x00,0x00,0x0a,0xfb,0x00,0x00,0x00,0x00,0x00,0x01,0xdf,0xc0, + 0x4f,0xe1,0x1e,0xfa,0x00,0x00,0x1b,0xfa,0x00,0x03,0xef,0xa0,0x00, + 0x00,0x3f,0xff,0xf4,0x00,0x6f,0xf3,0x00,0x00,0x4f,0xff,0xf3,0x00, + 0x6f,0xe1,0x00,0x00,0x02,0xff,0x60,0x2f,0xfc,0x00,0x00,0x00,0x00, + 0xbf,0xa0,0x00,0x5f,0xf4,0x00,0x05,0xff,0x30,0xdf,0xfe,0x12,0xff, + 0x60,0x00,0x0a,0xfc,0x00,0x00,0x1e,0xf7,0x00,0x00,0x7f,0xf9,0x10, + 0x00,0x9f,0xf6,0x00,0x00,0x3f,0xf6,0x00,0x00,0x7f,0xf2,0x01,0x10, + 0x2f,0xff,0xff,0xff,0xff,0xd1,0x00,0x00,0x2f,0xf6,0x00,0x00,0x2f, + 0xf9,0x00,0x00,0x3f,0xf5,0x00,0x00,0x0e,0xfa,0x00,0x00,0x01,0xef, + 0x70,0x00,0x00,0x00,0x00,0x00,0x1f,0xf8,0x00,0x00,0xaf,0xa0,0x00, + 0x00,0x0a,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0xf3,0x3f,0xe1, + 0x8f,0xf4,0x00,0x00,0x00,0x32,0x00,0x00,0xdf,0xb0,0x00,0x00,0x3f, + 0xff,0xfd,0x10,0x6f,0xf3,0x00,0x00,0x4f,0xff,0xfb,0x00,0x6f,0xe1, + 0x00,0x00,0x02,0xff,0x50,0xaf,0xf5,0x00,0x00,0x00,0x00,0xbf,0xb0, + 0x00,0x5f,0xf4,0x00,0x05,0xff,0x32,0xff,0xff,0x32,0xff,0x60,0x00, + 0x0a,0xfc,0x00,0x00,0x1e,0xf7,0x00,0x00,0xdf,0xd1,0x00,0x00,0x1d, + 0xfd,0x00,0x00,0x3f,0xf6,0x00,0x00,0x6f,0xf2,0x01,0x10,0x0a,0xfe, + 0x87,0x79,0xff,0x90,0x00,0x00,0x2f,0xf6,0x00,0x00,0x5f,0xf8,0x00, + 0x00,0x3f,0xf4,0x00,0x00,0x4f,0xf8,0x00,0x00,0x01,0xef,0x70,0x00, + 0x00,0x00,0x00,0x00,0x0b,0xfb,0x00,0x00,0xbf,0xb0,0x00,0x00,0x0a, + 0xfb,0x00,0x00,0x00,0x00,0x00,0x00,0x1d,0xfb,0x6f,0xe5,0xef,0xa0, + 0x00,0x00,0x00,0x00,0x01,0x28,0xff,0x80,0x00,0x00,0x3f,0xfa,0xff, + 0x70,0x6f,0xf3,0x00,0x00,0x4f,0xfa,0xff,0x60,0x6f,0xe1,0x00,0x00, + 0x02,0xff,0x65,0xff,0xb0,0x00,0x00,0x00,0x00,0xbf,0xb0,0x00,0x5f, + 0xf4,0x00,0x05,0xff,0x35,0xfc,0xbf,0x72,0xff,0x60,0x00,0x0a,0xfb, + 0x00,0x00,0x0e,0xf7,0x00,0x02,0xff,0x90,0x00,0x00,0x09,0xff,0x20, + 0x00,0x3f,0xf6,0x00,0x00,0x7f,0xf2,0x01,0x10,0x05,0xff,0x20,0x06, + 0xff,0x40,0x00,0x00,0x2f,0xfb,0x77,0x79,0xff,0xf2,0x00,0x00,0x3f, + 0xfa,0x77,0x79,0xef,0xd2,0x00,0x00,0x01,0xef,0x70,0x00,0x00,0x00, + 0x00,0x00,0x08,0xfe,0x10,0x00,0xbf,0xb0,0x00,0x00,0x0a,0xfe,0x87, + 0x77,0x77,0x30,0x00,0x00,0x04,0xef,0xff,0xff,0xfc,0x20,0x00,0x00, + 0x00,0x00,0x1e,0xff,0xfb,0x10,0x00,0x00,0x3f,0xf4,0xaf,0xf2,0x5f, + 0xf3,0x00,0x00,0x4f,0xf4,0xcf,0xe1,0x6f,0xe1,0x00,0x00,0x02,0xff, + 0xff,0xfc,0x20,0x00,0x00,0x00,0x00,0xbf,0xb0,0x00,0x5f,0xf4,0x00, + 0x05,0xff,0x38,0xf7,0x6f,0xa3,0xff,0x60,0x00,0x0a,0xfe,0x87,0x77, + 0x8f,0xf7,0x00,0x04,0xff,0x70,0x00,0x00,0x07,0xff,0x40,0x00,0x3f, + 0xf6,0x00,0x00,0x6f,0xf2,0x01,0x10,0x01,0xef,0x70,0x0c,0xfd,0x00, + 0x00,0x00,0x2f,0xff,0xff,0xff,0xfe,0x50,0x00,0x00,0x3f,0xff,0xff, + 0xff,0xfc,0x20,0x00,0x00,0x01,0xff,0x70,0x00,0x00,0x00,0x00,0x00, + 0x06,0xff,0x20,0x00,0xbf,0xb0,0x00,0x00,0x0a,0xff,0xff,0xff,0xff, + 0x60,0x00,0x00,0x01,0xbf,0xff,0xff,0xf7,0x00,0x00,0x00,0x00,0x00, + 0x1e,0xff,0xb0,0x00,0x00,0x00,0x3f,0xf4,0x2f,0xfa,0x5f,0xf3,0x00, + 0x00,0x4f,0xf3,0x3f,0xf8,0x6f,0xf1,0x00,0x00,0x02,0xff,0xff,0xf7, + 0x00,0x00,0x00,0x00,0x00,0xbf,0xb0,0x00,0x5f,0xf4,0x00,0x05,0xff, + 0x5b,0xf3,0x2f,0xd4,0xff,0x60,0x00,0x0a,0xff,0xff,0xff,0xff,0xf7, + 0x00,0x04,0xff,0x70,0x00,0x00,0x07,0xff,0x30,0x00,0x3f,0xf6,0x00, + 0x00,0x6f,0xf2,0x01,0x10,0x00,0x9f,0xc0,0x3f,0xf8,0x00,0x00,0x00, + 0x2f,0xfb,0x77,0x77,0x62,0x00,0x00,0x00,0x3f,0xfa,0x77,0x79,0xfe, + 0x60,0x00,0x00,0x01,0xef,0x70,0x00,0x00,0x00,0x00,0x00,0x05,0xff, + 0x40,0x00,0xbf,0xb0,0x00,0x00,0x0a,0xfe,0x87,0x77,0x77,0x30,0x00, + 0x00,0x06,0xfc,0x6f,0xe5,0xef,0x30,0x00,0x00,0x00,0x00,0x01,0x3c, + 0xf8,0x00,0x00,0x00,0x3f,0xf5,0x08,0xff,0xbf,0xf3,0x00,0x00,0x4f, + 0xf3,0x09,0xfe,0xbf,0xf1,0x00,0x00,0x02,0xff,0x66,0xff,0x30,0x00, + 0x00,0x00,0x00,0xbf,0xb0,0x00,0x5f,0xf4,0x00,0x05,0xff,0x7f,0xe1, + 0x0d,0xf7,0xff,0x60,0x00,0x0a,0xfe,0x87,0x77,0x8f,0xf7,0x00,0x02, + 0xff,0x90,0x00,0x00,0x09,0xff,0x20,0x00,0x3f,0xf6,0x00,0x00,0x6f, + 0xf2,0x01,0x10,0x00,0x4f,0xf2,0x7f,0xf3,0x00,0x00,0x00,0x2f,0xf6, + 0x00,0x00,0x00,0x00,0x00,0x00,0x3f,0xf4,0x00,0x00,0x8f,0xe1,0x00, + 0x00,0x01,0xff,0x70,0x00,0x00,0x00,0x00,0x00,0x04,0xff,0x40,0x00, + 0xbf,0xb0,0x00,0x00,0x0a,0xfb,0x00,0x00,0x00,0x00,0x00,0x00,0x0c, + 0xf4,0x3f,0xe1,0x9f,0x80,0x00,0x00,0x00,0x00,0x00,0x03,0xff,0x30, + 0x00,0x00,0x3f,0xf5,0x01,0xdf,0xff,0xf3,0x00,0x00,0x4f,0xf3,0x02, + 0xef,0xff,0xe1,0x00,0x00,0x02,0xff,0x50,0xcf,0x80,0x00,0x00,0x00, + 0x00,0xbf,0xb0,0x00,0x5f,0xf4,0x00,0x05,0xff,0xbf,0xa0,0x09,0xfa, + 0xff,0x60,0x00,0x0a,0xfb,0x00,0x00,0x0e,0xf7,0x00,0x00,0xdf,0xd1, + 0x00,0x00,0x1d,0xfc,0x00,0x00,0x3f,0xf6,0x00,0x00,0x7f,0xf2,0x01, + 0x10,0x00,0x0d,0xf8,0xbf,0xc0,0x00,0x00,0x00,0x2f,0xf6,0x00,0x00, + 0x00,0x00,0x00,0x00,0x3f,0xf4,0x00,0x00,0x7f,0xf2,0x00,0x00,0x01, + 0xff,0x70,0x00,0x00,0x00,0x00,0x00,0x03,0xff,0x40,0x00,0xaf,0xb0, + 0x00,0x00,0x0a,0xfb,0x00,0x00,0x00,0x00,0x00,0x00,0x3f,0xe1,0x4f, + 0xe1,0x3f,0xd1,0x00,0x00,0x07,0xcb,0x10,0x06,0xff,0x50,0x00,0x00, + 0x3f,0xf5,0x00,0x5f,0xff,0xf3,0x00,0x00,0x4f,0xf3,0x00,0x6f,0xff, + 0xe1,0x00,0x00,0x02,0xff,0x60,0x6f,0xe1,0x00,0x00,0x00,0x00,0xbf, + 0xa0,0x00,0x4f,0xf4,0x00,0x05,0xff,0xff,0x60,0x05,0xfe,0xff,0x60, + 0x00,0x0a,0xfc,0x00,0x00,0x1e,0xf7,0x00,0x00,0x6f,0xf9,0x00,0x00, + 0x8f,0xf6,0x00,0x00,0x3f,0xf5,0x00,0x00,0x6f,0xf2,0x01,0x10,0x00, + 0x08,0xfe,0xff,0x70,0x00,0x00,0x00,0x2f,0xfb,0x77,0x77,0x77,0x40, + 0x00,0x00,0x3f,0xfa,0x77,0x79,0xff,0xc1,0x00,0x00,0x01,0xff,0xb7, + 0x77,0x77,0x50,0x00,0x00,0x03,0xff,0xa7,0x77,0xdf,0xb0,0x00,0x00, + 0x0a,0xfe,0x87,0x77,0x77,0x60,0x00,0x28,0xdf,0x80,0x4f,0xe1,0x0c, + 0xfb,0x60,0x00,0x08,0xff,0xc7,0x9f,0xfe,0x10,0x00,0x00,0x3f,0xf5, + 0x00,0x0a,0xff,0xf3,0x00,0x00,0x4f,0xf3,0x00,0x0c,0xff,0xf1,0x00, + 0x00,0x02,0xff,0x60,0x1e,0xfc,0x72,0x00,0x00,0x00,0xbf,0xd7,0x77, + 0xaf,0xf4,0x00,0x05,0xff,0xff,0x30,0x02,0xff,0xff,0x60,0x00,0x0a, + 0xfc,0x00,0x00,0x1f,0xf7,0x00,0x00,0x1b,0xff,0xc7,0x7c,0xff,0xb1, + 0x00,0x00,0x3f,0xfa,0x77,0x77,0xbf,0xf2,0x01,0x10,0x00,0x03,0xff, + 0xff,0x20,0x00,0x00,0x00,0x2f,0xff,0xff,0xff,0xff,0x90,0x00,0x00, + 0x4f,0xff,0xff,0xff,0xfd,0x30,0x00,0x00,0x01,0xff,0xff,0xff,0xff, + 0xc0,0x00,0x00,0x04,0xff,0xff,0xff,0xff,0xb0,0x00,0x00,0x0b,0xff, + 0xff,0xff,0xff,0xe1,0x00,0x4f,0xfd,0x20,0x5f,0xf2,0x04,0xef,0xf1, + 0x00,0x01,0x9f,0xff,0xff,0xd4,0x00,0x00,0x00,0x4f,0xf5,0x00,0x02, + 0xff,0xf3,0x00,0x00,0x4f,0xf3,0x01,0x13,0xff,0xf1,0x00,0x00,0x02, + 0xff,0x60,0x05,0xff,0xf4,0x00,0x00,0x00,0xbf,0xff,0xff,0xff,0xf4, + 0x00,0x05,0xff,0xfd,0x00,0x00,0xcf,0xff,0x60,0x00,0x0b,0xfc,0x00, + 0x00,0x1f,0xf8,0x00,0x00,0x01,0x9f,0xff,0xff,0xf8,0x10,0x00,0x00, + 0x3f,0xff,0xff,0xff,0xff,0xf2,0x01,0x10,0x00,0x00,0x67,0x76,0x00, + 0x00,0x00,0x00,0x17,0x77,0x77,0x77,0x77,0x40,0x00,0x00,0x27,0x77, + 0x77,0x77,0x51,0x00,0x00,0x00,0x01,0x77,0x77,0x77,0x77,0x50,0x00, + 0x00,0x02,0x77,0x77,0x77,0x77,0x50,0x00,0x00,0x05,0x77,0x77,0x77, + 0x77,0x60,0x00,0x27,0x61,0x00,0x27,0x71,0x00,0x27,0x70,0x00,0x00, + 0x03,0x67,0x75,0x10,0x00,0x00,0x00,0x27,0x72,0x00,0x00,0x57,0x71, + 0x00,0x00,0x27,0x73,0xae,0xd4,0x57,0x71,0x00,0x00,0x01,0x77,0x30, + 0x00,0x36,0x72,0x00,0x00,0x00,0x57,0x77,0x77,0x77,0x72,0x00,0x02, + 0x77,0x75,0x00,0x00,0x47,0x77,0x30,0x00,0x05,0x76,0x00,0x00,0x17, + 0x74,0x00,0x00,0x00,0x02,0x67,0x76,0x20,0x00,0x00,0x00,0x17,0x77, + 0x77,0x77,0x77,0x71,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x06,0xc1,0x6c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x04,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x10,0x00,0x00,0x16,0x76,0x10,0x00,0x00,0x00,0x00,0x00,0x03, + 0x77,0x10,0x00,0x00,0x00,0x04,0x77,0x77,0x77,0x77,0x10,0x00,0x00, + 0x00,0x00,0x37,0x76,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x37,0x71, + 0x00,0x00,0x00,0x00,0x00,0x47,0x74,0x00,0x00,0x00,0x00,0x00,0x01, + 0x57,0x73,0x00,0x00,0x00,0x00,0x00,0x17,0x73,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x47,0x75,0x00,0x00,0x00,0x00,0x00,0x00,0x47,0x73, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x0a,0xd2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x17,0x71,0x00,0x00,0x01,0x10, + 0x00,0x03,0xdf,0xff,0xd2,0x00,0x00,0x00,0x00,0x00,0x07,0xff,0x20, + 0x00,0x00,0x00,0x06,0xff,0xff,0xff,0xff,0x30,0x00,0x00,0x00,0x07, + 0xff,0xff,0xe5,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0xf2,0x00,0x00, + 0x00,0x00,0x1b,0xff,0xff,0xc2,0x00,0x00,0x00,0x00,0x2c,0xff,0xff, + 0x80,0x00,0x00,0x00,0x00,0x1f,0xf6,0x00,0x00,0x00,0x00,0x00,0x00, + 0x2c,0xff,0xff,0xc2,0x00,0x00,0x00,0x00,0x09,0xff,0xff,0xa1,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xd8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x3f,0xf3,0x00,0x00,0x01,0x10,0x00,0x1d, + 0xfd,0x7d,0xfb,0x00,0x00,0x00,0x00,0x00,0x06,0xff,0x20,0x00,0x00, + 0x00,0x01,0xdf,0xfe,0xee,0xee,0x20,0x00,0x00,0x00,0x6f,0xfc,0x7d, + 0xff,0x30,0x00,0x00,0x01,0x11,0x11,0x7f,0xf3,0x10,0x00,0x00,0x00, + 0x9f,0xf9,0x8f,0xfb,0x00,0x00,0x00,0x00,0xcf,0xf8,0xaf,0xf6,0x00, + 0x00,0x00,0x00,0x0c,0xf8,0x00,0x00,0x00,0x00,0x00,0x01,0xcf,0xf9, + 0x9f,0xfb,0x00,0x00,0x00,0x00,0x6f,0xfa,0x8f,0xfa,0x00,0x00,0x00, + 0x00,0x00,0x27,0x72,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xda,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x4a,0x30,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xa4,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x2e,0xe2,0x00,0x00,0x01,0x10,0x00,0x5f,0xf3,0x04, + 0xff,0x30,0x00,0x00,0x00,0x00,0x07,0xff,0x20,0x00,0x00,0x00,0x00, + 0x5f,0xf7,0x11,0x11,0x00,0x00,0x00,0x00,0xcf,0xe1,0x02,0xef,0x90, + 0x00,0x00,0x09,0xef,0xff,0xff,0xff,0x70,0x00,0x00,0x02,0xff,0xa0, + 0x06,0xff,0x40,0x00,0x00,0x04,0xff,0x60,0x0c,0xfb,0x00,0x00,0x00, + 0x00,0x0a,0xfa,0x00,0x00,0x00,0x00,0x00,0x03,0xff,0x70,0x08,0xff, + 0x20,0x00,0x00,0x00,0x6c,0xc0,0x06,0xff,0x30,0x00,0x00,0x00,0x00, + 0x4f,0xf4,0x00,0x00,0x00,0x00,0x00,0x00,0x0d,0xfa,0x00,0x00,0x00, + 0x00,0x00,0x00,0x01,0x5c,0xff,0x30,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0xff,0xc5,0x10,0x00,0x00,0x00,0x00,0x00, + 0x00,0x02,0x20,0x00,0x00,0x01,0x10,0x00,0x8f,0xd0,0x01,0xef,0x60, + 0x00,0x00,0x00,0x00,0x06,0xff,0x20,0x00,0x00,0x00,0x00,0x07,0xff, + 0x60,0x00,0x00,0x00,0x00,0x00,0x36,0x50,0x00,0xcf,0xb0,0x00,0x00, + 0x0a,0xff,0xff,0xff,0xff,0x70,0x00,0x00,0x01,0x47,0x20,0x02,0xff, + 0x60,0x00,0x00,0x08,0xff,0x20,0x09,0xfd,0x10,0x00,0x00,0x00,0x06, + 0xfe,0x10,0x00,0x00,0x00,0x00,0x04,0xff,0x30,0x04,0xff,0x40,0x00, + 0x00,0x00,0x00,0x11,0x11,0xff,0x70,0x00,0x00,0x00,0x00,0x4e,0xf4, + 0x00,0x00,0x00,0x00,0x00,0x00,0x0b,0xfa,0x00,0x00,0x00,0x00,0x00, + 0x02,0x7d,0xff,0xc5,0x00,0x00,0x00,0x02,0x77,0x77,0x77,0x77,0x50, + 0x00,0x00,0x00,0x5c,0xff,0xd7,0x20,0x00,0x00,0x00,0x00,0x00,0x2e, + 0xe2,0x00,0x00,0x01,0x10,0x00,0xaf,0xb0,0x00,0xdf,0x80,0x00,0x00, + 0x00,0x00,0x06,0xff,0x20,0x00,0x00,0x00,0x00,0x00,0x8f,0xf8,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x02,0xef,0x90,0x00,0x00,0x05,0xfe, + 0x21,0x7f,0xf3,0x10,0x00,0x00,0x00,0x00,0x00,0x02,0xff,0x60,0x00, + 0x00,0x0a,0xff,0x20,0x0a,0xfd,0x00,0x00,0x00,0x00,0x02,0xff,0x40, + 0x00,0x00,0x00,0x00,0x02,0xff,0x70,0x08,0xff,0x20,0x00,0x00,0x00, + 0x03,0xbf,0xd7,0xef,0x90,0x00,0x00,0x00,0x00,0x01,0x10,0x00,0x00, + 0x00,0x00,0x00,0x00,0x01,0x11,0x00,0x00,0x00,0x00,0x03,0x9e,0xff, + 0xc5,0x00,0x00,0x00,0x00,0x04,0xff,0xff,0xff,0xff,0xc0,0x00,0x00, + 0x00,0x00,0x5c,0xff,0xe9,0x30,0x00,0x00,0x00,0x00,0x1f,0xf7,0x00, + 0x00,0x01,0x10,0x00,0xaf,0xb0,0x00,0xcf,0x90,0x00,0x00,0x00,0x00, + 0x06,0xff,0x20,0x00,0x00,0x00,0x00,0x00,0x07,0xff,0x90,0x00,0x00, + 0x00,0x00,0x00,0x05,0x7c,0xfd,0x20,0x00,0x00,0x00,0x9f,0x90,0x6f, + 0xf2,0x00,0x00,0x00,0x00,0x9e,0x92,0x2a,0xff,0x30,0x00,0x00,0x0b, + 0xff,0x91,0x4e,0xfa,0x00,0x00,0x00,0x00,0x00,0xaf,0x90,0x00,0x00, + 0x00,0x00,0x00,0x7f,0xf8,0x9f,0xf7,0x00,0x00,0x00,0x00,0x3e,0xff, + 0xff,0xff,0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0a,0xff,0xc4,0x00,0x00, + 0x00,0x00,0x00,0x02,0x77,0x77,0x77,0x77,0x50,0x00,0x00,0x00,0x00, + 0x00,0x4b,0xff,0xa0,0x00,0x00,0x00,0x00,0x09,0xff,0x70,0x00,0x01, + 0x10,0x00,0xaf,0xb0,0x00,0xcf,0x90,0x00,0x00,0x00,0x00,0x07,0xff, + 0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0xf6,0x00,0x00,0x00,0x00, + 0x00,0x08,0xff,0xc1,0x00,0x00,0x00,0x00,0x2e,0xf3,0x6f,0xf2,0x00, + 0x00,0x00,0x00,0xbf,0xff,0xff,0xf8,0x00,0x00,0x00,0x0b,0xff,0xff, + 0xff,0xe2,0x00,0x00,0x00,0x00,0x00,0x4f,0xf2,0x00,0x00,0x00,0x00, + 0x00,0x09,0xff,0xff,0x80,0x00,0x00,0x00,0x00,0xaf,0xe3,0x1a,0xff, + 0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x0a,0xff,0xb3,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3b, + 0xff,0xa0,0x00,0x00,0x00,0x00,0x01,0xaf,0xf8,0x00,0x01,0x10,0x00, + 0xaf,0xb0,0x01,0xdf,0x80,0x00,0x00,0x00,0x8a,0x26,0xff,0x20,0x00, + 0x00,0x00,0x00,0x00,0x00,0x0b,0xfd,0x00,0x00,0x00,0x00,0x00,0x03, + 0x9f,0xf7,0x00,0x00,0x00,0x00,0x06,0xfc,0x7f,0xf2,0x00,0x00,0x00, + 0x00,0x8f,0xed,0xfd,0x70,0x00,0x00,0x00,0x0a,0xfd,0x7e,0xfa,0x30, + 0x00,0x00,0x00,0x00,0x00,0x0b,0xf8,0x00,0x00,0x00,0x00,0x00,0x6e, + 0xe8,0x9f,0xe4,0x00,0x00,0x00,0x01,0xef,0x90,0x03,0xff,0x90,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x9e,0xff,0xb4,0x00,0x00,0x00,0x00,0x04, + 0xff,0xff,0xff,0xff,0xa0,0x00,0x00,0x00,0x00,0x4b,0xff,0xf9,0x30, + 0x00,0x00,0x00,0x00,0x00,0x08,0xff,0x60,0x01,0x10,0x00,0x8f,0xd0, + 0x01,0xef,0x60,0x00,0x00,0x00,0x8f,0xec,0xff,0x20,0x00,0x00,0x00, + 0x01,0x47,0x20,0x05,0xff,0x20,0x00,0x00,0x00,0x00,0x10,0x08,0xff, + 0x20,0x00,0x00,0x00,0x00,0xbf,0xef,0xf2,0x00,0x00,0x00,0x00,0x5f, + 0xd1,0x10,0x00,0x00,0x00,0x00,0x08,0xfe,0x11,0x11,0x00,0x00,0x00, + 0x00,0x00,0x00,0x03,0xff,0x30,0x00,0x00,0x00,0x01,0xdf,0x70,0x09, + 0xfb,0x00,0x00,0x00,0x01,0xef,0x80,0x02,0xff,0x70,0x00,0x00,0x00, + 0x00,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x11,0x00,0x00, + 0x00,0x00,0x00,0x02,0x7d,0xff,0xc5,0x00,0x00,0x00,0x04,0xff,0xff, + 0xff,0xff,0xa0,0x00,0x00,0x00,0x5c,0xff,0xd7,0x20,0x00,0x00,0x00, + 0x04,0x75,0x00,0x00,0xcf,0xb0,0x01,0x10,0x00,0x4f,0xf3,0x04,0xff, + 0x30,0x00,0x00,0x00,0x18,0xff,0xff,0x20,0x00,0x00,0x00,0x02,0xff, + 0x70,0x07,0xff,0x20,0x00,0x00,0x00,0x6c,0xc0,0x07,0xff,0x30,0x00, + 0x00,0x00,0x00,0x2e,0xff,0xf2,0x00,0x00,0x00,0x00,0x3f,0xf3,0x11, + 0x11,0x00,0x00,0x00,0x04,0xff,0x60,0x1d,0xc6,0x00,0x00,0x00,0x01, + 0x11,0x11,0x8f,0xc1,0x00,0x00,0x00,0x01,0xff,0x70,0x09,0xfc,0x00, + 0x00,0x00,0x00,0xcf,0xb0,0x07,0xff,0x30,0x00,0x00,0x00,0x00,0x4e, + 0xf4,0x00,0x00,0x00,0x00,0x00,0x00,0x0b,0xea,0x00,0x00,0x00,0x00, + 0x00,0x00,0x01,0x5c,0xff,0x30,0x00,0x00,0x00,0x11,0x11,0x11,0x11, + 0x10,0x00,0x00,0x03,0xff,0xc5,0x10,0x00,0x00,0x00,0x00,0x07,0xff, + 0x30,0x01,0xdf,0xa0,0x01,0x10,0x00,0x1d,0xfd,0x7d,0xfb,0x00,0x00, + 0x00,0x00,0x00,0x5f,0xff,0x20,0x00,0x00,0x00,0x00,0xbf,0xf8,0x9f, + 0xfb,0x00,0x00,0x00,0x00,0x4f,0xfb,0x8e,0xfc,0x10,0x00,0x00,0x00, + 0x00,0x06,0xff,0xf2,0x00,0x00,0x00,0x00,0x1e,0xff,0xee,0xfc,0x00, + 0x00,0x00,0x00,0xbf,0xe8,0xbf,0xf4,0x00,0x00,0x00,0x0a,0xff,0xee, + 0xef,0xf8,0x00,0x00,0x00,0x00,0xbf,0xe8,0x9f,0xf8,0x00,0x00,0x00, + 0x00,0x6f,0xf9,0x8f,0xfb,0x00,0x00,0x00,0x00,0x00,0x4f,0xf4,0x00, + 0x00,0x00,0x00,0x00,0x00,0x0d,0xfb,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x4a,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x03,0xa4,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xdf,0xe8,0x7d, + 0xff,0x40,0x01,0x10,0x00,0x03,0xef,0xff,0xd2,0x00,0x00,0x00,0x00, + 0x00,0x06,0xff,0x20,0x00,0x00,0x00,0x00,0x2c,0xff,0xff,0xc2,0x00, + 0x00,0x00,0x00,0x07,0xff,0xff,0xd3,0x00,0x00,0x00,0x00,0x00,0x00, + 0xcf,0xf2,0x00,0x00,0x00,0x00,0x0c,0xff,0xff,0xfe,0x10,0x00,0x00, + 0x00,0x1b,0xff,0xff,0x80,0x00,0x00,0x00,0x0c,0xff,0xff,0xff,0xfb, + 0x00,0x00,0x00,0x00,0x2c,0xff,0xff,0xb1,0x00,0x00,0x00,0x00,0x08, + 0xff,0xff,0xb2,0x00,0x00,0x00,0x00,0x00,0x27,0x72,0x00,0x00,0x00, + 0x00,0x00,0x00,0x06,0x75,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2c,0xff,0xff,0xe6,0x00, + 0x01,0x10,0x00,0x00,0x26,0x76,0x10,0x00,0x00,0x00,0x00,0x00,0x00, + 0x77,0x10,0x00,0x00,0x00,0x00,0x00,0x47,0x75,0x10,0x00,0x00,0x00, + 0x00,0x00,0x37,0x75,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x27,0x71, + 0x00,0x00,0x00,0x00,0x05,0x77,0x77,0x76,0x00,0x00,0x00,0x00,0x00, + 0x47,0x74,0x00,0x00,0x00,0x00,0x05,0x77,0x77,0x77,0x75,0x00,0x00, + 0x00,0x00,0x01,0x57,0x74,0x00,0x00,0x00,0x00,0x00,0x00,0x37,0x75, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x47,0x75,0x10,0x00,0x01,0x10, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x10,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x40,0x00,0x00,0x00,0x00,0x01, + 0x10,0x00,0x00,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01, + 0x75,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x85,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x25,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x10,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x27,0x72,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x75,0x03,0x74,0x00,0x00, + 0x00,0x00,0x00,0x01,0x5e,0xd4,0x00,0x00,0x00,0x00,0x02,0xeb,0x00, + 0x00,0x6d,0xfb,0x20,0x00,0x00,0x26,0x77,0x51,0x00,0x51,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0a,0xf5,0x00, + 0x00,0x00,0x00,0x00,0x00,0x01,0xdf,0x30,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x4f,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x27,0x72,0x00,0x00,0x00,0x00,0x00, + 0x04,0x73,0x00,0x00,0x00,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x5f,0xf4,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x03,0xfe,0x04,0xfc,0x00,0x00,0x00,0x00, + 0x00,0x3d,0xff,0xff,0xb1,0x00,0x00,0x00,0x00,0x9f,0x50,0x03,0xfd, + 0x9f,0xb0,0x00,0x07,0xef,0xff,0xfd,0x6b,0xf9,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x4f,0xc0,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x5f,0xb0,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x04,0xf2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x4f,0xf4,0x00,0x00,0x00,0x00,0x00,0x05,0xf9, + 0x00,0x00,0x00,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x4e,0xe3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xdf,0x10,0xee,0x00,0x00,0x00,0x00,0x01,0xdf, + 0xee,0xcc,0xfa,0x00,0x00,0x00,0x00,0x2f,0xc0,0x07,0xf7,0x0d,0xf2, + 0x00,0x5f,0xfc,0x79,0xef,0xff,0xfa,0x10,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xaf,0x80,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x1e,0xf3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x06,0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x28, + 0xf3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x3f,0xf4,0x00,0x00,0x00,0x00,0x00,0x02,0xfc,0x00,0x00, + 0x00,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x02,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x06,0xdf,0xa7,0xef,0x97,0x50,0x00,0x00,0x03,0xff,0x5a,0x81, + 0xff,0x20,0x00,0x00,0x00,0x09,0xf5,0x08,0xf6,0x0c,0xf3,0x00,0xaf, + 0xd1,0x00,0x5f,0xff,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x02,0xff,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0b,0xf8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x0e,0xf6,0x00,0x00,0x00,0x00,0x00,0x00,0x4f,0xf4,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0xcf,0x20,0x00,0x00,0x01, + 0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0b,0xa0, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0e, + 0xff,0xff,0xff,0xff,0xc0,0x00,0x00,0x00,0x35,0x0b,0x81,0xff,0x30, + 0x00,0x00,0x00,0x02,0xfc,0x07,0xf7,0x0d,0xf2,0x00,0xaf,0xc0,0x01, + 0xcf,0xff,0x90,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x05,0xff,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0xfc, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0c,0xf5,0x00,0x00,0x00,0x00,0x00,0x00,0x4f,0xf3,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x9f,0x50,0x00,0x00,0x01,0x10,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0d,0xc0,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x9f,0xd7, + 0xaf,0xd7,0x50,0x00,0x00,0x00,0x00,0x0b,0xba,0xff,0x20,0x00,0x00, + 0x00,0x00,0x9f,0x42,0xfd,0x9f,0xb0,0x00,0x6f,0xf6,0x1b,0xff,0xaf, + 0xf2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08, + 0xfd,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0xfe,0x10,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x11,0x1d,0xf6, + 0x11,0x10,0x00,0x00,0x00,0x00,0x01,0x10,0x00,0x00,0x00,0x00,0x00, + 0x01,0x11,0x11,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x5f,0x90,0x00,0x00,0x01,0x10,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1e,0xe1,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1f,0xe0,0x2f,0xc0, + 0x00,0x00,0x00,0x00,0x02,0x9e,0xff,0xf8,0x00,0x00,0x00,0x58,0x61, + 0x2f,0xc0,0x6d,0xfb,0x20,0x00,0x0a,0xff,0xdf,0xf7,0x17,0x51,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x09,0xfc,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xff,0x20,0x00,0x00,0x00, + 0x05,0xb1,0x4b,0x20,0x00,0x00,0x00,0x04,0xff,0xff,0xff,0xff,0xa0, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xff, + 0xff,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x2f,0xc0,0x00,0x00,0x01,0x10,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x2f,0xf1,0x00,0x00,0x00,0x00,0x00,0x00, + 0x11,0x10,0x11,0x10,0x00,0x00,0x01,0x1d,0xf3,0x1e,0xf3,0x10,0x00, + 0x00,0x00,0x3e,0xff,0xfc,0x60,0x00,0x00,0x08,0xff,0xfb,0x07,0xf5, + 0x01,0x10,0x00,0x00,0x00,0x6f,0xff,0xe2,0x00,0x00,0x00,0x00,0x00, + 0x00,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x08,0xfd,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x06,0xff,0x10,0x00,0x00,0x00,0x02,0xea, + 0xcc,0x10,0x00,0x00,0x00,0x04,0xff,0xff,0xff,0xff,0xa0,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xff,0xff,0x50, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0d,0xf2,0x00,0x00,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x3f,0xf2,0x00,0x00,0x00,0x00,0x00,0x00,0xce,0x90, + 0xae,0xa0,0x00,0x00,0x0c,0xff,0xff,0xff,0xff,0xa0,0x00,0x00,0x00, + 0xcf,0xee,0x90,0x00,0x00,0x00,0x1f,0xe2,0xbf,0x41,0xec,0x00,0x00, + 0x00,0x00,0x00,0x4f,0xff,0xfd,0x20,0x00,0x00,0x00,0x00,0x00,0x3e, + 0xe4,0x00,0x00,0x00,0x00,0x00,0x06,0xfe,0x10,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x08,0xfd,0x00,0x00,0x00,0x00,0x03,0x8f,0xf7,0x20, + 0x00,0x00,0x00,0x00,0x11,0x1d,0xf6,0x11,0x10,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x11,0x11,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x09,0xf4, + 0x00,0x00,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x4f,0xf4,0x00,0x00,0x00,0x00,0x00,0x00,0xdf,0xa0,0xbf,0xc0, + 0x00,0x00,0x0c,0xff,0xff,0xff,0xff,0xa0,0x00,0x00,0x01,0xff,0x4a, + 0x90,0x00,0x00,0x00,0x4f,0xa0,0x7f,0x60,0x8f,0x50,0x00,0x00,0x00, + 0x00,0xcf,0xc3,0x9f,0xc0,0x00,0x00,0x00,0x00,0x00,0x3f,0xf5,0x00, + 0x00,0x00,0x00,0x00,0x02,0xff,0x30,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x0a,0xf9,0x00,0x00,0x00,0x00,0x2f,0xee,0xde,0xd1,0x00,0x00, + 0x00,0x00,0x00,0x0c,0xf5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0xf8,0x00,0x00, + 0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5f, + 0xf5,0x00,0x00,0x00,0x00,0x00,0x00,0xcb,0x40,0xbc,0x50,0x00,0x00, + 0x01,0x14,0xfc,0x15,0xfb,0x10,0x00,0x00,0x01,0xff,0x4a,0x87,0xc7, + 0x00,0x00,0x4f,0xa0,0x7f,0x60,0x1e,0xc0,0x00,0x00,0x00,0x02,0xff, + 0x60,0x3f,0xf2,0x00,0x00,0x00,0x00,0x00,0x3f,0x92,0x00,0x00,0x00, + 0x00,0x00,0x00,0xbf,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1e, + 0xf4,0x00,0x00,0x00,0x00,0x04,0x2c,0x92,0x40,0x00,0x00,0x00,0x00, + 0x00,0x0e,0xf6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0xfc,0x00,0x00,0x01,0x10, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6f,0xf5,0x00, + 0x00,0x00,0x00,0x00,0x00,0xba,0x00,0x9c,0x00,0x00,0x00,0x00,0x01, + 0xff,0x12,0xfd,0x00,0x00,0x00,0x00,0xbf,0xdd,0xde,0xf9,0x00,0x00, + 0x2f,0xd2,0xbf,0x40,0x08,0xf5,0x00,0x00,0x00,0x01,0xef,0xd7,0xcf, + 0xd1,0x00,0x00,0x00,0x00,0x00,0x1f,0x50,0x00,0x00,0x00,0x00,0x00, + 0x00,0x4f,0xd0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5f,0xb0,0x00, + 0x00,0x00,0x00,0x00,0x0d,0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x06, + 0x73,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xdf,0x10,0x00,0x01,0x10,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6f,0xf5,0x00,0x00,0x00, + 0x00,0x00,0x00,0x3e,0x80,0x2d,0xa0,0x00,0x00,0x00,0x00,0xcf,0x40, + 0xef,0x30,0x00,0x00,0x00,0x2c,0xff,0xff,0xb1,0x00,0x00,0x08,0xff, + 0xfb,0x10,0x01,0xfe,0x10,0x00,0x00,0x00,0x6f,0xff,0xfe,0x40,0x00, + 0x00,0x00,0x00,0x00,0x07,0xf4,0x00,0x00,0x00,0x00,0x00,0x00,0x0a, + 0xf6,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0xdf,0x30,0x00,0x00,0x00, + 0x00,0x00,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xaf,0x50,0x00,0x01,0x10,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x37,0x72,0x00,0x00,0x00,0x00,0x00, + 0x00,0x03,0x40,0x02,0x50,0x00,0x00,0x00,0x00,0x57,0x30,0x57,0x20, + 0x00,0x00,0x00,0x01,0x5e,0xd5,0x00,0x00,0x00,0x00,0x58,0x61,0x00, + 0x00,0x58,0x20,0x00,0x00,0x00,0x02,0x77,0x62,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x42,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x75,0x00, + 0x00,0x00,0x00,0x00,0x00,0x02,0x85,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x37,0x30,0x00,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x05,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01, + 0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x10,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x10,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x10,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x10, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x10,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x10,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0d,0xdd,0x20,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x0c,0xdd,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x11,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01, + 0x10,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0xef, + 0xfc,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0d,0xd0, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x0f,0xff,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0e,0xff,0x40,0x00,0x00,0x00,0x0d,0xdd,0x20,0x00,0x00,0x00,0x00, + 0x01,0x9e,0xff,0xfc,0x50,0x00,0x00,0x00,0x00,0x03,0xdf,0xfe,0x20, + 0x00,0x00,0x00,0x02,0xcf,0xfa,0x4d,0xda,0x00,0x00,0x00,0x00,0x00, + 0x9d,0xd7,0x00,0x00,0x00,0x00,0x00,0x6d,0xd3,0x02,0xdd,0x60,0x00, + 0x00,0x2d,0xdd,0x40,0x0b,0xdd,0xa0,0x00,0x00,0x05,0xbb,0xff,0xf2, + 0x00,0x00,0x00,0x00,0x00,0x9d,0xdd,0xdd,0xdd,0x70,0x00,0x00,0x00, + 0x00,0x00,0x4c,0xff,0x50,0x00,0x00,0x00,0x00,0x0f,0xf0,0x00,0x00, + 0x00,0x00,0x00,0x5f,0xfc,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0f,0xff,0x33,0x54,0x10,0x00,0x00,0x00,0x00,0x03,0x54,0x1e,0xff, + 0x40,0x00,0x00,0x00,0x0f,0xff,0x30,0x00,0x00,0x00,0x00,0x0b,0xff, + 0xeb,0xef,0xf4,0x00,0x00,0x00,0x00,0x0b,0xff,0xff,0x10,0x00,0x00, + 0x00,0x0c,0xff,0xff,0xef,0xfb,0x00,0x00,0x00,0x00,0x01,0xff,0xfe, + 0x00,0x00,0x00,0x00,0x00,0xbf,0xf7,0x06,0xff,0xc0,0x00,0x00,0x06, + 0xff,0xe1,0x6f,0xff,0x20,0x00,0x00,0x00,0x00,0xaf,0xfa,0x00,0x00, + 0x00,0x00,0x00,0xaf,0xff,0xff,0xff,0x80,0x00,0x00,0x00,0x00,0x00, + 0xdf,0xff,0x50,0x00,0x00,0x00,0x00,0x0f,0xf0,0x00,0x00,0x00,0x00, + 0x00,0x5f,0xff,0xd0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x55,0x00,0x00,0x00,0x00,0x0f,0xff, + 0xdf,0xff,0xd2,0x00,0x00,0x00,0x01,0xbf,0xff,0xdf,0xff,0x40,0x00, + 0x00,0x00,0x0f,0xff,0x30,0x00,0x00,0x00,0x00,0x07,0x9a,0x30,0x5f, + 0xfa,0x00,0x00,0x00,0x00,0x0d,0xff,0x84,0x00,0x00,0x00,0x00,0x1f, + 0xff,0xb7,0xff,0xfb,0x00,0x00,0x00,0x00,0x07,0xff,0xff,0x50,0x00, + 0x00,0x00,0x01,0xff,0xfb,0x0c,0xff,0xf2,0x00,0x00,0x00,0xbf,0xfa, + 0xef,0xf6,0x00,0x00,0x00,0x00,0x00,0xdf,0xff,0x20,0x00,0x00,0x00, + 0x00,0x5f,0xff,0x94,0x44,0x20,0x00,0x00,0x00,0x00,0x00,0xff,0xf5, + 0x10,0x00,0x00,0x00,0x00,0x0f,0xf0,0x00,0x00,0x00,0x00,0x00,0x15, + 0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x5c,0xf7,0x00,0x00,0x00,0x00,0x0f,0xff,0xe9,0xef, + 0xfc,0x00,0x00,0x00,0x08,0xff,0xfa,0xdf,0xff,0x40,0x00,0x00,0x00, + 0x0f,0xff,0x30,0x00,0x00,0x00,0x00,0x00,0x15,0x9c,0xff,0xf8,0x00, + 0x00,0x00,0x00,0x0d,0xff,0x50,0x00,0x00,0x00,0x00,0x1f,0xff,0x20, + 0x8f,0xfb,0x00,0x00,0x00,0x00,0x0d,0xfd,0xef,0xc0,0x00,0x00,0x00, + 0x05,0xfd,0xff,0x2f,0xfd,0xf6,0x00,0x00,0x00,0x1e,0xff,0xff,0xb0, + 0x00,0x00,0x00,0x00,0x06,0xff,0xff,0x80,0x00,0x00,0x00,0x00,0x05, + 0xff,0xf5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xe0,0x00,0x00, + 0x00,0x00,0x00,0x0f,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0xef,0xf0, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x5c,0xff,0xf8,0x00,0x00,0x00,0x00,0x0f,0xff,0x50,0x3f,0xff,0x10, + 0x00,0x00,0x0d,0xff,0x60,0x1f,0xff,0x40,0x00,0x00,0x00,0x0f,0xff, + 0x40,0x00,0x00,0x00,0x00,0x03,0xef,0xff,0xff,0xc1,0x00,0x00,0x00, + 0x00,0x0d,0xff,0x50,0x00,0x00,0x00,0x00,0x1f,0xff,0x20,0x6f,0xfb, + 0x00,0x00,0x00,0x00,0x4f,0xf8,0x9f,0xf3,0x00,0x00,0x00,0x0b,0xf8, + 0xdf,0xaf,0xd8,0xfb,0x00,0x00,0x00,0x08,0xff,0xff,0x30,0x00,0x00, + 0x00,0x00,0x1e,0xfd,0xbf,0xf1,0x00,0x00,0x00,0x00,0x00,0x6f,0xff, + 0x40,0x00,0x00,0x00,0x00,0x00,0x02,0xff,0xd0,0x00,0x00,0x00,0x00, + 0x00,0x0f,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0xdf,0xf2,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x4c,0xff,0xff, + 0xff,0xee,0xe8,0x00,0x00,0x0f,0xff,0x30,0x1f,0xff,0x20,0x00,0x00, + 0x0d,0xff,0x50,0x0f,0xff,0x40,0x00,0x00,0x00,0x0f,0xff,0xa0,0x00, + 0x00,0x00,0x00,0x0a,0xff,0xc8,0x63,0x00,0x00,0x00,0x00,0x00,0x0d, + 0xff,0x50,0x00,0x00,0x00,0x00,0x1f,0xff,0x20,0x6f,0xfb,0x00,0x00, + 0x00,0x00,0xbf,0xf4,0x4f,0xf9,0x00,0x00,0x00,0x1f,0xf5,0x8f,0xff, + 0x84,0xff,0x10,0x00,0x00,0x2f,0xff,0xff,0xc0,0x00,0x00,0x00,0x00, + 0x7f,0xf7,0x5f,0xf7,0x00,0x00,0x00,0x00,0x00,0x06,0xff,0xf4,0x00, + 0x00,0x00,0x00,0x00,0x5e,0xff,0x90,0x00,0x00,0x00,0x00,0x00,0x0f, + 0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x9f,0xfe,0x50,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3a,0xff,0xff,0xfc,0x88, + 0x85,0x00,0x00,0x0f,0xff,0x91,0x7f,0xfe,0x00,0x00,0x00,0x0a,0xff, + 0xa1,0x5f,0xff,0x40,0x00,0x00,0x00,0x0f,0xff,0xfe,0xc0,0x00,0x00, + 0x00,0x0a,0xff,0x53,0xbc,0xb3,0x00,0x00,0x00,0x01,0xdf,0xff,0xed, + 0x10,0x00,0x00,0x00,0x1f,0xff,0x20,0x6f,0xfb,0x00,0x00,0x00,0x02, + 0xff,0xe0,0x0f,0xff,0x10,0x00,0x00,0x5f,0xf2,0x4f,0xff,0x41,0xff, + 0x50,0x00,0x00,0xcf,0xfa,0xdf,0xf7,0x00,0x00,0x00,0x01,0xef,0xf2, + 0x1f,0xfe,0x00,0x00,0x00,0x00,0x4a,0xaa,0xef,0xff,0x20,0x00,0x00, + 0x00,0x00,0x7f,0xfd,0x10,0x00,0x00,0x00,0x00,0x00,0x0f,0xf0,0x00, + 0x00,0x00,0x00,0x00,0x00,0x1d,0xff,0x70,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3a,0xff,0xf7,0x00,0x00,0x00, + 0x00,0x0f,0xff,0xff,0xff,0xf6,0x00,0x00,0x00,0x03,0xff,0xff,0xff, + 0xff,0x40,0x00,0x00,0x00,0x0f,0xff,0xcf,0xf3,0x00,0x00,0x00,0x03, + 0xff,0xff,0xff,0xc0,0x00,0x00,0x00,0x01,0xff,0xff,0xff,0x10,0x00, + 0x00,0x00,0x1f,0xff,0x20,0x6f,0xfb,0x00,0x00,0x00,0x08,0xff,0xa0, + 0x0a,0xff,0x70,0x00,0x00,0x9f,0xe0,0x1f,0xfe,0x10,0xdf,0xa0,0x00, + 0x08,0xff,0xf2,0x4f,0xff,0x30,0x00,0x00,0x08,0xff,0xb0,0x0a,0xff, + 0x60,0x00,0x00,0x00,0x5f,0xff,0xff,0xff,0x50,0x00,0x00,0x00,0x00, + 0x4d,0xff,0x90,0x00,0x00,0x00,0x00,0x00,0x0f,0xf0,0x00,0x00,0x00, + 0x00,0x00,0x00,0x9f,0xfd,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x3a,0xf7,0x00,0x00,0x00,0x00,0x0b, + 0xba,0x3a,0xdc,0x60,0x00,0x00,0x00,0x00,0x3b,0xdb,0x57,0xbb,0x30, + 0x00,0x00,0x00,0x06,0x66,0x18,0x82,0x00,0x00,0x00,0x00,0x27,0x99, + 0x85,0x00,0x00,0x00,0x00,0x00,0x6e,0xff,0x96,0x00,0x00,0x00,0x00, + 0x06,0x66,0x10,0x36,0x65,0x00,0x00,0x00,0x05,0x66,0x30,0x03,0x66, + 0x50,0x00,0x00,0x56,0x50,0x05,0x65,0x00,0x46,0x60,0x00,0x06,0x66, + 0x40,0x05,0x66,0x50,0x00,0x00,0x06,0x66,0x30,0x02,0x66,0x50,0x00, + 0x00,0x00,0x26,0x66,0x66,0x66,0x20,0x00,0x00,0x00,0x00,0x02,0xff, + 0xc0,0x00,0x00,0x00,0x00,0x00,0x0f,0xf0,0x00,0x00,0x00,0x00,0x00, + 0x00,0xcf,0xf2,0x00,0x00,0x00,0x00,0x01,0x55,0x10,0x55,0x10,0x00, + 0x00,0x00,0x00,0x00,0x00,0x34,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0d,0xff,0x50,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xd0,0x00, + 0x00,0x00,0x00,0x00,0x0f,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0xdf, + 0xf0,0x00,0x00,0x00,0x00,0x04,0xff,0x43,0xff,0x50,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x02,0xaf,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xf5,0x10,0x00,0x00, + 0x00,0x00,0x0f,0xf0,0x00,0x00,0x00,0x00,0x00,0x15,0xff,0xf0,0x00, + 0x00,0x00,0x00,0x08,0xff,0x76,0xff,0x90,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x02,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xcf,0xff,0x50,0x00,0x00,0x00,0x00, + 0x0f,0xf0,0x00,0x00,0x00,0x00,0x00,0x5f,0xff,0xc0,0x00,0x00,0x00, + 0x00,0x09,0xff,0x87,0xff,0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x3c,0xff,0x50,0x00,0x00,0x00,0x00,0x0c,0xc0, + 0x00,0x00,0x00,0x00,0x00,0x5f,0xfc,0x30,0x00,0x00,0x00,0x00,0x08, + 0xcc,0x76,0xcc,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x8d,0xff,0xfd,0x91,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x7f,0xfb,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x06,0xbc,0xb5,0x29,0x97,0x00,0x00,0x00, + 0x99,0x91,0x9c,0xa3,0x00,0x00,0x00,0x00,0x06,0xac,0xca,0x60,0x00, + 0x00,0x00,0x00,0x8f,0xfd,0x3d,0xdc,0x00,0x00,0x00,0x00,0x07,0xbc, + 0xca,0x60,0x00,0x00,0x00,0x00,0x09,0xdd,0x60,0x00,0x00,0x00,0x00, + 0x07,0xff,0xea,0xcf,0xfc,0x00,0x00,0x00,0x0b,0xdd,0x50,0x2d,0xdd, + 0x00,0x00,0x00,0x00,0x00,0xcd,0xd3,0x00,0x00,0x00,0x00,0x00,0x7f, + 0xff,0x90,0x00,0x00,0x00,0x00,0x01,0xdd,0xd2,0x02,0xdd,0xd6,0x00, + 0x00,0x00,0x00,0x0d,0xdd,0x30,0x00,0x00,0x00,0xdd,0xc0,0x1d,0xdc, + 0x01,0xdd,0xb0,0x00,0x01,0xdd,0xd2,0x06,0xdd,0xa0,0x00,0x00,0x00, + 0x05,0xac,0xc9,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x6f,0xff,0xff,0xef,0xf8,0x00,0x00,0x00,0xff,0xfd, + 0xff,0xff,0x40,0x00,0x00,0x01,0xcf,0xff,0xff,0xfb,0x00,0x00,0x00, + 0x07,0xff,0xff,0xef,0xfe,0x00,0x00,0x00,0x01,0xcf,0xff,0xff,0xfb, + 0x00,0x00,0x00,0x00,0x0a,0xff,0x70,0x00,0x00,0x00,0x00,0x06,0x86, + 0x30,0x1f,0xff,0x30,0x00,0x00,0x0c,0xff,0x50,0x3f,0xff,0x00,0x00, + 0x00,0x00,0x00,0xef,0xf4,0x00,0x00,0x00,0x00,0x00,0x1a,0xff,0xb0, + 0x00,0x00,0x00,0x00,0x01,0xff,0xf3,0x09,0xff,0xd0,0x00,0x00,0x00, + 0x00,0x0e,0xff,0x40,0x00,0x00,0x00,0xff,0xe0,0x1f,0xfd,0x01,0xff, + 0xd0,0x00,0x01,0xff,0xf2,0x06,0xff,0xb0,0x00,0x00,0x00,0xaf,0xff, + 0xff,0xf6,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xaf,0xf9,0x03,0xef,0xf7,0x00,0x00,0x00,0xff,0xfe,0x8d,0xff, + 0xc0,0x00,0x00,0x08,0xff,0xe6,0x6e,0xff,0x60,0x00,0x00,0x0e,0xff, + 0xa4,0xcf,0xfe,0x00,0x00,0x00,0x07,0xff,0xc2,0x3b,0xb9,0x30,0x00, + 0x00,0x00,0x0a,0xff,0x70,0x00,0x00,0x00,0x00,0x00,0x5c,0xed,0x6e, + 0xff,0x40,0x00,0x00,0x0c,0xff,0x50,0x3f,0xff,0x00,0x00,0x00,0x00, + 0x00,0xef,0xf4,0x00,0x00,0x00,0x00,0x00,0x06,0xff,0xb0,0x00,0x00, + 0x00,0x00,0x01,0xff,0xf6,0x2f,0xff,0x40,0x00,0x00,0x00,0x00,0x0e, + 0xff,0x40,0x00,0x00,0x00,0xff,0xe0,0x1f,0xfd,0x01,0xff,0xd0,0x00, + 0x01,0xff,0xf2,0x06,0xff,0xb0,0x00,0x00,0x07,0xff,0xe6,0x7f,0xff, + 0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7f, + 0xfd,0x40,0xaf,0xf7,0x00,0x00,0x00,0xff,0xf5,0x03,0xff,0xf1,0x00, + 0x00,0x0d,0xff,0x60,0x04,0x86,0x30,0x00,0x00,0x2f,0xff,0x10,0x5f, + 0xfe,0x00,0x00,0x00,0x0d,0xff,0x84,0x44,0x44,0x20,0x00,0x00,0x00, + 0x0a,0xff,0x70,0x00,0x00,0x00,0x00,0x05,0xff,0xfd,0xff,0xff,0x40, + 0x00,0x00,0x0c,0xff,0x50,0x3f,0xff,0x00,0x00,0x00,0x00,0x00,0xef, + 0xf4,0x00,0x00,0x00,0x00,0x00,0x06,0xff,0xb0,0x00,0x00,0x00,0x00, + 0x01,0xff,0xff,0xdf,0xfb,0x00,0x00,0x00,0x00,0x00,0x0e,0xff,0x40, + 0x00,0x00,0x00,0xff,0xe0,0x1f,0xfd,0x01,0xff,0xd0,0x00,0x01,0xff, + 0xf2,0x06,0xff,0xb0,0x00,0x00,0x0c,0xff,0x60,0x0b,0xff,0x70,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x19,0xef,0xff, + 0xef,0xf7,0x00,0x00,0x00,0xff,0xf3,0x01,0xff,0xf2,0x00,0x00,0x0e, + 0xff,0x40,0x00,0x00,0x00,0x00,0x00,0x3f,0xff,0x00,0x4f,0xfe,0x00, + 0x00,0x00,0x0e,0xff,0xff,0xff,0xff,0xa0,0x00,0x00,0x00,0x0a,0xff, + 0x70,0x00,0x00,0x00,0x00,0x0c,0xff,0x70,0x2f,0xff,0x40,0x00,0x00, + 0x0c,0xff,0x60,0x3f,0xff,0x00,0x00,0x00,0x00,0x00,0xef,0xf4,0x00, + 0x00,0x00,0x00,0x00,0x06,0xff,0xb0,0x00,0x00,0x00,0x00,0x01,0xff, + 0xff,0xff,0xf2,0x00,0x00,0x00,0x00,0x00,0x0e,0xff,0x40,0x00,0x00, + 0x00,0xff,0xe0,0x1f,0xfd,0x01,0xff,0xd0,0x00,0x01,0xff,0xf2,0x06, + 0xff,0xb0,0x00,0x00,0x0e,0xff,0x40,0x08,0xff,0x90,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x6a,0xff,0xf7, + 0x00,0x00,0x00,0xff,0xf5,0x03,0xff,0xf1,0x00,0x00,0x0d,0xff,0x60, + 0x03,0x31,0x00,0x00,0x00,0x1f,0xff,0x40,0x7f,0xfe,0x00,0x00,0x00, + 0x0d,0xff,0x95,0x5c,0xff,0x80,0x00,0x00,0x00,0x0a,0xff,0x70,0x00, + 0x00,0x00,0x00,0x0e,0xff,0x40,0x0f,0xff,0x40,0x00,0x00,0x0c,0xff, + 0xa0,0x5f,0xff,0x00,0x00,0x00,0x00,0x00,0xef,0xf4,0x00,0x00,0x00, + 0x00,0x00,0x06,0xff,0xb0,0x00,0x00,0x00,0x00,0x01,0xff,0xff,0xff, + 0xa0,0x00,0x00,0x00,0x00,0x00,0x0e,0xff,0x40,0x00,0x00,0x00,0xff, + 0xf3,0x2f,0xff,0x23,0xff,0xd0,0x00,0x01,0xff,0xf6,0x09,0xff,0xb0, + 0x00,0x00,0x0d,0xff,0x60,0x0a,0xff,0x80,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x4c,0xed,0x21,0xbf,0xf6,0x00,0x00, + 0x00,0xff,0xfe,0x8d,0xff,0xd0,0x00,0x00,0x08,0xff,0xe5,0x5e,0xff, + 0x50,0x00,0x00,0x0c,0xff,0xfc,0xff,0xfe,0x00,0x00,0x00,0x08,0xff, + 0xb1,0x2e,0xff,0x40,0x00,0x00,0x00,0xcf,0xff,0xed,0x00,0x00,0x00, + 0x00,0x0d,0xff,0x91,0x6f,0xff,0x40,0x00,0x00,0x0c,0xff,0xfe,0xff, + 0xfe,0x00,0x00,0x00,0x00,0x00,0xef,0xf4,0x00,0x00,0x00,0x00,0x00, + 0x06,0xff,0xb0,0x00,0x00,0x00,0x00,0x01,0xff,0xf5,0xef,0xf8,0x00, + 0x00,0x00,0x00,0x00,0x0e,0xff,0x40,0x00,0x00,0x00,0xff,0xff,0xff, + 0xff,0xff,0xff,0xb0,0x00,0x01,0xff,0xff,0xef,0xff,0xa0,0x00,0x00, + 0x07,0xff,0xe5,0x6f,0xff,0x30,0x00,0x00,0x00,0x03,0x54,0x00,0x00, + 0x00,0x00,0x00,0x00,0x1e,0xff,0xff,0xff,0xe1,0x00,0x00,0x00,0xff, + 0xff,0xff,0xff,0x50,0x00,0x00,0x01,0xcf,0xff,0xff,0xfb,0x00,0x00, + 0x00,0x03,0xff,0xff,0xef,0xfe,0x00,0x00,0x00,0x01,0xcf,0xff,0xff, + 0xf9,0x00,0x00,0x00,0x00,0xdf,0xff,0xff,0x00,0x00,0x00,0x00,0x06, + 0xff,0xff,0xff,0xff,0x40,0x00,0x00,0x0c,0xff,0xcf,0xff,0xf7,0x00, + 0x00,0x00,0x00,0x00,0xef,0xf4,0x00,0x00,0x00,0x00,0x00,0x06,0xff, + 0xb0,0x00,0x00,0x00,0x00,0x01,0xff,0xf3,0x4f,0xff,0x80,0x00,0x00, + 0x00,0x00,0x0e,0xff,0x40,0x00,0x00,0x00,0xff,0xde,0xff,0xf9,0xef, + 0xff,0x50,0x00,0x01,0xff,0xea,0xff,0xff,0x40,0x00,0x00,0x00,0xbf, + 0xff,0xff,0xf8,0x00,0x00,0x00,0x00,0x0b,0xfc,0x00,0x00,0x00,0x00, + 0x00,0x00,0x02,0x8c,0xdd,0xc9,0x20,0x00,0x00,0x00,0xff,0xf5,0xad, + 0xb4,0x00,0x00,0x00,0x00,0x17,0xbd,0xdb,0x60,0x00,0x00,0x00,0x00, + 0x28,0x96,0x4f,0xfe,0x00,0x00,0x00,0x00,0x17,0xbd,0xdb,0x50,0x00, + 0x00,0x00,0x00,0x6d,0xff,0xb6,0x00,0x00,0x00,0x00,0x00,0x5b,0xdb, + 0x57,0xbb,0x20,0x00,0x00,0x0c,0xff,0x53,0x89,0x50,0x00,0x00,0x00, + 0x00,0x00,0x66,0x62,0x00,0x00,0x00,0x00,0x00,0x06,0xff,0xb0,0x00, + 0x00,0x00,0x00,0x01,0xff,0xf3,0x04,0x66,0x62,0x00,0x00,0x00,0x00, + 0x0e,0xff,0x40,0x00,0x00,0x00,0x66,0x51,0x89,0x60,0x28,0x95,0x00, + 0x00,0x00,0x66,0x60,0x59,0x93,0x00,0x00,0x00,0x00,0x06,0xbd,0xda, + 0x40,0x00,0x00,0x00,0x00,0x0f,0xff,0x10,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xf3,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x4f,0xfe,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x09,0xff,0xb4,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x0c,0xff,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x88,0x82,0x00,0x00,0x00,0x00,0x00,0x01,0x33,0x20,0x00,0x00,0x00, + 0x00,0x01,0xff,0xf3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0e,0xff, + 0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x1f,0xff,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xf3,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x4f,0xfe, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06, + 0xff,0xff,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x0c,0xff,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xef,0xf4, + 0x00,0x00,0x00,0x00,0x00,0x06,0xff,0xb0,0x00,0x00,0x00,0x00,0x01, + 0xff,0xf3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0e,0xff,0x40,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x1c,0xcc,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xcc,0xc2,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x28,0x88,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x6b,0xba, + 0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07, + 0x88,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x88,0x82,0x00,0x00, + 0x00,0x00,0x00,0x05,0xcc,0x90,0x00,0x00,0x00,0x00,0x01,0x88,0x81, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x88,0x20,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x86,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x11,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0d,0xdd,0xda,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xad,0xdd,0xd0,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xbd,0xda,0x00,0x00,0x00,0x00,0x00,0x00,0x17, + 0xac,0xca,0x6d,0xfe,0x10,0x00,0x3d,0xdd,0x40,0x03,0xdd,0xda,0x00, + 0x00,0x00,0x04,0x9c,0xcc,0x94,0x00,0x00,0x00,0x00,0x0a,0xdd,0xb0, + 0x00,0x00,0x00,0x00,0x00,0x19,0xef,0xff,0xd8,0x00,0x00,0x00,0x00, + 0x00,0xad,0xdd,0x50,0x00,0x00,0x00,0x01,0xdd,0xc0,0x01,0xdd,0xc0, + 0x00,0x01,0xcd,0xdc,0x10,0x01,0xdd,0xdb,0x00,0x00,0x00,0x00,0x6d, + 0xdd,0x10,0x00,0x00,0x00,0x0d,0xdd,0xdd,0xdd,0xdd,0xdc,0x00,0x00, + 0x00,0x00,0x0f,0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x00,0x9a,0x00, + 0x00,0x00,0x00,0x00,0xcf,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0, + 0x00,0x00,0xcf,0xfb,0x00,0x00,0x00,0x00,0x00,0x03,0xef,0xff,0xff, + 0xff,0xf6,0x00,0x00,0x4f,0xff,0x40,0x0b,0xff,0xf4,0x00,0x00,0x00, + 0x9f,0xff,0xff,0xff,0x90,0x00,0x00,0x00,0x0b,0xff,0xc0,0x00,0x00, + 0x00,0x00,0x01,0xdf,0xff,0xff,0xff,0xb0,0x00,0x00,0x00,0x02,0xff, + 0xff,0xb0,0x00,0x00,0x00,0x03,0xff,0xf2,0x04,0xff,0xf1,0x00,0x00, + 0x5f,0xff,0x80,0x0a,0xff,0xf4,0x00,0x00,0x00,0x00,0x7f,0xff,0x10, + 0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xfd,0x00,0x00,0x00,0x00, + 0x0f,0xff,0x76,0x00,0x00,0x00,0x00,0x00,0x00,0xf9,0x00,0x00,0x00, + 0x00,0x00,0x67,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x00,0x00, + 0xcf,0xfb,0x00,0x00,0x00,0x00,0x00,0x2e,0xff,0xfd,0xef,0xff,0xd1, + 0x00,0x00,0x4f,0xff,0x50,0x4f,0xff,0xc0,0x00,0x00,0x03,0xff,0xfc, + 0x79,0xff,0xf4,0x00,0x00,0x00,0x0b,0xff,0xc0,0x00,0x00,0x00,0x00, + 0x09,0xff,0xfd,0xae,0xff,0xf6,0x00,0x00,0x00,0x08,0xff,0xff,0xf2, + 0x00,0x00,0x00,0x05,0xff,0xf6,0x07,0xff,0xf4,0x00,0x00,0x0a,0xff, + 0xf3,0x4f,0xff,0x90,0x00,0x00,0x00,0x00,0x7f,0xff,0x10,0x00,0x00, + 0x00,0x0d,0xff,0xfb,0x88,0x88,0x87,0x00,0x00,0x00,0x00,0x0f,0xfe, + 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xf5,0x00,0x00,0x00,0x00,0x00, + 0x00,0xef,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xcf,0xfb, + 0x00,0x00,0x00,0x00,0x00,0x8f,0xff,0x55,0xef,0xff,0xf6,0x00,0x00, + 0x4f,0xff,0x50,0xcf,0xff,0x30,0x00,0x00,0x09,0xff,0xe1,0x00,0xcf, + 0xf9,0x00,0x00,0x00,0x0b,0xff,0xc0,0x00,0x00,0x00,0x00,0x0e,0xff, + 0xc0,0x01,0xef,0xfb,0x00,0x00,0x00,0x0d,0xff,0xdf,0xf7,0x00,0x00, + 0x00,0x08,0xff,0xf9,0x0a,0xff,0xf7,0x00,0x00,0x01,0xef,0xfc,0xdf, + 0xfe,0x10,0x00,0x00,0x00,0x00,0x7f,0xff,0x10,0x00,0x00,0x00,0x02, + 0xef,0xfe,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xfe,0x00,0x00, + 0x00,0x00,0x00,0x00,0x07,0xf2,0x00,0x00,0x00,0x00,0x00,0x00,0xef, + 0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xcf,0xfe,0x99,0x96, + 0x10,0x00,0x00,0xdf,0xfc,0x06,0xb2,0xef,0xfa,0x00,0x00,0x4f,0xff, + 0x79,0xff,0xf6,0x00,0x00,0x00,0x02,0x34,0x40,0x16,0xff,0xfa,0x00, + 0x00,0x00,0x0b,0xff,0xc0,0x00,0x00,0x00,0x00,0x1f,0xff,0x80,0x00, + 0xaf,0xfd,0x00,0x00,0x00,0x4f,0xff,0x5f,0xfd,0x00,0x00,0x00,0x0a, + 0xff,0xfc,0x0e,0xff,0xf9,0x00,0x00,0x00,0x5f,0xff,0xff,0xf4,0x00, + 0x00,0x00,0x00,0x00,0x7f,0xff,0x10,0x00,0x00,0x00,0x00,0x2e,0xff, + 0xe2,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xfe,0x00,0x00,0x00,0x00, + 0x00,0x00,0x0b,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0xef,0xf0,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xcf,0xff,0xff,0xff,0xd1,0x00, + 0x00,0xff,0xf9,0x00,0x00,0xcf,0xfc,0x00,0x00,0x4f,0xff,0xff,0xff, + 0xd6,0x00,0x00,0x00,0x00,0x01,0x7c,0xff,0xff,0xf5,0x00,0x00,0x00, + 0x0b,0xff,0xc0,0x00,0x00,0x00,0x00,0x1f,0xff,0x70,0x00,0xaf,0xfd, + 0x00,0x00,0x00,0xaf,0xfa,0x1f,0xff,0x30,0x00,0x00,0x0e,0xfd,0xff, + 0x3f,0xfd,0xfc,0x00,0x00,0x00,0x0a,0xff,0xff,0xa0,0x00,0x00,0x00, + 0x00,0x01,0xdf,0xff,0x70,0x00,0x00,0x00,0x00,0x03,0xef,0xfe,0x20, + 0x00,0x00,0x00,0x00,0x00,0x0f,0xfe,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0f,0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0xef,0xf0,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xcf,0xfe,0xac,0xff,0xf9,0x00,0x00,0xff, + 0xf8,0x00,0x00,0xcf,0xfc,0x00,0x00,0x4f,0xff,0xfe,0xff,0xff,0x70, + 0x00,0x00,0x00,0x5f,0xff,0xff,0xff,0x90,0x00,0x00,0x00,0x0b,0xff, + 0xc0,0x00,0x00,0x00,0x00,0x1f,0xff,0x70,0x00,0xaf,0xfd,0x00,0x00, + 0x01,0xff,0xf5,0x0a,0xff,0x90,0x00,0x00,0x1f,0xf8,0xff,0xaf,0xe8, + 0xff,0x00,0x00,0x00,0x08,0xff,0xff,0x90,0x00,0x00,0x00,0x00,0x08, + 0xff,0xff,0xf2,0x00,0x00,0x00,0x00,0x00,0x3e,0xff,0xd1,0x00,0x00, + 0x00,0x00,0x00,0x0f,0xfe,0x00,0x00,0x00,0x00,0x00,0x00,0x3f,0x60, + 0x00,0x00,0x00,0x00,0x00,0x00,0xef,0xf0,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xcf,0xfb,0x00,0xbf,0xfd,0x00,0x00,0xef,0xfb,0x00, + 0x00,0xef,0xfa,0x00,0x00,0x4f,0xff,0x50,0x1b,0xff,0xf0,0x00,0x00, + 0x01,0xff,0xff,0xfd,0x83,0x00,0x00,0x00,0x00,0x0b,0xff,0xc0,0x00, + 0x00,0x00,0x00,0x1f,0xff,0x70,0x00,0xaf,0xfd,0x00,0x00,0x06,0xff, + 0xf1,0x05,0xff,0xf1,0x00,0x00,0x4f,0xf5,0xcf,0xff,0xa6,0xff,0x20, + 0x00,0x00,0x3f,0xff,0xff,0xf4,0x00,0x00,0x00,0x00,0x3f,0xff,0xff, + 0xfc,0x00,0x00,0x00,0x00,0x00,0x03,0xff,0xfd,0x10,0x00,0x00,0x00, + 0x00,0x0f,0xfe,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0x20,0x00,0x00, + 0x00,0x00,0x00,0x00,0xef,0xf0,0x00,0x00,0x00,0x00,0x01,0xde,0xa0, + 0x0a,0xec,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xcf,0xfb,0x00,0xbf,0xfd,0x00,0x00,0x9f,0xff,0x30,0x05,0xff, + 0xf7,0x00,0x00,0x4f,0xff,0x50,0x09,0xff,0xf1,0x00,0x00,0x03,0xff, + 0xf6,0x11,0x98,0x72,0x00,0x01,0x44,0x4c,0xff,0xd4,0x44,0x20,0x00, + 0x00,0x1f,0xff,0x70,0x00,0xaf,0xfd,0x00,0x00,0x0c,0xff,0xb0,0x01, + 0xff,0xf5,0x00,0x00,0x6f,0xf3,0x8f,0xff,0x74,0xff,0x50,0x00,0x01, + 0xdf,0xfc,0xbf,0xfd,0x10,0x00,0x00,0x01,0xdf,0xfd,0x4f,0xff,0x70, + 0x00,0x00,0x00,0x00,0x00,0x4f,0xff,0xc1,0x00,0x00,0x00,0x00,0x0f, + 0xfe,0x00,0x00,0x00,0x00,0x00,0x00,0xbe,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xef,0xf0,0x00,0x00,0x00,0x00,0x00,0x6f,0xf3,0x3f,0xf5, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xcf, + 0xfe,0xbd,0xff,0xfa,0x00,0x00,0x3f,0xff,0xfb,0xcf,0xff,0xe1,0x00, + 0x00,0x4f,0xff,0xdb,0xcf,0xff,0xe0,0x00,0x00,0x02,0xff,0xf5,0x29, + 0xff,0xf2,0x00,0x05,0xff,0xff,0xff,0xff,0xff,0x60,0x00,0x00,0x1f, + 0xff,0x70,0x00,0xaf,0xfd,0x00,0x00,0x2f,0xff,0x60,0x00,0xbf,0xfb, + 0x00,0x00,0x9f,0xf0,0x5f,0xff,0x42,0xff,0x80,0x00,0x08,0xff,0xf3, + 0x2f,0xff,0x80,0x00,0x00,0x08,0xff,0xf4,0x0a,0xff,0xf2,0x00,0x00, + 0x05,0xdd,0xdd,0xdf,0xff,0xf9,0x00,0x00,0x00,0x00,0x0f,0xfe,0x00, + 0x00,0x00,0x00,0x00,0x00,0xea,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xef,0xf0,0x00,0x00,0x00,0x00,0x00,0x0b,0xfb,0xbf,0xa0,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xcf,0xff,0xff, + 0xff,0xe2,0x00,0x00,0x06,0xff,0xff,0xff,0xff,0x40,0x00,0x00,0x4f, + 0xff,0xff,0xff,0xff,0x50,0x00,0x00,0x00,0x9f,0xff,0xff,0xff,0x90, + 0x00,0x05,0xff,0xff,0xff,0xff,0xff,0x60,0x00,0x00,0x1f,0xff,0x70, + 0x00,0xaf,0xfd,0x00,0x00,0x8f,0xff,0x10,0x00,0x6f,0xff,0x20,0x00, + 0xcf,0xd0,0x2f,0xff,0x00,0xff,0xa0,0x00,0x3f,0xff,0x90,0x08,0xff, + 0xf3,0x00,0x00,0x3f,0xff,0x90,0x02,0xff,0xfc,0x00,0x00,0x05,0xff, + 0xff,0xff,0xff,0xf9,0x00,0x00,0x00,0x00,0x0f,0xff,0x86,0x00,0x00, + 0x00,0x00,0x03,0xf6,0x00,0x00,0x00,0x00,0x00,0x00,0x68,0xff,0xf0, + 0x00,0x00,0x00,0x00,0x00,0x02,0xff,0xfe,0x10,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x78,0x88,0x88,0x86,0x10, + 0x00,0x00,0x00,0x39,0xef,0xfd,0x92,0x00,0x00,0x00,0x28,0x88,0x88, + 0x88,0x62,0x00,0x00,0x00,0x00,0x06,0xcf,0xff,0xc6,0x00,0x00,0x03, + 0x88,0x88,0x88,0x88,0x88,0x40,0x00,0x00,0x18,0x88,0x40,0x00,0x68, + 0x87,0x00,0x00,0x78,0x87,0x00,0x00,0x18,0x88,0x30,0x00,0x88,0x60, + 0x08,0x87,0x00,0x78,0x70,0x00,0x68,0x88,0x10,0x01,0x88,0x86,0x00, + 0x00,0x68,0x88,0x10,0x00,0x58,0x88,0x30,0x00,0x03,0x88,0x88,0x88, + 0x88,0x85,0x00,0x00,0x00,0x00,0x0f,0xff,0xfc,0x00,0x00,0x00,0x00, + 0x06,0xf2,0x00,0x00,0x00,0x00,0x00,0x00,0xcf,0xff,0xf0,0x00,0x00, + 0x00,0x00,0x00,0x00,0x6f,0xf5,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0c,0xcc,0xca,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xac,0xcc,0xc0,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0x5a,0xbb,0x83,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3d,0xd8, + 0x67,0xbf,0xb1,0x00,0x00,0x8d,0xdd,0x10,0x00,0x1d,0xdd,0x80,0x00, + 0x0b,0xdd,0xdd,0xdd,0xc9,0x20,0x00,0x00,0x00,0x17,0xbc,0xca,0x50, + 0x00,0x00,0x00,0x0d,0xdd,0xdd,0xdb,0x71,0x00,0x00,0x00,0x4d,0xdd, + 0xdd,0xdd,0xdd,0x30,0x00,0x00,0x00,0xbd,0xda,0x00,0x00,0x00,0x00, + 0x00,0x00,0x17,0xac,0xcb,0x83,0x00,0x00,0x00,0x1d,0xdd,0x60,0x00, + 0x9d,0xdc,0x00,0x00,0x00,0x0d,0xdd,0x80,0x00,0x00,0x00,0x00,0x00, + 0x17,0xbc,0xc8,0x20,0x00,0x00,0x00,0x4d,0xdd,0x30,0x00,0xad,0xdd, + 0x70,0x00,0x01,0xdd,0xdd,0xdd,0xdd,0xd0,0x00,0x02,0xdd,0xa0,0x1d, + 0xda,0x01,0xdd,0xb0,0x00,0x0b,0xdd,0x60,0x00,0x6d,0xdd,0x20,0x00, + 0x00,0x17,0xac,0xca,0x61,0x00,0x00,0x00,0x02,0xe8,0x00,0x00,0x02, + 0xbc,0x10,0x00,0x3f,0xff,0x40,0x00,0x5f,0xff,0x30,0x00,0x0c,0xff, + 0xff,0xff,0xff,0xf2,0x00,0x00,0x04,0xef,0xff,0xff,0xfa,0x00,0x00, + 0x00,0x0f,0xff,0xff,0xff,0xfe,0x20,0x00,0x00,0x5f,0xff,0xff,0xff, + 0xff,0x40,0x00,0x00,0x00,0xcf,0xfb,0x00,0x00,0x00,0x00,0x00,0x03, + 0xef,0xff,0xff,0xff,0xa2,0x00,0x00,0x1f,0xff,0x70,0x00,0xaf,0xfd, + 0x00,0x00,0x00,0x0e,0xff,0x90,0x00,0x00,0x00,0x00,0x01,0xef,0xff, + 0xff,0xf3,0x00,0x00,0x00,0x5f,0xff,0x40,0x04,0xff,0xfe,0x10,0x00, + 0x01,0xff,0xff,0xff,0xff,0xf0,0x00,0x02,0xff,0xb0,0x5f,0xff,0x11, + 0xff,0xc0,0x00,0x0c,0xff,0x70,0x02,0xff,0xff,0x20,0x00,0x03,0xef, + 0xff,0xff,0xfd,0x20,0x00,0x00,0x0b,0x83,0xdf,0x89,0xfb,0x20,0x00, + 0x00,0x0d,0xff,0xff,0xff,0xff,0xfd,0x00,0x00,0x0c,0xff,0xe8,0x8b, + 0xff,0xf9,0x00,0x00,0x2e,0xff,0xfe,0xff,0xff,0x70,0x00,0x00,0x0f, + 0xff,0xeb,0xdf,0xff,0xc0,0x00,0x00,0x5f,0xff,0xcb,0xbb,0xbb,0x30, + 0x00,0x00,0x00,0xcf,0xfb,0x00,0x00,0x00,0x00,0x00,0x1e,0xff,0xfc, + 0xbe,0xff,0xf9,0x00,0x00,0x1f,0xff,0x70,0x00,0xaf,0xfd,0x00,0x00, + 0x00,0x0e,0xff,0x90,0x00,0x00,0x00,0x00,0x09,0xff,0xfe,0xff,0xfd, + 0x00,0x00,0x00,0x5f,0xff,0x40,0x0b,0xff,0xf5,0x00,0x00,0x01,0xff, + 0xff,0xee,0xee,0xe0,0x00,0x02,0xff,0xb0,0x9f,0xff,0x41,0xff,0xc0, + 0x00,0x0c,0xff,0x70,0x0c,0xff,0xff,0x20,0x00,0x2e,0xff,0xfd,0xef, + 0xff,0xd1,0x00,0x00,0x3f,0x2e,0xc3,0xbf,0xb8,0xe3,0x00,0x00,0x07, + 0xff,0xff,0xff,0xff,0xf7,0x00,0x00,0x0c,0xff,0xb0,0x00,0xdf,0xfb, + 0x00,0x00,0x8f,0xff,0x40,0x1b,0xff,0xe0,0x00,0x00,0x0f,0xff,0x80, + 0x08,0xff,0xf3,0x00,0x00,0x5f,0xff,0x40,0x00,0x00,0x00,0x00,0x00, + 0x00,0xcf,0xfb,0x00,0x00,0x00,0x00,0x00,0x8f,0xff,0x40,0x00,0xaf, + 0xf9,0x00,0x00,0x1f,0xff,0x70,0x00,0xaf,0xfd,0x00,0x00,0x00,0x0e, + 0xff,0x90,0x00,0x00,0x00,0x00,0x0e,0xff,0x90,0x8f,0xff,0x20,0x00, + 0x00,0x5f,0xff,0xc1,0x4f,0xff,0xc0,0x00,0x00,0x01,0xff,0xf7,0x00, + 0x00,0x00,0x00,0x02,0xff,0xb0,0xdf,0xff,0x81,0xff,0xc0,0x00,0x0c, + 0xff,0x70,0x7f,0xff,0xff,0x20,0x00,0x8f,0xff,0x50,0x07,0xff,0xf6, + 0x00,0x00,0x6b,0x4f,0x50,0x2f,0xa0,0x8c,0x00,0x00,0x02,0xff,0xfa, + 0x7a,0xff,0xf2,0x00,0x00,0x0c,0xff,0xc2,0x24,0xff,0xf8,0x00,0x00, + 0xdf,0xfb,0x00,0x02,0xe9,0x50,0x00,0x00,0x0f,0xff,0x80,0x02,0xff, + 0xf6,0x00,0x00,0x5f,0xff,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0xcf, + 0xfc,0x33,0x33,0x10,0x00,0x00,0xdf,0xfc,0x00,0x67,0xbf,0xf9,0x00, + 0x00,0x1f,0xff,0x94,0x44,0xcf,0xfd,0x00,0x00,0x00,0x0e,0xff,0x90, + 0x00,0x00,0x00,0x00,0x05,0x79,0x40,0x5f,0xff,0x30,0x00,0x00,0x5f, + 0xff,0xfc,0xcf,0xff,0x20,0x00,0x00,0x01,0xff,0xf7,0x00,0x00,0x00, + 0x00,0x02,0xff,0xb2,0xff,0xff,0xc1,0xff,0xc0,0x00,0x0c,0xff,0x73, + 0xff,0xff,0xff,0x20,0x00,0xdf,0xfb,0x00,0x00,0xef,0xfa,0x00,0x00, + 0x7a,0x5f,0x50,0x0d,0xd0,0x1f,0x20,0x00,0x00,0xbf,0xf8,0x09,0xff, + 0xb0,0x00,0x00,0x0c,0xff,0xff,0xff,0xff,0xb1,0x00,0x00,0xff,0xf9, + 0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0x80,0x01,0xff,0xf7,0x00, + 0x00,0x5f,0xff,0xff,0xff,0xfa,0x00,0x00,0x00,0x00,0xcf,0xff,0xff, + 0xff,0x30,0x00,0x00,0xff,0xf9,0x00,0xcf,0xff,0xf9,0x00,0x00,0x1f, + 0xff,0xff,0xff,0xff,0xfd,0x00,0x00,0x00,0x0e,0xff,0x90,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x5f,0xff,0x40,0x00,0x00,0x5f,0xff,0xff, + 0xff,0xf8,0x00,0x00,0x00,0x01,0xff,0xf7,0x00,0x00,0x00,0x00,0x02, + 0xff,0xb5,0xff,0x8f,0xf2,0xff,0xc0,0x00,0x0c,0xff,0x8d,0xff,0xcf, + 0xff,0x20,0x00,0xff,0xf9,0x00,0x00,0xcf,0xfc,0x00,0x00,0x7a,0x3f, + 0x70,0x0a,0xf0,0x0d,0x50,0x00,0x00,0x5f,0xfc,0x0e,0xff,0x50,0x00, + 0x00,0x0c,0xff,0xff,0xff,0xfd,0x30,0x00,0x00,0xff,0xf8,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0f,0xff,0x80,0x01,0xff,0xf7,0x00,0x00,0x5f, + 0xff,0xff,0xff,0xfa,0x00,0x00,0x00,0x00,0xcf,0xff,0xff,0xff,0x30, + 0x00,0x00,0xff,0xf9,0x00,0xbd,0xdd,0xd8,0x00,0x00,0x1f,0xff,0xff, + 0xff,0xff,0xfd,0x00,0x00,0x00,0x0e,0xff,0x90,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x5f,0xff,0x40,0x00,0x00,0x5f,0xff,0xff,0xff,0xe1, + 0x00,0x00,0x00,0x01,0xff,0xf7,0x00,0x00,0x00,0x00,0x02,0xff,0xb9, + 0xfd,0x2f,0xf6,0xff,0xc0,0x00,0x0c,0xff,0xef,0xfd,0x4f,0xff,0x20, + 0x00,0xff,0xf9,0x00,0x00,0xcf,0xfc,0x00,0x00,0x6c,0x0e,0xb0,0x09, + 0xf2,0x0d,0x50,0x00,0x00,0x1f,0xff,0x5f,0xff,0x10,0x00,0x00,0x0c, + 0xff,0xc1,0x18,0xff,0xe1,0x00,0x00,0xef,0xfa,0x00,0x01,0x20,0x00, + 0x00,0x00,0x0f,0xff,0x80,0x04,0xff,0xf5,0x00,0x00,0x5f,0xff,0x75, + 0x55,0x53,0x00,0x00,0x00,0x00,0xcf,0xfc,0x22,0x22,0x00,0x00,0x00, + 0xdf,0xfb,0x00,0x00,0x00,0x00,0x00,0x00,0x1f,0xff,0xc8,0x88,0xdf, + 0xfd,0x00,0x00,0x00,0x0e,0xff,0x90,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x5f,0xff,0x40,0x00,0x00,0x5f,0xff,0xcf,0xff,0xb0,0x00,0x00, + 0x00,0x01,0xff,0xf7,0x00,0x00,0x00,0x00,0x02,0xff,0xbe,0xf9,0x0e, + 0xfa,0xff,0xc0,0x00,0x0c,0xff,0xff,0xf4,0x3f,0xff,0x20,0x00,0xef, + 0xfb,0x00,0x00,0xef,0xfb,0x00,0x00,0x2f,0x28,0xf3,0x0c,0xf4,0x0f, + 0x30,0x00,0x00,0x09,0xff,0xef,0xf9,0x00,0x00,0x00,0x0c,0xff,0xb0, + 0x05,0xff,0xf3,0x00,0x00,0xaf,0xff,0x20,0x07,0xfe,0x90,0x00,0x00, + 0x0f,0xff,0x91,0x3c,0xff,0xf1,0x00,0x00,0x5f,0xff,0x40,0x00,0x00, + 0x00,0x00,0x00,0x00,0xcf,0xfb,0x00,0x00,0x00,0x00,0x00,0x9f,0xff, + 0x20,0x02,0xc9,0x62,0x00,0x00,0x1f,0xff,0x70,0x00,0xaf,0xfd,0x00, + 0x00,0x00,0x0e,0xff,0x90,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5f, + 0xff,0x40,0x00,0x00,0x5f,0xff,0x4a,0xff,0xfa,0x00,0x00,0x00,0x01, + 0xff,0xf7,0x00,0x00,0x00,0x00,0x02,0xff,0xef,0xf5,0x09,0xfe,0xff, + 0xc0,0x00,0x0c,0xff,0xff,0x80,0x3f,0xff,0x20,0x00,0x9f,0xff,0x30, + 0x06,0xff,0xf7,0x00,0x00,0x0a,0xa0,0xbf,0xea,0xf6,0x5e,0x10,0x00, + 0x00,0x03,0xff,0xff,0xf4,0x00,0x00,0x00,0x0c,0xff,0xeb,0xcf,0xff, + 0xf2,0x00,0x00,0x3f,0xff,0xfa,0xbf,0xff,0x80,0x00,0x00,0x0f,0xff, + 0xff,0xff,0xff,0x90,0x00,0x00,0x5f,0xff,0xed,0xdd,0xdd,0x20,0x00, + 0x00,0x00,0xcf,0xff,0xdd,0xdd,0xd0,0x00,0x00,0x2f,0xff,0xe9,0x8e, + 0xff,0xf3,0x00,0x00,0x1f,0xff,0x70,0x00,0xaf,0xfd,0x00,0x00,0x00, + 0x0e,0xff,0x90,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5f,0xff,0x40, + 0x00,0x00,0x5f,0xff,0x41,0xcf,0xff,0x90,0x00,0x00,0x01,0xff,0xf7, + 0x00,0x00,0x00,0x00,0x02,0xff,0xff,0xf1,0x05,0xff,0xff,0xc0,0x00, + 0x0c,0xff,0xfd,0x10,0x3f,0xff,0x20,0x00,0x3f,0xff,0xfb,0xcf,0xff, + 0xe1,0x00,0x00,0x02,0xe9,0x13,0x30,0x14,0xe6,0x00,0x00,0x00,0x00, + 0xdf,0xff,0xe0,0x00,0x00,0x00,0x0c,0xff,0xff,0xff,0xff,0x90,0x00, + 0x00,0x06,0xff,0xff,0xff,0xfc,0x10,0x00,0x00,0x0f,0xff,0xff,0xff, + 0xfa,0x10,0x00,0x00,0x5f,0xff,0xff,0xff,0xff,0x20,0x00,0x00,0x00, + 0xcf,0xff,0xff,0xff,0xe0,0x00,0x00,0x05,0xff,0xff,0xff,0xff,0x90, + 0x00,0x00,0x1f,0xff,0x70,0x00,0xaf,0xfd,0x00,0x00,0x00,0x0e,0xff, + 0x90,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x5f,0xff,0x40,0x00,0x00, + 0x5f,0xff,0x40,0x1e,0xff,0xf9,0x00,0x00,0x01,0xff,0xf7,0x00,0x00, + 0x00,0x00,0x02,0xff,0xff,0xc0,0x01,0xff,0xff,0xc0,0x00,0x0c,0xff, + 0xf3,0x00,0x3f,0xff,0x20,0x00,0x06,0xff,0xff,0xff,0xff,0x40,0x00, + 0x00,0x00,0x3d,0xe9,0x77,0xbf,0x70,0x00,0x00,0x00,0x00,0x58,0x88, + 0x50,0x00,0x00,0x00,0x07,0x88,0x88,0x88,0x84,0x00,0x00,0x00,0x00, + 0x3a,0xef,0xfc,0x71,0x00,0x00,0x00,0x08,0x88,0x88,0x87,0x30,0x00, + 0x00,0x00,0x38,0x88,0x88,0x88,0x88,0x10,0x00,0x00,0x00,0x78,0x88, + 0x88,0x88,0x80,0x00,0x00,0x00,0x29,0xdf,0xff,0xc6,0x00,0x00,0x00, + 0x18,0x88,0x40,0x00,0x68,0x87,0x00,0x00,0x00,0x08,0x88,0x50,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x4c,0xcc,0x30,0x00,0x00,0x38,0x88, + 0x20,0x03,0x88,0x88,0x30,0x00,0x01,0x88,0x84,0x00,0x00,0x00,0x00, + 0x01,0x88,0x88,0x50,0x00,0x78,0x88,0x70,0x00,0x07,0x88,0x50,0x00, + 0x28,0x88,0x10,0x00,0x00,0x39,0xef,0xfd,0x92,0x00,0x00,0x00,0x00, + 0x00,0x5a,0xbb,0x82,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x10,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x10,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x67,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x18,0xcc,0xb7,0x10,0x00,0x00,0x00,0x00, + 0x00,0x08,0xdd,0x80,0x00,0x00,0x00,0x0a,0xdd,0xdd,0xdd,0xdd,0xa0, + 0x00,0x00,0x00,0x2b,0xff,0xfe,0x81,0x00,0x00,0x00,0x00,0x00,0x02, + 0xdd,0x80,0x00,0x00,0x00,0x00,0x3b,0xff,0xfd,0x70,0x00,0x00,0x00, + 0x00,0x07,0xdf,0xfe,0x91,0x00,0x00,0x00,0x00,0x07,0xdd,0x80,0x00, + 0x00,0x00,0x00,0x00,0x39,0xbc,0xc9,0x40,0x00,0x00,0x00,0x00,0x18, + 0xbc,0xb7,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xbf,0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x69,0x95,0x00,0x00, + 0x00,0x00,0x02,0xef,0xff,0xff,0xc1,0x00,0x00,0x00,0x00,0x00,0x09, + 0xff,0x90,0x00,0x00,0x00,0x07,0xff,0xff,0xff,0xff,0xb0,0x00,0x00, + 0x02,0xef,0xfe,0xff,0xfc,0x00,0x00,0x00,0x00,0x00,0x02,0xff,0x90, + 0x00,0x00,0x00,0x03,0xff,0xfe,0xff,0xfa,0x00,0x00,0x00,0x00,0x9f, + 0xff,0xff,0xfd,0x00,0x00,0x00,0x00,0x06,0xff,0xb0,0x00,0x00,0x00, + 0x00,0x04,0xff,0xff,0xff,0xf7,0x00,0x00,0x00,0x01,0xdf,0xff,0xff, + 0xe2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x0b,0xf3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x17,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x71, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xaf,0xf7,0x00,0x00,0x00,0x00, + 0x0a,0xff,0xc6,0xef,0xf7,0x00,0x00,0x00,0x00,0x00,0x09,0xff,0x90, + 0x00,0x00,0x00,0x01,0xef,0xff,0xbb,0xbb,0x80,0x00,0x00,0x0b,0xff, + 0xa0,0x1e,0xff,0x50,0x00,0x00,0xbb,0xbb,0xbc,0xff,0xeb,0x10,0x00, + 0x00,0x0c,0xff,0xa0,0x1e,0xff,0x30,0x00,0x00,0x03,0xff,0xe2,0x1b, + 0xff,0x60,0x00,0x00,0x00,0x03,0xff,0xe0,0x00,0x00,0x00,0x00,0x0d, + 0xff,0xb2,0x8f,0xff,0x10,0x00,0x00,0x08,0xff,0xd2,0x8f,0xfc,0x00, + 0x00,0x00,0x00,0x29,0x99,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x9c, + 0xf5,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0xef,0x00,0x00,0x00,0x00, + 0x01,0x11,0x11,0x11,0x11,0x00,0x00,0x00,0x00,0x00,0xfe,0x60,0x00, + 0x00,0x00,0x00,0x00,0x00,0xaf,0xf7,0x00,0x00,0x00,0x00,0x1f,0xff, + 0x20,0x6f,0xfc,0x00,0x00,0x00,0x00,0x00,0x09,0xff,0x90,0x00,0x00, + 0x00,0x00,0x4f,0xff,0x90,0x00,0x00,0x00,0x00,0x03,0x57,0x30,0x0a, + 0xff,0x80,0x00,0x00,0xef,0xff,0xff,0xff,0xff,0x10,0x00,0x00,0x02, + 0x45,0x20,0x0a,0xff,0x80,0x00,0x00,0x08,0xff,0x90,0x06,0xff,0x90, + 0x00,0x00,0x00,0x00,0xef,0xf2,0x00,0x00,0x00,0x00,0x1f,0xff,0x20, + 0x0e,0xff,0x40,0x00,0x00,0x00,0x13,0x20,0x1f,0xff,0x20,0x00,0x00, + 0x00,0x4f,0xff,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xf5,0x00, + 0x00,0x00,0x00,0x00,0x06,0xdf,0xff,0x00,0x00,0x00,0x00,0x0b,0xff, + 0xff,0xff,0xfb,0x00,0x00,0x00,0x00,0x00,0xff,0xfd,0x60,0x00,0x00, + 0x00,0x00,0x00,0x23,0x31,0x00,0x00,0x00,0x00,0x3f,0xff,0x00,0x3f, + 0xff,0x00,0x00,0x00,0x00,0x00,0x09,0xff,0x90,0x00,0x00,0x00,0x00, + 0x04,0xff,0xfd,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x0d,0xff,0x70, + 0x00,0x00,0xef,0xfb,0xbc,0xff,0xeb,0x10,0x00,0x00,0x00,0x00,0x00, + 0x0b,0xff,0x80,0x00,0x00,0x0b,0xff,0x90,0x07,0xff,0x90,0x00,0x00, + 0x00,0x00,0x8f,0xf7,0x00,0x00,0x00,0x00,0x0f,0xff,0x40,0x1f,0xff, + 0x30,0x00,0x00,0x00,0x29,0xcb,0x4d,0xff,0x60,0x00,0x00,0x00,0x4f, + 0xff,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xf5,0x00,0x00,0x00, + 0x00,0x05,0xdf,0xff,0xb4,0x00,0x00,0x00,0x00,0x0b,0xff,0xff,0xff, + 0xfb,0x00,0x00,0x00,0x00,0x00,0x4c,0xff,0xfd,0x50,0x00,0x00,0x00, + 0x00,0x8f,0xf7,0x00,0x00,0x00,0x00,0x4f,0xfd,0x00,0x2f,0xff,0x10, + 0x00,0x00,0x00,0x00,0x09,0xff,0x90,0x00,0x00,0x00,0x00,0x00,0x2d, + 0xff,0xf4,0x00,0x00,0x00,0x00,0x00,0x2c,0xef,0xfe,0x10,0x00,0x00, + 0x6f,0xf7,0x02,0xff,0x90,0x00,0x00,0x00,0x05,0xcf,0xb4,0x6f,0xff, + 0x40,0x00,0x00,0x0c,0xff,0xf6,0x5e,0xff,0x50,0x00,0x00,0x00,0x00, + 0x2f,0xfd,0x00,0x00,0x00,0x00,0x08,0xff,0xd7,0xcf,0xfb,0x00,0x00, + 0x00,0x02,0xef,0xff,0xfe,0xff,0x70,0x00,0x00,0x00,0x28,0x88,0x10, + 0x00,0x00,0x00,0x00,0x00,0x00,0x88,0x83,0x00,0x00,0x00,0x00,0x0e, + 0xff,0xb4,0x00,0x00,0x00,0x00,0x00,0x05,0x66,0x66,0x66,0x65,0x00, + 0x00,0x00,0x00,0x00,0x00,0x4b,0xff,0xe0,0x00,0x00,0x00,0x00,0x6f, + 0xfe,0x20,0x00,0x00,0x00,0x5f,0xfd,0x00,0x2f,0xff,0x10,0x00,0x00, + 0x00,0xb6,0x09,0xff,0x90,0x00,0x00,0x00,0x00,0x00,0x01,0xaf,0xff, + 0x20,0x00,0x00,0x00,0x00,0x1f,0xff,0xc1,0x00,0x00,0x00,0x08,0xff, + 0x42,0xff,0x90,0x00,0x00,0x00,0x05,0xff,0xff,0xff,0xfa,0x00,0x00, + 0x00,0x0c,0xff,0xef,0xff,0xfa,0x00,0x00,0x00,0x00,0x00,0x08,0xff, + 0x60,0x00,0x00,0x00,0x00,0x9f,0xff,0xff,0xd1,0x00,0x00,0x00,0x0a, + 0xff,0xa3,0x8f,0xff,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0e,0xff,0x82, + 0x00,0x00,0x00,0x00,0x00,0x03,0x44,0x44,0x44,0x43,0x00,0x00,0x00, + 0x00,0x00,0x00,0x28,0xff,0xe0,0x00,0x00,0x00,0x00,0x0b,0xff,0xe4, + 0x00,0x00,0x00,0x4f,0xfe,0x00,0x2f,0xff,0x00,0x00,0x00,0x00,0xef, + 0xdc,0xff,0x90,0x00,0x00,0x00,0x00,0x01,0x10,0x0c,0xff,0x80,0x00, + 0x00,0x00,0x00,0x05,0xbf,0xf8,0x00,0x00,0x00,0x00,0xbf,0xf5,0xff, + 0x90,0x00,0x00,0x00,0x03,0xff,0xc9,0xba,0x60,0x00,0x00,0x00,0x0a, + 0xff,0x86,0xbb,0x60,0x00,0x00,0x00,0x00,0x00,0x01,0xdf,0xe2,0x00, + 0x00,0x00,0x04,0xff,0xd7,0xcf,0xf7,0x00,0x00,0x00,0x0e,0xff,0x20, + 0x0e,0xff,0x60,0x00,0x00,0x00,0x27,0x77,0x10,0x00,0x00,0x00,0x00, + 0x00,0x00,0x77,0x73,0x00,0x00,0x00,0x00,0x07,0xef,0xff,0x92,0x00, + 0x00,0x00,0x00,0x0b,0xff,0xff,0xff,0xfb,0x00,0x00,0x00,0x00,0x00, + 0x29,0xff,0xfe,0x70,0x00,0x00,0x00,0x00,0x00,0x9f,0xff,0x20,0x00, + 0x00,0x2f,0xff,0x10,0x5f,0xfc,0x00,0x00,0x00,0x00,0xcf,0xff,0xff, + 0x90,0x00,0x00,0x00,0x07,0xff,0xc0,0x08,0xff,0xa0,0x00,0x00,0x03, + 0x69,0x40,0x2f,0xff,0x00,0x00,0x00,0x00,0x1c,0xff,0xff,0x90,0x00, + 0x00,0x00,0x01,0xff,0xc1,0x11,0x11,0x00,0x00,0x00,0x07,0xff,0xb0, + 0x04,0x31,0x00,0x00,0x00,0x01,0x22,0x22,0x5f,0xfc,0x10,0x00,0x00, + 0x0a,0xff,0x50,0x2f,0xfe,0x00,0x00,0x00,0x0e,0xff,0x10,0x0e,0xff, + 0x30,0x00,0x00,0x00,0x4f,0xff,0x20,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xf5,0x00,0x00,0x00,0x00,0x00,0x18,0xff,0xff,0x00,0x00,0x00, + 0x00,0x0b,0xff,0xff,0xff,0xfb,0x00,0x00,0x00,0x00,0x00,0xff,0xff, + 0x81,0x00,0x00,0x00,0x01,0xce,0xf1,0x0d,0xff,0x60,0x00,0x00,0x0d, + 0xff,0x82,0xcf,0xf8,0x00,0x00,0x00,0x00,0x04,0xdf,0xff,0x90,0x00, + 0x00,0x00,0x03,0xff,0xf9,0x8f,0xff,0x70,0x00,0x00,0x07,0xff,0xc4, + 0x9f,0xfe,0x00,0x00,0x00,0x00,0x02,0xef,0xff,0x90,0x00,0x00,0x00, + 0x00,0xef,0xff,0xff,0xff,0x00,0x00,0x00,0x01,0xff,0xf5,0x5f,0xff, + 0x30,0x00,0x00,0x08,0xff,0xff,0xff,0xff,0xa0,0x00,0x00,0x09,0xff, + 0x80,0x6f,0xfd,0x00,0x00,0x00,0x0a,0xff,0x80,0x6f,0xfd,0x00,0x00, + 0x00,0x00,0x4f,0xff,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xf5, + 0x00,0x00,0x00,0x00,0x00,0x00,0x29,0xff,0x00,0x00,0x00,0x00,0x03, + 0x44,0x44,0x44,0x43,0x00,0x00,0x00,0x00,0x00,0xff,0x92,0x00,0x00, + 0x00,0x00,0x00,0xef,0xfb,0x7f,0xff,0x50,0x00,0x00,0x04,0xff,0xff, + 0xff,0xe1,0x00,0x00,0x00,0x00,0x00,0x1c,0xff,0x90,0x00,0x00,0x00, + 0x00,0x9f,0xff,0xff,0xfc,0x10,0x00,0x00,0x01,0xcf,0xff,0xff,0xf5, + 0x00,0x00,0x00,0x00,0x00,0x3f,0xff,0x90,0x00,0x00,0x00,0x00,0xbf, + 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x5f,0xff,0xff,0xf8,0x00,0x00, + 0x00,0x08,0xff,0xff,0xff,0xff,0xb0,0x00,0x00,0x03,0xff,0xff,0xff, + 0xf6,0x00,0x00,0x00,0x02,0xff,0xff,0xff,0xf4,0x00,0x00,0x00,0x00, + 0x2a,0xaa,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0xaa,0xa4,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x2a,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xa2,0x00,0x00,0x00,0x00,0x00, + 0x00,0x6f,0xff,0xff,0xfc,0x00,0x00,0x00,0x00,0x4b,0xef,0xea,0x20, + 0x00,0x00,0x00,0x00,0x00,0x03,0xbb,0x70,0x00,0x00,0x00,0x00,0x05, + 0x9b,0xba,0x61,0x00,0x00,0x00,0x00,0x06,0xab,0xb9,0x30,0x00,0x00, + 0x00,0x00,0x00,0x05,0xbb,0x70,0x00,0x00,0x00,0x00,0x58,0x88,0x88, + 0x88,0x00,0x00,0x00,0x00,0x02,0x8b,0xba,0x50,0x00,0x00,0x00,0x05, + 0x88,0x88,0x88,0x88,0x60,0x00,0x00,0x00,0x3a,0xef,0xec,0x50,0x00, + 0x00,0x00,0x00,0x2a,0xef,0xeb,0x30,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05, + 0xcf,0xfd,0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x07,0xf9,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x09,0xf7,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x95,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x22,0x20,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xdd,0xd3,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0xcc,0x13,0xcc, + 0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x80,0x00,0x00,0x00,0x00,0x00, + 0x9e,0x10,0x00,0x6d,0xec,0x40,0x00,0x01,0x7b,0xcc,0xa6,0x11,0x83, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x2f,0xf2, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0xff,0x20,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x01,0xef,0x60,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0xff,0xf2,0x00,0x00,0x00, + 0x00,0x00,0x08,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xef,0xf4,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0x31,0xff,0x20,0x00, + 0x00,0x00,0x00,0x02,0x5b,0xc6,0x20,0x00,0x00,0x00,0x00,0x2f,0x80, + 0x04,0xfe,0x6f,0xf2,0x00,0x2e,0xff,0xff,0xff,0xee,0xfe,0x20,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xbf,0xc0,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xcf,0xb0,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x26,0x62,0x00,0x00,0x00, + 0x00,0x00,0x00,0x1e,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x04,0xff,0xf2,0x00,0x00,0x00,0x00,0x00, + 0x06,0xf3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xde,0xe3,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x01,0xcf,0x71,0xef,0x61,0x10,0x00,0x00, + 0x00,0x8f,0xff,0xff,0xf9,0x00,0x00,0x00,0x00,0x08,0xf2,0x09,0xf8, + 0x0b,0xf7,0x00,0xaf,0xfe,0x54,0x9f,0xff,0xfd,0x30,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xff,0x70,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x7f,0xf3,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x4f,0xf4,0x00,0x00,0x00,0x00,0x00, + 0x02,0x9d,0xf2,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x04,0xff,0xf2,0x00,0x00,0x00,0x00,0x00,0x03,0xf6, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x24,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x0b,0xff,0xff,0xff,0xff,0xb0,0x00,0x00,0x06,0xff, + 0xfc,0xcc,0xff,0x70,0x00,0x00,0x00,0x01,0xea,0x0a,0xf8,0x0b,0xf7, + 0x00,0xcf,0xf5,0x01,0xaf,0xff,0xf1,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x08,0xff,0x30,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x3f,0xf8,0x00,0x00,0x00,0x00,0x04,0x90,0xa2,0x00,0x00, + 0x00,0x00,0x00,0x00,0x4f,0xf4,0x00,0x00,0x00,0x00,0x00,0x04,0xff, + 0xf2,0x00,0x00,0x00,0x00,0x00,0x01,0x33,0x33,0x10,0x00,0x00,0x00, + 0x00,0x00,0x11,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0xea,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x8f,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x0b,0xff,0xff,0xff,0xff,0xb0,0x00,0x00,0x08,0xce,0x87,0x92, + 0xff,0xd0,0x00,0x00,0x00,0x00,0x7f,0x36,0xfc,0x4e,0xf3,0x00,0x9f, + 0xfa,0x1c,0xff,0xff,0xf7,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x0b,0xff,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x1f,0xfb,0x00,0x00,0x00,0x00,0x09,0xf8,0xf6,0x00,0x00,0x00,0x00, + 0x02,0x66,0x9f,0xf9,0x66,0x20,0x00,0x00,0x00,0x04,0xff,0xf2,0x00, + 0x00,0x00,0x00,0x00,0x05,0xff,0xff,0x40,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xae,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x9f,0xe0, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01, + 0x4f,0xf2,0x5f,0xf2,0x10,0x00,0x00,0x00,0x00,0x08,0xba,0xff,0xc0, + 0x00,0x00,0x00,0x00,0x0d,0xb0,0x8f,0xff,0x60,0x00,0x1d,0xff,0xff, + 0xfe,0x3c,0xa5,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0d,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xfd, + 0x00,0x00,0x00,0x00,0x14,0xdf,0xc4,0x10,0x00,0x00,0x00,0x04,0xff, + 0xff,0xff,0xff,0x40,0x00,0x00,0x00,0x02,0x88,0x81,0x00,0x00,0x00, + 0x00,0x00,0x05,0xff,0xff,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0x20,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xbf,0xf1,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xf3, + 0x1f,0xf2,0x00,0x00,0x00,0x00,0x06,0xbf,0xff,0xff,0x60,0x00,0x00, + 0x6b,0xb7,0x15,0xf4,0x01,0x20,0x00,0x00,0x01,0x9f,0xff,0xf6,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0d,0xff, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xfd,0x00,0x00, + 0x00,0x00,0x5f,0xef,0xdf,0x40,0x00,0x00,0x00,0x04,0xff,0xff,0xff, + 0xff,0x40,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x02,0x55,0x55,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x3f,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xdf,0xf3,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0b,0xff,0xff,0xff,0xff, + 0xb0,0x00,0x00,0x00,0xbf,0xff,0xff,0xc5,0x00,0x00,0x07,0xfd,0xbf, + 0xa0,0xcd,0x00,0x00,0x00,0x00,0x00,0x8f,0xff,0xff,0x70,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0b,0xff,0x10,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x1f,0xfb,0x00,0x00,0x00,0x00, + 0x15,0x4f,0x25,0x10,0x00,0x00,0x00,0x00,0x11,0x4f,0xf4,0x11,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x0f,0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xff,0xf5,0x00,0x00,0x00,0x00,0x00,0x15, + 0x51,0x05,0x51,0x00,0x00,0x00,0x0b,0xff,0xff,0xff,0xff,0xb0,0x00, + 0x00,0x04,0xff,0xfd,0xb2,0x00,0x00,0x00,0x0e,0xf4,0x1f,0xf2,0x4f, + 0x60,0x00,0x00,0x00,0x01,0xff,0xe2,0x9f,0xf3,0x00,0x00,0x00,0x00, + 0x00,0x35,0x40,0x00,0x00,0x00,0x00,0x08,0xff,0x40,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x4f,0xf8,0x00,0x00,0x00,0x00,0x00,0x4b, + 0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x4f,0xf4,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0b,0xe0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x01,0xff,0xf5,0x00,0x00,0x00,0x00,0x00,0x4f,0xf4,0x3f, + 0xf5,0x00,0x00,0x00,0x02,0x37,0xfd,0x38,0xfc,0x20,0x00,0x00,0x05, + 0xff,0xa7,0x95,0x75,0x20,0x00,0x0f,0xf3,0x0f,0xf2,0x0b,0xe1,0x00, + 0x00,0x00,0x01,0xff,0xc0,0x3f,0xf6,0x00,0x00,0x00,0x00,0x00,0xbf, + 0xc0,0x00,0x00,0x00,0x00,0x03,0xff,0x70,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x7f,0xf3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x4f,0xf4,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xf2, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x01,0xff,0xf5,0x00,0x00,0x00,0x00,0x00,0x8f,0xf7,0x6f,0xf9,0x00, + 0x00,0x00,0x00,0x03,0xff,0x14,0xfe,0x00,0x00,0x00,0x01,0xff,0xfc, + 0xdf,0xff,0x40,0x00,0x0b,0xf7,0x4f,0xe1,0x02,0xf7,0x00,0x00,0x00, + 0x00,0xcf,0xfc,0xef,0xf2,0x00,0x00,0x00,0x00,0x00,0xff,0xf1,0x00, + 0x00,0x00,0x00,0x00,0xbf,0xd0,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xcf,0xb0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x01,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xf6,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x88, + 0x83,0x00,0x00,0x00,0x00,0x00,0x9f,0xf8,0x7f,0xfa,0x00,0x00,0x00, + 0x00,0x00,0xff,0x31,0xff,0x20,0x00,0x00,0x00,0x4d,0xff,0xff,0xe6, + 0x00,0x00,0x02,0xdf,0xfe,0x40,0x00,0x9e,0x10,0x00,0x00,0x00,0x19, + 0xef,0xfb,0x40,0x00,0x00,0x00,0x00,0x01,0xff,0xf2,0x00,0x00,0x00, + 0x00,0x00,0x3f,0xf3,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xff, + 0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xf9,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x8c,0xc7,0x6c,0xc8,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3a,0xb3,0x10,0x00,0x00, + 0x00,0x03,0x31,0x00,0x00,0x14,0x10,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x01,0xcc,0xc1,0x00,0x00,0x00,0x00,0x00, + 0x07,0xfa,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0a,0xf7,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x20,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff, + 0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x0f,0xff,0xff,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xff,0xff,0xf0,0x0f,0xff,0xff,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xff,0xf0,0x00,0x07,0xbf,0xff,0xff,0xff,0xff,0xf0, + 0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff, + 0xfb,0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x00,0x00, + 0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x0f,0xff,0xff,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xff,0xf0,0x0f,0xff,0xff,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xf0,0x00,0xbf,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff, + 0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xfb, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xff,0xff,0x00,0x00,0x0f,0xff,0xff,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xf0,0x0f,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, + 0xf0,0x07,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff, + 0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0x70,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff, + 0xff,0x00,0x00,0x0f,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, + 0xf0,0x0f,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xf0,0x0b, + 0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff, + 0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xb0,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xaf,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff, + 0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xfa,0x00,0x00,0x00,0x00, + 0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x00, + 0x00,0x0f,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xf0,0x0f, + 0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xf0,0x0f,0xff,0xff, + 0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0, + 0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xaf, + 0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff, + 0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xfa,0x00,0x00,0x00,0xff,0xff, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x00,0x00,0x0f, + 0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xf0,0x0f,0xff,0xff, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xf0,0x0f,0xff,0xff,0xb0,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x0b,0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xff,0xff,0x00,0x00,0x00,0x0a,0xff,0xff,0xff, + 0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f, + 0xff,0xff,0xff,0xff,0xff,0xff,0xa2,0x00,0x00,0xff,0xff,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x00,0x00,0x0f,0xff,0xff, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xf0,0x0f,0xff,0xff,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xff,0xff,0xf0,0x0f,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xff,0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xff, + 0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff, + 0xff,0xff,0xff,0xff,0xf2,0x00,0x00,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xff,0xff,0x00,0x00,0x0f,0xff,0xff,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xff,0xff,0xf0,0x0f,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xff,0xff,0xf0,0x0f,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff, + 0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0, + 0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff, + 0xff,0xff,0xf2,0x00,0x00,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xff,0x00,0x00,0x0f,0xff,0xff,0xb0,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0b,0xff,0xff,0xf0,0x0f,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff, + 0xff,0xf0,0x0f,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xf0, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, + 0x00,0x00,0x00,0x0a,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff, + 0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff, + 0xa2,0x00,0x00,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f, + 0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff, + 0xff,0xf0,0x0f,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xf0, + 0x0f,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xf0,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xaf,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff, + 0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xfa,0x00,0x00, + 0x00,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, + 0x00,0x00,0x0b,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff, + 0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xb0, + 0x0f,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xf0,0x0f,0xff, + 0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xf0,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xaf,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff, + 0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xfa,0x00,0x00,0x00,0x00,0xff, + 0xff,0xff,0xff,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x00,0x00, + 0x07,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff, + 0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0x70,0x0f,0xff, + 0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xf0,0x0f,0xff,0xff,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff, + 0xff,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0xbf, + 0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff, + 0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xfb,0x00,0x0f,0xff,0xff,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xff,0xff,0xf0,0x0f,0xff,0xff,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xbf,0xff, + 0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f, + 0xff,0xff,0xff,0xff,0xfb,0x70,0x00,0x0f,0xff,0xff,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xff,0xf0,0x0f,0xff,0xff,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x11,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x22,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xff, + 0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xff, + 0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff, + 0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0xff,0xff, + 0xff,0xff,0xff,0xf0,0x00,0x00,0x7b,0xff,0xff,0xff,0xff,0xf0,0x0f, + 0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xfb, + 0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0f,0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xff,0xff, + 0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0xff,0xff, + 0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xff, + 0xff,0xf0,0x00,0x0b,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff, + 0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xfb,0x00, + 0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x00,0xff,0xff,0xff,0xff, + 0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xff, + 0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xff, + 0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0, + 0x00,0x7f,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff, + 0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0x70,0x0f,0xff, + 0xff,0xff,0xff,0xff,0xff,0xf0,0x00,0xff,0xff,0xff,0xff,0xff,0xff, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0xff,0xa0, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0, + 0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x22,0xbf, + 0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff, + 0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xb0,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xa0,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff, + 0xff,0xff,0xff,0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x22,0xff,0xff,0xff, + 0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f, + 0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xa0,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x00,0x00,0x00,0x0f,0xff, + 0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff, + 0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff, + 0xff,0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f, + 0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x22,0xff,0xff,0xff,0xff,0xff, + 0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff, + 0xff,0xff,0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xff,0xa0,0x00,0x00,0x00, + 0x00,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x00,0x00, + 0x00,0x00,0x00,0x00,0xff,0xff,0x00,0x00,0x00,0x0f,0xff,0xff,0xff, + 0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f, + 0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff, + 0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0xff, + 0xff,0xff,0xff,0xff,0xf0,0x22,0xff,0xff,0xff,0xff,0xff,0xff,0xf0, + 0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff, + 0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff, + 0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xa0,0x00,0x00,0x00,0xff, + 0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xff,0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xff, + 0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff, + 0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xff, + 0xff,0xff,0xf0,0x22,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff, + 0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff, + 0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff, + 0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f, + 0xff,0xff,0xff,0xff,0xff,0xa0,0x00,0x00,0x00,0x00,0xff,0xff,0x00, + 0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0, + 0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff, + 0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xff,0xff, + 0xf0,0x22,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff, + 0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff, + 0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0xff, + 0xff,0xff,0xa0,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x00,0x00,0x00, + 0x00,0x00,0x00,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff, + 0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff, + 0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x22, + 0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff, + 0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xa0, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x0b,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff, + 0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xb0,0x0f, + 0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x22,0xff,0xff, + 0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0, + 0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0xff,0xa0,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07, + 0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff, + 0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0x70,0x0f,0xff,0xff, + 0xff,0xff,0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x22,0xff,0xff,0xff,0xff, + 0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff, + 0xff,0xff,0xff,0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0f,0xff,0xa0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xbf,0xff, + 0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0, + 0x0f,0xff,0xff,0xff,0xff,0xff,0xfb,0x00,0x0f,0xff,0xff,0xff,0xff, + 0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xff, + 0xff,0xff,0xff,0xff,0xff,0xf0,0x22,0xff,0xff,0xff,0xff,0xff,0xff, + 0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff,0xff,0xff, + 0xff,0xff,0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x0f,0xa0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x07,0xbf,0xff,0xff, + 0xff,0xff,0xf0,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x0f,0xff, + 0xff,0xff,0xff,0xfb,0x70,0x00,0x0f,0xff,0xff,0xff,0xff,0xff,0xff, + 0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0xff,0xff, + 0xff,0xff,0xff,0xf0,0x22,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x11, + 0x11,0x11,0x11,0x11,0x11,0x11,0x11,0x0f,0xff,0xff,0xff,0xff,0xff, + 0xff,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xff,0xff,0xf0,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0xff,0xff,0xff,0xff,0xff, + 0xff,0xf0,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 +}; + +#endif//MENUFONT_H \ No newline at end of file diff --git a/mainui/udll_int.cpp b/mainui/udll_int.cpp new file mode 100644 index 0000000..62e55ea --- /dev/null +++ b/mainui/udll_int.cpp @@ -0,0 +1,67 @@ +/* +dll_int.cpp - dll entry point +Copyright (C) 2010 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "extdll.h" +#include "basemenu.h" +#include "utils.h" + +ui_enginefuncs_t g_engfuncs; +ui_globalvars_t *gpGlobals; +CMenu gMenu; + +// main DLL entry point +BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved ) +{ + return TRUE; +} + +static UI_FUNCTIONS gFunctionTable = +{ + UI_VidInit, + UI_Init, + UI_Shutdown, + UI_UpdateMenu, + UI_KeyEvent, + UI_MouseMove, + UI_SetActiveMenu, + UI_AddServerToList, + UI_GetCursorPos, + UI_SetCursorPos, + UI_ShowCursor, + UI_CharEvent, + UI_MouseInRect, + UI_IsVisible, + UI_CreditsActive, + UI_FinalCredits +}; + +//======================================================================= +// GetApi +//======================================================================= +int GetMenuAPI( UI_FUNCTIONS *pFunctionTable, ui_enginefuncs_t* pEngfuncsFromEngine, ui_globalvars_t *pGlobals ) +{ + if( !pFunctionTable || !pEngfuncsFromEngine ) + { + return FALSE; + } + + // copy HUD_FUNCTIONS table to engine, copy engfuncs table from engine + memcpy( pFunctionTable, &gFunctionTable, sizeof( UI_FUNCTIONS )); + memcpy( &g_engfuncs, pEngfuncsFromEngine, sizeof( ui_enginefuncs_t )); + + gpGlobals = pGlobals; + + return TRUE; +} \ No newline at end of file diff --git a/mainui/ui_title_anim.cpp b/mainui/ui_title_anim.cpp new file mode 100644 index 0000000..01eed5a --- /dev/null +++ b/mainui/ui_title_anim.cpp @@ -0,0 +1,146 @@ +#include "extdll.h" +#include "basemenu.h" +#include "utils.h" +#include "ui_title_anim.h" + +#define BANNER_X_FIX -16 +#define BANNER_Y_FIX -20 + +// Title Transition Time period +#define TTT_PERIOD 200.0f + +quad_t TitleLerpQuads[2]; +int transition_initial_time; +int transition_state; +HIMAGE TransPic = 0; +int PreClickDepth; +bool hold_button_stack = false; + +void UI_TACheckMenuDepth( void ) +{ + PreClickDepth = uiStatic.menuDepth; +} + +menuPicButton_s *ButtonStack[UI_MAX_MENUDEPTH]; +int ButtonStackDepth; + +void UI_PopPButtonStack() +{ + if( hold_button_stack ) return; + + if ( ButtonStack[ButtonStackDepth] ) UI_SetTitleAnim( AS_TO_BUTTON, ButtonStack[ButtonStackDepth] ); + if ( ButtonStackDepth ) ButtonStackDepth--; +} + +void UI_PushPButtonStack( menuPicButton_s *button ) +{ + if( ButtonStack[ButtonStackDepth] == button ) + return; + + ButtonStackDepth++; + ButtonStack[ButtonStackDepth] = button; +} + +float UI_GetTitleTransFraction( void ) +{ + float fraction = (float)(uiStatic.realTime - transition_initial_time ) / TTT_PERIOD; + + if( fraction > 1.0f ) + fraction = 1.0f; + + return fraction; +} + +void LerpQuad( quad_t a, quad_t b, float frac, quad_t *c ) +{ + c->x = a.x + (b.x - a.x) * frac; + c->y = a.y + (b.y - a.y) * frac; + c->lx = a.lx + (b.lx - a.lx) * frac; + c->ly = a.ly + (b.ly - a.ly) * frac; +} + +void UI_SetupTitleQuad() +{ + TitleLerpQuads[1].x = UI_BANNER_POSX + BANNER_X_FIX; + TitleLerpQuads[1].y = UI_BANNER_POSY + BANNER_Y_FIX; + TitleLerpQuads[1].lx = UI_BANNER_WIDTH - 125; + TitleLerpQuads[1].ly = UI_BANNER_HEIGHT - 40; +} + +void UI_DrawTitleAnim() +{ + UI_SetupTitleQuad(); + + if( !TransPic ) return; + + wrect_t r = { 0, uiStatic.buttons_width, 26, 51 }; + + float frac = UI_GetTitleTransFraction();/*(sin(gpGlobals->time*4)+1)/2*/; + +#ifdef TA_ALT_MODE + if( frac == 1 && transition_state == AS_TO_BUTTON ) + return; +#else + if( frac == 1 ) return; +#endif + + quad_t c; + + int f_idx = (transition_state == AS_TO_TITLE) ? 0 : 1; + int s_idx = (transition_state == AS_TO_TITLE) ? 1 : 0; + + LerpQuad( TitleLerpQuads[f_idx], TitleLerpQuads[s_idx], frac, &c ); + + PIC_Set( TransPic, 255, 255, 255, 255 ); + PIC_DrawAdditive( c.x, c.y, c.lx, c.ly, &r ); +} + +void UI_SetTitleAnim( int anim_state, menuPicButton_s *button ) +{ + // skip buttons which don't call new menu + if( !button || PreClickDepth == uiStatic.menuDepth && anim_state == AS_TO_TITLE ) + return; + + // replace cancel\done button with button which called this menu + if( PreClickDepth > uiStatic.menuDepth && anim_state == AS_TO_TITLE ) + { + anim_state = AS_TO_BUTTON; + + // HACK HACK HACK + if ( ButtonStack[ButtonStackDepth + 1] ) + button = ButtonStack[ButtonStackDepth+1]; + } + + // don't reset anim if dialog buttons pressed + if( button->generic.id == 130 || button->generic.id == 131 ) + return; + + if( anim_state == AS_TO_TITLE ) + UI_PushPButtonStack( button ); + + transition_state = anim_state; + + TitleLerpQuads[0].x = button->generic.x; + TitleLerpQuads[0].y = button->generic.y; + TitleLerpQuads[0].lx = button->generic.width; + TitleLerpQuads[0].ly = button->generic.height; + + transition_initial_time = uiStatic.realTime; + TransPic = button->pic; +} + +void UI_InitTitleAnim() +{ + memset( TitleLerpQuads, 0, sizeof( quad_t ) * 2 ); + + UI_SetupTitleQuad(); + + ButtonStackDepth = 0; + memset( ButtonStack, 0, sizeof( ButtonStack )); +} + +void UI_ClearButtonStack( void ) +{ + ButtonStackDepth = 0; + memset( ButtonStack, 0, sizeof( ButtonStack )); +} \ No newline at end of file diff --git a/mainui/ui_title_anim.h b/mainui/ui_title_anim.h new file mode 100644 index 0000000..bae710c --- /dev/null +++ b/mainui/ui_title_anim.h @@ -0,0 +1,19 @@ +#define AS_TO_TITLE 1 +#define AS_TO_BUTTON 2 + +void UI_SetTitleAnim( int anim_state, menuPicButton_s *picid ); +void UI_DrawTitleAnim( void ); +void UI_InitTitleAnim( void ); +void UI_TACheckMenuDepth( void ); +float UI_GetTitleTransFraction( void ); + +typedef struct +{ + int x, y, lx, ly; +} quad_t; + +void UI_PopPButtonStack( void ); +void UI_ClearButtonStack( void ); + +// èñïîëüçîâàòü êóñîê èç btns_main.bmp çàìåñòî head_%s.bmp +//#define TA_ALT_MODE 1 \ No newline at end of file diff --git a/mainui/utils.cpp b/mainui/utils.cpp new file mode 100644 index 0000000..824bafa --- /dev/null +++ b/mainui/utils.cpp @@ -0,0 +1,2266 @@ +/* +Copyright (C) 1997-2001 Id Software, Inc. + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + + +// ui_qmenu.c -- Quake menu framework + +#include "extdll.h" +#include "basemenu.h" +#include "utils.h" +#include "keydefs.h" +#include "menu_btnsbmp_table.h" +//CR +#include "ui_title_anim.h" + +#ifdef _DEBUG +void DBG_AssertFunction( BOOL fExpr, const char* szExpr, const char* szFile, int szLine, const char* szMessage ) +{ + if( fExpr ) return; + + char szOut[512]; + if( szMessage != NULL ) + sprintf( szOut, "ASSERT FAILED:\n %s \n(%s@%d)\n%s", szExpr, szFile, szLine, szMessage ); + else sprintf( szOut, "ASSERT FAILED:\n %s \n(%s@%d)", szExpr, szFile, szLine ); + HOST_ERROR( szOut ); +} +#endif // DEBUG + +int ColorStrlen( const char *str ) +{ + const char *p; + + if( !str ) + return 0; + + int len = 0; + p = str; + + while( *p ) + { + if( IsColorString( p )) + { + p += 2; + continue; + } + + p++; + len++; + } + + return len; +} + +int ColorPrexfixCount( const char *str ) +{ + const char *p; + + if( !str ) + return 0; + + int len = 0; + p = str; + + while( *p ) + { + if( IsColorString( p )) + { + len += 2; + p += 2; + continue; + } + p++; + } + + return len; +} + +void StringConcat( char *dst, const char *src, size_t size ) +{ + register char *d = dst; + register const char *s = src; + register size_t n = size; + size_t dlen; + + if( !dst || !src || !size ) + return; + + // find the end of dst and adjust bytes left but don't go past end + while(n-- != 0 && *d != '\0') d++; + dlen = d - dst; + n = size - dlen; + + if ( n == 0 ) return; + while ( *s != '\0' ) + { + if ( n != 1 ) + { + *d++ = *s; + n--; + } + s++; + } + + *d = '\0'; + return; +} + +char *StringCopy( const char *input ) +{ + if( !input ) return NULL; + + char *out = (char *)MALLOC( strlen( input ) + 1 ); + strcpy( out, input ); + + return out; +} + +/* +============ +COM_CompareSaves +============ +*/ +int COM_CompareSaves( const void **a, const void **b ) +{ + char *file1, *file2; + + file1 = (char *)*a; + file2 = (char *)*b; + + int bResult; + + COMPARE_FILE_TIME( file2, file1, &bResult ); + + return bResult; +} + +/* +============ +COM_FileBase +============ +*/ +// Extracts the base name of a file (no path, no extension, assumes '/' as path separator) +void COM_FileBase ( const char *in, char *out ) +{ + int len, start, end; + + len = strlen( in ); + + // scan backward for '.' + end = len - 1; + while ( end && in[end] != '.' && in[end] != '/' && in[end] != '\\' ) + end--; + + if ( in[end] != '.' ) // no '.', copy to end + end = len-1; + else + end--; // Found ',', copy to left of '.' + + + // Scan backward for '/' + start = len-1; + while ( start >= 0 && in[start] != '/' && in[start] != '\\' ) + start--; + + if ( in[start] != '/' && in[start] != '\\' ) + start = 0; + else + start++; + + // Length of new sting + len = end - start + 1; + + // Copy partial string + strncpy( out, &in[start], len ); + // Terminate it + out[len] = 0; +} + +/* +=============== +Info_ValueForKey + +Searches the string for the given +key and returns the associated value, or an empty string. +=============== +*/ +char *Info_ValueForKey( const char *s, const char *key ) +{ + static char value[MAX_INFO_STRING]; + char pkey[MAX_INFO_STRING]; + + if ( *s == '\\' ) + s++; + + while ( 1 ) + { + char *o = pkey; + + while ( *s != '\\' && *s != '\n' ) + { + if ( !*s ) + return ""; + *o++ = *s++; + } + + *o = 0; + s++; + + o = value; + + while ( *s != '\\' && *s != '\n' && *s ) + { + if ( !*s ) + return ""; + *o++ = *s++; + } + *o = 0; + + if ( !strcmp( key, pkey )) + return value; + + if ( !*s ) + return ""; + s++; + } +} + +/* +=================== +Key_GetKey +=================== +*/ +int KEY_GetKey( const char *binding ) +{ + const char *b; + + if ( !binding ) + return -1; + + for ( int i = 0; i < 256; i++ ) + { + b = KEY_GetBinding( i ); + if( !b ) continue; + + if( !stricmp( binding, b )) + return i; + } + return -1; +} + +/* +================ +UI_FadeAlpha +================ +*/ +int UI_FadeAlpha( int starttime, int endtime ) +{ + int time, fade_time; + + if( starttime == 0 ) + { + return 0xFFFFFFFF; + } + + time = ( gpGlobals->time * 1000 ) - starttime; + + if( time >= endtime ) + { + return 0x00FFFFFF; + } + + // fade time is 1/4 of endtime + fade_time = endtime / 4; + fade_time = bound( 300, fade_time, 10000 ); + + int alpha; + + // fade out + if(( endtime - time ) < fade_time ) + alpha = bound( 0, (( endtime - time ) * 1.0f / fade_time ) * 255, 255 ); + else alpha = 255; + + return PackRGBA( 255, 255, 255, alpha ); +} + +void UI_UtilSetupPicButton( menuPicButton_s *pic, int ID ) +{ + if( ID < 0 || ID > PC_BUTTONCOUNT ) + return; // bad id + +#if 0 // too different results on various games. disabled + pic->generic.width = PicButtonWidth( ID ) * UI_BUTTON_CHARWIDTH; +#else + pic->generic.width = UI_BUTTONS_WIDTH; +#endif + pic->generic.height = UI_BUTTONS_HEIGHT; + + pic->pic = uiStatic.buttonsPics[ID]; + pic->button_id = ID; + + if( pic->pic ) // text buttons not use it + pic->generic.flags|= QMF_ACT_ONRELEASE; +} + +/* +================= +UI_ScrollList_Init +================= +*/ +void UI_ScrollList_Init( menuScrollList_s *sl ) +{ + if( !sl->generic.name ) sl->generic.name = ""; + + if( sl->generic.flags & QMF_BIGFONT ) + { + sl->generic.charWidth = UI_BIG_CHAR_WIDTH; + sl->generic.charHeight = UI_BIG_CHAR_HEIGHT; + } + else if( sl->generic.flags & QMF_SMALLFONT ) + { + sl->generic.charWidth = UI_SMALL_CHAR_WIDTH; + sl->generic.charHeight = UI_SMALL_CHAR_HEIGHT; + } + else + { + if( sl->generic.charWidth < 1 ) sl->generic.charWidth = UI_MED_CHAR_WIDTH; + if( sl->generic.charHeight < 1 ) sl->generic.charHeight = UI_MED_CHAR_HEIGHT; + } + + UI_ScaleCoords( NULL, NULL, &sl->generic.charWidth, &sl->generic.charHeight ); + + if(!(sl->generic.flags & (QMF_LEFT_JUSTIFY|QMF_CENTER_JUSTIFY|QMF_RIGHT_JUSTIFY))) + sl->generic.flags |= QMF_LEFT_JUSTIFY; + + if( !sl->generic.color ) sl->generic.color = uiPromptTextColor; + if( !sl->generic.focusColor ) sl->generic.focusColor = uiPromptFocusColor; + if( !sl->upArrow ) sl->upArrow = UI_UPARROW; + if( !sl->upArrowFocus ) sl->upArrowFocus = UI_UPARROWFOCUS; + if( !sl->downArrow ) sl->downArrow = UI_DOWNARROW; + if( !sl->downArrowFocus ) sl->downArrowFocus = UI_DOWNARROWFOCUS; + +// sl->curItem = 0; + sl->topItem = 0; + sl->numItems = 0; + + // count number of items + while( sl->itemNames[sl->numItems] ) + sl->numItems++; + + // scale the center box + sl->generic.x2 = sl->generic.x; + sl->generic.y2 = sl->generic.y; + sl->generic.width2 = sl->generic.width; + sl->generic.height2 = sl->generic.height; + UI_ScaleCoords( &sl->generic.x2, &sl->generic.y2, &sl->generic.width2, &sl->generic.height2 ); + + // calculate number of visible rows + sl->numRows = (sl->generic.height2 / sl->generic.charHeight) - 2; + if( sl->numRows > sl->numItems ) sl->numRows = sl->numItems; + + // extend the height so it has room for the arrows + sl->generic.height += (sl->generic.width / 4); + + // calculate new Y for the control + sl->generic.y -= (sl->generic.width / 8); + + UI_ScaleCoords( &sl->generic.x, &sl->generic.y, &sl->generic.width, &sl->generic.height ); +} + +/* +================= +UI_ScrollList_Key +================= +*/ +const char *UI_ScrollList_Key( menuScrollList_s *sl, int key, int down ) +{ + const char *sound = 0; + int arrowWidth, arrowHeight, upX, upY, downX, downY; + int i, y; + + if( !down ) + { + sl->scrollBarSliding = false; + return uiSoundNull; + } + + switch( key ) + { + case K_MOUSE1: + if(!( sl->generic.flags & QMF_HASMOUSEFOCUS )) + break; + + // use fixed size for arrows + arrowWidth = 24; + arrowHeight = 24; + + UI_ScaleCoords( NULL, NULL, &arrowWidth, &arrowHeight ); + + // glue with right top and right bottom corners + upX = sl->generic.x2 + sl->generic.width2 - arrowWidth; + upY = sl->generic.y2 + UI_OUTLINE_WIDTH; + downX = sl->generic.x2 + sl->generic.width2 - arrowWidth; + downY = sl->generic.y2 + (sl->generic.height2 - arrowHeight) - UI_OUTLINE_WIDTH; + + // ADAMIX + if( UI_CursorInRect( sl->scrollBarX, sl->scrollBarY, sl->scrollBarWidth, sl->scrollBarHeight )) + { + sl->scrollBarSliding = true; + break; + } + // ADAMIX END + + // Now see if either up or down has focus + if( UI_CursorInRect( upX, upY, arrowWidth, arrowHeight )) + { + if( sl->curItem != 0 ) + { + sl->curItem--; + sound = uiSoundMove; + } + else sound = uiSoundBuzz; + break; + } + else if( UI_CursorInRect( downX, downY, arrowWidth, arrowHeight )) + { + if( sl->curItem != sl->numItems - 1 ) + { + sl->curItem++; + sound = uiSoundMove; + } + else sound = uiSoundBuzz; + break; + } + + // see if an item has been selected + y = sl->generic.y2 + sl->generic.charHeight; + for( i = sl->topItem; i < sl->topItem + sl->numRows; i++, y += sl->generic.charHeight ) + { + if( !sl->itemNames[i] ) + break; // done + + if( UI_CursorInRect( sl->generic.x, y, sl->generic.width, sl->generic.charHeight )) + { + sl->curItem = i; + sound = uiSoundNull; + break; + } + } + break; + case K_HOME: + case K_KP_HOME: + if( sl->curItem != 0 ) + { + sl->curItem = 0; + sound = uiSoundMove; + } + else sound = uiSoundBuzz; + break; + case K_END: + case K_KP_END: + if( sl->curItem != sl->numItems - 1 ) + { + sl->curItem = sl->numItems - 1; + sound = uiSoundMove; + } + else sound = uiSoundBuzz; + break; + case K_PGUP: + case K_KP_PGUP: + if( sl->curItem != 0 ) + { + sl->curItem -= 2; + if( sl->curItem < 0 ) + sl->curItem = 0; + sound = uiSoundMove; + } + else sound = uiSoundBuzz; + break; + case K_PGDN: + case K_KP_PGDN: + if( sl->curItem != sl->numItems - 1 ) + { + sl->curItem += 2; + if( sl->curItem > sl->numItems - 1 ) + sl->curItem = sl->numItems - 1; + sound = uiSoundMove; + } + else sound = uiSoundBuzz; + break; + case K_UPARROW: + case K_KP_UPARROW: + case K_MWHEELUP: + if( sl->curItem != 0 ) + { + sl->curItem--; + sound = uiSoundMove; + } + else sound = uiSoundBuzz; + break; + case K_DOWNARROW: + case K_MWHEELDOWN: + if( sl->numItems > 0 && sl->curItem != sl->numItems - 1 ) + { + sl->curItem++; + sound = uiSoundMove; + } + else sound = uiSoundBuzz; + break; + } + + sl->topItem = sl->curItem - sl->numRows + 1; + if( sl->topItem < 0 ) sl->topItem = 0; + if( sl->topItem > sl->numItems - sl->numRows ) + sl->topItem = sl->numItems - sl->numRows; + + if( sound && ( sl->generic.flags & QMF_SILENT )) + sound = uiSoundNull; + + if( sound && sl->generic.callback ) + { + if( sound != uiSoundBuzz ) + sl->generic.callback( sl, QM_CHANGED ); + } + return sound; +} + +/* +================= +UI_ScrollList_Draw +================= +*/ +void UI_ScrollList_Draw( menuScrollList_s *sl ) +{ + int justify; + int shadow; + int i, x, y, w, h; + int selColor = 0xFF503818; // Red 80, Green 56, Blue 24, Alpha 255 + int arrowWidth, arrowHeight, upX, upY, downX, downY; + int upFocus, downFocus, scrollbarFocus; + + if( sl->generic.flags & QMF_LEFT_JUSTIFY ) + justify = 0; + else if( sl->generic.flags & QMF_CENTER_JUSTIFY ) + justify = 1; + else if( sl->generic.flags & QMF_RIGHT_JUSTIFY ) + justify = 2; + + shadow = (sl->generic.flags & QMF_DROPSHADOW); + + // use fixed size for arrows + arrowWidth = 24; + arrowHeight = 24; + + UI_ScaleCoords( NULL, NULL, &arrowWidth, &arrowHeight ); + + x = sl->generic.x2; + y = sl->generic.y2; + w = sl->generic.width2; + h = sl->generic.height2; + + if( !sl->background ) + { + // draw the opaque outlinebox first + UI_FillRect( x, y, w, h, uiColorBlack ); + } + + // hightlight the selected item + if( !( sl->generic.flags & QMF_GRAYED )) + { + y = sl->generic.y2 + sl->generic.charHeight; + for( i = sl->topItem; i < sl->topItem + sl->numRows; i++, y += sl->generic.charHeight ) + { + if( !sl->itemNames[i] ) + break; // Done + + if( i == sl->curItem ) + { + UI_FillRect( sl->generic.x, y, sl->generic.width - arrowWidth, sl->generic.charHeight, selColor ); + break; + } + } + } + + if( sl->background ) + { + // get size and position for the center box + UI_DrawPic( x, y, w, h, uiColorWhite, sl->background ); + } + else + { + x = sl->generic.x2 - UI_OUTLINE_WIDTH; + y = sl->generic.y2; + w = UI_OUTLINE_WIDTH; + h = sl->generic.height2; + + // draw left + UI_FillRect( x, y, w, h, uiInputFgColor ); + + x = sl->generic.x2 + sl->generic.width2; + y = sl->generic.y2; + w = UI_OUTLINE_WIDTH; + h = sl->generic.height2; + + // draw right + UI_FillRect( x, y, w, h, uiInputFgColor ); + + x = sl->generic.x2; + y = sl->generic.y2; + w = sl->generic.width2 + UI_OUTLINE_WIDTH; + h = UI_OUTLINE_WIDTH; + + // draw top + UI_FillRect( x, y, w, h, uiInputFgColor ); + + // draw bottom + x = sl->generic.x2; + y = sl->generic.y2 + sl->generic.height2 - UI_OUTLINE_WIDTH; + w = sl->generic.width2 + UI_OUTLINE_WIDTH; + h = UI_OUTLINE_WIDTH; + + UI_FillRect( x, y, w, h, uiInputFgColor ); + } + + // glue with right top and right bottom corners + upX = sl->generic.x2 + sl->generic.width2 - arrowWidth; + upY = sl->generic.y2 + UI_OUTLINE_WIDTH; + downX = sl->generic.x2 + sl->generic.width2 - arrowWidth; + downY = sl->generic.y2 + (sl->generic.height2 - arrowHeight) - UI_OUTLINE_WIDTH; + + // draw the arrows base + UI_FillRect( upX, upY + arrowHeight, arrowWidth, downY - upY - arrowHeight, uiInputFgColor ); + + // ADAMIX + sl->scrollBarX = upX + sl->generic.charHeight/4; + sl->scrollBarWidth = arrowWidth - sl->generic.charHeight/4; + + int step = (sl->numItems <= 1 ) ? 1 : (downY - upY - arrowHeight) / (sl->numItems - 1); + + if(((downY - upY - arrowHeight) - (((sl->numItems-1)*sl->generic.charHeight)/2)) < 2) + { + sl->scrollBarHeight = (downY - upY - arrowHeight) - (step * (sl->numItems-1)); + sl->scrollBarY = upY + arrowHeight + (step*sl->curItem); + } + else + { + sl->scrollBarHeight = downY - upY - arrowHeight - (((sl->numItems-1) * sl->generic.charHeight) / 2); + sl->scrollBarY = upY + arrowHeight + (((sl->curItem) * sl->generic.charHeight)/2); + } + + if( sl->scrollBarSliding ) + { + int dist = uiStatic.cursorY - sl->scrollBarY - (sl->scrollBarHeight>>2); + + if((((dist / 2) > (sl->generic.charHeight / 2)) || ((dist / 2) < (sl->generic.charHeight / 2))) && sl->curItem <= (sl->numItems - 1) && sl->curItem >= 0) + { + if(sl->generic.callback) + sl->generic.callback( sl, QM_CHANGED ); + + if((dist / 2) > ( sl->generic.charHeight / 2 ) && sl->curItem < ( sl->numItems - 1 )) + { + sl->curItem++; + } + + if((dist / 2) < -(sl->generic.charHeight / 2) && sl->curItem > 0 ) + { + sl->curItem--; + } + } + + sl->topItem = sl->curItem - sl->numRows + 1; + if( sl->topItem < 0 ) sl->topItem = 0; + if( sl->topItem > ( sl->numItems - sl->numRows )) + sl->topItem = sl->numItems - sl->numRows; + } + + if( sl->scrollBarSliding ) + { + // Draw scrollbar background + UI_FillRect ( sl->scrollBarX, upY + arrowHeight, sl->scrollBarWidth, downY - upY - arrowHeight, uiColorBlack); + } + + // ADAMIX END + // draw the arrows + if( sl->generic.flags & QMF_GRAYED ) + { + UI_DrawPic( upX, upY, arrowWidth, arrowHeight, uiColorDkGrey, sl->upArrow ); + UI_DrawPic( downX, downY, arrowWidth, arrowHeight, uiColorDkGrey, sl->downArrow ); + } + else + { + scrollbarFocus = UI_CursorInRect( sl->scrollBarX, sl->scrollBarY, sl->scrollBarWidth, sl->scrollBarHeight ); + + // special case if we sliding but lost focus + if( sl->scrollBarSliding ) scrollbarFocus = true; + + // Draw scrollbar itself + UI_FillRect( sl->scrollBarX, sl->scrollBarY, sl->scrollBarWidth, sl->scrollBarHeight, scrollbarFocus ? uiInputTextColor : uiColorBlack ); + + if((menuCommon_s *)sl != (menuCommon_s *)UI_ItemAtCursor(sl->generic.parent)) + { + UI_DrawPic( upX, upY, arrowWidth, arrowHeight, uiColorWhite, sl->upArrow ); + UI_DrawPic( downX, downY, arrowWidth, arrowHeight, uiColorWhite, sl->downArrow ); + } + else + { + // see which arrow has the mouse focus + upFocus = UI_CursorInRect( upX, upY, arrowWidth, arrowHeight ); + downFocus = UI_CursorInRect( downX, downY, arrowWidth, arrowHeight ); + + if(!( sl->generic.flags & QMF_FOCUSBEHIND )) + { + UI_DrawPic( upX, upY, arrowWidth, arrowHeight, uiColorWhite, sl->upArrow ); + UI_DrawPic( downX, downY, arrowWidth, arrowHeight, uiColorWhite, sl->downArrow ); + } + + if( sl->generic.flags & QMF_HIGHLIGHTIFFOCUS ) + { + UI_DrawPic( upX, upY, arrowWidth, arrowHeight, uiColorWhite, (upFocus) ? sl->upArrowFocus : sl->upArrow ); + UI_DrawPic( downX, downY, arrowWidth, arrowHeight, uiColorWhite, (downFocus) ? sl->downArrowFocus : sl->downArrow ); + } + else if( sl->generic.flags & QMF_PULSEIFFOCUS ) + { + int color; + + color = PackAlpha( sl->generic.color, 255 * (0.5 + 0.5 * sin( uiStatic.realTime / UI_PULSE_DIVISOR ))); + + UI_DrawPic( upX, upY, arrowWidth, arrowHeight, (upFocus) ? color : sl->generic.color, (upFocus) ? sl->upArrowFocus : sl->upArrow ); + UI_DrawPic( downX, downY, arrowWidth, arrowHeight, (downFocus) ? color : sl->generic.color, (downFocus) ? sl->downArrowFocus : sl->downArrow ); + } + + if( sl->generic.flags & QMF_FOCUSBEHIND ) + { + UI_DrawPic( upX, upY, arrowWidth, arrowHeight, sl->generic.color, sl->upArrow ); + UI_DrawPic( downX, downY, arrowWidth, arrowHeight, sl->generic.color, sl->downArrow ); + } + } + } + + // Draw the list + x = sl->generic.x2; + w = sl->generic.width2; + h = sl->generic.charHeight; + y = sl->generic.y2 + sl->generic.charHeight; + + // prevent the columns out of rectangle bounds + PIC_EnableScissor( x, y, sl->generic.width - arrowWidth - uiStatic.outlineWidth, sl->generic.height ); + + for( i = sl->topItem; i < sl->topItem + sl->numRows; i++, y += sl->generic.charHeight ) + { + if( !sl->itemNames[i] ) + break; // done + + if( sl->generic.flags & QMF_GRAYED ) + { + UI_DrawString( x, y, w, h, sl->itemNames[i], uiColorDkGrey, true, sl->generic.charWidth, sl->generic.charHeight, justify, shadow ); + continue; // grayed + } + + if( i != sl->curItem ) + { + UI_DrawString( x, y, w, h, sl->itemNames[i], sl->generic.color, false, sl->generic.charWidth, sl->generic.charHeight, justify, shadow ); + continue; // no focus + } + + if(!( sl->generic.flags & QMF_FOCUSBEHIND )) + UI_DrawString( x, y, w, h, sl->itemNames[i], sl->generic.color, false, sl->generic.charWidth, sl->generic.charHeight, justify, shadow ); + + if( sl->generic.flags & QMF_HIGHLIGHTIFFOCUS ) + UI_DrawString( x, y, w, h, sl->itemNames[i], sl->generic.focusColor, false, sl->generic.charWidth, sl->generic.charHeight, justify, shadow ); + else if( sl->generic.flags & QMF_PULSEIFFOCUS ) + { + int color; + + color = PackAlpha( sl->generic.color, 255 * (0.5 + 0.5 * sin( uiStatic.realTime / UI_PULSE_DIVISOR ))); + + UI_DrawString( x, y, w, h, sl->itemNames[i], color, false, sl->generic.charWidth, sl->generic.charHeight, justify, shadow ); + } + + if( sl->generic.flags & QMF_FOCUSBEHIND ) + UI_DrawString( x, y, w, h, sl->itemNames[i], sl->generic.color, false, sl->generic.charWidth, sl->generic.charHeight, justify, shadow ); + } + + PIC_DisableScissor(); +} + +/* +================= +UI_SpinControl_Init +================= +*/ +void UI_SpinControl_Init( menuSpinControl_s *sc ) +{ + if( !sc->generic.name ) sc->generic.name = ""; // this is also the text displayed + + if( sc->generic.flags & QMF_BIGFONT ) + { + sc->generic.charWidth = UI_BIG_CHAR_WIDTH; + sc->generic.charHeight = UI_BIG_CHAR_HEIGHT; + } + else if( sc->generic.flags & QMF_SMALLFONT ) + { + sc->generic.charWidth = UI_SMALL_CHAR_WIDTH; + sc->generic.charHeight = UI_SMALL_CHAR_HEIGHT; + } + else + { + if( sc->generic.charWidth < 1 ) sc->generic.charWidth = UI_MED_CHAR_WIDTH; + if( sc->generic.charHeight < 1 ) sc->generic.charHeight = UI_MED_CHAR_HEIGHT; + } + + UI_ScaleCoords( NULL, NULL, &sc->generic.charWidth, &sc->generic.charHeight ); + + if(!( sc->generic.flags & (QMF_LEFT_JUSTIFY|QMF_CENTER_JUSTIFY|QMF_RIGHT_JUSTIFY))) + sc->generic.flags |= QMF_LEFT_JUSTIFY; + + if( !sc->generic.color ) sc->generic.color = uiColorHelp; + if( !sc->generic.focusColor ) sc->generic.focusColor = uiPromptTextColor; + if( !sc->leftArrow ) sc->leftArrow = UI_LEFTARROW; + if( !sc->leftArrowFocus ) sc->leftArrowFocus = UI_LEFTARROWFOCUS; + if( !sc->rightArrow ) sc->rightArrow = UI_RIGHTARROW; + if( !sc->rightArrowFocus ) sc->rightArrowFocus = UI_RIGHTARROWFOCUS; + + // scale the center box + sc->generic.x2 = sc->generic.x; + sc->generic.y2 = sc->generic.y; + sc->generic.width2 = sc->generic.width; + sc->generic.height2 = sc->generic.height; + UI_ScaleCoords( &sc->generic.x2, &sc->generic.y2, &sc->generic.width2, &sc->generic.height2 ); + + // extend the width so it has room for the arrows + sc->generic.width += (sc->generic.height * 3); + + // calculate new X for the control + sc->generic.x -= (sc->generic.height + (sc->generic.height/2)); + + UI_ScaleCoords( &sc->generic.x, &sc->generic.y, &sc->generic.width, &sc->generic.height ); +} + +/* +================= +UI_SpinControl_Key +================= +*/ +const char *UI_SpinControl_Key( menuSpinControl_s *sc, int key, int down ) +{ + const char *sound = 0; + int arrowWidth, arrowHeight, leftX, leftY, rightX, rightY; + + if( !down ) return uiSoundNull; + + switch( key ) + { + case K_MOUSE1: + case K_MOUSE2: + if( !( sc->generic.flags & QMF_HASMOUSEFOCUS )) + break; + + // calculate size and position for the arrows + arrowWidth = sc->generic.height + (UI_OUTLINE_WIDTH * 2); + arrowHeight = sc->generic.height + (UI_OUTLINE_WIDTH * 2); + + leftX = sc->generic.x + UI_OUTLINE_WIDTH; + leftY = sc->generic.y - UI_OUTLINE_WIDTH; + rightX = sc->generic.x + (sc->generic.width - arrowWidth) - UI_OUTLINE_WIDTH; + rightY = sc->generic.y - UI_OUTLINE_WIDTH; + + // now see if either left or right arrow has focus + if( UI_CursorInRect( leftX, leftY, arrowWidth, arrowHeight )) + { + if( sc->curValue > sc->minValue ) + { + sc->curValue -= sc->range; + if( sc->curValue < sc->minValue ) + sc->curValue = sc->minValue; + sound = uiSoundMove; + } + else sound = uiSoundBuzz; + } + else if( UI_CursorInRect( rightX, rightY, arrowWidth, arrowHeight )) + { + if( sc->curValue < sc->maxValue ) + { + sc->curValue += sc->range; + if( sc->curValue > sc->maxValue ) + sc->curValue = sc->maxValue; + sound = uiSoundMove; + } + else sound = uiSoundBuzz; + } + break; + case K_LEFTARROW: + case K_KP_LEFTARROW: + if( sc->generic.flags & QMF_MOUSEONLY ) + break; + if( sc->curValue > sc->minValue ) + { + sc->curValue -= sc->range; + if( sc->curValue < sc->minValue ) + sc->curValue = sc->minValue; + sound = uiSoundMove; + } + else sound = uiSoundBuzz; + break; + case K_RIGHTARROW: + case K_KP_RIGHTARROW: + if( sc->generic.flags & QMF_MOUSEONLY ) + break; + + if( sc->curValue < sc->maxValue ) + { + sc->curValue += sc->range; + if( sc->curValue > sc->maxValue ) + sc->curValue = sc->maxValue; + sound = uiSoundMove; + } + else sound = uiSoundBuzz; + break; + } + + if( sound && ( sc->generic.flags & QMF_SILENT )) + sound = uiSoundNull; + + if( sound && sc->generic.callback ) + { + if( sound != uiSoundBuzz ) + sc->generic.callback( sc, QM_CHANGED ); + } + return sound; +} + +/* +================= +UI_SpinControl_Draw +================= +*/ +void UI_SpinControl_Draw( menuSpinControl_s *sc ) +{ + int justify; + int shadow; + int x, y, w, h; + int arrowWidth, arrowHeight, leftX, leftY, rightX, rightY; + int leftFocus, rightFocus; + + if( sc->generic.flags & QMF_LEFT_JUSTIFY ) + justify = 0; + else if( sc->generic.flags & QMF_CENTER_JUSTIFY ) + justify = 1; + else if( sc->generic.flags & QMF_RIGHT_JUSTIFY ) + justify = 2; + + shadow = (sc->generic.flags & QMF_DROPSHADOW); + + // calculate size and position for the arrows + arrowWidth = sc->generic.height + (UI_OUTLINE_WIDTH * 2); + arrowHeight = sc->generic.height + (UI_OUTLINE_WIDTH * 2); + + leftX = sc->generic.x + UI_OUTLINE_WIDTH; + leftY = sc->generic.y - UI_OUTLINE_WIDTH; + rightX = sc->generic.x + (sc->generic.width - arrowWidth) - UI_OUTLINE_WIDTH; + rightY = sc->generic.y - UI_OUTLINE_WIDTH; + + // get size and position for the center box + w = sc->generic.width2; + h = sc->generic.height2; + x = sc->generic.x2; + y = sc->generic.y2; + + if( sc->background ) + { + UI_DrawPic( x, y, w, h, uiColorWhite, sc->background ); + } + else + { + // draw the background + UI_FillRect( x, y, w, h, uiColorBlack ); + + // draw the rectangle + UI_DrawRectangle( x, y, w, h, uiInputFgColor ); + } + + if( sc->generic.flags & QMF_GRAYED ) + { + UI_DrawString( x, y, w, h, sc->generic.name, uiColorDkGrey, true, sc->generic.charWidth, sc->generic.charHeight, justify, shadow ); + UI_DrawPic( leftX, leftY, arrowWidth, arrowHeight, uiColorDkGrey, sc->leftArrow ); + UI_DrawPic( rightX, rightY, arrowWidth, arrowHeight, uiColorDkGrey, sc->rightArrow ); + return; // grayed + } + + if((menuCommon_s *)sc != (menuCommon_s *)UI_ItemAtCursor(sc->generic.parent )) + { + UI_DrawString(x, y, w, h, sc->generic.name, sc->generic.color, false, sc->generic.charWidth, sc->generic.charHeight, justify, shadow ); + UI_DrawPic(leftX, leftY, arrowWidth, arrowHeight, sc->generic.color, sc->leftArrow); + UI_DrawPic(rightX, rightY, arrowWidth, arrowHeight, sc->generic.color, sc->rightArrow); + return; // No focus + } + + // see which arrow has the mouse focus + leftFocus = UI_CursorInRect( leftX, leftY, arrowWidth, arrowHeight ); + rightFocus = UI_CursorInRect( rightX, rightY, arrowWidth, arrowHeight ); + + if( !( sc->generic.flags & QMF_FOCUSBEHIND )) + { + UI_DrawString( x, y, w, h, sc->generic.name, sc->generic.color, false, sc->generic.charWidth, sc->generic.charHeight, justify, shadow ); + UI_DrawPic( leftX, leftY, arrowWidth, arrowHeight, sc->generic.color, sc->leftArrow ); + UI_DrawPic( rightX, rightY, arrowWidth, arrowHeight, sc->generic.color, sc->rightArrow ); + } + + if( sc->generic.flags & QMF_HIGHLIGHTIFFOCUS ) + { + UI_DrawString( x, y, w, h, sc->generic.name, sc->generic.focusColor, false, sc->generic.charWidth, sc->generic.charHeight, justify, shadow ); + UI_DrawPic( leftX, leftY, arrowWidth, arrowHeight, (leftFocus) ? sc->generic.color : sc->generic.color, (leftFocus) ? sc->leftArrowFocus : sc->leftArrow ); + UI_DrawPic( rightX, rightY, arrowWidth, arrowHeight, (rightFocus) ? sc->generic.color : sc->generic.color, (rightFocus) ? sc->rightArrowFocus : sc->rightArrow ); + } + else if( sc->generic.flags & QMF_PULSEIFFOCUS ) + { + int color; + + color = PackAlpha( sc->generic.color, 255 * (0.5 + 0.5 * sin( uiStatic.realTime / UI_PULSE_DIVISOR ))); + + UI_DrawString( x, y, w, h, sc->generic.name, color, false, sc->generic.charWidth, sc->generic.charHeight, justify, shadow ); + UI_DrawPic( leftX, leftY, arrowWidth, arrowHeight, (leftFocus) ? color : sc->generic.color, (leftFocus) ? sc->leftArrowFocus : sc->leftArrow ); + UI_DrawPic( rightX, rightY, arrowWidth, arrowHeight, (rightFocus) ? color : sc->generic.color, (rightFocus) ? sc->rightArrowFocus : sc->rightArrow ); + } + + if( sc->generic.flags & QMF_FOCUSBEHIND ) + { + UI_DrawString( x, y, w, h, sc->generic.name, sc->generic.color, false, sc->generic.charWidth, sc->generic.charHeight, justify, shadow ); + UI_DrawPic( leftX, leftY, arrowWidth, arrowHeight, sc->generic.color, sc->leftArrow ); + UI_DrawPic( rightX, rightY, arrowWidth, arrowHeight, sc->generic.color, sc->rightArrow ); + } +} + +/* +================= +UI_Slider_Init +================= +*/ +void UI_Slider_Init( menuSlider_s *sl ) +{ + if( !sl->generic.name ) sl->generic.name = ""; // this is also the text displayed + + if( !sl->generic.width ) sl->generic.width = 200; + if( !sl->generic.height) sl->generic.height = 4; + if( !sl->generic.color ) sl->generic.color = uiColorWhite; + if( !sl->generic.focusColor ) sl->generic.focusColor = uiColorWhite; + if( !sl->range ) sl->range = 1.0f; + if( sl->range < 0.05f ) sl->range = 0.05f; + + if( sl->generic.flags & QMF_BIGFONT ) + { + sl->generic.charWidth = UI_BIG_CHAR_WIDTH; + sl->generic.charHeight = UI_BIG_CHAR_HEIGHT; + } + else if( sl->generic.flags & QMF_SMALLFONT ) + { + sl->generic.charWidth = UI_SMALL_CHAR_WIDTH; + sl->generic.charHeight = UI_SMALL_CHAR_HEIGHT; + } + else + { + if( sl->generic.charWidth < 1 ) sl->generic.charWidth = 12; + if( sl->generic.charHeight < 1 ) sl->generic.charHeight = 24; + } + + UI_ScaleCoords( NULL, NULL, &sl->generic.charWidth, &sl->generic.charHeight ); + + if(!(sl->generic.flags & (QMF_LEFT_JUSTIFY|QMF_CENTER_JUSTIFY|QMF_RIGHT_JUSTIFY))) + sl->generic.flags |= QMF_LEFT_JUSTIFY; + + // scale the center box + sl->generic.x2 = sl->generic.x; + sl->generic.y2 = sl->generic.y; + sl->generic.width2 = sl->generic.width / 5; + sl->generic.height2 = 4; + + UI_ScaleCoords( &sl->generic.x2, &sl->generic.y2, &sl->generic.width2, &sl->generic.height2 ); + UI_ScaleCoords( &sl->generic.x, &sl->generic.y, &sl->generic.width, &sl->generic.height ); + + sl->generic.y -= uiStatic.sliderWidth; + sl->generic.height += uiStatic.sliderWidth * 2; + sl->generic.y2 -= uiStatic.sliderWidth; + + sl->drawStep = (sl->generic.width - sl->generic.width2) / ((sl->maxValue - sl->minValue) / sl->range); + sl->numSteps = ((sl->maxValue - sl->minValue) / sl->range) + 1; +} + +/* +================= +UI_Slider_Key +================= +*/ +const char *UI_Slider_Key( menuSlider_s *sl, int key, int down ) +{ + int sliderX; + + if( !down ) + { + if( sl->keepSlider ) + { + // tell menu about changes + if( sl->generic.callback ) + sl->generic.callback( sl, QM_CHANGED ); + sl->keepSlider = false; // button released + } + return uiSoundNull; + } + + switch( key ) + { + case K_MOUSE1: + if(!( sl->generic.flags & QMF_HASMOUSEFOCUS )) + return uiSoundNull; + + // find the current slider position + sliderX = sl->generic.x2 + (sl->drawStep * (sl->curValue * sl->numSteps)); + if( UI_CursorInRect( sliderX, sl->generic.y2, sl->generic.width2, sl->generic.height )) + { + sl->keepSlider = true; + } + else + { + int dist, numSteps; + + // immediately move slider into specified place + dist = uiStatic.cursorX - sl->generic.x2 - (sl->generic.width2>>2); + numSteps = dist / (int)sl->drawStep; + sl->curValue = bound( sl->minValue, numSteps * sl->range, sl->maxValue ); + + // tell menu about changes + if( sl->generic.callback ) + sl->generic.callback( sl, QM_CHANGED ); + } + break; + } + return uiSoundNull; +} + +/* +================= +UI_Slider_Draw +================= +*/ +void UI_Slider_Draw( menuSlider_s *sl ) +{ + int justify; + int shadow; + int textHeight, sliderX; + + if( sl->generic.flags & QMF_LEFT_JUSTIFY ) + justify = 0; + else if( sl->generic.flags & QMF_CENTER_JUSTIFY ) + justify = 1; + else if( sl->generic.flags & QMF_RIGHT_JUSTIFY ) + justify = 2; + + shadow = (sl->generic.flags & QMF_DROPSHADOW); + + if( sl->keepSlider ) + { + int dist, numSteps; + + // move slider follow the holded mouse button + dist = uiStatic.cursorX - sl->generic.x2 - (sl->generic.width2>>2); + numSteps = dist / (int)sl->drawStep; + sl->curValue = bound( sl->minValue, numSteps * sl->range, sl->maxValue ); + + // tell menu about changes + if( sl->generic.callback ) sl->generic.callback( sl, QM_CHANGED ); + } + + // keep value in range + sl->curValue = bound( sl->minValue, sl->curValue, sl->maxValue ); + + // calc slider position + sliderX = sl->generic.x2 + (sl->drawStep * (sl->curValue * sl->numSteps)); + + UI_DrawRectangleExt( sl->generic.x, sl->generic.y + uiStatic.sliderWidth, sl->generic.width, sl->generic.height2, uiInputBgColor, uiStatic.sliderWidth ); + UI_DrawPic( sliderX, sl->generic.y2, sl->generic.width2, sl->generic.height, uiColorWhite, UI_SLIDER_MAIN ); + + textHeight = sl->generic.y - (sl->generic.charHeight * 1.5f); + UI_DrawString( sl->generic.x, textHeight, sl->generic.width, sl->generic.charHeight, sl->generic.name, uiColorHelp, true, sl->generic.charWidth, sl->generic.charHeight, justify, shadow ); +} + +/* +================= +UI_CheckBox_Init +================= +*/ +void UI_CheckBox_Init( menuCheckBox_s *cb ) +{ + if( !cb->generic.name ) cb->generic.name = ""; + + if( cb->generic.flags & QMF_BIGFONT ) + { + cb->generic.charWidth = UI_BIG_CHAR_WIDTH; + cb->generic.charHeight = UI_BIG_CHAR_HEIGHT; + } + else if( cb->generic.flags & QMF_SMALLFONT ) + { + cb->generic.charWidth = UI_SMALL_CHAR_WIDTH; + cb->generic.charHeight = UI_SMALL_CHAR_HEIGHT; + } + else + { + if( cb->generic.charWidth < 1 ) cb->generic.charWidth = 12; + if( cb->generic.charHeight < 1 ) cb->generic.charHeight = 24; + } + + UI_ScaleCoords( NULL, NULL, &cb->generic.charWidth, &cb->generic.charHeight ); + + if(!(cb->generic.flags & (QMF_LEFT_JUSTIFY|QMF_CENTER_JUSTIFY|QMF_RIGHT_JUSTIFY))) + cb->generic.flags |= QMF_LEFT_JUSTIFY; + + if( !cb->emptyPic ) cb->emptyPic = UI_CHECKBOX_EMPTY; + if( !cb->focusPic ) cb->focusPic = UI_CHECKBOX_FOCUS; + if( !cb->checkPic ) cb->checkPic = UI_CHECKBOX_ENABLED; + if( !cb->grayedPic ) cb->grayedPic = UI_CHECKBOX_GRAYED; + if( !cb->generic.color ) cb->generic.color = uiColorWhite; + if( !cb->generic.focusColor ) cb->generic.focusColor = uiColorWhite; + + if( !cb->generic.width ) cb->generic.width = 32; + if( !cb->generic.height ) cb->generic.height = 32; + + UI_ScaleCoords( &cb->generic.x, &cb->generic.y, &cb->generic.width, &cb->generic.height ); +} + +/* +================= +UI_CheckBox_Key +================= +*/ +const char *UI_CheckBox_Key( menuCheckBox_s *cb, int key, int down ) +{ + const char *sound = 0; + + switch( key ) + { + case K_MOUSE1: + if(!( cb->generic.flags & QMF_HASMOUSEFOCUS )) + break; + sound = uiSoundGlow; + break; + case K_ENTER: + case K_KP_ENTER: + if( !down ) return sound; + if( cb->generic.flags & QMF_MOUSEONLY ) + break; + sound = uiSoundGlow; + break; + } + if( sound && ( cb->generic.flags & QMF_SILENT )) + sound = uiSoundNull; + + if( cb->generic.flags & QMF_ACT_ONRELEASE ) + { + if( sound && cb->generic.callback ) + { + int event; + + if( down ) + { + event = QM_PRESSED; + cb->generic.bPressed = true; + } + else event = QM_CHANGED; + if( !down ) cb->enabled = !cb->enabled; // apply on release + cb->generic.callback( cb, event ); + } + } + else if( down ) + { + if( sound && cb->generic.callback ) + { + cb->enabled = !cb->enabled; + cb->generic.callback( cb, QM_CHANGED ); + } + } + return sound; +} + +/* +================= +UI_CheckBox_Draw +================= +*/ +void UI_CheckBox_Draw( menuCheckBox_s *cb ) +{ + int justify; + int shadow; + int textOffset, y; + + if( cb->generic.flags & QMF_LEFT_JUSTIFY ) + justify = 0; + else if( cb->generic.flags & QMF_CENTER_JUSTIFY ) + justify = 1; + else if( cb->generic.flags & QMF_RIGHT_JUSTIFY ) + justify = 2; + + shadow = (cb->generic.flags & QMF_DROPSHADOW); + + y = cb->generic.y + (cb->generic.height>>2); + textOffset = cb->generic.x + (cb->generic.width * 1.7f); + UI_DrawString( textOffset, y, strlen( cb->generic.name ) * cb->generic.charWidth, cb->generic.charHeight, cb->generic.name, uiColorHelp, true, cb->generic.charWidth, cb->generic.charHeight, justify, shadow ); + + if( cb->generic.statusText && cb->generic.flags & QMF_NOTIFY ) + { + int charW, charH; + int x, w; + + charW = UI_SMALL_CHAR_WIDTH; + charH = UI_SMALL_CHAR_HEIGHT; + + UI_ScaleCoords( NULL, NULL, &charW, &charH ); + + x = 250; + w = UI_SMALL_CHAR_WIDTH * strlen( cb->generic.statusText ); + UI_ScaleCoords( &x, NULL, &w, NULL ); + x += cb->generic.x; + + int r, g, b; + + UnpackRGB( r, g, b, uiColorHelp ); + TextMessageSetColor( r, g, b ); + DrawConsoleString( x, cb->generic.y, cb->generic.statusText ); + } + + if( cb->generic.flags & QMF_GRAYED ) + { + UI_DrawPic( cb->generic.x, cb->generic.y, cb->generic.width, cb->generic.height, uiColorWhite, cb->grayedPic ); + return; // grayed + } + + if(( cb->generic.flags & QMF_MOUSEONLY ) && !( cb->generic.flags & QMF_HASMOUSEFOCUS )) + { + if( !cb->enabled ) + UI_DrawPic( cb->generic.x, cb->generic.y, cb->generic.width, cb->generic.height, cb->generic.color, cb->emptyPic ); + else UI_DrawPic( cb->generic.x, cb->generic.y, cb->generic.width, cb->generic.height, cb->generic.color, cb->checkPic ); + return; // no focus + } + + if((menuCommon_s *)cb != (menuCommon_s *)UI_ItemAtCursor( cb->generic.parent )) + { + if( !cb->enabled ) + UI_DrawPic( cb->generic.x, cb->generic.y, cb->generic.width, cb->generic.height, cb->generic.color, cb->emptyPic ); + else UI_DrawPic( cb->generic.x, cb->generic.y, cb->generic.width, cb->generic.height, cb->generic.color, cb->checkPic ); + return; // no focus + } + + if( cb->generic.flags & QMF_HIGHLIGHTIFFOCUS && !cb->enabled ) + { + UI_DrawPic( cb->generic.x, cb->generic.y, cb->generic.width, cb->generic.height, cb->generic.focusColor, cb->focusPic ); + } + else + { + if( !cb->enabled ) + UI_DrawPic( cb->generic.x, cb->generic.y, cb->generic.width, cb->generic.height, cb->generic.color, cb->emptyPic ); + else UI_DrawPic( cb->generic.x, cb->generic.y, cb->generic.width, cb->generic.height, cb->generic.color, cb->checkPic ); + } +} + +/* +================= +UI_Field_Init +================= +*/ +void UI_Field_Init( menuField_s *f ) +{ + if( !f->generic.name ) f->generic.name = ""; + + if( f->generic.flags & QMF_BIGFONT ) + { + f->generic.charWidth = UI_BIG_CHAR_WIDTH; + f->generic.charHeight = UI_BIG_CHAR_HEIGHT; + } + else if( f->generic.flags & QMF_SMALLFONT ) + { + f->generic.charWidth = UI_SMALL_CHAR_WIDTH; + f->generic.charHeight = UI_SMALL_CHAR_HEIGHT; + } + else + { + if( f->generic.charWidth < 1 ) f->generic.charWidth = UI_MED_CHAR_WIDTH; + if( f->generic.charHeight < 1 ) f->generic.charHeight = UI_MED_CHAR_HEIGHT; + } + + UI_ScaleCoords( NULL, NULL, &f->generic.charWidth, &f->generic.charHeight ); + + if( !(f->generic.flags & (QMF_LEFT_JUSTIFY|QMF_CENTER_JUSTIFY|QMF_RIGHT_JUSTIFY))) + f->generic.flags |= QMF_LEFT_JUSTIFY; + + if( !f->generic.color ) f->generic.color = uiInputTextColor; + if( !f->generic.focusColor ) f->generic.focusColor = uiInputTextColor; + + f->maxLength++; + if( f->maxLength <= 1 || f->maxLength >= UI_MAX_FIELD_LINE ) + f->maxLength = UI_MAX_FIELD_LINE - 1; + + UI_ScaleCoords( &f->generic.x, &f->generic.y, &f->generic.width, &f->generic.height ); + + // calculate number of visible characters + f->widthInChars = (f->generic.width / f->generic.charWidth); + + f->cursor = strlen( f->buffer ); +} + +/* +================ +UI_Field_Paste +================ +*/ +void UI_Field_Paste( void ) +{ + char *str; + int pasteLen, i; + + str = GET_CLIPBOARD (); + if( !str ) return; + + // send as if typed, so insert / overstrike works properly + pasteLen = strlen( str ); + for( i = 0; i < pasteLen; i++ ) + UI_CharEvent( str[i] ); + FREE( str ); +} + +/* +================ +UI_Field_Clear +================ +*/ +void UI_Field_Clear( menuField_s *f ) +{ + memset( f->buffer, 0, UI_MAX_FIELD_LINE ); + f->cursor = 0; + f->scroll = 0; +} + + +/* +================= +UI_Field_Key +================= +*/ +const char *UI_Field_Key( menuField_s *f, int key, int down ) +{ + int len; + + if( !down ) return 0; + + // clipboard paste + if((( key == K_INS ) || ( key == K_KP_INS )) && KEY_IsDown( K_SHIFT )) + { + UI_Field_Paste(); + return 0; + } + + len = strlen( f->buffer ); + + if( key == K_INS ) + { + // toggle overstrike mode + KEY_SetOverstrike( !KEY_GetOverstrike( )); + return uiSoundNull; // handled + } + + // previous character + if( key == K_LEFTARROW ) + { + if( f->cursor > 0 ) f->cursor--; + if( f->cursor < f->scroll ) f->scroll--; + return uiSoundNull; + } + + // next character + if( key == K_RIGHTARROW ) + { + if( f->cursor < len ) f->cursor++; + if( f->cursor >= f->scroll + f->widthInChars && f->cursor <= len ) + f->scroll++; + return uiSoundNull; + } + + // first character + if( key == K_HOME ) + { + f->cursor = 0; + return uiSoundNull; + } + + // last character + if( key == K_END ) + { + f->cursor = len; + return uiSoundNull; + } + + if( key == K_BACKSPACE ) + { + if( f->cursor > 0 ) + { + memmove( f->buffer + f->cursor - 1, f->buffer + f->cursor, len - f->cursor + 1 ); + f->cursor--; + if( f->scroll ) f->scroll--; + } + } + if( key == K_DEL ) + { + if( f->cursor < len ) + memmove( f->buffer + f->cursor, f->buffer + f->cursor + 1, len - f->cursor ); + } + + if( f->generic.callback ) + f->generic.callback( f, QM_CHANGED ); + return 0; +} + +/* +================= +UI_Field_Char +================= +*/ +void UI_Field_Char( menuField_s *f, int key ) +{ + int len; + + if( key == 'v' - 'a' + 1 ) + { + // ctrl-v is paste + UI_Field_Paste(); + return; + } + + if( key == 'c' - 'a' + 1 ) + { + // ctrl-c clears the field + UI_Field_Clear( f ); + return; + } + + len = strlen( f->buffer ); + + if( key == 'a' - 'a' + 1 ) + { + // ctrl-a is home + f->cursor = 0; + f->scroll = 0; + return; + } + + if( key == 'e' - 'a' + 1 ) + { + // ctrl-e is end + f->cursor = len; + f->scroll = f->cursor - f->widthInChars; + return; + } + + // ignore any other non printable chars + if( key < 32 ) return; + + if( key == '^' && !( f->generic.flags & QMF_ALLOW_COLORSTRINGS )) + { + // ignore color key-symbol + return; + } + + if( f->generic.flags & QMF_NUMBERSONLY ) + { + if( key < '0' || key > '9' ) + return; + } + + if( f->generic.flags & QMF_LOWERCASE ) + key = tolower( key ); + else if( f->generic.flags & QMF_UPPERCASE ) + key = toupper( key ); + + if( KEY_GetOverstrike( )) + { + if( f->cursor == f->maxLength - 1 ) return; + f->buffer[f->cursor] = key; + f->cursor++; + } + else + { + // insert mode + if( len == f->maxLength - 1 ) return; // all full + memmove( f->buffer + f->cursor + 1, f->buffer + f->cursor, len + 1 - f->cursor ); + f->buffer[f->cursor] = key; + f->cursor++; + } + + if( f->cursor >= f->widthInChars ) f->scroll++; + if( f->cursor == len + 1 ) f->buffer[f->cursor] = 0; + + if( f->generic.callback ) + f->generic.callback( f, QM_CHANGED ); +} + +/* +================= +UI_Field_Draw +================= +*/ +void UI_Field_Draw( menuField_s *f ) +{ + int justify; + int shadow; + char text[UI_MAX_FIELD_LINE]; + int len, drawLen, prestep; + int cursor, x, textHeight; + char cursor_char[3]; + + if( f->generic.flags & QMF_LEFT_JUSTIFY ) + justify = 0; + else if( f->generic.flags & QMF_CENTER_JUSTIFY ) + justify = 1; + else if( f->generic.flags & QMF_RIGHT_JUSTIFY ) + justify = 2; + + shadow = (f->generic.flags & QMF_DROPSHADOW); + + cursor_char[1] = '\0'; + if( KEY_GetOverstrike( )) + cursor_char[0] = 11; + else cursor_char[0] = 95; + + drawLen = f->widthInChars; + len = strlen( f->buffer ) + 1; + + // guarantee that cursor will be visible + if( len <= drawLen ) + { + prestep = 0; + } + else + { + if( f->scroll + drawLen > len ) + { + f->scroll = len - drawLen; + if( f->scroll < 0 ) f->scroll = 0; + } + prestep = f->scroll; + } + + if( prestep + drawLen > len ) + drawLen = len - prestep; + + // extract characters from the field at + if( drawLen >= UI_MAX_FIELD_LINE ) + HOST_ERROR( "UI_Field_Draw: drawLen >= UI_MAX_FIELD_LINE\n" ); + + memcpy( text, f->buffer + prestep, drawLen ); + text[drawLen] = 0; + + if( f->generic.flags & QMF_HIDEINPUT ) + { + for( int i = 0; i < drawLen; i++ ) + if( text[i] ) text[i] = '*'; + } + + // find cursor position + x = drawLen - (ColorStrlen( text ) + 1 ); + if( x < 0 ) x = 0; + cursor = ( f->cursor - prestep - x ); + if( cursor < 0 ) cursor = 0; + + if( justify == 0 ) x = f->generic.x; + else if( justify == 1 ) + x = f->generic.x + ((f->generic.width - (ColorStrlen( text ) * f->generic.charWidth )) / 2 ); + else if( justify == 2 ) + x = f->generic.x + (f->generic.width - (ColorStrlen( text ) * f->generic.charWidth )); + + if( f->background ) + { + UI_DrawPic( f->generic.x, f->generic.y, f->generic.width, f->generic.height, uiColorWhite, f->background ); + } + else + { + // draw the background + UI_FillRect( f->generic.x, f->generic.y, f->generic.width, f->generic.height, uiInputBgColor ); + + // draw the rectangle + UI_DrawRectangle( f->generic.x, f->generic.y, f->generic.width, f->generic.height, uiInputFgColor ); + } + + textHeight = f->generic.y - (f->generic.charHeight * 1.5f); + UI_DrawString( f->generic.x, textHeight, f->generic.width, f->generic.charHeight, f->generic.name, uiColorHelp, true, f->generic.charWidth, f->generic.charHeight, 0, shadow ); + + if( f->generic.flags & QMF_GRAYED ) + { + UI_DrawString( f->generic.x, f->generic.y, f->generic.width, f->generic.height, text, uiColorDkGrey, true, f->generic.charWidth, f->generic.charHeight, justify, shadow ); + return; // grayed + } + + if((menuCommon_s *)f != (menuCommon_s *)UI_ItemAtCursor( f->generic.parent )) + { + UI_DrawString( f->generic.x, f->generic.y, f->generic.width, f->generic.height, text, f->generic.color, false, f->generic.charWidth, f->generic.charHeight, justify, shadow ); + return; // no focus + } + + if( !( f->generic.flags & QMF_FOCUSBEHIND )) + { + UI_DrawString( f->generic.x, f->generic.y, f->generic.width, f->generic.height, text, f->generic.color, false, f->generic.charWidth, f->generic.charHeight, justify, shadow ); + + if(( uiStatic.realTime & 499 ) < 250 ) + UI_DrawString( x + (cursor * f->generic.charWidth), f->generic.y, f->generic.charWidth, f->generic.height, cursor_char, f->generic.color, true, f->generic.charWidth, f->generic.charHeight, 0, shadow ); + } + + if( f->generic.flags & QMF_HIGHLIGHTIFFOCUS ) + { + UI_DrawString( f->generic.x, f->generic.y, f->generic.width, f->generic.height, text, f->generic.focusColor, false, f->generic.charWidth, f->generic.charHeight, justify, shadow ); + + if(( uiStatic.realTime & 499 ) < 250 ) + UI_DrawString( x + (cursor * f->generic.charWidth), f->generic.y, f->generic.charWidth, f->generic.height, cursor_char, f->generic.focusColor, true, f->generic.charWidth, f->generic.charHeight, 0, shadow ); + } + else if( f->generic.flags & QMF_PULSEIFFOCUS ) + { + int color; + + color = PackAlpha( f->generic.color, 255 * (0.5 + 0.5 * sin( uiStatic.realTime / UI_PULSE_DIVISOR ))); + UI_DrawString( f->generic.x, f->generic.y, f->generic.width, f->generic.height, text, color, false, f->generic.charWidth, f->generic.charHeight, justify, shadow ); + + if(( uiStatic.realTime & 499 ) < 250 ) + UI_DrawString( x + (cursor * f->generic.charWidth), f->generic.y, f->generic.charWidth, f->generic.height, cursor_char, color, true, f->generic.charWidth, f->generic.charHeight, 0, shadow ); + } + + if( f->generic.flags & QMF_FOCUSBEHIND ) + { + UI_DrawString( f->generic.x, f->generic.y, f->generic.width, f->generic.height, text, f->generic.color, false, f->generic.charWidth, f->generic.charHeight, justify, shadow ); + + if(( uiStatic.realTime & 499 ) < 250 ) + UI_DrawString( x + (cursor * f->generic.charWidth), f->generic.y, f->generic.charWidth, f->generic.height, cursor_char, f->generic.color, true, f->generic.charWidth, f->generic.charHeight, 0, shadow ); + } +} + +/* +================= +UI_Action_Init +================= +*/ +void UI_Action_Init( menuAction_s *a ) +{ + if( !a->generic.name ) a->generic.name = ""; // this is also the text displayed + + if( a->generic.flags & QMF_BIGFONT ) + { + a->generic.charWidth = UI_BIG_CHAR_WIDTH; + a->generic.charHeight = UI_BIG_CHAR_HEIGHT; + } + else if( a->generic.flags & QMF_SMALLFONT ) + { + a->generic.charWidth = UI_SMALL_CHAR_WIDTH; + a->generic.charHeight = UI_SMALL_CHAR_HEIGHT; + } + else + { + if( a->generic.charWidth < 1 ) a->generic.charWidth = UI_MED_CHAR_WIDTH; + if( a->generic.charHeight < 1 ) a->generic.charHeight = UI_MED_CHAR_HEIGHT; + } + + if(!( a->generic.flags & ( QMF_LEFT_JUSTIFY|QMF_CENTER_JUSTIFY|QMF_RIGHT_JUSTIFY ))) + a->generic.flags |= QMF_LEFT_JUSTIFY; + + if( !a->generic.color ) a->generic.color = uiPromptTextColor; + if( !a->generic.focusColor ) a->generic.focusColor = uiPromptFocusColor; + + if( a->generic.width < 1 || a->generic.height < 1 ) + { + if( a->background ) + { + HIMAGE handle = PIC_Load( a->background ); + a->generic.width = PIC_Width( handle ); + a->generic.height = PIC_Height( handle ); + } + else + { + if( a->generic.width < 1 ) + a->generic.width = a->generic.charWidth * strlen( a->generic.name ); + + if( a->generic.height < 1 ) + a->generic.height = a->generic.charHeight * 1.5; + } + } + + UI_ScaleCoords( NULL, NULL, &a->generic.charWidth, &a->generic.charHeight ); + UI_ScaleCoords( &a->generic.x, &a->generic.y, &a->generic.width, &a->generic.height ); +} + +/* +================= +UI_Action_Key +================= +*/ +const char *UI_Action_Key( menuAction_s *a, int key, int down ) +{ + const char *sound = 0; + + switch( key ) + { + case K_MOUSE1: + if(!( a->generic.flags & QMF_HASMOUSEFOCUS )) + break; + sound = uiSoundLaunch; + break; + case K_ENTER: + case K_KP_ENTER: + if( !down ) return sound; + if( a->generic.flags & QMF_MOUSEONLY ) + break; + sound = uiSoundLaunch; + break; + } + + if( sound && ( a->generic.flags & QMF_SILENT )) + sound = uiSoundNull; + + if( a->generic.flags & QMF_ACT_ONRELEASE ) + { + if( sound && a->generic.callback ) + { + int event; + + if( down ) + { + event = QM_PRESSED; + a->generic.bPressed = true; + } + else event = QM_ACTIVATED; + a->generic.callback( a, event ); + } + } + else if( down ) + { + if( sound && a->generic.callback ) + a->generic.callback( a, QM_ACTIVATED ); + } + + return sound; +} + +/* +================= +UI_Action_Draw +================= +*/ +void UI_Action_Draw( menuAction_s *a ) +{ + int justify; + int shadow; + + if( a->generic.flags & QMF_LEFT_JUSTIFY ) + justify = 0; + else if( a->generic.flags & QMF_CENTER_JUSTIFY ) + justify = 1; + else if( a->generic.flags & QMF_RIGHT_JUSTIFY ) + justify = 2; + + shadow = (a->generic.flags & QMF_DROPSHADOW); + + if( a->background ) + UI_DrawPic( a->generic.x, a->generic.y, a->generic.width, a->generic.height, uiColorWhite, a->background ); + + if( a->generic.statusText && a->generic.flags & QMF_NOTIFY ) + { + int charW, charH; + int x, w; + + charW = UI_SMALL_CHAR_WIDTH; + charH = UI_SMALL_CHAR_HEIGHT; + + UI_ScaleCoords( NULL, NULL, &charW, &charH ); + + x = 290; + w = UI_SMALL_CHAR_WIDTH * strlen( a->generic.statusText ); + UI_ScaleCoords( &x, NULL, &w, NULL ); + x += a->generic.x; + + int r, g, b; + + UnpackRGB( r, g, b, uiColorHelp ); + TextMessageSetColor( r, g, b ); + DrawConsoleString( x, a->generic.y, a->generic.statusText ); + } + + if( a->generic.flags & QMF_GRAYED ) + { + UI_DrawString( a->generic.x, a->generic.y, a->generic.width, a->generic.height, a->generic.name, uiColorDkGrey, true, a->generic.charWidth, a->generic.charHeight, justify, shadow ); + return; // grayed + } + + if((menuCommon_s *)a != (menuCommon_s *)UI_ItemAtCursor( a->generic.parent )) + { + UI_DrawString( a->generic.x, a->generic.y, a->generic.width, a->generic.height, a->generic.name, a->generic.color, false, a->generic.charWidth, a->generic.charHeight, justify, shadow ); + return; // no focus + } + + if(!( a->generic.flags & QMF_FOCUSBEHIND )) + UI_DrawString( a->generic.x, a->generic.y, a->generic.width, a->generic.height, a->generic.name, a->generic.color, false, a->generic.charWidth, a->generic.charHeight, justify, shadow ); + + if( a->generic.flags & QMF_HIGHLIGHTIFFOCUS ) + UI_DrawString( a->generic.x, a->generic.y, a->generic.width, a->generic.height, a->generic.name, a->generic.focusColor, false, a->generic.charWidth, a->generic.charHeight, justify, shadow ); + else if( a->generic.flags & QMF_PULSEIFFOCUS ) + { + int color; + + color = PackAlpha( a->generic.color, 255 * (0.5 + 0.5 * sin( uiStatic.realTime / UI_PULSE_DIVISOR ))); + + UI_DrawString( a->generic.x, a->generic.y, a->generic.width, a->generic.height, a->generic.name, color, false, a->generic.charWidth, a->generic.charHeight, justify, shadow ); + } + + if( a->generic.flags & QMF_FOCUSBEHIND ) + UI_DrawString( a->generic.x, a->generic.y, a->generic.width, a->generic.height, a->generic.name, a->generic.color, false, a->generic.charWidth, a->generic.charHeight, justify, shadow ); +} + +/* +================= +UI_Bitmap_Init +================= +*/ +void UI_Bitmap_Init( menuBitmap_s *b ) +{ + if( !b->generic.name ) b->generic.name = ""; + if( !b->focusPic ) b->focusPic = b->pic; + if( !b->generic.color ) b->generic.color = uiColorWhite; + + UI_ScaleCoords( &b->generic.x, &b->generic.y, &b->generic.width, &b->generic.height ); +} + +/* +================= +UI_Bitmap_Key +================= +*/ +const char *UI_Bitmap_Key( menuBitmap_s *b, int key, int down ) +{ + const char *sound = 0; + + switch( key ) + { + case K_MOUSE1: + if(!( b->generic.flags & QMF_HASMOUSEFOCUS )) + break; + sound = uiSoundLaunch; + break; + case K_ENTER: + case K_KP_ENTER: + if( !down ) return sound; + if( b->generic.flags & QMF_MOUSEONLY ) + break; + sound = uiSoundLaunch; + break; + } + if( sound && ( b->generic.flags & QMF_SILENT )) + sound = uiSoundNull; + + if( b->generic.flags & QMF_ACT_ONRELEASE ) + { + if( sound && b->generic.callback ) + { + int event; + + if( down ) + { + event = QM_PRESSED; + b->generic.bPressed = true; + } + else event = QM_ACTIVATED; + b->generic.callback( b, event ); + } + } + else if( down ) + { + if( sound && b->generic.callback ) + b->generic.callback( b, QM_ACTIVATED ); + } + + return sound; +} + +/* +================= +UI_Bitmap_Draw +================= +*/ +void UI_Bitmap_Draw( menuBitmap_s *b ) +{ + if( b->generic.id == ID_BACKGROUND ) // background is always 0! + { + if( CVAR_GET_FLOAT( "cl_background" )) + return; // has background map disable images + + // UGLY HACK for replace all backgrounds + UI_DrawBackground_Callback( b ); + return; + } + + //CR + if( b->generic.id == 1 ) + { + // don't draw banners until transition is done +#ifdef TA_ALT_MODE + if( UI_GetTitleTransFraction() != 10 ) return; +#else + if( UI_GetTitleTransFraction() < 1.0f ) return; +#endif + } + + if( b->generic.flags & QMF_GRAYED ) + { + UI_DrawPic( b->generic.x, b->generic.y, b->generic.width, b->generic.height, uiColorDkGrey, b->pic ); + return; // grayed + } + + if(( b->generic.flags & QMF_MOUSEONLY ) && !( b->generic.flags & QMF_HASMOUSEFOCUS )) + { + UI_DrawPic( b->generic.x, b->generic.y, b->generic.width, b->generic.height, b->generic.color, b->pic ); + return; // no focus + } + + if((menuCommon_s *)b != (menuCommon_s *)UI_ItemAtCursor( b->generic.parent )) + { + // UNDONE: only inactive bitmaps supported + if( b->generic.flags & QMF_DRAW_ADDITIVE ) + UI_DrawPicAdditive( b->generic.x, b->generic.y, b->generic.width, b->generic.height, b->generic.color, b->pic ); + else UI_DrawPic( b->generic.x, b->generic.y, b->generic.width, b->generic.height, b->generic.color, b->pic ); + return; // no focus + } + + if(!( b->generic.flags & QMF_FOCUSBEHIND )) + UI_DrawPic( b->generic.x, b->generic.y, b->generic.width, b->generic.height, b->generic.color, b->pic ); + if( b->generic.flags & QMF_HIGHLIGHTIFFOCUS ) + UI_DrawPic( b->generic.x, b->generic.y, b->generic.width, b->generic.height, b->generic.color, b->focusPic ); + else if( b->generic.flags & QMF_PULSEIFFOCUS ) + { + int color; + + color = PackAlpha( b->generic.color, 255 * (0.5 + 0.5 * sin( uiStatic.realTime / UI_PULSE_DIVISOR ))); + UI_DrawPic( b->generic.x, b->generic.y, b->generic.width, b->generic.height, color, b->focusPic ); + } + + if( b->generic.flags & QMF_FOCUSBEHIND ) + UI_DrawPic( b->generic.x, b->generic.y, b->generic.width, b->generic.height, b->generic.color, b->pic ); +} + +/* +================= +UI_PicButton_Init +================= +*/ +void UI_PicButton_Init( menuPicButton_s *pb ) +{ + if( !pb->generic.name ) pb->generic.name = ""; + + if( pb->generic.flags & QMF_BIGFONT ) + { + pb->generic.charWidth = UI_BIG_CHAR_WIDTH; + pb->generic.charHeight = UI_BIG_CHAR_HEIGHT; + } + else if( pb->generic.flags & QMF_SMALLFONT ) + { + pb->generic.charWidth = UI_SMALL_CHAR_WIDTH; + pb->generic.charHeight = UI_SMALL_CHAR_HEIGHT; + } + else + { + if( pb->generic.charWidth < 1 ) pb->generic.charWidth = UI_MED_CHAR_WIDTH; + if( pb->generic.charHeight < 1 ) pb->generic.charHeight = UI_MED_CHAR_HEIGHT; + } + + if(!( pb->generic.flags & ( QMF_LEFT_JUSTIFY|QMF_CENTER_JUSTIFY|QMF_RIGHT_JUSTIFY ))) + pb->generic.flags |= QMF_LEFT_JUSTIFY; + + if( !pb->generic.color ) pb->generic.color = uiPromptTextColor; + if( !pb->generic.focusColor ) pb->generic.focusColor = uiPromptFocusColor; + + if( pb->generic.width < 1 || pb->generic.height < 1 ) + { + if( pb->generic.width < 1 ) + pb->generic.width = pb->generic.charWidth * strlen( pb->generic.name ); + + if( pb->generic.height < 1 ) + pb->generic.height = pb->generic.charHeight * 1.5; + } + + UI_ScaleCoords( &pb->generic.x, &pb->generic.y, &pb->generic.width, &pb->generic.height ); + UI_ScaleCoords( NULL, NULL, &pb->generic.charWidth, &pb->generic.charHeight ); +} + +/* +================= +UI_PicButton_Key +================= +*/ +const char *UI_PicButton_Key( menuPicButton_s *b, int key, int down ) +{ + const char *sound = 0; + + switch( key ) + { + case K_MOUSE1: + if(!( b->generic.flags & QMF_HASMOUSEFOCUS )) + break; + sound = uiSoundLaunch; + break; + case K_ENTER: + case K_KP_ENTER: + if( b->generic.flags & QMF_MOUSEONLY ) + break; + sound = uiSoundLaunch; + break; + } + if( sound && ( b->generic.flags & QMF_SILENT )) + sound = uiSoundNull; + + if( b->generic.flags & QMF_ACT_ONRELEASE ) + { + if( sound && b->generic.callback ) + { + int event; + + if( down ) + { + event = QM_PRESSED; + b->generic.bPressed = true; + } + else event = QM_ACTIVATED; + //CR + UI_TACheckMenuDepth(); + b->generic.callback( b, event ); + UI_SetTitleAnim( AS_TO_TITLE, b ); + } + } + else if( down ) + { + if( sound && b->generic.callback ) + b->generic.callback( b, QM_ACTIVATED ); + } + + return sound; +} + +/* +================= +UI_PicButton_Draw +================= +*/ +void UI_PicButton_Draw( menuPicButton_s *item ) +{ + int state = BUTTON_NOFOCUS; + + if( item->generic.flags & QMF_HASMOUSEFOCUS ) + state = BUTTON_FOCUS; + + // make sure what cursor in rect + if( item->generic.bPressed ) + state = BUTTON_PRESSED; +#if 0 + if( item->generic.statusText && item->generic.flags & QMF_NOTIFY ) + { + int charW, charH; + int x, w; + + charW = UI_SMALL_CHAR_WIDTH; + charH = UI_SMALL_CHAR_HEIGHT; + + UI_ScaleCoords( NULL, NULL, &charW, &charH ); + + x = 290; + w = UI_SMALL_CHAR_WIDTH * strlen( item->generic.statusText ); + UI_ScaleCoords( &x, NULL, &w, NULL ); + x += item->generic.x; + + int r, g, b; + + UnpackRGB( r, g, b, uiColorHelp ); + TextMessageSetColor( r, g, b ); + DrawConsoleString( x, item->generic.y, item->generic.statusText ); + } +#endif + if( item->pic ) + { + int r, g, b, a; + + UnpackRGB( r, g, b, item->generic.flags & QMF_GRAYED ? uiColorDkGrey : uiColorWhite ); + + wrect_t rects[]= + { + { 0, uiStatic.buttons_width, 0, 26 }, + { 0, uiStatic.buttons_width, 26, 52 }, + { 0, uiStatic.buttons_width, 52, 78 } + }; + + PIC_Set( item->pic, r, g, b, 255 ); + PIC_EnableScissor( item->generic.x, item->generic.y, uiStatic.buttons_draw_width, uiStatic.buttons_draw_height - 2 ); + PIC_DrawAdditive( item->generic.x, item->generic.y, uiStatic.buttons_draw_width, uiStatic.buttons_draw_height, &rects[state] ); + + a = (512 - (uiStatic.realTime - item->generic.lastFocusTime)) >> 1; + + if( state == BUTTON_NOFOCUS && a > 0 ) + { + PIC_Set( item->pic, r, g, b, a ); + PIC_DrawAdditive( item->generic.x, item->generic.y, uiStatic.buttons_draw_width, uiStatic.buttons_draw_height, &rects[BUTTON_FOCUS] ); + } + PIC_DisableScissor(); + } + else + { + int justify; + int shadow; + + if( item->generic.flags & QMF_LEFT_JUSTIFY ) + justify = 0; + else if( item->generic.flags & QMF_CENTER_JUSTIFY ) + justify = 1; + else if( item->generic.flags & QMF_RIGHT_JUSTIFY ) + justify = 2; + + shadow = (item->generic.flags & QMF_DROPSHADOW); + + if( item->generic.flags & QMF_GRAYED ) + { + UI_DrawString( item->generic.x, item->generic.y, item->generic.width, item->generic.height, item->generic.name, uiColorDkGrey, true, item->generic.charWidth, item->generic.charHeight, justify, shadow ); + return; // grayed + } + + if((menuCommon_s *)item != (menuCommon_s *)UI_ItemAtCursor( item->generic.parent )) + { + UI_DrawString( item->generic.x, item->generic.y, item->generic.width, item->generic.height, item->generic.name, item->generic.color, false, item->generic.charWidth, item->generic.charHeight, justify, shadow ); + return; // no focus + } + + if(!( item->generic.flags & QMF_FOCUSBEHIND )) + UI_DrawString( item->generic.x, item->generic.y, item->generic.width, item->generic.height, item->generic.name, item->generic.color, false, item->generic.charWidth, item->generic.charHeight, justify, shadow ); + + if( item->generic.flags & QMF_HIGHLIGHTIFFOCUS ) + UI_DrawString( item->generic.x, item->generic.y, item->generic.width, item->generic.height, item->generic.name, item->generic.focusColor, false, item->generic.charWidth, item->generic.charHeight, justify, shadow ); + else if( item->generic.flags & QMF_PULSEIFFOCUS ) + { + int color; + + color = PackAlpha( item->generic.color, 255 * (0.5 + 0.5 * sin( uiStatic.realTime / UI_PULSE_DIVISOR ))); + + UI_DrawString( item->generic.x, item->generic.y, item->generic.width, item->generic.height, item->generic.name, color, false, item->generic.charWidth, item->generic.charHeight, justify, shadow ); + } + + if( item->generic.flags & QMF_FOCUSBEHIND ) + UI_DrawString( item->generic.x, item->generic.y, item->generic.width, item->generic.height, item->generic.name, item->generic.color, false, item->generic.charWidth, item->generic.charHeight, justify, shadow ); + } +} \ No newline at end of file diff --git a/mainui/utils.h b/mainui/utils.h new file mode 100644 index 0000000..0c30e5d --- /dev/null +++ b/mainui/utils.h @@ -0,0 +1,119 @@ +/* +utils.h - draw helper +Copyright (C) 2010 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef UTILS_H +#define UTILS_H + +extern ui_enginefuncs_t g_engfuncs; + +#include "enginecallback.h" +#include "gameinfo.h" + +#define FILE_GLOBAL static +#define DLL_GLOBAL + +#define MAX_INFO_STRING 256 // engine limit + +#define RAD2DEG( x ) ((float)(x) * (float)(180.f / M_PI)) +#define DEG2RAD( x ) ((float)(x) * (float)(M_PI / 180.f)) + +// +// How did I ever live without ASSERT? +// +#ifdef _DEBUG +void DBG_AssertFunction( BOOL fExpr, const char* szExpr, const char* szFile, int szLine, const char* szMessage ); +#define ASSERT( f ) DBG_AssertFunction( f, #f, __FILE__, __LINE__, NULL ) +#define ASSERTSZ( f, sz ) DBG_AssertFunction( f, #f, __FILE__, __LINE__, sz ) +#else +#define ASSERT( f ) +#define ASSERTSZ( f, sz ) +#endif + +extern ui_globalvars_t *gpGlobals; + +// exports +extern int UI_VidInit( void ); +extern void UI_Init( void ); +extern void UI_Shutdown( void ); +extern void UI_UpdateMenu( float flTime ); +extern void UI_KeyEvent( int key, int down ); +extern void UI_MouseMove( int x, int y ); +extern void UI_SetActiveMenu( int fActive ); +extern void UI_AddServerToList( netadr_t adr, const char *info ); +extern void UI_GetCursorPos( int *pos_x, int *pos_y ); +extern void UI_SetCursorPos( int pos_x, int pos_y ); +extern void UI_ShowCursor( int show ); +extern void UI_CharEvent( int key ); +extern int UI_MouseInRect( void ); +extern int UI_IsVisible( void ); +extern int UI_CreditsActive( void ); +extern void UI_FinalCredits( void ); + +#include "cvardef.h" + +// ScreenHeight returns the height of the screen, in pixels +#define ScreenHeight (gpGlobals->scrHeight) +// ScreenWidth returns the width of the screen, in pixels +#define ScreenWidth (gpGlobals->scrWidth) + +inline unsigned int PackRGB( int r, int g, int b ) +{ + return ((0xFF)<<24|(r)<<16|(g)<<8|(b)); +} + +inline unsigned int PackRGBA( int r, int g, int b, int a ) +{ + return ((a)<<24|(r)<<16|(g)<<8|(b)); +} + +inline void UnpackRGB( int &r, int &g, int &b, unsigned int ulRGB ) +{ + r = (ulRGB & 0xFF0000) >> 16; + g = (ulRGB & 0xFF00) >> 8; + b = (ulRGB & 0xFF) >> 0; +} + +inline void UnpackRGBA( int &r, int &g, int &b, int &a, unsigned int ulRGBA ) +{ + a = (ulRGBA & 0xFF000000) >> 24; + r = (ulRGBA & 0xFF0000) >> 16; + g = (ulRGBA & 0xFF00) >> 8; + b = (ulRGBA & 0xFF) >> 0; +} + +inline int PackAlpha( unsigned int ulRGB, unsigned int ulAlpha ) +{ + return (ulRGB)|(ulAlpha<<24); +} + +inline int UnpackAlpha( unsigned int ulRGBA ) +{ + return ((ulRGBA & 0xFF000000) >> 24); +} + +extern int ColorStrlen( const char *str ); // returns string length without color symbols +extern int ColorPrexfixCount( const char *str ); +extern const int g_iColorTable[8]; +extern void COM_FileBase( const char *in, char *out ); // ripped out from hlsdk 2.3 +extern int UI_FadeAlpha( int starttime, int endtime ); +extern void StringConcat( char *dst, const char *src, size_t size ); // strncat safe prototype +extern char *Info_ValueForKey( const char *s, const char *key ); +extern int KEY_GetKey( const char *binding ); // ripped out from engine +extern char *StringCopy( const char *input ); // copy string into new memory +extern int COM_CompareSaves( const void **a, const void **b ); + +extern void UI_LoadCustomStrings( void ); + +#endif//UTILS_H \ No newline at end of file diff --git a/materials.h b/materials.h new file mode 100644 index 0000000..2d9616f --- /dev/null +++ b/materials.h @@ -0,0 +1,14 @@ + +settings format + +"TEXNAME" +{ // material begin + "detailmap" "gfx/details/dt_bricks" // field to set path to detail texture + "detailScale" "10 10" // detail texture magnification + "reflectScale" "0" // enable reflection with Box Parallax Corrected Cubemap and set intensity of reflection + "refractScale" "0" // works only on glass surfaces (refraction factor uses normal map) + "AberrationScale" "0" // works only on glass surfaces (chromatic aberration factor uses normal map) + "smoothness" "0.5" // glossiness + "material" "default" // description of physical params of surface +} // material end + diff --git a/p2_launch/Paranoia 2.exp b/p2_launch/Paranoia 2.exp new file mode 100644 index 0000000..47e69c7 Binary files /dev/null and b/p2_launch/Paranoia 2.exp differ diff --git a/p2_launch/Paranoia 2.lib b/p2_launch/Paranoia 2.lib new file mode 100644 index 0000000..2c14f1c Binary files /dev/null and b/p2_launch/Paranoia 2.lib differ diff --git a/p2_launch/game.aps b/p2_launch/game.aps new file mode 100644 index 0000000..d0e48f1 Binary files /dev/null and b/p2_launch/game.aps differ diff --git a/p2_launch/game.cpp b/p2_launch/game.cpp new file mode 100644 index 0000000..a4f0ecd --- /dev/null +++ b/p2_launch/game.cpp @@ -0,0 +1,80 @@ +/* +game.cpp -- executable to run Xash3D Engine +Copyright (C) 2011 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include + +#define GAME_PATH "base" // default dir to start from + +#ifdef WIN32 +// enable NVIDIA High Performance Graphics while using Integrated Graphics. +extern "C" __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; +extern "C" __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 1; +#endif + +typedef void (*pfnChangeGame)( const char *progname ); +typedef int (*pfnInit)( const char *progname, int bChangeGame, pfnChangeGame func ); +typedef void (*pfnShutdown)( void ); + +pfnInit Host_Main; +pfnShutdown Host_Shutdown = NULL; +char szGameDir[128]; // safe place to keep gamedir +HINSTANCE hEngine; + +void Sys_Error( const char *errorstring ) +{ + MessageBox( NULL, errorstring, "Xash Error", MB_OK|MB_SETFOREGROUND|MB_ICONSTOP ); + exit( 1 ); +} + +void Sys_LoadEngine( void ) +{ + if(( hEngine = LoadLibrary( "xash.dll" )) == NULL ) + { + Sys_Error( "Unable to load the xash.dll" ); + } + + if(( Host_Main = (pfnInit)GetProcAddress( hEngine, "Host_Main" )) == NULL ) + { + Sys_Error( "core.dll missed 'Host_Main' export" ); + } + + // this is non-fatal for us but change game will not working + Host_Shutdown = (pfnShutdown)GetProcAddress( hEngine, "Host_Shutdown" ); +} + +void Sys_UnloadEngine( void ) +{ + if( Host_Shutdown ) Host_Shutdown( ); + if( hEngine ) FreeLibrary( hEngine ); +} + +void Sys_ChangeGame( const char *progname ) +{ + if( !progname || !progname[0] ) Sys_Error( "Sys_ChangeGame: NULL gamedir" ); + if( Host_Shutdown == NULL ) Sys_Error( "Sys_ChangeGame: missed 'Host_Shutdown' export\n" ); + strncpy( szGameDir, progname, sizeof( szGameDir ) - 1 ); + + Sys_UnloadEngine (); + Sys_LoadEngine (); + + Host_Main( szGameDir, TRUE, ( Host_Shutdown != NULL ) ? Sys_ChangeGame : NULL ); +} + +int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) +{ + Sys_LoadEngine(); + + return Host_Main( GAME_PATH, FALSE, ( Host_Shutdown != NULL ) ? Sys_ChangeGame : NULL ); +} \ No newline at end of file diff --git a/p2_launch/game.dsp b/p2_launch/game.dsp new file mode 100644 index 0000000..48ae55d --- /dev/null +++ b/p2_launch/game.dsp @@ -0,0 +1,82 @@ +# Microsoft Developer Studio Project File - Name="game" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=game - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "game.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "game.mak" CFG="game - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "game - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "" +# PROP Intermediate_Dir "" +# PROP Ignore_Export_Lib 1 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /Ob0 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 /opt:nowin98 +# ADD LINK32 msvcrt.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /pdb:none /machine:I386 /nodefaultlib:"libc.lib" /out:"Paranoia 2.exe" /opt:nowin98 +# Begin Target + +# Name "game - Win32 Release" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\game.cpp +# End Source File +# Begin Source File + +SOURCE=.\game.rc +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# Begin Source File + +SOURCE=.\game.ico +# End Source File +# End Group +# End Target +# End Project diff --git a/p2_launch/game.dsw b/p2_launch/game.dsw new file mode 100644 index 0000000..2d84f43 --- /dev/null +++ b/p2_launch/game.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "game"=".\game.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/p2_launch/game.ico b/p2_launch/game.ico new file mode 100644 index 0000000..f8b6623 Binary files /dev/null and b/p2_launch/game.ico differ diff --git a/p2_launch/game.ncb b/p2_launch/game.ncb new file mode 100644 index 0000000..e1a0875 Binary files /dev/null and b/p2_launch/game.ncb differ diff --git a/p2_launch/game.obj b/p2_launch/game.obj new file mode 100644 index 0000000..6cb91ec Binary files /dev/null and b/p2_launch/game.obj differ diff --git a/p2_launch/game.opt b/p2_launch/game.opt new file mode 100644 index 0000000..eb7cd27 Binary files /dev/null and b/p2_launch/game.opt differ diff --git a/p2_launch/game.rc b/p2_launch/game.rc new file mode 100644 index 0000000..e10b65c --- /dev/null +++ b/p2_launch/game.rc @@ -0,0 +1,28 @@ +#include + +#define IDI_ICON1 101 + +#define VER_FILEVERSION 1,00 +#define VER_FILEVERSION_STR "1.00" +#define VER_PRODUCTVERSION 1,00 +#define VER_PRODUCTVERSION_STR "1.00" + +#define VER_FILEFLAGSMASK VS_FF_PRERELEASE | VS_FF_PATCHED +#define VER_FILEFLAGS VS_FF_PRERELEASE +#define VER_FILEOS VOS__WINDOWS32 +#define VER_FILETYPE VFT_DLL +#define VER_FILESUBTYPE VFT2_UNKNOWN + +#define VER_COMPANYNAME_STR "XashXT Group and Paranoia Team" +#define VER_LEGALCOPYRIGHT_STR "XashXT 2011" +#define VER_PRODUCTNAME_STR "Paranoia 2 Launcher" + +#define VER_ANSICP + +#define VER_FILEDESCRIPTION_STR "Paranoia 2 Launcher" +#define VER_ORIGINALFILENAME_STR "Paranoia 2.exe" +#define VER_INTERNALNAME_STR "Paranoia 2" + +#include + +IDI_ICON1 ICON DISCARDABLE "game.ico" \ No newline at end of file diff --git a/p2_launch/game.res b/p2_launch/game.res new file mode 100644 index 0000000..ccc833a Binary files /dev/null and b/p2_launch/game.res differ diff --git a/p2_launch/vc60.idb b/p2_launch/vc60.idb new file mode 100644 index 0000000..163fe59 Binary files /dev/null and b/p2_launch/vc60.idb differ diff --git a/paranoia_sdk.dsw b/paranoia_sdk.dsw new file mode 100644 index 0000000..4b143e6 --- /dev/null +++ b/paranoia_sdk.dsw @@ -0,0 +1,293 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "hl"=".\dlls\hl.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "client"=".\cl_dll\cl_dll.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "mainui"=".\mainui\mainui.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "mxtk"=.\utils\mxtk\mxtk.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "decal2tga"=.\utils\decal2tga\decal2tga.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "p2csg"=".\utils\p2csg\p2csg.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "hlmv"=".\utils\hlmv\hlmv.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "hlsv"=".\utils\hlsv\hlsv.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "p2bsp"=".\utils\p2bsp\p2bsp.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "p2vis"=".\utils\p2vis\p2vis.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "dlight"=".\utils\p2rad\dlight.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "hlrad"=".\utils\p2rad\hlrad.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "p1rad"=".\utils\p2rad\p1rad.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "p2rad"=".\utils\p2rad\p2rad.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "bsp31migrate"=".\utils\bsp31migrate\bsp31migrate.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "reqtest"=".\utils\reqtest\reqtest.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "makefont"=".\utils\makefont\makefont.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "maketex"=".\utils\maketex\maketex.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "makewad"=".\utils\makewad\makewad.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "stalker2tga"=".\utils\stalker2tga\stalker2tga.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "spritegen"=".\utils\spritegen\spritegen.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "studiomdl"=".\utils\studiomdl\studiomdl.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "game"=".\p2_launch\game.dsp" - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/pm_shared/clipfile.h b/pm_shared/clipfile.h new file mode 100644 index 0000000..cedf625 --- /dev/null +++ b/pm_shared/clipfile.h @@ -0,0 +1,62 @@ +/* +clipfile.h - studio cached clip geometry +Copyright (C) 2019 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef CLIPFILE_H +#define CLIPFILE_H + +/* +============================================================================== + +CLIP FILES + +.clip contain static geometry clip +============================================================================== +*/ + +#define IDCLIPHEADER (('P'<<24)+('I'<<16)+('L'<<8)+'C') // little-endian "CLIP" +#define CLIP_VERSION 1 + +// quake lump ordering +#define LUMP_CLIP_FACETS 0 +#define LUMP_CLIP_PLANES 1 +#define LUMP_CLIP_PLANE_INDEXES 2 +// for future expansions +#define LUMP_COUNT 8 + +typedef struct +{ + int fileofs; + int filelen; +} dcachelump_t; + +typedef struct +{ + int id; // must be little endian STCH + int version; + unsigned int modelCRC; // catch for model changes + dcachelump_t lumps[LUMP_COUNT]; +} dcachehdr_t; + +typedef struct +{ + short skinref; // pointer to texture for special effects + mvert_t triangle[3]; // store triangle points + vec3_t mins, maxs; // an individual size of each facet + vec3_t edge1, edge2; // new trace stuff + byte numplanes; // because numplanes for each facet can't exceeds MAX_FACET_PLANES! + uint firstindex; // first index into CLIP_PLANE_INDEXES lump +} dfacet_t; + +#endif//CLIPFILE_H \ No newline at end of file diff --git a/pm_shared/meshdesc.cpp b/pm_shared/meshdesc.cpp new file mode 100644 index 0000000..d379689 --- /dev/null +++ b/pm_shared/meshdesc.cpp @@ -0,0 +1,1244 @@ +/* +meshdesc.cpp - cached mesh for tracing custom objects +Copyright (C) 2012 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#define WIN32_LEAN_AND_MEAN +#include "windows.h" +#include +#include "vector.h" +#include "matrix.h" +#include "const.h" +#include "com_model.h" +#include "trace.h" +#include "mathlib.h" +#include "stringlib.h" +#include "virtualfs.h" +#include "clipfile.h" + +#ifdef CLIENT_DLL +#include "cl_dll.h" +#include "render_api.h" +#else +#include "edict.h" +#include "eiface.h" +#include "physcallback.h" +#endif + +#include "enginecallback.h" + +CMeshDesc *UTIL_GetCollisionMesh( int modelindex ) +{ + model_t *mod = (model_t *)MODEL_HANDLE( modelindex ); + + if( !mod || mod->type != mod_studio ) + return NULL; + + // see if already cached + if( mod->bodymesh ) + return mod->bodymesh; + + // no studiodata ??? + if( !mod->cache.data ) + return NULL; + + CMeshDesc *bodyMesh = (CMeshDesc *)Mem_Alloc( sizeof( CMeshDesc )); + if( !bodyMesh ) return NULL; + + bodyMesh->CMeshDesc::CMeshDesc(); + bodyMesh->SetDebugName( mod->name ); + bodyMesh->SetModel( mod ); + + if( bodyMesh->StudioConstructMesh( )) + { + // now cached + mod->bodymesh = bodyMesh; + return bodyMesh; + } + + // failed to build + delete bodyMesh; + + return NULL; +} + +CMeshDesc :: CMeshDesc( void ) +{ + memset( &m_mesh, 0, sizeof( m_mesh )); + m_debugName = NULL; + m_srcPlaneElems = NULL; + m_curPlaneElems = NULL; + m_srcPlaneHash = NULL; + m_srcPlanePool = NULL; + m_srcFacets = NULL; + m_pModel = NULL; + m_iNumTris = 0; +} + +CMeshDesc :: ~CMeshDesc( void ) +{ + FreeMesh (); +} + +void CMeshDesc :: InsertLinkBefore( link_t *l, link_t *before ) +{ + l->next = before; + l->prev = before->prev; + l->prev->next = l; + l->next->prev = l; +} + +void CMeshDesc :: RemoveLink( link_t *l ) +{ + l->next->prev = l->prev; + l->prev->next = l->next; +} + +void CMeshDesc :: ClearLink( link_t *l ) +{ + l->prev = l->next = l; +} + +void CMeshDesc :: StartPacifier( void ) +{ + m_iOldPercent = -1; + UpdatePacifier( 0.001f ); +} + +void CMeshDesc :: UpdatePacifier( float percent ) +{ + int f; + + f = (int)(percent * (float)PACIFIER_STEP); + f = bound( m_iOldPercent, f, PACIFIER_STEP ); + + if( f != m_iOldPercent ) + { + for( int i = m_iOldPercent + 1; i <= f; i++ ) + { + if(( i % PACIFIER_REM ) == 0 ) + { + Msg( "%d%%%%", ( i / PACIFIER_REM ) * 10 ); + } + else + { + if( i != PACIFIER_STEP ) + { + Msg( "." ); + } + } + } + + m_iOldPercent = f; + } +} + +void CMeshDesc :: EndPacifier( float total ) +{ + UpdatePacifier( 1.0f ); + Msg( " (%.2f secs)", total ); + Msg( "\n" ); +} + +/* +=============== +CreateAreaNode + +builds a uniformly subdivided tree for the given mesh size +=============== +*/ +areanode_t *CMeshDesc :: CreateAreaNode( int depth, const Vector &mins, const Vector &maxs ) +{ + areanode_t *anode; + Vector size; + Vector mins1, maxs1; + Vector mins2, maxs2; + + anode = &areanodes[numareanodes++]; + + // use 'solid_edicts' to store facets + ClearLink( &anode->solid_edicts ); + + if( depth == MAX_AREA_DEPTH ) + { + anode->axis = -1; + anode->children[0] = anode->children[1] = NULL; + return anode; + } + + size = maxs - mins; + + if( size[0] > size[1] ) + anode->axis = 0; + else anode->axis = 1; + + anode->dist = 0.5f * ( maxs[anode->axis] + mins[anode->axis] ); + mins1 = mins; + mins2 = mins; + maxs1 = maxs; + maxs2 = maxs; + + maxs1[anode->axis] = mins2[anode->axis] = anode->dist; + anode->children[0] = CreateAreaNode( depth+1, mins2, maxs2 ); + anode->children[1] = CreateAreaNode( depth+1, mins1, maxs1 ); + + return anode; +} + +void CMeshDesc :: FreeMesh( void ) +{ + has_tree = false; + + if( m_mesh.numfacets <= 0 ) + return; + + m_iTotalPlanes = 0; + m_iAllocPlanes = 0; + m_iHashPlanes = 0; + m_iNumTris = 0; + + // single memory block + Mem_Free( m_mesh.planes ); + + FreeMeshBuild(); + + memset( &m_mesh, 0, sizeof( m_mesh )); +} + +/* +================ +PlaneEqual +================ +*/ +bool CMeshDesc :: PlaneEqual( const mplane_t *p, const Vector &normal, float dist ) +{ + vec_t t; + + if( -PLANE_DIST_EPSILON < ( t = p->dist - dist ) && t < PLANE_DIST_EPSILON + && -PLANE_DIR_EPSILON < ( t = p->normal[0] - normal[0] ) && t < PLANE_DIR_EPSILON + && -PLANE_DIR_EPSILON < ( t = p->normal[1] - normal[1] ) && t < PLANE_DIR_EPSILON + && -PLANE_DIR_EPSILON < ( t = p->normal[2] - normal[2] ) && t < PLANE_DIR_EPSILON ) + return true; + + return false; +} + +/* +================= +PlaneTypeForNormal +================= +*/ +int CMeshDesc :: PlaneTypeForNormal( const Vector &normal ) +{ + vec_t ax, ay, az; + + ax = fabs( normal[0] ); + ay = fabs( normal[1] ); + az = fabs( normal[2] ); + + if(( ax > 1.0 - PLANE_DIR_EPSILON ) && ( ay < PLANE_DIR_EPSILON ) && ( az < PLANE_DIR_EPSILON )) + return PLANE_X; + + if(( ay > 1.0 - PLANE_DIR_EPSILON ) && ( az < PLANE_DIR_EPSILON ) && ( ax < PLANE_DIR_EPSILON )) + return PLANE_Y; + + if(( az > 1.0 - PLANE_DIR_EPSILON ) && ( ax < PLANE_DIR_EPSILON ) && ( ay < PLANE_DIR_EPSILON )) + return PLANE_Z; + + return PLANE_NONAXIAL; +} + +/* +================ +SnapNormal +================ +*/ +int CMeshDesc :: SnapNormal( Vector &normal ) +{ + int type = PlaneTypeForNormal( normal ); + bool renormalize = false; + + // snap normal to nearest axial if possible + if( type <= PLANE_LAST_AXIAL ) + { + for( int i = 0; i < 3; i++ ) + { + if( i == type ) + normal[i] = normal[i] > 0 ? 1 : -1; + else normal[i] = 0; + } + renormalize = true; + } + else + { + for( int i = 0; i < 3; i++ ) + { + if( fabs( fabs( normal[i] ) - 0.707106 ) < PLANE_DIR_EPSILON ) + { + normal[i] = normal[i] > 0 ? 0.707106 : -0.707106; + renormalize = true; + } + } + } + + if( renormalize ) + normal = normal.Normalize(); + + return type; +} + +/* +================ +CreateNewFloatPlane +================ +*/ +int CMeshDesc :: CreateNewFloatPlane( const Vector &srcnormal, float dist, int hash ) +{ + hashplane_t *p; + + if( srcnormal.Length() < 0.5f ) + return -1; + + // sanity check + if( m_mesh.numplanes >= m_iAllocPlanes ) + return -1; + + // snap plane normal + Vector normal = srcnormal; + int type = SnapNormal( normal ); + + // only snap distance if the normal is an axis. Otherwise there + // is nothing "natural" about snapping the distance to an integer. + if( VectorIsOnAxis( normal ) && fabs( dist - Q_rint( dist )) < PLANE_DIST_EPSILON ) + dist = Q_rint( dist ); // catch -0.0 + + // create a new one + p = &m_srcPlanePool[m_mesh.numplanes]; + p->hash = m_srcPlaneHash[hash]; + m_srcPlaneHash[hash] = p; + + // record the new plane + SetPlane( &p->pl, normal, dist, type ); + + return m_mesh.numplanes++; +} + +/* +============= +FindFloatPlane + +============= +*/ +int CMeshDesc :: FindFloatPlane( const Vector &normal, float dist ) +{ + int hash; + + // trying to find equal plane + hash = (int)fabs( dist ); + hash &= (PLANE_HASHES - 1); + + // search the border bins as well + for( int i = -1; i <= 1; i++ ) + { + int h = (hash + i) & (PLANE_HASHES - 1); + for( hashplane_t *p = m_srcPlaneHash[h]; p; p = p->hash ) + { + if( PlaneEqual( &p->pl, normal, dist )) + return (int)(p - m_srcPlanePool); // already exist + } + } + + // allocate a new two opposite planes + return CreateNewFloatPlane( normal, dist, hash ); +} + +/* +================ +PlaneFromPoints +================ +*/ +int CMeshDesc :: PlaneFromPoints( const Vector &p0, const Vector &p1, const Vector &p2 ) +{ + Vector t1 = p0 - p1; + Vector t2 = p2 - p1; + Vector normal = CrossProduct( t1, t2 ); + + if( !normal.NormalizeLength()) + return -1; + + return FindFloatPlane( normal, DotProduct( normal, p0 )); +} + +void CMeshDesc :: ExtractAnimValue( int frame, mstudioanim_t *panim, int dof, float scale, float &v1 ) +{ + if( !panim || panim->offset[dof] == 0 ) + { + v1 = 0.0f; + return; + } + + const mstudioanimvalue_t *panimvalue = (mstudioanimvalue_t *)((byte *)panim + panim->offset[dof]); + int k = frame; + + while( panimvalue->num.total <= k ) + { + k -= panimvalue->num.total; + panimvalue += panimvalue->num.valid + 1; + + if( panimvalue->num.total == 0 ) + { + v1 = 0.0f; + return; + } + } + + // Bah, missing blend! + if( panimvalue->num.valid > k ) + { + v1 = panimvalue[k+1].value * scale; + } + else + { + // get last valid data block + v1 = panimvalue[panimvalue->num.valid].value * scale; + } +} + +void CMeshDesc :: StudioCalcBoneTransform( int frame, mstudiobone_t *pbone, mstudioanim_t *panim, Vector &pos, Vector4D &q ) +{ + vec3_t origin; + Radian angles; + + ExtractAnimValue( frame, panim, 0, pbone->scale[0], origin.x ); + ExtractAnimValue( frame, panim, 1, pbone->scale[1], origin.y ); + ExtractAnimValue( frame, panim, 2, pbone->scale[2], origin.z ); + ExtractAnimValue( frame, panim, 3, pbone->scale[3], angles.x ); + ExtractAnimValue( frame, panim, 4, pbone->scale[4], angles.y ); + ExtractAnimValue( frame, panim, 5, pbone->scale[5], angles.z ); + + for( int j = 0; j < 3; j++ ) + { + origin[j] = pbone->value[j+0] + origin[j]; + angles[j] = pbone->value[j+3] + angles[j]; + } + + AngleQuaternion( angles, q ); + VectorCopy( origin, pos ); +} + +bool CMeshDesc :: AddMeshTrinagle( const mvert_t triangle[3], int skinref ) +{ + int i, planenum; + + if( m_iNumTris <= 0 ) + return false; // were not in a build mode! + + if( m_mesh.numfacets >= m_iNumTris ) + return false; // not possible? + + mfacet_t *facet = &m_srcFacets[m_mesh.numfacets]; + mplane_t *mainplane; + + // calculate plane for this triangle + if(( planenum = PlaneFromPoints( triangle[0].point, triangle[1].point, triangle[2].point )) == -1 ) + return false; // bad plane + + mplane_t planes[MAX_FACET_PLANES]; + Vector normal; + int numplanes; + float dist; + + mainplane = &m_srcPlanePool[planenum].pl; + facet->numplanes = numplanes = 0; + + planes[numplanes].normal = mainplane->normal; + planes[numplanes].dist = mainplane->dist; + numplanes++; + + // calculate mins & maxs + ClearBounds( facet->mins, facet->maxs ); + + for( i = 0; i < 3; i++ ) + { + AddPointToBounds( triangle[i].point, facet->mins, facet->maxs ); + facet->triangle[i] = triangle[i]; + } + + facet->edge1 = facet->triangle[1].point - facet->triangle[0].point; + facet->edge2 = facet->triangle[2].point - facet->triangle[0].point; + + // add the axial planes + for( int axis = 0; axis < 3; axis++ ) + { + for( int dir = -1; dir <= 1; dir += 2 ) + { + for( i = 0; i < numplanes; i++ ) + { + if( planes[i].normal[axis] == dir ) + break; + } + + if( i == numplanes ) + { + normal = g_vecZero; + normal[axis] = dir; + if( dir == 1 ) + dist = facet->maxs[axis]; + else dist = -facet->mins[axis]; + + planes[numplanes].normal = normal; + planes[numplanes].dist = dist; + numplanes++; + } + + if( numplanes >= MAX_FACET_PLANES ) + return false; + } + } + + // add the edge bevels + for( i = 0; i < 3; i++ ) + { + int j = (i + 1) % 3; + int k = (i + 2) % 3; + + Vector vec = triangle[i].point - triangle[j].point; + if( vec.Length() < 0.5f ) continue; + + vec = vec.Normalize(); +// SnapNormal( vec ); + + for( j = 0; j < 3; j++ ) + { + if( vec[j] == 1.0f || vec[j] == -1.0f ) + break; // axial + } + + if( j != 3 ) continue; // only test non-axial edges + + // try the six possible slanted axials from this edge + for( int axis = 0; axis < 3; axis++ ) + { + for( int dir = -1; dir <= 1; dir += 2 ) + { + // construct a plane + Vector vec2 = g_vecZero; + vec2[axis] = dir; + normal = CrossProduct( vec, vec2 ); + + if( normal.Length() < 0.5f ) + continue; + + normal = normal.Normalize(); + dist = DotProduct( triangle[i].point, normal ); + + for( j = 0; j < numplanes; j++ ) + { + // if this plane has already been used, skip it + if( PlaneEqual( &planes[j], normal, dist )) + break; + } + + if( j != numplanes ) continue; + + // if all other points are behind this plane, it is a proper edge bevel + for( j = 0; j < 3; j++ ) + { + if( j != i ) + { + float d = DotProduct( triangle[j].point, normal ) - dist; + // point in front: this plane isn't part of the outer hull + if( d > 0.1f ) break; + } + } + + if( j != 3 ) continue; + + // add this plane + planes[numplanes].normal = normal; + planes[numplanes].dist = dist; + numplanes++; + + if( numplanes >= MAX_FACET_PLANES ) + return false; + } + } + } + + // add triangle to bounds + for( i = 0; i < 3; i++ ) + AddPointToBounds( triangle[i].point, m_mesh.mins, m_mesh.maxs ); + + facet->indices = m_curPlaneElems; + m_curPlaneElems += numplanes; + facet->numplanes = numplanes; + facet->skinref = skinref; + + for( i = 0; i < facet->numplanes; i++ ) + { + // add plane to global pool + facet->indices[i] = FindFloatPlane( planes[i].normal, planes[i].dist ); + } + + for( i = 0; i < 3; i++ ) + { + // spread the mins / maxs by a pixel + facet->mins[i] -= 1.0f; + facet->maxs[i] += 1.0f; + } + + // added + m_iTotalPlanes += numplanes; + m_mesh.numfacets++; + return true; +} + +void CMeshDesc :: RelinkFacet( mfacet_t *facet ) +{ + // find the first node that the facet box crosses + areanode_t *node = areanodes; + + while( 1 ) + { + if( node->axis == -1 ) break; + if( facet->mins[node->axis] > node->dist ) + node = node->children[0]; + else if( facet->maxs[node->axis] < node->dist ) + node = node->children[1]; + else break; // crosses the node + } + + // link it in + InsertLinkBefore( &facet->area, &node->solid_edicts ); +} + +bool CMeshDesc :: StudioLoadCache( const char *pszModelName ) +{ + char szFilename[MAX_PATH]; + char szModelname[MAX_PATH]; + int i, length, iCompare; + + Q_strncpy( szModelname, pszModelName + Q_strlen( "models/" ), sizeof( szModelname )); + COM_StripExtension( szModelname ); + Q_snprintf( szFilename, sizeof( szFilename ), "cache/%s.clip", szModelname ); + + if( COMPARE_FILE_TIME( m_pModel->name, szFilename, &iCompare )) + { + // MDL file is newer. + if( iCompare > 0 ) + return false; + } + else + { + return false; + } + + byte *aMemFile = LOAD_FILE( szFilename, &length ); + if( !aMemFile ) return false; + + CVirtualFS file( aMemFile, length ); + dcachelump_t *lump; + dcachehdr_t hdr; + dfacet_t facet; + dplane_t plane; + + file.Read( &hdr, sizeof( hdr )); + + if( hdr.id != IDCLIPHEADER ) + { + ALERT( at_warning, "%s has wrong id (%p should be %p)\n", szFilename, hdr.id, IDCLIPHEADER ); + goto cleanup; + } + + if( hdr.version != CLIP_VERSION ) + { + ALERT( at_warning, "%s has wrong version (%i should be %i)\n", szFilename, hdr.version, CLIP_VERSION ); + goto cleanup; + } + + if( hdr.modelCRC != m_pModel->modelCRC ) + { + ALERT( at_console, "%s was changed, CLIP cache will be updated\n", szFilename ); + goto cleanup; + } + + ClearBounds( m_mesh.mins, m_mesh.maxs ); + memset( areanodes, 0, sizeof( areanodes )); + numareanodes = 0; + + // read plane representation table + lump = &hdr.lumps[LUMP_CLIP_PLANE_INDEXES]; + m_iAllocPlanes = m_iTotalPlanes = lump->filelen / sizeof( uint ); + + if( lump->filelen <= 0 || lump->filelen % sizeof( uint )) + { + ALERT( at_warning, "%s has funny size of LUMP_CLIP_PLANE_INDEXES\n", szFilename ); + goto cleanup; + } + + m_srcPlaneElems = (uint *)calloc( sizeof( uint ), m_iAllocPlanes ); + m_curPlaneElems = m_srcPlaneElems; + file.Seek( lump->fileofs, SEEK_SET ); + + // fill in plane indexes + file.Read( m_srcPlaneElems, lump->filelen ); + + // read unique planes array (hash is unused) + lump = &hdr.lumps[LUMP_CLIP_PLANES]; + m_mesh.numplanes = lump->filelen / sizeof( dplane_t ); + + if( lump->filelen <= 0 || lump->filelen % sizeof( dplane_t )) + { + ALERT( at_warning, "%s has funny size of LUMP_CLIP_PLANES\n", szFilename ); + goto cleanup; + } + + m_srcPlanePool = (hashplane_t *)calloc( sizeof( hashplane_t ), m_mesh.numplanes ); + file.Seek( lump->fileofs, SEEK_SET ); + + for( i = 0; i < m_mesh.numplanes; i++ ) + { + file.Read( &plane, sizeof( plane )); + m_srcPlanePool[i].pl.normal = plane.normal; + m_srcPlanePool[i].pl.dist = plane.dist; + m_srcPlanePool[i].pl.type = plane.type; + // categorize it + CategorizePlane( &m_srcPlanePool[i].pl ); + } + + lump = &hdr.lumps[LUMP_CLIP_FACETS]; + m_mesh.numfacets = m_iNumTris = lump->filelen / sizeof( dfacet_t ); + + if( lump->filelen <= 0 || lump->filelen % sizeof( dfacet_t )) + { + ALERT( at_warning, "%s has funny size of LUMP_CLIP_FACETS\n", szFilename ); + goto cleanup; + } + + m_srcFacets = (mfacet_t *)calloc( sizeof( mfacet_t ), m_iNumTris ); + file.Seek( lump->fileofs, SEEK_SET ); + + for( i = 0; i < m_iNumTris; i++ ) + { + file.Read( &facet, sizeof( facet )); + m_srcFacets[i].skinref = facet.skinref; + m_srcFacets[i].mins = facet.mins; + m_srcFacets[i].maxs = facet.maxs; + m_srcFacets[i].edge1 = facet.edge1; + m_srcFacets[i].edge2 = facet.edge2; + m_srcFacets[i].numplanes = facet.numplanes; + + // bounds checking + if( m_curPlaneElems != &m_srcPlaneElems[facet.firstindex] ) + Msg( "bad mem %p != %p\n", m_curPlaneElems, &m_srcPlaneElems[facet.firstindex] ); + + // just setup pointer to index array + m_srcFacets[i].indices = m_curPlaneElems; + m_curPlaneElems += m_srcFacets[i].numplanes; + + for( int k = 0; k < 3; k++ ) + { + m_srcFacets[i].triangle[k] = facet.triangle[k]; + AddPointToBounds( facet.triangle[k].point, m_mesh.mins, m_mesh.maxs ); + } + } + + if( m_iNumTris >= 256 ) + has_tree = true; // too many triangles invoke to build AABB tree + else has_tree = false; + + // all done + FREE_FILE( aMemFile ); + return true; +cleanup: + FREE_FILE( aMemFile ); + FreeMeshBuild(); + + return false; +} + +bool CMeshDesc :: StudioSaveCache( const char *pszModelName ) +{ + char szFilename[MAX_PATH]; + char szModelname[MAX_PATH]; + dcachelump_t *lump; + dcachehdr_t hdr; + CVirtualFS file; + int i, curIndex; + + // something went wrong + if( m_mesh.numfacets <= 0 ) + return false; + + memset( &hdr, 0, sizeof( hdr )); + hdr.id = IDCLIPHEADER; + hdr.version = CLIP_VERSION; + hdr.modelCRC = m_pModel->modelCRC; + + file.Write( &hdr, sizeof( hdr )); + + dfacet_t *out_facets = (dfacet_t *)Mem_Alloc( sizeof( dfacet_t ) * m_mesh.numfacets ); + dplane_t *out_planes = (dplane_t *)Mem_Alloc( sizeof( dplane_t ) * m_mesh.numplanes ); + + // copy planes into mesh array (probably aligned block) + for( i = 0, curIndex = 0; i < m_mesh.numfacets; i++ ) + { + out_facets[i].mins = m_srcFacets[i].mins; + out_facets[i].maxs = m_srcFacets[i].maxs; + out_facets[i].edge1 = m_srcFacets[i].edge1; + out_facets[i].edge2 = m_srcFacets[i].edge2; + out_facets[i].numplanes = m_srcFacets[i].numplanes; + out_facets[i].skinref = m_srcFacets[i].skinref; + out_facets[i].firstindex = curIndex; + curIndex += m_srcFacets[i].numplanes; + + for( int k = 0; k < 3; k++ ) + out_facets[i].triangle[k] = m_srcFacets[i].triangle[k]; + } + + if( curIndex != m_iTotalPlanes ) + ALERT( at_error, "StudioSaveCache: invalid planecount! %d != %d\n", curIndex, m_iTotalPlanes ); + + for( i = 0; i < m_mesh.numplanes; i++ ) + { + VectorCopy( m_srcPlanePool[i].pl.normal, out_planes[i].normal ); + out_planes[i].dist = m_srcPlanePool[i].pl.dist; + out_planes[i].type = m_srcPlanePool[i].pl.type; + } + + if( curIndex != m_iTotalPlanes ) + ALERT( at_error, "StudioSaveCache: invalid planecount! %d != %d\n", curIndex, m_iTotalPlanes ); + + lump = &hdr.lumps[LUMP_CLIP_FACETS]; + lump->fileofs = file.Tell(); + lump->filelen = sizeof( dfacet_t ) * m_mesh.numfacets; + file.Write( out_facets, (lump->filelen + 3) & ~3 ); + + lump = &hdr.lumps[LUMP_CLIP_PLANES]; + lump->fileofs = file.Tell(); + lump->filelen = sizeof( dplane_t ) * m_mesh.numplanes; + file.Write( out_planes, (lump->filelen + 3) & ~3 ); + + lump = &hdr.lumps[LUMP_CLIP_PLANE_INDEXES]; + lump->fileofs = file.Tell(); + lump->filelen = sizeof( uint ) * m_iTotalPlanes; + file.Write( m_srcPlaneElems, (lump->filelen + 3) & ~3 ); + + // update header + file.Seek( 0, SEEK_SET ); + file.Write( &hdr, sizeof( hdr )); + + Mem_Free( out_facets ); + Mem_Free( out_planes ); + + Q_strncpy( szModelname, pszModelName + Q_strlen( "models/" ), sizeof( szModelname )); + COM_StripExtension( szModelname ); + Q_snprintf( szFilename, sizeof( szFilename ), "cache/%s.clip", szModelname ); + + if( SAVE_FILE( szFilename, file.GetBuffer(), file.GetSize( ))) + return true; + + ALERT( at_error, "StudioSaveCache: couldn't store %s\n", szFilename ); + return false; +} + +bool CMeshDesc :: InitMeshBuild( int numTriangles ) +{ + if( numTriangles <= 0 ) + return false; + + // perfomance warning + if( numTriangles >= MAX_TRIANGLES ) + { + ALERT( at_error, "%s have too many triangles (%i). Mesh cannot be build\n", m_debugName, numTriangles ); + return false; // failed to build (too many triangles) + } + else if( numTriangles >= (MAX_TRIANGLES >> 1)) + ALERT( at_warning, "%s have too many triangles (%i)\n", m_debugName, numTriangles ); + + // show the pacifier for user is knowelege what engine is not hanging + if( numTriangles >= (MAX_TRIANGLES >> 3)) + m_bShowPacifier = true; + else m_bShowPacifier = false; + + if( numTriangles >= 256 ) + has_tree = true; // too many triangles invoke to build AABB tree + else has_tree = false; + + ClearBounds( m_mesh.mins, m_mesh.maxs ); + + memset( areanodes, 0, sizeof( areanodes )); + numareanodes = 0; + + // bevels for each triangle can't exceeds MAX_FACET_PLANES + m_iAllocPlanes = numTriangles * MAX_FACET_PLANES; + m_iHashPlanes = (m_iAllocPlanes>>2); + m_iNumTris = numTriangles; + m_iTotalPlanes = 0; + + // create pools for construct mesh + m_srcFacets = (mfacet_t *)calloc( sizeof( mfacet_t ), numTriangles ); + m_srcPlaneHash = (hashplane_t **)calloc( sizeof( hashplane_t* ), m_iHashPlanes ); + m_srcPlanePool = (hashplane_t *)calloc( sizeof( hashplane_t ), m_iAllocPlanes ); + m_srcPlaneElems = (uint *)calloc( sizeof( uint ), m_iAllocPlanes ); + m_curPlaneElems = m_srcPlaneElems; + + return true; +} + +bool CMeshDesc :: StudioConstructMesh( void ) +{ + float start_time = Sys_DoubleTime(); + + if( !m_pModel || m_pModel->type != mod_studio ) + return false; + + studiohdr_t *phdr = (studiohdr_t *)m_pModel->cache.data; + if( !phdr ) return false; + + int body = 0, skin = 0; // FIXME: allow to use body + + if( StudioLoadCache( m_pModel->name )) + { + if( !FinishMeshBuild( )) + return false; + + FreeMeshBuild(); + ALERT( at_aiconsole, "%s: load time %g secs, size %s\n", m_debugName, Sys_DoubleTime() - start_time, Q_memprint( mesh_size )); + PrintMeshInfo(); + + return true; + } + + if( phdr->numbones < 1 ) + return false; + + // compute default pose for building mesh from + mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)phdr + phdr->seqindex); + mstudioseqgroup_t *pseqgroup = (mstudioseqgroup_t *)((byte *)phdr + phdr->seqgroupindex); + mstudioanim_t *panim = (mstudioanim_t *)((byte *)phdr + pseqdesc->animindex); + mstudiobone_t *pbone = (mstudiobone_t *)((byte *)phdr + phdr->boneindex); + static Vector pos[MAXSTUDIOBONES]; + static Vector4D q[MAXSTUDIOBONES]; + int totalVertSize = 0; + + for( int i = 0; i < phdr->numbones; i++, pbone++, panim++ ) + { + StudioCalcBoneTransform( 0, pbone, panim, pos[i], q[i] ); + } + + pbone = (mstudiobone_t *)((byte *)phdr + phdr->boneindex); + matrix3x4 transform, bonematrix, bonetransform[MAXSTUDIOBONES]; + transform = matrix3x4( g_vecZero, g_vecZero, Vector( 1.0f, 1.0f, 1.0f )); + + // compute bones for default anim + for( i = 0; i < phdr->numbones; i++ ) + { + // initialize bonematrix + bonematrix = matrix3x4( pos[i], q[i] ); + + if( pbone[i].parent == -1 ) + bonetransform[i] = transform.ConcatTransforms( bonematrix ); + else bonetransform[i] = bonetransform[pbone[i].parent].ConcatTransforms( bonematrix ); + } + + // through all bodies to determine max vertices count + for( i = 0; i < phdr->numbodyparts; i++ ) + { + mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)phdr + phdr->bodypartindex) + i; + + int index = body / pbodypart->base; + index = index % pbodypart->nummodels; + + mstudiomodel_t *psubmodel = (mstudiomodel_t *)((byte *)phdr + pbodypart->modelindex) + index; + totalVertSize += psubmodel->numverts; + } + + Vector *verts = new Vector[totalVertSize * 8]; // allocate temporary vertices array + float *coords = new float[totalVertSize * 16]; // allocate temporary texcoords array + uint *skinrefs = new uint[totalVertSize * 8]; // store material pointers + unsigned int *indices = new unsigned int[totalVertSize * 24]; + int numVerts = 0, numElems = 0, numTris = 0; + mvert_t triangle[3]; + + for( int k = 0; k < phdr->numbodyparts; k++ ) + { + mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)phdr + phdr->bodypartindex) + k; + + int index = body / pbodypart->base; + index = index % pbodypart->nummodels; + int m_skinnum = bound( 0, skin, MAXSTUDIOSKINS ); + + mstudiomodel_t *psubmodel = (mstudiomodel_t *)((byte *)phdr + pbodypart->modelindex) + index; + Vector *pstudioverts = (Vector *)((byte *)phdr + psubmodel->vertindex); + Vector *m_verts = new Vector[psubmodel->numverts]; + byte *pvertbone = ((byte *)phdr + psubmodel->vertinfoindex); + + // setup all the vertices + for( i = 0; i < psubmodel->numverts; i++ ) + m_verts[i] = bonetransform[pvertbone[i]].VectorTransform( pstudioverts[i] ); + + mstudiomaterial_t *pmaterial = (m_pModel) ? (mstudiomaterial_t *)m_pModel->materials : NULL; + mstudiotexture_t *ptexture = (mstudiotexture_t *)((byte *)phdr + phdr->textureindex); + short *pskinref = (short *)((byte *)phdr + phdr->skinindex); + if( m_skinnum != 0 && m_skinnum < phdr->numskinfamilies ) + pskinref += (m_skinnum * phdr->numskinref); + + for( int j = 0; j < psubmodel->nummesh; j++ ) + { + mstudiomesh_t *pmesh = (mstudiomesh_t *)((byte *)phdr + psubmodel->meshindex) + j; + short *ptricmds = (short *)((byte *)phdr + pmesh->triindex); + float s = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].width; + float t = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].height; + int flags = ptexture[pskinref[pmesh->skinref]].flags; + + while( i = *( ptricmds++ )) + { + int vertexState = 0; + bool tri_strip; + + if( i < 0 ) + { + tri_strip = false; + i = -i; + } + else tri_strip = true; + + numTris += (i - 2); + + for( ; i > 0; i--, ptricmds += 4 ) + { + // build in indices + if( vertexState++ < 3 ) + { + indices[numElems++] = numVerts; + } + else if( tri_strip ) + { + // flip triangles between clockwise and counter clockwise + if( vertexState & 1 ) + { + // draw triangle [n-2 n-1 n] + indices[numElems++] = numVerts - 2; + indices[numElems++] = numVerts - 1; + indices[numElems++] = numVerts; + } + else + { + // draw triangle [n-1 n-2 n] + indices[numElems++] = numVerts - 1; + indices[numElems++] = numVerts - 2; + indices[numElems++] = numVerts; + } + } + else + { + // draw triangle fan [0 n-1 n] + indices[numElems++] = numVerts - ( vertexState - 1 ); + indices[numElems++] = numVerts - 1; + indices[numElems++] = numVerts; + } + + verts[numVerts] = m_verts[ptricmds[0]]; + skinrefs[numVerts] = pmesh->skinref; + + if( flags & STUDIO_NF_CHROME ) + { + // probably always equal 64 (see studiomdl.c for details) + coords[numVerts*2+0] = s; + coords[numVerts*2+1] = t; + } + else if( flags & STUDIO_NF_UV_COORDS ) + { + coords[numVerts*2+0] = HalfToFloat( ptricmds[2] ); + coords[numVerts*2+1] = HalfToFloat( ptricmds[3] ); + } + else + { + coords[numVerts*2+0] = ptricmds[2] * s; + coords[numVerts*2+1] = ptricmds[3] * t; + } + numVerts++; + } + } + } + + delete [] m_verts; // don't keep this because different submodels may have difference count of vertices + } + + if( numTris != ( numElems / 3 )) + ALERT( at_error, "StudioConstructMesh: mismatch triangle count (%i should be %i)\n", (numElems / 3), numTris ); + + InitMeshBuild( numTris ); + + if( m_bShowPacifier ) + { + if( numTris >= 262144 ) + Msg( "StudioConstructMesh: ^1%s^7\n", m_debugName ); + else if( numTris >= 131072 ) + Msg( "StudioConstructMesh: ^3%s^7\n", m_debugName ); + else Msg( "StudioConstructMesh: ^2%s^7\n", m_debugName ); + StartPacifier(); + } + + for( i = 0; i < numElems; i += 3 ) + { + // fill the triangle + triangle[0].point = verts[indices[i+0]]; + triangle[1].point = verts[indices[i+1]]; + triangle[2].point = verts[indices[i+2]]; + + triangle[0].st[0] = coords[indices[i+0]*2+0]; + triangle[0].st[1] = coords[indices[i+0]*2+1]; + triangle[1].st[0] = coords[indices[i+1]*2+0]; + triangle[1].st[1] = coords[indices[i+1]*2+1]; + triangle[2].st[0] = coords[indices[i+2]*2+0]; + triangle[2].st[1] = coords[indices[i+2]*2+1]; + + // add it to mesh + AddMeshTrinagle( triangle, skinrefs[indices[i]] ); + + if( m_bShowPacifier ) + { + UpdatePacifier( (float)m_mesh.numfacets / numTris ); + } + } + + if( m_bShowPacifier ) + { + float end_time = Sys_DoubleTime(); + EndPacifier( end_time - start_time ); + } + + delete [] skinrefs; + delete [] coords; + delete [] verts; + delete [] indices; + + if( !FinishMeshBuild( )) + return false; + + // now dump the collision into cachefile + StudioSaveCache( m_pModel->name ); + FreeMeshBuild(); + + if( !m_bShowPacifier ) + { + ALERT( at_console, "%s: CLIP build time %g secs\n", m_debugName, Sys_DoubleTime() - start_time ); + PrintMeshInfo(); + } + + // done + return true; +} + +bool CMeshDesc :: FinishMeshBuild( void ) +{ + if( m_mesh.numfacets <= 0 ) + { + ALERT( at_aiconsole, "%s: failed to build triangle mesh\n", m_debugName ); + FreeMesh(); + return false; + } + + for( int i = 0; i < 3; i++ ) + { + // spread the mins / maxs by a pixel + m_mesh.mins[i] -= 1.0f; + m_mesh.maxs[i] += 1.0f; + } + + size_t memsize = (sizeof( mfacet_t ) * m_mesh.numfacets) + (sizeof( mplane_t ) * m_mesh.numplanes) + (sizeof( uint ) * m_iTotalPlanes); + mesh_size = sizeof( m_mesh ) + memsize; + + // create non-fragmented memory piece and move mesh + byte *buffer = (byte *)Mem_Alloc( memsize ); + byte *bufend = buffer + memsize; + + // setup pointers + m_mesh.planes = (mplane_t *)buffer; // so we free mem with planes + buffer += (sizeof( mplane_t ) * m_mesh.numplanes); + m_mesh.facets = (mfacet_t *)buffer; + buffer += (sizeof( mfacet_t ) * m_mesh.numfacets); + + // setup mesh pointers + for( i = 0; i < m_mesh.numfacets; i++ ) + { + m_mesh.facets[i].indices = (uint *)buffer; + buffer += (sizeof( uint ) * m_srcFacets[i].numplanes); + } + + if( buffer != bufend ) + ALERT( at_error, "FinishMeshBuild: memory representation error! %x != %x\n", buffer, bufend ); + + // re-use pointer + m_curPlaneElems = m_srcPlaneElems; + + // copy planes into mesh array (probably aligned block) + for( i = 0; i < m_mesh.numplanes; i++ ) + m_mesh.planes[i] = m_srcPlanePool[i].pl; + + // copy planes into mesh array (probably aligned block) + for( i = 0; i < m_mesh.numfacets; i++ ) + { + m_mesh.facets[i].mins = m_srcFacets[i].mins; + m_mesh.facets[i].maxs = m_srcFacets[i].maxs; + m_mesh.facets[i].edge1 = m_srcFacets[i].edge1; + m_mesh.facets[i].edge2 = m_srcFacets[i].edge2; + m_mesh.facets[i].area.next = m_mesh.facets[i].area.prev = NULL; + m_mesh.facets[i].numplanes = m_srcFacets[i].numplanes; + m_mesh.facets[i].skinref = m_srcFacets[i].skinref; + + for( int j = 0; j < m_srcFacets[i].numplanes; j++ ) + m_mesh.facets[i].indices[j] = m_curPlaneElems[j]; + m_curPlaneElems += m_srcFacets[i].numplanes; + + for( int k = 0; k < 3; k++ ) + m_mesh.facets[i].triangle[k] = m_srcFacets[i].triangle[k]; + } + + if( has_tree ) + { + // create tree + CreateAreaNode( 0, m_mesh.mins, m_mesh.maxs ); + + for( i = 0; i < m_mesh.numfacets; i++ ) + RelinkFacet( &m_mesh.facets[i] ); + } + + return true; +} + +void CMeshDesc :: PrintMeshInfo( void ) +{ +#if 0 // g-cont. just not needs + ALERT( at_console, "FinishMesh: %s %s", m_debugName, Q_memprint( mesh_size )); + ALERT( at_console, " (planes reduced from %i to %i)", m_iTotalPlanes, m_mesh.numplanes ); + ALERT( at_console, "\n" ); +#endif +} + +void CMeshDesc :: FreeMeshBuild( void ) +{ + // no reason to keep these arrays + free( m_srcPlaneElems ); + free( m_srcPlaneHash ); + free( m_srcPlanePool ); + free( m_srcFacets ); + + m_srcPlaneElems = NULL; + m_curPlaneElems = NULL; + m_srcPlaneHash = NULL; + m_srcPlanePool = NULL; + m_srcFacets = NULL; +} \ No newline at end of file diff --git a/pm_shared/meshdesc.h b/pm_shared/meshdesc.h new file mode 100644 index 0000000..df96ab2 --- /dev/null +++ b/pm_shared/meshdesc.h @@ -0,0 +1,139 @@ +/* +meshdesc.h - cached mesh for tracing custom objects +Copyright (C) 2012 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef MESHDESC_H +#define MESHDESC_H + +#include "studio.h" +#include "areanode.h" + +#define MAX_AREA_DEPTH 5 +#define MAX_AREANODES BIT( MAX_AREA_DEPTH + 1 ) + +#define MAX_FACET_PLANES 32 // can be increased up to 255 +#define MAX_TRIANGLES 524288 // studio triangles +#define PLANE_HASHES m_iHashPlanes +#define PACIFIER_STEP 40 +#define PACIFIER_REM ( PACIFIER_STEP / 10 ) + +typedef struct hashplane_s +{ + mplane_t pl; + struct hashplane_s *hash; +} hashplane_t; + +typedef struct mvert_s +{ + Vector point; + float st[2]; // for alpha-texture test +} mvert_t; + +typedef struct mfacet_s +{ + link_t area; // linked to a division node or leaf + int skinref; // pointer to texture for special effects + mvert_t triangle[3]; // store triangle points + Vector mins, maxs; // an individual size of each facet + vec3_t edge1, edge2; // new trace stuff + byte numplanes; // because numplanes for each facet can't exceeds MAX_FACET_PLANES! + uint *indices; // a indexes into mesh plane pool +} mfacet_t; + +typedef struct +{ + Vector mins, maxs; + int numfacets; + int numplanes; + mfacet_t *facets; + mplane_t *planes; // shared plane pool +} mmesh_t; + +class CMeshDesc +{ +private: + mmesh_t m_mesh; + const char *m_debugName; // just for debug purpoces + areanode_t areanodes[MAX_AREANODES]; // AABB tree for speedup trace test + int numareanodes; + bool has_tree; // build AABB tree + int m_iTotalPlanes; // just for stats + int m_iAllocPlanes; // allocated count of planes + int m_iHashPlanes; // total count of hashplanes + int m_iNumTris; // if > 0 we are in build mode + size_t mesh_size; // mesh total size + model_t *m_pModel; // parent model pointer + + // used only while mesh is constructing + mfacet_t *m_srcFacets; + hashplane_t **m_srcPlaneHash; + hashplane_t *m_srcPlanePool; + uint *m_srcPlaneElems; + uint *m_curPlaneElems; // sliding pointer + + // pacifier stuff + bool m_bShowPacifier; + int m_iOldPercent; +public: + CMeshDesc(); + ~CMeshDesc(); + + // mesh construction + bool InitMeshBuild( int numTrinagles ); + bool AddMeshTrinagle( const mvert_t triangle[3], int skinref ); + bool FinishMeshBuild( void ); + void FreeMeshBuild( void ); + void FreeMesh( void ); + + void SetDebugName( const char *name ) { m_debugName = name; } + void SetModel( const model_t *mod ) { m_pModel = (model_t *)mod; } + void PrintMeshInfo( void ); + + // pacifier stuff + void StartPacifier( void ); + void UpdatePacifier( float percent ); + void EndPacifier( float total ); + + // studio models processing + void ExtractAnimValue( int frame, mstudioanim_t *panim, int dof, float scale, float &v1 ); + void StudioCalcBoneTransform( int frame, mstudiobone_t *pbone, mstudioanim_t *panim, Vector &pos, Vector4D &q ); + bool StudioLoadCache( const char *pCacheName ); + bool StudioSaveCache( const char *pCacheName ); + bool StudioConstructMesh( void ); + + // linked list operations + void InsertLinkBefore( link_t *l, link_t *before ); + void RemoveLink( link_t *l ); + void ClearLink( link_t *l ); + + // AABB tree contsruction + areanode_t *CreateAreaNode( int depth, const Vector &mins, const Vector &maxs ); + void RelinkFacet( mfacet_t *facet ); + areanode_t *GetHeadNode( void ) { return (has_tree) ? areanodes : NULL; } + + // plane cache + int PlaneFromPoints( const Vector &p0, const Vector &p1, const Vector &p2 ); + int FindFloatPlane( const Vector &normal, float dist ); + int CreateNewFloatPlane( const Vector &srcnormal, float dist, int hash ); + bool PlaneEqual( const mplane_t *p, const Vector &normal, float dist ); + int PlaneTypeForNormal( const Vector &normal ); + int SnapNormal( Vector &normal ); + + // get cached collision + mmesh_t *GetMesh() { return (mesh_size) ? &m_mesh : NULL; } +}; + +CMeshDesc *UTIL_GetCollisionMesh( int modelindex ); + +#endif//MESHDESC_H \ No newline at end of file diff --git a/pm_shared/pm_debug.cpp b/pm_shared/pm_debug.cpp new file mode 100644 index 0000000..3a9ebe6 --- /dev/null +++ b/pm_shared/pm_debug.cpp @@ -0,0 +1,303 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include "mathlib.h" +#include "const.h" +#include "usercmd.h" +#include "pm_defs.h" +#include "pm_shared.h" +#include "pm_movevars.h" +#include "pm_debug.h" + +extern playermove_t *pmove; + +// Expand debugging BBOX particle hulls by this many units. +#define BOX_GAP 0.0f + +static int PM_boxpnt[6][4] = +{ + { 0, 4, 6, 2 }, // +X + { 0, 1, 5, 4 }, // +Y + { 0, 2, 3, 1 }, // +Z + { 7, 5, 1, 3 }, // -X + { 7, 3, 2, 6 }, // -Y + { 7, 6, 4, 5 }, // -Z +}; + +void PM_ShowClipBox( void ) +{ +#if defined( _DEBUG ) + Vector org; + Vector offset = Vector( 0, 0, 0 ); + + if ( !pmove->runfuncs ) + return; + + // More debugging, draw the particle bbox for player and for the entity we are looking directly at. + // aslo prints entity info to the console overlay. + //if ( !pmove->server ) + // return; + + // Draw entity in center of view + // Also draws the normal to the clip plane that intersects our movement ray. Leaves a particle + // trail at the intersection point. + PM_ViewEntity(); + + org = pmove->origin; + + if ( pmove->server ) + { + org += offset; + } + else + { + org -= offset; + } + + // Show our BBOX in particles. + PM_DrawBBox( pmove->player_mins[pmove->usehull], pmove->player_maxs[pmove->usehull], org, pmove->server ? 132 : 0, 0.1 ); + + PM_ParticleLine( org, org, pmove->server ? 132 : 0, 0.1, 5.0 ); +/* + { + for ( int i = 0; i < pmove->numphysent; i++ ) + { + if ( pmove->physents[ i ].info >= 1 && pmove->physents[ i ].info <= 4 ) + { + PM_DrawBBox( pmove->player_mins[pmove->usehull], pmove->player_maxs[pmove->usehull], pmove->physents[i].origin, 132, 0.1 ); + } + } + } +*/ +#endif +} + +/* +=============== +PM_ParticleLine + +================ +*/ +void PM_ParticleLine( const Vector &start, const Vector &end, int pcolor, float life, float vert ) +{ + float linestep = 2.0f; + float curdist; + float len; + Vector curpos; + Vector diff; + + // Determine distance; + diff = end - start; + + len = diff.Length(); + diff = diff.Normalize(); + + curdist = 0; + while (curdist <= len) + { + curpos = start + curdist * diff; + pmove->PM_Particle( curpos, pcolor, life, 0, vert); + curdist += linestep; + } + +} + +/* +================ +PM_DrawRectangle(Vector tl, Vector br) + +================ +*/ +void PM_DrawRectangle( const Vector &tl, const Vector &bl, const Vector &tr, const Vector &br, int pcolor, float life ) +{ + PM_ParticleLine( tl, bl, pcolor, life, 0 ); + PM_ParticleLine( bl, br, pcolor, life, 0 ); + PM_ParticleLine( br, tr, pcolor, life, 0 ); + PM_ParticleLine( tr, tl, pcolor, life, 0 ); +} + +/* +================ +PM_DrawPhysEntBBox(int num) + +================ +*/ +void PM_DrawPhysEntBBox( int num, int pcolor, float life ) +{ + physent_t *pe; + Vector org; + int j; + Vector tmp; + Vector p[8]; + float gap = BOX_GAP; + Vector modelmins, modelmaxs; + + if (num >= pmove->numphysent || num <= 0) + return; + + pe = &pmove->physents[num]; + + if (pe->model) + { + org = pe->origin; + + pmove->PM_GetModelBounds( pe->model, modelmins, modelmaxs ); + for (j = 0; j < 8; j++) + { + tmp[0] = (j & 1) ? modelmins[0] - gap : modelmaxs[0] + gap; + tmp[1] = (j & 2) ? modelmins[1] - gap : modelmaxs[1] + gap; + tmp[2] = (j & 4) ? modelmins[2] - gap : modelmaxs[2] + gap; + + p[j] = tmp; + } + + // If the bbox should be rotated, do that + if (pe->angles[0] || pe->angles[1] || pe->angles[2]) + { + Vector forward, right, up; + matrix3x3 mat( pe->angles ); + + // g-cont. probably this is wrong + mat = mat.Transpose(); + + for (j = 0; j < 8; j++) + { + tmp = p[j]; + p[j][0] = DotProduct ( tmp, mat.GetForward() ); + p[j][1] = DotProduct ( tmp, mat.GetRight() ); + p[j][2] = DotProduct ( tmp, mat.GetUp() ); + } + } + + // Offset by entity origin, if any. + for (j = 0; j < 8; j++) + p[j] += org; + + for (j = 0; j < 6; j++) + { + PM_DrawRectangle( + p[PM_boxpnt[j][1]], + p[PM_boxpnt[j][0]], + p[PM_boxpnt[j][2]], + p[PM_boxpnt[j][3]], + pcolor, life); + } + } + else + { + for (j = 0; j < 8; j++) + { + tmp[0] = (j & 1) ? pe->mins[0] : pe->maxs[0]; + tmp[1] = (j & 2) ? pe->mins[1] : pe->maxs[1]; + tmp[2] = (j & 4) ? pe->mins[2] : pe->maxs[2]; + + p[j] = tmp + pe->origin; + } + + for (j = 0; j < 6; j++) + { + PM_DrawRectangle( + p[PM_boxpnt[j][1]], + p[PM_boxpnt[j][0]], + p[PM_boxpnt[j][2]], + p[PM_boxpnt[j][3]], + pcolor, life); + } + + } +} + +/* +================ +PM_DrawBBox(Vector mins, Vector maxs, Vector origin, int pcolor, float life) + +================ +*/ +void PM_DrawBBox( const Vector &mins, const Vector &maxs, const Vector &origin, int pcolor, float life ) +{ + int j; + + Vector tmp; + Vector p[8]; + float gap = BOX_GAP; + + for (j = 0; j < 8; j++) + { + tmp[0] = (j & 1) ? mins[0] - gap : maxs[0] + gap; + tmp[1] = (j & 2) ? mins[1] - gap : maxs[1] + gap ; + tmp[2] = (j & 4) ? mins[2] - gap : maxs[2] + gap ; + + p[j] = tmp + origin; + } + + for (j = 0; j < 6; j++) + { + PM_DrawRectangle( + p[PM_boxpnt[j][1]], + p[PM_boxpnt[j][0]], + p[PM_boxpnt[j][2]], + p[PM_boxpnt[j][3]], + pcolor, life); + } +} + + +#ifndef DEDICATED + +/* +================ +PM_ViewEntity + +Shows a particle trail from player to entity in crosshair. +Shows particles at that entities bbox + +Tries to shoot a ray out by about 128 units. +================ +*/ +void PM_ViewEntity( void ) +{ + float raydist = 256.0f; + pmtrace_t trace; + int pcolor = 77; + float fup; + +#if 0 + if ( !pm_showclip.value ) + return; +#endif + matrix3x3 mat( pmove->angles ); + Vector origin = pmove->origin; + + fup = 0.5f * ( pmove->player_mins[pmove->usehull][2] + pmove->player_maxs[pmove->usehull][2] ); + fup += pmove->view_ofs[2]; + fup -= 4; + + Vector end = origin + raydist * mat.GetForward(); + + trace = pmove->PM_PlayerTrace( origin, end, PM_STUDIO_BOX, -1 ); + + if (trace.ent > 0) // Not the world + { + pcolor = 111; + } + + // Draw the hull or bbox. + if (trace.ent > 0) + { + PM_DrawPhysEntBBox(trace.ent, pcolor, 0.3f); + } +} + +#endif diff --git a/pm_shared/pm_debug.h b/pm_shared/pm_debug.h new file mode 100644 index 0000000..643b033 --- /dev/null +++ b/pm_shared/pm_debug.h @@ -0,0 +1,24 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#ifndef PM_DEBUG_H +#define PM_DEBUG_H +#pragma once + +void PM_ViewEntity( void ); +void PM_DrawBBox( const Vector &mins, const Vector &maxs, const Vector &origin, int pcolor, float life ); +void PM_ParticleLine( const Vector &start, const Vector &end, int pcolor, float life, float vert ); +void PM_ShowClipBox( void ); + +#endif // PMOVEDBG_H \ No newline at end of file diff --git a/pm_shared/pm_defs.h b/pm_shared/pm_defs.h new file mode 100644 index 0000000..f4f326c --- /dev/null +++ b/pm_shared/pm_defs.h @@ -0,0 +1,222 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef PM_DEFS_H +#define PM_DEFS_H + +#define MAX_PHYSENTS 600 // Must have room for all entities in the world. +#define MAX_MOVEENTS 64 +#define MAX_CLIP_PLANES 5 + +#define PM_NORMAL 0x00000000 +#define PM_STUDIO_IGNORE 0x00000001 // Skip studio models +#define PM_STUDIO_BOX 0x00000002 // Use boxes for non-complex studio models (even in traceline) +#define PM_GLASS_IGNORE 0x00000004 // Ignore entities with non-normal rendermode +#define PM_WORLD_ONLY 0x00000008 // Only trace against the world +#define PM_CUSTOM_IGNORE 0x00000010 // Ignore entities with SOLID_CUSTOM mode + +// Values for flags parameter of PM_TraceLine +#define PM_TRACELINE_PHYSENTSONLY 0 +#define PM_TRACELINE_ANYVISIBLE 1 + +#include "pm_info.h" + +// PM_PlayerTrace results. +#include "pmtrace.h" + + +#include "usercmd.h" + +// physent_t +typedef struct physent_s +{ + char name[32]; // Name of model, or "player" or "world". + int player; + vec3_t origin; // Model's origin in world coordinates. + struct model_s *model; // only for bsp models + struct model_s *studiomodel; // SOLID_BBOX, but studio clip intersections. + vec3_t mins, maxs; // only for non-bsp models + int info; // For client or server to use to identify (index into edicts or cl_entities) + vec3_t angles; // rotated entities need this info for hull testing to work. + + int solid; // Triggers and func_door type WATER brushes are SOLID_NOT + int skin; // BSP Contents for such things like fun_door water brushes. + int rendermode; // So we can ignore glass + + // Complex collision detection. + float frame; + int sequence; + byte controller[4]; + byte blending[2]; + + int movetype; + int takedamage; + int blooddecal; + int team; + int classnumber; + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; // also contains pev->scale when "sv_allow_studio_scaling" is "1" + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; +} physent_t; + +typedef struct playermove_s +{ + int player_index; // So we don't try to run the PM_CheckStuck nudging too quickly. + qboolean server; // For debugging, are we running physics code on server side? + + qboolean multiplayer; // 1 == multiplayer server + float time; // realtime on host, for reckoning duck timing + float frametime; // Duration of this frame + + vec3_t forward, right, up; // Vectors for angles + + // player state + vec3_t origin; // Movement origin. + vec3_t angles; // Movement view angles. + vec3_t oldangles; // Angles before movement view angles were looked at. + vec3_t velocity; // Current movement direction. + vec3_t movedir; // For waterjumping, a forced forward velocity so we can fly over lip of ledge. + vec3_t basevelocity; // Velocity of the conveyor we are standing, e.g. + + // For ducking/dead + vec3_t view_ofs; // Our eye position. + float flDuckTime; // Time we started duck + qboolean bInDuck; // In process of ducking or ducked already? + + // For walking/falling + int flTimeStepSound; // Next time we can play a step sound + int iStepLeft; + + float flFallVelocity; + vec3_t punchangle; + + float flSwimTime; + float flNextPrimaryAttack; + + int effects; // MUZZLE FLASH, e.g. + + int flags; // FL_ONGROUND, FL_DUCKING, etc. + int usehull; // 0 = regular player hull, 1 = ducked player hull, 2 = point hull + float gravity; // Our current gravity and friction. + float friction; + int oldbuttons; // Buttons last usercmd + float waterjumptime; // Amount of time left in jumping out of water cycle. + qboolean dead; // Are we a dead player? + int deadflag; + int spectator; // Should we use spectator physics model? + int movetype; // Our movement type, NOCLIP, WALK, FLY + + int onground; + int waterlevel; + int watertype; + int oldwaterlevel; + + char sztexturename[252]; // cutoff four bytes for a new variable (see below) + struct matdef_s *pMaterial; // pointer to a material + char chtexturetype; + + float maxspeed; + float clientmaxspeed; // Player specific maxspeed + + // For mods + int iuser1; + int iuser2; + int iuser3; + int iuser4; + float fuser1; + float fuser2; + float fuser3; + float fuser4; + vec3_t vuser1; + vec3_t vuser2; + vec3_t vuser3; + vec3_t vuser4; + + // world state + + // Number of entities to clip against. + int numphysent; + physent_t physents[MAX_PHYSENTS]; + + // Number of momvement entities (ladders) + int nummoveent; + // just a list of ladders + physent_t moveents[MAX_MOVEENTS]; + + // All things being rendered, for tracing against things you don't actually collide with + int numvisent; + physent_t visents[MAX_PHYSENTS]; + + // input to run through physics. + usercmd_t cmd; + + // Trace results for objects we collided with. + int numtouch; + pmtrace_t touchindex[MAX_PHYSENTS]; + + char physinfo[MAX_PHYSINFO_STRING]; // Physics info string + + struct movevars_s *movevars; + vec3_t player_mins[4]; + vec3_t player_maxs[4]; + + // Common functions + const char *(*PM_Info_ValueForKey) ( const char *s, const char *key ); + void (*PM_Particle)( float *origin, int color, float life, int zpos, int zvel ); + int (*PM_TestPlayerPosition)( float *pos, pmtrace_t *ptrace ); + void (*Con_NPrintf)( int idx, char *fmt, ... ); + void (*Con_DPrintf)( char *fmt, ... ); + void (*Con_Printf)( char *fmt, ... ); + double (*Sys_FloatTime)( void ); + void (*PM_StuckTouch)( int hitent, pmtrace_t *ptraceresult ); + int (*PM_PointContents)( float *p, int *truecontents /*filled in if this is non-null*/ ); + int (*PM_TruePointContents)( float *p ); + int (*PM_HullPointContents)( struct hull_s *hull, int num, float *p ); + pmtrace_t (*PM_PlayerTrace)( float *start, float *end, int traceFlags, int ignore_pe ); + struct pmtrace_s *(*PM_TraceLine)( float *start, float *end, int flags, int usehulll, int ignore_pe ); + long (*RandomLong)( long lLow, long lHigh ); + float (*RandomFloat)( float flLow, float flHigh ); + int (*PM_GetModelType)( struct model_s *mod ); + void (*PM_GetModelBounds)( struct model_s *mod, float *mins, float *maxs ); + void *(*PM_HullForBsp)( physent_t *pe, float *offset ); + float (*PM_TraceModel)( physent_t *pEnt, float *start, float *end, trace_t *trace ); + int (*COM_FileSize)( char *filename ); + byte *(*COM_LoadFile)( char *path, int usehunk, int *pLength ); + void (*COM_FreeFile)( void *buffer ); + char *(*memfgets)( byte *pMemFile, int fileSize, int *pFilePos, char *pBuffer, int bufferSize ); + + // Functions + // Run functions for this frame? + qboolean runfuncs; + void (*PM_PlaySound)( int channel, const char *sample, float volume, float attenuation, int fFlags, int pitch ); + const char *(*PM_TraceTexture)( int ground, float *vstart, float *vend ); + void (*PM_PlaybackEventFull)( int flags, int clientindex, unsigned short eventindex, float delay, float *origin, float *angles, float fparam1, float fparam2, int iparam1, int iparam2, int bparam1, int bparam2 ); + pmtrace_t (*PM_PlayerTraceEx) (float *start, float *end, int traceFlags, int (*pfnIgnore)( physent_t *pe )); + int (*PM_TestPlayerPositionEx) (float *pos, pmtrace_t *ptrace, int (*pfnIgnore)( physent_t *pe )); + struct pmtrace_s *(*PM_TraceLineEx)( float *start, float *end, int flags, int usehulll, int (*pfnIgnore)( physent_t *pe )); + struct msurface_s *(*PM_TraceSurface)( int ground, float *vstart, float *vend ); +} playermove_t; +#endif//PM_DEFS_H \ No newline at end of file diff --git a/pm_shared/pm_info.h b/pm_shared/pm_info.h new file mode 100644 index 0000000..ddb9528 --- /dev/null +++ b/pm_shared/pm_info.h @@ -0,0 +1,24 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// Physics info string definition +#if !defined( PM_INFOH ) +#define PM_INFOH +#ifdef _WIN32 +#pragma once +#endif + +#define MAX_PHYSINFO_STRING 256 + +#endif // PM_INFOH diff --git a/pm_shared/pm_math.cpp b/pm_shared/pm_math.cpp new file mode 100644 index 0000000..e994eb7 --- /dev/null +++ b/pm_shared/pm_math.cpp @@ -0,0 +1,432 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// pm_math.c -- math primitives + +#include "mathlib.h" +#include "const.h" +#include + +// up / down +#define PITCH 0 +// left / right +#define YAW 1 +// fall over +#define ROLL 2 + +#pragma warning(disable : 4244) + +vec3_t vec3_origin = {0,0,0}; + +#ifndef VECTOR_H + #define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2]) +#endif + +float anglemod(float a) +{ + a = (360.0/65536) * ((int)(a*(65536/360.0)) & 65535); + return a; +} + +void AngleVectors (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + if (forward) + { + forward[0] = cp*cy; + forward[1] = cp*sy; + forward[2] = -sp; + } + if (right) + { + right[0] = (-1*sr*sp*cy+-1*cr*-sy); + right[1] = (-1*sr*sp*sy+-1*cr*cy); + right[2] = -1*sr*cp; + } + if (up) + { + up[0] = (cr*sp*cy+-sr*-sy); + up[1] = (cr*sp*sy+-sr*cy); + up[2] = cr*cp; + } +} + +void AngleVectorsTranspose (const vec3_t angles, vec3_t forward, vec3_t right, vec3_t up) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + if (forward) + { + forward[0] = cp*cy; + forward[1] = (sr*sp*cy+cr*-sy); + forward[2] = (cr*sp*cy+-sr*-sy); + } + if (right) + { + right[0] = cp*sy; + right[1] = (sr*sp*sy+cr*cy); + right[2] = (cr*sp*sy+-sr*cy); + } + if (up) + { + up[0] = -sp; + up[1] = sr*cp; + up[2] = cr*cp; + } +} + + +void AngleMatrix (const vec3_t angles, float (*matrix)[4] ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + // matrix = (YAW * PITCH) * ROLL + matrix[0][0] = cp*cy; + matrix[1][0] = cp*sy; + matrix[2][0] = -sp; + matrix[0][1] = sr*sp*cy+cr*-sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[2][1] = sr*cp; + matrix[0][2] = (cr*sp*cy+-sr*-sy); + matrix[1][2] = (cr*sp*sy+-sr*cy); + matrix[2][2] = cr*cp; + matrix[0][3] = 0.0; + matrix[1][3] = 0.0; + matrix[2][3] = 0.0; +} + +void AngleIMatrix (const vec3_t angles, float matrix[3][4] ) +{ + float angle; + float sr, sp, sy, cr, cp, cy; + + angle = angles[YAW] * (M_PI*2 / 360); + sy = sin(angle); + cy = cos(angle); + angle = angles[PITCH] * (M_PI*2 / 360); + sp = sin(angle); + cp = cos(angle); + angle = angles[ROLL] * (M_PI*2 / 360); + sr = sin(angle); + cr = cos(angle); + + // matrix = (YAW * PITCH) * ROLL + matrix[0][0] = cp*cy; + matrix[0][1] = cp*sy; + matrix[0][2] = -sp; + matrix[1][0] = sr*sp*cy+cr*-sy; + matrix[1][1] = sr*sp*sy+cr*cy; + matrix[1][2] = sr*cp; + matrix[2][0] = (cr*sp*cy+-sr*-sy); + matrix[2][1] = (cr*sp*sy+-sr*cy); + matrix[2][2] = cr*cp; + matrix[0][3] = 0.0; + matrix[1][3] = 0.0; + matrix[2][3] = 0.0; +} + +void NormalizeAngles( float *angles ) +{ + int i; + // Normalize angles + for ( i = 0; i < 3; i++ ) + { + if ( angles[i] > 180.0 ) + { + angles[i] -= 360.0; + } + else if ( angles[i] < -180.0 ) + { + angles[i] += 360.0; + } + } +} + +/* +=================== +InterpolateAngles + +Interpolate Euler angles. +FIXME: Use Quaternions to avoid discontinuities +Frac is 0.0 to 1.0 ( i.e., should probably be clamped, but doesn't have to be ) +=================== +*/ +void InterpolateAngles( float *start, float *end, float *output, float frac ) +{ + int i; + float ang1, ang2; + float d; + + NormalizeAngles( start ); + NormalizeAngles( end ); + + for ( i = 0 ; i < 3 ; i++ ) + { + ang1 = start[i]; + ang2 = end[i]; + + d = ang2 - ang1; + if ( d > 180 ) + { + d -= 360; + } + else if ( d < -180 ) + { + d += 360; + } + + output[i] = ang1 + d * frac; + } + + NormalizeAngles( output ); +} + + +/* +=================== +AngleBetweenVectors + +=================== +*/ +float AngleBetweenVectors( const vec3_t v1, const vec3_t v2 ) +{ + float angle; + float l1 = Length( v1 ); + float l2 = Length( v2 ); + + if ( !l1 || !l2 ) + return 0.0f; + + angle = acos( DotProduct( v1, v2 ) ) / (l1*l2); + angle = ( angle * 180.0f ) / M_PI; + + return angle; +} + + +void VectorTransform (const vec3_t in1, float in2[3][4], vec3_t out) +{ + out[0] = DotProduct(in1, in2[0]) + in2[0][3]; + out[1] = DotProduct(in1, in2[1]) + in2[1][3]; + out[2] = DotProduct(in1, in2[2]) + in2[2][3]; +} + + +int VectorCompare (const vec3_t v1, const vec3_t v2) +{ + int i; + + for (i=0 ; i<3 ; i++) + if (v1[i] != v2[i]) + return 0; + + return 1; +} + +void VectorMA (const vec3_t veca, float scale, const vec3_t vecb, vec3_t vecc) +{ + vecc[0] = veca[0] + scale*vecb[0]; + vecc[1] = veca[1] + scale*vecb[1]; + vecc[2] = veca[2] + scale*vecb[2]; +} + + +vec_t _DotProduct (vec3_t v1, vec3_t v2) +{ + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2]; +} + +void _VectorSubtract (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]-vecb[0]; + out[1] = veca[1]-vecb[1]; + out[2] = veca[2]-vecb[2]; +} + +void _VectorAdd (vec3_t veca, vec3_t vecb, vec3_t out) +{ + out[0] = veca[0]+vecb[0]; + out[1] = veca[1]+vecb[1]; + out[2] = veca[2]+vecb[2]; +} + +void _VectorCopy (vec3_t in, vec3_t out) +{ + out[0] = in[0]; + out[1] = in[1]; + out[2] = in[2]; +} + +void CrossProduct (const vec3_t v1, const vec3_t v2, vec3_t cross) +{ + cross[0] = v1[1]*v2[2] - v1[2]*v2[1]; + cross[1] = v1[2]*v2[0] - v1[0]*v2[2]; + cross[2] = v1[0]*v2[1] - v1[1]*v2[0]; +} + +double sqrt(double x); + +float Length(const vec3_t v) +{ + int i; + float length = 0.0f; + + for (i=0 ; i< 3 ; i++) + length += v[i]*v[i]; + length = sqrt (length); // FIXME + + return length; +} + +float Distance(const vec3_t v1, const vec3_t v2) +{ + vec3_t d; + VectorSubtract(v2,v1,d); + return Length(d); +} + +float VectorNormalize (vec3_t v) +{ + float length, ilength; + + length = v[0]*v[0] + v[1]*v[1] + v[2]*v[2]; + length = sqrt (length); // FIXME + + if (length) + { + ilength = 1/length; + v[0] *= ilength; + v[1] *= ilength; + v[2] *= ilength; + } + + return length; + +} + +void VectorInverse (vec3_t v) +{ + v[0] = -v[0]; + v[1] = -v[1]; + v[2] = -v[2]; +} + +void VectorScale (const vec3_t in, vec_t scale, vec3_t out) +{ + out[0] = in[0]*scale; + out[1] = in[1]*scale; + out[2] = in[2]*scale; +} + + +int Q_log2(int val) +{ + int answer=0; + while (val>>=1) + answer++; + return answer; +} + +void VectorMatrix( vec3_t forward, vec3_t right, vec3_t up) +{ + vec3_t tmp; + + if (forward[0] == 0 && forward[1] == 0) + { + right[0] = 1; + right[1] = 0; + right[2] = 0; + up[0] = -forward[2]; + up[1] = 0; + up[2] = 0; + return; + } + + tmp[0] = 0; tmp[1] = 0; tmp[2] = 1.0; + CrossProduct( forward, tmp, right ); + VectorNormalize( right ); + CrossProduct( right, forward, up ); + VectorNormalize( up ); +} + + +void VectorAngles( const vec3_t forward, vec3_t angles ) +{ + float tmp, yaw, pitch; + + if (forward[1] == 0 && forward[0] == 0) + { + yaw = 0; + if (forward[2] > 0) + pitch = 90; + else + pitch = 270; + } + else + { + yaw = (atan2(forward[1], forward[0]) * 180 / M_PI); + if (yaw < 0) + yaw += 360; + + tmp = sqrt (forward[0]*forward[0] + forward[1]*forward[1]); + pitch = (atan2(forward[2], tmp) * 180 / M_PI); + if (pitch < 0) + pitch += 360; + } + + angles[0] = pitch; + angles[1] = yaw; + angles[2] = 0; +} + +// PLut addition +// Remap a value in the range [A,B] to [C,D]. +float RemapVal( float val, float A, float B, float C, float D) +{ + return C + (D - C) * (val - A) / (B - A); +} \ No newline at end of file diff --git a/pm_shared/pm_movevars.h b/pm_shared/pm_movevars.h new file mode 100644 index 0000000..ac8d11e --- /dev/null +++ b/pm_shared/pm_movevars.h @@ -0,0 +1,50 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// pm_movevars.h +#if !defined( PM_MOVEVARSH ) +#define PM_MOVEVARSH + +// movevars_t // Physics variables. +typedef struct movevars_s movevars_t; + +struct movevars_s +{ + float gravity; // Gravity for map + float stopspeed; // Deceleration when not moving + float maxspeed; // Max allowed speed + float spectatormaxspeed; + float accelerate; // Acceleration factor + float airaccelerate; // Same for when in open air + float wateraccelerate; // Same for when in water + float friction; + float edgefriction; // Extra friction near dropofs + float waterfriction; // Less in water + float entgravity; // 1.0 + float bounce; // Wall bounce value. 1.0 + float stepsize; // sv_stepsize; + float maxvelocity; // maximum server velocity. + float zmax; // Max z-buffer range (for GL) + float waveHeight; // Water wave height (for GL) + qboolean footsteps; // Play footstep sounds + char skyName[32]; // Name of the sky map + float rollangle; + float rollspeed; + float skycolor_r; // Sky color + float skycolor_g; // + float skycolor_b; // + float skyvec_x; // Sky vector + float skyvec_y; // + float skyvec_z; // + int features; // engine features that shared across network + int fog_settings; // Global fog settings (packed color+density) + float wateralpha; // World water alpha 1.0 - solid 0.0 - transparent +}; + +extern movevars_t movevars; + +#endif \ No newline at end of file diff --git a/pm_shared/pm_shared.cpp b/pm_shared/pm_shared.cpp new file mode 100644 index 0000000..d032e28 --- /dev/null +++ b/pm_shared/pm_shared.cpp @@ -0,0 +1,3134 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include +#include "mathlib.h" +#include "const.h" +#include "usercmd.h" +#include "pm_defs.h" +#include "pm_shared.h" +#include "pm_movevars.h" +#include "pm_debug.h" +#include // NULL +#include // sqrt +#include // strcpy +#include // atoi +#include // isspace +#include "com_model.h" +#include "stringlib.h" +#include "material.h" +#include "studio.h" + +#ifdef CLIENT_DLL + // Spectator Mode + int iJumpSpectator; + float vJumpOrigin[3]; + float vJumpAngles[3]; +#endif + +// g-cont. Damn! stupid valve coders!!! +#ifdef CLIENT_DLL +#include "cdll_int.h" +extern cl_enginefunc_t gEngfuncs; +#define AngleVectors (*gEngfuncs.pfnAngleVectors) +#else +#include "eiface.h" +extern enginefuncs_t g_engfuncs; +#define AngleVectors (*g_engfuncs.pfnAngleVectors) +#endif + +static int pm_shared_initialized = 0; + +playermove_t *pmove = NULL; + +// Ducking time +#define TIME_TO_DUCK 0.4 +#define VEC_DUCK_HULL_MIN -18 +#define VEC_DUCK_HULL_MAX 18 +#define VEC_DUCK_VIEW 12 +#define PM_DEAD_VIEWHEIGHT -8 +#define MAX_CLIMB_SPEED 200 +#define STUCK_MOVEUP 1 +#define STUCK_MOVEDOWN -1 +#define VEC_HULL_MIN -36 +#define VEC_HULL_MAX 36 +#define VEC_VIEW 28 + +#define STEP_MATERIAL 0 // custom step sounds +#define STEP_SLOSH 1 // shallow liquid puddle +#define STEP_WADE 2 // wading in liquid +#define STEP_LADDER 3 // climbing ladder + +#define PLAYER_FATAL_FALL_SPEED 1024 // approx 60 feet +#define PLAYER_MAX_SAFE_FALL_SPEED 580 // approx 20 feet +#define DAMAGE_FOR_FALL_SPEED (float) 100 / ( PLAYER_FATAL_FALL_SPEED - PLAYER_MAX_SAFE_FALL_SPEED )// damage per unit per second. +#define PLAYER_MIN_BOUNCE_SPEED 200 +#define PLAYER_FALL_PUNCH_THRESHHOLD (float)350 // won't punch player's screen/make scrape noise unless player falling at least this fast. + +#define PLAYER_LONGJUMP_SPEED 350 // how fast we longjump + +// double to float warning +//#pragma warning(disable : 4244) + +#define PITCH 0 // up / down +#define YAW 1 // left / right +#define ROLL 2 // fall over + +#define MAX_CLIENTS 32 + +#define CONTENTS_CURRENT_0 -9 +#define CONTENTS_CURRENT_90 -10 +#define CONTENTS_CURRENT_180 -11 +#define CONTENTS_CURRENT_270 -12 +#define CONTENTS_CURRENT_UP -13 +#define CONTENTS_CURRENT_DOWN -14 +#define CONTENTS_TRANSLUCENT -15 +#define CONTENTS_FLYFIELD -17 +#define CONTENTS_FLYFIELD_GRAVITY -18 +#define CONTENTS_FOG -19 + +static vec3_t rgv3tStuckTable[54]; +static int rgStuckLast[MAX_CLIENTS][2]; +static float rgStuckCheckTime[MAX_CLIENTS][2]; // Last time we did a full + +int g_onladder = 0; + +void PM_PlayStepSound( int step, float fvol ) +{ + static int iSkipStep = 0; + int irand; + + pmove->iStepLeft = !pmove->iStepLeft; + + if( !pmove->runfuncs ) + return; + + irand = pmove->RandomLong( 0, 1 ) + ( pmove->iStepLeft * 2 ); + + // FIXME mp_footsteps needs to be a movevar + if ( pmove->multiplayer && !pmove->movevars->footsteps ) + return; + + Vector hvel = pmove->velocity; + hvel.z = 0.0f; + + if( pmove->multiplayer && ( !g_onladder && hvel.Length() <= 160.0f )) + return; + + // irand - 0,1 for right foot, 2,3 for left foot + // used to alternate left and right foot + // FIXME, move to player state + + switch ( step ) + { + case STEP_SLOSH: + switch( irand ) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_slosh1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_slosh3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_slosh2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_slosh4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_WADE: + if ( iSkipStep == 0 ) + { + iSkipStep++; + break; + } + + if ( iSkipStep++ == 3 ) + { + iSkipStep = 0; + } + + switch( irand ) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + case STEP_LADDER: + switch( irand ) + { + // right foot + case 0: pmove->PM_PlaySound( CHAN_BODY, "player/pl_ladder1.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 1: pmove->PM_PlaySound( CHAN_BODY, "player/pl_ladder3.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + // left foot + case 2: pmove->PM_PlaySound( CHAN_BODY, "player/pl_ladder2.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + case 3: pmove->PM_PlaySound( CHAN_BODY, "player/pl_ladder4.wav", fvol, ATTN_NORM, 0, PITCH_NORM ); break; + } + break; + default: + if( pmove->pMaterial ) + { + irand = Q_min( irand, pmove->chtexturetype - 1 ); + if( irand < 0 ) irand = 0; // to prevent to get bad value + pmove->PM_PlaySound( CHAN_BODY, pmove->pMaterial->step_sounds[irand], fvol, ATTN_NORM, 0, PITCH_NORM ); + } + break; + } +} + +/* +==================== +PM_CatagorizeTextureType + +Determine texture info for the texture we are standing on. +==================== +*/ +void PM_CatagorizeTextureType( void ) +{ + Vector start = pmove->origin; + Vector end = pmove->origin; + + // Straight down + end.z -= 64; + + // Fill in default values, just in case. + pmove->pMaterial = NULL; + pmove->chtexturetype = 0; + + if( pmove->onground == -1 ) return; // not on ground + + physent_t *pe = &pmove->physents[pmove->onground]; + + if( pe->solid == SOLID_BSP || pe->movetype == MOVETYPE_PUSHSTEP ) + { + pmove->pMaterial = COM_MatDefFromSurface( pmove->PM_TraceSurface( pmove->onground, start, end ), pmove->origin ); + } + else if( pe->solid == SOLID_CUSTOM ) + { + pmtrace_t *tr = pmove->PM_TraceLine( start, end, PM_STUDIO_IGNORE, 2, -1 ); + + if( tr->ent == pmove->onground && tr->surf ) + pmove->pMaterial = tr->surf->effects; + } + + if( !pmove->pMaterial ) return; + + // count sounds + for( pmove->chtexturetype = 0; pmove->pMaterial->step_sounds[pmove->chtexturetype] != NULL; pmove->chtexturetype++ ); +} + +void PM_UpdateStepSound( void ) +{ + int fWalking; + float fvol; + Vector knee; + Vector feet; + Vector center; + float height; + float speed; + float velrun; + float velwalk; + float flduck; + int fLadder; + int step; + + if ( pmove->movetype == MOVETYPE_NOCLIP ) + return; + + if ( pmove->flTimeStepSound > 0 ) + return; + + if ( pmove->flags & FL_FROZEN ) + return; + + PM_CatagorizeTextureType(); + + speed = pmove->velocity.Length(); + + // determine if we are on a ladder + fLadder = ( pmove->movetype == MOVETYPE_FLY );// IsOnLadder(); + + // UNDONE: need defined numbers for run, walk, crouch, crouch run velocities!!!! + if ( ( pmove->flags & FL_DUCKING) || fLadder ) + { + velwalk = 60; // These constants should be based on cl_movespeedkey * cl_forwardspeed somehow + velrun = 80; // UNDONE: Move walking to server + flduck = 100; + } + else + { + velwalk = 80; + velrun = 160; + flduck = 0; + } + + // If we're on a ladder or on the ground, and we're moving fast enough, + // play step sound. Also, if pmove->flTimeStepSound is zero, get the new + // sound right away - we just started moving in new level. + if ( (fLadder || ( pmove->onground != -1 ) ) && ( pmove->velocity.Length() > 0.0 ) && ( speed >= velwalk || !pmove->flTimeStepSound ) ) + { + fWalking = speed < velrun; + + VectorCopy( pmove->origin, center ); + VectorCopy( pmove->origin, knee ); + VectorCopy( pmove->origin, feet ); + + height = pmove->player_maxs[ pmove->usehull ][ 2 ] - pmove->player_mins[ pmove->usehull ][ 2 ]; + + knee[2] = pmove->origin[2] - 0.3 * height; + feet[2] = pmove->origin[2] - 0.5 * height; + + // find out what we're stepping in or on... + if (fLadder) + { + step = STEP_LADDER; + fvol = 0.35; + pmove->flTimeStepSound = 350; + } + else if ( pmove->PM_PointContents ( knee, NULL ) == CONTENTS_WATER ) + { + step = STEP_WADE; + fvol = 0.65; + pmove->flTimeStepSound = 600; + } + else if ( pmove->PM_PointContents ( feet, NULL ) == CONTENTS_WATER ) + { + step = STEP_SLOSH; + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + } + else + { + // find texture under player, if different from current texture, + // get material type + step = STEP_MATERIAL; + fvol = fWalking ? 0.2 : 0.5; + pmove->flTimeStepSound = fWalking ? 400 : 300; + } + + pmove->flTimeStepSound += flduck; // slower step time if ducking + + // play the sound + // 35% volume if ducking + if ( pmove->flags & FL_DUCKING ) + { + fvol *= 0.35; + } + + PM_PlayStepSound( step, fvol ); + } +} + +/* +================ +PM_AddToTouched + +Add's the trace result to touch list, if contact is not already in list. +================ +*/ +qboolean PM_AddToTouched(pmtrace_t tr, const Vector &impactvelocity) +{ + int i; + + for (i = 0; i < pmove->numtouch; i++) + { + if (pmove->touchindex[i].ent == tr.ent) + break; + } + if (i != pmove->numtouch) // Already in list. + return false; + + VectorCopy( impactvelocity, tr.deltavelocity ); + + if (pmove->numtouch >= MAX_PHYSENTS) + pmove->Con_DPrintf("Too many entities were touched!\n"); + + pmove->touchindex[pmove->numtouch++] = tr; + return true; +} + +/* +================ +PM_CheckVelocity + +See if the player has a bogus velocity value. +================ +*/ +void PM_CheckVelocity () +{ + int i; + +// +// bound velocity +// + for (i=0 ; i<3 ; i++) + { + // See if it's bogus. + if (IS_NAN(pmove->velocity[i])) + { + pmove->Con_Printf ("PM Got a NaN velocity %i\n", i); + pmove->velocity[i] = 0; + } + if (IS_NAN(pmove->origin[i])) + { + pmove->Con_Printf ("PM Got a NaN origin on %i\n", i); + pmove->origin[i] = 0; + } + + // Bound it. + if (pmove->velocity[i] > pmove->movevars->maxvelocity) + { + pmove->Con_DPrintf ("PM Got a velocity too high on %i\n", i); + pmove->velocity[i] = pmove->movevars->maxvelocity; + } + else if (pmove->velocity[i] < -pmove->movevars->maxvelocity) + { + pmove->Con_DPrintf ("PM Got a velocity too low on %i\n", i); + pmove->velocity[i] = -pmove->movevars->maxvelocity; + } + } +} + +/* +================== +PM_ClipVelocity + +Slide off of the impacting object +returns the blocked flags: +0x01 == floor +0x02 == step / wall +================== +*/ +int PM_ClipVelocity( const Vector &in, const Vector &normal, Vector &out, float overbounce ) +{ + float backoff; + float change; + float angle; + int i, blocked; + + angle = normal[ 2 ]; + + blocked = 0x00; // Assume unblocked. + if (angle > 0) // If the plane that is blocking us has a positive z component, then assume it's a floor. + blocked |= 0x01; // + if (!angle) // If the plane has no Z, it is vertical (wall/step) + blocked |= 0x02; // + + // Determine how far along plane to slide based on incoming direction. + // Scale by overbounce factor. + backoff = DotProduct (in, normal) * overbounce; + + for (i=0 ; i<3 ; i++) + { + change = normal[i]*backoff; + out[i] = in[i] - change; + // If out velocity is too small, zero it out. + if (out[i] > -STOP_EPSILON && out[i] < STOP_EPSILON) + out[i] = 0; + } + + // Return blocking flags. + return blocked; +} + +void PM_AddCorrectGravity () +{ + float ent_gravity; + + if ( pmove->waterjumptime ) + return; + + if (pmove->gravity) + ent_gravity = pmove->gravity; + else + ent_gravity = 1.0; + + // Add gravity so they'll be in the correct position during movement + // yes, this 0.5 looks wrong, but it's not. + pmove->velocity[2] -= (ent_gravity * pmove->movevars->gravity * 0.5 * pmove->frametime ); + pmove->velocity[2] += pmove->basevelocity[2] * pmove->frametime; + pmove->basevelocity[2] = 0; + + PM_CheckVelocity(); +} + + +void PM_FixupGravityVelocity () +{ + float ent_gravity; + + if ( pmove->waterjumptime ) + return; + + if (pmove->gravity) + ent_gravity = pmove->gravity; + else + ent_gravity = 1.0; + + // Get the correct velocity for the end of the dt + pmove->velocity[2] -= (ent_gravity * pmove->movevars->gravity * pmove->frametime * 0.5 ); + + PM_CheckVelocity(); +} + +/* +============ +PM_FlyMove + +The basic solid body movement clip that slides along multiple planes +============ +*/ +int PM_FlyMove (void) +{ + int bumpcount, numbumps; + Vector dir; + float d; + int numplanes; + Vector planes[MAX_CLIP_PLANES]; + Vector primal_velocity, original_velocity; + Vector new_velocity; + int i, j; + pmtrace_t trace; + Vector end; + float time_left, allFraction; + int blocked; + + numbumps = 4; // Bump up to four times + + blocked = 0; // Assume not blocked + numplanes = 0; // and not sliding along any planes + VectorCopy (pmove->velocity, original_velocity); // Store original velocity + VectorCopy (pmove->velocity, primal_velocity); + + allFraction = 0; + time_left = pmove->frametime; // Total time for this movement operation. + + for (bumpcount=0 ; bumpcountvelocity == g_vecZero ) + break; + + // Assume we can move all the way from the current origin to the + // end point. + for (i=0 ; i<3 ; i++) + end[i] = pmove->origin[i] + time_left * pmove->velocity[i]; + + // See if we can make it from origin to end point. + trace = pmove->PM_PlayerTrace (pmove->origin, end, PM_NORMAL, -1 ); + + allFraction += trace.fraction; + + // If we started in a solid object, or we were in solid space + // the whole way, zero out our velocity and return that we + // are blocked by floor and wall. + if (trace.allsolid) + { + // entity is trapped in another solid + pmove->velocity = g_vecZero; + pmove->flags |= FL_STUCKED; + return 4; + } + + // If we moved some portion of the total distance, then + // copy the end position into the pmove->origin and + // zero the plane counter. + if (trace.fraction > 0) + { + // actually covered some distance + VectorCopy (trace.endpos, pmove->origin); + VectorCopy (pmove->velocity, original_velocity); + numplanes = 0; + } + + // If we covered the entire distance, we are done + // and can return. + if (trace.fraction == 1) + break; // moved the entire distance + + //if (!trace.ent) + // Sys_Error ("PM_PlayerTrace: !trace.ent"); + + // Save entity that blocked us (since fraction was < 1.0) + // for contact + // Add it if it's not already in the list!!! + PM_AddToTouched(trace, pmove->velocity); + + // If the plane we hit has a high z component in the normal, then + // it's probably a floor + if (trace.plane.normal[2] > 0.7) + { + blocked |= 1; // floor + } + // If the plane has a zero z component in the normal, then it's a + // step or wall + if (!trace.plane.normal[2]) + { + blocked |= 2; // step / wall + //Con_DPrintf("Blocked by %i\n", trace.ent); + } + + // Reduce amount of pmove->frametime left by total time left * fraction + // that we covered. + time_left -= time_left * trace.fraction; + + // Did we run out of planes to clip against? + if (numplanes >= MAX_CLIP_PLANES) + { + // this shouldn't really happen + // Stop our movement if so. + pmove->velocity = g_vecZero; + //Con_DPrintf("Too many planes 4\n"); + break; + } + + // Set up next clipping plane + VectorCopy (trace.plane.normal, planes[numplanes]); + numplanes++; +// + +// modify original_velocity so it parallels all of the clip planes +// + if ( pmove->movetype == MOVETYPE_WALK && ((pmove->onground == -1) || (pmove->friction != 1)) ) // relfect player velocity + { + for ( i = 0; i < numplanes; i++ ) + { + if ( planes[i][2] > 0.7 ) + {// floor or slope + PM_ClipVelocity( original_velocity, planes[i], new_velocity, 1 ); + VectorCopy( new_velocity, original_velocity ); + } + else + PM_ClipVelocity( original_velocity, planes[i], new_velocity, 1.0 + pmove->movevars->bounce * (1-pmove->friction) ); + } + + VectorCopy( new_velocity, pmove->velocity ); + VectorCopy( new_velocity, original_velocity ); + } + else + { + for (i=0 ; ivelocity, + 1); + for (j=0 ; jvelocity, planes[j]) < 0) + break; // not ok + } + if (j == numplanes) // Didn't have to clip, so we're ok + break; + } + + // Did we go all the way through plane set + if (i != numplanes) + { + // go along this plane + // pmove->velocity is set in clipping call, no need to set again. + ; + } + else + { // go along the crease + if (numplanes != 2) + { + //Con_Printf ("clip velocity, numplanes == %i\n",numplanes); + pmove->velocity = g_vecZero; + //Con_DPrintf("Trapped 4\n"); + + break; + } + dir = CrossProduct (planes[0], planes[1]); + d = DotProduct (dir, pmove->velocity); + pmove->velocity = dir * d; + } + + // + // if original velocity is against the original velocity, stop dead + // to avoid tiny occilations in sloping corners + // + if (DotProduct (pmove->velocity, primal_velocity) <= 0) + { + //Con_DPrintf("Back\n"); + pmove->velocity = g_vecZero; + break; + } + } + } + + if( allFraction == 0.0f ) + { + pmove->velocity = g_vecZero; +// pmove->Con_Printf( "Don't stick\n" ); + } + + return blocked; +} + +/* +============== +PM_Accelerate +============== +*/ +void PM_Accelerate (const Vector &wishdir, float wishspeed, float accel) +{ + int i; + float addspeed, accelspeed, currentspeed; + + // Dead player's don't accelerate + if (pmove->dead) + return; + + // If waterjumping, don't accelerate + if (pmove->waterjumptime) + return; + + // See if we are changing direction a bit + currentspeed = DotProduct (pmove->velocity, wishdir); + + // Reduce wishspeed by the amount of veer. + addspeed = wishspeed - currentspeed; + + // If not going to add any speed, done. + if (addspeed <= 0) + return; + + // Determine amount of accleration. + accelspeed = accel * pmove->frametime * wishspeed * pmove->friction; + + // Cap at addspeed + if (accelspeed > addspeed) + accelspeed = addspeed; + + // Adjust velocity. + for (i=0 ; i<3 ; i++) + { + pmove->velocity[i] += accelspeed * wishdir[i]; + } +} + +/* +===================== +PM_WalkMove + +Only used by players. Moves along the ground when player is a MOVETYPE_WALK. +====================== +*/ +void PM_WalkMove () +{ + int clip; + int oldonground; + int i; + + Vector wishvel; + float spd; + float fmove, smove; + Vector wishdir; + float wishspeed; + + Vector dest, start; + Vector original, originalvel; + Vector down, downvel; + float downdist, updist; + + pmtrace_t trace; + + qboolean bIsSprint; + float iSprintFactor = 1.25; + + if ( (pmove->cmd.buttons & IN_RUN) && (pmove->cmd.buttons & IN_FORWARD) ) + { + pmove->oldbuttons |= IN_RUN; + bIsSprint = true; + } + else + { + pmove->oldbuttons &= ~IN_RUN; + bIsSprint = false; + } + + // Copy movement amounts + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + + // Zero out z components of movement vectors + pmove->forward[2] = 0; + pmove->right[2] = 0; + + pmove->forward = pmove->forward.Normalize(); // Normalize remainder of vectors. + pmove->right = pmove->right.Normalize(); // + + for( i = 0; i < 2; i++ ) // Determine x and y parts of velocity + { + if (bIsSprint && (!( pmove->flags & FL_DUCKING))) + wishvel[i] = pmove->forward[i]*(fmove * iSprintFactor) + pmove->right[i] * smove; + else + wishvel[i] = pmove->forward[i]*fmove + pmove->right[i]*smove; + } + + wishvel[2] = 0; // Zero out z part of velocity + + wishdir = wishvel; // determine maginitude of speed of move + wishspeed = wishdir.Length(); + wishdir = wishdir.Normalize(); + + // buz: clientmaxspeed is now means speed factor to wishspeed - + // from 0 to 100 percent +// pmove->Con_Printf("wishspeed before: %f\n", wishspeed); + wishspeed = wishspeed * pmove->clientmaxspeed / (float)100; +// pmove->Con_Printf("wishspeed: %f, maxspeed: %f\n", wishspeed, pmove->clientmaxspeed); + +// +// Clamp to server defined max speed +// + if( bIsSprint && (!( pmove->flags & FL_DUCKING)) ) + { + if (wishspeed > pmove->maxspeed + iSprintFactor) + { + wishvel *= (( pmove->maxspeed + iSprintFactor ) / wishspeed ); + wishspeed = pmove->maxspeed + iSprintFactor; + } + } + else + { + if (wishspeed > pmove->maxspeed) + { + wishvel *= (pmove->maxspeed / wishspeed); + wishspeed = pmove->maxspeed; + } + } + + // Set pmove velocity + pmove->velocity[2] = 0; + PM_Accelerate (wishdir, wishspeed, pmove->movevars->accelerate); + pmove->velocity[2] = 0; + + // Add in any base velocity to the current velocity. + pmove->velocity += pmove->basevelocity; + + spd = pmove->velocity.Length(); + + if (spd < 1.0f) + { + pmove->velocity = g_vecZero; + return; + } + + // If we are not moving, do nothing + //if (!pmove->velocity[0] && !pmove->velocity[1] && !pmove->velocity[2]) + // return; + + oldonground = pmove->onground; + +// first try just moving to the destination + dest[0] = pmove->origin[0] + pmove->velocity[0]*pmove->frametime; + dest[1] = pmove->origin[1] + pmove->velocity[1]*pmove->frametime; + dest[2] = pmove->origin[2]; + + // first try moving directly to the next spot + VectorCopy (dest, start); + trace = pmove->PM_PlayerTrace (pmove->origin, dest, PM_NORMAL, -1 ); + // If we made it all the way, then copy trace end + // as new player position. + if (trace.fraction == 1) + { + VectorCopy (trace.endpos, pmove->origin); + return; + } + + if (oldonground == -1 && // Don't walk up stairs if not on ground. + (pmove->waterlevel == 0 || pmove->watertype == CONTENTS_FOG)) + return; + + if (pmove->waterjumptime) // If we are jumping out of water, don't do anything more. + return; + + // Try sliding forward both on ground and up 16 pixels + // take the move that goes farthest + VectorCopy (pmove->origin, original); // Save out original pos & + VectorCopy (pmove->velocity, originalvel); // velocity. + + // Slide move + clip = PM_FlyMove (); + + // Copy the results out + VectorCopy (pmove->origin , down); + VectorCopy (pmove->velocity, downvel); + + // Reset original values. + VectorCopy (original, pmove->origin); + + VectorCopy (originalvel, pmove->velocity); + + // Start out up one stair height + VectorCopy (pmove->origin, dest); + dest[2] += pmove->movevars->stepsize; + + trace = pmove->PM_PlayerTrace (pmove->origin, dest, PM_NORMAL, -1 ); + // If we started okay and made it part of the way at least, + // copy the results to the movement start position and then + // run another move try. + if (!trace.startsolid && !trace.allsolid) + { + VectorCopy (trace.endpos, pmove->origin); + } + +// slide move the rest of the way. + clip = PM_FlyMove (); + +// Now try going back down from the end point +// press down the stepheight + VectorCopy (pmove->origin, dest); + dest[2] -= pmove->movevars->stepsize; + + trace = pmove->PM_PlayerTrace (pmove->origin, dest, PM_NORMAL, -1 ); + + // If we are not on the ground any more then + // use the original movement attempt + if ( trace.plane.normal[2] < 0.7) + goto usedown; + // If the trace ended up in empty space, copy the end + // over to the origin. + if (!trace.startsolid && !trace.allsolid) + { + VectorCopy (trace.endpos, pmove->origin); + } + // Copy this origion to up. + VectorCopy (pmove->origin, pmove->up); + + // decide which one went farther + downdist = (down[0] - original[0])*(down[0] - original[0]) + + (down[1] - original[1])*(down[1] - original[1]); + updist = (pmove->up[0] - original[0])*(pmove->up[0] - original[0]) + + (pmove->up[1] - original[1])*(pmove->up[1] - original[1]); + + if (downdist > updist) + { +usedown: + VectorCopy (down , pmove->origin); + VectorCopy (downvel, pmove->velocity); + } else // copy z value from slide move + pmove->velocity[2] = downvel[2]; + +} + +/* +================== +PM_Friction + +Handles both ground friction and water friction +================== +*/ +void PM_Friction (void) +{ + float *vel; + float speed, newspeed, control; + float friction; + float drop; + Vector newvel; + + // If we are in water jump cycle, don't apply friction + if (pmove->waterjumptime) + return; + + // Get velocity + vel = pmove->velocity; + + // Calculate speed + speed = sqrt(vel[0]*vel[0] +vel[1]*vel[1] + vel[2]*vel[2]); + + // If too slow, return + if (speed < 0.1f) + { + return; + } + + drop = 0; + +// apply ground friction + if (pmove->onground != -1) // On an entity that is the ground + { + Vector start, stop; + pmtrace_t trace; + + start[0] = stop[0] = pmove->origin[0] + vel[0]/speed*16; + start[1] = stop[1] = pmove->origin[1] + vel[1]/speed*16; + start[2] = pmove->origin[2] + pmove->player_mins[pmove->usehull][2]; + stop[2] = start[2] - 34; + + trace = pmove->PM_PlayerTrace (start, stop, PM_NORMAL, -1 ); + + if (trace.fraction == 1.0) + friction = pmove->movevars->friction*pmove->movevars->edgefriction; + else + friction = pmove->movevars->friction; + + // Grab friction value. + //friction = pmove->movevars->friction; + + friction *= pmove->friction; // player friction? + + // Bleed off some speed, but if we have less than the bleed + // threshhold, bleed the theshold amount. + control = (speed < pmove->movevars->stopspeed) ? + pmove->movevars->stopspeed : speed; + // Add the amount to t'he drop amount. + drop += control*friction*pmove->frametime; + } + +// apply water friction +// if (pmove->waterlevel) +// drop += speed * pmove->movevars->waterfriction * waterlevel * pmove->frametime; + +// scale the velocity + newspeed = speed - drop; + if (newspeed < 0) + newspeed = 0; + + // Determine proportion of old speed we are using. + newspeed /= speed; + + // Adjust velocity according to proportion. + newvel[0] = vel[0] * newspeed; + newvel[1] = vel[1] * newspeed; + newvel[2] = vel[2] * newspeed; + + VectorCopy( newvel, pmove->velocity ); +} + +void PM_AirAccelerate (const Vector &wishdir, float wishspeed, float accel) +{ + int i; + float addspeed, accelspeed, currentspeed, wishspd = wishspeed; + + if (pmove->dead) + return; + if (pmove->waterjumptime) + return; + + // Cap speed + + if (wishspd > 30) + wishspd = 30; + // Determine veer amount + currentspeed = DotProduct (pmove->velocity, wishdir); + // See how much to add + addspeed = wishspd - currentspeed; + // If not adding any, done. + if (addspeed <= 0) + return; + // Determine acceleration speed after acceleration + + accelspeed = accel * wishspeed * pmove->frametime * pmove->friction; + // Cap it + if (accelspeed > addspeed) + accelspeed = addspeed; + + // Adjust pmove vel. + for (i=0 ; i<3 ; i++) + { + pmove->velocity[i] += accelspeed*wishdir[i]; + } +} + +/* +=================== +PM_WaterMove + +=================== +*/ +void PM_WaterMove (void) +{ + int i; + Vector wishvel; + float wishspeed; + Vector wishdir; + Vector start, dest; + Vector temp; + pmtrace_t trace; + + float speed, newspeed, addspeed, accelspeed; + +// +// user intentions +// + for (i=0 ; i<3 ; i++) + wishvel[i] = pmove->forward[i]*pmove->cmd.forwardmove + pmove->right[i]*pmove->cmd.sidemove; + + // Sinking after no other movement occurs + if (!pmove->cmd.forwardmove && !pmove->cmd.sidemove && !pmove->cmd.upmove && pmove->watertype != CONTENTS_FLYFIELD) //LRC + wishvel[2] -= 60; // drift towards bottom + else // Go straight up by upmove amount. + wishvel[2] += pmove->cmd.upmove; + + // Copy it over and determine speed + wishdir = wishvel; + wishspeed = wishdir.Length(); + wishdir = wishdir.Normalize(); + + // buz: clientmaxspeed is now means speed factor to wishspeed - + // from 0 to 100 percent + wishspeed = wishspeed * pmove->clientmaxspeed / (float)100; + + // Cap speed. + if (wishspeed > pmove->maxspeed) + { + wishvel *= ( pmove->maxspeed / wishspeed ); + wishspeed = pmove->maxspeed; + } + // Slow us down a bit. + wishspeed *= 0.8; + + pmove->velocity += pmove->basevelocity; +// Water friction + VectorCopy(pmove->velocity, temp); + speed = pmove->velocity.Length(); + if (speed) + { + newspeed = speed - pmove->frametime * speed * pmove->movevars->friction * pmove->friction; + + if (newspeed < 0) + newspeed = 0; + pmove->velocity *= ( newspeed / speed ); + } + else + newspeed = 0; + +// +// water acceleration +// + if ( wishspeed < 0.1f ) + { + return; + } + + addspeed = wishspeed - newspeed; + if (addspeed > 0) + { + + wishvel = wishvel.Normalize(); + accelspeed = pmove->movevars->accelerate * wishspeed * pmove->frametime * pmove->friction; + if (accelspeed > addspeed) + accelspeed = addspeed; + + for (i = 0; i < 3; i++) + pmove->velocity[i] += accelspeed * wishvel[i]; + } + + // Now move + // assume it is a stair or a slope, so press down from stepheight above + dest = pmove->origin + pmove->velocity * pmove->frametime; + start = dest; + + start[2] += pmove->movevars->stepsize + 1; + trace = pmove->PM_PlayerTrace (start, dest, PM_NORMAL, -1 ); + if (!trace.startsolid && !trace.allsolid) // FIXME: check steep slope? + { + // walked up the step, so just keep result and exit + VectorCopy (trace.endpos, pmove->origin); + return; + } + + // Try moving straight along out normal path. + PM_FlyMove (); +} + + +/* +=================== +PM_AirMove + +=================== +*/ +void PM_AirMove (void) +{ + int i; + Vector wishvel; + float fmove, smove; + Vector wishdir; + float wishspeed; + + // Copy movement amounts + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + + // Zero out z components of movement vectors + pmove->forward[2] = 0; + pmove->right[2] = 0; + + // Renormalize + pmove->forward = pmove->forward.Normalize(); + pmove->right = pmove->right.Normalize(); + + // Determine x and y parts of velocity + for (i=0 ; i<2 ; i++) + { + wishvel[i] = pmove->forward[i]*fmove + pmove->right[i]*smove; + } + // Zero out z part of velocity + wishvel[2] = 0; + + // Determine maginitude of speed of move + wishdir = wishvel; // determine maginitude of speed of move + wishspeed = wishdir.Length(); + wishdir = wishdir.Normalize(); + + // buz: clientmaxspeed is now means speed factor to wishspeed - + // from 0 to 100 percent + wishspeed = wishspeed * pmove->clientmaxspeed / (float)100; + + // Clamp to server defined max speed + if (wishspeed > pmove->maxspeed) + { + wishvel *= ( pmove->maxspeed / wishspeed ); + wishspeed = pmove->maxspeed; + } + + PM_AirAccelerate (wishdir, wishspeed, pmove->movevars->airaccelerate); + + // Add in any base velocity to the current velocity. + pmove->velocity += pmove->basevelocity; + + PM_FlyMove (); +} + +qboolean PM_InWater( void ) +{ + return ( pmove->waterlevel > 1 && pmove->watertype != CONTENTS_FOG ); +} + +/* +============= +PM_CheckWater + +Sets pmove->waterlevel and pmove->watertype values. +============= +*/ +qboolean PM_CheckWater () +{ + Vector point; + int cont; + int truecont; + float height; + float heightover2; + + // Pick a spot just above the players feet. + point[0] = pmove->origin[0] + (pmove->player_mins[pmove->usehull][0] + pmove->player_maxs[pmove->usehull][0]) * 0.5; + point[1] = pmove->origin[1] + (pmove->player_mins[pmove->usehull][1] + pmove->player_maxs[pmove->usehull][1]) * 0.5; + point[2] = pmove->origin[2] + pmove->player_mins[pmove->usehull][2] + 1; + + // Assume that we are not in water at all. + pmove->waterlevel = 0; + pmove->watertype = CONTENTS_EMPTY; + + // Grab point contents. + cont = pmove->PM_PointContents (point, &truecont ); + // Are we under water? (not solid and not empty?) + if ((cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT ) || (cont >= CONTENTS_FOG && cont <= CONTENTS_FLYFIELD)) + { + // Set water type + pmove->watertype = cont; + + // We are at least at level one + pmove->waterlevel = 1; + + height = (pmove->player_mins[pmove->usehull][2] + pmove->player_maxs[pmove->usehull][2]); + heightover2 = height * 0.5; + + // Now check a point that is at the player hull midpoint. + point[2] = pmove->origin[2] + heightover2; + cont = pmove->PM_PointContents (point, NULL ); + // If that point is also under water... + if ((cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT ) || (cont >= CONTENTS_FOG && cont <= CONTENTS_FLYFIELD)) + { + // Set a higher water level. + pmove->waterlevel = 2; + + // Now check the eye position. (view_ofs is relative to the origin) + point[2] = pmove->origin[2] + pmove->view_ofs[2]; + + cont = pmove->PM_PointContents (point, NULL ); + if ((cont <= CONTENTS_WATER && cont > CONTENTS_TRANSLUCENT ) || cont == CONTENTS_FOG) // Flyfields never cover the eyes + pmove->waterlevel = 3; // In over our eyes + } + + // Adjust velocity based on water current, if any. + if ( ( truecont <= CONTENTS_CURRENT_0 ) && + ( truecont >= CONTENTS_CURRENT_DOWN ) ) + { + // The deeper we are, the stronger the current. + static Vector current_table[] = + { + Vector( 1, 0, 0 ), + Vector( 0, 1, 0 ), + Vector(-1, 0, 0 ), + Vector( 0, -1, 0 ), + Vector( 0, 0, 1 ), + Vector( 0, 0, -1 ) + }; + + pmove->basevelocity = pmove->basevelocity + current_table[CONTENTS_CURRENT_0 - truecont] * (pmove->waterlevel * 50.0f); + } + } + + return pmove->waterlevel > 1; +} + +/* +============= +PM_CatagorizePosition +============= +*/ +void PM_CatagorizePosition (void) +{ + Vector point; + pmtrace_t tr; + +// if the player hull point one unit down is solid, the player +// is on ground + + if( pmove->movetype == MOVETYPE_NOCLIP ) + return; + +// see if standing on something solid + + // Doing this before we move may introduce a potential latency in water detection, but + // doing it after can get us stuck on the bottom in water if the amount we move up + // is less than the 1 pixel 'threshold' we're about to snap to. Also, we'll call + // this several times per frame, so we really need to avoid sticking to the bottom of + // water on each call, and the converse case will correct itself if called twice. + PM_CheckWater(); + + point[0] = pmove->origin[0]; + point[1] = pmove->origin[1]; + point[2] = pmove->origin[2] - 2; + + if (pmove->velocity[2] > 180) // Shooting up really fast. Definitely not on ground. + { + pmove->onground = -1; + } + else + { + // Try and move down. + tr = pmove->PM_PlayerTrace (pmove->origin, point, PM_NORMAL, -1 ); + // If we hit a steep plane, we are not on ground + if ( tr.plane.normal[2] < 0.7) + pmove->onground = -1; // too steep + else + pmove->onground = tr.ent; // Otherwise, point to index of ent under us. + + // If we are on something... + if (pmove->onground != -1) + { + // Then we are not in water jump sequence + pmove->waterjumptime = 0; + // If we could make the move, drop us down that 1 pixel + if (pmove->waterlevel < 2 && !tr.startsolid && !tr.allsolid) + VectorCopy (tr.endpos, pmove->origin); + } + + // Standing on an entity other than the world + if (tr.ent > 0) // So signal that we are touching something. + { + PM_AddToTouched(tr, pmove->velocity); + } + } +} + +/* +================= +PM_GetRandomStuckOffsets + +When a player is stuck, it's costly to try and unstick them +Grab a test offset for the player based on a passed in index +================= +*/ +int PM_GetRandomStuckOffsets( int nIndex, int server, Vector &offset ) +{ + // last time we did a full + int idx = rgStuckLast[nIndex][server]++; + offset = rgv3tStuckTable[idx % 54]; + + return (idx % 54); +} + +void PM_ResetStuckOffsets(int nIndex, int server) +{ + rgStuckLast[nIndex][server] = 0; +} + +/* +================= +NudgePosition + +If pmove->origin is in a solid position, +try nudging slightly on all axis to +allow for the cut precision of the net coordinates +================= +*/ +#define PM_CHECKSTUCK_MINTIME 0.05 // Don't check again too quickly. + +int PM_CheckStuck (void) +{ + pmtrace_t traceresult; + + // If position is okay, exit + int hitent = pmove->PM_TestPlayerPosition( pmove->origin, &traceresult ); + + if( hitent == -1 ) + { + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); + return 0; + } + + Vector base = pmove->origin; + Vector offset, test; + + // Deal with precision error in network or stuck with physics body + if( !pmove->server || pmove->physents[hitent].solid == SOLID_CUSTOM ) + { + // world, BSP model or custom convex hull + if(( !hitent == 0 ) || pmove->physents[hitent].model || pmove->physents[hitent].solid == SOLID_CUSTOM ) + { + int nReps = 0; + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); + + do + { + int i = PM_GetRandomStuckOffsets( pmove->player_index, pmove->server, offset ); + test = base + offset; + + if( pmove->PM_TestPlayerPosition( test, &traceresult ) == -1 ) + { + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); +// pmove->Con_Printf( "Unstick by %i\n", i ); + pmove->origin = test; + + return 0; + } + } while( ++nReps < 54 ); + } + } + + // only an issue on the client. + int idx = (pmove->server) ? 0 : 1; + float fTime = pmove->Sys_FloatTime(); + + // too soon? + if( rgStuckCheckTime[pmove->player_index][idx] >= ( fTime - PM_CHECKSTUCK_MINTIME )) + { + // can't move we're stuck! + pmove->flags |= FL_STUCKED; + return 1; + } + + rgStuckCheckTime[pmove->player_index][idx] = fTime; + pmove->PM_StuckTouch( hitent, &traceresult ); + + int i = PM_GetRandomStuckOffsets( pmove->player_index, pmove->server, offset ); + test = base + offset; + + if(( hitent = pmove->PM_TestPlayerPosition( test, NULL )) == -1 ) + { + PM_ResetStuckOffsets( pmove->player_index, pmove->server ); + pmove->origin = test; + + return 0; + } + + // If player is flailing while stuck in another player ( should never happen ), then see + // if we can't "unstick" them forceably. + if( pmove->cmd.buttons & ( IN_JUMP|IN_DUCK|IN_ATTACK ) && pmove->physents[hitent].player ) + { + for( float z = 0; z <= (4 * pmove->movevars->stepsize); z += pmove->movevars->stepsize ) + { + for( float x = -8.0f; x <= 8.0f; x += 8.0f ) + { + for( float y = -8.0f; y <= 8.0f; y += 8.0f ) + { + test = base; + test.x += x; + test.y += y; + test.z += z; + + if( pmove->PM_TestPlayerPosition( test, NULL ) == -1 ) + { + pmove->origin = test; + return 0; + } + } + } + } + } + + // can't move we're stuck! + pmove->flags |= FL_STUCKED; + + return 1; +} + +/* +=============== +PM_SpectatorMove +=============== +*/ +void PM_SpectatorMove (void) +{ + float speed, drop, friction, control, newspeed; + //float accel; + float currentspeed, addspeed, accelspeed; + int i; + Vector wishvel; + float fmove, smove; + Vector wishdir; + float wishspeed; + // this routine keeps track of the spectators psoition + // there a two different main move types : track player or moce freely (OBS_ROAMING) + // doesn't need excate track position, only to generate PVS, so just copy + // targets position and real view position is calculated on client (saves server CPU) + + if ( pmove->iuser1 == OBS_ROAMING) + { + +#ifdef CLIENT_DLL + // jump only in roaming mode + if ( iJumpSpectator ) + { + VectorCopy( vJumpOrigin, pmove->origin ); + VectorCopy( vJumpAngles, pmove->angles ); + pmove->velocity = g_vecZero; + iJumpSpectator = 0; + return; + } +#endif + // Move around in normal spectator method + + speed = pmove->velocity.Length(); + + if (speed < 1) + { + pmove->velocity = g_vecZero; + } + else + { + drop = 0; + + friction = pmove->movevars->friction*1.5; // extra friction + control = speed < pmove->movevars->stopspeed ? pmove->movevars->stopspeed : speed; + drop += control*friction*pmove->frametime; + + // scale the velocity + newspeed = speed - drop; + if (newspeed < 0) + newspeed = 0; + newspeed /= speed; + + pmove->velocity *= newspeed; + } + + // accelerate + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + + pmove->forward = pmove->forward.Normalize(); + pmove->right = pmove->right.Normalize(); + + for (i=0 ; i<3 ; i++) + { + wishvel[i] = pmove->forward[i]*fmove + pmove->right[i]*smove; + } + wishvel[2] += pmove->cmd.upmove; + + wishdir = wishvel; + wishspeed = wishdir.Length(); + wishdir = wishdir.Normalize(); + + // + // clamp to server defined max speed + // + if (wishspeed > pmove->movevars->spectatormaxspeed) + { + wishvel *= ( pmove->movevars->spectatormaxspeed / wishspeed ); + wishspeed = pmove->movevars->spectatormaxspeed; + } + + currentspeed = DotProduct(pmove->velocity, wishdir); + addspeed = wishspeed - currentspeed; + if (addspeed <= 0) + return; + + accelspeed = pmove->movevars->accelerate*pmove->frametime*wishspeed; + if (accelspeed > addspeed) + accelspeed = addspeed; + + pmove->velocity += wishdir * accelspeed; + + // move + pmove->origin += pmove->velocity * pmove->frametime; + } + else + { + // all other modes just track some kind of target, so spectator PVS = target PVS + + int target; + + // no valid target ? + if ( pmove->iuser2 <= 0) + return; + + // Find the client this player's targeting + for (target = 0; target < pmove->numphysent; target++) + { + if ( pmove->physents[target].info == pmove->iuser2 ) + break; + } + + if (target == pmove->numphysent) + return; + + // use targets position as own origin for PVS + VectorCopy( pmove->physents[target].angles, pmove->angles ); + VectorCopy( pmove->physents[target].origin, pmove->origin ); + + // no velocity + pmove->velocity = g_vecZero; + } +} + +/* +================== +PM_SplineFraction + +Use for ease-in, ease-out style interpolation (accel/decel) +Used by ducking code. +================== +*/ +float PM_SplineFraction( float value, float scale ) +{ + float valueSquared; + + value = scale * value; + valueSquared = value * value; + + // Nice little ease-in, ease-out spline-like curve + return 3 * valueSquared - 2 * valueSquared * value; +} + +void PM_FixPlayerCrouchStuck( int direction ) +{ + int hitent; + int i; + Vector test; + + hitent = pmove->PM_TestPlayerPosition ( pmove->origin, NULL ); + if (hitent == -1 ) + return; + + VectorCopy( pmove->origin, test ); + for ( i = 0; i < 36; i++ ) + { + pmove->origin[2] += direction; + hitent = pmove->PM_TestPlayerPosition ( pmove->origin, NULL ); + if (hitent == -1 ) + return; + } + + VectorCopy( test, pmove->origin ); // Failed +} + +void PM_UnDuck( void ) +{ + int i; + pmtrace_t trace; + Vector newOrigin; + + VectorCopy( pmove->origin, newOrigin ); + + if ( pmove->onground != -1 && pmove->flags & FL_DUCKING && pmove->bInDuck == false) + { + for ( i = 0; i < 3; i++ ) + { + newOrigin[i] += ( pmove->player_mins[1][i] - pmove->player_mins[0][i] ); + } + } + + trace = pmove->PM_PlayerTrace( newOrigin, newOrigin, PM_NORMAL, -1 ); + + if ( !trace.startsolid ) + { + pmove->usehull = 0; + + // Oh, no, changing hulls stuck us into something, try unsticking downward first. + trace = pmove->PM_PlayerTrace( newOrigin, newOrigin, PM_NORMAL, -1 ); + if ( trace.startsolid ) + { + // See if we are stuck? If so, stay ducked with the duck hull until we have a clear spot + //Con_Printf( "unstick got stuck\n" ); + pmove->usehull = 1; + return; + } + + pmove->flags &= ~FL_DUCKING; + pmove->bInDuck = false; + pmove->view_ofs[2] = VEC_VIEW; + pmove->flDuckTime = 0; + + VectorCopy( newOrigin, pmove->origin ); + + // Recatagorize position since ducking can change origin + PM_CatagorizePosition(); + } +} + +void PM_Duck( void ) +{ + int i; + float time; + float duckFraction; + + int buttonsChanged = ( pmove->oldbuttons ^ pmove->cmd.buttons ); // These buttons have changed this frame + int nButtonPressed = buttonsChanged & pmove->cmd.buttons; // The changed ones still down are "pressed" + + int duckchange = buttonsChanged & IN_DUCK ? 1 : 0; + int duckpressed = nButtonPressed & IN_DUCK ? 1 : 0; + + if ( pmove->cmd.buttons & IN_DUCK ) + { + pmove->oldbuttons |= IN_DUCK; + } + else + { + pmove->oldbuttons &= ~IN_DUCK; + } + + // Prevent ducking if the iuser3 variable is set + if ( pmove->dead ) + { + // Try to unduck + if ( pmove->flags & FL_DUCKING ) + { + PM_UnDuck(); + } + return; + } + + if ( pmove->flags & FL_DUCKING ) + { + pmove->cmd.forwardmove *= 0.333; + pmove->cmd.sidemove *= 0.333; + pmove->cmd.upmove *= 0.333; + } + + if ( ( pmove->cmd.buttons & IN_DUCK ) || ( pmove->bInDuck ) || ( pmove->flags & FL_DUCKING ) ) + { + if ( pmove->cmd.buttons & IN_DUCK ) + { + if ( (nButtonPressed & IN_DUCK ) && !( pmove->flags & FL_DUCKING ) ) + { + // Use 1 second so super long jump will work + pmove->flDuckTime = 1000; + pmove->bInDuck = true; + } + + time = Q_max( 0.0, ( 1.0 - (float)pmove->flDuckTime / 1000.0 ) ); + + if ( pmove->bInDuck ) + { + // Finish ducking immediately if duck time is over or not on ground + if ( ( (float)pmove->flDuckTime / 1000.0 <= ( 1.0 - TIME_TO_DUCK ) ) || + ( pmove->onground == -1 ) ) + { + pmove->usehull = 1; + pmove->view_ofs[2] = VEC_DUCK_VIEW; + pmove->flags |= FL_DUCKING; + pmove->bInDuck = false; + + // HACKHACK - Fudge for collision bug - no time to fix this properly + if ( pmove->onground != -1 ) + { + for ( i = 0; i < 3; i++ ) + { + pmove->origin[i] -= ( pmove->player_mins[1][i] - pmove->player_mins[0][i] ); + } + // See if we are stuck? + PM_FixPlayerCrouchStuck( STUCK_MOVEUP ); + + // Recatagorize position since ducking can change origin + PM_CatagorizePosition(); + } + } + else + { + float fMore = (VEC_DUCK_HULL_MIN - VEC_HULL_MIN); + + // Calc parametric time + duckFraction = PM_SplineFraction( time, (1.0/TIME_TO_DUCK) ); + pmove->view_ofs[2] = ((VEC_DUCK_VIEW - fMore ) * duckFraction) + (VEC_VIEW * (1-duckFraction)); + } + } + } + else + { + // Try to unduck + PM_UnDuck(); + } + } +} + +void PM_LadderMove( physent_t *pLadder ) +{ + Vector ladderCenter; + trace_t trace; + qboolean onFloor; + Vector floor; + Vector modelmins, modelmaxs; + + if ( pmove->movetype == MOVETYPE_NOCLIP ) + return; + + pmove->PM_GetModelBounds( pLadder->model, modelmins, modelmaxs ); + + ladderCenter = pLadder->origin + ( modelmins + modelmaxs ) * 0.5f; + + pmove->movetype = MOVETYPE_FLY; + + // On ladder, convert movement to be relative to the ladder + + VectorCopy( pmove->origin, floor ); + floor[2] += pmove->player_mins[pmove->usehull][2] - 1; + + // g-cont. check bmodels ground too + pmtrace_t *tr = pmove->PM_TraceLine( floor, floor, PM_STUDIO_IGNORE, 2, -1 ); + + if( /*pmove->PM_PointContents( floor, NULL ) == CONTENTS_SOLID || */ tr->ent != -1 ) + onFloor = true; + else onFloor = false; + + pmove->gravity = 0; + pLadder->solid = SOLID_BSP; // HACKHACK: to allow rotating on ladder model + pmove->PM_TraceModel( pLadder, pmove->origin, ladderCenter, &trace ); + pLadder->solid = SOLID_NOT; + + if ( trace.fraction != 1.0 ) + { + float forward = 0, right = 0; + Vector vpn, v_right; + + AngleVectors( pmove->angles, vpn, v_right, NULL ); + if ( pmove->cmd.buttons & IN_BACK ) + forward -= MAX_CLIMB_SPEED; + if ( pmove->cmd.buttons & IN_FORWARD ) + forward += MAX_CLIMB_SPEED; + if ( pmove->cmd.buttons & IN_MOVELEFT ) + right -= MAX_CLIMB_SPEED; + if ( pmove->cmd.buttons & IN_MOVERIGHT ) + right += MAX_CLIMB_SPEED; + + if ( pmove->cmd.buttons & IN_JUMP ) + { + pmove->movetype = MOVETYPE_WALK; + pmove->velocity = trace.plane.normal * 270; + } + else + { + if ( forward != 0 || right != 0 ) + { + Vector velocity, cross, lateral, tmp; + float normal; + + //ALERT(at_console, "pev %.2f %.2f %.2f - ", + // pev->velocity.x, pev->velocity.y, pev->velocity.z); + // Calculate player's intended velocity + //Vector velocity = (forward * gpGlobals->v_forward) + (right * gpGlobals->v_right); + velocity = vpn * forward; + velocity += v_right * right; + + + // Perpendicular in the ladder plane + Vector perp = CrossProduct( Vector(0,0,1), trace.plane.normal ); + perp = perp.Normalize(); + + // decompose velocity into ladder plane + normal = DotProduct( velocity, trace.plane.normal ); + // This is the velocity into the face of the ladder + cross = trace.plane.normal * normal; + + + // This is the player's additional velocity + lateral = velocity - cross; + + // This turns the velocity into the face of the ladder into velocity that + // is roughly vertically perpendicular to the face of the ladder. + // NOTE: It IS possible to face up and move down or face down and move up + // because the velocity is a sum of the directional velocity and the converted + // velocity through the face of the ladder -- by design. + tmp = CrossProduct( trace.plane.normal, perp ); + pmove->velocity = lateral + tmp * -normal; + + if( onFloor && normal > 0 ) // On ground moving away from the ladder + { + pmove->velocity += trace.plane.normal * MAX_CLIMB_SPEED; + } + //pev->velocity = lateral - (CrossProduct( trace.vecPlaneNormal, perp ) * normal); + } + else + { + pmove->velocity = g_vecZero; + } + } + } +} + +physent_t *PM_Ladder( void ) +{ + physent_t *pe; + Vector offset, test; + + for( int i = 0; i < pmove->nummoveent; i++ ) + { + pe = &pmove->moveents[i]; + + if( pe->model && (modtype_t)pmove->PM_GetModelType( pe->model ) == mod_brush && pe->skin == CONTENTS_LADDER ) + { + hull_t *hull = (hull_t *)pmove->PM_HullForBsp( pe, offset ); + int num = hull->firstclipnode; + + // Offset the test point appropriately for this hull. + test = pmove->origin - offset; + + // support for rotational triggers + if(( pe->model->flags & 2 ) && pe->angles != g_vecZero ) + { + // g-cont. can't acess to CBase->EntityToWorldTransform() :'-( + // so we build matrix from scratch + matrix4x4 entityToWorld( offset, pe->angles ); + test = entityToWorld.VectorITransform( pmove->origin ); + } + + // Test the player's hull for intersection with this model + if( pmove->PM_HullPointContents( hull, num, test ) == CONTENTS_EMPTY ) + continue; + + return pe; + } + } + + return NULL; +} + + + +void PM_WaterJump (void) +{ + if ( pmove->waterjumptime > 10000 ) + { + pmove->waterjumptime = 10000; + } + + if ( !pmove->waterjumptime ) + return; + + pmove->waterjumptime -= pmove->cmd.msec; + if ( pmove->waterjumptime < 0 || + !pmove->waterlevel ) + { + pmove->waterjumptime = 0; + pmove->flags &= ~FL_WATERJUMP; + } + + pmove->velocity[0] = pmove->movedir[0]; + pmove->velocity[1] = pmove->movedir[1]; +} + +/* +============ +PM_AddGravity + +============ +*/ +void PM_AddGravity () +{ + float ent_gravity; + + if (pmove->gravity) + ent_gravity = pmove->gravity; + else + ent_gravity = 1.0; + + // Add gravity incorrectly + pmove->velocity[2] -= (ent_gravity * pmove->movevars->gravity * pmove->frametime ); + pmove->velocity[2] += pmove->basevelocity[2] * pmove->frametime; + pmove->basevelocity[2] = 0; + PM_CheckVelocity(); +} +/* +============ +PM_PushEntity + +Does not change the entities velocity at all +============ +*/ +pmtrace_t PM_PushEntity (const Vector &push) +{ + pmtrace_t trace; + Vector end; + + end = pmove->origin + push; + + trace = pmove->PM_PlayerTrace (pmove->origin, end, PM_NORMAL, -1 ); + + VectorCopy (trace.endpos, pmove->origin); + + // So we can run impact function afterwards. + if (trace.fraction < 1.0 && + !trace.allsolid) + { + PM_AddToTouched(trace, pmove->velocity); + } + + return trace; +} + +/* +============ +PM_Physics_Toss() + +Dead player flying through air., e.g. +============ +*/ +void PM_Physics_Toss() +{ + pmtrace_t trace; + Vector move; + float backoff; + + PM_CheckWater(); + + if (pmove->velocity[2] > 0) + pmove->onground = -1; + + // If on ground and not moving, return. + if ( pmove->onground != -1 ) + { + if(( pmove->basevelocity == g_vecZero) && ( pmove->velocity == g_vecZero )) + return; + } + + PM_CheckVelocity (); + +// add gravity + if ( pmove->movetype != MOVETYPE_FLY && + pmove->movetype != MOVETYPE_BOUNCEMISSILE && + pmove->movetype != MOVETYPE_FLYMISSILE ) + PM_AddGravity (); + +// move origin + // Base velocity is not properly accounted for since this entity will move again after the bounce without + // taking it into account + pmove->velocity += pmove->basevelocity; + + PM_CheckVelocity(); + + move = pmove->velocity * pmove->frametime; + pmove->velocity -= pmove->basevelocity; + + trace = PM_PushEntity (move); // Should this clear basevelocity + + PM_CheckVelocity(); + + if (trace.allsolid) + { + // entity is trapped in another solid + pmove->onground = trace.ent; + pmove->velocity = g_vecZero; + return; + } + + if (trace.fraction == 1) + { + PM_CheckWater(); + return; + } + + + if (pmove->movetype == MOVETYPE_BOUNCE) + backoff = 2.0 - pmove->friction; + else if (pmove->movetype == MOVETYPE_BOUNCEMISSILE) + backoff = 2.0; + else + backoff = 1; + + PM_ClipVelocity (pmove->velocity, trace.plane.normal, pmove->velocity, backoff); + + // stop if on ground + if (trace.plane.normal[2] > 0.7) + { + float vel; + Vector base; + + base = g_vecZero; + + if (pmove->velocity[2] < pmove->movevars->gravity * pmove->frametime) + { + // we're rolling on the ground, add static friction. + pmove->onground = trace.ent; + pmove->velocity[2] = 0; + } + + vel = DotProduct( pmove->velocity, pmove->velocity ); + + // Con_DPrintf("%f %f: %.0f %.0f %.0f\n", vel, trace.fraction, ent->velocity[0], ent->velocity[1], ent->velocity[2] ); + + if (vel < (30 * 30) || (pmove->movetype != MOVETYPE_BOUNCE && pmove->movetype != MOVETYPE_BOUNCEMISSILE)) + { + pmove->onground = trace.ent; + pmove->velocity = g_vecZero; + } + else + { + move = pmove->velocity * (( 1.0f - trace.fraction ) * pmove->frametime * 0.9f ); + trace = PM_PushEntity (move); + } + pmove->velocity -= base; + } + +// check for in water + PM_CheckWater(); +} + +/* +==================== +PM_NoClip + +==================== +*/ +void PM_NoClip( float factor ) +{ + Vector wishvel; + float fmove, smove, umove; + float maxspeed = pmove->maxspeed * factor; // speed for noclip movement + + // Copy movement amounts + fmove = pmove->cmd.forwardmove; + smove = pmove->cmd.sidemove; + umove = pmove->cmd.upmove; + + pmove->forward = pmove->forward.Normalize() * factor; + pmove->right = pmove->right.Normalize() * factor; + pmove->up = pmove->up.Normalize() * factor; + + // Determine x and y parts of velocity + wishvel = pmove->forward * fmove + pmove->right * smove + pmove->up * umove; + + Vector wishdir = wishvel.Normalize(); + + float wishspeed = wishvel.Length(); + + if( wishspeed > maxspeed ) + { + wishvel *= ( maxspeed / wishspeed ); + wishspeed = maxspeed; + } + + pmove->origin += wishvel * pmove->frametime; + + // Zero out the velocity so that we don't accumulate a huge downward velocity from + // gravity, etc. + pmove->velocity = g_vecZero; +} + +// Only allow bunny jumping up to 1.7x server / player maxspeed setting +#define BUNNYJUMP_MAX_SPEED_FACTOR 1.7f + +//----------------------------------------------------------------------------- +// Purpose: Corrects bunny jumping ( where player initiates a bunny jump before other +// movement logic runs, thus making onground == -1 thus making PM_Friction get skipped and +// running PM_AirMove, which doesn't crop velocity to maxspeed like the ground / other +// movement logic does. +//----------------------------------------------------------------------------- +void PM_PreventMegaBunnyJumping( void ) +{ + // Current player speed + float spd; + // If we have to crop, apply this cropping fraction to velocity + float fraction; + // Speed at which bunny jumping is limited + float maxscaledspeed; + + maxscaledspeed = BUNNYJUMP_MAX_SPEED_FACTOR * pmove->maxspeed; + + // Don't divide by zero + if ( maxscaledspeed <= 0.0f ) + return; + + spd = pmove->velocity.Length(); + + if ( spd <= maxscaledspeed ) + return; + + fraction = ( maxscaledspeed / spd ) * 0.65; //Returns the modifier for the velocity + + pmove->velocity *= fraction; //Crop it down!. +} + +/* +============= +PM_Jump +============= +*/ +void PM_Jump (void) +{ + int i; + qboolean tfc = false; + + qboolean cansuperjump = false; + + if (pmove->dead) + { + pmove->oldbuttons |= IN_JUMP ; // don't jump again until released + return; + } + + tfc = atoi( pmove->PM_Info_ValueForKey( pmove->physinfo, "tfc" ) ) == 1 ? true : false; + + // Spy that's feigning death cannot jump + if ( tfc && + ( pmove->deadflag == ( DEAD_DISCARDBODY + 1 ) ) ) + { + return; + } + + // See if we are waterjumping. If so, decrement count and return. + if ( pmove->waterjumptime ) + { + pmove->waterjumptime -= pmove->cmd.msec; + if (pmove->waterjumptime < 0) + { + pmove->waterjumptime = 0; + } + return; + } + + // If we are in the water most of the way... + if (pmove->waterlevel >= 2 && pmove->watertype != CONTENTS_FOG) + { // swimming, not jumping + pmove->onground = -1; + + if (pmove->watertype == CONTENTS_WATER) // We move up a certain amount + pmove->velocity[2] = 100; + else if (pmove->watertype == CONTENTS_SLIME) + pmove->velocity[2] = 80; + else // LAVA + pmove->velocity[2] = 50; + + // play swiming sound + if ( pmove->flSwimTime <= 0 ) + { + // Don't play sound again for 1 second + pmove->flSwimTime = 1000; + switch ( pmove->RandomLong( 0, 3 ) ) + { + case 0: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 1: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 2: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 3: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + } + } + + return; + } + + // No more effect + if ( pmove->onground == -1 ) + { + // Flag that we jumped. + // HACK HACK HACK + // Remove this when the game .dll no longer does physics code!!!! + pmove->oldbuttons |= IN_JUMP; // don't jump again until released + return; // in air, so no effect + } + + if ( pmove->oldbuttons & IN_JUMP ) + return; // don't pogo stick + + // In the air now. + pmove->onground = -1; + + PM_PreventMegaBunnyJumping(); + + if ( tfc ) + { + pmove->PM_PlaySound( CHAN_BODY, "player/plyrjmp8.wav", 0.5, ATTN_NORM, 0, PITCH_NORM ); + } + else + { + PM_PlayStepSound( STEP_MATERIAL, 1.0 ); + } + + // See if user can super long jump? + cansuperjump = atoi( pmove->PM_Info_ValueForKey( pmove->physinfo, "slj" ) ) == 1 ? true : false; + + // Acclerate upward + // If we are ducking... + if ( ( pmove->bInDuck ) || ( pmove->flags & FL_DUCKING ) ) + { + // Adjust for super long jump module + // UNDONE -- note this should be based on forward angles, not current velocity. + if ( cansuperjump && + ( pmove->cmd.buttons & IN_DUCK ) && + ( pmove->flDuckTime > 0 ) && + pmove->velocity.Length() > 50 ) + { + pmove->punchangle[0] = -5; + + for (i =0; i < 2; i++) + { + pmove->velocity[i] = pmove->forward[i] * PLAYER_LONGJUMP_SPEED * 1.6; + } + + pmove->velocity[2] = sqrt(2 * 800 * 56.0); + } + else + { + pmove->velocity[2] = sqrt(2 * 800 * 45.0); + } + } + else + { + // buz: get jump height from player settings + // jumpheight is percent from normal [0-100] + + int jumpheight = atoi(pmove->PM_Info_ValueForKey(pmove->physinfo, "jh")); + pmove->velocity[2] = sqrt(2 * (800 / 100) * jumpheight * 45.0); +// pmove->Con_DPrintf("PM: jumping with: %d\n", jumpheight); + } + + // Decay it for simulation + PM_FixupGravityVelocity(); + + // Flag that we jumped. + pmove->oldbuttons |= IN_JUMP; // don't jump again until released +} + +/* +============= +PM_CheckWaterJump +============= +*/ +#define WJ_HEIGHT 8 +void PM_CheckWaterJump (void) +{ + Vector vecStart, vecEnd; + Vector flatforward; + Vector flatvelocity; + float curspeed; + pmtrace_t tr; + int savehull; + + // Already water jumping. + if ( pmove->waterjumptime ) + return; + + // Don't hop out if we just jumped in + if ( pmove->velocity[2] < -180 ) + return; // only hop out if we are moving up + + // See if we are backing up + flatvelocity[0] = pmove->velocity[0]; + flatvelocity[1] = pmove->velocity[1]; + flatvelocity[2] = 0; + + // Must be moving + curspeed = flatvelocity.Length(); + flatvelocity = flatvelocity.Normalize(); + + // see if near an edge + flatforward[0] = pmove->forward[0]; + flatforward[1] = pmove->forward[1]; + flatforward[2] = 0; + flatforward = flatforward.Normalize(); + + // Are we backing into water from steps or something? If so, don't pop forward + if ( curspeed != 0.0 && ( DotProduct( flatvelocity, flatforward ) < 0.0 ) ) + return; + + VectorCopy( pmove->origin, vecStart ); + vecStart[2] += WJ_HEIGHT; + + vecEnd = vecStart + flatforward * 24; + + // Trace, this trace should use the point sized collision hull + savehull = pmove->usehull; + pmove->usehull = 2; + tr = pmove->PM_PlayerTrace( vecStart, vecEnd, PM_NORMAL, -1 ); + if ( tr.fraction < 1.0 && fabs( tr.plane.normal[2] ) < 0.1f ) // Facing a near vertical wall? + { + vecStart[2] += pmove->player_maxs[ savehull ][2] - WJ_HEIGHT; + vecEnd = vecStart + flatforward * 24; + pmove->movedir = tr.plane.normal * -50; + + tr = pmove->PM_PlayerTrace( vecStart, vecEnd, PM_NORMAL, -1 ); + if ( tr.fraction == 1.0 ) + { + pmove->waterjumptime = 2000; + pmove->velocity[2] = 225; + pmove->oldbuttons |= IN_JUMP; + pmove->flags |= FL_WATERJUMP; + } + } + + // Reset the collision hull + pmove->usehull = savehull; +} + +void PM_CheckFalling( void ) +{ + if ( pmove->onground != -1 && + !pmove->dead && + pmove->flFallVelocity >= PLAYER_FALL_PUNCH_THRESHHOLD ) + { + float fvol = 0.5; + + if ( pmove->waterlevel > 0 ) + { + } + else if ( pmove->flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED ) + { + // NOTE: In the original game dll , there were no breaks after these cases, causing the first one to + // cascade into the second + //switch ( RandomLong(0,1) ) + //{ + //case 0: + //pmove->PM_PlaySound( CHAN_VOICE, "player/pl_fallpain2.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + //break; + //case 1: + pmove->PM_PlaySound( CHAN_VOICE, "player/pl_fallpain3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + // break; + //} + fvol = 1.0; + } + else if ( pmove->flFallVelocity > PLAYER_MAX_SAFE_FALL_SPEED / 2 ) + { + qboolean tfc = false; + tfc = atoi( pmove->PM_Info_ValueForKey( pmove->physinfo, "tfc" ) ) == 1 ? true : false; + + if ( tfc ) + { + pmove->PM_PlaySound( CHAN_VOICE, "player/pl_fallpain3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + } + + fvol = 0.85; + } + else if ( pmove->flFallVelocity < PLAYER_MIN_BOUNCE_SPEED ) + { + fvol = 0; + } + + if ( fvol > 0.0 ) + { + // Play landing step right away + pmove->flTimeStepSound = 0; + + PM_UpdateStepSound(); + + // play step sound for current texture + PM_PlayStepSound( STEP_MATERIAL, fvol ); + + // buz: new punch system + float punch = (pmove->flFallVelocity > 700 ? 700 : pmove->flFallVelocity) * 0.42; + // pmove->Con_DPrintf("fall vel: %f, punch: %f\n", pmove->flFallVelocity, punch/20); + pmove->fuser2 += punch; + pmove->fuser3 += pmove->RandomFloat(punch/-10, punch/10); + pmove->fuser4 += pmove->RandomFloat(punch/-10, punch/10); + } + } + + if ( pmove->onground != -1 ) + { + pmove->flFallVelocity = 0; + } +} + +/* +================= +PM_PlayWaterSounds + +================= +*/ +void PM_PlayWaterSounds( void ) +{ + // Did we enter or leave water? + if ( ( pmove->oldwaterlevel == 0 && pmove->waterlevel != 0 && pmove->watertype > CONTENTS_FLYFIELD) || + ( pmove->oldwaterlevel != 0 && pmove->waterlevel == 0 )) + { + switch ( pmove->RandomLong(0,3) ) + { + case 0: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade1.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 1: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade2.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 2: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + case 3: + pmove->PM_PlaySound( CHAN_BODY, "player/pl_wade4.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + break; + } + } +} + +/* +=============== +PM_CalcRoll + +=============== +*/ +float PM_CalcRoll( const Vector &angles, const Vector &velocity, float rollangle, float rollspeed ) +{ + float sign; + float side; + float value; + Vector forward, right, up; + + AngleVectors (angles, forward, right, up); + + side = DotProduct (velocity, right); + + sign = side < 0 ? -1 : 1; + + side = fabs(side); + + value = rollangle; + + if (side < rollspeed) + { + side = side * value / rollspeed; + } + else + { + side = value; + } + + return side * sign; +} + +/* +============= +PM_DropPunchAngle + +============= +*/ +#define PUNCH_DAMPING 9.0f // bigger number makes the response more damped, smaller is less damped + // currently the system will overshoot, with larger damping values it won't +#define PUNCH_SPRING_CONSTANT 65.0f // bigger number increases the speed at which the view corrects + +void PM_DropPunchAngle ( Vector &punchangle ) +{ + // buz: ó íàñ òåïåðü íîâûé, ñâåæåñïåðòûé èç õë2, ïóí÷. + float damping; + float springForceMagnitude; + Vector savepunch = Vector( pmove->fuser2, pmove->fuser3, pmove->fuser4 ); + + if ( punchangle.Length() > 0.001 || savepunch.Length() > 0.001 ) + { + punchangle += savepunch * pmove->frametime; + damping = 1.0f - (PUNCH_DAMPING * pmove->frametime); + + if ( damping < 0.0f ) + { + damping = 0.0f; + } + savepunch *= damping; + + // torsional spring + // UNDONE: Per-axis spring constant? + springForceMagnitude = PUNCH_SPRING_CONSTANT * pmove->frametime; + springForceMagnitude = bound( 0, springForceMagnitude, 2 ); + + savepunch += punchangle * -springForceMagnitude; + + // don't wrap around + punchangle[0] = bound( -89, punchangle[0], 89 ); + punchangle[1] = bound( -179, punchangle[1], 179 ); + punchangle[2] = bound( -89, punchangle[2], 89 ); + + pmove->fuser2 = savepunch.x; + pmove->fuser3 = savepunch.y; + pmove->fuser4 = savepunch.z; + } +} + +/* +============== +PM_CheckParamters + +============== +*/ +void PM_CheckParamters( void ) +{ + float spd; + Vector v_angle; + + // clear stuck flag for each frame + pmove->flags &= ~FL_STUCKED; + + spd = ( pmove->cmd.forwardmove * pmove->cmd.forwardmove ) + ( pmove->cmd.sidemove * pmove->cmd.sidemove ) + ( pmove->cmd.upmove * pmove->cmd.upmove ); + spd = sqrt( spd ); + + // buz: clientmaxspeed is now means speed factor to wishspeed - + // from 0 to 100 percent + +// maxspeed = pmove->clientmaxspeed; //atof( pmove->PM_Info_ValueForKey( pmove->physinfo, "maxspd" ) ); +// if ( maxspeed != 0.0 ) +// { +// pmove->maxspeed = min( maxspeed, pmove->maxspeed ); +// } + + if ( ( spd != 0.0 ) && ( spd > pmove->maxspeed ) ) + { + float fRatio = pmove->maxspeed / spd; + pmove->cmd.forwardmove *= fRatio; + pmove->cmd.sidemove *= fRatio; + pmove->cmd.upmove *= fRatio; + } + + if ( pmove->flags & FL_FROZEN || pmove->flags & FL_ONTRAIN || pmove->dead ) + { + pmove->cmd.forwardmove = 0; + pmove->cmd.sidemove = 0; + pmove->cmd.upmove = 0; + + if( !( pmove->flags & FL_ONTRAIN )) + pmove->cmd.buttons = 0; // LRC - no jump sounds when frozen! + } + + PM_DropPunchAngle( pmove->punchangle ); + + // Take angles from command. + if ( !pmove->dead ) + { + VectorCopy ( pmove->cmd.viewangles, v_angle ); + v_angle += pmove->punchangle; + + // set up view angles. + pmove->angles[PITCH] = v_angle[PITCH]; + pmove->angles[YAW] = v_angle[YAW]; + pmove->angles[ROLL] = PM_CalcRoll( v_angle, pmove->velocity, pmove->movevars->rollangle, pmove->movevars->rollspeed ) * 4; + } + else + { + VectorCopy( pmove->oldangles, pmove->angles ); + } + + // Set dead player view_offset + if ( pmove->dead ) + { + pmove->view_ofs[2] = PM_DEAD_VIEWHEIGHT; + } + + // Adjust client view angles to match values used on server. + if (pmove->angles[YAW] > 180.0f) + { + pmove->angles[YAW] -= 360.0f; + } + +} + +void PM_ReduceTimers( void ) +{ + if ( pmove->flTimeStepSound > 0 ) + { + pmove->flTimeStepSound -= pmove->cmd.msec; + if ( pmove->flTimeStepSound < 0 ) + { + pmove->flTimeStepSound = 0; + } + } + if ( pmove->flDuckTime > 0 ) + { + pmove->flDuckTime -= pmove->cmd.msec; + if ( pmove->flDuckTime < 0 ) + { + pmove->flDuckTime = 0; + } + } + if ( pmove->flSwimTime > 0 ) + { + pmove->flSwimTime -= pmove->cmd.msec; + if ( pmove->flSwimTime < 0 ) + { + pmove->flSwimTime = 0; + } + } +} + +/* +============= +PlayerMove + +Returns with origin, angles, and velocity modified in place. + +Numtouch and touchindex[] will be set if any of the physents +were contacted during the move. +============= +*/ +void PM_PlayerMove ( qboolean server ) +{ + physent_t *pLadder = NULL; + + // Are we running server code? + pmove->server = server; + + // Adjust speeds etc. + PM_CheckParamters(); + + // Assume we don't touch anything + pmove->numtouch = 0; + + // # of msec to apply movement + pmove->frametime = pmove->cmd.msec * 0.001; + + PM_ReduceTimers(); + + // Convert view angles to vectors + AngleVectors (pmove->angles, pmove->forward, pmove->right, pmove->up); + + // PM_ShowClipBox(); + + // Special handling for spectator and observers. (iuser1 is set if the player's in observer mode) + if ( pmove->spectator || pmove->iuser1 > 0 ) + { + PM_SpectatorMove(); + PM_CatagorizePosition(); + return; + } + + // Always try and unstick us unless we are in NOCLIP mode + if ( pmove->movetype != MOVETYPE_NOCLIP && pmove->movetype != MOVETYPE_NONE ) + { + if ( PM_CheckStuck() ) + { + return; // Can't move, we're stuck + } + } + + // Now that we are "unstuck", see where we are ( waterlevel and type, pmove->onground ). + PM_CatagorizePosition(); + + // Store off the starting water level + pmove->oldwaterlevel = pmove->waterlevel; + if (pmove->watertype > CONTENTS_FLYFIELD) + pmove->oldwaterlevel = pmove->waterlevel; + else + pmove->oldwaterlevel = 0; + + // If we are not on ground, store off how fast we are moving down + if ( pmove->onground == -1 && pmove->movetype != MOVETYPE_NOCLIP ) + { + pmove->flFallVelocity = -pmove->velocity[2]; + } + + g_onladder = 0; + // Don't run ladder code if dead or on a train + if ( !pmove->dead && !(pmove->flags & FL_ONTRAIN) ) + { + pLadder = PM_Ladder(); + if ( pLadder ) + { + g_onladder = 1; + } + } + + PM_UpdateStepSound(); + + PM_Duck(); + + // Don't run ladder code if dead or on a train + if ( !pmove->dead && !(pmove->flags & FL_ONTRAIN) ) + { + if ( pLadder ) + { + PM_LadderMove( pLadder ); + } + else if ( pmove->movetype != MOVETYPE_WALK && pmove->movetype != MOVETYPE_NOCLIP ) + { + // Clear ladder stuff unless player is noclipping + // it will be set immediately again next frame if necessary + pmove->movetype = MOVETYPE_WALK; + } + } + + // Slow down, I'm pulling it! (a box maybe) but only when I'm standing on ground + if ( ( pmove->onground != -1 ) && ( pmove->cmd.buttons & IN_USE) ) + { + pmove->velocity *= 0.3f; + } + + // Handle movement + switch ( pmove->movetype ) + { + default: + pmove->Con_DPrintf("Bogus pmove player movetype %i on (%i) 0=cl 1=sv\n", pmove->movetype, pmove->server); + break; + + case MOVETYPE_NONE: + break; + + case MOVETYPE_NOCLIP: + PM_NoClip( 1.0f ); + break; + + case MOVETYPE_TOSS: + case MOVETYPE_BOUNCE: + PM_Physics_Toss(); + break; + + case MOVETYPE_FLY: + + PM_CheckWater(); + + // Was jump button pressed? + // If so, set velocity to 270 away from ladder. This is currently wrong. + // Also, set MOVE_TYPE to walk, too. + if ( pmove->cmd.buttons & IN_JUMP ) + { + if ( !pLadder ) + { + PM_Jump (); + } + } + else + { + pmove->oldbuttons &= ~IN_JUMP; + } + + // perform the move accounting for any base velocity. + pmove->velocity += pmove->basevelocity; + + PM_FlyMove(); + + pmove->velocity -= pmove->basevelocity; + break; + + case MOVETYPE_WALK: + if ( !PM_InWater() ) + { + PM_AddCorrectGravity(); + } + + // If we are leaping out of the water, just update the counters. + if ( pmove->waterjumptime ) + { + PM_WaterJump(); + PM_FlyMove(); + + // Make sure waterlevel is set correctly + PM_CheckWater(); + return; + } + + // If we are swimming in the water, see if we are nudging against a place we can jump up out + // of, and, if so, start out jump. Otherwise, if we are not moving up, then reset jump timer to 0 + if ( pmove->waterlevel >= 2 && pmove->watertype != CONTENTS_FOG) + { + if ( pmove->waterlevel == 2 ) + { + PM_CheckWaterJump(); + } + + // If we are falling again, then we must not trying to jump out of water any more. + if ( pmove->velocity[2] < 0 && pmove->waterjumptime ) + { + pmove->waterjumptime = 0; + } + + // Was jump button pressed? + if (pmove->cmd.buttons & IN_JUMP) + { + PM_Jump (); + } + else + { + pmove->oldbuttons &= ~IN_JUMP; + } + + // Perform regular water movement + PM_WaterMove(); + + pmove->velocity -= pmove->basevelocity; + + // Get a final position + PM_CatagorizePosition(); + } + else + + // Not underwater + { + // Was jump button pressed? + if ( pmove->cmd.buttons & IN_JUMP ) + { + if ( !pLadder ) + { + PM_Jump (); + } + } + else + { + pmove->oldbuttons &= ~IN_JUMP; + } + + // Fricion is handled before we add in any base velocity. That way, if we are on a conveyor, + // we don't slow when standing still, relative to the conveyor. + if ( pmove->onground != -1 ) + { + pmove->velocity[2] = 0.0; + PM_Friction(); + } + + // Make sure velocity is valid. + PM_CheckVelocity(); + + // Are we on ground now + if ( pmove->onground != -1 ) + { + PM_WalkMove(); + } + else + { + PM_AirMove(); // Take into account movement when in air. + } + + // Set final flags. + PM_CatagorizePosition(); + + // Now pull the base velocity back out. + // Base velocity is set if you are on a moving object, like + // a conveyor (or maybe another monster?) + pmove->velocity -= pmove->basevelocity; + + // Make sure velocity is valid. + PM_CheckVelocity(); + + // Add any remaining gravitational component. + if ( !PM_InWater() ) + { + PM_FixupGravityVelocity(); + } + + // If we are on ground, no downward velocity. + if ( pmove->onground != -1 ) + { + pmove->velocity[2] = 0; + } + + // See if we landed on the ground with enough force to play + // a landing sound. + PM_CheckFalling(); + } + + // Did we enter or leave the water? + PM_PlayWaterSounds(); + break; + } +} + +void PM_CreateStuckTable( void ) +{ + float x, y, z; + int idx; + int i; + float zi[3]; + + memset(rgv3tStuckTable, 0, 54 * sizeof(Vector)); + + idx = 0; + // Little Moves. + x = y = 0; + // Z moves + for (z = -0.125 ; z <= 0.125 ; z += 0.125) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + x = z = 0; + // Y moves + for (y = -0.125 ; y <= 0.125 ; y += 0.125) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + y = z = 0; + // X moves + for (x = -0.125 ; x <= 0.125 ; x += 0.125) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + + // Remaining multi axis nudges. + for ( x = - 0.125; x <= 0.125; x += 0.250 ) + { + for ( y = - 0.125; y <= 0.125; y += 0.250 ) + { + for ( z = - 0.125; z <= 0.125; z += 0.250 ) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + } + } + + // Big Moves. + x = y = 0; + zi[0] = 0.0f; + zi[1] = 1.0f; + zi[2] = 6.0f; + + for (i = 0; i < 3; i++) + { + // Z moves + z = zi[i]; + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + + x = z = 0; + + // Y moves + for (y = -2.0f ; y <= 2.0f ; y += 2.0) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + y = z = 0; + // X moves + for (x = -2.0f ; x <= 2.0f ; x += 2.0f) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + + // Remaining multi axis nudges. + for (i = 0 ; i < 3; i++) + { + z = zi[i]; + + for (x = -2.0f ; x <= 2.0f ; x += 2.0f) + { + for (y = -2.0f ; y <= 2.0f ; y += 2.0) + { + rgv3tStuckTable[idx][0] = x; + rgv3tStuckTable[idx][1] = y; + rgv3tStuckTable[idx][2] = z; + idx++; + } + } + } +} + + + +/* +This modume implements the shared player physics code between any particular game and +the engine. The same PM_Move routine is built into the game .dll and the client .dll and is +invoked by each side as appropriate. There should be no distinction, internally, between server +and client. This will ensure that prediction behaves appropriately. +*/ + +void PM_Move ( struct playermove_s *ppmove, int server ) +{ + assert( pm_shared_initialized ); + + pmove = ppmove; + +// ppmove->Con_Printf("clientmaxspeed: %f\n", ppmove->clientmaxspeed); + + PM_PlayerMove( ( server != 0 ) ? true : false ); + + if ( pmove->onground != -1 ) + { + pmove->flags |= FL_ONGROUND; + } + else + { + pmove->flags &= ~FL_ONGROUND; + } + + // In single player, reset friction after each movement to FrictionModifier Triggers work still. + if ( !pmove->multiplayer && ( pmove->movetype == MOVETYPE_WALK ) ) + { + pmove->friction = 1.0f; + } +} + +int PM_GetVisEntInfo( int ent ) +{ + if ( ent >= 0 && ent <= pmove->numvisent ) + { + return pmove->visents[ ent ].info; + } + return -1; +} + +int PM_GetPhysEntInfo( int ent ) +{ + if ( ent >= 0 && ent <= pmove->numphysent) + { + return pmove->physents[ ent ].info; + } + return -1; +} + +playermove_t *PM_GetPlayerMove( void ) +{ + return pmove; +} + +void PM_Init( struct playermove_s *ppmove ) +{ + assert( !pm_shared_initialized ); + + pmove = ppmove; + + PM_CreateStuckTable(); + pm_shared_initialized = 1; +} \ No newline at end of file diff --git a/pm_shared/pm_shared.h b/pm_shared/pm_shared.h new file mode 100644 index 0000000..81243ae --- /dev/null +++ b/pm_shared/pm_shared.h @@ -0,0 +1,37 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +// +// pm_shared.h +// +#if !defined( PM_SHAREDH ) +#define PM_SHAREDH +#pragma once + +void PM_Init( struct playermove_s *ppmove ); +void PM_Move ( struct playermove_s *ppmove, int server ); +char PM_FindTextureType( char *name ); +struct playermove_s *PM_GetPlayerMove( void ); + +// Spectator Movement modes (stored in pev->iuser1, so the physics code can get at them) +#define OBS_NONE 0 +#define OBS_CHASE_LOCKED 1 +#define OBS_CHASE_FREE 2 +#define OBS_ROAMING 3 +#define OBS_IN_EYE 4 +#define OBS_MAP_FREE 5 +#define OBS_MAP_CHASE 6 + +#endif \ No newline at end of file diff --git a/pm_shared/trace.cpp b/pm_shared/trace.cpp new file mode 100644 index 0000000..ac219b8 --- /dev/null +++ b/pm_shared/trace.cpp @@ -0,0 +1,568 @@ +/* +trace.cpp - trace triangle meshes +Copyright (C) 2012 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#define WIN32_LEAN_AND_MEAN +#include "windows.h" +#include +#include "vector.h" +#include "matrix.h" +#include "const.h" +#include "com_model.h" +#include "trace.h" +#include "mathlib.h" + +#ifdef CLIENT_DLL +#include "cl_dll.h" +#include "render_api.h" +#else +#include "edict.h" +#include "eiface.h" +#include "physcallback.h" +#endif + +#include "enginecallback.h" + +void TraceMesh :: SetTraceMesh( mmesh_t *cached_mesh, areanode_t *tree, int modelindex ) +{ + m_pModel = (model_t *)MODEL_HANDLE( modelindex ); + mesh = cached_mesh; + areanodes = tree; +} + +mstudiomaterial_t *TraceMesh :: GetMaterialForFacet( const mfacet_t *facet ) +{ + if( !m_pModel ) return NULL; + + mstudiomaterial_t *materials = m_pModel->materials; + studiohdr_t *phdr = (studiohdr_t *)m_pModel->cache.data; + if( !materials || !phdr ) return NULL; + + short *pskinref = (short *)((byte *)phdr + phdr->skinindex); + if( m_iSkin > 0 && m_iSkin < phdr->numskinfamilies ) + pskinref += (m_iSkin * phdr->numskinref); + + return &materials[pskinref[facet->skinref]]; +} + +mstudiotexture_t *TraceMesh :: GetTextureForFacet( const mfacet_t *facet ) +{ + mstudiomaterial_t *material = GetMaterialForFacet( facet ); + + if( material ) + return material->pSource; + return NULL; +} + +bool TraceMesh :: IsTrans( const mfacet_t *facet ) +{ + mstudiotexture_t *texture = GetTextureForFacet( facet ); + + return (texture && FBitSet( texture->flags, STUDIO_NF_MASKED )); +} + +void CheckAngles( Vector &angles ) +{ + // blackmagic to avoid invalid signbits state + if( angles.y != 0.0f && fmod( angles.y, 45.0f ) == 0.0f ) + angles.y += 0.01f; +} + +void TraceMesh :: SetupTrace( const Vector &start, const Vector &mins, const Vector &maxs, const Vector &end, trace_t *tr ) +{ + trace = tr; + memset( trace, 0, sizeof( *trace )); + trace->fraction = m_flRealFraction = 1.0f; + Vector lmins = mins, lmaxs = maxs, offset; + float t, halfwidth, halfheight; + int i, total_signbits = 0; + + m_vecSrcStart = start; + m_vecSrcEnd = end; + + // adjust so that mins and maxs are always symetric, which + // avoids some complications with plane expanding of rotated + // bmodels + for( i = 0; i < 3; i++ ) + { + offset[i] = ( mins[i] + maxs[i] ) * 0.5f; + lmins[i] = mins[i] - offset[i]; + lmaxs[i] = maxs[i] - offset[i]; + m_vecSrcStart[i] = start[i] + offset[i]; + m_vecSrcEnd[i] = end[i] + offset[i]; + } + + // HACKHACK: shrink bbox a bit... + if( mins != maxs ) + ExpandBounds( lmins, lmaxs, -(1.0f/8.0f)); + + halfwidth = lmaxs[0]; + halfheight = lmaxs[2]; + + bUseCapsule = (offset == g_vecZero) ? false : true; + m_flSphereRadius = ( halfwidth > halfheight ) ? halfheight : halfwidth; + t = halfheight - m_flSphereRadius; + + CheckAngles( m_vecAngles ); + + // inverse pitch because of stupid quake bug + m_transform = matrix4x4( m_vecOrigin, Vector( -m_vecAngles.x, m_vecAngles.y, m_vecAngles.z ), m_vecScale ).InvertFull(); + + m_vecStart = m_transform.VectorTransform( m_vecSrcStart ); + m_vecEnd = m_transform.VectorTransform( m_vecSrcEnd ); + + if( mins != maxs ) + { + // compute a full bounding box + for( i = 0; i < 8; i++ ) + { + Vector p1, p2; + p1.x = ( i & 1 ) ? lmins[0] : lmaxs[0]; + p1.y = ( i & 2 ) ? lmins[1] : lmaxs[1]; + p1.z = ( i & 4 ) ? lmins[2] : lmaxs[2]; + + p2 = m_transform.VectorRotate( p1 ); + // NOTE: this is looks silly but it works for some reasons: + // bbox are symetric and stored in local space + // signbits are detected normals for bbox side tests + int j = SignbitsForPlane( -p2 ); + m_vecOffsets[j] = p2; + total_signbits += j; + } + + if( total_signbits != 28 ) + { + ALERT( at_error, "total signbits %d != 28 (mins %g %g %g) maxs( %g %g %g)\n", + total_signbits, lmins.x, lmins.y, lmins.z, lmaxs.x, lmaxs.y, lmaxs.z ); + ALERT( at_error, "rotated angles %g %g %g\n", -m_vecAngles.x, m_vecAngles.y, m_vecAngles.z ); + + for( i = 0; i < 8; i++ ) + { + Vector p1, p2; + p1.x = ( i & 1 ) ? lmins[0] : lmaxs[0]; + p1.y = ( i & 2 ) ? lmins[1] : lmaxs[1]; + p1.z = ( i & 4 ) ? lmins[2] : lmaxs[2]; + + p2 = m_transform.VectorRotate( p1 ); + // NOTE: this is looks silly but it works for some reasons: + // bbox are symetric and stored in local space + // signbits are detected normals for bbox side tests + int j = SignbitsForPlane( -p2 ); + Msg( "#%i (%g %g %g) -> (%g %g %g), signbits %d\n", i, p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, j ); + } + } + } + else + { + // just reset offsets + memset( m_vecOffsets, 0, sizeof( m_vecOffsets )); + } + + // rotated sphere offset for capsule + m_flSphereOffset[0] = m_transform[2][0] * t; + m_flSphereOffset[1] = -m_transform[2][1] * t; + m_flSphereOffset[2] = m_transform[2][2] * t; + + // now calculate the local bbox + ClearBounds( lmins, lmaxs ); + for( i = 0; i < 8; i++ ) + AddPointToBounds( m_vecOffsets[i], lmins, lmaxs ); + + material = NULL; + m_vecTraceDirection = m_vecEnd - m_vecStart; + m_flTraceDistance = m_vecTraceDirection.Length(); + m_vecTraceDirection = m_vecTraceDirection.Normalize(); + + // build a bounding box of the entire move + ClearBounds( m_vecAbsMins, m_vecAbsMaxs ); + AddPointToBounds( m_vecStart + lmins, m_vecAbsMins, m_vecAbsMaxs ); + AddPointToBounds( m_vecStart + lmaxs, m_vecAbsMins, m_vecAbsMaxs ); + AddPointToBounds( m_vecEnd + lmins, m_vecAbsMins, m_vecAbsMaxs ); + AddPointToBounds( m_vecEnd + lmaxs, m_vecAbsMins, m_vecAbsMaxs ); + + // spread min\max by a pixel + for( i = 0; i < 3; i++ ) + { + m_vecAbsMins[i] -= 1.0f; + m_vecAbsMaxs[i] += 1.0f; + } + + // use untransformed values to avoid FP rounding errors + if( mins == maxs ) + bIsTraceLine = true; + else bIsTraceLine = false; + + if( start == end ) + bIsTestPosition = true; + else bIsTestPosition = false; +} + +void TraceMesh :: ClipBoxToFacet( mfacet_t *facet ) +{ + mplane_t *p, *clipplane; + float enterfrac, leavefrac, distfrac; + mstudiotexture_t *ptexture; + bool getout, startout; + Vector startp, endp; + float d, d1, d2, f; + + if( !facet->numplanes ) + return; + + ptexture = GetTextureForFacet( facet ); + + if( !bIsTraceLine && ptexture ) + { + if( FBitSet( ptexture->flags, STUDIO_NF_MASKED ) && !FBitSet( ptexture->flags, STUDIO_NF_ALPHASOLID )) + return; + } + + enterfrac = -1.0f; + leavefrac = 1.0f; + clipplane = NULL; + checkcount++; + + getout = false; + startout = false; + + for( int i = 0; i < facet->numplanes; i++ ) + { + p = &mesh->planes[facet->indices[i]]; + + if( bUseCapsule ) + { + // adjust the plane distance apropriately for radius + float dist = p->dist + m_flSphereRadius; + // find the closest point on the capsule to the plane + float t = DotProduct( p->normal, m_flSphereOffset ); + + if( t > 0.0f ) + { + startp = m_vecStart - m_flSphereOffset; + endp = m_vecEnd - m_flSphereOffset; + } + else + { + startp = m_vecStart + m_flSphereOffset; + endp = m_vecEnd + m_flSphereOffset; + } + + d1 = DotProduct( startp, p->normal ) - dist; + d2 = DotProduct( endp, p->normal ) - dist; + } + else + { + // adjust the plane distance apropriately for mins/maxs + float dist = p->dist - DotProduct( m_vecOffsets[p->signbits], p->normal ); + + d1 = DotProduct( m_vecStart, p->normal ) - dist; + d2 = DotProduct( m_vecEnd, p->normal ) - dist; + } + + if( d2 > 0.0f ) getout = true; // endpoint is not in solid + if( d1 > 0.0f ) startout = true; + + // if completely in front of face, no intersection + if( d1 > 0 && d2 >= d1 ) + return; + + if( d1 <= 0 && d2 <= 0 ) + continue; + + // crosses face + d = 1.0f / (d1 - d2); + f = d1 * d; + + if( d > 0.0f ) + { + // enter + if( f > enterfrac ) + { + distfrac = d; + enterfrac = f; + clipplane = p; + } + } + else if( d < 0.0f ) + { + // leave + if( f < leavefrac ) + leavefrac = f; + } + } + + if( !startout ) + { + // original point was inside brush + trace->startsolid = true; + if( !getout ) trace->allsolid = true; + return; + } + + if( enterfrac - FRAC_EPSILON <= leavefrac ) + { + if( enterfrac > -1.0f && enterfrac < m_flRealFraction ) + { + if( enterfrac < 0 ) enterfrac = 0; + m_flRealFraction = enterfrac; + trace->plane.normal = clipplane->normal; + trace->plane.dist = clipplane->dist; + trace->fraction = enterfrac - DIST_EPSILON * distfrac; + material = GetMaterialForFacet( facet ); // material was hit + } + } +} + +void TraceMesh :: TestBoxInFacet( mfacet_t *facet ) +{ + mstudiotexture_t *ptexture; + Vector startp; + float d1; + mplane_t *p; + + if( !facet->numplanes ) + return; + + ptexture = GetTextureForFacet( facet ); + + if( !bIsTraceLine && ptexture ) + { + if( FBitSet( ptexture->flags, STUDIO_NF_MASKED ) && !FBitSet( ptexture->flags, STUDIO_NF_ALPHASOLID )) + return; + } + + checkcount++; + + for( int i = 0; i < facet->numplanes; i++ ) + { + p = &mesh->planes[facet->indices[i]]; + + if( bUseCapsule ) + { + // adjust the plane distance apropriately for radius + float dist = p->dist + m_flSphereRadius; + // find the closest point on the capsule to the plane + float t = DotProduct( p->normal, m_flSphereOffset ); + if( t > 0.0f ) startp = m_vecStart - m_flSphereOffset; + else startp = m_vecStart + m_flSphereOffset; + d1 = DotProduct( startp, p->normal ) - dist; + } + else + { + // adjust the plane distance apropriately for mins/maxs + float dist = p->dist - DotProduct( m_vecOffsets[p->signbits], p->normal ); + d1 = DotProduct( m_vecStart, p->normal ) - dist; + } + + // if completely in front of face, no intersection + if( d1 > 0 ) return; + } + + // inside this brush + m_flRealFraction = 0.0f; + trace->startsolid = true; + trace->allsolid = true; +} + +bool TraceMesh :: ClipRayToFacet( const mfacet_t *facet ) +{ + // begin calculating determinant - also used to calculate u parameter + Vector pvec = CrossProduct( m_vecTraceDirection, facet->edge2 ); + + // if determinant is near zero, trace lies in plane of triangle + float det = DotProduct( facet->edge1, pvec ); + + // the non-culling branch + if( fabs( det ) < COPLANAR_EPSILON ) + return false; + + checkcount++; + + Vector n = CrossProduct( facet->edge2, facet->edge1 ); + Vector p = m_vecEnd - m_vecStart; + + // calculate distance from first vertex to ray origin + Vector tvec = m_vecStart - facet->triangle[0].point; + + float d1 = -DotProduct( n, tvec ); + float d2 = DotProduct( n, p ); + + if( fabs( d2 ) < 0.0001 ) + return false; + + // get intersect point of ray with triangle plane + float frac = d1 / d2; + + if( frac <= 0.0f ) return false; + if( frac > m_flRealFraction ) + return false; // we have hit something earlier + + float invDet = 1.0f / det; + bool force_solid = false; + + // calculate u parameter and test bounds + float u = DotProduct( tvec, pvec ) * invDet; + if( u < -BARY_EPSILON || u > ( 1.0f + BARY_EPSILON )) + return false; + + // prepare to test v parameter + Vector qvec = CrossProduct( tvec, facet->edge1 ); + + // calculate v parameter and test bounds + float v = DotProduct( m_vecTraceDirection, qvec ) * invDet; + if( v < -BARY_EPSILON || ( u + v ) > ( 1.0f + BARY_EPSILON )) + return false; + + // calculate t (depth) + float depth = DotProduct( facet->edge2, qvec ) * invDet; + if( depth <= 0.001f || depth >= m_flTraceDistance ) + return false; + + n = n.Normalize(); + + mstudiotexture_t *ptexture = GetTextureForFacet( facet ); + + if( ptexture && !FBitSet( ptexture->flags, STUDIO_NF_MASKED )) + force_solid = true; + +#ifdef CLIENT_DLL + // FIXME: how to handle trace flags on the client? +#else + if( FBitSet( gpGlobals->trace_flags, FTRACE_IGNORE_ALPHATEST )) + force_solid = true; +#endif + // most surfaces are completely opaque + if( !ptexture || !ptexture->index || force_solid ) + { + trace->fraction = m_flRealFraction = frac; + material = GetMaterialForFacet( facet ); // material was hit + + if( DotProduct( m_vecTraceDirection, n ) >= 0.0f ) + trace->plane.normal = -n; + else trace->plane.normal = n; + + return true; + } + + // try to avoid double shadows near triangle seams + if( u < -ASLF_EPSILON || u > ( 1.0f + ASLF_EPSILON ) || v < -ASLF_EPSILON || ( u + v ) > ( 1.0f + ASLF_EPSILON )) + return false; + + // calculate w parameter + float w = 1.0f - ( u + v ); + + // calculate st from uvw (barycentric) coordinates + float s = w * facet->triangle[0].st[0] + u * facet->triangle[1].st[0] + v * facet->triangle[2].st[0]; + float t = w * facet->triangle[0].st[1] + u * facet->triangle[1].st[1] + v * facet->triangle[2].st[1]; + s = s - floor( s ); + t = t - floor( t ); + + int is = s * ptexture->width; + int it = t * ptexture->height; + + if( is < 0 ) is = 0; + if( it < 0 ) it = 0; + + if( is > ptexture->width - 1 ) + is = ptexture->width - 1; + if( it > ptexture->height - 1 ) + it = ptexture->height - 1; + + byte *pixels = (byte *)GET_TEXTURE_DATA( ptexture->index ); + + // test pixel + if( pixels && pixels[it * ptexture->width + is] == 0xFF ) + return false; // last color in palette is indicated alpha-pixel + + trace->fraction = m_flRealFraction = frac; + material = GetMaterialForFacet( facet ); // material was hit + + if( DotProduct( m_vecTraceDirection, n ) >= 0.0f ) + trace->plane.normal = -n; + else trace->plane.normal = n; + + return true; +} + +void TraceMesh :: ClipToLinks( areanode_t *node ) +{ + link_t *l, *next; + mfacet_t *facet; + + // touch linked edicts + for( l = node->solid_edicts.next; l != &node->solid_edicts; l = next ) + { + next = l->next; + + facet = FACET_FROM_AREA( l ); + + if( !BoundsIntersect( m_vecAbsMins, m_vecAbsMaxs, facet->mins, facet->maxs )) + continue; + + // might intersect, so do an exact clip + if( !m_flRealFraction ) return; + + if( bIsTestPosition ) + TestBoxInFacet( facet ); + else if( bIsTraceLine ) + ClipRayToFacet( facet ); + else ClipBoxToFacet( facet ); + } + + // recurse down both sides + if( node->axis == -1 ) return; + + if( m_vecAbsMaxs[node->axis] > node->dist ) + ClipToLinks( node->children[0] ); + if( m_vecAbsMins[node->axis] < node->dist ) + ClipToLinks( node->children[1] ); +} + +bool TraceMesh :: DoTrace( void ) +{ + if( !mesh || !BoundsIntersect( mesh->mins, mesh->maxs, m_vecAbsMins, m_vecAbsMaxs )) + return false; // invalid mesh or no intersection + + checkcount = 0; + + if( areanodes ) + { + ClipToLinks( areanodes ); + } + else + { + mfacet_t *facet = mesh->facets; + for( int i = 0; i < mesh->numfacets; i++, facet++ ) + { + if( bIsTestPosition ) + TestBoxInFacet( facet ); + else if( bIsTraceLine ) + ClipRayToFacet( facet ); + else ClipBoxToFacet( facet ); + + if( !m_flRealFraction ) + break; + } + } + +// ALERT( at_aiconsole, "total %i checks for %s\n", checkcount, areanodes ? "tree" : "brute force" ); + + trace->plane.normal = m_transform.VectorIRotate( trace->plane.normal ).Normalize(); + trace->fraction = bound( 0.0f, trace->fraction, 1.0f ); + if( trace->fraction == 1.0f ) trace->endpos = m_vecSrcEnd; + else VectorLerp( m_vecSrcStart, trace->fraction, m_vecSrcEnd, trace->endpos ); + trace->plane.dist = DotProduct( trace->endpos, trace->plane.normal ); + + return (trace->fraction != 1.0f); +} \ No newline at end of file diff --git a/pm_shared/trace.h b/pm_shared/trace.h new file mode 100644 index 0000000..513fa81 --- /dev/null +++ b/pm_shared/trace.h @@ -0,0 +1,85 @@ +/* +trace.h - trace triangle meshes +Copyright (C) 2012 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef TRACEMESH_H +#define TRACEMESH_H + +#include "vector.h" +#include "meshdesc.h" + +#define FRAC_EPSILON (1.0f / 32.0f) +#define BARY_EPSILON 0.01f +#define ASLF_EPSILON 0.0001f +#define COPLANAR_EPSILON 0.25f + +class TraceMesh +{ +private: + Vector m_vecSrcStart, m_vecSrcEnd; + Vector m_vecAbsMins, m_vecAbsMaxs; + Vector m_vecStart, m_vecEnd; + vec3_t m_vecTraceDirection;// ray direction + Vector m_vecOffsets[8]; // for fast signbits tests + float m_flSphereRadius; // capsule + Vector m_flSphereOffset; + float m_flTraceDistance; + float m_flRealFraction; + bool m_bHitTriangle; // now we hit triangle + bool bIsTestPosition; + bool bIsTraceLine; // more accurate than ClipBoxToFacet + bool bUseCapsule; // use capsule instead of bbox + areanode_t *areanodes; // AABB for static meshes + mmesh_t *mesh; // mesh to trace + trace_t *trace; // output + mstudiomaterial_t *material; // pointer to texture for special effects + int checkcount; // debug + Vector m_vecOrigin; + Vector m_vecAngles; + Vector m_vecScale; + int m_iSkin; + int m_iBody; + matrix4x4 m_transform; + model_t *m_pModel; +public: + TraceMesh() { mesh = NULL; } + ~TraceMesh() {} + + // trace stuff + void SetTraceMesh( mmesh_t *cached_mesh, areanode_t *tree, int modelindex ); + void SetMeshOrientation( const Vector &pos, const Vector &ang, const Vector &xform ) + { + m_vecOrigin = pos, m_vecAngles = ang, m_vecScale = xform; + m_iBody = m_iSkin = 0; // reset it + } + + void SetMeshParams( int body, int skin ) + { + m_iBody = body; + m_iSkin = skin; + } + + void SetupTrace( const Vector &start, const Vector &mins, const Vector &maxs, const Vector &end, trace_t *trace ); + mstudiomaterial_t *GetLastHitSurface( void ) { return material; } + mstudiomaterial_t *GetMaterialForFacet( const mfacet_t *facet ); + mstudiotexture_t *GetTextureForFacet( const mfacet_t *facet ); + bool ClipRayToFacet( const mfacet_t *facet ); + void ClipBoxToFacet( mfacet_t *facet ); + void TestBoxInFacet( mfacet_t *facet ); + bool IsTrans( const mfacet_t *facet ); + void ClipToLinks( areanode_t *node ); + bool DoTrace( void ); +}; + +#endif//TRACEMESH_H \ No newline at end of file diff --git a/release.bat b/release.bat new file mode 100644 index 0000000..20ecbf7 --- /dev/null +++ b/release.bat @@ -0,0 +1,114 @@ +@echo off + +set MSDEV=BuildConsole +set CONFIG=/ShowTime /ShowAgent /nologo /cfg= +set MSDEV=msdev +set CONFIG=/make +set build_type=release +set BUILD_ERROR= +call vcvars32 + +%MSDEV% cl_dll/cl_dll.dsp %CONFIG%"cl_dll - Win32 Release" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% dlls/hl.dsp %CONFIG%"hl - Win32 Release" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% mainui/mainui.dsp %CONFIG%"mainui - Win32 Release" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/bsp31migrate/bsp31migrate.dsp %CONFIG%"bsp31migrate - Win32 Release" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/hlmv/hlmv.dsp %CONFIG%"hlmv - Win32 Release" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/p2csg/p2csg.dsp %CONFIG%"p2csg - Win32 Release" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/p2bsp/p2bsp.dsp %CONFIG%"p2bsp - Win32 Release" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/p2vis/p2vis.dsp %CONFIG%"p2vis - Win32 Release" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/p2rad/dlight.dsp %CONFIG%"dlight - Win32 Release" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/p2rad/hlrad.dsp %CONFIG%"hlrad - Win32 Release" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/p2rad/p1rad.dsp %CONFIG%"p1rad - Win32 Release" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/p2rad/p2rad.dsp %CONFIG%"p2rad - Win32 Release" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/makefont/makefont.dsp %CONFIG%"makefont - Win32 Release" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/maketex/maketex.dsp %CONFIG%"maketex - Win32 Release" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/makewad/makewad.dsp %CONFIG%"makewad - Win32 Release" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/decal2tga/decal2tga.dsp %CONFIG%"decal2tga - Win32 Release" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/reqtest/reqtest.dsp %CONFIG%"reqtest - Win32 Release" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/spritegen/spritegen.dsp %CONFIG%"spritegen - Win32 Release" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/stalker2tga/stalker2tga.dsp %CONFIG%"stalker2tga - Win32 Release" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +%MSDEV% utils/studiomdl/studiomdl.dsp %CONFIG%"studiomdl - Win32 Release" %build_target% +if errorlevel 1 set BUILD_ERROR=1 + +if "%BUILD_ERROR%"=="" goto build_ok + +echo ********************* +echo ********************* +echo *** Build Errors! *** +echo ********************* +echo ********************* +echo press any key to exit +echo ********************* +pause>nul +goto done + + +@rem +@rem Successful build +@rem +:build_ok + +rem //delete log files +if exist dlls\hl.plg del /f /q dlls\hl.plg +if exist cl_dll\cl_dll.plg del /f /q cl_dll\cl_dll.plg +if exist mainui\mainui.plg del /f /q mainui\mainui.plg +if exist utils\bsp31migrate\bsp31migrate.plg del /f /q utils\bsp31migrate\bsp31migrate.plg +if exist utils\hlmv\hlmv.plg del /f /q utils\hlmv\hlmv.plg +if exist utils\makefont\makefont.plg del /f /q utils\makefont\makefont.plg +if exist utils\maketex\maketex.plg del /f /q utils\maketex\maketex.plg +if exist utils\makewad\makewad.plg del /f /q utils\makewad\makewad.plg +if exist utils\p2csg\p2csg.plg del /f /q utils\p2csg\p2csg.plg +if exist utils\p2bsp\p2bsp.plg del /f /q utils\p2bsp\p2bsp.plg +if exist utils\p2vis\p2vis.plg del /f /q utils\p2vis\p2vis.plg +if exist utils\p2rad\dlight.plg del /f /q utils\p2rad\dlight.plg +if exist utils\p2rad\hlrad.plg del /f /q utils\p2rad\hlrad.plg +if exist utils\p2rad\p1rad.plg del /f /q utils\p2rad\p1rad.plg +if exist utils\p2rad\p2rad.plg del /f /q utils\p2rad\p2rad.plg +if exist utils\decal2tga\decal2tga.plg del /f /q utils\decal2tga\decal2tga.plg +if exist utils\reqtest\reqtest.plg del /f /q utils\reqtest\reqtest.plg +if exist utils\stalker2tga\stalker2tga.plg del /f /q utils\stalker2tga\stalker2tga.plg +if exist utils\spritegen\spritegen.plg del /f /q utils\spritegen\spritegen.plg +if exist utils\studiomdl\studiomdl.plg del /f /q utils\studiomdl\studiomdl.plg + +echo +echo Build succeeded! +echo +:done \ No newline at end of file diff --git a/renderer.txt b/renderer.txt new file mode 100644 index 0000000..14273cb --- /dev/null +++ b/renderer.txt @@ -0,0 +1,5 @@ +1. implement studio LOD-system OK +2. fix studio bump-mapping + +1. match light values with lightmap levels +2. implement shadow maps \ No newline at end of file diff --git a/todo.txt b/todo.txt new file mode 100644 index 0000000..4f2b548 --- /dev/null +++ b/todo.txt @@ -0,0 +1,77 @@ +remove old renderer - OK +build smooth TBN like in HLFX 0.6 - OK +build env_cubemap system - OK +handle new lightstyles system - OK +do revision for all shaders - OK +optimize new renderer - OK +new grass system - OK +add new punch system - OK +control over player speed - OK +control over player jump height - OK +kill shit message about NULL model - OK +remove hardcoded names from hud - OK +do revision for light shaders - OK +optimize light pass - OK +create shaders for dlight LQ specular - OK +optimize shadow pass - OK +gloss for studio transparent objects - OK +move decal POM settings into script - OK +do revision for decal shaders - OK +make gloss for decals - OK +gloss for bmodel transparent objects - OK +crop matrix for shadows from sun - OK +optimize sky drawing - OK +remove hdrl - OK +remove bloom - OK +implement god-rays - OK +fix flashlight disappearing on p_savior2 - OK +fix rain visibility check - OK +fix studio chrome parazite lighting - OK +fix tubes lighting artifacts - OK +update matdesc documentation - OK +flags SF_NOSHADOW and SF_NOBUMP for env_dynlight - OK +gloss lighting models for each poly - OK +write documentation for entities - OK +remove unneeded pglDepthMask - OK +remove triapiobjects stuff - OK +finalize custom particles - OK +check all extensions that needs for correct work - OK +unload grass correctly - OK +full renderer revision - OK +beta-testing for other videocards - OK +fixup point shadows on radeons - OK +create ubershaders for decals - OK +do revision of post-process filtering - OK +create ubershaders for all studio models - OK +correct screen copy for translucent studio - OK +create uber-shaders for studio decals - OK +do revision of all sunlight shaders - OK +finalize occlusion query - OK +fix bug with sun light & studio alpha test blink - OK +convert textures to DDS - OK +local beta-testing - OK +global beta-testing - OK +create compilers for VHLT v34 - OK +final beta-testing and release test version - OK +create tool for custom DXT encoding - OK +pass the encoding param into renderer - OK +optimize world rendering +update BSP file format (lightprobes, worldlights) - OK +plane hashing - OK +new lighting for studiomodels - OK +new shaders +new dynamic lighting +allow maketex to compress cubemaps - OK + +compilers +blur direct light - OK +build filesystem for radiosity - OK +make advanced trace for rad - OK +diffuse_light for sky - OK +fix bug with rad-pathes - OK +store deluxemap - OK +store worldlights - OK +store shadow occlusion - OK +store worldlightvismatrix - OK +fix bug in CSG (invalid planes) - OK +finalize vertex lighting - OK \ No newline at end of file diff --git a/utils/bsp31migrate/bsp31migrate.cpp b/utils/bsp31migrate/bsp31migrate.cpp new file mode 100644 index 0000000..7f0882f --- /dev/null +++ b/utils/bsp31migrate/bsp31migrate.cpp @@ -0,0 +1,33 @@ +/* +bsp31migrate.cpp - bsp tool main file +Copyright (C) 2016 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "bsp31migrate.h" + +int main( int argc, char **argv ) +{ + int i; + +// if( argc <= 1 ) +// COM_FatalError( "usage: bsp31migrate -file -output \n" ); + + COM_InitCmdlib( argv, argc ); + + if(( i = COM_CheckParm( "-dev" )) != 0 ) + { + SetDeveloperLevel( atoi( argv[i+1] )); + } + + return BspConvert( argc, argv ); +} \ No newline at end of file diff --git a/utils/bsp31migrate/bsp31migrate.dsp b/utils/bsp31migrate/bsp31migrate.dsp new file mode 100644 index 0000000..dbc5774 --- /dev/null +++ b/utils/bsp31migrate/bsp31migrate.dsp @@ -0,0 +1,190 @@ +# Microsoft Developer Studio Project File - Name="bsp31migrate" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=bsp31migrate - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "bsp31migrate.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "bsp31migrate.mak" CFG="bsp31migrate - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "bsp31migrate - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "bsp31migrate - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "bsp31migrate - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\..\temp\bsp31migrate\!release" +# PROP Intermediate_Dir "..\..\temp\bsp31migrate\!release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O1 /Ob2 /I "../common" /I "../../common" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 msvcrt.lib /nologo /subsystem:console /pdb:none /machine:I386 /nodefaultlib:"libc" +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\bsp31migrate\!release +InputPath=\Paranoia2\src_main\temp\bsp31migrate\!release\bsp31migrate.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\bsp31migrate.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\bsp31migrate.exe "D:\Paranoia2\tools\bsp31migrate.exe" + +# End Custom Build + +!ELSEIF "$(CFG)" == "bsp31migrate - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "bsp31migrate___Win32_Debug" +# PROP BASE Intermediate_Dir "bsp31migrate___Win32_Debug" +# PROP BASE Ignore_Export_Lib 0 +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\..\temp\bsp31migrate\!debug" +# PROP Intermediate_Dir "..\..\temp\bsp31migrate\!debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /Ob2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c +# SUBTRACT BASE CPP /YX +# ADD CPP /nologo /MTd /W3 /Gi /GX /ZI /Od /I "../common" /I "../../common" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FAs /FR /FD /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /pdb:none /machine:I386 /out:"bsp31migrate.exe" +# ADD LINK32 user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /incremental:yes /debug /machine:I386 +# SUBTRACT LINK32 /pdb:none +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\bsp31migrate\!debug +InputPath=\Paranoia2\src_main\temp\bsp31migrate\!debug\bsp31migrate.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\bsp31migrate.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\bsp31migrate.exe "D:\Paranoia2\tools\bsp31migrate.exe" + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "bsp31migrate - Win32 Release" +# Name "bsp31migrate - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\bsp31migrate.cpp +# End Source File +# Begin Source File + +SOURCE=.\bspfile.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\cmdlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\conprint.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\filesystem.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\mathlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\scriplib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\stringlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\wadfile.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\zone.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\bsp31migrate.h +# End Source File +# Begin Source File + +SOURCE=..\common\cmdlib.h +# End Source File +# Begin Source File + +SOURCE=..\common\conprint.h +# End Source File +# Begin Source File + +SOURCE=..\common\filesystem.h +# End Source File +# Begin Source File + +SOURCE=..\common\mathlib.h +# End Source File +# Begin Source File + +SOURCE=..\common\scriplib.h +# End Source File +# Begin Source File + +SOURCE=..\common\stringlib.h +# End Source File +# End Group +# End Target +# End Project diff --git a/utils/bsp31migrate/bsp31migrate.h b/utils/bsp31migrate/bsp31migrate.h new file mode 100644 index 0000000..ac11a68 --- /dev/null +++ b/utils/bsp31migrate/bsp31migrate.h @@ -0,0 +1,49 @@ +/* +bsp31migrate.h - bsp tool header file +Copyright (C) 2016 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef BSP31MIGRATE_H +#define BSP31MIGRATE_H + +#include +#include "cmdlib.h" +#include "mathlib.h" +#include "stringlib.h" +#include "filesystem.h" +#include "scriplib.h" +#include "bspfile.h" +#include "wadfile.h" +#include "bspfile31.h" +#include +#include + +typedef enum +{ + MAP_UNKNOWN = 0, // Unrecognized map type + MAP_XASH31, // Like GoldSrc but have HQ Lightmaps (8 texels per luxel) and two additional lumps for clipnodes +} map_type; + +typedef enum +{ + MAP_NORMAL = 0, + MAP_HLFX06, // HLFX 0.6 expansion + MAP_XASHXT_OLD, // extraversion 1 + MAP_P2SAVIOR, // extraversion 2 has smoothed TBN space, cubemap positions etc + MAP_DEPRECATED, // extraversion 3 (not supported) + MAP_XASH3D_EXT, // extraversion 4 (generic extension) +} sub_type; + +extern int BspConvert( int argc, char **argv ); + +#endif//BSP31MIGRATE_H \ No newline at end of file diff --git a/utils/bsp31migrate/bspfile.cpp b/utils/bsp31migrate/bspfile.cpp new file mode 100644 index 0000000..129da1c --- /dev/null +++ b/utils/bsp31migrate/bspfile.cpp @@ -0,0 +1,1212 @@ +/* +bspfile.cpp - load, convert & write bsp +Copyright (C) 2016 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "bsp31migrate.h" +#include "filesystem.h" + +#define VALVE_FORMAT 220 + +map_type g_maptype = MAP_UNKNOWN; +sub_type g_subtype = MAP_NORMAL; + +//============================================================================= +bool g_found_extradata = false; + +int g_nummodels; +dmodel_t g_dmodels[MAX_MAP_MODELS]; + +int g_visdatasize; +byte g_dvisdata[MAX_MAP_VISIBILITY]; + +int g_lightdatasize; +byte *g_dlightdata; + +int g_deluxdatasize; +byte *g_ddeluxdata; + +int g_shadowdatasize; +byte *g_dshadowdata; + +int g_texdatasize; +byte* g_dtexdata; // (dmiptexlump_t) + +int g_entdatasize; +char g_dentdata[MAX_MAP_ENTSTRING]; + +int g_numleafs; +dleaf_t g_dleafs[MAX_MAP_LEAFS]; + +int g_numplanes; +dplane_t g_dplanes[MAX_INTERNAL_MAP_PLANES]; + +int g_numvertexes; +dvertex_t g_dvertexes[MAX_MAP_VERTS]; + +int g_numnodes; +dnode_t g_dnodes[MAX_MAP_NODES]; + +int g_numtexinfo; +dtexinfo_t g_texinfo[MAX_INTERNAL_MAP_TEXINFO]; + +int g_numfaces; +dface_t g_dfaces[MAX_MAP_FACES]; + +int g_numclipnodes; +dclipnode_t g_dclipnodes[MAX_MAP_CLIPNODES]; +dclipnode32_t g_dclipnodes32[MAX_MAP_CLIPNODES32]; + +int g_numedges; +dedge_t g_dedges[MAX_MAP_EDGES]; + +int g_nummarksurfaces; +dmarkface_t g_dmarksurfaces[MAX_MAP_MARKSURFACES]; + +int g_numsurfedges; +int g_dsurfedges[MAX_MAP_SURFEDGES]; + +int g_numfaceinfo; +dfaceinfo_t g_dfaceinfo[MAX_MAP_FACEINFO]; + +int g_numnormals; +dnormal_t g_dnormals[MAX_MAP_NORMS]; + +int g_numcubemaps; +dcubemap_t g_dcubemaps[MAX_MAP_CUBEMAPS]; + +int g_numleaflights; +dleafsample_t g_dleaflights[MAX_MAP_LEAFLIGHTS]; + +int g_numworldlights; +dworldlight_t g_dworldlights[MAX_MAP_WORLDLIGHTS]; + +int g_vlightdatasize; +byte *g_dvlightdata; // (dvlightlump_t) + +int g_numfacedata; +dfacedata_t g_dfacedata[MAX_MAP_FACES]; + +int g_numTNbasis; +dTNbasis_t g_dTNbasis[MAX_MAP_TNBASIS]; + +int g_bumpdatasize; +byte *g_dbumpdata; + +int g_numentities; +entity_t g_entities[MAX_MAP_ENTITIES]; + +char g_mapinfo[8192]; +char g_mapname[1024]; +int g_mapversion; +byte *mod_base; + +void PrintMapInfo( void ) +{ + size_t infolen = Q_strlen( g_mapinfo ); + char *ptr = &g_mapinfo[infolen-2]; + + if( *ptr == ',' ) *ptr = '.'; + + Msg( "Map name: %s", g_mapname ); + Msg( "\nMap type: " ); + + switch( g_maptype ) + { + case MAP_XASH31: + Msg( "^2XashXT BSP31^7" ); + break; + default: + COM_FatalError( "%s unknown map format\n", g_mapname ); + break; + } + + if( g_subtype != MAP_NORMAL ) + Msg( "\nSub type: " ); + else Msg( "\n" ); + + switch( g_subtype ) + { + case MAP_HLFX06: + Msg( "^4HLFX 0.6^7\n" ); + break; + case MAP_XASHXT_OLD: + Msg( "^4XashXT 0.5^7\n" ); + break; + case MAP_P2SAVIOR: + Msg( "^4Paranoia2: Savior^7\n" ); + break; + case MAP_DEPRECATED: + Msg( "^1intermediate deprecated version^7\n" ); + break; + case MAP_XASH3D_EXT: + Msg( "^4Xash3D extended^7\n" ); + break; + } + + if( g_mapinfo[0] ) Msg( "Map info: %s", g_mapinfo ); + + Msg( "\n\n" ); +} + +//============================================================================= + +int CopyLump( int lump, void *dest, int size, dheader31_t *header ) +{ + int length = header->lumps[lump].filelen; + int ofs = header->lumps[lump].fileofs; + + if( length % size ) + COM_FatalError( "LoadBSPFile: odd lump size\n" ); + + // alloc matched size + if( lump == LUMP_TEXTURES ) + dest = g_dtexdata = (byte *)Mem_Alloc( length ); + if( lump == LUMP_LIGHTING ) + dest = g_dlightdata = (byte *)Mem_Alloc( length ); + memcpy( dest, (byte *)header + ofs, length ); + + return length / size; +} + +int CopyLump( int lump, void *dest, int size, dheader31_hlfx_t *header ) +{ + int length = header->lumps[lump].filelen; + int ofs = header->lumps[lump].fileofs; + + if( length % size ) + COM_FatalError( "LoadBSPFile: odd lump size\n" ); + + // alloc matched size + if( lump == LUMP_TEXTURES ) + dest = g_dtexdata = (byte *)Mem_Alloc( length ); + if( lump == LUMP_LIGHTING ) + dest = g_dlightdata = (byte *)Mem_Alloc( length ); + if( lump == HLFX31_LUMP_BUMP ) + dest = g_dbumpdata = (byte *)Mem_Alloc( length ); + memcpy( dest, (byte *)header + ofs, length ); + + return length / size; +} + +static int CopyExtraLump( int lump, void *dest, int size, const dheader31_t *header ) +{ + dextrahdr_t *extrahdr = (dextrahdr_t *)((byte *)header + sizeof( dheader31_t )); + + int length = extrahdr->lumps[lump].filelen; + int ofs = extrahdr->lumps[lump].fileofs; + + if( length % size ) + COM_FatalError( "LoadBSPFile: odd lump size\n" ); + + if( lump == LUMP_LIGHTVECS ) + dest = g_ddeluxdata = (byte *)Mem_Alloc( length ); + if( lump == LUMP_SHADOWMAP ) + dest = g_dshadowdata = (byte *)Mem_Alloc( length ); + if( lump == LUMP_VERTEX_LIGHT ) + dest = g_dvlightdata = (byte *)Mem_Alloc( length ); + memcpy( dest, (byte *)header + ofs, length ); + + return length / size; +} + +//============================================================================= +static void AddLump( int lumpnum, void *data, size_t len, dheader30_hlfx_t *header, long handle ) +{ + dlump_t *lump = &header->lumps[lumpnum]; + + lump->fileofs = tell( handle ); + lump->filelen = len; + + SafeWrite( handle, data, (len + 3) & ~3 ); +} + +static void AddLump( int lumpnum, void *data, size_t len, dheader_t *header, long handle ) +{ + dlump_t *lump = &header->lumps[lumpnum]; + + lump->fileofs = tell( handle ); + lump->filelen = len; + + SafeWrite( handle, data, (len + 3) & ~3 ); +} + +static void AddExtraLump( int lumpnum, void *data, int len, dextrahdr_t *header, long handle ) +{ + dlump_t* lump = &header->lumps[lumpnum]; + + lump->fileofs = tell( handle ); + lump->filelen = len; + + SafeWrite( handle, data, (len + 3) & ~3 ); +} + +void AddLumpClipnodes( int lumpnum, dheader_t *header, long handle ) +{ + dlump_t *lump = &header->lumps[lumpnum]; + lump->fileofs = tell( handle ); + + if( g_numclipnodes < MAX_MAP_CLIPNODES ) + { + // copy clipnodes into 16-bit array + for( int i = 0; i < g_numclipnodes; i++ ) + { + g_dclipnodes[i].children[0] = (short)g_dclipnodes32[i].children[0]; + g_dclipnodes[i].children[1] = (short)g_dclipnodes32[i].children[1]; + g_dclipnodes[i].planenum = g_dclipnodes32[i].planenum; + } + + lump->filelen = g_numclipnodes * sizeof( dclipnode_t ); + SafeWrite( handle, g_dclipnodes, (lump->filelen + 3) & ~3 ); + } + else + { + // copy clipnodes into 32-bit array + lump->filelen = g_numclipnodes * sizeof( dclipnode32_t ); + SafeWrite( handle, g_dclipnodes32, (lump->filelen + 3) & ~3 ); + } +} + +//============================================ +void StripTrailing( char *e ) +{ + char *s; + + s = e + Q_strlen( e ) - 1; + + while( s >= e && *s <= 32 ) + { + *s = 0; + s--; + } +} + +/* +================ +InsertLinkBefore +================ +*/ +void InsertLinkBefore( epair_t *e, entity_t *mapent ) +{ + e->next = NULL; + + if( mapent->epairs != NULL ) + { + e->prev = mapent->tail; + mapent->tail->next = e; + mapent->tail = e; + } + else + { + mapent->epairs = mapent->tail = e; + e->prev = NULL; + } +} + +/* +================ +UnlinkEpair +================ +*/ +void UnlinkEpair( epair_t *e, entity_t *mapent ) +{ + if( e->prev ) e->prev->next = e->next; + else mapent->epairs = e->next; + + if( e->next ) e->next->prev = e->prev; + else mapent->tail = e->prev; + + e->prev = e->next = e; +} + +/* +================= +ParseEpair +================= +*/ +epair_t *ParseEpair( void ) +{ + const char *ext; + epair_t *e; + + e = (epair_t *)Mem_Alloc( sizeof( epair_t ), C_EPAIR ); + + if( Q_strlen( token ) >= ( MAX_KEY - 1 )) + COM_FatalError( "ParseEpair: 'key' token too long\n" ); + + e->key = copystring( token ); + GetToken( false ); + + if( Q_strlen( token ) >= ( MAX_VALUE - 1 )) + COM_FatalError( "ParseEpair: 'value' token too long\n" ); + ext = COM_FileExtension( token ); + + if( !Q_stricmp( ext, "wav" ) || !Q_stricmp( ext, "mdl" ) || !Q_stricmp( ext, "bsp" )) + COM_FixSlashes( token ); + e->value = copystring( token ); + + // strip trailing spaces + StripTrailing( e->key ); + StripTrailing( e->value ); + + return e; +} + +/* +================ +ParseEntity +================ +*/ +bool ParseEntity( void ) +{ + entity_t *mapent; + epair_t *e; + + if( !GetToken( true )) + return false; + + if( Q_strcmp( token, "{" )) + { + if( g_numentities == 0 ) + COM_FatalError( "ParseEntity: '{' not found\n" ); + else return false; // probably entity string is broken at the end + } + + if( g_numentities == MAX_MAP_ENTITIES ) + COM_FatalError( "MAX_MAP_ENTITIES limit exceeded\n" ); + + mapent = &g_entities[g_numentities]; + g_numentities++; + + do + { + if( !GetToken( true )) + COM_FatalError( "ParseEntity: EOF without closing brace\n" ); + + if( !Q_strcmp( token, "}" )) + break; + + e = ParseEpair(); + InsertLinkBefore( e, mapent ); + + } while( 1 ); + + return true; +} + +/* +================ +ParseEntities + +Parses the dentdata string into entities +================ +*/ +void ParseEntities( void ) +{ + ParseFromMemory( g_dentdata, g_entdatasize ); + memset( &g_entities, 0, sizeof( g_entities )); + g_numentities = 0; + + while( ParseEntity( )); +} + +/* +================ +FreeEntities + +release all the entity data +================ +*/ +void FreeEntity( entity_t *mapent ) +{ + epair_t *ep, *next; + + for( ep = mapent->epairs; ep != NULL; ep = next ) + { + next = ep->next; + freestring( ep->key ); + freestring( ep->value ); + Mem_Free( ep, C_EPAIR ); + } + + if( mapent->cache ) Mem_Free( mapent->cache ); + mapent->epairs = mapent->tail = NULL; + mapent->cache = NULL; +} + +/* +================ +FreeEntities + +release all the dynamically allocated data +================ +*/ +void FreeEntities( void ) +{ + for( int i = 0; i < g_numentities; i++ ) + { + FreeEntity( &g_entities[i] ); + } +} + +/* +================ +UnparseEntities + +Generates the dentdata string from all the entities +================ +*/ +void UnparseEntities( void ) +{ + char *buf, *end; + char line[2048]; + char key[1024], value[1024]; + epair_t *ep; + + buf = g_dentdata; + end = buf; + *end = 0; + + for( int i = 0; i < g_numentities; i++ ) + { + entity_t *ent = &g_entities[i]; + if( !ent->epairs ) continue; // ent got removed + + Q_strcat( end, "{\n" ); + end += 2; + + for( ep = ent->epairs; ep; ep = ep->next ) + { + Q_strncpy( key, ep->key, sizeof( key )); + StripTrailing( key ); + Q_strncpy( value, ep->value, sizeof( value )); + StripTrailing( value ); + + Q_snprintf( line, sizeof( line ), "\"%s\" \"%s\"\n", key, value ); + Q_strcat( end, line ); + end += Q_strlen( line ); + } + + Q_strcat( end, "}\n" ); + end += 2; + + if( end > ( buf + MAX_MAP_ENTSTRING )) + COM_FatalError( "Entity text too long\n" ); + } + + g_entdatasize = end - buf + 1; // write term +} + +void RemoveKey( entity_t *ent, const char *key ) +{ + epair_t *ep; + + for( ep = ent->epairs; ep; ep = ep->next ) + { + if( !Q_strcmp( ep->key, key )) + { + freestring( ep->key ); + freestring( ep->value ); + UnlinkEpair( ep, ent ); + Mem_Free( ep, C_EPAIR ); + return; + } + } +} + +void SetKeyValue( entity_t *ent, const char *key, const char *value ) +{ + epair_t *ep; + + for( ep = ent->epairs; ep; ep = ep->next ) + { + if( !Q_strcmp( ep->key, key )) + { + freestring( ep->value ); + ep->value = copystring( value ); + return; + } + } + + ep = (epair_t *)Mem_Alloc( sizeof( epair_t ), C_EPAIR ); + ep->key = copystring( key ); + ep->value = copystring( value ); + InsertLinkBefore( ep, ent ); +} + +char *ValueForKey( entity_t *ent, const char *key, bool check ) +{ + epair_t *ep; + + for( ep = ent->epairs; ep; ep = ep->next ) + { + if( !Q_strcmp( ep->key, key )) + return ep->value; + } + + if( check ) + return NULL; + return ""; +} + +vec_t FloatForKey( entity_t *ent, const char *key ) +{ + return atof( ValueForKey( ent, key )); +} + +int IntForKey( entity_t *ent, const char *key ) +{ + return atoi( ValueForKey( ent, key )); +} + +bool BoolForKey( entity_t *ent, const char *key ) +{ + if( atoi( ValueForKey( ent, key ))) + return true; + return false; +} + +int GetVectorForKey( entity_t *ent, const char *key, vec3_t vec ) +{ + double v1, v2, v3; + int count; + char *k; + + k = ValueForKey( ent, key ); + + // scanf into doubles, then assign, so it is vec_t size independent + v1 = v2 = v3 = 0; + + count = sscanf( k, "%lf %lf %lf", &v1, &v2, &v3 ); + + vec[0] = v1; + vec[1] = v2; + vec[2] = v3; + + return count; +} + +void ReleaseBSPFile( void ) +{ + FreeEntities(); + + g_maptype = MAP_UNKNOWN; + g_subtype = MAP_NORMAL; + + g_nummodels = 0; + memset( g_dmodels, 0, sizeof( g_dmodels )); + + g_visdatasize = 0; + memset( g_dvisdata, 0, sizeof( g_dvisdata )); + + g_lightdatasize = 0; + Mem_Free( g_dlightdata ); + g_dlightdata = NULL; + + g_deluxdatasize = 0; + Mem_Free( g_ddeluxdata ); + g_ddeluxdata = NULL; + + g_texdatasize = 0; + Mem_Free( g_dtexdata ); + g_dtexdata = NULL; + + g_shadowdatasize = 0; + Mem_Free( g_dshadowdata ); + g_dshadowdata = NULL; + + g_vlightdatasize = 0; + Mem_Free( g_dvlightdata ); + g_dvlightdata = NULL; + + g_bumpdatasize = 0; + Mem_Free( g_dbumpdata ); + g_dbumpdata = NULL; + + g_entdatasize = 0; + memset( g_dentdata, 0, sizeof( g_dentdata )); + + g_numleafs = 0; + memset( g_dleafs, 0, sizeof( g_dleafs )); + + g_numplanes = 0; + memset( g_dplanes, 0, sizeof( g_dplanes )); + + g_numvertexes = 0; + memset( g_dvertexes, 0, sizeof( g_dvertexes )); + + g_numnormals = 0; + memset( g_dnormals, 0, sizeof( g_dnormals )); + + g_numnodes = 0; + memset( g_dnodes, 0, sizeof( g_dnodes )); + + g_numtexinfo = 0; + memset( g_texinfo, 0, sizeof( g_texinfo )); + + g_numfaces = 0; + memset( g_dfaces, 0, sizeof( g_dfaces )); + + g_numclipnodes = 0; + memset( g_dclipnodes, 0, sizeof( g_dclipnodes )); + memset( g_dclipnodes32, 0, sizeof( g_dclipnodes32 )); + + g_numedges = 0; + memset( g_dedges, 0, sizeof( g_dedges )); + + g_nummarksurfaces = 0; + memset( g_dmarksurfaces, 0, sizeof( g_dmarksurfaces )); + + g_numsurfedges = 0; + memset( g_dsurfedges, 0, sizeof( g_dsurfedges )); + + g_numentities = 0; + memset( g_entities, 0, sizeof( g_entities )); + + g_numfaceinfo = 0; + memset( g_dfaceinfo, 0, sizeof( g_dfaceinfo )); + + g_numcubemaps = 0; + memset( g_dcubemaps, 0, sizeof( g_dcubemaps )); + + g_numleaflights = 0; + memset( g_dleaflights, 0, sizeof( g_dleaflights )); + + g_numworldlights = 0; + memset( g_dworldlights, 0, sizeof( g_dworldlights )); + + g_mapinfo[0] = '\0'; + g_mapname[0] = '\0'; + g_mapversion = 0; + + Mem_Free( mod_base ); + mod_base = NULL; +} + +// ===================================================================================== +// WriteBSPFile +// Swaps the bsp file in place, so it should not be referenced again +// ===================================================================================== +void WriteBSPFile( const char *filename ) +{ + dheader30_hlfx_t outheader; // long header from HLFX + dheader_t *header; + dheader30_hlfx_t *hlfxhdr; + dextrahdr_t outextrahdr; + dextrahdr_t *extrahdr; + long bspfile; + + header = (dheader_t *)&outheader; + hlfxhdr = &outheader; + memset( &outheader, 0, sizeof( dheader30_hlfx_t )); + extrahdr = &outextrahdr; + memset( extrahdr, 0, sizeof( dextrahdr_t )); + + header->version = BSPVERSION; + extrahdr->id = IDEXTRAHEADER; + extrahdr->version = EXTRA_VERSION; + hlfxhdr->magicID = HLFX_BSP_MAGIC_ID; + + COM_CreatePath( (char *)filename ); + bspfile = SafeOpenWrite( filename ); + + if( g_subtype == MAP_HLFX06 ) + { + SafeWrite( bspfile, hlfxhdr, sizeof( dheader30_hlfx_t )); // overwritten later + } + else + { + SafeWrite( bspfile, header, sizeof( dheader_t )); // overwritten later + SafeWrite( bspfile, extrahdr, sizeof( dextrahdr_t )); // overwritten later + } + + UnparseEntities(); + + // LUMP TYPE DATA LENGTH HEADER BSPFILE + AddLump( LUMP_PLANES, g_dplanes, g_numplanes * sizeof( dplane_t ), header, bspfile ); + AddLump( LUMP_LEAFS, g_dleafs, g_numleafs * sizeof( dleaf_t ), header, bspfile ); + AddLump( LUMP_VERTEXES, g_dvertexes, g_numvertexes * sizeof( dvertex_t ), header, bspfile ); + AddLump( LUMP_NODES, g_dnodes, g_numnodes * sizeof( dnode_t ), header, bspfile ); + AddLump( LUMP_TEXINFO, g_texinfo, g_numtexinfo * sizeof( dtexinfo_t ), header, bspfile ); + AddLump( LUMP_FACES, g_dfaces, g_numfaces * sizeof( dface_t ), header, bspfile ); + AddLump( LUMP_MARKSURFACES, g_dmarksurfaces, g_nummarksurfaces * sizeof( dmarkface_t ), header, bspfile ); + AddLump( LUMP_SURFEDGES, g_dsurfedges, g_numsurfedges * sizeof( dsurfedge_t ), header, bspfile ); + AddLump( LUMP_EDGES, g_dedges, g_numedges * sizeof( dedge_t ), header, bspfile ); + AddLump( LUMP_MODELS, g_dmodels, g_nummodels * sizeof( dmodel_t ), header, bspfile ); + + AddLumpClipnodes( LUMP_CLIPNODES, header, bspfile ); // clipnodes can using 16-bit or 32-bit indexes + + AddLump( LUMP_LIGHTING, g_dlightdata, g_lightdatasize, header, bspfile ); + AddLump( LUMP_VISIBILITY, g_dvisdata, g_visdatasize, header, bspfile ); + AddLump( LUMP_ENTITIES, g_dentdata, g_entdatasize, header, bspfile ); + AddLump( LUMP_TEXTURES, g_dtexdata, g_texdatasize, header, bspfile ); + + if( g_subtype == MAP_HLFX06 ) + { + AddLump( HLFX30_LUMP_FACEINFO, g_dfacedata, g_numfacedata * sizeof( dfacedata_t ), hlfxhdr, bspfile ); + AddLump( HLFX30_LUMP_BUMP, g_dbumpdata, g_bumpdatasize, hlfxhdr, bspfile ); + AddLump( HLFX30_LUMP_TNBASIS, g_dTNbasis, g_numTNbasis * sizeof( dTNbasis_t ), hlfxhdr, bspfile ); + } + else + { + AddExtraLump( LUMP_VERTNORMALS, g_dnormals, g_numnormals * sizeof( dnormal_t ), extrahdr, bspfile ); + AddExtraLump( LUMP_LIGHTVECS, g_ddeluxdata, g_deluxdatasize, extrahdr, bspfile ); + AddExtraLump( LUMP_CUBEMAPS, g_dcubemaps, g_numcubemaps * sizeof( dcubemap_t ), extrahdr, bspfile ); + AddExtraLump( LUMP_FACEINFO, g_dfaceinfo, g_numfaceinfo * sizeof( dfaceinfo_t ), extrahdr, bspfile ); + AddExtraLump( LUMP_WORLDLIGHTS, g_dworldlights, g_numworldlights * sizeof( dworldlight_t ), extrahdr, bspfile ); + AddExtraLump( LUMP_SHADOWMAP, g_dshadowdata, g_shadowdatasize, extrahdr, bspfile ); + AddExtraLump( LUMP_LEAF_LIGHTING,g_dleaflights, g_numleaflights * sizeof( dleafsample_t ), extrahdr, bspfile ); + AddExtraLump( LUMP_VERTEX_LIGHT, g_dvlightdata, g_vlightdatasize, extrahdr, bspfile ); + } + + lseek( bspfile, 0, SEEK_SET ); + + if( g_subtype == MAP_HLFX06 ) + { + SafeWrite( bspfile, hlfxhdr, sizeof( dheader30_hlfx_t )); + } + else + { + SafeWrite( bspfile, header, sizeof( dheader_t )); + SafeWrite( bspfile, extrahdr, sizeof( dextrahdr_t )); + } + close( bspfile ); + + ReleaseBSPFile(); +} + +/* +================= +Mod_FindModelOrigin + +routine to detect bmodels with origin-brush +================= +*/ +static void Mod_FindModelOrigin( const char *modelname, vec3_t origin ) +{ + VectorClear( origin ); + + if( !g_numentities ) return; + + // skip the world + for( int i = 1; i < g_numentities; i++ ) + { + entity_t *mapent = &g_entities[i]; + + if( !Q_stricmp( modelname, ValueForKey( mapent, "model" ))) + { + GetVectorForKey( mapent, "origin", origin ); + return; + } + } +} + +/* +=============================================================================== + + MAP LOADING + +=============================================================================== +*/ +/* +================= +Mod_LoadSubmodels +================= +*/ +static void Mod_LoadSubmodels( const dlump_t *l ) +{ + dmodel_t *out = g_dmodels; + dmodel_t *in; + int i, j; + + in = (dmodel_t *)(mod_base + l->fileofs); + + if( l->filelen % sizeof( *in )) + COM_FatalError( "Mod_LoadSubmodels: funny lump size\n" ); + g_nummodels = l->filelen / sizeof( *in ); + + if( g_nummodels < 1 ) COM_FatalError( "Map %s without models\n", g_mapname ); + if( g_nummodels > MAX_MAP_MODELS ) COM_FatalError( "Map %s has too many models\n", g_mapname ); + + for( i = 0; i < g_nummodels; i++, in++, out++ ) + { + for( j = 0; j < 3; j++ ) + { + // spread the mins / maxs by a pixel + out->mins[j] = in->mins[j]; + out->maxs[j] = in->maxs[j]; + out->origin[j] = in->origin[j]; + } + + if( i != 0 && VectorCompare( vec3_origin, out->origin )) + Mod_FindModelOrigin( va( "*%i", i ), out->origin ); + + for( j = 0; j < MAX_MAP_HULLS; j++ ) + out->headnode[j] = in->headnode[j]; + + if( in->visleafs >= 65535 ) + COM_FatalError( "Mod_LoadSubmodels: visleafs %i exceeds an unsigned short limit\n", in->visleafs ); + + out->visleafs = in->visleafs; + out->firstface = in->firstface; + out->numfaces = in->numfaces; + } +} + +/* +================= +Mod_LoadEntities +================= +*/ +static void Mod_LoadEntities( const dlump_t *l ) +{ + if( l->filelen > MAX_MAP_ENTSTRING ) + COM_FatalError( "Map %s exceeds MAX_MAP_ENTSTRING\n", g_mapname ); + + memcpy( g_dentdata, mod_base + l->fileofs, l->filelen ); + g_entdatasize = l->filelen; + + ParseEntities(); + + // grab mapversion + g_mapversion = IntForKey( &g_entities[0], "mapversion" ); +} + +/* +================= +Mod_LoadTexInfo +================= +*/ +static void Mod_LoadTexInfo( const dlump_t *l ) +{ + dtexinfo_t *out = g_texinfo; + dtexinfo_t *in; + int i, j; + + in = (dtexinfo_t *)(mod_base + l->fileofs); + if( l->filelen % sizeof( *in )) + COM_FatalError( "Mod_LoadTexInfo: funny lump size in %s\n", g_mapname ); + + g_numtexinfo = l->filelen / sizeof( *in ); + + for( i = 0; i < g_numtexinfo; i++, in++, out++ ) + { + for( j = 0; j < 8; j++ ) + out->vecs[0][j] = in->vecs[0][j]; + + out->miptex = in->miptex; + out->flags = in->flags; + + // tell the engine about hi-res lightmaps + SetBits( out->flags, TEX_EXTRA_LIGHTMAP ); + + if( g_subtype == MAP_XASH3D_EXT ) + out->faceinfo = in->faceinfo; + else out->faceinfo = -1; + } +} + +/* +================= +Mod_LoadDeluxemap +================= +*/ +static void Mod_LoadDeluxemap( void ) +{ + char path[64]; + size_t filesize; + byte *in; + + if( g_subtype == MAP_XASH3D_EXT || g_subtype == MAP_HLFX06 ) + return; // Xash3D ext format have built-in deluxemap + + Q_strncpy( path, g_mapname, sizeof( path )); + COM_StripExtension( path ); + COM_DefaultExtension( path, ".dlit" ); + if( !COM_FileExists( path )) return; // missed + + in = (byte *)COM_LoadFile( path, &filesize ); + + if( *(uint *)in != IDDELUXEMAPHEADER || *((uint *)in + 1) != DELUXEMAP_VERSION ) + { + MsgDev( D_ERROR, "Mod_LoadDeluxemap: %s is not a deluxemap file\n", path ); + g_ddeluxdata = NULL; + g_deluxdatasize = 0; + Mem_Free( in ); + return; + } + + g_deluxdatasize = filesize; + + // skip header bytes + g_deluxdatasize -= 8; + + if( g_deluxdatasize != g_lightdatasize ) + { + MsgDev( D_ERROR, "Mod_LoadDeluxemap: %s has mismatched size (%i should be %i)\n", path, g_deluxdatasize, g_lightdatasize ); + g_ddeluxdata = NULL; + g_deluxdatasize = 0; + Mem_Free( in ); + return; + } + + Q_strncat( g_mapinfo, "deluxemap included, ", sizeof( g_mapinfo )); + MsgDev( D_REPORT, "Mod_LoadDeluxemap: %s loaded\n", path ); + g_ddeluxdata = (byte *)Mem_Alloc( g_deluxdatasize ); + memcpy( g_ddeluxdata, in + 8, g_deluxdatasize ); + Mem_Free( in ); +} + +/* +================= +Mod_LoadLightVecs +================= +*/ +static void Mod_LoadLightVecs( const dlump_t *l ) +{ + byte *in; + + in = (byte *)(mod_base + l->fileofs); + g_deluxdatasize = l->filelen; + if( !l->filelen ) return; + + if( g_deluxdatasize != g_lightdatasize ) + { + MsgDev( D_ERROR, "Mod_LoadLightVecs: has mismatched size (%i should be %i)\n", g_deluxdatasize, g_lightdatasize ); + g_ddeluxdata = NULL; + g_deluxdatasize = 0; + return; + } + + Q_strncat( g_mapinfo, "deluxemap included, ", sizeof( g_mapinfo )); + g_ddeluxdata = (byte *)Mem_Alloc( g_deluxdatasize ); + memcpy( g_ddeluxdata, in, g_deluxdatasize ); +} + +/* +================= +Mod_LoadClipnodes +================= +*/ +static void Mod_LoadClipnodes( const dlump_t *l, const dlump_t *l2, const dlump_t *l3 ) +{ + dclipnode_t *in, *in2, *in3; + dclipnode32_t *out = g_dclipnodes32; + int i, count, count2, count3; + + in = (dclipnode_t *)(mod_base + l->fileofs); + if( l->filelen % sizeof( *in )) COM_FatalError( "Mod_LoadClipnodes1: funny lump size\n" ); + count = l->filelen / sizeof( *in ); + + in2 = (dclipnode_t *)(mod_base + l2->fileofs); + if( l2->filelen % sizeof( *in2 )) COM_FatalError( "Mod_LoadClipnodes2: funny lump size\n" ); + count2 = l2->filelen / sizeof( *in2 ); + + in3 = (dclipnode_t *)(mod_base + l3->fileofs); + if( l3->filelen % sizeof( *in3 )) COM_FatalError( "Mod_LoadClipnodes3: funny lump size\n" ); + count3 = l3->filelen / sizeof( *in3 ); + + g_numclipnodes = 0; + + for( i = 0; i < count; i++, out++, in++ ) + { + out->planenum = in->planenum; + out->children[0] = in->children[0]; + out->children[1] = in->children[1]; + g_numclipnodes++; + } + + // merge offsets so we have shared array of clipnodes again + for( i = 0; i < count2; i++, out++, in2++ ) + { + out->planenum = in2->planenum; + out->children[0] = in2->children[0]; + out->children[1] = in2->children[1]; + + if( out->children[0] >= 0 ) + out->children[0] += count; + if( out->children[1] >= 0 ) + out->children[1] += count; + g_numclipnodes++; + } + + // merge offsets so we have shared array of clipnodes again + for( i = 0; i < count3; i++, out++, in3++ ) + { + out->planenum = in3->planenum; + out->children[0] = in3->children[0]; + out->children[1] = in3->children[1]; + + if( out->children[0] >= 0 ) + out->children[0] += (count + count2); + if( out->children[1] >= 0 ) + out->children[1] += (count + count2); + g_numclipnodes++; + } + + // update headnode offsets + for( i = 0; i < g_nummodels; i++ ) + { + g_dmodels[i].headnode[2] += count; + g_dmodels[i].headnode[3] += (count + count2); + } + + if( g_numclipnodes != ( count + count2 + count3 )) + COM_FatalError( "Mod_LoadClipnodes: mismatch node count (%i should be %i)\n", g_numclipnodes, ( count + count2 + count3 )); +} + +/* +============= +LoadBsp31 + +load XashXT level file format +============= +*/ +void LoadBsp31( void ) +{ + dheader31_t *header = (dheader31_t *)mod_base; + dextrahdr_t *extrahdr = (dextrahdr_t *)((byte *)mod_base + sizeof( dheader31_t )); + dheader31_hlfx_t *hlfxhdr = (dheader31_hlfx_t *)mod_base; + + g_maptype = MAP_XASH31; + + if( extrahdr->id == IDEXTRAHEADER ) + { + switch( extrahdr->version ) + { + case 1: + g_subtype = MAP_XASHXT_OLD; + break; + case EXTRA_VERSION_OLD: + g_subtype = MAP_P2SAVIOR; + break; + case 3: + g_subtype = MAP_DEPRECATED; + break; + case EXTRA_VERSION: + g_subtype = MAP_XASH3D_EXT; + break; + } + } + else if( hlfxhdr->magicID == HLFX_BSP_MAGIC_ID ) + { + g_subtype = MAP_HLFX06; + } + + // load into heap + Mod_LoadEntities( &header->lumps[LUMP_ENTITIES] ); + + if( g_mapversion != VALVE_FORMAT ) + MsgDev( D_WARN, "%s not a Valve 220 format\n", g_mapname ); + + Mod_LoadSubmodels( &header->lumps[LUMP_MODELS] ); + g_numplanes = CopyLump( LUMP_PLANES, g_dplanes, sizeof( dplane_t ), header ); + g_numvertexes = CopyLump( LUMP_VERTEXES, g_dvertexes, sizeof( dvertex_t ), header ); + g_numedges = CopyLump( LUMP_EDGES, g_dedges, sizeof( dedge_t ), header ); + g_numsurfedges = CopyLump( LUMP_SURFEDGES, g_dsurfedges, sizeof( dsurfedge_t ), header ); + g_visdatasize = CopyLump( LUMP_VISIBILITY, g_dvisdata, 1, header ); + Mod_LoadTexInfo( &header->lumps[LUMP_TEXINFO] ); + g_texdatasize = CopyLump( LUMP_TEXTURES, g_dtexdata, 1, header ); + g_lightdatasize = CopyLump( LUMP_LIGHTING, g_dlightdata, 1, header ); + g_numfaces = CopyLump( LUMP_FACES, g_dfaces, sizeof( dface_t ), header ); + g_nummarksurfaces = CopyLump( LUMP_MARKSURFACES, g_dmarksurfaces, sizeof( dmarkface_t ), header ); + g_numleafs = CopyLump( LUMP_LEAFS, g_dleafs, sizeof( dleaf_t ), header ); + g_numnodes = CopyLump( LUMP_NODES, g_dnodes, sizeof( dnode_t ), header ); + Mod_LoadClipnodes( &header->lumps[LUMP_CLIPNODES], &header->lumps[LUMP_CLIPNODES2], &header->lumps[LUMP_CLIPNODES3] ); + Mod_LoadDeluxemap (); + + if( g_subtype == MAP_XASH3D_EXT ) + { + g_found_extradata = true; + + // g-cont. copy the extra lumps + g_numnormals = CopyExtraLump( LUMP_VERTNORMALS, g_dnormals, sizeof( dnormal_t ), header ); + g_deluxdatasize = CopyExtraLump( LUMP_LIGHTVECS, g_ddeluxdata, 1, header ); + g_numcubemaps = CopyExtraLump( LUMP_CUBEMAPS, g_dcubemaps, sizeof( dcubemap_t ), header ); + g_numfaceinfo = CopyExtraLump( LUMP_FACEINFO, g_dfaceinfo, sizeof( dfaceinfo_t ), header ); + g_numworldlights = CopyExtraLump( LUMP_WORLDLIGHTS, g_dworldlights, sizeof( dworldlight_t ), header ); + g_numleaflights = CopyExtraLump( LUMP_LEAF_LIGHTING, g_dleaflights, sizeof( dleafsample_t ), header ); + g_shadowdatasize = CopyExtraLump( LUMP_SHADOWMAP, g_dshadowdata, 1, header ); + g_vlightdatasize = CopyExtraLump( LUMP_VERTEX_LIGHT, g_dvlightdata, 1, header ); + } + else if( g_subtype == MAP_P2SAVIOR ) + { + // P2: Savior regular format + g_numcubemaps = CopyExtraLump( LUMP_CUBEMAPS, g_dcubemaps, sizeof( dcubemap_t ), header ); + g_numworldlights = CopyExtraLump( LUMP_WORLDLIGHTS, g_dworldlights, sizeof( dworldlight_t ), header ); + } + else if( g_subtype == MAP_HLFX06 ) + { + g_numfacedata = CopyLump( HLFX31_LUMP_FACEINFO, g_dfacedata, sizeof( dfacedata_t ), hlfxhdr ); + g_bumpdatasize = CopyLump( HLFX31_LUMP_BUMP, g_dbumpdata, 1, hlfxhdr ); + g_numTNbasis = CopyLump( HLFX31_LUMP_TNBASIS, g_dTNbasis, sizeof( dTNbasis_t ), hlfxhdr ); + } + // preform some operations here... +} + +/* +============= +LoadBSPFile +============= +*/ +void LoadBSPFile( const char *infilename, const char *outfilename ) +{ + MsgDev( D_REPORT, "Loading: %s\n", infilename ); + Q_strncpy( g_mapname, infilename, sizeof( g_mapname )); + mod_base = (byte *)COM_LoadFile( g_mapname, NULL ); + + if( *(uint *)mod_base == XT_BSPVERSION ) + { + LoadBsp31(); + PrintMapInfo(); + WriteBSPFile( outfilename ); + } + else + { + // not an BSP31 + Mem_Free( mod_base ); + } +} + +int BspConvert( int argc, char **argv ) +{ + char source[1024], name[1024]; + char output[1024]; + + + if( !COM_GetParmExt( "-file", source, sizeof( source ))) + Q_strncpy( source, "*.bsp", sizeof( source )); + + search_t *search = COM_Search( source, true ); + + if( !search ) return 0; + + for( int i = 0; i < search->numfilenames; i++ ) + { + COM_FileBase( search->filenames[i], name ); + Q_snprintf( output, sizeof( output ), "%s.bsp", name ); +#if 0 + if( COM_FileExists( output )) + continue; // map already converted +#endif + LoadBSPFile( search->filenames[i], output ); + } + + Mem_Free( search ); + Mem_Check(); + + Msg( "press any key to exit\n" ); + system( "pause>nul" ); + + return 0; +} \ No newline at end of file diff --git a/utils/bsp31migrate/bspfile31.h b/utils/bsp31migrate/bspfile31.h new file mode 100644 index 0000000..a208cd2 --- /dev/null +++ b/utils/bsp31migrate/bspfile31.h @@ -0,0 +1,70 @@ + +#ifndef BSPFILE_31_H +#define BSPFILE_31_H + +//============================================================================= + +#define XT_BSPVERSION 31 +#define IDEXTRAHEADER (('H'<<24)+('S'<<16)+('A'<<8)+'X') // little-endian "XASH" +#define EXTRA_VERSION 4 // ver. 1 was occupied by old versions of XashXT, ver. 2 was occupied by old vesrions of P2:savior + // ver. 3 was occupied by experimental versions of P2:savior change fmt +#define EXTRA_VERSION_OLD 2 // extra version 2 (P2:Savior regular version) to get minimal backward compatibility + +#define HLFX_BSP_MAGIC_ID 0xFEED + +#define DELUXEMAP_VERSION 1 +#define IDDELUXEMAPHEADER (('T'<<24)+('I'<<16)+('L'<<8)+'Q') // little-endian "QLIT" + +// version 31 +#define LUMP_CLIPNODES2 15 // hull0 goes into LUMP_NODES, hull1 goes into LUMP_CLIPNODES, +#define LUMP_CLIPNODES3 16 // hull2 goes into LUMP_CLIPNODES2, hull3 goes into LUMP_CLIPNODES3 +#define HEADER_LUMPS_31 17 + +// HLFX combined with BSP30 +#define HLFX30_LUMP_FACEINFO 15 +#define HLFX30_LUMP_BUMP 16 +#define HLFX30_LUMP_TNBASIS 17 +#define HEADER_LUMPS_HLFX_30 18 + +// HLFX combined with BSP31 +#define HLFX31_LUMP_FACEINFO 17 +#define HLFX31_LUMP_BUMP 18 +#define HLFX31_LUMP_TNBASIS 19 +#define HEADER_LUMPS_HLFX_31 20 + +#define MAX_MAP_TNBASIS 0xFFFF0 + +typedef struct +{ + int version; + dlump_t lumps[HEADER_LUMPS_31]; +} dheader31_t; + +typedef struct +{ + int version; + dlump_t lumps[HEADER_LUMPS_HLFX_30]; + int magicID; +} dheader30_hlfx_t; + +typedef struct +{ + int version; + dlump_t lumps[HEADER_LUMPS_HLFX_31]; + int magicID; +} dheader31_hlfx_t; + +typedef struct +{ + float normal[3]; + float tangent[3]; +} dTNbasis_t; + +typedef struct +{ + int bumpofs; + int firstTN; + short numTN; +} dfacedata_t; + +#endif \ No newline at end of file diff --git a/utils/common/basefs.cpp b/utils/common/basefs.cpp new file mode 100644 index 0000000..70229d6 --- /dev/null +++ b/utils/common/basefs.cpp @@ -0,0 +1,1598 @@ +/* +filesystem.c - game filesystem based on DP fs +Copyright (C) 2007 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include +#include +#include +#include "cmdlib.h" +#include "mathlib.h" +#include "stringlib.h" +#include "filesystem.h" +#include "wfile.h" +#include "bspfile.h" + +#define FILE_COPY_SIZE (1024 * 1024) +#define FILE_BUFF_SIZE (65535) +#define MAX_SYSPATH 1024 // system filepath + +/* +======================================================================== +PAK FILES + +The .pak files are just a linear collapse of a directory tree +======================================================================== +*/ +// header +#define IDPACKV1HEADER (('K'<<24)+('C'<<16)+('A'<<8)+'P') // little-endian "PACK" +#define MAX_FILES_IN_PACK 65536 // pak + +typedef struct +{ + int ident; + int dirofs; + int dirlen; +} dpackheader_t; + +typedef struct +{ + char name[56]; // total 64 bytes + int filepos; + int filelen; +} dpackfile_t; + +// filesystem flags +#define FS_GAMEDIR_PATH 1 // just a marker for gamedir path + +struct file_s +{ + int handle; // file descriptor + long real_length; // uncompressed file size (for files opened in "read" mode) + long position; // current position in the file + long offset; // offset into the package (0 if external file) + int ungetc; // single stored character from ungetc, cleared to EOF when read contents buffer + time_t filetime; // pak, wad or real filetime + long buff_ind, buff_len; // buffer current index and length + byte buff[FILE_BUFF_SIZE]; // intermediate buffer +}; + +typedef struct pack_s +{ + char filename[256]; + int handle; + int numfiles; + time_t filetime; // common for all packed files + dpackfile_t *files; +} pack_t; + +typedef struct searchpath_s +{ + char filename[256]; + pack_t *pack; + wfile_t *wad; + int flags; + struct searchpath_s *next; +} searchpath_t; + +searchpath_t *fs_searchpaths = NULL; // chain +searchpath_t fs_directpath; // static direct path +char fs_rootdir[MAX_SYSPATH]; // engine root directory +char fs_basedir[MAX_SYSPATH]; // base directory of game +char fs_gamedir[MAX_SYSPATH]; // game current directory +char fs_falldir[MAX_SYSPATH]; // game falling directory +bool fs_ext_path = false; // attempt to read\write from ./ or ../ pathes + +static searchpath_t *FS_FindFile( const char *name, int *index, bool gamedironly ); +static dpackfile_t* FS_AddFileToPack( const char* name, pack_t *pack, long offset, long size ); +static byte *W_LoadFile( const char *path, size_t *filesizeptr, bool gamedironly ); +long FS_FileTime( const char *filename, bool gamedironly ); +static void FS_Purge( file_t* file ); + +void FS_AllowDirectPaths( bool enable ) +{ + fs_ext_path = enable; +} + +/* +============================================================================= + +OTHER PRIVATE FUNCTIONS + +============================================================================= +*/ +/* +==================== +FS_AddFileToPack + +Add a file to the list of files contained into a package +==================== +*/ +static dpackfile_t *FS_AddFileToPack( const char *name, pack_t *pack, long offset, long size ) +{ + int left, right, middle; + dpackfile_t *pfile; + + // look for the slot we should put that file into (binary search) + left = 0; + right = pack->numfiles - 1; + + while( left <= right ) + { + int diff; + + middle = (left + right) / 2; + diff = Q_stricmp( pack->files[middle].name, name ); + + // If we found the file, there's a problem + if( !diff ) MsgDev( D_WARN, "Package %s contains the file %s several times\n", pack->filename, name ); + + // If we're too far in the list + if( diff > 0 ) right = middle - 1; + else left = middle + 1; + } + + // We have to move the right of the list by one slot to free the one we need + pfile = &pack->files[left]; + memmove( pfile + 1, pfile, (pack->numfiles - left) * sizeof( *pfile )); + pack->numfiles++; + + Q_strncpy( pfile->name, name, sizeof( pfile->name )); + pfile->filepos = offset; + pfile->filelen = size; + + return pfile; +} + +/* +================= +FS_LoadPackPAK + +Takes an explicit (not game tree related) path to a pak file. + +Loads the header and directory, adding the files at the beginning +of the list so they override previous pack files. +================= +*/ +pack_t *FS_LoadPackPAK( const char *packfile, int *error ) +{ + dpackheader_t header; + int packhandle; + int i, numpackfiles; + pack_t *pack; + dpackfile_t *info; + + packhandle = open( packfile, O_RDONLY|O_BINARY ); + + if( packhandle < 0 ) + { + MsgDev( D_NOTE, "%s couldn't open\n", packfile ); + if( error ) *error = PAK_LOAD_COULDNT_OPEN; + return NULL; + } + + read( packhandle, (void *)&header, sizeof( header )); + + if( header.ident != IDPACKV1HEADER ) + { + MsgDev( D_NOTE, "%s is not a packfile. Ignored.\n", packfile ); + if( error ) *error = PAK_LOAD_BAD_HEADER; + close( packhandle ); + return NULL; + } + + if( header.dirlen % sizeof( dpackfile_t )) + { + MsgDev( D_ERROR, "%s has an invalid directory size. Ignored.\n", packfile ); + if( error ) *error = PAK_LOAD_BAD_FOLDERS; + close( packhandle ); + return NULL; + } + + numpackfiles = header.dirlen / sizeof( dpackfile_t ); + + if( numpackfiles > MAX_FILES_IN_PACK ) + { + MsgDev( D_ERROR, "%s has too many files ( %i ). Ignored.\n", packfile, numpackfiles ); + if( error ) *error = PAK_LOAD_TOO_MANY_FILES; + close( packhandle ); + return NULL; + } + + if( numpackfiles <= 0 ) + { + MsgDev( D_NOTE, "%s has no files. Ignored.\n", packfile ); + if( error ) *error = PAK_LOAD_NO_FILES; + close( packhandle ); + return NULL; + } + + info = (dpackfile_t *)Mem_Alloc( sizeof( *info ) * numpackfiles, C_FILESYSTEM ); + lseek( packhandle, header.dirofs, SEEK_SET ); + + if( header.dirlen != read( packhandle, (void *)info, header.dirlen )) + { + MsgDev( D_NOTE, "%s is an incomplete PAK, not loading\n", packfile ); + if( error ) *error = PAK_LOAD_CORRUPTED; + close( packhandle ); + Mem_Free( info, C_FILESYSTEM ); + return NULL; + } + + pack = (pack_t *)Mem_Alloc( sizeof( pack_t ), C_FILESYSTEM ); + Q_strncpy( pack->filename, packfile, sizeof( pack->filename )); + pack->files = (dpackfile_t *)Mem_Alloc( numpackfiles * sizeof( dpackfile_t ), C_FILESYSTEM ); + pack->filetime = COM_FileTime( packfile ); + pack->handle = packhandle; + pack->numfiles = 0; + + // parse the directory + for( i = 0; i < numpackfiles; i++ ) + FS_AddFileToPack( info[i].name, pack, info[i].filepos, info[i].filelen ); + + if( error ) *error = PAK_LOAD_OK; + Mem_Free( info, C_FILESYSTEM ); + + return pack; +} + +/* +==================== +FS_AddWad_Fullpath +==================== +*/ +static bool FS_AddWad_Fullpath( const char *wadfile, bool *already_loaded, int flags ) +{ + searchpath_t *search; + wfile_t *wad = NULL; + const char *ext = COM_FileExtension( wadfile ); + int errorcode = WAD_LOAD_COULDNT_OPEN; + + for( search = fs_searchpaths; search; search = search->next ) + { + if( search->wad && !Q_stricmp( search->wad->filename, wadfile )) + { + if( already_loaded ) *already_loaded = true; + return true; // already loaded + } + } + + if( already_loaded ) *already_loaded = false; + if( !Q_stricmp( ext, "wad" )) wad = W_Open( wadfile, "rb", &errorcode, fs_ext_path ); + else MsgDev( D_ERROR, "\"%s\" doesn't have a wad extension\n", wadfile ); + + if( wad ) + { + search = (searchpath_t *)Mem_Alloc( sizeof( searchpath_t ), C_FILESYSTEM ); + search->wad = wad; + search->next = fs_searchpaths; + search->flags |= flags; + fs_searchpaths = search; + + MsgDev( D_REPORT, "Adding wadfile: %s (%i files)\n", wadfile, wad->numlumps ); + return true; + } + else + { + if( errorcode != WAD_LOAD_NO_FILES ) + MsgDev( D_ERROR, "FS_AddWad_Fullpath: unable to load wad \"%s\"\n", wadfile ); + return false; + } +} + +/* +================ +FS_AddPak_Fullpath + +Adds the given pack to the search path. +The pack type is autodetected by the file extension. + +Returns true if the file was successfully added to the +search path or if it was already included. + +If keep_plain_dirs is set, the pack will be added AFTER the first sequence of +plain directories. +================ +*/ +static bool FS_AddPak_Fullpath( const char *pakfile, bool *already_loaded, int flags ) +{ + searchpath_t *search; + pack_t *pak = NULL; + const char *ext = COM_FileExtension( pakfile ); + int i, errorcode = PAK_LOAD_COULDNT_OPEN; + + for( search = fs_searchpaths; search; search = search->next ) + { + if( search->pack && !Q_stricmp( search->pack->filename, pakfile )) + { + if( already_loaded ) *already_loaded = true; + return true; // already loaded + } + } + + if( already_loaded ) *already_loaded = false; + + if( !Q_stricmp( ext, "pak" )) pak = FS_LoadPackPAK( pakfile, &errorcode ); + else MsgDev( D_ERROR, "\"%s\" does not have a pack extension\n", pakfile ); + + if( pak ) + { + string fullpath; + + search = (searchpath_t *)Mem_Alloc( sizeof( searchpath_t ), C_FILESYSTEM ); + search->pack = pak; + search->next = fs_searchpaths; + search->flags |= flags; + fs_searchpaths = search; + + MsgDev( D_REPORT, "Adding pakfile: %s (%i files)\n", pakfile, pak->numfiles ); + + // time to add in search list all the wads that contains in current pakfile (if do) + for( i = 0; i < pak->numfiles; i++ ) + { + if( !Q_stricmp( COM_FileExtension( pak->files[i].name ), "wad" )) + { + Q_sprintf( fullpath, "%s/%s", pakfile, pak->files[i].name ); + FS_AddWad_Fullpath( fullpath, NULL, flags ); + } + } + + return true; + } + else + { + if( errorcode != PAK_LOAD_NO_FILES ) + MsgDev( D_ERROR, "FS_AddPak_Fullpath: unable to load pak \"%s\"\n", pakfile ); + return false; + } +} + +/* +================ +FS_AddGameDirectory + +Sets fs_gamedir, adds the directory to the head of the path, +then loads and adds pak1.pak pak2.pak ... +================ +*/ +void FS_AddGameDirectory( const char *dir, int flags ) +{ + stringlist_t list; + searchpath_t *search; + char fullpath[256]; + int i; + + stringlistinit( &list ); + listdirectory( &list, dir, true ); + stringlistsort( &list ); + + // add any PAK package in the directory + for( i = 0; i < list.numstrings; i++ ) + { + if( !Q_stricmp( COM_FileExtension( list.strings[i] ), "pak" )) + { + Q_sprintf( fullpath, "%s%s", dir, list.strings[i] ); + FS_AddPak_Fullpath( fullpath, NULL, flags ); + } + } + + FS_AllowDirectPaths( true ); + + // add any WAD package in the directory + for( i = 0; i < list.numstrings; i++ ) + { + if( !Q_stricmp( COM_FileExtension( list.strings[i] ), "wad" )) + { + Q_sprintf( fullpath, "%s%s", dir, list.strings[i] ); + FS_AddWad_Fullpath( fullpath, NULL, flags ); + } + } + + stringlistfreecontents( &list ); + FS_AllowDirectPaths( false ); + + // add the directory to the search path + // (unpacked files have the priority over packed files) + search = (searchpath_t *)Mem_Alloc( sizeof( searchpath_t ), C_FILESYSTEM ); + Q_strncpy( search->filename, dir, sizeof ( search->filename )); + search->next = fs_searchpaths; + search->flags = flags; + fs_searchpaths = search; + + +} + +/* +================ +FS_AddGameHierarchy +================ +*/ +void FS_AddGameHierarchy( const char *dir, int flags ) +{ + // Add the common game directory + if( dir && *dir ) FS_AddGameDirectory( va( "%s/%s/", fs_rootdir, dir ), flags ); +} + +/* +================ +FS_ClearSearchPath +================ +*/ +void FS_ClearSearchPath( void ) +{ + while( fs_searchpaths ) + { + searchpath_t *search = fs_searchpaths; + + if( !search ) break; + + fs_searchpaths = search->next; + + if( search->pack ) + { + if( search->pack->files ) + Mem_Free( search->pack->files, C_FILESYSTEM ); + Mem_Free( search->pack, C_FILESYSTEM ); + } + + if( search->wad ) + W_Close( search->wad ); + + Mem_Free( search, C_FILESYSTEM ); + } + + fs_searchpaths = NULL; +} + +/* +================ +FS_ParseLiblistGam +================ +*/ +static bool FS_ParseLiblistGam( const char *filename, const char *gamedir ) +{ + char *afile, *pfile; + char token[256]; + + afile = (char *)FS_LoadFile( filename, NULL, false ); + if( !afile ) return false; + + Q_strncpy( fs_basedir, "valve", sizeof( fs_basedir )); + COM_FileBase( gamedir, fs_gamedir ); + + pfile = afile; + + while(( pfile = COM_ParseFile( pfile, token )) != NULL ) + { + if( !Q_stricmp( token, "gamedir" )) + { + pfile = COM_ParseFile( pfile, token ); + if( Q_stricmp( token, fs_basedir ) || Q_stricmp( token, fs_gamedir )) + Q_strncpy( fs_gamedir, token, sizeof( fs_gamedir )); + } + if( !Q_stricmp( token, "fallback_dir" )) + { + pfile = COM_ParseFile( pfile, token ); + if( Q_stricmp( token, fs_basedir ) || Q_stricmp( token, fs_falldir )) + Q_strncpy( fs_falldir, token, sizeof( fs_falldir )); + } + } + + if( afile != NULL ) + Mem_Free( afile, C_FILESYSTEM ); + + return true; +} + +/* +================ +FS_ParseGameInfo +================ +*/ +static bool FS_ParseGameInfo( const char *filename, const char *gamedir ) +{ + char *afile, *pfile; + char token[256]; + + afile = (char *)FS_LoadFile( filename, NULL, false ); + if( !afile ) return false; + + // setup default values + COM_FileBase( gamedir, fs_gamedir ); + + pfile = afile; + + while(( pfile = COM_ParseFile( pfile, token )) != NULL ) + { + if( !Q_stricmp( token, "basedir" )) + { + pfile = COM_ParseFile( pfile, token ); + if( Q_stricmp( token, fs_basedir ) || Q_stricmp( token, fs_gamedir )) + Q_strncpy( fs_basedir, token, sizeof( fs_basedir )); + } + else if( !Q_stricmp( token, "fallback_dir" )) + { + pfile = COM_ParseFile( pfile, token ); + if( Q_stricmp( token, fs_basedir ) || Q_stricmp( token, fs_falldir )) + Q_strncpy( fs_falldir, token, sizeof( fs_falldir )); + } + else if( !Q_stricmp( token, "gamedir" )) + { + pfile = COM_ParseFile( pfile, token ); + if( Q_stricmp( token, fs_basedir ) || Q_stricmp( token, fs_gamedir )) + Q_strncpy( fs_gamedir, token, sizeof( fs_gamedir )); + } + } + + if( afile != NULL ) + Mem_Free( afile, C_FILESYSTEM ); + + return true; +} + +void FS_ReadGameInfo( const char *gamedir ) +{ + char liblist[256], gameinfo[256]; + + Q_strncpy( gameinfo, "gameinfo.txt", sizeof( gameinfo )); + Q_strncpy( liblist, "liblist.gam", sizeof( liblist )); + + // if user change liblist.gam update the gameinfo.txt + if( FS_FileTime( liblist, false ) > FS_FileTime( gameinfo, false )) + FS_ParseLiblistGam( liblist, gamedir ); + else FS_ParseGameInfo( gameinfo, gamedir ); +} + +/* +================ +FS_Rescan +================ +*/ +void FS_Rescan( void ) +{ + FS_ClearSearchPath(); + + if( Q_stricmp( fs_basedir, fs_gamedir )) + FS_AddGameHierarchy( fs_basedir, 0 ); + + if( Q_stricmp( fs_basedir, fs_falldir ) && Q_stricmp( fs_gamedir, fs_falldir )) + FS_AddGameHierarchy( fs_falldir, 0 ); + + FS_AddGameHierarchy( fs_gamedir, FS_GAMEDIR_PATH ); +} + +/* +================ +FS_Init +================ +*/ +void FS_Init( const char *source ) +{ + char workdir[_MAX_PATH]; + char mapdir[_MAX_PATH]; + char hullfile[_MAX_PATH]; + char mapfile[_MAX_PATH]; + char *pathend; + + fs_searchpaths = NULL; + + // skip the unneeded separator + if( source[0] == '.' && ( source[1] == '/' || source[1] == '\\' )) + Q_snprintf( mapdir, sizeof( mapdir ), "%s.map", source + 2 ); + else Q_snprintf( mapdir, sizeof( mapdir ), "%s.map", source ); + + // create full path to a map + Q_strncpy( workdir, COM_ExpandArg( mapdir ), sizeof( workdir )); + + // search for 'maps' in path + pathend = Q_stristr( workdir, "maps" ); + + if( !pathend ) + { + MsgDev( D_ERROR, "FS_Init: couldn't init game directory!\n" ); + return; + } + + // create gamedir path + Q_strncpy( fs_rootdir, workdir, pathend - workdir ); + int length = Q_strlen( fs_rootdir ); + + if( fs_rootdir[length-1] == '.' && ( fs_rootdir[length-2] == '/' || fs_rootdir[length-2] == '\\' )) + fs_rootdir[length-2] = '\0'; + + MsgDev( D_REPORT, "workdir: %s\n", fs_rootdir ); + + Q_snprintf( hullfile, sizeof( hullfile ), "%s\\hulls.txt", fs_rootdir ); + FS_AddGameDirectory( va( "%s\\", fs_rootdir ), 0 ); + FS_ReadGameInfo( fs_rootdir ); + + MsgDev( D_REPORT, "gamedir: %s, basedir %s, falldir %s\n", fs_gamedir, fs_basedir, fs_falldir ); + COM_ExtractFilePath( fs_rootdir, fs_rootdir ); + COM_FileBase( source, mapfile ); + + if( !Q_strcmp( fs_rootdir, "" )) + Q_strncpy( fs_rootdir, "..\\", sizeof( fs_rootdir )); + + MsgDev( D_INFO, "rootdir %s\n", fs_rootdir ); // for debug + MsgDev( D_INFO, "source: %s.map\n\n", mapfile ); // for debug + FS_Rescan(); // create new filesystem + + MsgDev( D_REPORT, "FS_Init: done\n" ); + + searchpath_t *s; + + MsgDev( D_REPORT, "Current search path:\n" ); + + for( s = fs_searchpaths; s; s = s->next ) + { + if( s->pack ) MsgDev( D_REPORT, "%s (%i files)\n", s->pack->filename, s->pack->numfiles ); + else if( s->wad ) MsgDev( D_REPORT, "%s (%i files)\n", s->wad->filename, s->wad->numlumps ); + else MsgDev( D_REPORT, "%s\n", s->filename ); + } + + if( COM_FileExists( hullfile )) + CheckHullFile( hullfile ); +} + +/* +================ +FS_Shutdown +================ +*/ +void FS_Shutdown( void ) +{ + FS_ClearSearchPath(); +} + +/* +==================== +FS_SysOpen + +Internal function used to create a file_t and open the relevant non-packed file on disk +==================== +*/ +static file_t *FS_SysOpen( const char *filepath, const char *mode ) +{ + file_t *file; + int mod, opt; + uint ind; + + // Parse the mode string + switch( mode[0] ) + { + case 'r': // read + mod = O_RDONLY; + opt = 0; + break; + case 'w': // write + mod = O_WRONLY; + opt = O_CREAT | O_TRUNC; + break; + case 'a': // append + mod = O_WRONLY; + opt = O_CREAT | O_APPEND; + break; + case 'e': // edit + mod = O_WRONLY; + opt = O_CREAT; + break; + default: + MsgDev( D_ERROR, "FS_SysOpen(%s, %s): invalid mode\n", filepath, mode ); + return NULL; + } + + for( ind = 1; mode[ind] != '\0'; ind++ ) + { + switch( mode[ind] ) + { + case '+': + mod = O_RDWR; + break; + case 'b': + opt |= O_BINARY; + break; + default: + MsgDev( D_ERROR, "FS_SysOpen: %s: unknown char in mode (%c)\n", filepath, mode, mode[ind] ); + break; + } + } + + file = (file_t *)Mem_Alloc( sizeof( *file ), C_FILESYSTEM ); + file->filetime = COM_FileTime( filepath ); + file->ungetc = EOF; + + file->handle = open( filepath, mod|opt, 0666 ); + + if( file->handle < 0 ) + { + Mem_Free( file, C_FILESYSTEM ); + return NULL; + } + + file->real_length = lseek( file->handle, 0, SEEK_END ); + + // For files opened in append mode, we start at the end of the file + if( mod & O_APPEND ) file->position = file->real_length; + else lseek( file->handle, 0, SEEK_SET ); + + return file; +} + +/* +=========== +FS_OpenPackedFile + +Open a packed file using its package file descriptor +=========== +*/ +file_t *FS_OpenPackedFile( pack_t *pack, int pack_ind ) +{ + dpackfile_t *pfile; + int dup_handle; + file_t *file; + + pfile = &pack->files[pack_ind]; + + if( lseek( pack->handle, pfile->filepos, SEEK_SET ) == -1 ) + return NULL; + + dup_handle = dup( pack->handle ); + + if( dup_handle < 0 ) + return NULL; + + file = (file_t *)Mem_Alloc( sizeof( *file ), C_FILESYSTEM ); + file->handle = dup_handle; + file->real_length = pfile->filelen; + file->offset = pfile->filepos; + file->position = 0; + file->ungetc = EOF; + + return file; +} + +/* +==================== +FS_FindFile + +Look for a file in the packages and in the filesystem + +Return the searchpath where the file was found (or NULL) +and the file index in the package if relevant +==================== +*/ +static searchpath_t *FS_FindFile( const char *name, int *index, bool gamedironly ) +{ + searchpath_t *search; + + if( !COM_CheckString( name )) + return NULL; + + // search through the path, one element at a time + for( search = fs_searchpaths; search; search = search->next ) + { + if( gamedironly & !FBitSet( search->flags, FS_GAMEDIR_PATH )) + continue; + + // is the element a pak file? + if( search->pack ) + { + int left, right, middle; + pack_t *pak; + + pak = search->pack; + + // look for the file (binary search) + left = 0; + right = pak->numfiles - 1; + while( left <= right ) + { + int diff; + + middle = (left + right) / 2; + diff = Q_stricmp( pak->files[middle].name, name ); + + // Found it + if( !diff ) + { + if( index ) *index = middle; + return search; + } + + // if we're too far in the list + if( diff > 0 ) + right = middle - 1; + else left = middle + 1; + } + } + else if( search->wad ) + { + dlumpinfo_t *lump; + char type = W_TypeFromExt( name ); + bool anywadname = true; + string wadname, wadfolder; + string shortname; + + // quick reject by filetype + if( type == TYP_NONE ) continue; + COM_ExtractFilePath( name, wadname ); + wadfolder[0] = '\0'; + + if( Q_strlen( wadname )) + { + COM_FileBase( wadname, wadname ); + Q_strncpy( wadfolder, wadname, sizeof( wadfolder )); + COM_DefaultExtension( wadname, ".wad" ); + anywadname = false; + } + + // make wadname from wad fullpath + COM_FileBase( search->wad->filename, shortname ); + COM_DefaultExtension( shortname, ".wad" ); + + // quick reject by wadname + if( !anywadname && Q_stricmp( wadname, shortname )) + continue; + + // NOTE: we can't using long names for wad, + // because we using original wad names[16]; + COM_FileBase( name, shortname ); + + lump = W_FindLump( search->wad, shortname, type ); + + if( lump ) + { + if( index ) + *index = lump - search->wad->lumps; + return search; + } + } + else + { + char netpath[MAX_SYSPATH]; + + Q_sprintf( netpath, "%s%s", search->filename, name ); + + if( COM_FileExists( netpath )) + { + if( index != NULL ) *index = -1; + return search; + } + } + } + + if( fs_ext_path ) + { + // clear searchpath + search = &fs_directpath; + memset( search, 0, sizeof( searchpath_t )); + + if( COM_FileExists( name )) + { + if( index != NULL ) + *index = -1; + return search; + } + } + + if( index != NULL ) + *index = -1; + + return NULL; +} + + +/* +=========== +FS_OpenReadFile + +Look for a file in the search paths and open it in read-only mode +=========== +*/ +file_t *FS_OpenReadFile( const char *filename, const char *mode, bool gamedironly ) +{ + searchpath_t *search; + int pack_ind; + + search = FS_FindFile( filename, &pack_ind, gamedironly ); + + // not found? + if( search == NULL ) + return NULL; + + if( search->pack ) + return FS_OpenPackedFile( search->pack, pack_ind ); + else if( search->wad ) + return NULL; // let W_LoadFile get lump correctly + else if( pack_ind < 0 ) + { + char path [MAX_SYSPATH]; + + // found in the filesystem? + Q_sprintf( path, "%s%s", search->filename, filename ); + return FS_SysOpen( path, mode ); + } + + return NULL; +} + +/* +============================================================================= + +MAIN PUBLIC FUNCTIONS + +============================================================================= +*/ +/* +==================== +FS_Open + +Open a file. The syntax is the same as fopen +==================== +*/ +file_t *FS_Open( const char *filepath, const char *mode, bool gamedironly ) +{ + // some stupid mappers used leading '/' or '\' in path to models or sounds + if( filepath[0] == '/' || filepath[0] == '\\' ) + filepath++; + + if( filepath[0] == '/' || filepath[0] == '\\' ) + filepath++; + + // if the file is opened in "write", "append", or "read/write" mode + if( mode[0] == 'w' || mode[0] == 'a'|| mode[0] == 'e' || Q_strchr( mode, '+' )) + { + char real_path[MAX_SYSPATH]; + + // open the file on disk directly + Q_sprintf( real_path, "%s", filepath ); + COM_CreatePath( real_path );// Create directories up to the file + return FS_SysOpen( real_path, mode ); + } + + // else, we look at the various search paths and open the file in read-only mode + return FS_OpenReadFile( filepath, mode, gamedironly ); +} + +/* +==================== +FS_Close + +Close a file +==================== +*/ +int FS_Close( file_t *file ) +{ + if( !file ) return 0; + + if( close( file->handle )) + return EOF; + + Mem_Free( file, C_FILESYSTEM ); + return 0; +} + +/* +==================== +FS_Read + +Read up to "buffersize" bytes from a file +==================== +*/ +long FS_Read( file_t *file, void *buffer, size_t buffersize ) +{ + long count, done; + long nb; + + // nothing to copy + if( buffersize == 0 ) return 1; + + // Get rid of the ungetc character + if( file->ungetc != EOF ) + { + ((char*)buffer)[0] = file->ungetc; + buffersize--; + file->ungetc = EOF; + done = 1; + } + else done = 0; + + // first, we copy as many bytes as we can from "buff" + if( file->buff_ind < file->buff_len ) + { + count = file->buff_len - file->buff_ind; + + done += ((long)buffersize > count ) ? count : (long)buffersize; + memcpy( buffer, &file->buff[file->buff_ind], done ); + file->buff_ind += done; + + buffersize -= done; + if( buffersize == 0 ) + return done; + } + + // NOTE: at this point, the read buffer is always empty + + // we must take care to not read after the end of the file + count = file->real_length - file->position; + + // if we have a lot of data to get, put them directly into "buffer" + if( buffersize > sizeof( file->buff ) / 2 ) + { + if( count > (long)buffersize ) + count = (long)buffersize; + lseek( file->handle, file->offset + file->position, SEEK_SET ); + nb = read (file->handle, &((byte *)buffer)[done], count ); + + if( nb > 0 ) + { + done += nb; + file->position += nb; + // purge cached data + FS_Purge( file ); + } + } + else + { + if( count > (long)sizeof( file->buff )) + count = (long)sizeof( file->buff ); + lseek( file->handle, file->offset + file->position, SEEK_SET ); + nb = read( file->handle, file->buff, count ); + + if( nb > 0 ) + { + file->buff_len = nb; + file->position += nb; + + // copy the requested data in "buffer" (as much as we can) + count = (long)buffersize > file->buff_len ? file->buff_len : (long)buffersize; + memcpy( &((byte *)buffer)[done], file->buff, count ); + file->buff_ind = count; + done += count; + } + } + + return done; +} + +/* +==================== +FS_Seek + +Move the position index in a file +==================== +*/ +int FS_Seek( file_t *file, long offset, int whence ) +{ + // compute the file offset + switch( whence ) + { + case SEEK_CUR: + offset += file->position - file->buff_len + file->buff_ind; + break; + case SEEK_SET: + break; + case SEEK_END: + offset += file->real_length; + break; + default: + return -1; + } + + if( offset < 0 || offset > file->real_length ) + return -1; + + // if we have the data in our read buffer, we don't need to actually seek + if( file->position - file->buff_len <= offset && offset <= file->position ) + { + file->buff_ind = offset + file->buff_len - file->position; + return 0; + } + + // Purge cached data + FS_Purge( file ); + + if( lseek( file->handle, file->offset + offset, SEEK_SET ) == -1 ) + return -1; + file->position = offset; + + return 0; +} + +/* +==================== +FS_Tell + +Give the current position in a file +==================== +*/ +long FS_Tell( file_t *file ) +{ + if( !file ) return 0; + return file->position - file->buff_len + file->buff_ind; +} + +/* +==================== +FS_Getc + +Get the next character of a file +==================== +*/ +int FS_Getc( file_t *file ) +{ + char c; + + if( FS_Read( file, &c, 1 ) != 1 ) + return EOF; + + return c; +} + +/* +==================== +FS_UnGetc + +Put a character back into the read buffer (only supports one character!) +==================== +*/ +int FS_UnGetc( file_t *file, byte c ) +{ + // If there's already a character waiting to be read + if( file->ungetc != EOF ) + return EOF; + + file->ungetc = c; + return c; +} + +/* +==================== +FS_Gets + +Same as fgets +==================== +*/ +int FS_Gets( file_t *file, byte *string, size_t bufsize ) +{ + int c, end = 0; + + while( 1 ) + { + c = FS_Getc( file ); + + if( c == '\r' || c == '\n' || c < 0 ) + break; + + if( end < bufsize - 1 ) + string[end++] = c; + } + string[end] = 0; + + // remove \n following \r + if( c == '\r' ) + { + c = FS_Getc( file ); + + if( c != '\n' ) + FS_UnGetc( file, (byte)c ); + } + + return c; +} + +/* +==================== +FS_Eof + +indicates at reached end of file +==================== +*/ +bool FS_Eof( file_t *file ) +{ + if( !file ) return true; + return (( file->position - file->buff_len + file->buff_ind ) == file->real_length ) ? true : false; +} + +/* +==================== +FS_Purge + +Erases any buffered input or output data +==================== +*/ +void FS_Purge( file_t *file ) +{ + file->buff_len = 0; + file->buff_ind = 0; + file->ungetc = EOF; +} + +/* +==================== +FS_Write + +Write "datasize" bytes into a file +==================== +*/ +long FS_Write( file_t *file, const void *data, size_t datasize ) +{ + long result; + + if( !file ) return 0; + + // if necessary, seek to the exact file position we're supposed to be + if( file->buff_ind != file->buff_len ) + lseek( file->handle, file->buff_ind - file->buff_len, SEEK_CUR ); + + // purge cached data + FS_Purge( file ); + + // write the buffer and update the position + result = write( file->handle, data, (long)datasize ); + file->position = lseek( file->handle, 0, SEEK_CUR ); + + if( file->real_length < file->position ) + file->real_length = file->position; + + if( result < 0 ) + return 0; + return result; +} + +/* +============ +FS_LoadFile + +Filename are relative to the xash directory. +Always appends a 0 byte. +============ +*/ +byte *FS_LoadFile( const char *path, size_t *filesizeptr, bool gamedironly ) +{ + file_t *file; + byte *buf = NULL; + size_t filesize = 0; + + file = FS_Open( path, "rb", gamedironly ); + + if( file ) + { + filesize = file->real_length; + buf = (byte *)Mem_Alloc( filesize + 1, C_FILESYSTEM ); + buf[filesize] = '\0'; + FS_Read( file, buf, filesize ); + FS_Close( file ); + } + else + { + buf = W_LoadFile( path, &filesize, gamedironly ); + } + + if( filesizeptr ) + *filesizeptr = filesize; + + return buf; +} + +/* +================== +FS_FileLength + +return size of file in bytes +================== +*/ +long FS_FileLength( file_t *f ) +{ + if( !f ) return 0; + return f->real_length; +} + +/* +============================================================================= + +OTHERS PUBLIC FUNCTIONS + +============================================================================= +*/ +/* +================== +FS_FileExists + +Look for a file in the packages and in the filesystem +================== +*/ +bool FS_FileExists( const char *filename, bool gamedironly ) +{ + if( FS_FindFile( filename, NULL, gamedironly )) + return true; + return false; +} + +/* +================== +FS_FileTime + +return time of creation file in seconds +================== +*/ +long FS_FileTime( const char *filename, bool gamedironly ) +{ + searchpath_t *search; + int pack_ind; + + search = FS_FindFile( filename, &pack_ind, gamedironly ); + if( !search ) return -1; // doesn't exist + + if( search->pack ) // grab pack filetime + return search->pack->filetime; + else if( search->wad ) // grab wad filetime + return search->wad->filetime; + else if( pack_ind < 0 ) + { + char path [MAX_SYSPATH]; + + // found in the filesystem? + Q_sprintf( path, "%s%s", search->filename, filename ); + return COM_FileTime( path ); + } + + return -1; // doesn't exist +} + +/* +=========== +FS_Search + +Allocate and fill a search structure with information on matching filenames. +=========== +*/ +search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ) +{ + search_t *search = NULL; + searchpath_t *searchpath; + pack_t *pak; + wfile_t *wad; + int i, basepathlength, numfiles, numchars; + int resultlistindex, dirlistindex; + const char *slash, *backslash, *colon, *separator; + string netpath, temp; + stringlist_t resultlist; + stringlist_t dirlist; + char *basepath; + + for( i = 0; pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\'; i++ ); + + if( i > 0 ) + { + MsgDev( D_INFO, "FS_Search: don't use punctuation at the beginning of a search pattern!\n"); + return NULL; + } + + stringlistinit( &resultlist ); + stringlistinit( &dirlist ); + slash = Q_strrchr( pattern, '/' ); + backslash = Q_strrchr( pattern, '\\' ); + colon = Q_strrchr( pattern, ':' ); + separator = max( slash, backslash ); + separator = max( separator, colon ); + basepathlength = separator ? (separator + 1 - pattern) : 0; + basepath = (char *)Mem_Alloc( basepathlength + 1, C_FILESYSTEM ); + if( basepathlength ) memcpy( basepath, pattern, basepathlength ); + basepath[basepathlength] = 0; + + // search through the path, one element at a time + for( searchpath = fs_searchpaths; searchpath; searchpath = searchpath->next ) + { + if( gamedironly && !FBitSet( searchpath->flags, FS_GAMEDIR_PATH )) + continue; + + // is the element a pak file? + if( searchpath->pack ) + { + // look through all the pak file elements + pak = searchpath->pack; + for( i = 0; i < pak->numfiles; i++ ) + { + Q_strncpy( temp, pak->files[i].name, sizeof( temp )); + while( temp[0] ) + { + if( matchpattern( temp, (char *)pattern, true )) + { + for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) + { + if( !Q_strcmp( resultlist.strings[resultlistindex], temp )) + break; + } + + if( resultlistindex == resultlist.numstrings ) + stringlistappend( &resultlist, temp ); + } + + // strip off one path element at a time until empty + // this way directories are added to the listing if they match the pattern + slash = Q_strrchr( temp, '/' ); + backslash = Q_strrchr( temp, '\\' ); + colon = Q_strrchr( temp, ':' ); + separator = temp; + if( separator < slash ) + separator = slash; + if( separator < backslash ) + separator = backslash; + if( separator < colon ) + separator = colon; + *((char *)separator) = 0; + } + } + } + else if( searchpath->wad ) + { + string wadpattern, wadname, temp2; + char type = W_TypeFromExt( pattern ); + qboolean anywadname = true; + string wadfolder; + + // quick reject by filetype + if( type == TYP_NONE ) continue; + COM_ExtractFilePath( pattern, wadname ); + COM_FileBase( pattern, wadpattern ); + wadfolder[0] = '\0'; + + if( Q_strlen( wadname )) + { + COM_FileBase( wadname, wadname ); + Q_strncpy( wadfolder, wadname, sizeof( wadfolder )); + COM_DefaultExtension( wadname, ".wad" ); + anywadname = false; + } + + // make wadname from wad fullpath + COM_FileBase( searchpath->wad->filename, temp2 ); + COM_DefaultExtension( temp2, ".wad" ); + + // quick reject by wadname + if( !anywadname && Q_stricmp( wadname, temp2 )) + continue; + + // look through all the wad file elements + wad = searchpath->wad; + + for( i = 0; i < wad->numlumps; i++ ) + { + // if type not matching, we already have no chance ... + if( type != TYP_ANY && wad->lumps[i].type != type ) + continue; + + // build the lumpname with image suffix (if present) + Q_snprintf( temp, sizeof( temp ), "%s%s", wad->lumps[i].name, wad_hints[wad->lumps[i].img_type].ext ); + + while( temp[0] ) + { + if( matchpattern( temp, wadpattern, true )) + { + for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) + { + if( !Q_strcmp( resultlist.strings[resultlistindex], temp )) + break; + } + + if( resultlistindex == resultlist.numstrings ) + { + // build path: wadname/lumpname.ext + Q_snprintf( temp2, sizeof(temp2), "%s/%s", wadfolder, temp ); + COM_DefaultExtension( temp2, va(".%s", W_ExtFromType( wad->lumps[i].type ))); + stringlistappend( &resultlist, temp2 ); + } + } + + // strip off one path element at a time until empty + // this way directories are added to the listing if they match the pattern + slash = Q_strrchr( temp, '/' ); + backslash = Q_strrchr( temp, '\\' ); + colon = Q_strrchr( temp, ':' ); + separator = temp; + if( separator < slash ) + separator = slash; + if( separator < backslash ) + separator = backslash; + if( separator < colon ) + separator = colon; + *((char *)separator) = 0; + } + } + } + else + { + // get a directory listing and look at each name + Q_sprintf( netpath, "%s%s", searchpath->filename, basepath ); + stringlistinit( &dirlist ); + listdirectory( &dirlist, netpath ); + + for( dirlistindex = 0; dirlistindex < dirlist.numstrings; dirlistindex++ ) + { + Q_sprintf( temp, "%s%s", basepath, dirlist.strings[dirlistindex] ); + + if( matchpattern( temp, (char *)pattern, true )) + { + for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) + { + if( !Q_strcmp( resultlist.strings[resultlistindex], temp )) + break; + } + + if( resultlistindex == resultlist.numstrings ) + stringlistappend( &resultlist, temp ); + } + } + + stringlistfreecontents( &dirlist ); + } + } + + if( resultlist.numstrings ) + { + stringlistsort( &resultlist ); + numfiles = resultlist.numstrings; + numchars = 0; + + for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) + numchars += (int)Q_strlen( resultlist.strings[resultlistindex]) + 1; + search = (search_t *)Mem_Alloc( sizeof( search_t ) + numchars + numfiles * sizeof( char* ), C_FILESYSTEM ); + search->filenames = (char **)((char *)search + sizeof( search_t )); + search->filenamesbuffer = (char *)((char *)search + sizeof( search_t ) + numfiles * sizeof( char* )); + search->numfilenames = (int)numfiles; + numfiles = numchars = 0; + + for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) + { + size_t textlen; + + search->filenames[numfiles] = search->filenamesbuffer + numchars; + textlen = Q_strlen(resultlist.strings[resultlistindex]) + 1; + memcpy( search->filenames[numfiles], resultlist.strings[resultlistindex], textlen ); + numfiles++; + numchars += (int)textlen; + } + } + + stringlistfreecontents( &resultlist ); + + Mem_Free( basepath, C_FILESYSTEM ); + + return search; +} + +/* +============================================================================= + +FILESYSTEM IMPLEMENTATION + +============================================================================= +*/ +/* +=========== +W_LoadFile + +loading lump into the tmp buffer +=========== +*/ +static byte *W_LoadFile( const char *path, size_t *lumpsizeptr, bool gamedironly ) +{ + searchpath_t *search; + int index; + + search = FS_FindFile( path, &index, gamedironly ); + if( search && search->wad ) + return W_ReadLump( search->wad, &search->wad->lumps[index], lumpsizeptr ); + return NULL; +} \ No newline at end of file diff --git a/utils/common/basetypes.h b/utils/common/basetypes.h new file mode 100644 index 0000000..caddc62 --- /dev/null +++ b/utils/common/basetypes.h @@ -0,0 +1,80 @@ +/* +basetypes.h - aliases for some base types for convenience +Copyright (C) 2012 Uncle Mike + +This file is part of XashNT source code. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef BASETYPES_H +#define BASETYPES_H + +//#define HLFX_BUILD + +#ifdef _WIN32 +#pragma warning( disable : 4244 ) // MIPS +#pragma warning( disable : 4018 ) // signed/unsigned mismatch +#pragma warning( disable : 4305 ) // truncation from const double to float +#endif + +typedef unsigned char byte; +typedef unsigned short word; +typedef unsigned long dword; +typedef unsigned int uint; +typedef unsigned long ulong; + +typedef unsigned char uint8; +typedef signed char int8; +typedef __int16 int16; +typedef unsigned __int16 uint16; +typedef __int32 int32; +typedef unsigned __int32 uint32; +typedef __int64 int64; +typedef unsigned __int64 uint64; + +#undef true +#undef false + +#ifndef __cplusplus +typedef enum { false, true } qboolean; +#else +typedef int qboolean; +#endif + +// We need to inform the compiler that Host_Error() and Sys_Error() will +// never return, so any conditions that leeds to them being called are +// guaranteed to be false in the following code +#define NO_RETURN __declspec( noreturn ) +#define ALIGN16 __declspec(align(16)) + +// a simple string implementation +#define MAX_STRING 256 +typedef char string[MAX_STRING]; + +// !!! if this is changed, it must be changed in alert.h too !!! +enum +{ + D_INFO = 1, // "-dev 1", shows various system messages + D_ERROR, // "-dev 2", shows critical warnings + D_WARN, // "-dev 3", shows not critical system warnings + D_REPORT, // "-dev 4", show system reports for advanced users and engine developers + D_NOTE // "-dev 5", show system notifications for engine developers +}; + +#define DXT_ENCODE_DEFAULT 0 // don't use custom encoders +#define DXT_ENCODE_COLOR_YCoCg 0x1A01 // make sure that value dosn't collide with anything +#define DXT_ENCODE_ALPHA_1BIT 0x1A02 // normal 1-bit alpha +#define DXT_ENCODE_ALPHA_8BIT 0x1A03 // normal 8-bit alpha +#define DXT_ENCODE_ALPHA_SDF 0x1A04 // signed distance field +#define DXT_ENCODE_NORMAL_AG_PARABOLOID 0x1A07 // paraboloid projection + +#endif//BASETYPES_H \ No newline at end of file diff --git a/utils/common/bspfile.cpp b/utils/common/bspfile.cpp new file mode 100644 index 0000000..5fb1826 --- /dev/null +++ b/utils/common/bspfile.cpp @@ -0,0 +1,1372 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include +#include +#include +#include "cmdlib.h" +#include "mathlib.h" +#include "bspfile.h" +#include "scriplib.h" +#include "filesystem.h" +#include "stringlib.h" +#include + +//============================================================================= +bool g_found_extradata = false; + +int g_nummodels; +dmodel_t g_dmodels[MAX_MAP_MODELS]; + +int g_visdatasize; +byte g_dvisdata[MAX_MAP_VISIBILITY]; + +int g_vislightdatasize; +byte *g_dvislightdata; + +int g_lightdatasize; +byte *g_dlightdata; + +int g_deluxdatasize; +byte *g_ddeluxdata; + +int g_shadowdatasize; +byte *g_dshadowdata; + +int g_texdatasize; +byte *g_dtexdata; // (dmiptexlump_t) + +int g_entdatasize; +char g_dentdata[MAX_MAP_ENTSTRING]; + +int g_numleafs; +int g_numvisleafs; +dleaf_t g_dleafs[MAX_MAP_LEAFS]; + +int g_numplanes; +dplane_t g_dplanes[MAX_INTERNAL_MAP_PLANES]; + +int g_numvertexes; +dvertex_t g_dvertexes[MAX_MAP_VERTS]; + +int g_numnodes; +dnode_t g_dnodes[MAX_MAP_NODES]; + +int g_numtexinfo; +dtexinfo_t g_texinfo[MAX_INTERNAL_MAP_TEXINFO]; + +int g_numfaces; +dface_t g_dfaces[MAX_MAP_FACES]; + +int g_numclipnodes; +dclipnode_t g_dclipnodes[MAX_MAP_CLIPNODES]; +dclipnode32_t g_dclipnodes32[MAX_MAP_CLIPNODES32]; + +int g_numedges; +dedge_t g_dedges[MAX_MAP_EDGES]; + +int g_nummarksurfaces; +dmarkface_t g_dmarksurfaces[MAX_MAP_MARKSURFACES]; + +int g_numsurfedges; +dsurfedge_t g_dsurfedges[MAX_MAP_SURFEDGES]; + +int g_numfaceinfo; +dfaceinfo_t g_dfaceinfo[MAX_MAP_FACEINFO]; + +int g_numcubemaps; +dcubemap_t g_dcubemaps[MAX_MAP_CUBEMAPS]; + +int g_numleaflights; +dleafsample_t g_dleaflights[MAX_MAP_LEAFLIGHTS]; + +int g_numworldlights; +dworldlight_t g_dworldlights[MAX_MAP_WORLDLIGHTS]; + +int g_vlightdatasize; +byte *g_dvlightdata; // (dvlightlump_t) + +int g_flightdatasize; +byte *g_dflightdata; // (dflightlump_t) + +int g_normaldatasize; +byte *g_dnormaldata; // (dnormallump_t) + +int g_numentities; +entity_t g_entities[MAX_MAP_ENTITIES]; + +dheader_t *header, outheader; +long wadfile; + +char g_wadpath[1024]; // path to wads may be empty + +// can be overrided from hlcsg +vec3_t g_hull_size[MAX_MAP_HULLS][2] = +{ +{ // 0x0x0 +{ 0, 0, 0 }, { 0, 0, 0 } +}, +{ // 32x32x72 +{-16, -16, -36 }, { 16, 16, 36 } +}, +{ // 64x64x64 +{-32, -32, -32 }, { 32, 32, 32 } +}, +{ // 32x32x36 +{-16, -16, -18 }, { 16, 16, 18 } +} +}; + +/* +============ +CheckHullFile +============ +*/ +void CheckHullFile( const char *filename ) +{ + vec3_t new_hulls[MAX_MAP_HULLS][2]; + bool read_error = false; + char scan[128]; + + if( !filename[0] ) return; + + // open up hull file + FILE *f = fopen( filename, "r" ); + + if( !f ) + { + MsgDev( D_WARN, "couldn't open hullfile %s, using default hulls", filename ); + return; + } + else + { + MsgDev( D_INFO, "[Reading hulls from '%s']\n", filename ); + } + + for( int i = 0; i < MAX_MAP_HULLS; i++ ) + { + float x1, y1, z1, x2, y2, z2; + vec3_t mins, maxs; + int argCnt; + + if( !fgets( scan, sizeof( scan ), f )) + { + MsgDev( D_WARN, "parsing %s, couln't read hull line %i, using default hulls\n", filename, i ); + read_error = true; + break; + } + + argCnt = sscanf( scan, "( %f %f %f ) ( %f %f %f ) ", &x1, &y1, &z1, &x2, &y2, &z2 ); + + if( argCnt != 6 ) + { + MsgDev( D_ERROR, "parsing %s, expeciting '( x y z ) ( x y z )' using default hulls\n", filename ); + read_error = true; + break; + } + else + { + VectorSet( mins, x1, y1, z1 ); + VectorSet( maxs, x2, y2, z2 ); + } + + VectorCopy( mins, new_hulls[i][0] ); + VectorCopy( maxs, new_hulls[i][1] ); + } + + if( read_error ) + { + MsgDev( D_REPORT, "error parsing %s, using default hulls\n", filename ); + } + else + { + memcpy( g_hull_size, new_hulls, 2 * MAX_MAP_HULLS * sizeof( vec3_t ) ); + } + + fclose( f ); +} + +/* +=============== +CompressVis + +=============== +*/ +int CompressVis( const byte *src, const uint src_length, byte *dest, uint dest_length ) +{ + uint j, current_length = 0; + byte *dest_p = dest; + int rep; + + for( j = 0; j < src_length; j++ ) + { + if( ++current_length > dest_length ) + COM_FatalError( "CompressVis: compressed vismap overflow\n" ); + + *dest_p = src[j]; + dest_p++; + if( src[j] ) continue; + + for( j++, rep = 1; j < src_length; j++ ) + { + if( src[j] || rep == 255 ) + break; + else rep++; + } + + if( ++current_length > dest_length ) + COM_FatalError( "CompressVis: compressed vismap overflow\n" ); + + *dest_p++ = rep; + j--; + } + + return dest_p - dest; +} + +/* +=================== +DecompressVis +=================== +*/ +void DecompressVis( byte *in, byte *decompressed ) +{ + byte *out; + int c, row; + + row = (g_numvisleafs + 7) >> 3; + out = decompressed; + + do + { + if( *in ) + { + *out++ = *in++; + continue; + } + + c = in[1]; + if( !c ) COM_FatalError( "DecompressVis: 0 repeat\n" ); + + in += 2; + while( c ) + { + *out++ = 0; + c--; + } + } while( out - decompressed < row ); +} + +//============================================================================= + +int CopyLump( int lump, void *dest, int size ) +{ + int length = header->lumps[lump].filelen; + int ofs = header->lumps[lump].fileofs; + + if( length % size ) + COM_FatalError( "LoadBSPFile: odd lump size\n" ); + + // alloc matched size + if( lump == LUMP_TEXTURES ) + dest = g_dtexdata = (byte *)Mem_Alloc( length ); + if( lump == LUMP_LIGHTING ) + dest = g_dlightdata = (byte *)Mem_Alloc( length ); + memcpy( dest, (byte *)header + ofs, length ); + + return length / size; +} + +static int CopyExtraLump( int lump, void *dest, int size, const dheader_t *header ) +{ + dextrahdr_t *extrahdr = (dextrahdr_t *)((byte *)header + sizeof( dheader_t )); + + int length = extrahdr->lumps[lump].filelen; + int ofs = extrahdr->lumps[lump].fileofs; + + if( length % size ) + COM_FatalError( "LoadBSPFile: odd lump size\n" ); + + if( lump == LUMP_LIGHTVECS ) + dest = g_ddeluxdata = (byte *)Mem_Alloc( length ); + if( lump == LUMP_SHADOWMAP ) + dest = g_dshadowdata = (byte *)Mem_Alloc( length ); + if( lump == LUMP_VERTEX_LIGHT ) + dest = g_dvlightdata = (byte *)Mem_Alloc( length ); + if( lump == LUMP_SURFACE_LIGHT ) + dest = g_dflightdata = (byte *)Mem_Alloc( length ); + if( lump == LUMP_VERTNORMALS ) + dest = g_dnormaldata = (byte *)Mem_Alloc( length ); + if( lump == LUMP_VISLIGHTDATA ) + dest = g_dvislightdata = (byte *)Mem_Alloc( length ); + memcpy( dest, (byte *)header + ofs, length ); + + return length / size; +} + +static int CopyLumpClipnodes( int lump ) +{ + int length = header->lumps[lump].filelen; + int ofs = header->lumps[lump].fileofs; + bool clipnodes_compatible = true; + int size; + + if(( length % sizeof( dclipnode_t )) || ( length / sizeof( dclipnode_t )) >= MAX_MAP_CLIPNODES ) + clipnodes_compatible = false; + + if( clipnodes_compatible ) + { + // compatible 16-bit clipnodes + size = sizeof( dclipnode_t ); + memcpy( g_dclipnodes, (byte *)header + ofs, length ); + + // share clipnodes with 32-bit array + for( int i = 0; i < (length / size); i++ ) + { + g_dclipnodes32[i].children[0] = g_dclipnodes[i].children[0]; + g_dclipnodes32[i].children[1] = g_dclipnodes[i].children[1]; + g_dclipnodes32[i].planenum = g_dclipnodes[i].planenum; + } + } + else + { + // extended 32-bit clipnodes + size = sizeof( dclipnode32_t ); + memcpy( g_dclipnodes32, (byte *)header + ofs, length ); + } + + return length / size; +} + +/* +============= +LoadBSPFile +============= +*/ +void LoadBSPFile( const char *filename ) +{ + size_t filesize; + + // load the file header + header = (dheader_t *)COM_LoadFile( filename, &filesize ); + if( !header ) COM_FatalError( "couldn't load: %s\n", filename ); + + if( header->version != BSPVERSION ) + COM_FatalError( "%s is version %i, not %i\n", filename, header->version, BSPVERSION ); + MsgDev( D_REPORT, "loading %s\n", filename ); + + g_nummodels = CopyLump( LUMP_MODELS, g_dmodels, sizeof( dmodel_t )); + g_numvertexes = CopyLump( LUMP_VERTEXES, g_dvertexes, sizeof( dvertex_t )); + g_numplanes = CopyLump( LUMP_PLANES, g_dplanes, sizeof( dplane_t )); + g_numleafs = CopyLump( LUMP_LEAFS, g_dleafs, sizeof( dleaf_t )); + g_numnodes = CopyLump( LUMP_NODES, g_dnodes, sizeof( dnode_t )); + g_numtexinfo = CopyLump( LUMP_TEXINFO, g_texinfo, sizeof( dtexinfo_t )); + g_numfaces = CopyLump( LUMP_FACES, g_dfaces, sizeof( dface_t )); + g_nummarksurfaces = CopyLump( LUMP_MARKSURFACES, g_dmarksurfaces, sizeof( dmarkface_t )); + g_numsurfedges = CopyLump( LUMP_SURFEDGES, g_dsurfedges, sizeof( dsurfedge_t )); + g_numedges = CopyLump( LUMP_EDGES, g_dedges, sizeof( dedge_t )); + g_numclipnodes = CopyLumpClipnodes( LUMP_CLIPNODES ); + g_numvisleafs = g_dmodels[0].visleafs; + + g_texdatasize = CopyLump( LUMP_TEXTURES, g_dtexdata, 1 ); + g_visdatasize = CopyLump( LUMP_VISIBILITY, g_dvisdata, 1 ); + g_lightdatasize = CopyLump( LUMP_LIGHTING, g_dlightdata, 1 ); + g_entdatasize = CopyLump( LUMP_ENTITIES, g_dentdata, 1 ); + + dextrahdr_t *extrahdr = (dextrahdr_t *)((byte *)header + sizeof( dheader_t )); + + if( extrahdr->id == IDEXTRAHEADER ) + { + if( extrahdr->version != EXTRA_VERSION ) + COM_FatalError( "BSP is extra version %i, not %i", extrahdr->version, EXTRA_VERSION ); + + g_found_extradata = true; + + // g-cont. copy the extra lumps + g_normaldatasize = CopyExtraLump( LUMP_VERTNORMALS, g_dnormaldata, 1, header ); + g_deluxdatasize = CopyExtraLump( LUMP_LIGHTVECS, g_ddeluxdata, 1, header ); + g_numcubemaps = CopyExtraLump( LUMP_CUBEMAPS, g_dcubemaps, sizeof( dcubemap_t ), header ); + g_numfaceinfo = CopyExtraLump( LUMP_FACEINFO, g_dfaceinfo, sizeof( dfaceinfo_t ), header ); + g_numworldlights = CopyExtraLump( LUMP_WORLDLIGHTS, g_dworldlights, sizeof( dworldlight_t ), header ); + g_numleaflights = CopyExtraLump( LUMP_LEAF_LIGHTING, g_dleaflights, sizeof( dleafsample_t ), header ); + g_shadowdatasize = CopyExtraLump( LUMP_SHADOWMAP, g_dshadowdata, 1, header ); + g_vlightdatasize = CopyExtraLump( LUMP_VERTEX_LIGHT, g_dvlightdata, 1, header ); + g_flightdatasize = CopyExtraLump( LUMP_SURFACE_LIGHT, g_dflightdata, 1, header ); + g_vislightdatasize = CopyExtraLump( LUMP_VISLIGHTDATA, g_dvislightdata, 1, header ); + } + + Mem_Free( header, C_FILESYSTEM ); // everything has been copied out +} + +//============================================================================ + +void AddLump( int lumpnum, void *data, int len ) +{ + dlump_t *lump = &header->lumps[lumpnum]; + lump->fileofs = tell( wadfile ); + lump->filelen = len; + SafeWrite( wadfile, data, (len + 3) & ~3 ); +} + +static void AddExtraLump( int lumpnum, void *data, int len, dextrahdr_t *header ) +{ + dlump_t* lump = &header->lumps[lumpnum]; + lump->fileofs = tell( wadfile ); + lump->filelen = len; + SafeWrite( wadfile, data, (len + 3) & ~3 ); +} + +void AddLumpClipnodes( int lumpnum ) +{ + dlump_t *lump = &header->lumps[lumpnum]; + lump->fileofs = tell( wadfile ); + + if( g_numclipnodes < MAX_MAP_CLIPNODES ) + { + // copy clipnodes into 16-bit array + for( int i = 0; i < g_numclipnodes; i++ ) + { + g_dclipnodes[i].children[0] = (short)g_dclipnodes32[i].children[0]; + g_dclipnodes[i].children[1] = (short)g_dclipnodes32[i].children[1]; + g_dclipnodes[i].planenum = g_dclipnodes32[i].planenum; + } + + lump->filelen = g_numclipnodes * sizeof( dclipnode_t ); + SafeWrite( wadfile, g_dclipnodes, (lump->filelen + 3) & ~3 ); + } + else + { + // copy clipnodes into 32-bit array + lump->filelen = g_numclipnodes * sizeof( dclipnode32_t ); + SafeWrite( wadfile, g_dclipnodes32, (lump->filelen + 3) & ~3 ); + } +} + +/* +============= +WriteBSPFile + +Swaps the bsp file in place, so it should not be referenced again +============= +*/ +void WriteBSPFile( const char *filename ) +{ + dextrahdr_t outextrahdr; + dextrahdr_t *extrahdr; + + header = &outheader; + memset( header, 0, sizeof( dheader_t )); + extrahdr = &outextrahdr; + memset( extrahdr, 0, sizeof( dextrahdr_t )); + + header->version = BSPVERSION; + extrahdr->id = IDEXTRAHEADER; + extrahdr->version = EXTRA_VERSION; + + wadfile = SafeOpenWrite( filename ); + SafeWrite( wadfile, header, sizeof( dheader_t )); // overwritten later + SafeWrite( wadfile, extrahdr, sizeof( dextrahdr_t )); // overwritten later + + AddLump( LUMP_PLANES, g_dplanes, g_numplanes * sizeof( dplane_t )); + AddLump( LUMP_LEAFS, g_dleafs, g_numleafs * sizeof( dleaf_t )); + AddLump( LUMP_VERTEXES, g_dvertexes, g_numvertexes * sizeof( dvertex_t )); + AddLump( LUMP_NODES, g_dnodes, g_numnodes * sizeof( dnode_t )); + AddLump( LUMP_TEXINFO, g_texinfo, g_numtexinfo * sizeof( dtexinfo_t )); + AddLump( LUMP_FACES, g_dfaces, g_numfaces * sizeof( dface_t )); + AddLump( LUMP_MARKSURFACES, g_dmarksurfaces, g_nummarksurfaces * sizeof( dmarkface_t )); + AddLump( LUMP_SURFEDGES, g_dsurfedges, g_numsurfedges * sizeof( dsurfedge_t )); + AddLump( LUMP_EDGES, g_dedges, g_numedges * sizeof( dedge_t )); + AddLump( LUMP_MODELS, g_dmodels, g_nummodels * sizeof( dmodel_t )); + AddLumpClipnodes( LUMP_CLIPNODES ); + + AddLump( LUMP_LIGHTING, g_dlightdata, g_lightdatasize ); + AddLump( LUMP_VISIBILITY, g_dvisdata, g_visdatasize ); + AddLump( LUMP_ENTITIES, g_dentdata, g_entdatasize ); + AddLump( LUMP_TEXTURES, g_dtexdata, g_texdatasize ); + + AddExtraLump( LUMP_VERTNORMALS, g_dnormaldata, g_normaldatasize, extrahdr ); + AddExtraLump( LUMP_LIGHTVECS, g_ddeluxdata, g_deluxdatasize, extrahdr ); + AddExtraLump( LUMP_CUBEMAPS, g_dcubemaps, g_numcubemaps * sizeof( dcubemap_t ), extrahdr ); + AddExtraLump( LUMP_FACEINFO, g_dfaceinfo, g_numfaceinfo * sizeof( dfaceinfo_t ), extrahdr ); + AddExtraLump( LUMP_WORLDLIGHTS, g_dworldlights, g_numworldlights * sizeof( dworldlight_t ), extrahdr ); + AddExtraLump( LUMP_SHADOWMAP, g_dshadowdata, g_shadowdatasize, extrahdr ); + AddExtraLump( LUMP_LEAF_LIGHTING,g_dleaflights, g_numleaflights * sizeof( dleafsample_t ), extrahdr ); + AddExtraLump( LUMP_VERTEX_LIGHT, g_dvlightdata, g_vlightdatasize, extrahdr ); + AddExtraLump( LUMP_SURFACE_LIGHT,g_dflightdata, g_flightdatasize, extrahdr ); + AddExtraLump( LUMP_VISLIGHTDATA, g_dvislightdata, g_vislightdatasize, extrahdr ); + + lseek( wadfile, 0, SEEK_SET ); + SafeWrite( wadfile, header, sizeof( dheader_t )); + SafeWrite( wadfile, extrahdr, sizeof( dextrahdr_t )); + + close( wadfile ); + + if( g_dvislightdata ) Mem_Free( g_dvislightdata ); + if( g_dlightdata ) Mem_Free( g_dlightdata ); + if( g_ddeluxdata ) Mem_Free( g_ddeluxdata ); + if( g_dshadowdata ) Mem_Free( g_dshadowdata ); + if( g_dvlightdata ) Mem_Free( g_dvlightdata ); + if( g_dflightdata ) Mem_Free( g_dflightdata ); + if( g_dnormaldata ) Mem_Free( g_dnormaldata ); + if( g_dtexdata ) Mem_Free( g_dtexdata ); + + g_dvislightdata = NULL; + g_dlightdata = NULL; + g_ddeluxdata = NULL; + g_dshadowdata = NULL; + g_dvlightdata = NULL; + g_dflightdata = NULL; + g_dnormaldata = NULL; + g_dtexdata = NULL; +} + +//============================================================================ + +#define ENTRIES(a) (sizeof(a)/sizeof(*(a))) +#define ENTRYSIZE(a) (sizeof(*(a))) + +// ===================================================================================== +// ArrayUsage +// blah +// ===================================================================================== +static int ArrayUsage( const char *szItem, const int items, const int maxitems, const int itemsize ) +{ + float percentage = maxitems ? items * 100.0 / maxitems : 0.0; + + Msg("%-13s %7i/%-7i %8i/%-8i (%4.1f%%)\n", szItem, items, maxitems, items * itemsize, maxitems * itemsize, percentage ); + + return items * itemsize; +} + +// ===================================================================================== +// GlobUsage +// pritn out global ussage line in chart +// ===================================================================================== +static int GlobUsage( const char *szItem, const int itemstorage, const int maxstorage ) +{ + float percentage = maxstorage ? itemstorage * 100.0 / maxstorage : 0.0; + + Msg("%-13s [variable] %8i/%-8i (%4.1f%%)\n", szItem, itemstorage, maxstorage, percentage ); + + return itemstorage; +} + +/* +============= +PrintBSPFileSizes + +Dumps info about current file +============= +*/ +void PrintBSPFileSizes (void) +{ + int numtextures = g_texdatasize ? ((dmiptexlump_t*)g_dtexdata)->nummiptex : 0; + int totalmemory = 0; + + Msg( "\n"); + Msg( "Object names Objects/Maxobjs Memory / Maxmem Fullness\n" ); + Msg( "------------ --------------- --------------- --------\n" ); + + totalmemory += ArrayUsage( "models", g_nummodels, ENTRIES( g_dmodels ), ENTRYSIZE( g_dmodels )); + totalmemory += ArrayUsage( "planes", g_numplanes, ENTRIES( g_dplanes ), ENTRYSIZE( g_dplanes )); + totalmemory += ArrayUsage( "vertexes", g_numvertexes, ENTRIES( g_dvertexes ), ENTRYSIZE( g_dvertexes )); + totalmemory += ArrayUsage( "nodes", g_numnodes, ENTRIES( g_dnodes ), ENTRYSIZE( g_dnodes )); + totalmemory += ArrayUsage( "texinfos", g_numtexinfo, ENTRIES( g_texinfo ), ENTRYSIZE( g_texinfo )); + totalmemory += ArrayUsage( "faces", g_numfaces, ENTRIES( g_dfaces ), ENTRYSIZE( g_dfaces )); + totalmemory += ArrayUsage( "clipnodes", g_numclipnodes, ENTRIES( g_dclipnodes ), ENTRYSIZE( g_dclipnodes )); + totalmemory += ArrayUsage( "leaves", g_numleafs, ENTRIES( g_dleafs ), ENTRYSIZE( g_dleafs )); + totalmemory += ArrayUsage( "marksurfaces", g_nummarksurfaces, ENTRIES( g_dmarksurfaces ), ENTRYSIZE( g_dmarksurfaces )); + totalmemory += ArrayUsage( "surfedges", g_numsurfedges, ENTRIES( g_dsurfedges ), ENTRYSIZE( g_dsurfedges )); + totalmemory += ArrayUsage( "edges", g_numedges, ENTRIES( g_dedges ), ENTRYSIZE( g_dedges )); + + totalmemory += GlobUsage( "texdata", g_texdatasize, MAX_MAP_MIPTEX ); + totalmemory += GlobUsage( "lightdata", g_lightdatasize, MAX_MAP_LIGHTING ); + totalmemory += GlobUsage( "visdata", g_visdatasize, sizeof( g_dvisdata )); + totalmemory += GlobUsage( "entdata", g_entdatasize, sizeof( g_dentdata )); + + if( g_found_extradata ) + { + totalmemory += GlobUsage( "normals", g_normaldatasize, MAX_MAP_LIGHTING ); + totalmemory += GlobUsage( "deluxdata", g_deluxdatasize, MAX_MAP_LIGHTING ); + totalmemory += ArrayUsage( "cubemaps", g_numcubemaps, ENTRIES( g_dcubemaps ), ENTRYSIZE( g_dcubemaps )); + totalmemory += ArrayUsage( "faceinfo", g_numfaceinfo, ENTRIES( g_dfaceinfo ), ENTRYSIZE( g_dfaceinfo )); + totalmemory += ArrayUsage( "direct lights", g_numworldlights, ENTRIES( g_dworldlights ), ENTRYSIZE( g_dworldlights )); + totalmemory += ArrayUsage( "ambient cubes", g_numleaflights, ENTRIES( g_dleaflights ), ENTRYSIZE( g_dleaflights )); + totalmemory += GlobUsage( "occlusion", g_shadowdatasize, MAX_MAP_LIGHTING / 3 ); + totalmemory += GlobUsage( "vertexlight", g_vlightdatasize, MAX_MAP_LIGHTING ); + totalmemory += GlobUsage( "vislightdata", g_vislightdatasize, MAX_MAP_VISLIGHTDATA ); + } + + Msg( "=== Total BSP file data space used: %s ===\n", Q_memprint( totalmemory )); +} + +int GetSurfaceExtent( const dtexinfo_t *tex ) +{ + ASSERT( tex != NULL ); + + if( FBitSet( tex->flags, TEX_EXTRA_LIGHTMAP )) + return MAX_EXTRA_EXTENTS; + + if( tex->faceinfo == -1 ) + return MAX_SURFACE_EXTENT; + + int max_extent = g_dfaceinfo[tex->faceinfo].max_extent; + + // check bounds + if( max_extent >= MIN_CUSTOM_SURFACE_EXTENT && max_extent <= MAX_CUSTOM_SURFACE_EXTENT ) + return max_extent; + + return MAX_SURFACE_EXTENT; +} + +int GetSurfaceExtent( const dface_t *f ) +{ + ASSERT( f != NULL ); + + return GetSurfaceExtent( &g_texinfo[f->texinfo] ); +} + +int GetTextureStep( const dtexinfo_t *tex, bool force ) +{ + ASSERT( tex != NULL ); + + if( !force && FBitSet( tex->flags, TEX_WORLD_LUXELS )) + return 1; + + if( FBitSet( tex->flags, TEX_EXTRA_LIGHTMAP )) + return TEXTURE_EXTRA_STEP; + + if( tex->faceinfo == -1 ) + return TEXTURE_STEP; + + int texture_step = g_dfaceinfo[tex->faceinfo].texture_step; + + // check bounds + if( texture_step >= MIN_CUSTOM_TEXTURE_STEP && texture_step <= MAX_CUSTOM_TEXTURE_STEP ) + return texture_step; + + return TEXTURE_STEP; +} + +int GetTextureStep( const dface_t *f, bool force ) +{ + ASSERT( f != NULL ); + + return GetTextureStep( &g_texinfo[f->texinfo], force ); +} + +word GetSurfaceGroupId( const dtexinfo_t *tex ) +{ + ASSERT( tex != NULL ); + + if( tex->faceinfo == -1 ) + return 0; + + return (word)g_dfaceinfo[tex->faceinfo].groupid; +} + +word GetSurfaceGroupId( const dface_t *f ) +{ + ASSERT( f != NULL ); + + return GetSurfaceGroupId( &g_texinfo[f->texinfo] ); +} + +const char *GetTextureByTexinfo( int texinfo ) +{ + if( texinfo != -1 ) + { + dtexinfo_t *info = &g_texinfo[texinfo]; + int ofs = ((dmiptexlump_t*)g_dtexdata)->dataofs[info->miptex]; + miptex_t *miptex = (miptex_t*)(&g_dtexdata[ofs]); + + return miptex->name; + } + + return ""; +} + +const char *ContentsToString( int type ) +{ + switch( type ) + { + case CONTENTS_EMPTY: + return "EMPTY"; + case CONTENTS_SOLID: + return "SOLID"; + case CONTENTS_WATER: + return "WATER"; + case CONTENTS_SLIME: + return "SLIME"; + case CONTENTS_LAVA: + return "LAVA"; + case CONTENTS_SKY: + return "SKY"; + case CONTENTS_ORIGIN: + return "ORIGIN"; + case CONTENTS_TRANSLUCENT: + return "TRANSLUCENT"; + default: + return "UNKNOWN"; + } +} + +int GetFaceContents( const char *name ) +{ + if( !Q_strnicmp( name, "SKY", 3 )) + return CONTENTS_SKY; + + if( name[0] == '!' || name[0] == '*' ) + { + if( !Q_strnicmp( name + 1, "lava", 4 )) + return CONTENTS_LAVA; + else if( !Q_strnicmp( name + 1, "slime", 5 )) + return CONTENTS_SLIME; + return CONTENTS_WATER; // otherwise it's water + } + + if( !Q_strnicmp( name, "water", 5 )) + return CONTENTS_WATER; + + return CONTENTS_SOLID; +} + +void MakeAxial( float normal[3] ) +{ + int i, type; + + for( type = 0; type < 3; type++ ) + { + if( fabs( normal[type] ) > 0.9999f ) + break; + } + + // make positive and pure axial + for( i = 0; i < 3 && type != 3; i++ ) + { + if( i == type ) + normal[i] = 1.0f; + else normal[i] = 0.0f; + } +} + +void LightMatrixFromTexMatrix( const dtexinfo_t *tx, float lmvecs[2][4] ) +{ + vec_t lmscale = TEXTURE_STEP; + + if( tx->faceinfo != -1 ) + { + int texture_step = g_dfaceinfo[tx->faceinfo].texture_step; + + // check bounds + if( texture_step >= MIN_CUSTOM_TEXTURE_STEP && texture_step <= MAX_CUSTOM_TEXTURE_STEP ) + lmscale = texture_step; + } + + // copy texmatrix into lightmap matrix fisrt + for( int i = 0; i < 2; i++ ) + { + for( int j = 0; j < 4; j++ ) + { + lmvecs[i][j] = tx->vecs[i][j]; + } + } + + if( !FBitSet( tx->flags, TEX_WORLD_LUXELS )) + return; // just use texmatrix + + VectorNormalize2( lmvecs[0] ); + VectorNormalize2( lmvecs[1] ); + + if( FBitSet( tx->flags, TEX_AXIAL_LUXELS )) + { + MakeAxial( lmvecs[0] ); + MakeAxial( lmvecs[1] ); + } + + // put the lighting origin at center the poly + VectorScale( lmvecs[0], (1.0 / lmscale), lmvecs[0] ); + VectorScale( lmvecs[1], -(1.0 / lmscale), lmvecs[1] ); + + lmvecs[0][3] = lmscale * 0.5; + lmvecs[1][3] = -lmscale * 0.5; +} + +/* +================= +MapPlaneTypeForNormal +================= +*/ +int MapPlaneTypeForNormal( const vec3_t normal ) +{ + vec_t ax, ay, az; + + ax = fabs( normal[0] ); + ay = fabs( normal[1] ); + az = fabs( normal[2] ); + + if(( ax > 1.0 - DIR_EPSILON ) && ( ay < DIR_EPSILON ) && ( az < DIR_EPSILON )) + return PLANE_X; + + if(( ay > 1.0 - DIR_EPSILON ) && ( az < DIR_EPSILON ) && ( ax < DIR_EPSILON )) + return PLANE_Y; + + if(( az > 1.0 - DIR_EPSILON ) && ( ax < DIR_EPSILON ) && ( ay < DIR_EPSILON )) + return PLANE_Z; + + return PLANE_NONAXIAL; +} + +/* +================ +SnapNormal +================ +*/ +int SnapNormal( vec3_t normal ) +{ + int type = MapPlaneTypeForNormal( normal ); + bool renormalize = false; + + // snap normal to nearest axial if possible + if( type <= PLANE_LAST_AXIAL ) + { + for( int i = 0; i < 3; i++ ) + { + if( i == type ) + normal[i] = normal[i] > 0 ? 1 : -1; + else normal[i] = 0; + } + renormalize = true; + } + else + { + for( int i = 0; i < 3; i++ ) + { + if( fabs( fabs( normal[i] ) - 0.707106 ) < DIR_EPSILON ) + { + normal[i] = normal[i] > 0 ? 0.707106 : -0.707106; + renormalize = true; + } + } + } + + if( renormalize ) + VectorNormalize( normal ); + + return type; +} + +void StripTrailing( char *e ) +{ + char *s; + + s = e + Q_strlen( e ) - 1; + + while( s >= e && *s <= 32 ) + { + *s = 0; + s--; + } +} + +/* +================ +InsertLinkBefore +================ +*/ +void InsertLinkBefore( epair_t *e, entity_t *mapent ) +{ + e->next = NULL; + + if( mapent->epairs != NULL ) + { + e->prev = mapent->tail; + mapent->tail->next = e; + mapent->tail = e; + } + else + { + mapent->epairs = mapent->tail = e; + e->prev = NULL; + } +} + +/* +================ +UnlinkEpair +================ +*/ +void UnlinkEpair( epair_t *e, entity_t *mapent ) +{ + if( e->prev ) e->prev->next = e->next; + else mapent->epairs = e->next; + + if( e->next ) e->next->prev = e->prev; + else mapent->tail = e->prev; + + e->prev = e->next = e; +} + +/* +================= +ParseEpair +================= +*/ +epair_t *ParseEpair( void ) +{ + const char *ext; + epair_t *e; + + e = (epair_t *)Mem_Alloc( sizeof( epair_t ), C_EPAIR ); + + if( Q_strlen( token ) >= ( MAX_KEY - 1 )) + COM_FatalError( "ParseEpair: 'key' token too long\n" ); + + e->key = copystring( token ); + GetToken( false ); + + if( Q_strlen( token ) >= ( MAX_VALUE - 1 )) + COM_FatalError( "ParseEpair: 'value' token too long\n" ); + ext = COM_FileExtension( token ); + + if( !Q_stricmp( ext, "wav" ) || !Q_stricmp( ext, "mdl" ) || !Q_stricmp( ext, "bsp" )) + COM_FixSlashes( token ); + e->value = copystring( token ); + + // strip trailing spaces + StripTrailing( e->key ); + StripTrailing( e->value ); + + return e; +} + +/* +================= +ParseEpair +================= +*/ +void CopyEpairs( entity_t *dst, entity_t *src ) +{ + epair_t *ep, *ep2; + + for( ep = src->epairs; ep; ep = ep->next ) + { + ep2 = (epair_t *)Mem_Alloc( sizeof( epair_t ), C_EPAIR ); + ep2->key = copystring( ep->key ); + ep2->value = copystring( ep->value ); + InsertLinkBefore( ep2, dst ); + } +} + +/* +================ +ParseEntity +================ +*/ +bool ParseEntity( void ) +{ + entity_t *mapent; + epair_t *e; + + if( !GetToken( true )) + return false; + + if( Q_strcmp( token, "{" )) + { + if( g_numentities == 0 ) + COM_FatalError( "ParseEntity: '{' not found\n" ); + else return false; // probably entity string is broken at the end + } + + if( g_numentities == MAX_MAP_ENTITIES ) + COM_FatalError( "MAX_MAP_ENTITIES limit exceeded\n" ); + + mapent = &g_entities[g_numentities]; + g_numentities++; + + do + { + if( !GetToken( true )) + COM_FatalError( "ParseEntity: EOF without closing brace\n" ); + + if( !Q_strcmp( token, "}" )) + break; + + e = ParseEpair(); + InsertLinkBefore( e, mapent ); + + } while( 1 ); + + return true; +} + +/* +================ +ParseEntities + +Parses the dentdata string into entities +================ +*/ +void ParseEntities( void ) +{ + ParseFromMemory( g_dentdata, g_entdatasize ); + g_numentities = 0; + + while( ParseEntity( )); +} + +/* +================ +FreeEntity + +release all the entity data +================ +*/ +void FreeEntity( entity_t *mapent ) +{ + epair_t *ep, *next; + + for( ep = mapent->epairs; ep != NULL; ep = next ) + { + next = ep->next; + freestring( ep->key ); + freestring( ep->value ); + Mem_Free( ep, C_EPAIR ); + } + + if( mapent->cache ) Mem_Free( mapent->cache ); + mapent->epairs = mapent->tail = NULL; + mapent->cache = NULL; +} + +/* +================ +FreeEntities + +release all the dynamically allocated data +================ +*/ +void FreeEntities( void ) +{ + for( int i = 0; i < g_numentities; i++ ) + { + FreeEntity( &g_entities[i] ); + } +} + +/* +================ +UnparseEntities + +Generates the dentdata string from all the entities +================ +*/ +void UnparseEntities( void ) +{ + char *buf, *end; + char line[2048]; + char key[1024], value[1024]; + epair_t *ep; + + buf = g_dentdata; + end = buf; + *end = 0; + + for( int i = 0; i < g_numentities; i++ ) + { + entity_t *ent = &g_entities[i]; + if( !ent->epairs ) continue; // ent got removed + + Q_strcat( end, "{\n" ); + end += 2; + + for( ep = ent->epairs; ep; ep = ep->next ) + { + Q_strncpy( key, ep->key, sizeof( key )); + StripTrailing( key ); + Q_strncpy( value, ep->value, sizeof( value )); + StripTrailing( value ); + + Q_snprintf( line, sizeof( line ), "\"%s\" \"%s\"\n", key, value ); + Q_strcat( end, line ); + end += Q_strlen( line ); + } + + Q_strcat( end, "}\n" ); + end += 2; + + if( end > ( buf + MAX_MAP_ENTSTRING )) + COM_FatalError( "Entity text too long\n" ); + } + + g_entdatasize = end - buf + 1; // write term +} + +void RemoveKey( entity_t *ent, const char *key ) +{ + epair_t *ep; + + for( ep = ent->epairs; ep; ep = ep->next ) + { + if( !Q_strcmp( ep->key, key )) + { + freestring( ep->key ); + freestring( ep->value ); + UnlinkEpair( ep, ent ); + Mem_Free( ep, C_EPAIR ); + return; + } + } +} + +void RenameKey( entity_t *ent, const char *key, const char *name ) +{ + epair_t *ep; + + for( ep = ent->epairs; ep; ep = ep->next ) + { + if( !Q_strcmp( ep->key, key )) + { + freestring( ep->key ); + ep->key = copystring( name ); + return; + } + } + // otherwise we using SetKeyValue as well +} + +void SetKeyValue( entity_t *ent, const char *key, const char *value ) +{ + epair_t *ep; + + for( ep = ent->epairs; ep; ep = ep->next ) + { + if( !Q_strcmp( ep->key, key )) + { + freestring( ep->value ); + ep->value = copystring( value ); + return; + } + } + + ep = (epair_t *)Mem_Alloc( sizeof( epair_t ), C_EPAIR ); + ep->key = copystring( key ); + ep->value = copystring( value ); + InsertLinkBefore( ep, ent ); +} + +char *ValueForKey( entity_t *ent, const char *key, bool check ) +{ + epair_t *ep; + + for( ep = ent->epairs; ep; ep = ep->next ) + { + if( !Q_strcmp( ep->key, key )) + return ep->value; + } + + if( check ) + return NULL; + return ""; +} + +vec_t FloatForKey( entity_t *ent, const char *key ) +{ + return atof( ValueForKey( ent, key )); +} + +int IntForKey( entity_t *ent, const char *key ) +{ + return atoi( ValueForKey( ent, key )); +} + +bool BoolForKey( entity_t *ent, const char *key ) +{ + if( atoi( ValueForKey( ent, key ))) + return true; + return false; +} + +int GetVectorForKey( entity_t *ent, const char *key, vec3_t vec ) +{ + double v1, v2, v3; + int count; + char *k; + + k = ValueForKey( ent, key ); + + // scanf into doubles, then assign, so it is vec_t size independent + v1 = v2 = v3 = 0; + + count = sscanf( k, "%lf %lf %lf", &v1, &v2, &v3 ); + + vec[0] = v1; + vec[1] = v2; + vec[2] = v3; + + return count; +} + +void SetVectorForKey( entity_t *ent, const char *key, vec3_t vec, bool precise ) +{ + char string[64]; + + if( precise ) Q_snprintf( string, sizeof( string ), "%.2f %.2f %.2f", vec[0], vec[1], vec[2] ); + else Q_snprintf( string, sizeof( string ), "%i %i %i", (int)vec[0], (int)vec[1], (int)vec[2] ); + + SetKeyValue( ent, key, string ); +} + +/* +================ +EntityForModel + +returns entity addy for given modelnum +================ +*/ +entity_t *EntityForModel( const int modnum ) +{ + char name[16]; + + Q_snprintf( name, sizeof( name ), "*%i", modnum ); + + // search the entities for one using modnum + for( int i = 0; i < g_numentities; i++ ) + { + const char *s = ValueForKey( &g_entities[i], "model" ); + if( !Q_strcmp( s, name )) return &g_entities[i]; + } + + return &g_entities[0]; +} + +// ===================================================================================== +// ModelForEntity +// returns model addy for given entity +// ===================================================================================== +dmodel_t *ModelForEntity( entity_t *ent ) +{ + const char *s = ValueForKey( ent, "model" ); + + if( *s != '*' ) return NULL; // non-bmodel + int modnum = atoi( s + 1 ); + + if( modnum < 1 || modnum >= g_nummodels ) + return NULL; + + return &g_dmodels[modnum]; +} + +/* +================== +FindTargetEntity +================== +*/ +entity_t *FindTargetEntity( const char *target ) +{ + for( int i = 0; i < g_numentities; i++ ) + { + char *n = ValueForKey( &g_entities[i], "targetname" ); + if( !Q_strcmp( n, target )) + return &g_entities[i]; + } + return NULL; +} + +/* +=============================================================================== + + ENTITY LINKING + +=============================================================================== +*/ +/* +=============== +ClearLink + +ClearLink is used for new headnodes +=============== +*/ +void ClearLink( link_t *l ) +{ + l->prev = l->next = l; +} + +/* +=============== +RemoveLink + +remove link from chain +=============== +*/ +void RemoveLink( link_t *l ) +{ + l->next->prev = l->prev; + l->prev->next = l->next; +} + +/* +=============== +InsertLinkBefore + +kept trigger and solid entities seperate +=============== +*/ +void InsertLinkBefore( link_t *l, link_t *before ) +{ + l->next = before; + l->prev = before->prev; + l->prev->next = l; + l->next->prev = l; +} + +/* +=============== +CreateAreaNode + +builds a uniformly subdivided tree for the given world size +=============== +*/ +areanode_t *CreateAreaNode( aabb_tree_t *tree, int depth, int maxdepth, vec3_t mins, vec3_t maxs ) +{ + vec3_t mins1, maxs1; + vec3_t mins2, maxs2; + areanode_t *anode; + vec3_t size; + + anode = &tree->areanodes[tree->numareanodes]; + tree->numareanodes++; + + ClearLink( &anode->solid_edicts ); + + if( depth == Q_min( maxdepth, AREA_MAX_DEPTH )) + { + anode->axis = -1; + anode->children[0] = anode->children[1] = NULL; + return anode; + } + + VectorSubtract( maxs, mins, size ); + if( size[0] > size[1] ) + anode->axis = 0; + else anode->axis = 1; + + anode->dist = 0.5f * ( maxs[anode->axis] + mins[anode->axis] ); + VectorCopy( mins, mins1 ); + VectorCopy( mins, mins2 ); + VectorCopy( maxs, maxs1 ); + VectorCopy( maxs, maxs2 ); + + maxs1[anode->axis] = mins2[anode->axis] = anode->dist; + anode->children[0] = CreateAreaNode( tree, depth+1, maxdepth, mins2, maxs2 ); + anode->children[1] = CreateAreaNode( tree, depth+1, maxdepth, mins1, maxs1 ); + + return anode; +} \ No newline at end of file diff --git a/utils/common/bspfile.h b/utils/common/bspfile.h new file mode 100644 index 0000000..c74aea9 --- /dev/null +++ b/utils/common/bspfile.h @@ -0,0 +1,322 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// comiple-time settings +#define BSPVERSION HLBSP_VERSION +#define COMPILERSTRING "^1P2ST ^3v0.98^7" +#define TOOLNAME "^1P2:Savior^7 tools" +#define VERSIONSTRING "^3v.0.98^7" + +// compile-time configuration like ZHLT and VHLT +#define HLCSG_SKYFIXEDSTYLE // P2:Savior used to differentiate sky light from other light sources +#define HLVIS_SORT_PORTALS // sort portals before flow +#define HLVIS_MERGE_PORTALS // merge visibility between portals +#define HLRAD_COMPRESS_TRANSFERS // saves 80% memory +#define HLRAD_RIGHTROUND // when you go down, when you go down-down! +#define HLRAD_TestLine_EDGE_FIX // remove shadow artifacts in some cases + +// P2:Savior +#ifdef HLRAD_PSAVIOR +#define HLRAD_REFLECTIVITY +#define HLRAD_DELUXEMAPPING // store deluxe vectors for bump-mapping +//#define HLRAD_SHADOWMAPPING // store shadow intensity for bump-mapping +#define HLRAD_VERTEXLIGHTING // vertex lighting for static models +#define HLRAD_LIGHTMAPMODELS // calculate lightmaps on the models +#define HLRAD_AMBIENTCUBES // leaf ambient lighting +#define HLRAD_COMPUTE_VISLIGHTMATRIX // XashNT experiments +#define HLRAD_EXTERNAL_TEXTURES // allow to loading external textures +#define HLRAD_SHRINK_MEMORY // TESTTEST +#define HLRAD_RAYTRACE // TESTTEST +#endif + +// Paranoia compatible +#ifdef HLRAD_PARANOIA +#define HLRAD_PARANOIA_BUMP // compute Paranoia-style bump (three lightstyles) +#define HLRAD_DELUXEMAPPING // store deluxe vectors for bump-mapping +#define HLRAD_VERTEXLIGHTING // vertex lighting for static models +#endif + +// GoldSrc compatible +#ifdef HLRAD_GOLDSRC +#define HLRAD_REFLECTIVITY +#define HLRAD_VERTEXLIGHTING // vertex lighting for static models +#define HLRAD_AMBIENTCUBES // leaf ambient lighting +#endif + +// epsilon values (critical stuff) +#define NORMAL_EPSILON 1e-5 +#define DIR_EPSILON 1e-4 +#define DIST_EPSILON 4e-2 +#define EQUAL_EPSILON 4e-3 +#define ON_EPSILON 4e-2 +#define POINT_EPSILON (ON_EPSILON * 0.5) +#define CSGCHOP_EPSILON ON_EPSILON +#define PRTCHOP_EPSILON ON_EPSILON +#define MICROVOLUME 0.1 +#define T_EPSILON ON_EPSILON +#define HULL_VISIBLE 0 + +#define IsLiquidContents( cnt ) ( cnt == CONTENTS_WATER || cnt == CONTENTS_SLIME || cnt == CONTENTS_LAVA ) +#define CHECKVISBIT( vis, b ) ((b) >= 0 ? (byte)((vis)[(b) >> 3] & (1 << ((b) & 7))) : (byte)false ) +#define SETVISBIT( vis, b )( void ) ((b) >= 0 ? (byte)((vis)[(b) >> 3] |= (1 << ((b) & 7))) : (byte)false ) +#define CLEARVISBIT( vis, b )( void ) ((b) >= 0 ? (byte)((vis)[(b) >> 3] &= ~(1 << ((b) & 7))) : (byte)false ) + +/* +============================================================================== + +BRUSH MODELS + +.bsp contain level static geometry with including PVS and lightning info +============================================================================== +*/ +#include "..\..\common\bspfile.h" + +// header +#define Q1BSP_VERSION 29 // quake1 regular version (beta is 28) +#define HLBSP_VERSION 30 // half-life regular version + +#define IDEXTRAHEADER (('H'<<24)+('S'<<16)+('A'<<8)+'X') // little-endian "XASH" +#define EXTRA_VERSION 4 // ver. 1 was occupied by old versions of XashXT, ver. 2 was occupied by old vesrions of P2:savior + // ver. 3 was occupied by experimental versions of P2:savior change fmt +#define EXTRA_VERSION_OLD 2 // extra version 2 (P2:Savior regular version) to get minimal backward compatibility + +// upper design bounds +#define MAX_MAP_HULLS 4 + +#define MAXPOINTS 28 // don't let a base face get past this + // because it can be split more later + +#define MAX_TEXFILES 128 // max connected wad-files +#define MAX_MAP_BRUSHES 68000 // even ad_sepulcher never reach this value +#define MAX_MAP_ENTSTRING 0x200000 // 2 mB should be enough +#define MAX_INTERNAL_MAP_PLANES 524288 // internal compiler limit +#define MAX_INTERNAL_MAP_TEXINFO 262144 // internal compiler limit +#define MAX_MAP_NORMS 32768 // sphere with step as two degrees (180*180=32400) +#define MAX_MAP_VERTNORMS 0x200000 // can be increased but not needed +#define MAX_MAP_PORTALS 65536 + +// lightmap resolution +#define TEXTURE_STEP 16 // if lightmap extent exceeds 16, the map will not be able to load in 'Software Mode' +#define WORLD_TEXTURE_STEP 1 +#define TEXTURE_EXTRA_STEP 8 +#define MAX_SURFACE_EXTENT 16 // renderer and HLDS. //--vluzacn +#define MAX_EXTRA_EXTENTS 64 + +#define MIN_CUSTOM_SURFACE_EXTENT 8 +#define MAX_CUSTOM_SURFACE_EXTENT 127 // don't increase this because GoldSrc default lightmap page resolution is 128x128 +#define MAX_MODEL_SURFACE_EXTENT 511 +#define MIN_CUSTOM_TEXTURE_STEP 2 +#define MAX_CUSTOM_TEXTURE_STEP 64 +#define MIN_STUDIO_TEXTURE_STEP 1 +#define MAX_STUDIO_TEXTURE_STEP 256 + +// edges limit per face (software renderer reqiured that) +#define MAX_VERTS_ON_FACE MAXPOINTS + +// temporary contents (not stored into BSP) +#define CONTENTS_ORIGIN -7 // removed at csg time +#define CONTENTS_VISBLOCKER -8 // removed at bsp time + +//============================================================================ +// the utilities get to be lazy and just use large static arrays + +extern int g_nummodels; +extern dmodel_t g_dmodels[MAX_MAP_MODELS]; + +extern int g_visdatasize; +extern byte g_dvisdata[MAX_MAP_VISIBILITY]; + +extern int g_lightdatasize; +extern byte *g_dlightdata; + +extern int g_deluxdatasize; +extern byte *g_ddeluxdata; + +extern int g_shadowdatasize; +extern byte *g_dshadowdata; + +extern int g_texdatasize; +extern byte *g_dtexdata; // (dmiptexlump_t) + +extern int g_vislightdatasize; +extern byte *g_dvislightdata; + +extern int g_entdatasize; +extern char g_dentdata[MAX_MAP_ENTSTRING]; + +extern int g_numleafs; +extern int g_numvisleafs; +extern dleaf_t g_dleafs[MAX_MAP_LEAFS]; + +extern int g_numplanes; +extern dplane_t g_dplanes[MAX_INTERNAL_MAP_PLANES]; + +extern int g_numvertexes; +extern dvertex_t g_dvertexes[MAX_MAP_VERTS]; + +extern int g_numnodes; +extern dnode_t g_dnodes[MAX_MAP_NODES]; + +extern int g_numtexinfo; +extern dtexinfo_t g_texinfo[MAX_INTERNAL_MAP_TEXINFO]; + +extern int g_numfaces; +extern dface_t g_dfaces[MAX_MAP_FACES]; + +extern int g_numclipnodes; +extern dclipnode_t g_dclipnodes[MAX_MAP_CLIPNODES]; +extern dclipnode32_t g_dclipnodes32[MAX_MAP_CLIPNODES32]; + +extern int g_numedges; +extern dedge_t g_dedges[MAX_MAP_EDGES]; + +extern int g_nummarksurfaces; +extern dmarkface_t g_dmarksurfaces[MAX_MAP_MARKSURFACES]; + +extern int g_numsurfedges; +extern dsurfedge_t g_dsurfedges[MAX_MAP_SURFEDGES]; + +extern int g_numfaceinfo; +extern dfaceinfo_t g_dfaceinfo[MAX_MAP_FACEINFO]; + +extern int g_numcubemaps; +extern dcubemap_t g_dcubemaps[MAX_MAP_CUBEMAPS]; + +extern int g_numworldlights; +extern dworldlight_t g_dworldlights[MAX_MAP_WORLDLIGHTS]; + +extern int g_numleaflights; +extern dleafsample_t g_dleaflights[MAX_MAP_LEAFLIGHTS]; + +extern int g_vlightdatasize; +extern byte *g_dvlightdata; // (dvlightlump_t) + +extern int g_flightdatasize; +extern byte *g_dflightdata; // (dflightlump_t) + +extern int g_normaldatasize; +extern byte *g_dnormaldata; + +extern vec3_t g_hull_size[MAX_MAP_HULLS][2]; +extern bool g_found_extradata; +extern char g_wadpath[1024]; + +void DecompressVis( byte *in, byte *decompressed ); +int CompressVis( const byte *src, const uint src_length, byte *dest, uint dest_length ); + +void LoadBSPFile( const char *filename ); +void WriteBSPFile( const char *filename ); +void PrintBSPFileSizes( void ); + +const char *ContentsToString( int type ); +int GetSurfaceExtent( const dface_t *f ); +int GetTextureStep( const dface_t *f, bool force = false ); +int GetSurfaceExtent( const dtexinfo_t *tex ); +int GetTextureStep( const dtexinfo_t *tex, bool force = false ); +word GetSurfaceGroupId( const dtexinfo_t *tex ); +word GetSurfaceGroupId( const dface_t *f ); +const char *GetTextureByTexinfo( int texinfo ); +void LightMatrixFromTexMatrix( const dtexinfo_t *tx, float lmvecs[2][4] ); +int MapPlaneTypeForNormal( const vec3_t normal ); +int GetFaceContents( const char *name ); +void MakeAxial( float normal[3] ); +int SnapNormal( vec3_t normal ); + +//=============== +#define STRUCT_FROM_LINK( l, t, m ) ((t *)((byte *)l - (int)&(((t *)0)->m))) +#define ENTITY_FROM_AREA( l ) STRUCT_FROM_LINK( l, entity_t, area ) +#define TFACE_FROM_AREA( l ) STRUCT_FROM_LINK( l, tface_t, area ) + +#define AREA_NODES BIT( AREA_MAX_DEPTH + 1 ) +#define AREA_MAX_DEPTH 10 // maximum depth +#define AREA_MIN_DEPTH 4 // default depth + +typedef enum +{ + mod_unknown = 0, + mod_brush, + mod_studio, + mod_alias, +} modtype_t; + +typedef struct epair_s +{ + struct epair_s *next; + struct epair_s *prev; // used to save fields in the right ordering + char *key; + char *value; +} epair_t; + +typedef struct link_s +{ + struct link_s *prev; + struct link_s *next; +} link_t; + +typedef struct areanode_s +{ + int axis; // -1 = leaf node + float dist; + struct areanode_s *children[2]; + link_t solid_edicts; +} areanode_t; + +typedef struct +{ + areanode_t areanodes[AREA_NODES]; + int numareanodes; +} aabb_tree_t; + +typedef struct entity_s +{ +// this part shared with mapentity_t + epair_t *epairs; // head + epair_t *tail; // tail + vec3_t origin; + vec3_t absmin; + vec3_t absmax; + +// this part is unique for entity_t + // rad trace + link_t area; // linked to a division node or leaf + modtype_t modtype; + void *cache; // rad usage +} entity_t; + +extern int g_numentities; +extern entity_t g_entities[MAX_MAP_ENTITIES]; + +void ParseEntities( void ); +void UnparseEntities( void ); +void FreeEntities( void ); +void FreeEntity( entity_t *mapent ); +void CheckHullFile( const char *filename ); +void CopyEpairs( entity_t *dst, entity_t *src ); +void InsertLinkBefore( epair_t *e, entity_t *mapent ); +void RenameKey( entity_t *ent, const char *key, const char *name ); +void SetKeyValue( entity_t *ent, const char *key, const char *value ); +char *ValueForKey( entity_t *ent, const char *key, bool check = false ); // will return "" if not present +#define CheckKey( ent, key ) ValueForKey( ent, key, true ) +void RemoveKey( entity_t *ent, const char *key ); +bool BoolForKey( entity_t *ent, const char *key ); +int IntForKey( entity_t *ent, const char *key ); +vec_t FloatForKey( entity_t *ent, const char *key ); +int GetVectorForKey( entity_t *ent, const char *key, vec3_t vec ); +void SetVectorForKey( entity_t *ent, const char *key, vec3_t vec, bool precise = false ); +entity_t *FindTargetEntity( const char *target ); +entity_t *EntityForModel( const int modnum ); +dmodel_t *ModelForEntity( entity_t *ent ); +epair_t *ParseEpair( void ); + +void ClearLink( link_t *l ); +void RemoveLink( link_t *l ); +void InsertLinkBefore( link_t *l, link_t *before ); +areanode_t *CreateAreaNode( aabb_tree_t *tree, int depth, int maxdepth, vec3_t mins, vec3_t maxs ); +void StripTrailing( char *e ); diff --git a/utils/common/cmdlib.cpp b/utils/common/cmdlib.cpp new file mode 100644 index 0000000..59b2253 --- /dev/null +++ b/utils/common/cmdlib.cpp @@ -0,0 +1,227 @@ +/* +=========================================================================== +Copyright (C) 1997-2006 Id Software, Inc. + +This file is part of Quake 2 Tools source code. + +Quake 2 Tools source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake 2 Tools source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake 2 Tools source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// cmdlib.c +#include +#include +#include +#include +#include +#include "stringlib.h" +#include "cmdlib.h" + +static char **com_argv; +static int com_argc = 0; + +/* +============ +COM_InitCmdlib + +must been initialized first +============ +*/ +void COM_InitCmdlib( char **argv, int argc ) +{ + com_argv = argv; + com_argc = argc; +} + +/* +================ +COM_CheckParm + +Returns the position (1 to argc-1) in the program's argument list +where the given parameter apears, or 0 if not present +================ +*/ +int COM_CheckParm( const char *parm ) +{ + for( int i = 1; i < com_argc; i++ ) + { + if( !com_argv[i] ) continue; + if( !Q_stricmp( parm, com_argv[i] )) + return i; + } + + return 0; +} + +/* +================ +COM_GetParmExt + +Returns the argument for specified parm +================ +*/ +bool COM_GetParmExt( char *parm, char *out, size_t size ) +{ + int argc = COM_CheckParm( parm ); + + if( !argc || !out || !com_argv[argc + 1] ) + return false; + + Q_strncpy( out, com_argv[argc+1], size ); + + return true; +} + +/* +============ +COM_GetLastParm + +get last parm from commandline +============ +*/ +bool COM_GetLastParmExt( char *out, size_t size ) +{ + // NOTE: all the tools with single input file + // have similar format of cmdline syntax like this: + // toolname.exe -arg -parm 1 -arg files/folder/myfile.ext + // so path to filename is always last in args list even + // if some other args contain errors we can grab it + + if( com_argc <= 1 || size < 0 ) return false; + + Q_strncpy( out, com_argv[com_argc - 1], size ); + return true; +} + +/* +============ +COM_Error + +Stop the app with error +============ +*/ +void COM_FatalError( const char *error, ... ) +{ + char message[8192]; + va_list argptr; + + va_start( argptr, error ); + _vsnprintf( message, sizeof( message ), error, argptr ); + va_end( argptr ); + + Msg( "^1Fatal Error:^7 %s", message ); + exit( 1 ); +} + +/* +============ +COM_Assert + +assertation +============ +*/ +void COM_Assert( const char *error, ... ) +{ + char message[8192]; + va_list argptr; + + va_start( argptr, error ); + _vsnprintf( message, sizeof( message ), error, argptr ); + va_end( argptr ); + + Msg( "^1assert failed at:^7 %s", message ); + exit( 1 ); +} + +void Q_getwd( char *out, size_t size ) +{ + _getcwd( out, size ); + Q_strncat( out, "\\", size ); +} + +/* +============ +COM_Error + +Legacy from old cmdlib +============ +*/ +char *COM_ExpandArg( const char *path ) +{ + static char full[1024]; + + if( path[0] != '/' && path[0] != '\\' && path[1] != ':' ) + { + Q_getwd( full, sizeof( full )); + Q_strncat( full, path, sizeof( full )); + } + else Q_strncpy( full, path, sizeof( full )); + + return full; +} + +/* +============ +COM_FixSlashes + +Changes all '/' characters into '\' characters, in place. +============ +*/ +void COM_FixSlashes( char *pname ) +{ + while( *pname ) + { + if( *pname == '\\' ) + *pname = '/'; + pname++; + } +} + +/* +============= +COM_CheckString + +============= +*/ +int COM_CheckString( const char *string ) +{ + if( !string || (byte)*string <= ' ' ) + return 0; + return 1; +} + +/* +================ +I_FloatTime + +g-cont. the prefix 'I' was come from Doom code heh +================ +*/ +double I_FloatTime( void ) +{ + static LARGE_INTEGER g_Frequency; + static LARGE_INTEGER g_ClockStart; + LARGE_INTEGER CurrentTime; + + if( !g_Frequency.QuadPart ) + { + QueryPerformanceFrequency( &g_Frequency ); + QueryPerformanceCounter( &g_ClockStart ); + } + + QueryPerformanceCounter( &CurrentTime ); + + return (double)( CurrentTime.QuadPart - g_ClockStart.QuadPart ) / (double)( g_Frequency.QuadPart ); +} \ No newline at end of file diff --git a/utils/common/cmdlib.h b/utils/common/cmdlib.h new file mode 100644 index 0000000..9b05174 --- /dev/null +++ b/utils/common/cmdlib.h @@ -0,0 +1,111 @@ +/* +cmdlib.h - system functions, backend +Copyright (C) 2016 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +// cmdlib.h + +#ifndef CMDLIB_H +#define CMDLIB_H + +#include +#include "conprint.h" + +// bit routines +#define BIT( n ) (1<<( n )) +#define SetBits( iBitVector, bits ) ((iBitVector) = (iBitVector) | (bits)) +#define ClearBits( iBitVector, bits ) ((iBitVector) = (iBitVector) & ~(bits)) +#define FBitSet( iBitVector, bit ) ((iBitVector) & (bit)) + +#define bound( min, num, max ) ((num) >= (min) ? ((num) < (max) ? (num) : (max)) : (min)) +#define ASSERT( exp ) if(!( exp )) COM_Assert( "%s:%i\n", __FILE__, __LINE__ ) + +void COM_Assert( const char *error, ... ); +void COM_FatalError( const char *error, ... ); +void COM_InitCmdlib( char **argv, int argc ); +bool COM_GetLastParmExt( char *out, size_t size ); +#define COM_GetLastParm( a ) COM_GetLastParmExt( a, sizeof( a )) +int COM_CheckParm( const char *parm ); +bool COM_GetParmExt( char *parm, char *out, size_t size ); +#define COM_GetParm( a, b ) COM_GetParmExt( a, b, sizeof( b )) +int COM_CheckString( const char *string ); +void Q_getwd( char *out, size_t size ); +char *COM_ExpandArg( const char *path ); // from scripts +void COM_FixSlashes( char *pname ); +double I_FloatTime( void ); + +// normal file +typedef struct file_s file_t; + +long SafeOpenWrite( const char *filename ); +long SafeOpenRead( const char *filename ); +void SafeReadExt( long handle, void *buffer, int count, const char *file, const int line ); +void SafeWriteExt( long handle, void *buffer, int count, const char *file, const int line ); + +#define SafeRead( file, buffer, count ) SafeReadExt( file, buffer, count, __FILE__, __LINE__ ) +#define SafeWrite( file, buffer, count ) SafeWriteExt( file, buffer, count, __FILE__, __LINE__ ) + +#define IMAGE_EXISTS( path ) ( FS_FileExists( va( "%s.tga", path ), false ) || FS_FileExists( va( "%s.dds", path ), false )) + +// +// zone.cpp +// +enum +{ + C_COMMON = 0, + C_TEMPORARY, + C_SAFEALLOC, + C_FILESYSTEM, + C_WINDING, + C_BRUSHSIDE, + C_BSPBRUSH, + C_LEAFNODE, + C_SURFACE, + C_BSPTREE, + C_PORTAL, + C_STRING, + C_EPAIR, + C_PATCH, + C_MAXSTAT, +}; + +void *Mem_Alloc( size_t sizeInBytes, unsigned int target = C_COMMON ); +void *Mem_Realloc( void *ptr, size_t sizeInBytes, unsigned int target = C_COMMON ); +void Mem_Free( void *ptr, unsigned int target = C_COMMON ); +size_t Mem_Size( void *ptr ); +void Mem_Check( void ); +void Mem_Peak( void ); + +// +// basefs.c +// +void FS_Init( const char *source ); +byte *FS_LoadFile( const char *path, size_t *filesizeptr, bool gamedironly ); +bool FS_FileExists( const char *filename, bool gamedironly ); +void FS_Shutdown( void ); +file_t *FS_Open( const char *filepath, const char *mode, bool gamedironly ); +long FS_Read( file_t *file, void *buffer, size_t buffersize ); +int FS_Gets( file_t *file, byte *string, size_t bufsize ); +void FS_AllowDirectPaths( bool enable ); +long FS_FileLength( file_t *f ); +int FS_Close( file_t *file ); + +// +// crclib.c +// +void CRC32_Init( dword *pulCRC ); +void CRC32_Final( dword *pulCRC ); +void CRC32_ProcessByte( dword *pulCRC, byte ch ); +void CRC32_ProcessBuffer( dword *pulCRC, const void *pBuffer, int nBuffer ); + +#endif \ No newline at end of file diff --git a/utils/common/conprint.cpp b/utils/common/conprint.cpp new file mode 100644 index 0000000..752a15a --- /dev/null +++ b/utils/common/conprint.cpp @@ -0,0 +1,231 @@ +/* +conprint.cpp - extended printf function that allows +colored printing scheme from Quake3 +Copyright (C) 2012 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include +#include +#include +#include "stringlib.h" +#include "conprint.h" + +#define IsColorString( p ) ( p && *( p ) == '^' && *(( p ) + 1) && *(( p ) + 1) >= '0' && *(( p ) + 1 ) <= '9' ) +#define ColorIndex( c ) ((( c ) - '0' ) & 7 ) + +static unsigned short g_color_table[8] = +{ +FOREGROUND_INTENSITY, // black +FOREGROUND_RED|FOREGROUND_INTENSITY, // red +FOREGROUND_GREEN|FOREGROUND_INTENSITY, // green +FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_INTENSITY, // yellow +FOREGROUND_BLUE|FOREGROUND_INTENSITY, // blue +FOREGROUND_GREEN|FOREGROUND_BLUE|FOREGROUND_INTENSITY, // cyan +FOREGROUND_RED|FOREGROUND_BLUE|FOREGROUND_INTENSITY, // magenta +FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE, // default color (white) +}; + +static int devloper_level = DEFAULT_DEVELOPER; +static bool ignore_log = false; +static FILE *logfile = NULL; + +void SetDeveloperLevel( int level ) +{ + if( level < D_INFO ) return; // debug messages disabled + if( level > D_NOTE ) level = D_NOTE; + devloper_level = level; +} + +int GetDeveloperLevel( void ) +{ + return devloper_level; +} + +/* +=============================================================================== + +SYSTEM LOG + +=============================================================================== +*/ +void Sys_InitLog( const char *logname ) +{ + logfile = fopen( logname, "w" ); + if( !logfile ) MsgDev( D_ERROR, "Sys_InitLog: can't create log file %s\n", logname ); +} + +void Sys_InitLogAppend( const char *logname ) +{ + logfile = fopen( logname, "a+" ); + if( !logfile ) MsgDev( D_ERROR, "Sys_InitLog: can't create log file %s\n", logname ); +} + +void Sys_IgnoreLog( bool ignore ) +{ + ignore_log = ignore; +} + +void Sys_CloseLog( void ) +{ + if( !logfile ) return; + fclose( logfile ); + logfile = NULL; +} + +void Sys_PrintLog( const char *pMsg ) +{ + if( !pMsg || ignore_log ) + return; + + if( !logfile ) return; + fprintf( logfile, "%s", pMsg ); + fflush( logfile ); +} + +/* +================ +Sys_Print + +print into win32 console +================ +*/ +void Sys_Print( const char *pMsg ) +{ + char tmpBuf[8192]; + HANDLE hOut = GetStdHandle( STD_OUTPUT_HANDLE ); + unsigned long cbWritten; + char *pTemp = tmpBuf; + + while( pMsg && *pMsg ) + { + if( IsColorString( pMsg )) + { + if(( pTemp - tmpBuf ) > 0 ) + { + // dump accumulated text before change color + *pTemp = 0; // terminate string + WriteFile( hOut, tmpBuf, strlen( tmpBuf ), &cbWritten, 0 ); + Sys_PrintLog( tmpBuf ); + pTemp = tmpBuf; + } + + // set new color + SetConsoleTextAttribute( hOut, g_color_table[ColorIndex( *(pMsg + 1))] ); + pMsg += 2; // skip color info + } + else if(( pTemp - tmpBuf ) < sizeof( tmpBuf ) - 1 ) + { + *pTemp++ = *pMsg++; + } + else + { + // temp buffer is full, dump it now + *pTemp = 0; // terminate string + WriteFile( hOut, tmpBuf, strlen( tmpBuf ), &cbWritten, 0 ); + Sys_PrintLog( tmpBuf ); + pTemp = tmpBuf; + } + } + + // check for last portion + if(( pTemp - tmpBuf ) > 0 ) + { + // dump accumulated text + *pTemp = 0; // terminate string + WriteFile( hOut, tmpBuf, strlen( tmpBuf ), &cbWritten, 0 ); + Sys_PrintLog( tmpBuf ); + pTemp = tmpBuf; + } +} + +/* +================ +Msg + +formatted message +================ +*/ +void Msg( const char *pMsg, ... ) +{ + va_list argptr; + char text[8192]; + + va_start( argptr, pMsg ); + Q_vsnprintf( text, sizeof( text ), pMsg, argptr ); + va_end( argptr ); + + Sys_Print( text ); +} + +/* +================ +MsgDev + +formatted developer message +================ +*/ +void MsgDev( int level, const char *pMsg, ... ) +{ + va_list argptr; + char text[8192]; + + if( devloper_level < level ) return; + + va_start( argptr, pMsg ); + Q_vsnprintf( text, sizeof( text ), pMsg, argptr ); + va_end( argptr ); + + switch( level ) + { + case D_WARN: + Sys_Print( va( "^3Warning:^7 %s", text )); + break; + case D_ERROR: + Sys_Print( va( "^1Error:^7 %s", text )); + break; + case D_INFO: + case D_NOTE: + case D_REPORT: + Sys_Print( text ); + break; + } +} + +void MsgAnim( int level, const char *pMsg, ... ) +{ + va_list argptr; + char text[1024]; + char empty[1024]; + + if( devloper_level < level ) return; + + va_start( argptr, pMsg ); + Q_vsnprintf( text, sizeof( text ), pMsg, argptr ); + va_end( argptr ); + + // fill clear string + for( int j = 0; j < Q_strlen( text ); j++ ) + empty[j] = ' '; + empty[j] = '\r'; + empty[j+1] = '\0'; + + // do animation + for( int i = 0; i < 8; i++ ) + { + Sys_IgnoreLog( i < 7 ); + if( i & 1 ) Sys_Print( text ); + else Sys_Print( empty ); + Sleep( 150 ); + } + Msg( "^7\n" ); +} \ No newline at end of file diff --git a/utils/common/conprint.h b/utils/common/conprint.h new file mode 100644 index 0000000..62649ef --- /dev/null +++ b/utils/common/conprint.h @@ -0,0 +1,33 @@ +/* +conprint.h - extended printf function that allows +colored printing scheme from Quake3 in Win32 console +Copyright (C) 2012 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef CONPRINT_H +#define CONPRINT_H + +#define DEFAULT_DEVELOPER D_ERROR + +void Msg( const char *pMsg, ... ); +void MsgDev( int level, const char *pMsg, ... ); +void MsgAnim( int level, const char *pMsg, ... ); +void SetDeveloperLevel( int level ); +int GetDeveloperLevel( void ); +void Sys_InitLog( const char *logname ); +void Sys_InitLogAppend( const char *logname ); +void Sys_CloseLog( void ); +void Sys_PrintLog( const char *pMsg ); +void Sys_IgnoreLog( bool ignore ); + +#endif//CONRPINT_H \ No newline at end of file diff --git a/utils/common/crc32.cpp b/utils/common/crc32.cpp new file mode 100644 index 0000000..c4f9ab9 --- /dev/null +++ b/utils/common/crc32.cpp @@ -0,0 +1,168 @@ +/* +crclib.c - generate crc stuff +Copyright (C) 2007 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "cmdlib.h" + +#define NUM_BYTES 256 +#define CRC32_INIT_VALUE 0xFFFFFFFFUL +#define CRC32_XOR_VALUE 0xFFFFFFFFUL + +static const dword crc32table[NUM_BYTES] = +{ +0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, +0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, +0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, +0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, +0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, +0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, +0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, +0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, +0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, +0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, +0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, +0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, +0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, +0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, +0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, +0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, +0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, +0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, +0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, +0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, +0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, +0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, +0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, +0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, +0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, +0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, +0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, +0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, +0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, +0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, +0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, +0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, +0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, +0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, +0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, +0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, +0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, +0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, +0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, +0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, +0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, +0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, +0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, +0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, +0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, +0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, +0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, +0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, +0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, +0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, +0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, +0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, +0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, +0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, +0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, +0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, +0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, +0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, +0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, +0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, +0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, +0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, +0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, +0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d +}; + +void CRC32_Init( dword *pulCRC ) +{ + *pulCRC = CRC32_INIT_VALUE; +} + +void CRC32_Final( dword *pulCRC ) +{ + *pulCRC ^= CRC32_XOR_VALUE; +} + +void CRC32_ProcessByte( dword *pulCRC, byte ch ) +{ + dword ulCrc = *pulCRC; + + ulCrc ^= ch; + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + *pulCRC = ulCrc; +} + +void CRC32_ProcessBuffer( dword *pulCRC, const void *pBuffer, int nBuffer ) +{ + dword ulCrc = *pulCRC; + byte *pb = (byte *)pBuffer; + uint nFront; + int nMain; +JustAfew: + switch( nBuffer ) + { + case 7: ulCrc = crc32table[*pb++ ^ (byte)ulCrc] ^ (ulCrc >> 8); + case 6: ulCrc = crc32table[*pb++ ^ (byte)ulCrc] ^ (ulCrc >> 8); + case 5: ulCrc = crc32table[*pb++ ^ (byte)ulCrc] ^ (ulCrc >> 8); + case 4: + ulCrc ^= *(dword *)pb; // warning, this only works on little-endian. + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + *pulCRC = ulCrc; + return; + case 3: ulCrc = crc32table[*pb++ ^ (byte)ulCrc] ^ (ulCrc >> 8); + case 2: ulCrc = crc32table[*pb++ ^ (byte)ulCrc] ^ (ulCrc >> 8); + case 1: ulCrc = crc32table[*pb++ ^ (byte)ulCrc] ^ (ulCrc >> 8); + case 0: *pulCRC = ulCrc; + return; + } + + // We may need to do some alignment work up front, and at the end, so that + // the main loop is aligned and only has to worry about 8 byte at a time. + // The low-order two bits of pb and nBuffer in total control the + // upfront work. + nFront = ((uint)pb) & 3; + nBuffer -= nFront; + + switch( nFront ) + { + case 3: ulCrc = crc32table[*pb++ ^ (byte)ulCrc] ^ (ulCrc >> 8); + case 2: ulCrc = crc32table[*pb++ ^ (byte)ulCrc] ^ (ulCrc >> 8); + case 1: ulCrc = crc32table[*pb++ ^ (byte)ulCrc] ^ (ulCrc >> 8); + } + + nMain = nBuffer >> 3; + while( nMain-- ) + { + ulCrc ^= *(dword *)pb; // warning, this only works on little-endian. + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + ulCrc ^= *(dword *)(pb + 4);// warning, this only works on little-endian. + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + ulCrc = crc32table[(byte)ulCrc] ^ (ulCrc >> 8); + pb += 8; + } + + nBuffer &= 7; + goto JustAfew; +} \ No newline at end of file diff --git a/utils/common/ddstex.cpp b/utils/common/ddstex.cpp new file mode 100644 index 0000000..7b7f04e --- /dev/null +++ b/utils/common/ddstex.cpp @@ -0,0 +1,895 @@ +/* +image_dds.cpp - image dds encoder. Based on original code from Doom3: BFG Edition +Copyright (C) 2016 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#define STB_DXT_IMPLEMENTATION + +#include "conprint.h" +#include +#include +#include +#include +#include "cmdlib.h" +#include "stringlib.h" +#include "imagelib.h" +#include "stringlib.h" +#include "filesystem.h" +#include "ddstex.h" +#include "squish.h" +#include "mathlib.h" + +#define BLOCK_SIZE ( 4 * 4 ) // DXT block size quad 4x4 pixels +#define RGB_TO_YCOCG_Y( r, g, b ) ((( r + (g<<1) + b ) + 2 ) >> 2 ) +#define RGB_TO_YCOCG_CO( r, g, b ) ((( (r<<1)-(b<<1) ) + 2 ) >> 2 ) +#define RGB_TO_YCOCG_CG( r, g, b ) ((( -r + (g<<1) - b ) + 2 ) >> 2 ) + +typedef enum +{ + PF_DXT1 = 0, // without alpha + PF_DXT3, // DXT3 + PF_DXT5, // DXT5 as default + PF_DXT5_ALPHA, // DXT5 with alpha + PF_DXT5_SDF_ALPHA, // DXT5 with SDF alpha + PF_DXT5_YCoCg, + PF_DXT5_NORM_BASE, // same as original + PF_ATI2_NORM_PARABOLOID, +} saveformat_t; + +/* +======================== +ExtractBlock + +params: inPtr - input image, 4 bytes per pixel +paramO: colorBlock - 4*4 output tile, 4 bytes per pixel +======================== +*/ +inline static void ExtractBlock( const byte *inPtr, int width, byte *colorBlock ) +{ + for( int i = 0; i < 4; i++ ) + { + memcpy( &colorBlock[i*BLOCK_SIZE], inPtr, BLOCK_SIZE ); + inPtr += width * 4; + } +} + +static size_t Image_DXTGetBlockSize( int format ) +{ + if( format == PF_DXT1 ) + return 8; + return 16; +} + +/* +======================== +ScaleYCoCg + +params: colorBlock - 16 pixel block for which find color indexes +======================== +*/ +inline static void ScaleYCoCg( byte *colorBlock ) +{ + for( int i = 0; i < 16; i++ ) + { + int r = colorBlock[i*4+0]; + int g = colorBlock[i*4+1]; + int b = colorBlock[i*4+2]; + int a = colorBlock[i*4+3]; + + const int Co = r - b; + const int t = b + Co / 2; + const int Cg = g - t; + const int Y = t + Cg / 2; + + // Just saturate the chroma here (we loose out of one bit in each channel) + // this just means that we won't have as high dynamic range. Perhaps a better option + // is to loose the least significant bit instead? + colorBlock[i*4+0] = bound( 0, Co + 128, 255 ); + colorBlock[i*4+1] = bound( 0, Cg + 128, 255 ); + colorBlock[i*4+2] = 0; // trying to use alpha ? + colorBlock[i*4+3] = Y; + } +} + +/* +======================== +NormalizeBlock + +params: colorBlock - 16 pixel block for which find color indexes +======================== +*/ +inline static void NormalizeBlock( byte *colorBlock, int format ) +{ + float pX, pY, a, t; + float discriminant; + vec3_t normal; + + for( int i = 0; i < 16; i++ ) + { + int x = colorBlock[i*4+0]; + int y = colorBlock[i*4+1]; + int z = colorBlock[i*4+2]; + + normal[0] = (x * (1.0f/127.0f) - 1.0f); + normal[1] = (y * (1.0f/127.0f) - 1.0f); + normal[2] = (z * (1.0f/127.0f) - 1.0f); + + if( VectorNormalize( normal ) == 0.0f ) + VectorSet( normal, 0.5f, 0.5f, 1.0f ); + + switch( format ) + { + case PF_ATI2_NORM_PARABOLOID: + pX = normal[0]; + pY = normal[1]; + a = (pX * pX) + (pY * pY); + discriminant = normal[2] * normal[2] - 4.0f * a * -1.0f; + t = ( -normal[2] + sqrtf( discriminant )) / ( 2.0f * a ); + pX *= t; + pY *= t; + // store normal as two channels (RG) + colorBlock[i*4+0] = 0; + colorBlock[i*4+1] = (byte)(128 + 127 * pY); + colorBlock[i*4+2] = 0; + colorBlock[i*4+3] = (byte)(128 + 127 * pX); + break; + default: + // store normal as normal image (XYZ) + colorBlock[i*4+0] = (byte)(128 + 127 * normal[0]); + colorBlock[i*4+1] = (byte)(128 + 127 * normal[1]); + colorBlock[i*4+2] = (byte)(128 + 127 * normal[2]); + colorBlock[i*4+3] = 0xFF; + break; + } + } +} + +/* +======================== +CompressYCoCgDXT5HQ + +params: inBuf - image to compress +paramO: outBuf - result of compression +params: width - width of image +params: height - height of image +======================== +*/ +inline static void CompressRGBABufferToDXT( const byte *inBuf, vfile_t *f, int width, int height, int format, bool estimate ) +{ + ALIGN16 byte block[64]; + ALIGN16 byte outBlock[16]; + float metricsRGB[3] = { 0.2126f, 0.7152f, 0.0722f }; + const char *typeString = NULL; + float *metrics = NULL; + double start, end; + int flags = 0; + char str[64]; + + start = I_FloatTime(); + + for( int j = 0; j < height; j += 4, inBuf += width * BLOCK_SIZE ) + { + for( int i = 0; i < width; i += 4 ) + { + ExtractBlock( inBuf + i * 4, width, block ); + + if( format == PF_DXT5_YCoCg ) + { + SetBits( flags, squish::kDxt5 | squish::kColourIterativeClusterFit ); + typeString = "DXT5 YCoCg"; + ScaleYCoCg( block ); + } + else if( format >= PF_DXT5_NORM_BASE && format <= PF_ATI2_NORM_PARABOLOID ) + { + switch( format ) + { + case PF_DXT5_NORM_BASE: + typeString = "DXT5 NormXYZ Base"; + SetBits( flags, squish::kDxt5 | squish::kColourIterativeClusterFit ); + break; + case PF_ATI2_NORM_PARABOLOID: + typeString = "ATI2 NormAG Paraboloid"; + SetBits( flags, squish::kAti2 ); + break; + } + + NormalizeBlock( block, format ); + } + else if( format == PF_DXT5 ) + { + SetBits( flags, squish::kDxt5 | squish::kColourIterativeClusterFit ); + typeString = "DXT5 RGB"; + } + else if( format == PF_DXT5_ALPHA || format == PF_DXT5_SDF_ALPHA ) + { + SetBits( flags, squish::kDxt5 | squish::kColourIterativeClusterFit | squish::kWeightColourByAlpha ); + typeString = "DXT5 RGBA"; + } + else if( format == PF_DXT1 ) + { + SetBits( flags, squish::kDxt1 | squish::kColourIterativeClusterFit ); + typeString = "DXT1 RGB"; + } + + squish::Compress( block, outBlock, flags, metrics ); + VFS_Write( f, outBlock, Image_DXTGetBlockSize( format )); + + if( estimate ) + { + Sys_IgnoreLog( true ); + Msg( "\rcompress %s: %2d%%", typeString, ( j * width + i ) * 100 / ( width * height ) ); + Sys_IgnoreLog( false ); + } + } + } + + end = I_FloatTime(); + Q_timestring((int)(end - start), str ); + + if( estimate ) + { + Msg( "\r" ); + Msg( "compress %s: 100%%. %s elapsed\n", typeString, str ); + } +} + +/* +======================================================================== + +.DDS image format + +======================================================================== +*/ +#define DDSHEADER ((' '<<24)+('S'<<16)+('D'<<8)+'D') // little-endian "DDS " + +// various four-cc types +#define TYPE_DXT1 (('1'<<24)+('T'<<16)+('X'<<8)+'D') // little-endian "DXT1" +#define TYPE_DXT3 (('3'<<24)+('T'<<16)+('X'<<8)+'D') // little-endian "DXT3" +#define TYPE_DXT5 (('5'<<24)+('T'<<16)+('X'<<8)+'D') // little-endian "DXT5" +#define TYPE_ATI2 (('2'<<24)+('I'<<16)+('T'<<8)+'A') // little-endian "ATI2" + +// dwFlags1 +#define DDS_CAPS 0x00000001L +#define DDS_HEIGHT 0x00000002L +#define DDS_WIDTH 0x00000004L +#define DDS_PITCH 0x00000008L +#define DDS_PIXELFORMAT 0x00001000L +#define DDS_MIPMAPCOUNT 0x00020000L +#define DDS_LINEARSIZE 0x00080000L +#define DDS_DEPTH 0x00800000L + +// dwFlags2 +#define DDS_ALPHAPIXELS 0x00000001L +#define DDS_ALPHA 0x00000002L +#define DDS_FOURCC 0x00000004L +#define DDS_RGB 0x00000040L +#define DDS_RGBA 0x00000041L // (DDS_RGB|DDS_ALPHAPIXELS) +#define DDS_LUMINANCE 0x00020000L +#define DDS_DUDV 0x00080000L + +// dwCaps1 +#define DDS_COMPLEX 0x00000008L +#define DDS_TEXTURE 0x00001000L +#define DDS_MIPMAP 0x00400000L + +// dwCaps2 +#define DDS_CUBEMAP 0x00000200L +#define DDS_CUBEMAP_POSITIVEX 0x00000400L +#define DDS_CUBEMAP_NEGATIVEX 0x00000800L +#define DDS_CUBEMAP_POSITIVEY 0x00001000L +#define DDS_CUBEMAP_NEGATIVEY 0x00002000L +#define DDS_CUBEMAP_POSITIVEZ 0x00004000L +#define DDS_CUBEMAP_NEGATIVEZ 0x00008000L +#define DDS_CUBEMAP_ALL_SIDES 0x0000FC00L +#define DDS_VOLUME 0x00200000L + +typedef struct dds_pf_s +{ + uint dwSize; + uint dwFlags; + uint dwFourCC; + uint dwRGBBitCount; + uint dwRBitMask; + uint dwGBitMask; + uint dwBBitMask; + uint dwABitMask; +} dds_pixf_t; + +// DDCAPS2 +typedef struct dds_caps_s +{ + uint dwCaps1; + uint dwCaps2; + uint dwCaps3; // currently unused + uint dwCaps4; // currently unused +} dds_caps_t; + +typedef struct dds_s +{ + uint dwIdent; // must matched with DDSHEADER + uint dwSize; + uint dwFlags; // determines what fields are valid + uint dwHeight; + uint dwWidth; + uint dwLinearSize; // Formless late-allocated optimized surface size + uint dwDepth; // depth if a volume texture + uint dwMipMapCount; // number of mip-map levels requested + uint dwAlphaBitDepth; // depth of alpha buffer requested + uint dwReserved1[10]; // reserved for future expansions + dds_pixf_t dsPixelFormat; + dds_caps_t dsCaps; + uint dwTextureStage; +} dds_t; + +static bool Image_DXTGetPixelFormat( dds_t *hdr, uint &flags, uint &sqFlags ) +{ + uint bits = hdr->dsPixelFormat.dwRGBBitCount; + + if( FBitSet( hdr->dsCaps.dwCaps2, DDS_VOLUME )) + return false; // 3D textures doesn't support + + if( FBitSet( hdr->dsPixelFormat.dwFlags, DDS_FOURCC )) + { + switch( hdr->dsPixelFormat.dwFourCC ) + { + case TYPE_DXT1: + SetBits( flags, IMAGE_DXT_FORMAT ); + SetBits( sqFlags, squish::kDxt1 ); + break; + case TYPE_DXT3: + SetBits( flags, IMAGE_DXT_FORMAT ); + SetBits( sqFlags, squish::kDxt3 ); + break; + case TYPE_DXT5: + SetBits( flags, IMAGE_DXT_FORMAT ); + SetBits( sqFlags, squish::kDxt5 ); + break; + default: return false; + } + } + else + { + // this dds texture isn't compressed so write out ARGB or luminance format + if( FBitSet( hdr->dsPixelFormat.dwFlags, DDS_DUDV )) + { + return false; + } + else if( FBitSet( hdr->dsPixelFormat.dwFlags, DDS_LUMINANCE )) + { + return false; + } + else + { + if( bits == 32 ) + SetBits( flags, IMAGE_HAS_8BIT_ALPHA ); + else if( bits != 24 ) + return false; + } + // no deals with other DXT types anyway + } + + SetBits( flags, IMAGE_HAS_COLOR ); // predict state + + if( FBitSet( hdr->dsPixelFormat.dwFlags, DDS_ALPHA )) + SetBits( flags, IMAGE_HAS_8BIT_ALPHA ); + + if( FBitSet( hdr->dsPixelFormat.dwFlags, DDS_ALPHAPIXELS )) + SetBits( flags, IMAGE_HAS_8BIT_ALPHA ); + + if( !FBitSet( hdr->dwFlags, DDS_MIPMAPCOUNT )) + hdr->dwMipMapCount = 1; + + // setup additional flags + if( FBitSet( hdr->dsCaps.dwCaps1, DDS_COMPLEX ) && FBitSet( hdr->dsCaps.dwCaps2, DDS_CUBEMAP )) + { + if( hdr->dwMipMapCount <= 1 ) + SetBits( flags, IMAGE_SKYBOX ); + else SetBits( flags, IMAGE_CUBEMAP ); + } + + return true; +} + +static size_t Image_DXTGetLinearSize( int block, int width, int height ) +{ + return ((width + 3) / 4) * ((height + 3) / 4) * block; +} + +static size_t Image_DXTGetBlockSize( dds_t *hdr ) +{ + if( FBitSet( hdr->dsPixelFormat.dwFlags, DDS_FOURCC )) + { + switch( hdr->dsPixelFormat.dwFourCC ) + { + case TYPE_DXT1: return 8; + case TYPE_DXT3: return 16; + case TYPE_DXT5: return 16; + case TYPE_ATI2: return 16; + default: return 0; + } + } + + // no deals with other DXT types anyway + return 0; +} + +static size_t Image_DXTCalcMipmapSize( dds_t *hdr ) +{ + size_t buffsize = 0; + int width, height; + + // now correct buffer size + for( int i = 0; i < hdr->dwMipMapCount; i++ ) + { + width = Q_max( 1, ( hdr->dwWidth >> i )); + height = Q_max( 1, ( hdr->dwHeight >> i )); + buffsize += Image_DXTGetLinearSize( Image_DXTGetBlockSize( hdr ), width, height ); + } + + return buffsize; +} + +static size_t Image_DXTCalcSize( const char *name, dds_t *hdr, size_t filesize ) +{ + size_t buffsize = Image_DXTCalcMipmapSize( hdr ); + + if( FBitSet( hdr->dsCaps.dwCaps2, DDS_CUBEMAP )) + buffsize *= 6; + + if( filesize != buffsize ) // main check + { + MsgDev( D_WARN, "LoadDDS: (%s) probably corrupted (%i should be %i)\n", name, buffsize, filesize ); + if( filesize < buffsize ) + return false; + } + return buffsize; +} + +static int Image_DXTCalcMipmapCount( rgbdata_t *pix ) +{ + int width, height; + + if( FBitSet( pix->flags, IMAGE_SKYBOX|IMAGE_NOMIPS )) + return 1; + + // mip-maps can't exceeds 16 + for( int mipcount = 0; mipcount < 16; mipcount++ ) + { + width = Q_max( 1, ( pix->width >> mipcount )); + height = Q_max( 1, ( pix->height >> mipcount )); + if( width == 1 && height == 1 ) + break; + } + + MsgDev( D_REPORT, "image[ %i x %i ] has %i mips\n", pix->width, pix->height, mipcount + 1 ); + + return mipcount + 1; +} + +static bool Image_CheckDXT3Alpha( dds_t *hdr, byte *fin ) +{ + word sAlpha; + byte *alpha; + int x, y, i, j; + + for( y = 0; y < hdr->dwHeight; y += 4 ) + { + for( x = 0; x < hdr->dwWidth; x += 4 ) + { + alpha = fin + 8; + fin += 16; + + for( j = 0; j < 4; j++ ) + { + sAlpha = alpha[2*j+0] + 256 * alpha[2*j+1]; + + for( i = 0; i < 4; i++ ) + { + if((( x + i ) < hdr->dwWidth ) && (( y + j ) < hdr->dwHeight )) + { + if( sAlpha == 0 ) + return true; + } + sAlpha >>= 4; + } + } + } + } + + return false; +} + +static bool Image_CheckDXT5Alpha( dds_t *hdr, byte *fin ) +{ + uint bits, bitmask; + byte *alphamask; + int x, y, i, j; + + for( y = 0; y < hdr->dwHeight; y += 4 ) + { + for( x = 0; x < hdr->dwWidth; x += 4 ) + { + if( y >= hdr->dwHeight || x >= hdr->dwWidth ) + break; + + alphamask = fin + 2; + fin += 8; + + bitmask = ((uint *)fin)[1]; + fin += 8; + + // last three bytes + bits = (alphamask[3]) | (alphamask[4] << 8) | (alphamask[5] << 16); + + for( j = 2; j < 4; j++ ) + { + for( i = 0; i < 4; i++ ) + { + // only put pixels out < width or height + if((( x + i ) < hdr->dwWidth ) && (( y + j ) < hdr->dwHeight )) + { + if( bits & 0x07 ) + return true; + } + bits >>= 3; + } + } + } + } + + return false; +} + +/* +============= +DDSToBuffer +============= +*/ +rgbdata_t *DDSToBuffer( const char *name, const byte *buffer, size_t filesize ) +{ + uint flags = 0; + uint dummy = 0; + dds_t header; + rgbdata_t *pic; + + if( filesize < sizeof( dds_t )) + { + MsgDev( D_ERROR, "Image_LoadDDS: file (%s) have invalid size\n", name ); + return NULL; + } + + memcpy( &header, buffer, sizeof( dds_t )); + + if( header.dwIdent != DDSHEADER ) + return NULL; // it's not a dds file, just skip it + + if( header.dwSize != sizeof( dds_t ) - sizeof( uint )) // size of the structure (minus MagicNum) + { + MsgDev( D_ERROR, "LoadDDS: (%s) have corrupted header\n", name ); + return NULL; + } + + if( header.dsPixelFormat.dwSize != sizeof( dds_pixf_t )) // size of the structure + { + MsgDev( D_ERROR, "LoadDDS: (%s) have corrupt pixelformat header\n", name ); + return NULL; + } + + if( !Image_ValidSize( name, header.dwWidth, header.dwHeight )) + return NULL; + + if( !Image_DXTGetPixelFormat( &header, flags, dummy )) + { + MsgDev( D_ERROR, "LoadDDS: (%s) unsupported DXT format (only DXT1, DXT3 and DXT5 is supported)\n", name ); + return NULL; + } + + if( !Image_DXTCalcSize( name, &header, filesize - sizeof( dds_t ))) + return NULL; + + // now all the checks are passed, store image as rgbdata + pic = (rgbdata_t *)Mem_Alloc( sizeof( rgbdata_t ) + filesize ); + pic->buffer = ((byte *)pic) + sizeof( rgbdata_t ); + memcpy( pic->buffer, buffer, filesize ); + + pic->width = header.dwWidth; + pic->height = header.dwHeight; + pic->size = filesize; + pic->flags = flags; + + return pic; +} + +static bool Image_DXTWriteHeader( vfile_t *f, rgbdata_t *pix, int format, int numMipMaps ) +{ + uint dwFourCC = 0, dwFlags1 = 0, dwFlags2 = 0, dwCaps1 = 0, dwCaps2 = 0; + uint dwLinearSize, dwSize = 124, dwSize2 = sizeof( dds_pixf_t ); + uint dwWidth, dwHeight, dwMipCount, dwCustomEncode = DXT_ENCODE_DEFAULT; + dword dwReflectivity = 0; + uint dwIdent = DDSHEADER; + + if( !pix || !pix->buffer ) + return false; + + // setup flags + SetBits( dwFlags1, DDS_LINEARSIZE|DDS_WIDTH|DDS_HEIGHT|DDS_CAPS|DDS_PIXELFORMAT|DDS_PITCH ); + SetBits( dwFlags2, DDS_FOURCC ); + dwMipCount = numMipMaps; + if( dwMipCount > 1 ) SetBits( dwFlags1, DDS_MIPMAPCOUNT ); + + switch( format ) + { + case PF_DXT1: + dwFourCC = TYPE_DXT1; + break; + case PF_DXT3: + dwFourCC = TYPE_DXT3; + break; + case PF_DXT5_YCoCg: + dwCustomEncode = DXT_ENCODE_COLOR_YCoCg; + dwFourCC = TYPE_DXT5; + break; + case PF_ATI2_NORM_PARABOLOID: + dwCustomEncode = DXT_ENCODE_NORMAL_AG_PARABOLOID; + dwFourCC = TYPE_ATI2; + break; + case PF_DXT5: + case PF_DXT5_ALPHA: + case PF_DXT5_NORM_BASE: + dwFourCC = TYPE_DXT5; + break; + case PF_DXT5_SDF_ALPHA: + dwCustomEncode = DXT_ENCODE_ALPHA_SDF; + dwFourCC = TYPE_DXT5; + break; + default: + MsgDev( D_ERROR, "Image_DXTWriteHeader: unknown format\n" ); + return false; + } + + Image_PackRGB( pix->reflectivity, dwReflectivity ); + + dwWidth = pix->width; + dwHeight = pix->height; + + VFS_Write( f, &dwIdent, sizeof( uint )); + VFS_Write( f, &dwSize, sizeof( uint )); + VFS_Write( f, &dwFlags1, sizeof( uint )); + VFS_Write( f, &dwHeight, sizeof( uint )); + VFS_Write( f, &dwWidth, sizeof( uint )); + + dwLinearSize = Image_DXTGetLinearSize( Image_DXTGetBlockSize( format ), pix->width, pix->height ); + VFS_Write( f, &dwLinearSize, sizeof( uint )); + + VFS_Write( f, 0, sizeof( uint )); + VFS_Write( f, &dwMipCount, sizeof( uint )); + VFS_Write( f, NULL, sizeof( uint )); + VFS_Write( f, &dwCustomEncode, sizeof( uint )); // was dwReserved[0] + VFS_Write( f, &dwReflectivity, sizeof( dword )); // was dwReserved[1] + VFS_Write( f, NULL, sizeof( uint ) * 8 ); // reserved fields + + VFS_Write( f, &dwSize2, sizeof( uint )); + VFS_Write( f, &dwFlags2, sizeof( uint )); + VFS_Write( f, &dwFourCC, sizeof( uint )); + VFS_Write( f, NULL, sizeof( uint )); + VFS_Write( f, NULL, sizeof( uint )); + VFS_Write( f, NULL, sizeof( uint )); + VFS_Write( f, NULL, sizeof( uint )); + VFS_Write( f, NULL, sizeof( uint )); + + dwCaps1 |= DDS_TEXTURE; + + if( dwMipCount > 1 ) + SetBits( dwCaps1, DDS_MIPMAP|DDS_COMPLEX ); + + if( pix->flags & ( IMAGE_CUBEMAP|IMAGE_SKYBOX )) + { + SetBits( dwCaps1, DDS_COMPLEX ); + SetBits( dwCaps2, DDS_CUBEMAP|DDS_CUBEMAP_ALL_SIDES ); + } + + VFS_Write( f, &dwCaps1, sizeof( uint )); + VFS_Write( f, &dwCaps2, sizeof( uint )); + VFS_Write( f, NULL, sizeof( uint ) * 3 ); // other caps and TextureStage + + return true; +} + +static size_t CompressDXT( vfile_t *f, const byte *buffer, int width, int height, int format, bool estimate ) +{ + size_t dst_size; + size_t cur_size; + size_t curpos; + + if( !buffer ) return 0; + + dst_size = Image_DXTGetLinearSize( Image_DXTGetBlockSize( format ), width, height ); + curpos = VFS_Tell( f ); + + CompressRGBABufferToDXT( buffer, f, width, height, format, estimate ); + + cur_size = VFS_Tell( f ) - curpos; + + if( cur_size != dst_size ) + { + MsgDev( D_ERROR, "CompressDXT: corrupt mem (buffer is %s bytes, written %s)\n", Q_memprint( dst_size ), Q_memprint( cur_size )); + return false; + } + + return true; +} + +rgbdata_t *BufferToDDS( rgbdata_t *pix, int saveformat ) +{ + vfile_t *file; // virtual file + rgbdata_t *out = NULL; + rgbdata_t *mip = NULL; + bool normalMap = (saveformat >= PF_DXT5_NORM_BASE && saveformat <= PF_ATI2_NORM_PARABOLOID) ? true : false; + int width, height; + int nummips = 1; + int numSides = 1; + + // check for all the possible problems + if( !pix ) return NULL; + + if( FBitSet( pix->flags, IMAGE_DXT_FORMAT )) + return NULL; // already compressed + + if(( pix->width & 15 ) || ( pix->height & 15 )) + return NULL; // not aligned by 16 + + file = VFS_Create( NULL, 0 ); + if( !file ) return NULL; + + nummips = Image_DXTCalcMipmapCount( pix ); + + if( !Image_DXTWriteHeader( file, pix, saveformat, nummips )) + { + MsgDev( D_ERROR, "BufferToDDS: unsupported format\n" ); + VFS_Close( file ); + return NULL; + } + + mip = Image_Copy( pix ); + + if( FBitSet( pix->flags, IMAGE_CUBEMAP|IMAGE_SKYBOX )) + numSides = 6; + + for( int i = 0; i < numSides; i++ ) + { + byte *buffer = mip->buffer + ((pix->width * pix->height * 4) * i); + + width = pix->width; + height = pix->height; + + for( int j = 0; j < nummips; j++ ) + { + if( j ) Image_BuildMipMap( buffer, width, height, normalMap ); + + width = Q_max( 1, ( pix->width >> j )); + height = Q_max( 1, ( pix->height >> j )); + + if( !CompressDXT( file, buffer, width, height, saveformat, ( j == 0 ) )) + { + MsgDev( D_ERROR, "BufferToDDS: can't compress image\n" ); + VFS_Close( file ); + Mem_Free( mip ); + return NULL; + } + } + } + + Mem_Free( mip ); + + // create a new pic + out = (rgbdata_t *)Mem_Alloc( sizeof( rgbdata_t ) + VFS_Tell( file )); + out->buffer = ((byte *)out) + sizeof( rgbdata_t ); + memcpy( out->buffer, VFS_GetBuffer( file ), VFS_Tell( file )); + + out->width = pix->width; + out->height = pix->height; + out->size = VFS_Tell( file ); + + SetBits( out->flags, IMAGE_HAS_COLOR ); + SetBits( out->flags, IMAGE_DXT_FORMAT ); + + if( FBitSet( pix->flags, IMAGE_CUBEMAP )) + SetBits( out->flags, IMAGE_CUBEMAP ); + + if( FBitSet( pix->flags, IMAGE_SKYBOX )) + SetBits( out->flags, IMAGE_SKYBOX ); + + // release virtual file + VFS_Close( file ); + + return out; +} + +rgbdata_t *DDSToRGBA( const char *name, const byte *buffer, size_t filesize ) +{ + uint flags = 0; + uint sqFlags = 0; + dds_t header; + rgbdata_t *pic; + byte *fin; + + if( filesize < sizeof( dds_t )) + { + MsgDev( D_ERROR, "Image_LoadDDS: file (%s) have invalid size\n", name ); + return NULL; + } + + memcpy( &header, buffer, sizeof( dds_t )); + + if( header.dwIdent != DDSHEADER ) + return NULL; // it's not a dds file, just skip it + + if( header.dwSize != sizeof( dds_t ) - sizeof( uint )) // size of the structure (minus MagicNum) + { + MsgDev( D_ERROR, "LoadDDS: (%s) have corrupted header\n", name ); + return NULL; + } + + if( header.dsPixelFormat.dwSize != sizeof( dds_pixf_t )) // size of the structure + { + MsgDev( D_ERROR, "LoadDDS: (%s) have corrupt pixelformat header\n", name ); + return NULL; + } + + if( !Image_ValidSize( name, header.dwWidth, header.dwHeight )) + return NULL; + + if( !Image_DXTGetPixelFormat( &header, flags, sqFlags )) + { + MsgDev( D_ERROR, "LoadDDS: (%s) unsupported DXT format (only DXT1, DXT3 and DXT5 is supported)\n", name ); + return NULL; + } + + if( FBitSet( flags, IMAGE_DXT_FORMAT )) + { + if( !Image_DXTCalcSize( name, &header, filesize - sizeof( dds_t ))) + return NULL; + } + + fin = (byte *)(buffer + sizeof( dds_t )); // pixels are immediately follows after header + + if( FBitSet( sqFlags, squish::kDxt3 ) && Image_CheckDXT3Alpha( &header, fin )) + SetBits( flags, IMAGE_HAS_8BIT_ALPHA ); + else if( FBitSet( sqFlags, squish::kDxt5 ) && Image_CheckDXT5Alpha( &header, fin )) + SetBits( flags, IMAGE_HAS_8BIT_ALPHA ); + + pic = Image_Alloc( header.dwWidth, header.dwHeight ); + + // now all the checks are passed, store image as rgbdata + if( FBitSet( flags, IMAGE_DXT_FORMAT )) + squish::DecompressImage( pic->buffer, header.dwWidth, header.dwHeight, fin, sqFlags ); + else memcpy( pic->buffer, fin, header.dwWidth * header.dwHeight * FBitSet( flags, IMAGE_HAS_ALPHA ) ? 4 : 3 ); + + pic->flags = flags; + + return pic; +} + +int DDS_GetSaveFormatForHint( char hint, rgbdata_t *pix ) +{ + if( hint == IMG_NORMALMAP ) + { + return PF_ATI2_NORM_PARABOLOID; + } + else + { + if( FBitSet( pix->flags, IMAGE_HAS_ALPHA )) + return PF_DXT5_ALPHA; + return PF_DXT1; + } +} \ No newline at end of file diff --git a/utils/common/ddstex.h b/utils/common/ddstex.h new file mode 100644 index 0000000..35a4fb0 --- /dev/null +++ b/utils/common/ddstex.h @@ -0,0 +1,24 @@ +/* +ddstex.h - image dds encoder. Use squish library +Copyright (C) 2016 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef DDSTEX_H +#define DDSTEX_H + +rgbdata_t *DDSToBuffer( const char *name, const byte *buffer, size_t filesize ); +rgbdata_t *DDSToRGBA( const char *name, const byte *buffer, size_t filesize ); +rgbdata_t *BufferToDDS( rgbdata_t *pix, int saveformat ); +int DDS_GetSaveFormatForHint( char hint, rgbdata_t *pix ); + +#endif//DDSTEX_H \ No newline at end of file diff --git a/utils/common/filesystem.cpp b/utils/common/filesystem.cpp new file mode 100644 index 0000000..07ca3cd --- /dev/null +++ b/utils/common/filesystem.cpp @@ -0,0 +1,621 @@ +/* +filesystem.cpp - simple version of game engine filesystem for tools +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include +#include +#include +#include +#include +#include "conprint.h" +#include "cmdlib.h" +#include "stringlib.h" +#include "filesystem.h" +#include + +/* +============================================================================= + +FILEMATCH COMMON SYSTEM + +============================================================================= +*/ +int matchpattern( const char *str, const char *cmp, bool caseinsensitive ) +{ + int c1, c2; + + while( *cmp ) + { + switch( *cmp ) + { + case 0: return 1; // end of pattern + case '?': // match any single character + if( *str == 0 || *str == '/' || *str == '\\' || *str == ':' ) + return 0; // no match + str++; + cmp++; + break; + case '*': // match anything until following string + if( !*str ) return 1; // match + cmp++; + while( *str ) + { + if( *str == '/' || *str == '\\' || *str == ':' ) + break; + // see if pattern matches at this offset + if( matchpattern( str, cmp, caseinsensitive )) + return 1; + // nope, advance to next offset + str++; + } + break; + default: + if( *str != *cmp ) + { + if( !caseinsensitive ) + return 0; // no match + c1 = Q_tolower( *str ); + c2 = Q_tolower( *cmp ); + if( c1 != c2 ) return 0; // no match + } + + str++; + cmp++; + break; + } + } + + // reached end of pattern but not end of input? + return (*str) ? 0 : 1; +} + +void stringlistinit( stringlist_t *list ) +{ + memset( list, 0, sizeof( *list )); +} + +void stringlistfreecontents( stringlist_t *list ) +{ + int i; + + for( i = 0; i < list->numstrings; i++ ) + { + if( list->strings[i] ) + Mem_Free( list->strings[i], C_STRING ); + list->strings[i] = NULL; + } + + if( list->strings ) + Mem_Free( list->strings, C_STRING ); + + list->numstrings = 0; + list->maxstrings = 0; + list->strings = NULL; +} + +void stringlistappend( stringlist_t *list, char *text ) +{ + size_t textlen; + char **oldstrings; + + if( !Q_stricmp( text, "." ) || !Q_stricmp( text, ".." )) + return; // ignore the virtual directories + + if( list->numstrings >= list->maxstrings ) + { + oldstrings = list->strings; + list->maxstrings += 4096; + list->strings = (char **)Mem_Alloc( list->maxstrings * sizeof( *list->strings ), C_STRING ); + if( list->numstrings ) memcpy( list->strings, oldstrings, list->numstrings * sizeof( *list->strings )); + if( oldstrings ) Mem_Free( oldstrings, C_STRING ); + } + + textlen = Q_strlen( text ) + 1; + list->strings[list->numstrings] = (char *)Mem_Alloc( textlen, C_STRING ); + memcpy( list->strings[list->numstrings], text, textlen ); + list->numstrings++; +} + +void stringlistsort( stringlist_t *list ) +{ + char *temp; + int i, j; + + // this is a selection sort (finds the best entry for each slot) + for( i = 0; i < list->numstrings - 1; i++ ) + { + for( j = i + 1; j < list->numstrings; j++ ) + { + if( Q_strcmp( list->strings[i], list->strings[j] ) > 0 ) + { + temp = list->strings[i]; + list->strings[i] = list->strings[j]; + list->strings[j] = temp; + } + } + } +} + +void listdirectory( stringlist_t *list, const char *path, bool tolower ) +{ + char pattern[4096]; + struct _finddata_t n_file; + long hFile; + char *c; + int i; + + Q_strncpy( pattern, path, sizeof( pattern )); + Q_strncat( pattern, "*", sizeof( pattern )); + + // ask for the directory listing handle + hFile = _findfirst( pattern, &n_file ); + if( hFile == -1 ) return; + + // start a new chain with the the first name + stringlistappend( list, n_file.name ); + + // iterate through the directory + while( _findnext( hFile, &n_file ) == 0 ) + stringlistappend( list, n_file.name ); + _findclose( hFile ); + + if( !tolower ) return; + + // convert names to lowercase because windows doesn't care, but pattern matching code often does + for( i = 0; i < list->numstrings; i++ ) + { + for( c = list->strings[i]; *c; c++ ) + *c = Q_tolower( *c ); + } +} + +/* +============================================================================= + +FILESYSTEM PUBLIC BASE FUNCTIONS + +============================================================================= +*/ +/* +=========== +FS_Search + +Allocate and fill a search structure with information on matching filenames. +=========== +*/ +search_t *COM_Search( const char *pattern, int caseinsensitive, wfile_t *source_wad ) +{ + search_t *search = NULL; + int i, basepathlength, numfiles, numchars; + int resultlistindex, dirlistindex; + const char *slash, *backslash, *colon, *separator; + char netpath[1024], temp[1024], root[1204]; + stringlist_t resultlist, dirlist; + char *basepath; + + for( i = 0; pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\'; i++ ); + + if( i > 0 ) + { + MsgDev( D_ERROR, "COM_Search: don't use punctuation at the beginning of a search pattern!\n" ); + return NULL; + } + + if( !GetCurrentDirectory( sizeof( root ), root )) + { + MsgDev( D_ERROR, "couldn't determine current directory\n" ); + return NULL; + } + + Q_strncat( root, "\\", sizeof( root )); + stringlistinit( &resultlist ); + stringlistinit( &dirlist ); + slash = Q_strrchr( pattern, '/' ); + backslash = Q_strrchr( pattern, '\\' ); + colon = Q_strrchr( pattern, ':' ); + separator = max( slash, backslash ); + separator = max( separator, colon ); + basepathlength = separator ? (separator + 1 - pattern) : 0; + basepath = (char *)Mem_Alloc( basepathlength + 1 ); + if( basepathlength ) memcpy( basepath, pattern, basepathlength ); + basepath[basepathlength] = 0; + +#ifndef IGNORE_SEARCH_IN_WADS + W_SearchForFile( source_wad, pattern, &resultlist ); +#endif + // get a directory listing and look at each name + Q_sprintf( netpath, "%s%s", root, basepath ); + stringlistinit( &dirlist ); + listdirectory( &dirlist, netpath ); + + for( dirlistindex = 0; dirlistindex < dirlist.numstrings; dirlistindex++ ) + { + Q_sprintf( temp, "%s%s", basepath, dirlist.strings[dirlistindex] ); + + if( matchpattern( temp, (char *)pattern, true )) + { + for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) + { + if( !Q_strcmp( resultlist.strings[resultlistindex], temp )) + break; + } + + if( resultlistindex == resultlist.numstrings ) + stringlistappend( &resultlist, temp ); + } + } + + stringlistfreecontents( &dirlist ); + + if( resultlist.numstrings ) + { + stringlistsort( &resultlist ); + numfiles = resultlist.numstrings; + numchars = 0; + + for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) + numchars += (int)Q_strlen( resultlist.strings[resultlistindex]) + 1; + + search = (search_t *)Mem_Alloc( sizeof( search_t ) + numchars + numfiles * sizeof( char* )); + search->filenames = (char **)((char *)search + sizeof( search_t )); + search->filenamesbuffer = (char *)((char *)search + sizeof( search_t ) + numfiles * sizeof( char* )); + search->numfilenames = (int)numfiles; + numfiles = numchars = 0; + + for( resultlistindex = 0; resultlistindex < resultlist.numstrings; resultlistindex++ ) + { + search->filenames[numfiles] = search->filenamesbuffer + numchars; + size_t textlen = Q_strlen(resultlist.strings[resultlistindex]) + 1; + memcpy( search->filenames[numfiles], resultlist.strings[resultlistindex], textlen ); + numchars += (int)textlen; + numfiles++; + } + } + + stringlistfreecontents( &resultlist ); + Mem_Free( basepath ); + + return search; +} + +byte *COM_LoadFile( const char *filepath, size_t *filesize, bool safe ) +{ + long handle; + size_t size; + unsigned char *buf; + + handle = open( filepath, O_RDONLY|O_BINARY, 0666 ); + + if( filesize ) *filesize = 0; + if( handle < 0 ) + { + if( safe ) COM_FatalError( "Couldn't open %s\n", filepath ); + return NULL; + } + + size = lseek( handle, 0, SEEK_END ); + lseek( handle, 0, SEEK_SET ); + + buf = (unsigned char *)Mem_Alloc( size + 1, C_FILESYSTEM ); + buf[size] = '\0'; + read( handle, (unsigned char *)buf, size ); + close( handle ); + + if( filesize ) *filesize = size; + return buf; +} + +bool COM_SaveFile( const char *filepath, void *buffer, size_t filesize, bool safe ) +{ + long handle; + size_t size; + + if( buffer == NULL || filesize <= 0 ) + return false; + + COM_CreatePath( (char *)filepath ); + + handle = open( filepath, O_WRONLY|O_BINARY|O_CREAT|O_TRUNC, 0666 ); + if( handle < 0 ) + { + if( safe ) COM_FatalError( "Couldn't write %s\n", filepath ); + return false; + } + + size = write( handle, buffer, filesize ); + close( handle ); + + if( size < 0 ) + return false; + return true; +} + +/* +================== +COM_CreatePath +================== +*/ +void COM_CreatePath( char *path ) +{ + char *ofs, save; + + for( ofs = path + 1; *ofs; ofs++ ) + { + if( *ofs == '/' || *ofs == '\\' ) + { + // create the directory + save = *ofs; + *ofs = 0; + _mkdir( path ); + *ofs = save; + } + } +} + +/* +================== +COM_FileExists +================== +*/ +bool COM_FileExists( const char *path ) +{ + int desc; + + if(( desc = open( path, O_RDONLY|O_BINARY )) < 0 ) + return false; + + close( desc ); + return true; +} + +/* +============ +COM_FileWithoutPath +============ +*/ +const char *COM_FileWithoutPath( const char *in ) +{ + const char *separator, *backslash, *colon; + + separator = Q_strrchr( in, '/' ); + backslash = Q_strrchr( in, '\\' ); + + if( !separator || separator < backslash ) + separator = backslash; + + colon = Q_strrchr( in, ':' ); + + if( !separator || separator < colon ) + separator = colon; + + return separator ? separator + 1 : in; +} + +/* +============ +COM_ExtractFilePath +============ +*/ +void COM_ExtractFilePath( const char *path, char *dest ) +{ + const char *src = path + Q_strlen( path ) - 1; + + // back up until a \ or the start + while( src != path && !(*(src - 1) == '\\' || *(src - 1) == '/' )) + src--; + + if( src != path ) + { + memcpy( dest, path, src - path ); + dest[src - path - 1] = 0; // cutoff backslash + } + else Q_strcpy( dest, "" ); // file without path +} + +/* +============ +COM_FileExtension +============ +*/ +const char *COM_FileExtension( const char *in ) +{ + const char *separator, *backslash, *colon, *dot; + + separator = Q_strrchr( in, '/' ); + backslash = Q_strrchr( in, '\\' ); + + if( !separator || separator < backslash ) + separator = backslash; + + colon = Q_strrchr( in, ':' ); + + if( !separator || separator < colon ) + separator = colon; + + dot = Q_strrchr( in, '.' ); + + if( dot == NULL || ( separator && ( dot < separator ))) + return ""; + + return dot + 1; +} + +/* +================== +COM_DefaultExtension +================== +*/ +void COM_DefaultExtension( char *path, const char *extension ) +{ + const char *src; + + // if path doesn't have a .EXT, append extension + // (extension should include the .) + src = path + Q_strlen( path ) - 1; + + while( *src != '/' && src != path ) + { + // it has an extension + if( *src == '.' ) return; + src--; + } + Q_strcat( path, extension ); +} + +/* +============ +COM_StripExtension +============ +*/ +void COM_StripExtension( char *path ) +{ + size_t length; + + length = Q_strlen( path ) - 1; + + while( length > 0 && path[length] != '.' ) + { + length--; + + if( path[length] == '/' || path[length] == '\\' || path[length] == ':' ) + return; // no extension + } + + if( length ) path[length] = 0; +} + +/* +================== +COM_ReplaceExtension +================== +*/ +void COM_ReplaceExtension( char *path, const char *extension ) +{ + COM_StripExtension( path ); + COM_DefaultExtension( path, extension ); +} + +/* +============ +COM_FileBase + +Extracts the base name of a file (no path, no extension, assumes '/' as path separator) +============ +*/ +void COM_FileBase( const char *in, char *out ) +{ + int len, start, end; + + len = Q_strlen( in ); + if( !len ) return; + + // scan backward for '.' + end = len - 1; + + while( end && in[end] != '.' && in[end] != '/' && in[end] != '\\' ) + end--; + + if( in[end] != '.' ) + end = len-1; // no '.', copy to end + else end--; // found ',', copy to left of '.' + + // scan backward for '/' + start = len - 1; + + while( start >= 0 && in[start] != '/' && in[start] != '\\' ) + start--; + + if( start < 0 || ( in[start] != '/' && in[start] != '\\' )) + start = 0; + else start++; + + // length of new sting + len = end - start + 1; + + // Copy partial string + Q_strncpy( out, &in[start], len + 1 ); + out[len] = 0; +} + +/* +================== +COM_FolderExists +================== +*/ +bool COM_FolderExists( const char *path ) +{ + DWORD dwFlags = GetFileAttributes( path ); + + return ( dwFlags != -1 ) && FBitSet( dwFlags, FILE_ATTRIBUTE_DIRECTORY ); +} + +/* +==================== +COM_FileTime + +Internal function used to determine filetime +==================== +*/ +long COM_FileTime( const char *filename ) +{ + struct stat buf; + + if( stat( filename, &buf ) == -1 ) + return -1; + + return buf.st_mtime; +} + +/* +============================================================================= + + OBSOLETE FUNCTIONS + +============================================================================= +*/ +long SafeOpenWrite( const char *filename ) +{ + long handle; + + handle = open( filename, O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, 0666 ); + if( handle < 0 ) COM_FatalError( "couldn't open %s\n", filename ); + + return handle; +} + +long SafeOpenRead( const char *filename ) +{ + long handle; + + handle = open( filename, O_RDONLY|O_BINARY, 0666 ); + if( handle < 0 ) COM_FatalError( "couldn't open %s\n", filename ); + + return handle; +} + +void SafeReadExt( long handle, void *buffer, int count, const char *file, const int line ) +{ + size_t read_count = read( handle, buffer, count ); + + if( read_count != (size_t)count ) + COM_FatalError( "file read failure ( %i != %i ) at %s:%i\n", read_count, count, file, line ); +} + +void SafeWriteExt( long handle, void *buffer, int count, const char *file, const int line ) +{ + size_t write_count = write( handle, buffer, count ); + + if( write_count != (size_t)count ) + COM_FatalError( "file write failure ( %i != %i ) at %s:%i\n", write_count, count, file, line ); +} \ No newline at end of file diff --git a/utils/common/filesystem.h b/utils/common/filesystem.h new file mode 100644 index 0000000..ac8ff33 --- /dev/null +++ b/utils/common/filesystem.h @@ -0,0 +1,146 @@ +/* +filesystem.h - simple version of game engine filesystem for tools +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef FILESYSTEM_H +#define FILESYSTEM_H + +#include +#include + +#define FILE_BUFF_SIZE (65535) + +// replace lumps in a wad +#define REP_IGNORE 0 +#define REP_NORMAL 1 +#define REP_FORCE 2 + +// PAK errors +#define PAK_LOAD_OK 0 +#define PAK_LOAD_COULDNT_OPEN 1 +#define PAK_LOAD_BAD_HEADER 2 +#define PAK_LOAD_BAD_FOLDERS 3 +#define PAK_LOAD_TOO_MANY_FILES 4 +#define PAK_LOAD_NO_FILES 5 +#define PAK_LOAD_CORRUPTED 6 + +// WAD errors +#define WAD_LOAD_OK 0 +#define WAD_LOAD_COULDNT_OPEN 1 +#define WAD_LOAD_BAD_HEADER 2 +#define WAD_LOAD_BAD_FOLDERS 3 +#define WAD_LOAD_TOO_MANY_FILES 4 +#define WAD_LOAD_NO_FILES 5 +#define WAD_LOAD_CORRUPTED 6 + +typedef struct stringlist_s +{ + // maxstrings changes as needed, causing reallocation of strings[] array + int maxstrings; + int numstrings; + char **strings; +} stringlist_t; + +typedef struct +{ + int numfilenames; + char **filenames; + char *filenamesbuffer; +} search_t; + +typedef struct wadtype_s +{ + char *ext; + char type; +} wadtype_t; + +// wadfile +typedef struct wfile_s wfile_t; + +typedef struct vfile_s +{ + byte *buff; + size_t buffsize; + size_t length; + size_t offset; +} vfile_t; + +extern const wadtype_t wad_hints[]; + +search_t *COM_Search( const char *pattern, int caseinsensitive, wfile_t *source_wad = NULL ); +search_t *FS_Search( const char *pattern, int caseinsensitive, int gamedironly ); +byte *COM_LoadFile( const char *filepath, size_t *filesize, bool safe = true ); +bool COM_SaveFile( const char *filepath, void *buffer, size_t filesize, bool safe = true ); +long COM_FileTime( const char *filename ); +bool COM_FolderExists( const char *path ); +bool COM_FileExists( const char *path ); +void COM_CreatePath( char *path ); +void COM_FileBase( const char *in, char *out ); +const char *COM_FileExtension( const char *in ); +void COM_DefaultExtension( char *path, const char *extension ); +void COM_ExtractFilePath( const char *path, char *dest ); +void COM_ReplaceExtension( char *path, const char *extension ); +const char *COM_FileWithoutPath( const char *in ); +void COM_StripExtension( char *path ); + +// virtual filesystem +vfile_t *VFS_Create( const byte *buffer = NULL, size_t buffsize = 0 ); +long VFS_Read( vfile_t *file, void *buffer, size_t buffersize ); +long VFS_Write( vfile_t *file, const void *buf, size_t size ); +long VFS_Insert( vfile_t *file, const void *buf, size_t size ); +byte *VFS_GetBuffer( vfile_t *file ); +long VFS_GetSize( vfile_t *file ); +long VFS_Tell( vfile_t *file ); +bool VFS_Eof( vfile_t *file ); +int VFS_Print( vfile_t *file, const char *msg ); +int VFS_IPrint( vfile_t *file, const char *msg ); +int VFS_VPrintf( vfile_t *file, const char *format, va_list ap ); +int VFS_VIPrintf( vfile_t *file, const char *format, va_list ap ); +int VFS_Printf( vfile_t *file, const char *format, ... ); +int VFS_IPrintf( vfile_t *file, const char *format, ... ); +int VFS_Seek( vfile_t *file, long offset, int whence ); +int VFS_Getc( vfile_t *file ); +int VFS_Gets( vfile_t* file, byte *string, size_t bufsize ); +void VFS_Close( vfile_t *file ); + +// wadfile routines +wfile_t *W_Open( const char *filename, const char *mode, int *error = NULL, bool ext_path = true ); +byte *W_ReadLump( wfile_t *wad, dlumpinfo_t *lump, size_t *lumpsizeptr ); +byte *W_LoadLump( wfile_t *wad, const char *lumpname, size_t *lumpsizeptr, const char type ); +int W_SaveLump( wfile_t *wad, const char *lump, const void *data, size_t datasize, char type, char attribs = ATTR_NONE ); +void W_SearchForFile( wfile_t *wad, const char *pattern, stringlist_t *resultlist ); +dlumpinfo_t *W_FindLump( wfile_t *wad, const char *name, const char matchtype ); +dlumpinfo_t *W_FindMiptex( wfile_t *wad, const char *name ); +dlumpinfo_t *W_FindLmptex( wfile_t *wad, const char *name ); +char W_TypeFromExt( const char *lumpname ); +const char *W_ExtFromType( char lumptype ); +char W_HintFromSuf( const char *lumpname ); +int W_GetHandle( wfile_t *wad ); +void W_Close( wfile_t *wad ); + +// compare routines +int matchpattern( const char *str, const char *cmp, bool caseinsensitive ); + +// search routines +void stringlistinit( stringlist_t *list ); +void stringlistfreecontents( stringlist_t *list ); +void stringlistappend( stringlist_t *list, char *text ); +void stringlistsort( stringlist_t *list ); +void listdirectory( stringlist_t *list, const char *path, bool tolower = false ); + +// replace lumps protect +void SetReplaceLevel( int level ); +int GetReplaceLevel( void ); + +#endif//FILESYSTEM_H \ No newline at end of file diff --git a/utils/common/gamma.cpp b/utils/common/gamma.cpp new file mode 100644 index 0000000..c2cf24a --- /dev/null +++ b/utils/common/gamma.cpp @@ -0,0 +1,68 @@ +/* +conprint.cpp - extended printf function that allows +colored printing scheme from Quake3 +Copyright (C) 2012 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include +#include +#include +#include "stringlib.h" +#include "conprint.h" +#include +#include + +#define GAMMA ( 2.2f ) // Valve Software gamma +#define INVGAMMA ( 1.0f / 2.2f ) // back to 1.0 +#define TEXGAMMA ( 0.7f ) +#define TEXINTENSITY ( 1.2f ) + +static float texturetolinear[256]; // texture (0..255) to linear (0..1) +static int lineartotexture[1024]; // linear (0..1) to texture (0..255) +static byte s_gammatable[256]; +static byte s_intensitytable[256]; + +void BuildGammaTable( void ) +{ + int i, inf; + float g; + + g = TEXGAMMA; + + for( i = 0; i < 256; i++ ) + { + // convert from nonlinear texture space (0..255) to linear space (0..1) + texturetolinear[i] = pow( i / 255.0f, INVGAMMA ); + + if( g == 1.0f ) inf = i; + else inf = 255 * pow( i / 255.0f, 1.0f / g ) + 0.5f; + s_gammatable[i] = bound( 0, inf, 255 ); + + inf = i * TEXINTENSITY; + s_intensitytable[i] = bound( 0, inf, 255 ); + } + + for( i = 0; i < 1024; i++ ) + { + // convert from linear space (0..1) to nonlinear texture space (0..255) + lineartotexture[i] = pow( i / 1023.0, INVGAMMA ) * 255; + } +} + +// convert texture to linear 0..1 value +float TextureToLinear( int c ) { return texturetolinear[bound( 0, c, 255 )]; } + +// convert texture to linear 0..1 value +int LinearToTexture( float f ) { return lineartotexture[bound( 0, (int)(f * 1023), 1023 )]; } + +byte TextureLightScale( byte c ) { return s_gammatable[s_intensitytable[c]]; } \ No newline at end of file diff --git a/utils/common/imagelib.cpp b/utils/common/imagelib.cpp new file mode 100644 index 0000000..cdde005 --- /dev/null +++ b/utils/common/imagelib.cpp @@ -0,0 +1,1494 @@ +/* +imagelib.cpp - simple loader\serializer for TGA & BMP +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "conprint.h" +#include +#include +#include +#include +#include +#include "cmdlib.h" +#include "stringlib.h" +#include "imagelib.h" +#include "filesystem.h" +#include "ddstex.h" +#include "mathlib.h" + +// suffix converts to img_type and back +const imgtype_t img_hints[] = +{ +{ "_mask", IMG_ALPHAMASK }, // alpha-channel stored to another lump +{ "_norm", IMG_NORMALMAP }, // indexed normalmap +{ "_n", IMG_NORMALMAP }, // indexed normalmap +{ "_nrm", IMG_NORMALMAP }, // indexed normalmap +{ "_local", IMG_NORMALMAP }, // indexed normalmap +{ "_ddn", IMG_NORMALMAP }, // indexed normalmap +{ "_spec", IMG_GLOSSMAP }, // grayscale\color specular +{ "_gloss", IMG_GLOSSMAP }, // grayscale\color specular +{ "_hmap", IMG_HEIGHTMAP }, // heightmap (can be converted to normalmap) +{ "_height", IMG_HEIGHTMAP }, // heightmap (can be converted to normalmap) +{ "_luma", IMG_LUMA }, // self-illuminate parts on the diffuse +{ "_add", IMG_LUMA }, // self-illuminate parts on the diffuse +{ "_illum", IMG_LUMA }, // self-illuminate parts on the diffuse +{ "_bump", IMG_STALKER_BUMP }, // stalker two-component bump +{ "_bump#", IMG_STALKER_GLOSS}, // stalker two-component bump +{ "ft", IMG_SKYBOX_FT }, +{ "bk", IMG_SKYBOX_BK }, +{ "up", IMG_SKYBOX_UP }, +{ "dn", IMG_SKYBOX_DN }, +{ "rt", IMG_SKYBOX_RT }, +{ "lf", IMG_SKYBOX_LF }, +{ "px", IMG_CUBEMAP_PX }, +{ "nx", IMG_CUBEMAP_NX }, +{ "py", IMG_CUBEMAP_PY }, +{ "ny", IMG_CUBEMAP_NY }, +{ "pz", IMG_CUBEMAP_PZ }, +{ "nz", IMG_CUBEMAP_NZ }, +{ NULL, 0 } // terminator +}; + +static const loadimage_t load_hint[] = +{ +{ "%s%s.%s", "bmp", Image_LoadBMP }, // Windows Bitmap +{ "%s%s.%s", "tga", Image_LoadTGA }, // TrueVision Targa +{ "%s%s.%s", "dds", Image_LoadDDS }, // DirectDraw Surface +{ NULL, NULL, NULL } +}; + +// Xash3D normal instance +static const saveimage_t save_hint[] = +{ +{ "%s%s.%s", "bmp", Image_SaveBMP }, // Windows Bitmap +{ "%s%s.%s", "tga", Image_SaveTGA }, // TrueVision Targa +{ "%s%s.%s", "dds", Image_SaveDDS }, // DirectDraw Surface +{ NULL, NULL, NULL } +}; + +/* +================= +Image_ValidSize + +check image for valid dimensions +================= +*/ +bool Image_ValidSize( const char *name, int width, int height ) +{ + if( width > IMAGE_MAXWIDTH || height > IMAGE_MAXHEIGHT || width < IMAGE_MINWIDTH || height < IMAGE_MINHEIGHT ) + { + MsgDev( D_ERROR, "Image: %s has invalid sizes %i x %i\n", name, width, height ); + return false; + } + + return true; +} + +/* +================= +Image_Alloc + +allocate image struct and partially fill it +================= +*/ +rgbdata_t *Image_Alloc( int width, int height, bool paletted ) +{ + size_t pic_size = sizeof( rgbdata_t ) + (width * height * 4); + rgbdata_t *pic = (rgbdata_t *)Mem_Alloc( pic_size ); + + pic->buffer = ((byte *)pic) + sizeof( rgbdata_t ); + pic->size = (width * height * 4); + pic->width = width; + pic->height = height; + + return pic; +} + +/* +================= +Image_AllocCubemap + +allocate image struct and partially fill it +================= +*/ +rgbdata_t *Image_AllocCubemap( int width, int height ) +{ + size_t pic_size = sizeof( rgbdata_t ) + (width * height * 4 * 6); + rgbdata_t *pic = (rgbdata_t *)Mem_Alloc( pic_size ); + + pic->buffer = ((byte *)pic) + sizeof( rgbdata_t ); + pic->size = (width * height * 4 * 6); + pic->width = width; + pic->height = height; + SetBits( pic->flags, IMAGE_CUBEMAP ); + + return pic; +} + +/* +================= +Image_AllocSkybox + +allocate image struct and partially fill it +================= +*/ +rgbdata_t *Image_AllocSkybox( int width, int height ) +{ + size_t pic_size = sizeof( rgbdata_t ) + (width * height * 4 * 6); + rgbdata_t *pic = (rgbdata_t *)Mem_Alloc( pic_size ); + + pic->buffer = ((byte *)pic) + sizeof( rgbdata_t ); + pic->size = (width * height * 4 * 6); + pic->width = width; + pic->height = height; + SetBits( pic->flags, IMAGE_SKYBOX ); + + return pic; +} + +/* +================= +Image_Copy + +make an copy of image +================= +*/ +rgbdata_t *Image_Copy( rgbdata_t *src ) +{ + size_t pic_size = sizeof( rgbdata_t ) + src->size; + rgbdata_t *dst = (rgbdata_t *)Mem_Alloc( pic_size ); + dst->buffer = ((byte *)dst) + sizeof( rgbdata_t ); + memcpy( dst->buffer, src->buffer, src->size ); + + dst->size = src->size; + dst->width = src->width; + dst->height = src->height; + dst->flags = src->flags; + VectorCopy( src->reflectivity, dst->reflectivity ); + + return dst; +} + +/* +=========== +Image_HintFromSuf + +Convert name suffix into image type +=========== +*/ +char Image_HintFromSuf( const char *lumpname ) +{ + char barename[64]; + char suffix[16]; + const imgtype_t *hint; + + // trying to extract hint from the name + Q_strncpy( barename, lumpname, sizeof( barename )); + + // we not known about filetype, so match only by filename + for( hint = img_hints; hint->ext; hint++ ) + { + if( Q_strlen( barename ) <= Q_strlen( hint->ext )) + continue; // name too short + + Q_strncpy( suffix, barename + Q_strlen( barename ) - Q_strlen( hint->ext ), sizeof( suffix )); + if( !Q_stricmp( suffix, hint->ext )) + return hint->type; + } + + // special additional check for "_normal" + if( Q_stristr( lumpname, "_normal" )) + return IMG_NORMALMAP; + + // no any special type was found + return IMG_DIFFUSE; +} + +const imgtype_t *Image_ImageTypeFromHint( char value ) +{ + const imgtype_t *hint; + + // we not known about filetype, so match only by filename + for( hint = img_hints; hint->ext; hint++ ) + { + if( hint->type == value ) + return hint; + } + + return NULL; +} + +void Image_PackRGB( float flColor[3], dword &icolor ) +{ + byte rgba[4]; + + rgba[0] = LinearToTexture( flColor[0] ); + rgba[1] = LinearToTexture( flColor[1] ); + rgba[2] = LinearToTexture( flColor[2] ); + + icolor = (0xFF << 24) | (rgba[2] << 16) | (rgba[1] << 8) | rgba[0]; +} + +void Image_UnpackRGB( dword icolor, float flColor[3] ) +{ + flColor[0] = TextureToLinear((icolor & 0x000000FF) >> 0 ); + flColor[1] = TextureToLinear((icolor & 0x0000FF00) >> 8 ); + flColor[2] = TextureToLinear((icolor & 0x00FF0000) >> 16); +} + +/* +============================================================================= + + IMAGE LOADING + +============================================================================= +*/ +/* +============= +Image_LoadTGA + +expand any image to RGBA32 but keep 8-bit unchanged +============= +*/ +rgbdata_t *Image_LoadTGA( const char *name, const byte *buffer, size_t filesize ) +{ + int i, columns, rows, row_inc, row, col; + byte *buf_p, *pixbuf, *targa_rgba; + byte palette[256][4], red = 0, green = 0, blue = 0, alpha = 0; + int readpixelcount, pixelcount, palIndex; + tga_t targa_header; + bool compressed; + rgbdata_t *pic; + + if( filesize < sizeof( tga_t )) + return NULL; + + buf_p = (byte *)buffer; + targa_header.id_length = *buf_p++; + targa_header.colormap_type = *buf_p++; + targa_header.image_type = *buf_p++; + + targa_header.colormap_index = *(short *)buf_p; buf_p += 2; + targa_header.colormap_length = *(short *)buf_p; buf_p += 2; + targa_header.colormap_size = *buf_p; buf_p += 1; + targa_header.x_origin = *(short *)buf_p; buf_p += 2; + targa_header.y_origin = *(short *)buf_p; buf_p += 2; + targa_header.width = *(short *)buf_p; buf_p += 2; + targa_header.height = *(short *)buf_p; buf_p += 2; + targa_header.pixel_size = *buf_p++; + targa_header.attributes = *buf_p++; + if( targa_header.id_length != 0 ) + buf_p += targa_header.id_length; // skip TARGA image comment + + // check for tga file + if( !Image_ValidSize( name, targa_header.width, targa_header.height )) + return NULL; + + if( targa_header.image_type == 1 || targa_header.image_type == 9 ) + { + // uncompressed colormapped image + if( targa_header.pixel_size != 8 ) + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) Only 8 bit images supported for type 1 and 9\n", name ); + return NULL; + } + + if( targa_header.colormap_length != 256 ) + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) Only 8 bit colormaps are supported for type 1 and 9\n", name ); + return NULL; + } + + if( targa_header.colormap_index ) + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) colormap_index is not supported for type 1 and 9\n", name ); + return NULL; + } + + if( targa_header.colormap_size == 24 ) + { + for( i = 0; i < targa_header.colormap_length; i++ ) + { + palette[i][2] = *buf_p++; + palette[i][1] = *buf_p++; + palette[i][0] = *buf_p++; + palette[i][3] = 255; + } + } + else if( targa_header.colormap_size == 32 ) + { + for( i = 0; i < targa_header.colormap_length; i++ ) + { + palette[i][2] = *buf_p++; + palette[i][1] = *buf_p++; + palette[i][0] = *buf_p++; + palette[i][3] = *buf_p++; + } + } + else + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) only 24 and 32 bit colormaps are supported for type 1 and 9\n", name ); + return NULL; + } + } + else if( targa_header.image_type == 2 || targa_header.image_type == 10 ) + { + // uncompressed or RLE compressed RGB + if( targa_header.pixel_size != 32 && targa_header.pixel_size != 24 ) + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) Only 32 or 24 bit images supported for type 2 and 10\n", name ); + return NULL; + } + } + else if( targa_header.image_type == 3 || targa_header.image_type == 11 ) + { + // uncompressed greyscale + if( targa_header.pixel_size != 8 ) + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) Only 8 bit images supported for type 3 and 11\n", name ); + return NULL; + } + } + + pic = Image_Alloc( targa_header.width, targa_header.height ); + + columns = targa_header.width; + rows = targa_header.height; + targa_rgba = pic->buffer; + + // if bit 5 of attributes isn't set, the image has been stored from bottom to top + if( targa_header.attributes & 0x20 ) + { + pixbuf = targa_rgba; + row_inc = 0; + } + else + { + pixbuf = targa_rgba + ( rows - 1 ) * columns * 4; + row_inc = -columns * 4 * 2; + } + + compressed = ( targa_header.image_type == 9 || targa_header.image_type == 10 || targa_header.image_type == 11 ); + + for( row = col = 0; row < rows; ) + { + pixelcount = 0x10000; + readpixelcount = 0x10000; + + if( compressed ) + { + pixelcount = *buf_p++; + if( pixelcount & 0x80 ) // run-length packet + readpixelcount = 1; + pixelcount = 1 + ( pixelcount & 0x7f ); + } + + while( pixelcount-- && ( row < rows ) ) + { + if( readpixelcount-- > 0 ) + { + switch( targa_header.image_type ) + { + case 1: + case 9: + // colormapped image + palIndex = *buf_p++; + red = palette[palIndex][0]; + green = palette[palIndex][1]; + blue = palette[palIndex][2]; + alpha = palette[palIndex][3]; + break; + case 2: + case 10: + // 24 or 32 bit image + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alpha = 255; + if( targa_header.pixel_size == 32 ) + alpha = *buf_p++; + break; + case 3: + case 11: + // greyscale image + blue = green = red = *buf_p++; + alpha = 255; + break; + } + } + + if( red != green || green != blue ) + pic->flags |= IMAGE_HAS_COLOR; + + if( alpha != 255 ) + { + if( alpha != 0 ) + { + SetBits( pic->flags, IMAGE_HAS_8BIT_ALPHA ); + ClearBits( pic->flags, IMAGE_HAS_1BIT_ALPHA ); + } + else if( !FBitSet( pic->flags, IMAGE_HAS_8BIT_ALPHA )) + SetBits( pic->flags, IMAGE_HAS_1BIT_ALPHA ); + } + + pic->reflectivity[0] += TextureToLinear( red ); + pic->reflectivity[1] += TextureToLinear( green ); + pic->reflectivity[2] += TextureToLinear( blue ); + + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha; + + if( ++col == columns ) + { + // run spans across rows + row++; + col = 0; + pixbuf += row_inc; + } + } + } + + VectorDivide( pic->reflectivity, ( pic->width * pic->height ), pic->reflectivity ); + + return pic; +} + +/* +============= +Image_LoadBMP + +expand any image to RGBA32 but keep 8-bit unchanged +============= +*/ +rgbdata_t *Image_LoadBMP( const char *name, const byte *buffer, size_t filesize ) +{ + byte *buf_p, *pixbuf; + byte palette[256][4]; + int columns, column, rows, row, bpp = 4; + int cbPalBytes = 0, padSize = 0, bps = 0; + rgbdata_t *pic; + bmp_t bhdr; + + if( filesize < sizeof( bhdr )) + return NULL; + + buf_p = (byte *)buffer; + bhdr.id[0] = *buf_p++; + bhdr.id[1] = *buf_p++; // move pointer + bhdr.fileSize = *(long *)buf_p; buf_p += 4; + bhdr.reserved0 = *(long *)buf_p; buf_p += 4; + bhdr.bitmapDataOffset = *(long *)buf_p; buf_p += 4; + bhdr.bitmapHeaderSize = *(long *)buf_p; buf_p += 4; + bhdr.width = *(long *)buf_p; buf_p += 4; + bhdr.height = *(long *)buf_p; buf_p += 4; + bhdr.planes = *(short *)buf_p; buf_p += 2; + bhdr.bitsPerPixel = *(short *)buf_p; buf_p += 2; + bhdr.compression = *(long *)buf_p; buf_p += 4; + bhdr.bitmapDataSize = *(long *)buf_p; buf_p += 4; + bhdr.hRes = *(long *)buf_p; buf_p += 4; + bhdr.vRes = *(long *)buf_p; buf_p += 4; + bhdr.colors = *(long *)buf_p; buf_p += 4; + bhdr.importantColors = *(long *)buf_p; buf_p += 4; + + // bogus file header check + if( bhdr.reserved0 != 0 ) return NULL; + if( bhdr.planes != 1 ) return NULL; + + if( memcmp( bhdr.id, "BM", 2 )) + { + MsgDev( D_ERROR, "Image_LoadBMP: only Windows-style BMP files supported (%s)\n", name ); + return NULL; + } + + if( bhdr.bitmapHeaderSize != 0x28 ) + { + MsgDev( D_ERROR, "Image_LoadBMP: invalid header size %i\n", bhdr.bitmapHeaderSize ); + return NULL; + } + + // bogus info header check + if( bhdr.fileSize != filesize ) + { + // Sweet Half-Life issues. splash.bmp have bogus filesize + MsgDev( D_WARN, "Image_LoadBMP: %s have incorrect file size %i should be %i\n", name, filesize, bhdr.fileSize ); + } + + // bogus compression? Only non-compressed supported. + if( bhdr.compression != BI_RGB ) + { + MsgDev( D_ERROR, "Image_LoadBMP: only uncompressed BMP files supported (%s)\n", name ); + return false; + } + + columns = bhdr.width; + rows = abs( bhdr.height ); + + if( !Image_ValidSize( name, columns, rows )) + return false; + + pic = Image_Alloc( columns, rows ); + + if( bhdr.bitsPerPixel <= 8 ) + { + // figure out how many entries are actually in the table + if( bhdr.colors == 0 ) + { + bhdr.colors = 256; + cbPalBytes = (1 << bhdr.bitsPerPixel) * sizeof( RGBQUAD ); + } + else cbPalBytes = bhdr.colors * sizeof( RGBQUAD ); + } + + memcpy( palette, buf_p, cbPalBytes ); + + buf_p += cbPalBytes; + bps = bhdr.width * (bhdr.bitsPerPixel >> 3); + + switch( bhdr.bitsPerPixel ) + { + case 1: + padSize = (( 32 - ( bhdr.width % 32 )) / 8 ) % 4; + break; + case 4: + padSize = (( 8 - ( bhdr.width % 8 )) / 2 ) % 4; + break; + case 16: + padSize = ( 4 - ( bhdr.width * 2 % 4 )) % 4; + break; + case 8: + case 24: + padSize = ( 4 - ( bps % 4 )) % 4; + break; + } + + for( row = rows - 1; row >= 0; row-- ) + { + pixbuf = pic->buffer + (row * columns * bpp); + + for( column = 0; column < columns; column++ ) + { + byte red, green, blue, alpha; + int c, k, palIndex; + word shortPixel; + + switch( bhdr.bitsPerPixel ) + { + case 1: + alpha = *buf_p++; + column--; // ingnore main iterations + for( c = 0, k = 128; c < 8; c++, k >>= 1 ) + { + red = green = blue = (!!(alpha & k) == 1 ? 0xFF : 0x00); + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 0x00; + if( ++column == columns ) + break; + } + break; + case 4: + alpha = *buf_p++; + palIndex = alpha >> 4; + *pixbuf++ = red = palette[palIndex][2]; + *pixbuf++ = green = palette[palIndex][1]; + *pixbuf++ = blue = palette[palIndex][0]; + *pixbuf++ = palette[palIndex][3]; + if( ++column == columns ) break; + palIndex = alpha & 0x0F; + *pixbuf++ = red = palette[palIndex][2]; + *pixbuf++ = green = palette[palIndex][1]; + *pixbuf++ = blue = palette[palIndex][0]; + *pixbuf++ = palette[palIndex][3]; + break; + case 8: + palIndex = *buf_p++; + red = palette[palIndex][2]; + green = palette[palIndex][1]; + blue = palette[palIndex][0]; + alpha = palette[palIndex][3]; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha; + break; + case 16: + shortPixel = *(word *)buf_p, buf_p += 2; + *pixbuf++ = blue = (shortPixel & ( 31 << 10 )) >> 7; + *pixbuf++ = green = (shortPixel & ( 31 << 5 )) >> 2; + *pixbuf++ = red = (shortPixel & ( 31 )) << 3; + *pixbuf++ = alpha = 0xff; + break; + case 24: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha = 0xFF; + break; + case 32: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alpha = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha; + break; + default: + MsgDev( D_ERROR, "Image_LoadBMP: illegal pixel_size (%s)\n", name ); + Mem_Free( pic ); + return NULL; + } + + pic->reflectivity[0] += TextureToLinear( red ); + pic->reflectivity[1] += TextureToLinear( green ); + pic->reflectivity[2] += TextureToLinear( blue ); + + if(( red != green ) || ( green != blue )) + pic->flags |= IMAGE_HAS_COLOR; + + if( alpha != 255 ) + { + if( alpha != 0 ) + { + SetBits( pic->flags, IMAGE_HAS_8BIT_ALPHA ); + ClearBits( pic->flags, IMAGE_HAS_1BIT_ALPHA ); + } + else if( !FBitSet( pic->flags, IMAGE_HAS_8BIT_ALPHA )) + SetBits( pic->flags, IMAGE_HAS_1BIT_ALPHA ); + } + } + + buf_p += padSize; // probably actual only for 4-bit bmps + } + + VectorDivide( pic->reflectivity, ( pic->width * pic->height ), pic->reflectivity ); + + return pic; +} + +/* +============= +Image_LoadDDS +============= +*/ +rgbdata_t *Image_LoadDDS( const char *name, const byte *buffer, size_t filesize ) +{ + return DDSToRGBA( name, buffer, filesize ); +} + +/* +================ +COM_LoadImage + +handle bmp & tga +================ +*/ +rgbdata_t *COM_LoadImage( const char *filename, bool quiet ) +{ + const char *ext = COM_FileExtension( filename ); + char path[128], loadname[128]; + bool anyformat = true; + size_t filesize = 0; + const loadimage_t *format; + rgbdata_t *image; + byte *buf; + + Q_strncpy( loadname, filename, sizeof( loadname )); + + if( Q_stricmp( ext, "" )) + { + // we needs to compare file extension with list of supported formats + // and be sure what is real extension, not a filename with dot + for( format = load_hint; format && format->formatstring; format++ ) + { + if( !Q_stricmp( format->ext, ext )) + { + COM_StripExtension( loadname ); + anyformat = false; + break; + } + } + } + + // now try all the formats in the selected list + for( format = load_hint; format && format->formatstring; format++ ) + { + if( anyformat || !Q_stricmp( ext, format->ext )) + { + Q_sprintf( path, format->formatstring, loadname, "", format->ext ); +#ifdef ALLOW_WADS_IN_PACKS + buf = FS_LoadFile( path, &filesize, false ); +#else + buf = COM_LoadFile( path, &filesize ); +#endif + if( buf && filesize > 0 ) + { + image = format->loadfunc( path, buf, filesize ); + Mem_Free( buf ); // release buffer + if( image ) return image; // loaded + } + } + } + + if( !quiet ) + MsgDev( D_ERROR, "COM_LoadImage: couldn't load \"%s\"\n", loadname ); + return NULL; +} + +/* +============================================================================= + + IMAGE SAVING + +============================================================================= +*/ +/* +============= +Image_SaveTGA +============= +*/ +bool Image_SaveTGA( const char *name, rgbdata_t *pix ) +{ + const char *comment = "Generated by XashNT MakeTex tool.\0"; + int y, outsize, pixel_size = 4; + const byte *bufend, *in; + byte *buffer, *out; + + if( COM_FileExists( name )) + return false; // already existed + + // bogus parameter check + if( !pix->buffer ) + return false; + + if( pix->flags & IMAGE_HAS_ALPHA ) + outsize = pix->width * pix->height * 4 + 18 + Q_strlen( comment ); + else outsize = pix->width * pix->height * 3 + 18 + Q_strlen( comment ); + + buffer = (byte *)Mem_Alloc( outsize ); + memset( buffer, 0, 18 ); + + // prepare header + buffer[0] = Q_strlen( comment ); // tga comment length + buffer[2] = 2; // uncompressed type + buffer[12] = (pix->width >> 0) & 0xFF; + buffer[13] = (pix->width >> 8) & 0xFF; + buffer[14] = (pix->height >> 0) & 0xFF; + buffer[15] = (pix->height >> 8) & 0xFF; + buffer[16] = ( pix->flags & IMAGE_HAS_ALPHA ) ? 32 : 24; // RGB pixel size + buffer[17] = ( pix->flags & IMAGE_HAS_ALPHA ) ? 8 : 0; // 8 bits of alpha + + Q_strncpy( (char *)(buffer + 18), comment, Q_strlen( comment )); + out = buffer + 18 + Q_strlen( comment ); + + // swap rgba to bgra and flip upside down + for( y = pix->height - 1; y >= 0; y-- ) + { + in = pix->buffer + y * pix->width * pixel_size; + bufend = in + pix->width * pixel_size; + for( ; in < bufend; in += pixel_size ) + { + *out++ = in[2]; + *out++ = in[1]; + *out++ = in[0]; + if( pix->flags & IMAGE_HAS_ALPHA ) + *out++ = in[3]; + } + } + + COM_SaveFile( name, buffer, outsize ); + Mem_Free( buffer ); + + return true; +} + +bool Image_SaveBMP( const char *name, rgbdata_t *pix ) +{ + long file; + BITMAPFILEHEADER bmfh; + BITMAPINFOHEADER bmih; + dword cbBmpBits; + byte *pb, *pbBmpBits; + dword biTrueWidth; + int pixel_size; + int i, x, y; + + if( COM_FileExists( name )) + return false; // already existed + + // bogus parameter check + if( !pix->buffer ) + return false; + + if( FBitSet( pix->flags, IMAGE_HAS_ALPHA )) + pixel_size = 4; + else pixel_size = 3; + + COM_CreatePath( (char *)name ); + + file = open( name, O_WRONLY|O_BINARY|O_CREAT|O_TRUNC, 0666 ); + if( file < 0 ) return false; + + // NOTE: align transparency column will sucessfully removed + // after create sprite or lump image, it's just standard requiriments + biTrueWidth = ((pix->width + 3) & ~3); + cbBmpBits = biTrueWidth * pix->height * pixel_size; + + // Bogus file header check + bmfh.bfType = MAKEWORD( 'B', 'M' ); + bmfh.bfSize = sizeof( bmfh ) + sizeof( bmih ) + cbBmpBits; + bmfh.bfOffBits = sizeof( bmfh ) + sizeof( bmih ); + bmfh.bfReserved1 = bmfh.bfReserved2 = 0; + + // write header + write( file, &bmfh, sizeof( bmfh )); + + // size of structure + bmih.biSize = sizeof( bmih ); + bmih.biWidth = biTrueWidth; + bmih.biHeight = pix->height; + bmih.biPlanes = 1; + bmih.biBitCount = pixel_size * 8; + bmih.biCompression = BI_RGB; + bmih.biSizeImage = cbBmpBits; + bmih.biXPelsPerMeter = 0; + bmih.biYPelsPerMeter = 0; + bmih.biClrUsed = 0; + bmih.biClrImportant = 0; + + // write info header + write( file, &bmih, sizeof( bmih )); + + pbBmpBits = (byte *)Mem_Alloc( cbBmpBits ); + + pb = pix->buffer; + + for( y = 0; y < bmih.biHeight; y++ ) + { + i = (bmih.biHeight - 1 - y ) * (bmih.biWidth); + + for( x = 0; x < pix->width; x++ ) + { + // 24 bit + pbBmpBits[i*pixel_size+0] = pb[x*4+2]; + pbBmpBits[i*pixel_size+1] = pb[x*4+1]; + pbBmpBits[i*pixel_size+2] = pb[x*4+0]; + + if( pixel_size == 4 ) // write alpha channel + pbBmpBits[i*pixel_size+3] = pb[x*4+3]; + i++; + } + + pb += pix->width * pixel_size; + } + + // write bitmap bits (remainder of file) + write( file, pbBmpBits, cbBmpBits ); + close( file ); + Mem_Free( pbBmpBits ); + + return true; +} + +/* +============= +Image_SaveDDS +============= +*/ +bool Image_SaveDDS( const char *name, rgbdata_t *pix ) +{ + char lumpname[64]; + rgbdata_t *dds_image = NULL; + + if( COM_FileExists( name )) + return false; // already existed + + // bogus parameter check + if( !pix->buffer ) + return false; + + // check for easy out + if( FBitSet( pix->flags, IMAGE_DXT_FORMAT )) + return COM_SaveFile( name, pix->buffer, pix->size ); + + COM_FileBase( name, lumpname ); + + char hint = Image_HintFromSuf( lumpname ); + + dds_image = BufferToDDS( pix, DDS_GetSaveFormatForHint( hint, pix )); + if( !dds_image ) return false; + + bool result = COM_SaveFile( name, dds_image->buffer, dds_image->size ); + + Mem_Free( dds_image ); + + return result; +} + +/* +================ +COM_SaveImage + +handle bmp & tga +================ +*/ +bool COM_SaveImage( const char *filename, rgbdata_t *pix ) +{ + const char *ext = COM_FileExtension( filename ); + bool anyformat = !Q_stricmp( ext, "" ) ? true : false; + char path[128], savename[128]; + const saveimage_t *format; + + if( !pix || !pix->buffer || anyformat ) + return false; + + Q_strncpy( savename, filename, sizeof( savename )); + COM_StripExtension( savename ); // remove extension if needed + + for( format = save_hint; format && format->formatstring; format++ ) + { + if( !Q_stricmp( ext, format->ext )) + { + Q_sprintf( path, format->formatstring, savename, "", format->ext ); + if( format->savefunc( path, pix )) + return true; // saved + } + } + + MsgDev( D_ERROR, "COM_SaveImage: unsupported format (%s)\n", ext ); + + return false; +} + +/* +============================================================================= + + IMAGE PROCESSING + +============================================================================= +*/ +#define TRANSPARENT_R 0x0 +#define TRANSPARENT_G 0x0 +#define TRANSPARENT_B 0xFF +#define IS_TRANSPARENT( p ) ( p[0] == TRANSPARENT_R && p[1] == TRANSPARENT_G && p[2] == TRANSPARENT_B ) +#define LERPBYTE( i ) r = resamplerow1[i]; out[i] = (byte)(((( resamplerow2[i] - r ) * lerp)>>16 ) + r ) + +static void Image_Resample32LerpLine( const byte *in, byte *out, int inwidth, int outwidth ) +{ + int j, xi, oldx = 0, f, fstep, endx, lerp; + + fstep = (int)(inwidth * 65536.0f / outwidth); + endx = (inwidth-1); + + for( j = 0, f = 0; j < outwidth; j++, f += fstep ) + { + xi = f>>16; + if( xi != oldx ) + { + in += (xi - oldx) * 4; + oldx = xi; + } + if( xi < endx ) + { + lerp = f & 0xFFFF; + *out++ = (byte)((((in[4] - in[0]) * lerp)>>16) + in[0]); + *out++ = (byte)((((in[5] - in[1]) * lerp)>>16) + in[1]); + *out++ = (byte)((((in[6] - in[2]) * lerp)>>16) + in[2]); + *out++ = (byte)((((in[7] - in[3]) * lerp)>>16) + in[3]); + } + else // last pixel of the line has no pixel to lerp to + { + *out++ = in[0]; + *out++ = in[1]; + *out++ = in[2]; + *out++ = in[3]; + } + } +} + +void Image_Resample32Lerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight ) +{ + const byte *inrow; + int i, j, r, yi, oldy = 0, f, fstep, lerp, endy = (inheight - 1); + int inwidth4 = inwidth * 4; + int outwidth4 = outwidth * 4; + byte *out = (byte *)outdata; + byte *resamplerow1; + byte *resamplerow2; + + fstep = (int)(inheight * 65536.0f / outheight); + + resamplerow1 = (byte *)Mem_Alloc( outwidth * 4 * 2 ); + resamplerow2 = resamplerow1 + outwidth * 4; + + inrow = (const byte *)indata; + + Image_Resample32LerpLine( inrow, resamplerow1, inwidth, outwidth ); + Image_Resample32LerpLine( inrow + inwidth4, resamplerow2, inwidth, outwidth ); + + for( i = 0, f = 0; i < outheight; i++, f += fstep ) + { + yi = f >> 16; + + if( yi < endy ) + { + lerp = f & 0xFFFF; + + if( yi != oldy ) + { + inrow = (byte *)indata + inwidth4 * yi; + if( yi == ( oldy + 1 )) memcpy( resamplerow1, resamplerow2, outwidth4 ); + else Image_Resample32LerpLine( inrow, resamplerow1, inwidth, outwidth ); + Image_Resample32LerpLine( inrow + inwidth4, resamplerow2, inwidth, outwidth ); + oldy = yi; + } + + j = outwidth - 4; + + while( j >= 0 ) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + LERPBYTE( 4); + LERPBYTE( 5); + LERPBYTE( 6); + LERPBYTE( 7); + LERPBYTE( 8); + LERPBYTE( 9); + LERPBYTE(10); + LERPBYTE(11); + LERPBYTE(12); + LERPBYTE(13); + LERPBYTE(14); + LERPBYTE(15); + out += 16; + resamplerow1 += 16; + resamplerow2 += 16; + j -= 4; + } + + if( j & 2 ) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + LERPBYTE( 4); + LERPBYTE( 5); + LERPBYTE( 6); + LERPBYTE( 7); + out += 8; + resamplerow1 += 8; + resamplerow2 += 8; + } + + if( j & 1 ) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + out += 4; + resamplerow1 += 4; + resamplerow2 += 4; + } + + resamplerow1 -= outwidth4; + resamplerow2 -= outwidth4; + } + else + { + if( yi != oldy ) + { + inrow = (byte *)indata + inwidth4 * yi; + if( yi == ( oldy + 1 )) memcpy( resamplerow1, resamplerow2, outwidth4 ); + else Image_Resample32LerpLine( inrow, resamplerow1, inwidth, outwidth ); + oldy = yi; + } + + memcpy( out, resamplerow1, outwidth4 ); + } + } + + Mem_Free( resamplerow1 ); +} + +/* +================ +Image_Resample +================ +*/ +rgbdata_t *Image_Resample( rgbdata_t *pic, int new_width, int new_height ) +{ + if( !pic ) return NULL; + + // nothing to resample ? + if( pic->width == new_width && pic->height == new_height ) + return pic; + + MsgDev( D_REPORT, "Image_Resample: from %ix%i to %ix%i\n", pic->width, pic->height, new_width, new_height ); + + rgbdata_t *out = Image_Alloc( new_width, new_height ); + + Image_Resample32Lerp( pic->buffer, pic->width, pic->height, out->buffer, out->width, out->height ); + + out->flags = pic->flags; + + // release old image + Mem_Free( pic ); + + return out; +} + +/* +================ +Image_ExtractAlphaMask + +we can't store alpha-channel into 8-bit texture +but we can store it separate as another image +================ +*/ +rgbdata_t *Image_ExtractAlphaMask( rgbdata_t *pic ) +{ + rgbdata_t *out; + + if( !pic ) return NULL; + + if( !FBitSet( pic->flags, IMAGE_HAS_ALPHA )) + return NULL; // no alpha-channel stored + + out = Image_Copy( pic ); // duplicate the image + + for( int i = 0; i < pic->width * pic->height; i++ ) + { + // copy the alpha into color buffer + out->buffer[i*4+0] = pic->buffer[i*4+3]; + out->buffer[i*4+1] = pic->buffer[i*4+3]; + out->buffer[i*4+2] = pic->buffer[i*4+3]; + out->buffer[i*4+3] = 0xFF; // clear the alpha + } + + ClearBits( out->flags, IMAGE_HAS_COLOR ); + ClearBits( out->flags, IMAGE_HAS_ALPHA ); + + return out; +} + +/* +================ +Image_ApplyGamma + +we can't store alpha-channel into 8-bit texture +but we can store it separate as another image +================ +*/ +void Image_ApplyGamma( rgbdata_t *pic ) +{ + if( !pic || FBitSet( pic->flags, IMAGE_DXT_FORMAT )) + return; // can't process compressed image + + for( int i = 0; i < pic->width * pic->height; i++ ) + { + // copy the alpha into color buffer + pic->buffer[i*4+0] = TextureLightScale( pic->buffer[i*4+0] ); + pic->buffer[i*4+1] = TextureLightScale( pic->buffer[i*4+1] ); + pic->buffer[i*4+2] = TextureLightScale( pic->buffer[i*4+2] ); + } +} + +/* +================ +Image_MergeColorAlpha + +we can't store alpha-channel into 8-bit texture +but we can store it separate as another image +================ +*/ +rgbdata_t *Image_MergeColorAlpha( rgbdata_t *color, rgbdata_t *alpha ) +{ + rgbdata_t *int_alpha; + byte avalue; + + if( !color ) return NULL; + if( !alpha ) return color; + + if( FBitSet( color->flags, IMAGE_DXT_FORMAT )) + return color; // can't merge compressed formats + + if( FBitSet( alpha->flags, IMAGE_DXT_FORMAT )) + return color; // can't merge compressed formats + + int_alpha = Image_Copy( alpha ); // duplicate the image + + if( color->width != alpha->width || color->height != alpha->height ) + { + Image_Resample( int_alpha, color->width, color->height ); + } + + for( int i = 0; i < color->width * color->height; i++ ) + { + // copy the alpha into color buffer (just use R instead?) + avalue = (int_alpha->buffer[i*4+0] + int_alpha->buffer[i*4+1] + int_alpha->buffer[i*4+2]) / 3; + + if( avalue != 255 ) + { + if( avalue != 0 ) + { + SetBits( color->flags, IMAGE_HAS_8BIT_ALPHA ); + ClearBits( color->flags, IMAGE_HAS_1BIT_ALPHA ); + } + else if( !FBitSet( color->flags, IMAGE_HAS_8BIT_ALPHA )) + SetBits( color->flags, IMAGE_HAS_1BIT_ALPHA ); + } + + color->buffer[i*4+3] = avalue; + } + + Mem_Free( int_alpha ); + + return color; +} + +/* +================ +Image_CreateCubemap + +create cubemap from 6 images +images must be sorted in properly order +================ +*/ +rgbdata_t *Image_CreateCubemap( rgbdata_t *images[6], bool skybox, bool nomips ) +{ + rgbdata_t *out; + int base_width; + int base_height; + int i; + + if( !images ) return NULL; + + base_width = (images[0]->width + 15) & ~15; + base_height = (images[0]->height + 15) & ~15; + + for( i = 0; i < 6; i++ ) + { + // validate the sides + if( !images[i] || FBitSet( images[i]->flags, IMAGE_DXT_FORMAT|IMAGE_CUBEMAP|IMAGE_SKYBOX )) + break; + + // rare case: cube sides with different dimensions + images[i] = Image_Resample( images[i], base_width, base_height ); + } + + if( i != 6 ) return NULL; + + if( skybox ) + out = Image_AllocSkybox( base_width, base_height ); + else out = Image_AllocCubemap( base_width, base_height ); + + if( nomips ) SetBits( out->flags, IMAGE_NOMIPS ); + + // copy the sides + for( i = 0; i < 6; i++ ) + { + VectorAdd( out->reflectivity, images[i]->reflectivity, out->reflectivity ); + memcpy( out->buffer + (images[i]->size * i), images[i]->buffer, images[i]->size ); + Mem_Free( images[i] ); // release original + } + + // divide by sides count + VectorDivide( out->reflectivity, 6.0f, out->reflectivity ); + + return out; +} + +/* +================ +R_MipMap2 + +Operates in place, quartering the size of the texture +Proper linear filter +================ +*/ +static void Image_BuildMipMapLinear( uint *in, int inWidth, int inHeight ) +{ + int i, j, k; + byte *outpix; + int inWidthMask, inHeightMask; + int total; + int outWidth, outHeight; + uint *temp; + + outWidth = inWidth >> 1; + outHeight = inHeight >> 1; + temp = (uint *)Mem_Alloc( outWidth * outHeight * 4 ); + + inWidthMask = inWidth - 1; + inHeightMask = inHeight - 1; + + for( i = 0; i < outHeight; i++ ) + { + for( j = 0; j < outWidth; j++ ) + { + outpix = (byte *)(temp + i * outWidth + j); + for( k = 0; k < 4; k++ ) + { + total = + 1 * ((byte *)&in[((i * 2 - 1) & inHeightMask) * inWidth + ((j * 2 - 1) & inWidthMask)])[k] + + 2 * ((byte *)&in[((i * 2 - 1) & inHeightMask) * inWidth + ((j * 2) & inWidthMask)])[k] + + 2 * ((byte *)&in[((i * 2 - 1) & inHeightMask) * inWidth + ((j * 2 + 1) & inWidthMask)])[k] + + 1 * ((byte *)&in[((i * 2 - 1) & inHeightMask) * inWidth + ((j * 2 + 2) & inWidthMask)])[k] + + 2 * ((byte *)&in[((i * 2) & inHeightMask) * inWidth + ((j * 2 - 1) & inWidthMask)])[k] + + 4 * ((byte *)&in[((i * 2) & inHeightMask) * inWidth + ((j * 2) & inWidthMask)])[k] + + 4 * ((byte *)&in[((i * 2) & inHeightMask) * inWidth + ((j * 2 + 1) & inWidthMask)])[k] + + 2 * ((byte *)&in[((i * 2) & inHeightMask) * inWidth + ((j * 2 + 2) & inWidthMask)])[k] + + 2 * ((byte *)&in[((i * 2 + 1) & inHeightMask) * inWidth + ((j * 2 - 1) & inWidthMask)])[k] + + 4 * ((byte *)&in[((i * 2 + 1) & inHeightMask) * inWidth + ((j * 2) & inWidthMask)])[k] + + 4 * ((byte *)&in[((i * 2 + 1) & inHeightMask) * inWidth + ((j * 2 + 1) & inWidthMask)])[k] + + 2 * ((byte *)&in[((i * 2 + 1) & inHeightMask) * inWidth + ((j * 2 + 2) & inWidthMask)])[k] + + 1 * ((byte *)&in[((i * 2 + 2) & inHeightMask) * inWidth + ((j * 2 - 1) & inWidthMask)])[k] + + 2 * ((byte *)&in[((i * 2 + 2) & inHeightMask) * inWidth + ((j * 2) & inWidthMask)])[k] + + 2 * ((byte *)&in[((i * 2 + 2) & inHeightMask) * inWidth + ((j * 2 + 1) & inWidthMask)])[k] + + 1 * ((byte *)&in[((i * 2 + 2) & inHeightMask) * inWidth + ((j * 2 + 2) & inWidthMask)])[k]; + outpix[k] = total / 36; + } + } + } + + memcpy( in, temp, outWidth * outHeight * 4 ); + Mem_Free( temp ); +} + +/* +================= +Image_BuildMipMap + +Operates in place, quartering the size of the texture +================= +*/ +void Image_BuildMipMap( byte *in, int width, int height, bool isNormalMap ) +{ + byte *out = in; + float inv127 = (1.0f / 127.0f); + vec3_t normal; + int x, y; + + if( isNormalMap ) + { + width <<= 2; + height >>= 1; + + for( y = 0; y < height; y++, in += width ) + { + for( x = 0; x < width; x += 8, in += 8, out += 4 ) + { + normal[0] = (in[0] * inv127 - 1.0f) + (in[4] * inv127 - 1.0f) + (in[width+0] * inv127 - 1.0f) + (in[width+4] * inv127 - 1.0f); + normal[1] = (in[1] * inv127 - 1.0f) + (in[5] * inv127 - 1.0f) + (in[width+1] * inv127 - 1.0f) + (in[width+5] * inv127 - 1.0f); + normal[2] = (in[2] * inv127 - 1.0f) + (in[6] * inv127 - 1.0f) + (in[width+2] * inv127 - 1.0f) + (in[width+6] * inv127 - 1.0f); + + if( VectorNormalize( normal ) == 0.0f ) + VectorSet( normal, 0.5f, 0.5f, 1.0f ); + + out[0] = (byte)(128 + 127 * normal[0]); + out[1] = (byte)(128 + 127 * normal[1]); + out[2] = (byte)(128 + 127 * normal[2]); + out[3] = 255; + } + } + } + else + { +#if 0 + Image_BuildMipMapLinear( (uint *)in, width, height ); +#else + width <<= 2; + height >>= 1; + + for( y = 0; y < height; y++, in += width ) + { + for( x = 0; x < width; x += 8, in += 8, out += 4 ) + { + out[0] = (in[0] + in[4] + in[width+0] + in[width+4]) >> 2; + out[1] = (in[1] + in[5] + in[width+1] + in[width+5]) >> 2; + out[2] = (in[2] + in[6] + in[width+2] + in[width+6]) >> 2; + out[3] = (in[3] + in[7] + in[width+3] + in[width+7]) >> 2; + } + } +#endif + } +} + +/* +================ +Image_ConvertBumpStalker + +convert stalker bump into normal textures +================ +*/ +void Image_ConvertBumpStalker( rgbdata_t *bump, rgbdata_t *spec ) +{ + if( !bump || !spec || bump->width != spec->width || bump->height != spec->height ) + return; // we need both the textures to processing + + for( int i = 0; i < bump->width * bump->height; i++ ) + { + byte bump_rgba[4], spec_rgba[4]; + vec3_t normal, error; + byte temp[4]; + + memcpy( bump_rgba, bump->buffer + (i * 4), sizeof( bump_rgba )); + memcpy( spec_rgba, spec->buffer + (i * 4), sizeof( bump_rgba )); + + normal[0] = (float)bump_rgba[3] * (1.0f / 255.0f); // alpha is X + normal[1] = (float)bump_rgba[2] * (1.0f / 255.0f); // blue is Y + normal[2] = (float)bump_rgba[1] * (1.0f / 255.0f); // green is Z + + error[0] = (float)spec_rgba[0] * (1.0f / 255.0f); // alpha is X + error[1] = (float)spec_rgba[1] * (1.0f / 255.0f); // blue is Y + error[2] = (float)spec_rgba[2] * (1.0f / 255.0f); // green is Z + + // compensate normal error + VectorAdd( normal, error, normal ); + normal[0] -= 1.0f; + normal[1] -= 1.0f; + normal[2] -= 1.0f; + + VectorNormalize( normal ); + + // store back to byte + temp[0] = (byte)(normal[0] * 127.0f + 128.0f); + temp[1] = (byte)(normal[1] * 127.0f + 128.0f); + temp[2] = (byte)(normal[2] * 127.0f + 128.0f); + temp[3] = 0; + + // put transformed pixel back to the buffer + memcpy( bump->buffer + (i * 4), temp, sizeof( temp )); + + // store glossiness + temp[0] = bump_rgba[0]; + temp[1] = bump_rgba[0]; + temp[2] = bump_rgba[0]; + temp[3] = spec_rgba[3]; // store heightmap into alpha + + // put transformed pixel back to the buffer + memcpy( spec->buffer + (i * 4), temp, sizeof( temp )); + } +} \ No newline at end of file diff --git a/utils/common/imagelib.h b/utils/common/imagelib.h new file mode 100644 index 0000000..20631c5 --- /dev/null +++ b/utils/common/imagelib.h @@ -0,0 +1,200 @@ +/* +imagelib.h - simple loader\serializer for TGA & BMP +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef IMAGELIB_H +#define IMAGELIB_H + +#define GAMMA ( 2.2f ) // Valve Software gamma +#define INVGAMMA ( 1.0f / 2.2f ) // back to 1.0 + +/* +======================================================================== + +.BMP image format + +======================================================================== +*/ +#pragma pack( 1 ) +typedef struct +{ + char id[2]; // bmfh.bfType + dword fileSize; // bmfh.bfSize + dword reserved0; // bmfh.bfReserved1 + bmfh.bfReserved2 + dword bitmapDataOffset; // bmfh.bfOffBits + dword bitmapHeaderSize; // bmih.biSize + int width; // bmih.biWidth + int height; // bmih.biHeight + word planes; // bmih.biPlanes + word bitsPerPixel; // bmih.biBitCount + dword compression; // bmih.biCompression + dword bitmapDataSize; // bmih.biSizeImage + dword hRes; // bmih.biXPelsPerMeter + dword vRes; // bmih.biYPelsPerMeter + dword colors; // bmih.biClrUsed + dword importantColors; // bmih.biClrImportant +} bmp_t; +#pragma pack( ) + +/* +======================================================================== + +.TGA image format (Truevision Targa) + +======================================================================== +*/ +#pragma pack( 1 ) +typedef struct tga_s +{ + byte id_length; + byte colormap_type; + byte image_type; + word colormap_index; + word colormap_length; + byte colormap_size; + word x_origin; + word y_origin; + word width; + word height; + byte pixel_size; + byte attributes; +} tga_t; +#pragma pack( ) + +#define IMAGE_MINWIDTH 1 // last mip-level is 1x1 +#define IMAGE_MINHEIGHT 1 +#define IMAGE_MAXWIDTH 4096 +#define IMAGE_MAXHEIGHT 4096 +#define MIP_MAXWIDTH 1024 // large sizes it's too complicated for quantizer +#define MIP_MAXHEIGHT 1024 // and provoked color degradation + +#define IMAGE_HAS_ALPHA (IMAGE_HAS_1BIT_ALPHA|IMAGE_HAS_8BIT_ALPHA|IMAGE_HAS_SDF_ALPHA) + +#define IMG_DIFFUSE 0 // same as default pad1 always equal 0 +#define IMG_ALPHAMASK 1 // alpha-channel that stored separate as luminance texture +#define IMG_NORMALMAP 2 // indexed normalmap +#define IMG_GLOSSMAP 3 // luminance or color specularity map +#define IMG_GLOSSPOWER 4 // gloss power map (each value is a specular pow) +#define IMG_HEIGHTMAP 5 // heightmap (for parallax occlusion mapping or source of normalmap) +#define IMG_LUMA 6 // luma or glow texture with self-illuminated parts +#define IMG_STALKER_BUMP 7 // stalker two-component bump +#define IMG_STALKER_GLOSS 8 // stalker two-component bump + +// NOTE: ordering is important! +#define IMG_SKYBOX_FT 9 +#define IMG_SKYBOX_BK 10 +#define IMG_SKYBOX_UP 11 +#define IMG_SKYBOX_DN 12 +#define IMG_SKYBOX_RT 13 +#define IMG_SKYBOX_LF 14 + +#define IMG_CUBEMAP_PX 15 +#define IMG_CUBEMAP_NX 16 +#define IMG_CUBEMAP_PY 17 +#define IMG_CUBEMAP_NY 18 +#define IMG_CUBEMAP_PZ 19 +#define IMG_CUBEMAP_NZ 20 + +// rgbdata->flags +typedef enum +{ + IMAGE_QUANTIZED = BIT( 0 ), // this image already quantized + IMAGE_HAS_COLOR = BIT( 1 ), // image contain RGB-channel + IMAGE_HAS_1BIT_ALPHA = BIT( 2 ), // textures with '{' + IMAGE_HAS_8BIT_ALPHA = BIT( 3 ), // image contain full-range alpha-channel + IMAGE_HAS_SDF_ALPHA = BIT( 4 ), // SIgned distance field alpha + IMAGE_CUBEMAP = BIT( 5 ), // it's 6-sides cubemap buffer + IMAGE_SKYBOX = BIT( 6 ), // it's 6-sides skybox buffer + IMAGE_DXT_FORMAT = BIT( 7 ), // this image have a DXT compression + IMAGE_QUAKE1_PAL = BIT( 8 ), // image has Quake1 palette + IMAGE_NOMIPS = BIT( 9 ), // don't build mips before DXT compression + + // Image_Process manipulation flags + IMAGE_FLIP_X = BIT( 10 ), // flip the image by width + IMAGE_FLIP_Y = BIT( 11 ), // flip the image by height + IMAGE_ROT_90 = BIT( 12 ), // flip from upper left corner to down right corner + IMAGE_ROT180 = IMAGE_FLIP_X|IMAGE_FLIP_Y, + IMAGE_ROT270 = IMAGE_FLIP_X|IMAGE_FLIP_Y|IMAGE_ROT_90, +} imgFlags_t; + +// loaded image +typedef struct rgbdata_s +{ + word width; // image width + word height; // image height + word flags; // misc image flags + byte *palette; // palette if present + byte *buffer; // image buffer + size_t size; // for bounds checking + float reflectivity[3]; // sum color of all pixels +} rgbdata_t; + +typedef struct imgtype_s +{ + char *ext; + char type; +} imgtype_t; + +typedef struct loadimage_s +{ + const char *formatstring; + const char *ext; + rgbdata_t *(*loadfunc)( const char *name, const byte *buffer, size_t filesize ); +} loadimage_t; + +typedef struct saveimage_s +{ + const char *formatstring; + const char *ext; + bool (*savefunc)( const char *name, rgbdata_t *pix ); +} saveimage_t; + +// image loading +rgbdata_t *Image_LoadTGA( const char *name, const byte *buffer, size_t filesize ); +rgbdata_t *Image_LoadBMP( const char *name, const byte *buffer, size_t filesize ); +rgbdata_t *Image_LoadDDS( const char *name, const byte *buffer, size_t filesize ); + +// image storing +bool Image_SaveTGA( const char *name, rgbdata_t *pix ); +bool Image_SaveBMP( const char *name, rgbdata_t *pix ); +bool Image_SaveDDS( const char *name, rgbdata_t *pix ); + +// common functions +rgbdata_t *Image_Alloc( int width, int height, bool paletted = false ); +rgbdata_t *Image_AllocCubemap( int width, int height ); +rgbdata_t *Image_AllocSkybox( int width, int height ); +rgbdata_t *Image_Copy( rgbdata_t *src ); +void Image_PackRGB( float flColor[3], dword &icolor ); +void Image_UnpackRGB( dword icolor, float flColor[3] ); +char Image_HintFromSuf( const char *lumpname ); +rgbdata_t *COM_LoadImage( const char *filename, bool quiet = false ); +rgbdata_t *COM_LoadImageMemory( const char *filename, const byte *buf, size_t fileSize ); +const imgtype_t *Image_ImageTypeFromHint( char value ); +bool COM_SaveImage( const char *filename, rgbdata_t *pix ); +bool Image_ValidSize( const char *name, int width, int height ); +void Image_BuildMipMap( byte *in, int width, int height, bool isNormalMap ); +rgbdata_t *Image_Resample( rgbdata_t *pic, int new_width, int new_height ); +rgbdata_t *Image_MergeColorAlpha( rgbdata_t *color, rgbdata_t *alpha ); +rgbdata_t *Image_CreateCubemap( rgbdata_t *images[6], bool skybox = false, bool nomips = false ); +void Image_ConvertBumpStalker( rgbdata_t *bump, rgbdata_t *gloss ); +void Image_MakeSignedDistanceField( rgbdata_t *pic ); +rgbdata_t *Image_ExtractAlphaMask( rgbdata_t *pic ); +void Image_MakeOneBitAlpha( rgbdata_t *pic ); +rgbdata_t *Image_Quantize( rgbdata_t *pic ); +void Image_ApplyGamma( rgbdata_t *pic ); +rgbdata_t *Image_Flip( rgbdata_t *src ); + +extern float g_gamma; + +#endif//IMAGELIB_H \ No newline at end of file diff --git a/utils/common/mathlib.cpp b/utils/common/mathlib.cpp new file mode 100644 index 0000000..24485d1 --- /dev/null +++ b/utils/common/mathlib.cpp @@ -0,0 +1,1223 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// mathlib.c -- math primitives +#include "cmdlib.h" +#include "mathlib.h" +#include "bspfile.h" + +vec3_t vec3_origin = { 0, 0, 0 }; + +#define IA 16807 +#define IM 2147483647 +#define IQ 127773 +#define IR 2836 +#define NTAB 32 +#define NDIV (1+(IM-1)/NTAB) +#define MAX_RANDOM_RANGE 0x7FFFFFFFUL + +// fran1 -- return a random floating-point number on the interval [0,1) +// +#define AM (1.0/IM) +#define EPS 1.2e-7 +#define RNMX (1.0-EPS) + +static int m_idum = 0; +static int m_iy = 0; +static int m_iv[NTAB]; + +static int GenerateRandomNumber( void ) +{ + int j, k; + + if( m_idum <= 0 || !m_iy ) + { + if( -(m_idum) < 1 ) + m_idum = 1; + else m_idum = -(m_idum); + + for( j = NTAB + 7; j >= 0; j-- ) + { + k = (m_idum) / IQ; + m_idum = IA * (m_idum - k * IQ) - IR * k; + + if( m_idum < 0 ) + m_idum += IM; + + if( j < NTAB ) + m_iv[j] = m_idum; + } + + m_iy = m_iv[0]; + } + + k = (m_idum) / IQ; + m_idum = IA * (m_idum - k * IQ) - IR * k; + + if( m_idum < 0 ) + m_idum += IM; + j = m_iy / NDIV; + + // We're seeing some strange memory corruption in the contents of s_pUniformStream. + // Perhaps it's being caused by something writing past the end of this array? + // Bounds-check in release to see if that's the case. + if( j >= NTAB || j < 0 ) + { + MsgDev( D_WARN, "GenerateRandomNumber had an array overrun: tried to write to element %d of 0..31.\n", j ); + j = ( j % NTAB ) & 0x7fffffff; + } + + m_iy = m_iv[j]; + m_iv[j] = m_idum; + + return m_iy; +} + +float RandomFloat( float flLow, float flHigh ) +{ + // float in [0,1) + float fl = AM * GenerateRandomNumber(); + if( fl > RNMX ) fl = RNMX; + + return (fl * ( flHigh - flLow ) ) + flLow; // float in [low,high) +} + +const matrix3x4 matrix3x4_identity = +{ +{ 1.0f, 0.0f, 0.0f }, // PITCH +{ 0.0f, 1.0f, 0.0f }, // YAW +{ 0.0f, 0.0f, 1.0f }, // ROLL +{ 0.0f, 0.0f, 0.0f }, // ORG +}; + +half :: half( const float x ) +{ + union + { + float floatI; + unsigned int i; + }; + + floatI = x; + int e = ((i >> 23) & 0xFF) - 112; + int m = i & 0x007FFFFF; + + sh = (i >> 16) & 0x8000; + if( e <= 0 ) + { + // Denorm + m = ((m | 0x00800000) >> (1 - e)) + 0x1000; + sh |= (m >> 13); + } + else if( e == 143 ) + { + sh |= 0x7C00; + if (m != 0){ + // NAN + m >>= 13; + sh |= m | (m == 0); + } + } + else + { + m += 0x1000; + + if( m & 0x00800000 ) + { + // Mantissa overflow + m = 0; + e++; + } + + if( e >= 31 ) + { + // Exponent overflow + sh |= 0x7C00; + } + else + { + sh |= (e << 10) | (m >> 13); + } + } +} + +half :: operator float() const +{ + union + { + unsigned int s; + float result; + }; + + s = (sh & 0x8000) << 16; + unsigned int e = (sh >> 10) & 0x1F; + unsigned int m = sh & 0x03FF; + + if( e == 0 ) + { + // +/- 0 + if( m == 0 ) return result; + + // Denorm + while(( m & 0x0400 ) == 0 ) + { + m += m; + e--; + } + e++; + m &= ~0x0400; + } + else if( e == 31 ) + { + // INF / NAN + s |= 0x7F800000 | (m << 13); + return result; + } + + s |= ((e + 112) << 23) | (m << 13); + + return result; +} + +/* +================= +SinCos +================= +*/ +void SinCos( float radians, float *sine, float *cosine ) +{ + _asm + { + fld dword ptr [radians] + fsincos + + mov edx, dword ptr [cosine] + mov eax, dword ptr [sine] + + fstp dword ptr [edx] + fstp dword ptr [eax] + } +} + +/* +============== +ColorNormalize + +============== +*/ +float ColorNormalize( const vec3_t in, vec3_t out ) +{ + float scale; + + if(( scale = VectorMax( in )) == 0 ) + return 0; + + VectorScale( in, (1.0 / scale), out ); + + return scale; +} + +/* +============== +FloatToHalf + +============== +*/ +unsigned short FloatToHalf( float v ) +{ + unsigned int i = *((unsigned int *)&v); + unsigned int e = (i >> 23) & 0x00ff; + unsigned int m = i & 0x007fffff; + unsigned short h; + + if( e <= 127 - 15 ) + h = ((m | 0x00800000) >> (127 - 14 - e)) >> 13; + else h = (i >> 13) & 0x3fff; + + h |= (i >> 16) & 0xc000; + + return h; +} + +/* +============== +HalfToFloat + +============== +*/ +float HalfToFloat( unsigned short h ) +{ + unsigned int f = (h << 16) & 0x80000000; + unsigned int em = h & 0x7fff; + + if( em > 0x03ff ) + { + f |= (em << 13) + ((127 - 15) << 23); + } + else + { + unsigned int m = em & 0x03ff; + + if( m != 0 ) + { + unsigned int e = (em >> 10) & 0x1f; + + while(( m & 0x0400 ) == 0 ) + { + m <<= 1; + e--; + } + + m &= 0x3ff; + f |= ((e + (127 - 14)) << 23) | (m << 13); + } + } + + return *((float *)&f); +} + +/* +============== +VectorCompareEpsilon + +============== +*/ +bool VectorCompareEpsilon( const vec3_t vec1, const vec3_t vec2, vec_t epsilon ) +{ + vec_t ax, ay, az; + + ax = fabs( vec1[0] - vec2[0] ); + ay = fabs( vec1[1] - vec2[1] ); + az = fabs( vec1[2] - vec2[2] ); + + if(( ax <= epsilon ) && ( ay <= epsilon ) && ( az <= epsilon )) + return true; + return false; +} + +bool VectorCompareEpsilon2( const vec3_t vec1, const float vec2[3], vec_t epsilon ) +{ + vec_t ax, ay, az; + + ax = fabs( vec1[0] - vec2[0] ); + ay = fabs( vec1[1] - vec2[1] ); + az = fabs( vec1[2] - vec2[2] ); + + if(( ax <= epsilon ) && ( ay <= epsilon ) && ( az <= epsilon )) + return true; + return false; +} + +/* +================== +VertexHashKey +================== +*/ +uint VertexHashKey( const vec3_t point, uint hashSize ) +{ + uint hashKey = 0; + + hashKey ^= int( fabs( point[0] )); + hashKey ^= int( fabs( point[1] )); + hashKey ^= int( fabs( point[2] )); + + hashKey &= (hashSize - 1); + + return hashKey; +} + +/* +================ +VectorIsOnAxis +================ +*/ +bool VectorIsOnAxis( const vec3_t v ) +{ + int count = 0; + + for( int i = 0; i < 3; i++ ) + { + if( v[i] == 0.0 ) + count++; + } + + // the zero vector will be on axis. + return (count > 1) ? true : false; +} + +vec_t VectorNormalizeLength2( const vec3_t v, vec3_t out ) +{ + vec_t length, ilength; + + length = v[0] * v[0] + v[1] * v[1] + v[2] * v[2]; + length = sqrt( length ); + + if( length ) + { + ilength = 1.0 / length; + out[0] = v[0] * ilength; + out[1] = v[1] * ilength; + out[2] = v[2] * ilength; + } + + return length; +} + +void VectorVectors( const vec3_t forward, vec3_t right, vec3_t up ) +{ + VectorSet( right, forward[2], -forward[0], forward[1] ); + vec_t d = DotProduct( forward, right ); + VectorMA( right, -d, forward, right ); + VectorNormalize( right ); + CrossProduct( right, forward, up ); + VectorNormalize( up ); +} + +/* +================= +PlaneTypeForNormal +================= +*/ +int PlaneTypeForNormal( const vec3_t normal ) +{ + if( normal[0] == 1 ) + return PLANE_X; + if( normal[1] == 1 ) + return PLANE_Y; + if( normal[2] == 1 ) + return PLANE_Z; + return PLANE_NONAXIAL; +} + +/* +================= +SignbitsForPlane + +fast box on planeside test +================= +*/ +int SignbitsForPlane( const vec3_t normal ) +{ + for( int bits = 0, i = 0; i < 3; i++ ) + if( normal[i] < 0.0f ) + bits |= 1< maxs[i] ) maxs[i] = val; + } +} + +/* +================= +ExpandBounds +================= +*/ +void ExpandBounds( vec3_t mins, vec3_t maxs, vec_t offset ) +{ + mins[0] -= offset; + mins[1] -= offset; + mins[2] -= offset; + maxs[0] += offset; + maxs[1] += offset; + maxs[2] += offset; +} + +/* +================= +RadiusFromBounds +================= +*/ +vec_t RadiusFromBounds( const vec3_t mins, const vec3_t maxs ) +{ + vec3_t corner; + int i; + + for( i = 0; i < 3; i++ ) + { + corner[i] = fabs( mins[i] ) > fabs( maxs[i] ) ? fabs( mins[i] ) : fabs( maxs[i] ); + } + return VectorLength( corner ); +} + +/* +================= +BoundsIsCleared +================= +*/ +bool BoundsIsCleared( const vec3_t mins, const vec3_t maxs ) +{ + if( mins[0] <= maxs[0] || mins[1] <= maxs[1] || mins[2] <= maxs[2] ) + return false; + return true; +} + +/* +================= +BoundsIntersect +================= +*/ +bool BoundsIntersect( const vec3_t mins1, const vec3_t maxs1, const vec3_t mins2, const vec3_t maxs2, vec_t epsilon ) +{ + if( mins1[0] > maxs2[0] + epsilon || mins1[1] > maxs2[1] + epsilon || mins1[2] > maxs2[2] + epsilon ) + return false; + if( maxs1[0] < mins2[0] - epsilon || maxs1[1] < mins2[1] - epsilon || maxs1[2] < mins2[2] - epsilon ) + return false; + return true; +} + +/* +================= +BoundsAndSphereIntersect +================= +*/ +bool BoundsAndSphereIntersect( const vec3_t mins, const vec3_t maxs, const vec3_t origin, vec_t radius ) +{ + if( mins[0] > origin[0] + radius || mins[1] > origin[1] + radius || mins[2] > origin[2] + radius ) + return false; + if( maxs[0] < origin[0] - radius || maxs[1] < origin[1] - radius || maxs[2] < origin[2] - radius ) + return false; + return true; +} + +/* +================= +SphereIntersect +================= +*/ +bool SphereIntersect( const vec3_t vSphereCenter, vec_t fSphereRadiusSquared, const vec3_t vLinePt, const vec3_t vLineDir ) +{ + vec_t a, b, c, insideSqr; + vec3_t p; + + // translate sphere to origin. + VectorSubtract( vLinePt, vSphereCenter, p ); + + a = DotProduct( vLineDir, vLineDir ); + b = 2.0 * DotProduct( p, vLineDir ); + c = DotProduct( p, p ) - fSphereRadiusSquared; + + insideSqr = b * b - 4.0 * a * c; + if( insideSqr <= 0.000001 ) + return false; + return true; +} + +void CalcST( const vec3_t p0, const vec3_t p1, const vec3_t p2, const vec2_t t0, const vec2_t t1, const vec2_t t2, vec3_t s, vec3_t t, bool areaweight ) +{ + vec3_t edge01, edge02, cross; + + // Compute the partial derivatives of X, Y, and Z with respect to S and T. + VectorClear( s ); + VectorClear( t ); + + // x, s, t + VectorSet( edge01, p1[0] - p0[0], t1[0] - t0[0], t1[1] - t0[1] ); + VectorSet( edge02, p2[0] - p0[0], t2[0] - t0[0], t2[1] - t0[1] ); + CrossProduct( edge01, edge02, cross ); + + if( fabs( cross[0] ) > SMALL_FLOAT ) + { + s[0] += -cross[1] / cross[0]; + t[0] += -cross[2] / cross[0]; + } + + // y, s, t + VectorSet( edge01, p1[1] - p0[1], t1[0] - t0[0], t1[1] - t0[1] ); + VectorSet( edge02, p2[1] - p0[1], t2[0] - t0[0], t2[1] - t0[1] ); + CrossProduct( edge01, edge02, cross ); + + if( fabs( cross[0] ) > SMALL_FLOAT ) + { + s[1] += -cross[1] / cross[0]; + t[1] += -cross[2] / cross[0]; + } + + // z, s, t + VectorSet( edge01, p1[2] - p0[2], t1[0] - t0[0], t1[1] - t0[1] ); + VectorSet( edge02, p2[2] - p0[2], t2[0] - t0[0], t2[1] - t0[1] ); + CrossProduct( edge01, edge02, cross ); + + if( fabs( cross[0] ) > SMALL_FLOAT ) + { + s[2] += -cross[1] / cross[0]; + t[2] += -cross[2] / cross[0]; + } + + if( !areaweight ) + { + // Normalize s and t + VectorNormalize2( s ); + VectorNormalize2( t ); + } +} + +/* +============= +COM_NormalizeAngles + +============= +*/ +void COM_NormalizeAngles( vec3_t angles ) +{ + for( int i = 0; i < 3; i++ ) + { + if( angles[i] > 180.0f ) + angles[i] -= 360.0f; + else if( angles[i] < -180.0f ) + angles[i] += 360.0f; + } +} + +/* +==================== +AngleQuaternion +==================== +*/ +void AngleQuaternion( const vec3_t angles, vec4_t quat ) +{ + float sr, sp, sy, cr, cp, cy; + + SinCos( angles[ROLL] * 0.5f, &sy, &cy ); + SinCos( angles[YAW] * 0.5f, &sp, &cp ); + SinCos( angles[PITCH] * 0.5f, &sr, &cr ); + + quat[0] = sr * cp * cy - cr * sp * sy; // X + quat[1] = cr * sp * cy + sr * cp * sy; // Y + quat[2] = cr * cp * sy - sr * sp * cy; // Z + quat[3] = cr * cp * cy + sr * sp * sy; // W +} + +/* +==================== +AngleMatrix +==================== +*/ +void Matrix3x4_CreateFromEntityScale3f( matrix3x4 out, const vec3_t angles, const vec3_t origin, const vec3_t scale ) +{ + float sr, sp, sy, cr, cp, cy; + + SinCos( DEG2RAD( angles[YAW] ), &sy, &cy ); + SinCos( DEG2RAD( angles[PITCH] ), &sp, &cp ); + SinCos( DEG2RAD( angles[ROLL] ), &sr, &cr ); + + out[0][0] = (cp*cy) * scale[0]; + out[1][0] = (sr*sp*cy+cr*-sy) * scale[1]; + out[2][0] = (cr*sp*cy+-sr*-sy) * scale[2]; + out[3][0] = origin[0]; + out[0][1] = (cp*sy) * scale[0]; + out[1][1] = (sr*sp*sy+cr*cy) * scale[1]; + out[2][1] = (cr*sp*sy+-sr*cy) * scale[2]; + out[3][1] = origin[1]; + out[0][2] = (-sp) * scale[0]; + out[1][2] = (sr*cp) * scale[1]; + out[2][2] = (cr*cp) * scale[2]; + out[3][2] = origin[2]; +} + +/* +================ +Matrix3x4_MatrixToEntityScale3f +================ +*/ +void Matrix3x4_MatrixToEntityScale3f( const matrix3x4 in, vec3_t origin, vec3_t angles, vec3_t scale ) +{ + float xyDist = sqrt( in[0][0] * in[0][0] + in[0][1] * in[0][1] ); + + if( xyDist > 0.001f ) + { + // enough here to get angles? + angles[0] = RAD2DEG( atan2( -in[0][2], xyDist )); + angles[1] = RAD2DEG( atan2( in[0][1], in[0][0] )); + angles[2] = RAD2DEG( atan2( in[1][2], in[2][2] )); + } + else + { + // forward is mostly Z, gimbal lock + angles[0] = RAD2DEG( atan2( -in[0][2], xyDist )); + angles[1] = RAD2DEG( atan2( -in[1][0], in[1][1] )); + angles[2] = 0.0f; + } + + origin[0] = in[3][0]; + origin[1] = in[3][1]; + origin[2] = in[3][2]; + + scale[0] = VectorLength( in[0] ); + scale[1] = VectorLength( in[1] ); + scale[2] = VectorLength( in[2] ); +} + +/* +================ +QuaternionMatrix +================ +*/ +void Matrix3x4_FromOriginQuat( matrix3x4 out, const vec4_t quaternion, const vec3_t origin ) +{ + out[0][0] = 1.0f - 2.0f * quaternion[1] * quaternion[1] - 2.0f * quaternion[2] * quaternion[2]; + out[0][1] = 2.0f * quaternion[0] * quaternion[1] + 2.0f * quaternion[3] * quaternion[2]; + out[0][2] = 2.0f * quaternion[0] * quaternion[2] - 2.0f * quaternion[3] * quaternion[1]; + + out[1][0] = 2.0f * quaternion[0] * quaternion[1] - 2.0f * quaternion[3] * quaternion[2]; + out[1][1] = 1.0f - 2.0f * quaternion[0] * quaternion[0] - 2.0f * quaternion[2] * quaternion[2]; + out[1][2] = 2.0f * quaternion[1] * quaternion[2] + 2.0f * quaternion[3] * quaternion[0]; + + out[2][0] = 2.0f * quaternion[0] * quaternion[2] + 2.0f * quaternion[3] * quaternion[1]; + out[2][1] = 2.0f * quaternion[1] * quaternion[2] - 2.0f * quaternion[3] * quaternion[0]; + out[2][2] = 1.0f - 2.0f * quaternion[0] * quaternion[0] - 2.0f * quaternion[1] * quaternion[1]; + + out[3][0] = origin[0]; + out[3][1] = origin[1]; + out[3][2] = origin[2]; +} + +/* +================ +ConcatTransforms +================ +*/ +void Matrix3x4_ConcatTransforms( matrix3x4 out, const matrix3x4 in1, const matrix3x4 in2 ) +{ + out[0][0] = in1[0][0] * in2[0][0] + in1[1][0] * in2[0][1] + in1[2][0] * in2[0][2]; + out[1][0] = in1[0][0] * in2[1][0] + in1[1][0] * in2[1][1] + in1[2][0] * in2[1][2]; + out[2][0] = in1[0][0] * in2[2][0] + in1[1][0] * in2[2][1] + in1[2][0] * in2[2][2]; + out[3][0] = in1[0][0] * in2[3][0] + in1[1][0] * in2[3][1] + in1[2][0] * in2[3][2] + in1[3][0]; + out[0][1] = in1[0][1] * in2[0][0] + in1[1][1] * in2[0][1] + in1[2][1] * in2[0][2]; + out[1][1] = in1[0][1] * in2[1][0] + in1[1][1] * in2[1][1] + in1[2][1] * in2[1][2]; + out[2][1] = in1[0][1] * in2[2][0] + in1[1][1] * in2[2][1] + in1[2][1] * in2[2][2]; + out[3][1] = in1[0][1] * in2[3][0] + in1[1][1] * in2[3][1] + in1[2][1] * in2[3][2] + in1[3][1]; + out[0][2] = in1[0][2] * in2[0][0] + in1[1][2] * in2[0][1] + in1[2][2] * in2[0][2]; + out[1][2] = in1[0][2] * in2[1][0] + in1[1][2] * in2[1][1] + in1[2][2] * in2[1][2]; + out[2][2] = in1[0][2] * in2[2][0] + in1[1][2] * in2[2][1] + in1[2][2] * in2[2][2]; + out[3][2] = in1[0][2] * in2[3][0] + in1[1][2] * in2[3][1] + in1[2][2] * in2[3][2] + in1[3][2]; +} + +/* +==================== +TransformStandardPlane +==================== +*/ +void Matrix3x4_TransformStandardPlane( const matrix3x4 in, const vec3_t normal, float d, vec3_t out, float *dist ) +{ +#if 0 + float scale = sqrt( in[0][0] * in[0][0] + in[0][1] * in[0][1] + in[0][2] * in[0][2] ); + float iscale = 1.0f / scale; + + out[0] = (normal[0] * in[0][0] + normal[1] * in[1][0] + normal[2] * in[2][0]) * iscale; + out[1] = (normal[0] * in[0][1] + normal[1] * in[1][1] + normal[2] * in[2][1]) * iscale; + out[2] = (normal[0] * in[0][2] + normal[1] * in[1][2] + normal[2] * in[2][2]) * iscale; + *dist = d * scale + ( out[0] * in[3][0] + out[1] * in[3][1] + out[2] * in[3][2] ); +#else + // g-cont. because matrix is already inverted + out[0] = (normal[0] * in[0][0] + normal[1] * in[1][0] + normal[2] * in[2][0]); + out[1] = (normal[0] * in[0][1] + normal[1] * in[1][1] + normal[2] * in[2][1]); + out[2] = (normal[0] * in[0][2] + normal[1] * in[1][2] + normal[2] * in[2][2]); + *dist = -((normal[0] * in[3][0] + normal[1] * in[3][1] + normal[2] * in[3][2]) - d); +#endif +} + +/* +==================== +VectorTransform +==================== +*/ +void Matrix3x4_VectorTransform( const matrix3x4 in, const vec3_t v, vec3_t out ) +{ + vec3_t iv; + + // in case v == out + iv[0] = v[0]; + iv[1] = v[1]; + iv[2] = v[2]; + + out[0] = iv[0] * in[0][0] + iv[1] * in[1][0] + iv[2] * in[2][0] + in[3][0]; + out[1] = iv[0] * in[0][1] + iv[1] * in[1][1] + iv[2] * in[2][1] + in[3][1]; + out[2] = iv[0] * in[0][2] + iv[1] * in[1][2] + iv[2] * in[2][2] + in[3][2]; +} + +/* +==================== +VectorTransform +==================== +*/ +void Matrix3x4_Vector4Transform( const matrix3x4 in, const vec4_t v, vec4_t out ) +{ + vec4_t iv; + + // in case v == out + iv[0] = v[0]; + iv[1] = v[1]; + iv[2] = v[2]; + iv[3] = v[3]; + + out[0] = iv[0] * in[0][0] + iv[1] * in[1][0] + iv[2] * in[2][0] + iv[3] * in[3][0]; + out[1] = iv[0] * in[0][1] + iv[1] * in[1][1] + iv[2] * in[2][1] + iv[3] * in[3][1]; + out[2] = iv[0] * in[0][2] + iv[1] * in[1][2] + iv[2] * in[2][2] + iv[3] * in[3][2]; +} + +/* +==================== +VectorITransform +==================== +*/ +void Matrix3x4_VectorITransform( const matrix3x4 in, const vec3_t v, vec3_t out ) +{ + vec3_t iv; + + iv[0] = v[0] - in[3][0]; + iv[1] = v[1] - in[3][1]; + iv[2] = v[2] - in[3][2]; + + out[0] = iv[0] * in[0][0] + iv[1] * in[0][1] + iv[2] * in[0][2]; + out[1] = iv[0] * in[1][0] + iv[1] * in[1][1] + iv[2] * in[1][2]; + out[2] = iv[0] * in[2][0] + iv[1] * in[2][1] + iv[2] * in[2][2]; +} + +/* +==================== +VectorRotate +==================== +*/ +void Matrix3x4_VectorRotate( const matrix3x4 in, const vec3_t v, vec3_t out ) +{ + vec3_t iv; + + // in case v == out + iv[0] = v[0]; + iv[1] = v[1]; + iv[2] = v[2]; + + out[0] = iv[0] * in[0][0] + iv[1] * in[1][0] + iv[2] * in[2][0]; + out[1] = iv[0] * in[0][1] + iv[1] * in[1][1] + iv[2] * in[2][1]; + out[2] = iv[0] * in[0][2] + iv[1] * in[1][2] + iv[2] * in[2][2]; +} + +/* +==================== +VectorIRotate +==================== +*/ +void Matrix3x4_VectorIRotate( const matrix3x4 in, const vec3_t v, vec3_t out ) +{ + vec3_t iv; + + // in case v == out + iv[0] = v[0]; + iv[1] = v[1]; + iv[2] = v[2]; + + out[0] = iv[0] * in[0][0] + iv[1] * in[0][1] + iv[2] * in[0][2]; + out[1] = iv[0] * in[1][0] + iv[1] * in[1][1] + iv[2] * in[1][2]; + out[2] = iv[0] * in[2][0] + iv[1] * in[2][1] + iv[2] * in[2][2]; +} + +/* +==================== +CalcSign +==================== +*/ +vec_t Matrix3x4_CalcSign( const matrix3x4 in ) +{ + vec3_t out; + + out[0] = in[0][1] * in[1][2] - in[0][2] * in[1][1]; + out[1] = in[0][2] * in[1][0] - in[0][0] * in[1][2]; + out[2] = in[0][0] * in[1][1] - in[0][1] * in[1][0]; + + return ( out[0] * in[2][0] + out[1] * in[2][1] + out[2] * in[2][2] ); +} + +/* +==================== +Invert_Full +==================== +*/ +bool Matrix3x4_Invert_Full( matrix3x4 out, const matrix3x4 in ) +{ + double texplanes[2][4]; + double faceplane[4]; + double texaxis[2][3]; + double normalaxis[3]; + double det, sqrlen1, sqrlen2, sqrlen3; + double dot_epsilon = NORMAL_EPSILON * NORMAL_EPSILON; + double texorg[3]; + + for( int i = 0; i < 4; i++ ) + { + texplanes[0][i] = in[i][0]; + texplanes[1][i] = in[i][1]; + faceplane[i] = in[i][2]; + } + + sqrlen1 = DotProduct( texplanes[0], texplanes[0] ); + sqrlen2 = DotProduct( texplanes[1], texplanes[1] ); + sqrlen3 = DotProduct( faceplane, faceplane ); + + if( sqrlen1 <= dot_epsilon || sqrlen2 <= dot_epsilon || sqrlen3 <= dot_epsilon ) + { + // s gradient, t gradient or face normal is too close to 0 + return false; + } + + CrossProduct( texplanes[0], texplanes[1], normalaxis ); + det = DotProduct( normalaxis, faceplane ); + + if( det * det <= sqrlen1 * sqrlen2 * sqrlen3 * dot_epsilon ) + { + // s gradient, t gradient and face normal are coplanar + return false; + } + + VectorScale( normalaxis, 1.0 / det, normalaxis ); + CrossProduct( texplanes[1], faceplane, texaxis[0] ); + VectorScale( texaxis[0], 1.0 / det, texaxis[0] ); + + CrossProduct( faceplane, texplanes[0], texaxis[1] ); + VectorScale( texaxis[1], 1.0 / det, texaxis[1] ); + + VectorScale( normalaxis, -faceplane[3], texorg ); + VectorMA( texorg, -texplanes[0][3], texaxis[0], texorg ); + VectorMA( texorg, -texplanes[1][3], texaxis[1], texorg ); + + VectorCopy( texaxis[0], out[0] ); + VectorCopy( texaxis[1], out[1] ); + VectorCopy( normalaxis, out[2] ); + VectorCopy( texorg, out[3] ); + + return true; +} + +const matrix4x4 matrix4x4_identity = +{ +{ 1.0f, 0.0f, 0.0f, 0.0f }, // PITCH +{ 0.0f, 1.0f, 0.0f, 0.0f }, // YAW +{ 0.0f, 0.0f, 1.0f, 0.0f }, // ROLL +{ 0.0f, 0.0f, 0.0f, 1.0f }, // ORIGIN +}; + +/* +======================================================================== + + Matrix4x4 operations + +======================================================================== +*/ +void Matrix4x4_Concat( matrix4x4 out, const matrix4x4 in1, const matrix4x4 in2 ) +{ + out[0][0] = in1[0][0] * in2[0][0] + in1[1][0] * in2[0][1] + in1[2][0] * in2[0][2] + in1[3][0] * in2[0][3]; + out[1][0] = in1[0][0] * in2[1][0] + in1[1][0] * in2[1][1] + in1[2][0] * in2[1][2] + in1[3][0] * in2[1][3]; + out[2][0] = in1[0][0] * in2[2][0] + in1[1][0] * in2[2][1] + in1[2][0] * in2[2][2] + in1[3][0] * in2[2][3]; + out[3][0] = in1[0][0] * in2[3][0] + in1[1][0] * in2[3][1] + in1[2][0] * in2[3][2] + in1[3][0] * in2[3][3]; + out[0][1] = in1[0][1] * in2[0][0] + in1[1][1] * in2[0][1] + in1[2][1] * in2[0][2] + in1[3][1] * in2[0][3]; + out[1][1] = in1[0][1] * in2[1][0] + in1[1][1] * in2[1][1] + in1[2][1] * in2[1][2] + in1[3][1] * in2[1][3]; + out[2][1] = in1[0][1] * in2[2][0] + in1[1][1] * in2[2][1] + in1[2][1] * in2[2][2] + in1[3][1] * in2[2][3]; + out[3][1] = in1[0][1] * in2[3][0] + in1[1][1] * in2[3][1] + in1[2][1] * in2[3][2] + in1[3][1] * in2[3][3]; + out[0][2] = in1[0][2] * in2[0][0] + in1[1][2] * in2[0][1] + in1[2][2] * in2[0][2] + in1[3][2] * in2[0][3]; + out[1][2] = in1[0][2] * in2[1][0] + in1[1][2] * in2[1][1] + in1[2][2] * in2[1][2] + in1[3][2] * in2[1][3]; + out[2][2] = in1[0][2] * in2[2][0] + in1[1][2] * in2[2][1] + in1[2][2] * in2[2][2] + in1[3][2] * in2[2][3]; + out[3][2] = in1[0][2] * in2[3][0] + in1[1][2] * in2[3][1] + in1[2][2] * in2[3][2] + in1[3][2] * in2[3][3]; + out[0][3] = in1[0][3] * in2[0][0] + in1[1][3] * in2[0][1] + in1[2][3] * in2[0][2] + in1[3][3] * in2[0][3]; + out[1][3] = in1[0][3] * in2[1][0] + in1[1][3] * in2[1][1] + in1[2][3] * in2[1][2] + in1[3][3] * in2[1][3]; + out[2][3] = in1[0][3] * in2[2][0] + in1[1][3] * in2[2][1] + in1[2][3] * in2[2][2] + in1[3][3] * in2[2][3]; + out[3][3] = in1[0][3] * in2[3][0] + in1[1][3] * in2[3][1] + in1[2][3] * in2[3][2] + in1[3][3] * in2[3][3]; +} + +bool Matrix4x4_Invert_Full( matrix4x4 out, const matrix4x4 in1 ) +{ + float *temp; + float *r[4]; + float rtemp[4][8]; + float m[4]; + float s; + + r[0] = rtemp[0]; + r[1] = rtemp[1]; + r[2] = rtemp[2]; + r[3] = rtemp[3]; + + r[0][0] = in1[0][0]; + r[0][1] = in1[1][0]; + r[0][2] = in1[2][0]; + r[0][3] = in1[3][0]; + r[0][4] = 1.0f; + r[0][5] = 0.0f; + r[0][6] = 0.0f; + r[0][7] = 0.0f; + + r[1][0] = in1[0][1]; + r[1][1] = in1[1][1]; + r[1][2] = in1[2][1]; + r[1][3] = in1[3][1]; + r[1][5] = 1.0f; + r[1][4] = 0.0f; + r[1][6] = 0.0f; + r[1][7] = 0.0f; + + r[2][0] = in1[0][2]; + r[2][1] = in1[1][2]; + r[2][2] = in1[2][2]; + r[2][3] = in1[3][2]; + r[2][6] = 1.0f; + r[2][4] = 0.0f; + r[2][5] = 0.0f; + r[2][7] = 0.0f; + + r[3][0] = in1[0][3]; + r[3][1] = in1[1][3]; + r[3][2] = in1[2][3]; + r[3][3] = in1[3][3]; + r[3][4] = 0.0f; + r[3][5] = 0.0f; + r[3][6] = 0.0f; + r[3][7] = 1.0f; + + if( fabs( r[3][0] ) > fabs( r[2][0] )) + { + temp = r[3]; + r[3] = r[2]; + r[2] = temp; + } + + if( fabs( r[2][0] ) > fabs( r[1][0] )) + { + temp = r[2]; + r[2] = r[1]; + r[1] = temp; + } + + if( fabs( r[1][0] ) > fabs( r[0][0] )) + { + temp = r[1]; + r[1] = r[0]; + r[0] = temp; + } + + if( r[0][0] ) + { + m[1] = r[1][0] / r[0][0]; + m[2] = r[2][0] / r[0][0]; + m[3] = r[3][0] / r[0][0]; + + s = r[0][1]; + r[1][1] -= m[1] * s; + r[2][1] -= m[2] * s; + r[3][1] -= m[3] * s; + + s = r[0][2]; + r[1][2] -= m[1] * s; + r[2][2] -= m[2] * s; + r[3][2] -= m[3] * s; + + s = r[0][3]; + r[1][3] -= m[1] * s; + r[2][3] -= m[2] * s; + r[3][3] -= m[3] * s; + + s = r[0][4]; + if( s ) + { + r[1][4] -= m[1] * s; + r[2][4] -= m[2] * s; + r[3][4] -= m[3] * s; + } + + s = r[0][5]; + if( s ) + { + r[1][5] -= m[1] * s; + r[2][5] -= m[2] * s; + r[3][5] -= m[3] * s; + } + + s = r[0][6]; + if( s ) + { + r[1][6] -= m[1] * s; + r[2][6] -= m[2] * s; + r[3][6] -= m[3] * s; + } + + s = r[0][7]; + if( s ) + { + r[1][7] -= m[1] * s; + r[2][7] -= m[2] * s; + r[3][7] -= m[3] * s; + } + + if( fabs( r[3][1] ) > fabs( r[2][1] )) + { + temp = r[3]; + r[3] = r[2]; + r[2] = temp; + } + + if( fabs( r[2][1] ) > fabs( r[1][1] )) + { + temp = r[2]; + r[2] = r[1]; + r[1] = temp; + } + + if( r[1][1] ) + { + m[2] = r[2][1] / r[1][1]; + m[3] = r[3][1] / r[1][1]; + r[2][2] -= m[2] * r[1][2]; + r[3][2] -= m[3] * r[1][2]; + r[2][3] -= m[2] * r[1][3]; + r[3][3] -= m[3] * r[1][3]; + + s = r[1][4]; + if( s ) + { + r[2][4] -= m[2] * s; + r[3][4] -= m[3] * s; + } + + s = r[1][5]; + if( s ) + { + r[2][5] -= m[2] * s; + r[3][5] -= m[3] * s; + } + + s = r[1][6]; + if( s ) + { + r[2][6] -= m[2] * s; + r[3][6] -= m[3] * s; + } + + s = r[1][7]; + if( s ) + { + r[2][7] -= m[2] * s; + r[3][7] -= m[3] * s; + } + + if( fabs( r[3][2] ) > fabs( r[2][2] )) + { + temp = r[3]; + r[3] = r[2]; + r[2] = temp; + } + + if( r[2][2] ) + { + m[3] = r[3][2] / r[2][2]; + r[3][3] -= m[3] * r[2][3]; + r[3][4] -= m[3] * r[2][4]; + r[3][5] -= m[3] * r[2][5]; + r[3][6] -= m[3] * r[2][6]; + r[3][7] -= m[3] * r[2][7]; + + if( r[3][3] ) + { + s = 1.0f / r[3][3]; + r[3][4] *= s; + r[3][5] *= s; + r[3][6] *= s; + r[3][7] *= s; + + m[2] = r[2][3]; + s = 1.0f / r[2][2]; + r[2][4] = s * (r[2][4] - r[3][4] * m[2]); + r[2][5] = s * (r[2][5] - r[3][5] * m[2]); + r[2][6] = s * (r[2][6] - r[3][6] * m[2]); + r[2][7] = s * (r[2][7] - r[3][7] * m[2]); + + m[1] = r[1][3]; + r[1][4] -= r[3][4] * m[1]; + r[1][5] -= r[3][5] * m[1]; + r[1][6] -= r[3][6] * m[1]; + r[1][7] -= r[3][7] * m[1]; + + m[0] = r[0][3]; + r[0][4] -= r[3][4] * m[0]; + r[0][5] -= r[3][5] * m[0]; + r[0][6] -= r[3][6] * m[0]; + r[0][7] -= r[3][7] * m[0]; + + m[1] = r[1][2]; + s = 1.0f / r[1][1]; + r[1][4] = s * (r[1][4] - r[2][4] * m[1]); + r[1][5] = s * (r[1][5] - r[2][5] * m[1]); + r[1][6] = s * (r[1][6] - r[2][6] * m[1]); + r[1][7] = s * (r[1][7] - r[2][7] * m[1]); + + m[0] = r[0][2]; + r[0][4] -= r[2][4] * m[0]; + r[0][5] -= r[2][5] * m[0]; + r[0][6] -= r[2][6] * m[0]; + r[0][7] -= r[2][7] * m[0]; + + m[0] = r[0][1]; + s = 1.0f / r[0][0]; + r[0][4] = s * (r[0][4] - r[1][4] * m[0]); + r[0][5] = s * (r[0][5] - r[1][5] * m[0]); + r[0][6] = s * (r[0][6] - r[1][6] * m[0]); + r[0][7] = s * (r[0][7] - r[1][7] * m[0]); + + out[0][0] = r[0][4]; + out[0][1] = r[1][4]; + out[0][2] = r[2][4]; + out[0][3] = r[3][4]; + out[1][0] = r[0][5]; + out[1][1] = r[1][5]; + out[1][2] = r[2][5]; + out[1][3] = r[3][5]; + out[2][0] = r[0][6]; + out[2][1] = r[1][6]; + out[2][2] = r[2][6]; + out[2][3] = r[3][6]; + out[3][0] = r[0][7]; + out[3][1] = r[1][7]; + out[3][2] = r[2][7]; + out[3][3] = r[3][7]; + + return true; + } + } + } + } + + return false; +} \ No newline at end of file diff --git a/utils/common/mathlib.h b/utils/common/mathlib.h new file mode 100644 index 0000000..8651c44 --- /dev/null +++ b/utils/common/mathlib.h @@ -0,0 +1,290 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#ifndef __MATHLIB__ +#define __MATHLIB__ + +// mathlib.h + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef DOUBLEVEC_T +typedef double vec_t; +#else +typedef float vec_t; +#endif + +struct half +{ + unsigned short sh; + + half( ){}; + half( const float x ); + operator float () const; +}; + +typedef vec_t vec2_t[2]; // x,y +typedef vec_t vec3_t[3]; // x,y,z +typedef vec_t vec4_t[4]; // x,y,z,w +typedef vec_t vec5_t[5]; // rare case +typedef vec_t matrix3x4[4][3]; // rad matrix +typedef vec_t matrix4x4[4][4]; // rad matrix + +typedef byte bvec3_t[3]; // x,y,z +typedef short svec3_t[3]; +typedef half hvec3_t[3]; + +// euler angle order +#define PITCH 0 +#define YAW 1 +#define ROLL 2 + +#ifndef M_PI +#define M_PI (vec_t)3.14159265358979323846 +#endif + +#ifndef M_PI2 +#define M_PI2 (vec_t)6.28318530717958647692 +#endif + +#define M_PI_F ((vec_t)(M_PI)) +#define M_PI2_F ((vec_t)(M_PI2)) + +#define RAD2DEG( x ) ((vec_t)(x) * (vec_t)(180 / M_PI)) +#define DEG2RAD( x ) ((vec_t)(x) * (vec_t)(M_PI / 180)) + +#define MINSPLIT_EPSILON 0.002 +#define MAXSPLIT_EPSILON 0.2 + +#define SMALL_FLOAT 1e-12 + +#define SIDE_FRONT 0 +#define SIDE_BACK 1 +#define SIDE_ON 2 +#define SIDE_CROSS -2 + +#define PLANE_X 0 // 0 - 2 are axial planes +#define PLANE_Y 1 // 3 needs alternate calc +#define PLANE_Z 2 +#define PLANE_ANY_X 3 +#define PLANE_ANY_Y 4 +#define PLANE_ANY_Z 5 +#define PLANE_LAST_AXIAL PLANE_Z +#define PLANE_NONAXIAL 3 + +#define Q_min( a, b ) (((a) < (b)) ? (a) : (b)) +#define Q_max( a, b ) (((a) > (b)) ? (a) : (b)) +#define Q_recip( a ) ((vec_t)(1.0 / (vec_t)(a))) +#define Q_floor( a ) ((vec_t)(long)(a)) +#define Q_ceil( a ) ((vec_t)(long)((a) + 1)) +#define Q_round( a ) (floor(( a ) + 0.5)) +#define Q_rounddn( x, y ) (floor( x / y + 0.5 ) * y ) +#define Q_roundup( x, y ) (ceil( x / y ) * y ) + +#define Q_rint(x) ((x) < 0 ? ((int)((x) - 0.5)) : ((int)((x) + 0.5))) +#define IS_NAN(x) (((*(int *)&x) & (255<<23)) == (255<<23)) + +#define AxisFromNormal( n ) ( fabs( n[0] ) < NORMAL_EPSILON ) + ( fabs( n[1] ) < NORMAL_EPSILON ) + ( fabs( n[2] ) < NORMAL_EPSILON ) +#define ARRAYSIZE( p ) (sizeof( p ) / sizeof( p[0] )) + +// +// Vector operations +// +#define Vector2Set(v, x, y) ((v)[0]=(x),(v)[1]=(y)) +#define Vector2Scale(in, scale, out) ((out)[0] = (in)[0] * (scale),(out)[1] = (in)[1] * (scale)) +#define Vector2Copy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1]) +#define VectorIsNAN(v) (IS_NAN(v[0]) || IS_NAN(v[1]) || IS_NAN(v[2])) +#define DotProduct(x,y) ((x)[0]*(y)[0]+(x)[1]*(y)[1]+(x)[2]*(y)[2]) +#define DotProductAbs(x,y) (abs((x)[0]*(y)[0])+abs((x)[1]*(y)[1])+abs((x)[2]*(y)[2])) +#define DotProductFabs(x,y) (fabs((x)[0]*(y)[0])+fabs((x)[1]*(y)[1])+fabs((x)[2]*(y)[2])) +#define CrossProduct(a,b,c) ((c)[0]=(a)[1]*(b)[2]-(a)[2]*(b)[1],(c)[1]=(a)[2]*(b)[0]-(a)[0]*(b)[2],(c)[2]=(a)[0]*(b)[1]-(a)[1]*(b)[0]) +#define Vector2Cross(a,b) ((a)[0]*(b)[1]-(a)[1]*(b)[0]) +#define VectorSubtract(a,b,c) ((c)[0]=(a)[0]-(b)[0],(c)[1]=(a)[1]-(b)[1],(c)[2]=(a)[2]-(b)[2]) +#define VectorAdd(a,b,c) ((c)[0]=(a)[0]+(b)[0],(c)[1]=(a)[1]+(b)[1],(c)[2]=(a)[2]+(b)[2]) +#define VectorAddScalar(a,b,c) ((c)[0]=(a)[0]+(b),(c)[1]=(a)[1]+(b),(c)[2]=(a)[2]+(b)) +#define VectorCopy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2]) +#define VectorScale(in, scale, out) ((out)[0] = (in)[0] * (scale),(out)[1] = (in)[1] * (scale),(out)[2] = (in)[2] * (scale)) +#define VectorCompare(v1,v2) ((v1)[0]==(v2)[0] && (v1)[1]==(v2)[1] && (v1)[2]==(v2)[2]) +#define VectorCompareMin(a,b,c) ((c)[0] = Q_min((a)[0], (b)[0]), (c)[1] = Q_min((a)[1], (b)[1]), (c)[2] = Q_min((a)[2], (b)[2])) +#define VectorCompareMax(a,b,c) ((c)[0] = Q_max((a)[0], (b)[0]), (c)[1] = Q_max((a)[1], (b)[1]), (c)[2] = Q_max((a)[2], (b)[2])) +#define VectorMultiply(a,b,c) ((c)[0]=(a)[0]*(b)[0],(c)[1]=(a)[1]*(b)[1],(c)[2]=(a)[2]*(b)[2]) +#define VectorDivide( in, d, out ) VectorScale( in, (1.0 / (d)), out ) +#define VectorFill( a, b ) ((a)[0] = (b), (a)[1] = (b), (a)[2] = (b)) +#define VectorMaximum(a) (Q_max((a)[0], Q_max((a)[1], (a)[2] ))) +#define VectorMinimum(a) (Q_min((a)[0], Q_min((a)[1], (a)[2] ))) +#define VectorMax(a) ( Q_max((a)[0], Q_max((a)[1], (a)[2])) ) +#define VectorAvg(a) ( ((a)[0] + (a)[1] + (a)[2]) / 3 ) +#define VectorLength(a) ( sqrt( DotProduct( a, a ))) +#define VectorLength2(a) (DotProduct( a, a )) +#define VectorDistance(a, b) (sqrt( VectorDistance2( a, b ))) +#define VectorDistance2(a, b) (((a)[0] - (b)[0]) * ((a)[0] - (b)[0]) + ((a)[1] - (b)[1]) * ((a)[1] - (b)[1]) + ((a)[2] - (b)[2]) * ((a)[2] - (b)[2])) +#define VectorAverage(a,b,o) ((o)[0]=((a)[0]+(b)[0])*0.5,(o)[1]=((a)[1]+(b)[1])*0.5,(o)[2]=((a)[2]+(b)[2])*0.5) +#define VectorSet(v, x, y, z) ((v)[0]=(x),(v)[1]=(y),(v)[2]=(z)) +#define VectorClear(x) ((x)[0]=(x)[1]=(x)[2]=0) +#define VectorNormalize2( v ) { vec_t ilength = sqrt( DotProduct( v, v )); if( ilength ) ilength = 1.0 / ilength; VectorScale( v, ilength, v ); } +#define VectorLerp( v1, lerp, v2, c ) ((c)[0] = (v1)[0] + (lerp) * ((v2)[0] - (v1)[0]), (c)[1] = (v1)[1] + (lerp) * ((v2)[1] - (v1)[1]), (c)[2] = (v1)[2] + (lerp) * ((v2)[2] - (v1)[2])) +#define VectorNegate(x, y) ((y)[0] = -(x)[0], (y)[1] = -(x)[1], (y)[2] = -(x)[2]) +#define VectorRecip(x, y) ((y)[0] = 1.0 / (x)[0], (y)[1] = 1.0 / (x)[1], (y)[2] = 1.0 / (x)[2]) +#define VectorMA(a, scale, b, c) ((c)[0] = (a)[0] + (scale) * (b)[0],(c)[1] = (a)[1] + (scale) * (b)[1],(c)[2] = (a)[2] + (scale) * (b)[2]) +#define VectorMAM( scale1, b1, scale2, b2, c ) ((c)[0] = (scale1) * (b1)[0] + (scale2) * (b2)[0],(c)[1] = (scale1) * (b1)[1] + (scale2) * (b2)[1],(c)[2] = (scale1) * (b1)[2] + (scale2) * (b2)[2]) +#define VectorIsNull( v ) ((v)[0] == 0.0 && (v)[1] == 0.0 && (v)[2] == 0.0) +#define VectorIsFinite( v ) ( _finite( (v)[0] ) && _finite( (v)[1] ) && _finite((v)[2] )) +#define PlaneDist(point,plane) ((plane)->type < 3 ? (point)[(plane)->type] : DotProduct((point), (plane)->normal)) +#define PlaneDiff(point,plane) (((plane)->type < 3 ? (point)[(plane)->type] : DotProduct((point), (plane)->normal)) - (plane)->dist) +#define PlaneDist2(point,plane) (DotProduct((point), (plane)->normal)) +#define PlaneDiff2(point,plane) ((DotProduct((point), (plane)->normal)) - (plane)->dist) +#define bound( min, num, max ) ((num) >= (min) ? ((num) < (max) ? (num) : (max)) : (min)) +#define Vector4Set(v, a, b, c, d) ((v)[0]=(a),(v)[1]=(b),(v)[2]=(c),(v)[3] = (d)) +#define Vector4Copy(a,b) ((b)[0]=(a)[0],(b)[1]=(a)[1],(b)[2]=(a)[2],(b)[3]=(a)[3]) +#define VectorSnap( a, b, c ) (c[0] = Q_rounddn( a[0], b ), c[1] = Q_rounddn( a[1], b ), c[2] = Q_rounddn( a[2], b )) + +// return the smallest power of two >= x. +// returns 0 if x == 0 or x > 0x80000000 (ie numbers that would be negative if x was signed) +// NOTE: the old code took an int, and if you pass in an int of 0x80000000 casted to a uint, +// you'll get 0x80000000, which is correct for uints, instead of 0, which was correct for ints +_forceinline uint SmallestPowerOfTwoGreaterOrEqual( uint x ) +{ + x -= 1; + x |= x >> 1; + x |= x >> 2; + x |= x >> 4; + x |= x >> 8; + x |= x >> 16; + return x + 1; +} + +// Remap a value in the range [A,B] to [C,D]. +inline float RemapVal( float val, float A, float B, float C, float D) +{ + if ( A == B ) + return val >= B ? D : C; + return C + (D - C) * (val - A) / (B - A); +} + +inline float RemapValClamped( float val, float A, float B, float C, float D) +{ + if ( A == B ) + return val >= B ? D : C; + float cVal = (val - A) / (B - A); + cVal = bound( 0.0f, cVal, 1.0f ); + + return C + (D - C) * cVal; +} + +inline vec_t VectorNormalize( vec3_t v ) +{ + double length; + + length = DotProduct( v, v ); + length = sqrt( length ); + + if( length < 1e-6 ) + { + VectorClear( v ); + return 0.0; + } + + v[0] /= length; + v[1] /= length; + v[2] /= length; + + return length; +} + +_forceinline int ffsl( long mask ) +{ + int bit; + + if( mask == 0 ) return 0; + for( bit = 1; !(mask & 1); bit++ ) + mask = (unsigned long)mask >> 1; + return bit; +} + +/* +============== +fix_coord + +converts the reletive tex coords to absolute +============== +*/ +_forceinline uint fix_coord( vec_t in, uint width ) +{ + if( in > 0 ) return (uint)in % width; + return width - ((uint)fabs( in ) % width); +} + +bool VectorIsOnAxis( const vec3_t v ); +bool VectorCompareEpsilon( const vec3_t vec1, const vec3_t vec2, vec_t epsilon ); +bool VectorCompareEpsilon2( const vec3_t vec1, const float vec2[3], vec_t epsilon ); +void CalcST( const vec3_t p0, const vec3_t p1, const vec3_t p2, const vec2_t t0, const vec2_t t1, const vec2_t t2, vec3_t s, vec3_t t, bool areaweight = false ); +void VectorVectors( const vec3_t forward, vec3_t right, vec3_t up ); +vec_t VectorNormalizeLength2( const vec3_t v, vec3_t out ); +void SinCos( float radians, float *sine, float *cosine ); +uint VertexHashKey( const vec3_t point, uint hashSize ); +int PlaneTypeForNormal( const vec3_t normal ); +int SignbitsForPlane( const vec3_t normal ); +float RandomFloat( float flLow, float flHigh ); +float ColorNormalize( const vec3_t in, vec3_t out ); +unsigned short FloatToHalf( float v ); +float HalfToFloat( unsigned short h ); + +// +// Bounding Box operations +// +void ClearBounds( vec3_t mins, vec3_t maxs ); +bool BoundsIsCleared( const vec3_t mins, const vec3_t maxs ); +void AddPointToBounds( const vec3_t v, vec3_t mins, vec3_t maxs ); +bool BoundsIntersect( const vec3_t mins1, const vec3_t maxs1, const vec3_t mins2, const vec3_t maxs2, vec_t epsilon = 0.0 ); +bool BoundsAndSphereIntersect( const vec3_t mins, const vec3_t maxs, const vec3_t origin, vec_t radius ); +bool SphereIntersect( const vec3_t vSphereCenter, vec_t fSphereRadiusSquared, const vec3_t vLinePt, const vec3_t vLineDir ); +vec_t RadiusFromBounds( const vec3_t mins, const vec3_t maxs ); +void ExpandBounds( vec3_t mins, vec3_t maxs, vec_t offset ); +void COM_NormalizeAngles( vec3_t angles ); + +// +// Quaternion & matrices +// +#define Matrix3x4_LoadIdentity( mat ) Matrix3x4_Copy( mat, matrix3x4_identity ) +#define Matrix3x4_Copy( out, in ) memcpy( out, in, sizeof( matrix3x4 )) +#define Matrix4x4_LoadIdentity( mat ) Matrix3x4_Copy( mat, matrix4x4_identity ) +#define Matrix4x4_Copy( out, in ) memcpy( out, in, sizeof( matrix4x4 )) + +void AngleQuaternion( const vec3_t angles, vec4_t q ); +void Matrix3x4_CreateFromEntityScale3f( matrix3x4 out, const vec3_t angles, const vec3_t origin, const vec3_t scale ); +void Matrix3x4_FromOriginQuat( matrix3x4 out, const vec4_t quaternion, const vec3_t origin ); +void Matrix3x4_MatrixToEntityScale3f( const matrix3x4 in, vec3_t origin, vec3_t angles, vec3_t scale ); +void Matrix3x4_ConcatTransforms( matrix3x4 out, const matrix3x4 in1, const matrix3x4 in2 ); +void Matrix3x4_TransformStandardPlane( const matrix3x4 in, const vec3_t normal, float d, vec3_t out, float *dist ); +void Matrix3x4_VectorITransform( const matrix3x4 in, const vec3_t v, vec3_t out ); +void Matrix3x4_VectorTransform( const matrix3x4 in, const vec3_t v, vec3_t out ); +void Matrix3x4_Vector4Transform( const matrix3x4 in, const vec4_t v, vec4_t out ); +void Matrix3x4_VectorIRotate( const matrix3x4 in, const vec3_t v, vec3_t out ); +void Matrix3x4_VectorRotate( const matrix3x4 in, const vec3_t v, vec3_t out ); +void QuaternionSlerp( const vec4_t p, vec4_t q, vec_t t, vec4_t qt ); +bool Matrix3x4_Invert_Full( matrix3x4 out, const matrix3x4 in ); +vec_t Matrix3x4_CalcSign( const matrix3x4 in ); + +extern vec3_t vec3_origin; +extern const matrix3x4 matrix3x4_identity; +extern const matrix4x4 matrix4x4_identity; + +bool Matrix4x4_Invert_Full( matrix4x4 out, const matrix4x4 in1 ); +void Matrix4x4_Concat( matrix4x4 out, const matrix4x4 in1, const matrix4x4 in2 ); +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/utils/common/mxtk.lib b/utils/common/mxtk.lib new file mode 100644 index 0000000..2fcd07c Binary files /dev/null and b/utils/common/mxtk.lib differ diff --git a/utils/common/polylib.cpp b/utils/common/polylib.cpp new file mode 100644 index 0000000..30fe8f8 --- /dev/null +++ b/utils/common/polylib.cpp @@ -0,0 +1,1446 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include "cmdlib.h" +#include "mathlib.h" +#include "polylib.h" +#include +#include "threads.h" +#include "bspfile.h" + +void pw( winding_t *w ) +{ + for( int i = 0; w && i < w->numpoints; i++ ) + Msg( "(%5.1f, %5.1f, %5.1f)\n", w->p[i][0], w->p[i][1], w->p[i][2] ); +} + +/* +============= +AllocWinding +============= +*/ +winding_t *AllocWinding( int numpoints ) +{ + if( numpoints > MAX_POINTS_ON_WINDING ) + COM_FatalError( "AllocWinding: MAX_POINTS_ON_WINDING limit exceeded\n", numpoints ); + return (winding_t *)Mem_Alloc( (int)((winding_t *)0)->p[numpoints], C_WINDING ); +} + +/* +============= +FreeWinding +============= +*/ +void FreeWinding( winding_t *w ) +{ + // simple sentinel by Carmack + if( *(unsigned *)w == 0xDEADC0DE ) + COM_FatalError( "FreeWinding: freed a freed winding\n" ); + + *(unsigned *)w = 0xDEADC0DE; + Mem_Free( w, C_WINDING ); +} + +/* +================== +CopyWinding +================== +*/ +winding_t *CopyWinding( const winding_t *w ) +{ + winding_t *c = AllocWinding( w->numpoints ); + memcpy( c, w, (int)((winding_t *)0)->p[w->numpoints] ); + + return c; +} + +/* +================== +CopyWindingExt +================== +*/ +winding_t *CopyWinding( int numpoints, vec3_t *points ) +{ + winding_t *c = AllocWinding( numpoints ); + memcpy( c->p, points, sizeof( *points ) * numpoints ); + c->numpoints = numpoints; + + return c; +} + +/* +================== +ReverseWinding +================== +*/ +void ReverseWinding( winding_t **inout ) +{ + winding_t *c, *w; + + if( !inout || !*inout ) + return; + + w = *inout; + c = AllocWinding( w->numpoints ); + + for( int i = 0; i < w->numpoints; i++ ) + VectorCopy( w->p[w->numpoints-1-i], c->p[i] ); + c->numpoints = w->numpoints; + + FreeWinding( w ); + *inout = c; +} + +/* +============ +RemoveColinearPoints +============ +*/ +void RemoveColinearPointsEpsilon( winding_t *w, vec_t epsilon ) +{ + vec3_t v1, v2; + vec_t *p1, *p2, *p3; + + for( int i = 0; w && i < w->numpoints; i++ ) + { + p1 = w->p[(i+w->numpoints-1) % w->numpoints]; + p2 = w->p[i]; + p3 = w->p[(i+1)%w->numpoints]; + VectorSubtract( p2, p1, v1 ); + VectorSubtract( p3, p2, v2 ); + + // v1 or v2 might be close to 0 + if( DotProduct( v1, v2 ) * DotProduct( v1, v2 ) >= DotProduct( v1, v1 ) * DotProduct( v2, v2 ) + - epsilon * epsilon * ( DotProduct( v1, v1 ) + DotProduct( v2, v2 ) + epsilon * epsilon )) + { + w->numpoints--; + + for( ; i < w->numpoints; i++ ) + VectorCopy( w->p[i+1], w->p[i] ); + i = -1; + continue; + } + } +} + +/* +============ +WindingPlane +============ +*/ +void WindingPlane( winding_t *w, vec3_t normal, vec_t *dist ) +{ + vec3_t v1, v2; + + if( w && w->numpoints >= 3 ) + { + VectorSubtract( w->p[2], w->p[1], v1 ); + VectorSubtract( w->p[0], w->p[1], v2 ); + CrossProduct( v2, v1, normal ); + VectorNormalize( normal ); + *dist = DotProduct( w->p[0], normal ); + } + else + { + VectorClear( normal ); + *dist = 0.0; + } +} + +/* +================ +WindingIsTiny + +Returns true if the winding would be crunched out of +existance by the vertex snapping. +================ +*/ +bool WindingIsTiny( winding_t *w ) +{ + vec3_t delta; + vec_t length; + int edges = 0; + + for( int i = 0; w != NULL && i < w->numpoints; i++ ) + { + int j = ( i == ( w->numpoints - 1 )) ? 0 : ( i + 1 ); + VectorSubtract( w->p[j], w->p[i], delta ); + length = VectorLength( delta ); + + if( length > EDGE_LENGTH ) + { + if( ++edges == 3 ) + return false; + } + } + + return true; +} + +/* +================ +WindingIsHuge + +Returns true if the winding still has one of the points +from basewinding for plane +================ +*/ +bool WindingIsHuge( winding_t *w ) +{ + for( int i = 0; w != NULL && i < w->numpoints; i++ ) + { + for( int j = 0; j < 3; j++ ) + { + if( w->p[i][j] < WORLD_MINS || w->p[i][j] > WORLD_MAXS ) + return true; + } + } + + return false; +} + +/* +============= +WindingArea +============= +*/ +vec_t WindingArea( const winding_t *w ) +{ + vec3_t d1, d2, cross; + vec_t total; + + total = 0.0; + + for( int i = 2; w != NULL && w->numpoints >= 3 && i < w->numpoints; i++ ) + { + VectorSubtract( w->p[i-1], w->p[0], d1 ); + VectorSubtract( w->p[i], w->p[0], d2 ); + CrossProduct( d1, d2, cross ); + total += 0.5 * VectorLength ( cross ); + } + + return total; +} + +/* +============= +WindingBounds +============= +*/ +void WindingBounds( winding_t *w, vec3_t mins, vec3_t maxs, bool merge ) +{ + if( !merge ) ClearBounds( mins, maxs ); + + for( int i = 0; w != NULL && i < w->numpoints; i++ ) + { + for( int j = 0; j < 3; j++ ) + { + vec_t v = w->p[i][j]; + if( v < mins[j] ) mins[j] = v; + if( v > maxs[j] ) maxs[j] = v; + } + } +} + +/* +============= +WindingCenter +============= +*/ +void WindingCenter( winding_t *w, vec3_t center ) +{ + VectorClear( center ); + + if( !w || w->numpoints <= 0 ) + return; + + for( int i = 0; i < w->numpoints; i++ ) + VectorAdd( w->p[i], center, center ); + + vec_t scale = 1.0 / w->numpoints; + VectorScale( center, scale, center ); +} + +/* +============= +WindingAreaAndBalancePoint +============= +*/ +vec_t WindingAreaAndBalancePoint( winding_t *w, vec3_t center ) +{ + vec3_t d1, d2, cross; + vec_t total, area; + + VectorClear( center ); + if( !w ) return 0; + + total = 0.0; + + for( int i = 2; i < w->numpoints; i++ ) + { + VectorSubtract( w->p[i-1], w->p[0], d1 ); + VectorSubtract( w->p[i], w->p[0], d2 ); + CrossProduct( d1, d2, cross ); + area = VectorLength( cross ); + total += area; + + // center of triangle, weighted by area + VectorMA( center, area / 3.0, w->p[i-1], center ); + VectorMA( center, area / 3.0, w->p[i], center ); + VectorMA( center, area / 3.0, w->p[0], center ); + } + + if( total ) VectorScale( center, 1.0 / total, center ); + + return total * 0.5; +} + +/* +================= +BaseWindingForPlane +================= +*/ +winding_t *BaseWindingForPlane( const vec3_t normal, vec_t dist ) +{ + vec3_t org, vright, vup; + vec_t v, max = 0.56; + int x = -1; + winding_t *w; + + // find the major axis + for( int i = 0; i < 3; i++ ) + { + v = fabs( normal[i] ); + + if( v > max ) + { + max = v; + x = i; + } + } + + if( x == -1 ) COM_FatalError( "BaseWindingForPlane: no axis found\n" ); + + switch( x ) + { + case 0: + case 1: // fall through to next case. + vright[0] = -normal[1]; + vright[1] = normal[0]; + vright[2] = 0; + break; + case 2: + vright[0] = 0; + vright[1] = -normal[2]; + vright[2] = normal[1]; + break; + } + + VectorScale( vright, (vec_t)WORLD_MAXS * 4.0, vright ); + CrossProduct( normal, vright, vup ); + VectorScale( normal, (vec_t)dist, org ); + + // project a really big axis aligned box onto the plane + w = AllocWinding( 4 ); + + VectorSubtract( org, vright, w->p[0] ); + VectorAdd( w->p[0], vup, w->p[0] ); + + VectorAdd( org, vright, w->p[1] ); + VectorAdd( w->p[1], vup, w->p[1] ); + + VectorAdd( org, vright, w->p[2] ); + VectorSubtract( w->p[2], vup, w->p[2] ); + + VectorSubtract( org, vright, w->p[3] ); + VectorSubtract( w->p[3], vup, w->p[3] ); + + w->numpoints = 4; + + return w; +} + +/* +============= +ClipWindingEpsilon +============= +*/ +void ClipWindingEpsilon( winding_t *in, const vec3_t normal, vec_t dist, vec_t epsilon, winding_t **front, winding_t **back, bool keepon ) +{ + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + vec_t dot; + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f, *b; + int maxpts; + + counts[0] = counts[1] = counts[2] = 0; + + // determine sides for each point + for( i = 0; i < in->numpoints; i++ ) + { + dot = DotProduct( in->p[i], normal ); + dot -= dist; + dists[i] = dot; + + if( dot > epsilon ) + sides[i] = SIDE_FRONT; + else if( dot < -epsilon ) + sides[i] = SIDE_BACK; + else sides[i] = SIDE_ON; + + counts[sides[i]]++; + } + + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = NULL; + + if( keepon && !counts[SIDE_FRONT] && !counts[SIDE_BACK] ) + { + vec_t sum = 0.0; + for( i = 0; i < in->numpoints; i++) + { + dot = DotProduct( in->p[i], normal ); + dot -= dist; + sum += dot; + } + + if( sum > NORMAL_EPSILON ) + *front = CopyWinding( in ); + else *back = CopyWinding( in ); + return; + } + + if( !counts[SIDE_FRONT] ) + { + *back = CopyWinding( in ); + return; + } + + if( !counts[SIDE_BACK] ) + { + *front = CopyWinding( in ); + return; + } + + maxpts = in->numpoints + 4; // cant use counts[0] + 2 because of fp grouping errors + + *front = f = AllocWinding( maxpts ); + *back = b = AllocWinding( maxpts ); + VectorClear( mid ); + + for( i = 0; i < in->numpoints; i++ ) + { + p1 = in->p[i]; + + if( sides[i] == SIDE_ON ) + { + VectorCopy( p1, f->p[f->numpoints] ); + VectorCopy( p1, b->p[b->numpoints] ); + f->numpoints++; + b->numpoints++; + continue; + } + else if( sides[i] == SIDE_FRONT ) + { + VectorCopy( p1, f->p[f->numpoints] ); + f->numpoints++; + } + else if( sides[i] == SIDE_BACK ) + { + VectorCopy( p1, b->p[b->numpoints] ); + b->numpoints++; + } + + if(( sides[i+1] == SIDE_ON ) | ( sides[i+1] == sides[i] )) // | instead of || for branch optimization + continue; + + // generate a split point + p2 = in->p[(i + 1) % in->numpoints]; + + dot = dists[i] / (dists[i] - dists[i+1]); + + for( j = 0; j < 3; j++ ) + { + // avoid round off error when possible + if( normal[j] == 1 ) + mid[j] = dist; + else if( normal[j] == -1 ) + mid[j] = -dist; + else mid[j] = p1[j] + dot * ( p2[j] - p1[j] ); + } + + VectorCopy( mid, f->p[f->numpoints] ); + VectorCopy( mid, b->p[b->numpoints] ); + f->numpoints++; + b->numpoints++; + } + + if(( f->numpoints > maxpts ) | ( b->numpoints > maxpts )) + COM_FatalError( "ClipWinding: points exceeded estimate\n" ); + + if(( f->numpoints > MAX_POINTS_ON_WINDING ) | ( b->numpoints > MAX_POINTS_ON_WINDING )) + COM_FatalError( "ClipWinding: MAX_POINTS_ON_WINDING limit exceeded\n" ); + + RemoveColinearPointsEpsilon( f, epsilon ); + RemoveColinearPointsEpsilon( b, epsilon ); + + if( f->numpoints < 3 ) + { + FreeWinding( f ); + *front = NULL; + } + + if( b->numpoints < 3 ) + { + FreeWinding( b ); + *back = NULL; + } +} + +/* +================== +DivideWindingEpsilon + +Divides a winding by a plane, producing one or two windings. The +original winding is not damaged or freed. If only on one side, the +returned winding will be the input winding. If on both sides, two +new windings will be created. +================== +*/ +void DivideWindingEpsilon( winding_t *in, vec3_t normal, vec_t dist, vec_t epsilon, winding_t **front, winding_t **back, vec_t *fnormal, bool keepon ) +{ + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + vec_t dot; + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f, *b; + int maxpts; + + counts[0] = counts[1] = counts[2] = 0; + + // determine sides for each point + for( i = 0; i < in->numpoints; i++ ) + { + dot = DotProduct( in->p[i], normal ); + dot -= dist; + dists[i] = dot; + + if( dot > epsilon ) + sides[i] = SIDE_FRONT; + else if( dot < -epsilon ) + sides[i] = SIDE_BACK; + else sides[i] = SIDE_ON; + + counts[sides[i]]++; + } + + sides[i] = sides[0]; + dists[i] = dists[0]; + + *front = *back = NULL; + + if( keepon && !counts[SIDE_FRONT] && !counts[SIDE_BACK] ) + { + if( fnormal ) + { + // put front face in front node, and back face in back node. + if( DotProduct( fnormal, normal ) > NORMAL_EPSILON ) // usually near 1.0 or -1.0 + *front = in; + else *back = in; + } + else + { + vec_t sum = 0.0; + for( i = 0; i < in->numpoints; i++) + { + dot = DotProduct( in->p[i], normal ); + dot -= dist; + sum += dot; + } + + if( sum > NORMAL_EPSILON ) + *front = in; + else *back = in; + } + return; + } + + if( !counts[SIDE_FRONT] ) + { + *back = in; + return; + } + + if( !counts[SIDE_BACK] ) + { + *front = in; + return; + } + + maxpts = in->numpoints + 4; // can't use counts[0]+2 because of fp grouping errors + + *front = f = AllocWinding( maxpts ); + *back = b = AllocWinding( maxpts ); + + for( i = 0; i < in->numpoints; i++ ) + { + p1 = in->p[i]; + + if( sides[i] == SIDE_ON ) + { + VectorCopy( p1, f->p[f->numpoints] ); + VectorCopy( p1, b->p[b->numpoints] ); + f->numpoints++; + b->numpoints++; + continue; + } + + if( sides[i] == SIDE_FRONT ) + { + VectorCopy( p1, f->p[f->numpoints] ); + f->numpoints++; + } + else if( sides[i] == SIDE_BACK ) + { + VectorCopy( p1, b->p[b->numpoints] ); + b->numpoints++; + } + + if(( sides[i+1] == SIDE_ON ) | ( sides[i+1] == sides[i] )) // | instead of || for branch optimization + continue; + + // generate a split point + p2 = in->p[(i + 1) % in->numpoints]; + + dot = dists[i] / (dists[i] - dists[i+1]); + + for( j = 0; j < 3; j++ ) + { + // avoid round off error when possible + if( normal[j] == 1 ) + mid[j] = dist; + else if( normal[j] == -1 ) + mid[j] = -dist; + else mid[j] = p1[j] + dot * ( p2[j] - p1[j] ); + } + + VectorCopy( mid, f->p[f->numpoints] ); + VectorCopy( mid, b->p[b->numpoints] ); + f->numpoints++; + b->numpoints++; + } + + if(( f->numpoints > maxpts ) | ( b->numpoints > maxpts )) + COM_FatalError( "ClipWinding: points exceeded estimate\n" ); + + if(( f->numpoints > MAX_POINTS_ON_WINDING ) | ( b->numpoints > MAX_POINTS_ON_WINDING )) + COM_FatalError( "ClipWinding: MAX_POINTS_ON_WINDING limit exceeded\n" ); + + RemoveColinearPointsEpsilon( f, epsilon ); + RemoveColinearPointsEpsilon( b, epsilon ); + + if( f->numpoints < 3 ) + { + FreeWinding( f ); + FreeWinding( b ); + *front = NULL; + *back = in; + } + else if( b->numpoints < 3 ) + { + FreeWinding( f ); + FreeWinding( b ); + *front = in; + *back = NULL; + } +} + +/* +============= +ChopWindingInPlace +============= +*/ +bool ChopWindingInPlace( winding_t **inout, const vec3_t normal, vec_t dist, vec_t epsilon, bool keepon ) +{ + winding_t *in; + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + vec_t dot; + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *f; + int maxpts; + + in = *inout; + counts[0] = counts[1] = counts[2] = 0; + + // determine sides for each point + for( i = 0; i < in->numpoints; i++ ) + { + dot = DotProduct( in->p[i], normal ); + dot -= dist; + dists[i] = dot; + + if( dot > epsilon ) + sides[i] = SIDE_FRONT; + else if( dot < -epsilon ) + sides[i] = SIDE_BACK; + else sides[i] = SIDE_ON; + + counts[sides[i]]++; + } + + sides[i] = sides[0]; + dists[i] = dists[0]; + + if( keepon && !counts[SIDE_FRONT] && !counts[SIDE_BACK] ) + return true; // SIDE_ON + + if( !counts[0] ) + { + FreeWinding( in ); + *inout = NULL; + return false; + } + + if( !counts[1] ) return true; // inout stays the same + + maxpts = in->numpoints + 4; // cant use counts[0] + 2 because of fp grouping errors + VectorClear( mid ); + + f = AllocWinding( maxpts ); + + for( i = 0; i < in->numpoints; i++ ) + { + p1 = in->p[i]; + + if( sides[i] == SIDE_ON ) + { + VectorCopy( p1, f->p[f->numpoints] ); + f->numpoints++; + continue; + } + + if( sides[i] == SIDE_FRONT ) + { + VectorCopy( p1, f->p[f->numpoints] ); + f->numpoints++; + } + + if(( sides[i+1] == SIDE_ON ) | ( sides[i+1] == sides[i] )) // | instead of || for branch optimization + continue; + + // generate a split point + p2 = in->p[(i + 1) % in->numpoints]; + + dot = dists[i] / (dists[i] - dists[i+1]); + + for( j = 0; j < 3; j++ ) + { + // avoid round off error when possible + if( normal[j] == 1 ) + mid[j] = dist; + else if( normal[j] == -1 ) + mid[j] = -dist; + else mid[j] = p1[j] + dot * ( p2[j] - p1[j] ); + } + + VectorCopy( mid, f->p[f->numpoints] ); + f->numpoints++; + } + + if( f->numpoints > maxpts ) + COM_FatalError( "ChopWinding: points exceeded estimate\n" ); + if( f->numpoints > MAX_POINTS_ON_WINDING ) + COM_FatalError( "ChopWinding: MAX_POINTS_ON_WINDING limit exceeded\n" ); + + RemoveColinearPointsEpsilon( f, epsilon ); + + if( f->numpoints < 3 ) + { + FreeWinding( f ); + *inout = NULL; + return false; + } + + FreeWinding( in ); + *inout = f; + return true; +} + +/* +============= +TryMergeWinding + +If two polygons share a common edge and the edges that meet at the +common points are both inside the other polygons, merge them + +Returns NULL if the windings couldn't be merged, or the new winding. +The originals will NOT be freed. +============= +*/ +winding_t *TryMergeWindingEpsilon( winding_t *w1, winding_t *w2, const vec3_t planenormal, vec_t epsilon ) +{ + vec_t *front, *back; + bool keep1, keep2; + vec3_t normal, delta; + int edge1, edge2; + winding_t *neww; + vec_t dot; + int k; + + // check for early out + if( !w1 || !w2 ) return NULL; + + edge1 = edge2 = -1; + + // + // find a common edge + // + for( int i = 0; i < w1->numpoints; i++ ) + { + vec_t *p1 = w1->p[i]; + vec_t *p2 = w1->p[(i + 1) % w1->numpoints]; + + for( int j = 0; j < w2->numpoints; j++ ) + { + vec_t *p3 = w2->p[j]; + vec_t *p4 = w2->p[(j + 1) % w2->numpoints]; + + if( VectorCompareEpsilon( p1, p4, epsilon ) && VectorCompareEpsilon( p2, p3, epsilon )) + { + edge2 = j; + break; + } + } + + if( edge2 >= 0 ) + { + edge1 = i; + break; + } + } + + if( edge1 < 0 || edge2 < 0 ) + return false; // no collinear edge + + // + // check slope of connected lines + // if the slopes are colinear, the point can be removed + // + front = w1->p[edge1]; + back = w1->p[(i - 1 + w1->numpoints) % w1->numpoints]; + VectorSubtract( front, back, delta ); + CrossProduct( planenormal, delta, normal ); + VectorNormalize( normal ); + + back = w2->p[(edge2 + 2) % w2->numpoints]; + VectorSubtract( back, front, delta ); + dot = DotProduct( delta, normal ); + if( dot > epsilon ) return NULL; // not a convex polygon + keep1 = dot < -epsilon; + + front = w2->p[edge2]; + back = w1->p[(edge1 + 2) % w1->numpoints]; + VectorSubtract( back, front, delta ); + CrossProduct( planenormal, delta, normal ); + VectorNormalize( normal ); + + back = w2->p[(edge2 - 1 + w2->numpoints) % w2->numpoints]; + VectorSubtract( back, front, delta ); + dot = DotProduct( delta, normal ); + if( dot > epsilon ) return NULL; // not a convex polygon + keep2 = dot < -epsilon; + + // + // build the new polygon + // + neww = AllocWinding( w1->numpoints + w2->numpoints - 4 + keep1 + keep2 ); + + // copy first polygon + for( k = (edge1 + 1) % w1->numpoints; k != edge1; k = (k + 1) % w1->numpoints ) + { + if( k == (edge1 + 1) % w1->numpoints && !keep2 ) + continue; + + VectorCopy( w1->p[k], neww->p[neww->numpoints] ); + neww->numpoints++; + } + + // copy second polygon + for( k = (edge2 + 1) % w2->numpoints; k != edge2; k = (k + 1) % w2->numpoints ) + { + if( k == (edge2 + 1) % w2->numpoints && !keep1 ) + continue; + + VectorCopy( w2->p[k], neww->p[neww->numpoints] ); + neww->numpoints++; + } + + RemoveColinearPointsEpsilon( neww, epsilon ); + + if( neww->numpoints < 3 ) + { + FreeWinding( neww ); + return NULL; + } + + return neww; +} + +/* +============= +TryMergeWinding + +If two polygons share a common edge and the edges that meet at the +common points are both inside the other polygons, merge them + +Returns NULL if the windings couldn't be merged, or the new winding. +The originals will NOT be freed. +============= +*/ +bool BiteWindingEpsilon( winding_t **in1, winding_t **in2, const vec3_t planenormal, vec_t epsilon ) +{ + vec_t *front, *back; + winding_t *neww, *fw, *bw; + bool convex1, convex2; + vec3_t normal, delta; + int edge1, edge2; + int edgetype = 0; + vec_t len1, len2; + winding_t *w1 = *in1; + winding_t *w2 = *in2; + vec3_t dir1, dir2; + vec_t dot, dist; + + if( !w1 || !w2 ) + return false; + + edge1 = edge2 = -1; + + // p11 = p1 + // p12 = p2 + // p21 = p3 + // p22 = p4 + + // + // find a common edge + // + for( int i = 0; i < w1->numpoints; i++ ) + { + vec_t *p1 = w1->p[i]; + vec_t *p2 = w1->p[(i + 1) % w1->numpoints]; + + for( int j = 0; j < w2->numpoints; j++ ) + { + vec_t *p3 = w2->p[j]; + vec_t *p4 = w2->p[(j + 1) % w2->numpoints]; + + if( VectorCompareEpsilon( p1, p4, epsilon ) && VectorCompareEpsilon( p2, p3, epsilon )) + { + // really common edge + edgetype = 0; + edge2 = j; + break; + } + + VectorSubtract( p1, p2, dir1 ); + VectorSubtract( p4, p3, dir2 ); + + len1 = VectorNormalize( dir1 ); + len2 = VectorNormalize( dir2 ); + dot = DotProduct( dir1, dir2 ); + + if( VectorCompareEpsilon( p1, p4, epsilon ) && ( dot > ( 1.0 - NORMAL_EPSILON )) && ( len1 < len2 )) + { + // collinear edge with common point 1 + edgetype = 1; + edge2 = j; + break; + } + + if( VectorCompareEpsilon( p2, p3, epsilon ) && ( dot > ( 1.0 - NORMAL_EPSILON )) && ( len1 < len2 )) + { + // collinear edge with common point 2 + edgetype = 2; + edge2 = j; + break; + } + } + + if( edge2 >= 0 ) + { + edge1 = i; + break; + } + } + + if( edge1 < 0 || edge2 < 0 ) + return false; // no collinear edge + + if( edgetype == 0 ) + { + // Detemine collinear edge, and split at non-collinear + // If both edges are non-collinear, don't split at all! + front = w1->p[edge1]; + back = w1->p[(edge1 + w1->numpoints - 1) % w1->numpoints]; + VectorSubtract( front, back, delta ); + CrossProduct( planenormal, delta, normal ); + VectorNormalize( normal ); + + back = w2->p[(edge2 + 2) % w2->numpoints]; + VectorSubtract( back, front, delta ); + dot = DotProduct( delta, normal ); + convex1 = dot <= epsilon; + + front = w2->p[edge2]; + back = w1->p[(edge1 + 2) % w1->numpoints]; + VectorSubtract( back, front, delta ); + CrossProduct( planenormal, delta, normal ); + VectorNormalize( normal ); + + back = w2->p[(edge2 + w2->numpoints - 1) % w2->numpoints]; + VectorSubtract( back, front, delta ); + dot = DotProduct( delta, normal ); + convex2 = dot <= epsilon; + + if( convex1 && convex2 ) + return false; + + front = w1->p[edge1]; + back = w1->p[(edge1 + w1->numpoints - 1) % w1->numpoints]; + VectorSubtract( front, back, delta ); + CrossProduct( planenormal, delta, normal ); + VectorNormalize( normal ); + dist = DotProduct( normal, front ); + + ClipWindingEpsilon( w2, normal, dist, epsilon, &fw, &bw ); + + if( !fw || !bw ) + { + if( fw ) FreeWinding( fw ); + if( bw ) FreeWinding( bw ); + + front = w1->p[(edge1 + 2) % w1->numpoints]; + back = w1->p[(edge1 + 1) % w1->numpoints]; + VectorSubtract( front, back, delta ); + CrossProduct( planenormal, delta, normal ); + VectorNormalize( normal ); + dist = DotProduct( normal, front ); + + ClipWindingEpsilon( w2, normal, dist, epsilon, &fw, &bw ); + + if( !fw || !bw ) + { + if( fw ) FreeWinding( fw ); + if( bw ) FreeWinding( bw ); + return false; + } + } + + neww = TryMergeWindingEpsilon( w1, bw, planenormal, epsilon ); + + if( !neww ) + { + if( fw ) FreeWinding( fw ); + if( bw ) FreeWinding( bw ); + return false; + } + + // throw old windings + FreeWinding( bw ); + FreeWinding( w1 ); + FreeWinding( w2 ); + + *in1 = neww; + *in2 = fw; + } + else if( edgetype == 1 ) + { + // common point 1 + front = w1->p[(edge1 + 2) % w1->numpoints]; + back = w1->p[(edge1 + 1) % w1->numpoints]; + VectorSubtract( front, back, delta ); + CrossProduct( planenormal, delta, normal ); + VectorNormalize( normal ); + dist = DotProduct( normal, front ); + + ClipWindingEpsilon( w2, normal, dist, epsilon, &fw, &bw ); + + if ( !fw || !bw ) + { + if( fw ) FreeWinding( fw ); + if( bw ) FreeWinding( bw ); + return false; + } + + neww = TryMergeWindingEpsilon( w1, bw, planenormal, epsilon ); + + if( !neww ) + { + if( fw ) FreeWinding( fw ); + if( bw ) FreeWinding( bw ); + return false; + } + + // throw old windings + FreeWinding( bw ); + FreeWinding( w1 ); + FreeWinding( w2 ); + + *in1 = neww; + *in2 = fw; + } + else if( edgetype == 2 ) + { + // common point 2 + front = w1->p[edge1]; + back = w1->p[(edge1 + w1->numpoints - 1) % w1->numpoints]; + VectorSubtract( front, back, delta ); + CrossProduct( planenormal, delta, normal ); + VectorNormalize( normal ); + dist = DotProduct( normal, front ); + + ClipWindingEpsilon( w2, normal, dist, epsilon, &fw, &bw ); + + if ( !fw || !bw ) + { + if( fw ) FreeWinding( fw ); + if( bw ) FreeWinding( bw ); + return false; + } + + neww = TryMergeWindingEpsilon( w1, bw, planenormal, epsilon ); + + if( !neww ) + { + if( fw ) FreeWinding( fw ); + if( bw ) FreeWinding( bw ); + return false; + } + + // throw old windings + FreeWinding( bw ); + FreeWinding( w1 ); + FreeWinding( w2 ); + + *in1 = neww; + *in2 = fw; + } + + return true; +} + +/* +================= +CheckWinding + +debugging thing +================= +*/ +void CheckWindingEpsilon( winding_t *w, vec_t epsilon, bool show_warnings ) +{ + vec_t *p1, *p2; + vec_t d, edgedist; + vec3_t dir, edgenormal; + vec_t area, facedist; + vec3_t facenormal; + int i, j; + + if( !w ) return; + + if( w->numpoints < 3 ) + COM_FatalError( "CheckWinding: %i points\n", w->numpoints ); + + area = WindingArea( w ); + if( area < MICROVOLUME ) COM_FatalError( "CheckWinding: %f area\n", area ); + + WindingPlane( w, facenormal, &facedist ); + + for( i = 0; i < w->numpoints; i++ ) + { + p1 = w->p[i]; + + for( j = 0; j < 3; j++ ) + { + if( p1[j] > WORLD_MAXS || p1[j] < WORLD_MINS ) + COM_FatalError( "CheckWinding: BOGUS_RANGE: %f", p1[j] ); + } + + j = i + 1 == w->numpoints ? 0 : i + 1; + + // check the point is on the face plane + d = DotProduct( p1, facenormal ) - facedist; + if( show_warnings && ( d < -epsilon || d > epsilon )) + MsgDev( D_WARN, "CheckWinding: point off plane: %f\n", d ); + + // check the edge isn't degenerate + p2 = w->p[j]; + VectorSubtract( p2, p1, dir ); + d = VectorLength( dir ); + + if( show_warnings && ( d < epsilon )) + MsgDev( D_WARN, "CheckWinding: degenerate edge: %f\n", d ); + + CrossProduct( facenormal, dir, edgenormal ); + VectorNormalize( edgenormal ); + edgedist = DotProduct( p1, edgenormal ); + edgedist += epsilon; + + // all other points must be on front side + for( j = 0; j < w->numpoints; j++ ) + { + if( j == i ) continue; + + d = DotProduct( w->p[j], edgenormal ); + if( d > edgedist ) COM_FatalError( "CheckWinding: non-convex\n" ); + } + } +} + +/* +============ +PointInWindingEpsilon +============ +*/ +bool PointInWindingEpsilon( const winding_t *w, const vec3_t planenormal, const vec3_t point, vec_t epsilon, bool noedge ) +{ + vec3_t delta; + vec3_t normal; + vec_t dist; + + ASSERT( w != NULL ); +#if 0 + vec3_t edge, cross0, cross1; + + // + // get the first normal to test + // + VectorSubtract( point, w->p[0], delta ); + VectorSubtract( w->p[1], w->p[0], edge ); + CrossProduct( edge, delta, cross0 ); + VectorNormalize( cross0 ); + + for( int i = 1; i < w->numpoints; i++ ) + { + VectorSubtract( point, w->p[i], delta ); + VectorSubtract( w->p[(i+1)%w->numpoints], w->p[i], edge ); + CrossProduct( edge, delta, cross1 ); + VectorNormalize( cross1 ); + + if( DotProduct( cross0, cross1 ) < 0.0f ) + return false; + } +#else + for( int i = 0; i < w->numpoints; i++ ) + { + VectorSubtract( w->p[(i+1) % w->numpoints], w->p[i], delta ); + CrossProduct( delta, planenormal, normal ); + dist = DotProduct( point, normal ) - DotProduct( w->p[i], normal ); + + if( noedge ) + { + if( dist < 0.0 && ( epsilon == 0.0 || dist * dist <= epsilon * epsilon * DotProduct( normal, normal ))) + return false; + } + else + { + if(dist < 0.0 && ( epsilon == 0.0 || dist * dist > epsilon * epsilon * DotProduct( normal, normal ))) + return false; + } + } +#endif + return true; +} + +/* +============ +WindingSnapPoint +============ +*/ +void WindingSnapPoint( const winding_t *w, const vec3_t planenormal, vec3_t point ) +{ + int x; + const vec_t *p1, *p2; + vec3_t delta; + vec3_t normal; + vec_t dist; + vec_t dot0, dot1, dot2; + vec3_t bestpoint; + vec_t bestdist; + bool in = true; + + for( x = 0; x < w->numpoints; x++ ) + { + p1 = w->p[x]; + p2 = w->p[(x + 1) % w->numpoints]; + VectorSubtract( p2, p1, delta ); + CrossProduct( delta, planenormal, normal ); + dist = DotProduct( point, normal ) - DotProduct( p1, normal ); + + if( dist < 0.0 ) + { + in = false; + CrossProduct( planenormal, normal, delta ); + dot0 = DotProduct( delta, point ); + dot1 = DotProduct( delta, p1 ); + dot2 = DotProduct( delta, p2 ); + + if( dot1 < dot0 && dot0 < dot2 ) + { + dist = dist / DotProduct( normal, normal ); + VectorMA( point, -dist, normal, point ); + return; + } + } + } + + if( in ) return; // inside + + vec_t planedot = DotProduct( planenormal, planenormal ); + + for( x = 0; x < w->numpoints; x++ ) + { + p1 = w->p[x]; + VectorSubtract( p1, point, delta ); + dist = DotProduct( delta, planenormal ) / planedot; + VectorMA( delta, -dist, planenormal, delta ); + dot0 = DotProduct( delta, delta ); + + if( x == 0 || dot0 < bestdist ) + { + VectorAdd( point, delta, bestpoint ); + bestdist = dot0; + } + } + + if( w->numpoints > 0 ) + VectorCopy( bestpoint, point ); +} + +/* +============ +WindingSnapPointEpsilon +============ +*/ +vec_t WindingSnapPointEpsilon( const winding_t *w, const vec3_t planenormal, vec3_t point, vec_t width, vec_t maxmove ) +{ + dplane_t planes[MAX_POINTS_ON_WINDING]; + int x, pass, numplanes; + vec3_t v, bestpoint; + vec_t bestwidth; + vec_t newwidth; + + WindingSnapPoint( w, planenormal, point ); + numplanes = 0; + + for( x = 0; x < w->numpoints; x++ ) + { + VectorSubtract( w->p[(x + 1) % w->numpoints], w->p[x], v ); + CrossProduct( v, planenormal, planes[numplanes].normal ); + vec_t len = VectorLength( planes[numplanes].normal ); + if( !len ) continue; + + VectorScale( planes[numplanes].normal, 1.0 / len, planes[numplanes].normal ); + planes[numplanes].dist = DotProduct( w->p[x], planes[numplanes].normal ); + numplanes++; + } + + VectorCopy( point, bestpoint ); + newwidth = width; + bestwidth = 0; + + // apply binary search method for 5 iterations to find the maximal distance that the point can be kept away from all the edges + for( pass = 0; pass < 5; pass++ ) + { + winding_t *neww = CopyWinding( w ); + vec3_t newpoint, planenormal; + bool failed = true; + + for( x = 0; x < numplanes && neww != NULL; x++ ) + { + VectorCopy( planes[x].normal, planenormal ); + ChopWindingInPlace( &neww, planenormal, planes[x].dist + newwidth, ON_EPSILON, false ); + } + + if( neww && neww->numpoints > 0 ) + { + VectorCopy( point, newpoint ); + WindingSnapPoint( neww, planenormal, newpoint ); + VectorSubtract( newpoint, point, v ); + + if( VectorLength( v ) <= maxmove + ON_EPSILON ) + failed = false; + FreeWinding( neww ); + } + + if( !failed ) + { + VectorCopy( newpoint, bestpoint ); + bestwidth = newwidth; + if( pass == 0 ) break; + newwidth += width * pow( 0.5, pass + 1 ); + } + else newwidth -= width * pow( 0.5, pass + 1 ); + } + + VectorCopy( bestpoint, point ); + + return bestwidth; +} + +/* +============ +WindingOnPlaneSide +============ +*/ +int WindingOnPlaneSide( winding_t *w, const vec3_t normal, vec_t dist, vec_t epsilon ) +{ + bool front = false; + bool back = false; + vec_t d; + + for( int i = 0; i < w->numpoints; i++ ) + { + d = DotProduct( w->p[i], normal ) - dist; + + if( d < -epsilon ) + { + if( front ) + return SIDE_CROSS; + back = true; + continue; + } + + if( d > epsilon ) + { + if( back ) + return SIDE_CROSS; + front = true; + continue; + } + } + + if( back ) return SIDE_BACK; + if( front ) return SIDE_FRONT; + + return SIDE_ON; +} \ No newline at end of file diff --git a/utils/common/polylib.h b/utils/common/polylib.h new file mode 100644 index 0000000..dfb45af --- /dev/null +++ b/utils/common/polylib.h @@ -0,0 +1,44 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + + +typedef struct +{ + int numpoints; + vec3_t p[4]; // variable sized +} winding_t; + +#define WindingSize( w ) (( w ) ? ((int)((winding_t *)0)->p[(w)->numpoints]) : 0) +#define MAX_POINTS_ON_WINDING 128 +#define EDGE_LENGTH 0.2 + +winding_t *AllocWinding( int points ); +winding_t *CopyWinding( const winding_t *w ); +winding_t *CopyWinding( int numpoints, vec3_t *points ); +void ReverseWinding( winding_t **inout ); +void FreeWinding( winding_t *w ); +vec_t WindingArea( const winding_t *w ); +void WindingCenter( winding_t *w, vec3_t center ); +vec_t WindingAreaAndBalancePoint( winding_t *w, vec3_t center ); +bool ChopWindingInPlace( winding_t **inout, const vec3_t normal, vec_t dist, vec_t epsilon, bool keepon = true ); +winding_t *TryMergeWindingEpsilon( winding_t *w1, winding_t *w2, const vec3_t planenormal, vec_t epsilon ); +void ClipWindingEpsilon( winding_t *in, const vec3_t normal, vec_t dist, vec_t epsilon, winding_t **front, winding_t **back, bool keepon = false ); +void DivideWindingEpsilon( winding_t *in, vec3_t normal, vec_t dist, vec_t eps, winding_t **fw, winding_t **bw, vec_t *fnorm = 0, bool keepon = true ); +bool BiteWindingEpsilon( winding_t **in1, winding_t **in2, const vec3_t planenormal, vec_t epsilon ); +bool PointInWindingEpsilon( const winding_t *w, const vec3_t planenormal, const vec3_t point, vec_t epsilon = 0.0, bool noedge = true ); +vec_t WindingSnapPointEpsilon( const winding_t *w, const vec3_t planenormal, vec3_t point, vec_t width, vec_t maxmove ); +void WindingSnapPoint( const winding_t *w, const vec3_t planenormal, vec3_t point ); +winding_t *BaseWindingForPlane( const vec3_t normal, vec_t dist ); +void CheckWindingEpsilon( winding_t *w, vec_t epsilon, bool show_warnings ); +void WindingPlane (winding_t *w, vec3_t normal, vec_t *dist ); +void RemoveColinearPointsEpsilon( winding_t *w, vec_t epsilon ); +int WindingOnPlaneSide( winding_t *w, const vec3_t normal, vec_t dist, vec_t epsilon ); +void WindingBounds( winding_t *w, vec3_t mins, vec3_t maxs, bool merge = false ); +void pw( winding_t *w ); diff --git a/utils/common/scriplib.cpp b/utils/common/scriplib.cpp new file mode 100644 index 0000000..0f78c68 --- /dev/null +++ b/utils/common/scriplib.cpp @@ -0,0 +1,561 @@ +/* +=========================================================================== +Copyright (C) 1997-2006 Id Software, Inc. + +This file is part of Quake 2 Tools source code. + +Quake 2 Tools source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake 2 Tools source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake 2 Tools source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// scriplib.c + +#include "cmdlib.h" +#include "mathlib.h" +#include "scriplib.h" +#include "stringlib.h" +#include "filesystem.h" +#include +#include + +/* +============================================================================= + + PARSING STUFF + +============================================================================= +*/ +#define MAX_INCLUDES 16 + +typedef struct +{ + string filename; + const char *buffer; + const char *script_p; + const char *end_p; + int line; + bool separate_stack; +} script_t; + +script_t scriptstack[MAX_INCLUDES]; +script_t *script; +int scriptline; +int oldscriptline; +int scriptdepth; + +char token[MAXTOKEN]; +char g_TXcommand; // QuArK 'TX' comment +int g_DXspecial; // Doom2Gold 'DX' comment + +bool endofscript; +bool tokenready; // only true if UnGetToken was just called + +/* +============== +AddScriptToStack +============== +*/ +void AddScriptToStack( const char *filename ) +{ + size_t size; + + script++; + if( script == &scriptstack[MAX_INCLUDES] ) + COM_FatalError( "script file exceeded MAX_INCLUDES\n" ); + Q_strcpy( script->filename, filename ); + + script->buffer = (char *)COM_LoadFile( script->filename, &size, true ); + + script->line = 1; + script->script_p = script->buffer; + script->end_p = script->buffer + size; + script->separate_stack = false; +} + +/* +============== +IncludeScriptFile +============== +*/ +#ifdef ALLOW_WADS_IN_PACKS +void IncludeScriptFile( const char *filename ) +{ + size_t size; + + script++; + if( script == &scriptstack[MAX_INCLUDES] ) + COM_FatalError( "script file exceeded MAX_INCLUDES\n" ); + Q_strcpy( script->filename, filename ); + MsgDev( D_REPORT, "entering the script %s\n", script->filename ); + script->buffer = (char *)FS_LoadFile( script->filename, &size, false ); + + script->line = 1; + script->script_p = script->buffer; + script->end_p = script->buffer + size; + script->separate_stack = true; +} +#endif + +/* +============== +LoadScriptFile +============== +*/ +void LoadScriptFile( const char *filename ) +{ + script = scriptstack; + AddScriptToStack( filename ); + + endofscript = false; + tokenready = false; +} + + +/* +============== +ParseFromMemory +============== +*/ +void ParseFromMemory( const char *buffer, size_t size ) +{ + script = scriptstack; + script++; + + if( script == &scriptstack[MAX_INCLUDES] ) + COM_FatalError( "script file exceeded MAX_INCLUDES\n" ); + Q_strcpy( script->filename, "memory buffer" ); + + script->buffer = buffer; + script->line = 1; + script->script_p = script->buffer; + script->end_p = script->buffer + size; + script->separate_stack = false; + + endofscript = false; + tokenready = false; +} + +/* +============== +UnGetToken + +Signals that the current token was not used, and should be reported +for the next GetToken. Note that + +GetToken (true); +UnGetToken (); +GetToken (false); + +could cross a line boundary. +============== +*/ +void UnGetToken( void ) +{ + tokenready = true; +} + + +bool EndOfScript( bool crossline ) +{ + bool finished = script->separate_stack; + + if( !crossline ) + COM_FatalError( "line %i is incomplete\n", scriptline ); + + if( !Q_strcmp( script->filename, "memory buffer" )) + { + endofscript = true; + return false; + } + + Mem_Free( (char *)script->buffer, C_FILESYSTEM ); + + if( script == scriptstack + 1 ) + { + endofscript = true; + return false; + } + + MsgDev( D_REPORT, "leave the script %s\n", script->filename ); + script--; + scriptline = script->line; + + // don't iterpret included files as 'solid' script + if( finished ) + return false; + + return GetToken( crossline ); +} + +/* +============== +GetToken +============== +*/ +bool GetToken( bool crossline ) +{ + char *token_p; + + if( tokenready ) + { + // is a token already waiting? + tokenready = false; + return true; + } + + if( script->script_p >= script->end_p ) + return EndOfScript( crossline ); + +// +// skip space +// +skipspace: + while( *script->script_p <= 32 && *script->script_p >= 0 ) + { + if( script->script_p >= script->end_p ) + return EndOfScript( crossline ); + + if( *script->script_p++ == '\n' ) + { + if( !crossline ) + COM_FatalError( "line %i is incomplete\n", scriptline ); + scriptline = script->line++; + } + } + + if( script->script_p >= script->end_p ) + return EndOfScript( crossline ); + + // ; # // comments + if( *script->script_p == ';' || *script->script_p == '#' || ( *script->script_p == '/' && *((script->script_p) + 1) == '/' )) + { + if( !crossline ) + COM_FatalError( "line %i is incomplete\n", scriptline ); + + if( *script->script_p == '/' ) + script->script_p++; + + if( script->script_p[1] == 'T' && script->script_p[2] == 'X' ) + g_TXcommand = script->script_p[3]; // TX#"-style comment + + if( script->script_p[1] == 'D' && script->script_p[2] == 'X' ) + g_DXspecial = atoi( script->script_p + 3 ); // DX#####"-style comment + + while( *script->script_p++ != '\n' ) + { + if( script->script_p >= script->end_p ) + return EndOfScript( crossline ); + } + + scriptline = script->line++; + goto skipspace; + } + + // /* */ comments + if( script->script_p[0] == '/' && script->script_p[1] == '*' ) + { + if( !crossline ) + COM_FatalError( "line %i is incomplete\n", scriptline ); + + script->script_p += 2; + + while( script->script_p[0] != '*' || script->script_p[1] != '/' ) + { + script->script_p++; + if( script->script_p >= script->end_p ) + return EndOfScript( crossline ); + } + + script->script_p += 2; + goto skipspace; + } + + // copy token + token_p = token; + + if( *script->script_p == '"' ) + { + // quoted token + script->script_p++; + while( *script->script_p != '"' ) + { + *token_p++ = *script->script_p++; + if( script->script_p == script->end_p ) + break; + + if( token_p == &token[MAXTOKEN] ) + COM_FatalError( "token too large on line %i\n", scriptline ); + } + script->script_p++; + } + else + { + // regular token + while(( *script->script_p > 32 || *script->script_p < 0 ) && *script->script_p != ';' ) + { + *token_p++ = *script->script_p++; + if( script->script_p == script->end_p ) + break; + if( token_p == &token[MAXTOKEN] ) + COM_FatalError( "token too large on line %i\n", scriptline ); + } + } + + *token_p = 0; // null terminate + + if( !Q_stricmp( token, "$include" )) + { + GetToken( false ); + MsgDev( D_REPORT, "entering the script %s\n", token ); + AddScriptToStack( token ); + return GetToken( crossline ); + } + + return true; +} + +bool GetTokenAppend( char *buffer, bool crossline ) +{ + bool result; + int i; + + // get the token + result = GetToken( crossline ); + + if( !result || !buffer || token[0] == '\0' ) + return result; + + if( token[0] == '}' ) + scriptdepth--; + + // append? + if( oldscriptline != scriptline ) + { + Q_strcat( buffer, "\n" ); + for( i = 0; i < scriptdepth; i++ ) + Q_strcat( buffer, "\t" ); + } + else Q_strcat( buffer, " " ); + + oldscriptline = scriptline; + Q_strcat( buffer, token ); + + if( token[0] == '{' ) + scriptdepth++; + + return result; +} + +/* +============== +TokenAvailable + +Returns true if there is another token on the line +============== +*/ +bool TokenAvailable( void ) +{ + const char *search_p; + + search_p = script->script_p; + + if( search_p >= script->end_p ) + return false; + + while( *search_p <= 32 ) + { + if( *search_p == '\n' ) + return false; + + search_p++; + + if( search_p == script->end_p ) + return false; + + } + + // ; # // comments + if( *search_p == ';' || *search_p == '#' || ( *search_p == '/' && *((search_p) + 1) == '/' )) + { + while( *search_p == '/' ) + search_p++; + + if( *search_p == 'T' && *((search_p) + 1) == 'X' ) + g_TXcommand = *((search_p) + 2); // TX#"-style comment + + if( *search_p == 'D' && *((search_p) + 1) == 'X' ) + g_DXspecial = atoi( ((search_p) + 2) ); // DX#####"-style comment + + return false; + } + + return true; +} + +/* +============== +TryToken + +check token for available on current line +============== +*/ +bool TryToken( void ) +{ + if( !TokenAvailable( )) + return false; + + GetToken( false ); + return true; +} + +void CheckToken( const char *match ) +{ + GetToken( true ); + + if( Q_strcmp( token, match )) + COM_FatalError( "missing '%s' at line %d\n", match, scriptline ); +} + +void Parse1DMatrix( int x, vec_t *m ) +{ + int i; + + CheckToken( "(" ); + + for( i = 0; i < x; i++ ) + { + GetToken( false ); + m[i] = atof( token ); + } + + CheckToken( ")" ); +} + +void Parse1DMatrixAppend( char *buffer, int x, vec_t *m ) +{ + if( !GetTokenAppend( buffer, true ) || Q_strcmp( token, "(" )) + COM_FatalError( "missing '(' at line %d\n", scriptline ); + + for( int i = 0; i < x; i++ ) + { + if( !GetTokenAppend( buffer, false )) + COM_FatalError( "line %d is incomplete\n", scriptline ); + m[i] = atof( token ); + } + + if( !GetTokenAppend( buffer, true ) || Q_strcmp( token, ")" )) + COM_FatalError( "missing ')' at line %d\n", scriptline ); +} + +void Parse2DMatrix( int y, int x, vec_t *m ) +{ + int i; + + CheckToken( "(" ); + + for( i = 0; i < y; i++ ) + { + Parse1DMatrix( x, m + i * x ); + } + + CheckToken( ")" ); +} + +void Parse3DMatrix( int z, int y, int x, vec_t *m ) +{ + int i; + + CheckToken( "(" ); + + for( i = 0; i < z; i++ ) + { + Parse2DMatrix( y, x, m + i * x * y ); + } + + CheckToken( ")" ); +} + +/* +================= +SkipBracedSection + +Skips until a matching close brace is found. +Internal brace depths are properly skipped. +================= +*/ +void SkipBracedSection( int depth ) +{ + do { + if( !GetToken( true )) + break; + if( !Q_stricmp( token, "{" )) + depth++; + else if( !Q_stricmp( token, "}" )) + depth--; + } while( depth ); +} + +/* +================= +SkipAtRestLine + +Skips until a newline. +================= +*/ +void SkipAtRestLine( void ) +{ + while( TryToken( )); +} + +bool GetTokenizerStatus( char **pFilename, int *pLine ) +{ + // is this the default state? + if( !script ) + return false; + + if( script->script_p >= script->end_p ) + return false; + + if( pFilename ) + *pFilename = script->filename; + + if( pLine ) + *pLine = script->line; + + return true; +} + +void TokenError( const char *fmt, ... ) +{ + static char output[1024]; + va_list args; + + char *pFilename; + int iLineNumber; + + if( GetTokenizerStatus( &pFilename, &iLineNumber )) + { + va_start( args, fmt ); + vsprintf( output, fmt, args ); + + COM_FatalError( "%s(%d): - %s", pFilename, iLineNumber, output ); + } + else + { + va_start( args, fmt ); + vsprintf( output, fmt, args ); + COM_FatalError( "%s", output ); + } +} \ No newline at end of file diff --git a/utils/common/scriplib.h b/utils/common/scriplib.h new file mode 100644 index 0000000..a109bd8 --- /dev/null +++ b/utils/common/scriplib.h @@ -0,0 +1,53 @@ +/* +=========================================================================== +Copyright (C) 1997-2006 Id Software, Inc. + +This file is part of Quake 2 Tools source code. + +Quake 2 Tools source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake 2 Tools source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Quake 2 Tools source code; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// scriplib.h + +#ifndef __CMDLIB__ +#include "cmdlib.h" +#endif + +#define MAXTOKEN 2048 + +extern char token[MAXTOKEN]; +extern char g_TXcommand; +extern int g_DXspecial; +extern int scriptline; +extern bool endofscript; + +void LoadScriptFile( const char *filename ); +void IncludeScriptFile( const char *filename ); +void ParseFromMemory( const char *buffer, size_t size ); + +bool GetToken( bool crossline ); +bool GetTokenAppend( char *buffer, bool crossline ); +void UnGetToken( void ); +bool TokenAvailable( void ); +bool TryToken( void ); +void CheckToken( const char *match ); +void Parse1DMatrix( int x, vec_t *m ); +void Parse1DMatrixAppend( char *buffer, int x, vec_t *m ); +void Parse2DMatrix( int y, int x, vec_t *m ); +void Parse3DMatrix( int z, int y, int x, vec_t *m ); +void TokenError( const char *fmt, ... ); +void SkipBracedSection( int depth ); +void SkipAtRestLine( void ); diff --git a/utils/common/shaders.cpp b/utils/common/shaders.cpp new file mode 100644 index 0000000..cb56bb5 --- /dev/null +++ b/utils/common/shaders.cpp @@ -0,0 +1,425 @@ +/* +shaders.cpp - parsing quake3 shaders for map-compile tools +Copyright (C) 2018 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +// cmdlib.c +#include "stringlib.h" +#include "cmdlib.h" +#include "mathlib.h" +#include "filesystem.h" +#include "shaders.h" +#include "scriplib.h" +#include "bspfile.h" + +shaderInfo_t *shaderInfo = NULL; +int numShaderInfo; +surfaceParm_t surfaceParams[] = +{ +{ "default", CONTENTS_NONE, FSHADER_DEFAULT }, +{ "lightgrid", CONTENTS_NONE, FSHADER_REMOVE }, +{ "antiportal", CONTENTS_NONE, FSHADER_REMOVE }, +{ "skip", CONTENTS_EMPTY, FSHADER_SKIP }, +{ "origin", CONTENTS_ORIGIN, FSHADER_REMOVE }, +{ "areaportal", CONTENTS_NONE, FSHADER_REMOVE }, +{ "trans", CONTENTS_NONE, FSHADER_DETAIL }, // FIXME: this should be CONTENTS_TRANSLUCENT +{ "detail", CONTENTS_SOLID, FSHADER_DETAIL }, +{ "structural", CONTENTS_SOLID, FSHADER_DEFAULT }, +{ "hint", CONTENTS_EMPTY, FSHADER_HINT }, +{ "nodraw", CONTENTS_SOLID, FSHADER_NODRAW }, +{ "alphashadow", CONTENTS_NONE, FSHADER_DEFAULT }, +{ "lightfilter", CONTENTS_NONE, FSHADER_DEFAULT }, +{ "nolightmap", CONTENTS_NONE, FSHADER_NOLIGHTMAP }, +{ "pointlight", CONTENTS_NONE, FSHADER_DEFAULT }, +{ "nonsolid", CONTENTS_EMPTY, FSHADER_NOCLIP }, +{ "trigger", CONTENTS_SOLID, FSHADER_TRIGGER }, +{ "water", CONTENTS_WATER, FSHADER_NOLIGHTMAP }, +{ "slime", CONTENTS_SLIME, FSHADER_NOLIGHTMAP }, +{ "lava", CONTENTS_LAVA, FSHADER_DEFAULT }, +{ "clip", CONTENTS_SOLID, FSHADER_CLIP }, +{ "playerclip", CONTENTS_SOLID, FSHADER_CLIP }, +{ "monsterclip", CONTENTS_EMPTY, FSHADER_CLIP }, // FIXME +{ "nodrop", CONTENTS_NONE, FSHADER_REMOVE }, +{ "clusterportal", CONTENTS_NONE, FSHADER_REMOVE }, +{ "donotenter", CONTENTS_NONE, FSHADER_REMOVE }, +{ "botclip", CONTENTS_NONE, FSHADER_REMOVE }, +{ "fog", CONTENTS_FOG, FSHADER_DEFAULT }, +{ "sky", CONTENTS_SKY, FSHADER_NOLIGHTMAP }, +{ "slick", CONTENTS_NONE, FSHADER_DEFAULT }, +{ "noimpact", CONTENTS_NONE, FSHADER_DEFAULT }, +{ "nomarks", CONTENTS_NONE, FSHADER_DEFAULT }, +{ "ladder", CONTENTS_NONE, FSHADER_DEFAULT }, +{ "nodamage", CONTENTS_NONE, FSHADER_DEFAULT }, +{ "metalsteps", CONTENTS_NONE, FSHADER_DEFAULT }, +{ "snowsteps", CONTENTS_NONE, FSHADER_DEFAULT }, +{ "woodsteps", CONTENTS_NONE, FSHADER_DEFAULT }, +{ "dmgthrough", CONTENTS_NONE, FSHADER_DEFAULT }, +{ "flesh", CONTENTS_NONE, FSHADER_DEFAULT }, +{ "nosteps", CONTENTS_NONE, FSHADER_DEFAULT }, +{ "nodlight", CONTENTS_NONE, FSHADER_DEFAULT }, +{ "dust", CONTENTS_NONE, FSHADER_DEFAULT }, +{ NULL, 0, 0 } +}; + +void TCModIdentity( matrix3x3 mod ) +{ + mod[0][0] = 1.0f; + mod[0][1] = 0.0f; + mod[0][2] = 0.0f; + mod[1][0] = 0.0f; + mod[1][1] = 1.0f; + mod[1][2] = 0.0f; + mod[2][0] = 0.0f; + mod[2][1] = 0.0f; + mod[2][2] = 1.0f; +} + + +/* +================= +ApplySurfaceParm + +applies a named surfaceparm to the supplied flags +================= +*/ +bool ApplySurfaceParm( const char *name, int *contents, int *compileFlags ) +{ + int fake; + surfaceParm_t *sp; + + if( name == NULL ) + return false; + + if( contents == NULL ) + contents = &fake; + + if( compileFlags == NULL ) + compileFlags = &fake; + + for( sp = surfaceParams; sp->name != NULL; sp++ ) + { + if( !Q_stricmp( name, sp->name )) + { + if( sp->contents != CONTENTS_NONE ) + *contents = sp->contents; + SetBits( *compileFlags, sp->compileFlags ); + + return true; + } + } + + return false; +} + +/* +================= +AllocShaderInfo + +allocates and initializes a new shader +================= +*/ +static shaderInfo_t *AllocShaderInfo( void ) +{ + shaderInfo_t *si; + + if( shaderInfo == NULL ) + { + shaderInfo = (shaderInfo_t *)Mem_Alloc( sizeof( shaderInfo_t ) * MAX_SHADER_INFO ); + numShaderInfo = 0; + } + + if( numShaderInfo == MAX_SHADER_INFO ) + COM_FatalError( "MAX_SHADER_INFO limit exceeded\n" ); + + si = &shaderInfo[numShaderInfo]; + numShaderInfo++; + + ApplySurfaceParm( "default", &si->contents, &si->flags ); + + si->lightmapSampleOffset = DEFAULT_LIGHTMAP_SAMPLE_OFFSET; + si->vertexShadows = true; + si->vertexScale = 1.0; + + TCModIdentity( si->mod ); + + return si; +} + +/* +================= +LoadShaderImages + +loads a shader's images +================= +*/ +static void LoadShaderImages( shaderInfo_t *si ) +{ + char shaderPath[MAX_SHADERPATH]; + + Q_snprintf( shaderPath, sizeof( shaderPath ), "%s.tga", si->name ); + + if( FS_FileExists( si->editorImagePath, false )) + Q_strncpy( si->imagePath, si->editorImagePath, sizeof( si->imagePath )); + else if( FS_FileExists( shaderPath, false )) + Q_strncpy( si->imagePath, shaderPath, sizeof( si->imagePath )); + else if( FS_FileExists( si->lightImagePath, false )) + Q_strncpy( si->imagePath, si->lightImagePath, sizeof( si->imagePath )); + else if( FS_FileExists( si->implicitImagePath, false )) + Q_strncpy( si->imagePath, si->implicitImagePath, sizeof( si->imagePath )); + + // leave as error + if( !COM_CheckString( si->imagePath )) + Q_strcpy( si->imagePath, shaderPath ); + + if( !COM_CheckString( si->implicitImagePath ) && FS_FileExists( si->imagePath, false )) + Q_strcpy( si->implicitImagePath, si->imagePath ); +} + +/* +================= +FinishShader + +loads a shader's images +================= +*/ +static void FinishShader( shaderInfo_t *si ) +{ + si->finished = true; +} + +/* +================= +ShaderInfoForShader + +finds a shaderinfo for a named shader +================= +*/ +shaderInfo_t *ShaderInfoForShader( const char *shaderName ) +{ + char shader[MAX_SHADERPATH]; + shaderInfo_t *si; + + // strip off extension + Q_snprintf( shader, sizeof( shader ), "textures/%s", shaderName ); + COM_StripExtension( shader ); + + for( int i = 0; i < numShaderInfo; i++ ) + { + si = &shaderInfo[i]; + + if( !Q_stricmp( shader, si->name )) + { + if( !si->finished ) + { + LoadShaderImages( si ); + FinishShader( si ); + } + return si; + } + } + + si = AllocShaderInfo(); + Q_strcpy( si->name, shader ); + SetBits( si->flags, FSHADER_DEFAULTED ); + LoadShaderImages( si ); + FinishShader( si ); + + return si; +} + +/* +================= +ParseShaderFile + +parses a shader file into discrete shaderInfo_t +================= +*/ +static void ParseShaderFile( const char *filename ) +{ + char shaderText[16384]; + char *filedata; + size_t filesize; + shaderInfo_t *si = NULL; + char *suffix; + + shaderText[0] = '\0'; + + // load the shader + filedata = (char *)FS_LoadFile( filename, &filesize, false ); + ParseFromMemory( filedata, filesize ); + + while ( 1 ) + { + // copy shader text to the shaderinfo + if( si != NULL && shaderText[0] != '\0' ) + { + Q_strcat( shaderText, "\n" ); + si->shaderText = copystring( shaderText ); + } + + shaderText[0] = '\0'; + + if( !GetToken( true )) + break; + + // shader name is initial token + si = AllocShaderInfo(); + Q_strncpy( si->name, token, sizeof( si->name )); + + // ignore ":q3map" suffix + suffix = Q_strstr( si->name, ":q3map" ); + if( suffix != NULL ) *suffix = '\0'; + + /* handle { } section */ + if( !GetTokenAppend( shaderText, true )) + break; + + if( Q_strcmp( token, "{" )) + { + if( si != NULL ) + { + COM_FatalError( "%s, line %d: { not found!\nFound instead: %s\nLast known shader: %s", + filename, scriptline, token, si->name ); + } + else + { + COM_FatalError( "%s, line %d: { not found!\nFound instead: %s", filename, scriptline, token ); + } + } + + while( 1 ) + { + if( !GetTokenAppend( shaderText, true )) + break; + + if( !Q_strcmp( token, "}" )) + break; + + // ----------------------------------------------------------------- + // shader stages (passes) + // ----------------------------------------------------------------- + + // parse stage directives + if( !Q_strcmp( token, "{" )) + { + while ( 1 ) + { + if( !GetTokenAppend( shaderText, true )) + break; + + if( !Q_strcmp( token, "}" )) + break; + + // digest any images + if( !Q_stricmp( token, "map" ) || !Q_stricmp( token, "clampMap" ) || !Q_stricmp( token, "animMap" )) + { + // skip one token for animated stages + if( !Q_stricmp( token, "animMap" )) + GetTokenAppend( shaderText, false ); + + GetTokenAppend( shaderText, false ); + if( token[0] != '*' && token[0] != '$' ) + { + Q_strcpy( si->implicitImagePath, token ); + COM_DefaultExtension( si->implicitImagePath, ".tga" ); + } + } + } + } + + // ----------------------------------------------------------------- + // surfaceparm * directives + // ----------------------------------------------------------------- + else if( !Q_stricmp( token, "surfaceparm" )) + { + GetTokenAppend( shaderText, false ); + if( !ApplySurfaceParm( token, &si->contents, &si->flags )) + MsgDev( D_WARN, "unknown surfaceparm: \"%s\"\n", token ); + } + + // ----------------------------------------------------------------- + // image directives + // ----------------------------------------------------------------- + + // qer_editorimage + else if( !Q_stricmp( token, "qer_editorImage" )) + { + GetTokenAppend( shaderText, false ); + Q_strcpy( si->editorImagePath, token ); + COM_DefaultExtension( si->editorImagePath, ".tga" ); + } + + // q3map_lightimage + else if( !Q_stricmp( token, "q3map_lightImage" )) + { + GetTokenAppend( shaderText, false ); + Q_strcpy( si->lightImagePath, token ); + COM_DefaultExtension( si->lightImagePath, ".tga" ); + } + + // skyparms + else if( !Q_stricmp( token, "skyParms" )) + { + GetTokenAppend( shaderText, false ); + + if( Q_stricmp( token, "-" ) && Q_stricmp( token, "full" )) + { + Q_strcpy( si->skyParmsImageBase, token ); + + // use top image as sky light image + if( si->lightImagePath[0] == '\0' ) + Q_sprintf( si->lightImagePath, "%s_up.tga", si->skyParmsImageBase ); + } + + // skip rest of line + GetTokenAppend( shaderText, false ); + GetTokenAppend( shaderText, false ); + } + + // ----------------------------------------------------------------- + // skip + // ----------------------------------------------------------------- + + // ignore all other tokens on the line + while( TokenAvailable() && GetTokenAppend( shaderText, false )); + } + } + + Mem_Free( filedata, C_FILESYSTEM ); +} + +void LoadShaderInfo( void ) +{ + search_t *search = FS_Search( "scripts/*.shader", true, false ); + if( !search ) return; + + for( int i = 0; i < search->numfilenames; i++ ) + ParseShaderFile( search->filenames[i] ); + Mem_Free( search, C_FILESYSTEM ); + + MsgDev( D_INFO, "%9d shaderInfo\n", numShaderInfo ); +} + +void FreeShaderInfo( void ) +{ + shaderInfo_t *si; + + for( int i = 0; i < numShaderInfo; i++ ) + { + si = &shaderInfo[i]; + freestring( si->shaderText ); + } + + Mem_Free( shaderInfo ); + shaderInfo = NULL; +} \ No newline at end of file diff --git a/utils/common/shaders.h b/utils/common/shaders.h new file mode 100644 index 0000000..805412f --- /dev/null +++ b/utils/common/shaders.h @@ -0,0 +1,75 @@ +/* +shaders.h - parsing quake3 shaders for map-compile tools +Copyright (C) 2018 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef SHADERS_H +#define SHADERS_H + +#define MAX_SHADER_INFO 8192 +#define MAX_SHADERPATH 64 + +#define DEFAULT_LIGHTMAP_SAMPLE_SIZE 16 +#define DEFAULT_LIGHTMAP_SAMPLE_OFFSET 1.0f + +#define FSHADER_DEFAULT 0 +#define FSHADER_NOCLIP BIT( 0 ) +#define FSHADER_NODRAW BIT( 1 ) +#define FSHADER_DETAIL BIT( 2 ) +#define FSHADER_NOLIGHTMAP BIT( 3 ) +#define FSHADER_SKIP BIT( 4 ) +#define FSHADER_CLIP BIT( 5 ) +#define FSHADER_HINT BIT( 6 ) +#define FSHADER_REMOVE BIT( 7 ) +#define FSHADER_DEFAULTED BIT( 8 ) // user-shader is not specified +#define FSHADER_TRIGGER BIT( 9 ) + +typedef vec_t matrix3x3[3][3]; + +typedef struct surfaceParm_s +{ + const char *name; + int contents; + int compileFlags; +} surfaceParm_t; + +typedef struct shaderInfo_s +{ + char name[MAX_SHADERPATH]; + int contents; + int flags; + float value; // light value + + int lightmapSampleSize; // lightmap sample size + float lightmapSampleOffset; // ydnar: lightmap sample offset (default: 1.0) + float shadeAngleDegrees; // ydnar: breaking angle for smooth shading (degrees) + matrix3x3 mod; // texture mod + + char imagePath[MAX_SHADERPATH]; // path to real image + + char implicitImagePath[MAX_SHADERPATH]; + char editorImagePath[MAX_SHADERPATH]; // use this image to generate texture coordinates + char lightImagePath[MAX_SHADERPATH]; // use this image to generate color / averageColor + char skyParmsImageBase[MAX_SHADERPATH]; + char *shaderText; + + float vertexScale; // vertex light scale + bool vertexShadows; // shadows will be casted at this surface even when vertex lit + bool finished; +} shaderInfo_t; + +shaderInfo_t *ShaderInfoForShader( const char *shaderName ); +void LoadShaderInfo( void ); +void FreeShaderInfo( void ); + +#endif//SHADERS_H \ No newline at end of file diff --git a/utils/common/squish.h b/utils/common/squish.h new file mode 100644 index 0000000..bfaa293 --- /dev/null +++ b/utils/common/squish.h @@ -0,0 +1,266 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_H +#define SQUISH_H + +//! All squish API functions live in this namespace. +namespace squish { + +// ----------------------------------------------------------------------------- + +//! Typedef a quantity that is a single unsigned byte. +typedef unsigned char u8; + +// ----------------------------------------------------------------------------- + +enum +{ + //! Use DXT1 compression. + kDxt1 = ( 1 << 0 ), + + //! Use DXT3 compression. + kDxt3 = ( 1 << 1 ), + + //! Use DXT5 compression. + kDxt5 = ( 1 << 2 ), + + //! Use ATI2 compression. + kAti2 = ( 1 << 5 ), + + //! Use a very slow but very high quality colour compressor. + kColourIterativeClusterFit = ( 1 << 8 ), + + //! Use a slow but high quality colour compressor (the default). + kColourClusterFit = ( 1 << 3 ), + + //! Use a fast but low quality colour compressor. + kColourRangeFit = ( 1 << 4 ), + + //! Weight the colour by alpha during cluster fit (disabled by default). + kWeightColourByAlpha = ( 1 << 7 ) +}; + +// ----------------------------------------------------------------------------- + +/*! @brief Compresses a 4x4 block of pixels. + + @param rgba The rgba values of the 16 source pixels. + @param mask The valid pixel mask. + @param block Storage for the compressed DXT block. + @param flags Compression flags. + @param metric An optional perceptual metric. + + The source pixels should be presented as a contiguous array of 16 rgba + values, with each component as 1 byte each. In memory this should be: + + { r1, g1, b1, a1, .... , r16, g16, b16, a16 } + + The mask parameter enables only certain pixels within the block. The lowest + bit enables the first pixel and so on up to the 16th bit. Bits beyond the + 16th bit are ignored. Pixels that are not enabled are allowed to take + arbitrary colours in the output block. An example of how this can be used + is in the CompressImage function to disable pixels outside the bounds of + the image when the width or height is not divisible by 4. + + The flags parameter should specify either kDxt1, kDxt3 or kDxt5 compression, + however, DXT1 will be used by default if none is specified. When using DXT1 + compression, 8 bytes of storage are required for the compressed DXT block. + DXT3 and DXT5 compression require 16 bytes of storage per block. + + The flags parameter can also specify a preferred colour compressor to use + when fitting the RGB components of the data. Possible colour compressors + are: kColourClusterFit (the default), kColourRangeFit (very fast, low + quality) or kColourIterativeClusterFit (slowest, best quality). + + When using kColourClusterFit or kColourIterativeClusterFit, an additional + flag can be specified to weight the importance of each pixel by its alpha + value. For images that are rendered using alpha blending, this can + significantly increase the perceived quality. + + The metric parameter can be used to weight the relative importance of each + colour channel, or pass NULL to use the default uniform weight of + { 1.0f, 1.0f, 1.0f }. This replaces the previous flag-based control that + allowed either uniform or "perceptual" weights with the fixed values + { 0.2126f, 0.7152f, 0.0722f }. If non-NULL, the metric should point to a + contiguous array of 3 floats. +*/ +void CompressMasked( u8 const* rgba, int mask, void* block, int flags, float* metric = 0 ); + +// ----------------------------------------------------------------------------- + +/*! @brief Compresses a 4x4 block of pixels. + + @param rgba The rgba values of the 16 source pixels. + @param block Storage for the compressed DXT block. + @param flags Compression flags. + @param metric An optional perceptual metric. + + The source pixels should be presented as a contiguous array of 16 rgba + values, with each component as 1 byte each. In memory this should be: + + { r1, g1, b1, a1, .... , r16, g16, b16, a16 } + + The flags parameter should specify either kDxt1, kDxt3 or kDxt5 compression, + however, DXT1 will be used by default if none is specified. When using DXT1 + compression, 8 bytes of storage are required for the compressed DXT block. + DXT3 and DXT5 compression require 16 bytes of storage per block. + + The flags parameter can also specify a preferred colour compressor to use + when fitting the RGB components of the data. Possible colour compressors + are: kColourClusterFit (the default), kColourRangeFit (very fast, low + quality) or kColourIterativeClusterFit (slowest, best quality). + + When using kColourClusterFit or kColourIterativeClusterFit, an additional + flag can be specified to weight the importance of each pixel by its alpha + value. For images that are rendered using alpha blending, this can + significantly increase the perceived quality. + + The metric parameter can be used to weight the relative importance of each + colour channel, or pass NULL to use the default uniform weight of + { 1.0f, 1.0f, 1.0f }. This replaces the previous flag-based control that + allowed either uniform or "perceptual" weights with the fixed values + { 0.2126f, 0.7152f, 0.0722f }. If non-NULL, the metric should point to a + contiguous array of 3 floats. + + This method is an inline that calls CompressMasked with a mask of 0xffff, + provided for compatibility with older versions of squish. +*/ +inline void Compress( u8 const* rgba, void* block, int flags, float* metric = 0 ) +{ + CompressMasked( rgba, 0xffff, block, flags, metric ); +} + +// ----------------------------------------------------------------------------- + +/*! @brief Decompresses a 4x4 block of pixels. + + @param rgba Storage for the 16 decompressed pixels. + @param block The compressed DXT block. + @param flags Compression flags. + + The decompressed pixels will be written as a contiguous array of 16 rgba + values, with each component as 1 byte each. In memory this is: + + { r1, g1, b1, a1, .... , r16, g16, b16, a16 } + + The flags parameter should specify either kDxt1, kDxt3 or kDxt5 compression, + however, DXT1 will be used by default if none is specified. All other flags + are ignored. +*/ +void Decompress( u8* rgba, void const* block, int flags ); + +// ----------------------------------------------------------------------------- + +/*! @brief Computes the amount of compressed storage required. + + @param width The width of the image. + @param height The height of the image. + @param flags Compression flags. + + The flags parameter should specify either kDxt1, kDxt3 or kDxt5 compression, + however, DXT1 will be used by default if none is specified. All other flags + are ignored. + + Most DXT images will be a multiple of 4 in each dimension, but this + function supports arbitrary size images by allowing the outer blocks to + be only partially used. +*/ +int GetStorageRequirements( int width, int height, int flags ); + +// ----------------------------------------------------------------------------- + +/*! @brief Compresses an image in memory. + + @param rgba The pixels of the source. + @param width The width of the source image. + @param height The height of the source image. + @param blocks Storage for the compressed output. + @param flags Compression flags. + @param metric An optional perceptual metric. + + The source pixels should be presented as a contiguous array of width*height + rgba values, with each component as 1 byte each. In memory this should be: + + { r1, g1, b1, a1, .... , rn, gn, bn, an } for n = width*height + + The flags parameter should specify either kDxt1, kDxt3 or kDxt5 compression, + however, DXT1 will be used by default if none is specified. When using DXT1 + compression, 8 bytes of storage are required for each compressed DXT block. + DXT3 and DXT5 compression require 16 bytes of storage per block. + + The flags parameter can also specify a preferred colour compressor to use + when fitting the RGB components of the data. Possible colour compressors + are: kColourClusterFit (the default), kColourRangeFit (very fast, low + quality) or kColourIterativeClusterFit (slowest, best quality). + + When using kColourClusterFit or kColourIterativeClusterFit, an additional + flag can be specified to weight the importance of each pixel by its alpha + value. For images that are rendered using alpha blending, this can + significantly increase the perceived quality. + + The metric parameter can be used to weight the relative importance of each + colour channel, or pass NULL to use the default uniform weight of + { 1.0f, 1.0f, 1.0f }. This replaces the previous flag-based control that + allowed either uniform or "perceptual" weights with the fixed values + { 0.2126f, 0.7152f, 0.0722f }. If non-NULL, the metric should point to a + contiguous array of 3 floats. + + Internally this function calls squish::CompressMasked for each block, which + allows for pixels outside the image to take arbitrary values. The function + squish::GetStorageRequirements can be called to compute the amount of memory + to allocate for the compressed output. +*/ +void CompressImage( u8 const* rgba, int width, int height, void* blocks, int flags, float* metric = 0 ); + +// ----------------------------------------------------------------------------- + +/*! @brief Decompresses an image in memory. + + @param rgba Storage for the decompressed pixels. + @param width The width of the source image. + @param height The height of the source image. + @param blocks The compressed DXT blocks. + @param flags Compression flags. + + The decompressed pixels will be written as a contiguous array of width*height + 16 rgba values, with each component as 1 byte each. In memory this is: + + { r1, g1, b1, a1, .... , rn, gn, bn, an } for n = width*height + + The flags parameter should specify either kDxt1, kDxt3 or kDxt5 compression, + however, DXT1 will be used by default if none is specified. All other flags + are ignored. + + Internally this function calls squish::Decompress for each block. +*/ +void DecompressImage( u8* rgba, int width, int height, void const* blocks, int flags ); + +// ----------------------------------------------------------------------------- + +} // namespace squish + +#endif // ndef SQUISH_H + diff --git a/utils/common/squish.lib b/utils/common/squish.lib new file mode 100644 index 0000000..6e99231 Binary files /dev/null and b/utils/common/squish.lib differ diff --git a/utils/common/stringlib.cpp b/utils/common/stringlib.cpp new file mode 100644 index 0000000..c712b0f --- /dev/null +++ b/utils/common/stringlib.cpp @@ -0,0 +1,537 @@ +//======================================================================= +// Copyright (C) XashXT Group 2011 +// stringlib.cpp - safety string routines +//======================================================================= + +#include +#include "stringlib.h" +#include +#include "cmdlib.h" +#include "mathlib.h" + +void Q_strnupr( const char *in, char *out, size_t size_out ) +{ + if( size_out == 0 ) return; + + while( *in && size_out > 1 ) + { + if( *in >= 'a' && *in <= 'z' ) + *out++ = *in++ + 'A' - 'a'; + else *out++ = *in++; + size_out--; + } + *out = '\0'; +} + +void Q_strnlwr( const char *in, char *out, size_t size_out ) +{ + if( size_out == 0 ) return; + + while( *in && size_out > 1 ) + { + if( *in >= 'A' && *in <= 'Z' ) + *out++ = *in++ + 'a' - 'A'; + else *out++ = *in++; + size_out--; + } + *out = '\0'; +} + +bool Q_isdigit( const char *str ) +{ + if( str && *str ) + { + while( isdigit( *str )) str++; + if( !*str ) return true; + } + return false; +} + +int Q_strlen( const char *string ) +{ + if( !string ) return 0; + + int len = 0; + const char *p = string; + while( *p ) + { + p++; + len++; + } + return len; +} + +char Q_toupper( const char in ) +{ + char out; + + if( in >= 'a' && in <= 'z' ) + out = in + 'A' - 'a'; + else out = in; + + return out; +} + +char Q_tolower( const char in ) +{ + char out; + + if( in >= 'A' && in <= 'Z' ) + out = in + 'a' - 'A'; + else out = in; + + return out; +} + +size_t Q_strncat( char *dst, const char *src, size_t size ) +{ + if( !dst || !src || !size ) + return 0; + + register char *d = dst; + register const char *s = src; + register size_t n = size; + size_t dlen; + + // find the end of dst and adjust bytes left but don't go past end + while( n-- != 0 && *d != '\0' ) d++; + dlen = d - dst; + n = size - dlen; + + if( n == 0 ) return( dlen + Q_strlen( s )); + + while( *s != '\0' ) + { + if( n != 1 ) + { + *d++ = *s; + n--; + } + s++; + } + + *d = '\0'; + return( dlen + ( s - src )); // count does not include NULL +} + +size_t Q_strncpy( char *dst, const char *src, size_t size ) +{ + if( !dst || !src || !size ) + return 0; + + register char *d = dst; + register const char *s = src; + register size_t n = size; + + // copy as many bytes as will fit + if( n != 0 && --n != 0 ) + { + do + { + if(( *d++ = *s++ ) == 0 ) + break; + } while( --n != 0 ); + } + + // not enough room in dst, add NULL and traverse rest of src + if( n == 0 ) + { + if( size != 0 ) + *d = '\0'; // NULL-terminate dst + while( *s++ ); + } + return ( s - src - 1 ); // count does not include NULL +} + +char *copystring( const char *s ) +{ + if( !s ) return NULL; + + char *b = (char *)Mem_Alloc( Q_strlen( s ) + 1, C_STRING ); + Q_strcpy( b, s ); + + return b; +} + +char *Q_strchr( const char *s, char c ) +{ + int len = Q_strlen( s ); + + while( len-- ) + { + if( *++s == c ) + return (char *)s; + } + return 0; +} + +char *Q_strrchr( const char *s, char c ) +{ + int len = Q_strlen( s ); + + s += len; + + while( len-- ) + { + if( *--s == c ) + return (char *)s; + } + return 0; +} + +int Q_strnicmp( const char *s1, const char *s2, int n ) +{ + int c1, c2; + + if( s1 == NULL ) + { + if( s2 == NULL ) return 0; + else return -1; + } + else if( s2 == NULL ) + return 1; + + do { + c1 = *s1++; + c2 = *s2++; + + if( !n-- ) return 0; // strings are equal until end point + + if( c1 != c2 ) + { + if( c1 >= 'a' && c1 <= 'z' ) c1 -= ('a' - 'A'); + if( c2 >= 'a' && c2 <= 'z' ) c2 -= ('a' - 'A'); + if( c1 != c2 ) return c1 < c2 ? -1 : 1; + } + } while( c1 ); + + // strings are equal + return 0; +} + +int Q_strncmp( const char *s1, const char *s2, int n ) +{ + int c1, c2; + + if( s1 == NULL ) + { + if( s2 == NULL ) return 0; + else return -1; + } + else if( s2 == NULL ) + return 1; + + do { + c1 = *s1++; + c2 = *s2++; + + // strings are equal until end point + if( !n-- ) return 0; + if( c1 != c2 ) return c1 < c2 ? -1 : 1; + + } while( c1 ); + + // strings are equal + return 0; +} + +char *Q_strstr( const char *string, const char *string2 ) +{ + int c, len; + + if( !string || !string2 ) return NULL; + + c = *string2; + len = Q_strlen( string2 ); + + while( string ) + { + for( ; *string && *string != c; string++ ); + + if( *string ) + { + if( !Q_strncmp( string, string2, len )) + break; + string++; + } + else return NULL; + } + return (char *)string; +} + +char *Q_stristr( const char *string, const char *string2 ) +{ + int c, len; + + if( !string || !string2 ) return NULL; + + c = Q_tolower( *string2 ); + len = Q_strlen( string2 ); + + while( string ) + { + for( ; *string && Q_tolower( *string ) != c; string++ ); + + if( *string ) + { + if( !Q_strnicmp( string, string2, len )) + break; + string++; + } + else return NULL; + } + return (char *)string; +} + +int Q_vsnprintf( char *buffer, size_t buffersize, const char *format, va_list args ) +{ + size_t result; + + __try + { + result = _vsnprintf( buffer, buffersize, format, args ); + } + + // to prevent crash while output + __except( EXCEPTION_EXECUTE_HANDLER ) + { + Q_strncpy( buffer, "^1sprintf throw exception^7\n", buffersize ); + result = buffersize; + } + + if( result < 0 || result >= buffersize ) + { + buffer[buffersize - 1] = '\0'; + return -1; + } + return result; +} + +int Q_snprintf( char *buffer, size_t buffersize, const char *format, ... ) +{ + va_list args; + int result; + + va_start( args, format ); + result = Q_vsnprintf( buffer, buffersize, format, args ); + va_end( args ); + + return result; +} + +int Q_sprintf( char *buffer, const char *format, ... ) +{ + va_list args; + int result; + + va_start( args, format ); + result = Q_vsnprintf( buffer, 99999, format, args ); + va_end( args ); + + return result; +} + +/* +============ +va + +does a varargs printf into a temp buffer, +so I don't need to have varargs versions +of all text functions. +============ +*/ +char *va( const char *format, ... ) +{ + va_list argptr; + static char string[64][1024], *s; + static int stringindex = 0; + + s = string[stringindex]; + stringindex = (stringindex + 1) & 63; + va_start( argptr, format ); + Q_vsnprintf( s, sizeof( string[0] ), format, argptr ); + va_end( argptr ); + + return s; +} + +char *Q_pretifymem( float value, int digitsafterdecimal ) +{ + static char output[8][32]; + static int current; + float onekb = 1024.0f; + float onemb = onekb * onekb; + char suffix[8]; + char *out = output[current]; + char val[32], *i, *o, *dot; + int pos; + + current = ( current + 1 ) & ( 8 - 1 ); + + // first figure out which bin to use + if( value > onemb ) + { + value /= onemb; + Q_sprintf( suffix, " Mb" ); + } + else if( value > onekb ) + { + value /= onekb; + Q_sprintf( suffix, " Kb" ); + } + else Q_sprintf( suffix, " bytes" ); + + // clamp to >= 0 + digitsafterdecimal = Q_max( digitsafterdecimal, 0 ); + + // if it's basically integral, don't do any decimals + if( fabs( value - (int)value ) < 0.00001 ) + { + Q_sprintf( val, "%i%s", (int)value, suffix ); + } + else + { + char fmt[32]; + + // otherwise, create a format string for the decimals + Q_sprintf( fmt, "%%.%if%s", digitsafterdecimal, suffix ); + Q_sprintf( val, fmt, value ); + } + + // copy from in to out + i = val; + o = out; + + // search for decimal or if it was integral, find the space after the raw number + dot = Q_strstr( i, "." ); + if( !dot ) dot = Q_strstr( i, " " ); + + pos = dot - i; // compute position of dot + pos -= 3; // don't put a comma if it's <= 3 long + + while( *i ) + { + // if pos is still valid then insert a comma every third digit, except if we would be + // putting one in the first spot + if( pos >= 0 && !( pos % 3 )) + { + // never in first spot + if( o != out ) *o++ = ','; + } + + pos--; // count down comma position + *o++ = *i++; // copy rest of data as normal + } + *o = 0; // terminate + + return out; +} + +void _Q_timestring( int seconds, char *msg, size_t size ) +{ + int nMin = seconds / 60; + int nSec = seconds - nMin * 60; + int nHour = nMin / 60; + char *ext[2] = { "", "s" }; + + nMin -= nHour * 60; + + if( nHour > 0 ) + Q_snprintf( msg, size, "%d hour%s, %d minute%s, %d second%s", nHour, ext[nHour != 1], nMin, ext[nMin != 1], nSec, ext[nSec != 1] ); + else if ( nMin > 0 ) + Q_snprintf( msg, size, "%d minute%s, %d second%s", nMin, ext[nMin != 1], nSec, ext[nSec != 1] ); + else Q_snprintf( msg, size, "%d second%s", nSec, ext[nSec != 1] ); +} + +/* +============== +COM_IsSingleChar + +interpert this character as single +============== +*/ +static int COM_IsSingleChar( char c ) +{ + if( c == '{' || c == '}' || c == ')' || c == '(' || c == '\'' || c == ',' ) + return true; + return false; +} + +/* +============== +COM_ParseFile + +text parser +============== +*/ +char *COM_ParseFile( char *data, char *token ) +{ + int c, len; + + if( !token ) + return NULL; + + len = 0; + token[0] = 0; + + if( !data ) + return NULL; +// skip whitespace +skipwhite: + while(( c = ((byte)*data)) <= ' ' ) + { + if( c == 0 ) + return NULL; // end of file; + data++; + } + + // skip // comments + if( c=='/' && data[1] == '/' ) + { + while( *data && *data != '\n' ) + data++; + goto skipwhite; + } + + // handle quoted strings specially + if( c == '\"' ) + { + data++; + while( 1 ) + { + c = (byte)*data++; + if( c == '\"' || !c ) + { + token[len] = 0; + return data; + } + token[len] = c; + len++; + } + } + + // parse single characters + if( COM_IsSingleChar( c )) + { + token[len] = c; + len++; + token[len] = 0; + return data + 1; + } + + // parse a regular word + do + { + token[len] = c; + data++; + len++; + c = ((byte)*data); + + if( COM_IsSingleChar( c )) + break; + } while( c > 32 ); + + token[len] = 0; + + return data; +} \ No newline at end of file diff --git a/utils/common/stringlib.h b/utils/common/stringlib.h new file mode 100644 index 0000000..66697d6 --- /dev/null +++ b/utils/common/stringlib.h @@ -0,0 +1,53 @@ +//======================================================================= +// Copyright (C) XashXT Group 2011 +// stringlib.h - safety string routines +//======================================================================= +#ifndef STRINGLIB_H +#define STRINGLIB_H + +#include +#include +#include + +extern void Q_strnupr( const char *in, char *out, size_t size_out ); +extern void Q_strnlwr( const char *in, char *out, size_t size_out ); +extern bool Q_isdigit( const char *str ); +extern int Q_strlen( const char *string ); +extern char Q_toupper( const char in ); +extern char Q_tolower( const char in ); +extern size_t Q_strncat( char *dst, const char *src, size_t size ); +extern size_t Q_strncpy( char *dst, const char *src, size_t size ); +extern char *copystring( const char *s ); // don't forget release memory after use +#define freestring( a ) ( Mem_Free( a, C_STRING )) +char *Q_strchr( const char *s, char c ); +char *Q_strrchr( const char *s, char c ); +int Q_strnicmp( const char *s1, const char *s2, int n ); +int Q_strncmp( const char *s1, const char *s2, int n ); +char *Q_strstr( const char *string, const char *string2 ); +char *Q_stristr( const char *string, const char *string2 ); +int Q_vsnprintf( char *buffer, size_t buffersize, const char *format, va_list args ); +int Q_snprintf( char *buffer, size_t buffersize, const char *format, ... ); +int Q_sprintf( char *buffer, const char *format, ... ); +char *Q_pretifymem( float value, int digitsafterdecimal ); +void _Q_timestring( int seconds, char *msg, size_t size ); +char *va( const char *format, ... ); + +#define Q_strupr( in, out ) Q_strnupr( in, out, 99999 ) +#define Q_strlwr( int, out ) Q_strnlwr( in, out, 99999 ) +#define Q_strcat( dst, src ) Q_strncat( dst, src, 99999 ) +#define Q_strcpy( dst, src ) Q_strncpy( dst, src, 99999 ) +#define Q_stricmp( s1, s2 ) Q_strnicmp( s1, s2, 99999 ) +#define Q_strcmp( s1, s2 ) Q_strncmp( s1, s2, 99999 ) +#define Q_vsprintf( buffer, format, args ) Q_vsnprintf( buffer, 99999, format, args ) +#define Q_memprint( val ) Q_pretifymem( val, 2 ) +#define Q_timestring( a, b ) _Q_timestring( a, b, sizeof( b )) +#define Q_isspace( ch ) (ch < 32 || ch > 255) + +char *COM_ParseFile( char *data, char *token ); + +void BuildGammaTable( void ); +float TextureToLinear( int c ); +int LinearToTexture( float f ); +byte TextureLightScale( byte c ); + +#endif//STRINGLIB_H \ No newline at end of file diff --git a/utils/common/threads.cpp b/utils/common/threads.cpp new file mode 100644 index 0000000..ec76252 --- /dev/null +++ b/utils/common/threads.cpp @@ -0,0 +1,248 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include "cmdlib.h" +#define NO_THREAD_NAMES +#include "threads.h" +#include + +#define THREAD_STACK_SIZE (4096 * 1024) // 4 Mb +#define PACIFIER_STEP 40 +#define PACIFIER_REM ( PACIFIER_STEP / 10 ) + +typedef struct thread_s +{ + int number; // threadnum + pfnRunThreads func; // thread func +} thread_t; + +static HANDLE g_threadhandles[MAX_THREADS]; +static thread_t g_threads[MAX_THREADS]; +static int g_dispatch = 0; +static int g_workcount = 0; +static qboolean g_pacifier = false; +static qboolean g_threaded = false; +static pfnThreadWork g_workfunction; +int g_numthreads = -1; +static int g_oldnumthreads; +static int g_oldf = -1; +static bool g_enter; +CRITICAL_SECTION g_crit; + +void UpdatePacifier( float percent ) +{ + int f; + + f = (int)(percent * (float)PACIFIER_STEP); + f = bound( g_oldf, f, PACIFIER_STEP ); + + if( f != g_oldf ) + { + for( int i = g_oldf + 1; i <= f; i++ ) + { + if(( i % PACIFIER_REM ) == 0 ) + { + Msg( "%d%%", ( i / PACIFIER_REM ) * 10 ); + } + else + { + if( i != PACIFIER_STEP ) + { + Msg( "." ); + } + } + } + + g_oldf = f; + } +} + +void StartPacifier( void ) +{ + g_oldf = -1; + UpdatePacifier( 0.001f ); +} + +void EndPacifier( double total ) +{ + UpdatePacifier( 1.0f ); + Msg( " (%.2f secs)\n", total ); +} + +/* +============= +GetThreadWork + +============= +*/ +int GetThreadWork( void ) +{ + int r; + + ThreadLock(); + + if( g_dispatch == g_workcount ) + { + ThreadUnlock(); + return -1; + } + + if( g_pacifier ) + UpdatePacifier( (float)g_dispatch / g_workcount ); + + r = g_dispatch; + g_dispatch++; + ThreadUnlock (); + + return r; +} + +static void ThreadWorkerFunction( int thread ) +{ + int work; + + while( 1 ) + { + work = GetThreadWork (); + if( work == -1 ) break; + g_workfunction( work, thread ); + } +} + +// This runs in the thread and dispatches a RunThreadsFn call. +static DWORD WINAPI InternalRunThreadsFn( LPVOID pData ) +{ + thread_t *pThread = (thread_t *)pData; + + pThread->func( pThread->number ); + + return 0; +} + +void ThreadSetDefault( void ) +{ + SYSTEM_INFO info; + + if( g_numthreads == -1 ) + { + // not set manually + GetSystemInfo( &info ); + g_numthreads = info.dwNumberOfProcessors; + } + + if( g_numthreads < 1 || g_numthreads > MAX_THREADS ) + g_numthreads = 1; + + MsgDev( D_REPORT, "%i threads\n", g_numthreads ); + g_oldnumthreads = g_numthreads; +} + +void ThreadLock( void ) +{ + if( !g_threaded ) return; + + EnterCriticalSection( &g_crit ); + + if( g_enter ) COM_FatalError( "recursive ThreadLock\n" ); + g_enter = true; +} + +void ThreadUnlock( void ) +{ + if( !g_threaded ) return; + + if( !g_enter ) COM_FatalError( "ThreadUnlock without lock\n" ); + g_enter = false; + + LeaveCriticalSection( &g_crit ); +} + +bool ThreadLocked( void ) +{ + return g_enter; +} + +void ThreadPush( void ) +{ + g_numthreads = 1; +} + +void ThreadPop( void ) +{ + g_numthreads = g_oldnumthreads; +} + +/* +============= +RunThreadsOn +============= +*/ +void RunThreadsOn( int workcnt, bool showpacifier, pfnRunThreads func ) +{ + double start, end; + DWORD dwDummy; + int i; + + if( showpacifier ) + { + if( g_numthreads == 1 ) + Msg( " (single-threaded)\n" ); + else Msg( "\n" ); + } + + start = I_FloatTime(); + g_pacifier = showpacifier; + g_workcount = workcnt; + g_dispatch = 0; + if( g_pacifier ) StartPacifier(); + + if( g_numthreads == 1 ) + { + // use same thread + func( 0 ); + } + else + { + // run threads in parallel + InitializeCriticalSection( &g_crit ); + g_threaded = true; + + for( i = 0; i < g_numthreads; i++ ) + { + g_threads[i].number = i; + g_threads[i].func = func; + + g_threadhandles[i] = CreateThread( NULL, THREAD_STACK_SIZE, InternalRunThreadsFn, &g_threads, 0, &dwDummy ); + } + + WaitForMultipleObjects( g_numthreads, g_threadhandles, TRUE, INFINITE ); + + for ( i = 0; i < g_numthreads; i++ ) + CloseHandle( g_threadhandles[i] ); + + DeleteCriticalSection( &g_crit ); + g_threaded = false; + } + + end = I_FloatTime (); + + if( g_pacifier ) EndPacifier( end - start ); +} + +void RunThreadsOnIndividual( int workcnt, bool showpacifier, pfnThreadWork func ) +{ + g_workfunction = func; + RunThreadsOn( workcnt, showpacifier, ThreadWorkerFunction ); +} + +void RunThreadsOnIncremental( int workcnt, bool showpacifier, pfnRunThreads func ) +{ + RunThreadsOn( workcnt, showpacifier, func ); +} \ No newline at end of file diff --git a/utils/common/threads.h b/utils/common/threads.h new file mode 100644 index 0000000..cf45d96 --- /dev/null +++ b/utils/common/threads.h @@ -0,0 +1,37 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +extern int g_numthreads; + +#define MAX_THREADS 16 + +typedef void (*pfnThreadWork)( int current, int threadnum ); +typedef void (*pfnRunThreads)( int threadnum ); + +int GetThreadWork( void ); +void ThreadSetDefault( void ); +void RunThreadsOnIndividual( int workcnt, bool showpacifier, pfnThreadWork func ); +void RunThreadsOnIncremental( int workcnt, bool showpacifier, pfnRunThreads func ); +void RunThreadsOn( int workcnt, bool showpacifier, pfnRunThreads func ); +bool ThreadLocked( void ); +void ThreadLock( void ); +void ThreadUnlock( void ); +void ThreadPush( void ); +void ThreadPop( void ); + +void StartPacifier( void ); +void UpdatePacifier( float percent ); +void EndPacifier( double total ); + +#ifndef NO_THREAD_NAMES +#define RunThreadsOn( n, p, f ) { if( p ) Msg( "%-20s", #f ":" ); RunThreadsOn( n, p, f ); } +#define RunThreadsOnIncremental( n, p, f, i ) { if( p ) Msg( "%s %i:", #f, i ); RunThreadsOnIncremental( n, p, f ); } +#define RunThreadsOnIndividual( n, p, f ) { if (p) Msg( "%-20s", #f ":" ); RunThreadsOnIndividual( n, p, f ); } +#endif diff --git a/utils/common/utlarray.h b/utils/common/utlarray.h new file mode 100644 index 0000000..1889222 --- /dev/null +++ b/utils/common/utlarray.h @@ -0,0 +1,771 @@ +//====== Copyright © 1996-2005, Valve Corporation, All rights reserved. =======// +// +// Purpose: +// +// $NoKeywords: $ +// +// A growable array class that maintains a free list and keeps elements +// in the same location +//=============================================================================// + +#ifndef UTLVECTOR_H +#define UTLVECTOR_H + +#ifdef _WIN32 +#pragma once +#endif + +#include +#include +#include "utlmemory.h" +#include "utlblockmemory.h" + +#define FOR_EACH_VEC( vecName, iteratorName ) \ + for ( int iteratorName = 0; iteratorName < vecName.Count(); iteratorName++ ) + +//----------------------------------------------------------------------------- +// The CUtlArray class: +// A growable array class which doubles in size by default. +// It will always keep all elements consecutive in memory, and may move the +// elements around in memory (via a PvRealloc) when elements are inserted or +// removed. Clients should therefore refer to the elements of the vector +// by index (they should *never* maintain pointers to elements in the vector). +//----------------------------------------------------------------------------- +template< class T, class A = CUtlMemory > +class CUtlArray +{ + typedef A CAllocator; +public: + typedef T ElemType_t; + + // constructor, destructor + CUtlArray( int growSize = 0, int initSize = 0 ); + CUtlArray( T* pMemory, int allocationCount, int numElements = 0 ); + ~CUtlArray(); + + // Copy the array. + CUtlArray& operator=( const CUtlArray &other ); + + // element access + T& operator[]( int i ); + const T& operator[]( int i ) const; + T& Element( int i ); + const T& Element( int i ) const; + T& Head(); + const T& Head() const; + T& Tail(); + const T& Tail() const; + + // Gets the base address (can change when adding elements!) + T* Base() { return m_Memory.Base(); } + const T* Base() const { return m_Memory.Base(); } + + // Returns the number of elements in the vector + // SIZE IS DEPRECATED! + int Count() const; + int Size() const; // don't use me! + + // Is element index valid? + bool IsValidIndex( int i ) const; + static int InvalidIndex(); + + // Adds an element, uses default constructor + int AddToHead(); + int AddToTail(); + int InsertBefore( int elem ); + int InsertAfter( int elem ); + + // Adds an element, uses copy constructor + int AddToHead( const T& src ); + int AddToTail( const T& src ); + int InsertBefore( int elem, const T& src ); + int InsertAfter( int elem, const T& src ); + + // Adds multiple elements, uses default constructor + int AddMultipleToHead( int num ); + int AddMultipleToTail( int num, const T *pToCopy=NULL ); + int InsertMultipleBefore( int elem, int num, const T *pToCopy=NULL ); // If pToCopy is set, then it's an array of length 'num' and + int InsertMultipleAfter( int elem, int num ); + + // Calls RemoveAll() then AddMultipleToTail. + void SetSize( int size ); + void SetCount( int count ); + + // Calls SetSize and copies each element. + void CopyArray( const T *pArray, int size ); + + // Fast swap + void Swap( CUtlArray< T, A > &vec ); + + // Add the specified array to the tail. + int AddVectorToTail( CUtlArray const &src ); + + // Finds an element (element needs operator== defined) + int Find( const T& src ) const; + + bool HasElement( const T& src ) const; + + // Makes sure we have enough memory allocated to store a requested # of elements + void EnsureCapacity( int num ); + + // Makes sure we have at least this many elements + void EnsureCount( int num ); + + // Element removal + void FastRemove( int elem ); // doesn't preserve order + void Remove( int elem ); // preserves order, shifts elements + bool FindAndRemove( const T& src ); // removes first occurrence of src, preserves order, shifts elements + void RemoveMultiple( int elem, int num ); // preserves order, shifts elements + void RemoveAll(); // doesn't deallocate memory + + // Memory deallocation + void Purge(); + + // Purges the list and calls delete on each element in it. + void PurgeAndDeleteElements(); + + // Compacts the vector to the number of elements actually in use + void Compact(); + + // Set the size by which it grows when it needs to allocate more memory. + void SetGrowSize( int size ) { m_Memory.SetGrowSize( size ); } + + int NumAllocated() const; // Only use this if you really know what you're doing! + + void Sort( int (__cdecl *pfnCompare)(const T *, const T *) ); + +#ifdef DBGFLAG_VALIDATE + void Validate( CValidator &validator, char *pchName ); // Validate our internal structures +#endif // DBGFLAG_VALIDATE + +protected: + // Can't copy this unless we explicitly do it! + CUtlArray( CUtlArray const& vec ) { assert(0); } + + // Grows the vector + void GrowVector( int num = 1 ); + + // Shifts elements.... + void ShiftElementsRight( int elem, int num = 1 ); + void ShiftElementsLeft( int elem, int num = 1 ); + + CAllocator m_Memory; + int m_Size; + + // For easier access to the elements through the debugger + // it's in release builds so this can be used in libraries correctly + T *m_pElements; + + inline void ResetDbgInfo() + { + m_pElements = Base(); + } +}; + + +// this is kind of ugly, but until C++ gets templatized typedefs in C++0x, it's our only choice +template < class T > +class CUtlBlockVector : public CUtlArray< T, CUtlBlockMemory< T, int > > +{ +public: + CUtlBlockVector( int growSize = 0, int initSize = 0 ) + : CUtlArray< T, CUtlBlockMemory< T, int > >( growSize, initSize ) {} +}; + +//----------------------------------------------------------------------------- +// The CUtlArrayFixed class: +// A array class with a fixed allocation scheme +//----------------------------------------------------------------------------- + +template< class BASE_UTLVECTOR, class MUTEX_TYPE = CThreadFastMutex > +class CUtlArrayMT : public BASE_UTLVECTOR, public MUTEX_TYPE +{ + typedef BASE_UTLVECTOR BaseClass; +public: + MUTEX_TYPE Mutex_t; + + // constructor, destructor + CUtlArrayMT( int growSize = 0, int initSize = 0 ) : BaseClass( growSize, initSize ) {} + CUtlArrayMT( typename BaseClass::ElemType_t* pMemory, int numElements ) : BaseClass( pMemory, numElements ) {} +}; + + +//----------------------------------------------------------------------------- +// The CUtlArrayFixed class: +// A array class with a fixed allocation scheme +//----------------------------------------------------------------------------- +template< class T, size_t MAX_SIZE > +class CUtlArrayFixed : public CUtlArray< T, CUtlMemoryFixed > +{ + typedef CUtlArray< T, CUtlMemoryFixed > BaseClass; +public: + + // constructor, destructor + CUtlArrayFixed( int growSize = 0, int initSize = 0 ) : BaseClass( growSize, initSize ) {} + CUtlArrayFixed( T* pMemory, int numElements ) : BaseClass( pMemory, numElements ) {} +}; + + +//----------------------------------------------------------------------------- +// The CUtlArrayFixed class: +// A array class with a fixed allocation scheme +//----------------------------------------------------------------------------- +template< class T, size_t MAX_SIZE > +class CUtlArrayFixedGrowable : public CUtlArray< T, CUtlMemoryFixedGrowable > +{ + typedef CUtlArray< T, CUtlMemoryFixedGrowable > BaseClass; + +public: + // constructor, destructor + CUtlArrayFixedGrowable( int growSize = 0 ) : BaseClass( growSize, MAX_SIZE ) {} +}; + + +//----------------------------------------------------------------------------- +// The CCopyableUtlVector class: +// A array class that allows copy construction (so you can nest a CUtlArray inside of another one of our containers) +// WARNING - this class lets you copy construct which can be an expensive operation if you don't carefully control when it happens +// Only use this when nesting a CUtlArray() inside of another one of our container classes (i.e a CUtlMap) +//----------------------------------------------------------------------------- +template< class T > +class CCopyableUtlVector : public CUtlArray< T, CUtlMemory > +{ + typedef CUtlArray< T, CUtlMemory > BaseClass; +public: + CCopyableUtlVector( int growSize = 0, int initSize = 0 ) : BaseClass( growSize, initSize ) {} + CCopyableUtlVector( T* pMemory, int numElements ) : BaseClass( pMemory, numElements ) {} + virtual ~CCopyableUtlVector() {} + CCopyableUtlVector( CCopyableUtlVector const& vec ) { CopyArray( vec.Base(), vec.Count() ); } +}; + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- +template< typename T, class A > +inline CUtlArray::CUtlArray( int growSize, int initSize ) : + m_Memory(growSize, initSize), m_Size(0) +{ + ResetDbgInfo(); +} + +template< typename T, class A > +inline CUtlArray::CUtlArray( T* pMemory, int allocationCount, int numElements ) : + m_Memory(pMemory, allocationCount), m_Size(numElements) +{ + ResetDbgInfo(); +} + +template< typename T, class A > +inline CUtlArray::~CUtlArray() +{ + Purge(); +} + +template< typename T, class A > +inline CUtlArray& CUtlArray::operator=( const CUtlArray &other ) +{ + int nCount = other.Count(); + SetSize( nCount ); + for ( int i = 0; i < nCount; i++ ) + { + (*this)[ i ] = other[ i ]; + } + return *this; +} + + +//----------------------------------------------------------------------------- +// element access +//----------------------------------------------------------------------------- +template< typename T, class A > +inline T& CUtlArray::operator[]( int i ) +{ + return m_Memory[ i ]; +} + +template< typename T, class A > +inline const T& CUtlArray::operator[]( int i ) const +{ + return m_Memory[ i ]; +} + +template< typename T, class A > +inline T& CUtlArray::Element( int i ) +{ + return m_Memory[ i ]; +} + +template< typename T, class A > +inline const T& CUtlArray::Element( int i ) const +{ + return m_Memory[ i ]; +} + +template< typename T, class A > +inline T& CUtlArray::Head() +{ + assert( m_Size > 0 ); + return m_Memory[ 0 ]; +} + +template< typename T, class A > +inline const T& CUtlArray::Head() const +{ + assert( m_Size > 0 ); + return m_Memory[ 0 ]; +} + +template< typename T, class A > +inline T& CUtlArray::Tail() +{ + assert( m_Size > 0 ); + return m_Memory[ m_Size - 1 ]; +} + +template< typename T, class A > +inline const T& CUtlArray::Tail() const +{ + assert( m_Size > 0 ); + return m_Memory[ m_Size - 1 ]; +} + + +//----------------------------------------------------------------------------- +// Count +//----------------------------------------------------------------------------- +template< typename T, class A > +inline int CUtlArray::Size() const +{ + return m_Size; +} + +template< typename T, class A > +inline int CUtlArray::Count() const +{ + return m_Size; +} + + +//----------------------------------------------------------------------------- +// Is element index valid? +//----------------------------------------------------------------------------- +template< typename T, class A > +inline bool CUtlArray::IsValidIndex( int i ) const +{ + return (i >= 0) && (i < m_Size); +} + + +//----------------------------------------------------------------------------- +// Returns in invalid index +//----------------------------------------------------------------------------- +template< typename T, class A > +inline int CUtlArray::InvalidIndex() +{ + return -1; +} + + +//----------------------------------------------------------------------------- +// Grows the vector +//----------------------------------------------------------------------------- +template< typename T, class A > +void CUtlArray::GrowVector( int num ) +{ + if (m_Size + num > m_Memory.NumAllocated()) + { + m_Memory.Grow( m_Size + num - m_Memory.NumAllocated() ); + } + + m_Size += num; + ResetDbgInfo(); +} + + +//----------------------------------------------------------------------------- +// Sorts the vector +//----------------------------------------------------------------------------- +template< typename T, class A > +void CUtlArray::Sort( int (__cdecl *pfnCompare)(const T *, const T *) ) +{ + typedef int (__cdecl *QSortCompareFunc_t)(const void *, const void *); + if ( Count() <= 1 ) + return; + + if ( Base() ) + { + qsort( Base(), Count(), sizeof(T), (QSortCompareFunc_t)(pfnCompare) ); + } + else + { + assert( 0 ); + // this path is untested + // if you want to sort vectors that use a non-sequential memory allocator, + // you'll probably want to patch in a quicksort algorithm here + // I just threw in this bubble sort to have something just in case... + + for ( int i = m_Size - 1; i >= 0; --i ) + { + for ( int j = 1; j <= i; ++j ) + { + if ( pfnCompare( &Element( j - 1 ), &Element( j ) ) < 0 ) + { + swap( Element( j - 1 ), Element( j ) ); + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Makes sure we have enough memory allocated to store a requested # of elements +//----------------------------------------------------------------------------- +template< typename T, class A > +void CUtlArray::EnsureCapacity( int num ) +{ + m_Memory.EnsureCapacity(num); + ResetDbgInfo(); +} + + +//----------------------------------------------------------------------------- +// Makes sure we have at least this many elements +//----------------------------------------------------------------------------- +template< typename T, class A > +void CUtlArray::EnsureCount( int num ) +{ + if (Count() < num) + AddMultipleToTail( num - Count() ); +} + + +//----------------------------------------------------------------------------- +// Shifts elements +//----------------------------------------------------------------------------- +template< typename T, class A > +void CUtlArray::ShiftElementsRight( int elem, int num ) +{ + assert( IsValidIndex(elem) || ( m_Size == 0 ) || ( num == 0 )); + int numToMove = m_Size - elem - num; + if ((numToMove > 0) && (num > 0)) + memmove( &Element(elem+num), &Element(elem), numToMove * sizeof(T) ); +} + +template< typename T, class A > +void CUtlArray::ShiftElementsLeft( int elem, int num ) +{ + assert( IsValidIndex(elem) || ( m_Size == 0 ) || ( num == 0 )); + int numToMove = m_Size - elem - num; + if ((numToMove > 0) && (num > 0)) + { + memmove( &Element(elem), &Element(elem+num), numToMove * sizeof(T) ); + +#ifdef _DEBUG + memset( &Element(m_Size-num), 0xDD, num * sizeof(T) ); +#endif + } +} + + +//----------------------------------------------------------------------------- +// Adds an element, uses default constructor +//----------------------------------------------------------------------------- +template< typename T, class A > +inline int CUtlArray::AddToHead() +{ + return InsertBefore(0); +} + +template< typename T, class A > +inline int CUtlArray::AddToTail() +{ + return InsertBefore( m_Size ); +} + +template< typename T, class A > +inline int CUtlArray::InsertAfter( int elem ) +{ + return InsertBefore( elem + 1 ); +} + +template< typename T, class A > +int CUtlArray::InsertBefore( int elem ) +{ + // Can insert at the end + assert( (elem == Count()) || IsValidIndex(elem) ); + + GrowVector(); + ShiftElementsRight(elem); + Construct( &Element(elem) ); + return elem; +} + + +//----------------------------------------------------------------------------- +// Adds an element, uses copy constructor +//----------------------------------------------------------------------------- +template< typename T, class A > +inline int CUtlArray::AddToHead( const T& src ) +{ + // Can't insert something that's in the list... reallocation may hose us + assert( (Base() == NULL) || (&src < Base()) || (&src >= (Base() + Count()) ) ); + return InsertBefore( 0, src ); +} + +template< typename T, class A > +inline int CUtlArray::AddToTail( const T& src ) +{ + // Can't insert something that's in the list... reallocation may hose us + assert( (Base() == NULL) || (&src < Base()) || (&src >= (Base() + Count()) ) ); + return InsertBefore( m_Size, src ); +} + +template< typename T, class A > +inline int CUtlArray::InsertAfter( int elem, const T& src ) +{ + // Can't insert something that's in the list... reallocation may hose us + assert( (Base() == NULL) || (&src < Base()) || (&src >= (Base() + Count()) ) ); + return InsertBefore( elem + 1, src ); +} + +template< typename T, class A > +int CUtlArray::InsertBefore( int elem, const T& src ) +{ + // Can't insert something that's in the list... reallocation may hose us + assert( (Base() == NULL) || (&src < Base()) || (&src >= (Base() + Count()) ) ); + + // Can insert at the end + assert( (elem == Count()) || IsValidIndex(elem) ); + + GrowVector(); + ShiftElementsRight(elem); + CopyConstruct( &Element(elem), src ); + return elem; +} + + +//----------------------------------------------------------------------------- +// Adds multiple elements, uses default constructor +//----------------------------------------------------------------------------- +template< typename T, class A > +inline int CUtlArray::AddMultipleToHead( int num ) +{ + return InsertMultipleBefore( 0, num ); +} + +template< typename T, class A > +inline int CUtlArray::AddMultipleToTail( int num, const T *pToCopy ) +{ + // Can't insert something that's in the list... reallocation may hose us + assert( (Base() == NULL) || !pToCopy || (pToCopy + num < Base()) || (pToCopy >= (Base() + Count()) ) ); + + return InsertMultipleBefore( m_Size, num, pToCopy ); +} + +template< typename T, class A > +int CUtlArray::InsertMultipleAfter( int elem, int num ) +{ + return InsertMultipleBefore( elem + 1, num ); +} + + +template< typename T, class A > +void CUtlArray::SetCount( int count ) +{ + RemoveAll(); + AddMultipleToTail( count ); +} + +template< typename T, class A > +inline void CUtlArray::SetSize( int size ) +{ + SetCount( size ); +} + +template< typename T, class A > +void CUtlArray::CopyArray( const T *pArray, int size ) +{ + // Can't insert something that's in the list... reallocation may hose us + assert( (Base() == NULL) || !pArray || (Base() >= (pArray + size)) || (pArray >= (Base() + Count()) ) ); + + SetSize( size ); + for( int i=0; i < size; i++ ) + { + (*this)[i] = pArray[i]; + } +} + +template< typename T, class A > +void CUtlArray::Swap( CUtlArray< T, A > &vec ) +{ + m_Memory.Swap( vec.m_Memory ); + swap( m_Size, vec.m_Size ); + swap( m_pElements, vec.m_pElements ); +} + +template< typename T, class A > +int CUtlArray::AddVectorToTail( CUtlArray const &src ) +{ + assert( &src != this ); + + int base = Count(); + + // Make space. + AddMultipleToTail( src.Count() ); + + // Copy the elements. + for ( int i=0; i < src.Count(); i++ ) + { + (*this)[base + i] = src[i]; + } + + return base; +} + +template< typename T, class A > +inline int CUtlArray::InsertMultipleBefore( int elem, int num, const T *pToInsert ) +{ + if( num == 0 ) + return elem; + + // Can insert at the end + assert( (elem == Count()) || IsValidIndex(elem) ); + + GrowVector(num); + ShiftElementsRight(elem, num); + + // Invoke default constructors + for (int i = 0; i < num; ++i) + Construct( &Element(elem+i) ); + + // Copy stuff in? + if ( pToInsert ) + { + for ( int i=0; i < num; i++ ) + { + Element( elem+i ) = pToInsert[i]; + } + } + + return elem; +} + + +//----------------------------------------------------------------------------- +// Finds an element (element needs operator== defined) +//----------------------------------------------------------------------------- +template< typename T, class A > +int CUtlArray::Find( const T& src ) const +{ + for ( int i = 0; i < Count(); ++i ) + { + if (Element(i) == src) + return i; + } + return -1; +} + +template< typename T, class A > +bool CUtlArray::HasElement( const T& src ) const +{ + return ( Find(src) >= 0 ); +} + + +//----------------------------------------------------------------------------- +// Element removal +//----------------------------------------------------------------------------- +template< typename T, class A > +void CUtlArray::FastRemove( int elem ) +{ + assert( IsValidIndex(elem) ); + + Destruct( &Element(elem) ); + if (m_Size > 0) + { + memcpy( &Element(elem), &Element(m_Size-1), sizeof(T) ); + --m_Size; + } +} + +template< typename T, class A > +void CUtlArray::Remove( int elem ) +{ + Destruct( &Element(elem) ); + ShiftElementsLeft(elem); + --m_Size; +} + +template< typename T, class A > +bool CUtlArray::FindAndRemove( const T& src ) +{ + int elem = Find( src ); + if ( elem != -1 ) + { + Remove( elem ); + return true; + } + return false; +} + +template< typename T, class A > +void CUtlArray::RemoveMultiple( int elem, int num ) +{ + assert( elem >= 0 ); + assert( elem + num <= Count() ); + + for (int i = elem + num; --i >= elem; ) + Destruct(&Element(i)); + + ShiftElementsLeft(elem, num); + m_Size -= num; +} + +template< typename T, class A > +void CUtlArray::RemoveAll() +{ + for (int i = m_Size; --i >= 0; ) + { + Destruct(&Element(i)); + } + + m_Size = 0; +} + + +//----------------------------------------------------------------------------- +// Memory deallocation +//----------------------------------------------------------------------------- + +template< typename T, class A > +inline void CUtlArray::Purge() +{ + RemoveAll(); + m_Memory.Purge(); + ResetDbgInfo(); +} + + +template< typename T, class A > +inline void CUtlArray::PurgeAndDeleteElements() +{ + for( int i=0; i < m_Size; i++ ) + { + delete Element(i); + } + Purge(); +} + +template< typename T, class A > +inline void CUtlArray::Compact() +{ + m_Memory.Purge(m_Size); +} + +template< typename T, class A > +inline int CUtlArray::NumAllocated() const +{ + return m_Memory.NumAllocated(); +} + +#endif // CCVECTOR_H \ No newline at end of file diff --git a/utils/common/utlblockmemory.h b/utils/common/utlblockmemory.h new file mode 100644 index 0000000..5ab3c86 --- /dev/null +++ b/utils/common/utlblockmemory.h @@ -0,0 +1,319 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +// +// A growable memory class. +//===========================================================================// + +#ifndef UTLBLOCKMEMORY_H +#define UTLBLOCKMEMORY_H + +#ifdef _WIN32 +#pragma once +#endif + +#pragma warning (disable:4100) +#pragma warning (disable:4514) + +//----------------------------------------------------------------------------- +// The CUtlBlockMemory class: +// A growable memory class that allocates non-sequential blocks, but is indexed sequentially +//----------------------------------------------------------------------------- +template< class T, class I > +class CUtlBlockMemory +{ +public: + // constructor, destructor + CUtlBlockMemory( int nGrowSize = 0, int nInitSize = 0 ); + ~CUtlBlockMemory(); + + // Set the size by which the memory grows - round up to the next power of 2 + void Init( int nGrowSize = 0, int nInitSize = 0 ); + + // here to match CUtlMemory, but only used by ResetDbgInfo, so it can just return NULL + T* Base() { return NULL; } + const T* Base() const { return NULL; } + + class Iterator_t + { + public: + Iterator_t( I i ) : index( i ) {} + I index; + + bool operator==( const Iterator_t it ) const { return index == it.index; } + bool operator!=( const Iterator_t it ) const { return index != it.index; } + }; + Iterator_t First() const { return Iterator_t( IsIdxValid( 0 ) ? 0 : InvalidIndex() ); } + Iterator_t Next( const Iterator_t &it ) const { return Iterator_t( IsIdxValid( it.index + 1 ) ? it.index + 1 : InvalidIndex() ); } + I GetIndex( const Iterator_t &it ) const { return it.index; } + bool IsIdxAfter( I i, const Iterator_t &it ) const { return i > it.index; } + bool IsValidIterator( const Iterator_t &it ) const { return IsIdxValid( it.index ); } + Iterator_t InvalidIterator() const { return Iterator_t( InvalidIndex() ); } + + // element access + T& operator[]( I i ); + const T& operator[]( I i ) const; + T& Element( I i ); + const T& Element( I i ) const; + + // Can we use this index? + bool IsIdxValid( I i ) const; + static I InvalidIndex() { return ( I )-1; } + + void Swap( CUtlBlockMemory< T, I > &mem ); + + // Size + int NumAllocated() const; + int Count() const { return NumAllocated(); } + + // Grows memory by max(num,growsize) rounded up to the next power of 2, and returns the allocation index/ptr + void Grow( int num = 1 ); + + // Makes sure we've got at least this much memory + void EnsureCapacity( int num ); + + // Memory deallocation + void Purge(); + + // Purge all but the given number of elements + void Purge( int numElements ); + +protected: + int Index( int major, int minor ) const { return ( major << m_nIndexShift ) | minor; } + int MajorIndex( int i ) const { return i >> m_nIndexShift; } + int MinorIndex( int i ) const { return i & m_nIndexMask; } + void ChangeSize( int nBlocks ); + int NumElementsInBlock() const { return m_nIndexMask + 1; } + + T** m_pMemory; + int m_nBlocks; + int m_nIndexMask : 27; + int m_nIndexShift : 5; +}; + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- + +template< class T, class I > +CUtlBlockMemory::CUtlBlockMemory( int nGrowSize, int nInitAllocationCount ) +: m_pMemory( 0 ), m_nBlocks( 0 ), m_nIndexMask( 0 ), m_nIndexShift( 0 ) +{ + Init( nGrowSize, nInitAllocationCount ); +} + +template< class T, class I > +CUtlBlockMemory::~CUtlBlockMemory() +{ + Purge(); +} + + +//----------------------------------------------------------------------------- +// Fast swap +//----------------------------------------------------------------------------- +template< class T, class I > +void CUtlBlockMemory::Swap( CUtlBlockMemory< T, I > &mem ) +{ + swap( m_pMemory, mem.m_pMemory ); + swap( m_nBlocks, mem.m_nBlocks ); + swap( m_nIndexMask, mem.m_nIndexMask ); + swap( m_nIndexShift, mem.m_nIndexShift ); +} + + +//----------------------------------------------------------------------------- +// Set the size by which the memory grows - round up to the next power of 2 +//----------------------------------------------------------------------------- +template< class T, class I > +void CUtlBlockMemory::Init( int nGrowSize /* = 0 */, int nInitSize /* = 0 */ ) +{ + Purge(); + + if ( nGrowSize == 0) + { + // default grow size is smallest size s.t. c++ allocation overhead is ~6% of block size + nGrowSize = ( 127 + sizeof( T ) ) / sizeof( T ); + } + nGrowSize = SmallestPowerOfTwoGreaterOrEqual( nGrowSize ); + m_nIndexMask = nGrowSize - 1; + + m_nIndexShift = 0; + while ( nGrowSize > 1 ) + { + nGrowSize >>= 1; + ++m_nIndexShift; + } + assert( m_nIndexMask + 1 == ( 1 << m_nIndexShift ) ); + + Grow( nInitSize ); +} + + +//----------------------------------------------------------------------------- +// element access +//----------------------------------------------------------------------------- +template< class T, class I > +inline T& CUtlBlockMemory::operator[]( I i ) +{ + assert( IsIdxValid(i) ); + T *pBlock = m_pMemory[ MajorIndex( i ) ]; + return pBlock[ MinorIndex( i ) ]; +} + +template< class T, class I > +inline const T& CUtlBlockMemory::operator[]( I i ) const +{ + assert( IsIdxValid(i) ); + const T *pBlock = m_pMemory[ MajorIndex( i ) ]; + return pBlock[ MinorIndex( i ) ]; +} + +template< class T, class I > +inline T& CUtlBlockMemory::Element( I i ) +{ + assert( IsIdxValid(i) ); + T *pBlock = m_pMemory[ MajorIndex( i ) ]; + return pBlock[ MinorIndex( i ) ]; +} + +template< class T, class I > +inline const T& CUtlBlockMemory::Element( I i ) const +{ + assert( IsIdxValid(i) ); + const T *pBlock = m_pMemory[ MajorIndex( i ) ]; + return pBlock[ MinorIndex( i ) ]; +} + + +//----------------------------------------------------------------------------- +// Size +//----------------------------------------------------------------------------- +template< class T, class I > +inline int CUtlBlockMemory::NumAllocated() const +{ + return m_nBlocks * NumElementsInBlock(); +} + + +//----------------------------------------------------------------------------- +// Is element index valid? +//----------------------------------------------------------------------------- +template< class T, class I > +inline bool CUtlBlockMemory::IsIdxValid( I i ) const +{ + return ( i >= 0 ) && ( MajorIndex( i ) < m_nBlocks ); +} + +template< class T, class I > +void CUtlBlockMemory::Grow( int num ) +{ + if ( num <= 0 ) + return; + + int nBlockSize = NumElementsInBlock(); + int nBlocks = ( num + nBlockSize - 1 ) / nBlockSize; + + ChangeSize( m_nBlocks + nBlocks ); +} + +template< class T, class I > +void CUtlBlockMemory::ChangeSize( int nBlocks ) +{ + int i, nBlocksOld = m_nBlocks; + m_nBlocks = nBlocks; + + // free old blocks if shrinking + for ( i = m_nBlocks; i < nBlocksOld; ++i ) + { + free( (void*)m_pMemory[ i ] ); + } + + if ( m_pMemory ) + { + m_pMemory = (T**)realloc( m_pMemory, m_nBlocks * sizeof(T*) ); + assert( m_pMemory ); + } + else + { + m_pMemory = (T**)malloc( m_nBlocks * sizeof(T*) ); + assert( m_pMemory ); + } + + if ( !m_pMemory ) + { + COM_FatalError( "CUtlBlockMemory overflow!\n" ); + } + + // allocate new blocks if growing + int nBlockSize = NumElementsInBlock(); + for ( i = nBlocksOld; i < m_nBlocks; ++i ) + { + m_pMemory[ i ] = (T*)malloc( nBlockSize * sizeof( T ) ); + assert( m_pMemory[ i ] ); + } +} + + +//----------------------------------------------------------------------------- +// Makes sure we've got at least this much memory +//----------------------------------------------------------------------------- +template< class T, class I > +inline void CUtlBlockMemory::EnsureCapacity( int num ) +{ + Grow( num - NumAllocated() ); +} + + +//----------------------------------------------------------------------------- +// Memory deallocation +//----------------------------------------------------------------------------- +template< class T, class I > +void CUtlBlockMemory::Purge() +{ + if ( !m_pMemory ) + return; + + for ( int i = 0; i < m_nBlocks; ++i ) + { + free( (void*)m_pMemory[ i ] ); + } + m_nBlocks = 0; + + free( (void*)m_pMemory ); + m_pMemory = 0; +} + +template< class T, class I > +void CUtlBlockMemory::Purge( int numElements ) +{ + assert( numElements >= 0 ); + + int nAllocated = NumAllocated(); + if ( numElements > nAllocated ) + { + // Ensure this isn't a grow request in disguise. + assert( numElements <= nAllocated ); + return; + } + + if ( numElements <= 0 ) + { + Purge(); + return; + } + + int nBlockSize = NumElementsInBlock(); + int nBlocksOld = m_nBlocks; + int nBlocks = ( numElements + nBlockSize - 1 ) / nBlockSize; + + // If the number of blocks is the same as the allocated number of blocks, we are done. + if ( nBlocks == m_nBlocks ) + return; + + ChangeSize( nBlocks ); +} + +#endif // UTLBLOCKMEMORY_H \ No newline at end of file diff --git a/utils/common/utlmemory.h b/utils/common/utlmemory.h new file mode 100644 index 0000000..4d32566 --- /dev/null +++ b/utils/common/utlmemory.h @@ -0,0 +1,906 @@ +//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======// +// +// Purpose: +// +// $NoKeywords: $ +// +// A growable memory class. +//===========================================================================// + +#ifndef UTLMEMORY_H +#define UTLMEMORY_H + +#ifdef _WIN32 +#pragma once +#endif + +#include +#include +#include + +#define ALIGN_VALUE( val, alignment ) (( val + alignment - 1 ) & ~( alignment - 1 )) +#define stackalloc( _size ) _alloca( ALIGN_VALUE( _size, 16 ) ) +#define stackfree( _p ) 0 + +// Swap two of anything. +template +_forceinline void swap( T& x, T& y ) +{ + T temp = x; + x = y; + y = temp; +} + +//----------------------------------------------------------------------------- +// Methods to invoke the constructor, copy constructor, and destructor +//----------------------------------------------------------------------------- + +template +inline void Construct( T* pMemory ) +{ + new( pMemory ) T; +} + +template +inline void CopyConstruct( T* pMemory, T const& src ) +{ + new( pMemory ) T(src); +} + +template +inline void Destruct( T* pMemory ) +{ + pMemory->~T(); + +#ifdef _DEBUG + memset( pMemory, 0xDD, sizeof(T) ); +#endif +} + +#pragma warning (disable:4100) +#pragma warning (disable:4514) + +//----------------------------------------------------------------------------- +// The CUtlMemory class: +// A growable memory class which doubles in size by default. +//----------------------------------------------------------------------------- +template< class T, class I = int > +class CUtlMemory +{ +public: + // constructor, destructor + CUtlMemory( int nGrowSize = 0, int nInitSize = 0 ); + CUtlMemory( T* pMemory, int numElements ); + CUtlMemory( const T* pMemory, int numElements ); + ~CUtlMemory(); + + // Set the size by which the memory grows + void Init( int nGrowSize = 0, int nInitSize = 0 ); + + class Iterator_t + { + public: + Iterator_t( I i ) : index( i ) {} + I index; + + bool operator==( const Iterator_t it ) const { return index == it.index; } + bool operator!=( const Iterator_t it ) const { return index != it.index; } + }; + Iterator_t First() const { return Iterator_t( IsIdxValid( 0 ) ? 0 : InvalidIndex() ); } + Iterator_t Next( const Iterator_t &it ) const { return Iterator_t( IsIdxValid( it.index + 1 ) ? it.index + 1 : InvalidIndex() ); } + I GetIndex( const Iterator_t &it ) const { return it.index; } + bool IsIdxAfter( I i, const Iterator_t &it ) const { return i > it.index; } + bool IsValidIterator( const Iterator_t &it ) const { return IsIdxValid( it.index ); } + Iterator_t InvalidIterator() const { return Iterator_t( InvalidIndex() ); } + + // element access + T& operator[]( I i ); + const T& operator[]( I i ) const; + T& Element( I i ); + const T& Element( I i ) const; + + // Can we use this index? + bool IsIdxValid( I i ) const; + static I InvalidIndex() { return ( I )-1; } + + // Gets the base address (can change when adding elements!) + T* Base(); + const T* Base() const; + + // Attaches the buffer to external memory.... + void SetExternalBuffer( T* pMemory, int numElements ); + void SetExternalBuffer( const T* pMemory, int numElements ); + void AssumeMemory( T *pMemory, int nSize ); + + // Fast swap + void Swap( CUtlMemory< T, I > &mem ); + + // Switches the buffer from an external memory buffer to a reallocatable buffer + // Will copy the current contents of the external buffer to the reallocatable buffer + void ConvertToGrowableMemory( int nGrowSize ); + + // Size + int NumAllocated() const; + int Count() const; + + // Grows the memory, so that at least allocated + num elements are allocated + void Grow( int num = 1 ); + + // Makes sure we've got at least this much memory + void EnsureCapacity( int num ); + + // Memory deallocation + void Purge(); + + // Purge all but the given number of elements + void Purge( int numElements ); + + // is the memory externally allocated? + bool IsExternallyAllocated() const; + + // is the memory read only? + bool IsReadOnly() const; + + // Set the size by which the memory grows + void SetGrowSize( int size ); + +protected: + void ValidateGrowSize() + { +#ifdef _X360 + if ( m_nGrowSize && m_nGrowSize != EXTERNAL_BUFFER_MARKER ) + { + // Max grow size at 128 bytes on XBOX + const int MAX_GROW = 128; + if ( m_nGrowSize * sizeof(T) > MAX_GROW ) + { + m_nGrowSize = max( 1, MAX_GROW / sizeof(T) ); + } + } +#endif + } + + enum + { + EXTERNAL_BUFFER_MARKER = -1, + EXTERNAL_CONST_BUFFER_MARKER = -2, + }; + + T* m_pMemory; + int m_nAllocationCount; + int m_nGrowSize; +}; + + +//----------------------------------------------------------------------------- +// The CUtlMemory class: +// A growable memory class which doubles in size by default. +//----------------------------------------------------------------------------- +template< class T, size_t SIZE, class I = int > +class CUtlMemoryFixedGrowable : public CUtlMemory< T, I > +{ + typedef CUtlMemory< T, I > BaseClass; + +public: + CUtlMemoryFixedGrowable( int nGrowSize = 0, int nInitSize = SIZE ) : BaseClass( m_pFixedMemory, SIZE ) + { + assert( nInitSize == 0 || nInitSize == SIZE ); + m_nMallocGrowSize = nGrowSize; + } + + void Grow( int nCount = 1 ) + { + if ( IsExternallyAllocated() ) + { + ConvertToGrowableMemory( m_nMallocGrowSize ); + } + BaseClass::Grow( nCount ); + } + + void EnsureCapacity( int num ) + { + if ( CUtlMemory::m_nAllocationCount >= num ) + return; + + if ( IsExternallyAllocated() ) + { + // Can't grow a buffer whose memory was externally allocated + ConvertToGrowableMemory( m_nMallocGrowSize ); + } + + BaseClass::EnsureCapacity( num ); + } + +private: + int m_nMallocGrowSize; + T m_pFixedMemory[ SIZE ]; +}; + +//----------------------------------------------------------------------------- +// The CUtlMemoryFixed class: +// A fixed memory class +//----------------------------------------------------------------------------- +template< typename T, size_t SIZE, int nAlignment = 0 > +class CUtlMemoryFixed +{ +public: + // constructor, destructor + CUtlMemoryFixed( int nGrowSize = 0, int nInitSize = 0 ) { assert( nInitSize == 0 || nInitSize == SIZE ); } + CUtlMemoryFixed( T* pMemory, int numElements ) { assert( 0 ); } + + // Can we use this index? + bool IsIdxValid( int i ) const { return (i >= 0) && (i < SIZE); } + static int InvalidIndex() { return -1; } + + // Gets the base address + T* Base() { if ( nAlignment == 0 ) return (T*)(&m_Memory[0]); else return (T*)AlignValue( &m_Memory[0], nAlignment ); } + const T* Base() const { if ( nAlignment == 0 ) return (T*)(&m_Memory[0]); else return (T*)AlignValue( &m_Memory[0], nAlignment ); } + + // element access + T& operator[]( int i ) { assert( IsIdxValid(i) ); return Base()[i]; } + const T& operator[]( int i ) const { assert( IsIdxValid(i) ); return Base()[i]; } + T& Element( int i ) { assert( IsIdxValid(i) ); return Base()[i]; } + const T& Element( int i ) const { assert( IsIdxValid(i) ); return Base()[i]; } + + // Attaches the buffer to external memory.... + void SetExternalBuffer( T* pMemory, int numElements ) { assert( 0 ); } + + // Size + int NumAllocated() const { return SIZE; } + int Count() const { return SIZE; } + + // Grows the memory, so that at least allocated + num elements are allocated + void Grow( int num = 1 ) { assert( 0 ); } + + // Makes sure we've got at least this much memory + void EnsureCapacity( int num ) { assert( num <= SIZE ); } + + // Memory deallocation + void Purge() {} + + // Purge all but the given number of elements (NOT IMPLEMENTED IN CUtlMemoryFixed) + void Purge( int numElements ) { assert( 0 ); } + + // is the memory externally allocated? + bool IsExternallyAllocated() const { return false; } + + // Set the size by which the memory grows + void SetGrowSize( int size ) {} + + class Iterator_t + { + public: + Iterator_t( int i ) : index( i ) {} + int index; + bool operator==( const Iterator_t it ) const { return index == it.index; } + bool operator!=( const Iterator_t it ) const { return index != it.index; } + }; + Iterator_t First() const { return Iterator_t( IsIdxValid( 0 ) ? 0 : InvalidIndex() ); } + Iterator_t Next( const Iterator_t &it ) const { return Iterator_t( IsIdxValid( it.index + 1 ) ? it.index + 1 : InvalidIndex() ); } + int GetIndex( const Iterator_t &it ) const { return it.index; } + bool IsIdxAfter( int i, const Iterator_t &it ) const { return i > it.index; } + bool IsValidIterator( const Iterator_t &it ) const { return IsIdxValid( it.index ); } + Iterator_t InvalidIterator() const { return Iterator_t( InvalidIndex() ); } + +private: + char m_Memory[ SIZE*sizeof(T) + nAlignment ]; +}; + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- + +template< class T, class I > +CUtlMemory::CUtlMemory( int nGrowSize, int nInitAllocationCount ) : m_pMemory(0), + m_nAllocationCount( nInitAllocationCount ), m_nGrowSize( nGrowSize ) +{ + ValidateGrowSize(); + assert( nGrowSize >= 0 ); + if (m_nAllocationCount) + { + m_pMemory = (T*)malloc( m_nAllocationCount * sizeof(T) ); + } +} + +template< class T, class I > +CUtlMemory::CUtlMemory( T* pMemory, int numElements ) : m_pMemory(pMemory), + m_nAllocationCount( numElements ) +{ + // Special marker indicating externally supplied modifyable memory + m_nGrowSize = EXTERNAL_BUFFER_MARKER; +} + +template< class T, class I > +CUtlMemory::CUtlMemory( const T* pMemory, int numElements ) : m_pMemory( (T*)pMemory ), + m_nAllocationCount( numElements ) +{ + // Special marker indicating externally supplied modifyable memory + m_nGrowSize = EXTERNAL_CONST_BUFFER_MARKER; +} + +template< class T, class I > +CUtlMemory::~CUtlMemory() +{ + Purge(); +} + +template< class T, class I > +void CUtlMemory::Init( int nGrowSize /*= 0*/, int nInitSize /*= 0*/ ) +{ + Purge(); + + m_nGrowSize = nGrowSize; + m_nAllocationCount = nInitSize; + ValidateGrowSize(); + assert( nGrowSize >= 0 ); + if (m_nAllocationCount) + { + m_pMemory = (T*)malloc( m_nAllocationCount * sizeof(T) ); + } +} + +//----------------------------------------------------------------------------- +// Fast swap +//----------------------------------------------------------------------------- +template< class T, class I > +void CUtlMemory::Swap( CUtlMemory &mem ) +{ + swap( m_nGrowSize, mem.m_nGrowSize ); + swap( m_pMemory, mem.m_pMemory ); + swap( m_nAllocationCount, mem.m_nAllocationCount ); +} + + +//----------------------------------------------------------------------------- +// Switches the buffer from an external memory buffer to a reallocatable buffer +//----------------------------------------------------------------------------- +template< class T, class I > +void CUtlMemory::ConvertToGrowableMemory( int nGrowSize ) +{ + if ( !IsExternallyAllocated() ) + return; + + m_nGrowSize = nGrowSize; + if (m_nAllocationCount) + { + int nNumBytes = m_nAllocationCount * sizeof(T); + T *pMemory = (T*)malloc( nNumBytes ); + memcpy( pMemory, m_pMemory, nNumBytes ); + m_pMemory = pMemory; + } + else + { + m_pMemory = NULL; + } +} + + +//----------------------------------------------------------------------------- +// Attaches the buffer to external memory.... +//----------------------------------------------------------------------------- +template< class T, class I > +void CUtlMemory::SetExternalBuffer( T* pMemory, int numElements ) +{ + // Blow away any existing allocated memory + Purge(); + + m_pMemory = pMemory; + m_nAllocationCount = numElements; + + // Indicate that we don't own the memory + m_nGrowSize = EXTERNAL_BUFFER_MARKER; +} + +template< class T, class I > +void CUtlMemory::SetExternalBuffer( const T* pMemory, int numElements ) +{ + // Blow away any existing allocated memory + Purge(); + + m_pMemory = const_cast( pMemory ); + m_nAllocationCount = numElements; + + // Indicate that we don't own the memory + m_nGrowSize = EXTERNAL_CONST_BUFFER_MARKER; +} + +template< class T, class I > +void CUtlMemory::AssumeMemory( T* pMemory, int numElements ) +{ + // Blow away any existing allocated memory + Purge(); + + // Simply take the pointer but don't mark us as external + m_pMemory = pMemory; + m_nAllocationCount = numElements; +} + + +//----------------------------------------------------------------------------- +// element access +//----------------------------------------------------------------------------- +template< class T, class I > +inline T& CUtlMemory::operator[]( I i ) +{ + assert( !IsReadOnly() ); + assert( IsIdxValid(i) ); + return m_pMemory[i]; +} + +template< class T, class I > +inline const T& CUtlMemory::operator[]( I i ) const +{ + assert( IsIdxValid(i) ); + return m_pMemory[i]; +} + +template< class T, class I > +inline T& CUtlMemory::Element( I i ) +{ + assert( !IsReadOnly() ); + assert( IsIdxValid(i) ); + return m_pMemory[i]; +} + +template< class T, class I > +inline const T& CUtlMemory::Element( I i ) const +{ + assert( IsIdxValid(i) ); + return m_pMemory[i]; +} + + +//----------------------------------------------------------------------------- +// is the memory externally allocated? +//----------------------------------------------------------------------------- +template< class T, class I > +bool CUtlMemory::IsExternallyAllocated() const +{ + return (m_nGrowSize < 0); +} + + +//----------------------------------------------------------------------------- +// is the memory read only? +//----------------------------------------------------------------------------- +template< class T, class I > +bool CUtlMemory::IsReadOnly() const +{ + return (m_nGrowSize == EXTERNAL_CONST_BUFFER_MARKER); +} + + +template< class T, class I > +void CUtlMemory::SetGrowSize( int nSize ) +{ + assert( !IsExternallyAllocated() ); + assert( nSize >= 0 ); + m_nGrowSize = nSize; + ValidateGrowSize(); +} + + +//----------------------------------------------------------------------------- +// Gets the base address (can change when adding elements!) +//----------------------------------------------------------------------------- +template< class T, class I > +inline T* CUtlMemory::Base() +{ + assert( !IsReadOnly() ); + return m_pMemory; +} + +template< class T, class I > +inline const T *CUtlMemory::Base() const +{ + return m_pMemory; +} + + +//----------------------------------------------------------------------------- +// Size +//----------------------------------------------------------------------------- +template< class T, class I > +inline int CUtlMemory::NumAllocated() const +{ + return m_nAllocationCount; +} + +template< class T, class I > +inline int CUtlMemory::Count() const +{ + return m_nAllocationCount; +} + + +//----------------------------------------------------------------------------- +// Is element index valid? +//----------------------------------------------------------------------------- +template< class T, class I > +inline bool CUtlMemory::IsIdxValid( I i ) const +{ + return ( ((int) i) >= 0 ) && ( ((int) i) < m_nAllocationCount ); +} + +//----------------------------------------------------------------------------- +// Grows the memory +//----------------------------------------------------------------------------- +inline int UtlMemory_CalcNewAllocationCount( int nAllocationCount, int nGrowSize, int nNewSize, int nBytesItem ) +{ + if ( nGrowSize ) + { + nAllocationCount = ((1 + ((nNewSize - 1) / nGrowSize)) * nGrowSize); + } + else + { + if ( !nAllocationCount ) + { + // Compute an allocation which is at least as big as a cache line... + nAllocationCount = (31 + nBytesItem) / nBytesItem; + } + + while (nAllocationCount < nNewSize) + { +#ifndef _X360 + nAllocationCount *= 2; +#else + int nNewAllocationCount = ( nAllocationCount * 9) / 8; // 12.5 % + if ( nNewAllocationCount > nAllocationCount ) + nAllocationCount = nNewAllocationCount; + else + nAllocationCount *= 2; +#endif + } + } + + return nAllocationCount; +} + +template< class T, class I > +void CUtlMemory::Grow( int num ) +{ + assert( num > 0 ); + + if ( IsExternallyAllocated() ) + { + // Can't grow a buffer whose memory was externally allocated + assert(0); + return; + } + + // Make sure we have at least numallocated + num allocations. + // Use the grow rules specified for this memory (in m_nGrowSize) + int nAllocationRequested = m_nAllocationCount + num; + + m_nAllocationCount = UtlMemory_CalcNewAllocationCount( m_nAllocationCount, m_nGrowSize, nAllocationRequested, sizeof(T) ); + + // if m_nAllocationRequested wraps index type I, recalculate + if ( ( int )( I )m_nAllocationCount < nAllocationRequested ) + { + if ( ( int )( I )m_nAllocationCount == 0 && ( int )( I )( m_nAllocationCount - 1 ) >= nAllocationRequested ) + { + --m_nAllocationCount; // deal w/ the common case of m_nAllocationCount == MAX_USHORT + 1 + } + else + { + if ( ( int )( I )nAllocationRequested != nAllocationRequested ) + { + // we've been asked to grow memory to a size s.t. the index type can't address the requested amount of memory + assert( 0 ); + return; + } + while ( ( int )( I )m_nAllocationCount < nAllocationRequested ) + { + m_nAllocationCount = ( m_nAllocationCount + nAllocationRequested ) / 2; + } + } + } + + if (m_pMemory) + { + m_pMemory = (T*)realloc( m_pMemory, m_nAllocationCount * sizeof(T) ); + ASSERT( m_pMemory ); + } + else + { + m_pMemory = (T*)malloc( m_nAllocationCount * sizeof(T) ); + ASSERT( m_pMemory ); + } +} + + +//----------------------------------------------------------------------------- +// Makes sure we've got at least this much memory +//----------------------------------------------------------------------------- +template< class T, class I > +inline void CUtlMemory::EnsureCapacity( int num ) +{ + if (m_nAllocationCount >= num) + return; + + if ( IsExternallyAllocated() ) + { + // Can't grow a buffer whose memory was externally allocated + assert(0); + return; + } + + m_nAllocationCount = num; + + if (m_pMemory) + { + m_pMemory = (T*)realloc( m_pMemory, m_nAllocationCount * sizeof(T) ); + } + else + { + m_pMemory = (T*)malloc( m_nAllocationCount * sizeof(T) ); + } +} + + +//----------------------------------------------------------------------------- +// Memory deallocation +//----------------------------------------------------------------------------- +template< class T, class I > +void CUtlMemory::Purge() +{ + if ( !IsExternallyAllocated() ) + { + if (m_pMemory) + { + free( (void*)m_pMemory ); + m_pMemory = 0; + } + m_nAllocationCount = 0; + } +} + +template< class T, class I > +void CUtlMemory::Purge( int numElements ) +{ + assert( numElements >= 0 ); + + if( numElements > m_nAllocationCount ) + { + // Ensure this isn't a grow request in disguise. + assert( numElements <= m_nAllocationCount ); + return; + } + + // If we have zero elements, simply do a purge: + if( numElements == 0 ) + { + Purge(); + return; + } + + if ( IsExternallyAllocated() ) + { + // Can't shrink a buffer whose memory was externally allocated, fail silently like purge + return; + } + + // If the number of elements is the same as the allocation count, we are done. + if( numElements == m_nAllocationCount ) + { + return; + } + + + if( !m_pMemory ) + { + // Allocation count is non zero, but memory is null. + assert( m_pMemory ); + return; + } + + m_nAllocationCount = numElements; + + // Allocation count > 0, shrink it down. + m_pMemory = (T*)realloc( m_pMemory, m_nAllocationCount * sizeof(T) ); +} + +//----------------------------------------------------------------------------- +// The CUtlMemory class: +// A growable memory class which doubles in size by default. +//----------------------------------------------------------------------------- +template< class T, int nAlignment > +class CUtlMemoryAligned : public CUtlMemory +{ +public: + // constructor, destructor + CUtlMemoryAligned( int nGrowSize = 0, int nInitSize = 0 ); + CUtlMemoryAligned( T* pMemory, int numElements ); + CUtlMemoryAligned( const T* pMemory, int numElements ); + ~CUtlMemoryAligned(); + + // Attaches the buffer to external memory.... + void SetExternalBuffer( T* pMemory, int numElements ); + void SetExternalBuffer( const T* pMemory, int numElements ); + + // Grows the memory, so that at least allocated + num elements are allocated + void Grow( int num = 1 ); + + // Makes sure we've got at least this much memory + void EnsureCapacity( int num ); + + // Memory deallocation + void Purge(); + + // Purge all but the given number of elements (NOT IMPLEMENTED IN CUtlMemoryAligned) + void Purge( int numElements ) { assert( 0 ); } + +private: + void *Align( const void *pAddr ); +}; + + +//----------------------------------------------------------------------------- +// Aligns a pointer +//----------------------------------------------------------------------------- +template< class T, int nAlignment > +void *CUtlMemoryAligned::Align( const void *pAddr ) +{ + size_t nAlignmentMask = nAlignment - 1; + return (void*)( ((size_t)pAddr + nAlignmentMask) & (~nAlignmentMask) ); +} + + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- +template< class T, int nAlignment > +CUtlMemoryAligned::CUtlMemoryAligned( int nGrowSize, int nInitAllocationCount ) +{ + CUtlMemory::m_pMemory = 0; + CUtlMemory::m_nAllocationCount = nInitAllocationCount; + CUtlMemory::m_nGrowSize = nGrowSize; + ValidateGrowSize(); + + // Alignment must be a power of two + COMPILE_TIME_ASSERT( (nAlignment & (nAlignment-1)) == 0 ); + assert( (nGrowSize >= 0) && (nGrowSize != CUtlMemory::EXTERNAL_BUFFER_MARKER) ); + if ( CUtlMemory::m_nAllocationCount ) + { + CUtlMemory::m_pMemory = (T*)_aligned_malloc( nInitAllocationCount * sizeof(T), nAlignment ); + } +} + +template< class T, int nAlignment > +CUtlMemoryAligned::CUtlMemoryAligned( T* pMemory, int numElements ) +{ + // Special marker indicating externally supplied memory + CUtlMemory::m_nGrowSize = CUtlMemory::EXTERNAL_BUFFER_MARKER; + + CUtlMemory::m_pMemory = (T*)Align( pMemory ); + CUtlMemory::m_nAllocationCount = ( (int)(pMemory + numElements) - (int)CUtlMemory::m_pMemory ) / sizeof(T); +} + +template< class T, int nAlignment > +CUtlMemoryAligned::CUtlMemoryAligned( const T* pMemory, int numElements ) +{ + // Special marker indicating externally supplied memory + CUtlMemory::m_nGrowSize = CUtlMemory::EXTERNAL_CONST_BUFFER_MARKER; + + CUtlMemory::m_pMemory = (T*)Align( pMemory ); + CUtlMemory::m_nAllocationCount = ( (int)(pMemory + numElements) - (int)CUtlMemory::m_pMemory ) / sizeof(T); +} + +template< class T, int nAlignment > +CUtlMemoryAligned::~CUtlMemoryAligned() +{ + Purge(); +} + + +//----------------------------------------------------------------------------- +// Attaches the buffer to external memory.... +//----------------------------------------------------------------------------- +template< class T, int nAlignment > +void CUtlMemoryAligned::SetExternalBuffer( T* pMemory, int numElements ) +{ + // Blow away any existing allocated memory + Purge(); + + CUtlMemory::m_pMemory = (T*)Align( pMemory ); + CUtlMemory::m_nAllocationCount = ( (int)(pMemory + numElements) - (int)CUtlMemory::m_pMemory ) / sizeof(T); + + // Indicate that we don't own the memory + CUtlMemory::m_nGrowSize = CUtlMemory::EXTERNAL_BUFFER_MARKER; +} + +template< class T, int nAlignment > +void CUtlMemoryAligned::SetExternalBuffer( const T* pMemory, int numElements ) +{ + // Blow away any existing allocated memory + Purge(); + + CUtlMemory::m_pMemory = (T*)Align( pMemory ); + CUtlMemory::m_nAllocationCount = ( (int)(pMemory + numElements) - (int)CUtlMemory::m_pMemory ) / sizeof(T); + + // Indicate that we don't own the memory + CUtlMemory::m_nGrowSize = CUtlMemory::EXTERNAL_CONST_BUFFER_MARKER; +} + + +//----------------------------------------------------------------------------- +// Grows the memory +//----------------------------------------------------------------------------- +template< class T, int nAlignment > +void CUtlMemoryAligned::Grow( int num ) +{ + assert( num > 0 ); + + if ( IsExternallyAllocated() ) + { + // Can't grow a buffer whose memory was externally allocated + assert(0); + return; + } + + // Make sure we have at least numallocated + num allocations. + // Use the grow rules specified for this memory (in m_nGrowSize) + int nAllocationRequested = CUtlMemory::m_nAllocationCount + num; + + CUtlMemory::m_nAllocationCount = UtlMemory_CalcNewAllocationCount( CUtlMemory::m_nAllocationCount, CUtlMemory::m_nGrowSize, nAllocationRequested, sizeof(T) ); + + if ( CUtlMemory::m_pMemory ) + { + CUtlMemory::m_pMemory = (T*)_aligned_realloc( CUtlMemory::m_pMemory, CUtlMemory::m_nAllocationCount * sizeof(T), nAlignment ); + assert( CUtlMemory::m_pMemory ); + } + else + { + CUtlMemory::m_pMemory = (T*)_aligned_malloc( CUtlMemory::m_nAllocationCount * sizeof(T), nAlignment ); + assert( CUtlMemory::m_pMemory ); + } +} + + +//----------------------------------------------------------------------------- +// Makes sure we've got at least this much memory +//----------------------------------------------------------------------------- +template< class T, int nAlignment > +inline void CUtlMemoryAligned::EnsureCapacity( int num ) +{ + if ( CUtlMemory::m_nAllocationCount >= num ) + return; + + if ( IsExternallyAllocated() ) + { + // Can't grow a buffer whose memory was externally allocated + assert(0); + return; + } + + CUtlMemory::m_nAllocationCount = num; + + if ( CUtlMemory::m_pMemory ) + { + CUtlMemory::m_pMemory = (T*)_aligned_realloc( CUtlMemory::m_pMemory, CUtlMemory::m_nAllocationCount * sizeof(T), nAlignment ); + } + else + { + CUtlMemory::m_pMemory = (T*)_aligned_malloc( CUtlMemory::m_nAllocationCount * sizeof(T), nAlignment ); + } +} + + +//----------------------------------------------------------------------------- +// Memory deallocation +//----------------------------------------------------------------------------- +template< class T, int nAlignment > +void CUtlMemoryAligned::Purge() +{ + if ( !IsExternallyAllocated() ) + { + if ( CUtlMemory::m_pMemory ) + { + _aligned_free( CUtlMemory::m_pMemory ); + CUtlMemory::m_pMemory = 0; + } + CUtlMemory::m_nAllocationCount = 0; + } +} + +#endif // UTLMEMORY_H \ No newline at end of file diff --git a/utils/common/virtualfs.cpp b/utils/common/virtualfs.cpp new file mode 100644 index 0000000..22721c7 --- /dev/null +++ b/utils/common/virtualfs.cpp @@ -0,0 +1,442 @@ +/* +virtualfs.cpp - virtual file system, operating into memory +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "conprint.h" +#include +#include +#include +#include +#include +#include "cmdlib.h" +#include "stringlib.h" +#include "filesystem.h" + +#define MAX_TOKEN 2048 // parse token length + +/* +============================================================================= + +VIRTUAL FILE SYSTEM - READ\WRITE DATA INTO MEMORY + +============================================================================= +*/ +/* +==================== +VFS_Create + +Create an empty virtualfile or from buffer +==================== +*/ +vfile_t *VFS_Create( const byte *buffer, size_t buffsize ) +{ + vfile_t *file = (vfile_t *)Mem_Alloc( sizeof( vfile_t )); + + if( buffsize <= 0 ) + buffsize = FILE_BUFF_SIZE; // empty file + + file->length = file->buffsize = buffsize; + file->buff = (byte *)Mem_Alloc( file->buffsize ); + if( buffer ) memcpy( file->buff, buffer, buffsize ); + file->offset = 0; + + return file; +} + +/* +==================== +VFS_Read + +Reading the virtual file +==================== +*/ +long VFS_Read( vfile_t *file, void *buffer, size_t buffersize ) +{ + long read_size = 0; + + if( buffersize == 0 ) + return 1; + + if( !file ) return 0; + + // check for enough room + if( file->offset >= file->length ) + return 0; //hit EOF + + if(( file->offset + buffersize ) <= file->length ) + { + memcpy( buffer, file->buff + file->offset, buffersize ); + file->offset += buffersize; + read_size = buffersize; + } + else + { + int reduced_size = file->length - file->offset; + memcpy( buffer, file->buff + file->offset, reduced_size ); + file->offset += reduced_size; + read_size = reduced_size; + } + + return read_size; +} + +/* +==================== +VFS_Write + +Write to the virtual file +==================== +*/ +long VFS_Write( vfile_t *file, const void *buf, size_t size ) +{ + if( !file ) return -1; + + if(( file->offset + size ) >= file->buffsize ) + { + int newsize = file->offset + size + FILE_BUFF_SIZE; + + if( file->buffsize < newsize ) + { + // reallocate buffer now + file->buff = (byte *)Mem_Realloc( file->buff, newsize ); + file->buffsize = newsize; // merge buffsize + } + } + + // write into buffer + if( buf ) memcpy( file->buff + file->offset, buf, size ); + file->offset += size; + + if( file->offset > file->length ) + file->length = file->offset; + + return size; +} + +/* +==================== +VFS_Insert + +Insert new portion at current position (not overwrite) +==================== +*/ +long VFS_Insert( vfile_t *file, const void *buf, size_t size ) +{ + byte *backup; + long rp_size; + + if( !file || !file->buff || size <= 0 ) + return -1; + + if(( file->length + size ) >= file->buffsize ) + { + int newsize = file->length + size + FILE_BUFF_SIZE; + + if( file->buffsize < newsize ) + { + // reallocate buffer now + file->buff = (byte *)Mem_Realloc( file->buff, newsize ); + file->buffsize = newsize; // update buffsize + } + } + + // backup right part + rp_size = file->length - file->offset; + backup = (byte *)Mem_Alloc( rp_size ); + memcpy( backup, file->buff + file->offset, rp_size ); + + // insert into buffer + memcpy( file->buff + file->offset, buf, size ); + file->offset += size; + + // write right part buffer + memcpy( file->buff + file->offset, backup, rp_size ); + Mem_Free( backup ); + + if(( file->offset + rp_size ) > file->length ) + file->length = file->offset + rp_size; + + return file->length; +} + +/* +==================== +VFS_GetBuffer + +Get buffer pointer +==================== +*/ +byte *VFS_GetBuffer( vfile_t *file ) +{ + if( !file ) return NULL; + return file->buff; +} + +/* +==================== +VFS_GetSize + +Get buffer size +==================== +*/ +long VFS_GetSize( vfile_t *file ) +{ + if( !file ) return 0; + return file->length; +} + +/* +==================== +VFS_Tell + +get current position +==================== +*/ +long VFS_Tell( vfile_t *file ) +{ + if( !file ) return 0; + return file->offset; +} + +/* +==================== +VFS_Eof + +indicates at reached end of virtual file +==================== +*/ +bool VFS_Eof( vfile_t *file ) +{ + if( !file ) return 1; + return (file->offset == file->length) ? true : false; +} + +/* +==================== +VFS_Print + +Print a string into a file +==================== +*/ +int VFS_Print( vfile_t *file, const char *msg ) +{ + return VFS_Write( file, msg, Q_strlen( msg )); +} + +/* +==================== +VFS_IPrint + +Insert a string into a file +==================== +*/ +int VFS_IPrint( vfile_t *file, const char *msg ) +{ + return VFS_Insert( file, msg, Q_strlen( msg )); +} + + +/* +==================== +VFS_VPrintf + +Print a formatted string into a buffer +==================== +*/ +int VFS_VPrintf( vfile_t *file, const char *format, va_list ap ) +{ + long buff_size = MAX_TOKEN; + char *tempbuff; + int len; + + while( 1 ) + { + tempbuff = (char *)Mem_Alloc( buff_size ); + len = Q_vsprintf( tempbuff, format, ap ); + if( len >= 0 && len < buff_size ) break; + Mem_Free( tempbuff ); + buff_size <<= 1; + tempbuff = NULL; + } + + len = VFS_Write( file, tempbuff, len ); + Mem_Free( tempbuff ); + + return len; +} + +/* +==================== +VFS_VIPrintf + +Insert a formatted string into a buffer +==================== +*/ +int VFS_VIPrintf( vfile_t *file, const char *format, va_list ap ) +{ + long buff_size = MAX_TOKEN; + char *tempbuff; + int len; + + while( 1 ) + { + tempbuff = (char *)Mem_Alloc( buff_size ); + len = Q_vsprintf( tempbuff, format, ap ); + if( len >= 0 && len < buff_size ) break; + Mem_Free( tempbuff ); + buff_size <<= 1; + tempbuff = NULL; + } + + len = VFS_Insert( file, tempbuff, len ); + Mem_Free( tempbuff ); + + return len; +} + +/* +==================== +VFS_Printf + +Print a formatted string into a buffer +==================== +*/ +int VFS_Printf( vfile_t *file, const char *format, ... ) +{ + int result; + va_list args; + + va_start( args, format ); + result = VFS_VPrintf( file, format, args ); + va_end( args ); + + return result; +} + +/* +==================== +VFS_IPrintf + +Print a formatted string into a buffer +==================== +*/ +int VFS_IPrintf( vfile_t *file, const char *format, ... ) +{ + int result; + va_list args; + + va_start( args, format ); + result = VFS_VIPrintf( file, format, args ); + va_end( args ); + + return result; +} + +/* +==================== +VFS_Seek + +seeking into buffer +==================== +*/ +int VFS_Seek( vfile_t *file, long offset, int whence ) +{ + if( !file ) return -1; + + // compute the file offset + switch( whence ) + { + case SEEK_CUR: + offset += file->offset; + break; + case SEEK_SET: + break; + case SEEK_END: + offset += file->length; + break; + default: return -1; + } + + if( offset < 0 || offset > file->length ) + return -1; + + file->offset = offset; + + return 0; +} + +/* +==================== +VFS_Getc + +Get the next character of a file +==================== +*/ +int VFS_Getc( vfile_t *file ) +{ + char c; + + if( !VFS_Read( file, &c, 1 )) + return EOF; + + return c; +} + +/* +==================== +VFS_Gets + +Get the newline +==================== +*/ +int VFS_Gets( vfile_t* file, byte *string, size_t bufsize ) +{ + int c, end = 0; + + while( 1 ) + { + c = VFS_Getc( file ); + + if( c == '\r' || c == '\n' || c < 0 ) + break; + + if( end < bufsize - 1 ) + string[end++] = c; + } + string[end] = 0; + + // remove \n following \r + if( c == '\r' ) + { + c = VFS_Getc( file ); + if( c != '\n' ) VFS_Seek( file, -1, SEEK_CUR ); // rewind + } + + return c; +} + +/* +==================== +VFS_Close + +Free the memory +==================== +*/ +void VFS_Close( vfile_t *file ) +{ + if( !file ) return; + + if( file->buff ) Mem_Free( file->buff ); + Mem_Free( file ); // himself +} \ No newline at end of file diff --git a/utils/common/wadfile.cpp b/utils/common/wadfile.cpp new file mode 100644 index 0000000..1156f51 --- /dev/null +++ b/utils/common/wadfile.cpp @@ -0,0 +1,933 @@ +/* +wadfile.cpp - reading & writing wad file lumps +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "conprint.h" +#include +#include +#include +#include +#include +#include "cmdlib.h" +#include "stringlib.h" +#include "filesystem.h" +#include "wfile.h" + +#ifdef ALLOW_WADS_IN_PACKS +long FS_Tell( file_t *file ); +int FS_Seek( file_t *file, long offset, int whence ); +file_t *FS_Open( const char *filepath, const char *mode, bool gamedironly ); +long FS_Read( file_t *file, void *buffer, size_t buffersize ); +long FS_Write( file_t *file, const void *data, size_t datasize ); +int FS_Close( file_t *file ); +#else +#define FS_Tell( x ) tell( x ) +#define FS_Seek( x, y, z ) lseek( x, y, z ) +#define FS_Read( x, y, z ) read( x, y, z ) +#define FS_Write( x, y, z ) write( x, y, z ) +#define FS_Close( x ) close( x ) +#endif + +static int replace_level = REP_IGNORE; + +/* +============================================================================= + +WADSYSTEM PRIVATE COMMON FUNCTIONS + +============================================================================= +*/ +// associate extension with wad type +static const wadtype_t wad_types[] = +{ +{ "pal", TYP_PALETTE}, // palette +{ "dds", TYP_DDSTEX }, // DDS image +{ "lmp", TYP_GFXPIC }, // quake1, hl pic +{ "fnt", TYP_QFONT }, // hl qfonts +{ "mip", TYP_MIPTEX }, // hl/q1 texture +{ NULL, TYP_NONE } // terminator +}; + +// suffix converts to img_type and back +const wadtype_t wad_hints[] = +{ +{ "", IMG_DIFFUSE }, // no suffix +{ "_mask", IMG_ALPHAMASK }, // alpha-channel stored to another lump +{ "_norm", IMG_NORMALMAP }, // indexed normalmap +{ "_spec", IMG_GLOSSMAP }, // grayscale\color specular +{ "_gpow", IMG_GLOSSPOWER }, // grayscale gloss power +{ "_hmap", IMG_HEIGHTMAP }, // heightmap (can be converted to normalmap) +{ "_luma", IMG_LUMA }, // self-illuminate parts on the diffuse +{ "_adec", IMG_DECAL_ALPHA }, // classic HL-decal (with alpha-channel) +{ "_cdec", IMG_DECAL_COLOR }, // paranoia decal (base 127 127 127) +{ NULL, 0 } // terminator +}; + +void SetReplaceLevel( int level ) +{ + if( level < REP_IGNORE ) level = REP_IGNORE; + if( level > REP_FORCE ) level = REP_FORCE; + + replace_level = level; +} + +int GetReplaceLevel( void ) +{ + return replace_level; +} + +/* +=========== +W_CheckHandle + +return filehandle +=========== +*/ +static bool W_CheckHandle( wfile_t *wad ) +{ + if( !wad ) return false; +#ifdef ALLOW_WADS_IN_PACKS + return (wad->handle != NULL) ? true : false; +#else + return (wad->handle >= 0) ? true : false; +#endif +} + +/* +=========== +W_GetHandle + +return filehandle +=========== +*/ +int W_GetHandle( wfile_t *wad ) +{ + if( !wad ) return -1; +#ifndef ALLOW_WADS_IN_PACKS + return wad->handle; +#endif + return -1; +} + +/* +=========== +W_TypeFromExt + +Extracts file type from extension +=========== +*/ +char W_TypeFromExt( const char *lumpname ) +{ + const char *ext = COM_FileExtension( lumpname ); + const wadtype_t *type; + + // we not known about filetype, so match only by filename + if( !Q_strcmp( ext, "*" ) || !Q_strcmp( ext, "" )) + return TYP_ANY; + + for( type = wad_types; type->ext; type++ ) + { + if( !Q_stricmp( ext, type->ext )) + return type->type; + } + + return TYP_NONE; +} + +/* +=========== +W_ExtFromType + +Convert type to extension +=========== +*/ +const char *W_ExtFromType( char lumptype ) +{ + const wadtype_t *type; + + // we not known about filetype, so match only by filename + if( lumptype == TYP_NONE || lumptype == TYP_ANY ) + return ""; + + for( type = wad_types; type->ext; type++ ) + { + if( lumptype == type->type ) + return type->ext; + } + + return ""; +} + +/* +=========== +W_HintFromSuf + +Convert name suffix into image type +=========== +*/ +char W_HintFromSuf( const char *lumpname ) +{ + char barename[64]; + char suffix[8]; + size_t namelen; + const wadtype_t *hint; + + // trying to extract hint from the name + Q_strncpy( barename, lumpname, sizeof( barename )); + namelen = Q_strlen( barename ); + + if( namelen <= HINT_NAMELEN ) + return IMG_DIFFUSE; + + Q_strncpy( suffix, barename + namelen - HINT_NAMELEN, sizeof( suffix )); + + // we not known about filetype, so match only by filename + for( hint = wad_hints; hint->ext; hint++ ) + { + if( !Q_stricmp( suffix, hint->ext )) + return hint->type; + } + + // no any special type was found + return IMG_DIFFUSE; +} + +/* +=========== +W_FindLump + +Serach for already existed lump +=========== +*/ +dlumpinfo_t *W_FindLump( wfile_t *wad, const char *name, const char matchtype ) +{ + char img_type = IMG_DIFFUSE; + char barename[64], suffix[8]; + int left, right; + size_t namelen; + const wadtype_t *hint; + + if( !wad || !wad->lumps || matchtype == TYP_NONE ) + return NULL; + + // trying to extract hint from the name + Q_strncpy( barename, name, sizeof( barename )); + namelen = Q_strlen( barename ); + + if( namelen > HINT_NAMELEN ) + { + Q_strncpy( suffix, barename + namelen - HINT_NAMELEN, sizeof( suffix )); + + // we not known about filetype, so match only by filename + for( hint = wad_hints; hint->ext; hint++ ) + { + if( !Q_stricmp( suffix, hint->ext )) + { + img_type = hint->type; + break; + } + } + + if( img_type != IMG_DIFFUSE ) + barename[namelen - HINT_NAMELEN] = '\0'; // kill the suffix + } + + // look for the file (binary search) + left = 0; + right = wad->numlumps - 1; + + while( left <= right ) + { + int middle = (left + right) / 2; + int diff = Q_stricmp( wad->lumps[middle].name, barename ); + + if( !diff ) + { + if( wad->lumps[middle].img_type > img_type ) + diff = 1; + else if( wad->lumps[middle].img_type < img_type ) + diff = -1; + else if(( matchtype == TYP_ANY ) || ( matchtype == wad->lumps[middle].type )) + return &wad->lumps[middle]; // found + else if( wad->lumps[middle].type < matchtype ) + diff = 1; + else if( wad->lumps[middle].type > matchtype ) + diff = -1; + else break; // not found + } + + // if we're too far in the list + if( diff > 0 ) right = middle - 1; + else left = middle + 1; + } + + return NULL; +} + +/* +==================== +W_AddFileToWad + +Add a file to the list of files contained into a package +and sort LAT in alpha-bethical order +==================== +*/ +static dlumpinfo_t *W_AddFileToWad( const char *name, wfile_t *wad, dlumpinfo_t *newlump ) +{ + int left, right; + dlumpinfo_t *plump; + + // look for the slot we should put that file into (binary search) + left = 0; + right = wad->numlumps - 1; + + while( left <= right ) + { + int middle = ( left + right ) / 2; + int diff = Q_stricmp( wad->lumps[middle].name, name ); + + if( !diff ) + { + if( wad->lumps[middle].img_type > newlump->img_type ) + diff = 1; + else if( wad->lumps[middle].img_type < newlump->img_type ) + diff = -1; + else if( wad->lumps[middle].type < newlump->type ) + diff = 1; + else if( wad->lumps[middle].type > newlump->type ) + diff = -1; + else MsgDev( D_WARN, "Wad %s contains the file %s several times\n", wad->filename, name ); + } + + // if we're too far in the list + if( diff > 0 ) right = middle - 1; + else left = middle + 1; + } + + // we have to move the right of the list by one slot to free the one we need + plump = &wad->lumps[left]; + memmove( plump + 1, plump, ( wad->numlumps - left ) * sizeof( *plump )); + wad->numlumps++; + + *plump = *newlump; + memcpy( plump->name, name, sizeof( plump->name )); + + return plump; +} + +/* +=========== +W_ReadLump + +reading lump into temp buffer +=========== +*/ +byte *W_ReadLump( wfile_t *wad, dlumpinfo_t *lump, size_t *lumpsizeptr ) +{ + size_t oldpos, size = 0; + byte *buf; + + // assume error + if( lumpsizeptr ) *lumpsizeptr = 0; + + // no wads loaded + if( !wad || !lump ) return NULL; + + oldpos = FS_Tell( wad->handle ); // don't forget restore original position + + if( FS_Seek( wad->handle, lump->filepos, SEEK_SET ) == -1 ) + { + MsgDev( D_ERROR, "W_ReadLump: %s is corrupted\n", lump->name ); + FS_Seek( wad->handle, oldpos, SEEK_SET ); + return NULL; + } + + buf = (byte *)Mem_Alloc( lump->disksize, C_FILESYSTEM ); + size = FS_Read( wad->handle, buf, lump->disksize ); + + if( size < lump->disksize ) + { + MsgDev( D_WARN, "W_ReadLump: %s is probably corrupted\n", lump->name ); + FS_Seek( wad->handle, oldpos, SEEK_SET ); + Mem_Free( buf, C_FILESYSTEM ); + return NULL; + } + + if( lumpsizeptr ) *lumpsizeptr = lump->disksize; + FS_Seek( wad->handle, oldpos, SEEK_SET ); + + return buf; +} + +/* +=========== +W_WriteLump + +compress and write lump +=========== +*/ +bool W_WriteLump( wfile_t *wad, dlumpinfo_t *lump, const void *data, size_t datasize ) +{ + if( !wad || !lump ) return false; + + if( !data || !datasize ) + { + MsgDev( D_WARN, "W_WriteLump: ignore blank lump %s - nothing to save\n", lump->name ); + return false; + } + + if( wad->mode == O_RDONLY ) + { + MsgDev( D_ERROR, "W_WriteLump: %s opened in readonly mode\n", wad->filename ); + return false; + } + + lump->size = lump->disksize = datasize; + + if( FS_Write( wad->handle, data, datasize ) == datasize ) + return true; + return false; +} + +static bool W_SysOpen( wfile_t *wad, const char *filename, const char *mode, bool ext_path ) +{ +#ifndef ALLOW_WADS_IN_PACKS + int mod, opt; + + // parse the mode string + switch( mode[0] ) + { + case 'r': + mod = O_RDONLY; + opt = 0; + break; + case 'w': + mod = O_WRONLY; + opt = O_CREAT|O_TRUNC; + break; + case 'a': + mod = O_WRONLY; + opt = O_CREAT; + break; + default: + MsgDev( D_ERROR, "W_Open(%s, %s): invalid mode\n", filename, mode ); + return false; + } + + for( int ind = 1; mode[ind] != '\0'; ind++ ) + { + switch( mode[ind] ) + { + case '+': + mod = O_RDWR; + break; + case 'b': + opt |= O_BINARY; + break; + default: + MsgDev( D_ERROR, "W_Open: %s: unknown char in mode (%c)\n", filename, mode, mode[ind] ); + break; + } + } + + wad->handle = open( filename, mod|opt, 0666 ); + return true; +#else + // NOTE: FS_Open is load wad file from the first pak in the list (while ext_path is false) + if( ext_path ) wad->handle = FS_Open( filename, "rb", false ); + else wad->handle = FS_Open( COM_FileWithoutPath( filename ), "rb", false ); + return true; +#endif +} + +/* +============================================================================= + +WADSYSTEM PUBLIC BASE FUNCTIONS + +============================================================================= +*/ +/* +=========== +W_Open + +open the wad for reading & writing +=========== +*/ +wfile_t *W_Open( const char *filename, const char *mode, int *error, bool ext_path ) +{ + dwadinfo_t header; + wfile_t *wad = (wfile_t *)Mem_Alloc( sizeof( wfile_t ), C_FILESYSTEM ); + const char *comment = "Generated by Xash3D MakeWad tool.\0"; + int ind, mod, opt, lumpcount; + + // parse the mode string + switch( mode[0] ) + { + case 'r': + mod = O_RDONLY; + opt = 0; + break; + case 'w': + mod = O_WRONLY; + opt = O_CREAT|O_TRUNC; + break; + case 'a': + mod = O_WRONLY; + opt = O_CREAT; + break; + default: + MsgDev( D_ERROR, "W_Open(%s, %s): invalid mode\n", filename, mode ); + return NULL; + } + + for( ind = 1; mode[ind] != '\0'; ind++ ) + { + switch( mode[ind] ) + { + case '+': + mod = O_RDWR; + break; + case 'b': + opt |= O_BINARY; + break; + default: + MsgDev( D_ERROR, "W_Open: %s: unknown char in mode (%c)\n", filename, mode, mode[ind] ); + break; + } + } + +#ifdef ALLOW_WADS_IN_PACKS + // NOTE: FS_Open is load wad file from the first pak in the list (while ext_path is false) + if( ext_path ) wad->handle = FS_Open( filename, "rb", false ); + else wad->handle = FS_Open( COM_FileWithoutPath( filename ), "rb", false ); +#else + wad->handle = open( filename, mod|opt, 0666 ); +#endif + if( !W_CheckHandle( wad )) + { + MsgDev( D_ERROR, "W_Open: couldn't open %s\n", filename ); + if( error ) *error = WAD_LOAD_COULDNT_OPEN; + W_Close( wad ); + return NULL; + } + + // copy wad name + Q_strncpy( wad->filename, filename, sizeof( wad->filename )); + wad->filetime = COM_FileTime( filename ); + FS_Seek( wad->handle, 0, SEEK_END ); + size_t wadsize = FS_Tell( wad->handle ); + FS_Seek( wad->handle, 0, SEEK_SET ); + + // if the file is opened in "write", "append", or "read/write" mode + if( mod == O_WRONLY || !wadsize ) + { + dwadinfo_t hdr; + + wad->numlumps = 0; // blank wad + wad->lumps = NULL; // + wad->mode = O_WRONLY; + + // save space for header + hdr.ident = IDWAD3HEADER; + hdr.numlumps = wad->numlumps; + hdr.infotableofs = sizeof( dwadinfo_t ); + FS_Write( wad->handle, &hdr, sizeof( hdr )); + FS_Write( wad->handle, comment, Q_strlen( comment ) + 1 ); + wad->infotableofs = FS_Tell( wad->handle ); + } + else if( mod == O_RDWR || mod == O_RDONLY ) + { + if( mod == O_RDWR ) + wad->mode = O_APPEND; + else wad->mode = O_RDONLY; + + if( FS_Read( wad->handle, &header, sizeof( dwadinfo_t )) != sizeof( dwadinfo_t )) + { + MsgDev( D_ERROR, "W_Open: %s can't read header\n", filename ); + if( error ) *error = WAD_LOAD_BAD_HEADER; + W_Close( wad ); + return NULL; + } + + if( header.ident != IDWAD3HEADER ) + { + if( header.ident != IDWAD2HEADER ) + { + MsgDev( D_ERROR, "W_Open: %s is not a WAD3 file\n", filename ); + if( error ) *error = WAD_LOAD_BAD_HEADER; + } + else if( error ) + *error = WAD_LOAD_NO_FILES;// shut up the warnings + + W_Close( wad ); + return NULL; + } + + lumpcount = header.numlumps; + + if( lumpcount >= MAX_FILES_IN_WAD && wad->mode == O_APPEND ) + { + MsgDev( D_WARN, "W_Open: %s is full (%i lumps)\n", filename, lumpcount ); + if( error ) *error = WAD_LOAD_TOO_MANY_FILES; + wad->mode = O_RDONLY; // set read-only mode + } + else if( lumpcount <= 0 && wad->mode == O_RDONLY ) + { + MsgDev( D_ERROR, "W_Open: %s has no lumps\n", filename ); + if( error ) *error = WAD_LOAD_NO_FILES; + W_Close( wad ); + return NULL; + } + else if( error ) *error = WAD_LOAD_OK; + + wad->infotableofs = header.infotableofs; // save infotableofs position + + if( FS_Seek( wad->handle, wad->infotableofs, SEEK_SET ) == -1 ) + { + MsgDev( D_ERROR, "W_Open: %s can't find lump allocation table\n", filename ); + if( error ) *error = WAD_LOAD_BAD_FOLDERS; + W_Close( wad ); + return NULL; + } + + size_t lat_size = lumpcount * sizeof( dlumpinfo_t ); + + // NOTE: lumps table can be reallocated for O_APPEND mode + dlumpinfo_t *srclumps = (dlumpinfo_t *)Mem_Alloc( lat_size, C_FILESYSTEM ); + + if( FS_Read( wad->handle, srclumps, lat_size ) != lat_size ) + { + MsgDev( D_ERROR, "W_ReadLumpTable: %s has corrupted lump allocation table\n", wad->filename ); + if( error ) *error = WAD_LOAD_CORRUPTED; + Mem_Free( srclumps, C_FILESYSTEM ); + W_Close( wad ); + return NULL; + } + + // starting to add lumps + wad->lumps = (dlumpinfo_t *)Mem_Alloc( lat_size, C_FILESYSTEM ); + wad->numlumps = 0; + + // sort lumps for binary search + for( int i = 0; i < lumpcount; i++ ) + { + char name[16]; + int k; + + // cleanup lumpname + Q_strncpy( name, srclumps[i].name, sizeof( name )); + + // check for '*' symbol issues (quake1) + k = Q_strlen( Q_strrchr( name, '*' )); + if( k ) name[Q_strlen( name ) - k] = '!'; // quake1 issues (can't save images that contain '*' symbol) + + // fixups bad image types (some quake wads) + if( srclumps[i].img_type < 0 || srclumps[i].img_type > IMG_DECAL_COLOR ) + srclumps[i].img_type = IMG_DIFFUSE; + + W_AddFileToWad( name, wad, &srclumps[i] ); + } + + // release source lumps + Mem_Free( srclumps, C_FILESYSTEM ); + + // if we are in append mode - we need started from infotableofs poisition + // overwrite lumptable as well, we have her copy in wad->lumps + if( wad->mode == O_APPEND ) + FS_Seek( wad->handle, wad->infotableofs, SEEK_SET ); + } + + // and leave the file open + return wad; +} + +/* +=========== +W_Close + +finalize wad or just close +=========== +*/ +void W_Close( wfile_t *wad ) +{ + if( !wad ) return; + + if( W_CheckHandle( wad ) && ( wad->mode == O_APPEND || wad->mode == O_WRONLY )) + { + dwadinfo_t hdr; + + // write the lumpinfo + size_t ofs = FS_Tell( wad->handle ); + FS_Write( wad->handle, wad->lumps, wad->numlumps * sizeof( dlumpinfo_t )); + + // write the header + hdr.ident = IDWAD3HEADER; + hdr.numlumps = wad->numlumps; + hdr.infotableofs = ofs; + + FS_Seek( wad->handle, 0, SEEK_SET ); + FS_Write( wad->handle, &hdr, sizeof( hdr )); + } + + if( wad->lumps ) + Mem_Free( wad->lumps, C_FILESYSTEM ); + + if( W_CheckHandle( wad )) + FS_Close( wad->handle ); + Mem_Free( wad, C_FILESYSTEM ); // free himself +} + +/* +=========== +W_SaveLump + +write new or replace existed lump +=========== +*/ +int W_SaveLump( wfile_t *wad, const char *lump, const void *data, size_t datasize, char type, char attribs ) +{ + dlumpinfo_t *find, newlump; + int lat_size, oldpos; + char lumpname[64]; + + if( !wad || !lump ) return -1; + + if( !data || !datasize ) + { + MsgDev( D_WARN, "W_SaveLump: ignore blank lump %s - nothing to save\n", lump ); + return -1; + } + + if( wad->mode == O_RDONLY ) + { + MsgDev( D_ERROR, "W_SaveLump: %s opened in readonly mode\n", wad->filename ); + return -1; + } + + if( wad->numlumps >= MAX_FILES_IN_WAD ) + { + MsgDev( D_ERROR, "W_SaveLump: %s is full\n", wad->filename ); + return -1; + } + + find = W_FindLump( wad, lump, type ); + + if( find != NULL ) + { + switch( replace_level ) + { + case REP_IGNORE: + MsgDev( D_ERROR, "W_SaveLump: %s already exist\n", lump ); + return -1; + case REP_NORMAL: + case REP_FORCE: + if( FBitSet( find->attribs, ATTR_READONLY )) + { + // g-cont. i left this limitation as a protect of the replacement of compressed lumps + MsgDev( D_ERROR, "W_ReplaceLump: %s is read-only\n", find->name ); + return -1; + } + + if( datasize != find->size ) + { + MsgDev( D_ERROR, "W_ReplaceLump: %s [%s] should be [%s]\n", + lumpname, Q_memprint( datasize ), Q_memprint( find->size )); + return -1; + } + + oldpos = FS_Tell( wad->handle ); // don't forget restore original position + + if( FS_Seek( wad->handle, find->filepos, SEEK_SET ) == -1 ) + { + MsgDev( D_ERROR, "W_ReplaceLump: %s is corrupted\n", find->name ); + FS_Seek( wad->handle, oldpos, SEEK_SET ); + return -1; + } + + if( FS_Write( wad->handle, data, datasize ) != find->disksize ) + MsgDev( D_WARN, "W_ReplaceLump: %s probably replaced with errors\n", find->name ); + + // restore old position + FS_Seek( wad->handle, oldpos, SEEK_SET ); + + return wad->numlumps; + } + } + + // prepare lump name + Q_strncpy( lumpname, lump, sizeof( lumpname )); + + // extract image hint + char hint = W_HintFromSuf( lumpname ); + if( hint != IMG_DIFFUSE ) lumpname[Q_strlen( lumpname ) - HINT_NAMELEN] = '\0'; // kill the suffix + + if( Q_strlen( lumpname ) >= WAD3_NAMELEN ) + { + // name is too long + MsgDev( D_ERROR, "W_SaveLump: %s more than %i symbols\n", lumpname, WAD3_NAMELEN ); + return -1; + } + + lat_size = sizeof( dlumpinfo_t ) * (wad->numlumps + 1); + + // reallocate lumptable + wad->lumps = (dlumpinfo_t *)Mem_Realloc( wad->lumps, lat_size, C_FILESYSTEM ); + + memset( &newlump, 0, sizeof( newlump )); + + // write header + Q_strncpy( newlump.name, lumpname, WAD3_NAMELEN ); + newlump.filepos = FS_Tell( wad->handle ); + newlump.attribs = attribs; + newlump.img_type = hint; + newlump.type = type; + + if( !W_WriteLump( wad, &newlump, data, datasize )) + return -1; + + // record entry and re-sort table + W_AddFileToWad( lumpname, wad, &newlump ); + + MsgDev( D_REPORT, "W_SaveLump: %s, size %s\n", newlump.name, Q_memprint( newlump.disksize )); + + return wad->numlumps; +} + +/* +=========== +W_LoadLump + +loading lump into the tmp buffer +=========== +*/ +byte *W_LoadLump( wfile_t *wad, const char *lumpname, size_t *lumpsizeptr, const char type ) +{ + // assume error + if( lumpsizeptr ) *lumpsizeptr = 0; + + if( !wad ) return NULL; + dlumpinfo_t *lump = W_FindLump( wad, lumpname, type ); + return W_ReadLump( wad, lump, lumpsizeptr ); +} + +/* +=========== +W_FindMiptex + +find specified lump for .mip +=========== +*/ +dlumpinfo_t *W_FindMiptex( wfile_t *wad, const char *name ) +{ + return W_FindLump( wad, name, TYP_MIPTEX ); +} + +/* +=========== +W_FindLmptex + +find specified lump for .lmp +=========== +*/ +dlumpinfo_t *W_FindLmptex( wfile_t *wad, const char *name ) +{ + return W_FindLump( wad, name, TYP_GFXPIC ); +} + +/* +=========== +W_SearchForFile + +search in wad for filename +=========== +*/ +void W_SearchForFile( wfile_t *wad, const char *pattern, stringlist_t *resultlist ) +{ + char wadpattern[256], wadname[256], temp2[256]; + const char *slash, *backslash, *colon, *separator; + char type = W_TypeFromExt( pattern ); + char wadfolder[256], temp[1024]; + bool anywadname = true; + int resultlistindex; + + if( !wad ) return; + + // quick reject by filetype + if( type == TYP_NONE ) return; + COM_ExtractFilePath( pattern, wadname ); + COM_FileBase( pattern, wadpattern ); + wadfolder[0] = '\0'; + + if( Q_strlen( wadname )) + { + COM_FileBase( wadname, wadname ); + Q_strncpy( wadfolder, wadname, sizeof( wadfolder )); + COM_DefaultExtension( wadname, ".wad" ); + anywadname = false; + } + + // make wadname from wad fullpath + COM_FileBase( wad->filename, temp2 ); + COM_DefaultExtension( temp2, ".wad" ); + + // quick reject by wadname + if( !anywadname && Q_stricmp( wadname, temp2 )) + return; + + // look through all the wad file elements + for( int i = 0; i < wad->numlumps; i++ ) + { + // if type not matching, we already have no chance ... + if( type != TYP_ANY && wad->lumps[i].type != type ) + continue; + + // build the lumpname with image suffix (if present) + Q_snprintf( temp, sizeof( temp ), "%s%s", wad->lumps[i].name, wad_hints[wad->lumps[i].img_type].ext ); + + while( temp[0] ) + { + if( matchpattern( temp, wadpattern, true )) + { + for( resultlistindex = 0; resultlistindex < resultlist->numstrings; resultlistindex++ ) + { + if( !Q_strcmp( resultlist->strings[resultlistindex], temp )) + break; + } + + if( resultlistindex == resultlist->numstrings ) + { + // build path: wadname/lumpname.ext + Q_snprintf( temp2, sizeof( temp2 ), "%s/%s.%s", wadfolder, temp, + W_ExtFromType( wad->lumps[i].type )); + stringlistappend( resultlist, temp2 ); + } + } + + // strip off one path element at a time until empty + // this way directories are added to the listing if they match the pattern + slash = Q_strrchr( temp, '/' ); + backslash = Q_strrchr( temp, '\\' ); + colon = Q_strrchr( temp, ':' ); + separator = temp; + if( separator < slash ) + separator = slash; + if( separator < backslash ) + separator = backslash; + if( separator < colon ) + separator = colon; + *((char *)separator) = 0; + } + } +} \ No newline at end of file diff --git a/utils/common/wfile.h b/utils/common/wfile.h new file mode 100644 index 0000000..56b555f --- /dev/null +++ b/utils/common/wfile.h @@ -0,0 +1,35 @@ +/* +wfile.h - simple version of game engine filesystem for tools +Copyright (C) 2018 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef WFILE_H +#define WFILE_H + +// wad intermediate struct +struct wfile_s +{ + char filename[256]; + int infotableofs; + int numlumps; + int mode; +#ifdef ALLOW_WADS_IN_PACKS + file_t *handle; +#else + int handle; +#endif + time_t filetime; + dlumpinfo_t *lumps; +}; + +#endif \ No newline at end of file diff --git a/utils/common/zone.cpp b/utils/common/zone.cpp new file mode 100644 index 0000000..b8942e8 --- /dev/null +++ b/utils/common/zone.cpp @@ -0,0 +1,176 @@ +/* +zone.cpp - simple memory manager with leak detector +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include +#include "cmdlib.h" +#include "threads.h" +#include "stringlib.h" +#include "mathlib.h" + +#define ZONE_ATTEMPT_CALLOC +//#define ZONE_DEBUG + +static int c_alloc[C_MAXSTAT] = { 0 }; +static size_t total_active, total_peakactive; + +typedef struct memhdr_s +{ + size_t size; +} memhdr_t; + +const char *c_stats[] = +{ + "Common", + "Temporary", + "Template", + "FileSystem", + "Winding", + "Brush Side", + "Bsp Brush", + "LeafNode", + "Surface", + "Bsp Tree", + "Portal", + "String", + "EntityPair", + "Patch", +}; + +// some platforms have a malloc that returns NULL but succeeds later +// (Windows growing its swapfile for example) +static void *attempt_calloc( size_t size ) +{ + uint attempts = 600; + + // one minute before completely failed + while( attempts-- ) + { + void *base; + + if(( base = (void *)calloc( size, 1 )) != NULL ) + return base; + // try for half a second or so + Sleep( 100 ); + } + return NULL; +} + +/* +============= +Mem_Alloc + +allocate mem +============= +*/ +void *Mem_Alloc( size_t size, unsigned int target ) +{ + memhdr_t *memhdr = NULL; + void *mem; + + if( size <= 0 ) return NULL; + +#ifdef ZONE_ATTEMPT_CALLOC + mem = attempt_calloc( sizeof( memhdr_t ) + size ); +#else + mem = calloc( sizeof( memhdr_t ) + size, 1 ); +#endif + if( mem == NULL ) + { + if( target == C_SAFEALLOC ) + return NULL; + COM_FatalError( "out of memory!\n" ); + } + + memhdr = (memhdr_t *)mem; + memhdr->size = size; +#ifdef ZONE_DEBUG + ThreadLock(); + total_active += size; + total_peakactive = Q_max( total_peakactive, total_active ); + c_alloc[target]++; + ThreadUnlock(); +#endif + return (void *)((byte *)mem + sizeof( memhdr_t )); +} + +void *Mem_Realloc( void *ptr, size_t size, unsigned int target ) +{ + memhdr_t *memhdr = NULL; + void *mem; + + if( size <= 0 ) return ptr; // no need to reallocate + + if( ptr ) + { + memhdr = (memhdr_t *)((byte *)ptr - sizeof( memhdr_t )); + if( size == memhdr->size ) return ptr; + } + + mem = Mem_Alloc( size, target ); + + if( ptr ) // first allocate? + { + size_t newsize = memhdr->size < size ? memhdr->size : size; // upper data can be trucnated! + memcpy( mem, ptr, newsize ); + Mem_Free( ptr, target ); // free unused old block + } + + return mem; +} + +void Mem_Free( void *ptr, unsigned int target ) +{ + memhdr_t *chunk; + + if( !ptr ) return; + + chunk = (memhdr_t *)((byte *)ptr - sizeof( memhdr_t )); +#ifdef ZONE_DEBUG + ThreadLock(); + total_active -= chunk->size; + c_alloc[target]--; + ThreadUnlock(); +#endif + free( chunk ); +} + +void Mem_Check( void ) +{ +#ifdef ZONE_DEBUG + MsgDev( D_INFO, "active memory %s, peak memory %s\n", Q_memprint( total_active ), Q_memprint( total_peakactive )); + for( int i = 0; i < C_MAXSTAT; i++ ) + { + if( c_alloc[i] ) MsgDev( D_REPORT, "%s memory allocations leaks count: %d\n", c_stats[i], c_alloc[i] ); + } +#endif +} + +void Mem_Peak( void ) +{ +#ifdef ZONE_DEBUG + MsgDev( D_INFO, "active memory %s, peak memory %s\n", Q_memprint( total_active ), Q_memprint( total_peakactive )); +#endif +} + +size_t Mem_Size( void *ptr ) +{ + memhdr_t *chunk; + + if( !ptr ) return 0; + + chunk = (memhdr_t *)((byte *)ptr - sizeof( memhdr_t )); + + return chunk->size; +} \ No newline at end of file diff --git a/utils/decal2tga/decal2tga.cpp b/utils/decal2tga/decal2tga.cpp new file mode 100644 index 0000000..b1bc632 --- /dev/null +++ b/utils/decal2tga/decal2tga.cpp @@ -0,0 +1,400 @@ +/* +decal2tga.cpp - convert Half-Life decals into TGA decals +Copyright (C) 2012 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct +{ + unsigned int width; + unsigned int height; + unsigned long table[256]; + unsigned char *pixels; + unsigned char *pixels32; // expanded + size_t size; +} image; + +typedef struct +{ + byte r, g, b, a; +} color32; + +wfile_t *source_wad = NULL; // input WAD3 file +int g_numdecals = 0; + +/* +================= +BoxFilter3x3 + +box filter 3x3 +================= +*/ +void BoxFilter3x3( byte *out, const byte *in, int w, int h, int x, int y ) +{ + int r = 0, g = 0, b = 0, a = 0; + int count = 0, acount = 0; + int i, j, u, v; + const byte *pixel; + + for( i = 0; i < 3; i++ ) + { + u = ( i - 1 ) + x; + + for( j = 0; j < 3; j++ ) + { + v = ( j - 1 ) + y; + + if( u >= 0 && u < w && v >= 0 && v < h ) + { + pixel = &in[( u + v * w ) * 4]; + + if( pixel[3] != 0 ) + { + r += pixel[0]; + g += pixel[1]; + b += pixel[2]; + a += pixel[3]; + acount++; + } + } + } + } + + if( acount == 0 ) + acount = 1; + + out[0] = r / acount; + out[1] = g / acount; + out[2] = b / acount; +// out[3] = (int)( SimpleSpline( ( a / 12.0f ) / 255.0f ) * 255 ); +} + +/* +============= +WriteTGAFile + +============= +*/ +bool WriteTGAFile( const char *filename, bool write_alpha ) +{ + size_t filesize; + const unsigned char *bufend, *in; + unsigned char *buffer, *out; + const char *comment = "Written by decal2tga tool\0"; + + if( write_alpha ) + filesize = image.width * image.height * 4 + 18 + strlen( comment ); + else filesize = image.width * image.height * 3 + 18 + strlen( comment ); + + buffer = (unsigned char *)malloc( filesize ); + memset( buffer, 0, 18 ); + + // prepare header + buffer[0] = strlen( comment ); // tga comment length + buffer[2] = 2; // uncompressed type + buffer[12] = (image.width >> 0) & 0xFF; + buffer[13] = (image.width >> 8) & 0xFF; + buffer[14] = (image.height >> 0) & 0xFF; + buffer[15] = (image.height >> 8) & 0xFF; + buffer[16] = write_alpha ? 32 : 24; + buffer[17] = write_alpha ? 8 : 0; // 8 bits of alpha + strncpy((char *)buffer + 18, comment, strlen( comment )); + out = buffer + 18 + strlen( comment ); + + // swap rgba to bgra and flip upside down + for( int y = image.height - 1; y >= 0; y-- ) + { + in = image.pixels32 + y * image.width * 4; + bufend = in + image.width * 4; + for( ; in < bufend; in += 4 ) + { + *out++ = in[2]; + *out++ = in[1]; + *out++ = in[0]; + if( write_alpha ) + *out++ = in[3]; + } + } + + // make sure what path is existing + for( char *ofs = (char *)(filename + 1); *ofs; ofs++ ) + { + if( *ofs == '/' || *ofs == '\\' ) + { + // create the directory + char save = *ofs; + *ofs = 0; + _mkdir( filename ); + *ofs = save; + } + } + + long handle = open( filename, O_WRONLY|O_BINARY|O_CREAT|O_TRUNC, 0666 ); + if( handle < 0 ) + { + MsgDev( D_ERROR, "couldn't write %s\n", filename ); + free( buffer ); + return false; + } + + size_t size = write( handle, buffer, filesize ); + close( handle ); + free( buffer ); + + if( size < 0 ) + { + MsgDev( D_ERROR, "couldn't write %s\n", filename ); + return false; + } + + return true; +} + +bool WriteBMPFile( const char *name, bool write_alpha ) +{ + long file; + BITMAPFILEHEADER bmfh; + BITMAPINFOHEADER bmih; + dword cbBmpBits; + byte *pb, *pbBmpBits; + dword biTrueWidth; + int pixel_size; + int i, x, y; + + if( write_alpha ) + pixel_size = 4; + else pixel_size = 3; + + file = open( name, O_WRONLY|O_BINARY|O_CREAT|O_TRUNC, 0666 ); + if( file < 0 ) return false; + + // NOTE: align transparency column will sucessfully removed + // after create sprite or lump image, it's just standard requiriments + biTrueWidth = ((image.width + 3) & ~3); + cbBmpBits = biTrueWidth * image.height * pixel_size; + + // Bogus file header check + bmfh.bfType = MAKEWORD( 'B', 'M' ); + bmfh.bfSize = sizeof( bmfh ) + sizeof( bmih ) + cbBmpBits; + bmfh.bfOffBits = sizeof( bmfh ) + sizeof( bmih ); + bmfh.bfReserved1 = bmfh.bfReserved2 = 0; + + // write header + write( file, &bmfh, sizeof( bmfh )); + + // size of structure + bmih.biSize = sizeof( bmih ); + bmih.biWidth = biTrueWidth; + bmih.biHeight = image.height; + bmih.biPlanes = 1; + bmih.biBitCount = pixel_size * 8; + bmih.biCompression = BI_RGB; + bmih.biSizeImage = cbBmpBits; + bmih.biXPelsPerMeter = 0; + bmih.biYPelsPerMeter = 0; + bmih.biClrUsed = 0; + bmih.biClrImportant = 0; + + // write info header + write( file, &bmih, sizeof( bmih )); + + pbBmpBits = (byte *)Mem_Alloc( cbBmpBits ); + + pb = image.pixels32; + + for( y = 0; y < bmih.biHeight; y++ ) + { + i = (bmih.biHeight - 1 - y ) * (bmih.biWidth); + + for( x = 0; x < image.width; x++ ) + { + // 24 bit + pbBmpBits[i*pixel_size+0] = pb[x*4+2]; + pbBmpBits[i*pixel_size+1] = pb[x*4+1]; + pbBmpBits[i*pixel_size+2] = pb[x*4+0]; + + if( pixel_size == 4 ) // write alpha channel + pbBmpBits[i*pixel_size+3] = pb[x*4+3]; + i++; + } + + pb += image.width * 4; + } + + // write bitmap bits (remainder of file) + write( file, pbBmpBits, cbBmpBits ); + close( file ); + Mem_Free( pbBmpBits ); + + return true; +} + +bool ConvertDecal( const dlumpinfo_t *lump ) +{ + byte *buffer = W_LoadLump( source_wad, lump->name, NULL, lump->type ); + mip_t *decal = (mip_t *)buffer; + + if( lump->disksize < sizeof( mip_t )) + { + MsgDev( D_ERROR, "ConvertDecal: file (%s) have invalid size\n", lump->name ); + Mem_Free( buffer, C_FILESYSTEM ); + return false; + } + + if( decal->width <= 0 || decal->width > 1024 || decal->height <= 0 || decal->height > 1024 ) + { + MsgDev( D_ERROR, "ConvertDecal: %s has invalid sizes %i x %i\n", lump->name, decal->width, decal->height ); + Mem_Free( buffer, C_FILESYSTEM ); + return false; + } + + image.width = decal->width; + image.height = decal->height; + image.size = image.width * image.height; + + image.pixels = (unsigned char *)(buffer + decal->offsets[0]); + unsigned char *pal = (byte *)(buffer + decal->offsets[0] + (((image.width * image.height) * 85)>>6)); + + short numcolors = *(short *)pal; + if( numcolors != 256 ) + { + MsgDev( D_ERROR, "ConvertDecal: %s has invalid palette size %i should be 256\n", lump->name, numcolors ); + Mem_Free( buffer, C_FILESYSTEM ); + return false; + } + + pal += sizeof( short ); // skip colorsize + bool gradient = true; + byte *in, pal32[4]; + char filename[64]; + byte base = 128; + + // clear blue color for 'transparent' decals + if( pal[255*3+0] == 0 && pal[255*3+1] == 0 && pal[255*3+2] == 255 ) + { + pal[255*3+0] = pal[255*3+1] = pal[255*3+2] = 0; + gradient = false; + } + + // convert 24-bit palette into 32-bit palette + for( int i = 0; i < 256; i++ ) + { + if( gradient ) + { + pal32[0] = pal[765]; + pal32[1] = pal[766]; + pal32[2] = pal[767]; + pal32[3] = i; + } + else + { + pal32[0] = pal[i*3+0]; + pal32[1] = pal[i*3+1]; + pal32[2] = pal[i*3+2]; + pal32[3] = 0xFF; + } + image.table[i] = *(unsigned long *)pal32; + } + + if( !gradient ) image.table[255] = 0; // last color is alpha + + // expand image into 32-bit buffer + image.pixels32 = in = (byte *)malloc( image.size * 4 ); + + for( i = 0; i < image.size; i++ ) + { + color32 *color = (color32 *)&image.table[image.pixels[i]]; + + if( gradient ) + { + float lerp = 1.0 - ((float)color->a / 255.0f); + + image.pixels32[i*4+0] = color->r + (base - color->r) * lerp; + image.pixels32[i*4+1] = color->g + (base - color->g) * lerp; + image.pixels32[i*4+2] = color->b + (base - color->b) * lerp; + image.pixels32[i*4+3] = color->a; + } + else + { + image.pixels32[i*4+0] = color->r; + image.pixels32[i*4+1] = color->g; + image.pixels32[i*4+2] = color->b; + image.pixels32[i*4+3] = color->a; + } + } +#if 1 + // apply boxfilter to 1-bit alpha + for( i = 0; !gradient && i < image.size; i++, in += 4 ) + { + if( in[0] == 0 && in[1] == 0 && in[2] == 0 && in[3] == 0 ) + BoxFilter3x3( in, image.pixels32, image.width, image.height, i % image.width, i / image.width ); + } +#endif + Q_snprintf( filename, sizeof( filename ), "decals/%s.tga", lump->name ); + bool bResult = WriteTGAFile( filename, true ); + Mem_Free( buffer, C_FILESYSTEM ); + free( image.pixels32 ); + + return bResult; +} + +bool ConvertDecals( const char *wadname ) +{ + if(( source_wad = W_Open( wadname, "rb" )) == NULL ) + return false; + + dlumpinfo_t *lump_p; + int i; + + for( i = 0, lump_p = source_wad->lumps; i < source_wad->numlumps; i++, lump_p++ ) + { + if( lump_p->type != TYP_MIPTEX || lump_p->name[0] != '{' ) + continue; // not a decal + + if( !ConvertDecal( lump_p )) + { + MsgDev( D_WARN, "can't convert decal %s\n", lump_p->name ); + continue; + } + + // decal sucessfully converted + g_numdecals++; + } + + W_Close( source_wad ); + return true; +} + +int main( int argc, char **argv ) +{ + if( !ConvertDecals( "decals.wad" )) + { + // let the user read error + system( "pause>nul" ); + return 1; + } + + Msg( "%i decals converted\n", g_numdecals ); + return 0; +} \ No newline at end of file diff --git a/utils/decal2tga/decal2tga.dsp b/utils/decal2tga/decal2tga.dsp new file mode 100644 index 0000000..652bc20 --- /dev/null +++ b/utils/decal2tga/decal2tga.dsp @@ -0,0 +1,154 @@ +# Microsoft Developer Studio Project File - Name="decal2tga" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=decal2tga - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "decal2tga.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "decal2tga.mak" CFG="decal2tga - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "decal2tga - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "decal2tga - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/SDKSrc/Tools/utils/decal2tga", IVGBAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "decal2tga - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir ".\Release" +# PROP BASE Intermediate_Dir ".\Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\..\temp\decal2tga\!release" +# PROP Intermediate_Dir "..\..\temp\decal2tga\!release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "..\common" /I "..\..\common" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /FD /c +# SUBTRACT CPP /Fr /YX +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 msvcrt.lib /nologo /subsystem:console /pdb:none /machine:I386 /nodefaultlib:"libcmt.lib" +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\decal2tga\!release +InputPath=\Paranoia2\src_main\temp\decal2tga\!release\decal2tga.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\decal2tga.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\decal2tga.exe "D:\Paranoia2\tools\decal2tga.exe" + +# End Custom Build + +!ELSEIF "$(CFG)" == "decal2tga - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir ".\Debug" +# PROP BASE Intermediate_Dir ".\Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\..\temp\decal2tga\!debug" +# PROP Intermediate_Dir "..\..\temp\decal2tga\!debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MDd /W3 /Gm /Gi /GX /ZI /Od /I "..\common" /I "..\..\common" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /FAs /FR /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:console /debug /machine:I386 +# ADD LINK32 msvcrtd.lib /nologo /subsystem:console /debug /machine:I386 /nodefaultlib:"libcd.lib" +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\decal2tga\!debug +InputPath=\Paranoia2\src_main\temp\decal2tga\!debug\decal2tga.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\decal2tga.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\decal2tga.exe "D:\Paranoia2\tools\decal2tga.exe" + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "decal2tga - Win32 Release" +# Name "decal2tga - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=..\common\cmdlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\conprint.cpp +# End Source File +# Begin Source File + +SOURCE=.\decal2tga.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\filesystem.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\stringlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\wadfile.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\zone.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=.\conprint.h +# End Source File +# Begin Source File + +SOURCE=..\..\common\wadfile.h +# End Source File +# Begin Source File + +SOURCE=..\common\wfile.h +# End Source File +# End Group +# End Target +# End Project diff --git a/utils/hlmv/ControlPanel.cpp b/utils/hlmv/ControlPanel.cpp new file mode 100644 index 0000000..c60e6c2 --- /dev/null +++ b/utils/hlmv/ControlPanel.cpp @@ -0,0 +1,1897 @@ +// +// Half-Life Model Viewer (c) 1999 by Mete Ciragan +// +// file: ControlPanel.cpp +// last modified: Oct 20 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +// version: 1.24 +// +// email: mete@swissquake.ch +// web: http://www.swissquake.ch/chumbalum-soft/ +// +#include "ControlPanel.h" +#include "ViewerSettings.h" +#include "StudioModel.h" +#include "GlWindow.h" +#include +#include +#include +#include +#include +#include "mdlviewer.h" +#include "stringlib.h" + +extern char g_appTitle[]; + +ControlPanel *g_ControlPanel = 0; +bool g_bStopPlaying = false; +bool g_bEndOfSequence = false; +static int g_nCurrFrame = 0; + + + +ControlPanel :: ControlPanel( mxWindow *parent ) : mxWindow( parent, 0, 0, 0, 0, "Control Panel", mxWindow::Normal ) +{ + // create tabcontrol with subdialog windows + tab = new mxTab (this, 0, 0, 0, 0, IDC_TAB); +#ifdef WIN32 + SetWindowLong ((HWND) tab->getHandle (), GWL_EXSTYLE, WS_EX_CLIENTEDGE); +#endif + mxWindow *wRender = new mxWindow (this, 0, 0, 0, 0); + tab->add (wRender, "Model Display"); + mxLabel *RenderLabel = new mxLabel (wRender, 5, 3, 120, 20, "Render Mode"); + cRenderMode = new mxChoice (wRender, 5, 17, 112, 22, IDC_RENDERMODE); + cRenderMode->add ("Wireframe"); + cRenderMode->add ("Flat Shaded"); + cRenderMode->add ("Smooth Shaded"); + cRenderMode->add ("Texture Shaded"); + cRenderMode->add ("BoneWeights"); + cRenderMode->select (3); + mxToolTip::add (cRenderMode, "Select Render Mode"); + lOpacityValue = new mxLabel (wRender, 5, 45, 100, 18, "Opacity: 100%"); + slTransparency = new mxSlider (wRender, 0, 62, 120, 18, IDC_TRANSPARENCY); + slTransparency->setValue (100); + mxToolTip::add (slTransparency, "Model Transparency"); + mxCheckBox *cbHitBoxes = new mxCheckBox (wRender, 140, 5, 120, 20, "Show Hit Boxes", IDC_HITBOXES); + mxCheckBox *cbBones = new mxCheckBox (wRender, 140, 25, 120, 20, "Show Bones", IDC_BONES); + cbAttachments = new mxCheckBox (wRender, 140, 45, 120, 20, "Show Attachments", IDC_ATTACHMENTS); + mxCheckBox *cbNormals = new mxCheckBox (wRender, 140, 65, 120, 20, "Show Normals", IDC_NORMALS); + + cbGround = new mxCheckBox (wRender, 260, 5, 130, 20, "Show Ground", IDC_GROUND); + cbGround->setChecked( g_viewerSettings.showGround ? true : false ); + + cbMirror = new mxCheckBox (wRender, 260, 25, 130, 20, "Mirror Model On Ground", IDC_MIRROR); + cbBackground = new mxCheckBox (wRender, 260, 45, 130, 20, "Show Background", IDC_BACKGROUND); + mxCheckBox *cbWireframe = new mxCheckBox (wRender, 260, 65, 130, 20, "Wireframe Overlay", IDC_WIREFRAME); + +#ifdef HAVE_SCALE + leMeshScale = new mxLineEdit (wRender, 430, 5, 50, 18, "1.0"); + mxToolTip::add (leMeshScale, "Mesh Scale"); + leBoneScale = new mxLineEdit (wRender, 430, 25, 50, 18, "1.0"); + mxToolTip::add (leBoneScale, "Bone Scale"); + mxButton *bMeshScale = new mxButton (wRender, 485, 5, 80, 18, "Sacle Meshes", 10001); + mxButton *bBoneScale = new mxButton (wRender, 485, 25, 80, 18, "Scale Bones", 10002); +#endif + lDrawnPolys = new mxLabel (wRender, 430, 65, 110, 18, "Drawn Polys: 0"); + + mxWindow *wBody = new mxWindow (this, 0, 0, 0, 0); + tab->add (wBody, "Body Parts"); + cBodypart = new mxChoice (wBody, 5, 5, 112, 22, IDC_BODYPART); + mxToolTip::add (cBodypart, "Choose a bodypart"); + BodyPartLabel = new mxLabel (wBody, 120, 8, 50, 20, "Body 0"); + cSubmodel = new mxChoice (wBody, 5, 30, 112, 22, IDC_SUBMODEL); + mxToolTip::add (cSubmodel, "Choose a submodel of current bodypart"); + mxLabel *SubModeLabel = new mxLabel (wBody, 120, 33, 55, 20, "Sub-model"); + lDrawnPolys2 = new mxLabel (wBody, 190, 58, 110, 18, "Drawn Polys: 0"); + lModelInfo1 = new mxLabel (wBody, 370, 12, 120, 100, ""); + lModelInfo2 = new mxLabel (wBody, 500, 12, 120, 100, ""); + cSkin = new mxChoice (wBody, 5, 55, 112, 22, IDC_SKINS); + mxToolTip::add (cSkin, "Choose a skin family"); + mxLabel *SkinLabel = new mxLabel (wBody, 120, 58, 30, 20, "Skin"); + + mxWindow *wTexture = new mxWindow (this, 0, 0, 0, 0); + tab->add (wTexture, "Textures"); + cTextures = new mxChoice (wTexture, 5, 18, 150, 22, IDC_TEXTURES); + mxToolTip::add (cTextures, "Choose a texture"); + + new mxButton (wTexture, 510, 5, 80, 18, "Import Texture", IDC_IMPORTTEXTURE); + new mxButton (wTexture, 510, 25, 80, 18, "Export Texture", IDC_EXPORTTEXTURE); + new mxButton (wTexture, 510, 45, 80, 18, "Export UV Map", IDC_EXPORT_UVMAP); + new mxButton (wTexture, 510, 65, 80, 18, "Change Name", IDC_SET_TEXTURE_NAME ); + lTexSize = new mxLabel (wTexture, 5, 3, 140, 14, "Texture"); + + cbChrome = new mxCheckBox (wTexture, 180, 3, 80, 22, "Chrome", IDC_CHROME); + cbAdditive = new mxCheckBox (wTexture, 180, 23, 80, 22, "Additive", IDC_ADDITIVE); + cbTransparent = new mxCheckBox (wTexture, 180, 43, 80, 22, "Transparent", IDC_TRANSPARENT); + + cbFullbright = new mxCheckBox (wTexture, 270, 3, 80, 22, "Fullbright", IDC_FULLBRIGHT); + cbFlatshade = new mxCheckBox (wTexture, 270, 23, 80, 22, "Flat Shade", IDC_FLATSHADE); + cbTwoSided = new mxCheckBox (wTexture, 270, 43, 80, 22, "Two Sided", IDC_TWO_SIDED); + cbSmooth = new mxCheckBox (wTexture, 180, 63, 80, 22, "Smooth TBN", IDC_SMOOTH_TBN); + cbSolidAlpha = new mxCheckBox (wTexture, 270, 63, 80, 22, "Alpha Solid", IDC_SOLID_ALPHA); + + cbShowUVMap = new mxCheckBox (wTexture, 400, 3, 100, 22, "Show UV Map", IDC_SHOW_UV_MAP); + cbOverlayUVMap = new mxCheckBox (wTexture, 400, 23, 100, 22, "Overlay UV Map", IDC_OVERLAY_UV_MAP); + cbAntiAliasLines = new mxCheckBox (wTexture, 400, 43, 100, 22, "Anti-Alias Lines", IDC_ANTI_ALIAS_LINES); + leTextureName = new mxLineEdit( wTexture, 400, 66, 100, 18, "", IDC_EDIT_TEXTURE_NAME ); + + mxToolTip::add (new mxSlider (wTexture, 0, 60, 160, 18, IDC_TEXTURESCALE), "Scale texture size"); + lTexScale = new mxLabel (wTexture, 5, 47, 140, 14, "Scale Texture View (1x)"); + + mxWindow *wSequence = new mxWindow (this, 0, 0, 0, 0); + tab->add (wSequence, "Sequences"); + + mxLabel *AnimSequence = new mxLabel (wSequence, 5, 3, 120, 18, "Animation Sequence"); + cSequence = new mxChoice (wSequence, 5, 18, 200, 22, IDC_SEQUENCE); + mxToolTip::add (cSequence, "Select Sequence"); + tbStop = new mxButton (wSequence, 5, 46, 60, 18, "Stop", IDC_STOP); + mxToolTip::add (tbStop, "Stop Playing"); + bPrevFrame = new mxButton (wSequence, 84, 46, 30, 18, "<<", IDC_PREVFRAME); + bPrevFrame->setEnabled (false); + mxToolTip::add (bPrevFrame, "Prev Frame"); + leFrame = new mxLineEdit (wSequence, 119, 46, 50, 18, "", IDC_FRAME); + leFrame->setEnabled (false); + mxToolTip::add (leFrame, "Set Frame"); + bNextFrame = new mxButton (wSequence, 174, 46, 30, 18, ">>", IDC_NEXTFRAME); + bNextFrame->setEnabled (false); + mxToolTip::add (bNextFrame, "Next Frame"); + + lSequenceInfo = new mxLabel (wSequence, 228, 12, 90, 100, ""); + + mxLabel *SpdLabel = new mxLabel (wSequence, 170, 70, 35, 18, "Speed"); + slSpeedScale = new mxSlider (wSequence, 0, 70, 165, 18, IDC_SPEEDSCALE); + slSpeedScale->setRange (0, 200); + slSpeedScale->setValue (40); + mxToolTip::add (slSpeedScale, "Speed Scale"); + + slBlender0 = new mxSlider( wSequence, 320, 12, 145, 18, IDC_BLENDER0 ); + slBlender1 = new mxSlider( wSequence, 320, 35, 145, 18, IDC_BLENDER1 ); + cbLoopAnim = new mxCheckBox( wSequence, 325, 60, 90, 22, "Loop Animation", IDC_LOOPANIM ); + new mxLabel( wSequence, 467, 12, 55, 18, "Blender 0" ); + new mxLabel( wSequence, 467, 35, 55, 18, "Blender 1" ); + slBlender0->setRange( 0, 255 ); + slBlender1->setRange( 0, 255 ); + + mxWindow *wMisc = new mxWindow (this, 0, 0, 0, 0); + tab->add (wMisc, "Misc"); + + mxLabel *FlagLabel = new mxLabel (wMisc, 5, 3, 120, 20, "Global model flags"); + cbFlagRocket = new mxCheckBox (wMisc, 10, 23, 95, 22, "Rocket Trail", IDC_STUDIO_ROCKET ); + mxToolTip::add (cbFlagRocket, "leave red-orange particle trail + dynamic light at model origin"); + cbFlagGrenade = new mxCheckBox (wMisc, 10, 43, 95, 22, "Grenade Smoke", IDC_STUDIO_GRENADE ); + mxToolTip::add (cbFlagGrenade, "leave gray-black particle trail"); + cbFlagGib = new mxCheckBox (wMisc, 10, 63, 95, 22, "Gib Blood", IDC_STUDIO_GIB ); + mxToolTip::add (cbFlagGib, "leave dark red particle trail that obey gravity"); + + cbFlagRotate = new mxCheckBox (wMisc, 110, 3, 95, 22, "Model Rotate", IDC_STUDIO_ROTATE ); + mxToolTip::add (cbFlagRotate, "model will auto-rotate by yaw axis. Useable for items (will be working only in Xash3D)"); + cbFlagTracer = new mxCheckBox (wMisc, 110, 23, 95, 22, "Green Trail", IDC_STUDIO_TRACER ); + mxToolTip::add (cbFlagTracer, "green split trail. e.g. monster_wizard from Quake"); + cbFlagZomgib = new mxCheckBox (wMisc, 110, 43, 95, 22, "Zombie Blood", IDC_STUDIO_ZOMGIB ); + mxToolTip::add (cbFlagZomgib, "small blood trail from zombie gibs"); + cbFlagTracer2 = new mxCheckBox (wMisc, 110, 63, 95, 22, "Orange Trail", IDC_STUDIO_TRACER2 ); + mxToolTip::add (cbFlagTracer2, "orange split trail + rotate"); + cbFlagTracer3 = new mxCheckBox (wMisc, 210, 3, 95, 22, "Purple Trail", IDC_STUDIO_TRACER3 ); + mxToolTip::add (cbFlagTracer3, "purple signle trail"); + cbFlagAmbientLight = new mxCheckBox (wMisc, 210, 23, 100, 22, "No shadelight", IDC_STUDIO_AMBIENT_LIGHT ); + mxToolTip::add (cbFlagAmbientLight, "ignore shadelight and adjust ambient light. Usefully for flying monsters"); + cbFlagTraceHitbox = new mxCheckBox (wMisc, 210, 43, 100, 22, "Hitbox Collision", IDC_STUDIO_TRACE_HITBOX ); + mxToolTip::add (cbFlagTraceHitbox, "allow model complex collision by hitboxes insted of bbox"); + cbFlagForceSkylight = new mxCheckBox (wMisc, 210, 63, 100, 22, "Force Skylight", IDC_STUDIO_FORCE_SKYLIGHT ); + mxToolTip::add (cbFlagForceSkylight, "always get light from environment settings even if sky is not visible or non-present"); + + mxLabel *RemapLabel = new mxLabel (wMisc, 325, 3, 120, 20, "Remap colors"); + slTopColor = new mxSlider( wMisc, 320, 20, 145, 18, IDC_TOPCOLOR ); + slBottomColor = new mxSlider( wMisc, 320, 40, 145, 18, IDC_BOTTOMCOLOR ); + new mxLabel( wMisc, 467, 20, 55, 18, "Top Color" ); + new mxLabel( wMisc, 467, 40, 75, 18, "Bottom Color" ); + slTopColor->setRange( 0, 255 ); + slBottomColor->setRange( 0, 255 ); + slTopColor->setValue( g_viewerSettings.topcolor ); + slBottomColor->setValue( g_viewerSettings.bottomcolor ); + + mxWindow *wControls = new mxWindow (this, 0, 0, 0, 0); + tab->add (wControls, "Controls"); + + for( int i = 0; i < NUM_POSEPARAMETERS; i++ ) + { + int x, y, y2, y3; + x = 0; + y = 2 + (i % 4) * 23; + y2 = 2 + (i % 4) * 23; + y3 = 3 + (i % 4) * 23; + + cPoseParameter[i] = new mxChoice (wControls, x + 3, y, 134, 18, IDC_POSEPARAMETER+i); + cPoseParameter[i]->setEnabled( false ); + + slPoseParameter[i] = new mxSlider (wControls, x + 186, y2, 140, 18, IDC_POSEPARAMETER_SCALE+i); + slPoseParameter[i]->setRange (0.0, 1.0, 1000); + mxToolTip::add (slPoseParameter[i], "Parameter"); + slPoseParameter[i]->setEnabled( false ); + + lePoseParameter[i] = new mxLineEdit ( wControls, x + 140, y3, 40, 18, ";-)", IDC_POSEPARAMETER_VALUE+i ); + lePoseParameter[i]->setEnabled( false ); + } + + cController = new mxChoice (wControls, 335, 5, 112, 22, IDC_CONTROLLER); + mxToolTip::add (cController, "Choose a bone controller"); + slController = new mxSlider (wControls, 330, 30, 117, 18, IDC_CONTROLLERVALUE); + slController->setRange (0, 45); + mxToolTip::add (slController, "Change current bone controller value"); + mxLabel *ControllerLabel = new mxLabel (wControls, 455, 8, 55, 20, "Controller"); + mxLabel *CtrlValueLabel = new mxLabel (wControls, 455, 33, 55, 20, "Value"); + + cbEnableIK = new mxCheckBox (wControls, 335, 55, 100, 22, "Run IK", IDC_ENABLE_IK); + cbEnableIK->setChecked( g_viewerSettings.enableIK ? true : false ); + + mxWindow *wEdit = new mxWindow (this, 0, 0, 0, 0); + tab->add (wEdit, "Editor"); + + mxLabel *EidtLabel1 = new mxLabel (wEdit, 5, 3, 120, 20, "Edit Type"); + cEditType = new mxChoice (wEdit, 5, 17, 112, 22, IDC_EDIT_TYPE); + mxToolTip::add (cEditType, "Select item to editing"); + + mxLabel *EidtLabel2 = new mxLabel (wEdit, 5, 42, 120, 20, "Edit Mode"); + cEditMode = new mxChoice (wEdit, 5, 58, 112, 22, IDC_EDIT_MODE); + mxToolTip::add (cEditMode, "Select editor mode (change current model or grab values to put them into QC source)"); + cEditMode->add ("QC Source"); + cEditMode->add ("Real Model"); + cEditMode->select (g_viewerSettings.editMode); + + mxLabel *EidtLabel3 = new mxLabel (wEdit, 125, 3, 120, 20, "Step Size"); + leEditStep = new mxLineEdit (wEdit, 125, 19, 50, 18, va( "%g", g_viewerSettings.editStep )); + mxToolTip::add (leEditStep, "Editor movement step size"); + cbEditSize = new mxCheckBox (wEdit, 125, 39, 50, 22, "size", IDC_EDIT_SIZE ); + mxToolTip::add (cbEditSize, "switch between editing the origin and size e.g. for hitboxes"); + + mxLabel *EidtLabel4 = new mxLabel (wEdit, 185, 3, 170, 20, "QC source code:"); + leEditString = new mxLineEdit (wEdit, 185, 19, 290, 18, "" ); + + if( g_viewerSettings.editMode == EDIT_SOURCE ) + leEditString->setEnabled( true ); + else leEditString->setEnabled( false ); + + tbMovePosX = new mxButton (wEdit, 185, 43, 55, 20, "Move +X", IDC_MOVE_PX ); + tbMoveNegX = new mxButton (wEdit, 185, 64, 55, 20, "Move -X", IDC_MOVE_NX ); + tbMovePosY = new mxButton (wEdit, 245, 43, 55, 20, "Move +Y", IDC_MOVE_PY ); + tbMoveNegY = new mxButton (wEdit, 245, 64, 55, 20, "Move -Y", IDC_MOVE_NY ); + tbMovePosZ = new mxButton (wEdit, 305, 43, 55, 20, "Move +Z", IDC_MOVE_PZ ); + tbMoveNegZ = new mxButton (wEdit, 305, 64, 55, 20, "Move -Z", IDC_MOVE_NZ ); + + tbConvertTexCoords = new mxButton (wEdit, 485, 19, 130, 20, "Convert Fixed TexCoords", IDC_CONVERT_TEXCOORDS ); + + g_ControlPanel = this; + + iSelectionToSequence = NULL; + iSequenceToSelection = NULL; +} + +ControlPanel::~ControlPanel () +{ + free( iSelectionToSequence ); + free( iSequenceToSelection ); +} + +int ControlPanel::getTableIndex() +{ + return tab->getSelectedIndex (); +} + + +int +ControlPanel::handleEvent (mxEvent *event) +{ + if (event->event == mxEvent::Size) + { + tab->setBounds (0, 0, event->width, event->height); + return 1; + } + + if ( event->event == mxEvent::KeyDown ) + { + if (event->action >= IDC_POSEPARAMETER_VALUE && event->action < IDC_POSEPARAMETER_VALUE + NUM_POSEPARAMETERS) + { + int index = event->action - IDC_POSEPARAMETER_VALUE; + int poseparam = cPoseParameter[index]->getSelectedIndex(); + + float value = atof( lePoseParameter[index]->getLabel() ); + setBlend( poseparam, value ); + slPoseParameter[index]->setValue( g_studioModel.GetPoseParameter( poseparam ) ); + return 1; + } + return 1; + } + + switch (event->action) + { + case IDC_TAB: + { + g_viewerSettings.showTexture = (tab->getSelectedIndex () == TAB_TEXTURES); + } + break; + + case IDC_RENDERMODE: + { + int index = cRenderMode->getSelectedIndex (); + if (index >= 0) + { + setRenderMode (index); + } + } + break; + + case IDC_TRANSPARENCY: + { + int value = slTransparency->getValue (); + g_viewerSettings.transparency = (float) value / 100.0f; + lOpacityValue->setLabel( "Opacity: %d%%", value ); + } + break; + + case IDC_GROUND: + setShowGround (((mxCheckBox *) event->widget)->isChecked ()); + break; + + case IDC_MIRROR: + setMirror (((mxCheckBox *) event->widget)->isChecked ()); + break; + + case IDC_BACKGROUND: + setShowBackground (((mxCheckBox *) event->widget)->isChecked ()); + break; + + case IDC_HITBOXES: + g_viewerSettings.showHitBoxes = ((mxCheckBox *) event->widget)->isChecked (); + break; + + case IDC_BONES: + g_viewerSettings.showBones = ((mxCheckBox *) event->widget)->isChecked (); + break; + + case IDC_ATTACHMENTS: + g_viewerSettings.showAttachments = ((mxCheckBox *) event->widget)->isChecked (); + break; + + case IDC_NORMALS: + g_viewerSettings.showNormals = ((mxCheckBox *) event->widget)->isChecked (); + break; + + case IDC_WIREFRAME: + g_viewerSettings.showWireframeOverlay = ((mxCheckBox *) event->widget)->isChecked (); + break; + + case IDC_ENABLE_IK: + g_viewerSettings.enableIK = ((mxCheckBox *) event->widget)->isChecked (); + break; + + case IDC_SEQUENCE: + { + int index = cSequence->getSelectedIndex (); + if (index >= 0) + { + index = iSelectionToSequence[index]; + setSequence (index); + } + } + break; + + case IDC_SPEEDSCALE: + { + int v = ((mxSlider *)event->widget)->getValue (); + g_viewerSettings.speedScale = (float) (v * 5) / 200.0f; + } + break; + + case IDC_BLENDER0: + { + int v = ((mxSlider *)event->widget)->getValue(); + g_studioModel.SetBlendValue( 0, v ); + } + break; + + case IDC_BLENDER1: + { + int v = ((mxSlider *)event->widget)->getValue(); + g_studioModel.SetBlendValue( 1, v ); + } + break; + + case IDC_LOOPANIM: + { + studiohdr_t *hdr = g_studioModel.getStudioHeader (); + if( hdr ) + { + mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *) ((byte *) hdr + hdr->seqindex) + g_viewerSettings.sequence; + if (cbLoopAnim->isChecked ()) + pseqdesc->flags |= STUDIO_LOOPING; + else + pseqdesc->flags &= ~STUDIO_LOOPING; + g_viewerSettings.numModelChanges++; + } + } + break; + + case IDC_STOP: + { + if( !g_bStopPlaying ) + { + tbStop->setLabel ("Play"); + g_bStopPlaying = true; + g_nCurrFrame = g_studioModel.SetFrame (-1); + leFrame->setLabel ("%d", g_nCurrFrame); + bPrevFrame->setEnabled (true); + leFrame->setEnabled (true); + bNextFrame->setEnabled (true); + } + else + { + tbStop->setLabel ("Stop"); + g_bStopPlaying = false; + if( g_bEndOfSequence ) + g_nCurrFrame = g_studioModel.SetFrame( 0 ); + bPrevFrame->setEnabled (false); + leFrame->setEnabled (false); + bNextFrame->setEnabled (false); + g_bEndOfSequence = false; + } + } + break; + + case IDC_PREVFRAME: + { + g_nCurrFrame = g_studioModel.SetFrame (g_nCurrFrame - 1); + leFrame->setLabel ("%d", g_nCurrFrame); + g_bEndOfSequence = false; + } + break; + + case IDC_FRAME: + { + g_nCurrFrame = atoi (leFrame->getLabel ()); + g_nCurrFrame = g_studioModel.SetFrame (g_nCurrFrame); + g_bEndOfSequence = false; + } + break; + + case IDC_NEXTFRAME: + { + g_nCurrFrame = g_studioModel.SetFrame (g_nCurrFrame + 1); + leFrame->setLabel ("%d", g_nCurrFrame); + g_bEndOfSequence = false; + } + break; + + case IDC_BODYPART: + { + int index = cBodypart->getSelectedIndex (); + if (index >= 0) + { + setBodypart (index); + } + } + break; + + case IDC_SUBMODEL: + { + int index = cSubmodel->getSelectedIndex (); + if (index >= 0) + { + int body = setSubmodel (index); + BodyPartLabel->setLabel( "Body %d", body ); + } + } + break; + + case IDC_CONTROLLER: + { + int index = cController->getSelectedIndex (); + if (index >= 0) + setBoneController (index); + } + break; + + case IDC_CONTROLLERVALUE: + { + int index = cController->getSelectedIndex (); + if (index >= 0) + setBoneControllerValue (index, (float) slController->getValue ()); + } + break; + + case IDC_SKINS: + { + int index = cSkin->getSelectedIndex (); + if (index >= 0) + { + g_studioModel.SetSkin (index); + g_viewerSettings.skin = index; + d_GlWindow->redraw (); + } + } + break; + + case IDC_TEXTURES: + { + int index = cTextures->getSelectedIndex (); + if (index >= 0) + { + g_viewerSettings.texture = index; + studiohdr_t *hdr = g_studioModel.getTextureHeader (); + if (hdr) + { + mstudiotexture_t *ptexture = (mstudiotexture_t *) ((byte *) hdr + hdr->textureindex) + index; + lTexSize->setLabel ("Texture (size: %d x %d)", ptexture->width, ptexture->height); + cbChrome->setChecked ((ptexture->flags & STUDIO_NF_CHROME) == STUDIO_NF_CHROME); + cbAdditive->setChecked ((ptexture->flags & STUDIO_NF_ADDITIVE) == STUDIO_NF_ADDITIVE); + cbTransparent->setChecked ((ptexture->flags & STUDIO_NF_MASKED) == STUDIO_NF_MASKED); + cbFullbright->setChecked ((ptexture->flags & STUDIO_NF_FULLBRIGHT) == STUDIO_NF_FULLBRIGHT); + cbFlatshade->setChecked ((ptexture->flags & STUDIO_NF_FLATSHADE) == STUDIO_NF_FLATSHADE); + cbTwoSided->setChecked ((ptexture->flags & STUDIO_NF_TWOSIDE) == STUDIO_NF_TWOSIDE); + cbSmooth->setChecked ((ptexture->flags & STUDIO_NF_SMOOTH) == STUDIO_NF_SMOOTH); + cbSolidAlpha->setChecked ((ptexture->flags & STUDIO_NF_ALPHASOLID) == STUDIO_NF_ALPHASOLID); + leTextureName->setLabel( ptexture->name ); + } + d_GlWindow->redraw (); + } + + cbShowUVMap->setChecked (g_viewerSettings.show_uv_map); + cbOverlayUVMap->setChecked (g_viewerSettings.overlay_uv_map); + cbAntiAliasLines->setChecked (g_viewerSettings.anti_alias_lines); + } + break; + + case IDC_CHROME: + { + studiohdr_t *hdr = g_studioModel.getTextureHeader (); + if (hdr) + { + mstudiotexture_t *ptexture = (mstudiotexture_t *) ((byte *) hdr + hdr->textureindex) + g_viewerSettings.texture; + if (cbChrome->isChecked ()) + { + ptexture->flags |= STUDIO_NF_CHROME|STUDIO_NF_FLATSHADE; + cbFlatshade->setChecked (true); + } + else + { + ptexture->flags &= ~STUDIO_NF_CHROME; + ptexture->flags &= ~STUDIO_NF_FLATSHADE; + cbFlatshade->setChecked (false); + } + g_viewerSettings.numModelChanges++; + } + } + break; + + case IDC_ADDITIVE: + { + studiohdr_t *hdr = g_studioModel.getTextureHeader (); + if (hdr) + { + mstudiotexture_t *ptexture = (mstudiotexture_t *) ((byte *) hdr + hdr->textureindex) + g_viewerSettings.texture; + if (cbAdditive->isChecked ()) + ptexture->flags |= STUDIO_NF_ADDITIVE; + else + ptexture->flags &= ~STUDIO_NF_ADDITIVE; + g_viewerSettings.numModelChanges++; + } + } + break; + + case IDC_TRANSPARENT: + { + studiohdr_t *hdr = g_studioModel.getTextureHeader (); + if (hdr) + { + mstudiotexture_t *ptexture = (mstudiotexture_t *) ((byte *) hdr + hdr->textureindex) + g_viewerSettings.texture; + if (cbTransparent->isChecked ()) + ptexture->flags |= STUDIO_NF_MASKED; + else + ptexture->flags &= ~STUDIO_NF_MASKED; + + // reload texture in case palette was changed + g_studioModel.UploadTexture (ptexture, (byte *) hdr + ptexture->index, (byte *) hdr + ptexture->index + ptexture->width * ptexture->height, TEXTURE_COUNT + g_viewerSettings.texture ); + g_viewerSettings.numModelChanges++; + } + } + break; + + case IDC_FULLBRIGHT: + { + studiohdr_t *hdr = g_studioModel.getTextureHeader (); + if (hdr) + { + mstudiotexture_t *ptexture = (mstudiotexture_t *) ((byte *) hdr + hdr->textureindex) + g_viewerSettings.texture; + if (cbFullbright->isChecked ()) + ptexture->flags |= STUDIO_NF_FULLBRIGHT; + else + ptexture->flags &= ~STUDIO_NF_FULLBRIGHT; + g_viewerSettings.numModelChanges++; + } + } + break; + + case IDC_FLATSHADE: + { + studiohdr_t *hdr = g_studioModel.getTextureHeader (); + if (hdr) + { + mstudiotexture_t *ptexture = (mstudiotexture_t *) ((byte *) hdr + hdr->textureindex) + g_viewerSettings.texture; + if (cbFlatshade->isChecked ()) + ptexture->flags |= STUDIO_NF_FLATSHADE; + else + ptexture->flags &= ~STUDIO_NF_FLATSHADE; + g_viewerSettings.numModelChanges++; + } + } + break; + + case IDC_TWO_SIDED: + { + studiohdr_t *hdr = g_studioModel.getTextureHeader (); + if (hdr) + { + mstudiotexture_t *ptexture = (mstudiotexture_t *) ((byte *) hdr + hdr->textureindex) + g_viewerSettings.texture; + if (cbTwoSided->isChecked ()) + ptexture->flags |= STUDIO_NF_TWOSIDE; + else + ptexture->flags &= ~STUDIO_NF_TWOSIDE; + g_viewerSettings.numModelChanges++; + } + } + break; + + case IDC_SMOOTH_TBN: + { + studiohdr_t *hdr = g_studioModel.getTextureHeader (); + if (hdr) + { + mstudiotexture_t *ptexture = (mstudiotexture_t *) ((byte *) hdr + hdr->textureindex) + g_viewerSettings.texture; + if (cbSmooth->isChecked ()) + ptexture->flags |= STUDIO_NF_SMOOTH; + else + ptexture->flags &= ~STUDIO_NF_SMOOTH; + g_viewerSettings.numModelChanges++; + } + } + break; + + case IDC_SOLID_ALPHA: + { + studiohdr_t *hdr = g_studioModel.getTextureHeader (); + if (hdr) + { + mstudiotexture_t *ptexture = (mstudiotexture_t *) ((byte *) hdr + hdr->textureindex) + g_viewerSettings.texture; + if (cbSolidAlpha->isChecked ()) + ptexture->flags |= STUDIO_NF_ALPHASOLID; + else + ptexture->flags &= ~STUDIO_NF_ALPHASOLID; + g_viewerSettings.numModelChanges++; + } + } + break; + + case IDC_SHOW_UV_MAP: + { + if (cbShowUVMap->isChecked ()) + g_viewerSettings.show_uv_map = true; + else g_viewerSettings.show_uv_map = false; + } + break; + + case IDC_OVERLAY_UV_MAP: + { + if (cbOverlayUVMap->isChecked ()) + g_viewerSettings.overlay_uv_map = true; + else g_viewerSettings.overlay_uv_map = false; + } + break; + + case IDC_ANTI_ALIAS_LINES: + { + if (cbAntiAliasLines->isChecked ()) + g_viewerSettings.anti_alias_lines = true; + else g_viewerSettings.anti_alias_lines = false; + } + break; + + case IDC_EXPORTTEXTURE: + { + char *ptr = (char *) mxGetSaveFileName (this, "", "*.bmp"); + if (!ptr) + break; + + char filename[256]; + char ext[16]; + + strcpy (filename, ptr); + strcpy (ext, mx_getextension (filename)); + if (mx_strcasecmp (ext, ".bmp")) + strcat (filename, ".bmp"); + + studiohdr_t *phdr = g_studioModel.getTextureHeader (); + if (phdr) + { + mxImage image; + mstudiotexture_t *ptexture = (mstudiotexture_t *) ((byte *) phdr + phdr->textureindex) + g_viewerSettings.texture; + image.width = ptexture->width; + image.height = ptexture->height; + image.bpp = 8; + image.data = (void *) ((byte *) phdr + ptexture->index); + image.palette = (void *) ((byte *) phdr + ptexture->width * ptexture->height + ptexture->index); + if (!mxBmpWrite (filename, &image)) + mxMessageBox (this, "Error writing .BMP texture.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + image.data = 0; + image.palette = 0; + } + } + break; + + case IDC_IMPORTTEXTURE: + { + char *ptr = (char *) mxGetOpenFileName (this, "", "*.bmp"); + if (!ptr) + break; + + char filename[256]; + char ext[16]; + + strcpy (filename, ptr); + strcpy (ext, mx_getextension (filename)); + if (mx_strcasecmp (ext, ".bmp")) + strcat (filename, ".bmp"); + + mxImage *image = mxBmpRead (filename); + if (!image) + { + mxMessageBox (this, "Error loading .BMP texture.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + return 1; + } + + if (!image->palette) + { + delete image; + mxMessageBox (this, "Error loading .BMP texture. Must be 8-bit!", g_appTitle, MX_MB_OK | MX_MB_ERROR); + return 1; + } + + studiohdr_t *phdr = g_studioModel.getTextureHeader (); + if (phdr) + { + mstudiotexture_t *ptexture = (mstudiotexture_t *) ((byte *) phdr + phdr->textureindex) + g_viewerSettings.texture; + if (image->width == ptexture->width && image->height == ptexture->height) + { + memcpy ((byte *) phdr + ptexture->index, image->data, image->width * image->height); + memcpy ((byte *) phdr + ptexture->index + image->width * image->height, image->palette, 768); + + g_studioModel.UploadTexture (ptexture, (byte *) phdr + ptexture->index, (byte *) phdr + ptexture->index + image->width * image->height, TEXTURE_COUNT + g_viewerSettings.texture ); + g_viewerSettings.numModelChanges++; + } + else + mxMessageBox (this, "Texture must be of same size.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + } + + delete image; + d_GlWindow->redraw (); + } + break; + + case IDC_EXPORT_UVMAP: + { + char *ptr = (char *) mxGetSaveFileName (this, "", "*.bmp"); + if (!ptr) + break; + + char ext[16]; + + strcpy (g_viewerSettings.uvmapPath, ptr); + strcpy (ext, mx_getextension (g_viewerSettings.uvmapPath)); + if (mx_strcasecmp (ext, ".bmp")) + strcat (g_viewerSettings.uvmapPath, ".bmp"); + g_viewerSettings.pending_export_uvmap = true; + } + break; + + case IDC_SET_TEXTURE_NAME: + { + studiohdr_t *phdr = g_studioModel.getTextureHeader (); + mstudiotexture_t *ptexture = (mstudiotexture_t *) ((byte *) phdr + phdr->textureindex) + g_viewerSettings.texture; + + // compare with original name + if( Q_strncmp( ptexture->name, leTextureName->getLabel(), 64 )) + { + Q_strncpy( ptexture->name, leTextureName->getLabel(), 64 ); + g_viewerSettings.numModelChanges++; + + // refresh choise + cTextures->removeAll (); + mstudiotexture_t *ptextures = (mstudiotexture_t *) ((byte *)phdr + phdr->textureindex); + for( int i = 0; i < phdr->numtextures; i++ ) + cTextures->add( ptextures[i].name ); + cTextures->select( g_viewerSettings.texture ); + } + } + break; + + case IDC_TEXTURESCALE: + { + g_viewerSettings.textureScale = 1.0f + (float) ((mxSlider *) event->widget)->getValue () * 4.0f / 100.0f; + lTexScale->setLabel( "Scale Texture View (%.fx)", g_viewerSettings.textureScale ); + d_GlWindow->redraw (); + } + break; + + case IDC_MISC: + { + slTopColor->setValue( g_viewerSettings.topcolor ); + slBottomColor->setValue( g_viewerSettings.bottomcolor ); + } + break; + + case IDC_STUDIO_ROCKET: + { + studiohdr_t *phdr = g_studioModel.getStudioHeader (); + if( phdr ) + { + if (cbFlagRocket->isChecked ()) + phdr->flags |= STUDIO_ROCKET; + else phdr->flags &= ~STUDIO_ROCKET; + g_viewerSettings.numModelChanges++; + } + } + + case IDC_STUDIO_GRENADE: + { + studiohdr_t *phdr = g_studioModel.getStudioHeader (); + if( phdr ) + { + if (cbFlagGrenade->isChecked ()) + phdr->flags |= STUDIO_GRENADE; + else phdr->flags &= ~STUDIO_GRENADE; + g_viewerSettings.numModelChanges++; + } + } + + case IDC_STUDIO_GIB: + { + studiohdr_t *phdr = g_studioModel.getStudioHeader (); + if( phdr ) + { + if (cbFlagGib->isChecked ()) + phdr->flags |= STUDIO_GIB; + else phdr->flags &= ~STUDIO_GIB; + g_viewerSettings.numModelChanges++; + } + } + + case IDC_STUDIO_ROTATE: + { + studiohdr_t *phdr = g_studioModel.getStudioHeader (); + if( phdr ) + { + if (cbFlagRotate->isChecked ()) + phdr->flags |= STUDIO_ROTATE; + else phdr->flags &= ~STUDIO_ROTATE; + g_viewerSettings.numModelChanges++; + } + } + + case IDC_STUDIO_TRACER: + { + studiohdr_t *phdr = g_studioModel.getStudioHeader (); + if( phdr ) + { + if (cbFlagTracer->isChecked ()) + phdr->flags |= STUDIO_TRACER; + else phdr->flags &= ~STUDIO_TRACER; + g_viewerSettings.numModelChanges++; + } + } + + case IDC_STUDIO_ZOMGIB: + { + studiohdr_t *phdr = g_studioModel.getStudioHeader (); + if( phdr ) + { + if (cbFlagZomgib->isChecked ()) + phdr->flags |= STUDIO_ZOMGIB; + else phdr->flags &= ~STUDIO_ZOMGIB; + g_viewerSettings.numModelChanges++; + } + } + + case IDC_STUDIO_TRACER2: + { + studiohdr_t *phdr = g_studioModel.getStudioHeader (); + if( phdr ) + { + if (cbFlagTracer2->isChecked ()) + phdr->flags |= STUDIO_TRACER2; + else phdr->flags &= ~STUDIO_TRACER2; + g_viewerSettings.numModelChanges++; + } + } + + case IDC_STUDIO_TRACER3: + { + studiohdr_t *phdr = g_studioModel.getStudioHeader (); + if( phdr ) + { + if (cbFlagTracer3->isChecked ()) + phdr->flags |= STUDIO_TRACER3; + else phdr->flags &= ~STUDIO_TRACER3; + g_viewerSettings.numModelChanges++; + } + } + + case IDC_STUDIO_AMBIENT_LIGHT: + { + studiohdr_t *phdr = g_studioModel.getStudioHeader (); + if( phdr ) + { + if (cbFlagAmbientLight->isChecked ()) + phdr->flags |= STUDIO_AMBIENT_LIGHT; + else phdr->flags &= ~STUDIO_AMBIENT_LIGHT; + g_viewerSettings.numModelChanges++; + } + } + + case IDC_STUDIO_TRACE_HITBOX: + { + studiohdr_t *phdr = g_studioModel.getStudioHeader (); + if( phdr ) + { + if (cbFlagTraceHitbox->isChecked ()) + phdr->flags |= STUDIO_TRACE_HITBOX; + else phdr->flags &= ~STUDIO_TRACE_HITBOX; + g_viewerSettings.numModelChanges++; + } + } + + case IDC_STUDIO_FORCE_SKYLIGHT: + { + studiohdr_t *phdr = g_studioModel.getStudioHeader (); + if( phdr ) + { + if (cbFlagForceSkylight->isChecked ()) + phdr->flags |= STUDIO_FORCE_SKYLIGHT; + else phdr->flags &= ~STUDIO_FORCE_SKYLIGHT; + g_viewerSettings.numModelChanges++; + } + } + + case IDC_TOPCOLOR: + { + int v = ((mxSlider *)event->widget)->getValue(); + g_studioModel.SetTopColor( v ); + } + break; + + case IDC_BOTTOMCOLOR: + { + int v = ((mxSlider *)event->widget)->getValue(); + g_studioModel.SetBottomColor( v ); + } + break; + + case IDC_EDIT_MODE: + { + int index = cEditMode->getSelectedIndex (); + if (index >= 0) + { + if( !g_studioModel.SetEditMode (index)) + { + // edit mode is cancelled + cEditMode->select (g_viewerSettings.editMode); + } + else + { + if( g_viewerSettings.editMode == EDIT_SOURCE ) + leEditString->setEnabled( true ); + else leEditString->setEnabled( false ); + leEditString->setLabel( g_studioModel.getQCcode( )); + } + } + } + break; + + case IDC_EDIT_TYPE: + { + int index = cEditType->getSelectedIndex (); + if (index >= 0) + { + bool editSize = g_studioModel.SetEditType (index); + leEditString->setLabel( g_studioModel.getQCcode( )); + + if( !editSize && g_viewerSettings.editSize ) + { + cbEditSize->setChecked( false ); + toggleMoveSize( false ); + } + cbEditSize->setEnabled( editSize ); + } + } + break; + case IDC_MOVE_PX: + case IDC_MOVE_NX: + case IDC_MOVE_PY: + case IDC_MOVE_NY: + case IDC_MOVE_PZ: + case IDC_MOVE_NZ: + { + g_viewerSettings.editStep = (float)atof (leEditStep->getLabel ()); + g_studioModel.editPosition( g_viewerSettings.editStep, event->action ); + leEditString->setLabel( g_studioModel.getQCcode( )); + } + break; + + case IDC_CONVERT_TEXCOORDS: + { + char str[1024]; + + Q_strcpy( str, "there is no reliable methods to autodetect changes between half-float and old fixed point.\nIf model will seems to be broken after proceed don't save her!\nIf you not sure what it is should doing, just press 'No' and forget about this tool" ); + if( mxMessageBox( g_GlWindow, str, g_appTitle, MX_MB_YESNO | MX_MB_QUESTION | MX_MB_WARNING ) == 0 ) + g_studioModel.ConvertTexCoords(); + } + break; + + case IDC_EDIT_SIZE: + toggleMoveSize( ((mxCheckBox *) event->widget)->isChecked ()); + break; +#ifdef HAVE_SCALE + case 10001: + { + float scale = (float) atof (leMeshScale->getLabel ()); + if (scale > 0.0f) + { + g_studioModel.scaleMeshes (scale); + } + } + break; + + case 10002: + { + float scale = (float) atof (leBoneScale->getLabel ()); + if (scale > 0.0f) + { + g_studioModel.scaleBones (scale); + } + } + break; +#endif + default: + { + if (event->action >= IDC_POSEPARAMETER && event->action < IDC_POSEPARAMETER + NUM_POSEPARAMETERS) + { + int index = event->action - IDC_POSEPARAMETER; + int poseparam = cPoseParameter[index]->getSelectedIndex(); + + float flMin, flMax; + if (g_studioModel.GetPoseParameterRange( poseparam, &flMin, &flMax )) + { + slPoseParameter[index]->setRange( flMin, flMax, 1000 ); + slPoseParameter[index]->setValue( g_studioModel.GetPoseParameter( poseparam ) ); + lePoseParameter[index]->setLabel( "%.1f", g_studioModel.GetPoseParameter( poseparam ) ); + } + } + else if (event->action >= IDC_POSEPARAMETER_SCALE && event->action < IDC_POSEPARAMETER_SCALE + NUM_POSEPARAMETERS) + { + int index = event->action - IDC_POSEPARAMETER_SCALE; + int poseparam = cPoseParameter[index]->getSelectedIndex(); + + setBlend( poseparam, ((mxSlider *) event->widget)->getValue() ); + lePoseParameter[index]->setLabel( "%.1f", ((mxSlider *) event->widget)->getValue() ); + + // move also sliders with same poseparams + for( int i = 0; i < NUM_POSEPARAMETERS; i++ ) + { + if( i == index ) continue; + + if( cPoseParameter[i]->getSelectedIndex() == poseparam ) + { + setBlend( poseparam, ((mxSlider *) event->widget)->getValue() ); + slPoseParameter[i]->setValue( g_studioModel.GetPoseParameter( poseparam ) ); + } + } + } + } + break; + } + + return 1; +} + +void ControlPanel::toggleMoveSize ( bool size ) +{ + if( size ) + { + tbMovePosX->setLabel ("Size +X"); + tbMoveNegX->setLabel ("Size -X"); + tbMovePosY->setLabel ("Size +Y"); + tbMoveNegY->setLabel ("Size -Y"); + tbMovePosZ->setLabel ("Size +Z"); + tbMoveNegZ->setLabel ("Size -Z"); + g_viewerSettings.editSize = true; + } + else + { + tbMovePosX->setLabel ("Move +X"); + tbMoveNegX->setLabel ("Move -X"); + tbMovePosY->setLabel ("Move +Y"); + tbMoveNegY->setLabel ("Move -Y"); + tbMovePosZ->setLabel ("Move +Z"); + tbMoveNegZ->setLabel ("Move -Z"); + g_viewerSettings.editSize = false; + } +} + +void ControlPanel::dumpModelInfo () +{ +#if 0 + studiohdr_t *hdr = g_studioModel.getStudioHeader (); + if (hdr) + { + DeleteFile ("midump.txt"); + FILE *file = fopen ("midump.txt", "wt"); + if (file) + { + byte *phdr = (byte *) hdr; + int i; + + fprintf (file, "id: %c%c%c%c\n", phdr[0], phdr[1], phdr[2], phdr[3]); + fprintf (file, "version: %d\n", hdr->version); + fprintf (file, "name: \"%s\"\n", hdr->name); + fprintf (file, "length: %d\n\n", hdr->length); + + fprintf (file, "eyeposition: %f %f %f\n", hdr->eyeposition[0], hdr->eyeposition[1], hdr->eyeposition[2]); + fprintf (file, "min: %f %f %f\n", hdr->min[0], hdr->min[1], hdr->min[2]); + fprintf (file, "max: %f %f %f\n", hdr->max[0], hdr->max[1], hdr->max[2]); + fprintf (file, "bbmin: %f %f %f\n", hdr->bbmin[0], hdr->bbmin[1], hdr->bbmin[2]); + fprintf (file, "bbmax: %f %f %f\n", hdr->bbmax[0], hdr->bbmax[1], hdr->bbmax[2]); + + fprintf (file, "flags: %d\n\n", hdr->flags); + + fprintf (file, "numbones: %d\n", hdr->numbones); + for (i = 0; i < hdr->numbones; i++) + { + mstudiobone_t *pbones = (mstudiobone_t *) (phdr + hdr->boneindex); + fprintf (file, "\nbone %d.name: \"%s\"\n", i + 1, pbones[i].name); + fprintf (file, "bone %d.parent: %d\n", i + 1, pbones[i].parent); + fprintf (file, "bone %d.flags: %d\n", i + 1, pbones[i].flags); + fprintf (file, "bone %d.bonecontroller: %d %d %d %d %d %d\n", i + 1, pbones[i].bonecontroller[0], pbones[i].bonecontroller[1], pbones[i].bonecontroller[2], pbones[i].bonecontroller[3], pbones[i].bonecontroller[4], pbones[i].bonecontroller[5]); + fprintf (file, "bone %d.value: %f %f %f %f %f %f\n", i + 1, pbones[i].value[0], pbones[i].value[1], pbones[i].value[2], pbones[i].value[3], pbones[i].value[4], pbones[i].value[5]); + fprintf (file, "bone %d.scale: %f %f %f %f %f %f\n", i + 1, pbones[i].scale[0], pbones[i].scale[1], pbones[i].scale[2], pbones[i].scale[3], pbones[i].scale[4], pbones[i].scale[5]); + } + + fprintf (file, "\nnumbonecontrollers: %d\n", hdr->numbonecontrollers); + for (i = 0; i < hdr->numbonecontrollers; i++) + { + mstudiobonecontroller_t *pbonecontrollers = (mstudiobonecontroller_t *) (phdr + hdr->bonecontrollerindex); + fprintf (file, "\nbonecontroller %d.bone: %d\n", i + 1, pbonecontrollers[i].bone); + fprintf (file, "bonecontroller %d.type: %d\n", i + 1, pbonecontrollers[i].type); + fprintf (file, "bonecontroller %d.start: %f\n", i + 1, pbonecontrollers[i].start); + fprintf (file, "bonecontroller %d.end: %f\n", i + 1, pbonecontrollers[i].end); + fprintf (file, "bonecontroller %d.rest: %d\n", i + 1, pbonecontrollers[i].rest); + fprintf (file, "bonecontroller %d.index: %d\n", i + 1, pbonecontrollers[i].index); + } + + fprintf (file, "\nnumhitboxes: %d\n", hdr->numhitboxes); + for (i = 0; i < hdr->numhitboxes; i++) + { + mstudiobbox_t *pbboxes = (mstudiobbox_t *) (phdr + hdr->hitboxindex); + fprintf (file, "\nhitbox %d.bone: %d\n", i + 1, pbboxes[i].bone); + fprintf (file, "hitbox %d.group: %d\n", i + 1, pbboxes[i].group); + fprintf (file, "hitbox %d.bbmin: %f %f %f\n", i + 1, pbboxes[i].bbmin[0], pbboxes[i].bbmin[1], pbboxes[i].bbmin[2]); + fprintf (file, "hitbox %d.bbmax: %f %f %f\n", i + 1, pbboxes[i].bbmax[0], pbboxes[i].bbmax[1], pbboxes[i].bbmax[2]); + } + + fprintf (file, "\nnumseq: %d\n", hdr->numseq); + for (i = 0; i < hdr->numseq; i++) + { + mstudioseqdesc_t *pseqdescs = (mstudioseqdesc_t *) (phdr + hdr->seqindex); + fprintf (file, "\nseqdesc %d.label: \"%s\"\n", i + 1, pseqdescs[i].label); + fprintf (file, "seqdesc %d.fps: %f\n", i + 1, pseqdescs[i].fps); + fprintf (file, "seqdesc %d.flags: %d\n", i + 1, pseqdescs[i].flags); + fprintf (file, "<...>\n"); + } +/* + fprintf (file, "\nnumseqgroups: %d\n", hdr->numseqgroups); + for (i = 0; i < hdr->numseqgroups; i++) + { + mstudioseqgroup_t *pseqgroups = (mstudioseqgroup_t *) (phdr + hdr->seqgroupindex); + fprintf (file, "\nseqgroup %d.label: \"%s\"\n", i + 1, pseqgroups[i].label); + fprintf (file, "\nseqgroup %d.namel: \"%s\"\n", i + 1, pseqgroups[i].name); + fprintf (file, "\nseqgroup %d.data: %d\n", i + 1, pseqgroups[i].data); + } +*/ + hdr = g_studioModel.getTextureHeader (); + fprintf (file, "\nnumtextures: %d\n", hdr->numtextures); + fprintf (file, "textureindex: %d\n", hdr->textureindex); + fprintf (file, "texturedataindex: %d\n", hdr->texturedataindex); + for (i = 0; i < hdr->numtextures; i++) + { + mstudiotexture_t *ptextures = (mstudiotexture_t *) ((byte *) hdr + hdr->textureindex); + fprintf (file, "\ntexture %d.name: \"%s\"\n", i + 1, ptextures[i].name); + fprintf (file, "texture %d.flags: %d\n", i + 1, ptextures[i].flags); + fprintf (file, "texture %d.width: %d\n", i + 1, ptextures[i].width); + fprintf (file, "texture %d.height: %d\n", i + 1, ptextures[i].height); + fprintf (file, "texture %d.index: %d\n", i + 1, ptextures[i].index); + } + + hdr = g_studioModel.getStudioHeader (); + fprintf (file, "\nnumskinref: %d\n", hdr->numskinref); + fprintf (file, "numskinfamilies: %d\n", hdr->numskinfamilies); + + fprintf (file, "\nnumbodyparts: %d\n", hdr->numbodyparts); + for (i = 0; i < hdr->numbodyparts; i++) + { + mstudiobodyparts_t *pbodyparts = (mstudiobodyparts_t *) ((byte *) hdr + hdr->bodypartindex); + fprintf (file, "\nbodypart %d.name: \"%s\"\n", i + 1, pbodyparts[i].name); + fprintf (file, "bodypart %d.nummodels: %d\n", i + 1, pbodyparts[i].nummodels); + fprintf (file, "bodypart %d.base: %d\n", i + 1, pbodyparts[i].base); + fprintf (file, "bodypart %d.modelindex: %d\n", i + 1, pbodyparts[i].modelindex); + } + + fprintf (file, "\nnumattachments: %d\n", hdr->numattachments); + for (i = 0; i < hdr->numattachments; i++) + { + mstudioattachment_t *pattachments = (mstudioattachment_t *) ((byte *) hdr + hdr->attachmentindex); + fprintf (file, "attachment %d.name: \"%s\"\n", i + 1, pattachments[i].name); + } + + fclose (file); + + ShellExecute ((HWND) getHandle (), "open", "midump.txt", 0, 0, SW_SHOW); + } + } +#endif +} + + + +int ControlPanel::loadModel (const char *filename, bool centering) +{ + g_studioModel.FreeModel (); + if (g_studioModel.LoadModel ((char *) filename)) + { + if (g_studioModel.PostLoadModel ((char *) filename)) + { + char str[256], basename[64], basepath[256]; + + initSequences (); + initBodyparts (); + initBoneControllers (); + initSkins (); + initTextures (); + if( centering ) + centerView (false); + initEditModes (); + setModelFlags (); + strcpy (g_viewerSettings.modelFile, filename); + Q_strncpy( g_viewerSettings.modelPath, filename, sizeof( g_viewerSettings.modelPath )); + setModelInfo (); + g_viewerSettings.sequence = 0; + g_viewerSettings.speedScale = 1.0f; + slSpeedScale->setValue (40); + int i; + for (i = 0; i < 32; i++) + g_viewerSettings.submodels[i] = 0; + + mx_setcwd (mx_getpath (filename)); + setSequence( 0 ); + + COM_FileBase( filename, basename ); + Q_snprintf( str, sizeof( str ), "%s - %s.mdl", g_appTitle, basename ); + g_MDLViewer->setLabel( str ); + + COM_ExtractFilePath( filename, basepath ); +// Sys_InitLog( va( "%s/%s.log", basepath, basename )); + ListDirectory(); + + return 1; + } + else + { + Q_strncpy( g_viewerSettings.modelPath, filename, sizeof( g_viewerSettings.modelPath )); + mxMessageBox (this, "Error post-loading model.", g_appTitle, MX_MB_ERROR | MX_MB_OK); + } + } + else + { +// mxMessageBox (this, "Error loading model.", g_appTitle, MX_MB_ERROR | MX_MB_OK); + } + + return 0; +} + +void ControlPanel :: resetPlayingSequence( void ) +{ + static char str[128]; + + if( !g_bStopPlaying ) + { + tbStop->setLabel ("Play"); + g_bStopPlaying = true; + g_nCurrFrame = g_studioModel.SetFrame( -1 ); + sprintf (str, "%d", g_nCurrFrame); + leFrame->setLabel (str); + bPrevFrame->setEnabled (true); + leFrame->setEnabled (true); + bNextFrame->setEnabled (true); + g_bEndOfSequence = true; + } +} + +void ControlPanel :: setPlaySequence( void ) +{ + static char str[128]; + + if( g_bStopPlaying ) + { + tbStop->setLabel ("Stop"); + g_bStopPlaying = false; + g_nCurrFrame = g_studioModel.SetFrame( 0 ); + sprintf (str, "%d", g_nCurrFrame); + leFrame->setLabel (str); + bPrevFrame->setEnabled (false); + leFrame->setEnabled (false); + bNextFrame->setEnabled (false); + g_bEndOfSequence = false; + } +} + +void ControlPanel::setRenderMode( int mode ) +{ + g_viewerSettings.renderMode = mode; + d_GlWindow->redraw (); +} + +void +ControlPanel::updatePoseParameters( ) +{ + for (int i = 0; i < NUM_POSEPARAMETERS; i++) + { + if (slPoseParameter[i]->isEnabled()) + { + int j = cPoseParameter[i]->getSelectedIndex(); + float value = g_studioModel.GetPoseParameter( j ); + + float temp = atof( lePoseParameter[i]->getLabel( ) ); + + if (fabs( temp - value ) > 0.1) + { + slPoseParameter[i]->setValue( value ); + lePoseParameter[i]->setLabel( "%.1f", value ); + } + } + } +} + +void +ControlPanel::setShowGround (bool b) +{ + g_viewerSettings.showGround = b; + cbGround->setChecked (b); + if (!b) + { + cbMirror->setChecked (b); + g_viewerSettings.mirror = b; + } +} + + + +void +ControlPanel::setMirror (bool b) +{ + g_viewerSettings.useStencil = b; + g_viewerSettings.mirror = b; + cbMirror->setChecked (b); + if (b) + { + cbGround->setChecked (b); + g_viewerSettings.showGround = b; + } +} + + + +void +ControlPanel::setShowBackground (bool b) +{ + g_viewerSettings.showBackground = b; + cbBackground->setChecked (b); +} + + + +void +ControlPanel::initSequences () +{ + studiohdr_t *hdr = g_studioModel.getStudioHeader (); + if (hdr) + { + mstudioseqdesc_t *pseqdescs = (mstudioseqdesc_t *) ((byte *) hdr + hdr->seqindex); + + if( iSelectionToSequence ) free( iSelectionToSequence ); + iSelectionToSequence = (int *)malloc( hdr->numseq * sizeof( int )); + + if( iSequenceToSelection ) free( iSequenceToSelection ); + iSequenceToSelection = (int*)malloc( hdr->numseq * sizeof( int )); + + cSequence->removeAll (); + int k = 0; + + for (int i = 0; i < hdr->numseq; i++) + { + if( !FBitSet( pseqdescs[i].flags, STUDIO_HIDDEN|STUDIO_AUTOPLAY )) + { + iSelectionToSequence[k] = i; + iSequenceToSelection[i] = k; + cSequence->add( pseqdescs[i].label ); + k++; + } + else + { + // previous valid selection + iSequenceToSelection[i] = (k > 0) ? (k - 1) : 0; + } + } + + cSequence->select (0); + } + + float flMin, flMax; + for (int i = 0; i < NUM_POSEPARAMETERS; i++) + { + if (g_studioModel.GetPoseParameterRange( i, &flMin, &flMax )) + { + cPoseParameter[i]->removeAll(); + for (int j = 0; j < g_boneSetup.CountPoseParameters(); j++) + { + cPoseParameter[i]->add( g_boneSetup.pPoseParameter(j)->name ); + } + cPoseParameter[i]->select( i ); + cPoseParameter[i]->setEnabled( true ); + + slPoseParameter[i]->setEnabled( true ); + slPoseParameter[i]->setRange( flMin, flMax, 1000 ); + mxToolTip::add (slPoseParameter[i], g_boneSetup.pPoseParameter(i)->name ); + lePoseParameter[i]->setLabel( "%.1f", 0.0 ); + lePoseParameter[i]->setEnabled( true ); + } + else + { + cPoseParameter[i]->setEnabled( false ); + slPoseParameter[i]->setEnabled( false ); + lePoseParameter[i]->setEnabled( false ); + } + slPoseParameter[i]->setValue( 0.0 ); + setBlend( i, 0.0 ); + } + + if ( hdr ) + { + for (int i = 0; i < g_boneSetup.CountPoseParameters(); i++) + { + setBlend( i, 0.0 ); + } + } +} + +void ControlPanel :: setSequence( int index ) +{ + cSequence->select( iSequenceToSelection[index] ); + g_studioModel.SetSequence( index ); + g_viewerSettings.sequence = index; + + int numSequenceBlends = g_studioModel.getNumBlendings(); + + if( g_studioModel.hasLocalBlending( )) + { + // reset blending values and move sliders into 'default' positions + slBlender0->setValue( g_studioModel.SetBlending( 0, 0.0 )); + slBlender1->setValue( g_studioModel.SetBlending( 1, 0.0 )); + } + else numSequenceBlends = 0; + + // counter strike blending + if( numSequenceBlends == 4 || numSequenceBlends == 9 ) + { + slBlender0->setEnabled( true ); + slBlender1->setEnabled( true ); + } + else if( numSequenceBlends > 1 ) + { + slBlender0->setEnabled( true ); + slBlender1->setEnabled( false ); + } + else + { + slBlender0->setEnabled( false ); + slBlender1->setEnabled( false ); + } + + // update animation looping + studiohdr_t *hdr = g_studioModel.getStudioHeader (); + if( hdr ) + { + mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *) ((byte *) hdr + hdr->seqindex) + g_viewerSettings.sequence; + cbLoopAnim->setChecked( pseqdesc->flags & STUDIO_LOOPING ); + } + + setSequenceInfo(); + setPlaySequence(); +} + +void ControlPanel::addEditType ( const char *name, int type, int id ) +{ + if( g_studioModel.AddEditField( type, id )) + cEditType->add (name); +} + +void ControlPanel::initEditModes( void ) +{ + studiohdr_t *hdr = g_studioModel.getStudioHeader (); + if (hdr) + { + char str[256]; + int i; + + cEditType->removeAll (); + addEditType ("Model origin", TYPE_ORIGIN ); + addEditType ("Bound Box", TYPE_BBOX ); + addEditType ("Clip Box", TYPE_CBOX ); + addEditType ("Eye Position", TYPE_EYEPOSITION); + + for( i = 0; i < hdr->numattachments; i++ ) + { + mstudioattachment_t *pattachment = (mstudioattachment_t *)((byte *)hdr + hdr->attachmentindex) + i; + + // local attachments contain the matrix and can't be edited directly + if( FBitSet( pattachment->flags, STUDIO_ATTACHMENT_LOCAL )) + continue; + + if( Q_strlen( pattachment->name ) && !Q_isdigit( pattachment->name )) + sprintf( str, "%s", pattachment->name ); + else sprintf( str, "Attachment %i", i + 1 ); + addEditType (str, TYPE_ATTACHMENT, i ); + } + + for( i = 0; i < hdr->numhitboxes; i++ ) + { + sprintf( str, "Hitbox %i", i + 1 ); + addEditType (str, TYPE_HITBOX, i ); + } + + g_studioModel.SetEditType (0); + cEditType->select (0); + + leEditString->setLabel( g_studioModel.getQCcode( )); + cbEditSize->setEnabled( false ); + toggleMoveSize( false ); + } +} + +void ControlPanel::initBodyparts () +{ + studiohdr_t *hdr = g_studioModel.getStudioHeader (); + if (hdr) + { + int i; + mstudiobodyparts_t *pbodyparts = (mstudiobodyparts_t *) ((byte *) hdr + hdr->bodypartindex); + + cBodypart->removeAll (); + if (hdr->numbodyparts > 0) + { + for (i = 0; i < hdr->numbodyparts; i++) + cBodypart->add (pbodyparts[i].name); + + cBodypart->select (0); + + cSubmodel->removeAll (); + for (i = 0; i < pbodyparts[0].nummodels; i++) + { + char str[64]; + sprintf (str, "Submodel %d", i + 1); + cSubmodel->add (str); + } + + cSubmodel->select (0); + setSequenceInfo(); + } + } +} + + + +void +ControlPanel::setBodypart (int index) +{ + studiohdr_t *hdr = g_studioModel.getStudioHeader (); + if (hdr) + { + //cBodypart->setEn + cBodypart->select (index); + if (index < hdr->numbodyparts) + { + mstudiobodyparts_t *pbodyparts = (mstudiobodyparts_t *) ((byte *) hdr + hdr->bodypartindex); + cSubmodel->removeAll (); + + for (int i = 0; i < pbodyparts[index].nummodels; i++) + { + char str[64]; + sprintf (str, "Submodel %d", i + 1); + cSubmodel->add (str); + } + cSubmodel->select (0); + //g_studioModel.SetBodygroup (index, 0); + } + } +} + + + +int ControlPanel::setSubmodel (int index) +{ + g_viewerSettings.submodels[cBodypart->getSelectedIndex ()] = index; + return g_studioModel.SetBodygroup (cBodypart->getSelectedIndex (), index); +} + +void ControlPanel :: initBoneControllers( void ) +{ + studiohdr_t *hdr = g_studioModel.getStudioHeader (); + if (hdr) + { + int i, controllerused[MAX_BONECTRLS]; + int count = 0; + + cController->setEnabled (hdr->numbonecontrollers > 0); + slController->setEnabled (hdr->numbonecontrollers > 0); + cController->removeAll (); + + memset( controllerindex, 0, sizeof( controllerindex )); + memset( controllervalues, 0, sizeof( controllervalues )); + memset( controllerused, 0, sizeof( controllerused )); + mstudiobonecontroller_t *pbonecontrollers = (mstudiobonecontroller_t *) ((byte *) hdr + hdr->bonecontrollerindex); + + // mark used controllers + for (i = 0; i < hdr->numbonecontrollers; i++) + { + int inputfield = pbonecontrollers[i].index; + inputfield = bound( 0, inputfield, STUDIO_MOUTH ); + controllerused[inputfield] = 1; + } + + for (i = 0; i < MAX_BONECTRLS; i++) + { + if( !controllerused[i] ) + continue; + + char str[32]; + if( i == STUDIO_MOUTH ) + sprintf( str, "Mouth" ); + else sprintf( str, "Controller %d", i ); + controllerindex[count] = i; + cController->add( str ); + count++; + } + + if (count > 0) + { + cController->select (0); + setBoneController (0); + } + + } +} + +void ControlPanel :: setBoneController( int index ) +{ + studiohdr_t *hdr = g_studioModel.getStudioHeader (); + if( hdr ) + { + mstudiobonecontroller_t *pbonecontroller = (mstudiobonecontroller_t *) ((byte *) hdr + hdr->bonecontrollerindex); + + // find first controller that matches the index + for (int i = 0; i < hdr->numbonecontrollers; i++, pbonecontroller++) + { + if (pbonecontroller->index == controllerindex[index]) + { + slController->setRange ((int) pbonecontroller->start, (int) pbonecontroller->end); + break; + } + } + slController->setValue (controllervalues[index]); + } +} + +void ControlPanel :: setBoneControllerValue( int index, float value ) +{ + studiohdr_t *hdr = g_studioModel.getStudioHeader (); + if (hdr) + { + index = controllerindex[index]; + + if (index == STUDIO_MOUTH) g_studioModel.SetMouth (value); + else g_studioModel.SetController (index, value); + controllervalues[index] = value; + } +} + +void ControlPanel::setBlend(int index, float value ) +{ + g_studioModel.SetPoseParameter( index, value ); + // reset number of frames.... +// updateFrameSelection( ); + +// updateGroundSpeed( ); +} + +void ControlPanel::initSkins () +{ + studiohdr_t *hdr = g_studioModel.getStudioHeader (); + if (hdr) + { + cSkin->setEnabled (hdr->numskinfamilies > 0); + cSkin->removeAll (); + + for (int i = 0; i < hdr->numskinfamilies; i++) + { + char str[32]; + sprintf (str, "Skin %d", i + 1); + cSkin->add (str); + } + + cSkin->select (0); + g_studioModel.SetSkin (0); + g_viewerSettings.skin = 0; + } +} + +void ControlPanel :: updateDrawnPolys( void ) +{ + static char str[64]; + + sprintf (str, "Drawn Polys: %d", g_viewerSettings.drawn_polys ); + lDrawnPolys->setLabel( str ); + lDrawnPolys2->setLabel( str ); +} + +void ControlPanel::setModelInfo () +{ + static char str[2048]; + studiohdr_t *hdr = g_studioModel.getStudioHeader (); + + if (!hdr) + return; + + cbAttachments->setEnabled( hdr->numattachments > 0 ); + + sprintf (str, + "Bones: %d\n" + "Bone Controllers: %d\n" + "Hit Boxes: %d\n" + "Sequences: %d\n" + "Sequence Groups: %d\n", + hdr->numbones, + hdr->numbonecontrollers, + hdr->numhitboxes, + hdr->numseq, + hdr->numseqgroups + ); + + lModelInfo1->setLabel (str); + + sprintf (str, + "Textures: %d\n" + "Skin Families: %d\n" + "Bodyparts: %d\n" + "Attachments: %d\n" + "Transitions: %d\n", + hdr->numtextures, + hdr->numskinfamilies, + hdr->numbodyparts, + hdr->numattachments, + hdr->numtransitions); + + lModelInfo2->setLabel (str); +} + +void ControlPanel::setSequenceInfo () +{ + static char str[2048]; + studiohdr_t *hdr = g_studioModel.getStudioHeader (); + + if (!hdr) + return; + + mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)hdr + hdr->seqindex) + g_viewerSettings.sequence; + + sprintf (str, + "Sequence#: %d\n" + "Frames: %d\n" + "FPS: %.f\n" + "Blends: %d\n" + "# of events: %d\n", + g_viewerSettings.sequence, + pseqdesc->numframes, + pseqdesc->fps, + pseqdesc->numblends, + pseqdesc->numevents + ); + + lSequenceInfo->setLabel (str); +} + +void ControlPanel::setModelFlags( void ) +{ + studiohdr_t *phdr = g_studioModel.getStudioHeader (); + if (phdr) + { + cbFlagRocket->setChecked ((phdr->flags & STUDIO_ROCKET) == STUDIO_ROCKET); + cbFlagGrenade->setChecked ((phdr->flags & STUDIO_GRENADE) == STUDIO_GRENADE); + cbFlagGib->setChecked ((phdr->flags & STUDIO_GIB) == STUDIO_GIB); + cbFlagRotate->setChecked ((phdr->flags & STUDIO_ROTATE) == STUDIO_ROTATE); + cbFlagTracer->setChecked ((phdr->flags & STUDIO_TRACER) == STUDIO_TRACER); + cbFlagZomgib->setChecked ((phdr->flags & STUDIO_ZOMGIB) == STUDIO_ZOMGIB); + cbFlagTracer2->setChecked ((phdr->flags & STUDIO_TRACER2) == STUDIO_TRACER2); + cbFlagTracer3->setChecked ((phdr->flags & STUDIO_TRACER3) == STUDIO_TRACER3); + cbFlagAmbientLight->setChecked ((phdr->flags & STUDIO_AMBIENT_LIGHT) == STUDIO_AMBIENT_LIGHT); + cbFlagTraceHitbox->setChecked ((phdr->flags & STUDIO_TRACE_HITBOX) == STUDIO_TRACE_HITBOX); + cbFlagForceSkylight->setChecked ((phdr->flags & STUDIO_FORCE_SKYLIGHT) == STUDIO_FORCE_SKYLIGHT); + } +} + +void +ControlPanel::initTextures () +{ + studiohdr_t *hdr = g_studioModel.getTextureHeader (); + if (hdr) + { + cTextures->removeAll (); + mstudiotexture_t *ptextures = (mstudiotexture_t *) ((byte *) hdr + hdr->textureindex); + for (int i = 0; i < hdr->numtextures; i++) + cTextures->add (ptextures[i].name); + cTextures->select (0); + g_viewerSettings.texture = 0; + if (hdr->numtextures > 0) + { + char str[32]; + sprintf (str, "Texture (size: %d x %d)", ptextures[0].width, ptextures[0].height); + lTexSize->setLabel (str); + cbChrome->setChecked ((ptextures[0].flags & STUDIO_NF_CHROME) == STUDIO_NF_CHROME); + cbAdditive->setChecked ((ptextures[0].flags & STUDIO_NF_ADDITIVE) == STUDIO_NF_ADDITIVE); + cbTransparent->setChecked ((ptextures[0].flags & STUDIO_NF_MASKED) == STUDIO_NF_MASKED); + cbFullbright->setChecked ((ptextures[0].flags & STUDIO_NF_FULLBRIGHT) == STUDIO_NF_FULLBRIGHT); + cbFlatshade->setChecked ((ptextures[0].flags & STUDIO_NF_FLATSHADE) == STUDIO_NF_FLATSHADE); + cbTwoSided->setChecked ((ptextures[0].flags & STUDIO_NF_TWOSIDE) == STUDIO_NF_TWOSIDE); + cbSmooth->setChecked ((ptextures[0].flags & STUDIO_NF_SMOOTH) == STUDIO_NF_SMOOTH); + cbSolidAlpha->setChecked ((ptextures[0].flags & STUDIO_NF_ALPHASOLID) == STUDIO_NF_ALPHASOLID); + leTextureName->setLabel( ptextures[0].name ); + } +#if 0 + if( hdr->numbones <= 0 ) + { + g_viewerSettings.showTexture = true; + tab->select( 2 ); // probably we load "T.mdl" force to select textures view + } +#endif + } +} + +float ControlPanel :: getEditStep( void ) +{ + return (float)atof( leEditStep->getLabel ()); +} + +void ControlPanel :: centerView( bool reset ) +{ + g_studioModel.centerView( reset ); + d_GlWindow->redraw (); +} \ No newline at end of file diff --git a/utils/hlmv/ControlPanel.h b/utils/hlmv/ControlPanel.h new file mode 100644 index 0000000..ba31395 --- /dev/null +++ b/utils/hlmv/ControlPanel.h @@ -0,0 +1,275 @@ +// +// Half-Life Model Viewer (c) 1999 by Mete Ciragan +// +// file: ControlPanel.h +// last modified: Oct 20 programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +// version: 1.24 +// +// email: mete@swissquake.ch +// web: http://www.swissquake.ch/chumbalum-soft/ +// +#ifndef INCLUDED_CONTROLPANEL +#define INCLUDED_CONTROLPANEL + + + +#ifndef INCLUDED_MXWINDOW +#include +#endif + +#define TAB_MODELDISPLAY 0 // render options, change rendermode, show info +#define TAB_BODYPARTS 1 // bodyparts, skins, etc +#define TAB_TEXTURES 2 // texture browser +#define TAB_SEQUENCES 3 // sequence browser +#define TAB_MISC 4 // model flags, remapping +#define TAB_CONTROLS 5 // global bone controllers, pose parameters +#define TAB_MODELEDITOR 6 // built-in model editor + +#define IDC_TAB 1901 +#define IDC_RENDERMODE 2001 +#define IDC_TRANSPARENCY 2002 +#define IDC_GROUND 2003 +#define IDC_MIRROR 2004 +#define IDC_BACKGROUND 2005 +#define IDC_HITBOXES 2006 +#define IDC_BONES 2007 +#define IDC_ATTACHMENTS 2008 +#define IDC_NORMALS 2009 +#define IDC_WIREFRAME 2010 + +#define MAX_BONECTRLS 5 // 4 user-controllers + mouth +#define IDC_BODYPART 3001 +#define IDC_SUBMODEL 3002 +#define IDC_CONTROLLER 3003 +#define IDC_CONTROLLERVALUE 3004 +#define IDC_SKINS 3005 + +#define IDC_TEXTURES 4001 +#define IDC_EXPORTTEXTURE 4002 +#define IDC_IMPORTTEXTURE 4003 +#define IDC_EXPORT_UVMAP 4004 +#define IDC_TEXTURESCALE 4005 +#define IDC_CHROME 4006 +#define IDC_ADDITIVE 4007 +#define IDC_TRANSPARENT 4008 +#define IDC_FULLBRIGHT 4009 +#define IDC_FLATSHADE 4010 +#define IDC_TWO_SIDED 4011 +#define IDC_SMOOTH_TBN 4012 +#define IDC_SOLID_ALPHA 4013 +#define IDC_EDIT_TEXTURE_NAME 4014 +#define IDC_SET_TEXTURE_NAME 4015 + +#define IDC_SHOW_UV_MAP 4020 +#define IDC_OVERLAY_UV_MAP 4021 +#define IDC_ANTI_ALIAS_LINES 4022 + +#define IDC_SEQUENCE 5001 +#define IDC_SPEEDSCALE 5002 +#define IDC_STOP 5003 +#define IDC_PREVFRAME 5004 +#define IDC_FRAME 5005 +#define IDC_NEXTFRAME 5006 +#define IDC_LOOPANIM 5007 +#define IDC_BLENDER0 5008 +#define IDC_BLENDER1 5009 + +#define NUM_POSEPARAMETERS 4 +#define IDC_POSEPARAMETER_SCALE 5100 +#define IDC_POSEPARAMETER 5120 +#define IDC_POSEPARAMETER_VALUE 5140 +#define IDC_ENABLE_IK 5200 + +#define IDC_MISC 6001 +#define IDC_STUDIO_ROCKET 6002 // leave a trail +#define IDC_STUDIO_GRENADE 6003 // leave a trail +#define IDC_STUDIO_GIB 6004 // leave a trail +#define IDC_STUDIO_ROTATE 6005 // rotate (bonus items) +#define IDC_STUDIO_TRACER 6006 // green split trail +#define IDC_STUDIO_ZOMGIB 6007 // small blood trail +#define IDC_STUDIO_TRACER2 6008 // orange split trail + rotate +#define IDC_STUDIO_TRACER3 6009 // purple trail +#define IDC_STUDIO_AMBIENT_LIGHT 6010 // dynamically get lighting from floor or ceil (flying monsters) +#define IDC_STUDIO_TRACE_HITBOX 6011 // always use hitbox trace instead of bbox +#define IDC_STUDIO_FORCE_SKYLIGHT 6012 // always grab lightvalues from the sky settings (even if sky is invisible) +#define IDC_TOPCOLOR 6013 +#define IDC_BOTTOMCOLOR 6014 + +#define IDC_EDITOR 7001 +#define IDC_MOVE_PX 7002 +#define IDC_MOVE_NX 7003 +#define IDC_MOVE_PY 7004 +#define IDC_MOVE_NY 7005 +#define IDC_MOVE_PZ 7006 +#define IDC_MOVE_NZ 7007 +#define IDC_EDIT_TYPE 7008 +#define IDC_EDIT_MODE 7009 +#define IDC_EDIT_STEP 7010 +#define IDC_EDIT_SIZE 7011 +#define IDC_CONVERT_TEXCOORDS 7012 + +class mxTab; +class mxChoice; +class mxCheckBox; +class mxSlider; +class mxLineEdit; +class mxLabel; +class mxButton; +class mxToggleButton; +class GlWindow; +class TextureWindow; + + + +class ControlPanel : public mxWindow +{ + mxTab *tab; + mxChoice *cRenderMode; + mxLabel *lOpacityValue; + mxSlider *slTransparency; + mxCheckBox *cbAttachments; + mxCheckBox *cbGround, *cbMirror, *cbBackground; + mxLabel *lDrawnPolys; + mxChoice *cSequence; + mxSlider *slSpeedScale; + mxButton *tbStop; + mxButton *bPrevFrame, *bNextFrame; + mxLabel *lSequenceInfo; + mxSlider *slBlender0; + mxSlider *slBlender1; + mxCheckBox *cbLoopAnim; + mxLineEdit *leFrame; + mxChoice *cBodypart, *cController, *cSubmodel; + mxLabel *BodyPartLabel; + mxSlider *slController; + mxChoice *cSkin; + mxLabel *lDrawnPolys2; + mxLabel *lModelInfo1, *lModelInfo2; + mxChoice *cTextures; + mxCheckBox *cbChrome; + mxCheckBox *cbAdditive; + mxCheckBox *cbTransparent; + mxCheckBox *cbFullbright; + mxCheckBox *cbFlatshade; + mxCheckBox *cbTwoSided; + mxCheckBox *cbSolidAlpha; + mxCheckBox *cbSmooth; + mxCheckBox *cbShowUVMap; + mxCheckBox *cbOverlayUVMap; + mxCheckBox *cbAntiAliasLines; + mxLineEdit *leTextureName; + mxLabel *lTexSize; + mxLabel *lTexScale; + mxLineEdit *leWidth, *leHeight; + mxLineEdit *leMeshScale, *leBoneScale; + mxCheckBox *cbFlagRocket; + mxCheckBox *cbFlagGrenade; + mxCheckBox *cbFlagGib; + mxCheckBox *cbFlagRotate; + mxCheckBox *cbFlagTracer; + mxCheckBox *cbFlagZomgib; + mxCheckBox *cbFlagTracer2; + mxCheckBox *cbFlagTracer3; + mxCheckBox *cbFlagAmbientLight; + mxCheckBox *cbFlagTraceHitbox; + mxCheckBox *cbFlagForceSkylight; + mxSlider *slTopColor; + mxSlider *slBottomColor; + mxChoice *cPoseParameter[NUM_POSEPARAMETERS]; + mxSlider *slPoseParameter[NUM_POSEPARAMETERS]; + mxLineEdit *lePoseParameter[NUM_POSEPARAMETERS]; + mxCheckBox *cbEnableIK; + mxChoice *cEditMode; + mxChoice *cEditType; + mxLineEdit *leEditStep; + mxLineEdit *leEditString; + mxCheckBox *cbEditSize; + + mxButton *tbMovePosX; + mxButton *tbMoveNegX; + mxButton *tbMovePosY; + mxButton *tbMoveNegY; + mxButton *tbMovePosZ; + mxButton *tbMoveNegZ; + mxButton *tbConvertTexCoords; + + GlWindow *d_GlWindow; + TextureWindow *d_textureWindow; + + // sequence remapping + int *iSelectionToSequence; // selection to sequence + int *iSequenceToSelection; // sequence to selection + float controllervalues[MAX_BONECTRLS]; + int controllerindex[MAX_BONECTRLS]; +public: + // CREATORS + ControlPanel (mxWindow *parent); + virtual ~ControlPanel (); + + // MANIPULATORS + int handleEvent (mxEvent *event); + + void dumpModelInfo (); + int loadModel (const char *filename, bool centerView = true); + + void setRenderMode (int mode); + void setShowGround (bool b); + void setMirror (bool b); + void setShowBackground (bool b); + + void initSequences (); + void setSequence (int index); + + void initBodyparts (); + void setBodypart (int index); + int setSubmodel (int index); + + void initBoneControllers (); + void setBoneController (int index); + void setBoneControllerValue (int index, float value); + void setBlend(int index, float value ); + + void initEditModes (); + void addEditType ( const char *name, int type, int id = -1 ); + void toggleMoveSize ( bool size ); + + void initSkins (); + + void setModelInfo (); + + void setSequenceInfo (); + + void setModelFlags (); + + void initTextures (); + + void updatePoseParameters( void ); + + void centerView ( bool reset ); + + void setGlWindow (GlWindow *window) { d_GlWindow = window; } + void resetPlayingSequence( void ); + void setPlaySequence( void ); + + void updateDrawnPolys (void); + + float getEditStep( void ); + + // no ACCESSORS + + int getTableIndex(); +}; + + + +extern ControlPanel *g_ControlPanel; + + + +#endif // INCLUDED_CONTROLPANEL \ No newline at end of file diff --git a/utils/hlmv/FileAssociation.cpp b/utils/hlmv/FileAssociation.cpp new file mode 100644 index 0000000..fb6ba5a --- /dev/null +++ b/utils/hlmv/FileAssociation.cpp @@ -0,0 +1,281 @@ +// +// Half-Life Model Viewer (c) 1999 by Mete Ciragan +// +// file: FileAssociation.cpp +// last modified: May 04 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +// version: 1.2 +// +// email: mete@swissquake.ch +// web: http://www.swissquake.ch/chumbalum-soft/ +// +#include "FileAssociation.h" +#include +#include +#include +#include + + + +FileAssociation *g_FileAssociation = 0; + + + +FileAssociation::FileAssociation () +: mxWindow (0, 100, 100, 400, 210, "File Associations", mxWindow::Dialog) +{ + cExtension = new mxChoice (this, 5, 5, 220, 22, IDC_EXTENSION); + + //new mxButton (this, 230, 5, 75, 22, "Add", IDC_ADD); + //new mxButton (this, 310, 5, 75, 22, "Remove", IDC_REMOVE); + + new mxGroupBox (this, 5, 30, 380, 115, "Assocations"); + rbAction[0] = new mxRadioButton (this, 10, 50, 120, 22, "program", IDC_ACTION1, true); + rbAction[1] = new mxRadioButton (this, 10, 72, 120, 22, "associated program", IDC_ACTION2); + rbAction[2] = new mxRadioButton (this, 10, 94, 120, 22, "HLMV default", IDC_ACTION3); + rbAction[3] = new mxRadioButton (this, 10, 116, 120, 22, "none", IDC_ACTION4); + leProgram = new mxLineEdit (this, 130, 50, 220, 22, "", IDC_PROGRAM); + leProgram->setEnabled (false); + bChooseProgram = new mxButton (this, 352, 50, 22, 22, ">>", IDC_CHOOSEPROGRAM); + bChooseProgram->setEnabled (false); + + rbAction[0]->setChecked (false); + rbAction[1]->setChecked (true); + + new mxButton (this, 110, 155, 75, 22, "Ok", IDC_OK); + new mxButton (this, 215, 155, 75, 22, "Cancel", IDC_CANCEL); + + initAssociations (); +} + + + +FileAssociation::~FileAssociation () +{ +} + + + +int +FileAssociation::handleEvent (mxEvent *event) +{ + if (event->event != mxEvent::Action) + return 0; + + switch (event->action) + { + case IDC_EXTENSION: + { + int index = cExtension->getSelectedIndex (); + if (index >= 0) + setAssociation (index); + } + break; + + case IDC_ACTION1: + case IDC_ACTION2: + case IDC_ACTION3: + case IDC_ACTION4: + { + leProgram->setEnabled (rbAction[0]->isChecked ()); + bChooseProgram->setEnabled (rbAction[0]->isChecked ()); + + int index = cExtension->getSelectedIndex (); + if (index >= 0) + d_associations[index].association = event->action - IDC_ACTION1; + + } + break; + + case IDC_PROGRAM: + { + int index = cExtension->getSelectedIndex (); + if (index >= 0) + strcpy (d_associations[index].program, leProgram->getLabel ()); + } + break; + + case IDC_CHOOSEPROGRAM: + { + const char *ptr = mxGetOpenFileName (this, 0, "*.exe"); + if (ptr) + { + leProgram->setLabel (ptr); + + int index = cExtension->getSelectedIndex (); + if (index >= 0) + strcpy (d_associations[index].program, leProgram->getLabel ()); + } + } + break; + + case IDC_OK: + saveAssociations (); + + case IDC_CANCEL: + setVisible (false); + break; + } + + return 1; +} + + + +void +FileAssociation::initAssociations () +{ + int i; + + cExtension->removeAll (); + + for (i = 0; i < 16; i++) + d_associations[i].association = -1; + + char path[256]; + strcpy (path, mx::getApplicationPath ()); + strcat (path, "/hlmv.fa"); + FILE *file = fopen (path, "rt"); + if (!file) + return; + + i = 0; + char line[256]; + while (i < 16 && fgets (line, 256, file)) + { + int j = 0; + while (line[++j] != '\"'); + line[j] = '\0'; + strcpy (d_associations[i].extension, &line[1]); + + while (line[++j] != '\"'); + int k = j + 1; + while (line[++j] != '\"'); + line[j] = '\0'; + strcpy (d_associations[i].program, &line[k]); + + d_associations[i].association = atoi (&line[++j]); + + cExtension->add (d_associations[i].extension); + ++i; + } + + fclose (file); + + setAssociation (0); +} + + + +void +FileAssociation::setAssociation (int index) +{ + cExtension->select (index); + leProgram->setLabel (d_associations[index].program); + + for (int i = 0; i < 4; i++) + rbAction[i]->setChecked (i == d_associations[index].association); + + leProgram->setEnabled (d_associations[index].association == 0); + bChooseProgram->setEnabled (d_associations[index].association == 0); + + // TODO: check for valid associtaion +#ifdef WIN32__ + char path[256]; + + strcpy (path, mx_gettemppath ()); + strcat (path, "/hlmvtemp."); + strcat (path, d_associations[index].extension); + + FILE *file = fopen (path, "wb"); + if (file) + fclose (file); + + int val = (int) ShellExecute ((HWND) getHandle (), "open", path, 0, 0, SW_HIDE); + char str[32]; + sprintf (str, "%d", val); + setLabel (str); + rbAction[1]->setEnabled (val != 31); +/* + WORD dw = 0; + HICON hIcon = ExtractAssociatedIcon ((HINSTANCE) GetWindowLong ((HWND) getHandle (), GWL_HINSTANCE), path, &dw); + SendMessage ((HWND) getHandle (), WM_SETICON, (WPARAM) ICON_SMALL, (LPARAM) hIcon); + char str[32]; + sprintf (str, "%d", (int) hIcon); + setLabel (str); +*/ + DeleteFile (path); + + //DestroyIcon (hIcon); +#endif + + rbAction[2]->setEnabled ( + !mx_strcasecmp (d_associations[index].extension, "mdl") || + !mx_strcasecmp (d_associations[index].extension, "tga") || + !mx_strcasecmp (d_associations[index].extension, "wav") + ); +} + + + +void +FileAssociation::saveAssociations () +{ + char path[256]; + + strcpy (path, mx::getApplicationPath ()); + strcat (path, "/hlmv.fa"); + + FILE *file = fopen (path, "wt"); + if (!file) + return; + + for (int i = 0; i < 16; i++) + { + if (d_associations[i].association == -1) + break; + + fprintf (file, "\"%s\" \"%s\" %d\n", + d_associations[i].extension, + d_associations[i].program, + d_associations[i].association); + } + + fclose (file); +} + + + +int +FileAssociation::getMode (char *extension) +{ + for (int i = 0; i < 16; i++) + { + //if (!strcmp (d_associations[i].extension, mx_strlower (extension))) + if (!strcmp (d_associations[i].extension, extension)) + return d_associations[i].association; + } + + return -1; +} + + + +char * +FileAssociation::getProgram (char *extension) +{ + for (int i = 0; i < 16; i++) + { + //if (!strcmp (d_associations[i].extension, mx_strlower (extension))) + if (!strcmp (d_associations[i].extension, extension)) + return d_associations[i].program; + } + + return 0; +} diff --git a/utils/hlmv/FileAssociation.h b/utils/hlmv/FileAssociation.h new file mode 100644 index 0000000..bda771c --- /dev/null +++ b/utils/hlmv/FileAssociation.h @@ -0,0 +1,91 @@ +// +// Half-Life Model Viewer (c) 1999 by Mete Ciragan +// +// file: FileAssociation.h +// last modified: Apr 28 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +// version: 1.2 +// +// email: mete@swissquake.ch +// web: http://www.swissquake.ch/chumbalum-soft/ +// +#ifndef INCLUDED_FILEASSOCIATION +#define INCLUDED_FILEASSOCIATION + + + +#ifndef INCLUDED_MXWINDOW +#include +#endif + + + +#define IDC_EXTENSION 1001 +#define IDC_ADD 1002 +#define IDC_REMOVE 1003 +#define IDC_ACTION1 1004 +#define IDC_ACTION2 1005 +#define IDC_ACTION3 1006 +#define IDC_ACTION4 1007 +#define IDC_PROGRAM 1008 +#define IDC_CHOOSEPROGRAM 1009 +#define IDC_OK 1010 +#define IDC_CANCEL 1011 + + + +typedef struct +{ + char extension[16]; + char program[256]; + int association; +} association_t; + + + + +class mxChoice; +class mxRadioButton; +class mxLineEdit; +class mxButton; + + + +class FileAssociation : public mxWindow +{ + mxChoice *cExtension; + mxRadioButton *rbAction[4]; + mxLineEdit *leProgram; + mxButton *bChooseProgram; + association_t d_associations[16]; + + void initAssociations (); + void saveAssociations (); + +public: + // CREATORS + FileAssociation (); + virtual ~FileAssociation (); + + // MANIPULATORS + int handleEvent (mxEvent *event); + void setAssociation (int index); + + // ACCESSORS + int getMode (char *extension); + char *getProgram (char *extension); +}; + + + +extern FileAssociation *g_FileAssociation; + + + +#endif // INCLUDED_FILEASSOCIATION \ No newline at end of file diff --git a/utils/hlmv/GlWindow.cpp b/utils/hlmv/GlWindow.cpp new file mode 100644 index 0000000..d92b07d --- /dev/null +++ b/utils/hlmv/GlWindow.cpp @@ -0,0 +1,799 @@ +// +// Half-Life Model Viewer (c) 1999 by Mete Ciragan +// +// file: GlWindow.cpp +// last modified: May 04 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +// version: 1.2 +// +// email: mete@swissquake.ch +// web: http://www.swissquake.ch/chumbalum-soft/ +// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "GlWindow.h" +#include "StudioModel.h" +#include "ViewerSettings.h" +#include "ControlPanel.h" +#include "stringlib.h" +#include "mdlviewer.h" +#include "muzzle1.h" +#include "muzzle2.h" +#include "muzzle3.h" + +extern char g_appTitle[]; +extern bool g_bStopPlaying; +extern bool bUseWeaponOrigin; +extern bool bUseWeaponLeftHand; +extern bool bUseParanoiaFOV; + +GlWindow *g_GlWindow = 0; + +GlWindow :: GlWindow( mxWindow *parent, int x, int y, int w, int h, const char *label, int style ) : mxGlWindow( parent, x, y, w, h, label, style ) +{ + glDepthFunc( GL_LEQUAL ); + + if( !parent ) setVisible( true ); + else mx :: setIdleWindow ( this ); + + // load muzzle flahses + loadTextureBuffer( muzzleflash1_bmp, sizeof( muzzleflash1_bmp ), TEXTURE_MUZZLEFLASH1 ); + loadTextureBuffer( muzzleflash2_bmp, sizeof( muzzleflash2_bmp ), TEXTURE_MUZZLEFLASH2 ); + loadTextureBuffer( muzzleflash3_bmp, sizeof( muzzleflash3_bmp ), TEXTURE_MUZZLEFLASH3 ); +} + +GlWindow :: ~GlWindow( void ) +{ + mx::setIdleWindow( 0 ); + loadTexture( NULL, TEXTURE_GROUND ); + loadTexture( NULL, TEXTURE_BACKGROUND ); + loadTexture( NULL, TEXTURE_MUZZLEFLASH1 ); + loadTexture( NULL, TEXTURE_MUZZLEFLASH2 ); + loadTexture( NULL, TEXTURE_MUZZLEFLASH3 ); +} + +int GlWindow :: handleEvent( mxEvent *event ) +{ + static float oldrx, oldry, oldtz, oldtx, oldty; + static float oldlx, oldly; + static int oldx, oldy; + static double lastupdate; + + switch( event->event ) + { + + case mxEvent::Idle: + { + static double prev; + double curr = (double) mx::getTickCount () / 1000.0; + double dt = (curr - prev); +#if 1 + // clamp to 100fps + if( dt >= 0.0 && dt < 0.01 ) + { + Sleep( Q_max( 10 - dt * 1000.0, 0 ) ); + return 1; + } +#endif + g_studioModel.updateTimings( curr, dt ); + + rand (); // keep the random time dependent + + if( !g_bStopPlaying && !g_viewerSettings.pause && prev != 0.0 ) + { + if( !g_studioModel.AdvanceFrame( dt * g_viewerSettings.speedScale )) + d_cpl->resetPlayingSequence(); // hit the end of sequence + } + + if( !g_viewerSettings.pause ) + redraw(); + + // update counter every 0.2 secs + if(( curr - lastupdate ) > 0.2 ) + { + g_ControlPanel->updateDrawnPolys(); + lastupdate = curr; + } + + prev = curr; + + return 1; + } + break; + + case mxEvent::MouseUp: + { + g_viewerSettings.pause = false; + } + break; + + case mxEvent::MouseDown: + { + oldrx = g_viewerSettings.rot[0]; + oldry = g_viewerSettings.rot[1]; + oldtx = g_viewerSettings.trans[0]; + oldty = g_viewerSettings.trans[1]; + oldtz = g_viewerSettings.trans[2]; + oldlx = g_viewerSettings.gLightVec[0]; + oldly = g_viewerSettings.gLightVec[1]; + oldx = event->x; + oldy = event->y; + + // HACKHACK: reset focus to main window to catch hot-keys again + if( g_MDLViewer ) SetFocus( (HWND) g_MDLViewer->getHandle ()); + g_viewerSettings.pause = true; + + return 1; + } + break; + + case mxEvent::MouseDrag: + { + if( g_viewerSettings.showTexture ) + { + redraw (); + return 1; + } + if( event->buttons & mxEvent::MouseLeftButton ) + { + if( event->modifiers & mxEvent::KeyShift ) + { + g_viewerSettings.trans[0] = oldtx - (float)(event->x - oldx) * g_viewerSettings.movementScale; + g_viewerSettings.trans[1] = oldty + (float)(event->y - oldy) * g_viewerSettings.movementScale; + } + else if( event->modifiers & mxEvent::KeyCtrl ) + { + float yaw = oldlx + (float)(event->x - oldx); + float pitch = oldly + (float)(event->y - oldy); + float sy, cy, sp, cp; + + pitch = DEG2RAD( anglemod( pitch * 0.6f )); + yaw = DEG2RAD( anglemod( yaw * 0.6f )); + SinCos( yaw, &sy, &cy ); + SinCos( pitch, &sp, &cp ); + + g_viewerSettings.gLightVec[0] = (cp*cy); + g_viewerSettings.gLightVec[1] = (-sy); + g_viewerSettings.gLightVec[2] = (sp*cy); + } + else + { + g_viewerSettings.rot[0] = oldrx + (float)(event->y - oldy); + g_viewerSettings.rot[1] = oldry + (float)(event->x - oldx); + } + } + else if( event->buttons & mxEvent::MouseRightButton ) + { + g_viewerSettings.trans[2] = oldtz + (float)(event->y - oldy) * g_viewerSettings.movementScale; + } + + redraw (); + + return 1; + } + break; + } // switch (event->event) + + return 1; +} + +void GlWindow :: drawFloor( int texture ) +{ + float scale = 5.0f; + float dist = -100.0f; + glEnable( GL_MULTISAMPLE ); + + if( texture ) + { + static Vector tMap( 0, 0, 0 ); + static Vector dxMap( 1, 0, 0 ); + static Vector dyMap( 0, 1, 0 ); + + Vector deltaPos; + Vector deltaAngles; + + g_studioModel.GetMovement( g_studioModel.m_prevGroundCycle, deltaPos, deltaAngles ); + + float dpdd = scale / dist; + + tMap.x = tMap.x + dxMap.x * deltaPos.x * dpdd + dxMap.y * deltaPos.y * dpdd; + tMap.y = tMap.y + dyMap.x * deltaPos.x * dpdd + dyMap.y * deltaPos.y * dpdd; + + while (tMap.x < 0.0) tMap.x += 1.0f; + while (tMap.x > 1.0) tMap.x += -1.0f; + while (tMap.y < 0.0) tMap.y += 1.0f; + while (tMap.y > 1.0) tMap.y += -1.0f; + + dxMap = VectorYawRotate( dxMap, -deltaAngles.y ); + dyMap = VectorYawRotate( dyMap, -deltaAngles.y ); + + glBegin( GL_QUADS ); + glTexCoord2f( tMap.x - (dxMap.x - dyMap.x) * scale, tMap.y - (-dxMap.y + dyMap.y) * scale ); + glVertex3f( -dist, -dist, 0 ); + + glTexCoord2f( tMap.x + (dxMap.x + dyMap.x) * scale, tMap.y + (-dxMap.y - dyMap.y) * scale ); + glVertex3f( dist, -dist, 0 ); + + glTexCoord2f( tMap.x + (dxMap.x - dyMap.x) * scale, tMap.y + (-dxMap.y + dyMap.y) * scale ); + glVertex3f( dist, dist, 0 ); + + glTexCoord2f( tMap.x + (-dxMap.x - dyMap.x) * scale, tMap.y + (dxMap.y + dyMap.y) * scale ); + glVertex3f( -dist, dist, 0 ); + glEnd(); + } + else + { + glBegin( GL_QUADS ); + glTexCoord2f( 0.0f, 1.0f ); + glVertex3f( -dist, -dist, 0 ); + + glTexCoord2f( 1.0f, 1.0f ); + glVertex3f( dist, -dist, 0 ); + + glTexCoord2f( 1.0f, 0.0f ); + glVertex3f( dist, dist, 0 ); + + glTexCoord2f( 0.0f, 0.0f ); + glVertex3f( -dist, dist, 0 ); + glEnd(); + } + + // restore original modelview + SetModelviewMatrix( m_modelview ); + glDisable( GL_MULTISAMPLE ); +} + +void GlWindow :: setupRenderMode( void ) +{ + switch( g_viewerSettings.renderMode ) + { + case RM_WIREFRAME: + glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + glDisable( GL_TEXTURE_2D ); + glDisable( GL_CULL_FACE ); + glEnable( GL_DEPTH_TEST ); + break; + case RM_FLATSHADED: + case RM_SMOOTHSHADED: + case RM_BONEWEIGHTS: + glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + glDisable( GL_TEXTURE_2D ); + glEnable( GL_CULL_FACE ); + glEnable( GL_DEPTH_TEST ); + + if( g_viewerSettings.renderMode == RM_FLATSHADED ) + glShadeModel( GL_FLAT ); + else glShadeModel( GL_SMOOTH ); + break; + default: + glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + glEnable( GL_TEXTURE_2D ); + glEnable( GL_CULL_FACE ); + glEnable( GL_DEPTH_TEST ); + glShadeModel( GL_SMOOTH ); + break; + } +} + +void GlWindow :: GluPerspective( float fov_y ) +{ + GLdouble xMin, xMax, yMin, yMax, zNear, zFar; + float aspect = (float)w() / h(); + + zFar = 131072.0f; // don't cull giantic models (e.g. skybox models) + zNear = bUseWeaponOrigin ? 4.0f : 0.1f; // ammo shell issues + + if( bUseWeaponOrigin && bUseParanoiaFOV ) + { + float fovX = 60.0f; // HL2 uses 54 as default + float fovY = V_CalcFov( fovX, w(), h() ); + + yMax = zNear * tan( fovY * M_PI / 360.0 ); + yMin = -yMax; + + xMax = zNear * tan( fovX * M_PI / 360.0 ); + xMin = -xMax; + } + else + { + yMax = zNear * tan( fov_y * M_PI / 360.0 ); + yMin = -yMax; + + xMin = yMin * aspect; + xMax = yMax * aspect; + } + + m_projection.CreateProjection( xMax, xMin, yMax, yMin, zNear, zFar ); + SetProjectionMatrix( m_projection ); +} + +void GlWindow :: SetProjectionMatrix( const matrix4x4 source ) +{ + GLfloat dest[16]; + + source.CopyToArray( dest ); + glMatrixMode( GL_PROJECTION ); + glLoadMatrixf( dest ); +} + +void GlWindow :: SetModelviewMatrix( const matrix4x4 source ) +{ + GLfloat dest[16]; + + source.CopyToArray( dest ); + glMatrixMode( GL_MODELVIEW ); + glLoadMatrixf( dest ); +} + +void GlWindow :: ResetModelviewMatrix( void ) +{ + glMatrixMode( GL_MODELVIEW ); + glLoadIdentity(); +} + +void GlWindow :: draw( void ) +{ + glClearColor( g_viewerSettings.bgColor[0], g_viewerSettings.bgColor[1], g_viewerSettings.bgColor[2], 0.0f ); + + if( g_viewerSettings.useStencil ) + glClear (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT|GL_STENCIL_BUFFER_BIT ); + else glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT ); + + glViewport( 0, 0, w2(), h2() ); + glDisable( GL_MULTISAMPLE ); + + // remap textures if changed + g_studioModel.RemapTextures(); + + // + // show textures + // + if( g_viewerSettings.showTexture ) + { + m_projection.CreateOrtho( 0.0f, w2(), h2(), 0.0f, 1.0f, -1.0f ); + SetProjectionMatrix( m_projection ); + glDisable( GL_MULTISAMPLE ); + + studiohdr_t *hdr = g_studioModel.getTextureHeader(); + + if( hdr ) + { + mstudiotexture_t *ptextures = (mstudiotexture_t *)((byte *)hdr + hdr->textureindex); + float w = (float) ptextures[g_viewerSettings.texture].width * g_viewerSettings.textureScale; + float h = (float) ptextures[g_viewerSettings.texture].height * g_viewerSettings.textureScale; + + ResetModelviewMatrix(); + + glDisable( GL_CULL_FACE ); + glDisable( GL_BLEND ); + + if( ptextures[g_viewerSettings.texture].flags & STUDIO_NF_MASKED ) + { + glEnable( GL_ALPHA_TEST ); + glAlphaFunc( GL_GREATER, 0.25f ); + } + else glDisable( GL_ALPHA_TEST ); + + glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + float x = ((float)w2 () - w) / 2; + float y = ((float)h2 () - h) / 2; + + if(( g_viewerSettings.show_uv_map || g_viewerSettings.pending_export_uvmap ) && !g_viewerSettings.overlay_uv_map ) + { + glColor4f (0.0f, 0.0f, 0.0f, 1.0f); + glDisable (GL_TEXTURE_2D); + } + else + { + glEnable (GL_TEXTURE_2D); + glColor4f (1.0f, 1.0f, 1.0f, 1.0f); + glBindTexture (GL_TEXTURE_2D, TEXTURE_COUNT + g_viewerSettings.texture ); + } + + glBegin( GL_TRIANGLE_STRIP ); + glTexCoord2f( 0, 0 ); + glVertex2f( x, y ); + glTexCoord2f( 1, 0 ); + glVertex2f( x + w, y ); + glTexCoord2f( 0, 1 ); + glVertex2f( x, y + h ); + glTexCoord2f( 1, 1 ); + glVertex2f( x + w, y + h ); + glEnd(); + + if( g_viewerSettings.show_uv_map || g_viewerSettings.pending_export_uvmap || g_viewerSettings.overlay_uv_map ) + { + if( g_viewerSettings.anti_alias_lines ) + { + glEnable( GL_LINE_SMOOTH ); + glEnable( GL_POLYGON_SMOOTH ); + glHint( GL_LINE_SMOOTH_HINT, GL_NICEST ); + glHint( GL_POLYGON_SMOOTH_HINT, GL_NICEST ); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + + g_studioModel.SetOffset2D( x, y ); + g_studioModel.DrawModelUVMap(); + + if( g_viewerSettings.anti_alias_lines ) + { + glDisable( GL_LINE_SMOOTH ); + glDisable(GL_POLYGON_SMOOTH); + } + } + + if( g_viewerSettings.pending_export_uvmap && g_viewerSettings.uvmapPath[0] ) + { + mxImage *image = new mxImage (); + + if( image->create((int)w, (int)h, 24 )) + { + glReadBuffer( GL_BACK ); + glReadPixels((int)x-1, (int)y, (int)w, (int)h, GL_RGB, GL_UNSIGNED_BYTE, image->data ); + + image->flip_vertical(); + + if( !mxBmpWrite( g_viewerSettings.uvmapPath, image )) + mxMessageBox( this, "Error writing .BMP texture.", g_appTitle, MX_MB_OK|MX_MB_ERROR ); + } + + // cleanup + memset( g_viewerSettings.uvmapPath, 0, sizeof( g_viewerSettings.uvmapPath )); + g_viewerSettings.pending_export_uvmap = false; + delete image; + } + + glClear( GL_DEPTH_BUFFER_BIT ); + glBindTexture( GL_TEXTURE_2D, 0 ); + } + return; + } + + // + // draw background + // + if( g_viewerSettings.showBackground && d_textureNames[TEXTURE_BACKGROUND] && !g_viewerSettings.showTexture ) + { + m_projection.CreateOrtho( 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, -1.0f ); + SetProjectionMatrix( m_projection ); + + ResetModelviewMatrix(); + + glDisable( GL_CULL_FACE ); + glEnable( GL_TEXTURE_2D ); + + glColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + + glBindTexture( GL_TEXTURE_2D, d_textureNames[TEXTURE_BACKGROUND] ); + + glBegin( GL_TRIANGLE_STRIP ); + glTexCoord2f( 0, 0 ); + glVertex2f( 0, 0 ); + glTexCoord2f( 1, 0 ); + glVertex2f( 1, 0 ); + glTexCoord2f( 0, 1 ); + glVertex2f( 0, 1 ); + glTexCoord2f( 1, 1 ); + glVertex2f( 1, 1 ); + glEnd(); + + glClear( GL_DEPTH_BUFFER_BIT ); + glBindTexture( GL_TEXTURE_2D, 0 ); + } + + // calc viewer FOV + float fov_x = (bUseWeaponOrigin) ? 68.0f : 65.0f; // same as original + float fov_y = V_CalcFov( fov_x, w(), h() ); + + // NOTE: GluPerspective receives fov_y as input but original code sent fov_x + // this is completely wrong but i'm leave it for backward compatibility + GluPerspective( fov_x ); + + if( bUseWeaponOrigin ) + { + m_modelview.CreateModelview(); // init quake world orientation + m_modelview.ConcatTranslate( 1.0f, 0.0f, -1.0f ); // shift back like in HL + vectors = matrix3x3( Vector( 90.0f, 0.0f, 0.0f )); + } + else + { + m_modelview.Identity(); + m_modelview.ConcatTranslate( -g_viewerSettings.trans[0], -g_viewerSettings.trans[1], -g_viewerSettings.trans[2] ); + m_modelview.ConcatRotate( g_viewerSettings.rot[0], 1, 0, 0 ); + m_modelview.ConcatRotate( g_viewerSettings.rot[1], 0, 0, 1 ); + vectors = matrix3x3( Vector( 180.0f - g_viewerSettings.rot[0], 270.0f - g_viewerSettings.rot[1], 0.0f )); + } + + SetModelviewMatrix( m_modelview ); + + // setup stencil buffer + if( g_viewerSettings.useStencil && !bUseWeaponOrigin ) + { + // Don't update color or depth. + glDisable( GL_DEPTH_TEST ); + glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE ); + + // Draw 1 into the stencil buffer. + glEnable( GL_STENCIL_TEST ); + glStencilOp( GL_KEEP, GL_KEEP, GL_REPLACE ); + glStencilFunc( GL_ALWAYS, 1, 0xffffffff ); + + // Now render floor; floor pixels just get their stencil set to 1. + drawFloor(); + + // Re-enable update of color and depth. + glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE ); + glEnable( GL_DEPTH_TEST ); + + // Now, only render where stencil is set to 1. + glStencilFunc( GL_EQUAL, 1, 0xffffffff ); // draw if == 1 + glStencilOp( GL_KEEP, GL_KEEP, GL_KEEP ); + } + + if( g_viewerSettings.mirror && !bUseWeaponOrigin ) + { + const GLdouble flClipPlane[] = { 0.0, 0.0, -1.0, 0.0 }; + + setupRenderMode(); + glCullFace( GL_BACK ); + glEnable( GL_CLIP_PLANE0 ); + glClipPlane( GL_CLIP_PLANE0, flClipPlane ); + g_studioModel.DrawModel( true ); + glDisable( GL_CLIP_PLANE0 ); + } + + g_viewerSettings.drawn_polys = 0; + + if( g_viewerSettings.useStencil ) + glDisable( GL_STENCIL_TEST ); + + setupRenderMode(); + + if( bUseWeaponOrigin && bUseWeaponLeftHand ) + glCullFace( GL_BACK ); + else glCullFace( GL_FRONT ); + + g_studioModel.DrawModel(); + + // + // draw ground + // + if( g_viewerSettings.showGround && !bUseWeaponOrigin ) + { + glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + glEnable( GL_DEPTH_TEST ); + glEnable( GL_CULL_FACE ); + + if( g_viewerSettings.useStencil ) + glFrontFace( GL_CW ); + else glDisable( GL_CULL_FACE ); + + glEnable( GL_BLEND ); + + if( !d_textureNames[TEXTURE_GROUND] ) + { + glDisable( GL_TEXTURE_2D ); + glColor4f( g_viewerSettings.gColor[0], g_viewerSettings.gColor[1], g_viewerSettings.gColor[2], 0.7f ); + glBindTexture( GL_TEXTURE_2D, 0 ); + } + else + { + glEnable( GL_TEXTURE_2D ); + glColor4f( 1.0f, 1.0f, 1.0f, 0.6f ); + glBindTexture( GL_TEXTURE_2D, d_textureNames[TEXTURE_GROUND] ); + } + + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + drawFloor( d_textureNames[TEXTURE_GROUND] ); + glDisable( GL_BLEND ); + + if( g_viewerSettings.useStencil ) + { + glCullFace( GL_BACK ); + glColor4f( 0.1f, 0.1f, 0.1f, 1.0f ); + glBindTexture( GL_TEXTURE_2D, 0 ); + drawFloor(); + glFrontFace( GL_CCW ); + } + else glEnable( GL_CULL_FACE ); + } + + if( bUseWeaponOrigin ) + { + float hW = (float)w2() / 2; + float hH = (float)h2() / 2; + float flColor = 1.0f; + int dir = 5; + + m_projection.CreateOrtho( 0.0f, w2(), h2(), 0.0f, 1.0f, -1.0f ); + SetProjectionMatrix( m_projection ); + + glDisable( GL_MULTISAMPLE ); + glDisable( GL_TEXTURE_2D ); + ResetModelviewMatrix(); + + glColor4f( 1.0f, flColor, flColor, 0.8f ); + + glBegin( GL_LINES ); + glVertex3f( hW, hH - dir, 0.0f ); + glVertex3f( hW, hH - ( CROSS_LENGTH * 1.2f ) - dir, 0.0f ); + glEnd(); + + glBegin( GL_LINES ); + glVertex3f( hW + dir, hH + dir, 0.0f ); + glVertex3f( hW + CROSS_LENGTH + dir, hH + CROSS_LENGTH + dir, 0.0f ); + glEnd(); + + glBegin( GL_LINES ); + glVertex3f( hW - dir, hH + dir, 0.0f ); + glVertex3f( hW - CROSS_LENGTH - dir, hH + CROSS_LENGTH + dir, 0.0f ); + glEnd(); + + glEnable( GL_TEXTURE_2D ); + } + + g_studioModel.incrementFramecounter(); +} + +int GlWindow :: loadTextureImage( mxImage *image, int name ) +{ + if( image ) + { + d_textureNames[name] = name; + glPixelStorei( GL_UNPACK_ALIGNMENT, 1 ); + glPixelStorei( GL_PACK_ALIGNMENT, 1 ); + + if( image->bpp == 8 ) + { + mstudiotexture_t texture; + texture.width = image->width; + texture.height = image->height; + texture.flags = 0; + + g_studioModel.UploadTexture( &texture, (byte *)image->data, (byte *)image->palette, name ); + } + else if( image->bpp == 24 ) + { + glBindTexture( GL_TEXTURE_2D, d_textureNames[name] ); + glHint( GL_GENERATE_MIPMAP_HINT_SGIS, GL_NICEST ); + glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE ); + glTexImage2D( GL_TEXTURE_2D, 0, 3, image->width, image->height, 0, GL_RGB, GL_UNSIGNED_BYTE, image->data ); + glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + + const char *extensions = (const char *)glGetString( GL_EXTENSIONS ); + + // check for anisotropy support + if( Q_strstr( extensions, "GL_EXT_texture_filter_anisotropic" )) + { + float anisotropy = 1.0f; + glGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisotropy ); + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropy ); + } + } + else if( image->bpp == 32 ) + { + glBindTexture( GL_TEXTURE_2D, d_textureNames[name] ); + glHint( GL_GENERATE_MIPMAP_HINT_SGIS, GL_NICEST ); + glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE ); + glTexImage2D( GL_TEXTURE_2D, 0, 4, image->width, image->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image->data ); + glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + + const char *extensions = (const char *)glGetString( GL_EXTENSIONS ); + + // check for anisotropy support + if( Q_strstr( extensions, "GL_EXT_texture_filter_anisotropic" )) + { + float anisotropy = 1.0f; + glGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisotropy ); + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropy ); + } + } + + delete image; + + return name; + } + + return TEXTURE_UNUSED; +} + +int GlWindow :: loadTexture( const char *filename, int name ) +{ + if( !filename || !strlen( filename )) + { + if( d_textureNames[name] ) + { + glDeleteTextures( 1, (const GLuint *)&d_textureNames[name] ); + d_textureNames[name] = TEXTURE_UNUSED; + + if( name == TEXTURE_BACKGROUND ) + strcpy( g_viewerSettings.backgroundTexFile, "" ); + else if( name == TEXTURE_GROUND ) + strcpy( g_viewerSettings.groundTexFile, "" ); + } + return TEXTURE_UNUSED; + } + + mxImage *image = NULL; + char ext[16]; + + strcpy( ext, mx_getextension( filename )); + + if( !mx_strcasecmp( ext, ".tga" )) + image = mxTgaRead( filename ); + else if( !mx_strcasecmp( ext, ".pcx" )) + image = mxPcxRead( filename ); + else if( !mx_strcasecmp( ext, ".bmp" )) + image = mxBmpRead( filename ); + + if( image ) + { + if( name == TEXTURE_BACKGROUND ) + strcpy( g_viewerSettings.backgroundTexFile, filename ); + else if( name == TEXTURE_GROUND ) + strcpy( g_viewerSettings.groundTexFile, filename ); + } + + return loadTextureImage( image, name ); +} + +int GlWindow :: loadTextureBuffer( const byte *buffer, size_t size, int name ) +{ + if( !buffer || size <= 0 ) + { + if( d_textureNames[name] ) + { + glDeleteTextures( 1, (const GLuint *)&d_textureNames[name] ); + d_textureNames[name] = TEXTURE_UNUSED; + } + return TEXTURE_UNUSED; + } + + mxImage *image = NULL; + + image = mxBmpReadBuffer( buffer, size ); + + return loadTextureImage( image, name ); +} + +void GlWindow :: dumpViewport( const char *filename ) +{ + redraw(); + int w = w2(); + int h = h2(); + + mxImage *image = new mxImage (); + if( image->create( w, h, 24 )) + { + glReadBuffer( GL_FRONT ); + glReadPixels( 0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, image->data ); + + image->flip_vertical(); + + if( !mxBmpWrite( filename, image )) + mxMessageBox( this, "Error writing screenshot.", g_appTitle, MX_MB_OK|MX_MB_ERROR ); + + delete image; + } +} \ No newline at end of file diff --git a/utils/hlmv/GlWindow.h b/utils/hlmv/GlWindow.h new file mode 100644 index 0000000..3df2f2b --- /dev/null +++ b/utils/hlmv/GlWindow.h @@ -0,0 +1,94 @@ +// +// Half-Life Model Viewer (c) 1999 by Mete Ciragan +// +// file: GlWindow.h +// last modified: Apr 28 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +// version: 1.2 +// +// email: mete@swissquake.ch +// web: http://www.swissquake.ch/chumbalum-soft/ +// +#ifndef INCLUDED_GLWINDOW +#define INCLUDED_GLWINDOW + + + +#ifndef INCLUDED_MXGLWINDOW +#include +#endif + +#ifndef INCLUDED_VIEWERSETTINGS +#include "ViewerSettings.h" +#endif + +#include "matrix.h" +#include "conprint.h" + +enum // texture names +{ + TEXTURE_UNUSED = 0, + TEXTURE_GROUND, + TEXTURE_BACKGROUND, + TEXTURE_MUZZLEFLASH1, + TEXTURE_MUZZLEFLASH2, + TEXTURE_MUZZLEFLASH3, + TEXTURE_COUNT +}; + +#define GL_GENERATE_MIPMAP_SGIS 0x8191 +#define GL_GENERATE_MIPMAP_HINT_SGIS 0x8192 +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#define GL_MULTISAMPLE 0x809D + +class StudioModel; +class ControlPanel; +class mxImage; + +class GlWindow : public mxGlWindow +{ + int d_textureNames[TEXTURE_COUNT]; + + matrix4x4 m_projection; + matrix4x4 m_modelview; + ControlPanel *d_cpl; +public: + // CREATORS + GlWindow (mxWindow *parent, int x, int y, int w, int h, const char *label, int style); + ~GlWindow (); + + // MANIPULATORS + virtual int handleEvent (mxEvent *event); + virtual void draw (); + + int loadTexture (const char *filename, int name); + int loadTextureBuffer( const byte *buffer, size_t size, int name ); + int loadTextureImage( mxImage *image, int name ); + void dumpViewport (const char *filename); + void GluPerspective( float fov_y ); + void SetProjectionMatrix( const matrix4x4 source ); + void SetModelviewMatrix( const matrix4x4 source ); + void ResetModelviewMatrix( void ); + void setupRenderMode( void ); + void drawFloor( int texture = 0 ); + const matrix4x4& GetModelview( void ) const { return m_modelview; } + void setControlPanel( ControlPanel *panel ) { d_cpl = panel; } + + // ACCESSORS + matrix3x3 vectors; // muzzleflashes uses this +}; + + + +extern GlWindow *g_GlWindow; + + + +#endif // INCLUDED_GLWINDOW diff --git a/utils/hlmv/StudioModel.h b/utils/hlmv/StudioModel.h new file mode 100644 index 0000000..a4783c8 --- /dev/null +++ b/utils/hlmv/StudioModel.h @@ -0,0 +1,245 @@ +/*** +* +* Copyright (c) 1998, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#ifndef INCLUDED_STUDIOMODEL +#define INCLUDED_STUDIOMODEL + +#ifndef byte +typedef unsigned char byte; +#endif // byte + +#include "mathlib.h" +#include "studio.h" +#include "jigglebones.h" +#include +#include "bs_defs.h" +#include "ikcontext.h" + +#define MAX_SEQBLENDS 4 // must be power of two +#define MASK_SEQBLENDS (MAX_SEQBLENDS - 1) + +// remapping info +#define PLATE_HUE_START 160 +#define PLATE_HUE_END 191 +#define SUIT_HUE_START PLATE_HUE_END+1 +#define SUIT_HUE_END 223 + +#define MAX_MUZZLEFLASHES 4 // per one frame +#define MASK_MUZZLEFLASHES (MAX_MUZZLEFLASHES - 1) + +#define CROSS_LENGTH 18.0f + +#define MAX_EDITFIELDS 128 + +#define TYPE_ORIGIN 0 +#define TYPE_BBOX 1 +#define TYPE_CBOX 2 +#define TYPE_EYEPOSITION 3 +#define TYPE_ATTACHMENT 4 +#define TYPE_HITBOX 5 + +#define RANDOM_LONG(MIN, MAX) ((rand() & 32767) * (((MAX)-(MIN)) * (1.0f / 32767.0f)) + (MIN)) +#define RANDOM_FLOAT(MIN,MAX) (((float)rand() / RAND_MAX) * ((MAX)-(MIN)) + (MIN)) + +typedef struct +{ + float blendtime; // time to blend between current and previous sequence + int sequence; // previous sequence number + byte blending[2]; // blending values from previous sequence + float cycle; // cycle where sequence was changed + float fadeout; +} blend_sequence_t; + +typedef struct +{ + int texture; + float rotate; + vec3_t origin; + float scale; + float time; +} muzzleflash_t; + +typedef struct +{ + int type; + int id; // #id for mutilple count + int bone; // name get + int hitgroup; // hitbox uses + vec3_t origin; + vec3_t mins; // bboxes only + vec3_t maxs; +} edit_field_t; + +class CBaseBoneSetup : public CStudioBoneSetup +{ +public: + virtual void debugMsg( char *szFmt, ... ); + virtual mstudioanim_t *GetAnimSourceData( mstudioseqdesc_t *pseqdesc ); + virtual void debugLine( const Vector& origin, const Vector& dest, int r, int g, int b, bool noDepthTest = false, float duration = 0.0f ); +}; + +class StudioModel +{ +public: + studiohdr_t *getStudioHeader () const { return m_pstudiohdr; } + studiohdr_t *getTextureHeader () const { return m_ptexturehdr; } + studiohdr_t *getAnimHeader (int i) const { return m_panimhdr[i]; } + void updateTimings( float flTime, float flFrametime ) { m_flTime = flTime; m_flFrameTime = flFrametime; } + void incrementFramecounter( void ) { m_iFramecounter++; }; + int getNumBlendings( void ); + int hasLocalBlending( void ); + float getCurrentTime( void ) { return m_flTime; } + + void UploadTexture( mstudiotexture_t *ptexture, byte *data, byte *pal, int name ); + void PaletteHueReplace( byte *palSrc, int newHue, int start, int end ); + void ComputeSkinMatrix( mstudioboneweight_t *boneweights, matrix3x4 &result ); + void ComputeWeightColor( mstudioboneweight_t *boneweights, Vector &result ); + void RemapTextures( void ); + void FreeModel (); + studiohdr_t *LoadModel( char *modelname ); + bool PostLoadModel ( char *modelname ); + bool SaveModel ( char *modelname ); + void DrawModel( bool bMirror = false ); + void DrawModelUVMap(); + void ConvertTexCoords( void ); + bool AdvanceFrame( float dt ); + + float GetInterval( void ); + float GetCycle( void ); + float GetFrame( void ); + int GetMaxFrame( void ); + int SetFrame( int nFrame ); + void ClientEvents( void ); + + void ExtractBbox( Vector &mins, Vector &maxs ); + + void SetOffset2D( float x, float y ) { offset_x = x, offset_y = y; } + int SetSequence( int iSequence ); + int GetSequence( void ); + void GetSequenceInfo( float *pflFrameRate, float *pflGroundSpeed ); + void GetMovement( float &prevCycle, Vector &vecPos, Vector &vecAngles ); + float GetDuration( int iSequence ); + float GetDuration( void ); + void SetBlendValue( int iBlender, int iValue ); + float SetController( int iController, float flValue ); + + int LookupPoseParameter( char const *szName ); + float SetPoseParameter( int iParameter, float flValue ); + float SetPoseParameter( char const *szName, float flValue ); + float GetPoseParameter( char const *szName ); + float GetPoseParameter( int iParameter ); + bool GetPoseParameterRange( int iParameter, float *pflMin, float *pflMax ); + float* GetPoseParameters(); + + float SetMouth( float flValue ); + float SetBlending( int iBlender, float flValue ); + int SetBodygroup( int iGroup, int iValue ); + int SetSkin( int iValue ); + bool SetEditType( int iType ); + bool SetEditMode( int iMode ); + bool AddEditField( int type, int id ); + void ReadEditField( studiohdr_t *phdr, edit_field_t *ed ); + void WriteEditField( studiohdr_t *phdr, edit_field_t *ed ); + void UpdateEditFields( bool write_to_model ); + + void SetTopColor( int color ); + void SetBottomColor( int color ); + + void scaleMeshes (float scale); + void scaleBones (float scale); + + void centerView( bool reset ); + void updateModel( void ); + void editPosition( float step, int type ); + edit_field_t *editField( void ) { return m_pedit; } + const char *getQCcode( void ); + + float m_prevGroundCycle; +private: + // global settings + float m_flTime; + float m_flFrameTime; + + // entity settings + int m_sequence; // sequence index + float m_dt; + float m_cycle; // 0 to 1 animation playback index + float m_prevcycle; + int m_bodynum; // bodypart selection + int m_skinnum; // skin group selection + byte m_controller[8]; // bone controllers + byte m_blending[2]; // animation blending + byte m_mouth; // mouth position + bool m_owntexmodel; // do we have a modelT.mdl ? + float offset_x, offset_y; + bool remap_textures; + int update_model; // set to true if model was edited + bool sequence_reset; + int m_iFramecounter; + + // muzzle data + muzzleflash_t m_muzzleflash[MAX_MUZZLEFLASHES]; + int m_current_muzzle; + + void DrawSpriteQuad( const Vector &org, const Vector &right, const Vector &up, float scale ); + void MuzzleFlash( int attachment, int type ); + void RenderMuzzleFlash( muzzleflash_t *muzzle ); + + // debug + void drawTransparentBox( Vector const &bbmin, Vector const &bbmax, const matrix3x4 &m, float const *color = NULL ); + void drawTransform( matrix3x4 &m, float flLength = 4.0f ); + + // internal data + studiohdr_t *m_pstudiohdr; + mstudiomodel_t *m_pmodel; + + studiohdr_t *m_ptexturehdr; + studiohdr_t *m_panimhdr[32]; + + float m_adj[MAXSTUDIOCONTROLLERS]; + + float m_poseparameter[MAXSTUDIOPOSEPARAM]; + + Vector m_pbonecolor[MAXSTUDIOBONES]; // animated is blue, procedural is green, jiggle is orange + matrix3x4 m_pbonetransform[MAXSTUDIOBONES]; // bone transformation matrix + matrix3x4 m_plocaltransform[MAXSTUDIOBONES]; + matrix3x4 m_pworldtransform[MAXSTUDIOBONES]; + + blend_sequence_t m_seqblend[MAX_SEQBLENDS]; + int m_current_seqblend; + + CJiggleBones *m_pJiggleBones; + + CIKContext m_ik; + + edit_field_t m_editfields[MAX_EDITFIELDS]; + int m_numeditfields; + edit_field_t *m_pedit; + + void SetupTransform( bool bMirror = false ); + + void BlendSequence( Vector pos[], Vector4D q[], blend_sequence_t *seqblend ); + void LoadLocalMatrix( int bone, mstudioboneinfo_t *boneinfo ); + void SetUpBones( bool bMirror = false ); + + void DrawPoints( bool wireframe = false ); + void DrawUVMapPoints(); + + void Lighting( Vector &lv, int bone, int flags, const Vector &normal ); + void Chrome( Vector2D &chrome, int bone, const Vector &normal ); + + void SetupLighting( void ); + void SetupModel( int bodypart ); +}; + +extern StudioModel g_studioModel; +extern CBaseBoneSetup g_boneSetup; + +#endif // INCLUDED_STUDIOMODEL \ No newline at end of file diff --git a/utils/hlmv/ViewerSettings.cpp b/utils/hlmv/ViewerSettings.cpp new file mode 100644 index 0000000..6ba3851 --- /dev/null +++ b/utils/hlmv/ViewerSettings.cpp @@ -0,0 +1,370 @@ +// +// Half-Life Model Viewer (c) 1999 by Mete Ciragan +// +// file: ViewerSettings.cpp +// last modified: May 29 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +// version: 1.2 +// +// email: mete@swissquake.ch +// web: http://www.swissquake.ch/chumbalum-soft/ +// +#include "ViewerSettings.h" +#include +#include +#include +#include +#include +#include "StudioModel.h" +#include "stringlib.h" +#include +#include "conprint.h" + +ViewerSettings g_viewerSettings; + +void InitViewerSettings( void ) +{ + memset (&g_viewerSettings, 0, sizeof (ViewerSettings)); + + g_viewerSettings.renderMode = RM_TEXTURED; + g_viewerSettings.transparency = 1.0f; + g_viewerSettings.movementScale = 1.0f; + g_viewerSettings.enableIK = false; + + g_viewerSettings.bgColor[0] = 0.5f; + g_viewerSettings.bgColor[1] = 0.5f; + g_viewerSettings.bgColor[2] = 0.5f; + + g_viewerSettings.gColor[0] = 0.85f; + g_viewerSettings.gColor[1] = 0.85f; + g_viewerSettings.gColor[2] = 0.69f; + + g_viewerSettings.lColor[0] = 1.0f; + g_viewerSettings.lColor[1] = 1.0f; + g_viewerSettings.lColor[2] = 1.0f; + + g_viewerSettings.gLightVec[0] = 0.0f; + g_viewerSettings.gLightVec[1] = 0.0f; + g_viewerSettings.gLightVec[2] = -1.0f; + + g_viewerSettings.speedScale = 1.0f; + g_viewerSettings.textureLimit = 256; + g_viewerSettings.showGround = 0; + g_viewerSettings.showMaximized = 0; + + g_viewerSettings.textureScale = 1.0f; + g_viewerSettings.sequence_autoplay = true; + g_viewerSettings.studio_blendweights = true; + g_viewerSettings.topcolor = 0; + g_viewerSettings.bottomcolor = 0; + + g_viewerSettings.editStep = 1.0f; + g_viewerSettings.editMode = EDIT_SOURCE; + g_viewerSettings.editSize = false; + + // init random generator + srand( (unsigned)time( NULL ) ); +} + +bool InitRegistry( void ) +{ + return mx::regCreateKey( HKEY_CURRENT_USER, "Software\\XashXT Group\\Paranoia 2 ModelViewer" ); +} + +bool SaveString( const char *pKey, char *pValue ) +{ + return mx::regSetValue( HKEY_CURRENT_USER, "Software\\XashXT Group\\Paranoia 2 ModelViewer", pKey, pValue ); +} + +bool LoadString( const char *pKey, char *pValue ) +{ + return mx::regGetValue( HKEY_CURRENT_USER, "Software\\XashXT Group\\Paranoia 2 ModelViewer", pKey, pValue ); +} + +void SaveVector4D( const char *pName, float *pValue ) +{ + char str[256]; + + mx_snprintf( str, sizeof( str ), "%g %g %g %g", pValue[0], pValue[1], pValue[2], pValue[3] ); + SaveString( pName, str ); +} + +void LoadVector4D( const char *pName, float *pValue ) +{ + char str[256]; + + if( LoadString( pName, str )) + { + sscanf( str, "%g %g %g %g", &pValue[0], &pValue[1], &pValue[2], &pValue[3] ); + } +} + +void SaveVector3D( const char *pName, float *pValue ) +{ + char str[256]; + + mx_snprintf( str, sizeof( str ), "%g %g %g", pValue[0], pValue[1], pValue[2] ); + SaveString( pName, str ); +} + +void LoadVector3D( const char *pName, float *pValue ) +{ + char str[256]; + + if( LoadString( pName, str )) + { + sscanf( str, "%g %g %g", &pValue[0], &pValue[1], &pValue[2] ); + } +} + +void SaveInt( const char *pName, int iValue ) +{ + char str[256]; + + mx_snprintf( str, sizeof( str ), "%d", iValue ); + SaveString( pName, str ); +} + +void LoadInt( const char *pName, int *iValue ) +{ + char str[256]; + + if( LoadString( pName, str )) + { + sscanf( str, "%d", iValue ); + } +} + +void SaveFloat( const char *pName, float fValue ) +{ + char str[256]; + + mx_snprintf( str, sizeof( str ), "%f", fValue ); + SaveString( pName, str ); +} + +void LoadFloat( const char *pName, float *fValue ) +{ + char str[256]; + + if( LoadString( pName, str )) + { + sscanf( str, "%f", fValue ); + } +} + +int LoadViewerSettings( void ) +{ + InitViewerSettings (); + + LoadVector4D( "Background Color", g_viewerSettings.bgColor ); + LoadVector4D( "Light Color", g_viewerSettings.lColor ); + LoadVector4D( "Ground Color", g_viewerSettings.gColor ); + LoadVector3D( "Light Vector", g_viewerSettings.gLightVec ); + LoadInt( "Sequence AutoPlay", &g_viewerSettings.sequence_autoplay ); + LoadInt( "Studio BlendWeights", &g_viewerSettings.studio_blendweights ); + LoadInt( "Top Color", &g_viewerSettings.topcolor ); + LoadInt( "Bottom Color", &g_viewerSettings.bottomcolor ); + LoadFloat( "Edit Step", &g_viewerSettings.editStep ); + LoadInt( "Edit Mode", &g_viewerSettings.editMode ); + LoadInt( "Allow IK", &g_viewerSettings.enableIK ); + LoadInt( "Show Ground", &g_viewerSettings.showGround ); + LoadString( "Ground TexPath", g_viewerSettings.groundTexFile ); + LoadInt( "Show Maximized", &g_viewerSettings.showMaximized ); + + return 1; +} + +int SaveViewerSettings( void ) +{ + if( !InitRegistry( )) + return 0; + + SaveVector4D( "Background Color", g_viewerSettings.bgColor ); + SaveVector4D( "Light Color", g_viewerSettings.lColor ); + SaveVector4D( "Ground Color", g_viewerSettings.gColor ); + SaveVector3D( "Light Vector", g_viewerSettings.gLightVec ); + SaveInt( "Sequence AutoPlay", g_viewerSettings.sequence_autoplay ); + SaveInt( "Studio BlendWeights", g_viewerSettings.studio_blendweights ); + SaveInt( "Top Color", g_viewerSettings.topcolor ); + SaveInt( "Bottom Color", g_viewerSettings.bottomcolor ); + SaveFloat( "Edit Step", g_viewerSettings.editStep ); + SaveInt( "Edit Mode", g_viewerSettings.editMode ); + SaveInt( "Allow IK", g_viewerSettings.enableIK ); + SaveInt( "Show Ground", g_viewerSettings.showGround ); + SaveString( "Ground TexPath", g_viewerSettings.groundTexFile ); + SaveInt( "Show Maximized", g_viewerSettings.showMaximized ); + + return 1; +} + +bool IsAliasModel( const char *path ) +{ + byte buffer[256]; + FILE *fp; + + if( !path ) return false; + + // load the model + if(( fp = fopen( path, "rb" )) == NULL ) + return false; + + fread( buffer, sizeof( buffer ), 1, fp ); + fclose( fp ); + + if( ftell( fp ) < sizeof( 84 )) + return false; + + // skip invalid signature + if( Q_strncmp((const char *)buffer, "IDPO", 4 )) + return false; + + // skip unknown version + if( *(int *)&buffer[4] != 6 ) + return false; + + return true; +} + +static bool ValidateModel( const char *path ) +{ + byte buffer[256]; + studiohdr_t *phdr; + FILE *fp; + + if( !path ) return false; + + // load the model + if(( fp = fopen( path, "rb" )) == NULL ) + return false; + + fread( buffer, sizeof( buffer ), 1, fp ); + fclose( fp ); + + if( ftell( fp ) < sizeof( studiohdr_t )) + return false; + + // skip invalid signature + if( Q_strncmp((const char *)buffer, "IDST", 4 )) + return false; + + phdr = (studiohdr_t *)buffer; + + // skip unknown version + if( phdr->version != STUDIO_VERSION ) + return false; + + // skip modelnameT.mdl + if( phdr->numbones <= 0 ) + return false; + + return true; +} + +static void AddPathToList( const char *path ) +{ + char modelPath[256]; + + if( g_viewerSettings.numModelPathes >= 2048 ) + return; // too many strings + + Q_snprintf( modelPath, sizeof( modelPath ), "%s\\%s", g_viewerSettings.oldModelPath, path ); + + if( !ValidateModel( modelPath )) + return; + + int i = g_viewerSettings.numModelPathes++; + + Q_strncpy( g_viewerSettings.modelPathList[i], modelPath, sizeof( g_viewerSettings.modelPathList[0] )); +} + +static void SortPathList( void ) +{ + char temp[256]; + int i, j; + + // this is a selection sort (finds the best entry for each slot) + for( i = 0; i < g_viewerSettings.numModelPathes - 1; i++ ) + { + for( j = i + 1; j < g_viewerSettings.numModelPathes; j++ ) + { + if( Q_strcmp( g_viewerSettings.modelPathList[i], g_viewerSettings.modelPathList[j] ) > 0 ) + { + Q_strncpy( temp, g_viewerSettings.modelPathList[i], sizeof( temp )); + Q_strncpy( g_viewerSettings.modelPathList[i], g_viewerSettings.modelPathList[j], sizeof( temp )); + Q_strncpy( g_viewerSettings.modelPathList[j], temp, sizeof( temp )); + } + } + } +} + +void ListDirectory( void ) +{ + char modelPath[256]; + struct _finddata_t n_file; + long hFile; + + COM_ExtractFilePath( g_viewerSettings.modelPath, modelPath ); + + if( !Q_stricmp( modelPath, g_viewerSettings.oldModelPath )) + return; // not changed + + Q_strncpy( g_viewerSettings.oldModelPath, modelPath, sizeof( g_viewerSettings.oldModelPath )); + Q_strncat( modelPath, "\\*.mdl", sizeof( modelPath )); + g_viewerSettings.numModelPathes = 0; + + // ask for the directory listing handle + hFile = _findfirst( modelPath, &n_file ); + if( hFile == -1 ) return; // how this possible? + + // start a new chain with the the first name + AddPathToList( n_file.name ); + + // iterate through the directory + while( _findnext( hFile, &n_file ) == 0 ) + AddPathToList( n_file.name ); + _findclose( hFile ); + + SortPathList(); +} + +const char *LoadNextModel( void ) +{ + int i; + + for( i = 0; i < g_viewerSettings.numModelPathes; i++ ) + { + if( !Q_stricmp( g_viewerSettings.modelPathList[i], g_viewerSettings.modelPath )) + { + i++; + break; + } + } + + if( i == g_viewerSettings.numModelPathes ) + i = 0; + return g_viewerSettings.modelPathList[i]; +} + +const char *LoadPrevModel( void ) +{ + int i; + + for( i = 0; i < g_viewerSettings.numModelPathes; i++ ) + { + if( !Q_stricmp( g_viewerSettings.modelPathList[i], g_viewerSettings.modelPath )) + { + i--; + break; + } + } + + if( i < 0 ) i = g_viewerSettings.numModelPathes - 1; + return g_viewerSettings.modelPathList[i]; +} \ No newline at end of file diff --git a/utils/hlmv/ViewerSettings.h b/utils/hlmv/ViewerSettings.h new file mode 100644 index 0000000..56ebb05 --- /dev/null +++ b/utils/hlmv/ViewerSettings.h @@ -0,0 +1,138 @@ +// +// Half-Life Model Viewer (c) 1999 by Mete Ciragan +// +// file: ViewerSettings.h +// last modified: May 29 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +// version: 1.2 +// +// email: mete@swissquake.ch +// web: http://www.swissquake.ch/chumbalum-soft/ +// +#ifndef INCLUDED_VIEWERSETTINGS +#define INCLUDED_VIEWERSETTINGS + +#include "vector.h" + +enum // render modes +{ + RM_WIREFRAME, + RM_FLATSHADED, + RM_SMOOTHSHADED, + RM_TEXTURED, + RM_BONEWEIGHTS +}; + +enum +{ + EDIT_SOURCE = 0, + EDIT_MODEL, +}; + +typedef struct +{ + // model + Vector rot; + Vector trans; + float movementScale; + float editStep; + int editMode; + bool editSize; + + // render + int renderMode; + float transparency; + bool showBackground; + int showGround; + bool showHitBoxes; + bool showBones; + bool showTexture; + bool showAttachments; + bool showNormals; + bool showWireframeOverlay; + int enableIK; + int texture; + float textureScale; + int skin; + bool mirror; + bool useStencil; // if 3dfx fullscreen set false + bool pending_export_uvmap; + + // animation + int sequence; + float speedScale; + + // bodyparts and bonecontrollers + int submodels[32]; + + // fullscreen + int width, height; + bool cds; + int showMaximized; + + // colors + float bgColor[4]; + float lColor[4]; + float gColor[4]; + float gLightVec[3]; + + int sequence_autoplay; + int studio_blendweights; + int topcolor; + int bottomcolor; + + bool show_uv_map; + bool overlay_uv_map; + bool anti_alias_lines; + + // misc + int textureLimit; + bool pause; + int drawn_polys; + int numModelChanges; // if user touch settings that directly stored into the model + int numSourceChanges; // editor counter + + // only used for fullscreen mode + char modelFile[256]; + char modelPath[256]; + char oldModelPath[256]; + char backgroundTexFile[256]; + char groundTexFile[256]; + char uvmapPath[256]; + + char modelPathList[2048][256]; + int numModelPathes; +} ViewerSettings; + + + +extern ViewerSettings g_viewerSettings; + +bool InitRegistry( void ); +bool SaveString( const char *pKey, char *pValue ); +bool LoadString( const char *pKey, char *pValue ); + +#ifdef __cplusplus +extern "C" { +#endif + +void InitViewerSettings (void); +int LoadViewerSettings (void); +int SaveViewerSettings (void); +void ListDirectory( void ); +const char *LoadNextModel( void ); +const char *LoadPrevModel( void ); +bool IsAliasModel( const char *path ); +#ifdef __cplusplus +} +#endif + + + +#endif // INCLUDED_VIEWERSETTINGS \ No newline at end of file diff --git a/utils/hlmv/activity.h b/utils/hlmv/activity.h new file mode 100644 index 0000000..68d64bd --- /dev/null +++ b/utils/hlmv/activity.h @@ -0,0 +1,160 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef ACTIVITY_H +#define ACTIVITY_H + +typedef enum +{ + ACT_RESET = 0, // Set m_Activity to this invalid value to force a reset to m_IdealActivity + ACT_IDLE = 1, + ACT_GUARD, + ACT_WALK, + ACT_RUN, + ACT_FLY, // Fly (and flap if appropriate) + ACT_SWIM, + ACT_HOP, // vertical jump + ACT_LEAP, // long forward jump + ACT_FALL, + ACT_LAND, + ACT_STRAFE_LEFT, + ACT_STRAFE_RIGHT, + ACT_ROLL_LEFT, // tuck and roll, left + ACT_ROLL_RIGHT, // tuck and roll, right + ACT_TURN_LEFT, // turn quickly left (stationary) + ACT_TURN_RIGHT, // turn quickly right (stationary) + ACT_CROUCH, // the act of crouching down from a standing position + ACT_CROUCHIDLE, // holding body in crouched position (loops) + ACT_STAND, // the act of standing from a crouched position + ACT_USE, + ACT_SIGNAL1, + ACT_SIGNAL2, + ACT_SIGNAL3, + ACT_TWITCH, + ACT_COWER, + ACT_SMALL_FLINCH, + ACT_BIG_FLINCH, + ACT_RANGE_ATTACK1, + ACT_RANGE_ATTACK2, + ACT_MELEE_ATTACK1, + ACT_MELEE_ATTACK2, + ACT_RELOAD, + ACT_ARM, // pull out gun, for instance + ACT_DISARM, // reholster gun + ACT_EAT, // monster chowing on a large food item (loop) + ACT_DIESIMPLE, + ACT_DIEBACKWARD, + ACT_DIEFORWARD, + ACT_DIEVIOLENT, + ACT_BARNACLE_HIT, // barnacle tongue hits a monster + ACT_BARNACLE_PULL, // barnacle is lifting the monster ( loop ) + ACT_BARNACLE_CHOMP, // barnacle latches on to the monster + ACT_BARNACLE_CHEW, // barnacle is holding the monster in its mouth ( loop ) + ACT_SLEEP, + ACT_INSPECT_FLOOR, // for active idles, look at something on or near the floor + ACT_INSPECT_WALL, // for active idles, look at something directly ahead of you ( doesn't HAVE to be a wall or on a wall ) + ACT_IDLE_ANGRY, // alternate idle animation in which the monster is clearly agitated. (loop) + ACT_WALK_HURT, // limp (loop) + ACT_RUN_HURT, // limp (loop) + ACT_HOVER, // Idle while in flight + ACT_GLIDE, // Fly (don't flap) + ACT_FLY_LEFT, // Turn left in flight + ACT_FLY_RIGHT, // Turn right in flight + ACT_DETECT_SCENT, // this means the monster smells a scent carried by the air + ACT_SNIFF, // this is the act of actually sniffing an item in front of the monster + ACT_BITE, // some large monsters can eat small things in one bite. This plays one time, EAT loops. + ACT_THREAT_DISPLAY, // without attacking, monster demonstrates that it is angry. (Yell, stick out chest, etc ) + ACT_FEAR_DISPLAY, // monster just saw something that it is afraid of + ACT_EXCITED, // for some reason, monster is excited. Sees something he really likes to eat, or whatever. + ACT_SPECIAL_ATTACK1, // very monster specific special attacks. + ACT_SPECIAL_ATTACK2, + ACT_COMBAT_IDLE, // agitated idle. + ACT_WALK_SCARED, + ACT_RUN_SCARED, + ACT_VICTORY_DANCE, // killed a player, do a victory dance. + ACT_DIE_HEADSHOT, // die, hit in head. + ACT_DIE_CHESTSHOT, // die, hit in chest + ACT_DIE_GUTSHOT, // die, hit in gut + ACT_DIE_BACKSHOT, // die, hit in back + ACT_FLINCH_HEAD, + ACT_FLINCH_CHEST, + ACT_FLINCH_STOMACH, + ACT_FLINCH_LEFTARM, + ACT_FLINCH_RIGHTARM, + ACT_FLINCH_LEFTLEG, + ACT_FLINCH_RIGHTLEG, + + // acts for viewmodel + ACT_VM_NONE, // weapon viewmodel animations + ACT_VM_DEPLOY, // deploy + ACT_VM_DEPLOY_EMPTY, // deploy empty weapon + ACT_VM_HOLSTER, // holster empty weapon + ACT_VM_HOLSTER_EMPTY, + ACT_VM_IDLE, + ACT_VM_IDLE_IS, // IronSight animations + ACT_VM_RANGE_ATTACK, + ACT_VM_RANGE_ATTACK_IS, // IronSight animations + ACT_VM_MELEE_ATTACK, + ACT_VM_MELEE_ATTACK_IS, // IronSight animations + ACT_VM_SHOOT_LAST, + ACT_VM_SHOOT_LAST_IS, // IronSight animations + ACT_VM_LAST_MELEE_ATTACK, + ACT_VM_LAST_MELEE_ATTACK_IS, // IronSight animations + ACT_VM_START_RELOAD, + ACT_VM_START_RELOAD_IS, + ACT_VM_RELOAD, + ACT_VM_RELOAD_IS, + ACT_VM_RELOAD_EMPTY, + ACT_VM_RELOAD_EMPTY_IS, + ACT_VM_TURNON, // switch firemode on + ACT_VM_TURNOFF, // switch firemode off + ACT_VM_PUMP, // user animations + ACT_VM_PUMP_IS, + ACT_VM_PUMP_EMPTY, + ACT_VM_PUMP_EMPTY_IS, + ACT_VM_START_CHARGE, + ACT_VM_CHARGE, + ACT_VM_OVERLOAD, + ACT_VM_IDLE_EMPTY, + ACT_VM_IDLE_EMPTY_IS, + ACT_VM_IRONSIGHT_ON, + ACT_VM_IRONSIGHT_OFF, + ACT_VM_SHOOT_EMPTY, + ACT_VM_SHOOT_EMPTY_IS, + ACT_VM_IRONSIGHT_ON_EMPTY, + ACT_VM_IRONSIGHT_OFF_EMPTY, + ACT_VM_RESERVED0, // reserved acts for future expansions + ACT_VM_RESERVED1, + ACT_VM_RESERVED2, + ACT_VM_RESERVED3, + ACT_VM_RESERVED4, + + // continue enumerate acts for monsters + ACT_FLASHLIGHT, + ACT_WALKBACK_FIRE, + + ACT_FIRINGWALK, + ACT_FIRINGRUN, +} Activity; + +typedef struct +{ + int type; + char *name; +} activity_map_t; + +extern activity_map_t activity_map[]; + +#endif // ACTIVITY_H \ No newline at end of file diff --git a/utils/hlmv/anorms.h b/utils/hlmv/anorms.h new file mode 100644 index 0000000..8117ed3 --- /dev/null +++ b/utils/hlmv/anorms.h @@ -0,0 +1,162 @@ +{-0.525731f, 0.000000f, 0.850651f}, +{-0.442863f, 0.238856f, 0.864188f}, +{-0.295242f, 0.000000f, 0.955423f}, +{-0.309017f, 0.500000f, 0.809017f}, +{-0.162460f, 0.262866f, 0.951056f}, +{0.000000f, 0.000000f, 1.000000f}, +{0.000000f, 0.850651f, 0.525731f}, +{-0.147621f, 0.716567f, 0.681718f}, +{0.147621f, 0.716567f, 0.681718f}, +{0.000000f, 0.525731f, 0.850651f}, +{0.309017f, 0.500000f, 0.809017f}, +{0.525731f, 0.000000f, 0.850651f}, +{0.295242f, 0.000000f, 0.955423f}, +{0.442863f, 0.238856f, 0.864188f}, +{0.162460f, 0.262866f, 0.951056f}, +{-0.681718f, 0.147621f, 0.716567f}, +{-0.809017f, 0.309017f, 0.500000f}, +{-0.587785f, 0.425325f, 0.688191f}, +{-0.850651f, 0.525731f, 0.000000f}, +{-0.864188f, 0.442863f, 0.238856f}, +{-0.716567f, 0.681718f, 0.147621f}, +{-0.688191f, 0.587785f, 0.425325f}, +{-0.500000f, 0.809017f, 0.309017f}, +{-0.238856f, 0.864188f, 0.442863f}, +{-0.425325f, 0.688191f, 0.587785f}, +{-0.716567f, 0.681718f, -0.147621f}, +{-0.500000f, 0.809017f, -0.309017f}, +{-0.525731f, 0.850651f, 0.000000f}, +{0.000000f, 0.850651f, -0.525731f}, +{-0.238856f, 0.864188f, -0.442863f}, +{0.000000f, 0.955423f, -0.295242f}, +{-0.262866f, 0.951056f, -0.162460f}, +{0.000000f, 1.000000f, 0.000000f}, +{0.000000f, 0.955423f, 0.295242f}, +{-0.262866f, 0.951056f, 0.162460f}, +{0.238856f, 0.864188f, 0.442863f}, +{0.262866f, 0.951056f, 0.162460f}, +{0.500000f, 0.809017f, 0.309017f}, +{0.238856f, 0.864188f, -0.442863f}, +{0.262866f, 0.951056f, -0.162460f}, +{0.500000f, 0.809017f, -0.309017f}, +{0.850651f, 0.525731f, 0.000000f}, +{0.716567f, 0.681718f, 0.147621f}, +{0.716567f, 0.681718f, -0.147621f}, +{0.525731f, 0.850651f, 0.000000f}, +{0.425325f, 0.688191f, 0.587785f}, +{0.864188f, 0.442863f, 0.238856f}, +{0.688191f, 0.587785f, 0.425325f}, +{0.809017f, 0.309017f, 0.500000f}, +{0.681718f, 0.147621f, 0.716567f}, +{0.587785f, 0.425325f, 0.688191f}, +{0.955423f, 0.295242f, 0.000000f}, +{1.000000f, 0.000000f, 0.000000f}, +{0.951056f, 0.162460f, 0.262866f}, +{0.850651f, -0.525731f, 0.000000f}, +{0.955423f, -0.295242f, 0.000000f}, +{0.864188f, -0.442863f, 0.238856f}, +{0.951056f, -0.162460f, 0.262866f}, +{0.809017f, -0.309017f, 0.500000f}, +{0.681718f, -0.147621f, 0.716567f}, +{0.850651f, 0.000000f, 0.525731f}, +{0.864188f, 0.442863f, -0.238856f}, +{0.809017f, 0.309017f, -0.500000f}, +{0.951056f, 0.162460f, -0.262866f}, +{0.525731f, 0.000000f, -0.850651f}, +{0.681718f, 0.147621f, -0.716567f}, +{0.681718f, -0.147621f, -0.716567f}, +{0.850651f, 0.000000f, -0.525731f}, +{0.809017f, -0.309017f, -0.500000f}, +{0.864188f, -0.442863f, -0.238856f}, +{0.951056f, -0.162460f, -0.262866f}, +{0.147621f, 0.716567f, -0.681718f}, +{0.309017f, 0.500000f, -0.809017f}, +{0.425325f, 0.688191f, -0.587785f}, +{0.442863f, 0.238856f, -0.864188f}, +{0.587785f, 0.425325f, -0.688191f}, +{0.688191f, 0.587785f, -0.425325f}, +{-0.147621f, 0.716567f, -0.681718f}, +{-0.309017f, 0.500000f, -0.809017f}, +{0.000000f, 0.525731f, -0.850651f}, +{-0.525731f, 0.000000f, -0.850651f}, +{-0.442863f, 0.238856f, -0.864188f}, +{-0.295242f, 0.000000f, -0.955423f}, +{-0.162460f, 0.262866f, -0.951056f}, +{0.000000f, 0.000000f, -1.000000f}, +{0.295242f, 0.000000f, -0.955423f}, +{0.162460f, 0.262866f, -0.951056f}, +{-0.442863f, -0.238856f, -0.864188f}, +{-0.309017f, -0.500000f, -0.809017f}, +{-0.162460f, -0.262866f, -0.951056f}, +{0.000000f, -0.850651f, -0.525731f}, +{-0.147621f, -0.716567f, -0.681718f}, +{0.147621f, -0.716567f, -0.681718f}, +{0.000000f, -0.525731f, -0.850651f}, +{0.309017f, -0.500000f, -0.809017f}, +{0.442863f, -0.238856f, -0.864188f}, +{0.162460f, -0.262866f, -0.951056f}, +{0.238856f, -0.864188f, -0.442863f}, +{0.500000f, -0.809017f, -0.309017f}, +{0.425325f, -0.688191f, -0.587785f}, +{0.716567f, -0.681718f, -0.147621f}, +{0.688191f, -0.587785f, -0.425325f}, +{0.587785f, -0.425325f, -0.688191f}, +{0.000000f, -0.955423f, -0.295242f}, +{0.000000f, -1.000000f, 0.000000f}, +{0.262866f, -0.951056f, -0.162460f}, +{0.000000f, -0.850651f, 0.525731f}, +{0.000000f, -0.955423f, 0.295242f}, +{0.238856f, -0.864188f, 0.442863f}, +{0.262866f, -0.951056f, 0.162460f}, +{0.500000f, -0.809017f, 0.309017f}, +{0.716567f, -0.681718f, 0.147621f}, +{0.525731f, -0.850651f, 0.000000f}, +{-0.238856f, -0.864188f, -0.442863f}, +{-0.500000f, -0.809017f, -0.309017f}, +{-0.262866f, -0.951056f, -0.162460f}, +{-0.850651f, -0.525731f, 0.000000f}, +{-0.716567f, -0.681718f, -0.147621f}, +{-0.716567f, -0.681718f, 0.147621f}, +{-0.525731f, -0.850651f, 0.000000f}, +{-0.500000f, -0.809017f, 0.309017f}, +{-0.238856f, -0.864188f, 0.442863f}, +{-0.262866f, -0.951056f, 0.162460f}, +{-0.864188f, -0.442863f, 0.238856f}, +{-0.809017f, -0.309017f, 0.500000f}, +{-0.688191f, -0.587785f, 0.425325f}, +{-0.681718f, -0.147621f, 0.716567f}, +{-0.442863f, -0.238856f, 0.864188f}, +{-0.587785f, -0.425325f, 0.688191f}, +{-0.309017f, -0.500000f, 0.809017f}, +{-0.147621f, -0.716567f, 0.681718f}, +{-0.425325f, -0.688191f, 0.587785f}, +{-0.162460f, -0.262866f, 0.951056f}, +{0.442863f, -0.238856f, 0.864188f}, +{0.162460f, -0.262866f, 0.951056f}, +{0.309017f, -0.500000f, 0.809017f}, +{0.147621f, -0.716567f, 0.681718f}, +{0.000000f, -0.525731f, 0.850651f}, +{0.425325f, -0.688191f, 0.587785f}, +{0.587785f, -0.425325f, 0.688191f}, +{0.688191f, -0.587785f, 0.425325f}, +{-0.955423f, 0.295242f, 0.000000f}, +{-0.951056f, 0.162460f, 0.262866f}, +{-1.000000f, 0.000000f, 0.000000f}, +{-0.850651f, 0.000000f, 0.525731f}, +{-0.955423f, -0.295242f, 0.000000f}, +{-0.951056f, -0.162460f, 0.262866f}, +{-0.864188f, 0.442863f, -0.238856f}, +{-0.951056f, 0.162460f, -0.262866f}, +{-0.809017f, 0.309017f, -0.500000f}, +{-0.864188f, -0.442863f, -0.238856f}, +{-0.951056f, -0.162460f, -0.262866f}, +{-0.809017f, -0.309017f, -0.500000f}, +{-0.681718f, 0.147621f, -0.716567f}, +{-0.681718f, -0.147621f, -0.716567f}, +{-0.850651f, 0.000000f, -0.525731f}, +{-0.688191f, 0.587785f, -0.425325f}, +{-0.587785f, 0.425325f, -0.688191f}, +{-0.425325f, 0.688191f, -0.587785f}, +{-0.425325f, -0.688191f, -0.587785f}, +{-0.587785f, -0.425325f, -0.688191f}, +{-0.688191f, -0.587785f, -0.425325f}, diff --git a/utils/hlmv/help.txt b/utils/hlmv/help.txt new file mode 100644 index 0000000..d6367df --- /dev/null +++ b/utils/hlmv/help.txt @@ -0,0 +1,17 @@ +Âîò òåêñòû: + g - Show/hide ground + h - Show/hide hitboxes + o - Show/hide bones + 5 - Increase global transparency + 6 - Decrease global transparency + b - Show/hide background texture + s - Enable/disable stencil on mirror + m - Enable/disable mirror + v - Toggle between first-person and standard view + l - Put view model into left hand + 1 - Rendermode: wireframe + 2 - Rendermode: flat shaded + 3 - Rendermode: smooth shaded + 4 - Rendermode: texture shaded + -\+ - Increase/descrease animation speed + Space - Cycle through animation sequences \ No newline at end of file diff --git a/utils/hlmv/hlmv.dsp b/utils/hlmv/hlmv.dsp new file mode 100644 index 0000000..1ecc3fc --- /dev/null +++ b/utils/hlmv/hlmv.dsp @@ -0,0 +1,251 @@ +# Microsoft Developer Studio Project File - Name="hlmv" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=hlmv - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "hlmv.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "hlmv.mak" CFG="hlmv - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "hlmv - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "hlmv - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "hlmv - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\..\temp\hlmv\!release" +# PROP Intermediate_Dir "..\..\temp\hlmv\!release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O1 /I "..\mxtk" /I "..\..\game_shared" /I "..\..\engine" /I "..\..\common" /I "..\common" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "HAVE_SCALE" /FD /I /mxtk/include" " /c +# SUBTRACT CPP /Fr /YX +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o "NUL" /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o "NUL" /win32 +# ADD BASE RSC /l 0x807 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 msvcrt.lib user32.lib gdi32.lib winspool.lib comctl32.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib opengl32.lib glu32.lib winmm.lib ..\common\mxtk.lib /nologo /version:1.2 /entry:"mainCRTStartup" /subsystem:windows /pdb:none /machine:I386 /nodefaultlib:"libcmt" /libpath:"/mxtk/lib" /release /opt:nowin98 +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\hlmv\!release +InputPath=\Paranoia2\src_main\temp\hlmv\!release\hlmv.exe +SOURCE="$(InputPath)" + +"C:\Program Files\ModelViewer\hlmv.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\hlmv.exe "C:\Program Files\ModelViewer\hlmv.exe" + +# End Custom Build + +!ELSEIF "$(CFG)" == "hlmv - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\..\temp\hlmv\!debug" +# PROP Intermediate_Dir "..\..\temp\hlmv\!debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "..\mxtk" /I "..\..\game_shared" /I "..\..\engine" /I "..\..\common" /I "..\common" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FAs /FR /FD /I /mxtk/include" " /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o "NUL" /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o "NUL" /win32 +# ADD BASE RSC /l 0x807 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 msvcrtd.lib user32.lib gdi32.lib winspool.lib comctl32.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib opengl32.lib glu32.lib winmm.lib ..\common\mxtk.lib /nologo /version:1.1 /entry:"mainCRTStartup" /subsystem:windows /debug /machine:I386 /nodefaultlib:"libcmtd" /nodefaultlib:"msvcrt" /pdbtype:sept /libpath:"/mxtk/lib/" +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\hlmv\!debug +InputPath=\Paranoia2\src_main\temp\hlmv\!debug\hlmv.exe +SOURCE="$(InputPath)" + +"C:\Program Files\ModelViewer\hlmv.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\hlmv.exe "C:\Program Files\ModelViewer\hlmv.exe" + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "hlmv - Win32 Release" +# Name "hlmv - Win32 Debug" +# Begin Source File + +SOURCE=.\anorms.h +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\bone_setup.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\bs_defs.h +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\common.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\conprint.cpp +# End Source File +# Begin Source File + +SOURCE=.\ControlPanel.cpp +# End Source File +# Begin Source File + +SOURCE=.\ControlPanel.h +# End Source File +# Begin Source File + +SOURCE=.\FileAssociation.cpp +# End Source File +# Begin Source File + +SOURCE=.\FileAssociation.h +# End Source File +# Begin Source File + +SOURCE=.\GlWindow.cpp +# End Source File +# Begin Source File + +SOURCE=.\GlWindow.h +# End Source File +# Begin Source File + +SOURCE=.\hlmv.manifest +# End Source File +# Begin Source File + +SOURCE=.\hlmview.manifest +# End Source File +# Begin Source File + +SOURCE=.\hlmviewer.rc +# End Source File +# Begin Source File + +SOURCE=.\icon1.ico +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\ikcontext.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\jigglebones.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\jigglebones.h +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\mathlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\matrix.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\matrix.h +# End Source File +# Begin Source File + +SOURCE=.\mdlviewer.cpp +# End Source File +# Begin Source File + +SOURCE=.\mdlviewer.h +# End Source File +# Begin Source File + +SOURCE=.\pakviewer.cpp +# End Source File +# Begin Source File + +SOURCE=.\pakviewer.h +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\procbones.cpp +# End Source File +# Begin Source File + +SOURCE=.\resource.h +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\stringlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\stringlib.h +# End Source File +# Begin Source File + +SOURCE=.\studio_render.cpp +# End Source File +# Begin Source File + +SOURCE=.\studio_utils.cpp +# End Source File +# Begin Source File + +SOURCE=.\StudioModel.h +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\vector.h +# End Source File +# Begin Source File + +SOURCE=.\ViewerSettings.cpp +# End Source File +# Begin Source File + +SOURCE=.\ViewerSettings.h +# End Source File +# End Target +# End Project diff --git a/utils/hlmv/hlmv.manifest b/utils/hlmv/hlmv.manifest new file mode 100644 index 0000000..d0ab83a --- /dev/null +++ b/utils/hlmv/hlmv.manifest @@ -0,0 +1,11 @@ + + + + Paranoia 2 Modelviewer 1.2.5.0 + + + + + + + \ No newline at end of file diff --git a/utils/hlmv/hlmviewer.rc b/utils/hlmv/hlmviewer.rc new file mode 100644 index 0000000..3cb80a6 --- /dev/null +++ b/utils/hlmv/hlmviewer.rc @@ -0,0 +1,74 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +1 24 MOVEABLE PURE "hlmv.manifest" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// Deutsch (Schweiz) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_DES) +#ifdef _WIN32 +LANGUAGE LANG_GERMAN, SUBLANG_GERMAN_SWISS +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +MX_ICON ICON DISCARDABLE "icon1.ico" + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // Deutsch (Schweiz) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/utils/hlmv/icon1.ico b/utils/hlmv/icon1.ico new file mode 100644 index 0000000..9415232 Binary files /dev/null and b/utils/hlmv/icon1.ico differ diff --git a/utils/hlmv/mdlviewer.cpp b/utils/hlmv/mdlviewer.cpp new file mode 100644 index 0000000..d92c34b --- /dev/null +++ b/utils/hlmv/mdlviewer.cpp @@ -0,0 +1,701 @@ +// +// Half-Life Model Viewer (c) 1999 by Mete Ciragan +// +// file: mdlviewer.cpp +// last modified: Jun 03 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +// version: 1.2 +// +// email: mete@swissquake.ch +// web: http://www.swissquake.ch/chumbalum-soft/ +// +#include +#include +#include +#include +#include +#include +#include "mdlviewer.h" +#include "GlWindow.h" +#include "ControlPanel.h" +#include "StudioModel.h" +#include "pakviewer.h" +#include "FileAssociation.h" +#include "stringlib.h" + +MDLViewer *g_MDLViewer = 0; +char g_appTitle[] = "Paranoia 2 Model Viewer v1.31 stable"; +static char recentFiles[8][256] = { "", "", "", "", "", "", "", "" }; +extern bool bUseWeaponOrigin; +extern bool bUseWeaponLeftHand; + +void MDLViewer::initRecentFiles( void ) +{ + for (int i = 0; i < 8; i++) + { + if (strlen (recentFiles[i])) + { + mb->modify (IDC_FILE_RECENTMODELS1 + i, IDC_FILE_RECENTMODELS1 + i, recentFiles[i]); + } + else + { + mb->modify (IDC_FILE_RECENTMODELS1 + i, IDC_FILE_RECENTMODELS1 + i, "(empty)"); + mb->setEnabled (IDC_FILE_RECENTMODELS1 + i, false); + } + } +} + +void MDLViewer::loadRecentFiles( void ) +{ + char str[256]; + + for( int i = 0; i < 8; i++ ) + { + mx_snprintf( str, sizeof( str ), "RecentFile%i", i ); + if( !LoadString( str, recentFiles[i] )) + break; + } +} + +void MDLViewer::saveRecentFiles( void ) +{ + char str[256]; + + if( !InitRegistry( )) + return; + + for( int i = 0; i < 8; i++ ) + { + mx_snprintf( str, sizeof( str ), "RecentFile%i", i ); + if( !SaveString( str, recentFiles[i] )) + break; + } +} + +MDLViewer :: MDLViewer() : mxWindow( 0, 0, 0, 0, 0, g_appTitle, mxWindow::Normal ) +{ + // create menu stuff + mb = new mxMenuBar (this); + mxMenu *menuFile = new mxMenu (); + mxMenu *menuOptions = new mxMenu (); + mxMenu *menuView = new mxMenu (); + mxMenu *menuHelp = new mxMenu (); + + mb->addMenu ("File", menuFile); + mb->addMenu ("Options", menuOptions); + mb->addMenu ("Tools", menuView); + mb->addMenu ("Help", menuHelp); + + mxMenu *menuRecentModels = new mxMenu (); + menuRecentModels->add ("(empty)", IDC_FILE_RECENTMODELS1); + menuRecentModels->add ("(empty)", IDC_FILE_RECENTMODELS2); + menuRecentModels->add ("(empty)", IDC_FILE_RECENTMODELS3); + menuRecentModels->add ("(empty)", IDC_FILE_RECENTMODELS4); + + mxMenu *menuRecentPakFiles = new mxMenu (); + menuRecentPakFiles->add ("(empty)", IDC_FILE_RECENTPAKFILES1); + menuRecentPakFiles->add ("(empty)", IDC_FILE_RECENTPAKFILES2); + menuRecentPakFiles->add ("(empty)", IDC_FILE_RECENTPAKFILES3); + menuRecentPakFiles->add ("(empty)", IDC_FILE_RECENTPAKFILES4); + + menuFile->add ("Load Model...", IDC_FILE_LOADMODEL); + menuFile->add ("Save Model...", IDC_FILE_SAVEMODEL); + menuFile->addSeparator (); + menuFile->add ("Load Background Texture...", IDC_FILE_LOADBACKGROUNDTEX); + menuFile->add ("Load Ground Texture...", IDC_FILE_LOADGROUNDTEX); + menuFile->addSeparator (); + menuFile->add ("Unload Ground Texture", IDC_FILE_UNLOADGROUNDTEX); + menuFile->addSeparator (); + menuFile->add ("Open PAK file...", IDC_FILE_OPENPAKFILE); + menuFile->add ("Close PAK file", IDC_FILE_CLOSEPAKFILE); + menuFile->addSeparator (); + menuFile->addMenu ("Recent Models", menuRecentModels); + menuFile->addMenu ("Recent PAK files", menuRecentPakFiles); + menuFile->addSeparator (); + menuFile->add ("Exit", IDC_FILE_EXIT); + + menuOptions->add ("Background Color...", IDC_OPTIONS_COLORBACKGROUND); + menuOptions->add ("Ground Color...", IDC_OPTIONS_COLORGROUND); + menuOptions->add ("Light Color...", IDC_OPTIONS_COLORLIGHT); + menuOptions->addSeparator (); + menuOptions->add( "Sequence AutoPlay", IDC_OPTIONS_AUTOPLAY ); + menuOptions->add( "Using BoneWeights", IDC_OPTIONS_BLENDWEIGHTS ); + menuOptions->addSeparator (); + menuOptions->add( "Use weapon origin", IDC_OPTIONS_WEAPONORIGIN ); + menuOptions->add( "Weapon left-handed", IDC_OPTIONS_LEFTHAND ); + menuOptions->addSeparator (); + menuOptions->add ("Center View", IDC_OPTIONS_CENTERVIEW); + menuOptions->add ("Reset View", IDC_OPTIONS_RESETVIEW); +#ifdef WIN32 + menuOptions->addSeparator (); + menuOptions->add ("Make Screenshot...", IDC_OPTIONS_MAKESCREENSHOT); + //menuOptions->add ("Dump Model Info", IDC_OPTIONS_DUMP); +#endif + + menuView->add ("File Associations...", IDC_VIEW_FILEASSOCIATIONS); + +#ifdef WIN32 + menuHelp->add ("Goto Homepage...", IDC_HELP_GOTOHOMEPAGE); + menuHelp->addSeparator (); +#endif + menuHelp->add ("About...", IDC_HELP_ABOUT); + + mb->setChecked( IDC_OPTIONS_WEAPONORIGIN, bUseWeaponOrigin ); + mb->setChecked( IDC_OPTIONS_LEFTHAND, bUseWeaponLeftHand ); + mb->setChecked( IDC_OPTIONS_AUTOPLAY, g_viewerSettings.sequence_autoplay ? true : false ); + mb->setChecked( IDC_OPTIONS_BLENDWEIGHTS, g_viewerSettings.studio_blendweights ? true : false ); + + // create the OpenGL window + d_GlWindow = new GlWindow (this, 0, 0, 0, 0, "", mxWindow::Normal); +#ifdef WIN32 + SetWindowLong ((HWND) d_GlWindow->getHandle (), GWL_EXSTYLE, WS_EX_CLIENTEDGE); +#endif + + d_cpl = new ControlPanel (this); + d_cpl->setGlWindow (d_GlWindow); + d_GlWindow->setControlPanel(d_cpl); + g_GlWindow = d_GlWindow; + + // finally create the pakviewer window + d_PAKViewer = new PAKViewer (this); + g_FileAssociation = new FileAssociation (); + + loadRecentFiles (); + initRecentFiles (); + + setBounds (20, 20, 640, 540); + setVisible (true); + + if( g_viewerSettings.showGround ) + d_cpl->setShowGround (true); + + if( g_viewerSettings.groundTexFile[0] ) + d_GlWindow->loadTexture( g_viewerSettings.groundTexFile, TEXTURE_GROUND ); + else d_GlWindow->loadTexture( NULL, TEXTURE_GROUND ); +} + + + +MDLViewer::~MDLViewer () +{ + // grab some params in case that hasn't updates + if( d_cpl ) + g_viewerSettings.editStep = d_cpl->getEditStep(); + g_viewerSettings.showMaximized = isMaximized(); + + saveRecentFiles (); + SaveViewerSettings (); + g_studioModel.FreeModel (); +#ifdef WIN32 + DeleteFile ("midump.txt"); +#endif +} + +void MDLViewer :: checkboxSet( int id, bool bState ) +{ + mb->setChecked( id, bState ); +} + +int +MDLViewer::handleEvent (mxEvent *event) +{ + switch (event->event) + { + case mxEvent::Action: + { + switch (event->action) + { + case IDC_FILE_LOADMODEL: + { + const char *ptr = mxGetOpenFileName (this, 0, "*.mdl"); + if (ptr) + { + int i; + d_cpl->loadModel (ptr); + + for (i = 0; i < 4; i++) + { + if (!mx_strcasecmp (recentFiles[i], ptr)) + break; + } + + // swap existing recent file + if (i < 4) + { + char tmp[256]; + strcpy (tmp, recentFiles[0]); + strcpy (recentFiles[0], recentFiles[i]); + strcpy (recentFiles[i], tmp); + } + + // insert recent file + else + { + for (i = 3; i > 0; i--) + strcpy (recentFiles[i], recentFiles[i - 1]); + + strcpy (recentFiles[0], ptr); + } + + initRecentFiles (); + } + } + break; + + case IDC_FILE_SAVEMODEL: + { + char *ptr = (char *) mxGetSaveFileName (this, g_viewerSettings.modelPath, "*.mdl", g_viewerSettings.modelPath); + if (!ptr) + break; + + char filename[256]; + char ext[16]; + + strcpy( filename, ptr ); + strcpy( ext, mx_getextension( filename )); + if( mx_strcasecmp( ext, ".mdl" )) + strcat( filename, ".mdl" ); + + if( !g_studioModel.SaveModel( filename )) + { + mxMessageBox( this, "Error saving model.", g_appTitle, MX_MB_OK | MX_MB_ERROR ); + } + else + { + strcpy( g_viewerSettings.modelFile, filename ); + g_viewerSettings.numModelChanges = 0; // all the settings are handled + } + } + break; + + case IDC_FILE_LOADBACKGROUNDTEX: + case IDC_FILE_LOADGROUNDTEX: + { + const char *ptr = mxGetOpenFileName (this, 0, "*.bmp;*.tga;*.pcx"); + if (ptr) + { + int name = TEXTURE_UNUSED; + + if( event->action == IDC_FILE_LOADBACKGROUNDTEX ) + name = TEXTURE_BACKGROUND; + else if( event->action == IDC_FILE_LOADGROUNDTEX ) + name = TEXTURE_GROUND; + + if (d_GlWindow->loadTexture( ptr, name )) + { + if (event->action == IDC_FILE_LOADBACKGROUNDTEX) + d_cpl->setShowBackground (true); + else + d_cpl->setShowGround (true); + + } + else + mxMessageBox (this, "Error loading texture.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + } + } + break; + + case IDC_FILE_UNLOADGROUNDTEX: + { + d_GlWindow->loadTexture( NULL, TEXTURE_GROUND ); + d_cpl->setShowGround (false); + } + break; + + case IDC_FILE_OPENPAKFILE: + { + const char *ptr = mxGetOpenFileName (this, "\\sierra\\half-life\\valve", "*.pak"); + if (ptr) + { + int i; + + d_PAKViewer->openPAKFile (ptr); + + for (i = 4; i < 8; i++) + { + if (!mx_strcasecmp (recentFiles[i], ptr)) + break; + } + + // swap existing recent file + if (i < 8) + { + char tmp[256]; + strcpy (tmp, recentFiles[4]); + strcpy (recentFiles[4], recentFiles[i]); + strcpy (recentFiles[i], tmp); + } + + // insert recent file + else + { + for (i = 7; i > 4; i--) + strcpy (recentFiles[i], recentFiles[i - 1]); + + strcpy (recentFiles[4], ptr); + } + + initRecentFiles (); + + redraw (); + } + } + break; + + case IDC_FILE_CLOSEPAKFILE: + { + d_PAKViewer->closePAKFile (); + redraw (); + } + break; + + case IDC_FILE_RECENTMODELS1: + case IDC_FILE_RECENTMODELS2: + case IDC_FILE_RECENTMODELS3: + case IDC_FILE_RECENTMODELS4: + { + int i = event->action - IDC_FILE_RECENTMODELS1; + d_cpl->loadModel (recentFiles[i]); + + char tmp[256]; + strcpy (tmp, recentFiles[0]); + strcpy (recentFiles[0], recentFiles[i]); + strcpy (recentFiles[i], tmp); + + initRecentFiles (); + + redraw (); + } + break; + + case IDC_FILE_RECENTPAKFILES1: + case IDC_FILE_RECENTPAKFILES2: + case IDC_FILE_RECENTPAKFILES3: + case IDC_FILE_RECENTPAKFILES4: + { + int i = event->action - IDC_FILE_RECENTPAKFILES1 + 4; + d_PAKViewer->openPAKFile (recentFiles[i]); + + char tmp[256]; + strcpy (tmp, recentFiles[4]); + strcpy (recentFiles[4], recentFiles[i]); + strcpy (recentFiles[i], tmp); + + initRecentFiles (); + + redraw (); + } + break; + + case IDC_FILE_EXIT: + { + d_PAKViewer->closePAKFile (); + redraw (); + mx::quit (); + } + break; + + case IDC_OPTIONS_COLORBACKGROUND: + case IDC_OPTIONS_COLORGROUND: + case IDC_OPTIONS_COLORLIGHT: + { + float *cols[3] = { g_viewerSettings.bgColor, g_viewerSettings.gColor, g_viewerSettings.lColor }; + float *col = cols[event->action - IDC_OPTIONS_COLORBACKGROUND]; + int r = (int) (col[0] * 255.0f); + int g = (int) (col[1] * 255.0f); + int b = (int) (col[2] * 255.0f); + if (mxChooseColor (this, &r, &g, &b)) + { + col[0] = (float) r / 255.0f; + col[1] = (float) g / 255.0f; + col[2] = (float) b / 255.0f; + } + } + break; + + case IDC_OPTIONS_CENTERVIEW: + d_cpl->centerView (false); + break; + + case IDC_OPTIONS_RESETVIEW: + d_cpl->centerView (true); + break; + + case IDC_OPTIONS_MAKESCREENSHOT: + { + char *ptr = (char *)mxGetSaveFileName( this, "", "*.bmp" ); + if( ptr ) + { + if( !strstr( ptr, ".bmp" )) + strcat( ptr, ".bmp" ); + d_GlWindow->dumpViewport( ptr ); + } + } + break; + + case IDC_OPTIONS_WEAPONORIGIN: + bUseWeaponOrigin = !mb->isChecked( IDC_OPTIONS_WEAPONORIGIN ); + mb->setChecked( IDC_OPTIONS_WEAPONORIGIN, bUseWeaponOrigin ); + break; + + case IDC_OPTIONS_LEFTHAND: + bUseWeaponLeftHand = !mb->isChecked( IDC_OPTIONS_LEFTHAND ); + mb->setChecked( IDC_OPTIONS_LEFTHAND, bUseWeaponLeftHand ); + break; + + case IDC_OPTIONS_AUTOPLAY: + g_viewerSettings.sequence_autoplay = !mb->isChecked( IDC_OPTIONS_AUTOPLAY ); + mb->setChecked( IDC_OPTIONS_AUTOPLAY, g_viewerSettings.sequence_autoplay ? true : false ); + break; + + case IDC_OPTIONS_BLENDWEIGHTS: + g_viewerSettings.studio_blendweights = !mb->isChecked( IDC_OPTIONS_BLENDWEIGHTS ); + mb->setChecked( IDC_OPTIONS_BLENDWEIGHTS, g_viewerSettings.studio_blendweights ? true : false ); + break; + + case IDC_OPTIONS_DUMP: + d_cpl->dumpModelInfo (); + break; + + case IDC_VIEW_FILEASSOCIATIONS: + g_FileAssociation->setAssociation (0); + g_FileAssociation->setVisible (true); + break; + +#ifdef WIN32 + case IDC_HELP_GOTOHOMEPAGE: + ShellExecute (0, "open", "http://cs-mapping.com.ua/forum/forumdisplay.php?f=189", 0, 0, SW_SHOW); + break; +#endif + + case IDC_HELP_ABOUT: + mxMessageBox (this, + "Paranoia 2 Model Viewer v1.31 (c) 2019 by Unkle Mike\n" + "Based on original HLMV code by Mete Ciragan\n\n" + "Left-drag to rotate.\n" + "Right-drag to zoom.\n" + "Shift-left-drag to x-y-pan.\n" + "Ctrl-drag to move lights.\n\n" + "Build:\t" __DATE__ ".\n" + "Email:\tg-cont@rambler.ru\n" + "Web:\thttp://www.hlfx.ru/forum", "About Paranoia 2 Model Viewer", + MX_MB_OK | MX_MB_INFORMATION ); + break; + + } //switch (event->action) + + } // mxEvent::Action + break; + + case mxEvent::Size: + { + int w = event->width; + int h = event->height; + int y = mb->getHeight (); +#ifdef WIN32 +#define HEIGHT 120 +#else +#define HEIGHT 140 + h -= 40; +#endif + + if (d_PAKViewer->isVisible ()) + { + w -= 170; + d_PAKViewer->setBounds (w, y, 170, h); + } + + d_GlWindow->setBounds (0, y, w, h - HEIGHT); + d_cpl->setBounds (0, y + h - HEIGHT, w, HEIGHT); + } + break; + case mxEvent::KeyDown: + { + switch( (char)event->key ) + { + case 32: + { + static float lasttime = 0.0f; + + if( lasttime > g_studioModel.getCurrentTime( )) + break; + lasttime = g_studioModel.getCurrentTime() + 0.1f; + + int iSeq = g_studioModel.GetSequence(); + + if( iSeq == g_studioModel.SetSequence( iSeq + 1 )) + g_studioModel.SetSequence( 0 ); + } + break; + + case 27: + if( !getParent( )) // fullscreen mode ? + mx::quit(); + break; + case 37: + if( g_viewerSettings.numModelPathes > 0 ) + d_cpl->loadModel( LoadPrevModel( )); + break; + case 39: + if( g_viewerSettings.numModelPathes > 0 ) + d_cpl->loadModel( LoadNextModel( )); + break; + case VK_F5: + { + bool oldUseWeaponOrigin = bUseWeaponOrigin; + d_cpl->loadModel( g_viewerSettings.modelFile, false ); + bUseWeaponOrigin = oldUseWeaponOrigin; + break; + } + case 'g': + case 'ï': + g_viewerSettings.showGround = !g_viewerSettings.showGround; + if( !g_viewerSettings.showGround ) + g_viewerSettings.mirror = false; + break; + case 'h': + case 'ð': + g_viewerSettings.showHitBoxes = !g_viewerSettings.showHitBoxes; + break; + case 'o': + case 'ù': + g_viewerSettings.showBones = !g_viewerSettings.showBones; + break; + case '5': + g_viewerSettings.transparency -= 0.05f; + if( g_viewerSettings.transparency < 0.0f ) + g_viewerSettings.transparency = 0.0f; + break; + case '6': + g_viewerSettings.transparency += 0.05f; + if( g_viewerSettings.transparency > 1.0f ) + g_viewerSettings.transparency = 1.0f; + break; + case 'b': + case 'è': + g_viewerSettings.showBackground = !g_viewerSettings.showBackground; + break; + case 's': + case 'û': + g_viewerSettings.useStencil = !g_viewerSettings.useStencil; + break; + case 'm': + case 'ü': + g_viewerSettings.mirror = !g_viewerSettings.mirror; + if( g_viewerSettings.mirror ) + g_viewerSettings.showGround = true; + break; + case 'v': + case 'ì': + bUseWeaponOrigin = !mb->isChecked( IDC_OPTIONS_WEAPONORIGIN ); + mb->setChecked( IDC_OPTIONS_WEAPONORIGIN, bUseWeaponOrigin ); + break; + case 'l': + case 'ä': + bUseWeaponLeftHand = !mb->isChecked( IDC_OPTIONS_LEFTHAND ); + mb->setChecked( IDC_OPTIONS_LEFTHAND, bUseWeaponLeftHand ); + break; + + case 'w': + case 'ö': + g_viewerSettings.studio_blendweights = !mb->isChecked( IDC_OPTIONS_BLENDWEIGHTS ); + mb->setChecked( IDC_OPTIONS_BLENDWEIGHTS, g_viewerSettings.studio_blendweights ? true : false ); + break; + case '1': + case '2': + case '3': + case '4': + g_viewerSettings.renderMode = event->key - '1'; + break; + case '-': + g_viewerSettings.speedScale -= 0.1f; + if( g_viewerSettings.speedScale < 0.0f ) + g_viewerSettings.speedScale = 0.0f; + break; + case '+': + g_viewerSettings.speedScale += 0.1f; + if( g_viewerSettings.speedScale > 5.0f ) + g_viewerSettings.speedScale = 5.0f; + break; + } + } + break; + } // event->event + + return 1; +} + + + +void +MDLViewer::redraw () +{ + mxEvent event; + event.event = mxEvent::Size; + event.width = w2 (); + event.height = h2 (); + handleEvent (&event); +} + + + +int main( int argc, char *argv[] ) +{ + // + // make sure, we start in the right directory + // + mx_setcwd (mx::getApplicationPath ()); + atexit( Sys_CloseLog ); + + char cmdline[1024] = ""; + if (argc > 1) + { + strcpy (cmdline, argv[1]); + for (int i = 2; i < argc; i++) + { + strcat (cmdline, " "); + strcat (cmdline, argv[i]); + } + } + + if( IsAliasModel( cmdline ) && COM_FileExists( "q1mv.exe" )) + { + strcpy (cmdline, "q1mv.exe" ); + for (int i = 1; i < argc; i++) + { + strcat (cmdline, " "); + strcat (cmdline, argv[i]); + } + WinExec (cmdline, SW_SHOW); + return 0; + } + + LoadViewerSettings(); + + //mx::setDisplayMode (0, 0, 0); + mx::init (argc, argv); + g_MDLViewer = new MDLViewer (); + g_MDLViewer->setMenuBar (g_MDLViewer->getMenuBar ()); + g_MDLViewer->setBounds (20, 20, 640, 540); + g_MDLViewer->setVisible (true); + + if( g_viewerSettings.showMaximized ) + g_MDLViewer->Maximize(); + + if (Q_stristr (cmdline, ".mdl")) + { + g_ControlPanel->loadModel (cmdline); + } + + int ret = mx::run (); + + mx::cleanup (); + + return ret; +} diff --git a/utils/hlmv/mdlviewer.h b/utils/hlmv/mdlviewer.h new file mode 100644 index 0000000..4f658db --- /dev/null +++ b/utils/hlmv/mdlviewer.h @@ -0,0 +1,104 @@ +// +// Half-Life Model Viewer (c) 1999 by Mete Ciragan +// +// file: mdlviewer.h +// last modified: Apr 28 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +// version: 1.2 +// +// email: mete@swissquake.ch +// web: http://www.swissquake.ch/chumbalum-soft/ +// +#ifndef INCLUDED_MDLVIEWER +#define INCLUDED_MDLVIEWER + + + +#ifndef INCLUDED_MXWINDOW +#include +#endif + + + +#define IDC_FILE_LOADMODEL 1001 +#define IDC_FILE_SAVEMODEL 1002 +#define IDC_FILE_LOADBACKGROUNDTEX 1003 +#define IDC_FILE_LOADGROUNDTEX 1004 +#define IDC_FILE_UNLOADGROUNDTEX 1005 +#define IDC_FILE_OPENPAKFILE 1006 +#define IDC_FILE_OPENPAKFILE2 1007 +#define IDC_FILE_CLOSEPAKFILE 1008 +#define IDC_FILE_RECENTMODELS1 1009 +#define IDC_FILE_RECENTMODELS2 1010 +#define IDC_FILE_RECENTMODELS3 1011 +#define IDC_FILE_RECENTMODELS4 1012 +#define IDC_FILE_RECENTPAKFILES1 1013 +#define IDC_FILE_RECENTPAKFILES2 1014 +#define IDC_FILE_RECENTPAKFILES3 1015 +#define IDC_FILE_RECENTPAKFILES4 1016 +#define IDC_FILE_EXIT 1017 + +#define IDC_OPTIONS_COLORBACKGROUND 1101 +#define IDC_OPTIONS_COLORGROUND 1102 +#define IDC_OPTIONS_COLORLIGHT 1103 +#define IDC_OPTIONS_CENTERVIEW 1104 +#define IDC_OPTIONS_RESETVIEW 1105 +#define IDC_OPTIONS_MAKESCREENSHOT 1106 +#define IDC_OPTIONS_WEAPONORIGIN 1107 +#define IDC_OPTIONS_LEFTHAND 1108 +#define IDC_OPTIONS_AUTOPLAY 1109 +#define IDC_OPTIONS_BLENDWEIGHTS 1110 +#define IDC_OPTIONS_DUMP 1111 + +#define IDC_VIEW_FILEASSOCIATIONS 1201 + +#define IDC_HELP_GOTOHOMEPAGE 1301 +#define IDC_HELP_ABOUT 1302 + + + +class mxMenuBar; +class GlWindow; +class ControlPanel; +class PAKViewer; + + + +class MDLViewer : public mxWindow +{ + mxMenuBar *mb; + GlWindow *d_GlWindow; + ControlPanel *d_cpl; + PAKViewer *d_PAKViewer; + + void loadRecentFiles (); + void saveRecentFiles (); + void initRecentFiles (); + +public: + // CREATORS + MDLViewer (); + ~MDLViewer (); + + // MANIPULATORS + virtual int handleEvent (mxEvent *event); + void checkboxSet( int id, bool bState ); + void redraw (); + + // ACCESSORS + mxMenuBar *getMenuBar () const { return mb; } + GlWindow *getGlWindow () const { return d_GlWindow; } + PAKViewer *getPAKViewer () const { return d_PAKViewer; } + +}; + +extern MDLViewer *g_MDLViewer; +extern char g_appTitle[]; + +#endif // INCLUDED_MDLVIEWER \ No newline at end of file diff --git a/utils/hlmv/muzzle1.h b/utils/hlmv/muzzle1.h new file mode 100644 index 0000000..596f645 --- /dev/null +++ b/utils/hlmv/muzzle1.h @@ -0,0 +1,474 @@ +const unsigned char muzzleflash1_bmp[] = +{ + 0x42, 0x4d, 0x36, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, + 0x04, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf9, + 0xfd, 0xfd, 0x00, 0xe6, 0xfe, 0xfd, 0x00, 0xe3, 0xf8, 0xfc, 0x00, + 0xd6, 0xf6, 0xfc, 0x00, 0xf5, 0xf4, 0xf1, 0x00, 0xe8, 0xf2, 0xf3, + 0x00, 0xd5, 0xef, 0xf6, 0x00, 0xc2, 0xec, 0xf8, 0x00, 0xd2, 0xeb, + 0xf3, 0x00, 0xb6, 0xe9, 0xfb, 0x00, 0xc5, 0xe5, 0xf8, 0x00, 0xcc, + 0xe2, 0xf6, 0x00, 0xb4, 0xdd, 0xfe, 0x00, 0xc1, 0xde, 0xf6, 0x00, + 0xb6, 0xdf, 0xf7, 0x00, 0xa9, 0xdc, 0xfd, 0x00, 0x9e, 0xd7, 0xfa, + 0x00, 0xab, 0xd8, 0xee, 0x00, 0x99, 0xd4, 0xf1, 0x00, 0xa0, 0xd7, + 0xe3, 0x00, 0x96, 0xc9, 0xf5, 0x00, 0x9f, 0xc8, 0xeb, 0x00, 0x8c, + 0xc4, 0xf7, 0x00, 0x87, 0xc6, 0xed, 0x00, 0x8b, 0xc8, 0xe6, 0x00, + 0x84, 0xc0, 0xf6, 0x00, 0xa2, 0xc7, 0xdc, 0x00, 0x86, 0xbe, 0xee, + 0x00, 0x88, 0xbc, 0xe2, 0x00, 0x8a, 0xbf, 0xd8, 0x00, 0x82, 0xb2, + 0xf1, 0x00, 0x7f, 0xb3, 0xe1, 0x00, 0x76, 0xb1, 0xe4, 0x00, 0x67, + 0xab, 0xeb, 0x00, 0x7b, 0xae, 0xd8, 0x00, 0x72, 0xa6, 0xea, 0x00, + 0x6e, 0xa3, 0xdf, 0x00, 0x60, 0xa1, 0xe7, 0x00, 0x6f, 0xa3, 0xd7, + 0x00, 0x63, 0xa2, 0xd9, 0x00, 0x78, 0xa0, 0xc5, 0x00, 0x60, 0x98, + 0xd9, 0x00, 0x6d, 0x99, 0xd0, 0x00, 0x71, 0xa1, 0xbe, 0x00, 0x6b, + 0x9b, 0xc7, 0x00, 0x60, 0x96, 0xd0, 0x00, 0x61, 0x98, 0xc7, 0x00, + 0x5b, 0x8c, 0xd0, 0x00, 0x6c, 0x95, 0xb8, 0x00, 0x62, 0x96, 0xb9, + 0x00, 0x4f, 0x89, 0xce, 0x00, 0x5a, 0x8a, 0xc2, 0x00, 0x5e, 0x89, + 0xb7, 0x00, 0x4e, 0x84, 0xc3, 0x00, 0x4c, 0x80, 0xcb, 0x00, 0x59, + 0x82, 0xb2, 0x00, 0x48, 0x7e, 0xbf, 0x00, 0x5b, 0x83, 0xa8, 0x00, + 0x57, 0x79, 0xa9, 0x00, 0x45, 0x76, 0xb3, 0x00, 0x3c, 0x76, 0xb2, + 0x00, 0x4e, 0x77, 0xa9, 0x00, 0x48, 0x72, 0xa5, 0x00, 0x4f, 0x76, + 0x98, 0x00, 0x3a, 0x6f, 0xa5, 0x00, 0x43, 0x70, 0x99, 0x00, 0x4d, + 0x6b, 0x96, 0x00, 0x41, 0x67, 0x9a, 0x00, 0x2b, 0x65, 0xa1, 0x00, + 0x43, 0x67, 0x8f, 0x00, 0x3c, 0x62, 0x9a, 0x00, 0x34, 0x5d, 0x98, + 0x00, 0x44, 0x62, 0x83, 0x00, 0x3c, 0x5e, 0x8d, 0x00, 0x3a, 0x60, + 0x82, 0x00, 0x26, 0x55, 0x99, 0x00, 0x33, 0x58, 0x8a, 0x00, 0x2b, + 0x57, 0x8d, 0x00, 0x37, 0x5b, 0x7a, 0x00, 0x2d, 0x56, 0x87, 0x00, + 0x3c, 0x56, 0x80, 0x00, 0x29, 0x59, 0x7b, 0x00, 0x32, 0x53, 0x80, + 0x00, 0x22, 0x50, 0x86, 0x00, 0x35, 0x53, 0x76, 0x00, 0x2a, 0x50, + 0x7c, 0x00, 0x34, 0x4e, 0x74, 0x00, 0x2a, 0x4a, 0x77, 0x00, 0x2c, + 0x4e, 0x6e, 0x00, 0x1e, 0x47, 0x7c, 0x00, 0x34, 0x49, 0x6a, 0x00, + 0x23, 0x47, 0x6b, 0x00, 0x2c, 0x45, 0x6b, 0x00, 0x29, 0x44, 0x63, + 0x00, 0x20, 0x3e, 0x6a, 0x00, 0x26, 0x43, 0x5c, 0x00, 0x33, 0x3f, + 0x5c, 0x00, 0x12, 0x3a, 0x6a, 0x00, 0x22, 0x3c, 0x5d, 0x00, 0x1c, + 0x3c, 0x5b, 0x00, 0x22, 0x37, 0x55, 0x00, 0x15, 0x37, 0x58, 0x00, + 0x0f, 0x31, 0x61, 0x00, 0x1f, 0x36, 0x4e, 0x00, 0x19, 0x2f, 0x59, + 0x00, 0x24, 0x36, 0x46, 0x00, 0x0f, 0x31, 0x54, 0x00, 0x1b, 0x30, + 0x4e, 0x00, 0x17, 0x31, 0x4c, 0x00, 0x0f, 0x2a, 0x58, 0x00, 0x11, + 0x2e, 0x4d, 0x00, 0x13, 0x2f, 0x47, 0x00, 0x1f, 0x2f, 0x42, 0x00, + 0x1b, 0x2f, 0x43, 0x00, 0x14, 0x2d, 0x40, 0x00, 0x16, 0x28, 0x48, + 0x00, 0x16, 0x28, 0x42, 0x00, 0x0b, 0x24, 0x48, 0x00, 0x1b, 0x2a, + 0x33, 0x00, 0x19, 0x27, 0x39, 0x00, 0x11, 0x24, 0x41, 0x00, 0x0b, + 0x22, 0x40, 0x00, 0x0a, 0x23, 0x3b, 0x00, 0x10, 0x22, 0x39, 0x00, + 0x18, 0x24, 0x31, 0x00, 0x10, 0x23, 0x33, 0x00, 0x0a, 0x1e, 0x3c, + 0x00, 0x1a, 0x22, 0x28, 0x00, 0x09, 0x21, 0x30, 0x00, 0x09, 0x1a, + 0x3c, 0x00, 0x13, 0x1d, 0x2f, 0x00, 0x12, 0x20, 0x27, 0x00, 0x09, + 0x1d, 0x30, 0x00, 0x0f, 0x1c, 0x2e, 0x00, 0x05, 0x14, 0x3a, 0x00, + 0x16, 0x1d, 0x22, 0x00, 0x0e, 0x1b, 0x27, 0x00, 0x07, 0x18, 0x2f, + 0x00, 0x07, 0x17, 0x29, 0x00, 0x0b, 0x18, 0x25, 0x00, 0x0f, 0x13, + 0x2d, 0x00, 0x0e, 0x19, 0x1f, 0x00, 0x07, 0x17, 0x25, 0x00, 0x14, + 0x19, 0x19, 0x00, 0x08, 0x17, 0x21, 0x00, 0x07, 0x11, 0x29, 0x00, + 0x09, 0x17, 0x1a, 0x00, 0x08, 0x12, 0x21, 0x00, 0x0c, 0x12, 0x1c, + 0x00, 0x03, 0x0d, 0x29, 0x00, 0x0d, 0x12, 0x16, 0x00, 0x03, 0x0e, + 0x21, 0x00, 0x03, 0x10, 0x1c, 0x00, 0x07, 0x0e, 0x1c, 0x00, 0x06, + 0x10, 0x16, 0x00, 0x00, 0x0a, 0x21, 0x00, 0x0c, 0x0d, 0x13, 0x00, + 0x02, 0x0a, 0x1b, 0x00, 0x02, 0x0d, 0x15, 0x00, 0x05, 0x0b, 0x15, + 0x00, 0x0b, 0x0c, 0x0d, 0x00, 0x06, 0x0c, 0x0e, 0x00, 0x00, 0x07, + 0x1a, 0x00, 0x0a, 0x0a, 0x0e, 0x00, 0x02, 0x08, 0x14, 0x00, 0x00, + 0x07, 0x15, 0x00, 0x0a, 0x0a, 0x0b, 0x00, 0x07, 0x09, 0x0d, 0x00, + 0x08, 0x08, 0x0c, 0x00, 0x03, 0x07, 0x0f, 0x00, 0x0b, 0x08, 0x09, + 0x00, 0x06, 0x08, 0x0a, 0x00, 0x06, 0x07, 0x0b, 0x00, 0x03, 0x02, + 0x14, 0x00, 0x03, 0x06, 0x0c, 0x00, 0x05, 0x06, 0x0a, 0x00, 0x07, + 0x06, 0x08, 0x00, 0x03, 0x06, 0x09, 0x00, 0x00, 0x04, 0x0e, 0x00, + 0x04, 0x05, 0x09, 0x00, 0x04, 0x07, 0x05, 0x00, 0x03, 0x03, 0x0d, + 0x00, 0x06, 0x05, 0x07, 0x00, 0x00, 0x02, 0x0e, 0x00, 0x04, 0x03, + 0x0a, 0x00, 0x04, 0x05, 0x06, 0x00, 0x03, 0x04, 0x08, 0x00, 0x05, + 0x05, 0x05, 0x00, 0x02, 0x03, 0x0a, 0x00, 0x00, 0x03, 0x0a, 0x00, + 0x05, 0x03, 0x08, 0x00, 0x04, 0x04, 0x05, 0x00, 0x04, 0x05, 0x03, + 0x00, 0x05, 0x03, 0x06, 0x00, 0x02, 0x03, 0x07, 0x00, 0x03, 0x02, + 0x08, 0x00, 0x03, 0x03, 0x06, 0x00, 0x04, 0x03, 0x05, 0x00, 0x03, + 0x03, 0x05, 0x00, 0x03, 0x04, 0x03, 0x00, 0x00, 0x03, 0x06, 0x00, + 0x07, 0x02, 0x05, 0x00, 0x02, 0x02, 0x06, 0x00, 0x02, 0x03, 0x04, + 0x00, 0x01, 0x02, 0x06, 0x00, 0x03, 0x02, 0x05, 0x00, 0x03, 0x03, + 0x03, 0x00, 0x02, 0x03, 0x03, 0x00, 0x03, 0x02, 0x04, 0x00, 0x03, + 0x03, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x03, 0x03, 0x00, + 0x02, 0x02, 0x04, 0x00, 0x02, 0x03, 0x02, 0x00, 0x01, 0x01, 0x06, + 0x00, 0x01, 0x02, 0x04, 0x00, 0x03, 0x01, 0x05, 0x00, 0x00, 0x01, + 0x06, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x02, + 0x02, 0x03, 0x00, 0x02, 0x03, 0x01, 0x00, 0x01, 0x01, 0x05, 0x00, + 0x01, 0x02, 0x03, 0x00, 0x01, 0x03, 0x01, 0x00, 0x00, 0x01, 0x05, + 0x00, 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x04, 0x00, 0x00, 0x00, + 0x06, 0x00, 0x00, 0x01, 0x04, 0x00, 0x08, 0x01, 0x01, 0x00, 0x05, + 0x01, 0x02, 0x00, 0x02, 0x01, 0x03, 0x00, 0x02, 0x02, 0x01, 0x00, + 0x01, 0x01, 0x03, 0x00, 0x01, 0x02, 0x01, 0x00, 0x03, 0x01, 0x02, + 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x01, 0x03, 0x00, 0x02, 0x00, + 0x04, 0x00, 0x01, 0x00, 0x04, 0x00, 0x01, 0x01, 0x02, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x02, 0x00, 0x03, 0x00, 0x02, 0x01, 0x01, 0x00, + 0x01, 0x00, 0x03, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, 0x02, 0x00, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, + 0xfc, 0xe2, 0xe2, 0xf6, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, + 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, + 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, + 0xfc, 0xfc, 0xeb, 0xe2, 0xeb, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, + 0xfc, 0xfc, 0xfc, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xfc, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xce, + 0xce, 0xeb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xd5, 0xce, 0xe2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xce, 0xc7, 0xd5, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xce, 0xc7, + 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfe, 0xfb, 0xfb, 0xba, 0x9c, 0x9c, 0xbd, 0xbd, + 0xd2, 0xc3, 0xd8, 0xf9, 0xf9, 0xf9, 0xe8, 0xe8, 0xff, 0xff, 0xf8, + 0xce, 0xc7, 0xf6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xe7, 0xfb, 0xd3, 0xc8, 0xc8, 0x96, 0xa7, 0xe5, 0xfb, + 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfe, 0xfb, 0xfb, 0xba, 0x9c, 0x96, 0x9e, 0x9e, 0xd2, 0xd2, + 0xd6, 0xf9, 0xf9, 0xf9, 0xe8, 0xe8, 0xfc, 0xff, 0xfa, 0xc7, 0xc7, + 0xf6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xde, 0xed, 0xab, 0x9a, 0x9e, 0x8f, 0xa7, 0xe5, 0xfb, 0xfb, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0xfb, 0xfb, 0xca, 0xa3, 0x94, 0x8d, 0x8d, 0xa4, 0xd2, 0xf2, 0xf1, + 0xf1, 0xf1, 0xe8, 0xe8, 0xff, 0xff, 0xf8, 0xce, 0xce, 0xf6, 0xff, + 0xfc, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xff, 0xff, 0xfe, 0xe4, 0xbd, + 0x94, 0x88, 0x83, 0x8f, 0xa7, 0xfb, 0xfb, 0xfb, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfb, 0xfe, + 0xf7, 0xa8, 0x92, 0x88, 0x88, 0x92, 0xa4, 0xd9, 0xe8, 0xe8, 0xe8, + 0xe8, 0xe8, 0xff, 0xff, 0xea, 0xce, 0xce, 0xeb, 0xff, 0xff, 0xce, + 0xce, 0xce, 0xce, 0xce, 0xfc, 0xff, 0xfb, 0xb7, 0x9d, 0x82, 0x76, + 0x7d, 0x8f, 0xa7, 0xfe, 0xfb, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfb, 0xfe, 0xf7, 0xa8, + 0x92, 0x80, 0x83, 0x88, 0x92, 0xa4, 0xe2, 0xe8, 0xe8, 0xe8, 0xe8, + 0xff, 0xff, 0xf8, 0xce, 0xce, 0xf6, 0xff, 0xff, 0xce, 0xce, 0xce, + 0xce, 0xce, 0xfc, 0xff, 0xf0, 0x9d, 0x93, 0x7d, 0x7b, 0x72, 0x8f, + 0xa7, 0xfe, 0xfb, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xb1, 0x94, + 0x7f, 0x85, 0x89, 0x8e, 0xa2, 0xd2, 0xed, 0xd4, 0xe6, 0xd4, 0xdc, + 0xd0, 0xc6, 0xc5, 0xea, 0xfe, 0xfe, 0xd8, 0xd8, 0xcf, 0xdb, 0xe0, + 0xfb, 0xb8, 0x99, 0x7e, 0x6f, 0x6b, 0x77, 0x85, 0xc0, 0xc7, 0xe2, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xfc, 0xfc, 0xfc, + 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xee, 0x9f, 0x87, 0x7d, + 0x7d, 0x84, 0x97, 0xa5, 0xd2, 0xd4, 0xe6, 0xd0, 0xd0, 0xd0, 0xbf, + 0xbf, 0xea, 0xfb, 0xfb, 0xd8, 0xd8, 0xdc, 0xdb, 0xd3, 0xbd, 0x9d, + 0x8c, 0x6e, 0x64, 0x67, 0x80, 0x98, 0xc7, 0xc7, 0xd5, 0xfc, 0xfc, + 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xfc, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xae, 0x99, 0x7d, 0x77, 0x70, + 0x85, 0x99, 0xa9, 0xd4, 0xe7, 0xc5, 0xc5, 0xc5, 0xb9, 0xc1, 0xd4, + 0xf5, 0xf5, 0xc1, 0xc1, 0xb9, 0xe0, 0xd6, 0x9d, 0x8a, 0x7a, 0x68, + 0x62, 0x71, 0x8c, 0xa4, 0xeb, 0xeb, 0xeb, 0xf6, 0xf6, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xe5, 0xbd, 0xbd, 0x8b, 0x77, 0x67, 0x77, 0x82, + 0x94, 0xb0, 0xe7, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xc1, 0xd8, 0xd8, + 0xbe, 0xbe, 0xa8, 0xc3, 0xa5, 0x84, 0x6f, 0x62, 0x5d, 0x64, 0x7b, + 0x97, 0xb7, 0xff, 0xff, 0xfa, 0xcf, 0xcf, 0xfa, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xda, 0xbd, 0xbd, 0x98, 0x80, 0x70, 0x69, 0x70, 0x87, 0x8f, + 0xe6, 0xe6, 0xab, 0xb0, 0xb0, 0xb0, 0xc1, 0xd8, 0xd8, 0xbe, 0xbe, + 0xb5, 0xa8, 0x8c, 0x64, 0x58, 0x51, 0x58, 0x64, 0x89, 0xa5, 0xf2, + 0xff, 0xff, 0xf8, 0xce, 0xce, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xd5, 0xd5, 0xe2, + 0xf4, 0xee, 0xa9, 0x87, 0x69, 0x5d, 0x63, 0x73, 0x81, 0x91, 0xaa, + 0xe6, 0xe6, 0xc0, 0xc0, 0xd5, 0xf6, 0xf4, 0xe6, 0xe6, 0xb5, 0x95, + 0x68, 0x54, 0x51, 0x4a, 0x60, 0x7f, 0x9f, 0xc8, 0xdb, 0xc0, 0xbf, + 0xce, 0xf8, 0xf8, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xf9, + 0xf9, 0xf9, 0xf9, 0xfe, 0xff, 0xfc, 0xce, 0xce, 0xe9, 0xf4, 0xfe, + 0xe1, 0x94, 0x77, 0x5e, 0x63, 0x62, 0x6b, 0x81, 0x99, 0x9c, 0x9c, + 0xa0, 0xa1, 0xab, 0xab, 0xab, 0x9c, 0x9c, 0x97, 0x6d, 0x56, 0x49, + 0x51, 0x5f, 0x77, 0x94, 0xbe, 0xdf, 0xdb, 0xbb, 0xbb, 0xc7, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xf9, 0xf9, 0xf9, + 0xf9, 0xfa, 0xff, 0xff, 0xd5, 0xd5, 0xe9, 0xf4, 0xfe, 0xfb, 0xa4, + 0x82, 0x68, 0x5d, 0x5a, 0x62, 0x68, 0x8c, 0x8c, 0x93, 0x94, 0x8d, + 0x96, 0x96, 0x94, 0x90, 0x90, 0x79, 0x57, 0x42, 0x3f, 0x58, 0x6f, + 0x8d, 0xad, 0xee, 0xcf, 0xdb, 0xbf, 0xc0, 0xce, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xf9, 0xf1, 0xe8, 0xe8, 0xf8, + 0xff, 0xff, 0xff, 0xff, 0xf4, 0xe7, 0xd7, 0xbd, 0xbd, 0xa5, 0x78, + 0x62, 0x54, 0x4e, 0x4e, 0x59, 0x61, 0x75, 0x85, 0x7f, 0x7f, 0x87, + 0x7d, 0x7e, 0x6d, 0x59, 0x42, 0x3a, 0x42, 0x5d, 0x8a, 0xad, 0xad, + 0xb8, 0xb9, 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfe, 0xf9, 0xf9, 0xe8, 0xe8, 0xfd, 0xff, 0xff, + 0xff, 0xff, 0xf4, 0xe7, 0xd7, 0xbd, 0xb7, 0xad, 0x95, 0x68, 0x54, + 0x42, 0x39, 0x36, 0x44, 0x59, 0x63, 0x70, 0x76, 0x77, 0x6b, 0x5e, + 0x59, 0x47, 0x34, 0x37, 0x50, 0x78, 0xa2, 0xad, 0xad, 0xb8, 0xb9, + 0xc0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfc, 0xfc, 0xf8, 0xf6, 0xf6, 0xf6, 0xfc, 0xfc, 0xfc, 0xe2, 0xe2, + 0xd1, 0xbb, 0xbf, 0xb9, 0xba, 0xbc, 0xa2, 0x79, 0x5e, 0x3e, 0x2a, + 0x21, 0x25, 0x38, 0x47, 0x5d, 0x67, 0x63, 0x5b, 0x4d, 0x3c, 0x2f, + 0x2d, 0x3a, 0x64, 0x8a, 0xbd, 0xb8, 0xb3, 0xc6, 0xdf, 0xe0, 0xfc, + 0xfc, 0xfc, 0xe2, 0xe2, 0xe2, 0xe2, 0xeb, 0xfc, 0xfc, 0xfc, 0xfc, + 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xfc, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xce, 0xce, 0xc7, 0xbb, + 0xbb, 0xbb, 0xbb, 0xb9, 0x99, 0x8c, 0x63, 0x46, 0x2f, 0x19, 0x19, + 0x23, 0x2f, 0x41, 0x58, 0x55, 0x49, 0x3b, 0x2d, 0x23, 0x2f, 0x47, + 0x6e, 0x89, 0x9a, 0xbb, 0xbb, 0xd5, 0xff, 0xff, 0xff, 0xff, 0xf8, + 0xce, 0xce, 0xce, 0xce, 0xd5, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xcf, 0xc7, 0xbb, 0xbb, 0xbb, + 0xbb, 0xb9, 0x9f, 0x84, 0x6d, 0x4c, 0x35, 0x2a, 0x16, 0x1e, 0x1f, + 0x31, 0x43, 0x45, 0x3a, 0x2e, 0x1f, 0x19, 0x38, 0x59, 0x6c, 0x76, + 0x9a, 0xbb, 0xbb, 0xd5, 0xff, 0xff, 0xff, 0xff, 0xfa, 0xcf, 0xcf, + 0xcf, 0xcf, 0xd5, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe2, 0xa6, 0x8f, + 0x87, 0x80, 0x6e, 0x57, 0x40, 0x22, 0x15, 0x11, 0x1d, 0x30, 0x39, + 0x28, 0x1c, 0x12, 0x12, 0x19, 0x2f, 0x46, 0x6e, 0x89, 0x9a, 0xa6, + 0xd5, 0xe2, 0xbf, 0xbf, 0xce, 0xce, 0xe2, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd5, 0xa6, 0x8f, 0x87, 0x76, + 0x6f, 0x5b, 0x47, 0x2a, 0x18, 0x07, 0x08, 0x0b, 0x1a, 0x13, 0x11, + 0x14, 0x1f, 0x24, 0x38, 0x4d, 0x64, 0x76, 0x8d, 0x8f, 0xa6, 0xaa, + 0xa0, 0xa0, 0xc7, 0xce, 0xd5, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf4, 0xe6, 0xe7, 0xe9, 0xa6, 0x96, 0x8f, 0x80, 0x6b, 0x57, + 0x49, 0x33, 0x1f, 0x03, 0x06, 0x04, 0x05, 0x05, 0x06, 0x11, 0x2c, + 0x41, 0x4c, 0x5e, 0x64, 0x71, 0x7f, 0x92, 0xa0, 0xce, 0xe7, 0xe6, + 0xe2, 0xdc, 0xe2, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, + 0xaa, 0xe6, 0xd1, 0xab, 0x96, 0x87, 0x7d, 0x6b, 0x56, 0x3e, 0x3c, + 0x2b, 0x0a, 0x02, 0x04, 0x04, 0x04, 0x02, 0x0b, 0x28, 0x42, 0x50, + 0x5a, 0x72, 0x80, 0x80, 0xa1, 0xb4, 0xec, 0xe6, 0xe6, 0xe8, 0xe8, + 0xf1, 0xf9, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xe6, 0xc9, + 0xe3, 0xa1, 0x98, 0x7a, 0x6f, 0x5d, 0x42, 0x34, 0x31, 0x28, 0x08, + 0x05, 0x04, 0x04, 0x04, 0x05, 0x06, 0x1a, 0x30, 0x4a, 0x55, 0x65, + 0x6f, 0x80, 0x9d, 0xb2, 0xef, 0xef, 0xe6, 0xe8, 0xe8, 0xf1, 0xf9, + 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xd5, 0xd5, 0xc1, 0xb5, 0xb7, 0xb7, 0x98, + 0x80, 0x61, 0x59, 0x4f, 0x2b, 0x11, 0x0a, 0x0b, 0x06, 0x05, 0x04, + 0x04, 0x04, 0x05, 0x05, 0x06, 0x11, 0x2c, 0x34, 0x3b, 0x53, 0x61, + 0x73, 0x95, 0xa5, 0x9c, 0xa6, 0xc5, 0xc5, 0xc6, 0xd4, 0xd4, 0xcf, + 0xd5, 0xd5, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, + 0xfc, 0xfc, 0xce, 0xd1, 0xbc, 0xad, 0xad, 0x9d, 0x85, 0x6f, 0x44, + 0x32, 0x2a, 0x1d, 0x0d, 0x08, 0x06, 0x06, 0x04, 0x04, 0x04, 0x05, + 0x05, 0x05, 0x05, 0x0a, 0x1f, 0x1f, 0x29, 0x3b, 0x4b, 0x64, 0x79, + 0x9b, 0x9e, 0xa1, 0xbf, 0xc5, 0xc5, 0xd0, 0xd0, 0xce, 0xce, 0xd5, + 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, + 0xfc, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xf5, 0xf5, 0xfe, 0xff, 0xe5, + 0xbc, 0xa5, 0x99, 0x84, 0x7e, 0x64, 0x5f, 0x52, 0x2f, 0x16, 0x0f, + 0x0f, 0x09, 0x0a, 0x0d, 0x08, 0x06, 0x04, 0x05, 0x05, 0x03, 0x06, + 0x06, 0x07, 0x16, 0x19, 0x19, 0x24, 0x35, 0x56, 0x64, 0x84, 0x8e, + 0x9e, 0xb5, 0xb5, 0xb5, 0xc3, 0xde, 0xca, 0xca, 0xca, 0xfb, 0xfb, + 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf2, 0xf2, 0xde, 0xc3, 0xc3, 0xde, 0xae, 0x9e, 0x89, 0x80, + 0x6e, 0x5e, 0x55, 0x49, 0x4c, 0x3e, 0x26, 0x10, 0x10, 0x22, 0x20, + 0x1e, 0x22, 0x12, 0x07, 0x04, 0x05, 0x07, 0x11, 0x17, 0x18, 0x18, + 0x24, 0x2d, 0x29, 0x2d, 0x33, 0x3f, 0x58, 0x63, 0x65, 0x75, 0x8c, + 0x8c, 0x8c, 0x95, 0xad, 0xd2, 0xd2, 0xd2, 0xed, 0xed, 0xf7, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf2, + 0xf2, 0xed, 0xd2, 0xd2, 0xa4, 0x90, 0x85, 0x67, 0x62, 0x5d, 0x57, + 0x4a, 0x48, 0x50, 0x42, 0x35, 0x21, 0x29, 0x3c, 0x44, 0x46, 0x3e, + 0x1d, 0x09, 0x05, 0x03, 0x11, 0x1c, 0x2e, 0x2b, 0x31, 0x33, 0x47, + 0x4d, 0x47, 0x46, 0x48, 0x54, 0x5d, 0x5d, 0x5d, 0x67, 0x67, 0x6c, + 0x80, 0x84, 0xa5, 0xd2, 0xd2, 0xed, 0xed, 0xf7, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xbd, 0xa7, + 0x9e, 0x94, 0x82, 0x77, 0x6b, 0x5a, 0x58, 0x57, 0x5d, 0x5a, 0x5d, + 0x5d, 0x5c, 0x53, 0x4d, 0x4d, 0x61, 0x61, 0x55, 0x4c, 0x2b, 0x11, + 0x03, 0x0a, 0x14, 0x20, 0x35, 0x3f, 0x48, 0x50, 0x57, 0x68, 0x66, + 0x63, 0x60, 0x62, 0x64, 0x60, 0x5a, 0x5f, 0x5f, 0x5b, 0x5f, 0x6c, + 0x80, 0x92, 0x9e, 0xbd, 0xbd, 0xc8, 0xd5, 0xd5, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbd, 0x9e, 0x92, 0x8d, 0x83, + 0x7d, 0x74, 0x64, 0x58, 0x55, 0x5b, 0x6b, 0x6b, 0x78, 0x7a, 0x7a, + 0x84, 0x7a, 0x6e, 0x78, 0x64, 0x5a, 0x4a, 0x3f, 0x1d, 0x0a, 0x0f, + 0x1e, 0x29, 0x40, 0x4e, 0x5a, 0x67, 0x71, 0x80, 0x80, 0x80, 0x81, + 0x81, 0x81, 0x78, 0x6b, 0x6c, 0x6f, 0x6b, 0x63, 0x6c, 0x76, 0x7c, + 0x8d, 0x9e, 0x9e, 0xa1, 0xcf, 0xce, 0xff, 0xff, 0xff, 0xff, 0xfc, + 0xff, 0xff, 0xff, 0xf6, 0x9e, 0x92, 0x88, 0x83, 0x76, 0x7d, 0x74, + 0x6e, 0x6d, 0x6d, 0x79, 0x85, 0x85, 0x9d, 0xa2, 0xa2, 0x9f, 0x94, + 0x87, 0x87, 0x69, 0x5a, 0x48, 0x51, 0x2b, 0x15, 0x16, 0x24, 0x36, + 0x4d, 0x5d, 0x77, 0x83, 0x8d, 0x9a, 0x9a, 0x9a, 0xa2, 0xa2, 0xa2, + 0x9b, 0x8c, 0x89, 0x89, 0x84, 0x74, 0x71, 0x77, 0x76, 0x7c, 0x8d, + 0x8b, 0x9f, 0xb9, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xf6, + 0xff, 0xbb, 0x98, 0x92, 0x8d, 0x88, 0x88, 0x8b, 0x90, 0x90, 0x93, + 0x93, 0x94, 0xa0, 0xa0, 0xc0, 0xce, 0xc7, 0xb3, 0xb3, 0x9c, 0x87, + 0x7f, 0x5f, 0x5d, 0x57, 0x47, 0x36, 0x2d, 0x3b, 0x4d, 0x5e, 0x72, + 0x85, 0x83, 0x92, 0xc8, 0xc8, 0xc8, 0xbb, 0xbb, 0xc0, 0xce, 0xce, + 0xa3, 0x9c, 0x9c, 0x8f, 0x8f, 0x87, 0x85, 0x88, 0x8b, 0x8b, 0x94, + 0xa3, 0xa3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd5, 0xbb, 0xff, 0xbb, + 0x9e, 0xa4, 0x9e, 0x9e, 0x9e, 0xa4, 0xb2, 0xb2, 0xbd, 0xb7, 0xbc, + 0xbf, 0xce, 0xce, 0xce, 0xce, 0xb3, 0xb3, 0xa8, 0x8f, 0x87, 0x76, + 0x6c, 0x68, 0x4f, 0x3b, 0x35, 0x46, 0x57, 0x5f, 0x74, 0x88, 0x92, + 0x9f, 0xc8, 0xda, 0xc8, 0xbb, 0xbb, 0xc7, 0xce, 0xce, 0xd6, 0xe4, + 0xc3, 0xae, 0xae, 0xa4, 0x9e, 0x9e, 0x98, 0x98, 0x9f, 0xa3, 0xa3, + 0xfe, 0xff, 0xff, 0xff, 0xff, 0xd5, 0xbb, 0xff, 0xe2, 0xd9, 0xc8, + 0xd9, 0xd9, 0xe4, 0xf7, 0xfb, 0xfb, 0xfb, 0xfb, 0xfe, 0xff, 0xff, + 0xd5, 0xce, 0xce, 0xc2, 0xc2, 0xb3, 0xa3, 0x9c, 0x8d, 0x85, 0x6e, + 0x52, 0x3b, 0x37, 0x4f, 0x65, 0x72, 0x82, 0x9f, 0xb1, 0xda, 0xf7, + 0xf7, 0xfb, 0xe2, 0xe2, 0xcf, 0xce, 0xce, 0xed, 0xed, 0xed, 0xf7, + 0xf7, 0xe1, 0xe1, 0xe1, 0xc8, 0xc8, 0xc2, 0xc4, 0xc4, 0xfe, 0xff, + 0xff, 0xff, 0xff, 0xf6, 0xe2, 0xff, 0xff, 0xf2, 0xf2, 0xf2, 0xf2, + 0xf2, 0xfb, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xd5, 0xce, + 0xcf, 0xca, 0xca, 0xb3, 0xa8, 0xa8, 0x9e, 0x8e, 0x7a, 0x5c, 0x43, + 0x3a, 0x52, 0x6c, 0x85, 0x94, 0xb1, 0xfe, 0xfe, 0xfe, 0xfb, 0xfe, + 0xff, 0xff, 0xdf, 0xd1, 0xc7, 0xd6, 0xd2, 0xe4, 0xf2, 0xf2, 0xfb, + 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xf2, 0xf0, 0xf2, 0xf2, 0xf2, 0xf4, + 0xf4, 0xf4, 0xf4, 0xf4, 0xfa, 0xff, 0xff, 0xd5, 0xce, 0xcf, 0xca, + 0xca, 0xb3, 0xa7, 0xa7, 0xb4, 0x9e, 0x8a, 0x68, 0x4c, 0x3f, 0x52, + 0x6b, 0x98, 0xa1, 0xea, 0xf3, 0xf3, 0xf3, 0xf3, 0xf4, 0xff, 0xff, + 0xe9, 0xcf, 0xcf, 0xe4, 0xd6, 0xe4, 0xf2, 0xf2, 0xfe, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfb, 0xfb, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xd5, 0xce, 0xce, 0xd0, 0xd0, 0xc1, + 0xa8, 0xa8, 0xaa, 0xa9, 0x9d, 0x71, 0x50, 0x3a, 0x49, 0x68, 0xa1, + 0xb6, 0xe2, 0xce, 0xce, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xd5, 0xce, 0xce, 0xd0, 0xd0, 0xb9, 0xa8, 0xa8, + 0xaa, 0xa9, 0x9d, 0x6b, 0x50, 0x3a, 0x49, 0x68, 0xa1, 0xb0, 0xec, + 0xce, 0xce, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf6, 0xeb, 0xe9, 0xc6, 0xc6, 0xb9, 0xb6, 0xb6, 0xaa, 0xb5, + 0xa5, 0x7b, 0x57, 0x42, 0x52, 0x68, 0xab, 0xb6, 0xe7, 0xce, 0xce, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfa, 0xc5, 0xc5, 0xbf, 0xc5, 0xc5, 0xe6, 0xb5, 0xa5, 0x7e, + 0x68, 0x4c, 0x57, 0x75, 0xc8, 0xc9, 0xc1, 0xce, 0xce, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, + 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xe6, 0xb5, 0xa5, 0x84, 0x5e, 0x57, + 0x66, 0x86, 0xc8, 0xc9, 0xc5, 0xce, 0xce, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe8, 0xe8, + 0xe8, 0xd0, 0xd0, 0xc9, 0xd6, 0xb2, 0x85, 0x71, 0x64, 0x72, 0x8e, + 0xf2, 0xf9, 0xf1, 0xce, 0xce, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe8, 0xe8, 0xe8, 0xd0, + 0xd0, 0xc9, 0xd6, 0xb2, 0x88, 0x71, 0x60, 0x72, 0x8e, 0xf2, 0xfb, + 0xf1, 0xce, 0xce, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xe2, 0xe2, 0xe8, 0xdc, 0xdc, 0xe7, + 0xd6, 0xbd, 0x90, 0x72, 0x67, 0x7d, 0x98, 0xe1, 0xfb, 0xf9, 0xeb, + 0xeb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xea, 0xea, 0xe8, 0xea, 0xea, 0xf3, 0xf0, 0xbd, + 0x8e, 0x80, 0x71, 0x80, 0x98, 0xed, 0xfb, 0xfe, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xf3, 0xf0, 0xbd, 0x8e, 0x80, + 0x71, 0x80, 0x98, 0xed, 0xfb, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, + 0xfe, 0xfe, 0xfe, 0xfe, 0xfa, 0xfe, 0xba, 0x8b, 0x7c, 0x76, 0x8b, + 0xa4, 0xf2, 0xf7, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xda, 0x98, 0x82, 0x76, 0x8d, 0xa1, 0xf2, + 0xf2, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xfb, 0xa7, 0x90, 0x7c, 0x8d, 0xa7, 0xf2, 0xf2, 0xfb, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfb, 0xb2, 0x94, 0x85, 0x9a, 0xbd, 0xf2, 0xf2, 0xfb, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xb2, 0x98, 0x87, 0x98, + 0xbd, 0xf2, 0xf2, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xf6, 0xba, 0xa7, 0x9c, 0xab, 0xb9, 0xfe, + 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xf6, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xeb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff +}; diff --git a/utils/hlmv/muzzle2.h b/utils/hlmv/muzzle2.h new file mode 100644 index 0000000..28a7c41 --- /dev/null +++ b/utils/hlmv/muzzle2.h @@ -0,0 +1,474 @@ +const unsigned char muzzleflash2_bmp[] = +{ + 0x42, 0x4d, 0x36, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, + 0x04, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf5, + 0xfe, 0xfd, 0x00, 0xfe, 0xfb, 0xff, 0x00, 0xfc, 0xfd, 0xfb, 0x00, + 0xef, 0xfd, 0xfe, 0x00, 0xe3, 0xfc, 0xfe, 0x00, 0xd6, 0xf7, 0xfe, + 0x00, 0xf8, 0xf3, 0xf6, 0x00, 0xeb, 0xf6, 0xef, 0x00, 0xf5, 0xf3, + 0xef, 0x00, 0xd4, 0xf2, 0xfa, 0x00, 0xeb, 0xf0, 0xf5, 0x00, 0xf6, + 0xed, 0xf5, 0x00, 0xe0, 0xf1, 0xf4, 0x00, 0xa6, 0xf4, 0xfc, 0x00, + 0xc0, 0xee, 0xfe, 0x00, 0xd2, 0xf0, 0xf1, 0x00, 0xca, 0xee, 0xf0, + 0x00, 0xc2, 0xef, 0xeb, 0x00, 0xbc, 0xe4, 0xfa, 0x00, 0xb4, 0xe3, + 0xfb, 0x00, 0xa3, 0xe6, 0xfa, 0x00, 0xbf, 0xe1, 0xee, 0x00, 0x94, + 0xe3, 0xf7, 0x00, 0xaa, 0xda, 0xfa, 0x00, 0xb4, 0xdb, 0xec, 0x00, + 0x90, 0xd7, 0xf7, 0x00, 0xa6, 0xd9, 0xeb, 0x00, 0xa5, 0xd1, 0xf4, + 0x00, 0x94, 0xcf, 0xf9, 0x00, 0x83, 0xce, 0xfb, 0x00, 0x8e, 0xd0, + 0xed, 0x00, 0xa3, 0xcf, 0xe6, 0x00, 0x81, 0xd1, 0xee, 0x00, 0xa0, + 0xc6, 0xe7, 0x00, 0x8f, 0xc3, 0xed, 0x00, 0x7c, 0xc4, 0xf2, 0x00, + 0x7c, 0xc3, 0xeb, 0x00, 0x92, 0xc2, 0xe0, 0x00, 0x79, 0xc2, 0xe1, + 0x00, 0x8e, 0xba, 0xe1, 0x00, 0x70, 0xb7, 0xe9, 0x00, 0x82, 0xb6, + 0xe3, 0x00, 0x8e, 0xb6, 0xda, 0x00, 0x83, 0xb4, 0xdb, 0x00, 0x73, + 0xb3, 0xdc, 0x00, 0x85, 0xb3, 0xd3, 0x00, 0x74, 0xaa, 0xe9, 0x00, + 0x65, 0xae, 0xdc, 0x00, 0x7c, 0xa9, 0xd1, 0x00, 0x78, 0xa8, 0xd1, + 0x00, 0x70, 0xa5, 0xd9, 0x00, 0x76, 0xa7, 0xcd, 0x00, 0x7b, 0xae, + 0xbd, 0x00, 0x61, 0xa1, 0xdc, 0x00, 0x7a, 0xa5, 0xc8, 0x00, 0x75, + 0xa0, 0xc9, 0x00, 0x6c, 0x9c, 0xcb, 0x00, 0x62, 0x9b, 0xcd, 0x00, + 0x5b, 0x9c, 0xcd, 0x00, 0x6e, 0x98, 0xbf, 0x00, 0x60, 0x90, 0xd1, + 0x00, 0x54, 0x90, 0xd3, 0x00, 0x55, 0x93, 0xca, 0x00, 0x60, 0x91, + 0xc4, 0x00, 0x64, 0x93, 0xbd, 0x00, 0x6d, 0x93, 0xb4, 0x00, 0x50, + 0x8f, 0xc5, 0x00, 0x60, 0x8e, 0xb8, 0x00, 0x54, 0x8d, 0xbe, 0x00, + 0x60, 0x8d, 0xb2, 0x00, 0x4c, 0x88, 0xb5, 0x00, 0x56, 0x87, 0xb2, + 0x00, 0x4e, 0x82, 0xbc, 0x00, 0x43, 0x7c, 0xc0, 0x00, 0x53, 0x81, + 0xac, 0x00, 0x4e, 0x7c, 0xb4, 0x00, 0x58, 0x80, 0xa3, 0x00, 0x44, + 0x7d, 0xaf, 0x00, 0x42, 0x79, 0xb4, 0x00, 0x4a, 0x7a, 0xa3, 0x00, + 0x41, 0x78, 0xa5, 0x00, 0x3f, 0x73, 0xac, 0x00, 0x57, 0x77, 0x92, + 0x00, 0x3c, 0x6f, 0xa4, 0x00, 0x46, 0x72, 0x97, 0x00, 0x4d, 0x72, + 0x92, 0x00, 0x3f, 0x6d, 0x99, 0x00, 0x3e, 0x6b, 0x92, 0x00, 0x34, + 0x66, 0x98, 0x00, 0x36, 0x66, 0x8e, 0x00, 0x40, 0x66, 0x84, 0x00, + 0x32, 0x61, 0x8f, 0x00, 0x35, 0x60, 0x86, 0x00, 0x31, 0x5c, 0x8f, + 0x00, 0x27, 0x59, 0x8c, 0x00, 0x30, 0x5b, 0x84, 0x00, 0x2f, 0x59, + 0x82, 0x00, 0x35, 0x5a, 0x79, 0x00, 0x2e, 0x52, 0x84, 0x00, 0x2e, + 0x56, 0x7a, 0x00, 0x24, 0x53, 0x81, 0x00, 0x28, 0x55, 0x79, 0x00, + 0x34, 0x55, 0x70, 0x00, 0x24, 0x4f, 0x77, 0x00, 0x2c, 0x4f, 0x70, + 0x00, 0x23, 0x4a, 0x78, 0x00, 0x19, 0x48, 0x7a, 0x00, 0x23, 0x4a, + 0x70, 0x00, 0x2f, 0x4c, 0x65, 0x00, 0x26, 0x4a, 0x6a, 0x00, 0x1a, + 0x47, 0x6f, 0x00, 0x1e, 0x46, 0x69, 0x00, 0x26, 0x46, 0x62, 0x00, + 0x1f, 0x45, 0x61, 0x00, 0x16, 0x3e, 0x70, 0x00, 0x21, 0x3e, 0x64, + 0x00, 0x19, 0x3d, 0x61, 0x00, 0x1f, 0x3e, 0x5c, 0x00, 0x11, 0x3c, + 0x63, 0x00, 0x20, 0x3d, 0x58, 0x00, 0x18, 0x3b, 0x57, 0x00, 0x20, + 0x3b, 0x51, 0x00, 0x19, 0x39, 0x50, 0x00, 0x12, 0x36, 0x54, 0x00, + 0x0c, 0x32, 0x5e, 0x00, 0x1c, 0x34, 0x50, 0x00, 0x16, 0x33, 0x4f, + 0x00, 0x0f, 0x31, 0x54, 0x00, 0x12, 0x34, 0x4b, 0x00, 0x1b, 0x32, + 0x46, 0x00, 0x11, 0x2f, 0x4a, 0x00, 0x10, 0x2c, 0x4c, 0x00, 0x18, + 0x2d, 0x45, 0x00, 0x07, 0x29, 0x4d, 0x00, 0x0d, 0x29, 0x46, 0x00, + 0x15, 0x2a, 0x3c, 0x00, 0x12, 0x28, 0x41, 0x00, 0x0f, 0x29, 0x3e, + 0x00, 0x0b, 0x28, 0x41, 0x00, 0x10, 0x27, 0x39, 0x00, 0x09, 0x26, + 0x3d, 0x00, 0x17, 0x26, 0x34, 0x00, 0x10, 0x21, 0x3b, 0x00, 0x12, + 0x21, 0x35, 0x00, 0x07, 0x21, 0x38, 0x00, 0x09, 0x21, 0x32, 0x00, + 0x14, 0x1f, 0x2f, 0x00, 0x0a, 0x1d, 0x35, 0x00, 0x06, 0x1d, 0x34, + 0x00, 0x08, 0x1e, 0x2f, 0x00, 0x0e, 0x1d, 0x2e, 0x00, 0x19, 0x1f, + 0x25, 0x00, 0x13, 0x1c, 0x25, 0x00, 0x0a, 0x1c, 0x28, 0x00, 0x07, + 0x19, 0x2d, 0x00, 0x00, 0x19, 0x2f, 0x00, 0x0f, 0x19, 0x23, 0x00, + 0x09, 0x18, 0x26, 0x00, 0x07, 0x18, 0x20, 0x00, 0x06, 0x15, 0x26, + 0x00, 0x16, 0x16, 0x19, 0x00, 0x0f, 0x14, 0x1e, 0x00, 0x06, 0x14, + 0x20, 0x00, 0x00, 0x11, 0x24, 0x00, 0x08, 0x14, 0x1b, 0x00, 0x0f, + 0x13, 0x19, 0x00, 0x12, 0x13, 0x17, 0x00, 0x0a, 0x12, 0x16, 0x00, + 0x09, 0x0f, 0x1a, 0x00, 0x03, 0x0e, 0x1d, 0x00, 0x0a, 0x0e, 0x17, + 0x00, 0x00, 0x0c, 0x1d, 0x00, 0x0e, 0x0e, 0x13, 0x00, 0x04, 0x0d, + 0x17, 0x00, 0x0f, 0x0e, 0x0f, 0x00, 0x04, 0x0d, 0x13, 0x00, 0x09, + 0x0c, 0x13, 0x00, 0x00, 0x0b, 0x15, 0x00, 0x0c, 0x0c, 0x0e, 0x00, + 0x0c, 0x0b, 0x0e, 0x00, 0x0a, 0x0b, 0x0e, 0x00, 0x0b, 0x0b, 0x0c, + 0x00, 0x08, 0x0a, 0x0e, 0x00, 0x03, 0x0a, 0x0f, 0x00, 0x08, 0x0a, + 0x0c, 0x00, 0x02, 0x08, 0x12, 0x00, 0x00, 0x06, 0x13, 0x00, 0x06, + 0x09, 0x0a, 0x00, 0x07, 0x07, 0x0b, 0x00, 0x04, 0x06, 0x0e, 0x00, + 0x04, 0x08, 0x0a, 0x00, 0x09, 0x07, 0x0a, 0x00, 0x01, 0x06, 0x0e, + 0x00, 0x05, 0x07, 0x09, 0x00, 0x03, 0x06, 0x0b, 0x00, 0x05, 0x06, + 0x09, 0x00, 0x00, 0x05, 0x0c, 0x00, 0x04, 0x06, 0x08, 0x00, 0x05, + 0x05, 0x09, 0x00, 0x03, 0x05, 0x09, 0x00, 0x04, 0x05, 0x07, 0x00, + 0x03, 0x04, 0x09, 0x00, 0x03, 0x05, 0x07, 0x00, 0x03, 0x03, 0x0a, + 0x00, 0x05, 0x04, 0x07, 0x00, 0x05, 0x05, 0x04, 0x00, 0x00, 0x02, + 0x0a, 0x00, 0x02, 0x03, 0x07, 0x00, 0x02, 0x04, 0x05, 0x00, 0x03, + 0x03, 0x06, 0x00, 0x00, 0x03, 0x07, 0x00, 0x02, 0x02, 0x08, 0x00, + 0x03, 0x04, 0x03, 0x00, 0x02, 0x03, 0x05, 0x00, 0x01, 0x02, 0x07, + 0x00, 0x01, 0x03, 0x05, 0x00, 0x02, 0x02, 0x06, 0x00, 0x04, 0x03, + 0x03, 0x00, 0x01, 0x02, 0x06, 0x00, 0x02, 0x03, 0x03, 0x00, 0x06, + 0x02, 0x03, 0x00, 0x03, 0x03, 0x02, 0x00, 0x00, 0x01, 0x07, 0x00, + 0x02, 0x02, 0x04, 0x00, 0x02, 0x03, 0x02, 0x00, 0x0a, 0x02, 0x01, + 0x00, 0x01, 0x01, 0x06, 0x00, 0x01, 0x02, 0x04, 0x00, 0x03, 0x02, + 0x03, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x02, 0x04, 0x00, 0x02, + 0x02, 0x03, 0x00, 0x01, 0x01, 0x05, 0x00, 0x00, 0x00, 0x07, 0x00, + 0x00, 0x01, 0x05, 0x00, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, 0x06, + 0x00, 0x00, 0x01, 0x04, 0x00, 0x02, 0x01, 0x03, 0x00, 0x01, 0x01, + 0x03, 0x00, 0x01, 0x02, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, + 0x01, 0x03, 0x00, 0x01, 0x01, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x01, 0x02, 0x00, 0x02, 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf3, 0xfa, + 0xf3, 0xfa, 0xe7, 0xef, 0xe7, 0xef, 0xe0, 0xf0, 0xe0, 0xf0, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xfc, 0xff, + 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, + 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, + 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xee, 0xee, 0xee, 0xee, + 0xe4, 0xdf, 0xe4, 0xdf, 0xdd, 0xd4, 0xdd, 0xd4, 0xfc, 0xfc, 0xff, + 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, + 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, + 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, + 0xff, 0xfc, 0xff, 0xfc, 0xfe, 0xe9, 0xc6, 0xb0, 0xcb, 0xcb, 0xcb, + 0xcb, 0xcc, 0xcc, 0xcc, 0xcc, 0xfe, 0xfb, 0xfe, 0xfb, 0xd8, 0xcf, + 0xd8, 0xcf, 0xd8, 0xcf, 0xd8, 0xcf, 0xff, 0xfc, 0xff, 0xfc, 0xff, + 0xff, 0xff, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xfe, 0xe9, 0xbd, 0xb0, 0xbd, 0xcb, 0xcb, 0xcb, 0xc6, + 0xcc, 0xc6, 0xcc, 0xfb, 0xfe, 0xfb, 0xfe, 0xd3, 0xcf, 0xc9, 0xcf, + 0xc9, 0xcf, 0xc9, 0xcf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0xec, 0xbc, 0xb0, 0xcb, 0xcb, 0xcb, 0xcb, 0xcc, 0xc6, 0xcc, + 0xc6, 0xfe, 0xfb, 0xfe, 0xfb, 0xcf, 0xc9, 0xcf, 0xc9, 0xcf, 0xc9, + 0xcf, 0xc9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xe9, + 0xbd, 0xb0, 0xc2, 0xcb, 0xcb, 0xcb, 0xc6, 0xcc, 0xc6, 0xcc, 0xfb, + 0xfe, 0xfb, 0xfe, 0xc9, 0xcf, 0xc9, 0xcf, 0xc9, 0xcf, 0xc9, 0xcf, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, + 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xdd, 0xd4, 0xd4, 0xd4, 0xf9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe2, 0xbc, 0xb0, 0xa0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xd1, 0xd1, 0xd8, 0xd1, 0xff, 0xff, 0xfe, + 0xd1, 0xac, 0xac, 0xac, 0xac, 0xe8, 0xd7, 0xe8, 0xd7, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, + 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, + 0xfc, 0xff, 0xfc, 0xff, 0xcd, 0xcd, 0xcd, 0xcd, 0xf0, 0xff, 0xfc, + 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xd3, 0xba, 0xac, 0xa0, 0xac, 0xac, + 0xac, 0xac, 0xd1, 0xe8, 0xd8, 0xe8, 0xff, 0xfb, 0xcc, 0xb4, 0xa6, + 0xa0, 0xa0, 0xa0, 0xe8, 0xea, 0xe8, 0xea, 0xfc, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, + 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, + 0xff, 0xfc, 0xd4, 0xcd, 0xcd, 0xcd, 0xf9, 0xfc, 0xff, 0xfc, 0xff, + 0xfc, 0xff, 0xfc, 0xd3, 0xbf, 0xac, 0xa0, 0xac, 0xac, 0xac, 0xac, + 0xd8, 0xd8, 0xe8, 0xd8, 0xfb, 0xcc, 0xb5, 0xac, 0xa6, 0xa6, 0xa6, + 0xa6, 0xe8, 0xe8, 0xea, 0xe8, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xff, + 0xff, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, + 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, + 0xcd, 0xcd, 0xcd, 0xcd, 0xf0, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, + 0xff, 0xd3, 0xbd, 0xac, 0xa0, 0xac, 0xaa, 0xac, 0xac, 0xd1, 0xe8, + 0xd8, 0xe8, 0xc6, 0xb3, 0xa6, 0xa0, 0xa0, 0xa0, 0xa0, 0xa6, 0xe8, + 0xea, 0xe8, 0xea, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, + 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xdc, 0xe1, 0xf6, 0xf6, 0xf3, 0xe8, + 0xcb, 0xba, 0xcf, 0xe6, 0xe6, 0xe6, 0xe6, 0xd7, 0xe6, 0xd7, 0xb1, + 0xa4, 0x9c, 0x92, 0x9c, 0xa1, 0xa9, 0xad, 0xe1, 0xe1, 0xf8, 0xfd, + 0xba, 0xa8, 0x9a, 0x8f, 0xa0, 0xa0, 0xa6, 0xa7, 0xf7, 0xf9, 0xfb, + 0xf9, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, + 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, + 0xff, 0xfc, 0xff, 0xe1, 0xe1, 0xe1, 0xdc, 0xe8, 0xcb, 0xba, 0xa8, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0x9d, 0x91, 0x8d, + 0x87, 0x8d, 0x91, 0x99, 0xa3, 0xe1, 0xe1, 0xe1, 0xc9, 0x9d, 0x92, + 0x8e, 0x88, 0xa0, 0xa6, 0xae, 0xb8, 0xfc, 0xff, 0xff, 0xff, 0xfc, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xfc, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xdc, 0xe1, 0xf6, 0xf6, 0xce, 0xbd, 0xab, 0x9d, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0x91, 0x87, 0x84, 0x7d, 0x84, + 0x87, 0x91, 0x94, 0xae, 0xae, 0xb0, 0xb0, 0x8d, 0x8d, 0x8c, 0x8e, + 0xa6, 0xa6, 0xb8, 0xd4, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xf6, + 0xf6, 0xfd, 0xfe, 0xc0, 0xa9, 0x9d, 0x9a, 0x96, 0x91, 0x95, 0x95, + 0x91, 0x91, 0x91, 0x91, 0x86, 0x7e, 0x7d, 0x79, 0x7a, 0x7e, 0x88, + 0x8c, 0x9c, 0x9c, 0x9d, 0x9d, 0x81, 0x84, 0x8b, 0x8c, 0xa6, 0xb5, + 0xcd, 0xfc, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xf7, 0xf7, 0xf7, + 0xf7, 0xf7, 0xf7, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xf6, 0xfd, + 0xeb, 0xaf, 0xa4, 0x99, 0x8f, 0x88, 0x88, 0x87, 0x8c, 0x8b, 0x8b, + 0x8c, 0x8b, 0x7d, 0x75, 0x70, 0x6c, 0x70, 0x75, 0x7b, 0x83, 0x96, + 0x96, 0x8f, 0x87, 0x6c, 0x81, 0x8b, 0x9a, 0xb8, 0xcd, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdb, 0xd4, 0xdb, 0xd4, 0xdb, + 0xd4, 0xdb, 0xf7, 0xfc, 0xfc, 0xfc, 0xff, 0xfc, 0xd0, 0xbe, 0xb1, + 0x9e, 0x91, 0x87, 0x77, 0x75, 0x7b, 0x80, 0x83, 0x83, 0x83, 0x83, + 0x6f, 0x67, 0x63, 0x62, 0x63, 0x6b, 0x72, 0x7c, 0x8a, 0x7f, 0x75, + 0x70, 0x6c, 0x7d, 0x93, 0xba, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, + 0xfc, 0xfc, 0xfc, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, + 0xfc, 0xfc, 0xfc, 0xfc, 0xdd, 0xdb, 0xdd, 0xdb, 0xdd, 0xdb, 0xdd, + 0xf9, 0xff, 0xff, 0xff, 0xfc, 0xe0, 0xbe, 0xa7, 0xa4, 0x9d, 0x8d, + 0x81, 0x70, 0x70, 0x70, 0x74, 0x75, 0x75, 0x75, 0x75, 0x65, 0x5d, + 0x59, 0x57, 0x5d, 0x65, 0x6e, 0x72, 0x74, 0x6d, 0x68, 0x66, 0x6c, + 0x7d, 0x95, 0xab, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xd4, 0xdd, 0xdb, 0xdd, 0xdb, 0xdd, 0xdb, 0xf7, 0xfc, + 0xfc, 0xfc, 0xd4, 0xbe, 0xaf, 0xa7, 0x9e, 0x91, 0x87, 0x7a, 0x71, + 0x6e, 0x67, 0x67, 0x6b, 0x6b, 0x6b, 0x6b, 0x58, 0x58, 0x53, 0x51, + 0x58, 0x5d, 0x64, 0x6a, 0x5e, 0x5b, 0x59, 0x59, 0x68, 0x7e, 0x91, + 0xb9, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, + 0xe0, 0xdb, 0xe0, 0xd4, 0xe4, 0xd5, 0xd4, 0xe0, 0xf9, 0xf5, 0xf9, + 0xbe, 0xaf, 0xa7, 0x9e, 0x91, 0x8d, 0x80, 0x7a, 0x6f, 0x67, 0x60, + 0x59, 0x60, 0x60, 0x60, 0x60, 0x53, 0x51, 0x4b, 0x48, 0x53, 0x58, + 0x5d, 0x5e, 0x4d, 0x51, 0x4f, 0x54, 0x6c, 0x80, 0x91, 0xb1, 0xf0, + 0xf9, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xfc, + 0xfc, 0xcc, 0xbf, 0xcb, 0xc2, 0xb7, 0xb1, 0xb7, 0xb7, 0xab, 0x9a, + 0x94, 0x91, 0x89, 0x80, 0x80, 0x7a, 0x6e, 0x60, 0x57, 0x4f, 0x59, + 0x5c, 0x5c, 0x5c, 0x4e, 0x3c, 0x35, 0x3c, 0x4b, 0x4e, 0x4b, 0x4b, + 0x39, 0x48, 0x54, 0x5f, 0x7e, 0x8a, 0x91, 0x9e, 0xa7, 0xbe, 0xe6, + 0xff, 0xfe, 0xfb, 0xfd, 0xfa, 0xfc, 0xfc, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xff, 0xff, 0xff, 0xcc, + 0xbf, 0xcb, 0xcb, 0xb7, 0xaf, 0xa7, 0x9e, 0x99, 0x93, 0x8b, 0x88, + 0x80, 0x7a, 0x77, 0x71, 0x65, 0x59, 0x54, 0x4a, 0x4f, 0x4f, 0x4f, + 0x4f, 0x42, 0x32, 0x2e, 0x32, 0x3c, 0x3c, 0x3c, 0x3c, 0x32, 0x44, + 0x56, 0x65, 0x7e, 0x8a, 0x91, 0xa3, 0xa7, 0xbe, 0xe6, 0xff, 0xfe, + 0xfe, 0xfb, 0xfe, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xfc, 0xcc, 0xbf, 0xcb, + 0xcb, 0xc0, 0xa7, 0x9e, 0x97, 0x8c, 0x88, 0x82, 0x7d, 0x78, 0x71, + 0x6f, 0x6d, 0x5f, 0x54, 0x4f, 0x45, 0x43, 0x43, 0x43, 0x43, 0x39, + 0x2c, 0x24, 0x2c, 0x2f, 0x2f, 0x2f, 0x2f, 0x2e, 0x44, 0x57, 0x67, + 0x7e, 0x8a, 0x91, 0x9e, 0xa7, 0xbe, 0xe6, 0xff, 0xfe, 0xfb, 0xfd, + 0xfa, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, + 0xfc, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xc6, 0xbf, 0xcb, 0xcb, 0xc0, + 0xa7, 0x99, 0x87, 0x81, 0x7d, 0x7d, 0x77, 0x70, 0x6d, 0x68, 0x63, + 0x57, 0x4f, 0x45, 0x3b, 0x33, 0x33, 0x36, 0x36, 0x2f, 0x23, 0x1c, + 0x24, 0x24, 0x24, 0x20, 0x24, 0x29, 0x3f, 0x57, 0x6e, 0x80, 0x8a, + 0x91, 0xa3, 0xa7, 0xbe, 0xe6, 0xff, 0xfe, 0xfe, 0xfb, 0xfe, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfc, 0xff, 0xfc, 0xfa, 0xee, 0xf8, 0xf4, 0xc4, 0xad, 0x9e, + 0x92, 0x82, 0x73, 0x68, 0x61, 0x63, 0x60, 0x59, 0x56, 0x54, 0x4f, + 0x4a, 0x43, 0x30, 0x27, 0x27, 0x22, 0x18, 0x18, 0x17, 0x17, 0x1e, + 0x16, 0x16, 0x20, 0x44, 0x53, 0x59, 0x67, 0x78, 0x80, 0x87, 0x91, + 0x97, 0x9e, 0xaf, 0xd2, 0xfb, 0xfb, 0xfd, 0xfa, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, + 0xff, 0xff, 0xef, 0xee, 0xf4, 0xf8, 0xc4, 0xaf, 0x9c, 0x92, 0x82, + 0x73, 0x6b, 0x5c, 0x59, 0x57, 0x4f, 0x4f, 0x45, 0x40, 0x3b, 0x33, + 0x27, 0x1e, 0x1b, 0x1b, 0x12, 0x12, 0x13, 0x13, 0x16, 0x14, 0x16, + 0x2c, 0x44, 0x53, 0x59, 0x67, 0x70, 0x7a, 0x81, 0x8f, 0x97, 0x9e, + 0xb0, 0xe6, 0xfb, 0xfe, 0xfb, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xfc, + 0xfa, 0xee, 0xf8, 0xf4, 0xc4, 0xa7, 0x9e, 0x92, 0x83, 0x74, 0x67, + 0x5f, 0x57, 0x50, 0x4a, 0x44, 0x3b, 0x30, 0x2b, 0x27, 0x1b, 0x1b, + 0x17, 0x13, 0x0a, 0x0b, 0x0c, 0x0c, 0x10, 0x10, 0x1a, 0x34, 0x44, + 0x51, 0x59, 0x67, 0x6c, 0x77, 0x81, 0x87, 0x97, 0x9e, 0xaf, 0xd7, + 0xfe, 0xfb, 0xfd, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xef, 0xee, + 0xf4, 0xf8, 0xc4, 0xaf, 0x9c, 0x92, 0x83, 0x76, 0x6b, 0x5f, 0x50, + 0x47, 0x40, 0x39, 0x2b, 0x25, 0x1c, 0x17, 0x17, 0x13, 0x0e, 0x0e, + 0x0b, 0x0b, 0x04, 0x0c, 0x05, 0x10, 0x1a, 0x34, 0x47, 0x53, 0x59, + 0x67, 0x6c, 0x70, 0x79, 0x81, 0x97, 0x9e, 0xaf, 0xe6, 0xfb, 0xfe, + 0xfb, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfc, 0xff, 0xfc, 0xff, 0xcf, 0xe8, 0xcf, 0xe1, 0xe1, 0xf6, 0xfb, + 0xc0, 0xab, 0x9b, 0x90, 0x85, 0x7c, 0x6e, 0x6e, 0x56, 0x4a, 0x3b, + 0x30, 0x25, 0x1f, 0x15, 0x15, 0x0c, 0x0c, 0x0b, 0x0b, 0x06, 0x06, + 0x07, 0x07, 0x0f, 0x18, 0x2a, 0x41, 0x41, 0x4c, 0x52, 0x5a, 0x61, + 0x6c, 0x70, 0x7b, 0x87, 0x90, 0x9e, 0xab, 0xfe, 0xfb, 0xdc, 0xdc, + 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xfe, 0xed, + 0xf8, 0xed, 0xda, 0xd6, 0xda, 0xe1, 0xe1, 0xe1, 0xd6, 0xab, 0x9a, + 0x90, 0x83, 0x76, 0x6e, 0x6e, 0x60, 0x57, 0x4b, 0x43, 0x31, 0x25, + 0x1f, 0x15, 0x10, 0x0a, 0x0a, 0x0b, 0x0b, 0x06, 0x06, 0x07, 0x07, + 0x0c, 0x18, 0x2a, 0x3b, 0x41, 0x4c, 0x52, 0x5a, 0x61, 0x6c, 0x71, + 0x7a, 0x89, 0x90, 0x9e, 0xab, 0xfb, 0xfe, 0xdc, 0xdc, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf2, 0xf8, 0xea, 0xf8, + 0xcf, 0xd7, 0xcf, 0xe1, 0xae, 0xbf, 0xb9, 0xa3, 0x91, 0x85, 0x7f, + 0x6e, 0x64, 0x5e, 0x58, 0x56, 0x4a, 0x40, 0x31, 0x1f, 0x15, 0x10, + 0x09, 0x0a, 0x0a, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x04, 0x15, + 0x25, 0x37, 0x41, 0x4c, 0x55, 0x5a, 0x61, 0x6d, 0x71, 0x7f, 0x87, + 0x90, 0x9e, 0xab, 0xfb, 0xfb, 0xdc, 0xdc, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xf4, 0xf8, 0xf4, 0xda, 0xcf, + 0xda, 0xae, 0xae, 0xac, 0xaa, 0x93, 0x88, 0x7b, 0x76, 0x64, 0x5e, + 0x58, 0x53, 0x47, 0x39, 0x2b, 0x22, 0x18, 0x15, 0x09, 0x04, 0x0a, + 0x0a, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x04, 0x10, 0x21, 0x36, + 0x41, 0x4c, 0x55, 0x5a, 0x68, 0x6d, 0x71, 0x7c, 0x87, 0x90, 0x9e, + 0xab, 0xfb, 0xfe, 0xdc, 0xdc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xf8, 0xf4, 0xf1, 0xf4, 0xac, 0xac, 0xac, 0xac, + 0xa0, 0x92, 0x92, 0x88, 0x75, 0x6e, 0x62, 0x58, 0x51, 0x48, 0x3a, + 0x2b, 0x24, 0x1f, 0x14, 0x10, 0x09, 0x0c, 0x0a, 0x0a, 0x0a, 0x08, + 0x08, 0x06, 0x06, 0x07, 0x07, 0x04, 0x10, 0x1b, 0x21, 0x36, 0x40, + 0x4a, 0x57, 0x60, 0x6e, 0x6e, 0x85, 0x89, 0x95, 0xa2, 0xb1, 0xed, + 0xed, 0xe1, 0xe1, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xe0, 0xd1, 0xc5, 0xc9, 0xc5, 0xac, 0xa6, 0xac, 0xa0, 0x97, 0x92, + 0x8f, 0x82, 0x74, 0x67, 0x5e, 0x58, 0x49, 0x3e, 0x35, 0x24, 0x1e, + 0x16, 0x14, 0x09, 0x0c, 0x0c, 0x0c, 0x0a, 0x0a, 0x08, 0x08, 0x06, + 0x06, 0x07, 0x07, 0x04, 0x10, 0x17, 0x21, 0x2b, 0x37, 0x45, 0x4f, + 0x57, 0x62, 0x72, 0x76, 0x80, 0x90, 0x9d, 0xab, 0xea, 0xea, 0xe1, + 0xe1, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdb, 0xc8, + 0xc6, 0xc3, 0xc6, 0xa6, 0xac, 0xa6, 0xa0, 0x9c, 0x8f, 0x8e, 0x83, + 0x74, 0x6a, 0x5e, 0x53, 0x49, 0x3d, 0x2e, 0x26, 0x1e, 0x16, 0x0d, + 0x0c, 0x0a, 0x0a, 0x0a, 0x0a, 0x08, 0x07, 0x08, 0x06, 0x06, 0x07, + 0x07, 0x04, 0x10, 0x1b, 0x21, 0x22, 0x32, 0x3f, 0x4a, 0x56, 0x5e, + 0x64, 0x72, 0x80, 0x89, 0x92, 0xa2, 0xea, 0xea, 0xe1, 0xe1, 0xff, + 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xd4, 0xc8, 0xc3, 0xc6, + 0xc3, 0xac, 0xa6, 0xac, 0xa1, 0x9c, 0x93, 0x8c, 0x7f, 0x76, 0x6a, + 0x5e, 0x51, 0x3d, 0x35, 0x28, 0x26, 0x20, 0x16, 0x14, 0x0c, 0x0a, + 0x0a, 0x0a, 0x08, 0x07, 0x08, 0x07, 0x08, 0x06, 0x07, 0x07, 0x04, + 0x10, 0x17, 0x21, 0x22, 0x29, 0x32, 0x44, 0x50, 0x58, 0x5e, 0x6a, + 0x79, 0x80, 0x8d, 0x9d, 0xea, 0xea, 0xe1, 0xe1, 0xfc, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xdb, 0xc8, 0xc6, 0xc3, 0xc6, 0xa0, + 0xa7, 0xa0, 0x9d, 0x96, 0x94, 0x90, 0x7b, 0x6d, 0x65, 0x5b, 0x51, + 0x48, 0x3e, 0x35, 0x2c, 0x28, 0x20, 0x16, 0x10, 0x0c, 0x03, 0x0a, + 0x0c, 0x0c, 0x07, 0x06, 0x07, 0x07, 0x0f, 0x0c, 0x05, 0x15, 0x1b, + 0x27, 0x2b, 0x37, 0x3f, 0x45, 0x4b, 0x57, 0x58, 0x64, 0x70, 0x79, + 0x81, 0x8f, 0xb9, 0xb9, 0xb6, 0xb6, 0xcc, 0xcd, 0xff, 0xfc, 0xff, + 0xff, 0xff, 0xff, 0xd4, 0xca, 0xbd, 0xc2, 0xbd, 0xa5, 0xa0, 0xa5, + 0x99, 0x90, 0x94, 0x94, 0x7f, 0x71, 0x67, 0x60, 0x58, 0x53, 0x4e, + 0x46, 0x42, 0x2f, 0x28, 0x1d, 0x15, 0x10, 0x0f, 0x0c, 0x0f, 0x0c, + 0x0a, 0x0a, 0x11, 0x11, 0x0f, 0x11, 0x15, 0x21, 0x2a, 0x37, 0x43, + 0x47, 0x4a, 0x4f, 0x57, 0x5d, 0x65, 0x6e, 0x70, 0x7d, 0x84, 0x8c, + 0xab, 0xab, 0xad, 0xad, 0xbf, 0xbc, 0xff, 0xd1, 0xdf, 0xff, 0xff, + 0xff, 0xdf, 0xc6, 0xbd, 0xbd, 0xbd, 0xa0, 0xa5, 0xa0, 0x99, 0x91, + 0x93, 0x94, 0x8a, 0x7b, 0x71, 0x6d, 0x64, 0x5d, 0x58, 0x51, 0x46, + 0x3a, 0x2f, 0x23, 0x1a, 0x1a, 0x1a, 0x18, 0x15, 0x15, 0x09, 0x09, + 0x11, 0x11, 0x11, 0x11, 0x25, 0x2d, 0x41, 0x4c, 0x4c, 0x4f, 0x54, + 0x57, 0x5f, 0x65, 0x6f, 0x76, 0x79, 0x88, 0x8f, 0x9a, 0xa9, 0xad, + 0xb0, 0xb0, 0xbf, 0xbf, 0xff, 0xbf, 0xcc, 0xff, 0xff, 0xff, 0xdf, + 0xc9, 0xbd, 0xbd, 0xbd, 0xa5, 0xa0, 0xa5, 0x98, 0x92, 0x91, 0x93, + 0x90, 0x8a, 0x7b, 0x75, 0x72, 0x6a, 0x5e, 0x5b, 0x4d, 0x42, 0x2f, + 0x28, 0x1e, 0x24, 0x25, 0x2d, 0x25, 0x25, 0x21, 0x1f, 0x18, 0x11, + 0x18, 0x13, 0x2a, 0x34, 0x3b, 0x4a, 0x55, 0x57, 0x5a, 0x5f, 0x68, + 0x71, 0x76, 0x85, 0x84, 0x8c, 0x99, 0xab, 0xad, 0xad, 0xac, 0xac, + 0xbf, 0xbc, 0xff, 0xc6, 0xcc, 0xff, 0xff, 0xff, 0xdf, 0xc6, 0xbd, + 0xbd, 0xbd, 0xa0, 0xa5, 0xa0, 0xa1, 0x98, 0x9d, 0x9d, 0xa3, 0x9b, + 0x90, 0x83, 0x7c, 0x72, 0x6a, 0x5d, 0x4d, 0x3e, 0x3a, 0x2f, 0x2c, + 0x33, 0x38, 0x3b, 0x3b, 0x37, 0x36, 0x30, 0x1f, 0x1f, 0x1f, 0x1a, + 0x27, 0x36, 0x43, 0x4c, 0x5a, 0x61, 0x61, 0x68, 0x71, 0x78, 0x85, + 0x8a, 0x93, 0x9a, 0xab, 0xba, 0xb7, 0xb7, 0xb5, 0xb5, 0xd1, 0xcc, + 0xff, 0xbf, 0xcc, 0xff, 0xff, 0xff, 0xdf, 0xc9, 0xc2, 0xbd, 0xc2, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb9, 0xba, 0xab, 0x9a, 0x8e, + 0x85, 0x72, 0x62, 0x58, 0x48, 0x3f, 0x42, 0x44, 0x46, 0x46, 0x46, + 0x46, 0x45, 0x45, 0x45, 0x43, 0x37, 0x30, 0x2b, 0x27, 0x31, 0x40, + 0x4a, 0x57, 0x5a, 0x61, 0x6c, 0x70, 0x70, 0x7e, 0x86, 0x90, 0xa2, + 0xab, 0xc4, 0xe9, 0xf7, 0xf9, 0xe1, 0xdc, 0xfb, 0xfb, 0xff, 0xd1, + 0xdf, 0xff, 0xff, 0xff, 0xdf, 0xc9, 0xc4, 0xc2, 0xc4, 0xbd, 0xbd, + 0xbd, 0xbc, 0xe1, 0xc2, 0xc2, 0xba, 0xab, 0x9a, 0x8f, 0x7c, 0x72, + 0x62, 0x5d, 0x50, 0x51, 0x51, 0x51, 0x50, 0x50, 0x4f, 0x4f, 0x4a, + 0x47, 0x45, 0x4c, 0x3b, 0x38, 0x37, 0x31, 0x38, 0x45, 0x50, 0x5d, + 0x61, 0x66, 0x6c, 0x78, 0x7a, 0x80, 0x90, 0x94, 0xa4, 0xab, 0xc4, + 0xe9, 0xf6, 0xf6, 0xe1, 0xe1, 0xfe, 0xfe, 0xff, 0xfb, 0xfe, 0xff, + 0xff, 0xff, 0xdf, 0xc9, 0xc2, 0xc4, 0xc2, 0xce, 0xe5, 0xce, 0xe1, + 0xe1, 0xdc, 0xdc, 0xcb, 0xa9, 0x9c, 0x8f, 0x74, 0x69, 0x62, 0x5d, + 0x5d, 0x5d, 0x5d, 0x5d, 0x59, 0x5a, 0x5a, 0x5a, 0x52, 0x52, 0x52, + 0x52, 0x43, 0x44, 0x3c, 0x39, 0x3f, 0x4b, 0x57, 0x5e, 0x66, 0x6c, + 0x6c, 0x7a, 0x7d, 0x88, 0x90, 0x9b, 0xa5, 0xaf, 0xc3, 0xf5, 0xdc, + 0xdc, 0xe1, 0xe1, 0xfd, 0xfb, 0xff, 0xfb, 0xfe, 0xff, 0xff, 0xff, + 0xdf, 0xc9, 0xc0, 0xc2, 0xc0, 0xfb, 0xf8, 0xfb, 0xf6, 0xf6, 0xfd, + 0xfe, 0xc4, 0xaa, 0x9c, 0x8d, 0x73, 0x6b, 0x67, 0x62, 0x6a, 0x6e, + 0x6a, 0x6a, 0x65, 0x61, 0x61, 0x61, 0x55, 0x55, 0x52, 0x55, 0x4f, + 0x4b, 0x4b, 0x43, 0x48, 0x50, 0x5b, 0x64, 0x66, 0x70, 0x79, 0x7d, + 0x80, 0x91, 0x94, 0xa3, 0xaa, 0xb9, 0xc8, 0xfb, 0xf6, 0xf6, 0xe1, + 0xe1, 0xfe, 0xfe, 0xff, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xe7, 0xcf, + 0xc9, 0xc9, 0xc9, 0xfc, 0xfc, 0xfc, 0xff, 0xfb, 0xd2, 0xc0, 0xaa, + 0xa0, 0x97, 0x97, 0x81, 0x7a, 0x7d, 0x7a, 0x7a, 0x7a, 0x7a, 0x7a, + 0x70, 0x6d, 0x68, 0x66, 0x61, 0x61, 0x61, 0x61, 0x5f, 0x59, 0x57, + 0x54, 0x54, 0x57, 0x5d, 0x63, 0x70, 0x75, 0x7d, 0x84, 0x91, 0x99, + 0xa4, 0xa9, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, + 0xfc, 0xff, 0xfb, 0xfe, 0xff, 0xff, 0xff, 0xfc, 0xfc, 0xfc, 0xfc, + 0xfc, 0xff, 0xff, 0xff, 0xfb, 0xe5, 0xb7, 0xb0, 0xa0, 0x98, 0x98, + 0x97, 0x8c, 0x89, 0x8a, 0x89, 0x80, 0x80, 0x80, 0x80, 0x78, 0x71, + 0x70, 0x6d, 0x68, 0x68, 0x68, 0x68, 0x65, 0x63, 0x5f, 0x59, 0x57, + 0x5c, 0x63, 0x6b, 0x7d, 0x7f, 0x88, 0x8c, 0x99, 0xa3, 0xa4, 0xb9, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, + 0xff, 0xfc, 0xd2, 0xb7, 0xb0, 0xa1, 0x97, 0x97, 0x98, 0xa0, 0x99, + 0x9a, 0x9a, 0x9a, 0x91, 0x87, 0x87, 0x87, 0x80, 0x7a, 0x75, 0x70, + 0x70, 0x70, 0x70, 0x6c, 0x71, 0x6d, 0x68, 0x68, 0x5a, 0x68, 0x6d, + 0x71, 0x82, 0x88, 0x91, 0x93, 0xa4, 0xa4, 0xb7, 0xc7, 0xff, 0xfc, + 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xff, 0xff, + 0xc2, 0xb0, 0xa5, 0xa0, 0x97, 0x97, 0x98, 0xa7, 0xa9, 0xab, 0xab, + 0xab, 0x9a, 0x95, 0x95, 0x95, 0x8a, 0x82, 0x80, 0x7a, 0x78, 0x75, + 0x75, 0x78, 0x7b, 0x78, 0x71, 0x70, 0x63, 0x6d, 0x71, 0x78, 0x8c, + 0x94, 0x9a, 0xa3, 0xa4, 0xb1, 0xc4, 0xfb, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcd, 0xcd, 0xcd, 0xb6, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xce, 0xe5, 0xe9, 0xe5, 0xbd, + 0xaa, 0xa1, 0x98, 0x8f, 0x87, 0x87, 0x87, 0x81, 0x81, 0x81, 0x81, + 0x80, 0x7e, 0x80, 0x7e, 0x7a, 0x79, 0x7a, 0x79, 0x8f, 0x99, 0xa4, + 0xa9, 0xfb, 0xf5, 0xf6, 0xf6, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, + 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, + 0xff, 0xfc, 0xff, 0xfc, 0xcd, 0xcd, 0xcd, 0xb7, 0xb0, 0xaf, 0xaf, + 0xb9, 0xc0, 0xb7, 0xc0, 0xce, 0xe9, 0xe5, 0xe9, 0xce, 0xba, 0xa8, + 0xa1, 0x95, 0x94, 0x94, 0x9a, 0x89, 0x87, 0x89, 0x87, 0x8a, 0x8c, + 0x8a, 0x8c, 0x80, 0x82, 0x80, 0x82, 0x95, 0xa3, 0xa9, 0xba, 0xfb, + 0xfe, 0xf6, 0xf6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xcd, 0xcd, 0xcd, 0xb6, 0xb0, 0xb0, 0xb0, 0xc2, 0xce, + 0xce, 0xce, 0xee, 0xe4, 0xf6, 0xe4, 0xf6, 0xe1, 0xb3, 0xac, 0xa4, + 0xa2, 0xa2, 0xa2, 0x99, 0x99, 0x91, 0x99, 0x91, 0x90, 0x9a, 0x90, + 0x91, 0x87, 0x89, 0x87, 0x9c, 0xa4, 0xb7, 0xd2, 0xf0, 0xf0, 0xe1, + 0xe1, 0xfc, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, + 0xcd, 0xcd, 0xcd, 0xb7, 0xb0, 0xaf, 0xaf, 0xc9, 0xf8, 0xf8, 0xf8, + 0xfa, 0xf6, 0xe4, 0xf6, 0xfd, 0xf6, 0xe1, 0xb3, 0xad, 0xb1, 0xb1, + 0xb1, 0xa2, 0xa2, 0xa3, 0xa2, 0xa3, 0xa3, 0x9b, 0xa3, 0x94, 0x9a, + 0x94, 0x9a, 0xa4, 0xb9, 0xc7, 0xfe, 0xf0, 0xf0, 0xe1, 0xe1, 0xfc, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xd4, 0xdd, + 0xd4, 0xf7, 0xf9, 0xfe, 0xf9, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, + 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xf7, 0xfe, 0xf9, 0xfe, 0xf9, 0xb5, + 0xb2, 0xb2, 0xb2, 0xb0, 0xac, 0xb0, 0xb0, 0xac, 0xa6, 0xac, 0xa6, + 0xa6, 0xb5, 0xcd, 0xfc, 0xff, 0xfc, 0xff, 0xf7, 0xff, 0xfc, 0xff, + 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xe0, 0xd4, 0xe0, 0xf0, + 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, + 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc8, 0xc8, 0xc8, + 0xc8, 0xbc, 0xc6, 0xbc, 0xc6, 0xb3, 0xb3, 0xb3, 0xb3, 0xa6, 0xb5, + 0xcd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xd4, 0xe0, 0xd4, 0xf7, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xe0, 0xf0, 0xe0, 0xef, + 0xee, 0xef, 0xee, 0xc6, 0xc6, 0xc6, 0xc6, 0xa6, 0xb5, 0xcd, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xe0, 0xd4, 0xe0, 0xf7, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf8, 0xf8, 0xef, 0xf8, 0xa6, 0xb5, 0xcd, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xf7, 0xff, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, + 0xfe, 0xfe, 0xfe, 0xc8, 0xc8, 0xcd, 0xcd, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, + 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, + 0xfc, 0xff, 0xfc, 0xff, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, + 0xfb, 0xcd, 0xcd, 0xcd, 0xcd, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, + 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, + 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, + 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xcd, 0xcd, + 0xcd, 0xf6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, + 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, + 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfb, + 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xcd, 0xcd, 0xcd, 0xcd, + 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, + 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xfc, + 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, + 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, + 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xd4, 0xd4, 0xd4, 0xd4, 0xff, 0xfc, + 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, + 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xfc, 0xff, + 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, + 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, + 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, + 0xff, 0xfc, 0xff, 0xdb, 0xdb, 0xd4, 0xdb, 0xfc, 0xff, 0xfc, 0xff, + 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0xfc, + 0xff, 0xff, 0xff, 0xff +}; diff --git a/utils/hlmv/muzzle3.h b/utils/hlmv/muzzle3.h new file mode 100644 index 0000000..9bdf36c --- /dev/null +++ b/utils/hlmv/muzzle3.h @@ -0,0 +1,474 @@ +const unsigned char muzzleflash3_bmp[] = +{ + 0x42, 0x4d, 0x36, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, + 0x04, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0xff, 0xff, 0x00, 0x08, 0x08, 0x08, 0x00, 0x10, 0x10, 0x21, 0x00, + 0x08, 0x08, 0x18, 0x00, 0x08, 0x08, 0x21, 0x00, 0x18, 0x18, 0x73, + 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x21, 0x00, 0x00, 0x00, 0x31, 0x00, 0x42, 0x4a, 0x9c, 0x00, 0x00, + 0x08, 0x5a, 0x00, 0x00, 0x08, 0x6b, 0x00, 0x21, 0x29, 0x73, 0x00, + 0x18, 0x21, 0x73, 0x00, 0x00, 0x08, 0x4a, 0x00, 0x18, 0x21, 0x5a, + 0x00, 0x10, 0x18, 0x52, 0x00, 0x00, 0x08, 0x42, 0x00, 0x00, 0x10, + 0x7b, 0x00, 0x18, 0x21, 0x52, 0x00, 0x29, 0x39, 0x94, 0x00, 0x21, + 0x31, 0x8c, 0x00, 0x10, 0x21, 0x84, 0x00, 0x08, 0x18, 0x73, 0x00, + 0x00, 0x10, 0x6b, 0x00, 0x21, 0x31, 0x7b, 0x00, 0x21, 0x39, 0xa5, + 0x00, 0x00, 0x18, 0x84, 0x00, 0x10, 0x18, 0x39, 0x00, 0x29, 0x42, + 0xad, 0x00, 0x18, 0x29, 0x6b, 0x00, 0x08, 0x18, 0x5a, 0x00, 0x08, + 0x21, 0x8c, 0x00, 0x00, 0x10, 0x52, 0x00, 0x00, 0x18, 0x73, 0x00, + 0x18, 0x21, 0x42, 0x00, 0x52, 0x73, 0xe7, 0x00, 0x31, 0x4a, 0x9c, + 0x00, 0x39, 0x5a, 0xc6, 0x00, 0x18, 0x31, 0x84, 0x00, 0x4a, 0x5a, + 0x8c, 0x00, 0x39, 0x5a, 0xbd, 0x00, 0x29, 0x4a, 0xad, 0x00, 0x08, + 0x10, 0x29, 0x00, 0x10, 0x21, 0x52, 0x00, 0x10, 0x31, 0x94, 0x00, + 0x08, 0x29, 0x94, 0x00, 0x00, 0x10, 0x42, 0x00, 0x52, 0x6b, 0xad, + 0x00, 0x4a, 0x73, 0xde, 0x00, 0x29, 0x4a, 0xa5, 0x00, 0x29, 0x52, + 0xbd, 0x00, 0x21, 0x42, 0x9c, 0x00, 0x29, 0x52, 0xc6, 0x00, 0x10, + 0x39, 0xa5, 0x00, 0x08, 0x21, 0x63, 0x00, 0x08, 0x31, 0x9c, 0x00, + 0x00, 0x18, 0x5a, 0x00, 0x39, 0x63, 0xc6, 0x00, 0x31, 0x63, 0xde, + 0x00, 0x18, 0x4a, 0xbd, 0x00, 0x21, 0x31, 0x52, 0x00, 0x39, 0x6b, + 0xd6, 0x00, 0x08, 0x18, 0x39, 0x00, 0x10, 0x39, 0x94, 0x00, 0x10, + 0x42, 0xad, 0x00, 0x08, 0x21, 0x5a, 0x00, 0x00, 0x10, 0x31, 0x00, + 0x00, 0x21, 0x6b, 0x00, 0x31, 0x42, 0x63, 0x00, 0x52, 0x73, 0xb5, + 0x00, 0x6b, 0x9c, 0xf7, 0x00, 0x31, 0x4a, 0x7b, 0x00, 0x39, 0x5a, + 0x9c, 0x00, 0x52, 0x84, 0xe7, 0x00, 0x39, 0x73, 0xe7, 0x00, 0x10, + 0x21, 0x42, 0x00, 0x29, 0x5a, 0xbd, 0x00, 0x29, 0x63, 0xce, 0x00, + 0x21, 0x63, 0xe7, 0x00, 0x08, 0x39, 0x94, 0x00, 0x00, 0x08, 0x18, + 0x00, 0x00, 0x39, 0xad, 0x00, 0x00, 0x42, 0xbd, 0x00, 0x5a, 0x94, + 0xf7, 0x00, 0x21, 0x39, 0x63, 0x00, 0x42, 0x73, 0xc6, 0x00, 0x21, + 0x4a, 0x94, 0x00, 0x10, 0x4a, 0xad, 0x00, 0x08, 0x31, 0x7b, 0x00, + 0x08, 0x52, 0xce, 0x00, 0x00, 0x29, 0x73, 0x00, 0x6b, 0x9c, 0xe7, + 0x00, 0x5a, 0x84, 0xc6, 0x00, 0x29, 0x42, 0x6b, 0x00, 0x52, 0x8c, + 0xe7, 0x00, 0x31, 0x63, 0xb5, 0x00, 0x29, 0x73, 0xe7, 0x00, 0x08, + 0x18, 0x31, 0x00, 0x10, 0x31, 0x63, 0x00, 0x08, 0x42, 0x9c, 0x00, + 0x6b, 0xa5, 0xf7, 0x00, 0x5a, 0x8c, 0xd6, 0x00, 0x42, 0x6b, 0xa5, + 0x00, 0x29, 0x4a, 0x7b, 0x00, 0x42, 0x8c, 0xf7, 0x00, 0x31, 0x6b, + 0xbd, 0x00, 0x39, 0x7b, 0xde, 0x00, 0x21, 0x5a, 0xad, 0x00, 0x18, + 0x4a, 0x94, 0x00, 0x21, 0x6b, 0xd6, 0x00, 0x10, 0x39, 0x73, 0x00, + 0x18, 0x5a, 0xb5, 0x00, 0x10, 0x42, 0x8c, 0x00, 0x18, 0x63, 0xce, + 0x00, 0x10, 0x63, 0xd6, 0x00, 0x00, 0x39, 0x8c, 0x00, 0x6b, 0x9c, + 0xde, 0x00, 0x29, 0x5a, 0x9c, 0x00, 0x10, 0x52, 0xa5, 0x00, 0x08, + 0x5a, 0xc6, 0x00, 0x00, 0x4a, 0xad, 0x00, 0x94, 0xc6, 0xff, 0x00, + 0x63, 0x94, 0xce, 0x00, 0x52, 0x9c, 0xf7, 0x00, 0x29, 0x52, 0x84, + 0x00, 0x42, 0x8c, 0xe7, 0x00, 0x29, 0x73, 0xce, 0x00, 0x21, 0x73, + 0xd6, 0x00, 0x21, 0x84, 0xf7, 0x00, 0x08, 0x31, 0x63, 0x00, 0x08, + 0x73, 0xef, 0x00, 0x00, 0x4a, 0xa5, 0x00, 0x42, 0x52, 0x63, 0x00, + 0x73, 0xb5, 0xff, 0x00, 0x21, 0x39, 0x52, 0x00, 0x63, 0xad, 0xff, + 0x00, 0x52, 0x94, 0xde, 0x00, 0x4a, 0x8c, 0xd6, 0x00, 0x42, 0x94, + 0xef, 0x00, 0x31, 0x73, 0xbd, 0x00, 0x31, 0x8c, 0xef, 0x00, 0x21, + 0x6b, 0xbd, 0x00, 0x21, 0x8c, 0xff, 0x00, 0x08, 0x5a, 0xb5, 0x00, + 0x00, 0x10, 0x21, 0x00, 0x00, 0x6b, 0xde, 0x00, 0x8c, 0xc6, 0xff, + 0x00, 0x18, 0x29, 0x39, 0x00, 0x39, 0x63, 0x8c, 0x00, 0x29, 0x5a, + 0x8c, 0x00, 0x4a, 0xa5, 0xff, 0x00, 0x39, 0x84, 0xce, 0x00, 0x31, + 0x7b, 0xc6, 0x00, 0x29, 0x6b, 0xad, 0x00, 0x18, 0x42, 0x6b, 0x00, + 0x29, 0x73, 0xbd, 0x00, 0x18, 0x5a, 0x9c, 0x00, 0x08, 0x31, 0x5a, + 0x00, 0x00, 0x21, 0x42, 0x00, 0x00, 0x29, 0x52, 0x00, 0x00, 0x42, + 0x84, 0x00, 0x39, 0x8c, 0xd6, 0x00, 0x31, 0x94, 0xef, 0x00, 0x29, + 0x84, 0xd6, 0x00, 0x18, 0x5a, 0x94, 0x00, 0x18, 0x7b, 0xd6, 0x00, + 0xa5, 0xd6, 0xff, 0x00, 0x63, 0xad, 0xe7, 0x00, 0x6b, 0xbd, 0xff, + 0x00, 0x21, 0x52, 0x7b, 0x00, 0x39, 0x9c, 0xef, 0x00, 0x18, 0x4a, + 0x73, 0x00, 0x29, 0x84, 0xce, 0x00, 0x10, 0x42, 0x6b, 0x00, 0x08, + 0x42, 0x73, 0x00, 0x84, 0xbd, 0xe7, 0x00, 0x39, 0x7b, 0xad, 0x00, + 0x39, 0x84, 0xbd, 0x00, 0x42, 0xa5, 0xef, 0x00, 0x39, 0xad, 0xff, + 0x00, 0x31, 0x94, 0xde, 0x00, 0x18, 0x52, 0x7b, 0x00, 0x21, 0x94, + 0xe7, 0x00, 0x10, 0x84, 0xd6, 0x00, 0x84, 0xce, 0xff, 0x00, 0x73, + 0xbd, 0xef, 0x00, 0x5a, 0xa5, 0xd6, 0x00, 0x5a, 0xad, 0xe7, 0x00, + 0x18, 0x31, 0x42, 0x00, 0x52, 0xb5, 0xf7, 0x00, 0x42, 0xa5, 0xe7, + 0x00, 0x52, 0x6b, 0x7b, 0x00, 0x73, 0xb5, 0xde, 0x00, 0x6b, 0xbd, + 0xef, 0x00, 0x52, 0x94, 0xbd, 0x00, 0x63, 0xb5, 0xe7, 0x00, 0x31, + 0xa5, 0xef, 0x00, 0x18, 0x73, 0xad, 0x00, 0x8c, 0xd6, 0xff, 0x00, + 0x73, 0xce, 0xff, 0x00, 0x5a, 0xb5, 0xe7, 0x00, 0x52, 0xad, 0xde, + 0x00, 0x42, 0x9c, 0xce, 0x00, 0x31, 0x8c, 0xbd, 0x00, 0x84, 0xd6, + 0xff, 0x00, 0x6b, 0xce, 0xff, 0x00, 0x5a, 0xbd, 0xef, 0x00, 0x39, + 0x7b, 0x9c, 0x00, 0x42, 0xa5, 0xd6, 0x00, 0x21, 0x84, 0xb5, 0x00, + 0x84, 0xce, 0xef, 0x00, 0x73, 0xce, 0xf7, 0x00, 0x29, 0x73, 0x94, + 0x00, 0x8c, 0xde, 0xff, 0x00, 0x84, 0xd6, 0xf7, 0x00, 0x73, 0xd6, + 0xff, 0x00, 0x52, 0xa5, 0xc6, 0x00, 0x8c, 0xd6, 0xef, 0x00, 0x6b, + 0xc6, 0xe7, 0x00, 0xbd, 0xef, 0xff, 0x00, 0xb5, 0xe7, 0xf7, 0x00, + 0xa5, 0xde, 0xef, 0x00, 0x9c, 0xe7, 0xff, 0x00, 0x7b, 0xce, 0xe7, + 0x00, 0x4a, 0xb5, 0xd6, 0x00, 0x08, 0x21, 0x29, 0x00, 0x7b, 0xbd, + 0xce, 0x00, 0x52, 0xa5, 0xbd, 0x00, 0x10, 0x31, 0x39, 0x00, 0x08, + 0x29, 0x31, 0x00, 0x00, 0x21, 0x29, 0x00, 0xce, 0xf7, 0xff, 0x00, + 0xa5, 0xef, 0xff, 0x00, 0x94, 0xe7, 0xf7, 0x00, 0xbd, 0xf7, 0xff, + 0x00, 0x84, 0xef, 0xff, 0x00, 0x63, 0xd6, 0xe7, 0x00, 0xb5, 0xf7, + 0xff, 0x00, 0xa5, 0xf7, 0xff, 0x00, 0x9c, 0xf7, 0xff, 0x00, 0x8c, + 0xe7, 0xef, 0x00, 0x73, 0xe7, 0xef, 0x00, 0xf7, 0xff, 0xff, 0x00, + 0xef, 0xff, 0xff, 0x00, 0xde, 0xff, 0xff, 0x00, 0xce, 0xff, 0xff, + 0x00, 0xbd, 0xff, 0xff, 0x00, 0x94, 0xff, 0xff, 0x00, 0xbd, 0xff, + 0xf7, 0x00, 0xd6, 0xf7, 0xef, 0x00, 0x18, 0x31, 0x29, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x10, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, 0x06, 0x06, 0x06, 0x06, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x06, 0x06, 0xff, 0xff, 0x06, 0x06, 0x06, 0xff, + 0xff, 0x06, 0x06, 0x01, 0x01, 0x06, 0x06, 0x06, 0x06, 0xff, 0x06, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x06, 0xff, 0x06, 0xff, 0x06, 0xff, 0xff, 0x06, 0xff, 0x06, + 0x06, 0x01, 0x06, 0x01, 0x01, 0x01, 0xff, 0x06, 0x06, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x07, 0x07, 0x07, 0x07, 0x06, 0xff, 0x06, 0x06, 0x06, 0xff, 0x06, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x06, + 0x07, 0x07, 0x06, 0x06, 0x06, 0x06, 0xff, 0x06, 0xff, 0xff, 0x06, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x01, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x06, 0x06, 0x06, 0x06, 0x06, 0xff, 0x06, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x06, 0x06, 0x06, 0x06, 0x07, 0x52, 0x52, 0x92, 0x52, 0x07, + 0x07, 0x07, 0xfe, 0x03, 0x92, 0x92, 0x52, 0x03, 0x03, 0x92, 0x52, + 0x07, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, + 0x01, 0x07, 0x06, 0x52, 0x52, 0x92, 0x2c, 0x2c, 0x92, 0x52, 0x07, + 0x03, 0x02, 0xe3, 0xe8, 0x04, 0x02, 0x02, 0xe8, 0x92, 0x52, 0x07, + 0x07, 0x92, 0x92, 0x03, 0x04, 0x03, 0x07, 0x06, 0x06, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, 0x06, 0x07, + 0x07, 0x52, 0x52, 0x08, 0x2c, 0x63, 0x2c, 0x52, 0x52, 0x02, 0x02, + 0xe7, 0xe3, 0x2c, 0x02, 0x02, 0xe7, 0x92, 0x52, 0x52, 0x52, 0x04, + 0x04, 0x02, 0x02, 0x02, 0x07, 0x07, 0x06, 0xff, 0x06, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, 0x01, 0x06, 0x07, 0x07, + 0x52, 0x92, 0x1d, 0x24, 0xe8, 0x92, 0x52, 0x01, 0x02, 0xe3, 0xe7, + 0xe3, 0xe3, 0x04, 0xe3, 0x52, 0x52, 0x08, 0x08, 0x09, 0x08, 0xa0, + 0xe7, 0x92, 0x07, 0x06, 0x06, 0x06, 0xff, 0xff, 0xff, 0xff, 0xfd, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x01, 0x06, 0x06, 0x03, 0x52, 0x52, 0x08, + 0x4d, 0x88, 0xe7, 0x52, 0x07, 0xff, 0x01, 0xe3, 0xe6, 0xe3, 0xe3, + 0x52, 0xfc, 0x06, 0x07, 0x2c, 0x63, 0x09, 0x40, 0xaf, 0xaf, 0x92, + 0x06, 0x06, 0x06, 0x07, 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x06, 0xff, 0x07, 0x01, 0x03, 0x52, 0x92, 0x1d, 0x88, + 0x95, 0xe8, 0x92, 0x01, 0x02, 0xe6, 0x88, 0x95, 0x95, 0x2c, 0xfc, + 0x07, 0x92, 0x1d, 0x24, 0x14, 0x46, 0x5f, 0x9f, 0x52, 0xff, 0x06, + 0x06, 0x07, 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, + 0x06, 0x06, 0x07, 0x03, 0x2c, 0xe3, 0x92, 0x43, 0x46, 0x9c, 0xa1, + 0xa0, 0x44, 0x63, 0x9f, 0xaf, 0x9f, 0x64, 0x40, 0x3e, 0x1d, 0x09, + 0xa1, 0xa1, 0xad, 0xd1, 0xc1, 0x03, 0x06, 0x07, 0x07, 0x06, 0x06, + 0x06, 0xff, 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, 0xff, 0x06, 0xff, 0x06, 0x06, + 0x07, 0x03, 0x2c, 0x1d, 0x1d, 0xe7, 0x5f, 0x69, 0x83, 0xa1, 0x40, + 0x0f, 0xad, 0xb7, 0xad, 0x0d, 0x38, 0x3e, 0x11, 0x40, 0x45, 0xad, + 0xd6, 0x68, 0x86, 0x01, 0x07, 0x52, 0x07, 0x07, 0x06, 0x06, 0xff, + 0xff, 0xfd, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x06, 0xff, 0xff, 0x06, 0x06, 0x06, 0x07, 0x03, + 0x04, 0x63, 0x24, 0xa1, 0x56, 0x96, 0x69, 0xaf, 0x2d, 0x40, 0xb7, + 0x97, 0x97, 0x49, 0x56, 0x14, 0x14, 0xb0, 0x45, 0xb2, 0xc4, 0xd6, + 0xbe, 0x07, 0x92, 0x02, 0x52, 0x07, 0x06, 0xff, 0x06, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x06, 0x06, 0x06, 0x07, 0x06, 0x07, 0x06, 0x07, 0x04, 0x08, 0x40, + 0x3e, 0x9f, 0x9f, 0x9b, 0x8d, 0x6d, 0x70, 0x3a, 0x78, 0x9d, 0x9b, + 0x34, 0x6d, 0xa1, 0x83, 0x72, 0xc7, 0xc3, 0xbc, 0xb0, 0x52, 0x08, + 0x02, 0x02, 0x04, 0x07, 0x06, 0xff, 0xff, 0xff, 0xfd, 0xfd, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x52, 0x04, 0x08, 0x40, 0x14, 0x64, + 0x83, 0x8d, 0xcb, 0xa3, 0x2b, 0x72, 0x9d, 0x7f, 0x7f, 0x3b, 0x3f, + 0x70, 0x97, 0x9b, 0xc5, 0xce, 0xcd, 0xa1, 0x08, 0x1d, 0x95, 0xfc, + 0x04, 0x01, 0x06, 0xff, 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0x52, 0x07, 0x07, + 0x07, 0x52, 0x52, 0x52, 0x03, 0x92, 0x63, 0x1d, 0x9f, 0x7e, 0x9a, + 0x89, 0x66, 0x67, 0x61, 0x7f, 0x89, 0x89, 0x4b, 0x55, 0x68, 0x7c, + 0xbd, 0xd9, 0xbd, 0xb7, 0x83, 0x4d, 0x46, 0xbe, 0x95, 0xfc, 0x03, + 0xff, 0xff, 0xff, 0xff, 0xfd, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x06, 0x01, 0x07, 0x52, 0x08, 0x2c, 0xe3, + 0xe8, 0xa0, 0xa0, 0x0f, 0x22, 0x45, 0x72, 0x57, 0xcc, 0xd5, 0xd7, + 0xf3, 0xda, 0xe2, 0xd5, 0xd9, 0xbf, 0xd0, 0xe5, 0xe1, 0xe0, 0xd5, + 0x8f, 0x75, 0x6b, 0x31, 0x7e, 0x2d, 0x40, 0x08, 0x52, 0x07, 0x07, + 0x07, 0x06, 0x06, 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x07, 0x07, 0x52, 0x52, 0x52, 0xe8, 0xe7, 0x2d, + 0x64, 0xaf, 0x45, 0x45, 0x75, 0x57, 0xcb, 0xcf, 0xf0, 0xeb, 0xdc, + 0xee, 0xed, 0xf9, 0xee, 0xd9, 0xf2, 0xf0, 0xe0, 0xd0, 0x78, 0x65, + 0x71, 0x4a, 0x0e, 0x43, 0x11, 0x30, 0x09, 0x08, 0x52, 0x07, 0x52, + 0x01, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x07, 0x07, 0x52, 0x52, 0x08, 0xe8, 0xe8, 0xa1, 0x56, 0xab, + 0x28, 0x5a, 0x72, 0x61, 0xca, 0xd8, 0xf0, 0xf0, 0xf2, 0xed, 0xeb, + 0xf9, 0xf3, 0xed, 0xf0, 0xf8, 0xd7, 0xa3, 0x71, 0x6b, 0x59, 0x28, + 0x3a, 0x43, 0x4d, 0x40, 0x40, 0x08, 0x08, 0x52, 0x07, 0x07, 0x01, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, 0x06, 0x06, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xff, 0xfd, 0xff, 0xfe, + 0xfe, 0x03, 0x04, 0x08, 0x44, 0xa0, 0x45, 0xab, 0x6d, 0x7a, 0x85, + 0x71, 0x71, 0xc5, 0xd7, 0xf0, 0xfa, 0xf7, 0xfa, 0xfa, 0xfa, 0xfa, + 0xfa, 0xfa, 0xdf, 0xd0, 0xb4, 0xbf, 0x8e, 0x53, 0x19, 0x19, 0x0e, + 0x38, 0x0b, 0x4d, 0x09, 0x08, 0x52, 0x52, 0x01, 0x01, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xfd, 0xfd, 0xff, 0xfd, 0xfe, 0x01, 0x03, + 0x04, 0x04, 0x44, 0x40, 0x3a, 0x72, 0x6d, 0x73, 0x80, 0xac, 0xa5, + 0xc5, 0xce, 0xdf, 0xf7, 0xfb, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xdf, + 0xef, 0xd7, 0x98, 0xb6, 0x5b, 0x54, 0x28, 0x18, 0x18, 0x05, 0x38, + 0x0f, 0x12, 0x08, 0x52, 0x07, 0x01, 0xfd, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x06, 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfd, 0xff, 0xff, 0xfd, 0xfd, 0xfe, 0xfe, 0x03, 0x04, 0x08, + 0x92, 0x44, 0x3a, 0x5a, 0x71, 0x80, 0x7d, 0xaa, 0x8c, 0xd0, 0xd8, + 0xea, 0xfb, 0xf7, 0xfb, 0xfa, 0xfb, 0xfa, 0xfa, 0xfa, 0xf0, 0xd7, + 0xaa, 0x81, 0x5b, 0x54, 0x15, 0x15, 0x16, 0x38, 0x18, 0x11, 0x0f, + 0x08, 0x52, 0x52, 0x01, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, 0x06, 0x06, 0x06, 0x06, 0xff, + 0xff, 0xff, 0x06, 0x06, 0x07, 0x07, 0x52, 0x08, 0x08, 0x0f, 0x0f, + 0x19, 0x5a, 0x51, 0x8f, 0x8c, 0xcf, 0xf9, 0xdf, 0xdf, 0xec, 0x00, + 0xf4, 0xf4, 0xf4, 0xf4, 0xf5, 0xf4, 0xfa, 0xfa, 0xc3, 0xd2, 0xae, + 0x6f, 0x74, 0x36, 0x34, 0x37, 0x17, 0x18, 0x0b, 0x22, 0x08, 0x08, + 0x08, 0x07, 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x06, 0x06, 0xff, 0xff, 0xff, 0x06, 0xff, 0xff, 0xff, + 0x07, 0x07, 0x08, 0x92, 0x12, 0x09, 0x12, 0x0f, 0x0b, 0x23, 0x21, + 0x71, 0x81, 0x8c, 0xcf, 0xed, 0xf0, 0xec, 0xe9, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xef, 0xf1, 0xd8, 0xbf, 0xa5, 0x62, 0x74, + 0x34, 0x3d, 0x2f, 0x17, 0x13, 0x20, 0x22, 0x09, 0x08, 0x08, 0x07, + 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0xff, 0x06, 0x07, 0x07, 0x07, + 0x08, 0x08, 0x63, 0x12, 0x40, 0x38, 0x1f, 0x2e, 0x65, 0x6d, 0x8f, + 0xb6, 0xe2, 0xd8, 0xde, 0xec, 0xe9, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf4, 0xfa, 0xfa, 0xd7, 0xd0, 0xb6, 0x6f, 0x74, 0x3d, 0x42, + 0x2e, 0x28, 0x17, 0x38, 0x0b, 0x30, 0x09, 0x08, 0x01, 0x07, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x92, 0x92, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x52, 0x08, 0x08, 0x22, 0x0b, + 0x45, 0x45, 0x5c, 0xa1, 0x5a, 0xb0, 0x77, 0xb3, 0xbc, 0x7c, 0xe4, + 0xdb, 0xf6, 0xf4, 0xf4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xf7, 0xf7, 0xea, 0xa8, 0xe5, 0x9b, 0x6e, 0x72, 0x5a, 0x5a, 0x51, + 0x5c, 0x45, 0x0b, 0x0f, 0x09, 0x09, 0x08, 0x08, 0x07, 0x07, 0x07, + 0x07, 0x07, 0xff, 0xff, 0xff, 0xff, 0x06, 0x01, 0x04, 0x04, 0x02, + 0x04, 0x04, 0x40, 0xa0, 0x4d, 0x43, 0x64, 0x6d, 0x61, 0x9a, 0x57, + 0x8b, 0xbc, 0xa9, 0xbb, 0xd5, 0x7b, 0xeb, 0xa8, 0xf8, 0xde, 0xf6, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf4, 0xf4, 0xfa, 0xf7, + 0xfa, 0xdf, 0xb1, 0xb1, 0xc2, 0x76, 0x67, 0x57, 0x6b, 0x61, 0x26, + 0x58, 0x70, 0x2d, 0x11, 0x30, 0x1d, 0x2c, 0x2c, 0x04, 0x2c, 0x2c, + 0x02, 0x03, 0x03, 0x03, 0x2c, 0x63, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x56, 0x56, 0x5f, 0x69, 0x49, 0x8d, 0x57, 0x8b, 0x8a, 0xc3, 0xbb, + 0xba, 0xd7, 0xc8, 0xd7, 0xea, 0xf8, 0xf7, 0xec, 0xf5, 0xf4, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb, 0xf7, 0xf8, 0xec, + 0xef, 0xfa, 0xea, 0xa8, 0x7b, 0x94, 0x87, 0x66, 0x5d, 0x67, 0x5e, + 0x47, 0x68, 0x29, 0x7e, 0x46, 0x46, 0x46, 0x46, 0x46, 0xbe, 0x95, + 0x95, 0x95, 0xff, 0xff, 0xff, 0x06, 0xff, 0x06, 0x06, 0x06, 0x07, + 0x08, 0x44, 0x40, 0x19, 0x23, 0x39, 0x71, 0x4f, 0x74, 0x82, 0xac, + 0xb4, 0xd9, 0xf0, 0xef, 0xf6, 0xf7, 0xf5, 0xf4, 0xf4, 0xf4, 0xf4, + 0xf5, 0xf4, 0xf4, 0xf4, 0x00, 0xf5, 0xf6, 0xef, 0xf0, 0xdb, 0xcb, + 0xb3, 0x61, 0x35, 0x28, 0x1a, 0x0d, 0x10, 0x10, 0x24, 0x1d, 0x2c, + 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x06, 0xff, 0x06, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, 0x06, 0x52, 0x08, + 0x44, 0x3a, 0x70, 0x39, 0x3d, 0x62, 0x84, 0x90, 0xc6, 0xbf, 0xcf, + 0xf0, 0xef, 0xf6, 0xf6, 0xf5, 0xf5, 0xf5, 0xf4, 0xf5, 0xf5, 0xf4, + 0xf4, 0xf4, 0x00, 0xf6, 0xf6, 0xef, 0xf0, 0xeb, 0xd2, 0xd3, 0x6e, + 0x13, 0x0b, 0x22, 0x11, 0x12, 0x4d, 0x2c, 0x52, 0x07, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, 0x06, 0x52, 0x08, 0x40, 0x5c, + 0x16, 0x71, 0x4f, 0x3c, 0x93, 0x84, 0xa7, 0xb8, 0xcf, 0xe0, 0xf0, + 0xfb, 0xfb, 0xf6, 0xf4, 0xf4, 0xf4, 0xf5, 0x00, 0x00, 0xf4, 0xf4, + 0xf4, 0xf6, 0xf6, 0xef, 0xe0, 0xf1, 0xd9, 0xcb, 0x32, 0x33, 0x0e, + 0x10, 0x22, 0x09, 0x09, 0x08, 0x52, 0x07, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x06, 0x52, 0x08, 0x44, 0x3a, 0x1f, 0x6e, + 0x61, 0x57, 0x4f, 0x7f, 0x60, 0x66, 0x87, 0x87, 0x66, 0xd4, 0xe0, + 0xec, 0xe9, 0xf7, 0xf7, 0xf7, 0xf6, 0xf6, 0xf6, 0xe9, 0xdd, 0xf9, + 0xf2, 0xd9, 0xcf, 0xaa, 0x89, 0x6a, 0x25, 0x27, 0x1e, 0x28, 0x18, + 0x12, 0x12, 0x09, 0x08, 0x08, 0x07, 0x06, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x06, 0x06, 0x52, 0x08, 0x63, 0x3a, 0x1f, 0x16, 0x77, 0x61, + 0x8f, 0x99, 0x7f, 0x6c, 0x99, 0x9d, 0x9a, 0xda, 0xd8, 0xdd, 0xdd, + 0xdd, 0xf7, 0xf7, 0xf6, 0xf7, 0xf7, 0xfb, 0xde, 0xf9, 0xf3, 0xbf, + 0xac, 0xa4, 0x4c, 0x50, 0x27, 0x1b, 0x35, 0x16, 0x28, 0x64, 0x0f, + 0x30, 0x30, 0x44, 0x07, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, + 0x06, 0x07, 0x08, 0x40, 0x43, 0x1f, 0x16, 0x33, 0x6e, 0x78, 0x78, + 0x37, 0x65, 0x51, 0x78, 0x9d, 0xda, 0xdb, 0xdf, 0xdf, 0xde, 0xfa, + 0xf7, 0xf6, 0xfb, 0xde, 0xdd, 0xde, 0xf3, 0xee, 0xb4, 0xa7, 0x74, + 0x42, 0x54, 0x3d, 0x37, 0x21, 0x0e, 0x70, 0x1f, 0x20, 0x43, 0x40, + 0x30, 0x04, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x06, 0xff, 0x06, 0xff, 0x06, 0x06, 0x52, + 0x08, 0x1d, 0x14, 0x3e, 0x1f, 0x1f, 0x43, 0x43, 0x3a, 0x19, 0x19, + 0x21, 0x4e, 0x4b, 0x66, 0x66, 0x89, 0x87, 0xba, 0xe0, 0xf0, 0xea, + 0xe0, 0xd4, 0xf0, 0xbc, 0x8b, 0xbc, 0x48, 0x3b, 0x2b, 0x28, 0x19, + 0x1d, 0x24, 0x2d, 0x0b, 0x0b, 0x22, 0x0f, 0x0f, 0x40, 0x44, 0x2c, + 0x52, 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x06, 0xff, 0xff, 0x07, 0x06, 0x07, 0x08, 0x40, + 0x0f, 0x30, 0x30, 0x12, 0x30, 0x0f, 0x0f, 0x19, 0x23, 0x35, 0x4e, + 0x32, 0x67, 0x60, 0x8b, 0x55, 0x87, 0xd7, 0xf1, 0xf0, 0xce, 0xa9, + 0xd7, 0xdc, 0xb2, 0x61, 0x57, 0x34, 0x2b, 0x28, 0x0b, 0x2c, 0x02, + 0x4d, 0x22, 0x0f, 0x12, 0x0f, 0x30, 0x44, 0x12, 0x04, 0x08, 0xff, + 0xff, 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x06, 0xfd, 0x06, 0xff, 0x06, 0x06, 0x52, 0x08, 0x63, 0x12, 0x09, + 0x09, 0x08, 0x09, 0x12, 0x0f, 0x0c, 0x5a, 0x2b, 0x4e, 0x4e, 0x9a, + 0x3b, 0x9a, 0x7f, 0x89, 0xd5, 0xd7, 0xf0, 0xbb, 0xda, 0xc3, 0xd5, + 0x6d, 0xa2, 0x2e, 0x37, 0x1b, 0x0e, 0x0b, 0x2c, 0x52, 0x44, 0x0f, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x08, 0x52, 0x06, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, 0xff, 0xff, + 0xff, 0x06, 0xff, 0x06, 0x06, 0x07, 0x52, 0x08, 0x08, 0x08, 0x08, + 0x12, 0x12, 0x40, 0x38, 0x1a, 0x26, 0x15, 0x16, 0x35, 0x33, 0x34, + 0x57, 0x55, 0x89, 0x87, 0xd7, 0xb4, 0x81, 0xa3, 0xc9, 0x2a, 0x13, + 0x18, 0x18, 0x1a, 0x0d, 0x10, 0x43, 0x12, 0x08, 0x12, 0x40, 0x4d, + 0x63, 0x63, 0x09, 0x08, 0x04, 0x03, 0x06, 0xff, 0xff, 0x06, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, 0xff, 0x06, 0xff, + 0x06, 0x06, 0x06, 0x07, 0x06, 0x52, 0x08, 0x08, 0x09, 0x12, 0x30, + 0x14, 0x0e, 0x15, 0x15, 0x17, 0x17, 0x5a, 0x21, 0x41, 0x36, 0x6c, + 0x6a, 0x98, 0xd9, 0x81, 0x91, 0x9a, 0xbf, 0x27, 0x13, 0x0b, 0x22, + 0x05, 0x0d, 0x1a, 0x0d, 0x43, 0x09, 0x09, 0x44, 0x63, 0x09, 0x2c, + 0x2c, 0x08, 0x03, 0x04, 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x06, 0x06, 0x08, 0x08, 0x44, 0x12, 0x40, 0x11, 0x10, 0x28, + 0x1a, 0x0e, 0x19, 0x18, 0x13, 0x0c, 0x2f, 0x42, 0x36, 0x80, 0x8e, + 0xc5, 0x7a, 0x85, 0x59, 0xb4, 0x25, 0x21, 0x0b, 0x22, 0x18, 0x10, + 0x46, 0x69, 0x1f, 0x30, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x04, 0x03, 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, 0xff, 0x06, 0x06, + 0x06, 0x07, 0x07, 0x08, 0x44, 0x44, 0x11, 0x11, 0x20, 0x2d, 0x0f, + 0x0f, 0x22, 0x38, 0x70, 0x17, 0x35, 0x1e, 0x3d, 0x4c, 0x4b, 0x1e, + 0x2f, 0x37, 0x27, 0x32, 0x42, 0x1c, 0x1c, 0x21, 0x41, 0x41, 0x75, + 0x78, 0x6e, 0xa1, 0x0f, 0x09, 0x08, 0x52, 0x52, 0x03, 0xfe, 0xfe, + 0x01, 0xff, 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, 0x06, 0xff, 0x06, 0xff, 0x07, + 0x52, 0x08, 0x09, 0x44, 0x40, 0x11, 0x40, 0x20, 0x12, 0x0f, 0x0f, + 0x3a, 0x3a, 0x17, 0x05, 0x35, 0x42, 0x36, 0x32, 0x2e, 0x1c, 0x21, + 0x1b, 0x6c, 0x3d, 0x1c, 0x1c, 0x1c, 0x21, 0x21, 0x75, 0x65, 0x6e, + 0x5a, 0x3a, 0x30, 0x09, 0x08, 0x52, 0x03, 0xfe, 0xfe, 0xfd, 0x06, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x06, 0xff, 0x06, 0x06, 0x06, 0x07, 0x07, 0x08, + 0x92, 0x44, 0x12, 0x30, 0x0f, 0x0f, 0x12, 0x12, 0x0f, 0x22, 0x22, + 0x18, 0x13, 0x28, 0x39, 0x3d, 0x27, 0x21, 0x13, 0x23, 0x17, 0x3c, + 0x3d, 0x1c, 0x0c, 0x1c, 0x13, 0x1c, 0x75, 0x75, 0x51, 0x70, 0x38, + 0x2d, 0x30, 0x2c, 0x04, 0x03, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x06, 0xff, 0x06, 0x06, 0x06, 0x07, 0x92, 0x92, 0x09, + 0x63, 0x09, 0x09, 0x09, 0x12, 0x12, 0x12, 0x0f, 0x11, 0x22, 0x10, + 0x05, 0x17, 0x1b, 0x0a, 0x0b, 0x0b, 0x0b, 0x05, 0x1e, 0x2b, 0x17, + 0x0c, 0x0c, 0x19, 0x0e, 0x5a, 0x5a, 0x5a, 0x70, 0x5a, 0x38, 0x20, + 0x12, 0x08, 0x08, 0x52, 0x06, 0xff, 0xff, 0xff, 0x06, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, + 0x06, 0x06, 0xff, 0x06, 0x07, 0x07, 0x08, 0x08, 0x2c, 0x09, 0x1d, + 0x09, 0x09, 0x09, 0x09, 0x12, 0x0f, 0x0f, 0x14, 0x0b, 0x0b, 0x05, + 0x26, 0x1a, 0x0b, 0x22, 0x22, 0x10, 0x2e, 0x1b, 0x16, 0x0c, 0x0c, + 0x18, 0x0e, 0x45, 0x45, 0x23, 0x5a, 0x45, 0x38, 0x3a, 0x30, 0x09, + 0x08, 0x07, 0x06, 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x07, 0x52, 0x08, 0x08, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x12, 0x12, 0x0f, 0x11, 0x0b, 0x10, 0x20, 0x17, 0x15, 0x1a, + 0x0b, 0x22, 0x0b, 0x11, 0x1c, 0x2e, 0x17, 0x0c, 0x0c, 0x19, 0x19, + 0x45, 0x45, 0x45, 0x45, 0x19, 0x3a, 0x0b, 0x40, 0x12, 0x08, 0x07, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, 0xff, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x03, 0x03, 0x52, 0x04, 0x52, 0x08, + 0x2c, 0x09, 0x40, 0x1d, 0x24, 0x12, 0x11, 0x69, 0x10, 0x12, 0x12, + 0x09, 0x12, 0x18, 0x28, 0x05, 0x0b, 0x22, 0x0b, 0x11, 0x0b, 0x22, + 0x3a, 0x38, 0x18, 0x2d, 0x10, 0x12, 0x08, 0x08, 0x07, 0x06, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, 0xff, 0xff, 0x06, 0x06, 0x07, + 0x07, 0x07, 0x52, 0x03, 0x03, 0x04, 0x03, 0x08, 0x08, 0x08, 0x09, + 0x63, 0x40, 0x12, 0x09, 0x38, 0x0d, 0x10, 0x09, 0x09, 0x09, 0x09, + 0x0b, 0x18, 0x05, 0x22, 0x22, 0x22, 0x11, 0x22, 0x22, 0x22, 0x0b, + 0x0b, 0x11, 0x22, 0x40, 0x08, 0x08, 0x07, 0x06, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x06, 0xff, 0x06, 0xff, 0xff, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x03, 0x03, 0x52, 0x52, 0x52, 0x08, 0x08, 0x08, 0x09, 0x09, + 0x09, 0x09, 0x20, 0x1f, 0x10, 0x09, 0x09, 0x09, 0x09, 0x22, 0x0b, + 0x05, 0x0b, 0x22, 0x22, 0x0f, 0x0f, 0x0f, 0x22, 0x22, 0x22, 0x0f, + 0x22, 0x09, 0x08, 0x08, 0x07, 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x06, 0x07, 0xfe, 0x01, 0x07, 0x06, 0x06, + 0xff, 0x06, 0xff, 0x06, 0x06, 0x06, 0x06, 0x06, 0x04, 0x02, 0x08, + 0x43, 0x3e, 0x24, 0x04, 0x08, 0x08, 0x52, 0x2c, 0x09, 0x1d, 0x1d, + 0x40, 0x44, 0x44, 0x09, 0x44, 0x09, 0x1d, 0x1d, 0x09, 0x02, 0x02, + 0x02, 0x01, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, + 0xff, 0x06, 0xff, 0x07, 0x07, 0x07, 0x07, 0x06, 0xff, 0x06, 0xff, + 0x06, 0xff, 0x06, 0x06, 0x01, 0x06, 0x52, 0x08, 0x04, 0x44, 0x0f, + 0x1d, 0x02, 0x52, 0x07, 0x52, 0x08, 0x08, 0x08, 0x09, 0x1d, 0x63, + 0x09, 0x44, 0x44, 0x09, 0x09, 0x09, 0x04, 0x2c, 0x02, 0xfe, 0x01, + 0xfd, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x06, 0x01, 0x06, 0x06, 0x06, 0xff, 0xff, 0xff, 0xff, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x03, 0x04, 0x04, 0x12, 0x40, 0x02, 0x04, + 0x52, 0x52, 0x52, 0x08, 0x08, 0x08, 0x08, 0x1d, 0x09, 0x08, 0x09, + 0x08, 0x09, 0x08, 0x08, 0x08, 0x52, 0x03, 0x03, 0xfe, 0xfd, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06, + 0x06, 0x06, 0xff, 0xff, 0x06, 0xff, 0x06, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x01, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06, 0x07, 0x07, 0x06, 0x07, + 0x07, 0x07, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06, 0xff, 0xff, + 0xff, 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x06, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x06, 0x07, 0x07, 0x07, 0x07, 0x01, 0xff, 0x06, 0xff, 0xff, + 0xff, 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, 0x07, 0x07, 0x01, + 0x07, 0x06, 0x06, 0x06, 0x07, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, + 0x07, 0x01, 0x06, 0x06, 0x06, 0xff, 0xff, 0xff, 0x06, 0xff, 0xff, + 0xff, 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, 0x06, 0x01, 0x07, 0x06, 0x06, + 0x07, 0x06, 0x06, 0x06, 0x06, 0x07, 0x06, 0x07, 0x06, 0x06, 0x07, + 0x06, 0x07, 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x06, 0x07, 0x07, 0x07, 0x07, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x07, 0x06, 0x06, 0x06, 0x06, 0x07, 0x06, 0x07, 0x06, + 0x06, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x06, 0xff, 0x06, 0x06, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff +}; diff --git a/utils/hlmv/pakviewer.cpp b/utils/hlmv/pakviewer.cpp new file mode 100644 index 0000000..28c1e2d --- /dev/null +++ b/utils/hlmv/pakviewer.cpp @@ -0,0 +1,558 @@ +// +// Half-Life Model Viewer (c) 1999 by Mete Ciragan +// +// file: pakviewer.cpp +// last modified: May 04 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +// version: 1.2 +// +// email: mete@swissquake.ch +// web: http://www.swissquake.ch/chumbalum-soft/ +// +#include +#include +#include +#include +#include "pakviewer.h" +#include "mdlviewer.h" +#include "GlWindow.h" +#include "StudioModel.h" +#include "ControlPanel.h" +#include "FileAssociation.h" + + + +int +pak_ExtractFile (const char *pakFile, const char *lumpName, char *outFile) +{ + FILE *file = fopen (pakFile, "rb"); + if (!file) + return 0; + + int ident, dirofs, dirlen; + + fread (&ident, sizeof (int), 1, file); + if (ident != (int) (('K' << 24) + ('C' << 16) + ('A' << 8) + 'P')) + { + fclose (file); + return 0; + } + + fread (&dirofs, sizeof (int), 1, file); + fread (&dirlen, sizeof (int), 1, file); + + fseek (file, dirofs, SEEK_SET); + int numLumps = dirlen / 64; + + for (int i = 0; i < numLumps; i++) + { + char name[56]; + int filepos, filelen; + + fread (name, 56, 1, file); + fread (&filepos, sizeof (int), 1, file); + fread (&filelen, sizeof (int), 1, file); + + if (!mx_strcasecmp (name, lumpName)) + { + FILE *out = fopen (outFile, "wb"); + if (!out) + { + fclose (file); + return 0; + } + + fseek (file, filepos, SEEK_SET); + + while (filelen--) + fputc (fgetc (file), out); + + fclose (out); + fclose (file); + + return 1; + } + } + + fclose (file); + + return 0; +} + + + +PAKViewer::PAKViewer (mxWindow *window) +: mxWindow (window, 0, 0, 0, 0, "", mxWindow::Normal) +{ + strcpy (d_pakFile, ""); + strcpy (d_currLumpName, ""); + + tvPAK = new mxTreeView (this, 0, 0, 0, 0, IDC_PAKVIEWER); + pmMenu = new mxPopupMenu (); + pmMenu->add ("Load Model", 1); + pmMenu->addSeparator (); + pmMenu->add ("Load Background", 2); + pmMenu->add ("Load Ground", 3); + pmMenu->addSeparator (); + pmMenu->add ("Play Sound", 4); + pmMenu->addSeparator (); + pmMenu->add ("Extract File...", 5); + setLoadEntirePAK (true); + + setVisible (false); +} + + + +PAKViewer::~PAKViewer () +{ + tvPAK->remove (0); + closePAKFile (); +} + + + +void +_makeTempFileName (char *str, const char *suffix) +{ + strcpy (str, mx_gettemppath ()); + + strcat (str, "/hltempmodel"); + strcat (str, suffix); +} + + + +int +PAKViewer::handleEvent (mxEvent *event) +{ + switch (event->event) + { + case mxEvent::Action: + { + switch (event->action) + { + case IDC_PAKVIEWER: // tvPAK + if (event->flags & mxEvent::RightClicked) + { + pmMenu->setEnabled (1, strstr (d_currLumpName, ".mdl") != 0); + pmMenu->setEnabled (2, strstr (d_currLumpName, ".tga") != 0 || strstr (d_currLumpName, ".bmp")); + pmMenu->setEnabled (3, strstr (d_currLumpName, ".tga") != 0 || strstr (d_currLumpName, ".bmp")); + pmMenu->setEnabled (4, strstr (d_currLumpName, ".wav") != 0); + int ret = pmMenu->popup (tvPAK, event->x, event->y); + switch (ret) + { + case 1: + OnLoadModel (); + break; + + case 2: + OnLoadTexture (TEXTURE_BACKGROUND); + break; + + case 3: + OnLoadTexture (TEXTURE_GROUND); + break; + + case 4: + OnPlaySound (); + break; + + case 5: + OnExtract (); + break; + } + } + else if (event->flags & mxEvent::DoubleClicked) + { + OnPAKViewer (); + char e[16]; + + strncpy (e, mx_getextension (d_currLumpName), 16); + int mode = g_FileAssociation->getMode (&e[1]); + if (mode == -1) + return 1; + + char *program = g_FileAssociation->getProgram (&e[1]); + +#ifdef WIN32 + if (mode == 0) + { + char str[256]; + _makeTempFileName (str, e); + if (!pak_ExtractFile (d_pakFile, d_currLumpName, str)) + mxMessageBox (this, "Error extracting from PAK file.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + else + { + if (program) + { + char path[256]; + strcpy (path, program); + strcat (path, " "); + strcat (path, str); + if ((int) WinExec (path, SW_SHOW) <= 32) + mxMessageBox (this, "Error executing specified program.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + } + } + } + + // associated program + else if (mode == 1) + { + char str[256]; + _makeTempFileName (str, e); + if (!pak_ExtractFile (d_pakFile, d_currLumpName, str)) + mxMessageBox (this, "Error extracting from PAK file.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + else + if ((int) ShellExecute ((HWND) getHandle (), "open", str, 0, 0, SW_SHOW) <= 32) + mxMessageBox (this, "Error executing document with associated program.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + } + + // HLMV default + else +#endif + if (mode == 2) + { + if (!mx_strcasecmp (e, ".mdl")) + OnLoadModel (); + + else if (!mx_strcasecmp (e, ".tga") || !mx_strcasecmp (e, ".bmp")) + OnLoadTexture (TEXTURE_BACKGROUND); + + else if (!mx_strcasecmp (e, ".wav")) + OnPlaySound (); + + return 1; + } + } + + return OnPAKViewer (); + } // event->action + } // mxEvent::Action + break; + + case mxEvent::Size: + { + tvPAK->setBounds (0, 0, event->width, event->height); + } // mxEvent::Size + break; + + } // event->event + + return 1; +} + + + +int +PAKViewer::OnPAKViewer () +{ + mxTreeViewItem *tvi = tvPAK->getSelectedItem (); + if (tvi) + { + strcpy (d_currLumpName, tvPAK->getLabel (tvi)); + + // find the full lump name + mxTreeViewItem *tviParent = tvPAK->getParent (tvi); + char tmp[128]; + while (tviParent) + { + strcpy (tmp, d_currLumpName); + strcpy (d_currLumpName, tvPAK->getLabel (tviParent)); + strcat (d_currLumpName, "/"); + strcat (d_currLumpName, tmp); + tviParent = tvPAK->getParent (tviParent); + } + + if (!d_loadEntirePAK) + { + // finally insert "models/" + strcpy (tmp, d_currLumpName); + strcpy (d_currLumpName, "models/"); + strcat (d_currLumpName, tmp); + } + } + + return 1; +} + + + +int +PAKViewer::OnLoadModel () +{ + static char str2[256]; + char suffix[16]; + + strcpy (suffix, ".mdl"); + _makeTempFileName (str2, suffix); + + if (!pak_ExtractFile (d_pakFile, d_currLumpName, str2)) + { + mxMessageBox (this, "Error extracting from PAK file.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + return 1; + } + + g_studioModel.FreeModel (); + studiohdr_t *hdr = g_studioModel.LoadModel (str2); + if (!hdr) + { +// mxMessageBox (this, "Error reading model header.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + return 1; + } + + if (hdr->numtextures == 0) + { + char texturename[256]; + + strcpy( texturename, d_currLumpName ); + strcpy( &texturename[strlen(texturename) - 4], "T.mdl" ); + + strcpy (suffix, "T.mdl"); + _makeTempFileName (str2, suffix); + + if (!pak_ExtractFile (d_pakFile, texturename, str2)) + { + g_studioModel.FreeModel (); + mxMessageBox (this, "Error extracting from PAK file 1.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + return 1; + } + } + + if (hdr->numseqgroups > 1) + { + for (int i = 1; i < hdr->numseqgroups; i++) + { + char seqgroupname[256]; + + strcpy( seqgroupname, d_currLumpName ); + sprintf( &seqgroupname[strlen(seqgroupname) - 4], "%02d.mdl", i ); + + sprintf (suffix, "%02d.mdl", i); + _makeTempFileName (str2, suffix); + + if (!pak_ExtractFile (d_pakFile, seqgroupname, str2)) + { + g_studioModel.FreeModel (); + mxMessageBox (this, "Error extracting from PAK file 2.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + return 1; + } + } + } + + g_studioModel.FreeModel (); + + strcpy (suffix, ".mdl"); + _makeTempFileName (str2, suffix); + g_ControlPanel->loadModel (str2); + + return 1; +} + + + +int +PAKViewer::OnLoadTexture (int pos) +{ + static char str2[256]; + char suffix[16] = ""; + + if (strstr (d_currLumpName, ".tga")) + sprintf (suffix, "%d%s", pos, ".tga"); + else if (strstr (d_currLumpName, ".bmp")) + sprintf (suffix, "%d%s", pos, ".bmp"); + + _makeTempFileName (str2, suffix); + + if (!pak_ExtractFile (d_pakFile, d_currLumpName, str2)) + { + mxMessageBox (this, "Error extracting from PAK file.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + return 1; + } + + if (g_MDLViewer->getGlWindow ()->loadTexture (str2, pos)) + { + if (pos == TEXTURE_BACKGROUND) + g_ControlPanel->setShowBackground (true); + else + g_ControlPanel->setShowGround (true); + } + else + mxMessageBox (this, "Error loading texture.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + + return 1; +} + + + +int +PAKViewer::OnPlaySound () +{ +#ifdef WIN32 + static char str2[256]; + char suffix[16] = ""; + + // stop any playing sound + PlaySound (0, 0, SND_FILENAME | SND_ASYNC); + + if (strstr (d_currLumpName, ".wav")) + sprintf (suffix, "%d%s", 44, ".wav"); + + _makeTempFileName (str2, suffix); + + if (!pak_ExtractFile (d_pakFile, d_currLumpName, str2)) + { + mxMessageBox (this, "Error extracting from PAK file.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + return 1; + } + + PlaySound (str2, 0, SND_FILENAME | SND_ASYNC); + +#endif + return 1; +} + + + +int +PAKViewer::OnExtract () +{ + char *ptr = (char *) mxGetSaveFileName (this, "", "*.*"); + if (ptr) + { + if (!pak_ExtractFile (d_pakFile, d_currLumpName, ptr)) + mxMessageBox (this, "Error extracting from PAK file.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + } + + return 1; +} + + + +int +_compare(const void *arg1, const void *arg2) +{ + if (strchr ((char *) arg1, '/') && !strchr ((char *) arg2, '/')) + return -1; + + else if (!strchr ((char *) arg1, '/') && strchr ((char *) arg2, '/')) + return 1; + + else + return strcmp ((char *) arg1, (char *) arg2); +} + + + +bool +PAKViewer::openPAKFile (const char *pakFile) +{ + FILE *file = fopen (pakFile, "rb"); + if (!file) + return false; + + int ident, dirofs, dirlen; + + // check for id + fread (&ident, sizeof (int), 1, file); + if (ident != (int) (('K' << 24) + ('C' << 16) + ('A' << 8) + 'P')) + { + fclose (file); + return false; + } + + // load lumps + fread (&dirofs, sizeof (int), 1, file); + fread (&dirlen, sizeof (int), 1, file); + int numLumps = dirlen / 64; + + fseek (file, dirofs, SEEK_SET); + lump_t *lumps = new lump_t[numLumps]; + if (!lumps) + { + fclose (file); + return false; + } + + fread (lumps, sizeof (lump_t), numLumps, file); + fclose (file); + + qsort (lumps, numLumps, sizeof (lump_t), _compare); + + // save pakFile for later + strcpy (d_pakFile, pakFile); + + tvPAK->remove (0); + + char namestack[32][32]; + mxTreeViewItem *tvistack[32]; + for (int k = 0; k < 32; k++) + { + strcpy (namestack[k], ""); + tvistack[k] = 0; + } + + for (int i = 0; i < numLumps; i++) + { + if (d_loadEntirePAK || !strncmp (lumps[i].name, "models", 6)) + { + char *tok; + if (d_loadEntirePAK) + tok = &lumps[i].name[0]; + else + tok = &lumps[i].name[7]; + + int i = 1; + while (tok) + { + char *end = strchr (tok, '/'); + if (end) + *end = '\0'; + + if (strcmp (namestack[i], tok)) + { + strcpy (namestack[i], tok); +/* + if (i == 0) + tvistack[i] = tvPAK->add (0, tok); + else*/ + tvistack[i] = tvPAK->add (tvistack[i - 1], tok); + + for (int j = i + 1; j < 32; j++) + { + strcpy (namestack[j], ""); + tvistack[j] = 0; + } + } + + ++i; + + if (end) + tok = end + 1; + else + tok = 0; + } + } + } + + delete[] lumps; + + setVisible (true); + + return true; +} + + + +void +PAKViewer::closePAKFile () +{ + strcpy (d_pakFile, ""); + setVisible (false); +} diff --git a/utils/hlmv/pakviewer.h b/utils/hlmv/pakviewer.h new file mode 100644 index 0000000..7395093 --- /dev/null +++ b/utils/hlmv/pakviewer.h @@ -0,0 +1,92 @@ +// +// Half-Life Model Viewer (c) 1999 by Mete Ciragan +// +// file: pakviewer.h +// last modified: Apr 28 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +// version: 1.2 +// +// email: mete@swissquake.ch +// web: http://www.swissquake.ch/chumbalum-soft/ +// +#ifndef INCLUDED_PAKVIEWER +#define INCLUDED_PAKVIEWER + + + +#ifndef INCLUDED_MXWINDOW +#include +#endif + + + +#define IDC_PAKVIEWER 1001 + + + +typedef struct +{ + char name[56]; + int filepos; + int filelen; +} lump_t; + + + +#ifdef __cpluspus +extern "C" { +#endif + +int pak_ExtractFile (const char *pakFile, const char *lumpName, char *outFile); + +#ifdef __cpluspus +} +#endif + + + +class mxTreeView; +class mxButton; +class mxPopupMenu; +class GlWindow; + + + +class PAKViewer : public mxWindow +{ + char d_pakFile[256]; + char d_currLumpName[256]; + bool d_loadEntirePAK; + mxTreeView *tvPAK; + mxPopupMenu *pmMenu; + +public: + // CREATORS + PAKViewer (mxWindow *window); + ~PAKViewer (); + + // MANIPULATORS + virtual int handleEvent (mxEvent *event); + int OnPAKViewer (); + int OnLoadModel (); + int OnLoadTexture (int pos); + int OnPlaySound (); + int OnExtract (); + + bool openPAKFile (const char *pakFile); + void closePAKFile (); + void setLoadEntirePAK (bool b) { d_loadEntirePAK = b; } + + // ACCESSORS + bool getLoadEntirePAK () const { return d_loadEntirePAK; } +}; + + + +#endif // INCLUDED_PAKVIEWER \ No newline at end of file diff --git a/utils/hlmv/resource.h b/utils/hlmv/resource.h new file mode 100644 index 0000000..7294f89 --- /dev/null +++ b/utils/hlmv/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by hlmviewer.rc +// +#define IDI_ICON1 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/utils/hlmv/studio_render.cpp b/utils/hlmv/studio_render.cpp new file mode 100644 index 0000000..d8913ff --- /dev/null +++ b/utils/hlmv/studio_render.cpp @@ -0,0 +1,1704 @@ +/*** +* +* Copyright (c) 1998, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ +// studio_render.cpp: routines for drawing Half-Life 3DStudio models +// updates: +// 1-4-99 fixed AdvanceFrame wraping bug + +#include +#include +#include +#include "StudioModel.h" +#include "ViewerSettings.h" +#include "ControlPanel.h" +#include "GlWindow.h" + +typedef struct sortedmesh_s +{ + mstudiomesh_t *mesh; + int flags; // face flags +} sortedmesh_t; + +//////////////////////////////////////////////////////////////////////// + +Vector g_xformverts[MAXSTUDIOVERTS]; // transformed vertices +Vector g_xformnorms[MAXSTUDIOVERTS]; // transformed vertices +Vector g_lightvalues[MAXSTUDIOVERTS]; // light surface normals +Vector *g_pxformverts; +Vector *g_pxformnorms; +Vector *g_pvlightvalues; + +Vector g_lightvec; // light vector in model reference frame +Vector g_blightvec[MAXSTUDIOBONES]; // light vectors in bone reference frames +int g_ambientlight; // ambient world light +float g_shadelight; // direct world light +Vector g_lightcolor; + +int g_smodels_total; // cookie +sortedmesh_t g_sorted_meshes[1024]; // sorted meshes + +matrix3x4 m_protationmatrix; +Vector2D g_chrome[MAXSTUDIOVERTS]; // texture coords for surface normals +int g_chromeage[MAXSTUDIOBONES]; // last time chrome vectors were updated +Vector g_chromeup[MAXSTUDIOBONES]; // chrome vector "up" in bone reference frames +Vector g_chromeright[MAXSTUDIOBONES]; // chrome vector "right" in bone reference frames +bool bUseWeaponOrigin = false; +bool bUseWeaponLeftHand = false; +bool bUseParanoiaFOV = false; +extern bool g_bStopPlaying; + +CBaseBoneSetup g_boneSetup; // new blender implementation with IK :-) + +static float hullcolor[8][3] = +{ +{ 1.0f, 1.0f, 1.0f }, +{ 1.0f, 0.5f, 0.5f }, +{ 0.5f, 1.0f, 0.5f }, +{ 1.0f, 1.0f, 0.5f }, +{ 0.5f, 0.5f, 1.0f }, +{ 1.0f, 0.5f, 1.0f }, +{ 0.5f, 1.0f, 1.0f }, +{ 1.0f, 1.0f, 1.0f }, +}; + +//----------------------------------------------------------------------------- +// Purpose: Keeps a global clock to autoplay sequences to run from +// Also deals with speedScale changes +//----------------------------------------------------------------------------- +float GetAutoPlayTime( void ) +{ + static int g_prevTicks; + static float g_time; + + int ticks = GetTickCount(); + // limit delta so that float time doesn't overflow + if (g_prevTicks == 0) + g_prevTicks = ticks; + + g_time += ( (ticks - g_prevTicks) / 1000.0f ) * g_viewerSettings.speedScale; + g_prevTicks = ticks; + + return g_time; +} + +//----------------------------------------------------------------------------- +// Purpose: Keeps a global clock for "realtime" overlays to run from +//----------------------------------------------------------------------------- +float GetRealtimeTime( void ) +{ + // renamed static's so debugger doesn't get confused and show the wrong one + static int g_prevTicksRT; + static float g_timeRT; + + int ticks = GetTickCount(); + // limit delta so that float time doesn't overflow + if (g_prevTicksRT == 0) + g_prevTicksRT = ticks; + + g_timeRT += ( (ticks - g_prevTicksRT) / 1000.0f ); + g_prevTicksRT = ticks; + + return g_timeRT; +} + +/* +=============== +MeshCompare + +Sorting opaque entities by model type +=============== +*/ +static int MeshCompare( const void *s1, const void *s2 ) +{ + sortedmesh_t *a = (sortedmesh_t *)s1; + sortedmesh_t *b = (sortedmesh_t *)s2; + + if( FBitSet( a->flags, STUDIO_NF_ADDITIVE )) + return 1; + + if( FBitSet( a->flags, STUDIO_NF_MASKED )) + return -1; + + return 0; +} + +//////////////////////////////////////////////////////////////////////// + +void StudioModel :: centerView( bool reset ) +{ + Vector min, max; + + ExtractBbox( min, max ); + + float dx = max[0] - min[0]; + float dy = max[1] - min[1]; + float dz = max[2] - min[2]; + float d = max( dx, max( dy, dz )); + + if( reset ) + { + g_viewerSettings.trans[0] = 0; + g_viewerSettings.trans[1] = 0; + g_viewerSettings.trans[2] = 0; + } + else + { + g_viewerSettings.trans[0] = 0; + g_viewerSettings.trans[1] = min[2] + dz / 2.0f; + g_viewerSettings.trans[2] = d * 1.0f; + } + + g_viewerSettings.rot[0] = -90.0f; + g_viewerSettings.rot[1] = -90.0f; + g_viewerSettings.rot[2] = 0.0f; + + g_viewerSettings.movementScale = Q_max( 1.0f, d * 0.01f ); +} + +bool StudioModel :: AdvanceFrame( float dt ) +{ + if( !m_pstudiohdr ) return false; + + mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pstudiohdr + m_pstudiohdr->seqindex) + m_sequence; + + if( dt > 0.1f ) + dt = 0.1f; + m_dt = dt; + + float t = GetDuration( ); + + if( t > 0.0f ) + { + if( dt > 0.0f ) + { + m_cycle += (dt / t); + + if( pseqdesc->flags & STUDIO_LOOPING || g_viewerSettings.sequence_autoplay ) + m_cycle -= (int)(m_cycle); + else m_cycle = bound( 0.0f, m_cycle, 1.0f ); + } + } + else + { + m_cycle = 0; + } + + return true; +} + +float StudioModel::GetInterval( void ) +{ + return m_dt; +} + +float StudioModel::GetCycle( void ) +{ + return m_cycle; +} + +float StudioModel::GetFrame( void ) +{ + return GetCycle() * GetMaxFrame(); +} + +int StudioModel::GetMaxFrame( void ) +{ + return g_boneSetup.LocalMaxFrame( m_sequence ); +} + +int StudioModel :: SetFrame( int frame ) +{ + if( frame == -1 ) + return GetFrame(); + + if ( frame <= 0 ) + frame = 0; + + int maxFrame = GetMaxFrame(); + if ( frame >= maxFrame ) + { + frame = maxFrame; + m_cycle = 0.99999; + return frame; + } + + m_cycle = frame / (float)maxFrame; + return frame; +} + +void StudioModel :: SetupTransform( bool bMirror ) +{ + Vector origin, angles; + float scale = 1.0f; + + origin = angles = g_vecZero; + + if( !bUseWeaponOrigin && FBitSet( m_pstudiohdr->flags, STUDIO_ROTATE )) + angles[1] = anglemod( 100.0f * m_flTime ); + + if( g_viewerSettings.editMode == EDIT_SOURCE ) + origin = m_editfields[0].origin; + + // build the rotation matrix + m_protationmatrix = matrix3x4( origin, angles, scale ); + + if( bMirror ) + { + m_protationmatrix.SetUp( -m_protationmatrix.GetUp() ); + } + + if( bUseWeaponOrigin && bUseWeaponLeftHand ) + { + // inverse the right vector + m_protationmatrix.SetRight( -m_protationmatrix.GetRight() ); + } +} + +void StudioModel :: BlendSequence( Vector pos[], Vector4D q[], blend_sequence_t *seqblend ) +{ + static Vector pos1b[MAXSTUDIOBONES]; + static Vector4D q1b[MAXSTUDIOBONES]; + float s; + + if( seqblend->blendtime != 0.0f && ( seqblend->blendtime + seqblend->fadeout > m_flTime ) && ( seqblend->sequence < m_pstudiohdr->numseq )) + { + s = 1.0f - (m_flTime - seqblend->blendtime) / seqblend->fadeout; + + if( s > 0 && s <= 1.0 ) + { + // do a nice spline curve + s = 3 * s * s - 2 * s * s * s; + } + else if( s > 1.0f ) + { + // Shouldn't happen, but maybe curtime is behind animtime? + s = 1.0f; + } + + g_boneSetup.AccumulatePose( &m_ik, pos, q, seqblend->sequence, seqblend->cycle, s ); + } +} + +void StudioModel :: SetUpBones( bool bMirror ) +{ + int i; + + mstudiobone_t *pbones; + mstudioboneinfo_t *pboneinfo; + CIKContext *pIK = NULL; + float adj[MAXSTUDIOCONTROLLERS]; + static Vector pos[MAXSTUDIOBONES]; + static Vector4D q[MAXSTUDIOBONES]; + matrix3x4 bonematrix; + + if( m_sequence >= m_pstudiohdr->numseq ) + m_sequence = 0; + + Vector a1 = m_protationmatrix.GetAngles(); + Vector p1 = m_protationmatrix.GetOrigin(); + + m_ik.Init( &g_boneSetup, a1, p1, GetRealtimeTime(), m_iFramecounter ); + pIK = NULL; + + if( g_viewerSettings.enableIK && !bMirror ) + { + pIK = &m_ik; + } + + g_boneSetup.InitPose( pos, q ); + g_boneSetup.UpdateRealTime( GetRealtimeTime() ); + g_boneSetup.CalcBoneAdj( adj, m_controller, m_mouth ); + g_boneSetup.AccumulatePose( pIK, pos, q, m_sequence, m_cycle, 1.0 ); + + // blends from previous sequences + for( i = 0; i < MAX_SEQBLENDS; i++ ) + BlendSequence( pos, q, &m_seqblend[i] ); + + CIKContext auto_ik; + auto_ik.Init( &g_boneSetup, a1, p1, 0.0f, 0 ); + + g_boneSetup.UpdateRealTime( GetAutoPlayTime() ); + g_boneSetup.CalcAutoplaySequences( &auto_ik, pos, q ); +// g_boneSetup.CalcBoneAdj( pos, q, m_controller, m_mouth ); + + if( pIK ) + { + Vector deltaPos; + Vector deltaAngles; + + GetMovement( m_prevcycle, deltaPos, deltaAngles ); + + deltaPos = m_protationmatrix.VectorRotate( deltaPos ); + + pIK->UpdateTargets( pos, q, m_pbonetransform ); + + glDisable (GL_TEXTURE_2D); + glDisable (GL_CULL_FACE); + + if (g_viewerSettings.transparency < 1.0f) + glDisable (GL_DEPTH_TEST); + else + glEnable (GL_DEPTH_TEST); + + glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); + glEnable (GL_BLEND); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // FIXME: check number of slots? + for( int i = 0; i < pIK->m_target.Count(); i++ ) + { + CIKTarget *pTarget = &pIK->m_target[i]; + + switch( pTarget->type ) + { + case IK_GROUND: + { + // g_boneSetup.debugLine( pTarget->est.pos, pTarget->est.pos + pTarget->offset.pos, 0, 255, 0 ); + + // hack in movement + pTarget->est.pos -= deltaPos; + + Vector tmp = m_protationmatrix.VectorITransform( pTarget->est.pos ); + tmp.z = pTarget->est.floor; + pTarget->est.pos = m_protationmatrix.VectorTransform( tmp ); + pTarget->est.q = m_protationmatrix.GetQuaternion(); + + float color[4] = { 1.0f, 1.0f, 0.0f, 1.0f }; + + if( pTarget->est.latched > 0.0f ) + color[1] = 1.0 - Q_min( pTarget->est.flWeight, 1.0 ); + else color[0] = 1.0 - Q_min( pTarget->est.flWeight, 1.0 ); + + float r = Q_max( pTarget->est.radius, 1.0f ); + Vector p0 = tmp + Vector( -r, -r, 0.1f ); + Vector p2 = tmp + Vector( r, r, 0.1f ); + + drawTransparentBox( p0, p2, m_protationmatrix, color ); + } + break; + case IK_ATTACHMENT: + { + matrix3x4 m = matrix3x4( pTarget->est.pos, pTarget->est.q ); + drawTransform( m, 4 ); + } + break; + } + + // g_boneSetup.drawLine( pTarget->est.pos, pTarget->latched.pos, 255, 0, 0 ); + } + + pIK->SolveDependencies( pos, q, m_pbonetransform ); + + g_GlWindow->setupRenderMode(); // restore right rendermode + } + + pbones = (mstudiobone_t *)((byte *)m_pstudiohdr + m_pstudiohdr->boneindex); + pboneinfo = (mstudioboneinfo_t *)((byte *)pbones + m_pstudiohdr->numbones * sizeof( mstudiobone_t )); + + for( i = 0; i < m_pstudiohdr->numbones; i++ ) + { + m_pbonecolor[i] = Vector( 0.0f, 0.0f, 1.0f ); // animated bone is blue (guess) + + // animate all non-simulated bones + if( CalcProceduralBone( m_pstudiohdr, i, m_pbonetransform )) + { + m_pbonecolor[i] = Vector( 0.0f, 1.0f, 0.0f ); // procedural bone is green + continue; + } + // initialize bonematrix + bonematrix = matrix3x4( pos[i], q[i] ); + + if( FBitSet( pbones[i].flags, BONE_JIGGLE_PROCEDURAL ) && FBitSet( m_pstudiohdr->flags, STUDIO_HAS_BONEINFO )) + { + // Physics-based "jiggle" bone + // Bone is assumed to be along the Z axis + // Pitch around X, yaw around Y + + // compute desired bone orientation + matrix3x4 goalMX; + + if( pbones[i].parent == -1 ) goalMX = m_protationmatrix.ConcatTransforms( bonematrix ); + else goalMX = m_pbonetransform[pbones[i].parent].ConcatTransforms( bonematrix ); + + // get jiggle properties from QC data + mstudiojigglebone_t *jiggleInfo = (mstudiojigglebone_t *)((byte *)m_pstudiohdr + pboneinfo[i].procindex); + if( !m_pJiggleBones ) m_pJiggleBones = new CJiggleBones; + + // do jiggle physics + if( pboneinfo[i].proctype == STUDIO_PROC_JIGGLE ) + { + m_pJiggleBones->BuildJiggleTransformations( i, m_flTime, jiggleInfo, goalMX, m_pbonetransform[i] ); + m_pbonecolor[i] = Vector( 1.0f, 0.5f, 0.0f ); // jiggle bone is orange + } + else m_pbonetransform[i] = goalMX; // fallback + } + else if( pbones[i].parent == -1 ) + { + m_pbonetransform[i] = m_protationmatrix.ConcatTransforms( bonematrix ); + } + else + { + m_pbonetransform[i] = m_pbonetransform[pbones[i].parent].ConcatTransforms( bonematrix ); + } + } + + for( i = 0; i < m_pstudiohdr->numbones; i++ ) + { + m_pworldtransform[i] = m_pbonetransform[i].ConcatTransforms( m_plocaltransform[i] ); + } +} + +/* +================ +StudioModel::TransformFinalVert +================ +*/ +void StudioModel :: Lighting( Vector &lv, int bone, int flags, const Vector &normal ) +{ + float illum; + float lightcos; + + illum = g_ambientlight; + + if (flags & STUDIO_NF_FULLBRIGHT) + { + lv = Vector( 1.0f ); + return; + } + + if (flags & STUDIO_NF_FLATSHADE) + { + illum += g_shadelight * 0.8; + } + else + { + float r; + + if( bone != -1 ) lightcos = DotProduct (normal, g_blightvec[bone]); + else lightcos = DotProduct (normal, g_lightvec); + if (lightcos > 1.0f) lightcos = 1.0f; // -1 colinear, 1 opposite + + illum += g_shadelight; + + r = 1.5f; // lambert + + // do modified hemispherical lighting + if( r <= 1.0f ) + { + r += 1.0f; + lightcos = (( r - 1.0f ) - lightcos) / r; + if( lightcos > 0.0f ) + illum += g_shadelight * lightcos; + } + else + { + lightcos = (lightcos + ( r - 1.0f )) / r; + if( lightcos > 0.0f ) + illum -= g_shadelight * lightcos; + } + + if (illum <= 0) + illum = 0; + } + + if (illum > 255) + illum = 255; + + lv = g_lightcolor * Vector( illum / 255.0 ); // Light from 0 to 1.0 +} + + +void StudioModel :: Chrome( Vector2D &chrome, int bone, const Vector &normal ) +{ + float n; + + if (g_chromeage[bone] != g_smodels_total) + { + // calculate vectors from the viewer to the bone. This roughly adjusts for position + Vector chromeupvec; // g_chrome t vector in world reference frame + Vector chromerightvec; // g_chrome s vector in world reference frame + Vector tmp, v_left; // vector pointing at bone in world reference frame + + tmp = -m_protationmatrix.GetOrigin(); + tmp += m_pbonetransform[bone].GetOrigin(); + tmp = tmp.Normalize(); + v_left = Vector( 0.0f, -1.0f, 0.0f ); + + chromeupvec = CrossProduct( tmp, v_left ).Normalize(); + chromerightvec = CrossProduct( tmp, chromeupvec ).Normalize(); + chromeupvec = -chromeupvec; // GoldSrc rules + + g_chromeup[bone] = m_pbonetransform[bone].VectorIRotate( chromeupvec ); + g_chromeright[bone] = m_pbonetransform[bone].VectorIRotate( chromerightvec ); + g_chromeage[bone] = g_smodels_total; + } + + // calc s coord + n = DotProduct( normal, g_chromeright[bone] ); + chrome.x = (n + 1.0f) * 32.0f; + + // calc t coord + n = DotProduct( normal, g_chromeup[bone] ); + chrome.y = (n + 1.0f) * 32.0f; +} + +void StudioModel :: DrawSpriteQuad( const Vector &org, const Vector &right, const Vector &up, float scale ) +{ + Vector point; + + glBegin( GL_QUADS ); + glTexCoord2f( 0.0f, 1.0f ); + point = org + up * (-32.0f * scale); + point = point + right * (-32.0f * scale); + glVertex3fv( point ); + glTexCoord2f( 0.0f, 0.0f ); + point = org + up * (32.0f * scale); + point = point + right * (-32.0f * scale); + glVertex3fv( point ); + glTexCoord2f( 1.0f, 0.0f ); + point = org + up * (32.0f * scale); + point = point + right * (32.0f * scale); + glVertex3fv( point ); + glTexCoord2f( 1.0f, 1.0f ); + point = org + up * (-32.0f * scale); + point = point + right * (32.0f * scale); + glVertex3fv( point ); + glEnd(); +} + +void StudioModel::RenderMuzzleFlash( muzzleflash_t *muzzle ) +{ + Vector v_right, v_up; + float sr, cr; + + if( muzzle->time < m_flTime ) + { + memset( muzzle, 0, sizeof( muzzleflash_t )); + return; // expired + } + + SinCos( DEG2RAD( muzzle->rotate ), &sr, &cr ); + for( int i = 0; i < 3; i++ ) + { + v_right[i] = (g_GlWindow->vectors[1][i] * cr + g_GlWindow->vectors[0][i] * sr); + v_up[i] = g_GlWindow->vectors[1][i] * -sr + g_GlWindow->vectors[0][i] * cr; + } + + if( bUseWeaponOrigin && bUseWeaponLeftHand ) + v_right = -v_right; + + glEnable( GL_BLEND ); + glDepthMask( GL_FALSE ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE ); + glColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + glBindTexture( GL_TEXTURE_2D, TEXTURE_MUZZLEFLASH1 + muzzle->texture ); + DrawSpriteQuad( muzzle->origin, v_right, v_up, muzzle->scale ); + glDisable( GL_BLEND ); + glDepthMask( GL_TRUE ); +} + +void StudioModel::MuzzleFlash( int attachment, int type ) +{ + if( attachment >= m_pstudiohdr->numattachments ) + return; // bad attachment + + muzzleflash_t *muzzle; + + // move current sequence into circular buffer + m_current_muzzle = (m_current_muzzle + 1) & MASK_MUZZLEFLASHES; + + muzzle = &m_muzzleflash[m_current_muzzle]; + + muzzle->texture = ( type % 10 ) % 3; + muzzle->scale = ( type / 10 ) * 0.1f; + if( muzzle->scale == 0.0f ) + muzzle->scale = 0.5f; + + // don't rotate on paused + if( !g_viewerSettings.pause && !g_bStopPlaying ) + { + if( muzzle->texture == 0 ) + muzzle->rotate = RANDOM_LONG( 0, 20 ); // rifle flash + else muzzle->rotate = RANDOM_LONG( 0, 359 ); + } + + bool isInEditMode = (g_ControlPanel->getTableIndex() == TAB_MODELEDITOR) ? true : false; + bool isEditSource = (g_viewerSettings.editMode == EDIT_SOURCE) ? true : false; + + if( isInEditMode && isEditSource ) + { + for( int i = 0; i < m_numeditfields; i++ ) + { + edit_field_t *ed = &m_editfields[i]; + + if( ed->type != TYPE_ATTACHMENT || ed->id != attachment ) + continue; + + muzzle->origin = m_pbonetransform[ed->bone].VectorTransform( ed->origin ); + break; + } + } + else + { + mstudioattachment_t *pattachment = (mstudioattachment_t *) ((byte *) m_pstudiohdr + m_pstudiohdr->attachmentindex) + attachment; + muzzle->origin = m_pbonetransform[pattachment->bone].VectorTransform( pattachment->org ); + } + + if( !g_viewerSettings.pause && !g_bStopPlaying ) + muzzle->time = m_flTime + 0.015f; + else muzzle->time = m_flTime + 0.0099f; +} + +void StudioModel::ClientEvents( void ) +{ + mstudioseqdesc_t *pseqdesc; + mstudioevent_t *pevent; + float end, start; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pstudiohdr + m_pstudiohdr->seqindex) + m_sequence; + + // no events for this animation + if( pseqdesc->numevents == 0 ) + return; + + start = GetFrame() - g_viewerSettings.speedScale * m_flFrameTime * pseqdesc->fps; + end = GetFrame(); + + if( sequence_reset ) + { + if( !FBitSet( pseqdesc->flags, STUDIO_LOOPING )) + start = -0.01f; + sequence_reset = false; + } + + pevent = (mstudioevent_t *)((byte *)m_pstudiohdr + pseqdesc->eventindex); + + for( int i = 0; i < pseqdesc->numevents; i++ ) + { + if( (float)pevent[i].frame > start && pevent[i].frame <= end ) + { + switch( pevent[i].event ) + { + case 5001: + MuzzleFlash( 0, atoi( pevent[i].options )); + break; + case 5011: + MuzzleFlash( 1, atoi( pevent[i].options )); + break; + case 5021: + MuzzleFlash( 2, atoi( pevent[i].options )); + break; + case 5031: + MuzzleFlash( 3, atoi( pevent[i].options )); + break; + } + } + } +} + +/* +================ +StudioModel::SetupLighting + set some global variables based on entity position +inputs: +outputs: + g_ambientlight + g_shadelight +================ +*/ +void StudioModel::SetupLighting ( ) +{ + g_ambientlight = 95; + g_shadelight = 160; + + g_lightvec[0] = g_viewerSettings.gLightVec[0]; + g_lightvec[1] = g_viewerSettings.gLightVec[1]; + g_lightvec[2] = g_viewerSettings.gLightVec[2]; + + g_lightcolor[0] = g_viewerSettings.lColor[0]; + g_lightcolor[1] = g_viewerSettings.lColor[1]; + g_lightcolor[2] = g_viewerSettings.lColor[2]; + + g_lightvec = g_lightvec.Normalize(); + + // TODO: only do it for bones that actually have textures + for( int i = 0; i < m_pstudiohdr->numbones; i++ ) + g_blightvec[i] = m_pbonetransform[i].VectorIRotate( g_lightvec ).Normalize(); +} + + +/* +================= +StudioModel::SetupModel + based on the body part, figure out which mesh it should be using. +inputs: + currententity +outputs: + pstudiomesh + pmdl +================= +*/ + +void StudioModel::SetupModel ( int bodypart ) +{ + int index; + + if (bodypart > m_pstudiohdr->numbodyparts) + { + // Con_DPrintf ("StudioModel::SetupModel: no such bodypart %d\n", bodypart); + bodypart = 0; + } + + mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)m_pstudiohdr + m_pstudiohdr->bodypartindex) + bodypart; + + index = m_bodynum / pbodypart->base; + index = index % pbodypart->nummodels; + + m_pmodel = (mstudiomodel_t *)((byte *)m_pstudiohdr + pbodypart->modelindex) + index; +} + + +void drawBox (Vector *v, float const * color = NULL) +{ + if( color ) glColor4fv( color ); + + glBegin (GL_QUAD_STRIP); + for (int i = 0; i < 10; i++) + glVertex3fv (v[i & 7]); + glEnd (); + + glBegin (GL_QUAD_STRIP); + glVertex3fv (v[6]); + glVertex3fv (v[0]); + glVertex3fv (v[4]); + glVertex3fv (v[2]); + glEnd (); + + glBegin (GL_QUAD_STRIP); + glVertex3fv (v[1]); + glVertex3fv (v[7]); + glVertex3fv (v[3]); + glVertex3fv (v[5]); + glEnd (); + +} + +//----------------------------------------------------------------------------- +// Draws the position and axies of a transformation matrix, x=red,y=green,z=blue +//----------------------------------------------------------------------------- +void StudioModel :: drawTransform( matrix3x4 &m, float flLength ) +{ + glBegin( GL_LINES ); + for( int k = 0; k < 3; k++ ) + { + glColor3f( 1, 0, 0 ); + glVertex3fv( m.GetOrigin() ); + glColor3f( 1, 1, 1 ); + glVertex3fv( m.GetOrigin() + m.GetRow( k ) * 4.0f ); + } + glEnd(); +} + +//----------------------------------------------------------------------------- +// Draws a transparent box with a wireframe outline +//----------------------------------------------------------------------------- +void StudioModel :: drawTransparentBox( Vector const &bbmin, Vector const &bbmax, const matrix3x4 &m, float const *color ) +{ + Vector v[8], v2[8]; + + v[0][0] = bbmin[0]; + v[0][1] = bbmax[1]; + v[0][2] = bbmin[2]; + + v[1][0] = bbmin[0]; + v[1][1] = bbmin[1]; + v[1][2] = bbmin[2]; + + v[2][0] = bbmax[0]; + v[2][1] = bbmax[1]; + v[2][2] = bbmin[2]; + + v[3][0] = bbmax[0]; + v[3][1] = bbmin[1]; + v[3][2] = bbmin[2]; + + v[4][0] = bbmax[0]; + v[4][1] = bbmax[1]; + v[4][2] = bbmax[2]; + + v[5][0] = bbmax[0]; + v[5][1] = bbmin[1]; + v[5][2] = bbmax[2]; + + v[6][0] = bbmin[0]; + v[6][1] = bbmax[1]; + v[6][2] = bbmax[2]; + + v[7][0] = bbmin[0]; + v[7][1] = bbmin[1]; + v[7][2] = bbmax[2]; + + v2[0] = m.VectorTransform( v[0] ); + v2[1] = m.VectorTransform( v[1] ); + v2[2] = m.VectorTransform( v[2] ); + v2[3] = m.VectorTransform( v[3] ); + v2[4] = m.VectorTransform( v[4] ); + v2[5] = m.VectorTransform( v[5] ); + v2[6] = m.VectorTransform( v[6] ); + v2[7] = m.VectorTransform( v[7] ); + + drawBox (v2, color); +} + +/* +================ +StudioModel::DrawModel +inputs: + currententity + r_entorigin +================ +*/ +void StudioModel :: DrawModel( bool bMirror ) +{ + int i; + + if( !m_pstudiohdr ) return; + + g_smodels_total++; // render data cache cookie + + g_pxformverts = &g_xformverts[0]; + g_pxformnorms = &g_xformnorms[0]; + g_pvlightvalues = &g_lightvalues[0]; + + if( m_pstudiohdr->numbodyparts == 0 ) + return; + + bool isInEditMode = (g_ControlPanel->getTableIndex() == TAB_MODELEDITOR) ? true : false; + bool drawEyePos = g_viewerSettings.showAttachments; + bool drawAttachments = g_viewerSettings.showAttachments; + bool drawHitboxes = g_viewerSettings.showHitBoxes; + bool drawAbsBox = false; + int drawIndex = -1; // draw all + int colorIndex = -1; + + if( isInEditMode && m_pedit ) + { + drawEyePos = (m_pedit->type == TYPE_EYEPOSITION) ? true : false; + drawAttachments = (m_pedit->type == TYPE_ATTACHMENT) ? true : false; + drawHitboxes = (m_pedit->type == TYPE_HITBOX) ? true : false; + drawAbsBox = (m_pedit->type == TYPE_BBOX || m_pedit->type == TYPE_CBOX) ? true : false; + drawIndex = m_pedit->id; + + if(m_pedit->type == TYPE_HITBOX) + { + mstudiobbox_t *phitbox = (mstudiobbox_t *) ((byte *) m_pstudiohdr + m_pstudiohdr->hitboxindex) + m_pedit->id; + + if( g_viewerSettings.editMode == EDIT_MODEL ) + colorIndex = (phitbox->group % 8); + else colorIndex = (m_pedit->hitgroup % 8); + } + } + + SetupTransform( bMirror ); + + SetUpBones( bMirror ); + + if( !bMirror ) + { + updateModel(); + } + + SetupLighting( ); + ClientEvents( ); + + for( i = 0; i < m_pstudiohdr->numbodyparts; i++ ) + { + SetupModel( i ); + + if( g_viewerSettings.transparency > 0.0f ) + DrawPoints(); + + if( g_viewerSettings.showWireframeOverlay && g_viewerSettings.renderMode != RM_WIREFRAME ) + DrawPoints( true ); + } + + glDisable( GL_MULTISAMPLE ); + + for( i = 0; i < MAX_MUZZLEFLASHES; i++ ) + { + if( m_muzzleflash[i].time == 0.0f ) + continue; + RenderMuzzleFlash ( &m_muzzleflash[i] ); + } + + if( drawEyePos ) + { + glDisable( GL_TEXTURE_2D ); + glDisable( GL_DEPTH_TEST ); + glDisable( GL_BLEND ); + + glPointSize( 7.0f ); + glColor3f( 1.0f, 0.5f, 1.0f ); + + glBegin( GL_POINTS ); + if( isInEditMode && m_pedit && g_viewerSettings.editMode == EDIT_SOURCE ) + glVertex3fv( m_protationmatrix.VectorTransform( m_pedit->origin )); + else glVertex3fv( m_protationmatrix.VectorTransform( m_pstudiohdr->eyeposition )); + glEnd(); + + glPointSize( 1.0f ); + } + + // draw abs box + if( drawAbsBox ) + { + vec3_t tmp, bbox[8]; + + glDisable( GL_MULTISAMPLE ); + glDisable (GL_TEXTURE_2D); + glDisable (GL_CULL_FACE); + glDisable( GL_BLEND ); + + if (g_viewerSettings.transparency < 1.0f) + glDisable (GL_DEPTH_TEST); + else + glEnable (GL_DEPTH_TEST); + + if( m_pedit && m_pedit->type == TYPE_BBOX ) + glColor4f (1, 0, 0, 1.0f); + else glColor4f (1, 0.5, 0, 1.0f); + glEnable (GL_BLEND); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + if( g_ControlPanel->getTableIndex() == TAB_SEQUENCES ) + { + mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pstudiohdr + m_pstudiohdr->seqindex) + (int)m_sequence; + + for( int j = 0; j < 8; j++ ) + { + tmp[0] = (j & 1) ? pseqdesc->bbmin[0] : pseqdesc->bbmax[0]; + tmp[1] = (j & 2) ? pseqdesc->bbmin[1] : pseqdesc->bbmax[1]; + tmp[2] = (j & 4) ? pseqdesc->bbmin[2] : pseqdesc->bbmax[2]; + bbox[j] = tmp; + } + } + else if( g_viewerSettings.editMode == EDIT_MODEL ) + { + if( m_pedit->type == TYPE_BBOX ) + { + for( int j = 0; j < 8; j++ ) + { + tmp[0] = (j & 1) ? m_pstudiohdr->min[0] : m_pstudiohdr->max[0]; + tmp[1] = (j & 2) ? m_pstudiohdr->min[1] : m_pstudiohdr->max[1]; + tmp[2] = (j & 4) ? m_pstudiohdr->min[2] : m_pstudiohdr->max[2]; + bbox[j] = tmp; + } + } + else + { + for( int j = 0; j < 8; j++ ) + { + tmp[0] = (j & 1) ? m_pstudiohdr->bbmin[0] : m_pstudiohdr->bbmax[0]; + tmp[1] = (j & 2) ? m_pstudiohdr->bbmin[1] : m_pstudiohdr->bbmax[1]; + tmp[2] = (j & 4) ? m_pstudiohdr->bbmin[2] : m_pstudiohdr->bbmax[2]; + bbox[j] = tmp; + } + } + } + else + { + for( int j = 0; j < 8; j++ ) + { + tmp[0] = (j & 1) ? m_pedit->mins[0] : m_pedit->maxs[0]; + tmp[1] = (j & 2) ? m_pedit->mins[1] : m_pedit->maxs[1]; + tmp[2] = (j & 4) ? m_pedit->mins[2] : m_pedit->maxs[2]; + bbox[j] = tmp; + } + } + + glBegin( GL_LINES ); + for( int i = 0; i < 2; i += 1 ) + { + glVertex3fv( bbox[i+0] ); + glVertex3fv( bbox[i+2] ); + glVertex3fv( bbox[i+4] ); + glVertex3fv( bbox[i+6] ); + glVertex3fv( bbox[i+0] ); + glVertex3fv( bbox[i+4] ); + glVertex3fv( bbox[i+2] ); + glVertex3fv( bbox[i+6] ); + glVertex3fv( bbox[i*2+0] ); + glVertex3fv( bbox[i*2+1] ); + glVertex3fv( bbox[i*2+4] ); + glVertex3fv( bbox[i*2+5] ); + } + glEnd(); + } + + // draw bones + if( g_viewerSettings.showBones ) + { + mstudiobone_t *pbones = (mstudiobone_t *)((byte *) m_pstudiohdr + m_pstudiohdr->boneindex); + glDisable( GL_MULTISAMPLE ); + glDisable( GL_TEXTURE_2D ); + glDisable( GL_DEPTH_TEST ); + glDisable( GL_BLEND ); + + for( i = 0; i < m_pstudiohdr->numbones; i++ ) + { + if( pbones[i].parent >= 0 ) + { + glPointSize( 4.0f ); + glColor3f( 1.0f, 0.7f, 0.0f ); + glBegin( GL_LINES ); + glVertex3fv( m_pbonetransform[pbones[i].parent].GetOrigin()); + glVertex3fv( m_pbonetransform[i].GetOrigin()); + glEnd(); + + glBegin( GL_POINTS ); + if( pbones[pbones[i].parent].parent != -1 ) + { + glColor3fv( m_pbonecolor[pbones[i].parent] ); + glVertex3fv( m_pbonetransform[pbones[i].parent].GetOrigin()); + } + glColor3fv( m_pbonecolor[i] ); + glVertex3fv( m_pbonetransform[i].GetOrigin()); + glEnd (); + } + else + { + // draw parent bone node + glPointSize( 6.0f ); + glColor3f( 0.8f, 0, 0 ); + + glBegin( GL_POINTS ); + glVertex3fv( m_pbonetransform[i].GetOrigin() ); + glEnd (); + } + } + + glPointSize( 1.0f ); + } + + if( drawAttachments ) + { + glDisable( GL_MULTISAMPLE ); + glDisable( GL_TEXTURE_2D ); + glDisable( GL_CULL_FACE ); + glDisable( GL_DEPTH_TEST ); + glDisable( GL_BLEND ); + + for (i = 0; i < m_pstudiohdr->numattachments; i++) + { + matrix3x4 local, world; + + if( drawIndex != -1 && i != drawIndex ) + continue; + + mstudioattachment_t *pattachments = (mstudioattachment_t *)((byte *) m_pstudiohdr + m_pstudiohdr->attachmentindex); + + local.SetForward( pattachments[i].vectors[0] ); + local.SetRight( pattachments[i].vectors[1] ); + local.SetUp( pattachments[i].vectors[2] ); + + if( drawIndex != -1 && g_viewerSettings.editMode == EDIT_SOURCE ) + local.SetOrigin( m_pedit->origin ); + else local.SetOrigin( pattachments[i].org ); + + world = m_pbonetransform[pattachments[i].bone].ConcatTransforms( local ); + + // draw the vector from bone to attachment + glBegin( GL_LINES ); + glColor3f( 1, 0, 0 ); + glVertex3fv( world.GetOrigin() ); + glColor3f( 1, 1, 1 ); + glVertex3fv( m_pbonetransform[pattachments[i].bone].GetOrigin() ); + glEnd(); + + // draw the matrix axes + if( FBitSet( pattachments[i].flags, STUDIO_ATTACHMENT_LOCAL )) + drawTransform( world ); + + glPointSize( 5 ); + glColor3f( 0, 1, 0 ); + + glBegin( GL_POINTS ); + glVertex3fv( world.GetOrigin() ); + glEnd(); + + glPointSize (1); + } + } + + if( drawHitboxes ) + { + glDisable( GL_MULTISAMPLE ); + glDisable (GL_TEXTURE_2D); + glDisable (GL_CULL_FACE); + glDisable( GL_BLEND ); + + if (g_viewerSettings.transparency < 1.0f) + glDisable (GL_DEPTH_TEST); + else + glEnable (GL_DEPTH_TEST); + + if( colorIndex == -1 ) + glColor4f (1, 0, 0, 0.5f); + else glColor4f( hullcolor[colorIndex][0], hullcolor[colorIndex][1], hullcolor[colorIndex][2], 1.0f ); + + glPolygonMode (GL_FRONT_AND_BACK, GL_LINE); + glEnable (GL_BLEND); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + for (i = 0; i < m_pstudiohdr->numhitboxes; i++) + { + int bone; + + if( g_viewerSettings.showHitBoxes && isInEditMode ) + { + if( i != drawIndex ) + glColor4f( 1.0f, 0.0f, 0.0f, 1.0f ); + else glColor4f( 0.0f, 1.0f, 0.0f, 1.0f ); + } + else + { + if( drawIndex != -1 && i != drawIndex ) + continue; + } + + mstudiobbox_t *pbboxes = (mstudiobbox_t *) ((byte *) m_pstudiohdr + m_pstudiohdr->hitboxindex); + Vector v[8], v2[8], bbmin, bbmax; + + if( g_viewerSettings.showHitBoxes && isInEditMode && g_viewerSettings.editMode == EDIT_SOURCE ) + { + for( int j = 0; j < m_numeditfields; j++ ) + { + edit_field_t *ed = &m_editfields[j]; + + if( ed->type != TYPE_HITBOX || ed->id != i ) + continue; + + bbmin = ed->mins; + bbmax = ed->maxs; + bone = ed->bone; + break; + } + } + else + { + if( drawIndex != -1 && g_viewerSettings.editMode == EDIT_SOURCE ) + { + bbmin = m_pedit->mins; + bbmax = m_pedit->maxs; + bone = m_pedit->bone; + } + else + { + bbmin = pbboxes[i].bbmin; + bbmax = pbboxes[i].bbmax; + bone = pbboxes[i].bone; + } + } + + drawTransparentBox( bbmin, bbmax, m_pbonetransform[bone] ); + } + } +} + +/* +================ + +================ +*/ +void StudioModel::DrawPoints ( bool bWireframe ) +{ + int i, j, k; + mstudiomesh_t *pmesh; + byte *pvertbone; + byte *pnormbone; + Vector *pstudioverts; + Vector *pstudionorms; + mstudioboneweight_t *pvertweight; + mstudioboneweight_t *pnormweight; + mstudiotexture_t *ptexture; + bool texEnabled; + bool need_sort; + matrix3x4 skinMat; + float *av, *nv; + Vector *lv; + Vector lv_tmp; + short *pskinref; + + pvertbone = ((byte *)m_pstudiohdr + m_pmodel->vertinfoindex); + pnormbone = ((byte *)m_pstudiohdr + m_pmodel->norminfoindex); + ptexture = (mstudiotexture_t *)((byte *)m_ptexturehdr + m_ptexturehdr->textureindex); + + pmesh = (mstudiomesh_t *)((byte *)m_pstudiohdr + m_pmodel->meshindex); + + pstudioverts = (Vector *)((byte *)m_pstudiohdr + m_pmodel->vertindex); + pstudionorms = (Vector *)((byte *)m_pstudiohdr + m_pmodel->normindex); + + pskinref = (short *)((byte *)m_ptexturehdr + m_ptexturehdr->skinindex); + if (m_skinnum != 0 && m_skinnum < m_ptexturehdr->numskinfamilies) + pskinref += (m_skinnum * m_ptexturehdr->numskinref); + + if( FBitSet( m_pstudiohdr->flags, STUDIO_HAS_BONEWEIGHTS ) && m_pmodel->blendvertinfoindex != 0 && m_pmodel->blendnorminfoindex != 0 ) + { + pvertweight = (mstudioboneweight_t *)((byte *)m_pstudiohdr + m_pmodel->blendvertinfoindex); + pnormweight = (mstudioboneweight_t *)((byte *)m_pstudiohdr + m_pmodel->blendnorminfoindex); + + for (i = 0; i < m_pmodel->numverts; i++) + { + ComputeSkinMatrix( &pvertweight[i], skinMat ); + g_pxformverts[i] = skinMat.VectorTransform( pstudioverts[i] ); + } + + for (i = 0; i < m_pmodel->numnorms; i++) + { + ComputeSkinMatrix( &pnormweight[i], skinMat ); + g_pxformnorms[i] = skinMat.VectorRotate( pstudionorms[i] ); + + if( g_viewerSettings.renderMode == RM_BONEWEIGHTS ) + ComputeWeightColor( &pnormweight[i], g_pvlightvalues[i] ); + } + } + else + { + for( i = 0; i < m_pmodel->numverts; i++ ) + { + g_pxformverts[i] = m_pbonetransform[pvertbone[i]].VectorTransform( pstudioverts[i] ); + } + + for( i = 0; i < m_pmodel->numnorms; i++ ) + { + g_pxformnorms[i] = m_pbonetransform[pnormbone[i]].VectorRotate( pstudionorms[i] ); + } + + if( g_viewerSettings.renderMode == RM_BONEWEIGHTS ) + { + for( i = 0; i < m_pmodel->numnorms; i++ ) + g_pvlightvalues[i] = Vector( 0.0f, 1.0f, 0.0f ); + } + } + + texEnabled = glIsEnabled( GL_TEXTURE_2D ) ? true : false; + + if( bWireframe ) + { + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + glDisable( GL_TEXTURE_2D ); + glColor4f( 1.0f, 0.0f, 0.0f, 0.99f ); + + glEnable( GL_LINE_SMOOTH ); + glEnable( GL_POLYGON_SMOOTH ); + glHint( GL_LINE_SMOOTH_HINT, GL_NICEST ); + glHint( GL_POLYGON_SMOOTH_HINT, GL_NICEST ); + } + else if (g_viewerSettings.transparency < 1.0f) + { + glEnable (GL_BLEND); + glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDepthMask( GL_TRUE ); + } + + if( bWireframe == false ) + glEnable( GL_MULTISAMPLE ); +// +// clip and draw all triangles +// + + lv = g_pvlightvalues; + need_sort = false; + + for( j = k = 0; j < m_pmodel->nummesh; j++ ) + { + int flags = ptexture[pskinref[pmesh[j].skinref]].flags; + + // fill in sortedmesh info + g_sorted_meshes[j].mesh = &pmesh[j]; + g_sorted_meshes[j].flags = flags; + + if( FBitSet( flags, STUDIO_NF_MASKED|STUDIO_NF_ADDITIVE )) + need_sort = true; + + for( i = 0; i < pmesh[j].numnorms; i++, k++, lv++, pstudionorms++, pnormbone++ ) + { + if( g_viewerSettings.renderMode != RM_BONEWEIGHTS ) + { + if( FBitSet( m_pstudiohdr->flags, STUDIO_HAS_BONEWEIGHTS )) + Lighting ( *lv, -1, flags, g_pxformnorms[k] ); + else Lighting ( *lv, *pnormbone, flags, *pstudionorms ); + } + + // FIX: move this check out of the inner loop + if (flags & STUDIO_NF_CHROME) + Chrome( g_chrome[k], *pnormbone, *pstudionorms ); + } + } + + if( need_sort ) + { + // resort opaque and translucent meshes draw order + qsort( g_sorted_meshes, m_pmodel->nummesh, sizeof( sortedmesh_t ), MeshCompare ); + } + + for (j = 0; j < m_pmodel->nummesh; j++) + { + float s, t; + float transparency = g_viewerSettings.transparency; + short *ptricmds; + + pmesh = (mstudiomesh_t *)((byte *)m_pstudiohdr + m_pmodel->meshindex) + j; + pmesh = g_sorted_meshes[j].mesh; + + ptricmds = (short *)((byte *)m_pstudiohdr + pmesh->triindex); + + s = 1.0/(float)ptexture[pskinref[pmesh->skinref]].width; + t = 1.0/(float)ptexture[pskinref[pmesh->skinref]].height; + + if( bWireframe == false ) + { + //glBindTexture( GL_TEXTURE_2D, ptexture[pskinref[pmesh->skinref]].index ); + glBindTexture( GL_TEXTURE_2D, TEXTURE_COUNT + pskinref[pmesh->skinref] ); + + if (ptexture[pskinref[pmesh->skinref]].flags & STUDIO_NF_TWOSIDE) + glDisable( GL_CULL_FACE ); + else glEnable( GL_CULL_FACE ); + + if (ptexture[pskinref[pmesh->skinref]].flags & STUDIO_NF_MASKED) + { + glEnable( GL_ALPHA_TEST ); + glAlphaFunc( GL_GREATER, 0.5f ); + } + else glDisable( GL_ALPHA_TEST ); + + if (ptexture[pskinref[pmesh->skinref]].flags & STUDIO_NF_ADDITIVE) + { + glEnable( GL_BLEND ); + glBlendFunc( GL_ONE, GL_ONE ); +// glDepthMask( GL_FALSE ); + } + else if (g_viewerSettings.transparency < 1.0f) + { + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); +// glDepthMask( GL_FALSE ); + } + } + + if (ptexture[pskinref[pmesh->skinref]].flags & STUDIO_NF_CHROME) + { + while (i = *(ptricmds++)) + { + if (i < 0) + { + glBegin( GL_TRIANGLE_FAN ); + i = -i; + } + else + { + glBegin( GL_TRIANGLE_STRIP ); + } + + if( bWireframe == false ) + g_viewerSettings.drawn_polys += (i - 2); + + for( ; i > 0; i--, ptricmds += 4) + { + if( bWireframe == false ) + { + // FIX: put these in as integer coords, not floats + glTexCoord2f(g_chrome[ptricmds[1]].x * s, g_chrome[ptricmds[1]].y * t); + + lv = &g_pvlightvalues[ptricmds[1]]; + glColor4f( lv->x, lv->y, lv->z, transparency); + } + + av = g_pxformverts[ptricmds[0]]; + glVertex3f(av[0], av[1], av[2]); + } + glEnd( ); + } + } + else + { + while (i = *(ptricmds++)) + { + if (i < 0) + { + glBegin( GL_TRIANGLE_FAN ); + i = -i; + } + else + { + glBegin( GL_TRIANGLE_STRIP ); + } + + if( bWireframe == false ) + g_viewerSettings.drawn_polys += (i - 2); + + for( ; i > 0; i--, ptricmds += 4) + { + if( bWireframe == false ) + { + if( ptexture[pskinref[pmesh->skinref]].flags & STUDIO_NF_UV_COORDS ) + { + glTexCoord2f( HalfToFloat( ptricmds[2] ), HalfToFloat( ptricmds[3] )); + } + else + { + // FIX: put these in as integer coords, not floats + glTexCoord2f(ptricmds[2]*s, ptricmds[3]*t); + } + + lv = &g_pvlightvalues[ptricmds[1]]; + glColor4f( lv->x, lv->y, lv->z, transparency); + } + + av = g_pxformverts[ptricmds[0]]; + glVertex3f(av[0], av[1], av[2]); + } + glEnd( ); + } + } + + if( bWireframe == false ) + { + if (ptexture[pskinref[pmesh->skinref]].flags & STUDIO_NF_MASKED) + glDisable( GL_ALPHA_TEST ); + + if (ptexture[pskinref[pmesh->skinref]].flags & STUDIO_NF_ADDITIVE || g_viewerSettings.transparency < 1.0f) + { + glDepthMask( GL_TRUE ); + glDisable( GL_BLEND ); + } + } + } + + if( bWireframe ) + { + glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + if( texEnabled ) glEnable( GL_TEXTURE_2D ); + glDisable( GL_BLEND ); + glDisable( GL_LINE_SMOOTH ); + glDisable(GL_POLYGON_SMOOTH); + } + else + { + glDisable( GL_MULTISAMPLE ); + } + + if( g_viewerSettings.showNormals ) + { + if( texEnabled ) glDisable( GL_TEXTURE_2D ); + glColor4f( 0.3f, 0.4f, 0.5f, 0.99f ); + glBegin( GL_LINES ); + for (j = 0; j < m_pmodel->nummesh; j++) + { + short *ptricmds; + + pmesh = (mstudiomesh_t *)((byte *)m_pstudiohdr + m_pmodel->meshindex) + j; + ptricmds = (short *)((byte *)m_pstudiohdr + pmesh->triindex); + + while( i = *(ptricmds++)) + { + for( i = abs( i ); i > 0; i--, ptricmds += 4 ) + { + av = g_pxformverts[ptricmds[0]]; + nv = g_pxformnorms[ptricmds[1]]; + glVertex3f( av[0], av[1], av[2] ); + glVertex3f( av[0] + nv[0] * 2.0f, av[1] + nv[1] * 2.0f, av[2] + nv[2] * 2.0f ); + } + } + } + glEnd(); + if( texEnabled ) glEnable( GL_TEXTURE_2D ); + } + +} + +/* +================ + +================ +*/ +void StudioModel::DrawUVMapPoints() +{ + int i, j, k; + mstudiomesh_t *pmesh; + byte *pvertbone; + byte *pnormbone; + Vector *pstudioverts; + Vector *pstudionorms; + mstudiotexture_t *ptexture; + short *pskinref_src; + short *pskinref; + + pvertbone = ((byte *)m_pstudiohdr + m_pmodel->vertinfoindex); + pnormbone = ((byte *)m_pstudiohdr + m_pmodel->norminfoindex); + ptexture = (mstudiotexture_t *)((byte *)m_ptexturehdr + m_ptexturehdr->textureindex); + + pmesh = (mstudiomesh_t *)((byte *)m_pstudiohdr + m_pmodel->meshindex); + + pstudioverts = (Vector *)((byte *)m_pstudiohdr + m_pmodel->vertindex); + pstudionorms = (Vector *)((byte *)m_pstudiohdr + m_pmodel->normindex); + + pskinref_src = (short *)((byte *)m_ptexturehdr + m_ptexturehdr->skinindex); + + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + glDisable( GL_TEXTURE_2D ); + glColor4f( 1.0f, 1.0f, 1.0f, 0.99f ); +// +// clip and draw all triangles +// + for (k = 0; k < m_ptexturehdr->numskinfamilies; k++ ) + { + // try all the skinfamilies + pskinref = pskinref_src + (k * m_ptexturehdr->numskinref); + + for (j = 0; j < m_pmodel->nummesh; j++) + { + float s, t, x, y; + float tex_w, tex_h; + short *ptricmds; + + pmesh = (mstudiomesh_t *)((byte *)m_pstudiohdr + m_pmodel->meshindex) + j; + ptricmds = (short *)((byte *)m_pstudiohdr + pmesh->triindex); + + if( pskinref[pmesh->skinref] != g_viewerSettings.texture ) + continue; + + tex_w = (float)ptexture[pskinref[pmesh->skinref]].width; + tex_h = (float)ptexture[pskinref[pmesh->skinref]].height; + s = 1.0 / tex_w; + t = 1.0 / tex_h; + + glBindTexture( GL_TEXTURE_2D, TEXTURE_COUNT + pskinref[pmesh->skinref] ); + + while (i = *(ptricmds++)) + { + if (i < 0) + { + glBegin( GL_TRIANGLE_FAN ); + i = -i; + } + else + { + glBegin( GL_TRIANGLE_STRIP ); + } + + for( ; i > 0; i--, ptricmds += 4) + { + if( ptexture[pskinref[pmesh->skinref]].flags & STUDIO_NF_UV_COORDS ) + { + x = HalfToFloat( ptricmds[2] ) * tex_w; + y = HalfToFloat( ptricmds[3] ) * tex_h; + if( y < 0.0f ) y += tex_h; // OpenGL issues + + if(( x < 0.0f || x > tex_w ) || ( y < 0.0f || y > tex_h )) + glColor3f( 1.0f, 0.0f, 0.0f ); + else glColor3f( 1.0f, 1.0f, 1.0f ); + + x = max( 0.0f, min( x, tex_w )); + y = max( 0.0f, min( y, tex_h )); + } + else + { + x = (float)ptricmds[2]; + y = (float)ptricmds[3]; + } + + x *= g_viewerSettings.textureScale; + y *= g_viewerSettings.textureScale; + + glVertex2f( offset_x + x, offset_y + y ); + } + + glEnd( ); + } + } + } + + glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + glEnable( GL_TEXTURE_2D ); + glDisable( GL_BLEND ); +} + +void StudioModel :: DrawModelUVMap( void ) +{ + if( !m_pstudiohdr ) return; + + // draw UV from all the bodyparts and skinfamilies + for( int i = 0; i < m_pstudiohdr->numbodyparts; i++ ) + { + mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)m_pstudiohdr + m_pstudiohdr->bodypartindex) + i; + + for( int j = 0; j < pbodypart->nummodels; j++ ) + { + int index = j / pbodypart->base; + index = index % pbodypart->nummodels; + + m_pmodel = (mstudiomodel_t *)((byte *)m_pstudiohdr + pbodypart->modelindex) + index; + DrawUVMapPoints(); + } + } +} + +void StudioModel :: ConvertTexCoords( void ) +{ + if( !m_pstudiohdr ) return; + + // draw UV from all the bodyparts and skinfamilies + for( int i = 0; i < m_pstudiohdr->numbodyparts; i++ ) + { + mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)m_pstudiohdr + m_pstudiohdr->bodypartindex) + i; + + for( int j = 0; j < pbodypart->nummodels; j++ ) + { + int index = j / pbodypart->base; + index = index % pbodypart->nummodels; + + m_pmodel = (mstudiomodel_t *)((byte *)m_pstudiohdr + pbodypart->modelindex) + index; + mstudiotexture_t *ptexture = (mstudiotexture_t *)((byte *)m_ptexturehdr + m_ptexturehdr->textureindex); + mstudiomesh_t *pmesh = (mstudiomesh_t *)((byte *)m_pstudiohdr + m_pmodel->meshindex); + short *pskinref_src = (short *)((byte *)m_ptexturehdr + m_ptexturehdr->skinindex); + + for( int k = 0; k < m_ptexturehdr->numskinfamilies; k++ ) + { + // try all the skinfamilies + short *pskinref = pskinref_src + (k * m_ptexturehdr->numskinref); + + for( int m = 0; m < m_pmodel->nummesh; m++ ) + { + short *ptricmds; + + pmesh = (mstudiomesh_t *)((byte *)m_pstudiohdr + m_pmodel->meshindex) + m; + ptricmds = (short *)((byte *)m_pstudiohdr + pmesh->triindex); + + while( int l = *( ptricmds++ )) + { + for( l = abs( l ); l > 0; l--, ptricmds += 4 ) + { + if( ptexture[pskinref[pmesh->skinref]].flags & STUDIO_NF_UV_COORDS ) + { + ptricmds[2] = FloatToHalf((float)ptricmds[2] * (1.0f / 32768.0f)); + ptricmds[3] = FloatToHalf((float)ptricmds[3] * (1.0f / 32768.0f)); + g_viewerSettings.numModelChanges++; + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/utils/hlmv/studio_utils.cpp b/utils/hlmv/studio_utils.cpp new file mode 100644 index 0000000..c90fd0d --- /dev/null +++ b/utils/hlmv/studio_utils.cpp @@ -0,0 +1,1565 @@ +/*** +* +* Copyright (c) 1998, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ +// updates: +// 1-4-99 fixed file texture load and file read bug + +//////////////////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include "StudioModel.h" +#include "GLWindow.h" +#include "ViewerSettings.h" +#include "ControlPanel.h" +#include "activity.h" +#include "stringlib.h" + +#include +#include "mdlviewer.h" + +StudioModel g_studioModel; +extern bool bUseWeaponOrigin; +extern bool bUseParanoiaFOV; + +//////////////////////////////////////////////////////////////////////// + +void CBaseBoneSetup :: debugMsg( char *szFmt, ... ) +{ + static char buffer[1024]; + va_list args; + int result; + + va_start( args, szFmt ); + result = Q_vsnprintf( buffer, 99999, szFmt, args ); + va_end( args ); + + Sys_PrintLog( buffer ); +} + +mstudioanim_t *CBaseBoneSetup :: GetAnimSourceData( mstudioseqdesc_t *pseqdesc ) +{ + mstudioseqgroup_t *pseqgroup = (mstudioseqgroup_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqgroupindex) + pseqdesc->seqgroup; + + if( pseqdesc->seqgroup == 0 ) + return (mstudioanim_t *)((byte *)m_pStudioHeader + pseqgroup->data + pseqdesc->animindex); + + return (mstudioanim_t *)((byte *)g_studioModel.getAnimHeader( pseqdesc->seqgroup ) + pseqdesc->animindex); +} + +void CBaseBoneSetup :: debugLine( const Vector& origin, const Vector& dest, int r, int g, int b, bool noDepthTest, float duration ) +{ + if( noDepthTest ) + glDisable( GL_DEPTH_TEST ); + + glColor3ub( r, g, b ); + + glBegin( GL_LINES ); + glVertex3fv( origin ); + glVertex3fv( dest ); + glEnd(); +} + +//////////////////////////////////////////////////////////////////////// + +static int g_tex_base = TEXTURE_COUNT; + +//Mugsy - upped the maximum texture size to 512. All changes are the replacement of '256' +//with this define, MAX_TEXTURE_DIMS +#define MAX_TEXTURE_DIMS 1024 + +void StudioModel::UploadTexture(mstudiotexture_t *ptexture, byte *data, byte *srcpal, int name) +{ + int row1[MAX_TEXTURE_DIMS], row2[MAX_TEXTURE_DIMS], col1[MAX_TEXTURE_DIMS], col2[MAX_TEXTURE_DIMS]; + byte *pix1, *pix2, *pix3, *pix4; + byte *tex, *in, *out, pal[768]; + int i, j; + + // convert texture to power of 2 + int outwidth; + for (outwidth = 1; outwidth < ptexture->width; outwidth <<= 1); + + if (outwidth > MAX_TEXTURE_DIMS) + outwidth = MAX_TEXTURE_DIMS; + + int outheight; + for (outheight = 1; outheight < ptexture->height; outheight <<= 1); + + if (outheight > MAX_TEXTURE_DIMS) + outheight = MAX_TEXTURE_DIMS; + + in = (byte *)malloc( ptexture->width * ptexture->height * 4); + if (!in) return; + + if( ptexture->flags & STUDIO_NF_COLORMAP ) + { + memcpy( pal, srcpal, 768 ); + + if( !Q_strnicmp( ptexture->name, "DM_Base", 7 )) + { + PaletteHueReplace( pal, g_viewerSettings.topcolor, PLATE_HUE_START, PLATE_HUE_END ); + PaletteHueReplace( pal, g_viewerSettings.bottomcolor, SUIT_HUE_START, SUIT_HUE_END ); + } + else + { + int len = Q_strlen( ptexture->name ); + int low, mid, high; + char sz[32]; +#if 1 + // GoldSource parser + if( len == 18 || len == 22 ) + { + char ch = ptexture->name[5]; + + if( len != 18 || ch == 'c' || ch == 'C' ) + { + Q_strncpy( sz, &ptexture->name[7], 4 ); + low = Q_atoi( sz ); + Q_strncpy( sz, &ptexture->name[11], 4 ); + mid = Q_atoi( sz ); + + if( len == 22 ) + { + Q_strncpy( sz, &ptexture->name[15], 4 ); + high = Q_atoi( sz ); + } + else high = 0; + + PaletteHueReplace( pal, g_viewerSettings.topcolor, low, mid ); + if( high ) PaletteHueReplace( pal, g_viewerSettings.bottomcolor, mid + 1, high ); + } + } +#else + // Xash3D parser + Q_strncpy( sz, ptexture->name + 7, 4 ); + low = bound( 0, Q_atoi( sz ), 255 ); + Q_strncpy( sz, ptexture->name + 11, 4 ); + mid = bound( 0, Q_atoi( sz ), 255 ); + Q_strncpy( sz, ptexture->name + 15, 4 ); + high = bound( 0, Q_atoi( sz ), 255 ); + + PaletteHueReplace( pal, g_viewerSettings.topcolor, low, mid ); + if( high ) PaletteHueReplace( pal, g_viewerSettings.bottomcolor, mid + 1, high ); +#endif + } + } + else + { + memcpy( pal, srcpal, 768 ); + } + + // expand pixels to rgba + for (i=0 ; i < ptexture->width * ptexture->height; i++) + { + if(( ptexture->flags & STUDIO_NF_MASKED ) && data[i] == 255 ) + { + in[i*4+0] = 0x00; + in[i*4+1] = 0x00; + in[i*4+2] = 0x00; + in[i*4+3] = 0x00; + } + else + { + in[i*4+0] = pal[data[i]*3+0]; + in[i*4+1] = pal[data[i]*3+1]; + in[i*4+2] = pal[data[i]*3+2]; + in[i*4+3] = 0xFF; + } + } + + tex = out = (byte *)malloc( outwidth * outheight * 4); + if (!out) + { + return; + } + + for (i = 0; i < outwidth; i++) + { + col1[i] = (int) ((i + 0.25) * (ptexture->width / (float)outwidth)); + col2[i] = (int) ((i + 0.75) * (ptexture->width / (float)outwidth)); + } + + for (i = 0; i < outheight; i++) + { + row1[i] = (int) ((i + 0.25) * (ptexture->height / (float)outheight)) * ptexture->width; + row2[i] = (int) ((i + 0.75) * (ptexture->height / (float)outheight)) * ptexture->width; + } + + // scale down and convert to 32bit RGB + for (i=0 ; i>2; + out[1] = (pix1[1] + pix2[1] + pix3[1] + pix4[1])>>2; + out[2] = (pix1[2] + pix2[2] + pix3[2] + pix4[2])>>2; + out[3] = (pix1[3] + pix2[3] + pix3[3] + pix4[3])>>2; + } + } + + GLint outFormat = GL_RGB; + + if( ptexture->flags & STUDIO_NF_MASKED ) + outFormat = GL_RGBA; + + glBindTexture( GL_TEXTURE_2D, name ); + glHint( GL_GENERATE_MIPMAP_HINT_SGIS, GL_NICEST ); + glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE ); + glTexImage2D( GL_TEXTURE_2D, 0, outFormat, outwidth, outheight, 0, GL_RGBA, GL_UNSIGNED_BYTE, tex ); + glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + const char *extensions = (const char *)glGetString( GL_EXTENSIONS ); + + // check for anisotropy support + if( Q_strstr( extensions, "GL_EXT_texture_filter_anisotropic" )) + { + float anisotropy = 1.0f; + glGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisotropy ); + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropy ); + } + + free( tex ); + free( in ); +} + +void StudioModel :: FreeModel( void ) +{ + if( g_viewerSettings.numModelChanges ) + { + if( !mxMessageBox( g_GlWindow, "Model has changes. Do you wish to save them?", g_appTitle, MX_MB_YESNO | MX_MB_QUESTION )) + { + char *ptr = (char *)mxGetSaveFileName( g_GlWindow , g_viewerSettings.modelPath, "*.mdl", g_viewerSettings.modelPath ); + if( ptr ) + { + char filename[256]; + char ext[16]; + + strcpy( filename, ptr ); + strcpy( ext, mx_getextension( filename )); + if( mx_strcasecmp( ext, ".mdl" )) + strcat( filename, ".mdl" ); + + if( !g_studioModel.SaveModel( filename )) + mxMessageBox( g_GlWindow, "Error saving model.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + } + } + + g_viewerSettings.numModelChanges = 0; // all the settings are handled or invalidated + } + + if (m_pJiggleBones) + { + delete m_pJiggleBones; + m_pJiggleBones = NULL; + } + + int i; + + if( m_ptexturehdr ) + { + // deleting textures + int textures[MAXSTUDIOSKINS]; + for (i = 0; i < m_ptexturehdr->numtextures; i++) + textures[i] = g_tex_base + i; + + glDeleteTextures (m_ptexturehdr->numtextures, (const GLuint *)textures); + } + + if (m_pstudiohdr) + free (m_pstudiohdr); + + if (m_ptexturehdr && m_owntexmodel) + free (m_ptexturehdr); + + g_boneSetup.SetStudioPointers( NULL, NULL ); + + g_viewerSettings.numSourceChanges = 0; + m_pstudiohdr = m_ptexturehdr = 0; + remap_textures = false; + m_owntexmodel = false; + m_numeditfields = 0; + + for (i = 0; i < 32; i++) + { + if (m_panimhdr[i]) + { + free (m_panimhdr[i]); + m_panimhdr[i] = 0; + } + } +} + +void StudioModel::RemapTextures( void ) +{ + if( !remap_textures ) return; + + studiohdr_t *phdr = getTextureHeader(); + mstudiotexture_t *ptexture; + byte *pin = (byte *)phdr; + + if (phdr && phdr->textureindex > 0 && phdr->numtextures <= MAXSTUDIOSKINS) + { + ptexture = (mstudiotexture_t *)(pin + phdr->textureindex); + + for (int i = 0; i < phdr->numtextures; i++) + { + if( !( ptexture[i].flags & STUDIO_NF_COLORMAP )) + continue; + + UploadTexture( &ptexture[i], pin + ptexture[i].index, pin + ptexture[i].width * ptexture[i].height + ptexture[i].index, g_tex_base + i ); + } + } + + remap_textures = false; +} + +studiohdr_t *StudioModel::LoadModel( char *modelname ) +{ + FILE *fp; + long size; + void *buffer; + + if (!modelname) + return 0; + + // load the model + if( (fp = fopen( modelname, "rb" )) == NULL) + return 0; + + fseek( fp, 0, SEEK_END ); + size = ftell( fp ); + fseek( fp, 0, SEEK_SET ); + + buffer = malloc( size ); + if (!buffer || size <= 0) + { + fclose (fp); + return 0; + } + + fread( buffer, size, 1, fp ); + fclose( fp ); + + byte *pin; + studiohdr_t *phdr; + mstudiotexture_t *ptexture; + + pin = (byte *)buffer; + phdr = (studiohdr_t *)pin; + ptexture = (mstudiotexture_t *)(pin + phdr->textureindex); + + if (strncmp ((const char *) buffer, "IDST", 4) && + strncmp ((const char *) buffer, "IDSQ", 4)) + { + if (!strncmp ((const char *) buffer, "IDPO", 4 )) + mxMessageBox( g_GlWindow, "Quake 1 models doesn't supported.", g_appTitle, MX_MB_OK | MX_MB_ERROR ); + else mxMessageBox( g_GlWindow, "Unknown file format.", g_appTitle, MX_MB_OK | MX_MB_ERROR ); + + free (buffer); + return 0; + } + + if (!strncmp ((const char *) buffer, "IDSQ", 4) && !m_pstudiohdr) + { + free (buffer); + return 0; + } + + if( phdr->version != STUDIO_VERSION ) + { + mxMessageBox( g_GlWindow, "Unsupported studio version.", g_appTitle, MX_MB_OK | MX_MB_ERROR ); + free (buffer); + return 0; + } + + if (phdr->textureindex > 0 && phdr->numtextures <= MAXSTUDIOSKINS) + { + for (int i = 0; i < phdr->numtextures; i++) + { + if( !Q_strnicmp( ptexture[i].name, "DM_Base", 7 ) || !Q_strnicmp( ptexture[i].name, "remap", 5 )) + ptexture[i].flags |= STUDIO_NF_COLORMAP; + + UploadTexture( &ptexture[i], pin + ptexture[i].index, pin + ptexture[i].width * ptexture[i].height + ptexture[i].index, g_tex_base + i ); + } + } + + m_pJiggleBones = NULL; + m_numeditfields = 0; + + if (!m_pstudiohdr) + m_pstudiohdr = (studiohdr_t *)buffer; + + return (studiohdr_t *)buffer; +} + +bool StudioModel::PostLoadModel( char *modelname ) +{ + // preload textures + if (m_pstudiohdr->numtextures == 0) + { + char texturename[256]; + + strcpy( texturename, modelname ); + strcpy( &texturename[strlen(texturename) - 4], "T.mdl" ); + + m_ptexturehdr = LoadModel( texturename ); + if (!m_ptexturehdr) + { + FreeModel (); + return false; + } + m_owntexmodel = true; + } + else + { + m_ptexturehdr = m_pstudiohdr; + m_owntexmodel = false; + } + + // preload animations + if (m_pstudiohdr->numseqgroups > 1) + { + for (int i = 1; i < m_pstudiohdr->numseqgroups; i++) + { + char seqgroupname[256]; + + strcpy( seqgroupname, modelname ); + sprintf( &seqgroupname[strlen(seqgroupname) - 4], "%02d.mdl", i ); + + m_panimhdr[i] = LoadModel( seqgroupname ); + if (!m_panimhdr[i]) + { + FreeModel (); + return false; + } + } + } + + SetSequence (0); + SetController (0, 0.0f); + SetController (1, 0.0f); + SetController (2, 0.0f); + SetController (3, 0.0f); + SetMouth (0.0f); + + int n; + for (n = 0; n < m_pstudiohdr->numbodyparts; n++) + SetBodygroup (n, 0); + + SetSkin (0); + + char basename[64]; + + COM_FileBase( modelname, basename ); + + if( !Q_strnicmp( basename, "v_", 2 )) + { + g_MDLViewer->checkboxSet( IDC_OPTIONS_WEAPONORIGIN, true ); + bUseWeaponOrigin = true; + } + else + { + g_MDLViewer->checkboxSet( IDC_OPTIONS_WEAPONORIGIN, false ); + bUseWeaponOrigin = false; + } + + // reset all the changes + g_viewerSettings.numModelChanges = 0; + g_viewerSettings.numSourceChanges = 0; + bUseParanoiaFOV = false; + + g_boneSetup.SetStudioPointers( m_pstudiohdr, m_poseparameter ); + + // set poseparam sliders to their default values + g_boneSetup.CalcDefaultPoseParameters( m_poseparameter ); + + if( FBitSet( m_pstudiohdr->flags, STUDIO_HAS_BONEINFO )) + { + // NOTE: extended boneinfo goes immediately after bones + mstudioboneinfo_t *boneinfo = (mstudioboneinfo_t *)((byte *)m_pstudiohdr + m_pstudiohdr->boneindex + m_pstudiohdr->numbones * sizeof( mstudiobone_t )); + + for( n = 0; n < m_pstudiohdr->numbones; n++ ) + LoadLocalMatrix( n, &boneinfo[n] ); + } + + // analyze ACT_VM_ for paranoia viewmodels + mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pstudiohdr + m_pstudiohdr->seqindex); + + for( n = 0; n < m_pstudiohdr->numseq; n++ ) + { + if( pseqdesc[n].activity >= ACT_VM_NONE && pseqdesc[n].activity <= ACT_VM_RESERVED4 ) + { + g_MDLViewer->checkboxSet( IDC_OPTIONS_WEAPONORIGIN, true ); + bUseWeaponOrigin = bUseParanoiaFOV = true; + break; // it's P2 viewmodel! + } + } + + return true; +} + +void StudioModel :: LoadLocalMatrix( int bone, mstudioboneinfo_t *boneinfo ) +{ + // transform Valve matrix to Xash matrix + m_plocaltransform[bone][0][0] = boneinfo->poseToBone[0][0]; + m_plocaltransform[bone][0][1] = boneinfo->poseToBone[1][0]; + m_plocaltransform[bone][0][2] = boneinfo->poseToBone[2][0]; + + m_plocaltransform[bone][1][0] = boneinfo->poseToBone[0][1]; + m_plocaltransform[bone][1][1] = boneinfo->poseToBone[1][1]; + m_plocaltransform[bone][1][2] = boneinfo->poseToBone[2][1]; + + m_plocaltransform[bone][2][0] = boneinfo->poseToBone[0][2]; + m_plocaltransform[bone][2][1] = boneinfo->poseToBone[1][2]; + m_plocaltransform[bone][2][2] = boneinfo->poseToBone[2][2]; + + m_plocaltransform[bone][3][0] = boneinfo->poseToBone[0][3]; + m_plocaltransform[bone][3][1] = boneinfo->poseToBone[1][3]; + m_plocaltransform[bone][3][2] = boneinfo->poseToBone[2][3]; +} + +bool StudioModel::SaveModel ( char *modelname ) +{ + if (!modelname) + return false; + + if (!m_pstudiohdr) + return false; + + FILE *file; + + file = fopen (modelname, "wb"); + if (!file) + return false; + + fwrite (m_pstudiohdr, sizeof (byte), m_pstudiohdr->length, file); + fclose (file); + + // write texture model + if (m_owntexmodel && m_ptexturehdr) + { + char texturename[256]; + + strcpy( texturename, modelname ); + strcpy( &texturename[strlen(texturename) - 4], "T.mdl" ); + + file = fopen (texturename, "wb"); + if (file) + { + fwrite (m_ptexturehdr, sizeof (byte), m_ptexturehdr->length, file); + fclose (file); + } + } + + // write seq groups + if (m_pstudiohdr->numseqgroups > 1) + { + for (int i = 1; i < m_pstudiohdr->numseqgroups; i++) + { + char seqgroupname[256]; + + strcpy( seqgroupname, modelname ); + sprintf( &seqgroupname[strlen(seqgroupname) - 4], "%02d.mdl", i ); + + file = fopen (seqgroupname, "wb"); + if (file) + { + fwrite (m_panimhdr[i], sizeof (byte), m_panimhdr[i]->length, file); + fclose (file); + } + } + } + + return true; +} + +void StudioModel::PaletteHueReplace( byte *palSrc, int newHue, int start, int end ) +{ + float r, g, b; + float maxcol, mincol; + float hue, val, sat; + int i; + + hue = (float)(newHue * ( 360.0f / 255 )); + + for( i = start; i <= end; i++ ) + { + r = palSrc[i*3+0]; + g = palSrc[i*3+1]; + b = palSrc[i*3+2]; + + maxcol = max( max( r, g ), b ) / 255.0f; + mincol = min( min( r, g ), b ) / 255.0f; + + if( maxcol == 0 ) continue; + + val = maxcol; + sat = (maxcol - mincol) / maxcol; + + mincol = val * (1.0f - sat); + + if( hue <= 120.0f ) + { + b = mincol; + if( hue < 60 ) + { + r = val; + g = mincol + hue * (val - mincol) / (120.0f - hue); + } + else + { + g = val; + r = mincol + (120.0f - hue) * (val - mincol) / hue; + } + } + else if( hue <= 240.0f ) + { + r = mincol; + if( hue < 180.0f ) + { + g = val; + b = mincol + (hue - 120.0f) * (val - mincol) / (240.0f - hue); + } + else + { + b = val; + g = mincol + (240.0f - hue) * (val - mincol) / (hue - 120.0f); + } + } + else + { + g = mincol; + if( hue < 300.0f ) + { + b = val; + r = mincol + (hue - 240.0f) * (val - mincol) / (360.0f - hue); + } + else + { + r = val; + b = mincol + (360.0f - hue) * (val - mincol) / (hue - 240.0f); + } + } + + palSrc[i*3+0] = (byte)(r * 255); + palSrc[i*3+1] = (byte)(g * 255); + palSrc[i*3+2] = (byte)(b * 255); + } +} + +//////////////////////////////////////////////////////////////////////// + +int StudioModel::GetSequence( ) +{ + return m_sequence; +} + +int StudioModel :: SetSequence( int iSequence ) +{ + if (iSequence > m_pstudiohdr->numseq) + return m_sequence; + + mstudioseqdesc_t *poldseqdesc = (mstudioseqdesc_t *)((byte *)m_pstudiohdr + m_pstudiohdr->seqindex) + m_sequence; + mstudioseqdesc_t *pnewseqdesc = (mstudioseqdesc_t *)((byte *)m_pstudiohdr + m_pstudiohdr->seqindex) + iSequence; + + // sequence has changed, hold the previous sequence info + if( iSequence != m_sequence && !bUseWeaponOrigin && !FBitSet( pnewseqdesc->flags, STUDIO_SNAP )) + { + blend_sequence_t *seqblending; + + // move current sequence into circular buffer + m_current_seqblend = (m_current_seqblend + 1) & MASK_SEQBLENDS; + + seqblending = &m_seqblend[m_current_seqblend]; + + seqblending->blendtime = m_flTime; + seqblending->sequence = m_sequence; + seqblending->cycle = Q_min( m_cycle, 1.0f ); + seqblending->fadeout = Q_min( poldseqdesc->fadeouttime / 100.0f, pnewseqdesc->fadeintime / 100.0f ); + if( seqblending->fadeout <= 0.0f ) seqblending->fadeout = 0.2f; // force to default + + // save current blends to right lerping from last sequence + for( int i = 0; i < 2; i++ ) + seqblending->blending[i] = m_blending[i]; + } + + m_sequence = iSequence; + sequence_reset = true; + m_cycle = 0; + + return m_sequence; +} + + +void StudioModel :: ExtractBbox( Vector &mins, Vector &maxs ) +{ + mstudioseqdesc_t *pseqdesc; + + if( !m_pstudiohdr || m_pstudiohdr->numseq <= 0 ) + { + mins = maxs = g_vecZero; + return; + } + + m_sequence = bound( 0, m_sequence, m_pstudiohdr->numseq - 1 ); + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pstudiohdr + m_pstudiohdr->seqindex) + m_sequence; + mins = pseqdesc->bbmin; + maxs = pseqdesc->bbmax; +} + +float StudioModel :: GetDuration( int iSequence ) +{ + return g_boneSetup.LocalDuration( iSequence ); +} + +float StudioModel :: GetDuration( void ) +{ + return GetDuration( m_sequence ); +} + +void StudioModel::GetSequenceInfo( float *pflFrameRate, float *pflGroundSpeed ) +{ + mstudioseqdesc_t *pseqdesc; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pstudiohdr + m_pstudiohdr->seqindex) + (int)m_sequence; + + if (pseqdesc->numframes > 1) + { + *pflFrameRate = 256 * pseqdesc->fps / (pseqdesc->numframes - 1); + *pflGroundSpeed = sqrt( pseqdesc->linearmovement[0]*pseqdesc->linearmovement[0]+ pseqdesc->linearmovement[1]*pseqdesc->linearmovement[1]+ pseqdesc->linearmovement[2]*pseqdesc->linearmovement[2] ); + *pflGroundSpeed = *pflGroundSpeed * pseqdesc->fps / (pseqdesc->numframes - 1); + } + else + { + *pflFrameRate = 256.0; + *pflGroundSpeed = 0.0; + } +} + +void StudioModel :: GetMovement( float &prevCycle, Vector &vecPos, Vector &vecAngles ) +{ + if( !m_pstudiohdr ) + { + vecPos.Init(); + vecAngles.Init(); + return; + } + + // assume that changes < -0.5 are loops.... + if( m_cycle - prevCycle < -0.5f ) + { + prevCycle = prevCycle - 1.0f; + } + + g_boneSetup.SeqMovement( m_sequence, prevCycle, m_cycle, vecPos, vecAngles ); + prevCycle = m_cycle; +} + +int StudioModel :: getNumBlendings( void ) +{ + if( !m_pstudiohdr ) return 0; + + mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pstudiohdr + m_pstudiohdr->seqindex) + m_sequence; + + return pseqdesc->numblends; +} + +int StudioModel :: hasLocalBlending( void ) +{ + if( !m_pstudiohdr ) return 0; + + mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pstudiohdr + m_pstudiohdr->seqindex) + m_sequence; + + return ( pseqdesc->numblends > 1 ) && !FBitSet( pseqdesc->flags, STUDIO_BLENDPOSE ); +} + +float StudioModel::SetController( int iController, float flValue ) +{ + if (!m_pstudiohdr) + return 0.0f; + + mstudiobonecontroller_t *pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pstudiohdr + m_pstudiohdr->bonecontrollerindex); + + // find first controller that matches the index + for (int i = 0; i < m_pstudiohdr->numbonecontrollers; i++, pbonecontroller++) + { + if (pbonecontroller->index == iController) + break; + } + if (i >= m_pstudiohdr->numbonecontrollers) + return flValue; + + // wrap 0..360 if it's a rotational controller + if (pbonecontroller->type & (STUDIO_XR | STUDIO_YR | STUDIO_ZR)) + { + // ugly hack, invert value if end < start + if (pbonecontroller->end < pbonecontroller->start) + flValue = -flValue; + + // does the controller not wrap? + if (pbonecontroller->start + 359.0 >= pbonecontroller->end) + { + if (flValue > ((pbonecontroller->start + pbonecontroller->end) / 2.0) + 180) + flValue = flValue - 360; + if (flValue < ((pbonecontroller->start + pbonecontroller->end) / 2.0) - 180) + flValue = flValue + 360; + } + else + { + if (flValue > 360) + flValue = flValue - (int)(flValue / 360.0) * 360.0; + else if (flValue < 0) + flValue = flValue + (int)((flValue / -360.0) + 1) * 360.0; + } + } + + int setting = (int) (255 * (flValue - pbonecontroller->start) / (pbonecontroller->end - pbonecontroller->start)); + + if (setting < 0) setting = 0; + if (setting > 255) setting = 255; + m_controller[iController] = setting; + + return setting * (1.0 / 255.0) * (pbonecontroller->end - pbonecontroller->start) + pbonecontroller->start; +} + +int StudioModel :: LookupPoseParameter( char const *szName ) +{ + if( !m_pstudiohdr ) + return false; + + for( int iParameter = 0; iParameter < g_boneSetup.CountPoseParameters(); iParameter++ ) + { + const mstudioposeparamdesc_t *pPose = g_boneSetup.pPoseParameter( iParameter ); + + if( !Q_stricmp( szName, pPose->name )) + { + return iParameter; + } + } + + return -1; +} + +float StudioModel::SetPoseParameter( char const *szName, float flValue ) +{ + return SetPoseParameter( LookupPoseParameter( szName ), flValue ); +} + +float StudioModel::SetPoseParameter( int iParameter, float flValue ) +{ + if( !m_pstudiohdr ) + return 0.0f; + + return g_boneSetup.SetPoseParameter( iParameter, flValue, m_poseparameter[iParameter] ); +} + +float StudioModel::GetPoseParameter( char const *szName ) +{ + return GetPoseParameter( LookupPoseParameter( szName )); +} + +float* StudioModel::GetPoseParameters() +{ + return m_poseparameter; +} + +float StudioModel::GetPoseParameter( int iParameter ) +{ + if( !m_pstudiohdr ) + return 0.0f; + + return g_boneSetup.GetPoseParameter( iParameter, m_poseparameter[iParameter] ); +} + +bool StudioModel::GetPoseParameterRange( int iParameter, float *pflMin, float *pflMax ) +{ + *pflMin = 0; + *pflMax = 0; + + if( !m_pstudiohdr ) + return false; + + if( iParameter < 0 || iParameter >= g_boneSetup.CountPoseParameters( )) + return false; + + const mstudioposeparamdesc_t *pPose = g_boneSetup.pPoseParameter( iParameter ); + + *pflMin = pPose->start; + *pflMax = pPose->end; + + return true; +} + +float StudioModel::SetMouth( float flValue ) +{ + if (!m_pstudiohdr) + return 0.0f; + + mstudiobonecontroller_t *pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pstudiohdr + m_pstudiohdr->bonecontrollerindex); + + // find first controller that matches the mouth + for (int i = 0; i < m_pstudiohdr->numbonecontrollers; i++, pbonecontroller++) + { + if (pbonecontroller->index == STUDIO_MOUTH) + break; + } + + // wrap 0..360 if it's a rotational controller + if (pbonecontroller->type & (STUDIO_XR | STUDIO_YR | STUDIO_ZR)) + { + // ugly hack, invert value if end < start + if (pbonecontroller->end < pbonecontroller->start) + flValue = -flValue; + + // does the controller not wrap? + if (pbonecontroller->start + 359.0 >= pbonecontroller->end) + { + if (flValue > ((pbonecontroller->start + pbonecontroller->end) / 2.0) + 180) + flValue = flValue - 360; + if (flValue < ((pbonecontroller->start + pbonecontroller->end) / 2.0) - 180) + flValue = flValue + 360; + } + else + { + if (flValue > 360) + flValue = flValue - (int)(flValue / 360.0) * 360.0; + else if (flValue < 0) + flValue = flValue + (int)((flValue / -360.0) + 1) * 360.0; + } + } + + int setting = (int) (64 * (flValue - pbonecontroller->start) / (pbonecontroller->end - pbonecontroller->start)); + + if (setting < 0) setting = 0; + if (setting > 64) setting = 64; + m_mouth = setting; + + return setting * (1.0 / 64.0) * (pbonecontroller->end - pbonecontroller->start) + pbonecontroller->start; +} + +void StudioModel :: SetBlendValue( int iBlender, int iValue ) +{ + m_blending[iBlender] = bound( 0, iValue, 255 ); + + if( hasLocalBlending( )) + m_poseparameter[iBlender] = bound( 0.0f, (float)iValue / 255.0f, 1.0f ); +} + +float StudioModel::SetBlending( int iBlender, float flValue ) +{ + mstudioseqdesc_t *pseqdesc; + int iOutBlend = iBlender; + + if (!m_pstudiohdr) + return 0.0f; + + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pstudiohdr + m_pstudiohdr->seqindex) + (int)m_sequence; + + if( pseqdesc->numblends == 4 || pseqdesc->numblends == 9 ) + iBlender = 0; // grab info from first blender + + if (pseqdesc->blendtype[iBlender] == 0) + return flValue; + + if (pseqdesc->blendtype[iBlender] & (STUDIO_XR | STUDIO_YR | STUDIO_ZR)) + { + // ugly hack, invert value if end < start + if (pseqdesc->blendend[iBlender] < pseqdesc->blendstart[iBlender]) + flValue = -flValue; + + // does the controller not wrap? + if (pseqdesc->blendstart[iBlender] + 359.0 >= pseqdesc->blendend[iBlender]) + { + if (flValue > ((pseqdesc->blendstart[iBlender] + pseqdesc->blendend[iBlender]) / 2.0) + 180) + flValue = flValue - 360; + if (flValue < ((pseqdesc->blendstart[iBlender] + pseqdesc->blendend[iBlender]) / 2.0) - 180) + flValue = flValue + 360; + } + } + + int setting = (int) (255 * (flValue - pseqdesc->blendstart[iBlender]) / (pseqdesc->blendend[iBlender] - pseqdesc->blendstart[iBlender])); + + if (setting < 0) setting = 0; + if (setting > 255) setting = 255; + + m_blending[iOutBlend] = setting; + + if( hasLocalBlending( )) + m_poseparameter[iOutBlend] = bound( 0.0f, (float)setting / 255.0f, 1.0f ); + +// return setting * (1.0 / 255.0) * (pseqdesc->blendend[iBlender] - pseqdesc->blendstart[iBlender]) + pseqdesc->blendstart[iBlender]; + + return setting; +} + +int StudioModel::SetBodygroup( int iGroup, int iValue ) +{ + if (!m_pstudiohdr) + return 0; + + if (iGroup > m_pstudiohdr->numbodyparts) + return -1; + + mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)m_pstudiohdr + m_pstudiohdr->bodypartindex) + iGroup; + + int iCurrent = (m_bodynum / pbodypart->base) % pbodypart->nummodels; + + if (iValue >= pbodypart->nummodels) + return iCurrent; + + m_bodynum = (m_bodynum - (iCurrent * pbodypart->base) + (iValue * pbodypart->base)); + + return m_bodynum; +} + + +int StudioModel::SetSkin( int iValue ) +{ + if (!m_pstudiohdr) + return 0; + + if (iValue >= m_pstudiohdr->numskinfamilies) + { + return m_skinnum; + } + + m_skinnum = iValue; + + return iValue; +} + +void StudioModel::ComputeWeightColor( mstudioboneweight_t *boneweights, Vector &result ) +{ + int numbones = 0; + + for( int i = 0; i < MAXSTUDIOBONEWEIGHTS; i++ ) + { + if( boneweights->bone[i] != -1 ) + numbones++; + } + + if( !g_viewerSettings.studio_blendweights ) + numbones = 1; + + switch( numbones ) + { + case 0: + result = Vector( 0.0f, 0.0f, 0.0f ); + break; + case 1: + result = Vector( 0.0f, 1.0f, 0.0f ); + break; + case 2: + result = Vector( 1.0f, 1.0f, 0.0f ); + break; + case 3: + result = Vector( 1.0f, 0.0f, 1.0f ); + break; + case 4: + result = Vector( 1.0f, 0.0f, 1.0f ); + break; + default: + result = Vector( 1.0f, 1.0f, 1.0f ); + break; + } +} + +void StudioModel::ComputeSkinMatrix( mstudioboneweight_t *boneweights, matrix3x4 &result ) +{ + float flWeight0, flWeight1, flWeight2, flWeight3; + int numbones = 0; + + for( int i = 0; i < MAXSTUDIOBONEWEIGHTS; i++ ) + { + if( boneweights->bone[i] != -1 ) + numbones++; + } + + if( !g_viewerSettings.studio_blendweights ) + numbones = 1; + + if( numbones == 4 ) + { + matrix3x4 &boneMat0 = m_pworldtransform[boneweights->bone[0]]; + matrix3x4 &boneMat1 = m_pworldtransform[boneweights->bone[1]]; + matrix3x4 &boneMat2 = m_pworldtransform[boneweights->bone[2]]; + matrix3x4 &boneMat3 = m_pworldtransform[boneweights->bone[3]]; + flWeight0 = boneweights->weight[0] / 255.0f; + flWeight1 = boneweights->weight[1] / 255.0f; + flWeight2 = boneweights->weight[2] / 255.0f; + flWeight3 = boneweights->weight[3] / 255.0f; + + result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2 + boneMat3[0][0] * flWeight3; + result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2 + boneMat3[0][1] * flWeight3; + result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2 + boneMat3[0][2] * flWeight3; + result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2 + boneMat3[1][0] * flWeight3; + result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2 + boneMat3[1][1] * flWeight3; + result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2 + boneMat3[1][2] * flWeight3; + result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2 + boneMat3[2][0] * flWeight3; + result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2 + boneMat3[2][1] * flWeight3; + result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2 + boneMat3[2][2] * flWeight3; + result[3][0] = boneMat0[3][0] * flWeight0 + boneMat1[3][0] * flWeight1 + boneMat2[3][0] * flWeight2 + boneMat3[3][0] * flWeight3; + result[3][1] = boneMat0[3][1] * flWeight0 + boneMat1[3][1] * flWeight1 + boneMat2[3][1] * flWeight2 + boneMat3[3][1] * flWeight3; + result[3][2] = boneMat0[3][2] * flWeight0 + boneMat1[3][2] * flWeight1 + boneMat2[3][2] * flWeight2 + boneMat3[3][2] * flWeight3; + } + else if( numbones == 3 ) + { + matrix3x4 &boneMat0 = m_pworldtransform[boneweights->bone[0]]; + matrix3x4 &boneMat1 = m_pworldtransform[boneweights->bone[1]]; + matrix3x4 &boneMat2 = m_pworldtransform[boneweights->bone[2]]; + flWeight0 = boneweights->weight[0] / 255.0f; + flWeight1 = boneweights->weight[1] / 255.0f; + flWeight2 = boneweights->weight[2] / 255.0f; + + result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2; + result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2; + result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2; + result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2; + result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2; + result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2; + result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2; + result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2; + result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2; + result[3][0] = boneMat0[3][0] * flWeight0 + boneMat1[3][0] * flWeight1 + boneMat2[3][0] * flWeight2; + result[3][1] = boneMat0[3][1] * flWeight0 + boneMat1[3][1] * flWeight1 + boneMat2[3][1] * flWeight2; + result[3][2] = boneMat0[3][2] * flWeight0 + boneMat1[3][2] * flWeight1 + boneMat2[3][2] * flWeight2; + } + else if( numbones == 2 ) + { + matrix3x4 &boneMat0 = m_pworldtransform[boneweights->bone[0]]; + matrix3x4 &boneMat1 = m_pworldtransform[boneweights->bone[1]]; + flWeight0 = boneweights->weight[0] / 255.0f; + flWeight1 = boneweights->weight[1] / 255.0f; + + // NOTE: Inlining here seems to make a fair amount of difference + result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1; + result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1; + result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1; + result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1; + result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1; + result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1; + result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1; + result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1; + result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1; + result[3][0] = boneMat0[3][0] * flWeight0 + boneMat1[3][0] * flWeight1; + result[3][1] = boneMat0[3][1] * flWeight0 + boneMat1[3][1] * flWeight1; + result[3][2] = boneMat0[3][2] * flWeight0 + boneMat1[3][2] * flWeight1; + } + else + { + result = m_pworldtransform[boneweights->bone[0]]; + } +} + +void StudioModel::scaleMeshes (float scale) +{ + if (!m_pstudiohdr) + return; + + int i, j, k; + + // scale verts + int tmp = m_bodynum; + for (i = 0; i < m_pstudiohdr->numbodyparts; i++) + { + mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)m_pstudiohdr + m_pstudiohdr->bodypartindex) + i; + for (j = 0; j < pbodypart->nummodels; j++) + { + SetBodygroup (i, j); + SetupModel (i); + + Vector *pstudioverts = (Vector *)((byte *)m_pstudiohdr + m_pmodel->vertindex); + + for (k = 0; k < m_pmodel->numverts; k++) + pstudioverts[k] *= scale; + } + } + + m_bodynum = tmp; + + // scale complex hitboxes + mstudiobbox_t *pbboxes = (mstudiobbox_t *) ((byte *) m_pstudiohdr + m_pstudiohdr->hitboxindex); + for (i = 0; i < m_pstudiohdr->numhitboxes; i++) + { + pbboxes[i].bbmin *= scale; + pbboxes[i].bbmax *= scale; + } + + // scale bounding boxes + mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pstudiohdr + m_pstudiohdr->seqindex); + for (i = 0; i < m_pstudiohdr->numseq; i++) + { + pseqdesc[i].bbmin *= scale; + pseqdesc[i].bbmax *= scale; + } + + // maybe scale exeposition, pivots, attachments + g_viewerSettings.numModelChanges++; +} + + + +void StudioModel::scaleBones (float scale) +{ + if (!m_pstudiohdr) + return; + + mstudiobone_t *pbones = (mstudiobone_t *) ((byte *) m_pstudiohdr + m_pstudiohdr->boneindex); + for (int i = 0; i < m_pstudiohdr->numbones; i++) + { + for (int j = 0; j < 3; j++) + { + pbones[i].value[j] *= scale; + pbones[i].scale[j] *= scale; + } + } + + g_viewerSettings.numModelChanges++; +} + +void StudioModel::SetTopColor( int color ) +{ + if( g_viewerSettings.topcolor != color ) + remap_textures = true; + g_viewerSettings.topcolor = color; +} + +void StudioModel::SetBottomColor( int color ) +{ + if( g_viewerSettings.bottomcolor != color ) + remap_textures = true; + g_viewerSettings.bottomcolor = color; +} + +bool StudioModel::SetEditType( int type ) +{ + if( type < 0 || type >= m_numeditfields ) + { + m_pedit = NULL; + return false; + } + + m_pedit = &m_editfields[type]; + + // is allowed to edit size + if( m_pedit->type == TYPE_BBOX || m_pedit->type == TYPE_CBOX || m_pedit->type == TYPE_HITBOX ) + return true; + return false; +} + +bool StudioModel::SetEditMode( int mode ) +{ + char str[256]; + + if( mode == EDIT_MODEL && g_viewerSettings.editMode == EDIT_SOURCE && g_viewerSettings.numSourceChanges > 0 ) + { + Q_strcpy( str, "we have some virtual changes for QC-code.\nApply them to real model or all the changes will be lost?" ); + int ret = mxMessageBox( g_GlWindow, str, g_appTitle, MX_MB_YESNOCANCEL | MX_MB_QUESTION ); + + if( ret == 2 ) return false; // cancelled + + if( ret == 0 ) UpdateEditFields( true ); + else UpdateEditFields( false ); + } + + g_viewerSettings.editMode = mode; + return true; +} + +void StudioModel::ReadEditField( studiohdr_t *phdr, edit_field_t *ed ) +{ + if( !ed || !phdr ) return; + + // get initial values + switch( ed->type ) + { + case TYPE_ORIGIN: + ed->origin = g_vecZero; // !!! + break; + case TYPE_BBOX: + ed->mins = phdr->min; + ed->maxs = phdr->max; + break; + case TYPE_CBOX: + ed->mins = phdr->bbmin; + ed->maxs = phdr->bbmax; + break; + case TYPE_EYEPOSITION: + ed->origin = phdr->eyeposition; + break; + case TYPE_ATTACHMENT: + { + mstudioattachment_t *pattachment = (mstudioattachment_t *) ((byte *)phdr + phdr->attachmentindex) + ed->id; + ed->origin = pattachment->org; + ed->bone = pattachment->bone; + } + break; + case TYPE_HITBOX: + { + mstudiobbox_t *phitbox = (mstudiobbox_t *) ((byte *) m_pstudiohdr + m_pstudiohdr->hitboxindex) + ed->id; + ed->hitgroup = phitbox->group; + ed->mins = phitbox->bbmin; + ed->maxs = phitbox->bbmax; + ed->bone = phitbox->bone; + } + break; + } +} + +void StudioModel::WriteEditField( studiohdr_t *phdr, edit_field_t *ed ) +{ + if( !ed || !phdr ) return; + + // get initial values + switch( ed->type ) + { + case TYPE_ORIGIN: + { + mstudiobone_t *pbone = (mstudiobone_t *)((byte *)m_pstudiohdr + m_pstudiohdr->boneindex); + + for( int i = 0; i < m_pstudiohdr->numbones; i++, pbone++ ) + { + // studioformat is allow multiple root bones... + if( pbone->parent != -1 ) continue; + + pbone->value[0] += ed->origin.x; + pbone->value[1] += ed->origin.y; + pbone->value[2] += ed->origin.z; + } + ed->origin = g_vecZero; + } + break; + case TYPE_BBOX: + phdr->min = ed->mins; + phdr->max = ed->maxs; + break; + case TYPE_CBOX: + phdr->bbmin = ed->mins; + phdr->bbmax = ed->maxs; + break; + case TYPE_EYEPOSITION: + phdr->eyeposition = ed->origin; + break; + case TYPE_ATTACHMENT: + { + mstudioattachment_t *pattachment = (mstudioattachment_t *) ((byte *)phdr + phdr->attachmentindex) + ed->id; + pattachment->org = ed->origin; + pattachment->bone = ed->bone; + } + break; + case TYPE_HITBOX: + { + mstudiobbox_t *phitbox = (mstudiobbox_t *) ((byte *) m_pstudiohdr + m_pstudiohdr->hitboxindex) + ed->id; + phitbox->group = ed->hitgroup; + phitbox->bbmin = ed->mins; + phitbox->bbmax = ed->maxs; + phitbox->bone = ed->bone; + } + break; + default: return; + } + + g_viewerSettings.numModelChanges++; +} + +void StudioModel::UpdateEditFields( bool write_to_model ) +{ + studiohdr_t *phdr = g_studioModel.getStudioHeader (); + + for( int i = 0; i < m_numeditfields; i++ ) + { + edit_field_t *ed = &m_editfields[i]; + + if( write_to_model ) + WriteEditField( phdr, ed ); + else ReadEditField( phdr, ed ); + } + + if( write_to_model && phdr ) + g_viewerSettings.numSourceChanges = 0; +} + +bool StudioModel::AddEditField( int type, int id ) +{ + if( m_numeditfields >= MAX_EDITFIELDS ) + { + mxMessageBox( g_GlWindow, "Edit fields limit exceeded.", g_appTitle, MX_MB_OK | MX_MB_ERROR ); + return false; + } + + studiohdr_t *phdr = g_studioModel.getStudioHeader (); + + if( phdr ) + { + edit_field_t *ed = &m_editfields[m_numeditfields++]; + + // initialize fields + ed->origin = g_vecZero; + ed->mins = g_vecZero; + ed->maxs = g_vecZero; + ed->hitgroup = 0; + ed->type = type; + ed->bone = -1; + ed->id = id; + + // read initial values from studiomodel + ReadEditField( phdr, ed ); + + return true; + } + + return false; +} + +const char *StudioModel::getQCcode( void ) +{ + studiohdr_t *phdr = g_studioModel.getStudioHeader (); + static char str[256]; + + if( !m_pedit ) return ""; + + edit_field_t *ed = m_pedit; + + Q_strncpy( str, "not available", sizeof( str )); + if( !phdr ) return str; + + // get initial values + switch( ed->type ) + { + case TYPE_ORIGIN: + if( g_viewerSettings.editMode == EDIT_SOURCE ) + Q_snprintf( str, sizeof( str ), "$origin %g %g %g", ed->origin.y, ed->origin.x, ed->origin.z ); + break; + case TYPE_BBOX: + Q_snprintf( str, sizeof( str ), "$bbox %g %g %g %g %g %g", ed->mins.x, ed->mins.y, ed->mins.z, ed->maxs.x, ed->maxs.y, ed->maxs.z ); + break; + case TYPE_CBOX: + Q_snprintf( str, sizeof( str ), "$cbox %g %g %g %g %g %g", ed->mins.x, ed->mins.y, ed->mins.z, ed->maxs.x, ed->maxs.y, ed->maxs.z ); + break; + case TYPE_EYEPOSITION: + Q_snprintf( str, sizeof( str ), "$eyeposition %g %g %g", ed->origin.x, ed->origin.y, ed->origin.z ); + break; + case TYPE_ATTACHMENT: + { + mstudiobone_t *pbone = (mstudiobone_t *)((byte *)phdr + phdr->boneindex) + ed->bone; + mstudioattachment_t *pattachment = (mstudioattachment_t *)((byte *)phdr + phdr->attachmentindex) + ed->id; + Q_snprintf( str, sizeof( str ), "$attachment \"%s\" \"%s\" %g %g %g", pattachment->name, pbone->name, ed->origin.x, ed->origin.y, ed->origin.z ); + } + break; + case TYPE_HITBOX: + { + mstudiobone_t *pbone = (mstudiobone_t *)((byte *)m_pstudiohdr + m_pstudiohdr->boneindex) + ed->bone; + Q_snprintf( str, sizeof( str ), "$hbox %i \"%s\" %g %g %g %g %g %g", ed->hitgroup, pbone->name, ed->mins.x, ed->mins.y, ed->mins.z, ed->maxs.x, ed->maxs.y, ed->maxs.z ); + } + break; + } + + return str; +} + +void StudioModel::updateModel( void ) +{ + if( !update_model ) return; + WriteEditField( m_pstudiohdr, m_pedit ); + update_model = false; +} + +void StudioModel::editPosition( float step, int type ) +{ + if( !m_pedit ) return; + + if( g_viewerSettings.editSize ) + { + switch( type ) + { + case IDC_MOVE_PX: + m_pedit->mins[0] -= step; + m_pedit->maxs[0] += step; + break; + case IDC_MOVE_NX: + m_pedit->mins[0] += step; + m_pedit->maxs[0] -= step; + break; + case IDC_MOVE_PY: + m_pedit->mins[1] -= step; + m_pedit->maxs[1] += step; + break; + case IDC_MOVE_NY: + m_pedit->mins[1] += step; + m_pedit->maxs[1] -= step; + break; + case IDC_MOVE_PZ: + m_pedit->mins[2] -= step; + m_pedit->maxs[2] += step; + break; + case IDC_MOVE_NZ: + m_pedit->mins[2] += step; + m_pedit->maxs[2] -= step; + break; + } + } + else + { + if( m_pedit->type == TYPE_BBOX || m_pedit->type == TYPE_CBOX || m_pedit->type == TYPE_HITBOX ) + { + switch( type ) + { + case IDC_MOVE_PX: + m_pedit->mins[0] += step; + m_pedit->maxs[0] += step; + break; + case IDC_MOVE_NX: + m_pedit->mins[0] -= step; + m_pedit->maxs[0] -= step; + break; + case IDC_MOVE_PY: + m_pedit->mins[1] += step; + m_pedit->maxs[1] += step; + break; + case IDC_MOVE_NY: + m_pedit->mins[1] -= step; + m_pedit->maxs[1] -= step; + break; + case IDC_MOVE_PZ: + m_pedit->mins[2] += step; + m_pedit->maxs[2] += step; + break; + case IDC_MOVE_NZ: + m_pedit->mins[2] -= step; + m_pedit->maxs[2] -= step; + break; + } + } + else + { + switch( type ) + { + case IDC_MOVE_PX: m_pedit->origin[0] += step; break; + case IDC_MOVE_NX: m_pedit->origin[0] -= step; break; + case IDC_MOVE_PY: m_pedit->origin[1] += step; break; + case IDC_MOVE_NY: m_pedit->origin[1] -= step; break; + case IDC_MOVE_PZ: m_pedit->origin[2] += step; break; + case IDC_MOVE_NZ: m_pedit->origin[2] -= step; break; + } + } + } + + if( g_viewerSettings.editMode == EDIT_MODEL ) + update_model = true; + else g_viewerSettings.numSourceChanges++; +} \ No newline at end of file diff --git a/utils/hlsv/FileAssociation.cpp b/utils/hlsv/FileAssociation.cpp new file mode 100644 index 0000000..3a54c54 --- /dev/null +++ b/utils/hlsv/FileAssociation.cpp @@ -0,0 +1,277 @@ +// +// MD2 Viewer (c) 1999 by Mete Ciragan +// +// file: FileAssociation.cpp +// last modified: Apr 29 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +// version: 1.4 +// +// email: mete@swissquake.ch +// web: http://www.swissquake.ch/chumbalum-soft/ +// +#include "FileAssociation.h" +#include +#include +#include +#include + + + +FileAssociation *g_FileAssociation = 0; + + + +FileAssociation::FileAssociation () +: mxWindow (0, 100, 100, 400, 210, "File Associations", mxWindow::Dialog) +{ + cExtension = new mxChoice (this, 5, 5, 220, 22, IDC_EXTENSION); + + //new mxButton (this, 230, 5, 75, 22, "Add", IDC_ADD); + //new mxButton (this, 310, 5, 75, 22, "Remove", IDC_REMOVE); + + new mxGroupBox (this, 5, 30, 380, 115, "Assocations"); + rbAction[0] = new mxRadioButton (this, 10, 50, 120, 22, "program", IDC_ACTION1, true); + rbAction[1] = new mxRadioButton (this, 10, 72, 120, 22, "associated program", IDC_ACTION2); + rbAction[2] = new mxRadioButton (this, 10, 94, 120, 22, "MD2 Viewer default", IDC_ACTION3); + rbAction[3] = new mxRadioButton (this, 10, 116, 120, 22, "none", IDC_ACTION4); + leProgram = new mxLineEdit (this, 130, 50, 220, 22, "", IDC_PROGRAM); + leProgram->setEnabled (false); + bChooseProgram = new mxButton (this, 352, 50, 22, 22, ">>", IDC_CHOOSEPROGRAM); + bChooseProgram->setEnabled (false); + + rbAction[0]->setChecked (false); + rbAction[1]->setChecked (true); + + new mxButton (this, 110, 155, 75, 22, "Ok", IDC_OK); + new mxButton (this, 215, 155, 75, 22, "Cancel", IDC_CANCEL); + + initAssociations (); +} + + + +FileAssociation::~FileAssociation () +{ +} + + + +int +FileAssociation::handleEvent (mxEvent *event) +{ + if (event->event != mxEvent::Action) + return 0; + + switch (event->action) + { + case IDC_EXTENSION: + { + int index = cExtension->getSelectedIndex (); + if (index >= 0) + setAssociation (index); + } + break; + + case IDC_ACTION1: + case IDC_ACTION2: + case IDC_ACTION3: + case IDC_ACTION4: + { + leProgram->setEnabled (rbAction[0]->isChecked ()); + bChooseProgram->setEnabled (rbAction[0]->isChecked ()); + + int index = cExtension->getSelectedIndex (); + if (index >= 0) + d_associations[index].association = event->action - IDC_ACTION1; + + } + break; + + case IDC_PROGRAM: + { + int index = cExtension->getSelectedIndex (); + if (index >= 0) + strcpy (d_associations[index].program, leProgram->getLabel ()); + } + break; + + case IDC_CHOOSEPROGRAM: + { + const char *ptr = mxGetOpenFileName (this, 0, "*.exe"); + if (ptr) + { + leProgram->setLabel (ptr); + + int index = cExtension->getSelectedIndex (); + if (index >= 0) + strcpy (d_associations[index].program, leProgram->getLabel ()); + } + } + break; + + case IDC_OK: + saveAssociations (); + + case IDC_CANCEL: + setVisible (false); + break; + } + + return 1; +} + + + +void +FileAssociation::initAssociations () +{ + int i; + + cExtension->removeAll (); + + for (i = 0; i < 16; i++) + d_associations[i].association = -1; + + FILE *file = fopen ("hlmv.fa", "rt"); + if (!file) + return; + + i = 0; + char line[256]; + while (i < 16 && fgets (line, 256, file)) + { + int j = 0; + while (line[++j] != '\"'); + line[j] = '\0'; + strcpy (d_associations[i].extension, &line[1]); + + while (line[++j] != '\"'); + int k = j + 1; + while (line[++j] != '\"'); + line[j] = '\0'; + strcpy (d_associations[i].program, &line[k]); + + d_associations[i].association = atoi (&line[++j]); + + cExtension->add (d_associations[i].extension); + ++i; + } + + fclose (file); + + setAssociation (0); +} + + + +void +FileAssociation::setAssociation (int index) +{ + cExtension->select (index); + leProgram->setLabel (d_associations[index].program); + + for (int i = 0; i < 4; i++) + rbAction[i]->setChecked (i == d_associations[index].association); + + leProgram->setEnabled (d_associations[index].association == 0); + bChooseProgram->setEnabled (d_associations[index].association == 0); + + // TODO: check for valid associtaion +#ifdef WIN32__ + char path[256]; + + strcpy (path, mx_gettemppath ()); + strcat (path, "/hlmvtemp."); + strcat (path, d_associations[index].extension); + + FILE *file = fopen (path, "wb"); + if (file) + fclose (file); + + int val = (int) ShellExecute ((HWND) getHandle (), "open", path, 0, 0, SW_HIDE); + char str[32]; + sprintf (str, "%d", val); + setLabel (str); + rbAction[1]->setEnabled (val != 31); +/* + WORD dw = 0; + HICON hIcon = ExtractAssociatedIcon ((HINSTANCE) GetWindowLong ((HWND) getHandle (), GWL_HINSTANCE), path, &dw); + SendMessage ((HWND) getHandle (), WM_SETICON, (WPARAM) ICON_SMALL, (LPARAM) hIcon); + char str[32]; + sprintf (str, "%d", (int) hIcon); + setLabel (str); +*/ + DeleteFile (path); + + //DestroyIcon (hIcon); +#endif + + rbAction[2]->setEnabled ( + !mx_strcasecmp (d_associations[index].extension, "md2") || + !mx_strcasecmp (d_associations[index].extension, "tga") || + !mx_strcasecmp (d_associations[index].extension, "pcx") || + !mx_strcasecmp (d_associations[index].extension, "wav") + ); +} + + + +void +FileAssociation::saveAssociations () +{ + char path[256]; + + strcpy (path, mx::getApplicationPath ()); + strcat (path, "/hlmv.fa"); + + FILE *file = fopen (path, "wt"); + if (!file) + return; + + for (int i = 0; i < 16; i++) + { + if (d_associations[i].association == -1) + break; + + fprintf (file, "\"%s\" \"%s\" %d\n", + d_associations[i].extension, + d_associations[i].program, + d_associations[i].association); + } + + fclose (file); +} + + + +int +FileAssociation::getMode (char *extension) +{ + for (int i = 0; i < 16; i++) + { + if (!strcmp (d_associations[i].extension, _strlwr (extension))) + return d_associations[i].association; + } + + return -1; +} + + + +char * +FileAssociation::getProgram (char *extension) +{ + for (int i = 0; i < 16; i++) + { + if (!strcmp (d_associations[i].extension, _strlwr (extension))) + return d_associations[i].program; + } + + return 0; +} \ No newline at end of file diff --git a/utils/hlsv/FileAssociation.h b/utils/hlsv/FileAssociation.h new file mode 100644 index 0000000..7d85e35 --- /dev/null +++ b/utils/hlsv/FileAssociation.h @@ -0,0 +1,91 @@ +// +// MD2 Viewer (c) 1999 by Mete Ciragan +// +// file: FileAssociation.h +// last modified: Apr 28 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +// version: 1.4 +// +// email: mete@swissquake.ch +// web: http://www.swissquake.ch/chumbalum-soft/ +// +#ifndef INCLUDED_FILEASSOCIATION +#define INCLUDED_FILEASSOCIATION + + + +#ifndef INCLUDED_MXWINDOW +#include +#endif + + + +#define IDC_EXTENSION 1001 +#define IDC_ADD 1002 +#define IDC_REMOVE 1003 +#define IDC_ACTION1 1004 +#define IDC_ACTION2 1005 +#define IDC_ACTION3 1006 +#define IDC_ACTION4 1007 +#define IDC_PROGRAM 1008 +#define IDC_CHOOSEPROGRAM 1009 +#define IDC_OK 1010 +#define IDC_CANCEL 1011 + + + +typedef struct +{ + char extension[16]; + char program[256]; + int association; +} association_t; + + + + +class mxChoice; +class mxRadioButton; +class mxLineEdit; +class mxButton; + + + +class FileAssociation : public mxWindow +{ + mxChoice *cExtension; + mxRadioButton *rbAction[4]; + mxLineEdit *leProgram; + mxButton *bChooseProgram; + association_t d_associations[16]; + + void initAssociations (); + void saveAssociations (); + +public: + // CREATORS + FileAssociation (); + virtual ~FileAssociation (); + + // MANIPULATORS + int handleEvent (mxEvent *event); + void setAssociation (int index); + + // ACCESSORS + int getMode (char *extension); + char *getProgram (char *extension); +}; + + + +extern FileAssociation *g_FileAssociation; + + + +#endif // INCLUDED_FILEASSOCIATION \ No newline at end of file diff --git a/utils/hlsv/GlWindow.cpp b/utils/hlsv/GlWindow.cpp new file mode 100644 index 0000000..adeabe7 --- /dev/null +++ b/utils/hlsv/GlWindow.cpp @@ -0,0 +1,535 @@ +// +// MD2 Viewer (c) 1999 by Mete Ciragan +// +// file: GlWindow.cpp +// last modified: Apr 29 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +// version: 1.4 +// +// email: mete@swissquake.ch +// web: http://www.swissquake.ch/chumbalum-soft/ +// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ViewerSettings.h" +#include "GlWindow.h" +#include "SpriteModel.h" + +extern bool g_bStopPlaying; +extern bool bUseWeaponOrigin; +extern bool bUseWeaponLeftHand; + +GlWindow *g_GlWindow = 0; + +GlWindow::GlWindow( mxWindow *parent, int x, int y, int w, int h, const char *label, int style ) : mxGlWindow( parent, x, y, w, h, label, style ) +{ + d_textureNames[0] = 0; + d_textureNames[1] = 0; + d_textureNames[2] = 0; + + setFrameInfo (0, 0); + setRenderMode (SPR_NORMAL); + setOrientType (SPR_FWD_PARALLEL); + d_pol = 0.0f; + + glDepthFunc( GL_LEQUAL ); + glCullFace (GL_FRONT); + + mx::setIdleWindow (this); +} + +GlWindow::~GlWindow () +{ + mx::setIdleWindow (0); + loadTexture (0, TEXTURE_BACKGROUND); + loadTexture (0, TEXTURE_GROUND); + loadSprite (0); +} + +int GlWindow::handleEvent (mxEvent *event) +{ + static float oldrx, oldry, oldtz, oldtx, oldty; + static int oldx, oldy; + static double lastupdate; + + switch (event->event) + { + case mxEvent::MouseUp: + { + g_viewerSettings.pause = false; + } + break; + case mxEvent::MouseDown: + oldrx = g_viewerSettings.rot[0]; + oldry = g_viewerSettings.rot[1]; + oldtx = g_viewerSettings.trans[0]; + oldty = g_viewerSettings.trans[1]; + oldtz = g_viewerSettings.trans[2]; + oldx = event->x; + oldy = event->y; + + // HACKHACK: reset focus to main window to catch hot-keys again + if( g_SPRViewer ) SetFocus( (HWND) g_SPRViewer->getHandle ()); + g_viewerSettings.pause = true; + + break; + + case mxEvent::MouseDrag: + if( event->buttons & mxEvent::MouseLeftButton ) + { + if( event->modifiers & mxEvent::KeyShift ) + { + g_viewerSettings.trans[0] = oldtx - (float)(event->x - oldx) * g_viewerSettings.movementScale; + g_viewerSettings.trans[1] = oldty + (float)(event->y - oldy) * g_viewerSettings.movementScale; + } + else + { + g_viewerSettings.rot[0] = oldrx + (float)(event->y - oldy); + g_viewerSettings.rot[1] = oldry + (float)(event->x - oldx); + } + } + else if( event->buttons & mxEvent::MouseRightButton ) + { + g_viewerSettings.trans[2] = oldtz + (float)(event->y - oldy) * g_viewerSettings.movementScale; + } + redraw (); + break; + + case mxEvent::Idle: + { + static double prev; + double curr = (double) mx::getTickCount () / 1000.0; + double dt = (curr - prev); +#if 1 + // clamp to 100fps + if( dt >= 0.0 && dt < 0.01 ) + { + Sleep( max( 10 - dt * 1000.0, 0 ) ); + return 1; + } +#endif + g_spriteModel.updateTimings( curr, dt ); + + if( !g_bStopPlaying && !g_viewerSettings.pause && prev != 0.0 ) + { + d_pol += (dt / 0.1) * g_viewerSettings.speedScale; + + if( d_pol >= 1.0f ) + { + if( d_startFrame == d_endFrame ) + g_spriteModel.setFrame( d_startFrame ); + else g_spriteModel.setFrame( d_currFrame++ ); + d_pol = 0.0f; + + if( d_currFrame > d_endFrame ) + d_currFrame = d_startFrame; + } + } + + if( !g_viewerSettings.pause ) + redraw (); + + prev = curr; + } + break; + + case mxEvent::KeyDown: + switch (event->key) + { + case 27: + if( !getParent( )) // fullscreen mode ? + mx::quit(); + break; + } + } + + return 1; +} + +void GlWindow :: setupRenderMode( void ) +{ + glDisable( GL_MULTISAMPLE ); + glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + glShadeModel( GL_SMOOTH ); + glEnable( GL_DEPTH_TEST ); + glEnable( GL_TEXTURE_2D ); + glEnable( GL_CULL_FACE ); + glDisable( GL_BLEND ); + glDisable( GL_ALPHA_TEST ); + glDepthMask( GL_TRUE ); + + switch( g_viewerSettings.renderMode ) + { + case SPR_NORMAL: + break; + case SPR_ADDITIVE: + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE ); + glDepthMask( GL_FALSE ); + break; + case SPR_INDEXALPHA: + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glDepthMask( GL_FALSE ); + break; + case SPR_ALPHTEST: +#if 0 + glEnable( GL_ALPHA_TEST ); + glAlphaFunc( GL_GREATER, 0.25f ); +#else + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glDepthMask( GL_FALSE ); +#endif + break; + } +} + +void GlWindow :: drawFloor( int texture ) +{ + float scale = 5.0f; + float dist = -100.0f; + glEnable( GL_MULTISAMPLE ); + + if( texture ) + { + static Vector tMap( 0, 0, 0 ); + static Vector dxMap( 1, 0, 0 ); + static Vector dyMap( 0, 1, 0 ); + + vec3_t deltaPos; + + g_spriteModel.GetMovement( deltaPos ); + + float dpdd = scale / dist; + + tMap[0] = tMap[0] + dxMap[0] * deltaPos[0] * dpdd + dxMap[1] * deltaPos[1] * dpdd; + tMap[1] = tMap[1] + dyMap[0] * deltaPos[0] * dpdd + dyMap[1] * deltaPos[1] * dpdd; + + while (tMap[0] < 0.0) tMap[0] += 1.0f; + while (tMap[0] > 1.0) tMap[0] += -1.0f; + while (tMap[1] < 0.0) tMap[1] += 1.0f; + while (tMap[1] > 1.0) tMap[1] += -1.0f; + + glBegin( GL_QUADS ); + glTexCoord2f( tMap[0] - (dxMap[0] - dyMap[0]) * scale, tMap[1] - (-dxMap[1] + dyMap[1]) * scale ); + glVertex3f( -dist, -dist, 0 ); + + glTexCoord2f( tMap[0] + (dxMap[0] + dyMap[0]) * scale, tMap[1] + (-dxMap[1] - dyMap[1]) * scale ); + glVertex3f( dist, -dist, 0 ); + + glTexCoord2f( tMap[0] + (dxMap[0] - dyMap[0]) * scale, tMap[1] + (-dxMap[1] + dyMap[1]) * scale ); + glVertex3f( dist, dist, 0 ); + + glTexCoord2f( tMap[0] + (-dxMap[0] - dyMap[0]) * scale, tMap[1] + (dxMap[1] + dyMap[1]) * scale ); + glVertex3f( -dist, dist, 0 ); + glEnd(); + } + else + { + glBegin( GL_QUADS ); + glTexCoord2f( 0.0f, 1.0f ); + glVertex3f( -dist, -dist, 0 ); + + glTexCoord2f( 1.0f, 1.0f ); + glVertex3f( dist, -dist, 0 ); + + glTexCoord2f( 1.0f, 0.0f ); + glVertex3f( dist, dist, 0 ); + + glTexCoord2f( 0.0f, 0.0f ); + glVertex3f( -dist, dist, 0 ); + glEnd(); + } + + glDisable( GL_MULTISAMPLE ); +} + +void GlWindow::draw () +{ + glClearColor( g_viewerSettings.bgColor[0], g_viewerSettings.bgColor[1], g_viewerSettings.bgColor[2], 0.0f ); + + glClear( GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT ); + + glViewport( 0, 0, w2(), h2() ); + glDisable( GL_MULTISAMPLE ); + + // + // draw background + // + if( g_viewerSettings.showBackground && d_textureNames[TEXTURE_BACKGROUND] ) + { + glMatrixMode( GL_PROJECTION ); + glLoadIdentity(); + glOrtho( 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, -1.0f ); + + glMatrixMode( GL_MODELVIEW ); + glPushMatrix (); + glLoadIdentity (); + + glDisable( GL_CULL_FACE ); + glEnable( GL_TEXTURE_2D ); + + glColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); + glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + + glBindTexture( GL_TEXTURE_2D, d_textureNames[TEXTURE_BACKGROUND] ); + + glBegin( GL_TRIANGLE_STRIP ); + glTexCoord2f( 0, 0 ); + glVertex2f( 0, 0 ); + glTexCoord2f( 1, 0 ); + glVertex2f( 1, 0 ); + glTexCoord2f( 0, 1 ); + glVertex2f( 0, 1 ); + glTexCoord2f( 1, 1 ); + glVertex2f( 1, 1 ); + glEnd(); + + glClear( GL_DEPTH_BUFFER_BIT ); + glBindTexture( GL_TEXTURE_2D, 0 ); + + glPopMatrix (); + } + + glMatrixMode (GL_PROJECTION); + glLoadIdentity (); + gluPerspective (65.0f, (GLfloat) w () / (GLfloat) h (), 0.1f, 131072.0f ); + + glMatrixMode (GL_MODELVIEW); + glPushMatrix (); + glLoadIdentity (); + + if( bUseWeaponOrigin ) + { + glRotatef( -90, 1, 0, 0 ); // put Z going up + glRotatef( 90, 0, 0, 1 ); // put Z going up + glTranslatef( 0.0f, 0.0f, 1.0f ); // shift back like in HL + } + else + { + glTranslatef (-g_viewerSettings.trans[0], -g_viewerSettings.trans[1], -g_viewerSettings.trans[2]); + + glRotatef( g_viewerSettings.rot[0], 1, 0, 0 ); + glRotatef( g_viewerSettings.rot[1], 0, 0, 1 ); + } + + setupRenderMode(); + + glCullFace( GL_FRONT ); + + g_spriteModel.DrawSprite(); + + // + // draw ground + // + if( g_viewerSettings.showGround && !bUseWeaponOrigin ) + { + glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + glEnable( GL_DEPTH_TEST ); + glEnable( GL_CULL_FACE ); + glDisable( GL_CULL_FACE ); + + glEnable( GL_BLEND ); + + if( !d_textureNames[TEXTURE_GROUND] ) + { + glDisable( GL_TEXTURE_2D ); + glColor4f( g_viewerSettings.gColor[0], g_viewerSettings.gColor[1], g_viewerSettings.gColor[2], 0.7f ); + glBindTexture( GL_TEXTURE_2D, 0 ); + } + else + { + glEnable( GL_TEXTURE_2D ); + glColor4f( 1.0f, 1.0f, 1.0f, 0.6f ); + glBindTexture( GL_TEXTURE_2D, d_textureNames[TEXTURE_GROUND] ); + } + + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + drawFloor( d_textureNames[TEXTURE_GROUND] ); + glDisable( GL_BLEND ); + glEnable( GL_CULL_FACE ); + } + + glPopMatrix (); +} + +int GlWindow :: loadSprite( const char *filename, bool centering ) +{ + g_spriteModel.FreeSprite (); + if( g_spriteModel.LoadSprite( filename )) + { + char str[256], basename[64]; + + if( centering ) + g_spriteModel.centerView (false); + g_viewerSettings.speedScale = 1.0f; + mx_setcwd( mx_getpath( filename )); + + COM_FileBase( filename, basename ); + Q_snprintf( str, sizeof( str ), "%s - %s.spr", g_appTitle, basename ); + g_SPRViewer->setLabel( str ); + + ListDirectory(); + return 1; + } + return 0; +} + +int GlWindow :: loadTextureImage( mxImage *image, int name ) +{ + if( image ) + { + d_textureNames[name] = name; + glPixelStorei( GL_UNPACK_ALIGNMENT, 1 ); + glPixelStorei( GL_PACK_ALIGNMENT, 1 ); + + if( image->bpp == 8 ) + { + g_spriteModel.UploadTexture( (byte *)image->data, image->width, image->height, (byte *)image->palette, name ); + } + else if( image->bpp == 24 ) + { + glBindTexture( GL_TEXTURE_2D, d_textureNames[name] ); + glHint( GL_GENERATE_MIPMAP_HINT_SGIS, GL_NICEST ); + glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE ); + glTexImage2D( GL_TEXTURE_2D, 0, 3, image->width, image->height, 0, GL_RGB, GL_UNSIGNED_BYTE, image->data ); + glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + + const char *extensions = (const char *)glGetString( GL_EXTENSIONS ); + + // check for anisotropy support + if( Q_strstr( extensions, "GL_EXT_texture_filter_anisotropic" )) + { + float anisotropy = 1.0f; + glGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisotropy ); + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropy ); + } + } + else if( image->bpp == 32 ) + { + glBindTexture( GL_TEXTURE_2D, d_textureNames[name] ); + glHint( GL_GENERATE_MIPMAP_HINT_SGIS, GL_NICEST ); + glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE ); + glTexImage2D( GL_TEXTURE_2D, 0, 4, image->width, image->height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image->data ); + glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + + const char *extensions = (const char *)glGetString( GL_EXTENSIONS ); + + // check for anisotropy support + if( Q_strstr( extensions, "GL_EXT_texture_filter_anisotropic" )) + { + float anisotropy = 1.0f; + glGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisotropy ); + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropy ); + } + } + + delete image; + + return name; + } + + return TEXTURE_UNUSED; +} + +int GlWindow :: loadTexture( const char *filename, int name ) +{ + if( !filename || !Q_strlen( filename )) + { + if( d_textureNames[name] ) + { + glDeleteTextures( 1, (const GLuint *)&d_textureNames[name] ); + d_textureNames[name] = TEXTURE_UNUSED; + + if( name == TEXTURE_BACKGROUND ) + strcpy( g_viewerSettings.backgroundTexFile, "" ); + else if( name == TEXTURE_GROUND ) + strcpy( g_viewerSettings.groundTexFile, "" ); + } + return TEXTURE_UNUSED; + } + + mxImage *image = NULL; + char ext[16]; + + strcpy( ext, mx_getextension( filename )); + + if( !mx_strcasecmp( ext, ".tga" )) + image = mxTgaRead( filename ); + else if( !mx_strcasecmp( ext, ".pcx" )) + image = mxPcxRead( filename ); + else if( !mx_strcasecmp( ext, ".bmp" )) + image = mxBmpRead( filename ); + + if( image ) + { + if( name == TEXTURE_BACKGROUND ) + strcpy( g_viewerSettings.backgroundTexFile, filename ); + else if( name == TEXTURE_GROUND ) + strcpy( g_viewerSettings.groundTexFile, filename ); + } + + return loadTextureImage( image, name ); +} + +void GlWindow :: setRenderMode( int mode ) +{ + g_viewerSettings.renderMode = mode; +} + +void GlWindow :: setOrientType( int mode ) +{ + g_viewerSettings.orientType = mode; +} + +void GlWindow::setFrameInfo (int startFrame, int endFrame) +{ + msprite_t *phdr = g_spriteModel.getSpriteHeader(); + + if (phdr) + { + d_startFrame = startFrame; + d_endFrame = endFrame; + + if (d_startFrame >= phdr->numframes) + d_startFrame = phdr->numframes - 1; + else if (d_startFrame < 0) + d_startFrame = 0; + + if (d_endFrame >= phdr->numframes) + d_endFrame = phdr->numframes - 1; + else if (d_endFrame < 0) + d_endFrame = 0; + + d_currFrame = d_startFrame; + + if (d_currFrame >= phdr->numframes) + d_currFrame = phdr->numframes - 1; + } + else + { + d_startFrame = d_endFrame = d_currFrame = 0; + } + + d_pol = 0; +} \ No newline at end of file diff --git a/utils/hlsv/GlWindow.h b/utils/hlsv/GlWindow.h new file mode 100644 index 0000000..aa87034 --- /dev/null +++ b/utils/hlsv/GlWindow.h @@ -0,0 +1,88 @@ +// +// MDL Viewer (c) 1999 by Mete Ciragan +// +// file: GlWindow.h +// last modified: Apr 28 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +// version: 1.4 +// +// email: mete@swissquake.ch +// web: http://www.swissquake.ch/chumbalum-soft/ +// +#ifndef INCLUDED_GLWINDOW +#define INCLUDED_GLWINDOW + + + +#ifndef INCLUDED_MXGLWINDOW +#include +#endif + +#ifndef INCLUDED_SPRVIEWER +#include "sprviewer.h" +#endif + +#ifndef INCLUDED_VIEWERSETTINGS +#include "ViewerSettings.h" +#endif + +#include "matrix.h" + +enum // texture names +{ + TEXTURE_UNUSED = 0, + TEXTURE_GROUND, + TEXTURE_BACKGROUND, + TEXTURE_COUNT +}; + +#define GL_TEXTURE0_ARB 0x84C0 +#define GL_TEXTURE1_ARB 0x84C1 +#define GL_GENERATE_MIPMAP_SGIS 0x8191 +#define GL_GENERATE_MIPMAP_HINT_SGIS 0x8192 +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#define GL_MULTISAMPLE 0x809D + +class mxImage; + +class GlWindow : public mxGlWindow +{ + unsigned int d_textureNames[TEXTURE_COUNT]; // 0 = none, 1 = model, 2 = weapon, 3 = water, 4 = font + float d_pol; // interpolate value 0.0f - 1.0f + int d_currFrame, d_startFrame, d_endFrame; +public: + friend SPRViewer; + + // CREATORS + GlWindow (mxWindow *parent, int x, int y, int w, int h, const char *label, int style); + ~GlWindow (); + + // MANIPULATORS + virtual int handleEvent (mxEvent *event); + virtual void draw (); + + void drawFloor( int texture = 0 ); + int loadSprite (const char *filename, bool centerView = true); + int loadTexture( const char *filename, int name ); + int loadTextureImage( mxImage *image, int name ); + void setupRenderMode( void ); + void setRenderMode (int mode); + void setOrientType (int mode); + void setFrameInfo (int startFrame, int endFrame); + // ACCESSORS + int getRenderMode () const { return g_viewerSettings.renderMode; } + int getCurrFrame () const { return d_currFrame; } + int getStartFrame () const { return d_startFrame; } + int getEndFrame () const { return d_endFrame; } +}; + +extern GlWindow *g_GlWindow; + +#endif // INCLUDED_GLWINDOW diff --git a/utils/hlsv/SpriteModel.h b/utils/hlsv/SpriteModel.h new file mode 100644 index 0000000..6373bc8 --- /dev/null +++ b/utils/hlsv/SpriteModel.h @@ -0,0 +1,124 @@ +/*** +* +* Copyright (c) 1998, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#ifndef INCLUDED_SPRITEMODEL +#define INCLUDED_SPRITEMODEL + +#include "mathlib.h" + +/* +============================================================================== + +SPRITE MODELS + +Alias models are position independent, so the cache manager can move them. +============================================================================== +*/ + +#include "sprite.h" + +typedef struct mspriteframe_s +{ + int width; + int height; + float up, down, left, right; + int gl_texturenum; +} mspriteframe_t; + +typedef struct +{ + int numframes; + float *intervals; + mspriteframe_t *frames[1]; +} mspritegroup_t; + +typedef struct +{ + frametype_t type; + mspriteframe_t *frameptr; +} mspriteframedesc_t; + +typedef struct +{ + short type; + short texFormat; + int maxwidth; + int maxheight; + int numframes; + int radius; + int facecull; + int synctype; + mspriteframedesc_t frames[1]; +} msprite_t; + +class SpriteModel +{ +public: + msprite_t *getSpriteHeader () const { return m_pspritehdr; } + void updateTimings( float flTime, float flFrametime ) { m_flTime = flTime; m_flFrameTime = flFrametime; } + float getCurrentTime( void ) { return m_flTime; } + void UploadTexture( byte *data, int width, int height, byte *palette, int name, int size = 768, bool has_alpha = false ); + int GetNumFrames( void ) { return m_pspritehdr ? m_pspritehdr->numframes : 0; } + void FreeSprite( void ); + msprite_t *LoadSprite( const char *modelname ); + bool SaveSprite( const char *modelname ); + void DrawSprite( void ); + int setFrame( int newframe ); + + void ExtractBbox( Vector &mins, Vector &maxs ); + void GetMovement( Vector &delta ); + + void centerView( bool reset ); + void updateSprite( void ); +private: + // global settings + float m_flTime; + float m_flFrameTime; + + // entity settings + byte m_prevblending[2]; + float m_sequencetime; + float m_yaw; + + float m_flLerpfrac; // lerp frames + float m_dt; + int m_frame; + + matrix4x4 m_protationmatrix; + matrix4x4 m_viewVectors; + + // internal data + msprite_t *m_pspritehdr; // pointer to sourcemodel + int m_iFileSize; // real size of model footprint + + byte m_palette[1024]; + vec3_t m_spritemins; + vec3_t m_spritemaxs; + int m_loadframe; + + void SetupTransform( void ); + bool AllowLerping( void ); + + // loading stuff + dframetype_t *LoadSpriteFrame( void *pin, mspriteframe_t **ppframe ); + dframetype_t *LoadSpriteGroup( void *pin, mspriteframe_t **ppframe ); + + // frame compute + mspriteframe_t *GetSpriteFrame( int frame ); + float GetSpriteFrameInterpolant( int frame, mspriteframe_t **old, mspriteframe_t **cur ); + + // draw + void DrawQuad( mspriteframe_t *frame, const vec3_t &org, const vec3_t &v_right, const vec3_t &v_up, float scale ); +}; + +extern SpriteModel g_spriteModel; +extern byte palette_q1[]; + +#endif \ No newline at end of file diff --git a/utils/hlsv/ViewerSettings.cpp b/utils/hlsv/ViewerSettings.cpp new file mode 100644 index 0000000..1bec4de --- /dev/null +++ b/utils/hlsv/ViewerSettings.cpp @@ -0,0 +1,335 @@ +// +// Half-Life Model Viewer (c) 1999 by Mete Ciragan +// +// file: ViewerSettings.cpp +// last modified: May 29 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +// version: 1.2 +// +// email: mete@swissquake.ch +// web: http://www.swissquake.ch/chumbalum-soft/ +// +#include "ViewerSettings.h" +#include +#include +#include +#include +#include +#include "SpriteModel.h" +#include "stringlib.h" +#include + +ViewerSettings g_viewerSettings; + +void InitViewerSettings( void ) +{ + memset (&g_viewerSettings, 0, sizeof (ViewerSettings)); + + g_viewerSettings.renderMode = SPR_NORMAL; + g_viewerSettings.orientType = SPR_FWD_PARALLEL; + + g_viewerSettings.bgColor[0] = 0.5f; + g_viewerSettings.bgColor[1] = 0.5f; + g_viewerSettings.bgColor[2] = 0.5f; + + g_viewerSettings.gColor[0] = 0.85f; + g_viewerSettings.gColor[1] = 0.85f; + g_viewerSettings.gColor[2] = 0.69f; + + g_viewerSettings.lColor[0] = 1.0f; + g_viewerSettings.lColor[1] = 1.0f; + g_viewerSettings.lColor[2] = 1.0f; + + g_viewerSettings.speedScale = 1.0f; + g_viewerSettings.textureLimit = 256; + g_viewerSettings.showGround = 0; + g_viewerSettings.showMaximized = 0; + g_viewerSettings.sequence_autoplay = true; + + // init random generator + srand( (unsigned)time( NULL ) ); +} + +bool InitRegistry( void ) +{ + return mx::regCreateKey( HKEY_CURRENT_USER, "Software\\XashXT Group\\Half-Life SpriteViewer" ); +} + +bool SaveString( const char *pKey, char *pValue ) +{ + return mx::regSetValue( HKEY_CURRENT_USER, "Software\\XashXT Group\\Half-Life SpriteViewer", pKey, pValue ); +} + +bool LoadString( const char *pKey, char *pValue ) +{ + return mx::regGetValue( HKEY_CURRENT_USER, "Software\\XashXT Group\\Half-Life SpriteViewer", pKey, pValue ); +} + +void SaveVector4D( const char *pName, float *pValue ) +{ + char str[256]; + + mx_snprintf( str, sizeof( str ), "%g %g %g %g", pValue[0], pValue[1], pValue[2], pValue[3] ); + SaveString( pName, str ); +} + +void LoadVector4D( const char *pName, float *pValue ) +{ + char str[256]; + + if( LoadString( pName, str )) + { + sscanf( str, "%g %g %g %g", &pValue[0], &pValue[1], &pValue[2], &pValue[3] ); + } +} + +void SaveVector3D( const char *pName, float *pValue ) +{ + char str[256]; + + mx_snprintf( str, sizeof( str ), "%g %g %g", pValue[0], pValue[1], pValue[2] ); + SaveString( pName, str ); +} + +void LoadVector3D( const char *pName, float *pValue ) +{ + char str[256]; + + if( LoadString( pName, str )) + { + sscanf( str, "%g %g %g", &pValue[0], &pValue[1], &pValue[2] ); + } +} + +void SaveInt( const char *pName, int iValue ) +{ + char str[256]; + + mx_snprintf( str, sizeof( str ), "%d", iValue ); + SaveString( pName, str ); +} + +void LoadInt( const char *pName, int *iValue ) +{ + char str[256]; + + if( LoadString( pName, str )) + { + sscanf( str, "%d", iValue ); + } +} + +void SaveFloat( const char *pName, float fValue ) +{ + char str[256]; + + mx_snprintf( str, sizeof( str ), "%f", fValue ); + SaveString( pName, str ); +} + +void LoadFloat( const char *pName, float *fValue ) +{ + char str[256]; + + if( LoadString( pName, str )) + { + sscanf( str, "%f", fValue ); + } +} + +int LoadViewerSettings( void ) +{ + InitViewerSettings (); + + LoadVector4D( "Background Color", g_viewerSettings.bgColor ); + LoadVector4D( "Light Color", g_viewerSettings.lColor ); + LoadVector4D( "Ground Color", g_viewerSettings.gColor ); + LoadInt( "Sequence AutoPlay", &g_viewerSettings.sequence_autoplay ); + LoadInt( "Show Ground", &g_viewerSettings.showGround ); + LoadString( "Ground TexPath", g_viewerSettings.groundTexFile ); + LoadInt( "Show Maximized", &g_viewerSettings.showMaximized ); + + return 1; +} + +int SaveViewerSettings( void ) +{ + if( !InitRegistry( )) + return 0; + + SaveVector4D( "Background Color", g_viewerSettings.bgColor ); + SaveVector4D( "Light Color", g_viewerSettings.lColor ); + SaveVector4D( "Ground Color", g_viewerSettings.gColor ); + SaveInt( "Sequence AutoPlay", g_viewerSettings.sequence_autoplay ); + SaveInt( "Show Ground", g_viewerSettings.showGround ); + SaveString( "Ground TexPath", g_viewerSettings.groundTexFile ); + SaveInt( "Show Maximized", g_viewerSettings.showMaximized ); + + return 1; +} + +bool IsSpriteModel( const char *path ) +{ + byte buffer[256]; + FILE *fp; + + if( !path ) return false; + + // load the sprite + if(( fp = fopen( path, "rb" )) == NULL ) + return false; + + fread( buffer, sizeof( buffer ), 1, fp ); + fclose( fp ); + + if( ftell( fp ) < sizeof( 36 )) + return false; + + // skip invalid signature + if( Q_strncmp((const char *)buffer, "IDSP", 4 )) + return false; + + // skip unknown version + if( *(int *)&buffer[4] != 1 && *(int *)&buffer[4] != 2 ) + return false; + return true; +} + +static bool ValidateSprite( const char *path ) +{ + byte buffer[256]; + dsprite_t *phdr; + FILE *fp; + + if( !path ) return false; + + // load the sprite + if(( fp = fopen( path, "rb" )) == NULL ) + return false; + + fread( buffer, sizeof( buffer ), 1, fp ); + fclose( fp ); + + if( ftell( fp ) < sizeof( dsprite_t )) + return false; + + // skip invalid signature + if( Q_strncmp((const char *)buffer, "IDSP", 4 )) + return false; + + phdr = (dsprite_t *)buffer; + + // skip unknown version + if( phdr->version != SPRITE_VERSION_Q1 && phdr->version != SPRITE_VERSION_HL ) + return false; + + return true; +} + +static void AddPathToList( const char *path ) +{ + char spritePath[256]; + + if( g_viewerSettings.numSpritePathes >= 2048 ) + return; // too many strings + + Q_snprintf( spritePath, sizeof( spritePath ), "%s\\%s", g_viewerSettings.oldSpritePath, path ); + + if( !ValidateSprite( spritePath )) + return; + + int i = g_viewerSettings.numSpritePathes++; + + Q_strncpy( g_viewerSettings.spritePathList[i], spritePath, sizeof( g_viewerSettings.spritePathList[0] )); +} + +static void SortPathList( void ) +{ + char temp[256]; + int i, j; + + // this is a selection sort (finds the best entry for each slot) + for( i = 0; i < g_viewerSettings.numSpritePathes - 1; i++ ) + { + for( j = i + 1; j < g_viewerSettings.numSpritePathes; j++ ) + { + if( Q_strcmp( g_viewerSettings.spritePathList[i], g_viewerSettings.spritePathList[j] ) > 0 ) + { + Q_strncpy( temp, g_viewerSettings.spritePathList[i], sizeof( temp )); + Q_strncpy( g_viewerSettings.spritePathList[i], g_viewerSettings.spritePathList[j], sizeof( temp )); + Q_strncpy( g_viewerSettings.spritePathList[j], temp, sizeof( temp )); + } + } + } +} + +void ListDirectory( void ) +{ + char spritePath[256]; + struct _finddata_t n_file; + long hFile; + + COM_ExtractFilePath( g_viewerSettings.spritePath, spritePath ); + + if( !Q_stricmp( spritePath, g_viewerSettings.oldSpritePath )) + return; // not changed + + Q_strncpy( g_viewerSettings.oldSpritePath, spritePath, sizeof( g_viewerSettings.oldSpritePath )); + Q_strncat( spritePath, "\\*.spr", sizeof( spritePath )); + g_viewerSettings.numSpritePathes = 0; + + // ask for the directory listing handle + hFile = _findfirst( spritePath, &n_file ); + if( hFile == -1 ) return; // how this possible? + + // start a new chain with the the first name + AddPathToList( n_file.name ); + + // iterate through the directory + while( _findnext( hFile, &n_file ) == 0 ) + AddPathToList( n_file.name ); + _findclose( hFile ); + + SortPathList(); +} + +const char *LoadNextSprite( void ) +{ + int i; + + for( i = 0; i < g_viewerSettings.numSpritePathes; i++ ) + { + if( !Q_stricmp( g_viewerSettings.spritePathList[i], g_viewerSettings.spritePath )) + { + i++; + break; + } + } + + if( i == g_viewerSettings.numSpritePathes ) + i = 0; + return g_viewerSettings.spritePathList[i]; +} + +const char *LoadPrevSprite( void ) +{ + int i; + + for( i = 0; i < g_viewerSettings.numSpritePathes; i++ ) + { + if( !Q_stricmp( g_viewerSettings.spritePathList[i], g_viewerSettings.spritePath )) + { + i--; + break; + } + } + + if( i < 0 ) i = g_viewerSettings.numSpritePathes - 1; + return g_viewerSettings.spritePathList[i]; +} \ No newline at end of file diff --git a/utils/hlsv/ViewerSettings.h b/utils/hlsv/ViewerSettings.h new file mode 100644 index 0000000..7174246 --- /dev/null +++ b/utils/hlsv/ViewerSettings.h @@ -0,0 +1,88 @@ +// +// Half-Life Model Viewer (c) 1999 by Mete Ciragan +// +// file: ViewerSettings.h +// last modified: May 29 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +// version: 1.2 +// +// email: mete@swissquake.ch +// web: http://www.swissquake.ch/chumbalum-soft/ +// +#ifndef INCLUDED_VIEWERSETTINGS +#define INCLUDED_VIEWERSETTINGS + +#include +#include + +typedef struct +{ + // model + vec3_t rot; + vec3_t trans; + float movementScale; + + // render + int renderMode; + int orientType; + bool showBackground; + int showGround; + int numModelChanges; // if user touch settings that directly stored into the model + int texture; + + // animation + int sequence_autoplay; + float speedScale; + + int showMaximized; + + // colors + float bgColor[4]; + float lColor[4]; + float gColor[4]; + + // misc + int textureLimit; + bool pause; + + // only used for fullscreen mode + char spriteFile[256]; + char spritePath[256]; + char oldSpritePath[256]; + char backgroundTexFile[256]; + char groundTexFile[256]; + + char spritePathList[2048][256]; + int numSpritePathes; +} ViewerSettings; + +extern ViewerSettings g_viewerSettings; + +bool InitRegistry( void ); +bool SaveString( const char *pKey, char *pValue ); +bool LoadString( const char *pKey, char *pValue ); + +#ifdef __cplusplus +extern "C" { +#endif + +void InitViewerSettings (void); +int LoadViewerSettings (void); +int SaveViewerSettings (void); +void ListDirectory( void ); +const char *LoadNextSprite( void ); +const char *LoadPrevSprite( void ); +bool IsSpriteModel( const char *path ); +#ifdef __cplusplus +} +#endif + + + +#endif // INCLUDED_VIEWERSETTINGS \ No newline at end of file diff --git a/utils/hlsv/hlsv.dsp b/utils/hlsv/hlsv.dsp new file mode 100644 index 0000000..c1e314a --- /dev/null +++ b/utils/hlsv/hlsv.dsp @@ -0,0 +1,192 @@ +# Microsoft Developer Studio Project File - Name="hlsv" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Application" 0x0101 + +CFG=hlsv - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "hlsv.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "hlsv.mak" CFG="hlsv - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "hlsv - Win32 Release" (based on "Win32 (x86) Application") +!MESSAGE "hlsv - Win32 Debug" (based on "Win32 (x86) Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "hlsv - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\..\temp\hlsv\!release" +# PROP Intermediate_Dir "..\..\temp\hlsv\!release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MT /W3 /GX /O1 /I "..\mxtk" /I "..\..\game_shared" /I "..\..\engine" /I "..\..\common" /I "..\common" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /FD /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /o "NUL" /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /o "NUL" /win32 +# ADD BASE RSC /l 0x807 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 msvcrt.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib opengl32.lib glu32.lib comctl32.lib winmm.lib ..\common\mxtk.lib /nologo /entry:"mainCRTStartup" /subsystem:windows /pdb:none /machine:I386 /nodefaultlib:"libcmt" /release /opt:nowin98 +# SUBTRACT LINK32 /nodefaultlib +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\hlsv\!release +InputPath=\Paranoia2\src_main\temp\hlsv\!release\hlsv.exe +SOURCE="$(InputPath)" + +"C:\Program Files\ModelViewer\hlsv.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\hlsv.exe "C:\Program Files\ModelViewer\hlsv.exe" + +# End Custom Build + +!ELSEIF "$(CFG)" == "hlsv - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\..\temp\hlsv\!debug" +# PROP Intermediate_Dir "..\..\temp\hlsv\!debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\mxtk" /I "..\..\game_shared" /I "..\..\engine" /I "..\..\common" /I "..\common" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FAs /FR /FD /c +# SUBTRACT CPP /YX +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /o "NUL" /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /o "NUL" /win32 +# ADD BASE RSC /l 0x807 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 msvcrtd.lib user32.lib gdi32.lib winspool.lib comctl32.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib opengl32.lib glu32.lib winmm.lib ..\common\mxtk.lib /nologo /entry:"mainCRTStartup" /subsystem:windows /debug /machine:I386 /nodefaultlib:"libcd" /pdbtype:sept +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\hlsv\!debug +InputPath=\Paranoia2\src_main\temp\hlsv\!debug\hlsv.exe +SOURCE="$(InputPath)" + +"C:\Program Files\ModelViewer\hlsv.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\hlsv.exe "C:\Program Files\ModelViewer\hlsv.exe" + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "hlsv - Win32 Release" +# Name "hlsv - Win32 Debug" +# Begin Source File + +SOURCE=..\..\game_shared\common.cpp +# End Source File +# Begin Source File + +SOURCE=.\FileAssociation.cpp +# End Source File +# Begin Source File + +SOURCE=.\FileAssociation.h +# End Source File +# Begin Source File + +SOURCE=.\GlWindow.cpp +# End Source File +# Begin Source File + +SOURCE=.\GlWindow.h +# End Source File +# Begin Source File + +SOURCE=.\hlsv.rc +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\mathlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\matrix.cpp +# End Source File +# Begin Source File + +SOURCE=.\pakviewer.cpp +# End Source File +# Begin Source File + +SOURCE=.\pakviewer.h +# End Source File +# Begin Source File + +SOURCE=.\resource.h +# End Source File +# Begin Source File + +SOURCE=.\sprite.h +# End Source File +# Begin Source File + +SOURCE=.\sprite_render.cpp +# End Source File +# Begin Source File + +SOURCE=.\sprite_utils.cpp +# End Source File +# Begin Source File + +SOURCE=.\SpriteModel.h +# End Source File +# Begin Source File + +SOURCE=.\sprviewer.cpp +# End Source File +# Begin Source File + +SOURCE=.\sprviewer.h +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\stringlib.cpp +# End Source File +# Begin Source File + +SOURCE=.\ViewerSettings.cpp +# End Source File +# Begin Source File + +SOURCE=.\ViewerSettings.h +# End Source File +# End Target +# End Project diff --git a/utils/hlsv/hlsv.manifest b/utils/hlsv/hlsv.manifest new file mode 100644 index 0000000..d0ab83a --- /dev/null +++ b/utils/hlsv/hlsv.manifest @@ -0,0 +1,11 @@ + + + + Paranoia 2 Modelviewer 1.2.5.0 + + + + + + + \ No newline at end of file diff --git a/utils/hlsv/hlsv.rc b/utils/hlsv/hlsv.rc new file mode 100644 index 0000000..8388ac2 --- /dev/null +++ b/utils/hlsv/hlsv.rc @@ -0,0 +1,74 @@ +//Microsoft Developer Studio generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +1 24 MOVEABLE PURE "hlsv.manifest" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// Deutsch (Schweiz) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_DES) +#ifdef _WIN32 +LANGUAGE LANG_GERMAN, SUBLANG_GERMAN_SWISS +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +MX_ICON ICON DISCARDABLE "icon1.ico" + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // Deutsch (Schweiz) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/utils/hlsv/icon1.ico b/utils/hlsv/icon1.ico new file mode 100644 index 0000000..a544916 Binary files /dev/null and b/utils/hlsv/icon1.ico differ diff --git a/utils/hlsv/pakviewer.cpp b/utils/hlsv/pakviewer.cpp new file mode 100644 index 0000000..b4ebcd1 --- /dev/null +++ b/utils/hlsv/pakviewer.cpp @@ -0,0 +1,506 @@ +// +// Quake1 Model Viewer (c) 1999 by Mete Ciragan +// +// file: pakviewer.cpp +// last modified: Apr 29 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +// version: 1.4 +// +// email: mete@swissquake.ch +// web: http://www.swissquake.ch/chumbalum-soft/ +// +#include +#include +#include +#include +#include "pakviewer.h" +#include "GlWindow.h" +#include "sprviewer.h" +#include "FileAssociation.h" +#include "SpriteModel.h" + + +int +pak_ExtractFile (const char *pakFile, const char *lumpName, char *outFile) +{ + FILE *file = fopen (pakFile, "rb"); + if (!file) + return 0; + + int ident, dirofs, dirlen; + + fread (&ident, sizeof (int), 1, file); + if (ident != (int) (('K' << 24) + ('C' << 16) + ('A' << 8) + 'P')) + { + fclose (file); + return 0; + } + + fread (&dirofs, sizeof (int), 1, file); + fread (&dirlen, sizeof (int), 1, file); + + fseek (file, dirofs, SEEK_SET); + int numLumps = dirlen / 64; + + for (int i = 0; i < numLumps; i++) + { + char name[56]; + int filepos, filelen; + + fread (name, 56, 1, file); + fread (&filepos, sizeof (int), 1, file); + fread (&filelen, sizeof (int), 1, file); + + if (!mx_strcasecmp (name, lumpName)) + { + FILE *out = fopen (outFile, "wb"); + if (!out) + { + fclose (file); + return 0; + } + + fseek (file, filepos, SEEK_SET); + + while (filelen--) + fputc (fgetc (file), out); + + fclose (out); + fclose (file); + + return 1; + } + } + + fclose (file); + + return 0; +} + + + +PAKViewer::PAKViewer (mxWindow *window) +: mxWindow (window, 0, 0, 0, 0, "", mxWindow::Normal) +{ + strcpy (d_pakFile, ""); + strcpy (d_currLumpName, ""); + + tvPAK = new mxTreeView (this, 0, 0, 0, 0, IDC_PAKVIEWER); + pmMenu = new mxPopupMenu (); + pmMenu->add ("Load Sprite", 1); + pmMenu->addSeparator (); + pmMenu->add ("Load Background", 2); + pmMenu->add ("Load Ground", 3); + pmMenu->addSeparator (); + pmMenu->add ("Play Sound", 4); + pmMenu->addSeparator (); + pmMenu->add ("Extract File...", 5); + setLoadEntirePAK (true); + + setVisible (false); +} + + + +PAKViewer::~PAKViewer () +{ + tvPAK->remove (0); + closePAKFile (); +} + + + +void +_makeTempFileName (char *str, const char *suffix) +{ + strcpy (str, mx_gettemppath ()); + + strcat (str, "/hltempsprite"); + strcat (str, suffix); +} + + + +int +PAKViewer::handleEvent (mxEvent *event) +{ + switch (event->event) + { + case mxEvent::Action: + { + switch (event->action) + { + case IDC_PAKVIEWER: // tvPAK + if (event->flags & mxEvent::RightClicked) + { + pmMenu->setEnabled (1, strstr (d_currLumpName, ".spr") != 0); + pmMenu->setEnabled (2, strstr (d_currLumpName, ".tga") != 0 || strstr (d_currLumpName, ".bmp")); + pmMenu->setEnabled (3, strstr (d_currLumpName, ".tga") != 0 || strstr (d_currLumpName, ".bmp")); + pmMenu->setEnabled (4, strstr (d_currLumpName, ".wav") != 0); + int ret = pmMenu->popup (tvPAK, event->x, event->y); + switch (ret) + { + case 1: + OnLoadSprite (); + break; + + case 2: + OnLoadTexture (TEXTURE_BACKGROUND); + break; + + case 3: + OnLoadTexture (TEXTURE_GROUND); + break; + + case 4: + OnPlaySound (); + break; + + case 5: + OnExtract (); + break; + } + } + else if (event->flags & mxEvent::DoubleClicked) + { + OnPAKViewer (); + char e[16]; + + strncpy (e, mx_getextension (d_currLumpName), 16); + int mode = g_FileAssociation->getMode (&e[1]); + if (mode == -1) + return 1; + + char *program = g_FileAssociation->getProgram (&e[1]); + +#ifdef WIN32 + if (mode == 0) + { + char str[256]; + _makeTempFileName (str, e); + if (!pak_ExtractFile (d_pakFile, d_currLumpName, str)) + mxMessageBox (this, "Error extracting from PAK file.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + else + { + if (program) + { + char path[256]; + strcpy (path, program); + strcat (path, " "); + strcat (path, str); + if ((int) WinExec (path, SW_SHOW) <= 32) + mxMessageBox (this, "Error executing specified program.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + } + } + } + + // associated program + else if (mode == 1) + { + char str[256]; + _makeTempFileName (str, e); + if (!pak_ExtractFile (d_pakFile, d_currLumpName, str)) + mxMessageBox (this, "Error extracting from PAK file.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + else + if ((int) ShellExecute ((HWND) getHandle (), "open", str, 0, 0, SW_SHOW) <= 32) + mxMessageBox (this, "Error executing document with associated program.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + } + + // HLMV default + else +#endif + if (mode == 2) + { + if (!mx_strcasecmp (e, ".spr")) + OnLoadSprite (); + + else if (!mx_strcasecmp (e, ".tga") || !mx_strcasecmp (e, ".bmp")) + OnLoadTexture (TEXTURE_BACKGROUND); + + else if (!mx_strcasecmp (e, ".wav")) + OnPlaySound (); + + return 1; + } + } + + return OnPAKViewer (); + } // event->action + } // mxEvent::Action + break; + + case mxEvent::Size: + { + tvPAK->setBounds (0, 0, event->width, event->height); + } // mxEvent::Size + break; + + } // event->event + + return 1; +} + + + +int +PAKViewer::OnPAKViewer () +{ + mxTreeViewItem *tvi = tvPAK->getSelectedItem (); + if (tvi) + { + strcpy (d_currLumpName, tvPAK->getLabel (tvi)); + + // find the full lump name + mxTreeViewItem *tviParent = tvPAK->getParent (tvi); + char tmp[128]; + while (tviParent) + { + strcpy (tmp, d_currLumpName); + strcpy (d_currLumpName, tvPAK->getLabel (tviParent)); + strcat (d_currLumpName, "/"); + strcat (d_currLumpName, tmp); + tviParent = tvPAK->getParent (tviParent); + } + + if (!d_loadEntirePAK) + { + // finally insert "sprites/" + strcpy (tmp, d_currLumpName); + strcpy (d_currLumpName, "sprites/"); + strcat (d_currLumpName, tmp); + } + } + + return 1; +} + + + +int +PAKViewer::OnLoadSprite () +{ + static char str2[256]; + char suffix[16]; + + strcpy (suffix, ".spr"); + _makeTempFileName (str2, suffix); + + if (!pak_ExtractFile (d_pakFile, d_currLumpName, str2)) + { + mxMessageBox (this, "Error extracting from PAK file.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + return 1; + } + + g_spriteModel.FreeSprite(); + g_SPRViewer->loadSprite( str2 ); + + return 1; +} + +int +PAKViewer::OnLoadTexture (int pos) +{ + static char str2[256]; + char suffix[16] = ""; + + if (strstr (d_currLumpName, ".tga")) + sprintf (suffix, "%d%s", pos, ".tga"); + else if (strstr (d_currLumpName, ".bmp")) + sprintf (suffix, "%d%s", pos, ".bmp"); + + _makeTempFileName (str2, suffix); + + if (!pak_ExtractFile (d_pakFile, d_currLumpName, str2)) + { + mxMessageBox (this, "Error extracting from PAK file.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + return 1; + } + + // now load the things + if (g_GlWindow->loadTexture (str2, pos)) + { + if (pos == TEXTURE_BACKGROUND) + g_SPRViewer->setShowBackground (true); + else + g_SPRViewer->setShowGround (true); + g_GlWindow->redraw (); + } + else + mxMessageBox (this, "Error loading texture.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + + return 1; +} + + + +int +PAKViewer::OnPlaySound () +{ +#ifdef WIN32 + static char str2[256]; + char suffix[16] = ""; + + // stop any playing sound + PlaySound (0, 0, SND_FILENAME | SND_ASYNC); + + if (strstr (d_currLumpName, ".wav")) + sprintf (suffix, "%d%s", 44, ".wav"); + + _makeTempFileName (str2, suffix); + + if (!pak_ExtractFile (d_pakFile, d_currLumpName, str2)) + { + mxMessageBox (this, "Error extracting from PAK file.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + return 1; + } + + PlaySound (str2, 0, SND_FILENAME | SND_ASYNC); + +#endif + return 1; +} + + + +int +PAKViewer::OnExtract () +{ + char *ptr = (char *) mxGetSaveFileName (this, "", "*.*"); + if (ptr) + { + if (!pak_ExtractFile (d_pakFile, d_currLumpName, ptr)) + mxMessageBox (this, "Error extracting from PAK file.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + } + + return 1; +} + + + +int +_compare(const void *arg1, const void *arg2) +{ + if (strchr ((char *) arg1, '/') && !strchr ((char *) arg2, '/')) + return -1; + + else if (!strchr ((char *) arg1, '/') && strchr ((char *) arg2, '/')) + return 1; + + else + return strcmp ((char *) arg1, (char *) arg2); +} + + + +bool +PAKViewer::openPAKFile (const char *pakFile) +{ + FILE *file = fopen (pakFile, "rb"); + if (!file) + return false; + + int ident, dirofs, dirlen; + + // check for id + fread (&ident, sizeof (int), 1, file); + if (ident != (int) (('K' << 24) + ('C' << 16) + ('A' << 8) + 'P')) + { + fclose (file); + return false; + } + + // load lumps + fread (&dirofs, sizeof (int), 1, file); + fread (&dirlen, sizeof (int), 1, file); + int numLumps = dirlen / 64; + + fseek (file, dirofs, SEEK_SET); + lump_t *lumps = new lump_t[numLumps]; + if (!lumps) + { + fclose (file); + return false; + } + + fread (lumps, sizeof (lump_t), numLumps, file); + fclose (file); + + qsort (lumps, numLumps, sizeof (lump_t), _compare); + + // save pakFile for later + strcpy (d_pakFile, pakFile); + + tvPAK->remove (0); + + char namestack[32][32]; + mxTreeViewItem *tvistack[32]; + for (int k = 0; k < 32; k++) + { + strcpy (namestack[k], ""); + tvistack[k] = 0; + } + + for (int i = 0; i < numLumps; i++) + { + if (d_loadEntirePAK || !strncmp (lumps[i].name, "sprites", 7)) + { + char *tok; + if (d_loadEntirePAK) + tok = &lumps[i].name[0]; + else + tok = &lumps[i].name[7]; + + int i = 1; + while (tok) + { + char *end = strchr (tok, '/'); + if (end) + *end = '\0'; + + if (strcmp (namestack[i], tok)) + { + strcpy (namestack[i], tok); +/* + if (i == 0) + tvistack[i] = tvPAK->add (0, tok); + else*/ + tvistack[i] = tvPAK->add (tvistack[i - 1], tok); + + for (int j = i + 1; j < 32; j++) + { + strcpy (namestack[j], ""); + tvistack[j] = 0; + } + } + + ++i; + + if (end) + tok = end + 1; + else + tok = 0; + } + } + } + + delete[] lumps; + + setVisible (true); + + return true; +} + + + +void +PAKViewer::closePAKFile () +{ + strcpy (d_pakFile, ""); + setVisible (false); +} diff --git a/utils/hlsv/pakviewer.h b/utils/hlsv/pakviewer.h new file mode 100644 index 0000000..14ad3c4 --- /dev/null +++ b/utils/hlsv/pakviewer.h @@ -0,0 +1,92 @@ +// +// MD2 Viewer (c) 1999 by Mete Ciragan +// +// file: pakviewer.h +// last modified: Apr 28 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +// version: 1.4 +// +// email: mete@swissquake.ch +// web: http://www.swissquake.ch/chumbalum-soft/ +// +#ifndef INCLUDED_PAKVIEWER +#define INCLUDED_PAKVIEWER + + + +#ifndef INCLUDED_MXWINDOW +#include +#endif + + + +#define IDC_PAKVIEWER 1001 + + + +typedef struct +{ + char name[56]; + int filepos; + int filelen; +} lump_t; + + + +#ifdef __cpluspus +extern "C" { +#endif + +int pak_ExtractFile (const char *pakFile, const char *lumpName, char *outFile); + +#ifdef __cpluspus +} +#endif + + + +class mxTreeView; +class mxButton; +class mxPopupMenu; +class GlWindow; + + + +class PAKViewer : public mxWindow +{ + char d_pakFile[256]; + char d_currLumpName[256]; + bool d_loadEntirePAK; + mxTreeView *tvPAK; + mxPopupMenu *pmMenu; + +public: + // CREATORS + PAKViewer (mxWindow *window); + ~PAKViewer (); + + // MANIPULATORS + virtual int handleEvent (mxEvent *event); + int OnPAKViewer (); + int OnLoadSprite (); + int OnLoadTexture (int pos); + int OnPlaySound (); + int OnExtract (); + + bool openPAKFile (const char *pakFile); + void closePAKFile (); + void setLoadEntirePAK (bool b) { d_loadEntirePAK = b; } + + // ACCESSORS + bool getLoadEntirePAK () const { return d_loadEntirePAK; } +}; + + + +#endif // INCLUDED_PAKVIEWER \ No newline at end of file diff --git a/utils/hlsv/resource.h b/utils/hlsv/resource.h new file mode 100644 index 0000000..8d23573 --- /dev/null +++ b/utils/hlsv/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by md2viewer.rc +// +#define IDI_ICON1 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/utils/hlsv/sprite.h b/utils/hlsv/sprite.h new file mode 100644 index 0000000..4eb49fa --- /dev/null +++ b/utils/hlsv/sprite.h @@ -0,0 +1,130 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#ifndef SPRITE_H +#define SPRITE_H + +/* +============================================================================== + +SPRITE MODELS + +.spr extended version (Half-Life compatible sprites with some Xash3D extensions) +============================================================================== +*/ + +#define IDSPRITEHEADER (('P'<<24)+('S'<<16)+('D'<<8)+'I') // little-endian "IDSP" + +#define SPRITE_VERSION_Q1 1 // Quake sprites +#define SPRITE_VERSION_HL 2 // Half-Life sprites +#define SPRITE_VERSION_32 32 // Captain Obvious mode on + +#define MAX_SPRITE_FRAMES 512 // from spritegen + +// must match definition in alias.h +#ifndef SYNCTYPE_T +#define SYNCTYPE_T +typedef enum +{ + ST_SYNC = 0, + ST_RAND +} synctype_t; +#endif + +typedef enum +{ + FRAME_SINGLE = 0, + FRAME_GROUP, + FRAME_ANGLED // Xash3D ext +} frametype_t; + +typedef enum +{ + SPR_NORMAL = 0, + SPR_ADDITIVE, + SPR_INDEXALPHA, + SPR_ALPHTEST, +} drawtype_t; + +typedef enum +{ + SPR_FWD_PARALLEL_UPRIGHT = 0, + SPR_FACING_UPRIGHT, + SPR_FWD_PARALLEL, + SPR_ORIENTED, + SPR_FWD_PARALLEL_ORIENTED, +} angletype_t; + +typedef enum +{ + SPR_CULL_FRONT = 0, // oriented sprite will be draw with one face + SPR_CULL_NONE, // oriented sprite will be draw back face too +} facetype_t; + +// generic helper +typedef struct +{ + int ident; // LittleLong 'ISPR' + int version; // current version 2 +} dsprite_t; + +typedef struct +{ + int ident; // LittleLong 'ISPR' + int version; // current version 2 + int type; // camera align + float boundingradius; // quick face culling + int bounds[2]; // mins\maxs + int numframes; // including groups + float beamlength; // ??? + synctype_t synctype; // animation synctype +} dsprite_q1_t; + +typedef struct +{ + int ident; // LittleLong 'ISPR' + int version; // current version 2 + angletype_t type; // camera align + drawtype_t texFormat; // rendering mode + int boundingradius; // quick face culling + int bounds[2]; // mins\maxs + int numframes; // including groups + facetype_t facetype; // cullface (Xash3D ext) + synctype_t synctype; // animation synctype +} dsprite_hl_t; + +typedef struct +{ + int origin[2]; + int width; + int height; +} dspriteframe_t; + +typedef struct +{ + int numframes; +} dspritegroup_t; + +typedef struct +{ + float interval; +} dspriteinterval_t; + +typedef struct +{ + frametype_t type; +} dframetype_t; + +#endif//SPRITE_H \ No newline at end of file diff --git a/utils/hlsv/sprite_render.cpp b/utils/hlsv/sprite_render.cpp new file mode 100644 index 0000000..7471b1d --- /dev/null +++ b/utils/hlsv/sprite_render.cpp @@ -0,0 +1,479 @@ +/*** +* +* Copyright (c) 1998, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ +// updates: +// 1-4-99 fixed file texture load and file read bug + +//////////////////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include "SpriteModel.h" +#include "GLWindow.h" +#include "ViewerSettings.h" +#include "stringlib.h" + +#include +#include "sprviewer.h" + +vec3_t g_lightcolor; + +bool bUseWeaponOrigin = false; +extern bool g_bStopPlaying; + +void SpriteModel :: centerView( bool reset ) +{ + vec3_t min, max; + + ExtractBbox( min, max ); + + float dx = max[0] - min[0]; + float dy = max[1] - min[1]; + float dz = max[2] - min[2]; + float d = max( dx, max( dy, dz )); + + if( reset ) + { + g_viewerSettings.trans[0] = 0; + g_viewerSettings.trans[1] = 0; + g_viewerSettings.trans[2] = 0; + } + else + { + g_viewerSettings.trans[0] = 0; + g_viewerSettings.trans[1] = min[2] + dz / 2.0f; + g_viewerSettings.trans[2] = d * 1.0f; + } + + g_viewerSettings.rot[0] = -90.0f; + g_viewerSettings.rot[1] = -90.0f; + g_viewerSettings.rot[2] = 0.0f; + + g_viewerSettings.movementScale = max( 1.0f, d * 0.01f ); + m_yaw = 90.0; +} + +int SpriteModel :: setFrame( int newframe ) +{ + if( !m_pspritehdr ) + return 0; + + if( newframe == -1 ) + return m_frame; + + if( newframe < g_GlWindow->getStartFrame( )) + return m_frame; + + if( newframe > g_GlWindow->getEndFrame( )) + return m_frame; + + m_frame = newframe; + return newframe; +} + +/* +================ +R_GetSpriteFrame + +assume pModel is valid +================ +*/ +mspriteframe_t *SpriteModel :: GetSpriteFrame( int frame ) +{ + msprite_t *psprite = m_pspritehdr; + mspritegroup_t *pspritegroup; + mspriteframe_t *pspriteframe = NULL; + float *pintervals, fullinterval; + int i, numframes; + float targettime; + + if( !psprite ) return NULL; + + if( frame < 0 ) + { + frame = 0; + } + else if( frame >= psprite->numframes ) + { + frame = psprite->numframes - 1; + } + + if( psprite->frames[frame].type == FRAME_SINGLE ) + { + pspriteframe = psprite->frames[frame].frameptr; + } + else if( psprite->frames[frame].type == FRAME_GROUP ) + { + pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr; + pintervals = pspritegroup->intervals; + numframes = pspritegroup->numframes; + fullinterval = pintervals[numframes-1]; + + // when loading in Mod_LoadSpriteGroup, we guaranteed all interval values + // are positive, so we don't have to worry about division by zero + targettime = m_flTime - ((int)( m_flTime / fullinterval )) * fullinterval; + + for( i = 0; i < (numframes - 1); i++ ) + { + if( pintervals[i] > targettime ) + break; + } + pspriteframe = pspritegroup->frames[i]; + } + else if( psprite->frames[frame].type == FRAME_ANGLED ) + { + int angleframe = (int)(Q_rint(( m_yaw - g_viewerSettings.rot[1] + 45.0f ) / 360 * 8) - 4) & 7; + + // e.g. doom-style sprite monsters + pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr; + pspriteframe = pspritegroup->frames[angleframe]; + } + + return pspriteframe; +} + +/* +================ +GetSpriteFrameInterpolant + +NOTE: we using prevblending[0] and [1] for holds interval +between frames where are we lerping +================ +*/ +float SpriteModel :: GetSpriteFrameInterpolant( int frame, mspriteframe_t **oldframe, mspriteframe_t **curframe ) +{ + msprite_t *psprite = m_pspritehdr; + mspritegroup_t *pspritegroup; + int i, j, numframes; + float lerpFrac, jtime, jinterval; + float *pintervals, fullinterval, targettime; + int m_fDoInterp = true; + + if( !psprite ) return 0.0f; + lerpFrac = 1.0f; + + if( frame < 0 ) + { + frame = 0; + } + else if( frame >= psprite->numframes ) + { + frame = psprite->numframes - 1; + } + + if( psprite->frames[frame].type == FRAME_SINGLE ) + { + if( m_fDoInterp ) + { + if( m_prevblending[0] >= psprite->numframes || psprite->frames[m_prevblending[0]].type != FRAME_SINGLE ) + { + // this can be happens when rendering switched between single and angled frames + // or change model on replace delta-entity + m_prevblending[0] = m_prevblending[1] = frame; + m_sequencetime = m_flTime; + lerpFrac = 1.0f; + } + + if( m_sequencetime < m_flTime ) + { + if( frame != m_prevblending[1] ) + { + m_prevblending[0] = m_prevblending[1]; + m_prevblending[1] = frame; + m_sequencetime = m_flTime; + lerpFrac = 0.0f; + } + else lerpFrac = (m_flTime - m_sequencetime) * 11.0f; + } + else + { + m_prevblending[0] = m_prevblending[1] = frame; + m_sequencetime = m_flTime; + lerpFrac = 0.0f; + } + } + else + { + m_prevblending[0] = m_prevblending[1] = frame; + lerpFrac = 1.0f; + } + + if( m_prevblending[0] >= psprite->numframes ) + { + // reset interpolation on change model + m_prevblending[0] = m_prevblending[1] = frame; + m_sequencetime = m_flTime; + lerpFrac = 0.0f; + } + + // get the interpolated frames + if( oldframe ) *oldframe = psprite->frames[m_prevblending[0]].frameptr; + if( curframe ) *curframe = psprite->frames[frame].frameptr; + } + else if( psprite->frames[frame].type == FRAME_GROUP ) + { + pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr; + pintervals = pspritegroup->intervals; + numframes = pspritegroup->numframes; + fullinterval = pintervals[numframes-1]; + jinterval = pintervals[1] - pintervals[0]; + jtime = 0.0f; + + // when loading in Mod_LoadSpriteGroup, we guaranteed all interval values + // are positive, so we don't have to worry about division by zero + targettime = m_flTime - ((int)( m_flTime / fullinterval)) * fullinterval; + + // LordHavoc: since I can't measure the time properly when it loops from numframes - 1 to 0, + // i instead measure the time of the first frame, hoping it is consistent + for( i = 0, j = numframes - 1; i < (numframes - 1); i++ ) + { + if( pintervals[i] > targettime ) + break; + j = i; + jinterval = pintervals[i] - jtime; + jtime = pintervals[i]; + } + + if( m_fDoInterp ) + lerpFrac = (targettime - jtime) / jinterval; + else j = i; // no lerping + + // get the interpolated frames + if( oldframe ) *oldframe = pspritegroup->frames[j]; + if( curframe ) *curframe = pspritegroup->frames[i]; + } + else if( psprite->frames[frame].type == FRAME_ANGLED ) + { + // e.g. doom-style sprite monsters + int angleframe = (int)(Q_rint(( g_viewerSettings.rot[1] - m_yaw + 45.0f ) / 360 * 8) - 4) & 7; + + if( m_fDoInterp ) + { + if( m_prevblending[0] >= psprite->numframes || psprite->frames[m_prevblending[0]].type != FRAME_ANGLED ) + { + // this can be happens when rendering switched between single and angled frames + // or change model on replace delta-entity + m_prevblending[0] = m_prevblending[1] = frame; + m_sequencetime = m_flTime; + lerpFrac = 1.0f; + } + + if( m_sequencetime < m_flTime ) + { + if( frame != m_prevblending[1] ) + { + m_prevblending[0] = m_prevblending[1]; + m_prevblending[1] = frame; + m_sequencetime = m_flTime; + lerpFrac = 0.0f; + } + else lerpFrac = (m_flTime - m_sequencetime) * 11.0f; + } + else + { + m_prevblending[0] = m_prevblending[1] = frame; + m_sequencetime = m_flTime; + lerpFrac = 0.0f; + } + } + else + { + m_prevblending[0] = m_prevblending[1] = frame; + lerpFrac = 1.0f; + } + + pspritegroup = (mspritegroup_t *)psprite->frames[m_prevblending[0]].frameptr; + if( oldframe ) *oldframe = pspritegroup->frames[angleframe]; + + pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr; + if( curframe ) *curframe = pspritegroup->frames[angleframe]; + } + + return lerpFrac; +} + +/* +================= +DrawSpriteQuad +================= +*/ +void SpriteModel :: DrawQuad( mspriteframe_t *frame, const vec3_t &org, const vec3_t &v_right, const vec3_t &v_up, float scale ) +{ + vec3_t point; + + glBegin( GL_QUADS ); + glTexCoord2f( 0.0f, 1.0f ); + point = org + v_up * (frame->down * scale); + point = point + v_right * (frame->left * scale); + glVertex3fv( point ); + glTexCoord2f( 0.0f, 0.0f ); + point = org + v_up * (frame->up * scale); + point = point + v_right * (frame->left * scale); + glVertex3fv( point ); + glTexCoord2f( 1.0f, 0.0f ); + point = org + v_up * (frame->up * scale); + point = point + v_right * (frame->right * scale); + glVertex3fv( point ); + glTexCoord2f( 1.0f, 1.0f ); + point = org + v_up * (frame->down * scale); + point = point + v_right * (frame->right * scale); + glVertex3fv( point ); + glEnd(); +} + +/* +================= +R_SpriteAllowLerping +================= +*/ +bool SpriteModel :: AllowLerping( void ) +{ + if( !m_pspritehdr ) + return false; + + if( m_pspritehdr->numframes <= 1 ) + return false; + + if( g_viewerSettings.renderMode != SPR_ADDITIVE ) + return false; + + return true; +} + +void SpriteModel :: SetupTransform( void ) +{ + vec3_t angles( 0.0f, 0.0f, 0.0f ); + vec3_t origin( 0.0f, 0.0f, 0.0f ); + + m_protationmatrix = matrix4x4( origin, angles, 1.0f ); + + angles[0] = 270.0f - g_viewerSettings.rot[0]; + angles[1] = 270.0f - g_viewerSettings.rot[1]; + origin = g_viewerSettings.trans; + m_viewVectors = matrix4x4( origin, angles, 1.0f ); +} + +/* +================ +StudioModel::DrawModel +inputs: + currententity + r_entorigin +================ +*/ +void SpriteModel :: DrawSprite( void ) +{ + mspriteframe_t *frame, *oldframe; + float angle, dot, sr, cr; + float lerp = 1.0f, ilerp; + vec3_t v_right, v_up; + vec3_t origin, color; + float blend = 1.0f; + msprite_t *psprite; + int i, type; + + if( !m_pspritehdr ) return; + + SetupTransform(); + + psprite = (msprite_t * )m_pspritehdr; + origin = g_vecZero; // set render origin + + // all sprites can have color + color = Vector( 1.0f, 1.0f, 1.0f ); + glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + + if( g_viewerSettings.renderMode == SPR_ADDITIVE ) + blend = 0.5f; + + if( g_viewerSettings.renderMode == SPR_ALPHTEST ) + { + color[0] = g_viewerSettings.lColor[0]; + color[1] = g_viewerSettings.lColor[1]; + color[2] = g_viewerSettings.lColor[2]; + } + + if( AllowLerping( )) + lerp = GetSpriteFrameInterpolant( m_frame, &oldframe, &frame ); + else frame = oldframe = GetSpriteFrame( m_frame ); + + type = psprite->type; + + switch( g_viewerSettings.orientType ) + { + case SPR_ORIENTED: + v_right = m_protationmatrix[1]; + v_up = m_protationmatrix[2]; + break; + case SPR_FACING_UPRIGHT: + case SPR_FWD_PARALLEL_UPRIGHT: + dot = m_viewVectors[0][2]; + if(( dot > 0.999848f ) || ( dot < -0.999848f )) // cos(1 degree) = 0.999848 + return; // invisible + v_up = Vector( 0.0f, 0.0f, 1.0f ); + v_right = Vector( -m_viewVectors[0][1], m_viewVectors[0][0], 0.0f ).Normalize(); + break; + case SPR_FWD_PARALLEL_ORIENTED: + angle = 0.0f * (M_PI2 / 360.0f); + SinCos( angle, &sr, &cr ); + for( i = 0; i < 3; i++ ) + { + v_right[i] = (m_viewVectors[1][i] * cr + m_viewVectors[2][i] * sr); + v_up[i] = m_viewVectors[1][i] * -sr + m_viewVectors[2][i] * cr; + } + break; + case SPR_FWD_PARALLEL: // normal sprite + default: + v_right = m_viewVectors[1]; + v_up = m_viewVectors[2]; + break; + } + + if( psprite->facecull == SPR_CULL_NONE ) + glCullFace( GL_NONE ); + + if( oldframe == frame ) + { + // draw the single non-lerped frame + glColor4f( color[0], color[1], color[2], blend ); + glBindTexture( GL_TEXTURE_2D, frame->gl_texturenum ); + DrawQuad( frame, origin, v_right, v_up, 1.0f ); + } + else + { + // draw two combined lerped frames + lerp = bound( 0.0f, lerp, 1.0f ); + ilerp = 1.0f - lerp; + + if( ilerp != 0.0f ) + { + glColor4f( color[0], color[1], color[2], blend * ilerp ); + glBindTexture( GL_TEXTURE_2D, oldframe->gl_texturenum ); + DrawQuad( oldframe, origin, v_right, v_up, 1.0f ); + } + + if( lerp != 0.0f ) + { + glColor4f( color[0], color[1], color[2], blend * lerp ); + glBindTexture( GL_TEXTURE_2D, frame->gl_texturenum ); + DrawQuad( frame, origin, v_right, v_up, 1.0f ); + } + } + + if( psprite->facecull == SPR_CULL_NONE ) + glCullFace( GL_FRONT ); + + glDisable( GL_ALPHA_TEST ); + glDepthMask( GL_TRUE ); + glDisable( GL_BLEND ); + glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); + glEnable( GL_DEPTH_TEST ); +} \ No newline at end of file diff --git a/utils/hlsv/sprite_utils.cpp b/utils/hlsv/sprite_utils.cpp new file mode 100644 index 0000000..407223a --- /dev/null +++ b/utils/hlsv/sprite_utils.cpp @@ -0,0 +1,569 @@ +/*** +* +* Copyright (c) 1998, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ +// updates: +// 1-4-99 fixed file texture load and file read bug + +//////////////////////////////////////////////////////////////////////// +#include +#include +#include +#include +#include +#include "SpriteModel.h" +#include "GLWindow.h" +#include "ViewerSettings.h" +#include "stringlib.h" + +#include +#include "sprviewer.h" + +SpriteModel g_spriteModel; +extern bool bUseWeaponOrigin; +extern bool bUseWeaponLeftHand; + +//////////////////////////////////////////////////////////////////////// + +// Quake1 always has same palette +byte palette_q1[768] = +{ +0,0,0,15,15,15,31,31,31,47,47,47,63,63,63,75,75,75,91,91,91,107,107,107,123,123,123,139,139,139,155,155,155,171, +171,171,187,187,187,203,203,203,219,219,219,235,235,235,15,11,7,23,15,11,31,23,11,39,27,15,47,35,19,55,43,23,63, +47,23,75,55,27,83,59,27,91,67,31,99,75,31,107,83,31,115,87,31,123,95,35,131,103,35,143,111,35,11,11,15,19,19,27, +27,27,39,39,39,51,47,47,63,55,55,75,63,63,87,71,71,103,79,79,115,91,91,127,99,99,139,107,107,151,115,115,163,123, +123,175,131,131,187,139,139,203,0,0,0,7,7,0,11,11,0,19,19,0,27,27,0,35,35,0,43,43,7,47,47,7,55,55,7,63,63,7,71,71, +7,75,75,11,83,83,11,91,91,11,99,99,11,107,107,15,7,0,0,15,0,0,23,0,0,31,0,0,39,0,0,47,0,0,55,0,0,63,0,0,71,0,0,79, +0,0,87,0,0,95,0,0,103,0,0,111,0,0,119,0,0,127,0,0,19,19,0,27,27,0,35,35,0,47,43,0,55,47,0,67,55,0,75,59,7,87,67,7, +95,71,7,107,75,11,119,83,15,131,87,19,139,91,19,151,95,27,163,99,31,175,103,35,35,19,7,47,23,11,59,31,15,75,35,19, +87,43,23,99,47,31,115,55,35,127,59,43,143,67,51,159,79,51,175,99,47,191,119,47,207,143,43,223,171,39,239,203,31,255, +243,27,11,7,0,27,19,0,43,35,15,55,43,19,71,51,27,83,55,35,99,63,43,111,71,51,127,83,63,139,95,71,155,107,83,167,123, +95,183,135,107,195,147,123,211,163,139,227,179,151,171,139,163,159,127,151,147,115,135,139,103,123,127,91,111,119, +83,99,107,75,87,95,63,75,87,55,67,75,47,55,67,39,47,55,31,35,43,23,27,35,19,19,23,11,11,15,7,7,187,115,159,175,107, +143,163,95,131,151,87,119,139,79,107,127,75,95,115,67,83,107,59,75,95,51,63,83,43,55,71,35,43,59,31,35,47,23,27,35, +19,19,23,11,11,15,7,7,219,195,187,203,179,167,191,163,155,175,151,139,163,135,123,151,123,111,135,111,95,123,99,83, +107,87,71,95,75,59,83,63,51,67,51,39,55,43,31,39,31,23,27,19,15,15,11,7,111,131,123,103,123,111,95,115,103,87,107, +95,79,99,87,71,91,79,63,83,71,55,75,63,47,67,55,43,59,47,35,51,39,31,43,31,23,35,23,15,27,19,11,19,11,7,11,7,255, +243,27,239,223,23,219,203,19,203,183,15,187,167,15,171,151,11,155,131,7,139,115,7,123,99,7,107,83,0,91,71,0,75,55, +0,59,43,0,43,31,0,27,15,0,11,7,0,0,0,255,11,11,239,19,19,223,27,27,207,35,35,191,43,43,175,47,47,159,47,47,143,47, +47,127,47,47,111,47,47,95,43,43,79,35,35,63,27,27,47,19,19,31,11,11,15,43,0,0,59,0,0,75,7,0,95,7,0,111,15,0,127,23, +7,147,31,7,163,39,11,183,51,15,195,75,27,207,99,43,219,127,59,227,151,79,231,171,95,239,191,119,247,211,139,167,123, +59,183,155,55,199,195,55,231,227,87,127,191,255,171,231,255,215,255,255,103,0,0,139,0,0,179,0,0,215,0,0,255,0,0,255, +243,147,255,247,199,255,255,255,159,91,83 +}; + +//////////////////////////////////////////////////////////////////////// +static int g_tex_base = TEXTURE_COUNT; + +//Mugsy - upped the maximum texture size to 512. All changes are the replacement of '256' +//with this define, MAX_TEXTURE_DIMS +#define MAX_TEXTURE_DIMS 512 + +void SpriteModel :: UploadTexture( byte *data, int width, int height, byte *srcpal, int name, int size, bool has_alpha ) +{ + int row1[MAX_TEXTURE_DIMS], row2[MAX_TEXTURE_DIMS], col1[MAX_TEXTURE_DIMS], col2[MAX_TEXTURE_DIMS]; + byte *pix1, *pix2, *pix3, *pix4; + byte *tex, *in, *out, pal[768]; + int i, j; + + // convert texture to power of 2 + int outwidth; + for (outwidth = 1; outwidth < width; outwidth <<= 1); + + if (outwidth > MAX_TEXTURE_DIMS) + outwidth = MAX_TEXTURE_DIMS; + + int outheight; + for (outheight = 1; outheight < height; outheight <<= 1); + + if (outheight > MAX_TEXTURE_DIMS) + outheight = MAX_TEXTURE_DIMS; + + in = (byte *)malloc(width * height * 4); + if (!in) return; + + if( size == 1024 ) + { + // expand pixels to rgba + for (i=0 ; i < width * height; i++) + { + in[i*4+0] = srcpal[data[i]*4+0]; + in[i*4+1] = srcpal[data[i]*4+1]; + in[i*4+2] = srcpal[data[i]*4+2]; + in[i*4+3] = srcpal[data[i]*4+3]; + } + } + else + { + memcpy( pal, srcpal, 768 ); + + // expand pixels to rgba + for (i=0 ; i < width * height; i++) + { + if( data[i] == 255 ) + { + has_alpha = true; + in[i*4+0] = 0x00; + in[i*4+1] = 0x00; + in[i*4+2] = 0x00; + in[i*4+3] = 0x00; + } + else + { + in[i*4+0] = pal[data[i]*3+0]; + in[i*4+1] = pal[data[i]*3+1]; + in[i*4+2] = pal[data[i]*3+2]; + in[i*4+3] = 0xFF; + } + } + } + + tex = out = (byte *)malloc( outwidth * outheight * 4); + if (!out) return; + + for (i = 0; i < outwidth; i++) + { + col1[i] = (int) ((i + 0.25) * (width / (float)outwidth)); + col2[i] = (int) ((i + 0.75) * (width / (float)outwidth)); + } + + for (i = 0; i < outheight; i++) + { + row1[i] = (int) ((i + 0.25) * (height / (float)outheight)) * width; + row2[i] = (int) ((i + 0.75) * (height / (float)outheight)) * width; + } + + // scale down and convert to 32bit RGB + for (i=0 ; i>2; + out[1] = (pix1[1] + pix2[1] + pix3[1] + pix4[1])>>2; + out[2] = (pix1[2] + pix2[2] + pix3[2] + pix4[2])>>2; + out[3] = (pix1[3] + pix2[3] + pix3[3] + pix4[3])>>2; + } + } + + GLint outFormat = (has_alpha) ? GL_RGBA : GL_RGB; + + glBindTexture( GL_TEXTURE_2D, name ); + glHint( GL_GENERATE_MIPMAP_HINT_SGIS, GL_NICEST ); + glTexParameteri( GL_TEXTURE_2D, GL_GENERATE_MIPMAP_SGIS, GL_TRUE ); + glTexImage2D( GL_TEXTURE_2D, 0, outFormat, outwidth, outheight, 0, GL_RGBA, GL_UNSIGNED_BYTE, tex ); + glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR ); + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + const char *extensions = (const char *)glGetString( GL_EXTENSIONS ); + + // check for anisotropy support + if( Q_strstr( extensions, "GL_EXT_texture_filter_anisotropic" )) + { + float anisotropy = 1.0f; + glGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &anisotropy ); + glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisotropy ); + } + + free( tex ); + free( in ); +} + +/* +================ +LoadSpriteFrame +================ +*/ +dframetype_t* SpriteModel :: LoadSpriteFrame( void *pin, mspriteframe_t **ppframe ) +{ + dspriteframe_t *pinframe; + mspriteframe_t *pspriteframe; + bool has_alpha = false; + + // check for alpha-channel + if( m_pspritehdr->texFormat == SPR_ALPHTEST || m_pspritehdr->texFormat == SPR_INDEXALPHA ) + has_alpha = true; + + pinframe = (dspriteframe_t *)pin; + + UploadTexture( (byte *)(pinframe + 1), pinframe->width, pinframe->height, m_palette, g_tex_base + m_loadframe, 1024, has_alpha ); + + // setup frame description + pspriteframe = (mspriteframe_t *)calloc( 1, sizeof( mspriteframe_t )); + pspriteframe->width = pinframe->width; + pspriteframe->height = pinframe->height; + pspriteframe->up = pinframe->origin[1]; + pspriteframe->left = pinframe->origin[0]; + pspriteframe->down = pinframe->origin[1] - pinframe->height; + pspriteframe->right = pinframe->width + pinframe->origin[0]; + pspriteframe->gl_texturenum = g_tex_base + m_loadframe; + *ppframe = pspriteframe; + m_loadframe++; + + return (dframetype_t *)((byte *)(pinframe + 1) + pinframe->width * pinframe->height ); +} + +/* +================ +LoadSpriteGroup +================ +*/ +dframetype_t* SpriteModel :: LoadSpriteGroup( void *pin, mspriteframe_t **ppframe ) +{ + dspritegroup_t *pingroup; + mspritegroup_t *pspritegroup; + dspriteinterval_t *pin_intervals; + float *poutintervals; + int i, groupsize, numframes; + void *ptemp; + + pingroup = (dspritegroup_t *)pin; + numframes = pingroup->numframes; + + groupsize = sizeof( mspritegroup_t ) + (numframes - 1) * sizeof( pspritegroup->frames[0] ); + pspritegroup = (mspritegroup_t *)calloc( 1, groupsize ); + pspritegroup->numframes = numframes; + + *ppframe = (mspriteframe_t *)pspritegroup; + pin_intervals = (dspriteinterval_t *)(pingroup + 1); + poutintervals = (float *)malloc( numframes * sizeof( float )); + pspritegroup->intervals = poutintervals; + + for( i = 0; i < numframes; i++ ) + { + *poutintervals = pin_intervals->interval; + if( *poutintervals <= 0.0f ) + *poutintervals = 1.0f; // set error value + poutintervals++; + pin_intervals++; + } + + ptemp = (void *)pin_intervals; + for( i = 0; i < numframes; i++ ) + { + ptemp = LoadSpriteFrame( ptemp, &pspritegroup->frames[i] ); + } + + return (dframetype_t *)ptemp; +} + +msprite_t *SpriteModel :: LoadSprite( const char *spritename ) +{ + FILE *fp; + void *buffer; + + if (!spritename) + return 0; + + // load the model + if( (fp = fopen( spritename, "rb" )) == NULL) + return 0; + + fseek( fp, 0, SEEK_END ); + m_iFileSize = ftell( fp ); + fseek( fp, 0, SEEK_SET ); + + buffer = malloc( m_iFileSize ); + if (!buffer) + { + m_iFileSize = 0; + fclose (fp); + return 0; + } + + fread( buffer, m_iFileSize, 1, fp ); + fclose( fp ); + + dsprite_q1_t *pinq1; + dsprite_hl_t *pinhl; + dsprite_t *pin; + short *numi = NULL; + dframetype_t *pframetype; + msprite_t *psprite; + int i, size; + + pin = (dsprite_t *)buffer; + + if (strncmp ((const char *) buffer, "IDSP", 4 )) + { + mxMessageBox( g_GlWindow, "Unknown file format.", g_appTitle, MX_MB_OK | MX_MB_ERROR ); + m_iFileSize = 0; + free (buffer); + return 0; + } + + if( pin->version != SPRITE_VERSION_Q1 && pin->version != SPRITE_VERSION_HL ) + { + mxMessageBox( g_GlWindow, "Unsupported sprite version.", g_appTitle, MX_MB_OK | MX_MB_ERROR ); + m_iFileSize = 0; + free (buffer); + return 0; + } + + if( pin->version == SPRITE_VERSION_Q1 ) + { + pinq1 = (dsprite_q1_t *)buffer; + size = sizeof( msprite_t ) + ( pinq1->numframes - 1 ) * sizeof( psprite->frames ); + psprite = (msprite_t *)calloc( 1, size ); + m_pspritehdr = psprite; // make link to extradata + + psprite->type = pinq1->type; + psprite->texFormat = SPR_ALPHTEST; + psprite->numframes = pinq1->numframes; + psprite->facecull = SPR_CULL_FRONT; + psprite->radius = pinq1->boundingradius; + psprite->synctype = pinq1->synctype; + + m_spritemins[0] = m_spritemins[1] = -pinq1->bounds[0] * 0.5f; + m_spritemaxs[0] = m_spritemaxs[1] = pinq1->bounds[0] * 0.5f; + m_spritemins[2] = -pinq1->bounds[1] * 0.5f; + m_spritemaxs[2] = pinq1->bounds[1] * 0.5f; + numi = NULL; + } + else if( pin->version == SPRITE_VERSION_HL ) + { + pinhl = (dsprite_hl_t *)buffer; + size = sizeof( msprite_t ) + ( pinhl->numframes - 1 ) * sizeof( psprite->frames ); + psprite = (msprite_t *)calloc( 1, size ); + m_pspritehdr = psprite; // make link to extradata + + psprite->type = pinhl->type; + psprite->texFormat = pinhl->texFormat; + psprite->numframes = pinhl->numframes; + psprite->facecull = pinhl->facetype; + psprite->radius = pinhl->boundingradius; + psprite->synctype = pinhl->synctype; + + m_spritemins[0] = m_spritemins[1] = -pinhl->bounds[0] * 0.5f; + m_spritemaxs[0] = m_spritemaxs[1] = pinhl->bounds[0] * 0.5f; + m_spritemins[2] = -pinhl->bounds[1] * 0.5f; + m_spritemaxs[2] = pinhl->bounds[1] * 0.5f; + numi = (short *)(pinhl + 1); + } + + // last color are transparent + m_palette[255*4+0] = m_palette[255*4+1] = m_palette[255*4+2] = m_palette[255*4+3] = 0; + m_loadframe = 0; + + if( numi == NULL ) + { + for( i = 0; i < 255; i++ ) + { + m_palette[i*4+0] = palette_q1[i*3+0]; + m_palette[i*4+1] = palette_q1[i*3+1]; + m_palette[i*4+2] = palette_q1[i*3+2]; + m_palette[i*4+3] = 0xFF; + } + pframetype = (dframetype_t *)(pinq1 + 1); + } + else if( *numi == 256 ) + { + byte *pal = (byte *)(numi+1); + + // install palette + switch( psprite->texFormat ) + { + case SPR_INDEXALPHA: + for( i = 0; i < 256; i++ ) + { + m_palette[i*4+0] = pal[765]; + m_palette[i*4+1] = pal[766]; + m_palette[i*4+2] = pal[767]; + m_palette[i*4+3] = i; + } + break; + case SPR_ALPHTEST: + for( i = 0; i < 255; i++ ) + { + m_palette[i*4+0] = pal[i*3+0]; + m_palette[i*4+1] = pal[i*3+1]; + m_palette[i*4+2] = pal[i*3+2]; + m_palette[i*4+3] = 0xFF; + } + break; + default: + for( i = 0; i < 256; i++ ) + { + m_palette[i*4+0] = pal[i*3+0]; + m_palette[i*4+1] = pal[i*3+1]; + m_palette[i*4+2] = pal[i*3+2]; + m_palette[i*4+3] = 0xFF; + } + break; + } + + pframetype = (dframetype_t *)(pal + 768); + } + else + { + mxMessageBox( g_GlWindow, "sprite has wrong number of palette colors.\n", g_appTitle, MX_MB_OK | MX_MB_ERROR ); + free( m_pspritehdr ); + m_iFileSize = 0; + free (buffer); + return 0; + } + + if( m_pspritehdr->numframes > MAX_SPRITE_FRAMES ) + { + mxMessageBox( g_GlWindow, "sprite has too many frames.", g_appTitle, MX_MB_OK | MX_MB_ERROR ); + free( m_pspritehdr ); + m_iFileSize = 0; + free (buffer); + return 0; + } + + Q_strncpy (g_viewerSettings.spriteFile, spritename, sizeof( g_viewerSettings.spriteFile )); + Q_strncpy( g_viewerSettings.spritePath, spritename, sizeof( g_viewerSettings.spritePath )); + + for( i = 0; i < m_pspritehdr->numframes; i++ ) + { + frametype_t frametype = pframetype->type; + psprite->frames[i].type = frametype; + + switch( frametype ) + { + case FRAME_SINGLE: + pframetype = LoadSpriteFrame( pframetype + 1, &psprite->frames[i].frameptr ); + break; + case FRAME_GROUP: + pframetype = LoadSpriteGroup( pframetype + 1, &psprite->frames[i].frameptr ); + break; + case FRAME_ANGLED: + pframetype = LoadSpriteGroup( pframetype + 1, &psprite->frames[i].frameptr ); + break; + } + if( pframetype == NULL ) break; // technically an error + } + + char basename[64]; + + COM_FileBase( spritename, basename ); + + if( !Q_strnicmp( basename, "v_", 2 )) + { + g_SPRViewer->checkboxSet( IDC_OPTIONS_WEAPONORIGIN, true ); + bUseWeaponOrigin = true; + } + else + { + g_SPRViewer->checkboxSet( IDC_OPTIONS_WEAPONORIGIN, false ); + bUseWeaponOrigin = false; + } + + // reset all the changes + g_viewerSettings.numModelChanges = 0; + + // free buffer + m_iFileSize = 0; + free (buffer); + + return m_pspritehdr; +} + +void SpriteModel :: FreeSprite( void ) +{ + int i; + + if( g_viewerSettings.numModelChanges ) + { + if( !mxMessageBox( g_GlWindow, "Sprite has changes. Do you wish to save them?", g_appTitle, MX_MB_YESNO | MX_MB_QUESTION )) + { + char *ptr = (char *)mxGetSaveFileName( g_GlWindow , g_viewerSettings.spritePath, "*.spr", g_viewerSettings.spritePath ); + if( ptr ) + { + char filename[256]; + char ext[16]; + + strcpy( filename, ptr ); + strcpy( ext, mx_getextension( filename )); + if( mx_strcasecmp( ext, ".spr" )) + strcat( filename, ".spr" ); + + if( !SaveSprite( filename )) + mxMessageBox( g_GlWindow, "Error saving sprite.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + } + } + + g_viewerSettings.numModelChanges = 0; // all the settings are handled or invalidated + } + + if( m_pspritehdr ) + { + mspritegroup_t *pgroup; + + // deleting textures + int textures[MAX_SPRITE_FRAMES]; + for (i = 0; i < m_loadframe; i++) + textures[i] = g_tex_base + i; + + for( i = 0; i < m_pspritehdr->numframes; i++ ) + { + if( m_pspritehdr->frames[i].type != FRAME_SINGLE ) + { + pgroup = (mspritegroup_t *)m_pspritehdr->frames[i].frameptr; + free( pgroup->intervals ); // throw intervals + // throw frames + for( i = 0; i < pgroup->numframes; i++ ) + free( pgroup->frames[i] ); + free( pgroup ); // throw himself + } + else free( m_pspritehdr->frames[i].frameptr ); + } + + glDeleteTextures (m_loadframe, (const GLuint *)textures); + m_loadframe = 0; + } + + if (m_pspritehdr) + free (m_pspritehdr); + + m_frame = 0; + m_pspritehdr = 0; + m_iFileSize = 0; +} + +bool SpriteModel :: SaveSprite( const char *spritename ) +{ +// if (!spritename) + return false; + + if (!m_pspritehdr) + return false; + + FILE *file; + + file = fopen (spritename, "wb"); + if (!file) + return false; + + fwrite (m_pspritehdr, sizeof (byte), m_iFileSize, file); + fclose (file); + return true; +} + +void SpriteModel :: ExtractBbox( vec3_t &mins, vec3_t &maxs ) +{ + if( !m_pspritehdr ) + { + mins = g_vecZero; + maxs = g_vecZero; + return; + } + + mins = m_spritemins; + maxs = m_spritemaxs; +} + +void SpriteModel:: GetMovement( vec3_t &delta ) +{ + delta = g_vecZero; +} \ No newline at end of file diff --git a/utils/hlsv/sprviewer.cpp b/utils/hlsv/sprviewer.cpp new file mode 100644 index 0000000..2a201a5 --- /dev/null +++ b/utils/hlsv/sprviewer.cpp @@ -0,0 +1,829 @@ +// +// MDL Viewer (c) 1999 by Mete Ciragan +// +// file: md2viewer.cpp +// last modified: Apr 29 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +// version: 1.4 +// +// email: mete@swissquake.ch +// web: http://www.swissquake.ch/chumbalum-soft/ +// +#include +#include +//#include +#include +#include +#include +#include "sprviewer.h" +#include "GlWindow.h" +#include "pakviewer.h" +#include "FileAssociation.h" +#include "stringlib.h" +#include "SpriteModel.h" + +SPRViewer *g_SPRViewer = 0; +char g_appTitle[] = "Half_life Sprite Viewer v0.1 beta"; +static char recentFiles[8][256] = { "", "", "", "", "", "", "", "" }; +extern bool bUseWeaponOrigin; +bool g_bStopPlaying = false; +bool g_bEndOfSequence = false; +static int g_nCurrFrame = 0; + +void SPRViewer::initRecentFiles () +{ + for (int i = 0; i < 8; i++) + { + if (strlen (recentFiles[i])) + { + mb->modify (IDC_FILE_RECENTMODELS1 + i, IDC_FILE_RECENTMODELS1 + i, recentFiles[i]); + } + else + { + mb->modify (IDC_FILE_RECENTMODELS1 + i, IDC_FILE_RECENTMODELS1 + i, "(empty)"); + mb->setEnabled (IDC_FILE_RECENTMODELS1 + i, false); + } + } +} + +void SPRViewer::loadRecentFiles( void ) +{ + char str[256]; + + for( int i = 0; i < 8; i++ ) + { + mx_snprintf( str, sizeof( str ), "RecentFile%i", i ); + if( !LoadString( str, recentFiles[i] )) + break; + } +} + +void SPRViewer::saveRecentFiles( void ) +{ + char str[256]; + + if( !InitRegistry( )) + return; + + for( int i = 0; i < 8; i++ ) + { + mx_snprintf( str, sizeof( str ), "RecentFile%i", i ); + if( !SaveString( str, recentFiles[i] )) + break; + } +} + +SPRViewer :: SPRViewer() : mxWindow( 0, 0, 0, 0, 0, g_appTitle, mxWindow::Normal ) +{ + // create menu stuff + mb = new mxMenuBar (this); + mxMenu *menuFile = new mxMenu (); + mxMenu *menuOptions = new mxMenu (); + mxMenu *menuView = new mxMenu (); + mxMenu *menuHelp = new mxMenu (); + + mb->addMenu ("File", menuFile); + mb->addMenu ("Options", menuOptions); + mb->addMenu ("Tools", menuView); + mb->addMenu ("Help", menuHelp); + + mxMenu *menuRecentModels = new mxMenu (); + menuRecentModels->add ("(empty)", IDC_FILE_RECENTMODELS1); + menuRecentModels->add ("(empty)", IDC_FILE_RECENTMODELS2); + menuRecentModels->add ("(empty)", IDC_FILE_RECENTMODELS3); + menuRecentModels->add ("(empty)", IDC_FILE_RECENTMODELS4); + + mxMenu *menuRecentPakFiles = new mxMenu (); + menuRecentPakFiles->add ("(empty)", IDC_FILE_RECENTPAKFILES1); + menuRecentPakFiles->add ("(empty)", IDC_FILE_RECENTPAKFILES2); + menuRecentPakFiles->add ("(empty)", IDC_FILE_RECENTPAKFILES3); + menuRecentPakFiles->add ("(empty)", IDC_FILE_RECENTPAKFILES4); + + menuFile->add ("Load Sprite...", IDC_FILE_LOADMODEL); + menuFile->addSeparator (); + menuFile->add ("Load Background Texture...", IDC_FILE_LOADBACKGROUNDTEX); + menuFile->add ("Load Ground Texture...", IDC_FILE_LOADGROUNDTEX); + menuFile->addSeparator (); + menuFile->add ("Unload Ground Texture", IDC_FILE_UNLOADGROUNDTEX); + menuFile->addSeparator (); + menuFile->add ("Open PAK file...", IDC_FILE_OPENPAKFILE); + menuFile->add ("Close PAK file", IDC_FILE_CLOSEPAKFILE); + menuFile->addSeparator (); + menuFile->addMenu ("Recent Sprites", menuRecentModels); + menuFile->addMenu ("Recent PAK files", menuRecentPakFiles); + menuFile->addSeparator (); + menuFile->add ("Exit", IDC_FILE_EXIT); + + menuOptions->add ("Background Color...", IDC_OPTIONS_COLORBACKGROUND); + menuOptions->add ("Ground Color...", IDC_OPTIONS_COLORGROUND); + menuOptions->add ("Light Color...", IDC_OPTIONS_COLORLIGHT); + menuOptions->addSeparator (); + menuOptions->add( "Sprite AutoPlay", IDC_OPTIONS_AUTOPLAY ); + menuOptions->addSeparator (); + menuOptions->add( "Use weapon origin", IDC_OPTIONS_WEAPONORIGIN ); + menuOptions->addSeparator (); + menuOptions->add ("Center View", IDC_OPTIONS_CENTERVIEW); + menuOptions->add ("Reset View", IDC_OPTIONS_RESETVIEW); +#ifdef WIN32 + menuOptions->addSeparator (); + menuOptions->add ("Make Screenshot...", IDC_OPTIONS_MAKESCREENSHOT); + //menuOptions->add ("Dump Sprite Info", IDC_OPTIONS_DUMP); +#endif + menuView->add ("File Associations...", IDC_VIEW_FILEASSOCIATIONS); + +#ifdef WIN32 + menuHelp->add ("Goto Homepage...", IDC_HELP_GOTOHOMEPAGE); + menuHelp->addSeparator (); +#endif + menuHelp->add ("About", IDC_HELP_ABOUT); + + mb->setChecked( IDC_OPTIONS_WEAPONORIGIN, bUseWeaponOrigin ); + + // create tabcontrol with subdialog windows + tab = new mxTab (this, 0, 0, 0, 0, IDC_TAB); +#ifdef WIN32 + SetWindowLong ((HWND) tab->getHandle (), GWL_EXSTYLE, WS_EX_CLIENTEDGE); +#endif + mxWindow *wRender = new mxWindow (this, 0, 0, 0, 0); + tab->add (wRender, "Sprite Display"); + mxLabel *RenderLabel = new mxLabel (wRender, 5, 3, 120, 20, "Render Mode"); + cRenderMode = new mxChoice (wRender, 5, 17, 112, 22, IDC_RENDERMODE); + cRenderMode->add ("Normal"); + cRenderMode->add ("Additive"); + cRenderMode->add ("Indexalpha"); + cRenderMode->add ("Alpha test"); + cRenderMode->select (0); + mxToolTip::add (cRenderMode, "Select Render Mode"); + + mxLabel *OrientLabel = new mxLabel (wRender, 5, 43, 120, 20, "Orientation"); + cOrientType = new mxChoice (wRender, 5, 60, 112, 22, IDC_ORIENTATION); + cOrientType->add ("Parallel Upright"); + cOrientType->add ("Facing Upright"); + cOrientType->add ("Parallel"); + cOrientType->add ("Oriented"); + cOrientType->add ("Parallel Oriented"); + cOrientType->select (0); + mxToolTip::add (cOrientType, "Select Sprite Orientation"); + + cbGround = new mxCheckBox (wRender, 130, 64, 85, 20, "Show Ground", IDC_GROUND); + cbGround->setChecked( g_viewerSettings.showGround ? true : false ); + cbBackground = new mxCheckBox (wRender, 224, 64, 130, 20, "Show Background", IDC_BACKGROUND); + + lSpriteInfo1 = new mxLabel (wRender, 130, 5, 120, 15, "" ); + + // Create widgets for the Animation Tab + tbStop = new mxButton (wRender, 130, 22, 60, 18, "Stop", IDC_STOP); + mxToolTip::add (tbStop, "Stop Playing"); + bPrevFrame = new mxButton (wRender, 209, 22, 30, 18, "<<", IDC_PREVFRAME); + bPrevFrame->setEnabled (false); + mxToolTip::add (bPrevFrame, "Prev Frame"); + leFrame = new mxLineEdit (wRender, 244, 22, 50, 18, "", IDC_FRAME); + leFrame->setEnabled (false); + mxToolTip::add (leFrame, "Set Frame"); + bNextFrame = new mxButton (wRender, 299, 22, 30, 18, ">>", IDC_NEXTFRAME); + bNextFrame->setEnabled (false); + mxToolTip::add (bNextFrame, "Next Frame"); + + mxLabel *SpdLabel = new mxLabel (wRender, 295, 44, 35, 18, "Speed"); + slSpeedScale = new mxSlider (wRender, 125, 44, 165, 18, IDC_SPEEDSCALE); + slSpeedScale->setRange (0, 200); + slSpeedScale->setValue (40); + mxToolTip::add (slSpeedScale, "Speed Scale"); + + // create the OpenGL window + d_GlWindow = new GlWindow (this, 0, 0, 0, 0, "", mxWindow::Normal); +#ifdef WIN32 + SetWindowLong ((HWND) d_GlWindow->getHandle (), GWL_EXSTYLE, WS_EX_CLIENTEDGE); +#endif + g_GlWindow = d_GlWindow; + + // finally create the pakviewer window + pakViewer = new PAKViewer (this); + + g_FileAssociation = new FileAssociation (); + + loadRecentFiles (); + initRecentFiles (); + + setBounds (20, 20, 690, 550); + setVisible (true); + + if( g_viewerSettings.showGround ) + setShowGround (true); + + if( g_viewerSettings.groundTexFile[0] ) + d_GlWindow->loadTexture( g_viewerSettings.groundTexFile, TEXTURE_GROUND ); + else d_GlWindow->loadTexture( NULL, TEXTURE_GROUND ); +} + +SPRViewer::~SPRViewer () +{ + g_viewerSettings.showMaximized = isMaximized(); + saveRecentFiles (); + SaveViewerSettings (); +} + +void SPRViewer :: checkboxSet( int id, bool bState ) +{ + mb->setChecked( id, bState ); +} + +int +SPRViewer::handleEvent (mxEvent *event) +{ + if (event->event == mxEvent::Size) + { + int w = event->width; + int h = event->height; + int y = mb->getHeight (); +#ifdef WIN32 +#define HEIGHT 120 +#else +#define HEIGHT 140 + h -= 40; +#endif + + if (pakViewer->isVisible ()) + { + w -= 170; + pakViewer->setBounds (w, y, 170, h); + } + + d_GlWindow->setBounds (0, y, w, h - HEIGHT); + tab->setBounds (0, y + h - HEIGHT, w, HEIGHT); + return 1; + } + + if ( event->event == mxEvent::KeyDown ) + { + redraw(); + switch( (char)event->key ) + { + case 27: + if( !getParent( )) // fullscreen mode ? + mx::quit(); + break; + case 37: + if( g_viewerSettings.numSpritePathes > 0 ) + loadSprite( LoadPrevSprite( )); + break; + case 39: + if( g_viewerSettings.numSpritePathes > 0 ) + loadSprite( LoadNextSprite( )); + break; + case VK_F5: + { + bool oldUseWeaponOrigin = bUseWeaponOrigin; + loadSprite( g_viewerSettings.spriteFile, false ); + bUseWeaponOrigin = oldUseWeaponOrigin; + break; + } + case 'v': + case 'ì': + bUseWeaponOrigin = !mb->isChecked( IDC_OPTIONS_WEAPONORIGIN ); + mb->setChecked( IDC_OPTIONS_WEAPONORIGIN, bUseWeaponOrigin ); + break; + } + return 1; + } + + switch (event->action) + { + case IDC_FILE_LOADMODEL: + { + const char *ptr = mxGetOpenFileName (this, 0, "*.spr"); + if (ptr) + { + if (!loadSprite (ptr )) + { + char str[256]; + + sprintf (str, "Error reading sprite: %s", ptr); + mxMessageBox (this, str, "ERROR", MX_MB_OK | MX_MB_ERROR); + break; + } + + // now update recent files list + + int i; + char path[256]; + + if (event->action == IDC_FILE_LOADMODEL) + strcpy (path, "[m] "); + else + strcpy (path, "[w] "); + + strcat (path, ptr); + + for (i = 0; i < 4; i++) + { + if (!mx_strcasecmp (recentFiles[i], path)) + break; + } + + // swap existing recent file + if (i < 4) + { + char tmp[256]; + strcpy (tmp, recentFiles[0]); + strcpy (recentFiles[0], recentFiles[i]); + strcpy (recentFiles[i], tmp); + } + + // insert recent file + else + { + for (i = 3; i > 0; i--) + strcpy (recentFiles[i], recentFiles[i - 1]); + + strcpy (recentFiles[0], path); + } + + initRecentFiles (); + } + } + break; + case IDC_FILE_SAVEMODEL: + { + char *ptr = (char *) mxGetSaveFileName (this, g_viewerSettings.spritePath, "*.spr", g_viewerSettings.spritePath); + if (!ptr) + break; + + char filename[256]; + char ext[16]; + + strcpy( filename, ptr ); + strcpy( ext, mx_getextension( filename )); + if( mx_strcasecmp( ext, ".spr" )) + strcat( filename, ".spr" ); + + if( !g_spriteModel.SaveSprite( filename )) + { + mxMessageBox( this, "Error saving sprite.", g_appTitle, MX_MB_OK | MX_MB_ERROR ); + } + else + { + strcpy( g_viewerSettings.spriteFile, filename ); + g_viewerSettings.numModelChanges = 0; // all the settings are handled + } + } + break; + case IDC_FILE_LOADBACKGROUNDTEX: + case IDC_FILE_LOADGROUNDTEX: + { + const char *ptr = mxGetOpenFileName (this, 0, "*.bmp;*.tga;*.pcx"); + if (ptr) + { + int name = TEXTURE_UNUSED; + + if( event->action == IDC_FILE_LOADBACKGROUNDTEX ) + name = TEXTURE_BACKGROUND; + else if( event->action == IDC_FILE_LOADGROUNDTEX ) + name = TEXTURE_GROUND; + + if (d_GlWindow->loadTexture( ptr, name )) + { + if (event->action == IDC_FILE_LOADBACKGROUNDTEX) + setShowBackground (true); + else + setShowGround (true); + + } + else + mxMessageBox (this, "Error loading texture.", g_appTitle, MX_MB_OK | MX_MB_ERROR); + } + } + break; + + case IDC_FILE_UNLOADGROUNDTEX: + { + d_GlWindow->loadTexture( NULL, TEXTURE_GROUND ); + setShowGround (false); + } + break; + case IDC_FILE_OPENPAKFILE: + { + const char *ptr = mxGetOpenFileName (this, "\\Quake\\id1\\", "*.pak"); + if (ptr) + { + int i; + + pakViewer->openPAKFile (ptr); + + for (i = 4; i < 8; i++) + { + if (!mx_strcasecmp (recentFiles[i], ptr)) + break; + } + + // swap existing recent file + if (i < 8) + { + char tmp[256]; + strcpy (tmp, recentFiles[4]); + strcpy (recentFiles[4], recentFiles[i]); + strcpy (recentFiles[i], tmp); + } + + // insert recent file + else + { + for (i = 7; i > 4; i--) + strcpy (recentFiles[i], recentFiles[i - 1]); + + strcpy (recentFiles[4], ptr); + } + + initRecentFiles (); + + redraw (); + } + } + break; + + case IDC_FILE_CLOSEPAKFILE: + { + pakViewer->closePAKFile (); + redraw (); + } + break; + + case IDC_FILE_RECENTMODELS1: + case IDC_FILE_RECENTMODELS2: + case IDC_FILE_RECENTMODELS3: + case IDC_FILE_RECENTMODELS4: + { + int i = event->action - IDC_FILE_RECENTMODELS1; + char *ptr = &recentFiles[i][4]; + + if (!loadSprite( ptr )) + { + char str[256]; + + sprintf (str, "Error reading sprite: %s", ptr); + mxMessageBox (this, str, "ERROR", MX_MB_OK | MX_MB_ERROR); + break; + } + + // update recent sprite list + + char tmp[256]; + strcpy (tmp, recentFiles[0]); + strcpy (recentFiles[0], recentFiles[i]); + strcpy (recentFiles[i], tmp); + + initRecentFiles (); + } + break; + + case IDC_FILE_RECENTPAKFILES1: + case IDC_FILE_RECENTPAKFILES2: + case IDC_FILE_RECENTPAKFILES3: + case IDC_FILE_RECENTPAKFILES4: + { + int i = event->action - IDC_FILE_RECENTPAKFILES1 + 4; + pakViewer->openPAKFile (recentFiles[i]); + + char tmp[256]; + strcpy (tmp, recentFiles[4]); + strcpy (recentFiles[4], recentFiles[i]); + strcpy (recentFiles[i], tmp); + + initRecentFiles (); + + redraw (); + } + break; + + case IDC_FILE_EXIT: + pakViewer->closePAKFile (); + redraw (); + mx::quit (); + break; + + case IDC_OPTIONS_COLORBACKGROUND: + case IDC_OPTIONS_COLORGROUND: + case IDC_OPTIONS_COLORLIGHT: + { + float *cols[3] = { g_viewerSettings.bgColor, g_viewerSettings.gColor, g_viewerSettings.lColor }; + float *col = cols[event->action - IDC_OPTIONS_COLORBACKGROUND]; + int r = (int) (col[0] * 255.0f); + int g = (int) (col[1] * 255.0f); + int b = (int) (col[2] * 255.0f); + if (mxChooseColor (this, &r, &g, &b)) + { + col[0] = (float) r / 255.0f; + col[1] = (float) g / 255.0f; + col[2] = (float) b / 255.0f; + } + } + break; +#ifdef WIN32 + case IDC_OPTIONS_MAKESCREENSHOT: + { + char *ptr = (char *)mxGetSaveFileName (this, 0, "*.bmp"); + if (ptr) + { + if( !strstr( ptr, ".bmp" )) + strcat( ptr, ".bmp" ); + makeScreenShot (ptr); + } + } + break; +#endif + case IDC_OPTIONS_WEAPONORIGIN: + bUseWeaponOrigin = !mb->isChecked( IDC_OPTIONS_WEAPONORIGIN ); + mb->setChecked( IDC_OPTIONS_WEAPONORIGIN, bUseWeaponOrigin ); + break; + + case IDC_OPTIONS_AUTOPLAY: + g_viewerSettings.sequence_autoplay = !mb->isChecked( IDC_OPTIONS_AUTOPLAY ); + mb->setChecked( IDC_OPTIONS_AUTOPLAY, g_viewerSettings.sequence_autoplay ? true : false ); + break; + + case IDC_OPTIONS_CENTERVIEW: + { + centerSprite (); + d_GlWindow->redraw (); + } + break; + case IDC_VIEW_FILEASSOCIATIONS: + g_FileAssociation->setAssociation (0); + g_FileAssociation->setVisible (true); + break; + +#ifdef WIN32 + case IDC_HELP_GOTOHOMEPAGE: + ShellExecute (0, "open", "http://cs-mapping.com.ua/forum/forumdisplay.php?f=189", 0, 0, SW_SHOW); + break; +#endif + + case IDC_HELP_ABOUT: + mxMessageBox (this, + "Half-Life Sprite Viewer v0.1 beta (c) 2018 by Unkle Mike\n" + "Based on original MD2V code by Mete Ciragan\n\n" + "Left-drag to rotate.\n" + "Right-drag to zoom.\n" + "Shift-left-drag to x-y-pan.\n" + "Ctrl-drag to move lights.\n\n" + "Build:\t" __DATE__ ".\n" + "Email:\tg-cont@rambler.ru\n" + "Web:\thttp://www.hlfx.ru/forum\n\thttp://cs-mapping.com.ua/forum", "Half-Life Sprite Viewer", + MX_MB_OK | MX_MB_INFORMATION ); + break; + + // + // widget actions + // + // + + // + // Sprite Panel + // + + case IDC_RENDERMODE: + { + int index = cRenderMode->getSelectedIndex (); + if (index >= 0) + { + setRenderMode (index); + } + } + break; + + case IDC_ORIENTATION: + { + int index = cOrientType->getSelectedIndex (); + if (index >= 0) + { + setOrientType (index); + } + } + break; + + case IDC_GROUND: + setShowGround (((mxCheckBox *) event->widget)->isChecked ()); + break; + + case IDC_BACKGROUND: + setShowBackground (((mxCheckBox *) event->widget)->isChecked ()); + break; + + case IDC_SPEEDSCALE: + { + int v = ((mxSlider *)event->widget)->getValue (); + g_viewerSettings.speedScale = (float) (v * 5) / 200.0f; + } + break; + + case IDC_STOP: + { + if( !g_bStopPlaying ) + { + tbStop->setLabel ("Play"); + g_bStopPlaying = true; + g_nCurrFrame = g_spriteModel.setFrame( -1 ); + leFrame->setLabel ("%d", g_nCurrFrame); + bPrevFrame->setEnabled (true); + leFrame->setEnabled (true); + bNextFrame->setEnabled (true); + } + else + { + tbStop->setLabel ("Stop"); + g_bStopPlaying = false; + if( g_bEndOfSequence ) + g_nCurrFrame = g_spriteModel.setFrame( 0 ); + bPrevFrame->setEnabled (false); + leFrame->setEnabled (false); + bNextFrame->setEnabled (false); + g_bEndOfSequence = false; + } + } + break; + + case IDC_PREVFRAME: + { + g_nCurrFrame = g_spriteModel.setFrame( g_nCurrFrame - 1 ); + leFrame->setLabel ("%d", g_nCurrFrame); + g_bEndOfSequence = false; + } + break; + + case IDC_FRAME: + { + g_nCurrFrame = atoi (leFrame->getLabel ()); + g_nCurrFrame = g_spriteModel.setFrame( g_nCurrFrame ); + g_bEndOfSequence = false; + } + break; + + case IDC_NEXTFRAME: + { + g_nCurrFrame = g_spriteModel.setFrame( g_nCurrFrame + 1 ); + leFrame->setLabel( "%d", g_nCurrFrame ); + g_bEndOfSequence = false; + } + break; + } + + return 1; +} + + +void SPRViewer :: redraw( void ) +{ + mxEvent event; + event.event = mxEvent::Size; + event.width = w2 (); + event.height = h2 (); + handleEvent (&event); +} + +void SPRViewer :: makeScreenShot( const char *filename ) +{ +#ifdef WIN32 + d_GlWindow->redraw (); + int w = d_GlWindow->w2 (); + int h = d_GlWindow->h2 (); + + mxImage *image = new mxImage (); + if( image->create( w, h, 24 )) + { + glReadBuffer( GL_FRONT ); + glReadPixels( 0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, image->data ); + + image->flip_vertical(); + + if( !mxBmpWrite( filename, image )) + mxMessageBox( this, "Error writing screenshot.", g_appTitle, MX_MB_OK|MX_MB_ERROR ); + + delete image; + } +#endif +} + +int SPRViewer::getTableIndex() +{ + return tab->getSelectedIndex (); +} + +void SPRViewer :: setRenderMode( int mode ) +{ + if (mode >= 0) + { + cRenderMode->select (mode); + d_GlWindow->setRenderMode (mode); + } +} + +void SPRViewer :: setOrientType( int mode ) +{ + if (mode >= 0) + { + cOrientType->select (mode); + d_GlWindow->setOrientType (mode); + } +} + +void SPRViewer :: setShowGround( bool b ) +{ + g_viewerSettings.showGround = b; + cbGround->setChecked (b); +} + +void SPRViewer :: setShowBackground( bool b ) +{ + g_viewerSettings.showBackground = b; + cbBackground->setChecked (b); +} + +void SPRViewer::centerSprite () +{ + g_spriteModel.centerView( false ); +} + +bool SPRViewer::loadSprite( const char *ptr, bool centering ) +{ + if( !d_GlWindow->loadSprite( ptr, centering )) + return false; + + char path[256]; + strcpy (path, mx_getpath (ptr)); + mx_setcwd (path); + + setSpriteInfo(); + centerSprite(); + d_GlWindow->redraw (); + + return true; +} + +void SPRViewer::setSpriteInfo (void) +{ + static char str[1024]; + + msprite_t *phdr = g_spriteModel.getSpriteHeader(); + + if (phdr) + { + sprintf( str, "Frames: %d\n", phdr->numframes ); + setRenderMode( phdr->texFormat ); + setOrientType( phdr->type ); + } + else + { + strcpy (str, "No Sprite."); + } + + lSpriteInfo1->setLabel (str); + d_GlWindow->setFrameInfo( 0, MAX_SPRITE_FRAMES ); +} + +int main( int argc, char *argv[] ) +{ + // + // make sure, we start in the right directory + // + mx_setcwd (mx::getApplicationPath ()); + + char cmdline[1024] = ""; + if (argc > 1) + { + strcpy (cmdline, argv[1]); + for (int i = 2; i < argc; i++) + { + strcat (cmdline, " "); + strcat (cmdline, argv[i]); + } + } + + LoadViewerSettings(); + + mx::init (argc, argv); + + g_SPRViewer = new SPRViewer (); + g_SPRViewer->setMenuBar (g_SPRViewer->getMenuBar ()); + g_SPRViewer->setBounds (20, 20, 640, 540); + g_SPRViewer->setVisible (true); + + if( g_viewerSettings.showMaximized ) + g_SPRViewer->Maximize(); + + if (Q_stristr (cmdline, ".spr")) + { + g_SPRViewer->loadSprite (cmdline); + } + + int ret = mx::run (); + + mx::cleanup (); + + return ret; +} diff --git a/utils/hlsv/sprviewer.h b/utils/hlsv/sprviewer.h new file mode 100644 index 0000000..de26754 --- /dev/null +++ b/utils/hlsv/sprviewer.h @@ -0,0 +1,137 @@ +// +// MDL Viewer (c) 1999 by Mete Ciragan +// +// file: md2viewer.h +// last modified: Apr 29 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +// version: 1.4 +// +// email: mete@swissquake.ch +// web: http://www.swissquake.ch/chumbalum-soft/ +// +#ifndef INCLUDED_SPRVIEWER +#define INCLUDED_SPRVIEWER + + + +#ifndef INCLUDED_MXWINDOW +#include +#endif + +#define IDC_FILE_LOADMODEL 1001 +#define IDC_FILE_SAVEMODEL 1002 +#define IDC_FILE_LOADBACKGROUNDTEX 1003 +#define IDC_FILE_LOADGROUNDTEX 1004 +#define IDC_FILE_UNLOADGROUNDTEX 1005 +#define IDC_FILE_OPENPAKFILE 1006 +#define IDC_FILE_OPENPAKFILE2 1007 +#define IDC_FILE_CLOSEPAKFILE 1008 +#define IDC_FILE_RECENTMODELS1 1009 +#define IDC_FILE_RECENTMODELS2 1010 +#define IDC_FILE_RECENTMODELS3 1011 +#define IDC_FILE_RECENTMODELS4 1012 +#define IDC_FILE_RECENTPAKFILES1 1013 +#define IDC_FILE_RECENTPAKFILES2 1014 +#define IDC_FILE_RECENTPAKFILES3 1015 +#define IDC_FILE_RECENTPAKFILES4 1016 +#define IDC_FILE_EXIT 1017 + +#define IDC_OPTIONS_COLORBACKGROUND 1101 +#define IDC_OPTIONS_COLORGROUND 1102 +#define IDC_OPTIONS_COLORLIGHT 1103 +#define IDC_OPTIONS_CENTERVIEW 1104 +#define IDC_OPTIONS_RESETVIEW 1105 +#define IDC_OPTIONS_MAKESCREENSHOT 1106 +#define IDC_OPTIONS_WEAPONORIGIN 1107 +#define IDC_OPTIONS_AUTOPLAY 1109 +#define IDC_OPTIONS_DUMP 1110 + +#define IDC_VIEW_FILEASSOCIATIONS 1201 + +#define IDC_HELP_GOTOHOMEPAGE 1301 +#define IDC_HELP_ABOUT 1302 + +// control panel +#define IDC_TAB 1901 +#define IDC_RENDERMODE 2001 +#define IDC_ORIENTATION 2002 +#define IDC_GROUND 2003 +#define IDC_BACKGROUND 2005 + +#define IDC_ANIMATION 5001 +#define IDC_SPEEDSCALE 5002 +#define IDC_STOP 5003 +#define IDC_PREVFRAME 5004 +#define IDC_FRAME 5005 +#define IDC_NEXTFRAME 5006 + +class mxTab; +class mxMenuBar; +class mxButton; +class mxLineEdit; +class mxLabel; +class mxChoice; +class mxCheckBox; +class mxSlider; +class GlWindow; +class PAKViewer; + +class SPRViewer : public mxWindow +{ + mxMenuBar *mb; + mxTab *tab; + GlWindow *d_GlWindow; + mxChoice *cRenderMode; + mxChoice *cOrientType; + mxCheckBox *cbGround, *cbBackground; + + mxButton *tbStop; + mxButton *bPrevFrame, *bNextFrame; + mxLineEdit *leFrame; + + mxLabel *lSpriteInfo1; + mxSlider *slSpeedScale; + PAKViewer *pakViewer; + + void loadRecentFiles (); + void saveRecentFiles (); + void initRecentFiles (); + + void setSpriteInfo (); +public: + friend PAKViewer; + + // CREATORS + SPRViewer (); + ~SPRViewer (); + + // MANIPULATORS + virtual int handleEvent (mxEvent *event); + void checkboxSet( int id, bool bState ); + void redraw (); + void makeScreenShot (const char *filename); + void setRenderMode (int index); + void setOrientType (int index); + void setShowGround (bool b); + void setShowBackground (bool b); + int getTableIndex(); + bool loadSprite( const char *ptr, bool centering = true ); + void centerSprite (); + + // ACCESSORS + mxMenuBar *getMenuBar () const { return mb; } +}; + + + +extern SPRViewer *g_SPRViewer; +extern char g_appTitle[]; + + +#endif // INCLUDED_SPRVIEWER \ No newline at end of file diff --git a/utils/makefont/makefont.cpp b/utils/makefont/makefont.cpp new file mode 100644 index 0000000..697c4a2 --- /dev/null +++ b/utils/makefont/makefont.cpp @@ -0,0 +1,465 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEFAULT_FONT "Arial" +#define QCHAR_WIDTH 16 // font width + +bool bItalic = false; +bool bBold = false; +bool bUnderline = false; +bool bRussian = true; +bool bMonotic = false; +char fontname[256]; +char destfile[256]; +int pointsize[3] = { 9, 11, 15 }; + +byte char_table_ansi[96] = +{ +32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, +66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, +100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127 +}; + +byte char_table_rus[164] = +{ +32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, +66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, +100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, +127, 168, 169, 174, 184, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, +214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, +241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 +}; + +/* +================= +Draw_SetupConsolePalette + +Set's the palette to full brightness ( 192 ) and +set's up palette entry 0 -- black +================= +*/ +void Draw_SetupConsolePalette( byte *pal ) +{ + byte *pPalette = pal; + + *(short *)pPalette = 256 * 3; + pPalette += sizeof( short ); + + // gradient palette + for( int i = 0; i < 256; i++ ) + { + pPalette[i*3+0] = i; + pPalette[i*3+1] = i; + pPalette[i*3+2] = i; + } + + // set palette zero correctly + pPalette[0] = 0; + pPalette[1] = 0; + pPalette[2] = 0; +} + +/* +================= +CreateConsoleFont + +Renders TT font into memory dc and creates appropriate qfont_t structure +================= +*/ + +// YWB: Sigh, VC 6.0's global optimizer causes weird stack fixups in release builds. Disable the globabl optimizer for this function. +// g-cont. At 2018 year this problem is actual! +#pragma optimize( "g", off ) +qfont_t *CreateConsoleFont( const char *pszFont, int nPointSize, bool bItalic, bool bUnderline, bool bBold, bool bRussian, int *outsize ) +{ + HDC hdc, hmemDC; + HBITMAP hbm, oldbm; + HFONT fnt, oldfnt; + int startchar = 32; + int c, i, j, x, y; + BITMAPINFO tempbmi, *pbmi; + BITMAPINFOHEADER *pbmheader; + byte *pqdata, *pCur; + int x1, y1, nScans; + byte *pPalette, *bits; + qfont_t *pqf = NULL; + int symbols = (bRussian) ? sizeof( char_table_rus ) : sizeof( char_table_ansi ); + byte *char_table = (bRussian) ? char_table_rus : char_table_ansi; + int fullsize; + int w = QCHAR_WIDTH; + int h = symbols / QCHAR_WIDTH; // !!! + int charheight = nPointSize + 5; // add some padding + int fontheight = h * charheight; + int charwidth = QCHAR_WIDTH; + RECT rc, rcChar; + + // Create the font + fnt = CreateFont( -nPointSize, 0, 0, 0, bBold ? FW_HEAVY : FW_MEDIUM, bItalic, bUnderline, 0, RUSSIAN_CHARSET, OUT_TT_PRECIS, + CLIP_DEFAULT_PRECIS, PROOF_QUALITY, VARIABLE_PITCH | FF_DONTCARE, pszFont ); + + bits = NULL; + + // last sixty four bytes - what the hell ???? + fullsize = sizeof( qfont_t ) + ( fontheight * w * charwidth ) + sizeof( short ) + 768 + 64; + + // store off final size + *outsize = fullsize; + + Msg( "%s ( 256 x %d )\n", pszFont, fontheight ); + + pqf = ( qfont_t * )Mem_Alloc( fullsize ); + pqdata = (byte *)pqf + sizeof( qfont_t ); + pPalette = pqdata + ( fontheight * w * charwidth ); + + // configure palette + Draw_SetupConsolePalette( pPalette ); + + hdc = GetDC( NULL ); + hmemDC = CreateCompatibleDC( hdc ); + rc.top = 0; + rc.left = 0; + rc.right = charwidth * w; + rc.bottom = charheight * h; + + hbm = CreateBitmap( charwidth * w, charheight * h, 1, 1, NULL ); + oldbm = (HBITMAP)SelectObject( hmemDC, hbm ); + oldfnt = (HFONT)SelectObject( hmemDC, fnt ); + SetTextColor( hmemDC, 0x00ffffff ); + SetBkMode( hmemDC, TRANSPARENT ); + + // Paint black background + FillRect( hmemDC, &rc, (HBRUSH)GetStockObject( BLACK_BRUSH )); + + // draw character set into memory DC + for( j = 0; j < h; j++ ) + { + for( i = 0; i < w; i++ ) + { + x = i * charwidth; + y = j * charheight; + + c = char_table[j * w + i]; + + // Only draw printable characters, of course + if( !Q_isspace( c )) + { + // draw it. + rcChar.left = x + 1; + rcChar.top = y + 1; + rcChar.right = x + charwidth - 1; + rcChar.bottom = y + charheight - 1; + DrawText( hmemDC, (char *)&c, 1, &rcChar, DT_NOPREFIX|DT_LEFT ); + } + } + } + + // now turn the qfont into raw format + memset( &tempbmi, 0, sizeof( BITMAPINFO )); + pbmheader = ( BITMAPINFOHEADER * )&tempbmi; + + pbmheader->biSize = sizeof( BITMAPINFOHEADER ); + pbmheader->biWidth = w * charwidth; + pbmheader->biHeight = -h * charheight; + pbmheader->biPlanes = 1; + pbmheader->biBitCount = 1; + pbmheader->biCompression = BI_RGB; + + // find out how big the bitmap is + nScans = GetDIBits( hmemDC, hbm, 0, h * charheight, NULL, &tempbmi, DIB_RGB_COLORS ); + + // Allocate space for all bits + pbmi = (BITMAPINFO *)Mem_Alloc( sizeof ( BITMAPINFOHEADER ) + 2 * sizeof( RGBQUAD ) + pbmheader->biSizeImage ); + memcpy( pbmi, &tempbmi, sizeof( BITMAPINFO )); + bits = (byte *)pbmi + sizeof( BITMAPINFOHEADER ) + 2 * sizeof( RGBQUAD ); + + // now read in bits + nScans = GetDIBits( hmemDC, hbm, 0, h * charheight, bits, pbmi, DIB_RGB_COLORS ); + + if( nScans > 0 ) + { + // Now convert to proper raw format + // + // now get results from dib + pqf->height = fontheight; + pqf->width = charwidth; + pqf->rowheight = charheight; + pqf->rowcount = h; + pCur = pqdata; + + // Set everything to index 255 ( 0xff ) == transparent + memset( pCur, 0xFF, w * charwidth * pqf->height ); + + for( j = 0; j < h; j++ ) + { + for ( i = 0; i < w; i++ ) + { + int edge = 1; + int bestwidth; + x = i * charwidth; + y = j * charheight; + + c = (byte)char_table[j * w + i]; + int startoffset = y * w * charwidth + x; + + if( startoffset > 0xFFFF ) + { + COM_FatalError( "\"%s\" with pointsize %d is greater than 64K ( %s )\n", + pszFont, nPointSize, Q_memprint( startoffset )); + } + + pqf->fontinfo[c].charwidth = charwidth; + pqf->fontinfo[c].startoffset = startoffset; + bestwidth = 0; + + // In this first pass, place the black drop shadow so characters draw ok in the engine against + // most backgrounds. + // YWB: FIXME, apply a box filter and enable blending? + // Make it all transparent for starters + for( y1 = 0; y1 < charheight; y1++ ) + { + for( x1 = 0; x1 < charwidth; x1++ ) + { + int offset = ( y + y1 ) * w * charwidth + x + x1; + // dest + pCur = pqdata + offset; + // assume transparent + pCur[0] = 255; + } + } + + // put black pixels below and to the right of each pixel + for( y1 = edge; y1 < charheight - edge; y1++ ) + { + for( x1 = 0; x1 < charwidth; x1++ ) + { + int offset; + int srcoffset; + int xx0, yy0; + + offset = ( y + y1 ) * w * charwidth + x + x1; + + // Dest + pCur = pqdata + offset; + + for( xx0 = -edge; xx0 <= edge; xx0++ ) + { + for( yy0 = -edge; yy0 <= edge; yy0++ ) + { + srcoffset = ( y + y1 + yy0 ) * w * charwidth + x + x1 + xx0; + + if( bits[ srcoffset >> 3 ] & ( 1 << ( 7 - srcoffset & 7 ))) + { + // near black + pCur[0] = 32; + } + } + } + } + } + + // now copy in the actual font pixels + for( y1 = 0; y1 < charheight; y1++ ) + { + for( x1 = 0; x1 < charwidth; x1++ ) + { + int offset = ( y + y1 ) * w * charwidth + x + x1; + + // dest + pCur = pqdata + offset; + + if( bits[ offset >> 3 ] & ( 1 << ( 7 - offset & 7 ))) + { + if( x1 > bestwidth ) + { + bestwidth = x1; + } + + // Full color + // FIXME: Enable true palette support in engine? + pCur[0] = 192; + } + } + } + + // Space character width + if( c == 32 ) + { + bestwidth = 8; + } + else + { + // small characters needs can be padded a bit so they don't run into each other + if( bestwidth <= 14 ) + bestwidth += 2; + } + + // Store off width + if( !bMonotic ) pqf->fontinfo[c].charwidth = bestwidth; + else pqf->fontinfo[c].charwidth = min( nPointSize - 2, charwidth ); + } + } + } + + // free memory bits + Mem_Free( pbmi ); + SelectObject( hmemDC, oldfnt ); + DeleteObject( fnt ); + SelectObject( hmemDC, oldbm ); + DeleteObject( hbm ); + DeleteDC( hmemDC ); + ReleaseDC( NULL, hdc ); + + return pqf; +} +#pragma optimize( "g", on ) + +/* +================= +main + +================= +*/ +int main( int argc, char* argv[] ) +{ + bool srcset = false; + double start, end; + int outsize[3]; + wfile_t *fonts_wad; + qfont_t *fonts[3]; + char str[64]; + int i; + + Q_strncpy( fontname, DEFAULT_FONT, sizeof( fontname )); + Q_strncpy( destfile, "fonts.wad", sizeof( destfile )); + + Msg( " P2:Savior Font Generator\n" ); + Msg( " XashXT Group 2018(^1c^7)\n\n\n" ); + + for( i = 1; i < argc; i++ ) + { + if( !Q_strcmp( argv[i], "-font" )) + { + Q_strncpy( fontname, argv[i+1], sizeof( fontname )); + i++; + } + else if( !Q_strcmp( argv[i], "-pointsizes" )) + { + if( i + 3 >= argc ) + COM_FatalError( "insufficient point sizes specified\n" ); + pointsize[0] = atoi( argv[i+1] ); + pointsize[1] = atoi( argv[i+2] ); + pointsize[2] = atoi( argv[i+3] ); + i += 3; + } + else if( !Q_strcmp( argv[i], "-italic" )) + { + bItalic = true; + } + else if( !Q_strcmp( argv[i], "-bold" )) + { + bBold = true; + } + else if( !Q_strcmp( argv[i], "-underline" )) + { + bUnderline = true; + } + else if( !Q_strcmp( argv[i], "-ansi" )) + { + bRussian = false; + } + else if( !Q_strcmp( argv[i], "-monotic" )) + { + bMonotic = true; + } + else if( !srcset ) + { + Q_strncpy( destfile, argv[i], sizeof( destfile )); + srcset = true; + } + else + { + Msg( "makefont: unknown option %s\n", argv[i] ); + break; + } + } + + if( i != argc ) + { + Msg( "Usage: makefont.exe -font \"fontname\" \n" + "\nlist options:\n" + "^2-italic^7 - make italic style\n" + "^2-bold^7 - make bold style\n" + "^2-underline^7 - underline characters\n" + "^2-monotic^7 - constant font widths\n" + "^2-pointsizes^7 0 1 2 - char sizes for font0, font1 and font2\n" + "^2-ansi^7 - disable russian charset\n" + "\t\tPress any key to exit" ); + + system( "pause>nul" ); + return 1; + } + + Msg( "Creating %i, %i, and %i point %s fonts\n", pointsize[0], pointsize[1], pointsize[2], fontname ); + + start = I_FloatTime(); + + // create the fonts + for( i = 0; i < 3; i++ ) + { + fonts[i] = CreateConsoleFont( fontname, pointsize[i], bItalic, bUnderline, bBold, bRussian, &outsize[i] ); + } + + // Create wad file + fonts_wad = W_Open( destfile, "wb" ); + if( !fonts_wad ) COM_FatalError( "couldn't create %s\n", destfile ); + + // add fonts as lumps + for( i = 0; i < 3; i++ ) + { + Q_snprintf( str, sizeof( str ), "font%i", i ); + + if( W_SaveLump( fonts_wad, str, fonts[i], outsize[i], TYP_QFONT, ATTR_NONE ) == -1 ) + COM_FatalError( "couldn't write '%s.fnt'\n", str ); + } + + // store results as a WAD3 + W_Close( fonts_wad ); + + // clean up memory + for( i = 0; i < 3; i++ ) + Mem_Free( fonts[i] ); + + end = I_FloatTime(); + + Q_timestring((int)(end - start), str ); + MsgDev( D_INFO, "%s elapsed\n", str ); + + // display for a second since it might not be running from command prompt + Sleep( 1000 ); + + return 0; +} \ No newline at end of file diff --git a/utils/makefont/makefont.dsp b/utils/makefont/makefont.dsp new file mode 100644 index 0000000..232911d --- /dev/null +++ b/utils/makefont/makefont.dsp @@ -0,0 +1,147 @@ +# Microsoft Developer Studio Project File - Name="makefont" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=makefont - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "makefont.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "makefont.mak" CFG="makefont - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "makefont - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "makefont - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "makefont - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\..\temp\makefont\!release" +# PROP Intermediate_Dir "..\..\temp\makefont\!release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /Yu"stdafx.h" /FD /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "../../common" /I "../common" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "IGNORE_SEARCH_IN_WADS" /FD /c +# SUBTRACT CPP /Z /YX +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 msvcrt.lib gdi32.lib user32.lib /nologo /subsystem:console /debug /machine:I386 /nodefaultlib:"libcmt" +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\makefont\!release +InputPath=\Paranoia2\src_main\temp\makefont\!release\makefont.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\makefont.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\makefont.exe "D:\Paranoia2\tools\makefont.exe" + +# End Custom Build + +!ELSEIF "$(CFG)" == "makefont - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\..\temp\makefont\!debug" +# PROP Intermediate_Dir "..\..\temp\makefont\!debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /Yu"stdafx.h" /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /Gi /GX /ZI /Od /I "../../common" /I "../common" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "IGNORE_SEARCH_IN_WADS" /FAs /FR /FD /GZ /c +# SUBTRACT CPP /YX /Yc /Yu +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 msvcrtd.lib gdi32.lib user32.lib /nologo /subsystem:console /debug /machine:I386 /nodefaultlib:"libcd.lib" +# SUBTRACT LINK32 /pdbtype: +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\makefont\!debug +InputPath=\Paranoia2\src_main\temp\makefont\!debug\makefont.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\makefont.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\makefont.exe "D:\Paranoia2\tools\makefont.exe" + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "makefont - Win32 Release" +# Name "makefont - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\common\cmdlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\conprint.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\filesystem.cpp +# End Source File +# Begin Source File + +SOURCE=.\makefont.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\stringlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\wadfile.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\zone.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/utils/maketex/maketex.cpp b/utils/maketex/maketex.cpp new file mode 100644 index 0000000..dace3a9 --- /dev/null +++ b/utils/maketex/maketex.cpp @@ -0,0 +1,359 @@ +/* +maketex.cpp - convert textures into DDS images with custom encode +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "conprint.h" +#include +#include +#include +#include +#include +#include "cmdlib.h" +#include "stringlib.h" +#include "filesystem.h" +#include "imagelib.h" + +int processed_files = 0; +int processed_errors = 0; + +bool make_sdf = false; +bool no_mips = false; + +int CheckSubeSides( const char *filename, char hint ) +{ + const char *ext = COM_FileExtension( filename ); + char starthint = 0; + char testpath[256]; + char cubepath[256]; + const imgtype_t *imgtype; + int numSides; + + // disable skybox->cubemap conversion + if( hint >= IMG_SKYBOX_FT && hint <= IMG_SKYBOX_LF ) + return 0;//starthint = IMG_SKYBOX_FT; + else if( hint >= IMG_CUBEMAP_PX && hint <= IMG_CUBEMAP_NZ ) + starthint = IMG_CUBEMAP_PX; + else return 0; // not a skybox or cubemap + + Q_strncpy( testpath, filename, sizeof( testpath )); + COM_StripExtension( testpath ); + + imgtype = Image_ImageTypeFromHint( starthint ); + + // NOTE: this safety operation because we just the cutoff end of string + if( imgtype ) Q_strncpy( testpath, testpath, Q_strlen( testpath ) - Q_strlen( imgtype->ext ) + 1 ); + + for( numSides = 0; numSides < 6; numSides++, imgtype++ ) + { + Q_snprintf( cubepath, sizeof( cubepath ), "%s%s.%s", testpath, imgtype->ext, ext ); + if( !COM_FileExists( cubepath )) break; + } + + return numSides; +} + +int ConvertCubeImageToEXT( const char *filename, char hint, const char *outext ) +{ + const char *ext = COM_FileExtension( filename ); + char cubepath[256]; + char outpath[256]; + const imgtype_t *imgtype; + rgbdata_t *images[6]; + bool skybox = false; + rgbdata_t *cube = NULL; + int i; + + memset( images, 0, sizeof( images )); + + if(( hint != IMG_SKYBOX_FT ) && ( hint != IMG_CUBEMAP_PX )) + { + // make sure what we have all the sides of cubemap + int sides = CheckSubeSides( filename, hint ); + return (sides == 6) ? -1 : 2; // don't process these files + } + else if( CheckSubeSides( filename, hint ) != 6 ) + return 2; + + if( hint == IMG_SKYBOX_FT ) + skybox = true; + + Q_strncpy( outpath, filename, sizeof( outpath )); + COM_StripExtension( outpath ); + + imgtype = Image_ImageTypeFromHint( hint ); + + // NOTE: this safety operation because we just the cutoff end of string + if( imgtype ) Q_strncpy( outpath, outpath, Q_strlen( outpath ) - Q_strlen( imgtype->ext ) + 1 ); + + if( COM_FileExists( va( "%s.%s", outpath, outext ))) + return -1; // already exist + + Msg( "%s found: %s\n", skybox ? "skybox" : "cubemap", outpath ); + + for( i = 0; i < 6; i++, imgtype++ ) + { + Q_snprintf( cubepath, sizeof( cubepath ), "%s%s.%s", outpath, imgtype->ext, ext ); + images[i] = COM_LoadImage( cubepath ); + if( !images[i] ) break; + + if( skybox ) MsgDev( D_INFO, "load skyside: %s[%i]\n", cubepath, i ); + else MsgDev( D_INFO, "load cubeside: %s[%i]\n", cubepath, i ); + } + + if(( i == 6 ) && ( cube = Image_CreateCubemap( images, skybox, no_mips )) != NULL ) + { + int result; + + if( COM_SaveImage( va( "%s.%s", outpath, outext ), cube )) + { + MsgDev( D_INFO, "write %s.%s\n", outpath, outext ); + result = 1; + } + else + { + MsgDev( D_ERROR, "failed to save %s.%s\n", outpath, outext ); + result = 0; + } + + Mem_Free( cube ); + return result; + } + else + { + for( ; i >= 0; i-- ) + Mem_Free( images[i] ); + // not a cubemap just diffuse + return 2; + } +} + +int ConvertImageToEXT( const char *filename, const char *outext ) +{ + const char *ext = COM_FileExtension( filename ); + char outpath[256], lumpname[64]; + rgbdata_t *pic, *alpha = NULL; + char maskpath[256]; + + // store name for detect suffixes + COM_FileBase( filename, lumpname ); + + char hint = Image_HintFromSuf( lumpname ); + + if( hint == IMG_ALPHAMASK ) + return -1; // ignore to process + else if( hint >= IMG_SKYBOX_FT && hint <= IMG_CUBEMAP_NZ ) + { + int result; + + result = ConvertCubeImageToEXT( filename, hint, outext ); + if( result != 2 ) return result; + + // continue as normal image + hint = IMG_DIFFUSE; + } + + Q_strncpy( outpath, filename, sizeof( outpath )); + COM_StripExtension( outpath ); + + if( COM_FileExists( va( "%s.%s", outpath, outext ))) + return -1; // already exist + + pic = COM_LoadImage( filename ); + if( !pic ) + { + MsgDev( D_ERROR, "couldn't load (%s)\n", filename ); + return 0; + } + + MsgDev( D_INFO, "processing %s\n", filename ); + + // align by 4 + pic = Image_Resample( pic, ( pic->width + 15 ) & ~15, ( pic->height + 15 ) & ~15 ); + + if( hint == IMG_DIFFUSE ) + { + Q_snprintf( maskpath, sizeof( maskpath ), "%s_mask.%s", outpath, ext ); + + if( COM_FileExists( maskpath )) + alpha = COM_LoadImage( maskpath ); + + if( alpha ) + { + // get sizes from the colormap + alpha = Image_Resample( alpha, pic->width, pic->height ); + + MsgDev( D_INFO, "load mask (%s)\n", maskpath ); + pic = Image_MergeColorAlpha( pic, alpha ); + Mem_Free( alpha ); + +// COM_SaveImage( va( "%s_merged.tga", outpath ), pic ); + + if( pic->flags & IMAGE_HAS_8BIT_ALPHA ) + MsgDev( D_REPORT, "8-bit alpha detected\n" ); + if( pic->flags & IMAGE_HAS_1BIT_ALPHA ) + MsgDev( D_REPORT, "1-bit alpha detected\n" ); + + if( make_sdf ) Image_MakeSignedDistanceField( pic ); + } + else if( pic->flags & IMAGE_HAS_1BIT_ALPHA ) + { + MsgDev( D_REPORT, "1-bit alpha detected\n" ); + + if( make_sdf ) Image_MakeSignedDistanceField( pic ); + } + } + + int result; + + if( COM_SaveImage( va( "%s.%s", outpath, outext ), pic )) + { + MsgDev( D_INFO, "write %s.dds\n", outpath ); + result = 1; + } + else + { + MsgDev( D_ERROR, "failed to save %s.%s\n", outpath, outext ); + result = 0; + } + Mem_Free( pic ); + + return result; +} + +int ProcessSearchResults( search_t *search, const char *outext ) +{ + if( !search ) return 0; + + for( int i = 0; i < search->numfilenames; i++ ) + { + int result = ConvertImageToEXT( search->filenames[i], outext ); + if( result > 0 ) processed_files++; + else if( !result ) processed_errors++; + } + + return 1; +} + +void ProcessFiles( const char *srcpath, const char *outext ) +{ + const char *ext = COM_FileExtension( srcpath ); + char folders[256], base[256], newpath[256]; + + // now convert all the files in the root folder + search_t *search = COM_Search( srcpath, true ); + ProcessSearchResults( search, outext ); + Mem_Free( search ); + + COM_FileBase( srcpath, base ); + Q_strncpy( folders, srcpath, sizeof( folders )); + COM_StripExtension( folders ); + search_t *foldsearch = COM_Search( folders, true ); + + for( int i = 0; foldsearch != NULL && i < foldsearch->numfilenames; i++ ) + { + if( COM_FolderExists( foldsearch->filenames[i] )) + { + Q_snprintf( newpath, sizeof( newpath ), "%s/%s.%s", foldsearch->filenames[i], base, ext ); + + // now convert all the files in the root folder + search_t *subsearch = COM_Search( newpath, true ); + ProcessSearchResults( subsearch, outext ); + Mem_Free( subsearch ); + } + } + Mem_Free( foldsearch ); +} + +int main( int argc, char **argv ) +{ + char srcpath[256]; + bool srcset = false; + double start, end; + char str[64]; + int i; + + atexit( Sys_CloseLog ); + Sys_InitLog( "maketex.log" ); + COM_InitCmdlib( argv, argc ); + + Msg( " P2:Savior DXT Convertor\n" ); + Msg( " XashXT Group 2017(^1c^7)\n\n\n" ); + + for( i = 1; i < argc; i++ ) + { + if( !Q_stricmp( argv[i], "-dev" )) + { + SetDeveloperLevel( atoi( argv[i+1] )); + i++; + } + else if( !Q_stricmp( argv[i], "-sdf" )) + { + make_sdf = true; + } + else if( !Q_stricmp( argv[i], "-nomips" )) + { + no_mips = true; + } + else if( !srcset ) + { + Q_strncpy( srcpath, argv[i], sizeof( srcpath )); + srcset = true; + } + else + { + Msg( "maketex: unknown option %s\n", argv[i] ); + break; + } + } + + // starting default from the 'textures' folder + if( !srcset ) Q_strncpy( srcpath, "*.tga", sizeof( srcpath )); + + if( i != argc ) + { + Msg( "Usage: maketex.exe \n" + "\nlist options:\n" + "^2-dev^7 - shows developer messages\n" + "^2-sdf^7 - create signed distance field from alpha-channel\n" + "^2-nomips^7 - don't build mip-levels for cubemaps\n" + "\t\tPress any key to exit" ); + + system( "pause>nul" ); + return 1; + } + else + { + BuildGammaTable(); // init gamma conversion helper + + start = I_FloatTime(); + ProcessFiles( srcpath, "dds" ); + end = I_FloatTime(); + + MsgDev( D_INFO, "%3i files processed, %3i errors\n", processed_files, processed_errors ); + + Q_timestring((int)(end - start), str ); + MsgDev( D_INFO, "%s elapsed\n", str ); + + SetDeveloperLevel( D_REPORT ); + Mem_Check(); // report leaks + + if( !srcset ) + { + MsgDev( D_INFO, "press any key to exit\n" ); + system( "pause>nul" ); + } + } + + return 0; +} \ No newline at end of file diff --git a/utils/maketex/maketex.dsp b/utils/maketex/maketex.dsp new file mode 100644 index 0000000..2bc82c1 --- /dev/null +++ b/utils/maketex/maketex.dsp @@ -0,0 +1,179 @@ +# Microsoft Developer Studio Project File - Name="maketex" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=maketex - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "maketex.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "maketex.mak" CFG="maketex - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "maketex - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "maketex - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/SDKSrc/Tools/utils/maketex", IVGBAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "maketex - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir ".\Release" +# PROP BASE Intermediate_Dir ".\Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\..\temp\maketex\!release" +# PROP Intermediate_Dir "..\..\temp\maketex\!release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "../../common" /I "../common" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "IGNORE_SEARCH_IN_WADS" /FD /c +# SUBTRACT CPP /Fr /YX +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:console /machine:I386 /opt:nowin98 +# ADD LINK32 msvcrt.lib ..\common\squish.lib /nologo /subsystem:console /pdb:none /machine:I386 /nodefaultlib:"libcmt" /nodefaultlib:"libc" /libpath:"./" /opt:nowin98 +# SUBTRACT LINK32 /nodefaultlib +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\maketex\!release +InputPath=\Paranoia2\src_main\temp\maketex\!release\maketex.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\maketex.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\maketex.exe "D:\Paranoia2\tools\maketex.exe" + +# End Custom Build + +!ELSEIF "$(CFG)" == "maketex - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir ".\Debug" +# PROP BASE Intermediate_Dir ".\Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\..\temp\maketex\!debug" +# PROP Intermediate_Dir "..\..\temp\maketex\!debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MDd /W3 /Gm /Gi /GX /ZI /Od /I "../../common" /I "../common" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "IGNORE_SEARCH_IN_WADS" /FAs /FR /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:console /debug /machine:I386 +# ADD LINK32 msvcrtd.lib ..\common\squish.lib /nologo /subsystem:console /debug /machine:I386 /nodefaultlib:"libc.lib" /libpath:"./" +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\maketex\!debug +InputPath=\Paranoia2\src_main\temp\maketex\!debug\maketex.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\maketex.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\maketex.exe "D:\Paranoia2\tools\maketex.exe" + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "maketex - Win32 Release" +# Name "maketex - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=..\common\cmdlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\conprint.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\ddstex.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\filesystem.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\gamma.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\imagelib.cpp +# End Source File +# Begin Source File + +SOURCE=.\maketex.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\mathlib.cpp +# End Source File +# Begin Source File + +SOURCE=.\sdf.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\stringlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\virtualfs.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\zone.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=..\common\ddstex.h +# End Source File +# Begin Source File + +SOURCE=..\common\filesystem.h +# End Source File +# Begin Source File + +SOURCE=.\imagelib.h +# End Source File +# Begin Source File + +SOURCE=..\common\stringlib.h +# End Source File +# End Group +# End Target +# End Project diff --git a/utils/maketex/sdf.cpp b/utils/maketex/sdf.cpp new file mode 100644 index 0000000..4541e93 --- /dev/null +++ b/utils/maketex/sdf.cpp @@ -0,0 +1,661 @@ +/* +imagelib.cpp - simple loader\serializer for TGA & BMP +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "conprint.h" +#include +#include +#include +#include +#include +#include "cmdlib.h" +#include "stringlib.h" +#include "imagelib.h" +#include "filesystem.h" +#include "mathlib.h" + +#define DISTAA( c, xc, yc, xi, yi ) ( distaa3( img, gx, gy, w, c, xc, yc, xi, yi )) +#define SQRT2 1.4142136 + +/* + * Compute the local gradient at edge pixels using convolution filters. + * The gradient is computed only at edge pixels. At other places in the + * image, it is never used, and it's mostly zero anyway. +*/ +void computegradient( double *img, int w, int h, double *gx, double *gy ) +{ + double glength; + + for( int i = 1; i < h - 1; i++ ) + { + // avoid edges where the kernels would spill over + for( int j = 1; j < w - 1; j++ ) + { + int k = i*w + j; + + if(( img[k] > 0.0 ) && ( img[k] < 1.0 )) + { + // compute gradient for edge pixels only + gx[k] = -img[k-w-1] - SQRT2 * img[k-1] - img[k+w-1] + img[k-w+1] + SQRT2 * img[k+1] + img[k+w+1]; + gy[k] = -img[k-w-1] - SQRT2 * img[k-w] - img[k+w-1] + img[k-w+1] + SQRT2 * img[k+w] + img[k+w+1]; + glength = gx[k] * gx[k] + gy[k] * gy[k]; + + if( glength > 0.0 ) + { + // avoid division by zero + glength = sqrt( glength ); + gx[k] = gx[k] / glength; + gy[k] = gy[k] / glength; + } + } + } + } +} + +/* + * A somewhat tricky function to approximate the distance to an edge in a + * certain pixel, with consideration to either the local gradient (gx,gy) + * or the direction to the pixel (dx,dy) and the pixel greyscale value a. + * The latter alternative, using (dx,dy), is the metric used by edtaa2(). + * Using a local estimate of the edge gradient (gx,gy) yields much better + * accuracy at and near edges, and reduces the error even at distant pixels + * provided that the gradient direction is accurately estimated. +*/ +double edgedf( double gx, double gy, double a ) +{ + double df, glength, temp, a1; + + if(( gx == 0 ) || ( gy == 0 )) + { + // Either A) gu or gv are zero, or B) both + df = 0.5 - a; // Linear approximation is A) correct or B) a fair guess + } + else + { + glength = sqrt( gx * gx + gy * gy ); + + if( glength > 0 ) + { + gx = gx / glength; + gy = gy / glength; + } + + /* Everything is symmetric wrt sign and transposition, + * so move to first octant (gx>=0, gy>=0, gx>=gy) to + * avoid handling all possible edge directions. + */ + + gx = fabs(gx); + gy = fabs(gy); + + if( gx < gy ) + { + temp = gx; + gx = gy; + gy = temp; + } + + a1 = 0.5 * gy / gx; + + if( a < a1 ) + { + // 0 <= a < a1 + df = 0.5 * (gx + gy) - sqrt( 2.0 * gx * gy * a ); + } + else if( a < ( 1.0 - a1 )) + { + // a1 <= a <= 1-a1 + df = (0.5 - a) * gx; + } + else + { + // 1 - a1 < a <= 1 + df = -0.5 * (gx + gy) + sqrt( 2.0 * gx * gy * ( 1.0 - a )); + } + } + + return df; +} + +double distaa3( double *img, double *gximg, double *gyimg, int w, int c, int xc, int yc, int xi, int yi ) +{ + double di, df, dx, dy, gx, gy, a; + int closest; + + closest = c - xc - yc * w; // index to the edge pixel pointed to from c + a = img[closest]; // grayscale value at the edge pixel + gx = gximg[closest]; // X gradient component at the edge pixel + gy = gyimg[closest]; // Y gradient component at the edge pixel + + a = bound( 0.0, a, 1.0 ); // clip grayscale values outside the range [0,1] + + if( a == 0.0 ) + return 1000000.0; // not an object pixel, return "very far" ("don't know yet") + + dx = (double)xi; + dy = (double)yi; + di = sqrt( dx * dx + dy * dy ); // length of integer vector, like a traditional EDT + + if( di == 0 ) + { + // use local gradient only at edges + // Estimate based on local gradient only + df = edgedf( gx, gy, a ); + } + else + { + // estimate gradient based on direction to edge (accurate for large di) + df = edgedf( dx, dy, a ); + } + + return di + df; // Same metric as edtaa2, except at edges (where di=0) +} + +void edtaa3( double *img, double *gx, double *gy, int w, int h, short *distx, short *disty, double *dist ) +{ + double threshold = 1e-6; + double olddist, newdist; + int offset_u, offset_ur, offset_r, offset_rd, offset_d, offset_dl, offset_l, offset_lu; + int cdistx, cdisty, newdistx, newdisty; + int changed, x, y, i, c; + + // initialize index offsets for the current image width + offset_u = -w; + offset_ur = -w + 1.0; + offset_r = 1.0; + offset_rd = w + 1.0; + offset_d = w; + offset_dl = w - 1.0; + offset_l = -1.0; + offset_lu = -w - 1.0; + + // initialize the distance images + for( i = 0; i < w * h; i++ ) + { + distx[i] = 0; // at first, all pixels point to + disty[i] = 0; // themselves as the closest known. + + if( img[i] <= 0.0 ) + { + // big value, means "not set yet" + dist[i] = 1000000.0; + } + else if( img[i] < 1.0 ) + { + // gradient-assisted estimate + dist[i] = edgedf( gx[i], gy[i], img[i] ); + } + else + { + // inside the object + dist[i]= 0.0; + } + } + + // Perform the transformation + do + { + changed = 0; + + // scan rows, except first row + for( y = 1; y < h; y++ ) + { + // move index to leftmost pixel of current row + i = y * w; + + // scan right, propagate distances from above & left + + // leftmost pixel is special, has no left neighbors + olddist = dist[i]; + + // if non-zero distance or not set yet + if( olddist > 0 ) + { + c = i + offset_u; // index of candidate for testing + cdistx = distx[c]; + cdisty = disty[c]; + newdistx = cdistx; + newdisty = cdisty+1; + newdist = DISTAA( c, cdistx, cdisty, newdistx, newdisty ); + + if( newdist < ( olddist - threshold )) + { + distx[i] = newdistx; + disty[i] = newdisty; + dist[i] = newdist; + olddist = newdist; + changed = 1; + } + + c = i + offset_ur; + cdistx = distx[c]; + cdisty = disty[c]; + newdistx = cdistx - 1; + newdisty = cdisty + 1; + newdist = DISTAA( c, cdistx, cdisty, newdistx, newdisty ); + + if( newdist < ( olddist - threshold )) + { + distx[i] = newdistx; + disty[i] = newdisty; + dist[i] = newdist; + changed = 1; + } + } + i++; + + // middle pixels have all neighbors + for( x = 1; x < w - 1; x++, i++ ) + { + olddist = dist[i]; + + if( olddist <= 0 ) + continue; // no need to update further + + c = i + offset_l; + cdistx = distx[c]; + cdisty = disty[c]; + newdistx = cdistx + 1.0; + newdisty = cdisty; + newdist = DISTAA( c, cdistx, cdisty, newdistx, newdisty ); + + if( newdist < ( olddist - threshold )) + { + distx[i] = newdistx; + disty[i] = newdisty; + dist[i] = newdist; + olddist = newdist; + changed = 1; + } + + c = i + offset_lu; + cdistx = distx[c]; + cdisty = disty[c]; + newdistx = cdistx+1; + newdisty = cdisty+1; + newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty); + if(newdist < (olddist-threshold)) + { + distx[i]=newdistx; + disty[i]=newdisty; + dist[i]=newdist; + olddist=newdist; + changed = 1; + } + + c = i+offset_u; + cdistx = distx[c]; + cdisty = disty[c]; + newdistx = cdistx; + newdisty = cdisty+1; + newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty); + if(newdist < (olddist-threshold)) + { + distx[i]=newdistx; + disty[i]=newdisty; + dist[i]=newdist; + olddist=newdist; + changed = 1; + } + + c = i+offset_ur; + cdistx = distx[c]; + cdisty = disty[c]; + newdistx = cdistx-1; + newdisty = cdisty+1; + newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty); + if(newdist < (olddist-threshold)) + { + distx[i]=newdistx; + disty[i]=newdisty; + dist[i]=newdist; + changed = 1; + } + } + + /* Rightmost pixel of row is special, has no right neighbors */ + olddist = dist[i]; + if(olddist > 0) // If not already zero distance + { + c = i+offset_l; + cdistx = distx[c]; + cdisty = disty[c]; + newdistx = cdistx+1; + newdisty = cdisty; + newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty); + if(newdist < (olddist-threshold)) + { + distx[i]=newdistx; + disty[i]=newdisty; + dist[i]=newdist; + olddist=newdist; + changed = 1; + } + + c = i+offset_lu; + cdistx = distx[c]; + cdisty = disty[c]; + newdistx = cdistx+1; + newdisty = cdisty+1; + newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty); + if(newdist < (olddist-threshold)) + { + distx[i]=newdistx; + disty[i]=newdisty; + dist[i]=newdist; + olddist=newdist; + changed = 1; + } + + c = i+offset_u; + cdistx = distx[c]; + cdisty = disty[c]; + newdistx = cdistx; + newdisty = cdisty+1; + newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty); + if(newdist < (olddist-threshold)) + { + distx[i]=newdistx; + disty[i]=newdisty; + dist[i]=newdist; + changed = 1; + } + } + + /* Move index to second rightmost pixel of current row. */ + /* Rightmost pixel is skipped, it has no right neighbor. */ + i = y*w + w-2; + + /* scan left, propagate distance from right */ + for(x=w-2; x>=0; x--, i--) + { + olddist = dist[i]; + if(olddist <= 0) continue; // Already zero distance + + c = i+offset_r; + cdistx = distx[c]; + cdisty = disty[c]; + newdistx = cdistx-1; + newdisty = cdisty; + newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty); + if(newdist < (olddist-threshold)) + { + distx[i]=newdistx; + disty[i]=newdisty; + dist[i]=newdist; + changed = 1; + } + } + } + + /* Scan rows in reverse order, except last row */ + for(y=h-2; y>=0; y--) + { + /* move index to rightmost pixel of current row */ + i = y*w + w-1; + + /* Scan left, propagate distances from below & right */ + + /* Rightmost pixel is special, has no right neighbors */ + olddist = dist[i]; + if(olddist > 0) // If not already zero distance + { + c = i+offset_d; + cdistx = distx[c]; + cdisty = disty[c]; + newdistx = cdistx; + newdisty = cdisty-1; + newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty); + if(newdist < (olddist-threshold)) + { + distx[i]=newdistx; + disty[i]=newdisty; + dist[i]=newdist; + olddist=newdist; + changed = 1; + } + + c = i+offset_dl; + cdistx = distx[c]; + cdisty = disty[c]; + newdistx = cdistx+1; + newdisty = cdisty-1; + newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty); + if(newdist < (olddist-threshold)) + { + distx[i]=newdistx; + disty[i]=newdisty; + dist[i]=newdist; + changed = 1; + } + } + i--; + + /* Middle pixels have all neighbors */ + for(x=w-2; x>0; x--, i--) + { + olddist = dist[i]; + if(olddist <= 0) continue; // Already zero distance + + c = i+offset_r; + cdistx = distx[c]; + cdisty = disty[c]; + newdistx = cdistx-1; + newdisty = cdisty; + newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty); + if(newdist < (olddist-threshold)) + { + distx[i]=newdistx; + disty[i]=newdisty; + dist[i]=newdist; + olddist=newdist; + changed = 1; + } + + c = i+offset_rd; + cdistx = distx[c]; + cdisty = disty[c]; + newdistx = cdistx-1; + newdisty = cdisty-1; + newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty); + if(newdist < (olddist-threshold)) + { + distx[i]=newdistx; + disty[i]=newdisty; + dist[i]=newdist; + olddist=newdist; + changed = 1; + } + + c = i+offset_d; + cdistx = distx[c]; + cdisty = disty[c]; + newdistx = cdistx; + newdisty = cdisty-1; + newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty); + if(newdist < (olddist-threshold)) + { + distx[i]=newdistx; + disty[i]=newdisty; + dist[i]=newdist; + olddist=newdist; + changed = 1; + } + + c = i+offset_dl; + cdistx = distx[c]; + cdisty = disty[c]; + newdistx = cdistx+1; + newdisty = cdisty-1; + newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty); + if(newdist < (olddist-threshold)) + { + distx[i]=newdistx; + disty[i]=newdisty; + dist[i]=newdist; + changed = 1; + } + } + /* Leftmost pixel is special, has no left neighbors */ + olddist = dist[i]; + if(olddist > 0) // If not already zero distance + { + c = i+offset_r; + cdistx = distx[c]; + cdisty = disty[c]; + newdistx = cdistx-1; + newdisty = cdisty; + newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty); + if(newdist < (olddist-threshold)) + { + distx[i]=newdistx; + disty[i]=newdisty; + dist[i]=newdist; + olddist=newdist; + changed = 1; + } + + c = i+offset_rd; + cdistx = distx[c]; + cdisty = disty[c]; + newdistx = cdistx-1; + newdisty = cdisty-1; + newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty); + if(newdist < (olddist-threshold)) + { + distx[i]=newdistx; + disty[i]=newdisty; + dist[i]=newdist; + olddist=newdist; + changed = 1; + } + + c = i+offset_d; + cdistx = distx[c]; + cdisty = disty[c]; + newdistx = cdistx; + newdisty = cdisty-1; + newdist = DISTAA(c, cdistx, cdisty, newdistx, newdisty); + if(newdist < (olddist-threshold)) + { + distx[i]=newdistx; + disty[i]=newdisty; + dist[i]=newdist; + changed = 1; + } + } + + /* Move index to second leftmost pixel of current row. */ + /* Leftmost pixel is skipped, it has no left neighbor. */ + i = y*w + 1; + for(x=1; xflags, IMAGE_DXT_FORMAT )) + return; // can't merge compressed formats + + if( !FBitSet( pic->flags, IMAGE_HAS_1BIT_ALPHA )) + return; // generate SDF from onebit alpha + + short *xdist = (short *)Mem_Alloc( pic->width * pic->height * sizeof( short )); + short *ydist = (short *)Mem_Alloc( pic->width * pic->height * sizeof( short )); + double *gx = (double *)Mem_Alloc( pic->width * pic->height * sizeof( double )); + double *gy = (double *)Mem_Alloc( pic->width * pic->height * sizeof( double )); + double *data = (double *)Mem_Alloc( pic->width * pic->height * sizeof( double )); + double *outside = (double *)Mem_Alloc( pic->width * pic->height * sizeof( double )); + double *inside = (double *)Mem_Alloc( pic->width * pic->height * sizeof( double )); + double img_min = 255, img_max = -255; + int i; + + // convert img into double (data) + for( i = 0; i < pic->width * pic->height; ++i ) + { + data[i] = pic->buffer[i*4+3]; + + img_min = Q_min( data[i], img_min ); + img_max = Q_max( data[i], img_max ); + } + + // rescale image levels between 0 and 1 + for( i = 0; i < pic->width * pic->height; ++i ) + { + data[i] = ( pic->buffer[i*4+3] - img_min ) / img_max; + } + + // compute outside = edtaa3( bitmap ); % Transform background (0's) + computegradient( data, pic->height, pic->width, gx, gy ); + edtaa3( data, gx, gy, pic->height, pic->width, xdist, ydist, outside ); + for( i = 0; i < pic->width * pic->height; ++i ) + outside[i] = Q_max( 0.0, outside[i] ); + + // compute inside = edtaa3( 1 - bitmap ); % Transform foreground (1's) + memset( gx, 0, pic->width * pic->height * sizeof( double )); + memset( gy, 0, pic->width * pic->height * sizeof( double )); + + for( i = 0; i < pic->width * pic->height; ++i ) + data[i] = 1.0 - data[i]; + + computegradient( data, pic->height, pic->width, gx, gy ); + edtaa3( data, gx, gy, pic->height, pic->width, xdist, ydist, inside ); + for( i = 0; i < pic->width * pic->height; ++i ) + inside[i] = Q_max( 0.0, inside[i] ); + + // distmap = outside - inside; % Bipolar distance field + for( i = 0; i < pic->width * pic->height; ++i ) + { + outside[i] -= inside[i]; + outside[i] = 128 + outside[i] * 8; + outside[i] = bound( 0, outside[i], 255 ); + pic->buffer[i*4+3] = 255 - (byte)outside[i]; + } + + ClearBits( pic->flags, IMAGE_HAS_1BIT_ALPHA ); + ClearBits( pic->flags, IMAGE_HAS_8BIT_ALPHA ); + SetBits( pic->flags, IMAGE_HAS_SDF_ALPHA ); + + Mem_Free( xdist ); + Mem_Free( ydist ); + Mem_Free( gx ); + Mem_Free( gy ); + Mem_Free( data ); + Mem_Free( outside ); + Mem_Free( inside ); +} \ No newline at end of file diff --git a/utils/makewad/imagelib.cpp b/utils/makewad/imagelib.cpp new file mode 100644 index 0000000..4735f6f --- /dev/null +++ b/utils/makewad/imagelib.cpp @@ -0,0 +1,1446 @@ +/* +imagelib.cpp - simple loader\serializer for TGA & BMP +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "conprint.h" +#include +#include +#include +#include +#include +#include "cmdlib.h" +#include "stringlib.h" +#include "imagelib.h" +#include "filesystem.h" +#include "makewad.h" +#include "mathlib.h" + +// using to detect quake1 textures and possible to convert luma +static const byte palette_q1[768] = +{ +0,0,0,15,15,15,31,31,31,47,47,47,63,63,63,75,75,75,91,91,91,107,107,107,123,123,123,139,139,139,155,155,155,171, +171,171,187,187,187,203,203,203,219,219,219,235,235,235,15,11,7,23,15,11,31,23,11,39,27,15,47,35,19,55,43,23,63, +47,23,75,55,27,83,59,27,91,67,31,99,75,31,107,83,31,115,87,31,123,95,35,131,103,35,143,111,35,11,11,15,19,19,27, +27,27,39,39,39,51,47,47,63,55,55,75,63,63,87,71,71,103,79,79,115,91,91,127,99,99,139,107,107,151,115,115,163,123, +123,175,131,131,187,139,139,203,0,0,0,7,7,0,11,11,0,19,19,0,27,27,0,35,35,0,43,43,7,47,47,7,55,55,7,63,63,7,71,71, +7,75,75,11,83,83,11,91,91,11,99,99,11,107,107,15,7,0,0,15,0,0,23,0,0,31,0,0,39,0,0,47,0,0,55,0,0,63,0,0,71,0,0,79, +0,0,87,0,0,95,0,0,103,0,0,111,0,0,119,0,0,127,0,0,19,19,0,27,27,0,35,35,0,47,43,0,55,47,0,67,55,0,75,59,7,87,67,7, +95,71,7,107,75,11,119,83,15,131,87,19,139,91,19,151,95,27,163,99,31,175,103,35,35,19,7,47,23,11,59,31,15,75,35,19, +87,43,23,99,47,31,115,55,35,127,59,43,143,67,51,159,79,51,175,99,47,191,119,47,207,143,43,223,171,39,239,203,31,255, +243,27,11,7,0,27,19,0,43,35,15,55,43,19,71,51,27,83,55,35,99,63,43,111,71,51,127,83,63,139,95,71,155,107,83,167,123, +95,183,135,107,195,147,123,211,163,139,227,179,151,171,139,163,159,127,151,147,115,135,139,103,123,127,91,111,119, +83,99,107,75,87,95,63,75,87,55,67,75,47,55,67,39,47,55,31,35,43,23,27,35,19,19,23,11,11,15,7,7,187,115,159,175,107, +143,163,95,131,151,87,119,139,79,107,127,75,95,115,67,83,107,59,75,95,51,63,83,43,55,71,35,43,59,31,35,47,23,27,35, +19,19,23,11,11,15,7,7,219,195,187,203,179,167,191,163,155,175,151,139,163,135,123,151,123,111,135,111,95,123,99,83, +107,87,71,95,75,59,83,63,51,67,51,39,55,43,31,39,31,23,27,19,15,15,11,7,111,131,123,103,123,111,95,115,103,87,107, +95,79,99,87,71,91,79,63,83,71,55,75,63,47,67,55,43,59,47,35,51,39,31,43,31,23,35,23,15,27,19,11,19,11,7,11,7,255, +243,27,239,223,23,219,203,19,203,183,15,187,167,15,171,151,11,155,131,7,139,115,7,123,99,7,107,83,0,91,71,0,75,55, +0,59,43,0,43,31,0,27,15,0,11,7,0,0,0,255,11,11,239,19,19,223,27,27,207,35,35,191,43,43,175,47,47,159,47,47,143,47, +47,127,47,47,111,47,47,95,43,43,79,35,35,63,27,27,47,19,19,31,11,11,15,43,0,0,59,0,0,75,7,0,95,7,0,111,15,0,127,23, +7,147,31,7,163,39,11,183,51,15,195,75,27,207,99,43,219,127,59,227,151,79,231,171,95,239,191,119,247,211,139,167,123, +59,183,155,55,199,195,55,231,227,87,127,191,255,171,231,255,215,255,255,103,0,0,139,0,0,179,0,0,215,0,0,255,0,0,255, +243,147,255,247,199,255,255,255,159,91,83 +}; + +// suffix converts to img_type and back +const imgtype_t img_hints[] = +{ +{ "_mask", IMG_ALPHAMASK }, // alpha-channel stored to another lump +{ "_norm", IMG_NORMALMAP }, // indexed normalmap +{ "_n", IMG_NORMALMAP }, // indexed normalmap +{ "_nrm", IMG_NORMALMAP }, // indexed normalmap +{ "_local", IMG_NORMALMAP }, // indexed normalmap +{ "_ddn", IMG_NORMALMAP }, // indexed normalmap +{ "_spec", IMG_GLOSSMAP }, // grayscale\color specular +{ "_gloss", IMG_GLOSSMAP }, // grayscale\color specular +{ "_hmap", IMG_HEIGHTMAP }, // heightmap (can be converted to normalmap) +{ "_height", IMG_HEIGHTMAP }, // heightmap (can be converted to normalmap) +{ "_luma", IMG_LUMA }, // self-illuminate parts on the diffuse +{ "_add", IMG_LUMA }, // self-illuminate parts on the diffuse +{ "_illum", IMG_LUMA }, // self-illuminate parts on the diffuse +{ NULL, 0 } // terminator +}; + +/* +================= +Image_ValidSize + +check image for valid dimensions +================= +*/ +bool Image_ValidSize( const char *name, int width, int height ) +{ + if( width > IMAGE_MAXWIDTH || height > IMAGE_MAXHEIGHT || width < IMAGE_MINWIDTH || height < IMAGE_MINHEIGHT ) + { + MsgDev( D_ERROR, "Image: %s has invalid sizes %i x %i\n", name, width, height ); + return false; + } + return true; +} + +/* +================= +Image_Alloc + +allocate image struct and partially fill it +================= +*/ +rgbdata_t *Image_Alloc( int width, int height, bool paletted ) +{ + size_t pic_size = sizeof( rgbdata_t ) + (width * height * (paletted ? 1 : 4)) + (paletted ? 1024 : 0); + rgbdata_t *pic = (rgbdata_t *)Mem_Alloc( pic_size ); + + if( paletted ) + { + pic->buffer = ((byte *)pic) + sizeof( rgbdata_t ); + pic->palette = ((byte *)pic) + sizeof( rgbdata_t ) + width * height; + pic->flags |= IMAGE_QUANTIZED; + } + else + { + pic->buffer = ((byte *)pic) + sizeof( rgbdata_t ); + pic->palette = NULL; // not present + } + + pic->size = (width * height * (paletted ? 1 : 4)); + pic->width = width; + pic->height = height; + + return pic; +} + +/* +================= +Image_Copy + +make an copy of image +================= +*/ +rgbdata_t *Image_Copy( rgbdata_t *src ) +{ + rgbdata_t *dst = Image_Alloc( src->width, src->height, FBitSet( src->flags, IMAGE_QUANTIZED )); + + if( FBitSet( src->flags, IMAGE_QUANTIZED )) + memcpy( dst->palette, src->palette, 1024 ); + memcpy( dst->buffer, src->buffer, src->size ); + + dst->size = src->size; + dst->width = src->width; + dst->height = src->height; + dst->flags = src->flags; + + return dst; +} + +/* +=========== +Image_HintFromSuf + +Convert name suffix into image type +=========== +*/ +char Image_HintFromSuf( const char *lumpname ) +{ + char barename[64]; + char suffix[16]; + const imgtype_t *hint; + + // trying to extract hint from the name + Q_strncpy( barename, lumpname, sizeof( barename )); + + // we not known about filetype, so match only by filename + for( hint = img_hints; hint->ext; hint++ ) + { + if( Q_strlen( barename ) <= Q_strlen( hint->ext )) + continue; // name too short + + Q_strncpy( suffix, barename + Q_strlen( barename ) - Q_strlen( hint->ext ), sizeof( suffix )); + if( !Q_stricmp( suffix, hint->ext )) + return hint->type; + } + + // special additional check for "_normal" + if( Q_stristr( lumpname, "_normal" )) + return IMG_NORMALMAP; + + // no any special type was found + return IMG_DIFFUSE; +} + +/* +============================================================================= + + IMAGE LOADING + +============================================================================= +*/ +/* +============= +Image_LoadTGA + +expand any image to RGBA32 but keep 8-bit unchanged +============= +*/ +rgbdata_t *Image_LoadTGA( const char *name, const byte *buffer, size_t filesize ) +{ + int i, columns, rows, row_inc, row, col; + byte *buf_p, *pixbuf, *targa_rgba; + byte palette[256][4], red = 0, green = 0, blue = 0, alpha = 0; + int readpixelcount, pixelcount, palIndex; + tga_t targa_header; + bool compressed; + bool paletted; + rgbdata_t *pic; + + if( filesize < sizeof( tga_t )) + return NULL; + + buf_p = (byte *)buffer; + targa_header.id_length = *buf_p++; + targa_header.colormap_type = *buf_p++; + targa_header.image_type = *buf_p++; + + targa_header.colormap_index = *(short *)buf_p; buf_p += 2; + targa_header.colormap_length = *(short *)buf_p; buf_p += 2; + targa_header.colormap_size = *buf_p; buf_p += 1; + targa_header.x_origin = *(short *)buf_p; buf_p += 2; + targa_header.y_origin = *(short *)buf_p; buf_p += 2; + targa_header.width = *(short *)buf_p; buf_p += 2; + targa_header.height = *(short *)buf_p; buf_p += 2; + targa_header.pixel_size = *buf_p++; + targa_header.attributes = *buf_p++; + if( targa_header.id_length != 0 ) + buf_p += targa_header.id_length; // skip TARGA image comment + + // check for tga file + if( !Image_ValidSize( name, targa_header.width, targa_header.height )) + return NULL; + + if( targa_header.image_type == 1 || targa_header.image_type == 9 ) + { + // uncompressed colormapped image + if( targa_header.pixel_size != 8 ) + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) Only 8 bit images supported for type 1 and 9\n", name ); + return NULL; + } + + if( targa_header.colormap_length != 256 ) + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) Only 8 bit colormaps are supported for type 1 and 9\n", name ); + return NULL; + } + + if( targa_header.colormap_index ) + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) colormap_index is not supported for type 1 and 9\n", name ); + return NULL; + } + + if( targa_header.colormap_size == 24 ) + { + for( i = 0; i < targa_header.colormap_length; i++ ) + { + palette[i][2] = *buf_p++; + palette[i][1] = *buf_p++; + palette[i][0] = *buf_p++; + palette[i][3] = 255; + } + } + else if( targa_header.colormap_size == 32 ) + { + for( i = 0; i < targa_header.colormap_length; i++ ) + { + palette[i][2] = *buf_p++; + palette[i][1] = *buf_p++; + palette[i][0] = *buf_p++; + palette[i][3] = *buf_p++; + } + } + else + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) only 24 and 32 bit colormaps are supported for type 1 and 9\n", name ); + return NULL; + } + } + else if( targa_header.image_type == 2 || targa_header.image_type == 10 ) + { + // uncompressed or RLE compressed RGB + if( targa_header.pixel_size != 32 && targa_header.pixel_size != 24 ) + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) Only 32 or 24 bit images supported for type 2 and 10\n", name ); + return NULL; + } + } + else if( targa_header.image_type == 3 || targa_header.image_type == 11 ) + { + // uncompressed greyscale + if( targa_header.pixel_size != 8 ) + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) Only 8 bit images supported for type 3 and 11\n", name ); + return NULL; + } + } + + paletted = ( targa_header.image_type == 1 || targa_header.image_type == 9 ); + + pic = Image_Alloc( targa_header.width, targa_header.height, paletted ); + if( paletted ) memcpy( pic->palette, palette, sizeof( palette )); + + columns = targa_header.width; + rows = targa_header.height; + targa_rgba = pic->buffer; + + // if bit 5 of attributes isn't set, the image has been stored from bottom to top + if( targa_header.attributes & 0x20 ) + { + pixbuf = targa_rgba; + row_inc = 0; + } + else + { + if( FBitSet( pic->flags, IMAGE_QUANTIZED )) + { + pixbuf = targa_rgba + ( rows - 1 ) * columns; + row_inc = -columns * 2; + } + else + { + pixbuf = targa_rgba + ( rows - 1 ) * columns * 4; + row_inc = -columns * 4 * 2; + } + } + + compressed = ( targa_header.image_type == 9 || targa_header.image_type == 10 || targa_header.image_type == 11 ); + + for( row = col = 0; row < rows; ) + { + pixelcount = 0x10000; + readpixelcount = 0x10000; + + if( compressed ) + { + pixelcount = *buf_p++; + if( pixelcount & 0x80 ) // run-length packet + readpixelcount = 1; + pixelcount = 1 + ( pixelcount & 0x7f ); + } + + while( pixelcount-- && ( row < rows ) ) + { + if( readpixelcount-- > 0 ) + { + switch( targa_header.image_type ) + { + case 1: + case 9: + // colormapped image + palIndex = *buf_p++; + red = palette[palIndex][0]; + green = palette[palIndex][1]; + blue = palette[palIndex][2]; + alpha = palette[palIndex][3]; + break; + case 2: + case 10: + // 24 or 32 bit image + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alpha = 255; + if( targa_header.pixel_size == 32 ) + alpha = *buf_p++; + break; + case 3: + case 11: + // greyscale image + blue = green = red = *buf_p++; + alpha = 255; + break; + } + } + + if( red != green || green != blue ) + pic->flags |= IMAGE_HAS_COLOR; + + if( alpha != 255 ) + { + if( alpha != 0 ) + { + SetBits( pic->flags, IMAGE_HAS_8BIT_ALPHA ); + ClearBits( pic->flags, IMAGE_HAS_1BIT_ALPHA ); + } + else if( !FBitSet( pic->flags, IMAGE_HAS_8BIT_ALPHA )) + SetBits( pic->flags, IMAGE_HAS_1BIT_ALPHA ); + } + + if( FBitSet( pic->flags, IMAGE_QUANTIZED )) + { + *pixbuf++ = palIndex; + } + else + { + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha; + } + + if( ++col == columns ) + { + // run spans across rows + row++; + col = 0; + pixbuf += row_inc; + } + } + } + + return pic; +} + +/* +============= +Image_LoadBMP + +expand any image to RGBA32 but keep 8-bit unchanged +============= +*/ +rgbdata_t *Image_LoadBMP( const char *name, const byte *buffer, size_t filesize ) +{ + byte *buf_p, *pixbuf; + byte palette[256][4]; + int i, columns, column, rows, row, bpp = 1; + int cbPalBytes = 0, padSize = 0, bps = 0; + rgbdata_t *pic; + bmp_t bhdr; + + if( filesize < sizeof( bhdr )) + return NULL; + + buf_p = (byte *)buffer; + bhdr.id[0] = *buf_p++; + bhdr.id[1] = *buf_p++; // move pointer + bhdr.fileSize = *(long *)buf_p; buf_p += 4; + bhdr.reserved0 = *(long *)buf_p; buf_p += 4; + bhdr.bitmapDataOffset = *(long *)buf_p; buf_p += 4; + bhdr.bitmapHeaderSize = *(long *)buf_p; buf_p += 4; + bhdr.width = *(long *)buf_p; buf_p += 4; + bhdr.height = *(long *)buf_p; buf_p += 4; + bhdr.planes = *(short *)buf_p; buf_p += 2; + bhdr.bitsPerPixel = *(short *)buf_p; buf_p += 2; + bhdr.compression = *(long *)buf_p; buf_p += 4; + bhdr.bitmapDataSize = *(long *)buf_p; buf_p += 4; + bhdr.hRes = *(long *)buf_p; buf_p += 4; + bhdr.vRes = *(long *)buf_p; buf_p += 4; + bhdr.colors = *(long *)buf_p; buf_p += 4; + bhdr.importantColors = *(long *)buf_p; buf_p += 4; + + // bogus file header check + if( bhdr.reserved0 != 0 ) return NULL; + if( bhdr.planes != 1 ) return NULL; + + if( memcmp( bhdr.id, "BM", 2 )) + { + MsgDev( D_ERROR, "Image_LoadBMP: only Windows-style BMP files supported (%s)\n", name ); + return NULL; + } + + if( bhdr.bitmapHeaderSize != 0x28 ) + { + MsgDev( D_ERROR, "Image_LoadBMP: invalid header size %i\n", bhdr.bitmapHeaderSize ); + return NULL; + } + + // bogus info header check + if( bhdr.fileSize != filesize ) + { + // Sweet Half-Life issues. splash.bmp have bogus filesize + MsgDev( D_WARN, "Image_LoadBMP: %s have incorrect file size %i should be %i\n", name, filesize, bhdr.fileSize ); + } + + // bogus compression? Only non-compressed supported. + if( bhdr.compression != BI_RGB ) + { + MsgDev( D_ERROR, "Image_LoadBMP: only uncompressed BMP files supported (%s)\n", name ); + return false; + } + + columns = bhdr.width; + rows = abs( bhdr.height ); + + if( !Image_ValidSize( name, columns, rows )) + return false; + + pic = Image_Alloc( columns, rows, ( bhdr.bitsPerPixel == 8 )); + + if( bhdr.bitsPerPixel <= 8 ) + { + // figure out how many entries are actually in the table + if( bhdr.colors == 0 ) + { + bhdr.colors = 256; + cbPalBytes = (1 << bhdr.bitsPerPixel) * sizeof( RGBQUAD ); + } + else cbPalBytes = bhdr.colors * sizeof( RGBQUAD ); + } + + memcpy( palette, buf_p, cbPalBytes ); + + if( bhdr.bitsPerPixel == 8 ) + { + pixbuf = pic->palette; + + // bmp have a reversed palette colors + for( i = 0; i < bhdr.colors; i++ ) + { + *pixbuf++ = palette[i][2]; + *pixbuf++ = palette[i][1]; + *pixbuf++ = palette[i][0]; + *pixbuf++ = palette[i][3]; + + if( palette[i][0] != palette[i][1] || palette[i][1] != palette[i][2] ) + pic->flags |= IMAGE_HAS_COLOR; + } + } + else bpp = 4; + + buf_p += cbPalBytes; + bps = bhdr.width * (bhdr.bitsPerPixel >> 3); + + switch( bhdr.bitsPerPixel ) + { + case 1: + padSize = (( 32 - ( bhdr.width % 32 )) / 8 ) % 4; + break; + case 4: + padSize = (( 8 - ( bhdr.width % 8 )) / 2 ) % 4; + break; + case 16: + padSize = ( 4 - ( bhdr.width * 2 % 4 )) % 4; + break; + case 8: + case 24: + padSize = ( 4 - ( bps % 4 )) % 4; + break; + } + + for( row = rows - 1; row >= 0; row-- ) + { + pixbuf = pic->buffer + (row * columns * bpp); + + for( column = 0; column < columns; column++ ) + { + byte red, green, blue, alpha; + int c, k, palIndex; + word shortPixel; + + switch( bhdr.bitsPerPixel ) + { + case 1: + alpha = *buf_p++; + column--; // ingnore main iterations + for( c = 0, k = 128; c < 8; c++, k >>= 1 ) + { + red = green = blue = (!!(alpha & k) == 1 ? 0xFF : 0x00); + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 0x00; + if( ++column == columns ) + break; + } + break; + case 4: + alpha = *buf_p++; + palIndex = alpha >> 4; + *pixbuf++ = red = palette[palIndex][2]; + *pixbuf++ = green = palette[palIndex][1]; + *pixbuf++ = blue = palette[palIndex][0]; + *pixbuf++ = palette[palIndex][3]; + if( ++column == columns ) break; + palIndex = alpha & 0x0F; + *pixbuf++ = red = palette[palIndex][2]; + *pixbuf++ = green = palette[palIndex][1]; + *pixbuf++ = blue = palette[palIndex][0]; + *pixbuf++ = palette[palIndex][3]; + break; + case 8: + palIndex = *buf_p++; + red = palette[palIndex][2]; + green = palette[palIndex][1]; + blue = palette[palIndex][0]; + alpha = palette[palIndex][3]; + + if( FBitSet( pic->flags, IMAGE_QUANTIZED )) + { + *pixbuf++ = palIndex; + } + else + { + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha; + } + break; + case 16: + shortPixel = *(word *)buf_p, buf_p += 2; + *pixbuf++ = blue = (shortPixel & ( 31 << 10 )) >> 7; + *pixbuf++ = green = (shortPixel & ( 31 << 5 )) >> 2; + *pixbuf++ = red = (shortPixel & ( 31 )) << 3; + *pixbuf++ = alpha = 0xff; + break; + case 24: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha = 0xFF; + break; + case 32: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alpha = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha; + break; + default: + MsgDev( D_ERROR, "Image_LoadBMP: illegal pixel_size (%s)\n", name ); + Mem_Free( pic ); + return NULL; + } + + if( !FBitSet( pic->flags, IMAGE_QUANTIZED ) && ( red != green || green != blue )) + pic->flags |= IMAGE_HAS_COLOR; + + if( alpha != 255 ) + { + if( alpha != 0 ) + { + SetBits( pic->flags, IMAGE_HAS_8BIT_ALPHA ); + ClearBits( pic->flags, IMAGE_HAS_1BIT_ALPHA ); + } + else if( !FBitSet( pic->flags, IMAGE_HAS_8BIT_ALPHA )) + SetBits( pic->flags, IMAGE_HAS_1BIT_ALPHA ); + } + } + + buf_p += padSize; // probably actual only for 4-bit bmps + } + + return pic; +} + +/* +============= +Image_LoadMIP + +just read header +============= +*/ +rgbdata_t *Image_LoadMIP( const char *name, const byte *buffer, size_t filesize ) +{ + char tmpname[64]; + + if( filesize < sizeof( mip_t )) + return NULL; + + mip_t *mip = (mip_t *)buffer; + + if( !Image_ValidSize( name, mip->width, mip->height )) + return NULL; + + int pixels = mip->width * mip->height; + + if( filesize < ( sizeof( mip_t ) + (( pixels * 85 ) >> 6 ) + sizeof( short ) + 768 )) + { + MsgDev( D_ERROR, "Image_LoadMIP: %s probably corrupted\n", name ); + return NULL; + } + + rgbdata_t *pic = Image_Alloc( mip->width, mip->height, true ); + memcpy( pic->buffer, buffer + mip->offsets[0], pixels ); + + byte *pal = (byte *)buffer + mip->offsets[0] + (((mip->width * mip->height) * 85)>>6); + + int numcolors = *(short *)pal; + if( numcolors != 256 ) MsgDev( D_WARN, "Image_LoadMIP: %s invalid palette num colors %i\n", name, numcolors ); + pal += sizeof( short ); // skip colorsize + + // expand palette + for( int i = 0; i < 256; i++ ) + { + pic->palette[i*4+0] = *pal++; + pic->palette[i*4+1] = *pal++; + pic->palette[i*4+2] = *pal++; + pic->palette[i*4+3] = 255; + + if( pic->palette[i*4+0] != pic->palette[i*4+1] || pic->palette[i*4+1] != pic->palette[i*4+2] ) + pic->flags |= IMAGE_HAS_COLOR; + } + + // check for one-bit alpha + COM_FileBase( name, tmpname ); + + if( tmpname[0] == '{' && pic->palette[255*3+0] == 0 && pic->palette[255*3+1] == 0 && pic->palette[255*3+2] == 255 ) + pic->flags |= IMAGE_HAS_1BIT_ALPHA; + + return pic; +} + +/* +============= +Image_LoadLMP + +just read header +============= +*/ +rgbdata_t *Image_LoadLMP( const char *name, const byte *buffer, size_t filesize ) +{ + if( filesize < sizeof( lmp_t )) + return NULL; + + lmp_t *lmp = (lmp_t *)buffer; + + if( !Image_ValidSize( name, lmp->width, lmp->height )) + return NULL; + + int pixels = lmp->width * lmp->height; + + if( filesize < ( sizeof( lmp_t ) + pixels + sizeof( short ) + 768 )) + { + MsgDev( D_ERROR, "Image_LoadLMP: %s probably corrupted\n", name ); + return NULL; + } + + rgbdata_t *pic = Image_Alloc( lmp->width, lmp->height, true ); + memcpy( pic->buffer, buffer + sizeof( lmp_t ), pixels ); + + byte *pal = (byte *)buffer + sizeof( lmp_t ) + pixels; + + int numcolors = *(short *)pal; + if( numcolors != 256 ) MsgDev( D_WARN, "Image_LoadLMP: %s invalid palette num colors %i\n", name, numcolors ); + pal += sizeof( short ); // skip colorsize + + // expand palette + for( int i = 0; i < 256; i++ ) + { + pic->palette[i*4+0] = *pal++; + pic->palette[i*4+1] = *pal++; + pic->palette[i*4+2] = *pal++; + pic->palette[i*4+3] = 255; + + if( pic->palette[i*4+0] != pic->palette[i*4+1] || pic->palette[i*4+1] != pic->palette[i*4+2] ) + pic->flags |= IMAGE_HAS_COLOR; + } + + // always has the alpha + pic->flags |= IMAGE_HAS_1BIT_ALPHA; + + return pic; +} + +/* +================ +COM_LoadImage + +handle bmp & tga +================ +*/ +rgbdata_t *COM_LoadImage( const char *filename, bool quiet ) +{ + size_t fileSize; + byte *buf = (byte *)COM_LoadFile( filename, &fileSize, false ); + const char *ext = COM_FileExtension( filename ); + rgbdata_t *pic = NULL; + char barename[64]; + + if( !buf && source_wad != NULL ) + { + COM_FileBase( filename, barename ); + buf = W_LoadLump( source_wad, barename, &fileSize, W_TypeFromExt( filename )); + } + + if( !buf ) + { + if( !quiet ) + MsgDev( D_ERROR, "COM_LoadImage: unable to load (%s)\n", filename ); + return NULL; + } + + if( !Q_stricmp( ext, "tga" )) + pic = Image_LoadTGA( filename, buf, fileSize ); + else if( !Q_stricmp( ext, "bmp" )) + pic = Image_LoadBMP( filename, buf, fileSize ); + else if( !Q_stricmp( ext, "mip" )) + pic = Image_LoadMIP( filename, buf, fileSize ); + else if( !Q_stricmp( ext, "lmp" )) + pic = Image_LoadLMP( filename, buf, fileSize ); + else if( !quiet ) + MsgDev( D_ERROR, "COM_LoadImage: unsupported format (%s)\n", ext ); + + Mem_Free( buf ); // release file + + if( pic != NULL ) + { + // check for quake1 palette + if( FBitSet( pic->flags, IMAGE_QUANTIZED ) && pic->palette != NULL ) + { + byte src[256*3]; + + // first we need to turn palette into 768 bytes + for( int i = 0; i < 256; i++ ) + { + src[i*3+0] = pic->palette[i*4+0]; + src[i*3+1] = pic->palette[i*4+1]; + src[i*3+2] = pic->palette[i*4+2]; + } + + if( !memcmp( src, palette_q1, sizeof( src ))) + SetBits( pic->flags, IMAGE_QUAKE1_PAL ); + } + } + + return pic; // may be NULL +} + +/* +============================================================================= + + IMAGE SAVING + +============================================================================= +*/ +/* +============= +Image_SaveTGA +============= +*/ +bool Image_SaveTGA( const char *name, rgbdata_t *pix ) +{ + const char *comment = "Generated by XashNT MakeWad tool.\0"; + int i, y, outsize, pixel_size = 4; + const byte *bufend, *in; + byte *buffer, *out; + + if( COM_FileExists( name )) + return false; // already existed + + // bogus parameter check + if( !pix->buffer ) + return false; + + if( FBitSet( pix->flags, IMAGE_QUANTIZED )) + { + outsize = pix->width * pix->height + 18 + Q_strlen( comment ); + } + else + { + if( pix->flags & IMAGE_HAS_ALPHA ) + outsize = pix->width * pix->height * 4 + 18 + Q_strlen( comment ); + else outsize = pix->width * pix->height * 3 + 18 + Q_strlen( comment ); + } + + if( FBitSet( pix->flags, IMAGE_QUANTIZED )) + outsize += 768; // palette + + buffer = (byte *)Mem_Alloc( outsize ); + memset( buffer, 0, 18 ); + + // prepare header + buffer[0] = Q_strlen( comment ); // tga comment length + + if( FBitSet( pix->flags, IMAGE_QUANTIZED )) + { + buffer[1] = 1; // color map type + buffer[2] = 1; // uncompressed color mapped image + buffer[5] = 0; // number of palette entries (lo) + buffer[6] = 1; // number of palette entries (hi) + buffer[7] = 24; // palette bpp + } + else buffer[2] = 2; // uncompressed type + + buffer[12] = (pix->width >> 0) & 0xFF; + buffer[13] = (pix->width >> 8) & 0xFF; + buffer[14] = (pix->height >> 0) & 0xFF; + buffer[15] = (pix->height >> 8) & 0xFF; + + if( !FBitSet( pix->flags, IMAGE_QUANTIZED )) + { + buffer[16] = ( pix->flags & IMAGE_HAS_ALPHA ) ? 32 : 24; // RGB pixel size + buffer[17] = ( pix->flags & IMAGE_HAS_ALPHA ) ? 8 : 0; // 8 bits of alpha + } + else buffer[16] = 8; // pixel size + Q_strncpy( (char *)(buffer + 18), comment, Q_strlen( comment )); + out = buffer + 18 + Q_strlen( comment ); + + // store palette, swapping rgb to bgr + if( FBitSet( pix->flags, IMAGE_QUANTIZED )) + { + for( i = 0; i < 256; i++ ) + { + *out++ = pix->palette[i*4+2]; // blue + *out++ = pix->palette[i*4+1]; // green + *out++ = pix->palette[i*4+0]; // red + } + + // store the image data (and flip upside down) + for( y = pix->height - 1; y >= 0; y-- ) + { + in = pix->buffer + y * pix->width; + bufend = in + pix->width; + for( ; in < bufend; in++ ) + *out++ = *in; + } + } + else + { + // swap rgba to bgra and flip upside down + for( y = pix->height - 1; y >= 0; y-- ) + { + in = pix->buffer + y * pix->width * pixel_size; + bufend = in + pix->width * pixel_size; + for( ; in < bufend; in += pixel_size ) + { + *out++ = in[2]; + *out++ = in[1]; + *out++ = in[0]; + if( pix->flags & IMAGE_HAS_ALPHA ) + *out++ = in[3]; + } + } + } + + COM_SaveFile( name, buffer, outsize ); + Mem_Free( buffer ); + + return true; +} + +bool Image_SaveBMP( const char *name, rgbdata_t *pix ) +{ + long file; + BITMAPFILEHEADER bmfh; + BITMAPINFOHEADER bmih; + RGBQUAD rgrgbPalette[256]; + dword cbBmpBits; + byte *pb, *pbBmpBits; + dword cbPalBytes = 0; + dword biTrueWidth; + int pixel_size; + int i, x, y; + + if( COM_FileExists( name )) + return false; // already existed + + // bogus parameter check + if( !pix->buffer ) + return false; + + if( FBitSet( pix->flags, IMAGE_QUANTIZED )) + pixel_size = 1; + else pixel_size = 4; + + COM_CreatePath( (char *)name ); + + file = open( name, O_WRONLY|O_BINARY|O_CREAT|O_TRUNC, 0666 ); + if( file < 0 ) return false; + + // NOTE: align transparency column will sucessfully removed + // after create sprite or lump image, it's just standard requiriments + biTrueWidth = ((pix->width + 3) & ~3); + cbBmpBits = biTrueWidth * pix->height * pixel_size; + if( pixel_size == 1 ) cbPalBytes = 256 * sizeof( RGBQUAD ); + + // Bogus file header check + bmfh.bfType = MAKEWORD( 'B', 'M' ); + bmfh.bfSize = sizeof( bmfh ) + sizeof( bmih ) + cbBmpBits + cbPalBytes; + bmfh.bfOffBits = sizeof( bmfh ) + sizeof( bmih ) + cbPalBytes; + bmfh.bfReserved1 = bmfh.bfReserved2 = 0; + + // write header + write( file, &bmfh, sizeof( bmfh )); + + // size of structure + bmih.biSize = sizeof( bmih ); + bmih.biWidth = biTrueWidth; + bmih.biHeight = pix->height; + bmih.biPlanes = 1; + bmih.biBitCount = pixel_size * 8; + bmih.biCompression = BI_RGB; + bmih.biSizeImage = cbBmpBits; + bmih.biXPelsPerMeter = 0; + bmih.biYPelsPerMeter = 0; + bmih.biClrUsed = ( pixel_size == 1 ) ? 256 : 0; + bmih.biClrImportant = 0; + + // write info header + write( file, &bmih, sizeof( bmih )); + + pbBmpBits = (byte *)Mem_Alloc( cbBmpBits ); + + if( pixel_size == 1 ) + { + pb = pix->palette; + + // copy over used entries + for( i = 0; i < (int)bmih.biClrUsed; i++ ) + { + rgrgbPalette[i].rgbRed = *pb++; + rgrgbPalette[i].rgbGreen = *pb++; + rgrgbPalette[i].rgbBlue = *pb++; + rgrgbPalette[i].rgbReserved = *pb++; + } + + // write palette + write( file, rgrgbPalette, cbPalBytes ); + } + + pb = pix->buffer; + + for( y = 0; y < bmih.biHeight; y++ ) + { + i = (bmih.biHeight - 1 - y ) * (bmih.biWidth); + + for( x = 0; x < pix->width; x++ ) + { + if( pixel_size == 1 ) + { + // 8-bit + pbBmpBits[i] = pb[x]; + } + else + { + // 24 bit + pbBmpBits[i*pixel_size+0] = pb[x*pixel_size+2]; + pbBmpBits[i*pixel_size+1] = pb[x*pixel_size+1]; + pbBmpBits[i*pixel_size+2] = pb[x*pixel_size+0]; + } + + if( pixel_size == 4 ) // write alpha channel + pbBmpBits[i*pixel_size+3] = pb[x*pixel_size+3]; + i++; + } + + pb += pix->width * pixel_size; + } + + // write bitmap bits (remainder of file) + write( file, pbBmpBits, cbBmpBits ); + close( file ); + Mem_Free( pbBmpBits ); + + return true; +} + +/* +================ +COM_SaveImage + +handle bmp & tga +================ +*/ +bool COM_SaveImage( const char *filename, rgbdata_t *pix ) +{ + const char *ext = COM_FileExtension( filename ); + + if( !pix ) + { + MsgDev( D_ERROR, "COM_SaveImage: pix == NULL\n" ); + return false; + } + + if( !Q_stricmp( ext, "tga" )) + return Image_SaveTGA( filename, pix ); + else if( !Q_stricmp( ext, "bmp" )) + return Image_SaveBMP( filename, pix ); + else if( !Q_stricmp( ext, "lmp" )) + return LMP_WriteLmptex( filename, pix, true ); + else + { + MsgDev( D_ERROR, "COM_SaveImage: unsupported format (%s)\n", ext ); + return false; + } +} + +/* +============================================================================= + + IMAGE PROCESSING + +============================================================================= +*/ +#define TRANSPARENT_R 0x0 +#define TRANSPARENT_G 0x0 +#define TRANSPARENT_B 0xFF +#define IS_TRANSPARENT( p ) ( p[0] == TRANSPARENT_R && p[1] == TRANSPARENT_G && p[2] == TRANSPARENT_B ) +#define LERPBYTE( i ) r = resamplerow1[i]; out[i] = (byte)(((( resamplerow2[i] - r ) * lerp)>>16 ) + r ) + +static void Image_Resample32LerpLine( const byte *in, byte *out, int inwidth, int outwidth ) +{ + int j, xi, oldx = 0, f, fstep, endx, lerp; + + fstep = (int)(inwidth * 65536.0f / outwidth); + endx = (inwidth-1); + + for( j = 0, f = 0; j < outwidth; j++, f += fstep ) + { + xi = f>>16; + if( xi != oldx ) + { + in += (xi - oldx) * 4; + oldx = xi; + } + if( xi < endx ) + { + lerp = f & 0xFFFF; + *out++ = (byte)((((in[4] - in[0]) * lerp)>>16) + in[0]); + *out++ = (byte)((((in[5] - in[1]) * lerp)>>16) + in[1]); + *out++ = (byte)((((in[6] - in[2]) * lerp)>>16) + in[2]); + *out++ = (byte)((((in[7] - in[3]) * lerp)>>16) + in[3]); + } + else // last pixel of the line has no pixel to lerp to + { + *out++ = in[0]; + *out++ = in[1]; + *out++ = in[2]; + *out++ = in[3]; + } + } +} + +void Image_Resample32Lerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight ) +{ + const byte *inrow; + int i, j, r, yi, oldy = 0, f, fstep, lerp, endy = (inheight - 1); + int inwidth4 = inwidth * 4; + int outwidth4 = outwidth * 4; + byte *out = (byte *)outdata; + byte *resamplerow1; + byte *resamplerow2; + + fstep = (int)(inheight * 65536.0f / outheight); + + resamplerow1 = (byte *)Mem_Alloc( outwidth * 4 * 2 ); + resamplerow2 = resamplerow1 + outwidth * 4; + + inrow = (const byte *)indata; + + Image_Resample32LerpLine( inrow, resamplerow1, inwidth, outwidth ); + Image_Resample32LerpLine( inrow + inwidth4, resamplerow2, inwidth, outwidth ); + + for( i = 0, f = 0; i < outheight; i++, f += fstep ) + { + yi = f >> 16; + + if( yi < endy ) + { + lerp = f & 0xFFFF; + + if( yi != oldy ) + { + inrow = (byte *)indata + inwidth4 * yi; + if( yi == ( oldy + 1 )) memcpy( resamplerow1, resamplerow2, outwidth4 ); + else Image_Resample32LerpLine( inrow, resamplerow1, inwidth, outwidth ); + Image_Resample32LerpLine( inrow + inwidth4, resamplerow2, inwidth, outwidth ); + oldy = yi; + } + + j = outwidth - 4; + + while( j >= 0 ) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + LERPBYTE( 4); + LERPBYTE( 5); + LERPBYTE( 6); + LERPBYTE( 7); + LERPBYTE( 8); + LERPBYTE( 9); + LERPBYTE(10); + LERPBYTE(11); + LERPBYTE(12); + LERPBYTE(13); + LERPBYTE(14); + LERPBYTE(15); + out += 16; + resamplerow1 += 16; + resamplerow2 += 16; + j -= 4; + } + + if( j & 2 ) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + LERPBYTE( 4); + LERPBYTE( 5); + LERPBYTE( 6); + LERPBYTE( 7); + out += 8; + resamplerow1 += 8; + resamplerow2 += 8; + } + + if( j & 1 ) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + out += 4; + resamplerow1 += 4; + resamplerow2 += 4; + } + + resamplerow1 -= outwidth4; + resamplerow2 -= outwidth4; + } + else + { + if( yi != oldy ) + { + inrow = (byte *)indata + inwidth4 * yi; + if( yi == ( oldy + 1 )) memcpy( resamplerow1, resamplerow2, outwidth4 ); + else Image_Resample32LerpLine( inrow, resamplerow1, inwidth, outwidth ); + oldy = yi; + } + + memcpy( out, resamplerow1, outwidth4 ); + } + } + + Mem_Free( resamplerow1 ); +} + +void Image_Resample8Nolerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight ) +{ + int i, j; + byte *in, *inrow; + size_t frac, fracstep; + byte *out = (byte *)outdata; + + in = (byte *)indata; + fracstep = inwidth * 0x10000 / outwidth; + + for( i = 0; i < outheight; i++, out += outwidth ) + { + inrow = in + inwidth * (i * inheight / outheight); + frac = fracstep >> 1; + + for( j = 0; j < outwidth; j++ ) + { + out[j] = inrow[frac>>16]; + frac += fracstep; + } + } +} + +/* +================ +Image_Resample +================ +*/ +rgbdata_t *Image_Resample( rgbdata_t *pic, int new_width, int new_height ) +{ + if( !pic ) return NULL; + + // nothing to resample ? + if( pic->width == new_width && pic->height == new_height ) + return pic; + + MsgDev( D_REPORT, "Image_Resample: from %ix%i to %ix%i\n", pic->width, pic->height, new_width, new_height ); + + rgbdata_t *out = Image_Alloc( new_width, new_height, FBitSet( pic->flags, IMAGE_QUANTIZED )); + + if( FBitSet( pic->flags, IMAGE_QUANTIZED )) + Image_Resample8Nolerp( pic->buffer, pic->width, pic->height, out->buffer, out->width, out->height ); + else Image_Resample32Lerp( pic->buffer, pic->width, pic->height, out->buffer, out->width, out->height ); + + // copy remaining data from source + if( FBitSet( pic->flags, IMAGE_QUANTIZED )) + memcpy( out->palette, pic->palette, 1024 ); + out->flags = pic->flags; + + // release old image + Mem_Free( pic ); + + return out; +} + +/* +================ +Image_ExtractAlphaMask + +we can't store alpha-channel into 8-bit texture +but we can store it separate as another image +================ +*/ +rgbdata_t *Image_ExtractAlphaMask( rgbdata_t *pic ) +{ + rgbdata_t *out; + + if( !pic ) return NULL; + + if( FBitSet( pic->flags, IMAGE_QUANTIZED )) + return NULL; // can extract only from RGBA buffer + + if( !FBitSet( pic->flags, IMAGE_HAS_ALPHA )) + return NULL; // no alpha-channel stored + + out = Image_Copy( pic ); // duplicate the image + + for( int i = 0; i < pic->width * pic->height; i++ ) + { + // copy the alpha into color buffer + out->buffer[i*4+0] = pic->buffer[i*4+3]; + out->buffer[i*4+1] = pic->buffer[i*4+3]; + out->buffer[i*4+2] = pic->buffer[i*4+3]; + out->buffer[i*4+3] = 0xFF; // clear the alpha + } + + ClearBits( out->flags, IMAGE_HAS_COLOR ); + ClearBits( out->flags, IMAGE_HAS_ALPHA ); + + return out; +} + +/* +================ +Image_MergeColorAlpha + +we can't store alpha-channel into 8-bit texture +but we can store it separate as another image +================ +*/ +rgbdata_t *Image_MergeColorAlpha( rgbdata_t *color, rgbdata_t *alpha ) +{ + rgbdata_t *int_alpha; + byte avalue; + + if( !color ) return NULL; + if( !alpha ) return color; + + if( FBitSet( color->flags|alpha->flags, IMAGE_QUANTIZED )) + return color; // can't merge compressed formats + + int_alpha = Image_Copy( alpha ); // duplicate the image + + if( color->width != alpha->width || color->height != alpha->height ) + { + Image_Resample( int_alpha, color->width, color->height ); + } + + for( int i = 0; i < color->width * color->height; i++ ) + { + // copy the alpha into color buffer (just use R instead?) + avalue = (int_alpha->buffer[i*4+0] + int_alpha->buffer[i*4+1] + int_alpha->buffer[i*4+2]) / 3; + + if( avalue != 255 ) + { + if( avalue != 0 ) + { + SetBits( color->flags, IMAGE_HAS_8BIT_ALPHA ); + ClearBits( color->flags, IMAGE_HAS_1BIT_ALPHA ); + } + else if( !FBitSet( color->flags, IMAGE_HAS_8BIT_ALPHA )) + SetBits( color->flags, IMAGE_HAS_1BIT_ALPHA ); + } + + color->buffer[i*4+3] = avalue; + } + + Mem_Free( int_alpha ); + + return color; +} + +/* +============== +Image_MakeOneBitAlpha + +remap all pixels of color 0, 0, 255 to index 255 +and remap index 255 to something else +============== +*/ +void Image_MakeOneBitAlpha( rgbdata_t *pic ) +{ + byte transtable[256], *buf; + int i, j, firsttrans = -1; + + if( !pic ) return; + + if( !FBitSet( pic->flags, IMAGE_QUANTIZED )) + return; // only for quantized images + + // don't move colors in quake palette! + if( FBitSet( pic->flags, IMAGE_QUAKE1_PAL )) + { + // needs for software mip generator + SetBits( pic->flags, IMAGE_HAS_1BIT_ALPHA ); + return; + } + + for( i = 0; i < 256; i++ ) + { + if( IS_TRANSPARENT(( pic->palette + ( i * 4 )))) + { + transtable[i] = 255; + if( firsttrans < 0 ) + firsttrans = i; + } + else transtable[i] = i; + } + + // if there is some transparency, translate it + if( firsttrans >= 0 ) + { + if( !IS_TRANSPARENT(( pic->palette + ( 255 * 4 )))) + transtable[255] = firsttrans; + buf = pic->buffer; + + for( j = 0; j < pic->height; j++ ) + { + for( i = 0; i < pic->width; i++ ) + { + *buf = transtable[*buf]; + buf++; + } + } + + // move palette entry for pixels previously mapped to entry 255 + pic->palette[firsttrans*4+0] = pic->palette[255*4+0]; + pic->palette[firsttrans*4+1] = pic->palette[255*4+1]; + pic->palette[firsttrans*4+2] = pic->palette[255*4+2]; + pic->palette[firsttrans*4+3] = pic->palette[255*4+3]; + pic->palette[255*4+0] = TRANSPARENT_R; + pic->palette[255*4+1] = TRANSPARENT_G; + pic->palette[255*4+2] = TRANSPARENT_B; + pic->palette[255*4+3] = 0xFF; + } + + // needs for software mip generator + SetBits( pic->flags, IMAGE_HAS_1BIT_ALPHA ); +} \ No newline at end of file diff --git a/utils/makewad/lmptex.cpp b/utils/makewad/lmptex.cpp new file mode 100644 index 0000000..2c98863 --- /dev/null +++ b/utils/makewad/lmptex.cpp @@ -0,0 +1,155 @@ +/* +lmptex.cpp - prepare and store lmptex into the wad +Copyright (C) 2018 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "conprint.h" +#include +#include +#include +#include +#include +#include +#include "cmdlib.h" +#include "stringlib.h" +#include "filesystem.h" +#include "imagelib.h" +#include "makewad.h" +#include "miptex.h" + +bool LMP_WriteLmptex( const char *lumpname, rgbdata_t *pix, bool todisk ) +{ + bool result; + lmp_t *lmp; + + // check for all the possible problems + if( !pix || !FBitSet( pix->flags, IMAGE_QUANTIZED )) + return false; + + // lmp may have any dimensions + if( pix->width < IMAGE_MINWIDTH || pix->width > IMAGE_MAXWIDTH || pix->height < IMAGE_MINHEIGHT || pix->height > IMAGE_MAXHEIGHT ) + return false; // to small or too large + + // calculate gamma corrected linear palette + for( int i = 0; i < 256; i++ ) + { + // setup palette + lbmpalette[i*3+0] = pix->palette[i*4+0]; + lbmpalette[i*3+1] = pix->palette[i*4+1]; + lbmpalette[i*3+2] = pix->palette[i*4+2]; + } + + size_t pixels = pix->width * pix->height; + size_t lumpsize = sizeof( lmp_t ) + pixels + sizeof( short ) + 768; + byte *lumpbuffer, *lump_p; + + // all the lumps must be aligned by 4 + // or Wally will stop working properly + lumpsize = (lumpsize + 3) & ~3; + + lumpbuffer = lump_p = (byte *)Mem_Alloc( lumpsize ); + lmp = (lmp_t *)lumpbuffer; + + // fill the header + lmp->width = pix->width; + lmp->height = pix->height; + lump_p += sizeof( lmp_t ); + + // write lmptex + memcpy( lump_p, pix->buffer, pixels ); + lump_p += pixels; + + // write out palette + *(unsigned short *)lump_p = 256; // palette size + lump_p += sizeof( short ); + + memcpy( lump_p, lbmpalette, 768 ); + lump_p += 768; + + size_t disksize = (( lump_p - lumpbuffer ) + 3) & ~3; + + if( lumpsize != disksize ) + MsgDev( D_ERROR, "%s is corrupted (buffer is %s bytes, written %s)\n", lumpname, Q_memprint( lumpsize ), Q_memprint( disksize )); + + if( todisk ) result = COM_SaveFile( lumpname, lumpbuffer, lumpsize ); + else result = W_SaveLump( output_wad, lumpname, lumpbuffer, lumpsize, TYP_GFXPIC, ATTR_NONE ) >= 0; + + Mem_Free( lumpbuffer ); + + return result; +} + +bool LMP_CheckForReplace( dlumpinfo_t *find, rgbdata_t *image, int &width, int &height ) +{ + // NOTE: we can replace this lump but this is unsafe + if( find != NULL ) + { + size_t lumpsize = ((sizeof( lmp_t ) + ( width * height ) + sizeof( short ) + 768) + 3) & ~3; + + switch( GetReplaceLevel( )) + { + case REP_IGNORE: + MsgDev( D_ERROR, "LMP_CreateLmptex: %s already exist\n", find->name ); + if( image ) Mem_Free( image ); + return false; + case REP_NORMAL: + if( FBitSet( find->attribs, ATTR_READONLY )) + { + // g-cont. i left this limitation as a protect of the replacement of compressed lumps + MsgDev( D_ERROR, "W_ReplaceLump: %s is read-only\n", find->name ); + if( image ) Mem_Free( image ); + return false; + } + if( lumpsize != find->size ) + { + MsgDev( D_ERROR, "W_ReplaceLump: %s.lmp [%s] should be [%s]\n", + find->name, Q_memprint( lumpsize ), Q_memprint( find->size )); + if( image ) Mem_Free( image ); + return false; + } + break; + case REP_FORCE: + if( lumpsize != find->size ) + { + size_t oldpos; + lmp_t test; + + oldpos = tell( W_GetHandle( output_wad ) ); // don't forget restore original position + + if( lseek( W_GetHandle( output_wad ), find->filepos, SEEK_SET ) == -1 ) + { + MsgDev( D_ERROR, "W_ReplaceLump: %s is corrupted\n", find->name ); + lseek( W_GetHandle( output_wad ), oldpos, SEEK_SET ); + if( image ) Mem_Free( image ); + return false; + } + + if( read( W_GetHandle( output_wad ), &test, sizeof( test )) != sizeof( test )) + { + MsgDev( D_ERROR, "W_ReplaceLump: %s is corrupted\n", find->name ); + lseek( W_GetHandle( output_wad ), oldpos, SEEK_SET ); + if( image ) Mem_Free( image ); + return false; + } + + // get new sizes from the saved lump to resample current + lseek( W_GetHandle( output_wad ), oldpos, SEEK_SET ); + height = test.height; + width = test.width; + } + break; + } + } + + return true; +} \ No newline at end of file diff --git a/utils/makewad/makewad.cpp b/utils/makewad/makewad.cpp new file mode 100644 index 0000000..b8e791b --- /dev/null +++ b/utils/makewad/makewad.cpp @@ -0,0 +1,507 @@ +/* +makewad.cpp - convert textures into 8-bit indexed and pack into wad3 file +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "conprint.h" +#include +#include +#include +#include +#include +#include "cmdlib.h" +#include "stringlib.h" +#include "filesystem.h" +#include "imagelib.h" +#include "makewad.h" + +wfile_t *source_wad = NULL; // input WAD3 file +wfile_t *output_wad = NULL; // output WAD3 file +char output_path[256]; +char output_ext[8]; +float resize_percent = 0.0f; +int processed_files = 0; +int processed_errors = 0; +int graphics_wadfile = 0; + +// just for debug +void Test_ConvertImageTo8Bit( const char *filename ) +{ + char outpath[256]; + rgbdata_t *pic; + + MsgDev( D_INFO, "Quantize %s\n", filename ); + + pic = COM_LoadImage( filename ); + if( !pic ) + { + MsgDev( D_ERROR, "couldn't load (%s)\n", filename ); + return; + } + + pic = Image_Quantize( pic ); + Q_strncpy( outpath, filename, sizeof( outpath )); + COM_StripExtension( outpath ); + Q_strncat( outpath, "_8bit.bmp", sizeof( outpath )); + + if( COM_SaveImage( outpath, pic )) + MsgDev( D_INFO, "Write %s\n", outpath ); + else MsgDev( D_INFO, "Failed to save %s\n", outpath ); + + Mem_Free( pic ); +} + +/* +============= +WAD_CreateTexture + +put the texture in the wad or +extract her onto disk +============= +*/ +bool WAD_CreateTexture( const char *filename ) +{ + const char *ext = COM_FileExtension( filename ); + int width, height, len = WAD3_NAMELEN; + dlumpinfo_t *find, *find2; + char lumpname[64], hint; + int mipwidth, mipheight; + rgbdata_t *image; + + // store name for detect suffixes + COM_FileBase( filename, lumpname ); + hint = Image_HintFromSuf( lumpname ); + + if( hint != IMG_DIFFUSE ) + { + MsgDev( D_ERROR, "WAD_CreateTexture: non-diffuse texture %s rejected\n", lumpname ); + return false; // only diffuse textures can be passed + } + + if( Q_strlen( lumpname ) > len ) + { + // NOTE: technically we can cutoff too long names but it just not needs + MsgDev( D_ERROR, "WAD_CreateTexture: %s more than %i symbols\n", lumpname, len ); + return false; + } + + image = COM_LoadImage( filename ); + if( !image ) return false; + + width = mipwidth = image->width; + height = mipheight = image->height; + + // wad-copy mode: wad->wad + if( source_wad && output_wad ) + { + if( !Q_stricmp( ext, "mip" )) + { + find = W_FindMiptex( source_wad, lumpname ); + + if( !find ) + { + Mem_Free( image ); + return false; + } + + find2 = W_FindMiptex( output_wad, lumpname ); + + if( !MIP_CheckForReplace( find2, image, mipwidth, mipheight )) + return false; // NOTE: image already freed on failed + + // fit to the replacement + image = Image_Resample( image, mipwidth, mipheight ); + image = Image_Quantize( image ); // just in case. + + bool result = MIP_WriteMiptex( lumpname, image ); + + if( result ) MsgDev( D_INFO, "%s.mip\n", lumpname ); + else MsgDev( D_ERROR, "coudln't copy: %s.mip\n", lumpname ); + + Mem_Free( image ); + + return result; + } + else if( !Q_stricmp( ext, "lmp" )) + { + find = W_FindLmptex( source_wad, lumpname ); + + if( !find ) + { + Mem_Free( image ); + return false; + } + + find2 = W_FindLmptex( output_wad, lumpname ); + + if( !LMP_CheckForReplace( find2, image, mipwidth, mipheight )) + return false; // NOTE: image already freed on failed + + // fit to the replacement + image = Image_Resample( image, mipwidth, mipheight ); + image = Image_Quantize( image ); // just in case. + + bool result = LMP_WriteLmptex( lumpname, image ); + + if( result ) MsgDev( D_INFO, "%s.lmp\n", lumpname ); + else MsgDev( D_ERROR, "coudln't copy: %s.lmp\n", lumpname ); + + Mem_Free( image ); + + return result; + } + + // unknown format? + Mem_Free( image ); + return false; + } + + // extraction or conversion mode: wad->bmp, wad->tga + if( !output_wad && output_path[0] && output_ext[0] ) + { + char real_ext[8]; + + // set default extension + Q_strcpy( real_ext, output_ext ); + + // wad-extract mode + const char *path = va( "%s\\%s.%s", output_path, lumpname, real_ext ); + bool result = COM_SaveImage( path, image ); + + if( result ) MsgDev( D_INFO, "%s\n", path ); + else MsgDev( D_ERROR, "coudln't save: %s\n", path ); + + Mem_Free( image ); + + return result; + } + + // normal mode: bmp->wad, tga->wad + if( graphics_wadfile ) + { + if( !FBitSet( image->flags, IMAGE_QUANTIZED )) + { + // check for minmax sizes + mipwidth = bound( IMAGE_MINWIDTH, mipwidth, IMAGE_MAXWIDTH ); + mipheight = bound( IMAGE_MINHEIGHT, mipheight, IMAGE_MAXHEIGHT ); + + find2 = W_FindLmptex( output_wad, lumpname ); + + if( !LMP_CheckForReplace( find2, image, mipwidth, mipheight )) + return false; // NOTE: image already freed on failed + + rgbdata_t *rgba_image = Image_Copy( image ); + + // align by 16 or fit to the replacement + image = Image_Resample( image, mipwidth, mipheight ); + + // now quantize image + image = Image_Quantize( image ); + + bool result = LMP_WriteLmptex( lumpname, image ); + + if( result ) MsgDev( D_INFO, "%s.lmp\n", lumpname ); + else MsgDev( D_ERROR, "coudln't write: %s.lmp\n", lumpname ); + + Mem_Free( rgba_image ); + Mem_Free( image ); // no reason to keep it + + return result; + } + else // image already indexed + { + // check for minmax sizes + mipwidth = bound( IMAGE_MINWIDTH, mipwidth, IMAGE_MAXWIDTH ); + mipheight = bound( IMAGE_MINHEIGHT, mipheight, IMAGE_MAXHEIGHT ); + + find = W_FindLmptex( output_wad, lumpname ); + + if( !LMP_CheckForReplace( find, image, mipwidth, mipheight )) + return false; // NOTE: image already freed on failed + + // align by 16 or fit to the replacement + image = Image_Resample( image, mipwidth, mipheight ); + + bool result = LMP_WriteLmptex( lumpname, image ); + + if( result ) MsgDev( D_INFO, "%s.lmp\n", lumpname ); + else MsgDev( D_ERROR, "coudln't write: %s.lmp\n", lumpname ); + if( image ) Mem_Free( image ); + + return result; + } + } + else + { + // all the mips must be aligned by 16 + width = (width + 7) & ~7; + height = (height + 7) & ~7; + + if( !FBitSet( image->flags, IMAGE_QUANTIZED )) + { + // check for minmax sizes + mipwidth = bound( IMAGE_MINWIDTH, mipwidth, IMAGE_MAXWIDTH ); + mipheight = bound( IMAGE_MINHEIGHT, mipheight, IMAGE_MAXHEIGHT ); + + if( resize_percent != 0.0f ) + { + mipwidth *= (resize_percent / 100.0f); + mipheight *= (resize_percent / 100.0f); + } + + // all the mips must be aligned by 16 + mipwidth = (mipwidth + 7) & ~7; + mipheight = (mipheight + 7) & ~7; + + find2 = W_FindMiptex( output_wad, lumpname ); + + if( !MIP_CheckForReplace( find2, image, mipwidth, mipheight )) + return false; // NOTE: image already freed on failed + + rgbdata_t *rgba_image = Image_Copy( image ); + + // align by 16 or fit to the replacement + image = Image_Resample( image, mipwidth, mipheight ); + + // now quantize image + image = Image_Quantize( image ); + + if( lumpname[0] == '{' ) + Image_MakeOneBitAlpha( image ); // make one-bit alpha from blue color + + bool result = MIP_WriteMiptex( lumpname, image ); + + if( result ) MsgDev( D_INFO, "%s.mip\n", lumpname ); + else MsgDev( D_ERROR, "coudln't write: %s.mip\n", lumpname ); + + Mem_Free( rgba_image ); + Mem_Free( image ); // no reason to keep it + + return result; + } + else // image already indexed + { + // check for minmax sizes + mipwidth = bound( IMAGE_MINWIDTH, mipwidth, IMAGE_MAXWIDTH ); + mipheight = bound( IMAGE_MINHEIGHT, mipheight, IMAGE_MAXHEIGHT ); + + if( resize_percent != 0.0f ) + { + mipwidth *= (resize_percent / 100.0f); + mipheight *= (resize_percent / 100.0f); + } + + // all the mips must be aligned by 16 + mipwidth = (mipwidth + 7) & ~7; + mipheight = (mipheight + 7) & ~7; + + find = W_FindMiptex( output_wad, lumpname ); + + if( !MIP_CheckForReplace( find, image, mipwidth, mipheight )) + return false; // NOTE: image already freed on failed + + // align by 16 or fit to the replacement + image = Image_Resample( image, mipwidth, mipheight ); + + if( lumpname[0] == '{' ) + Image_MakeOneBitAlpha( image ); // make one-bit alpha from blue color + bool result = MIP_WriteMiptex( lumpname, image ); + + if( result ) MsgDev( D_INFO, "%s.mip\n", lumpname ); + else MsgDev( D_ERROR, "coudln't write: %s.mip\n", lumpname ); + if( image ) Mem_Free( image ); + + return result; + } + } + + return false; +} + +void CDECL Shutdown_Makewad( void ) +{ + W_Close( source_wad ); + W_Close( output_wad ); +} + +int main( int argc, char **argv ) +{ + char srcpath[256], dstpath[256]; + char srcwad[256], dstwad[256]; + bool srcset = false; + bool dstset = false; + double start, end; + char str[64]; + int i; + + start = I_FloatTime(); + + Msg( " Image Quantizer & Wad3 creator\n" ); + Msg( " Xash XT Group 2018(^1c^7)\n\n\n" ); + + atexit( Shutdown_Makewad ); + + // initialize command vars + SetReplaceLevel( REP_IGNORE ); + output_path[0] = '\0'; + output_ext[0] = '\0'; + + for( i = 1; i < argc; i++ ) + { + if( !Q_stricmp( argv[i], "-input" )) + { + Q_strncpy( srcpath, argv[i+1], sizeof( srcpath )); + srcset = true; + i++; + } + else if( !Q_stricmp( argv[i], "-output" )) + { + Q_strncpy( dstpath, argv[i+1], sizeof( dstpath )); + dstset = true; + i++; + } + else if( !Q_stricmp( argv[i], "-replace" )) + { + SetReplaceLevel( REP_NORMAL ); // replace only if image dimensions is equal + } + else if( !Q_stricmp( argv[i], "-forcereplace" )) + { + SetReplaceLevel( REP_FORCE ); // rescale image, replace in any cases + } + else if( !Q_stricmp( argv[i], "-resize" )) + { + resize_percent = atof( argv[i+1] ); // resize source image (percent) + resize_percent = bound( 10.0f, resize_percent, 200.0f ); + i++; + } + else if( !Q_stricmp( argv[i], "-dev" )) + { + SetDeveloperLevel( atoi( argv[i+1] )); + i++; + } + else + { + Msg( "makewad: unknown option %s\n", argv[i] ); + break; + } + } + + if( i != argc || !srcset || !dstset ) + { + Msg( "Usage: -input -output \n" + "\nlist options:\n" + "^2-replace^7 - replace existing images if they matched by size\n" + "^2-forcereplace^7 - replace existing images even if they doesn't matched by size\n" + "^2-resize^7 - resize source image in percents (range 10%%-200%%)\n\n" + "\t\tPress any key to exit" ); + + system( "pause>nul" ); + return 1; + } + else + { + char testname[64]; + char *find = NULL; + + Q_strncpy( srcwad, srcpath, sizeof( srcwad )); + find = Q_stristr( srcwad, ".wad" ); + + if( find ) + { + find += 4, *find = '\0'; + source_wad = W_Open( srcwad, "rb" ); + } + + search_t *search = COM_Search( srcpath, true, source_wad ); + if( !search ) return 1; // nothing found + + Q_strncpy( dstwad, dstpath, sizeof( dstwad )); + find = Q_stristr( dstwad, ".wad" ); + + if( find ) + { + find += 4, *find = '\0'; + output_wad = W_Open( dstwad, "a+b" ); + COM_FileBase( dstwad, testname ); + + if( !Q_stricmp( testname, "gfx" ) || !Q_stricmp( testname, "cached" )) + { + Msg( "graphics wad detected\n" ); + graphics_wadfile = 1; + } + } + else + { + const char *ext = COM_FileExtension( srcwad ); + + if( !Q_stricmp( dstpath, "bmp" ) || !Q_stricmp( dstpath, "tga" ) || !Q_stricmp( dstpath, "lmp" )) + { + COM_ExtractFilePath( srcwad, output_path ); + COM_StripExtension( output_path ); + Q_strncpy( output_ext, dstpath, sizeof( output_ext )); + if( !output_path[0] ) _getcwd( output_path, sizeof( output_path )); + } + else + { + Msg( "Usage: -input -output \n" + "\nlist options:\n" + "^2-replace^7 - replace existing images if they matched by size\n" + "^2-forcereplace^7 - replace existing images even if they doesn't matched by size\n" + "^2-resize^7 - resize source image in percents (range 10%%-200%%)\n\n" + "\t\tPress any key to exit" ); + + Mem_Free( search ); + system( "pause>nul" ); + return 1; + } + } + + if( source_wad && !output_wad && output_path[0] && output_ext[0] ) + { + MsgDev( D_INFO, "extract textures from %s\n", srcwad ); + } + else if( source_wad && output_wad ) + { + MsgDev( D_INFO, "copying textures from %s to %s\n", srcwad, dstwad ); + } + else + { + MsgDev( D_INFO, "write textures into %s\n", dstwad ); + } + + for( i = 0; i < search->numfilenames; i++ ) + { + if( WAD_CreateTexture( search->filenames[i] )) + processed_files++; + else processed_errors++; + } + + end = I_FloatTime(); + + MsgDev( D_INFO, "%3i files processed, %3i errors\n", processed_files, processed_errors ); + + Q_timestring((int)(end - start), str ); + MsgDev( D_INFO, "%s elapsed\n", str ); + + W_Close( source_wad ); + source_wad = NULL; + + W_Close( output_wad ); + output_wad = NULL; + + Mem_Free( search ); + Mem_Check(); // report leaks + } + + return 0; +} \ No newline at end of file diff --git a/utils/makewad/makewad.dsp b/utils/makewad/makewad.dsp new file mode 100644 index 0000000..4d66d90 --- /dev/null +++ b/utils/makewad/makewad.dsp @@ -0,0 +1,179 @@ +# Microsoft Developer Studio Project File - Name="makewad" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=makewad - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "makewad.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "makewad.mak" CFG="makewad - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "makewad - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "makewad - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/SDKSrc/Tools/utils/makewad", IVGBAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "makewad - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir ".\Release" +# PROP BASE Intermediate_Dir ".\Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\..\temp\makewad\!release" +# PROP Intermediate_Dir "..\..\temp\makewad\!release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /W3 /GX /O2 /I "../../common" /I "../common" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /FD /c +# SUBTRACT CPP /Fr /YX +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:console /machine:I386 /opt:nowin98 +# ADD LINK32 /nologo /subsystem:console /pdb:none /machine:I386 /nodefaultlib:"libc libcmt" /libpath:"./" /opt:nowin98 +# SUBTRACT LINK32 /nodefaultlib +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\makewad\!release +InputPath=\Paranoia2\src_main\temp\makewad\!release\makewad.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\makewad.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\makewad.exe "D:\Paranoia2\tools\makewad.exe" + +# End Custom Build + +!ELSEIF "$(CFG)" == "makewad - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir ".\Debug" +# PROP BASE Intermediate_Dir ".\Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\..\temp\makewad\!debug" +# PROP Intermediate_Dir "..\..\temp\makewad\!debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MDd /W3 /Gm /Gi /GX /ZI /Od /I "../../common" /I "../common" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /FAs /FR /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:console /debug /machine:I386 +# ADD LINK32 msvcrtd.lib /nologo /subsystem:console /debug /machine:I386 /nodefaultlib:"libc.lib" /libpath:"./" +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\makewad\!debug +InputPath=\Paranoia2\src_main\temp\makewad\!debug\makewad.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\makewad.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\makewad.exe "D:\Paranoia2\tools\makewad.exe" + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "makewad - Win32 Release" +# Name "makewad - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=..\common\cmdlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\conprint.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\filesystem.cpp +# End Source File +# Begin Source File + +SOURCE=.\imagelib.cpp +# End Source File +# Begin Source File + +SOURCE=.\lmptex.cpp +# End Source File +# Begin Source File + +SOURCE=.\makewad.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\mathlib.cpp +# End Source File +# Begin Source File + +SOURCE=.\miptex.cpp +# End Source File +# Begin Source File + +SOURCE=.\quantizer.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\stringlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\wadfile.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\zone.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=..\common\filesystem.h +# End Source File +# Begin Source File + +SOURCE=.\imagelib.h +# End Source File +# Begin Source File + +SOURCE=.\makewad.h +# End Source File +# Begin Source File + +SOURCE=..\common\stringlib.h +# End Source File +# End Group +# End Target +# End Project diff --git a/utils/makewad/makewad.h b/utils/makewad/makewad.h new file mode 100644 index 0000000..4480ca0 --- /dev/null +++ b/utils/makewad/makewad.h @@ -0,0 +1,28 @@ +/* +makewad.h - convert textures into 8-bit indexed and pack into wad3 file +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef MAKEWAD_H +#define MAKEWAD_H + +#include "cmdlib.h" +#include "filesystem.h" +#include "miptex.h" + +extern wfile_t *source_wad; +extern wfile_t *output_wad; +extern char output_path[256]; +extern float resize_percent; + +#endif//MAKEWAD_H \ No newline at end of file diff --git a/utils/makewad/miptex.cpp b/utils/makewad/miptex.cpp new file mode 100644 index 0000000..79ab444 --- /dev/null +++ b/utils/makewad/miptex.cpp @@ -0,0 +1,371 @@ +/* +miptex.cpp - prepare and store miptex into the wad +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "conprint.h" +#include +#include +#include +#include +#include +#include +#include "cmdlib.h" +#include "stringlib.h" +#include "filesystem.h" +#include "imagelib.h" +#include "makewad.h" +#include "miptex.h" + +byte lbmpalette[256*3]; +float linearpalette[256][3]; +float d_red, d_green, d_blue; +bool color_used[256]; +float maxdistortion; +int colors_used; +byte pixdata[256]; + +/* +============= +MIP_AddColor + +add unique color and restore original gamma +============= +*/ +byte MIP_AddColor( float r, float g, float b ) +{ + // one color as reserved for transparent + for( int i = 0; i < 255; i++ ) + { + if( !color_used[i] ) + { + linearpalette[i][0] = r; + linearpalette[i][1] = g; + linearpalette[i][2] = b; + + r = bound( 0.0f, r, 1.0f ); + lbmpalette[i*3+0] = (byte)pow( r, INVGAMMA ) * 255; + g = bound( 0.0f, g, 1.0f ); + lbmpalette[i*3+1] = (byte)pow( g, INVGAMMA ) * 255; + r = bound( 0.0f, r, 1.0f ); + lbmpalette[i*3+2] = (byte)pow( b, INVGAMMA ) * 255; + + color_used[i] = true; + colors_used++; + + return i; + } + } + + return 0; +} + +/* +============= +MIP_AveragePixels + +average pixels for mip-mapping +============= +*/ +byte MIP_AveragePixels( int count ) +{ + float bestdistortion, distortion; + float r, g, b, dr, dg, db; + int i, pix, bestcolor; + + r = g = b = 0.0f; + + for( i = 0; i < count; i++ ) + { + pix = pixdata[i]; + r += linearpalette[pix][0]; + g += linearpalette[pix][1]; + b += linearpalette[pix][2]; + } + + r /= count; + g /= count; + b /= count; + + r += d_red; + g += d_green; + b += d_blue; + + // find the best color + bestdistortion = 3.0; + bestcolor = -1; + + for( i = 0; i < 255; i++ ) + { + if( color_used[i] ) + { + pix = i; + dr = r - linearpalette[i][0]; + dg = g - linearpalette[i][1]; + db = b - linearpalette[i][2]; + + distortion = (dr * dr) + (dg * dg) + (db * db); + + if( distortion < bestdistortion ) + { + if( !distortion ) + { + d_red = d_green = d_blue = 0.0f; // no distortion yet + return pix; // perfect match + } + + bestdistortion = distortion; + bestcolor = pix; + } + } + } + + if( bestdistortion > 0.001f && colors_used < 255 ) + { + bestcolor = MIP_AddColor( r, g, b ); + d_red = d_green = d_blue = 0.0f; + bestdistortion = 0.0f; + } + else + { + // error diffusion + d_red = r - linearpalette[bestcolor][0]; + d_green = g - linearpalette[bestcolor][1]; + d_blue = b - linearpalette[bestcolor][2]; + } + + if( bestdistortion > maxdistortion ) + maxdistortion = bestdistortion; + + return bestcolor; +} + +bool MIP_WriteMiptex( const char *lumpname, rgbdata_t *pix ) +{ + char tmpname[64]; + mip_t *mip; + + // check for all the possible problems + if( !pix || !FBitSet( pix->flags, IMAGE_QUANTIZED )) + return false; + + if(( pix->width & 7 ) || ( pix->height & 7 )) + return false; // not aligned by 16 + + if( pix->width < IMAGE_MINWIDTH || pix->width > IMAGE_MAXWIDTH || pix->height < IMAGE_MINHEIGHT || pix->height > IMAGE_MAXHEIGHT ) + return false; // to small or too large + + // calculate gamma corrected linear palette + for( int i = 0; i < 256; i++ ) + { + // setup palette + lbmpalette[i*3+0] = pix->palette[i*4+0]; + lbmpalette[i*3+1] = pix->palette[i*4+1]; + lbmpalette[i*3+2] = pix->palette[i*4+2]; + + for( int j = 0; j < 3; j++ ) + { + float f = lbmpalette[i*3+j] / 255.0f; + linearpalette[i][j] = pow( f, GAMMA ); // assume textures are done at 2.2, we want to remap them at 1.0 + } + } + + char hint = W_HintFromSuf( lumpname ); + bool all_colors = false; + + size_t pixels = pix->width * pix->height; + size_t lumpsize = sizeof( mip_t ) + (( pixels * 85 )>>6) + sizeof( short ) + 768; + byte *lumpbuffer, *lump_p; + + // all the lumps must be aligned by 4 + // or Wally will stop working properly + lumpsize = (lumpsize + 3) & ~3; + maxdistortion = 0.0f; + + if( FBitSet( pix->flags, IMAGE_HAS_1BIT_ALPHA )) + all_colors = true; + + if( hint == IMG_NORMALMAP ) + all_colors = true; + + if( all_colors ) + { + // assume palette full for some reasons + for( int i = 0; i < 256; i++ ) + color_used[i] = true; + colors_used = 256; + } + else + { + // figure out what palette entries are actually used + for( int i = 0; i < 256; i++ ) + color_used[i] = false; + colors_used = 0; + + for( int x = 0; x < pix->width; x++ ) + { + for( int y = 0; y < pix->height; y++ ) + { + int color = pix->buffer[y * pix->width + x]; + + if( !color_used[color] ) + { + color_used[color] = true; + colors_used++; + } + } + } + + MsgDev( D_REPORT, "%s (colors %i)\n", lumpname, colors_used ); + } + + lumpbuffer = lump_p = (byte *)Mem_Alloc( lumpsize ); + mip = (mip_t *)lumpbuffer; + + // prepare lump name + Q_strncpy( tmpname, lumpname, sizeof( tmpname )); + if( hint != IMG_DIFFUSE ) tmpname[Q_strlen( tmpname ) - HINT_NAMELEN] = '\0'; // kill the suffix + + // fill the header + Q_strncpy( mip->name, tmpname, WAD3_NAMELEN ); + mip->width = pix->width; + mip->height = pix->height; + mip->offsets[0] = sizeof( mip_t ); + lump_p += sizeof( mip_t ); + + // write miptex + memcpy( lump_p, pix->buffer, pixels ); + lump_p += pixels; + + // subsample for greater mip levels + for( int miplevel = 1; miplevel < 4; miplevel++ ) + { + int mipstep = BIT( miplevel ); + int pixTest = (int)((float)(mipstep * mipstep) * 0.4f ); // 40% of pixels + + mip->offsets[miplevel] = lump_p - (byte *)mip; + d_red = d_green = d_blue = 0.0f; // no distortion yet + + for( int y = 0; y < pix->height; y += mipstep ) + { + for( int x = 0; x < pix->width; x += mipstep ) + { + int count = 0; + + for( int yy = 0; yy < mipstep; yy++ ) + { + for( int xx = 0; xx < mipstep; xx++ ) + { + byte testpixel = pix->buffer[(y + yy) * pix->width + x + xx]; + + // if 255 is not transparent, or this isn't a transparent pixel, + // add it in to the image filter + if( !FBitSet( pix->flags, IMAGE_HAS_1BIT_ALPHA ) || testpixel != 255 ) + { + pixdata[count] = testpixel; + count++; + } + } + } + + // solid pixels account for < 40% of this pixel, make it transparent + if( count > pixTest ) + *lump_p++ = MIP_AveragePixels( count ); + else *lump_p++ = 255; + } + } + } + + // write out palette + *(unsigned short *)lump_p = 256; // palette size + lump_p += sizeof( short ); + + memcpy( lump_p, lbmpalette, 768 ); + lump_p += 768; + + size_t disksize = (( lump_p - lumpbuffer ) + 3) & ~3; + + if( lumpsize != disksize ) + MsgDev( D_ERROR, "%s is corrupted (buffer is %s bytes, written %s)\n", lumpname, Q_memprint( lumpsize ), Q_memprint( disksize )); + + bool result = W_SaveLump( output_wad, lumpname, lumpbuffer, lumpsize, TYP_MIPTEX, ATTR_NONE ) >= 0; + + Mem_Free( lumpbuffer ); + + return result; +} + +bool MIP_CheckForReplace( dlumpinfo_t *find, rgbdata_t *image, int &width, int &height ) +{ + // NOTE: we can replace this lump but this is unsafe + if( find != NULL ) + { + size_t lumpsize = ((sizeof( mip_t ) + ((( width * height ) * 85 )>>6) + sizeof( short ) + 768) + 3) & ~3; + + switch( GetReplaceLevel( )) + { + case REP_IGNORE: + MsgDev( D_ERROR, "MIP_CreateMiptex: %s already exist\n", find->name ); + if( image ) Mem_Free( image ); + return false; + case REP_NORMAL: + if( FBitSet( find->attribs, ATTR_READONLY )) + { + // g-cont. i left this limitation as a protect of the replacement of compressed lumps + MsgDev( D_ERROR, "W_ReplaceLump: %s is read-only\n", find->name ); + if( image ) Mem_Free( image ); + return false; + } + if( lumpsize != find->size ) + { + MsgDev( D_ERROR, "W_ReplaceLump: %s.mip [%s] should be [%s]\n", + find->name, Q_memprint( lumpsize ), Q_memprint( find->size )); + if( image ) Mem_Free( image ); + return false; + } + break; + case REP_FORCE: + if( lumpsize != find->size ) + { + size_t oldpos; + mip_t test; + + oldpos = tell( W_GetHandle( output_wad ) ); // don't forget restore original position + + if( lseek( W_GetHandle( output_wad ), find->filepos, SEEK_SET ) == -1 ) + { + MsgDev( D_ERROR, "W_ReplaceLump: %s is corrupted\n", find->name ); + lseek( W_GetHandle( output_wad ), oldpos, SEEK_SET ); + if( image ) Mem_Free( image ); + return false; + } + + if( read( W_GetHandle( output_wad ), &test, sizeof( test )) != sizeof( test )) + { + MsgDev( D_ERROR, "W_ReplaceLump: %s is corrupted\n", find->name ); + lseek( W_GetHandle( output_wad ), oldpos, SEEK_SET ); + if( image ) Mem_Free( image ); + return false; + } + + // get new sizes from the saved lump to resample current + lseek( W_GetHandle( output_wad ), oldpos, SEEK_SET ); + height = test.height; + width = test.width; + } + break; + } + } + + return true; +} \ No newline at end of file diff --git a/utils/makewad/miptex.h b/utils/makewad/miptex.h new file mode 100644 index 0000000..136b1b9 --- /dev/null +++ b/utils/makewad/miptex.h @@ -0,0 +1,27 @@ +/* +miptex.h - image quantizer, miptex creator +Copyright (C) 2016 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef MIPTEX_H +#define MIPTEX_H + +extern byte lbmpalette[256*3]; + +bool MIP_CheckForReplace( dlumpinfo_t *find, rgbdata_t *image, int &width, int &height ); +bool MIP_WriteMiptex( const char *lumpname, rgbdata_t *pix ); + +bool LMP_CheckForReplace( dlumpinfo_t *find, rgbdata_t *image, int &width, int &height ); +bool LMP_WriteLmptex( const char *lumpname, rgbdata_t *pix, bool todisk = false ); + +#endif//MIPTEX_H \ No newline at end of file diff --git a/utils/makewad/quantizer.cpp b/utils/makewad/quantizer.cpp new file mode 100644 index 0000000..2af1f9b --- /dev/null +++ b/utils/makewad/quantizer.cpp @@ -0,0 +1,471 @@ +/* +quantizer.cpp - image quantizer. based on Antony Dekker original code +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "conprint.h" +#include +#include +#include +#include +#include +#include "cmdlib.h" +#include "stringlib.h" +#include "imagelib.h" + +#define netsize 256 // number of colours used +#define prime1 499 +#define prime2 491 +#define prime3 487 +#define prime4 503 + +#define minpicturebytes (3*prime4) // minimum size for input image + +#define maxnetpos (netsize-1) +#define netbiasshift 4 // bias for colour values +#define ncycles 100 // no. of learning cycles + +// defs for freq and bias +#define intbiasshift 16 // bias for fractions +#define intbias (1<>betashift) // beta = 1 / 1024 +#define betagamma (intbias<<(gammashift - betashift)) + +// defs for decreasing radius factor +#define initrad (netsize>>3) // for 256 cols, radius starts +#define radiusbiasshift 6 // at 32.0 biased by 6 bits +#define radiusbias (1<> netbiasshift; + if( temp > 255 ) temp = 255; + network[i][j] = temp; + } + network[i][3] = i; // record colour num + } +} + +// insertion sort of network and building of netindex[0..255] (to do after unbias) +void inxbuild( void ) +{ + register int *p, *q; + register int i, j, smallpos, smallval; + int previouscol, startpos; + + previouscol = 0; + startpos = 0; + + for( i = 0; i < netsize; i++ ) + { + p = network[i]; + smallpos = i; + smallval = p[1]; // index on g + + // find smallest in i..netsize-1 + for( j = i + 1; j < netsize; j++ ) + { + q = network[j]; + + if( q[1] < smallval ) + { + // index on g + smallpos = j; + smallval = q[1]; // index on g + } + } + + q = network[smallpos]; + + // swap p (i) and q (smallpos) entries + if( i != smallpos ) + { + j = q[0]; q[0] = p[0]; p[0] = j; + j = q[1]; q[1] = p[1]; p[1] = j; + j = q[2]; q[2] = p[2]; p[2] = j; + j = q[3]; q[3] = p[3]; p[3] = j; + } + + // smallval entry is now in position i + if( smallval != previouscol ) + { + netindex[previouscol] = (startpos+i) >> 1; + + for( j = previouscol + 1; j < smallval; j++ ) + netindex[j] = i; + + previouscol = smallval; + startpos = i; + } + } + + netindex[previouscol] = (startpos + maxnetpos)>>1; + + for( j = previouscol + 1; j < 256; j++ ) + netindex[j] = maxnetpos; // really 256 +} + +// search for BGR values 0..255 (after net is unbiased) and return colour index +int inxsearch( int r, int g, int b ) +{ + register int i, j, dist, a, bestd; + register int *p; + int best; + + bestd = 1000; // biggest possible dist is 256 * 3 + best = -1; + i = netindex[g]; // index on g + j = i - 1; // start at netindex[g] and work outwards + + while(( i < netsize ) || ( j >= 0 )) + { + if( i < netsize ) + { + p = network[i]; + dist = p[1] - g; // inx key + + if( dist >= bestd ) + { + i = netsize; // stop iter + } + else + { + i++; + if( dist < 0 ) dist = -dist; + a = p[2] - b; + if( a < 0 ) a = -a; + dist += a; + + if( dist < bestd ) + { + a = p[0] - r; + if( a < 0 ) a = -a; + dist += a; + + if( dist < bestd ) + { + bestd = dist; + best = p[3]; + } + } + } + } + + if( j >= 0 ) + { + p = network[j]; + dist = g - p[1]; // inx key - reverse dif + + if( dist >= bestd ) + { + j = -1; // stop iter + } + else + { + j--; + if( dist < 0 ) dist = -dist; + a = p[2] - b; + if( a < 0 ) a = -a; + dist += a; + + if( dist < bestd ) + { + a = p[0] - r; + if( a < 0 ) a = -a; + dist += a; + if( dist < bestd ) + { + bestd = dist; + best = p[3]; + } + } + } + } + } + + return best; +} + +// search for biased BGR values +int contest( int r, int g, int b ) +{ + // finds closest neuron (min dist) and updates freq + // finds best neuron (min dist-bias) and returns position + // for frequently chosen neurons, freq[i] is high and bias[i] is negative + // bias[i] = gamma * ((1 / netsize) - freq[i]) + + register int *p, *f, *n; + register int i, dist, a, biasdist, betafreq; + int bestpos, bestbiaspos, bestd, bestbiasd; + + bestd = ~(1<<31); + bestbiasd = bestd; + bestpos = -1; + bestbiaspos = bestpos; + p = bias; + f = freq; + + for( i = 0; i < netsize; i++ ) + { + n = network[i]; + dist = n[2] - b; + if( dist < 0 ) dist = -dist; + a = n[1] - g; + if( a < 0 ) a = -a; + dist += a; + a = n[0] - r; + if( a < 0 ) a = -a; + dist += a; + + if( dist < bestd ) + { + bestd = dist; + bestpos = i; + } + + biasdist = dist - ((*p) >> (intbiasshift - netbiasshift)); + + if( biasdist < bestbiasd ) + { + bestbiasd = biasdist; + bestbiaspos = i; + } + + betafreq = (*f >> betashift); + *f++ -= betafreq; + *p++ += (betafreq << gammashift); + } + + freq[bestpos] += beta; + bias[bestpos] -= betagamma; + + return bestbiaspos; +} + +// move neuron i towards biased (b,g,r) by factor alpha +void altersingle( int alpha, int i, int r, int g, int b ) +{ + register int *n; + + n = network[i]; // alter hit neuron + *n -= (alpha * (*n - r)) / initalpha; + n++; + *n -= (alpha * (*n - g)) / initalpha; + n++; + *n -= (alpha * (*n - b)) / initalpha; +} + +// move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|] +void alterneigh( int rad, int i, int r, int g, int b ) +{ + register int j, k, lo, hi, a; + register int *p, *q; + + lo = i - rad; + if( lo < -1 ) + lo = -1; + + hi = i + rad; + if( hi > netsize ) + hi = netsize; + + j = i + 1; + k = i - 1; + q = radpower; + + while(( j < hi ) || ( k > lo )) + { + a = (*(++q)); + + if( j < hi ) + { + p = network[j]; + *p -= (a * (*p - r)) / alpharadbias; + p++; + *p -= (a * (*p - g)) / alpharadbias; + p++; + *p -= (a * (*p - b)) / alpharadbias; + j++; + } + + if( k > lo ) + { + p = network[k]; + *p -= (a * (*p - r)) / alpharadbias; + p++; + *p -= (a * (*p - g)) / alpharadbias; + p++; + *p -= (a * (*p - b)) / alpharadbias; + k--; + } + } +} + +// main Learning Loop +void learn( void ) +{ + register byte *p; + register int i, j, r, g, b; + int radius, rad, alpha, step; + int delta, samplepixels; + byte *lim; + + alphadec = 30 + ((samplefac - 1) / 3); + p = thepicture; + lim = thepicture + lengthcount; + samplepixels = lengthcount / (samplefac * 4); // RGBA + delta = samplepixels / ncycles; + alpha = initalpha; + radius = initradius; + + rad = radius >> radiusbiasshift; + if( rad <= 1 ) rad = 0; + + for( i = 0; i < rad; i++ ) + radpower[i] = alpha * ((( rad * rad - i * i ) * radbias ) / ( rad * rad )); + + if( delta <= 0 ) return; + + if(( lengthcount % prime1 ) != 0 ) + { + step = prime1 * 4; // RGBA + } + else if(( lengthcount % prime2 ) != 0 ) + { + step = prime2 * 4; // RGBA + } + else if(( lengthcount % prime3 ) != 0 ) + { + step = prime3 * 4; // RGBA + } + else + { + step = prime4 * 4; // RGBA; + } + + i = 0; + + while( i < samplepixels ) + { + r = p[0] << netbiasshift; + g = p[1] << netbiasshift; + b = p[2] << netbiasshift; + j = contest( r, g, b ); + + altersingle( alpha, j, r, g, b ); + if( rad ) alterneigh( rad, j, r, g, b ); // alter neighbours + + p += step; + while( p >= lim ) p -= lengthcount; + + i++; + + if( i % delta == 0 ) + { + alpha -= alpha / alphadec; + radius -= radius / radiusdec; + rad = radius >> radiusbiasshift; + if( rad <= 1 ) rad = 0; + + for( j = 0; j < rad; j++ ) + radpower[j] = alpha * ((( rad * rad - j * j ) * radbias ) / ( rad * rad )); + } + } +} + +// returns the actual number of palette entries. +rgbdata_t *Image_Quantize( rgbdata_t *pic ) +{ + rgbdata_t *out; + int i, samples; + + if( !pic ) return NULL; + + // already quantized? + if( FBitSet( pic->flags, IMAGE_QUANTIZED )) + return pic; + + out = Image_Alloc( pic->width, pic->height, true ); + + if( FBitSet( pic->flags, IMAGE_HAS_COLOR )) + samples = 1; // maximum quality + else samples = 10; // fast mode + + initnet( pic->buffer, pic->size, samples ); + learn(); + unbiasnet(); + + for( i = 0; i < netsize; i++ ) + { + out->palette[i*4+0] = network[i][0]; // red + out->palette[i*4+1] = network[i][1]; // green + out->palette[i*4+2] = network[i][2]; // blue + out->palette[i*4+3] = 0xFF; // alpha + } + + inxbuild(); + + for( i = 0; i < pic->width * pic->height; i++ ) + out->buffer[i] = inxsearch( pic->buffer[i*4+0], pic->buffer[i*4+1], pic->buffer[i*4+2] ); + Mem_Free( pic ); // release RGBA image + + return out; +} \ No newline at end of file diff --git a/utils/mxtk/GL.H b/utils/mxtk/GL.H new file mode 100644 index 0000000..37ffea8 --- /dev/null +++ b/utils/mxtk/GL.H @@ -0,0 +1,29 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: gl.h +// implementation: all +// last modified: Mar 14 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MTKGL +#define INCLUDED_MTKGL + + + +#ifdef WIN32 +#include "windows.h" +#endif + + + +#include "GL/gl.h" + + + +#endif // INCLUDED_MTKGL diff --git a/utils/mxtk/MX.H b/utils/mxtk/MX.H new file mode 100644 index 0000000..efbc319 --- /dev/null +++ b/utils/mxtk/MX.H @@ -0,0 +1,141 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mx.h +// implementation: all +// last modified: Jun 13 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MX +#define INCLUDED_MX + + + +#ifdef WIN32 +#include +#endif + +#ifndef INCLUDED_MXBUTTON +#include +#endif + +#ifndef INCLUDED_MXCHECKBOX +#include +#endif + +#ifndef INCLUDED_MXCHOICE +#include +#endif + +#ifndef INCLUDED_MXCHOOSECOLOR +#include +#endif + +#ifndef INCLUDED_MXEVENT +#include +#endif + +#ifndef INCLUDED_MXFILEDIALOG +#include +#endif + +#ifndef INCLUDED_MXGLWINDOW +#include +#endif + +#ifndef INCLUDED_MXGROUPBOX +#include +#endif + +#ifndef INCLUDED_MXINIT +#include +#endif + +#ifndef INCLUDED_MXLABEL +#include +#endif + +#ifndef INCLUDED_MXLINEEDIT +#include +#endif + +#ifndef INCLUDED_MXLINKEDLIST +#include +#endif + +#ifndef INCLUDED_MXLISTBOX +#include +#endif + +#ifndef INCLUDED_MXMENU +#include +#endif + +#ifndef INCLUDED_MXMENUBAR +#include +#endif + +#ifndef INCLUDED_MXMESSAGEBOX +#include +#endif + +#ifndef INCLUDED_MXMULTILINEEDIT +#include +#endif + +#ifndef INCLUDED_MXPOPUPMENU +#include +#endif + +#ifndef INCLUDED_MXPROGRESSBAR +#include +#endif + +#ifndef INCLUDED_MXRADIOBUTTON +#include +#endif + +#ifndef INCLUDED_MXSLIDER +#include +#endif + +#ifndef INCLUDED_MXTAB +#include +#endif + +#ifndef INCLUDED_MXTOGGLEBUTTON +#include +#endif + +#ifndef INCLUDED_MXTOOLTIP +#include +#endif + +#ifndef INCLUDED_MXTREEVIEW +#include +#endif + +#ifndef INCLUDED_MXWIDGET +#include +#endif + +#ifndef INCLUDED_MXWINDOW +#include +#endif + +#ifndef INCLUDED_MXPATH +#include +#endif + +#ifndef INCLUDED_MXSTRING +#include +#endif + + + +#endif // INCLUDED_MX diff --git a/utils/mxtk/MXPATH.CPP b/utils/mxtk/MXPATH.CPP new file mode 100644 index 0000000..804c316 --- /dev/null +++ b/utils/mxtk/MXPATH.CPP @@ -0,0 +1,98 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxpath.cpp +// implementation: all +// last modified: May 04 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#include +#ifdef WIN32 +#include +#else +#include +#endif +#include +#include +#include + + + +bool +mx_setcwd (const char *path) +{ +#ifdef WIN32 + return (SetCurrentDirectory (path) == TRUE); +#else + return (chdir (path) != -1); +#endif +} + + + +const char * +mx_getcwd () +{ + static char path[256]; +#ifdef WIN32 + GetCurrentDirectory (256, path); +#else + getcwd (path, 256); +#endif + return path; +} + + + +const char * +mx_getpath (const char *filename) +{ + static char path[256]; +#ifdef WIN32 + _splitpath (filename, 0, path, 0, 0); +#else + strcpy (path, filename); + char *ptr = strrchr (path, '/'); + if (ptr) + *ptr = '\0'; +#endif + return path; +} + + + +const char * +mx_getextension (const char *filename) +{ + static char ext[256]; +#ifdef WIN32 + _splitpath (filename, 0, 0, 0, ext); +#else + char *ptr = strrchr (filename, '.'); + if (ptr) + strcpy (ext, ptr); + else + strcpy (ext, ""); +#endif + return ext; +} + + + +const char * +mx_gettemppath () +{ + static char path[256]; +#ifdef WIN32 + GetTempPath (256, path); +#else + strcpy (path, "/tmp"); +#endif + + return path; +} diff --git a/utils/mxtk/MXPATH.H b/utils/mxtk/MXPATH.H new file mode 100644 index 0000000..909248a --- /dev/null +++ b/utils/mxtk/MXPATH.H @@ -0,0 +1,37 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxpath.h +// implementation: all +// last modified: Apr 28 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXPATH +#define INCLUDED_MXPATH + + + +#ifdef __cplusplus +extern "C" { +#endif + +bool mx_setcwd (const char *path); +const char *mx_getcwd (); + +const char *mx_getpath (const char *filename); +const char *mx_getextension (const char *filename); + +const char *mx_gettemppath (); + +#ifdef __cplusplus +} +#endif + + + +#endif // INCLUDED_MXPATH diff --git a/utils/mxtk/MXSTRING.CPP b/utils/mxtk/MXSTRING.CPP new file mode 100644 index 0000000..8415e74 --- /dev/null +++ b/utils/mxtk/MXSTRING.CPP @@ -0,0 +1,143 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxstring.cpp +// implementation: all +// last modified: May 04 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// + +#include +#include +#include +#include +#include +#include + + + +int +mx_strncasecmp (const char *s1, const char *s2, int count) +{ +#ifdef WIN32 + return _strnicmp (s1, s2, count); +#else + return strncasecmp (s1, s2, count); +#endif +} + + + +int +mx_strcasecmp (const char *s1, const char *s2) +{ +#ifdef WIN32 + return _stricmp (s1, s2); +#else + return strcasecmp (s1, s2); +#endif +} + +int mx_strlen( const char *string ) +{ + int len; + const char *p; + + if( !string ) return 0; + + len = 0; + p = string; + while( *p ) + { + p++; + len++; + } + return len; +} + +char *mx_strlower (char *str) +{ + int i; + for (i = strlen (str) - 1; i >= 0; i--) + str[i] = tolower (str[i]); + + return str; +} + +int mx_vsnprintf( char *buffer, size_t buffersize, const char *format, va_list args ) +{ + size_t result; + + result = _vsnprintf( buffer, buffersize, format, args ); + + if( result < 0 || result >= buffersize ) + { + buffer[buffersize - 1] = '\0'; + return -1; + } + return result; +} + +int mx_snprintf( char *buffer, size_t buffersize, const char *format, ... ) +{ + va_list args; + int result; + + va_start( args, format ); + result = mx_vsnprintf( buffer, buffersize, format, args ); + va_end( args ); + + return result; +} + +int mx_sprintf( char *buffer, const char *format, ... ) +{ + va_list args; + int result; + + va_start( args, format ); + result = mx_vsnprintf( buffer, 99999, format, args ); + va_end( args ); + + return result; +} + +char mx_tolower( const char in ) +{ + char out; + + if( in >= 'A' && in <= 'Z' ) + out = in + 'a' - 'A'; + else out = in; + + return out; +} + +char *mx_stristr( const char *string, const char *string2 ) +{ + int c, len; + + if( !string || !string2 ) return NULL; + + c = mx_tolower( *string2 ); + len = mx_strlen( string2 ); + + while( string ) + { + for( ; *string && mx_tolower( *string ) != c; string++ ); + + if( *string ) + { + if( !mx_strncasecmp( string, string2, len )) + break; + string++; + } + else return NULL; + } + return (char *)string; +} \ No newline at end of file diff --git a/utils/mxtk/MXSTRING.H b/utils/mxtk/MXSTRING.H new file mode 100644 index 0000000..f910174 --- /dev/null +++ b/utils/mxtk/MXSTRING.H @@ -0,0 +1,37 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxstring.h +// implementation: all +// last modified: Apr 28 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXSTRING +#define INCLUDED_MXSTRING + + + +#ifdef __cplusplus +extern "C" { +#endif + +int mx_strncasecmp (const char *s1, const char *s2, int count); +int mx_strcasecmp (const char *s1, const char *s2); +int mx_vsnprintf( char *buffer, size_t buffersize, const char *format, va_list args ); +int mx_snprintf( char *buffer, size_t buffersize, const char *format, ... ); +int mx_sprintf( char *buffer, const char *format, ... ); +char *mx_stristr( const char *string, const char *string2 ); +char *mx_strlower (char *str); + +#ifdef __cplusplus +} +#endif + + + +#endif // INCLUDED_MXPATH diff --git a/utils/mxtk/Mx.cpp b/utils/mxtk/Mx.cpp new file mode 100644 index 0000000..89abf64 --- /dev/null +++ b/utils/mxtk/Mx.cpp @@ -0,0 +1,937 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mx.cpp +// implementation: Win32 API +// last modified: Jun 01 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CLASSNAME "MXTK::CLASSNAME" + +extern HACCEL g_hAccel; + + + +void mxTab_resizeChild (HWND hwnd); + + + +mxWindow *g_mainWindow = 0; +//static mxLinkedList *g_widgetList = 0; +static mxWindow *g_idleWindow = 0; + +static MSG msg; +static HWND g_hwndToolTipControl = 0; +static bool isClosing = false; +static HWND g_CurrentHWND = 0; +static HACCEL g_hAcceleratorTable = NULL; + +void mx::createAccleratorTable( int numentries, Accel_t *entries ) +{ + CUtlArray< ACCEL > accelentries; + + for ( int i = 0; i < numentries; ++i ) + { + const Accel_t& entry = entries[ i ]; + + ACCEL add; + add.key = entry.key; + add.cmd = entry.command; + add.fVirt = 0; + if ( entry.flags & ACCEL_ALT ) + { + add.fVirt |= FALT; + } + if ( entry.flags & ACCEL_CONTROL ) + { + add.fVirt |= FCONTROL; + } + if ( entry.flags & ACCEL_SHIFT ) + { + add.fVirt |= FSHIFT; + } + if ( entry.flags & ACCEL_VIRTKEY ) + { + add.fVirt |= FVIRTKEY; + } + + accelentries.AddToTail( add ); + } + + g_hAcceleratorTable = ::CreateAcceleratorTable( accelentries.Base(), accelentries.Count() ); +} + +HWND +mx_CreateToolTipControl () +{ + if (!g_hwndToolTipControl) + { + if (g_mainWindow) + { + g_hwndToolTipControl = CreateWindowEx (0, TOOLTIPS_CLASS, "", WS_POPUP | WS_EX_TOPMOST, + 0, 0, 0, 0, (HWND) g_mainWindow->getHandle (), + (HMENU) NULL, (HINSTANCE) GetModuleHandle (NULL), NULL); + } + } + + return g_hwndToolTipControl; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *window - +// *event - +// Output : static void +//----------------------------------------------------------------------------- +static void RecursiveHandleEvent( mxWindow *window, mxEvent *event ) +{ + while ( window ) + { + if ( window->handleEvent ( event ) ) + break; + + window = window->getParent(); + } +} + +static LRESULT CALLBACK WndProc (HWND hwnd, UINT uMessage, WPARAM wParam, LPARAM lParam) +{ + static bool bDragging = FALSE; + + switch (uMessage) + { + case WM_COMMAND: + { + if (isClosing) + break; + + mxWindow *window = (mxWindow *) GetWindowLong (hwnd, GWL_USERDATA); + if (LOWORD (wParam) > 0 && window) + { + WORD wNotifyCode = (WORD) HIWORD (wParam); + HWND hwndCtrl = (HWND) lParam; + mxEvent event; + + CHAR className[128]; + GetClassName (hwndCtrl, className, 128); + if (!strcmpi (className, "edit")) + { + if (wNotifyCode != EN_CHANGE) + break; + } + else if (!strcmpi (className, "combobox")) + { + if (wNotifyCode != CBN_SELCHANGE) + break; + } + else if (!strcmpi (className, "listbox")) + { + if (wNotifyCode != LBN_SELCHANGE) + break; + } + + event.event = mxEvent::Action; + event.widget = (mxWidget *) GetWindowLong (hwndCtrl, GWL_USERDATA); + event.action = (int) LOWORD (wParam); + RecursiveHandleEvent( window, &event ); + } + } + break; + + case WM_SYSCOMMAND: + { + mxWindow *window = (mxWindow *) GetWindowLong (hwnd, GWL_USERDATA); + switch(wParam) + { + case SC_RESTORE: + window->setMaximized( false ); + break; + case SC_MAXIMIZE: + window->setMaximized( true ); + break; + case SC_CLOSE: + break; + } + } + break; + + case WM_NOTIFY: + { + if (isClosing) + break; + + NMHDR *nmhdr = (NMHDR *) lParam; + mxEvent event; + + if (nmhdr->code == TVN_SELCHANGED) + { + if (nmhdr->idFrom > 0) + { + mxWindow *window = (mxWindow *) GetWindowLong (hwnd, GWL_USERDATA); + event.event = mxEvent::Action; + event.widget = (mxWidget *) GetWindowLong (nmhdr->hwndFrom, GWL_USERDATA); + event.action = (int) nmhdr->idFrom; + + RECT rc; + HTREEITEM hItem = TreeView_GetSelection (nmhdr->hwndFrom); + TreeView_GetItemRect (nmhdr->hwndFrom, hItem, &rc, TRUE); + event.x = (int) rc.left; + event.y = (int) rc.bottom; + RecursiveHandleEvent( window, &event ); + } + } + else if (nmhdr->code == LVN_ITEMCHANGED) + { + if (nmhdr->idFrom > 0) + { + mxWindow *window = (mxWindow *) GetWindowLong (hwnd, GWL_USERDATA); + event.event = mxEvent::Action; + event.widget = (mxWidget *) GetWindowLong (nmhdr->hwndFrom, GWL_USERDATA); + event.action = (int) nmhdr->idFrom; + + RecursiveHandleEvent( window, &event ); + } + } + else if (nmhdr->code == NM_RCLICK) + { + if (nmhdr->idFrom > 0) + { + mxWindow *window = (mxWindow *) GetWindowLong (hwnd, GWL_USERDATA); + event.event = mxEvent::Action; + event.widget = (mxWidget *) GetWindowLong (nmhdr->hwndFrom, GWL_USERDATA); + event.action = (int) nmhdr->idFrom; + event.flags = mxEvent::RightClicked; + + if (event.widget && (event.widget->getType () == MX_TREEVIEW)) + { + RECT rc; + HTREEITEM hItem = TreeView_GetSelection (nmhdr->hwndFrom); + TreeView_GetItemRect (nmhdr->hwndFrom, hItem, &rc, TRUE); + event.x = (int) rc.left; + event.y = (int) rc.bottom; + } + + RecursiveHandleEvent( window, &event ); + } + } + else if (nmhdr->code == NM_DBLCLK) + { + if (nmhdr->idFrom > 0) + { + mxWindow *window = (mxWindow *) GetWindowLong (hwnd, GWL_USERDATA); + event.event = mxEvent::Action; + event.widget = (mxWidget *) GetWindowLong (nmhdr->hwndFrom, GWL_USERDATA); + event.action = (int) nmhdr->idFrom; + event.flags = mxEvent::DoubleClicked; + + if (event.widget && event.widget->getType () == MX_TREEVIEW) + { + RECT rc; + HTREEITEM hItem = TreeView_GetSelection (nmhdr->hwndFrom); + TreeView_GetItemRect (nmhdr->hwndFrom, hItem, &rc, TRUE); + event.x = (int) rc.left; + event.y = (int) rc.bottom; + } + + RecursiveHandleEvent( window, &event ); + + return TRUE; + } + } + else if (nmhdr->code == TCN_SELCHANGING) + { + TC_ITEM ti; + + int index = TabCtrl_GetCurSel (nmhdr->hwndFrom); + if (index >= 0) + { + ti.mask = TCIF_PARAM; + TabCtrl_GetItem (nmhdr->hwndFrom, index, &ti); + mxWindow *window = (mxWindow *) ti.lParam; + if (window) + window->setVisible (false); + } + } + else if (nmhdr->code == TCN_SELCHANGE) + { + mxTab_resizeChild (nmhdr->hwndFrom); + if (nmhdr->idFrom > 0) + { + mxWindow *window = (mxWindow *) GetWindowLong (hwnd, GWL_USERDATA); + event.event = mxEvent::Action; + event.widget = (mxWidget *) GetWindowLong (nmhdr->hwndFrom, GWL_USERDATA); + event.action = (int) nmhdr->idFrom; + RecursiveHandleEvent( window, &event ); + } + } + } + break; + + case WM_SIZE: + { + if (isClosing) + break; + + mxEvent event; + + mxWindow *window = (mxWindow *) GetWindowLong (hwnd, GWL_USERDATA); + if (window) + { + event.event = mxEvent::Size; + event.width = (int) LOWORD (lParam); + event.height = (int) HIWORD (lParam); + window->handleEvent (&event); + } + } + break; + + case WM_ERASEBKGND: + { + if (isClosing) + break; + + mxWindow *window = (mxWindow *) GetWindowLong (hwnd, GWL_USERDATA); + if (window) + { + if (window->getType () == MX_GLWINDOW) + return 0; + } + } + break; + + case WM_HSCROLL: + case WM_VSCROLL: + { + if (isClosing) + break; + + switch (LOWORD (wParam)) + { + case TB_ENDTRACK: + case TB_THUMBTRACK: + { + mxWidget *widget = (mxWidget *) GetWindowLong ((HWND) lParam, GWL_USERDATA); + mxEvent event; + + if (!widget) + break; + + event.event = mxEvent::Action; + event.widget = widget; + event.action = widget->getId (); + + mxWindow *window = widget->getParent (); + while (window && event.action > 0) + { + if (window->handleEvent (&event)) + break; + + window = window->getParent (); + } + } + break; + } + } + break; + + case WM_PAINT: + { + if (isClosing) + break; + + PAINTSTRUCT ps; + HDC hDC = BeginPaint (hwnd, &ps); + + mxWindow *window = (mxWindow *) GetWindowLong (hwnd, GWL_USERDATA); + if (window) + { + window->redraw (); + } + + EndPaint (hwnd, &ps); + } + break; + + case WM_LBUTTONDOWN: + case WM_MBUTTONDOWN: + case WM_RBUTTONDOWN: + { + bDragging = TRUE; + SetCapture (hwnd); + mxWindow *window = (mxWindow *) GetWindowLong (hwnd, GWL_USERDATA); + + if (window) + { + mxEvent event; + event.event = mxEvent::MouseDown; + event.x = (int) (signed short) LOWORD (lParam); + event.y = (int) (signed short) HIWORD (lParam); + event.buttons = 0; + event.modifiers = 0; + + if (uMessage == WM_MBUTTONDOWN) + event.buttons |= mxEvent::MouseMiddleButton; + else if (uMessage == WM_RBUTTONDOWN) + event.buttons |= mxEvent::MouseRightButton; + else + event.buttons |= mxEvent::MouseLeftButton; + + if (wParam & MK_LBUTTON) + event.buttons |= mxEvent::MouseLeftButton; + + if (wParam & MK_RBUTTON) + event.buttons |= mxEvent::MouseRightButton; + + if (wParam & MK_MBUTTON) + event.buttons |= mxEvent::MouseMiddleButton; + + if (wParam & MK_CONTROL) + event.modifiers |= mxEvent::KeyCtrl; + + if (wParam & MK_SHIFT) + event.modifiers |= mxEvent::KeyShift; + + window->handleEvent (&event); + } + } + break; + + case WM_LBUTTONUP: + case WM_MBUTTONUP: + case WM_RBUTTONUP: + { + mxWindow *window = (mxWindow *) GetWindowLong (hwnd, GWL_USERDATA); + if (window) + { + mxEvent event; + event.event = mxEvent::MouseUp; + event.x = (int) (signed short) LOWORD (lParam); + event.y = (int) (signed short) HIWORD (lParam); + event.buttons = 0; + event.modifiers = 0; + + if (uMessage == WM_MBUTTONUP) + event.buttons |= mxEvent::MouseMiddleButton; + else if (uMessage == WM_RBUTTONUP) + event.buttons |= mxEvent::MouseRightButton; + else + event.buttons |= mxEvent::MouseLeftButton; + + if (wParam & MK_LBUTTON) + event.buttons |= mxEvent::MouseLeftButton; + + if (wParam & MK_RBUTTON) + event.buttons |= mxEvent::MouseRightButton; + + if (wParam & MK_MBUTTON) + event.buttons |= mxEvent::MouseMiddleButton; + + if (wParam & MK_CONTROL) + event.modifiers |= mxEvent::KeyCtrl; + + if (wParam & MK_SHIFT) + event.modifiers |= mxEvent::KeyShift; + + window->handleEvent (&event); + } + bDragging = FALSE; + ReleaseCapture (); + } + break; + + case WM_MOUSEMOVE: + { + mxWindow *window = (mxWindow *) GetWindowLong (hwnd, GWL_USERDATA); + if (window) + { + mxEvent event; + + if (bDragging) + event.event = mxEvent::MouseDrag; + else + event.event = mxEvent::MouseMove; + + event.x = (int) (signed short) LOWORD (lParam); + event.y = (int) (signed short) HIWORD (lParam); + event.buttons = 0; + event.modifiers = 0; + + if (wParam & MK_LBUTTON) + event.buttons |= mxEvent::MouseLeftButton; + + if (wParam & MK_RBUTTON) + event.buttons |= mxEvent::MouseRightButton; + + if (wParam & MK_MBUTTON) + event.buttons |= mxEvent::MouseMiddleButton; + + if (wParam & MK_CONTROL) + event.modifiers |= mxEvent::KeyCtrl; + + if (wParam & MK_SHIFT) + event.modifiers |= mxEvent::KeyShift; + + window->handleEvent (&event); + } + } + break; + + case 0x020A://WM_MOUSEWHEEL: + { + mxWindow *window = (mxWindow *) GetWindowLong (hwnd, GWL_USERDATA); + if (window) + { + mxEvent event; + + event.event = mxEvent::MouseWheel; + + POINT pt; + + GetCursorPos (&pt); + event.x = (int) (signed short) LOWORD (lParam); + event.y = (int) (signed short) HIWORD (lParam); + event.buttons = 0; + event.modifiers = 0; + + wParam = LOWORD(wParam); + if (wParam & MK_LBUTTON) + event.buttons |= mxEvent::MouseLeftButton; + + if (wParam & MK_RBUTTON) + event.buttons |= mxEvent::MouseRightButton; + + if (wParam & MK_MBUTTON) + event.buttons |= mxEvent::MouseMiddleButton; + + if (wParam & MK_CONTROL) + event.modifiers |= mxEvent::KeyCtrl; + + if (wParam & MK_SHIFT) + event.modifiers |= mxEvent::KeyShift; + + window->handleEvent (&event); + return TRUE; + } + } + break; + + case WM_KEYDOWN: + { + mxWindow *window = (mxWindow *) GetWindowLong (hwnd, GWL_USERDATA); + if (window) + { + mxEvent event; + event.event = mxEvent::KeyDown; + event.key = (int) wParam; + window->handleEvent (&event); + } + } + break; + + case WM_CHAR: + { + mxWindow *window = (mxWindow *) GetWindowLong (hwnd, GWL_USERDATA); + if (window) + { + mxEvent event; + event.event = mxEvent::KeyDown; + event.key = (int) wParam; + window->handleEvent (&event); + } + } + break; + + case WM_KEYUP: + { + mxWindow *window = (mxWindow *) GetWindowLong (hwnd, GWL_USERDATA); + if (window) + { + mxEvent event; + event.event = mxEvent::KeyUp; + event.key = (int) wParam; + window->handleEvent (&event); + } + } + break; + + case WM_TIMER: + { + if (isClosing) + break; + + mxWindow *window = (mxWindow *) GetWindowLong (hwnd, GWL_USERDATA); + if (window) + { + mxEvent event; + event.event = mxEvent::Timer; + window->handleEvent (&event); + } + } + break; +#if 1 + case WM_ACTIVATE: + { + if (isClosing) + break; + + mxWindow *window = (mxWindow *) GetWindowLong (hwnd, GWL_USERDATA); + if (window) + { + HWND hWnd = (HWND) lParam; + if (::IsWindow(hWnd) && (LOWORD (wParam) == WA_ACTIVE || LOWORD (wParam) == WA_CLICKACTIVE)) + { + mxWindow *window2 = (mxWindow *) GetWindowLong (hWnd, GWL_USERDATA); + if (window2) + { + if (hWnd == g_CurrentHWND && window2->getType () == MX_DIALOGWINDOW) + { + //SetWindowPos ((HWND) lParam, HWND_TOPMOST, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE); + SetForegroundWindow (hWnd); + return 0; + } + } + } + } + } + break; +#endif +/* case WM_SHOWWINDOW: + { + if (isClosing) + break; + + mxWindow *window = (mxWindow *) GetWindowLong (hwnd, GWL_USERDATA); + if (window) + { + printf ("%s %d %d\n", window->getLabel (), wParam, window->getType ()); + if ((BOOL) wParam && window->getType () == MX_DIALOGWINDOW) + g_CurrentHWND = hwnd; + else + g_CurrentHWND = 0; + printf ("hwnd: %d\n", g_CurrentHWND); + } + } + break; +*/ + case WM_CLOSE: + if (g_mainWindow) + { + if ((void *) hwnd == g_mainWindow->getHandle ()) + { + mx::quit (); + } + else + ShowWindow (hwnd, SW_HIDE); + } + //else // shouldn't happen + //DestroyWindow (hwnd); + return 0; +/* + case WM_DESTROY: + if (g_mainWindow) + { + if ((void *) hwnd == g_mainWindow->getHandle ()) + mx::quit (); + } + break; +*/ + } + + return DefWindowProc (hwnd, uMessage, wParam, lParam); +} + + + +int +mx::init(int argc, char **argv) +{ + WNDCLASS wc; + wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; + wc.lpfnWndProc = WndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = (HINSTANCE) GetModuleHandle (NULL); + wc.hIcon = LoadIcon (wc.hInstance, "MX_ICON"); + wc.hCursor = LoadCursor (NULL, IDC_ARROW); + wc.hbrBackground = (HBRUSH)COLOR_3DSHADOW; + wc.lpszMenuName = NULL; + wc.lpszClassName = CLASSNAME; + + if (!wc.hIcon) + wc.hIcon = LoadIcon (NULL, IDI_WINLOGO); + + if (!RegisterClass (&wc)) + return 0; + + InitCommonControls (); + + isClosing = false; + + return 1; +} + + + +int +mx::run() +{ + int messagecount = 0; + + while (!isClosing) + { + bool doframe = false; + if ( PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE) || !g_idleWindow ) + { + if (!GetMessage (&msg, NULL, 0, 0)) + { + doframe = false; + break; + } + + if ( !g_hAcceleratorTable || !TranslateAccelerator( (HWND)g_mainWindow->getHandle (), g_hAcceleratorTable, &msg )) + { + TranslateMessage( &msg ); + DispatchMessage( &msg ); + } + messagecount++; + + if ( messagecount > 10 ) + { + messagecount = 0; + doframe = true; + } + } + else if (g_idleWindow) + { + doframe = true; + messagecount = 0; + } + + if ( doframe && g_idleWindow ) + { + mxEvent event; + event.event = mxEvent::Idle; + g_idleWindow->handleEvent (&event); + } + } + + return msg.wParam; +} + + + +int +mx::check () +{ + if (PeekMessage (&msg, 0, 0, 0, PM_NOREMOVE)) + { + if (GetMessage (&msg, 0, 0, 0)) + { + TranslateMessage (&msg); + DispatchMessage (&msg); + } + + return 1; + } + + return 0; +} + + + +void +mx::quit () +{ + isClosing = true; +} + + + +void +mx::cleanup () +{ + delete g_mainWindow; + + if (g_hwndToolTipControl) + DestroyWindow (g_hwndToolTipControl); + + if ( g_hAcceleratorTable ) + { + DestroyAcceleratorTable( g_hAcceleratorTable ); + g_hAcceleratorTable = 0; + } + + PostQuitMessage (0); + UnregisterClass (CLASSNAME, (HINSTANCE) GetModuleHandle (NULL)); +} + + + +void +mx::setEventWindow (mxWindow *window) +{ + if (window) + g_CurrentHWND = (HWND) window->getHandle (); + else + g_CurrentHWND = 0; +} + + + +int +mx::setDisplayMode (int w, int h, int bpp) +{ + DEVMODE dm; + + dm.dmSize = sizeof (DEVMODE); + dm.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; + dm.dmBitsPerPel = bpp; + dm.dmPelsWidth = w; + dm.dmPelsHeight = h; + + if (w == 0 || h == 0 || bpp == 0) + ChangeDisplaySettings (0, 0); + else + ChangeDisplaySettings (&dm, CDS_FULLSCREEN); + + return 0; +} + + + +void +mx::setIdleWindow (mxWindow *window) +{ + g_idleWindow = window; +} + + + +int +mx::getDisplayWidth () +{ + return (int) GetSystemMetrics (SM_CXSCREEN); +} + + + +int +mx::getDisplayHeight () +{ + return (int) GetSystemMetrics (SM_CYSCREEN); +} + + + +mxWindow* +mx::getMainWindow () +{ + return g_mainWindow; +} + + + +const char * +mx::getApplicationPath () +{ + static char path[256]; + GetModuleFileName (0, path, 256); + char *ptr = strrchr (path, '\\'); + if (ptr) + *ptr = '\0'; + + return path; +} + + + +unsigned int mx :: getTickCount() +{ + static DWORD timebase; + + if( !timebase ) + { + timeBeginPeriod( 1 ); + timebase = timeGetTime(); + } + + return timeGetTime() - timebase; +} + +bool mx :: regCreateKey( HKEY hKey, const char *SubKey ) +{ + HKEY newKey = 0; // Registry function result code + ULONG dwDisposition;// Type of key opening event + long lRet; + + + if( lRet = RegCreateKeyEx( hKey, SubKey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &newKey, &dwDisposition ) != ERROR_SUCCESS ) + { + return false; + } + else + { + RegCloseKey( newKey ); + } + + return true; +} + +bool mx :: regGetValue( HKEY hKey, const char *SubKey, const char *Value, char *pBuffer ) +{ + DWORD dwBufLen = 256; + long lRet; + + if( lRet = RegOpenKeyEx( hKey, SubKey, 0, KEY_READ, &hKey ) != ERROR_SUCCESS ) + { + return false; + } + else + { + lRet = RegQueryValueEx( hKey, Value, NULL, NULL, (byte *)pBuffer, &dwBufLen ); + + if( lRet != ERROR_SUCCESS ) + return false; + + RegCloseKey( hKey ); + } + return true; +} + +bool mx :: regSetValue( HKEY hKey, const char *SubKey, const char *Value, char *pBuffer ) +{ + DWORD dwBufLen = 256; + long lRet; + + if( lRet = RegOpenKeyEx( hKey, SubKey, 0, KEY_WRITE, &hKey ) != ERROR_SUCCESS ) + { + return false; + } + else + { + lRet = RegSetValueEx( hKey, Value, 0, REG_SZ, (byte *)pBuffer, dwBufLen ); + + if( lRet != ERROR_SUCCESS ) + return false; + + RegCloseKey( hKey ); + } + + return true; +} \ No newline at end of file diff --git a/utils/mxtk/mxBmp.cpp b/utils/mxtk/mxBmp.cpp new file mode 100644 index 0000000..9fc8da2 --- /dev/null +++ b/utils/mxtk/mxBmp.cpp @@ -0,0 +1,534 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxBmp.cpp +// implementation: all +// last modified: Apr 15 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +/*** +* +* Copyright (c) 1998, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// lbmlib.c + +#include +#include +#include +#include +#include "mxMessageBox.h" + +mxImage *mxBmpRead( const char *filename ) +{ + int i; + FILE *pfile = 0; + mxBitmapFileHeader bmfh; + mxBitmapInfoHeader bmih; + mxBitmapRGBQuad rgrgbPalette[256]; + int columns, column, rows, row, bpp = 1; + int cbBmpBits, padSize = 0, bps = 0; + byte *pbBmpBits; + byte *pb, *pbHold; + int cbPalBytes = 0; + int biTrueWidth; + mxImage *image = 0; + + // File exists? + if(( pfile = fopen( filename, "rb" )) == 0 ) + return 0; + + // Read file header + if( fread( &bmfh, sizeof bmfh, 1, pfile ) != 1 ) + goto GetOut; + + // Bogus file header check + if( !( bmfh.bfReserved1 == 0 && bmfh.bfReserved2 == 0 )) + goto GetOut; + + // Read info header + if( fread( &bmih, sizeof bmih, 1, pfile ) != 1 ) + goto GetOut; + + // Bogus info header check + if( !( bmih.biSize == sizeof bmih && bmih.biPlanes == 1 )) + goto GetOut; + + if( memcmp( (void *)&bmfh.bfType, "BM", 2 )) + goto GetOut; + + // Bogus bit depth? + if( bmih.biBitCount < 8 ) + goto GetOut; + + // Bogus compression? Only non-compressed supported. + if( bmih.biCompression != 0 ) //BI_RGB) + goto GetOut; + + if( bmih.biBitCount == 8 ) + { + // Figure out how many entires are actually in the table + if( bmih.biClrUsed == 0 ) + { + cbPalBytes = (1 << bmih.biBitCount) * sizeof( mxBitmapRGBQuad ); + bmih.biClrUsed = 256; + } + else + { + cbPalBytes = bmih.biClrUsed * sizeof( mxBitmapRGBQuad ); + } + + // Read palette (bmih.biClrUsed entries) + if( fread( rgrgbPalette, cbPalBytes, 1, pfile ) != 1 ) + goto GetOut; + } + + image = new mxImage(); + if( !image ) goto GetOut; + + if( !image->create( bmih.biWidth, abs( bmih.biHeight ), bmih.biBitCount )) + { + delete image; + goto GetOut; + } + + if( bmih.biBitCount <= 8 ) + { + pb = (byte *)image->palette; + + // Copy over used entries + for( i = 0; i < (int)bmih.biClrUsed; i++ ) + { + *pb++ = rgrgbPalette[i].rgbRed; + *pb++ = rgrgbPalette[i].rgbGreen; + *pb++ = rgrgbPalette[i].rgbBlue; + } + + // Fill in unused entires will 0,0,0 + for( i = bmih.biClrUsed; i < 256; i++ ) + { + *pb++ = 0; + *pb++ = 0; + *pb++ = 0; + } + } + else + { + if( bmih.biBitCount == 24 ) + bpp = 3; + else if( bmih.biBitCount == 32 ) + bpp = 4; + } + + // Read bitmap bits (remainder of file) + cbBmpBits = bmfh.bfSize - ftell( pfile ); + + pbHold = pb = (byte *)malloc( cbBmpBits * sizeof( byte )); + if( pb == 0 ) + { + delete image; + goto GetOut; + } + + if( fread( pb, cbBmpBits, 1, pfile ) != 1 ) + { + free( pbHold ); + delete image; + goto GetOut; + } + + // data is actually stored with the width being rounded up to a multiple of 4 + biTrueWidth = (bmih.biWidth + 3) & ~3; + bps = bmih.biWidth * (bmih.biBitCount >> 3); + + columns = bmih.biWidth; + rows = abs( bmih.biHeight ); + + switch( bmih.biBitCount ) + { + case 8: + case 24: + padSize = ( 4 - ( bps % 4 )) % 4; + break; + } + + for( row = rows - 1; row >= 0; row-- ) + { + pbBmpBits = (byte *)image->data + (row * columns * bpp); + + for( column = 0; column < columns; column++ ) + { + byte red, green, blue, alpha; + int palIndex; + + switch( bmih.biBitCount ) + { + case 8: + palIndex = *pb++; + *pbBmpBits++ = palIndex; + break; + case 24: + blue = *pb++; + green = *pb++; + red = *pb++; + *pbBmpBits++ = red; + *pbBmpBits++ = green; + *pbBmpBits++ = blue; + break; + case 32: + blue = *pb++; + green = *pb++; + red = *pb++; + alpha = *pb++; + *pbBmpBits++ = red; + *pbBmpBits++ = green; + *pbBmpBits++ = blue; + *pbBmpBits++ = alpha; + break; + default: + free( pbHold ); + delete image; + goto GetOut; + } + } + + pb += padSize; // actual only for 4-bit bmps + } + + free( pbHold ); +GetOut: + if( pfile ) + fclose( pfile ); + + return image; +} + +mxImage *mxBmpReadBuffer( const byte *buffer, size_t size ) +{ + int i; + mxBitmapFileHeader bmfh; + mxBitmapInfoHeader bmih; + mxBitmapRGBQuad rgrgbPalette[256]; + int columns, column, rows, row, bpp = 1; + int cbBmpBits, padSize = 0, bps = 0; + const byte *bufstart, *bufend; + byte *pbBmpBits; + byte *pb, *pbHold; + int cbPalBytes = 0; + int biTrueWidth; + mxImage *image = 0; + + // Buffer exists? + if( !buffer || size <= 0 ) + return 0; + + bufstart = buffer; + bufend = bufstart + size; + + // Read file header + memcpy( &bmfh, buffer, sizeof bmfh ); + buffer += sizeof( bmfh ); + + // Bogus file header check + if( !( bmfh.bfReserved1 == 0 && bmfh.bfReserved2 == 0 )) + goto GetOut; + + // Read info header + memcpy( &bmih, buffer, sizeof bmih ); + buffer += sizeof( bmih ); + + // Bogus info header check + if( !( bmih.biSize == sizeof bmih && bmih.biPlanes == 1 )) + goto GetOut; + + if( memcmp( (void *)&bmfh.bfType, "BM", 2 )) + goto GetOut; + + // Bogus bit depth? + if( bmih.biBitCount < 8 ) + goto GetOut; + + // Bogus compression? Only non-compressed supported. + if( bmih.biCompression != 0 ) //BI_RGB) + goto GetOut; + + if( bmih.biBitCount == 8 ) + { + // Figure out how many entires are actually in the table + if( bmih.biClrUsed == 0 ) + { + cbPalBytes = (1 << bmih.biBitCount) * sizeof( mxBitmapRGBQuad ); + bmih.biClrUsed = 256; + } + else + { + cbPalBytes = bmih.biClrUsed * sizeof( mxBitmapRGBQuad ); + } + + // Read palette (bmih.biClrUsed entries) + memcpy( rgrgbPalette, buffer, cbPalBytes ); + buffer += cbPalBytes; + } + + image = new mxImage(); + if( !image ) goto GetOut; + + if( !image->create( bmih.biWidth, abs( bmih.biHeight ), bmih.biBitCount )) + { + delete image; + goto GetOut; + } + + if( bmih.biBitCount <= 8 ) + { + pb = (byte *)image->palette; + + // Copy over used entries + for( i = 0; i < (int)bmih.biClrUsed; i++ ) + { + *pb++ = rgrgbPalette[i].rgbRed; + *pb++ = rgrgbPalette[i].rgbGreen; + *pb++ = rgrgbPalette[i].rgbBlue; + } + + // Fill in unused entires will 0,0,0 + for( i = bmih.biClrUsed; i < 256; i++ ) + { + *pb++ = 0; + *pb++ = 0; + *pb++ = 0; + } + } + else + { + if( bmih.biBitCount == 24 ) + bpp = 3; + else if( bmih.biBitCount == 32 ) + bpp = 4; + } + + // Read bitmap bits (remainder of file) + cbBmpBits = bmfh.bfSize - (buffer - bufstart); + + pbHold = pb = (byte *)malloc( cbBmpBits * sizeof( byte )); + if( pb == 0 ) + { + delete image; + goto GetOut; + } + + memcpy( pb, buffer, cbBmpBits ); + buffer += cbBmpBits; + + // data is actually stored with the width being rounded up to a multiple of 4 + biTrueWidth = (bmih.biWidth + 3) & ~3; + bps = bmih.biWidth * (bmih.biBitCount >> 3); + + columns = bmih.biWidth; + rows = abs( bmih.biHeight ); + + switch( bmih.biBitCount ) + { + case 8: + case 24: + padSize = ( 4 - ( bps % 4 )) % 4; + break; + } + + for( row = rows - 1; row >= 0; row-- ) + { + pbBmpBits = (byte *)image->data + (row * columns * bpp); + + for( column = 0; column < columns; column++ ) + { + byte red, green, blue, alpha; + int palIndex; + + switch( bmih.biBitCount ) + { + case 8: + palIndex = *pb++; + *pbBmpBits++ = palIndex; + break; + case 24: + blue = *pb++; + green = *pb++; + red = *pb++; + *pbBmpBits++ = red; + *pbBmpBits++ = green; + *pbBmpBits++ = blue; + break; + case 32: + blue = *pb++; + green = *pb++; + red = *pb++; + alpha = *pb++; + *pbBmpBits++ = red; + *pbBmpBits++ = green; + *pbBmpBits++ = blue; + *pbBmpBits++ = alpha; + break; + default: + free( pbHold ); + delete image; + goto GetOut; + } + } + + pb += padSize; // actual only for 4-bit bmps + } + + free( pbHold ); +GetOut: + return image; +} + +bool mxBmpWrite( const char *filename, mxImage *image ) +{ + int i, x, y; + FILE *pfile = 0; + mxBitmapFileHeader bmfh; + mxBitmapInfoHeader bmih; + mxBitmapRGBQuad rgrgbPalette[256]; + int cbBmpBits; + byte *pbBmpBits; + byte *pb; + int cbPalBytes = 0; + int biTrueWidth; + int pixel_size; + + if( !image || !image->data ) + return false; + + // File exists? + if(( pfile = fopen( filename, "wb" )) == 0 ) + return false; + + if( image->bpp == 8 ) + pixel_size = 1; + else if( image->bpp == 24 ) + pixel_size = 3; + else if( image->bpp == 32 ) + pixel_size = 4; + else return false; + + biTrueWidth = ((image->width + 3) & ~3); + cbBmpBits = biTrueWidth * image->height * pixel_size; + if( pixel_size == 1 ) cbPalBytes = 256 * sizeof (mxBitmapRGBQuad); + + // Bogus file header check + bmfh.bfType = (word) (('M' << 8) | 'B'); + bmfh.bfSize = sizeof bmfh + sizeof bmih + cbBmpBits + cbPalBytes; + bmfh.bfOffBits = sizeof bmfh + sizeof bmih + cbPalBytes; + bmfh.bfReserved1 = bmfh.bfReserved2 = 0; + + // write file header + if( fwrite( &bmfh, sizeof bmfh, 1, pfile ) != 1 ) + { + fclose( pfile ); + return false; + } + + bmih.biSize = sizeof bmih; + bmih.biWidth = biTrueWidth; + bmih.biHeight = image->height; + bmih.biPlanes = 1; + bmih.biBitCount = pixel_size * 8; + bmih.biCompression = 0; //BI_RGB; + bmih.biSizeImage = cbBmpBits; + bmih.biXPelsPerMeter = 0; + bmih.biYPelsPerMeter = 0; + bmih.biClrUsed = ( pixel_size == 1 ) ? 256 : 0; + bmih.biClrImportant = 0; + + // Write info header + if( fwrite( &bmih, sizeof bmih, 1, pfile ) != 1 ) + { + fclose (pfile); + return false; + } + + if( pixel_size == 1 ) + { + // convert to expanded palette + pb = (byte *)image->palette; + + // Copy over used entries + for (i = 0; i < (int) bmih.biClrUsed; i++) + { + rgrgbPalette[i].rgbRed = *pb++; + rgrgbPalette[i].rgbGreen = *pb++; + rgrgbPalette[i].rgbBlue = *pb++; + rgrgbPalette[i].rgbReserved = 0; + } + + // Write palette (bmih.biClrUsed entries) + cbPalBytes = bmih.biClrUsed * sizeof (mxBitmapRGBQuad); + + if( fwrite( rgrgbPalette, cbPalBytes, 1, pfile ) != 1 ) + { + fclose (pfile); + return false; + } + } + + pbBmpBits = (byte *)malloc( cbBmpBits * sizeof( byte )); + + if( !pbBmpBits ) + { + fclose( pfile ); + return false; + } + + pb = (byte *)image->data; + + for( y = 0; y < bmih.biHeight; y++ ) + { + i = (bmih.biHeight - 1 - y ) * (bmih.biWidth); + + for( x = 0; x < image->width; x++ ) + { + if( pixel_size == 1 ) + { + // 8-bit + pbBmpBits[i] = pb[x]; + } + else + { + // 24 bit + pbBmpBits[i*pixel_size+0] = pb[x*pixel_size+2]; + pbBmpBits[i*pixel_size+1] = pb[x*pixel_size+1]; + pbBmpBits[i*pixel_size+2] = pb[x*pixel_size+0]; + } + + if( pixel_size == 4 ) // write alpha channel + pbBmpBits[i*pixel_size+3] = pb[x*pixel_size+3]; + i++; + } + + pb += image->width * pixel_size; + } + + // Write bitmap bits (remainder of file) + if( fwrite( pbBmpBits, cbBmpBits, 1, pfile ) != 1 ) + { + free( pbBmpBits ); + fclose( pfile ); + return false; + } + + free( pbBmpBits ); + fclose( pfile ); + + return true; +} \ No newline at end of file diff --git a/utils/mxtk/mxBmp.h b/utils/mxtk/mxBmp.h new file mode 100644 index 0000000..d840c10 --- /dev/null +++ b/utils/mxtk/mxBmp.h @@ -0,0 +1,80 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxBmp.h +// implementation: all +// last modified: Apr 15 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXBMP +#define INCLUDED_MXBMP + + + +#ifndef INCLUDED_MXIMAGE +#include +#endif + + +#ifdef WIN32 +#include +#endif + +typedef struct +{ + word bfType; + int bfSize; + word bfReserved1; + word bfReserved2; + int bfOffBits; +} mxBitmapFileHeader; + +typedef struct tagMxBitmapFileHeader +{ + int biSize; + int biWidth; + int biHeight; + word biPlanes; + word biBitCount; + int biCompression; + int biSizeImage; + int biXPelsPerMeter; + int biYPelsPerMeter; + int biClrUsed; + int biClrImportant; +} mxBitmapInfoHeader; + +typedef struct +{ + byte rgbBlue; + byte rgbGreen; + byte rgbRed; + byte rgbReserved; +} mxBitmapRGBQuad; + +#ifdef WIN32 +#include +#endif + + + +#ifdef __cplusplus +extern "C" { +#endif + +mxImage *mxBmpRead (const char *filename); +mxImage *mxBmpReadBuffer(const byte *buffer, size_t size); +bool mxBmpWrite (const char *filename, mxImage *image); + +#ifdef __cplusplus +} +#endif + + + +#endif // INCLUDED_MXBMP diff --git a/utils/mxtk/mxButton.cpp b/utils/mxtk/mxButton.cpp new file mode 100644 index 0000000..e444f37 --- /dev/null +++ b/utils/mxtk/mxButton.cpp @@ -0,0 +1,56 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxButton.cpp +// implementation: Win32 API +// last modified: Apr 12 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#include +#include +#include + + + +class mxButton_i +{ +public: + int dummy; +}; + + + +mxButton::mxButton (mxWindow *parent, int x, int y, int w, int h, const char *label, int id) +: mxWidget (parent, x, y, w, h, label) +{ + if (!parent) + return; + + DWORD dwStyle = WS_VISIBLE | WS_CHILD | WS_TABSTOP; + HWND hwndParent = (HWND) ((mxWidget *) parent)->getHandle (); + + void *handle = (void *) CreateWindowEx (0, "BUTTON", label, dwStyle, + x, y, w, h, hwndParent, + (HMENU) id, (HINSTANCE) GetModuleHandle (NULL), NULL); + + SendMessage ((HWND) handle, WM_SETFONT, (WPARAM) (HFONT) GetStockObject (ANSI_VAR_FONT), MAKELPARAM (TRUE, 0)); + SetWindowLong ((HWND) handle, GWL_USERDATA, (LONG) this); + + setType (MX_BUTTON); + setHandle (handle); + setParent (parent); + setId (id); + + parent->addWidget (this); +} + + + +mxButton::~mxButton () +{ +} diff --git a/utils/mxtk/mxButton.h b/utils/mxtk/mxButton.h new file mode 100644 index 0000000..c4ca300 --- /dev/null +++ b/utils/mxtk/mxButton.h @@ -0,0 +1,50 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxButton.h +// implementation: all +// last modified: Mar 14 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXBUTTON +#define INCLUDED_MXBUTTON + + + +#ifndef INCLUDED_MXWIDGET +#include +#endif + + + +class mxWindow; + + + +class mxButton_i; +class mxButton : public mxWidget +{ + mxButton_i *d_this; + +public: + // CREATORS + + mxButton (mxWindow *parent, int x, int y, int w, int h, const char *label = 0, int id = 0); + + virtual ~mxButton (); + +private: + // NOT IMPLEMENTED + mxButton (const mxButton&); + mxButton& operator= (const mxButton&); +}; + + + +#endif // INCLUDED_MXBUTTON + diff --git a/utils/mxtk/mxCheckBox.cpp b/utils/mxtk/mxCheckBox.cpp new file mode 100644 index 0000000..fcae4ea --- /dev/null +++ b/utils/mxtk/mxCheckBox.cpp @@ -0,0 +1,72 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxCheckBox.cpp +// implementation: Win32 API +// last modified: Mar 18 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#include +#include +#include + + + +class mxCheckBox_i +{ +public: + int dummy; +}; + + + +mxCheckBox::mxCheckBox (mxWindow *parent, int x, int y, int w, int h, const char *label, int id) +: mxWidget (parent, x, y, w, h, label) +{ + if (!parent) + return; + + HWND hwndParent = (HWND) ((mxWidget *) parent)->getHandle (); + + void *handle = (void *) CreateWindowEx (0, "BUTTON", label, WS_VISIBLE | WS_CHILD | WS_TABSTOP | BS_AUTOCHECKBOX, + x, y, w, h, hwndParent, + (HMENU) id, (HINSTANCE) GetModuleHandle (NULL), NULL); + + SendMessage ((HWND) handle, WM_SETFONT, (WPARAM) (HFONT) GetStockObject (ANSI_VAR_FONT), MAKELPARAM (TRUE, 0)); + SetWindowLong ((HWND) handle, GWL_USERDATA, (LONG) this); + + setHandle (handle); + setType (MX_CHECKBOX); + setParent (parent); + setId (id); + setChecked (false); + + parent->addWidget (this); +} + + + +mxCheckBox::~mxCheckBox () +{ +} + + + +void +mxCheckBox::setChecked (bool b) +{ + SendMessage ((HWND) getHandle (), BM_SETCHECK, (WPARAM) b ? BST_CHECKED:BST_UNCHECKED, 0L); +} + + + +bool +mxCheckBox::isChecked () const +{ + return (SendMessage ((HWND) getHandle (), BM_GETCHECK, 0, 0L) == BST_CHECKED); +} diff --git a/utils/mxtk/mxCheckBox.h b/utils/mxtk/mxCheckBox.h new file mode 100644 index 0000000..43a868e --- /dev/null +++ b/utils/mxtk/mxCheckBox.h @@ -0,0 +1,53 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxCheckBox.h +// implementation: all +// last modified: Mar 14 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXCHECKBOX +#define INCLUDED_MXCHECKBOX + + + +#ifndef INCLUDED_MXWIDGET +#include +#endif + + + +class mxWindow; + + + +class mxCheckBox_i; +class mxCheckBox : public mxWidget +{ + mxCheckBox_i *d_this; + +public: + // CREATORS + mxCheckBox (mxWindow *parent, int x, int y, int w, int h, const char *label = 0, int id = 0); + virtual ~mxCheckBox (); + + // MANIPULATORS + void setChecked (bool b); + + // ACCESSORS + bool isChecked () const; + +private: + // NOT IMPLEMENTED + mxCheckBox (const mxCheckBox&); + mxCheckBox& operator= (const mxCheckBox&); +}; + + + +#endif // INCLUDED_MXCHECKBOX diff --git a/utils/mxtk/mxChoice.cpp b/utils/mxtk/mxChoice.cpp new file mode 100644 index 0000000..0ca71cb --- /dev/null +++ b/utils/mxtk/mxChoice.cpp @@ -0,0 +1,104 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxChoice.cpp +// implementation: Win32 API +// last modified: Apr 28 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#include +#include +#include + + + +class mxChoice_i +{ +public: + int dummy; +}; + + + +mxChoice::mxChoice (mxWindow *parent, int x, int y, int w, int h, int id) +: mxWidget (parent, x, y, w, h) +{ + if (!parent) + return; + + HWND hwndParent = (HWND) ((mxWidget *) parent)->getHandle (); + + void *handle = (void *) CreateWindowEx (0, "COMBOBOX", "", WS_VISIBLE | WS_CHILD | WS_TABSTOP | WS_VSCROLL | CBS_DROPDOWNLIST, + x, y, w, h + 100, hwndParent, + (HMENU) id, (HINSTANCE) GetModuleHandle (NULL), NULL); + + SendMessage ((HWND) handle, WM_SETFONT, (WPARAM) (HFONT) GetStockObject (ANSI_VAR_FONT), MAKELPARAM (TRUE, 0)); + SetWindowLong ((HWND) handle, GWL_USERDATA, (LONG) this); + + setHandle (handle); + setType (MX_CHOICE); + setParent (parent); + setId (id); + + parent->addWidget (this); +} + + + +mxChoice::~mxChoice () +{ + removeAll (); +} + + + +void +mxChoice::add (const char *item) +{ + SendMessage ((HWND) getHandle (), CB_ADDSTRING, 0, (LPARAM) (LPCTSTR) item); +} + + + +void +mxChoice::select (int index) +{ + SendMessage ((HWND) getHandle (), CB_SETCURSEL, (WPARAM) index, 0L); +} + + + +void +mxChoice::remove (int index) +{ + SendMessage ((HWND) getHandle (), CB_DELETESTRING, (WPARAM) index, 0L); +} + + + +void +mxChoice::removeAll () +{ + SendMessage ((HWND) getHandle (), CB_RESETCONTENT, 0, 0L); +} + + + +int +mxChoice::getItemCount () const +{ + return (int) SendMessage ((HWND) getHandle (), CB_GETCOUNT, 0, 0L); +} + + + +int +mxChoice::getSelectedIndex () const +{ + return (int) SendMessage ((HWND) getHandle (), CB_GETCURSEL, 0, 0L); +} diff --git a/utils/mxtk/mxChoice.h b/utils/mxtk/mxChoice.h new file mode 100644 index 0000000..6ab800b --- /dev/null +++ b/utils/mxtk/mxChoice.h @@ -0,0 +1,57 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxChoice.h +// implementation: all +// last modified: Mar 14 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXCHOICE +#define INCLUDED_MXCHOICE + + + +#ifndef INCLUDED_MXWIDGET +#include +#endif + + + +class mxWindow; + + + +class mxChoice_i; +class mxChoice : public mxWidget +{ + mxChoice_i *d_this; + +public: + // CREATORS + mxChoice (mxWindow *parent, int x, int y, int w, int h, int id = 0); + virtual ~mxChoice (); + + // MANIPULATORS + void add (const char *item); + void select (int index); + void remove (int index); + void removeAll (); + + // ACCESSORS + int getItemCount () const; + int getSelectedIndex () const; + +private: + // NOT IMPLEMENTED + mxChoice (const mxChoice&); + mxChoice& operator= (const mxChoice&); +}; + + + +#endif // INCLUDED_MXCHOICE diff --git a/utils/mxtk/mxChooseColor.cpp b/utils/mxtk/mxChooseColor.cpp new file mode 100644 index 0000000..b0f3411 --- /dev/null +++ b/utils/mxtk/mxChooseColor.cpp @@ -0,0 +1,48 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxChooseColor.cpp +// implementation: Win32 API +// last modified: Mar 14 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#include +#include +#include +#include + + + +bool +mxChooseColor (mxWindow *parent, int *r, int *g, int *b) +{ + CHOOSECOLOR cc; + static COLORREF custColors[16]; + + BYTE rr = *r; + BYTE gg = *g; + BYTE bb = *b; + + memset (&cc, 0, sizeof (CHOOSECOLOR)); + cc.lStructSize = sizeof (CHOOSECOLOR); + cc.hwndOwner = parent ? (HWND) parent->getHandle ():NULL; + cc.rgbResult = RGB (rr, gg, bb); + cc.lpCustColors = custColors; + cc.Flags = CC_ANYCOLOR | CC_FULLOPEN | CC_RGBINIT; + + if (ChooseColor (&cc)) + { + *r = (int) GetRValue (cc.rgbResult); + *g = (int) GetGValue (cc.rgbResult); + *b = (int) GetBValue (cc.rgbResult); + + return true; + } + + return false; +} diff --git a/utils/mxtk/mxChooseColor.h b/utils/mxtk/mxChooseColor.h new file mode 100644 index 0000000..414a641 --- /dev/null +++ b/utils/mxtk/mxChooseColor.h @@ -0,0 +1,34 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxChooseColor.h +// implementation: all +// last modified: Mar 14 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXCHOOSECOLOR +#define INCLUDED_MXCHOOSECOLOR + + + +class mxWindow; + + +#ifdef __cplusplus +extern "C" { +#endif + +bool mxChooseColor (mxWindow *parent, int *r, int *g, int *b); + +#ifdef __cplusplus +} +#endif + + + +#endif // INCLUDED_MXCHOOSECOLOR diff --git a/utils/mxtk/mxEvent.h b/utils/mxtk/mxEvent.h new file mode 100644 index 0000000..d074469 --- /dev/null +++ b/utils/mxtk/mxEvent.h @@ -0,0 +1,79 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxEvent.h +// implementation: all +// last modified: Apr 12 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXEVENT +#define INCLUDED_MXEVENT + + + +class mxWidget; + + + +class mxEvent +{ +public: + // ENUMS + enum { + Action, + Size, + Timer, + Idle, + Show, + Hide, + MouseUp, + MouseDown, + MouseMove, + MouseDrag, + KeyUp, + KeyDown, + MouseWheel, + Focus, + Activate, + NCMouseUp, + NCMouseDown, + NCMouseMove, + NCMouseDrag, + Close, + PosChanged, + Char, + ParentNotify + }; + + enum { MouseLeftButton = 1, MouseRightButton = 2, MouseMiddleButton = 4}; + enum { KeyCtrl = 1, KeyShift = 2 }; + enum { RightClicked = 1, DoubleClicked = 2 }; + + // DATA + int event; + mxWidget *widget; + int action; + int width, height; + int x, y, buttons; + int key; + int modifiers; + int flags; + + // NO CREATORS + mxEvent () : event (0), widget (0), action (0), width (0), height (0), x (0), y (0), buttons (0), key (0), modifiers (0), flags (0) {} + virtual ~mxEvent () {} + +private: + // NOT IMPLEMENTED + mxEvent (const mxEvent&); + mxEvent& operator= (const mxEvent&); +}; + + + +#endif // INCLUDED_MXEVENT diff --git a/utils/mxtk/mxFileDialog.cpp b/utils/mxtk/mxFileDialog.cpp new file mode 100644 index 0000000..ec5f0c9 --- /dev/null +++ b/utils/mxtk/mxFileDialog.cpp @@ -0,0 +1,100 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxFileDialog.cpp +// implementation: Win32 API +// last modified: Mar 14 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#include +#include +#include +#include +#include + +static char sd_open_path[_MAX_PATH] = ""; +static char sd_save_path[_MAX_PATH] = ""; + +const char *mxGetOpenFileName( mxWindow *parent, const char *path, const char *filter ) +{ + CHAR szPath[_MAX_PATH], szFilter[_MAX_PATH]; + + strcpy (sd_open_path, ""); + + if (path) + strcpy (szPath, path); + else + strcpy (szPath, ""); + + if (filter) + { + memset (szFilter, 0, _MAX_PATH); + strcpy (szFilter, filter); + strcpy (szFilter + strlen (szFilter) + 1, filter); + } + else + strcpy (szFilter, ""); + + + OPENFILENAME ofn; + memset (&ofn, 0, sizeof (ofn)); + ofn.lStructSize = sizeof (ofn); + if (parent) + ofn.hwndOwner = (HWND) parent->getHandle (); + ofn.hInstance = (HINSTANCE) GetModuleHandle (NULL); + ofn.lpstrFilter = szFilter; + ofn.lpstrFile = sd_open_path; + ofn.nMaxFile = _MAX_PATH; + if (path && strlen (path)) + ofn.lpstrInitialDir = szPath; + ofn.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY; + + if( GetOpenFileName( &ofn )) + return sd_open_path; + return 0; +} + +const char *mxGetSaveFileName (mxWindow *parent, const char *path, const char *filter, const char *filepath ) +{ + CHAR szPath[_MAX_PATH], szFilter[_MAX_PATH]; + + if( filepath ) + strcpy (sd_save_path, filepath ); + else strcpy (sd_save_path, ""); + + if (path) + strcpy (szPath, path); + else + strcpy (szPath, ""); + + if (filter) + { + memset (szFilter, 0, _MAX_PATH); + strcpy (szFilter, filter); + strcpy (szFilter + strlen (szFilter) + 1, filter); + } + else + strcpy (szFilter, ""); + + OPENFILENAME ofn; + memset (&ofn, 0, sizeof (ofn)); + ofn.lStructSize = sizeof (ofn); + if (parent) + ofn.hwndOwner = (HWND) parent->getHandle (); + ofn.hInstance = (HINSTANCE) GetModuleHandle (NULL); + ofn.lpstrFilter = szFilter; + ofn.lpstrFile = sd_save_path; + ofn.nMaxFile = _MAX_PATH; + if (path && strlen (path)) + ofn.lpstrInitialDir = szPath; + ofn.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY; + + if( GetSaveFileName( &ofn )) + return sd_save_path; + return 0; +} \ No newline at end of file diff --git a/utils/mxtk/mxFileDialog.h b/utils/mxtk/mxFileDialog.h new file mode 100644 index 0000000..85c2474 --- /dev/null +++ b/utils/mxtk/mxFileDialog.h @@ -0,0 +1,36 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxFileDialog.h +// implementation: all +// last modified: Mar 14 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXFILEDIALOG +#define INCLUDED_MXFILEDIALOG + + + +class mxWindow; + + + +#ifdef __cplusplus +extern "C" { +#endif + +const char *mxGetOpenFileName (mxWindow *parent, const char *path, const char *filter); +const char *mxGetSaveFileName (mxWindow *parent, const char *path, const char *filter, const char *filepath = 0 ); + +#ifdef __cplusplus +} +#endif + + + +#endif // INCLUDED_MXFILEDIALOG diff --git a/utils/mxtk/mxGlWindow.cpp b/utils/mxtk/mxGlWindow.cpp new file mode 100644 index 0000000..13d42f9 --- /dev/null +++ b/utils/mxtk/mxGlWindow.cpp @@ -0,0 +1,383 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxGlWindow.cpp +// implementation: Win32 API +// last modified: Apr 21 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// + +#include +#include +#include +#include + +static BOOL (WINAPI *wglGetPixelFormatAttribiv)( HDC hDC, int iPixelFormat, int iLayerPlane, UINT nAttributes, const int *piAttrib, int *piValues); +static BOOL (WINAPI *wglChoosePixelFormat)( HDC hDC, const int *piAttribIList, const FLOAT *pfAttribFList, UINT nMaxFmts, int *piFmts, UINT *nNumFmts ); + +void MsLog (const char *fmt, ...) +{ +#if 0 // _DEBUG_ + char output[1024]; + va_list argptr; + + va_start( argptr, fmt ); + _vsnprintf( output, sizeof( output ), fmt, argptr ); + va_end( argptr ); + + ::MessageBox (0, output, "Error", MB_OK|MB_ICONERROR ); +#endif +} + +class mxGlWindow_i +{ +public: + HDC hdc; + HGLRC hglrc; +}; + +mxGlWindow::mxGlWindow (mxWindow *parent, int x, int y, int w, int h, const char *label, int style) +: mxWindow (parent, x, y, w, h, label, style) +{ + PIXELFORMATDESCRIPTOR pfd; + + d_this = new mxGlWindow_i; + + setType (MX_GLWINDOW); + setDrawFunc (0); + + bool error = false; + + MsLog ("mxGlWindow, GetDC"); + if(( d_this->hdc = GetDC(( HWND )getHandle( ))) == NULL ) + { + MsLog( "ERROR: mxGlWindow, GetDC" ); + error = true; + goto done; + } + + MsLog( "mxGlWindow, ChoosePixelFormat" ); + int pfm; + if(( pfm = choosePixelFormat( &pfd )) == 0 ) // g-cont. new style initialization with multisamples + { + MsLog ("ERROR: mxGlWindow, ChoosePixelFormat"); + error = true; + goto done; + } + + MsLog ("mxGlWindow, SetPixelFormat"); + if( ::SetPixelFormat( d_this->hdc, pfm, &pfd ) == FALSE ) + { + MsLog( "ERROR: mxGlWindow, SetPixelFormat" ); + error = true; + goto done; + } + + MsLog ("mxGlWindow, wglCreateContext"); + if ((d_this->hglrc = wglCreateContext (d_this->hdc)) == 0) + { + MsLog ("ERROR: mxGlWindow, wglCreateContext"); + error = true; + goto done; + } + + MsLog ("mxGlWindow, wglMakeCurrent"); + if (!wglMakeCurrent (d_this->hdc, d_this->hglrc)) + { + MsLog ("ERROR: mxGlWindow, wglMakeCurrent"); + error = true; + goto done; + } +done: + if (error) + { + ::MessageBox(0, "Error creating OpenGL window, please install the latest graphics drivers or Mesa 3.x!", "Error", MB_OK|MB_ICONERROR); + exit(-1); + } + //else if (parent) + // parent->addWidget (this); +} + + + +mxGlWindow::~mxGlWindow () +{ + if (d_this->hglrc) + { + wglMakeCurrent (NULL, NULL); + wglDeleteContext (d_this->hglrc); + } + + if (d_this->hdc) + ReleaseDC ((HWND) getHandle (), d_this->hdc); + + delete d_this; +} + +/* +================== +initFakeOpenGL + +Fake OpenGL stuff to work around the crappy WGL limitations. +Do this silently. +================== +*/ +void mxGlWindow :: initFakeOpenGL( void ) +{ + WNDCLASSEX wndClass; + PIXELFORMATDESCRIPTOR pfd; + int pixelFormat; + + // Register the window class + wndClass.cbSize = sizeof(WNDCLASSEX); + wndClass.style = 0; + wndClass.lpfnWndProc = DefWindowProc; + wndClass.cbClsExtra = 0; + wndClass.cbWndExtra = 0; + wndClass.hInstance = (HINSTANCE)GetModuleHandle( NULL ); + wndClass.hIcon = NULL; + wndClass.hIconSm = NULL; + wndClass.hCursor = NULL; + wndClass.hbrBackground = NULL; + wndClass.lpszMenuName = NULL; + wndClass.lpszClassName = "fake"; + + if( !RegisterClassEx( &wndClass )) + return; + + // Create the fake window + if(( hWndFake = CreateWindowEx( 0, "fake", "mxtk", 0, 0, 0, 100, 100, NULL, NULL, wndClass.hInstance, NULL )) == NULL ) + { + UnregisterClass( "fake", wndClass.hInstance ); + return; + } + + // Get a DC for the fake window + if(( hDCFake = GetDC( hWndFake )) == NULL ) + return; + + // Choose a pixel format + memset( &pfd, 0, sizeof( PIXELFORMATDESCRIPTOR )); + + pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.iLayerType = PFD_MAIN_PLANE; + pfd.cColorBits = 32; + pfd.cAlphaBits = 8; + pfd.cDepthBits = 24; + pfd.cStencilBits = 8; + + pixelFormat = ChoosePixelFormat( hDCFake, &pfd ); + if( !pixelFormat ) return; + + // Set the pixel format + if( !SetPixelFormat( hDCFake, pixelFormat, &pfd )) + return; + + // Create the fake GL context + if(( hGLRCFake = wglCreateContext( hDCFake )) == NULL ) + return; + + // Make the fake GL context current + if( !wglMakeCurrent( hDCFake, hGLRCFake )) + return; + + // We only need these function pointers if available + wglGetPixelFormatAttribiv = (PFNWGLGETPIXELFORMATATTRIBIVARBPROC)wglGetProcAddress( "wglGetPixelFormatAttribivARB" ); + wglChoosePixelFormat = (PFNWGLCHOOSEPIXELFORMATARBPROC)wglGetProcAddress( "wglChoosePixelFormatARB" ); +} + +/* +================== +shutdownFakeOpenGL + +Fake OpenGL stuff to work around the crappy WGL limitations. +Do this silently. +================== +*/ +void mxGlWindow :: shutdownFakeOpenGL( void ) +{ + if( hGLRCFake ) + { + wglMakeCurrent( NULL, NULL ); + wglDeleteContext( hGLRCFake ); + hGLRCFake = NULL; + } + + if( hDCFake ) + { + ReleaseDC( hWndFake, hDCFake ); + hDCFake = NULL; + } + + if( hWndFake ) + { + DestroyWindow( hWndFake ); + hWndFake = NULL; + UnregisterClass( "fake", GetModuleHandle( NULL )); + } + + wglGetPixelFormatAttribiv = NULL; + wglChoosePixelFormat = NULL; +} + +/* +================== +choosePixelFormat +================== +*/ +int mxGlWindow :: choosePixelFormat( PIXELFORMATDESCRIPTOR *pfd ) +{ + int attribs[24]; + int samples = 0; + int pixelFormat = 0; + int numPixelFormats; + + // Initialize the fake OpenGL stuff so that we can use the extended pixel + // format functionality + initFakeOpenGL(); + + // Choose a pixel format + if( wglChoosePixelFormat == NULL ) + { + memset( pfd, 0, sizeof( PIXELFORMATDESCRIPTOR )); + + pfd->nSize = sizeof( PIXELFORMATDESCRIPTOR ); + pfd->dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; + pfd->iPixelType = PFD_TYPE_RGBA; + pfd->iLayerType = PFD_MAIN_PLANE; + pfd->cColorBits = 32; + pfd->cAlphaBits = 0; + pfd->cDepthBits = 24; + pfd->cStencilBits = 8; + pfd->nVersion = 1; + + pixelFormat = ChoosePixelFormat( d_this->hdc, pfd ); + } + else + { + attribs[0] = WGL_ACCELERATION_ARB; + attribs[1] = WGL_FULL_ACCELERATION_ARB; + attribs[2] = WGL_DRAW_TO_WINDOW_ARB; + attribs[3] = TRUE; + attribs[4] = WGL_SUPPORT_OPENGL_ARB; + attribs[5] = TRUE; + attribs[6] = WGL_DOUBLE_BUFFER_ARB; + attribs[7] = TRUE; + attribs[8] = WGL_PIXEL_TYPE_ARB; + attribs[9] = WGL_TYPE_RGBA_ARB; + attribs[10] = WGL_COLOR_BITS_ARB; + attribs[11] = 32; + attribs[12] = WGL_ALPHA_BITS_ARB; + attribs[13] = 0; + attribs[14] = WGL_DEPTH_BITS_ARB; + attribs[15] = 24; + attribs[16] = WGL_STENCIL_BITS_ARB; + attribs[17] = 8; + attribs[18] = WGL_SAMPLE_BUFFERS_ARB; + attribs[19] = 1; + attribs[20] = WGL_SAMPLES_ARB; + attribs[21] = 4; + attribs[22] = 0; + attribs[23] = 0; + + wglChoosePixelFormat( d_this->hdc, attribs, NULL, 1, &pixelFormat, (UINT *)&numPixelFormats ); + + if( pixelFormat ) + { + samples = WGL_SAMPLES_ARB; + wglGetPixelFormatAttribiv( d_this->hdc, pixelFormat, 0, 1, &samples, &samples ); + } + } + + // shutdown the fake OpenGL stuff since we no longer need it + shutdownFakeOpenGL(); + + // Make sure we have a valid pixel format + if( !pixelFormat ) return 0; + + // Describe the pixel format + DescribePixelFormat( d_this->hdc, pixelFormat, sizeof( PIXELFORMATDESCRIPTOR ), pfd ); + + if( pfd->dwFlags & ( PFD_GENERIC_FORMAT | PFD_GENERIC_ACCELERATED )) + { + MsLog( "...no hardware acceleration found\n" ); + return 0; + } + + MsLog( "...hardware acceleration found\n" ); + + if( pfd->cColorBits < 32 || pfd->cDepthBits < 24 || pfd->cStencilBits < 8 ) + { + MsLog( "...insufficient pixel format\n" ); + return 0; + } + + // report if multisampling is desired but unavailable + if( samples <= 1 ) MsLog( "...failed to select a multisample pixel format\n" ); + + return pixelFormat; +} + +int +mxGlWindow::handleEvent (mxEvent *event) +{ + return 0; +} + + + +void +mxGlWindow::redraw () +{ + makeCurrent (); + if (d_drawFunc) + d_drawFunc (); + else + draw (); + swapBuffers (); +} + + + +void +mxGlWindow::draw () +{ +} + + + +int +mxGlWindow::makeCurrent () +{ + if (wglMakeCurrent (d_this->hdc, d_this->hglrc)) + return 1; + + return 0; +} + + + +int +mxGlWindow::swapBuffers () +{ + if (SwapBuffers (d_this->hdc)) + return 1; + + return 0; +} + + + +void +mxGlWindow::setDrawFunc (void (*func) (void)) +{ + d_drawFunc = func; +} \ No newline at end of file diff --git a/utils/mxtk/mxGlWindow.h b/utils/mxtk/mxGlWindow.h new file mode 100644 index 0000000..9a3876b --- /dev/null +++ b/utils/mxtk/mxGlWindow.h @@ -0,0 +1,65 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxGlWindow.h +// implementation: all +// last modified: Apr 21 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXGLWINDOW +#define INCLUDED_MXGLWINDOW + +#include + +#ifndef INCLUDED_MXWINDOW +#include +#endif + + + +class mxGlWindow_i; +class mxGlWindow : public mxWindow +{ + mxGlWindow_i *d_this; + void (*d_drawFunc) (void); + +public: + // ENUMS + enum { FormatDouble, FormatSingle }; + + // CREATORS + mxGlWindow (mxWindow *parent, int x, int y, int w, int h, const char *label = 0, int style = 0); + virtual ~mxGlWindow (); + + // MANIPULATORS + virtual int handleEvent (mxEvent *event); + virtual void redraw (); + virtual void draw (); + + int makeCurrent (); + int swapBuffers (); + + // MANIPULATORS + void setDrawFunc (void (*func) (void)); +private: + // NOT IMPLEMENTED + mxGlWindow (const mxGlWindow&); + mxGlWindow& operator= (const mxGlWindow&); + + void initFakeOpenGL( void ); + void shutdownFakeOpenGL( void ); + int choosePixelFormat( PIXELFORMATDESCRIPTOR *pfd ); +protected: + HWND hWndFake; + HDC hDCFake; + HGLRC hGLRCFake; +}; + + + +#endif // INCLUDED_MXGLWINDOW diff --git a/utils/mxtk/mxGroupBox.cpp b/utils/mxtk/mxGroupBox.cpp new file mode 100644 index 0000000..e380037 --- /dev/null +++ b/utils/mxtk/mxGroupBox.cpp @@ -0,0 +1,53 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxGroupBox.cpp +// implementation: Win32 API +// last modified: Mar 18 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#include +#include +#include + + + +class mxGroupBox_i +{ +public: + int dummy; +}; + + + +mxGroupBox::mxGroupBox (mxWindow *parent, int x, int y, int w, int h, const char *label) +: mxWidget (parent, x, y, w, h, label) +{ + if (!parent) + return; + + HWND hwndParent = (HWND) ((mxWidget *) parent)->getHandle (); + + void *handle = (void *) CreateWindowEx (0, "BUTTON", label, WS_VISIBLE | WS_CHILD | BS_GROUPBOX, + x, y, w, h, hwndParent, + (HMENU) NULL, (HINSTANCE) GetModuleHandle (NULL), NULL); + + SendMessage ((HWND) handle, WM_SETFONT, (WPARAM) (HFONT) GetStockObject (ANSI_VAR_FONT), MAKELPARAM (TRUE, 0)); + + setHandle (handle); + setType (MX_GROUPBOX); + setParent (parent); + + parent->addWidget (this); +} + + + +mxGroupBox::~mxGroupBox () +{ +} diff --git a/utils/mxtk/mxGroupBox.h b/utils/mxtk/mxGroupBox.h new file mode 100644 index 0000000..04e8ef6 --- /dev/null +++ b/utils/mxtk/mxGroupBox.h @@ -0,0 +1,47 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxGroupBox.h +// implementation: all +// last modified: Mar 14 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXGROUPBOX +#define INCLUDED_MXGROUPBOX + + + +#ifndef INCLUDED_MXWIDGET +#include +#endif + + + +class mxWindow; + + + +class mxGroupBox_i; +class mxGroupBox : public mxWidget +{ + mxGroupBox_i *d_this; + +public: + // CREATORS + mxGroupBox (mxWindow *parent, int x, int y, int w, int h, const char *label = 0); + virtual ~mxGroupBox (); + +private: + // NOT IMPLEMENTED + mxGroupBox (const mxGroupBox&); + mxGroupBox& operator= (const mxGroupBox&); +}; + + + +#endif // INCLUDED_MXGROUPBOX diff --git a/utils/mxtk/mxImage.h b/utils/mxtk/mxImage.h new file mode 100644 index 0000000..e9e6224 --- /dev/null +++ b/utils/mxtk/mxImage.h @@ -0,0 +1,145 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxImage.h +// implementation: all +// last modified: Apr 15 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXIMAGE +#define INCLUDED_MXIMAGE + + + +#include + + + +#ifndef byte +typedef unsigned char byte; +#endif // byte + +#ifndef word +typedef unsigned short word; +#endif // word + + + +class mxImage +{ +public: + int width; + int height; + int bpp; + void *data; + void *palette; + + // CREATORS + mxImage () : width (0), height (0), bpp (0), data (0), palette (0) + { + } + + mxImage (int w, int h, int bpp) + { + create (w, h, bpp); + } + + virtual ~mxImage () + { + destroy (); + } + + // MANIPULATORS + bool create (int w, int h, int pixelSize) + { + if (data) + free (data); + + if (palette) + free (palette); + + data = malloc (w * h * pixelSize / 8); + if (!data) + return false; + + // allocate a palette for 8-bit images + if (pixelSize == 8) + { + palette = malloc (768); + if (!palette) + { + //delete[] data; + free (data); + return false; + } + } + else + palette = 0; + + width = w; + height = h; + bpp = pixelSize; + + return true; + } + + void destroy () + { + if (data) + free (data); + //delete[] data; + + if (palette) + free (palette); + //delete[] palette; + + data = palette = 0; + width = height = bpp = 0; + } + + bool flip_vertical( void ) + { + if( bpp != 24 && bpp != 32 ) + return false; + + int pixel_size = bpp / 8; + size_t img_size = width * height * pixel_size; + unsigned char *buffer = (unsigned char *)malloc ( img_size ); + unsigned char *out = buffer, *src = (unsigned char *)data; + + // swap rgba to bgra and flip upside down + for( int y = height - 1; y >= 0; y-- ) + { + unsigned char *in = src + y * width * pixel_size; + unsigned char *bufend = in + width * pixel_size; + + for( ; in < bufend; in += pixel_size ) + { + *out++ = in[0]; + *out++ = in[1]; + *out++ = in[2]; + if( pixel_size == 4 ) + *out++ = in[3]; + } + } + + // swap buffers + free( data ); + data = buffer; + + return true; + } +private: + // NOT IMPLEMENTED + mxImage (const mxImage&); + mxImage& operator= (const mxImage&); +}; + + + +#endif // INCLUDED_MXIMAGE diff --git a/utils/mxtk/mxInit.h b/utils/mxtk/mxInit.h new file mode 100644 index 0000000..1b09630 --- /dev/null +++ b/utils/mxtk/mxInit.h @@ -0,0 +1,90 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxInit.h +// implementation: all +// last modified: Apr 28 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXINIT +#define INCLUDED_MXINIT + + + +#ifdef WIN32 +#include +#endif + + + +class mxWindow; + + + +class mx +{ +public: + // NO CREATORS + mx() {} + virtual ~mx () {} + + // MANIPULATORS + static int init (int argc, char *argv[]); + static int run (); + static int check (); + static void quit (); + static void cleanup (); + static void setEventWindow (mxWindow *window); + static int setDisplayMode (int w, int h, int bpp); + static void setIdleWindow (mxWindow *window); + + // ACCESSORS + static int getDisplayWidth (); + static int getDisplayHeight (); + static mxWindow *getMainWindow (); + static const char *getApplicationPath (); + static unsigned int getTickCount (); + + enum + { + ACCEL_ALT = (1<<0), // The ALT key must be held down when the accelerator key is pressed. + ACCEL_CONTROL = (1<<1), // The CTRL key must be held down when the accelerator key is pressed. + ACCEL_SHIFT = (1<<2), // The SHIFT key must be held down when the accelerator key is pressed. + ACCEL_VIRTKEY = (1<<3), // The key member specifies a virtual-key code. If this flag is not specified, key is assumed to specify a character code. + }; + + // Based on windows.h ACCEL structure!!! + struct Accel_t + { + Accel_t() : + flags( 0 ), + key( 0 ), + command( 0 ) + { + } + unsigned char flags; // one or more of above ACCEL_ flags + unsigned short key; // Specifies the accelerator key. This member can be either a virtual-key code or a character code. + unsigned short command; // Specifies the accelerator identifier. This value is placed in the low-order word of the wParam parameter of the WM_COMMAND or WM_SYSCOMMAND message when the accelerator is pressed. + }; + + static void createAccleratorTable( int numentries, Accel_t *entries ); + + static bool regCreateKey( HKEY hKey, const char *SubKey ); + static bool regGetValue( HKEY hKey, const char *SubKey, const char *Value, char *pBuffer ); + static bool regSetValue( HKEY hKey, const char *SubKey, const char *Value, char *pBuffer ); + +private: + // NOT IMPLEMENTED + mx (const mx&); + mx& operator= (const mx&); +}; + + + + +#endif // INCLUDED_MXINIT diff --git a/utils/mxtk/mxLabel.cpp b/utils/mxtk/mxLabel.cpp new file mode 100644 index 0000000..4c08d27 --- /dev/null +++ b/utils/mxtk/mxLabel.cpp @@ -0,0 +1,54 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxLabel.cpp +// implementation: Win32 API +// last modified: Mar 18 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#include +#include +#include + + + +class mxLabel_i +{ +public: + int dummy; +}; + + + +mxLabel::mxLabel (mxWindow *parent, int x, int y, int w, int h, const char *label) +: mxWidget (parent, x, y, w, h, label) +{ + + if (!parent) + return; + + HWND hwndParent = (HWND) ((mxWidget *) parent)->getHandle (); + + void *handle = (void *) CreateWindowEx (0, "STATIC", label, WS_VISIBLE | WS_CHILD, + x, y, w, h, hwndParent, + (HMENU) NULL, (HINSTANCE) GetModuleHandle (NULL), NULL); + + SendMessage ((HWND) handle, WM_SETFONT, (WPARAM) (HFONT) GetStockObject (ANSI_VAR_FONT), MAKELPARAM (TRUE, 0)); + + setHandle (handle); + setType (MX_LABEL); + setParent (parent); + + parent->addWidget (this); +} + + + +mxLabel::~mxLabel () +{ +} diff --git a/utils/mxtk/mxLabel.h b/utils/mxtk/mxLabel.h new file mode 100644 index 0000000..6905cae --- /dev/null +++ b/utils/mxtk/mxLabel.h @@ -0,0 +1,47 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxLabel.h +// implementation: all +// last modified: Mar 14 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXLABEL +#define INCLUDED_MXLABEL + + + +#ifndef INCLUDED_MXWIDGET +#include +#endif + + + +class mxWindow; + + + +class mxLabel_i; +class mxLabel : public mxWidget +{ + mxLabel_i *d_this; + +public: + // CREATORS + mxLabel (mxWindow *parent, int x, int y, int w, int h, const char *label = 0); + virtual ~mxLabel (); + +private: + // NOT IMPLEMENTED + mxLabel (const mxLabel&); + mxLabel& operator= (const mxLabel&); +}; + + + +#endif // INCLUDED_MXLABEL diff --git a/utils/mxtk/mxLineEdit.cpp b/utils/mxtk/mxLineEdit.cpp new file mode 100644 index 0000000..2add588 --- /dev/null +++ b/utils/mxtk/mxLineEdit.cpp @@ -0,0 +1,97 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxLineEdit.cpp +// implementation: Win32 API +// last modified: May 18 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#include +#include +#include + + + +class mxLineEdit_i +{ +public: + int dummy; + //WNDPROC wndProc; +}; +/* + +static LRESULT CALLBACK mxLineEdit_WindowProc( + HWND hwnd, // handle to window + UINT uMsg, // message identifier + WPARAM wParam, // first message parameter + LPARAM lParam // second message parameter +) +{ + mxLineEdit *pEdit = (mxLineEdit *) GetWindowLong(hwnd, GWL_USERDATA); + if (uMsg == WM_CHAR) + { + if (wParam == 13) + { + //pEdit->getParent()-> + } + } + return ::CallWindowProc(pEdit->d_this->wndProc, hwnd, uMsg, wParam, lParam); +} + */ +mxLineEdit::mxLineEdit (mxWindow *parent, int x, int y, int w, int h, const char *label, int id, int style) +: mxWidget (parent, x, y, w, h, label) +{ + if (!parent) + return; + + DWORD dwStyle = WS_VISIBLE | WS_CHILD | WS_TABSTOP | ES_AUTOHSCROLL; + HWND hwndParent = (HWND) ((mxWidget *) parent)->getHandle (); + + if (style == ReadOnly) + dwStyle |= ES_READONLY; + else if (style == Password) + dwStyle |= ES_PASSWORD; + + void *handle = (void *) CreateWindowEx (WS_EX_CLIENTEDGE, "EDIT", label, dwStyle, + x, y, w, h, hwndParent, + (HMENU) id, (HINSTANCE) GetModuleHandle (NULL), NULL); + + SendMessage ((HWND) handle, WM_SETFONT, (WPARAM) (HFONT) GetStockObject (ANSI_VAR_FONT), MAKELPARAM (TRUE, 0)); + SendMessage ((HWND) handle, EM_LIMITTEXT, (WPARAM) 256, 0L); + SetWindowLong ((HWND) handle, GWL_USERDATA, (LONG) this); + + setHandle (handle); + setType (MX_LINEEDIT); + setParent (parent); + setId (id); + + //d_this->wndProc = (WNDPROC) ::SetWindowLong((HWND) handle, GWL_WNDPROC, (DWORD) mxLineEdit_WindowProc); + parent->addWidget (this); +} + + + +mxLineEdit::~mxLineEdit () +{ +} + + + +void +mxLineEdit::setMaxLength (int max) +{ + SendMessage ((HWND) getHandle (), EM_LIMITTEXT, (WPARAM) max, 0L); +} + + + +int +mxLineEdit::getMaxLength () const +{ + return (int) SendMessage ((HWND) getHandle (), EM_GETLIMITTEXT, 0, 0L); +} diff --git a/utils/mxtk/mxLineEdit.h b/utils/mxtk/mxLineEdit.h new file mode 100644 index 0000000..7bb1204 --- /dev/null +++ b/utils/mxtk/mxLineEdit.h @@ -0,0 +1,57 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxLineEdit.h +// implementation: all +// last modified: Mar 14 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXLINEEDIT +#define INCLUDED_MXLINEEDIT + + + +#ifndef INCLUDED_MXWIDGET +#include +#endif + + + +class mxWindow; + + + +class mxLineEdit_i; +class mxLineEdit : public mxWidget +{ + mxLineEdit_i *d_this; + +public: + // ENUMS + enum { Normal, ReadOnly, Password }; + + // CREATORS + mxLineEdit (mxWindow *parent, int x, int y, int w, int h, const char *label = 0, int id = 0, int style = 0); + virtual ~mxLineEdit (); + + // MANIPULATORS + void setMaxLength (int max); + + // ACCESSORS + int getMaxLength () const; + +private: + // NOT IMPLEMENTED + mxLineEdit (const mxLineEdit&); + mxLineEdit& operator= (const mxLineEdit&); +}; + + + +#endif // INCLUDED_MXLINEEDIT + diff --git a/utils/mxtk/mxLinkedList.h b/utils/mxtk/mxLinkedList.h new file mode 100644 index 0000000..1935790 --- /dev/null +++ b/utils/mxtk/mxLinkedList.h @@ -0,0 +1,191 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxLinkedList.h +// implementation: all +// last modified: Mar 19 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXLINKEDLIST +#define INCLUDED_MXLINKEDLIST + + + +typedef struct mxListNode_s +{ + void *d_data; + struct mxListNode_s *d_next; + struct mxListNode_s *d_prev; +} mxListNode; + + + +class mxLinkedList +{ + mxListNode *d_head; + mxListNode *d_tail; + int d_nodeCount; + + // NOT IMPLEMENTED + mxLinkedList (const mxLinkedList&); + mxLinkedList& operator= (const mxLinkedList&); + +public: + //CREATORS + mxLinkedList () + { + d_head = new mxListNode; + d_tail = new mxListNode; + d_head->d_data = 0; + d_head->d_next = d_tail; + d_head->d_prev = 0; + d_tail->d_data = 0; + d_tail->d_next = 0; + d_tail->d_prev = d_head; + d_nodeCount = 0; + } + + ~mxLinkedList () + { + removeAll (); + delete d_tail; + delete d_head; + } + + // MANIPULATORS + void add (void *data) + { + mxListNode *node = new mxListNode; + node->d_data = data; + d_tail->d_prev->d_next = node; + node->d_prev = d_tail->d_prev; + node->d_next = d_tail; + d_tail->d_prev = node; + ++d_nodeCount; + } + + void remove (void *data) + { + mxListNode *node = d_head->d_next; + while (node != d_tail) + { + mxListNode *next = node->d_next; + if (node->d_data == data) + { + node->d_prev->d_next = node->d_next; + node->d_next->d_prev = node->d_prev; + delete node; + } + + node = next; + } + --d_nodeCount; + } + + void removeAll () + { + mxListNode *node = d_head->d_next; + + while (node != d_tail) + { + mxListNode *next = node->d_next; + delete node; + node = next; + } + + d_head->d_next = d_tail; + d_tail->d_prev = d_head; + d_nodeCount = 0; + } + + void setData (mxListNode *node, void *data) + { + if (node) + node->d_data = data; + } + + // ACCESSORS + void *getData (mxListNode *node) const + { + if (node) + return node->d_data; + + return 0; + } + + mxListNode *getFirst () const + { + if (d_head->d_next != d_tail) + return d_head->d_next; + + return 0; + } + + mxListNode *getNext (mxListNode *node) const + { + if (node) + { + if (node->d_next != d_tail) + return node->d_next; + + return 0; + } + + return 0; + } + + mxListNode *getLast () const + { + if (d_tail->d_prev != d_head) + return d_tail->d_prev; + + return 0; + } + + mxListNode *getPrev (mxListNode *node) const + { + if (node) + { + if (node->d_prev != d_head) + return node->d_prev; + + return 0; + } + + return 0; + } + + mxListNode *at (int pos) const + { + mxListNode *node = d_head->d_next; + while (pos > 0 && node != d_tail) + { + pos--; + node = node->d_next; + } + + if (node != d_tail) + return node; + + return 0; + } + + bool isEmpty () const + { + return (d_head->d_next == d_tail); + } + + int getNodeCount () const + { + return d_nodeCount; + } +}; + + + +#endif // INCLUDED_MXLINKEDLIST \ No newline at end of file diff --git a/utils/mxtk/mxListBox.cpp b/utils/mxtk/mxListBox.cpp new file mode 100644 index 0000000..d43cfa2 --- /dev/null +++ b/utils/mxtk/mxListBox.cpp @@ -0,0 +1,158 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxListBox.cpp +// implementation: Win32 API +// last modified: Mar 18 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#include +#include +#include + + + +class mxListBox_i +{ +public: + int dummy; +}; + + + +mxListBox::mxListBox (mxWindow *parent, int x, int y, int w, int h, int id, int style) +: mxWidget (parent, x, y, w, h) +{ + if (!parent) + return; + + DWORD dwStyle = WS_VISIBLE | WS_CHILD | WS_TABSTOP | LBS_NOTIFY | WS_VSCROLL | WS_HSCROLL | WS_BORDER; + HWND hwndParent = (HWND) ((mxWidget *) parent)->getHandle (); + + if (style == MultiSelection) + dwStyle |= LBS_MULTIPLESEL | LBS_EXTENDEDSEL; + + void *handle = (void *) CreateWindowEx (WS_EX_CLIENTEDGE, "LISTBOX", "", dwStyle, + x, y, w, h, hwndParent, + (HMENU) id, (HINSTANCE) GetModuleHandle (NULL), NULL); + + SendMessage ((HWND) handle, WM_SETFONT, (WPARAM) (HFONT) GetStockObject (ANSI_VAR_FONT), MAKELPARAM (TRUE, 0)); + SetWindowLong ((HWND) handle, GWL_USERDATA, (LONG) this); + + setHandle (handle); + setType (MX_LISTBOX); + setParent (parent); + setId (id); + + parent->addWidget (this); +} + + + +mxListBox::~mxListBox () +{ + removeAll (); +} + + + +void +mxListBox::add (const char *item) +{ + SendMessage ((HWND) getHandle (), LB_ADDSTRING, 0, (LPARAM) (LPCTSTR) item); +} + + + +void +mxListBox::select (int index) +{ + SendMessage ((HWND) getHandle (), LB_SETCURSEL, (WPARAM) index, 0L); +} + + + +void +mxListBox::deselect (int index) +{ + SendMessage ((HWND) getHandle (), LB_SETSEL, (WPARAM) FALSE, (LPARAM) (UINT) index); +} + + + +void +mxListBox::remove (int index) +{ + SendMessage ((HWND) getHandle (), LB_DELETESTRING, (WPARAM) index, 0L); +} + + + +void +mxListBox::removeAll () +{ + SendMessage ((HWND) getHandle (), LB_RESETCONTENT, 0, 0L); +} + + + +void +mxListBox::setItemText (int index, const char *item) +{ + //SendMessage ((HWND) getHandle (), LB_SETTEXT, (WPARAM) index, (LPARAM) (LPCTSTR) item); +} + + + +void +mxListBox::setItemData (int index, void *data) +{ + SendMessage ((HWND) getHandle (), LB_SETITEMDATA, (WPARAM) index, (LPARAM) data); +} + + + +int +mxListBox::getItemCount () const +{ + return (int) SendMessage ((HWND) getHandle (), LB_GETCOUNT, 0, 0L); +} + + + +int +mxListBox::getSelectedIndex () const +{ + return (int) SendMessage ((HWND) getHandle (), LB_GETCURSEL, 0, 0L); +} + + + +bool +mxListBox::isSelected (int index) const +{ + return (bool) (SendMessage ((HWND) getHandle (), LB_GETSEL, (WPARAM) index, 0L) > 0); +} + + + +const char* +mxListBox::getItemText (int index) const +{ + static char text[256]; + SendMessage ((HWND) getHandle (), LB_GETTEXT, (WPARAM) index, (LPARAM) (LPCTSTR) text); + return text; +} + + + +void* +mxListBox::getItemData (int index) const +{ + return (void *) SendMessage ((HWND) getHandle (), LB_GETITEMDATA, (WPARAM) index, 0L); +} diff --git a/utils/mxtk/mxListBox.h b/utils/mxtk/mxListBox.h new file mode 100644 index 0000000..30758f2 --- /dev/null +++ b/utils/mxtk/mxListBox.h @@ -0,0 +1,66 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxListBox.h +// implementation: all +// last modified: Mar 14 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXLIST +#define INCLUDED_MXLIST + + + +#ifndef INCLUDED_MxWIDGET +#include +#endif + + + +class mxWindow; + + + +class mxListBox_i; +class mxListBox : public mxWidget +{ + mxListBox_i *d_this; + +public: + // ENUMS + enum { Normal, MultiSelection }; + + // CREATORS + mxListBox (mxWindow *parent, int x, int y, int w, int h, int id = 0, int style = 0); + virtual ~mxListBox (); + + // MANIPULATORS + void add (const char *item); + void select (int index); + void deselect (int index); + void remove (int index); + void removeAll (); + void setItemText (int index, const char *text); + void setItemData (int index, void *data); + + // ACCESSORS + int getItemCount () const; + int getSelectedIndex () const; + bool isSelected (int index) const; + const char *getItemText (int index) const; + void *getItemData (int index) const; + +private: + // NOT IMPLEMENTED + mxListBox (const mxListBox&); + mxListBox& operator= (const mxListBox&); +}; + + + +#endif // INCLUDED_MXLISTBOX diff --git a/utils/mxtk/mxMenu.cpp b/utils/mxtk/mxMenu.cpp new file mode 100644 index 0000000..5afe0df --- /dev/null +++ b/utils/mxtk/mxMenu.cpp @@ -0,0 +1,128 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxMenu.cpp +// implementation: Win32 API +// last modified: Mar 18 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#include +#include +#include +//#include +#include +using namespace std; + + + +class mxMenu_i +{ +public: + vector d_menuList; +}; + + + +mxMenu::mxMenu () +: mxWidget (0, 0, 0, 0, 0) +{ + d_this = new mxMenu_i; + void *handle = (void *) CreateMenu (); + + setHandle (handle); + setType (MX_MENU); +} + + + +mxMenu::~mxMenu () +{ + int nSize = d_this->d_menuList.size(); + for (int i = 0; i < nSize; i++) + { + delete d_this->d_menuList[i]; + } + d_this->d_menuList.clear(); + + delete d_this; +} + + + +void +mxMenu::add (const char *item, int id) +{ + AppendMenu ((HMENU) getHandle (), MF_STRING, (UINT) id, item); +} + + + +void +mxMenu::addMenu (const char *item, mxMenu *menu) +{ + AppendMenu ((HMENU) getHandle (), MF_POPUP, (UINT) menu->getHandle (), item); + d_this->d_menuList.push_back(menu); +} + + + +void +mxMenu::addSeparator () +{ + AppendMenu ((HMENU) getHandle (), MF_SEPARATOR, 0, 0); +} + + + +void +mxMenu::setEnabled (int id, bool b) +{ + EnableMenuItem ((HMENU) getHandle (), (UINT) id, MF_BYCOMMAND | (b ? MF_ENABLED:MF_GRAYED)); +} + + + +void +mxMenu::setChecked (int id, bool b) +{ + CheckMenuItem ((HMENU) getHandle (), (UINT) id, MF_BYCOMMAND | (b ? MF_CHECKED:MF_UNCHECKED)); +} + + + +bool +mxMenu::isEnabled (int id) const +{ + MENUITEMINFO mii; + + memset (&mii, 0, sizeof (mii)); + mii.cbSize = sizeof (mii); + mii.fMask = MIIM_STATE; + GetMenuItemInfo ((HMENU) getHandle (), (UINT) id, false, &mii); + if (mii.fState & MFS_GRAYED) + return true; + + return false; +} + + + +bool +mxMenu::isChecked (int id) const +{ + MENUITEMINFO mii; + + memset (&mii, 0, sizeof (mii)); + mii.cbSize = sizeof (mii); + mii.fMask = MIIM_STATE; + GetMenuItemInfo ((HMENU) getHandle (), (UINT) id, false, &mii); + if (mii.fState & MFS_CHECKED) + return true; + + return false; +} diff --git a/utils/mxtk/mxMenu.h b/utils/mxtk/mxMenu.h new file mode 100644 index 0000000..1ef188a --- /dev/null +++ b/utils/mxtk/mxMenu.h @@ -0,0 +1,54 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxMenu.h +// implementation: all +// last modified: Mar 14 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXMENU +#define INCLUDED_MXMENU + + + +#ifndef INCLUDED_MXWIDGET +#include +#endif + + + +class mxMenu_i; +class mxMenu : public mxWidget +{ + mxMenu_i *d_this; + +public: + // CREATORS + mxMenu (); + virtual ~mxMenu (); + + // MANIPULATORS + void add (const char *item, int id); + void addMenu (const char *item, mxMenu *menu); + void addSeparator (); + void setEnabled (int id, bool b); + void setChecked (int id, bool b); + + // ACCESSORS + bool isEnabled (int id) const; + bool isChecked (int id) const; + +private: + // NOT IMPLEMENTED + mxMenu (const mxMenu&); + mxMenu& operator= (const mxMenu&); +}; + + + +#endif // INCLUDED_MXMENU diff --git a/utils/mxtk/mxMenuBar.cpp b/utils/mxtk/mxMenuBar.cpp new file mode 100644 index 0000000..d9973f4 --- /dev/null +++ b/utils/mxtk/mxMenuBar.cpp @@ -0,0 +1,138 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxMenuBar.cpp +// implementation: Win32 API +// last modified: Apr 28 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#include +#include +#include +#include +#include +#include +using namespace std; + + + +class mxMenuBar_i +{ +public: + vector d_menuList; +}; + + + +mxMenuBar::mxMenuBar (mxWindow *parent) +: mxWidget (0, 0, 0, 0, 0) +{ + d_this = new mxMenuBar_i; + void *handle = (void *) CreateMenu (); + setHandle (handle); + setType (MX_MENUBAR); + setParent (parent); + + if (parent) + { + mxWidget *w = (mxWidget *) parent; + SetMenu ((HWND) w->getHandle (), (HMENU) handle); + parent->addWidget (this); + } +} + + + +mxMenuBar::~mxMenuBar () +{ + int nSize = d_this->d_menuList.size(); + for (int i = 0; i < nSize; i++) + { + delete d_this->d_menuList[i]; + } + d_this->d_menuList.clear(); + + delete d_this; + + ::DestroyMenu((HMENU) getHandle()); +} + + + +void +mxMenuBar::addMenu (const char *item, mxMenu *menu) +{ + AppendMenu ((HMENU) getHandle (), MF_POPUP, (UINT) ((mxWidget *) menu)->getHandle (), item); + d_this->d_menuList.push_back(menu); +} + + + +void +mxMenuBar::setEnabled (int id, bool b) +{ + EnableMenuItem ((HMENU) getHandle (), (UINT) id, MF_BYCOMMAND | (b ? MF_ENABLED:MF_GRAYED)); +} + + + +void +mxMenuBar::setChecked (int id, bool b) +{ + CheckMenuItem ((HMENU) getHandle (), (UINT) id, MF_BYCOMMAND | (b ? MF_CHECKED:MF_UNCHECKED)); +} + + + +void +mxMenuBar::modify (int id, int newId, const char *newItem) +{ + ModifyMenu ((HMENU) getHandle (), (UINT) id, MF_BYCOMMAND | MF_STRING, (UINT) newId, (LPCTSTR) newItem); +} + + + +bool +mxMenuBar::isEnabled (int id) const +{ + MENUITEMINFO mii; + + memset (&mii, 0, sizeof (mii)); + mii.cbSize = sizeof (mii); + mii.fMask = MIIM_STATE; + GetMenuItemInfo ((HMENU) getHandle (), (UINT) id, false, &mii); + if (mii.fState & MFS_GRAYED) + return true; + + return false; +} + + + +bool +mxMenuBar::isChecked (int id) const +{ + MENUITEMINFO mii; + + memset (&mii, 0, sizeof (mii)); + mii.cbSize = sizeof (mii); + mii.fMask = MIIM_STATE; + GetMenuItemInfo ((HMENU) getHandle (), (UINT) id, false, &mii); + if (mii.fState & MFS_CHECKED) + return true; + + return false; +} + + + +int +mxMenuBar::getHeight () const +{ + return 0; +} diff --git a/utils/mxtk/mxMenuBar.h b/utils/mxtk/mxMenuBar.h new file mode 100644 index 0000000..ac3849c --- /dev/null +++ b/utils/mxtk/mxMenuBar.h @@ -0,0 +1,59 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxMenuBar.h +// implementation: all +// last modified: Apr 28 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXMENUBAR +#define INCLUDED_MXMENUBAR + + + +#ifndef INCLUDED_MXWIDGET +#include +#endif + + + +class mxWindow; +class mxMenu; + + + +class mxMenuBar_i; +class mxMenuBar : public mxWidget +{ + mxMenuBar_i *d_this; + +public: + // CREATORS + mxMenuBar (mxWindow *parent); + virtual ~mxMenuBar (); + + // MANIPULATORS + void addMenu (const char *item, mxMenu *menu); + void setEnabled (int id, bool b); + void setChecked (int id, bool b); + void modify (int id, int newId, const char *newItem); + + // ACCESSORS + bool isEnabled (int id) const; + bool isChecked (int id) const; + int getHeight () const; + +private: + // NOT IMPLEMENTED + mxMenuBar (const mxMenuBar&); + mxMenuBar& operator= (const mxMenuBar&); +}; + + + +#endif // INCLUDED_MXMENUBAR diff --git a/utils/mxtk/mxMessageBox.cpp b/utils/mxtk/mxMessageBox.cpp new file mode 100644 index 0000000..5e267af --- /dev/null +++ b/utils/mxtk/mxMessageBox.cpp @@ -0,0 +1,63 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxMessageBox.cpp +// implementation: Win32 API +// last modified: Mar 14 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#include +#include +#include +#include +#include + + + +int +mxMessageBox (mxWindow *parent, const char *msg, const char *title, int style) +{ + HWND hwndParent = 0; + if (parent) + hwndParent = (HWND) parent->getHandle (); + + UINT uType = 0; + + if (style & MX_MB_OK) + uType |= MB_OK; + else if (style & MX_MB_YESNO) + uType |= MB_YESNO; + else if (style & MX_MB_YESNOCANCEL) + uType |= MB_YESNOCANCEL; + + if (style & MX_MB_INFORMATION) + uType |= MB_ICONINFORMATION; + else if (style & MX_MB_ERROR) + uType |= MB_ICONHAND; + else if (style & MX_MB_WARNING) + uType |= MB_ICONEXCLAMATION; + else if (style & MX_MB_QUESTION) + uType |= MB_ICONQUESTION; + + int ret = MessageBox (hwndParent, msg, title, uType); + + switch (ret) + { + case IDOK: + case IDYES: + return 0; + + case IDNO: + return 1; + + case IDCANCEL: + return 2; + } + + return 0; +} diff --git a/utils/mxtk/mxMessageBox.h b/utils/mxtk/mxMessageBox.h new file mode 100644 index 0000000..98dc7b6 --- /dev/null +++ b/utils/mxtk/mxMessageBox.h @@ -0,0 +1,47 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxMessageBox.h +// implementation: all +// last modified: Mar 14 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXMESSAGEBOX +#define INCLUDED_MXMESSAGEBOX + + + +class mxWindow; + + + +enum { + MX_MB_OK = 0, + MX_MB_YESNO = 1, + MX_MB_YESNOCANCEL = 2, + MX_MB_INFORMATION = 4, + MX_MB_ERROR = 8, + MX_MB_WARNING = 16, + MX_MB_QUESTION = 32 +}; + + + +#ifdef __cplusplus +extern "C" { +#endif + +int mxMessageBox (mxWindow *parent, const char *msg, const char *title, int style = 0); + +#ifdef __cplusplus +} +#endif + + + +#endif // INCLUDED_MXMESSAGEBOX diff --git a/utils/mxtk/mxMultiLineEdit.cpp b/utils/mxtk/mxMultiLineEdit.cpp new file mode 100644 index 0000000..6ad2b43 --- /dev/null +++ b/utils/mxtk/mxMultiLineEdit.cpp @@ -0,0 +1,86 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxMultiLineEdit.cpp +// implementation: Win32 API +// last modified: May 24 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#include +#include +#include + + + +class mxMultiLineEdit_i +{ +public: + int dummy; +}; + + + +mxMultiLineEdit::mxMultiLineEdit (mxWindow *parent, int x, int y, int w, int h, const char *label, int id, int style) +: mxWidget (parent, x, y, w, h, label) +{ + if (!parent) + return; + + DWORD dwStyle = WS_VISIBLE | WS_CHILD | WS_TABSTOP | ES_AUTOHSCROLL | ES_AUTOVSCROLL | WS_HSCROLL | WS_VSCROLL | ES_MULTILINE; + HWND hwndParent = (HWND) ((mxWidget *) parent)->getHandle (); + + if (style == ReadOnly) + dwStyle |= ES_READONLY; + else if (style == Password) + dwStyle |= ES_PASSWORD; + + void *handle = (void *) CreateWindowEx (WS_EX_CLIENTEDGE, "EDIT", label, dwStyle, + x, y, w, h, hwndParent, + (HMENU) id, (HINSTANCE) GetModuleHandle (NULL), NULL); + + SendMessage ((HWND) handle, WM_SETFONT, (WPARAM) (HFONT) GetStockObject (ANSI_VAR_FONT), MAKELPARAM (TRUE, 0)); + SetWindowLong ((HWND) handle, GWL_USERDATA, (LONG) this); + + setHandle (handle); + setType (MX_MULTILINEEDIT); + setParent (parent); + setId (id); + + parent->addWidget (this); +} + + + +mxMultiLineEdit::~mxMultiLineEdit () +{ +} + + + +void +mxMultiLineEdit::setMaxLength (int max) +{ + SendMessage ((HWND) getHandle (), EM_LIMITTEXT, (WPARAM) max, 0L); +} + + + +void +mxMultiLineEdit::appendText (const char *text) +{ + SendMessage ((HWND) getHandle (), EM_SETSEL, (WPARAM) -1, (LPARAM) -1); + SendMessage ((HWND) getHandle (), EM_REPLACESEL, 0, (LPARAM) text); +} + + + +int +mxMultiLineEdit::getMaxLength () const +{ + return (int) SendMessage ((HWND) getHandle (), EM_GETLIMITTEXT, 0, 0L); +} diff --git a/utils/mxtk/mxMultiLineEdit.h b/utils/mxtk/mxMultiLineEdit.h new file mode 100644 index 0000000..267dadd --- /dev/null +++ b/utils/mxtk/mxMultiLineEdit.h @@ -0,0 +1,58 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxMultiMultiLineEdit.h +// implementation: all +// last modified: May 24 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXMULTILINEEDIT +#define INCLUDED_MXMULTILINEEDIT + + + +#ifndef INCLUDED_MXWIDGET +#include +#endif + + + +class mxWindow; + + + +class mxMultiLineEdit_i; +class mxMultiLineEdit : public mxWidget +{ + mxMultiLineEdit_i *d_this; + +public: + // ENUMS + enum { Normal, ReadOnly, Password }; + + // CREATORS + mxMultiLineEdit (mxWindow *parent, int x, int y, int w, int h, const char *label = 0, int id = 0, int style = 0); + virtual ~mxMultiLineEdit (); + + // MANIPULATORS + void setMaxLength (int max); + void appendText (const char *text); + + // ACCESSORS + int getMaxLength () const; + +private: + // NOT IMPLEMENTED + mxMultiLineEdit (const mxMultiLineEdit&); + mxMultiLineEdit& operator= (const mxMultiLineEdit&); +}; + + + +#endif // INCLUDED_MXMULTILINEEDIT + diff --git a/utils/mxtk/mxPcx.cpp b/utils/mxtk/mxPcx.cpp new file mode 100644 index 0000000..3790d75 --- /dev/null +++ b/utils/mxtk/mxPcx.cpp @@ -0,0 +1,98 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxPcx.cpp +// implementation: all +// last modified: Apr 15 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#include +#include +#include + + + +mxImage * +mxPcxRead (const char *filename) +{ + FILE *file = fopen (filename, "rb"); + if (!file) + return 0; + + mxPcxHeader header; + if (fread (&header, sizeof (mxPcxHeader), 1, file) == -1) + { + fclose (file); + return 0; + } +/* + if (header.bitsPerPixel != 8 || + header.version != 5) + { + fclose (file); + return 0; + } + + (void) fseek (file, -769, SEEK_END); + if (fgetc (file) != 12) { + fclose (file); + return NULL; + } +*/ + (void) fseek (file, -768, SEEK_END); + + int w = header.xmax - header.xmin + 1; + int h = header.ymax - header.ymin + 1; + + mxImage *image = new mxImage (); + if (!image->create (w, h, 8)) + { + delete image; + fclose (file); + return 0; + } + + if (fread ((byte *) image->palette, sizeof (byte), 768, file) == -1) + { + fclose (file); + return 0; + } + + (void) fseek(file, sizeof (mxPcxHeader), SEEK_SET); + int ptr = 0; + int ch, rep; + byte *data = (byte *) image->data; + int size = w * h; + while (ptr < size) + { + ch = fgetc(file); + if (ch >= 192) + { + rep = ch - 192; + ch = fgetc(file); + } + else { + rep = 1; + } + + while (rep--) + data[ptr++] = ch; + } + + fclose(file); + + return image; +} + + + +bool +mxPcxWrite (const char *filename, mxImage *image) +{ + return false; +} \ No newline at end of file diff --git a/utils/mxtk/mxPcx.h b/utils/mxtk/mxPcx.h new file mode 100644 index 0000000..ee7276b --- /dev/null +++ b/utils/mxtk/mxPcx.h @@ -0,0 +1,62 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxPcx.h +// implementation: all +// last modified: Apr 15 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXPCX +#define INCLUDED_MXPCX + + + +#ifndef INCLUDED_MXIMAGE +#include +#endif + + + +typedef struct +{ + byte manufacturer; /* 10 = ZSoft .pcx */ + byte version; /* 0 = Version 2.5 of PC Paintbrush + 2 = Version 2.8 w/palette information + 3 = Version 2.8 w/o palette information + 4 = PC Paintbrush for Windows(Plus for + Windows uses Ver 5) + 5 = Version 3.0 and > of PC Paintbrush + and PC Paintbrush +, includes + Publisher's Paintbrush . Includes + 24-bit .PCX files */ + byte encoding; /* 1 = .pcx rle encoding */ + byte bitsPerPixel; /* 1, 2, 4, 8 per plane */ + word xmin; + word ymin; + word xmax; + word ymax; + word hDpi; + word vDpi; + byte colorMap[48]; + byte reserved; /* should be set to 0 */ + byte numPlanes; /* number of color planes */ + word bytesPerLine; /* MUST be EVEN number */ + word paletteInfo; /* 1 = color, 2 = grayscale */ + word hScreenSize; + word vScreenSize; + byte filler[54]; /* set all to 0 */ +} mxPcxHeader; + + + +mxImage *mxPcxRead (const char *filename); +bool mxPcxWrite (const char *filename, mxImage *image); + + + +#endif // INCLUDED_MXPCX diff --git a/utils/mxtk/mxPopupMenu.cpp b/utils/mxtk/mxPopupMenu.cpp new file mode 100644 index 0000000..03aa3e6 --- /dev/null +++ b/utils/mxtk/mxPopupMenu.cpp @@ -0,0 +1,141 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxPopupMenu.cpp +// implementation: Win32 API +// last modified: Mar 18 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#include +#include +#include +#include +using namespace std; + + + +class mxPopupMenu_i +{ +public: + vector d_menuList; +}; + + + +mxPopupMenu::mxPopupMenu () +: mxWidget (0, 0, 0, 0, 0) +{ + d_this = new mxPopupMenu_i; + void *handle = (void *) CreatePopupMenu (); + setHandle (handle); + setType (MX_POPUPMENU); +} + + + +mxPopupMenu::~mxPopupMenu () +{ + int nSize = d_this->d_menuList.size(); + for (int i = 0; i < nSize; i++) + { + delete d_this->d_menuList[i]; + } + d_this->d_menuList.clear(); + + delete d_this; + + ::DestroyMenu((HMENU) getHandle()); +} + + + +int +mxPopupMenu::popup (mxWidget *widget, int x, int y) +{ + POINT pt; + pt.x = x; + pt.y = y; + + ClientToScreen ((HWND) widget->getHandle (), &pt); + return (int) TrackPopupMenu ((HMENU) getHandle (), TPM_NONOTIFY | TPM_RETURNCMD | TPM_LEFTALIGN | TPM_TOPALIGN, pt.x, pt.y, 0, (HWND) widget->getHandle (), NULL); +} + + + +void +mxPopupMenu::add (const char *item, int id) +{ + AppendMenu ((HMENU) getHandle (), MF_STRING, (UINT) id, item); +} + + + +void +mxPopupMenu::addMenu (const char *item, mxMenu *menu) +{ + AppendMenu ((HMENU) getHandle (), MF_POPUP, (UINT) menu->getHandle (), item); + d_this->d_menuList.push_back(menu); +} + + + +void +mxPopupMenu::addSeparator () +{ + AppendMenu ((HMENU) getHandle (), MF_SEPARATOR, 0, 0); +} + + + +void +mxPopupMenu::setEnabled (int id, bool b) +{ + EnableMenuItem ((HMENU) getHandle (), (UINT) id, MF_BYCOMMAND | (b ? MF_ENABLED:MF_GRAYED)); +} + + + +void +mxPopupMenu::setChecked (int id, bool b) +{ + CheckMenuItem ((HMENU) getHandle (), (UINT) id, MF_BYCOMMAND | (b ? MF_CHECKED:MF_UNCHECKED)); +} + + + +bool +mxPopupMenu::isEnabled (int id) const +{ + MENUITEMINFO mii; + + memset (&mii, 0, sizeof (mii)); + mii.cbSize = sizeof (mii); + mii.fMask = MIIM_STATE; + GetMenuItemInfo ((HMENU) getHandle (), (UINT) id, false, &mii); + if (mii.fState & MFS_GRAYED) + return true; + + return false; +} + + + +bool +mxPopupMenu::isChecked (int id) const +{ + MENUITEMINFO mii; + + memset (&mii, 0, sizeof (mii)); + mii.cbSize = sizeof (mii); + mii.fMask = MIIM_STATE; + GetMenuItemInfo ((HMENU) getHandle (), (UINT) id, false, &mii); + if (mii.fState & MFS_CHECKED) + return true; + + return false; +} diff --git a/utils/mxtk/mxPopupMenu.h b/utils/mxtk/mxPopupMenu.h new file mode 100644 index 0000000..7c0d97a --- /dev/null +++ b/utils/mxtk/mxPopupMenu.h @@ -0,0 +1,55 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxPopupMenu.h +// implementation: all +// last modified: Mar 14 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXPOPUPMENU +#define INCLUDED_MXPOPUPMENU + + + +#ifndef INCLUDED_MXWIDGET +#include +#endif + + +class mxMenu; +class mxPopupMenu_i; +class mxPopupMenu : public mxWidget +{ + mxPopupMenu_i *d_this; + +public: + // CREATORS + mxPopupMenu (); + virtual ~mxPopupMenu (); + + // MANIPULATORS + int popup (mxWidget *widget, int x, int y); + void add (const char *item, int id); + void addMenu (const char *item, mxMenu *menu); + void addSeparator (); + void setEnabled (int id, bool b); + void setChecked (int id, bool b); + + // ACCESSORS + bool isEnabled (int id) const; + bool isChecked (int id) const; + +private: + // NOT IMPLEMENTED + mxPopupMenu (const mxPopupMenu&); + mxPopupMenu& operator= (const mxPopupMenu&); +}; + + + +#endif // INCLUDED_MXPOPUPMENU diff --git a/utils/mxtk/mxProgressBar.cpp b/utils/mxtk/mxProgressBar.cpp new file mode 100644 index 0000000..6afe490 --- /dev/null +++ b/utils/mxtk/mxProgressBar.cpp @@ -0,0 +1,95 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxProgressBar.cpp +// implementation: Win32 API +// last modified: Mar 18 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#include +#include +#include +#include + + + +class mxProgressBar_i +{ +public: + int d_value; + int d_steps; +}; + + + +mxProgressBar::mxProgressBar (mxWindow *parent, int x, int y, int w, int h, int style) +: mxWidget (parent, x, y, w, h) +{ + if (!parent) + return; + + d_this = new mxProgressBar_i; + + DWORD dwStyle = WS_VISIBLE | WS_CHILD; + HWND hwndParent = (HWND) ((mxWidget *) parent)->getHandle (); + + if (style == Smooth) + dwStyle |= PBS_SMOOTH; + + void *handle = (void *) CreateWindowEx (0, PROGRESS_CLASS, "", dwStyle, + x, y, w, h, hwndParent, + (HMENU) NULL, (HINSTANCE) GetModuleHandle (NULL), NULL); + + SendMessage ((HWND) handle, WM_SETFONT, (WPARAM) (HFONT) GetStockObject (ANSI_VAR_FONT), MAKELPARAM (TRUE, 0)); + + setHandle (handle); + setType (MX_PROGRESSBAR); + setParent (parent); + + parent->addWidget (this); +} + + + +mxProgressBar::~mxProgressBar () +{ + delete d_this; +} + + + +void +mxProgressBar::setValue (int value) +{ + d_this->d_value = value; + SendMessage ((HWND) getHandle (), PBM_SETPOS, (WPARAM) value, 0L); +} + + +void +mxProgressBar::setTotalSteps (int steps) +{ + d_this->d_steps = steps; + SendMessage ((HWND) getHandle (), PBM_SETRANGE, 0, MAKELPARAM (0, steps)); +} + + + +int +mxProgressBar::getValue () const +{ + return d_this->d_value; +} + + + +int +mxProgressBar::getTotalSteps () const +{ + return d_this->d_steps; +} diff --git a/utils/mxtk/mxProgressBar.h b/utils/mxtk/mxProgressBar.h new file mode 100644 index 0000000..eb1d275 --- /dev/null +++ b/utils/mxtk/mxProgressBar.h @@ -0,0 +1,58 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxProgressBar.h +// implementation: all +// last modified: Mar 14 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXPROGRESSBAR +#define INCLUDED_MXPROGRESSBAR + + + +#ifndef INCLUDED_MXWIDGET +#include +#endif + + + +class mxWindow; + + + +class mxProgressBar_i; +class mxProgressBar : public mxWidget +{ + mxProgressBar_i *d_this; + +public: + // ENUMS + enum { Normal, Smooth }; + + // CREATORS + mxProgressBar (mxWindow *parent, int x, int y, int w, int h, int style = 0); + virtual ~mxProgressBar (); + + // MANIPULATORS + void setValue (int value); + void setTotalSteps (int steps); + + // ACCESSORS + int getValue () const; + int getTotalSteps () const; + +private: + // NOT IMPLEMENTED + mxProgressBar (const mxProgressBar&); + mxProgressBar& operator= (const mxProgressBar&); +}; + + + +#endif // INCLUDED_MXPROGRESSBAR diff --git a/utils/mxtk/mxRadioButton.cpp b/utils/mxtk/mxRadioButton.cpp new file mode 100644 index 0000000..a4b67ea --- /dev/null +++ b/utils/mxtk/mxRadioButton.cpp @@ -0,0 +1,77 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxRadioButton.cpp +// implementation: Win32 API +// last modified: Mar 18 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#include +#include +#include + + + +class mxRadioButton_i +{ +public: + int dummy; +}; + + + +mxRadioButton::mxRadioButton (mxWindow *parent, int x, int y, int w, int h, const char *label, int id, bool newGroup) +: mxWidget (parent, x, y, w, h, label) +{ + if (!parent) + return; + + DWORD dwStyle = WS_VISIBLE | WS_CHILD | WS_TABSTOP | BS_AUTORADIOBUTTON; + HWND hwndParent = (HWND) ((mxWidget *) parent)->getHandle (); + + if (newGroup) + dwStyle |= WS_GROUP; + + void *handle = (void *) CreateWindowEx (0, "BUTTON", label, dwStyle, + x, y, w, h, hwndParent, + (HMENU) id, (HINSTANCE) GetModuleHandle (NULL), NULL); + + SendMessage ((HWND) handle, WM_SETFONT, (WPARAM) (HFONT) GetStockObject (ANSI_VAR_FONT), MAKELPARAM (TRUE, 0)); + SetWindowLong ((HWND) handle, GWL_USERDATA, (LONG) this); + + setHandle (handle); + setType (MX_RADIOBUTTON); + setParent (parent); + setId (id); + + setChecked (newGroup); + + parent->addWidget (this); +} + + + +mxRadioButton::~mxRadioButton () +{ +} + + + +void +mxRadioButton::setChecked (bool b) +{ + SendMessage ((HWND) getHandle (), BM_SETCHECK, (WPARAM) b ? BST_CHECKED:BST_UNCHECKED, 0L); +} + + + +bool +mxRadioButton::isChecked () const +{ + return (SendMessage ((HWND) getHandle (), BM_GETCHECK, 0, 0L) == BST_CHECKED); +} diff --git a/utils/mxtk/mxRadioButton.h b/utils/mxtk/mxRadioButton.h new file mode 100644 index 0000000..c135eb5 --- /dev/null +++ b/utils/mxtk/mxRadioButton.h @@ -0,0 +1,53 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxRadioButton.h +// implementation: all +// last modified: Mar 14 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXRADIOBUTTON +#define INCLUDED_MXRADIOBUTTON + + + +#ifndef INCLUDED_MXWIDGET +#include +#endif + + + +class mxWindow; + + + +class mxRadioButton_i; +class mxRadioButton : public mxWidget +{ + mxRadioButton_i *d_this; + +public: + // CREATORS + mxRadioButton (mxWindow *parent, int x, int y, int w, int h, const char *label = 0, int id = 0, bool newGroup = 0); + virtual ~mxRadioButton (); + + // MANIPULATORS + void setChecked (bool b); + + // ACCESSORS + bool isChecked () const; + +private: + // NOT IMPLEMENTED + mxRadioButton (const mxRadioButton&); + mxRadioButton& operator= (const mxRadioButton&); +}; + + + +#endif // INCLUDED_MXRADIOBUTTON diff --git a/utils/mxtk/mxSlider.cpp b/utils/mxtk/mxSlider.cpp new file mode 100644 index 0000000..788b6fd --- /dev/null +++ b/utils/mxtk/mxSlider.cpp @@ -0,0 +1,139 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxSlider.cpp +// implementation: Win32 API +// last modified: Mar 18 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#include +#include +#include +#include + + + +class mxSlider_i +{ +public: + int dummy; +}; + + + +mxSlider::mxSlider (mxWindow *parent, int x, int y, int w, int h, int id, int style) +: mxWidget (parent, x, y, w, h) +{ + if (!parent) + return; + + DWORD dwStyle = WS_CHILD | WS_VISIBLE; + HWND hwndParent = (HWND) ((mxWidget *) parent)->getHandle (); + + if (style == Horizontal) + dwStyle = WS_CHILD | WS_VISIBLE | TBS_HORZ; + else if (style == Vertical) + dwStyle = WS_CHILD | WS_VISIBLE | TBS_VERT; + + void *handle = (void *) CreateWindowEx (0, TRACKBAR_CLASS, "", dwStyle, + x, y, w, h, hwndParent, + (HMENU) id, (HINSTANCE) GetModuleHandle (NULL), NULL); + + SendMessage ((HWND) handle, WM_SETFONT, (WPARAM) (HFONT) GetStockObject (ANSI_VAR_FONT), MAKELPARAM (TRUE, 0)); + SetWindowLong ((HWND) handle, GWL_USERDATA, (LONG) this); + + setHandle (handle); + setType (MX_SLIDER); + setParent (parent); + setId (id); + setRange( 0, 100 ); + + parent->addWidget (this); +} + + + +mxSlider::~mxSlider () +{ +} + + + +void +mxSlider::setValue (float value) +{ + int ivalue = (int)((((value - m_min) / (m_max - m_min)) * m_ticks) + 0.5); + SendMessage ((HWND) getHandle (), TBM_SETPOS, (WPARAM) true, (LPARAM) ivalue); +} + + + +void +mxSlider::setRange (float min, float max, int ticks) +{ + m_min = min; + m_max = max; + m_ticks = ticks; + SendMessage ((HWND) getHandle (), TBM_SETRANGE, (WPARAM) true, (LPARAM) MAKELONG(0, ticks)); +} + + + +void +mxSlider::setSteps (int line, int page) +{ + SendMessage ((HWND) getHandle (), TBM_SETLINESIZE, 0, (LPARAM) line); + SendMessage ((HWND) getHandle (), TBM_SETPAGESIZE, 0, (LPARAM) page); +} + + +float +mxSlider::getValue () const +{ + int ivalue = SendMessage ((HWND) getHandle (), TBM_GETPOS, 0, 0L); + return (ivalue / (float)m_ticks) * (m_max - m_min) + m_min; +} + + +float +mxSlider::getTrackValue ( int ivalue ) const +{ + return (ivalue / (float)m_ticks) * (m_max - m_min) + m_min; +} + + +float +mxSlider::getMinValue () const +{ + return m_min; + // return (int) SendMessage ((HWND) getHandle (), TBM_GETRANGEMIN, 0, 0L); +} + + + +float +mxSlider::getMaxValue () const +{ + return m_max; + // return (int) SendMessage ((HWND) getHandle (), TBM_GETRANGEMAX, 0, 0L); +} + + +int +mxSlider::getLineStep () const +{ + return (int) SendMessage ((HWND) getHandle (), TBM_GETLINESIZE, 0, 0L); +} + + + +int +mxSlider::getPageStep () const +{ + return (int) SendMessage ((HWND) getHandle (), TBM_GETPAGESIZE, 0, 0L); +} diff --git a/utils/mxtk/mxSlider.h b/utils/mxtk/mxSlider.h new file mode 100644 index 0000000..3303986 --- /dev/null +++ b/utils/mxtk/mxSlider.h @@ -0,0 +1,65 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxSlider.h +// implementation: all +// last modified: Mar 14 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXSLIDER +#define INCLUDED_MXSLIDER + + + +#ifndef INCLUDED_MXWIDGET +#include +#endif + + + +class mxWindow; + + + +class mxSlider_i; +class mxSlider : public mxWidget +{ + mxSlider_i *d_this; + float m_min; + float m_max; + int m_ticks; +public: + // ENUMS + enum { Horizontal, Vertical }; + + // CREATORS + mxSlider (mxWindow *parent, int x, int y, int w, int h, int id = 0, int style = 0); + virtual ~mxSlider (); + + // MANIPULATORS + void setValue (float value); + void setRange (float min, float max, int ticks = 100); + void setSteps (int line, int page); + + // ACCESSORS + float getValue () const; + float getTrackValue ( int ivalue ) const; + float getMinValue () const; + float getMaxValue () const; + int getLineStep () const; + int getPageStep () const; + +private: + // NOT IMPLEMENTED + mxSlider (const mxSlider&); + mxSlider& operator= (const mxSlider&); +}; + + + +#endif // INCLUDED_MXSLIDER diff --git a/utils/mxtk/mxTab.cpp b/utils/mxtk/mxTab.cpp new file mode 100644 index 0000000..d2a0c1f --- /dev/null +++ b/utils/mxtk/mxTab.cpp @@ -0,0 +1,133 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxTab.cpp +// implementation: Win32 API +// last modified: Apr 18 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#include +#include +#include +#include + +#include + +class mxTab_i +{ +public: + int dummy; +}; + + + +void mxTab_resizeChild (HWND hwnd) +{ + TC_ITEM ti; + + int index = TabCtrl_GetCurSel (hwnd); + if (index >= 0) + { + ti.mask = TCIF_PARAM; + TabCtrl_GetItem (hwnd, index, &ti); + mxWidget *widget = (mxWidget *) ti.lParam; + if (widget) + { + RECT rc, rc2; + + GetWindowRect (hwnd, &rc); + ScreenToClient (GetParent (hwnd), (LPPOINT) &rc.left); + ScreenToClient (GetParent (hwnd), (LPPOINT) &rc.right); + + TabCtrl_GetItemRect (hwnd, index, &rc2); + + int ex = GetSystemMetrics (SM_CXEDGE); + int ey = GetSystemMetrics (SM_CYEDGE); + rc.top += (rc2.bottom - rc2.top) + 3 * ey; + rc.left += 2 * ex; + rc.right -= 2 * ex; + rc.bottom -= 2 * ey; + HDWP hdwp = BeginDeferWindowPos (2); + DeferWindowPos (hdwp, hwnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER); + DeferWindowPos (hdwp, (HWND) widget->getHandle (), HWND_TOP, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, SWP_SHOWWINDOW); + EndDeferWindowPos (hdwp); + } + } +} + + + +mxTab::mxTab (mxWindow *parent, int x, int y, int w, int h, int id) +: mxWidget (parent, x, y, w, h) +{ + if (!parent) + return; + + DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_CLIPSIBLINGS; + HWND hwndParent = (HWND) ((mxWidget *) parent)->getHandle (); + + void *handle = (void *) CreateWindowEx (0, WC_TABCONTROL, "", dwStyle, + x, y, w, h, hwndParent, + (HMENU) id, (HINSTANCE) GetModuleHandle (NULL), NULL); + + SendMessage ((HWND) handle, WM_SETFONT, (WPARAM) (HFONT) GetStockObject (ANSI_VAR_FONT), MAKELPARAM (TRUE, 0)); + SetWindowLong ((HWND) handle, GWL_USERDATA, (LONG) this); + + setHandle (handle); + setType (MX_TAB); + setParent (parent); + setId (id); + + parent->addWidget (this); +} + + + +mxTab::~mxTab () +{ + //TabCtrl_DeleteAllItems ((HWND) getHandle ()); +} + + + +void +mxTab::add (mxWidget *widget, const char *text) +{ + TC_ITEM ti; + + ti.mask = TCIF_TEXT | TCIF_PARAM; + ti.pszText = (LPSTR) text; + ti.lParam = (LPARAM) widget; + + TabCtrl_InsertItem ((HWND) getHandle (), TabCtrl_GetItemCount ((HWND) getHandle ()), &ti); + mxTab_resizeChild ((HWND) getHandle ()); +} + + + +void +mxTab::remove (int index) +{ + TabCtrl_DeleteItem ((HWND) getHandle (), index); +} + + + +void +mxTab::select (int index) +{ + TabCtrl_SetCurSel ((HWND) getHandle (), index); +} + + + +int +mxTab::getSelectedIndex () const +{ + return (int) TabCtrl_GetCurSel ((HWND) getHandle ()); +} diff --git a/utils/mxtk/mxTab.h b/utils/mxtk/mxTab.h new file mode 100644 index 0000000..63d0570 --- /dev/null +++ b/utils/mxtk/mxTab.h @@ -0,0 +1,55 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxTab.h +// implementation: all +// last modified: Apr 18 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXTAB +#define INCLUDED_MXTAB + + + +#ifndef INCLUDED_MXWIDGET +#include +#endif + + + +class mxWindow; + + + +class mxTab_i; +class mxTab : public mxWidget +{ + mxTab_i *d_this; + +public: + // CREATORS + mxTab (mxWindow *parent, int x, int y, int w, int h, int id = 0); + virtual ~mxTab (); + + // MANIPULATORS + void add (mxWidget *widget, const char *text); + void remove (int index); + void select (int index); + + // ACCESSORS + int getSelectedIndex () const; + +private: + // NOT IMPLEMENTED + mxTab (const mxTab&); + mxTab& operator= (const mxTab&); +}; + + + +#endif // INCLUDED_MXTAB diff --git a/utils/mxtk/mxTga.cpp b/utils/mxtk/mxTga.cpp new file mode 100644 index 0000000..f14c985 --- /dev/null +++ b/utils/mxtk/mxTga.cpp @@ -0,0 +1,140 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxTga.cpp +// implementation: all +// last modified: Apr 15 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#include +#include +#include + + + +mxImage * +mxTgaRead (const char *filename) +{ + FILE *file; + file = fopen (filename, "rb"); + if (!file) + return 0; + + byte identFieldLength; + byte colorMapType; + byte imageTypeCode; + fread (&identFieldLength, sizeof (byte), 1, file); + fread (&colorMapType, sizeof (byte), 1, file); + fread (&imageTypeCode, sizeof (byte), 1, file); + + fseek (file, 12, SEEK_SET); + + word width, height; + byte pixelSize; + fread (&width, sizeof (word), 1, file); + fread (&height, sizeof (word), 1, file); + fread (&pixelSize, sizeof (byte), 1, file); + + // only 24-bit RGB uncompressed + if (colorMapType != 0 || + imageTypeCode != 2 || + pixelSize != 24) + { + fclose (file); + return 0; + } + + fseek (file, 18 + identFieldLength, SEEK_SET); + + mxImage *image = new mxImage (); + if (!image->create (width, height, 24)) + { + delete image; + fclose (file); + return 0; + } + + byte *data = (byte *) image->data; + for (int y = 0; y < height; y++) + { + byte *scanline = (byte *) &data[(height - y - 1) * width * 3]; + for (int x = 0; x < width; x++) + { + scanline[x * 3 + 2] = (byte) fgetc (file); + scanline[x * 3 + 1] = (byte) fgetc (file); + scanline[x * 3 + 0] = (byte) fgetc (file); + //scanline[x * 4 + 3] = 0xff; + } + } + + fclose (file); + + return image; +} + + + +bool +mxTgaWrite (const char *filename, mxImage *image) +{ + if (!image) + return false; + + if (image->bpp != 24) + return false; + + FILE *file = fopen (filename, "wb"); + if (!file) + return false; + + // + // write header + // + fputc (0, file); // identFieldLength + fputc (0, file); // colorMapType == 0, no color map + fputc (2, file); // imageTypeCode == 2, uncompressed RGB + + word w = 0; + fwrite (&w, sizeof (word), 1, file); // colorMapOrigin + fwrite (&w, sizeof (word), 1, file); // colorMapLength + fputc (0, file); // colorMapEntrySize + + fwrite (&w, sizeof (word), 1, file); // imageOriginX + fwrite (&w, sizeof (word), 1, file); // imageOriginY + + w = (word) image->width; + fwrite (&w, sizeof (word), 1, file); // imageWidth + + w = (word) image->height; + fwrite (&w, sizeof (word), 1, file); // imageHeight + + fputc (24, file); // imagePixelSize + fputc (0, file); // imageDescriptorByte + + // write no ident field + + // write no color map + + // write imagedata + + byte *data = (byte *) image->data; + for (int y = 0; y < image->height; y++) + { + byte *scanline = (byte *) &data[(image->height - y - 1) * image->width * 3]; + for (int x = 0; x < image->width; x++) + { + fputc ((byte) scanline[x * 3 + 2], file); + fputc ((byte) scanline[x * 3 + 1], file); + fputc ((byte) scanline[x * 3 + 0], file); + } + } + + fclose (file); + + return true; +} \ No newline at end of file diff --git a/utils/mxtk/mxTga.h b/utils/mxtk/mxTga.h new file mode 100644 index 0000000..5f7b33d --- /dev/null +++ b/utils/mxtk/mxTga.h @@ -0,0 +1,30 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxTga.h +// implementation: all +// last modified: Apr 15 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXTGA +#define INCLUDED_MXTGA + + + +#ifndef INCLUDED_MXIMAGE +#include +#endif + + + +mxImage *mxTgaRead (const char *filename); +bool mxTgaWrite (const char *filename, mxImage *image); + + + +#endif // INCLUDED_MXTGA diff --git a/utils/mxtk/mxToggleButton.cpp b/utils/mxtk/mxToggleButton.cpp new file mode 100644 index 0000000..f31dc6d --- /dev/null +++ b/utils/mxtk/mxToggleButton.cpp @@ -0,0 +1,72 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxToggleButton.cpp +// implementation: Win32 API +// last modified: Apr 28 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#include +#include +#include + + + +class mxToggleButton_i +{ +public: + int dummy; +}; + + + +mxToggleButton::mxToggleButton (mxWindow *parent, int x, int y, int w, int h, const char *label, int id) +: mxWidget (parent, x, y, w, h, label) +{ + if (!parent) + return; + + HWND hwndParent = (HWND) ((mxWidget *) parent)->getHandle (); + + void *handle = (void *) CreateWindowEx (0, "BUTTON", label, WS_VISIBLE | WS_CHILD | WS_TABSTOP | BS_PUSHLIKE | BS_AUTOCHECKBOX, + x, y, w, h, hwndParent, + (HMENU) id, (HINSTANCE) GetModuleHandle (NULL), NULL); + + SendMessage ((HWND) handle, WM_SETFONT, (WPARAM) (HFONT) GetStockObject (ANSI_VAR_FONT), MAKELPARAM (TRUE, 0)); + SetWindowLong ((HWND) handle, GWL_USERDATA, (LONG) this); + + setHandle (handle); + setType (MX_TOGGLEBUTTON); + setParent (parent); + setId (id); + setChecked (false); + + parent->addWidget (this); +} + + + +mxToggleButton::~mxToggleButton () +{ +} + + + +void +mxToggleButton::setChecked (bool b) +{ + SendMessage ((HWND) getHandle (), BM_SETCHECK, (WPARAM) b ? BST_CHECKED:BST_UNCHECKED, 0L); +} + + + +bool +mxToggleButton::isChecked () const +{ + return (SendMessage ((HWND) getHandle (), BM_GETCHECK, 0, 0L) == BST_CHECKED); +} diff --git a/utils/mxtk/mxToggleButton.h b/utils/mxtk/mxToggleButton.h new file mode 100644 index 0000000..07dd761 --- /dev/null +++ b/utils/mxtk/mxToggleButton.h @@ -0,0 +1,53 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxToggleButton.h +// implementation: all +// last modified: Apr 28 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXTOGGLEBUTTON +#define INCLUDED_MXTOGGLEBUTTON + + + +#ifndef INCLUDED_MXWIDEGT +#include +#endif + + + +class mxWindow; + + + +class mxToggleButton_i; +class mxToggleButton : public mxWidget +{ + mxToggleButton_i *d_this; + +public: + // CREATORS + mxToggleButton (mxWindow *parent, int x, int y, int w, int h, const char *label = 0, int id = 0); + virtual ~mxToggleButton (); + + // MANIPULATORS + void setChecked (bool b); + + // ACCESSORS + bool isChecked () const; + +private: + // NOT IMPLEMENTED + mxToggleButton (const mxToggleButton&); + mxToggleButton& operator= (const mxToggleButton&); +}; + + + +#endif // INCLUDED_MXTOGGLEBUTTON diff --git a/utils/mxtk/mxToolTip.cpp b/utils/mxtk/mxToolTip.cpp new file mode 100644 index 0000000..da7697f --- /dev/null +++ b/utils/mxtk/mxToolTip.cpp @@ -0,0 +1,41 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxToolTip.cpp +// implementation: Win32 API +// last modified: Mar 14 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#include +#include +#include +#include + + + +HWND mx_CreateToolTipControl (void); + + + +void +mxToolTip::add (mxWidget *widget, const char *text) +{ + if (!widget) + return; + + TOOLINFO ti; + + memset (&ti, 0, sizeof (TOOLINFO)); + ti.cbSize = sizeof (TOOLINFO); + ti.uFlags = TTF_IDISHWND | TTF_SUBCLASS; + ti.uId = (UINT) (HWND) widget->getHandle (); + ti.lpszText = (LPTSTR) text; + + HWND ctrl = mx_CreateToolTipControl (); + SendMessage (ctrl, TTM_ADDTOOL, 0, (LPARAM) &ti); +} diff --git a/utils/mxtk/mxToolTip.h b/utils/mxtk/mxToolTip.h new file mode 100644 index 0000000..ed38ad8 --- /dev/null +++ b/utils/mxtk/mxToolTip.h @@ -0,0 +1,41 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxToolTip.h +// implementation: all +// last modified: Mar 14 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXTOOLTIP +#define INCLUDED_MXTOOLTIP + + + +class mxWidget; + + + +class mxToolTip +{ +public: + // NO CREATORS + mxToolTip() {} + virtual ~mxToolTip () {} + + // MANIPULATORS + static void add (mxWidget *widget, const char *text); + +private: + // NOT IMPLEMENTED + mxToolTip (const mxToolTip&); + mxToolTip& operator= (const mxToolTip&); +}; + + + +#endif // INCLUDED_MXTOOLTIP diff --git a/utils/mxtk/mxTreeView.cpp b/utils/mxtk/mxTreeView.cpp new file mode 100644 index 0000000..c6522da --- /dev/null +++ b/utils/mxtk/mxTreeView.cpp @@ -0,0 +1,309 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxTreeView.cpp +// implementation: Win32 API +// last modified: May 03 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#include +#include +#include +#include + + + +class mxTreeView_i +{ +public: + HWND d_hwnd; +}; + + + +mxTreeView::mxTreeView (mxWindow *parent, int x, int y, int w, int h, int id) +: mxWidget (parent, x, y, w, h) +{ + if (!parent) + return; + + d_this = new mxTreeView_i; + + DWORD dwStyle = TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | TVS_SHOWSELALWAYS | WS_VISIBLE | WS_CHILD | WS_TABSTOP; + HWND hwndParent = (HWND) ((mxWidget *) parent)->getHandle (); + + d_this->d_hwnd = CreateWindowEx (WS_EX_CLIENTEDGE, WC_TREEVIEW, "", dwStyle, + x, y, w, h, hwndParent, + (HMENU) id, (HINSTANCE) GetModuleHandle (NULL), NULL); + + SendMessage (d_this->d_hwnd, WM_SETFONT, (WPARAM) (HFONT) GetStockObject (ANSI_VAR_FONT), MAKELPARAM (TRUE, 0)); + SetWindowLong (d_this->d_hwnd, GWL_USERDATA, (LONG) this); + + setHandle ((void *) d_this->d_hwnd); + setType (MX_TREEVIEW); + setParent (parent); + setId (id); + + parent->addWidget (this); +} + + + +mxTreeView::~mxTreeView () +{ + remove (0); + delete d_this; +} + + + +mxTreeViewItem* +mxTreeView::add (mxTreeViewItem *parent, const char *item) +{ + if (!d_this) + return 0; + + TV_ITEM tvItem; + tvItem.mask = TVIF_TEXT; + tvItem.pszText = (LPSTR) item; + tvItem.cchTextMax = 256; + + HTREEITEM hParent; + if (!parent) + hParent = TVI_ROOT; + else + hParent = (HTREEITEM) parent; + + TV_INSERTSTRUCT tvInsert; + tvInsert.hParent = hParent; + tvInsert.hInsertAfter = TVI_LAST; + tvInsert.item = tvItem; + + return (mxTreeViewItem *) TreeView_InsertItem (d_this->d_hwnd, &tvInsert); +} + + +void +mxTreeView::remove (mxTreeViewItem *item) +{ + if (!d_this) + return; + + if (!item) + TreeView_DeleteAllItems (d_this->d_hwnd); + else + TreeView_DeleteItem (d_this->d_hwnd, (HTREEITEM) item); +} + + + +void +mxTreeView::setLabel (mxTreeViewItem *item, const char *label) +{ + if (!d_this) + return; + + if (item) + { + TV_ITEM tvItem; + tvItem.mask = TVIF_HANDLE | TVIF_TEXT; + tvItem.hItem = (HTREEITEM) item; + tvItem.pszText = (LPSTR) label; + tvItem.cchTextMax = 256; + + TreeView_SetItem (d_this->d_hwnd, &tvItem); + } +} + + + +void +mxTreeView::setUserData (mxTreeViewItem *item, void *userData) +{ + if (!d_this) + return; + + if (item) + { + TV_ITEM tvItem; + tvItem.mask = TVIF_HANDLE | TVIF_PARAM; + tvItem.hItem = (HTREEITEM) item; + tvItem.lParam = (LPARAM) userData; + + TreeView_SetItem (d_this->d_hwnd, &tvItem); + } +} + + + +void +mxTreeView::setOpen (mxTreeViewItem *item, bool b) +{ + if (!d_this) + return; + + if (item) + TreeView_Expand (d_this->d_hwnd, (HTREEITEM) item, b ? TVE_EXPAND:TVE_COLLAPSE); +} + + + +void +mxTreeView::setSelected (mxTreeViewItem *item, bool b) +{ + if (!d_this) + return; + + if (item) + TreeView_SelectItem (d_this->d_hwnd, (HTREEITEM) item); +} + + + +mxTreeViewItem* +mxTreeView::getFirstChild (mxTreeViewItem *item) const +{ + if (!d_this) + return 0; + + return (mxTreeViewItem *) TreeView_GetChild (d_this->d_hwnd, item ? (HTREEITEM) item:TVI_ROOT); +} + + + +mxTreeViewItem* +mxTreeView::getNextChild (mxTreeViewItem *item) const +{ + if (!d_this) + return 0; + + if (item) + return (mxTreeViewItem *) TreeView_GetNextSibling (d_this->d_hwnd, (HTREEITEM) item); + else + return 0; +} + + + +mxTreeViewItem* +mxTreeView::getSelectedItem () const +{ + if (!d_this) + return 0; + + return (mxTreeViewItem *) TreeView_GetSelection (d_this->d_hwnd); +} + + + +const char* +mxTreeView::getLabel (mxTreeViewItem *item) const +{ + static char label[256]; + strcpy (label, ""); + + if (!d_this) + return label; + + if (item) + { + TV_ITEM tvItem; + tvItem.mask = TVIF_HANDLE | TVIF_TEXT; + tvItem.hItem = (HTREEITEM) item; + tvItem.pszText = (LPSTR) label; + tvItem.cchTextMax = 256; + + TreeView_GetItem (d_this->d_hwnd, &tvItem); + + return tvItem.pszText; + } + + return label; +} + + + +void* +mxTreeView::getUserData (mxTreeViewItem *item) const +{ + if (!d_this) + return 0; + + if (item) + { + TV_ITEM tvItem; + tvItem.mask = TVIF_HANDLE | TVIF_PARAM; + tvItem.hItem = (HTREEITEM) item; + + TreeView_GetItem (d_this->d_hwnd, &tvItem); + + return (void *) tvItem.lParam; + } + + return 0; +} + + + +bool +mxTreeView::isOpen (mxTreeViewItem *item) const +{ + if (!d_this) + return false; + + if (item) + { + TV_ITEM tvItem; + tvItem.mask = TVIF_HANDLE | TVIF_STATE; + tvItem.hItem = (HTREEITEM) item; + tvItem.stateMask = TVIS_STATEIMAGEMASK; + + TreeView_GetItem (d_this->d_hwnd, &tvItem); + + return ((tvItem.state & TVIS_EXPANDED) == TVIS_EXPANDED); + } + + return false; +} + + + +bool +mxTreeView::isSelected (mxTreeViewItem *item) const +{ + if (!d_this) + return false; + + if (item) + { + TV_ITEM tvItem; + tvItem.mask = TVIF_HANDLE | TVIF_STATE; + tvItem.hItem = (HTREEITEM) item; + tvItem.stateMask = TVIS_STATEIMAGEMASK; + + TreeView_GetItem (d_this->d_hwnd, &tvItem); + + return ((tvItem.state & TVIS_SELECTED) == TVIS_SELECTED); + } + + return false; +} + + + +mxTreeViewItem * +mxTreeView::getParent (mxTreeViewItem *item) const +{ + if (!d_this) + return 0; + + if (item) + return (mxTreeViewItem *) TreeView_GetParent (d_this->d_hwnd, (HTREEITEM) item); + + return 0; +} diff --git a/utils/mxtk/mxTreeView.h b/utils/mxtk/mxTreeView.h new file mode 100644 index 0000000..4cc923b --- /dev/null +++ b/utils/mxtk/mxTreeView.h @@ -0,0 +1,70 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxTreeView.h +// implementation: all +// last modified: Apr 12 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXTREEVIEW +#define INCLUDED_MXTREEVIEW + + + +#ifndef INCLUDED_MXWIDGET +#include +#endif + + + +class mxWindow; + + + +typedef void *mxTreeViewItem; + + + +class mxTreeView_i; +class mxTreeView : public mxWidget +{ + mxTreeView_i *d_this; + +public: + // CREATORS + mxTreeView (mxWindow *parent, int x, int y, int w, int h, int id = 0); + virtual ~mxTreeView (); + + // MANIPULATORS + mxTreeViewItem *add (mxTreeViewItem *parent, const char *item); + void remove (mxTreeViewItem *item); + void removeAll (); + void setLabel (mxTreeViewItem *item, const char *label); + void setUserData (mxTreeViewItem *item, void *userData); + void setOpen (mxTreeViewItem *item, bool b); + void setSelected (mxTreeViewItem *item, bool b); + + // ACCESSORS + mxTreeViewItem *getFirstChild (mxTreeViewItem *item) const; + mxTreeViewItem *getNextChild (mxTreeViewItem *item) const; + mxTreeViewItem *getSelectedItem () const; + const char *getLabel (mxTreeViewItem *item) const; + void *getUserData (mxTreeViewItem *item) const; + bool isOpen (mxTreeViewItem *item) const; + bool isSelected (mxTreeViewItem *item) const; + mxTreeViewItem *getParent (mxTreeViewItem *item) const; + +private: + // NOT IMPLEMENTED + mxTreeView (const mxTreeView&); + mxTreeView& operator= (const mxTreeView&); +}; + + + +#endif // INCLUDED_MXTREEVIEW diff --git a/utils/mxtk/mxWidget.cpp b/utils/mxtk/mxWidget.cpp new file mode 100644 index 0000000..433d926 --- /dev/null +++ b/utils/mxtk/mxWidget.cpp @@ -0,0 +1,304 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxWidget.cpp +// implementation: Win32 API +// last modified: Jun 08 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#include +#include +#include +#include +#include +#include + +void mxTab_resizeChild (HWND hwnd); + + + +class mxWidget_i +{ +public: + mxWindow *d_parent_p; + HWND d_hwnd; + void *d_userData; + int d_type; +}; + + + +mxWidget::mxWidget (mxWindow *parent, int x, int y, int w, int h, const char *label) +{ + d_this = new mxWidget_i; + + setHandle (0); + setType (-1); + setParent (parent); + setBounds (x, y, w, h); + setVisible (true); + setEnabled (true); + setId (0); + setUserData (0); + setLabel (label); +} + + + +mxWidget::~mxWidget () +{ + if (d_this->d_type == MX_MENU || + d_this->d_type == MX_MENUBAR || + d_this->d_type == MX_POPUPMENU) + ;//DestroyMenu ((HMENU) d_this->d_hwnd); + else + DestroyWindow (d_this->d_hwnd); + + delete d_this; +} + + + +void +mxWidget::setHandle (void *handle) +{ + d_this->d_hwnd = (HWND) handle; +} + + + +void +mxWidget::setType (int type) +{ + d_this->d_type = type; +} + + + +void +mxWidget::setParent (mxWindow *parentWindow) +{ + d_this->d_parent_p = parentWindow; +} + + + +void +mxWidget::setBounds (int x, int y, int w, int h) +{ + if (::IsWindow(d_this->d_hwnd)) + { + char str[128]; + GetClassName (d_this->d_hwnd, str, 128); + + if (!strcmp (str, "COMBOBOX")) + MoveWindow (d_this->d_hwnd, x, y, w, h + 100, TRUE); + else + MoveWindow (d_this->d_hwnd, x, y, w, h, TRUE); + + if (!strcmp (str, WC_TABCONTROL)) + mxTab_resizeChild (d_this->d_hwnd); + } +} + + + +void +mxWidget::setLabel (const char *format, ... ) +{ + if (format == NULL) + { + if (::IsWindow(d_this->d_hwnd)) + { + SetWindowText (d_this->d_hwnd, NULL); + } + return; + } + + va_list argptr; + static char string[1024]; + + va_start (argptr, format); + vsprintf (string, format,argptr); + va_end (argptr); + + if (::IsWindow(d_this->d_hwnd)) + { + SetWindowText (d_this->d_hwnd, string); + } +} + + + +void +mxWidget::setVisible (bool b) +{ + if (::IsWindow(d_this->d_hwnd)) + { + if (b) + SetWindowPos (d_this->d_hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); + else + ShowWindow (d_this->d_hwnd, SW_HIDE); + } +} + + + +void +mxWidget::setEnabled (bool b) +{ + if (::IsWindow(d_this->d_hwnd)) + { + EnableWindow (d_this->d_hwnd, b); + } +} + + + +void +mxWidget::setId (int id) +{ + if (::IsWindow(d_this->d_hwnd)) + { + SetWindowLong (d_this->d_hwnd, GWL_ID, (LONG) id); + } +} + + + +void +mxWidget::setUserData (void *userData) +{ + d_this->d_userData = userData; +} + + + +void* +mxWidget:: getHandle () const +{ + return (void *) d_this->d_hwnd; +} + + + +int +mxWidget::getType () const +{ + return d_this->d_type; +} + + + +mxWindow* +mxWidget::getParent () const +{ + return d_this->d_parent_p; +} + + + +int +mxWidget::x () const +{ + RECT rc; + GetWindowRect (d_this->d_hwnd, &rc); + return (int) rc.left; +} + + + +int +mxWidget::y () const +{ + RECT rc; + GetWindowRect (d_this->d_hwnd, &rc); + return (int) rc.top; +} + + + +int +mxWidget::w () const +{ + RECT rc; + GetWindowRect (d_this->d_hwnd, &rc); + return (int) (rc.right - rc.left); +} + + + +int +mxWidget::h () const +{ + RECT rc; + GetWindowRect (d_this->d_hwnd, &rc); + return (int) (rc.bottom - rc.top); +} + + + +int +mxWidget::w2 () const +{ + RECT rc; + GetClientRect (d_this->d_hwnd, &rc); + return (int) (rc.right - rc.left); +} + + + +int +mxWidget::h2 () const +{ + RECT rc; + GetClientRect (d_this->d_hwnd, &rc); + return (int) (rc.bottom - rc.top); +} + + + +const char* +mxWidget::getLabel () const +{ + static char label[256]; + GetWindowText (d_this->d_hwnd, label, 256); + return label; +} + + + +bool +mxWidget::isVisible () const +{ + return (IsWindowVisible (d_this->d_hwnd) == TRUE); +} + + + +bool +mxWidget::isEnabled () const +{ + return (IsWindowEnabled (d_this->d_hwnd) == TRUE); +} + + + +int +mxWidget::getId () const +{ + return (int) GetWindowLong (d_this->d_hwnd, GWL_ID); +} + + + +void* +mxWidget::getUserData () const +{ + return d_this->d_userData; +} diff --git a/utils/mxtk/mxWidget.h b/utils/mxtk/mxWidget.h new file mode 100644 index 0000000..ab68f6c --- /dev/null +++ b/utils/mxtk/mxWidget.h @@ -0,0 +1,105 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxWidget.h +// implementation: all +// last modified: May 24 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXWIDGET +#define INCLUDED_MXWIDGET + + + +enum +{ + MX_BUTTON, + MX_CHECKBOX, + MX_CHOICE, + MX_GLWINDOW, + MX_GROUPBOX, + MX_LABEL, + MX_LINEEDIT, + MX_LISTBOX, + MX_MENU, + MX_MENUBAR, + MX_MULTILINEEDIT, + MX_POPUPMENU, + MX_PROGRESSBAR, + MX_RADIOBUTTON, + MX_SLIDER, + MX_TAB, + MX_TOGGLEBUTTON, + MX_TREEVIEW, + MX_WINDOW, + MX_DIALOGWINDOW +}; + + + +class mxWindow; + + + +class mxWidget_i; +class mxWidget +{ + mxWidget_i *d_this; + +protected: + void setHandle (void *handle); + void setType (int type); + void setParent (mxWindow *parentWindow); + +public: + // CREATORS + mxWidget (mxWindow *parent, int x, int y, int w, int h, const char *label = 0); + virtual ~mxWidget (); + + // MANIPULATORS + + // void setBounds (int x, int y, int w, int h); + // + // + void setBounds (int x, int y, int w, int h); + + // void setLabel (const char *label); + // + // + void setLabel (const char *format, ... ); + + void setVisible (bool b); + void setEnabled (bool b); + void setId (int id); + void setUserData (void *userData); + + // ACCESSORS + void *getHandle () const; + int getType () const; + mxWindow *getParent () const; + int x () const; + int y () const; + int w () const; + int h () const; + int w2 () const; + int h2 () const; + const char *getLabel () const; + bool isVisible () const; + bool isEnabled () const; + int getId () const; + void *getUserData () const; + +private: + // NOT IMPLEMENTED + mxWidget (const mxWidget&); + mxWidget& operator= (const mxWidget&); +}; + + + +#endif // INCLUDED_MXWIDGET diff --git a/utils/mxtk/mxWindow.cpp b/utils/mxtk/mxWindow.cpp new file mode 100644 index 0000000..0805055 --- /dev/null +++ b/utils/mxtk/mxWindow.cpp @@ -0,0 +1,287 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxWindow.cpp +// implementation: Win32 API +// last modified: Jun 01 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#include +#include +#include +#include + +#include +using namespace std; + +#define CLASSNAME "MXTK::CLASSNAME" + +extern mxWindow *g_mainWindow; + + + +class mxWindow_i +{ +public: + UINT d_uTimer; + int d_dlgRet; + bool d_dlgRunning; + vector d_widgetList; +}; + + + +mxWindow::mxWindow (mxWindow *parent, int x, int y, int w, int h, const char *label, int style) +: mxWidget (parent, x, y, w, h, label) +{ + d_this = new mxWindow_i; + d_this->d_uTimer = 0; + d_this->d_dlgRet = 0; + d_this->d_dlgRunning = false; + m_isMaximized = false; + + DWORD dwStyle; + DWORD dwStyleEx = 0; + if (style == Normal) + dwStyle = WS_OVERLAPPEDWINDOW; + else if (style == Popup) + dwStyle = WS_POPUP; + else if (style == Dialog) + dwStyle = WS_DLGFRAME | WS_CAPTION; + else if (style == ModalDialog) + dwStyle = WS_DLGFRAME | WS_CAPTION; + + void *parentHandle = 0; + if (parent) + { + parentHandle = parent->getHandle (); + dwStyle = WS_CHILD | WS_VISIBLE/* | WS_CLIPCHILDREN | WS_CLIPSIBLINGS*/ | WS_GROUP | WS_TABSTOP; + //dwStyleEx = WS_EX_CONTROLPARENT; + } + else + { + x = y = CW_USEDEFAULT; + } + + void *handle = (void *) CreateWindowEx (dwStyleEx, CLASSNAME, label, dwStyle, + x, y, w, h, (HWND) parentHandle, + (HMENU) NULL, (HINSTANCE) GetModuleHandle (NULL), NULL); + + SetWindowLong ((HWND) handle, GWL_USERDATA, (LONG) this); + + setHandle (handle); + setType (MX_WINDOW); + setParent (parent); + if (style == ModalDialog) + setType (MX_DIALOGWINDOW); + + if (!parent && !g_mainWindow) + g_mainWindow = this; + + if (parent) + parent->addWidget(this); + + //MC: d_widgetList = new mxLinkedList (); +} + + + +mxWindow::~mxWindow () +{ +/*MC: + mxListNode *node = d_widgetList->getFirst (); + while (node) + { + mxWidget *widget = (mxWidget *) d_widgetList->getData (node); + delete widget; + node = d_widgetList->getNext (node); + } + d_widgetList->removeAll (); + delete d_widgetList; +*/ + int nSize = d_this->d_widgetList.size(); + for (int i = 0; i < nSize; i++) + { + delete d_this->d_widgetList[i]; + } + d_this->d_widgetList.clear(); + + SetWindowLong ((HWND) getHandle (), GWL_USERDATA, (LONG) 0); + delete d_this; +} + + + +void +mxWindow::addWidget (mxWidget *widget) +{ + if (d_this->d_widgetList.empty()) + //MC: if (d_widgetList->isEmpty ()) + { + HWND hWnd = (HWND) widget->getHandle (); + if (::IsWindow(hWnd)) + { + LONG l = GetWindowLong (hWnd, GWL_STYLE); + l |= WS_GROUP; + SetWindowLong (hWnd, GWL_STYLE, l); + } + } + //MC: d_widgetList->add (widget); + d_this->d_widgetList.push_back(widget); +} + + + +int +mxWindow::handleEvent (mxEvent *event) +{ + return 0; +} + + + +void +mxWindow::redraw () +{ +} + +void mxWindow :: Maximize( void ) +{ + ShowWindow ((HWND) getHandle (), SW_SHOWMAXIMIZED ); + m_isMaximized = true; +} + +void +mxWindow::setTimer (int milliSeconds) +{ + if (d_this->d_uTimer) + { + KillTimer ((HWND) getHandle (), d_this->d_uTimer); + d_this->d_uTimer = 0; + } + + if (milliSeconds > 0) + { + d_this->d_uTimer = 21001; + d_this->d_uTimer = SetTimer ((HWND) getHandle (), d_this->d_uTimer, milliSeconds, NULL); + } +} + + + +void +mxWindow::setMenuBar (mxMenuBar *menuBar) +{ + SetMenu ((HWND) getHandle (), (HMENU) ((mxWidget *) menuBar)->getHandle ()); +} + + + +int +mxWindow::doModal () +{ + d_this->d_dlgRet = 0; + d_this->d_dlgRunning = true; + MSG msg; + + mx::setEventWindow (this); + setVisible (true); + + // acquire and dispatch messages until the modal state is done + for (;;) + { +#if 0 + // phase1: check to see if we can do idle work + while (bIdle && + !::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE)) + { + ASSERT(ContinueModal()); + + // show the dialog when the message queue goes idle + if (bShowIdle) + { + ShowWindow(SW_SHOWNORMAL); + UpdateWindow(); + bShowIdle = FALSE; + } + + // call OnIdle while in bIdle state + if (!(dwFlags & MLF_NOIDLEMSG) && hWndParent != NULL && lIdleCount == 0) + { + // send WM_ENTERIDLE to the parent + ::SendMessage(hWndParent, WM_ENTERIDLE, MSGF_DIALOGBOX, (LPARAM)m_hWnd); + } + if ((dwFlags & MLF_NOKICKIDLE) || + !SendMessage(WM_KICKIDLE, MSGF_DIALOGBOX, lIdleCount++)) + { + // stop idle processing next time + bIdle = FALSE; + } + } +#endif + // phase2: pump messages while available + do + { +#if 0 + // pump message, but quit on WM_QUIT + if (!AfxGetThread()->PumpMessage()) + { + AfxPostQuitMessage(0); + return -1; + } +#endif + if (GetMessage (&msg, 0, 0, 0)) + { + TranslateMessage (&msg); + DispatchMessage (&msg); + } + else + { + PostQuitMessage (0); + return -1; + } + + // show the window when certain special messages rec'd + if (/*bShowIdle &&*/ + (msg.message == 0x118 || msg.message == WM_SYSKEYDOWN)) + { + ShowWindow ((HWND) getHandle (), SW_SHOWNORMAL); + UpdateWindow ((HWND) getHandle ()); + //bShowIdle = FALSE; + } + + if (!d_this->d_dlgRunning) + goto ExitModal; +#if 0 + // reset "no idle" state after pumping "normal" message + if (AfxGetThread()->IsIdleMessage(pMsg)) + { + bIdle = TRUE; + lIdleCount = 0; + } +#endif + } while (PeekMessage(&msg, NULL, NULL, NULL, PM_NOREMOVE)); + } + +ExitModal: + + PostMessage ((HWND) getHandle (), WM_NULL, 0, 0L); + + return d_this->d_dlgRet; +} + + + +void +mxWindow::endDialog (int retValue) +{ + d_this->d_dlgRet = retValue; + d_this->d_dlgRunning = false; + mx::setEventWindow (0); + setVisible (false); +} diff --git a/utils/mxtk/mxWindow.h b/utils/mxtk/mxWindow.h new file mode 100644 index 0000000..b801c42 --- /dev/null +++ b/utils/mxtk/mxWindow.h @@ -0,0 +1,77 @@ +// +// mxToolKit (c) 1999 by Mete Ciragan +// +// file: mxWindow.h +// implementation: all +// last modified: Mar 14 1999, Mete Ciragan +// copyright: The programs and associated files contained in this +// distribution were developed by Mete Ciragan. The programs +// are not in the public domain, but they are freely +// distributable without licensing fees. These programs are +// provided without guarantee or warrantee expressed or +// implied. +// +#ifndef INCLUDED_MXWINDOW +#define INCLUDED_MXWINDOW + + + +#ifndef INCLUDED_MXWIDGET +#include +#endif + +#ifndef INCLUDED_MXEVENT +#include +#endif + + + +class mxMenuBar; +class mxLinkedList; + + + +class mxWindow_i; +class mxWindow : public mxWidget +{ + mxWindow_i *d_this; + mxLinkedList *d_widgetList; + bool m_isMaximized; +public: + // ENUMS + enum { Normal, Popup, Dialog, ModalDialog }; + + // CREATORS + mxWindow (mxWindow *parent, int x, int y, int w, int h, const char *label = 0, int style = 0); + virtual ~mxWindow (); + + // MANIPULATORS + void addWidget (mxWidget *widget); + virtual int handleEvent (mxEvent *event); +/* + virtual int handleActionEvent (int action); + virtual void handleSizeEvent (int w, int h); + virtual void handleMouseEvent (int event, int x, int y, int button); + virtual void handleKeyEvent (int event, int key); + virtual void handleTimerEvent (); + virtual void handleIdleEvent (); +*/ + virtual void redraw (); + + void setTimer (int milliSeconds); + void setMenuBar (mxMenuBar *menuBar); + + int doModal (); + void endDialog (int retValue); + bool isMaximized( void ) { return m_isMaximized; } + void setMaximized( bool b ) { m_isMaximized = b; } + void Maximize( void ); +private: + // NOT IMPLEMENTED + mxWindow (const mxWindow&); + mxWindow& operator= (const mxWindow&); +}; + + + +#endif // INCLUDED_MXWINDOW diff --git a/utils/mxtk/mxtk.dsp b/utils/mxtk/mxtk.dsp new file mode 100644 index 0000000..71dc1ce --- /dev/null +++ b/utils/mxtk/mxtk.dsp @@ -0,0 +1,353 @@ +# Microsoft Developer Studio Project File - Name="mxtk" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=mxtk - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "mxtk.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "mxtk.mak" CFG="mxtk - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mxtk - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "mxtk - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "mxtk - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\..\temp\mxtk\!release" +# PROP Intermediate_Dir "..\..\temp\mxtk\!release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MD /W3 /GX /O2 /I ".\\" /I "..\..\game_shared" /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD BASE RSC /l 0x807 +# ADD RSC /l 0x409 +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo /out:"..\common\mxtk.lib" + +!ELSEIF "$(CFG)" == "mxtk - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\..\temp\mxtk\!debug" +# PROP Intermediate_Dir "..\..\temp\mxtk\!debug" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /Z7 /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MDd /W3 /GX /ZI /Od /I ".\\" /I "..\..\game_shared" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /FAs /FR /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x807 +# ADD RSC /l 0x409 +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo /out:"..\common\mxtk.lib" + +!ENDIF + +# Begin Target + +# Name "mxtk - Win32 Release" +# Name "mxtk - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp" +# Begin Source File + +SOURCE=.\mx.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxBmp.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxButton.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxCheckBox.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxChoice.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxChooseColor.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxFileDialog.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxGlWindow.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxGroupBox.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxLabel.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxLineEdit.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxListBox.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxMenu.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxMenuBar.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxMessageBox.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxMultiLineEdit.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxpath.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxPcx.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxPopupMenu.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxProgressBar.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxRadioButton.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxSlider.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxstring.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxTab.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxTga.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxToggleButton.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxToolTip.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxTreeView.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxWidget.cpp +# End Source File +# Begin Source File + +SOURCE=.\mxWindow.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=.\gl.h +# End Source File +# Begin Source File + +SOURCE=.\mx.h +# End Source File +# Begin Source File + +SOURCE=.\mxBmp.h +# End Source File +# Begin Source File + +SOURCE=.\mxButton.h +# End Source File +# Begin Source File + +SOURCE=.\mxCheckBox.h +# End Source File +# Begin Source File + +SOURCE=.\mxChoice.h +# End Source File +# Begin Source File + +SOURCE=.\mxChooseColor.h +# End Source File +# Begin Source File + +SOURCE=.\mxEvent.h +# End Source File +# Begin Source File + +SOURCE=.\mxFileDialog.h +# End Source File +# Begin Source File + +SOURCE=.\mxGlWindow.h +# End Source File +# Begin Source File + +SOURCE=.\mxGroupBox.h +# End Source File +# Begin Source File + +SOURCE=.\mxImage.h +# End Source File +# Begin Source File + +SOURCE=.\mxInit.h +# End Source File +# Begin Source File + +SOURCE=.\mxLabel.h +# End Source File +# Begin Source File + +SOURCE=.\mxLineEdit.h +# End Source File +# Begin Source File + +SOURCE=.\mxLinkedList.h +# End Source File +# Begin Source File + +SOURCE=.\mxListBox.h +# End Source File +# Begin Source File + +SOURCE=.\mxMenu.h +# End Source File +# Begin Source File + +SOURCE=.\mxMenuBar.h +# End Source File +# Begin Source File + +SOURCE=.\mxMessageBox.h +# End Source File +# Begin Source File + +SOURCE=.\mxMultiLineEdit.h +# End Source File +# Begin Source File + +SOURCE=.\mxpath.h +# End Source File +# Begin Source File + +SOURCE=.\mxPcx.h +# End Source File +# Begin Source File + +SOURCE=.\mxPopupMenu.h +# End Source File +# Begin Source File + +SOURCE=.\mxProgressBar.h +# End Source File +# Begin Source File + +SOURCE=.\mxRadioButton.h +# End Source File +# Begin Source File + +SOURCE=.\mxSlider.h +# End Source File +# Begin Source File + +SOURCE=.\mxstring.h +# End Source File +# Begin Source File + +SOURCE=.\mxTab.h +# End Source File +# Begin Source File + +SOURCE=.\mxTga.h +# End Source File +# Begin Source File + +SOURCE=.\mxToggleButton.h +# End Source File +# Begin Source File + +SOURCE=.\mxToolTip.h +# End Source File +# Begin Source File + +SOURCE=.\mxTreeView.h +# End Source File +# Begin Source File + +SOURCE=.\mxWidget.h +# End Source File +# Begin Source File + +SOURCE=.\mxWindow.h +# End Source File +# End Group +# End Target +# End Project diff --git a/utils/p2bsp/bsp5.h b/utils/p2bsp/bsp5.h new file mode 100644 index 0000000..9d37ac7 --- /dev/null +++ b/utils/p2bsp/bsp5.h @@ -0,0 +1,316 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// bsp5.h + +#include "cmdlib.h" +#include "mathlib.h" +#include "bspfile.h" +#include "threads.h" +#include "polylib.h" +#include "stringlib.h" +#include "filesystem.h" + +#include + +#ifndef DOUBLEVEC_T +#error you must add -dDOUBLEVEC_T to the project! +#endif + +#define DEFAULT_SUBDIVIDE_SIZE ((MAX_SURFACE_EXTENT - 1) * TEXTURE_STEP) +#define DEFAULT_MAXNODE_SIZE -1 +#define DEFAULT_MERGE_LEVEL 1 +#define DEFAULT_NOFILL false +#define DEFAULT_NOTJUNC false +#define DEFAULT_NOCLIP false +#define DEFAULT_FORCEVIS true +#define BSPCHOP_EPSILON ( Q_min( g_prtepsilon * 4.0, ON_EPSILON )) + +// the exact bounding box of the brushes is expanded some for the headnode +// volume. is this still needed? +#define SIDESPACE 24 + +// !!! if this is changed, it must be changed in csg.h too !!! +typedef struct +{ + vec3_t normal; + vec3_t origin; // unused + vec_t dist; + int type; + int outplanenum; // mapped planenum (in CSG it was called a hash_chain) +} plane_t; + +//============================================================================ +typedef enum +{ + face_normal = 0, + face_hint, + face_discardable, // contents must not differ between front and back +} facestyle_e; + +typedef struct face_s +{ + struct face_s *next; + + int planenum; + int texturenum; + int contents; // contents in front of face + + struct face_s *original; // face on node + int outputnumber; // only valid for original faces after + int referenced; // only valid for original faces + facestyle_e facestyle; + int bite; + + int detaillevel; // defined by hlcsg + int *outputedges; // used in WriteDrawNodes + int numedges; // outcount + // write surfaces + winding_t *w; +} face_t; + + +typedef struct surface_s +{ + struct surface_s *next; + int planenum; + vec3_t mins, maxs; + struct node_s *onnode; // true if surface has already been used + // as a splitting node + int detaillevel; // minimum detail level of its faces + face_t *faces; // links to all the faces on either side of the surf +} surface_t; + +// detail brushes stuff +typedef struct side_s +{ + struct side_s *next; + plane_t plane; // facing inside (reversed when loading brush file) + winding_t *w; // (also reversed) +} side_t; + +typedef struct brush_s +{ + struct brush_s *next; + side_t *sides; +} brush_t; + +// +// there is a node_t structure for every node and leaf in the bsp tree +// +#define PLANENUM_LEAF -1 +#define BOUNDS_EXPANSION 1.0 + +#define FNODE_DETAIL (1<<0) // is under a diskleaf +#define FNODE_LEAFPORTAL (1<<1) // not detail and children are detail; only visleafs have contents, portals, mins, maxs +#define FNODE_DETAILCONTENTS (1<<2) // inside a detail brush +#define FNODE_EMPTY (1<<3) // not solid or sky + +typedef struct node_s +{ + surface_t *surfaces; + brush_t *detailbrushes; + + brush_t *boundsbrush; + vec3_t loosemins; // all leafs and nodes have this, while 'mins' and 'maxs' + vec3_t loosemaxs; // are only valid for nondetail leafs and nodes. + + int flags; + vec3_t mins, maxs; // bounding volume of portals; + + // information for decision nodes + int planenum; // -1 = leaf node + int firstface; // decision node only + int numfaces; // decision node only + struct node_s *children[2]; // only valid for decision nodes + face_t *faces; // decision nodes only, list for both sides + + // information for leafs + int contents; // leaf nodes (0 for decision nodes) + face_t **markfaces; // leaf nodes only, point to node faces + struct portal_s *portals; + int visleafnum; // -1 = solid + int valid; // for flood filling + int occupied; // light number in leaf for outside filling + + bool opaque() const { return contents == CONTENTS_SOLID || contents == CONTENTS_SKY; } +} node_t; + +typedef struct portal_s +{ + int planenum; + node_t *onnode; // NULL = outside box + node_t *nodes[2]; // [0] = front side of plane + struct portal_s *next[2]; + winding_t *winding; +} portal_t; + +typedef struct +{ + vec3_t mins, maxs; // bounding box + face_t *validfaces[MAX_INTERNAL_MAP_PLANES]; // build surfaces is also used by GatherNodeFaces + brush_t *detailbrushes; + surface_t *surfaces; + node_t *headnode; + int numsurfaces; // surface_t[count] + int numfaces; // debug +} tree_t; + +face_t *NewFaceFromFace (face_t *in); +void SplitFace( face_t *in, plane_t *split, face_t **front, face_t **back, bool keepsource = false ); +void SplitFaceEpsilon( face_t *in, plane_t *split, face_t **front, face_t **back, vec_t epsilon, bool keepsource = false ); + +//============================================================================= + +// solidbsp.c + +void DivideFacet( face_t *in, plane_t *split, face_t **front, face_t **back ); +void CalcSurfaceInfo( surface_t *surf ); +void SubdivideFace( face_t *f, face_t **prevptr ); +void SolidBSP( tree_t *tree, int modnum, int hullnum ); +vec_t SplitPlaneMetric( const plane_t *p, const vec3_t mins, const vec3_t maxs ); +void MakeNodePortal( node_t *node ); +void SplitNodePortals( node_t *node ); +void FreeLeafSurfs( node_t *leaf ); + +//============================================================================= + +// merge.c + +void MergePlaneFaces( surface_t *plane, int mergelevel ); +face_t *FreeMergeListScraps (face_t *merged); +void MergeTreeFaces( tree_t *tree ); + +//============================================================================= + +// surfaces.c + +extern int firstmodeledge; + +void SubdivideFaces (surface_t *surfhead); +int GetEdge (vec3_t p1, vec3_t p2, face_t *f); +void GatherTreeFaces( tree_t *tree ); +void MakeFaceEdges( void ); +void InitHash( void ); + +//============================================================================= + +// portals.c + +void AddPortalToNodes( portal_t *p, node_t *front, node_t *back ); +void RemovePortalFromNode( portal_t *portal, node_t *l ); +void MakeHeadnodePortals( node_t *node, const vec3_t mins, const vec3_t maxs ); +void FreeTreePortals( tree_t *tree ); +void WritePortalfile( tree_t *tree, bool leaked ); +void FillInside( node_t *node ); + +//============================================================================= + +// detail.c + +brush_t *ReadBrushes( FILE *file ); + +//============================================================================= + +// partition.c + +surface_t *SelectPartition( surface_t *surfaces, node_t *node, bool midsplit, int splitdetaillevel, vec3_t validmins, vec3_t validmaxs ); + +// tree.c + +tree_t *AllocTree( void ); +void FreeTree( tree_t *t ); +tree_t *MakeTreeFromHullFaces( FILE *polyfile, FILE *brushfile ); +tree_t *TreeProcessModel( tree_t *tree, int modnum, int hullnum ); +void MakeSurflistFromValidFaces( tree_t *tree ); +bool CheckFaceForHint( const face_t *f ); +bool CheckFaceForDiscardable( const face_t *f ); + +//============================================================================= + +// tjunc.c + +void tjunc( node_t *headnode, bool worldmodel ); + +//============================================================================= + +// writebsp.c + +void WriteNodePlanes (node_t *headnode); +void WriteClipNodes (node_t *headnode); +void WriteDrawNodes (node_t *headnode); + +void EmitDrawNodes( tree_t *tree ); +void EmitClipNodes( tree_t *tree, int modnum, int hullnum ); +void EmitNodeFaces( node_t *headnode ); +int EmitVertex( const vec3_t point ); +void BeginBSPFile( void ); +void FinishBSPFile( const char *source ); +int FindFloatPlane( const vec3_t normal, vec_t dist ); + +//============================================================================= + +// outside.c + +bool FillOutside( tree_t *tree, int hullnum, bool leakfile ); + +//============================================================================= + +extern bool g_nofill; +extern bool g_notjunc; +extern bool g_noclip; +extern bool g_forcevis; + +extern int g_maxnode_size; +extern int g_merge_level; +extern vec_t g_prtepsilon; + +extern int valid; +extern int c_splitnodes; +extern int c_unsplitted_faces; + +extern char g_portfilename[1024]; +extern char g_pointfilename[1024]; +extern char g_linefilename[1024]; + +extern plane_t g_mapplanes[MAX_INTERNAL_MAP_PLANES]; +extern int g_nummapplanes; +extern node_t g_outside_node; + +void AddFaceToBounds( face_t *f, vec3_t mins, vec3_t maxs ); + +// misc functions + +face_t *AllocFace( void ); +void FreeFace( face_t *f ); +void UnlinkFace( face_t **head, face_t *face ); +int CountListFaces( face_t *list ); + +struct portal_s *AllocPortal( void ); +void FreePortal( struct portal_s *p ); + +surface_t *AllocSurface( void ); +void FreeSurface( surface_t *s ); + +side_t *AllocSide( void ); +void FreeSide( side_t *s ); +side_t *NewSideFromSide( const side_t *s ); +brush_t *AllocBrush( void ); +void FreeBrush( brush_t *b ); +brush_t *NewBrushFromBrush( const brush_t *b ); +void SplitBrush( brush_t *in, const plane_t *split, brush_t **front, brush_t **back ); +brush_t *BrushFromBox( const vec3_t mins, const vec3_t maxs ); +void CalcBrushBounds( const brush_t *b, vec3_t &mins, vec3_t &maxs ); + +node_t *AllocNode( void ); +void FreeNode( node_t *n ); +void FreeLeaf( node_t *n ); + +//============================================================================= diff --git a/utils/p2bsp/detail.cpp b/utils/p2bsp/detail.cpp new file mode 100644 index 0000000..e268f36 --- /dev/null +++ b/utils/p2bsp/detail.cpp @@ -0,0 +1,280 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// detail.c + +#include "bsp5.h" + +side_t *AllocSide( void ) +{ + return (side_t *)Mem_Alloc( sizeof( side_t ), C_BRUSHSIDE ); +} + +void FreeSide( side_t *s ) +{ + if( s->w ) FreeWinding( s->w ); + Mem_Free( s, C_BRUSHSIDE ); +} + +side_t *NewSideFromSide( const side_t *s ) +{ + side_t *news; + + news = AllocSide (); + news->plane = s->plane; + news->w = CopyWinding( s->w ); + + return news; +} + +brush_t *AllocBrush( void ) +{ + return (brush_t *)Mem_Alloc( sizeof( brush_t ), C_BSPBRUSH ); +} + +void FreeBrush( brush_t *b ) +{ + if( b->sides ) + { + for( side_t *s = b->sides, *next = NULL; s != NULL; s = next ) + { + next = s->next; + FreeSide( s ); + } + } + + Mem_Free( b, C_BSPBRUSH ); +} + +brush_t *NewBrushFromBrush( const brush_t *b ) +{ + brush_t *newb = AllocBrush (); + + for( side_t *s = b->sides, **pnews = &newb->sides; s != NULL; s = s->next, pnews = &(*pnews)->next ) + *pnews = NewSideFromSide( s ); + + return newb; +} + +void ClipBrush( brush_t **b, const plane_t *split, vec_t epsilon ) +{ + side_t *s, **pnext; + winding_t *w; + + for( pnext = &(*b)->sides, s = *pnext; s; s = *pnext ) + { + if( ChopWindingInPlace( &s->w, split->normal, split->dist, epsilon, false )) + { + pnext = &s->next; + } + else + { + *pnext = s->next; + FreeSide( s ); + } + } + + if( !(*b)->sides ) + { + // empty brush + FreeBrush( *b ); + *b = NULL; + return; + } + + w = BaseWindingForPlane( split->normal, split->dist ); + + for( s = (*b)->sides; s; s = s->next ) + { + if( !ChopWindingInPlace( &w, s->plane.normal, s->plane.dist, epsilon, false )) + { + break; + } + } + + if( w->numpoints == 0 ) + { + FreeWinding( w ); + } + else + { + s = AllocSide(); + s->plane = *split; + s->w = CopyWinding( w ); + s->next = (*b)->sides; + (*b)->sides = s; + } +} + +void SplitBrush( brush_t *in, const plane_t *split, brush_t **front, brush_t **back ) +{ + bool onfront = false; + bool onback = false; + side_t *s; + + in->next = NULL; + + for( s = in->sides; s != NULL; s = s->next ) + { + switch( WindingOnPlaneSide( s->w, split->normal, split->dist, ON_EPSILON * 2 )) + { + case SIDE_CROSS: + onfront = true; + onback = true; + break; + case SIDE_FRONT: + onfront = true; + break; + case SIDE_BACK: + onback = true; + break; + case SIDE_ON: + break; + } + + if( onfront && onback ) + break; + } + + if( !onfront && !onback ) + { + FreeBrush( in ); + *front = NULL; + *back = NULL; + return; + } + + if( !onfront ) + { + *front = NULL; + *back = in; + return; + } + + if( !onback ) + { + *front = in; + *back = NULL; + return; + } + + *front = in; + *back = NewBrushFromBrush( in ); + + plane_t frontclip = *split; + plane_t backclip = *split; + + VectorNegate( backclip.normal, backclip.normal ); + backclip.dist = -backclip.dist; + + ClipBrush( front, &frontclip, NORMAL_EPSILON ); + ClipBrush( back, &backclip, NORMAL_EPSILON ); +} + +brush_t *BrushFromBox( const vec3_t mins, const vec3_t maxs ) +{ + brush_t *b = AllocBrush (); + plane_t planes[6]; + int k; + + for( k = 0; k < 3; k++ ) + { + VectorClear( planes[k].normal ); + planes[k].normal[k] = 1.0; + planes[k].dist = mins[k]; + VectorClear( planes[k+3].normal ); + planes[k+3].normal[k] = -1.0; + planes[k+3].dist = -maxs[k]; + } + + b->sides = AllocSide(); + b->sides->plane = planes[0]; + b->sides->w = BaseWindingForPlane( planes[0].normal, planes[0].dist ); + + for( k = 1; k < 6; k++ ) + { + ClipBrush( &b, &planes[k], NORMAL_EPSILON ); + if( b == NULL ) break; + } + + return b; +} + +void CalcBrushBounds( const brush_t *b, vec3_t &mins, vec3_t &maxs ) +{ + vec3_t windingmins, windingmaxs; + + ClearBounds( mins, maxs ); + + for( side_t *s = b->sides; s; s = s->next ) + { + WindingBounds( s->w, windingmins, windingmaxs ); + AddPointToBounds( windingmins, mins, maxs ); + AddPointToBounds( windingmaxs, mins, maxs ); + } +} + +brush_t *ReadBrushes( FILE *file ) +{ + brush_t *brushes = NULL; + int planenum, numpoints; + int r, brushinfo; + + while( 1 ) + { + r = fscanf( file, "%i\n", &brushinfo ); + + if( r == 0 || r == -1 ) + { + if( brushes == NULL ) + COM_FatalError( "ReadBrushes: no more models\n" ); + else COM_FatalError( "ReadBrushes: file end\n" ); + } + + if( brushinfo == -1 ) + break; // end of detail brushes list + + brush_t *b; + b = AllocBrush (); + b->next = brushes; + brushes = b; + side_t **psn; + psn = &b->sides; + + while( 1 ) + { + + r = fscanf( file, "%i %u\n", &planenum, &numpoints ); + if( r != 2 ) COM_FatalError( "ReadBrushes: get side failed\n" ); + + if( planenum == -1 ) + break; // end of brushes description + + side_t *s = AllocSide(); + s->plane = g_mapplanes[planenum ^ 1]; + s->w = AllocWinding( numpoints ); + s->w->numpoints = numpoints; + + for( int x = 0; x < numpoints; x++ ) + { + double v[3]; + r = fscanf( file, "%lf %lf %lf\n", &v[0], &v[1], &v[2] ); + if( r != 3 ) COM_FatalError( "ReadBrushes: get point failed\n" ); + VectorCopy( v, s->w->p[numpoints - 1 - x] ); + } + + s->next = NULL; + *psn = s; + psn = &s->next; + } + } + + return brushes; +} \ No newline at end of file diff --git a/utils/p2bsp/merge.cpp b/utils/p2bsp/merge.cpp new file mode 100644 index 0000000..ddede53 --- /dev/null +++ b/utils/p2bsp/merge.cpp @@ -0,0 +1,353 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// merge.c + +#include "bsp5.h" + +/* +================ +CheckColinear + +================ +*/ +void CheckColinear( winding_t *w ) +{ + vec3_t v1, v2; + int i, j; + + for( i = 0; i < w->numpoints; i++ ) + { + // skip the point if the vector from the previous point is the same + // as the vector to the next point + j = (i - 1 < 0) ? w->numpoints - 1 : i - 1; + VectorSubtract( w->p[i], w->p[j], v1 ); + VectorNormalize( v1 ); + + j = (i + 1 == w->numpoints) ? 0 : i + 1; + VectorSubtract( w->p[j], w->p[i], v2 ); + VectorNormalize( v2 ); + + if( VectorCompareEpsilon( v1, v2, EQUAL_EPSILON )) + COM_FatalError( "colinear edge\n" ); + } +} + +/* +============= +FacesCanMerge + +check if two faces can't be merged into one +for some reasons +============= +*/ +bool FacesCanMerge( face_t *f1, face_t *f2 ) +{ + // one of faces is not valid + if( !f1->w || !f2->w ) + return false; + + // different texinfo + if( f1->texturenum != f2->texturenum ) + return false; + + // different contents (in clipping hulls) + if( f1->contents != f2->contents ) + return false; + + // different plane + if( f1->planenum != f2->planenum ) + return false; + + // unmatched detail level + if( f1->detaillevel != f2->detaillevel ) + return false; + + // different facestyle (g-cont. how this possible?) + if( f1->facestyle != f2->facestyle ) + return false; + + return true; +} + +/* +============= +TryMerge + +If two polygons share a common edge and the edges that meet at the +common points are both inside the other polygons, merge them + +Returns NULL if the faces couldn't be merged, or the new face. +The originals will NOT be freed. +============= +*/ +face_t *TryMerge( face_t *f1, face_t *f2 ) +{ + face_t *newf; + winding_t *mergew; + plane_t *plane; + + if( !FacesCanMerge( f1, f2 )) + return NULL; + + plane = &g_mapplanes[f1->planenum]; + mergew = TryMergeWindingEpsilon( f1->w, f2->w, plane->normal, ON_EPSILON ); + + if( !mergew ) return NULL; // faces can't be merged + + newf = NewFaceFromFace( f1 ); + newf->w = mergew; + + return newf; +} + +/* +============= +TryBite + +============= +*/ +bool TryBite( face_t *f1, face_t *f2, int maxbites ) +{ + plane_t *plane; + + // perform general checks + if( f2->bite >= maxbites ) + return false; + + if( !FacesCanMerge( f1, f2 )) + return false; + + plane = &g_mapplanes[f1->planenum]; + + if( !BiteWindingEpsilon( &f1->w, &f2->w, plane->normal, ON_EPSILON )) + return false; + + f2->bite++; + + return true; +} + +/* +=============== +MergeFaceToList +=============== +*/ +face_t *MergeFaceToList( face_t *face, face_t *list ) +{ + face_t *newf, *f; + + for( f = list; f != NULL; f = f->next ) + { + // CheckColinear( f->w ); + newf = TryMerge( face, f ); + if( !newf ) continue; + + FreeFace( face ); + FreeWinding( f->w ); + f->w = NULL; // merged out + + return MergeFaceToList( newf, list ); + } + + // didn't merge, so add at start + face->next = list; + return face; +} + +face_t *SplitAndMergeFaceToList( face_t **list, int maxbites ) +{ + face_t *next, *next2; + face_t *merge = NULL; + face_t *f1, *f2; + + for( f1 = *list; f1 != NULL; f1 = next ) + { + bool bite = false; + + // try to bite faces in merged list + for( f2 = merge; f2 != NULL; f2 = next2 ) + { + next2 = f2->next; + + if( !TryBite( f1, f2, maxbites )) + continue; + + UnlinkFace( &merge, f2 ); + + merge = MergeFaceToList( f2, merge ); + bite = true; + break; + } + + if( !bite ) + { + // try to bite faces in face list + for( f2 = *list; f2 != NULL; f2 = next2 ) + { + next2 = f2->next; + if( f1 == f2 ) continue; + + if( !TryBite( f1, f2, maxbites )) + continue; + + UnlinkFace( list, f2 ); + + merge = MergeFaceToList( f2, merge ); + bite = true; + break; + } + } + + next = f1->next; + + if( bite ) + { + UnlinkFace( list, f1 ); + merge = MergeFaceToList( f1, merge ); + } + } + + // add all remaining faces + for( f1 = *list; f1 != NULL; f1 = next ) + { + next = f1->next; + merge = MergeFaceToList( f1, merge ); + } + + return merge; +} + +/* +=============== +FreeMergeListScraps +=============== +*/ +face_t *FreeMergeListScraps( face_t *merged ) +{ + face_t *head, *next; + + for( head = NULL; merged != NULL; merged = next ) + { + next = merged->next; + + if( merged->w == NULL ) + { + FreeFace( merged ); + } + else + { + merged->next = head; + head = merged; + } + } + + return head; +} + +/* +=============== +MergePlaneFaces +=============== +*/ +void MergePlaneFaces( surface_t *plane, int mergelevel ) +{ + face_t *f1, *f2, *next; + face_t *merged, *backup; + + if( mergelevel <= 0 ) + return; + + merged = backup = NULL; + + for( f1 = plane->faces; f1 != NULL; f1 = next ) + { + next = f1->next; + merged = MergeFaceToList( f1, merged ); + } + + if( mergelevel > 1 ) + { + // chain all of the non-empty faces to the plane + plane->faces = FreeMergeListScraps( merged ); + + // try split-n-merge approach + // make copy of all faces + for( f1 = plane->faces; f1 != NULL; f1 = f1->next ) + { + f2 = NewFaceFromFace( f1 ); + f2->w = CopyWinding( f1->w ); + f2->original = f1; + + f2->next = backup; + backup = f2; + } + + merged = SplitAndMergeFaceToList( &plane->faces, mergelevel * 2 ); + + // sanity check + if( CountListFaces( merged ) >= CountListFaces( backup )) + { + // free mergelist + for( f1 = merged; f1 != NULL; f1 = next ) + { + next = f1->next; + FreeFace( f1 ); + } + + // assign original chain + plane->faces = FreeMergeListScraps( backup ); + } + else + { + // free originallist + for( f1 = backup; f1 != NULL; f1 = next ) + { + next = f1->next; + FreeFace( f1 ); + } + + // assign merged chain + plane->faces = FreeMergeListScraps( merged ); + } + } + else + { + // chain all of the non-empty faces to the plane + plane->faces = FreeMergeListScraps( merged ); + } +} + +/* +============ +MergeTreeFaces +============ +*/ +void MergeTreeFaces( tree_t *tree ) +{ + surface_t *surf; + int mergefaces; + int origfaces; + int reduced; + + MsgDev( D_REPORT, "---- MergeTreeFaces ----\n" ); + + mergefaces = origfaces = 0; + + for( surf = tree->surfaces; surf; surf = surf->next ) + { + origfaces += CountListFaces( surf->faces ); + MergePlaneFaces( surf, 1 ); + mergefaces += CountListFaces( surf->faces ); + } + + reduced = origfaces - mergefaces; + if( reduced ) MsgDev( D_REPORT, "%i faces reduced\n", reduced ); +} \ No newline at end of file diff --git a/utils/p2bsp/outside.cpp b/utils/p2bsp/outside.cpp new file mode 100644 index 0000000..628ff04 --- /dev/null +++ b/utils/p2bsp/outside.cpp @@ -0,0 +1,532 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include "bsp5.h" + +int outleafs; +int valid; +int c_falsenodes; +int c_free_faces; +int c_keep_faces; +portal_t *prevleaknode; +FILE *pointfile = NULL; +FILE *linefile = NULL; +int hit_occupied; +int backdraw; + +/* +=========== +PointInLeaf +=========== +*/ +node_t *PointInLeaf( node_t *node, const vec3_t point ) +{ + while( !FBitSet( node->flags, FNODE_LEAFPORTAL )) + node = node->children[PlaneDiff( point, &g_mapplanes[node->planenum] ) <= 0]; + return node; +} + +/* +=========== +PlaceOccupant +=========== +*/ +bool PlaceOccupant( int num, vec3_t point, node_t *headnode ) +{ + node_t *n; + + n = PointInLeaf( headnode, point ); + if( n->contents == CONTENTS_SOLID ) + return false; + n->occupied = num; + + return true; +} + +/* +============= +Portal_Passable + +Returns true if the portal has non-opaque leafs on both sides + +from q3map +============= +*/ +static bool Portal_Passable( const portal_t *p ) +{ + if( p->nodes[0] == &g_outside_node || p->nodes[1] == &g_outside_node ) + return false; + +#if 0 // this break monster navigation + if( p->nodes[0]->opaque() || p->nodes[1]->opaque( )) + return false; +#endif + return true; +} + +/* +============== +MarkLeakTrail +============== +*/ +void MarkLeakTrail( portal_t *n2 ) +{ + vec3_t p1, p2, dir; + vec_t len; + portal_t *n1; + + n1 = prevleaknode; + prevleaknode = n2; + if( !n1 ) return; + + WindingCenter( n1->winding, p1 ); + WindingCenter( n2->winding, p2 ); + + // NOTE: create only if there was a leak. + if( !pointfile ) pointfile = fopen( g_pointfilename, "w" ); + if( !pointfile ) COM_FatalError( "couldn't open %s\n", g_pointfilename ); + if( !linefile ) linefile = fopen( g_linefilename, "w" ); + if( !linefile ) COM_FatalError( "couldn't open %s\n", g_linefilename ); + + fprintf( linefile, "%f %f %f - %f %f %f\n", p1[0], p1[1], p1[2], p2[0], p2[1], p2[2] ); + fprintf( pointfile, "%f %f %f\n", p1[0], p1[1], p1[2] ); + + VectorSubtract( p2, p1, dir ); + len = VectorLength( dir ); + VectorNormalize( dir ); + + while( len > 2.0 ) + { + fprintf( pointfile,"%f %f %f\n", p1[0], p1[1], p1[2] ); + VectorMA( p1, 2.0, dir, p1 ); + len -= 2.0; + } + +} + +/* +============== +FreeDetailNode_r +============== +*/ +static void FreeDetailNode_r( node_t *node ) +{ + face_t *f, *next; + + if( node->planenum == PLANENUM_LEAF ) + { + if(!( FBitSet( node->flags, FNODE_LEAFPORTAL ) && node->contents == CONTENTS_SOLID )) + { + Mem_Free( node->markfaces ); + node->markfaces = NULL; + } + return; + } + + for( int i = 0; i < 2; i++ ) + { + FreeDetailNode_r( node->children[i] ); + FreeNode( node->children[i] ); + node->children[i] = NULL; + } + + for( f = node->faces; f != NULL; f = next ) + { + next = f->next; + FreeFace( f ); + } + + node->faces = NULL; +} + +/* +============== +FillLeaf +============== +*/ +static void FillLeaf( node_t *l ) +{ + if( !FBitSet( l->flags, FNODE_LEAFPORTAL )) + { + MsgDev( D_WARN, "FillLeaf: not leaf\n" ); + return; + } + + if( l->contents == CONTENTS_SOLID ) + { + MsgDev( D_WARN, "FillLeaf: fill solid\n" ); + return; + } + + FreeDetailNode_r( l ); + + l->contents = CONTENTS_SOLID; + l->planenum = PLANENUM_LEAF; +} + +/* +================== +RecursiveFillOutside + +If fill is false, just check, don't fill +Returns true if an occupied leaf is reached +================== +*/ +bool RecursiveFillOutside( node_t *l, bool fill, bool leakfile ) +{ + portal_t *p; + int s; + + if( l->opaque() ) + return false; + + if( l->valid == valid ) + return false; + + if( l->occupied ) + { + hit_occupied = l->occupied; + backdraw = 1000; + return true; + } + + l->valid = valid; + + // fill it and it's neighbors + if( fill ) FillLeaf( l ); + + outleafs++; + + for( p = l->portals; p != NULL; ) + { + s = (p->nodes[0] == l); + + if( Portal_Passable( p ) && RecursiveFillOutside( p->nodes[s], fill, leakfile )) + { + // leaked, so stop filling + if( backdraw-- > 0 && leakfile ) + MarkLeakTrail( p ); + return true; + } + p = p->next[!s]; + } + + return false; +} + +static void MarkFacesInside_r( node_t *node ) +{ + if( node->planenum == PLANENUM_LEAF ) + { + for( face_t **fp = node->markfaces; *fp != NULL; fp++ ) + (*fp)->outputnumber = 0; + } + else + { + MarkFacesInside_r( node->children[0] ); + MarkFacesInside_r( node->children[1] ); + } +} + +/* +================== +ClearOutFaces_r + +Removes unused nodes +================== +*/ +node_t *ClearOutFaces_r( node_t *node ) +{ + face_t *f, *fnext; + portal_t *p; + + // mark the node and all it's faces, so they + // can be removed if no children use them + node->valid = 0; // will be set if any children touch it + + for( f = node->faces; f != NULL; f = f->next ) + f->outputnumber = -1; + + // go down the children + if( !FBitSet( node->flags, FNODE_LEAFPORTAL )) + { + // decision node + node->children[0] = ClearOutFaces_r( node->children[0] ); + node->children[1] = ClearOutFaces_r( node->children[1] ); + + // free any faces not in open child leafs + f = node->faces; + node->faces = NULL; + + for( ; f != NULL; f = fnext ) + { + fnext = f->next; + + if( f->outputnumber == -1 ) + { + // never referenced, so free it + c_free_faces++; + FreeFace( f ); + } + else + { + c_keep_faces++; + f->next = node->faces; + node->faces = f; + } + } + + // TODO: free memory + if( !node->valid ) + { + // this node does not touch any interior leafs + // if both children are solid, just make this node solid + if( node->children[0]->contents == CONTENTS_SOLID && node->children[1]->contents == CONTENTS_SOLID ) + { + SetBits( node->flags, FNODE_LEAFPORTAL ); + node->contents = CONTENTS_SOLID; + node->planenum = PLANENUM_LEAF; + c_splitnodes--; + return node; + } + + // if one child is solid, shortcut down the other side + if( node->children[0]->contents == CONTENTS_SOLID ) + { + c_splitnodes--; + return node->children[1]; + } + + if( node->children[1]->contents == CONTENTS_SOLID ) + { + c_splitnodes--; + return node->children[0]; + } + c_falsenodes++; + } + return node; + } + + // leaf node + if( node->contents != CONTENTS_SOLID ) + { + // this node is still inside + + // mark all the nodes used as portals + for( p = node->portals; p != NULL; ) + { + if( p->onnode ) + p->onnode->valid = 1; + + // only write out from first leaf + if( p->nodes[0] == node ) + p = p->next[0]; + else p = p->next[1]; + } + + MarkFacesInside_r( node ); + + return node; + } + + return node; +} + +/* +=========== +FillOutside + +=========== +*/ +bool FillOutside( tree_t *tree, int hullnum, bool leakfile ) +{ + bool inside = false; + node_t *node = tree->headnode; + bool leaked = false; + int s, i, c_nodes; + vec3_t origin; + + MsgDev( D_REPORT, "----- FillOutside ----\n" ); + c_nodes = c_splitnodes; + + // place markers for all entities so + // we know if we leak inside + for( i = 1; i < g_numentities; i++ ) + { + entity_t *mapent = &g_entities[i]; + + GetVectorForKey( mapent, "origin", origin ); + + // make sure what entity doesn't have brush-model + if( !VectorIsNull( origin ) && ValueForKey( mapent, "model" )[0] != '*' ) + { + origin[2] += 1.0; + + if( PlaceOccupant( i, origin, node )) + inside = true; + } + } + + if( !inside ) + { + Msg( "%i nodes (not optimized)\n", c_nodes ); + MsgDev( D_ERROR, "No entities in empty space -- no filling performed\n" ); + return false; + } + + s = !(g_outside_node.portals->nodes[1] == &g_outside_node); + // first check to see if an occupied leaf is hit + prevleaknode = NULL; + outleafs = 0; + valid++; + + if( RecursiveFillOutside( g_outside_node.portals->nodes[s], false, leakfile )) + { + if( pointfile ) fclose( pointfile ); + if( linefile ) fclose( linefile ); + pointfile = linefile = NULL; + leaked = true; + + // do animation + MsgAnim( D_INFO, "^3=== LEAK in hull %i ===\r", hullnum ); + GetVectorForKey( &g_entities[hit_occupied], "origin", origin ); + MsgDev( D_REPORT, "\nEntity %s @ (%4.0f,%4.0f,%4.0f)\n", origin[0], origin[1], origin[2] ); + MsgDev( D_REPORT, "no filling performed\n" ); + MsgDev( D_REPORT, "point file and line file generated\n" ); + + // allow to vis maps even with leak + if( g_forcevis == false ) + { + Msg( "%i nodes (not optimized)\n", c_nodes ); + return false; + } + } + + // now go back and fill things in + valid++; + RecursiveFillOutside( g_outside_node.portals->nodes[s], true, false ); + + // remove faces and nodes from filled in leafs + c_falsenodes = 0; + c_free_faces = 0; + c_keep_faces = 0; + tree->headnode = ClearOutFaces_r( node ); + + MsgDev( D_REPORT, "%5i outleafs\n", outleafs ); + MsgDev( D_REPORT, "%5i freed faces\n", c_free_faces ); + MsgDev( D_REPORT, "%5i keep faces\n", c_keep_faces ); + MsgDev( D_REPORT, "%5i falsenodes\n", c_falsenodes ); + Msg( "%i nodes (%i after merging)\n", c_nodes, c_splitnodes ); + + // save portal file for vis tracing + if( leakfile ) WritePortalfile( tree, leaked ); + + return true; +} + +//============================================================================= + +/* +=========== +ResetMark_r +=========== +*/ +void ResetMark_r( node_t *node ) +{ + if( FBitSet( node->flags, FNODE_LEAFPORTAL )) + { + if( node->opaque( )) + ClearBits( node->flags, FNODE_EMPTY ); + else SetBits( node->flags, FNODE_EMPTY ); + } + else + { + ResetMark_r( node->children[0] ); + ResetMark_r( node->children[1] ); + } +} + +/* +=========== +MarkOccupied_r +=========== +*/ +void MarkOccupied_r( node_t *node ) +{ + portal_t *p; + int s; + + if( FBitSet( node->flags, FNODE_EMPTY )) + { + ClearBits( node->flags, FNODE_EMPTY ); + + for( p = node->portals; p != NULL; p = p->next[!s] ) + { + s = (p->nodes[0] == node); + MarkOccupied_r( p->nodes[s] ); + } + } +} + +/* +=========== +RemoveUnused_r +=========== +*/ +void RemoveUnused_r( node_t *node ) +{ + if( FBitSet( node->flags, FNODE_LEAFPORTAL )) + { + if( FBitSet( node->flags, FNODE_EMPTY )) + { + FillLeaf( node ); + } + } + else + { + RemoveUnused_r( node->children[0] ); + RemoveUnused_r( node->children[1] ); + } +} + +/* +=========== +FillInside + +=========== +*/ +void FillInside( node_t *node ) +{ + vec3_t origin; + node_t *innode; + + ClearBits( g_outside_node.flags, FNODE_EMPTY ); + ResetMark_r( node ); + + for( int i = 1; i < g_numentities; i++ ) + { + entity_t *mapent = &g_entities[i]; + + if( CheckKey( mapent, "origin" )) + { + GetVectorForKey( mapent, "origin", origin ); + + origin[2] += 1.0; + innode = PointInLeaf( node, origin ); + MarkOccupied_r( innode ); + + origin[2] -= 2.0; + innode = PointInLeaf( node, origin ); + MarkOccupied_r( innode ); + } + } + + RemoveUnused_r( node ); +} \ No newline at end of file diff --git a/utils/p2bsp/p2bsp.dsp b/utils/p2bsp/p2bsp.dsp new file mode 100644 index 0000000..65fb11f --- /dev/null +++ b/utils/p2bsp/p2bsp.dsp @@ -0,0 +1,226 @@ +# Microsoft Developer Studio Project File - Name="p2bsp" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=p2bsp - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "p2bsp.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "p2bsp.mak" CFG="p2bsp - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "p2bsp - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "p2bsp - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/SDKSrc/Tools/utils/p2bsp", VUGBAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "p2bsp - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir ".\p2bsp___" +# PROP BASE Intermediate_Dir ".\p2bsp___" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\..\temp\p2bsp\!release" +# PROP Intermediate_Dir "..\..\temp\p2bsp\!release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "..\common" /I "..\..\common" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "DOUBLEVEC_T" /D "IGNORE_SEARCH_IN_WADS" /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /opt:nowin98 +# ADD LINK32 /nologo /subsystem:console /pdb:none /machine:I386 /opt:nowin98 +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\p2bsp\!release +InputPath=\Paranoia2\src_main\temp\p2bsp\!release\p2bsp.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\p2bsp.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\p2bsp.exe "D:\Paranoia2\tools\p2bsp.exe" + +# End Custom Build + +!ELSEIF "$(CFG)" == "p2bsp - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir ".\p2bsp__0" +# PROP BASE Intermediate_Dir ".\p2bsp__0" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\..\temp\p2bsp\!debug" +# PROP Intermediate_Dir "..\..\temp\p2bsp\!debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MTd /W3 /Gm /Gi /GX /ZI /Od /I "..\common" /I "..\..\common" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "DOUBLEVEC_T" /D "IGNORE_SEARCH_IN_WADS" /FAs /FR /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 +# ADD LINK32 msvcrtd.lib /nologo /subsystem:console /debug /machine:I386 /nodefaultlib:"libcmtd.lib" +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\p2bsp\!debug +InputPath=\Paranoia2\src_main\temp\p2bsp\!debug\p2bsp.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\p2bsp.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\p2bsp.exe "D:\Paranoia2\tools\p2bsp.exe" + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "p2bsp - Win32 Release" +# Name "p2bsp - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=.\bsp5.h +# End Source File +# Begin Source File + +SOURCE=..\common\bspfile.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\cmdlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\conprint.cpp +# End Source File +# Begin Source File + +SOURCE=.\detail.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\filesystem.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\mathlib.cpp +# End Source File +# Begin Source File + +SOURCE=.\merge.cpp +# End Source File +# Begin Source File + +SOURCE=.\outside.cpp +# End Source File +# Begin Source File + +SOURCE=.\partition.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\polylib.cpp +# End Source File +# Begin Source File + +SOURCE=.\portals.cpp +# End Source File +# Begin Source File + +SOURCE=.\qbsp.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\scriplib.cpp +# End Source File +# Begin Source File + +SOURCE=.\solidbsp.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\stringlib.cpp +# End Source File +# Begin Source File + +SOURCE=.\surfaces.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\threads.cpp +# End Source File +# Begin Source File + +SOURCE=.\tjunc.cpp +# End Source File +# Begin Source File + +SOURCE=.\tree.cpp +# End Source File +# Begin Source File + +SOURCE=.\writebsp.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\zone.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=..\common\bspfile.h +# End Source File +# Begin Source File + +SOURCE=..\common\cmdlib.h +# End Source File +# Begin Source File + +SOURCE=..\common\mathlib.h +# End Source File +# Begin Source File + +SOURCE=..\common\scriplib.h +# End Source File +# Begin Source File + +SOURCE=..\common\threads.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/utils/p2bsp/partition.cpp b/utils/p2bsp/partition.cpp new file mode 100644 index 0000000..b647f14 --- /dev/null +++ b/utils/p2bsp/partition.cpp @@ -0,0 +1,667 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include "bsp5.h" + +#define MarkFacesCount( mf ) ( (mf) ? mf->count : 0 ) + +typedef struct +{ + face_t **array; + int count; + int reserve; +} markfaces_t; + +typedef struct surfnode_s +{ + int size; // can be zero, which invalidates mins and maxs + int size_discardable; + vec3_t mins; + vec3_t maxs; + bool isleaf; + + // node + surfnode_s *children[2]; + markfaces_t *nodefaces; + int nodefaces_discardablesize; + + // leaf + markfaces_t *leaffaces; +} surfnode_t; + +typedef struct +{ + bool dontbuild; + vec_t epsilon; // if a face is not epsilon far from the splitting plane, put it in result.middle + surfnode_t *headnode; + + // result + int frontsize; + int backsize; + markfaces_t *middle; // may contains coplanar faces and discardable(SOLIDHINT) faces +} surftree_t; + +static double g_splitvalue[MAX_INTERNAL_MAP_PLANES][2]; + +// organize all surfaces into a tree structure to accelerate intersection test +// can reduce more than 90% compile time for very complicated maps +surfnode_t *AllocSurfNode( void ) +{ + return (surfnode_t *)Mem_Alloc( sizeof( surfnode_t ), C_LEAFNODE ); +} + +void FreeSurfNode( surfnode_t *node ) +{ + Mem_Free( node, C_LEAFNODE ); +} + +markfaces_t *AllocMarkFaces( void ) +{ + return (markfaces_t *)Mem_Alloc( sizeof( markfaces_t )); +} + +void FreeMarkFaces( markfaces_t **mf ) +{ + if( !mf || !*mf ) return; + if((*mf)->array ) + Mem_Free((*mf)->array ); + Mem_Free( *mf ); + *mf = NULL; +} + +void InsertMarkFace( markfaces_t *mf, face_t *f ) +{ + if( !mf ) return; + + if( mf->reserve <= 0 ) + { + mf->reserve = 256; // alloc reserve to avoid realloc on each face + mf->array = (face_t **)Mem_Realloc( mf->array, sizeof( face_t* ) * ( mf->count + mf->reserve + 1 )); + } + + mf->array[mf->count++] = f; + mf->array[mf->count] = NULL; + mf->reserve--; +} + +void ClearMarkFaces( markfaces_t *mf ) +{ + if( !mf ) return; + + if( mf->array ) + { + Mem_Free( mf->array ); + mf->array = NULL; + } + mf->reserve = mf->count = 0; +} + +void BuildSurfaceTree_r( surftree_t *tree, surfnode_t *node ) +{ + face_t *f, **fp; + + node->size = MarkFacesCount( node->leaffaces ); + node->size_discardable = 0; + + if( node->size == 0 ) + { + node->isleaf = true; + return; + } + + ClearBounds( node->mins, node->maxs ); + + for( fp = node->leaffaces->array; fp && *fp != NULL; fp++ ) + { + f = *fp; + WindingBounds( f->w, node->mins, node->maxs, true ); + + if( f->facestyle == face_discardable ) + node->size_discardable++; + } + + int bestaxis = -1; + vec_t bestdelta = 0; + + for( int k = 0; k < 3; k++ ) + { + if( node->maxs[k] - node->mins[k] > bestdelta + BSPCHOP_EPSILON ) + { + bestdelta = node->maxs[k] - node->mins[k]; + bestaxis = k; + } + } + + if( node->size <= 5 || tree->dontbuild || bestaxis == -1 ) + { + node->isleaf = true; + return; + } + + vec_t dist, dist1, dist2; + + node->isleaf = false; + dist = (node->mins[bestaxis] + node->maxs[bestaxis]) * 0.5; + dist1 = (3 * node->mins[bestaxis] + node->maxs[bestaxis]) * 0.25; + dist2 = (node->mins[bestaxis] + 3 * node->maxs[bestaxis]) * 0.25; + + // each child node is at most 3/4 the size of the parent node. + // Most faces should be passed to a child node, faces left in the + // parent node are the ones whose dimensions are large enough to + // be comparable to the dimension of the parent node. + node->nodefaces = AllocMarkFaces(); + node->nodefaces_discardablesize = 0; + + node->children[0] = AllocSurfNode(); + node->children[0]->leaffaces = AllocMarkFaces(); + node->children[1] = AllocSurfNode(); + node->children[1]->leaffaces = AllocMarkFaces(); + + for( fp = node->leaffaces->array; fp && *fp != NULL; fp++ ) + { + f = *fp; + + winding_t *w = f->w; + vec_t low = BOGUS_RANGE; + vec_t high = -BOGUS_RANGE; + + if( !w || w->numpoints < 3 ) + continue; + + for( int x = 0; x < w->numpoints; x++ ) + { + low = Q_min( low, w->p[x][bestaxis] ); + high = Q_max( high, w->p[x][bestaxis] ); + } + + if( low < dist1 + BSPCHOP_EPSILON && high > dist2 - BSPCHOP_EPSILON ) + { + InsertMarkFace( node->nodefaces, f ); + + if( f->facestyle == face_discardable ) + node->nodefaces_discardablesize++; + } + else if( low >= dist1 && high <= dist2 ) + { + if(( low + high ) * 0.5 > dist ) + { + InsertMarkFace( node->children[0]->leaffaces, f ); + } + else + { + InsertMarkFace( node->children[1]->leaffaces, f ); + } + } + else if( low >= dist1 ) + { + InsertMarkFace( node->children[0]->leaffaces, f ); + } + else if( high <= dist2 ) + { + InsertMarkFace( node->children[1]->leaffaces, f ); + } + } + + int leafcount = MarkFacesCount( node->leaffaces ); + + if( MarkFacesCount( node->children[0]->leaffaces ) == leafcount || MarkFacesCount( node->children[1]->leaffaces ) == leafcount ) + { + MsgDev( D_WARN, "BuildSurfaceTree_r: didn't split the node\n" ); + + FreeMarkFaces( &node->children[0]->leaffaces ); + FreeMarkFaces( &node->children[1]->leaffaces ); + FreeSurfNode( node->children[0] ); + FreeSurfNode( node->children[1] ); + FreeMarkFaces( &node->nodefaces ); + node->isleaf = true; + return; + } + + FreeMarkFaces( &node->leaffaces ); + BuildSurfaceTree_r( tree, node->children[0] ); + BuildSurfaceTree_r( tree, node->children[1] ); +} + +surftree_t *BuildSurfaceTree( surface_t *surfaces, vec_t epsilon ) +{ + surftree_t *tree = (surftree_t *)Mem_Alloc( sizeof( surftree_t ), C_BSPTREE ); + surface_t *p2; + face_t *f; + + tree->headnode = AllocSurfNode(); + tree->headnode->leaffaces = AllocMarkFaces(); + tree->middle = AllocMarkFaces(); + tree->epsilon = epsilon; + + for( p2 = surfaces; p2 != NULL; p2 = p2->next ) + { + if( p2->onnode ) + continue; + + for( f = p2->faces; f != NULL; f = f->next ) + { + InsertMarkFace( tree->headnode->leaffaces, f ); + InsertMarkFace( tree->middle, f ); + } + } + + tree->dontbuild = MarkFacesCount( tree->headnode->leaffaces ) < 20; + BuildSurfaceTree_r( tree, tree->headnode ); + + if( tree->dontbuild ) + { + tree->backsize = 0; + tree->frontsize = 0; + } + else ClearMarkFaces( tree->middle ); + + return tree; +} + +void TestSurfaceTree_r( surftree_t *tree, const surfnode_t *node, const plane_t *split ) +{ + vec_t low, high; + face_t **fp; + + if( node->size == 0 ) + return; + + low = high = -split->dist; + + for( int k = 0; k < 3; k++ ) + { + if( split->normal[k] >= 0 ) + { + high += split->normal[k] * node->maxs[k]; + low += split->normal[k] * node->mins[k]; + } + else + { + high += split->normal[k] * node->mins[k]; + low += split->normal[k] * node->maxs[k]; + } + } + + if( low > tree->epsilon ) + { + tree->frontsize += node->size; + tree->frontsize -= node->size_discardable; + return; + } + + if( high < -tree->epsilon ) + { + tree->backsize += node->size; + tree->backsize -= node->size_discardable; + return; + } + + if( node->isleaf ) + { + for( fp = node->leaffaces->array; fp && *fp != NULL; fp++ ) + { + InsertMarkFace( tree->middle, *fp ); + } + } + else + { + for( fp = node->nodefaces->array; fp && *fp != NULL; fp++ ) + { + InsertMarkFace( tree->middle, *fp ); + } + + TestSurfaceTree_r( tree, node->children[0], split ); + TestSurfaceTree_r( tree, node->children[1], split ); + } +} + +void TestSurfaceTree( surftree_t *tree, const plane_t *split ) +{ + if( tree->dontbuild ) + return; + + ClearMarkFaces( tree->middle ); + tree->backsize = tree->frontsize = 0; + TestSurfaceTree_r( tree, tree->headnode, split ); +} + +void DeleteSurfaceTree_r( surfnode_t *node ) +{ + if( node->isleaf ) + { + FreeMarkFaces( &node->leaffaces ); + } + else + { + DeleteSurfaceTree_r( node->children[0] ); + FreeSurfNode( node->children[0] ); + DeleteSurfaceTree_r( node->children[1] ); + FreeSurfNode( node->children[1] ); + FreeMarkFaces( &node->nodefaces ); + } +} + +void DeleteSurfaceTree( surftree_t *tree ) +{ + DeleteSurfaceTree_r( tree->headnode ); + FreeSurfNode( tree->headnode ); + FreeMarkFaces( &tree->middle ); + Mem_Free( tree, C_BSPTREE ); +} + +/* +================== +FaceSide + +For BSP hueristic +================== +*/ +static int FaceSide( const face_t *in, const plane_t *split, double *epsilonsplit = NULL ) +{ + vec_t d_front = 0.0; + vec_t d_back = 0.0; + vec_t dot; + winding_t *w; + vec_t *p; + int i; + + ASSERT( in && in->w ); + + w = in->w; + + // axial planes are fast + if( split->type <= PLANE_LAST_AXIAL ) + { + for( i = 0, p = w->p[0] + split->type; i < w->numpoints; i++, p += 3 ) + { + dot = *p - split->dist; + d_front = Q_max( dot, d_front ); + d_back = Q_min( dot, d_back ); + } + } + else + { + // sloping planes take longer + for( i = 0, p = w->p[0]; i < w->numpoints; i++, p += 3 ) + { + dot = DotProduct( p, split->normal ) - split->dist; + d_front = Q_max( dot, d_front ); + d_back = Q_min( dot, d_back ); + } + } + + if( d_front <= BSPCHOP_EPSILON ) + { + if( epsilonsplit && ( d_front > MINSPLIT_EPSILON || d_back > -MAXSPLIT_EPSILON )) + (*epsilonsplit)++; + return SIDE_BACK; + } + + if( d_back >= -BSPCHOP_EPSILON ) + { + if( epsilonsplit && ( d_back < -MINSPLIT_EPSILON || d_front < MAXSPLIT_EPSILON )) + (*epsilonsplit)++; + return SIDE_FRONT; + } + + if( epsilonsplit && ( d_front < MAXSPLIT_EPSILON || d_back > -MAXSPLIT_EPSILON )) + (*epsilonsplit)++; + + return SIDE_ON; +} + +/* +================== +ChooseMidPlaneFromList + +When there are a huge number of planes, just choose one closest +to the middle. +================== +*/ +surface_t *ChooseMidPlaneFromList( surface_t *surfaces, const vec3_t mins, const vec3_t maxs, int detaillevel ) +{ + surface_t *p, *bestsurface; + vec_t bestvalue, value; + plane_t *plane; + surftree_t *surfacetree; + vec_t dist; + face_t *f, **fp; + + surfacetree = BuildSurfaceTree( surfaces, BSPCHOP_EPSILON ); + + // pick the plane that splits the least + bestsurface = NULL; + bestvalue = 9e30; + + for( p = surfaces; p != NULL; p = p->next ) + { + if( p->onnode || p->detaillevel != detaillevel ) + continue; + + plane = &g_mapplanes[p->planenum]; + + // check for axis aligned surfaces + int l = plane->type; + + if( l > PLANE_LAST_AXIAL ) + continue; + + // calculate the split metric along axis l, smaller values are better + value = 0; + + dist = plane->dist * plane->normal[l]; + + if( maxs[l] - dist < g_maxnode_size / 2.0 - ON_EPSILON || dist - mins[l] < g_maxnode_size / 2.0 - ON_EPSILON ) + continue; + + double crosscount = 0; + double frontcount = 0; + double backcount = 0; + double coplanarcount = 0; + + TestSurfaceTree( surfacetree, plane ); + frontcount += surfacetree->frontsize; + backcount += surfacetree->backsize; + + for( fp = surfacetree->middle->array; fp && *fp != NULL; fp++ ) + { + f = *fp; + + if( f->facestyle == face_discardable ) + continue; + + if( f->planenum == p->planenum || f->planenum == ( p->planenum ^ 1 )) + { + coplanarcount++; + continue; + } + + switch( FaceSide( f, plane )) + { + case SIDE_FRONT: + frontcount++; + break; + case SIDE_BACK: + backcount++; + break; + case SIDE_ON: + crosscount++; + break; + } + } + + double frontsize = frontcount + 0.5 * coplanarcount + 0.5 * crosscount; + double frontfrac = (maxs[l] - dist) / (maxs[l] - mins[l]); + double backsize = backcount + 0.5 * coplanarcount + 0.5 * crosscount; + double backfrac = (dist - mins[l]) / (maxs[l] - mins[l]); + + value = crosscount + 0.1 * (frontsize * (log( frontfrac ) / log( 2.0 )) + backsize * ( log( backfrac ) / log( 2.0 ))); + // the first part is how the split will increase the number of faces + // the second part is how the split will increase the average depth of the bsp tree + + if( value > bestvalue ) + continue; + + // currently the best! + bestvalue = value; + bestsurface = p; + } + + DeleteSurfaceTree( surfacetree ); + + return bestsurface; +} + +/* +================== +ChoosePlaneFromList + +Choose the plane that splits the least faces +================== +*/ +surface_t *ChoosePlaneFromList( surface_t *surfaces, const vec3_t mins, const vec3_t maxs, int detaillevel ) +{ + surface_t *p, *bestsurface; + vec_t value, bestvalue; + double totalsplit; + double avesplit; + double planecount; + surftree_t* surfacetree; + plane_t *plane; + face_t *f, **fp; + + planecount = totalsplit = 0; + surfacetree = BuildSurfaceTree( surfaces, BSPCHOP_EPSILON ); + + // pick the plane that splits the least + bestvalue = 9e30; + bestsurface = NULL; + + for( p = surfaces; p != NULL; p = p->next ) + { + if( p->onnode || p->detaillevel != detaillevel ) + continue; + + planecount++; + + double crosscount = 0; + double frontcount = 0; + double backcount = 0; + double coplanarcount = 0; + double epsilonsplit = 0; + + plane = &g_mapplanes[p->planenum]; + + for( f = p->faces; f != NULL; f = f->next ) + { + if( f->facestyle == face_discardable ) + continue; + coplanarcount++; + } + + TestSurfaceTree( surfacetree, plane ); + + frontcount += surfacetree->frontsize; + backcount += surfacetree->backsize; + + for( fp = surfacetree->middle->array; fp && *fp != NULL; fp++ ) + { + f = *fp; + + if( f->planenum == p->planenum || f->planenum == ( p->planenum ^ 1 )) + continue; + + if( f->facestyle == face_discardable ) + { + FaceSide( f, plane, &epsilonsplit ); + continue; + } + + switch( FaceSide( f, plane, &epsilonsplit )) + { + case SIDE_FRONT: + frontcount++; + break; + case SIDE_BACK: + backcount++; + break; + case SIDE_ON: + totalsplit++; + crosscount++; + break; + } + } + + value = crosscount - sqrt( coplanarcount ); // Not optimized. --vluzacn + if( coplanarcount == 0 ) crosscount += 1; + + // This is the most efficient code among what I have ever tested: + // (1) BSP file is small, despite possibility of slowing down vis and rad + // (but still faster than the original non BSP balancing method). + // (2) Factors need not adjust across various maps. + double frac = (coplanarcount / 2 + crosscount / 2 + frontcount) / (coplanarcount + frontcount + backcount + crosscount); + double ent = 0.0; + + if( frac > 0.0001 && frac < 0.9999 ) + ent = (-frac * log( frac ) / log( 2.0 ) - (1.0 - frac) * log( 1.0 - frac ) / log( 2.0 )); + g_splitvalue[p->planenum][1] = crosscount * (1.0 - ent); + value += epsilonsplit * 10000; + g_splitvalue[p->planenum][0] = value; + } + + avesplit = totalsplit / planecount; + + for( p = surfaces; p != NULL; p = p->next ) + { + if( p->onnode || p->detaillevel != detaillevel ) + continue; + + value = g_splitvalue[p->planenum][0] + avesplit * g_splitvalue[p->planenum][1]; + + if( value < bestvalue ) + { + bestvalue = value; + bestsurface = p; + } + } + + if( !bestsurface ) + COM_FatalError( "ChoosePlaneFromList: no valid planes\n" ); + + DeleteSurfaceTree( surfacetree ); + + return bestsurface; +} + +/* +================== +SelectPartition + +Selects a surface from a linked list of surfaces to split the group on +returns NULL if the surface list can not be divided any more (a leaf) +================== +*/ +surface_t *SelectPartition( surface_t *surfaces, node_t *node, bool midsplit, int splitdetaillevel, vec3_t validmins, vec3_t validmaxs ) +{ + surface_t *p; + + if( splitdetaillevel == -1 ) + return NULL; + + if( midsplit ) + { + // do fast way for clipping hull + if(( p = ChooseMidPlaneFromList( surfaces, validmins, validmaxs, splitdetaillevel )) != NULL ) + return p; + } + + // do slow way to save poly splits for drawing hull + return ChoosePlaneFromList( surfaces, node->mins, node->maxs, splitdetaillevel ); +} \ No newline at end of file diff --git a/utils/p2bsp/portals.cpp b/utils/p2bsp/portals.cpp new file mode 100644 index 0000000..bc392da --- /dev/null +++ b/utils/p2bsp/portals.cpp @@ -0,0 +1,427 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include "bsp5.h" + + +node_t g_outside_node; // portals outside the world face this + +//============================================================================= +/* +============= +AddPortalToNodes +============= +*/ +void AddPortalToNodes( portal_t *p, node_t *front, node_t *back ) +{ + if( p->nodes[0] || p->nodes[1] ) + COM_FatalError( "AddPortalToNode: allready included\n" ); + + p->nodes[0] = front; + p->next[0] = front->portals; + front->portals = p; + + p->nodes[1] = back; + p->next[1] = back->portals; + back->portals = p; +} + +/* +============= +RemovePortalFromNode +============= +*/ +void RemovePortalFromNode( portal_t *portal, node_t *l ) +{ + portal_t **pp, *t; + + // remove reference to the current portal + pp = &l->portals; + while( 1 ) + { + t = *pp; + if( !t ) COM_FatalError( "RemovePortalFromNode: portal not in leaf\n" ); + + if( t == portal ) + break; + + if( t->nodes[0] == l ) + pp = &t->next[0]; + else if( t->nodes[1] == l ) + pp = &t->next[1]; + else COM_FatalError( "RemovePortalFromNode: portal not bounding leaf\n" ); + } + + if( portal->nodes[0] == l ) + { + *pp = portal->next[0]; + portal->nodes[0] = NULL; + } + else if( portal->nodes[1] == l ) + { + *pp = portal->next[1]; + portal->nodes[1] = NULL; + } +} + +//============================================================================ + +void PrintPortal( portal_t *p ) +{ + pw( p->winding ); +} + +/* +================ +MakeHeadnodePortals + +The created portals will face the global outside_node +================ +*/ +void MakeHeadnodePortals( node_t *node, const vec3_t mins, const vec3_t maxs ) +{ + vec3_t bounds[2]; + portal_t *p, *portals[6]; + plane_t bplanes[6], *pl; + int i, j, n; + + // pad with some space so there will never be null volume leafs + VectorCopy( mins, bounds[0] ); + VectorCopy( maxs, bounds[1] ); + + ExpandBounds( bounds[0], bounds[1], SIDESPACE ); + g_outside_node.contents = CONTENTS_SOLID; + g_outside_node.portals = NULL; + + for( i = 0; i < 3; i++ ) + { + for( j = 0; j < 2; j++ ) + { + n = j * 3 + i; + + p = AllocPortal (); + portals[n] = p; + + pl = &bplanes[n]; + memset( pl, 0, sizeof( *pl )); + + if( j ) + { + pl->normal[i] = -1; + pl->dist = -bounds[j][i]; + } + else + { + pl->normal[i] = 1; + pl->dist = bounds[j][i]; + } + + p->planenum = FindFloatPlane( pl->normal, pl->dist ); + p->winding = BaseWindingForPlane( pl->normal, pl->dist ); + AddPortalToNodes( p, node, &g_outside_node ); + } + } + + // clip the basewindings by all the other planes + for( i = 0; i < 6; i++ ) + { + for( j = 0; j < 6; j++ ) + { + if( j == i ) continue; + ChopWindingInPlace( &portals[i]->winding, bplanes[j].normal, bplanes[j].dist, g_prtepsilon ); + } + } +} + +//============================================================================ + +/* +================ +PortalSidesVisible + +translucent water support +================ +*/ +bool PortalSidesVisible( portal_t *p ) +{ +#if 1 + int contents0 = p->nodes[0]->contents; + int contents1 = p->nodes[1]->contents; + + // can't see through solids + if( contents0 == CONTENTS_SOLID || contents1 == CONTENTS_SOLID ) + return false; + + // if contents values are the same and not solid, can see through + if( contents0 == contents1 ) + return true; + + // can't see through func_illusionary_visblocker + if( contents0 == CONTENTS_VISBLOCKER || contents1 == CONTENTS_VISBLOCKER ) + return false; + + if( IsLiquidContents( contents0 ) && contents1 == CONTENTS_EMPTY ) + return true; + + if( IsLiquidContents( contents1 ) && contents0 == CONTENTS_EMPTY ) + return true; + + return false; +#else + if( p->nodes[0]->contents == p->nodes[1]->contents ) + return true; + + if( p->nodes[0]->contents != CONTENTS_SOLID && p->nodes[1]->contents != CONTENTS_SOLID + && p->nodes[0]->contents != CONTENTS_SKY && p->nodes[1]->contents != CONTENTS_SKY ) + return true; + + return false; +#endif +} + +/* +============================================================================== + +PORTAL FILE GENERATION + +============================================================================== +*/ + +FILE *pf; +int num_visleafs; // leafs the player can be in +int num_visportals; +int c_backward_portals; +int c_colinear_portals; + +static void WritePortalFile_r( node_t *node ) +{ + plane_t *plane, plane2; + portal_t *p, *next; + winding_t *w; + int i; + + if( !FBitSet( node->flags, FNODE_LEAFPORTAL )) + { + WritePortalFile_r( node->children[0] ); + WritePortalFile_r( node->children[1] ); + return; + } + + if( node->contents == CONTENTS_SOLID ) + return; + + for( p = node->portals; p != NULL; p = next ) + { + next = (p->nodes[0] == node) ? p->next[0] : p->next[1]; + + if( !p->winding || p->nodes[0] != node ) + continue; + + if( !PortalSidesVisible( p )) + continue; + + w = p->winding; + + // write out to the file + + // sometimes planes get turned around when they are very near + // the changeover point between different axis. interpret the + // plane the same way vis will, and flip the side orders if needed + WindingPlane( w, plane2.normal, &plane2.dist ); + plane = &g_mapplanes[p->planenum]; + + if( DotProduct( plane->normal, plane2.normal ) < ( 1.0 - g_prtepsilon )) + { + if( DotProduct( plane->normal, plane2.normal ) > -1.0 + g_prtepsilon ) + c_colinear_portals++; + else c_backward_portals++; + + fprintf( pf, "%i %i %i ", w->numpoints, p->nodes[1]->visleafnum, p->nodes[0]->visleafnum ); + } + else + { + fprintf( pf, "%i %i %i ", w->numpoints, p->nodes[0]->visleafnum, p->nodes[1]->visleafnum ); + } + + for( i = 0; i < w->numpoints; i++ ) + { + fprintf( pf, "(%f %f %f) ", w->p[i][0], w->p[i][1], w->p[i][2] ); + } + + fprintf( pf,"\n" ); + } +} + +/* +================ +NumberLeafs_r +================ +*/ +void NumberLeafs_r( node_t *node ) +{ + portal_t *p; + + if( !FBitSet( node->flags, FNODE_LEAFPORTAL )) + { + // decision node + node->visleafnum = -99; + NumberLeafs_r( node->children[0] ); + NumberLeafs_r( node->children[1] ); + return; + } + + if( node->contents == CONTENTS_SOLID ) + { + // solid block, viewpoint never inside + node->visleafnum = -1; + return; + } + + node->visleafnum = num_visleafs++; + + for( p = node->portals; p != NULL; ) + { + if( p->nodes[0] == node ) + { + // only write out from first leaf + if( p->winding && PortalSidesVisible( p )) + num_visportals++; + p = p->next[0]; + } + else p = p->next[1]; + } + +} + +/* +================ +CountChildLeafs_r +================ +*/ +static int CountChildLeafs_r( node_t *node ) +{ + if( node->planenum == PLANENUM_LEAF ) + { + if( FBitSet( node->flags, FNODE_DETAILCONTENTS )) + { + // solid + return 0; + } + else + { + return 1; + } + } + else + { + // node + int count = 0; + + count += CountChildLeafs_r( node->children[0] ); + count += CountChildLeafs_r( node->children[1] ); + + return count; + } +} + +/* +================ +WriteLeafCount_r +================ +*/ +static void WriteLeafCount_r( node_t *node ) +{ + if( !FBitSet( node->flags, FNODE_LEAFPORTAL )) + { + WriteLeafCount_r( node->children[0] ); + WriteLeafCount_r( node->children[1] ); + } + else + { + if( node->contents == CONTENTS_SOLID ) + return; + + int count = CountChildLeafs_r( node ); + fprintf( pf, "%i\n", count ); + } +} + +/* +================ +WritePortalfile +================ +*/ +void WritePortalfile( tree_t *tree, bool leaked ) +{ + char shortname[1024]; + + num_visleafs = 0; + num_visportals = 0; + c_backward_portals = 0; + c_colinear_portals = 0; + + COM_FileBase( g_portfilename, shortname ); + + // set the visleafnum field in every leaf + // and count the total number of portals + NumberLeafs_r( tree->headnode ); + + // write the file + if( leaked ) MsgDev( D_INFO, "^3BSP probably has a leak, writing portal file:^7 %s.prt\n", shortname ); + else MsgDev( D_INFO, "^2BSP generation successful, writing portal file:^7 %s.prt\n", shortname ); + pf = fopen( g_portfilename, "w" ); + if( !pf ) COM_FatalError( "error opening %s\n", g_portfilename ); + + fprintf( pf, "%i\n", num_visleafs ); + fprintf( pf, "%i\n", num_visportals ); + + WriteLeafCount_r( tree->headnode ); + WritePortalFile_r( tree->headnode ); + + fclose( pf ); + + MsgDev( D_REPORT, "%i colinear portals\n", c_colinear_portals ); + MsgDev( D_REPORT, "%i backward portals\n", c_backward_portals ); +} + + +//=================================================== +void FreePortals( node_t *node ) +{ + portal_t *p, *nextp; + + if( node->children[0] ) + FreePortals( node->children[0] ); + + if( node->children[1] ) + FreePortals( node->children[1] ); + + for( p = node->portals; p != NULL; p = nextp ) + { + if( p->nodes[0] == node ) + nextp = p->next[0]; + else nextp = p->next[1]; + + RemovePortalFromNode( p, p->nodes[0] ); + RemovePortalFromNode( p, p->nodes[1] ); + FreeWinding( p->winding ); + FreePortal( p ); + } +} + +/* +================== +FreeTreePortals +================== +*/ +void FreeTreePortals( tree_t *tree ) +{ + FreePortals( tree->headnode ); +} \ No newline at end of file diff --git a/utils/p2bsp/qbsp.cpp b/utils/p2bsp/qbsp.cpp new file mode 100644 index 0000000..b206278 --- /dev/null +++ b/utils/p2bsp/qbsp.cpp @@ -0,0 +1,633 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// qbsp.c + +#include "bsp5.h" + +// +// command line flags +// +bool g_nofill = DEFAULT_NOFILL; +bool g_notjunc = DEFAULT_NOTJUNC; +bool g_noclip = DEFAULT_NOCLIP; +bool g_forcevis = DEFAULT_FORCEVIS; + +int g_maxnode_size = DEFAULT_MAXNODE_SIZE; +int g_merge_level = DEFAULT_MERGE_LEVEL; +vec_t g_prtepsilon = PRTCHOP_EPSILON; + +char g_pointfilename[1024]; +char g_linefilename[1024]; +char g_portfilename[1024]; + +//=========================================================================== +/* +============ +ReadMapPlanes +============ +*/ +void ReadMapPlanes( const char *source ) +{ + char path[1024]; + size_t filelen; + FILE *f; + + Q_snprintf( path, sizeof( path ), "%s.pln", source ); + f = fopen( path, "rb" ); + if( !f ) COM_FatalError( "couldn't open %s\n", path ); + + fseek( f, 0, SEEK_END ); + filelen = ftell( f ); + fseek( f, 0, SEEK_SET ); + + if( filelen > sizeof( g_mapplanes )) + COM_FatalError( "insufficient MAX_INTERNAL_MAP_PLANES size\n" ); + + if( filelen % sizeof( plane_t )) + COM_FatalError( "msimatch plane_t struct between csg and bsp\n" ); + + if( fread( g_mapplanes, 1, filelen, f ) != filelen ) + COM_FatalError( "failed to read mapplanes\n" ); + + g_nummapplanes = filelen / sizeof( plane_t ); + + fclose( f ); + + // clear out hash_chain, now is output planenum! + for( int i = 0; i < g_nummapplanes; i++ ) + g_mapplanes[i].outplanenum = -1; +} + +/* +============ +ReadHullSizes +============ +*/ +void ReadHullSizes( const char *source ) +{ + float x1, y1, z1; + float x2, y2, z2; + char path[1024]; + FILE *f; + + Q_snprintf( path, sizeof( path ), "%s.hsz", source ); + f = fopen( path, "rb" ); + if( !f ) return; // just use predefined sizes + + for( int i = 0; i < MAX_MAP_HULLS; i++ ) + { + int count = fscanf( f, "%f %f %f %f %f %f\n", &x1, &y1, &z1, &x2, &y2, &z2 ); + if( count != 6 ) COM_FatalError( "Load hull size (line %i): scanf failure", i + 1 ); + + g_hull_size[i][0][0] = x1; + g_hull_size[i][0][1] = y1; + g_hull_size[i][0][2] = z1; + g_hull_size[i][1][0] = x2; + g_hull_size[i][1][1] = y2; + g_hull_size[i][1][2] = z2; + } + + fclose( f ); +} + +//=========================================================================== +/* +================== +NewFaceFromFace + +Duplicates the non point information of a face, used by SplitFace and +MergeFace. +================== +*/ +face_t *NewFaceFromFace( face_t *in ) +{ + face_t *newf; + + newf = AllocFace (); + + newf->planenum = in->planenum; + newf->texturenum = in->texturenum; + newf->original = in->original; + newf->contents = in->contents; + newf->detaillevel = in->detaillevel; + newf->facestyle = in->facestyle; + + return newf; +} + +/* +================== +SplitFace + +================== +*/ +void SplitFaceEpsilon( face_t *in, plane_t *split, face_t **front, face_t **back, vec_t epsilon, bool keepsource ) +{ + vec_t *facenormal = NULL; + winding_t *frontw, *backw; + + *front = *back = NULL; + + if( in->detaillevel ) + facenormal = g_mapplanes[in->planenum].normal; + + DivideWindingEpsilon( in->w, split->normal, split->dist, epsilon, &frontw, &backw, facenormal ); + + if( frontw && backw ) + { + // face is divided + *front = NewFaceFromFace( in ); + *back = NewFaceFromFace( in ); + (*front)->w = frontw; + (*back)->w = backw; + + if( !keepsource ) + { + // free original + FreeFace( in ); + } + } + else if( frontw != NULL ) + { + *front = in; + } + else if( backw != NULL ) + { + *back = in; + } +} + +void SplitFace( face_t *in, plane_t *split, face_t **front, face_t **back, bool keepsource ) +{ + SplitFaceEpsilon( in, split, front, back, ON_EPSILON, keepsource ); +} + +//=========================================================================== + +int c_activefaces, c_peakfaces; +int c_activesurfaces, c_peaksurfaces; +int c_activeportals, c_peakportals; + +void PrintMemory( void ) +{ + Msg( "faces : %6i (%6i)\n", c_activefaces, c_peakfaces ); + Msg( "surfaces: %6i (%6i)\n", c_activesurfaces, c_peaksurfaces ); + Msg( "portals : %6i (%6i)\n", c_activeportals, c_peakportals ); +} + +/* +=========== +AllocFace +=========== +*/ +face_t *AllocFace( void ) +{ + face_t *f; + + c_activefaces++; + + if( c_activefaces > c_peakfaces ) + c_peakfaces = c_activefaces; + + f = (face_t *)Mem_Alloc( sizeof( face_t ), C_SURFACE ); + f->planenum = -1; + + return f; +} + +/* +=========== +AddFaceToBounds +=========== +*/ +void AddFaceToBounds( face_t *f, vec3_t mins, vec3_t maxs ) +{ + winding_t *w = f->w; + + for ( int i = 0; i < w->numpoints; i++ ) + AddPointToBounds( w->p[i], mins, maxs ); +} + +/* +=========== +FreeFace +=========== +*/ +void FreeFace( face_t *f ) +{ + if( !f ) return; + + if( f->w ) FreeWinding( f->w ); + Mem_Free( f, C_SURFACE ); + + c_activefaces--; +} + +/* +================== +UnlinkFace + +release specified face or purge all chain +================== +*/ +void UnlinkFace( face_t **head, face_t *face ) +{ + face_t **prev = head; + face_t *cur; + + while( 1 ) + { + cur = *prev; + if( !cur ) break; + + if( face != NULL && face != cur ) + { + prev = &cur->next; + continue; + } + + *prev = cur->next; + } +} + +/* +================== +CountListFaces + +return count of valid faces +================== +*/ +int CountListFaces( face_t *list ) +{ + int count = 0; + + for( face_t *f1 = list; f1 != NULL; f1 = f1->next ) + if( f1->w ) count++; + + return count; +} + +/* +=========== +AllocSurface +=========== +*/ +surface_t *AllocSurface( void ) +{ + surface_t *s; + + c_activesurfaces++; + + if( c_activesurfaces > c_peaksurfaces ) + c_peaksurfaces = c_activesurfaces; + + s = (surface_t *)Mem_Alloc( sizeof( surface_t ), C_SURFACE ); + ClearBounds( s->mins, s->maxs ); + s->planenum = -1; + + return s; +} + +/* +=========== +FreeSurface +=========== +*/ +void FreeSurface( surface_t *s ) +{ + Mem_Free( s, C_SURFACE ); + c_activesurfaces--; +} + +/* +=========== +AllocPortal +=========== +*/ +portal_t *AllocPortal( void ) +{ + c_activeportals++; + if( c_activeportals > c_peakportals ) + c_peakportals = c_activeportals; + + return (portal_t *)Mem_Alloc( sizeof( portal_t ), C_PORTAL ); +} + +/* +=========== +FreePortal +=========== +*/ +void FreePortal( portal_t *p ) +{ + c_activeportals--; + Mem_Free( p, C_PORTAL ); +} + +/* +=========== +AllocNode +=========== +*/ +node_t *AllocNode( void ) +{ + return (node_t *)Mem_Alloc( sizeof( node_t ), C_LEAFNODE ); +} + +/* +=========== +FreeNode +=========== +*/ +void FreeNode( node_t *n ) +{ + Mem_Free( n, C_LEAFNODE ); +} + +/* +=========== +FreeLeaf +=========== +*/ +void FreeLeaf( node_t *n ) +{ + if( n->markfaces ) + Mem_Free( n->markfaces ); + Mem_Free( n, C_LEAFNODE ); +} + +//=========================================================================== +/* +================= +CreateSingleHull +================= +*/ +void CreateSingleHull( const char *source, int hullnum ) +{ + int modnum = 0; + FILE *brushfile; + FILE *polyfile; + char name[1024]; + tree_t *tree; + + Msg( "CreateHull: %i\n", hullnum ); + + Q_snprintf( name, sizeof( name ), "%s.p%i", source, hullnum ); + polyfile = fopen( name, "r" ); + if( !polyfile ) COM_FatalError( "Can't open %s", name ); + + Q_snprintf( name, sizeof( name ), "%s.b%i", source, hullnum ); + brushfile = fopen( name, "r" ); + if( !brushfile ) COM_FatalError( "Can't open %s", name ); + + while(( tree = MakeTreeFromHullFaces( polyfile, brushfile )) != NULL ) + { + tree = TreeProcessModel( tree, modnum, hullnum ); + + if( hullnum == 0 ) EmitDrawNodes( tree ); + else EmitClipNodes( tree, modnum, hullnum ); + + FreeTree( tree ); + modnum++; + } + + Q_snprintf( name, sizeof( name ), "%s.p%i", source, hullnum ); + fclose( polyfile ); + unlink( name ); + + Q_snprintf( name, sizeof( name ), "%s.b%i", source, hullnum ); + fclose( brushfile ); + unlink( name ); +} + +/* +================= +ProcessFile + +================= +*/ +void ProcessFile( const char *source ) +{ + char bspfilename[1024]; + char name[1024]; + int i; + + // create filenames + Q_snprintf( g_portfilename, sizeof( g_portfilename ), "%s.prt", source ); + remove( g_portfilename ); + + Q_snprintf( g_pointfilename, sizeof( g_pointfilename ), "%s.pts", source ); + remove( g_pointfilename ); + + Q_snprintf( g_linefilename, sizeof( g_linefilename ), "%s.lin", source ); + remove( g_linefilename ); + + // reading hull sizes from text-file + ReadHullSizes( source ); + + // reading planes from binary dump + ReadMapPlanes( source ); + + // load the output of qcsg + Q_snprintf( bspfilename, sizeof( bspfilename ), "%s.bsp", source ); + LoadBSPFile( bspfilename ); + + ParseEntities (); + + // init the tables to be shared by all models + BeginBSPFile (); + + for( i = 0; i < MAX_MAP_HULLS; i++ ) + { + CreateSingleHull( source, i ); + } + + // write the updated bsp file out + FinishBSPFile( bspfilename ); + + Q_snprintf( name, sizeof( name ), "%s.pln", source ); + unlink( name ); + + Q_snprintf( name, sizeof( name ), "%s.hsz", source ); + unlink( name ); +} + +//========================================= +/* +============ +GetNodeSize + +print node size +============ +*/ +const char *GetNodeSize( int nodesize ) +{ + if( nodesize == DEFAULT_MAXNODE_SIZE ) + return va( "%s", "Auto" ); + return va( "%d", nodesize ); +} + +/* +============ +PrintBspSettings + +show compiler settings like ZHLT +============ +*/ +static void PrintBspSettings( void ) +{ + Msg( "\nCurrent p2bsp settings\n" ); + Msg( "Name | Setting | Default\n" ); + Msg( "---------------------|-----------|-------------------------\n" ); + Msg( "developer [ %7d ] [ %7d ]\n", GetDeveloperLevel(), DEFAULT_DEVELOPER ); + Msg( "max node size [ %7s ] [ %7s ]\n", GetNodeSize( g_maxnode_size ), GetNodeSize( DEFAULT_MAXNODE_SIZE )); + Msg( "merge faces depth [ %7d ] [ %7d ]\n", g_merge_level, DEFAULT_MERGE_LEVEL ); + Msg( "notjunc [ %7s ] [ %7s ]\n", g_notjunc ? "on" : "off", DEFAULT_NOTJUNC ? "on" : "off" ); + Msg( "noclip [ %7s ] [ %7s ]\n", g_noclip ? "on" : "off", DEFAULT_NOCLIP ? "on" : "off" ); + Msg( "nofill [ %7s ] [ %7s ]\n", g_nofill ? "on" : "off", DEFAULT_NOFILL ? "on" : "off" ); + Msg( "portal chop epsilon [ %.6f] [ %.6f]\n", g_prtepsilon, PRTCHOP_EPSILON ); + Msg( "force vis [ %7s ] [ %7s ]\n", g_forcevis ? "on" : "off", DEFAULT_FORCEVIS ? "on" : "off" ); + Msg( "\n" ); +} + +/* +============ +PrintBspUsage + +show compiler usage like ZHLT +============ +*/ +static void PrintBspUsage( void ) +{ + Msg( "\n-= p2bsp Options =-\n\n" ); + Msg( " -dev # : compile with developer message (1 - 4). default is %d\n", DEFAULT_DEVELOPER ); + Msg( " -threads # : manually specify the number of threads to run\n" ); + Msg( " -noclip : don't create clipping hulls\n" ); + Msg( " -notjunc : don't break edges on t-junctions (not for final runs)\n" ); + Msg( " -nofill : don't fill outside (used for brush models, not levels)\n" ); + Msg( " -noforcevis : don't make a .prt if the map leaks\n" ); + Msg( " -merge : merge together chopped faces on nodes (merging depth level)\n" ); + Msg( " -maxnodesize val : sets the maximum portal node size\n" ); + Msg( " -epsilon : portal chop precision epsilon\n" ); + Msg( " mapfile : the mapfile to compile\n\n" ); + + exit( 1 ); +} + +/* +================== +main + +================== +*/ +int main( int argc, char **argv ) +{ + int i; + double start, end; + char source[1024]; + char str[64]; + + atexit( Sys_CloseLog ); + source[0] = '\0'; + + for( i = 1; i < argc; i++ ) + { + if( !Q_strcmp( argv[i], "-dev" )) + { + SetDeveloperLevel( atoi( argv[i+1] )); + i++; + } + else if( !Q_strcmp( argv[i], "-threads" )) + { + g_numthreads = atoi( argv[i+1] ); + i++; + } + else if( !Q_strcmp( argv[i], "-noclip" )) + { + g_noclip = true; + } + else if( !Q_strcmp( argv[i], "-notjunc" )) + { + g_notjunc = true; + } + else if( !Q_strcmp( argv[i], "-nofill" )) + { + g_nofill = true; + } + else if( !Q_strcmp( argv[i], "-noforcevis" )) + { + g_forcevis = false; + } + else if( !Q_strcmp( argv[i], "-merge" )) + { + g_merge_level = atoi( argv[i+1] ); + g_merge_level = bound( 0, g_merge_level, 4 ); + i++; + } + else if( !Q_strcmp( argv[i], "-maxnodesize" )) + { + g_maxnode_size = atoi( argv[i+1] ); + g_maxnode_size = bound( 256, g_maxnode_size, 65536 ); + i++; + } + else if( !Q_strcmp( argv[i], "-epsilon" )) + { + g_prtepsilon = atof( argv[i+1] ); + i++; + } + else if( argv[i][0] == '-' ) + { + MsgDev( D_ERROR, "\nUnknown option \"%s\"\n", argv[i] ); + break; + } + else if( !source[0] ) + { + Q_strncpy( source, COM_ExpandArg( argv[i] ), sizeof( source )); + COM_StripExtension( source ); + } + else + { + MsgDev( D_ERROR, "\nUnknown option \"%s\"\n", argv[i] ); + break; + } + } + + if( i != argc || !source[0] ) + { + if( !source[0] ) + Msg( "no mapfile specified\n" ); + PrintBspUsage(); + } + + start = I_FloatTime (); + + Sys_InitLogAppend( va( "%s.log", source )); + + Msg( "\n%s %s (%s)\n", TOOLNAME, VERSIONSTRING, __DATE__ ); + + PrintBspSettings(); + + ThreadSetDefault (); + + ProcessFile( source ); + + FreeEntities(); + + // now check for leaks + SetDeveloperLevel( D_REPORT ); + Mem_Check(); + + end = I_FloatTime (); + Q_timestring((int)( end - start ), str ); + Msg( "%s elapsed\n", str ); + + return 0; +} diff --git a/utils/p2bsp/solidbsp.cpp b/utils/p2bsp/solidbsp.cpp new file mode 100644 index 0000000..bbd5fa8 --- /dev/null +++ b/utils/p2bsp/solidbsp.cpp @@ -0,0 +1,1096 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// solidbsp.c + +#include "bsp5.h" +#include + +/* + + Each node or leaf will have a set of portals that completely enclose + the volume of the node and pass into an adjacent node. + +*/ + +int c_leaffaces; +int c_nodefaces; +int c_splitnodes; +int c_clipped_portals; + +//============================================================================ +static bool g_report_progress = false; +static face_t *markfaces[MAX_MAP_MARKSURFACES + 1]; +static int dispatch_tree_faces; +static int total_tree_faces; + +/* +================== +Split a bounding box by a plane; The front and back bounds returned +are such that they completely contain the portion of the input box +on that side of the plane. Therefore, if the split plane is +non-axial, then the returned bounds will overlap. +================== +*/ +static void DivideBounds( const vec3_t mins, const vec3_t maxs, const plane_t *split, vec3_t fmins, vec3_t fmaxs, vec3_t bmins, vec3_t bmaxs ) +{ + vec_t dist1, dist2, mid; + vec_t split_mins, split_maxs; + int a, b, c, i, j; + const vec_t *bounds[2]; + vec3_t corner; + + VectorCopy( mins, fmins ); + VectorCopy( mins, bmins ); + VectorCopy( maxs, fmaxs ); + VectorCopy( maxs, bmaxs ); + + if( split->type < PLANE_NONAXIAL ) + { + // axial split is easy + fmins[split->type] = bmaxs[split->type] = split->dist; + return; + } + + // make proper sloping cuts... + bounds[0] = mins; + bounds[1] = maxs; + + for( a = 0; a < 3; a++ ) + { + // check for parallel case... no intersection + if( fabs( split->normal[a] ) < NORMAL_EPSILON ) + continue; + + b = (a + 1) % 3; + c = (a + 2) % 3; + + split_mins = maxs[a]; + split_maxs = mins[a]; + + for( i = 0; i < 2; i++ ) + { + corner[b] = bounds[i][b]; + + for( j = 0; j < 2; j++ ) + { + corner[c] = bounds[j][c]; + corner[a] = bounds[0][a]; + + dist1 = DotProduct( corner, split->normal ) - split->dist; + + corner[a] = bounds[1][a]; + dist2 = DotProduct( corner, split->normal ) - split->dist; + + mid = bounds[1][a] - bounds[0][a]; + mid *= ( dist1 / ( dist1 - dist2 )); + mid += bounds[0][a]; + + split_mins = bound( mins[a], split_mins, mid ); + split_maxs = bound( mid, split_maxs, maxs[a] ); + } + } + + if( split->normal[a] > 0 ) + { + fmins[a] = split_mins; + bmaxs[a] = split_maxs; + } + else + { + bmins[a] = split_mins; + fmaxs[a] = split_maxs; + } + } +} + +vec_t SplitPlaneMetric( const plane_t *p, const vec3_t mins, const vec3_t maxs ) +{ + vec_t value = 0.0; + vec_t dist; + + if( p->type < PLANE_NONAXIAL ) + { + dist = p->dist * p->normal[p->type]; + + for( int i = 0; i < 3; i++ ) + { + if( i == p->type ) + { + value += (maxs[i] - dist) * (maxs[i] - dist); + value += (dist - mins[i]) * (dist - mins[i]); + } + else value += 2 * (maxs[i] - mins[i]) * (maxs[i] - mins[i]); + } + } + else + { + vec3_t fmins, fmaxs, bmins, bmaxs; + + DivideBounds( mins, maxs, p, fmins, fmaxs, bmins, bmaxs ); + + for( int i = 0; i < 3; i++ ) + { + value += (fmaxs[i] - fmins[i]) * (fmaxs[i] - fmins[i]); + value += (bmaxs[i] - bmins[i]) * (bmaxs[i] - bmins[i]); + } + } + + return value; +} + +//============================================================================ + +/* +================= +CalcSurfaceInfo + +Calculates the bounding box +================= +*/ +void CalcSurfaceInfo( surface_t *surf ) +{ + if( !surf->faces ) + COM_FatalError( "CalcSurfaceInfo: surface without a face\n" ); + + // calculate a bounding box + ClearBounds( surf->mins, surf->maxs ); + + surf->detaillevel = -1; + + for( face_t *f = surf->faces; f != NULL; f = f->next ) + { + ASSERT( f->w != NULL ); + + if( f->contents >= 0 ) + COM_FatalError( "bad contents %d\n", f->contents ); + + WindingBounds( f->w, surf->mins, surf->maxs, true ); + + if( surf->detaillevel == -1 || f->detaillevel < surf->detaillevel ) + surf->detaillevel = f->detaillevel; + } +} + +/* +================== +DivideSurface +================== +*/ +void DivideSurface( surface_t *in, plane_t *split, surface_t **front, surface_t **back ) +{ + face_t *facet, *next; + face_t *frontlist, *backlist; + face_t *frontfrag, *backfrag; + plane_t *inplane; + surface_t *news; + + inplane = &g_mapplanes[in->planenum]; + + // parallel case is easy + if( VectorCompare( inplane->normal, split->normal )) + { + // check for exactly on node + if( inplane->dist > split->dist ) + { + *front = in; + *back = NULL; + } + else if( inplane->dist < split->dist ) + { + *front = NULL; + *back = in; + } + else + { + frontlist = NULL; + backlist = NULL; + + for( facet = in->faces; facet; facet = next ) + { + next = facet->next; + + if( facet->planenum & 1 ) + { + facet->next = backlist; + backlist = facet; + } + else + { + facet->next = frontlist; + frontlist = facet; + } + } + goto makesurfs; + } + return; + } + + // do a real split. may still end up entirely on one side + // OPTIMIZE: use bounding box for fast test + frontlist = backlist = NULL; + + for( facet = in->faces; facet != NULL; facet = next ) + { + next = facet->next; + + SplitFaceEpsilon( facet, split, &frontfrag, &backfrag, BSPCHOP_EPSILON ); + + if( frontfrag ) + { + frontfrag->next = frontlist; + frontlist = frontfrag; + } + + if( backfrag ) + { + backfrag->next = backlist; + backlist = backfrag; + } + } +makesurfs: + // if nothing actually got split, just move the in plane + if( frontlist == NULL ) + { + *front = NULL; + *back = in; + in->faces = backlist; + return; + } + + if( backlist == NULL ) + { + *front = in; + *back = NULL; + in->faces = frontlist; + return; + } + + // stuff got split, so allocate one new surface and reuse in + news = AllocSurface (); + total_tree_faces++; + *news = *in; + news->faces = backlist; + *back = news; + + in->faces = frontlist; + *front = in; + + // recalc bboxes and flags + CalcSurfaceInfo( news ); + CalcSurfaceInfo( in ); +} + +/* +================== +DivideNodeBounds +================== +*/ +static void DivideNodeBounds( node_t *node, plane_t *split ) +{ + node_t *front = node->children[0]; + node_t *back = node->children[1]; + + ASSERT( front && back ); + + DivideBounds( node->mins, node->maxs, split, front->mins, front->maxs, back->mins, back->maxs ); +} + +/* +================== +SplitNodeSurfaces +================== +*/ +static void SplitNodeSurfaces( surface_t *surfaces, const node_t *node ) +{ + surface_t *frontlist, *frontfrag; + surface_t *backlist, *backfrag; + plane_t *splitplane; + surface_t *p, *next; + + splitplane = &g_mapplanes[node->planenum]; + + frontlist = NULL; + backlist = NULL; + + for( p = surfaces; p != NULL; p = next ) + { + next = p->next; + DivideSurface( p, splitplane, &frontfrag, &backfrag ); + + if( frontfrag ) + { + if( !frontfrag->faces ) + COM_FatalError( "surface with no faces\n" ); + frontfrag->next = frontlist; + frontlist = frontfrag; + } + + if( backfrag ) + { + if( !backfrag->faces ) + COM_FatalError( "surface with no faces\n" ); + backfrag->next = backlist; + backlist = backfrag; + } + } + + node->children[0]->surfaces = frontlist; + node->children[1]->surfaces = backlist; +} + +/* +================== +SplitNodeBrushes +================== +*/ +static void SplitNodeBrushes( brush_t *brushes, const node_t *node ) +{ + brush_t *frontlist, *frontfrag; + brush_t *backlist, *backfrag; + plane_t *splitplane; + brush_t *b, *next; + + splitplane = &g_mapplanes[node->planenum]; + + frontlist = NULL; + backlist = NULL; + + for( b = brushes; b; b = next ) + { + next = b->next; + SplitBrush( b, splitplane, &frontfrag, &backfrag ); + + if( frontfrag ) + { + frontfrag->next = frontlist; + frontlist = frontfrag; + } + + if( backfrag ) + { + backfrag->next = backlist; + backlist = backfrag; + } + } + + node->children[0]->detailbrushes = frontlist; + node->children[1]->detailbrushes = backlist; +} + +/* +================== +RankForContents +================== +*/ +int RankForContents( int contents ) +{ + switch( contents ) + { + case CONTENTS_EMPTY: return 0; + case CONTENTS_VISBLOCKER: return 1; + case CONTENTS_TRANSLUCENT: return 2; + case CONTENTS_FOG: return 3; + case CONTENTS_WATER: return 4; + case CONTENTS_SLIME: return 5; + case CONTENTS_LAVA : return 6; + case CONTENTS_SKY : return 7; + case CONTENTS_SOLID: return 8; + default: COM_FatalError( "RankForContents: bad contents %i\n", contents ); + } + + return -1; +} + +/* +================== +ContentsForRank +================== +*/ +int ContentsForRank( int rank ) +{ + switch( rank ) + { + case -1: return CONTENTS_EMPTY; // no faces at all + case 0: return CONTENTS_EMPTY; + case 1: return CONTENTS_VISBLOCKER; + case 2: return CONTENTS_TRANSLUCENT; + case 3: return CONTENTS_FOG; + case 4: return CONTENTS_WATER; + case 5: return CONTENTS_SLIME; + case 6: return CONTENTS_LAVA; + case 7: return CONTENTS_SKY; + case 8: return CONTENTS_SOLID; + default: COM_FatalError( "ContentsForRank: bad rank %i\n", rank ); + } + + return -1; +} + +/* +================== +MakeNodePortal + +create the new portal by taking the full plane winding for the cutting plane +and clipping it by all of the planes from the other portals. + +Each portal tracks the node that created it, so unused nodes +can be removed later. +================== +*/ +void MakeNodePortal( node_t *node ) +{ + portal_t *new_portal, *p; + plane_t *plane; + plane_t *clipplane; + int side = 0; + winding_t *w; + + plane = &g_mapplanes[node->planenum]; + w = BaseWindingForPlane( plane->normal, plane->dist ); + + new_portal = AllocPortal(); + new_portal->planenum = node->planenum; + new_portal->onnode = node; + + for( p = node->portals; p != NULL; p = p->next[side] ) + { + if( p->nodes[0] == node ) + { + clipplane = &g_mapplanes[p->planenum]; + side = 0; + } + else if( p->nodes[1] == node ) + { + clipplane = &g_mapplanes[p->planenum ^ 1]; + side = 1; + } + else COM_FatalError( "MakeNodePortal: mislinked portal\n" ); + + if( !ChopWindingInPlace( &w, clipplane->normal, clipplane->dist, g_prtepsilon )) + { + FreePortal( new_portal ); + c_clipped_portals++; + return; + } + } + + new_portal->winding = w; + AddPortalToNodes( new_portal, node->children[0], node->children[1] ); +} + +/* +============== +SplitNodePortals + +Move or split the portals that bound node so that the node's +children have portals instead of node. +============== +*/ +void SplitNodePortals( node_t *node ) +{ + portal_t *p, *next_portal, *new_portal; + winding_t *frontwinding, *backwinding; + node_t *f, *b, *other_node; + int side = 0; + plane_t *plane; + + plane = &g_mapplanes[node->planenum]; + f = node->children[0]; + b = node->children[1]; + + for( p = node->portals; p != NULL; p = next_portal ) + { + if( p->nodes[0] == node ) + side = 0; + else if( p->nodes[1] == node ) + side = 1; + else COM_FatalError( "CutNodePortals_r: mislinked portal\n" ); + + next_portal = p->next[side]; + other_node = p->nodes[!side]; + + RemovePortalFromNode( p, p->nodes[0] ); + RemovePortalFromNode( p, p->nodes[1] ); + + // cut the portal into two portals, one on each side of the cut plane + DivideWindingEpsilon( p->winding, plane->normal, plane->dist, g_prtepsilon, &frontwinding, &backwinding ); + + if( !frontwinding && !backwinding ) + continue; + + if( !frontwinding ) + { + if( !side ) AddPortalToNodes( p, b, other_node ); + else AddPortalToNodes( p, other_node, b ); + continue; + } + + if( !backwinding ) + { + if( !side ) AddPortalToNodes( p, f, other_node ); + else AddPortalToNodes( p, other_node, f ); + continue; + } + + // the winding is split + new_portal = AllocPortal(); + *new_portal = *p; + new_portal->winding = backwinding; + FreeWinding( p->winding ); + p->winding = frontwinding; + + if( side == 0 ) + { + AddPortalToNodes( p, f, other_node ); + AddPortalToNodes( new_portal, b, other_node ); + } + else + { + AddPortalToNodes( p, other_node, f ); + AddPortalToNodes( new_portal, other_node, b ); + } + } + + node->portals = NULL; +} + +/* +================== +CalcNodeBounds + +Determines the boundaries of a node by +minmaxing all the portal points, whcih +completely enclose the node. + + Returns true if the node should be midsplit.(very large) +================== +*/ +bool CalcNodeBounds( node_t *node, vec3_t validmins, vec3_t validmaxs ) +{ + portal_t *p, *next_portal; + int i, side; + + if( FBitSet( node->flags, FNODE_DETAIL )) + return false; + + ClearBounds( node->mins, node->maxs ); + + for( p = node->portals; p != NULL; p = next_portal ) + { + if( p->nodes[0] == node ) + side = 0; + else if( p->nodes[1] == node ) + side = 1; + else COM_FatalError( "CalcNodeBounds: mislinked portal\n" ); + + next_portal = p->next[side]; + WindingBounds( p->winding, node->mins, node->maxs, true ); + } + + if( FBitSet( node->flags, FNODE_LEAFPORTAL )) + return false; + + for( i = 0; i < 3; i++ ) + { + validmins[i] = Q_max( node->mins[i], -( 32768.0 + g_maxnode_size )); + validmaxs[i] = Q_min( node->maxs[i], ( 32768.0 + g_maxnode_size )); + } + + for( i = 0; i < 3; i++ ) + { + if( validmaxs[i] - validmins[i] <= ON_EPSILON ) + return false; + } + + for( i = 0; i < 3; i++ ) + { + if( validmaxs[i] - validmins[i] > g_maxnode_size + ON_EPSILON ) + return true; + } + + return false; +} + +/* +================== +FreeLeafSurfs +================== +*/ +void FreeLeafSurfs( node_t *leaf ) +{ + surface_t *surf, *snext; + face_t *f, *fnext; + + for( surf = leaf->surfaces; surf != NULL; surf = snext ) + { + snext = surf->next; + + for( f = surf->faces; f != NULL; f = fnext ) + { + fnext = f->next; + FreeFace( f ); + } + + FreeSurface( surf ); + } + + leaf->surfaces = NULL; +} + +/* +================== +FreeLeafBrushes +================== +*/ +static void FreeLeafBrushes( node_t *leaf ) +{ + brush_t *b, *next; + + for( b = leaf->detailbrushes; b != NULL; b = next ) + { + next = b->next; + FreeBrush( b ); + } + + leaf->detailbrushes = NULL; +} + +/* +================== +LinkNodeFaces + +Do a final merge attempt, then subdivide the faces to surface cache size if needed. +These are final faces that will be drawable in the game. +Copies of these faces are further chopped up into the leafs, but they will reference these originals. +================== +*/ +void LinkNodeFaces( node_t *node, surface_t *surf, bool subdivide ) +{ + face_t *f, *newf, **prevptr; + + // merge as much as possible + MergePlaneFaces( surf, subdivide ? g_merge_level : 1 ); + + // subdivide + prevptr = &surf->faces; + + while( subdivide ) + { + f = *prevptr; + if( !f ) break; + + SubdivideFace( f, prevptr ); + + f = *prevptr; + prevptr = &f->next; + } + + node->surfaces = NULL; + node->faces = NULL; + + // copy the faces to the node, and consider them the originals + for( f = surf->faces; f != NULL; f = f->next ) + { + dispatch_tree_faces++; + + if( f->facestyle == face_discardable ) + continue; + + // FIXME: we shouldn't check for CONTENTS_SKY here!!! + if( f->contents != CONTENTS_SOLID && f->contents != CONTENTS_SKY ) + { + newf = NewFaceFromFace( f ); + newf->w = CopyWinding( f->w ); + f->original = newf; + newf->next = node->faces; + node->faces = newf; + c_nodefaces++; + } + } + + if( g_report_progress ) + { + UpdatePacifier( (float)dispatch_tree_faces / total_tree_faces ); + } +} + +/* +================== +SetLeafContents + +Determines the contents of the leaf and creates the final list of +original faces that have some fragment inside this leaf +================== +*/ +void SetLeafContents( surface_t *planelist, node_t *leafnode ) +{ + int rank, r; + surface_t *surf; + + rank = -1; + + for( surf = planelist; surf != NULL; surf = surf->next ) + { + if( !surf->onnode ) + continue; + + for( face_t *f = surf->faces; f != NULL; f = f->next ) + { + if( f->detaillevel ) continue; + r = RankForContents( f->contents ); + rank = Q_max( rank, r ); + } + } + + leafnode->contents = ContentsForRank( rank ); +} + +static void MakeLeaf( node_t *leafnode ) +{ + int nummarkfaces; + surface_t *surf; + face_t *f; + + leafnode->planenum = PLANENUM_LEAF; + + if( leafnode->detailbrushes ) + SetBits( leafnode->flags, FNODE_DETAILCONTENTS ); + FreeLeafBrushes( leafnode ); + leafnode->detailbrushes = NULL; + + if( leafnode->boundsbrush ) + FreeBrush( leafnode->boundsbrush ); + leafnode->boundsbrush = NULL; + + if( !( FBitSet( leafnode->flags, FNODE_LEAFPORTAL ) && leafnode->contents == CONTENTS_SOLID )) + { + nummarkfaces = 0; + for (surf = leafnode->surfaces; surf; surf = surf->next ) + { + if( !surf->onnode ) + continue; + + for( f = surf->faces; f != NULL; f = f->next ) + { + if( f->original == NULL ) + { + // because it is not on node or its content is solid + continue; + } + + if( nummarkfaces == MAX_MAP_MARKSURFACES ) + COM_FatalError( "MAX_MAP_MARKSURFACES limit exceeded\n" ); + markfaces[nummarkfaces++] = f->original; + } + } + + markfaces[nummarkfaces] = NULL; // end marker + nummarkfaces++; + + leafnode->markfaces = (face_t **)Mem_Alloc( nummarkfaces * sizeof( *leafnode->markfaces )); + memcpy( leafnode->markfaces, markfaces, nummarkfaces * sizeof( *leafnode->markfaces )); + } + + FreeLeafSurfs( leafnode ); + leafnode->surfaces = NULL; +} + +int CalcSplitDetaillevel( const node_t *node ) +{ + int bestdetaillevel = -1; + surface_t *s; + + for( s = node->surfaces; s != NULL; s = s->next ) + { + if( s->onnode ) + continue; + + for( face_t *f = s->faces; f != NULL; f = f->next ) + { + if( f->facestyle == face_discardable ) + continue; + + if( bestdetaillevel == -1 || f->detaillevel < bestdetaillevel ) + bestdetaillevel = f->detaillevel; + } + } + + return bestdetaillevel; +} + +void FixDetaillevelForDiscardable( node_t *node, int detaillevel ) +{ + surface_t *s, **psnext; + face_t *f, **pfnext; + + // when we move on to the next detaillevel, some discardable faces of previous detail level remain not on node + // (because they are discardable). remove them now + for( psnext = &node->surfaces; s = *psnext, s != NULL; ) + { + if( s->onnode ) + { + psnext = &s->next; + continue; + } + + ASSERT( s->faces != NULL ); + + for( pfnext = &s->faces; f = *pfnext, f != NULL; ) + { + if( detaillevel == -1 || f->detaillevel < detaillevel ) + { + *pfnext = f->next; + FreeFace( f ); + } + else + { + pfnext = &f->next; + } + } + + if( !s->faces ) + { + *psnext = s->next; + FreeSurface( s ); + } + else + { + psnext = &s->next; + CalcSurfaceInfo( s ); + ASSERT( !( detaillevel == -1 || s->detaillevel < detaillevel )); + } + } +} + +/* +================== +BuildBspTree_r +================== +*/ +void BuildBspTree_r( node_t *node, bool subdivide ) +{ + vec3_t validmins, validmaxs; + surface_t *allsurfs; + bool midsplit; + surface_t *split; + + midsplit = CalcNodeBounds( node, validmins, validmaxs ); + + if( node->boundsbrush ) + { + CalcBrushBounds( node->boundsbrush, node->loosemins, node->loosemaxs ); + } + else + { + VectorFill( node->loosemins, BOGUS_RANGE ); + VectorFill( node->loosemaxs, -BOGUS_RANGE ); + } + + int splitdetaillevel = CalcSplitDetaillevel( node ); + FixDetaillevelForDiscardable( node, splitdetaillevel ); + + // select the partition plane + split = SelectPartition( node->surfaces, node, midsplit, splitdetaillevel, validmins, validmaxs ); + + if( !FBitSet( node->flags, FNODE_DETAIL ) && ( !split || split->detaillevel > 0 )) + { + SetBits( node->flags, FNODE_LEAFPORTAL ); + SetLeafContents( node->surfaces, node ); + + if( node->contents == CONTENTS_SOLID ) + split = NULL; + } + else + { + ClearBits( node->flags, FNODE_LEAFPORTAL ); + } + + if( !split ) + { + // this is a leaf node + MakeLeaf( node ); + return; + } + + split->onnode = node; // can't use again + node->planenum = split->planenum; + allsurfs = node->surfaces; + + // these are final polygons + LinkNodeFaces( node, split, subdivide ); + node->children[0] = AllocNode (); + node->children[1] = AllocNode (); + c_splitnodes++; + + if( split->detaillevel > 0 ) + SetBits( node->children[0]->flags, FNODE_DETAIL ); + + if( split->detaillevel > 0 ) + SetBits( node->children[1]->flags, FNODE_DETAIL ); + + // split all the polysurfaces into front and back lists + SplitNodeSurfaces( allsurfs, node ); + SplitNodeBrushes( node->detailbrushes, node ); + + if( node->boundsbrush ) + { + for( int k = 0; k < 2; k++ ) + { + brush_t *copy, *front, *back; + plane_t p; + + if( k == 0 ) + { + // front child + VectorCopy( g_mapplanes[split->planenum].normal, p.normal ); + p.dist = g_mapplanes[split->planenum].dist - BOUNDS_EXPANSION; + } + else + { + // back child + VectorNegate( g_mapplanes[split->planenum].normal, p.normal ); + p.dist = -g_mapplanes[split->planenum].dist - BOUNDS_EXPANSION; + } + + copy = NewBrushFromBrush( node->boundsbrush ); + SplitBrush( copy, &p, &front, &back ); + + if( back ) FreeBrush( back ); + if( !front ) MsgDev( D_WARN, "BuildBspTree_r: bounds was clipped away\n" ); + + node->children[k]->boundsbrush = front; + } + FreeBrush( node->boundsbrush ); + } + + node->boundsbrush = NULL; + + if( !split->detaillevel ) + { + // create the portal that seperates the two children + MakeNodePortal( node ); + + // carve the portals on the boundaries of the node + SplitNodePortals( node ); + } + + // recursively do the children + BuildBspTree_r( node->children[0], subdivide ); + BuildBspTree_r( node->children[1], subdivide ); +} + +/* +================== +SolidBSP + +Takes a chain of surfaces plus a split type, and +returns a bsp tree with faces off the nodes. + +The original surface chain will be completely freed. +================== +*/ +void SolidBSP( tree_t *tree, int modnum, int hullnum ) +{ + vec3_t brushmins, brushmaxs, size; + bool report = (modnum == 0); + double start, end; + int flags = 0; + vec_t maxnode; + + MsgDev( D_REPORT, "----- SolidBSP ----- (hull %i, model %i)\n", hullnum, modnum ); + + // calc the maxnode size based on world size + if( g_maxnode_size == DEFAULT_MAXNODE_SIZE ) + { + VectorSubtract( tree->maxs, tree->mins, size ); + maxnode = VectorMax( size ) / 8.0; // 8192 / 8 = 1024 + maxnode = Q_roundup( maxnode, 1024.0 ); + MsgDev( D_REPORT, "max node size %g\n", maxnode ); + g_maxnode_size = maxnode; + } + + tree->headnode = AllocNode (); + tree->headnode->detailbrushes = tree->detailbrushes; + tree->headnode->surfaces = tree->surfaces; + + if( !tree->surfaces || ( hullnum != 0 && g_noclip )) + { + // nothing at all to build + if( hullnum != 0 ) + { + tree->headnode->planenum = PLANENUM_LEAF; + tree->headnode->contents = CONTENTS_EMPTY; + SetBits( tree->headnode->flags, FNODE_LEAFPORTAL ); + } + else + { + tree->headnode->children[0] = AllocNode (); + tree->headnode->children[0]->planenum = PLANENUM_LEAF; + tree->headnode->children[0]->contents = CONTENTS_EMPTY; + tree->headnode->children[0]->markfaces = (face_t **)Mem_Alloc( sizeof( face_t * )); + SetBits( tree->headnode->children[0]->flags, FNODE_LEAFPORTAL ); + + tree->headnode->children[1] = AllocNode (); + tree->headnode->children[1]->planenum = PLANENUM_LEAF; + tree->headnode->children[1]->contents = CONTENTS_EMPTY; + tree->headnode->children[1]->markfaces = (face_t **)Mem_Alloc( sizeof( face_t * )); + SetBits( tree->headnode->children[1]->flags, FNODE_LEAFPORTAL ); + } + return; + } + + // calculate a bounding box for the entire model + for( int i = 0; i < 3; i++ ) + { + tree->headnode->mins[i] = tree->mins[i]; + tree->headnode->maxs[i] = tree->maxs[i]; + brushmins[i] = tree->mins[i] - SIDESPACE; + brushmaxs[i] = tree->maxs[i] + SIDESPACE; + } + + tree->headnode->boundsbrush = BrushFromBox( brushmins, brushmaxs ); + + c_unsplitted_faces = 0; + c_clipped_portals = 0; + c_splitnodes = 0; + c_nodefaces = 0; + c_leaffaces = 0; + + // generate six portals that enclose the entire world + MakeHeadnodePortals( tree->headnode, tree->mins, tree->maxs ); + g_report_progress = report; + + if( g_report_progress ) + { + // because we have mirror for each face + total_tree_faces = tree->numsurfaces; + dispatch_tree_faces = 0; + start = I_FloatTime(); + StartPacifier(); + } + + // + // recursively partition everything + // + BuildBspTree_r( tree->headnode, ( hullnum == 0 )); + + if( report ) + { + end = I_FloatTime (); + EndPacifier( end - start ); + if( c_clipped_portals ) MsgDev( D_WARN, "%i portals was clipped away\n", c_clipped_portals ); + if( c_unsplitted_faces ) MsgDev( D_WARN, "%i faces can't be a split\n", c_unsplitted_faces ); + } + + MsgDev( D_REPORT, "%5i split nodes\n", c_splitnodes ); + MsgDev( D_REPORT, "%5i node faces\n", c_nodefaces ); + MsgDev( D_REPORT, "%5i leaf faces\n", c_leaffaces ); +} \ No newline at end of file diff --git a/utils/p2bsp/surfaces.cpp b/utils/p2bsp/surfaces.cpp new file mode 100644 index 0000000..e423fe3 --- /dev/null +++ b/utils/p2bsp/surfaces.cpp @@ -0,0 +1,361 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// divide.h + +#include "bsp5.h" + +/* +a surface has all of the faces that could be drawn on a given plane + +the outside filling stage can remove some of them so a better bsp can be generated + +*/ + +int c_totalverts; +int c_uniqueverts; +int c_unsplitted_faces; + +/* +=============== +SubdivideFace + +If the face is >256 in either texture direction, carve a valid sized +piece off and insert the remainder in the next link +=============== +*/ +void SubdivideFace( face_t *f, face_t **prevptr ) +{ + face_t *front, *back, *next; + int max_surface_extent; + int subdivide_size; + int texture_step; + float lmvecs[2][4]; + vec_t mins, maxs; + vec_t v, extent; + plane_t plane; + dtexinfo_t *tex; + vec3_t temp; + + if( f->facestyle == face_hint || f->facestyle == face_discardable ) + return; // ideally these should have their tex_special flag set, so its here jic + + if( f->texturenum == -1 ) + return; + + // special (non-surface cached) faces don't need subdivision + tex = &g_texinfo[f->texturenum]; + + if( FBitSet( tex->flags, TEX_SPECIAL )) + return; + + // compute subdivision size + max_surface_extent = GetSurfaceExtent( tex ); + texture_step = GetTextureStep( tex ); + subdivide_size = ((max_surface_extent - 1) * texture_step); + + LightMatrixFromTexMatrix( tex, lmvecs ); + + for( int axis = 0; axis < 2; axis++ ) + { + while( 1 ) + { + mins = 999999; + maxs = -999999; + + for( int i = 0; i < f->w->numpoints; i++ ) + { + v = DotProduct( f->w->p[i], lmvecs[axis] ); + mins = Q_min( v, mins ); + maxs = Q_max( v, maxs ); + } + + extent = ceil( maxs ) - floor( mins ); +// extent = maxs - mins; + + if( extent <= subdivide_size ) + break; + + // split it + VectorCopy( lmvecs[axis], temp ); + v = VectorNormalize( temp ); + VectorCopy( temp, plane.normal ); + plane.dist = (mins + subdivide_size - texture_step) / v; + + next = f->next; + SplitFace( f, &plane, &front, &back ); +#if 0 + if( !front || !back ) + { + c_unsplitted_faces++; + printf( "can't split\n" ); + break; + } + + *prevptr = back; + back->next = front; + front->next = next; + f = back; +#else + f = next; + + if( front ) + { + front->next = f; + f = front; + } + + if( back ) + { + back->next = f; + f = back; + } + + *prevptr = f; + + if( !front || !back ) + { + c_unsplitted_faces++; + break; + } +#endif + } + } +} + +//=========================================================================== +#define MAX_HASH 4096 +#define MAX_HASH_NEIGHBORS 4 + +typedef struct hashvert_s +{ + struct hashvert_s *next; + vec3_t point; + int planenums[2]; + int numplanes; // for corner determination + int num; +} hashvert_t; + +static face_t *g_edgefaces[MAX_MAP_EDGES][2]; +static hashvert_t hvertex[MAX_MAP_VERTS]; +static hashvert_t *hashverts[MAX_HASH]; +static int firstmodeledge = 1; +static int h_numslots[3]; +static hashvert_t *hvert_p; +static vec3_t h_scale; +static vec3_t h_min; + +//============================================================================ +/* +=============== +InitHash +=============== +*/ +void InitHash( void ) +{ + vec_t volume; + vec_t scale; + vec3_t size; + + memset( hashverts, 0, sizeof( hashverts )); + VectorFill( h_min, -8000 ); + VectorFill( size, 16000 ); + + volume = size[0] * size[1]; + scale = sqrt( volume / MAX_HASH ); + + h_numslots[0] = (int)floor( size[0] / scale ); + h_numslots[1] = (int)floor( size[1] / scale ); + + while( h_numslots[0] * h_numslots[1] > MAX_HASH ) + { + h_numslots[0]--; + h_numslots[1]--; + } + + h_scale[0] = h_numslots[0] / size[0]; + h_scale[1] = h_numslots[1] / size[1]; + hvert_p = hvertex; +} + +/* +=============== +HashVec + +returned value: the one bucket that a new vertex may "write" into +returned hashneighbors: the buckets that we should "read" to check for an existing vertex +=============== +*/ +static int HashVec( const vec3_t vec, int *num, int *hash ) +{ + vec_t normalized[2]; + int h, i, x, y; + vec_t sdiff[2]; + int slot[2]; + + for( i = 0; i < 2; i++ ) + { + normalized[i] = h_scale[i] * (vec[i] - h_min[i]); + slot[i] = (int)floor( normalized[i] ); + sdiff[i] = normalized[i] - (vec_t)slot[i]; + + slot[i] = (slot[i] + h_numslots[i]) % h_numslots[i]; + slot[i] = (slot[i] + h_numslots[i]) % h_numslots[i]; // do it twice to handle negative values + } + + h = slot[0] * h_numslots[1] + slot[1]; + *num = 0; + + for( x = -1; x <= 1; x++ ) + { + if( x == -1 && sdiff[0] > h_scale[0] * ON_EPSILON || x == 1 && sdiff[0] < 1 - h_scale[0] * ON_EPSILON ) + continue; + + for( y = -1; y <= 1; y++ ) + { + if( y == -1 && sdiff[1] > h_scale[1] * ON_EPSILON || y == 1 && sdiff[1] < 1 - h_scale[1] * ON_EPSILON ) + continue; + + if( *num >= MAX_HASH_NEIGHBORS ) + COM_FatalError( "HashVec: internal error\n" ); + + hash[*num] = ((slot[0] + x + h_numslots[0]) % h_numslots[0]) * h_numslots[1] + + (slot[1] + y + h_numslots[1]) % h_numslots[1]; + (*num)++; + } + } + + return h; +} + +/* +=============== +GetVertex +=============== +*/ +static int GetVertex( const vec3_t in, const int planenum ) +{ + int hashneighbors[MAX_HASH_NEIGHBORS]; + int num_hashneighbors; + int i, h; + vec3_t vert; + hashvert_t *hv; + + for( i = 0; i < 3; i++ ) + { + if( fabs( in[i] - Q_round( in[i] )) < 0.001 ) + vert[i] = Q_round( in[i] ); + else vert[i] = in[i]; + } + + h = HashVec( vert, &num_hashneighbors, hashneighbors ); + + for( i = 0; i < num_hashneighbors; i++ ) + { + for( hv = hashverts[hashneighbors[i]]; hv != NULL; hv = hv->next ) + { + if( !VectorCompareEpsilon( hv->point, vert, ON_EPSILON )) + continue; + + // already known to be a corner + if( hv->numplanes == 3 ) + return hv->num; + + for( i = 0; i < hv->numplanes; i++ ) + { + // already know this plane + if( hv->planenums[i] == planenum ) + return hv->num; + } + + if( hv->numplanes != 2 ) + hv->planenums[hv->numplanes] = planenum; + hv->numplanes++; + + return hv->num; + } + } + + hv = hvert_p; + hv->numplanes = 1; + hv->planenums[0] = planenum; + hv->next = hashverts[h]; + hashverts[h] = hv; + VectorCopy( vert, hv->point ); + hv->num = g_numvertexes; + if( hv->num == MAX_MAP_VERTS ) + COM_FatalError( "MAX_MAP_VERTS limit exceeded\n" ); + hvert_p++; + + // emit a vertex + if( g_numvertexes == MAX_MAP_VERTS ) + COM_FatalError( "MAX_MAP_VERTS limit exceeded\n" ); + VectorCopy( vert, g_dvertexes[g_numvertexes].point ); + g_numvertexes++; + + return hv->num; +} + +//=========================================================================== + +/* +================== +GetEdge + +Don't allow four way edges +================== +*/ +int GetEdge( vec3_t p1, vec3_t p2, face_t *f ) +{ + int v1, v2; + dedge_t *edge; + int i; + + if( !f->contents ) + COM_FatalError( "GetEdge: CONTENTS_NONE\n" ); + + v1 = GetVertex( p1, f->planenum ); + v2 = GetVertex( p2, f->planenum ); + + for( i = firstmodeledge; i < g_numedges; i++ ) + { + edge = &g_dedges[i]; + + if( v1 == edge->v[1] && v2 == edge->v[0] && !g_edgefaces[i][1] && g_edgefaces[i][0]->contents == f->contents + && g_edgefaces[i][0]->planenum != ( f->planenum ^ 1 )) + { + g_edgefaces[i][1] = f; + return -i; + } + } + + // emit an edge + if( g_numedges >= MAX_MAP_EDGES ) + COM_FatalError( "MAX_MAP_EDGES limit exceeded\n" ); + edge = &g_dedges[g_numedges]; + g_numedges++; + + g_edgefaces[i][0] = f; + edge->v[0] = v1; + edge->v[1] = v2; + + return i; +} + +/* +================ +MakeFaceEdges +================ +*/ +void MakeFaceEdges( void ) +{ + firstmodeledge = g_numedges; // !!! +// InitHash(); +} \ No newline at end of file diff --git a/utils/p2bsp/tjunc.cpp b/utils/p2bsp/tjunc.cpp new file mode 100644 index 0000000..3ad70d3 --- /dev/null +++ b/utils/p2bsp/tjunc.cpp @@ -0,0 +1,578 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// tjunc.c + +#include "bsp5.h" + +#define MAX_VERTS_ON_SUPERFACE 8192 +#define MAX_HASH_NEIGHBORS 4 + +typedef struct wvert_s +{ + vec_t t; + struct wvert_s *prev; + struct wvert_s *next; +} wvert_t; + +typedef struct wedge_s +{ + struct wedge_s *next; + vec3_t dir; + vec3_t origin; + wvert_t head; +} wedge_t; + +typedef struct +{ + int numpoints; + vec3_t points[MAX_VERTS_ON_SUPERFACE]; + face_t original; +} superface_t; + +int maxwedges, maxwverts; +int numwedges, numwverts; +int c_degenerateEdges; +int c_degenerateFaces; +int c_tjuncs; +int c_tjuncfaces; +int c_rotated; + +wvert_t *wverts; +wedge_t *wedges; + + +void PrintFace( face_t *f ) +{ + if( f->w ) pw( f->w ); +} + +//============================================================================ + +#define NUM_HASH 1024 + +wedge_t *wedge_hash[NUM_HASH]; + +static vec3_t h_min, h_scale; +static int h_numslots[3]; + +static void InitHash( void ) +{ + vec3_t size; + vec_t volume; + vec_t scale; + + memset( wedge_hash, 0, sizeof( wedge_hash )); + VectorFill( h_min, -8000 ); + VectorFill( size, 16000 ); + + volume = size[0] * size[1]; + + scale = sqrt( volume / NUM_HASH ); + + h_numslots[0] = (int)floor( size[0] / scale ); + h_numslots[1] = (int)floor( size[1] / scale ); + + while( h_numslots[0] * h_numslots[1] > NUM_HASH ) + { + h_numslots[0]--; + h_numslots[1]--; + } + + h_scale[0] = h_numslots[0] / size[0]; + h_scale[1] = h_numslots[1] / size[1]; +} + +static int HashVec( const vec3_t vec, int *num, int *hash ) +{ + int h, i, x, y; + vec_t normalized[2]; + vec_t sdiff[2]; + int slot[2]; + + for( i = 0; i < 2; i++ ) + { + normalized[i] = h_scale[i] * (vec[i] - h_min[i]); + slot[i] = (int)floor( normalized[i] ); + sdiff[i] = normalized[i] - (vec_t)slot[i]; + + slot[i] = (slot[i] + h_numslots[i]) % h_numslots[i]; + slot[i] = (slot[i] + h_numslots[i]) % h_numslots[i]; // do it twice to handle negative values + } + + h = slot[0] * h_numslots[1] + slot[1]; + *num = 0; + + for( x = -1; x <= 1; x++ ) + { + if( x == -1 && sdiff[0] > h_scale[0] * ( 2 * ON_EPSILON ) || x == 1 && sdiff[0] < 1 - h_scale[0] * ( 2 * ON_EPSILON )) + continue; + + for( y = -1; y <= 1; y++ ) + { + if( y == -1 && sdiff[1] > h_scale[1] * ( 2 * ON_EPSILON ) || y == 1 && sdiff[1] < 1 - h_scale[1] * ( 2 * ON_EPSILON )) + continue; + + if( *num >= MAX_HASH_NEIGHBORS ) + COM_FatalError( "HashVec: internal error\n" ); + + hash[*num] = ((slot[0] + x + h_numslots[0]) % h_numslots[0]) * h_numslots[1] + + (slot[1] + y + h_numslots[1]) % h_numslots[1]; + (*num)++; + } + } + + return h; +} + +//============================================================================ +bool CanonicalVector( vec3_t vec ) +{ + if( VectorNormalize( vec )) + { + if( vec[0] > NORMAL_EPSILON ) + { + return true; + } + else if( vec[0] < -NORMAL_EPSILON ) + { + VectorNegate( vec, vec ); + return true; + } + else + { + vec[0] = 0; + } + + if( vec[1] > NORMAL_EPSILON ) + { + return true; + } + else if( vec[1] < -NORMAL_EPSILON ) + { + VectorNegate( vec, vec ); + return true; + } + else + { + vec[1] = 0; + } + + if( vec[2] > NORMAL_EPSILON ) + { + return true; + } + else if( vec[2] < -NORMAL_EPSILON ) + { + VectorNegate( vec, vec ); + return true; + } + else + { + vec[2] = 0; + } + return false; + } + + return false; +} + +wedge_t *FindEdge( vec3_t p1, vec3_t p2, vec_t *t1, vec_t *t2 ) +{ + vec3_t origin, dir; + int h, num_hashneighbors; + int hashneighbors[MAX_HASH_NEIGHBORS]; + vec_t temp; + wedge_t *w; + + VectorSubtract( p2, p1, dir ); + + // ignore degenerate edges + if( !CanonicalVector( dir )) + { + c_degenerateEdges++; + return NULL; + } + + *t1 = DotProduct( p1, dir ); + *t2 = DotProduct( p2, dir ); + + VectorMA( p1, -*t1, dir, origin ); + + if( *t1 > *t2 ) + { + temp = *t1; + *t1 = *t2; + *t2 = temp; + } + + h = HashVec( origin, &num_hashneighbors, hashneighbors ); + + for( int i = 0; i < num_hashneighbors; i++ ) + { + for( w = wedge_hash[hashneighbors[i]]; w; w = w->next ) + { + if( !VectorCompareEpsilon( w->origin, origin, EQUAL_EPSILON )) + continue; + + if( !VectorCompareEpsilon( w->dir, dir, NORMAL_EPSILON )) + continue; + + return w; + } + } + + if( numwedges == maxwedges ) + COM_FatalError( "FindEdge: numwedges == MAXWEDGES\n" ); + w = &wedges[numwedges]; + numwedges++; + + w->next = wedge_hash[h]; + wedge_hash[h] = w; + + VectorCopy( origin, w->origin ); + w->head.next = w->head.prev = &w->head; + VectorCopy( dir, w->dir ); + w->head.t = 99999; + return w; +} + +/* +=============== +AddVert + +=============== +*/ +void AddVert( wedge_t *w, vec_t t ) +{ + wvert_t *v, *newv; + + v = w->head.next; + do + { + if( fabs( v->t - t ) < T_EPSILON ) + return; + if( v->t > t ) + break; + v = v->next; + } while( 1 ); + + // insert a new wvert before v + if( numwverts == maxwverts ) + COM_FatalError( "AddVert: numwverts == MAXWVERTS\n" ); + newv = &wverts[numwverts]; + numwverts++; + + newv->t = t; + newv->next = v; + newv->prev = v->prev; + v->prev->next = newv; + v->prev = newv; +} + +/* +=============== +AddEdge + +=============== +*/ +void AddEdge( vec3_t p1, vec3_t p2 ) +{ + wedge_t *w; + vec_t t1, t2; + + w = FindEdge( p1, p2, &t1, &t2 ); + if( !w ) return; + + AddVert( w, t1 ); + AddVert( w, t2 ); +} + +/* +=============== +AddFaceEdges + +=============== +*/ +void AddFaceEdges( face_t *f ) +{ + winding_t *w = f->w; + int i, j; + + for( i = 0; i < w->numpoints; i++ ) + { + j = (i+1) % w->numpoints; + AddEdge( w->p[i], w->p[j] ); + } +} + +//============================================================================ + +superface_t *superface; +face_t *newlist; + +void FaceFromSuperface( face_t *original ) +{ + int firstcorner, lastcorner; + int i, numpts; + face_t *newf, *chain; + vec3_t dir, test; + vec_t v; + + chain = NULL; + while( 1 ) + { + if( superface->numpoints < 3 ) + { + c_degenerateFaces++; + return; + } + + if( superface->numpoints <= MAX_VERTS_ON_FACE ) + { + // the face is now small enough without more cutting + // so copy it back to the original + *original = superface->original; + original->w = CopyWinding( superface->numpoints, superface->points ); + original->original = chain; + original->next = newlist; + newlist = original; + return; + } + + c_tjuncfaces++; +restart: + // find the last corner + VectorSubtract( superface->points[superface->numpoints-1], superface->points[0], dir ); + VectorNormalize( dir ); + for( lastcorner = superface->numpoints - 1; lastcorner > 0; lastcorner-- ) + { + VectorSubtract (superface->points[lastcorner-1], superface->points[lastcorner], test ); + VectorNormalize (test); + v = DotProduct (test, dir); + + if( v < 1.0 - ON_EPSILON || v > 1.0 + ON_EPSILON ) + { + break; + } + } + + // find the first corner + VectorSubtract( superface->points[1], superface->points[0], dir ); + VectorNormalize( dir ); + for( firstcorner = 1; firstcorner < superface->numpoints - 1; firstcorner++ ) + { + VectorSubtract( superface->points[firstcorner+1], superface->points[firstcorner], test ); + VectorNormalize( test ); + v = DotProduct( test, dir ); + + if( v < 1.0 - ON_EPSILON || v > 1.0 + ON_EPSILON ) + break; + } + + if( firstcorner + 2 >= MAX_VERTS_ON_FACE ) + { + c_rotated++; + // rotate the point winding + VectorCopy( superface->points[0], test ); + for( i = 1; i < superface->numpoints; i++ ) + VectorCopy( superface->points[i], superface->points[i-1] ); + VectorCopy( test, superface->points[superface->numpoints-1] ); + goto restart; + } + + + // cut off as big a piece as possible, less than MAXPOINTS, + // and not past lastcorner + newf = NewFaceFromFace (&superface->original); + newf->original = chain; + chain = newf; + newf->next = newlist; + newlist = newf; + + if( superface->numpoints - firstcorner <= MAX_VERTS_ON_FACE ) + numpts = firstcorner + 2; + else if( lastcorner+2 < MAX_VERTS_ON_FACE && superface->numpoints - lastcorner <= MAX_VERTS_ON_FACE ) + numpts = lastcorner + 2; + else numpts = MAX_VERTS_ON_FACE; + + if( numpts < 3 ) + { + c_degenerateFaces++; + return; + } + + newf->w = CopyWinding( numpts, superface->points ); + + for( i = newf->w->numpoints - 1; i < superface->numpoints; i++ ) + VectorCopy( superface->points[i], superface->points[i - (newf->w->numpoints - 2)]); + superface->numpoints -= (newf->w->numpoints - 2); + } +} + +/* +=============== +FixFaceEdges + +=============== +*/ +void FixFaceEdges( face_t *f ) +{ + int i, j, k; + vec_t t1, t2; + wedge_t *w; + wvert_t *v; + + if( f->w->numpoints > MAX_VERTS_ON_SUPERFACE ) + COM_FatalError( "FixFaceEdges: f->w->numpoints > MAX_VERTS_ON_SUPERFACE\n" ); + + superface->original = *f; + superface->numpoints = f->w->numpoints; + memcpy( superface->points, f->w->p, sizeof( vec3_t ) * f->w->numpoints ); + FreeWinding( f->w ); + f->w = NULL; +restart: + for( i = 0; i < superface->numpoints; i++ ) + { + j = (i+1) % superface->numpoints; + + w = FindEdge( superface->points[i], superface->points[j], &t1, &t2 ); + if( !w ) continue; + + for( v = w->head.next; v->t < t1 + T_EPSILON; v = v->next ); + + if( v->t < t2 - T_EPSILON ) + { + c_tjuncs++; + + // insert a new vertex here + for( k = superface->numpoints; k > j; k-- ) + VectorCopy( superface->points[k-1], superface->points[k] ); + VectorMA( w->origin, v->t, w->dir, superface->points[j] ); + superface->numpoints++; + + if( superface->numpoints > MAX_VERTS_ON_SUPERFACE ) + COM_FatalError( "FixFaceEdges: superface->numpoints > MAX_VERTS_ON_SUPERFACE\n" ); + goto restart; + } + } + + // the face might be split into multiple faces because of too many edges + FaceFromSuperface( f ); +} + +//============================================================================ + +static void tjunc_count_r( node_t *node ) +{ + face_t *f; + + if( node->planenum == PLANENUM_LEAF ) + return; + + for( f = node->faces; f != NULL; f = f->next ) + maxwedges += f->w->numpoints; + + tjunc_count_r( node->children[0] ); + tjunc_count_r( node->children[1] ); +} + +void tjunc_find_r( node_t *node ) +{ + face_t *f; + + if( node->planenum == PLANENUM_LEAF ) + return; + + for( f = node->faces; f != NULL; f = f->next ) + AddFaceEdges( f ); + + tjunc_find_r( node->children[0] ); + tjunc_find_r( node->children[1] ); +} + +void tjunc_fix_r( node_t *node ) +{ + face_t *f, *next; + + if( node->planenum == PLANENUM_LEAF ) + return; + + newlist = NULL; + + for( f = node->faces; f != NULL; f = next ) + { + next = f->next; + FixFaceEdges( f ); + } + + node->faces = newlist; + + tjunc_fix_r( node->children[0] ); + tjunc_fix_r( node->children[1] ); +} + +/* +=========== +tjunc + +=========== +*/ +void tjunc( node_t *headnode, bool worldmodel ) +{ + double start, end; + char str[64]; + + if( g_notjunc ) return; + + MsgDev( D_REPORT, "---- tjunc ----\n"); + start = I_FloatTime (); + InitHash(); + + maxwedges = maxwverts = 0; + tjunc_count_r( headnode ); + maxwverts = maxwedges * 2; + + // alloc space for work + wverts = (wvert_t *)Mem_Alloc( sizeof( *wverts ) * maxwverts ); + wedges = (wedge_t *)Mem_Alloc( sizeof( *wedges ) * maxwedges ); + superface = (superface_t *)Mem_Alloc( sizeof( superface_t )); + numwedges = numwverts = 0; + + // + // identify all points on common edges + // + tjunc_find_r( headnode ); + + MsgDev( D_REPORT, "%i world edges %i edge points\n", numwedges, numwverts ); + + // + // add extra vertexes on edges where needed + // + c_tjuncs = c_tjuncfaces = c_degenerateEdges = c_degenerateFaces = c_rotated = 0; + + tjunc_fix_r( headnode ); + + Mem_Free( wverts ); + Mem_Free( wedges ); + Mem_Free( superface ); + superface = NULL; + + MsgDev( D_REPORT, "%i degenerate edges\n", c_degenerateEdges ); + MsgDev( D_REPORT, "%i degenerate faces\n", c_degenerateFaces ); + MsgDev( D_REPORT, "%i edges added by tjunctions\n", c_tjuncs ); + MsgDev( D_REPORT, "%i faces added by tjunctions\n", c_tjuncfaces ); + MsgDev( D_REPORT, "%i naturally ordered\n", c_tjuncfaces - c_rotated ); + MsgDev( D_REPORT, "%i rotated orders\n", c_rotated ); + + end = I_FloatTime (); + Q_timestring((int)( end - start ), str ); + if( worldmodel ) MsgDev( D_INFO, "t-junction: %s elapsed\n", str ); +} \ No newline at end of file diff --git a/utils/p2bsp/tree.cpp b/utils/p2bsp/tree.cpp new file mode 100644 index 0000000..3eb06b4 --- /dev/null +++ b/utils/p2bsp/tree.cpp @@ -0,0 +1,263 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include "bsp5.h" + +//=========================================================================== + +/* +=========== +AllocTree +=========== +*/ +tree_t *AllocTree( void ) +{ + tree_t *t; + + t = (tree_t *)Mem_Alloc( sizeof( tree_t ), C_BSPTREE ); + ClearBounds( t->mins, t->maxs ); + + return t; +} + +/* +=========== +FreeTree +=========== +*/ +void FreeTree( tree_t *t ) +{ + Mem_Free( t, C_BSPTREE ); +} + +// ===================================================================================== +// CheckFaceForHint +// Returns true if the passed face is facetype hint +// ===================================================================================== +bool CheckFaceForHint( const face_t *f ) +{ + const char *name = GetTextureByTexinfo( f->texturenum ); + + if( !Q_strnicmp( name, "HINT", 4 )) + return true; + return false; +} + +// ===================================================================================== +// CheckFaceForHint +// Returns true if the passed face is facetype solidhint +// ===================================================================================== +bool CheckFaceForDiscardable( const face_t *f ) +{ + const char *name = GetTextureByTexinfo( f->texturenum ); + + if( !Q_strnicmp( name, "SOLIDHINT", 9 )) + return true; + return false; +} + +static facestyle_e SetFaceType( face_t *f ) +{ + if( CheckFaceForHint( f )) + f->facestyle = face_hint; + else if( CheckFaceForDiscardable( f )) + f->facestyle = face_discardable; + return f->facestyle; +} + +/* +=============== +SurflistFromValidFaces +=============== +*/ +void MakeSurflistFromValidFaces( tree_t *tree ) +{ + face_t *f, *next; + surface_t *n; + + tree->surfaces = NULL; + ClearBounds( tree->mins, tree->maxs ); + tree->numfaces = 0; + + // grab planes from both sides + for( int i = 0; i < g_nummapplanes; i += 2 ) + { + if( !tree->validfaces[i+0] && !tree->validfaces[i+1] ) + continue; + + n = AllocSurface(); + n->next = tree->surfaces; + n->detaillevel = -1; + tree->surfaces = n; + tree->numsurfaces++; + n->planenum = i; + n->faces = NULL; + + for( f = tree->validfaces[i+0]; f != NULL; f = next ) + { + AddFaceToBounds( f, n->mins, n->maxs ); + + if( n->detaillevel == -1 || f->detaillevel < n->detaillevel ) + n->detaillevel = f->detaillevel; + + next = f->next; + f->next = n->faces; + n->faces = f; + tree->numfaces++; + } + + for( f = tree->validfaces[i+1]; f != NULL; f = next ) + { + AddFaceToBounds( f, n->mins, n->maxs ); + + if( n->detaillevel == -1 || f->detaillevel < n->detaillevel ) + n->detaillevel = f->detaillevel; + + next = f->next; + f->next = n->faces; + n->faces = f; + tree->numfaces++; + } + + // update tree size + AddPointToBounds( n->mins, tree->mins, tree->maxs ); + AddPointToBounds( n->maxs, tree->mins, tree->maxs ); + + tree->validfaces[i+0] = NULL; + tree->validfaces[i+1] = NULL; + } + + // merge polygons + MergeTreeFaces( tree ); +} + +/* +=============== +MakeTreeFromHullFaces +=============== +*/ +tree_t *MakeTreeFromHullFaces( FILE *polyfile, FILE *brushfile ) +{ + int texinfo, contents, numpoints; + int r, planenum, detaillevel; + tree_t *tree = NULL; + bool skipface; + int line = 0; + double v[3]; + face_t *f; + + // read in the polygons + while( 1 ) + { + r = fscanf( polyfile, "%i %i %i %i %i\n", &detaillevel, &planenum, &texinfo, &contents, &numpoints ); + skipface = false; + line++; + + if( r == 0 || r == -1 ) + return NULL; + + // alloc a new tree for model + if( !tree ) tree = AllocTree(); + + if( planenum == -1 ) // end of model + break; + + if( r != 5 ) + COM_FatalError( "ReadSurfs (line %i): scanf failure %d != 5\n", line, r ); + + if( planenum > g_nummapplanes ) + COM_FatalError( "ReadSurfs (line %i): %i > numplanes\n", line, planenum ); + + if( texinfo > g_numtexinfo ) + COM_FatalError( "ReadSurfs (line %i): %i > numtexinfo\n", line, texinfo ); + + if( detaillevel < 0 ) + COM_FatalError( "ReadSurfs (line %i): detaillevel %i < 0", line, detaillevel); + + if( !Q_stricmp( GetTextureByTexinfo( texinfo ), "SKIP" )) + skipface = true; + + if( !skipface ) + { + f = AllocFace (); + f->planenum = planenum; + f->texturenum = texinfo; + f->contents = contents; + f->detaillevel = detaillevel; + f->w = AllocWinding( numpoints ); + f->w->numpoints = numpoints; + f->next = tree->validfaces[planenum]; + tree->validfaces[planenum] = f; + SetFaceType( f ); + } + + // restore winding + for( int i = 0; i < numpoints; i++ ) + { + r = fscanf( polyfile, "%lf %lf %lf\n", &v[0], &v[1], &v[2] ); + if( !skipface ) VectorCopy( v, f->w->p[i] ); + line++; + } + + fscanf( polyfile, "\n" ); + line++; + } + + MakeSurflistFromValidFaces( tree ); + + // time to read detailbrushes + tree->detailbrushes = ReadBrushes( brushfile ); + + return tree; +} + +/* +=============== +TreeProcessModel +=============== +*/ +tree_t *TreeProcessModel( tree_t *tree, int modnum, int hullnum ) +{ + bool worldmodel = (modnum == 0); + + if( hullnum == 0 ) + { + // if not the world, make a good tree first + // the world is just going to make a bad tree + // because the outside filling will force a regeneration later + SolidBSP( tree, modnum, hullnum ); // SolidBSP generates a node tree + + if( worldmodel && !g_nofill ) + { + FillInside( tree->headnode ); + FillOutside( tree, hullnum, true ); + } + + FreeTreePortals( tree ); + + // fix tjunctions + tjunc( tree->headnode, worldmodel ); + + MakeFaceEdges(); + } + else + { + // clipping tree are simply + SolidBSP( tree, modnum, hullnum ); + + // assume non-world bmodels are simple + if( worldmodel && !g_nofill && !g_noclip ) + FillOutside( tree, hullnum, false ); + + FreeTreePortals( tree ); + } + + return tree; +} \ No newline at end of file diff --git a/utils/p2bsp/writebsp.cpp b/utils/p2bsp/writebsp.cpp new file mode 100644 index 0000000..fae52d6 --- /dev/null +++ b/utils/p2bsp/writebsp.cpp @@ -0,0 +1,763 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + + +#include "bsp5.h" + +//=========================================================================== +plane_t g_mapplanes[MAX_INTERNAL_MAP_PLANES]; +int g_mappedtexinfo[MAX_INTERNAL_MAP_TEXINFO]; +dtexinfo_t g_maptexinfo[MAX_MAP_TEXINFO]; +int g_nummaptexinfo; +int g_nummapplanes; + +/* +============================================================================= + +PLANE FINDING + +============================================================================= +*/ +/* +================ +PlaneEqual +================ +*/ +static bool PlaneEqual( const plane_t *p, const vec3_t normal, vec_t dist ) +{ + vec_t t; + + if( -DIST_EPSILON < ( t = p->dist - dist ) && t < DIST_EPSILON + && -DIR_EPSILON < ( t = p->normal[0] - normal[0] ) && t < DIR_EPSILON + && -DIR_EPSILON < ( t = p->normal[1] - normal[1] ) && t < DIR_EPSILON + && -DIR_EPSILON < ( t = p->normal[2] - normal[2] ) && t < DIR_EPSILON ) + return true; + + return false; +} + +/* +================ +CreateNewFloatPlane +================ +*/ +static int CreateNewFloatPlane( const vec3_t srcnormal, vec_t dist ) +{ + plane_t *p0, *p1, temp; + vec3_t normal; + int type; + + if( VectorLength( srcnormal ) < 0.5 ) + return -1; + + // create a new plane + if(( g_nummapplanes + 2 ) > MAX_INTERNAL_MAP_PLANES ) + COM_FatalError( "MAX_INTERNAL_MAP_PLANES limit exceeded\n" ); + + p0 = &g_mapplanes[g_nummapplanes+0]; + p1 = &g_mapplanes[g_nummapplanes+1]; + + // snap plane normal + VectorCopy( srcnormal, normal ); + type = SnapNormal( normal ); + + // only snap distance if the normal is an axis. Otherwise there + // is nothing "natural" about snapping the distance to an integer. + if( VectorIsOnAxis( normal ) && fabs( dist - Q_rint( dist )) < DIST_EPSILON ) + dist = Q_rint( dist ); // catch -0.0 + + VectorCopy( normal, p0->normal ); + VectorNegate( normal, p1->normal ); + p0->dist = dist; + p1->dist = -dist; + p0->type = type; + p1->type = type; + g_nummapplanes += 2; + + // always put axial planes facing positive first + if( normal[type % 3] < 0 ) + { + // flip order + temp = *p0; + *p0 = *p1; + *p1 = temp; + + return g_nummapplanes - 1; + } + + return g_nummapplanes - 2; +} + +/* +============= +FindFloatPlane + +============= +*/ +int FindFloatPlane( const vec3_t normal, vec_t dist ) +{ + vec_t srcdist = dist; + plane_t *p; + + // NOTE: bsp only alloc a few planes for world portalize + // so linear search doesn't hit by perfomance + for( int i = 0; i < g_nummapplanes; i++ ) + { + p = &g_mapplanes[i]; + + if( PlaneEqual( p, normal, dist )) + return p - g_mapplanes; + } + + // allocate a new two opposite planes + return CreateNewFloatPlane( normal, srcdist ); +} + +/* +================== +EmitPlane + +emit a mapped plane +================== +*/ +int EmitPlane( int planenum, bool check ) +{ + plane_t *plane = &g_mapplanes[planenum & (~1)]; + + if( plane->outplanenum == -1 ) + { + int i = g_numplanes; + dplane_t *dplane = &g_dplanes[i]; + + if( check && g_numplanes == MAX_MAP_PLANES ) + COM_FatalError( "MAX_MAP_PLANES limit exceeded\n" ); + + VectorCopy( plane->normal, dplane->normal ); + dplane->dist = plane->dist; + dplane->type = plane->type; + plane->outplanenum = g_numplanes++; + } + + return plane->outplanenum; +} + +/* +================== +EmitTexinfo + +emit a texinfo +================== +*/ +int EmitTexinfo( int texinfo ) +{ + if( texinfo == -1 ) COM_FatalError( "EmitTexinfo: texinfo == -1\n" ); + + dtexinfo_t *dtexinfo = &g_texinfo[texinfo]; + + if( g_mappedtexinfo[texinfo] == -1 ) + { + int i = g_nummaptexinfo; + dtexinfo_t *mtexinfo = &g_maptexinfo[i]; + + if( g_nummaptexinfo == MAX_MAP_TEXINFO ) + COM_FatalError( "MAX_MAP_TEXINFO limit exceeded\n" ); + + memcpy( mtexinfo, dtexinfo, sizeof( dtexinfo_t )); + g_mappedtexinfo[texinfo] = g_nummaptexinfo++; + } + + return g_mappedtexinfo[texinfo]; +} + +/* +================== +EmitClipNodes_r +================== +*/ +static int EmitClipNodes_r( node_t *node, const node_t *portalleaf ) +{ + int i, c; + dclipnode32_t *cn; + int num; + + if( FBitSet( node->flags, FNODE_LEAFPORTAL )) + { + if( node->contents == CONTENTS_SOLID ) + return CONTENTS_SOLID; + else portalleaf = node; + } + + if( node->planenum == PLANENUM_LEAF ) + { + if( FBitSet( node->flags, FNODE_DETAILCONTENTS )) + num = CONTENTS_SOLID; + else num = portalleaf->contents; + + return num; + } + + // emit a clipnode + if( g_numclipnodes == MAX_MAP_CLIPNODES ) + MsgAnim( D_INFO, "^3=== MAX_MAP_CLIPNODES is exceeded. Map will not run under GoldSource ===\r" ); + + if( g_numclipnodes == MAX_MAP_CLIPNODES32 ) + COM_FatalError( "MAX_MAP_CLIPNODES32 limit exceeded\n" ); + + c = g_numclipnodes; + cn = &g_dclipnodes32[g_numclipnodes++]; + + if( node->planenum & 1 ) + COM_FatalError( "WriteClipNodes_r: odd planenum\n" ); + + cn->planenum = EmitPlane( node->planenum, false ); + + for( i = 0; i < 2; i++ ) + cn->children[i] = EmitClipNodes_r( node->children[i], portalleaf ); + + return c; +} + +//=========================================================================== +/* +================== +EmitLeaf +================== +*/ +static int EmitLeaf( node_t *node, const node_t *portalleaf ) +{ + face_t **fp, *f; + dleaf_t *leaf_p; + int leafnum = g_numleafs; + vec3_t mins, maxs; + + if( g_numleafs == MAX_MAP_LEAFS ) + COM_FatalError( "MAX_MAP_LEAFS limit exceeded\n" ); + + leaf_p = &g_dleafs[g_numleafs++]; // emit a leaf + leaf_p->contents = portalleaf->contents; + leaf_p->visofs = -1; // no vis info yet + + if( FBitSet( node->flags, FNODE_DETAIL )) + { + // intersect its loose bounds with the strict bounds of its parent portalleaf + VectorCompareMax( portalleaf->mins, node->loosemins, mins ); + VectorCompareMin( portalleaf->maxs, node->loosemaxs, maxs ); + } + else + { + VectorCopy( node->mins, mins ); + VectorCopy( node->maxs, maxs ); + } + + // write bounding box info + for( int k = 0; k < 3; k++ ) + { + leaf_p->mins[k] = (short)bound( -32767, (int)mins[k], 32767 ); + leaf_p->maxs[k] = (short)bound( -32767, (int)maxs[k], 32767 ); + } + + // write the marksurfaces + leaf_p->firstmarksurface = g_nummarksurfaces; + + for( fp = node->markfaces; *fp != NULL; fp++ ) + { + for( f = *fp; f != NULL; f = f->original ) + { + if( f->outputnumber == -1 ) + continue; + + // grab tjunction split faces + if( g_nummarksurfaces == MAX_MAP_MARKSURFACES ) + COM_FatalError( "MAX_MAP_MARKSURFACES limit exceeded\n" ); + g_dmarksurfaces[g_nummarksurfaces++] = f->outputnumber; // emit a marksurface + } + } + + leaf_p->nummarksurfaces = g_nummarksurfaces - leaf_p->firstmarksurface; + + return leafnum; +} + +/* +================== +CheckNullFace +================== +*/ +static bool CheckNullFace( face_t *f ) +{ + // bad winding + if( !f->w || f->w->numpoints < 3 ) + { + MsgDev( D_ERROR, "degenerated face\n" ); + return true; + } + + // no texinfo or face is unreferenced + if( f->texturenum == -1 || f->referenced == 0 ) + return true; + + // discardable contents + if( f->facestyle == face_hint || f->facestyle == face_discardable ) + return true; + + return false; +} + +/* +================== +WriteFace +================== +*/ +static void WriteFace( face_t *f ) +{ + dface_t *df; + winding_t *w = f->w; + int i, e; + + if( CheckNullFace( f )) + { + f->outputnumber = -1; + return; // discardable face + } + + f->outputnumber = g_numfaces; + + df = &g_dfaces[g_numfaces]; + + if( g_numfaces >= MAX_MAP_FACES ) + COM_FatalError( "MAX_MAP_FACES limit exceeded\n" ); + g_numfaces++; + + df->planenum = EmitPlane( f->planenum, true ); + df->side = f->planenum & 1; // odd-even + df->firstedge = g_numsurfedges; + df->numedges = f->numedges; + df->texinfo = EmitTexinfo( f->texturenum ); + + for( i = 0; i < f->numedges; i++ ) + { + e = f->outputedges[i]; + if( g_numsurfedges >= MAX_MAP_SURFEDGES ) + COM_FatalError( "MAX_MAP_SURFEDGES limit exceeded\n" ); + g_dsurfedges[g_numsurfedges] = e; + g_numsurfedges++; + } + + Mem_Free( f->outputedges ); + f->outputedges = NULL; +} + +/* +================== +EmitDrawNodes_r +================== +*/ +static int EmitDrawNodes_r( node_t *node, const node_t *portalleaf ) +{ + dnode_t *n; + face_t *f; + int nodenum = g_numnodes; + vec3_t mins, maxs; + + if( FBitSet( node->flags, FNODE_LEAFPORTAL )) + { + if( node->contents == CONTENTS_SOLID ) + return -1; + else portalleaf = node; + } + + if( node->planenum == PLANENUM_LEAF ) + { + if( FBitSet( node->flags, FNODE_DETAILCONTENTS )) + { + Mem_Free( node->markfaces ); + node->markfaces = NULL; + return -1; + } + else + { + int leafnum = EmitLeaf( node, portalleaf ); + return -1 - leafnum; + } + } + + if( g_numnodes == MAX_MAP_NODES ) + COM_FatalError( "MAX_MAP_NODES limit exceeded\n" ); + + n = &g_dnodes[g_numnodes++]; // emit a node + + if( FBitSet( node->flags, FNODE_DETAIL )) + { + // intersect its loose bounds with the strict bounds of its parent portalleaf + VectorCompareMax( portalleaf->mins, node->loosemins, mins ); + VectorCompareMin( portalleaf->maxs, node->loosemaxs, maxs ); + } + else + { + VectorCopy( node->mins, mins ); + VectorCopy( node->maxs, maxs ); + } + + // write bounding box info + for( int k = 0; k < 3; k++ ) + { + n->mins[k] = (short)bound( -32767, (int)mins[k], 32767 ); + n->maxs[k] = (short)bound( -32767, (int)maxs[k], 32767 ); + } + + if( node->planenum & 1 ) + COM_FatalError( "EmitDrawNodes_r: odd planenum\n" ); + + n->planenum = EmitPlane( node->planenum, true ); + n->firstface = g_numfaces; + + for( f = node->faces; f != NULL; f = f->next ) + WriteFace( f ); + + n->numfaces = g_numfaces - n->firstface; + + // recursively output the other nodes + for( int i = 0; i < 2; i++ ) + n->children[i] = EmitDrawNodes_r( node->children[i], portalleaf ); + + return nodenum; +} + +/* +=========== +FreeDrawNodes_r +=========== +*/ +static void FreeDrawNodes_r( node_t *node ) +{ + face_t *f, *next; + + for( int i = 0; i < 2; i++ ) + { + // NOTE: optimized tree converts some nodes to leafs + // but leave childrens are valid, so we can throw mem correctly + if( node->children[i] != NULL ) + FreeDrawNodes_r( node->children[i] ); + } + + if( node->surfaces ) + { + FreeLeafSurfs( node ); + node->surfaces = NULL; + } + + if( !node->children[0] && !node->children[1] ) + { + // real leaf + Mem_Free( node->markfaces ); + node->markfaces = NULL; + } + else + { + // free the faces on the node + for( f = node->faces; f != NULL; f = next ) + { + next = f->next; + FreeFace( f ); + } + } + + FreeNode( node ); +} + +/* +=========== +CalcModelBoundBox +=========== +*/ +static void CalcModelBoundBox( dmodel_t *bmod, tree_t *tree, int hullnum, int modnum ) +{ + vec3_t mins, maxs; + int i; + + if( !tree->surfaces || ( tree->mins[0] > tree->maxs[0] )) + { + MsgDev( D_REPORT, "model %d hull %d empty\n", modnum, hullnum ); + } + else + { + if( hullnum && ( bmod->mins[0] <= bmod->maxs[0] )) + return; // bbox is computed + + VectorSubtract( tree->mins, g_hull_size[0][0], mins ); + VectorSubtract( tree->maxs, g_hull_size[0][1], maxs ); + + for( i = 0; i < 3; i++ ) + { + if( mins[i] > maxs[i] ) + { + vec_t tmp = (mins[i] + maxs[i]) * 0.5; + mins[i] = maxs[i] = tmp; + } + } + + for( i = 0; i < 3; i++ ) + { + bmod->maxs[i] = Q_max( bmod->maxs[i], maxs[i] ); + bmod->mins[i] = Q_min( bmod->mins[i], mins[i] ); + } + } +} + +/* +=========== +FaceOutputEdges +=========== +*/ +static void FaceOutputEdges( face_t *f ) +{ + if( CheckNullFace( f )) + return; + + f->outputedges = (int *)Mem_Alloc( f->w->numpoints * sizeof( int )); + f->numedges = f->w->numpoints; + + for( int i = 0; i < f->w->numpoints; i++ ) + { + f->outputedges[i] = GetEdge( f->w->p[i], f->w->p[(i + 1) % f->w->numpoints], f ); + } +} + +/* +=========== +OutputEdges_r +=========== +*/ +static int OutputEdges_r( node_t *node, int detaillevel ) +{ + int next = -1; + + if( node->planenum == PLANENUM_LEAF ) + return next; + + for( face_t *f = node->faces; f != NULL; f = f->next ) + { + if( f->detaillevel > detaillevel ) + { + if( next == -1 ? true : f->detaillevel < next ) + { + next = f->detaillevel; + } + } + + if( f->detaillevel == detaillevel ) + FaceOutputEdges( f ); + } + + for( int i = 0; i < 2; i++ ) + { + int r = OutputEdges_r( node->children[i], detaillevel ); + + if( r == -1 ? false : next == -1 ? true : r < next ) + next = r; + } + + return next; +} + +/* +=========== +RemoveCoveredFaces_r +=========== +*/ +static void RemoveCoveredFaces_r( node_t *node ) +{ + if( FBitSet( node->flags, FNODE_LEAFPORTAL )) + { + if( node->contents == CONTENTS_SOLID ) + { + return; // stop here, don't go deeper into children + } + } + + if( node->planenum == PLANENUM_LEAF ) + { + // this is a leaf + if( FBitSet( node->flags, FNODE_DETAILCONTENTS )) + { + return; + } + else + { + for( face_t **fp = node->markfaces; *fp != NULL; fp++ ) + { + for( face_t *f = *fp; f != NULL; f = f->original ) // for each tjunc subface + { + f->referenced++; // mark the face as referenced + } + } + } + return; + } + + // this is a node + for( face_t *f = node->faces; f != NULL; f = f->next ) + f->referenced = 0; // clear the mark + + RemoveCoveredFaces_r( node->children[0] ); + RemoveCoveredFaces_r( node->children[1] ); +} + +/* +================== +EmitDrawNodes +================== +*/ +void EmitDrawNodes( tree_t *tree ) +{ + int firstleaf; + int l, next; + vec3_t origin; + dmodel_t *bm; + + MsgDev( D_REPORT, "--- EmitDrawNodes ---\n" ); + + if( g_nummodels == MAX_MAP_MODELS ) + COM_FatalError( "MAX_MAP_MODELS limit exceeded\n" ); + + bm = &g_dmodels[g_nummodels++]; // emit a model + VectorFill( bm->mins, 999999 ); + VectorFill( bm->maxs, -999999 ); + bm->headnode[0] = g_numnodes; + bm->firstface = g_numfaces; + firstleaf = g_numleafs; + + // fill "referenced" value + RemoveCoveredFaces_r( tree->headnode ); + + for( l = 0; l != -1; l = next ) + next = OutputEdges_r( tree->headnode, l ); + + EmitDrawNodes_r( tree->headnode, NULL ); + + bm->numfaces = g_numfaces - bm->firstface; + bm->visleafs = g_numleafs - firstleaf; + + CalcModelBoundBox( bm, tree, 0, g_nummodels - 1 ); + + // g-cont. copy origin from entity settings into dmodel_t struct + entity_t *ent = EntityForModel( g_nummodels - 1 ); + GetVectorForKey( ent, "origin", origin ); + VectorCopy( origin, bm->origin ); + + FreeDrawNodes_r( tree->headnode ); + tree->headnode = NULL; +} + +/* +================== +EmitClipNodes + +Called after the clipping hull is completed. Generates a disk format +representation and frees the original memory. +================== +*/ +void EmitClipNodes( tree_t *tree, int modnum, int hullnum ) +{ + dmodel_t *bm; + + MsgDev( D_REPORT, "--- EmitClipNodes ---\n" ); + + bm = &g_dmodels[modnum]; // emit a model + + // if noclip is enabled + if( tree->headnode->planenum == PLANENUM_LEAF ) + bm->headnode[hullnum] = CONTENTS_EMPTY; + else bm->headnode[hullnum] = g_numclipnodes; + + EmitClipNodes_r( tree->headnode, NULL ); + CalcModelBoundBox( bm, tree, hullnum, modnum ); + + FreeDrawNodes_r( tree->headnode ); + tree->headnode = NULL; +} + +/* +================== +EmitVertex +================== +*/ +int EmitVertex( const vec3_t point ) +{ + dvertex_t *vert; + + if( g_numvertexes == MAX_MAP_VERTS ) + COM_FatalError( "MAX_MAP_VERTS limit exceeded\n" ); + + for( int i = 0; i < g_numvertexes; i++ ) + { + if( VectorCompareEpsilon2( point, g_dvertexes[i].point, EQUAL_EPSILON )) + return i; + } + + vert = &g_dvertexes[g_numvertexes++]; // emit a new vertex + VectorCopy( point, vert->point ); + + return i; +} + +//=========================================================================== + +/* +================== +BeginBSPFile +================== +*/ +void BeginBSPFile (void) +{ + // these values may actually be initialized + // if the file existed when loaded, so clear them explicitly + g_nummodels = 0; + g_numfaces = 0; + g_numnodes = 0; + g_numclipnodes = 0; + g_numvertexes = 0; + g_nummarksurfaces = 0; + g_numsurfedges = 0; + g_numplanes = 0; + + // edge 0 is not used, because 0 can't be negated + g_numedges = 1; + + // leaf 0 is common solid with no faces + g_numleafs = 1; + g_dleafs[0].contents = CONTENTS_SOLID; + + memset( g_mappedtexinfo, -1, sizeof( g_mappedtexinfo )); + + InitHash (); +} + + +/* +================== +FinishBSPFile +================== +*/ +void FinishBSPFile( const char *source ) +{ + MsgDev( D_REPORT, "--- FinishBSPFile ---\n" ); + + memcpy( g_texinfo, g_maptexinfo, sizeof( dtexinfo_t ) * g_nummaptexinfo ); + MsgDev( D_REPORT, "total texinfo %d, output texinfo %d\n", g_numtexinfo, g_nummaptexinfo ); + g_numtexinfo = g_nummaptexinfo; + + MsgDev( D_REPORT, "total planes %d, output planes %d\n", g_nummapplanes, g_numplanes ); + + if( GetDeveloperLevel() >= D_REPORT ) + PrintBSPFileSizes (); + + WriteBSPFile( source ); +} \ No newline at end of file diff --git a/utils/p2csg/aselib.cpp b/utils/p2csg/aselib.cpp new file mode 100644 index 0000000..0a5f0cf --- /dev/null +++ b/utils/p2csg/aselib.cpp @@ -0,0 +1,819 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "csg.h" +#include "aselib.h" + +#define MAX_ASE_MATERIALS 1024 +#define MAX_ASE_OBJECTS 2048 +#define MAX_ASE_ANIMATIONS 32 +#define MAX_ASE_ANIMATION_FRAMES 32 + +typedef struct +{ + float x, y, z; + float nx, ny, nz; + float s, t; +} aseVertex_t; + +typedef struct +{ + float s, t; +} aseTVertex_t; + +typedef int aseFace_t[3]; + +typedef struct +{ + int numFaces; + int numVertexes; + int numTVertexes; + int timeValue; + aseVertex_t *vertexes; + aseTVertex_t *tvertexes; + aseFace_t *faces, *tfaces; + + int currentFace; + int currentVertex; +} aseMesh_t; + +typedef struct +{ + int numFrames; + aseMesh_t frames[MAX_ASE_ANIMATION_FRAMES]; + int currentFrame; +} aseMeshAnimation_t; + +typedef struct +{ + char name[128]; +} aseMaterial_t; + +/* +** contains the animate sequence of a single surface +** using a single material +*/ +typedef struct +{ + char name[128]; + int materialRef; + int numAnimations; + aseMeshAnimation_t anim; +} aseGeomObject_t; + +typedef struct +{ + int numMaterials; + aseMaterial_t materials[MAX_ASE_MATERIALS]; + aseGeomObject_t objects[MAX_ASE_OBJECTS]; + + char *buffer; + char *curpos; + size_t len; + int currentObject; + bool grabAnims; +} ase_t; + +static char s_token[1024]; +static ase_t ase; + +static bool ASE_Process( void ); +static void ASE_FreeGeomObject( int ndx ); + +/* +** ASE_Load +*/ +bool ASE_Load( const char *filename, bool grabAnims ) +{ + memset( &ase, 0, sizeof( ase )); + + ase.curpos = ase.buffer = (char *)FS_LoadFile( filename, &ase.len, false ); + if( !ase.buffer ) return false; + + ase.grabAnims = grabAnims; + if( !ASE_Process( )) + return false; + + return true; +} + +/* +** ASE_Free +*/ +void ASE_Free( void ) +{ + for( int i = 0; i < ase.currentObject; i++ ) + { + ASE_FreeGeomObject( i ); + } +} + +/* +** ASE_GetNumSurfaces +*/ +int ASE_GetNumSurfaces( void ) +{ + return ase.currentObject; +} + +/* +** ASE_GetSurfaceName +*/ +const char *ASE_GetSurfaceName( int which ) +{ + aseGeomObject_t *pObject = &ase.objects[which]; + + if( !pObject->anim.numFrames ) + return NULL; + + return pObject->name; +} + +/* +** ASE_GetSurfaceAnimation +** +** Returns an animation (sequence of polysets) +*/ +polyset_t *ASE_GetSurfaceAnimation( int which, int *pNumFrames, int skipFrameStart, int skipFrameEnd, int maxFrames ) +{ + aseGeomObject_t *pObject = &ase.objects[which]; + int numFramesInAnimation; + int numFramesToKeep; + polyset_t *psets; + + if( !pObject->anim.numFrames ) + return 0; + + if ( pObject->anim.numFrames > maxFrames && maxFrames != -1 ) + { + numFramesInAnimation = maxFrames; + } + else + { + numFramesInAnimation = pObject->anim.numFrames; + if( maxFrames != -1 ) + MsgDev( D_WARN, "ASE_GetSurfaceAnimation maxFrames > numFramesInAnimation\n" ); + } + + if( skipFrameEnd != -1 ) + numFramesToKeep = numFramesInAnimation - ( skipFrameEnd - skipFrameStart + 1 ); + else numFramesToKeep = numFramesInAnimation; + + *pNumFrames = numFramesToKeep; + + psets = (polyset_t *)Mem_Alloc( sizeof( polyset_t ) * numFramesToKeep ); + + for( int f = 0, i = 0; i < numFramesInAnimation; i++ ) + { + aseMesh_t *pMesh = &pObject->anim.frames[i]; + + if( skipFrameStart != -1 ) + { + if( i >= skipFrameStart && i <= skipFrameEnd ) + continue; + } + + Q_strcpy( psets[f].name, pObject->name ); + char *start = Q_stristr( ase.materials[pObject->materialRef].name, "textures\\" ); + if( !start ) start = ase.materials[pObject->materialRef].name; + else start += Q_strlen( "textures\\" ); + Q_strcpy( psets[f].materialname, start ); + + psets[f].triangles = (poly_t *)Mem_Alloc( sizeof( poly_t ) * pObject->anim.frames[i].numFaces ); + psets[f].numtriangles = pObject->anim.frames[i].numFaces; + + for( int t = 0; t < pObject->anim.frames[i].numFaces; t++ ) + { + for( int k = 0; k < 3; k++ ) + { + psets[f].triangles[t].verts[k].point[0] = pMesh->vertexes[pMesh->faces[t][k]].x; + psets[f].triangles[t].verts[k].point[1] = pMesh->vertexes[pMesh->faces[t][k]].y; + psets[f].triangles[t].verts[k].point[2] = pMesh->vertexes[pMesh->faces[t][k]].z; + + if( pMesh->tvertexes && pMesh->tfaces ) + { + psets[f].triangles[t].verts[k].coord[0] = pMesh->tvertexes[pMesh->tfaces[t][k]].s; + psets[f].triangles[t].verts[k].coord[1] = pMesh->tvertexes[pMesh->tfaces[t][k]].t; + } + + } + } + f++; + } + + return psets; +} + +static void ASE_FreeGeomObject( int ndx ) +{ + aseGeomObject_t *pObject; + + pObject = &ase.objects[ndx]; + + for( int i = 0; i < pObject->anim.numFrames; i++ ) + { + if( pObject->anim.frames[i].vertexes ) + { + Mem_Free( pObject->anim.frames[i].vertexes ); + } + if ( pObject->anim.frames[i].tvertexes ) + { + Mem_Free( pObject->anim.frames[i].tvertexes ); + } + if ( pObject->anim.frames[i].faces ) + { + Mem_Free( pObject->anim.frames[i].faces ); + } + if ( pObject->anim.frames[i].tfaces ) + { + Mem_Free( pObject->anim.frames[i].tfaces ); + } + } + + memset( pObject, 0, sizeof( *pObject ) ); +} + +static aseMesh_t *ASE_GetCurrentMesh( void ) +{ + aseGeomObject_t *pObject; + + if( ase.currentObject >= MAX_ASE_OBJECTS ) + { + COM_FatalError( "Too many GEOMOBJECTs\n" ); + return 0; // never called + } + + pObject = &ase.objects[ase.currentObject]; + + if( pObject->anim.currentFrame >= MAX_ASE_ANIMATION_FRAMES ) + { + COM_FatalError( "Too many MESHes\n" ); + return 0; + } + + return &pObject->anim.frames[pObject->anim.currentFrame]; +} + +static int CharIsTokenDelimiter( int ch ) +{ + if ( ch <= 32 ) + return 1; + return 0; +} + +static int ASE_GetToken( bool restOfLine ) +{ + int i = 0; + + if( ase.buffer == 0 ) + return 0; + + if(( ase.curpos - ase.buffer ) == ase.len ) + return 0; + + // skip over crap + while((( ase.curpos - ase.buffer ) < ase.len ) && ( *ase.curpos <= 32 )) + { + ase.curpos++; + } + + while(( ase.curpos - ase.buffer ) < ase.len ) + { + s_token[i] = *ase.curpos; + + ase.curpos++; + i++; + + if(( CharIsTokenDelimiter( s_token[i-1] ) && !restOfLine ) || (( s_token[i-1] == '\n' ) || ( s_token[i-1] == '\r' ))) + { + s_token[i-1] = 0; + break; + } + } + + s_token[i] = 0; + + return 1; +} + +static void ASE_ParseBracedBlock( void (*parser)( const char *token )) +{ + int indent = 0; + + while( ASE_GetToken( false )) + { + if( !Q_strcmp( s_token, "{" )) + { + indent++; + } + else if( !Q_strcmp( s_token, "}" )) + { + indent--; + if( indent == 0 ) + break; + else if( indent < 0 ) + COM_FatalError( "Unexpected '}'\n" ); + } + else + { + if( parser ) + parser( s_token ); + } + } +} + +static void ASE_SkipEnclosingBraces( void ) +{ + int indent = 0; + + while( ASE_GetToken( false )) + { + if( !Q_strcmp( s_token, "{" )) + { + indent++; + } + else if( !Q_strcmp( s_token, "}" )) + { + indent--; + if( indent == 0 ) + break; + else if ( indent < 0 ) + COM_FatalError( "Unexpected '}'\n" ); + } + } +} + +static void ASE_SkipRestOfLine( void ) +{ + ASE_GetToken( true ); +} + +static void ASE_KeyMAP_DIFFUSE( const char *token ) +{ + char buffer[1024]; + + if( !Q_strcmp( token, "*BITMAP" )) + { + ASE_GetToken( false ); + + Q_strcpy( buffer, s_token + 1 ); + if( Q_strchr( buffer, '"' )) + *Q_strchr( buffer, '"' ) = 0; + Q_strcpy( ase.materials[ase.numMaterials].name, buffer ); + } +} + +static void ASE_KeyMATERIAL( const char *token ) +{ + if( !Q_strcmp( token, "*MAP_DIFFUSE" ) ) + { + ASE_ParseBracedBlock( ASE_KeyMAP_DIFFUSE ); + } +} + +static void ASE_KeyMATERIAL_LIST( const char *token ) +{ + if( !Q_strcmp( token, "*MATERIAL_COUNT" )) + { + ASE_GetToken( false ); + MsgDev( D_REPORT, "..num materials: %s\n", s_token ); + if( atoi( s_token ) > MAX_ASE_MATERIALS ) + { + COM_FatalError( "Too many materials!\n" ); + } + ase.numMaterials = 0; + } + else if( !Q_strcmp( token, "*MATERIAL" )) + { + MsgDev( D_REPORT, "..material %d ", ase.numMaterials ); + ASE_ParseBracedBlock( ASE_KeyMATERIAL ); + ase.numMaterials++; + } +} + +static void ASE_KeyMESH_VERTEX_LIST( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + if( !Q_strcmp( token, "*MESH_VERTEX" )) + { + ASE_GetToken( false ); // skip number + + ASE_GetToken( false ); + pMesh->vertexes[pMesh->currentVertex].y = atof( s_token ); + + ASE_GetToken( false ); + pMesh->vertexes[pMesh->currentVertex].x = -atof( s_token ); + + ASE_GetToken( false ); + pMesh->vertexes[pMesh->currentVertex].z = atof( s_token ); + + pMesh->currentVertex++; + + if( pMesh->currentVertex > pMesh->numVertexes ) + { + COM_FatalError( "pMesh->currentVertex >= pMesh->numVertexes\n" ); + } + } + else + { + COM_FatalError( "Unknown token '%s' while parsing MESH_VERTEX_LIST\n", token ); + } +} + +static void ASE_KeyMESH_FACE_LIST( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + if( !Q_strcmp( token, "*MESH_FACE" ) ) + { + ASE_GetToken( false ); // skip face number + + ASE_GetToken( false ); // skip label + ASE_GetToken( false ); // first vertex + pMesh->faces[pMesh->currentFace][0] = atoi( s_token ); + + ASE_GetToken( false ); // skip label + ASE_GetToken( false ); // second vertex + pMesh->faces[pMesh->currentFace][2] = atoi( s_token ); + + ASE_GetToken( false ); // skip label + ASE_GetToken( false ); // third vertex + pMesh->faces[pMesh->currentFace][1] = atoi( s_token ); + + ASE_GetToken( true ); + pMesh->currentFace++; + } + else + { + COM_FatalError( "Unknown token '%s' while parsing MESH_FACE_LIST\n", token ); + } +} + +static void ASE_KeyTFACE_LIST( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + if( !Q_strcmp( token, "*MESH_TFACE" )) + { + int a, b, c; + + ASE_GetToken( false ); + + ASE_GetToken( false ); + a = atoi( s_token ); + ASE_GetToken( false ); + c = atoi( s_token ); + ASE_GetToken( false ); + b = atoi( s_token ); + + pMesh->tfaces[pMesh->currentFace][0] = a; + pMesh->tfaces[pMesh->currentFace][1] = b; + pMesh->tfaces[pMesh->currentFace][2] = c; + + pMesh->currentFace++; + } + else + { + COM_FatalError( "Unknown token '%s' in MESH_TFACE\n", token ); + } +} + +static void ASE_KeyMESH_TVERTLIST( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + if( !Q_strcmp( token, "*MESH_TVERT" )) + { + char u[80], v[80], w[80]; + + ASE_GetToken( false ); + + ASE_GetToken( false ); + strcpy( u, s_token ); + + ASE_GetToken( false ); + strcpy( v, s_token ); + + ASE_GetToken( false ); + strcpy( w, s_token ); + + pMesh->tvertexes[pMesh->currentVertex].s = atof( u ); + pMesh->tvertexes[pMesh->currentVertex].t = 1.0f - atof( v ); + + pMesh->currentVertex++; + + if( pMesh->currentVertex > pMesh->numTVertexes ) + { + COM_FatalError( "pMesh->currentVertex > pMesh->numTVertexes\n" ); + } + } + else + { + COM_FatalError( "Unknown token '%s' while parsing MESH_TVERTLIST\n" ); + } +} + +static void ASE_KeyMESH( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + if( !Q_strcmp( token, "*TIMEVALUE" )) + { + ASE_GetToken( false ); + + pMesh->timeValue = atoi( s_token ); + MsgDev( D_REPORT, ".....timevalue: %d\n", pMesh->timeValue ); + } + else if( !Q_strcmp( token, "*MESH_NUMVERTEX" )) + { + ASE_GetToken( false ); + + pMesh->numVertexes = atoi( s_token ); + MsgDev( D_REPORT, ".....TIMEVALUE: %d\n", pMesh->timeValue ); + MsgDev( D_REPORT, ".....num vertexes: %d\n", pMesh->numVertexes ); + } + else if( !Q_strcmp( token, "*MESH_NUMFACES" )) + { + ASE_GetToken( false ); + + pMesh->numFaces = atoi( s_token ); + MsgDev( D_REPORT, ".....num faces: %d\n", pMesh->numFaces ); + } + else if( !Q_strcmp( token, "*MESH_NUMTVFACES" )) + { + ASE_GetToken( false ); + + if( atoi( s_token ) != pMesh->numFaces ) + { + COM_FatalError( "MESH_NUMTVFACES != MESH_NUMFACES\n" ); + } + } + else if( !Q_strcmp( token, "*MESH_NUMTVERTEX" )) + { + ASE_GetToken( false ); + + pMesh->numTVertexes = atoi( s_token ); + MsgDev( D_REPORT, ".....num tvertexes: %d\n", pMesh->numTVertexes ); + } + else if( !Q_strcmp( token, "*MESH_VERTEX_LIST" )) + { + pMesh->vertexes = (aseVertex_t *)Mem_Alloc( sizeof( aseVertex_t ) * pMesh->numVertexes ); + pMesh->currentVertex = 0; + MsgDev( D_REPORT, ".....parsing MESH_VERTEX_LIST\n" ); + ASE_ParseBracedBlock( ASE_KeyMESH_VERTEX_LIST ); + } + else if( !Q_strcmp( token, "*MESH_TVERTLIST" )) + { + pMesh->currentVertex = 0; + pMesh->tvertexes = (aseTVertex_t *)Mem_Alloc( sizeof( aseTVertex_t ) * pMesh->numTVertexes ); + MsgDev( D_REPORT, ".....parsing MESH_TVERTLIST\n" ); + ASE_ParseBracedBlock( ASE_KeyMESH_TVERTLIST ); + } + else if( !Q_strcmp( token, "*MESH_FACE_LIST" )) + { + pMesh->faces = (aseFace_t *)Mem_Alloc( sizeof( aseFace_t ) * pMesh->numFaces ); + pMesh->currentFace = 0; + MsgDev( D_REPORT, ".....parsing MESH_FACE_LIST\n" ); + ASE_ParseBracedBlock( ASE_KeyMESH_FACE_LIST ); + } + else if( !Q_strcmp( token, "*MESH_TFACELIST" )) + { + pMesh->tfaces = (aseFace_t *)Mem_Alloc( sizeof( aseFace_t ) * pMesh->numFaces ); + pMesh->currentFace = 0; + MsgDev( D_REPORT, ".....parsing MESH_TFACE_LIST\n" ); + ASE_ParseBracedBlock( ASE_KeyTFACE_LIST ); + } + else if( !Q_strcmp( token, "*MESH_NORMALS" )) + { + ASE_ParseBracedBlock( 0 ); + } +} + +static void ASE_KeyMESH_ANIMATION( const char *token ) +{ + aseMesh_t *pMesh = ASE_GetCurrentMesh(); + + // loads a single animation frame + if( !Q_strcmp( token, "*MESH" ) ) + { + MsgDev( D_REPORT, "...found MESH\n" ); + assert( pMesh->faces == 0 ); + assert( pMesh->vertexes == 0 ); + assert( pMesh->tvertexes == 0 ); + memset( pMesh, 0, sizeof( *pMesh ) ); + + ASE_ParseBracedBlock( ASE_KeyMESH ); + + if( ++ase.objects[ase.currentObject].anim.currentFrame == MAX_ASE_ANIMATION_FRAMES ) + { + COM_FatalError( "Too many animation frames\n" ); + } + } + else + { + COM_FatalError( "Unknown token '%s' while parsing MESH_ANIMATION\n", token ); + } +} + +static void ASE_KeyGEOMOBJECT( const char *token ) +{ + if ( !strcmp( token, "*NODE_NAME" ) ) + { + char *name = ase.objects[ase.currentObject].name; + + ASE_GetToken( true ); + MsgDev( D_REPORT, " %s\n", s_token ); + Q_strcpy( ase.objects[ase.currentObject].name, s_token + 1 ); + if( Q_strchr( ase.objects[ase.currentObject].name, '"' )) + *Q_strchr( ase.objects[ase.currentObject].name, '"' ) = 0; + + if( Q_strstr( name, "tag" ) == name ) + { + while( Q_strchr( name, '_' ) != Q_strrchr( name, '_' )) + { + *Q_strrchr( name, '_' ) = 0; + } + while( Q_strrchr( name, ' ' )) + { + *Q_strrchr( name, ' ' ) = 0; + } + } + } + else if( !Q_strcmp( token, "*NODE_PARENT" )) + { + ASE_SkipRestOfLine(); + } + // ignore unused data blocks + else if( !Q_strcmp( token, "*NODE_TM" ) || !Q_strcmp( token, "*TM_ANIMATION" )) + { + ASE_ParseBracedBlock( 0 ); + } + // ignore regular meshes that aren't part of animation + else if( !Q_strcmp( token, "*MESH" ) && !ase.grabAnims ) + { + ASE_ParseBracedBlock( ASE_KeyMESH ); + + if( ++ase.objects[ase.currentObject].anim.currentFrame == MAX_ASE_ANIMATION_FRAMES ) + { + COM_FatalError( "Too many animation frames\n" ); + } + + ase.objects[ase.currentObject].anim.numFrames = ase.objects[ase.currentObject].anim.currentFrame; + ase.objects[ase.currentObject].numAnimations++; + } + // according to spec these are obsolete + else if( !Q_strcmp( token, "*MATERIAL_REF" )) + { + ASE_GetToken( false ); + + ase.objects[ase.currentObject].materialRef = atoi( s_token ); + } + // loads a sequence of animation frames + else if( !Q_strcmp( token, "*MESH_ANIMATION" )) + { + if( ase.grabAnims ) + { + MsgDev( D_REPORT, "..found MESH_ANIMATION\n" ); + + if( ase.objects[ase.currentObject].numAnimations ) + { + COM_FatalError( "Multiple MESH_ANIMATIONS within a single GEOM_OBJECT\n" ); + } + ASE_ParseBracedBlock( ASE_KeyMESH_ANIMATION ); + ase.objects[ase.currentObject].anim.numFrames = ase.objects[ase.currentObject].anim.currentFrame; + ase.objects[ase.currentObject].numAnimations++; + } + else + { + ASE_SkipEnclosingBraces(); + } + } + // skip unused info + else if( !Q_strcmp( token, "*PROP_MOTIONBLUR" ) || !Q_strcmp( token, "*PROP_CASTSHADOW" ) || !Q_strcmp( token, "*PROP_RECVSHADOW" )) + { + ASE_SkipRestOfLine(); + } +} + +static void ConcatenateObjects( aseGeomObject_t *pObjA, aseGeomObject_t *pObjB ) +{ +} + +static void CollapseObjects( void ) +{ + int numObjects = ase.currentObject; + + for( int i = 0; i < numObjects; i++ ) + { + int j; + + // skip tags + if( Q_strstr( ase.objects[i].name, "tag" ) == ase.objects[i].name ) + { + continue; + } + + if( !ase.objects[i].numAnimations ) + { + continue; + } + + for( j = i + 1; j < numObjects; j++ ) + { + if( Q_strstr( ase.objects[j].name, "tag" ) == ase.objects[j].name ) + { + continue; + } + + if( ase.objects[i].materialRef == ase.objects[j].materialRef ) + { + if( ase.objects[j].numAnimations ) + { + ConcatenateObjects( &ase.objects[i], &ase.objects[j] ); + } + } + } + } +} + +/* +** ASE_Process +*/ +static bool ASE_Process( void ) +{ + while( ASE_GetToken( false )) + { + if( !Q_strcmp( s_token, "*3DSMAX_ASCIIEXPORT" ) || !Q_strcmp( s_token, "*COMMENT" )) + { + ASE_SkipRestOfLine(); + } + else if( !Q_strcmp( s_token, "*SCENE" )) + ASE_SkipEnclosingBraces(); + else if( !Q_strcmp( s_token, "*MATERIAL_LIST" )) + { + MsgDev( D_REPORT, "MATERIAL_LIST\n" ); + + ASE_ParseBracedBlock( ASE_KeyMATERIAL_LIST ); + } + else if( !Q_strcmp( s_token, "*GEOMOBJECT" )) + { + MsgDev( D_REPORT, "GEOMOBJECT" ); + + ASE_ParseBracedBlock( ASE_KeyGEOMOBJECT ); + + if( Q_strstr( ase.objects[ase.currentObject].name, "Bip" ) || Q_strstr( ase.objects[ase.currentObject].name, "ignore_" )) + { + ASE_FreeGeomObject( ase.currentObject ); + MsgDev( D_REPORT, "(discarding BIP/ignore object)\n" ); + } + else if(( Q_strstr( ase.objects[ase.currentObject].name, "h_" ) != ase.objects[ase.currentObject].name ) && + ( Q_strstr( ase.objects[ase.currentObject].name, "l_" ) != ase.objects[ase.currentObject].name ) && + ( Q_strstr( ase.objects[ase.currentObject].name, "u_" ) != ase.objects[ase.currentObject].name ) && + ( Q_strstr( ase.objects[ase.currentObject].name, "tag" ) != ase.objects[ase.currentObject].name ) && + ase.grabAnims ) + { + MsgDev( D_REPORT, "(ignoring improperly labeled object '%s')\n", ase.objects[ase.currentObject].name ); + ASE_FreeGeomObject( ase.currentObject ); + } + else + { + if( ++ase.currentObject == MAX_ASE_OBJECTS ) + { + COM_FatalError( "Too many GEOMOBJECTs\n" ); + } + } + } + else if( s_token[0] ) + { + MsgDev( D_WARN, "Unknown token '%s'\n", s_token ); + } + } + Mem_Free( ase.buffer, C_FILESYSTEM ); + + if( !ase.currentObject ) + return false; + + CollapseObjects(); + + return true; +} \ No newline at end of file diff --git a/utils/p2csg/aselib.h b/utils/p2csg/aselib.h new file mode 100644 index 0000000..e854f30 --- /dev/null +++ b/utils/p2csg/aselib.h @@ -0,0 +1,52 @@ +/* +=========================================================================== +Copyright (C) 1999-2005 Id Software, Inc. + +This file is part of Quake III Arena source code. + +Quake III Arena source code is free software; you can redistribute it +and/or modify it under the terms of the GNU General Public License as +published by the Free Software Foundation; either version 2 of the License, +or (at your option) any later version. + +Quake III Arena source code is distributed in the hope that it will be +useful, but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Foobar; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +#include "cmdlib.h" +#include "mathlib.h" + +typedef struct +{ + vec3_t point; + vec2_t coord; +} trivert_t; + +typedef struct +{ + trivert_t verts[3]; +} poly_t; + +typedef struct +{ + char name[100]; + char materialname[100]; + poly_t *triangles; + int numtriangles; +} polyset_t; + +bool ASE_Load( const char *filename, bool meshanims ); +int ASE_GetNumSurfaces( void ); +polyset_t *ASE_GetSurfaceAnimation( int ndx, int *numFrames, int skipFrameStart, int skipFrameEnd, int maxFrames ); +const char *ASE_GetSurfaceName( int ndx ); +void ASE_Free( void ); + +bool MakeBrushFor3Points( mapent_t *mapent, side_t *mainSide, short entindex, trivert_t *a, trivert_t *b, trivert_t *c ); +bool MakeBrushFor4Points( mapent_t *mapent, side_t *mainSide, short entindex, trivert_t *a, trivert_t *b, trivert_t *c, trivert_t *d ); diff --git a/utils/p2csg/brush.cpp b/utils/p2csg/brush.cpp new file mode 100644 index 0000000..f37d539 --- /dev/null +++ b/utils/p2csg/brush.cpp @@ -0,0 +1,1486 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// brush.c + +#include "csg.h" + +plane_t g_mapplanes[MAX_INTERNAL_MAP_PLANES]; +int g_planehash[PLANE_HASHES]; +int g_nummapplanes; + +/* +============================================================================= + +PLANE FINDING + +============================================================================= +*/ +/* +================ +PlaneEqual +================ +*/ +bool PlaneEqual( const plane_t *p, const vec3_t normal, const vec3_t origin, vec_t dist ) +{ + vec_t t; + + if( -DIR_EPSILON < ( t = normal[0] - p->normal[0] ) && t < DIR_EPSILON && + -DIR_EPSILON < ( t = normal[1] - p->normal[1] ) && t < DIR_EPSILON && + -DIR_EPSILON < ( t = normal[2] - p->normal[2] ) && t < DIR_EPSILON ) + { + t = PlaneDiff2( origin, p ); + + if( -DIST_EPSILON < t && t < DIST_EPSILON ) + return true; + } + return false; +} + +/* +================ +AddPlaneToHash +================ +*/ +void AddPlaneToHash( plane_t *p ) +{ + int hash; + + hash = (PLANE_HASHES - 1) & (int)fabs( p->dist ); + p->hash_chain = g_planehash[hash]; + g_planehash[hash] = p - g_mapplanes + 1; +} + +/* +================ +CreateNewFloatPlane +================ +*/ +int CreateNewFloatPlane( const vec3_t srcnormal, const vec3_t origin ) +{ + plane_t *p0, *p1, temp; + vec3_t normal; + vec_t dist; + int type; + + if( VectorLength( srcnormal ) < 0.5 ) + return -1; + + // create a new plane + if(( g_nummapplanes + 2 ) > MAX_INTERNAL_MAP_PLANES ) + COM_FatalError( "MAX_INTERNAL_MAP_PLANES limit exceeded\n" ); + + p0 = &g_mapplanes[g_nummapplanes+0]; + p1 = &g_mapplanes[g_nummapplanes+1]; + + // snap plane normal + VectorCopy( srcnormal, normal ); + type = SnapNormal( normal ); + + // snap plane distance + dist = DotProduct( origin, normal ); + + // only snap distance if the normal is an axis. Otherwise there + // is nothing "natural" about snapping the distance to an integer. + if( VectorIsOnAxis( normal ) && fabs( dist - Q_rint( dist )) < DIST_EPSILON ) + dist = Q_rint( dist ); // catch -0.0 + + VectorCopy( origin, p0->origin ); + VectorCopy( normal, p0->normal ); + VectorCopy( origin, p1->origin ); + VectorNegate( normal, p1->normal ); + p0->dist = dist; + p1->dist = -dist; + p0->type = type; + p1->type = type; + g_nummapplanes += 2; + + // always put axial planes facing positive first + if( normal[type % 3] < 0 ) + { + // flip order + temp = *p0; + *p0 = *p1; + *p1 = temp; + + AddPlaneToHash( p0 ); + AddPlaneToHash( p1 ); + return g_nummapplanes - 1; + } + + AddPlaneToHash( p0 ); + AddPlaneToHash( p1 ); + + return g_nummapplanes - 2; +} + +/* +============= +FindFloatPlane + +============= +*/ +int FindFloatPlane( const vec3_t normal, const vec3_t origin ) +{ + int i, hash, h; + vec_t dist; + plane_t *p; + + dist = DotProduct( origin, normal ); + hash = (PLANE_HASHES - 1) & (int)fabs( dist ); + + ThreadLock(); + + // search the border bins as well + for( i = -1; i <= 1; i++ ) + { + h = (hash + i) & (PLANE_HASHES - 1); + for( int pidx = g_planehash[h] - 1; pidx != -1; pidx = g_mapplanes[pidx].hash_chain - 1 ) + { + p = &g_mapplanes[pidx]; + + if( PlaneEqual( p, normal, origin, dist )) + { + ThreadUnlock(); + return p - g_mapplanes; + } + } + } + + // allocate a new two opposite planes + int returnval = CreateNewFloatPlane( normal, origin ); + + ThreadUnlock(); + + return returnval; +} + +int FindFloatPlane2( const vec3_t normal, const vec3_t origin ) +{ + int returnval; + plane_t *p, temp; + vec_t t; + + returnval = 0; + +find_plane: + for( ; returnval < g_nummapplanes; returnval++ ) + { + // BUG: there might be some multithread issue --vluzacn + if( -DIR_EPSILON < ( t = normal[0] - g_mapplanes[returnval].normal[0] ) && t < DIR_EPSILON && + -DIR_EPSILON < ( t = normal[1] - g_mapplanes[returnval].normal[1] ) && t < DIR_EPSILON && + -DIR_EPSILON < ( t = normal[2] - g_mapplanes[returnval].normal[2] ) && t < DIR_EPSILON ) + { + t = DotProduct (origin, g_mapplanes[returnval].normal) - g_mapplanes[returnval].dist; + + if( -DIST_EPSILON < t && t < DIST_EPSILON ) + return returnval; + } + } + + ThreadLock(); + + if( returnval != g_nummapplanes ) // make sure we don't race + { + ThreadUnlock(); + // check to see if other thread added plane we need + goto find_plane; + } + + // create new planes - double check that we have room for 2 planes + if(( g_nummapplanes + 2 ) > MAX_INTERNAL_MAP_PLANES ) + COM_FatalError( "MAX_INTERNAL_MAP_PLANES limit exceeded\n" ); + + p = &g_mapplanes[g_nummapplanes]; + VectorCopy( origin, p->origin ); + VectorCopy( normal, p->normal ); + VectorNormalize( p->normal ); + p->type = MapPlaneTypeForNormal( p->normal ); + // snap normal to nearest axial if possible + if( p->type <= PLANE_LAST_AXIAL ) + { + for( int i = 0; i < 3; i++ ) + { + if( i == p->type ) + p->normal[i] = p->normal[i] > 0 ? 1 : -1; + else p->normal[i] = 0; + } + } + + p->dist = DotProduct( origin, p->normal ); + VectorCopy( origin, (p+1)->origin ); + VectorNegate( p->normal, (p+1)->normal ); + (p+1)->type = p->type; + (p+1)->dist = -p->dist; + + // always put axial planes facing positive first + if( normal[(p->type) % 3] < 0 ) + { + temp = *p; + *p = *(p+1); + *(p+1) = temp; + returnval = g_nummapplanes + 1; + } + else + { + returnval = g_nummapplanes; + } + + g_nummapplanes += 2; + ThreadUnlock(); + + return returnval; +} + +/* +================ +PlaneFromPoints +================ +*/ +int PlaneFromPoints( const vec_t *p0, const vec_t *p1, const vec_t *p2 ) +{ + vec3_t t1, t2, normal; + + VectorSubtract( p0, p1, t1 ); + VectorSubtract( p2, p1, t2 ); + CrossProduct( t1, t2, normal ); + + if( !VectorNormalize( normal )) + return -1; + + return FindFloatPlane( normal, p0 ); +} + +/* +============================================================================= + + CONTENTS DETERMINING + +============================================================================= +*/ +/* +=========== +BrushCheckSides + +some compilers may use SKIP instead of NULL texture +replace it for consistency +=========== +*/ +void BrushCheckSides( mapent_t *mapent, brush_t *b ) +{ + int i, nodraw_faces = 0; + int skip_faces = 0; + int hint_faces = 0; + side_t *s; + + // never apply this for a patches! + if( FBitSet( b->flags, FBRUSH_PATCH )) + return; + + // explicit non-solid brushes also ignore it + if( FBitSet( b->flags, FBRUSH_NOCLIP )) + return; + + for( i = 0; i < b->sides.Count(); i++ ) + { + s = &b->sides[i]; + + if( FBitSet( s->flags, FSIDE_NODRAW )) + nodraw_faces++; + + // count only non-shaders skip + if( FBitSet( s->flags, FSIDE_SKIP ) && FBitSet( s->shader->flags, FSHADER_DEFAULTED )) + skip_faces++; + + if( FBitSet( s->flags, FSIDE_HINT|FSIDE_SOLIDHINT )) + hint_faces++; + } + + // brush have "skip" faces and remaining faces not a hint + if( skip_faces && !hint_faces ) + { + for( i = 0; i < b->sides.Count(); i++ ) + { + s = &b->sides[i]; + + if( FBitSet( s->flags, FSIDE_SKIP )) + { + Q_strncpy( s->name, "NULL", sizeof( s->name )); + SetBits( s->flags, FSIDE_NODRAW ); + ClearBits( s->flags, FSIDE_SKIP ); + s->contents = CONTENTS_SOLID; + } + } + } +} + +/* +=========== +BrushContents +=========== +*/ +int BrushContents( mapent_t *mapent, brush_t *b ) +{ + const char *name = ValueForKey( (entity_t *)mapent, "classname" ); + int i, best_i, contents; + bool assigned = false; + int nodraw_fases = 0; + int best_contents; + side_t *s; + + // cycle though the sides of the brush and attempt to get our best side contents for + // determining overall brush contents + if( b->sides.Count() == 0 ) + COM_FatalError( "Entity %i, Brush %i: Brush with no sides.\n", b->originalentitynum, b->originalbrushnum ); + + // for each face of each brush of this entity + if( BoolForKey( (entity_t *)mapent, "zhlt_precisionclip" )) + SetBits( b->flags, FBRUSH_PRECISIONCLIP ); + + // non-solid detail entity + if( !Q_stricmp( name, "func_detail_illusionary" )) + { + SetBits( b->flags, FBRUSH_NOCLIP ); + b->detaillevel = 2; + return CONTENTS_EMPTY; + } + + // solid detail entity + if( !Q_stricmp( name, "func_detail_fence" ) || !Q_stricmp( name, "func_detail_wall" )) + { + b->detaillevel = 1; + } + + BrushCheckSides( mapent, b ); + + s = &b->sides[0]; + + // NULL can't be used to select contents + if( FBitSet( s->flags, FSIDE_NODRAW )) + best_contents = CONTENTS_NULL; + else best_contents = s->contents; + best_i = 0; + + // SKIP doesn't split space in bsp process, ContentEmpty splits space normally. + if( FBitSet( s->flags, FSIDE_SKIP )) + assigned = true; + + for( i = 1; i < b->sides.Count(); i++ ) + { + s = &b->sides[i]; + + if( assigned ) break; + + if( FBitSet( s->flags, FSIDE_SKIP )) + { + best_contents = s->contents; + assigned = true; + best_i = i; + } + + // g-cont. NULL has lower priority than other contents + if( s->contents > best_contents && !FBitSet( s->flags, FSIDE_NODRAW )) + { + // if our current surface contents is better (larger) + // than our best, make it our best. + best_contents = s->contents; + best_i = i; + } + } + + contents = best_contents; + + for( i = 0; i < b->sides.Count(); i++ ) + { + s = &b->sides[i]; + + if( FBitSet( s->flags, FSIDE_NODRAW )) + nodraw_fases++; + + if( assigned && !FBitSet( s->flags, FSIDE_SKIP|FSIDE_HINT ) && s->contents != CONTENTS_ORIGIN ) + continue; // overwrite content for this texture + + // sky are not to cause mixed face contents + if( s->contents == CONTENTS_SKY || FBitSet( s->flags, FSIDE_NODRAW )) + continue; + + if( s->contents != best_contents ) + { + MsgDev( D_ERROR, "Entity %i, Brush %i: mixed face contents\n Texture %s and %s\n", + b->originalentitynum, b->originalbrushnum, b->sides[best_i].name, s->name ); + break; + } + } + + if( contents != CONTENTS_ORIGIN ) + { + if( FBitSet( b->flags, FBRUSH_NOCLIP )) + contents = CONTENTS_EMPTY; + + if( FBitSet( b->flags, FBRUSH_CLIPONLY )) + contents = CONTENTS_SOLID; + } + + if( contents == CONTENTS_NULL ) + contents = CONTENTS_SOLID; + + // check to make sure we dont have an origin brush as part of worldspawn + if(( b->entitynum == 0 ) || ( !Q_strcmp( "func_group", name ) || !Q_strcmp( "func_landscape", name ) || !Q_strcmp( "func_detail", name ))) + { + if( contents == CONTENTS_ORIGIN ) + MsgDev( D_ERROR, "Entity %i, Brush %i: origin brush not allowed in world\n", b->originalentitynum, b->originalbrushnum ); + } + else + { + // otherwise its not worldspawn, therefore its an entity. check to make sure this brush is allowed + // to be an entity. + switch( contents ) + { + case CONTENTS_SOLID: + case CONTENTS_WATER: + case CONTENTS_SLIME: + case CONTENTS_LAVA: + case CONTENTS_ORIGIN: + case CONTENTS_EMPTY: + break; + default: + COM_FatalError( "Entity %i, Brush %i: %s brushes not allowed in entity", + b->originalentitynum, b->originalbrushnum, ContentsToString( contents )); + break; + } + } + + // turn detail null-brushes into clipbrushes + if( nodraw_fases == b->sides.Count() && b->detaillevel ) + SetBits( b->flags, FBRUSH_CLIPONLY ); + + // TyrTools style details: chop faces but ignore visportals + if( !Q_stricmp( "func_detail", name ) && ( contents == CONTENTS_SOLID || contents == CONTENTS_EMPTY )) + b->detaillevel++; + + return contents; +} + +/* +============================================================================== + +BEVELED CLIPPING HULL GENERATION + +This is done by brute force, and could easily get a lot faster if anyone cares. +============================================================================== +*/ +#define MAX_HULL_POINTS 512 +#define MAX_HULL_EDGES 1024 + +typedef struct expand_s +{ + // input + brush_t *b; + int hullnum; + // output + vec3_t points[MAX_HULL_POINTS]; + vec3_t corners[MAX_HULL_POINTS * 8]; + int edges[MAX_HULL_EDGES][2]; + int numpoints; + int numedges; +} expand_t; + +/* +============ +AddHullPlane +============= +*/ +void AddHullPlane( expand_t *ex, const vec3_t normal, const vec3_t origin ) +{ + int planenum = FindFloatPlane( normal, origin ); + brushhull_t *hull = &ex->b->hull[ex->hullnum]; + + for( bface_t *f = hull->faces; f != NULL; f = f->next ) + { + if( f->planenum == planenum ) + return; // don't add a plane twice + } + + bface_t *face = AllocFace(); + face->planenum = planenum; + face->plane = &g_mapplanes[face->planenum]; + face->contents[0] = CONTENTS_EMPTY; + face->next = hull->faces; + hull->faces = face; + face->texinfo = -1; +} + +/* +============ +TestAddPlane + +Adds the given plane to the brush description if all of the original brush +vertexes can be put on the front side +============= +*/ +void TestAddPlane( expand_t *ex, const vec3_t normal, const vec3_t origin ) +{ + int planenum = FindFloatPlane( normal, origin ); + brushhull_t *hull = &ex->b->hull[ex->hullnum]; + int points_front, points_back; + vec_t *corner; + vec_t d; + + // see if the plane has allready been added + for( bface_t *f = hull->faces; f != NULL; f = f->next ) + { + if( f->planenum == planenum ) + return; // don't add a plane twice + } + + // check all the corner points + points_front = points_back = 0; + corner = ex->corners[0]; + + for( int i = 0; i < ex->numpoints * 8; i++, corner += 3 ) + { + d = (corner[0] - origin[0]) * normal[0]; + d += (corner[1] - origin[1]) * normal[1]; + d += (corner[2] - origin[2]) * normal[2]; + + if( d < -ON_EPSILON ) + { + if( points_front ) + return; + points_back = 1; + } + else if( d > ON_EPSILON ) + { + if( points_back ) + return; + points_front = 1; + } + } + + bface_t *face = AllocFace(); + face->planenum = ( points_front ) ? planenum ^ 1 : planenum; + face->plane = &g_mapplanes[face->planenum]; + face->contents[0] = CONTENTS_EMPTY; + face->next = hull->faces; + hull->faces = face; + face->texinfo = -1; +} + +/* +============ +AddHullPoint + +Doesn't add if duplicated +============= +*/ +int AddHullPoint( expand_t *ex, const vec3_t p ) +{ + vec_t *c; + int i; + + for( i = 0; i < ex->numpoints; i++ ) + { + if( VectorCompareEpsilon( p, ex->points[i], EQUAL_EPSILON )) + return i; + } + + if( ex->numpoints == MAX_HULL_POINTS ) + COM_FatalError( "MAX_HULL_POINTS limit exceeded\n" ); + VectorCopy( p, ex->points[ex->numpoints] ); + c = ex->corners[i*8]; + ex->numpoints++; + + // also add eight hull corners + for( int x = 0; x < 2; x++ ) + { + for( int y = 0; y < 2; y++ ) + { + for( int z = 0; z < 2; z++ ) + { + c[0] = p[0] + g_hull_size[ex->hullnum][x][0]; + c[1] = p[1] + g_hull_size[ex->hullnum][y][1]; + c[2] = p[2] + g_hull_size[ex->hullnum][z][2]; + c += 3; + } + } + } + + return i; +} + +/* +============ +AddHullEdge + +Creates all of the hull planes around the given edge, if not done already +============= +*/ +void AddHullEdge( expand_t *ex, const vec3_t p1, const vec3_t p2 ) +{ + vec3_t edgevec, planevec; + vec3_t normal, origin; + int a, b, c, d, e; + int pt1, pt2; + vec_t length; + + pt1 = AddHullPoint( ex, p1 ); + pt2 = AddHullPoint( ex, p2 ); + + for( int i = 0; i < ex->numedges; i++ ) + { + if(( ex->edges[i][0] == pt1 && ex->edges[i][1] == pt2 ) || ( ex->edges[i][0] == pt2 && ex->edges[i][1] == pt1 )) + return; // already added + } + + if( ex->numedges == MAX_HULL_EDGES ) + COM_FatalError( "MAX_HULL_EDGES limit exceeded\n" ); + + ex->edges[i][0] = pt1; + ex->edges[i][1] = pt2; + ex->numedges++; + + VectorSubtract( p1, p2, edgevec ); + VectorNormalize( edgevec ); + + for( a = 0; a < 3; a++ ) + { + b = (a + 1) % 3; + c = (a + 2) % 3; + + planevec[a] = 1.0; + planevec[b] = 0.0; + planevec[c] = 0.0; + CrossProduct( planevec, edgevec, normal ); + length = VectorNormalize( normal ); + + // if this edge is almost parallel to the hull edge, skip it. + if( length < NORMAL_EPSILON ) continue; + + for( d = 0; d <= 1; d++ ) + { + for( e = 0; e <= 1; e++ ) + { + VectorCopy( p1, origin ); + origin[b] += g_hull_size[ex->hullnum][d][b]; + origin[c] += g_hull_size[ex->hullnum][e][c]; + TestAddPlane( ex, normal, origin ); + } + } + } +} + +/* +============ +ExpandBrush +============= +*/ +void ExpandBrush2( brush_t *b, int hullnum ) +{ + bface_t *brush_faces, *f; + vec3_t origin, normal; + bool axial = true; + int i, x, s; + vec_t corner; + brushhull_t *hull; + expand_t ex; + plane_t *p; + + brush_faces = b->hull[0].faces; + hull = &b->hull[hullnum]; + ex.hullnum = hullnum; + ex.numpoints = 0; + ex.numedges = 0; + ex.b = b; + + // expand all of the planes + for( f = brush_faces; f != NULL; f = f->next ) + { + p = f->plane; + + if( p->type > PLANE_LAST_AXIAL ) + axial = false; // not an xyz axial plane + + VectorCopy( p->origin, origin ); + VectorCopy( p->normal, normal ); + + for ( x = 0; x < 3; x++ ) + { + if( p->normal[x] > 0.0 ) + corner = g_hull_size[hullnum][1][x]; + else if( p->normal[x] < 0.0 ) + corner = -g_hull_size[hullnum][0][x]; + else corner = 0.0; + + origin[x] += p->normal[x] * corner; + } + + bface_t *face = AllocFace(); + face->planenum = FindFloatPlane( normal, origin ); + face->plane = &g_mapplanes[face->planenum]; + face->contents[0] = CONTENTS_EMPTY; + face->next = hull->faces; + hull->faces = face; + face->texinfo = -1; + } + + // if this was an axial brush, we are done + if( axial ) return; + + // add any axis planes not contained in the brush to bevel off corners + for( x = 0; x < 3; x++ ) + { + for( s = -1; s <= 1; s += 2 ) + { + // add the plane + VectorClear( normal ); + normal[x] = s; + + if( s == -1 ) VectorAdd( b->hull[0].mins, g_hull_size[hullnum][0], origin ); + else VectorAdd( b->hull[0].maxs, g_hull_size[hullnum][1], origin ); + AddHullPlane( &ex, normal, origin ); + } + } + + // create all the hull points + for( f = brush_faces; f != NULL; f = f->next ) + { + for( i = 0; i < f->w->numpoints; i++ ) + AddHullPoint( &ex, f->w->p[i] ); + } + + // add all of the edge bevels + for( f = brush_faces; f != NULL; f = f->next ) + { + for( i = 0; i < f->w->numpoints; i++ ) + AddHullEdge( &ex, f->w->p[i], f->w->p[(i + 1) % f->w->numpoints] ); + } +} + +/* +============================================================================== + +BEVELED CLIPPING HULL GENERATION + +This is done by brute force, and could easily get a lot faster if anyone cares. +============================================================================== +*/ +// ===================================================================================== +// AddHullPlane (subroutine for replacement of ExpandBrush, KGP) +// Called to add any and all clip hull planes by the new ExpandBrush. +// ===================================================================================== +void AddHullPlane( brushhull_t *hull, const vec3_t normal, const vec3_t origin, bool check_planenum ) +{ + int planenum = FindFloatPlane( normal, origin ); + + // check to see if this plane is already in the brush (optional to speed + // up cases where we know the plane hasn't been added yet, like axial case) + if( check_planenum ) + { + // we know axial planes are added in last step + if( g_mapplanes[planenum].type <= PLANE_LAST_AXIAL ) + return; + + for( bface_t *f = hull->faces; f != NULL; f = f->next ) + { + if( f->planenum == planenum ) + return; // don't add a plane twice + } + } + + bface_t *face = AllocFace(); + face->planenum = planenum; + face->plane = &g_mapplanes[face->planenum]; + face->contents[0] = CONTENTS_EMPTY; + face->next = hull->faces; + hull->faces = face; + face->texinfo = -1; +} + +/* +============ +ExpandBrush +============= +*/ +void ExpandBrush( brush_t *b, int hullnum ) +{ + vec3_t edge_start, edge_end; + plane_t *plane, *plane2; + vec3_t edge, bevel_edge; + bool start_found, end_found; + bool warned = false; + vec3_t origin, normal; + bface_t *f, *f2; + winding_t *w, *w2; + brushhull_t *hull; + + hull = &b->hull[hullnum]; + + // step 1: for collision between player vertex and brush face. --vluzacn + for( f = b->hull[0].faces; f != NULL; f = f->next ) + { + plane = f->plane; + + // don't bother adding axial planes, + // they're defined by adding the bounding box anyway + if( plane->type <= PLANE_LAST_AXIAL ) + continue; + + // add the offset non-axial plane to the expanded hull + VectorCopy( plane->origin, origin ); + VectorCopy( plane->normal, normal ); + + // old code multiplied offset by normal -- this led to post-csg "sticky" walls where a + // slope met an axial plane from the next brush since the offset from the slope would be less + // than the full offset for the axial plane -- the discontinuity also contributes to increased + // clipnodes. If the normal is zero along an axis, shifting the origin in that direction won't + // change the plane number, so I don't explicitly test that case. The old method is still used if + // preciseclip is turned off to allow backward compatability -- some of the improperly beveled edges + // grow using the new origins, and might cause additional problems. + origin[0] += g_hull_size[hullnum][(normal[0] > 0 ? 1 : 0)][0]; + origin[1] += g_hull_size[hullnum][(normal[1] > 0 ? 1 : 0)][1]; + origin[2] += g_hull_size[hullnum][(normal[2] > 0 ? 1 : 0)][2]; + AddHullPlane( hull, normal, origin, false ); + } + + // step 2: for collision between player edge and brush edge. --vluzacn + // split bevel check into a second pass so we don't have to check for duplicate planes when adding offset planes + // in step above -- otherwise a bevel plane might duplicate an offset plane, causing problems later on. + for( f = b->hull[0].faces; f != NULL; f = f->next ) + { + plane = f->plane; + + // test to see if the plane is completely non-axial (if it is, need to add bevels to any + // existing "inflection edges" where there's a sign change with a neighboring plane's normal for + // a given axis) + + // move along w and find plane on other side of each edge. If normals change sign, + // add a new plane by offsetting the points of the w to bevel the edge in that direction. + // It's possible to have inflection in multiple directions -- in this case, a new plane + // must be added for each sign change in the edge. + w = f->w; + + // do it for each edge + for( int i = 0; i < w->numpoints; i++ ) + { + VectorCopy( w->p[i], edge_start ); + VectorCopy( w->p[(i + 1) % w->numpoints], edge_end ); + + // grab the edge (find relative length) + VectorSubtract( edge_end, edge_start, edge ); + + // brute force - need to check every other w for common points + // if the points match, the other face is the one we need to look at. + for( f2 = b->hull[0].faces; f2 != NULL; f2 = f2->next ) + { + if( f == f2 ) continue; + + start_found = false; + end_found = false; + w2 = f2->w; + + for( int j = 0; j < w2->numpoints; j++ ) + { + if( !start_found && VectorCompareEpsilon( w2->p[j], edge_start, EQUAL_EPSILON )) + start_found = true; + + if( !end_found && VectorCompareEpsilon( w2->p[j], edge_end, EQUAL_EPSILON )) + end_found = true; + + // we've found the face we want, move on to planar comparison + if( start_found && end_found ) + break; + } + + if( start_found && end_found ) + break; + } + + if( !f2 ) + { + if( hullnum == 1 && !warned ) + { + MsgDev( D_WARN, "Illegal Brush (edge without opposite face): Entity %i, Brush %i\n", + b->originalentitynum, b->originalbrushnum ); + warned = true; + } + continue; + } + + plane2 = f2->plane; + + + // check each direction for sign change in normal -- zero can be safely ignored + for( int dir = 0; dir < 3; dir++ ) + { + // if sign changed, add bevel + if( plane->normal[dir] * plane2->normal[dir] < -NORMAL_EPSILON ) + { + // pick direction of bevel edge by looking at normal of existing planes + VectorClear( bevel_edge ); + bevel_edge[dir] = (plane->normal[dir] > 0) ? -1 : 1; + + // find normal by taking normalized cross of the edge vector and the bevel edge + CrossProduct( edge, bevel_edge, normal ); + VectorNormalize( normal ); + + if( fabs( normal[(dir+1)%3]) <= NORMAL_EPSILON || fabs( normal[(dir+2)%3] ) <= NORMAL_EPSILON ) + { + // coincide with axial plane + continue; + } + + // get the origin + VectorCopy( edge_start, origin ); + + // note: if normal == 0 in direction indicated, shifting origin doesn't change plane # + origin[0] += g_hull_size[hullnum][(normal[0] > 0 ? 1 : 0)][0]; + origin[1] += g_hull_size[hullnum][(normal[1] > 0 ? 1 : 0)][1]; + origin[2] += g_hull_size[hullnum][(normal[2] > 0 ? 1 : 0)][2]; + + // add the bevel plane to the expanded hull + AddHullPlane( hull, normal, origin, true ); + } + } + } + } + + // step 3: for collision between player face and brush vertex. --vluzacn + // add the bounding box to the expanded hull -- for a + // completely axial brush, this is the only necessary step + + // add mins + VectorAdd( b->hull[0].mins, g_hull_size[hullnum][0], origin ); + VectorSet( normal, -1.0, 0.0, 0.0 ); + AddHullPlane( hull, normal, origin, false ); + VectorSet( normal, 0.0, -1.0, 0.0 ); + AddHullPlane( hull, normal, origin, false ); + VectorSet( normal, 0.0, 0.0, -1.0 ); + AddHullPlane( hull, normal, origin, false ); + + // add maxs + VectorAdd( b->hull[0].maxs, g_hull_size[hullnum][1], origin ); + VectorSet( normal, 1.0, 0.0, 0.0 ); + AddHullPlane( hull, normal, origin, false ); + VectorSet( normal, 0.0, 1.0, 0.0 ); + AddHullPlane( hull, normal, origin, false ); + VectorSet( normal, 0.0, 0.0, 1.0 ); + AddHullPlane( hull, normal, origin, false ); + +#if _DEBUG + // sanity check + for( f = hull->faces; f != NULL; f = f->next ) + { + for( f2 = b->hull[0].faces; f2 != NULL; f2 = f2->next ) + { + if( f2->w->numpoints < 3 ) + continue; + + for( int i = 0; i < f2->w->numpoints; i++ ) + { + if( DotProduct( f->plane->normal, f->plane->origin ) < DotProduct( f->plane->normal, f2->w->p[i] )) + { + MsgDev( D_WARN, "Illegal Brush (clip hull [%i] has backward face): Entity %i, Brush %i\n", + hullnum, b->originalentitynum, b->originalbrushnum ); + break; + } + } + } + } +#endif +} + +//============================================================================ +void FreeHullFaces( void ) +{ + for( int i = 0; i < g_nummapbrushes; i++ ) + { + brush_t *b = &g_mapbrushes[i]; + + for( int j = 0; j < MAX_MAP_HULLS; j++ ) + { + UnlinkFaces( &b->hull[j].faces ); + } + + // throw sides too + b->sides.Purge(); + } +} + +void SortHullFaces( brushhull_t *h ) +{ + int numfaces; + bface_t **faces; + vec3_t *normals; + bool *isused; + int i, j; + int *sorted; + bface_t *f; + + for( numfaces = 0, f = h->faces; f != NULL; f = f->next ) + numfaces++; + + faces = (bface_t **)Mem_Alloc( numfaces * sizeof( bface_t* ), C_TEMPORARY ); + normals = (vec3_t *)Mem_Alloc( numfaces * sizeof( vec3_t ), C_TEMPORARY ); + isused = (bool *)Mem_Alloc( numfaces * sizeof( bool ), C_TEMPORARY ); + sorted = (int *)Mem_Alloc( numfaces * sizeof( int ), C_TEMPORARY ); + + for( i = 0, f = h->faces; f != NULL; i++, f = f->next ) + { + const plane_t *p = &g_mapplanes[f->planenum]; + VectorCopy( p->normal, normals[i] ); + faces[i] = f; + } + + for( i = 0; i < numfaces; i++ ) + { + int bestaxial = -1; + int bestside; + + for( j = 0; j < numfaces; j++) + { + if( isused[j] ) continue; + + int axial = AxisFromNormal( normals[j] ); + + if( axial > bestaxial ) + { + bestaxial = axial; + bestside = j; + } + } + + sorted[i] = bestside; + isused[bestside] = true; + } + + for( i = -1; i < numfaces; i++ ) + { + *(i >= 0 ? &faces[sorted[i]]->next: &h->faces) = (i + 1 < numfaces ? faces[sorted[i + 1]] : NULL); + } + + Mem_Free( faces, C_TEMPORARY ); + Mem_Free( normals, C_TEMPORARY ); + Mem_Free( isused, C_TEMPORARY ); + Mem_Free( sorted, C_TEMPORARY ); +} + +/* +=========== +MakeHullFaces +=========== +*/ +void MakeHullFaces( brush_t *b, brushhull_t *h, int hullnum ) +{ + bface_t *f, *f2; + mapent_t *mapent; + bool warned = false; + vec_t v, area; + int i, j; + vec3_t point; + winding_t *w; + plane_t *p; + + mapent = &g_mapentities[b->entitynum]; + + // sorted faces make BSP-splits is more axial than unsorted + SortHullFaces( h ); +restart: + ClearBounds( h->mins, h->maxs ); + + for( f = h->faces; f != NULL; f = f->next ) + { + w = BaseWindingForPlane( f->plane->normal, f->plane->dist ); + + ASSERT( w != NULL ); + + for( f2 = h->faces; f2 != NULL; f2 = f2->next ) + { + if( f == f2 ) continue; + + p = &g_mapplanes[f2->planenum ^ 1]; + + if( !ChopWindingInPlace( &w, p->normal, p->dist, NORMAL_EPSILON, false )) + break; // nothing to chop? + } + + RemoveColinearPointsEpsilon( w, ON_EPSILON ); + + area = WindingArea( w ); + + if( area < MICROVOLUME ) + { + if( w ) FreeWinding( w ); + + if( !w && !warned && hullnum == 0 ) + { + MsgDev( D_WARN, "Illegal Brush (plane doesn't contribute to final shape): Entity %i, Brush %i\n", + b->originalentitynum, b->originalbrushnum ); + warned = true; + } + + // remove the face and regenerate the hull + UnlinkFaces( &h->faces, f ); + goto restart; + } + + f->contents[0] = CONTENTS_EMPTY; + f->w = w; // at this point winging is always valid + + for( i = 0; i < w->numpoints; i++ ) + { + for( j = 0; j < 3; j++ ) + { + point[j] = w->p[i][j]; + v = Q_rint( point[j] ); + + if( fabs( point[j] - v ) < DIR_EPSILON ) + w->p[i][j] = v; + else w->p[i][j] = point[j]; + + // check for incomplete brushes + if( w->p[i][j] >= WORLD_MAXS || w->p[i][j] <= WORLD_MINS ) + break; + } + + // remove this brush + if( j < 3 ) + { + UnlinkFaces( &h->faces ); + MsgDev( D_REPORT, "Entity %i, Brush %i: degenerate brush was removed\n", + b->originalentitynum, b->originalbrushnum ); + return; + } + + if( !FBitSet( f->flags, FSIDE_SKIP )) + AddPointToBounds( w->p[i], h->mins, h->maxs ); + } + + // make sure what all faces has valid ws + CheckWindingEpsilon( w, ON_EPSILON, ( hullnum == 0 )); + } + + for( i = 0; i < 3; i++ ) + { + if( h->mins[i] < WORLD_MINS || h->maxs[i] > WORLD_MAXS ) + { + MsgDev( D_ERROR, "Entity %i, Brush %i: outside world(+/-%d): (%.0f,%.0f,%.0f)-(%.0f,%.0f,%.0f)\n", + b->originalentitynum, b->originalbrushnum, BOGUS_RANGE / 2, + h->mins[0], h->mins[1], h->mins[2], h->maxs[0], h->maxs[1], h->maxs[2] ); + break; + } + } + + if( i == 3 && hullnum == 0 ) + { + // compute total entity bounds + AddPointToBounds( h->mins, mapent->absmin, mapent->absmax ); + AddPointToBounds( h->maxs, mapent->absmin, mapent->absmax ); + } +} + +/* +=========== +MakeBrushPlanes +=========== +*/ +int MakeBrushPlanes( brush_t *b ) +{ + bool skipface; + int badsides = 0; + vec3_t planepts[3]; + vec3_t origin; + bface_t *f; + side_t *s; + + // + // if the origin key is set (by an origin brush), offset all of the values + // + GetVectorForKey( (entity_t *)&g_mapentities[b->entitynum], "origin", origin ); + + // convert to mapplanes + for( int i = 0; i < b->sides.Count(); i++ ) + { + s = &b->sides[i]; + skipface = false; + + if( b->entitynum ) + { + for( int k = 0; k < 3; k++ ) + VectorSubtract( s->planepts[k], origin, planepts[k] ); + s->planenum = PlaneFromPoints( planepts[0], planepts[1], planepts[2] ); + } + else + { + // world doesn't required offset by origin + s->planenum = PlaneFromPoints( s->planepts[0], s->planepts[1], s->planepts[2] ); + } + + if( s->planenum == -1 ) + { + MsgDev( D_REPORT, "Entity %i, Brush %i: plane with no normal\n", b->originalentitynum, b->originalbrushnum ); + badsides++; + continue; + } + + // see if the plane has been used already + for( f = b->hull[0].faces; f != NULL; f = f->next ) + { + // g-cont. there is non fatal for us. just reject this face and trying again + if( f->planenum == s->planenum || f->planenum == ( s->planenum ^ 1 )) + { + MsgDev( D_REPORT, "Entity %i, Brush %i, Side %i: has a coplanar plane\n", + b->originalentitynum, b->originalbrushnum, i ); + skipface = true; + badsides++; + break; + } + } + + if( skipface ) continue; + + f = AllocFace(); + f->planenum = s->planenum; + f->plane = &g_mapplanes[s->planenum]; + f->next = b->hull[0].faces; + b->hull[0].faces = f; + f->texinfo = g_onlyents ? -1 : TexinfoForSide( f->plane, s, origin ); + f->flags = s->flags; + } + + return badsides; +} + +/* +=========== +CreateBrushFaces +=========== +*/ +void CreateBrushFaces( brush_t *b ) +{ + // convert brush sides to planes + int badsides = MakeBrushPlanes( b ); + + if( badsides > 0 ) + { + MsgDev( D_WARN, "Entity %i, Brush %i: has %d invalid sides (total %d)\n", + b->originalentitynum, b->originalbrushnum, badsides, b->sides.Count() ); + } + + MakeHullFaces( b, &b->hull[0], 0 ); + + if( b->contents == CONTENTS_EMPTY || b->contents == CONTENTS_ORIGIN ) + return; + + if( !g_noclip && ( FBitSet( b->flags, FBRUSH_CLIPONLY ) || !FBitSet( b->flags, FBRUSH_NOCLIP ))) + { + for( int h = 1; h < MAX_MAP_HULLS; h++ ) + { + if( VectorIsNull( g_hull_size[h][0] ) && VectorIsNull( g_hull_size[h][1] )) + continue; + + if( FBitSet( b->flags, FBRUSH_PRECISIONCLIP )) + ExpandBrush2( b, h ); + else ExpandBrush( b, h ); + MakeHullFaces( b, &b->hull[h], h ); + } + } + + // invisible detail brush it's a clipbrush + if( FBitSet( b->flags, FBRUSH_CLIPONLY )) + { + UnlinkFaces( &b->hull[0].faces ); + b->hull[0].faces = NULL; + } +} + +/* +=========== +CreateBrush + +multi-thread version +=========== +*/ +void CreateBrush( int brushnum, int threadnum ) +{ + CreateBrushFaces( &g_mapbrushes[brushnum] ); +} + +/* +================== +DeleteBrushFaces + +release specified face or purge all chain +================== +*/ +void DeleteBrushFaces( brush_t *b ) +{ + if( !b ) return; + + for( int i = 0; i < MAX_MAP_HULLS; i++ ) + UnlinkFaces( &b->hull[i].faces ); +} + +/* +=========== +DumpBrushPlanes + +NOTE: never alloc planes during threads work because planes order will be +different for each compile and we gets various BSP'ing result every +time when we compile map again! +=========== +*/ +void DumpBrushPlanes( void ) +{ +#if 0 + for( int i = 0; i < g_nummapbrushes; i++ ) + { + brush_t *b = &g_mapbrushes[i]; + + if( b->contents == CONTENTS_ORIGIN ) + continue; + + Msg( "brush #%i\n", i ); + + // convert to mapplanes + for( int j = 0; j < b->sides.Count(); j++ ) + { + side_t *s = &b->sides[j]; + plane_t *mp = &g_mapplanes[s->planenum]; + + Msg( "#%i, (%g %g %g) - (%g), %d\n", j, mp->normal[0], mp->normal[1], mp->normal[2], mp->dist, mp->type ); + } + } +#endif +} + +void ProcessAutoOrigins( void ) +{ + char string[32]; + const char *classname; + const char *pclassname; + const char *ptarget; + mapent_t *parent; + mapent_t *mapent; + vec3_t origin; + int c_origins_processed = 0; + bool origin_from_parent; + + MsgDev( D_REPORT, "ProcessAutoOrigins:\n" ); + + // skip the world + for( int i = 1; i < g_mapentities.Count(); i++ ) + { + origin_from_parent = false; + mapent = &g_mapentities[i]; + parent = NULL; + + if( !mapent->numbrushes ) + continue; + + // for some reasons entity doesn't have a valid size + if( BoundsIsCleared( mapent->absmin, mapent->absmax )) + continue; + + GetVectorForKey( (entity_t *)mapent, "origin", origin ); + + // origin was set by level-designer + if( !VectorIsNull( origin )) + continue; + + classname = ValueForKey( (entity_t *)mapent, "classname" ); + + // g-cont. old-good hack for my entity :-) + if( !Q_strcmp( classname, "func_traindoor" )) + { + if( CheckKey( (entity_t *)mapent, "train" )) + parent = FindTargetMapEntity( &g_mapentities, ValueForKey( (entity_t *)mapent, "train" )); + } + else if( !Q_strncmp( classname, "rotate_", 7 )) + { + // Quake1 rotational objects support + parent = FindTargetMapEntity( &g_mapentities, ValueForKey( (entity_t *)mapent, "target" )); + origin_from_parent = true; + } + else if( !Q_strncmp( classname, "func_portal", 11 )) + { + // portal always required origin-brush + parent = mapent; + } + + if( !parent && CheckKey( (entity_t *)mapent, "parent" )) + parent = FindTargetMapEntity( &g_mapentities, ValueForKey( (entity_t *)mapent, "parent" )); + + if( !parent ) continue; + + pclassname = ValueForKey( (entity_t *)parent, "classname" ); + ptarget = ValueForKey( (entity_t *)parent, "targetname" ); + + if( origin_from_parent || !Q_strcmp( pclassname, "func_door_rotating" )) + { + GetVectorForKey( (entity_t *)parent, "origin", origin ); + Q_snprintf( string, sizeof( string ), "%.1f %.1f %.1f", origin[0], origin[1], origin[2] ); + SetKeyValue( (entity_t *)mapent, "origin", string ); + } + else + { + // now we have: + // 1. entity with valid absmin\absmax + // 2. it have valid parent + // 3. it doesn't have custom origin + VectorAverage( mapent->absmin, mapent->absmax, origin ); + Q_snprintf( string, sizeof( string ), "%.1f %.1f %.1f", origin[0], origin[1], origin[2] ); + SetKeyValue( (entity_t *)mapent, "origin", string ); + } + + MsgDev( D_REPORT, "%s, parent %s (%s) auto origin %g %g %g\n", classname, pclassname, ptarget, origin[0], origin[1], origin[2] ); + + for( int j = 0; j < mapent->numbrushes; j++ ) + { + brush_t *b = &g_mapbrushes[mapent->firstbrush + j]; + + // remove old brush faces + DeleteBrushFaces( b ); + + // new brush with origin-adjusted sides + CreateBrushFaces( b ); + } + c_origins_processed++; + } + + if( c_origins_processed > 0 ) + MsgDev( D_INFO, "total %i entities was adjusted with auto-origin\n", c_origins_processed ); +} + +void RestoreModelOrigins( void ) +{ + const char *pclassname; + const char *ptarget; + char string[32]; + + for( int i = 1; i < g_nummodels; i++ ) + { + mapent_t *ent = MapEntityForModel( i ); + dmodel_t *bm = &g_dmodels[i]; + + if( VectorIsNull( bm->origin )) + continue; + + // for some reasons origin already set + if( CheckKey( (entity_t *)ent, "origin" )) + continue; + + pclassname = ValueForKey( (entity_t *)ent, "classname" ); + ptarget = ValueForKey( (entity_t *)ent, "targetname" ); + + MsgDev( D_REPORT, "%s, (%s) auto origin %g %g %g\n", pclassname, ptarget, bm->origin[0], bm->origin[1], bm->origin[2] ); + Q_snprintf( string, sizeof( string ), "%.1f %.1f %.1f", bm->origin[0], bm->origin[1], bm->origin[2] ); + SetKeyValue( (entity_t *)ent, "origin", string ); + } +} \ No newline at end of file diff --git a/utils/p2csg/csg.h b/utils/p2csg/csg.h new file mode 100644 index 0000000..23fff7c --- /dev/null +++ b/utils/p2csg/csg.h @@ -0,0 +1,258 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + + +#include "cmdlib.h" +#include "mathlib.h" +#include "stringlib.h" +#include "scriplib.h" +#include "filesystem.h" +#include "polylib.h" +#include "threads.h" +#include "bspfile.h" +#include "shaders.h" +#include +#include "utlarray.h" + +#ifndef DOUBLEVEC_T +#error you must add -dDOUBLEVEC_T to the project! +#endif + +#define PLANE_HASHES 8192 +#define MAX_SWITCHED_LIGHTS 32 + +// supported map formats +enum +{ + BRUSH_UNKNOWN = 0, + BRUSH_WORLDCRAFT_21, // quake worldcraft <= 2.1 + BRUSH_WORLDCRAFT_22, // half-life worldcraft >= 2.2 + BRUSH_RADIANT, + BRUSH_QUARK, + BRUSH_COUNT +}; + +// this part is shared with bsp +#define FSIDE_NODRAW BIT( 0 ) // don't store face into bsp +#define FSIDE_HINT BIT( 1 ) // this is a hint for bsp partition +#define FSIDE_SOLIDHINT BIT( 2 ) // solid hint +#define FSIDE_SKY BIT( 3 ) // eliminate this face after CSG + +// this part a private to csg +#define FSIDE_USED BIT( 4 ) // just for statistics +#define FSIDE_NOLIGHTMAP BIT( 5 ) // cause to set TEX_SPECIAL +#define FSIDE_SKIP BIT( 6 ) // eliminate this face after CSG +#define FSIDE_NOSHADOW BIT( 7 ) // eliminate this face after CSG +#define FSIDE_NODIRT BIT( 8 ) // eliminate this face after CSG +#define FSIDE_PATCH BIT( 9 ) // ignore worldluxels +#define FSIDE_SCROLL BIT( 10 ) // doom scroll-side effect + +// brush flags +#define FBRUSH_CLIPONLY BIT( 0 ) // this is clipbrush +#define FBRUSH_NOCLIP BIT( 1 ) // only visible hull +#define FBRUSH_NOCSG BIT( 2 ) // ignore CSG operations for this brush +#define FBRUSH_REMOVE BIT( 3 ) // this brush should be removed at parse-time +#define FBRUSH_PATCH BIT( 4 ) // it's part of patch +#define FBRUSH_PRECISIONCLIP BIT( 5 ) // force old reliable code in some cases + +#define CONTENTS_NULL -99 // helper content + +// !!! if this is changed, it must be changed in bsp5.h too !!! +typedef struct +{ + vec3_t normal; + vec3_t origin; + vec_t dist; + int type; + int hash_chain; // hash chain (in BSP it was called a outplanenum ) +} plane_t; + +typedef union +{ + struct + { + vec_t UAxis[3]; + vec_t VAxis[3]; + vec_t shift[2]; + vec_t scale[2]; + vec_t rotate; + }; // Worldcraft, Hammer + + struct + { + vec_t vecs[2][4]; + }; // QuArK + struct + { + vec_t matrix[2][3]; + }; // Radiant (brush primitives) +} texvecs_t; + +typedef struct side_s +{ + char name[64]; // path to texture or shader + vec_t vecs[2][4]; // texture vectors + vec_t planepts[3][3]; // source points + shaderInfo_t *shader; // shader settings + short faceinfo; // terrain stuff + int planenum; // plane from points + int contents; // side contents + int flags; // side settings +} side_t; + +typedef struct bface_s +{ + struct bface_s *next; + int planenum; + plane_t *plane; + winding_t *w; + int texinfo; + int flags; // side settings + char contents[2]; // 0 = front side + vec3_t mins, maxs; +} bface_t; + +typedef struct +{ + vec3_t mins, maxs; + bface_t *faces; +} brushhull_t; + +typedef struct brush_s +{ + // debug info + int originalentitynum; // debug info + int originalbrushnum; // debug info + int entitynum; // upcast to entity + + // brush settings + int flags; // brush settings + char detaillevel; + int contents; + int csg_detaillevel( int hull ) const { return (hull == 0) ? detaillevel : 0; } + + CUtlArray sides; // source data + brushhull_t hull[MAX_MAP_HULLS];// output data +} brush_t; + +typedef struct mapent_s +{ +// this part shared with entity_t + epair_t *epairs; // head + epair_t *tail; // tail + vec3_t origin; + vec3_t absmin; + vec3_t absmax; + +// this part is unique for mapent_t + int firstbrush; + int numbrushes; + CUtlArray brushes; // parsed brushes (valid only at LoadMapFile) +} mapent_t; + +typedef struct +{ + char name[64]; + int width, height; + byte *data; + size_t datasize; + int wadnum; // wadlist[wadnum] +} mipentry_t; + +extern CUtlArray g_mapentities; +extern plane_t g_mapplanes[MAX_INTERNAL_MAP_PLANES]; +extern int g_nummapplanes; +extern brush_t g_mapbrushes[MAX_MAP_BRUSHES]; +extern int g_nummapbrushes; +extern int g_numparsedentities; +extern int g_numparsedbrushes; +extern int g_world_luxels; + +extern bool g_noclip; +extern bool g_wadtextures; +extern bool g_nullifytrigger; +extern bool g_onlyents; +extern vec_t g_csgepsilon; + +extern char g_pszWadInclude[MAX_TEXFILES][64]; +extern int g_nWadInclude; +extern int c_outfaces; + +//============================================================================= + +// textures.c + +void TEX_LoadTextures( CUtlArray *entities, bool merge ); +void TEX_FreeTextures( void ); +void WriteMiptex( void ); +void TEX_GetSize( const char *name, int *width, int *height ); +void TextureAxisFromNormal( vec3_t normal, vec3_t xv, vec3_t yv, bool brush_primitive ); +int TexinfoForSide( plane_t *plane, side_t *s, const vec3_t origin ); +short FaceinfoForTexinfo( const char *landname, const int in_texture_step, const int in_max_extent, const int groupid ); +void TextureAxisFromSide( const side_t *side, vec3_t xv, vec3_t yv, bool brush_primitive ); + +//============================================================================= + +// brush.c + +int PlaneFromPoints( const vec_t *p0, const vec_t *p1, const vec_t *p2 ); +int BrushContents( mapent_t *mapent, brush_t *b ); +brush_t *Brush_LoadEntity( mapent_t *ent, int hullnum ); +int PlaneTypeForNormal( const vec3_t normal ); +void CreateBrush( int brushnum, int threadnum = -1 ); +brush_t *AllocBrush( mapent_t *entity ); +side_t *AllocSide( brush_t *brush ); +void CreateBrushFaces( brush_t *b ); +void DeleteBrushFaces( brush_t *b ); +void ProcessAutoOrigins( void ); +void RestoreModelOrigins( void ); +void DumpBrushPlanes( void ); +void FreeHullFaces( void ); + +//============================================================================= + +// patch.c +void ParsePatch( mapent_t *mapent, short entindex, short faceinfo, short &brush_type ); + +//============================================================================= +// utils.c + +bool InsertASEModel( const char *modelName, mapent_t *mapent, short entindex, short faceinfo ); +void TEX_LoadTGA( const char *texname, mipentry_t *tex ); + +// map.c + +void LoadMapFile( const char *filename ); +void FreeMapEntity( mapent_t *mapent ); +void UnparseMapEntities( void ); +void FreeMapEntities( void ); +mapent_t *MapEntityForModel( const int modnum ); +mapent_t *FindTargetMapEntity( CUtlArray *entities, const char *target ); +bool IncludeMapFile( const char *filename, CUtlArray *entities, int index, bool external_map ); + +//============================================================================= + +// csg4.c + +bface_t *AllocFace( void ); +void FreeFace( bface_t *f ); +bface_t *NewFaceFromFace( const bface_t *in ); +bface_t *CopyFace( const bface_t *f ); +void UnlinkFaces( bface_t **head, bface_t *face = NULL ); +void EmitFace( int hull, const bface_t *f, int detaillevel = 0 ); +void EmitDetailBrush( int hull, const bface_t *faces ); +void ChopEntityBrushes( mapent_t *mapent ); +void WriteMapBrushes( brush_t *b, bface_t *outside ); + +//============================================================================= + +// hullfile.c + +void CheckHullFile( const char *filename ); diff --git a/utils/p2csg/csg4.cpp b/utils/p2csg/csg4.cpp new file mode 100644 index 0000000..8e61eaa --- /dev/null +++ b/utils/p2csg/csg4.cpp @@ -0,0 +1,586 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include "csg.h" + +int c_outfaces; +int g_firstbrush; + +/* +=========== +AllocFace +=========== +*/ +bface_t *AllocFace( void ) +{ + bface_t *f; + + f = (bface_t *)Mem_Alloc( sizeof( bface_t ), C_SURFACE ); +// ClearBounds( f->mins, f->maxs ); + f->planenum = -1; +// f->texinfo = -1; + + return f; +} + +/* +================== +FreeFace +================== +*/ +void FreeFace( bface_t *f ) +{ + if( !f ) return; + + if( f->w ) FreeWinding( f->w ); + Mem_Free( f, C_SURFACE ); +} + +/* +================== +NewFaceFromFace + +Duplicates the non point information of a face, used by SplitFace +================== +*/ +bface_t *NewFaceFromFace( const bface_t *in ) +{ + bface_t *newf; + + newf = AllocFace(); + newf->contents[0] = in->contents[0]; + newf->contents[1] = in->contents[1]; + newf->planenum = in->planenum; + newf->texinfo = in->texinfo; + newf->plane = in->plane; + newf->flags = in->flags; + + return newf; +} + +/* +================== +CopyFace +================== +*/ +bface_t *CopyFace( const bface_t *f ) +{ + bface_t *n; + + n = NewFaceFromFace( f ); + n->w = CopyWinding( f->w ); + VectorCopy( f->mins, n->mins ); + VectorCopy( f->maxs, n->maxs ); + + return n; +} + +/* +================== +CopyFacesToOutside + +Make a copy of all the faces of the brush, so they +can be chewed up by other brushes. + +All of the faces start on the outside list. +As other brushes take bites out of the faces, the +fragments are moved to the inside list, so they +can be freed when they are determined to be +completely enclosed in solid. +================== +*/ +bface_t *CopyFacesToOutside( brush_t *b, int num ) +{ + bface_t *f, *newf; + bface_t *outside; + + outside = NULL; + + for( f = b->hull[num].faces; f != NULL; f = f->next ) + { + newf = CopyFace( f ); + WindingBounds( newf->w, newf->mins, newf->maxs ); + newf->contents[1] = b->contents; + newf->next = outside; + outside = newf; + } + + return outside; +} + +/* +================== +UnlinkFaces + +release specified face or purge all chain +================== +*/ +void UnlinkFaces( bface_t **head, bface_t *face ) +{ + bface_t **prev = head; + bface_t *cur; + + while( 1 ) + { + cur = *prev; + if( !cur ) break; + + if( face != NULL && face != cur ) + { + prev = &cur->next; + continue; + } + + // unlink face from list + *prev = cur->next; + FreeFace( cur ); + } +} + +//============================================================ +/* +================== +SaveOutside + +The faces remaining on the outside list are final +polygons. Write them to the output file. + +Passable contents (water, lava, etc) will generate +a mirrored copy of the face to be seen from the inside. +================== +*/ +static void SaveOutside( brush_t *b, int hull, bface_t *outside ) +{ + bface_t *f, *f2, *next; + + for( f = outside; f != NULL; f = next ) + { + int frontcontents = f->contents[0]; + int texinfo = f->texinfo; + bool frontnull = false; + bool backnull = false; + int backcontents; + + next = f->next; + + if( b->contents == CONTENTS_EMPTY ) + backcontents = f->contents[1]; + else backcontents = b->contents; + + if( b->contents == CONTENTS_EMPTY ) + { + // SKIP and HINT are special textures for hlbsp + if( !FBitSet( f->flags, FSIDE_HINT|FSIDE_SKIP|FSIDE_SOLIDHINT )) + backnull = true; + } + + if( FBitSet( f->flags, FSIDE_SOLIDHINT )) + { + if( frontcontents != backcontents ) + frontnull = backnull = true; // not discardable, so remove "SOLIDHINT" texture name and behave like NULL + } + + if( b->entitynum != 0 && IsLiquidContents( f->contents[0] )) + backnull = true; // strip water face on one side + + if( WindingArea( f->w ) <= 0.0 ) + { + MsgDev( D_WARN, "Entity %i, Brush %i: tiny fragment\n", b->originalentitynum, b->originalbrushnum ); + FreeFace( f ); + continue; + } + + if( hull == HULL_VISIBLE ) + { + // count unique faces for visible hull + for( f2 = b->hull[hull].faces; f2 != NULL; f2 = f2->next ) + { + if( f2->planenum == f->planenum ) + { + if( !FBitSet( f2->flags, FSIDE_USED )) + { + SetBits( f2->flags, FSIDE_USED ); + c_outfaces++; + } + break; + } + } + } + + f->contents[0] = frontcontents; + f->texinfo = frontnull ? -1 : texinfo; + + // save front face + if( hull == HULL_VISIBLE ) + EmitFace( hull, f, b->detaillevel ); + else EmitFace( hull, f, 0 ); + + // filp face + f->planenum ^= 1; // reverse side + f->plane = &g_mapplanes[f->planenum]; + f->contents[0] = backcontents; + f->texinfo = backnull ? -1 : texinfo; + ReverseWinding( &f->w ); + + // save back face + if( hull == HULL_VISIBLE ) + EmitFace( hull, f, b->detaillevel ); + else EmitFace( hull, f, 0 ); + + FreeFace( f ); + } +} + +//========================================================================== +/* +=========== +CSGBrush +=========== +*/ +static void CSGBrush( int brushnum, int threadnum = -1 ) +{ + bface_t *f, *f2, *next; + brushhull_t *bh1, *bh2; + bool overwrite; + brush_t *b1, *b2; + bface_t *outside; + vec_t area; + mapent_t *e; + + brushnum = g_firstbrush + brushnum; + b1 = &g_mapbrushes[brushnum]; + e = &g_mapentities[b1->entitynum]; + + // for each of the hulls + for( int hull = 0; hull < MAX_MAP_HULLS; hull++ ) + { + bh1 = &b1->hull[hull]; + + // g-cont. if brush detaillevel is 0 it's not a detailbrush! + if( bh1->faces && b1->csg_detaillevel( hull ) > 0 ) + { + switch( b1->contents ) + { + case CONTENTS_EMPTY: + break; + case CONTENTS_SOLID: + EmitDetailBrush( hull, bh1->faces ); + break; + default: // g-cont. detail brushes can't be a liquid + COM_FatalError( "Entity %i, Brush %i: not allowed in detail\n", b1->originalentitynum, b1->originalbrushnum ); + break; + } + } + + // set outside to a copy of the brush's faces + outside = CopyFacesToOutside( b1, hull ); + overwrite = false; + + if( b1->contents == CONTENTS_EMPTY ) + { + for( f = outside; f != NULL; f = f->next ) + { + f->contents[0] = CONTENTS_EMPTY; + f->contents[1] = CONTENTS_EMPTY; + } + } + + // for each brush in entity e + for( int bn = 0; bn < e->numbrushes; bn++ ) + { + // see if b2 needs to clip a chunk out of b1 + if( e->firstbrush + bn == brushnum ) + continue; + + overwrite = (e->firstbrush + bn > brushnum) ? true : false; + b2 = &g_mapbrushes[e->firstbrush + bn]; + bh2 = &b2->hull[hull]; + + if( b2->contents == CONTENTS_EMPTY ) + continue; + + if( b2->csg_detaillevel( hull ) > b1->csg_detaillevel( hull )) + continue; // you can't chop + + // check if brush1 can overwrite brush2 + if( b2->contents == b1->contents ) + { + if( b1->csg_detaillevel( hull ) != b2->csg_detaillevel( hull )) + { + overwrite = (b2->csg_detaillevel( hull ) < b1->csg_detaillevel( hull )) ? true : false; + } + } + + if(( !bh2->faces ) || ( hull == 0 && FBitSet( b2->flags, FBRUSH_NOCSG ))) + continue; // brush isn't in this hull + + if( !BoundsIntersect( bh1->mins, bh1->maxs, bh2->mins, bh2->maxs )) + continue; + + // divide faces by the planes of the b2 to find which + // fragments are inside + for( f = outside, outside = NULL; f != NULL; f = next ) + { + next = f->next; + + if( !BoundsIntersect( bh2->mins, bh2->maxs, f->mins, f->maxs )) + { + // this face doesn't intersect brush2's bbox + f->next = outside; + outside = f; + continue; + } + + if( b2->csg_detaillevel( hull ) > b1->csg_detaillevel( hull )) + { + if( FBitSet( f->flags, FSIDE_NODRAW|FSIDE_SKIP|FSIDE_HINT|FSIDE_SOLIDHINT )) + { + // should not nullify the fragment inside detail brush + f->next = outside; + outside = f; + continue; + } + } + + // throw pieces on the front sides of the planes + // into the outside list, return the remains on the inside + + // find the fragment inside brush2 + winding_t *w = CopyWinding( f->w ); + + for( f2 = bh2->faces; f2 != NULL; f2 = f2->next ) + { + if( f->planenum == f2->planenum ) + { + if( !overwrite || FBitSet( f2->flags, FSIDE_NODRAW )) + { + // face plane is outside brush2 + FreeWinding( w ); + w = NULL; + break; + } + else + { + continue; + } + } + + if( f->planenum == ( f2->planenum ^ 1 )) + { +#if 0 + if( FBitSet( f2->flags, FSIDE_NODRAW )) + { + // face plane is outside brush2 + FreeWinding( w ); + w = NULL; + break; + } + else +#endif + { + continue; + } + } + + winding_t *fw, *bw; + + ClipWindingEpsilon( w, f2->plane->normal, f2->plane->dist, g_csgepsilon, &fw, &bw ); + + if( fw ) + { + FreeWinding( fw ); + } + + if( bw ) + { + FreeWinding( w ); + w = bw; + } + else + { + FreeWinding( w ); + w = NULL; + break; + } + } + + // do real split + if( w != NULL ) + { + for( f2 = bh2->faces; f2 != NULL; f2 = f2->next ) + { + if( f->planenum == f2->planenum || f->planenum == ( f2->planenum ^ 1 )) + { + continue; + } + + int valid = 0; + + for( int x = 0; x < w->numpoints; x++ ) + { + vec_t dist = DotProduct( w->p[x], f2->plane->normal ) - f2->plane->dist; + + if( dist >= -g_csgepsilon * 4 ) // only estimate + valid++; + } + + if( valid >= 2 ) + { + // this splitplane forms an edge + winding_t *fw, *bw; + + ClipWindingEpsilon( f->w, f2->plane->normal, f2->plane->dist, g_csgepsilon, &fw, &bw ); + + if( fw ) + { + bface_t *front = NewFaceFromFace( f ); + front->w = fw; + WindingBounds( fw, front->mins, front->maxs ); + front->next = outside; + outside = front; + } + + if( bw ) + { + FreeWinding( f->w ); + WindingBounds( bw, f->mins, f->maxs ); + f->w = bw; + } + else + { + FreeFace( f ); + f = NULL; + break; + } + } + } + + FreeWinding( w ); + } + else + { + f->next = outside; + outside = f; + f = NULL; + } + + area = f ? WindingArea( f->w ) : 0.0; + + if( f && area <= 0.0 ) + { + MsgDev( D_WARN, "Entity %i, Brush %i: tiny penetration\n", b1->originalentitynum, b1->originalbrushnum ); + FreeFace( f ); + f = NULL; + } + + if( f ) + { + // there is one convex fragment of the original + // face left inside brush2 + if( b2->csg_detaillevel( hull ) > b1->csg_detaillevel( hull )) + { + // don't chop or set contents, only nullify + f->next = outside; + outside = f; + SetBits( f->flags, FSIDE_NODRAW ); + f->texinfo = -1; + continue; + } + + if( b2->csg_detaillevel( hull ) < b1->csg_detaillevel( hull ) && b2->contents == CONTENTS_SOLID ) + { + // real solid + FreeFace( f ); + continue; + } + + if( b1->contents == CONTENTS_EMPTY ) + { + bool onfront = true; + bool onback = true; + + for( f2 = bh2->faces; f2 != NULL; f2 = f2->next ) + { + if( f->planenum == ( f2->planenum ^ 1 )) + onback = false; + if( f->planenum == f2->planenum ) + onfront = false; + } + + if( onfront && f->contents[0] < b2->contents ) + f->contents[0] = b2->contents; + + if( onback && f->contents[1] < b2->contents ) + f->contents[1] = b2->contents; + + if( f->contents[0] == CONTENTS_SOLID && f->contents[1] == CONTENTS_SOLID && FBitSet( f->flags, FSIDE_SOLIDHINT )) + { + FreeFace( f ); + } + else + { + f->next = outside; + outside = f; + } + continue; + } + + if( b1->contents > b2->contents || ( b1->contents == b2->contents && FBitSet( f->flags, FSIDE_SOLIDHINT ))) + { + // inside a water brush + f->contents[0] = b2->contents; + f->next = outside; + outside = f; + } + else + { + // inside a solid brush + FreeFace( f ); // throw it away + } + } + } + + } + + if( hull == HULL_VISIBLE ) + WriteMapBrushes( b1, outside ); + + // all of the faces left in outside are real surface faces + SaveOutside( b1, hull, outside ); + } +} + +/* +================== +ChopEntityBrushes + +Chop brushes for a gived entity +and dump result faces into text-format files +that called mapname.p0-3 +================== +*/ +void ChopEntityBrushes( mapent_t *mapent ) +{ + ASSERT( mapent->numbrushes > 0 ); + + // sort the contents down so stone bites water, etc + g_firstbrush = mapent->firstbrush; + + // csg them in order + if( mapent == &g_mapentities[0] ) + { + RunThreadsOnIndividual( mapent->numbrushes, true, CSGBrush ); + } + else + { + // brushmodels use silent threads + RunThreadsOnIndividual( mapent->numbrushes, false, CSGBrush ); + } +} \ No newline at end of file diff --git a/utils/p2csg/hulls.txt b/utils/p2csg/hulls.txt new file mode 100644 index 0000000..c617bb5 --- /dev/null +++ b/utils/p2csg/hulls.txt @@ -0,0 +1,4 @@ +( 0 0 0 ) ( 0 0 0 ) +( -16 -16 -24 ) ( 16 16 32 ) +( -32 -32 -32 ) ( 32 32 32 ) +( -16 -16 -24 ) ( 16 16 32 ) diff --git a/utils/p2csg/map.cpp b/utils/p2csg/map.cpp new file mode 100644 index 0000000..78156b5 --- /dev/null +++ b/utils/p2csg/map.cpp @@ -0,0 +1,1606 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include "csg.h" + +CUtlArray g_mapentities; +brush_t g_mapbrushes[MAX_MAP_BRUSHES]; +int g_nummapbrushes; +int g_numparsedentities; +int g_numparsedbrushes; + +static short g_groupid = 0; +static short g_world_faceinfo = -1; // shared faceinfo in case level-desginer apply it to the world but except landscapes +int g_world_luxels = 0; // alternative lightmap matrix will be used (luxels per world units instead of luxels per texels) +static int g_brushtype = BRUSH_UNKNOWN; + +const char *g_sMapType[BRUSH_COUNT] = +{ +"Unknown format", +"Worldcraft 2.1", +"Valve Hammer 3.4", +"Radiant", +"QuArK" +}; + +/* +================= +AllocSide + +allocate a new side +================= +*/ +side_t *AllocSide( brush_t *brush ) +{ + int sideidx = brush->sides.AddToTail(); + side_t *news = &brush->sides[sideidx]; + + memset( news, 0, sizeof( side_t )); + + return news; +} + +/* +================= +AllocBrush + +allocate a new local brush +================= +*/ +brush_t *AllocBrush( mapent_t *entity ) +{ + int brushidx = entity->brushes.AddToTail(); + brush_t *newb = &entity->brushes[brushidx]; + + memset( newb, 0, sizeof( brush_t )); + newb->sides.Purge(); + + return newb; +} + +/* +================= +AllocEntity + +allocate a new entity +================= +*/ +int AllocEntity( CUtlArray *entities ) +{ + int index = entities->AddToTail(); + mapent_t *mapent = &entities->Element( index ); + + memset( mapent, 0, sizeof( mapent_t )); + ClearBounds( mapent->absmin, mapent->absmax ); + + return index; +} + +/* +================= +CopyBrush + +copy specified brush into new one +================= +*/ +static brush_t *CopyBrush( mapent_t *entity, const brush_t *brush ) +{ + int brushidx = brush - entity->brushes.Base(); + brush_t *newb = AllocBrush( entity ); + + // maybe array is changed so we need to refresh pointer to source brush + brush = (const brush_t *)&entity->brushes[brushidx]; + memcpy( newb, brush, sizeof( brush_t )); + memset( &newb->sides, 0, sizeof( newb->sides )); + newb->sides.AddMultipleToTail( brush->sides.Count() ); + + // duplicate brush sides into newbrush + for( int i = 0; i < brush->sides.Count(); i++ ) + newb->sides[i] = brush->sides[i]; + + return newb; +} + +/* +================= +DeleteBrush + +delete brush from local array +================= +*/ +static void DeleteBrush( mapent_t *entity, const brush_t *brush ) +{ + int brushidx = brush - entity->brushes.Base(); + + if( brushidx < 0 || brushidx >= entity->brushes.Count()) + COM_FatalError( "DeleteBrush: invalid brush index %d\n", brushidx ); + + brush_t *b = &entity->brushes[brushidx]; + + b->sides.Purge(); // remove brush sides + entity->brushes.Remove( brushidx ); +} + +/* +================ +MoveBrushesToEntity + +copy all the brushes into another entity +================ +*/ +void MoveBrushesToEntity( CUtlArray *entities, mapent_t *dst, mapent_t *src ) +{ + int oldcount = dst->brushes.Count(); + // add the brushes to the tail of local array + dst->brushes.AddMultipleToTail( src->brushes.Count() ); + int entnum = dst - entities->Base(); + + for( int i = 0; i < src->brushes.Count(); i++ ) + { + brush_t *srcb = &src->brushes[i]; + brush_t *dstb = &dst->brushes[oldcount+i]; + + memcpy( dstb, srcb, sizeof( brush_t )); + memset( &dstb->sides, 0, sizeof( dstb->sides )); + dstb->sides.AddMultipleToTail( srcb->sides.Count() ); + dstb->entitynum = entnum; // need to setup entitynum + + // copy brush sides into newbrush + for( int j = 0; j < srcb->sides.Count(); j++ ) + dstb->sides[j] = srcb->sides[j]; + } +} + +/* +================= +EntityApplyTransform + +hierarchical transform brushes by entity settings +================= +*/ +static void EntityApplyTransform( mapent_t *src, mapent_t *dst, bool brushentity, bool external_map, vec_t yaw_offset = 0.0 ) +{ + vec3_t origin, angles, dscale, iscale; + matrix3x4 ptransform, itransform; + vec_t temp; + + GetVectorForKey( (entity_t *)src, "origin", origin ); + VectorSet( dscale, 1.0, 1.0, 1.0 ); + VectorClear( angles ); + + if( external_map ) + { + // TyrTtils external map + if( CheckKey( (entity_t *)src, "_external_map_angles" )) + GetVectorForKey( (entity_t *)src, "_external_map_angles", angles ); + + if( CheckKey( (entity_t *)src, "_external_map_angle" )) + angles[1] = FloatForKey( (entity_t *)src, "_external_map_angle" ); + + if( CheckKey( (entity_t *)src, "_external_map_scale" )) + { + switch( GetVectorForKey( (entity_t *)src, "_external_map_scale", iscale )) + { + case 1: + VectorSet( dscale, iscale[0], iscale[0], iscale[0] ); + break; + case 3: + VectorCopy( iscale, dscale ); + break; + } + } + } + else + { + temp = FloatForKey( (entity_t *)src, "modelscale" ); + if( temp != 0.0f ) VectorSet( dscale, temp, temp, temp ); + + if( CheckKey( (entity_t *)src, "modelscale_vec" )) + GetVectorForKey( (entity_t *)src, "modelscale_vec", dscale ); + + if( CheckKey( (entity_t *)src, "angles" )) + GetVectorForKey( (entity_t *)src, "angles", angles ); + + if( CheckKey( (entity_t *)src, "angle" )) + angles[1] = FloatForKey( (entity_t *)src, "angle" ); + } + + angles[1] += yaw_offset; + COM_NormalizeAngles( angles ); + VectorSet( iscale, 1.0 / dscale[0], 1.0 / dscale[1], 1.0 / dscale[2] ); + Matrix3x4_CreateFromEntityScale3f( ptransform, angles, origin, dscale ); + Matrix3x4_CreateFromEntityScale3f( itransform, angles, origin, iscale ); + + if( brushentity ) + { + for( int i = 0; i < dst->brushes.Count(); i++ ) + { + brush_t *b = &dst->brushes[i]; + + for( int j = 0; j < b->sides.Count(); j++ ) + { + side_t *s = &b->sides[j]; + + // transform plane points + Matrix3x4_VectorTransform( ptransform, s->planepts[0], s->planepts[0] ); + Matrix3x4_VectorTransform( ptransform, s->planepts[1], s->planepts[1] ); + Matrix3x4_VectorTransform( ptransform, s->planepts[2], s->planepts[2] ); + + // transform texture vectors + Matrix3x4_VectorRotate( itransform, s->vecs[0], s->vecs[0] ); + Matrix3x4_VectorRotate( itransform, s->vecs[1], s->vecs[1] ); + s->vecs[0][3] -= DotProduct( origin, s->vecs[0] ); + s->vecs[1][3] -= DotProduct( origin, s->vecs[1] ); + } + } + } + else + { + vec3_t dst_origin, dst_angles, dst_scale; + matrix3x4 dst_transform, out_matrix; + vec3_t default_scale, dummy; + + VectorSet( default_scale, 1.0, 1.0, 1.0 ); + + // a point entity + GetVectorForKey( (entity_t *)dst, "origin", dst_origin ); + GetVectorForKey( (entity_t *)dst, "angles", dst_angles ); + VectorSet( dst_scale, 1.0, 1.0, 1.0 ); + + if( CheckKey( (entity_t *)dst, "xform" )) + GetVectorForKey( (entity_t *)dst, "xform", dst_scale ); + else if( CheckKey( (entity_t *)dst, "scale" )) + { + temp = FloatForKey( (entity_t *)src, "scale" ); + if( temp != 0.0f ) VectorSet( dst_scale, temp, temp, temp ); + } + + // re-create source matrix with default scale + Matrix3x4_CreateFromEntityScale3f( ptransform, angles, origin, default_scale ); + Matrix3x4_CreateFromEntityScale3f( dst_transform, dst_angles, dst_origin, default_scale ); + Matrix3x4_ConcatTransforms( out_matrix, ptransform, dst_transform ); + Matrix3x4_MatrixToEntityScale3f( out_matrix, dst_origin, dst_angles, dummy ); + + // NOTE: point entities ignore scale for transformation + dst_scale[0] *= dscale[0]; + dst_scale[1] *= dscale[1]; + dst_scale[2] *= dscale[2]; + + // set transformed values back + SetVectorForKey( (entity_t *)dst, "origin", dst_origin, true ); + SetVectorForKey( (entity_t *)dst, "angles", dst_angles, true ); + SetVectorForKey( (entity_t *)dst, "xform", dst_scale, true ); + } +} + +/* +================= +SetupSideContents + +side contents must be set as soon as possible +================= +*/ +static void SetupSideContents( brush_t *brush, side_t *side ) +{ + if( !Q_strnicmp( side->name, "sky", 3 )) + { + SetBits( side->flags, FSIDE_SKY ); + side->contents = CONTENTS_SKY; + return; + } + + if( side->name[0] == '!' || side->name[0] == '*' ) + { + if( !Q_strnicmp( side->name + 1, "lava", 4 )) + side->contents = CONTENTS_LAVA; + else if( !Q_strnicmp( side->name + 1, "slime", 5 )) + side->contents = CONTENTS_SLIME; + else side->contents = CONTENTS_WATER; // otherwise it's water + + return; + } + + if( !Q_strnicmp( side->name, "water", 5 )) + { + side->contents = CONTENTS_WATER; + return; + } + + // special case for origin-brush + if( !Q_strnicmp( side->name, "origin", 6 )) + { + SetBits( brush->flags, FBRUSH_REMOVE ); + side->contents = CONTENTS_ORIGIN; + return; + } + + // this is needs only at build brush stage + if( !Q_strnicmp( side->name, "skip", 4 ) || !Q_strnicmp( side->name, "hintskip", 8 )) + { + SetBits( side->flags, FSIDE_SKIP ); + side->contents = CONTENTS_EMPTY; + return; + } + + if( side->name[0] == '@' || !Q_strnicmp( side->name, "translucent", 11 )) + { + side->contents = CONTENTS_TRANSLUCENT; + return; + } + + if( !Q_strnicmp( side->name, "fog", 3 )) + { + side->contents = CONTENTS_FOG; + return; + } + + if( !Q_strnicmp( side->name, "hint", 4 )) + { + SetBits( side->flags, FSIDE_HINT ); + side->contents = CONTENTS_EMPTY; + return; + } + + if( !Q_strnicmp( side->name, "solidhint", 9 )) + SetBits( side->flags, FSIDE_SOLIDHINT|FSIDE_NODRAW ); + + if( !Q_strnicmp( side->name, "null", 4 )) // structural clip + SetBits( side->flags, FSIDE_NODRAW ); + + if( !Q_strnicmp( side->name, "clip", 4 )) + SetBits( brush->flags, FBRUSH_CLIPONLY ); + + // all other it's a solid contents + side->contents = CONTENTS_SOLID; +} + +/* +================= +SetupSideParams + +load shader, apply side flags +================= +*/ +static void SetupSideParams( mapent_t *mapent, brush_t *brush, side_t *side ) +{ + const char *classname = ValueForKey( (entity_t *)mapent, "classname" ); + + // check for Quake1 issues + if( side->name[0] == '*' ) + side->name[0] = '!'; + + SetupSideContents( brush, side ); + + // try to find shader for this side + side->shader = ShaderInfoForShader( side->name ); + + // no user shader specified, ignore it + if( !FBitSet( side->shader->flags, FSHADER_DEFAULTED )) + { + // update contents from shader + if( side->shader->contents ) + side->contents = side->shader->contents; + + if( side->shader->contents == CONTENTS_SKY ) + SetBits( side->flags, FSIDE_SKY ); + + if( FBitSet( side->shader->flags, FSHADER_NOCLIP )) + SetBits( brush->flags, FBRUSH_NOCLIP ); + + if( FBitSet( side->shader->flags, FSHADER_TRIGGER )) + SetBits( brush->flags, FBRUSH_CLIPONLY ); + + if( FBitSet( side->shader->flags, FSHADER_NODRAW )) + SetBits( side->flags, FSIDE_NODRAW ); + + if( FBitSet( side->shader->flags, FSHADER_DETAIL )) + brush->detaillevel = 1; + + if( FBitSet( side->shader->flags, FSHADER_NOLIGHTMAP )) + SetBits( side->flags, FSIDE_NOLIGHTMAP ); + + if( FBitSet( side->shader->flags, FSHADER_SKIP )) + SetBits( side->flags, FSIDE_SKIP ); + + if( FBitSet( side->shader->flags, FSHADER_CLIP )) + SetBits( brush->flags, FBRUSH_CLIPONLY ); + + if( FBitSet( side->shader->flags, FSHADER_HINT )) + { + Q_strncpy( side->name, "HINT", sizeof( side->name )); + SetBits( side->flags, FSIDE_HINT ); + } + + if( FBitSet( side->shader->flags, FSHADER_REMOVE )) + SetBits( brush->flags, FBRUSH_REMOVE ); + } + + // don't compute lightmaps for sky and triggers + if( side->contents == CONTENTS_SKY ) + SetBits( side->flags, FSIDE_NOLIGHTMAP ); + + if( !Q_strnicmp( side->name, "trigger", 7 ) || !Q_strnicmp( side->name, "aaatrigger", 10 )) + { + SetBits( side->flags, FSIDE_NOLIGHTMAP ); + SetBits( brush->flags, FBRUSH_NOCSG ); + } + + // for each face of each brush of this entity + if( BoolForKey( (entity_t *)mapent, "zhlt_invisible" ) && side->contents != CONTENTS_ORIGIN ) + SetBits( side->flags, FSIDE_NODRAW ); + + const int shadow = IntForKey( (entity_t *)mapent, "_shadow" ); + + if( IntForKey( (entity_t *)mapent, "_dirt" ) == -1 ) + SetBits( side->flags, FSIDE_NODIRT ); + + if( shadow == -1 ) + SetBits( side->flags, FSIDE_NOSHADOW ); + + if( !Q_stricmp( classname, "func_detail_illusionary" )) + { + // mark these entities as TEX_NOSHADOW unless the mapper set "_shadow" "1" + if( shadow != 1 ) SetBits( side->flags, FSIDE_NOSHADOW ); + } +} + +/* +================= +SetupTextureVectors + +parse and setup tex->vecs +================= +*/ +static void SetupTextureVectors( mapent_t *mapent, brush_t *brush, side_t *side, short &brush_type ) +{ + int side_flags = 0; + bool read_flags; + texvecs_t tex_vects; + + if( brush_type == BRUSH_RADIANT ) + Parse2DMatrix( 2, 3, (vec_t *)tex_vects.matrix ); + + // read the texturedef + GetToken( false ); + + Q_strncpy( side->name, token, sizeof( side->name )); + SetupSideParams( mapent, brush, side ); + + // continue determine brush type + if( brush_type != BRUSH_RADIANT ) + GetToken( false ); + + if( brush_type == BRUSH_WORLDCRAFT_22 || !Q_strcmp( token, "[" )) // Worldcraft 2.2+ + { + brush_type = BRUSH_WORLDCRAFT_22; + + // texture U axis + if( Q_strcmp( token, "[" )) + COM_FatalError( "missing '[' in texturedef (U) at line %i\n", scriptline ); + + GetToken( false ); + tex_vects.UAxis[0] = atof( token ); + GetToken( false ); + tex_vects.UAxis[1] = atof( token ); + GetToken( false ); + tex_vects.UAxis[2] = atof( token ); + GetToken( false ); + tex_vects.shift[0] = atof( token ); + GetToken( false ); + + if( Q_strcmp( token, "]" )) + COM_FatalError( "missing ']' in texturedef (U) at line %i\n", scriptline ); + + // texture V axis + GetToken( false ); + + if( Q_strcmp( token, "[" )) + COM_FatalError( "missing '[' in texturedef (V) at line %i\n", scriptline ); + + GetToken( false ); + tex_vects.VAxis[0] = atof( token ); + GetToken( false ); + tex_vects.VAxis[1] = atof( token ); + GetToken( false ); + tex_vects.VAxis[2] = atof( token ); + GetToken( false ); + tex_vects.shift[1] = atof( token ); + GetToken( false ); + + if( Q_strcmp( token, "]" )) + COM_FatalError( "missing ']' in texturedef (V) at line %i\n", scriptline ); + + // texture rotation is implicit in U/V axes. + GetToken( false ); + tex_vects.rotate = 0.0; + + // texure scale + GetToken( false ); + tex_vects.scale[0] = atof( token ); + GetToken( false ); + tex_vects.scale[1] = atof( token ); + } + else if( brush_type == BRUSH_WORLDCRAFT_21 || brush_type == BRUSH_QUARK ) + { + // worldcraft 2.1-, old Radiant, QuArK + tex_vects.shift[0] = atof( token ); + GetToken( false ); + tex_vects.shift[1] = atof( token ); + GetToken( false ); + tex_vects.rotate = atof( token ); + GetToken( false ); + tex_vects.scale[0] = atof( token ); + GetToken( false ); + tex_vects.scale[1] = atof( token ); + } + + // can't read it here because we're unfinished detecting map type + read_flags = TryToken( ) ? true : false; + if( read_flags ) side_flags = atoi( token ); + + if( g_TXcommand == '1' || g_TXcommand == '2' ) + { + vec_t a, b, c, d, determinant; + vec3_t vecs[2]; + + brush_type = BRUSH_QUARK; + + // we are QuArK mode and need to translate some numbers to align textures it's way + // from QuArK, the texture vectors are given directly from the three points + switch( g_TXcommand - '0' ) + { + case 1: + VectorSubtract( side->planepts[2], side->planepts[0], vecs[0] ); + VectorSubtract( side->planepts[1], side->planepts[0], vecs[1] ); + break; + case 2: + VectorSubtract( side->planepts[1], side->planepts[0], vecs[0] ); + VectorSubtract( side->planepts[2], side->planepts[0], vecs[1] ); + break; + default: + COM_FatalError( "QuArK: bad TX%d type\n", g_TXcommand - '0' ); + } + + // default QuArK scale correction + VectorScale( vecs[0], 1.0 / 128.0, vecs[0] ); + VectorScale( vecs[1], 1.0 / 128.0, vecs[1] ); + + a = DotProduct( vecs[0], vecs[0] ); + b = DotProduct( vecs[0], vecs[1] ); + c = DotProduct( vecs[1], vecs[0] ); // b == c + d = DotProduct( vecs[1], vecs[1] ); + determinant = a * d - b * c; + + if( fabs( determinant ) > 1e-6 ) + { + for( int i = 0; i < 3; i++ ) + { + side->vecs[0][i] = (d * vecs[0][i] - b * vecs[1][i]) / determinant; + side->vecs[1][i] =-(a * vecs[1][i] - c * vecs[0][i]) / determinant; + } + + side->vecs[0][3] = -DotProduct( side->vecs[0], side->planepts[0] ); + side->vecs[1][3] = -DotProduct( side->vecs[1], side->planepts[0] ); + } + else + { + MsgDev( D_WARN, "Degenerate QuArK-style brush texture: Entity %i, Brush %i\n", + brush->originalentitynum, brush->originalbrushnum ); + } + } + else if( brush_type == BRUSH_WORLDCRAFT_21 || brush_type == BRUSH_WORLDCRAFT_22 ) + { + vec3_t axis[2]; + int i, j; + + if( brush_type == BRUSH_WORLDCRAFT_21 ) + TextureAxisFromSide( side, axis[0], axis[1], false ); + + if( !tex_vects.scale[0] ) tex_vects.scale[0] = 1.0; + if( !tex_vects.scale[1] ) tex_vects.scale[1] = 1.0; + + if( brush_type == BRUSH_WORLDCRAFT_21 ) + { + vec_t sinv, cosv; + vec_t ns, nt; + int sv, tv; + + // rotate axis + if( tex_vects.rotate == 0.0 ) + { + sinv = 0.0; + cosv = 1.0; + } + else if( tex_vects.rotate == 90.0 ) + { + sinv = 1.0; + cosv = 0.0; + } + else if( tex_vects.rotate == 180.0 ) + { + sinv = 0.0; + cosv = -1.0; + } + else if( tex_vects.rotate == 270.0 ) + { + sinv = -1.0; + cosv = 0.0; + } + else + { + vec_t ang = DEG2RAD( tex_vects.rotate ); + + sinv = sin( ang ); + cosv = cos( ang ); + } + + if( axis[0][0] ) + sv = 0; + else if( axis[0][1] ) + sv = 1; + else sv = 2; + + if( axis[1][0] ) + tv = 0; + else if( axis[1][1] ) + tv = 1; + else tv = 2; + + for( i = 0; i < 2; i++ ) + { + ns = cosv * axis[i][sv] - sinv * axis[i][tv]; + nt = sinv * axis[i][sv] + cosv * axis[i][tv]; + axis[i][sv] = ns; + axis[i][tv] = nt; + } + + for( i = 0; i < 2; i++ ) + { + for( j = 0; j < 3; j++ ) + { + side->vecs[i][j] = axis[i][j] / tex_vects.scale[i]; + } + } + } + else if( brush_type == BRUSH_WORLDCRAFT_22 ) + { + for( j = 0; j < 3; j++ ) + side->vecs[0][j] = tex_vects.UAxis[j] / tex_vects.scale[0]; + for( j = 0; j < 3; j++ ) + side->vecs[1][j] = tex_vects.VAxis[j] / tex_vects.scale[1]; + } + + side->vecs[0][3] = tex_vects.shift[0]; + side->vecs[1][3] = tex_vects.shift[1]; + } + else if( brush_type == BRUSH_RADIANT ) + { + int width, height; + vec3_t axis[2]; + + TEX_GetSize( side->shader->imagePath, &width, &height ); + TextureAxisFromSide( side, axis[0], axis[1], true ); + + side->vecs[0][0] = width * ((axis[0][0] * tex_vects.matrix[0][0]) + (axis[1][0] * tex_vects.matrix[0][1])); + side->vecs[0][1] = width * ((axis[0][1] * tex_vects.matrix[0][0]) + (axis[1][1] * tex_vects.matrix[0][1])); + side->vecs[0][2] = width * ((axis[0][2] * tex_vects.matrix[0][0]) + (axis[1][2] * tex_vects.matrix[0][1])); + side->vecs[0][3] = width * tex_vects.matrix[0][2]; + + side->vecs[1][0] = height * ((axis[0][0] * tex_vects.matrix[1][0]) + (axis[1][0] * tex_vects.matrix[1][1])); + side->vecs[1][1] = height * ((axis[0][1] * tex_vects.matrix[1][0]) + (axis[1][1] * tex_vects.matrix[1][1])); + side->vecs[1][2] = height * ((axis[0][2] * tex_vects.matrix[1][0]) + (axis[1][2] * tex_vects.matrix[1][1])); + side->vecs[1][3] = height * tex_vects.matrix[1][2]; + } + + // Quake3 detail brushes or Volatile3D detail brushes + if( FBitSet( side_flags, 0x8000000 ) || FBitSet( side_flags, 0x400000 )) + { + // enable detaillevel feature on solid brushes + if( side->contents == CONTENTS_SOLID ) + brush->detaillevel = 1; +// SetBits( brush->flags, FBRUSH_NOCSG ); + } + + // Doom2Gold extrainfo + if( g_DXspecial != 0 ) + { + dfaceinfo_t *fi = NULL; + short doomlight; + + if( FBitSet( g_DXspecial, (1<<16) )) + { + SetBits( side->flags, FSIDE_SCROLL ); + ClearBits( g_DXspecial, (1<<16)); + } + + doomlight = (short)g_DXspecial; + + // check for already exist faceinfo + if( side->faceinfo != -1 ) + { + fi = &g_dfaceinfo[side->faceinfo]; + side->faceinfo = FaceinfoForTexinfo( fi->landname, fi->texture_step, fi->max_extent, doomlight ); + } + else + { + // build from scratch + side->faceinfo = FaceinfoForTexinfo( "", TEXTURE_STEP, MAX_SURFACE_EXTENT, doomlight ); + } + } + + // first token it's already waiting + if( read_flags ) + { + TryToken(); // unused + TryToken(); // unused + } +} + +/* +================= +ParseBrush +================= +*/ +static void ParseBrush( mapent_t *mapent, short entindex, short faceinfo, short &brush_type ) +{ + brush_t *brush; + side_t *side; + int i; + + if( brush_type == BRUSH_RADIANT ) + CheckToken( "{" ); + + brush = AllocBrush( mapent ); + brush->originalentitynum = g_numparsedentities; + brush->originalbrushnum = g_numparsedbrushes; + brush->entitynum = entindex; + + // read ZHLT settings for this brush + brush->detaillevel = Q_max( IntForKey( (entity_t *)mapent, "zhlt_detaillevel" ), 0 ); + + if( BoolForKey( (entity_t *)mapent, "zhlt_noclip" )) + SetBits( brush->flags, FBRUSH_NOCLIP ); + if( BoolForKey( (entity_t *)mapent, "zhlt_nocsg" )) + SetBits( brush->flags, FBRUSH_NOCSG ); + + while( 1 ) + { + g_TXcommand = 0; + g_DXspecial = 0; + + if( !GetToken( true )) + break; + + if( !Q_strcmp( token, "}" )) + break; + + if( brush_type == BRUSH_RADIANT ) + { + while( 1 ) + { + if( Q_strcmp( token, "(" )) + GetToken( false ); + else break; + GetToken( true ); + } + } + + side = AllocSide( brush ); + UnGetToken(); + + // read the three point plane definition + Parse1DMatrix( 3, side->planepts[0] ); + Parse1DMatrix( 3, side->planepts[1] ); + Parse1DMatrix( 3, side->planepts[2] ); + + // store faceinfo number for group of brushes. Otherwise write -1 + side->faceinfo = faceinfo; + + SetupTextureVectors( mapent, brush, side, brush_type ); + } + + if( brush_type == BRUSH_RADIANT ) + { + UnGetToken(); + CheckToken( "}" ); + CheckToken( "}" ); + } + + brush->contents = BrushContents( mapent, brush ); + + // check for faces that should be a nullify + for( i = 0; i < brush->sides.Count(); i++ ) + { + side = &brush->sides[i]; + + // remove aaatrigger from the world and brush entities + if(( g_nullifytrigger || brush->entitynum == 0 ) && ( !Q_strnicmp( side->name, "AAATRIGGER", 10 ) || !Q_strnicmp( side->name, "trigger", 7 ))) + SetBits( side->flags, FSIDE_NODRAW ); + } + + // origin brushes are removed, but they set + // the rotation origin for the rest of the brushes + // in the entity + if( brush->contents == CONTENTS_ORIGIN ) + { + char string[32]; + vec3_t origin; + + CreateBrushFaces( brush ); // to get sizes + DeleteBrushFaces( brush ); + + if( brush->entitynum != 0 ) + { + // ignore for WORLD (code elsewhere enforces no ORIGIN in world message) + VectorAverage( brush->hull[0].mins, brush->hull[0].maxs, origin ); + Q_snprintf( string, sizeof( string ), "%i %i %i", (int)origin[0], (int)origin[1], (int)origin[2] ); + SetKeyValue( (entity_t *)mapent, "origin", string ); + } + } + + // brush will no longer used and should be removed + if( FBitSet( brush->flags, FBRUSH_REMOVE )) + { + DeleteBrush( mapent, brush ); + return; + } + + // apply clip to the sky (NOTE: after call CopyBrush brush ptr is possibly invalidate) + if( brush->contents == CONTENTS_SKY && !FBitSet( brush->flags, FBRUSH_NOCLIP )) + { + brush_t *newb = CopyBrush( mapent, brush ); + SetBits( newb->flags, FBRUSH_CLIPONLY ); + newb->contents = CONTENTS_SOLID; + } +} + +/* +================ +ParseMapEntity +================ +*/ +short GetFaceInfoForEntity( mapent_t *mapent ) +{ + if( g_onlyents ) return -1; // don't modify g_dfaceinfo! + + // NOTE: we guranteed what this function called only for non-point entities + const char *landname = ValueForKey( (entity_t *)mapent, "zhlt_landscape" ); + int texture_step = Q_max( 0, IntForKey( (entity_t *)mapent, "zhlt_texturestep" )); + int max_extent = Q_max( 0, IntForKey( (entity_t *)mapent, "zhlt_maxextent" )); + int global_texture_step = TEXTURE_STEP, global_max_extent = MAX_SURFACE_EXTENT; + int lmscale = TEXTURE_STEP * Q_max( 0, FloatForKey( (entity_t *)mapent, "_lmscale" )); + short faceinfo = -1; + + // TyrUtils: handle _lmscale feature + if( lmscale && lmscale != TEXTURE_STEP ) + texture_step = lmscale; + + // update global settings + if( g_world_faceinfo != -1 ) + { + global_texture_step = g_dfaceinfo[g_world_faceinfo].texture_step; + global_max_extent = g_dfaceinfo[g_world_faceinfo].max_extent; + } + + // setup the global parms for world entity + if( g_mapentities.Count() == 1 ) + { + // if user not specified landscape but desire to change lightmap resolution\subdiv size globally + if( !ValueForKey( (entity_t *)mapent, "zhlt_landscape", true )) + { + // get defaults + if( !max_extent ) max_extent = MAX_SURFACE_EXTENT; + if( !texture_step ) texture_step = TEXTURE_STEP; + + // check for default values + if( max_extent == MAX_SURFACE_EXTENT && texture_step == TEXTURE_STEP ) + return -1; // nothing changed + + // store a global settings for lightmap resoltion\subdiv size + g_world_faceinfo = FaceinfoForTexinfo( landname, texture_step, max_extent, 0 ); + + return g_world_faceinfo; + } + else // user specified landscape for world-entity, so all other entities will be ingnore them + { + // get defaults + if( !max_extent ) max_extent = MAX_SURFACE_EXTENT; + if( !texture_step ) texture_step = TEXTURE_STEP; + g_groupid++; // increase group number because landscape was specified + + // now we specified a global landscape for all the world brushes but except all other entities (include func_group etc) + faceinfo = FaceinfoForTexinfo( landname, texture_step, max_extent, g_groupid ); + + return faceinfo; + } + } + else // all other non-point ents + { + // all the fields are completely missed, so we can use world setttings + if( !CheckKey( (entity_t *)mapent, "zhlt_landscape" ) && !CheckKey( (entity_t *)mapent, "zhlt_texturestep" ) && !CheckKey( (entity_t *)mapent, "zhlt_maxextent" )) + { + return g_world_faceinfo; // nothing changed + } + + // if user not specified landscape but desire to change lightmap resolution\subdiv size + if( !ValueForKey( (entity_t *)mapent, "zhlt_landscape", true )) + { + // get defaults + if( !max_extent ) max_extent = global_max_extent; + if( !texture_step ) texture_step = global_texture_step; + + // check for default values + if( max_extent == global_max_extent && texture_step == global_texture_step ) + return g_world_faceinfo; // nothing changed + + // store a global settings for lightmap resoltion\subdiv size + faceinfo = FaceinfoForTexinfo( landname, texture_step, max_extent, 0 ); + + return faceinfo; + } + else // user specified landscape for world-entity, so all other entities will be ingnore them + { + // get defaults + if( !max_extent ) max_extent = global_max_extent; + if( !texture_step ) texture_step = global_texture_step; + g_groupid++; // increase group number because landscape was specified + + // now we specified a global landscape for all the world brushes but except all other entities (include func_group etc) + faceinfo = FaceinfoForTexinfo( landname, texture_step, max_extent, g_groupid ); + + return faceinfo; + } + } +} + +/* +================ +ParseMapEntity +================ +*/ +bool ParseMapEntity( CUtlArray *entities, bool external = false ) +{ + short brush_type; + short faceinfo = -1; + char str[128]; + mapent_t *mapent; + int index; + epair_t *e; + + brush_type = BRUSH_UNKNOWN; + g_numparsedbrushes = 0; + + if( !GetToken( true )) + return false; + + if( Q_strcmp( token, "{" )) + COM_FatalError( "ParseEntity: { not found at line %i\n", scriptline ); + + index = AllocEntity( entities ); + mapent = &entities->Element( index ); + + while( 1 ) + { + if( !GetToken( true )) + COM_FatalError( "ParseEntity: EOF without closing brace at line %i\n", scriptline ); + + if( !Q_strcmp( token, "}" )) + { + // in case worldspawn doesn't contain any brushes + if( entities->Count() == 1 && !mapent->brushes.Count( )) + TEX_LoadTextures( entities, external ); + break; + } + + if( !Q_strcmp( token, "{" )) + { + // parse a brush or patch + if( !GetToken( true )) + break; + + // "wad" field is now valid and can be parsed + if( entities->Count() == 1 && !mapent->brushes.Count( )) + TEX_LoadTextures( entities, external ); + + if( !mapent->brushes.Count( )) + faceinfo = GetFaceInfoForEntity( mapent ); + + if( !Q_strcmp( token, "patchDef2" )) + { + // NOTE: patchDef may be combined with old brush descripton + ParsePatch( mapent, index, faceinfo, brush_type ); + g_numparsedbrushes++; + } + else if( !Q_strcmp( token, "terrainDef" )) + { + MsgDev( D_REPORT, "terrains not supported, skipping terrain on line %d\n", scriptline ); + SkipBracedSection( 0 ); + } + else if( !Q_strcmp( token, "brushDef" )) + { + brush_type = BRUSH_RADIANT; + ParseBrush( mapent, index, faceinfo, brush_type ); // parse brush primitive + g_numparsedbrushes++; + } + else + { + // predict state + if( brush_type == BRUSH_UNKNOWN ) + brush_type = BRUSH_WORLDCRAFT_21; + + // QuArK or WorldCraft map + UnGetToken(); + ParseBrush( mapent, index, faceinfo, brush_type ); + g_numparsedbrushes++; + } + } + else + { + e = ParseEpair (); + + if( mapent->brushes.Count() > 0 ) + MsgDev( D_WARN, "ParseMapEntity: keyvalue comes after brushes\n" ); //--vluzacn + + if( !external && entities->Count() == 1 && !Q_strcmp( e->key, "zhlt_worldluxels" )) + g_world_luxels = atoi( e->value ); + + InsertLinkBefore( e, (entity_t *)mapent ); + } + } + + if( !external && entities->Count() == 1 ) + { + // let the map tell which version of the compiler it comes from, to help tracing compiler bugs. + Q_snprintf( str, sizeof( str ), "%s (%s)", COMPILERSTRING, __DATE__ ); + SetKeyValue( (entity_t *)mapent, "_compiler", str ); // g-cont. don't pass this field into game dlls + } + + // save off to displaying the map format + if( g_brushtype == BRUSH_UNKNOWN ) + g_brushtype = brush_type; + + GetVectorForKey( (entity_t *)mapent, "origin", mapent->origin ); + + const char *classname = ValueForKey( (entity_t *)mapent, "classname" ); + + // group entities are just for editor convenience toss all brushes into the world entity + if( !Q_strcmp( "func_group", classname ) || !Q_strcmp( "func_landscape", classname ) || !Q_strncmp( "func_detail", classname, 11 )) + { + MoveBrushesToEntity( entities, &entities->Element( 0 ), mapent ); + FreeMapEntity( mapent ); // throw all key-value pairs + entities->Remove( index ); + + return true; + } + + // processing the external mapfile + if( !Q_strcmp( classname, "misc_model" )) + { + char modelpath[64]; + + Q_strncpy( modelpath, ValueForKey((entity_t *)mapent, "model" ), sizeof( modelpath )); + COM_ReplaceExtension( modelpath, ".mdl" ); + + if( FS_FileExists( modelpath, false )) + { + // keep this entity and convert into env_static + SetKeyValue((entity_t *)mapent, "classname", "env_static" ); + SetKeyValue((entity_t *)mapent, "model", modelpath ); + SetKeyValue((entity_t *)mapent, "spawnflags", "1" ); // TESTTEST + classname = ValueForKey( (entity_t *)mapent, "classname" ); // refresh the pointer + + // rename some keys + if( CheckKey( (entity_t *)mapent, "modelscale_vec" )) + RenameKey( (entity_t *)mapent, "modelscale_vec", "xform" ); + if( CheckKey( (entity_t *)mapent, "modelscale" )) + RenameKey( (entity_t *)mapent, "modelscale_vec", "scale" ); + } + else + { + Q_strncpy( modelpath, ValueForKey((entity_t *)mapent, "model" ), sizeof( modelpath )); + IncludeMapFile( modelpath, entities, index, false ); + FreeMapEntity( mapent ); // throw all key-value pairs + entities->Remove( index ); + } + + return true; + } + + // processing the external mapfile Tyr-Utils (not recursive) + if( !external && !Q_strcmp( classname, "misc_external_map" )) + { + char modelpath[64]; + + Q_strncpy( modelpath, ValueForKey((entity_t *)mapent, "_external_map" ), sizeof( modelpath )); + + if( FS_FileExists( modelpath, false )) + { + // keep this entity and convert into specified classname + SetKeyValue((entity_t *)mapent, "classname", ValueForKey((entity_t *)mapent, "_external_map_classname" )); + RemoveKey( (entity_t *)mapent, "_external_map_classname" ); + RemoveKey( (entity_t *)mapent, "_external_map" ); + classname = ValueForKey( (entity_t *)mapent, "classname" ); // refresh the pointer + + IncludeMapFile( modelpath, entities, index, true ); + + // remove source keys + RemoveKey( (entity_t *)mapent, "_external_map_angles" ); + RemoveKey( (entity_t *)mapent, "_external_map_angle" ); + RemoveKey( (entity_t *)mapent, "_external_map_scale" ); + RemoveKey( (entity_t *)mapent, "origin" ); + } + else + { + // failed to loading external map + entities->Remove( index ); + } + + return true; + } + + if( !external && !Q_strcmp( classname, "env_cubemap" )) + { + if( g_numcubemaps == MAX_MAP_CUBEMAPS ) + COM_FatalError( "MAX_MAP_CUBEMAPS limit exceeded\n" ); + + // save off cubemap positions + dcubemap_t *pCubemap = &g_dcubemaps[g_numcubemaps]; + pCubemap->origin[0] = (short)mapent->origin[0]; + pCubemap->origin[1] = (short)mapent->origin[1]; + pCubemap->origin[2] = (short)mapent->origin[2]; + pCubemap->size = (short)IntForKey( (entity_t *)mapent, "cubemapsize" ); + FreeMapEntity( mapent ); // throw all key-value pairs + entities->Remove( index ); + g_numcubemaps++; + + return true; + } + + // auto-enum targets + if( !Q_strcmp( classname, "multi_switcher" )) + { + char string[32]; + int count = 0; + + for( e = mapent->epairs; e; e = e->next ) + { + if( !Q_strcmp( e->key, "mode" )) + continue; // ignore mode + + // only change if value is 0 + if( Q_isdigit( e->value ) && !atoi( e->value )) + { + Q_snprintf( string, sizeof( string ), "%d", count ); + freestring( e->value ); + e->value = copystring( string ); + count++; + } + } + } + + if( fabs( mapent->origin[0] ) > WORLD_MAXS || fabs( mapent->origin[1] ) > WORLD_MAXS || fabs( mapent->origin[2] ) > WORLD_MAXS ) + { + if( Q_strncmp( classname, "light", 5 )) + { + MsgDev( D_WARN, "Entity %i (classname \"%s\"): origin outside +/-%.0f: (%.0f,%.0f,%.0f)", + g_numparsedentities, classname, (double)WORLD_MAXS, mapent->origin[0], mapent->origin[1], mapent->origin[2] ); + } + } + + return true; +} + +/* +================ +IncludeMapFile +================ +*/ +bool IncludeMapFile( const char *filename, CUtlArray *entities, int index, bool external_map ) +{ + mapent_t *mapent = &entities->Element( index ); + const char *ext = COM_FileExtension( filename ); + int oldentitynum = g_numparsedentities; + int oldbrushtype = g_brushtype; + mapent_t *localworld, *target; + CUtlArray localents; + char path[256]; + int i; + + // only .ase and .map is allowed + if( Q_stricmp( ext, "ase" ) && Q_stricmp( ext, "map" )) + return false; + + int spawnflags = IntForKey((entity_t *)mapent, "spawnflags" ); + + if( InsertASEModel( filename, mapent, index, -1 )) + { + MsgDev( D_INFO, "include: %s\n", filename ); + + for( int i = 0; i < mapent->brushes.Count(); i++ ) + { + brush_t *b = &mapent->brushes[i]; + + if( !FBitSet( spawnflags, 2 )) + SetBits( b->flags, FBRUSH_NOCLIP ); + SetBits( b->flags, FBRUSH_NOCSG ); + } + + // all the remaining brushes it our included model. transform it + EntityApplyTransform( mapent, mapent, true, false, -90.0 ); + + // find the corresponding entity which we send brushes from this model + target = FindTargetMapEntity( entities, ValueForKey((entity_t *)mapent, "target" )); + if( !target ) target = &entities->Element( 0 ); // move to world as default + + // moving our transformed brushes into supposed entity + MoveBrushesToEntity( entities, target, mapent ); + + return true; + } + + Q_strncpy( path, filename, sizeof( path )); + COM_ReplaceExtension( path, ".map" ); + + if( !FS_FileExists( path, false )) + return false; + + // parse sub-map with models + IncludeScriptFile( path ); + g_numparsedentities = g_numparsedbrushes = 0; + g_brushtype = BRUSH_UNKNOWN; + localents.Purge(); + + while( ParseMapEntity( &localents, true )) + { + g_numparsedentities++; + } + + // deal only with world entity (all the misc_models should be recursive include into the worldentity) + localworld = &localents[0]; + + MsgDev( D_INFO, "include: %s ^2%s^7\n", path, g_sMapType[g_brushtype] ); + MsgDev( D_REPORT, "\n" ); + MsgDev( D_REPORT, "%5i brushes\n", localworld->brushes.Count()); + MsgDev( D_REPORT, "%5i entities\n", localents.Count() ); +restart: + if( !external_map ) + { + // Quake3 style recursive-included prefabs (keep only detail brushes) + for( i = 0; i < localworld->brushes.Count(); i++ ) + { + brush_t *b = &localworld->brushes[i]; + + // remove non-detail brushes, this is a debug stuff + if( b->detaillevel ) continue; + + DeleteBrush( localworld, b ); + goto restart; // array is changed, restart + } + + for( i = 0; i < localworld->brushes.Count(); i++ ) + { + brush_t *b = &localworld->brushes[i]; + + if( !FBitSet( spawnflags, 2 )) + SetBits( b->flags, FBRUSH_NOCLIP ); +// SetBits( b->flags, FBRUSH_NOCSG ); + } + } + + mapent = &entities->Element( index ); // refresh the source pointer + // all the remaining brushes it our included model. transform it + EntityApplyTransform( mapent, localworld, true, external_map ); + + if( !external_map ) + { + // find the corresponding entity which we send brushes from this model + target = FindTargetMapEntity( entities, ValueForKey((entity_t *)mapent, "target" )); + if( !target ) target = &entities->Element( 0 ); // move to world as default + } + else + { + // target is himself + target = mapent; + } + + // moving our transformed brushes into supposed entity + MoveBrushesToEntity( &localents, target, localworld ); + + // move env_static from sub-levels + for( i = 0; i < localents.Count(); i++ ) + { + mapent_t *ent = &localents[i]; + + if( Q_strcmp( ValueForKey( (entity_t *)ent, "classname" ), "env_static" )) + continue; + + mapent = &entities->Element( index ); // refresh the source pointer + EntityApplyTransform( mapent, ent, false, false ); + + // copy point entity into the parent pool + int newidx = AllocEntity( entities ); + mapent_t *newent = &entities->Element( newidx ); + memcpy( newent, ent, sizeof( mapent_t )); + newent->epairs = newent->tail = NULL; + CopyEpairs( (entity_t *)newent, (entity_t *)ent ); + } + + // free any remaining ents + for( i = 0; i < localents.Count(); i++ ) + FreeMapEntity( &localents[i] ); + localents.Purge(); + + // all done, restore oldcount + g_numparsedentities = oldentitynum; + g_brushtype = oldbrushtype; + + return true; +} + +/* +================ +FilterBrushesIntoArray +================ +*/ +void FilterBrushesIntoArray( mapent_t *mapent, int entnum, int contents ) +{ + // simple version of clip-nazi + const char *classname = ValueForKey( (entity_t *)mapent, "classname" ); + bool noclip = !Q_stricmp( classname, "func_illusionary" ); + + for( int j = 0; j < mapent->brushes.Count(); j++ ) + { + brush_t *dst = &g_mapbrushes[g_nummapbrushes]; + brush_t *src = &mapent->brushes[j]; + + if( noclip ) SetBits( src->flags, FBRUSH_NOCLIP ); + + // update contents for nonclipped brushes + if( FBitSet( src->flags, FBRUSH_NOCLIP )) + { + for( int k = 0; k < src->sides.Count(); k++ ) + { + side_t *s = &src->sides[k]; + if( FBitSet( s->flags, FSIDE_NODRAW )) + { + Q_strncpy( s->name, "SOLIDHINT", sizeof( s->name )); + SetBits( s->flags, FSIDE_SOLIDHINT ); + } + } +// g-cont. This causes problems. Disabled since 13.03.2019 +// src->contents = CONTENTS_EMPTY; + } + + if( src->contents != contents ) + continue; + + memcpy( dst, src, sizeof( brush_t )); + memset( src, 0, sizeof( brush_t )); + dst->entitynum = entnum; + mapent->numbrushes++; + g_nummapbrushes++; + + // make sure what we not overflowed + if( g_nummapbrushes == MAX_MAP_BRUSHES ) + COM_FatalError( "MAX_MAP_BRUSHES limit exceeded\n" ); + } +} + +/* +================ +PrepareMapBrushes +================ +*/ +void PrepareMapBrushes( void ) +{ + g_nummapbrushes = 0; + + // move local brushes into single solid array + for( int i = 0; i < g_mapentities.Count(); i++ ) + { + mapent_t *mapent = &g_mapentities[i]; + + mapent->firstbrush = g_nummapbrushes; + + FilterBrushesIntoArray( mapent, i, CONTENTS_EMPTY ); + FilterBrushesIntoArray( mapent, i, CONTENTS_FOG ); + FilterBrushesIntoArray( mapent, i, CONTENTS_WATER ); + FilterBrushesIntoArray( mapent, i, CONTENTS_SLIME ); + FilterBrushesIntoArray( mapent, i, CONTENTS_LAVA ); + FilterBrushesIntoArray( mapent, i, CONTENTS_TRANSLUCENT ); + FilterBrushesIntoArray( mapent, i, CONTENTS_SKY ); + FilterBrushesIntoArray( mapent, i, CONTENTS_SOLID ); // must be last + + // release localcopy we doesn't need them + mapent->brushes.Purge(); + } +} + +/* +================ +LoadMapFile +================ +*/ +void LoadMapFile( const char *filename ) +{ + LoadScriptFile( filename ); + g_numparsedentities = 0; + g_mapentities.Purge(); + + while( ParseMapEntity( &g_mapentities )) + { + g_numparsedentities++; + } + + Msg( "LoadMapFile: ^2%s^7\n\n", g_sMapType[g_brushtype] ); + + // now copy all the brushes into solid array + PrepareMapBrushes(); + + // at this point all the brushes stored into global array + MsgDev( D_INFO, "%5i brushes\n", g_nummapbrushes ); + MsgDev( D_INFO, "%5i entities\n", g_mapentities.Count() ); +} + +/* +================ +MapEntityForModel + +returns entity addy for given modelnum +================ +*/ +mapent_t *MapEntityForModel( const int modnum ) +{ + char name[16]; + + Q_snprintf( name, sizeof( name ), "*%i", modnum ); + + // search the entities for one using modnum + for( int i = 0; i < g_mapentities.Count(); i++ ) + { + const char *s = ValueForKey((entity_t *)&g_mapentities[i], "model" ); + if( !Q_strcmp( s, name )) return &g_mapentities[i]; + } + + return &g_mapentities[0]; +} + +/* +================== +FindTargetEntity +================== +*/ +mapent_t *FindTargetMapEntity( CUtlArray *entities, const char *target ) +{ + for( int i = 0; i < entities->Count(); i++ ) + { + char *n = ValueForKey( (entity_t *)&entities->Element( i ), "targetname" ); + if( !Q_strcmp( n, target )) + return &entities->Element( i ); + } + return NULL; +} + +/* +================ +UnparseMapEntities + +Generates the dentdata string from all the entities +================ +*/ +void UnparseMapEntities( void ) +{ + char *buf, *end; + char line[2048]; + char key[1024], value[1024]; + epair_t *ep; + + buf = g_dentdata; + end = buf; + *end = 0; + + for( int i = 0; i < g_mapentities.Count(); i++ ) + { + mapent_t *ent = &g_mapentities[i]; + + if( !ent->epairs ) + continue; // ent got removed + + Q_strcat( end, "{\n" ); + end += 2; + + // ugly hack to put spawnorigin after origin pt.1 + for( ep = ent->epairs; ep; ep = ep->next ) + { + if( !Q_stricmp( ep->key, "spawnorigin" )) + continue; + + Q_strncpy( key, ep->key, sizeof( key )); + StripTrailing( key ); + Q_strncpy( value, ep->value, sizeof( value )); + StripTrailing( value ); + + Q_snprintf( line, sizeof( line ), "\"%s\" \"%s\"\n", key, value ); + Q_strcat( end, line ); + end += Q_strlen( line ); + } + + // ugly hack to put spawnorigin after origin pt.2 + for( ep = ent->epairs; ep; ep = ep->next ) + { + if( Q_stricmp( ep->key, "spawnorigin" )) + continue; + + Q_strncpy( key, ep->key, sizeof( key )); + StripTrailing( key ); + Q_strncpy( value, ep->value, sizeof( value )); + StripTrailing( value ); + + Q_snprintf( line, sizeof( line ), "\"%s\" \"%s\"\n", key, value ); + Q_strcat( end, line ); + end += Q_strlen( line ); + } + + Q_strcat( end, "}\n" ); + end += 2; + + if( end > ( buf + MAX_MAP_ENTSTRING )) + COM_FatalError( "Entity text too long\n" ); + } + + g_entdatasize = end - buf + 1; // write term +} + +/* +================ +FreeMapEntity + +release all the entity data +================ +*/ +void FreeMapEntity( mapent_t *mapent ) +{ + epair_t *ep, *next; + + for( ep = mapent->epairs; ep != NULL; ep = next ) + { + next = ep->next; + freestring( ep->key ); + freestring( ep->value ); + Mem_Free( ep, C_EPAIR ); + } + + mapent->epairs = mapent->tail = NULL; + + // release brushsides + for( int i = 0; i < mapent->brushes.Count(); i++ ) + mapent->brushes[i].sides.Purge(); + + // release brushes + mapent->brushes.Purge(); +} + +/* +================ +FreeMapEntities + +release all the dynamically allocated data +================ +*/ +void FreeMapEntities( void ) +{ + for( int i = 0; i < g_mapentities.Count(); i++ ) + { + FreeMapEntity( &g_mapentities[i] ); + } + + // remove all the entities + g_mapentities.Purge(); +} \ No newline at end of file diff --git a/utils/p2csg/p2csg.dsp b/utils/p2csg/p2csg.dsp new file mode 100644 index 0000000..85b07bf --- /dev/null +++ b/utils/p2csg/p2csg.dsp @@ -0,0 +1,238 @@ +# Microsoft Developer Studio Project File - Name="p2csg" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=p2csg - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "p2csg.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "p2csg.mak" CFG="p2csg - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "p2csg - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "p2csg - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/SDKSrc/Tools/utils/p2csg", XUGBAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "p2csg - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir ".\Release" +# PROP BASE Intermediate_Dir ".\Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\..\temp\p2csg\!release" +# PROP Intermediate_Dir "..\..\temp\p2csg\!release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "..\common" /I "..\..\common" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "DOUBLEVEC_T" /D "IGNORE_SEARCH_IN_WADS" /D "ALLOW_WADS_IN_PACKS" /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /opt:nowin98 +# ADD LINK32 /nologo /subsystem:console /pdb:none /machine:I386 /opt:nowin98 +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\p2csg\!release +InputPath=\Paranoia2\src_main\temp\p2csg\!release\p2csg.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\p2csg.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\p2csg.exe "D:\Paranoia2\tools\p2csg.exe" + +# End Custom Build + +!ELSEIF "$(CFG)" == "p2csg - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir ".\Debug" +# PROP BASE Intermediate_Dir ".\Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\..\temp\p2csg\!debug" +# PROP Intermediate_Dir "..\..\temp\p2csg\!debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MTd /W3 /Gm /Gi /GX /ZI /Od /I "..\common" /I "..\..\common" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "DOUBLEVEC_T" /D "IGNORE_SEARCH_IN_WADS" /D "ALLOW_WADS_IN_PACKS" /FAs /FR /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 +# ADD LINK32 msvcrtd.lib /nologo /subsystem:console /debug /machine:I386 /nodefaultlib:"libcmtd.lib" +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\p2csg\!debug +InputPath=\Paranoia2\src_main\temp\p2csg\!debug\p2csg.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\p2csg.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\p2csg.exe "D:\Paranoia2\tools\p2csg.exe" + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "p2csg - Win32 Release" +# Name "p2csg - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=.\aselib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\basefs.cpp +# End Source File +# Begin Source File + +SOURCE=.\brush.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\bspfile.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\cmdlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\conprint.cpp +# End Source File +# Begin Source File + +SOURCE=.\csg4.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\filesystem.cpp +# End Source File +# Begin Source File + +SOURCE=.\map.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\mathlib.cpp +# End Source File +# Begin Source File + +SOURCE=.\patch.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\polylib.cpp +# End Source File +# Begin Source File + +SOURCE=.\qcsg.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\scriplib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\shaders.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\stringlib.cpp +# End Source File +# Begin Source File + +SOURCE=.\textures.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\threads.cpp +# End Source File +# Begin Source File + +SOURCE=.\utils.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\wadfile.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\zone.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=.\aselib.h +# End Source File +# Begin Source File + +SOURCE=..\common\bspfile.h +# End Source File +# Begin Source File + +SOURCE=..\common\cmdlib.h +# End Source File +# Begin Source File + +SOURCE=.\csg.h +# End Source File +# Begin Source File + +SOURCE=..\common\mathlib.h +# End Source File +# Begin Source File + +SOURCE=..\common\polylib.h +# End Source File +# Begin Source File + +SOURCE=..\common\scriplib.h +# End Source File +# Begin Source File + +SOURCE=..\common\shaders.h +# End Source File +# Begin Source File + +SOURCE=..\common\threads.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/utils/p2csg/patch.cpp b/utils/p2csg/patch.cpp new file mode 100644 index 0000000..cf5f32d --- /dev/null +++ b/utils/p2csg/patch.cpp @@ -0,0 +1,1051 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include "csg.h" +#include "aselib.h" + +// don't modify these! +#define MIN_PATCH_EDGE_LENGTH (1.0 / (vec_t)PATCH_SUBDIVISION) +#define MAX_EXPANDED_AXIS 128 +#define MAX_PATCH_SIZE 32 +#define PATCH_SUBDIVISION 8 +#define PLANAR_EPSILON 0.01 +#define STITCH_TOLERANCE 0.01 +#define STITCH_ERROR 0.1 + +typedef struct +{ + int width; + int height; + trivert_t *verts; +} patchmesh_t; + +/* +================ +PlaneFromPoints +================ +*/ +static bool PlaneFromPoints( plane_t *plane, const vec_t *p0, const vec_t *p1, const vec_t *p2 ) +{ + vec3_t t1, t2; + vec_t dist; + + VectorSubtract( p0, p1, t1 ); + VectorSubtract( p2, p1, t2 ); + CrossProduct( t1, t2, plane->normal ); + + if( VectorNormalize( plane->normal )) + { + dist = DotProduct( plane->normal, p0 ); + plane->dist = dist; + + return true; + } + + return false; +} + +vec_t Det3x3( vec_t a00, vec_t a01, vec_t a02, vec_t a10, vec_t a11, vec_t a12, vec_t a20, vec_t a21, vec_t a22 ) +{ + return a00 * ( a11 * a22 - a12 * a21 ) - a01 * ( a10 * a22 - a12 * a20 ) + a02 * ( a10 * a21 - a11 * a20 ); +} + +bool TexMatDegenerate( const vec3_t texMat[2] ) +{ + vec2_t vec0, vec1; + + Vector2Set( vec0, texMat[0][0], texMat[0][1] ); + Vector2Set( vec1, texMat[1][0], texMat[1][1] ); + + return !Vector2Cross( vec0, vec1 ) || IS_NAN( texMat[0][0] ); +} + +void TexMatFromPoints( vec3_t normal, vec3_t texMat[2], trivert_t *a, trivert_t *b, trivert_t *c ) +{ + vec2_t xyI, xyJ, xyK; + vec_t D, D0, D1, D2; + vec3_t texX, texY; + + // assume error: make orthogonal + VectorVectors( normal, texMat[0], texMat[1] ); + TextureAxisFromNormal( normal, texX, texY, true ); + + xyI[0] = DotProduct( a->point, texX ); + xyI[1] = DotProduct( a->point, texY ); + xyJ[0] = DotProduct( b->point, texX ); + xyJ[1] = DotProduct( b->point, texY ); + xyK[0] = DotProduct( c->point, texX ); + xyK[1] = DotProduct( c->point, texY ); + + // - solve linear equations: + // - (x, y) := xyz . (texX, texY) + // - st[i] = texMat[i][0]*x + texMat[i][1]*y + texMat[i][2] + // (for three vertices) + D = Det3x3( xyI[0], xyI[1], 1.0, xyJ[0], xyJ[1], 1.0, xyK[0], xyK[1], 1.0 ); + + if( D != 0 ) + { + for( int i = 0; i < 2; i++ ) + { + D0 = Det3x3( a->coord[i], xyI[1], 1.0, b->coord[i], xyJ[1], 1.0, c->coord[i], xyK[1], 1.0 ); + D1 = Det3x3( xyI[0], a->coord[i], 1.0, xyJ[0], b->coord[i], 1.0, xyK[0], c->coord[i], 1.0 ); + D2 = Det3x3( xyI[0], xyI[1], a->coord[i], xyJ[0], xyJ[1], b->coord[i], xyK[0], xyK[1], c->coord[i] ); + texMat[i][0] = D0 / D; + texMat[i][1] = D1 / D; + texMat[i][2] = fmod( D2 / D, 1.0 ); + } + } +} + +/* +===================== +TexVecsFromPoints + +turn the st into texture vectors +===================== +*/ +static void TexVecsFromPoints( const char *name, vec_t vecs[2][4], trivert_t *a, trivert_t *b, trivert_t *c ) +{ + int width, height; + vec3_t texMat[2]; + vec3_t axis[2]; + plane_t plane; + + PlaneFromPoints( &plane, a->point, b->point, c->point ); + TexMatFromPoints( plane.normal, texMat, a, b, c ); + + if( TexMatDegenerate( texMat )) // just throw warning. we allow non-orthogonal matrix + MsgDev( D_REPORT, "non-orthogonal texture vecs: Entity %i, Brush %i\n", g_numparsedentities, g_numparsedbrushes ); + + TEX_GetSize( name, &width, &height ); + TextureAxisFromNormal( plane.normal, axis[0], axis[1], true ); + + vecs[0][0] = width * ((axis[0][0] * texMat[0][0]) + (axis[1][0] * texMat[0][1])); + vecs[0][1] = width * ((axis[0][1] * texMat[0][0]) + (axis[1][1] * texMat[0][1])); + vecs[0][2] = width * ((axis[0][2] * texMat[0][0]) + (axis[1][2] * texMat[0][1])); + vecs[0][3] = width * texMat[0][2]; + + vecs[1][0] = height * ((axis[0][0] * texMat[1][0]) + (axis[1][0] * texMat[1][1])); + vecs[1][1] = height * ((axis[0][1] * texMat[1][0]) + (axis[1][1] * texMat[1][1])); + vecs[1][2] = height * ((axis[0][2] * texMat[1][0]) + (axis[1][2] * texMat[1][1])); + vecs[1][3] = height * texMat[1][2]; +} + +/* +================= +LerpPatchVert + +returns an 50/50 interpolated vert +================= +*/ +static void LerpPatchVert( trivert_t *a, trivert_t *b, vec_t factor, trivert_t *out ) +{ + out->point[0] = a->point[0] + factor * ( b->point[0] - a->point[0] ); + out->point[1] = a->point[1] + factor * ( b->point[1] - a->point[1] ); + out->point[2] = a->point[2] + factor * ( b->point[2] - a->point[2] ); + + out->coord[0] = a->coord[0] + factor * ( b->coord[0] - a->coord[0] ); + out->coord[1] = a->coord[1] + factor * ( b->coord[1] - a->coord[1] ); +} + +/* +================= +LerpPatchVert + +returns a biased interpolated vert +================= +*/ +static void LerpPatchVert( trivert_t *a, trivert_t *b, trivert_t *out ) +{ + LerpPatchVert( a, b, 0.5, out ); +} + +/* +===================== +GenerateBoundaryForPoints +===================== +*/ +static void GenerateBoundaryForPoints( plane_t *boundary, plane_t *plane, vec3_t a, vec3_t b ) +{ + vec3_t d1; + + // make a perpendicular vector to the edge and the surface + VectorSubtract( b, a, d1 ); + CrossProduct( plane->normal, d1, boundary->normal ); + VectorNormalize( boundary->normal ); + boundary->dist = DotProduct( a, boundary->normal ); +} + +/* +================= +PlanePointsFromPlane +================= +*/ +static void PlanePointsFromPlane( const plane_t *plane, vec3_t planepts[3] ) +{ + vec3_t org, vright, vup; + vec_t max = 0.56; + int x = -1; + vec_t v; + + // find the major axis + for( int i = 0; i < 3; i++ ) + { + v = fabs( plane->normal[i] ); + + if( v > max ) + { + max = v; + x = i; + } + } + + if( x == -1 ) COM_FatalError( "PlanePointsFromPlane: no axis found\n" ); + + switch( x ) + { + case 0: + case 1: // fall through to next case. + vright[0] = -plane->normal[1]; + vright[1] = plane->normal[0]; + vright[2] = 0; + break; + case 2: + vright[0] = 0; + vright[1] = -plane->normal[2]; + vright[2] = plane->normal[1]; + break; + } + + VectorScale( vright, (vec_t)WORLD_MAXS * 4.0, vright ); + CrossProduct( plane->normal, vright, vup ); + VectorScale( plane->normal, plane->dist, org ); + + VectorSubtract( org, vright, planepts[0] ); + VectorAdd( planepts[0], vup, planepts[0] ); + + VectorAdd( org, vright, planepts[1] ); + VectorAdd( planepts[1], vup, planepts[1] ); + + VectorAdd( org, vright, planepts[2] ); + VectorSubtract( planepts[2], vup, planepts[2] ); +} + +/* +================ +ProjectPointOntoVector +================ +*/ +void ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vEnd, vec3_t vProj ) +{ + vec3_t pVec, vec; + vec_t dist; + + VectorSubtract( point, vStart, pVec ); + VectorSubtract( vEnd, vStart, vec ); + VectorNormalize( vec ); + dist = DotProduct( pVec, vec ); + + // project onto the directional vector for this segment + VectorMA( vStart, dist, vec, vProj ); +} + +/* +===================== +CreatePatchBevel + +create bevel for patch main plane +===================== +*/ +void CreatePatchBevel( brush_t *brush, plane_t *plane, vec3_t a, vec3_t b ) +{ + side_t *side = AllocSide( brush ); + plane_t boundary; + + Q_strncpy( side->name, "SKIP", sizeof( side->name )); + GenerateBoundaryForPoints( &boundary, plane, a, b ); + PlanePointsFromPlane( &boundary, side->planepts ); + SetBits( side->flags, FSIDE_SKIP ); + side->contents = CONTENTS_EMPTY; + // no reason to calc texture vectors for disardable face +} + +/* +===================== +CreatePatchBevel + +create bevel for patch main plane +===================== +*/ +void CreatePatchBackplane( brush_t *brush, plane_t *plane ) +{ + side_t *side = AllocSide( brush ); + vec_t dist = 8192.0; // FIXME: tune this + plane_t boundary; + + Q_strncpy( side->name, "SKIP", sizeof( side->name )); + VectorNegate( plane->normal, boundary.normal ); + boundary.dist = plane->dist; + PlanePointsFromPlane( &boundary, side->planepts ); + SetBits( side->flags, FSIDE_SKIP ); + side->contents = CONTENTS_EMPTY; + // no reason to calc texture vectors for disardable face + + // at the finally generate the backplane + VectorMA( side->planepts[0], dist, boundary.normal, side->planepts[0] ); + VectorMA( side->planepts[1], dist, boundary.normal, side->planepts[1] ); + VectorMA( side->planepts[2], dist, boundary.normal, side->planepts[2] ); +} + +/* +===================== +MakeBrushFor3Points +===================== +*/ +bool MakeBrushFor3Points( mapent_t *mapent, side_t *mainSide, short entindex, trivert_t *a, trivert_t *b, trivert_t *c ) +{ + brush_t *brush; + side_t *side; + plane_t plane; + + // before createing the brush we should make sure what all points lies on the plane + if( !PlaneFromPoints( &plane, a->point, b->point, c->point )) + return false; // bad points ? + + // alloc brush to store the path + brush = AllocBrush( mapent ); + brush->originalentitynum = g_numparsedentities; + brush->originalbrushnum = g_numparsedbrushes; + brush->entitynum = entindex; + + // read ZHLT settings for this brush + if( BoolForKey( (entity_t *)mapent, "zhlt_noclip" )) + SetBits( brush->flags, FBRUSH_NOCLIP ); + SetBits( brush->flags, FBRUSH_PATCH ); +// SetBits( brush->flags, FBRUSH_NOCSG ); + brush->detaillevel = 2; // patches are always is detail + + // alloc main side (patch) + side = AllocSide( brush ); + memcpy( side, mainSide, sizeof( side_t )); + TexVecsFromPoints( side->name, side->vecs, a, b, c ); + VectorCopy( a->point, side->planepts[0] ); + VectorCopy( b->point, side->planepts[1] ); + VectorCopy( c->point, side->planepts[2] ); + + // make boundaries (side planes) + CreatePatchBevel( brush, &plane, a->point, b->point ); + CreatePatchBevel( brush, &plane, b->point, c->point ); + CreatePatchBevel( brush, &plane, c->point, a->point ); + + // at finally generate the backplane + CreatePatchBackplane( brush, &plane ); + + // determine contents + brush->contents = BrushContents( mapent, brush ); + + return true; +} + +/* +===================== +MakeBrushFor4Points + +Attempts to use four points as a planar quad +This is optimal case for lightmapping +===================== +*/ +bool MakeBrushFor4Points( mapent_t *mapent, side_t *mainSide, short entindex, trivert_t *a, trivert_t *b, trivert_t *c, trivert_t *d ) +{ + plane_t plane, plane2; + vec3_t verts[4]; + brush_t *brush; + side_t *side; + int i; + + // before createing the brush we should make sure what all points lies on the plane + if( !PlaneFromPoints( &plane, a->point, b->point, c->point )) + return false; // bad points ? + + // if the fourth point is also on the plane, we can make a quad facet + vec_t dist = PlaneDiff2( d->point, &plane ); + + if( fabs( dist ) > PLANAR_EPSILON ) + return false; + + VectorCopy( a->point, verts[0] ); + VectorCopy( b->point, verts[1] ); + VectorCopy( c->point, verts[2] ); + VectorCopy( d->point, verts[3] ); + + for( i = 1; i < 4; i++ ) + { + if( !PlaneFromPoints( &plane2, verts[i], verts[(i+1) % 4], verts[(i+2) % 4] )) + return false; + + // maximum difference between two planes + if( DotProduct( plane.normal, plane2.normal ) < 0.9 ) + return false; + } + + // alloc brush to store the path + brush = AllocBrush( mapent ); + brush->originalentitynum = g_numparsedentities; + brush->originalbrushnum = g_numparsedbrushes; + brush->entitynum = entindex; + + // read ZHLT settings for this brush + if( BoolForKey( (entity_t *)mapent, "zhlt_noclip" )) + SetBits( brush->flags, FBRUSH_NOCLIP ); + SetBits( brush->flags, FBRUSH_PATCH ); +// SetBits( brush->flags, FBRUSH_NOCSG ); + brush->detaillevel = 2; // patches are always is detail + + // alloc main side (patch) + side = AllocSide( brush ); + memcpy( side, mainSide, sizeof( side_t )); + TexVecsFromPoints( side->name, side->vecs, a, b, c ); + VectorCopy( a->point, side->planepts[0] ); + VectorCopy( b->point, side->planepts[1] ); + VectorCopy( c->point, side->planepts[2] ); + + // make boundaries (side planes) + CreatePatchBevel( brush, &plane, a->point, b->point ); + CreatePatchBevel( brush, &plane, b->point, c->point ); + CreatePatchBevel( brush, &plane, c->point, d->point ); + CreatePatchBevel( brush, &plane, d->point, a->point ); + + // at finally generate the backplane + CreatePatchBackplane( brush, &plane ); + + // determine contents + brush->contents = BrushContents( mapent, brush ); + + return true; +} + +/* +================= +AllocPatchMesh + +alloc a new patch +================= +*/ +patchmesh_t *AllocPatchMesh( int width, int height ) +{ + int size = sizeof( patchmesh_t ) + ( width * height * sizeof( trivert_t )); + patchmesh_t *out = (patchmesh_t *)Mem_Alloc( size, C_PATCH ); + + out->verts = (trivert_t *)(out + 1); + out->width = width; + out->height = height; + + return out; +} + +/* +================= +CopyPatchMesh + +make a mesh copy +================= +*/ +patchmesh_t *CopyPatchMesh( patchmesh_t *mesh ) +{ + int size = sizeof( patchmesh_t ) + ( mesh->width * mesh->height * sizeof( trivert_t )); + patchmesh_t *out = (patchmesh_t *)Mem_Alloc( size, C_PATCH ); + + out->verts = (trivert_t *)(out + 1); + out->width = mesh->width; + out->height = mesh->height; + memcpy( out->verts, mesh->verts, size - sizeof( patchmesh_t )); + + return out; +} + +/* +================= +FreePatchMesh + +release a mesh +================= +*/ +void FreePatchMesh( patchmesh_t *m ) +{ + Mem_Free( m, C_PATCH ); +} + +/* +================ +RemoveLinearMeshColumsRows +================ +*/ +patchmesh_t *RemoveLinearMeshColumnsRows( patchmesh_t *in ) +{ + trivert_t expand[MAX_EXPANDED_AXIS][MAX_EXPANDED_AXIS]; + float len, maxLength; + vec3_t proj, dir; + int i, j, k; + patchmesh_t out; + + out.width = in->width; + out.height = in->height; + + for( i = 0; i < in->width; i++ ) + { + for( j = 0; j < in->height; j++ ) + { + expand[j][i] = in->verts[j * in->width + i]; + } + } + + for( j = 1; j < out.width - 1; j++ ) + { + maxLength = 0.0; + + for( i = 0; i < out.height; i++ ) + { + ProjectPointOntoVector( expand[i][j].point, expand[i][j - 1].point, expand[i][j + 1].point, proj ); + VectorSubtract( expand[i][j].point, proj, dir ); + len = VectorLength( dir ); + maxLength = Q_max( len, maxLength ); + } + + if( maxLength < PLANAR_EPSILON ) + { + out.width--; + + for( i = 0; i < out.height; i++ ) + { + for( k = j; k < out.width; k++ ) + { + expand[i][k] = expand[i][k + 1]; + } + } + j--; + } + } + + for( j = 1; j < out.height - 1; j++ ) + { + maxLength = 0.0; + + for( i = 0; i < out.width; i++ ) + { + ProjectPointOntoVector( expand[j][i].point, expand[j - 1][i].point, expand[j + 1][i].point, proj ); + VectorSubtract( expand[j][i].point, proj, dir ); + len = VectorLength( dir ); + maxLength = Q_max( len, maxLength ); + } + + if( maxLength < PLANAR_EPSILON ) + { + out.height--; + for( i = 0; i < out.width; i++ ) + { + for( k = j; k < out.height; k++ ) + { + expand[k][i] = expand[k + 1][i]; + } + } + j--; + } + } + + // collapse the verts + out.verts = &expand[0][0]; + for( i = 1; i < out.height; i++ ) + memmove( &out.verts[i * out.width], expand[i], out.width * sizeof( trivert_t )); + + return CopyPatchMesh( &out ); +} + +/* +================= +PutMeshOnCurve + +Drops the aproximating points onto the curve +================= +*/ +void PutMeshOnCurve( patchmesh_t *in ) +{ + float prev, next; + int i, j, l; + + for( i = 0; i < in->width; i++ ) + { + for( j = 1; j < in->height; j += 2 ) + { + for( l = 0; l < 3; l++ ) + { + prev = ( in->verts[j*in->width+i].point[l] + in->verts[(j+1)*in->width+i].point[l] ) * 0.5; + next = ( in->verts[j*in->width+i].point[l] + in->verts[(j-1)*in->width+i].point[l] ) * 0.5; + in->verts[j*in->width+i].point[l] = ( prev + next ) * 0.5; + + if( l >= 2 ) continue; + + prev = ( in->verts[j*in->width+i].coord[l] + in->verts[(j+1)*in->width+i].coord[l] ) * 0.5; + next = ( in->verts[j*in->width+i].coord[l] + in->verts[(j-1)*in->width+i].coord[l] ) * 0.5; + in->verts[j*in->width+i].coord[l] = ( prev + next ) * 0.5; + } + } + } + + for( j = 0; j < in->height; j++ ) + { + for( i = 1; i < in->width; i += 2 ) + { + for( l = 0 ; l < 3 ; l++ ) + { + prev = ( in->verts[j*in->width+i].point[l] + in->verts[j*in->width+i+1].point[l] ) * 0.5; + next = ( in->verts[j*in->width+i].point[l] + in->verts[j*in->width+i-1].point[l] ) * 0.5; + in->verts[j*in->width+i].point[l] = ( prev + next ) * 0.5; + + if( l >= 2 ) continue; + + prev = ( in->verts[j*in->width+i].coord[l] + in->verts[j*in->width+i+1].coord[l] ) * 0.5; + next = ( in->verts[j*in->width+i].coord[l] + in->verts[j*in->width+i-1].coord[l] ) * 0.5; + in->verts[j*in->width+i].coord[l] = ( prev + next ) * 0.5; + } + } + } +} + +/* +================= +SubdivideMesh + +subdivides each mesh quad a specified number of times +================= +*/ +patchmesh_t *SubdivideMesh( patchmesh_t *in, int iterations ) +{ + trivert_t expand[MAX_EXPANDED_AXIS][MAX_EXPANDED_AXIS]; + trivert_t prev, next, mid; + int i, j, k; + patchmesh_t out; + + out.width = in->width; + out.height = in->height; + + for( i = 0; i < in->width; i++ ) + { + for( j = 0; j < in->height; j++ ) + expand[j][i] = in->verts[j * in->width + i]; + } + + // keep chopping + for( ; iterations > 0; iterations-- ) + { + // horizontal subdivisions + for( j = 0; j + 2 < out.width; j += 4 ) + { + // check size limit + if( out.width + 2 >= MAX_EXPANDED_AXIS ) + break; + + out.width += 2; + + // insert two columns and replace the peak + for( i = 0; i < out.height; i++ ) + { + LerpPatchVert( &expand[i][j+0], &expand[i][j+1], &prev ); + LerpPatchVert( &expand[i][j+1], &expand[i][j+2], &next ); + LerpPatchVert( &prev, &next, &mid ); + + for( k = out.width - 1 ; k > j + 3; k-- ) + expand[i][k] = expand[i][k-2]; + + expand[i][j+1] = prev; + expand[i][j+2] = mid; + expand[i][j+3] = next; + } + + } + + // vertical subdivisions + for( j = 0; j + 2 < out.height; j += 4 ) + { + // check size limit + if( out.height + 2 >= MAX_EXPANDED_AXIS ) + break; + + out.height += 2; + + // insert two columns and replace the peak + for( i = 0; i < out.width; i++ ) + { + LerpPatchVert( &expand[j+0][i], &expand[j+1][i], &prev ); + LerpPatchVert( &expand[j+1][i], &expand[j+2][i], &next ); + LerpPatchVert( &prev, &next, &mid ); + + for( k = out.height - 1; k > j + 3; k-- ) + expand[k][i] = expand[k-2][i]; + + expand[j+1][i] = prev; + expand[j+2][i] = mid; + expand[j+3][i] = next; + } + } + } + + // collapse the verts + out.verts = &expand[0][0]; + for( i = 1; i < out.height; i++ ) + memmove( &out.verts[i * out.width], expand[i], out.width * sizeof( trivert_t )); + + return CopyPatchMesh( &out ); +} + + +/* +================= +ExpandLongestCurve + +finds length of quadratic curve specified and determines if length is longer than the supplied max +================= +*/ +static void ExpandLongestCurve( vec_t *longestCurve, vec3_t a, vec3_t b, vec3_t c ) +{ + vec3_t ab, bc, ac, pt, last, delta; + vec_t t, len; + int i; + + VectorSubtract( b, a, ab ); + if( VectorNormalize( ab ) < MIN_PATCH_EDGE_LENGTH ) + return; + + VectorSubtract( c, b, bc ); + if( VectorNormalize( bc ) < MIN_PATCH_EDGE_LENGTH ) + return; + + VectorSubtract( c, a, ac ); + if( VectorNormalize( ac ) < MIN_PATCH_EDGE_LENGTH ) + return; + + // if all 3 vectors are the same direction, then this edge is linear, so we ignore it + if( DotProduct( ab, bc ) > (1.0 - NORMAL_EPSILON) && DotProduct( ab, ac ) > (1.0 - NORMAL_EPSILON)) + return; + + VectorSubtract( b, a, ab ); + VectorSubtract( c, b, bc ); + VectorCopy( a, last ); + + for( i = 0, len = 0.0f, t = 0.0f; i < PATCH_SUBDIVISION; i++ ) + { + delta[0] = ((1.0f - t) * ab[0]) + (t * bc[0]); + delta[1] = ((1.0f - t) * ab[1]) + (t * bc[1]); + delta[2] = ((1.0f - t) * ab[2]) + (t * bc[2]); + + // add to first point and calculate pt-pt delta + VectorAdd( a, delta, pt ); + VectorSubtract( pt, last, delta ); + + // add it to length and store last point + t += ( 1.0 / PATCH_SUBDIVISION ); + len += VectorLength( delta ); + VectorCopy( pt, last ); + } + + if( len > *longestCurve ) + *longestCurve = len; +} + + + +/* +================= +ExpandMaxIterations + +determines how many iterations a quadratic curve needs to be subdivided with to fit the specified error +================= +*/ +static void ExpandMaxIterations( int *maxIterations, int maxError, vec3_t a, vec3_t b, vec3_t c ) +{ + vec3_t prev, next, mid, delta, delta2; + vec3_t points[MAX_EXPANDED_AXIS]; + int numPoints, iterations; + vec_t len, len2; + int i, j; + + VectorCopy( a, points[0] ); + VectorCopy( b, points[1] ); + VectorCopy( c, points[2] ); + numPoints = 3; + + for( i = 0; i + 2 < numPoints; i += 2 ) + { + if( numPoints + 2 >= MAX_EXPANDED_AXIS ) + break; + + for( j = 0; j < 3; j++ ) + { + prev[j] = points[i + 1][j] - points[i][j]; + next[j] = points[i + 2][j] - points[i + 1][j]; + mid[j] = (points[i][j] + points[i + 1][j] * 2.0 + points[i + 2][j]) * 0.25; + } + + // see if this midpoint is off far enough to subdivide + VectorSubtract( points[i + 1], mid, delta ); + len = VectorLength( delta ); + if( len < maxError ) continue; + + numPoints += 2; + + for( j = 0; j < 3; j++ ) + { + prev[j] = 0.5 * (points[i][j] + points[i + 1][j]); + next[j] = 0.5 * (points[ i + 1 ][j] + points[i + 2][j]); + mid[j] = 0.5 * (prev[j] + next[j]); + } + + for( j = numPoints - 1; j > i + 3; j-- ) + VectorCopy( points[j - 2], points[j] ); + + VectorCopy( prev, points[i + 1] ); + VectorCopy( mid, points[i + 2] ); + VectorCopy( next, points[i + 3] ); + + // back up and recheck this set again, it may need more subdivision + i -= 2; + } + + for( i = 1; i < numPoints; i += 2 ) + { + for( j = 0; j < 3; j++ ) + { + prev[j] = 0.5 * (points[i][j] + points[i + 1][j] ); + next[j] = 0.5 * (points[i][j] + points[i - 1][j] ); + points[i][j] = 0.5 * (prev[j] + next[j]); + } + } + + // eliminate linear sections + for( i = 0; i + 2 < numPoints; i++ ) + { + VectorSubtract( points[i + 1], points[i], delta ); + len = VectorNormalize( delta ); + VectorSubtract( points[i + 2], points[i + 1], delta2 ); + len2 = VectorNormalize( delta2 ); + + // if either edge is degenerate, then eliminate it + if( len < 0.0625 || len2 < 0.0625 || DotProduct( delta, delta2 ) >= 1.0 ) + { + for( j = i + 1; j + 1 < numPoints; j++ ) + VectorCopy( points[j + 1], points[j] ); + numPoints--; + continue; + } + } + + // the number of iterations is 2^(points - 1) - 1 + numPoints >>= 1; + iterations = 0; + + while( numPoints > 1 ) + { + numPoints >>= 1; + iterations++; + } + + if( iterations > *maxIterations ) + *maxIterations = iterations; +} + +/* +================= +ExpandLongestCurveAndMaxIterations + +combined version +================= +*/ +void ExpandLongestCurveAndMaxIterations( vec_t sub, patchmesh_t *m, int i, int j, vec_t *longestCurve, int *maxIterations ) +{ + ExpandLongestCurve( longestCurve, m->verts[i*m->width+j].point, m->verts[i*m->width+(j+1)].point, m->verts[i*m->width+(j+2)].point ); + ExpandLongestCurve( longestCurve, m->verts[i*m->width+j].point, m->verts[(i+1)*m->width+j].point, m->verts[(i+2)*m->width+j].point ); + ExpandMaxIterations( maxIterations, sub, m->verts[i*m->width+j].point, m->verts[i*m->width+(j+1)].point, m->verts[i*m->width+(j+2)].point ); + ExpandMaxIterations( maxIterations, sub, m->verts[i*m->width+j].point, m->verts[(i+1)*m->width+j].point, m->verts[(i+2) * m->width+j].point ); +} + +/* +================= +IterationsForCurve + +given a curve of a certain length, return the number of subdivision iterations +note: this is affected by subdivision amount +================= +*/ +int IterationsForCurve( vec_t len, int subdivisions ) +{ + int iterations, facets; + + // calculate the number of subdivisions + for( iterations = 0; iterations < 3; iterations++ ) + { + facets = subdivisions * 16 * pow( 2, iterations ); + if( facets >= len ) + break; + } + + return iterations; +} + +/* +================= +ParsePatch + +creates a mapDrawSurface_t from the patch text +================= +*/ +void ParsePatch( mapent_t *mapent, short entindex, short faceinfo, short &brush_type ) +{ + vec4_t delta, delta2, delta3; + trivert_t *v1, *v2, *v3, *v4; + int maxIterations; + vec_t longestCurve; + bool degenerate; + patchmesh_t *patchMesh; + side_t patchSide; + vec_t info[5]; + int count; + int i, j; + + CheckToken( "{" ); + + if( !GetToken( true )) + return; + + memset( &patchSide, 0, sizeof( patchSide )); + Q_strncpy( patchSide.name, token, sizeof( patchSide.name )); + patchSide.shader = ShaderInfoForShader( token ); + SetBits( patchSide.flags, FSIDE_PATCH ); + patchSide.contents = CONTENTS_EMPTY; + patchSide.faceinfo = faceinfo; + Parse1DMatrix( 5, info ); + + if( info[0] < 0 || info[0] > MAX_PATCH_SIZE || info[1] < 0 || info[1] > MAX_PATCH_SIZE ) + COM_FatalError( "patch bad size\n" ); + + patchMesh = AllocPatchMesh( (int)info[0], (int)info[1] ); + + CheckToken( "(" ); + + for( j = 0; j < patchMesh->width ; j++ ) + { + CheckToken( "(" ); + + for( i = 0; i < patchMesh->height; i++ ) + Parse1DMatrix( 5, (vec_t *)patchMesh->verts[i * patchMesh->width + j].point ); + + CheckToken( ")" ); + } + + CheckToken( ")" ); + GetToken( true ); + + // if brush primitives format, we may have some epairs to ignore here + if( brush_type == BRUSH_RADIANT && Q_strcmp( token, "}" )) + InsertLinkBefore( ParseEpair(), (entity_t *)mapent ); + else UnGetToken(); + + CheckToken( "}" ); + CheckToken( "}" ); + + // delete and warn about degenerate patches + j = (patchMesh->width * patchMesh->height); + VectorClear( delta ); + degenerate = true; + delta[3] = 0; + + // find first valid vector + for( i = 1; i < j && delta[3] == 0; i++ ) + { + VectorSubtract( patchMesh->verts[0].point, patchMesh->verts[i].point, delta ); + delta[3] = VectorNormalize( delta ); + } + + // secondary degenerate test + if( delta[3] != 0 ) + { + // if all vectors match this or are zero, then this is a degenerate patch + for( i = 1; i < j && degenerate; i++ ) + { + VectorSubtract( patchMesh->verts[0].point, patchMesh->verts[i].point, delta2 ); + delta2[3] = VectorNormalize( delta2 ); + + if( delta2[3] != 0 ) + { + VectorCopy( delta2, delta3 ); + delta3[3] = delta2[3]; + VectorNegate( delta3, delta3 ); + + if( !VectorCompare( delta, delta2 ) && !VectorCompare( delta, delta3 )) + degenerate = false; + } + } + } + + if( degenerate ) + { + MsgDev( D_ERROR, "Entity %i, Brush %i: degenerate patch\n", g_numparsedentities, g_numparsedbrushes ); + FreePatchMesh( patchMesh ); + return; + } + + longestCurve = 0.0f; + maxIterations = 0; + + // find longest curve on the mesh + for( j = 0; j + 2 < patchMesh->width; j += 2 ) + { + for( i = 0; i + 2 < patchMesh->height; i += 2 ) + { + ExpandLongestCurveAndMaxIterations( PATCH_SUBDIVISION, patchMesh, i, j, &longestCurve, &maxIterations ); + } + } + + int iterations = IterationsForCurve( longestCurve, PATCH_SUBDIVISION ); + patchmesh_t *subdivided = SubdivideMesh( patchMesh, maxIterations /* iterations */ ); + patchmesh_t *finalMesh; + int stitch = 0; + + PutMeshOnCurve( subdivided ); + finalMesh = RemoveLinearMeshColumnsRows( subdivided ); + FreePatchMesh( subdivided ); + count = 0; + + for( i = 0; i < finalMesh->width - 1; i++ ) + { + for( j = 0; j < finalMesh->height - 1; j++ ) + { + v1 = finalMesh->verts + j * finalMesh->width + i; + v2 = v1 + 1; + v3 = v1 + finalMesh->width + 1; + v4 = v1 + finalMesh->width; + + if( MakeBrushFor4Points( mapent, &patchSide, entindex, v1, v4, v3, v2 )) + { + count++; + } + else + { + if( MakeBrushFor3Points( mapent, &patchSide, entindex, v1, v4, v3 )) + count++; + + if( MakeBrushFor3Points( mapent, &patchSide, entindex, v1, v3, v2 )) + count++; + } + } + } + + MsgDev( D_REPORT, "convert patch cp %d x %d: %d brushes, %i stitchs\n", finalMesh->width, finalMesh->height, count, stitch ); + FreePatchMesh( patchMesh ); + FreePatchMesh( finalMesh ); +} \ No newline at end of file diff --git a/utils/p2csg/qcsg.cpp b/utils/p2csg/qcsg.cpp new file mode 100644 index 0000000..1adc975 --- /dev/null +++ b/utils/p2csg/qcsg.cpp @@ -0,0 +1,808 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include "csg.h" + +// default compiler settings +#define DEFAULT_ONLYENTS false +#define DEFAULT_NULLIFYTRIGGER true +#define DEFAULT_WADTEXTURES true +#define DEFAULT_NOCLIP false + +// acutal compiler settings +bool g_onlyents = DEFAULT_ONLYENTS; +bool g_wadtextures = DEFAULT_WADTEXTURES; +bool g_nullifytrigger = DEFAULT_NULLIFYTRIGGER; +bool g_noclip = DEFAULT_NOCLIP; +vec_t g_csgepsilon = CSGCHOP_EPSILON; + +static FILE *out_surfaces[MAX_MAP_HULLS]; +static FILE *out_detbrush[MAX_MAP_HULLS]; +vec3_t world_mins, world_maxs, world_size; +static FILE *test_mapfile = NULL; + +//====================================================================== +/* +=========== +EmitFace +=========== +*/ +void EmitFace( int hull, const bface_t *f, int detaillevel ) +{ + // don't write out the discardable faces + if( FBitSet( f->flags, FSIDE_SKIP )) + return; + + ThreadLock(); + + fprintf( out_surfaces[hull], "%i %i %i %i %i\n", detaillevel, f->planenum, f->texinfo, f->contents[0], f->w->numpoints ); + + for( int i = 0; i < f->w->numpoints; i++ ) + { + fprintf( out_surfaces[hull], "%5.8f %5.8f %5.8f\n", f->w->p[i][0], f->w->p[i][1], f->w->p[i][2] ); + } + + // put in an extra line break + fprintf( out_surfaces[hull], "\n" ); + + ThreadUnlock (); +} + +/* +=========== +EmitDetailBrush +=========== +*/ +void EmitDetailBrush( int hull, const bface_t *faces ) +{ + ThreadLock(); + + fprintf( out_detbrush[hull], "0\n" ); + + for( const bface_t *f = faces; f != NULL; f = f->next ) + { + fprintf( out_detbrush[hull], "%i %u\n", f->planenum, f->w->numpoints ); + + for( int i = 0; i < f->w->numpoints; i++ ) + { + fprintf( out_detbrush[hull], "%5.8f %5.8f %5.8f\n", f->w->p[i][0], f->w->p[i][1], f->w->p[i][2] ); + } + } + + // write end marker + fprintf( out_detbrush[hull], "-1 -1\n" ); + + ThreadUnlock(); +} + +/* +============ +EmitPlanes +============ +*/ +void EmitPlanes( void ) +{ + dplane_t *dp; + plane_t *mp; + + g_numplanes = g_nummapplanes; + mp = g_mapplanes; + dp = g_dplanes; + + for( int i = 0; i < g_nummapplanes; i++, mp++, dp++ ) + { + VectorCopy( mp->normal, dp->normal ); + dp->dist = mp->dist; + dp->type = mp->type; + } +} + +/* +================== +WriteMapKeyValues + +test.map key-values +================== +*/ +void WriteMapKeyValues( mapent_t *ent ) +{ + char key[1024], value[1024]; + epair_t *ep; + + if( !test_mapfile ) return; + + for( ep = ent->epairs; ep; ep = ep->next ) + { + Q_strncpy( key, ep->key, sizeof( key )); + StripTrailing( key ); + Q_strncpy( value, ep->value, sizeof( value )); + StripTrailing( value ); + fprintf( test_mapfile, "\"%s\" \"%s\"\n", key, value ); + } +} + +void WriteMapFace( bface_t *f ) +{ + dtexinfo_t *tx = NULL; + char texname[16]; + texvecs_t valve; + + if( !test_mapfile ) return; + + fprintf( test_mapfile, " " ); + + // write three plane points + fprintf( test_mapfile, "( %g %g %g ) ", f->w->p[0][0], f->w->p[0][1], f->w->p[0][2] ); + fprintf( test_mapfile, "( %g %g %g ) ", f->w->p[1][0], f->w->p[1][1], f->w->p[1][2] ); + fprintf( test_mapfile, "( %g %g %g ) ", f->w->p[2][0], f->w->p[2][1], f->w->p[2][2] ); + + if( f->texinfo != -1 ) + { + vec_t length; + vec3_t axis; + + tx = &g_texinfo[f->texinfo]; + + for( int i = 0; i < 2; i++ ) + { + for( int j = 0; j < 3; j++ ) + axis[j] = tx->vecs[i][j]; + length = VectorNormalize( axis ); + + // avoid division by 0 + if( length != 0.0 ) + valve.scale[i] = 1.0 / length; + else valve.scale[i] = 0.0; + + valve.shift[i] = tx->vecs[i][3]; + if( !i ) VectorCopy( axis, valve.UAxis ); + else VectorCopy( axis, valve.VAxis ); + } + + Q_strncpy( texname, (char *)tx->miptex, sizeof( texname )); + } + else + { + TextureAxisFromNormal( f->plane->normal, valve.UAxis, valve.VAxis, false ); + valve.shift[0] = valve.shift[1] = 0.0f; + valve.scale[0] = valve.scale[1] = 1.0f; + + if( FBitSet( f->flags, FSIDE_SKIP )) + Q_strcpy( texname, "SKIP" ); + else if( FBitSet( f->flags, FSIDE_SOLIDHINT )) + Q_strcpy( texname, "SOLIDHINT" ); + else if( FBitSet( f->flags, FSIDE_HINT )) + Q_strcpy( texname, "HINT" ); + else Q_strcpy( texname, "NULL" ); // fallback + } + + fprintf( test_mapfile, "%s [ ", texname ); + fprintf( test_mapfile, "%g ", valve.UAxis[0] ); + fprintf( test_mapfile, "%g ", valve.UAxis[1] ); + fprintf( test_mapfile, "%g ", valve.UAxis[2] ); + fprintf( test_mapfile, "%g ", valve.shift[0] ); + fprintf( test_mapfile, "] [ " ); + fprintf( test_mapfile, "%g ", valve.VAxis[0] ); + fprintf( test_mapfile, "%g ", valve.VAxis[1] ); + fprintf( test_mapfile, "%g ", valve.VAxis[2] ); + fprintf( test_mapfile, "%g ", valve.shift[1] ); + fprintf( test_mapfile, "] 0 " ); // rotate (unused) + fprintf( test_mapfile, "%g ", valve.scale[0] ); + fprintf( test_mapfile, "%g ", valve.scale[1] ); + fprintf( test_mapfile, "\n" ); +} + +/* +================== +WriteMapBrushes + +test.map brushes +================== +*/ +void WriteMapBrushes( brush_t *b, bface_t *outside ) +{ +// outside = b->hull[0].faces; + + if( !outside || !test_mapfile ) + return; // no faces? + + ThreadLock(); + + fprintf( test_mapfile, " {\n" ); + + for( bface_t *f = outside; f != NULL; f = f->next ) + { + if( WindingArea( f->w ) <= 0.0 ) + continue; + + // write face + WriteMapFace( f ); + } + + fprintf( test_mapfile, " }\n" ); + + ThreadUnlock(); +} + +//====================================================================== +/* +============ +ProcessModels +============ +*/ +void ProcessModels( const char *source ) +{ + char name[1024]; + int i; + + // open surface and detail files + for( i = 0; i < MAX_MAP_HULLS; i++ ) + { + Q_snprintf( name, sizeof( name ), "%s.p%i", source, i ); + out_surfaces[i] = fopen( name, "w" ); + if( !out_surfaces[i] ) COM_FatalError( "couldn't open %s\n", name ); + + Q_snprintf( name, sizeof( name ), "%s.b%i", source, i ); + out_detbrush[i] = fopen( name, "w" ); + if( !out_detbrush[i] ) COM_FatalError( "couldn't open %s\n", name ); + } + + // DEBUG: write test map + Q_snprintf( name, sizeof( name ), "%s_csg.map", source ); +// test_mapfile = fopen( name, "w" ); + + for( i = 0; i < g_mapentities.Count(); i++ ) + { + mapent_t *ent = &g_mapentities[i]; + + if( !ent->epairs ) continue; // ent got removed + + if( test_mapfile ) + { + fprintf( test_mapfile, "{\n" ); + WriteMapKeyValues( ent ); + } + + if( ent->numbrushes ) + { + ChopEntityBrushes( ent ); + + // write end of model marker + for( int j = 0; j < MAX_MAP_HULLS; j++ ) + { + if( j != 0 && VectorIsNull( g_hull_size[j][0] ) && VectorIsNull( g_hull_size[j][1] )) + continue; + + fprintf( out_surfaces[j], "-1 -1 -1 -1 -1\n" ); + fprintf( out_detbrush[j], "-1\n" ); + } + } + + if( test_mapfile ) + fprintf( test_mapfile, "}\n" ); + } + + // close surface and detail files + for( i = 0; i < MAX_MAP_HULLS; i++ ) + { + fclose( out_surfaces[i] ); + fclose( out_detbrush[i] ); + } + + if( test_mapfile ) + fclose( test_mapfile ); + EmitPlanes(); // VHLT compatible (P2 compilers will be ignore it) + + Msg( "\n" ); +} + +/* +============ +WriteHullSizes +============ +*/ +void WriteHullSizes( const char *source ) +{ + float x1, y1, z1; + float x2, y2, z2; + char path[1024]; + FILE *f; + + Q_snprintf( path, sizeof( path ), "%s.hsz", source ); + f = fopen( path, "w" ); + if( !f ) COM_FatalError( "couldn't open %s\n", path ); + + // g-cont. may be better store these sizes as keyvalues in worldspawn so engine can read them too + for( int i = 0; i < MAX_MAP_HULLS; i++ ) + { + x1 = g_hull_size[i][0][0]; + y1 = g_hull_size[i][0][1]; + z1 = g_hull_size[i][0][2]; + x2 = g_hull_size[i][1][0]; + y2 = g_hull_size[i][1][1]; + z2 = g_hull_size[i][1][2]; + fprintf( f, "%g %g %g %g %g %g\n", x1, y1, z1, x2, y2, z2 ); + } + + fclose( f ); +} + +/* +============ +WriteMapPlanes +============ +*/ +void WriteMapPlanes( const char *source ) +{ + char path[1024]; + size_t len; + FILE *f; + + Q_snprintf( path, sizeof( path ), "%s.pln", source ); + f = fopen( path, "wb" ); + if( !f ) COM_FatalError( "couldn't open %s\n", path ); + + len = g_nummapplanes * sizeof( plane_t ); + + if( fwrite( g_mapplanes, 1, len, f ) != len ) + COM_FatalError( "failed to store mapplanes\n" ); + + fclose( f ); +} + +//====================================================================== +/* +============ +SetModelNumbers +============ +*/ +void SetModelNumbers( void ) +{ + char value[10]; + + for( int mod = 1, i = 1; i < g_mapentities.Count(); i++ ) + { + if( g_mapentities[i].numbrushes <= 0 ) + continue; + + Q_snprintf( value, sizeof( value ), "*%i", mod++ ); + SetKeyValue( (entity_t *)&g_mapentities[i], "model", value ); + } +} + +/* +============ +SetLightStyles +============ +*/ +void SetLightStyles( void ) +{ + char value[10]; + char lighttargets[MAX_SWITCHED_LIGHTS][64]; + bool newtexlight = false; + int stylenum = 0; + + // any light that is controlled (has a targetname) + // must have a unique style number generated for it + for( int i = 1; i < g_mapentities.Count(); i++ ) + { + mapent_t *e = &g_mapentities[i]; + const char *classname = ValueForKey( (entity_t *)e, "classname" ); + const char *t = NULL; + + if( Q_strncmp( classname, "light", 5 )) + { + // if it's not a normal light entity, allocate it a new style if necessary. + // xash func_light (a simple prefab for switchable texlight) + if( !Q_strncmp( classname, "func_light", 10 )) + { + // func_light always has style -1 + t = "-1"; + } + else + { + // if it's not a normal light entity, allocate it a new style if necessary. + t = ValueForKey( (entity_t *)e, "style" ); + } + + int style = atoi( t ); + + // ignore std styles (may be some settings in quake) + if( style >= 0 && style <= 20 ) + continue; + + switch( style ) + { + case -1: // normal switchable texlight + Q_snprintf( value, sizeof( value ), "%i", (32 + stylenum)); + SetKeyValue( (entity_t *)e, "style", value ); + stylenum++; + continue; + case -2: // backwards switchable texlight + Q_snprintf( value, sizeof( value ), "%i", -(32 + stylenum)); + SetKeyValue( (entity_t *)e, "style", value ); + stylenum++; + continue; + case -3: // (HACK) a piggyback texlight: switched on and off by triggering a real light that has the same name + SetKeyValue( (entity_t *)e, "style", "0" ); // just in case the level designer didn't give it a name + newtexlight = true; + // don't 'continue', fall out + } + } + + t = ValueForKey( (entity_t *)e, "targetname" ); + + if( CheckKey( (entity_t *)e, "zhlt_usestyle" )) + { + t = ValueForKey( (entity_t *)e, "zhlt_usestyle" ); + + if( !Q_stricmp( t, "NULL" )) + t = ""; + } + + // if no custom style specified + if( !t[0] ) + { +#ifdef HLCSG_SKYFIXEDSTYLE + if( BoolForKey( (entity_t *)e, "_sky" ) || !Q_strcmp( classname, "light_environment" )) + { + Q_snprintf( value, sizeof( value ), "%i", LS_SKY ); + SetKeyValue( (entity_t *)e, "style", value ); + } +#endif + continue; + } + + // find this targetname + for( int j = 0; j < stylenum; j++ ) + { + if( !Q_strcmp( lighttargets[j], t )) + break; + } + + if( j == stylenum ) + { + if( stylenum == MAX_SWITCHED_LIGHTS ) + COM_FatalError( "MAX_SWITCHED_LIGHTS limit exceeded\n" ); + Q_strncpy( lighttargets[j], t, sizeof( lighttargets[0] )); + stylenum++; + } + + Q_snprintf( value, sizeof( value ), "%i", 32 + j ); + SetKeyValue( (entity_t *)e, "style", value ); + } +} + +/* +============ +LoadWadValue + +don't change "wad" string in 'onlyents' mode +============ +*/ +void LoadWadValue( void ) +{ + mapent_t mapent[2]; + char *wadvalue; + epair_t *e; + + ParseFromMemory( g_dentdata, g_entdatasize ); + memset( mapent, 0, sizeof( entity_t )); + + if( !GetToken( true )) + { + wadvalue = copystring( "" ); + } + else + { + if( Q_strcmp( token, "{" )) + { + COM_FatalError( "ParseEntity: { not found\n" ); + } + + while( 1 ) + { + if( !GetToken( true )) + COM_FatalError( "ParseEntity: EOF without closing brace\n" ); + + if( !Q_strcmp( token, "}" )) + break; + + e = ParseEpair (); + e->next = mapent->epairs; + mapent->epairs = e; + } + + wadvalue = copystring( ValueForKey( (entity_t *)mapent, "wad" )); + FreeMapEntity( mapent ); // throw memory + } + + if( *wadvalue ) MsgDev( D_REPORT, "Wad files required to run the map: \"%s\"\n", wadvalue ); + else MsgDev( D_REPORT, "Wad files required to run the map: (None)\n" ); + SetKeyValue( (entity_t *)&g_mapentities[0], "wad", wadvalue ); + freestring( wadvalue ); +} + +/* +============ +BoundWorld + +tell designer about world bounds +============ +*/ +void BoundWorld( void ) +{ + const char *axis[3] = { "X", "Y", "Z" }; + int negative_world_bounds = 0; + + ClearBounds( world_mins, world_maxs ); + + for( int i = 0; i < g_nummapbrushes; i++ ) + { + brushhull_t *h = &g_mapbrushes[i].hull[0]; + + if( !h->faces ) continue; + + for( int j = 0; j < 3; j++ ) + { + if( h->mins[j] > WORLD_MAXS || h->maxs[j] < WORLD_MINS ) + break; + } + + if( j != 3 ) continue; // no valid points + + AddPointToBounds( h->mins, world_mins, world_maxs ); + AddPointToBounds( h->maxs, world_mins, world_maxs ); + } + + VectorSubtract( world_maxs, world_mins, world_size ); + + for( i = 0; i < 3; i++ ) + { + if( world_size[i] > WORLD_SIZE ) + MsgDev( D_ERROR, "world is not fit in allowed bounds by %s-axis (%g > %g)\n", axis[i], world_size[i], WORLD_SIZE ); + if( world_size[i] < 0 ) negative_world_bounds++; + } + + MsgDev( D_INFO, "\n^3World Size:^7 (%.f %.f %.f)\n\n", world_size[0], world_size[1], world_size[2] ); + if( negative_world_bounds ) COM_FatalError( "Negative world bounds\n" ); +} + +//====================================================================== +/* +============ +WriteBSP +============ +*/ +void WriteBSP( const char *source ) +{ + char path[1024]; + + Q_snprintf( path, sizeof( path ), "%s.bsp", source ); + + SetModelNumbers (); + SetLightStyles (); + + if( g_onlyents ) + { + // restore origins that was created by auto-origin system + RestoreModelOrigins (); + LoadWadValue (); + } + else WriteMiptex (); + + UnparseMapEntities (); + WriteBSPFile( path ); +} + +//========================================= +/* +============ +PrintCsgSettings + +show compiler settings like ZHLT +============ +*/ +static void PrintCsgSettings( void ) +{ + Msg( "\nCurrent p2csg settings\n" ); + Msg( "Name | Setting | Default\n" ); + Msg( "---------------------|-----------|-------------------------\n" ); + Msg( "developer [ %7d ] [ %7d ]\n", GetDeveloperLevel(), DEFAULT_DEVELOPER ); + Msg( "wadtextures [ %7s ] [ %7s ]\n", g_wadtextures ? "on" : "off", DEFAULT_WADTEXTURES ? "on" : "off" ); + Msg( "nullify trigger [ %7s ] [ %7s ]\n", g_nullifytrigger ? "on" : "off", DEFAULT_NULLIFYTRIGGER ? "on" : "off" ); + Msg( "noclip [ %7s ] [ %7s ]\n", g_noclip ? "on" : "off", DEFAULT_NOCLIP ? "on" : "off" ); + Msg( "onlyents [ %7s ] [ %7s ]\n", g_onlyents ? "on" : "off", DEFAULT_ONLYENTS ? "on" : "off" ); + Msg( "CSG chop epsilon [ %.6f] [ %.6f]\n", g_csgepsilon, CSGCHOP_EPSILON ); + Msg( "\n" ); +} + +/* +============ +PrintCsgUsage + +show compiler usage like ZHLT +============ +*/ +static void PrintCsgUsage( void ) +{ + Msg( "\n-= p2csg Options =-\n\n" ); + Msg( " -dev # : compile with developer message (1 - 4). default is %d\n", DEFAULT_DEVELOPER ); + Msg( " -threads # : manually specify the number of threads to run\n" ); + Msg( " -noclip : don't create clipping hulls\n" ); + Msg( " -onlyents : do an entity update from .map to .bsp\n" ); + Msg( " -nowadtextures : include all used textures into bsp\n" ); + Msg( " -wadinclude file : place textures used from wad specified into bsp\n" ); + Msg( " -nonullifytrigger: remove 'aaatrigger' visible faces\n" ); + Msg( " -epsilon : CSG chop precision epsilon\n" ); + Msg( " mapfile : the mapfile to compile\n\n" ); + + exit( 1 ); +} + +/* +============ +main +============ +*/ +int main( int argc, char **argv ) +{ + char source[1024]; + char mapname[1024]; + double start, end; + char str[64]; + int i; + + atexit( Sys_CloseLog ); + source[0] = '\0'; + + for( i = 1; i < argc; i++ ) + { + if( !Q_strcmp( argv[i], "-dev" )) + { + SetDeveloperLevel( atoi( argv[i+1] )); + i++; + } + else if( !Q_strcmp( argv[i], "-threads" )) + { + g_numthreads = atoi( argv[i+1] ); + i++; + } + else if( !Q_strcmp( argv[i], "-noclip" )) + { + g_noclip = true; + } + else if( !Q_strcmp( argv[i], "-onlyents" )) + { + g_onlyents = true; + } + else if( !Q_strcmp( argv[i], "-nowadtextures" )) + { + g_wadtextures = false; + } + else if( !Q_strcmp( argv[i], "-wadinclude" )) + { + COM_FileBase( argv[i + 1], g_pszWadInclude[g_nWadInclude] ); + g_nWadInclude++; + i++; + } + else if( !Q_strcmp(argv[i], "-nonullifytrigger" )) + { + g_nullifytrigger = false; + } + else if( !Q_strcmp( argv[i], "-epsilon" )) + { + g_csgepsilon = atof( argv[i+1] ); + i++; + } + else if( argv[i][0] == '-' ) + { + MsgDev( D_ERROR, "\nUnknown option \"%s\"\n", argv[i] ); + break; + } + else if( !source[0] ) + { + Q_strncpy( source, COM_ExpandArg( argv[i] ), sizeof( source )); + COM_StripExtension( source ); + } + else + { + MsgDev( D_ERROR, "\nUnknown option \"%s\"\n", argv[i] ); + break; + } + } + + if( i != argc || !source[0] ) + { + if( !source[0] ) + Msg( "no mapfile specified\n" ); + PrintCsgUsage(); + } + + start = I_FloatTime (); + + Sys_InitLog( va( "%s.log", source )); + + Q_snprintf( mapname, sizeof( mapname ), "%s.map", source ); + + // trying to extract wadpath from the mapname + if( Q_stristr( mapname, "maps" )) + { + char temp[1024]; + + COM_ExtractFilePath( mapname, temp ); + COM_ExtractFilePath( temp, g_wadpath ); + } + + Msg( "\n%s %s (%s)\n", TOOLNAME, VERSIONSTRING, __DATE__ ); + + PrintCsgSettings(); + + ThreadSetDefault (); + + // starting base filesystem + FS_Init( source ); + LoadShaderInfo(); + + if( g_onlyents ) + { + char bspname[1024]; + + // if onlyents, just grab the entites and resave + Q_snprintf( bspname, sizeof( bspname ), "%s.bsp", source ); + LoadBSPFile( bspname ); + + // get the new entity data from the map file + LoadMapFile( mapname ); + } + else + { + // start from scratch + LoadMapFile( mapname ); + + // create brushes from map planes + RunThreadsOnIndividual( g_nummapbrushes, true, CreateBrush ); + MsgDev( D_REPORT, "%5i map planes\n", g_nummapplanes ); + + ProcessAutoOrigins(); + + DumpBrushPlanes(); + + BoundWorld (); + + ProcessModels ( source ); + + WriteHullSizes( source ); + + WriteMapPlanes( source ); + + MsgDev( D_INFO, "%5i used faces\n", c_outfaces ); + } + + // write it all back out again. + WriteBSP( source ); + + // release dynamically allocated data + TEX_FreeTextures(); + FreeHullFaces(); + FreeMapEntities(); + FreeShaderInfo(); + FS_Shutdown(); + + // now check for leaks + SetDeveloperLevel( D_REPORT ); + Mem_Check(); + + end = I_FloatTime (); + Q_timestring((int)( end - start ), str ); + Msg( "%s elapsed\n", str ); + + return 0; +} \ No newline at end of file diff --git a/utils/p2csg/textures.cpp b/utils/p2csg/textures.cpp new file mode 100644 index 0000000..8bfeec0 --- /dev/null +++ b/utils/p2csg/textures.cpp @@ -0,0 +1,674 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include "csg.h" + +#define FWAD_USED BIT( 0 ) +#define FWAD_INCLUDE BIT( 1 ) + +typedef struct +{ + char name[64]; + int flags; +} wadentry_t; + +static const vec3_t baseaxis[18] = +{ +{ 0, 0, 1}, { 1, 0, 0}, { 0,-1, 0}, // floor +{ 0, 0,-1}, { 1, 0, 0}, { 0,-1, 0}, // ceiling +{ 1, 0, 0}, { 0, 1, 0}, { 0, 0,-1}, // west wall +{-1, 0, 0}, { 0, 1, 0}, { 0, 0,-1}, // east wall +{ 0, 1, 0}, { 1, 0, 0}, { 0, 0,-1}, // south wall +{ 0,-1, 0}, { 1, 0, 0}, { 0, 0,-1}, // north wall +}; + +static mipentry_t g_miptex[MAX_MAP_TEXTURES]; +static int g_nummiptex; +static wadentry_t g_wadlist[MAX_TEXFILES]; +static int g_wadcount; +char g_pszWadInclude[MAX_TEXFILES][64]; +int g_nWadInclude; + +void TEX_LoadTextures( CUtlArray *entities, bool merge ) +{ + char wadstring[MAX_VALUE]; + char tmpWadName[64]; + char *pszWadString; + char *pszWadFile; + int startcount; + + if( !merge ) + { + memset( g_wadlist, 0 , sizeof( g_wadlist )); + memset( g_miptex, 0 , sizeof( g_miptex )); + g_wadcount = g_nummiptex = 0; + } + + if( entities->Count() <= 0 ) + return; + + pszWadString = ValueForKey( (entity_t *)&entities[0], "wad" ); + if( !*pszWadString ) pszWadString = ValueForKey( (entity_t *)&entities[0], "_wad" ); + if( !*pszWadString ) return; // may be -nowadtextures was used? + startcount = g_wadcount; + + Q_strncpy( wadstring, pszWadString, MAX_VALUE - 2 ); + wadstring[MAX_VALUE-1] = 0; + + if( !Q_strchr( wadstring, ';' )) + Q_strcat( wadstring, ";" ); + + // parse wad pathes + for( pszWadFile = strtok( wadstring, ";" ); pszWadFile != NULL; pszWadFile = strtok( NULL, ";" )) + { + COM_FixSlashes( pszWadFile ); + COM_FileBase( pszWadFile, tmpWadName ); + + // check for duplicate + for( int i = 0; i < g_wadcount; i++ ) + { + if( !Q_stricmp( g_wadlist[i].name, tmpWadName )) + break; + } + + if( i != g_wadcount ) + continue; // already included + + Q_strncpy( g_wadlist[g_wadcount].name, tmpWadName, sizeof( g_wadlist[0].name )); + + if( ++g_wadcount >= MAX_TEXFILES ) + break; // too many wads... + } + + // set wads that should be included... + for( int i = startcount; i < g_wadcount; i++ ) + { + if( g_wadtextures ) + { + for( int j = 0; j < g_nWadInclude; j++ ) + { + if( !Q_stricmp( g_wadlist[i].name, g_pszWadInclude[j] )) + { + MsgDev( D_INFO, "Including WAD File: %s\n", g_wadlist[i].name ); + SetBits( g_wadlist[i].flags, FWAD_INCLUDE ); + break; + } + } + } + else + { + // force to include all the wads + SetBits( g_wadlist[i].flags, FWAD_INCLUDE ); + } + } +} + +bool TEX_CheckMiptex( const char *name ) +{ + char texname[64]; + + Q_snprintf( texname, sizeof( texname ), "%s.mip", name ); + + // check wads in reverse order + for( int i = g_wadcount - 1; i >= 0; i-- ) + { + char *texpath = va( "%s.wad/%s", g_wadlist[i].name, texname ); + + if( FS_FileExists( texpath, false )) + return true; + } + + // maybe it's tga path? + Q_snprintf( texname, sizeof( texname ), "textures/%s.tga", name ); + + if( FS_FileExists( texname, false )) + return true; + + return false; +} + +bool TEX_LoadMiptex( const char *name, mipentry_t *tex ) +{ + char texname[64]; + shaderInfo_t *shader; + + Q_snprintf( texname, sizeof( texname ), "%s.mip", name ); + Q_strncpy( tex->name, name, sizeof( tex->name )); + tex->width = tex->height = 16; // same as "*default" + + // check wads in reverse order + for( int i = g_wadcount - 1; i >= 0; i-- ) + { + char *texpath = va( "%s.wad/%s", g_wadlist[i].name, texname ); + + if( FS_FileExists( texpath, false )) + { + // texture is loaded, wad is used + SetBits( g_wadlist[i].flags, FWAD_USED ); + tex->data = FS_LoadFile( texpath, &tex->datasize, false ); + tex->width = ((miptex_t *)tex->data)->width; + tex->height = ((miptex_t *)tex->data)->height; + tex->wadnum = i; // wad where the tex is located + return true; + } + } +#if 0 // this is no longer support by engine + // wadpath is not specified + if( FS_FileExists( texname, false )) + { + // texture is loaded, wad is used + tex->data = FS_LoadFile( texname, &tex->datasize, false ); + tex->width = ((miptex_t *)tex->data)->width; + tex->height = ((miptex_t *)tex->data)->height; + tex->wadnum = -1; // wad where the tex is located + return true; + } +#endif + shader = ShaderInfoForShader( name ); + + // maybe it's tga path? + if( FS_FileExists( shader->imagePath, false )) + { + // texture is loaded, wad is not used + TEX_LoadTGA( shader->imagePath, tex ); + + // FIXME: add loader for dds + return true; + } + + MsgDev( D_WARN, "%s failed to load\n", name ); + return false; +} + +int TEX_FindMiptex( const char *name ) +{ + ThreadLock (); + + // see if already loaded + for( int i = 0; i < g_nummiptex; i++ ) + { + if( !Q_stricmp( name, g_miptex[i].name )) + { + ThreadUnlock(); + return i; + } + } + + if( g_nummiptex == MAX_MAP_TEXTURES ) + COM_FatalError( "MAX_MAP_TEXTURES limit exceeded\n" ); + + TEX_LoadMiptex( name, &g_miptex[i] ); + g_nummiptex++; + ThreadUnlock (); + + return i; +} + +void TEX_GetSize( const char *name, int *width, int *height ) +{ + int miptex = TEX_FindMiptex( name ); + + *width = g_miptex[miptex].width; + *height = g_miptex[miptex].height; +} + +void TEX_FreeTextures( void ) +{ + // purge texture cache + for( int i = 0; i < g_nummiptex; i++ ) + { + Mem_Free( g_miptex[i].data, C_FILESYSTEM ); + } +} + +/* +================= +lump_sorters +================= +*/ +static int lump_sorter_by_wad_and_name( const void *lump1, const void *lump2 ) +{ + mipentry_t *plump1 = (mipentry_t *)lump1; + mipentry_t *plump2 = (mipentry_t *)lump2; + + if( plump1->wadnum == plump2->wadnum ) + return Q_stricmp( plump1->name, plump2->name ); + else return plump1->wadnum - plump2->wadnum; +} + +/* +================== +LoadLump +================== +*/ +int LoadLump( mipentry_t *source, byte *dest ) +{ + if( source->datasize ) + { + // should we just load the texture header w/o the palette & bitmap? + if( source->wadnum == -1 || !FBitSet( g_wadlist[source->wadnum].flags, FWAD_INCLUDE )) + { + // Just read the miptex header and zero out the data offsets. + // We will load the entire texture from the WAD at engine runtime + miptex_t *miptex = (miptex_t *)dest; + + Q_strncpy( miptex->name, source->name, WAD3_NAMELEN ); + miptex->width = source->width; + miptex->height = source->height; + for( int i = 0; i < MIPLEVELS; i++ ) + miptex->offsets[i] = 0; + + return sizeof( miptex_t ); + } + else + { + // Load the entire texture here so the BSP contains the texture + memcpy( dest, source->data, source->datasize ); + return source->datasize; + } + } + + return 0; +} + +/* +================== +LumpSize + +returns the size of lump +================== +*/ +int LumpSize( const mipentry_t *source ) +{ + if( source->datasize ) + { + // should we just load the texture header w/o the palette & bitmap? + if( source->wadnum == -1 || !FBitSet( g_wadlist[source->wadnum].flags, FWAD_INCLUDE )) + return sizeof( miptex_t ); + return source->datasize; + } + return 0; +} + +/* +================== +AddAnimatingTextures +================== +*/ +void AddAnimatingTextures( void ) +{ + int base = g_nummiptex; + char name[64]; + + for( int i = 0; i < base; i++ ) + { + if( g_miptex[i].name[0] != '+' && g_miptex[i].name[0] != '-' ) + continue; + Q_strcpy( name, g_miptex[i].name ); + + for( int j = 0; j < 20; j++ ) + { + if( j < 10 ) name[1] = '0' + j; + else name[1] = 'A' + j - 10; // alternate animation + + // see if this name exists in the wadfile + if( TEX_CheckMiptex( name )) + { + // add to the miptex list + TEX_FindMiptex( name ); + } + } + } + + if( g_nummiptex - base ) + MsgDev( D_REPORT, "added %i additional animating textures.\n", g_nummiptex - base ); +} + +/* +================== +CheckSpecialTexture +================== +*/ +bool CheckSpecialTexture( const char *name ) +{ + if( !Q_stricmp( name, "NULL" )) + return true; + if( !Q_stricmp( name, "SKIP" )) + return true; + if( !Q_stricmp( name, "HINT" )) + return true; + if( !Q_stricmp( name, "SOLIDHINT" )) + return true; + if( !Q_stricmp( name, "SKY" )) + return true; + if( !Q_stricmp( name, "ORIGIN" )) + return true; + if( !Q_stricmp( name, "CLIP" )) + return true; + return false; +} + +/* +================== +WriteMiptex +================== +*/ +void WriteMiptex( void ) +{ + dtexinfo_t *tx = g_texinfo; + char szTmpWad[1024]; + int i, len; + byte *data; + dmiptexlump_t *l; + + g_texdatasize = 0; + szTmpWad[0] = 0; + + // add animating textures finally + AddAnimatingTextures(); + + // sort them FIRST by wadfile and THEN by name for most efficient loading in the engine. + qsort( (void *)g_miptex, (size_t)g_nummiptex, sizeof( g_miptex[0] ), lump_sorter_by_wad_and_name ); + + // Sleazy Hack 104 Pt 2 - After sorting the miptex array, reset the texinfos to point to the right miptexs + for( i = 0; i < g_numtexinfo; i++, tx++ ) + { + char *miptex_name = (char *)tx->miptex; + + tx->miptex = TEX_FindMiptex( miptex_name ); + + // free up the originally strdup()'ed miptex_name + free( miptex_name ); + } + + int totaldatasize = sizeof( int ) + ( sizeof( int ) * g_nummiptex ); + + for( i = 0; i < g_nummiptex; i++ ) + { + len = LumpSize( g_miptex + i ); + totaldatasize += len; + } + + Msg( "total texture data %s\n", Q_memprint( totaldatasize )); + g_dtexdata = (byte *)Mem_Alloc( totaldatasize ); + + if( totaldatasize > MAX_MAP_MIPTEX ) + MsgDev( D_WARN, "MAX_MAP_MIPTEX limit overflow\n" ); + + // now setup to get the miptex data (or just the headers if using -wadtextures) from the wadfile + l = (dmiptexlump_t *)g_dtexdata; + data = (byte *)&l->dataofs[g_nummiptex]; + l->nummiptex = g_nummiptex; + + for( i = 0; i < g_nummiptex; i++ ) + { + l->dataofs[i] = data - (byte *)l; + len = LoadLump( g_miptex + i, data ); + if( !len ) l->dataofs[i] = -1; // didn't find the texture + data += len; + } + + g_texdatasize = data - g_dtexdata; + + if( totaldatasize != g_texdatasize ) + COM_FatalError( "WriteMiptex: memory corrupted\n" ); + + for( i = 0; i < g_wadcount; i++ ) + { + // all used textures from this wad will be include into map + if( FBitSet( g_wadlist[i].flags, FWAD_INCLUDE )) + continue; + + // nothing used from this wad + if( !FBitSet( g_wadlist[i].flags, FWAD_USED )) + continue; + + Q_snprintf( szTmpWad, sizeof( szTmpWad ), "%s%s.wad;", szTmpWad, g_wadlist[i].name ); + MsgDev( D_INFO, "Using WAD File: %s.wad\n", g_wadlist[i].name ); + } + + len = Q_strlen( szTmpWad ); + + // cutoff last semicolon + if( len > 0 && szTmpWad[Q_strlen( szTmpWad ) - 1] == ';' ) + szTmpWad[len-1] = '\0'; + + if( *szTmpWad ) MsgDev( D_REPORT, "Wad files required to run the map: \"%s\"\n", szTmpWad ); + else MsgDev( D_REPORT, "Wad files required to run the map: (None)\n" ); + + // update 'wad' field + SetKeyValue( (entity_t *)&g_mapentities[0], "wad", szTmpWad ); +} + +// ===================================================================================== +// FaceinfoForTexinfo +// ===================================================================================== +short FaceinfoForTexinfo( const char *landname, const int in_texture_step, const int in_max_extent, const int groupid ) +{ + byte texture_step = bound( MIN_CUSTOM_TEXTURE_STEP, in_texture_step, MAX_CUSTOM_TEXTURE_STEP ); + byte max_extent = bound( MIN_CUSTOM_SURFACE_EXTENT, in_max_extent, MAX_CUSTOM_SURFACE_EXTENT ); + dfaceinfo_t *fi = g_dfaceinfo; + int i; + + ThreadLock(); + + for( i = 0; i < g_numfaceinfo; i++, fi++ ) + { + if( Q_stricmp( landname, fi->landname )) + continue; + + if( fi->texture_step != texture_step ) + continue; + + if( fi->max_extent != max_extent ) + continue; + + if( fi->groupid != groupid ) + continue; + + ThreadUnlock(); + return i; + } + + if( g_numfaceinfo == MAX_MAP_FACEINFO ) + COM_FatalError( "MAX_MAP_FACEINFO limit exceeded\n" ); + + // allocate tne entry + Q_strncpy( fi->landname, landname, sizeof( fi->landname )); + fi->texture_step = texture_step; + fi->max_extent = max_extent; + fi->groupid = groupid; + g_numfaceinfo++; + + ThreadUnlock(); + + return i; +} + +/* +================== +ComputeAxisBase +================== +*/ +void ComputeAxisBase( vec3_t normal, vec3_t texX, vec3_t texY ) +{ + vec_t RotY, RotZ; + + if( fabs( normal[0] ) < 1e-6 ) + normal[0] = 0.0f; + + if( fabs( normal[1] ) < 1e-6 ) + normal[1] = 0.0f; + + if( fabs( normal[2] ) < 1e-6 ) + normal[2] = 0.0f; + + // compute the two rotations around y and z to rotate x to normal + RotY = -atan2( normal[2], sqrt( normal[1] * normal[1] + normal[0] * normal[0] )); + RotZ = atan2( normal[1], normal[0] ); + + // rotate (0,1,0) and (0,0,1) to compute texX and texY + texX[0] = -sin( RotZ ); + texX[1] = cos( RotZ ); + texX[2] = 0; + + // the texY vector is along -z (t texture coorinates axis) + texY[0] = -sin( RotY ) * cos( RotZ ); + texY[1] = -sin( RotY ) * sin( RotZ ); + texY[2] = -cos( RotY ); +} + +/* +================== +TextureAxisFromPlane +================== +*/ +void TextureAxisFromNormal( vec3_t normal, vec3_t xv, vec3_t yv, bool brush_primitive ) +{ + vec_t dot, best = 0.0; + int bestaxis = 0; + + if( brush_primitive ) + { + ComputeAxisBase( normal, xv, yv ); + return; + } + + for( int i = 0; i < 6; i++ ) + { + dot = DotProduct( normal, baseaxis[i*3+0] ); + + if( dot > best + 0.0001 ) + { + bestaxis = i; + best = dot; + } + } + + VectorCopy( baseaxis[bestaxis*3+1], xv ); + VectorCopy( baseaxis[bestaxis*3+2], yv ); +} + +/* +================== +TextureAxisFromPlane +================== +*/ +void TextureAxisFromSide( const side_t *side, vec3_t xv, vec3_t yv, bool brush_primitive ) +{ + vec3_t t1, t2, normal; + + VectorSubtract( side->planepts[0], side->planepts[1], t1 ); + VectorSubtract( side->planepts[2], side->planepts[1], t2 ); + CrossProduct( t1, t2, normal ); + VectorNormalize( normal ); + TextureAxisFromNormal( normal, xv, yv, brush_primitive ); +} + +int TexinfoForSide( plane_t *plane, side_t *s, const vec3_t origin ) +{ + vec_t vecs[2][4]; + dtexinfo_t tx, *tc; + int i, j, k; + + if( FBitSet( s->flags, FSIDE_NODRAW|FSIDE_SKIP )) + return -1; + + memset( &tx, 0, sizeof( tx )); + tx.miptex = TEX_FindMiptex( s->name ); + // Note: FindMiptex() still needs to be called here to add it to the global miptex array + tx.faceinfo = s->faceinfo; + + if( FBitSet( s->flags, FSIDE_NOLIGHTMAP )) + SetBits( tx.flags, TEX_SPECIAL ); + + if( FBitSet( s->flags, FSIDE_NOSHADOW )) + SetBits( tx.flags, TEX_NOSHADOW ); + + if( FBitSet( s->flags, FSIDE_NODIRT )) + SetBits( tx.flags, TEX_NODIRT ); + + if( FBitSet( s->flags, FSIDE_SCROLL )) + SetBits( tx.flags, TEX_SCROLL ); + + if( !FBitSet( s->flags, FSIDE_PATCH )) + { + if( g_world_luxels >= 1 ) + SetBits( tx.flags, TEX_WORLD_LUXELS ); + + if( g_world_luxels >= 2 ) + SetBits( tx.flags, TEX_AXIAL_LUXELS ); + } + + // prepare the source vecs + memcpy( vecs, s->vecs, sizeof( vecs )); + + // add the origin offset + if( origin[0] || origin[1] || origin[2] ) + { + vecs[0][3] += DotProduct( origin, vecs[0] ); + vecs[1][3] += DotProduct( origin, vecs[1] ); + } + + // can't use memcpy because size can be mismatched + for( i = 0; i < 2; i++ ) + { + for( j = 0; j < 4; j++ ) + { + tx.vecs[i][j] = vecs[i][j]; + } + } + + // + // find the texinfo + // + ThreadLock(); + tc = g_texinfo; + + for( i = 0; i < g_numtexinfo; i++, tc++ ) + { + // Sleazy hack 104, Pt 3 - Use strcmp on names to avoid dups + if( Q_stricmp( (char *)tc->miptex, s->name )) + continue; + + if( tc->flags != tx.flags ) + continue; + + if( tc->faceinfo != tx.faceinfo ) + continue; + + for( j = 0; j < 2; j++ ) + { + for( k = 0; k < 4; k++ ) + { + if( tc->vecs[j][k] != tx.vecs[j][k] ) + goto skip; + } + } + + ThreadUnlock(); + return i; +skip:; + } + + if( g_numtexinfo == MAX_MAP_TEXINFO ) + COM_FatalError( "MAX_MAP_TEXINFO limit exceeded\n" ); + + // Very Sleazy Hack 104 - since the tx.miptex index will be bogus after we sort the miptex array later + // Put the string name of the miptex in this "index" until after we are done sorting it in WriteMiptex(). + // g-cont. do it only for unique elements, but i can't use copysting here because it may call ThreadLock again + tx.miptex = (int)strdup( s->name ); + + *tc = tx; + g_numtexinfo++; + ThreadUnlock (); + + return i; +} \ No newline at end of file diff --git a/utils/p2csg/utils.cpp b/utils/p2csg/utils.cpp new file mode 100644 index 0000000..9bcf446 --- /dev/null +++ b/utils/p2csg/utils.cpp @@ -0,0 +1,110 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include "csg.h" +#include "aselib.h" +#include "imagelib.h" + +/* +============= +Image_LoadTGA + +expand any image to RGBA32 but keep 8-bit unchanged +============= +*/ +void TEX_LoadTGA( const char *texname, mipentry_t *tex ) +{ + byte buffer[256], *buf_p; + tga_t targa_header; + file_t *f; + + // texture is loaded, wad is not used + f = FS_Open( texname, "rb", false ); + tex->datasize = FS_FileLength( f ); + FS_Read( f, buffer, sizeof( buffer )); + FS_Close( f ); + + if( tex->datasize < sizeof( tga_t )) + return; + + buf_p = (byte *)buffer; + targa_header.id_length = *buf_p++; + targa_header.colormap_type = *buf_p++; + targa_header.image_type = *buf_p++; + + targa_header.colormap_index = *(short *)buf_p; buf_p += 2; + targa_header.colormap_length = *(short *)buf_p; buf_p += 2; + targa_header.colormap_size = *buf_p; buf_p += 1; + targa_header.x_origin = *(short *)buf_p; buf_p += 2; + targa_header.y_origin = *(short *)buf_p; buf_p += 2; + targa_header.width = *(short *)buf_p; buf_p += 2; + targa_header.height = *(short *)buf_p; buf_p += 2; + targa_header.pixel_size = *buf_p++; + targa_header.attributes = *buf_p++; + + // we need only width & height + tex->width = targa_header.width; + tex->height = targa_header.height; + tex->wadnum = -1; // wad is not used +// Msg( "%s width %d, height %d\n", texname, tex->width, tex->height ); +} + +/* +============ +InsertASEModel + +Convert a model entity to raw geometry surfaces and insert it in the tree +============ +*/ +bool InsertASEModel( const char *modelName, mapent_t *mapent, short entindex, short faceinfo ) +{ + side_t modelSide; + int numSurfaces; + const char *name; + polyset_t *pset; + int numFrames; + +//return false; // temporary disabled + + // load the model + if( !ASE_Load( modelName, false )) + return false; + + // each ase surface will become a new bsp surface + numSurfaces = ASE_GetNumSurfaces(); + + // expand, translate, and rotate the vertexes + // swap all the surfaces + for( int i = 0; i < numSurfaces; i++ ) + { + name = ASE_GetSurfaceName( i ); + + pset = ASE_GetSurfaceAnimation( i, &numFrames, -1, -1, -1 ); + if( !name || !pset ) continue; + + memset( &modelSide, 0, sizeof( modelSide )); + + Q_strncpy( modelSide.name, pset->materialname, sizeof( modelSide.name )); + modelSide.shader = ShaderInfoForShader( pset->materialname ); + modelSide.contents = CONTENTS_EMPTY; + modelSide.faceinfo = faceinfo; + + // emit the vertexes + for( int j = 0; j < pset->numtriangles; j++ ) + { + poly_t *tri = &pset->triangles[j]; + + MakeBrushFor3Points( mapent, &modelSide, entindex, &tri->verts[0], &tri->verts[1], &tri->verts[2] ); + } + } + + ASE_Free(); + return true; +} \ No newline at end of file diff --git a/utils/p2rad/alias.cpp b/utils/p2rad/alias.cpp new file mode 100644 index 0000000..9730b6a --- /dev/null +++ b/utils/p2rad/alias.cpp @@ -0,0 +1,404 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// alias.c + +#include "qrad.h" +#include "..\..\engine\alias.h" +#include "model_trace.h" + +static trivertex_t *g_poseverts[MAXALIASFRAMES]; +static stvert_t g_stverts[MAXALIASVERTS]; +static dtriangle_t g_triangles[MAXALIASTRIS]; +static int g_posenum; + +void *LoadSingleSkin( daliasskintype_t *pskintype, int size ) +{ + return ((byte *)(pskintype + 1) + size); +} + +void *LoadGroupSkin( daliasskintype_t *pskintype, int size ) +{ + daliasskininterval_t *pinskinintervals; + daliasskingroup_t *pinskingroup; + int i; + + // animating skin group. yuck. + pskintype++; + pinskingroup = (daliasskingroup_t *)pskintype; + pinskinintervals = (daliasskininterval_t *)(pinskingroup + 1); + pskintype = (daliasskintype_t *)(pinskinintervals + pinskingroup->numskins); + + for( i = 0; i < pinskingroup->numskins; i++ ) + pskintype = (daliasskintype_t *)((byte *)(pskintype) + size); + return pskintype; +} + +/* +=============== +LoadAllSkins +=============== +*/ +void *LoadAllSkins( daliashdr_t *phdr, daliasskintype_t *pskintype ) +{ + int i, size; + + size = phdr->skinwidth * phdr->skinheight; + + for( i = 0; i < phdr->numskins; i++ ) + { + if( pskintype->type == ALIAS_SKIN_SINGLE ) + pskintype = (daliasskintype_t *)LoadSingleSkin( pskintype, size ); + else pskintype = (daliasskintype_t *)LoadGroupSkin( pskintype, size ); + } + + return (void *)pskintype; +} + +/* +================= +LoadAliasFrame +================= +*/ +void *LoadAliasFrame( daliashdr_t *phdr, void *pin ) +{ + daliasframe_t *pdaliasframe; + trivertex_t *pinframe; + + pdaliasframe = (daliasframe_t *)pin; + pinframe = (trivertex_t *)(pdaliasframe + 1); + + g_poseverts[g_posenum] = (trivertex_t *)pinframe; + pinframe += phdr->numverts; + g_posenum++; + + return (void *)pinframe; +} + +/* +================= +LoadAliasGroup +================= +*/ +void *LoadAliasGroup( daliashdr_t *phdr, void *pin ) +{ + daliasgroup_t *pingroup; + int i, numframes; + daliasinterval_t *pin_intervals; + void *ptemp; + + pingroup = (daliasgroup_t *)pin; + numframes = pingroup->numframes; + + pin_intervals = (daliasinterval_t *)(pingroup + 1); + pin_intervals += numframes; + ptemp = (void *)pin_intervals; + + for( i = 0; i < numframes; i++ ) + { + g_poseverts[g_posenum] = (trivertex_t *)((daliasframe_t *)ptemp + 1); + ptemp = (trivertex_t *)((daliasframe_t *)ptemp + 1) + phdr->numverts; + g_posenum++; + } + + return ptemp; +} + +static void AliasSetupTriangle( tmesh_t *mesh, int index ) +{ + tface_t *face = &mesh->faces[index]; + + // calculate mins & maxs + ClearBounds( face->absmin, face->absmax ); + face->contents = CONTENTS_SOLID; + + // calc bounds + AddPointToBounds( mesh->verts[face->a].point, face->absmin, face->absmax ); + AddPointToBounds( mesh->verts[face->a].point, mesh->absmin, mesh->absmax ); + AddPointToBounds( mesh->verts[face->b].point, face->absmin, face->absmax ); + AddPointToBounds( mesh->verts[face->b].point, mesh->absmin, mesh->absmax ); + AddPointToBounds( mesh->verts[face->c].point, face->absmin, face->absmax ); + AddPointToBounds( mesh->verts[face->c].point, mesh->absmin, mesh->absmax ); + ExpandBounds( face->absmin, face->absmax, 1.0 ); + + // alias always have single texture on a mesh + face->texture = mesh->textures; + // setup efficiency intersection data + face->PrepareIntersectionData( mesh->verts ); +} + +static void AliasRelinkFace( aabb_tree_t *tree, tface_t *face ) +{ + // only shadow faces must be linked + if( !face->shadow ) return; + + // find the first node that the facet box crosses + areanode_t *node = tree->areanodes; + + while( 1 ) + { + if( node->axis == -1 ) break; + if( face->absmin[node->axis] > node->dist ) + node = node->children[0]; + else if( face->absmax[node->axis] < node->dist ) + node = node->children[1]; + else break; // crosses the node + } + + // link it in + InsertLinkBefore( &face->area, &node->solid_edicts ); +} + +void LoadAlias( entity_t *ent, void *extradata, long fileLength, int flags ) +{ + const char *modname = ValueForKey( ent, "model" ); + int keyframe = IntForKey( ent, "frame" ); + double start = I_FloatTime(); + dword modelCRC = 0; + daliashdr_t *phdr; + stvert_t *pinstverts; + dtriangle_t *pintriangles; + daliasframetype_t *pframetype; + daliasskintype_t *pskintype; + trivertex_t *poseverts; + int i, j; + + // alias doesn't allow these features + ClearBits( flags, FMESH_VERTEX_LIGHTING|FMESH_MODEL_LIGHTMAPS ); + + if( !extradata ) + { + MsgDev( D_WARN, "LoadAlias: couldn't load %s\n", modname ); + return; + } + + phdr = (daliashdr_t *)extradata; + i = phdr->version; + + if( i != ALIAS_VERSION ) + { + MsgDev( D_ERROR, "%s has wrong version number (%i should be %i)\n", modname, i, ALIAS_VERSION ); + Mem_Free( extradata, C_FILESYSTEM ); + return; + } + + if( phdr->numframes < 1 ) + { + MsgDev( D_ERROR, "%s has invalid # of frames: %d\n", modname, phdr->numframes ); + Mem_Free( extradata, C_FILESYSTEM ); + return; + } + + if( keyframe < 0 || keyframe >= phdr->numframes ) + { + MsgDev( D_WARN, "%s specified invalid frame: %d\n", modname, keyframe ); + keyframe = 0; + } + + if( phdr->numverts <= 0 ) + { + MsgDev( D_ERROR, "model %s has no vertices\n", modname ); + Mem_Free( extradata, C_FILESYSTEM ); + return; + } + + if( phdr->numverts > MAXALIASVERTS ) + { + MsgDev( D_ERROR, "model %s has too many vertices\n", modname ); + Mem_Free( extradata, C_FILESYSTEM ); + return; + } + + if( phdr->numtris <= 0 ) + { + MsgDev( D_ERROR, "model %s has no triangles\n", modname ); + Mem_Free( extradata, C_FILESYSTEM ); + return; + } + + if( phdr->numskins < 1 || phdr->numskins > MAX_SKINS ) + { + MsgDev( D_ERROR, "model %s has invalid # of skins: %d\n", modname, phdr->numskins ); + Mem_Free( extradata, C_FILESYSTEM ); + return; + } + + // load the skins + pskintype = (daliasskintype_t *)&phdr[1]; + pskintype = (daliasskintype_t *)LoadAllSkins( phdr, pskintype ); + + // load base s and t vertices + pinstverts = (stvert_t *)pskintype; + + for( i = 0; i < phdr->numverts; i++ ) + { + g_stverts[i].onseam = pinstverts[i].onseam; + g_stverts[i].s = pinstverts[i].s; + g_stverts[i].t = pinstverts[i].t; + } + + // load triangle lists + pintriangles = (dtriangle_t *)&pinstverts[phdr->numverts]; + + for( i = 0; i < phdr->numtris; i++ ) + { + g_triangles[i].facesfront = pintriangles[i].facesfront; + + for( j = 0; j < 3; j++ ) + g_triangles[i].vertindex[j] = pintriangles[i].vertindex[j]; + } + + // load the frames + pframetype = (daliasframetype_t *)&pintriangles[phdr->numtris]; + g_posenum = 0; + + for( i = 0; i < phdr->numframes; i++ ) + { + aliasframetype_t frametype = pframetype->type; + + if( frametype == ALIAS_SINGLE ) + pframetype = (daliasframetype_t *)LoadAliasFrame( phdr, pframetype + 1 ); + else pframetype = (daliasframetype_t *)LoadAliasGroup( phdr, pframetype + 1 ); + } + + size_t memsize = sizeof( tmesh_t ) + ( sizeof( tface_t ) * phdr->numtris ) + sizeof( timage_t ) + sizeof( tvert_t ) * phdr->numverts; + byte *meshdata = (byte *)Mem_Alloc( memsize ); + byte *meshend = meshdata + memsize; + tmesh_t *mesh = (tmesh_t *)meshdata; + vec3_t origin, angles, point, normal; + matrix3x4 transform; + vec3_t xform; + float scale; + + if( ent->cache ) Mem_Free( ent->cache ); // throw previous instance + ent->cache = meshdata; // FreeEntities will be automatically free that + + // setup pointers + meshdata += sizeof( tmesh_t ); + mesh->faces = (tface_t *)meshdata; + meshdata += sizeof( tface_t ) * phdr->numtris; + mesh->textures = (timage_t *)meshdata; + meshdata += sizeof( timage_t ); + mesh->verts = (tvert_t *)meshdata; + meshdata += sizeof( tvert_t ) * phdr->numverts; + mesh->numverts = phdr->numverts; + + if( meshdata != meshend ) + Msg( "%s memory corrupted\n", modname ); + + // mesh->submodels aren't used because alias model doesn't have the submodels + for( int l = 0; l < MAXLIGHTMAPS; l++ ) + mesh->styles[l] = 255; + + // select the specified frame + poseverts = g_poseverts[keyframe]; + ASSERT( poseverts != NULL ); + + ClearBounds( mesh->absmin, mesh->absmax ); + + // just in case, not needs + mesh->textures->width = phdr->skinwidth; + mesh->textures->height = phdr->skinheight; + + // get model properties + GetVectorForKey( ent, "origin", origin ); + GetVectorForKey( ent, "angles", angles ); + + angles[0] = -angles[0]; // Stupid quake bug workaround + scale = FloatForKey( ent, "scale" ); + GetVectorForKey( ent, "xform", xform ); + if( VectorIsNull( xform )) + VectorFill( xform, scale ); + + // check xform values + if( xform[0] < 0.01f ) xform[0] = 1.0f; + if( xform[1] < 0.01f ) xform[1] = 1.0f; + if( xform[2] < 0.01f ) xform[2] = 1.0f; + if( xform[0] > 16.0f ) xform[0] = 16.0f; + if( xform[1] > 16.0f ) xform[1] = 16.0f; + if( xform[2] > 16.0f ) xform[2] = 16.0f; + + Matrix3x4_CreateFromEntityScale3f( transform, angles, origin, xform ); + + // fill the verts + for( i = 0; i < phdr->numverts; i++ ) + { + stvert_t *st = &g_stverts[i]; + trivertex_t *v = &poseverts[i]; + tvert_t *tv = &mesh->verts[i]; + float s, t; + + point[0] = (float)v->v[0] * phdr->scale[0] + phdr->scale_origin[0]; + point[1] = (float)v->v[1] * phdr->scale[1] + phdr->scale_origin[1]; + point[2] = (float)v->v[2] * phdr->scale[2] + phdr->scale_origin[2]; + Matrix3x4_VectorTransform( transform, point, tv->point ); + + // compute lightvert normal + VectorCopy( g_anorms[v->lightnormalindex], point ); + Matrix3x4_VectorRotate( transform, point, normal ); + VectorNormalize2( normal ); + VectorCopy( normal, tv->normal ); + + s = st->s; + t = st->t; + +#if 0 // FIXME: unreachable. but who cares? + if( !tri->facesfront && st->onseam ) + s += phdr->skinwidth / 2; // on back side +#endif + tv->st[0] = (s + 0.5f) / phdr->skinwidth; + tv->st[1] = (t + 0.5f) / phdr->skinheight; + } + + // fill the triangles + for( i = 0; i < phdr->numtris; i++ ) + { + dtriangle_t *tri = &g_triangles[i]; + tface_t *tf = &mesh->faces[i]; + + tf->a = tri->vertindex[0]; + tf->b = tri->vertindex[1]; + tf->c = tri->vertindex[2]; + tf->shadow = true; // used for trace + } + + // do some post-initialization + for( i = 0; i < phdr->numtris; i++ ) + AliasSetupTriangle( mesh, i ); + + // create AABB tree for speedup reasons + CreateAreaNode( &mesh->face_tree, 0, AREA_MIN_DEPTH, mesh->absmin, mesh->absmax ); + + for( i = 0; i < phdr->numtris; i++ ) + AliasRelinkFace( &mesh->face_tree, &mesh->faces[i] ); + + Mem_Free( extradata, C_FILESYSTEM ); + ent->modtype = mod_alias; // now our mesh is valid and ready to trace + mesh->backfrac = FloatForKey( ent, "zhlt_backfrac" ); + mesh->flags = flags; // copy settings + mesh->modelCRC = modelCRC; + + MsgDev( D_REPORT, "%s: build time %g secs, size %s\n", modname, I_FloatTime() - start, Q_memprint( memsize )); +} + +void AliasGetBounds( entity_t *ent, vec3_t mins, vec3_t maxs ) +{ + tmesh_t *mesh = (tmesh_t *)ent->cache; + + // assume point hull + VectorClear( mins ); + VectorClear( maxs ); + + if( !mesh || ent->modtype != mod_alias ) + return; + + VectorSubtract( mesh->absmin, ent->origin, mins ); + VectorSubtract( mesh->absmax, ent->origin, maxs ); +} \ No newline at end of file diff --git a/utils/p2rad/ambientcube.cpp b/utils/p2rad/ambientcube.cpp new file mode 100644 index 0000000..56afebd --- /dev/null +++ b/utils/p2rad/ambientcube.cpp @@ -0,0 +1,882 @@ +#include "qrad.h" + +#ifdef HLRAD_AMBIENTCUBES + +// the angle between consecutive g_anorms[] vectors is ~14.55 degrees +#define MIN_LOCAL_SAMPLES 4 +#define MAX_LOCAL_SAMPLES 16 // unsigned byte limit +#define MAX_SAMPLES 32 // enough +#define MAX_LEAF_PLANES 512 // QuArK cylinder :-) +#define AMBIENT_SCALE 128.0 // ambient clamp at 128 +#define GAMMA ( 2.2f ) // Valve Software gamma +#define INVGAMMA ( 1.0f / 2.2f ) // back to 1.0 + +static vec3_t g_BoxDirections[6] = +{ + { 1, 0, 0 }, + { -1, 0, 0 }, + { 0, 1, 0 }, + { 0, -1, 0 }, + { 0, 0, 1 }, + { 0, 0, -1 }, +}; + +// this stores each sample of the ambient lighting +struct ambientsample_t +{ + vec3_t cube[6]; + vec3_t pos; +}; + +struct ambientlist_t +{ + int numSamples; + ambientsample_t *samples; +}; + +struct ambientlocallist_t +{ + ambientsample_t samples[MAX_LOCAL_SAMPLES]; + int numSamples; +}; + +struct leafplanes_t +{ + dplane_t planes[MAX_LEAF_PLANES]; + int numLeafPlanes; +}; + +typedef struct +{ + vec3_t diffuse; + vec3_t average; + float fraction; + dface_t *surf; + bool hitsky; +} lightpoint_t; + +static int leafparents[MAX_MAP_LEAFS]; +static int nodeparents[MAX_MAP_NODES]; +ambientlist_t g_leaf_samples[MAX_MAP_LEAFS]; + +static void MakeParents( const int nodenum, const int parent ) +{ + dnode_t *node; + int i, j; + + nodeparents[nodenum] = parent; + node = g_dnodes + nodenum; + + for( i = 0; i < 2; i++ ) + { + j = node->children[i]; + if( j < 0 ) leafparents[-j - 1] = nodenum; + else MakeParents(j, nodenum); + } +} + +static vec_t LightAngle( const dworldlight_t *wl, const vec3_t lnormal, const vec3_t snormal, const vec3_t delta, float dist ) +{ + vec_t dot, dot2; + + ASSERT( wl->emittype == emit_surface ); + + dot = DotProduct( snormal, delta ); + if( dot <= NORMAL_EPSILON ) + return 0; // behind light surface + + dot2 = -DotProduct( delta, lnormal ); + if( dot2 * dist <= MINIMUM_PATCH_DISTANCE ) + return 0; // behind light surface + + // variable power falloff (1 = inverse linear, 2 = inverse square) + vec_t denominator = dist * wl->fade; + + if( wl->falloff == 2 ) + denominator *= dist; + + return dot * dot2 / denominator; +} + +static vec_t LightDistanceFalloff( const dworldlight_t *wl, const vec3_t delta ) +{ + vec_t radius = DotProduct( delta, delta ); + + ASSERT( wl->emittype == emit_surface ); + + // cull out stuff that's too far + if( wl->radius != 0 ) + { + if( radius > ( wl->radius * wl->radius )) + return 0.0f; + } + + if( radius < 1.0 ) + return 1.0; + return 1.0 / radius; +} + +static void AddEmitSurfaceLights( int threadnum, const vec3_t vStart, vec3_t lightBoxColor[6] ) +{ + vec3_t wlOrigin; + trace_t trace; + + for( int iLight = 0; iLight < g_numworldlights; iLight++ ) + { + dworldlight_t *wl = &g_dworldlights[iLight]; + + // Should this light even go in the ambient cubes? + if( !FBitSet( wl->flags, DWL_FLAGS_INAMBIENTCUBE )) + continue; + + ASSERT( wl->emittype == emit_surface ); + + VectorCopy( wl->origin, wlOrigin ); // short to float + + // Can this light see the point? + TestLine( threadnum, vStart, wlOrigin, &trace ); + + if( trace.contents != CONTENTS_EMPTY ) + continue; + + // add this light's contribution. + vec3_t vDelta, vDeltaNorm; + VectorSubtract( wlOrigin, vStart, vDelta ); + float flDistanceScale = LightDistanceFalloff( wl, vDelta ); + + VectorCopy( vDelta, vDeltaNorm ); + VectorMA( vDeltaNorm, -DEFAULT_HUNT_OFFSET * 0.5, wl->normal, vDeltaNorm ); + float dist = VectorNormalize( vDeltaNorm ); + dist = Q_max( dist, 1.0 ); + + float flAngleScale = LightAngle( wl, wl->normal, vDeltaNorm, vDeltaNorm, dist ); + + float ratio = flDistanceScale * flAngleScale * trace.fraction; + if( ratio == 0.0 ) continue; + + for( int i = 0; i < 6; i++ ) + { + vec_t t = DotProduct( g_BoxDirections[i], vDeltaNorm ); + if( t > 0.0 ) VectorMA( lightBoxColor[i], (t * ratio), wl->intensity, lightBoxColor[i] ); + } + } +} + +static bool R_GetDirectLightFromSurface( dface_t *surf, const vec3_t point, lightpoint_t *info ) +{ + faceneighbor_t *fn = &g_faceneighbor[surf - g_dfaces]; + int texture_step = GetTextureStep( surf ); + dtexinfo_t *tex = g_texinfo + surf->texinfo; + int map, size, smax, tmax; + float lmvecs[2][4]; + vec_t s, t; + byte *lm; + + LightMatrixFromTexMatrix( tex, lmvecs ); + + // recalc face extents here + s = DotProduct( point, lmvecs[0] ) + lmvecs[0][3] - fn->lightmapmins[0]; + t = DotProduct( point, lmvecs[1] ) + lmvecs[1][3] - fn->lightmapmins[1]; + + if(( s < 0 || s > fn->lightextents[0] ) || ( t < 0 || t > fn->lightextents[1] )) + return false; + + if( FBitSet( tex->flags, TEX_SPECIAL )) + { + const char *texname = GetTextureByTexinfo( surf->texinfo ); + + if( !Q_strnicmp( texname, "sky", 3 )) + info->hitsky = true; + return false; // no lightmaps + } + + if( surf->lightofs == -1 ) + return true; + + smax = (fn->lightextents[0] / texture_step) + 1; + tmax = (fn->lightextents[1] / texture_step) + 1; + s /= (vec_t)texture_step; + t /= (vec_t)texture_step; + + byte *samples = g_dlightdata + (unsigned int)surf->lightofs; + lm = samples + (unsigned int)Q_rint( t ) * smax + Q_rint( s ); + size = smax * tmax; + + VectorClear( info->diffuse ); + + for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255; map++ ) + { + info->diffuse[0] += (float)lm[0] * 264.0f; + info->diffuse[1] += (float)lm[1] * 264.0f; + info->diffuse[2] += (float)lm[2] * 264.0f; + lm += size; // skip to next lightmap + } + + // same as shift >> 7 in quake + info->diffuse[0] = Q_min( info->diffuse[0] * (1.0f / 128.0f), 255.0f ) * (1.0f / 255.0f); + info->diffuse[1] = Q_min( info->diffuse[1] * (1.0f / 128.0f), 255.0f ) * (1.0f / 255.0f); + info->diffuse[2] = Q_min( info->diffuse[2] * (1.0f / 128.0f), 255.0f ) * (1.0f / 255.0f); + VectorClear( info->average ); + lm = samples; + + // also collect the average value + for( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255; map++ ) + { + for( int i = 0; i < size; i++, lm += 3 ) + { + info->average[0] += (float)lm[0] * 264.0f; + info->average[1] += (float)lm[1] * 264.0f; + info->average[2] += (float)lm[2] * 264.0f; + } + VectorScale( info->average, ( 1.0f / (float)size ), info->average ); + } + + // same as shift >> 7 in quake + info->average[0] = Q_min( info->average[0] * (1.0f / 128.0f), 255.0f ) * (1.0f / 255.0f); + info->average[1] = Q_min( info->average[1] * (1.0f / 128.0f), 255.0f ) * (1.0f / 255.0f); + info->average[2] = Q_min( info->average[2] * (1.0f / 128.0f), 255.0f ) * (1.0f / 255.0f); + info->surf = surf; + + return true; +} + +/* +================= +R_RecursiveLightPoint +================= +*/ +static bool R_RecursiveLightPoint( const int nodenum, float p1f, float p2f, const vec3_t start, const vec3_t end, lightpoint_t *info ) +{ + vec3_t mid; + + // hit a leaf + if( nodenum < 0 ) return false; + dnode_t *node = g_dnodes + nodenum; + dplane_t *plane = g_dplanes + node->planenum; + + // calculate mid point + float front = PlaneDiff( start, plane ); + float back = PlaneDiff( end, plane ); + + int side = front < 0.0f; + if(( back < 0.0f ) == side ) + return R_RecursiveLightPoint( node->children[side], p1f, p2f, start, end, info ); + + float frac = front / ( front - back ); + + float midf = p1f + ( p2f - p1f ) * frac; + VectorLerp( start, frac, end, mid ); + + // co down front side + if( R_RecursiveLightPoint( node->children[side], p1f, midf, start, mid, info )) + return true; // hit something + + if(( back < 0.0f ) == side ) + return false;// didn't hit anything + + // check for impact on this node + for( int i = 0; i < node->numfaces; i++ ) + { + dface_t *surf = g_dfaces + node->firstface + i; + + if( R_GetDirectLightFromSurface( surf, mid, info )) + { + info->fraction = midf; + return true; + } + } + + // go down back side + return R_RecursiveLightPoint( node->children[!side], midf, p2f, mid, end, info ); +} + +//----------------------------------------------------------------------------- +// Finds ambient sky lights +//----------------------------------------------------------------------------- +static dworldlight_t *FindAmbientSkyLight( void ) +{ + static dworldlight_t *s_pCachedSkylight = NULL; + + // Don't keep searching for the same light. + if( !s_pCachedSkylight ) + { + // find any ambient lights + for( int iLight = 0; iLight < g_numworldlights; iLight++ ) + { + dworldlight_t *wl = &g_dworldlights[iLight]; + + if( wl->emittype == emit_skylight ) + { + s_pCachedSkylight = wl; + break; + } + } + } + + return s_pCachedSkylight; +} + +static void ComputeLightmapColorFromPoint( lightpoint_t *info, dworldlight_t* pSkylight, float scale, vec3_t radcolor, bool average ) +{ + vec3_t color; + + if( !info->surf && info->hitsky ) + { + if( pSkylight ) + { + VectorScale( pSkylight->intensity, scale * 0.5, color ); + VectorScale( pSkylight->intensity, (1.0 / 255.0), color ); + VectorAdd( radcolor, color, radcolor ); + } + return; + } + + if( info->surf != NULL ) + { + if( average ) VectorScale( info->average, scale, color ); + else VectorScale( info->diffuse, scale, color ); +#if 0 + vec3_t light, reflectivity; + + BaseLightForFace( info->surf, light, reflectivity ); + + if( !VectorCompare( reflectivity, vec3_origin )) + VectorMultiply( color, reflectivity, color ); +#endif + VectorAdd( radcolor, color, radcolor ); + } +} + +//----------------------------------------------------------------------------- +// Computes ambient lighting along a specified ray. +// Ray represents a cone, tanTheta is the tan of the inner cone angle +//----------------------------------------------------------------------------- +static void CalcRayAmbientLighting( const vec3_t vStart, const vec3_t vEnd, dworldlight_t *pSkyLight, float tanTheta, vec3_t radcolor ) +{ + lightpoint_t info; + vec3_t vDelta; + + memset( &info, 0, sizeof( lightpoint_t )); + info.fraction = 1.0f; + VectorClear( radcolor ); + + // Now that we've got a ray, see what surface we've hit + R_RecursiveLightPoint( 0, 0.0f, 1.0f, vStart, vEnd, &info ); + + VectorSubtract( vEnd, vStart, vDelta ); + + // compute the approximate radius of a circle centered around the intersection point + float dist = VectorLength( vDelta ) * tanTheta * info.fraction; + + // until 20" we use the point sample, then blend in the average until we're covering 40" + // This is attempting to model the ray as a cone - in the ideal case we'd simply sample all + // luxels in the intersection of the cone with the surface. Since we don't have surface + // neighbor information computed we'll just approximate that sampling with a blend between + // a point sample and the face average. + // This yields results that are similar in that aliasing is reduced at distance while + // point samples provide accuracy for intersections with near geometry + float scaleAvg = RemapValClamped( dist, 20, 40, 0.0f, 1.0f ); + + if( !info.surf ) + { + // don't have luxel UV, so just use average sample + scaleAvg = 1.0; + } + + float scaleSample = 1.0f - scaleAvg; + + if( scaleAvg != 0 ) + { + ComputeLightmapColorFromPoint( &info, pSkyLight, scaleAvg, radcolor, true ); + } + + if( scaleSample != 0 ) + { + ComputeLightmapColorFromPoint( &info, pSkyLight, scaleSample, radcolor, false ); + } +} + +static void ComputeAmbientFromSphericalSamples( int threadnum, const vec3_t p1, vec3_t lightBoxColor[6] ) +{ + // Figure out the color that rays hit when shot out from this position. + float tanTheta = tan( VERTEXNORMAL_CONE_INNER_ANGLE ); + dworldlight_t *pSkyLight = FindAmbientSkyLight(); + vec3_t radcolor[NUMVERTEXNORMALS], p2; + + for( int i = 0; i < NUMVERTEXNORMALS; i++ ) + { + VectorMA( p1, (65536.0 * 1.74), g_anorms[i], p2 ); + + // Now that we've got a ray, see what surface we've hit + CalcRayAmbientLighting( p1, p2, pSkyLight, tanTheta, radcolor[i] ); + } + + // accumulate samples into radiant box + for ( int j = 0; j < 6; j++ ) + { + float t = 0.0f; + + VectorClear( lightBoxColor[j] ); + + for( int i = 0; i < NUMVERTEXNORMALS; i++ ) + { + float c = DotProduct( g_anorms[i], g_BoxDirections[j] ); + + if( c > 0.0f ) + { + VectorMA( lightBoxColor[j], c, radcolor[i], lightBoxColor[j] ); + t += c; + } + } + + VectorScale( lightBoxColor[j], ( 1.0 / t ), lightBoxColor[j] ); + } + + // Now add direct light from the emit_surface lights. These go in the ambient cube because + // there are a ton of them and they are often so dim that they get filtered out by r_worldlightmin. + AddEmitSurfaceLights( threadnum, p1, lightBoxColor ); +} + +bool IsLeafAmbientSurfaceLight( dworldlight_t *wl ) +{ + const float g_flWorldLightMinEmitSurfaceDistanceRatio = 0.000003f; + const float g_flWorldLightMinEmitSurface = 0.005f; + + if( wl->emittype != emit_surface ) + return false; + + if( wl->style != 0 ) + return false; + + float intensity = VectorMax( wl->intensity ); + + return ( intensity * g_flWorldLightMinEmitSurfaceDistanceRatio ) < g_flWorldLightMinEmitSurface; +} + +// Generate a random point in the leaf's bounding volume +// reject any points that aren't actually in the leaf +// do a couple of tracing heuristics to eliminate points that are inside detail brushes +// or underneath displacement surfaces in the leaf +// return once we have a valid point, use the center if one can't be computed quickly +void GenerateLeafSamplePosition( int leafIndex, ambientlocallist_t *list, const leafplanes_t *leafPlanes, vec3_t samplePosition ) +{ + dleaf_t *pLeaf = g_dleafs + leafIndex; + vec3_t vCenter, leafMins, leafMaxs; + + VectorCopy( pLeaf->mins, leafMins ); + VectorCopy( pLeaf->maxs, leafMaxs ); + bool bValid = false; + + VectorAverage( leafMins, leafMaxs, vCenter ); + + // place first sample always at center to leaf + if( list->numSamples == 0 ) + { + VectorCopy( vCenter, samplePosition ); + return; + } + + // lock so random float will working properly + ThreadLock(); + + for( int i = 0; i < 1024 && !bValid; i++ ) + { + VectorLerp( leafMins, RandomFloat( 0.01f, 0.99f ), leafMaxs, samplePosition ); + vec3_t vDiff; + + int l; + for( l = 0; l < list->numSamples; l++ ) + { + VectorSubtract( samplePosition, list->samples[l].pos, vDiff ); + float flLength = VectorLength( vDiff ); + if( flLength < 32.0f ) break; // too tight + } + + if( l != list->numSamples ) + continue; + + bValid = true; + + for( int k = leafPlanes->numLeafPlanes - 1; --k >= 0 && bValid; ) + { + float d = PlaneDiff( samplePosition, leafPlanes->planes + k ); + + if( d < DIST_EPSILON ) + { + // not inside the leaf, try again + bValid = false; + break; + } + } + } + + ThreadUnlock(); + + if( !bValid ) + { + // didn't generate a valid sample point, just use the center of the leaf bbox + VectorCopy( vCenter, samplePosition ); + } +} + +// gets a list of the planes pointing into a leaf +void GetLeafBoundaryPlanes( leafplanes_t *list, int leafIndex ) +{ + int nodeIndex = leafparents[leafIndex]; + int child = -(leafIndex + 1); + + while( nodeIndex >= 0 ) + { + dnode_t *pNode = g_dnodes + nodeIndex; + dplane_t *pNodePlane = g_dplanes + pNode->planenum; + + if( pNode->children[0] == child ) + { + // front side + list->planes[list->numLeafPlanes] = *pNodePlane; + } + else + { + // back side + int plane = list->numLeafPlanes; + list->planes[plane].dist = -pNodePlane->dist; + list->planes[plane].normal[0] = -pNodePlane->normal[0]; + list->planes[plane].normal[1] = -pNodePlane->normal[1]; + list->planes[plane].normal[2] = -pNodePlane->normal[2]; + list->planes[plane].type = pNodePlane->type; + list->numLeafPlanes++; + } + + if( list->numLeafPlanes >= MAX_LEAF_PLANES ) + break; // there was a too many planes + + child = nodeIndex; + nodeIndex = nodeparents[child]; + } +} + +// add the sample to the list. If we exceed the maximum number of samples, the worst sample will +// be discarded. This has the effect of converging on the best samples when enough are added. +void AddSampleToList( ambientlocallist_t *list, const vec3_t samplePosition, vec3_t pCube[6] ) +{ + int i, index = list->numSamples++; + + VectorCopy( samplePosition, list->samples[index].pos ); + + for( int k = 0; k < 6; k++ ) + { + VectorCopy( pCube[k], list->samples[index].cube[k] ); + } + + if( list->numSamples <= MAX_SAMPLES ) + return; + + ambientlocallist_t oldlist; + int nearestNeighborIndex = 0; + float nearestNeighborDist = FLT_MAX; + float nearestNeighborTotal = 0; + + // do a copy of current list + memcpy( &oldlist, list, sizeof( ambientlocallist_t )); + + for( i = 0; i < oldlist.numSamples; i++ ) + { + int closestIndex = 0; + float closestDist = FLT_MAX; + float totalDC = 0; + + for( int j = 0; j < oldlist.numSamples; j++ ) + { + if( j == i ) continue; + + vec3_t vDelta; + + VectorSubtract( oldlist.samples[i].pos, oldlist.samples[j].pos, vDelta ); + + float dist = VectorLength( vDelta ); + float maxDC = 0; + + for( int k = 0; k < 6; k++ ) + { + // color delta is computed per-component, per cube side + for( int s = 0; s < 3; s++ ) + { + float dc = fabs( oldlist.samples[i].cube[k][s] - oldlist.samples[j].cube[k][s] ); + maxDC = Q_max( maxDC, dc ); + } + + totalDC += maxDC; + } + + // need a measurable difference in color or we'll just rely on position + if( maxDC < 1e-4f ) + { + maxDC = 0; + } + else if( maxDC > 1.0f ) + { + maxDC = 1.0f; + } + + // selection criteria is 10% distance, 90% color difference + // choose samples that fill the space (large distance from each other) + // and have largest color variation + float distanceFactor = 0.1f + (maxDC * 0.9f); + dist *= distanceFactor; + + // find the "closest" sample to this one + if( dist < closestDist ) + { + closestDist = dist; + closestIndex = j; + } + } + + // the sample with the "closest" neighbor is rejected + if( closestDist < nearestNeighborDist || ( closestDist == nearestNeighborDist && totalDC < nearestNeighborTotal )) + { + nearestNeighborDist = closestDist; + nearestNeighborIndex = i; + } + } + + list->numSamples = 0; + + // copy the entries back but skip nearestNeighborIndex + for( i = 0; i < oldlist.numSamples; i++ ) + { + if( i != nearestNeighborIndex ) + { + memcpy( &list->samples[list->numSamples], &oldlist.samples[i], sizeof( ambientsample_t )); + list->numSamples++; + } + } +} + +// max number of units in gamma space of per-side delta +int CubeDeltaColor( vec3_t pCube0[6], vec3_t pCube1[6] ) +{ + int maxDelta = 0; + + // do this comparison in gamma space to try and get a perceptual basis for the compare + for( int i = 0; i < 6; i++ ) + { + for ( int j = 0; j < 3; j++ ) + { + int val0 = pCube0[i][j]; + int val1 = pCube1[i][j]; + int delta = abs( val0 - val1 ); + + if( delta > maxDelta ) + maxDelta = delta; + } + } + + return maxDelta; +} +// reconstruct the ambient lighting for a leaf at the given position in worldspace +// optionally skip one of the entries in the list +void Mod_LeafAmbientColorAtPos( vec3_t pOut[6], const vec3_t pos, ambientlocallist_t *list, int skipIndex ) +{ + vec3_t vDelta; + int i; + + for( i = 0; i < 6; i++ ) + { + VectorClear( pOut[i] ); + } + + float totalFactor = 0.0f; + + for( i = 0; i < list->numSamples; i++ ) + { + if ( i == skipIndex ) + continue; + + // do an inverse squared distance weighted average of the samples to reconstruct + // the original function + VectorSubtract( list->samples[i].pos, pos, vDelta ); + float dist = DotProduct( vDelta, vDelta ); + float factor = 1.0f / (dist + 1.0f); + totalFactor += factor; + + for( int j = 0; j < 6; j++ ) + { + VectorMA( pOut[j], factor, list->samples[i].cube[j], pOut[j] ); + } + } + + for( i = 0; i < 6; i++ ) + { + VectorScale( pOut[i], (1.0f / totalFactor), pOut[i] ); + } +} + +// this samples the lighting at each sample and removes any unnecessary samples +void CompressAmbientSampleList( ambientlocallist_t *list ) +{ + ambientlocallist_t oldlist; + vec3_t testCube[6]; + + // do a copy of current list + memcpy( &oldlist, list, sizeof( ambientlocallist_t )); + list->numSamples = 0; + + for( int i = 0; i < oldlist.numSamples; i++ ) + { + Mod_LeafAmbientColorAtPos( testCube, oldlist.samples[i].pos, &oldlist, i ); + + // at least one sample must be included in the list + if( i == 0 || CubeDeltaColor( testCube, oldlist.samples[i].cube ) >= 10 ) + { + memcpy( &list->samples[list->numSamples], &oldlist.samples[i], sizeof( ambientsample_t )); + list->numSamples++; + } + } +} + +void ComputeAmbientForLeaf( int threadnum, int leafID, ambientlocallist_t *list ) +{ + leafplanes_t leafPlanes; + + leafPlanes.numLeafPlanes = 0; + + GetLeafBoundaryPlanes( &leafPlanes, leafID ); + + // this heuristic tries to generate at least one sample per volume (chosen to be similar to the size of a player) in the space + int xSize = (g_dleafs[leafID].maxs[0] - g_dleafs[leafID].mins[0]) / 64; + int ySize = (g_dleafs[leafID].maxs[1] - g_dleafs[leafID].mins[1]) / 64; + int zSize = (g_dleafs[leafID].maxs[2] - g_dleafs[leafID].mins[2]) / 64; + + xSize = Q_max( xSize, 1 ); + ySize = Q_max( ySize, 1 ); + zSize = Q_max( zSize, 1 ); + + // generate update 128 candidate samples, always at least one sample + int volumeCount = xSize * ySize * zSize; + + if( g_fastmode ) + volumeCount *= 0.01; + else if( !g_extra ) + volumeCount *= 0.05; + else volumeCount *= 0.1; + + int sampleCount = bound( MIN_LOCAL_SAMPLES, volumeCount, MAX_LOCAL_SAMPLES ); + + if( g_dleafs[leafID].contents == CONTENTS_SOLID ) + { + // don't generate any samples in solid leaves + // NOTE: We copy the nearest non-solid leaf + // sample pointers into this leaf at the end + return; + } + + vec3_t cube[6]; + + for( int i = 0; i < sampleCount; i++ ) + { + // compute each candidate sample and add to the list + vec3_t samplePosition; + GenerateLeafSamplePosition( leafID, list, &leafPlanes, samplePosition ); + ComputeAmbientFromSphericalSamples( threadnum, samplePosition, cube ); + // note this will remove the least valuable sample once the limit is reached + AddSampleToList( list, samplePosition, cube ); + } + + // remove any samples that can be reconstructed with the remaining data + CompressAmbientSampleList( list ); +} + +static void LeafAmbientLighting( int threadnum ) +{ + ambientlocallist_t list; + + while( 1 ) + { + int leafID = GetThreadWork (); + if( leafID == -1 ) break; + + list.numSamples = 0; + + ComputeAmbientForLeaf( threadnum, leafID, &list ); + + // copy to the output array + g_leaf_samples[leafID].numSamples = list.numSamples; + g_leaf_samples[leafID].samples = (ambientsample_t *)Mem_Alloc( sizeof( ambientsample_t ) * list.numSamples ); + memcpy( g_leaf_samples[leafID].samples, list.samples, sizeof( ambientsample_t ) * list.numSamples ); + } +} + +void ComputeLeafAmbientLighting( void ) +{ + // Figure out which lights should go in the per-leaf ambient cubes. + int nInAmbientCube = 0; + int nSurfaceLights = 0; + int i; + + // always matched + memset( g_leaf_samples, 0, sizeof( g_leaf_samples )); + + for( i = 0; i < g_numworldlights; i++ ) + { + dworldlight_t *wl = &g_dworldlights[i]; + + if( IsLeafAmbientSurfaceLight( wl )) + SetBits( wl->flags, DWL_FLAGS_INAMBIENTCUBE ); + else ClearBits( wl->flags, DWL_FLAGS_INAMBIENTCUBE ); + + if( wl->emittype == emit_surface ) + nSurfaceLights++; + + if( FBitSet( wl->flags, DWL_FLAGS_INAMBIENTCUBE )) + nInAmbientCube++; + } + + srand( time( NULL )); // init random generator + MakeParents( 0, -1 ); + + MsgDev( D_REPORT, "%d of %d (%d%% of) surface lights went in leaf ambient cubes.\n", + nInAmbientCube, nSurfaceLights, nSurfaceLights ? ((nInAmbientCube*100) / nSurfaceLights) : 0 ); + + RunThreadsOn( g_dmodels[0].visleafs + 1, true, LeafAmbientLighting ); + + // clear old samples + g_numleaflights = 0; + + for ( int leafID = 0; leafID < g_dmodels[0].visleafs + 1; leafID++ ) + { + ambientlist_t *list = &g_leaf_samples[leafID]; + + ASSERT( list->numSamples <= 255 ); + + if( !list->numSamples ) continue; + + // compute the samples in disk format. Encode the positions in 8-bits using leaf bounds fractions + for ( int i = 0; i < list->numSamples; i++ ) + { + if( g_numleaflights == MAX_MAP_LEAFLIGHTS ) + COM_FatalError( "MAX_MAP_LEAFLIGHTS limit exceeded\n" ); + + dleafsample_t *sample = &g_dleaflights[g_numleaflights]; + + for( int j = 0; j < 3; j++ ) + sample->origin[j] = (short)bound( -32767, (int)list->samples[i].pos[j], 32767 ); + sample->leafnum = leafID; + + for( int side = 0; side < 6; side++ ) + { + sample->ambient.color[side][0] = bound( 0, list->samples[i].cube[side][0] * 255, 255 ); + sample->ambient.color[side][1] = bound( 0, list->samples[i].cube[side][1] * 255, 255 ); + sample->ambient.color[side][2] = bound( 0, list->samples[i].cube[side][2] * 255, 255 ); + } + g_numleaflights++; + } + + Mem_Free( list->samples ); // release src + list->samples = NULL; + } + + MsgDev( D_REPORT, "%i ambient samples stored\n", g_numleaflights ); +} + +#endif \ No newline at end of file diff --git a/utils/p2rad/anorms.h b/utils/p2rad/anorms.h new file mode 100644 index 0000000..6da15ca --- /dev/null +++ b/utils/p2rad/anorms.h @@ -0,0 +1,649 @@ + +{ +-0.525731, 0.000000, 0.850651} + +, +{ +-0.442863, 0.238856, 0.864188} + +, +{ +-0.295242, 0.000000, 0.955423} + +, +{ +-0.309017, 0.500000, 0.809017} + +, +{ +-0.162460, 0.262866, 0.951056} + +, +{ +0.000000, 0.000000, 1.000000} + +, +{ +0.000000, 0.850651, 0.525731} + +, +{ +-0.147621, 0.716567, 0.681718} + +, +{ +0.147621, 0.716567, 0.681718} + +, +{ +0.000000, 0.525731, 0.850651} + +, +{ +0.309017, 0.500000, 0.809017} + +, +{ +0.525731, 0.000000, 0.850651} + +, +{ +0.295242, 0.000000, 0.955423} + +, +{ +0.442863, 0.238856, 0.864188} + +, +{ +0.162460, 0.262866, 0.951056} + +, +{ +-0.681718, 0.147621, 0.716567} + +, +{ +-0.809017, 0.309017, 0.500000} + +, +{ +-0.587785, 0.425325, 0.688191} + +, +{ +-0.850651, 0.525731, 0.000000} + +, +{ +-0.864188, 0.442863, 0.238856} + +, +{ +-0.716567, 0.681718, 0.147621} + +, +{ +-0.688191, 0.587785, 0.425325} + +, +{ +-0.500000, 0.809017, 0.309017} + +, +{ +-0.238856, 0.864188, 0.442863} + +, +{ +-0.425325, 0.688191, 0.587785} + +, +{ +-0.716567, 0.681718, -0.147621} + +, +{ +-0.500000, 0.809017, -0.309017} + +, +{ +-0.525731, 0.850651, 0.000000} + +, +{ +0.000000, 0.850651, -0.525731} + +, +{ +-0.238856, 0.864188, -0.442863} + +, +{ +0.000000, 0.955423, -0.295242} + +, +{ +-0.262866, 0.951056, -0.162460} + +, +{ +0.000000, 1.000000, 0.000000} + +, +{ +0.000000, 0.955423, 0.295242} + +, +{ +-0.262866, 0.951056, 0.162460} + +, +{ +0.238856, 0.864188, 0.442863} + +, +{ +0.262866, 0.951056, 0.162460} + +, +{ +0.500000, 0.809017, 0.309017} + +, +{ +0.238856, 0.864188, -0.442863} + +, +{ +0.262866, 0.951056, -0.162460} + +, +{ +0.500000, 0.809017, -0.309017} + +, +{ +0.850651, 0.525731, 0.000000} + +, +{ +0.716567, 0.681718, 0.147621} + +, +{ +0.716567, 0.681718, -0.147621} + +, +{ +0.525731, 0.850651, 0.000000} + +, +{ +0.425325, 0.688191, 0.587785} + +, +{ +0.864188, 0.442863, 0.238856} + +, +{ +0.688191, 0.587785, 0.425325} + +, +{ +0.809017, 0.309017, 0.500000} + +, +{ +0.681718, 0.147621, 0.716567} + +, +{ +0.587785, 0.425325, 0.688191} + +, +{ +0.955423, 0.295242, 0.000000} + +, +{ +1.000000, 0.000000, 0.000000} + +, +{ +0.951056, 0.162460, 0.262866} + +, +{ +0.850651, -0.525731, 0.000000} + +, +{ +0.955423, -0.295242, 0.000000} + +, +{ +0.864188, -0.442863, 0.238856} + +, +{ +0.951056, -0.162460, 0.262866} + +, +{ +0.809017, -0.309017, 0.500000} + +, +{ +0.681718, -0.147621, 0.716567} + +, +{ +0.850651, 0.000000, 0.525731} + +, +{ +0.864188, 0.442863, -0.238856} + +, +{ +0.809017, 0.309017, -0.500000} + +, +{ +0.951056, 0.162460, -0.262866} + +, +{ +0.525731, 0.000000, -0.850651} + +, +{ +0.681718, 0.147621, -0.716567} + +, +{ +0.681718, -0.147621, -0.716567} + +, +{ +0.850651, 0.000000, -0.525731} + +, +{ +0.809017, -0.309017, -0.500000} + +, +{ +0.864188, -0.442863, -0.238856} + +, +{ +0.951056, -0.162460, -0.262866} + +, +{ +0.147621, 0.716567, -0.681718} + +, +{ +0.309017, 0.500000, -0.809017} + +, +{ +0.425325, 0.688191, -0.587785} + +, +{ +0.442863, 0.238856, -0.864188} + +, +{ +0.587785, 0.425325, -0.688191} + +, +{ +0.688191, 0.587785, -0.425325} + +, +{ +-0.147621, 0.716567, -0.681718} + +, +{ +-0.309017, 0.500000, -0.809017} + +, +{ +0.000000, 0.525731, -0.850651} + +, +{ +-0.525731, 0.000000, -0.850651} + +, +{ +-0.442863, 0.238856, -0.864188} + +, +{ +-0.295242, 0.000000, -0.955423} + +, +{ +-0.162460, 0.262866, -0.951056} + +, +{ +0.000000, 0.000000, -1.000000} + +, +{ +0.295242, 0.000000, -0.955423} + +, +{ +0.162460, 0.262866, -0.951056} + +, +{ +-0.442863, -0.238856, -0.864188} + +, +{ +-0.309017, -0.500000, -0.809017} + +, +{ +-0.162460, -0.262866, -0.951056} + +, +{ +0.000000, -0.850651, -0.525731} + +, +{ +-0.147621, -0.716567, -0.681718} + +, +{ +0.147621, -0.716567, -0.681718} + +, +{ +0.000000, -0.525731, -0.850651} + +, +{ +0.309017, -0.500000, -0.809017} + +, +{ +0.442863, -0.238856, -0.864188} + +, +{ +0.162460, -0.262866, -0.951056} + +, +{ +0.238856, -0.864188, -0.442863} + +, +{ +0.500000, -0.809017, -0.309017} + +, +{ +0.425325, -0.688191, -0.587785} + +, +{ +0.716567, -0.681718, -0.147621} + +, +{ +0.688191, -0.587785, -0.425325} + +, +{ +0.587785, -0.425325, -0.688191} + +, +{ +0.000000, -0.955423, -0.295242} + +, +{ +0.000000, -1.000000, 0.000000} + +, +{ +0.262866, -0.951056, -0.162460} + +, +{ +0.000000, -0.850651, 0.525731} + +, +{ +0.000000, -0.955423, 0.295242} + +, +{ +0.238856, -0.864188, 0.442863} + +, +{ +0.262866, -0.951056, 0.162460} + +, +{ +0.500000, -0.809017, 0.309017} + +, +{ +0.716567, -0.681718, 0.147621} + +, +{ +0.525731, -0.850651, 0.000000} + +, +{ +-0.238856, -0.864188, -0.442863} + +, +{ +-0.500000, -0.809017, -0.309017} + +, +{ +-0.262866, -0.951056, -0.162460} + +, +{ +-0.850651, -0.525731, 0.000000} + +, +{ +-0.716567, -0.681718, -0.147621} + +, +{ +-0.716567, -0.681718, 0.147621} + +, +{ +-0.525731, -0.850651, 0.000000} + +, +{ +-0.500000, -0.809017, 0.309017} + +, +{ +-0.238856, -0.864188, 0.442863} + +, +{ +-0.262866, -0.951056, 0.162460} + +, +{ +-0.864188, -0.442863, 0.238856} + +, +{ +-0.809017, -0.309017, 0.500000} + +, +{ +-0.688191, -0.587785, 0.425325} + +, +{ +-0.681718, -0.147621, 0.716567} + +, +{ +-0.442863, -0.238856, 0.864188} + +, +{ +-0.587785, -0.425325, 0.688191} + +, +{ +-0.309017, -0.500000, 0.809017} + +, +{ +-0.147621, -0.716567, 0.681718} + +, +{ +-0.425325, -0.688191, 0.587785} + +, +{ +-0.162460, -0.262866, 0.951056} + +, +{ +0.442863, -0.238856, 0.864188} + +, +{ +0.162460, -0.262866, 0.951056} + +, +{ +0.309017, -0.500000, 0.809017} + +, +{ +0.147621, -0.716567, 0.681718} + +, +{ +0.000000, -0.525731, 0.850651} + +, +{ +0.425325, -0.688191, 0.587785} + +, +{ +0.587785, -0.425325, 0.688191} + +, +{ +0.688191, -0.587785, 0.425325} + +, +{ +-0.955423, 0.295242, 0.000000} + +, +{ +-0.951056, 0.162460, 0.262866} + +, +{ +-1.000000, 0.000000, 0.000000} + +, +{ +-0.850651, 0.000000, 0.525731} + +, +{ +-0.955423, -0.295242, 0.000000} + +, +{ +-0.951056, -0.162460, 0.262866} + +, +{ +-0.864188, 0.442863, -0.238856} + +, +{ +-0.951056, 0.162460, -0.262866} + +, +{ +-0.809017, 0.309017, -0.500000} + +, +{ +-0.864188, -0.442863, -0.238856} + +, +{ +-0.951056, -0.162460, -0.262866} + +, +{ +-0.809017, -0.309017, -0.500000} + +, +{ +-0.681718, 0.147621, -0.716567} + +, +{ +-0.681718, -0.147621, -0.716567} + +, +{ +-0.850651, 0.000000, -0.525731} + +, +{ +-0.688191, 0.587785, -0.425325} + +, +{ +-0.587785, 0.425325, -0.688191} + +, +{ +-0.425325, 0.688191, -0.587785} + +, +{ +-0.425325, -0.688191, -0.587785} + +, +{ +-0.587785, -0.425325, -0.688191} + +, +{ +-0.688191, -0.587785, -0.425325} + +, diff --git a/utils/p2rad/dirtmap.cpp b/utils/p2rad/dirtmap.cpp new file mode 100644 index 0000000..bec609f --- /dev/null +++ b/utils/p2rad/dirtmap.cpp @@ -0,0 +1,121 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// dirtmap.c // quake3 dirt-mapping (fake Ambient Occlusion) + +#include "qrad.h" + +#define DIRT_CONE_ANGLE 115 // in degrees +#define DIRT_NUM_ELEVATION_STEPS 3 +#define DIRT_NUM_ANGLE_STEPS 16 +#define DIRT_NUM_VECTORS ( DIRT_NUM_ANGLE_STEPS * DIRT_NUM_ELEVATION_STEPS ) + +static vec3_t g_dirtvecs[DIRT_NUM_VECTORS]; +static int g_num_dirtvecs = 0; + +/* +============ +SetupDirt + +sets up dirtmap (ambient occlusion) +============ +*/ +void SetupDirt( void ) +{ + // check if needed + if( !g_dirtmapping ) + return; + + // calculate angular steps + vec_t angleStep = DEG2RAD( 360.0f / DIRT_NUM_ANGLE_STEPS ); + vec_t elevationStep = DEG2RAD( DIRT_CONE_ANGLE / DIRT_NUM_ELEVATION_STEPS ); + vec_t angle = 0.0f; + + for( int i = 0; i < DIRT_NUM_ANGLE_STEPS; i++, angle += angleStep ) + { + vec_t elevation = elevationStep * 0.5; + + for( int j = 0; j < DIRT_NUM_ELEVATION_STEPS; j++, elevation += elevationStep ) + { + g_dirtvecs[g_num_dirtvecs][0] = sin( elevation ) * cos( angle ); + g_dirtvecs[g_num_dirtvecs][1] = sin( elevation ) * sin( angle ); + g_dirtvecs[g_num_dirtvecs][2] = cos( elevation ); + g_num_dirtvecs++; + } + } + + MsgDev( D_REPORT, "%d dirtmap vectors\n", g_num_dirtvecs ); +} + +float GatherSampleDirt( int threadnum, int fn, const vec3_t pos, const vec3_t normal, entity_t *ignoreent ) +{ + vec3_t tangent, binormal, direction; + vec_t gatherDirt, outDirt; + vec_t dirtDepth = 128.0; + vec_t dirtScale = 2.0; + vec_t dirtGain = 2.0; + vec3_t vecSrc, vecEnd; + trace_t trace; + + if( !g_dirtmapping ) return 1.0f; // early out + + VectorCopy( pos, vecSrc ); + gatherDirt = 0.0f; + + if( fn >= 0 ) + { + dface_t *f = &g_dfaces[fn]; + dtexinfo_t *tx = &g_texinfo[f->texinfo]; + float lmvecs[2][4]; + + if( FBitSet( tx->flags, TEX_NODIRT )) + return 1.0f; // disabled by user + + LightMatrixFromTexMatrix( tx, lmvecs ); + VectorCopy( lmvecs[1], binormal ); + VectorCopy( lmvecs[0], tangent ); + } + else + { + // compute tangent space from phong-shaded normal + VectorVectors( normal, tangent, binormal ); + } + + for( int i = 0; i < g_num_dirtvecs; i++ ) + { + // transform vector into tangent space + direction[0] = tangent[0] * g_dirtvecs[i][0] + binormal[0] * g_dirtvecs[i][1] + normal[0] * g_dirtvecs[i][2]; + direction[1] = tangent[1] * g_dirtvecs[i][0] + binormal[1] * g_dirtvecs[i][1] + normal[1] * g_dirtvecs[i][2]; + direction[2] = tangent[2] * g_dirtvecs[i][0] + binormal[2] * g_dirtvecs[i][1] + normal[2] * g_dirtvecs[i][2]; + + VectorMA( pos, dirtDepth, direction, vecEnd ); + TestLine( threadnum, vecSrc, vecEnd, &trace ); + + if( trace.contents == CONTENTS_SOLID ) + gatherDirt += 1.0f - trace.fraction; + } + + // direct ray + VectorMA( pos, dirtDepth, normal, vecEnd ); + TestLine( threadnum, vecSrc, vecEnd, &trace ); + + if( trace.contents == CONTENTS_SOLID ) + gatherDirt += 1.0f - trace.fraction; + + // early out + if( gatherDirt <= 0.0f ) + return 1.0f; + + // apply gain (does this even do much? heh) + outDirt = pow( gatherDirt / ( g_num_dirtvecs + 1 ), dirtGain ) * dirtScale; + if( outDirt > 1.0f ) outDirt = 1.0f; + + return 1.0f - outDirt; +} \ No newline at end of file diff --git a/utils/p2rad/dlight.cpp b/utils/p2rad/dlight.cpp new file mode 100644 index 0000000..ebd9b8b --- /dev/null +++ b/utils/p2rad/dlight.cpp @@ -0,0 +1,758 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// doom per-sector lighting + +#include "dlight.h" + +char source[MAX_PATH] = ""; +vec3_t g_reflectivity[MAX_MAP_TEXTURES]; +bool g_texture_init[MAX_MAP_TEXTURES]; +vec_t g_direct_scale = DEFAULT_DLIGHT_SCALE; +float g_maxlight = DEFAULT_LIGHTCLIP; // originally this was 196 +vec_t g_gamma = DEFAULT_GAMMA; +vec3_t g_ambient = { 0, 0, 0 }; +dplane_t g_backplanes[MAX_MAP_PLANES]; // equal to MAX_MAP_FACES, there is no errors +faceinfo_t g_faceinfo[MAX_MAP_FACES]; +facelight_t g_facelight[MAX_MAP_FACES]; + +static char global_lights[MAX_PATH] = ""; +static char level_lights[MAX_PATH] = ""; + +/* +=================================================================== + +MISC + +=================================================================== +*/ +const dplane_t *GetPlaneFromFace( const dface_t *face ) +{ + ASSERT( face != NULL ); + + if( face->side ) + return &g_backplanes[face->planenum]; + return &g_dplanes[face->planenum]; +} + +const dplane_t *GetPlaneFromFace( const uint facenum ) +{ + ASSERT( facenum < MAX_MAP_FACES ); + + dface_t *face = &g_dfaces[facenum]; + + if( face->side ) + return &g_backplanes[face->planenum]; + return &g_dplanes[face->planenum]; +} + +dleaf_t *PointInLeaf( const vec3_t point ) +{ + int nodenum = 0; + + while( nodenum >= 0 ) + { + dnode_t *node = &g_dnodes[nodenum]; + + if( PlaneDiff( point, &g_dplanes[node->planenum] ) > 0 ) + nodenum = node->children[0]; + else nodenum = node->children[1]; + } + + return &g_dleafs[-nodenum - 1]; +} + +/* +============= +MakeBackplanes +============= +*/ +void MakeBackplanes( void ) +{ + for( int i = 0; i < g_numplanes; i++ ) + { + VectorNegate( g_dplanes[i].normal, g_backplanes[i].normal ); + g_backplanes[i].dist = -g_dplanes[i].dist; + } +} + +/* +=================================================================== + + TEXTURE LIGHT VALUES + +=================================================================== +*/ +static texlight_t g_texlights[MAX_TEXLIGHTS]; +static int g_num_texlights; + +/* +============ +ReadLightFile +============ +*/ +void ReadLightFile( const char *filename, bool use_direct_path ) +{ + int file_texlights = 0; + char scan[128]; + int j, argCnt; + file_t *f; + + FS_AllowDirectPaths( use_direct_path ); + f = FS_Open( filename, "r", false ); + FS_AllowDirectPaths( false ); + if( !f ) return; + else MsgDev( D_INFO, "[Reading texlights from '%s']\n", filename ); + + while( FS_Gets( f, (byte *)&scan, sizeof( scan )) != EOF ) + { + char szTexlight[256]; + vec_t r, g, b, i = 1; + char *comment = scan; + + // skip the comments + if( comment[0] == '/' && comment[1] == '/' ) + continue; + + argCnt = sscanf( scan, "%s %f %f %f %f", szTexlight, &r, &g, &b, &i ); + + if( argCnt == 2 ) + { + // eith 1+1 args, the R,G,B values are all equal to the first value + g = b = r; + } + else if( argCnt == 5 ) + { + // With 1 + 4 args, the R,G,B values are "scaled" by the fourth numeric value i; + r *= i / 255.0; + g *= i / 255.0; + b *= i / 255.0; + } + else if( argCnt != 4 ) + { + if( Q_strlen( scan ) > 4 ) + MsgDev( D_WARN, "ignoring bad texlight '%s' in %s", scan, filename ); + continue; + } + + for( j = 0; j < g_num_texlights; j++ ) + { + texlight_t *tl = &g_texlights[j]; + + if( !Q_strcmp( tl->name, szTexlight )) + { + if( !Q_strcmp( tl->filename, filename )) + { + MsgDev( D_REPORT, "duplication of '%s' in file '%s'!\n", tl->name, tl->filename ); + } + else if( tl->value[0] != r || tl->value[1] != g || tl->value[2] != b ) + { + MsgDev( D_REPORT, "overriding '%s' from '%s' with '%s'!\n", tl->name, tl->filename, filename ); + } + else + { + MsgDev( D_WARN, "redundant '%s' def in '%s' AND '%s'!\n", tl->name, tl->filename, filename ); + } + break; + } + } + + Q_strncpy( g_texlights[j].name, szTexlight, sizeof( g_texlights[0].name )); + VectorSet( g_texlights[j].value, r, g, b ); + g_texlights[j].filename = filename; + file_texlights++; + + g_num_texlights = Q_max( g_num_texlights, j + 1 ); + + if( g_num_texlights == MAX_TEXLIGHTS ) + COM_FatalError( "MAX_TEXLIGHTS limit exceeded\n" ); + } + + MsgDev( D_REPORT, "[%i texlights parsed from '%s']\n\n", file_texlights, filename ); + FS_Close( f ); +} + +/* +============ +LightForTexture +============ +*/ +void LightForTexture( const char *name, vec3_t result ) +{ + VectorClear( result ); + + for( int i = 0; i < g_num_texlights; i++ ) + { + if( !Q_stricmp( name, g_texlights[i].name )) + { + VectorCopy( g_texlights[i].value, result ); + MsgDev( D_REPORT, "Texture '%s': baselight is (%f,%f,%f).\n", name, result[0], result[1], result[2] ); + return; + } + } +} + +/* +============= +BaseLightForFace +============= +*/ +void BaseLightForFace( dface_t *f, vec3_t light, vec3_t reflectivity ) +{ + int miptex = g_texinfo[f->texinfo].miptex; + miptex_t *mt; + + // check for light emited by texture + mt = GetTextureByMiptex( miptex ); + if( !mt ) return; + + LightForTexture( mt->name, light ); + VectorClear( reflectivity ); + + int samples = mt->width * mt->height; + byte *pal = ((byte *)mt) + mt->offsets[0] + (((mt->width * mt->height) * 85) >> 6); + byte *buf = ((byte *)mt) + mt->offsets[0]; + vec3_t total; + + // check for cache + if( g_texture_init[miptex] ) + { + VectorCopy( g_reflectivity[miptex], reflectivity ); + return; + } + + pal += sizeof( short ); // skip colorsize + VectorClear( total ); + + for( int i = 0; i < samples; i++ ) + { + vec3_t reflectivity; + + if( mt->name[0] == '{' && buf[i] == 0xFF ) + { + VectorClear( reflectivity ); + } + else + { + int texel = buf[i]; + reflectivity[0] = pow( pal[texel*3+0] * (1.0f / 255.0f), DEFAULT_TEXREFLECTGAMMA ); + reflectivity[1] = pow( pal[texel*3+1] * (1.0f / 255.0f), DEFAULT_TEXREFLECTGAMMA ); + reflectivity[2] = pow( pal[texel*3+2] * (1.0f / 255.0f), DEFAULT_TEXREFLECTGAMMA ); + VectorScale( reflectivity, DEFAULT_TEXREFLECTSCALE, reflectivity ); + } + VectorAdd( total, reflectivity, total ); + } + + VectorScale( total, 1.0 / (double)(mt->width * mt->height), g_reflectivity[miptex] ); + VectorCopy( g_reflectivity[miptex], reflectivity ); + MsgDev( D_REPORT, "Texture '%s': reflectivity is (%f,%f,%f).\n", mt->name, reflectivity[0], reflectivity[1], reflectivity[2] ); + g_texture_init[miptex] = true; +} + +/* +============ +BuildFaceInfos + +calc lightmap sizes +============ +*/ +void BuildFaceInfos( void ) +{ + int m, j, e; + int facenum1; + vec_t val, lmmins[2], lmmaxs[2]; + float lmvecs[2][4]; + dtexinfo_t *tex; + const dplane_t *dp1; + const dface_t *f1; + faceinfo_t *fn; + dvertex_t *v; + + // store a list of every face that uses a particular vertex + for( facenum1 = 0; facenum1 < g_numfaces; facenum1++ ) + { + fn = &g_faceinfo[facenum1]; + + f1 = &g_dfaces[facenum1]; + dp1 = GetPlaneFromFace( f1 ); + tex = &g_texinfo[f1->texinfo]; + VectorCopy( dp1->normal, fn->facenormal ); + lmmins[0] = lmmins[1] = 999999; + lmmaxs[0] = lmmaxs[1] =-999999; + + int max_surface_extent = GetSurfaceExtent( tex ); + int texture_step = GetTextureStep( tex ); + + LightMatrixFromTexMatrix( tex, lmvecs ); + + for( j = 0; j < f1->numedges; j++ ) + { + e = g_dsurfedges[f1->firstedge + j]; + + if( e >= 0 ) v = &g_dvertexes[g_dedges[e].v[0]]; + else v = &g_dvertexes[g_dedges[-e].v[1]]; + + for( m = 0; m < 2; m++ ) + { + /* The following calculation is sensitive to floating-point + * precision. It needs to produce the same result that the + * light compiler does, because R_BuildLightMap uses surf-> + * extents to know the width/height of a surface's lightmap, + * and incorrect rounding here manifests itself as patches + * of "corrupted" looking lightmaps. + * Most light compilers are win32 executables, so they use + * x87 floating point. This means the multiplies and adds + * are done at 80-bit precision, and the result is rounded + * down to 32-bits and stored in val. + * Adding the casts to double seems to be good enough to fix + * lighting glitches when Quakespasm is compiled as x86_64 + * and using SSE2 floating-point. A potential trouble spot + * is the hallway at the beginning of mfxsp17. -- ericw + */ + val = ((double)v->point[0] * (double)lmvecs[m][0]) + + ((double)v->point[1] * (double)lmvecs[m][1]) + + ((double)v->point[2] * (double)lmvecs[m][2]) + + (double)lmvecs[m][3]; + lmmins[m] = Q_min( val, lmmins[m] ); + lmmaxs[m] = Q_max( val, lmmaxs[m] ); + } + } + + // calc face extents for traceline and lightmap extents for LightForPoint + for( j = 0; j < 2; j++ ) + { + lmmins[j] = floor( lmmins[j] / texture_step ); + lmmaxs[j] = ceil( lmmaxs[j] / texture_step ); + + fn->texmins[j] = lmmins[j]; + fn->texsize[j] = (lmmaxs[j] - lmmins[j]); + } + + if( !FBitSet( tex->flags, TEX_SPECIAL )) + { + if( fn->texsize[0] * fn->texsize[1] > ( MAX_SINGLEMAP / 3 )) + COM_FatalError( "surface to large to map\n" ); + + if( fn->texsize[0] > max_surface_extent ) + MsgDev( D_ERROR, "bad surface extents %d > %d\n", fn->texsize[0], max_surface_extent ); + + if( fn->texsize[1] > max_surface_extent ) + MsgDev( D_ERROR, "bad surface extents %d > %d\n", fn->texsize[1], max_surface_extent ); + + if( fn->texsize[0] < 0 || fn->texsize[1] < 0 ) + COM_FatalError( "negative extents\n" ); + } + } +} + +/* +============= +BuildSectorLights +============= +*/ +void BuildSectorLights( int facenum, int thread ) +{ + facelight_t *fl = &g_facelight[facenum]; + int lmwidth, lmheight; + word sectorlight; + vec_t lightvalue; + int i, j; + faceinfo_t *fn; + dface_t *f; + sample_t *s; + + f = &g_dfaces[facenum]; + + // some surfaces don't need lightmaps + f->lightofs = -1; + for( j = 0; j < MAXLIGHTMAPS; j++ ) + f->styles[j] = 255; + + if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL )) + return; // non-lit texture + + // decode per-sector lighting values + sectorlight = GetSurfaceGroupId( f ); + + f->styles[0] = ((sectorlight & 0xFF00) >> 8); + lightvalue = (vec_t)(sectorlight & 0x00FF); + + fn = &g_faceinfo[facenum]; + lmwidth = fn->texsize[0] + 1; + lmheight = fn->texsize[1] + 1; + fl->numsamples = lmwidth * lmheight; + + // alloc lightmap for this face + fl->samples = (sample_t *)Mem_Alloc( fl->numsamples * sizeof( sample_t )); + + // doom lighting it's easy. just fill face lightmap with single color from sector + // TODO: add lighting from self-illuminated textures and reflecitivity? + for( i = 0; i < fl->numsamples; i++ ) + { + for( j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ ) + { + VectorFill( fl->samples[i].light[j], lightvalue ); + } + } + + // add an ambient term if desired + if( g_ambient[0] || g_ambient[1] || g_ambient[2] ) + { + for( j = 0; j < MAXLIGHTMAPS && f->styles[j] == 255; j++ ); + if( j == MAXLIGHTMAPS ) f->styles[0] = 0; // adding style + + for( j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ ) + { + if( f->styles[j] == 0 ) + { + s = fl->samples; + for( i = 0; i < fl->numsamples; i++, s++ ) + VectorAdd( s->light[j], g_ambient, s->light[j] ); + break; + } + } + + } +} + +/* +============ +ScaleDirectLights +============ +*/ +void ScaleDirectLights( void ) +{ + sample_t *samp; + facelight_t *fl; + dface_t *f; + + for( int facenum = 0; facenum < g_numfaces; facenum++ ) + { + f = &g_dfaces[facenum]; + + if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL )) + continue; + + fl = &g_facelight[facenum]; + + for( int k = 0; k < MAXLIGHTMAPS && f->styles[k] != 255; k++ ) + { + for( int i = 0; i < fl->numsamples; i++ ) + { + samp = &fl->samples[i]; + VectorScale( samp->light[k], g_direct_scale, samp->light[k] ); + } + } + } +} + +void PrecompLightmapOffsets( void ) +{ + int lightstyles; + facelight_t *fl; + dface_t *f; + + g_shadowdatasize = 0; + g_normaldatasize = 0; + g_lightdatasize = 0; + + for( int facenum = 0; facenum < g_numfaces; facenum++ ) + { + f = &g_dfaces[facenum]; + fl = &g_facelight[facenum]; + + if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL )) + continue; // non-lit texture + + for( lightstyles = 0; lightstyles < MAXLIGHTMAPS; lightstyles++ ) + { + if( f->styles[lightstyles] == 255 ) + break; // end if styles + } + + if( !lightstyles ) continue; + + f->lightofs = g_lightdatasize; + g_lightdatasize += fl->numsamples * 3 * lightstyles; + } + + g_dlightdata = (byte *)Mem_Realloc( g_dlightdata, g_lightdatasize ); +} + +void FinalLightFace( int facenum, int threadnum ) +{ + float minlight = 0.0f; // TODO: allow minlight? + int lightstyles; + int i, j, k; + sample_t *samp; + dtexinfo_t *tx; + facelight_t *fl; + dface_t *f; + vec3_t lb; + + f = &g_dfaces[facenum]; + fl = &g_facelight[facenum]; + tx = &g_texinfo[f->texinfo]; + + if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL )) + return; // non-lit texture + + for( lightstyles = 0; lightstyles < MAXLIGHTMAPS; lightstyles++ ) + { + if( f->styles[lightstyles] == 255 ) + break; + } + + if( !lightstyles ) return; + + for( k = 0; k < lightstyles; k++ ) + { + samp = fl->samples; + + for( j = 0; j < fl->numsamples; j++, samp++ ) + { + VectorCopy( samp->light[k], lb ); + + // clip from the bottom first + lb[0] = Q_max( lb[0], minlight ); + lb[1] = Q_max( lb[1], minlight ); + lb[2] = Q_max( lb[2], minlight ); + + // clip from the top + if( lb[0] > g_maxlight || lb[1] > g_maxlight || lb[2] > g_maxlight ) + { + // find max value and scale the whole color down; + float max = VectorMax( lb ); + + for( i = 0; i < 3; i++ ) + lb[i] = ( lb[i] * g_maxlight ) / max; + } + + // do gamma adjust + lb[0] = (float)pow( lb[0] / 256.0f, g_gamma ) * 256.0f; + lb[1] = (float)pow( lb[1] / 256.0f, g_gamma ) * 256.0f; + lb[2] = (float)pow( lb[2] / 256.0f, g_gamma ) * 256.0f; + + g_dlightdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 0] = Q_rint( lb[0] ); + g_dlightdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 1] = Q_rint( lb[1] ); + g_dlightdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 2] = Q_rint( lb[2] ); + } + } +} + +void FreeFaceLights( void ) +{ + for( int i = 0; i < g_numfaces; i++ ) + { + Mem_Free( g_facelight[i].samples ); + } +} + +//============================================================== +/* +============= +DoomLightWorld +============= +*/ +void DoomLightWorld( void ) +{ + MakeBackplanes(); + BuildFaceInfos(); + + // build initial facelights + RunThreadsOnIndividual( g_numfaces, true, BuildSectorLights ); + + ScaleDirectLights(); + + PrecompLightmapOffsets(); + + RunThreadsOnIndividual( g_numfaces, true, FinalLightFace ); + + FreeFaceLights(); +} + +/* +============ +PrintDlightSettings + +show compiler settings like ZHLT +============ +*/ +static void PrintDlightSettings( void ) +{ + char buf1[1024]; + char buf2[1024]; + + Msg( "\nCurrent dlight settings\n" ); + Msg( "Name | Setting | Default\n" ); + Msg( "---------------------|-----------|-------------------------\n" ); + Msg( "developer [ %7d ] [ %7d ]\n", GetDeveloperLevel(), DEFAULT_DEVELOPER ); + Q_snprintf( buf1, sizeof( buf1 ), "%3.3f", g_direct_scale ); + Q_snprintf( buf2, sizeof( buf2 ), "%3.3f", DEFAULT_DLIGHT_SCALE ); + Msg( "direct light scale [ %7s ] [ %7s ]\n", buf1, buf2 ); + Q_snprintf( buf1, sizeof( buf1 ), "%3.3f", g_gamma ); + Q_snprintf( buf2, sizeof( buf2 ), "%3.3f", DEFAULT_GAMMA ); + Msg( "gamma factor [ %7s ] [ %7s ]\n", buf1, buf2 ); + Msg( "\n" ); +} + +/* +============ +PrintDlightUsage + +show compiler usage like ZHLT +============ +*/ +static void PrintDlightUsage( void ) +{ + Msg( "\n-= dlight Options =-\n\n" ); + Msg( " -dev # : compile with developer message (1 - 4). default is %d\n", DEFAULT_DEVELOPER ); + Msg( " -threads # : manually specify the number of threads to run\n" ); + Msg( " -ambient r g b : set ambient world light (0.0 to 1.0, r g b)\n" ); + Msg( " -dscale : direct light scaling factor\n" ); + Msg( " -gamma : set global gamma value\n" ); + Msg( " bspfile : The bspfile to compile\n\n" ); + + exit( 1 ); +} + +/* +======== +main + +light modelfile +======== +*/ +int main( int argc, char **argv ) +{ + double start, end; + char str[64]; + int i; + + atexit( Sys_CloseLog ); + source[0] = '\0'; + + for( i = 1; i < argc; i++ ) + { + if( !Q_strcmp( argv[i], "-dev" )) + { + SetDeveloperLevel( atoi( argv[i+1] )); + i++; + } + else if( !Q_strcmp( argv[i], "-threads" )) + { + g_numthreads = atoi( argv[i+1] ); + i++; + } + else if( !Q_strcmp( argv[i], "-ambient" )) + { + if( argc > ( i + 3 )) + { + g_ambient[0] = (float)atof( argv[i+1] ) * 0.5f; + g_ambient[1] = (float)atof( argv[i+2] ) * 0.5f; + g_ambient[2] = (float)atof( argv[i+3] ) * 0.5f; + i += 3; + } + else + { + break; + } + } + else if( !Q_strcmp( argv[i], "-dscale" )) + { + g_direct_scale = (float)atof( argv[i+1] ); + i++; + } + else if( !Q_strcmp( argv[i], "-gamma" )) + { + g_gamma = (float)atof( argv[i+1] ); + i++; + } + else if( argv[i][0] == '-' ) + { + MsgDev( D_ERROR, "\nUnknown option \"%s\"\n", argv[i] ); + break; + } + else if( !source[0] ) + { + Q_strncpy( source, COM_ExpandArg( argv[i] ), sizeof( source )); + COM_StripExtension( source ); + } + else + { + MsgDev( D_ERROR, "\nUnknown option \"%s\"\n", argv[i] ); + break; + } + } + + if( i != argc || !source[0] ) + { + if( !source[0] ) + Msg( "no mapfile specified\n" ); + PrintDlightUsage(); + } + + start = I_FloatTime (); + + Sys_InitLogAppend( va( "%s.log", source )); + + Msg( "\n%s %s (%s)\n", TOOLNAME, VERSIONSTRING, __DATE__ ); + + PrintDlightSettings(); + + ThreadSetDefault (); + + // starting base filesystem + FS_Init( source ); + + // Set the required global lights filename + // try looking in the directory we were run from + GetModuleFileName( NULL, global_lights, sizeof( global_lights )); + COM_ExtractFilePath( global_lights, global_lights ); + Q_strncat( global_lights, "\\lights.rad", sizeof( global_lights )); + + // Set the optional level specific lights filename + COM_FileBase( source, str ); + Q_snprintf( level_lights, sizeof( level_lights ), "maps/%s.rad", str ); + if( !FS_FileExists( level_lights, false )) level_lights[0] = '\0'; + + ReadLightFile( global_lights, true ); // Required + if( *level_lights ) ReadLightFile( level_lights, false ); // Optional & implied + + COM_DefaultExtension( source, ".bsp" ); + MsgDev( D_INFO, "\n" ); + + LoadBSPFile( source ); + + if( g_nummodels <= 0 ) + COM_FatalError( "map %s without any models\n", source ); + + ParseEntities(); + TEX_LoadTextures(); + + // keep it in acceptable range + g_gamma = bound( 0.5, g_gamma, 2.0 ); + + DoomLightWorld (); + + WriteBSPFile( source ); + TEX_FreeTextures (); + FreeEntities (); + FS_Shutdown(); + + SetDeveloperLevel( D_REPORT ); + Mem_Check(); + + end = I_FloatTime (); + Q_timestring((int)( end - start ), str ); + Msg( "%s elapsed\n", str ); + + return 0; +} \ No newline at end of file diff --git a/utils/p2rad/dlight.dsp b/utils/p2rad/dlight.dsp new file mode 100644 index 0000000..1c18c34 --- /dev/null +++ b/utils/p2rad/dlight.dsp @@ -0,0 +1,211 @@ +# Microsoft Developer Studio Project File - Name="dlight" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=dlight - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "dlight.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "dlight.mak" CFG="dlight - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "dlight - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "dlight - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/SDKSrc/Tools/utils/dlight", FVGBAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "dlight - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir ".\Release" +# PROP BASE Intermediate_Dir ".\Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\..\temp\dlight\!release" +# PROP Intermediate_Dir "..\..\temp\dlight\!release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "..\common" /I "..\..\common" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "IGNORE_SEARCH_IN_WADS" /D "ALLOW_WADS_IN_PACKS" /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /opt:nowin98 /largeaddressaware +# ADD LINK32 msvcrt.lib /nologo /stack:0x400000,0x100000 /subsystem:console /pdb:none /machine:I386 /nodefaultlib:"libcmt.lib" /opt:nowin98 /largeaddressaware +# SUBTRACT LINK32 /profile /map +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\dlight\!release +InputPath=\Paranoia2\src_main\temp\dlight\!release\dlight.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\dlight.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\dlight.exe "D:\Paranoia2\tools\dlight.exe" + +# End Custom Build + +!ELSEIF "$(CFG)" == "dlight - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir ".\Debug" +# PROP BASE Intermediate_Dir ".\Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\..\temp\dlight\!debug" +# PROP Intermediate_Dir "..\..\temp\dlight\!debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MTd /W3 /Gm /Gi /GX /ZI /Od /I "..\common" /I "..\..\common" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "IGNORE_SEARCH_IN_WADS" /D "ALLOW_WADS_IN_PACKS" /FAs /FR /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 +# ADD LINK32 msvcrtd.lib /nologo /stack:0x400000,0x100000 /subsystem:console /debug /machine:I386 /nodefaultlib:"libcmtd.lib" +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\dlight\!debug +InputPath=\Paranoia2\src_main\temp\dlight\!debug\dlight.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\dlight.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\dlight.exe "D:\Paranoia2\tools\dlight.exe" + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "dlight - Win32 Release" +# Name "dlight - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=..\common\basefs.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\bspfile.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\cmdlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\conprint.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\crc32.cpp +# End Source File +# Begin Source File + +SOURCE=.\dlight.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\filesystem.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\mathlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\polylib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\scriplib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\stringlib.cpp +# End Source File +# Begin Source File + +SOURCE=.\textures.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\threads.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\wadfile.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\zone.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=..\common\bspfile.h +# End Source File +# Begin Source File + +SOURCE=..\common\cmdlib.h +# End Source File +# Begin Source File + +SOURCE=.\dlight.h +# End Source File +# Begin Source File + +SOURCE=..\common\mathlib.h +# End Source File +# Begin Source File + +SOURCE=..\common\polylib.h +# End Source File +# Begin Source File + +SOURCE=.\qrad.h +# End Source File +# Begin Source File + +SOURCE=..\common\scriplib.h +# End Source File +# Begin Source File + +SOURCE=..\common\threads.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/utils/p2rad/dlight.h b/utils/p2rad/dlight.h new file mode 100644 index 0000000..17674e9 --- /dev/null +++ b/utils/p2rad/dlight.h @@ -0,0 +1,64 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include +#include +#include +#include "cmdlib.h" +#include "mathlib.h" +#include "bspfile.h" +#include "polylib.h" +#include "threads.h" +#include "stringlib.h" +#include "filesystem.h" + +#define DEFAULT_TEXSCALE true +#define DEFAULT_DLIGHT_SCALE 1.0 +#define DEFAULT_WADTEXTURES false +#define DEFAULT_TEXREFLECTGAMMA (1.0 / 2.2) // turn back to linear space +#define DEFAULT_TEXREFLECTSCALE 1.0 +#define DEFAULT_LIGHTCLIP 255 +#define DEFAULT_GAMMA 1.8 + +#define MAX_SINGLEMAP ((MAX_CUSTOM_SURFACE_EXTENT+1) * (MAX_CUSTOM_SURFACE_EXTENT+1) * 3) +#define MAX_SUBDIVIDE 16384 +#define MAX_TEXLIGHTS 1024 + +typedef struct +{ + char name[256]; + const char *filename; + vec3_t value; +} texlight_t; + +typedef struct +{ + vec3_t facenormal; // face normal + short texmins[2]; // also used for face testing + short texsize[2]; +} faceinfo_t; + +typedef struct +{ + vec3_t light[MAXLIGHTMAPS]; // total lightvalue pex luxel +} sample_t; + +typedef struct +{ + int numsamples; + sample_t *samples; +} facelight_t; + +// +// textures.c +// +void TEX_LoadTextures( void ); +miptex_t *GetTextureByMiptex( int miptex ); +void TEX_FreeTextures( void ); \ No newline at end of file diff --git a/utils/p2rad/facepos.cpp b/utils/p2rad/facepos.cpp new file mode 100644 index 0000000..49603f7 --- /dev/null +++ b/utils/p2rad/facepos.cpp @@ -0,0 +1,577 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include "qrad.h" + +#define MAX_NUDGES 12 + +static const vec3_t g_nudgelist[MAX_NUDGES] = +{ +{ 0.1, 0.0, 0.0 }, {-0.1, 0.0, 0.0 }, { 0.0, 0.1, 0.0 }, +{ 0.0,-0.1, 0.0 }, { 0.3, 0.0, 0.0 }, {-0.3, 0.0, 0.0 }, +{ 0.0, 0.3, 0.0 }, { 0.0,-0.3, 0.0 }, { 0.3, 0.3, 0.0 }, +{-0.3, 0.3, 0.0 }, {-0.3,-0.3, 0.0 }, { 0.3,-0.3, 0.0 } +}; + +typedef struct +{ + bool valid; + vec_t best_s; // FindNearestPosition will return this value + vec_t best_t; + vec3_t pos; // with DEFAULT_HUNT_OFFSET +} position_t; + +// Size of position_t (21) * positions per sample (9) * max number of samples (max AllocBlock (64) * 128 * 128) +// = 200MB of RAM +// But they are freed before BuildVisLeafs, so it's not a problem. + +typedef struct +{ + bool valid; + int facenum; + vec3_t face_offset; + matrix3x4 worldtotex; + matrix3x4 textoworld; + winding_t *facewinding; + dplane_t faceplane; + winding_t *facewindingwithoffset; + dplane_t faceplanewithoffset; + winding_t *texwinding; + dplane_t texplane; // (0, 0, 1, 0) or (0, 0, -1, 0) + vec3_t texcentroid; + vec3_t start; // s_start, t_start, 0 + vec3_t step; // s_step, t_step, 0 + int w; // number of s + int h; // number of t + position_t *grid; // [h][w] +} positionmap_t; + +static positionmap_t g_face_positions[MAX_MAP_FACES]; + +static bool IsPositionValid( int threadnum, positionmap_t *map, const vec3_t pos_st, vec3_t pos_out, bool phong = true, bool edgetest = true ) +{ + vec3_t pos, pos_normal; + vec_t hunt_scale = 0.2; + int hunt_size = 2; + vec_t hunt_offset; + + Matrix3x4_VectorTransform( map->textoworld, pos_st, pos ); + VectorAdd( pos, map->face_offset, pos ); + + if( phong ) + { + GetPhongNormal( map->facenum, pos, pos_normal ); + } + else + { + VectorCopy( map->faceplanewithoffset.normal, pos_normal ); + } + + VectorMA( pos, DEFAULT_HUNT_OFFSET, pos_normal, pos ); + + // might be smaller than DEFAULT_HUNT_OFFSET + hunt_offset = DotProduct( pos, map->faceplanewithoffset.normal ) - map->faceplanewithoffset.dist; + + // push the point 0.2 units around to avoid walls + if( !HuntForWorld( pos, vec3_origin, &map->faceplanewithoffset, hunt_size, hunt_scale, hunt_offset )) + { + return false; + } + + if( edgetest && !PointInWindingEpsilon( map->facewindingwithoffset, map->faceplanewithoffset.normal, pos, DEFAULT_EDGE_WIDTH )) + { + vec_t width = DEFAULT_EDGE_WIDTH; + vec3_t test; + + VectorCopy( pos, test ); + + // if the sample has gone beyond face boundaries, be careful that it hasn't passed a wall + WindingSnapPointEpsilon( map->facewindingwithoffset, map->faceplanewithoffset.normal, test, width, width * 4 ); + + if( !HuntForWorld( test, vec3_origin, &map->faceplanewithoffset, hunt_size, hunt_scale, hunt_offset )) + { + return false; + } + + if( TestLine( threadnum, pos, test, true ) != CONTENTS_EMPTY ) + return false; + } + + VectorCopy( pos, pos_out ); + + return true; +} + +static void CalcSinglePosition( int threadnum, positionmap_t *map, int is, int it ) +{ + vec_t smin, smax, tmin, tmax; + const vec3_t v_s = { 1.0, 0.0, 0.0 }; + const vec3_t v_t = { 0.0, 1.0, 0.0 }; + dplane_t clipplanes[4]; + winding_t *zone; + position_t *p; + + p = &map->grid[is + map->w * it]; + smin = map->start[0] + is * map->step[0]; + smax = map->start[0] + (is + 1) * map->step[0]; + tmin = map->start[1] + it * map->step[1]; + tmax = map->start[1] + (it + 1) * map->step[1]; + + // setup clip planes + VectorScale( v_s, 1.0, clipplanes[0].normal ); + clipplanes[0].dist = smin; + + VectorScale( v_s, -1.0, clipplanes[1].normal ); + clipplanes[1].dist = -smax; + + VectorScale( v_t, 1.0, clipplanes[2].normal ); + clipplanes[2].dist = tmin; + + VectorScale( v_t, -1.0, clipplanes[3].normal ); + clipplanes[3].dist = -tmax; + + zone = CopyWinding( map->texwinding ); + + for( int x = 0; x < 4 && zone != NULL; x++ ) + ChopWindingInPlace( &zone, clipplanes[x].normal, clipplanes[x].dist, ON_EPSILON, false ); + + if( !zone ) + { + p->valid = false; + } + else + { + vec3_t original_st; + vec3_t test_st; + + original_st[0] = map->start[0] + (is + 0.5) * map->step[0]; + original_st[1] = map->start[1] + (it + 0.5) * map->step[1]; + original_st[2] = 0.0; + p->valid = false; + + VectorCopy (original_st, test_st); + WindingSnapPoint( zone, map->texplane.normal, test_st ); + + if( IsPositionValid( threadnum, map, test_st, p->pos )) + { + p->best_s = test_st[0]; + p->best_t = test_st[1]; + p->valid = true; + } + + if( !p->valid ) + { + WindingCenter( zone, test_st ); + + if( IsPositionValid( threadnum, map, test_st, p->pos )) + { + p->best_s = test_st[0]; + p->best_t = test_st[1]; + p->valid = true; + } + } + + if( !p->valid && !g_fastmode ) + { + for( int i = 0; i < MAX_NUDGES; i++ ) + { + VectorMultiply( g_nudgelist[i], map->step, test_st ); + VectorAdd( test_st, original_st, test_st ); + WindingSnapPoint( zone, map->texplane.normal, test_st ); + + if( IsPositionValid( threadnum, map, test_st, p->pos )) + { + p->best_s = test_st[0]; + p->best_t = test_st[1]; + p->valid = true; + break; + } + } + } + + FreeWinding( zone ); + } +} + +/* +============ +FindFacePositions + +assume g_face_offset and g_face_centroids are valid +and g_edgeshare have been calculated +============ +*/ +void FindFacePositions( int facenum, int threadnum ) +{ + vec_t texmins[2], texmaxs[2]; + int imins[2], imaxs[2]; + const vec3_t v_up = { 0, 0, 1 }; + int x, k, is, it; + vec_t density; + positionmap_t *map; + dtexinfo_t *ti; + dface_t *f; + vec3_t v; + + f = &g_dfaces[facenum]; + map = &g_face_positions[facenum]; + map->valid = true; + map->facenum = facenum; + map->facewinding = NULL; + map->facewindingwithoffset = NULL; + map->texwinding = NULL; + map->grid = NULL; + + ti = &g_texinfo[f->texinfo]; + + if( FBitSet( ti->flags, TEX_SPECIAL )) + { + map->valid = false; + return; + } + + VectorCopy( g_face_offset[facenum], map->face_offset ); + TranslateWorldToTex( facenum, map->worldtotex ); + + if( !Matrix3x4_Invert_Full( map->textoworld, map->worldtotex )) + { + map->valid = false; + return; + } + + map->facewinding = WindingFromFace( f ); + map->faceplane = *GetPlaneFromFace( f ); + map->facewindingwithoffset = AllocWinding( map->facewinding->numpoints ); + map->facewindingwithoffset->numpoints = map->facewinding->numpoints; + + for( x = 0; x < map->facewinding->numpoints; x++ ) + VectorAdd( map->facewinding->p[x], map->face_offset, map->facewindingwithoffset->p[x] ); + + map->faceplanewithoffset = map->faceplane; + map->faceplanewithoffset.dist = map->faceplane.dist + DotProduct( map->face_offset, map->faceplane.normal ); + + map->texwinding = AllocWinding( map->facewinding->numpoints ); + map->texwinding->numpoints = map->facewinding->numpoints; + + for( x = 0; x < map->facewinding->numpoints; x++ ) + { + Matrix3x4_VectorTransform( map->worldtotex, map->facewinding->p[x], map->texwinding->p[x] ); + map->texwinding->p[x][2] = 0.0; + } + + RemoveColinearPointsEpsilon( map->texwinding, ON_EPSILON ); + VectorCopy( v_up, map->texplane.normal ); + + if( Matrix3x4_CalcSign( map->worldtotex ) < 0.0 ) + map->texplane.normal[2] *= -1.0; + map->texplane.dist = 0.0; + + if( map->texwinding->numpoints == 0 ) + { + FreeWinding( map->facewindingwithoffset ); + FreeWinding( map->facewinding ); + FreeWinding( map->texwinding ); + + map->facewindingwithoffset = NULL; + map->facewinding = NULL; + map->texwinding = NULL; + + map->valid = false; + return; + } + + VectorSubtract( g_face_centroids[facenum], map->face_offset, v ); + Matrix3x4_VectorTransform( map->worldtotex, v, map->texcentroid ); + map->texcentroid[2] = 0.0; + + int texture_step = GetTextureStep( ti ); + + for( x = 0; x < map->texwinding->numpoints; x++ ) + { + for( k = 0; k < 2; k++ ) + { + if( x == 0 || map->texwinding->p[x][k] < texmins[k] ) + texmins[k] = map->texwinding->p[x][k]; + if( x == 0 || map->texwinding->p[x][k] > texmaxs[k] ) + texmaxs[k] = map->texwinding->p[x][k]; + } + } + + if( g_fastmode || texture_step == 1.0 ) + density = 1.0; + else if( texture_step < 8.0 ) + density = 2.0; + else density = 3.0; + + // experimental + density = 1.0f; + + map->step[0] = (vec_t)texture_step / density; + map->step[1] = (vec_t)texture_step / density; + map->step[2] = 1.0; + + for( k = 0; k < 2; k++ ) + { + imins[k] = (int)floor( texmins[k] / map->step[k] + 0.5 - ON_EPSILON ); + imaxs[k] = (int)ceil( texmaxs[k] / map->step[k] - 0.5 + ON_EPSILON ); + } + + map->start[0] = (imins[0] - 0.5) * map->step[0]; + map->start[1] = (imins[1] - 0.5) * map->step[1]; + map->start[2] = 0.0; + map->w = imaxs[0] - imins[0] + 1; + map->h = imaxs[1] - imins[1] + 1; + + if( map->w <= 0 || map->h <= 0 || (double)map->w * (double)map->h > 99999999 ) + { + FreeWinding( map->facewindingwithoffset ); + FreeWinding( map->facewinding ); + FreeWinding( map->texwinding ); + + map->facewindingwithoffset = NULL; + map->facewinding = NULL; + map->texwinding = NULL; + + map->valid = false; + return; + } + + map->grid = (position_t *)Mem_Alloc( map->w * map->h * sizeof( position_t )); + + for( it = 0; it < map->h; it++ ) + { + for( is = 0; is < map->w; is++ ) + { + CalcSinglePosition( threadnum, map, is, it ); + } + } +} + +void FreeFacePositions( void ) +{ + if( g_drawsample ) + { + char path[MAX_PATH], name[MAX_PATH]; + + Q_strncpy( path, source, sizeof( path )); + COM_StripExtension( path ); + Q_snprintf( name, sizeof( name ), "%s.pts", path ); + Msg( "Writing '%s' ...\n", name ); + + FILE *f = fopen( name, "w" ); + + if( f ) + { + vec3_t v; + + for( int i = 0; i < g_numfaces; i++ ) + { + positionmap_t *map = &g_face_positions[i]; + + if( !map->valid ) continue; + + for( int j = 0; j < map->h * map->w; j++ ) + { + if( !map->grid[j].valid ) + continue; + + VectorCopy( map->grid[j].pos, v ); + fprintf( f, "%g %g %g\n", v[0], v[1], v[2] ); + } + } + + fclose( f ); + Msg( "OK.\n" ); + } + else Msg( "Error.\n" ); + } + + for( int facenum = 0; facenum < g_numfaces; facenum++ ) + { + positionmap_t *map = &g_face_positions[facenum]; + + if( map->valid ) + { + FreeWinding( map->facewindingwithoffset ); + FreeWinding( map->facewinding ); + FreeWinding( map->texwinding ); + Mem_Free( map->grid ); + + map->facewindingwithoffset = NULL; + map->facewinding = NULL; + map->texwinding = NULL; + map->valid = false; + map->grid = NULL; + } + } +} + +bool FindNearestPosition( int facenum, const winding_t *w, vec_t s, vec_t t, vec3_t pos, vec_t *best_s, vec_t *best_t, vec_t *dist ) +{ + int itmin, itmax, ismin, ismax; + const vec3_t v_s = { 1, 0, 0 }; + const vec3_t v_t = { 0, 1, 0 }; + vec3_t original_st; + int x, is, it; + int best_is; + int best_it; + vec_t best_dist; + bool found; + positionmap_t *map; + vec3_t v; + + map = &g_face_positions[facenum]; + if( !map->valid ) return false; + + VectorSet( original_st, s, t, 0.0 ); + + if( PointInWindingEpsilon( map->texwinding, map->texplane.normal, original_st, 4 * ON_EPSILON, false )) + { + itmin = (int)ceil(( original_st[1] - map->start[1] - 2 * ON_EPSILON ) / map->step[1]) - 1; + itmax = (int)floor(( original_st[1] - map->start[1] + 2 * ON_EPSILON ) / map->step[1]); + ismin = (int)ceil(( original_st[0] - map->start[0] - 2 * ON_EPSILON ) / map->step[0]) - 1; + ismax = (int)floor(( original_st[0] - map->start[0] + 2 * ON_EPSILON ) / map->step[0]); + itmin = Q_max( 0, itmin ); + itmax = Q_min( itmax, map->h - 1 ); + ismin = Q_max( 0, ismin ); + ismax = Q_min( ismax, map->w - 1 ); + found = false; + + for( it = itmin; it <= itmax; it++ ) + { + for( is = ismin; is <= ismax; is++ ) + { + vec3_t current_st; + position_t *p; + vec_t d; + + p = &map->grid[is + map->w * it]; + if( !p->valid ) continue; + + current_st[0] = p->best_s; + current_st[1] = p->best_t; + current_st[2] = 0.0; + + VectorSubtract( current_st, original_st, v ); + d = VectorLength( v ); + + if( !found || d < best_dist - 2 * ON_EPSILON ) + { + found = true; + best_is = is; + best_it = it; + best_dist = d; + } + } + } + + if( found ) + { + position_t *p; + + p = &map->grid[best_is + map->w * best_it]; + VectorCopy( p->pos, pos ); + *best_s = p->best_s; + *best_t = p->best_t; + *dist = 0.0; + + return true; + } + } + + itmin = map->h; + itmax = -1; + ismin = map->w; + ismax = -1; + + for( x = 0; x < w->numpoints; x++ ) + { + it = (int)floor(( w->p[x][1] - map->start[1] + 0.5 * ON_EPSILON ) / map->step[1]); + itmin = Q_min( itmin, it ); + it = (int)ceil(( w->p[x][1] - map->start[1] - 0.5 * ON_EPSILON ) / map->step[1]) - 1; + itmax = Q_max( it, itmax ); + is = (int)floor(( w->p[x][0] - map->start[0] + 0.5 * ON_EPSILON ) / map->step[0]); + ismin = Q_min( ismin, is ); + is = (int)ceil(( w->p[x][0] - map->start[0] - 0.5 * ON_EPSILON ) / map->step[0]) - 1; + ismax = Q_max( is, ismax ); + } + + itmin = Q_max( 0, itmin ); + itmax = Q_min( itmax, map->h - 1 ); + ismin = Q_max( 0, ismin ); + ismax = Q_min( ismax, map->w - 1 ); + found = false; + + for( it = itmin; it <= itmax; it++ ) + { + for( is = ismin; is <= ismax; is++ ) + { + vec3_t current_st; + position_t *p; + vec_t d; + + p = &map->grid[is + map->w * it]; + if( !p->valid ) continue; + + current_st[0] = p->best_s; + current_st[1] = p->best_t; + current_st[2] = 0.0; + + VectorSubtract( current_st, original_st, v ); + d = VectorLength( v ); + + if( !found || d < best_dist - ON_EPSILON ) + { + found = true; + best_is = is; + best_it = it; + best_dist = d; + } + } + } + + if( found ) + { + position_t *p; + + p = &map->grid[best_is + map->w * best_it]; + VectorCopy( p->pos, pos ); + *best_s = p->best_s; + *best_t = p->best_t; + *dist = best_dist; + return true; + } + + return false; +} + +/* +============ +CalcPositionsSize +============ +*/ +void CalcPositionsSize( void ) +{ + int total_size = 0; + + for( int facenum = 0; facenum < g_numfaces; facenum++ ) + { + positionmap_t *map = &g_face_positions[facenum]; + + if( !map->valid ) continue; + + total_size += map->w * map->h * sizeof( position_t ); + total_size += WindingSize( map->facewindingwithoffset ); + total_size += WindingSize( map->facewinding ); + total_size += WindingSize( map->texwinding ); + } + + MsgDev( D_INFO, "position maps: %s\n", Q_memprint( total_size )); +} \ No newline at end of file diff --git a/utils/p2rad/hlrad.dsp b/utils/p2rad/hlrad.dsp new file mode 100644 index 0000000..5c44a76 --- /dev/null +++ b/utils/p2rad/hlrad.dsp @@ -0,0 +1,243 @@ +# Microsoft Developer Studio Project File - Name="hlrad" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=hlrad - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "hlrad.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "hlrad.mak" CFG="hlrad - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "hlrad - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "hlrad - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/SDKSrc/Tools/utils/hlrad", FVGBAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "hlrad - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir ".\Release" +# PROP BASE Intermediate_Dir ".\Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\..\temp\hlrad\!release" +# PROP Intermediate_Dir "..\..\temp\hlrad\!release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "..\common" /I "..\..\common" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "IGNORE_SEARCH_IN_WADS" /D "ALLOW_WADS_IN_PACKS" /D "HLRAD_GOLDSRC" /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /opt:nowin98 /largeaddressaware +# ADD LINK32 msvcrt.lib /nologo /stack:0x400000,0x100000 /subsystem:console /pdb:none /machine:I386 /nodefaultlib:"libcmt.lib" /opt:nowin98 /largeaddressaware +# SUBTRACT LINK32 /profile /map +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\hlrad\!release +InputPath=\Paranoia2\src_main\temp\hlrad\!release\hlrad.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\hlrad.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\hlrad.exe "D:\Paranoia2\tools\hlrad.exe" + +# End Custom Build + +!ELSEIF "$(CFG)" == "hlrad - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir ".\Debug" +# PROP BASE Intermediate_Dir ".\Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\..\temp\hlrad\!debug" +# PROP Intermediate_Dir "..\..\temp\hlrad\!debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MTd /W3 /Gm /Gi /GX /ZI /Od /I "..\common" /I "..\..\common" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "IGNORE_SEARCH_IN_WADS" /D "ALLOW_WADS_IN_PACKS" /D "HLRAD_GOLDSRC" /FAs /FR /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 +# ADD LINK32 msvcrtd.lib /nologo /stack:0x400000,0x100000 /subsystem:console /debug /machine:I386 /nodefaultlib:"libcmtd.lib" +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\hlrad\!debug +InputPath=\Paranoia2\src_main\temp\hlrad\!debug\hlrad.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\hlrad.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\hlrad.exe "D:\Paranoia2\tools\hlrad.exe" + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "hlrad - Win32 Release" +# Name "hlrad - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=.\alias.cpp +# End Source File +# Begin Source File + +SOURCE=.\ambientcube.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\basefs.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\bspfile.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\cmdlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\conprint.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\crc32.cpp +# End Source File +# Begin Source File + +SOURCE=.\dirtmap.cpp +# End Source File +# Begin Source File + +SOURCE=.\facepos.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\filesystem.cpp +# End Source File +# Begin Source File + +SOURCE=.\lerp.cpp +# End Source File +# Begin Source File + +SOURCE=.\lightmap.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\mathlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\polylib.cpp +# End Source File +# Begin Source File + +SOURCE=.\qrad.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\scriplib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\stringlib.cpp +# End Source File +# Begin Source File + +SOURCE=.\studio.cpp +# End Source File +# Begin Source File + +SOURCE=.\textures.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\threads.cpp +# End Source File +# Begin Source File + +SOURCE=.\trace.cpp +# End Source File +# Begin Source File + +SOURCE=.\vertexlight.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\wadfile.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\zone.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=..\common\bspfile.h +# End Source File +# Begin Source File + +SOURCE=..\common\cmdlib.h +# End Source File +# Begin Source File + +SOURCE=..\common\mathlib.h +# End Source File +# Begin Source File + +SOURCE=..\common\polylib.h +# End Source File +# Begin Source File + +SOURCE=.\qrad.h +# End Source File +# Begin Source File + +SOURCE=..\common\scriplib.h +# End Source File +# Begin Source File + +SOURCE=..\common\threads.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/utils/p2rad/lerp.cpp b/utils/p2rad/lerp.cpp new file mode 100644 index 0000000..abac296 --- /dev/null +++ b/utils/p2rad/lerp.cpp @@ -0,0 +1,1793 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// lerp.c + +#include "qrad.h" +#include "utlarray.h" + +#define TRIANGLE_SHAPE_THRESHOLD DEG2RAD( 115.0 ) + +struct interpolation_t +{ + struct Point + { + int patchnum; + vec_t weight; + }; + + bool isbiased; + vec_t totalweight; + CUtlArray points; +}; + +// replace std::pair +struct pair_t +{ + vec_t first; + int second; +}; + +struct localtrian_t +{ + struct Wedge + { + enum eShape + { + eTriangular, + eConvex, + eConcave, + eSquareLeft, + eSquareRight, + }; + + eShape shape; + int leftpatchnum; + vec3_t leftspot; + vec3_t leftdirection; + // right side equals to the left side of the next wedge + vec3_t wedgenormal; // for custom usage + }; + + struct HullPoint + { + vec3_t spot; + vec3_t direction; + }; + + dplane_t plane; + winding_t *winding; + vec3_t center; // center is on the plane + + vec3_t normal; + int patchnum; + + CUtlArray< int > neighborfaces; // including the face itself + + CUtlArray sortedwedges; // in clockwise order (same as Winding) + CUtlArray sortedhullpoints; // in clockwise order (same as Winding) +}; + +struct facetriangulation_t +{ + struct Wall + { + vec3_t points[2]; + vec3_t direction; + vec3_t normal; + }; + + int facenum; + CUtlArray< int > neighbors; // including the face itself + CUtlArray< Wall > walls; + CUtlArray< localtrian_t * > localtriangulations; + CUtlArray< int > usedpatches; +}; + +// replace std::sort +int __cdecl LessThan( const pair_t *s0, const pair_t *s1 ) +{ + if( s0->first > s1->first ) + return 1; + if( s0->first < s1->first ) + return -1; + if( s0->second > s1->second ) + return 1; + if( s0->second < s1->second ) + return -1; + return 0; +} + +facetriangulation_t *g_facetriangulations[MAX_MAP_FACES]; +bool g_drawlerp = false; + +// If the surface formed by the face and its neighbor faces is not flat, the surface should be unfolded onto the face plane +// CalcAdaptedSpot calculates the coordinate of the unfolded spot on the face plane from the original position on the surface +// CalcAdaptedSpot(center) = {0,0,0} +// CalcAdaptedSpot(position on the face plane) = position - center +// Param position: must include g_face_offset +static bool CalcAdaptedSpot( const localtrian_t *lt, const vec3_t position, int surface, vec3_t spot ) +{ + vec3_t surfacespot; + vec_t dist, dist2; + vec3_t phongnormal; + vec_t dot, frac; + vec3_t v, middle; + int i; + + for( i = 0; i < lt->neighborfaces.Count(); i++ ) + { + if( lt->neighborfaces[i] == surface ) + break; + } + + if( i == lt->neighborfaces.Count( )) + { + VectorClear( spot ); + return false; + } + + VectorSubtract( position, lt->center, surfacespot ); + dot = DotProduct( surfacespot, lt->normal ); + VectorMA( surfacespot, -dot, lt->normal, spot ); + + // use phong normal instead of face normal, because phong normal is a continuous function + GetPhongNormal( surface, position, phongnormal ); + dot = DotProduct( spot, phongnormal ); + + if( fabs( dot ) > ON_EPSILON ) + { + frac = DotProduct( surfacespot, phongnormal ) / dot; + frac = bound( 0, frac, 1 ); // to correct some extreme cases + } + else frac = 0; + + VectorScale( spot, frac, middle ); + dist = VectorLength( spot ); + VectorSubtract( surfacespot, middle, v ); + dist2 = VectorLength( middle ) + VectorLength( v ); + + if( dist > ON_EPSILON && fabs( dist2 - dist ) > ON_EPSILON ) + VectorScale( spot, dist2 / dist, spot ); + + return true; +} + +static vec_t GetAngle( const vec3_t leftdirection, const vec3_t rightdirection, const vec3_t normal ) +{ + vec_t angle; + vec3_t v; + + CrossProduct( rightdirection, leftdirection, v ); + angle = atan2( DotProduct( v, normal ), DotProduct( rightdirection, leftdirection )); + + return angle; +} + +static vec_t GetAngleDiff( vec_t angle, vec_t base ) +{ + vec_t diff = angle - base; + + if( diff < 0 ) + diff += 2 * M_PI; + return diff; +} + +static vec_t GetFrac( const vec3_t leftspot, const vec3_t rightspot, const vec3_t direction, const vec3_t normal ) +{ + vec_t dot1; + vec_t dot2; + vec_t frac; + vec3_t v; + + CrossProduct( direction, normal, v ); + dot1 = DotProduct( leftspot, v ); + dot2 = DotProduct( rightspot, v ); + + // dot1 <= 0 < dot2 + if( dot1 >= -NORMAL_EPSILON ) + { + if( g_drawlerp && dot1 > ON_EPSILON ) + MsgDev( D_REPORT, "Debug: triangulation: internal error 1.\n" ); + frac = 0.0; + } + else if( dot2 <= NORMAL_EPSILON ) + { + if( g_drawlerp && dot2 < -ON_EPSILON ) + MsgDev( D_REPORT, "Debug: triangulation: internal error 2.\n" ); + frac = 1.0; + } + else + { + frac = dot1 / (dot1 - dot2); + frac = bound( 0, frac, 1 ); + } + + return frac; +} + +static vec_t GetDirection( const vec3_t spot, const vec3_t normal, vec3_t direction_out ) +{ + vec_t dot = DotProduct( spot, normal ); + VectorMA( spot, -dot, normal, direction_out ); + return VectorNormalize( direction_out ); +} + +// It returns true when the point is inside the hull region (with boundary), even if weight = 0. +static bool CalcWeight( const localtrian_t *lt, const vec3_t spot, vec_t *weight_out ) +{ + vec_t angle, len; + vec_t frac, dist; + vec3_t direction; + bool istoofar; + CUtlArray< vec_t > angles; + vec_t ratio; + const localtrian_t::HullPoint *hp1; + const localtrian_t::HullPoint *hp2; + int i, j; + + if( GetDirection( spot, lt->normal, direction ) <= 2 * ON_EPSILON ) + { + *weight_out = 1.0; + return true; + } + + if( lt->sortedhullpoints.Count() == 0 ) + { + *weight_out = 0.0; + return false; + } + + angles.SetCount( lt->sortedhullpoints.Count() ); + + for( i = 0; i < lt->sortedhullpoints.Count(); i++ ) + { + angle = GetAngle( lt->sortedhullpoints[i].direction, direction, lt->normal ); + angles[i] = GetAngleDiff( angle, 0 ); + } + + for( i = 1, j = 0; i < lt->sortedhullpoints.Count(); i++ ) + { + if( angles[i] < angles[j] ) + j = i; + } + + hp1 = <->sortedhullpoints[j]; + hp2 = <->sortedhullpoints[(j + 1) % lt->sortedhullpoints.Count()]; + + frac = GetFrac( hp1->spot, hp2->spot, direction, lt->normal ); + + len = (1.0 - frac) * DotProduct( hp1->spot, direction ) + frac * DotProduct( hp2->spot, direction ); + dist = DotProduct( spot, direction ); + + if( len <= ON_EPSILON / 4 || dist > len + 2 * ON_EPSILON ) + { + istoofar = true; + ratio = 1.0; + } + else if( dist >= len - ON_EPSILON ) + { + istoofar = false; // if we change this "false" to "true", we will see many places turned "green" in "-drawlerp" mode + ratio = 1.0; // in order to prevent excessively small weight + } + else + { + istoofar = false; + ratio = dist / len; + ratio = bound( 0, ratio, 1 ); + } + + *weight_out = 1 - ratio; + return !istoofar; +} + +static void CalcInterpolation_Square( const localtrian_t *lt, int i, const vec3_t spot, interpolation_t *interp ) +{ + vec_t frac, frac_near, frac_far; + vec3_t normal, normal1, normal2; + vec3_t test, mid_far, mid_near; + vec_t dot, dot1, dot2, ratio; + vec_t weights[4]; + const localtrian_t::Wedge *w1; + const localtrian_t::Wedge *w2; + const localtrian_t::Wedge *w3; + + w1 = <->sortedwedges[i]; + w2 = <->sortedwedges[(i + 1) % lt->sortedwedges.Count()]; + w3 = <->sortedwedges[(i + 2) % lt->sortedwedges.Count()]; + + if( w1->shape != localtrian_t::Wedge::eSquareLeft || w2->shape != localtrian_t::Wedge::eSquareRight ) + COM_FatalError( "CalcInterpolation_Square: internal error: not square.\n" ); + + weights[0] = 0.0; + weights[1] = 0.0; + weights[2] = 0.0; + weights[3] = 0.0; + + // find mid_near on (o,p3), mid_far on (p1,p2), spot on (mid_near,mid_far) + CrossProduct( w1->leftdirection, lt->normal, normal1 ); + VectorNormalize( normal1 ); + CrossProduct( w2->wedgenormal, lt->normal, normal2 ); + VectorNormalize( normal2 ); + dot1 = DotProduct( spot, normal1 ) - 0; + dot2 = DotProduct( spot, normal2 ) - DotProduct( w3->leftspot, normal2 ); + + if( dot1 <= NORMAL_EPSILON ) + { + frac = 0.0; + } + else if( dot2 <= NORMAL_EPSILON ) + { + frac = 1.0; + } + else + { + frac = dot1 / (dot1 + dot2); + frac = bound( 0, frac, 1 ); + } + + dot1 = DotProduct( w3->leftspot, normal1 ) - 0; + dot2 = 0 - DotProduct( w3->leftspot, normal2 ); + + if( dot1 <= NORMAL_EPSILON ) + { + frac_near = 1.0; + } + else if( dot2 <= NORMAL_EPSILON ) + { + frac_near = 0.0; + } + else + { + frac_near = (frac * dot2) / ((1.0 - frac) * dot1 + frac * dot2); + } + + VectorScale( w3->leftspot, frac_near, mid_near ); + dot1 = DotProduct( w2->leftspot, normal1 ) - 0; + dot2 = DotProduct( w1->leftspot, normal2 ) - DotProduct( w3->leftspot, normal2 ); + + if( dot1 <= NORMAL_EPSILON ) + { + frac_far = 1.0; + } + else if( dot2 <= NORMAL_EPSILON ) + { + frac_far = 0.0; + } + else + { + frac_far = (frac * dot2) / ((1 - frac) * dot1 + frac * dot2); + } + + VectorScale( w1->leftspot, 1.0 - frac_far, mid_far ); + VectorMA( mid_far, frac_far, w2->leftspot, mid_far ); + CrossProduct( lt->normal, w3->leftdirection, normal ); + VectorNormalize( normal ); + dot = DotProduct( spot, normal ) - 0; + dot1 = (1.0 - frac_far) * DotProduct( w1->leftspot, normal ) + frac_far * DotProduct( w2->leftspot, normal ) - 0; + + if( dot <= NORMAL_EPSILON ) + { + ratio = 0.0; + } + else if( dot >= dot1 ) + { + ratio = 1.0; + } + else + { + ratio = dot / dot1; + ratio = bound( 0, ratio, 1 ); + } + + VectorScale( mid_near, 1.0 - ratio, test ); + VectorMA( test, ratio, mid_far, test ); + VectorSubtract( test, spot, test ); + + if( g_drawlerp && VectorLength( test ) > 4 * ON_EPSILON ) + MsgDev( D_REPORT, "Debug: triangulation: internal error 12.\n" ); + + weights[0] += 0.5 * (1.0 - ratio) * (1.0 - frac_near); + weights[3] += 0.5 * (1.0 - ratio) * frac_near; + weights[1] += 0.5 * ratio * (1.0 - frac_far); + weights[2] += 0.5 * ratio * frac_far; + + // find mid_near on (o,p1), mid_far on (p2,p3), spot on (mid_near,mid_far) + CrossProduct( lt->normal, w3->leftdirection, normal1 ); + VectorNormalize( normal1 ); + CrossProduct( w1->wedgenormal, lt->normal, normal2 ); + VectorNormalize( normal2 ); + dot1 = DotProduct( spot, normal1 ) - 0; + dot2 = DotProduct( spot, normal2 ) - DotProduct( w1->leftspot, normal2 ); + + if( dot1 <= NORMAL_EPSILON ) + { + frac = 0.0; + } + else if( dot2 <= NORMAL_EPSILON ) + { + frac = 1.0; + } + else + { + frac = dot1 / (dot1 + dot2); + frac = bound( 0, frac, 1 ); + } + + dot1 = DotProduct( w1->leftspot, normal1 ) - 0; + dot2 = 0 - DotProduct( w1->leftspot, normal2 ); + + if( dot1 <= NORMAL_EPSILON ) + { + frac_near = 1.0; + } + else if( dot2 <= NORMAL_EPSILON ) + { + frac_near = 0.0; + } + else + { + frac_near = (frac * dot2) / ((1.0 - frac) * dot1 + frac * dot2); + } + + VectorScale( w1->leftspot, frac_near, mid_near ); + dot1 = DotProduct( w2->leftspot, normal1 ) - 0; + dot2 = DotProduct( w3->leftspot, normal2 ) - DotProduct( w1->leftspot, normal2 ); + + if( dot1 <= NORMAL_EPSILON ) + { + frac_far = 1.0; + } + else if( dot2 <= NORMAL_EPSILON ) + { + frac_far = 0.0; + } + else + { + frac_far = (frac * dot2) / ((1.0 - frac) * dot1 + frac * dot2); + } + + VectorScale( w3->leftspot, 1.0 - frac_far, mid_far ); + VectorMA( mid_far, frac_far, w2->leftspot, mid_far ); + CrossProduct( w1->leftdirection, lt->normal, normal ); + VectorNormalize( normal ); + dot = DotProduct( spot, normal ) - 0; + dot1 = (1.0 - frac_far) * DotProduct( w3->leftspot, normal ) + frac_far * DotProduct( w2->leftspot, normal ) - 0; + + if( dot <= NORMAL_EPSILON ) + { + ratio = 0.0; + } + else if( dot >= dot1 ) + { + ratio = 1.0; + } + else + { + ratio = dot / dot1; + ratio = bound( 0, ratio, 1 ); + } + + VectorScale( mid_near, 1.0 - ratio, test ); + VectorMA( test, ratio, mid_far, test ); + VectorSubtract( test, spot, test ); + + if( g_drawlerp && VectorLength( test ) > 4 * ON_EPSILON ) + MsgDev( D_REPORT, "Debug: triangulation: internal error 13.\n" ); + + weights[0] += 0.5 * (1 - ratio) * (1 - frac_near); + weights[1] += 0.5 * (1 - ratio) * frac_near; + weights[3] += 0.5 * ratio * (1 - frac_far); + weights[2] += 0.5 * ratio * frac_far; + + interp->isbiased = false; + interp->totalweight = 1.0; + interp->points.SetCount( 4 ); + interp->points[0].patchnum = lt->patchnum; + interp->points[0].weight = weights[0]; + interp->points[1].patchnum = w1->leftpatchnum; + interp->points[1].weight = weights[1]; + interp->points[2].patchnum = w2->leftpatchnum; + interp->points[2].weight = weights[2]; + interp->points[3].patchnum = w3->leftpatchnum; + interp->points[3].weight = weights[3]; +} + +// The interpolation function is defined over the entire plane, so CalcInterpolation never fails. +static void CalcInterpolation( const localtrian_t *lt, const vec3_t spot, interpolation_t *interp ) +{ + vec_t frac, len, dist; + vec_t dot, dot1, dot2; + vec_t angle, ratio; + const localtrian_t::Wedge *w, *wnext; + vec3_t direction; + bool istoofar; + CUtlArray< vec_t > angles; + int i, j; + + if( GetDirection( spot, lt->normal, direction ) <= 2 * ON_EPSILON ) + { + // spot happens to be at the center + interp->isbiased = false; + interp->totalweight = 1.0; + interp->points.SetCount( 1 ); + interp->points[0].patchnum = lt->patchnum; + interp->points[0].weight = 1.0; + return; + } + + if( lt->sortedwedges.Count() == 0 ) // this local triangulation only has center patch + { + interp->isbiased = true; + interp->totalweight = 1.0; + interp->points.SetCount( 1 ); + interp->points[0].patchnum = lt->patchnum; + interp->points[0].weight = 1.0; + return; + } + + // Find the wedge with minimum non-negative angle (counterclockwise) pass the spot + angles.SetCount( lt->sortedwedges.Count( )); + + for( i = 0; i < lt->sortedwedges.Count(); i++ ) + { + angle = GetAngle( lt->sortedwedges[i].leftdirection, direction, lt->normal ); + angles[i] = GetAngleDiff( angle, 0 ); + } + + for( i = 1, j = 0; i < lt->sortedwedges.Count(); i++ ) + { + if( angles[i] < angles[j] ) + j = i; + } + + w = <->sortedwedges[j]; + wnext = <->sortedwedges[(j + 1) % lt->sortedwedges.Count()]; + + // Different wedge types have different interpolation methods + switch( w->shape ) + { + case localtrian_t::Wedge::eSquareLeft: + case localtrian_t::Wedge::eSquareRight: + case localtrian_t::Wedge::eTriangular: + // w->wedgenormal is undefined + frac = GetFrac( w->leftspot, wnext->leftspot, direction, lt->normal ); + len = (1.0 - frac) * DotProduct( w->leftspot, direction ) + frac * DotProduct( wnext->leftspot, direction ); + dist = DotProduct( spot, direction ); + + if( len <= ON_EPSILON / 4 || dist > len + 2 * ON_EPSILON ) + { + istoofar = true; + ratio = 1.0; + } + else if( dist >= len - ON_EPSILON ) + { + istoofar = false; + ratio = 1.0; + } + else + { + istoofar = false; + ratio = dist / len; + ratio = bound( 0, ratio, 1 ); + } + + if( istoofar ) + { + interp->isbiased = true; + interp->totalweight = 1.0; + interp->points.SetCount( 2 ); + interp->points[0].patchnum = w->leftpatchnum; + interp->points[0].weight = 1.0 - frac; + interp->points[1].patchnum = wnext->leftpatchnum; + interp->points[1].weight = frac; + } + else if( w->shape == localtrian_t::Wedge::eSquareLeft ) + { + i = w - <->sortedwedges[0]; + CalcInterpolation_Square( lt, i, spot, interp ); + } + else if( w->shape == localtrian_t::Wedge::eSquareRight ) + { + i = w - <->sortedwedges[0]; + i = (i - 1 + lt->sortedwedges.Count()) % lt->sortedwedges.Count(); + CalcInterpolation_Square( lt, i, spot, interp ); + } + else + { + interp->isbiased = false; + interp->totalweight = 1.0; + interp->points.SetCount( 3 ); + interp->points[0].patchnum = lt->patchnum; + interp->points[0].weight = 1.0 - ratio; + interp->points[1].patchnum = w->leftpatchnum; + interp->points[1].weight = ratio * (1.0 - frac); + interp->points[2].patchnum = wnext->leftpatchnum; + interp->points[2].weight = ratio * frac; + } + break; + case localtrian_t::Wedge::eConvex: + // w->wedgenormal is the unit vector pointing from w->leftspot to wnext->leftspot + dot1 = DotProduct( w->leftspot, w->wedgenormal ) - DotProduct( spot, w->wedgenormal ); + dot2 = DotProduct( wnext->leftspot, w->wedgenormal ) - DotProduct( spot, w->wedgenormal ); + dot = 0 - DotProduct( spot, w->wedgenormal ); + // for eConvex type: dot1 < dot < dot2 + + if( g_drawlerp && ( dot1 > dot || dot > dot2 )) + { + MsgDev( D_REPORT, "Debug: triangulation: internal error 3.\n" ); + } + + if( dot1 >= -NORMAL_EPSILON ) // 0 <= dot1 < dot < dot2 + { + interp->isbiased = true; + interp->totalweight = 1.0; + interp->points.SetCount( 1 ); + interp->points[0].patchnum = w->leftpatchnum; + interp->points[0].weight = 1.0; + } + else if( dot2 <= NORMAL_EPSILON ) // dot1 < dot < dot2 <= 0 + { + interp->isbiased = true; + interp->totalweight = 1.0; + interp->points.SetCount( 1 ); + interp->points[0].patchnum = wnext->leftpatchnum; + interp->points[0].weight = 1.0; + } + else if( dot > 0 ) // dot1 < 0 < dot < dot2 + { + frac = dot1 / (dot1 - dot); + frac = bound( 0, frac, 1 ); + + interp->isbiased = true; + interp->totalweight = 1.0; + interp->points.SetCount( 2 ); + interp->points[0].patchnum = w->leftpatchnum; + interp->points[0].weight = 1 - frac; + interp->points[1].patchnum = lt->patchnum; + interp->points[1].weight = frac; + } + else // dot1 < dot <= 0 < dot2 + { + frac = dot / (dot - dot2); + frac = bound( 0, frac, 1 ); + + interp->isbiased = true; + interp->totalweight = 1.0; + interp->points.SetCount( 2 ); + interp->points[0].patchnum = lt->patchnum; + interp->points[0].weight = 1 - frac; + interp->points[1].patchnum = wnext->leftpatchnum; + interp->points[1].weight = frac; + } + break; + case localtrian_t::Wedge::eConcave: + if( DotProduct( spot, w->wedgenormal ) < 0 ) // the spot is closer to the left edge than the right edge + { + len = DotProduct( w->leftspot, w->leftdirection ); + dist = DotProduct( spot, w->leftdirection ); + + if( g_drawlerp && len <= ON_EPSILON ) + { + MsgDev( D_REPORT, "Debug: triangulation: internal error 4.\n" ); + } + + if( dist <= NORMAL_EPSILON ) + { + interp->isbiased = true; + interp->totalweight = 1.0; + interp->points.SetCount( 1 ); + interp->points[0].patchnum = lt->patchnum; + interp->points[0].weight = 1.0; + } + else if( dist >= len ) + { + interp->isbiased = true; + interp->totalweight = 1.0; + interp->points.SetCount( 1 ); + interp->points[0].patchnum = w->leftpatchnum; + interp->points[0].weight = 1.0; + } + else + { + ratio = dist / len; + ratio = bound( 0, ratio, 1 ); + + interp->isbiased = true; + interp->totalweight = 1.0; + interp->points.SetCount( 2 ); + interp->points[0].patchnum = lt->patchnum; + interp->points[0].weight = 1.0 - ratio; + interp->points[1].patchnum = w->leftpatchnum; + interp->points[1].weight = ratio; + } + } + else // the spot is closer to the right edge than the left edge + { + len = DotProduct( wnext->leftspot, wnext->leftdirection ); + dist = DotProduct( spot, wnext->leftdirection ); + + if( g_drawlerp && len <= ON_EPSILON ) + { + MsgDev( D_REPORT, "Debug: triangulation: internal error 5.\n" ); + } + + if( dist <= NORMAL_EPSILON ) + { + interp->isbiased = true; + interp->totalweight = 1.0; + interp->points.SetCount( 1 ); + interp->points[0].patchnum = lt->patchnum; + interp->points[0].weight = 1.0; + } + else if( dist >= len ) + { + interp->isbiased = true; + interp->totalweight = 1.0; + interp->points.SetCount( 1 ); + interp->points[0].patchnum = wnext->leftpatchnum; + interp->points[0].weight = 1.0; + } + else + { + ratio = dist / len; + ratio = bound( 0, ratio, 1 ); + + interp->isbiased = true; + interp->totalweight = 1.0; + interp->points.SetCount( 2 ); + interp->points[0].patchnum = lt->patchnum; + interp->points[0].weight = 1 - ratio; + interp->points[1].patchnum = wnext->leftpatchnum; + interp->points[1].weight = ratio; + } + } + break; + default: + COM_FatalError( "CalcInterpolation: internal error: invalid wedge type\n" ); + break; + } +} + +static void ApplyInterpolation( const interpolation_t *interp, int numstyles, const int *styles, vec3_t *outs, vec3_t *outs_dir = NULL ) +{ + int i, j; + + for( j = 0; j < numstyles; j++ ) + { + if( outs_dir != NULL ) + VectorClear( outs_dir[j] ); + VectorClear( outs[j] ); + } + + if( interp->totalweight <= 0 ) + return; + + for( i = 0; i < interp->points.Count(); i++ ) + { + vec_t lerp = interp->points[i].weight / interp->totalweight; + + for( j = 0; j < numstyles; j++ ) + { + const vec_t *b = GetTotalLight( &g_patches[interp->points[i].patchnum], styles[j] ); + VectorMA( outs[j], lerp, b, outs[j] ); + if( !outs_dir ) continue; + const vec_t *d = GetTotalDirection( &g_patches[interp->points[i].patchnum], styles[j] ); + VectorMA( outs_dir[j], lerp, d, outs_dir[j] ); + } + } +} + +// ===================================================================================== +// InterpolateSampleLight +// ===================================================================================== +void InterpolateSampleLight( const vec3_t position, int surface, int numstyles, const int *styles, vec3_t *outs, vec3_t *outs_dir ) +{ + vec_t dot, bestdist; + vec_t weight, dist; + CUtlArray< vec_t > localweights; + CUtlArray< interpolation_t*> localinterps; + interpolation_t *maininterp; + interpolation_t *interp; + vec3_t v, spot; + const localtrian_t *best; + const facetriangulation_t *ft1; + const facetriangulation_t *ft2; + const localtrian_t *lt; + int i, j, n; + + if( surface < 0 || surface >= g_numfaces ) + COM_FatalError( "InterpolateSampleLight: surface number out of range.\n" ); + + ft1 = g_facetriangulations[surface]; + maininterp = new interpolation_t; + maininterp->points.EnsureCount( 64 ); + + // Calculate local interpolations and their weights + localweights.Purge(); + localinterps.Purge(); + + if( g_lerp_enabled ) + { + for( i = 0; i < ft1->neighbors.Count(); i++ ) // for this face and each of its neighbors + { + ft2 = g_facetriangulations[ft1->neighbors[i]]; + + for( j = 0; j < ft2->localtriangulations.Count(); j++ ) // for each patch on that face + { + lt = ft2->localtriangulations[j]; + + if( !CalcAdaptedSpot( lt, position, surface, spot )) + { + if( g_drawlerp && ft2 == ft1 ) + { + MsgDev( D_REPORT, "Debug: triangulation: internal error 6.\n" ); + } + continue; + } + + if( !CalcWeight( lt, spot, &weight )) + continue; + + interp = new interpolation_t; + interp->points.EnsureCount( 4 ); + + CalcInterpolation( lt, spot, interp ); + + localweights.AddToTail( weight ); + localinterps.AddToTail( interp ); + } + } + } + + // combine into one interpolation + maininterp->isbiased = false; + maininterp->totalweight = 0; + maininterp->points.Purge(); + + for( i = 0; i < localinterps.Count(); i++ ) + { + if( localinterps[i]->isbiased ) + { + maininterp->isbiased = true; + } + + for( j = 0; j < localinterps[i]->points.Count(); j++ ) + { + weight = localinterps[i]->points[j].weight * localweights[i]; + + if( FBitSet( g_patches[localinterps[i]->points[j].patchnum].flags, PATCH_OUTSIDE )) + { + weight *= 0.01; + } + + n = maininterp->points.AddToTail(); + maininterp->points[n].patchnum = localinterps[i]->points[j].patchnum; + maininterp->points[n].weight = weight; + maininterp->totalweight += weight; + } + } + + if( maininterp->totalweight > 0 ) + { + ApplyInterpolation( maininterp, numstyles, styles, outs, outs_dir ); + + if( g_drawlerp ) + { + for( j = 0; j < numstyles; j++ ) + { + // white or yellow + outs[j][0] = 100; + outs[j][1] = 100; + outs[j][2] = (maininterp->isbiased ? 0 : 100); + } + } + } + else + { + // try again, don't multiply localweights[i] (which equals to 0) + maininterp->isbiased = false; + maininterp->totalweight = 0; + maininterp->points.Purge(); + + for( i = 0; i < localinterps.Count(); i++ ) + { + if( localinterps[i]->isbiased ) + { + maininterp->isbiased = true; + } + + for( j = 0; j < localinterps[i]->points.Count(); j++ ) + { + weight = localinterps[i]->points[j].weight; + + if( FBitSet( g_patches[localinterps[i]->points[j].patchnum].flags, PATCH_OUTSIDE )) + { + weight *= 0.01; + } + + n = maininterp->points.AddToTail(); + maininterp->points[n].patchnum = localinterps[i]->points[j].patchnum; + maininterp->points[n].weight = weight; + maininterp->totalweight += weight; + } + } + + if( maininterp->totalweight > 0 ) + { + ApplyInterpolation( maininterp, numstyles, styles, outs, outs_dir ); + + if( g_drawlerp ) + { + for( j = 0; j < numstyles; j++ ) + { + // red + outs[j][0] = 100; + outs[j][1] = 0; + outs[j][2] = (maininterp->isbiased ? 0 : 100); + } + } + } + else + { + // worst case, simply use the nearest patch + best = NULL; + + for( i = 0; i < ft1->localtriangulations.Count(); i++ ) + { + lt = ft1->localtriangulations[i]; + VectorCopy( position, v ); + WindingSnapPoint( lt->winding, lt->plane.normal, v ); + VectorSubtract( v, position, v ); + dist = VectorLength( v ); + + if( best == NULL || dist < bestdist - ON_EPSILON ) + { + best = lt; + bestdist = dist; + } + } + + if( best ) + { + lt = best; + VectorSubtract( position, lt->center, spot ); + dot = DotProduct( spot, lt->normal ); + VectorMA( spot, -dot, lt->normal, spot ); + CalcInterpolation( lt, spot, maininterp ); + + maininterp->totalweight = 0; + + for( j = 0; j < maininterp->points.Count(); j++ ) + { + if( FBitSet( g_patches[maininterp->points[j].patchnum].flags, PATCH_OUTSIDE )) + { + maininterp->points[j].weight *= 0.01; + } + maininterp->totalweight += maininterp->points[j].weight; + } + + ApplyInterpolation( maininterp, numstyles, styles, outs, outs_dir ); + + if( g_drawlerp ) + { + for( j = 0; j < numstyles; j++ ) + { + // green + outs[j][0] = 0; + outs[j][1] = 100; + outs[j][2] = (maininterp->isbiased ? 0 : 100); + } + } + } + else + { + maininterp->isbiased = true; + maininterp->totalweight = 0; + maininterp->points.Purge(); + ApplyInterpolation( maininterp, numstyles, styles, outs, outs_dir ); + + if( g_drawlerp ) + { + for( j = 0; j < numstyles; j++ ) + { + // black + outs[j][0] = 0; + outs[j][1] = 0; + outs[j][2] = 0; + } + } + } + } + } + + delete maininterp; + + for( i = 0; i < localinterps.Count(); i++ ) + delete localinterps[i]; + +} + +static bool TestLineSegmentIntersectWall( const facetriangulation_t *facetrian, const vec3_t p1, const vec3_t p2 ) +{ + vec_t frac, top, bottom; + vec_t dot, dot1, dot2; + vec_t front, back; + const facetriangulation_t::Wall *wall; + + for( int i = 0; i < facetrian->walls.Count(); i++ ) + { + wall = &facetrian->walls[i]; + bottom = DotProduct( wall->points[0], wall->direction ); + top = DotProduct( wall->points[1], wall->direction ); + front = DotProduct( p1, wall->normal ) - DotProduct( wall->points[0], wall->normal ); + back = DotProduct( p2, wall->normal ) - DotProduct( wall->points[0], wall->normal ); + + if( front > ON_EPSILON && back > ON_EPSILON || front < -ON_EPSILON && back < -ON_EPSILON ) + continue; + + dot1 = DotProduct( p1, wall->direction ); + dot2 = DotProduct( p2, wall->direction ); + + if( fabs( front ) <= 2 * ON_EPSILON && fabs( back ) <= 2 * ON_EPSILON ) + { + top = Q_min( top, Q_max( dot1, dot2 )); + bottom = Q_max( bottom, Q_min( dot1, dot2 )); + } + else + { + frac = front / (front - back); + frac = bound( 0, frac, 1 ); + dot = dot1 + frac * (dot2 - dot1); + top = Q_min( top, dot ); + bottom = Q_max( bottom, dot ); + } + + if( top - bottom >= -ON_EPSILON ) + return true; + } + + return false; +} + +static bool TestFarPatch( const localtrian_t *lt, const vec3_t p2, const winding_t *p2winding ) +{ + vec_t size1, size2; + vec_t dist; + vec3_t v; + int i; + + for( i = 0, size1 = 0; i < lt->winding->numpoints; i++ ) + { + VectorSubtract( lt->winding->p[i], lt->center, v ); + dist = VectorLength( v ); + + if( dist > size1 ) + size1 = dist; + } + + for( i = 0, size2 = 0; i < p2winding->numpoints; i++ ) + { + VectorSubtract( p2winding->p[i], p2, v ); + dist = VectorLength( v ); + + if( dist > size2 ) + size2 = dist; + } + + VectorSubtract( p2, lt->center, v ); + dist = VectorLength( v ); + + return dist > 1.4 * (size1 + size2); +} + +// if one of the angles in a triangle exceeds this threshold, the most distant point will be removed or the triangle will break into convex-type wedge. +static void GatherPatches( localtrian_t *lt, const facetriangulation_t *facetrian ) +{ + int patchnum2; + int facenum2; + const patch_t *patch2; + CUtlArray points; + CUtlArray angles; + localtrian_t::Wedge point; + vec_t angle; + const dplane_t *dp2; + vec3_t v; + int i; + + points.Purge(); + + for( i = 0; i < lt->neighborfaces.Count(); i++ ) + { + facenum2 = lt->neighborfaces[i]; + dp2 = GetPlaneFromFace( facenum2 ); + + for( patch2 = g_face_patches[facenum2]; patch2 != NULL; patch2 = patch2->next ) + { + patchnum2 = patch2 - g_patches; + + point.leftpatchnum = patchnum2; + VectorMA( patch2->origin, -DEFAULT_HUNT_OFFSET, dp2->normal, v ); + + // Do permission tests using the original position of the patch + if( patchnum2 == lt->patchnum || PointInWindingEpsilon( lt->winding, lt->plane.normal, v )) + continue; + + if( facenum2 != facetrian->facenum && TestLineSegmentIntersectWall( facetrian, lt->center, v )) + continue; + + if( TestFarPatch( lt, v, patch2->winding )) + continue; + + // Store the adapted position of the patch + if( !CalcAdaptedSpot( lt, v, facenum2, point.leftspot )) + continue; + + if( GetDirection( point.leftspot, lt->normal, point.leftdirection ) <= 2 * ON_EPSILON ) + continue; + + points.AddToTail( point ); + } + } + + // Sort the patches into clockwise order + angles.SetCount( points.Count( )); + + for( i = 0; i < points.Count(); i++ ) + { + angle = GetAngle( points[0].leftdirection, points[i].leftdirection, lt->normal ); + + if( i == 0 ) + { + if( g_drawlerp && fabs( angle ) > NORMAL_EPSILON ) + { + MsgDev( D_REPORT, "Debug: triangulation: internal error 7.\n" ); + } + angle = 0.0; + } + + angles[i].first = GetAngleDiff( angle, 0 ); + angles[i].second = i; + } + + angles.Sort( LessThan ); + lt->sortedwedges.SetCount( points.Count( )); + + for( i = 0; i < points.Count(); i++ ) + { + lt->sortedwedges[i] = points[angles[i].second]; + } +} + +static void PurgePatches( localtrian_t *lt ) +{ + CUtlArray< localtrian_t::Wedge > points; + int i, cur; + CUtlArray< int > next; + CUtlArray< int > prev; + CUtlArray< int > valid; + CUtlArray< pair_t > dists; + vec_t angle; + vec3_t normal; + vec3_t v; + + points.Swap( lt->sortedwedges ); + lt->sortedwedges.Purge(); + + next.SetCount( points.Count( )); + prev.SetCount( points.Count( )); + valid.SetCount( points.Count( )); + dists.SetCount( points.Count( )); + + for( i = 0; i < points.Count(); i++ ) + { + next[i] = (i + 1) % points.Count(); + prev[i] = (i - 1 + points.Count( )) % points.Count(); + valid[i] = 1; + dists[i].first = DotProduct( points[i].leftspot, points[i].leftdirection ); + dists[i].second = i; + } + + dists.Sort( LessThan ); + + for( i = 0; i < points.Count(); i++ ) + { + vec_t sangle, cangle; + + cur = dists[i].second; + if( valid[cur] == 0 ) + continue; + valid[cur] = 2; // mark current patch as final + + SinCos( TRIANGLE_SHAPE_THRESHOLD, &sangle, &cangle ); + CrossProduct( points[cur].leftdirection, lt->normal, normal ); + VectorNormalize( normal ); + VectorScale( normal, cangle, v ); + VectorMA( v, sangle, points[cur].leftdirection, v ); + + while( next[cur] != cur && valid[next[cur]] != 2 ) + { + angle = GetAngle( points[cur].leftdirection, points[next[cur]].leftdirection, lt->normal ); + if( fabs( angle ) <= DEG2RAD( 1.0 ) || GetAngleDiff( angle, 0 ) <= M_PI + NORMAL_EPSILON + && DotProduct( points[next[cur]].leftspot, v ) >= DotProduct( points[cur].leftspot, v ) - ON_EPSILON / 2 ) + { + // remove next patch + valid[next[cur]] = 0; + next[cur] = next[next[cur]]; + prev[next[cur]] = cur; + continue; + } + // the triangle is good + break; + } + + CrossProduct( lt->normal, points[cur].leftdirection, normal ); + VectorNormalize( normal ); + VectorScale( normal, cangle, v ); + VectorMA( v, sangle, points[cur].leftdirection, v ); + + while( prev[cur] != cur && valid[prev[cur]] != 2 ) + { + angle = GetAngle( points[prev[cur]].leftdirection, points[cur].leftdirection, lt->normal ); + if( fabs( angle ) <= DEG2RAD( 1.0 ) || GetAngleDiff( angle, 0 ) <= M_PI + NORMAL_EPSILON + && DotProduct (points[prev[cur]].leftspot, v) >= DotProduct (points[cur].leftspot, v ) - ON_EPSILON / 2 ) + { + // remove previous patch + valid[prev[cur]] = 0; + prev[cur] = prev[prev[cur]]; + next[prev[cur]] = cur; + continue; + } + // the triangle is good + break; + } + } + + for( i = 0; i < points.Count(); i++ ) + { + if( valid[i] == 2 ) + { + lt->sortedwedges.AddToTail( points[i] ); + } + } +} + +static void PlaceHullPoints( localtrian_t *lt ) +{ + int i, j, n; + vec_t dot, angle; + localtrian_t::HullPoint hp; + CUtlArray< localtrian_t::HullPoint > spots; + CUtlArray< pair_t > angles; + const localtrian_t::Wedge *w; + const localtrian_t::Wedge *wnext; + CUtlArray< localtrian_t::HullPoint > arc_spots; + CUtlArray< vec_t > arc_angles; + CUtlArray< int > next; + CUtlArray< int > prev; + vec_t frac; + vec_t len; + vec_t dist; + vec3_t v; + +// spots.reserve( lt->winding->numpoints ); + spots.Purge(); + + for( i = 0; i < lt->winding->numpoints; i++ ) + { + VectorSubtract( lt->winding->p[i], lt->center, v ); + dot = DotProduct( v, lt->normal ); + VectorMA( v, -dot, lt->normal, hp.spot ); + + if( !GetDirection( hp.spot, lt->normal, hp.direction )) + continue; + spots.AddToTail( hp ); + } + + if( lt->sortedwedges.Count() == 0 ) + { + angles.SetCount( spots.Count( )); + + for( i = 0; i < spots.Count(); i++ ) + { + angle = GetAngle( spots[0].direction, spots[i].direction, lt->normal ); + if( i == 0 ) angle = 0.0; + angles[i].first = GetAngleDiff( angle, 0 ); + angles[i].second = i; + } + + angles.Sort( LessThan ); + lt->sortedhullpoints.Purge(); + + for( i = 0; i < spots.Count(); i++ ) + { + if( g_drawlerp && angles[i].second != i ) + MsgDev( D_REPORT, "Debug: triangulation: internal error 8.\n"); + lt->sortedhullpoints.AddToTail( spots[angles[i].second] ); + } + return; + } + + lt->sortedhullpoints.Purge(); + + for( i = 0; i < lt->sortedwedges.Count(); i++ ) + { + w = <->sortedwedges[i]; + wnext = <->sortedwedges[(i + 1) % lt->sortedwedges.Count()]; + + angles.SetCount( spots.Count( )); + + for( j = 0; j < spots.Count(); j++ ) + { + angle = GetAngle( w->leftdirection, spots[j].direction, lt->normal ); + angles[j].first = GetAngleDiff( angle, 0 ); + angles[j].second = j; + } + + angles.Sort( LessThan ); + angle = GetAngle( w->leftdirection, wnext->leftdirection, lt->normal ); + + if( lt->sortedwedges.Count() == 1 ) + { + angle = M_PI2; + } + else + { + angle = GetAngleDiff( angle, 0 ); + } + + arc_spots.SetCount( spots.Count() + 2 ); + arc_angles.SetCount( spots.Count() + 2 ); + next.SetCount( spots.Count() + 2 ); + prev.SetCount( spots.Count() + 2 ); + + VectorCopy( w->leftspot, arc_spots[0].spot ); + VectorCopy( w->leftdirection, arc_spots[0].direction ); + arc_angles[0] = 0; + next[0] = 1; + prev[0] = -1; + n = 1; + + for( j = 0; j < spots.Count(); j++ ) + { + if( NORMAL_EPSILON <= angles[j].first && angles[j].first <= angle - NORMAL_EPSILON ) + { + arc_spots[n] = spots[angles[j].second]; + arc_angles[n] = angles[j].first; + next[n] = n + 1; + prev[n] = n - 1; + n++; + } + } + + VectorCopy( wnext->leftspot, arc_spots[n].spot ); + VectorCopy( wnext->leftdirection, arc_spots[n].direction ); + arc_angles[n] = angle; + next[n] = -1; + prev[n] = n - 1; + n++; + + for( j = 1; next[j] != -1; j = next[j] ) + { + while( prev[j] != -1 ) + { + if( arc_angles[next[j]] - arc_angles[prev[j]] <= M_PI + NORMAL_EPSILON ) + { + frac = GetFrac( arc_spots[prev[j]].spot, arc_spots[next[j]].spot, arc_spots[j].direction, lt->normal); + len = ( 1.0 - frac ) * DotProduct( arc_spots[prev[j]].spot, arc_spots[j].direction ) + + frac * DotProduct( arc_spots[next[j]].spot, arc_spots[j].direction ); + dist = DotProduct( arc_spots[j].spot, arc_spots[j].direction ); + + if( dist <= len + NORMAL_EPSILON ) + { + j = prev[j]; + next[j] = next[next[j]]; + prev[next[j]] = j; + continue; + } + } + break; + } + } + + for( j = 0; next[j] != -1; j = next[j] ) + { + lt->sortedhullpoints.AddToTail( arc_spots[j] ); + } + } +} + +static bool TryMakeSquare( localtrian_t *lt, int i ) +{ + localtrian_t::Wedge *w1; + localtrian_t::Wedge *w2; + localtrian_t::Wedge *w3; + vec3_t dir1; + vec3_t dir2; + vec_t angle; + vec3_t v; + + w1 = <->sortedwedges[i]; + w2 = <->sortedwedges[(i + 1) % lt->sortedwedges.Count()]; + w3 = <->sortedwedges[(i + 2) % lt->sortedwedges.Count()]; + + // (o, p1, p2) and (o, p2, p3) must be triangles and not in a square + if( w1->shape != localtrian_t::Wedge::eTriangular || w2->shape != localtrian_t::Wedge::eTriangular ) + return false; + + // (o, p1, p3) must be a triangle + angle = GetAngle( w1->leftdirection, w3->leftdirection, lt->normal ); + angle = GetAngleDiff( angle, 0 ); + + if( angle >= TRIANGLE_SHAPE_THRESHOLD ) + return false; + + // (p2, p1, p3) must be a triangle + VectorSubtract( w1->leftspot, w2->leftspot, v ); + if( !GetDirection( v, lt->normal, dir1 )) + return false; + + VectorSubtract( w3->leftspot, w2->leftspot, v ); + if( !GetDirection( v, lt->normal, dir2 )) + return false; + + angle = GetAngle( dir2, dir1, lt->normal ); + angle = GetAngleDiff( angle, 0 ); + if( angle >= TRIANGLE_SHAPE_THRESHOLD ) + return false; + + w1->shape = localtrian_t::Wedge::eSquareLeft; + VectorNegate( dir1, w1->wedgenormal ); + w2->shape = localtrian_t::Wedge::eSquareRight; + VectorCopy( dir2, w2->wedgenormal ); + + return true; +} + +static void FindSquares( localtrian_t *lt ) +{ + CUtlArray< pair_t > dists; + localtrian_t::Wedge *w; + int i; + + if(lt->sortedwedges.Count() <= 2 ) + return; + + dists.SetCount( lt->sortedwedges.Count( )); + + for( i = 0; i < lt->sortedwedges.Count(); i++ ) + { + w = <->sortedwedges[i]; + dists[i].first = DotProduct( w->leftspot, w->leftdirection ); + dists[i].second = i; + } + + dists.Sort( LessThan ); + + for( i = 0; i < lt->sortedwedges.Count(); i++ ) + { + TryMakeSquare( lt, dists[i].second ); + TryMakeSquare( lt, (dists[i].second - 2 + lt->sortedwedges.Count( )) % lt->sortedwedges.Count( )); + } +} + +static localtrian_t *CreateLocalTriangulation( const facetriangulation_t *facetrian, int patchnum ) +{ + localtrian_t *lt; + const patch_t *patch; + int facenum; + localtrian_t::Wedge *w; + localtrian_t::Wedge *wnext; + vec_t angle; + vec_t total; + vec3_t normal; + vec_t dot; + vec3_t v; + + facenum = facetrian->facenum; + patch = &g_patches[patchnum]; + lt = new localtrian_t; + + // Fill basic information for this local triangulation + lt->plane = *GetPlaneFromFace( facenum ); + lt->plane.dist += DotProduct( g_face_offset[facenum], lt->plane.normal ); + lt->winding = patch->winding; + VectorMA( patch->origin, -DEFAULT_HUNT_OFFSET, lt->plane.normal, lt->center ); + dot = DotProduct (lt->center, lt->plane.normal) - lt->plane.dist; + VectorMA( lt->center, -dot, lt->plane.normal, lt->center ); + + if( !PointInWindingEpsilon( lt->winding, lt->plane.normal, lt->center, ON_EPSILON, DEFAULT_EDGE_WIDTH )) + { + WindingSnapPointEpsilon( lt->winding, lt->plane.normal, lt->center, DEFAULT_EDGE_WIDTH, 4 * DEFAULT_EDGE_WIDTH ); + } + + VectorCopy( lt->plane.normal, lt->normal ); + lt->patchnum = patchnum; + lt->neighborfaces = facetrian->neighbors; + + // Gather all patches from nearby faces + GatherPatches( lt, facetrian ); + + // Remove distant patches + PurgePatches( lt ); + + // Calculate wedge types + total = 0.0; + for( int i = 0; i < lt->sortedwedges.Count(); i++ ) + { + w = <->sortedwedges[i]; + wnext = <->sortedwedges[(i + 1) % lt->sortedwedges.Count()]; + + angle = GetAngle (w->leftdirection, wnext->leftdirection, lt->normal ); + if( g_drawlerp && (lt->sortedwedges.Count() >= 2 && fabs( angle ) <= DEG2RAD( 0.9 ))) + { + MsgDev( D_REPORT, "Debug: triangulation: internal error 9.\n"); + } + angle = GetAngleDiff( angle, 0 ); + if(lt->sortedwedges.Count() == 1 ) + { + angle = M_PI2; + } + total += angle; + + if( angle <= M_PI + NORMAL_EPSILON ) + { + if( angle < TRIANGLE_SHAPE_THRESHOLD ) + { + w->shape = localtrian_t::Wedge::eTriangular; + VectorClear( w->wedgenormal ); + } + else + { + w->shape = localtrian_t::Wedge::eConvex; + VectorSubtract( wnext->leftspot, w->leftspot, v ); + GetDirection( v, lt->normal, w->wedgenormal ); + } + } + else + { + w->shape = localtrian_t::Wedge::eConcave; + VectorAdd( wnext->leftdirection, w->leftdirection, v ); + CrossProduct( lt->normal, v, normal ); + VectorSubtract( wnext->leftdirection, w->leftdirection, v ); + VectorAdd( normal, v, normal ); + GetDirection( normal, lt->normal, w->wedgenormal ); + if( g_drawlerp && VectorLength( w->wedgenormal ) == 0 ) + { + MsgDev( D_REPORT, "Debug: triangulation: internal error 10.\n"); + } + } + } + + if( g_drawlerp && (lt->sortedwedges.Count() > 0 && fabs( total - M_PI2 ) > 10 * NORMAL_EPSILON )) + { + MsgDev( D_REPORT, "Debug: triangulation: internal error 11.\n" ); + } + + FindSquares( lt ); + + // Calculate hull points + PlaceHullPoints( lt ); + + return lt; +} + +static void FindNeighbors( facetriangulation_t *facetrian ) +{ + int i, j, e; + int facenum1; + int facenum2; + const dplane_t *dp1; + const dplane_t *dp2; + const edgeshare_t *es; + const dface_t *f1; + const dface_t *f2; + + facenum1 = facetrian->facenum; + f1 = &g_dfaces[facenum1]; + dp1 = GetPlaneFromFace( f1 ); + + facetrian->neighbors.Purge(); + facetrian->neighbors.AddToTail( facenum1 ); + + for( i = 0; i < f1->numedges; i++ ) + { + e = g_dsurfedges[f1->firstedge + i]; + es = &g_edgeshare[abs( e )]; + if( !es->smooth ) continue; + + f2 = es->faces[e > 0 ? 1 : 0]; + facenum2 = f2 - g_dfaces; + dp2 = GetPlaneFromFace( f2 ); + + if( DotProduct( dp1->normal, dp2->normal ) < -NORMAL_EPSILON ) + continue; + + for( j = 0; j < facetrian->neighbors.Count(); j++ ) + { + if( facetrian->neighbors[j] == facenum2 ) + break; + } + + if( j == facetrian->neighbors.Count( )) + { + facetrian->neighbors.AddToTail( facenum2 ); + } + } +} + +static void BuildWalls( facetriangulation_t *facetrian ) +{ + int i, j, e; + int facenum; + int facenum2; + const dface_t *f; + const dface_t *f2; + const dplane_t *dp; + const dplane_t *dp2; + const edgeshare_t *es; + vec_t dot; + + facenum = facetrian->facenum; + f = &g_dfaces[facenum]; + dp = GetPlaneFromFace( f ); + + facetrian->walls.Purge(); + + for( i = 0; i < facetrian->neighbors.Count(); i++ ) + { + facenum2 = facetrian->neighbors[i]; + f2 = &g_dfaces[facenum2]; + dp2 = GetPlaneFromFace( f2 ); + + if( DotProduct( dp->normal, dp2->normal ) <= 0.1 ) + continue; + + for( j = 0; j < f2->numedges; j++ ) + { + e = g_dsurfedges[f2->firstedge + j]; + es = &g_edgeshare[abs(e)]; + + if( !es->smooth ) + { + facetriangulation_t::Wall wall; + + VectorAdd( g_dvertexes[g_dedges[abs(e)].v[0]].point, g_face_offset[facenum], wall.points[0] ); + VectorAdd( g_dvertexes[g_dedges[abs(e)].v[1]].point, g_face_offset[facenum], wall.points[1] ); + VectorSubtract( wall.points[1], wall.points[0], wall.direction ); + dot = DotProduct( wall.direction, dp->normal ); + VectorMA( wall.direction, -dot, dp->normal, wall.direction ); + + if( VectorNormalize( wall.direction )) + { + CrossProduct( wall.direction, dp->normal, wall.normal ); + VectorNormalize( wall.normal ); + facetrian->walls.AddToTail( wall ); + } + } + } + } +} + +static void CollectUsedPatches( facetriangulation_t *facetrian ) +{ + int i, j, k; + int patchnum; + const localtrian_t *lt; + const localtrian_t::Wedge *w; + + facetrian->usedpatches.Purge(); + + for( i = 0; i < facetrian->localtriangulations.Count(); i++ ) + { + lt = facetrian->localtriangulations[i]; + patchnum = lt->patchnum; + + for( k = 0; k < facetrian->usedpatches.Count(); k++ ) + { + if( facetrian->usedpatches[k] == patchnum ) + break; + } + + if( k == facetrian->usedpatches.Count( )) + facetrian->usedpatches.AddToTail( patchnum ); + + for( j = 0; j < lt->sortedwedges.Count(); j++ ) + { + w = <->sortedwedges[j]; + patchnum = w->leftpatchnum; + + for( k = 0; k < facetrian->usedpatches.Count(); k++ ) + { + if( facetrian->usedpatches[k] == patchnum ) + break; + } + + if( k == facetrian->usedpatches.Count( )) + facetrian->usedpatches.AddToTail( patchnum ); + } + } +} + +// ===================================================================================== +// CreateTriangulations +// ===================================================================================== +void CreateTriangulations( int facenum, int threadnum ) +{ + facetriangulation_t *facetrian; + int patchnum; + const patch_t *patch; + localtrian_t *lt; + + g_facetriangulations[facenum] = new facetriangulation_t; + facetrian = g_facetriangulations[facenum]; + + facetrian->facenum = facenum; + + // find neighbors + FindNeighbors( facetrian ); + + // Build walls + BuildWalls( facetrian ); + + // Create local triangulation around each patch + facetrian->localtriangulations.Purge(); + + for( patch = g_face_patches[facenum]; patch; patch = patch->next ) + { + patchnum = patch - g_patches; + lt = CreateLocalTriangulation( facetrian, patchnum ); + facetrian->localtriangulations.AddToTail( lt ); + } + + // Collect used patches + CollectUsedPatches( facetrian ); +} + +// ===================================================================================== +// GetTriangulationPatches +// ===================================================================================== +void GetTriangulationPatches( int facenum, int *numpatches, const int **patches ) +{ + const facetriangulation_t *facetrian; + + if( g_numbounce <= 0 && !g_fastmode ) + { + *numpatches = 0; + *patches = NULL; + } + else + { + facetrian = g_facetriangulations[facenum]; + *numpatches = facetrian->usedpatches.Count(); + *patches = facetrian->usedpatches.Base(); + } +} + +// ===================================================================================== +// FreeTriangulations +// ===================================================================================== +void FreeTriangulations( void ) +{ + facetriangulation_t *facetrian; + + for( int i = 0; i < g_numfaces; i++ ) + { + facetrian = g_facetriangulations[i]; + + for( int j = 0; j < facetrian->localtriangulations.Count(); j++ ) + { + delete facetrian->localtriangulations[j]; + } + + g_facetriangulations[i] = NULL; + delete facetrian; + } +} \ No newline at end of file diff --git a/utils/p2rad/lightmap.cpp b/utils/p2rad/lightmap.cpp new file mode 100644 index 0000000..8069626 --- /dev/null +++ b/utils/p2rad/lightmap.cpp @@ -0,0 +1,3988 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include "qrad.h" + +typedef struct facelist_s +{ + word facenum; + facelist_s *next; +} facelist_t; + +static int g_numnormals; +static dnormal_t g_dnormals[MAX_MAP_NORMS]; +static int g_numvertnormals; +static dvertnorm_t g_vertnormals[MAX_MAP_VERTNORMS]; +faceneighbor_t g_faceneighbor[MAX_MAP_FACES]; +vec3_t g_face_centroids[MAX_MAP_FACES]; +static facelist_t *g_dependentfacelights[MAX_MAP_FACES]; +static float g_normal_blend = cos( DEG2RAD( 2.0 )); +edgeshare_t *g_edgeshare; + +vec_t *GetTotalLight( patch_t *patch, int style ) +{ + for( int i = 0; i < MAXLIGHTMAPS && patch->totalstyle[i] != 255; i++ ) + { + if( patch->totalstyle[i] == style ) + return patch->totallight[i]; + } + return vec3_origin; +} + +vec_t *GetTotalDirection( patch_t *patch, int style ) +{ +#ifdef HLRAD_DELUXEMAPPING + for( int i = 0; i < MAXLIGHTMAPS && patch->totalstyle[i] != 255; i++ ) + { + if( patch->totalstyle[i] == style ) + return patch->totallight_dir[i]; + } +#endif + return vec3_origin; +} + +/* +=================== +StoreNormal +=================== +*/ +unsigned short StoreNormal( const vec3_t normal ) +{ + static bool showerror = false; + + // search if already added + for( int i = 1; i < g_numnormals; i++ ) + { + dnormal_t *dv = &g_dnormals[i]; + + if( DotProduct( dv->normal, normal ) > g_normal_blend ) + return i; + } + + // normals limit don't stop the compilation, just show non-fatal error + // we can use unsmoothed normals in this case... + if( g_numnormals == MAX_MAP_NORMS ) + { + if( !showerror ) + { + MsgDev( D_ERROR, "MAX_MAP_NORMS limit exceeded\n" ); + showerror = true; + } + return 0; + } + + // put new elem + VectorCopy( normal, g_dnormals[i].normal ); + + return g_numnormals++; +} + +// Must guarantee these faces will form a loop or a chain, otherwise will result in endless loop. +// +// e[end]/enext[endnext] +// * +// |\. +// |a\ fnext +// | \, +// | f \. +// | \. +// e enext +// +int AddFaceForVertexNormal( const int edgeabs, int &edgeabsnext, const int edgeend, int &edgeendnext, const dface_t *f, dface_t *&fnext, vec_t &angle ) +{ + int vnum = g_dedges[edgeabs].v[edgeend]; + int iedge, iedgenext, edge, edgenext; + int i, e, count1, count2; + vec_t dot; + + for( count1 = count2 = 0, i = 0; i < f->numedges; i++ ) + { + e = g_dsurfedges[f->firstedge + i]; + + if( g_dedges[abs(e)].v[0] == g_dedges[abs(e)].v[1] ) + continue; + + if( abs(e) == edgeabs ) + { + iedge = i; + edge = e; + count1++; + } + else if( g_dedges[abs(e)].v[0] == vnum || g_dedges[abs(e)].v[1] == vnum ) + { + iedgenext = i; + edgenext = e; + count2++; + } + } + + if( count1 != 1 || count2 != 1 ) + return -1; + + int vnum11, vnum12, vnum21, vnum22; + vec3_t vec1, vec2; + + vnum11 = g_dedges[abs(edge)].v[edge > 0 ? 0 : 1]; + vnum12 = g_dedges[abs(edge)].v[edge > 0 ? 1 : 0]; + vnum21 = g_dedges[abs(edgenext)].v[edgenext > 0 ? 0 : 1]; + vnum22 = g_dedges[abs(edgenext)].v[edgenext > 0 ? 1 : 0]; + + if( vnum == vnum12 && vnum == vnum21 && vnum != vnum11 && vnum != vnum22 ) + { + VectorSubtract( g_dvertexes[vnum11].point, g_dvertexes[vnum].point, vec1 ); + VectorSubtract( g_dvertexes[vnum22].point, g_dvertexes[vnum].point, vec2 ); + edgeabsnext = abs( edgenext ); + edgeendnext = edgenext > 0 ? 0 : 1; + } + else if( vnum == vnum11 && vnum == vnum22 && vnum != vnum12 && vnum != vnum21 ) + { + VectorSubtract( g_dvertexes[vnum12].point, g_dvertexes[vnum].point, vec1 ); + VectorSubtract( g_dvertexes[vnum21].point, g_dvertexes[vnum].point, vec2 ); + edgeabsnext = abs( edgenext ); + edgeendnext = edgenext > 0 ? 1 : 0; + } + else + { + return -1; + } + + VectorNormalize( vec1 ); + VectorNormalize( vec2 ); + dot = DotProduct( vec1, vec2 ); + dot = dot > 1 ? 1 : dot < -1 ? -1 : dot; + angle = acos( dot ); + + edgeshare_t *es = &g_edgeshare[edgeabsnext]; + + if(!( es->faces[0] && es->faces[1] )) + return 1; + + if( es->faces[0] == f && es->faces[1] != f ) + fnext = es->faces[1]; + else if( es->faces[1] == f && es->faces[0] != f ) + fnext = es->faces[0]; + else return -1; + + return 0; +} + +void TranslateWorldToTex( int facenum, matrix3x4 out ) +{ + vec_t lmvecs[2][4]; + dtexinfo_t *tex; + const dplane_t *fp; + dface_t *f; + + f = &g_dfaces[facenum]; + tex = &g_texinfo[f->texinfo]; + fp = GetPlaneFromFace( f ); + + LightMatrixFromTexMatrix( tex, lmvecs ); + + for( int i = 0; i < 3; i++ ) + { + out[i][0] = lmvecs[0][i]; + out[i][1] = lmvecs[1][i]; + out[i][2] = fp->normal[i]; + } + + out[3][0] = lmvecs[0][3]; + out[3][1] = lmvecs[1][3]; + out[3][2] = -fp->dist; +} + +/* +============ +TranslateTexToTex + +This function creates a matrix that can translate texture coords in face1 into texture coords in face2. +It keeps all points in the common edge invariant. For example, if there is a point in the edge, +and in the texture of face1, its (s,t) = (16,0), and in face2, its (s,t) = (128,64), then we must let matrix * (16,0,0) = (128,64,0) +============ +*/ +static bool TranslateTexToTex( int facenum1, int edgenum, int facenum2, matrix3x4 out1, matrix3x4 out2 ) +{ + dvertex_t *vert[2]; + vec3_t face1_vert[2]; + vec3_t face2_vert[2]; + vec3_t face1_axis[2]; + vec3_t face2_axis[2]; + const vec3_t v_up = { 0, 0, 1 }; + matrix3x4 edgetotex1, edgetotex2; + matrix3x4 worldtotex1, worldtotex2; + matrix3x4 inv1, inv2; + vec_t len1, len2; + dedge_t *e; + + TranslateWorldToTex( facenum1, worldtotex1 ); + TranslateWorldToTex( facenum2, worldtotex2 ); + + e = &g_dedges[edgenum]; + + for( int i = 0; i < 2; i++ ) + { + vert[i] = &g_dvertexes[e->v[i]]; + Matrix3x4_VectorTransform( worldtotex1, vert[i]->point, face1_vert[i] ); + face1_vert[i][2] = 0.0; // don't need we are in texture space + Matrix3x4_VectorTransform( worldtotex2, vert[i]->point, face2_vert[i] ); + face2_vert[i][2] = 0.0; // don't need we are in texture space + } + + VectorSubtract( face1_vert[1], face1_vert[0], face1_axis[0] ); + len1 = VectorLength( face1_axis[0] ); + CrossProduct( v_up, face1_axis[0], face1_axis[1] ); + + if( Matrix3x4_CalcSign( worldtotex1 ) < 0.0 ) + { + // the three vectors s, t, facenormal are in reverse order + VectorNegate( face1_axis[1], face1_axis[1] ); + } + + VectorSubtract( face2_vert[1], face2_vert[0], face2_axis[0] ); + len2 = VectorLength( face2_axis[0] ); + CrossProduct( v_up, face2_axis[0], face2_axis[1] ); + + if( Matrix3x4_CalcSign( worldtotex2 ) < 0.0 ) + { + // the three vectors s, t, facenormal are in reverse order + VectorNegate( face2_axis[1], face2_axis[1] ); + } + + VectorCopy( face1_axis[0], edgetotex1[0] ); // / v[0][0] v[1][0] \ is a rotation (possibly with a reflection by the edge) + VectorCopy( face1_axis[1], edgetotex1[1] ); // \ v[0][1] v[1][1] / + VectorScale( v_up, len1, edgetotex1[2] ); // encode the length into the 3rd value of the matrix + VectorCopy( face1_vert[0], edgetotex1[3] ); // map (0,0) into the origin point + + VectorCopy( face2_axis[0], edgetotex2[0] ); + VectorCopy( face2_axis[1], edgetotex2[1] ); + VectorScale( v_up, len2, edgetotex2[2] ); + VectorCopy( face2_vert[0], edgetotex2[3] ); + + // for some reasons matrix can't be inverted + if( !Matrix3x4_Invert_Full( inv1, edgetotex1 ) || !Matrix3x4_Invert_Full( inv2, edgetotex2 )) + return false; + + Matrix3x4_ConcatTransforms( out1, edgetotex2, inv1 ); + Matrix3x4_ConcatTransforms( out2, edgetotex1, inv2 ); + + return true; +} + +/* +============ +BuildFaceNeighbors +============ +*/ +void BuildFaceNeighbors( void ) +{ + int m, j, e; + int facenum1; + int facenum2; + int tmpneighbor[64]; + int numneighbors; + vec_t lmmins[2], lmmaxs[2]; + vec_t mins[2], maxs[2], val; + float lmvecs[2][4]; + dtexinfo_t *tex; + const dplane_t *dp1; + const dplane_t *dp2; + const edgeshare_t *es; + const dface_t *f1; + const dface_t *f2; + faceneighbor_t *fn; + dvertex_t *v; + + // store a list of every face that uses a particular vertex + for( facenum1 = 0; facenum1 < g_numfaces; facenum1++ ) + { + fn = &g_faceneighbor[facenum1]; + numneighbors = 0; + + f1 = &g_dfaces[facenum1]; + dp1 = GetPlaneFromFace( f1 ); + tex = &g_texinfo[f1->texinfo]; + VectorCopy( dp1->normal, fn->facenormal ); + mins[0] = lmmins[0] = mins[1] = lmmins[1] = 999999; + maxs[0] = lmmaxs[0] = maxs[1] = lmmaxs[1] =-999999; + + LightMatrixFromTexMatrix( tex, lmvecs ); + + for( j = 0; j < f1->numedges; j++ ) + { + e = g_dsurfedges[f1->firstedge + j]; + + if( e >= 0 ) v = &g_dvertexes[g_dedges[e].v[0]]; + else v = &g_dvertexes[g_dedges[-e].v[1]]; + + for( m = 0; m < 2; m++ ) + { + /* The following calculation is sensitive to floating-point + * precision. It needs to produce the same result that the + * light compiler does, because R_BuildLightMap uses surf-> + * extents to know the width/height of a surface's lightmap, + * and incorrect rounding here manifests itself as patches + * of "corrupted" looking lightmaps. + * Most light compilers are win32 executables, so they use + * x87 floating point. This means the multiplies and adds + * are done at 80-bit precision, and the result is rounded + * down to 32-bits and stored in val. + * Adding the casts to double seems to be good enough to fix + * lighting glitches when Quakespasm is compiled as x86_64 + * and using SSE2 floating-point. A potential trouble spot + * is the hallway at the beginning of mfxsp17. -- ericw + */ + val = ((double)v->point[0] * (double)tex->vecs[m][0]) + + ((double)v->point[1] * (double)tex->vecs[m][1]) + + ((double)v->point[2] * (double)tex->vecs[m][2]) + + (double)tex->vecs[m][3]; + mins[m] = Q_min( val, mins[m] ); + maxs[m] = Q_max( val, maxs[m] ); + + val = ((double)v->point[0] * (double)lmvecs[m][0]) + + ((double)v->point[1] * (double)lmvecs[m][1]) + + ((double)v->point[2] * (double)lmvecs[m][2]) + + (double)lmvecs[m][3]; + lmmins[m] = Q_min( val, lmmins[m] ); + lmmaxs[m] = Q_max( val, lmmaxs[m] ); + } + + es = &g_edgeshare[abs( e )]; + if( !es->smooth ) continue; + + f2 = es->faces[e > 0 ? 1 : 0]; + facenum2 = f2 - g_dfaces; + dp2 = GetPlaneFromFace( f2 ); + + if( DotProduct( dp1->normal, dp2->normal ) < -NORMAL_EPSILON ) + continue; + + // look to see if we've already added this one + for( m = 0; m < numneighbors; m++ ) + { + if( tmpneighbor[m] == facenum2 ) + break; + } + + if( m >= numneighbors ) + { + // add to neighbor list + tmpneighbor[m] = facenum2; + numneighbors++; + + if( numneighbors > ARRAYSIZE( tmpneighbor )) + COM_FatalError( "BuildFaceNeighbors: stack overflow\n" ); + } + } + + // calc face extents for traceline and lightmap extents for LightForPoint + for( j = 0; j < 2; j++ ) + { + mins[j] = floor( mins[j] ); + maxs[j] = ceil( maxs[j] ); + + fn->texmins[j] = mins[j]; + fn->extents[j] = (maxs[j] - mins[j]); + + if( FBitSet( tex->flags, TEX_WORLD_LUXELS )) + { + lmmins[j] = floor( lmmins[j] ); + lmmaxs[j] = ceil( lmmaxs[j] ); + + fn->lightmapmins[j] = lmmins[j]; + fn->lightextents[j] = (lmmaxs[j] - lmmins[j]); + } + else + { + // just copy texturemins + fn->lightmapmins[j] = fn->texmins[j]; + fn->lightextents[j] = fn->extents[j]; + } + } + + if( numneighbors ) + { + // copy over neighbor list + fn->neighbor = (int *)Mem_Alloc( numneighbors * sizeof( fn->neighbor[0] )); + fn->numneighbors = numneighbors; + + for( m = 0; m < numneighbors; m++ ) + { + fn->neighbor[m] = tmpneighbor[m]; + } + } + } +} + +void FreeFaceNeighbors( void ) +{ + faceneighbor_t *fn; + + for( int i = 0; i < g_numfaces; i++ ) + { + fn = &g_faceneighbor[i]; + Mem_Free( fn->neighbor ); + fn->neighbor = NULL; + } +} + +void GetPhongNormal( int facenum, const vec3_t spot, vec3_t phongnormal ) +{ + int j, s; + const dface_t *f = g_dfaces + facenum; + const dplane_t *p = GetPlaneFromFace( f ); + vec3_t facenormal; + + VectorCopy( p->normal, facenormal ); + VectorCopy( facenormal, phongnormal ); + + if( g_smoothing_threshold != 0 ) + { + // Calculate modified point normal for surface + // Use the edge normals iff they are defined. Bend the surface towards the edge normal(s) + // Crude first attempt: find nearest edge normal and do a simple interpolation with facenormal. + // Second attempt: find edge points+center that bound the point and do a three-point triangulation(baricentric) + // Better third attempt: generate the point normals for all vertices and do baricentric triangulation. + + for( j = 0; j < f->numedges; j++ ) + { + vec3_t p1, p2, v1, v2, vspot; + uint prev_edge, next_edge; + vec_t a1, a2, aa, bb, ab; + edgeshare_t *es0, *es1, *es2; + int e0, e1, e2; + vec3_t n1, n2; + dface_t *f2; + + if( j != 0 ) prev_edge = f->firstedge + ((j + f->numedges - 1) % f->numedges); + else prev_edge = f->firstedge + f->numedges - 1; + + if(( j + 1 ) != f->numedges ) next_edge = f->firstedge + ((j + 1) % f->numedges); + else next_edge = f->firstedge; + + e0 = g_dsurfedges[f->firstedge + j]; + e1 = g_dsurfedges[prev_edge]; + e2 = g_dsurfedges[next_edge]; + + es0 = &g_edgeshare[abs(e0)]; + es1 = &g_edgeshare[abs(e1)]; + es2 = &g_edgeshare[abs(e2)]; + + if(( !es0->smooth || es0->coplanar ) && ( !es1->smooth || es1->coplanar ) && ( !es2->smooth || es2->coplanar )) + continue; + + if( e0 > 0 ) + { + f2 = es0->faces[1]; + VectorCopy( g_dvertexes[g_dedges[e0].v[0]].point, p1 ); + VectorCopy( g_dvertexes[g_dedges[e0].v[1]].point, p2 ); + } + else + { + f2 = es0->faces[0]; + VectorCopy( g_dvertexes[g_dedges[-e0].v[1]].point, p1 ); + VectorCopy( g_dvertexes[g_dedges[-e0].v[0]].point, p2 ); + } + + // adjust for origin-based models + VectorAdd( p1, g_face_offset[facenum], p1 ); + VectorAdd( p2, g_face_offset[facenum], p2 ); + + for( s = 0; s < 2; s++ ) + { + vec3_t s1, s2; + + if( s == 0 ) VectorCopy( p1, s1 ); + else VectorCopy( p2, s1 ); + VectorAdd( p1, p2, s2 ); // edge center + VectorScale( s2, 0.5, s2 ); + + VectorSubtract( s1, g_face_centroids[facenum], v1 ); + VectorSubtract( s2, g_face_centroids[facenum], v2 ); + + VectorSubtract( spot, g_face_centroids[facenum], vspot ); + + aa = DotProduct( v1, v1 ); + bb = DotProduct( v2, v2 ); + ab = DotProduct( v1, v2 ); + a1 = (bb * DotProduct( v1, vspot ) - ab * DotProduct( vspot, v2 )) / (aa * bb - ab * ab); + a2 = (DotProduct( vspot, v2 ) - a1 * ab) / bb; + + // test center to sample vector for inclusion between center to vertex vectors (Use dot product of vectors) + if( a1 >= -0.01 && a2 >= -0.01 ) + { + // calculate distance from edge to pos + vec3_t temp; + + if( es0->smooth ) + { + if( s == 0 ) VectorCopy( es0->vertex_normal[e0 > 0 ? 0 : 1], n1 ); + else VectorCopy( es0->vertex_normal[e0 > 0 ? 1 : 0], n1 ); + } + else if( s == 0 && es1->smooth ) + { + VectorCopy( es1->vertex_normal[e1 > 0 ? 1 : 0], n1 ); + } + else if( s == 1 && es2->smooth ) + { + VectorCopy( es2->vertex_normal[e2 > 0 ? 0 : 1], n1 ); + } + else + { + VectorCopy( facenormal, n1 ); + } + + if( es0->smooth ) + VectorCopy( es0->interface_normal, n2 ); + else VectorCopy( facenormal, n2 ); + + // Interpolate between the center and edge normals based on sample position + VectorScale( facenormal, 1.0 - a1 - a2, phongnormal ); + VectorScale( n1, a1, temp ); + VectorAdd( phongnormal, temp, phongnormal ); + VectorScale( n2, a2, temp ); + VectorAdd( phongnormal, temp, phongnormal ); + VectorNormalize( phongnormal ); + break; + } + } + } + } +} + +/* +============ +PairEdges +============ +*/ +void PairEdges( void ) +{ + int edgeabs, absnext; + int edgeend, endnext; + vec3_t normal, normals, edgenormal; + int i, j, k, d, r, count; + dface_t *f, *fcur, *fnext; + vec_t angle, angles; + edgeshare_t *e; + + g_edgeshare = (edgeshare_t *)Mem_Alloc( sizeof( edgeshare_t ) * g_numedges ); + + for( i = 0, f = g_dfaces; i < g_numfaces; i++, f++ ) + { + const dtexinfo_t *tex = &g_texinfo[f->texinfo]; + const dplane_t *fp = GetPlaneFromFace( f ); + + if( FBitSet( tex->flags, TEX_SPECIAL )) + { + // special textures don't have lightmaps + continue; + } + + for( j = 0; j < f->numedges; j++ ) + { + k = g_dsurfedges[f->firstedge + j]; + + if( k < 0 ) + { + e = &g_edgeshare[-k]; + e->faces[1] = f; + } + else + { + e = &g_edgeshare[k]; + e->faces[0] = f; + } + + if( e->faces[0] && e->faces[1] ) + { + // determine if coplanar + if( e->faces[0]->planenum == e->faces[1]->planenum && e->faces[0]->side == e->faces[1]->side ) + { + VectorCopy( GetPlaneFromFace( e->faces[0] )->normal, e->interface_normal ); + e->cos_normals_angle = 1.0; + e->coplanar = true; + } + else + { + // see if they fall into a "smoothing group" based on angle of the normals + vec3_t normals[2]; + + VectorCopy( GetPlaneFromFace( e->faces[0] )->normal, normals[0] ); + VectorCopy( GetPlaneFromFace( e->faces[1] )->normal, normals[1] ); + e->cos_normals_angle = DotProduct( normals[0], normals[1] ); + + if( e->cos_normals_angle > ( 1.0 - NORMAL_EPSILON )) + { + VectorCopy( GetPlaneFromFace( e->faces[0] )->normal, e->interface_normal ); + e->cos_normals_angle = 1.0; + e->coplanar = true; + } + else if( g_smoothing_threshold > 0.0 ) + { + if( e->cos_normals_angle >= g_smoothing_threshold ) + { + VectorAdd( normals[0], normals[1], e->interface_normal ); + VectorNormalize( e->interface_normal ); + } + } + } + + if( !VectorIsNull( e->interface_normal )) + e->smooth = true; + + if( e->smooth ) + { + int facenum1 = e->faces[0] - g_dfaces; + int facenum2 = e->faces[1] - g_dfaces; + + // compute the matrix in advance + if( !TranslateTexToTex( facenum1, abs( k ), facenum2, e->textotex[0], e->textotex[1] )) + { + VectorClear( e->interface_normal ); + e->coplanar = false; + e->smooth = false; + MsgDev( D_WARN, "TranslateTexToTex failed on face %d and %d\n", facenum1, facenum2 ); + } + } + } + } + } + + for( edgeabs = 0; edgeabs < g_numedges; edgeabs++ ) + { + e = &g_edgeshare[edgeabs]; + if( !e->smooth ) continue; + + VectorCopy( e->interface_normal, edgenormal ); + + if( g_dedges[edgeabs].v[0] != g_dedges[edgeabs].v[1] ) + { + const dplane_t *p0 = GetPlaneFromFace( e->faces[0] ); + const dplane_t *p1 = GetPlaneFromFace( e->faces[1] ); + + for( edgeend = 0; edgeend < 2; edgeend++ ) + { + VectorClear( normals ); + angles = 0; + + for( d = 0; d < 2; d++ ) + { + f = e->faces[d]; + absnext = edgeabs; + endnext = edgeend; + count = 0; + fnext = f; + + while( 1 ) + { + fcur = fnext; + r = AddFaceForVertexNormal( absnext, absnext, endnext, endnext, fcur, fnext, angle ); + count++; + + if( r == -1 || count > 128 ) + { + MsgDev( D_WARN, "PairEdges: face edges mislink\n" ); + break; + } + + VectorCopy( GetPlaneFromFace( fcur )->normal, normal ); + + if( DotProduct( normal, p0->normal ) <= NORMAL_EPSILON ) + break; + + if( DotProduct( normal, p1->normal ) <= NORMAL_EPSILON ) + break; + + if( DotProduct( edgenormal, normal ) + NORMAL_EPSILON < g_smoothing_threshold ) + break; + + VectorMA( normals, angle, normal, normals ); + angles += angle; + + if( r != 0 || fnext == f ) + break; + } + } + + if( angles < NORMAL_EPSILON ) + { + VectorCopy( edgenormal, e->vertex_normal[edgeend] ); + MsgDev( D_WARN, "PairEdges: no valid faces\n" ); + } + else + { + VectorNormalize( normals ); + VectorCopy( normals, e->vertex_normal[edgeend] ); + } + } + } + else + { + MsgDev( D_WARN, "PairEdges: invalid edge\n" ); + VectorCopy( edgenormal, e->vertex_normal[0] ); + VectorCopy( edgenormal, e->vertex_normal[1] ); + } + + if( e->coplanar ) + { + if( !VectorCompare( e->vertex_normal[0], e->interface_normal )) + e->coplanar = false; + + if( !VectorCompare( e->vertex_normal[1], e->interface_normal )) + e->coplanar = false; + } + } +} + +void FreeSharedEdges( void ) +{ + Mem_Free( g_edgeshare ); + g_edgeshare = NULL; +} + +/* +================================================================= + + LIGHTMAP SAMPLE GENERATION + +================================================================= +*/ +/* +================ +CalcFaceExtents + +Fills in s->texmins[] and s->texsize[] +also sets exactmins[] and exactmaxs[] +================ +*/ +void CalcFaceExtents( lightinfo_t *l ) +{ + vec_t mins[2]; + vec_t maxs[2]; + float lmvecs[2][4]; + int i, j, e; + dtexinfo_t *tex; + vec_t val; + dvertex_t *v; + dface_t *s; + + s = l->face; + + mins[0] = mins[1] = 999999; + maxs[0] = maxs[1] = -999999; + + tex = &g_texinfo[s->texinfo]; + + int max_surface_extent = GetSurfaceExtent( tex ); + int texture_step = GetTextureStep( tex ); + + LightMatrixFromTexMatrix( tex, lmvecs ); + + for( i = 0; i < s->numedges; i++ ) + { + e = g_dsurfedges[s->firstedge+i]; + if( e >= 0 ) v = g_dvertexes + g_dedges[e].v[0]; + else v = g_dvertexes + g_dedges[-e].v[1]; + + for( j = 0; j < 2; j++ ) + { + /* The following calculation is sensitive to floating-point + * precision. It needs to produce the same result that the + * light compiler does, because R_BuildLightMap uses surf-> + * extents to know the width/height of a surface's lightmap, + * and incorrect rounding here manifests itself as patches + * of "corrupted" looking lightmaps. + * Most light compilers are win32 executables, so they use + * x87 floating point. This means the multiplies and adds + * are done at 80-bit precision, and the result is rounded + * down to 32-bits and stored in val. + * Adding the casts to double seems to be good enough to fix + * lighting glitches when Quakespasm is compiled as x86_64 + * and using SSE2 floating-point. A potential trouble spot + * is the hallway at the beginning of mfxsp17. -- ericw + */ + val = ((double)v->point[0] * (double)lmvecs[j][0]) + + ((double)v->point[1] * (double)lmvecs[j][1]) + + ((double)v->point[2] * (double)lmvecs[j][2]) + + (double)lmvecs[j][3]; + mins[j] = Q_min( val, mins[j] ); + maxs[j] = Q_max( val, maxs[j] ); + } + } + + for( i = 0; i < 2; i++ ) + { + l->exactmins[i] = mins[i]; + l->exactmaxs[i] = maxs[i]; + + mins[i] = floor( mins[i] / texture_step ); + maxs[i] = ceil( maxs[i] / texture_step ); + + l->texmins[i] = mins[i]; + l->texsize[i] = (maxs[i] - mins[i]); + } + + if( !FBitSet( tex->flags, TEX_SPECIAL )) + { + if( l->texsize[0] * l->texsize[1] > ( MAX_SINGLEMAP / 3 )) + COM_FatalError( "surface to large to map\n" ); + + if( l->texsize[0] > max_surface_extent ) + MsgDev( D_ERROR, "bad surface extents %d > %d\n", l->texsize[0], max_surface_extent ); + + if( l->texsize[1] > max_surface_extent ) + MsgDev( D_ERROR, "bad surface extents %d > %d\n", l->texsize[1], max_surface_extent ); + + if( l->texsize[0] < 0 || l->texsize[1] < 0 ) + COM_FatalError( "negative extents\n" ); + } + + l->lmcache_density = 1; + l->lmcache_side = (int)ceil(( 0.5 * g_blur * l->lmcache_density - 0.5 ) * ( 1.0 - NORMAL_EPSILON )); + l->lmcache_offset = l->lmcache_side; + l->lmcachewidth = l->texsize[0] * l->lmcache_density + 1 + 2 * l->lmcache_side; + l->lmcacheheight = l->texsize[1] * l->lmcache_density + 1 + 2 * l->lmcache_side; + + l->light = (vec3_t (*)[MAXLIGHTMAPS])Mem_Alloc( l->lmcachewidth * l->lmcacheheight * sizeof( vec3_t[MAXLIGHTMAPS] )); +#ifdef HLRAD_DELUXEMAPPING + l->deluxe = (vec3_t (*)[MAXLIGHTMAPS])Mem_Alloc( l->lmcachewidth * l->lmcacheheight * sizeof( vec3_t[MAXLIGHTMAPS] )); + l->normals = (vec3_t *)Mem_Alloc( l->lmcachewidth * l->lmcacheheight * sizeof( vec3_t )); +#ifdef HLRAD_SHADOWMAPPING + l->shadow = (vec_t (*)[MAXLIGHTMAPS])Mem_Alloc( l->lmcachewidth * l->lmcacheheight * sizeof( vec_t[MAXLIGHTMAPS] )); +#endif +#endif +} + +/* +================ +CalcFaceVectors + +Fills in texorg, worldtotex. and textoworld +================ +*/ +void CalcFaceVectors( lightinfo_t *l ) +{ + dtexinfo_t *tex; + vec_t distscale; + vec_t dist, len; + float lmvecs[2][4]; + vec3_t texnormal; + int i, j; + + tex = &g_texinfo[l->face->texinfo]; + + LightMatrixFromTexMatrix( tex, lmvecs ); + + // convert from float to double + for( i = 0; i < 2; i++ ) + { + for( j = 0; j < 3; j++ ) + { + l->worldtotex[i][j] = lmvecs[i][j]; + } + } + + // calculate a normal to the texture axis. points can be moved along this + // without changing their S/T + CrossProduct( lmvecs[1], lmvecs[0], texnormal ); + VectorNormalize( texnormal ); + + // flip it towards plane normal + distscale = DotProduct( texnormal, l->plane->normal ); + if( !distscale ) + { + MsgDev( D_WARN, "Texture axis perpendicular to face\n" ); + distscale = 1.0; + } + + if( distscale < 0 ) + { + VectorNegate( texnormal, texnormal ); + distscale = -distscale; + } + + // distscale is the ratio of the distance along the texture normal to + // the distance along the plane normal + distscale = 1.0 / distscale; + + for( i = 0; i < 2; i++ ) + { + CrossProduct( l->worldtotex[!i], l->plane->normal, l->textoworld[i] ); + len = DotProduct( l->textoworld[i], l->worldtotex[i] ); + VectorScale( l->textoworld[i], ( 1.0 / len ), l->textoworld[i] ); + } + + // calculate texorg on the texture plane + VectorMA( l->texorg, -lmvecs[0][3], l->textoworld[0], l->texorg ); + VectorMA( l->texorg, -lmvecs[1][3], l->textoworld[1], l->texorg ); + + // project back to the face plane + dist = ( DotProduct( l->texorg, l->plane->normal ) - l->plane->dist ) * distscale; + VectorMA( l->texorg, -dist, texnormal, l->texorg ); + + // compensate for org'd bmodels + VectorAdd( l->texorg, g_face_offset[l->surfnum], l->texorg ); +} + +/* +================= +TexelSpaceToWorld + +translate texel coord into world coord +================= +*/ +void TexelSpaceToWorld( const lightinfo_t *l, vec3_t world, const vec_t s, const vec_t t ) +{ + VectorMA( l->texorg, s, l->textoworld[0], world ); + VectorMA( world, t, l->textoworld[1], world ); +} + +/* +================= +WorldToTexelSpace + +translate world coord into texel coord +================= +*/ +void WorldToTexelSpace( const lightinfo_t *l, const vec3_t world, vec_t &s, vec_t &t ) +{ + vec3_t pos; + + VectorSubtract( world, l->texorg, pos ); + s = DotProduct( pos, l->worldtotex[0] ); + t = DotProduct( pos, l->worldtotex[1] ); +} + +typedef struct +{ + int edgenum; // g_dedges index + int edgeside; + int nextfacenum; // where to grow + bool tried; + + vec3_t point1; // start point + vec3_t point2; // end point + vec3_t direction; // normalized; from point1 to point2 + + bool noseam; + vec_t distance; // distance from origin + vec_t distancereduction; + vec_t flippedangle; + + matrix3x4 prevtonext; + matrix3x4 nexttoprev; + vec_t ratio; // if ratio != 1, seam is unavoidable +} samplefragedge_t; + +typedef struct +{ + dplane_t planes[4]; +} samplefragrect_t; + +typedef struct samplefrag_s +{ + samplefrag_s *next; // since this is a node in a list + samplefrag_s *parentfrag; // where it grew from + samplefragedge_t *parentedge; + int facenum; // facenum + + vec_t flippedangle; // copied from parent edge + bool noseam; // copied from parent edge + + matrix3x4 coordtomycoord; // v[2][2] > 0, v[2][0] = v[2][1] = v[0][2] = v[1][2] = 0.0 + matrix3x4 mycoordtocoord; + + vec3_t origin; // original s,t + vec3_t myorigin; // relative to the texture coordinate on that face + samplefragrect_t rect; // original rectangle that forms the boundary + samplefragrect_t myrect; // relative to the texture coordinate on that face + + winding_t *winding; // a fragment of the original rectangle in the texture coordinate plane; + // windings of different frags should not overlap + dplane_t windingplane; // normal = (0,0,1) or (0,0,-1); if this normal is wrong, PointInWinding() will never return true + winding_t *mywinding; // relative to the texture coordinate on that face + dplane_t mywindingplane; + + int numedges; // # of candicates for the next growth + samplefragedge_t *edges; // candicates for the next growth +} samplefrag_t; + +typedef struct +{ + int maxsize; + int size; + samplefrag_t *head; +} samplefraginfo_t; + +/* +================= +ChopFrag + +fill winding, windingplane, mywinding, +mywindingplane, numedges, edges +================= +*/ +void ChopFrag( samplefrag_t *frag ) +{ + // get the shape of the fragment by clipping the face using the boundaries + const vec3_t v_up = { 0, 0, 1 }; + winding_t *facewinding; + matrix3x4 worldtotex; + dface_t *f; + int x; + + f = &g_dfaces[frag->facenum]; + facewinding = WindingFromFace( f ); + + TranslateWorldToTex( frag->facenum, worldtotex ); + frag->mywinding = AllocWinding( facewinding->numpoints ); + frag->mywinding->numpoints = facewinding->numpoints; + + // transform facewinding into texture space + for( x = 0; x < facewinding->numpoints; x++ ) + { + Matrix3x4_VectorTransform( worldtotex, facewinding->p[x], frag->mywinding->p[x] ); + frag->mywinding->p[x][2] = 0.0; + } + + RemoveColinearPointsEpsilon( frag->mywinding, ON_EPSILON ); + VectorCopy( v_up, frag->mywindingplane.normal ); // this is the same as applying the worldtotex matrix to the faceplane + + if( Matrix3x4_CalcSign( worldtotex ) < 0.0 ) + frag->mywindingplane.normal[2] *= -1.0; + frag->mywindingplane.dist = 0.0; + + for( x = 0; x < 4 && frag->mywinding != NULL; x++ ) + ChopWindingInPlace( &frag->mywinding, frag->myrect.planes[x].normal, frag->myrect.planes[x].dist, ON_EPSILON, false ); + + if( !frag->mywinding ) + { + FreeWinding( facewinding ); + return; // failed to chop + } + + ASSERT( frag->mywinding != NULL ); + + frag->winding = AllocWinding( frag->mywinding->numpoints ); + frag->winding->numpoints = frag->mywinding->numpoints; + + // translate coords from current surface to another + for( x = 0; x < frag->mywinding->numpoints; x++ ) + Matrix3x4_VectorTransform( frag->mycoordtocoord, frag->mywinding->p[x], frag->winding->p[x] ); + + RemoveColinearPointsEpsilon( frag->winding, ON_EPSILON ); + VectorCopy( frag->mywindingplane.normal, frag->windingplane.normal ); + + if( Matrix3x4_CalcSign( frag->mycoordtocoord ) < 0.0 ) + frag->windingplane.normal[2] *= -1.0; + frag->windingplane.dist = 0.0; + + FreeWinding( facewinding ); + + // find the edges where the fragment can grow in the future + frag->edges = (samplefragedge_t *)Mem_Alloc( f->numedges * sizeof( samplefragedge_t )); + frag->numedges = 0; + + for( int i = 0; i < f->numedges; i++ ) + { + samplefragedge_t *e; + edgeshare_t *es; + dedge_t *de; + dvertex_t *dv1; + dvertex_t *dv2; + matrix3x4 *m1; + matrix3x4 *m2; + vec_t frac1, frac2; + vec_t dot0, dot1, dot2; + vec3_t tmp, v, normal; + vec_t edgelen; + + e = &frag->edges[frag->numedges]; + + // some basic info + e->edgenum = abs( g_dsurfedges[f->firstedge + i] ); + e->edgeside = (g_dsurfedges[f->firstedge + i] < 0 ? 1 : 0); + es = &g_edgeshare[e->edgenum]; + if( !es->smooth ) continue; + + if(( es->faces[e->edgeside] - g_dfaces ) != frag->facenum ) + COM_FatalError( "ChopFrag: internal error\n" ); + + e->nextfacenum = (es->faces[!e->edgeside] - g_dfaces); + m1 = (matrix3x4 *)&es->textotex[e->edgeside]; + m2 = (matrix3x4 *)&es->textotex[!e->edgeside]; + + if( e->nextfacenum == frag->facenum ) + continue; // an invalid edge (usually very short) + e->tried = false; // because the frag hasn't been linked into the list yet + + // translate the edge points from world to the texture plane of the original frag + // so the distances are able to be compared among edges from different frags + de = &g_dedges[e->edgenum]; + dv1 = &g_dvertexes[de->v[e->edgeside]]; + dv2 = &g_dvertexes[de->v[!e->edgeside]]; + + // translate to another frag + Matrix3x4_VectorTransform( worldtotex, dv1->point, tmp ); + Matrix3x4_VectorTransform( frag->mycoordtocoord, tmp, e->point1 ); + e->point1[2] = 0.0; + + Matrix3x4_VectorTransform( worldtotex, dv2->point, tmp ); + Matrix3x4_VectorTransform( frag->mycoordtocoord, tmp, e->point2 ); + e->point2[2] = 0.0; + + VectorSubtract( e->point2, e->point1, e->direction ); + edgelen = VectorNormalize( e->direction ); + + if( edgelen <= ON_EPSILON ) + continue; + + // clip the edge + frac1 = 0; + frac2 = 1; + + for( int x = 0; x < 4; x++ ) + { + vec_t dot1 = DotProduct( e->point1, frag->rect.planes[x].normal ) - frag->rect.planes[x].dist; + vec_t dot2 = DotProduct( e->point2, frag->rect.planes[x].normal ) - frag->rect.planes[x].dist; + + if( dot1 <= ON_EPSILON && dot2 <= ON_EPSILON ) + { + frac1 = 1.0; + frac2 = 0.0; + } + else if( dot1 < 0.0 ) + { + frac1 = Q_max( frac1, dot1 / ( dot1 - dot2 )); + } + else if( dot2 < 0.0 ) + { + frac2 = Q_min( frac2, dot1 / ( dot1 - dot2 )); + } + } + + if( edgelen * ( frac2 - frac1 ) <= ON_EPSILON ) + continue; + + VectorMA( e->point1, edgelen * frac2, e->direction, e->point2 ); + VectorMA( e->point1, edgelen * frac1, e->direction, e->point1 ); + + // calculate the distance, etc., which are used to determine its priority + dot0 = DotProduct( frag->origin, e->direction ); + dot1 = DotProduct( e->point1, e->direction ); + dot2 = DotProduct( e->point2, e->direction ); + e->noseam = frag->noseam; + + dot0 = Q_max( dot1, Q_min( dot0, dot2 )); + VectorMA( e->point1, dot0 - dot1, e->direction, v ); + VectorSubtract( v, frag->origin, v ); + e->distance = VectorLength( v ); + CrossProduct( e->direction, frag->windingplane.normal, normal ); + VectorNormalize( normal ); // points inward + e->distancereduction = DotProduct( v, normal ); + e->flippedangle = frag->flippedangle + acos( Q_min( es->cos_normals_angle, 1.0 )); + + // calculate the matrix + e->ratio = (*m2)[2][2]; + + if( e->ratio <= NORMAL_EPSILON || ( 1.0 / e->ratio ) <= NORMAL_EPSILON ) + { + MsgDev( D_INFO, "TranslateTexToTex failed on face %d and %d\n", frag->facenum, e->nextfacenum ); + continue; + } + + Matrix3x4_Copy( e->prevtonext, *m1 ); + Matrix3x4_Copy( e->nexttoprev, *m2 ); + + if( fabs( e->ratio - 1.0 ) >= 0.005 ) + e->noseam = false; + frag->numedges++; + } +} + +static samplefrag_t *GrowSingleFrag( const samplefraginfo_t *info, samplefrag_t *parent, samplefragedge_t *edge ) +{ + dplane_t clipplanes[MAX_POINTS_ON_WINDING]; + int x, numclipplanes; + bool overlap; + samplefrag_t *frag; + + frag = (samplefrag_t *)Mem_Alloc( sizeof( samplefrag_t )); + + // some basic info + frag->next = NULL; + frag->parentfrag = parent; + frag->parentedge = edge; + frag->facenum = edge->nextfacenum; + frag->flippedangle = edge->flippedangle; + frag->noseam = edge->noseam; + + // calculate the matrix + Matrix3x4_ConcatTransforms( frag->coordtomycoord, edge->prevtonext, parent->coordtomycoord ); + Matrix3x4_ConcatTransforms( frag->mycoordtocoord, parent->mycoordtocoord, edge->nexttoprev ); + + // fill in origin + VectorCopy( parent->origin, frag->origin ); + Matrix3x4_VectorTransform( frag->coordtomycoord, frag->origin, frag->myorigin ); + + // fill in boundaries + frag->rect = parent->rect; + + for( x = 0; x < 4; x++ ) + { + // since a plane's parameters are in the dual coordinate space, + // we translate the original absolute plane into this relative plane by multiplying the inverse matrix + Matrix3x4_TransformStandardPlane( frag->mycoordtocoord, frag->rect.planes[x].normal, frag->rect.planes[x].dist, + frag->myrect.planes[x].normal, &frag->myrect.planes[x].dist ); + vec_t len = VectorLength( frag->myrect.planes[x].normal ); + + if( !len ) + { + MsgDev( D_INFO, "couldn't translate sample boundaries on face %d\n", frag->facenum ); + Mem_Free( frag ); + return NULL; + } + + VectorScale( frag->myrect.planes[x].normal, (1.0 / len), frag->myrect.planes[x].normal ); + frag->myrect.planes[x].dist /= len; + } + + // chop windings and edges + ChopFrag( frag ); + + if( !frag->winding || !frag->mywinding ) + { + // empty + if( frag->mywinding ) + FreeWinding( frag->mywinding ); + if( frag->winding ) + FreeWinding( frag->winding ); + Mem_Free( frag->edges ); + Mem_Free( frag ); + + return NULL; + } + + // do overlap test + numclipplanes = 0; + overlap = false; + + for( x = 0; x < frag->winding->numpoints; x++ ) + { + vec3_t v; + + VectorSubtract( frag->winding->p[(x + 1) % frag->winding->numpoints], frag->winding->p[x], v ); + CrossProduct( v, frag->windingplane.normal, clipplanes[numclipplanes].normal ); + + if( !VectorNormalize( clipplanes[numclipplanes].normal )) + continue; + + clipplanes[numclipplanes].dist = DotProduct( frag->winding->p[x], clipplanes[numclipplanes].normal ); + numclipplanes++; + } + + for( samplefrag_t *f2 = info->head; f2 && !overlap; f2 = f2->next ) + { + winding_t *w = CopyWinding( f2->winding ); + + for( x = 0; x < numclipplanes && w != NULL; x++ ) + ChopWindingInPlace( &w, clipplanes[x].normal, clipplanes[x].dist, ON_EPSILON * 4, false ); + overlap = (w) ? true : false; + if( w ) FreeWinding( w ); + } + + if( overlap ) + { + // in the original texture plane, this fragment overlaps with some existing fragments + if( frag->mywinding ) + FreeWinding( frag->mywinding ); + if( frag->winding ) + FreeWinding( frag->winding ); + Mem_Free( frag->edges ); + Mem_Free( frag ); + + return NULL; + } + + return frag; +} + +static bool FindBestEdge( samplefraginfo_t *info, samplefrag_t *&bestfrag, samplefragedge_t *&bestedge ) +{ + bool found = false; + bool better; + samplefrag_t *f; + samplefragedge_t *e; + + for( f = info->head; f != NULL; f = f->next ) + { + for( e = f->edges; e < f->edges + f->numedges; e++ ) + { + if( e->tried ) continue; + + if( !found ) + better = true; + else if(( e->flippedangle < M_PI + NORMAL_EPSILON ) != ( bestedge->flippedangle < M_PI + NORMAL_EPSILON )) + better = (( e->flippedangle < M_PI + NORMAL_EPSILON ) && !( bestedge->flippedangle < M_PI + NORMAL_EPSILON )); + else if( e->noseam != bestedge->noseam ) + better = (e->noseam && !bestedge->noseam); + else if( fabs( e->distance - bestedge->distance ) > ON_EPSILON ) + better = ( e->distance < bestedge->distance ); + else if( fabs( e->distancereduction - bestedge->distancereduction ) > ON_EPSILON ) + better = ( e->distancereduction > bestedge->distancereduction ); + else better = e->edgenum < bestedge->edgenum; + + if( better ) + { + found = true; + bestfrag = f; + bestedge = e; + } + } + } + + return found; +} + +static samplefraginfo_t *CreateSampleFrag( int facenum, vec_t s, vec_t t, const vec_t square[2][2], int maxsize ) +{ + const vec3_t v_s = { 1, 0, 0 }; + const vec3_t v_t = { 0, 1, 0 }; + samplefraginfo_t *info; + + info = (samplefraginfo_t *)Mem_Alloc( sizeof( samplefraginfo_t )); + info->maxsize = maxsize; + info->size = 1; + info->head = (samplefrag_t *)Mem_Alloc( sizeof( samplefrag_t )); + info->head->next = NULL; + info->head->parentfrag = NULL; + info->head->parentedge = NULL; + info->head->facenum = facenum; + info->head->flippedangle = 0.0; + info->head->noseam = true; + + Matrix3x4_LoadIdentity( info->head->coordtomycoord ); + Matrix3x4_LoadIdentity( info->head->mycoordtocoord ); + + VectorSet( info->head->origin, s, t, 0 ); + VectorCopy( info->head->origin, info->head->myorigin ); + + VectorScale( v_s, 1.0, info->head->rect.planes[0].normal ); + info->head->rect.planes[0].dist = square[0][0]; // smin + VectorScale( v_s, -1.0, info->head->rect.planes[1].normal ); + info->head->rect.planes[1].dist = -square[1][0]; // smax + VectorScale( v_t, 1.0, info->head->rect.planes[2].normal ); + info->head->rect.planes[2].dist = square[0][1]; // tmin + VectorScale( v_t, -1.0, info->head->rect.planes[3].normal ); + info->head->rect.planes[3].dist = -square[1][1]; // tmax + + info->head->myrect = info->head->rect; + ChopFrag( info->head ); + + if( !info->head->winding || !info->head->mywinding ) + { + // empty + if( info->head->mywinding ) + FreeWinding( info->head->mywinding ); + if( info->head->winding ) + FreeWinding( info->head->winding ); + Mem_Free( info->head->edges ); + Mem_Free( info->head ); + info->head = NULL; + info->size = 0; + } + else + { + // prune edges + for( samplefragedge_t *e = info->head->edges; e < info->head->edges + info->head->numedges; e++ ) + { + if( e->nextfacenum == info->head->facenum ) + e->tried = true; + } + } + + while( info->size < info->maxsize ) + { + samplefrag_t *bestfrag; + samplefragedge_t *e, *bestedge; + samplefrag_t *f, *newfrag; + + if( !FindBestEdge( info, bestfrag, bestedge )) + break; + + newfrag = GrowSingleFrag( info, bestfrag, bestedge ); + bestedge->tried = true; + + if( newfrag ) + { + newfrag->next = info->head; + info->head = newfrag; + info->size++; + + + for( f = info->head; f != NULL; f = f->next ) + { + for( e = newfrag->edges; e < newfrag->edges + newfrag->numedges; e++ ) + { + if( e->nextfacenum == f->facenum ) + e->tried = true; + } + } + + for( f = info->head; f != NULL; f = f->next ) + { + for( e = f->edges; e < f->edges + f->numedges; e++ ) + { + if( e->nextfacenum == newfrag->facenum ) + e->tried = true; + } + } + } + } + + return info; +} + +static bool IsFragEmpty( samplefraginfo_t *fraginfo ) +{ + return (fraginfo->size == 0); +} + +static void DeleteSampleFrag( samplefraginfo_t *fraginfo ) +{ + while( fraginfo->head ) + { + samplefrag_t *f; + + f = fraginfo->head; + fraginfo->head = f->next; + + if( f->mywinding ) FreeWinding( f->mywinding ); + if( f->winding ) FreeWinding( f->winding ); + Mem_Free( f->edges ); + Mem_Free( f ); + } + Mem_Free( fraginfo ); +} + +static bool CalcSamplePoint( vec3_t point, vec3_t position, int *surface, const lightinfo_t *l, vec_t orig_s, vec_t orig_t, const vec_t square[2][2] ) +{ + bool found = false; + vec_t bests, bestt; + const dplane_t *faceplane; + samplefraginfo_t *fraginfo; + samplefrag_t *bestfrag; + vec_t best_dist; + vec3_t bestpos; + int facenum; + bool outside; + dface_t *face; + samplefrag_t *f; + + facenum = l->surfnum; + face = l->face; + faceplane = GetPlaneFromFace( face ); + fraginfo = CreateSampleFrag( facenum, orig_s, orig_t, square, 100 ); + + for( f = fraginfo->head; f != NULL; f = f->next ) + { + vec_t s, t, dist; + bool better; + vec3_t pos; + + if( !FindNearestPosition( f->facenum, f->mywinding, f->myorigin[0], f->myorigin[1], pos, &s, &t, &dist )) + continue; + + if( !found ) + better = true; + else if( fabs( dist - best_dist ) > ON_EPSILON * 2 ) + better = ( dist < best_dist ); + else if( f->noseam != bestfrag->noseam ) + better = (f->noseam && !bestfrag->noseam); + else better = (f->facenum < bestfrag->facenum); + + if( better ) + { + VectorCopy( pos, bestpos ); + best_dist = dist; + bestfrag = f; + found = true; + bests = s; + bestt = t; + } + } + + if( found ) + { + matrix3x4 worldtotex, textoworld; + vec3_t tex; + + TranslateWorldToTex( bestfrag->facenum, worldtotex ); + + if( !Matrix3x4_Invert_Full( textoworld, worldtotex )) + MsgDev( D_WARN, "Texture axis perpendicular to face\n" ); + + // adjust source point and store valid position + VectorSet( tex, bests, bestt, 0.0 ); + Matrix3x4_VectorTransform( textoworld, tex, point ); + VectorAdd( point, g_face_offset[bestfrag->facenum], point ); + VectorCopy( bestpos, position ); + *surface = bestfrag->facenum; + outside = false; + } + else + { + TexelSpaceToWorld( l, point, orig_s, orig_t ); + VectorMA( point, DEFAULT_HUNT_OFFSET, faceplane->normal, position ); + *surface = facenum; + outside = true; + } + + DeleteSampleFrag( fraginfo ); + + return outside; +} + +/* +================= +CalcPoints + +For each texture aligned grid point, back project onto the plane +to get the world xyz value of the sample point +================= +*/ +void CalcPoints( lightinfo_t *l ) +{ + vec_t starts, startt, us, ut; + int h = l->texsize[1] + 1; + int w = l->texsize[0] + 1; + int s, t, texture_step; + vec_t square[2][2]; + vec_t step; + + l->surfpt = (surfpt_t *)Mem_Alloc( w * h * sizeof( surfpt_t )); + texture_step = GetTextureStep( l->face ); + starts = (float)l->texmins[0] * texture_step; + startt = (float)l->texmins[1] * texture_step; + step = (float)texture_step; + l->numsurfpt = w * h; + + for( t = 0; t < h; t++ ) + { + for( s = 0; s < w; s++ ) + { + surfpt_t *surf = &l->surfpt[s+w*t]; + us = starts + s * step; + ut = startt + t * step; + square[0][0] = us - step; + square[0][1] = ut - step; + square[1][0] = us + step; + square[1][1] = ut + step; + surf->occluded = CalcSamplePoint( surf->point, surf->position, &surf->surface, l, us, ut, square ); + } + } +} + + +//============================================================== +facelight_t g_facelight[MAX_MAP_FACES]; +directlight_t *g_directlights; +int g_numdlights; + +/* +============= +GetLightType + +trying to determine light type by settings +============= +*/ +emittype_t GetLightType( entity_t *e ) +{ + const char *name = ValueForKey( e, "classname" ); + const char *target = ValueForKey( e, "target" ); + int style = IntForKey( e, "style" ); + + if( Q_strncmp( name, "light", 5 ) && !CheckKey( e, "_sunlight" )) + return emit_ignored; // not a light entity + +#ifdef HLRAD_PARANOIA_BUMP + if( style != 0 && style != LS_SKY ) + return emit_ignored; +#endif + if( CheckKey( e, "_surface" )) + return emit_ignored; // already handled + + // check for skylight or sunlight + if( BoolForKey( e, "_sky" ) || CheckKey( e, "_sunlight" ) || !Q_strcmp( name, "light_environment" )) + return emit_skylight; + + // check for spotlight + if( !Q_strcmp( name, "light_spot" )) + return emit_spotlight; + else if( target[0] || CheckKey( e, "mangle" )) + return emit_spotlight; + + // otherwise it's pointlight + return emit_point; +} + +/* +============= +ParseLightIntensity + +get light intensity +============= +*/ +int ParseLightIntensity( const char *pLight, vec3_t intensity, vec_t multiplier ) +{ + double r, g, b, scaler; + int argCnt; + + // scanf into doubles, then assign, so it is vec_t size independent + r = g = b = scaler = 0; + argCnt = sscanf( pLight, "%lf %lf %lf %lf", &r, &g, &b, &scaler ); + intensity[0] = (float)r; + + if( argCnt == 1 ) + { + // the R, G, B values are all equal. + intensity[1] = intensity[2] = (float)r; + } + else if( argCnt == 3 || argCnt == 4 ) + { + // save the other two G,B values. + intensity[1] = (float)g; + intensity[2] = (float)b; + + // did we also get an "intensity" scaler value too? + if( argCnt == 4 ) + { + // Scale the normalized 0-255 R,G,B values by the intensity scaler + VectorScale( intensity, scaler / multiplier, intensity ); + } + } + else + { + // quake default lightvalue + VectorFill( intensity, 300.0f ); + } + + return argCnt; +} + +/* +============= +ParseLightIntensity + +get light intensity and color +============= +*/ +void ParseLightIntensity( entity_t *e, directlight_t *dl ) +{ + double r, g, b, scaler; + char *pLight = NULL; + char *pColor = NULL; + int argCnt; + + if( CheckKey( e, "_sunlight" )) + pLight = ValueForKey( e, "_sunlight" ); + else if( CheckKey( e, "_light" )) + pLight = ValueForKey( e, "_light" ); + else pLight = ValueForKey( e, "light" ); + + argCnt = ParseLightIntensity( pLight, dl->intensity ); + + switch( argCnt ) + { + case 3: + case 4: + dl->falloff = falloff_valve; + break; + default: + dl->falloff = falloff_quake; + break; + } + + // quake light + if( argCnt <= 1 ) + { + scaler = dl->intensity[0]; + r = g = b = 0; + + if( CheckKey( e, "_sunlight_color" )) + { + pColor = ValueForKey( e, "_sunlight_color" ); + dl->radius = BOGUS_RANGE; // sunlight has infinite radius + } + else if( CheckKey( e, "_color" )) + { + pColor = ValueForKey( e, "_color" ); + dl->radius = scaler; + } + else + { + pColor = ValueForKey( e, "color" ); + dl->radius = scaler; + } + + if( pColor[0] && sscanf( pColor, "%lf %lf %lf", &r, &g, &b ) == 3 ) + { + dl->intensity[0] = r * (float)scaler; + dl->intensity[1] = g * (float)scaler; + dl->intensity[2] = b * (float)scaler; + } + } +} + +/* +============= +ParseLightDirection + +determine light direction +============= +*/ +void ParseLightDirection( entity_t *e, directlight_t *dl ) +{ + const char *target = ValueForKey( e, "target" ); + vec3_t vAngles; + + if( target[0] ) + { + // point towards target + entity_t *e2 = FindTargetEntity( target ); + vec3_t dest; + + if( e2 ) + { + GetVectorForKey( e2, "origin", dest ); + VectorSubtract( dest, dl->origin, dl->normal ); + VectorNormalize( dl->normal ); + } + else MsgDev( D_WARN, "%s[%i] has missing target %s\n", ValueForKey( e, "classname" ), e - g_entities, target ); + } + + // try angles + if( VectorIsNull( dl->normal )) + { + if( CheckKey( e, "mangle" )) + { + // Quake spotlight angles + GetVectorForKey( e, "mangle", vAngles ); + + dl->normal[0] = cos( DEG2RAD( vAngles[0] )) * cos( DEG2RAD( vAngles[1] )); + dl->normal[1] = sin( DEG2RAD( vAngles[0] )) * cos( DEG2RAD( vAngles[1] )); + dl->normal[2] = sin( DEG2RAD( vAngles[1] )); + } + else if( CheckKey( e, "_sunlight_mangle" )) + { + // Quake sunlight angles + GetVectorForKey( e, "_sunlight_mangle", vAngles ); + + dl->normal[0] = cos( DEG2RAD( vAngles[0] )) * cos( DEG2RAD( vAngles[1] )); + dl->normal[1] = sin( DEG2RAD( vAngles[0] )) * cos( DEG2RAD( vAngles[1] )); + dl->normal[2] = sin( DEG2RAD( vAngles[1] )); + } + else if( CheckKey( e, "_sun_mangle" )) + { + // Quake sunlight angles + GetVectorForKey( e, "_sun_mangle", vAngles ); + + dl->normal[0] = cos( DEG2RAD( vAngles[0] )) * cos( DEG2RAD( vAngles[1] )); + dl->normal[1] = sin( DEG2RAD( vAngles[0] )) * cos( DEG2RAD( vAngles[1] )); + dl->normal[2] = sin( DEG2RAD( vAngles[1] )); + } + else + { + vec_t angle = (float)FloatForKey( e, "angle" ); + + // Half-Life spotlight or sunlight angles + GetVectorForKey( e, "angles", vAngles ); + + if( angle == ANGLE_UP ) + { + VectorSet( dl->normal, 0.0, 0.0, 1.0 ); + } + else if( angle == ANGLE_DOWN ) + { + VectorSet( dl->normal, 0.0, 0.0, -1.0 ); + } + else + { + // if we don't have a specific "angle" use the "angles" YAW + if( !angle ) angle = vAngles[1]; + dl->normal[0] = (float)cos( DEG2RAD( angle )); + dl->normal[1] = (float)sin( DEG2RAD( angle )); + dl->normal[2] = 0; + } + + angle = FloatForKey( e, "pitch" ); + // if we don't have a specific "pitch" use the "angles" PITCH + if( !angle ) angle = vAngles[0]; + + dl->normal[0] *= (float)cos( DEG2RAD( angle )); + dl->normal[1] *= (float)cos( DEG2RAD( angle )); + dl->normal[2] = (float)sin( DEG2RAD( angle )); + } + } +} + +/* +============= +ParseLightAttenuation + +get attenuation value +============= +*/ +void ParseLightAttenuation( entity_t *e, directlight_t *dl ) +{ + if( CheckKey( e, "_fade" )) + dl->fade = FloatForKey( e, "_fade" ); + else if( CheckKey( e, "wait" )) + dl->fade = FloatForKey( e, "wait" ); + + // to prevent division by zero + if( dl->fade <= 0.0 ) dl->fade = 1.0; +} + +/* +============= +ParseLightFalloff + +get falloff formula +============= +*/ +void ParseLightFalloff( entity_t *e, directlight_t *dl ) +{ + + if( CheckKey( e, "_falloff" )) + { + // strange behavior... + dl->falloff = IntForKey( e, "_falloff" ); + if( dl->falloff == 1 ) + dl->falloff = falloff_inverse; + else dl->falloff = falloff_valve; + } + else if( CheckKey( e, "delay" )) + { + dl->falloff = IntForKey( e, "delay" ); + dl->falloff = bound( falloff_quake, dl->falloff, falloff_inverse2a ); + dl->lf_scale = LF_SCALE; + } + + // oterwise it's already set in ParseLightIntensity +} + +/* +============= +ParsePointLight +============= +*/ +void ParsePointLight( entity_t *e, directlight_t *dl ) +{ + ParseLightIntensity( e, dl ); + ParseLightAttenuation( e, dl ); + ParseLightFalloff( e, dl ); +} + +/* +============= +ParseSpotLight +============= +*/ +void ParseSpotLight( entity_t *e, directlight_t *dl ) +{ + ParseLightIntensity( e, dl ); + ParseLightDirection( e, dl ); + ParseLightAttenuation( e, dl ); + ParseLightFalloff( e, dl ); + + if( CheckKey( e, "mangle" )) + { + // Quake spotlights + dl->stopdot = FloatForKey( e, "_softangle" ); + dl->stopdot2 = FloatForKey( e, "angle" ); + if( !dl->stopdot ) dl->stopdot = 40; + if( !dl->stopdot2 ) dl->stopdot2 = dl->stopdot; + dl->stopdot2 = Q_max( dl->stopdot, dl->stopdot2 ); + dl->stopdot = (float)cos( DEG2RAD( dl->stopdot * 0.5 )); + dl->stopdot2 = (float)cos( DEG2RAD( dl->stopdot2 * 0.5 )); + } + else + { + // Half-Life spotlights + dl->stopdot = FloatForKey( e, "_cone" ); + dl->stopdot2 = FloatForKey( e, "_cone2" ); + if( !dl->stopdot ) dl->stopdot = 10; + if( !dl->stopdot2 ) dl->stopdot2 = dl->stopdot; + dl->stopdot2 = Q_max( dl->stopdot, dl->stopdot2 ); + dl->stopdot = (float)cos( DEG2RAD( dl->stopdot )); + dl->stopdot2 = (float)cos( DEG2RAD( dl->stopdot2 )); + } +} + +/* +============= +ParseSkyLight +============= +*/ +void ParseSkyLight( entity_t *e, directlight_t *dl ) +{ + char *pLight = NULL; + + ParseLightIntensity( e, dl ); + ParseLightDirection( e, dl ); + + // check for sky diffuse light + if( CheckKey( e, "_diffuse_light" )) + { + ParseLightIntensity( ValueForKey( e, "_diffuse_light" ), dl->diffuse_intensity ); + } + else if( CheckKey( e, "_sunlight2" )) + { + if( CheckKey( e, "_sunlight2_color" )) + pLight = va( "%s %s", ValueForKey( e, "_sunlight2_color" ), ValueForKey( e, "_sunlight2" )); + else pLight = ValueForKey( e, "_sunlight2" ); + ParseLightIntensity( pLight, dl->diffuse_intensity, 1.0 ); + } + + // FIXME: get support for diffuse_intensity2 and _sunlight3? + if( CheckKey( e, "_sunlight3" )) + { + } + + if( CheckKey( e, "_spread" )) + dl->sunspreadangle = FloatForKey( e, "_spread" ); + else if( CheckKey( e, "_sunlight_penumbra" )) + dl->sunspreadangle = FloatForKey( e, "_sunlight_penumbra" ); + + if( dl->sunspreadangle < 0.0 || dl->sunspreadangle > 180 ) + { + MsgDev( D_ERROR, "invalid spread angle '%s'. Defaulting to 0\n", ValueForKey( e, "_spread" )); + dl->sunspreadangle = 0.0; + } + + if( dl->sunspreadangle > 0.0 ) + { + vec_t testangle = dl->sunspreadangle; + vec_t totalw = 0, testdot; + int i, count; + + // we will later centralize all the normals we have collected. + if( dl->sunspreadangle < SUNSPREAD_THRESHOLD ) + testangle = SUNSPREAD_THRESHOLD; + + testdot = cos( DEG2RAD( testangle )); + + for( count = 0, i = 0; i < g_numskynormals[SUNSPREAD_SKYLEVEL]; i++ ) + { + vec_t *testnormal = g_skynormals[SUNSPREAD_SKYLEVEL][i]; + vec_t dot = DotProduct( dl->normal, testnormal ); + + if( dot >= testdot - NORMAL_EPSILON ) + { + // this is not the right formula when dl->sunspreadangle < SUNSPREAD_THRESHOLD, + // but it gives almost the same result as the right one. + totalw += Q_max( 0, dot - testdot ) * g_skynormalsizes[SUNSPREAD_SKYLEVEL][i]; + count++; + } + } + + if( count <= 10 || totalw <= NORMAL_EPSILON ) + COM_FatalError( "CreateDirectLights: internal error\n" ); + + dl->sunnormals = (vec3_t *)Mem_Alloc( count * sizeof( vec3_t )); + dl->sunnormalweights = (vec_t *)Mem_Alloc( count * sizeof( vec_t )); + dl->numsunnormals = count; + + for( count = 0, i = 0; i < g_numskynormals[SUNSPREAD_SKYLEVEL]; i++ ) + { + vec_t *testnormal = g_skynormals[SUNSPREAD_SKYLEVEL][i]; + vec_t dot = DotProduct( dl->normal, testnormal ); + + if( dot >= testdot - NORMAL_EPSILON ) + { + if( count >= dl->numsunnormals ) + COM_FatalError( "CreateDirectLights: internal error\n" ); + + VectorCopy( testnormal, dl->sunnormals[count] ); + dl->sunnormalweights[count] = Q_max( 0, dot - testdot ); + dl->sunnormalweights[count] *= g_skynormalsizes[SUNSPREAD_SKYLEVEL][i] / totalw; + count++; + } + } + + if( count != dl->numsunnormals ) + COM_FatalError( "CreateDirectLights: internal error\n" ); + + if( dl->sunspreadangle < SUNSPREAD_THRESHOLD ) + { + for( i = 0; i < dl->numsunnormals; i++ ) + { + vec_t iscale = 1.0 / DotProduct( dl->sunnormals[i], dl->normal ); + vec_t angle = dl->sunspreadangle / SUNSPREAD_THRESHOLD; + vec3_t tmp; + + VectorScale( dl->sunnormals[i], iscale, tmp ); + VectorSubtract( tmp, dl->normal, tmp ); + VectorMA( dl->normal, angle, tmp, dl->sunnormals[i] ); + VectorNormalize( dl->sunnormals[i] ); + } + } + } + else + { + dl->sunnormals = (vec3_t *)Mem_Alloc( sizeof( vec3_t )); + dl->sunnormalweights = (vec_t *)Mem_Alloc( sizeof( vec_t )); + VectorCopy( dl->normal, dl->sunnormals[0] ); + dl->sunnormalweights[0] = 1.0; + dl->numsunnormals = 1; + } +} + +/* +============= +BuildVisForDLight + +create visibility cache for dlight +============= +*/ +int BuildVisForDLight( directlight_t *dl ) +{ + int leafnum; + + if( dl->type == emit_skylight ) + { + // all leafs that contain skyface should be added to sun visibility + for( leafnum = 0; leafnum < g_numvisleafs; leafnum++ ) + { + uint firstmarkface = g_dleafs[leafnum + 1].firstmarksurface; + + for( int markface = 0; markface < g_dleafs[leafnum + 1].nummarksurfaces; markface++ ) + { + dface_t *surf = &g_dfaces[g_dmarksurfaces[firstmarkface + markface]]; + dtexinfo_t *tex = g_texinfo + surf->texinfo; + + if( FBitSet( tex->flags, TEX_SPECIAL )) + { + if( !Q_strnicmp( GetTextureByTexinfo( surf->texinfo ), "sky", 3 )) + { + MergeDLightVis( dl, leafnum + 1 ); + break; // no reason to check all faces, go to next leaf + } + } + } + } + + // technically light_environment is outside of world + return -1; + } + else + { + leafnum = PointInLeaf( dl->origin ) - g_dleafs; + SetDLightVis( dl, leafnum ); + + return leafnum; + } +} + +/* +============= +ParseLightEntity +============= +*/ +bool ParseLightEntity( entity_t *e, directlight_t *dl ) +{ + int leafnum; + vec_t l1; + + // all the lights has the origin + GetVectorForKey( e, "origin", dl->origin ); + + switch( dl->type ) + { + case emit_point: + ParsePointLight( e, dl ); + break; + case emit_spotlight: + ParseSpotLight( e, dl ); + break; + case emit_skylight: + ParseSkyLight( e, dl ); + break; + default: + // unknown or unsupported light type + Mem_Free( dl ); + return false; + } + + if( VectorMax( dl->intensity ) <= 1 ) + { + Mem_Free( dl ); + return false; // bad light value? + } + + if( dl->type != emit_skylight && dl->falloff == falloff_valve ) + { + l1 = VectorMax( dl->intensity ); + l1 = l1 * l1 / 10; // Valve weird divider + VectorScale( dl->intensity, l1, dl->intensity ); + dl->radius = l1; + } + + dl->facenum = -1; // no texinfo for point and spotlights + dl->modelnum = 0; // worldmodel for pointlights +#ifdef HLRAD_PARANOIA_BUMP + dl->flags = IntForKey( e, "spawnflags" ); // buz +#else + dl->style = abs( IntForKey( e, "style" )); +#endif + leafnum = BuildVisForDLight( dl ); + dl->topatch = g_fastmode; + dl->next = g_directlights; + g_directlights = dl; + g_numdlights++; + + // copy worldlight params and set unique number + InitWorldLightFromDlight( dl, leafnum ); + + return true; +} + +/* +============= +CreateDirectLights +============= +*/ +void CreateDirectLights( void ) +{ + directlight_t *dl; + patch_t *p; + int i; + + g_numdlights = 0; + + // surfaces + for( i = 0, p = g_patches; i < g_num_patches; i++, p++ ) + { + vec_t value; + + if( !VectorIsNull( p->reflectivity )) + value = DotProduct( p->baselight, p->reflectivity ) / 3.0; + else value = VectorAvg( p->baselight ); + + if( value > 0.0 ) + { + dl = (directlight_t *)Mem_Alloc( sizeof( directlight_t )); + g_numdlights++; + + VectorCopy( p->origin, dl->origin ); + dl->type = emit_surface; + dl->style = p->emitstyle; + dl->lf_scale = 1.0f; + + VectorCopy( GetPlaneFromFace( p->faceNumber )->normal, dl->normal ); + VectorCopy( p->baselight, dl->intensity ); + VectorScale( dl->intensity, p->area, dl->intensity ); + VectorScale( dl->intensity, p->exposure, dl->intensity ); + dl->falloff = falloff_valve; + dl->radius = p->area * 1.74; + dl->facenum = p->faceNumber; + dl->modelnum = p->modelnum; + dl->lightnum = p->lightnum; // already set + dl->next = g_directlights; + g_directlights = dl; + + if( !VectorIsNull( p->reflectivity )) + { + VectorScale( dl->intensity, 0.5 / M_PI, dl->intensity ); + VectorMultiply( dl->intensity, p->reflectivity, dl->intensity ); + } + else + { + VectorScale( dl->intensity, DIRECT_SCALE, dl->intensity ); + } + + if( !FBitSet( p->flags, PATCH_EMITLIGHT ) || g_fastmode ) + dl->topatch = true; + dl->patch_emitter_range = p->emitter_range; + dl->patch_area = p->area; + BuildVisForDLight( dl ); + dl->fade = p->fade; + dl->patch = p; + } + } + + // entities + for( i = 0; i < g_numentities; i++ ) + { + emittype_t type; + entity_t *e; + + e = &g_entities[i]; + type = GetLightType( e ); + if( type == emit_ignored ) + continue; // not a light entity + + dl = (directlight_t *)Mem_Alloc( sizeof( directlight_t )); + dl->lf_scale = 1.0f; + dl->type = type; + + if( !ParseLightEntity( e, dl )) + continue; + } + + if( g_numdlights <= 0 ) + COM_FatalError( "map %s without any lights\n", source ); + MsgDev( D_INFO, "%i direct lights\n", g_numdlights ); + + if( g_onlylights ) return; + +#ifdef HLRAD_COMPUTE_VISLIGHTMATRIX + // rows: facenum -> visible light bits like a normal vis info + g_vislightdatasize = g_numfaces * ((g_numworldlights + 7) / 8); + if( g_dvislightdata ) Mem_Free( g_dvislightdata ); + g_dvislightdata = (byte *)Mem_Alloc( g_vislightdatasize ); +#endif +} + +/* +============= +DeleteDirectLights +============= +*/ +void DeleteDirectLights( void ) +{ + directlight_t *dl, *next; + + for( dl = g_directlights; dl; dl = next ) + { + next = dl->next; + Mem_Free( dl->pvs ); + Mem_Free( dl ); + } +} + +/* +============= +GetLightDenominator + +calc fallof denominator +============= +*/ +vec_t GetLightDenominator( const directlight_t *dl, vec_t dist ) +{ + vec_t lf_scale = Q_max( 1.0, dl->lf_scale ); + vec_t value = dist * dl->fade; + + switch( dl->falloff ) + { + case falloff_quake: + // Quake attenuation. + if( dl->radius - value > 0.0f ) + return 1.0 / (1.0 - value * ( 1.0 / dl->radius )); + return 0.0f; + case falloff_inverse: + return value / lf_scale; + break; + case falloff_inverse2a: + value += lf_scale; + case falloff_inverse2: + return value * value / (lf_scale * lf_scale); + case falloff_valve: + // Valve attenuation + return value * value; + default: + // No attenuation + return 1.0; + } +} + +/* +============= +GatherSampleLight +============= +*/ +void GatherSampleLight( int threadnum, int fn, const vec3_t pos, int leafnum, const vec3_t n, +vec3_t *s_light, vec3_t *s_dir, vec_t *s_occ, byte *styles, byte *vislight, bool topatch, entity_t *ignoreent ) +{ + int skylevel = (fn != -1) ? SKYLEVEL_SOFTSKYON : SKYLEVEL_SOFTSKYOFF; + vec3_t add, delta, add_one; + vec3_t testline_origin; + int style_index = 0; + vec3_t add_direction; + float dist, ratio; + float dot, dot2; + vec3_t direction; + + for( directlight_t *dl = g_directlights; dl != NULL; dl = dl->next ) + { + // check light visibility + if( !leafnum || !dl->pvs || !CHECKVISBIT( dl->pvs, leafnum - 1 )) + continue; + + // skylights work fundamentally differently than normal lights + if( dl->type == emit_skylight ) + { + VectorClear( add_direction ); + VectorClear( add ); + + // add sun light + if( topatch == dl->topatch ) + { + // loop over the normals + for( int i = 0; i < dl->numsunnormals; i++ ) + { + // make sure the angle is okay + dot = -DotProduct( n, dl->sunnormals[i] ); + if( dot <= NORMAL_EPSILON ) + continue; + + // search back to see if we can hit a sky brush + VectorScale( dl->sunnormals[i], -BOGUS_RANGE, delta ); + VectorAdd( pos, delta, delta ); + + if( TestLine( threadnum, pos, delta, dl->topatch, ignoreent ) != CONTENTS_SKY ) + continue; // occluded + + VectorCopy( dl->sunnormals[i], direction ); + VectorScale( dl->intensity, dot * dl->sunnormalweights[i], add_one ); + // add to the contribution of this light + VectorAdd( add, add_one, add ); + vec_t avg = VectorAvg( add_one ); + VectorMA( add_direction, avg, direction, add_direction ); + } + } + + if( topatch && g_indirect_sun > 0.0 ) + { + vec3_t *skynormals = g_skynormals[skylevel]; + vec_t *skyweights = g_skynormalsizes[skylevel]; + vec3_t sky_intensity; + + // loop over the normals + for( int i = 0; i < g_numskynormals[skylevel]; i++ ) + { + // make sure the angle is okay + dot = -DotProduct( n, skynormals[i] ); + if( dot <= NORMAL_EPSILON ) + continue; + + // search back to see if we can hit a sky brush + VectorScale( skynormals[i], -BOGUS_RANGE, delta ); + VectorAdd( pos, delta, delta ); + + if( TestLine( threadnum, pos, delta, true, ignoreent ) != CONTENTS_SKY ) + continue; // occluded + + // how far this piece of sky has deviated from the sun + vec_t factor = (( 1.0 - DotProduct( dl->normal, skynormals[i] )) / 2.0f ); + + factor = bound( 0.0, factor, 1.0 ); + VectorScale( dl->diffuse_intensity, 1.0 - factor, sky_intensity ); + VectorMA( sky_intensity, factor, dl->intensity, sky_intensity ); + VectorCopy( skynormals[i], direction ); + + VectorScale( sky_intensity, skyweights[i] * g_indirect_sun * 0.5, sky_intensity ); + VectorScale( sky_intensity, dot, add_one ); + // add to the contribution of this light + VectorAdd( add, add_one, add ); + vec_t avg = VectorAvg( add_one ); + VectorMA( add_direction, avg, direction, add_direction ); + } + } + } + else + { + bool light_behind_surface = false; + vec_t range; + + if( topatch != dl->topatch ) + continue; + + VectorCopy( dl->origin, testline_origin ); + VectorSubtract( dl->origin, pos, delta ); + + if( dl->type == emit_surface ) // move emitter back to its plane + VectorMA( delta, -DEFAULT_HUNT_OFFSET * 0.5, dl->normal, delta ); + + dist = VectorNormalize( delta ); + dot = DotProduct( delta, n ); + dist = Q_max( dist, 1.0 ); + + // save some compile time + if( dl->type == emit_point && dl->falloff == falloff_quake && dist > dl->radius && topatch == false ) + continue; // don't bother with light too far away + + // variable power falloff (1 = inverse linear, 2 = inverse square) + vec_t denominator = GetLightDenominator( dl, dist ); + if( denominator <= 0.0 ) continue; + VectorNegate( delta, direction ); + + if(( -dot ) > 0 ) + { + // reflect the direction back (this is not ideal!) + VectorMA( direction, -(-dot) * 2.0f, n, direction ); + } + + switch( dl->type ) + { + case emit_point: + if( dot <= NORMAL_EPSILON ) + continue; + ratio = dot / denominator; + VectorScale( dl->intensity, ratio, add ); + break; + case emit_surface: + if( dot <= NORMAL_EPSILON ) + light_behind_surface = true; + dot2 = -DotProduct( delta, dl->normal ); + if( dot2 * dist <= MINIMUM_PATCH_DISTANCE ) + continue; + range = dl->patch_emitter_range; + ratio = dot * dot2 / denominator; + + // analogous to the one in MakeScales + // 0.4f is tested to be able to fully eliminate bright spots + if( ratio * dl->patch_area > 0.4f ) + ratio = 0.4f / dl->patch_area; + + if( dist < range - ON_EPSILON ) + { + vec_t sightarea; + vec_t ratio2, frac; + + // do things slow + if( light_behind_surface ) + { + ratio = 0.0; + dot = 0.0; + } + + GetAlternateOrigin( pos, n, dl->patch, testline_origin ); + sightarea = CalcSightArea( pos, n, dl->patch->winding, dl->patch->emitter_skylevel ); + + frac = dist / range; + frac = ( frac - 0.5 ) * 2.0; // make a smooth transition between the two methods + frac = bound( 0.0, frac, 1.0 ); + // because dl->patch_area has been multiplied into dl->intensity + ratio2 = (sightarea / dl->patch_area); + ratio = frac * ratio + (1.0 - frac) * ratio2; + } + else if( light_behind_surface ) + { + continue; + } + VectorScale( dl->intensity, ratio, add ); + break; + case emit_spotlight: + if( dot <= NORMAL_EPSILON ) + continue; + dot2 = -DotProduct( delta, dl->normal ); + if( dot2 <= dl->stopdot2 ) + continue; // outside light cone + ratio = dot * dot2 / denominator; + if( dot2 <= dl->stopdot ) + ratio *= (dot2 - dl->stopdot2) / (dl->stopdot - dl->stopdot2); + VectorScale( dl->intensity, ratio, add ); + break; + default: + COM_FatalError( "bad dl->type\n" ); + break; + } + + VectorCopy( direction, add_direction ); // will scale it later + } + + if( topatch == false ) + { + vec_t dirtSample = GatherSampleDirt( threadnum, fn, pos, n, ignoreent ); + VectorScale( add, dirtSample, add ); + } + + // g-cont. when lightmap will be turned from float to byte some lightvalue will be unreachable + // (1.0f / 255.0f) ~= 0.003, and EQUAL_EPSILON is = 0.004 * 255 = 1.02, minimal brightness of lightmap + if( VectorMax( add ) > EQUAL_EPSILON ) + { + if( dl->type != emit_skylight && TestLine( threadnum, pos, testline_origin, dl->topatch, ignoreent ) != CONTENTS_EMPTY ) + continue; // occluded +#ifdef HLRAD_PARANOIA_BUMP + // hardcoded style representation + if( !FBitSet( dl->flags, LIGHTFLAG_NOT_NORMAL )) + { + VectorAdd( s_light[STYLE_ORIGINAL_LIGHT], add, s_light[STYLE_ORIGINAL_LIGHT] ); + styles[0] = STYLE_ORIGINAL_LIGHT; // used + } + + if( !FBitSet( dl->flags, LIGHTFLAG_NOT_RENDERER )) + { + VectorAdd( s_light[STYLE_BUMPED_LIGHT], add, s_light[STYLE_BUMPED_LIGHT] ); + styles[1] = STYLE_BUMPED_LIGHT; // used + } +#else + for( style_index = 0; style_index < MAXLIGHTMAPS; style_index++ ) + { + if( styles[style_index] == dl->style || styles[style_index] == 255 ) + break; + } + + if( style_index == MAXLIGHTMAPS ) + { + if( topatch ) g_overflowed_styles_onpatch[threadnum]++; + else g_overflowed_styles_onface[threadnum]++; + continue; + } + + // allocate a new one + if( styles[style_index] == 255 ) + styles[style_index] = dl->style; + + VectorAdd( s_light[style_index], add, s_light[style_index] ); +#endif + if( topatch == false ) + g_lighted_luxels[threadnum]++; + + if( s_dir ) + { +#ifdef HLRAD_PARANOIA_BUMP + // buz: add intensity to lightdir vector + // delta must contain direction to light + if( !FBitSet( dl->flags, LIGHTFLAG_NOT_RENDERER )) + { + if( dl->type != emit_skylight ) + { + vec_t maxlight = VectorMaximum( add ); + VectorScale( add_direction, maxlight, add_direction ); + } + VectorAdd( s_dir[STYLE_BUMPED_LIGHT], add_direction, s_dir[STYLE_BUMPED_LIGHT] ); + } +#else + if( dl->type != emit_skylight ) + { + vec_t avg = VectorAvg( add ); + VectorScale( add_direction, avg, add_direction ); + } + + VectorAdd( s_dir[style_index], add_direction, s_dir[style_index] ); +#endif + } +#ifdef HLRAD_SHADOWMAPPING + if( s_occ ) s_occ[style_index] = 1.0f; +#endif +#ifdef HLRAD_COMPUTE_VISLIGHTMATRIX + // no reason to set it again + if( vislight != NULL && !CHECKVISBIT( vislight, dl->lightnum )) + { + ThreadLock(); + SETVISBIT( vislight, dl->lightnum ); + ThreadUnlock(); + } +#endif + } + } +} + +// ===================================================================================== +// AddSampleToPatch +// Take the sample's collected light and add it back into the apropriate patch for the radiosity pass. +// ===================================================================================== +static void AddSamplesToPatches( int threadnum, const sample_t *samples, const byte *styles, int facenum, const lightinfo_t *l ) +{ + int numtexwindings = 0; + winding_t **texwindings; + int texture_step; + int i, j, m, k; + patch_t *p; + + for( p = g_face_patches[facenum]; p != NULL; p = p->next ) + numtexwindings++; + + // create array of pointers + texwindings = (winding_t **)Mem_Alloc( numtexwindings * sizeof( winding_t* )); + texture_step = GetTextureStep( &g_dfaces[facenum] ); + + // translate world winding into winding in s,t plane + for( j = 0, p = g_face_patches[facenum]; j < numtexwindings && p != NULL; j++, p = p->next ) + { + winding_t *w = AllocWinding( p->winding->numpoints ); + + w->numpoints = p->winding->numpoints; + + for( int x = 0; x < w->numpoints; x++ ) + { + vec_t s, t; + WorldToTexelSpace( l, p->winding->p[x], s, t ); + VectorSet( w->p[x], s, t, 0.0 ); + } + + RemoveColinearPointsEpsilon( w, ON_EPSILON ); + texwindings[j] = w; + } + + for( i = 0; i < l->numsurfpt; i++ ) + { + dplane_t clipplanes[4]; + vec_t s_vec, t_vec; + + s_vec = l->texmins[0] * texture_step + ( i % (l->texsize[0] + 1 )) * texture_step; + t_vec = l->texmins[1] * texture_step + ( i / (l->texsize[0] + 1 )) * texture_step; + + // prepare clip planes (in 2D) + VectorSet( clipplanes[0].normal, 1.0, 0.0, 0.0 ); + clipplanes[0].dist = (s_vec - 0.5 * texture_step); + VectorSet( clipplanes[1].normal,-1.0, 0.0, 0.0 ); + clipplanes[1].dist = -(s_vec + 0.5 * texture_step); + VectorSet( clipplanes[2].normal, 0.0, 1.0, 0.0 ); + clipplanes[2].dist = (t_vec - 0.5 * texture_step); + VectorSet( clipplanes[3].normal, 0.0,-1.0, 0.0 ); + clipplanes[3].dist = -(t_vec + 0.5 * texture_step); + + // clip each patch + for( j = 0, p = g_face_patches[facenum]; j < numtexwindings && p != NULL; j++, p = p->next ) + { + winding_t *w = CopyWinding( texwindings[j] ); + + for( k = 0; k < 4 && w != NULL; k++ ) + { + ChopWindingInPlace( &w, clipplanes[k].normal, clipplanes[k].dist, ON_EPSILON, false ); + } + + if( w != NULL ) + { + // add sample to patch + vec_t area = WindingArea( w ) / (texture_step * texture_step); + const sample_t *s = &samples[i]; + + for( m = 0; m < MAXLIGHTMAPS && ( styles[m] != 255 ); m++ ) + { + if( VectorMax( s->light[m] ) < EQUAL_EPSILON ) + continue; + + for( k = 0; k < MAXLIGHTMAPS && p->totalstyle[k] != 255; k++ ) + { + if( p->totalstyle[k] == styles[m] ) + break; + } + + if( k < MAXLIGHTMAPS ) + { + if( p->totalstyle[k] == 255 ) + p->totalstyle[k] = styles[m]; + + // add the sample to the patch + VectorMA( p->samplelight[k], area, s->light[m], p->samplelight[k] ); +#ifdef HLRAD_DELUXEMAPPING + VectorMA( p->samplelight_dir[k], area, s->deluxe[m], p->samplelight_dir[k] ); +#endif + p->samples[k] += area; + } + else + { + g_overflowed_styles_onpatch[threadnum]++; + } + } + + FreeWinding( w ); + } + } + } + + for( j = 0; j < numtexwindings; j++ ) + FreeWinding( texwindings[j] ); + Mem_Free( texwindings ); +} + +static void CalcLightmap( int thread, lightinfo_t *l, facelight_t *fl ) +{ + vec_t texture_step = GetTextureStep( &g_dfaces[l->surfnum] ); + vec_t density = (vec_t)l->lmcache_density; + int w = l->texsize[0] + 1; + int h = l->texsize[1] + 1; + byte *vislight = NULL; + dface_t *f = l->face; + vec_t square[2][2]; + int i, j; + + // allocate light samples + fl->samples = (sample_t *)Mem_Alloc( l->numsurfpt * sizeof( sample_t )); + fl->numsamples = l->numsurfpt; + + // stats + g_direct_luxels[thread] += fl->numsamples; + +#ifdef HLRAD_COMPUTE_VISLIGHTMATRIX + vislight = g_dvislightdata + l->surfnum * ((g_numworldlights + 7) / 8); +#endif + // copy surf points from lightinfo with offset 0,0 + for( i = 0; i < fl->numsamples; i++ ) + { + VectorCopy( l->surfpt[i].point, fl->samples[i].pos ); + fl->samples[i].occluded = l->surfpt[i].occluded; + fl->samples[i].surface = l->surfpt[i].surface; + } + + // for each sample whose light we need to calculate + for( i = 0; i < l->lmcachewidth * l->lmcacheheight; i++ ) + { + vec_t s, t, s_vec, t_vec; + int nearest_s, nearest_t; + vec3_t spot, surfpt; + vec3_t pointnormal; + int surface; + bool blocked; + + // prepare input parameter and output parameter + s = ((i % l->lmcachewidth) - l->lmcache_offset) / (vec_t)l->lmcache_density; + t = ((i / l->lmcachewidth) - l->lmcache_offset) / (vec_t)l->lmcache_density; + s_vec = l->texmins[0] * texture_step + s * texture_step; + t_vec = l->texmins[1] * texture_step + t * texture_step; + nearest_s = Q_max( 0, Q_min((int)floor( s + 0.5 ), l->texsize[0] )); + nearest_t = Q_max( 0, Q_min((int)floor( t + 0.5 ), l->texsize[1] )); + square[0][0] = l->texmins[0] * texture_step + ceil( s - (l->lmcache_side + 0.5) / density) * texture_step - texture_step; + square[0][1] = l->texmins[1] * texture_step + ceil( t - (l->lmcache_side + 0.5) / density) * texture_step - texture_step; + square[1][0] = l->texmins[0] * texture_step + floor( s + (l->lmcache_side + 0.5) / density) * texture_step + texture_step; + square[1][1] = l->texmins[1] * texture_step + floor( t + (l->lmcache_side + 0.5) / density) * texture_step + texture_step; + + // find world's position for the sample + blocked = false; + + if( CalcSamplePoint( surfpt, spot, &surface, l, s_vec, t_vec, square )) + { + j = nearest_s + w * nearest_t; + + if( l->surfpt[j].occluded ) + { + blocked = true; + } + else + { + // the area this light sample has effect on is completely covered by solid, so take whatever valid position. + VectorCopy( l->surfpt[j].point, surfpt ); + VectorCopy( l->surfpt[j].position, spot ); + surface = l->surfpt[j].surface; + } + } + + // calculate normal for the sample + GetPhongNormal( surface, surfpt, pointnormal ); +#ifdef HLRAD_DELUXEMAPPING + VectorCopy( pointnormal, l->normals[i] ); +#endif + if( blocked ) continue; + + // calculate visibility for the sample + int leaf = PointInLeaf( spot ) - g_dleafs; + + // gather light +#if defined( HLRAD_DELUXEMAPPING ) && defined( HLRAD_SHADOWMAPPING ) + GatherSampleLight( thread, l->surfnum, spot, leaf, pointnormal, l->light[i], l->deluxe[i], l->shadow[i], f->styles, vislight, 0 ); +#elif defined( HLRAD_DELUXEMAPPING ) + GatherSampleLight( thread, l->surfnum, spot, leaf, pointnormal, l->light[i], l->deluxe[i], NULL, f->styles, vislight, 0 ); +#else + GatherSampleLight( thread, l->surfnum, spot, leaf, pointnormal, l->light[i], NULL, NULL, f->styles, vislight, 0 ); +#endif + } + + for( i = 0; i < fl->numsamples; i++ ) + { +#ifdef HLRAD_DELUXEMAPPING + vec_t weighting_correction; + vec3_t centernormal; +#endif + int s_center, t_center; + vec_t weighting, subsamples; + int s, t, pos; + vec_t sizehalf; + + s_center = (i % w) * l->lmcache_density + l->lmcache_offset; + t_center = (i / w) * l->lmcache_density + l->lmcache_offset; + sizehalf = 0.5 * g_blur * l->lmcache_density; + subsamples = 0.0; +#ifdef HLRAD_DELUXEMAPPING + VectorCopy( l->normals[s_center + l->lmcachewidth * t_center], centernormal ); +#endif + for( s = s_center - l->lmcache_side; s <= s_center + l->lmcache_side; s++ ) + { + for( t = t_center - l->lmcache_side; t <= t_center + l->lmcache_side; t++ ) + { + weighting = (Q_min( 0.5, sizehalf - ( s - s_center )) - Q_max( -0.5, -sizehalf - ( s - s_center ))); + weighting *=(Q_min( 0.5, sizehalf - ( t - t_center )) - Q_max( -0.5, -sizehalf - ( t - t_center ))); + + pos = s + l->lmcachewidth * t; +#ifdef HLRAD_DELUXEMAPPING + // when blur distance (g_blur) is large, the subsample can be very far from the original lightmap sample + // in some cases such as a thin cylinder, the subsample can even grow into the opposite side + // as a result, when exposed to a directional light, the light on the cylinder may "leak" into + // the opposite dark side this correction limits the effect of blur distance when the normal changes very fast + // this correction will not break the smoothness that HLRAD_GROWSAMPLE ensures + weighting_correction = DotProduct( l->normals[pos], centernormal ); + weighting_correction = (weighting_correction > 0) ? weighting_correction * weighting_correction : 0; + weighting = weighting * weighting_correction; +#endif + for( j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ ) + { + VectorMA( fl->samples[i].light[j], weighting, l->light[pos][j], fl->samples[i].light[j] ); +#ifdef HLRAD_DELUXEMAPPING + VectorMA( fl->samples[i].deluxe[j], weighting, l->deluxe[pos][j], fl->samples[i].deluxe[j] ); +#ifdef HLRAD_SHADOWMAPPING + fl->samples[i].shadow[j] += l->shadow[pos][j] * weighting; +#endif +#endif + } + subsamples += weighting; + } + } + + if( subsamples > NORMAL_EPSILON ) + { +#ifdef HLRAD_DELUXEMAPPING + VectorCopy( centernormal, fl->samples[i].normal ); +#endif + for( j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ ) + { + VectorScale( fl->samples[i].light[j], (1.0 / subsamples), fl->samples[i].light[j] ); +#ifdef HLRAD_DELUXEMAPPING + VectorScale( fl->samples[i].deluxe[j], (1.0 / subsamples), fl->samples[i].deluxe[j] ); +#ifdef HLRAD_SHADOWMAPPING + fl->samples[i].shadow[j] *= (1.0 / subsamples); +#endif +#endif + } + } + } + +#ifdef HLRAD_DELUXEMAPPING +#ifdef HLRAD_SHADOWMAPPING + // multiply light by shadow to prevent blur artifacts + for( i = 0; i < fl->numsamples; i++ ) + { + for( j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ ) + { +// VectorScale( fl->samples[i].light[j], fl->samples[i].shadow[j], fl->samples[i].light[j] ); +// VectorScale( fl->samples[i].deluxe[j], fl->samples[i].shadow[j], fl->samples[i].deluxe[j] ); + } + } + + // output occlusion shouldn't be blured + for( i = 0; i < fl->numsamples; i++ ) + { + for( j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ ) + { + int s = (i % w) + l->lmcache_side; + int t = (i / w) + l->lmcache_side; + int pos = s + l->lmcachewidth * t; + fl->samples[i].shadow[j] = l->shadow[pos][j]; + } + } +#endif +#endif + + Mem_Free( l->light ); +#ifdef HLRAD_DELUXEMAPPING + Mem_Free( l->normals ); + Mem_Free( l->deluxe ); +#ifdef HLRAD_SHADOWMAPPING + Mem_Free( l->shadow ); +#endif +#endif +} + +/* +============= +InitLightinfo +============= +*/ +void InitLightinfo( lightinfo_t *l, int facenum ) +{ + dface_t *f; + + f = &g_dfaces[facenum]; + + memset( l, 0, sizeof( *l )); + l->plane = GetPlaneFromFace( f ); + l->surfnum = facenum; + l->face = f; + + CalcFaceVectors( l ); + CalcFaceExtents( l ); +} + +/* +============= +BuildFaceLights +============= +*/ +void BuildFaceLights( int facenum, int thread ) +{ + facelight_t *fl = &g_facelight[facenum]; + vec3_t normal; + int i, j; + patch_t *p; + dface_t *f; + sample_t *s; + lightinfo_t l; + vec3_t v; + + f = &g_dfaces[facenum]; + + // some surfaces don't need lightmaps + f->lightofs = -1; + for( j = 0; j < MAXLIGHTMAPS; j++ ) + f->styles[j] = 255; + + if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL )) + return; // non-lit texture + + if( g_face_patches[facenum] && g_face_patches[facenum]->emitstyle ) + f->styles[0] = g_face_patches[facenum]->emitstyle; + + InitLightinfo( &l, facenum ); + CalcPoints( &l ); + CalcLightmap( thread, &l, fl ); + VectorCopy( l.plane->normal, normal ); + + AddSamplesToPatches( thread, fl->samples, f->styles, facenum, &l ); + + // average up the direct light on each patch for radiosity + for( p = g_face_patches[facenum]; p != NULL; p = p->next ) + { + ASSERT( p->faceNumber == facenum ); + + for( int i = 0; i < MAXLIGHTMAPS && p->totalstyle[i] != 255; i++ ) + { + // g-cont. when lightmap will be turned from float to byte some lightvalue will be unreachable + // (1.0f / 255.0f) ~= 0.003, and EQUAL_EPSILON is = 0.004 * 255 = 1.02, minimal brightness of lightmap + if( p->samples[i] <= EQUAL_EPSILON ) + p->samples[i] = 0.0; + + if( p->samples[i] != 0.0 ) + { + vec_t iscale = ( 1.0f / p->samples[i] ); + + VectorScale( p->samplelight[i], iscale, v ); + VectorAdd( p->directlight[i], v, p->directlight[i] ); + VectorAdd( p->totallight[i], v, p->totallight[i] ); +#ifdef HLRAD_DELUXEMAPPING + VectorScale( p->samplelight_dir[i], iscale, v ); + VectorAdd( p->directlight_dir[i], v, p->directlight_dir[i] ); + VectorAdd( p->totallight_dir[i], v, p->totallight_dir[i] ); +#endif + } + } + } + + for( p = g_face_patches[facenum]; p != NULL; p = p->next ) + { + int leafnum = p->leafnum; +#ifdef HLRAD_DELUXEMAPPING + GatherSampleLight( thread, l.surfnum, p->origin, leafnum, normal, p->totallight, p->totallight_dir, NULL, p->totalstyle, NULL, 1 ); +#else + GatherSampleLight( thread, l.surfnum, p->origin, leafnum, normal, p->totallight, NULL, NULL, p->totalstyle, NULL, 1 ); +#endif + } + + Mem_Free( l.surfpt ); + + // add an ambient term if desired + if( g_ambient[0] || g_ambient[1] || g_ambient[2] ) + { + for( j = 0; j < MAXLIGHTMAPS && f->styles[j] == 255; j++ ); + if( j == MAXLIGHTMAPS ) f->styles[0] = 0; // adding style + + for( j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ ) + { + if( f->styles[j] == 0 ) + { + s = fl->samples; + for( i = 0; i < fl->numsamples; i++, s++ ) + { + VectorAdd( s->light[j], g_ambient, s->light[j] ); +#ifdef HLRAD_DELUXEMAPPING + vec_t avg = VectorAvg( g_ambient ); + VectorMA( s->deluxe[j], -DIFFUSE_DIRECTION_SCALE * avg, s->normal, s->deluxe[j] ); +#endif + } + break; + } + } + + } + + // light from dlight_threshold and above is sent out, but the + // texture itself should still be full bright + if( g_face_patches[facenum] ) + { + for( j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ ) + { + if( f->styles[j] == g_face_patches[facenum]->emitstyle ) + break; + } + + if( j == MAXLIGHTMAPS ) + { + g_overflowed_styles_onpatch[thread]++; + } + else + { + if( f->styles[j] == 255 ) + f->styles[j] = g_face_patches[facenum]->emitstyle; + + s = fl->samples; + for( i = 0; i < fl->numsamples; i++, s++ ) + { + VectorAdd( s->light[j], g_face_patches[facenum]->baselight, s->light[j] ); +#ifdef HLRAD_DELUXEMAPPING + vec_t avg = VectorAvg( g_face_patches[facenum]->baselight ); + VectorMA( s->deluxe[j], -DIFFUSE_DIRECTION_SCALE * avg, s->normal, s->deluxe[j] ); +#endif + } + } + } +} + +/* +============= +PrecompLightmapOffsets +============= +*/ +#ifdef HLRAD_PARANOIA_BUMP +void PrecompLightmapOffsets( void ) +{ + int lightstyles; + facelight_t *fl; + dface_t *f; + + g_shadowdatasize = 0; // unused + g_normaldatasize = 0; // unused + g_numvertnormals = 0; // unused + g_lightdatasize = 0; + lightstyles = 4; + + for( int facenum = 0; facenum < g_numfaces; facenum++ ) + { + f = &g_dfaces[facenum]; + fl = &g_facelight[facenum]; + + if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL )) + continue; // non-lit texture + + f->lightofs = g_lightdatasize; + g_lightdatasize += fl->numsamples * 3 * lightstyles; + } + + g_dlightdata = (byte *)Mem_Realloc( g_dlightdata, g_lightdatasize ); +} +#else +void PrecompLightmapOffsets( void ) +{ + int overflow_styles_onpatch = 0; + int overflow_styles_onface = 0; + int i, e, facenum; + int lightstyles; + patch_t *patch; + dvertex_t *vert; + facelight_t *fl; + dface_t *f; + + g_lightdatasize = g_normaldatasize = 0; + g_numvertnormals = g_numsurfedges; // indexes count is always matched surfedge count + g_numnormals = 1; // leave first normal empty as error + + for( facenum = 0; facenum < g_numfaces; facenum++ ) + { + vec3_t maxlights1[MAXSTYLES]; + vec3_t maxlights2[MAXSTYLES]; + vec_t maxlights[MAXSTYLES]; + vec3_t phongNormal; + int j, k; + + f = &g_dfaces[facenum]; + fl = &g_facelight[facenum]; + + if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL )) + { + // NOTE: for non-lit faces all the vertex-normals have index0 (empty normal) + continue; // non-lit texture + } + + // write smoothed normals for face + for( i = 0; i < f->numedges; i++ ) + { + e = g_dsurfedges[f->firstedge+i]; + + if( e >= 0 ) vert = g_dvertexes + g_dedges[e].v[0]; + else vert = g_dvertexes + g_dedges[-e].v[1]; + GetPhongNormal( facenum, vert->point, phongNormal ); + g_vertnormals[f->firstedge+i] = StoreNormal( phongNormal ); + } + + for( j = 0; j < MAXSTYLES; j++ ) + { + VectorClear( maxlights1[j] ); + VectorClear( maxlights2[j] ); + } + + for( k = 0; k < MAXLIGHTMAPS && f->styles[k] != 255; k++ ) + { + for( i = 0; i < fl->numsamples; i++ ) + { + VectorCompareMax( maxlights1[f->styles[k]], fl->samples[i].light[k], maxlights1[f->styles[k]] ); + } + } + + int numpatches; + const int *patches; + + GetTriangulationPatches( facenum, &numpatches, &patches ); // collect patches and their neighbors + + for( i = 0; i < numpatches; i++ ) + { + patch = &g_patches[patches[i]]; + + for( k = 0; k < MAXLIGHTMAPS && patch->totalstyle[k] != 255; k++ ) + { + VectorCompareMax( maxlights2[patch->totalstyle[k]], patch->totallight[k], maxlights2[patch->totalstyle[k]] ); + } + } + + for( j = 0; j < MAXSTYLES; j++ ) + { + vec3_t v; + + VectorAdd( maxlights1[j], maxlights2[j], v ); + maxlights[j] = VectorMaximum( v ); + + if( maxlights[j] <= EQUAL_EPSILON ) + maxlights[j] = 0; + } + + byte oldstyles[MAXLIGHTMAPS]; + sample_t *oldsamples = (sample_t *)Mem_Alloc( sizeof( sample_t ) * fl->numsamples ); + + for( k = 0; k < MAXLIGHTMAPS; k++ ) + oldstyles[k] = f->styles[k]; + + // make backup and clear the source + for( k = 0; k < fl->numsamples; k++ ) + { + for( j = 0; j < MAXLIGHTMAPS; j++ ) + { + VectorCopy( fl->samples[k].light[j], oldsamples[k].light[j] ); + VectorClear( fl->samples[k].light[j] ); +#ifdef HLRAD_DELUXEMAPPING + VectorCopy( fl->samples[k].deluxe[j], oldsamples[k].deluxe[j] ); + VectorClear( fl->samples[k].deluxe[j] ); +#ifdef HLRAD_SHADOWMAPPING + oldsamples[k].shadow[j] = fl->samples[k].shadow[j]; + fl->samples[k].shadow[j] = 0.0f; +#endif +#endif + } + } + + for( k = 0; k < MAXLIGHTMAPS; k++ ) + { + byte beststyle = 255; + + if( k == 0 ) + { + beststyle = 0; + } + else + { + vec_t bestmaxlight = 0; + + for( j = 1; j < MAXSTYLES; j++ ) + { + if( maxlights[j] > bestmaxlight + NORMAL_EPSILON ) + { + bestmaxlight = maxlights[j]; + beststyle = j; + } + } + } + + if( beststyle != 255 ) + { + maxlights[beststyle] = 0; + f->styles[k] = beststyle; + + for( i = 0; i < MAXLIGHTMAPS && oldstyles[i] != 255; i++ ) + { + if( oldstyles[i] == f->styles[k] ) + break; + } + + if( i < MAXLIGHTMAPS && oldstyles[i] != 255 ) + { + for( j = 0; j < fl->numsamples; j++ ) + { + VectorCopy( oldsamples[j].light[i], fl->samples[j].light[k] ); +#ifdef HLRAD_DELUXEMAPPING + VectorCopy( oldsamples[j].deluxe[i], fl->samples[j].deluxe[k] ); +#ifdef HLRAD_SHADOWMAPPING + fl->samples[j].shadow[k] = oldsamples[j].shadow[i]; +#endif +#endif + } + } + else + { + for( j = 0; j < fl->numsamples; j++ ) + { + VectorClear( fl->samples[j].light[k] ); +#ifdef HLRAD_DELUXEMAPPING + VectorClear( fl->samples[j].deluxe[k] ); +#ifdef HLRAD_SHADOWMAPPING + fl->samples[j].shadow[k] = 0.0f; +#endif +#endif + } + } + } + else + { + f->styles[k] = 255; + + for( j = 0; j < fl->numsamples; j++ ) + { + VectorClear( fl->samples[j].light[k] ); +#ifdef HLRAD_DELUXEMAPPING + VectorClear( fl->samples[j].deluxe[k] ); +#ifdef HLRAD_SHADOWMAPPING + fl->samples[j].shadow[k] = 0.0f; +#endif +#endif + } + } + } + + Mem_Free( oldsamples ); + + for( lightstyles = 0; lightstyles < MAXLIGHTMAPS; lightstyles++ ) + { + if( f->styles[lightstyles] == 255 ) + break; // end if styles + } + + if( !lightstyles ) continue; + + f->lightofs = g_lightdatasize; + g_lightdatasize += fl->numsamples * 3 * lightstyles; + } +#ifdef HLRAD_LIGHTMAPMODELS + PrecompModelLightmapOffsets(); +#endif + // now we have lightdata size and alloc the arrays + // g-cont. we use realloc in case we already have valid lighting data and do it again + if( g_found_extradata ) + { +#ifdef HLRAD_DELUXEMAPPING + g_ddeluxdata = (byte *)Mem_Realloc( g_ddeluxdata, g_lightdatasize ); + g_deluxdatasize = g_lightdatasize; +#ifdef HLRAD_SHADOWMAPPING + g_dshadowdata = (byte *)Mem_Realloc( g_dshadowdata, g_lightdatasize / 3 ); + g_shadowdatasize = g_lightdatasize / 3; +#endif +#endif + } + g_dlightdata = (byte *)Mem_Realloc( g_dlightdata, g_lightdatasize ); + + // calc normal datasize + g_normaldatasize = sizeof( dnormallump_t ) + ( g_numvertnormals * sizeof( dvertnorm_t )) + (g_numnormals * sizeof( dnormal_t )); + g_dnormaldata = (byte *)Mem_Realloc( g_dnormaldata, g_normaldatasize ); + + // write indexed normals into memory + byte *buffer = g_dnormaldata; + dnormallump_t *normhdr = (dnormallump_t *)buffer; + + normhdr->ident = NORMIDENT; + normhdr->numnormals = g_numnormals; // this is trivialy to compute in engine but i'm leave this count for bounds checking + buffer += sizeof( dnormallump_t ); + + // store normal indexes + memcpy( buffer, g_vertnormals, g_numvertnormals * sizeof( dvertnorm_t )); + buffer += g_numvertnormals * sizeof( dvertnorm_t ); + + // store unique normals + memcpy( buffer, g_dnormals, g_numnormals * sizeof( dnormal_t )); + buffer += g_numnormals * sizeof( dnormal_t ); + + if(( buffer - g_dnormaldata ) != g_normaldatasize ) + COM_FatalError( "WriteVertexNormals: memory corrupted\n" ); + + // now count how many styles was overflowed + for( i = 0; i < MAX_THREADS; i++ ) + { + overflow_styles_onpatch += g_overflowed_styles_onpatch[i]; + overflow_styles_onface += g_overflowed_styles_onface[i]; + } + + if( overflow_styles_onface > 0 ) + Msg( "^3Warning:^7 too many light styles on a face (%i faces overflowed)\n", overflow_styles_onface ); + + if( overflow_styles_onpatch > 0 ) + Msg( "^3Warning:^7 too many light styles on a patch (%i patches overflowed)\n", overflow_styles_onpatch ); +} +#endif + +/* +============ +CreateFacelightDependencyList +============ +*/ +void CreateFacelightDependencyList( void ) +{ + facelist_t *item; + facelight_t *fl; + dface_t *f; + + for( int i = 0; i < MAX_MAP_FACES; i++ ) + g_dependentfacelights[i] = NULL; + + // for each face + for( int facenum = 0; facenum < g_numfaces; facenum++ ) + { + f = &g_dfaces[facenum]; + fl = &g_facelight[facenum]; + + if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL )) + continue; + + for( int i = 0; i < fl->numsamples; i++ ) + { + int surface = fl->samples[i].surface; // that surface contains at least one sample from this face + + if( surface >= 0 && surface < g_numfaces ) + { + // insert this face into the dependency list of that surface + for( item = g_dependentfacelights[surface]; item != NULL; item = item->next ) + { + if( item->facenum == facenum ) + break; + } + + if( item ) continue; // already added? + + item = (facelist_t *)Mem_Alloc( sizeof( facelist_t )); + + item->next = g_dependentfacelights[surface]; + g_dependentfacelights[surface] = item; + item->facenum = facenum; + } + } + } +} + +/* +============ +FreeFacelightDependencyList +============ +*/ +void FreeFacelightDependencyList( void ) +{ + facelist_t *item; + + for( int i = 0; i < MAX_MAP_FACES; i++ ) + { + while( g_dependentfacelights[i] ) + { + item = g_dependentfacelights[i]; + g_dependentfacelights[i] = item->next; + Mem_Free( item ); + } + } +} + +/* +============ +CalcSampleSize +============ +*/ +void CalcSampleSize( void ) +{ + facelight_t *fl; + dface_t *f; + size_t samples_total_size = 0; + + for( int facenum = 0; facenum < g_numfaces; facenum++ ) + { + f = &g_dfaces[facenum]; + + if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL )) + continue; + + fl = &g_facelight[facenum]; + samples_total_size += fl->numsamples * sizeof( sample_t ); + } + + Msg( "total facelight data: %s\n", Q_memprint( samples_total_size )); +} + +/* +============ +CalcLuxelsCount +============ +*/ +void CalcLuxelsCount( void ) +{ + size_t total_luxels = 0; + size_t lighted_luxels = 0; + + for( int i = 0; i < MAX_THREADS; i++ ) + { + total_luxels += g_direct_luxels[i]; + lighted_luxels += g_lighted_luxels[i]; + } + Msg( "%d luxels affected by direct light\n", total_luxels ); + Msg( "%d luxels reached by direct light\n", lighted_luxels ); +} + +/* +============ +ScaleDirectLights +============ +*/ +void ScaleDirectLights( void ) +{ +#ifndef HLRAD_PARANOIA_BUMP + sample_t *samp; + facelight_t *fl; + dface_t *f; + + for( int facenum = 0; facenum < g_numfaces; facenum++ ) + { + f = &g_dfaces[facenum]; + + if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL )) + continue; + + fl = &g_facelight[facenum]; + + for( int k = 0; k < MAXLIGHTMAPS && f->styles[k] != 255; k++ ) + { + for( int i = 0; i < fl->numsamples; i++ ) + { + samp = &fl->samples[i]; + VectorScale( samp->light[k], g_direct_scale, samp->light[k] ); +#ifdef HLRAD_DELUXEMAPPING + VectorScale( samp->deluxe[k], g_direct_scale, samp->deluxe[k] ); +#endif + } + } + } +#endif +#ifdef HLRAD_LIGHTMAPMODELS + ScaleModelDirectLights(); +#endif +} + +/* +============ +FacePatchLights + +This function is run multithreaded +============ +*/ +void FacePatchLights( int facenum, int threadnum ) +{ + dface_t *f_other; + facelight_t *fl_other; + vec3_t v, v_dir; + facelist_t *item; + sample_t *samp; + dface_t *f; + + f = &g_dfaces[facenum]; + + if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL )) + return; + + for( item = g_dependentfacelights[facenum]; item != NULL; item = item->next ) + { + f_other = &g_dfaces[item->facenum]; + fl_other = &g_facelight[item->facenum]; + + for( int k = 0; k < MAXLIGHTMAPS && f_other->styles[k] != 255; k++ ) + { + for( int i = 0; i < fl_other->numsamples; i++ ) + { + samp = &fl_other->samples[i]; + + if( samp->surface != facenum ) + { + // the sample is not in this surface + continue; + } + + int style = f_other->styles[k]; + InterpolateSampleLight( samp->pos, samp->surface, 1, &style, &v, &v_dir ); +#ifdef HLRAD_PARANOIA_BUMP + if( f_other->styles[k] == STYLE_ORIGINAL_LIGHT ) + { + VectorAdd( samp->light[STYLE_ORIGINAL_LIGHT], v, samp->light[STYLE_ORIGINAL_LIGHT] ); + VectorAdd( samp->light[STYLE_INDIRECT_LIGHT], v, samp->light[STYLE_INDIRECT_LIGHT] ); + } + else if( f_other->styles[k] == STYLE_BUMPED_LIGHT ) + { + // store indirect light into separate style + VectorAdd( samp->deluxe[STYLE_BUMPED_LIGHT], v_dir, samp->deluxe[STYLE_BUMPED_LIGHT] ); + } + // only STYLE_ORIGINAL_LIGHT and STYLE_BUMPED_LIGHT are handled +#else + VectorScale( v, g_indirect_scale, v ); + VectorAdd( samp->light[k], v, v ); +#ifdef HLRAD_DELUXEMAPPING + VectorScale( v_dir, g_indirect_scale, v_dir ); + VectorAdd( samp->deluxe[k], v_dir, v_dir ); +#endif + if( VectorMaximum( v ) >= EQUAL_EPSILON ) + { + VectorCopy( v, samp->light[k] ); +#ifdef HLRAD_DELUXEMAPPING + VectorCopy( v_dir, samp->deluxe[k] ); +#endif + } +#endif + } + } + } +} + +/* +============= +FinalLightFace + +Add the indirect lighting on top of the direct +lighting and save into final map format +============= +*/ +#ifdef HLRAD_PARANOIA_BUMP +void ApplyLightGamma( vec3_t lb ) +{ + // clip from the top + if( lb[0] > g_maxlight || lb[1] > g_maxlight || lb[2] > g_maxlight ) + { + // find max value and scale the whole color down; + float max = VectorMax( lb ); + + for( int i = 0; i < 3; i++ ) + lb[i] = ( lb[i] * g_maxlight ) / max; + } + + // do gamma adjust + if( g_gammamode == 0 ) + { + lb[0] = (float)pow( lb[0] / 256.0f, g_gamma ) * 256.0f; + lb[1] = (float)pow( lb[1] / 256.0f, g_gamma ) * 256.0f; + lb[2] = (float)pow( lb[2] / 256.0f, g_gamma ) * 256.0f; + } + else + { + float avg, avgbefore; + + if( g_gammamode == 2 ) + avg = VectorMaximum( lb ); + else avg = VectorAvg( lb ); + avgbefore = avg; + + if( avgbefore ) + { + avg = (float)pow( avg / 256.0f, g_gamma ) * 256.0f; + avg = avg / avgbefore; + VectorScale( lb, avg, lb ); + } + } +} + +void PackColor( vec3_t lb, int facenum, int style, int sample ) +{ + dface_t *f = &g_dfaces[facenum]; + facelight_t *fl = &g_facelight[facenum]; + + // check + for( int i = 0; i < 3; i++ ) + { + if( lb[i] < 0.0f ) MsgDev( D_WARN, "component %d on style %d is too low: %f\n", i, style, lb[i] ); + if( lb[i] > 256.0f ) MsgDev( D_WARN, "component %d on style %d is too high: %f\n", i, style, lb[i] ); + } + + g_dlightdata[f->lightofs + style * fl->numsamples * 3 + sample * 3 + 0] = (byte)lb[0]; + g_dlightdata[f->lightofs + style * fl->numsamples * 3 + sample * 3 + 1] = (byte)lb[1]; + g_dlightdata[f->lightofs + style * fl->numsamples * 3 + sample * 3 + 2] = (byte)lb[2]; +} + +void FinalLightFace( int facenum, int threadnum ) +{ + vec_t lmvecs[2][4]; + sample_t *samp; + dtexinfo_t *tx; + facelight_t *fl; + dface_t *f; + + f = &g_dfaces[facenum]; + fl = &g_facelight[facenum]; + tx = &g_texinfo[f->texinfo]; + samp = fl->samples; + + if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL )) + return; // non-lit texture + + f->styles[0] = 0; // everyone gets the style zero map. + f->styles[BUMP_LIGHTVECS_MAP] = BUMP_LIGHTVECS_STYLE; + f->styles[BUMP_BASELIGHT_MAP] = BUMP_BASELIGHT_STYLE; + f->styles[BUMP_ADDLIGHT_MAP] = BUMP_ADDLIGHT_STYLE; + + // we assume what all the four styles is always present + LightMatrixFromTexMatrix( tx, lmvecs ); + + for( int j = 0; j < fl->numsamples; j++, samp++ ) + { + const vec3_t &facenormal = GetPlaneFromFace( f )->normal; + vec3_t directionnormals[3]; + vec3_t texdirections[2]; + vec3_t original_light; // direct + indirect + vec3_t light_deluxe; // deluxemap + vec3_t light_diffuse; // direct light + vec3_t light_indirect; // radiosity + vec3_t direction; + int side; + + VectorCopy( samp->normal, directionnormals[2] ); + + for( side = 0; side < 2; side++ ) + { + CrossProduct( facenormal, lmvecs[!side], texdirections[side] ); + VectorNormalize( texdirections[side] ); + if( DotProduct( texdirections[side], lmvecs[side]) < 0.0f ) + VectorNegate( texdirections[side], texdirections[side] ); + } + + for( side = 0; side < 2; side++ ) + { + vec_t dot = DotProduct( texdirections[side], samp->normal ); + VectorMA( texdirections[side], -dot, samp->normal, directionnormals[side] ); + VectorNormalize( directionnormals[side] ); + } + +// VectorNegate( directionnormals[1], directionnormals[1] ); + + // prepare four styles + VectorScale( samp->light[STYLE_ORIGINAL_LIGHT], g_direct_scale, original_light ); + VectorScale( samp->light[STYLE_BUMPED_LIGHT], g_direct_scale, light_diffuse ); + VectorScale( samp->light[STYLE_INDIRECT_LIGHT], g_direct_scale, light_indirect ); + VectorScale( samp->deluxe[STYLE_BUMPED_LIGHT], g_direct_scale, direction ); + VectorNegate( direction, direction ); // let the direction point from face sample to light source + VectorNormalize( direction ); + + ApplyLightGamma( original_light ); // apply standard gamma rules + ApplyLightGamma( light_indirect ); // apply standard gamma rules + ApplyLightGamma( light_diffuse ); // apply standard gamma rules + + // buz - divide by two bump styles. + // this decreases precision, but increases range + VectorScale( light_indirect, 0.5f, light_indirect ); + VectorScale( light_diffuse, 0.5f, light_diffuse ); + + // turn to tangent space + for( int x = 0; x < 3; x++ ) + { + light_deluxe[x] = DotProduct( direction, directionnormals[x] ) * 127.0f + 128.0f; + light_deluxe[x] = bound( 0, light_deluxe[x], 255.0 ); + } + + PackColor( original_light, facenum, 0, j ); + PackColor( light_indirect, facenum, 1, j ); + PackColor( light_deluxe, facenum, 2, j ); + PackColor( light_diffuse, facenum, 3, j ); + } +} +#else +void FinalLightFace( int facenum, int threadnum ) +{ + vec_t lmvecs[2][4]; + int lightstyles; + float minlight; + int i, j, k; + sample_t *samp; + dtexinfo_t *tx; + facelight_t *fl; + dface_t *f; + vec3_t lb; + + f = &g_dfaces[facenum]; + fl = &g_facelight[facenum]; + tx = &g_texinfo[f->texinfo]; + + if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL )) + return; // non-lit texture + + for( lightstyles = 0; lightstyles < MAXLIGHTMAPS; lightstyles++ ) + { + if( f->styles[lightstyles] == 255 ) + break; + } + + if( !lightstyles ) return; + + LightMatrixFromTexMatrix( tx, lmvecs ); + + minlight = FloatForKey( g_face_entity[facenum], "_minlight" ); + if( minlight < 1.0 ) minlight *= 128.0f; // GoldSrc + else minlight *= 0.5f; // Quake + + if( g_lightbalance ) + minlight *= g_direct_scale; + if( g_numbounce > 0 ) minlight = 0.0f; // ignore for radiosity + + for( k = 0; k < lightstyles; k++ ) + { + samp = fl->samples; + + for( j = 0; j < fl->numsamples; j++, samp++ ) + { +#ifdef HLRAD_DELUXEMAPPING + const vec3_t &facenormal = GetPlaneFromFace( f )->normal; + vec3_t directionnormals[3]; + vec3_t texdirections[2]; + vec3_t direction; + int side; + vec3_t v; + + VectorCopy( samp->normal, directionnormals[2] ); + + for( side = 0; side < 2; side++ ) + { + CrossProduct( facenormal, lmvecs[!side], texdirections[side] ); + VectorNormalize( texdirections[side] ); + if( DotProduct( texdirections[side], lmvecs[side]) < 0.0f ) + VectorNegate( texdirections[side], texdirections[side] ); + } + + for( side = 0; side < 2; side++ ) + { + vec_t dot = DotProduct( texdirections[side], samp->normal ); + VectorMA( texdirections[side], -dot, samp->normal, directionnormals[side] ); + VectorNormalize( directionnormals[side] ); + } + VectorNegate( directionnormals[1], directionnormals[1] ); +#endif + VectorCopy( samp->light[k], lb ); +#ifdef HLRAD_DELUXEMAPPING + VectorCopy( samp->deluxe[k], direction ); + vec_t avg = VectorAvg( lb ); + VectorScale( direction, 1.0 / Q_max( 1.0, avg ), direction ); +#endif + // clip from the bottom first + lb[0] = Q_max( lb[0], minlight ); + lb[1] = Q_max( lb[1], minlight ); + lb[2] = Q_max( lb[2], minlight ); + + // clip from the top + if( lb[0] > g_maxlight || lb[1] > g_maxlight || lb[2] > g_maxlight ) + { + // find max value and scale the whole color down; + float max = VectorMax( lb ); + + for( i = 0; i < 3; i++ ) + lb[i] = ( lb[i] * g_maxlight ) / max; + } + + // do gamma adjust + lb[0] = (float)pow( lb[0] / 256.0f, g_gamma ) * 256.0f; + lb[1] = (float)pow( lb[1] / 256.0f, g_gamma ) * 256.0f; + lb[2] = (float)pow( lb[2] / 256.0f, g_gamma ) * 256.0f; + +#ifdef HLRAD_RIGHTROUND // when you go down, when you go down down! + g_dlightdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 0] = Q_rint( lb[0] ); + g_dlightdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 1] = Q_rint( lb[1] ); + g_dlightdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 2] = Q_rint( lb[2] ); +#else + g_dlightdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 0] = (byte)lb[0]; + g_dlightdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 1] = (byte)lb[1]; + g_dlightdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 2] = (byte)lb[2]; +#endif + +#ifdef HLRAD_DELUXEMAPPING + if( g_deluxdatasize ) + { + VectorScale( direction, 0.225, v ); // the scale is calculated such that length( v ) < 1 + + if( DotProduct( v, v ) > ( 1.0 - NORMAL_EPSILON )) + VectorNormalize( v ); + + VectorNegate( v, v ); // let the direction point from face sample to light source + + for( int x = 0; x < 3; x++ ) + { + lb[x] = DotProduct( v, directionnormals[x] ) * 127.0f + 128.0f; + lb[x] = bound( 0, lb[x], 255.0 ); + } + + g_ddeluxdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 0] = (byte)lb[0]; + g_ddeluxdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 1] = (byte)lb[1]; + g_ddeluxdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 2] = (byte)lb[2]; + } +#ifdef HLRAD_SHADOWMAPPING + if( g_shadowdatasize ) + g_dshadowdata[(f->lightofs / 3) + k * fl->numsamples + j] = (byte)samp->shadow[k] * 255; +#endif +#endif + } + } +} +#endif + +void FreeFaceLights( void ) +{ + for( int i = 0; i < g_numfaces; i++ ) + { + Mem_Free( g_facelight[i].samples ); + } +#ifdef HLRAD_LIGHTMAPMODELS + FreeModelFaceLights(); +#endif +} + +void ReduceLightmap( void ) +{ + // store compiler settings to help matching with dynamic lighting + SetKeyValue( &g_entities[0], "_lightgamma", va( "%.2f", g_gamma )); + SetKeyValue( &g_entities[0], "_dscale", va( "%.2f", g_direct_scale )); + SetKeyValue( &g_entities[0], "_ambient", va( "%.2f %.2f %.2f", g_ambient[0], g_ambient[1], g_ambient[2] )); + SetKeyValue( &g_entities[0], "_maxlight", va( "%.2f", g_maxlight )); + SetKeyValue( &g_entities[0], "_smooth", va( "%.2f", g_smoothvalue )); + + int oldsize = g_lightdatasize; + + byte *oldlightdata = (byte *)Mem_Alloc( g_lightdatasize, C_SAFEALLOC ); + + if( !oldlightdata ) + { + MsgDev( D_WARN, "failed to reduce lightmap due to failed to allocate memory for lightdata\n" ); + return; + } + memcpy( oldlightdata, g_dlightdata, g_lightdatasize ); + +#ifdef HLRAD_DELUXEMAPPING + byte *olddeluxdata = (byte *)Mem_Alloc( g_deluxdatasize, C_SAFEALLOC ); + + if( !olddeluxdata ) + { + MsgDev( D_WARN, "failed to reduce lightmap due to failed to allocate memory for deluxdata\n" ); + Mem_Free( oldlightdata, C_SAFEALLOC ); + return; + } + memcpy( olddeluxdata, g_ddeluxdata, g_deluxdatasize ); +#ifdef HLRAD_SHADOWMAPPING + byte *oldshadowdata = (byte *)Mem_Alloc( g_shadowdatasize, C_SAFEALLOC ); + + if( !oldshadowdata ) + { + MsgDev( D_WARN, "failed to reduce lightmap due to failed to allocate memory for shadowdata\n" ); + Mem_Free( oldlightdata, C_SAFEALLOC ); + Mem_Free( olddeluxdata, C_SAFEALLOC ); + return; + } + memcpy( oldshadowdata, g_dshadowdata, g_shadowdatasize ); +#endif +#endif + g_lightdatasize = 0; + g_deluxdatasize = 0; + g_shadowdatasize = 0; + + for( int facenum = 0; facenum < g_numfaces; facenum++ ) + { + dface_t *f = &g_dfaces[facenum]; + facelight_t *fl = &g_facelight[facenum]; + + if( FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL )) + continue; // non-lit texture + + if( f->lightofs == -1 ) + continue; + + byte oldstyles[MAXLIGHTMAPS]; + int numstyles = 0; + int i, k, oldofs; + + oldofs = f->lightofs; + f->lightofs = g_lightdatasize; + + for( k = 0; k < MAXLIGHTMAPS; k++ ) + { + oldstyles[k] = f->styles[k]; + f->styles[k] = 255; + } + + for( k = 0; k < MAXLIGHTMAPS && oldstyles[k] != 255; k++ ) + { + int count = fl->numsamples; + byte maxb = 0; + + for( i = 0; i < count; i++ ) + { + byte *v = &oldlightdata[oldofs + count * 3 * k + i * 3]; + maxb = Q_max( maxb, VectorMaximum( v )); + } + + if( maxb <= 0 ) // black + continue; + + f->styles[numstyles] = oldstyles[k]; + memcpy( &g_dlightdata[f->lightofs + count * 3 * numstyles], &oldlightdata[oldofs + count * 3 * k], count * 3 ); +#ifdef HLRAD_DELUXEMAPPING + if( g_ddeluxdata != NULL ) + memcpy( &g_ddeluxdata[f->lightofs + count * 3 * numstyles], &olddeluxdata[oldofs + count * 3 * k], count * 3 ); +#ifdef HLRAD_SHADOWMAPPING + if( g_dshadowdata != NULL ) + memcpy( &g_dshadowdata[(f->lightofs/3) + count * numstyles], &oldshadowdata[(oldofs / 3) + count * k], count ); +#endif +#endif + numstyles++; + } + g_lightdatasize += fl->numsamples * 3 * numstyles; +#ifdef HLRAD_DELUXEMAPPING + if( g_ddeluxdata != NULL ) + g_deluxdatasize += fl->numsamples * 3 * numstyles; +#ifdef HLRAD_SHADOWMAPPING + if( g_dshadowdata != NULL ) + g_shadowdatasize += fl->numsamples * numstyles; +#endif +#endif + } + +#ifdef HLRAD_LIGHTMAPMODELS +#ifdef HLRAD_DELUXEMAPPING +#ifdef HLRAD_SHADOWMAPPING + ReduceModelLightmap( oldlightdata, olddeluxdata, oldshadowdata ); +#else + ReduceModelLightmap( oldlightdata, olddeluxdata, NULL ); +#endif +#else + ReduceModelLightmap( oldlightdata, NULL, NULL ); +#endif +#endif + Mem_Free( oldlightdata, C_SAFEALLOC ); +#ifdef HLRAD_DELUXEMAPPING + Mem_Free( olddeluxdata, C_SAFEALLOC ); +#ifdef HLRAD_SHADOWMAPPING + Mem_Free( oldshadowdata, C_SAFEALLOC ); +#endif +#endif + int newsize = g_lightdatasize; + + if( oldsize != newsize ) + { +#ifdef HLRAD_DELUXEMAPPING + Msg( "Reduce lightmap from %s to %s\n", Q_memprint( oldsize * 2 + ( oldsize / 3 )), Q_memprint( newsize * 2 + ( newsize / 3 ))); +#else + Msg( "Reduce lightmap from %s to %s\n", Q_memprint( oldsize + ( oldsize / 3 )), Q_memprint( newsize + ( newsize / 3 ))); +#endif + } +} \ No newline at end of file diff --git a/utils/p2rad/model_lightmaps.cpp b/utils/p2rad/model_lightmaps.cpp new file mode 100644 index 0000000..4603840 --- /dev/null +++ b/utils/p2rad/model_lightmaps.cpp @@ -0,0 +1,1424 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include "qrad.h" +#include "model_trace.h" +#include "..\..\engine\studio.h" + +#ifdef HLRAD_LIGHTMAPMODELS + +#define TRI_BORDER 0.4f // FIXME: this is too much + +typedef struct +{ + int modelnum : 10; + int facenum : 22; +} faceremap_t; + +typedef struct +{ + float *point; + float *coord; +} trivert_t; + +static entity_t *g_modellight[MAX_MAP_MODELS]; +static int g_modellight_modnum; +static faceremap_t *g_modellight_indexes; +static uint g_modellight_numindexes; +static float g_nudge[2][9] = {{ 0, -1, 0, 1, -1, 1, -1, 0, 1 }, { 0, -1, -1, -1, 0, 0, 1, 1, 1 }}; +static float g_studio_blur; + +static void CalcLightmapAxis( tmesh_t *mesh, lface_t *face, trivert_t *a, trivert_t *b, trivert_t *c ) +{ + int ssize = face->texture_step; + vec3_t mins, maxs, size; + vec3_t planeNormal; + int i, axis; + float d; + + ClearBounds( mins, maxs ); + AddPointToBounds( a->point, mins, maxs ); + AddPointToBounds( b->point, mins, maxs ); + AddPointToBounds( c->point, mins, maxs ); + + // round to the lightmap resolution + for( i = 0; i < 3; i++ ) + { + mins[i] = ssize * floor( mins[i] / ssize ); + maxs[i] = ssize * ceil( maxs[i] / ssize ); + size[i] = (maxs[i] - mins[i]) / ssize; + } + + // the two largest axis will be the lightmap size + planeNormal[0] = fabs( face->normal[0] ); + planeNormal[1] = fabs( face->normal[1] ); + planeNormal[2] = fabs( face->normal[2] ); + + if( planeNormal[0] >= planeNormal[1] && planeNormal[0] >= planeNormal[2] ) + { + face->extents[0] = size[1]; + face->extents[1] = size[2]; + face->lmvecs[0][1] = 1.0 / ssize; + face->lmvecs[1][2] = 1.0 / ssize; + axis = 0; + } + else if( planeNormal[1] >= planeNormal[0] && planeNormal[1] >= planeNormal[2] ) + { + face->extents[0] = size[0]; + face->extents[1] = size[2]; + face->lmvecs[0][0] = 1.0 / ssize; + face->lmvecs[1][2] = 1.0 / ssize; + axis = 1; + } + else + { + face->extents[0] = size[0]; + face->extents[1] = size[1]; + face->lmvecs[0][0] = 1.0 / ssize; + face->lmvecs[1][1] = 1.0 / ssize; + axis = 2; + } + + if( !face->normal[axis] ) + COM_FatalError( "Chose a 0 valued axis\n" ); + + if( face->extents[0] > ( MAX_STUDIO_LIGHTMAP_SIZE - 1 )) + { + VectorScale( face->lmvecs[0], (float)(MAX_STUDIO_LIGHTMAP_SIZE - 1.0f) / face->extents[0], face->lmvecs[0] ); + face->extents[0] = MAX_STUDIO_LIGHTMAP_SIZE - 1; + } + + if( face->extents[1] > ( MAX_STUDIO_LIGHTMAP_SIZE - 1 )) + { + VectorScale( face->lmvecs[1], (float)(MAX_STUDIO_LIGHTMAP_SIZE - 1.0f) / face->extents[1], face->lmvecs[1] ); + face->extents[1] = MAX_STUDIO_LIGHTMAP_SIZE - 1; + } + + // calculate the world coordinates of the lightmap samples + + // project mins onto plane to get origin + d = DotProduct( mins, face->normal ) - DotProduct( face->normal, a->point ); + d /= face->normal[axis]; + VectorCopy( mins, face->origin ); + face->origin[axis] -= d; + + // project stepped lightmap blocks and subtract to get planevecs + for( i = 0; i < 2 ; i++ ) + { + vec3_t normalized; + float len; + + len = VectorNormalizeLength2( face->lmvecs[i], normalized ); + VectorScale( normalized, (1.0 / len), face->lmvecs[i] ); + d = DotProduct( face->lmvecs[i], face->normal ); + d /= face->normal[axis]; + face->lmvecs[i][axis] -= d; + } + +} + +// we need to compute tangent vectors +void CalcTriangleVectors( int modelnum, int threadnum = -1 ) +{ + entity_t *mapent = g_modellight[modelnum]; + tmesh_t *mesh; + trivert_t v[3]; + + // sanity check + if( !mapent || !mapent->cache ) + return; + + mesh = (tmesh_t *)mapent->cache; + if( !mesh->verts || mesh->numverts <= 0 ) + return; + + if( !mesh->faces || mesh->numfaces <= 0 ) + return; + + // build the smoothed normals + if( !FBitSet( mesh->flags, FMESH_DONT_SMOOTH ) && g_smoothing_threshold ) + { + vec3_t *normals = (vec3_t *)Mem_Alloc( mesh->numverts * sizeof( vec3_t )); + + for( int hashSize = 1; hashSize < mesh->numverts; hashSize <<= 1 ); + hashSize = hashSize >> 2; + + // build a map from vertex to a list of triangles that share the vert. + CUtlArray vertHashMap; + + vertHashMap.AddMultipleToTail( hashSize ); + + for( int vertID = 0; vertID < mesh->numverts; vertID++ ) + { + tvert_t *tv = &mesh->verts[vertID]; + uint hash = VertexHashKey( tv->point, hashSize ); + vertHashMap[hash].AddToTail( vertID ); + } + + for( int hashID = 0; hashID < hashSize; hashID++ ) + { + for( int i = 0; i < vertHashMap[hashID].Size(); i++ ) + { + int vertID = vertHashMap[hashID][i]; + tvert_t *tv0 = &mesh->verts[vertID]; + + for( int j = 0; j < vertHashMap[hashID].Size(); j++ ) + { + tvert_t *tv1 = &mesh->verts[vertHashMap[hashID][j]]; + + if( !VectorCompareEpsilon( tv0->point, tv1->point, ON_EPSILON )) + continue; + + if( DotProduct( tv0->normal, tv1->normal ) >= g_smoothing_threshold ) + VectorAdd( normals[vertID], tv1->normal, normals[vertID] ); + } + } + } + + // copy smoothed normals back + for( int j = 0; j < mesh->numverts; j++ ) + { + VectorCopy( normals[j], mesh->verts[j].normal ); + VectorNormalize2( mesh->verts[j].normal ); + } + + Mem_Free( normals ); + } + + for( int triID = 0; triID < mesh->numfaces; triID++ ) + { + tface_t *face = &mesh->faces[triID]; + + if( !face->light ) continue; + + // get positions and UV from three points + v[0].point = (float *)&mesh->verts[face->a].point; + v[0].coord = (float *)&mesh->verts[face->a].st; + v[1].point = (float *)&mesh->verts[face->b].point; + v[1].coord = (float *)&mesh->verts[face->b].st; + v[2].point = (float *)&mesh->verts[face->c].point; + v[2].coord = (float *)&mesh->verts[face->c].st; + face->light->texture_step = mesh->texture_step; + + if( VectorIsNull( face->light->normal )) + continue; + + CalcLightmapAxis( mesh, face->light, &v[0], &v[1], &v[2] ); + } +} + +bool GetTrianglePhongNormal( lightinfo_t *l, const vec3_t spot, vec3_t phongnormal ) +{ + tface_t *face = l->tface; + tvert_t *a = &l->mesh->verts[face->a]; + tvert_t *b = &l->mesh->verts[face->b]; + tvert_t *c = &l->mesh->verts[face->c]; + vec3_t edge1, edge2, p0, p1; + float uu, uv, vv, wu, wv; + float d1, d2, d, frac; + vec3_t q, n, p; + float u, v, w; + + // setup fallback + VectorCopy( l->tface->light->normal, phongnormal ); +#if 0 + VectorSubtract( b->point, a->point, edge1 ); + VectorSubtract( c->point, a->point, edge2 ); + CrossProduct( edge2, edge1, n ); + VectorMA( spot, DEFAULT_HUNT_OFFSET, n, p0 ); + VectorMA( spot,-DEFAULT_HUNT_OFFSET, n, p1 ); + + VectorSubtract( p1, p0, p ); + VectorSubtract( p0, a->point, q ); + + d1 = -DotProduct( n, q ); + d2 = DotProduct( n, p ); + + if( fabs( d2 ) < FRAC_EPSILON ) + return false; // parallel with plane + + // get intersect point of ray with triangle plane + frac = d1 / d2; + if( frac < -0.001f ) return false; + + // calculate the impact point + VectorLerp( p0, frac, p1, p ); + + // does p lie inside triangle? + uu = DotProduct( edge1, edge1 ); + uv = DotProduct( edge1, edge2 ); + vv = DotProduct( edge2, edge2 ); + + VectorSubtract( p, a->point, q ); + wu = DotProduct( q, edge1 ); + wv = DotProduct( q, edge2 ); + d = uv * uv - uu * vv; + + // get and test parametric coords + u = (uv * wv - vv * wu) / d; + if( u < -TRI_BORDER || u > ( 1.0f + TRI_BORDER )) + return false; // p is outside + + v = (uv * wu - uu * wv) / d; + if( v < -TRI_BORDER || (u + v) > ( 1.0f + TRI_BORDER )) + return false; // p is outside + // calculate w parameter + w = 1.0f - ( u + v ); +#else + // now, check 3 edges + float hitc1 = spot[face->pcoord0] + ( -phongnormal[face->pcoord0] ); + float hitc2 = spot[face->pcoord1] + ( -phongnormal[face->pcoord1] ); + + // do barycentric coordinate check + v = face->edge1[0] * hitc1 + face->edge1[1] * hitc2 + face->edge1[2]; + if( v < -TRI_BORDER ) return false; + + w = face->edge2[0] * hitc1 + face->edge2[1] * hitc2 + face->edge2[2]; + if( w < -TRI_BORDER ) return false; + + u = v + w; + if( u > 1.0f + TRI_BORDER ) return false; + + // calculate w parameter + u = 1.0f - u; +#endif + if( g_smoothing_threshold > 0.0 ) + { + u = bound( 0.0f, u, 1.0f ); + v = bound( 0.0f, v, 1.0f ); + w = bound( 0.0f, w, 1.0f ); + + // calculate st from uvw (barycentric) coordinates + phongnormal[0] = w * a->normal[0] + u * b->normal[0] + v * c->normal[0]; + phongnormal[1] = w * a->normal[1] + u * b->normal[1] + v * c->normal[1]; + phongnormal[2] = w * a->normal[2] + u * b->normal[2] + v * c->normal[2]; +// VectorNormalize2( phongnormal ); + } + + return true; +} + +/* +================ +CalcTriangleExtents + +Fills in s->texmins[] and s->texsize[] +also sets exactmins[] and exactmaxs[] +================ +*/ +void CalcTriangleExtents( lightinfo_t *l ) +{ + l->texsize[0] = l->tface->light->extents[0]; + l->texsize[1] = l->tface->light->extents[1]; + + if( l->texsize[0] * l->texsize[1] > ( MAX_SINGLEMAP_MODEL / 3 )) + COM_FatalError( "surface to large to map %d > %d\n", l->texsize[0] * l->texsize[1], ( MAX_SINGLEMAP_MODEL / 3 )); + + if( l->texsize[0] < 0 || l->texsize[1] < 0 ) + COM_FatalError( "negative extents\n" ); + + l->lmcache_density = 1; + l->lmcache_side = (int)ceil(( 0.5 * g_studio_blur * l->lmcache_density - 0.5 ) * ( 1.0 - NORMAL_EPSILON )); + l->lmcache_offset = l->lmcache_side; + l->lmcachewidth = l->texsize[0] * l->lmcache_density + 1 + 2 * l->lmcache_side; + l->lmcacheheight = l->texsize[1] * l->lmcache_density + 1 + 2 * l->lmcache_side; + + l->light = (vec3_t (*)[MAXLIGHTMAPS])Mem_Alloc( l->lmcachewidth * l->lmcacheheight * sizeof( vec3_t[MAXLIGHTMAPS] )); +#ifdef HLRAD_DELUXEMAPPING + l->deluxe = (vec3_t (*)[MAXLIGHTMAPS])Mem_Alloc( l->lmcachewidth * l->lmcacheheight * sizeof( vec3_t[MAXLIGHTMAPS] )); + l->normals = (vec3_t *)Mem_Alloc( l->lmcachewidth * l->lmcacheheight * sizeof( vec3_t )); +#ifdef HLRAD_SHADOWMAPPING + l->shadow = (vec_t (*)[MAXLIGHTMAPS])Mem_Alloc( l->lmcachewidth * l->lmcacheheight * sizeof( vec_t[MAXLIGHTMAPS] )); +#endif +#endif +} + +/* +================= +CalcPoints + +For each texture aligned grid point, back project onto the plane +to get the world xyz value of the sample point +================= +*/ +void CalcTrianglePoints( lightinfo_t *l, float sofs, float tofs ) +{ + int h = l->texsize[1] + 1; + int w = l->texsize[0] + 1; + lface_t *face = l->tface->light; + int s, t, i, j; + dleaf_t *leaf; + + l->surfpt = (surfpt_t *)Mem_Alloc( w * h * sizeof( surfpt_t )); + l->numsurfpt = w * h; + + for( t = 0; t < h; t++ ) + { + for( s = 0; s < w; s++ ) + { + surfpt_t *surf = &l->surfpt[s+w*t]; + + // calculate texture point + for( j = 0; j < 3; j++ ) + surf->point[j] = face->origin[j] + face->normal[j] + s * face->lmvecs[0][j] + t * face->lmvecs[1][j]; + + // we may need to slightly nudge the sample point + // if directly on a wall + for( i = 0; i < 9; i++ ) + { + // calculate texture point + for( j = 0; j < 3; j++ ) + surf->position[j] = surf->point[j] + + ( g_nudge[0][i] / 16.0f ) * face->lmvecs[0][j] + + ( g_nudge[1][i] / 16.0f ) * face->lmvecs[1][j]; + + leaf = PointInLeaf( surf->position ); + + if( leaf->contents != CONTENTS_SOLID ) + { +// if( TestLine( -1, face->origin, surf->position ) == CONTENTS_EMPTY ) + break; // got it + } + } + + if( i == 9 ) surf->occluded = true; + } + } +} + +/* +============= +InitLightinfo +============= +*/ +void InitModelLightinfo( lightinfo_t *l, entity_t *mapent, tmesh_t *mesh, tface_t *tf ) +{ + memset( l, 0, sizeof( *l )); + l->mapent = mapent; + l->mesh = mesh; + l->tface = tf; + + CalcTriangleExtents( l ); +} + +static void CalcModelLightmap( int thread, lightinfo_t *l, facelight_t *fl ) +{ + vec_t density = (vec_t)l->lmcache_density; + vec_t texture_step = l->tface->light->texture_step; + int w = l->texsize[0] + 1; + int h = l->texsize[1] + 1; + lface_t *f = l->tface->light; + byte *vislight = NULL; + int i, j, count; + vec3_t acolor, adelux; + float ashadow; + +#ifdef HLRAD_COMPUTE_VISLIGHTMATRIX + vislight = l->mesh->vislight; +#endif + ASSERT( l->numsurfpt > 0 ); + + // allocate light samples + fl->samples = (sample_t *)Mem_Alloc( l->numsurfpt * sizeof( sample_t )); + fl->numsamples = l->numsurfpt; + + // stats + g_direct_luxels[thread] += fl->numsamples; + + // copy surf points from lightinfo with offset 0,0 + for( i = 0; i < fl->numsamples; i++ ) + { + VectorCopy( l->surfpt[i].point, fl->samples[i].pos ); + fl->samples[i].occluded = l->surfpt[i].occluded; + fl->samples[i].surface = l->surfpt[i].surface; + } + + // for each sample whose light we need to calculate + for( i = 0; i < l->lmcachewidth * l->lmcacheheight; i++ ) + { + vec3_t pointnormal; + vec3_t spot, surfpt; + bool blocked; + + // prepare input parameter and output parameter + vec_t s = ((i % l->lmcachewidth) - l->lmcache_offset) / (vec_t)l->lmcache_density; + vec_t t = ((i / l->lmcachewidth) - l->lmcache_offset) / (vec_t)l->lmcache_density; + int nearest_s = Q_max( 0, Q_min((int)floor( s + 0.5 ), l->texsize[0] )); + int nearest_t = Q_max( 0, Q_min((int)floor( t + 0.5 ), l->texsize[1] )); + + j = nearest_s + w * nearest_t; + + VectorCopy( l->surfpt[j].position, surfpt ); + VectorMA( surfpt, DEFAULT_HUNT_OFFSET, f->normal, spot ); + + // calculate normal for the sample + if( !GetTrianglePhongNormal( l, surfpt, pointnormal )) + l->surfpt[j].occluded = true; + + // find world's position for the sample + blocked = l->surfpt[j].occluded; + +#ifdef HLRAD_DELUXEMAPPING + VectorCopy( pointnormal, l->normals[i] ); +#endif + if( blocked ) continue; + + // calculate visibility for the sample + int leaf = PointInLeaf( spot ) - g_dleafs; + + // gather light +#if defined( HLRAD_DELUXEMAPPING ) && defined( HLRAD_SHADOWMAPPING ) + GatherSampleLight( thread, -1, spot, leaf, pointnormal, l->light[i], l->deluxe[i], l->shadow[i], f->styles, vislight, 0, l->mapent ); + GatherSampleLight( thread, -1, spot, leaf, pointnormal, l->light[i], l->deluxe[i], l->shadow[i], f->styles, vislight, 1, l->mapent ); +#elif defined( HLRAD_DELUXEMAPPING ) + GatherSampleLight( thread, -1, spot, leaf, pointnormal, l->light[i], l->deluxe[i], NULL, f->styles, vislight, 0, l->mapent ); + GatherSampleLight( thread, -1, spot, leaf, pointnormal, l->light[i], l->deluxe[i], NULL, f->styles, vislight, 1, l->mapent ); +#else + GatherSampleLight( thread, -1, spot, leaf, pointnormal, l->light[i], NULL, NULL, f->styles, vislight, 0, l->mapent ); + GatherSampleLight( thread, -1, spot, leaf, pointnormal, l->light[i], NULL, NULL, f->styles, vislight, 1, l->mapent ); +#endif + } + + for( i = 0; i < fl->numsamples; i++ ) + { +#ifdef HLRAD_DELUXEMAPPING + vec_t weighting_correction; + vec3_t centernormal; +#endif + int s_center, t_center; + vec_t weighting, subsamples; + int s, t, pos; + vec_t sizehalf; + + s_center = (i % w) * l->lmcache_density + l->lmcache_offset; + t_center = (i / w) * l->lmcache_density + l->lmcache_offset; + sizehalf = 0.5 * g_studio_blur * l->lmcache_density; + subsamples = 0.0; +#ifdef HLRAD_DELUXEMAPPING + VectorCopy( l->normals[s_center + l->lmcachewidth * t_center], centernormal ); +#endif + for( s = s_center - l->lmcache_side; s <= s_center + l->lmcache_side; s++ ) + { + for( t = t_center - l->lmcache_side; t <= t_center + l->lmcache_side; t++ ) + { + weighting = (Q_min( 0.5, sizehalf - ( s - s_center )) - Q_max( -0.5, -sizehalf - ( s - s_center ))); + weighting *=(Q_min( 0.5, sizehalf - ( t - t_center )) - Q_max( -0.5, -sizehalf - ( t - t_center ))); + + pos = s + l->lmcachewidth * t; +#ifdef HLRAD_DELUXEMAPPING + // when blur distance (g_blur) is large, the subsample can be very far from the original lightmap sample + // in some cases such as a thin cylinder, the subsample can even grow into the opposite side + // as a result, when exposed to a directional light, the light on the cylinder may "leak" into + // the opposite dark side this correction limits the effect of blur distance when the normal changes very fast + // this correction will not break the smoothness that HLRAD_GROWSAMPLE ensures + weighting_correction = DotProduct( l->normals[pos], centernormal ); + weighting_correction = (weighting_correction > 0) ? weighting_correction * weighting_correction : 0; + weighting = weighting * weighting_correction; +#endif + for( j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ ) + { + VectorMA( fl->samples[i].light[j], weighting, l->light[pos][j], fl->samples[i].light[j] ); +#ifdef HLRAD_DELUXEMAPPING + VectorMA( fl->samples[i].deluxe[j], weighting, l->deluxe[pos][j], fl->samples[i].deluxe[j] ); +#ifdef HLRAD_SHADOWMAPPING + fl->samples[i].shadow[j] += l->shadow[pos][j] * weighting; +#endif +#endif + } + subsamples += weighting; + } + } + + if( subsamples > NORMAL_EPSILON ) + { +#ifdef HLRAD_DELUXEMAPPING + VectorCopy( centernormal, fl->samples[i].normal ); +#endif + for( j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ ) + { + VectorScale( fl->samples[i].light[j], (1.0 / subsamples), fl->samples[i].light[j] ); +#ifdef HLRAD_DELUXEMAPPING + VectorScale( fl->samples[i].deluxe[j], (1.0 / subsamples), fl->samples[i].deluxe[j] ); +#ifdef HLRAD_SHADOWMAPPING + fl->samples[i].shadow[j] *= (1.0 / subsamples); +#endif +#endif + } + } + } + +#ifdef HLRAD_DELUXEMAPPING +#ifdef HLRAD_SHADOWMAPPING + // multiply light by shadow to prevent blur artifacts + for( i = 0; i < fl->numsamples; i++ ) + { + for( j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ ) + { +// VectorScale( fl->samples[i].light[j], fl->samples[i].shadow[j], fl->samples[i].light[j] ); +// VectorScale( fl->samples[i].deluxe[j], fl->samples[i].shadow[j], fl->samples[i].deluxe[j] ); + } + } + + // output occlusion shouldn't be blured + for( i = 0; i < fl->numsamples; i++ ) + { + for( j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ ) + { + int s = (i % w) + l->lmcache_side; + int t = (i / w) + l->lmcache_side; + int pos = s + l->lmcachewidth * t; + fl->samples[i].shadow[j] = l->shadow[pos][j]; + } + } +#endif +#endif + // calculate average values for occluded samples + for( int k = 0; k < MAXLIGHTMAPS && f->styles[k] != 255; k++ ) + { + for( i = 0; i < w; i++ ) + { + for( j = 0; j < h; j++ ) + { + int pos0 = i+w*j; + if( !l->surfpt[pos0].occluded ) + continue; + + int step = 1; // 3x3 + + // scan all surrounding samples + VectorClear( acolor ); + VectorClear( adelux ); + ashadow = 0.0f; + count = 0; + + for( int x = -step; x <= step; x++ ) + { + for( int y = -step; y <= step; y++ ) + { + if( i + x < 0 || i + x >= w ) + continue; + + if( j + y < 0 || j + y >= h ) + continue; + + int pos1 = (i+x)+w*(j+y); + if( l->surfpt[pos1].occluded ) + continue; + + VectorAdd( fl->samples[pos1].light[k], acolor, acolor ); +#ifdef HLRAD_DELUXEMAPPING + VectorAdd( fl->samples[pos1].deluxe[k], adelux, adelux ); +#ifdef HLRAD_SHADOWMAPPING + ashadow += fl->samples[pos1].shadow[k]; +#endif +#endif + count++; + } + } + + if( !count ) continue; + VectorScale( acolor, 1.0f / count, fl->samples[pos0].light[k] ); +#ifdef HLRAD_DELUXEMAPPING + VectorScale( adelux, 1.0f / count, fl->samples[pos0].deluxe[k] ); +#ifdef HLRAD_SHADOWMAPPING + fl->samples[pos0].shadow[k] = ashadow * (1.0f / count); +#endif +#endif + } + } + } + + Mem_Free( l->light ); +#ifdef HLRAD_DELUXEMAPPING + Mem_Free( l->normals ); + Mem_Free( l->deluxe ); +#ifdef HLRAD_SHADOWMAPPING + Mem_Free( l->shadow ); +#endif +#endif +} + +void BuildModelLightmaps( int indexnum, int thread = -1 ) +{ + int modelnum = g_modellight_indexes[indexnum].modelnum; + int facenum = g_modellight_indexes[indexnum].facenum; + entity_t *mapent = g_modellight[modelnum]; + entity_t *ignoreent = NULL; + byte *vislight = NULL; + tmesh_t *mesh; + int i, j; + sample_t *s; + lightinfo_t l; + + // sanity check + if( !mapent || !mapent->cache ) + return; + + mesh = (tmesh_t *)mapent->cache; + if( !mesh->verts || mesh->numverts <= 0 ) + return; + + if( !mesh->faces || mesh->numfaces <= 0 ) + return; + + if( !FBitSet( mesh->flags, FMESH_SELF_SHADOW )) + ignoreent = mapent; + + // lightmaps onto models it's more compilcated than vertexlighting... + if( !mesh->faces[facenum].light ) return; + lface_t *f = mesh->faces[facenum].light; + tface_t *tf = &mesh->faces[facenum]; + facelight_t *fl = &f->facelight; + f->lightofs = -1; + + if( VectorIsNull( f->normal )) + return; + + InitModelLightinfo( &l, ignoreent, mesh, tf ); + CalcTrianglePoints( &l, 0.0f, 0.0f ); + CalcModelLightmap( thread, &l, fl ); + + Mem_Free( l.surfpt ); + + // add an ambient term if desired + if( g_ambient[0] || g_ambient[1] || g_ambient[2] ) + { + for( j = 0; j < MAXLIGHTMAPS && f->styles[j] == 255; j++ ); + if( j == MAXLIGHTMAPS ) f->styles[0] = 0; // adding style + + for( j = 0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ ) + { + if( f->styles[j] == 0 ) + { + s = fl->samples; + for( i = 0; i < fl->numsamples; i++, s++ ) + { + VectorAdd( s->light[j], g_ambient, s->light[j] ); +#ifdef HLRAD_DELUXEMAPPING + vec_t avg = VectorAvg( g_ambient ); + VectorMA( s->deluxe[j], -DIFFUSE_DIRECTION_SCALE * avg, s->normal, s->deluxe[j] ); +#endif + } + break; + } + } + + } +} + +/* +============ +CalcModelSampleSize +============ +*/ +void CalcModelSampleSize( void ) +{ + facelight_t *fl; + tface_t *f; + size_t samples_total_size = 0; + tmesh_t *mesh; + + for( int i = 0; i < g_modellight_numindexes; i++ ) + { + int modelnum = g_modellight_indexes[i].modelnum; + int facenum = g_modellight_indexes[i].facenum; + entity_t *mapent = g_modellight[modelnum]; + + mesh = (tmesh_t *)mapent->cache; + f = &mesh->faces[facenum]; + if( !f->light ) continue; + fl = &f->light->facelight; + + samples_total_size += fl->numsamples * sizeof( sample_t ); + } + + Msg( "total studiolight data: %s\n", Q_memprint( samples_total_size )); +} + +/* +============ +PrecompModelLightmapOffsets +============ +*/ +void PrecompModelLightmapOffsets( void ) +{ + int lightstyles; + tmesh_t *mesh; + facelight_t *fl; + lface_t *f; + + for( int l = 0; l < g_modellight_numindexes; l++ ) + { + int modelnum = g_modellight_indexes[l].modelnum; + int facenum = g_modellight_indexes[l].facenum; + entity_t *mapent = g_modellight[modelnum]; + vec3_t maxlights1[MAXSTYLES]; + vec3_t maxlights2[MAXSTYLES]; + vec_t maxlights[MAXSTYLES]; + int i, j, k; + + mesh = (tmesh_t *)mapent->cache; + f = mesh->faces[facenum].light; + if( !f ) continue; + fl = &f->facelight; + + for( j = 0; j < MAXSTYLES; j++ ) + { + VectorClear( maxlights1[j] ); + VectorClear( maxlights2[j] ); + } + + for( k = 0; k < MAXLIGHTMAPS && f->styles[k] != 255; k++ ) + { + for( i = 0; i < fl->numsamples; i++ ) + { + VectorCompareMax( maxlights1[f->styles[k]], fl->samples[i].light[k], maxlights1[f->styles[k]] ); + } + } +#ifdef LATER + int numpatches; + const int *patches; + + GetTriangulationPatches( facenum, &numpatches, &patches ); // collect patches and their neighbors + + for( i = 0; i < numpatches; i++ ) + { + patch_t *patch = &g_patches[patches[i]]; + + for( k = 0; k < MAXLIGHTMAPS && patch->totalstyle[k] != 255; k++ ) + { + VectorCompareMax( maxlights2[patch->totalstyle[k]], patch->totallight[k], maxlights2[patch->totalstyle[k]] ); + } + } +#endif + for( j = 0; j < MAXSTYLES; j++ ) + { + vec3_t v; + + VectorAdd( maxlights1[j], maxlights2[j], v ); + maxlights[j] = VectorMaximum( v ); + + if( maxlights[j] <= EQUAL_EPSILON ) + maxlights[j] = 0; + } + + byte oldstyles[MAXLIGHTMAPS]; + sample_t *oldsamples = (sample_t *)Mem_Alloc( sizeof( sample_t ) * fl->numsamples ); + + for( k = 0; k < MAXLIGHTMAPS; k++ ) + oldstyles[k] = f->styles[k]; + + // make backup and clear the source + for( k = 0; k < fl->numsamples; k++ ) + { + for( j = 0; j < MAXLIGHTMAPS; j++ ) + { + VectorCopy( fl->samples[k].light[j], oldsamples[k].light[j] ); + VectorClear( fl->samples[k].light[j] ); +#ifdef HLRAD_DELUXEMAPPING + VectorCopy( fl->samples[k].deluxe[j], oldsamples[k].deluxe[j] ); + VectorClear( fl->samples[k].deluxe[j] ); +#ifdef HLRAD_SHADOWMAPPING + oldsamples[k].shadow[j] = fl->samples[k].shadow[j]; + fl->samples[k].shadow[j] = 0.0f; +#endif +#endif + } + } + + for( k = 0; k < MAXLIGHTMAPS; k++ ) + { + byte beststyle = 255; + + if( k == 0 ) + { + beststyle = 0; + } + else + { + vec_t bestmaxlight = 0; + + for( j = 1; j < MAXSTYLES; j++ ) + { + if( maxlights[j] > bestmaxlight + NORMAL_EPSILON ) + { + bestmaxlight = maxlights[j]; + beststyle = j; + } + } + } + + if( beststyle != 255 ) + { + maxlights[beststyle] = 0; + f->styles[k] = beststyle; + + for( i = 0; i < MAXLIGHTMAPS && oldstyles[i] != 255; i++ ) + { + if( oldstyles[i] == f->styles[k] ) + break; + } + + if( i < MAXLIGHTMAPS && oldstyles[i] != 255 ) + { + for( j = 0; j < fl->numsamples; j++ ) + { + VectorCopy( oldsamples[j].light[i], fl->samples[j].light[k] ); +#ifdef HLRAD_DELUXEMAPPING + VectorCopy( oldsamples[j].deluxe[i], fl->samples[j].deluxe[k] ); +#ifdef HLRAD_SHADOWMAPPING + fl->samples[j].shadow[k] = oldsamples[j].shadow[i]; +#endif +#endif + } + } + else + { + for( j = 0; j < fl->numsamples; j++ ) + { + VectorClear( fl->samples[j].light[k] ); +#ifdef HLRAD_DELUXEMAPPING + VectorClear( fl->samples[j].deluxe[k] ); +#ifdef HLRAD_SHADOWMAPPING + fl->samples[j].shadow[k] = 0.0f; +#endif +#endif + } + } + } + else + { + f->styles[k] = 255; + + for( j = 0; j < fl->numsamples; j++ ) + { + VectorClear( fl->samples[j].light[k] ); +#ifdef HLRAD_DELUXEMAPPING + VectorClear( fl->samples[j].deluxe[k] ); +#ifdef HLRAD_SHADOWMAPPING + fl->samples[j].shadow[k] = 0.0f; +#endif +#endif + } + } + } + + Mem_Free( oldsamples ); + + for( lightstyles = 0; lightstyles < MAXLIGHTMAPS; lightstyles++ ) + { + if( f->styles[lightstyles] == 255 ) + break; // end if styles + } + + if( !lightstyles ) continue; + + f->lightofs = g_lightdatasize; + g_lightdatasize += fl->numsamples * 3 * lightstyles; + } +} + +/* +============ +ScaleModelDirectLights +============ +*/ +void ScaleModelDirectLights( void ) +{ + tmesh_t *mesh; + sample_t *samp; + facelight_t *fl; + lface_t *f; + + for( int i = 0; i < g_modellight_numindexes; i++ ) + { + int modelnum = g_modellight_indexes[i].modelnum; + int facenum = g_modellight_indexes[i].facenum; + entity_t *mapent = g_modellight[modelnum]; + + mesh = (tmesh_t *)mapent->cache; + f = mesh->faces[facenum].light; + if( !f ) continue; + fl = &f->facelight; + + for( int k = 0; k < MAXLIGHTMAPS && f->styles[k] != 255; k++ ) + { + for( int i = 0; i < fl->numsamples; i++ ) + { + samp = &fl->samples[i]; + VectorScale( samp->light[k], g_direct_scale, samp->light[k] ); +#ifdef HLRAD_DELUXEMAPPING + VectorScale( samp->deluxe[k], g_direct_scale, samp->deluxe[k] ); +#endif + } + } + } +} + +void FinalModelLightFace( int indexnum, int threadnum ) +{ + int modelnum = g_modellight_indexes[indexnum].modelnum; + int facenum = g_modellight_indexes[indexnum].facenum; + entity_t *mapent = g_modellight[modelnum]; + int lightstyles; + float minlight; + int i, j, k; + sample_t *samp; + tmesh_t *mesh; + facelight_t *fl; + lface_t *f; + vec3_t lb; + + mesh = (tmesh_t *)mapent->cache; + f = mesh->faces[facenum].light; + if( !f ) return; + fl = &f->facelight; + + for( lightstyles = 0; lightstyles < MAXLIGHTMAPS; lightstyles++ ) + { + if( f->styles[lightstyles] == 255 ) + break; + } + + if( !lightstyles ) return; + + // wrote styles into mesh to determine completely black models + for( i = 0; i < MAXLIGHTMAPS && f->styles[i] != 255; i++ ) + { + for( k = 0; k < MAXLIGHTMAPS; k++ ) + { + if( mesh->styles[k] == f->styles[i] || mesh->styles[k] == 255 ) + break; + } + + // for our purpoces obviously overflow doesn't matter + if( k == MAXLIGHTMAPS ) + continue; + + // allocate a new one + if( mesh->styles[k] == 255 ) + mesh->styles[k] = f->styles[i]; + } + + minlight = FloatForKey( mapent, "_minlight" ); + if( minlight < 1.0 ) minlight *= 128.0f; // GoldSrc + else minlight *= 0.5f; // Quake + + if( g_lightbalance ) + minlight *= g_direct_scale; + if( g_numbounce > 0 ) minlight = 0.0f; // ignore for radiosity + + for( k = 0; k < lightstyles; k++ ) + { + samp = fl->samples; + + for( j = 0; j < fl->numsamples; j++, samp++ ) + { +#ifdef HLRAD_DELUXEMAPPING + vec3_t directionnormals[3]; + vec3_t texdirections[2]; + vec3_t direction; + int side; + vec3_t v; + + VectorCopy( samp->normal, directionnormals[2] ); + + for( side = 0; side < 2; side++ ) + { + CrossProduct( f->normal, f->lmvecs[!side], texdirections[side] ); + VectorNormalize( texdirections[side] ); + if( DotProduct( texdirections[side], f->lmvecs[side]) < 0.0f ) + VectorNegate( texdirections[side], texdirections[side] ); + } + + for( side = 0; side < 2; side++ ) + { + vec_t dot = DotProduct( texdirections[side], samp->normal ); + VectorMA( texdirections[side], -dot, samp->normal, directionnormals[side] ); + VectorNormalize( directionnormals[side] ); + } +#endif + VectorCopy( samp->light[k], lb ); +#ifdef HLRAD_DELUXEMAPPING + VectorCopy( samp->deluxe[k], direction ); + vec_t avg = VectorAvg( lb ); + VectorScale( direction, 1.0 / Q_max( 1.0, avg ), direction ); +#endif + // clip from the bottom first + lb[0] = Q_max( lb[0], minlight ); + lb[1] = Q_max( lb[1], minlight ); + lb[2] = Q_max( lb[2], minlight ); + + // clip from the top + if( lb[0] > g_maxlight || lb[1] > g_maxlight || lb[2] > g_maxlight ) + { + // find max value and scale the whole color down; + float max = VectorMax( lb ); + + for( i = 0; i < 3; i++ ) + lb[i] = ( lb[i] * g_maxlight ) / max; + } + + // do gamma adjust + lb[0] = (float)pow( lb[0] / 256.0f, g_gamma ) * 256.0f; + lb[1] = (float)pow( lb[1] / 256.0f, g_gamma ) * 256.0f; + lb[2] = (float)pow( lb[2] / 256.0f, g_gamma ) * 256.0f; + +#ifdef HLRAD_RIGHTROUND // when you go down, when you go down down! + g_dlightdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 0] = Q_rint( lb[0] ); + g_dlightdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 1] = Q_rint( lb[1] ); + g_dlightdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 2] = Q_rint( lb[2] ); +#else + g_dlightdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 0] = (byte)lb[0]; + g_dlightdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 1] = (byte)lb[1]; + g_dlightdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 2] = (byte)lb[2]; +#endif + +#ifdef HLRAD_DELUXEMAPPING + if( g_deluxdatasize ) + { + VectorScale( direction, 0.225, v ); // the scale is calculated such that length( v ) < 1 + + if( DotProduct( v, v ) > ( 1.0 - NORMAL_EPSILON )) + VectorNormalize( v ); + + VectorNegate( v, v ); // let the direction point from face sample to light source + + for( int x = 0; x < 3; x++ ) + { + lb[x] = DotProduct( v, directionnormals[x] ) * 127.0f + 128.0f; + lb[x] = bound( 0, lb[x], 255.0 ); + } + + g_ddeluxdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 0] = (byte)lb[0]; + g_ddeluxdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 1] = (byte)lb[1]; + g_ddeluxdata[f->lightofs + k * fl->numsamples * 3 + j * 3 + 2] = (byte)lb[2]; + } +#ifdef HLRAD_SHADOWMAPPING + if( g_shadowdatasize ) + g_dshadowdata[(f->lightofs / 3) + k * fl->numsamples + j] = (byte)samp->shadow[k] * 255; +#endif +#endif + } + } +} + +void FreeModelFaceLights( void ) +{ + facelight_t *fl; + lface_t *f; + tmesh_t *mesh; + + for( int i = 0; i < g_modellight_numindexes; i++ ) + { + int modelnum = g_modellight_indexes[i].modelnum; + int facenum = g_modellight_indexes[i].facenum; + entity_t *mapent = g_modellight[modelnum]; + + mesh = (tmesh_t *)mapent->cache; + f = mesh->faces[facenum].light; + if( !f ) continue; + fl = &f->facelight; + Mem_Free( fl->samples ); + } +} + +void ReduceModelLightmap( byte *oldlightdata, byte *olddeluxdata, byte *oldshadowdata ) +{ + facelight_t *fl; + lface_t *f; + tmesh_t *mesh; + + // first clearing old lightstyles + for( int i = 0; i < g_modellight_modnum; i++ ) + { + entity_t *mapent = g_modellight[i]; + + mesh = (tmesh_t *)mapent->cache; + mesh->styles[0] = 255; + mesh->styles[1] = 255; + mesh->styles[2] = 255; + mesh->styles[3] = 255; + } + + for( int index = 0; index < g_modellight_numindexes; index++ ) + { + int modelnum = g_modellight_indexes[index].modelnum; + int facenum = g_modellight_indexes[index].facenum; + entity_t *mapent = g_modellight[modelnum]; + + mesh = (tmesh_t *)mapent->cache; + f = mesh->faces[facenum].light; + if( !f ) continue; + fl = &f->facelight; + + if( f->lightofs == -1 ) + continue; + + byte oldstyles[MAXLIGHTMAPS]; + int numstyles = 0; + int i, k, oldofs; + + oldofs = f->lightofs; + f->lightofs = g_lightdatasize; + + for( k = 0; k < MAXLIGHTMAPS; k++ ) + { + oldstyles[k] = f->styles[k]; + f->styles[k] = 255; + } + + for( k = 0; k < MAXLIGHTMAPS && oldstyles[k] != 255; k++ ) + { + int count = fl->numsamples; + byte maxb = 0; + + for( i = 0; i < count; i++ ) + { + byte *v = &oldlightdata[oldofs + count * 3 * k + i * 3]; + maxb = Q_max( maxb, VectorMaximum( v )); + } + + if( maxb <= 0 ) // black + continue; + + f->styles[numstyles] = oldstyles[k]; + memcpy( &g_dlightdata[f->lightofs + count * 3 * numstyles], &oldlightdata[oldofs + count * 3 * k], count * 3 ); +#ifdef HLRAD_DELUXEMAPPING + if( g_ddeluxdata != NULL ) + memcpy( &g_ddeluxdata[f->lightofs + count * 3 * numstyles], &olddeluxdata[oldofs + count * 3 * k], count * 3 ); + if( g_dshadowdata != NULL ) + memcpy( &g_dshadowdata[(f->lightofs/3) + count * numstyles], &oldshadowdata[(oldofs / 3) + count * k], count ); +#endif + numstyles++; + } + g_lightdatasize += fl->numsamples * 3 * numstyles; +#ifdef HLRAD_DELUXEMAPPING + if( g_ddeluxdata != NULL ) + g_deluxdatasize += fl->numsamples * 3 * numstyles; + + if( g_dshadowdata != NULL ) + g_shadowdatasize += fl->numsamples * numstyles; +#endif + // wrote styles into mesh to determine completely black models + for( i = 0; i < MAXLIGHTMAPS && f->styles[i] != 255; i++ ) + { + for( k = 0; k < MAXLIGHTMAPS; k++ ) + { + if( mesh->styles[k] == f->styles[i] || mesh->styles[k] == 255 ) + break; + } + + // for our purpoces obviously overflow doesn't matter + if( k == MAXLIGHTMAPS ) + continue; + + // allocate a new one + if( mesh->styles[k] == 255 ) + mesh->styles[k] = f->styles[i]; + } + } +} + +static void GenerateLightCacheNumbers( void ) +{ + char string[32]; + tmesh_t *mesh; + int i, j; + + for( i = 1; i < g_numentities; i++ ) + { + entity_t *mapent = &g_entities[i]; + + // no cache - no lighting + if( !mapent->cache ) continue; + + mesh = (tmesh_t *)mapent->cache; + + if( mapent->modtype != mod_alias && mapent->modtype != mod_studio ) + continue; + + if( !mesh->verts || mesh->numverts <= 0 ) + continue; + + if( !mesh->faces || mesh->numfaces <= 0 ) + continue; + + if( !FBitSet( mesh->flags, FMESH_MODEL_LIGHTMAPS )) + continue; + + mesh->texture_step = TEXTURE_STEP; + + // check texturestep + int texture_step = Q_max( 0, IntForKey( mapent, "zhlt_texturestep" )); + + // check bounds + if( texture_step >= MIN_STUDIO_TEXTURE_STEP && texture_step <= MAX_STUDIO_TEXTURE_STEP ) + mesh->texture_step = texture_step; + + short lightid = g_modellight_modnum++; + // at this point we have valid target for model lightmaps + Q_snprintf( string, sizeof( string ), "%i", lightid + 1 ); + SetKeyValue( mapent, "flight_cache", string ); + g_modellight[lightid] = mapent; + } + + g_modellight_numindexes = 0; + g_studio_blur = g_blur; + + // generate remapping table for more effective CPU utilize + for( i = 0; i < g_modellight_modnum; i++ ) + { + entity_t *mapent = g_modellight[i]; + mesh = (tmesh_t *)mapent->cache; + g_modellight_numindexes += mesh->numfaces; + } + + g_modellight_indexes = (faceremap_t *)Mem_Alloc( g_modellight_numindexes * sizeof( faceremap_t )); + uint curIndex = 0; + + for( i = 0; i < g_modellight_modnum; i++ ) + { + entity_t *mapent = g_modellight[i]; + mesh = (tmesh_t *)mapent->cache; + + // encode model as lowpart and facenum as highpart + for( j = 0; j < mesh->numfaces; j++ ) + { + g_modellight_indexes[curIndex].modelnum = i; + g_modellight_indexes[curIndex].facenum = j; + curIndex++; + } + ASSERT( curIndex <= g_modellight_numindexes ); + } +} + +static int ModelSize( tmesh_t *mesh ) +{ + if( !mesh ) return 0; + + if( !mesh->faces || mesh->numfaces <= 0 ) + return 0; + + for( int lightstyles = 0; lightstyles < MAXLIGHTMAPS; lightstyles++ ) + { + if( mesh->styles[lightstyles] == 255 ) + break; + } + + // model is valid but completely not lighted by direct + if( !lightstyles ) return 0; + + return sizeof( dmodelfacelight_t ) - ( sizeof( dfacelight_t ) * 3 ) + sizeof( dfacelight_t ) * mesh->numfaces + ((g_numworldlights + 7) / 8); +} + +static int WriteModelLight( tmesh_t *mesh, byte *out ) +{ + int size = ModelSize( mesh ); + dmodelfacelight_t *dml; + + if( !size ) return 0; + + dml = (dmodelfacelight_t *)out; + out += sizeof( dmodelfacelight_t ) - ( sizeof( dfacelight_t ) * 3 ) + sizeof( dfacelight_t ) * mesh->numfaces; + + dml->modelCRC = mesh->modelCRC; + dml->numfaces = mesh->numfaces; + dml->texture_step = mesh->texture_step; + + memcpy( dml->styles, mesh->styles, sizeof( dml->styles )); + memcpy( dml->submodels, mesh->fsubmodels, sizeof( dml->submodels )); + memcpy( out, mesh->vislight, ((g_numworldlights + 7) / 8)); + VectorCopy( mesh->origin, dml->origin ); + VectorCopy( mesh->angles, dml->angles ); + VectorCopy( mesh->scale, dml->scale ); + + // faces could be store now + for( int i = 0; i < mesh->numfaces; i++ ) + { + memcpy( dml->faces[i].styles, mesh->faces[i].light->styles, sizeof( dml->styles )); + dml->faces[i].lightofs = mesh->faces[i].light->lightofs; + } + + return size; +} + +void WriteModelLighting( void ) +{ + int totaldatasize = ( sizeof( int ) * 3 ) + ( sizeof( int ) * g_modellight_modnum ); + int i, len; + byte *data; + dvlightlump_t *l; + + for( i = 0; i < g_modellight_modnum; i++ ) + { + entity_t *mapent = g_modellight[i]; + + // sanity check + if( !mapent ) continue; + + len = ModelSize( (tmesh_t *)mapent->cache ); + totaldatasize += len; + } + + Msg( "total modellight data: %s\n", Q_memprint( totaldatasize )); + g_dflightdata = (byte *)Mem_Realloc( g_dflightdata, totaldatasize ); + + // now setup to get the miptex data (or just the headers if using -wadtextures) from the wadfile + l = (dvlightlump_t *)g_dflightdata; + data = (byte *)&l->dataofs[g_modellight_modnum]; + + l->ident = FLIGHTIDENT; + l->version = FLIGHT_VERSION; + l->nummodels = g_modellight_modnum; + + for( i = 0; i < g_modellight_modnum; i++ ) + { + entity_t *mapent = g_modellight[i]; + + l->dataofs[i] = data - (byte *)l; + len = WriteModelLight( (tmesh_t *)mapent->cache, data ); + if( !len ) l->dataofs[i] = -1; // completely black model + data += len; + } + + g_flightdatasize = data - g_dflightdata; + + if( totaldatasize != g_flightdatasize ) + COM_FatalError( "WriteModelLighting: memory corrupted\n" ); + + // vertex cache acesss + // const char *id = ValueForKey( ent, "flight_cache" ); + // int cacheID = atoi( id ) - 1; + // if( cacheID < 0 || cacheID > num_map_entities ) return; // bad cache num + // if( l->dataofs[cacheID] == -1 ) return; // cache missed + // otherwise it's valid +} + +void BuildModelLightmaps( void ) +{ + GenerateLightCacheNumbers(); + + // new code is very fast, so no reason to show progress + RunThreadsOnIndividual( g_modellight_modnum, false, CalcTriangleVectors ); + + if( !g_modellight_numindexes ) return; + + RunThreadsOnIndividual( g_modellight_numindexes, true, BuildModelLightmaps ); +} + +void FinalModelLightFace( void ) +{ + if( !g_modellight_numindexes ) return; + + RunThreadsOnIndividual( g_modellight_numindexes, true, FinalModelLightFace ); +} + +#endif \ No newline at end of file diff --git a/utils/p2rad/model_trace.h b/utils/p2rad/model_trace.h new file mode 100644 index 0000000..1832200 --- /dev/null +++ b/utils/p2rad/model_trace.h @@ -0,0 +1,215 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include "raytracer.h" + +#define MAX_TRIANGLES 524288 // studio triangles + +#define FMESH_VERTEX_LIGHTING BIT( 0 ) +#define FMESH_MODEL_LIGHTMAPS BIT( 1 ) +#define FMESH_CAST_SHADOW BIT( 2 ) +#define FMESH_SELF_SHADOW BIT( 3 ) +#define FMESH_DONT_SMOOTH BIT( 4 ) +#define TEX_ALPHATEST BIT( 31 ) // virtual flag + +#define PLANECHECK_POSITIVE 1 +#define PLANECHECK_NEGATIVE -1 +#define PLANECHECK_STRADDLING 0 + +typedef struct +{ + int flags; // STUDIO_NF_??? + int width; + int height; + byte *data; // may be NULL +} timage_t; + +typedef struct lvert_s +{ + vec3_t pos; // adjusted position +#ifdef HLRAD_SHRINK_MEMORY + hvec3_t light[MAXLIGHTMAPS]; // lightvalue + hvec3_t deluxe[MAXLIGHTMAPS]; // deluxe vectors + half shadow[MAXLIGHTMAPS]; // shadow values +#else + vec3_t light[MAXLIGHTMAPS]; // lightvalue + vec3_t deluxe[MAXLIGHTMAPS]; // deluxe vectors + vec_t shadow[MAXLIGHTMAPS]; // shadow values +#endif +} lvert_t; + +typedef struct tvert_s +{ + vec3_t point; + half st[2]; // for alpha-texture test +#ifdef HLRAD_SHRINK_MEMORY + hvec3_t normal; // smoothed normal +#else + vec3_t normal; // smoothed normal +#endif + lvert_t *light; // vertex lighting only (may be NULL) +} tvert_t; + +typedef struct lface_s +{ +#ifdef HLRAD_SHRINK_MEMORY + hvec3_t normal; +#else + vec3_t normal; +#endif + byte styles[MAXLIGHTMAPS]; // each face has individual styles + short texture_step; + short extents[2]; + facelight_t facelight; // will be alloced later + vec3_t lmvecs[2]; // worldluxels face matrix + vec3_t origin; // lightmap origin + int lightofs; // offset into global lighmaps array +} lface_t; + +struct tface_t +{ + vec3_t absmin, absmax; // an individual size of each facet + vec3_t edge1, edge2; // new trace stuff + char contents; // surface contents + byte pcoord0; // coords selection + byte pcoord1; + bool shadow; // this face is used for traceline + int a, b, c; // face indices + vec3_t normal; // triangle unsmoothed normal + float NdotP1; + + union + { + timage_t *texture; // texture->data valid only for alpha-testing surfaces + int skinref; // index during loading + }; + + lface_t *light; // may be NULL + link_t area; // linked to a division node or leaf + + void GetEdgeEquation( const vec3_t p1, const vec3_t p2, vec3_t InsidePoint, vec3_t out ) + { + float nx = p1[pcoord1] - p2[pcoord1]; + float ny = p2[pcoord0] - p1[pcoord0]; + float d = -(nx * p1[pcoord0] + ny * p1[pcoord1]); + + // use the convention that negative is "outside" + float trial_dist = InsidePoint[pcoord0] * nx + InsidePoint[pcoord1] * ny + d; + if( trial_dist == 0 ) trial_dist = FLT_EPSILON; + + if( trial_dist < 0 ) + { + trial_dist = -trial_dist; + nx = -nx; + ny = -ny; + d = -d; + } + + // scale so that it will be =1.0 at the oppositve vertex + nx /= trial_dist; + ny /= trial_dist; + d /= trial_dist; + + VectorSet( out, nx, ny, d ); + } + + void PrepareIntersectionData( tvert_t *triangle ) + { + VectorSubtract( triangle[a].point, triangle[b].point, edge1 ); + VectorSubtract( triangle[c].point, triangle[b].point, edge2 ); + CrossProduct( edge1, edge2, normal ); + VectorNormalize( normal ); + int drop_axis = 0; + + // now, determine which axis to drop + for( int i = 1; i < 3; i++ ) + { + if( fabs( normal[i] ) > fabs( normal[drop_axis] )) + drop_axis = i; + } + + NdotP1 = DotProduct( normal, triangle[a].point ); + + // decide which axes to keep + pcoord0 = ( drop_axis + 1 ) % 3; + pcoord1 = ( drop_axis + 2 ) % 3; + + GetEdgeEquation( triangle[a].point, triangle[b].point, triangle[c].point, edge1 ); + GetEdgeEquation( triangle[b].point, triangle[c].point, triangle[a].point, edge2 ); + if( light != NULL ) VectorCopy( normal, light->normal ); + } + +#if defined( HLRAD_RAYTRACE ) + // NEW TRACE STUFF BEGIN HERE!!! + char pcheck0; // KD-tree intermediate data + char pcheck1; + + int ClassifyAgainstAxisSplit( tvert_t *triangle, int split_plane, float split_value ) + { + // classify a triangle against an axis-aligned plane + float minc = triangle[a].point[split_plane]; + float maxc = minc; + + minc = Q_min( minc, triangle[b].point[split_plane] ); + maxc = Q_max( maxc, triangle[b].point[split_plane] ); + minc = Q_min( minc, triangle[c].point[split_plane] ); + maxc = Q_max( maxc, triangle[c].point[split_plane] ); + + if( minc >= split_value ) + return PLANECHECK_POSITIVE; + + if( maxc <= split_value ) + return PLANECHECK_NEGATIVE; + + if( minc == maxc ) + return PLANECHECK_POSITIVE; + + return PLANECHECK_STRADDLING; + } +#endif +}; + +struct tmesh_t +{ + vec3_t absmin, absmax; + timage_t *textures; + tface_t *faces; + int numfaces; + tvert_t *verts; + int numverts; + + int flags; + float backfrac; // 0.0 by default + dword modelCRC; + byte styles[MAXLIGHTMAPS]; + int texture_step; // lightmap resolution + dvlightofs_t vsubmodels[32]; // MAXSTUDIOMODELS + dflightofs_t fsubmodels[32]; + byte *vislight; //[numworldlights/8] + float origin[3]; + float angles[3]; + float scale[3]; + + aabb_tree_t face_tree; +#ifdef HLRAD_RAYTRACE + CWorldRayTrace ray; +#endif +}; + +typedef struct +{ + dplane_t *edges; + int numedges; + vec3_t origin; // face origin + vec_t radius; // for culling tests + const dface_t *original; + int contents; // sky or solid + int flags; // texinfo->flags +} twface_t; \ No newline at end of file diff --git a/utils/p2rad/p1rad.dsp b/utils/p2rad/p1rad.dsp new file mode 100644 index 0000000..c3b00e6 --- /dev/null +++ b/utils/p2rad/p1rad.dsp @@ -0,0 +1,243 @@ +# Microsoft Developer Studio Project File - Name="p1rad" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=p1rad - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "p1rad.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "p1rad.mak" CFG="p1rad - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "p1rad - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "p1rad - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/SDKSrc/Tools/utils/p1rad", FVGBAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "p1rad - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir ".\Release" +# PROP BASE Intermediate_Dir ".\Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\..\temp\p1rad\!release" +# PROP Intermediate_Dir "..\..\temp\p1rad\!release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "..\common" /I "..\..\common" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "IGNORE_SEARCH_IN_WADS" /D "ALLOW_WADS_IN_PACKS" /D "HLRAD_PARANOIA" /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /opt:nowin98 /largeaddressaware +# ADD LINK32 /nologo /stack:0x400000,0x100000 /subsystem:console /pdb:none /machine:I386 /opt:nowin98 /largeaddressaware +# SUBTRACT LINK32 /profile /map +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\p1rad\!release +InputPath=\Paranoia2\src_main\temp\p1rad\!release\p1rad.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\p1rad.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\p1rad.exe "D:\Paranoia2\tools\p1rad.exe" + +# End Custom Build + +!ELSEIF "$(CFG)" == "p1rad - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir ".\Debug" +# PROP BASE Intermediate_Dir ".\Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\..\temp\p1rad\!debug" +# PROP Intermediate_Dir "..\..\temp\p1rad\!debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MTd /W3 /Gm /Gi /GX /ZI /Od /I "..\common" /I "..\..\common" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "IGNORE_SEARCH_IN_WADS" /D "ALLOW_WADS_IN_PACKS" /D "HLRAD_PARANOIA" /FAs /FR /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 +# ADD LINK32 msvcrtd.lib /nologo /stack:0x400000,0x100000 /subsystem:console /debug /machine:I386 /nodefaultlib:"libcmtd.lib" +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\p1rad\!debug +InputPath=\Paranoia2\src_main\temp\p1rad\!debug\p1rad.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\p1rad.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\p1rad.exe "D:\Paranoia2\tools\p1rad.exe" + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "p1rad - Win32 Release" +# Name "p1rad - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=.\alias.cpp +# End Source File +# Begin Source File + +SOURCE=.\ambientcube.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\basefs.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\bspfile.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\cmdlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\conprint.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\crc32.cpp +# End Source File +# Begin Source File + +SOURCE=.\dirtmap.cpp +# End Source File +# Begin Source File + +SOURCE=.\facepos.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\filesystem.cpp +# End Source File +# Begin Source File + +SOURCE=.\lerp.cpp +# End Source File +# Begin Source File + +SOURCE=.\lightmap.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\mathlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\polylib.cpp +# End Source File +# Begin Source File + +SOURCE=.\qrad.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\scriplib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\stringlib.cpp +# End Source File +# Begin Source File + +SOURCE=.\studio.cpp +# End Source File +# Begin Source File + +SOURCE=.\textures.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\threads.cpp +# End Source File +# Begin Source File + +SOURCE=.\trace.cpp +# End Source File +# Begin Source File + +SOURCE=.\vertexlight.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\wadfile.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\zone.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=..\common\bspfile.h +# End Source File +# Begin Source File + +SOURCE=..\common\cmdlib.h +# End Source File +# Begin Source File + +SOURCE=..\common\mathlib.h +# End Source File +# Begin Source File + +SOURCE=..\common\polylib.h +# End Source File +# Begin Source File + +SOURCE=.\qrad.h +# End Source File +# Begin Source File + +SOURCE=..\common\scriplib.h +# End Source File +# Begin Source File + +SOURCE=..\common\threads.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/utils/p2rad/p2rad.dsp b/utils/p2rad/p2rad.dsp new file mode 100644 index 0000000..59ab3e0 --- /dev/null +++ b/utils/p2rad/p2rad.dsp @@ -0,0 +1,283 @@ +# Microsoft Developer Studio Project File - Name="p2rad" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=p2rad - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "p2rad.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "p2rad.mak" CFG="p2rad - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "p2rad - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "p2rad - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/SDKSrc/Tools/utils/p2rad", FVGBAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "p2rad - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir ".\Release" +# PROP BASE Intermediate_Dir ".\Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\..\temp\p2rad\!release" +# PROP Intermediate_Dir "..\..\temp\p2rad\!release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "..\common" /I "..\..\common" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "IGNORE_SEARCH_IN_WADS" /D "ALLOW_WADS_IN_PACKS" /D "HLRAD_PSAVIOR" /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /opt:nowin98 /largeaddressaware +# ADD LINK32 ..\common\squish.lib /nologo /stack:0x400000,0x100000 /subsystem:console /pdb:none /machine:I386 /nodefaultlib:"libc" /opt:nowin98 /largeaddressaware +# SUBTRACT LINK32 /profile /map +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\p2rad\!release +InputPath=\Paranoia2\src_main\temp\p2rad\!release\p2rad.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\p2rad.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\p2rad.exe "D:\Paranoia2\tools\p2rad.exe" + +# End Custom Build + +!ELSEIF "$(CFG)" == "p2rad - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir ".\Debug" +# PROP BASE Intermediate_Dir ".\Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\..\temp\p2rad\!debug" +# PROP Intermediate_Dir "..\..\temp\p2rad\!debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MTd /W3 /Gm /Gi /GX /ZI /Od /I "..\common" /I "..\..\common" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "IGNORE_SEARCH_IN_WADS" /D "ALLOW_WADS_IN_PACKS" /D "HLRAD_PSAVIOR" /FAs /FR /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 +# ADD LINK32 msvcrtd.lib ..\common\squish.lib /nologo /stack:0x400000,0x100000 /subsystem:console /debug /machine:I386 /nodefaultlib:"libcmtd.lib" /nodefaultlib:"libc.lib" +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\p2rad\!debug +InputPath=\Paranoia2\src_main\temp\p2rad\!debug\p2rad.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\p2rad.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\p2rad.exe "D:\Paranoia2\tools\p2rad.exe" + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "p2rad - Win32 Release" +# Name "p2rad - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=.\alias.cpp +# End Source File +# Begin Source File + +SOURCE=.\ambientcube.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\basefs.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\bspfile.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\cmdlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\conprint.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\crc32.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\ddstex.cpp +# End Source File +# Begin Source File + +SOURCE=.\dirtmap.cpp +# End Source File +# Begin Source File + +SOURCE=.\facepos.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\filesystem.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\gamma.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\imagelib.cpp +# End Source File +# Begin Source File + +SOURCE=.\lerp.cpp +# End Source File +# Begin Source File + +SOURCE=.\lightmap.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\mathlib.cpp +# End Source File +# Begin Source File + +SOURCE=.\model_lightmaps.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\polylib.cpp +# End Source File +# Begin Source File + +SOURCE=.\qrad.cpp +# End Source File +# Begin Source File + +SOURCE=.\raytracer.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\scriplib.cpp +# End Source File +# Begin Source File + +SOURCE=.\raytrace\sseconst.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\stringlib.cpp +# End Source File +# Begin Source File + +SOURCE=.\studio.cpp +# End Source File +# Begin Source File + +SOURCE=.\textures.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\threads.cpp +# End Source File +# Begin Source File + +SOURCE=.\trace.cpp +# End Source File +# Begin Source File + +SOURCE=.\vertexlight.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\virtualfs.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\wadfile.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\zone.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=..\common\bspfile.h +# End Source File +# Begin Source File + +SOURCE=..\common\cmdlib.h +# End Source File +# Begin Source File + +SOURCE=..\common\ddstex.h +# End Source File +# Begin Source File + +SOURCE=..\common\imagelib.h +# End Source File +# Begin Source File + +SOURCE=..\common\mathlib.h +# End Source File +# Begin Source File + +SOURCE=..\common\polylib.h +# End Source File +# Begin Source File + +SOURCE=.\qrad.h +# End Source File +# Begin Source File + +SOURCE=.\raytracer.h +# End Source File +# Begin Source File + +SOURCE=..\common\scriplib.h +# End Source File +# Begin Source File + +SOURCE=..\common\threads.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/utils/p2rad/qrad.cpp b/utils/p2rad/qrad.cpp new file mode 100644 index 0000000..e380375 --- /dev/null +++ b/utils/p2rad/qrad.cpp @@ -0,0 +1,2721 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// qrad.c + +#include "qrad.h" + +/* +NOTES +----- +every surface must be divided into at least two patches each axis +*/ + +patch_t *g_face_patches[MAX_MAP_FACES]; +entity_t *g_face_entity[MAX_MAP_FACES]; +vec_t *g_skynormalsizes[SKYLEVELMAX+1]; +int g_numskynormals[SKYLEVELMAX+1]; +vec3_t *g_skynormals[SKYLEVELMAX+1]; +patch_t *g_patches; +uint g_num_patches; +static vec3_t (*emitlight)[MAXLIGHTMAPS]; +static vec3_t (*addlight)[MAXLIGHTMAPS]; +static byte (*newstyles)[MAXLIGHTMAPS]; +#ifdef HLRAD_DELUXEMAPPING +static vec3_t (*emitlight_dir)[MAXLIGHTMAPS]; +static vec3_t (*addlight_dir)[MAXLIGHTMAPS]; +#endif +vec3_t g_face_offset[MAX_MAP_FACES]; // for rotating bmodels +dplane_t g_backplanes[MAX_MAP_PLANES]; // equal to MAX_MAP_FACES, there is no errors +int g_overflowed_styles_onface[MAX_THREADS]; +int g_overflowed_styles_onpatch[MAX_THREADS]; +int g_direct_luxels[MAX_THREADS]; +int g_lighted_luxels[MAX_THREADS]; +vec3_t g_reflectivity[MAX_MAP_TEXTURES]; +bool g_texture_init[MAX_MAP_TEXTURES]; +static winding_t *g_windingArray[MAX_SUBDIVIDE]; +static uint g_numwindings = 0; +static size_t g_transfer_data_bytes; +size_t g_transfer_data_size[MAX_THREADS]; +uint g_numbounce = DEFAULT_BOUNCE; // originally this was 8 +vec_t g_chop = DEFAULT_CHOP; +vec_t g_texchop = DEFAULT_TEXCHOP; +vec_t g_smoothvalue = DEFAULT_SMOOTHVALUE; +float g_totalarea; +bool g_fastmode = DEFAULT_FASTMODE; +vec_t g_direct_scale = DEFAULT_DLIGHT_SCALE; +vec_t g_indirect_scale = DEFAULT_LIGHT_SCALE; +vec_t g_blur = DEFAULT_BLUR; +vec_t g_gamma = DEFAULT_GAMMA; +bool g_drawsample = false; +vec3_t g_ambient = { 0, 0, 0 }; +float g_maxlight = DEFAULT_LIGHTCLIP; // originally this was 196 +float g_indirect_sun = DEFAULT_INDIRECT_SUN; +bool g_dirtmapping = DEFAULT_DIRTMAPPING; +bool g_lerp_enabled = DEFAULT_LERP_ENABLED; +bool g_extra = DEFAULT_EXTRAMODE; +bool g_nomodelshadow = false; +bool g_lightbalance = false; +bool g_onlylights = false; +float g_smoothing_threshold; // cosine of smoothing angle(in radians) +char source[MAX_PATH] = ""; +uint g_gammamode = DEFAULT_GAMMAMODE; + +static char global_lights[MAX_PATH] = ""; +static char level_lights[MAX_PATH] = ""; + +// pre-quantized table normals from Quake1 +vec_t g_anorms[NUMVERTEXNORMALS][3] = +{ +#include "anorms.h" +}; + +/* +=================================================================== + +MISC + +=================================================================== +*/ +const dplane_t *GetPlaneFromFace( const dface_t *face ) +{ + ASSERT( face != NULL ); + + if( face->side ) + return &g_backplanes[face->planenum]; + return &g_dplanes[face->planenum]; +} + +const dplane_t *GetPlaneFromFace( const uint facenum ) +{ + ASSERT( facenum < MAX_MAP_FACES ); + + dface_t *face = &g_dfaces[facenum]; + + if( face->side ) + return &g_backplanes[face->planenum]; + return &g_dplanes[face->planenum]; +} + +dleaf_t *PointInLeaf( const vec3_t point ) +{ + int nodenum = 0; + + while( nodenum >= 0 ) + { + dnode_t *node = &g_dnodes[nodenum]; + + if( PlaneDiff( point, &g_dplanes[node->planenum] ) > 0 ) + nodenum = node->children[0]; + else nodenum = node->children[1]; + } + + return &g_dleafs[-nodenum - 1]; +} + +dleaf_t *PointInLeaf_r( int nodenum, const vec3_t point ) +{ + dplane_t *plane; + dnode_t *node; + vec_t dist; + + while( nodenum >= 0 ) + { + node = &g_dnodes[nodenum]; + plane = &g_dplanes[node->planenum]; + dist = DotProduct( point, plane->normal ) - plane->dist; + + if( dist > HUNT_WALL_EPSILON ) + { + nodenum = node->children[0]; + } + else if( dist < -HUNT_WALL_EPSILON ) + { + nodenum = node->children[1]; + } + else + { + dleaf_t *result[2]; + + result[0] = PointInLeaf_r( node->children[0], point ); + result[1] = PointInLeaf_r( node->children[1], point ); + + if( result[0] == g_dleafs || result[0]->contents == CONTENTS_SOLID ) + return result[0]; + + if( result[1] == g_dleafs || result[1]->contents == CONTENTS_SOLID ) + return result[1]; + + if( result[0]->contents == CONTENTS_SKY ) + return result[0]; + + if( result[1]->contents == CONTENTS_SKY ) + return result[1]; + + return result[0]; + } + } + + return &g_dleafs[-nodenum - 1]; +} + +dleaf_t *PointInLeaf2( const vec3_t point ) +{ + return PointInLeaf_r( 0, point ); +} + +/* +============== +GetVisCache + +decompress visibility string +============== +*/ +int GetVisCache( int lastoffset, int offset, byte *pvs ) +{ + // get the PVS for the pos to limit the number of checks + if( !g_visdatasize ) + { + if( offset != lastoffset ) + { + memset( pvs, 255, (g_numvisleafs + 7) / 8 ); + lastoffset = -1; + } + } + else if( offset != lastoffset ) + { + if( offset == -1 ) memset( pvs, 0, (g_numvisleafs + 7) / 8 ); + else DecompressVis( &g_dvisdata[offset], pvs ); + lastoffset = offset; + } + + return lastoffset; +} + +/* +============== +SetDLightVis + +Init dlight visibility +============== +*/ +void SetDLightVis( directlight_t *dl, int leafnum ) +{ + if( !dl->pvs ) + dl->pvs = (byte *)Mem_Alloc( (g_numvisleafs + 7) / 8 ); + GetVisCache( -2, g_dleafs[leafnum].visofs, dl->pvs ); +} + +/* +============== +MergeDLightVis + +Merge dlight vis with another leaf +============== +*/ +void MergeDLightVis( directlight_t *dl, int leafnum ) +{ + if( !dl->pvs ) + { + SetDLightVis( dl, leafnum ); + } + else + { + byte pvs[(MAX_MAP_LEAFS + 7) / 8]; + + GetVisCache( -2, g_dleafs[leafnum].visofs, pvs ); + + // merge both vis graphs + for( int i = 0; i < (g_numvisleafs + 7) / 8; i++ ) + dl->pvs[i] |= pvs[i]; + } +} + +/* +============== +PatchPlaneDist + +Fixes up patch planes for brush models with an origin brush +============== +*/ +vec_t PatchPlaneDist( patch_t *patch ) +{ + const dplane_t *plane = GetPlaneFromFace( patch->faceNumber ); + + return DotProduct( g_face_offset[patch->faceNumber], plane->normal ) + plane->dist; +} + +bool PvsForOrigin( const vec3_t org, byte *pvs ) +{ + dleaf_t *leaf; + + if( !g_visdatasize ) + { + memset( pvs, 255, (g_numvisleafs + 7) / 8 ); + return true; + } + + leaf = PointInLeaf( org ); + if( leaf->visofs == -1 ) + COM_FatalError( "leaf->visofs == -1\n" ); + + if( leaf->contents == CONTENTS_SOLID ) + return false; + + DecompressVis( &g_dvisdata[leaf->visofs], pvs ); + return true; +} + +/* +============= +HuntForWorld + +never return CONTENTS_SKY or CONTENTS_SOLID leafs +============= +*/ +dleaf_t *HuntForWorld( vec3_t point, const vec3_t plane_offset, const dplane_t *plane, int hunt_size, vec_t hunt_scale, vec_t hunt_offset ) +{ + vec3_t current_point; + vec3_t original_point; + dleaf_t *best_leaf = NULL; + vec_t best_dist = 99999999.0; + dplane_t new_plane = *plane; + int a, x, y, z; + vec3_t best_point; + vec3_t scales; + dleaf_t *leaf; + + scales[0] = 0.0; + scales[1] = -hunt_scale; + scales[2] = hunt_scale; + + VectorCopy( point, best_point ); + VectorCopy( point, original_point ); + + new_plane.dist += DotProduct( plane->normal, plane_offset ); + + for( a = 0; a < hunt_size; a++ ) + { + for( x = 0; x < 3; x++ ) + { + current_point[0] = original_point[0] + (scales[x % 3] * a); + + for( y = 0; y < 3; y++ ) + { + current_point[1] = original_point[1] + (scales[y % 3] * a); + + for( z = 0; z < 3; z++ ) + { + if( a == 0 && ( x || y || z )) + { + continue; + } + + vec3_t delta; + vec_t dist; + + current_point[2] = original_point[2] + (scales[z % 3] * a); + + dist = DotProduct( current_point, new_plane.normal ) - new_plane.dist - hunt_offset; + VectorMA( current_point, -dist, new_plane.normal, current_point ); + VectorSubtract( current_point, original_point, delta ); + dist = DotProduct( delta, delta ); + + if( dist < best_dist ) + { + if(( leaf = PointInLeaf2( current_point )) != g_dleafs ) + { + if(( leaf->contents != CONTENTS_SKY ) && ( leaf->contents != CONTENTS_SOLID )) + { + if( x || y || z ) + { + VectorCopy( current_point, best_point ); + best_dist = dist; + best_leaf = leaf; + continue; + } + else + { + VectorCopy( current_point, point ); + return leaf; + } + } + } + } + } + } + } + + if( best_leaf ) + break; + } + + VectorCopy( best_point, point ); + return best_leaf; +} + +dworldlight_t *AllocWorldLight( word *lightnum ) +{ + if( g_numworldlights == MAX_MAP_WORLDLIGHTS ) + COM_FatalError( "MAX_MAP_WORLDLIGHTS limit exceeded\n" ); + + if( lightnum ) *lightnum = g_numworldlights; + + return &g_dworldlights[g_numworldlights++]; +} + +void InitWorldLightFromDlight( directlight_t *dl, int leafnum ) +{ + dworldlight_t *wl; + + if( dl->type != emit_skylight && leafnum == 0 ) + return; // light that be removed + + wl = AllocWorldLight( &dl->lightnum ); + + wl->emittype = dl->type; + wl->style = dl->style; + VectorCopy( dl->origin, wl->origin ); + VectorCopy( dl->intensity, wl->intensity ); + VectorCopy( dl->normal, wl->normal ); + wl->stopdot = dl->stopdot; + wl->stopdot2 = dl->stopdot2; + wl->fade = dl->fade; + wl->falloff = dl->falloff; + wl->leafnum = leafnum; // leaf where light is located + wl->radius = dl->radius; + wl->facenum = dl->facenum; + wl->modelnumber = dl->modelnum; +} + +void InitWorldLightFromPatch( dworldlight_t *wl, patch_t *p ) +{ + dleaf_t *leaf; + + leaf = PointInLeaf( p->origin ); + + wl->emittype = emit_surface; + wl->style = p->emitstyle; + VectorCopy( p->origin, wl->origin ); + + VectorCopy( p->baselight, wl->intensity ); + VectorScale( wl->intensity, p->area, wl->intensity ); + VectorScale( wl->intensity, p->exposure, wl->intensity ); + + if( !VectorIsNull( p->reflectivity )) + { + VectorScale( wl->intensity, 0.5 / M_PI, wl->intensity ); + VectorMultiply( wl->intensity, p->reflectivity, wl->intensity ); + } + else + { + VectorScale( wl->intensity, DIRECT_SCALE, wl->intensity ); + } + + VectorCopy( GetPlaneFromFace( p->faceNumber )->normal, wl->normal ); + wl->stopdot = wl->stopdot2 = 0.0f; // not used + wl->fade = p->fade; // constant + wl->falloff = falloff_valve; // inverse square falloff + wl->leafnum = leaf - g_dleafs; // leaf where light is located + wl->radius = VectorLength2( wl->intensity ) * 0.5f; + wl->facenum = p->faceNumber; + wl->modelnumber = p->modelnum; +} + +// ===================================================================================== +// CalcSightArea +// ===================================================================================== +vec_t CalcSightArea( const vec3_t receiver_origin, const vec3_t receiver_normal, const winding_t *emitter_winding, int skylevel ) +{ + // maybe there are faster ways in calculating the weighted area, but at least this way is not bad. + int numedges = emitter_winding->numpoints; + vec3_t *edges = (vec3_t *)Mem_Alloc( numedges * sizeof( vec3_t )); + vec3_t *pnormal, *pedge; + vec_t *psize, dot; + vec_t area = 0.0; + int i, j; + + for( int x = 0; x < numedges; x++ ) + { + vec3_t v1, v2, normal; + + VectorSubtract( emitter_winding->p[x], receiver_origin, v1 ); + VectorSubtract( emitter_winding->p[(x + 1) % numedges], receiver_origin, v2 ); + CrossProduct( v1, v2, normal ); // pointing inward + + if( !VectorNormalize( normal )) + break; + VectorCopy( normal, edges[x] ); + } + + if( x != numedges ) + { + Mem_Free( edges ); + return area; + } + + for( i = 0, pnormal = g_skynormals[skylevel], psize = g_skynormalsizes[skylevel]; i < g_numskynormals[skylevel]; i++, pnormal++, psize++ ) + { + dot = DotProduct( *pnormal, receiver_normal ); + if( dot <= 0 ) continue; + + for( j = 0, pedge = edges; j < numedges; j++, pedge++ ) + { + if( DotProduct( *pnormal, *pedge ) <= 0 ) + break; + } + + if( j < numedges ) + continue; + area += dot * (*psize); + } + + area = area * 4.0 * M_PI; // convert to absolute sphere area + Mem_Free( edges ); + + return area; +} + +// ===================================================================================== +// GetAlternateOrigin +// ===================================================================================== +void GetAlternateOrigin( const vec3_t pos, const vec3_t normal, const patch_t *patch, vec3_t origin ) +{ + const dplane_t *faceplane; + const vec_t *faceplaneoffset; + const vec_t *facenormal; + dplane_t clipplane; + winding_t *w; + + faceplane = GetPlaneFromFace( patch->faceNumber ); + faceplaneoffset = g_face_offset[patch->faceNumber]; + facenormal = faceplane->normal; + VectorCopy( normal, clipplane.normal ); + clipplane.dist = DotProduct( pos, clipplane.normal ); + + if( WindingOnPlaneSide( patch->winding, clipplane.normal, clipplane.dist, ON_EPSILON ) != SIDE_CROSS ) + { + VectorCopy( patch->origin, origin ); + } + else + { + w = CopyWinding( patch->winding ); + + ChopWindingInPlace( &w, clipplane.normal, clipplane.dist, ON_EPSILON, false ); + + if( w == NULL ) + { + VectorCopy( patch->origin, origin ); + } + else + { + vec3_t v, point, bestpoint; + vec_t dist, bestdist = -1.0; + bool found = false; + vec3_t center; + + WindingCenter( w, center ); + + VectorMA( center, DEFAULT_HUNT_OFFSET, facenormal, point ); + + if( HuntForWorld( point, faceplaneoffset, faceplane, 2, 1.0, DEFAULT_HUNT_OFFSET )) + { + VectorSubtract( point, center, v ); + dist = VectorLength( v ); + + if( !found || dist < bestdist ) + { + VectorCopy( point, bestpoint ); + bestdist = dist; + found = true; + } + } + + if( !found ) + { + for( int i = 0; i < (int)w->numpoints; i++ ) + { + const vec_t *p1 = w->p[i]; + const vec_t *p2 = w->p[(i + 1) % w->numpoints]; + + VectorAdd( p1, p2, point ); + VectorAdd( point, center, point ); + VectorScale( point, 1.0 / 3.0, point ); + VectorMA( point, DEFAULT_HUNT_OFFSET, facenormal, point ); + + if( HuntForWorld( point, faceplaneoffset, faceplane, 1, 0.0, DEFAULT_HUNT_OFFSET )) + { + VectorSubtract( point, center, v ); + dist = VectorLength( v ); + + if( !found || dist < bestdist ) + { + VectorCopy( point, bestpoint ); + bestdist = dist; + found = true; + } + } + } + } + + if( found ) VectorCopy( bestpoint, origin ); + else VectorCopy( patch->origin, origin ); + + FreeWinding( w ); + } + } +} + +/* +============= +MakeBackplanes +============= +*/ +void MakeBackplanes( void ) +{ + for( int i = 0; i < g_numplanes; i++ ) + { + VectorNegate( g_dplanes[i].normal, g_backplanes[i].normal ); + g_backplanes[i].dist = -g_dplanes[i].dist; + } +} + +/* +=================================================================== + + TEXTURE LIGHT VALUES + +=================================================================== +*/ +static texlight_t g_texlights[MAX_TEXLIGHTS]; +static int g_num_texlights; + +int ParseInfoTexlights( entity_t *mapent ) +{ + int numtexlights = 0; + float r, g, b, i; + int j, values; + epair_t *ep; + + if( Q_strcmp( ValueForKey( mapent, "classname" ), "info_texlights" )) + return 0; + + MsgDev( D_REPORT, "Reading texlights from info_texlights map entity\n" ); + + for( ep = mapent->epairs; ep; ep = ep->next ) + { + if( !Q_strcmp( ep->key, "classname" ) || !Q_strcmp( ep->key, "origin" )) + continue; // we dont care about these keyvalues + + values = sscanf( ep->value, "%f %f %f %f", &r, &g, &b, &i ); + + if( values == 1 ) + { + g = b = r; + } + else if( values == 4 ) // use brightness value. + { + r *= i / 255.0; + g *= i / 255.0; + b *= i / 255.0; + } + else if( values != 3 ) + { + MsgDev( D_WARN, "ignoring bad texlight '%s' in info_texlights entity", ep->key ); + continue; + } + + for( j = 0; j < g_num_texlights; j++ ) + { + texlight_t *tl = &g_texlights[j]; + + if( !Q_strcmp( tl->name, ep->key )) + { + if( !Q_strcmp( tl->filename, "info_texlights" )) + { + MsgDev( D_REPORT, "duplication of '%s' in info_texlights map entity!\n", tl->name ); + } + else if( tl->value[0] != r || tl->value[1] != g || tl->value[2] != b ) + { + MsgDev( D_REPORT, "overriding '%s' from '%s' with info_texlights map entity!\n", + tl->name, tl->filename ); + } + else + { + MsgDev( D_WARN, "redundant '%s' def in '%s' AND info_texlights map entity!\n", + tl->name, tl->filename ); + } + break; + } + } + + Q_strncpy( g_texlights[j].name, ep->key, sizeof( g_texlights[0].name )); + g_texlights[j].filename = "info_texlights"; + VectorSet( g_texlights[j].value, r, g, b ); + g_texlights[j].fade = 1.0f; + numtexlights++; + + g_num_texlights = Q_max( g_num_texlights, j + 1 ); + + if( g_num_texlights == MAX_TEXLIGHTS ) + COM_FatalError( "MAX_TEXLIGHTS limit exceeded\n" ); + } + + return numtexlights; +} + +int ParseSurfaceLights( entity_t *mapent ) +{ + char name[64]; + char *pLight = NULL; + char *pColor = NULL; + vec3_t intensity; + int j, argCnt; + + if( Q_strcmp( ValueForKey( mapent, "classname" ), "light" )) + return 0; + + if( !CheckKey( mapent, "_surface" )) + return 0; + + Q_strncpy( name, ValueForKey( mapent, "_surface" ), sizeof( name )); + if( CheckKey( mapent, "_light" )) + pLight = ValueForKey( mapent, "_light" ); + else pLight = ValueForKey( mapent, "light" ); + argCnt = ParseLightIntensity( pLight, intensity ); + if( name[0] == '*' ) name[0] = '!'; + + // quake light + if( argCnt <= 1 ) + { + double r, g, b, scaler; + + scaler = intensity[0]; + r = g = b = 0; + + if( CheckKey( mapent, "_color" )) + pColor = ValueForKey( mapent, "_color" ); + else pColor = ValueForKey( mapent, "color" ); + + if( pColor[0] && sscanf( pColor, "%lf %lf %lf", &r, &g, &b ) == 3 ) + { + intensity[0] = r * (float)scaler; + intensity[1] = g * (float)scaler; + intensity[2] = b * (float)scaler; + } + } + + for( j = 0; j < g_num_texlights; j++ ) + { + texlight_t *tl = &g_texlights[j]; + + if( !Q_strcmp( tl->name, name )) + { + if( !Q_strcmp( tl->filename, "info_texlights" )) + { + MsgDev( D_REPORT, "duplication of '%s' in info_texlights map entity!\n", tl->name ); + } + else if( !VectorCompareEpsilon( tl->value, intensity, ON_EPSILON )) + { + MsgDev( D_REPORT, "overriding '%s' from '%s' with info_texlights map entity!\n", tl->name, tl->filename ); + } + else + { + MsgDev( D_WARN, "redundant '%s' def in '%s' AND info_texlights map entity!\n", tl->name, tl->filename ); + } + break; + } + } + + Q_strncpy( g_texlights[j].name, name, sizeof( g_texlights[0].name )); + VectorCopy( intensity, g_texlights[j].value ); + g_texlights[j].filename = "light_surface"; + g_texlights[j].fade = FloatForKey( mapent, "wait" ); + + // to prevent division by zero + if( g_texlights[j].fade <= 0.0 ) g_texlights[j].fade = 1.0; + + g_num_texlights = Q_max( g_num_texlights, j + 1 ); + if( g_num_texlights == MAX_TEXLIGHTS ) + COM_FatalError( "MAX_TEXLIGHTS limit exceeded\n" ); + + return 1; +} + +// ===================================================================================== +// ReadInfoTexlights +// try and parse texlight info from the info_texlights entity +// ===================================================================================== +void ReadInfoTexlights( void ) +{ + int numtexlights = 0; + entity_t *mapent; + + for( int k = 0; k < g_numentities; k++ ) + { + mapent = &g_entities[k]; + + // all the vertex cache ID's should be removed now + RemoveKey( mapent, "_vlight_id" ); + + numtexlights += ParseInfoTexlights( mapent ); + + numtexlights += ParseSurfaceLights( mapent ); + } + + MsgDev( D_REPORT, "[%i texlights parsed from info_texlights map entity]\n\n", numtexlights ); +} + +/* +============ +ReadLightFile +============ +*/ +void ReadLightFile( const char *filename, bool use_direct_path ) +{ + int file_texlights = 0; + int result = 0; + char scan[128]; + int j, argCnt; + file_t *f; + + FS_AllowDirectPaths( use_direct_path ); + f = FS_Open( filename, "r", false ); + FS_AllowDirectPaths( false ); + if( !f ) return; + + while( result != EOF ) + { + char szTexlight[256]; + vec_t r, g, b, i = 1; + char *comment = scan; + + result = FS_Gets( f, (byte *)&scan, sizeof( scan )); + + // skip the comments + if( comment[0] == '/' && comment[1] == '/' ) + continue; + + argCnt = sscanf( scan, "%s %f %f %f %f", szTexlight, &r, &g, &b, &i ); + + if( argCnt == 2 ) + { + // eith 1+1 args, the R,G,B values are all equal to the first value + g = b = r; + } + else if( argCnt == 5 ) + { + // With 1 + 4 args, the R,G,B values are "scaled" by the fourth numeric value i; + r *= i / 255.0; + g *= i / 255.0; + b *= i / 255.0; + } + else if( argCnt != 4 ) + { + if( Q_strlen( scan ) > 4 ) + MsgDev( D_WARN, "ignoring bad texlight '%s' in %s", scan, filename ); + continue; + } + + for( j = 0; j < g_num_texlights; j++ ) + { + texlight_t *tl = &g_texlights[j]; + + if( !Q_strcmp( tl->name, szTexlight )) + { + if( !Q_strcmp( tl->filename, filename )) + { + MsgDev( D_REPORT, "duplication of '%s' in file '%s'!\n", tl->name, tl->filename ); + } + else if( tl->value[0] != r || tl->value[1] != g || tl->value[2] != b ) + { + MsgDev( D_REPORT, "overriding '%s' from '%s' with '%s'!\n", tl->name, tl->filename, filename ); + } + else + { + MsgDev( D_WARN, "redundant '%s' def in '%s' AND '%s'!\n", tl->name, tl->filename, filename ); + } + break; + } + } + + Q_strncpy( g_texlights[j].name, szTexlight, sizeof( g_texlights[0].name )); + VectorSet( g_texlights[j].value, r, g, b ); + g_texlights[j].filename = filename; + g_texlights[j].fade = 1.0f; + file_texlights++; + + g_num_texlights = Q_max( g_num_texlights, j + 1 ); + + if( g_num_texlights == MAX_TEXLIGHTS ) + COM_FatalError( "MAX_TEXLIGHTS limit exceeded\n" ); + } + + MsgDev( D_INFO, "[%i texlights parsed from '%s']\n\n", file_texlights, filename ); + FS_Close( f ); +} + +/* +============ +LightForTexture +============ +*/ +vec_t LightForTexture( const char *name, vec3_t result ) +{ + VectorClear( result ); + + for( int i = 0; i < g_num_texlights; i++ ) + { + if( !Q_stricmp( name, g_texlights[i].name )) + { + VectorCopy( g_texlights[i].value, result ); + MsgDev( D_REPORT, "Texture '%s': baselight is (%f,%f,%f).\n", name, result[0], result[1], result[2] ); + return g_texlights[i].fade; + } + } + + return 1.0f; +} + +/* +======================================================================= + +MAKE FACES + +======================================================================= +*/ +/* +============= +WindingFromFace +============= +*/ +winding_t *WindingFromFace( const dface_t *f ) +{ + int i, se, v; + dvertex_t *dv; + winding_t *w; + + w = AllocWinding( f->numedges ); + w->numpoints = f->numedges; + + for( i = 0; i < f->numedges; i++ ) + { + se = g_dsurfedges[f->firstedge + i]; + if( se < 0 ) v = g_dedges[-se].v[1]; + else v = g_dedges[se].v[0]; + dv = &g_dvertexes[v]; + VectorCopy( dv->point, w->p[i] ); + } + + RemoveColinearPointsEpsilon( w, ON_EPSILON ); + + return w; +} + +/* +============= +BaseLightForFace +============= +*/ +vec_t BaseLightForFace( dface_t *f, vec3_t light, vec3_t reflectivity ) +{ + int miptex = g_texinfo[f->texinfo].miptex; + vec_t fade; + miptex_t *mt; + + // check for light emited by texture + mt = GetTextureByMiptex( miptex ); + if( !mt ) return 1.0f; + + fade = LightForTexture( mt->name, light ); + VectorClear( reflectivity ); + +#ifdef HLRAD_REFLECTIVITY + int samples = mt->width * mt->height; + byte *pal = ((byte *)mt) + mt->offsets[0] + (((mt->width * mt->height) * 85) >> 6); + byte *buf = ((byte *)mt) + mt->offsets[0]; + vec3_t total; + + // check for cache + if( g_texture_init[miptex] ) + { + VectorCopy( g_reflectivity[miptex], reflectivity ); + return fade; + } + + pal += sizeof( short ); // skip colorsize + VectorClear( total ); + + for( int i = 0; i < samples; i++ ) + { + vec3_t reflectivity; + + if( mt->name[0] == '{' && buf[i] == 0xFF ) + { + VectorClear( reflectivity ); + } + else + { + int texel = buf[i]; + reflectivity[0] = pow( pal[texel*3+0] * (1.0f / 255.0f), DEFAULT_TEXREFLECTGAMMA ); + reflectivity[1] = pow( pal[texel*3+1] * (1.0f / 255.0f), DEFAULT_TEXREFLECTGAMMA ); + reflectivity[2] = pow( pal[texel*3+2] * (1.0f / 255.0f), DEFAULT_TEXREFLECTGAMMA ); + VectorScale( reflectivity, DEFAULT_TEXREFLECTSCALE, reflectivity ); + } + VectorAdd( total, reflectivity, total ); + } + + VectorScale( total, 1.0 / (double)(mt->width * mt->height), g_reflectivity[miptex] ); + VectorCopy( g_reflectivity[miptex], reflectivity ); + MsgDev( D_REPORT, "Texture '%s': reflectivity is (%f,%f,%f).\n", mt->name, reflectivity[0], reflectivity[1], reflectivity[2] ); + g_texture_init[miptex] = true; +#endif + return fade; +} + +static void UpdateEmitterInfo( patch_t *patch ) +{ + const vec_t *origin = patch->origin; + const winding_t *winding = patch->winding; + vec_t radius = ON_EPSILON; + + for( int x = 0; x < winding->numpoints; x++ ) + { + vec3_t delta; + vec_t dist; + VectorSubtract( winding->p[x], origin, delta ); + dist = VectorLength( delta ); + radius = Q_max( dist, radius ); + } + + int skylevel = ACCURATEBOUNCE_DEFAULT_SKYLEVEL; + vec_t area = WindingArea( winding ); + vec_t size = 0.8f; + + if( area < size * radius * radius ) // the shape is too thin + { + skylevel++; + size *= 0.25f; + + if( area < size * radius * radius ) + { + skylevel++; + size *= 0.25f; + + if( area < size * radius * radius ) + { + // stop here + radius = sqrt( area / size ); + // just decrease the range to limit the use of the new method. + // because when the area is small, the new method becomes randomized and unstable. + } + } + } + + patch->emitter_range = ACCURATEBOUNCE_THRESHOLD * radius; + patch->emitter_skylevel = skylevel; +} + +// ===================================================================================== +// PlacePatchInside +// ===================================================================================== +static bool PlacePatchInside( patch_t *patch ) +{ + const vec_t *face_offset = g_face_offset[patch->faceNumber]; + vec_t offset = DEFAULT_HUNT_OFFSET; + vec3_t v, center, point, bestpoint; + vec_t pointsfound, pointstested; + vec_t dist, bestdist = -1.0; + const dplane_t *plane; + bool found; + + plane = GetPlaneFromFace( patch->faceNumber ); + WindingCenter( patch->winding, center ); + pointsfound = pointstested = 0; + found = false; + + VectorMA( center, offset, plane->normal, point ); + pointstested++; + + if( HuntForWorld( point, face_offset, plane, 4, 0.2, offset ) || HuntForWorld( point, face_offset, plane, 4, 0.8, offset )) + { + VectorSubtract( point, center, v ); + dist = VectorLength( v ); + pointsfound++; + + if( !found || dist < bestdist ) + { + VectorCopy( point, bestpoint ); + bestdist = dist; + found = true; + } + } + + for( int i = 0; i < patch->winding->numpoints; i++ ) + { + const vec_t *p1 = patch->winding->p[i]; + const vec_t *p2 = patch->winding->p[(i+1) % patch->winding->numpoints]; + + VectorAdd( p1, p2, point ); + VectorAdd( point, center, point ); + VectorScale( point, 1.0 / 3.0, point ); + VectorMA( point, offset, plane->normal, point ); + pointstested++; + + if( HuntForWorld( point, face_offset, plane, 4, 0.2, offset ) || HuntForWorld( point, face_offset, plane, 4, 0.8, offset )) + { + VectorSubtract( point, center, v ); + dist = VectorLength( v ); + pointsfound++; + + if( !found || dist < bestdist ) + { + VectorCopy( point, bestpoint ); + bestdist = dist; + found = true; + } + } + } + + patch->exposure = pointsfound / pointstested; + + if( found ) + { + VectorCopy( bestpoint, patch->origin ); + return true; + } + else + { + VectorMA( center, offset, plane->normal, patch->origin ); + SetBits( patch->flags, PATCH_OUTSIDE ); + return false; + } +} + +// ===================================================================================== +// +// SUBDIVIDE PATCHES +// +// ===================================================================================== +// ===================================================================================== +// CutWindingWithGrid +// Caller must free this returned value at some point +// ===================================================================================== +static void CutWindingWithGrid( patch_t *patch, const dplane_t *plA, const dplane_t *plB ) +{ + // patch->winding->m_NumPoints must > 0 + // plA->dist and plB->dist will not be used + winding_t *winding = NULL; + vec_t chop, epsilon; + const int max_gridsize = 64; + vec_t gridstartA, minA, maxA; + vec_t gridstartB, minB, maxB; + int gridsizeA; + int gridsizeB; + vec_t gridchopA; + vec_t gridchopB; + int i, numstrips; + + winding = CopyWinding( patch->winding ); // perform all the operations on the copy + chop = Q_max( 1.0, patch->chop ); + epsilon = 0.6; + + // optimize the grid + minA = minB = BOGUS_RANGE; + maxA = maxB = -BOGUS_RANGE; + + for( int x = 0; x < winding->numpoints; x++ ) + { + vec_t dotA, dotB; + vec_t *point; + + point = winding->p[x]; + dotA = DotProduct( point, plA->normal ); + minA = Q_min( minA, dotA ); + maxA = Q_max( maxA, dotA ); + dotB = DotProduct( point, plB->normal ); + minB = Q_min( minB, dotB ); + maxB = Q_max( maxB, dotB ); + } + + gridchopA = chop; + gridsizeA = (int)ceil(( maxA - minA - 2 * epsilon ) / gridchopA ); + gridsizeA = Q_max( 1, gridsizeA ); + + if( gridsizeA > max_gridsize ) + { + gridsizeA = max_gridsize; + gridchopA = (maxA - minA) / (vec_t)gridsizeA; + } + gridstartA = (minA + maxA) / 2.0 - (gridsizeA / 2.0) * gridchopA; + + gridchopB = chop; + gridsizeB = (int)ceil(( maxB - minB - 2 * epsilon ) / gridchopB); + gridsizeB = Q_max( 1, gridsizeB ); + + if( gridsizeB > max_gridsize ) + { + gridsizeB = max_gridsize; + gridchopB = (maxB - minB) / (vec_t)gridsizeB; + } + gridstartB = (minB + maxB) / 2.0 - (gridsizeB / 2.0) * gridchopB; + + // cut the winding by the direction of plane A and save into windingArray + g_numwindings = 0; + + for( i = 1; i < gridsizeA; i++ ) + { + winding_t *front = NULL; + winding_t *back = NULL; + vec_t dist; + + dist = gridstartA + i * gridchopA; + ClipWindingEpsilon( winding, plA->normal, dist, ON_EPSILON, &front, &back ); + + if( !front || WindingOnPlaneSide( front, plA->normal, dist, epsilon ) == SIDE_ON ) // ended + { + if( front ) + { + FreeWinding( front ); + front = NULL; + } + if( back ) + { + FreeWinding( back ); + back = NULL; + } + break; + } + + if( !back || WindingOnPlaneSide( back, plA->normal, dist, epsilon ) == SIDE_ON ) // didn't begin + { + if( front ) + { + FreeWinding( front ); + front = NULL; + } + if( back ) + { + FreeWinding( back ); + back = NULL; + } + continue; + } + + FreeWinding( winding ); + winding = NULL; + + g_windingArray[g_numwindings] = back; + g_numwindings++; + back = NULL; + + winding = front; + front = NULL; + } + + g_windingArray[g_numwindings] = winding; + g_numwindings++; + winding = NULL; + + // cut by the direction of plane B + numstrips = g_numwindings; + + for( i = 0; i < numstrips; i++ ) + { + winding_t *strip = g_windingArray[i]; + + g_windingArray[i] = NULL; + + for( int j = 1; j < gridsizeB; j++ ) + { + winding_t *front = NULL; + winding_t *back = NULL; + vec_t dist; + + dist = gridstartB + j * gridchopB; + ClipWindingEpsilon( strip, plB->normal, dist, ON_EPSILON, &front, &back ); + + if( !front || WindingOnPlaneSide( front, plB->normal, dist, epsilon ) == SIDE_ON ) // ended + { + if( front ) + { + FreeWinding( front ); + front = NULL; + } + if( back ) + { + FreeWinding( back ); + back = NULL; + } + break; + } + if( !back || WindingOnPlaneSide( back, plB->normal, dist, epsilon ) == SIDE_ON ) // didn't begin + { + if( front ) + { + FreeWinding( front ); + front = NULL; + } + if( back ) + { + FreeWinding( back ); + back = NULL; + } + continue; + } + + FreeWinding( strip ); + strip = NULL; + + g_windingArray[g_numwindings] = back; + g_numwindings++; + back = NULL; + + strip = front; + front = NULL; + } + + g_windingArray[g_numwindings] = strip; + g_numwindings++; + strip = NULL; + } + + FreeWinding( patch->winding ); + patch->winding = NULL; +} + +// ===================================================================================== +// GetGridPlanes +// From patch, determine perpindicular grid planes to subdivide with (returned in planeA and planeB) +// assume S and T is perpindicular (they SHOULD be in worldcraft 3.3 but aren't always...) +// ===================================================================================== +static void GetGridPlanes( const patch_t *p, dplane_t *pl ) +{ + const dface_t *f = g_dfaces + p->faceNumber; + dtexinfo_t *tx = &g_texinfo[f->texinfo]; + const dplane_t *faceplane = GetPlaneFromFace( p->faceNumber ); + dplane_t *plane = pl; + float lmvecs[2][4]; + + LightMatrixFromTexMatrix( tx, lmvecs ); + + for( int x = 0; x < 2; x++, plane++ ) + { + // cut the patch along texel grid planes + vec_t val = DotProduct( faceplane->normal, lmvecs[!x] ); + VectorMA( lmvecs[!x], -val, faceplane->normal, plane->normal ); + VectorNormalize( plane->normal ); + plane->dist = DotProduct( plane->normal, p->origin ); + } +} + +// ===================================================================================== +// SubdividePatch +// ===================================================================================== +static void SubdividePatch( patch_t *patch ) +{ + dplane_t planes[2]; + dplane_t *plA = &planes[0]; + dplane_t *plB = &planes[1]; + patch_t *new_patch; + winding_t **winding; + int x; + + memset( g_windingArray, 0, sizeof( g_windingArray )); + g_numwindings = 0; + + GetGridPlanes( patch, planes ); + CutWindingWithGrid( patch, plA, plB ); + + x = 0; + patch->next = NULL; + winding = g_windingArray; + + while( *winding == NULL ) + { + winding++; + x++; + } + + patch->winding = *winding; + winding++; + x++; + + patch->area = WindingAreaAndBalancePoint( patch->winding, patch->origin ); + SetBits( patch->flags, PATCH_SOURCE ); + PlacePatchInside( patch ); + UpdateEmitterInfo( patch ); + + new_patch = &g_patches[g_num_patches]; + + for( ; x < g_numwindings; x++, winding++ ) + { + if( *winding ) + { + memcpy( new_patch, patch, sizeof( patch_t )); + ClearBits( new_patch->flags, PATCH_SOURCE ); + + new_patch->winding = *winding; + new_patch->area = WindingAreaAndBalancePoint( new_patch->winding, new_patch->origin ); + SetBits( new_patch->flags, PATCH_CHILD ); + PlacePatchInside( new_patch ); + UpdateEmitterInfo( new_patch ); + g_num_patches++; + new_patch++; + + if( g_num_patches == MAX_PATCHES ) + COM_FatalError( "MAX_PATCHES limit exceeded\n" ); + } + } + // ATTENTION: We let SortPatches relink all the ->next correctly! instead of doing it here too which is somewhat complicated +} + +/* +============ +getScale + +compute scale for patch +============ +*/ +static vec_t getScale( const patch_t *patch ) +{ + dface_t *f = &g_dfaces[patch->faceNumber]; + dtexinfo_t *tx = &g_texinfo[f->texinfo]; + const dplane_t *faceplane = GetPlaneFromFace( f ); + vec3_t outvecs[2]; + vec_t scale[2]; + vec_t dot; + + // snap texture "vecs" to faceplane without affecting texture alignment + for( int x = 0; x < 2; x++ ) + { + dot = DotProduct( faceplane->normal, tx->vecs[x] ); + VectorMA( tx->vecs[x], -dot, faceplane->normal, outvecs[x] ); + } + + scale[0] = 1.0 / Q_max( NORMAL_EPSILON, VectorLength( outvecs[0] )); + scale[1] = 1.0 / Q_max( NORMAL_EPSILON, VectorLength( outvecs[1] )); + + // don't care about the angle between vecs[0] and vecs[1] + // (given the length of "vecs", smaller angle = larger texel area), + // because gridplanes will have the same angle (also smaller angle = larger patch area) + return sqrt( scale[0] * scale[1] ); +} + +/* +============ +getEmitMode + +some pathches are emit_surfaces +============ +*/ +static bool getEmitMode( const patch_t *patch ) +{ + bool emitmode = false; + vec_t value; + + if( !VectorIsNull( patch->reflectivity )) + value = DotProduct( patch->baselight, patch->reflectivity ) / 3.0; + else value = VectorAvg( patch->baselight ); + + // this patch is emitting surface + if( value >= DLIGHT_THRESHOLD ) + emitmode = true; + + return emitmode; +} + +/* +============ +getChop + +compute chop value for patch +============ +*/ +static vec_t getChop( const patch_t *patch ) +{ + vec_t rval; + + if( FBitSet( patch->flags, PATCH_EMITLIGHT )) + rval = g_texchop * getScale( patch ); + else rval = g_chop * getScale( patch ); + + return rval; +} + +/* +============= +IsSpecial +============= +*/ +static bool IsSpecial( const dface_t *f ) +{ + return FBitSet( g_texinfo[f->texinfo].flags, TEX_SPECIAL ); +} + +/* +============= +MakePatchForFace +============= +*/ +bool MakePatchForFace( int modelnum, int fn, winding_t *w, int style ) +{ + dface_t *f = g_dfaces + fn; + vec3_t centroid = { 0, 0, 0 }; + vec3_t mins, maxs, delta; + patch_t *patch; + vec_t fade; + dworldlight_t *wl; + int i; + + // No patches at all for the sky! + if( IsSpecial( f )) return false; + + if( w->numpoints < 3 ) // g-cont. how this possible? + return false; + + patch = &g_patches[g_num_patches]; + if( g_num_patches == MAX_PATCHES ) + COM_FatalError( "MAX_PATCHES limit exceeded\n" ); + memset( patch, 0, sizeof( patch_t )); + + patch->area = WindingAreaAndBalancePoint( w, patch->origin ); + patch->modelnum = modelnum; + patch->faceNumber = fn; + patch->winding = w; + + // update debug info + g_totalarea += patch->area; + + fade = BaseLightForFace( f, patch->baselight, patch->reflectivity ); + + for( i = 0; i < MAXLIGHTMAPS; i++ ) + patch->totalstyle[i] = 255; + + if( getEmitMode( patch )) + { + SetBits( patch->flags, PATCH_EMITLIGHT ); + + if( style ) + { + patch->totalstyle[0] = style; + patch->emitstyle = style; + } + } + + SetBits( patch->flags, PATCH_SOURCE ); + patch->scale = getScale( patch ); + patch->chop = getChop( patch ); + PlacePatchInside( patch ); + UpdateEmitterInfo( patch ); + patch->fade = fade; + + // NOTE: we alloc worldlights only once per face. + // all child patches after subdivision has the same lightnum + if( FBitSet( patch->flags, PATCH_EMITLIGHT )) + { + wl = AllocWorldLight( &patch->lightnum ); + InitWorldLightFromPatch( wl, patch ); + } + + g_face_patches[fn] = patch; + g_num_patches++; + + // per-face data + for( i = 0; i < f->numedges; i++ ) + { + int edge = g_dsurfedges[f->firstedge + i]; + + if( edge > 0 ) + { + VectorAdd( g_dvertexes[g_dedges[edge].v[0]].point, centroid, centroid ); + VectorAdd( g_dvertexes[g_dedges[edge].v[1]].point, centroid, centroid ); + } + else + { + VectorAdd( g_dvertexes[g_dedges[-edge].v[1]].point, centroid, centroid ); + VectorAdd( g_dvertexes[g_dedges[-edge].v[0]].point, centroid, centroid ); + } + } + + VectorScale( centroid, 1.0 / (f->numedges * 2), centroid ); + VectorAdd( centroid, g_face_offset[fn], g_face_centroids[fn] ); // save them for generating the patch normals later. + + WindingBounds( patch->winding, mins, maxs ); + VectorSubtract( maxs, mins, delta ); + + if( VectorLength( delta ) > patch->chop ) + { + if( patch->area < 1.0 ) + MsgDev( D_WARN, "patch at face %d tiny area (%4.3f) not subdividing\n", patch->faceNumber, patch->area ); + else SubdividePatch( patch ); + } + + return true; +} + +/* +============= +MakePatches +============= +*/ +void MakePatches( void ) +{ + int fn, style = 0; + vec3_t light_origin; + vec3_t model_center; + bool b_light_origin; + bool b_model_center; + int i, j, k; + vec3_t origin; + entity_t *ent; + dmodel_t *mod; + dface_t *f; + winding_t *w; + char *s; + + g_patches = (patch_t *)Mem_Alloc( sizeof( patch_t ) * MAX_PATCHES ); + g_numworldlights = 0; + + for( i = 0; i < g_nummodels; i++ ) + { + mod = &g_dmodels[i]; + ent = EntityForModel( i ); + VectorClear( origin ); + b_light_origin = false; + b_model_center = false; + + // bmodels with origin brushes need to be offset + // into their in-use position + if( *( s = ValueForKey( ent, "origin" ))) + { + double v1, v2, v3; + + if( sscanf( s, "%lf %lf %lf", &v1, &v2, &v3 ) == 3 ) + VectorSet( origin, v1, v2, v3 ); + } + + // allow models to be lit in an alternate location (pt1) + if( *( s = ValueForKey( ent, "light_origin" ))) + { + entity_t *e = FindTargetEntity( s ); + + if( e ) + { + if( *( s = ValueForKey( e, "origin" ))) + { + double v1, v2, v3; + + if( sscanf( s, "%lf %lf %lf", &v1, &v2, &v3 ) == 3 ) + { + VectorSet( light_origin, v1, v2, v3 ); + b_light_origin = true; + } + } + } + } + + // allow models to be lit in an alternate location (pt2) + if( *( s = ValueForKey( ent, "model_center" ))) + { + double v1, v2, v3; + + if( sscanf( s, "%lf %lf %lf", &v1, &v2, &v3 ) == 3 ) + { + VectorSet( model_center, v1, v2, v3 ); + b_model_center = true; + } + } +#ifndef HLRAD_PARANOIA_BUMP + if( CheckKey( ent, "style" )) + style = abs( IntForKey( ent, "style" )); + else style = 0; +#endif + // allow models to be lit in an alternate location (pt3) + if( b_light_origin && b_model_center ) + { + VectorSubtract( light_origin, model_center, origin ); + } + + for( j = 0; j < mod->numfaces; j++ ) + { + fn = mod->firstface + j; + g_face_entity[fn] = ent; + VectorCopy( origin, g_face_offset[fn] ); + f = &g_dfaces[fn]; + w = WindingFromFace( f ); + + // adjust model origins + for( k = 0; k < w->numpoints; k++ ) + VectorAdd( w->p[k], origin, w->p[k] ); + + // some faces won't create patch on them + if( !MakePatchForFace( i, fn, w, style )) + FreeWinding( w ); + } + } + + MsgDev( D_REPORT, "%i square feet [%.2f square inches]\n", (int)(g_totalarea / 144), g_totalarea ); + MsgDev( D_INFO, "%i base patches, required %s\n", g_num_patches, Q_memprint( sizeof( patch_t ) * g_num_patches )); + MsgDev( D_NOTE, "patch_t = %s\n", Q_memprint( sizeof( patch_t ))); + MsgDev( D_NOTE, "sample_t = %s\n", Q_memprint( sizeof( sample_t ))); +} + +// ===================================================================================== +// patch_sorter +// ===================================================================================== +static int CDECL patch_sorter( const void *p1, const void *p2 ) +{ + patch_t *patch1 = (patch_t *)p1; + patch_t *patch2 = (patch_t *)p2; + + if( patch1->faceNumber < patch2->faceNumber ) + return -1; + else if( patch1->faceNumber > patch2->faceNumber ) + return 1; + + return 0; +} + +/* +============= +SortPatches +============= +*/ +static void SortPatches( void ) +{ + patch_t *old_patches = g_patches; + g_patches = (patch_t *)Mem_Alloc( sizeof( patch_t ) * ( g_num_patches + 1 )); + memcpy( g_patches, old_patches, g_num_patches * sizeof( patch_t )); + Mem_Free( old_patches ); + + qsort((void *)g_patches, (size_t)g_num_patches, sizeof( patch_t ), patch_sorter ); + + // fixup g_face_patches & fFixup patch->next + memset( g_face_patches, 0, sizeof( g_face_patches )); + + patch_t *patch = g_patches + 1; + patch_t *prev = g_patches; + int x; + + g_face_patches[prev->faceNumber] = prev; + + for( x = 1; x < g_num_patches; x++, patch++ ) + { + if( patch->faceNumber != prev->faceNumber ) + { + prev->next = NULL; + g_face_patches[patch->faceNumber] = patch; + } + else + { + prev->next = patch; + } + prev = patch; + } + + for( x = 0; x < g_num_patches; x++ ) + { + patch_t *patch = &g_patches[x]; + patch->leafnum = PointInLeaf( patch->origin ) - g_dleafs; + } + + // validate chains (debug thing) + for( int facenum = 0; facenum < g_numfaces; facenum++ ) + { + for( patch = g_face_patches[facenum]; patch != NULL; patch = patch->next ) + { + ASSERT( patch->faceNumber == facenum ); + } + } + + // if we haven't patches we don't need bounces + if( g_num_patches <= 0 ) g_numbounce = 0; + if( g_numbounce <= 0 ) g_indirect_sun = 0.0f; +} + +/* +============= +FreePatches +============= +*/ +static void FreePatches( void ) +{ + patch_t *patch = g_patches; + + for( int i = 0; i < g_num_patches; i++, patch++ ) + FreeWinding( patch->winding ); + + memset( g_patches, 0, sizeof( patch_t ) * g_num_patches ); + Mem_Free( g_patches ); + g_patches = NULL; +} + +/* +============= +FreeTransfers +============= +*/ +static void FreeTransfers( void ) +{ + patch_t *patch = g_patches; + + for( int i = 0; i < g_num_patches; i++, patch++ ) + { + if( patch->tData ) + { + Mem_Free( patch->tData ); + patch->tData = NULL; + } + + if( patch->tIndex ) + { + Mem_Free( patch->tIndex ); + patch->tIndex = NULL; + } + } +} + +//===================================================================== +#ifdef HLRAD_COMPRESS_TRANSFERS +static uint GetLengthOfRun( const uint *raw, const uint *end ) +{ + uint run_size = 0; + + while( raw < end ) + { + if((( *raw ) + 1 ) == (*( raw + 1 ))) + { + run_size++; + raw++; + + if( run_size >= MAX_COMPRESSED_TRANSFER_INDEX ) + return run_size; + } + else + { + return run_size; + } + } + + return run_size; +} + +static transfer_index_t *CompressTransferIndicies( const uint *tRaw, const uint rawSize, uint *iSize, int threadnum ) +{ + uint *end = (uint *)tRaw + rawSize - 1; + uint compressed_count_1 = 0; + uint compressed_count = 0; + uint *raw = (uint *)tRaw; + uint x, size = rawSize; + + if( !size ) return NULL; + + for( x = 0; x < rawSize; x++ ) + { + x += GetLengthOfRun( tRaw + x, end ); + compressed_count_1++; + } + + if( !compressed_count_1 ) + return NULL; + + transfer_index_t *CompressedArray = (transfer_index_t *)Mem_Alloc( sizeof( transfer_index_t ) * compressed_count_1 ); + transfer_index_t *compressed = CompressedArray; + + for( x = 0; x < size; x++, raw++, compressed++ ) + { + compressed->index = (*raw); + // zero based (count 0 still implies 1 item in the list, so 256 max entries result) + compressed->size = GetLengthOfRun( raw, end ); + raw += compressed->size; + x += compressed->size; + compressed_count++; // number of entries in compressed table + } + + *iSize = compressed_count; + + if( compressed_count != compressed_count_1 ) + COM_FatalError( "CompressTransferIndicies: internal error\n" ); + + g_transfer_data_size[threadnum] += sizeof( transfer_index_t ) * compressed_count; + + return CompressedArray; +} +#else +static transfer_index_t *CompressTransferIndicies( const uint *tRaw, const uint rawSize, uint *iSize, int threadnum ) +{ + uint *end = (uint *)tRaw + rawSize; + uint compressed_count = 0; + uint *raw = (uint *)tRaw; + uint x, size = rawSize; + + if( !size ) return NULL; + + transfer_index_t *CompressedArray = (transfer_index_t *)Mem_Alloc( sizeof( transfer_index_t ) * size ); + transfer_index_t *compressed = CompressedArray; + + for( x = 0; x < size; x++, raw++, compressed++ ) + { + compressed->index = (*raw); + compressed->size = 0; + compressed_count++; // number of entries in compressed table + } + + *iSize = compressed_count; + + g_transfer_data_size[threadnum] += sizeof( transfer_index_t ) * compressed_count; + + return CompressedArray; +} +#endif + +/* +=========== +MakeTransfers + +This is run by multiple threads +=========== +*/ +static void MakeTransfers( int threadnum ) +{ + transfer_data_t *tData_All = (transfer_data_t *)Mem_Alloc( sizeof( transfer_data_t ) * ( g_num_patches + 1 )); + uint *tIndex_All = (uint *)Mem_Alloc( sizeof( transfer_index_t ) * ( g_num_patches + 1 )); + patch_t **vispatches = (patch_t **)Mem_Alloc(( g_num_patches + 1 ) * sizeof( patch_t* )); + byte pvs[(MAX_MAP_LEAFS+7)/8]; + const vec_t *normal1, *normal2; + float trans, total, dist; + bool check_vis = true; + patch_t *patch1, *patch2; + int i, j, lastoffset; + int count = 0; + uint *tIndex; + transfer_data_t *tData; + vec3_t delta; + + while( 1 ) + { + if(( i = GetThreadWork( )) == -1 ) + break; + + patch1 = g_patches + i; + + // calculate visibility for the patch + if( !g_visdatasize ) + { + if( check_vis ) + { + memset( pvs, 255, (g_numvisleafs + 7) / 8 ); + check_vis = false; + } + } + else + { + dleaf_t *leaf = &g_dleafs[patch1->leafnum]; + int thisoffset = leaf->visofs; + + if( check_vis || thisoffset != lastoffset ) + { + if( thisoffset == -1 ) + { + memset( pvs, 0, (g_numvisleafs + 7) / 8 ); + } + else + { + DecompressVis( &g_dvisdata[leaf->visofs], pvs ); + } + check_vis = false; + } + lastoffset = thisoffset; + } + + const dplane_t *plane1 = GetPlaneFromFace( patch1->faceNumber ); + count = 0; + + // find out which patch2's will collect light from patch + for( j = 0, patch2 = g_patches; j < g_num_patches; j++, patch2++ ) + { + if( i == j ) continue; + + if( !patch2->leafnum || !CHECKVISBIT( pvs, patch2->leafnum - 1 )) + continue; + + const dplane_t *plane2 = GetPlaneFromFace( patch2->faceNumber ); + + if( DotProduct( patch1->origin, plane2->normal ) <= PatchPlaneDist( patch2 ) + ON_EPSILON - patch1->emitter_range ) + continue; + + // we need to do a real test + vec3_t origin1, origin2, delta; + vec_t dist; + + VectorSubtract( patch1->origin, patch2->origin, delta ); + dist = VectorLength( delta ); + + if( dist < patch2->emitter_range - ON_EPSILON ) + GetAlternateOrigin( patch1->origin, plane1->normal, patch2, origin2 ); + else VectorCopy( patch2->origin, origin2 ); + + if( DotProduct( origin2, plane1->normal ) <= PatchPlaneDist( patch1 ) + MINIMUM_PATCH_DISTANCE ) + continue; + + if( dist < patch1->emitter_range - ON_EPSILON ) + GetAlternateOrigin( patch2->origin, plane2->normal, patch1, origin1 ); + else VectorCopy( patch1->origin, origin1 ); + + if( DotProduct( origin1, plane2->normal ) <= PatchPlaneDist( patch2 ) + MINIMUM_PATCH_DISTANCE ) + continue; + + // we ignore models here, brushes only + if( TestLine( threadnum, origin1, origin2, true ) != CONTENTS_EMPTY ) + continue; + + vispatches[count] = patch2; + count++; + } + + // compute transfers for this patch + patch1->iIndex = patch1->iData = 0; + normal1 = plane1->normal; + tIndex = tIndex_All; + tData = tData_All; + + for( j = 0; j < count; j++ ) + { + bool light_behind_surface = false; + vec_t dot1, dot2; + + patch2 = vispatches[j]; + normal2 = GetPlaneFromFace( patch2->faceNumber )->normal; + + // calculate transference + VectorSubtract( patch2->origin, patch1->origin, delta ); + + // move emitter back to its plane + VectorMA( delta, -DEFAULT_HUNT_OFFSET, normal2, delta ); + + dist = VectorNormalize( delta ); + dot1 = DotProduct( delta, normal1 ); + dot2 = -DotProduct( delta, normal2 ); + + if( dot1 <= NORMAL_EPSILON ) + light_behind_surface = true; + + if( dot2 * dist <= MINIMUM_PATCH_DISTANCE ) + continue; + + // inverse square falloff factoring angle between patch normals + trans = (dot1 * dot2) / (dist * dist); + + // HLRAD_TRANSWEIRDFIX: + // we should limit "trans( patch2receive ) * patch1area" + // instead of "trans( patch2receive ) * patch2area". + // also raise "0.4f" to "0.8f" ( 0.8 / M_PI = 1 / 4). + if( trans * patch2->area > 0.8f ) + trans = 0.8f / patch2->area; + + if( dist < patch2->emitter_range - ON_EPSILON ) + { + if( light_behind_surface ) + trans = 0.0; + + vec_t sightarea = CalcSightArea( patch1->origin, normal1, patch2->winding, patch2->emitter_skylevel ); + vec_t frac = dist / patch2->emitter_range; + + frac = (frac - 0.5f) * 2.0f; // make a smooth transition between the two methods + frac = bound( 0.0, frac, 1.0 ); + trans = frac * trans + (1 - frac) * (sightarea / patch2->area); // because later we will multiply this back + } + else if( light_behind_surface ) + { + continue; + } + + trans *= patch2->exposure; + + // scale to 16 bit (black magic) + trans = trans * patch2->area * INVERSE_TRANSFER_SCALE; + if( trans > TRANSFER_SCALE_MAX ) + trans = TRANSFER_SCALE_MAX; + if( trans <= 0.0 ) continue; + + *tData = trans; + patch1->iData++; + *tIndex = patch2 - g_patches; + tIndex++; + tData++; + } + + // copy the transfers out + if( patch1->iData ) + { + uint data_size = patch1->iData * sizeof( transfer_data_t ); + transfer_data_t *t1, *t2; + + patch1->tData = (transfer_data_t *)Mem_Alloc( data_size ); + patch1->tIndex = CompressTransferIndicies( tIndex_All, patch1->iData, &patch1->iIndex, threadnum ); + g_transfer_data_size[threadnum] += data_size; + + total = 0.5 / M_PI; + t1 = patch1->tData; + t2 = tData_All; + + for( uint x = 0; x < patch1->iData; x++, t1++, t2++ ) + (*t1) = (*t2) * total; + } + } + + Mem_Free( vispatches ); + Mem_Free( tIndex_All ); + Mem_Free( tData_All ); +} + +/* +============ +CalcTransferSize +============ +*/ +void CalcTransferSize( void ) +{ + g_transfer_data_bytes = 0; + + for( int i = 0; i < MAX_THREADS; i++ ) + g_transfer_data_bytes += g_transfer_data_size[i]; + + MsgDev( D_INFO, "transfer lists: %s\n", Q_memprint( g_transfer_data_bytes )); +} + +/* +============== +MakeTransfers +============== +*/ +void MakeTransfers( void ) +{ + RunThreadsOn( g_num_patches, true, MakeTransfers ); + + // display transfer size + CalcTransferSize(); +} + +/* +============= +CollectLight +============= +*/ +void CollectLight( void ) +{ + patch_t *patch; + int i, j; + + for( i = 0, patch = g_patches; i < g_num_patches; i++, patch++ ) + { + for( j = 0; j < MAXLIGHTMAPS && newstyles[i][j] != 255; j++ ) + { + VectorAdd( patch->totallight[j], addlight[i][j], patch->totallight[j] ); + VectorScale( addlight[i][j], TRANSFER_SCALE, emitlight[i][j] ); + VectorClear( addlight[i][j] ); +#ifdef HLRAD_DELUXEMAPPING + VectorAdd( patch->totallight_dir[j], addlight_dir[i][j], patch->totallight_dir[j] ); + VectorCopy( addlight_dir[i][j], emitlight_dir[i][j] ); + VectorClear( addlight_dir[i][j] ); +#endif + } + + // store new styles back into patch + memcpy( g_patches[i].totalstyle, newstyles[i], sizeof( byte[MAXLIGHTMAPS] )); + } +} + +/* +============= +BounceLight + +Get light from other patches + Run multi-threaded +============= +*/ +void BounceLight( int threadnum ) +{ + int j, k, m; + patch_t *patch; + vec3_t v; + + while( 1 ) + { + if(( j = GetThreadWork()) == -1 ) + break; + + patch = &g_patches[j]; + + transfer_data_t *tData = patch->tData; + transfer_index_t *tIndex = patch->tIndex; + uint iIndex = patch->iIndex; + int overflowed_styles = 0; + + for( m = 0; m < MAXLIGHTMAPS && newstyles[j][m] != 255; m++ ) + VectorClear( addlight[j][m] ); + + for( k = 0; k < iIndex; k++, tIndex++ ) + { + uint size = (tIndex->size + 1); + uint patchnum = tIndex->index; + uint l; + + for( l = 0; l < size; l++, tData++, patchnum++ ) + { + if( patchnum < 0 || patchnum >= g_num_patches ) + { + MsgDev( D_ERROR, "bad patchnum %i, max %i\n", patchnum, g_num_patches ); + continue; + } + + patch_t *emitpatch = &g_patches[patchnum]; +#ifdef HLRAD_DELUXEMAPPING + vec3_t direction; + VectorSubtract( patch->origin, emitpatch->origin, direction ); + VectorNormalize( direction ); +#endif + // for each style on the emitting patch + for( int emitstyle = 0; emitstyle < MAXLIGHTMAPS && emitpatch->totalstyle[emitstyle] != 255; emitstyle++ ) + { + // find the matching style on this (destination) patch + for( m = 0; m < MAXLIGHTMAPS && newstyles[j][m] != 255; m++ ) + { + if( newstyles[j][m] == emitpatch->totalstyle[emitstyle] ) + break; + } + + if( m < MAXLIGHTMAPS ) + { + VectorScale( emitlight[patchnum][emitstyle], (float)(*tData), v ); + if( !VectorIsFinite( v )) continue; + + if( VectorMaximum( v ) < EQUAL_EPSILON ) + continue; + + if( newstyles[j][m] == 255 ) + newstyles[j][m] = emitpatch->totalstyle[emitstyle]; + + VectorAdd( addlight[j][m], v, addlight[j][m] ); +#ifdef HLRAD_DELUXEMAPPING + vec_t brightness = VectorAvg( v ); + VectorMA( addlight_dir[j][m], brightness, direction, addlight_dir[j][m] ); +#endif + } + else + { + overflowed_styles++; + } + } + } + } + + g_overflowed_styles_onpatch[threadnum] += overflowed_styles; + } +} + +/* +============= +BounceLight +============= +*/ +void BounceLight( void ) +{ + int i, j; + + for( i = 0; i < g_num_patches; i++ ) + { + patch_t *patch = &g_patches[i]; + + for( j = 0; j < MAXLIGHTMAPS && g_patches[i].totalstyle[j] != 255; j++ ) + { + VectorScale( patch->totallight[j], TRANSFER_SCALE, emitlight[i][j] ); +#ifdef HLRAD_DELUXEMAPPING + VectorCopy( patch->totallight_dir[j], emitlight_dir[i][j] ); +#endif + } + memcpy( newstyles[i], g_patches[i].totalstyle, sizeof( byte[MAXLIGHTMAPS] )); + } + + for( i = 0; i < g_numbounce; i++ ) + { + RunThreadsOnIncremental( g_num_patches, true, BounceLight, i + 1 ); + CollectLight(); + } +} + +//============================================================== +/* +============= +RadWorld +============= +*/ +void RadWorld( void ) +{ + MakeBackplanes(); + + // turn each face into a single patch + MakePatches (); + + SortPatches (); + + PairEdges (); + + BuildFaceNeighbors(); + + BuildDiffuseNormals(); + + // create directlights out of patches and lights + CreateDirectLights (); + + if( g_onlylights ) + { + DeleteDirectLights (); + FreeDiffuseNormals (); + FreeFaceNeighbors(); + FreeSharedEdges(); + FreeFaceLights(); + FreePatches(); + + return; + } + + InitWorldTrace(); + + // generate a position map for each face + RunThreadsOnIndividual( g_numfaces, true, FindFacePositions ); + CalcPositionsSize(); + + // build initial facelights + RunThreadsOnIndividual( g_numfaces, true, BuildFaceLights ); + CalcSampleSize (); + +#ifdef HLRAD_LIGHTMAPMODELS + BuildModelLightmaps(); + CalcModelSampleSize(); +#endif + +#ifdef HLRAD_VERTEXLIGHTING + BuildVertexLights(); +#endif + // free up the direct lights now that we have facelights + DeleteDirectLights (); + + FreeFacePositions (); + + CalcLuxelsCount(); + + if( g_numbounce > 0 ) + { + // build transfer lists + MakeTransfers(); + + emitlight = (vec3_t (*)[MAXLIGHTMAPS])Mem_Alloc(( g_num_patches + 1 ) * sizeof( vec3_t[MAXLIGHTMAPS] )); + addlight = (vec3_t (*)[MAXLIGHTMAPS])Mem_Alloc(( g_num_patches + 1 ) * sizeof( vec3_t[MAXLIGHTMAPS] )); + newstyles = (byte (*)[MAXLIGHTMAPS])Mem_Alloc(( g_num_patches + 1 ) * sizeof( byte[MAXLIGHTMAPS] )); +#ifdef HLRAD_DELUXEMAPPING + emitlight_dir = (vec3_t (*)[MAXLIGHTMAPS])Mem_Alloc(( g_num_patches + 1 ) * sizeof( vec3_t[MAXLIGHTMAPS] )); + addlight_dir = (vec3_t (*)[MAXLIGHTMAPS])Mem_Alloc(( g_num_patches + 1 ) * sizeof( vec3_t[MAXLIGHTMAPS] )); +#endif + // spread light around + BounceLight (); + + Mem_Free( emitlight ); + Mem_Free( addlight ); + Mem_Free( newstyles ); + emitlight = NULL; + addlight = NULL; + newstyles = NULL; +#ifdef HLRAD_DELUXEMAPPING + Mem_Free( emitlight_dir ); + Mem_Free( addlight_dir ); + emitlight_dir = NULL; + addlight_dir = NULL; +#endif + // transfers don't need anymore + FreeTransfers(); + } + + // remove direct light from patches + for( int i = 0; i < g_num_patches; i++ ) + { + patch_t *p = &g_patches[i]; + + for( int j = 0; j < MAXLIGHTMAPS && p->totalstyle[j] != 255; j++ ) + { + VectorSubtract( p->totallight[j], p->directlight[j], p->totallight[j] ); +#ifdef HLRAD_DELUXEMAPPING + VectorSubtract( p->totallight_dir[j], p->directlight_dir[j], p->totallight_dir[j] ); +#endif + } + } + + if( !g_lightbalance ) + ScaleDirectLights(); + + // because fastmode uses patches instead of samples + if( g_numbounce > 0 || g_fastmode ) + RunThreadsOnIndividual( g_numfaces, false, CreateTriangulations ); + + // blend bounced light into direct light and save + PrecompLightmapOffsets(); + + if( g_numbounce > 0 || g_fastmode ) + { + CreateFacelightDependencyList(); + + RunThreadsOnIndividual( g_numfaces, true, FacePatchLights ); +#ifdef HLRAD_VERTEXLIGHTING + VertexPatchLights(); +#endif + FreeFacelightDependencyList(); + } + + FreeDiffuseNormals (); + + if( g_numbounce > 0 || g_fastmode ) + FreeTriangulations(); + + if( g_lightbalance ) + ScaleDirectLights(); + + RunThreadsOnIndividual( g_numfaces, true, FinalLightFace ); +#ifdef HLRAD_LIGHTMAPMODELS + FinalModelLightFace(); +#endif + // now can be freed + FreeFaceLights(); + +#ifndef HLRAD_PARANOIA_BUMP + ReduceLightmap(); +#endif +#ifdef HLRAD_AMBIENTCUBES + ComputeLeafAmbientLighting(); +#endif +#ifdef HLRAD_LIGHTMAPMODELS + WriteModelLighting(); +#endif +#ifdef HLRAD_VERTEXLIGHTING + FinalLightVertex(); + UnparseEntities(); +#endif + FreeFaceNeighbors(); + + FreeSharedEdges(); + + FreePatches(); +} + +/* +============ +PrintRadSettings + +show compiler settings like ZHLT +============ +*/ +static void PrintRadSettings( void ) +{ + char buf1[1024]; + char buf2[1024]; + + Msg( "\nCurrent p2rad settings\n" ); + Msg( "Name | Setting | Default\n" ); + Msg( "---------------------|-----------|-------------------------\n" ); + Msg( "developer [ %7d ] [ %7d ]\n", GetDeveloperLevel(), DEFAULT_DEVELOPER ); + Msg( "fast rad [ %7s ] [ %7s ]\n", g_fastmode ? "on" : "off", DEFAULT_FASTMODE ? "on" : "off" ); + Msg( "extra rad [ %7s ] [ %7s ]\n", g_extra ? "on" : "off", DEFAULT_EXTRAMODE ? "on" : "off" ); + Msg( "bounces [ %7d ] [ %7d ]\n", g_numbounce, DEFAULT_BOUNCE ); + Q_snprintf( buf1, sizeof( buf1 ), "%3.3f", g_smoothvalue ); + Q_snprintf( buf2, sizeof( buf2 ), "%3.3f", DEFAULT_SMOOTHVALUE ); + Msg( "smoothing threshold [ %7s ] [ %7s ]\n", buf1, buf2 ); + Q_snprintf( buf1, sizeof( buf1 ), "%3.3f", g_blur ); + Q_snprintf( buf2, sizeof( buf2 ), "%3.3f", DEFAULT_BLUR ); + Msg( "blur size [ %7s ] [ %7s ]\n", buf1, buf2 ); + Q_snprintf( buf1, sizeof( buf1 ), "%3.3f", g_lightbalance ? 1.0f : g_direct_scale ); + Q_snprintf( buf2, sizeof( buf2 ), "%3.3f", DEFAULT_DLIGHT_SCALE ); + Msg( "direct light scale [ %7s ] [ %7s ]\n", buf1, buf2 ); + Q_snprintf( buf1, sizeof( buf1 ), "%3.3f", g_lightbalance ? g_direct_scale : 1.0f ); + Q_snprintf( buf2, sizeof( buf2 ), "%3.3f", DEFAULT_LIGHT_SCALE ); + Msg( "global light scale [ %7s ] [ %7s ]\n", buf1, buf2 ); + Q_snprintf( buf1, sizeof( buf1 ), "%3.3f", g_gamma ); + Q_snprintf( buf2, sizeof( buf2 ), "%3.3f", DEFAULT_GAMMA ); + Msg( "gamma factor [ %7s ] [ %7s ]\n", buf1, buf2 ); + Q_snprintf( buf1, sizeof( buf1 ), "%3.3f", g_chop ); + Q_snprintf( buf2, sizeof( buf2 ), "%3.3f", DEFAULT_CHOP ); + Msg( "chop value [ %7s ] [ %7s ]\n", buf1, buf2 ); + Q_snprintf( buf1, sizeof( buf1 ), "%3.3f", g_texchop ); + Q_snprintf( buf2, sizeof( buf2 ), "%3.3f", DEFAULT_TEXCHOP ); + Msg( "texchop value [ %7s ] [ %7s ]\n", buf1, buf2 ); + Q_snprintf( buf1, sizeof( buf1 ), "%3.3f", g_indirect_sun ); + Q_snprintf( buf2, sizeof( buf2 ), "%3.3f", DEFAULT_INDIRECT_SUN ); + Msg( "global sky diffusion [ %7s ] [ %7s ]\n", buf1, buf2 ); + Msg( "dirtmapping [ %7s ] [ %7s ]\n", g_dirtmapping ? "on" : "off", DEFAULT_DIRTMAPPING ? "on" : "off" ); +#ifdef HLRAD_PARANOIA_BUMP + Msg( "gamma mode [ %7d ] [ %7d ]\n", g_gammamode, DEFAULT_GAMMAMODE ); +#endif + Msg( "\n" ); +} + +/* +============ +PrintRadUsage + +show compiler usage like ZHLT +============ +*/ +static void PrintRadUsage( void ) +{ + Msg( "\n-= p2rad Options =-\n\n" ); + Msg( " -dev # : compile with developer message (1 - 4). default is %d\n", DEFAULT_DEVELOPER ); + Msg( " -threads # : manually specify the number of threads to run\n" ); + Msg( " -extra : improve lighting quality with lightmap filtering\n" ); + Msg( " -bounce # : set number of radiosity bounces\n" ); + Msg( " -ambient r g b : set ambient world light (0.0 to 1.0, r g b)\n" ); + Msg( " -smooth # : set smoothing threshold for blending (in degrees)\n" ); + Msg( " -blur : filtering the lightmap by post-processing\n" ); + Msg( " -dscale : direct light scaling factor\n" ); + Msg( " -gamma : set global gamma value\n" ); + Msg( " -chop # : set radiosity patch size for normal textures\n" ); + Msg( " -texchop # : set radiosity patch size for texture light faces\n" ); + Msg( " -sky # : set ambient sunlight contribution in the shade outside\n" ); + Msg( " -nomodelshadow : ignore shadows from alias and studiomodels\n" ); + Msg( " -balance : -dscale will be interpret as global scaling factor\n" ); + Msg( " -dirty : enable dirtmapping (baked AO)\n" ); + Msg( " -onlylights : update only worldlights lump\n" ); +#ifdef HLRAD_PARANOIA_BUMP + Msg( " -gammamode # : gamma correction mode (0, 1, 2)\n" ); +#endif + Msg( " bspfile : The bspfile to compile\n\n" ); + + exit( 1 ); +} + +/* +======== +main + +light modelfile +======== +*/ +int main( int argc, char **argv ) +{ + double start, end; + char str[64]; + int i; + + atexit( Sys_CloseLog ); + source[0] = '\0'; + + g_smoothing_threshold = cos( DEG2RAD( g_smoothvalue )); // Originally zero. + + for( i = 1; i < argc; i++ ) + { + if( !Q_strcmp( argv[i], "-dev" )) + { + SetDeveloperLevel( atoi( argv[i+1] )); + i++; + } + else if( !Q_strcmp( argv[i], "-threads" )) + { + g_numthreads = atoi( argv[i+1] ); + i++; + } + else if( !Q_strcmp( argv[i], "-fast" )) + { + g_nomodelshadow = true; + g_lerp_enabled = false; + g_dirtmapping = false; + g_fastmode = true; + } + else if( !Q_strcmp( argv[i], "-extra" )) + { + g_lerp_enabled = true; + g_extra = true; + g_blur = 1.5f; + } + else if( !Q_strcmp( argv[i], "-bounce" )) + { + g_numbounce = atoi( argv[i+1] ); + g_numbounce = bound( 0, g_numbounce, 128 ); + i++; + } + else if( !Q_strcmp( argv[i], "-ambient" )) + { + if( argc > ( i + 3 )) + { + g_ambient[0] = (float)atof( argv[i+1] ) * 0.5f; + g_ambient[1] = (float)atof( argv[i+2] ) * 0.5f; + g_ambient[2] = (float)atof( argv[i+3] ) * 0.5f; + i += 3; + } + else + { + break; + } + } + else if( !Q_strcmp( argv[i], "-smooth" )) + { + g_smoothvalue = atof( argv[i+1] ); + g_smoothing_threshold = (float)cos( DEG2RAD( g_smoothvalue )); + i++; + } + else if( !Q_strcmp( argv[i], "-blur" )) + { + g_blur = atof( argv[i+1] ); + i++; + } + else if( !Q_strcmp( argv[i], "-dscale" )) + { + g_direct_scale = (float)atof( argv[i+1] ); + i++; + } + else if( !Q_strcmp( argv[i], "-gamma" )) + { + g_gamma = (float)atof( argv[i+1] ); + i++; + } + else if( !Q_strcmp( argv[i], "-chop" )) + { + g_chop = (float)atof( argv[i+1] ); + g_chop = bound( 32, g_chop, 128 ); + i++; + } + else if( !Q_strcmp( argv[i], "-texchop" )) + { + g_texchop = (float)atof( argv[i+1] ); + g_texchop = bound( 32, g_texchop, 128 ); + i++; + } + else if( !Q_strcmp( argv[i], "-sky" )) + { + g_indirect_sun = (float)atof( argv[i+1] ); + g_indirect_sun = bound( 0.0f, g_indirect_sun, 2.0f ); + i++; + } + else if( !Q_strcmp( argv[i], "-nomodelshadow" )) + { + g_nomodelshadow = true; + } + else if( !Q_strcmp( argv[i], "-balance" )) + { + g_lightbalance = true; + } + else if( !Q_strcmp( argv[i], "-onlylights" )) + { + g_onlylights = true; + } + else if( !Q_strcmp( argv[i], "-quake" )) + { + // special preset for quake + g_lightbalance = true; + g_indirect_scale = 1.0f; + g_direct_scale = 0.5f; + g_indirect_sun = 0.0f; + g_gamma = 1.0f; + } + else if( !Q_strcmp( argv[i], "-dirty" )) + { + g_dirtmapping = !g_fastmode; + } +#ifdef HLRAD_PARANOIA_BUMP + else if( !Q_strcmp( argv[i], "-gammamode" )) + { + g_gammamode = (float)atoi( argv[i+1] ); + i++; + } +#endif + else if( argv[i][0] == '-' ) + { + MsgDev( D_ERROR, "\nUnknown option \"%s\"\n", argv[i] ); + break; + } + else if( !source[0] ) + { + Q_strncpy( source, COM_ExpandArg( argv[i] ), sizeof( source )); + COM_StripExtension( source ); + } + else + { + MsgDev( D_ERROR, "\nUnknown option \"%s\"\n", argv[i] ); + break; + } + } + + if( i != argc || !source[0] ) + { + if( !source[0] ) + Msg( "no mapfile specified\n" ); + PrintRadUsage(); + } + + start = I_FloatTime (); + + Sys_InitLogAppend( va( "%s.log", source )); + + Msg( "\n%s %s (%s)\n", TOOLNAME, VERSIONSTRING, __DATE__ ); + + PrintRadSettings(); + + ThreadSetDefault (); + + // starting base filesystem + FS_Init( source ); + + // Set the required global lights filename + // try looking in the directory we were run from + GetModuleFileName( NULL, global_lights, sizeof( global_lights )); + COM_ExtractFilePath( global_lights, global_lights ); + Q_strncat( global_lights, "\\lights.rad", sizeof( global_lights )); + + // Set the optional level specific lights filename + COM_FileBase( source, str ); + Q_snprintf( level_lights, sizeof( level_lights ), "maps/%s.rad", str ); + if( !FS_FileExists( level_lights, false )) level_lights[0] = '\0'; + + ReadLightFile( global_lights, true ); // Required + if( *level_lights ) ReadLightFile( level_lights, false ); // Optional & implied + + COM_DefaultExtension( source, ".bsp" ); + + LoadBSPFile( source ); + + if( g_nummodels <= 0 ) + COM_FatalError( "map %s without any models\n", source ); + + ParseEntities(); + TEX_LoadTextures(); + ReadInfoTexlights(); + SetupDirt(); + + if( !g_visdatasize && g_numbounce > 0 ) + MsgDev( D_ERROR, "no vis information, compile time may be adversely affected.\n" ); + + // keep it in acceptable range + g_blur = bound( 1.0, g_blur, 8.0 ); + g_gamma = bound( 0.3, g_gamma, 1.0 ); + + RadWorld (); + + WriteBSPFile( source ); + TEX_FreeTextures (); + FreeWorldTrace (); + FreeEntities (); + FS_Shutdown(); + + SetDeveloperLevel( D_REPORT ); + Mem_Check(); + + end = I_FloatTime (); + Q_timestring((int)( end - start ), str ); + Msg( "%s elapsed\n", str ); + + return 0; +} \ No newline at end of file diff --git a/utils/p2rad/qrad.h b/utils/p2rad/qrad.h new file mode 100644 index 0000000..0a07922 --- /dev/null +++ b/utils/p2rad/qrad.h @@ -0,0 +1,515 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include +#include +#include +#include "cmdlib.h" +#include "mathlib.h" +#include "bspfile.h" +#include "polylib.h" +#include "threads.h" +#include "stringlib.h" +#include "filesystem.h" +#include "utlarray.h" + +#define DEFAULT_FASTMODE false +#define DEFAULT_EXTRAMODE false +#define DEFAULT_TEXSCALE true +#define DEFAULT_CHOP 128.0 +#define DEFAULT_TEXCHOP 32.0 +#define DEFAULT_DLIGHT_SCALE 2.0 +#define DEFAULT_LIGHT_SCALE 1.0 +#define DEFAULT_LERP_ENABLED false +#define DEFAULT_WADTEXTURES false +#define DEFAULT_DIRTMAPPING false +#define DEFAULT_TEXREFLECTGAMMA (1.0 / 2.2) // turn back to linear space +#define DEFAULT_TEXREFLECTSCALE 1.0 +#define DEFAULT_BLUR 1.0 +#define DEFAULT_BOUNCE 3 +#define DEFAULT_LIGHTCLIP 196 +#define DEFAULT_SMOOTHVALUE 50.0 +#define DEFAULT_INDIRECT_SUN 1.0 +#define DEFAULT_GAMMA 0.5 +#define DLIGHT_THRESHOLD 10.0 + +// worldcraft predefined angles +#define ANGLE_UP -1 +#define ANGLE_DOWN -2 + +#define DIRECT_SCALE 0.1 +#define DEFAULT_HUNT_OFFSET 0.5 +#define DEFAULT_EDGE_WIDTH 0.8 +#define HUNT_WALL_EPSILON (ON_EPSILON * 3) +#define DIFFUSE_DIRECTION_SCALE (2.0 / 3.0) +#define MINIMUM_PATCH_DISTANCE ON_EPSILON +#define ACCURATEBOUNCE_THRESHOLD 4.0 // If the receiver patch is closer to emitter patch than + // EXACTBOUNCE_THRESHOLD * emitter_patch->radius, calculate the exact visibility amount. +#define ACCURATEBOUNCE_DEFAULT_SKYLEVEL 5 // sample 1026 normals +#define SKYLEVELMAX 8 +#define SKYLEVEL_SOFTSKYON 7 +#define SKYLEVEL_SOFTSKYOFF 4 +#define SUNSPREAD_SKYLEVEL 7 +#define SUNSPREAD_THRESHOLD 15.0 +#define NUMVERTEXNORMALS 162 +#define LF_SCALE 128.0 // TyrUtils magic value +#define DEFAULT_GAMMAMODE 0 +#define FRAC_EPSILON (1.0f / 32.0f) + +#define MAX_SINGLEMAP ((MAX_CUSTOM_SURFACE_EXTENT+1) * (MAX_CUSTOM_SURFACE_EXTENT+1) * 3) +#define MAX_SINGLEMAP_MODEL ((MAX_MODEL_SURFACE_EXTENT+1) * (MAX_MODEL_SURFACE_EXTENT+1) * 3) +#define MAX_SUBDIVIDE 16384 +#define MAX_TEXLIGHTS 1024 + +// Paranoia settings +#define LIGHTFLAG_NOT_NORMAL 2 +#define LIGHTFLAG_NOT_RENDERER 4 + +enum +{ + STYLE_ORIGINAL_LIGHT = 0, // direct + indirect, used while gl_renderer is 0 + STYLE_BUMPED_LIGHT = 1, + STYLE_INDIRECT_LIGHT = 2, // store indirect lighting from patches +}; + +// buz +enum +{ + BUMP_BASELIGHT_MAP = 1, // indirect + BUMP_LIGHTVECS_MAP = 2, // light vectors + BUMP_ADDLIGHT_MAP = 3, // direct + BUMP_BASELIGHT_STYLE = 61, + BUMP_ADDLIGHT_STYLE = 62, + BUMP_LIGHTVECS_STYLE = 63, +}; + +typedef struct +{ + char name[256]; + const char *filename; + vec3_t value; + vec_t fade; +} texlight_t; + +typedef CUtlArray CIntVector; + +typedef struct dlight_s +{ + // private part + struct dlight_s *next; + vec_t patch_area; + vec_t patch_emitter_range; + struct patch_s *patch; + byte *pvs; // accumulated domain of the light + int flags; // buz: how to work without flags??? + + // sun spread stuff + vec_t *sunnormalweights; + vec_t sunspreadangle; + int numsunnormals; + vec3_t *sunnormals; + +// this part is shared with dworldlight_t + emittype_t type; + int style; + vec_t fade; // falloff scaling 1.0 = normal, 0.5 = farther, 2.0 = shorter etc + byte falloff; // falloff style 0,2 = inverse square, 1 = inverse falloff + word lightnum; // worldlight number + + vec3_t origin; + vec3_t intensity; + vec3_t diffuse_intensity; // skylight only + vec3_t normal; // for surfaces and spotlights + float stopdot; // for spotlights + float stopdot2; // for spotlights + float lf_scale; // 1.0 for half-life, 128.0 for quake + bool topatch; + int facenum; + word modelnum; + float radius; +} directlight_t; + +typedef struct +{ + vec3_t point; // originally that called a surfpt + bool occluded; // luxel was occluded + vec3_t position; + int surface; +} surfpt_t; + +typedef struct +{ + union + { + int surfnum; + entity_t *mapent; + }; + + // reuse it for studiomodels + union + { + const dplane_t *plane; // faceplane + struct tface_t *tface; + }; + union + { + dface_t *face; + struct tmesh_t *mesh; + }; + + vec3_t texorg; + vec3_t worldtotex[2]; // s = (world - texorg) . worldtotex[0] + vec3_t textoworld[2]; // world = texorg + s * textoworld[0] + vec_t exactmins[2]; + vec_t exactmaxs[2]; + int texmins[2]; + int texsize[2]; + + surfpt_t *surfpt; // product of CalcPoints + int numsurfpt; + + int lmcache_side; + int lmcache_density; // shared by both s and t direction + int lmcache_offset; // shared by both s and t direction + int lmcachewidth; + int lmcacheheight; + vec3_t (*light)[MAXLIGHTMAPS]; +#ifdef HLRAD_DELUXEMAPPING + vec3_t *normals; + vec3_t (*deluxe)[MAXLIGHTMAPS]; +#ifdef HLRAD_SHADOWMAPPING + vec_t (*shadow)[MAXLIGHTMAPS]; +#endif +#endif +} lightinfo_t; + +typedef struct +{ + vec3_t facenormal; // face normal + int numneighbors; // neighboring faces that share vertices + int *neighbor; // neighboring face list (max of 64) + short texmins[2]; // also used for face testing + short extents[2]; + short lightmapmins[2]; // lightmatrix + short lightextents[2]; +} faceneighbor_t; + +typedef struct +{ + dface_t *faces[2]; + vec3_t interface_normal; + vec3_t vertex_normal[2]; + bool coplanar, smooth; + vec_t cos_normals_angle; + matrix3x4 textotex[2]; // how we translate texture coordinates from one face to the other face +} edgeshare_t; + +struct trace_t +{ + int contents; + float fraction; // time completed, 1.0 = didn't hit anything + int surface; // return the facenum +}; + +#ifdef HLRAD_SHRINK_MEMORY +#define TRANSFER_SCALE_VAL (1.0) +#else +#define TRANSFER_SCALE_VAL (16384) +#endif + +#define TRANSFER_SCALE (1.0f / TRANSFER_SCALE_VAL) +#define INVERSE_TRANSFER_SCALE (TRANSFER_SCALE_VAL) +#define TRANSFER_SCALE_MAX (65536.0f) +#define MAX_COMPRESSED_TRANSFER_INDEX ( BIT( 12 ) - 1 ) + +typedef struct +{ + uint size : 12; + uint index : 20; +} transfer_index_t; + +#ifdef HLRAD_SHRINK_MEMORY +typedef half transfer_data_t; +#else +typedef unsigned short transfer_data_t; +#endif + +#define PATCH_OUTSIDE BIT( 0 ) +#define PATCH_EMITLIGHT BIT( 1 ) // is the emit_surface patch +#define PATCH_SOURCE BIT( 2 ) // non subdivided patch +#define PATCH_CHILD BIT( 3 ) // subdivised from parent patch + +#define MAX_PATCHES (65536 * 4) // probably enough + +typedef struct patch_s +{ + struct patch_s *next; // next in face + vec3_t origin; + vec_t area; + vec_t exposure; + vec_t fade; + winding_t *winding; + vec_t scale; // scaling of texture in s & t + byte flags; + vec_t chop; // smallest acceptable width of patch face + int leafnum; + int faceNumber; + word modelnum; + word lightnum; + vec_t emitter_range; // Range from patch origin (cached info calculated from winding) + byte emitter_skylevel; // The "skylevel" used for sampling of normals, when the receiver patch is + // within the range of ACCURATEBOUNCE_THRESHOLD * this->radius. + // input + byte emitstyle; // for switchable texlights + vec3_t baselight; // emissivity only, uses emitstyle + vec3_t reflectivity; // Average RGB of texture, modified by material type. + + // light transport + uint iIndex; + uint iData; + + transfer_index_t *tIndex; + transfer_data_t *tData; + + // output + byte totalstyle[MAXLIGHTMAPS]; // gives the styles for use by the new switchable totallight values + vec3_t totallight[MAXLIGHTMAPS]; // accumulated by radiosity does NOT include light accounted for by direct lighting + vec3_t directlight[MAXLIGHTMAPS]; // direct light only + vec3_t samplelight[MAXLIGHTMAPS]; + vec_t samples[MAXLIGHTMAPS]; // for averaging direct light +#ifdef HLRAD_DELUXEMAPPING + vec3_t totallight_dir[MAXLIGHTMAPS]; + vec3_t directlight_dir[MAXLIGHTMAPS]; + vec3_t samplelight_dir[MAXLIGHTMAPS]; +#endif +} patch_t; + +#ifdef HLRAD_SHRINK_MEMORY +typedef struct +{ + // in local luxel space + bool occluded; // luxel was occluded + int surface; // this sample can grow into another face +#ifdef HLRAD_DELUXEMAPPING + hvec3_t normal; // phong normal +#endif + vec3_t pos; // in world units + + hvec3_t light[MAXLIGHTMAPS]; // total lightvalue +#ifdef HLRAD_DELUXEMAPPING + hvec3_t deluxe[MAXLIGHTMAPS]; // direct lightnormal (ignoring occlusion) +#ifdef HLRAD_SHADOWMAPPING + half shadow[MAXLIGHTMAPS]; // shadow value (occlusion only) +#endif +#endif +} sample_t; +#else +typedef struct +{ + // in local luxel space + bool occluded; // luxel was occluded + int surface; // this sample can grow into another face +#ifdef HLRAD_DELUXEMAPPING + vec3_t normal; // phong normal +#endif + vec3_t pos; // in world units + + vec3_t light[MAXLIGHTMAPS]; // total lightvalue +#ifdef HLRAD_DELUXEMAPPING + vec3_t deluxe[MAXLIGHTMAPS]; // direct lightnormal (ignoring occlusion) +#ifdef HLRAD_SHADOWMAPPING + vec_t shadow[MAXLIGHTMAPS]; // shadow value (occlusion only) +#endif +#endif +} sample_t; +#endif + +typedef struct +{ + unsigned short numsamples; + sample_t *samples; +} facelight_t; + +extern patch_t *g_face_patches[MAX_MAP_FACES]; +extern entity_t *g_face_entity[MAX_MAP_FACES]; +extern vec3_t g_face_offset[MAX_MAP_FACES]; // for rotating bmodels +extern vec3_t g_face_centroids[MAX_MAP_FACES]; +extern faceneighbor_t g_faceneighbor[MAX_MAP_FACES]; +extern facelight_t g_facelight[MAX_MAP_FACES]; +extern vec_t *g_skynormalsizes[SKYLEVELMAX+1]; +extern int g_numskynormals[SKYLEVELMAX+1]; +extern vec3_t *g_skynormals[SKYLEVELMAX+1]; +extern int g_overflowed_styles_onface[MAX_THREADS]; +extern int g_overflowed_styles_onpatch[MAX_THREADS]; +extern int g_direct_luxels[MAX_THREADS]; +extern int g_lighted_luxels[MAX_THREADS]; +extern vec_t g_anorms[NUMVERTEXNORMALS][3]; +extern size_t g_transfer_data_size[MAX_THREADS]; +extern edgeshare_t *g_edgeshare; +extern patch_t *g_patches; +extern uint g_num_patches; + +//============================================== + +//============================================== + +extern bool g_extra; +extern vec3_t g_ambient; +extern bool g_fastmode; +extern float g_maxlight; +extern uint g_numbounce; +extern vec_t g_direct_scale; +extern vec_t g_indirect_scale; +extern float g_indirect_sun; +extern float g_smoothing_threshold; +extern bool g_lightbalance; +extern char source[MAX_PATH]; +extern bool g_lerp_enabled; +extern bool g_nomodelshadow; +extern vec_t g_smoothvalue; +extern bool g_drawsample; +extern bool g_wadtextures; +extern bool g_dirtmapping; +extern bool g_onlylights; +extern int g_numdlights; +extern uint g_gammamode; +extern vec_t g_gamma; +extern vec_t g_blur; + +// +// ambientcube.c +// +void ComputeLeafAmbientLighting( void ); + +// +// facepos.c +// +void TranslateWorldToTex( int facenum, matrix3x4 out ); +void FindFacePositions( int facenum, int threadnum = -1 ); +void FreeFacePositions( void ); +bool FindNearestPosition( int facenum, const winding_t *w, vec_t s, vec_t t, vec3_t pos, vec_t *out_s, vec_t *out_t, vec_t *dist ); +void CalcPositionsSize( void ); + +// +// dirtmap.c +// +void SetupDirt( void ); +float GatherSampleDirt( int threadnum, int fn, const vec3_t pos, const vec3_t normal, entity_t *ignoreent ); + +// +// qrad.c +// +const dplane_t *GetPlaneFromFace( const dface_t *face ); +const dplane_t *GetPlaneFromFace( const uint facenum ); +dleaf_t *HuntForWorld( vec3_t point, const vec3_t plane_offset, const dplane_t *plane, int hunt_size, vec_t hunt_scale, vec_t hunt_offset ); +vec_t CalcSightArea( const vec3_t receiver_origin, const vec3_t receiver_normal, const winding_t *emitter_winding, int skylevel ); +void GetAlternateOrigin( const vec3_t pos, const vec3_t normal, const patch_t *patch, vec3_t origin ); +vec_t BaseLightForFace( dface_t *f, vec3_t light, vec3_t reflectivity ); +void InitWorldLightFromPatch( dworldlight_t *wl, patch_t *p ); +void InitWorldLightFromDlight( directlight_t *dl, int leafnum ); +int GetVisCache( int lastoffset, int offset, byte *pvs ); +void SetDLightVis( directlight_t *dl, int leafnum ); +void MergeDLightVis( directlight_t *dl, int leafnum ); +winding_t *WindingFromFace( const dface_t *f ); +void CalcTransferSize( void ); +void PairEdges( void ); +void FreeSharedEdges( void ); +void BuildFaceNeighbors( void ); +void FreeFaceNeighbors( void ); +void BuildDiffuseNormals( void ); +void FreeDiffuseNormals( void ); +void BuildFaceLights( int facenum, int threadnum = -1 ); +void PrecompLightmapOffsets(); +void FinalLightFace( int facenum, int threadnum = -1 ); +bool PvsForOrigin( const vec3_t org, byte *pvs ); +void CreateDirectLights (void); +void DeleteDirectLights (void); +vec_t PatchPlaneDist( patch_t *patch ); +void GetPhongNormal( int facenum, const vec3_t spot, vec3_t phongnormal ); +void GetPhongNormal2( int facenum, const vec3_t spot, vec3_t phongnormal ); +void FreeFaceLights( void ); +void ReduceLightmap( void ); + +// +// lerp.c +// +extern void CreateTriangulations( int facenum, int threadnum ); +extern void GetTriangulationPatches( int facenum, int *numpatches, const int **patches ); +extern void InterpolateSampleLight( const vec3_t position, int surface, int numstyles, const int *styles, vec3_t *outs, vec3_t *outs_dir = NULL ); +extern void FreeTriangulations( void ); + +// +// lightmap.c +// +void GatherSampleLight( int threadnum, int fn, const vec3_t pos, int leafnum, const vec3_t normal, +vec3_t *s_light, vec3_t *s_dir, vec_t *s_occ, byte *styles, byte *vislight, bool topatch, entity_t *ignoreent = NULL ); +void TexelSpaceToWorld( const lightinfo_t *l, vec3_t world, const vec_t s, const vec_t t ); +void WorldToTexelSpace( const lightinfo_t *l, const vec3_t world, vec_t &s, vec_t &t ); +int ParseLightIntensity( const char *pLight, vec3_t intensity, vec_t multiplier = 255.0 ); +void TranslateWorldToTex( int facenum, matrix3x4 out ); +void InitLightinfo( lightinfo_t *pl, int facenum ); +vec_t *GetTotalLight( patch_t *patch, int style ); +vec_t *GetTotalDirection( patch_t *patch, int style ); +void ScaleDirectLights( void ); +void CreateFacelightDependencyList( void ); +void FacePatchLights( int facenum, int threadnum = -1 ); +void FreeFacelightDependencyList( void ); +void CalcSampleSize( void ); +void CalcLuxelsCount( void ); + +// +// vertexlight.c +// +void BuildVertexLights( void ); +void VertexPatchLights( void ); +void FinalLightVertex( void ); + +// +// model_lightmaps.c +// +void BuildModelLightmaps( void ); +void CalcModelSampleSize( void ); +void ScaleModelDirectLights( void ); +void PrecompModelLightmapOffsets( void ); +void FinalModelLightFace( void ); +void FreeModelFaceLights( void ); +void ReduceModelLightmap( byte *oldlightdata, byte *olddeluxdata, byte *oldshadowdata ); +void WriteModelLighting( void ); + +// +// alias.c +// +void LoadAlias( entity_t *ent, void *buffer, long fileLength, int flags ); +void AliasGetBounds( entity_t *ent, vec3_t mins, vec3_t maxs ); + +// +// studio.c +// +void LoadStudio( entity_t *ent, void *buffer, long fileLength, int flags ); +void StudioGetBounds( entity_t *ent, vec3_t mins, vec3_t maxs ); + +// +// textures.c +// +void TEX_LoadTextures( void ); +miptex_t *GetTextureByMiptex( int miptex ); +void TEX_FreeTextures( void ); + +// +// trace.c +// +void InitWorldTrace( void ); +int TestLine( int threadnum, const vec3_t start, const vec3_t end, bool nomodels = false, entity_t *ignoreent = NULL ); +void TestLine( int threadnum, const vec3_t start, const vec3_t stop, trace_t *trace ); +void FreeWorldTrace( void ); + +dleaf_t *PointInLeaf( const vec3_t point ); +dleaf_t *PointInLeaf2( const vec3_t point ); +dvertex_t *GetVertexByNumber( int vertexnum ); diff --git a/utils/p2rad/raytracer.cpp b/utils/p2rad/raytracer.cpp new file mode 100644 index 0000000..b6c402e --- /dev/null +++ b/utils/p2rad/raytracer.cpp @@ -0,0 +1,575 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// raytracer.cpp + +#include "qrad.h" +#include "raytracer.h" +#include "..\..\engine\studio.h" +#include "model_trace.h" + +#ifdef HLRAD_RAYTRACE +void CWorldRayTrace :: AddTriangle( tface_t *tf ) +{ + if( !tf->shadow ) return; + m_TriangleList.AddToTail( tf ); +} + +float CWorldRayTrace :: BoxSurfaceArea( const vec3_t boxmin, const vec3_t boxmax ) +{ + vec3_t boxdim; + vec_t area; + + VectorSubtract( boxmax, boxmin, boxdim ); + area = 2.0 * ((boxdim[0] * boxdim[2]) + (boxdim[0] * boxdim[1]) + (boxdim[1] * boxdim[2])); + if( area == 0.0f ) area = FLT_EPSILON; // to prevent INF + + assert( area != 0.0 ); + + return area; +} + +int CWorldRayTrace :: MakeLeafNode( int first_tri, int last_tri ) +{ + KDNode ret; + + ret.m_iChild = KDNODE_STATE_LEAF + (m_TriangleIndexList.Count() << 2); + ret.SetNumberOfTrianglesInLeafNode( 1 + (last_tri - first_tri)); + + for( int tnum = first_tri; tnum <= last_tri; tnum++ ) + m_TriangleIndexList.AddToTail( tnum ); + + m_KDTree.AddToTail( ret ); + return m_KDTree.Count() - 1; +} + +void CWorldRayTrace :: CalculateTriangleListBounds( const int *tris, int ntris, vec3_t minout, vec3_t maxout ) +{ + VectorSet( minout, 1.0e23, 1.0e23, 1.0e23 ); + VectorSet( maxout, -1.0e23, -1.0e23, -1.0e23 ); + + for( int i = 0; i < ntris; i++ ) + { + const tface_t *face = m_TriangleList[tris[i]]; + + for( int c = 0; c < 3; c++ ) + { + minout[c] = Q_min( minout[c], mesh->verts[face->a].point[c] ); + maxout[c] = Q_max( maxout[c], mesh->verts[face->a].point[c] ); + minout[c] = Q_min( minout[c], mesh->verts[face->b].point[c] ); + maxout[c] = Q_max( maxout[c], mesh->verts[face->b].point[c] ); + minout[c] = Q_min( minout[c], mesh->verts[face->c].point[c] ); + maxout[c] = Q_max( maxout[c], mesh->verts[face->c].point[c] ); + } + } +} + +float CWorldRayTrace :: CalculateCostsOfSplit( int plane, int *list, int ntris, vec3_t absmin, vec3_t absmax, float &value, int &nleft, int &nright, int &nboth ) +{ + float min_coord = 1.0e23; + float max_coord = -1.0e23; + + // determine the costs of splitting on a given axis, and label triangles with respect to + // that axis by storing the value in coordselect0. It will also return the number of + // tris in the left, right, and nboth groups, in order to facilitate memory + nleft = nboth = nright = 0; + + // now, label each triangle. Since we have not converted the triangles into + // intersection fromat yet, we can use the CoordSelect0 field of each as a temp. + for( int t = 0; t < ntris; t++ ) + { + tface_t *face = m_TriangleList[list[t]]; + + // determine max and min coordinate values for later optimization + min_coord = Q_min( min_coord, mesh->verts[face->a].point[plane] ); + max_coord = Q_max( max_coord, mesh->verts[face->a].point[plane] ); + min_coord = Q_min( min_coord, mesh->verts[face->b].point[plane] ); + max_coord = Q_max( max_coord, mesh->verts[face->b].point[plane] ); + min_coord = Q_min( min_coord, mesh->verts[face->c].point[plane] ); + max_coord = Q_max( max_coord, mesh->verts[face->c].point[plane] ); + + switch( face->ClassifyAgainstAxisSplit( mesh->verts, plane, value )) + { + case PLANECHECK_NEGATIVE: + face->pcheck0 = PLANECHECK_NEGATIVE; + nleft++; + break; + case PLANECHECK_POSITIVE: + face->pcheck0 = PLANECHECK_POSITIVE; + nright++; + break; + case PLANECHECK_STRADDLING: + face->pcheck0 = PLANECHECK_STRADDLING; + nboth++; + break; + default: + ASSERT( 0 ); + break; + } + } + + // now, if the split resulted in one half being empty, "grow" the empty half + if( nleft && (nboth == 0) && (nright == 0)) + value = max_coord; + if( nright && (nboth == 0) && (nleft == 0)) + value = min_coord; + + vec3_t leftMins, leftMaxs; + vec3_t rightMins, rightMaxs; + + // now, perform surface area/cost check to determine whether this split was worth it + VectorCopy( absmin, leftMins ); + VectorCopy( absmax, leftMaxs ); + VectorCopy( absmin, rightMins ); + VectorCopy( absmax, rightMaxs ); + + leftMaxs[plane] = value; + rightMins[plane] = value; + + float SA_L = BoxSurfaceArea( leftMins, leftMaxs ); + float SA_R = BoxSurfaceArea( rightMins, rightMaxs ); + float ISA = 1.0 / BoxSurfaceArea( absmin, absmax ); + + return COST_OF_TRAVERSAL + COST_OF_INTERSECTION * (nboth + (SA_L * ISA * nleft) + (SA_R * ISA * nright)); +} + +void CWorldRayTrace :: RefineNode( int node_number, int *list, int ntris, vec3_t absmin, vec3_t absmax, int depth ) +{ + // never split empty lists + if( ntris < 3 ) + { + // no point in continuing + m_KDTree[node_number].m_iChild = KDNODE_STATE_LEAF + (m_TriangleIndexList.Count() << 2); + m_KDTree[node_number].SetNumberOfTrianglesInLeafNode( ntris ); + + for( int t = 0; t < ntris; t++ ) + m_TriangleIndexList.AddToTail( list[t] ); + return; + } + + float best_splitvalue = 0; + float best_cost = 1.0e23; + int best_nleft = 0; + int best_nright = 0; + int best_nboth = 0; + int split_plane = 0; + + // don't try all trinagles as split + int tri_skip = 1 + (ntris / 10); + + // points when there are a lot of them + for( int axis = 0; axis < 3; axis++ ) + { + for( int ts = -1; ts < ntris; ts += tri_skip ) + { + for( int tv = 0; tv < 3; tv++ ) + { + int trial_nleft, trial_nright, trial_nboth; + float trial_splitvalue; + + if( ts == -1 ) + { + trial_splitvalue = 0.5f * (absmin[axis] + absmax[axis]); + } + else + { + // else, split at the triangle vertex if possible + tface_t *face = m_TriangleList[list[ts]]; + int vert = -1; + + switch( tv ) + { + case 0: vert = face->a; break; + case 1: vert = face->b; break; + case 2: vert = face->c; break; + } + + assert( vert != -1 ); + trial_splitvalue = mesh->verts[vert].point[axis]; + + if(( trial_splitvalue > absmax[axis] ) || ( trial_splitvalue < absmin[axis] )) + continue; // don't try this vertex - not inside + + } + + float trial_cost = CalculateCostsOfSplit( axis, list, ntris, absmin, absmax, + trial_splitvalue, trial_nleft, trial_nright, trial_nboth ); + + if( trial_cost < best_cost ) + { + split_plane = axis; + best_cost = trial_cost; + best_nleft = trial_nleft; + best_nright = trial_nright; + best_nboth = trial_nboth; + best_splitvalue = trial_splitvalue; + + // save away the axis classification of each triangle + for( int t = 0; t < ntris; t++ ) + { + tface_t *face = m_TriangleList[list[t]]; + face->pcheck1 = face->pcheck0; + } + } + + if( ts == -1 ) break; + } + } + + } + + float cost_of_no_split = COST_OF_INTERSECTION * ntris; + + if(( cost_of_no_split <= best_cost ) || NEVER_SPLIT || ( depth > MAX_TREE_DEPTH )) + { + // no benefit to splitting. just make this a leaf node + m_KDTree[node_number].m_iChild = KDNODE_STATE_LEAF + (m_TriangleIndexList.Count() << 2); + m_KDTree[node_number].SetNumberOfTrianglesInLeafNode( ntris ); + + for( int t = 0; t < ntris; t++ ) + m_TriangleIndexList.AddToTail( list[t] ); + } + else + { + // its worth splitting! + // we will achieve the splitting without sorting by using a selection algorithm. + int *new_triangle_list = new int[ntris]; + + vec3_t leftMins, leftMaxs; + vec3_t rightMins, rightMaxs; + int n_right_output = 0; + int n_left_output = 0; + int n_both_output = 0; + + // now, perform surface area/cost check to determine whether this split was worth it + VectorCopy( absmin, leftMins ); + VectorCopy( absmax, leftMaxs ); + VectorCopy( absmin, rightMins ); + VectorCopy( absmax, rightMaxs ); + + leftMaxs[split_plane] = best_splitvalue; + rightMins[split_plane] = best_splitvalue; +#ifdef _DEBUG + memset( new_triangle_list, 0, sizeof( int ) * ntris ); +#endif + for( int t = 0; t < ntris; t++ ) + { + tface_t *face = m_TriangleList[list[t]]; + + switch( face->pcheck1 ) + { + case PLANECHECK_NEGATIVE: + new_triangle_list[n_left_output++] = list[t]; + break; + case PLANECHECK_POSITIVE: + n_right_output++; + new_triangle_list[ntris - n_right_output] = list[t]; + break; + case PLANECHECK_STRADDLING: + new_triangle_list[best_nleft + n_both_output] = list[t]; + n_both_output++; + break; + default: + ASSERT( 0 ); + break; + } + } + + int left_child = m_KDTree.Count(); + int right_child = left_child + 1; + + m_KDTree[node_number].m_iChild = split_plane + (left_child<<2); + m_KDTree[node_number].m_flSplitValue = best_splitvalue; + + KDNode newnode; + + m_KDTree.AddToTail( newnode ); + m_KDTree.AddToTail( newnode ); + + // now, recurse! + if(( ntris < 20) && ((best_nleft == 0) || (best_nright == 0 ))) + depth += 100; + + RefineNode( left_child, new_triangle_list, best_nleft + best_nboth, leftMins, leftMaxs, depth + 1 ); + RefineNode( right_child, new_triangle_list + best_nleft, best_nright + best_nboth, rightMins, rightMaxs, depth + 1 ); + delete[] new_triangle_list; + } +} + +void CWorldRayTrace :: BuildTree( tmesh_t *src ) +{ + KDNode root; + + m_KDTree.AddToTail( root ); + mesh = src; + + int *root_triangle_list = new int[m_TriangleList.Count()]; + + for( int t = 0; t < m_TriangleList.Count(); t++ ) + root_triangle_list[t] = t; + + CalculateTriangleListBounds( root_triangle_list, m_TriangleList.Count(), m_AbsMins, m_AbsMaxs ); + RefineNode( 0, root_triangle_list, m_TriangleList.Count(), m_AbsMins, m_AbsMaxs, 0 ); + delete[] root_triangle_list; +} + +void CWorldRayTrace :: TraceRay( const vec3_t start, const vec3_t stop, trace_t *trace ) +{ + vec3_t direction; + vec_t dist; + + VectorSubtract( stop, start, direction ); + dist = VectorNormalize( direction ); + trace->fraction = dist; + + TraceRay( 0.0f, dist, start, direction, trace ); + trace->fraction = trace->fraction / dist; +} + +_forceinline float Reciprocal( const float a ) +{ + float ret = 1.0 / a; + // newton iteration is: Y(n+1) = 2*Y(n)-a*Y(n)^2 + ret = (( ret + ret ) - ( a * ( ret * ret ))); + return ret; +} + +bool CWorldRayTrace :: TraceTexture( const tface_t *face, float u, float v, float w ) +{ + // calculate st from uvw (barycentric) coordinates + float s = w * mesh->verts[face->a].st[0] + u * mesh->verts[face->b].st[0] + v * mesh->verts[face->c].st[0]; + float t = w * mesh->verts[face->a].st[1] + u * mesh->verts[face->b].st[1] + v * mesh->verts[face->c].st[1]; + + // convert ST to real pixels position + int x = fix_coord( s * face->texture->width, face->texture->width - 1 ); + int y = fix_coord( t * face->texture->height, face->texture->height - 1 ); + + // test pixel + if( face->texture->data[(face->texture->width * y) + x] != 255 ) + { + // at this point we hit the opaque pixel + return true; + } + + return false; +} + +void CWorldRayTrace :: TraceRay( vec_t p1f, vec_t p2f, const vec3_t start, const vec3_t direction, trace_t *trace ) +{ + vec3_t OneOverRayDir; + int c; + + VectorCopy( direction, OneOverRayDir ); + + // add epsilon to avoid division by zero + for( c = 0; c < 3; c++ ) + { + if( OneOverRayDir[c] == 0.0f ) + OneOverRayDir[c] = FLT_EPSILON; + OneOverRayDir[c] = Reciprocal( OneOverRayDir[c] ); + } + + // now, clip rays against bounding box + for( c = 0; c < 3; c++ ) + { + float isect_min_t = ( m_AbsMins[c] - start[c] ) * OneOverRayDir[c]; + float isect_max_t = ( m_AbsMaxs[c] - start[c] ) * OneOverRayDir[c]; + p1f = Q_min( p1f, Q_min( isect_min_t, isect_max_t )); + p2f = Q_max( p2f, Q_max( isect_min_t, isect_max_t )); + } + + if( p1f > p2f ) return; + + // based on ray direction, whether to + // visit left or right node first + int front_idx[3], back_idx[3]; + int signbits = SignbitsForPlane( direction ); + + if( FBitSet( signbits, 1 )) + { + back_idx[0] = 0; + front_idx[0] = 1; + } + else + { + back_idx[0] = 1; + front_idx[0] = 0; + } + + if( FBitSet( signbits, 2 )) + { + back_idx[1] = 0; + front_idx[1] = 1; + } + else + { + back_idx[1] = 1; + front_idx[1] = 0; + } + + if( FBitSet( signbits, 4 )) + { + back_idx[2] = 0; + front_idx[2] = 1; + } + else + { + back_idx[2] = 1; + front_idx[2] = 0; + } + + NodeToVisit NodeQueue[MAX_NODE_STACK_LEN]; + NodeToVisit *stack_ptr = &NodeQueue[MAX_NODE_STACK_LEN]; + int mailboxids[MAILBOX_HASH_SIZE]; // used to avoid redundant triangle tests + KDNode const *CurNode = &(m_KDTree[0]); + memset( mailboxids, 0xff, sizeof( mailboxids )); // !!speed!! keep around? + + while( 1 ) + { + // traverse until next leaf + while( CurNode->NodeType() != KDNODE_STATE_LEAF ) + { + KDNode const *FrontChild = &(m_KDTree[CurNode->LeftChild()]); + int split_plane_number = CurNode->NodeType(); + + // dist = (split - org) / dir + float dist_to_sep_plane = ( CurNode->m_flSplitValue - start[split_plane_number] ) * OneOverRayDir[split_plane_number]; + bool active = ( p1f <= p2f ); // mask of which rays are active + bool hits_front = active && ( dist_to_sep_plane >= p1f ); + + // now, decide how to traverse children. can either do front, back, or do front and push back. + if( !hits_front ) + { + // missed the front. only traverse back + CurNode = FrontChild + back_idx[split_plane_number]; + p1f = Q_max( p1f, dist_to_sep_plane ); + + } + else + { + bool hits_back = active && ( dist_to_sep_plane <= p2f ); + + if( !hits_back ) + { + // missed the back - only need to traverse front node + CurNode = FrontChild + front_idx[split_plane_number]; + p2f = Q_min( p2f, dist_to_sep_plane ); + } + else + { + // at least some rays hit both nodes. + // must push far, traverse near + assert( stack_ptr > NodeQueue ); + stack_ptr--; + stack_ptr->node = FrontChild + back_idx[split_plane_number]; + stack_ptr->p1f = Q_max( p1f, dist_to_sep_plane ); + stack_ptr->p2f = p2f; + CurNode = FrontChild + front_idx[split_plane_number]; + p2f = Q_min( p2f, dist_to_sep_plane ); + } + } + } + + // hit a leaf! must do intersection check + int ntris = CurNode->NumberOfTrianglesInLeaf(); + + if( ntris ) + { + int const *tlist = &(m_TriangleIndexList[CurNode->TriangleIndexStart()]); + + do + { + int tnum = *(tlist++); + + // check mailbox + int mbox_slot = tnum & (MAILBOX_HASH_SIZE - 1); + const tface_t *face = m_TriangleList[tnum]; + + if( mailboxids[mbox_slot] != tnum ) + { + mailboxids[mbox_slot] = tnum; + + // compute plane intersection + float DDotN = DotProduct( direction, face->normal ); + + // mask off zero or near zero (ray parallel to surface) + bool did_hit = (( DDotN > FLT_EPSILON ) || ( DDotN < -FLT_EPSILON )); + + if( !FBitSet( mesh->flags, FMESH_VERTEX_LIGHTING|FMESH_MODEL_LIGHTMAPS )) + { + if( !FBitSet( face->texture->flags, STUDIO_NF_TWOSIDE )) + { + // NOTE: probably we should normalize the n but for speed reasons i don't do it + if( DDotN < -FLT_EPSILON ) continue; + } + } + + if( !did_hit ) continue; // to prevent division by zero + + float numerator = face->NdotP1 - DotProduct( start, face->normal ); + float isect_t = numerator / DDotN; // fraction + + // now, we have the distance to the plane. lets update our mask + did_hit = did_hit && ( isect_t > m_flBackFrac ); + did_hit = did_hit && ( isect_t < trace->fraction ); + if( !did_hit ) continue; + + // now, check 3 edges + float hitc1 = start[face->pcoord0] + ( isect_t * direction[face->pcoord0] ); + float hitc2 = start[face->pcoord1] + ( isect_t * direction[face->pcoord1] ); + + // do barycentric coordinate check + float B0 = face->edge1[0] * hitc1 + face->edge1[1] * hitc2 + face->edge1[2]; + did_hit = did_hit && ( B0 >= 0.0f ); + + float B1 = face->edge2[0] * hitc1 + face->edge2[1] * hitc2 + face->edge2[2]; + did_hit = did_hit && ( B1 >= 0.0f ); + + float B2 = B0 + B1; + did_hit = did_hit && ( B2 <= 1.0f ); + + if( !did_hit ) continue; + + // if the triangle is transparent + if( face->texture->data ) + { + // assuming a triangle indexed as v0, v1, v2 + // the projected edge equations are set up such that the vert opposite the first + // equation is v2, and the vert opposite the second equation is v0 + // Therefore we pass them back in 1, 2, 0 order + // Also B2 is currently B1 + B0 and needs to be 1 - (B1+B0) in order to be a real + // barycentric coordinate. Compute that now and pass it to the callback + if( !TraceTexture( face, 1.0 - B2, B0, B1 )) + continue; // passed through alpha-pixel + } + + if( trace->fraction > isect_t ) + { + trace->contents = face->contents; + trace->fraction = isect_t; + } + } + } while( --ntris ); + + // now, check if all rays have terminated + if( p2f > trace->fraction ) return; + } + + if( stack_ptr == &NodeQueue[MAX_NODE_STACK_LEN] ) + return; + + // pop stack! + CurNode = stack_ptr->node; + p1f = stack_ptr->p1f; + p2f = stack_ptr->p2f; + stack_ptr++; + } +} +#endif \ No newline at end of file diff --git a/utils/p2rad/raytracer.h b/utils/p2rad/raytracer.h new file mode 100644 index 0000000..fceb787 --- /dev/null +++ b/utils/p2rad/raytracer.h @@ -0,0 +1,147 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#ifdef HLRAD_RAYTRACE +#ifndef RAYTRACER_H +#define RAYTRACER_H + +#include +#include "cmdlib.h" +#include "mathlib.h" +#include + +struct tface_t; +struct trace_t; + +#define KDNODE_STATE_XSPLIT 0 // this node is an x split +#define KDNODE_STATE_YSPLIT 1 // this node is a ysplit +#define KDNODE_STATE_ZSPLIT 2 // this node is a zsplit +#define KDNODE_STATE_LEAF 3 // this node is a leaf + +#define NEVER_SPLIT 0 // set to 1 to test tree efficiency + +#define MAILBOX_HASH_SIZE 256 +#define MAX_TREE_DEPTH 21 +#define MAX_NODE_STACK_LEN (40 * MAX_TREE_DEPTH) + +#define COST_OF_TRAVERSAL 75 // approximate #operations +#define COST_OF_INTERSECTION 167 // approximate #operations + +struct KDNode +{ + // this is the cache intensive data structure. "Tricks" are used to fit it into 8 bytes: + // + // A) the right child is always stored after the left child, which means we only need one pointer + // B) The type of node (KDNODE_xx) is stored in the lower 2 bits of the pointer. + // C) for leaf nodes, we store the number of triangles in the leaf in the same place as the floating + // point splitting parameter is stored in a non-leaf node + + int m_iChild; // child idx, or'ed with flags above + float m_flSplitValue; // for non-leaf nodes, the nodes on the + // "high" side of the splitting plane + // are on the right + inline KDNode() + { + m_flSplitValue = 0.0f; + m_iChild = 0; + } + + inline int NodeType( void ) const + { + return m_iChild & 3; + } + + inline int TriangleIndexStart( void ) const + { + assert( NodeType() == KDNODE_STATE_LEAF ); + return m_iChild >> 2; + } + + inline int LeftChild( void ) const + { + assert( NodeType() != KDNODE_STATE_LEAF ); + return m_iChild >> 2; + } + + inline int RightChild( void ) const + { + return LeftChild() + 1; + } + + inline int NumberOfTrianglesInLeaf( void ) const + { + assert( NodeType() == KDNODE_STATE_LEAF ); + return *((int32 *)&m_flSplitValue); + } + + inline void SetNumberOfTrianglesInLeafNode( int n ) + { + *((int32 *)&m_flSplitValue) = n; + } +}; + +struct NodeToVisit +{ + KDNode const *node; + vec_t p1f; + vec_t p2f; +}; + +class CWorldRayTrace +{ +private: + vec3_t m_AbsMins; + vec3_t m_AbsMaxs; + float m_flBackFrac; // to prevent light leaks + tmesh_t *mesh; // pointer to mesh + + CUtlArray m_KDTree; //< the packed kdtree. root is 0 + CUtlBlockVector m_TriangleList; //< the packed triangles + CUtlArray m_TriangleIndexList;//< the list of triangle indices. +public: + CWorldRayTrace() + { + m_KDTree.Purge(); + m_TriangleList.Purge(); + m_TriangleIndexList.Purge(); + ClearBounds( m_AbsMins, m_AbsMaxs ); + m_flBackFrac = 0.0f; + mesh = NULL; + } + + // make pointers to model triangles + void AddTriangle( tface_t *tf ); + + void SetBackFraction( float back ) { m_flBackFrac = back * 100.0f; } + // SetupAccelerationStructure to prepare for tracing + void BuildTree( tmesh_t *src ); + + void TraceRay( const vec3_t start, const vec3_t stop, trace_t *trace ); +private: + // lowest level intersection routine - fire 4 rays through the scene. all 4 rays must pass the + // Check() function, and t extents must be initialized. skipid can be set to exclude a + // particular id (such as the origin surface). This function finds the closest intersection. + void TraceRay( vec_t p1f, vec_t p2f, const vec3_t start, const vec3_t direction, trace_t *trace ); + + bool TraceTexture( const tface_t *face, float u, float v, float w ); + + int MakeLeafNode( int first_tri, int last_tri ); + + float CalculateCostsOfSplit( int plane, int *list, int ntris, vec3_t min, vec3_t max, float &value, int &nleft, int &nright, int &nboth ); + + void RefineNode( int node_number, int *list, int ntris, vec3_t absmin, vec3_t absmax, int depth ); + + void CalculateTriangleListBounds( const int *tris, int ntris, vec3_t minout, vec3_t maxout ); + + float BoxSurfaceArea( const vec3_t boxmin, const vec3_t boxmax ); +}; + +#endif//RAYTRACER_H +#endif//HLRAD_RAYTRACE \ No newline at end of file diff --git a/utils/p2rad/studio.cpp b/utils/p2rad/studio.cpp new file mode 100644 index 0000000..6b8d8ac --- /dev/null +++ b/utils/p2rad/studio.cpp @@ -0,0 +1,729 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// studio.c + +#include "qrad.h" +#include "..\..\engine\studio.h" +#include "model_trace.h" +#include "imagelib.h" + +typedef struct +{ + char name[64]; + mstudiomodel_t *pmodel; + bool shadow; +} TmpModel_t; + +static void ExtractAnimValue( int frame, mstudioanim_t *panim, int dof, float scale, float &v1 ) +{ + if( !panim || panim->offset[dof] == 0 ) + { + v1 = 0.0f; + return; + } + + const mstudioanimvalue_t *panimvalue = (mstudioanimvalue_t *)((byte *)panim + panim->offset[dof]); + int k = frame; + + while( panimvalue->num.total <= k ) + { + k -= panimvalue->num.total; + panimvalue += panimvalue->num.valid + 1; + + if( panimvalue->num.total == 0 ) + { + v1 = 0.0f; + return; + } + } + + // Bah, missing blend! + if( panimvalue->num.valid > k ) + { + v1 = panimvalue[k+1].value * scale; + } + else + { + // get last valid data block + v1 = panimvalue[panimvalue->num.valid].value * scale; + } +} + +static void StudioCalcBoneTransform( int frame, mstudiobone_t *pbone, mstudioanim_t *panim, vec3_t pos, vec4_t q ) +{ + vec3_t origin; + vec3_t angles; + + ExtractAnimValue( frame, panim, 0, pbone->scale[0], origin[0] ); + ExtractAnimValue( frame, panim, 1, pbone->scale[1], origin[1] ); + ExtractAnimValue( frame, panim, 2, pbone->scale[2], origin[2] ); + ExtractAnimValue( frame, panim, 3, pbone->scale[3], angles[0] ); + ExtractAnimValue( frame, panim, 4, pbone->scale[4], angles[1] ); + ExtractAnimValue( frame, panim, 5, pbone->scale[5], angles[2] ); + + for( int j = 0; j < 3; j++ ) + { + origin[j] = pbone->value[j+0] + origin[j]; + angles[j] = pbone->value[j+3] + angles[j]; + } + + AngleQuaternion( angles, q ); + VectorCopy( origin, pos ); +} + +static void StudioSetupTriangle( tmesh_t *mesh, int index ) +{ + tface_t *face = &mesh->faces[index]; + + // calculate mins & maxs + ClearBounds( face->absmin, face->absmax ); + face->contents = CONTENTS_SOLID; + + assert( face->a < mesh->numverts ); + assert( face->b < mesh->numverts ); + assert( face->c < mesh->numverts ); + + // calc bounds + AddPointToBounds( mesh->verts[face->a].point, face->absmin, face->absmax ); + AddPointToBounds( mesh->verts[face->a].point, mesh->absmin, mesh->absmax ); + AddPointToBounds( mesh->verts[face->b].point, face->absmin, face->absmax ); + AddPointToBounds( mesh->verts[face->b].point, mesh->absmin, mesh->absmax ); + AddPointToBounds( mesh->verts[face->c].point, face->absmin, face->absmax ); + AddPointToBounds( mesh->verts[face->c].point, mesh->absmin, mesh->absmax ); + ExpandBounds( face->absmin, face->absmax, 1.0 ); + + // convert skinref to texture pointer + face->texture = &mesh->textures[face->skinref]; + // setup efficiency intersection data + face->PrepareIntersectionData( mesh->verts ); +} + +static void StudioRelinkFace( aabb_tree_t *tree, tface_t *face ) +{ + // only shadow faces must be linked + if( !face->shadow ) return; + + // find the first node that the facet box crosses + areanode_t *node = tree->areanodes; + + while( 1 ) + { + if( node->axis == -1 ) break; + if( face->absmin[node->axis] > node->dist ) + node = node->children[0]; + else if( face->absmax[node->axis] < node->dist ) + node = node->children[1]; + else break; // crosses the node + } + + // link it in + InsertLinkBefore( &face->area, &node->solid_edicts ); +} + +static int StudioCreateMeshFromTriangles( entity_t *ent, studiohdr_t *phdr, const char *modname, int body, int skin, int flags, matrix3x4 transform[] ) +{ + TmpModel_t submodel[MAXSTUDIOMODELS]; // list of unique models + int i, j, k, totalVertSize = 0; + int num_submodels = 0; + mstudiobodyparts_t *pbodypart; + mstudiomodel_t *psubmodel; + + memset( submodel, 0, sizeof( submodel )); + + // build list of unique submodels (by name) + for( i = 0; i < phdr->numbodyparts; i++ ) + { + pbodypart = (mstudiobodyparts_t *)((byte *)phdr + phdr->bodypartindex) + i; + + for( j = 0; j < pbodypart->nummodels; j++ ) + { + psubmodel = (mstudiomodel_t *)((byte *)phdr + pbodypart->modelindex) + j; + if( !psubmodel->nummesh ) continue; // blank submodel, ignore it + + for( k = 0; k < num_submodels; k++ ) + { + if( !Q_stricmp( submodel[k].name, psubmodel->name )) + break; + } + + // add new one + if( k == num_submodels ) + { + Q_strncpy( submodel[k].name, psubmodel->name, sizeof( submodel[k].name )); + submodel[k].pmodel = psubmodel; + num_submodels++; + } + } + } + + // find which parts are used by current body + for( i = 0; i < phdr->numbodyparts; i++ ) + { + pbodypart = (mstudiobodyparts_t *)((byte *)phdr + phdr->bodypartindex) + i; + + int index = body / pbodypart->base; + index = index % pbodypart->nummodels; + psubmodel = (mstudiomodel_t *)((byte *)phdr + pbodypart->modelindex) + index; + + for( j = 0; j < num_submodels; j++ ) + { + if( submodel[j].pmodel == psubmodel ) + { + submodel[j].shadow = true; + break; + } + } + } + + // count unique model vertices + for( i = 0; i < num_submodels; i++ ) + { + psubmodel = submodel[i].pmodel; + totalVertSize += psubmodel->numverts; + } + + tface_t *faces = (tface_t *)Mem_Alloc( sizeof( tface_t ) * totalVertSize * 8 ); // allocate face lighting array + tvert_t *verts = (tvert_t *)Mem_Alloc( sizeof( tvert_t ) * totalVertSize * 8 ); // allocate vertex array + mstudiotexture_t *ptexture = (mstudiotexture_t *)((byte *)phdr + phdr->textureindex); + dvlightofs_t vsubmodels[MAXSTUDIOMODELS]; + dflightofs_t fsubmodels[MAXSTUDIOMODELS]; + int numFaces = 0, numVerts = 0; + + memset( vsubmodels, 0, sizeof( vsubmodels )); + memset( fsubmodels, 0, sizeof( fsubmodels )); + + for( k = 0; k < num_submodels; k++ ) + { + mstudiomodel_t *psubmodel = submodel[k].pmodel; + + if( psubmodel->numverts <= 0 ) + continue; // a blank submodel + + vec3_t *pstudioverts = (vec3_t *)((byte *)phdr + psubmodel->vertindex); + vec3_t *pstudionorms = (vec3_t *)((byte *)phdr + psubmodel->normindex); + vec3_t *m_verts = (vec3_t *)Mem_Alloc( sizeof( vec3_t ) * psubmodel->numverts ); + vec3_t *m_norms = (vec3_t *)Mem_Alloc( sizeof( vec3_t ) * psubmodel->numnorms ); + byte *pvertbone = ((byte *)phdr + psubmodel->vertinfoindex); + byte *pnormbone = ((byte *)phdr + psubmodel->norminfoindex); + short *pskinref = (short *)((byte *)phdr + phdr->skinindex); + int m_skinnum = bound( 0, skin, MAXSTUDIOSKINS - 1 ); + + // setup skin + if( m_skinnum != 0 && m_skinnum < phdr->numskinfamilies ) + pskinref += (m_skinnum * phdr->numskinref); + + // setup all the vertices + for( i = 0; i < psubmodel->numverts; i++ ) + Matrix3x4_VectorTransform( transform[pvertbone[i]], pstudioverts[i], m_verts[i] ); + + // setup all the normals + for( i = 0; i < psubmodel->numnorms; i++ ) + Matrix3x4_VectorRotate( transform[pnormbone[i]], pstudionorms[i], m_norms[i] ); + + vsubmodels[k].submodel_offset = (byte *)psubmodel - (byte *)phdr; + vsubmodels[k].vertex_offset = numVerts; + fsubmodels[k].submodel_offset = (byte *)psubmodel - (byte *)phdr; + fsubmodels[k].surface_offset = numFaces; + + // build all the light vertices because we should include all the bodies into the buffer + for( j = 0; j < psubmodel->nummesh; j++ ) + { + mstudiomesh_t *pmesh = (mstudiomesh_t *)((byte *)phdr + psubmodel->meshindex) + j; + float s = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].width; + float t = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].height; + short *ptricmds = (short *)((byte *)phdr + pmesh->triindex); + int flags = ptexture[pskinref[pmesh->skinref]].flags; + + while( i = *( ptricmds++ )) + { + int vertexState = 0; + bool tri_strip; + + if( i < 0 ) + { + tri_strip = false; + i = -i; + } + else tri_strip = true; + + for( ; i > 0; i--, ptricmds += 4 ) + { + tvert_t *tv = &verts[numVerts]; + tface_t *tf = &faces[numFaces]; + + if( submodel[k].shadow ) + tf->shadow = true; + tf->skinref = pskinref[pmesh->skinref]; + + // build in indices + if( vertexState++ < 3 ) + { + switch( vertexState ) + { + case 1: + tf->a = numVerts; + break; + case 2: + tf->b = numVerts; + break; + case 3: + tf->c = numVerts; + numFaces++; + break; + } + } + else if( tri_strip ) + { + // flip triangles between clockwise and counter clockwise + if( vertexState & 1 ) + { + // draw triangle [n-2 n-1 n] + tf->a = numVerts - 2; + tf->b = numVerts - 1; + tf->c = numVerts; + } + else + { + // draw triangle [n-1 n-2 n] + tf->a = numVerts - 1; + tf->b = numVerts - 2; + tf->c = numVerts; + } + numFaces++; + } + else + { + // draw triangle fan [0 n-1 n] + tf->a = numVerts - ( vertexState - 1 ); + tf->b = numVerts - 1; + tf->c = numVerts; + numFaces++; + } + + if( FBitSet( flags, STUDIO_NF_CHROME )) + { + // probably always equal 64 (see studiomdl.c for details) + tv->st[0] = float( s ); + tv->st[1] = float( t ); + } + else if( FBitSet( flags, STUDIO_NF_UV_COORDS )) + { + tv->st[0] = HalfToFloat( ptricmds[2] ); + tv->st[1] = HalfToFloat( ptricmds[3] ); + } + else + { + tv->st[0] = float( ptricmds[2] * s ); + tv->st[1] = float( ptricmds[3] * t ); + } + + VectorCopy( m_verts[ptricmds[0]], tv->point ); + VectorCopy( m_norms[ptricmds[1]], tv->normal ); + VectorNormalize2( tv->normal ); + numVerts++; + } + } + } + + // don't keep this because different submodels may have difference count of normals + Mem_Free( m_norms ); + Mem_Free( m_verts ); + } + + // perfomance warning + if( numFaces >= MAX_TRIANGLES ) + { + MsgDev( D_ERROR, "%s have too many triangles (%i). Ignored\n", modname, numFaces ); + Mem_Free( verts ); + Mem_Free( faces ); + return 0; // failed to build (too many triangles) + } + else if( numFaces >= (MAX_TRIANGLES>>1)) + MsgDev( D_WARN, "%s have too many triangles (%i)\n", modname, numFaces ); + + size_t texdata_size = sizeof( timage_t ) * phdr->numtextures; + char diffuse[128], texname[128], mdlname[64]; + rgbdata_t *external_textures[256]; + + COM_FileBase( modname, mdlname ); + memset( external_textures, 0 , sizeof( external_textures )); + + // store only texdata where we has alpha-testing + for( i = 0; i < phdr->numtextures; i++ ) + { + mstudiotexture_t *tex = &ptexture[i]; + + COM_FileBase( tex->name, texname ); + Q_snprintf( diffuse, sizeof( diffuse ), "textures/%s/%s", mdlname, texname ); + +#ifdef HLRAD_EXTERNAL_TEXTURES + if( FBitSet( tex->flags, STUDIO_NF_ADDITIVE|STUDIO_NF_MASKED ) && IMAGE_EXISTS( diffuse )) + { + rgbdata_t *test = COM_LoadImage( diffuse ); + + // external texture has alpha - use it + if( test ) + { + if( FBitSet( test->flags, IMAGE_HAS_ALPHA )) + { + MsgDev( D_REPORT, "load external texture %s\n", diffuse ); + texdata_size += test->width * test->height; + external_textures[i] = test; + } + else + { + // has no alpha + Mem_Free( test ); + } + } + } +#endif + if( external_textures[i] ) continue; // already loaded + + // NOTE: store the only pixels, we doesn't need a palette + if( FBitSet( tex->flags, STUDIO_NF_MASKED )) + texdata_size += tex->width * tex->height; + } + + size_t memsize = sizeof( tmesh_t ) + ( sizeof( tface_t ) * numFaces ) + ( sizeof( tvert_t ) * numVerts ) + texdata_size; + + // alloc vislight matrix + if( FBitSet( flags, FMESH_MODEL_LIGHTMAPS|FMESH_VERTEX_LIGHTING )) + memsize += (g_numworldlights + 7) / 8; + + // alloc lighting faces + if( FBitSet( flags, FMESH_MODEL_LIGHTMAPS )) + memsize += sizeof( lface_t ) * numFaces; + + // alloc lighting verts + if( FBitSet( flags, FMESH_VERTEX_LIGHTING )) + memsize += sizeof( lvert_t ) * numVerts; + +// Msg( "%s alloc %s\n", modname, Q_memprint( memsize )); + + byte *meshdata = (byte *)Mem_Alloc( memsize ); + byte *meshend = meshdata + memsize; // bounds checking + tmesh_t *mesh = (tmesh_t *)meshdata; + + if( ent->cache ) Mem_Free( ent->cache ); // throw previous instance + ent->cache = meshdata; // FreeEntities will be automatically free that + + // setup pointers + meshdata += sizeof( tmesh_t ); + mesh->textures = (timage_t *)meshdata; + meshdata += sizeof( timage_t ) * phdr->numtextures; + mesh->faces = (tface_t *)meshdata; + meshdata += sizeof( tface_t ) * numFaces; + mesh->numfaces = numFaces; + + // store faces + memcpy( mesh->faces, faces, sizeof( tface_t ) * mesh->numfaces ); + Mem_Free( faces ); + + // setup additional lightdata if present + if( FBitSet( flags, FMESH_MODEL_LIGHTMAPS )) + { + for( int i = 0; i < numFaces; i++ ) + { + mesh->faces[i].light = (lface_t *)meshdata; + meshdata += sizeof( lface_t ); + + // clearing lightdata + for( int j = 0; j < MAXLIGHTMAPS; j++ ) + mesh->faces[i].light->styles[j] = 255; + mesh->faces[i].light->lightofs = -1; + } + } + + mesh->verts = (tvert_t *)meshdata; + meshdata += sizeof( tvert_t ) * numVerts; + mesh->numverts = numVerts; + + // store vertexes + memcpy( mesh->verts, verts, sizeof( tvert_t ) * mesh->numverts ); + Mem_Free( verts ); + + // setup additional lightdata if present + if( FBitSet( flags, FMESH_VERTEX_LIGHTING )) + { + for( int i = 0; i < numVerts; i++ ) + { + mesh->verts[i].light = (lvert_t *)meshdata; + meshdata += sizeof( lvert_t ); + VectorCopy( mesh->verts[i].point, mesh->verts[i].light->pos ); + } + } + + memcpy( mesh->vsubmodels, vsubmodels, sizeof( mesh->vsubmodels )); + memcpy( mesh->fsubmodels, fsubmodels, sizeof( mesh->fsubmodels )); + + if( FBitSet( flags, FMESH_MODEL_LIGHTMAPS|FMESH_VERTEX_LIGHTING )) + { + mesh->vislight = (byte *)meshdata; + meshdata += (g_numworldlights + 7) / 8; + } + + for( int l = 0; l < MAXLIGHTMAPS; l++ ) + mesh->styles[l] = 255; + + // store only texdata where we has alpha-testing + for( i = 0; i < phdr->numtextures; i++ ) + { + mstudiotexture_t *src = &ptexture[i]; + timage_t *dst = &mesh->textures[i]; + + dst->width = src->width; + dst->height = src->height; + dst->flags = src->flags; + + if( external_textures[i] ) + { + rgbdata_t *test = external_textures[i]; + dst->data = meshdata; + dst->width = test->width; + dst->height = test->height; + + // copy alpha channel from external texture + for( j = 0; j < test->width * test->height; j++ ) + dst->data[j] = test->buffer[j*4+3] < 255 ? 255 : 0; // because we used palette index, not alpha value + meshdata += test->width * test->height; // move pointer + external_textures[i] = NULL; + Mem_Free( test ); + } + else if( FBitSet( src->flags, STUDIO_NF_MASKED )) + { + // NOTE: store the only pixels, we doesn't need a palette + dst->data = meshdata; + memcpy( dst->data, (byte *)phdr + src->index, src->width * src->height ); + meshdata += src->width * src->height; // move pointer + } + } + + if( meshdata != meshend ) + Msg( "%s memory corrupted\n", modname ); + + ClearBounds( mesh->absmin, mesh->absmax ); + + // do some post-initialization + for( i = 0; i < numFaces; i++ ) + StudioSetupTriangle( mesh, i ); + + return numFaces; +} + +bool StudioConstructMesh( entity_t *ent, void *extradata, const char *modname, uint modelCRC, int flags ) +{ + studiohdr_t *phdr = (studiohdr_t *)extradata; + double start = I_FloatTime(); + vec3_t origin, angles; + int body, skin; + int numFaces; + vec3_t xform; + float scale; + + if( phdr->numbones < 1 ) + return false; + + // get model properties + GetVectorForKey( ent, "origin", origin ); + GetVectorForKey( ent, "angles", angles ); + + angles[0] = -angles[0]; // Stupid quake bug workaround + scale = FloatForKey( ent, "scale" ); + body = IntForKey( ent, "body" ); + skin = IntForKey( ent, "skin" ); + + GetVectorForKey( ent, "xform", xform ); + + if( VectorIsNull( xform )) + VectorFill( xform, scale ); + + // check xform values + if( xform[0] < 0.01f ) xform[0] = 1.0f; + if( xform[1] < 0.01f ) xform[1] = 1.0f; + if( xform[2] < 0.01f ) xform[2] = 1.0f; + if( xform[0] > 16.0f ) xform[0] = 16.0f; + if( xform[1] > 16.0f ) xform[1] = 16.0f; + if( xform[2] > 16.0f ) xform[2] = 16.0f; + + // compute default pose for building mesh from + mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)phdr + phdr->seqindex); + mstudioseqgroup_t *pseqgroup = (mstudioseqgroup_t *)((byte *)phdr + phdr->seqgroupindex); + mstudioanim_t *panim = (mstudioanim_t *)((byte *)phdr + pseqdesc->animindex); + mstudiobone_t *pbone = (mstudiobone_t *)((byte *)phdr + phdr->boneindex); + static vec3_t pos[MAXSTUDIOBONES]; + static vec4_t q[MAXSTUDIOBONES]; + + for( int i = 0; i < phdr->numbones; i++, pbone++, panim++ ) + StudioCalcBoneTransform( 0, pbone, panim, pos[i], q[i] ); + pbone = (mstudiobone_t *)((byte *)phdr + phdr->boneindex); + matrix3x4 transform, bonematrix, bonetransform[MAXSTUDIOBONES]; + Matrix3x4_CreateFromEntityScale3f( transform, angles, origin, xform ); + + // compute bones for default anim + for( i = 0; i < phdr->numbones; i++ ) + { + // initialize bonematrix + Matrix3x4_FromOriginQuat( bonematrix, q[i], pos[i] ); + + if( pbone[i].parent == -1 ) + Matrix3x4_ConcatTransforms( bonetransform[i], transform, bonematrix ); + else Matrix3x4_ConcatTransforms( bonetransform[i], bonetransform[pbone[i].parent], bonematrix ); + } + + if(( numFaces = StudioCreateMeshFromTriangles( ent, phdr, modname, body, skin, flags, bonetransform )) == 0 ) + return false; + + tmesh_t *mesh = (tmesh_t *)ent->cache; + size_t memsize = Mem_Size( ent->cache ); + int maxDepth = AREA_MIN_DEPTH; + + // FIXME: check this efficiency + if( numFaces < 1000 ) + maxDepth = AREA_MIN_DEPTH; + else if( numFaces >= 1000 && numFaces <= 10000 ) + maxDepth = 5; + else if( numFaces >= 10000 && numFaces <= 20000 ) + maxDepth = 6; + else if( numFaces >= 20000 && numFaces <= 50000 ) + maxDepth = 7; + else if( numFaces >= 50000 && numFaces <= 100000 ) + maxDepth = 8; + else if( numFaces >= 100000 && numFaces <= 200000 ) + maxDepth = 9; + else maxDepth = AREA_MAX_DEPTH; + + // create AABB tree for speedup reasons + CreateAreaNode( &mesh->face_tree, 0, maxDepth, mesh->absmin, mesh->absmax ); + + for( i = 0; i < numFaces; i++ ) + StudioRelinkFace( &mesh->face_tree, &mesh->faces[i] ); + + ent->modtype = mod_studio; // now our mesh is valid and ready to trace + mesh->modelCRC = modelCRC; + VectorCopy( origin, mesh->origin ); + VectorCopy( angles, mesh->angles ); + VectorCopy( xform, mesh->scale ); + mesh->backfrac = FloatForKey( ent, "zhlt_backfrac" ); + mesh->flags = flags; // copy settings + + MsgDev( D_REPORT, "%s: build time %g secs, size %s\n", modname, I_FloatTime() - start, Q_memprint( memsize )); + + return true; +} + +void LoadStudio( entity_t *ent, void *extradata, long fileLength, int flags ) +{ + const char *modname = ValueForKey( ent, "model" ); + dword modelCRC = 0; + studiohdr_t *phdr; + + if( !extradata ) + { + MsgDev( D_WARN, "LoadStudio: couldn't load %s\n", modname ); + return; + } + + phdr = (studiohdr_t *)extradata; + + if( phdr->ident != IDSTUDIOHEADER || phdr->version != STUDIO_VERSION ) + { + if( phdr->ident != IDSTUDIOHEADER ) + MsgDev( D_WARN, "LoadStudio: %s not a studio model\n", modname ); + else if( phdr->version != STUDIO_VERSION ) + MsgDev( D_WARN, "LoadStudio: %s has wrong version number (%i should be %i)\n", modname, phdr->version, STUDIO_VERSION ); + Mem_Free( extradata, C_FILESYSTEM ); + return; + } + +#ifndef HLRAD_VERTEXLIGHTING + ClearBits( flags, FMESH_VERTEX_LIGHTING ); +#endif + +#ifndef HLRAD_LIGHTMAPMODELS + ClearBits( flags, FMESH_MODEL_LIGHTMAPS ); +#endif + +#if defined( HLRAD_VERTEXLIGHTING ) || defined( HLRAD_LIGHTMAPMODELS ) + CRC32_Init( &modelCRC ); + CRC32_ProcessBuffer( &modelCRC, extradata, phdr->length ); + CRC32_Final( &modelCRC ); +#endif + // well the textures place in separate file (very stupid case) + if( phdr->numtextures == 0 ) + { + char texname[128], texpath[128]; + byte *texdata, *moddata; + studiohdr_t *thdr, *newhdr; + + Q_strncpy( texname, modname, sizeof( texname )); + COM_StripExtension( texname ); + + Q_snprintf( texpath, sizeof( texpath ), "%sT.mdl", texname ); + MsgDev( D_REPORT, "loading %s\n", texpath ); + texdata = FS_LoadFile( texpath, NULL, false ); + + if( !texdata ) + { + MsgDev( D_WARN, "LoadStudioModel: couldn't load %s\n", texpath ); + Mem_Free( extradata, C_FILESYSTEM ); + return; + } + + moddata = (byte *)extradata; + phdr = (studiohdr_t *)moddata; + thdr = (studiohdr_t *)texdata; + + // merge textures with main model buffer + extradata = Mem_Alloc( phdr->length + thdr->length - sizeof( studiohdr_t ), C_FILESYSTEM ); // we don't need two headers + memcpy( extradata, moddata, phdr->length ); + memcpy((byte *)extradata + phdr->length, texdata + sizeof( studiohdr_t ), thdr->length - sizeof( studiohdr_t )); + + // merge header + newhdr = (studiohdr_t *)extradata; + + newhdr->numskinfamilies = thdr->numskinfamilies; + newhdr->numtextures = thdr->numtextures; + newhdr->numskinref = thdr->numskinref; + newhdr->textureindex = phdr->length; + newhdr->skinindex = newhdr->textureindex + ( newhdr->numtextures * sizeof( mstudiotexture_t )); + newhdr->texturedataindex = newhdr->skinindex + (newhdr->numskinfamilies * newhdr->numskinref * sizeof( short )); + newhdr->length = phdr->length + thdr->length - sizeof( studiohdr_t ); + + // and finally merge datapointers for textures + for( int i = 0; i < newhdr->numtextures; i++ ) + { + mstudiotexture_t *ptexture = (mstudiotexture_t *)(((byte *)newhdr) + newhdr->textureindex); + ptexture[i].index += ( phdr->length - sizeof( studiohdr_t )); + } + + Mem_Free( moddata, C_FILESYSTEM ); + Mem_Free( texdata, C_FILESYSTEM ); + } + + StudioConstructMesh( ent, extradata, modname, modelCRC, flags ); + Mem_Free( extradata, C_FILESYSTEM ); +} + +void StudioGetBounds( entity_t *ent, vec3_t mins, vec3_t maxs ) +{ + tmesh_t *mesh = (tmesh_t *)ent->cache; + + // assume point hull + VectorClear( mins ); + VectorClear( maxs ); + + if( !mesh || ent->modtype != mod_studio ) + return; + + VectorSubtract( mesh->absmin, ent->origin, mins ); + VectorSubtract( mesh->absmax, ent->origin, maxs ); +} \ No newline at end of file diff --git a/utils/p2rad/textures.cpp b/utils/p2rad/textures.cpp new file mode 100644 index 0000000..ab5a3a9 --- /dev/null +++ b/utils/p2rad/textures.cpp @@ -0,0 +1,113 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// textures.c + +#include "qrad.h" + +typedef struct wadlist_s +{ + char wadnames[MAX_TEXFILES][32]; + int count; +} wadlist_t; + +bool g_wadtextures = DEFAULT_WADTEXTURES; // completely ignore textures in the bsp-file if enabled +static miptex_t *g_textures[MAX_MAP_TEXTURES]; +static wadlist_t wadlist; + +void TEX_LoadTextures( void ) +{ + char wadstring[MAX_VALUE]; + char *pszWadString; + char *pszWadFile; + + memset( g_textures, 0 , sizeof( g_textures )); + wadlist.count = 0; + + if( g_numentities <= 0 ) + return; + + pszWadString = ValueForKey( g_entities, "wad" ); + if( !*pszWadString ) pszWadString = ValueForKey( g_entities, "_wad" ); + if( !*pszWadString ) return; // may be -nowadtextures was used? + + Q_strncpy( wadstring, pszWadString, MAX_VALUE - 2 ); + wadstring[MAX_VALUE-1] = 0; + + if( !Q_strchr( wadstring, ';' )) + Q_strcat( wadstring, ";" ); + + // parse wad pathes + for( pszWadFile = strtok( wadstring, ";" ); pszWadFile != NULL; pszWadFile = strtok( NULL, ";" )) + { + COM_FixSlashes( pszWadFile ); + COM_FileBase( pszWadFile, wadlist.wadnames[wadlist.count++] ); + if( wadlist.count >= MAX_TEXFILES ) + break; // too many wads... + } +} + +miptex_t *GetTextureByMiptex( int miptex ) +{ + ASSERT( g_dtexdata != NULL ); + + int nummiptex = ((dmiptexlump_t*)g_dtexdata)->nummiptex; + char texname[64]; + int ofs; + + if( miptex < 0 || miptex >= nummiptex ) + return NULL; // bad miptex ? + + if(( ofs = ((dmiptexlump_t*)g_dtexdata)->dataofs[miptex] ) == -1 ) + return NULL; // texture is completely missed + + // check cache + if( g_textures[miptex] ) + return g_textures[miptex]; + + miptex_t *mt = (miptex_t *)(&g_dtexdata[ofs]); + + // trying wad texture (force while g_wadtextures is 1) + if( g_wadtextures || mt->offsets[0] <= 0 ) + { + Q_snprintf( texname, sizeof( texname ), "%s.mip", mt->name ); + + // check wads in reverse order + for( int i = wadlist.count - 1; i >= 0; i-- ) + { + char *texpath = va( "%s.wad/%s", wadlist.wadnames[i], texname ); + + if( FS_FileExists( texpath, false )) + { + g_textures[miptex] = (miptex_t *)FS_LoadFile( texpath, NULL, false ); + break; + } + } + } + + // wad failed, so use internal texture (if present) + if( mt->offsets[0] > 0 && !g_textures[miptex] ) + { + size_t size = sizeof( miptex_t ) + ((mt->width * mt->height * 85) >> 6) + sizeof( short ) + 768; + g_textures[miptex] = (miptex_t *)Mem_Alloc( size, C_FILESYSTEM ); + memcpy( g_textures[miptex], &g_dtexdata[ofs], size ); + } + + return g_textures[miptex]; +} + +void TEX_FreeTextures( void ) +{ + // purge texture cache + for( int i = 0; i < MAX_MAP_TEXTURES; i++ ) + { + Mem_Free( g_textures[i], C_FILESYSTEM ); + } +} \ No newline at end of file diff --git a/utils/p2rad/trace.cpp b/utils/p2rad/trace.cpp new file mode 100644 index 0000000..de374c1 --- /dev/null +++ b/utils/p2rad/trace.cpp @@ -0,0 +1,1326 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// trace.c + +#include "qrad.h" +#include "..\..\engine\alias.h" +#include "..\..\engine\studio.h" +#include "model_trace.h" + +typedef struct moveclip_s +{ + vec3_t boxmins, boxmaxs; // enclose the test object along entire move + const float *start, *end; + vec3_t direction; + vec_t distance; + bool nomodels; + entity_t *ignore; + trace_t trace; + tmesh_t *mesh; // mesh trace (may be NULL) +} moveclip_t; + +typedef struct tnode_s +{ + int type; + vec3_t normal; + float dist; + int children[2]; + word firstface; + word numfaces; // counting both sides +} tnode_t; + +static tnode_t *tnode_p; +static aabb_tree_t entity_tree; +static int numsolidedicts; +static twface_t *g_world_faces[MAX_MAP_FACES]; // polygons that turned into triangles + +/* +============= +SampleMiptex + +fence texture testing +============= +*/ +int SampleMiptex( const dface_t *face, const vec3_t point ) +{ + vec_t ds, dt; + byte *data; + int x, y; + dtexinfo_t *tx; + miptex_t *mt; + + tx = &g_texinfo[face->texinfo]; + mt = GetTextureByMiptex( tx->miptex ); + if( !mt ) return CONTENTS_EMPTY; + + if( mt->name[0] != '{' ) + return CONTENTS_SOLID; + + ds = DotProduct( point, tx->vecs[0] ) + tx->vecs[0][3]; + dt = DotProduct( point, tx->vecs[1] ) + tx->vecs[1][3]; + + // convert ST to real pixels position + x = fix_coord( ds, mt->width - 1 ); + y = fix_coord( dt, mt->height - 1 ); + + ASSERT( x >= 0 && y >= 0 ); + + data = ((byte *)mt) + mt->offsets[0]; + if( data[(mt->width * y) + x] == 255 ) + return CONTENTS_EMPTY; + return CONTENTS_SOLID; +} + +/* +================== +MoveBounds +================== +*/ +void MoveBounds( const vec3_t start, const vec3_t end, vec3_t boxmins, vec3_t boxmaxs ) +{ + for( int i = 0; i < 3; i++ ) + { + if( end[i] > start[i] ) + { + boxmins[i] = start[i] - 1.0f; + boxmaxs[i] = end[i] + 1.0f; + } + else + { + boxmins[i] = end[i] - 1.0f; + boxmaxs[i] = start[i] + 1.0f; + } + } +} + +/* +================== +CombineTraces +================== +*/ +trace_t CombineTraces( trace_t *cliptrace, trace_t *trace ) +{ + if( trace->fraction < cliptrace->fraction ) + *cliptrace = *trace; + return *cliptrace; +} + +/* +============== +MakeTnode + +Converts the disk node structure into the efficient tracing structure +============== +*/ +void MakeTnode( tnode_t *head, int nodenum ) +{ + dplane_t *plane; + dnode_t *node; + tnode_t *t; + + t = tnode_p++; + + node = g_dnodes + nodenum; + plane = g_dplanes + node->planenum; + + t->type = plane->type; + VectorCopy( plane->normal, t->normal ); + t->dist = plane->dist; + t->firstface = node->firstface; + t->numfaces = node->numfaces; + + for( int i = 0; i < 2; i++ ) + { + if( node->children[i] < 0 ) + { + t->children[i] = g_dleafs[-node->children[i] - 1].contents; + } + else + { + t->children[i] = tnode_p - head; + MakeTnode( head, node->children[i] ); + } + } +} + +/* +============= +CountTnodes_r + +count nodes for a given model +============= +*/ +static void CountTnodes_r( int &total, int nodenum ) +{ + // leaf? + if( nodenum < 0 ) return; + + total++; + + CountTnodes_r( total, g_dnodes[nodenum].children[0] ); + CountTnodes_r( total, g_dnodes[nodenum].children[1] ); +} + +/* +============= +MakeTnodes + +Loads the node structure out of a .bsp file to be used for light occlusion +============= +*/ +static void MakeTnodes( entity_t *ent, dmodel_t *mod ) +{ + int numnodes = 0; + + CountTnodes_r( numnodes, mod->headnode[0] ); + if( ent->cache ) Mem_Free( ent->cache ); + + ent->cache = Mem_Alloc(( numnodes + 1 ) * sizeof( tnode_t )); + tnode_p = (tnode_t *)ent->cache; + + MakeTnode( tnode_p, mod->headnode[0] ); + ent->modtype = mod_brush; +} + +//========================================================== +/* +================== +ClipMoveToTriangle + +Handles selection or creation of a clipping hull, and offseting (and +eventually rotation) of the end points +================== +*/ +void ClipMoveToTriangle( tface_t *face, moveclip_t *clip ) +{ + // compute plane intersection + float DDotN = DotProduct( clip->direction, face->normal ); + + // while vertex lighting is disabled we should lighting space + // under model so R_LightVec will working correctly + if( !FBitSet( clip->mesh->flags, FMESH_VERTEX_LIGHTING|FMESH_MODEL_LIGHTMAPS )) + { + if( !FBitSet( face->texture->flags, STUDIO_NF_TWOSIDE )) + { + // NOTE: probably we should normalize the n but for speed reasons i don't do it + if( DDotN >= FLT_EPSILON ) return; + } + } + + // mask off zero or near zero (ray parallel to surface) + bool did_hit = (( DDotN > FLT_EPSILON ) || ( DDotN < -FLT_EPSILON )); + if( !did_hit ) return; // to prevent division by zero + + float numerator = face->NdotP1 - DotProduct( clip->start, face->normal ); + float isect_t = numerator / DDotN; // fraction + float frac = isect_t / clip->distance; // remap to [0..1] + + // now, we have the distance to the plane. lets update our mask + did_hit = did_hit && ( frac > clip->mesh->backfrac ); + did_hit = did_hit && ( frac < clip->trace.fraction ); + if( !did_hit ) return; + + // now, check 3 edges + float hitc1 = clip->start[face->pcoord0] + ( isect_t * clip->direction[face->pcoord0] ); + float hitc2 = clip->start[face->pcoord1] + ( isect_t * clip->direction[face->pcoord1] ); + + // do barycentric coordinate check + float B0 = face->edge1[0] * hitc1 + face->edge1[1] * hitc2 + face->edge1[2]; + did_hit = did_hit && ( B0 >= 0.0f ); + + float B1 = face->edge2[0] * hitc1 + face->edge2[1] * hitc2 + face->edge2[2]; + did_hit = did_hit && ( B1 >= 0.0f ); + + float B2 = B0 + B1; + did_hit = did_hit && ( B2 <= 1.0f ); + + if( !did_hit ) return; + + // if the triangle is transparent + if( face->texture->data ) + { + float u = 1.0 - B2; + float v = B0; + float w = B1; + + // assuming a triangle indexed as v0, v1, v2 + // the projected edge equations are set up such that the vert opposite the first + // equation is v2, and the vert opposite the second equation is v0 + // Therefore we pass them back in 1, 2, 0 order + // Also B2 is currently B1 + B0 and needs to be 1 - (B1+B0) in order to be a real + // barycentric coordinate. Compute that now and pass it to the callback + float s = w * clip->mesh->verts[face->a].st[0] + u * clip->mesh->verts[face->b].st[0] + v * clip->mesh->verts[face->c].st[0]; + float t = w * clip->mesh->verts[face->a].st[1] + u * clip->mesh->verts[face->b].st[1] + v * clip->mesh->verts[face->c].st[1]; + + // convert ST to real pixels position + int x = fix_coord( s * face->texture->width, face->texture->width - 1 ); + int y = fix_coord( t * face->texture->height, face->texture->height - 1 ); + + // test pixel + if( face->texture->data[(face->texture->width * y) + x] == 255 ) + return; + } + + if( clip->trace.fraction > frac ) + { + // at this point we hit the opaque pixel + clip->trace.fraction = bound( 0.0, frac, 1.0 ); + clip->trace.contents = face->contents; + } +} + +//========================================================== +#define HLRAD_TRACE_FACES + +/* +================== +TestLine_r + +================== +*/ +int TestLine_r( tnode_t *head, int node, vec_t p1f, vec_t p2f, const vec3_t start, const vec3_t stop, trace_t *trace ) +{ + tnode_t *tnode; + float front, back; + float frac, midf; + int r, side; + vec3_t mid; +loc0: + if( node < 0 ) + { + // water, slime or lava interpret as empty + if( node == CONTENTS_SOLID ) + return CONTENTS_SOLID; + if( node == CONTENTS_SKY ) + return CONTENTS_SKY; + trace->fraction = 1.0f; + return CONTENTS_EMPTY; + } + + tnode = &head[node]; + front = PlaneDiff( start, tnode ); + back = PlaneDiff( stop, tnode ); +#ifdef HLRAD_TestLine_EDGE_FIX + if( front > FRAC_EPSILON / 2 && back > FRAC_EPSILON / 2 ) + { + node = tnode->children[0]; + goto loc0; + } + + if( front < -FRAC_EPSILON / 2 && back < -FRAC_EPSILON / 2 ) + { + node = tnode->children[1]; + goto loc0; + } + + if( fabs( front ) <= FRAC_EPSILON && fabs( back ) <= FRAC_EPSILON ) + { + int r1 = TestLine_r( head, tnode->children[0], p1f, p2f, start, stop, trace ); + + if( r1 == CONTENTS_SOLID ) + { + trace->contents = r1; + return CONTENTS_SOLID; + } + + int r2 = TestLine_r( head, tnode->children[1], p1f, p2f, start, stop, trace ); + + if( r2 == CONTENTS_SOLID ) + { + trace->contents = r2; + return CONTENTS_SOLID; + } + + if( r1 == CONTENTS_SKY || r2 == CONTENTS_SKY ) + { + trace->contents = r2; + return CONTENTS_SKY; + } + + trace->contents = CONTENTS_EMPTY; + trace->fraction = 1.0f; + return CONTENTS_EMPTY; + } + + side = (front - back) < 0; + frac = front / (front - back); + frac = bound( 0.0, frac, 1.0 ); +#else + if( front >= -FRAC_EPSILON && back >= -FRAC_EPSILON ) + { + node = tnode->children[0]; + goto loc0; + } + + if( front < FRAC_EPSILON && back < FRAC_EPSILON ) + { + node = tnode->children[1]; + goto loc0; + } + + side = (front < 0); + frac = front / (front - back); + frac = bound( 0.0, frac, 1.0 ); +#endif + VectorLerp( start, frac, stop, mid ); + midf = p1f + ( p2f - p1f ) * frac; + + r = TestLine_r( head, tnode->children[side], p1f, midf, start, mid, trace ); + + // trace back faces (in case point was inside of brush) + if( r != CONTENTS_EMPTY ) + { + if( trace->surface == -1 ) + trace->fraction = midf; + trace->contents = r; + return r; + } + +#ifdef HLRAD_TRACE_FACES + // walk through real faces + for( int i = 0; i < tnode->numfaces; i++ ) + { + twface_t *wf = g_world_faces[tnode->firstface + i]; + int contents; + vec3_t delta; + + if( !wf || wf->contents == CONTENTS_SKY ) + continue; + + VectorSubtract( mid, wf->origin, delta ); + if( DotProduct( delta, delta ) >= wf->radius ) + continue; // no intersection + + for( int j = 0; j < wf->numedges; j++ ) + { + if( PlaneDiff( mid, &wf->edges[j] ) > FRAC_EPSILON ) + break; // outside the bounds + } + + if( j != wf->numedges ) + continue; // we are outside the bounds of the facet + + // hit the surface + if( FBitSet( wf->flags, TEX_ALPHATEST )) + { + contents = SampleMiptex( wf->original, mid ); + if( contents == CONTENTS_EMPTY ) + { + // traced through fence + trace->contents = contents; + trace->fraction = midf; + return contents; + } + } + else contents = wf->contents; // sky or solid + + if( contents != CONTENTS_EMPTY ) + { + // fill the trace and out + trace->surface = tnode->firstface + i; + trace->contents = contents; + trace->fraction = midf; + return contents; + } + } +#endif + return TestLine_r( head, tnode->children[!side], midf, p2f, mid, stop, trace ); +} + +/* +==================== +ClipToTriangles + +Mins and maxs enclose the entire area swept by the move +==================== +*/ +static void ClipToTriangles( areanode_t *node, moveclip_t *clip ) +{ + link_t *l, *next; + tface_t *touch; +loc0: + // touch linked edicts + for( l = node->solid_edicts.next; l != &node->solid_edicts; l = next ) + { + next = l->next; + + touch = TFACE_FROM_AREA( l ); + + if( !BoundsIntersect( clip->boxmins, clip->boxmaxs, touch->absmin, touch->absmax )) + continue; + + // might intersect, so do an exact clip + if( clip->trace.contents == CONTENTS_SOLID ) + return; + + ClipMoveToTriangle( touch, clip ); + } + + // recurse down both sides + if( node->axis == -1 ) return; + + if( clip->boxmaxs[node->axis] > node->dist) + { + if( clip->boxmins[node->axis] < node->dist ) + ClipToTriangles( node->children[1], clip ); + node = node->children[0]; + goto loc0; + } + else if( clip->boxmins[node->axis] < node->dist ) + { + node = node->children[1]; + goto loc0; + } +} + +/* +=============== +UnlinkEdict + +just in case for pair +=============== +*/ +static void UnlinkEdict( entity_t *ent ) +{ + // not linked in anywhere + if( !ent->area.prev ) return; + + RemoveLink( &ent->area ); + ent->area.prev = NULL; + ent->area.next = NULL; + numsolidedicts--; +} + +/* +=============== +LinkEdict +=============== +*/ +static void LinkEdict( entity_t *ent, modtype_t modtype, const char *modname, int flags = 0 ) +{ + vec3_t mins, maxs; + void *filedata = NULL; + areanode_t *node; + dmodel_t *bm; + + if( ent->area.prev ) UnlinkEdict( ent ); // unlink from old position + if( ent == g_entities ) return; // don't add the world + + // set the origin + GetVectorForKey( ent, "origin", ent->origin ); + + // also check lightorigin for brushmodels + if( modtype == mod_brush ) + { + bool b_light_origin = false; + bool b_model_center = false; + vec3_t light_origin; + vec3_t model_center; + char *s; + + // allow models to be lit in an alternate location (pt1) + if( *( s = ValueForKey( ent, "light_origin" ))) + { + entity_t *e = FindTargetEntity( s ); + + if( e ) + { + if( *( s = ValueForKey( e, "origin" ))) + { + double v1, v2, v3; + + if( sscanf( s, "%lf %lf %lf", &v1, &v2, &v3 ) == 3 ) + { + VectorSet( light_origin, v1, v2, v3 ); + b_light_origin = true; + } + } + } + } + + // allow models to be lit in an alternate location (pt2) + if( *( s = ValueForKey( ent, "model_center" ))) + { + double v1, v2, v3; + + if( sscanf( s, "%lf %lf %lf", &v1, &v2, &v3 ) == 3 ) + { + VectorSet( model_center, v1, v2, v3 ); + b_model_center = true; + } + } + + // allow models to be lit in an alternate location (pt3) + if( b_light_origin && b_model_center ) + { + VectorSubtract( light_origin, model_center, ent->origin ); + } + } + + if( ent->modtype == mod_unknown ) + { + size_t fileLength = 0; + + // init trace nodes + switch( modtype ) + { + case mod_brush: + bm = ModelForEntity( ent ); + if( !bm ) COM_FatalError( "LinkEdict: not a inline bmodel!\n" ); + MakeTnodes( ent, bm ); + break; + default: + // trying to recognize file format by it's header + if( !g_nomodelshadow ) + filedata = FS_LoadFile( modname, &fileLength, false ); + break; + } + + if( filedata != NULL ) + { + MsgDev( D_NOTE, "loading %s\n", modname ); + + // call the apropriate loader + switch( *(uint *)filedata ) + { + case IDSTUDIOHEADER: + LoadStudio( ent, filedata, fileLength, flags ); + break; + case IDALIASHEADER: + LoadAlias( ent, filedata, fileLength, flags ); + break; + default: + MsgDev( D_ERROR, "%s unknown file format\n", modname ); + Mem_Free( filedata, C_FILESYSTEM ); + break; + } + } + } + + // entity failed to load model + if( ent->modtype == mod_unknown ) + return; + + if( ent->modtype == mod_brush ) + { + bm = ModelForEntity( ent ); + // set the abs box + VectorAdd( ent->origin, bm->mins, ent->absmin ); + VectorAdd( ent->origin, bm->maxs, ent->absmax ); + ExpandBounds( ent->absmin, ent->absmax, 1.0 ); + } + else if( ent->modtype == mod_studio ) + { + StudioGetBounds( ent, mins, maxs ); + VectorAdd( ent->origin, mins, ent->absmin ); + VectorAdd( ent->origin, maxs, ent->absmax ); + ExpandBounds( ent->absmin, ent->absmax, 1.0 ); + } + else if( ent->modtype == mod_alias ) + { + AliasGetBounds( ent, mins, maxs ); + VectorAdd( ent->origin, mins, ent->absmin ); + VectorAdd( ent->origin, maxs, ent->absmax ); + ExpandBounds( ent->absmin, ent->absmax, 1.0 ); + } + else + { + return; + } + + // don't link into world if shadow was disabled + if(( ent->modtype == mod_studio || ent->modtype == mod_alias ) && !FBitSet( flags, FMESH_CAST_SHADOW )) + return; + +#ifdef HLRAD_RAYTRACE + if( ent->modtype == mod_studio || ent->modtype == mod_alias ) + { + tmesh_t *mesh = (tmesh_t *)ent->cache; + + for( int i = 0; i < mesh->numfaces; i++ ) + mesh->ray.AddTriangle( &mesh->faces[i] ); + mesh->ray.BuildTree( mesh ); + + // set backfraction if specified + mesh->ray.SetBackFraction( FloatForKey( ent, "zhlt_backfrac" )); + } +#endif + // find the first node that the ent's box crosses + node = entity_tree.areanodes; + + while( 1 ) + { + if( node->axis == -1 ) break; + if( ent->absmin[node->axis] > node->dist ) + node = node->children[0]; + else if( ent->absmax[node->axis] < node->dist ) + node = node->children[1]; + else break; // crosses the node + } + + // link it in + InsertLinkBefore( &ent->area, &node->solid_edicts ); + numsolidedicts++; +} + +dvertex_t *GetVertexByNumber( int vertexnum ) +{ + ASSERT( vertexnum >= 0 && vertexnum < g_numsurfedges ); + + int e = g_dsurfedges[vertexnum]; + dvertex_t *v; + + if( e >= 0 ) v = &g_dvertexes[g_dedges[e].v[0]]; + else v = &g_dvertexes[g_dedges[-e].v[1]]; + + return v; +} + +void GetTexCoordsForPoint( const vec3_t point, dtexinfo_t *tex, miptex_t *mt, float stcoord[2] ) +{ + float s, t; + + // texture coordinates + s = DotProduct( point, tex->vecs[0] ) + tex->vecs[0][3]; + if( mt ) s /= mt->width; + + t = DotProduct( point, tex->vecs[1] ) + tex->vecs[1][3]; + if( mt ) t /= mt->height; + + stcoord[0] = s; + stcoord[1] = t; +} + +/* +=============== +BuildWorldFaces + +=============== +*/ +void BuildWorldFaces( void ) +{ + // store a list of every face that uses a particular vertex + for( int facenum = 0; facenum < g_numfaces; facenum++ ) + { + vec3_t delta, edgevec; + const dplane_t *faceplane; + byte *facedata; + int contents; + int i, size; + dtexinfo_t *tx; + miptex_t *mt; + twface_t *wf; + const dface_t *f; + + f = &g_dfaces[facenum]; + tx = &g_texinfo[f->texinfo]; + mt = GetTextureByMiptex( tx->miptex ); + if( mt ) contents = GetFaceContents( mt->name ); + else contents = CONTENTS_SOLID; + + if( IsLiquidContents( contents )/* || contents == CONTENTS_SKY*/ || FBitSet( tx->flags, TEX_NOSHADOW )) + continue; // ignore non-lightmapped surfaces + + size = sizeof( twface_t ) + f->numedges * sizeof( dplane_t ); + facedata = (byte *)Mem_Alloc( size ); + wf = (twface_t *)facedata; + facedata += sizeof( twface_t ); + wf->edges = (dplane_t *)facedata; + facedata += f->numedges * sizeof( dplane_t ); + g_world_faces[facenum] = wf; + wf->numedges = f->numedges; + wf->contents = contents; + wf->flags = tx->flags; + wf->original = f; + + if( mt && mt->name[0] == '{' ) + SetBits( wf->flags, TEX_ALPHATEST ); + + faceplane = GetPlaneFromFace( facenum ); + + // compute face origin and plane edges + for( i = 0; i < f->numedges; i++ ) + { + dplane_t *dest = &wf->edges[i]; + vec3_t v0, v1; + + VectorCopy( GetVertexByNumber( f->firstedge + i )->point, v0 ); + VectorCopy( GetVertexByNumber( f->firstedge + (i + 1) % f->numedges )->point, v1 ); + VectorSubtract( v1, v0, edgevec ); + CrossProduct( faceplane->normal, edgevec, dest->normal ); + VectorNormalize( dest->normal ); + dest->dist = DotProduct( dest->normal, v0 ); + dest->type = PlaneTypeForNormal( dest->normal ); + VectorAdd( wf->origin, v0, wf->origin ); + } + + VectorScale( wf->origin, 1.0f / f->numedges, wf->origin ); + + // compute face radius + for( i = 0; i < f->numedges; i++ ) + { + vec3_t v0; + + VectorCopy( GetVertexByNumber( f->firstedge + i )->point, v0 ); + VectorSubtract( v0, wf->origin, delta ); + vec_t radius = DotProduct( delta, delta ); + wf->radius = Q_max( radius, wf->radius ); + } + } +} + +void FreeWorldFaces( void ) +{ + // store a list of every face that uses a particular vertex + for( int facenum = 0; facenum < g_numfaces; facenum++ ) + { + Mem_Free( g_world_faces[facenum] ); + g_world_faces[facenum] = NULL; + } +} + +/* +=============== +InitWorldTrace + +=============== +*/ +void InitWorldTrace( void ) +{ + int i; + + memset( &entity_tree, 0, sizeof( entity_tree )); + + CreateAreaNode( &entity_tree, 0, AREA_MIN_DEPTH, g_dmodels[0].mins, g_dmodels[0].maxs ); + BuildWorldFaces(); // init world faces + MakeTnodes( g_entities, g_dmodels ); // init world nodes + + // link entities into world + for( i = 1; i < g_numentities; i++ ) + { + entity_t *e = &g_entities[i]; + const char *c = ValueForKey( e, "classname" ); + const char *t = ValueForKey( e, "model" ); + int flags = 0; + + if( !*t ) continue; + + // drop to floor env_static's + if( !Q_strcmp( c, "env_static" )) + { + int spawnflags = IntForKey( e, "spawnflags" ); + + // drop static to the floor + if( FBitSet( spawnflags, BIT( 1 ))) + { + vec3_t end; + + // set the origin + GetVectorForKey( e, "origin", e->origin ); + VectorCopy( e->origin, end ); + + if( PointInLeaf( end )->contents != CONTENTS_SOLID ) + { + // delicate drop-to-floor + for( int j = 0; j < 256; j++ ) + { + if( PointInLeaf( end )->contents == CONTENTS_SOLID ) + break; // we hit the floor + end[2] -= 1.0f; + } + + if( j != 256 ) + { + SetVectorForKey( e, "origin", end, true ); + MsgDev( D_REPORT, "%s origin adjusted to -%g units\n", t, e->origin[2] - end[2] ); + ClearBits( spawnflags, BIT( 1 )); + SetKeyValue( e, "spawnflags", va( "%i", spawnflags )); + } + } + } + } + } +#ifdef HLRAD_RAYTRACE + int dispatch = 0, total = 0; + double start = I_FloatTime(); + Msg( "Build KD-Trees:\n" ); + StartPacifier(); + + for( i = 1; i < g_numentities; i++ ) + { + entity_t *e = &g_entities[i]; + const char *c = ValueForKey( e, "classname" ); + const char *t = ValueForKey( e, "model" ); + int flags = 0; + + if( !*t ) continue; + + // both support for quake and half-life + if( !Q_strcmp( c, "env_static" ) || !Q_strcmp( c, "misc_model" )) + { + int spawnflags = IntForKey( e, "spawnflags" ); + + if( !FBitSet( spawnflags, BIT( 2 ))) + total++; + } + else if( BoolForKey( e, "zhlt_modelshadow" ) || BoolForKey( e, "zhlt_vertexlight" )) + { + if( BoolForKey( e, "zhlt_modelshadow" )) + total++; + } + } +#endif + // link entities into world + for( i = 1; i < g_numentities; i++ ) + { + entity_t *e = &g_entities[i]; + const char *c = ValueForKey( e, "classname" ); + const char *t = ValueForKey( e, "model" ); + int flags = 0; + + if( !*t ) continue; + + // both support for quake and half-life + if( !Q_strcmp( c, "env_static" ) || !Q_strcmp( c, "misc_model" )) + { + int spawnflags = IntForKey( e, "spawnflags" ); + + if( !FBitSet( spawnflags, BIT( 2 ))) // spawnflag 4 - disable the shadows + SetBits( flags, FMESH_CAST_SHADOW ); + if( BoolForKey( e, "zhlt_modellightmap" )) + SetBits( flags, FMESH_MODEL_LIGHTMAPS ); + else if( !FBitSet( spawnflags, BIT( 3 ))) // spawnflag 8 - disable the vertex lighting + SetBits( flags, FMESH_VERTEX_LIGHTING ); + if( BoolForKey( e, "zhlt_selfshadow" )) + SetBits( flags, FMESH_SELF_SHADOW ); + if( BoolForKey( e, "zhlt_nosmooth" )) + SetBits( flags, FMESH_DONT_SMOOTH ); + LinkEdict( e, mod_unknown, t, flags ); + } + else if( BoolForKey( e, "zhlt_modelshadow" ) || BoolForKey( e, "zhlt_vertexlight" )) + { + // NOTE: compatibility case for GoldSrc + if( BoolForKey( e, "zhlt_modellightmap" )) + SetBits( flags, FMESH_MODEL_LIGHTMAPS ); + else if( BoolForKey( e, "zhlt_vertexlight" )) + SetBits( flags, FMESH_VERTEX_LIGHTING ); + if( BoolForKey( e, "zhlt_modelshadow" )) + SetBits( flags, FMESH_CAST_SHADOW ); + if( BoolForKey( e, "zhlt_selfshadow" )) + SetBits( flags, FMESH_SELF_SHADOW ); + if( BoolForKey( e, "zhlt_nosmooth" )) + SetBits( flags, FMESH_DONT_SMOOTH ); + LinkEdict( e, mod_unknown, t, flags ); + } + else if( *t == '*' ) + { + int flags = IntForKey( e, "zhlt_lightflags" ); + int shadow = IntForKey( e, "_shadow" ); + + if( FBitSet( flags, BIT( 1 )) || shadow == 1 ) + LinkEdict( e, mod_brush, t ); + } +#ifdef HLRAD_RAYTRACE + if(( e->modtype == mod_studio || e->modtype == mod_alias ) && FBitSet( flags, FMESH_CAST_SHADOW )) + { + dispatch++; + UpdatePacifier( (float)dispatch / total ); + } +#endif + } +#ifdef HLRAD_RAYTRACE + double end = I_FloatTime(); + EndPacifier( end - start ); +#endif +} + +void FreeWorldTrace( void ) +{ + FreeWorldFaces(); +} + +/* +=============================================================================== + +LINE TESTING IN HULLS + +=============================================================================== +*/ +/* +================== +ClipMoveToEntity + +Handles selection or creation of a clipping hull, and offseting (and +eventually rotation) of the end points +================== +*/ +void ClipMoveToEntity( entity_t *ent, const vec3_t start, const vec3_t end, trace_t *trace ) +{ + trace->contents = CONTENTS_EMPTY; + trace->fraction = 1.0f; + trace->surface = -1; + + if( ent->modtype == mod_studio || ent->modtype == mod_alias ) + { + moveclip_t clip; + + clip.mesh = (tmesh_t *)ent->cache; + clip.trace.contents = CONTENTS_EMPTY; + clip.trace.fraction = 1.0f; + clip.trace.surface = -1; + clip.start = start; + clip.end = end; + + MoveBounds( start, end, clip.boxmins, clip.boxmaxs ); + VectorSubtract( end, start, clip.direction ); + clip.distance = VectorNormalize( clip.direction ); + + // run through studio triangles +#ifdef HLRAD_RAYTRACE + clip.mesh->ray.TraceRay( start, end, &clip.trace ); +#else + ClipToTriangles( clip.mesh->face_tree.areanodes, &clip ); +#endif + trace->contents = clip.trace.contents; + trace->fraction = clip.trace.fraction; + } + else if( ent->modtype == mod_brush ) + { + vec3_t start_l, end_l; + + VectorSubtract( start, ent->origin, start_l ); + VectorSubtract( end, ent->origin, end_l ); + trace->fraction = 0.0f; + + TestLine_r((tnode_t *)ent->cache, 0, 0.0, 1.0, start_l, end_l, trace ); + } + else + { + COM_FatalError( "ClipMoveToEntity: unknown model type\n" ); + } +} + +/* +==================== +ClipToLinks + +Mins and maxs enclose the entire area swept by the move +==================== +*/ +static void ClipToLinks( areanode_t *node, moveclip_t *clip ) +{ + link_t *l, *next; + entity_t *touch; + trace_t trace; +loc0: + // touch linked edicts + for( l = node->solid_edicts.next; l != &node->solid_edicts; l = next ) + { + next = l->next; + + touch = ENTITY_FROM_AREA( l ); + + if( touch == clip->ignore ) + continue; + + if( !BoundsIntersect( clip->boxmins, clip->boxmaxs, touch->absmin, touch->absmax )) + continue; + + // might intersect, so do an exact clip + if( clip->trace.contents == CONTENTS_SOLID ) + return; + + if( clip->nomodels && ( touch->modtype == mod_studio || touch->modtype == mod_alias )) + continue; + + ClipMoveToEntity( touch, clip->start, clip->end, &trace ); + + clip->trace = CombineTraces( &clip->trace, &trace ); + } + + // recurse down both sides + if( node->axis == -1 ) return; + + if( clip->boxmaxs[node->axis] > node->dist) + { + if( clip->boxmins[node->axis] < node->dist ) + ClipToLinks( node->children[1], clip ); + node = node->children[0]; + goto loc0; + } + else if( clip->boxmins[node->axis] < node->dist ) + { + node = node->children[1]; + goto loc0; + } +} + +/* +================== +TraceLine +================== +*/ +int TestLine( int threadnum, const vec3_t start, const vec3_t end, bool nomodels, entity_t *ignoreent ) +{ + moveclip_t clip; + + clip.trace.contents = CONTENTS_EMPTY; + clip.trace.fraction = 0.0f; + clip.trace.surface = -1; + + // trace world first + TestLine_r( (tnode_t *)g_entities->cache, 0, 0.0f, 1.0f, start, end, &clip.trace ); + + // run through entities (bmodels, studiomodels) + if(( numsolidedicts > 0 ) && ( clip.trace.fraction != 0.0f )) + { + float trace_fraction; + vec3_t trace_endpos; + + VectorLerp( start, clip.trace.fraction, end, trace_endpos ); + trace_fraction = clip.trace.fraction; + clip.trace.fraction = 1.0f; + clip.nomodels = nomodels; + clip.start = start; + clip.end = trace_endpos; + clip.ignore = ignoreent; + + MoveBounds( start, trace_endpos, clip.boxmins, clip.boxmaxs ); + ClipToLinks( entity_tree.areanodes, &clip ); + + clip.trace.fraction *= trace_fraction; + } + + return clip.trace.contents; +} + +/* +================== +TestLine + +trace world only +================== +*/ +void TestLine( int threadnum, const vec3_t start, const vec3_t stop, trace_t *trace ) +{ + trace->contents = CONTENTS_EMPTY; + trace->fraction = 0.0f; + trace->surface = -1; + + // trace world first + TestLine_r( (tnode_t *)g_entities->cache, 0, 0.0f, 1.0f, start, stop, trace ); +} + +typedef double point_t[3]; + +typedef struct +{ + int point[2]; + bool divided; + int child[2]; +} edge_t; + +typedef struct +{ + int edge[3]; + int dir[3]; +} triangle_t; + +void CopyToSkynormals( int skylevel, int numpoints, point_t *points, int numedges, edge_t *edges, int numtriangles, triangle_t *triangles ) +{ + double totalsize = 0; + int j, k; + + ASSERT( numpoints == ( 1 << ( 2 * skylevel )) + 2 ); + ASSERT( numedges == ( 1 << ( 2 * skylevel )) * 4 - 4 ); + ASSERT( numtriangles == ( 1 << ( 2 * skylevel )) * 2 ); + + g_skynormalsizes[skylevel] = (vec_t *)Mem_Alloc( numpoints * sizeof( vec_t )); + g_skynormals[skylevel] = (vec3_t *)Mem_Alloc( numpoints * sizeof( vec3_t )); + g_numskynormals[skylevel] = numpoints; + + for( j = 0; j < numpoints; j++ ) + { + VectorCopy( points[j], g_skynormals[skylevel][j] ); + g_skynormalsizes[skylevel][j] = 0; + } + + for( j = 0; j < numtriangles; j++ ) + { + double currentsize; + double tmp[3]; + int pt[3]; + + for( k = 0; k < 3; k++ ) + pt[k] = edges[triangles[j].edge[k]].point[triangles[j].dir[k]]; + + + CrossProduct( points[pt[0]], points[pt[1]], tmp ); + currentsize = DotProduct( tmp, points[pt[2]] ); + + ASSERT( currentsize > 0 ); + + g_skynormalsizes[skylevel][pt[0]] += currentsize / 3.0; + g_skynormalsizes[skylevel][pt[1]] += currentsize / 3.0; + g_skynormalsizes[skylevel][pt[2]] += currentsize / 3.0; + totalsize += currentsize; + } + + for( j = 0; j < numpoints; j++ ) + { + g_skynormalsizes[skylevel][j] /= totalsize; + } +} + +void BuildDiffuseNormals( void ) +{ + int numpoints = 6; + int numedges = 12; + int numtriangles = 8; + int i, j, k; + + g_numskynormals[0] = 0; + g_skynormals[0] = NULL; //don't use this + g_skynormalsizes[0] = NULL; + + point_t *points = (point_t *)Mem_Alloc((( 1 << ( 2 * SKYLEVELMAX )) + 2 ) * sizeof( point_t ), C_TEMPORARY ); + edge_t *edges = (edge_t *)Mem_Alloc((( 1 << ( 2 * SKYLEVELMAX )) * 4 - 4 ) * sizeof( edge_t ), C_TEMPORARY ); + triangle_t *triangles = (triangle_t *)Mem_Alloc((( 1 << ( 2 * SKYLEVELMAX )) * 2 ) * sizeof( triangle_t ), C_TEMPORARY ); + + VectorSet( points[0], 1.0, 0.0, 0.0 ); + VectorSet( points[1],-1.0, 0.0, 0.0 ); + VectorSet( points[2], 0.0, 1.0, 0.0 ); + VectorSet( points[3], 0.0,-1.0, 0.0 ); + VectorSet( points[4], 0.0, 0.0, 1.0 ); + VectorSet( points[5], 0.0, 0.0,-1.0 ); + + edges[0].point[0] = 0, edges[0].point[1] = 2, edges[0].divided = false; + edges[1].point[0] = 2, edges[1].point[1] = 1, edges[1].divided = false; + edges[2].point[0] = 1, edges[2].point[1] = 3, edges[2].divided = false; + edges[3].point[0] = 3, edges[3].point[1] = 0, edges[3].divided = false; + edges[4].point[0] = 2, edges[4].point[1] = 4, edges[4].divided = false; + edges[5].point[0] = 4, edges[5].point[1] = 3, edges[5].divided = false; + edges[6].point[0] = 3, edges[6].point[1] = 5, edges[6].divided = false; + edges[7].point[0] = 5, edges[7].point[1] = 2, edges[7].divided = false; + edges[8].point[0] = 4, edges[8].point[1] = 0, edges[8].divided = false; + edges[9].point[0] = 0, edges[9].point[1] = 5, edges[9].divided = false; + edges[10].point[0] = 5, edges[10].point[1] = 1, edges[10].divided = false; + edges[11].point[0] = 1, edges[11].point[1] = 4, edges[11].divided = false; + + triangles[0].edge[0] = 0, triangles[0].dir[0] = 0, triangles[0].edge[1] = 4; + triangles[0].dir[1] = 0, triangles[0].edge[2] = 8, triangles[0].dir[2] = 0; + triangles[1].edge[0] = 1, triangles[1].dir[0] = 0, triangles[1].edge[1] = 11; + triangles[1].dir[1] = 0, triangles[1].edge[2] = 4, triangles[1].dir[2] = 1; + triangles[2].edge[0] = 2, triangles[2].dir[0] = 0, triangles[2].edge[1] = 5; + triangles[2].dir[1] = 1, triangles[2].edge[2] = 11, triangles[2].dir[2] = 1; + triangles[3].edge[0] = 3, triangles[3].dir[0] = 0, triangles[3].edge[1] = 8; + triangles[3].dir[1] = 1, triangles[3].edge[2] = 5, triangles[3].dir[2] = 0; + triangles[4].edge[0] = 0, triangles[4].dir[0] = 1, triangles[4].edge[1] = 9; + triangles[4].dir[1] = 0, triangles[4].edge[2] = 7, triangles[4].dir[2] = 0; + triangles[5].edge[0] = 1, triangles[5].dir[0] = 1, triangles[5].edge[1] = 7; + triangles[5].dir[1] = 1, triangles[5].edge[2] = 10, triangles[5].dir[2] = 0; + triangles[6].edge[0] = 2, triangles[6].dir[0] = 1, triangles[6].edge[1] = 10; + triangles[6].dir[1] = 1, triangles[6].edge[2] = 6, triangles[6].dir[2] = 1; + triangles[7].edge[0] = 3, triangles[7].dir[0] = 1, triangles[7].edge[1] = 6; + triangles[7].dir[1] = 0, triangles[7].edge[2] = 9, triangles[7].dir[2] = 1; + + CopyToSkynormals( 1, numpoints, points, numedges, edges, numtriangles, triangles ); + + for( i = 1; i < SKYLEVELMAX; i++ ) + { + int oldnumtriangles = numtriangles; + int oldnumedges = numedges; + + for( j = 0; j < oldnumedges; j++ ) + { + if( !edges[j].divided ) + { + point_t mid; + double len; + int p2; + + ASSERT( numpoints < ( 1 << ( 2 * SKYLEVELMAX )) + 2 ); + + VectorAdd( points[edges[j].point[0]], points[edges[j].point[1]], mid ); + len = VectorLength( mid ); + + ASSERT( len > 0.2 ); + + VectorScale( mid, 1.0 / len, mid ); + p2 = numpoints; + VectorCopy( mid, points[numpoints] ); + numpoints++; + + ASSERT( numedges < ( 1 << ( 2 * SKYLEVELMAX )) * 4 - 4 ); + edges[j].child[0] = numedges; + edges[numedges].divided = false; + edges[numedges].point[0] = edges[j].point[0]; + edges[numedges].point[1] = p2; + numedges++; + + ASSERT( numedges < ( 1 << ( 2 * SKYLEVELMAX )) * 4 - 4 ); + + edges[j].child[1] = numedges; + edges[numedges].divided = false; + edges[numedges].point[0] = p2; + edges[numedges].point[1] = edges[j].point[1]; + numedges++; + edges[j].divided = true; + } + } + + for( j = 0; j < oldnumtriangles; j++ ) + { + int mid[3]; + + for( k = 0; k < 3; k++ ) + { + ASSERT( numtriangles < ( 1 << ( 2 * SKYLEVELMAX )) * 2 ); + mid[k] = edges[edges[triangles[j].edge[k]].child[0]].point[1]; + triangles[numtriangles].edge[0] = edges[triangles[j].edge[k]].child[1 - triangles[j].dir[k]]; + triangles[numtriangles].dir[0] = triangles[j].dir[k]; + triangles[numtriangles].edge[1] = edges[triangles[j].edge[(k+1)%3]].child[triangles[j].dir[(k+1)%3]]; + triangles[numtriangles].dir[1] = triangles[j].dir[(k+1)%3]; + triangles[numtriangles].edge[2] = numedges + k; + triangles[numtriangles].dir[2] = 1; + numtriangles++; + } + + for( k = 0; k < 3; k++ ) + { + ASSERT( numedges < ( 1 << ( 2 * SKYLEVELMAX )) * 4 - 4 ); + triangles[j].edge[k] = numedges; + triangles[j].dir[k] = 0; + edges[numedges].divided = false; + edges[numedges].point[0] = mid[k]; + edges[numedges].point[1] = mid[(k+1)%3]; + numedges++; + } + } + + CopyToSkynormals( i + 1, numpoints, points, numedges, edges, numtriangles, triangles ); + } + + Mem_Free( triangles, C_TEMPORARY ); + Mem_Free( points, C_TEMPORARY ); + Mem_Free( edges, C_TEMPORARY ); +} + +void FreeDiffuseNormals( void ) +{ + for( int i = 0; i < SKYLEVELMAX + 1; i++ ) + { + Mem_Free( g_skynormalsizes[i] ); + Mem_Free( g_skynormals[i] ); + g_skynormalsizes[i] = NULL; + g_skynormals[i] = NULL; + } +} \ No newline at end of file diff --git a/utils/p2rad/vertexlight.cpp b/utils/p2rad/vertexlight.cpp new file mode 100644 index 0000000..cf1bff7 --- /dev/null +++ b/utils/p2rad/vertexlight.cpp @@ -0,0 +1,757 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include "qrad.h" +#include "model_trace.h" +#include "..\..\engine\studio.h" + +#ifdef HLRAD_VERTEXLIGHTING + +#define MAX_INDIRECT_DIST 1024.0f + +typedef struct +{ + int modelnum : 10; + int vertexnum : 22; +} vertremap_t; + +static entity_t *g_vertexlight[MAX_MAP_MODELS]; +static int g_vertexlight_modnum; +static vertremap_t *g_vertexlight_indexes; +static uint g_vertexlight_numindexes; + +static vec3_t g_box_directions[6] = +{ +{ 1.0f, 0.0f, 0.0f }, +{-1.0f, 0.0f, 0.0f }, +{ 0.0f, 1.0f, 0.0f }, +{ 0.0f, -1.0f, 0.0f }, +{ 0.0f, 0.0f, 1.0f }, +{ 0.0f, 0.0f, -1.0f }, +}; + +void NudgeVertexPosition( vec3_t position ) +{ + vec3_t test; + int x; + + VectorCopy( position, test ); + + if( PointInLeaf( test ) != g_dleafs ) + return; + + for( x = 0; x < 6; x++ ) + { + VectorMA( position, 2.0f, g_box_directions[x], test ); + if( PointInLeaf( test )->contents != CONTENTS_SOLID ) + { + VectorCopy( test, position ); + return; + } + } + + for( x = 0; x < 6; x++ ) + { + VectorMA( position, 3.0f, g_box_directions[x], test ); + if( PointInLeaf( test )->contents != CONTENTS_SOLID ) + { + VectorCopy( test, position ); + return; + } + } +} + +void GetStylesFromMesh( byte newstyles[4], const tmesh_t *mesh ) +{ + ThreadLock(); + memcpy( newstyles, mesh->styles, sizeof( newstyles )); + ThreadUnlock(); +} + +void AddStylesToMesh( tmesh_t *mesh, byte newstyles[4] ) +{ + int i, j; + + // store styles back to the mesh + ThreadLock(); + + for( i = 0; i < MAXLIGHTMAPS && newstyles[i] != 255; i++ ) + { + for( j = 0; j < MAXLIGHTMAPS; j++ ) + { + if( mesh->styles[j] == newstyles[i] || mesh->styles[j] == 255 ) + break; + } + + if( j == MAXLIGHTMAPS ) + continue; + + // allocate a new one + if( mesh->styles[j] == 255 ) + mesh->styles[j] = newstyles[i]; + } + + ThreadUnlock(); +} + +void SmoothModelNormals( int modelnum, int threadnum = -1 ) +{ + entity_t *mapent = g_vertexlight[modelnum]; + tmesh_t *mesh; + + if( g_smoothing_threshold == 0 ) + return; + + // sanity check + if( !mapent || !mapent->cache ) + return; + + mesh = (tmesh_t *)mapent->cache; + if( !mesh->verts || mesh->numverts <= 0 ) + return; + + // build the smoothed normals + if( FBitSet( mesh->flags, FMESH_DONT_SMOOTH )) + return; + + vec3_t *normals = (vec3_t *)Mem_Alloc( mesh->numverts * sizeof( vec3_t )); + + for( int hashSize = 1; hashSize < mesh->numverts; hashSize <<= 1 ); + hashSize = hashSize >> 2; + + // build a map from vertex to a list of triangles that share the vert. + CUtlArray vertHashMap; + + vertHashMap.AddMultipleToTail( hashSize ); + + for( int vertID = 0; vertID < mesh->numverts; vertID++ ) + { + tvert_t *tv = &mesh->verts[vertID]; + uint hash = VertexHashKey( tv->point, hashSize ); + vertHashMap[hash].AddToTail( vertID ); + } + + for( int hashID = 0; hashID < hashSize; hashID++ ) + { + for( int i = 0; i < vertHashMap[hashID].Size(); i++ ) + { + int vertID = vertHashMap[hashID][i]; + tvert_t *tv0 = &mesh->verts[vertID]; + + for( int j = 0; j < vertHashMap[hashID].Size(); j++ ) + { + tvert_t *tv1 = &mesh->verts[vertHashMap[hashID][j]]; + + if( !VectorCompareEpsilon( tv0->point, tv1->point, ON_EPSILON )) + continue; + + if( DotProduct( tv0->normal, tv1->normal ) >= g_smoothing_threshold ) + VectorAdd( normals[vertID], tv1->normal, normals[vertID] ); + } + } + } + + // copy smoothed normals back + for( int j = 0; j < mesh->numverts; j++ ) + { + VectorCopy( normals[j], mesh->verts[j].normal ); + VectorNormalize2( mesh->verts[j].normal ); + } + + Mem_Free( normals ); +} + +void BuildVertexLights( int indexnum, int thread = -1 ) +{ + int modelnum = g_vertexlight_indexes[indexnum].modelnum; + int vertexnum = g_vertexlight_indexes[indexnum].vertexnum; + entity_t *mapent = g_vertexlight[modelnum]; + float shadow[MAXLIGHTMAPS]; + vec3_t light[MAXLIGHTMAPS]; + vec3_t delux[MAXLIGHTMAPS]; + entity_t *ignoreent = NULL; + byte *vislight = NULL; + byte styles[4]; + vec3_t normal; + tmesh_t *mesh; + int j; + + // sanity check + if( !mapent || !mapent->cache ) + return; + + mesh = (tmesh_t *)mapent->cache; + if( !mesh->verts || mesh->numverts <= 0 ) + return; + + if( !FBitSet( mesh->flags, FMESH_SELF_SHADOW )) + ignoreent = mapent; + + GetStylesFromMesh( styles, mesh ); + + // stats + g_direct_luxels[thread]++; + +#ifdef HLRAD_COMPUTE_VISLIGHTMATRIX + vislight = mesh->vislight; +#endif + // vertexlighting is easy. We don't needs to find valid points etc + tvert_t *tv = &mesh->verts[vertexnum]; + vec3_t point; + + // not supposed for vertex lighting? + if( !tv->light ) return; + + // nudge position from ground + NudgeVertexPosition( tv->light->pos ); // nudged vertexes will be used on indirect lighting too + VectorCopy( tv->normal, normal ); + + // calculate visibility for the sample + int leaf = PointInLeaf( tv->light->pos ) - g_dleafs; + + if( FBitSet( mesh->flags, FMESH_SELF_SHADOW )) + VectorMA( tv->light->pos, DEFAULT_HUNT_OFFSET, tv->normal, point ); + else VectorCopy( tv->light->pos, point ); + + memset( light, 0, sizeof( light )); + memset( delux, 0, sizeof( delux )); + memset( shadow, 0, sizeof( shadow )); + + // gather direct lighting for our vertex + GatherSampleLight( thread, -1, point, leaf, normal, light, delux, shadow, styles, vislight, 0, ignoreent ); + + // add an ambient term if desired + if( g_ambient[0] || g_ambient[1] || g_ambient[2] ) + { + for( j = 0; j < MAXLIGHTMAPS && styles[j] == 255; j++ ); + if( j == MAXLIGHTMAPS ) styles[0] = 0; // adding style + + for( j = 0; j < MAXLIGHTMAPS && styles[j] != 255; j++ ) + { + if( styles[j] == 0 ) + { + VectorAdd( light[j], g_ambient, light[j] ); +#ifdef HLRAD_DELUXEMAPPING + vec_t avg = VectorAvg( g_ambient ); + VectorMA( delux[j], -DIFFUSE_DIRECTION_SCALE * avg, normal, delux[j] ); +#endif + break; + } + } + } + + if( !g_lightbalance ) + { + for( int k = 0; k < MAXLIGHTMAPS; k++ ) + { + VectorScale( light[k], g_direct_scale, light[k] ); + VectorScale( delux[k], g_direct_scale, delux[k] ); + } + } + + // grab indirect lighting for vertex from light_environment or lighting vertex in -fast mode + GatherSampleLight( thread, -1, point, leaf, normal, light, delux, shadow, styles, NULL, 1, ignoreent ); + + // store results back into the vertex + for( j = 0; j < MAXLIGHTMAPS && styles[j] != 255; j++ ) + { + VectorCopy( light[j], tv->light->light[j] ); + VectorCopy( delux[j], tv->light->deluxe[j] ); + tv->light->shadow[j] = shadow[j]; + } + + AddStylesToMesh( mesh, styles ); +} + +bool AddPatchStyleToMesh( trace_t *trace, tmesh_t *mesh, tvert_t *tv, vec3_t *s_light, vec3_t *s_dir, byte newstyles[4] ) +{ + vec3_t sampled_light[MAXLIGHTMAPS]; + vec3_t sampled_dir[MAXLIGHTMAPS]; + int i, lightstyles; + vec_t dot, total; + int numpatches; + const int *patches; + vec3_t v, dir; + + if( trace->surface < 0 || trace->surface >= g_numfaces ) + return false; + + GetTriangulationPatches( trace->surface, &numpatches, &patches ); // collect patches and their neighbors + + memset( sampled_light, 0, sizeof( sampled_light )); + memset( sampled_dir, 0, sizeof( sampled_dir )); + total = 0.0f; + + for( i = 0; i < numpatches; i++ ) + { + patch_t *p = &g_patches[patches[i]]; + + if( FBitSet( p->flags, PATCH_OUTSIDE )) + continue; + + for( int j = 0; j < MAXLIGHTMAPS && p->totalstyle[j] != 255; j++ ) + { + if( p->samples[j] <= 0.0 ) + continue; + + for( lightstyles = 0; lightstyles < MAXLIGHTMAPS && newstyles[lightstyles] != 255; lightstyles++ ) + { + if( newstyles[lightstyles] == p->totalstyle[j] ) + break; + } + + if( lightstyles == MAXLIGHTMAPS ) + { + // overflowed + continue; + } + else if( newstyles[lightstyles] == 255 ) + { + newstyles[lightstyles] = p->totalstyle[j]; + } + + const vec_t *b = GetTotalLight( p, p->totalstyle[j] ); + VectorScale( b, (1.0), v ); + VectorAdd( sampled_light[lightstyles], v, sampled_light[lightstyles] ); + + const vec_t *d = GetTotalDirection(p, p->totalstyle[j] ); + VectorScale( d, (1.0), v ); + + VectorCopy( v, dir ); + VectorNormalize( dir ); + dot = DotProduct( dir, tv->normal ); + + if(( -dot ) > 0 ) + { + // reflect the direction back (this is not ideal!) + VectorMA( v, -(-dot) * 2.0f, tv->normal, v ); + } + + + VectorAdd( sampled_dir[lightstyles], v, sampled_dir[lightstyles] ); + total++; + } + } + + // add light to vertex + for( int k = 0; k < MAXLIGHTMAPS && newstyles[k] != 255; k++ ) + { + VectorScale( sampled_light[k], 1.0f / total, sampled_light[k] ); + VectorScale( sampled_dir[k], 1.0f / total, sampled_dir[k] ); + + if( VectorMaximum( sampled_light[k] ) >= EQUAL_EPSILON ) + { + if( s_light ) VectorAdd( s_light[k], sampled_light[k], s_light[k] ); + if( s_dir ) VectorAdd( s_dir[k], sampled_dir[k], s_dir[k] ); + } + } + + return true; +} + +/* +============ +VertexPatchLights + +This function is run multithreaded +============ +*/ +void VertexPatchLights( int indexnum, int threadnum = -1 ) +{ + int modelnum = g_vertexlight_indexes[indexnum].modelnum; + int vertexnum = g_vertexlight_indexes[indexnum].vertexnum; + vec3_t *skynormals = g_skynormals[SKYLEVEL_SOFTSKYOFF]; + entity_t *mapent = g_vertexlight[modelnum]; + vec3_t sampled_light[MAXLIGHTMAPS]; + vec3_t sampled_dir[MAXLIGHTMAPS]; + byte newstyles[4]; + trace_t besttrace; + vec_t total; + trace_t trace; + tmesh_t *mesh; + vec3_t delta; + vec_t dot; + + // sanity check + if( !mapent || !mapent->cache ) + return; + + mesh = (tmesh_t *)mapent->cache; + if( !mesh->verts || mesh->numverts <= 0 ) + return; + + besttrace.surface = -1; + besttrace.fraction = 1.0f; + besttrace.contents = CONTENTS_EMPTY; + GetStylesFromMesh( newstyles, mesh ); + + tvert_t *tv = &mesh->verts[vertexnum]; + + // not supposed for vertex lighting? + if( !tv->light ) return; + + memset( sampled_light, 0, sizeof( sampled_light )); + memset( sampled_dir, 0, sizeof( sampled_dir )); + total = 0.0f; + + // NOTE: we can't using patches directly because they linked to faces + // we should find nearest face with matched normal and just nearest face + // then lerp between them + for( int j = 0; j < g_numskynormals[SKYLEVEL_SOFTSKYOFF]; j++ ) + { + dot = -DotProduct( skynormals[j], tv->normal ); +// if( dot <= NORMAL_EPSILON ) continue; + + VectorScale( skynormals[j], -MAX_INDIRECT_DIST, delta ); + VectorAdd( tv->light->pos, delta, delta ); + + TestLine( threadnum, tv->light->pos, delta, &trace ); + + if( trace.surface == -1 ) + continue; + + if( trace.fraction < besttrace.fraction ) + besttrace = trace; + + AddPatchStyleToMesh( &trace, mesh, tv, sampled_light, sampled_dir, newstyles ); + total++; + } + + if( total <= 0 ) return; + + // add light to vertex + for( int k = 0; k < MAXLIGHTMAPS && mesh->styles[k] != 255; k++ ) + { + VectorScale( sampled_light[k], 1.0f / total, sampled_light[k] ); + VectorScale( sampled_dir[k], 1.0f / total, sampled_dir[k] ); + VectorScale( sampled_light[k], g_indirect_scale, sampled_light[k] ); + VectorScale( sampled_dir[k], g_indirect_scale, sampled_dir[k] ); + + if( VectorMaximum( sampled_light[k] ) >= EQUAL_EPSILON ) + { + VectorAdd( tv->light->light[k], sampled_light[k], tv->light->light[k] ); + VectorAdd( tv->light->deluxe[k], sampled_dir[k], tv->light->deluxe[k] ); + } + } + + AddStylesToMesh( mesh, newstyles ); +} + +void FinalLightVertex( int modelnum, int threadnum = -1 ) +{ + entity_t *mapent = g_vertexlight[modelnum]; + int usedstyles[MAXLIGHTMAPS]; + vec3_t lb, v, direction; + int lightstyles; + vec_t minlight; + tmesh_t *mesh; + dmodelvertlight_t *dml; + dvlightlump_t *l; + + // sanity check + if( !mapent || !mapent->cache ) + return; + + mesh = (tmesh_t *)mapent->cache; + if( !mesh->verts || mesh->numverts <= 0 ) + return; + + for( lightstyles = 0; lightstyles < MAXLIGHTMAPS; lightstyles++ ) + { + if( mesh->styles[lightstyles] == 255 ) + break; + } + + // completely black model + if( !lightstyles ) return; + + memset( usedstyles, 0, sizeof( usedstyles )); + + l = (dvlightlump_t *)g_dvlightdata; + ASSERT( l->dataofs[modelnum] != -1 ); + + dml = (dmodelvertlight_t *)((byte *)g_dvlightdata + l->dataofs[modelnum]); + ASSERT( dml->numverts == mesh->numverts ); + + minlight = FloatForKey( mapent, "_minlight" ); + if( minlight < 1.0 ) minlight *= 128.0f; // GoldSrc + else minlight *= 0.5f; // Quake + + if( g_lightbalance ) + minlight *= g_direct_scale; + if( g_numbounce > 0 ) minlight = 0.0f; // ignore for radiosity + + // vertexlighting is easy. We don't needs to find valid points etc + for( int i = 0; i < mesh->numverts; i++ ) + { + tvert_t *tv = &mesh->verts[i]; + dvertlight_t *dvl = &dml->verts[i]; + int j; + + if( !tv->light ) continue; + + for( int k = 0; k < lightstyles; k++ ) + { + VectorCopy( tv->light->light[k], lb ); + VectorCopy( tv->light->deluxe[k], direction ); + + if( VectorMax( lb ) > EQUAL_EPSILON ) + usedstyles[k]++; + + if( g_lightbalance ) + { + VectorScale( lb, g_direct_scale, lb ); + VectorScale( direction, g_direct_scale, direction ); + } + + vec_t avg = VectorAvg( lb ); + VectorScale( direction, 1.0 / Q_max( 1.0, avg ), direction ); + + // clip from the bottom first + lb[0] = Q_max( lb[0], minlight ); + lb[1] = Q_max( lb[1], minlight ); + lb[2] = Q_max( lb[2], minlight ); + + // clip from the top + if( lb[0] > g_maxlight || lb[1] > g_maxlight || lb[2] > g_maxlight ) + { + // find max value and scale the whole color down; + float max = VectorMax( lb ); + + for( j = 0; j < 3; j++ ) + lb[j] = ( lb[j] * g_maxlight ) / max; + } + + // do gamma adjust + lb[0] = (float)pow( lb[0] / 256.0f, g_gamma ) * 256.0f; + lb[1] = (float)pow( lb[1] / 256.0f, g_gamma ) * 256.0f; + lb[2] = (float)pow( lb[2] / 256.0f, g_gamma ) * 256.0f; +#ifdef HLRAD_RIGHTROUND + dvl->light[k].r = Q_rint( lb[0] ); + dvl->light[k].g = Q_rint( lb[1] ); + dvl->light[k].b = Q_rint( lb[2] ); +#else + dvl->light[k].r = (byte)lb[0]; + dvl->light[k].g = (byte)lb[1]; + dvl->light[k].b = (byte)lb[2]; +#endif + VectorScale( direction, 0.225, v ); // the scale is calculated such that length( v ) < 1 + + if( DotProduct( v, v ) > ( 1.0 - NORMAL_EPSILON )) + VectorNormalize( v ); + + VectorNegate( v, v ); // let the direction point from face sample to light source + + // keep deluxe vectors in modelspace for vertex lighting + for( int x = 0; x < 3; x++ ) + { + lb[x] = v[x] * 127.0f + 128.0f; + lb[x] = bound( 0, lb[x], 255.0 ); + } + + dvl->deluxe[k].r = (byte)lb[0]; + dvl->deluxe[k].g = (byte)lb[1]; + dvl->deluxe[k].b = (byte)lb[2]; + + // shadows are simple + dvl->shadow[k] = tv->light->shadow[k] * 255; + } + } + + for( int k = 0; k < lightstyles; k++ ) + { + if( usedstyles[k] == 0 ) + mesh->styles[k] = 255; + } +} + +static void GenerateLightCacheNumbers( void ) +{ + char string[32]; + tmesh_t *mesh; + int i, j; + + for( i = 1; i < g_numentities; i++ ) + { + entity_t *mapent = &g_entities[i]; + + // no cache - no lighting + if( !mapent->cache ) continue; + + mesh = (tmesh_t *)mapent->cache; + + if( mapent->modtype != mod_alias && mapent->modtype != mod_studio ) + continue; + + if( !mesh->verts || mesh->numverts <= 0 ) + continue; + + if( !FBitSet( mesh->flags, FMESH_VERTEX_LIGHTING )) + continue; + + short lightid = g_vertexlight_modnum++; + // at this point we have valid target for vertex lighting + Q_snprintf( string, sizeof( string ), "%i", lightid + 1 ); + SetKeyValue( mapent, "vlight_cache", string ); + g_vertexlight[lightid] = mapent; + } + + g_vertexlight_numindexes = 0; + + // generate remapping table for more effective CPU utilize + for( i = 0; i < g_vertexlight_modnum; i++ ) + { + entity_t *mapent = g_vertexlight[i]; + mesh = (tmesh_t *)mapent->cache; + g_vertexlight_numindexes += mesh->numverts; + } + + g_vertexlight_indexes = (vertremap_t *)Mem_Alloc( g_vertexlight_numindexes * sizeof( vertremap_t )); + uint curIndex = 0; + + for( i = 0; i < g_vertexlight_modnum; i++ ) + { + entity_t *mapent = g_vertexlight[i]; + mesh = (tmesh_t *)mapent->cache; + + // encode model as lowpart and vertexnum as highpart + for( j = 0; j < mesh->numverts; j++ ) + { + g_vertexlight_indexes[curIndex].modelnum = i; + g_vertexlight_indexes[curIndex].vertexnum = j; + curIndex++; + } + ASSERT( curIndex <= g_vertexlight_numindexes ); + } +} + +static int ModelSize( tmesh_t *mesh ) +{ + if( !mesh ) return 0; + + if( !mesh->verts || mesh->numverts <= 0 ) + return 0; + + for( int lightstyles = 0; lightstyles < MAXLIGHTMAPS; lightstyles++ ) + { + if( mesh->styles[lightstyles] == 255 ) + break; + } + + // model is valid but completely not lighted by direct + if( !lightstyles ) return 0; + + return sizeof( dmodelvertlight_t ) - ( sizeof( dvertlight_t ) * 3 ) + sizeof( dvertlight_t ) * mesh->numverts + ((g_numworldlights + 7) / 8); +} + +static int WriteModelLight( tmesh_t *mesh, byte *out ) +{ + int size = ModelSize( mesh ); + dmodelvertlight_t *dml; + + if( !size ) return 0; + + dml = (dmodelvertlight_t *)out; + out += sizeof( dmodelvertlight_t ) - ( sizeof( dvertlight_t ) * 3 ) + sizeof( dvertlight_t ) * mesh->numverts; + + dml->modelCRC = mesh->modelCRC; + dml->numverts = mesh->numverts; + + memcpy( dml->styles, mesh->styles, sizeof( dml->styles )); + memcpy( dml->submodels, mesh->vsubmodels, sizeof( dml->submodels )); + memcpy( out, mesh->vislight, ((g_numworldlights + 7) / 8)); + + return size; +} + +static void AllocVertexLighting( void ) +{ + int totaldatasize = ( sizeof( int ) * 3 ) + ( sizeof( int ) * g_vertexlight_modnum ); + int i, len; + byte *data; + dvlightlump_t *l; + + for( i = 0; i < g_vertexlight_modnum; i++ ) + { + entity_t *mapent = g_vertexlight[i]; + + // sanity check + if( !mapent ) continue; + + len = ModelSize( (tmesh_t *)mapent->cache ); + totaldatasize += len; + } + + Msg( "total vertexlight data: %s\n", Q_memprint( totaldatasize )); + g_dvlightdata = (byte *)Mem_Realloc( g_dvlightdata, totaldatasize ); + + // now setup to get the miptex data (or just the headers if using -wadtextures) from the wadfile + l = (dvlightlump_t *)g_dvlightdata; + data = (byte *)&l->dataofs[g_vertexlight_modnum]; + + l->ident = VLIGHTIDENT; + l->version = VLIGHT_VERSION; + l->nummodels = g_vertexlight_modnum; + + for( i = 0; i < g_vertexlight_modnum; i++ ) + { + entity_t *mapent = g_vertexlight[i]; + + l->dataofs[i] = data - (byte *)l; + len = WriteModelLight( (tmesh_t *)mapent->cache, data ); + if( !len ) l->dataofs[i] = -1; // completely black model + data += len; + } + + g_vlightdatasize = data - g_dvlightdata; + + if( totaldatasize != g_vlightdatasize ) + COM_FatalError( "WriteVertexLighting: memory corrupted\n" ); + + // vertex cache acesss + // const char *id = ValueForKey( ent, "vlight_cache" ); + // int cacheID = atoi( id ) - 1; + // if( cacheID < 0 || cacheID > num_map_entities ) return; // bad cache num + // if( l->dataofs[cacheID] == -1 ) return; // cache missed + // otherwise it's valid +} + +void BuildVertexLights( void ) +{ + GenerateLightCacheNumbers(); + + // new code is very fast, so no reason to show progress + RunThreadsOnIndividual( g_vertexlight_modnum, false, SmoothModelNormals ); + + if( !g_vertexlight_numindexes ) return; + + RunThreadsOnIndividual( g_vertexlight_numindexes, true, BuildVertexLights ); +} + +void VertexPatchLights( void ) +{ + if( !g_vertexlight_numindexes ) return; + + RunThreadsOnIndividual( g_vertexlight_numindexes, true, VertexPatchLights ); +} + +void FinalLightVertex( void ) +{ + if( !g_vertexlight_modnum ) return; + + AllocVertexLighting(); + + RunThreadsOnIndividual( g_vertexlight_modnum, true, FinalLightVertex ); + + Mem_Free( g_vertexlight_indexes ); + g_vertexlight_indexes = NULL; + g_vertexlight_numindexes = 0; +} +#endif \ No newline at end of file diff --git a/utils/p2vis/flow.cpp b/utils/p2vis/flow.cpp new file mode 100644 index 0000000..c6a8085 --- /dev/null +++ b/utils/p2vis/flow.cpp @@ -0,0 +1,624 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include "qvis.h" +#include "threads.h" + +int c_chains; +int c_portalskip, c_leafskip; +int c_vistest, c_mighttest; +int c_mightseeupdate; +int active; + +#ifdef _DEBUG +void CheckStack( leaf_t *leaf, threaddata_t *thread ) +{ + pstack_t *p, *p2; + + for( p = thread->pstack_head.next; p != NULL; p = p->next ) + { + if( p->leaf == leaf ) + COM_FatalError( "CheckStack: leaf recursion\n" ); + + for( p2 = thread->pstack_head.next; p2 != p; p2 = p2->next ) + { + if( p2->leaf == p->leaf ) + COM_FatalError( "CheckStack: late leaf recursion\n" ); + } + } +} +#endif + +/* +============== +ClipToSeperators + +Source, pass, and target are an ordering of portals. + +Generates seperating planes canidates by taking two points from source and one +point from pass, and clips target by them. + +If target is totally clipped away, that portal can not be seen through. + +Normal clip keeps target on the same side as pass, which is correct if the +order goes source, pass, target. If the order goes pass, source, target then +flipclip should be set. +============== +*/ +static winding_t *ClipToSeperators( winding_t *source, winding_t *pass, winding_t *target, bool flipclip, pstack_t *stack ) +{ + int i, j, k, l; + int counts[3]; + bool fliptest; + vec3_t v1, v2; + plane_t plane; + vec_t d; + + // check all combinations + for( i = 0; i < source->numpoints; i++ ) + { + l = (i + 1) % source->numpoints; + VectorSubtract( source->p[l], source->p[i], v1 ); + + // fing a vertex of pass that makes a plane that puts all of the + // vertexes of pass on the front side and all of the vertexes of + // source on the back side + for( j = 0; j < pass->numpoints; j++ ) + { + VectorSubtract( pass->p[j], source->p[i], v2 ); + CrossProduct( v1, v2, plane.normal ); + + if( VectorNormalize( plane.normal ) < VIS_EPSILON ) + continue; + + plane.dist = DotProduct( pass->p[j], plane.normal ); + fliptest = false; + + // find out which side of the generated seperating plane + // has the source portal + for( k = 0; k < source->numpoints; k++ ) + { + if(( k == i ) | ( k == l )) // | instead of || for branch optimization + continue; + + d = DotProduct( source->p[k], plane.normal ) - plane.dist; + + if( d < -VIS_EPSILON ) + { + // source is on the negative side, so we want all + // pass and target on the positive side + fliptest = false; + break; + } + else if( d > VIS_EPSILON ) + { + // source is on the positive side, so we want all + // pass and target on the negative side + fliptest = true; + break; + } + } + + if( k == source->numpoints ) + continue; // planar with source portal + + if( fliptest ) + { + // flip the normal if the source portal is backwards + VectorNegate( plane.normal, plane.normal ); + plane.dist = -plane.dist; + } + + // if all of the pass portal points are now on the positive side, + // this is the seperating plane + counts[0] = counts[1] = counts[2] = 0; + for( k = 0; k < pass->numpoints; k++ ) + { + if( k == j ) continue; + + d = DotProduct( pass->p[k], plane.normal ) - plane.dist; + + if( d < -VIS_EPSILON ) + break; + else if( d > VIS_EPSILON ) + counts[0]++; + else counts[2]++; + } + + if( k != pass->numpoints ) + continue; // points on negative side, not a seperating plane + + if( !counts[0] ) + continue; // planar with seperating plane + + if( flipclip ) + { + // flip the normal if we want the back side + VectorNegate( plane.normal, plane.normal ); + plane.dist = -plane.dist; + } + + stack->seperators[flipclip][stack->numseperators[flipclip]] = plane; + + if( ++stack->numseperators[flipclip] >= MAX_SEPERATORS ) + COM_FatalError( "MAX_SEPERATORS on stack\n" ); + + // fast check first + d = DotProduct( stack->portal->origin, plane.normal ) - plane.dist; + + // if completely at the back of the seperator plane + if( d < -stack->portal->radius ) + return NULL; + + // if completely on the front of the seperator plane + if( d > stack->portal->radius ) + break; + + // clip target by the seperating plane + target = ChopWindingEpsilon( target, stack, &plane, VIS_EPSILON ); + if( !target ) return NULL; // target is not visible + + break; // optimization by Antony Suter + } + } + + return target; +} + +/* +================== +RecursiveLeafFlow + +Flood fill through the leafs +If src_portal is NULL, this is the originating leaf +================== +*/ +inline static void RecursiveLeafFlow( int leafnum, threaddata_t *thread, pstack_t *prevstack ) +{ + pstack_t stack; + plane_t backplane; + long *test, *might; + long more, *vis; + leaf_t *leaf; + portal_t *p; + vec_t d; + + leaf = &g_leafs[leafnum]; + c_chains++; + +#ifdef _DEBUG + CheckStack( leaf, thread ); +#endif + // mark the leaf as visible + if( !CHECKVISBIT( thread->leafvis, leafnum )) + { + SETVISBIT( thread->leafvis, leafnum ); + thread->base->numcansee++; + } + + prevstack->next = &stack; + stack.head = prevstack->head; + stack.numseperators[0] = 0; + stack.numseperators[1] = 0; + stack.next = NULL; + stack.leaf = leaf; + stack.portal = NULL; + + might = (long *)stack.mightsee; + vis = (long *)thread->leafvis; + + // check all portals for flowing into other leafs + for( int i = 0; i < leaf->numportals; i++ ) + { + p = leaf->portals[i]; + + if( !CHECKVISBIT( stack.head->mightsee, p->leaf )) + { + c_leafskip++; + continue; // can't possibly see it + } + + if( !CHECKVISBIT( prevstack->mightsee, p->leaf )) + { + c_leafskip++; + continue; // can't possibly see it + } + + // if the portal can't see anything we haven't allready seen, skip it + if( p->status == stat_done ) + { + test = (long *)p->visbits; + c_vistest++; + } + else + { + test = (long *)p->mightsee; + c_mighttest++; + } + + more = 0; + + for( int j = 0; j < g_bitlongs; j++ ) + { + might[j] = ((long *)prevstack->mightsee)[j] & test[j]; + more |= (might[j] & ~vis[j]); + } + + if( !more ) + { + // can't see anything new + c_portalskip++; + continue; + } + + // get plane of portal, point normal into the neighbor leaf + stack.portalplane = p->plane; + VectorNegate( p->plane.normal, backplane.normal ); + backplane.dist = -p->plane.dist; + + if( VectorCompareEpsilon( prevstack->portalplane.normal, backplane.normal, EQUAL_EPSILON )) + continue; // can't go out a coplanar face + + c_portalcheck++; + + stack.portal = p; + stack.next = NULL; + + stack.freewindings[0] = 1; + stack.freewindings[1] = 1; + stack.freewindings[2] = 1; + + d = DotProduct( p->origin, thread->pstack_head.portalplane.normal ); + d -= thread->pstack_head.portalplane.dist; + + if( d < -p->radius ) + { + continue; + } + else if( d > p->radius ) + { + stack.pass = p->winding; + } + else + { + stack.pass = ChopWindingEpsilon( p->winding, &stack, &thread->pstack_head.portalplane, VIS_EPSILON ); + if( !stack.pass ) continue; + } + + d = DotProduct( thread->base->origin, p->plane.normal ); + d -= p->plane.dist; + + if( d > thread->base->radius ) + { + continue; + } + else if( d < -thread->base->radius ) + { + stack.source = prevstack->source; + } + else + { + stack.source = ChopWindingEpsilon( prevstack->source, &stack, &backplane, VIS_EPSILON ); + // FIXME: shouldn't we create a new source origin and radius for fast checks? + if( !stack.source ) continue; + } + + if( !prevstack->pass ) + { + // the second leaf can only be blocked if coplanar + RecursiveLeafFlow( p->leaf, thread, &stack ); + continue; + } + + stack.pass = ChopWindingEpsilon( stack.pass, &stack, &prevstack->portalplane, VIS_EPSILON ); + if( !stack.pass ) continue; + + c_portaltest++; + + if( stack.numseperators[0] ) + { + for( int n = 0; n < stack.numseperators[0]; n++ ) + { + stack.pass = ChopWindingEpsilon( stack.pass, &stack, &stack.seperators[0][n], VIS_EPSILON ); + if( !stack.pass ) break; // target is not visible + } + + if( n < stack.numseperators[0] ) + continue; + } + else stack.pass = ClipToSeperators( stack.source, prevstack->pass, stack.pass, false, &stack ); + if( !stack.pass ) continue; + + if( stack.numseperators[1] ) + { + for( int n = 0; n < stack.numseperators[1]; n++ ) + { + stack.pass = ChopWindingEpsilon( stack.pass, &stack, &stack.seperators[1][n], VIS_EPSILON ); + if( !stack.pass ) break; // target is not visible + } + } + else stack.pass = ClipToSeperators( prevstack->pass, stack.source, stack.pass, true, &stack ); + if( !stack.pass ) continue; + + c_portalpass++; + + // flow through it for real + RecursiveLeafFlow( p->leaf, thread, &stack ); + stack.next = NULL; + } +} + +/* +============= +UpdateMightSee + +Called after completing a portal and finding that the source leaf is no +longer visible from the dest leaf. Visibility is symetrical, so the reverse +must also be true. Update mightsee for any portals on the source leaf which +haven't yet started processing. + +Called with the lock held. +============= +*/ +static void UpdateMightsee( const leaf_t *source, const leaf_t *dest ) +{ + int leafnum = dest - g_leafs; + portal_t *p; + + for( int i = 0; i < source->numportals; i++ ) + { + p = source->portals[i]; + + if( p->status != stat_none ) + continue; + + if( CHECKVISBIT( p->mightsee, leafnum )) + { + CLEARVISBIT( p->mightsee, leafnum ); + c_mightseeupdate++; + p->nummightsee--; + } + } +} + +/* +============= +PortalCompleted + +Mark the portal completed and propogate new vis information across +to the complementry portals. + +Called with the lock held. +============= +*/ +static void PortalCompleted( portal_t *completed ) +{ + long *might, *vis; + int leafnum; + portal_t *p, *p2; + leaf_t *myleaf; + long changed; + + ThreadLock(); + + // for each portal on the leaf, check the leafs we eliminated from + // mightsee during the full vis so far. + myleaf = &g_leafs[completed->leaf]; + + for( int i = 0; i < myleaf->numportals; i++ ) + { + p = myleaf->portals[i]; + if( p->status != stat_done ) + continue; + + might = (long *)p->mightsee; + vis = (long *)p->visbits; + + for( int j = 0; j < g_bitlongs; j++ ) + { + changed = might[j] & ~vis[j]; + if( !changed ) continue; + + // if any of these changed bits are still visible + // from another portal, we can't update yet. + for( int k = 0; k < myleaf->numportals; k++ ) + { + if( k == i ) continue; + + p2 = myleaf->portals[k]; + if( p2->status == stat_done ) + changed &= ~((long *)p2->visbits)[j]; + else changed &= ~((long *)p2->mightsee)[j]; + if( !changed ) break; + } + + // update mightsee for any of the changed bits that survived + while( changed ) + { + int bit = ffsl( changed ) - 1; + changed &= ~BIT( bit ); + leafnum = (j << 5) + bit; + UpdateMightsee( g_leafs + leafnum, myleaf ); + } + } + } + + ThreadUnlock(); +} + +/* +=============== +PortalFlow + +=============== +*/ +void PortalFlow( int portalnum, int threadnum ) +{ + threaddata_t data; + portal_t *p; + + p = g_sorted_portals[portalnum]; + p->status = stat_working; + + p->visbits = (byte *)Mem_Alloc( g_bitbytes ); + memset( &data, 0, sizeof( data )); + data.leafvis = p->visbits; + data.base = p; + + data.pstack_head.head = &data.pstack_head; + data.pstack_head.portal = p; + data.pstack_head.source = p->winding; + data.pstack_head.portalplane = p->plane; + + for( int i = 0; i < g_bitlongs; i++ ) + ((long *)data.pstack_head.mightsee)[i] = ((long *)p->mightsee)[i]; + + RecursiveLeafFlow( p->leaf, &data, &data.pstack_head ); + p->status = stat_done; +#ifdef HLVIS_MERGE_PORTALS + PortalCompleted( p ); +#endif +} + +/* +=============== +PortalFlow + +=============== +*/ +void PortalFlow( portal_t *p ) +{ + threaddata_t data; + + if( p->status != stat_working ) + COM_FatalError( "PortalFlow: reflowed\n" ); + + p->visbits = (byte *)Mem_Alloc( g_bitbytes ); + memset( &data, 0, sizeof( data )); + data.leafvis = p->visbits; + data.base = p; + + data.pstack_head.head = &data.pstack_head; + data.pstack_head.portal = p; + data.pstack_head.source = p->winding; + data.pstack_head.portalplane = p->plane; + + for( int i = 0; i < g_bitlongs; i++ ) + ((long *)data.pstack_head.mightsee)[i] = ((long *)p->mightsee)[i]; + + RecursiveLeafFlow( p->leaf, &data, &data.pstack_head ); + p->status = stat_done; +#ifdef HLVIS_MERGE_PORTALS + PortalCompleted( p ); +#endif +} + +/* +=============================================================================== + +This is a rough first-order aproximation that is used to trivially reject some +of the final calculations. + +=============================================================================== +*/ +static void SimpleFlood( portal_t *srcportal, int leafnum, byte *portalsee, int *c_leafsee ) +{ + leaf_t *leaf; + + if( CHECKVISBIT( srcportal->mightsee, leafnum )) + return; + + SETVISBIT( srcportal->mightsee, leafnum ); + (*c_leafsee)++; + + leaf = &g_leafs[leafnum]; + + for( int i = 0; i < leaf->numportals; i++ ) + { + portal_t *p = leaf->portals[i]; + + if( !portalsee[p - g_portals] ) + continue; + + SimpleFlood( srcportal, p->leaf, portalsee, c_leafsee ); + } +} + +/* +============== +BasePortalVis +============== +*/ +void BasePortalVis( int threadnum ) +{ + byte portalsee[MAX_MAP_PORTALS*2]; + int i, j, k, c_leafsee; + vec3_t backnormal, dist; + portal_t *tp, *p; + winding_t *w; + float d; + + while( 1 ) + { + if(( i = GetThreadWork()) == -1 ) + break; + + p = &g_portals[i]; + + p->mightsee = (byte *)Mem_Alloc( g_bitbytes ); + memset( portalsee, 0, g_numportals * 2 ); + VectorNegate( p->plane.normal, backnormal ); + + for( j = 0, tp = g_portals; j < g_numportals * 2; j++, tp++ ) + { + if( j == i ) continue; + + if( VectorCompare( backnormal, tp->plane.normal )) + continue; + + if( g_farplane > 0 ) + { + VectorSubtract( tp->origin, p->origin, dist ); + if( VectorLength( dist ) - tp->radius - p->radius > g_farplane ) + continue; + } + + w = tp->winding; + + for( k = 0; k < w->numpoints; k++ ) + { + d = DotProduct( w->p[k], p->plane.normal ) - p->plane.dist; + if( d > -VIS_EPSILON ) + break; + } + + if( k == w->numpoints ) + continue; // no points on front + + w = p->winding; + + for( k = 0; k < w->numpoints; k++ ) + { + d = DotProduct( w->p[k], tp->plane.normal ) - tp->plane.dist; + if( d < VIS_EPSILON ) + break; + } + + if( k == w->numpoints ) + continue; // no points on back + + portalsee[j] = 1; + } + + c_leafsee = 0; + SimpleFlood( p, p->leaf, portalsee, &c_leafsee ); + p->nummightsee = c_leafsee; + } +} \ No newline at end of file diff --git a/utils/p2vis/p2vis.dsp b/utils/p2vis/p2vis.dsp new file mode 100644 index 0000000..97c36f0 --- /dev/null +++ b/utils/p2vis/p2vis.dsp @@ -0,0 +1,194 @@ +# Microsoft Developer Studio Project File - Name="p2vis" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=p2vis - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "p2vis.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "p2vis.mak" CFG="p2vis - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "p2vis - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "p2vis - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/SDKSrc/Tools/utils/p2vis", KVGBAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "p2vis - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir ".\Release" +# PROP BASE Intermediate_Dir ".\Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\..\temp\p2vis\!release" +# PROP Intermediate_Dir "..\..\temp\p2vis\!release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "..\common" /I "..\..\common" /D "NDEBUG" /D "WIN32" /D "_CONSOLE" /D "IGNORE_SEARCH_IN_WADS" /FD /c +# SUBTRACT CPP /Fr /YX +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 /opt:nowin98 +# ADD LINK32 /nologo /subsystem:console /pdb:none /machine:I386 /opt:nowin98 +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\p2vis\!release +InputPath=\Paranoia2\src_main\temp\p2vis\!release\p2vis.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\p2vis.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\p2vis.exe "D:\Paranoia2\tools\p2vis.exe" + +# End Custom Build + +!ELSEIF "$(CFG)" == "p2vis - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir ".\Debug" +# PROP BASE Intermediate_Dir ".\Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\..\temp\p2vis\!debug" +# PROP Intermediate_Dir "..\..\temp\p2vis\!debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MTd /W3 /Gm /Gi /GX /ZI /Od /I "..\common" /I "..\..\common" /D "_DEBUG" /D "WIN32" /D "_CONSOLE" /D "IGNORE_SEARCH_IN_WADS" /FAs /FR /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 +# ADD LINK32 msvcrtd.lib /nologo /subsystem:console /debug /machine:I386 /nodefaultlib:"libcmtd.lib" +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\p2vis\!debug +InputPath=\Paranoia2\src_main\temp\p2vis\!debug\p2vis.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\p2vis.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\p2vis.exe "D:\Paranoia2\tools\p2vis.exe" + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "p2vis - Win32 Release" +# Name "p2vis - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=..\common\bspfile.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\cmdlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\conprint.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\filesystem.cpp +# End Source File +# Begin Source File + +SOURCE=.\flow.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\mathlib.cpp +# End Source File +# Begin Source File + +SOURCE=.\qvis.cpp +# End Source File +# Begin Source File + +SOURCE=.\qvis.h +# End Source File +# Begin Source File + +SOURCE=..\common\scriplib.cpp +# End Source File +# Begin Source File + +SOURCE=.\soundpvs.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\stringlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\threads.cpp +# End Source File +# Begin Source File + +SOURCE=.\winding.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\zone.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=..\common\bspfile.h +# End Source File +# Begin Source File + +SOURCE=..\common\cmdlib.h +# End Source File +# Begin Source File + +SOURCE=..\common\mathlib.h +# End Source File +# Begin Source File + +SOURCE=..\common\scriplib.h +# End Source File +# Begin Source File + +SOURCE=..\common\threads.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/utils/p2vis/qvis.cpp b/utils/p2vis/qvis.cpp new file mode 100644 index 0000000..6421266 --- /dev/null +++ b/utils/p2vis/qvis.cpp @@ -0,0 +1,682 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// qvis.c + +#include "qvis.h" +#include "threads.h" + +int g_numportals; +int g_portalleafs; +portal_t *g_portals; +leaf_t *g_leafs; +int *g_leafstarts; +int *g_leafcounts; +int g_leafcount_all; + +int c_portaltest, c_portalpass, c_portalcheck; +int c_totalvis, c_saw_into_leaf, c_optimized; +portal_t *g_sorted_portals[MAX_MAP_PORTALS*2]; + +byte *vismap, *vismap_p, *vismap_end; // past visfile +int originalvismapsize; +byte *g_uncompressed; // [bitbytes*portalleafs] +int c_reused; + +int g_bitbytes; // (portalleafs+63)>>3 +int g_bitlongs; + +bool g_fastvis = DEFAULT_FASTVIS; +bool g_nosort = DEFAULT_NOSORT; +int g_testlevel = DEFAULT_TESTLEVEL; +vec_t g_farplane = DEFAULT_FARPLANE; + +//============================================================================= +void prl( leaf_t *l ) +{ + portal_t *p; + plane_t pl; + + for( int i = 0; i < l->numportals; i++ ) + { + p = l->portals[i]; + pl = p->plane; + Msg( "portal %4i to leaf %4i : %7.1f : (%4.1f, %4.1f, %4.1f)\n", + (int)(p - g_portals), p->leaf, pl.dist, pl.normal[0], pl.normal[1], pl.normal[2] ); + } +} + +/* +============= +GetNextPortal + +Returns the next portal for a thread to work on +Returns the portals from the least complex, so the later ones can reuse +the earlier information. +============= +*/ +portal_t *GetNextPortal( void ) +{ + int i, j, min; + portal_t *p, *tp; + + i = GetThreadWork(); // bump the pacifier + if( i == -1 ) return NULL; + + ThreadLock(); + + min = 99999; + p = NULL; + + for( j = 0, tp = g_portals; j < g_numportals * 2; j++, tp++ ) + { + if( tp->nummightsee < min && tp->status == stat_none ) + { + min = tp->nummightsee; + p = tp; + } + } + + if( p ) p->status = stat_working; + + ThreadUnlock(); + + return p; +} + +/* +============== +LeafThread +============== +*/ +static void LeafThread( int thread ) +{ + portal_t *p; + + while( 1 ) + { + if(( p = GetNextPortal()) == NULL ) + break; + PortalFlow( p ); + }; +} + +//============================================================================= +/* +============= +SortPortals + +Sorts the portals from the least complex, so the later ones can reuse +the earlier information. +============= +*/ +static int PComp( const void *a, const void *b ) +{ + if((*(portal_t **)a)->nummightsee == (*(portal_t **)b)->nummightsee ) + return 0; + + if((*(portal_t **)a)->nummightsee < (*(portal_t **)b)->nummightsee ) + return -1; + + return 1; +} + +static void SortPortals( void ) +{ + for( int i = 0; i < g_numportals * 2; i++ ) + g_sorted_portals[i] = &g_portals[i]; + + if( g_nosort ) return; + + qsort( g_sorted_portals, g_numportals * 2, sizeof( g_sorted_portals[0] ), PComp ); +} + +//============================================================================= +/* +=============== +LeafMerge + +Merges the portal visibility for a leaf +=============== +*/ +void LeafMerge( int leafnum ) +{ + leaf_t *leaf; + byte *outbuffer; + int i, j, numvis = 0; + portal_t *p; + + // flow through all portals, collecting visible bits + outbuffer = g_uncompressed + leafnum * g_bitbytes; + leaf = &g_leafs[leafnum]; + + for( i = 0; i < leaf->numportals; i++ ) + { + p = leaf->portals[i]; + + if( p->status != stat_done ) + COM_FatalError( "portal not done (leaf %d)\n", leafnum ); + + for( j = 0; j < g_bitbytes; j++ ) + outbuffer[j] |= p->visbits[j]; + } + + if( CHECKVISBIT( outbuffer, leafnum )) + c_saw_into_leaf++; + + SETVISBIT( outbuffer, leafnum ); + numvis++; // count the leaf itself + + for( i = 0; i < g_portalleafs; i++ ) + { + if( CHECKVISBIT( outbuffer, i )) + numvis++; + } + + // compress the bit string + MsgDev( D_REPORT, "leaf %4i : %4i visible\n", leafnum, numvis ); + c_totalvis += numvis; +} + +/* +=============== +LeafFlow + +Builds the entire visibility list for a leaf +=============== +*/ +static void LeafFlow( int leafnum ) +{ + byte compressed[MAX_MAP_LEAFS/8]; + byte outbuffer2[MAX_MAP_LEAFS/8]; + int diskbytes = (g_leafcount_all + 7) >> 3; + byte *dest, *data = NULL; + byte *outbuffer; + int i, j; + + memset( compressed, 0, sizeof( compressed )); + outbuffer = g_uncompressed + leafnum * g_bitbytes; + + // crosscheck the leafs + for( i = 0; i < g_portalleafs; i++ ) + { + if( i == leafnum ) continue; + + // sometimes leaf A is visible from leaf B + // but leaf B is not visible from leaf A + // fixup this issue - make leaf A invisible from B + if( CHECKVISBIT( outbuffer, i )) + { + byte *other = g_uncompressed + i * g_bitbytes; + + if( !CHECKVISBIT( other, leafnum )) + { + CLEARVISBIT( outbuffer, i ); + c_optimized++; + } + } + } + + memset( outbuffer2, 0, diskbytes ); + + for( i = 0; i < g_portalleafs; i++ ) + { + for( j = 0; j < g_leafcounts[i]; j++ ) + { + int visbit = (g_leafstarts[i] + j); + + if( CHECKVISBIT( outbuffer, i )) + SETVISBIT( outbuffer2, visbit ); + } + } + + for( i = 0, data = g_uncompressed; i < leafnum; i++, data += g_bitbytes ) + { + if( !memcmp( data, outbuffer2, g_bitbytes )) + { + g_dleafs[g_leafstarts[leafnum]+1].visofs = g_dleafs[i+1].visofs; + c_reused++; + return; + } + } + + // compress the buffer now + i = CompressVis( outbuffer2, diskbytes, compressed, sizeof( compressed )); + + dest = vismap_p; + vismap_p += i; + + if( vismap_p > vismap_end ) + COM_FatalError( "Vismap expansion overflow\n" ); + + for( j = 0; j < g_leafcounts[leafnum]; j++ ) + { + g_dleafs[g_leafstarts[leafnum]+j+1].visofs = dest - vismap; + } + + memcpy( dest, compressed, i ); +} + +/* +================== +CalcFastVis + +fastvis just uses mightsee for a very loose bound +================== +*/ +static void CalcFastVis( void ) +{ + for( int i = 0; i < g_numportals * 2; i++ ) + { + Mem_Free( g_portals[i].visbits ); + g_portals[i].visbits = g_portals[i].mightsee; + g_portals[i].status = stat_done; + g_portals[i].mightsee = NULL; + } +} + +/* +================== +CalcPortalVis +================== +*/ +static void CalcPortalVis( void ) +{ +#ifdef HLVIS_SORT_PORTALS + RunThreadsOnIndividual( g_numportals * 2, true, PortalFlow ); +#else + RunThreadsOn( g_numportals * 2, true, LeafThread ); +#endif + MsgDev( D_REPORT, "portalcheck: %i portaltest: %i portalpass: %i\n", c_portalcheck, c_portaltest, c_portalpass ); + MsgDev( D_REPORT, "c_vistest: %i c_mighttest: %i, c_merged %i\n", c_vistest, c_mighttest, c_mightseeupdate ); +} + +/* +================== +CalcVis +================== +*/ +static void CalcVis( void ) +{ + int i; + + RunThreadsOn( g_numportals * 2, true, BasePortalVis ); +#ifdef HLVIS_SORT_PORTALS + SortPortals (); +#endif + if( g_fastvis ) + { + CalcFastVis (); + } + else + { + CalcPortalVis (); + } + + // assemble the leaf vis lists by oring and compressing the portal lists + for( i = 0; i < g_portalleafs; i++ ) + LeafMerge( i ); + + if( c_saw_into_leaf ) + MsgDev( D_WARN, "%i leaf portals saw into leaf\n", c_saw_into_leaf ); + + // now crosscheck each leaf's vis and compress + for( i = 0; i < g_portalleafs; i++ ) + LeafFlow( i ); + + MsgDev( D_INFO, "optimized: %d visible leafs %d (%.2f%%)\n", c_optimized, c_totalvis, c_optimized * 100 / (float)c_totalvis ); + MsgDev( D_INFO, "average leafs visible: %i\n", c_totalvis / g_portalleafs ); + MsgDev( D_REPORT, "total leafs visible: %i, reused vis data %i\n", c_totalvis, c_reused ); +} + +/* +================== +SetPortalSphere +================== +*/ +void SetPortalSphere( portal_t *p ) +{ + vec3_t center, edge; + winding_t *w; + int i; + + VectorClear( center ); + w = p->winding; + + for( i = 0; i < w->numpoints; i++ ) + VectorAdd( center, w->p[i], center ); + VectorScale( center, ( 1.0 / w->numpoints ), center ); + + for( i = 0; i < w->numpoints; i++ ) + { + VectorSubtract( center, w->p[i], edge ); + float r = VectorLength( edge ); + p->radius = Q_max( r, p->radius ); + } + + VectorCopy( center, p->origin ); +} + +/* +============ +LoadPortals +============ +*/ +static void LoadPortals( const char *name ) +{ + int i, j, leafnums[2]; + int maxportals = 0; + int numpoints; + plane_t plane; + portal_t *p; + leaf_t *l; + FILE *f; + winding_t *w; + + f = fopen( name, "r" ); + if( !f ) COM_FatalError( "couldn't read %s\nno vis performed\n", name ); + + if( fscanf( f, "%i\n%i\n", &g_portalleafs, &g_numportals ) != 2 ) + COM_FatalError( "LoadPortals: failed to read header\n" ); + + Msg( "%4i portalleafs\n", g_portalleafs ); + Msg( "%4i numportals\n", g_numportals ); + + if( g_numportals > MAX_MAP_PORTALS ) + COM_FatalError( "MAX_MAP_PORTALS limit exceeded\n" ); + + // these counts should take advantage of 64 bit systems automatically + g_bitbytes = ((g_portalleafs + 63) & ~63) >> 3; + g_bitlongs = g_bitbytes / sizeof( long ); + + // each file portal is split into two memory portals + g_portals = (portal_t *)Mem_Alloc( 2 * g_numportals * sizeof( portal_t )); + g_leafs = (leaf_t *)Mem_Alloc( g_portalleafs * sizeof( leaf_t )); + g_leafcounts = (int *)Mem_Alloc( g_portalleafs * sizeof( int )); + g_leafstarts = (int *)Mem_Alloc( g_portalleafs * sizeof( int )); + + originalvismapsize = g_portalleafs * ((g_portalleafs + 7) / 8 ); + vismap = vismap_p = g_dvisdata; + vismap_end = vismap + MAX_MAP_VISIBILITY; + + if( g_portalleafs > MAX_MAP_LEAFS ) + { + // this may cause hlvis to overflow, because numportalleafs can be larger than g_numleafs in some special cases + COM_FatalError( "Too many portalleafs( g_portalleafs( %d ) > MAX_MAP_LEAFS( %d ))\n", g_portalleafs, MAX_MAP_LEAFS ); + } + + g_leafcount_all = 0; + + for( i = 0; i < g_portalleafs; i++ ) + { + if( fscanf( f, "%i\n", &g_leafcounts[i] ) != 1 ) + COM_FatalError("LoadPortals: read leaf %i failed\n", i ); + g_leafstarts[i] = g_leafcount_all; + g_leafcount_all += g_leafcounts[i]; + } + + if( g_leafcount_all != g_numvisleafs ) + { + // internal error (this should never happen) + COM_FatalError( "LoadPortals: portalleafs %d mismatch bsp visleafs %d\n", g_leafcount_all, g_numvisleafs ); + } + + for( i = 0, p = g_portals; i < g_numportals; i++ ) + { + if( fscanf( f, "%i %i %i ", &numpoints, &leafnums[0], &leafnums[1] ) != 3 ) + COM_FatalError( "LoadPortals: reading portal %i\n", i ); + + if( numpoints > MAX_POINTS_ON_WINDING ) + COM_FatalError( "LoadPortals: portal %i has too many points\n", i ); + + if((uint)leafnums[0] > g_portalleafs || (uint)leafnums[1] > g_portalleafs ) + COM_FatalError( "LoadPortals: reading portal %i\n", i ); + + w = p->winding = AllocWinding( numpoints ); + w->numpoints = numpoints; + + for( j = 0; j < numpoints; j++ ) + { + double v[3]; + + // scanf into double, then assign to vec_t + if( fscanf( f, "(%lf %lf %lf ) ", &v[0], &v[1], &v[2]) != 3 ) + COM_FatalError( "LoadPortals: reading portal %i\n", i ); + VectorCopy( v, w->p[j] ); + } + fscanf( f, "\n" ); + + // calc plane + WindingPlane( w, plane.normal, &plane.dist ); + + // create forward portal + l = &g_leafs[leafnums[0]]; + if( l->numportals == MAX_PORTALS_ON_LEAF ) + COM_FatalError( "Leaf with too many portals\n" ); + l->portals[l->numportals] = p; + l->numportals++; + + maxportals = Q_max( maxportals, l->numportals ); + + p->winding = w; + VectorNegate( plane.normal, p->plane.normal ); + p->plane.dist = -plane.dist; + p->leaf = leafnums[1]; + SetPortalSphere( p ); + p++; + + // create backwards portal + l = &g_leafs[leafnums[1]]; + if( l->numportals == MAX_PORTALS_ON_LEAF ) + COM_FatalError( "Leaf with too many portals\n" ); + l->portals[l->numportals] = p; + l->numportals++; + + maxportals = Q_max( maxportals, l->numportals ); + + p->winding = AllocWinding( w->numpoints ); + p->winding->numpoints = w->numpoints; + p->plane = plane; + p->leaf = leafnums[0]; + + // reverse winding for backward portal + for( j = 0; j < w->numpoints; j++ ) + VectorCopy( w->p[w->numpoints-1-j], p->winding->p[j] ); + SetPortalSphere( p ); + p++; + + } + + fclose( f ); + + if( maxportals > 60 ) + Msg( "^1%d peak portals on a leaf^7\n", maxportals ); + else if( maxportals > 40 ) + Msg( "^3%d peak portals on a leaf^7\n", maxportals ); + else Msg( "^2%d peak portals on a leaf^7\n", maxportals ); +} + +/* +============ +FreePortals +============ +*/ +static void FreePortals( void ) +{ + for( int i = 0; i < g_numportals * 2; i++ ) + { + portal_t *p = &g_portals[i]; + + if( p->mightsee ) Mem_Free( p->mightsee ); + if( p->visbits ) Mem_Free( p->visbits ); + if( p->winding ) FreeWinding( p->winding ); + } + + Mem_Free( g_leafcounts ); + Mem_Free( g_leafstarts ); + Mem_Free( g_portals ); + Mem_Free( g_leafs ); +} + +/* +============ +PrintVisSettings + +show compiler settings like ZHLT +============ +*/ +static void PrintVisSettings( void ) +{ + Msg( "\nCurrent p2vis settings\n" ); + Msg( "Name | Setting | Default\n" ); + Msg( "---------------------|-----------|-------------------------\n" ); + Msg( "developer [ %7d ] [ %7d ]\n", GetDeveloperLevel(), DEFAULT_DEVELOPER ); + Msg( "fast vis [ %7s ] [ %7s ]\n", g_fastvis ? "on" : "off", DEFAULT_FASTVIS ? "on" : "off" ); + Msg( "no sort portals [ %7s ] [ %7s ]\n", g_nosort ? "on" : "off", DEFAULT_NOSORT ? "on" : "off" ); + Msg( "maxdistance [ %7d ] [ %7d ]\n", (int)g_farplane, (int)DEFAULT_FARPLANE ); + Msg( "\n" ); +} + +/* +============ +PrintVisUsage + +show compiler usage like ZHLT +============ +*/ +static void PrintVisUsage( void ) +{ + Msg( "\n-= p2vis Options =-\n\n" ); + Msg( " -dev # : compile with developer message (1 - 4). default is %d\n", DEFAULT_DEVELOPER ); + Msg( " -threads # : manually specify the number of threads to run\n" ); + Msg( " -fast : only do first quick pass on vis calculations\n" ); + Msg( " -nosort : don't sort portals (disable optimization)\n" ); + Msg( " -maxdistance : limit visible distance (e.g. for fogged levels)\n" ); + Msg( " bspfile : The bspfile to compile\n\n" ); + + exit( 1 ); +} + +/* +=========== +main +=========== +*/ +int main( int argc, char **argv ) +{ + char portalfile[1024]; + char source[1024]; + int i; + double start, end; + char str[64]; + + atexit( Sys_CloseLog ); + source[0] = '\0'; + + for( i = 1; i < argc; i++ ) + { + if( !Q_strcmp( argv[i], "-dev" )) + { + SetDeveloperLevel( atoi( argv[i+1] )); + i++; + } + else if( !Q_strcmp( argv[i], "-threads" )) + { + g_numthreads = atoi( argv[i+1] ); + i++; + } + else if( !Q_strcmp( argv[i], "-fast" )) + { + g_fastvis = true; + } + else if( !Q_strcmp( argv[i], "-full" )) + { + // compatibility issues, does nothing + } + else if( !Q_strcmp( argv[i], "-nosort" )) + { + g_nosort = true; + } + else if( !Q_strcmp( argv[i], "-maxdistance" )) + { + g_farplane = atof( argv[i+1] ); + g_farplane = bound( 64.0, g_farplane, 65536.0 * 1.73 ); + i++; + } + else if( argv[i][0] == '-' ) + { + MsgDev( D_ERROR, "\nUnknown option \"%s\"\n", argv[i] ); + break; + } + else if( !source[0] ) + { + Q_strncpy( source, COM_ExpandArg( argv[i] ), sizeof( source )); + COM_StripExtension( source ); + } + else + { + MsgDev( D_ERROR, "\nUnknown option \"%s\"\n", argv[i] ); + break; + } + } + + if( i != argc || !source[0] ) + { + if( !source[0] ) + Msg( "no mapfile specified\n" ); + PrintVisUsage(); + } + + start = I_FloatTime (); + + Sys_InitLogAppend( va( "%s.log", source )); + + Msg( "\n%s %s (%s)\n", TOOLNAME, VERSIONSTRING, __DATE__ ); + + PrintVisSettings(); + + ThreadSetDefault (); + + Q_strncpy( portalfile, source, sizeof( portalfile )); + COM_DefaultExtension( portalfile, ".prt" ); + COM_DefaultExtension( source, ".bsp" ); + + LoadBSPFile( source ); + LoadPortals( portalfile ); + + g_uncompressed = (byte *)Mem_Alloc( g_bitbytes * g_portalleafs ); + + CalcVis (); + + MsgDev( D_REPORT, "c_chains: %i\n", c_chains ); + g_visdatasize = vismap_p - g_dvisdata; + + if( originalvismapsize < g_visdatasize ) + MsgDev( D_INFO, "visdatasize: ^1%s expanded from %s^7\n", Q_memprint( g_visdatasize ), Q_memprint( originalvismapsize )); + else MsgDev( D_INFO, "visdatasize: ^2%s compressed from %s^7\n", Q_memprint( g_visdatasize ), Q_memprint( originalvismapsize )); + + CalcAmbientSounds (); + + WriteBSPFile( source ); + + if( !g_fastvis ) + unlink( portalfile ); + Mem_Free( g_uncompressed ); + FreePortals(); + + SetDeveloperLevel( D_REPORT ); + Mem_Check(); + + end = I_FloatTime (); + Q_timestring((int)( end - start ), str ); + Msg( "%s elapsed\n", str ); + + return 0; +} \ No newline at end of file diff --git a/utils/p2vis/qvis.h b/utils/p2vis/qvis.h new file mode 100644 index 0000000..e1269ef --- /dev/null +++ b/utils/p2vis/qvis.h @@ -0,0 +1,130 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// vis.h + +#include "cmdlib.h" +#include "mathlib.h" +#include "bspfile.h" +#include "stringlib.h" +#include "filesystem.h" + +#include + +#define DEFAULT_FASTVIS false +#define DEFAULT_TESTLEVEL 2 +#define DEFAULT_NOSORT false +#define DEFAULT_FARPLANE 0 + +#define MAX_PORTALS MAX_MAP_PORTALS +#define VIS_EPSILON ON_EPSILON + +typedef struct +{ + vec3_t normal; + float dist; +} plane_t; + +#define MAX_POINTS_ON_WINDING 64 +#define MAX_POINTS_ON_STACK_WINDING 24 +#define MAX_PORTALS_ON_LEAF 256 +#define MAX_SEPERATORS MAX_POINTS_ON_WINDING + +typedef struct +{ + int numpoints; + vec3_t p[MAX_POINTS_ON_STACK_WINDING]; // variable sized +} winding_t; + +typedef enum +{ + stat_none = 0, + stat_working, + stat_done +} vstatus_t; + +typedef struct +{ + plane_t plane; // normal pointing into neighbor + vec3_t origin; + vec_t radius; + int leaf; // neighbor + winding_t *winding; + vstatus_t status; + byte *visbits; + byte *mightsee; + int nummightsee; + int numcansee; +} portal_t; + +typedef struct leaf_s +{ + int numportals; + portal_t *portals[MAX_PORTALS_ON_LEAF]; +} leaf_t; + +typedef struct pstack_s +{ + byte mightsee[MAX_MAP_LEAFS/8]; // bit string + struct pstack_s *head; + struct pstack_s *next; + leaf_t *leaf; + portal_t *portal; // portal exiting + winding_t *source; + winding_t *pass; + + winding_t windings[3]; // source, pass, temp in any order + int freewindings[3]; + + plane_t portalplane; + plane_t seperators[2][MAX_SEPERATORS]; + int numseperators[2]; +} pstack_t; + +typedef struct +{ + byte *leafvis; // bit string + portal_t *base; + pstack_t pstack_head; +} threaddata_t; + +extern int g_numportals; +extern int g_portalleafs; + +extern portal_t *g_sorted_portals[MAX_MAP_PORTALS*2]; +extern portal_t *g_portals; +extern leaf_t *g_leafs; + +extern int c_portaltest, c_portalpass, c_portalcheck; +extern int c_portalskip, c_leafskip; +extern int c_vistest, c_mighttest; +extern int c_mightseeupdate; +extern int c_chains; + +extern byte *vismap, *vismap_p, *vismap_end; // past visfile + +extern byte *g_uncompressed; +extern int g_bitbytes; +extern int g_bitlongs; +extern vec_t g_farplane; + +void LeafFlow( int leafnum ); +void BasePortalVis( int threadnum ); +void PortalFlow( int portalnum, int threadnum = -1 ); +void PortalFlow( portal_t *p ); +void CalcAmbientSounds( void ); + +// +// winding.c +// +winding_t *AllocWinding( int points ); +void FreeWinding( winding_t *w ); +winding_t *ChopWindingEpsilon( winding_t *in, pstack_t *stack, plane_t *split, vec_t epsilon ); +void WindingPlane( winding_t *w, vec3_t normal, vec_t *dist ); diff --git a/utils/p2vis/soundpvs.cpp b/utils/p2vis/soundpvs.cpp new file mode 100644 index 0000000..ac0f191 --- /dev/null +++ b/utils/p2vis/soundpvs.cpp @@ -0,0 +1,161 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +#include "qvis.h" +#include "threads.h" + +/* +Some textures (sky, water, slime, lava) are considered ambient sound emiters. +Find an aproximate distance to the nearest emiter of each class for each leaf. +*/ + +/* +==================== +SurfaceBBox +==================== +*/ +void SurfaceBBox( dface_t *s, vec3_t mins, vec3_t maxs ) +{ + int e, vi; + vec3_t v; + + ClearBounds( mins, maxs ); + + for( int i = 0; i < s->numedges; i++ ) + { + e = g_dsurfedges[s->firstedge + i]; + + if( e >= 0 ) vi = g_dedges[e].v[0]; + else vi = g_dedges[-e].v[1]; + + VectorCopy( g_dvertexes[vi].point, v ); + AddPointToBounds( v, mins, maxs ); + } +} + +/* +==================== +CalcAmbientSounds + +==================== +*/ +static void CalcAmbientSounds( int leafnum, int threadnum = -1 ) +{ + int j, k, l; + int ambient_type; + dleaf_t *leaf, *hit; + byte vis[(MAX_MAP_LEAFS + 7) / 8]; + vec_t dists[NUM_AMBIENTS]; + vec_t d, maxd, vol; + vec3_t mins, maxs; + dface_t *surf; + + leaf = &g_dleafs[leafnum+1]; + + // clear ambients + for( j = 0; j < NUM_AMBIENTS; j++ ) + dists[j] = BOGUS_RANGE; + + DecompressVis( &g_dvisdata[leaf->visofs], vis ); + + for( j = 0; j < g_numvisleafs; j++ ) + { + if( !CHECKVISBIT( vis, j )) + continue; + + // check this leaf for sound textures + hit = &g_dleafs[j+1]; + + for( k = 0; k < hit->nummarksurfaces; k++ ) + { + surf = &g_dfaces[g_dmarksurfaces[hit->firstmarksurface + k]]; + const char *texname = GetTextureByTexinfo( surf->texinfo ); + + if( !Q_strnicmp( texname, "sky", 3 )) + { + ambient_type = AMBIENT_SKY; + } + else if( texname[0] == '!' ) + { + if( !Q_strnicmp( texname, "!water", 6 )) + { + ambient_type = AMBIENT_WATER; + } + else if( !Q_strnicmp( texname, "!04water", 8 )) + { + ambient_type = AMBIENT_WATER; + } + else if( !Q_strnicmp( texname, "!slime", 6 )) + { + ambient_type = AMBIENT_SLIME; + } + else if( !Q_strnicmp( texname, "!lava", 6 )) + { + ambient_type = AMBIENT_LAVA; + } + else + { + continue; + } + } + else + { + continue; + } + + // find distance from source leaf to polygon + SurfaceBBox( surf, mins, maxs ); + maxd = 0; + + for( l = 0; l < 3; l++ ) + { + if( mins[l] > leaf->maxs[l] ) + d = mins[l] - leaf->maxs[l]; + else if( maxs[l] < leaf->mins[l] ) + d = leaf->mins[l] - mins[l]; + else d = 0; + + maxd = Q_max( d, maxd ); + } + + dists[ambient_type] = Q_min( maxd, dists[ambient_type] ); + } + } + + for( j = 0; j < NUM_AMBIENTS; j++ ) + { + // remap to quake range + dists[j] = RemapVal( dists[j], 0.0, 65536.0, 0.0f, 512.0f ); + + if( dists[j] < 100 ) + { + vol = 1.0; + } + else + { + vol = 1.0 - dists[j] * 0.002; + vol = bound( 0.0, vol, 1.0 ); + } + leaf->ambient_level[j] = vol * 255; + } +} + +/* +==================== +CalcAmbientSounds + +==================== +*/ +void CalcAmbientSounds( void ) +{ + MsgDev( D_REPORT, "---- Calc Ambient Sounds ----\n" ); + + RunThreadsOnIndividual( g_numvisleafs, true, CalcAmbientSounds ); +} \ No newline at end of file diff --git a/utils/p2vis/winding.cpp b/utils/p2vis/winding.cpp new file mode 100644 index 0000000..f355353 --- /dev/null +++ b/utils/p2vis/winding.cpp @@ -0,0 +1,222 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// winding.c + +#include "qvis.h" + +void pw( winding_t *w ) +{ + for( int i = 0; w && i < w->numpoints; i++ ) + Msg( "(%5.1f, %5.1f, %5.1f)\n", w->p[i][0], w->p[i][1], w->p[i][2] ); +} + +/* +============= +AllocWinding +============= +*/ +winding_t *AllocWinding( int numpoints ) +{ + if( numpoints > MAX_POINTS_ON_WINDING ) + COM_FatalError( "AllocWinding: MAX_POINTS_ON_WINDING limit exceeded\n" ); + + return (winding_t *)Mem_Alloc( (int)((winding_t *)0)->p[numpoints], C_WINDING ); +} + +/* +============= +FreeWinding +============= +*/ +void FreeWinding( winding_t *w ) +{ + // simple sentinel by Carmack + if( *(unsigned *)w == 0xDEADC0DE ) + COM_FatalError( "FreeWinding: freed a freed winding\n" ); + *(unsigned *)w = 0xDEADC0DE; + + Mem_Free( w, C_WINDING ); +} + +/* +============= +AllocStackWinding +============= +*/ +static winding_t *AllocStackWinding( pstack_t *stack ) +{ + for( int i = 0; i < 3; i++ ) + { + if( stack->freewindings[i] ) + { + stack->freewindings[i] = 0; + return &stack->windings[i]; + } + } + + COM_FatalError( "AllocStackWinding: failed\n" ); + + return NULL; +} + +/* +============= +FreeStackWinding +============= +*/ +static void FreeStackWinding( winding_t *w, pstack_t *stack ) +{ + int i = w - stack->windings; + + if( i < 0 || i > 2 ) + return; // not from local + + if( stack->freewindings[i] ) + COM_FatalError( "FreeStackWinding: allready free\n" ); + stack->freewindings[i] = 1; +} + +/* +============ +WindingPlane +============ +*/ +void WindingPlane( winding_t *w, vec3_t normal, vec_t *dist ) +{ + vec3_t v1, v2; + + if( w && w->numpoints >= 3 ) + { + VectorSubtract( w->p[2], w->p[1], v1 ); + VectorSubtract( w->p[0], w->p[1], v2 ); + CrossProduct( v2, v1, normal ); + VectorNormalize( normal ); + *dist = DotProduct( w->p[0], normal ); + } + else + { + VectorClear( normal ); + *dist = 0.0; + } +} + +/* +============== +ChopWinding + +============== +*/ +winding_t *ChopWindingEpsilon( winding_t *in, pstack_t *stack, plane_t *split, vec_t epsilon ) +{ + vec_t dists[MAX_POINTS_ON_WINDING+4]; + int sides[MAX_POINTS_ON_WINDING+4]; + int counts[3]; + vec_t dot; + int i, j; + vec_t *p1, *p2; + vec3_t mid; + winding_t *neww; + + counts[0] = counts[1] = counts[2] = 0; + + if( in->numpoints > MAX_POINTS_ON_WINDING ) + COM_FatalError( "ChopWinding: MAX_POINTS_ON_WINDING limit exceeded\n" ); + + // determine sides for each point + for( i = 0; i < in->numpoints; i++ ) + { + dot = DotProduct( in->p[i], split->normal ); + dot -= split->dist; + dists[i] = dot; + + if( dot > epsilon ) + sides[i] = SIDE_FRONT; + else if( dot < -epsilon ) + sides[i] = SIDE_BACK; + else sides[i] = SIDE_ON; + + counts[sides[i]]++; + } + + if( counts[SIDE_ON] == in->numpoints ) + return in; + + if( !counts[1] ) + return in; // completely on front side + + if( !counts[0] ) + { + FreeStackWinding( in, stack ); + return NULL; + } + + sides[i] = sides[0]; + dists[i] = dists[0]; + + neww = AllocStackWinding( stack ); + neww->numpoints = 0; + + for( i = 0; i < in->numpoints; i++ ) + { + p1 = in->p[i]; + + if( neww->numpoints == MAX_POINTS_ON_STACK_WINDING ) + { + FreeStackWinding( neww, stack ); + return in; // can't chop -- fall back to original + } + + if( sides[i] == SIDE_ON ) + { + VectorCopy( p1, neww->p[neww->numpoints] ); + neww->numpoints++; + continue; + } + + if( sides[i] == SIDE_FRONT ) + { + VectorCopy( p1, neww->p[neww->numpoints] ); + neww->numpoints++; + } + + if(( sides[i+1] == SIDE_ON ) | ( sides[i+1] == sides[i] )) // | instead of || for branch optimization + continue; + + if( neww->numpoints == MAX_POINTS_ON_STACK_WINDING ) + { + FreeStackWinding( neww, stack ); + return in; // can't chop -- fall back to original + } + + // generate a split point + p2 = in->p[(i + 1) % in->numpoints]; + + dot = dists[i] / (dists[i] - dists[i+1]); + + for( j = 0; j < 3; j++ ) + { + // avoid round off error when possible + if( split->normal[j] == 1 ) + mid[j] = split->dist; + else if( split->normal[j] == -1 ) + mid[j] = -split->dist; + else mid[j] = p1[j] + dot * ( p2[j] - p1[j] ); + } + + VectorCopy( mid, neww->p[neww->numpoints] ); + neww->numpoints++; + } + + // free the original winding + FreeStackWinding( in, stack ); + + return neww; +} \ No newline at end of file diff --git a/utils/reqtest/gl_export.h b/utils/reqtest/gl_export.h new file mode 100644 index 0000000..64150ab --- /dev/null +++ b/utils/reqtest/gl_export.h @@ -0,0 +1,1357 @@ +/* +gl_export.h - OpenGL definition +Copyright (C) 2011 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef GL_EXPORT_H +#define GL_EXPORT_H + +#include + +#ifndef APIENTRY +#define APIENTRY +#endif + +#ifndef EXTERN +#define EXTERN extern +#endif + +typedef unsigned int GLenum; +typedef unsigned char GLboolean; +typedef unsigned int GLbitfield; +typedef void GLvoid; +typedef signed char GLbyte; +typedef short GLshort; +typedef int GLint; +typedef unsigned char GLubyte; +typedef unsigned short GLushort; +typedef unsigned int GLuint; +typedef int GLsizei; +typedef float GLfloat; +typedef float GLclampf; +typedef double GLdouble; +typedef double GLclampd; +typedef int GLintptrARB; +typedef int GLsizeiptrARB; +typedef char GLcharARB; +typedef unsigned int GLhandleARB; + +#define GL_MODELVIEW 0x1700 +#define GL_PROJECTION 0x1701 +#define GL_TEXTURE 0x1702 +#define GL_MATRIX_MODE 0x0BA0 +#define GL_MODELVIEW_MATRIX 0x0BA6 +#define GL_PROJECTION_MATRIX 0x0BA7 +#define GL_TEXTURE_MATRIX 0x0BA8 + +#define GL_DONT_CARE 0x1100 +#define GL_FASTEST 0x1101 +#define GL_NICEST 0x1102 + +#define GL_ACCUM 0x0100 +#define GL_LOAD 0x0101 +#define GL_RETURN 0x0102 +#define GL_MULT 0x0103 +#define GL_ADD 0x0104 + +#define GL_DEPTH_TEST 0x0B71 + +#define GL_CULL_FACE 0x0B44 +#define GL_CW 0x0900 +#define GL_CCW 0x0901 +#define GL_BLEND 0x0BE2 +#define GL_ALPHA_TEST 0x0BC0 +#define GL_ALPHA_TEST_FUNC 0x0BC1 +#define GL_ALPHA_TEST_REF 0x0BC2 + +// shading model +#define GL_FLAT 0x1D00 +#define GL_SMOOTH 0x1D01 + +#define GL_ZERO 0 +#define GL_ONE 1 +#define GL_RED 0x1903 +#define GL_GREEN 0x1904 +#define GL_BLUE 0x1905 +#define GL_ALPHA 0x1906 +#define GL_SRC_COLOR 0x0300 +#define GL_ONE_MINUS_SRC_COLOR 0x0301 +#define GL_DST_COLOR 0x0306 +#define GL_ONE_MINUS_DST_COLOR 0x0307 +#define GL_SRC_ALPHA 0x0302 +#define GL_ONE_MINUS_SRC_ALPHA 0x0303 +#define GL_DST_ALPHA 0x0304 +#define GL_ONE_MINUS_DST_ALPHA 0x0305 +#define GL_SRC_ALPHA_SATURATE 0x0308 +#define GL_CONSTANT_COLOR 0x8001 +#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002 +#define GL_CONSTANT_ALPHA 0x8003 +#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004 + +#define GL_TEXTURE_WIDTH 0x1000 +#define GL_TEXTURE_HEIGHT 0x1001 +#define GL_TEXTURE_INTERNAL_FORMAT 0x1003 +#define GL_TEXTURE_BORDER_COLOR 0x1004 +#define GL_TEXTURE_BORDER 0x1005 +#define GL_RED_BITS 0x0D52 +#define GL_GREEN_BITS 0x0D53 +#define GL_BLUE_BITS 0x0D54 +#define GL_ALPHA_BITS 0x0D55 +#define GL_DEPTH_BITS 0x0D56 +#define GL_STENCIL_BITS 0x0D57 +#define GL_ACCUM_RED_BITS 0x0D58 +#define GL_ACCUM_GREEN_BITS 0x0D59 +#define GL_ACCUM_BLUE_BITS 0x0D5A +#define GL_ACCUM_ALPHA_BITS 0x0D5B +#define GL_TEXTURE_ENV 0x2300 +#define GL_TEXTURE_ENV_MODE 0x2200 +#define GL_TEXTURE_ENV_COLOR 0x2201 +#define GL_TEXTURE_1D 0x0DE0 +#define GL_TEXTURE_2D 0x0DE1 +#define GL_TEXTURE_WRAP_S 0x2802 +#define GL_TEXTURE_WRAP_T 0x2803 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_TEXTURE_BORDER_COLOR 0x1004 +#define GL_TEXTURE_MAG_FILTER 0x2800 +#define GL_TEXTURE_MIN_FILTER 0x2801 +#define GL_PACK_ALIGNMENT 0x0D05 +#define GL_UNPACK_ALIGNMENT 0x0CF5 +#define GL_TEXTURE_BINDING_1D 0x8068 +#define GL_TEXTURE_BINDING_2D 0x8069 +#define GL_CLAMP_TO_EDGE 0x812F +#define GL_NEAREST 0x2600 +#define GL_LINEAR 0x2601 +#define GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 + +#define GL_LINE 0x1B01 +#define GL_FILL 0x1B02 + +#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF + +#define GL_MAX_TEXTURE_LOD_BIAS_EXT 0x84FD +#define GL_TEXTURE_FILTER_CONTROL_EXT 0x8500 +#define GL_TEXTURE_LOD_BIAS_EXT 0x8501 + +#define GL_CLAMP_TO_BORDER_ARB 0x812D + +#define GL_ADD 0x0104 +#define GL_DECAL 0x2101 +#define GL_MODULATE 0x2100 + +#define GL_REPEAT 0x2901 +#define GL_CLAMP 0x2900 + +#define GL_POINTS 0x0000 +#define GL_LINES 0x0001 +#define GL_LINE_LOOP 0x0002 +#define GL_LINE_STRIP 0x0003 +#define GL_TRIANGLES 0x0004 +#define GL_TRIANGLE_STRIP 0x0005 +#define GL_TRIANGLE_FAN 0x0006 +#define GL_QUADS 0x0007 +#define GL_QUAD_STRIP 0x0008 +#define GL_POLYGON 0x0009 + +#define GL_FALSE 0 +#define GL_TRUE 1 + +#define GL_BYTE 0x1400 +#define GL_UNSIGNED_BYTE 0x1401 +#define GL_SHORT 0x1402 +#define GL_UNSIGNED_SHORT 0x1403 +#define GL_INT 0x1404 +#define GL_UNSIGNED_INT 0x1405 +#define GL_FLOAT 0x1406 +#define GL_DOUBLE 0x140A +#define GL_2_BYTES 0x1407 +#define GL_3_BYTES 0x1408 +#define GL_4_BYTES 0x1409 + +#define GL_VERTEX_ARRAY 0x8074 +#define GL_NORMAL_ARRAY 0x8075 +#define GL_COLOR_ARRAY 0x8076 +#define GL_INDEX_ARRAY 0x8077 +#define GL_TEXTURE_COORD_ARRAY 0x8078 +#define GL_EDGE_FLAG_ARRAY 0x8079 + +#define GL_NONE 0 +#define GL_FRONT_LEFT 0x0400 +#define GL_FRONT_RIGHT 0x0401 +#define GL_BACK_LEFT 0x0402 +#define GL_BACK_RIGHT 0x0403 +#define GL_FRONT 0x0404 +#define GL_BACK 0x0405 +#define GL_LEFT 0x0406 +#define GL_RIGHT 0x0407 +#define GL_FRONT_AND_BACK 0x0408 +#define GL_AUX0 0x0409 +#define GL_AUX1 0x040A +#define GL_AUX2 0x040B +#define GL_AUX3 0x040C + +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 +#define GL_EXTENSIONS 0x1F03 + +#define GL_NO_ERROR 0 +#define GL_INVALID_VALUE 0x0501 +#define GL_INVALID_ENUM 0x0500 +#define GL_INVALID_OPERATION 0x0502 +#define GL_STACK_OVERFLOW 0x0503 +#define GL_STACK_UNDERFLOW 0x0504 +#define GL_OUT_OF_MEMORY 0x0505 + +#define GL_DITHER 0x0BD0 +#define GL_RGB 0x1907 +#define GL_RGBA 0x1908 +#define GL_BGR 0x80E0 +#define GL_BGRA 0x80E1 +#define GL_ALPHA4 0x803B +#define GL_ALPHA8 0x803C +#define GL_ALPHA12 0x803D +#define GL_ALPHA16 0x803E +#define GL_LUMINANCE4 0x803F +#define GL_LUMINANCE8 0x8040 +#define GL_LUMINANCE12 0x8041 +#define GL_LUMINANCE16 0x8042 +#define GL_LUMINANCE4_ALPHA4 0x8043 +#define GL_LUMINANCE6_ALPHA2 0x8044 +#define GL_LUMINANCE8_ALPHA8 0x8045 +#define GL_LUMINANCE12_ALPHA4 0x8046 +#define GL_LUMINANCE12_ALPHA12 0x8047 +#define GL_LUMINANCE16_ALPHA16 0x8048 +#define GL_LUMINANCE 0x1909 +#define GL_LUMINANCE_ALPHA 0x190A +#define GL_DEPTH_COMPONENT 0x1902 +#define GL_INTENSITY 0x8049 +#define GL_INTENSITY4 0x804A +#define GL_INTENSITY8 0x804B +#define GL_INTENSITY12 0x804C +#define GL_INTENSITY16 0x804D +#define GL_R3_G3_B2 0x2A10 +#define GL_RGB4 0x804F +#define GL_RGB5 0x8050 +#define GL_RGB8 0x8051 +#define GL_RGB10 0x8052 +#define GL_RGB12 0x8053 +#define GL_RGB16 0x8054 +#define GL_RGBA2 0x8055 +#define GL_RGBA4 0x8056 +#define GL_RGB5_A1 0x8057 +#define GL_RGBA8 0x8058 +#define GL_RGB10_A2 0x8059 +#define GL_RGBA12 0x805A +#define GL_RGBA16 0x805B +#define GL_TEXTURE_RED_SIZE 0x805C +#define GL_TEXTURE_GREEN_SIZE 0x805D +#define GL_TEXTURE_BLUE_SIZE 0x805E +#define GL_TEXTURE_ALPHA_SIZE 0x805F +#define GL_TEXTURE_LUMINANCE_SIZE 0x8060 +#define GL_TEXTURE_INTENSITY_SIZE 0x8061 +#define GL_PROXY_TEXTURE_1D 0x8063 +#define GL_PROXY_TEXTURE_2D 0x8064 +#define GL_MAX_TEXTURE_SIZE 0x0D33 + +// texture coord name +#define GL_S 0x2000 +#define GL_T 0x2001 +#define GL_R 0x2002 +#define GL_Q 0x2003 + +// texture gen mode +#define GL_EYE_LINEAR 0x2400 +#define GL_OBJECT_LINEAR 0x2401 +#define GL_SPHERE_MAP 0x2402 + +// texture gen parameter +#define GL_TEXTURE_GEN_MODE 0x2500 +#define GL_OBJECT_PLANE 0x2501 +#define GL_EYE_PLANE 0x2502 +#define GL_FOG_HINT 0x0C54 +#define GL_TEXTURE_GEN_S 0x0C60 +#define GL_TEXTURE_GEN_T 0x0C61 +#define GL_TEXTURE_GEN_R 0x0C62 +#define GL_TEXTURE_GEN_Q 0x0C63 + +#define GL_SCISSOR_BOX 0x0C10 +#define GL_SCISSOR_TEST 0x0C11 + +#define GL_NEVER 0x0200 +#define GL_LESS 0x0201 +#define GL_EQUAL 0x0202 +#define GL_LEQUAL 0x0203 +#define GL_GREATER 0x0204 +#define GL_NOTEQUAL 0x0205 +#define GL_GEQUAL 0x0206 +#define GL_ALWAYS 0x0207 +#define GL_DEPTH_TEST 0x0B71 + +#define GL_RED_SCALE 0x0D14 +#define GL_GREEN_SCALE 0x0D18 +#define GL_BLUE_SCALE 0x0D1A +#define GL_ALPHA_SCALE 0x0D1C + +/* AttribMask */ +#define GL_CURRENT_BIT 0x00000001 +#define GL_POINT_BIT 0x00000002 +#define GL_LINE_BIT 0x00000004 +#define GL_POLYGON_BIT 0x00000008 +#define GL_POLYGON_STIPPLE_BIT 0x00000010 +#define GL_PIXEL_MODE_BIT 0x00000020 +#define GL_LIGHTING_BIT 0x00000040 +#define GL_FOG_BIT 0x00000080 +#define GL_DEPTH_BUFFER_BIT 0x00000100 +#define GL_ACCUM_BUFFER_BIT 0x00000200 +#define GL_STENCIL_BUFFER_BIT 0x00000400 +#define GL_VIEWPORT_BIT 0x00000800 +#define GL_TRANSFORM_BIT 0x00001000 +#define GL_ENABLE_BIT 0x00002000 +#define GL_COLOR_BUFFER_BIT 0x00004000 +#define GL_HINT_BIT 0x00008000 +#define GL_EVAL_BIT 0x00010000 +#define GL_LIST_BIT 0x00020000 +#define GL_TEXTURE_BIT 0x00040000 +#define GL_SCISSOR_BIT 0x00080000 +#define GL_ALL_ATTRIB_BITS 0x000fffff + +#define GL_STENCIL_TEST 0x0B90 +#define GL_KEEP 0x1E00 +#define GL_REPLACE 0x1E01 +#define GL_INCR 0x1E02 +#define GL_DECR 0x1E03 + +// fog stuff +#define GL_FOG 0x0B60 +#define GL_FOG_INDEX 0x0B61 +#define GL_FOG_DENSITY 0x0B62 +#define GL_FOG_START 0x0B63 +#define GL_FOG_END 0x0B64 +#define GL_FOG_MODE 0x0B65 +#define GL_FOG_COLOR 0x0B66 +#define GL_EXP 0x0800 +#define GL_EXP2 0x0801 + +#define GL_POLYGON_OFFSET_FACTOR 0x8038 +#define GL_POLYGON_OFFSET_UNITS 0x2A00 +#define GL_POLYGON_OFFSET_POINT 0x2A01 +#define GL_POLYGON_OFFSET_LINE 0x2A02 +#define GL_POLYGON_OFFSET_FILL 0x8037 + +#define GL_POINT_SMOOTH 0x0B10 +#define GL_LINE_SMOOTH 0x0B20 +#define GL_POLYGON_SMOOTH 0x0B41 +#define GL_POLYGON_STIPPLE 0x0B42 +#define GL_CLIP_PLANE0 0x3000 +#define GL_CLIP_PLANE1 0x3001 +#define GL_CLIP_PLANE2 0x3002 +#define GL_CLIP_PLANE3 0x3003 +#define GL_CLIP_PLANE4 0x3004 +#define GL_CLIP_PLANE5 0x3005 +#define GL_POINT_SIZE_MIN_EXT 0x8126 +#define GL_POINT_SIZE_MAX_EXT 0x8127 +#define GL_POINT_FADE_THRESHOLD_SIZE_EXT 0x8128 +#define GL_DISTANCE_ATTENUATION_EXT 0x8129 +#define GL_ACTIVE_TEXTURE_ARB 0x84E0 +#define GL_CLIENT_ACTIVE_TEXTURE_ARB 0x84E1 +#define GL_MAX_TEXTURE_UNITS_ARB 0x84E2 +#define GL_TEXTURE0_ARB 0x84C0 +#define GL_TEXTURE1_ARB 0x84C1 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE3_ARB 0x84C3 +#define GL_TEXTURE2_ARB 0x84C2 +#define GL_TEXTURE0_SGIS 0x835E +#define GL_TEXTURE1_SGIS 0x835F +#define GL_GENERATE_MIPMAP_SGIS 0x8191 +#define GL_GENERATE_MIPMAP_HINT_SGIS 0x8192 +#define GL_TEXTURE_RECTANGLE_NV 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_NV 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_NV 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_NV 0x84F8 +#define GL_TEXTURE_RECTANGLE_EXT 0x84F5 +#define GL_TEXTURE_BINDING_RECTANGLE_EXT 0x84F6 +#define GL_PROXY_TEXTURE_RECTANGLE_EXT 0x84F7 +#define GL_MAX_RECTANGLE_TEXTURE_SIZE_EXT 0x84F8 +#define GL_MAX_TEXTURE_UNITS 0x84E2 +#define GL_MAX_TEXTURE_UNITS_ARB 0x84E2 + +#define GL_REGISTER_COMBINERS_NV 0x8522 +#define GL_VARIABLE_A_NV 0x8523 +#define GL_VARIABLE_B_NV 0x8524 +#define GL_VARIABLE_C_NV 0x8525 +#define GL_VARIABLE_D_NV 0x8526 +#define GL_VARIABLE_E_NV 0x8527 +#define GL_VARIABLE_F_NV 0x8528 +#define GL_VARIABLE_G_NV 0x8529 +#define GL_CONSTANT_COLOR0_NV 0x852A +#define GL_CONSTANT_COLOR1_NV 0x852B +#define GL_PRIMARY_COLOR_NV 0x852C +#define GL_SECONDARY_COLOR_NV 0x852D +#define GL_SPARE0_NV 0x852E +#define GL_SPARE1_NV 0x852F +#define GL_DISCARD_NV 0x8530 +#define GL_E_TIMES_F_NV 0x8531 +#define GL_SPARE0_PLUS_SECONDARY_COLOR_NV 0x8532 +#define GL_UNSIGNED_IDENTITY_NV 0x8536 +#define GL_UNSIGNED_INVERT_NV 0x8537 +#define GL_EXPAND_NORMAL_NV 0x8538 +#define GL_EXPAND_NEGATE_NV 0x8539 +#define GL_HALF_BIAS_NORMAL_NV 0x853A +#define GL_HALF_BIAS_NEGATE_NV 0x853B +#define GL_SIGNED_IDENTITY_NV 0x853C +#define GL_SIGNED_NEGATE_NV 0x853D +#define GL_SCALE_BY_TWO_NV 0x853E +#define GL_SCALE_BY_FOUR_NV 0x853F +#define GL_SCALE_BY_ONE_HALF_NV 0x8540 +#define GL_BIAS_BY_NEGATIVE_ONE_HALF_NV 0x8541 +#define GL_COMBINER_INPUT_NV 0x8542 +#define GL_COMBINER_MAPPING_NV 0x8543 +#define GL_COMBINER_COMPONENT_USAGE_NV 0x8544 +#define GL_COMBINER_AB_DOT_PRODUCT_NV 0x8545 +#define GL_COMBINER_CD_DOT_PRODUCT_NV 0x8546 +#define GL_COMBINER_MUX_SUM_NV 0x8547 +#define GL_COMBINER_SCALE_NV 0x8548 +#define GL_COMBINER_BIAS_NV 0x8549 +#define GL_COMBINER_AB_OUTPUT_NV 0x854A +#define GL_COMBINER_CD_OUTPUT_NV 0x854B +#define GL_COMBINER_SUM_OUTPUT_NV 0x854C +#define GL_MAX_GENERAL_COMBINERS_NV 0x854D +#define GL_NUM_GENERAL_COMBINERS_NV 0x854E +#define GL_COLOR_SUM_CLAMP_NV 0x854F +#define GL_COMBINER0_NV 0x8550 +#define GL_COMBINER1_NV 0x8551 +#define GL_COMBINER2_NV 0x8552 +#define GL_COMBINER3_NV 0x8553 +#define GL_COMBINER4_NV 0x8554 +#define GL_COMBINER5_NV 0x8555 +#define GL_COMBINER6_NV 0x8556 +#define GL_COMBINER7_NV 0x8557 +#define GL_DSDT8_NV 0x8709 +#define GL_DSDT_NV 0x86F5 + +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#define GL_COMPRESSED_ALPHA_ARB 0x84E9 +#define GL_COMPRESSED_LUMINANCE_ARB 0x84EA +#define GL_COMPRESSED_LUMINANCE_ALPHA_ARB 0x84EB +#define GL_COMPRESSED_INTENSITY_ARB 0x84EC +#define GL_COMPRESSED_RGB_ARB 0x84ED +#define GL_COMPRESSED_RGBA_ARB 0x84EE +#define GL_TEXTURE_COMPRESSION_HINT_ARB 0x84EF +#define GL_TEXTURE_COMPRESSED_IMAGE_SIZE_ARB 0x86A0 +#define GL_TEXTURE_COMPRESSED_ARB 0x86A1 +#define GL_NUM_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A2 +#define GL_COMPRESSED_TEXTURE_FORMATS_ARB 0x86A3 +#define GL_UNSIGNED_BYTE_2_3_3_REV 0x8362 +#define GL_UNSIGNED_SHORT_5_6_5 0x8363 +#define GL_UNSIGNED_SHORT_5_6_5_REV 0x8364 +#define GL_UNSIGNED_SHORT_4_4_4_4_REV 0x8365 +#define GL_UNSIGNED_SHORT_1_5_5_5_REV 0x8366 +#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367 +#define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 +#define GL_TEXTURE_MAX_LEVEL 0x813D +#define GL_GENERATE_MIPMAP 0x8191 +#define GL_ADD_SIGNED 0x8574 + +#define GL_PROGRAM_OBJECT_ARB 0x8B40 +#define GL_OBJECT_TYPE_ARB 0x8B4E +#define GL_OBJECT_SUBTYPE_ARB 0x8B4F +#define GL_OBJECT_DELETE_STATUS_ARB 0x8B80 +#define GL_OBJECT_COMPILE_STATUS_ARB 0x8B81 +#define GL_OBJECT_LINK_STATUS_ARB 0x8B82 +#define GL_OBJECT_VALIDATE_STATUS_ARB 0x8B83 +#define GL_OBJECT_INFO_LOG_LENGTH_ARB 0x8B84 +#define GL_OBJECT_ATTACHED_OBJECTS_ARB 0x8B85 +#define GL_OBJECT_ACTIVE_UNIFORMS_ARB 0x8B86 +#define GL_OBJECT_ACTIVE_UNIFORM_MAX_LENGTH_ARB 0x8B87 +#define GL_OBJECT_SHADER_SOURCE_LENGTH_ARB 0x8B88 +#define GL_SHADER_OBJECT_ARB 0x8B48 +#define GL_FLOAT_VEC2_ARB 0x8B50 +#define GL_FLOAT_VEC3_ARB 0x8B51 +#define GL_FLOAT_VEC4_ARB 0x8B52 +#define GL_INT_VEC2_ARB 0x8B53 +#define GL_INT_VEC3_ARB 0x8B54 +#define GL_INT_VEC4_ARB 0x8B55 +#define GL_BOOL_ARB 0x8B56 +#define GL_BOOL_VEC2_ARB 0x8B57 +#define GL_BOOL_VEC3_ARB 0x8B58 +#define GL_BOOL_VEC4_ARB 0x8B59 +#define GL_FLOAT_MAT2_ARB 0x8B5A +#define GL_FLOAT_MAT3_ARB 0x8B5B +#define GL_FLOAT_MAT4_ARB 0x8B5C +#define GL_SAMPLER_1D_ARB 0x8B5D +#define GL_SAMPLER_2D_ARB 0x8B5E +#define GL_SAMPLER_3D_ARB 0x8B5F +#define GL_SAMPLER_CUBE_ARB 0x8B60 +#define GL_SAMPLER_1D_SHADOW_ARB 0x8B61 +#define GL_SAMPLER_2D_SHADOW_ARB 0x8B62 +#define GL_SAMPLER_2D_RECT_ARB 0x8B63 +#define GL_SAMPLER_2D_RECT_SHADOW_ARB 0x8B64 + +#define GL_PACK_SKIP_IMAGES 0x806B +#define GL_PACK_IMAGE_HEIGHT 0x806C +#define GL_UNPACK_SKIP_IMAGES 0x806D +#define GL_UNPACK_IMAGE_HEIGHT 0x806E +#define GL_TEXTURE_3D 0x806F +#define GL_PROXY_TEXTURE_3D 0x8070 +#define GL_TEXTURE_DEPTH 0x8071 +#define GL_TEXTURE_WRAP_R 0x8072 +#define GL_MAX_3D_TEXTURE_SIZE 0x8073 +#define GL_TEXTURE_BINDING_3D 0x806A + +#define GL_STENCIL_TEST_TWO_SIDE_EXT 0x8910 +#define GL_ACTIVE_STENCIL_FACE_EXT 0x8911 +#define GL_STENCIL_BACK_FUNC 0x8800 +#define GL_STENCIL_BACK_FAIL 0x8801 +#define GL_STENCIL_BACK_PASS_DEPTH_FAIL 0x8802 +#define GL_STENCIL_BACK_PASS_DEPTH_PASS 0x8803 + +#define GL_DEPTH_TEXTURE_MODE_ARB 0x884B +#define GL_TEXTURE_COMPARE_MODE_ARB 0x884C +#define GL_TEXTURE_COMPARE_FUNC_ARB 0x884D +#define GL_COMPARE_R_TO_TEXTURE_ARB 0x884E +#define GL_TEXTURE_COMPARE_FAIL_VALUE_ARB 0x80BF + +#define GL_TEXTURE_1D_ARRAY_EXT 0x8C18 +#define GL_PROXY_TEXTURE_1D_ARRAY_EXT 0x8C19 +#define GL_TEXTURE_2D_ARRAY_EXT 0x8C1A +#define GL_PROXY_TEXTURE_2D_ARRAY_EXT 0x8C1B +#define GL_TEXTURE_BINDING_1D_ARRAY_EXT 0x8C1C +#define GL_TEXTURE_BINDING_2D_ARRAY_EXT 0x8C1D +#define GL_MAX_ARRAY_TEXTURE_LAYERS_EXT 0x88FF + +#define GL_QUERY_COUNTER_BITS_ARB 0x8864 +#define GL_CURRENT_QUERY_ARB 0x8865 +#define GL_QUERY_RESULT_ARB 0x8866 +#define GL_QUERY_RESULT_AVAILABLE_ARB 0x8867 +#define GL_SAMPLES_PASSED_ARB 0x8914 + +#define GL_FUNC_ADD_EXT 0x8006 +#define GL_FUNC_SUBTRACT_EXT 0x800A +#define GL_FUNC_REVERSE_SUBTRACT_EXT 0x800B +#define GL_MIN_EXT 0x8007 +#define GL_MAX_EXT 0x8008 +#define GL_BLEND_EQUATION_EXT 0x8009 + +#define GL_DEPTH_COMPONENT16 0x81A5 +#define GL_DEPTH_COMPONENT24 0x81A6 +#define GL_DEPTH_COMPONENT32 0x81A7 +#define GL_VERTEX_SHADER_ARB 0x8B31 +#define GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB 0x8B4A +#define GL_MAX_VARYING_FLOATS_ARB 0x8B4B +#define GL_MAX_VERTEX_ATTRIBS_ARB 0x8869 +#define GL_MAX_TEXTURE_IMAGE_UNITS_ARB 0x8872 +#define GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS_ARB 0x8B4C +#define GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS_ARB 0x8B4D +#define GL_MAX_TEXTURE_COORDS_ARB 0x8871 +#define GL_VERTEX_PROGRAM_POINT_SIZE_ARB 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_ARB 0x8643 +#define GL_OBJECT_ACTIVE_ATTRIBUTES_ARB 0x8B89 +#define GL_OBJECT_ACTIVE_ATTRIBUTE_MAX_LENGTH_ARB 0x8B8A +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED_ARB 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE_ARB 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE_ARB 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE_ARB 0x8625 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED_ARB 0x886A +#define GL_CURRENT_VERTEX_ATTRIB_ARB 0x8626 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER_ARB 0x8645 +#define GL_FLOAT_VEC2_ARB 0x8B50 +#define GL_FLOAT_VEC3_ARB 0x8B51 +#define GL_FLOAT_VEC4_ARB 0x8B52 +#define GL_FLOAT_MAT2_ARB 0x8B5A +#define GL_FLOAT_MAT3_ARB 0x8B5B +#define GL_FLOAT_MAT4_ARB 0x8B5C + +#define WGL_CONTEXT_MAJOR_VERSION_ARB 0x2091 +#define WGL_CONTEXT_MINOR_VERSION_ARB 0x2092 +#define WGL_CONTEXT_LAYER_PLANE_ARB 0x2093 +#define WGL_CONTEXT_FLAGS_ARB 0x2094 +#define WGL_CONTEXT_DEBUG_BIT_ARB 0x0001 +#define WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x0002 +#define WGL_CONTEXT_PROFILE_MASK_ARB 0x9126 +#define WGL_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 +#define WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 +#define WGL_CONTEXT_ES2_PROFILE_BIT_EXT 0x00000004 /*WGL_CONTEXT_ES2_PROFILE_BIT_EXT*/ +#define ERROR_INVALID_VERSION_ARB 0x2095 +#define ERROR_INVALID_PROFILE_ARB 0x2096 + +#define GL_FRAGMENT_SHADER_ARB 0x8B30 +#define GL_MAX_FRAGMENT_UNIFORM_COMPONENTS_ARB 0x8B49 +#define GL_MAX_TEXTURE_COORDS_ARB 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_ARB 0x8872 +#define GL_FRAGMENT_SHADER_DERIVATIVE_HINT_ARB 0x8B8B + +//GL_ARB_vertex_buffer_object +#define GL_ARRAY_BUFFER_ARB 0x8892 +#define GL_ELEMENT_ARRAY_BUFFER_ARB 0x8893 +#define GL_ARRAY_BUFFER_BINDING_ARB 0x8894 +#define GL_ELEMENT_ARRAY_BUFFER_BINDING_ARB 0x8895 +#define GL_VERTEX_ARRAY_BUFFER_BINDING_ARB 0x8896 +#define GL_NORMAL_ARRAY_BUFFER_BINDING_ARB 0x8897 +#define GL_COLOR_ARRAY_BUFFER_BINDING_ARB 0x8898 +#define GL_INDEX_ARRAY_BUFFER_BINDING_ARB 0x8899 +#define GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING_ARB 0x889A +#define GL_EDGE_FLAG_ARRAY_BUFFER_BINDING_ARB 0x889B +#define GL_WEIGHT_ARRAY_BUFFER_BINDING_ARB 0x889E +#define GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING_ARB 0x889F +#define GL_STREAM_DRAW_ARB 0x88E0 +#define GL_STREAM_READ_ARB 0x88E1 +#define GL_STREAM_COPY_ARB 0x88E2 +#define GL_STATIC_DRAW_ARB 0x88E4 +#define GL_STATIC_READ_ARB 0x88E5 +#define GL_STATIC_COPY_ARB 0x88E6 +#define GL_DYNAMIC_DRAW_ARB 0x88E8 +#define GL_DYNAMIC_READ_ARB 0x88E9 +#define GL_DYNAMIC_COPY_ARB 0x88EA +#define GL_READ_ONLY_ARB 0x88B8 +#define GL_WRITE_ONLY_ARB 0x88B9 +#define GL_READ_WRITE_ARB 0x88BA +#define GL_BUFFER_SIZE_ARB 0x8764 +#define GL_BUFFER_USAGE_ARB 0x8765 +#define GL_BUFFER_ACCESS_ARB 0x88BB +#define GL_BUFFER_MAPPED_ARB 0x88BC +#define GL_BUFFER_MAP_POINTER_ARB 0x88BD +#define GL_SECONDARY_COLOR_ARRAY_BUFFER_BINDING_ARB 0x889C +#define GL_FOG_COORDINATE_ARRAY_BUFFER_BINDING_ARB 0x889D + +#define GL_NORMAL_MAP_ARB 0x8511 +#define GL_REFLECTION_MAP_ARB 0x8512 +#define GL_TEXTURE_CUBE_MAP_ARB 0x8513 +#define GL_TEXTURE_BINDING_CUBE_MAP_ARB 0x8514 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB 0x8515 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB 0x8516 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB 0x8517 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB 0x8518 +#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB 0x8519 +#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB 0x851A +#define GL_PROXY_TEXTURE_CUBE_MAP_ARB 0x851B +#define GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB 0x851C + +#define GL_COMBINE_ARB 0x8570 +#define GL_COMBINE_RGB_ARB 0x8571 +#define GL_COMBINE_ALPHA_ARB 0x8572 +#define GL_SOURCE0_RGB_ARB 0x8580 +#define GL_SOURCE1_RGB_ARB 0x8581 +#define GL_SOURCE2_RGB_ARB 0x8582 +#define GL_SOURCE0_ALPHA_ARB 0x8588 +#define GL_SOURCE1_ALPHA_ARB 0x8589 +#define GL_SOURCE2_ALPHA_ARB 0x858A +#define GL_OPERAND0_RGB_ARB 0x8590 +#define GL_OPERAND1_RGB_ARB 0x8591 +#define GL_OPERAND2_RGB_ARB 0x8592 +#define GL_OPERAND0_ALPHA_ARB 0x8598 +#define GL_OPERAND1_ALPHA_ARB 0x8599 +#define GL_OPERAND2_ALPHA_ARB 0x859A +#define GL_RGB_SCALE_ARB 0x8573 +#define GL_ADD_SIGNED_ARB 0x8574 +#define GL_INTERPOLATE_ARB 0x8575 +#define GL_SUBTRACT_ARB 0x84E7 +#define GL_CONSTANT_ARB 0x8576 +#define GL_PRIMARY_COLOR_ARB 0x8577 +#define GL_PREVIOUS_ARB 0x8578 + +#define GL_DOT3_RGB_ARB 0x86AE +#define GL_DOT3_RGBA_ARB 0x86AF + +#define GL_MULTISAMPLE_ARB 0x809D +#define GL_SAMPLE_ALPHA_TO_COVERAGE_ARB 0x809E +#define GL_SAMPLE_ALPHA_TO_ONE_ARB 0x809F +#define GL_SAMPLE_COVERAGE_ARB 0x80A0 +#define GL_SAMPLE_BUFFERS_ARB 0x80A8 +#define GL_SAMPLES_ARB 0x80A9 +#define GL_SAMPLE_COVERAGE_VALUE_ARB 0x80AA +#define GL_SAMPLE_COVERAGE_INVERT_ARB 0x80AB +#define GL_MULTISAMPLE_BIT_ARB 0x20000000 + +#define GL_COLOR_SUM_ARB 0x8458 +#define GL_VERTEX_PROGRAM_ARB 0x8620 +#define GL_VERTEX_ATTRIB_ARRAY_ENABLED_ARB 0x8622 +#define GL_VERTEX_ATTRIB_ARRAY_SIZE_ARB 0x8623 +#define GL_VERTEX_ATTRIB_ARRAY_STRIDE_ARB 0x8624 +#define GL_VERTEX_ATTRIB_ARRAY_TYPE_ARB 0x8625 +#define GL_CURRENT_VERTEX_ATTRIB_ARB 0x8626 +#define GL_PROGRAM_LENGTH_ARB 0x8627 +#define GL_PROGRAM_STRING_ARB 0x8628 +#define GL_MAX_PROGRAM_MATRIX_STACK_DEPTH_ARB 0x862E +#define GL_MAX_PROGRAM_MATRICES_ARB 0x862F +#define GL_CURRENT_MATRIX_STACK_DEPTH_ARB 0x8640 +#define GL_CURRENT_MATRIX_ARB 0x8641 +#define GL_VERTEX_PROGRAM_POINT_SIZE_ARB 0x8642 +#define GL_VERTEX_PROGRAM_TWO_SIDE_ARB 0x8643 +#define GL_VERTEX_ATTRIB_ARRAY_POINTER_ARB 0x8645 +#define GL_PROGRAM_ERROR_POSITION_ARB 0x864B +#define GL_PROGRAM_BINDING_ARB 0x8677 +#define GL_MAX_VERTEX_ATTRIBS_ARB 0x8869 +#define GL_VERTEX_ATTRIB_ARRAY_NORMALIZED_ARB 0x886A +#define GL_PROGRAM_ERROR_STRING_ARB 0x8874 +#define GL_PROGRAM_FORMAT_ASCII_ARB 0x8875 +#define GL_PROGRAM_FORMAT_ARB 0x8876 +#define GL_PROGRAM_INSTRUCTIONS_ARB 0x88A0 +#define GL_MAX_PROGRAM_INSTRUCTIONS_ARB 0x88A1 +#define GL_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A2 +#define GL_MAX_PROGRAM_NATIVE_INSTRUCTIONS_ARB 0x88A3 +#define GL_PROGRAM_TEMPORARIES_ARB 0x88A4 +#define GL_MAX_PROGRAM_TEMPORARIES_ARB 0x88A5 +#define GL_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A6 +#define GL_MAX_PROGRAM_NATIVE_TEMPORARIES_ARB 0x88A7 +#define GL_PROGRAM_PARAMETERS_ARB 0x88A8 +#define GL_MAX_PROGRAM_PARAMETERS_ARB 0x88A9 +#define GL_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AA +#define GL_MAX_PROGRAM_NATIVE_PARAMETERS_ARB 0x88AB +#define GL_PROGRAM_ATTRIBS_ARB 0x88AC +#define GL_MAX_PROGRAM_ATTRIBS_ARB 0x88AD +#define GL_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AE +#define GL_MAX_PROGRAM_NATIVE_ATTRIBS_ARB 0x88AF +#define GL_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B0 +#define GL_MAX_PROGRAM_ADDRESS_REGISTERS_ARB 0x88B1 +#define GL_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B2 +#define GL_MAX_PROGRAM_NATIVE_ADDRESS_REGISTERS_ARB 0x88B3 +#define GL_MAX_PROGRAM_LOCAL_PARAMETERS_ARB 0x88B4 +#define GL_MAX_PROGRAM_ENV_PARAMETERS_ARB 0x88B5 +#define GL_PROGRAM_UNDER_NATIVE_LIMITS_ARB 0x88B6 +#define GL_TRANSPOSE_CURRENT_MATRIX_ARB 0x88B7 +#define GL_MATRIX0_ARB 0x88C0 +#define GL_MATRIX1_ARB 0x88C1 +#define GL_MATRIX2_ARB 0x88C2 +#define GL_MATRIX3_ARB 0x88C3 +#define GL_MATRIX4_ARB 0x88C4 +#define GL_MATRIX5_ARB 0x88C5 +#define GL_MATRIX6_ARB 0x88C6 +#define GL_MATRIX7_ARB 0x88C7 +#define GL_MATRIX8_ARB 0x88C8 +#define GL_MATRIX9_ARB 0x88C9 +#define GL_MATRIX10_ARB 0x88CA +#define GL_MATRIX11_ARB 0x88CB +#define GL_MATRIX12_ARB 0x88CC +#define GL_MATRIX13_ARB 0x88CD +#define GL_MATRIX14_ARB 0x88CE +#define GL_MATRIX15_ARB 0x88CF +#define GL_MATRIX16_ARB 0x88D0 +#define GL_MATRIX17_ARB 0x88D1 +#define GL_MATRIX18_ARB 0x88D2 +#define GL_MATRIX19_ARB 0x88D3 +#define GL_MATRIX20_ARB 0x88D4 +#define GL_MATRIX21_ARB 0x88D5 +#define GL_MATRIX22_ARB 0x88D6 +#define GL_MATRIX23_ARB 0x88D7 +#define GL_MATRIX24_ARB 0x88D8 +#define GL_MATRIX25_ARB 0x88D9 +#define GL_MATRIX26_ARB 0x88DA +#define GL_MATRIX27_ARB 0x88DB +#define GL_MATRIX28_ARB 0x88DC +#define GL_MATRIX29_ARB 0x88DD +#define GL_MATRIX30_ARB 0x88DE +#define GL_MATRIX31_ARB 0x88DF +#define GL_FRAGMENT_PROGRAM_ARB 0x8804 +#define GL_PROGRAM_ALU_INSTRUCTIONS_ARB 0x8805 +#define GL_PROGRAM_TEX_INSTRUCTIONS_ARB 0x8806 +#define GL_PROGRAM_TEX_INDIRECTIONS_ARB 0x8807 +#define GL_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x8808 +#define GL_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x8809 +#define GL_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x880A +#define GL_MAX_PROGRAM_ALU_INSTRUCTIONS_ARB 0x880B +#define GL_MAX_PROGRAM_TEX_INSTRUCTIONS_ARB 0x880C +#define GL_MAX_PROGRAM_TEX_INDIRECTIONS_ARB 0x880D +#define GL_MAX_PROGRAM_NATIVE_ALU_INSTRUCTIONS_ARB 0x880E +#define GL_MAX_PROGRAM_NATIVE_TEX_INSTRUCTIONS_ARB 0x880F +#define GL_MAX_PROGRAM_NATIVE_TEX_INDIRECTIONS_ARB 0x8810 +#define GL_MAX_TEXTURE_COORDS_ARB 0x8871 +#define GL_MAX_TEXTURE_IMAGE_UNITS_ARB 0x8872 + +#define GL_INVALID_FRAMEBUFFER_OPERATION_EXT 0x0506 +#define GL_MAX_RENDERBUFFER_SIZE_EXT 0x84E8 +#define GL_FRAMEBUFFER_BINDING_EXT 0x8CA6 +#define GL_RENDERBUFFER_BINDING_EXT 0x8CA7 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE_EXT 0x8CD0 +#define GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME_EXT 0x8CD1 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL_EXT 0x8CD2 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE_EXT 0x8CD3 +#define GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_3D_ZOFFSET_EXT 0x8CD4 +#define GL_FRAMEBUFFER_COMPLETE_EXT 0x8CD5 +#define GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT 0x8CD6 +#define GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT 0x8CD7 +#define GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT 0x8CD9 +#define GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT 0x8CDA +#define GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT 0x8CDB +#define GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT 0x8CDC +#define GL_FRAMEBUFFER_UNSUPPORTED_EXT 0x8CDD +#define GL_MAX_COLOR_ATTACHMENTS_EXT 0x8CDF +#define GL_COLOR_ATTACHMENT0_EXT 0x8CE0 +#define GL_COLOR_ATTACHMENT1_EXT 0x8CE1 +#define GL_COLOR_ATTACHMENT2_EXT 0x8CE2 +#define GL_COLOR_ATTACHMENT3_EXT 0x8CE3 +#define GL_COLOR_ATTACHMENT4_EXT 0x8CE4 +#define GL_COLOR_ATTACHMENT5_EXT 0x8CE5 +#define GL_COLOR_ATTACHMENT6_EXT 0x8CE6 +#define GL_COLOR_ATTACHMENT7_EXT 0x8CE7 +#define GL_COLOR_ATTACHMENT8_EXT 0x8CE8 +#define GL_COLOR_ATTACHMENT9_EXT 0x8CE9 +#define GL_COLOR_ATTACHMENT10_EXT 0x8CEA +#define GL_COLOR_ATTACHMENT11_EXT 0x8CEB +#define GL_COLOR_ATTACHMENT12_EXT 0x8CEC +#define GL_COLOR_ATTACHMENT13_EXT 0x8CED +#define GL_COLOR_ATTACHMENT14_EXT 0x8CEE +#define GL_COLOR_ATTACHMENT15_EXT 0x8CEF +#define GL_DEPTH_ATTACHMENT_EXT 0x8D00 +#define GL_STENCIL_ATTACHMENT_EXT 0x8D20 +#define GL_FRAMEBUFFER_EXT 0x8D40 +#define GL_RENDERBUFFER_EXT 0x8D41 +#define GL_RENDERBUFFER_WIDTH_EXT 0x8D42 +#define GL_RENDERBUFFER_HEIGHT_EXT 0x8D43 +#define GL_RENDERBUFFER_INTERNAL_FORMAT_EXT 0x8D44 +#define GL_STENCIL_INDEX1_EXT 0x8D46 +#define GL_STENCIL_INDEX4_EXT 0x8D47 +#define GL_STENCIL_INDEX8_EXT 0x8D48 +#define GL_STENCIL_INDEX16_EXT 0x8D49 +#define GL_RENDERBUFFER_RED_SIZE_EXT 0x8D50 +#define GL_RENDERBUFFER_GREEN_SIZE_EXT 0x8D51 +#define GL_RENDERBUFFER_BLUE_SIZE_EXT 0x8D52 +#define GL_RENDERBUFFER_ALPHA_SIZE_EXT 0x8D53 +#define GL_RENDERBUFFER_DEPTH_SIZE_EXT 0x8D54 +#define GL_RENDERBUFFER_STENCIL_SIZE_EXT 0x8D55 +#define GL_VERTEX_ARRAY_BINDING 0x85B5 + +#define GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB 0x8242 +#define GL_MAX_DEBUG_MESSAGE_LENGTH_ARB 0x9143 +#define GL_MAX_DEBUG_LOGGED_MESSAGES_ARB 0x9144 +#define GL_DEBUG_LOGGED_MESSAGES_ARB 0x9145 +#define GL_DEBUG_NEXT_LOGGED_MESSAGE_LENGTH_ARB 0x8243 +#define GL_DEBUG_CALLBACK_FUNCTION_ARB 0x8244 +#define GL_DEBUG_CALLBACK_USER_PARAM_ARB 0x8245 +#define GL_DEBUG_SOURCE_API_ARB 0x8246 +#define GL_DEBUG_SOURCE_WINDOW_SYSTEM_ARB 0x8247 +#define GL_DEBUG_SOURCE_SHADER_COMPILER_ARB 0x8248 +#define GL_DEBUG_SOURCE_THIRD_PARTY_ARB 0x8249 +#define GL_DEBUG_SOURCE_APPLICATION_ARB 0x824A +#define GL_DEBUG_SOURCE_OTHER_ARB 0x824B +#define GL_DEBUG_TYPE_ERROR_ARB 0x824C +#define GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB 0x824D +#define GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB 0x824E +#define GL_DEBUG_TYPE_PORTABILITY_ARB 0x824F +#define GL_DEBUG_TYPE_PERFORMANCE_ARB 0x8250 +#define GL_DEBUG_TYPE_OTHER_ARB 0x8251 +#define GL_DEBUG_SEVERITY_HIGH_ARB 0x9146 +#define GL_DEBUG_SEVERITY_MEDIUM_ARB 0x9147 +#define GL_DEBUG_SEVERITY_LOW_ARB 0x9148 + +// helper opengl functions +EXTERN GLenum ( APIENTRY *pglGetError )(void); +EXTERN const GLcharARB * ( APIENTRY *pglGetString )(GLenum name); + +// base gl functions +EXTERN void ( APIENTRY *pglAccum )(GLenum op, GLfloat value); +EXTERN void ( APIENTRY *pglAlphaFunc )(GLenum func, GLclampf ref); +EXTERN void ( APIENTRY *pglBegin )(GLenum mode); +EXTERN void ( APIENTRY *pglBindTexture )(GLenum target, GLuint texture); +EXTERN void ( APIENTRY *pglBitmap )(GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig, GLfloat xmove, GLfloat ymove, const GLubyte *bitmap); +EXTERN void ( APIENTRY *pglBlendFunc )(GLenum sfactor, GLenum dfactor); +EXTERN void ( APIENTRY *pglCallList )(GLuint list); +EXTERN void ( APIENTRY *pglCallLists )(GLsizei n, GLenum type, const GLvoid *lists); +EXTERN void ( APIENTRY *pglClear )(GLbitfield mask); +EXTERN void ( APIENTRY *pglClearAccum )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +EXTERN void ( APIENTRY *pglClearColor )(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); +EXTERN void ( APIENTRY *pglClearDepth )(GLclampd depth); +EXTERN void ( APIENTRY *pglClearIndex )(GLfloat c); +EXTERN void ( APIENTRY *pglClearStencil )(GLint s); +EXTERN GLboolean ( APIENTRY *pglIsEnabled )( GLenum cap ); +EXTERN GLboolean ( APIENTRY *pglIsList )( GLuint list ); +EXTERN GLboolean ( APIENTRY *pglIsTexture )( GLuint texture ); +EXTERN void ( APIENTRY *pglClipPlane )(GLenum plane, const GLdouble *equation); +EXTERN void ( APIENTRY *pglColor3b )(GLbyte red, GLbyte green, GLbyte blue); +EXTERN void ( APIENTRY *pglColor3bv )(const GLbyte *v); +EXTERN void ( APIENTRY *pglColor3d )(GLdouble red, GLdouble green, GLdouble blue); +EXTERN void ( APIENTRY *pglColor3dv )(const GLdouble *v); +EXTERN void ( APIENTRY *pglColor3f )(GLfloat red, GLfloat green, GLfloat blue); +EXTERN void ( APIENTRY *pglColor3fv )(const GLfloat *v); +EXTERN void ( APIENTRY *pglColor3i )(GLint red, GLint green, GLint blue); +EXTERN void ( APIENTRY *pglColor3iv )(const GLint *v); +EXTERN void ( APIENTRY *pglColor3s )(GLshort red, GLshort green, GLshort blue); +EXTERN void ( APIENTRY *pglColor3sv )(const GLshort *v); +EXTERN void ( APIENTRY *pglColor3ub )(GLubyte red, GLubyte green, GLubyte blue); +EXTERN void ( APIENTRY *pglColor3ubv )(const GLubyte *v); +EXTERN void ( APIENTRY *pglColor3ui )(GLuint red, GLuint green, GLuint blue); +EXTERN void ( APIENTRY *pglColor3uiv )(const GLuint *v); +EXTERN void ( APIENTRY *pglColor3us )(GLushort red, GLushort green, GLushort blue); +EXTERN void ( APIENTRY *pglColor3usv )(const GLushort *v); +EXTERN void ( APIENTRY *pglColor4b )(GLbyte red, GLbyte green, GLbyte blue, GLbyte alpha); +EXTERN void ( APIENTRY *pglColor4bv )(const GLbyte *v); +EXTERN void ( APIENTRY *pglColor4d )(GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha); +EXTERN void ( APIENTRY *pglColor4dv )(const GLdouble *v); +EXTERN void ( APIENTRY *pglColor4f )(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha); +EXTERN void ( APIENTRY *pglColor4fv )(const GLfloat *v); +EXTERN void ( APIENTRY *pglColor4i )(GLint red, GLint green, GLint blue, GLint alpha); +EXTERN void ( APIENTRY *pglColor4iv )(const GLint *v); +EXTERN void ( APIENTRY *pglColor4s )(GLshort red, GLshort green, GLshort blue, GLshort alpha); +EXTERN void ( APIENTRY *pglColor4sv )(const GLshort *v); +EXTERN void ( APIENTRY *pglColor4ub )(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha); +EXTERN void ( APIENTRY *pglColor4ubv )(const GLubyte *v); +EXTERN void ( APIENTRY *pglColor4ui )(GLuint red, GLuint green, GLuint blue, GLuint alpha); +EXTERN void ( APIENTRY *pglColor4uiv )(const GLuint *v); +EXTERN void ( APIENTRY *pglColor4us )(GLushort red, GLushort green, GLushort blue, GLushort alpha); +EXTERN void ( APIENTRY *pglColor4usv )(const GLushort *v); +EXTERN void ( APIENTRY *pglColorMask )(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); +EXTERN void ( APIENTRY *pglColorMaterial )(GLenum face, GLenum mode); +EXTERN void ( APIENTRY *pglCopyPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum type); +EXTERN void ( APIENTRY *pglCopyTexImage1D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLint border); +EXTERN void ( APIENTRY *pglCopyTexImage2D )(GLenum target, GLint level, GLenum internalFormat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border); +EXTERN void ( APIENTRY *pglCopyTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width); +EXTERN void ( APIENTRY *pglCopyTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height); +EXTERN void ( APIENTRY *pglCullFace )(GLenum mode); +EXTERN void ( APIENTRY *pglDeleteLists )(GLuint list, GLsizei range); +EXTERN void ( APIENTRY *pglDeleteTextures )(GLsizei n, const GLuint *textures); +EXTERN void ( APIENTRY *pglDepthFunc )(GLenum func); +EXTERN void ( APIENTRY *pglDepthMask )(GLboolean flag); +EXTERN void ( APIENTRY *pglDepthRange )(GLclampd zNear, GLclampd zFar); +EXTERN void ( APIENTRY *pglDisable )(GLenum cap); +EXTERN void ( APIENTRY *pglDisableClientState )(GLenum array); +EXTERN void ( APIENTRY *pglDrawArrays )(GLenum mode, GLint first, GLsizei count); +EXTERN void ( APIENTRY *pglDrawBuffer )(GLenum mode); +EXTERN void ( APIENTRY *pglDrawPixels )(GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +EXTERN void ( APIENTRY *pglEdgeFlag )(GLboolean flag); +EXTERN void ( APIENTRY *pglEdgeFlagPointer )(GLsizei stride, const GLvoid *pointer); +EXTERN void ( APIENTRY *pglEdgeFlagv )(const GLboolean *flag); +EXTERN void ( APIENTRY *pglEnable )(GLenum cap); +EXTERN void ( APIENTRY *pglEnableClientState )(GLenum array); +EXTERN void ( APIENTRY *pglEnd )(void); +EXTERN void ( APIENTRY *pglEndList )(void); +EXTERN void ( APIENTRY *pglEvalCoord1d )(GLdouble u); +EXTERN void ( APIENTRY *pglEvalCoord1dv )(const GLdouble *u); +EXTERN void ( APIENTRY *pglEvalCoord1f )(GLfloat u); +EXTERN void ( APIENTRY *pglEvalCoord1fv )(const GLfloat *u); +EXTERN void ( APIENTRY *pglEvalCoord2d )(GLdouble u, GLdouble v); +EXTERN void ( APIENTRY *pglEvalCoord2dv )(const GLdouble *u); +EXTERN void ( APIENTRY *pglEvalCoord2f )(GLfloat u, GLfloat v); +EXTERN void ( APIENTRY *pglEvalCoord2fv )(const GLfloat *u); +EXTERN void ( APIENTRY *pglEvalMesh1 )(GLenum mode, GLint i1, GLint i2); +EXTERN void ( APIENTRY *pglEvalMesh2 )(GLenum mode, GLint i1, GLint i2, GLint j1, GLint j2); +EXTERN void ( APIENTRY *pglEvalPoint1 )(GLint i); +EXTERN void ( APIENTRY *pglEvalPoint2 )(GLint i, GLint j); +EXTERN void ( APIENTRY *pglFeedbackBuffer )(GLsizei size, GLenum type, GLfloat *buffer); +EXTERN void ( APIENTRY *pglFinish )(void); +EXTERN void ( APIENTRY *pglFlush )(void); +EXTERN void ( APIENTRY *pglFogf )(GLenum pname, GLfloat param); +EXTERN void ( APIENTRY *pglFogfv )(GLenum pname, const GLfloat *params); +EXTERN void ( APIENTRY *pglFogi )(GLenum pname, GLint param); +EXTERN void ( APIENTRY *pglFogiv )(GLenum pname, const GLint *params); +EXTERN void ( APIENTRY *pglFrontFace )(GLenum mode); +EXTERN void ( APIENTRY *pglFrustum )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +EXTERN void ( APIENTRY *pglGenTextures )(GLsizei n, GLuint *textures); +EXTERN void ( APIENTRY *pglGetBooleanv )(GLenum pname, GLboolean *params); +EXTERN void ( APIENTRY *pglGetClipPlane )(GLenum plane, GLdouble *equation); +EXTERN void ( APIENTRY *pglGetDoublev )(GLenum pname, GLdouble *params); +EXTERN void ( APIENTRY *pglGetFloatv )(GLenum pname, GLfloat *params); +EXTERN void ( APIENTRY *pglGetIntegerv )(GLenum pname, GLint *params); +EXTERN void ( APIENTRY *pglGetLightfv )(GLenum light, GLenum pname, GLfloat *params); +EXTERN void ( APIENTRY *pglGetLightiv )(GLenum light, GLenum pname, GLint *params); +EXTERN void ( APIENTRY *pglGetMapdv )(GLenum target, GLenum query, GLdouble *v); +EXTERN void ( APIENTRY *pglGetMapfv )(GLenum target, GLenum query, GLfloat *v); +EXTERN void ( APIENTRY *pglGetMapiv )(GLenum target, GLenum query, GLint *v); +EXTERN void ( APIENTRY *pglGetMaterialfv )(GLenum face, GLenum pname, GLfloat *params); +EXTERN void ( APIENTRY *pglGetMaterialiv )(GLenum face, GLenum pname, GLint *params); +EXTERN void ( APIENTRY *pglGetPixelMapfv )(GLenum map, GLfloat *values); +EXTERN void ( APIENTRY *pglGetPixelMapuiv )(GLenum map, GLuint *values); +EXTERN void ( APIENTRY *pglGetPixelMapusv )(GLenum map, GLushort *values); +EXTERN void ( APIENTRY *pglGetPointerv )(GLenum pname, GLvoid* *params); +EXTERN void ( APIENTRY *pglGetPolygonStipple )(GLubyte *mask); +EXTERN void ( APIENTRY *pglGetTexEnvfv )(GLenum target, GLenum pname, GLfloat *params); +EXTERN void ( APIENTRY *pglGetTexEnviv )(GLenum target, GLenum pname, GLint *params); +EXTERN void ( APIENTRY *pglGetTexGendv )(GLenum coord, GLenum pname, GLdouble *params); +EXTERN void ( APIENTRY *pglGetTexGenfv )(GLenum coord, GLenum pname, GLfloat *params); +EXTERN void ( APIENTRY *pglGetTexGeniv )(GLenum coord, GLenum pname, GLint *params); +EXTERN void ( APIENTRY *pglGetTexImage )(GLenum target, GLint level, GLenum format, GLenum type, GLvoid *pixels); +EXTERN void ( APIENTRY *pglGetTexLevelParameterfv )(GLenum target, GLint level, GLenum pname, GLfloat *params); +EXTERN void ( APIENTRY *pglGetTexLevelParameteriv )(GLenum target, GLint level, GLenum pname, GLint *params); +EXTERN void ( APIENTRY *pglGetTexParameterfv )(GLenum target, GLenum pname, GLfloat *params); +EXTERN void ( APIENTRY *pglGetTexParameteriv )(GLenum target, GLenum pname, GLint *params); +EXTERN void ( APIENTRY *pglHint )(GLenum target, GLenum mode); +EXTERN void ( APIENTRY *pglIndexMask )(GLuint mask); +EXTERN void ( APIENTRY *pglIndexPointer )(GLenum type, GLsizei stride, const GLvoid *pointer); +EXTERN void ( APIENTRY *pglIndexd )(GLdouble c); +EXTERN void ( APIENTRY *pglIndexdv )(const GLdouble *c); +EXTERN void ( APIENTRY *pglIndexf )(GLfloat c); +EXTERN void ( APIENTRY *pglIndexfv )(const GLfloat *c); +EXTERN void ( APIENTRY *pglIndexi )(GLint c); +EXTERN void ( APIENTRY *pglIndexiv )(const GLint *c); +EXTERN void ( APIENTRY *pglIndexs )(GLshort c); +EXTERN void ( APIENTRY *pglIndexsv )(const GLshort *c); +EXTERN void ( APIENTRY *pglIndexub )(GLubyte c); +EXTERN void ( APIENTRY *pglIndexubv )(const GLubyte *c); +EXTERN void ( APIENTRY *pglInitNames )(void); +EXTERN void ( APIENTRY *pglInterleavedArrays )(GLenum format, GLsizei stride, const GLvoid *pointer); +EXTERN void ( APIENTRY *pglLightModelf )(GLenum pname, GLfloat param); +EXTERN void ( APIENTRY *pglLightModelfv )(GLenum pname, const GLfloat *params); +EXTERN void ( APIENTRY *pglLightModeli )(GLenum pname, GLint param); +EXTERN void ( APIENTRY *pglLightModeliv )(GLenum pname, const GLint *params); +EXTERN void ( APIENTRY *pglLightf )(GLenum light, GLenum pname, GLfloat param); +EXTERN void ( APIENTRY *pglLightfv )(GLenum light, GLenum pname, const GLfloat *params); +EXTERN void ( APIENTRY *pglLighti )(GLenum light, GLenum pname, GLint param); +EXTERN void ( APIENTRY *pglLightiv )(GLenum light, GLenum pname, const GLint *params); +EXTERN void ( APIENTRY *pglLineStipple )(GLint factor, GLushort pattern); +EXTERN void ( APIENTRY *pglLineWidth )(GLfloat width); +EXTERN void ( APIENTRY *pglListBase )(GLuint base); +EXTERN void ( APIENTRY *pglLoadIdentity )(void); +EXTERN void ( APIENTRY *pglLoadMatrixd )(const GLdouble *m); +EXTERN void ( APIENTRY *pglLoadMatrixf )(const GLfloat *m); +EXTERN void ( APIENTRY *pglLoadName )(GLuint name); +EXTERN void ( APIENTRY *pglLogicOp )(GLenum opcode); +EXTERN void ( APIENTRY *pglMap1d )(GLenum target, GLdouble u1, GLdouble u2, GLint stride, GLint order, const GLdouble *points); +EXTERN void ( APIENTRY *pglMap1f )(GLenum target, GLfloat u1, GLfloat u2, GLint stride, GLint order, const GLfloat *points); +EXTERN void ( APIENTRY *pglMap2d )(GLenum target, GLdouble u1, GLdouble u2, GLint ustride, GLint uorder, GLdouble v1, GLdouble v2, GLint vstride, GLint vorder, const GLdouble *points); +EXTERN void ( APIENTRY *pglMap2f )(GLenum target, GLfloat u1, GLfloat u2, GLint ustride, GLint uorder, GLfloat v1, GLfloat v2, GLint vstride, GLint vorder, const GLfloat *points); +EXTERN void ( APIENTRY *pglMapGrid1d )(GLint un, GLdouble u1, GLdouble u2); +EXTERN void ( APIENTRY *pglMapGrid1f )(GLint un, GLfloat u1, GLfloat u2); +EXTERN void ( APIENTRY *pglMapGrid2d )(GLint un, GLdouble u1, GLdouble u2, GLint vn, GLdouble v1, GLdouble v2); +EXTERN void ( APIENTRY *pglMapGrid2f )(GLint un, GLfloat u1, GLfloat u2, GLint vn, GLfloat v1, GLfloat v2); +EXTERN void ( APIENTRY *pglMaterialf )(GLenum face, GLenum pname, GLfloat param); +EXTERN void ( APIENTRY *pglMaterialfv )(GLenum face, GLenum pname, const GLfloat *params); +EXTERN void ( APIENTRY *pglMateriali )(GLenum face, GLenum pname, GLint param); +EXTERN void ( APIENTRY *pglMaterialiv )(GLenum face, GLenum pname, const GLint *params); +EXTERN void ( APIENTRY *pglMatrixMode )(GLenum mode); +EXTERN void ( APIENTRY *pglMultMatrixd )(const GLdouble *m); +EXTERN void ( APIENTRY *pglMultMatrixf )(const GLfloat *m); +EXTERN void ( APIENTRY *pglNewList )(GLuint list, GLenum mode); +EXTERN void ( APIENTRY *pglNormal3b )(GLbyte nx, GLbyte ny, GLbyte nz); +EXTERN void ( APIENTRY *pglNormal3bv )(const GLbyte *v); +EXTERN void ( APIENTRY *pglNormal3d )(GLdouble nx, GLdouble ny, GLdouble nz); +EXTERN void ( APIENTRY *pglNormal3dv )(const GLdouble *v); +EXTERN void ( APIENTRY *pglNormal3f )(GLfloat nx, GLfloat ny, GLfloat nz); +EXTERN void ( APIENTRY *pglNormal3fv )(const GLfloat *v); +EXTERN void ( APIENTRY *pglNormal3i )(GLint nx, GLint ny, GLint nz); +EXTERN void ( APIENTRY *pglNormal3iv )(const GLint *v); +EXTERN void ( APIENTRY *pglNormal3s )(GLshort nx, GLshort ny, GLshort nz); +EXTERN void ( APIENTRY *pglNormal3sv )(const GLshort *v); +EXTERN void ( APIENTRY *pglOrtho )(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar); +EXTERN void ( APIENTRY *pglPassThrough )(GLfloat token); +EXTERN void ( APIENTRY *pglPixelMapfv )(GLenum map, GLsizei mapsize, const GLfloat *values); +EXTERN void ( APIENTRY *pglPixelMapuiv )(GLenum map, GLsizei mapsize, const GLuint *values); +EXTERN void ( APIENTRY *pglPixelMapusv )(GLenum map, GLsizei mapsize, const GLushort *values); +EXTERN void ( APIENTRY *pglPixelStoref )(GLenum pname, GLfloat param); +EXTERN void ( APIENTRY *pglPixelStorei )(GLenum pname, GLint param); +EXTERN void ( APIENTRY *pglPixelTransferf )(GLenum pname, GLfloat param); +EXTERN void ( APIENTRY *pglPixelTransferi )(GLenum pname, GLint param); +EXTERN void ( APIENTRY *pglPixelZoom )(GLfloat xfactor, GLfloat yfactor); +EXTERN void ( APIENTRY *pglPointSize )(GLfloat size); +EXTERN void ( APIENTRY *pglPolygonMode )(GLenum face, GLenum mode); +EXTERN void ( APIENTRY *pglPolygonOffset )(GLfloat factor, GLfloat units); +EXTERN void ( APIENTRY *pglPolygonStipple )(const GLubyte *mask); +EXTERN void ( APIENTRY *pglPopAttrib )(void); +EXTERN void ( APIENTRY *pglPopClientAttrib )(void); +EXTERN void ( APIENTRY *pglPopMatrix )(void); +EXTERN void ( APIENTRY *pglPopName )(void); +EXTERN void ( APIENTRY *pglPushAttrib )(GLbitfield mask); +EXTERN void ( APIENTRY *pglPushClientAttrib )(GLbitfield mask); +EXTERN void ( APIENTRY *pglPushMatrix )(void); +EXTERN void ( APIENTRY *pglPushName )(GLuint name); +EXTERN void ( APIENTRY *pglRasterPos2d )(GLdouble x, GLdouble y); +EXTERN void ( APIENTRY *pglRasterPos2dv )(const GLdouble *v); +EXTERN void ( APIENTRY *pglRasterPos2f )(GLfloat x, GLfloat y); +EXTERN void ( APIENTRY *pglRasterPos2fv )(const GLfloat *v); +EXTERN void ( APIENTRY *pglRasterPos2i )(GLint x, GLint y); +EXTERN void ( APIENTRY *pglRasterPos2iv )(const GLint *v); +EXTERN void ( APIENTRY *pglRasterPos2s )(GLshort x, GLshort y); +EXTERN void ( APIENTRY *pglRasterPos2sv )(const GLshort *v); +EXTERN void ( APIENTRY *pglRasterPos3d )(GLdouble x, GLdouble y, GLdouble z); +EXTERN void ( APIENTRY *pglRasterPos3dv )(const GLdouble *v); +EXTERN void ( APIENTRY *pglRasterPos3f )(GLfloat x, GLfloat y, GLfloat z); +EXTERN void ( APIENTRY *pglRasterPos3fv )(const GLfloat *v); +EXTERN void ( APIENTRY *pglRasterPos3i )(GLint x, GLint y, GLint z); +EXTERN void ( APIENTRY *pglRasterPos3iv )(const GLint *v); +EXTERN void ( APIENTRY *pglRasterPos3s )(GLshort x, GLshort y, GLshort z); +EXTERN void ( APIENTRY *pglRasterPos3sv )(const GLshort *v); +EXTERN void ( APIENTRY *pglRasterPos4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +EXTERN void ( APIENTRY *pglRasterPos4dv )(const GLdouble *v); +EXTERN void ( APIENTRY *pglRasterPos4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +EXTERN void ( APIENTRY *pglRasterPos4fv )(const GLfloat *v); +EXTERN void ( APIENTRY *pglRasterPos4i )(GLint x, GLint y, GLint z, GLint w); +EXTERN void ( APIENTRY *pglRasterPos4iv )(const GLint *v); +EXTERN void ( APIENTRY *pglRasterPos4s )(GLshort x, GLshort y, GLshort z, GLshort w); +EXTERN void ( APIENTRY *pglRasterPos4sv )(const GLshort *v); +EXTERN void ( APIENTRY *pglReadBuffer )(GLenum mode); +EXTERN void ( APIENTRY *pglReadPixels )(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels); +EXTERN void ( APIENTRY *pglRectd )(GLdouble x1, GLdouble y1, GLdouble x2, GLdouble y2); +EXTERN void ( APIENTRY *pglRectdv )(const GLdouble *v1, const GLdouble *v2); +EXTERN void ( APIENTRY *pglRectf )(GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2); +EXTERN void ( APIENTRY *pglRectfv )(const GLfloat *v1, const GLfloat *v2); +EXTERN void ( APIENTRY *pglRecti )(GLint x1, GLint y1, GLint x2, GLint y2); +EXTERN void ( APIENTRY *pglRectiv )(const GLint *v1, const GLint *v2); +EXTERN void ( APIENTRY *pglRects )(GLshort x1, GLshort y1, GLshort x2, GLshort y2); +EXTERN void ( APIENTRY *pglRectsv )(const GLshort *v1, const GLshort *v2); +EXTERN void ( APIENTRY *pglRotated )(GLdouble angle, GLdouble x, GLdouble y, GLdouble z); +EXTERN void ( APIENTRY *pglRotatef )(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); +EXTERN void ( APIENTRY *pglScaled )(GLdouble x, GLdouble y, GLdouble z); +EXTERN void ( APIENTRY *pglScalef )(GLfloat x, GLfloat y, GLfloat z); +EXTERN void ( APIENTRY *pglScissor )(GLint x, GLint y, GLsizei width, GLsizei height); +EXTERN void ( APIENTRY *pglSelectBuffer )(GLsizei size, GLuint *buffer); +EXTERN void ( APIENTRY *pglShadeModel )(GLenum mode); +EXTERN void ( APIENTRY *pglStencilFunc )(GLenum func, GLint ref, GLuint mask); +EXTERN void ( APIENTRY *pglStencilMask )(GLuint mask); +EXTERN void ( APIENTRY *pglStencilOp )(GLenum fail, GLenum zfail, GLenum zpass); +EXTERN void ( APIENTRY *pglTexCoord1d )(GLdouble s); +EXTERN void ( APIENTRY *pglTexCoord1dv )(const GLdouble *v); +EXTERN void ( APIENTRY *pglTexCoord1f )(GLfloat s); +EXTERN void ( APIENTRY *pglTexCoord1fv )(const GLfloat *v); +EXTERN void ( APIENTRY *pglTexCoord1i )(GLint s); +EXTERN void ( APIENTRY *pglTexCoord1iv )(const GLint *v); +EXTERN void ( APIENTRY *pglTexCoord1s )(GLshort s); +EXTERN void ( APIENTRY *pglTexCoord1sv )(const GLshort *v); +EXTERN void ( APIENTRY *pglTexCoord2d )(GLdouble s, GLdouble t); +EXTERN void ( APIENTRY *pglTexCoord2dv )(const GLdouble *v); +EXTERN void ( APIENTRY *pglTexCoord2f )(GLfloat s, GLfloat t); +EXTERN void ( APIENTRY *pglTexCoord2fv )(const GLfloat *v); +EXTERN void ( APIENTRY *pglTexCoord2i )(GLint s, GLint t); +EXTERN void ( APIENTRY *pglTexCoord2iv )(const GLint *v); +EXTERN void ( APIENTRY *pglTexCoord2s )(GLshort s, GLshort t); +EXTERN void ( APIENTRY *pglTexCoord2sv )(const GLshort *v); +EXTERN void ( APIENTRY *pglTexCoord3d )(GLdouble s, GLdouble t, GLdouble r); +EXTERN void ( APIENTRY *pglTexCoord3dv )(const GLdouble *v); +EXTERN void ( APIENTRY *pglTexCoord3f )(GLfloat s, GLfloat t, GLfloat r); +EXTERN void ( APIENTRY *pglTexCoord3fv )(const GLfloat *v); +EXTERN void ( APIENTRY *pglTexCoord3i )(GLint s, GLint t, GLint r); +EXTERN void ( APIENTRY *pglTexCoord3iv )(const GLint *v); +EXTERN void ( APIENTRY *pglTexCoord3s )(GLshort s, GLshort t, GLshort r); +EXTERN void ( APIENTRY *pglTexCoord3sv )(const GLshort *v); +EXTERN void ( APIENTRY *pglTexCoord4d )(GLdouble s, GLdouble t, GLdouble r, GLdouble q); +EXTERN void ( APIENTRY *pglTexCoord4dv )(const GLdouble *v); +EXTERN void ( APIENTRY *pglTexCoord4f )(GLfloat s, GLfloat t, GLfloat r, GLfloat q); +EXTERN void ( APIENTRY *pglTexCoord4fv )(const GLfloat *v); +EXTERN void ( APIENTRY *pglTexCoord4i )(GLint s, GLint t, GLint r, GLint q); +EXTERN void ( APIENTRY *pglTexCoord4iv )(const GLint *v); +EXTERN void ( APIENTRY *pglTexCoord4s )(GLshort s, GLshort t, GLshort r, GLshort q); +EXTERN void ( APIENTRY *pglTexCoord4sv )(const GLshort *v); +EXTERN void ( APIENTRY *pglTexEnvf )(GLenum target, GLenum pname, GLfloat param); +EXTERN void ( APIENTRY *pglTexEnvfv )(GLenum target, GLenum pname, const GLfloat *params); +EXTERN void ( APIENTRY *pglTexEnvi )(GLenum target, GLenum pname, GLint param); +EXTERN void ( APIENTRY *pglTexEnviv )(GLenum target, GLenum pname, const GLint *params); +EXTERN void ( APIENTRY *pglTexGend )(GLenum coord, GLenum pname, GLdouble param); +EXTERN void ( APIENTRY *pglTexGendv )(GLenum coord, GLenum pname, const GLdouble *params); +EXTERN void ( APIENTRY *pglTexGenf )(GLenum coord, GLenum pname, GLfloat param); +EXTERN void ( APIENTRY *pglTexGenfv )(GLenum coord, GLenum pname, const GLfloat *params); +EXTERN void ( APIENTRY *pglTexGeni )(GLenum coord, GLenum pname, GLint param); +EXTERN void ( APIENTRY *pglTexGeniv )(GLenum coord, GLenum pname, const GLint *params); +EXTERN void ( APIENTRY *pglTexImage1D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +EXTERN void ( APIENTRY *pglTexImage2D )(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels); +EXTERN void ( APIENTRY *pglTexParameterf )(GLenum target, GLenum pname, GLfloat param); +EXTERN void ( APIENTRY *pglTexParameterfv )(GLenum target, GLenum pname, const GLfloat *params); +EXTERN void ( APIENTRY *pglTexParameteri )(GLenum target, GLenum pname, GLint param); +EXTERN void ( APIENTRY *pglTexParameteriv )(GLenum target, GLenum pname, const GLint *params); +EXTERN void ( APIENTRY *pglTexSubImage1D )(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid *pixels); +EXTERN void ( APIENTRY *pglTexSubImage2D )(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *pixels); +EXTERN void ( APIENTRY *pglTranslated )(GLdouble x, GLdouble y, GLdouble z); +EXTERN void ( APIENTRY *pglTranslatef )(GLfloat x, GLfloat y, GLfloat z); +EXTERN void ( APIENTRY *pglVertex2d )(GLdouble x, GLdouble y); +EXTERN void ( APIENTRY *pglVertex2dv )(const GLdouble *v); +EXTERN void ( APIENTRY *pglVertex2f )(GLfloat x, GLfloat y); +EXTERN void ( APIENTRY *pglVertex2fv )(const GLfloat *v); +EXTERN void ( APIENTRY *pglVertex2i )(GLint x, GLint y); +EXTERN void ( APIENTRY *pglVertex2iv )(const GLint *v); +EXTERN void ( APIENTRY *pglVertex2s )(GLshort x, GLshort y); +EXTERN void ( APIENTRY *pglVertex2sv )(const GLshort *v); +EXTERN void ( APIENTRY *pglVertex3d )(GLdouble x, GLdouble y, GLdouble z); +EXTERN void ( APIENTRY *pglVertex3dv )(const GLdouble *v); +EXTERN void ( APIENTRY *pglVertex3f )(GLfloat x, GLfloat y, GLfloat z); +EXTERN void ( APIENTRY *pglVertex3fv )(const GLfloat *v); +EXTERN void ( APIENTRY *pglVertex3i )(GLint x, GLint y, GLint z); +EXTERN void ( APIENTRY *pglVertex3iv )(const GLint *v); +EXTERN void ( APIENTRY *pglVertex3s )(GLshort x, GLshort y, GLshort z); +EXTERN void ( APIENTRY *pglVertex3sv )(const GLshort *v); +EXTERN void ( APIENTRY *pglVertex4d )(GLdouble x, GLdouble y, GLdouble z, GLdouble w); +EXTERN void ( APIENTRY *pglVertex4dv )(const GLdouble *v); +EXTERN void ( APIENTRY *pglVertex4f )(GLfloat x, GLfloat y, GLfloat z, GLfloat w); +EXTERN void ( APIENTRY *pglVertex4fv )(const GLfloat *v); +EXTERN void ( APIENTRY *pglVertex4i )(GLint x, GLint y, GLint z, GLint w); +EXTERN void ( APIENTRY *pglVertex4iv )(const GLint *v); +EXTERN void ( APIENTRY *pglVertex4s )(GLshort x, GLshort y, GLshort z, GLshort w); +EXTERN void ( APIENTRY *pglVertex4sv )(const GLshort *v); +EXTERN void ( APIENTRY *pglViewport )(GLint x, GLint y, GLsizei width, GLsizei height); +EXTERN void ( APIENTRY *pglPointParameterfEXT)( GLenum param, GLfloat value ); +EXTERN void ( APIENTRY *pglPointParameterfvEXT)( GLenum param, const GLfloat *value ); +EXTERN void ( APIENTRY *pglLockArraysEXT) (int , int); +EXTERN void ( APIENTRY *pglUnlockArraysEXT) (void); +EXTERN void ( APIENTRY *pglActiveTextureARB)( GLenum ); +EXTERN void ( APIENTRY *pglClientActiveTextureARB)( GLenum ); +EXTERN void ( APIENTRY *pglGetCompressedTexImage)( GLenum target, GLint lod, const void* data ); +EXTERN void ( APIENTRY *pglDrawRangeElements)( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices ); +EXTERN void ( APIENTRY *pglDrawRangeElementsEXT)( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void *indices ); +EXTERN void ( APIENTRY *pglDrawElements)(GLenum mode, GLsizei count, GLenum type, const void *indices); +EXTERN void ( APIENTRY *pglVertexPointer)(GLint size, GLenum type, GLsizei stride, const void *ptr); +EXTERN void ( APIENTRY *pglNormalPointer)(GLenum type, GLsizei stride, const void *ptr); +EXTERN void ( APIENTRY *pglColorPointer)(GLint size, GLenum type, GLsizei stride, const void *ptr); +EXTERN void ( APIENTRY *pglTexCoordPointer)(GLint size, GLenum type, GLsizei stride, const void *ptr); +EXTERN void ( APIENTRY *pglArrayElement)(GLint i); +EXTERN void ( APIENTRY *pglMultiTexCoord1f) (GLenum, GLfloat); +EXTERN void ( APIENTRY *pglMultiTexCoord2f) (GLenum, GLfloat, GLfloat); +EXTERN void ( APIENTRY *pglMultiTexCoord3f) (GLenum, GLfloat, GLfloat, GLfloat); +EXTERN void ( APIENTRY *pglMultiTexCoord4f) (GLenum, GLfloat, GLfloat, GLfloat, GLfloat); +EXTERN void ( APIENTRY *pglActiveTexture) (GLenum); +EXTERN void ( APIENTRY *pglClientActiveTexture) (GLenum); +EXTERN void ( APIENTRY *pglCompressedTexImage3DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void *data); +EXTERN void ( APIENTRY *pglCompressedTexImage2DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void *data); +EXTERN void ( APIENTRY *pglCompressedTexImage1DARB)(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void *data); +EXTERN void ( APIENTRY *pglCompressedTexSubImage3DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void *data); +EXTERN void ( APIENTRY *pglCompressedTexSubImage2DARB)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *data); +EXTERN void ( APIENTRY *pglCompressedTexSubImage1DARB)(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void *data); +EXTERN void ( APIENTRY *pglDeleteObjectARB)(GLhandleARB obj); +EXTERN GLhandleARB ( APIENTRY *pglGetHandleARB)(GLenum pname); +EXTERN void ( APIENTRY *pglDetachObjectARB)(GLhandleARB containerObj, GLhandleARB attachedObj); +EXTERN GLhandleARB ( APIENTRY *pglCreateShaderObjectARB)(GLenum shaderType); +EXTERN void ( APIENTRY *pglShaderSourceARB)(GLhandleARB shaderObj, GLsizei count, const GLcharARB **string, const GLint *length); +EXTERN void ( APIENTRY *pglCompileShaderARB)(GLhandleARB shaderObj); +EXTERN GLhandleARB ( APIENTRY *pglCreateProgramObjectARB)(void); +EXTERN void ( APIENTRY *pglAttachObjectARB)(GLhandleARB containerObj, GLhandleARB obj); +EXTERN void ( APIENTRY *pglLinkProgramARB)(GLhandleARB programObj); +EXTERN void ( APIENTRY *pglUseProgramObjectARB)(GLhandleARB programObj); +EXTERN void ( APIENTRY *pglValidateProgramARB)(GLhandleARB programObj); +EXTERN void ( APIENTRY *pglBindProgramARB)(GLenum target, GLuint program); +EXTERN void ( APIENTRY *pglDeleteProgramsARB)(GLsizei n, const GLuint *programs); +EXTERN void ( APIENTRY *pglGenProgramsARB)(GLsizei n, GLuint *programs); +EXTERN void ( APIENTRY *pglProgramStringARB)(GLenum target, GLenum format, GLsizei len, const void *string); +EXTERN void ( APIENTRY *pglProgramEnvParameter4fARB)(GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +EXTERN void ( APIENTRY *pglProgramLocalParameter4fARB)(GLenum target, GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w); +EXTERN void ( APIENTRY *pglGetProgramivARB)( GLenum target, GLenum pname, GLint *params ); +EXTERN void ( APIENTRY *pglUniform1fARB)(GLint location, GLfloat v0); +EXTERN void ( APIENTRY *pglUniform2fARB)(GLint location, GLfloat v0, GLfloat v1); +EXTERN void ( APIENTRY *pglUniform3fARB)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2); +EXTERN void ( APIENTRY *pglUniform4fARB)(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); +EXTERN void ( APIENTRY *pglUniform1iARB)(GLint location, GLint v0); +EXTERN void ( APIENTRY *pglUniform2iARB)(GLint location, GLint v0, GLint v1); +EXTERN void ( APIENTRY *pglUniform3iARB)(GLint location, GLint v0, GLint v1, GLint v2); +EXTERN void ( APIENTRY *pglUniform4iARB)(GLint location, GLint v0, GLint v1, GLint v2, GLint v3); +EXTERN void ( APIENTRY *pglUniform1fvARB)(GLint location, GLsizei count, const GLfloat *value); +EXTERN void ( APIENTRY *pglUniform2fvARB)(GLint location, GLsizei count, const GLfloat *value); +EXTERN void ( APIENTRY *pglUniform3fvARB)(GLint location, GLsizei count, const GLfloat *value); +EXTERN void ( APIENTRY *pglUniform4fvARB)(GLint location, GLsizei count, const GLfloat *value); +EXTERN void ( APIENTRY *pglUniform1ivARB)(GLint location, GLsizei count, const GLint *value); +EXTERN void ( APIENTRY *pglUniform2ivARB)(GLint location, GLsizei count, const GLint *value); +EXTERN void ( APIENTRY *pglUniform3ivARB)(GLint location, GLsizei count, const GLint *value); +EXTERN void ( APIENTRY *pglUniform4ivARB)(GLint location, GLsizei count, const GLint *value); +EXTERN void ( APIENTRY *pglUniformMatrix2fvARB)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +EXTERN void ( APIENTRY *pglUniformMatrix3fvARB)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +EXTERN void ( APIENTRY *pglUniformMatrix4fvARB)(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value); +EXTERN void ( APIENTRY *pglGetObjectParameterfvARB)(GLhandleARB obj, GLenum pname, GLfloat *params); +EXTERN void ( APIENTRY *pglGetObjectParameterivARB)(GLhandleARB obj, GLenum pname, GLint *params); +EXTERN void ( APIENTRY *pglGetInfoLogARB)(GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *infoLog); +EXTERN void ( APIENTRY *pglGetAttachedObjectsARB)(GLhandleARB containerObj, GLsizei maxCount, GLsizei *count, GLhandleARB *obj); +EXTERN GLint ( APIENTRY *pglGetUniformLocationARB)(GLhandleARB programObj, const GLcharARB *name); +EXTERN void ( APIENTRY *pglGetActiveUniformARB)(GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +EXTERN void ( APIENTRY *pglGetUniformfvARB)(GLhandleARB programObj, GLint location, GLfloat *params); +EXTERN void ( APIENTRY *pglGetUniformivARB)(GLhandleARB programObj, GLint location, GLint *params); +EXTERN void ( APIENTRY *pglGetShaderSourceARB)(GLhandleARB obj, GLsizei maxLength, GLsizei *length, GLcharARB *source); +EXTERN void ( APIENTRY *pglTexImage3D)( GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void *pixels ); +EXTERN void ( APIENTRY *pglTexSubImage3D)( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void *pixels ); +EXTERN void ( APIENTRY *pglCopyTexSubImage3D)( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height ); +EXTERN void ( APIENTRY *pglBlendEquationEXT)(GLenum); +EXTERN void ( APIENTRY *pglStencilOpSeparate)(GLenum, GLenum, GLenum, GLenum); +EXTERN void ( APIENTRY *pglStencilFuncSeparate)(GLenum, GLenum, GLint, GLuint); +EXTERN void ( APIENTRY *pglActiveStencilFaceEXT)(GLenum); +EXTERN void ( APIENTRY *pglVertexAttribPointerARB)(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *pointer); +EXTERN void ( APIENTRY *pglEnableVertexAttribArrayARB)(GLuint index); +EXTERN void ( APIENTRY *pglDisableVertexAttribArrayARB)(GLuint index); +EXTERN void ( APIENTRY *pglBindAttribLocationARB)(GLhandleARB programObj, GLuint index, const GLcharARB *name); +EXTERN void ( APIENTRY *pglGetActiveAttribARB)(GLhandleARB programObj, GLuint index, GLsizei maxLength, GLsizei *length, GLint *size, GLenum *type, GLcharARB *name); +EXTERN GLint ( APIENTRY *pglGetAttribLocationARB)(GLhandleARB programObj, const GLcharARB *name); +EXTERN void ( APIENTRY *pglBindFragDataLocation)(GLuint programObj, GLuint index, const GLcharARB *name); +EXTERN void ( APIENTRY *pglVertexAttrib2fARB)( GLuint index, GLfloat x, GLfloat y ); +EXTERN void ( APIENTRY *pglVertexAttrib2fvARB)( GLuint index, const GLfloat *v ); +EXTERN void ( APIENTRY *pglVertexAttrib3fvARB)( GLuint index, const GLfloat *v ); +EXTERN void ( APIENTRY *pglBindBufferARB) (GLenum target, GLuint buffer); +EXTERN void ( APIENTRY *pglDeleteBuffersARB) (GLsizei n, const GLuint *buffers); +EXTERN void ( APIENTRY *pglGenBuffersARB) (GLsizei n, GLuint *buffers); +EXTERN GLboolean ( APIENTRY *pglIsBufferARB) (GLuint buffer); +EXTERN void* ( APIENTRY *pglMapBufferARB) (GLenum target, GLenum access); +EXTERN GLboolean ( APIENTRY *pglUnmapBufferARB) (GLenum target); +EXTERN void ( APIENTRY *pglBufferDataARB) (GLenum target, GLsizeiptrARB size, const void *data, GLenum usage); +EXTERN void ( APIENTRY *pglBufferSubDataARB) (GLenum target, GLintptrARB offset, GLsizeiptrARB size, const void *data); +EXTERN void ( APIENTRY *pglGenQueriesARB) (GLsizei n, GLuint *ids); +EXTERN void ( APIENTRY *pglDeleteQueriesARB) (GLsizei n, const GLuint *ids); +EXTERN GLboolean ( APIENTRY *pglIsQueryARB) (GLuint id); +EXTERN void ( APIENTRY *pglBeginQueryARB) (GLenum target, GLuint id); +EXTERN void ( APIENTRY *pglEndQueryARB) (GLenum target); +EXTERN void ( APIENTRY *pglGetQueryivARB) (GLenum target, GLenum pname, GLint *params); +EXTERN void ( APIENTRY *pglGetQueryObjectivARB) (GLuint id, GLenum pname, GLint *params); +EXTERN void ( APIENTRY *pglGetQueryObjectuivARB) (GLuint id, GLenum pname, GLuint *params); +EXTERN void ( APIENTRY * pglSelectTextureSGIS )( GLenum ); +EXTERN void ( APIENTRY * pglMTexCoord2fSGIS )( GLenum, GLfloat, GLfloat ); +EXTERN void ( APIENTRY * pglSwapInterval )( int interval ); +EXTERN GLboolean ( APIENTRY *pglIsRenderbuffer )(GLuint renderbuffer); +EXTERN void ( APIENTRY *pglBindRenderbuffer )(GLenum target, GLuint renderbuffer); +EXTERN void ( APIENTRY *pglDeleteRenderbuffers )(GLsizei n, const GLuint *renderbuffers); +EXTERN void ( APIENTRY *pglGenRenderbuffers )(GLsizei n, GLuint *renderbuffers); +EXTERN void ( APIENTRY *pglRenderbufferStorage )(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); +EXTERN void ( APIENTRY *pglRenderbufferStorageMultisample )(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height); +EXTERN void ( APIENTRY *pglGetRenderbufferParameteriv )(GLenum target, GLenum pname, GLint *params); +EXTERN GLboolean (APIENTRY *pglIsFramebuffer )(GLuint framebuffer); +EXTERN void ( APIENTRY *pglBindFramebuffer )(GLenum target, GLuint framebuffer); +EXTERN void ( APIENTRY *pglDeleteFramebuffers )(GLsizei n, const GLuint *framebuffers); +EXTERN void ( APIENTRY *pglGenFramebuffers )(GLsizei n, GLuint *framebuffers); +EXTERN GLenum ( APIENTRY *pglCheckFramebufferStatus )(GLenum target); +EXTERN void ( APIENTRY *pglFramebufferTexture1D )(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +EXTERN void ( APIENTRY *pglFramebufferTexture2D )(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +EXTERN void ( APIENTRY *pglFramebufferTexture3D )(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint layer); +EXTERN void ( APIENTRY *pglFramebufferTextureLayer )(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer); +EXTERN void ( APIENTRY *pglFramebufferRenderbuffer )(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +EXTERN void ( APIENTRY *pglGetFramebufferAttachmentParameteriv )(GLenum target, GLenum attachment, GLenum pname, GLint *params); +EXTERN void ( APIENTRY *pglBlitFramebuffer )(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLbitfield mask, GLenum filter); +EXTERN void ( APIENTRY *pglCopyImageSubData )( GLuint srcName, GLenum srcTarget, GLint srcLevel, GLint srcX, GLint srcY, GLint srcZ, GLuint dstName, GLenum dstTarget, GLint dstLevel, GLint dstX, GLint dstY, GLint dstZ, GLsizei srcWidth, GLsizei srcHeight, GLsizei srcDepth ); +EXTERN void ( APIENTRY *pglCombinerParameterfvNV )( GLenum, const GLfloat * ); +EXTERN void ( APIENTRY *pglCombinerParameterfNV )( GLenum, GLfloat ); +EXTERN void ( APIENTRY *pglCombinerParameterivNV )(GLenum, const GLint * ); +EXTERN void ( APIENTRY *pglCombinerParameteriNV )( GLenum, GLint ); +EXTERN void ( APIENTRY *pglCombinerInputNV )( GLenum, GLenum, GLenum, GLenum, GLenum, GLenum ); +EXTERN void ( APIENTRY *pglCombinerOutputNV )( GLenum, GLenum, GLenum, GLenum, GLenum, GLenum, GLenum, GLboolean, GLboolean, GLboolean ); +EXTERN void ( APIENTRY *pglFinalCombinerInputNV )( GLenum, GLenum, GLenum, GLenum ); +EXTERN void ( APIENTRY *pglGetCombinerInputParameterfvNV )( GLenum, GLenum, GLenum, GLenum, GLfloat * ); +EXTERN void ( APIENTRY *pglGetCombinerInputParameterivNV )( GLenum, GLenum, GLenum, GLenum, GLint * ); +EXTERN void ( APIENTRY *pglGetCombinerOutputParameterfvNV )( GLenum, GLenum, GLenum, GLfloat * ); +EXTERN void ( APIENTRY *pglGetCombinerOutputParameterivNV )( GLenum, GLenum, GLenum, GLint * ); +EXTERN void ( APIENTRY *pglGetFinalCombinerInputParameterfvNV )( GLenum, GLenum, GLfloat * ); +EXTERN void ( APIENTRY *pglGetFinalCombinerInputParameterivNV )( GLenum, GLenum, GLint * ); +EXTERN void ( APIENTRY *pglGenerateMipmap )( GLenum target ); +EXTERN void ( APIENTRY *pglDrawBuffersARB)( GLsizei n, const GLenum *bufs ); +EXTERN BOOL ( WINAPI * pwglSwapBuffers)(HDC); +EXTERN BOOL ( WINAPI * pwglCopyContext)(HGLRC, HGLRC, UINT); +EXTERN HGLRC ( WINAPI * pwglCreateContext)(HDC); +EXTERN HGLRC ( WINAPI * pwglCreateLayerContext)(HDC, int); +EXTERN BOOL ( WINAPI * pwglDeleteContext)(HGLRC); +EXTERN HGLRC ( WINAPI * pwglGetCurrentContext)(VOID); +EXTERN PROC ( WINAPI * pwglGetProcAddress)(LPCSTR); +EXTERN BOOL ( WINAPI * pwglMakeCurrent)(HDC, HGLRC); +EXTERN BOOL ( WINAPI * pwglShareLists)(HGLRC, HGLRC); +EXTERN BOOL ( WINAPI * pwglUseFontBitmaps)(HDC, DWORD, DWORD, DWORD); +EXTERN BOOL ( WINAPI * pwglUseFontOutlines)(HDC, DWORD, DWORD, DWORD, FLOAT, FLOAT, int, LPGLYPHMETRICSFLOAT); +EXTERN BOOL ( WINAPI * pwglDescribeLayerPlane)(HDC, int, int, UINT, LPLAYERPLANEDESCRIPTOR); +EXTERN int ( WINAPI * pwglSetLayerPaletteEntries)(HDC, int, int, int, CONST COLORREF *); +EXTERN int ( WINAPI * pwglGetLayerPaletteEntries)(HDC, int, int, int, COLORREF *); +EXTERN BOOL ( WINAPI * pwglRealizeLayerPalette)(HDC, int, BOOL); +EXTERN BOOL ( WINAPI * pwglSwapLayerBuffers)(HDC, UINT); +EXTERN BOOL ( WINAPI * pwglSwapIntervalEXT)( int interval ); +EXTERN HGLRC ( WINAPI * pwglCreateContextAttribsARB)( HDC hDC, HGLRC hShareContext, const int *attribList ); +typedef HGLRC (WINAPI * PFNWGLCREATECONTEXTATTRIBS)( HDC, HGLRC, const int * ); + +EXTERN void ( APIENTRY *pglBindVertexArray )( GLuint array ); +EXTERN void ( APIENTRY *pglDeleteVertexArrays )( GLsizei n, const GLuint *arrays ); +EXTERN void ( APIENTRY *pglGenVertexArrays )( GLsizei n, const GLuint *arrays ); +EXTERN GLboolean ( APIENTRY *pglIsVertexArray )( GLuint array ); + +typedef void ( APIENTRY *pglDebugProcARB)( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLcharARB* message, GLvoid* userParam ); +EXTERN void ( APIENTRY *pglDebugMessageControlARB)( GLenum source, GLenum type, GLenum severity, GLsizei count, const GLuint* ids, GLboolean enabled ); +EXTERN void ( APIENTRY *pglDebugMessageInsertARB)( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const char* buf ); +EXTERN void ( APIENTRY *pglDebugMessageCallbackARB)( pglDebugProcARB callback, void* userParam ); +EXTERN GLuint ( APIENTRY *pglGetDebugMessageLogARB)( GLuint count, GLsizei bufsize, GLenum* sources, GLenum* types, GLuint* ids, GLuint* severities, GLsizei* lengths, char* messageLog ); + +#endif//GL_EXPORT_H \ No newline at end of file diff --git a/utils/reqtest/reqtest.cpp b/utils/reqtest/reqtest.cpp new file mode 100644 index 0000000..055a40e --- /dev/null +++ b/utils/reqtest/reqtest.cpp @@ -0,0 +1,1310 @@ +/* +texrep.cpp - replace internal bsp textures with external textures (from specified wad) +Copyright (C) 2012 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#define EXTERN + +#include "gl_export.h" +#include "conprint.h" +#include +#include +#include +#include +#include +#include + +#define bound( min, num, max ) ((num) >= (min) ? ((num) < (max) ? (num) : (max)) : (min)) + +#define MIN_SHADER_UNIFOMS 1024 // GLSL spec says that at least 1024 uniform is allowed on sm 2.0 +#define MAX_RESERVED_UNIFORMS 256 // while MAX_LIGHTSTYLES 64 +#define WINDOW_STYLE (WS_OVERLAPPED|WS_BORDER|WS_SYSMENU|WS_CAPTION) +#define WINDOW_EX_STYLE (0) +#define WINDOW_NAME "Xash Window" // Half-Life + +typedef HMODULE dllhandle_t; + +typedef struct dllfunc_s +{ + const char *name; + void **func; +} dllfunc_t; + +typedef enum +{ + rserr_ok, + rserr_invalid_fullscreen, + rserr_invalid_mode, + rserr_unknown +} rserr_t; + +void *Sys_GetProcAddress( dllhandle_t handle, const char *name ) +{ + return (void *)GetProcAddress( handle, name ); +} + +void Sys_FreeLibrary( dllhandle_t *handle ) +{ + if( !handle || !*handle ) + return; + + FreeLibrary( *handle ); + *handle = NULL; +} + +bool Sys_LoadLibrary( const char* dllname, dllhandle_t* handle, const dllfunc_t *fcts ) +{ + const dllfunc_t *gamefunc; + dllhandle_t dllhandle; + + if( !handle ) return false; + + // Initializations + for( gamefunc = fcts; gamefunc && gamefunc->name != NULL; gamefunc++ ) + *gamefunc->func = NULL; + + dllhandle = LoadLibrary( dllname ); + + // No DLL found + if( !dllhandle ) return false; + + // Get the function adresses + for( gamefunc = fcts; gamefunc && gamefunc->name != NULL; gamefunc++ ) + { + if( !( *gamefunc->func = (void *)Sys_GetProcAddress( dllhandle, gamefunc->name ))) + { + Sys_FreeLibrary( &dllhandle ); + return false; + } + } + + *handle = dllhandle; + + return true; +} + +/* +======================================================================= + + GL STATE MACHINE + +======================================================================= +*/ +enum +{ + R_OPENGL_110 = 0, // base + R_WGL_PROCADDRESS, + R_ARB_VERTEX_BUFFER_OBJECT_EXT, + R_ARB_VERTEX_ARRAY_OBJECT_EXT, + R_EXT_GPU_SHADER4, // shaders only + R_ARB_MULTITEXTURE, + R_TEXTURECUBEMAP_EXT, + R_SHADER_GLSL100_EXT, + R_DRAW_RANGEELEMENTS_EXT, + R_TEXTURE_3D_EXT, + R_SHADER_OBJECTS_EXT, + R_VERTEX_SHADER_EXT, // glsl vertex program + R_FRAGMENT_SHADER_EXT, // glsl fragment program + R_ARB_TEXTURE_NPOT_EXT, + R_TEXTURE_ARRAY_EXT, + R_DEPTH_TEXTURE, + R_SHADOW_EXT, + R_FRAMEBUFFER_OBJECT, + R_PARANOIA_EXT, // custom OpenGL32.dll with hacked function glDepthRange + R_DEBUG_OUTPUT, + R_ARB_TEXTURE_FLOAT_EXT, + R_DRAW_BUFFERS_EXT, + R_ARB_HALF_FLOAT_EXT, + R_EXTCOUNT, // must be last +}; + +typedef struct +{ + const char *renderer_string; // ptrs to OpenGL32.dll, use with caution + const char *version_string; + const char *vendor_string; + + // list of supported extensions + const char *extensions_string; + bool extension[R_EXTCOUNT]; + + int block_size; // lightmap blocksize + + int max_texture_units; + int max_texture_coords; + int max_teximage_units; + GLint max_2d_texture_size; + GLint max_2d_texture_layers; + GLint max_2d_rectangle_size; + GLint max_3d_texture_size; + GLint max_cubemap_size; + + int color_bits; + int alpha_bits; + int depth_bits; + int stencil_bits; + + int max_vertex_uniforms; + int max_vertex_attribs; + int max_skinning_bones; // total bones that can be transformed with GLSL +} glConfig_t; + +typedef struct +{ + HWND hWnd; + HDC hDC; // handle to device context + HGLRC hGLRC; // handle to GL rendering context + HINSTANCE hInst; + + int desktopBitsPixel; + int desktopWidth; + int desktopHeight; + + bool software; // OpenGL software emulation + bool initialized; // OpenGL subsystem started +} glwstate_t; + +glwstate_t glw_state; +glConfig_t glConfig; + +static dllfunc_t opengl_110funcs[] = +{ +{ "glClearColor" , (void **)&pglClearColor }, +{ "glClear" , (void **)&pglClear }, +{ "glAlphaFunc" , (void **)&pglAlphaFunc }, +{ "glBlendFunc" , (void **)&pglBlendFunc }, +{ "glCullFace" , (void **)&pglCullFace }, +{ "glDrawBuffer" , (void **)&pglDrawBuffer }, +{ "glReadBuffer" , (void **)&pglReadBuffer }, +{ "glAccum" , (void **)&pglAccum }, +{ "glEnable" , (void **)&pglEnable }, +{ "glDisable" , (void **)&pglDisable }, +{ "glEnableClientState" , (void **)&pglEnableClientState }, +{ "glDisableClientState" , (void **)&pglDisableClientState }, +{ "glGetBooleanv" , (void **)&pglGetBooleanv }, +{ "glGetDoublev" , (void **)&pglGetDoublev }, +{ "glGetFloatv" , (void **)&pglGetFloatv }, +{ "glGetIntegerv" , (void **)&pglGetIntegerv }, +{ "glGetError" , (void **)&pglGetError }, +{ "glGetString" , (void **)&pglGetString }, +{ "glFinish" , (void **)&pglFinish }, +{ "glFlush" , (void **)&pglFlush }, +{ "glClearDepth" , (void **)&pglClearDepth }, +{ "glDepthFunc" , (void **)&pglDepthFunc }, +{ "glDepthMask" , (void **)&pglDepthMask }, +{ "glDepthRange" , (void **)&pglDepthRange }, +{ "glFrontFace" , (void **)&pglFrontFace }, +{ "glDrawElements" , (void **)&pglDrawElements }, +{ "glColorMask" , (void **)&pglColorMask }, +{ "glIndexPointer" , (void **)&pglIndexPointer }, +{ "glVertexPointer" , (void **)&pglVertexPointer }, +{ "glNormalPointer" , (void **)&pglNormalPointer }, +{ "glColorPointer" , (void **)&pglColorPointer }, +{ "glTexCoordPointer" , (void **)&pglTexCoordPointer }, +{ "glArrayElement" , (void **)&pglArrayElement }, +{ "glColor3f" , (void **)&pglColor3f }, +{ "glColor3fv" , (void **)&pglColor3fv }, +{ "glColor4f" , (void **)&pglColor4f }, +{ "glColor4fv" , (void **)&pglColor4fv }, +{ "glColor3ub" , (void **)&pglColor3ub }, +{ "glColor4ub" , (void **)&pglColor4ub }, +{ "glColor4ubv" , (void **)&pglColor4ubv }, +{ "glTexCoord1f" , (void **)&pglTexCoord1f }, +{ "glTexCoord2f" , (void **)&pglTexCoord2f }, +{ "glTexCoord3f" , (void **)&pglTexCoord3f }, +{ "glTexCoord4f" , (void **)&pglTexCoord4f }, +{ "glTexCoord1fv" , (void **)&pglTexCoord1fv }, +{ "glTexCoord2fv" , (void **)&pglTexCoord2fv }, +{ "glTexCoord3fv" , (void **)&pglTexCoord3fv }, +{ "glTexCoord4fv" , (void **)&pglTexCoord4fv }, +{ "glTexGenf" , (void **)&pglTexGenf }, +{ "glTexGenfv" , (void **)&pglTexGenfv }, +{ "glTexGeni" , (void **)&pglTexGeni }, +{ "glVertex2f" , (void **)&pglVertex2f }, +{ "glVertex3f" , (void **)&pglVertex3f }, +{ "glVertex3fv" , (void **)&pglVertex3fv }, +{ "glNormal3f" , (void **)&pglNormal3f }, +{ "glNormal3fv" , (void **)&pglNormal3fv }, +{ "glBegin" , (void **)&pglBegin }, +{ "glEnd" , (void **)&pglEnd }, +{ "glLineWidth" , (void**)&pglLineWidth }, +{ "glPointSize" , (void**)&pglPointSize }, +{ "glMatrixMode" , (void **)&pglMatrixMode }, +{ "glOrtho" , (void **)&pglOrtho }, +{ "glRasterPos2f" , (void **) &pglRasterPos2f }, +{ "glFrustum" , (void **)&pglFrustum }, +{ "glViewport" , (void **)&pglViewport }, +{ "glPushMatrix" , (void **)&pglPushMatrix }, +{ "glPopMatrix" , (void **)&pglPopMatrix }, +{ "glPushAttrib" , (void **)&pglPushAttrib }, +{ "glPopAttrib" , (void **)&pglPopAttrib }, +{ "glLoadIdentity" , (void **)&pglLoadIdentity }, +{ "glLoadMatrixd" , (void **)&pglLoadMatrixd }, +{ "glLoadMatrixf" , (void **)&pglLoadMatrixf }, +{ "glMultMatrixd" , (void **)&pglMultMatrixd }, +{ "glMultMatrixf" , (void **)&pglMultMatrixf }, +{ "glRotated" , (void **)&pglRotated }, +{ "glRotatef" , (void **)&pglRotatef }, +{ "glScaled" , (void **)&pglScaled }, +{ "glScalef" , (void **)&pglScalef }, +{ "glTranslated" , (void **)&pglTranslated }, +{ "glTranslatef" , (void **)&pglTranslatef }, +{ "glReadPixels" , (void **)&pglReadPixels }, +{ "glDrawPixels" , (void **)&pglDrawPixels }, +{ "glStencilFunc" , (void **)&pglStencilFunc }, +{ "glStencilMask" , (void **)&pglStencilMask }, +{ "glStencilOp" , (void **)&pglStencilOp }, +{ "glClearStencil" , (void **)&pglClearStencil }, +{ "glIsEnabled" , (void **)&pglIsEnabled }, +{ "glIsList" , (void **)&pglIsList }, +{ "glIsTexture" , (void **)&pglIsTexture }, +{ "glTexEnvf" , (void **)&pglTexEnvf }, +{ "glTexEnvfv" , (void **)&pglTexEnvfv }, +{ "glTexEnvi" , (void **)&pglTexEnvi }, +{ "glTexParameterf" , (void **)&pglTexParameterf }, +{ "glTexParameterfv" , (void **)&pglTexParameterfv }, +{ "glTexParameteri" , (void **)&pglTexParameteri }, +{ "glHint" , (void **)&pglHint }, +{ "glPixelStoref" , (void **)&pglPixelStoref }, +{ "glPixelStorei" , (void **)&pglPixelStorei }, +{ "glGenTextures" , (void **)&pglGenTextures }, +{ "glDeleteTextures" , (void **)&pglDeleteTextures }, +{ "glBindTexture" , (void **)&pglBindTexture }, +{ "glTexImage1D" , (void **)&pglTexImage1D }, +{ "glTexImage2D" , (void **)&pglTexImage2D }, +{ "glTexSubImage1D" , (void **)&pglTexSubImage1D }, +{ "glTexSubImage2D" , (void **)&pglTexSubImage2D }, +{ "glCopyTexImage1D" , (void **)&pglCopyTexImage1D }, +{ "glCopyTexImage2D" , (void **)&pglCopyTexImage2D }, +{ "glCopyTexSubImage1D" , (void **)&pglCopyTexSubImage1D }, +{ "glCopyTexSubImage2D" , (void **)&pglCopyTexSubImage2D }, +{ "glScissor" , (void **)&pglScissor }, +{ "glGetTexImage" , (void **)&pglGetTexImage }, +{ "glGetTexEnviv" , (void **)&pglGetTexEnviv }, +{ "glPolygonOffset" , (void **)&pglPolygonOffset }, +{ "glPolygonMode" , (void **)&pglPolygonMode }, +{ "glPolygonStipple" , (void **)&pglPolygonStipple }, +{ "glClipPlane" , (void **)&pglClipPlane }, +{ "glGetClipPlane" , (void **)&pglGetClipPlane }, +{ "glShadeModel" , (void **)&pglShadeModel }, +{ "glGetTexLevelParameteriv" , (void **)&pglGetTexLevelParameteriv }, +{ "glGetTexLevelParameterfv" , (void **)&pglGetTexLevelParameterfv }, +{ "glFogfv" , (void **)&pglFogfv }, +{ "glFogf" , (void **)&pglFogf }, +{ "glFogi" , (void **)&pglFogi }, +{ NULL, NULL } +}; + +static dllfunc_t drawrangeelementsfuncs[] = +{ +{ "glDrawRangeElements" , (void **)&pglDrawRangeElements }, +{ NULL, NULL } +}; + +static dllfunc_t drawrangeelementsextfuncs[] = +{ +{ "glDrawRangeElementsEXT" , (void **)&pglDrawRangeElementsEXT }, +{ NULL, NULL } +}; + +static dllfunc_t debugoutputfuncs[] = +{ +{ "glDebugMessageControlARB" , (void **)&pglDebugMessageControlARB }, +{ "glDebugMessageInsertARB" , (void **)&pglDebugMessageInsertARB }, +{ "glDebugMessageCallbackARB" , (void **)&pglDebugMessageCallbackARB }, +{ "glGetDebugMessageLogARB" , (void **)&pglGetDebugMessageLogARB }, +{ NULL, NULL } +}; + +static dllfunc_t multitexturefuncs[] = +{ +{ "glMultiTexCoord1fARB" , (void **)&pglMultiTexCoord1f }, +{ "glMultiTexCoord2fARB" , (void **)&pglMultiTexCoord2f }, +{ "glMultiTexCoord3fARB" , (void **)&pglMultiTexCoord3f }, +{ "glMultiTexCoord4fARB" , (void **)&pglMultiTexCoord4f }, +{ "glActiveTextureARB" , (void **)&pglActiveTexture }, +{ "glActiveTextureARB" , (void **)&pglActiveTextureARB }, +{ "glClientActiveTextureARB" , (void **)&pglClientActiveTexture }, +{ "glClientActiveTextureARB" , (void **)&pglClientActiveTextureARB }, +{ NULL, NULL } +}; + +static dllfunc_t texture3dextfuncs[] = +{ +{ "glTexImage3DEXT" , (void **)&pglTexImage3D }, +{ "glTexSubImage3DEXT" , (void **)&pglTexSubImage3D }, +{ "glCopyTexSubImage3DEXT" , (void **)&pglCopyTexSubImage3D }, +{ NULL, NULL } +}; + +static dllfunc_t shaderobjectsfuncs[] = +{ +{ "glDeleteObjectARB" , (void **)&pglDeleteObjectARB }, +{ "glGetHandleARB" , (void **)&pglGetHandleARB }, +{ "glDetachObjectARB" , (void **)&pglDetachObjectARB }, +{ "glCreateShaderObjectARB" , (void **)&pglCreateShaderObjectARB }, +{ "glShaderSourceARB" , (void **)&pglShaderSourceARB }, +{ "glCompileShaderARB" , (void **)&pglCompileShaderARB }, +{ "glCreateProgramObjectARB" , (void **)&pglCreateProgramObjectARB }, +{ "glAttachObjectARB" , (void **)&pglAttachObjectARB }, +{ "glLinkProgramARB" , (void **)&pglLinkProgramARB }, +{ "glUseProgramObjectARB" , (void **)&pglUseProgramObjectARB }, +{ "glValidateProgramARB" , (void **)&pglValidateProgramARB }, +{ "glUniform1fARB" , (void **)&pglUniform1fARB }, +{ "glUniform2fARB" , (void **)&pglUniform2fARB }, +{ "glUniform3fARB" , (void **)&pglUniform3fARB }, +{ "glUniform4fARB" , (void **)&pglUniform4fARB }, +{ "glUniform1iARB" , (void **)&pglUniform1iARB }, +{ "glUniform2iARB" , (void **)&pglUniform2iARB }, +{ "glUniform3iARB" , (void **)&pglUniform3iARB }, +{ "glUniform4iARB" , (void **)&pglUniform4iARB }, +{ "glUniform1fvARB" , (void **)&pglUniform1fvARB }, +{ "glUniform2fvARB" , (void **)&pglUniform2fvARB }, +{ "glUniform3fvARB" , (void **)&pglUniform3fvARB }, +{ "glUniform4fvARB" , (void **)&pglUniform4fvARB }, +{ "glUniform1ivARB" , (void **)&pglUniform1ivARB }, +{ "glUniform2ivARB" , (void **)&pglUniform2ivARB }, +{ "glUniform3ivARB" , (void **)&pglUniform3ivARB }, +{ "glUniform4ivARB" , (void **)&pglUniform4ivARB }, +{ "glUniformMatrix2fvARB" , (void **)&pglUniformMatrix2fvARB }, +{ "glUniformMatrix3fvARB" , (void **)&pglUniformMatrix3fvARB }, +{ "glUniformMatrix4fvARB" , (void **)&pglUniformMatrix4fvARB }, +{ "glGetObjectParameterfvARB" , (void **)&pglGetObjectParameterfvARB }, +{ "glGetObjectParameterivARB" , (void **)&pglGetObjectParameterivARB }, +{ "glGetInfoLogARB" , (void **)&pglGetInfoLogARB }, +{ "glGetAttachedObjectsARB" , (void **)&pglGetAttachedObjectsARB }, +{ "glGetUniformLocationARB" , (void **)&pglGetUniformLocationARB }, +{ "glGetActiveUniformARB" , (void **)&pglGetActiveUniformARB }, +{ "glGetUniformfvARB" , (void **)&pglGetUniformfvARB }, +{ "glGetUniformivARB" , (void **)&pglGetUniformivARB }, +{ "glGetShaderSourceARB" , (void **)&pglGetShaderSourceARB }, +{ "glVertexAttribPointerARB" , (void **)&pglVertexAttribPointerARB }, +{ "glEnableVertexAttribArrayARB" , (void **)&pglEnableVertexAttribArrayARB }, +{ "glDisableVertexAttribArrayARB" , (void **)&pglDisableVertexAttribArrayARB }, +{ "glBindAttribLocationARB" , (void **)&pglBindAttribLocationARB }, +{ "glGetActiveAttribARB" , (void **)&pglGetActiveAttribARB }, +{ "glGetAttribLocationARB" , (void **)&pglGetAttribLocationARB }, +{ "glVertexAttrib2f" , (void **)&pglVertexAttrib2fARB }, +{ "glVertexAttrib2fv" , (void **)&pglVertexAttrib2fvARB }, +{ "glVertexAttrib3fv" , (void **)&pglVertexAttrib3fvARB }, +{ NULL, NULL } +}; + +static dllfunc_t vertexshaderfuncs[] = +{ +{ "glVertexAttribPointerARB" , (void **)&pglVertexAttribPointerARB }, +{ "glEnableVertexAttribArrayARB" , (void **)&pglEnableVertexAttribArrayARB }, +{ "glDisableVertexAttribArrayARB" , (void **)&pglDisableVertexAttribArrayARB }, +{ "glBindAttribLocationARB" , (void **)&pglBindAttribLocationARB }, +{ "glGetActiveAttribARB" , (void **)&pglGetActiveAttribARB }, +{ "glGetAttribLocationARB" , (void **)&pglGetAttribLocationARB }, +{ NULL, NULL } +}; + +static dllfunc_t vbofuncs[] = +{ +{ "glBindBufferARB" , (void **)&pglBindBufferARB }, +{ "glDeleteBuffersARB" , (void **)&pglDeleteBuffersARB }, +{ "glGenBuffersARB" , (void **)&pglGenBuffersARB }, +{ "glIsBufferARB" , (void **)&pglIsBufferARB }, +{ "glMapBufferARB" , (void **)&pglMapBufferARB }, +{ "glUnmapBufferARB" , (void **)&pglUnmapBufferARB }, +{ "glBufferDataARB" , (void **)&pglBufferDataARB }, +{ "glBufferSubDataARB" , (void **)&pglBufferSubDataARB }, +{ NULL, NULL } +}; + +static dllfunc_t vaofuncs[] = +{ +{ "glBindVertexArray" , (void **)&pglBindVertexArray }, +{ "glDeleteVertexArrays" , (void **)&pglDeleteVertexArrays }, +{ "glGenVertexArrays" , (void **)&pglGenVertexArrays }, +{ "glIsVertexArray" , (void **)&pglIsVertexArray }, +{ NULL, NULL } +}; + +static dllfunc_t fbofuncs[] = +{ +{ "glIsRenderbuffer" , (void **)&pglIsRenderbuffer }, +{ "glBindRenderbuffer" , (void **)&pglBindRenderbuffer }, +{ "glDeleteRenderbuffers" , (void **)&pglDeleteRenderbuffers }, +{ "glGenRenderbuffers" , (void **)&pglGenRenderbuffers }, +{ "glRenderbufferStorage" , (void **)&pglRenderbufferStorage }, +{ "glGetRenderbufferParameteriv" , (void **)&pglGetRenderbufferParameteriv }, +{ "glIsFramebuffer" , (void **)&pglIsFramebuffer }, +{ "glBindFramebuffer" , (void **)&pglBindFramebuffer }, +{ "glDeleteFramebuffers" , (void **)&pglDeleteFramebuffers }, +{ "glGenFramebuffers" , (void **)&pglGenFramebuffers }, +{ "glCheckFramebufferStatus" , (void **)&pglCheckFramebufferStatus }, +{ "glFramebufferTexture1D" , (void **)&pglFramebufferTexture1D }, +{ "glFramebufferTexture2D" , (void **)&pglFramebufferTexture2D }, +{ "glFramebufferTexture3D" , (void **)&pglFramebufferTexture3D }, +{ "glFramebufferRenderbuffer" , (void **)&pglFramebufferRenderbuffer }, +{ "glGetFramebufferAttachmentParameteriv" , (void **)&pglGetFramebufferAttachmentParameteriv }, +{ "glGenerateMipmap" , (void **)&pglGenerateMipmap }, +{ NULL, NULL} +}; + +static dllfunc_t drawbuffersfuncs[] = +{ +{ "glDrawBuffersARB" , (void **)&pglDrawBuffersARB }, +{ NULL , NULL } +}; + +static dllfunc_t wglproc_funcs[] = +{ +{ "wglGetProcAddress" , (void **)&pwglGetProcAddress }, +{ NULL, NULL } +}; + +static dllfunc_t wgl_funcs[] = +{ +{ "wglSwapBuffers" , (void **)&pwglSwapBuffers }, +{ "wglCreateContext" , (void **)&pwglCreateContext }, +{ "wglDeleteContext" , (void **)&pwglDeleteContext }, +{ "wglMakeCurrent" , (void **)&pwglMakeCurrent }, +{ "wglGetCurrentContext" , (void **)&pwglGetCurrentContext }, +{ NULL, NULL } +}; + +static dllhandle_t opengl_dll = NULL; + +/* +======================== +DebugCallback + +For ARB_debug_output +======================== +*/ +static void CALLBACK GL_DebugOutput( GLuint source, GLuint type, GLuint id, GLuint severity, GLint length, const GLcharARB *message, GLvoid *userParam ) +{ + switch( type ) + { + case GL_DEBUG_TYPE_ERROR_ARB: + Msg( "^1OpenGL Error:^7 %s\n", message ); + break; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR_ARB: + Msg( "^3OpenGL Warning:^7 %s\n", message ); + break; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR_ARB: + Msg( "^3OpenGL Warning:^7 %s\n", message ); + break; + case GL_DEBUG_TYPE_PORTABILITY_ARB: + Msg( "^3OpenGL Warning:^7 %s\n", message ); + break; + case GL_DEBUG_TYPE_PERFORMANCE_ARB: + Msg( "OpenGL Notify: %s\n", message ); + break; + case GL_DEBUG_TYPE_OTHER_ARB: + default: + Msg( "OpenGL: %s\n", message ); + break; + } +} + +/* +================= +GL_SetExtension +================= +*/ +void GL_SetExtension( int r_ext, int enable ) +{ + if( r_ext >= 0 && r_ext < R_EXTCOUNT ) + glConfig.extension[r_ext] = enable ? GL_TRUE : GL_FALSE; + else Msg( "^1Error:^7 GL_SetExtension: invalid extension %d\n", r_ext ); +} + +/* +================= +GL_Active +================= +*/ +bool GL_Active( void ) +{ + return (opengl_dll != NULL) ? true : false; +} + +/* +================= +GL_Support +================= +*/ +bool GL_Support( int r_ext ) +{ + if( r_ext >= 0 && r_ext < R_EXTCOUNT ) + return glConfig.extension[r_ext] ? true : false; + Msg( "^1Error:^7 GL_Support: invalid extension %d\n", r_ext ); + + return false; +} + +/* +================= +GL_GetProcAddress +================= +*/ +void *GL_GetProcAddress( const char *name ) +{ + void *p = NULL; + + if( pwglGetProcAddress != NULL ) + p = (void *)pwglGetProcAddress( name ); + if( !p ) p = (void *)Sys_GetProcAddress( opengl_dll, name ); + + return p; +} + +/* +================= +GL_MaxTextureUnits +================= +*/ +int GL_MaxTextureUnits( void ) +{ + if( GL_Support( R_SHADER_GLSL100_EXT )) + return min( max( glConfig.max_texture_coords, glConfig.max_teximage_units ), 32 ); + return glConfig.max_texture_units; +} + +/* +================= +GL_CheckExtension +================= +*/ +void GL_CheckExtension( const char *name, const dllfunc_t *funcs, int r_ext ) +{ + const dllfunc_t *func; + bool real_name = ( name[0] == 'P' || name[2] == '_' || name[3] == '_' ) ? true : false; + + if( real_name ) + { + Msg( "GL_CheckExtension: %s ", name ); + Sleep( 100 ); + } + + if( real_name && !Q_strstr( glConfig.extensions_string, name )) + { + GL_SetExtension( r_ext, false ); // update render info + if( name[0] == 'P' ) + Msg( "- ^2missed^7\n" ); + else Msg( "- ^1failed^7\n" ); + return; + } + + // clear exports + for( func = funcs; func && func->name; func++ ) + *func->func = NULL; + + GL_SetExtension( r_ext, true ); // predict extension state + for( func = funcs; func && func->name != NULL; func++ ) + { + // functions are cleared before all the extensions are evaluated + if(!(*func->func = (void *)GL_GetProcAddress( func->name ))) + GL_SetExtension( r_ext, false ); // one or more functions are invalid, extension will be disabled + } + + if( real_name ) + { + if( GL_Support( r_ext )) + { + if( name[0] == 'P' ) + Msg( "- ^1present^7\n" ); + else Msg( "- ^2enabled^7\n" ); + } + else Msg( "- ^1failed^7\n" ); + } +} + +/* +=============== +GL_ContextError +=============== +*/ +static void GL_ContextError( void ) +{ + DWORD error = GetLastError(); + + if( error == ( 0xc0070000|ERROR_INVALID_VERSION_ARB )) + Msg( "^1Error:^7 Unsupported OpenGL context version (%s).\n", "2.0" ); + else if( error == ( 0xc0070000|ERROR_INVALID_PROFILE_ARB )) + Msg( "^1Error:^7 Unsupported OpenGL profile (%s).\n", "compat" ); + else if( error == ( 0xc0070000|ERROR_INVALID_OPERATION )) + Msg( "^1Error:^7 wglCreateContextAttribsARB returned invalid operation.\n" ); + else if( error == ( 0xc0070000|ERROR_DC_NOT_FOUND )) + Msg( "^1Error:^7 wglCreateContextAttribsARB returned dc not found.\n" ); + else if( error == ( 0xc0070000|ERROR_INVALID_PIXEL_FORMAT )) + Msg( "^1Error:^7 wglCreateContextAttribsARB returned dc not found.\n" ); + else if( error == ( 0xc0070000|ERROR_NO_SYSTEM_RESOURCES )) + Msg( "^1Error:^7 wglCreateContextAttribsARB ran out of system resources.\n" ); + else if( error == ( 0xc0070000|ERROR_INVALID_PARAMETER )) + Msg( "^1Error:^7 wglCreateContextAttribsARB reported invalid parameter.\n" ); + else Msg( "^1Error:^7 Unknown error creating an OpenGL (%s) Context.\n", "2.0" ); +} + +/* +================= +GL_DeleteContext +================= +*/ +bool GL_DeleteContext( void ) +{ + if( pwglMakeCurrent ) + pwglMakeCurrent( NULL, NULL ); + + if( glw_state.hGLRC ) + { + if( pwglDeleteContext ) + pwglDeleteContext( glw_state.hGLRC ); + glw_state.hGLRC = NULL; + } + + if( glw_state.hDC ) + { + ReleaseDC( glw_state.hWnd, glw_state.hDC ); + glw_state.hDC = NULL; + } + + return false; +} + +/* +================= +GL_CreateContext +================= +*/ +bool GL_CreateContext( void ) +{ + HGLRC hBaseRC; + + if(!( glw_state.hGLRC = pwglCreateContext( glw_state.hDC ))) + return GL_DeleteContext(); + + if(!( pwglMakeCurrent( glw_state.hDC, glw_state.hGLRC ))) + return GL_DeleteContext(); + + pwglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBS)GL_GetProcAddress( "wglCreateContextAttribsARB" ); + + if( pwglCreateContextAttribsARB != NULL ) + { + int attribs[] = + { + WGL_CONTEXT_MAJOR_VERSION_ARB, 2, + WGL_CONTEXT_MINOR_VERSION_ARB, 0, + WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_DEBUG_BIT_ARB, +// WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB, + 0 + }; + + hBaseRC = glw_state.hGLRC; // backup + glw_state.hGLRC = NULL; + + if( !( glw_state.hGLRC = pwglCreateContextAttribsARB( glw_state.hDC, NULL, attribs ))) + { + glw_state.hGLRC = hBaseRC; + GL_ContextError(); + return true; // just use old context + } + + if(!( pwglMakeCurrent( glw_state.hDC, glw_state.hGLRC ))) + { + pwglDeleteContext( glw_state.hGLRC ); + glw_state.hGLRC = hBaseRC; + GL_ContextError(); + return true; + } + + Msg( "GL_CreateContext: using extended context\n" ); + pwglDeleteContext( hBaseRC ); // release first context + } + + return true; +} + +/* +================= +VID_ChoosePFD +================= +*/ +static int VID_ChoosePFD( PIXELFORMATDESCRIPTOR *pfd, int colorBits, int alphaBits, int depthBits, int stencilBits ) +{ + int pixelFormat = 0; + + Msg( "VID_ChoosePFD( color %i, alpha %i, depth %i, stencil %i )\n", colorBits, alphaBits, depthBits, stencilBits ); + + // Fill out the PFD + pfd->nSize = sizeof (PIXELFORMATDESCRIPTOR); + pfd->nVersion = 1; + pfd->dwFlags = PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER; + pfd->iPixelType = PFD_TYPE_RGBA; + + pfd->cColorBits = colorBits; + pfd->cRedBits = 0; + pfd->cRedShift = 0; + pfd->cGreenBits = 0; + pfd->cGreenShift = 0; + pfd->cBlueBits = 0; + pfd->cBlueShift = 0; // wow! Blue Shift %) + + pfd->cAlphaBits = alphaBits; + pfd->cAlphaShift = 0; + + pfd->cAccumBits = 0; + pfd->cAccumRedBits = 0; + pfd->cAccumGreenBits = 0; + pfd->cAccumBlueBits = 0; + pfd->cAccumAlphaBits= 0; + + pfd->cDepthBits = depthBits; + pfd->cStencilBits = stencilBits; + + pfd->cAuxBuffers = 0; + pfd->iLayerType = PFD_MAIN_PLANE; + pfd->bReserved = 0; + + pfd->dwLayerMask = 0; + pfd->dwVisibleMask = 0; + pfd->dwDamageMask = 0; + + // count PFDs + pixelFormat = ChoosePixelFormat( glw_state.hDC, pfd ); + + if( !pixelFormat ) + { + Msg( "^1Error:^7 VID_ChoosePFD failed\n" ); + return 0; + } + + return pixelFormat; +} + +/* +================= +GL_SetPixelformat +================= +*/ +bool GL_SetPixelformat( void ) +{ + PIXELFORMATDESCRIPTOR PFD; + int pixelFormat; + + if(( glw_state.hDC = GetDC( glw_state.hWnd )) == NULL ) + return false; + + // choose a pixel format + pixelFormat = VID_ChoosePFD( &PFD, 24, 0, 32, 0 ); + + if( !pixelFormat ) + { + Msg( "^1Error:^7 GL_SetPixelformat: failed to find an appropriate PIXELFORMAT\n" ); + return false; + } + + // set the pixel format + if( !SetPixelFormat( glw_state.hDC, pixelFormat, &PFD )) + { + Msg( "^1Error:^7 GL_SetPixelformat: failed\n" ); + return false; + } + + DescribePixelFormat( glw_state.hDC, pixelFormat, sizeof( PIXELFORMATDESCRIPTOR ), &PFD ); + + if( PFD.dwFlags & PFD_GENERIC_FORMAT ) + { + Msg( "^1Error:^7 GL_SetPixelformat: no hardware acceleration found\n" ); + glw_state.software = true; + return false; + } + else + { + glw_state.software = false; + } + + glConfig.color_bits = PFD.cColorBits; + glConfig.alpha_bits = PFD.cAlphaBits; + glConfig.depth_bits = PFD.cDepthBits; + glConfig.stencil_bits = PFD.cStencilBits; + + // print out PFD specifics + Msg( "GL PFD: color( %d-bits ) alpha( %d-bits ) Z( %d-bit )\n", PFD.cColorBits, PFD.cAlphaBits, PFD.cDepthBits ); + + return true; +} + +/* +==================== +IN_WndProc + +main window procedure +==================== +*/ +long IN_WndProc( HWND hWnd, unsigned int uMsg, unsigned int wParam, long lParam ) +{ + switch( uMsg ) + { + case WM_CREATE: + glw_state.hWnd = hWnd; + break; + } + + return DefWindowProc( hWnd, uMsg, wParam, lParam ); +} + +bool VID_CreateWindow( int width, int height ) +{ + WNDCLASS wc; + RECT rect; + int x = 0, y = 0, w, h; + int stylebits = WINDOW_STYLE; + int exstyle = WINDOW_EX_STYLE; + static char wndname[256]; + HWND window; + + strncpy( wndname, "TestWindow", sizeof( wndname )); + + glw_state.hInst = GetModuleHandle( NULL ); + + // register the frame class + wc.style = CS_OWNDC|CS_NOCLOSE; + wc.lpfnWndProc = (WNDPROC)IN_WndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = glw_state.hInst; + wc.hCursor = LoadCursor( NULL, IDC_ARROW ); + wc.hbrBackground = (HBRUSH)COLOR_3DSHADOW; + wc.lpszClassName = WINDOW_NAME; + wc.lpszMenuName = 0; + wc.hIcon = LoadIcon( glw_state.hInst, MAKEINTRESOURCE( 101 )); + + if( !RegisterClass( &wc )) + { + Msg( "^1Error:^7 VID_CreateWindow: couldn't register window class %s\n" WINDOW_NAME ); + return false; + } + + rect.left = 0; + rect.top = 0; + rect.right = width; + rect.bottom = height; + + AdjustWindowRect( &rect, stylebits, FALSE ); + w = rect.right - rect.left; + h = rect.bottom - rect.top; + + window = CreateWindowEx( exstyle, WINDOW_NAME, wndname, stylebits, x, y, w, h, NULL, NULL, glw_state.hInst, NULL ); + + if( glw_state.hWnd != window ) + { + // probably never happens + Msg( "^3Warning:^7 VID_CreateWindow: bad hWnd for '%s'\n", wndname ); + } + + // host.hWnd must be filled in IN_WndProc + if( !glw_state.hWnd ) + { + Msg( "^1Error:^7 VID_CreateWindow: couldn't create '%s'\n", wndname ); + return false; + } + + ShowWindow( glw_state.hWnd, SW_HIDE ); + UpdateWindow( glw_state.hWnd ); + + // init all the gl stuff for the window + if( !GL_SetPixelformat( )) + { + DestroyWindow( glw_state.hWnd ); + glw_state.hWnd = NULL; + + UnregisterClass( WINDOW_NAME, glw_state.hInst ); + Msg( "^1Error:^7 OpenGL driver not installed\n" ); + + return false; + } + + if( !GL_CreateContext( )) + return false; + + return true; +} + +void VID_DestroyWindow( void ) +{ + if( pwglMakeCurrent ) + pwglMakeCurrent( NULL, NULL ); + + if( glw_state.hDC ) + { + ReleaseDC( glw_state.hWnd, glw_state.hDC ); + glw_state.hDC = NULL; + } + + if( glw_state.hWnd ) + { + DestroyWindow( glw_state.hWnd ); + glw_state.hWnd = NULL; + } + + UnregisterClass( WINDOW_NAME, glw_state.hInst ); +} + +rserr_t R_ChangeDisplaySettings( void ) +{ + HDC hDC; + + // check our desktop attributes + hDC = GetDC( GetDesktopWindow( )); + glw_state.desktopBitsPixel = GetDeviceCaps( hDC, BITSPIXEL ); + glw_state.desktopWidth = GetDeviceCaps( hDC, HORZRES ); + glw_state.desktopHeight = GetDeviceCaps( hDC, VERTRES ); + ReleaseDC( GetDesktopWindow(), hDC ); + + // destroy the existing window + if( glw_state.hWnd ) VID_DestroyWindow(); + + ChangeDisplaySettings( 0, 0 ); + + if( !VID_CreateWindow( 1024, 768 )) + return rserr_invalid_mode; + + return rserr_ok; +} + +/* +================== +VID_SetMode + +Set the described video mode +================== +*/ +bool VID_SetMode( void ) +{ + if( R_ChangeDisplaySettings() == rserr_ok ) + { + return true; + } + + return false; +} + +static bool GL_InitExtensions( void ) +{ + if( !Sys_LoadLibrary( "opengl32.dll", &opengl_dll, wgl_funcs )) + { + Msg( "^1Error:^7 OpenGL32.dll couldn't be loaded.\n" ); + return false; + } + + // initalize until base opengl functions loaded + GL_CheckExtension( "OpenGL ProcAddress", wglproc_funcs, R_WGL_PROCADDRESS ); + + if( !VID_SetMode( )) + { + return false; + } + + // initialize gl extensions + GL_CheckExtension( "OpenGL 1.1.0", opengl_110funcs, R_OPENGL_110 ); + + if( !GL_Support( R_OPENGL_110 )) + { + Msg( "^1Error:^7 OpenGL 1.0 can't be installed.\n" ); + return false; + } + + // get our various GL strings + glConfig.vendor_string = pglGetString( GL_VENDOR ); + glConfig.renderer_string = pglGetString( GL_RENDERER ); + glConfig.version_string = pglGetString( GL_VERSION ); + glConfig.extensions_string = pglGetString( GL_EXTENSIONS ); + + Msg( "\n" ); + Msg( "GL_VENDOR: %s\n", glConfig.vendor_string ); + Msg( "GL_RENDERER: %s\n", glConfig.renderer_string ); + Msg( "GL_VERSION: %s\n", glConfig.version_string ); + + GL_CheckExtension( "glDrawRangeElements", drawrangeelementsfuncs, R_DRAW_RANGEELEMENTS_EXT ); + + if( !GL_Support( R_DRAW_RANGEELEMENTS_EXT )) + GL_CheckExtension( "GL_EXT_draw_range_elements", drawrangeelementsextfuncs, R_DRAW_RANGEELEMENTS_EXT ); + + // we don't care if it's an extension or not, they are identical functions, so keep it simple in the rendering code + if( pglDrawRangeElementsEXT == NULL ) pglDrawRangeElementsEXT = pglDrawRangeElements; + + if( !GL_Support( R_DRAW_RANGEELEMENTS_EXT )) + { + Msg( "^1Error:^7 GL_EXT_draw_range_elements not support.\n" ); + return false; + } + + // multitexture + glConfig.max_texture_units = glConfig.max_texture_coords = glConfig.max_teximage_units = 1; + GL_CheckExtension( "GL_ARB_multitexture", multitexturefuncs, R_ARB_MULTITEXTURE ); + + if( GL_Support( R_ARB_MULTITEXTURE )) + pglGetIntegerv( GL_MAX_TEXTURE_UNITS_ARB, &glConfig.max_texture_units ); + + if( glConfig.max_texture_units == 1 ) + GL_SetExtension( R_ARB_MULTITEXTURE, false ); + + if( !GL_Support( R_ARB_MULTITEXTURE )) + { + Msg( "^1Error:^7 GL_ARB_multitexture not support.\n" ); + return false; + } + + // 3d texture support + GL_CheckExtension( "GL_EXT_texture3D", texture3dextfuncs, R_TEXTURE_3D_EXT ); + + if( GL_Support( R_TEXTURE_3D_EXT )) + { + pglGetIntegerv( GL_MAX_3D_TEXTURE_SIZE, &glConfig.max_3d_texture_size ); + + if( glConfig.max_3d_texture_size < 32 ) + { + GL_SetExtension( R_TEXTURE_3D_EXT, false ); + Msg( "^3Warning:^7 GL_EXT_texture3D reported bogus GL_MAX_3D_TEXTURE_SIZE, disabled\n" ); + } + } + + // 2d texture array support + GL_CheckExtension( "GL_EXT_texture_array", texture3dextfuncs, R_TEXTURE_ARRAY_EXT ); + + if( GL_Support( R_TEXTURE_ARRAY_EXT )) + pglGetIntegerv( GL_MAX_ARRAY_TEXTURE_LAYERS_EXT, &glConfig.max_2d_texture_layers ); + + // hardware cubemaps + GL_CheckExtension( "GL_ARB_texture_cube_map", NULL, R_TEXTURECUBEMAP_EXT ); + + if( GL_Support( R_TEXTURECUBEMAP_EXT )) + { + pglGetIntegerv( GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB, &glConfig.max_cubemap_size ); + } + else + { + Msg( "^3Warning:^7 GL_ARB_texture_cube_map not support. Cubemap reflections and omni shadows will be disabled.\n" ); + } + + GL_CheckExtension( "GL_ARB_draw_buffers", drawbuffersfuncs, R_DRAW_BUFFERS_EXT ); + + if( !GL_Support( R_DRAW_BUFFERS_EXT )) + { + Msg( "^1Error:^7 GL_ARB_draw_buffers not support.\n" ); + return false; + } + + GL_CheckExtension( "GL_ARB_texture_non_power_of_two", NULL, R_ARB_TEXTURE_NPOT_EXT ); + + GL_CheckExtension( "GL_ARB_vertex_buffer_object", vbofuncs, R_ARB_VERTEX_BUFFER_OBJECT_EXT ); + + if( !GL_Support( R_ARB_VERTEX_BUFFER_OBJECT_EXT )) + { + Msg( "^1Error:^7 GL_ARB_vertex_buffer_object not support.\n" ); + return false; + } + + GL_CheckExtension( "GL_ARB_vertex_array_object", vaofuncs, R_ARB_VERTEX_ARRAY_OBJECT_EXT ); + + if( !GL_Support( R_ARB_VERTEX_ARRAY_OBJECT_EXT )) + { + Msg( "^1Error:^7 GL_ARB_vertex_array_object not support.\n" ); + return false; + } + + GL_CheckExtension( "GL_EXT_gpu_shader4", NULL, R_EXT_GPU_SHADER4 ); + + if( !GL_Support( R_EXT_GPU_SHADER4 )) + Msg( "^3Warning:^7 GL_EXT_gpu_shader4 not support\n" ); + + GL_CheckExtension( "GL_ARB_debug_output", debugoutputfuncs, R_DEBUG_OUTPUT ); + + // vp and fp shaders + GL_CheckExtension( "GL_ARB_shader_objects", shaderobjectsfuncs, R_SHADER_OBJECTS_EXT ); + + if( !GL_Support( R_SHADER_OBJECTS_EXT )) + { + Msg( "^1Error:^7 GL_ARB_shader_objects not support.\n" ); + return false; + } + + GL_CheckExtension( "GL_ARB_shading_language_100", NULL, R_SHADER_GLSL100_EXT ); + + if( !GL_Support( R_SHADER_GLSL100_EXT )) + { + Msg( "^1Error:^7 GL_ARB_shading_language_100 not support.\n" ); + return false; + } + + GL_CheckExtension( "GL_ARB_vertex_shader", vertexshaderfuncs, R_VERTEX_SHADER_EXT ); + + if( !GL_Support( R_VERTEX_SHADER_EXT )) + { + Msg( "^1Error:^7 GL_ARB_vertext_shader not support.\n" ); + return false; + } + + GL_CheckExtension( "GL_ARB_fragment_shader", NULL, R_FRAGMENT_SHADER_EXT ); + + if( !GL_Support( R_FRAGMENT_SHADER_EXT )) + { + Msg( "^1Error:^7 GL_ARB_fragment_shader not support.\n" ); + return false; + } + + GL_CheckExtension( "GL_ARB_depth_texture", NULL, R_DEPTH_TEXTURE ); + GL_CheckExtension( "GL_ARB_shadow", NULL, R_SHADOW_EXT ); + + if( !GL_Support( R_DEPTH_TEXTURE ) || !GL_Support( R_SHADOW_EXT )) + { + Msg( "^3Warning:^7 GL_ARB_depth_texture or GL_ARB_shadow not support. Dynamic shadows will be disabled\n" ); + } + + if( GL_Support( R_SHADER_GLSL100_EXT )) + { + pglGetIntegerv( GL_MAX_TEXTURE_COORDS_ARB, &glConfig.max_texture_coords ); + pglGetIntegerv( GL_MAX_TEXTURE_IMAGE_UNITS_ARB, &glConfig.max_teximage_units ); + } + else + { + // just get from multitexturing + glConfig.max_texture_coords = glConfig.max_teximage_units = glConfig.max_texture_units; + } + + if( glConfig.max_teximage_units < 8 ) + { + Msg( "^1Error:^7 not enough texture image units.\n" ); + return false; + } + + glConfig.max_2d_texture_size = 0; + pglGetIntegerv( GL_MAX_TEXTURE_SIZE, &glConfig.max_2d_texture_size ); + if( glConfig.max_2d_texture_size <= 0 ) glConfig.max_2d_texture_size = 256; + + // FBO support + GL_CheckExtension( "GL_ARB_framebuffer_object", fbofuncs, R_FRAMEBUFFER_OBJECT ); + + // Paranoia OpenGL32.dll may be eliminate shadows. Run special check for it + GL_CheckExtension( "PARANOIA_HACKS_V1", NULL, R_PARANOIA_EXT ); + + if( GL_Support( R_PARANOIA_EXT )) + { + Msg( "^3Warning:^7 hacked Paranoia opengl32.dll was detected\n We recommend to remove it from Paranoia 2:Savior folder.\n" ); + } + + GL_CheckExtension( "GL_ARB_texture_float", NULL, R_ARB_TEXTURE_FLOAT_EXT ); + + GL_CheckExtension( "GL_ARB_half_float_vertex", NULL, R_ARB_HALF_FLOAT_EXT ); +// GL_CheckExtension( "GL_ARB_half_float_pixel", NULL, R_ARB_HALF_FLOAT_EXT ); + + if( !GL_Support( R_ARB_HALF_FLOAT_EXT )) + { + Msg( "^1Error:^7 GL_ARB_half_float_vertex not support by your hardware.\n" ); + return false; + } + + // rectangle textures support + if( Q_strstr( glConfig.extensions_string, "GL_NV_texture_rectangle" )) + { + pglGetIntegerv( GL_MAX_RECTANGLE_TEXTURE_SIZE_NV, &glConfig.max_2d_rectangle_size ); + } + else if( Q_strstr( glConfig.extensions_string, "GL_EXT_texture_rectangle" )) + { + pglGetIntegerv( GL_MAX_RECTANGLE_TEXTURE_SIZE_EXT, &glConfig.max_2d_rectangle_size ); + } + else glConfig.max_2d_rectangle_size = 0; // no rectangle + + // check for hardware skinning + pglGetIntegerv( GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB, &glConfig.max_vertex_uniforms ); + pglGetIntegerv( GL_MAX_VERTEX_ATTRIBS_ARB, &glConfig.max_vertex_attribs ); + + if( glConfig.max_vertex_attribs < 10 ) + { + Msg( "^1Error:^7 not enough vertex attrib count.\n" ); + return false; + } + + if( Q_strstr( glConfig.renderer_string, "radeon" ) || Q_strstr( glConfig.renderer_string, "Radeon" )) + glConfig.max_vertex_uniforms /= 4; // radeon returns not correct info + + // clamp to a minimal value on SM 2.0 + glConfig.max_vertex_uniforms = max( glConfig.max_vertex_uniforms, MIN_SHADER_UNIFOMS ); + + glConfig.max_skinning_bones = bound( 0, ( max( glConfig.max_vertex_uniforms - MAX_RESERVED_UNIFORMS, 0 ) / 12 ), 128 ); + + if( glConfig.max_skinning_bones < 64 ) + { + Msg( "^1Error:^7 Hardware Skinning not support.\n" ); + return false; + } + else if( glConfig.max_skinning_bones < 128 ) + Msg( "^3Warning:^7 Hardware Skinning probably has a limitation (max %i bones)\n", glConfig.max_skinning_bones ); + + Msg( "MaxSkinned bones %i\n", glConfig.max_skinning_bones ); + + Msg( "GL_MAX_TEXTURE_SIZE: %i\n", glConfig.max_2d_texture_size ); + + if( GL_Support( R_ARB_MULTITEXTURE )) + Msg( "GL_MAX_TEXTURE_UNITS_ARB: %i\n", glConfig.max_texture_units ); + if( GL_Support( R_TEXTURECUBEMAP_EXT )) + Msg( "GL_MAX_CUBE_MAP_TEXTURE_SIZE_ARB: %i\n", glConfig.max_cubemap_size ); + if( glConfig.max_2d_rectangle_size ) + Msg( "GL_MAX_RECTANGLE_TEXTURE_SIZE_ARB: %i\n", glConfig.max_2d_rectangle_size ); + if( GL_Support( R_TEXTURE_ARRAY_EXT )) + Msg( "GL_MAX_ARRAY_TEXTURE_LAYERS_EXT: %i\n", glConfig.max_2d_texture_layers ); + + if( GL_Support( R_SHADER_GLSL100_EXT )) + { + Msg( "GL_MAX_TEXTURE_COORDS_ARB: %i\n", glConfig.max_texture_coords ); + Msg( "GL_MAX_TEXTURE_IMAGE_UNITS_ARB: %i\n", glConfig.max_teximage_units ); + } + + Msg( "GL_MAX_VERTEX_UNIFORM_COMPONENTS_ARB: %i\n", glConfig.max_vertex_uniforms ); + Msg( "GL_MAX_VERTEX_ATTRIBS_ARB: %i\n", glConfig.max_vertex_attribs ); + + if( GL_Support( R_DEBUG_OUTPUT )) + { + pglDebugMessageCallbackARB( GL_DebugOutput, NULL ); + + // force everything to happen in the main thread instead of in a separate driver thread + pglEnable( GL_DEBUG_OUTPUT_SYNCHRONOUS_ARB ); + + // enable all the low priority messages + pglDebugMessageControlARB( GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_LOW_ARB, 0, NULL, true ); + } + + return true; +} + +int main( int argc, char **argv ) +{ + Sys_InitLog( "status.log" ); + + Msg("\n\n\t\t\t^3Xash XT Group^7 2018(c).\n"); + Msg("\t\t\tRequirements Test v1.03.\n\n\n"); + + if( GL_InitExtensions( )) + { + Msg( "\n\t\t^2TEST SUCESSFULLY PASSED!\n\n\n\n\t\t\tHit any key to exit" ); + } + else + { + Msg( "\n\t\t\t^1TEST FAILED!\n\n\n\n\t\t\tHit any key to exit" ); + } + + system( "pause>nul" ); + + GL_DeleteContext (); + VID_DestroyWindow (); + Sys_FreeLibrary( &opengl_dll ); + Sys_CloseLog(); + + return 0; +} \ No newline at end of file diff --git a/utils/reqtest/reqtest.dsp b/utils/reqtest/reqtest.dsp new file mode 100644 index 0000000..769162d --- /dev/null +++ b/utils/reqtest/reqtest.dsp @@ -0,0 +1,138 @@ +# Microsoft Developer Studio Project File - Name="reqtest" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=reqtest - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "reqtest.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "reqtest.mak" CFG="reqtest - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "reqtest - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "reqtest - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/SDKSrc/Tools/utils/reqtest", IVGBAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "reqtest - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir ".\Release" +# PROP BASE Intermediate_Dir ".\Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\..\temp\reqtest\!release" +# PROP Intermediate_Dir "..\..\temp\reqtest\!release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "../common" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /FD /c +# SUBTRACT CPP /Fr /YX +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 msvcrt.lib user32.lib gdi32.lib /nologo /subsystem:console /pdb:none /machine:I386 /nodefaultlib:"libcmt.lib" +# Begin Custom Build +TargetDir=\Paranoia2\temp\reqtest\!release +InputPath=\Paranoia2\temp\reqtest\!release\reqtest.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\reqtest.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\reqtest.exe "D:\Paranoia2\tools\reqtest.exe" + +# End Custom Build + +!ELSEIF "$(CFG)" == "reqtest - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir ".\Debug" +# PROP BASE Intermediate_Dir ".\Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\..\temp\reqtest\!debug" +# PROP Intermediate_Dir "..\..\temp\reqtest\!debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MDd /W3 /Gm /Gi /GX /ZI /Od /I "../common" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /FAs /FR /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:console /debug /machine:I386 +# ADD LINK32 msvcrtd.lib user32.lib gdi32.lib /nologo /subsystem:console /debug /machine:I386 /nodefaultlib:"libcd.lib" +# Begin Custom Build +TargetDir=\Paranoia2\temp\reqtest\!debug +InputPath=\Paranoia2\temp\reqtest\!debug\reqtest.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\reqtest.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\reqtest.exe "D:\Paranoia2\tools\reqtest.exe" + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "reqtest - Win32 Release" +# Name "reqtest - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=..\common\cmdlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\conprint.cpp +# End Source File +# Begin Source File + +SOURCE=.\reqtest.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\stringlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\zone.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=..\common\conprint.h +# End Source File +# End Group +# End Target +# End Project diff --git a/utils/spritegen/imagelib.cpp b/utils/spritegen/imagelib.cpp new file mode 100644 index 0000000..81bf52a --- /dev/null +++ b/utils/spritegen/imagelib.cpp @@ -0,0 +1,1009 @@ +/* +imagelib.cpp - simple loader\serializer for TGA & BMP +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include +#include "cmdlib.h" +#include "mathlib.h" +#include "stringlib.h" +#include "scriplib.h" +#include "filesystem.h" +#include "imagelib.h" + +/* +================= +Image_ValidSize + +check image for valid dimensions +================= +*/ +bool Image_ValidSize( const char *name, int width, int height ) +{ + if( width > IMAGE_MAXWIDTH || height > IMAGE_MAXHEIGHT || width < IMAGE_MINWIDTH || height < IMAGE_MINHEIGHT ) + { + MsgDev( D_ERROR, "Image: %s has invalid sizes %i x %i\n", name, width, height ); + return false; + } + + return true; +} + +/* +================= +Image_Alloc + +allocate image struct and partially fill it +================= +*/ +rgbdata_t *Image_Alloc( int width, int height, bool paletted ) +{ + size_t pic_size = sizeof( rgbdata_t ) + (width * height * (paletted ? 1 : 4)) + (paletted ? 768 : 0); + rgbdata_t *pic = (rgbdata_t *)Mem_Alloc( pic_size ); + + if( paletted ) + { + pic->buffer = ((byte *)pic) + sizeof( rgbdata_t ); + pic->palette = ((byte *)pic) + sizeof( rgbdata_t ) + width * height; + pic->flags |= IMAGE_QUANTIZED; + } + else + { + pic->buffer = ((byte *)pic) + sizeof( rgbdata_t ); + pic->palette = NULL; // not present + } + + pic->size = (width * height * (paletted ? 1 : 4)); + pic->width = width; + pic->height = height; + + return pic; +} + +/* +================= +Image_Copy + +make an copy of image +================= +*/ +rgbdata_t *Image_Copy( rgbdata_t *src ) +{ + size_t pic_size = sizeof( rgbdata_t ) + src->size + (FBitSet( src->flags, IMAGE_QUANTIZED ) ? 768 : 0 ); + rgbdata_t *dst = (rgbdata_t *)Mem_Alloc( pic_size ); + dst->buffer = ((byte *)dst) + sizeof( rgbdata_t ); + + if( FBitSet( src->flags, IMAGE_QUANTIZED )) + { + dst->palette = dst->buffer + src->size; + memcpy( dst->palette, src->palette, 768 ); + } + + memcpy( dst->buffer, src->buffer, src->size ); + + dst->size = src->size; + dst->width = src->width; + dst->height = src->height; + dst->flags = src->flags; + + return dst; +} + +/* +============================================================================= + + IMAGE LOADING + +============================================================================= +*/ +/* +============= +Image_LoadTGA + +expand any image to RGBA32 but keep 8-bit unchanged +============= +*/ +rgbdata_t *Image_LoadTGA( const char *name, const byte *buffer, size_t filesize ) +{ + int i, columns, rows, row_inc, row, col; + byte *buf_p, *pixbuf, *targa_rgba; + byte palette[256][3], red = 0, green = 0, blue = 0, alpha = 0; + int readpixelcount, pixelcount, palIndex; + tga_t targa_header; + bool compressed; + bool paletted; + rgbdata_t *pic; + + if( filesize < sizeof( tga_t )) + return NULL; + + buf_p = (byte *)buffer; + targa_header.id_length = *buf_p++; + targa_header.colormap_type = *buf_p++; + targa_header.image_type = *buf_p++; + + targa_header.colormap_index = *(short *)buf_p; buf_p += 2; + targa_header.colormap_length = *(short *)buf_p; buf_p += 2; + targa_header.colormap_size = *buf_p; buf_p += 1; + targa_header.x_origin = *(short *)buf_p; buf_p += 2; + targa_header.y_origin = *(short *)buf_p; buf_p += 2; + targa_header.width = *(short *)buf_p; buf_p += 2; + targa_header.height = *(short *)buf_p; buf_p += 2; + targa_header.pixel_size = *buf_p++; + targa_header.attributes = *buf_p++; + if( targa_header.id_length != 0 ) + buf_p += targa_header.id_length; // skip TARGA image comment + + // check for tga file + if( !Image_ValidSize( name, targa_header.width, targa_header.height )) + return NULL; + + if( targa_header.image_type == 1 || targa_header.image_type == 9 ) + { + // uncompressed colormapped image + if( targa_header.pixel_size != 8 ) + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) Only 8 bit images supported for type 1 and 9\n", name ); + return NULL; + } + + if( targa_header.colormap_length != 256 ) + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) Only 8 bit colormaps are supported for type 1 and 9\n", name ); + return NULL; + } + + if( targa_header.colormap_index ) + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) colormap_index is not supported for type 1 and 9\n", name ); + return NULL; + } + + if( targa_header.colormap_size == 24 ) + { + for( i = 0; i < targa_header.colormap_length; i++ ) + { + palette[i][2] = *buf_p++; + palette[i][1] = *buf_p++; + palette[i][0] = *buf_p++; + } + } + else if( targa_header.colormap_size == 32 ) + { + for( i = 0; i < targa_header.colormap_length; i++ ) + { + palette[i][2] = *buf_p++; + palette[i][1] = *buf_p++; + palette[i][0] = *buf_p++; + *buf_p++; // skip the alpha + } + } + else + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) only 24 and 32 bit colormaps are supported for type 1 and 9\n", name ); + return NULL; + } + } + else if( targa_header.image_type == 2 || targa_header.image_type == 10 ) + { + // uncompressed or RLE compressed RGB + if( targa_header.pixel_size != 32 && targa_header.pixel_size != 24 ) + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) Only 32 or 24 bit images supported for type 2 and 10\n", name ); + return NULL; + } + } + else if( targa_header.image_type == 3 || targa_header.image_type == 11 ) + { + // uncompressed greyscale + if( targa_header.pixel_size != 8 ) + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) Only 8 bit images supported for type 3 and 11\n", name ); + return NULL; + } + } + + paletted = ( targa_header.image_type == 1 || targa_header.image_type == 9 ); + + pic = Image_Alloc( targa_header.width, targa_header.height, paletted ); + if( paletted ) memcpy( pic->palette, palette, sizeof( palette )); + + columns = targa_header.width; + rows = targa_header.height; + targa_rgba = pic->buffer; + + // if bit 5 of attributes isn't set, the image has been stored from bottom to top + if( targa_header.attributes & 0x20 ) + { + pixbuf = targa_rgba; + row_inc = 0; + } + else + { + if( FBitSet( pic->flags, IMAGE_QUANTIZED )) + { + pixbuf = targa_rgba + ( rows - 1 ) * columns; + row_inc = -columns * 2; + } + else + { + pixbuf = targa_rgba + ( rows - 1 ) * columns * 4; + row_inc = -columns * 4 * 2; + } + } + + compressed = ( targa_header.image_type == 9 || targa_header.image_type == 10 || targa_header.image_type == 11 ); + + for( row = col = 0; row < rows; ) + { + pixelcount = 0x10000; + readpixelcount = 0x10000; + + if( compressed ) + { + pixelcount = *buf_p++; + if( pixelcount & 0x80 ) // run-length packet + readpixelcount = 1; + pixelcount = 1 + ( pixelcount & 0x7f ); + } + + while( pixelcount-- && ( row < rows ) ) + { + if( readpixelcount-- > 0 ) + { + switch( targa_header.image_type ) + { + case 1: + case 9: + // colormapped image + palIndex = *buf_p++; + red = palette[palIndex][0]; + green = palette[palIndex][1]; + blue = palette[palIndex][2]; + alpha = 255; + break; + case 2: + case 10: + // 24 or 32 bit image + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alpha = 255; + if( targa_header.pixel_size == 32 ) + alpha = *buf_p++; + break; + case 3: + case 11: + // greyscale image + blue = green = red = *buf_p++; + alpha = 255; + break; + } + } + + if( red != green || green != blue ) + pic->flags |= IMAGE_HAS_COLOR; + + if( alpha != 255 ) + { + if( alpha != 0 ) + { + SetBits( pic->flags, IMAGE_HAS_8BIT_ALPHA ); + ClearBits( pic->flags, IMAGE_HAS_1BIT_ALPHA ); + } + else if( !FBitSet( pic->flags, IMAGE_HAS_8BIT_ALPHA )) + SetBits( pic->flags, IMAGE_HAS_1BIT_ALPHA ); + } + + if( FBitSet( pic->flags, IMAGE_QUANTIZED )) + { + *pixbuf++ = palIndex; + } + else + { + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha; + } + + if( ++col == columns ) + { + // run spans across rows + row++; + col = 0; + pixbuf += row_inc; + } + } + } + + return pic; +} + +/* +============= +Image_LoadBMP + +expand any image to RGBA32 but keep 8-bit unchanged +============= +*/ +rgbdata_t *Image_LoadBMP( const char *name, const byte *buffer, size_t filesize ) +{ + byte *buf_p, *pixbuf; + byte palette[256][4]; + int i, columns, column, rows, row, bpp = 1; + int cbPalBytes = 0, padSize = 0, bps = 0; + rgbdata_t *pic; + bmp_t bhdr; + + if( filesize < sizeof( bhdr )) + return NULL; + + buf_p = (byte *)buffer; + bhdr.id[0] = *buf_p++; + bhdr.id[1] = *buf_p++; // move pointer + bhdr.fileSize = *(long *)buf_p; buf_p += 4; + bhdr.reserved0 = *(long *)buf_p; buf_p += 4; + bhdr.bitmapDataOffset = *(long *)buf_p; buf_p += 4; + bhdr.bitmapHeaderSize = *(long *)buf_p; buf_p += 4; + bhdr.width = *(long *)buf_p; buf_p += 4; + bhdr.height = *(long *)buf_p; buf_p += 4; + bhdr.planes = *(short *)buf_p; buf_p += 2; + bhdr.bitsPerPixel = *(short *)buf_p; buf_p += 2; + bhdr.compression = *(long *)buf_p; buf_p += 4; + bhdr.bitmapDataSize = *(long *)buf_p; buf_p += 4; + bhdr.hRes = *(long *)buf_p; buf_p += 4; + bhdr.vRes = *(long *)buf_p; buf_p += 4; + bhdr.colors = *(long *)buf_p; buf_p += 4; + bhdr.importantColors = *(long *)buf_p; buf_p += 4; + + // bogus file header check + if( bhdr.reserved0 != 0 ) return NULL; + if( bhdr.planes != 1 ) return NULL; + + if( memcmp( bhdr.id, "BM", 2 )) + { + MsgDev( D_ERROR, "Image_LoadBMP: only Windows-style BMP files supported (%s)\n", name ); + return NULL; + } + + if( bhdr.bitmapHeaderSize != 0x28 ) + { + MsgDev( D_ERROR, "Image_LoadBMP: invalid header size %i\n", bhdr.bitmapHeaderSize ); + return NULL; + } + + // bogus info header check + if( bhdr.fileSize != filesize ) + { + // Sweet Half-Life issues. splash.bmp have bogus filesize + MsgDev( D_WARN, "Image_LoadBMP: %s have incorrect file size %i should be %i\n", name, filesize, bhdr.fileSize ); + } + + // bogus compression? Only non-compressed supported. + if( bhdr.compression != BI_RGB ) + { + MsgDev( D_ERROR, "Image_LoadBMP: only uncompressed BMP files supported (%s)\n", name ); + return false; + } + + columns = bhdr.width; + rows = abs( bhdr.height ); + + if( !Image_ValidSize( name, columns, rows )) + return false; + + pic = Image_Alloc( columns, rows, ( bhdr.bitsPerPixel == 4 || bhdr.bitsPerPixel == 8 )); + + if( bhdr.bitsPerPixel <= 8 ) + { + // figure out how many entries are actually in the table + if( bhdr.colors == 0 ) + { + bhdr.colors = 256; + cbPalBytes = (1 << bhdr.bitsPerPixel) * sizeof( RGBQUAD ); + } + else cbPalBytes = bhdr.colors * sizeof( RGBQUAD ); + } + + memcpy( palette, buf_p, cbPalBytes ); + + if( bhdr.bitsPerPixel == 4 || bhdr.bitsPerPixel == 8 ) + { + pixbuf = pic->palette; + + // bmp have a reversed palette colors + for( i = 0; i < bhdr.colors; i++ ) + { + *pixbuf++ = palette[i][2]; + *pixbuf++ = palette[i][1]; + *pixbuf++ = palette[i][0]; + + if( palette[i][0] != palette[i][1] || palette[i][1] != palette[i][2] ) + pic->flags |= IMAGE_HAS_COLOR; + } + } + else bpp = 4; + + buf_p += cbPalBytes; + bps = bhdr.width * (bhdr.bitsPerPixel >> 3); + + switch( bhdr.bitsPerPixel ) + { + case 1: + padSize = (( 32 - ( bhdr.width % 32 )) / 8 ) % 4; + break; + case 4: + padSize = (( 8 - ( bhdr.width % 8 )) / 2 ) % 4; + break; + case 16: + padSize = ( 4 - ( bhdr.width * 2 % 4 )) % 4; + break; + case 8: + case 24: + padSize = ( 4 - ( bps % 4 )) % 4; + break; + } + + for( row = rows - 1; row >= 0; row-- ) + { + pixbuf = pic->buffer + (row * columns * bpp); + + for( column = 0; column < columns; column++ ) + { + byte red, green, blue, alpha; + int c, k, palIndex; + word shortPixel; + + switch( bhdr.bitsPerPixel ) + { + case 1: + alpha = *buf_p++; + column--; // ingnore main iterations + for( c = 0, k = 128; c < 8; c++, k >>= 1 ) + { + red = green = blue = (!!(alpha & k) == 1 ? 0xFF : 0x00); + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 0x00; + if( ++column == columns ) + break; + } + break; + case 4: + alpha = *buf_p++; + palIndex = alpha >> 4; + red = palette[palIndex][2]; + green = palette[palIndex][1]; + blue = palette[palIndex][0]; + alpha = palette[palIndex][3]; + + if( FBitSet( pic->flags, IMAGE_QUANTIZED )) + { + *pixbuf++ = palIndex; + } + else + { + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha; + } + + if( ++column == columns ) + break; + + palIndex = alpha & 0x0F; + red = palette[palIndex][2]; + green = palette[palIndex][1]; + blue = palette[palIndex][0]; + alpha = palette[palIndex][3]; + + if( FBitSet( pic->flags, IMAGE_QUANTIZED )) + { + *pixbuf++ = palIndex; + } + else + { + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha; + } + break; + case 8: + palIndex = *buf_p++; + red = palette[palIndex][2]; + green = palette[palIndex][1]; + blue = palette[palIndex][0]; + alpha = palette[palIndex][3]; + + if( FBitSet( pic->flags, IMAGE_QUANTIZED )) + { + *pixbuf++ = palIndex; + } + else + { + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha; + } + break; + case 16: + shortPixel = *(word *)buf_p, buf_p += 2; + *pixbuf++ = blue = (shortPixel & ( 31 << 10 )) >> 7; + *pixbuf++ = green = (shortPixel & ( 31 << 5 )) >> 2; + *pixbuf++ = red = (shortPixel & ( 31 )) << 3; + *pixbuf++ = alpha = 0xff; + break; + case 24: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha = 0xFF; + break; + case 32: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alpha = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha; + break; + default: + MsgDev( D_ERROR, "Image_LoadBMP: illegal pixel_size (%s)\n", name ); + Mem_Free( pic ); + return NULL; + } + + if( !FBitSet( pic->flags, IMAGE_QUANTIZED ) && ( red != green || green != blue )) + pic->flags |= IMAGE_HAS_COLOR; + + if( alpha != 255 ) + { + if( alpha != 0 ) + { + SetBits( pic->flags, IMAGE_HAS_8BIT_ALPHA ); + ClearBits( pic->flags, IMAGE_HAS_1BIT_ALPHA ); + } + else if( !FBitSet( pic->flags, IMAGE_HAS_8BIT_ALPHA )) + SetBits( pic->flags, IMAGE_HAS_1BIT_ALPHA ); + } + } + + buf_p += padSize; // probably actual only for 4-bit bmps + } + + return pic; +} + +/* +================ +COM_LoadImage + +handle bmp & tga +================ +*/ +rgbdata_t *COM_LoadImage( const char *filename ) +{ + size_t fileSize; + byte *buf = (byte *)COM_LoadFile( filename, &fileSize, false ); + const char *ext = COM_FileExtension( filename ); + rgbdata_t *pic = NULL; + + if( !buf ) return NULL; + + if( !Q_stricmp( ext, "tga" )) + pic = Image_LoadTGA( filename, buf, fileSize ); + else if( !Q_stricmp( ext, "bmp" )) + pic = Image_LoadBMP( filename, buf, fileSize ); + else MsgDev( D_ERROR, "COM_LoadImage: unsupported format (%s)\n", ext ); + + Mem_Free( buf ); // release file + + return pic; // may be NULL +} + +/* +================ +COM_LoadImage + +handle bmp & tga +================ +*/ +rgbdata_t *COM_LoadImageMemory( const char *filename, const byte *buf, size_t fileSize ) +{ + const char *ext = COM_FileExtension( filename ); + rgbdata_t *pic = NULL; + + if( !buf ) + { + MsgDev( D_ERROR, "COM_LoadImageMemory: unable to load (%s)\n", filename ); + return NULL; + } + + if( !Q_stricmp( ext, "tga" )) + pic = Image_LoadTGA( filename, buf, fileSize ); + else if( !Q_stricmp( ext, "bmp" )) + pic = Image_LoadBMP( filename, buf, fileSize ); + else MsgDev( D_ERROR, "COM_LoadImage: unsupported format (%s)\n", ext ); + + return pic; // may be NULL +} + +/* +============================================================================= + + IMAGE PROCESSING + +============================================================================= +*/ +#define TRANSPARENT_R 0x0 +#define TRANSPARENT_G 0x0 +#define TRANSPARENT_B 0xFF +#define IS_TRANSPARENT( p ) ( p[0] == TRANSPARENT_R && p[1] == TRANSPARENT_G && p[2] == TRANSPARENT_B ) +#define LERPBYTE( i ) r = resamplerow1[i]; out[i] = (byte)(((( resamplerow2[i] - r ) * lerp)>>16 ) + r ) + +static void Image_Resample32LerpLine( const byte *in, byte *out, int inwidth, int outwidth ) +{ + int j, xi, oldx = 0, f, fstep, endx, lerp; + + fstep = (int)(inwidth * 65536.0f / outwidth); + endx = (inwidth-1); + + for( j = 0, f = 0; j < outwidth; j++, f += fstep ) + { + xi = f>>16; + if( xi != oldx ) + { + in += (xi - oldx) * 4; + oldx = xi; + } + if( xi < endx ) + { + lerp = f & 0xFFFF; + *out++ = (byte)((((in[4] - in[0]) * lerp)>>16) + in[0]); + *out++ = (byte)((((in[5] - in[1]) * lerp)>>16) + in[1]); + *out++ = (byte)((((in[6] - in[2]) * lerp)>>16) + in[2]); + *out++ = (byte)((((in[7] - in[3]) * lerp)>>16) + in[3]); + } + else // last pixel of the line has no pixel to lerp to + { + *out++ = in[0]; + *out++ = in[1]; + *out++ = in[2]; + *out++ = in[3]; + } + } +} + +void Image_Resample32Lerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight ) +{ + const byte *inrow; + int i, j, r, yi, oldy = 0, f, fstep, lerp, endy = (inheight - 1); + int inwidth4 = inwidth * 4; + int outwidth4 = outwidth * 4; + byte *out = (byte *)outdata; + byte *resamplerow1; + byte *resamplerow2; + + fstep = (int)(inheight * 65536.0f / outheight); + + resamplerow1 = (byte *)Mem_Alloc( outwidth * 4 * 2 ); + resamplerow2 = resamplerow1 + outwidth * 4; + + inrow = (const byte *)indata; + + Image_Resample32LerpLine( inrow, resamplerow1, inwidth, outwidth ); + Image_Resample32LerpLine( inrow + inwidth4, resamplerow2, inwidth, outwidth ); + + for( i = 0, f = 0; i < outheight; i++, f += fstep ) + { + yi = f >> 16; + + if( yi < endy ) + { + lerp = f & 0xFFFF; + + if( yi != oldy ) + { + inrow = (byte *)indata + inwidth4 * yi; + if( yi == ( oldy + 1 )) memcpy( resamplerow1, resamplerow2, outwidth4 ); + else Image_Resample32LerpLine( inrow, resamplerow1, inwidth, outwidth ); + Image_Resample32LerpLine( inrow + inwidth4, resamplerow2, inwidth, outwidth ); + oldy = yi; + } + + j = outwidth - 4; + + while( j >= 0 ) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + LERPBYTE( 4); + LERPBYTE( 5); + LERPBYTE( 6); + LERPBYTE( 7); + LERPBYTE( 8); + LERPBYTE( 9); + LERPBYTE(10); + LERPBYTE(11); + LERPBYTE(12); + LERPBYTE(13); + LERPBYTE(14); + LERPBYTE(15); + out += 16; + resamplerow1 += 16; + resamplerow2 += 16; + j -= 4; + } + + if( j & 2 ) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + LERPBYTE( 4); + LERPBYTE( 5); + LERPBYTE( 6); + LERPBYTE( 7); + out += 8; + resamplerow1 += 8; + resamplerow2 += 8; + } + + if( j & 1 ) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + out += 4; + resamplerow1 += 4; + resamplerow2 += 4; + } + + resamplerow1 -= outwidth4; + resamplerow2 -= outwidth4; + } + else + { + if( yi != oldy ) + { + inrow = (byte *)indata + inwidth4 * yi; + if( yi == ( oldy + 1 )) memcpy( resamplerow1, resamplerow2, outwidth4 ); + else Image_Resample32LerpLine( inrow, resamplerow1, inwidth, outwidth ); + oldy = yi; + } + + memcpy( out, resamplerow1, outwidth4 ); + } + } + + Mem_Free( resamplerow1 ); +} + +void Image_Resample8Nolerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight ) +{ + int i, j; + byte *in, *inrow; + size_t frac, fracstep; + byte *out = (byte *)outdata; + + in = (byte *)indata; + fracstep = inwidth * 0x10000 / outwidth; + + for( i = 0; i < outheight; i++, out += outwidth ) + { + inrow = in + inwidth * (i * inheight / outheight); + frac = fracstep >> 1; + + for( j = 0; j < outwidth; j++ ) + { + out[j] = inrow[frac>>16]; + frac += fracstep; + } + } +} + +/* +================ +Image_Resample +================ +*/ +rgbdata_t *Image_Resample( rgbdata_t *pic, int new_width, int new_height ) +{ + if( !pic ) return NULL; + + // nothing to resample ? + if( pic->width == new_width && pic->height == new_height ) + return pic; + + MsgDev( D_REPORT, "Image_Resample: from %ix%i to %ix%i\n", pic->width, pic->height, new_width, new_height ); + + rgbdata_t *out = Image_Alloc( new_width, new_height, FBitSet( pic->flags, IMAGE_QUANTIZED )); + + if( FBitSet( pic->flags, IMAGE_QUANTIZED )) + Image_Resample8Nolerp( pic->buffer, pic->width, pic->height, out->buffer, out->width, out->height ); + else Image_Resample32Lerp( pic->buffer, pic->width, pic->height, out->buffer, out->width, out->height ); + + // copy remaining data from source + if( FBitSet( pic->flags, IMAGE_QUANTIZED )) + memcpy( out->palette, pic->palette, 768 ); + out->flags = pic->flags; + + // release old image + Mem_Free( pic ); + + return out; +} + +/* +============== +Image_MakeOneBitAlpha + +remap all pixels of color 0, 0, 255 to index 255 +and remap index 255 to something else +============== +*/ +void Image_MakeOneBitAlpha( rgbdata_t *pic ) +{ + byte transtable[256], *buf; + int i, j, firsttrans = -1; + + if( !pic || !FBitSet( pic->flags, IMAGE_QUANTIZED )) + return; // only for quantized images + + for( i = 0; i < 256; i++ ) + { + if( IS_TRANSPARENT(( pic->palette + ( i * 3 )))) + { + transtable[i] = 255; + if( firsttrans < 0 ) + firsttrans = i; + } + else transtable[i] = i; + } + + // if there is some transparency, translate it + if( 0 )//firsttrans >= 0 ) + { + if( !IS_TRANSPARENT(( pic->palette + ( 255 * 3 )))) + transtable[255] = firsttrans; + buf = pic->buffer; + + for( j = 0; j < pic->height; j++ ) + { + for( i = 0; i < pic->width; i++ ) + { + *buf = transtable[*buf]; + buf++; + } + } + + // move palette entry for pixels previously mapped to entry 255 + pic->palette[firsttrans*3+0] = pic->palette[255*3+0]; + pic->palette[firsttrans*3+1] = pic->palette[255*3+1]; + pic->palette[firsttrans*3+2] = pic->palette[255*3+2]; + pic->palette[255*3+0] = TRANSPARENT_R; + pic->palette[255*3+1] = TRANSPARENT_G; + pic->palette[255*3+2] = TRANSPARENT_B; + } + else + { + // jsut turn last color to blue + pic->palette[255*3+0] = TRANSPARENT_R; + pic->palette[255*3+1] = TRANSPARENT_G; + pic->palette[255*3+2] = TRANSPARENT_B; + } + + // needs for software mip generator + SetBits( pic->flags, IMAGE_HAS_1BIT_ALPHA ); +} + +/* +================ +Image_ApplyGamma + +we can't store alpha-channel into 8-bit texture +but we can store it separate as another image +================ +*/ +void Image_ApplyGamma( rgbdata_t *pic ) +{ + if( !pic || g_gamma == 1.8f ) + return; + + if( !FBitSet( pic->flags, IMAGE_QUANTIZED )) + return; // only for quantized images + + float g = g_gamma / 1.8; + + // gamma correct the monster textures to a gamma of 1.8 + for( int i = 0; i < 256; i++ ) + { + pic->palette[i*3+0] = pow( pic->palette[i*3+0] / 255.0f, g ) * 255; + pic->palette[i*3+1] = pow( pic->palette[i*3+1] / 255.0f, g ) * 255; + pic->palette[i*3+2] = pow( pic->palette[i*3+2] / 255.0f, g ) * 255; + } +} + +/* +================ +Image_Flip +================ +*/ +rgbdata_t *Image_Flip( rgbdata_t *src ) +{ + word width = src->width; + word height = src->height; + int samples = FBitSet( src->flags, IMAGE_QUANTIZED ) ? 1 : 4; + bool flip_x = FBitSet( src->flags, IMAGE_FLIP_X ) ? true : false; + bool flip_y = FBitSet( src->flags, IMAGE_FLIP_Y ) ? true : false; + bool flip_i = FBitSet( src->flags, IMAGE_ROT_90 ) ? true : false; + int row_inc = ( flip_y ? -samples : samples ) * width; + int col_inc = ( flip_x ? -samples : samples ); + int row_ofs = ( flip_y ? ( height - 1 ) * width * samples : 0 ); + int col_ofs = ( flip_x ? ( width - 1 ) * samples : 0 ); + const byte *p, *line; + int i, x, y; + rgbdata_t *dst; + byte *out; + + // nothing to process + if( !FBitSet( src->flags, IMAGE_FLIP_X|IMAGE_FLIP_Y|IMAGE_ROT_90 )) + return src; + + dst = Image_Copy( src ); + out = dst->buffer; + + if( flip_i ) + { + for( x = 0, line = src->buffer + col_ofs; x < width; x++, line += col_inc ) + for( y = 0, p = line + row_ofs; y < height; y++, p += row_inc, out += samples ) + for( i = 0; i < samples; i++ ) + out[i] = p[i]; + } + else + { + for( y = 0, line = src->buffer + row_ofs; y < height; y++, line += row_inc ) + for( x = 0, p = line + col_ofs; x < width; x++, p += col_inc, out += samples ) + for( i = 0; i < samples; i++ ) + out[i] = p[i]; + } + + // update bounds + if( FBitSet( src->flags, IMAGE_ROT_90 )) + { + dst->width = height; + dst->height = width; + } + else + { + dst->width = width; + dst->height = height; + } + Mem_Free( src ); // release source image + + return dst; +} \ No newline at end of file diff --git a/utils/spritegen/imagelib.h b/utils/spritegen/imagelib.h new file mode 100644 index 0000000..8e38783 --- /dev/null +++ b/utils/spritegen/imagelib.h @@ -0,0 +1,122 @@ +/* +imagelib.h - simple loader\serializer for TGA & BMP +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef IMAGELIB_H +#define IMAGELIB_H + +/* +======================================================================== + +.BMP image format + +======================================================================== +*/ +#pragma pack( 1 ) +typedef struct +{ + char id[2]; // bmfh.bfType + dword fileSize; // bmfh.bfSize + dword reserved0; // bmfh.bfReserved1 + bmfh.bfReserved2 + dword bitmapDataOffset; // bmfh.bfOffBits + dword bitmapHeaderSize; // bmih.biSize + int width; // bmih.biWidth + int height; // bmih.biHeight + word planes; // bmih.biPlanes + word bitsPerPixel; // bmih.biBitCount + dword compression; // bmih.biCompression + dword bitmapDataSize; // bmih.biSizeImage + dword hRes; // bmih.biXPelsPerMeter + dword vRes; // bmih.biYPelsPerMeter + dword colors; // bmih.biClrUsed + dword importantColors; // bmih.biClrImportant +} bmp_t; +#pragma pack( ) + +/* +======================================================================== + +.TGA image format (Truevision Targa) + +======================================================================== +*/ +#pragma pack( 1 ) +typedef struct tga_s +{ + byte id_length; + byte colormap_type; + byte image_type; + word colormap_index; + word colormap_length; + byte colormap_size; + word x_origin; + word y_origin; + word width; + word height; + byte pixel_size; + byte attributes; +} tga_t; +#pragma pack( ) + +#define IMAGE_MINWIDTH 1 // last mip-level is 1x1 +#define IMAGE_MINHEIGHT 1 +#define IMAGE_MAXWIDTH 4096 +#define IMAGE_MAXHEIGHT 4096 +#define MIP_MAXWIDTH 512 // large sizes it's too complicated for quantizer +#define MIP_MAXHEIGHT 512 // and provoked color degradation + +#define IMAGE_HAS_ALPHA (IMAGE_HAS_1BIT_ALPHA|IMAGE_HAS_8BIT_ALPHA) + +// rgbdata->flags +typedef enum +{ + IMAGE_QUANTIZED = BIT( 0 ), // this image already quantized + IMAGE_HAS_COLOR = BIT( 1 ), // image contain RGB-channel + IMAGE_HAS_1BIT_ALPHA = BIT( 2 ), // textures with '{' + IMAGE_HAS_8BIT_ALPHA = BIT( 3 ), // image contain full-range alpha-channel + + // Image_Process manipulation flags + IMAGE_FLIP_X = BIT( 6 ), // flip the image by width + IMAGE_FLIP_Y = BIT( 7 ), // flip the image by height + IMAGE_ROT_90 = BIT( 8 ), // flip from upper left corner to down right corner + IMAGE_ROT180 = IMAGE_FLIP_X|IMAGE_FLIP_Y, + IMAGE_ROT270 = IMAGE_FLIP_X|IMAGE_FLIP_Y|IMAGE_ROT_90, +} imgFlags_t; + +// loaded image +typedef struct rgbdata_s +{ + word width; // image width + word height; // image height + word flags; // misc image flags + byte *palette; // palette if present + byte *buffer; // image buffer + size_t size; // for bounds checking +} rgbdata_t; + +// common functions +rgbdata_t *Image_Alloc( int width, int height, bool paletted = false ); +rgbdata_t *Image_Copy( rgbdata_t *src ); +rgbdata_t *COM_LoadImage( const char *filename ); +rgbdata_t *COM_LoadImageMemory( const char *filename, const byte *buf, size_t fileSize ); +bool Image_ValidSize( const char *name, int width, int height ); +rgbdata_t *Image_Resample( rgbdata_t *pic, int new_width, int new_height ); +rgbdata_t *Image_Quantize( rgbdata_t *pic ); +void Image_MakeOneBitAlpha( rgbdata_t *pic ); +void Image_ApplyGamma( rgbdata_t *pic ); +rgbdata_t *Image_Flip( rgbdata_t *src ); + +extern float g_gamma; + +#endif//IMAGELIB_H \ No newline at end of file diff --git a/utils/spritegen/quantizer.cpp b/utils/spritegen/quantizer.cpp new file mode 100644 index 0000000..6866fdb --- /dev/null +++ b/utils/spritegen/quantizer.cpp @@ -0,0 +1,469 @@ +/* +quantizer.cpp - image quantizer. based on Antony Dekker original code +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include +#include "cmdlib.h" +#include "mathlib.h" +#include "stringlib.h" +#include "scriplib.h" +#include "filesystem.h" +#include "imagelib.h" + +#define netsize 256 // number of colours used +#define prime1 499 +#define prime2 491 +#define prime3 487 +#define prime4 503 + +#define minpicturebytes (3*prime4) // minimum size for input image + +#define maxnetpos (netsize-1) +#define netbiasshift 4 // bias for colour values +#define ncycles 100 // no. of learning cycles + +// defs for freq and bias +#define intbiasshift 16 // bias for fractions +#define intbias (1<>betashift) // beta = 1 / 1024 +#define betagamma (intbias<<(gammashift - betashift)) + +// defs for decreasing radius factor +#define initrad (netsize>>3) // for 256 cols, radius starts +#define radiusbiasshift 6 // at 32.0 biased by 6 bits +#define radiusbias (1<> netbiasshift; + if( temp > 255 ) temp = 255; + network[i][j] = temp; + } + network[i][3] = i; // record colour num + } +} + +// insertion sort of network and building of netindex[0..255] (to do after unbias) +void inxbuild( void ) +{ + register int *p, *q; + register int i, j, smallpos, smallval; + int previouscol, startpos; + + previouscol = 0; + startpos = 0; + + for( i = 0; i < netsize; i++ ) + { + p = network[i]; + smallpos = i; + smallval = p[1]; // index on g + + // find smallest in i..netsize-1 + for( j = i + 1; j < netsize; j++ ) + { + q = network[j]; + + if( q[1] < smallval ) + { + // index on g + smallpos = j; + smallval = q[1]; // index on g + } + } + + q = network[smallpos]; + + // swap p (i) and q (smallpos) entries + if( i != smallpos ) + { + j = q[0]; q[0] = p[0]; p[0] = j; + j = q[1]; q[1] = p[1]; p[1] = j; + j = q[2]; q[2] = p[2]; p[2] = j; + j = q[3]; q[3] = p[3]; p[3] = j; + } + + // smallval entry is now in position i + if( smallval != previouscol ) + { + netindex[previouscol] = (startpos+i) >> 1; + + for( j = previouscol + 1; j < smallval; j++ ) + netindex[j] = i; + + previouscol = smallval; + startpos = i; + } + } + + netindex[previouscol] = (startpos + maxnetpos)>>1; + + for( j = previouscol + 1; j < 256; j++ ) + netindex[j] = maxnetpos; // really 256 +} + +// search for BGR values 0..255 (after net is unbiased) and return colour index +int inxsearch( int r, int g, int b ) +{ + register int i, j, dist, a, bestd; + register int *p; + int best; + + bestd = 1000; // biggest possible dist is 256 * 3 + best = -1; + i = netindex[g]; // index on g + j = i - 1; // start at netindex[g] and work outwards + + while(( i < netsize ) || ( j >= 0 )) + { + if( i < netsize ) + { + p = network[i]; + dist = p[1] - g; // inx key + + if( dist >= bestd ) + { + i = netsize; // stop iter + } + else + { + i++; + if( dist < 0 ) dist = -dist; + a = p[2] - b; + if( a < 0 ) a = -a; + dist += a; + + if( dist < bestd ) + { + a = p[0] - r; + if( a < 0 ) a = -a; + dist += a; + + if( dist < bestd ) + { + bestd = dist; + best = p[3]; + } + } + } + } + + if( j >= 0 ) + { + p = network[j]; + dist = g - p[1]; // inx key - reverse dif + + if( dist >= bestd ) + { + j = -1; // stop iter + } + else + { + j--; + if( dist < 0 ) dist = -dist; + a = p[2] - b; + if( a < 0 ) a = -a; + dist += a; + + if( dist < bestd ) + { + a = p[0] - r; + if( a < 0 ) a = -a; + dist += a; + if( dist < bestd ) + { + bestd = dist; + best = p[3]; + } + } + } + } + } + + return best; +} + +// search for biased BGR values +int contest( int r, int g, int b ) +{ + // finds closest neuron (min dist) and updates freq + // finds best neuron (min dist-bias) and returns position + // for frequently chosen neurons, freq[i] is high and bias[i] is negative + // bias[i] = gamma * ((1 / netsize) - freq[i]) + + register int *p, *f, *n; + register int i, dist, a, biasdist, betafreq; + int bestpos, bestbiaspos, bestd, bestbiasd; + + bestd = ~(1<<31); + bestbiasd = bestd; + bestpos = -1; + bestbiaspos = bestpos; + p = bias; + f = freq; + + for( i = 0; i < netsize; i++ ) + { + n = network[i]; + dist = n[2] - b; + if( dist < 0 ) dist = -dist; + a = n[1] - g; + if( a < 0 ) a = -a; + dist += a; + a = n[0] - r; + if( a < 0 ) a = -a; + dist += a; + + if( dist < bestd ) + { + bestd = dist; + bestpos = i; + } + + biasdist = dist - ((*p) >> (intbiasshift - netbiasshift)); + + if( biasdist < bestbiasd ) + { + bestbiasd = biasdist; + bestbiaspos = i; + } + + betafreq = (*f >> betashift); + *f++ -= betafreq; + *p++ += (betafreq << gammashift); + } + + freq[bestpos] += beta; + bias[bestpos] -= betagamma; + + return bestbiaspos; +} + +// move neuron i towards biased (b,g,r) by factor alpha +void altersingle( int alpha, int i, int r, int g, int b ) +{ + register int *n; + + n = network[i]; // alter hit neuron + *n -= (alpha * (*n - r)) / initalpha; + n++; + *n -= (alpha * (*n - g)) / initalpha; + n++; + *n -= (alpha * (*n - b)) / initalpha; +} + +// move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|] +void alterneigh( int rad, int i, int r, int g, int b ) +{ + register int j, k, lo, hi, a; + register int *p, *q; + + lo = i - rad; + if( lo < -1 ) + lo = -1; + + hi = i + rad; + if( hi > netsize ) + hi = netsize; + + j = i + 1; + k = i - 1; + q = radpower; + + while(( j < hi ) || ( k > lo )) + { + a = (*(++q)); + + if( j < hi ) + { + p = network[j]; + *p -= (a * (*p - r)) / alpharadbias; + p++; + *p -= (a * (*p - g)) / alpharadbias; + p++; + *p -= (a * (*p - b)) / alpharadbias; + j++; + } + + if( k > lo ) + { + p = network[k]; + *p -= (a * (*p - r)) / alpharadbias; + p++; + *p -= (a * (*p - g)) / alpharadbias; + p++; + *p -= (a * (*p - b)) / alpharadbias; + k--; + } + } +} + +// main Learning Loop +void learn( void ) +{ + register byte *p; + register int i, j, r, g, b; + int radius, rad, alpha, step; + int delta, samplepixels; + byte *lim; + + alphadec = 30 + ((samplefac - 1) / 3); + p = thepicture; + lim = thepicture + lengthcount; + samplepixels = lengthcount / (samplefac * 4); // RGBA + delta = samplepixels / ncycles; + alpha = initalpha; + radius = initradius; + + rad = radius >> radiusbiasshift; + if( rad <= 1 ) rad = 0; + + for( i = 0; i < rad; i++ ) + radpower[i] = alpha * ((( rad * rad - i * i ) * radbias ) / ( rad * rad )); + + if( delta <= 0 ) return; + + if(( lengthcount % prime1 ) != 0 ) + { + step = prime1 * 4; // RGBA + } + else if(( lengthcount % prime2 ) != 0 ) + { + step = prime2 * 4; // RGBA + } + else if(( lengthcount % prime3 ) != 0 ) + { + step = prime3 * 4; // RGBA + } + else + { + step = prime4 * 4; // RGBA; + } + + i = 0; + + while( i < samplepixels ) + { + r = p[0] << netbiasshift; + g = p[1] << netbiasshift; + b = p[2] << netbiasshift; + j = contest( r, g, b ); + + altersingle( alpha, j, r, g, b ); + if( rad ) alterneigh( rad, j, r, g, b ); // alter neighbours + + p += step; + if( p >= lim ) p -= lengthcount; + + i++; + + if( i % delta == 0 ) + { + alpha -= alpha / alphadec; + radius -= radius / radiusdec; + rad = radius >> radiusbiasshift; + if( rad <= 1 ) rad = 0; + + for( j = 0; j < rad; j++ ) + radpower[j] = alpha * ((( rad * rad - j * j ) * radbias ) / ( rad * rad )); + } + } +} + +// returns the actual number of palette entries. +rgbdata_t *Image_Quantize( rgbdata_t *pic ) +{ + rgbdata_t *out; + int i, samples; + + if( !pic ) return NULL; + + // already quantized? + if( FBitSet( pic->flags, IMAGE_QUANTIZED )) + return pic; + + out = Image_Alloc( pic->width, pic->height, true ); + out->flags = pic->flags; + + if( FBitSet( pic->flags, IMAGE_HAS_COLOR )) + samples = 1; // maximum quality + else samples = 10; // fast mode + + initnet( pic->buffer, pic->size, samples ); + learn(); + unbiasnet(); + + for( i = 0; i < netsize; i++ ) + { + out->palette[i*3+0] = network[i][0]; // red + out->palette[i*3+1] = network[i][1]; // green + out->palette[i*3+2] = network[i][2]; // blue + } + + inxbuild(); + + for( i = 0; i < pic->width * pic->height; i++ ) + out->buffer[i] = inxsearch( pic->buffer[i*4+0], pic->buffer[i*4+1], pic->buffer[i*4+2] ); + Mem_Free( pic ); // release RGBA image + + return out; +} \ No newline at end of file diff --git a/utils/spritegen/spritegen.cpp b/utils/spritegen/spritegen.cpp new file mode 100644 index 0000000..eaf2c06 --- /dev/null +++ b/utils/spritegen/spritegen.cpp @@ -0,0 +1,927 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +****/ + +// +// spritegen.c: generates a .spr file from a series of .lbm frame files. +// Result is stored in /raid/quake/id1/sprites/.spr. +// +#include +#include "cmdlib.h" +#include "mathlib.h" +#include "stringlib.h" +#include "scriplib.h" +#include "imagelib.h" +#include "sprite.h" +#include "filesystem.h" + +#define MIN_INTERVAL 0.001f +#define MAX_INTERVAL 64.0f +#define MAX_BUFFER_SIZE (10 * 1024 * 1024) // 10 Mb for now +#define MAX_FRAMES 512 + +dsprite_t sprite; +rgbdata_t *frame = NULL; +byte *lumpbuffer = NULL, *plump; +static byte basepalette[256*3]; +char spritedir[1024]; +char spriteoutname[1024]; +int framesmaxs[2]; +int framecount; +float g_gamma = 1.8f; +float frameinterval; +int origin_x; +int origin_y; +bool need_resample; +bool ignore_resample; +int resample_w; +int resample_h; + +typedef struct +{ + frametype_t type; // single frame or group of frames + void *pdata; // either a dspriteframe_t or group info + float interval; // only used for frames in groups + int numgroupframes; // only used by group headers +} spritepackage_t; + +spritepackage_t frames[MAX_FRAMES]; + +int verify_atoi( const char *token ) +{ + if( token[0] != '-' && ( token[0] < '0' || token[0] > '9' )) + { + TokenError( "expecting number, got \"%s\"\n", token ); + } + return atoi( token ); +} + +float verify_atof( const char *token ) +{ + if( token[0] != '-' && token[0] != '.' && ( token[0] < '0' || token[0] > '9' )) + { + TokenError( "expecting number, got \"%s\"\n", token ); + } + return atof( token ); +} + +/* +============ +WriteFrame +============ +*/ +void WriteFrame( vfile_t *file, int framenum ) +{ + dspriteframe_t *pframe; + dspriteframe_t frametemp; + + pframe = (dspriteframe_t *)frames[framenum].pdata; + frametemp.origin[0] = pframe->origin[0]; + frametemp.origin[1] = pframe->origin[1]; + frametemp.width = pframe->width; + frametemp.height = pframe->height; + + VFS_Write( file, &frametemp, sizeof( frametemp )); + VFS_Write( file, (byte *)(pframe + 1), pframe->height * pframe->width ); +} + +/* +============ +WriteSprite +============ +*/ +void WriteSprite( vfile_t *file ) +{ + int i, groupframe, curframe; + dsprite_t spritetemp; + short cnt = 256; + + sprite.boundingradius = sqrt((( framesmaxs[0] >> 1 ) * ( framesmaxs[0] >> 1 )) + (( framesmaxs[1] >> 1 ) * ( framesmaxs[1] >> 1 ))); + + // write out the sprite header + spritetemp.type = sprite.type; + spritetemp.texFormat = sprite.texFormat; + spritetemp.boundingradius = sprite.boundingradius; + spritetemp.bounds[0] = framesmaxs[0]; + spritetemp.bounds[1] = framesmaxs[1]; + spritetemp.numframes = sprite.numframes; + spritetemp.beamlength = sprite.beamlength; + spritetemp.synctype = sprite.synctype; + spritetemp.version = SPRITE_VERSION; + spritetemp.ident = IDSPRITEHEADER; + + VFS_Write( file, &spritetemp, sizeof( spritetemp )); + + // Write out palette in 16bit mode + VFS_Write( file, (void *)&cnt, sizeof( cnt )); + VFS_Write( file, basepalette, sizeof( basepalette )); + + // write out the frames + curframe = 0; + + for( i = 0; i < sprite.numframes; i++ ) + { + VFS_Write( file, &frames[curframe].type, sizeof( frames[curframe].type )); + + if( frames[curframe].type == FRAME_SINGLE ) + { + // single (non-grouped) frame + WriteFrame( file, curframe ); + curframe++; + } + else + { + int j, numframes; + float totinterval; + dspritegroup_t dsgroup; + + groupframe = curframe; + curframe++; + numframes = frames[groupframe].numgroupframes; + + // set and write the group header + dsgroup.numframes = numframes; + + VFS_Write( file, &dsgroup, sizeof( dsgroup )); + + // write the interval array + totinterval = 0.0f; + + for( j = 0; j < numframes; j++ ) + { + dspriteinterval_t temp; + + totinterval += frames[groupframe+1+j].interval; + temp.interval = totinterval; + + VFS_Write( file, &temp, sizeof( temp )); + } + + for( j = 0; j < numframes; j++ ) + { + WriteFrame( file, curframe ); + curframe++; + } + } + } +} + +/* +============== +ResetSpriteInfo +============== +*/ +void ResetSpriteInfo( void ) +{ + // set default sprite parms + spriteoutname[0] = 0; + + memset( &sprite, 0, sizeof( sprite )); + memset( frames, 0, sizeof( frames )); + memset( basepalette, 0, sizeof( basepalette )); + framecount = origin_x = origin_y = 0; + frameinterval = 0.0f; + g_gamma = 1.8f; + + framesmaxs[0] = -9999; + framesmaxs[1] = -9999; + sprite.type = SPR_FWD_PARALLEL; + sprite.synctype = ST_RAND; // default + if( frame ) Mem_Free( frame ); + frame = NULL; +} + +/* +============== +WriteSPRFile +============== +*/ +void WriteSPRFile( void ) +{ + int i, groups = 0, grpframes = 0; + int sngframes = framecount; + + if( sprite.numframes == 0 ) + COM_FatalError( "WriteSPRFile: no frames\n" ); + + if( !Q_strlen( spriteoutname )) + COM_FatalError( "WriteSPRFile: didn't name sprite file\n" ); + + vfile_t *f = VFS_Create(); + WriteSprite( f ); + + Msg( "writing: %s\n", spriteoutname ); + COM_SaveFile( spriteoutname, VFS_GetBuffer( f ), VFS_Tell( f ), true ); + + VFS_Close( f ); + + // count frames + for( i = 0; i < framecount; i++ ) + { + if( frames[i].numgroupframes ) + { + sngframes -= frames[i].numgroupframes; + grpframes += frames[i].numgroupframes; + groups++; + } + } + + // display info about current sprite + if( groups ) + { + Msg( "%d group%s,", groups, groups > 1 ? "s":"" ); + Msg( " contain %d frame%s\n", grpframes, grpframes > 1 ? "s":"" ); + } + + if( sngframes - groups ) + Msg( "%d ungrouped frame%s\n", sngframes - groups, (sngframes - groups) > 1 ? "s" : "" ); + + ResetSpriteInfo(); + + if(( plump - lumpbuffer ) > MAX_BUFFER_SIZE ) + COM_FatalError( "WriteSPRFile: memory buffer is corrupted. Check the sprite!\n" ); +} + +/* +============== +Cmd_Spritename +============== +*/ +void Cmd_Spritename( void ) +{ + if( sprite.numframes ) + WriteSPRFile(); + + GetToken( false ); + Q_snprintf( spriteoutname, sizeof( spriteoutname ), "%s/%s", spritedir, token ); + COM_DefaultExtension( spriteoutname, ".spr" ); + + if( !lumpbuffer ) + lumpbuffer = (byte *)Mem_Alloc( MAX_BUFFER_SIZE ); + else memset( lumpbuffer, 0, MAX_BUFFER_SIZE ); + + plump = lumpbuffer; +} + +/* +============== +LoadScreen +============== +*/ +void LoadScreen( const char *filename, const char *displayname ) +{ + if( frame != NULL ) + Mem_Free( frame ); // release previous frame + + if( !COM_FileExists( filename )) + COM_FatalError( "%s doesn't exist\n", filename ); + + MsgDev( D_INFO, "grabbing: %s\t\t%s\n", filename, displayname ); + frame = COM_LoadImage( filename ); + if( !frame ) COM_FatalError( "%s couldn't load", filename ); // ??? + + // get support for doom-angled sprites + while( TryToken( )) + { + if( !Q_stricmp( token, "flip_diagonal" )) + SetBits( frame->flags, IMAGE_ROT_90 ); + else if( !Q_stricmp( token, "flip_y" )) + SetBits( frame->flags, IMAGE_FLIP_Y ); + else if( !Q_stricmp( token, "flip_x" )) + SetBits( frame->flags, IMAGE_FLIP_X ); + } + + int new_width = Q_min( frame->width, MIP_MAXWIDTH ); + int new_height = Q_min( frame->height, MIP_MAXHEIGHT ); + + // TODO: merge all frames into single image, quantize it and then deconstruct back to single frames + // TODO: only for truecolor images... + frame = Image_Resample( frame, new_width, new_height ); + frame = Image_Quantize( frame ); // quantize if needs + frame = Image_Flip( frame ); // rotate image if desired + + if( sprite.texFormat == SPR_ALPHTEST ) + Image_MakeOneBitAlpha( frame ); // check alpha + + if( sprite.numframes == 0 ) + memcpy( basepalette, frame->palette, sizeof( basepalette )); + else if( memcmp( basepalette, frame->palette, sizeof( basepalette ))) + MsgDev( D_WARN, "\"%s\" doesn't share a pallette with the previous bitmap\n", filename ); +} + +/* +=============== +Cmd_Type + +syntax: "$type preset" +=============== +*/ +void Cmd_Type( void ) +{ + GetToken( false ); + + if( !Q_stricmp( token, "vp_parallel_upright" )) + sprite.type = SPR_FWD_PARALLEL_UPRIGHT; + else if( !Q_stricmp( token, "facing_upright" )) + sprite.type = SPR_FACING_UPRIGHT; + else if( !Q_stricmp( token, "vp_parallel" )) + sprite.type = SPR_FWD_PARALLEL; + else if( !Q_stricmp( token, "oriented" )) + sprite.type = SPR_ORIENTED; + else if( !Q_stricmp( token, "vp_parallel_oriented" )) + sprite.type = SPR_FWD_PARALLEL_ORIENTED; + else COM_FatalError( "bad sprite type\n" ); +} + +/* +=============== +Cmd_Texture + +syntax: "$texture preset" +syntax: "$rendermode preset" +=============== +*/ +void Cmd_Texture( void ) +{ + GetToken( false ); + + if( !Q_stricmp( token, "additive" )) + sprite.texFormat = SPR_ADDITIVE; + else if( !Q_stricmp( token, "normal" )) + sprite.texFormat = SPR_NORMAL; + else if( !Q_stricmp( token, "indexalpha" )) + sprite.texFormat = SPR_INDEXALPHA; + else if( !Q_stricmp( token, "alphatest" )) + sprite.texFormat = SPR_ALPHTEST; + else COM_FatalError( "bad sprite texture type\n" ); +} + +/* +=============== +Cmd_Beamlength +=============== +*/ +void Cmd_Beamlength( void ) +{ + GetToken( false ); + sprite.beamlength = verify_atof( token ); +} + +/* +=============== +Cmd_Framerate + +syntax: "$framerate value" +=============== +*/ +void Cmd_Framerate( void ) +{ + GetToken( false ); + float framerate = verify_atof( token ); + if( framerate <= 0.0f ) COM_FatalError( "bad framerate %g\n", framerate ); + frameinterval = bound( MIN_INTERVAL, (1.0f / framerate), MAX_INTERVAL ); +} + +/* +=============== +Cmd_Resample + +syntax: "$resample " +=============== +*/ +void Cmd_Resample( void ) +{ + GetToken( false ); + resample_w = verify_atoi( token ); + GetToken( false ); + resample_h = verify_atoi( token ); + + if( !ignore_resample ) + need_resample = true; +} + +/* +=============== +Cmd_NoResample + +syntax: "$noresample" +=============== +*/ +void Cmd_NoResample( void ) +{ + ignore_resample = true; +} + +/* +=============== +Cmd_Load + +syntax "$load fire01.bmp" +=============== +*/ +void Cmd_Load( const char *displayname ) +{ + char path[1024]; + + GetToken( false ); + + Q_snprintf( path, sizeof( path ), "%s/%s", spritedir, token ); + LoadScreen( path, displayname );//COM_ExpandArg( token )); +} + + +/* +=============== +Cmd_Origin + +syntax: $origin "x_pos y_pos" +=============== +*/ +static void Cmd_Origin( void ) +{ + GetToken( false ); + origin_x = verify_atoi( token ); + GetToken( false ); + origin_y = verify_atoi( token ); +} + +/* +=============== +Cmd_Frame + +syntax "$frame xoffset yoffset width height " +=============== +*/ +void Cmd_Frame( void ) +{ + int x, y, xl, yl, xh, yh, w, h; + byte *screen_p, *source; + bool resampled = false; + int org_x, org_y; + int linedelta; + dspriteframe_t *pframe; + int pix; + + if( !frame || !frame->buffer ) + COM_FatalError( "frame not loaded\n" ); + + if( framecount >= MAX_FRAMES ) + COM_FatalError( "too many frames in package\n" ); + + GetToken( false ); + xl = verify_atoi( token ); + GetToken( false ); + yl = verify_atoi( token ); + GetToken( false ); + w = verify_atoi( token ); + GetToken( false ); + h = verify_atoi( token ); + + // merge bounds + if( xl <= 0 || xl > frame->width ) + xl = 0; + if( yl <= 0 || yl > frame->width ) + yl = 0; + if( w <= 0 || w > frame->width ) + w = frame->width; + if( h <= 0 || h > frame->height ) + h = frame->height; + + if(( xl & 0x07 ) || ( yl & 0x07 ) || ( w & 0x07 ) || ( h & 0x07 )) + { + if( need_resample ) + { + rgbdata_t *dst = Image_Resample( frame, resample_w, resample_h ); + if( frame != dst ) resampled = true; + frame = dst; + } + if( !resampled ) MsgDev( D_WARN, "frame dimensions not multiples of 8\n" ); + } + + if(( w > MIP_MAXWIDTH ) || ( h > MIP_MAXHEIGHT )) + COM_FatalError( "sprite has a dimension longer than %dx%d\n", MIP_MAXWIDTH, MIP_MAXHEIGHT ); + + // get interval + if( TokenAvailable( )) + { + GetToken( false ); + frames[framecount].interval = verify_atof( token ); + if( frames[framecount].interval <= 0.0f ) MsgDev( D_WARN, "non-positive interval\n" ); + frames[framecount].interval = bound( MIN_INTERVAL, frames[framecount].interval, MAX_INTERVAL ); + } + else if( frameinterval != 0.0f ) + { + frames[framecount].interval = frameinterval; + } + else + { + // use default interval + frames[framecount].interval = (float)0.1f; + } + + if( TokenAvailable( )) + { + GetToken (false); + org_x = -verify_atoi( token ); + GetToken( false ); + org_y = verify_atoi( token ); + } + else if(( origin_x != 0 ) && ( origin_y != 0 )) + { + // write shared origin + org_x = -origin_x; + org_y = origin_y; + } + else + { + // use center of rectangle + org_x = -(w >> 1); + org_y = h >> 1; + } + + // merge all sprite info + if( need_resample && resampled ) + { + // check for org[n] == size[n] and org[n] == size[n]/2 + // another cases will be not changed + if( org_x == -w ) + org_x = -frame->width; + else if( org_x == -( w >> 1 )) + org_x = -frame->width >> 1; + if( org_y == h ) + org_y = frame->height; + else if( org_y == ( h >> 1 )) + org_y = frame->height >> 1; + + w = frame->width; + h = frame->height; + } + + xh = xl + w; + yh = yl + h; + + pframe = (dspriteframe_t *)plump; + frames[framecount].pdata = pframe; + frames[framecount].type = FRAME_SINGLE; + + pframe->origin[0] = org_x; + pframe->origin[1] = org_y; + pframe->width = w; + pframe->height = h; + + if( w > framesmaxs[0] ) framesmaxs[0] = w; + if( h > framesmaxs[1] ) framesmaxs[1] = h; + + plump = (byte *)(pframe + 1); + + screen_p = frame->buffer + yl * frame->width + xl; + linedelta = frame->width - w; + source = plump; + + for( y = yl; y < yh; y++ ) + { + for( x = xl; x < xh; x++ ) + { + pix = *screen_p; + *screen_p++ = 0; // DEBUG +// if( pix == 255 ) +// pix = 0; + *plump++ = pix; + } + screen_p += linedelta; + } + framecount++; +} + +/* +=============== +Cmd_Group + +syntax: +$group or $angled +{ + $load fire01.bmp + $frame xoffset yoffset width height + $load fire02.bmp + $frame xoffset yoffset width height " + $load fire03.bmp + $frame xoffset yoffset width height +} +=============== +*/ +void Cmd_Group( bool angled ) +{ + int groupframe; + int depth = 0; + + groupframe = framecount++; + + frames[groupframe].type = angled ? FRAME_ANGLED : FRAME_GROUP; + resample_w = resample_h = 0; // invalidate resample for group + frames[groupframe].numgroupframes = 0; + need_resample = false; + + while( 1 ) + { + GetToken( true ); + + if( endofscript ) + { + if( depth != 0 ) + COM_FatalError( "missing }\n" ); + break; + } + + if( !Q_stricmp( token, "{" )) + { + depth++; + } + else if( !Q_stricmp( token, "}" )) + { + depth--; + break; // end of group + } + else if( !Q_stricmp( token, "$framerate" )) + { + Cmd_Framerate(); + } + else if( !Q_stricmp( token, "$resample" )) + { + Cmd_Resample(); + } + else if( !Q_stricmp( token, "$frame" )) + { + Cmd_Frame(); + frames[groupframe].numgroupframes++; + } + else if( !Q_stricmp( token, "$load" )) + { + Cmd_Load( angled ? "[^3angled frame^7]" : "[^2group frame^7]" ); + } + else + { + while( TryToken( )); // skip unknown commands + } + } + + if( depth != 0 ) + COM_FatalError( "missing }\n" ); + + if( frames[groupframe].numgroupframes == 0 ) + { + // don't create blank groups, rewind frames + MsgDev( D_WARN, "Cmd_Group: remove blank group\n" ); + sprite.numframes--; + framecount--; + } + else if( angled && frames[groupframe].numgroupframes != 8 ) + { + // don't create blank groups, rewind frames + MsgDev(D_WARN, "Cmd_Group: remove angled group with invalid framecount\n" ); + sprite.numframes--; + framecount--; + } + + // back to single frames, invalidate resample + resample_w = resample_h = 0; + need_resample = false; +} + +/* +=============== +Cmd_GroupStart + +syntax: +$groupstart + $load fire01.bmp + $frame xoffset yoffset width height + $load fire02.bmp + $frame xoffset yoffset width height " + $load fire03.bmp + $frame xoffset yoffset width height +$groupend +=============== +*/ +void Cmd_GroupStart( void ) +{ + int groupframe; + + groupframe = framecount++; + resample_w = resample_h = 0; // invalidate resample for group + frames[groupframe].type = FRAME_GROUP; + frames[groupframe].numgroupframes = 0; + need_resample = false; + + while( 1 ) + { + GetToken( true ); + + if( endofscript ) + COM_FatalError( "end of file during group\n" ); + + if( !Q_stricmp( token, "$frame" )) + { + Cmd_Frame(); + frames[groupframe].numgroupframes++; + } + else if( !Q_stricmp( token, "$load" )) + { + Cmd_Load( "[^2group frame^7]" ); + } + else if( !Q_stricmp( token, "$framerate" )) + { + Cmd_Framerate(); + } + else if( !Q_stricmp( token, "$resample" )) + { + Cmd_Resample(); + } + else if( !Q_stricmp( token, "$groupend" )) + { + break; + } + else + { + while( TryToken( )); // skip unknown commands + } + + } + + if( frames[groupframe].numgroupframes == 0 ) + { + // don't create blank groups, rewind frames + MsgDev( D_WARN, "remove blank group\n" ); + sprite.numframes--; + framecount--; + } + + // back to single frames, invalidate resample + resample_w = resample_h = 0; + need_resample = false; +} + +/* +=============== +ParseScript +=============== +*/ +void ParseScript( void ) +{ + while( 1 ) + { + GetToken( true ); + + if( endofscript ) + break; + + if( !Q_stricmp( token, "$load" )) + { + Cmd_Load ( "[^1frame^7]" ); + } + else if( !Q_stricmp( token, "$spritename" )) + { + Cmd_Spritename (); + } + else if( !Q_stricmp( token, "$type" )) + { + Cmd_Type (); + } + else if( !Q_stricmp( token, "$texture" )) + { + Cmd_Texture (); + } + else if( !Q_stricmp( token, "$beamlength" )) + { + Cmd_Beamlength (); + } + else if( !Q_stricmp( token, "$origin" )) + { + Cmd_Origin(); + } + else if( !Q_stricmp( token, "$sync" )) + { + sprite.synctype = ST_SYNC; + } + else if( !Q_stricmp( token, "$frame" )) + { + Cmd_Frame(); + sprite.numframes++; + } + else if( !Q_stricmp( token, "$group" )) + { + Cmd_Group( false ); + sprite.numframes++; + } + else if( !Q_stricmp( token, "$angled" )) + { + Cmd_Group( true ); + sprite.numframes++; + } + else if( !Q_stricmp( token, "$groupstart" )) + { + Cmd_GroupStart(); + sprite.numframes++; + } + else if( !Q_stricmp( token, "$noresample" )) + { + Cmd_NoResample(); + } + else if( !Q_stricmp( token, "$resample" )) + { + Cmd_Resample(); + } + else + { + while( TryToken( )); // skip unknown commands + } + } +} + +/* +============== +main + +============== +*/ +int main( int argc, char **argv ) +{ + int i; + char path[1024]; + bool log_append = false; + bool log_exist = false; + + atexit( Sys_CloseLog ); + COM_InitCmdlib( argv, argc ); + + if( argc == 1 ) + { + Msg( " P2:Savior Sprite Model Compiler\n" ); + Msg( " XashXT Group 2018(^1c^7)\n\n\n" ); + + Msg( "usage: spritegen file.qc\n" + "\nlist options:\n" + "^2-a^7 - append logfile\n" + "^2-dev^7 - shows developer messages\n" + "\n\t\tPress any key to exit" ); + + system( "pause>nul" ); + return 1; + } + + for( i = 1; i < argc - 1; i++ ) + { + if( argv[i][0] == '-' ) + { + if( !Q_stricmp( argv[i], "-dev" )) + { + SetDeveloperLevel( verify_atoi( argv[i+1] )); + i++; + continue; + } + } + switch( argv[i][1] ) + { + case 'a': + log_append = true; + break; + } + } + + if( !argv[i] ) return 1; + + log_exist = COM_FileExists( "spritegen.log" ); + + if( log_append ) + Sys_InitLogAppend( "spritegen.log" ); + else Sys_InitLog( "spritegen.log" ); + + if( !log_exist || !log_append ) + { + Msg( " P2:Savior Sprite Model Compiler\n" ); + Msg( " XashXT Group 2018(^1c^7)\n\n" ); + } + + // load the script + Q_strncpy( path, argv[i], sizeof( path )); + COM_ExtractFilePath( path, spritedir ); + COM_DefaultExtension( path, ".qc" ); + Msg( "parse: %s\n", path ); + LoadScriptFile( path ); + + // parse it + ParseScript (); + WriteSPRFile (); + Mem_Free( lumpbuffer ); + + SetDeveloperLevel( D_REPORT ); + Mem_Check(); // report leaks + if( log_append ) Msg( "\n" ); + + return 0; +} \ No newline at end of file diff --git a/utils/spritegen/spritegen.dsp b/utils/spritegen/spritegen.dsp new file mode 100644 index 0000000..90b4d32 --- /dev/null +++ b/utils/spritegen/spritegen.dsp @@ -0,0 +1,178 @@ +# Microsoft Developer Studio Project File - Name="spritegen" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=spritegen - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "spritegen.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "spritegen.mak" CFG="spritegen - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "spritegen - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "spritegen - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/SDKSrc/Tools/utils/spritegen", IVGBAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "spritegen - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir ".\Release" +# PROP BASE Intermediate_Dir ".\Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\..\temp\spritegen\!release" +# PROP Intermediate_Dir "..\..\temp\spritegen\!release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MD /W3 /GX /O2 /I "../common" /I "..\..\engine" /I "../../common" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "IGNORE_SEARCH_IN_WADS" /FD /c +# SUBTRACT CPP /Fr /YX +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 msvcrt.lib /nologo /subsystem:console /pdb:none /machine:I386 /nodefaultlib:"libc.lib" +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\spritegen\!release +InputPath=\Paranoia2\src_main\temp\spritegen\!release\spritegen.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\spritegen.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\spritegen.exe "D:\Paranoia2\tools\spritegen.exe" + +# End Custom Build + +!ELSEIF "$(CFG)" == "spritegen - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir ".\Debug" +# PROP BASE Intermediate_Dir ".\Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\..\temp\spritegen\!debug" +# PROP Intermediate_Dir "..\..\temp\spritegen\!debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MDd /W3 /Gm /Gi /GX /ZI /Od /I "../common" /I "..\..\engine" /I "../../common" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "IGNORE_SEARCH_IN_WADS" /FAs /FR /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 +# ADD LINK32 msvcrtd.lib /nologo /subsystem:console /debug /machine:I386 /nodefaultlib:"libcd.lib" +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\spritegen\!debug +InputPath=\Paranoia2\src_main\temp\spritegen\!debug\spritegen.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\spritegen.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\spritegen.exe "D:\Paranoia2\tools\spritegen.exe" + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "spritegen - Win32 Release" +# Name "spritegen - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=..\common\cmdlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\conprint.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\filesystem.cpp +# End Source File +# Begin Source File + +SOURCE=.\imagelib.cpp +# End Source File +# Begin Source File + +SOURCE=.\quantizer.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\scriplib.cpp +# End Source File +# Begin Source File + +SOURCE=.\spritegen.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\stringlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\virtualfs.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\zone.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=..\common\cmdlib.h +# End Source File +# Begin Source File + +SOURCE=..\common\conprint.h +# End Source File +# Begin Source File + +SOURCE=.\imagelib.h +# End Source File +# Begin Source File + +SOURCE=..\common\scriplib.h +# End Source File +# Begin Source File + +SOURCE=..\common\stringlib.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/utils/squish/alpha.cpp b/utils/squish/alpha.cpp new file mode 100644 index 0000000..d7fc021 --- /dev/null +++ b/utils/squish/alpha.cpp @@ -0,0 +1,354 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#include "alpha.h" +#include "maths.h" + +#include +#include + +namespace squish { + +static int FloatToInt( float a, int limit ) +{ + // use ANSI round-to-zero behaviour to get round-to-nearest + int i = ( int )( a + 0.5f ); + + // clamp to the limit + if( i < 0 ) + i = 0; + else if( i > limit ) + i = limit; + + // done + return i; +} + +void CompressAlphaDxt3( u8 const* rgba, int mask, void* block ) +{ + u8* bytes = reinterpret_cast< u8* >( block ); + + // quantise and pack the alpha values pairwise + for( int i = 0; i < 8; ++i ) + { + // quantise down to 4 bits + float alpha1 = ( float )rgba[8*i + 3] * ( 15.0f/255.0f ); + float alpha2 = ( float )rgba[8*i + 7] * ( 15.0f/255.0f ); + int quant1 = FloatToInt( alpha1, 15 ); + int quant2 = FloatToInt( alpha2, 15 ); + + // set alpha to zero where masked + int bit1 = 1 << ( 2*i ); + int bit2 = 1 << ( 2*i + 1 ); + if( ( mask & bit1 ) == 0 ) + quant1 = 0; + if( ( mask & bit2 ) == 0 ) + quant2 = 0; + + // pack into the byte + bytes[i] = ( u8 )( quant1 | ( quant2 << 4 ) ); + } +} + +void DecompressAlphaDxt3( u8* rgba, void const* block ) +{ + u8 const* bytes = reinterpret_cast< u8 const* >( block ); + + // unpack the alpha values pairwise + for( int i = 0; i < 8; ++i ) + { + // quantise down to 4 bits + u8 quant = bytes[i]; + + // unpack the values + u8 lo = quant & 0x0f; + u8 hi = quant & 0xf0; + + // convert back up to bytes + rgba[8*i + 3] = lo | ( lo << 4 ); + rgba[8*i + 7] = hi | ( hi >> 4 ); + } +} + +static void FixRange( int& min, int& max, int steps ) +{ + if( max - min < steps ) + max = Q_min( min + steps, 255 ); + if( max - min < steps ) + min = Q_max( 0, max - steps ); +} + +static int FitCodes( u8 const* rgba, int mask, u8 const* codes, u8* indices, int channel ) +{ + // fit each alpha value to the codebook + int err = 0; + for( int i = 0; i < 16; ++i ) + { + // check this pixel is valid + int bit = 1 << i; + if( ( mask & bit ) == 0 ) + { + // use the first code + indices[i] = 0; + continue; + } + + // find the least error and corresponding index + int value = rgba[4*i + channel]; + int least = INT_MAX; + int index = 0; + for( int j = 0; j < 8; ++j ) + { + // get the squared error from this code + int dist = ( int )value - ( int )codes[j]; + dist *= dist; + + // compare with the best so far + if( dist < least ) + { + least = dist; + index = j; + } + } + + // save this index and accumulate the error + indices[i] = ( u8 )index; + err += least; + } + + // return the total error + return err; +} + +static void WriteAlphaBlock( int alpha0, int alpha1, u8 const* indices, void* block ) +{ + u8* bytes = reinterpret_cast< u8* >( block ); + + // write the first two bytes + bytes[0] = ( u8 )alpha0; + bytes[1] = ( u8 )alpha1; + + // pack the indices with 3 bits each + u8* dest = bytes + 2; + u8 const* src = indices; + for( int i = 0; i < 2; ++i ) + { + // pack 8 3-bit values + int j, value = 0; + for( j = 0; j < 8; ++j ) + { + int index = *src++; + value |= ( index << 3*j ); + } + + // store in 3 bytes + for( j = 0; j < 3; ++j ) + { + int byte = ( value >> 8*j ) & 0xff; + *dest++ = ( u8 )byte; + } + } +} + +static void WriteAlphaBlock5( int alpha0, int alpha1, u8 const* indices, void* block ) +{ + // check the relative values of the endpoints + if( alpha0 > alpha1 ) + { + // swap the indices + u8 swapped[16]; + for( int i = 0; i < 16; ++i ) + { + u8 index = indices[i]; + if( index == 0 ) + swapped[i] = 1; + else if( index == 1 ) + swapped[i] = 0; + else if( index <= 5 ) + swapped[i] = 7 - index; + else + swapped[i] = index; + } + + // write the block + WriteAlphaBlock( alpha1, alpha0, swapped, block ); + } + else + { + // write the block + WriteAlphaBlock( alpha0, alpha1, indices, block ); + } +} + +static void WriteAlphaBlock7( int alpha0, int alpha1, u8 const* indices, void* block ) +{ + // check the relative values of the endpoints + if( alpha0 < alpha1 ) + { + // swap the indices + u8 swapped[16]; + for( int i = 0; i < 16; ++i ) + { + u8 index = indices[i]; + if( index == 0 ) + swapped[i] = 1; + else if( index == 1 ) + swapped[i] = 0; + else + swapped[i] = 9 - index; + } + + // write the block + WriteAlphaBlock( alpha1, alpha0, swapped, block ); + } + else + { + // write the block + WriteAlphaBlock( alpha0, alpha1, indices, block ); + } +} + +void CompressAlphaDxt5( u8 const* rgba, int mask, void* block, int channel ) +{ + // get the range for 5-alpha and 7-alpha interpolation + int min5 = 255; + int max5 = 0; + int min7 = 255; + int max7 = 0; + int i; + + for( i = 0; i < 16; ++i ) + { + // check this pixel is valid + int bit = 1 << i; + if( ( mask & bit ) == 0 ) + continue; + + // incorporate into the min/max + int value = rgba[4*i + channel]; + if( value < min7 ) + min7 = value; + if( value > max7 ) + max7 = value; + if( value != 0 && value < min5 ) + min5 = value; + if( value != 255 && value > max5 ) + max5 = value; + } + + // handle the case that no valid range was found + if( min5 > max5 ) + min5 = max5; + if( min7 > max7 ) + min7 = max7; + + // fix the range to be the minimum in each case + FixRange( min5, max5, 5 ); + FixRange( min7, max7, 7 ); + + // set up the 5-alpha code book + u8 codes5[8]; + codes5[0] = ( u8 )min5; + codes5[1] = ( u8 )max5; + for( i = 1; i < 5; ++i ) + codes5[1 + i] = ( u8 )( ( ( 5 - i )*min5 + i*max5 )/5 ); + codes5[6] = 0; + codes5[7] = 255; + + // set up the 7-alpha code book + u8 codes7[8]; + codes7[0] = ( u8 )min7; + codes7[1] = ( u8 )max7; + for( i = 1; i < 7; ++i ) + codes7[1 + i] = ( u8 )( ( ( 7 - i )*min7 + i*max7 )/7 ); + + // fit the data to both code books + u8 indices5[16]; + u8 indices7[16]; + int err5 = FitCodes( rgba, mask, codes5, indices5, channel ); + int err7 = FitCodes( rgba, mask, codes7, indices7, channel ); + + // save the block with least error + if( err5 <= err7 ) + WriteAlphaBlock5( min5, max5, indices5, block ); + else + WriteAlphaBlock7( min7, max7, indices7, block ); +} + +void DecompressAlphaDxt5( u8* rgba, void const* block ) +{ + // get the two alpha values + u8 const* bytes = reinterpret_cast< u8 const* >( block ); + int alpha0 = bytes[0]; + int alpha1 = bytes[1]; + int i; + + // compare the values to build the codebook + u8 codes[8]; + codes[0] = ( u8 )alpha0; + codes[1] = ( u8 )alpha1; + if( alpha0 <= alpha1 ) + { + // use 5-alpha codebook + for( int i = 1; i < 5; ++i ) + codes[1 + i] = ( u8 )( ( ( 5 - i )*alpha0 + i*alpha1 )/5 ); + codes[6] = 0; + codes[7] = 255; + } + else + { + // use 7-alpha codebook + for( int i = 1; i < 7; ++i ) + codes[1 + i] = ( u8 )( ( ( 7 - i )*alpha0 + i*alpha1 )/7 ); + } + + // decode the indices + u8 indices[16]; + u8 const* src = bytes + 2; + u8* dest = indices; + for( i = 0; i < 2; ++i ) + { + // grab 3 bytes + int j, value = 0; + for( j = 0; j < 3; ++j ) + { + int byte = *src++; + value |= ( byte << 8*j ); + } + + // unpack 8 3-bit values from it + for( j = 0; j < 8; ++j ) + { + int index = ( value >> 3*j ) & 0x7; + *dest++ = ( u8 )index; + } + } + + // write out the indexed codebook values + for( i = 0; i < 16; ++i ) + rgba[4*i + 3] = codes[indices[i]]; +} + +} // namespace squish diff --git a/utils/squish/alpha.h b/utils/squish/alpha.h new file mode 100644 index 0000000..7201b48 --- /dev/null +++ b/utils/squish/alpha.h @@ -0,0 +1,41 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_ALPHA_H +#define SQUISH_ALPHA_H + +#include "squish.h" + +namespace squish { + +void CompressAlphaDxt3( u8 const* rgba, int mask, void* block ); +void CompressAlphaDxt5( u8 const* rgba, int mask, void* block, int channel = 3 ); + +void DecompressAlphaDxt3( u8* rgba, void const* block ); +void DecompressAlphaDxt5( u8* rgba, void const* block ); + +} // namespace squish + +#endif // ndef SQUISH_ALPHA_H diff --git a/utils/squish/clusterfit.cpp b/utils/squish/clusterfit.cpp new file mode 100644 index 0000000..f546c6f --- /dev/null +++ b/utils/squish/clusterfit.cpp @@ -0,0 +1,395 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + Copyright (c) 2007 Ignacio Castano icastano@nvidia.com + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#include "clusterfit.h" +#include "colourset.h" +#include "colourblock.h" +#include + +namespace squish { + +ClusterFit::ClusterFit( ColourSet const* colours, int flags, float* metric ) + : ColourFit( colours, flags ) +{ + // set the iteration count + m_iterationCount = ( m_flags & kColourIterativeClusterFit ) ? kMaxIterations : 1; + + // initialise the metric (old perceptual = 0.2126f, 0.7152f, 0.0722f) + if( metric ) + m_metric = Vec4( metric[0], metric[1], metric[2], 1.0f ); + else + m_metric = VEC4_CONST( 1.0f ); + + // initialise the best error + m_besterror = VEC4_CONST( FLT_MAX ); + + // cache some values + int const count = m_colours->GetCount(); + Vec3 const* values = m_colours->GetPoints(); + + // get the covariance matrix + Sym3x3 covariance = ComputeWeightedCovariance( count, values, m_colours->GetWeights(), m_metric.GetVec3() ); + + // compute the principle component + m_principle = ComputePrincipleComponent( covariance ); +} + +bool ClusterFit::ConstructOrdering( Vec3 const& axis, int iteration ) +{ + // cache some values + int const count = m_colours->GetCount(); + Vec3 const* values = m_colours->GetPoints(); + int i; + + // build the list of dot products + float dps[16]; + u8* order = ( u8* )m_order + 16*iteration; + for( i = 0; i < count; ++i ) + { + dps[i] = Dot( values[i], axis ); + order[i] = ( u8 )i; + } + + // stable sort using them + for( i = 0; i < count; ++i ) + { + for( int j = i; j > 0 && dps[j] < dps[j - 1]; --j ) + { + std::swap( dps[j], dps[j - 1] ); + std::swap( order[j], order[j - 1] ); + } + } + + // check this ordering is unique + for( int it = 0; it < iteration; ++it ) + { + u8 const* prev = ( u8* )m_order + 16*it; + bool same = true; + for( int i = 0; i < count; ++i ) + { + if( order[i] != prev[i] ) + { + same = false; + break; + } + } + if( same ) + return false; + } + + // copy the ordering and weight all the points + Vec3 const* unweighted = m_colours->GetPoints(); + float const* weights = m_colours->GetWeights(); + m_xsum_wsum = VEC4_CONST( 0.0f ); + for( i = 0; i < count; ++i ) + { + int j = order[i]; + Vec4 p( unweighted[j].X(), unweighted[j].Y(), unweighted[j].Z(), 1.0f ); + Vec4 w( weights[j] ); + Vec4 x = p*w; + m_points_weights[i] = x; + m_xsum_wsum += x; + } + return true; +} + +void ClusterFit::Compress3( void* block ) +{ + // declare variables + int const count = m_colours->GetCount(); + Vec4 const two = VEC4_CONST( 2.0 ); + Vec4 const one = VEC4_CONST( 1.0f ); + Vec4 const half_half2( 0.5f, 0.5f, 0.5f, 0.25f ); + Vec4 const zero = VEC4_CONST( 0.0f ); + Vec4 const half = VEC4_CONST( 0.5f ); + Vec4 const grid( 31.0f, 63.0f, 31.0f, 0.0f ); + Vec4 const gridrcp( 1.0f/31.0f, 1.0f/63.0f, 1.0f/31.0f, 0.0f ); + + // prepare an ordering using the principle axis + ConstructOrdering( m_principle, 0 ); + + // check all possible clusters and iterate on the total order + Vec4 beststart = VEC4_CONST( 0.0f ); + Vec4 bestend = VEC4_CONST( 0.0f ); + Vec4 besterror = m_besterror; + u8 bestindices[16]; + int bestiteration = 0; + int besti = 0, bestj = 0; + + // loop over iterations (we avoid the case that all points in first or last cluster) + for( int iterationIndex = 0;; ) + { + // first cluster [0,i) is at the start + Vec4 part0 = VEC4_CONST( 0.0f ); + for( int i = 0; i < count; ++i ) + { + // second cluster [i,j) is half along + Vec4 part1 = ( i == 0 ) ? m_points_weights[0] : VEC4_CONST( 0.0f ); + int jmin = ( i == 0 ) ? 1 : i; + for( int j = jmin;; ) + { + // last cluster [j,count) is at the end + Vec4 part2 = m_xsum_wsum - part1 - part0; + + // compute least squares terms directly + Vec4 alphax_sum = MultiplyAdd( part1, half_half2, part0 ); + Vec4 alpha2_sum = alphax_sum.SplatW(); + + Vec4 betax_sum = MultiplyAdd( part1, half_half2, part2 ); + Vec4 beta2_sum = betax_sum.SplatW(); + + Vec4 alphabeta_sum = ( part1*half_half2 ).SplatW(); + + // compute the least-squares optimal points + Vec4 factor = Reciprocal( NegativeMultiplySubtract( alphabeta_sum, alphabeta_sum, alpha2_sum*beta2_sum ) ); + Vec4 a = NegativeMultiplySubtract( betax_sum, alphabeta_sum, alphax_sum*beta2_sum )*factor; + Vec4 b = NegativeMultiplySubtract( alphax_sum, alphabeta_sum, betax_sum*alpha2_sum )*factor; + + // clamp to the grid + a = Min( one, Max( zero, a ) ); + b = Min( one, Max( zero, b ) ); + a = Truncate( MultiplyAdd( grid, a, half ) )*gridrcp; + b = Truncate( MultiplyAdd( grid, b, half ) )*gridrcp; + + // compute the error (we skip the constant xxsum) + Vec4 e1 = MultiplyAdd( a*a, alpha2_sum, b*b*beta2_sum ); + Vec4 e2 = NegativeMultiplySubtract( a, alphax_sum, a*b*alphabeta_sum ); + Vec4 e3 = NegativeMultiplySubtract( b, betax_sum, e2 ); + Vec4 e4 = MultiplyAdd( two, e3, e1 ); + + // apply the metric to the error term + Vec4 e5 = e4*m_metric; + Vec4 error = e5.SplatX() + e5.SplatY() + e5.SplatZ(); + + // keep the solution if it wins + if( CompareAnyLessThan( error, besterror ) ) + { + beststart = a; + bestend = b; + besti = i; + bestj = j; + besterror = error; + bestiteration = iterationIndex; + } + + // advance + if( j == count ) + break; + part1 += m_points_weights[j]; + ++j; + } + + // advance + part0 += m_points_weights[i]; + } + + // stop if we didn't improve in this iteration + if( bestiteration != iterationIndex ) + break; + + // advance if possible + ++iterationIndex; + if( iterationIndex == m_iterationCount ) + break; + + // stop if a new iteration is an ordering that has already been tried + Vec3 axis = ( bestend - beststart ).GetVec3(); + if( !ConstructOrdering( axis, iterationIndex ) ) + break; + } + + // save the block if necessary + if( CompareAnyLessThan( besterror, m_besterror ) ) + { + // remap the indices + u8 const* order = ( u8* )m_order + 16*bestiteration; + int m; + + u8 unordered[16]; + for( m = 0; m < besti; ++m ) + unordered[order[m]] = 0; + for( m = besti; m < bestj; ++m ) + unordered[order[m]] = 2; + for( m = bestj; m < count; ++m ) + unordered[order[m]] = 1; + + m_colours->RemapIndices( unordered, bestindices ); + + // save the block + WriteColourBlock3( beststart.GetVec3(), bestend.GetVec3(), bestindices, block ); + + // save the error + m_besterror = besterror; + } +} + +void ClusterFit::Compress4( void* block ) +{ + // declare variables + int const count = m_colours->GetCount(); + Vec4 const two = VEC4_CONST( 2.0f ); + Vec4 const one = VEC4_CONST( 1.0f ); + Vec4 const onethird_onethird2( 1.0f/3.0f, 1.0f/3.0f, 1.0f/3.0f, 1.0f/9.0f ); + Vec4 const twothirds_twothirds2( 2.0f/3.0f, 2.0f/3.0f, 2.0f/3.0f, 4.0f/9.0f ); + Vec4 const twonineths = VEC4_CONST( 2.0f/9.0f ); + Vec4 const zero = VEC4_CONST( 0.0f ); + Vec4 const half = VEC4_CONST( 0.5f ); + Vec4 const grid( 31.0f, 63.0f, 31.0f, 0.0f ); + Vec4 const gridrcp( 1.0f/31.0f, 1.0f/63.0f, 1.0f/31.0f, 0.0f ); + + // prepare an ordering using the principle axis + ConstructOrdering( m_principle, 0 ); + + // check all possible clusters and iterate on the total order + Vec4 beststart = VEC4_CONST( 0.0f ); + Vec4 bestend = VEC4_CONST( 0.0f ); + Vec4 besterror = m_besterror; + u8 bestindices[16]; + int bestiteration = 0; + int besti = 0, bestj = 0, bestk = 0; + + // loop over iterations (we avoid the case that all points in first or last cluster) + for( int iterationIndex = 0;; ) + { + // first cluster [0,i) is at the start + Vec4 part0 = VEC4_CONST( 0.0f ); + for( int i = 0; i < count; ++i ) + { + // second cluster [i,j) is one third along + Vec4 part1 = VEC4_CONST( 0.0f ); + for( int j = i;; ) + { + // third cluster [j,k) is two thirds along + Vec4 part2 = ( j == 0 ) ? m_points_weights[0] : VEC4_CONST( 0.0f ); + int kmin = ( j == 0 ) ? 1 : j; + for( int k = kmin;; ) + { + // last cluster [k,count) is at the end + Vec4 part3 = m_xsum_wsum - part2 - part1 - part0; + + // compute least squares terms directly + Vec4 const alphax_sum = MultiplyAdd( part2, onethird_onethird2, MultiplyAdd( part1, twothirds_twothirds2, part0 ) ); + Vec4 const alpha2_sum = alphax_sum.SplatW(); + + Vec4 const betax_sum = MultiplyAdd( part1, onethird_onethird2, MultiplyAdd( part2, twothirds_twothirds2, part3 ) ); + Vec4 const beta2_sum = betax_sum.SplatW(); + + Vec4 const alphabeta_sum = twonineths*( part1 + part2 ).SplatW(); + + // compute the least-squares optimal points + Vec4 factor = Reciprocal( NegativeMultiplySubtract( alphabeta_sum, alphabeta_sum, alpha2_sum*beta2_sum ) ); + Vec4 a = NegativeMultiplySubtract( betax_sum, alphabeta_sum, alphax_sum*beta2_sum )*factor; + Vec4 b = NegativeMultiplySubtract( alphax_sum, alphabeta_sum, betax_sum*alpha2_sum )*factor; + + // clamp to the grid + a = Min( one, Max( zero, a ) ); + b = Min( one, Max( zero, b ) ); + a = Truncate( MultiplyAdd( grid, a, half ) )*gridrcp; + b = Truncate( MultiplyAdd( grid, b, half ) )*gridrcp; + + // compute the error (we skip the constant xxsum) + Vec4 e1 = MultiplyAdd( a*a, alpha2_sum, b*b*beta2_sum ); + Vec4 e2 = NegativeMultiplySubtract( a, alphax_sum, a*b*alphabeta_sum ); + Vec4 e3 = NegativeMultiplySubtract( b, betax_sum, e2 ); + Vec4 e4 = MultiplyAdd( two, e3, e1 ); + + // apply the metric to the error term + Vec4 e5 = e4*m_metric; + Vec4 error = e5.SplatX() + e5.SplatY() + e5.SplatZ(); + + // keep the solution if it wins + if( CompareAnyLessThan( error, besterror ) ) + { + beststart = a; + bestend = b; + besterror = error; + besti = i; + bestj = j; + bestk = k; + bestiteration = iterationIndex; + } + + // advance + if( k == count ) + break; + part2 += m_points_weights[k]; + ++k; + } + + // advance + if( j == count ) + break; + part1 += m_points_weights[j]; + ++j; + } + + // advance + part0 += m_points_weights[i]; + } + + // stop if we didn't improve in this iteration + if( bestiteration != iterationIndex ) + break; + + // advance if possible + ++iterationIndex; + if( iterationIndex == m_iterationCount ) + break; + + // stop if a new iteration is an ordering that has already been tried + Vec3 axis = ( bestend - beststart ).GetVec3(); + if( !ConstructOrdering( axis, iterationIndex ) ) + break; + } + + // save the block if necessary + if( CompareAnyLessThan( besterror, m_besterror ) ) + { + // remap the indices + u8 const* order = ( u8* )m_order + 16*bestiteration; + int m; + + u8 unordered[16]; + for( m = 0; m < besti; ++m ) + unordered[order[m]] = 0; + for( m = besti; m < bestj; ++m ) + unordered[order[m]] = 2; + for( m = bestj; m < bestk; ++m ) + unordered[order[m]] = 3; + for( m = bestk; m < count; ++m ) + unordered[order[m]] = 1; + + m_colours->RemapIndices( unordered, bestindices ); + + // save the block + WriteColourBlock4( beststart.GetVec3(), bestend.GetVec3(), bestindices, block ); + + // save the error + m_besterror = besterror; + } +} + +} // namespace squish diff --git a/utils/squish/clusterfit.h b/utils/squish/clusterfit.h new file mode 100644 index 0000000..3b52def --- /dev/null +++ b/utils/squish/clusterfit.h @@ -0,0 +1,61 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + Copyright (c) 2007 Ignacio Castano icastano@nvidia.com + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_CLUSTERFIT_H +#define SQUISH_CLUSTERFIT_H + +#include "squish.h" +#include "maths.h" +#include "simd.h" +#include "colourfit.h" + +namespace squish { + +class ClusterFit : public ColourFit +{ +public: + ClusterFit( ColourSet const* colours, int flags, float* metric ); + +private: + bool ConstructOrdering( Vec3 const& axis, int iteration ); + + virtual void Compress3( void* block ); + virtual void Compress4( void* block ); + + enum { kMaxIterations = 8 }; + + int m_iterationCount; + Vec3 m_principle; + u8 m_order[16*kMaxIterations]; + Vec4 m_points_weights[16]; + Vec4 m_xsum_wsum; + Vec4 m_metric; + Vec4 m_besterror; +}; + +} // namespace squish + +#endif // ndef SQUISH_CLUSTERFIT_H diff --git a/utils/squish/colourblock.cpp b/utils/squish/colourblock.cpp new file mode 100644 index 0000000..5ab2fb7 --- /dev/null +++ b/utils/squish/colourblock.cpp @@ -0,0 +1,215 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#include "colourblock.h" + +namespace squish { + +static int FloatToInt( float a, int limit ) +{ + // use ANSI round-to-zero behaviour to get round-to-nearest + int i = ( int )( a + 0.5f ); + + // clamp to the limit + if( i < 0 ) + i = 0; + else if( i > limit ) + i = limit; + + // done + return i; +} + +static int FloatTo565( Vec3::Arg colour ) +{ + // get the components in the correct range + int r = FloatToInt( 31.0f*colour.X(), 31 ); + int g = FloatToInt( 63.0f*colour.Y(), 63 ); + int b = FloatToInt( 31.0f*colour.Z(), 31 ); + + // pack into a single value + return ( r << 11 ) | ( g << 5 ) | b; +} + +static void WriteColourBlock( int a, int b, u8* indices, void* block ) +{ + // get the block as bytes + u8* bytes = ( u8* )block; + + // write the endpoints + bytes[0] = ( u8 )( a & 0xff ); + bytes[1] = ( u8 )( a >> 8 ); + bytes[2] = ( u8 )( b & 0xff ); + bytes[3] = ( u8 )( b >> 8 ); + + // write the indices + for( int i = 0; i < 4; ++i ) + { + u8 const* ind = indices + 4*i; + bytes[4 + i] = ind[0] | ( ind[1] << 2 ) | ( ind[2] << 4 ) | ( ind[3] << 6 ); + } +} + +void WriteColourBlock3( Vec3::Arg start, Vec3::Arg end, u8 const* indices, void* block ) +{ + // get the packed values + int a = FloatTo565( start ); + int b = FloatTo565( end ); + + // remap the indices + u8 remapped[16]; + if( a <= b ) + { + // use the indices directly + for( int i = 0; i < 16; ++i ) + remapped[i] = indices[i]; + } + else + { + // swap a and b + std::swap( a, b ); + for( int i = 0; i < 16; ++i ) + { + if( indices[i] == 0 ) + remapped[i] = 1; + else if( indices[i] == 1 ) + remapped[i] = 0; + else + remapped[i] = indices[i]; + } + } + + // write the block + WriteColourBlock( a, b, remapped, block ); +} + +void WriteColourBlock4( Vec3::Arg start, Vec3::Arg end, u8 const* indices, void* block ) +{ + // get the packed values + int a = FloatTo565( start ); + int b = FloatTo565( end ); + + // remap the indices + u8 remapped[16]; + if( a < b ) + { + // swap a and b + std::swap( a, b ); + for( int i = 0; i < 16; ++i ) + remapped[i] = ( indices[i] ^ 0x1 ) & 0x3; + } + else if( a == b ) + { + // use index 0 + for( int i = 0; i < 16; ++i ) + remapped[i] = 0; + } + else + { + // use the indices directly + for( int i = 0; i < 16; ++i ) + remapped[i] = indices[i]; + } + + // write the block + WriteColourBlock( a, b, remapped, block ); +} + +static int Unpack565( u8 const* packed, u8* colour ) +{ + // build the packed value + int value = ( int )packed[0] | ( ( int )packed[1] << 8 ); + + // get the components in the stored range + u8 red = ( u8 )( ( value >> 11 ) & 0x1f ); + u8 green = ( u8 )( ( value >> 5 ) & 0x3f ); + u8 blue = ( u8 )( value & 0x1f ); + + // scale up to 8 bits + colour[0] = ( red << 3 ) | ( red >> 2 ); + colour[1] = ( green << 2 ) | ( green >> 4 ); + colour[2] = ( blue << 3 ) | ( blue >> 2 ); + colour[3] = 255; + + // return the value + return value; +} + +void DecompressColour( u8* rgba, void const* block, bool isDxt1 ) +{ + // get the block bytes + u8 const* bytes = reinterpret_cast< u8 const* >( block ); + + // unpack the endpoints + u8 codes[16]; + int a = Unpack565( bytes, codes ); + int b = Unpack565( bytes + 2, codes + 4 ); + int i; + + // generate the midpoints + for( i = 0; i < 3; ++i ) + { + int c = codes[i]; + int d = codes[4 + i]; + + if( isDxt1 && a <= b ) + { + codes[8 + i] = ( u8 )( ( c + d )/2 ); + codes[12 + i] = 0; + } + else + { + codes[8 + i] = ( u8 )( ( 2*c + d )/3 ); + codes[12 + i] = ( u8 )( ( c + 2*d )/3 ); + } + } + + // fill in alpha for the intermediate values + codes[8 + 3] = 255; + codes[12 + 3] = ( isDxt1 && a <= b ) ? 0 : 255; + + // unpack the indices + u8 indices[16]; + for( i = 0; i < 4; ++i ) + { + u8* ind = indices + 4*i; + u8 packed = bytes[4 + i]; + + ind[0] = packed & 0x3; + ind[1] = ( packed >> 2 ) & 0x3; + ind[2] = ( packed >> 4 ) & 0x3; + ind[3] = ( packed >> 6 ) & 0x3; + } + + // store out the colours + for( i = 0; i < 16; ++i ) + { + u8 offset = 4*indices[i]; + for( int j = 0; j < 4; ++j ) + rgba[4*i + j] = codes[offset + j]; + } +} + +} // namespace squish diff --git a/utils/squish/colourblock.h b/utils/squish/colourblock.h new file mode 100644 index 0000000..a2cf9ea --- /dev/null +++ b/utils/squish/colourblock.h @@ -0,0 +1,41 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_COLOURBLOCK_H +#define SQUISH_COLOURBLOCK_H + +#include "squish.h" +#include "maths.h" + +namespace squish { + +void WriteColourBlock3( Vec3::Arg start, Vec3::Arg end, u8 const* indices, void* block ); +void WriteColourBlock4( Vec3::Arg start, Vec3::Arg end, u8 const* indices, void* block ); + +void DecompressColour( u8* rgba, void const* block, bool isDxt1 ); + +} // namespace squish + +#endif // ndef SQUISH_COLOURBLOCK_H diff --git a/utils/squish/colourfit.cpp b/utils/squish/colourfit.cpp new file mode 100644 index 0000000..3ef4061 --- /dev/null +++ b/utils/squish/colourfit.cpp @@ -0,0 +1,54 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#include "colourfit.h" +#include "colourset.h" + +namespace squish { + +ColourFit::ColourFit( ColourSet const* colours, int flags ) + : m_colours( colours ), + m_flags( flags ) +{ +} + +ColourFit::~ColourFit() +{ +} + +void ColourFit::Compress( void* block ) +{ + bool isDxt1 = ( ( m_flags & kDxt1 ) != 0 ); + if( isDxt1 ) + { + Compress3( block ); + if( !m_colours->IsTransparent() ) + Compress4( block ); + } + else + Compress4( block ); +} + +} // namespace squish diff --git a/utils/squish/colourfit.h b/utils/squish/colourfit.h new file mode 100644 index 0000000..3ef782c --- /dev/null +++ b/utils/squish/colourfit.h @@ -0,0 +1,56 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_COLOURFIT_H +#define SQUISH_COLOURFIT_H + +#include "squish.h" +#include "maths.h" + +#include + +namespace squish { + +class ColourSet; + +class ColourFit +{ +public: + ColourFit( ColourSet const* colours, int flags ); + virtual ~ColourFit(); + + void Compress( void* block ); + +protected: + virtual void Compress3( void* block ) = 0; + virtual void Compress4( void* block ) = 0; + + ColourSet const* m_colours; + int m_flags; +}; + +} // namespace squish + +#endif // ndef SQUISH_COLOURFIT_H diff --git a/utils/squish/colourset.cpp b/utils/squish/colourset.cpp new file mode 100644 index 0000000..98045c0 --- /dev/null +++ b/utils/squish/colourset.cpp @@ -0,0 +1,122 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#include "colourset.h" + +namespace squish { + +ColourSet::ColourSet( u8 const* rgba, int mask, int flags ) + : m_count( 0 ), + m_transparent( false ) +{ + // check the compression mode for dxt1 + bool isDxt1 = ( ( flags & kDxt1 ) != 0 ); + bool weightByAlpha = ( ( flags & kWeightColourByAlpha ) != 0 ); + int i; + + // create the minimal set + for( i = 0; i < 16; ++i ) + { + // check this pixel is enabled + int bit = 1 << i; + if( ( mask & bit ) == 0 ) + { + m_remap[i] = -1; + continue; + } + + // check for transparent pixels when using dxt1 + if( isDxt1 && rgba[4*i + 3] < 128 ) + { + m_remap[i] = -1; + m_transparent = true; + continue; + } + + // loop over previous points for a match + for( int j = 0;; ++j ) + { + // allocate a new point + if( j == i ) + { + // normalise coordinates to [0,1] + float x = ( float )rgba[4*i + 0] / 255.0f; + float y = ( float )rgba[4*i + 1] / 255.0f; + float z = ( float )rgba[4*i + 2] / 255.0f; + + // ensure there is always non-zero weight even for zero alpha + float w = ( float )( rgba[4*i + 3] + 1 ) / 256.0f; + + // add the point + m_points[m_count] = Vec3( x, y, z ); + m_weights[m_count] = ( weightByAlpha ? w : 1.0f ); + m_remap[i] = m_count; + + // advance + ++m_count; + break; + } + + // check for a match + int oldbit = 1 << j; + bool match = ( ( mask & oldbit ) != 0 ) + && ( rgba[4*i + 0] == rgba[4*j + 0] ) + && ( rgba[4*i + 1] == rgba[4*j + 1] ) + && ( rgba[4*i + 2] == rgba[4*j + 2] ) + && ( rgba[4*j + 3] >= 128 || !isDxt1 ); + if( match ) + { + // get the index of the match + int index = m_remap[j]; + + // ensure there is always non-zero weight even for zero alpha + float w = ( float )( rgba[4*i + 3] + 1 ) / 256.0f; + + // map to this point and increase the weight + m_weights[index] += ( weightByAlpha ? w : 1.0f ); + m_remap[i] = index; + break; + } + } + } + + // square root the weights + for( i = 0; i < m_count; ++i ) + m_weights[i] = sqrt( m_weights[i] ); +} + +void ColourSet::RemapIndices( u8 const* source, u8* target ) const +{ + for( int i = 0; i < 16; ++i ) + { + int j = m_remap[i]; + if( j == -1 ) + target[i] = 3; + else + target[i] = source[j]; + } +} + +} // namespace squish diff --git a/utils/squish/colourset.h b/utils/squish/colourset.h new file mode 100644 index 0000000..9a5e3ec --- /dev/null +++ b/utils/squish/colourset.h @@ -0,0 +1,58 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_COLOURSET_H +#define SQUISH_COLOURSET_H + +#include "squish.h" +#include "maths.h" + +namespace squish { + +/*! @brief Represents a set of block colours +*/ +class ColourSet +{ +public: + ColourSet( u8 const* rgba, int mask, int flags ); + + int GetCount() const { return m_count; } + Vec3 const* GetPoints() const { return m_points; } + float const* GetWeights() const { return m_weights; } + bool IsTransparent() const { return m_transparent; } + + void RemapIndices( u8 const* source, u8* target ) const; + +private: + int m_count; + Vec3 m_points[16]; + float m_weights[16]; + int m_remap[16]; + bool m_transparent; +}; + +} // namespace sqish + +#endif // ndef SQUISH_COLOURSET_H diff --git a/utils/squish/config.h b/utils/squish/config.h new file mode 100644 index 0000000..5b9e4a3 --- /dev/null +++ b/utils/squish/config.h @@ -0,0 +1,49 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_CONFIG_H +#define SQUISH_CONFIG_H + +// Set to 1 when building squish to use Altivec instructions. +#ifndef SQUISH_USE_ALTIVEC +#define SQUISH_USE_ALTIVEC 0 +#endif + +// Set to 1 or 2 when building squish to use SSE or SSE2 instructions. +#ifndef SQUISH_USE_SSE +#define SQUISH_USE_SSE 0 +#endif + +// Internally set SQUISH_USE_SIMD when either Altivec or SSE is available. +#if SQUISH_USE_ALTIVEC && SQUISH_USE_SSE +#error "Cannot enable both Altivec and SSE!" +#endif +#if SQUISH_USE_ALTIVEC || SQUISH_USE_SSE +#define SQUISH_USE_SIMD 1 +#else +#define SQUISH_USE_SIMD 0 +#endif + +#endif // ndef SQUISH_CONFIG_H diff --git a/utils/squish/maths.cpp b/utils/squish/maths.cpp new file mode 100644 index 0000000..e6c52c3 --- /dev/null +++ b/utils/squish/maths.cpp @@ -0,0 +1,116 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +/*! @file + + The symmetric eigensystem solver algorithm is from + http://www.geometrictools.com/Documentation/EigenSymmetric3x3.pdf +*/ + +#include "maths.h" +#include "simd.h" +#include + +namespace squish { + +Sym3x3 ComputeWeightedCovariance( int n, Vec3 const* points, float const* weights, Vec3::Arg metric ) +{ + // compute the centroid + float total = 0.0f; + Vec3 centroid( 0.0f ); + int i; + + for( i = 0; i < n; ++i ) + { + total += weights[i]; + centroid += weights[i]*points[i]; + } + if( total > FLT_EPSILON ) + centroid /= total; + + // accumulate the covariance matrix + Sym3x3 covariance( 0.0f ); + for( i = 0; i < n; ++i ) + { + Vec3 a = (points[i] - centroid) * metric; + Vec3 b = weights[i]*a; + + covariance[0] += a.X()*b.X(); + covariance[1] += a.X()*b.Y(); + covariance[2] += a.X()*b.Z(); + covariance[3] += a.Y()*b.Y(); + covariance[4] += a.Y()*b.Z(); + covariance[5] += a.Z()*b.Z(); + } + + // return it + return covariance; +} + +static Vec3 EstimatePrincipleComponent( Sym3x3 const& matrix ) +{ + Vec3 const row0(matrix[0], matrix[1], matrix[2]); + Vec3 const row1(matrix[1], matrix[3], matrix[4]); + Vec3 const row2(matrix[2], matrix[4], matrix[5]); + + float r0 = Dot(row0, row0); + float r1 = Dot(row1, row1); + float r2 = Dot(row2, row2); + + if (r0 > r1 && r0 > r2) return row0; + if (r1 > r2) return row1; + return row2; +} + +#define POWER_ITERATION_COUNT 8 + +Vec3 ComputePrincipleComponent( Sym3x3 const& matrix ) +{ + Vec4 const row0( matrix[0], matrix[1], matrix[2], 0.0f ); + Vec4 const row1( matrix[1], matrix[3], matrix[4], 0.0f ); + Vec4 const row2( matrix[2], matrix[4], matrix[5], 0.0f ); +#if 1 + Vec3 v3 = EstimatePrincipleComponent( matrix ); + Vec4 v( v3.X(), v3.Y(), v3.Z(), 0.0f ); +#else + Vec4 v = VEC4_CONST( 1.0f ); +#endif + for( int i = 0; i < POWER_ITERATION_COUNT; ++i ) + { + // matrix multiply + Vec4 w = row0*v.SplatX(); + w = MultiplyAdd(row1, v.SplatY(), w); + w = MultiplyAdd(row2, v.SplatZ(), w); + + // get max component from xyz in all channels + Vec4 a = Max(w.SplatX(), Max(w.SplatY(), w.SplatZ())); + + // divide through and advance + v = w*Reciprocal(a); + } + return v.GetVec3(); +} + +} // namespace squish diff --git a/utils/squish/maths.h b/utils/squish/maths.h new file mode 100644 index 0000000..72f3284 --- /dev/null +++ b/utils/squish/maths.h @@ -0,0 +1,238 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_MATHS_H +#define SQUISH_MATHS_H + +#define SQUISH_USE_SSE 2 + +#include +#include +#include "config.h" + +#define Q_min( a, b ) (((a) < (b)) ? (a) : (b)) +#define Q_max( a, b ) (((a) > (b)) ? (a) : (b)) + +namespace squish { + +class Vec3 +{ +public: + typedef Vec3 const& Arg; + + Vec3() + { + } + + explicit Vec3( float s ) + { + m_x = s; + m_y = s; + m_z = s; + } + + Vec3( float x, float y, float z ) + { + m_x = x; + m_y = y; + m_z = z; + } + + float X() const { return m_x; } + float Y() const { return m_y; } + float Z() const { return m_z; } + + Vec3 operator-() const + { + return Vec3( -m_x, -m_y, -m_z ); + } + + Vec3& operator+=( Arg v ) + { + m_x += v.m_x; + m_y += v.m_y; + m_z += v.m_z; + return *this; + } + + Vec3& operator-=( Arg v ) + { + m_x -= v.m_x; + m_y -= v.m_y; + m_z -= v.m_z; + return *this; + } + + Vec3& operator*=( Arg v ) + { + m_x *= v.m_x; + m_y *= v.m_y; + m_z *= v.m_z; + return *this; + } + + Vec3& operator*=( float s ) + { + m_x *= s; + m_y *= s; + m_z *= s; + return *this; + } + + Vec3& operator/=( Arg v ) + { + m_x /= v.m_x; + m_y /= v.m_y; + m_z /= v.m_z; + return *this; + } + + Vec3& operator/=( float s ) + { + float t = 1.0f/s; + m_x *= t; + m_y *= t; + m_z *= t; + return *this; + } + + friend Vec3 operator+( Arg left, Arg right ) + { + Vec3 copy( left ); + return copy += right; + } + + friend Vec3 operator-( Arg left, Arg right ) + { + Vec3 copy( left ); + return copy -= right; + } + + friend Vec3 operator*( Arg left, Arg right ) + { + Vec3 copy( left ); + return copy *= right; + } + + friend Vec3 operator*( Arg left, float right ) + { + Vec3 copy( left ); + return copy *= right; + } + + friend Vec3 operator*( float left, Arg right ) + { + Vec3 copy( right ); + return copy *= left; + } + + friend Vec3 operator/( Arg left, Arg right ) + { + Vec3 copy( left ); + return copy /= right; + } + + friend Vec3 operator/( Arg left, float right ) + { + Vec3 copy( left ); + return copy /= right; + } + + friend float Dot( Arg left, Arg right ) + { + return left.m_x*right.m_x + left.m_y*right.m_y + left.m_z*right.m_z; + } + + friend Vec3 Min( Arg left, Arg right ) + { + return Vec3( + Q_min( left.m_x, right.m_x ), + Q_min( left.m_y, right.m_y ), + Q_min( left.m_z, right.m_z ) + ); + } + + friend Vec3 Max( Arg left, Arg right ) + { + return Vec3( + Q_max( left.m_x, right.m_x ), + Q_max( left.m_y, right.m_y ), + Q_max( left.m_z, right.m_z ) + ); + } + + friend Vec3 Truncate( Arg v ) + { + return Vec3( + v.m_x > 0.0f ? floor( v.m_x ) : ceil( v.m_x ), + v.m_y > 0.0f ? floor( v.m_y ) : ceil( v.m_y ), + v.m_z > 0.0f ? floor( v.m_z ) : ceil( v.m_z ) + ); + } + +private: + float m_x; + float m_y; + float m_z; +}; + +inline float LengthSquared( Vec3::Arg v ) +{ + return Dot( v, v ); +} + +class Sym3x3 +{ +public: + Sym3x3() + { + } + + Sym3x3( float s ) + { + for( int i = 0; i < 6; ++i ) + m_x[i] = s; + } + + float operator[]( int index ) const + { + return m_x[index]; + } + + float& operator[]( int index ) + { + return m_x[index]; + } + +private: + float m_x[6]; +}; + +Sym3x3 ComputeWeightedCovariance( int n, Vec3 const* points, float const* weights, Vec3::Arg metric ); +Vec3 ComputePrincipleComponent( Sym3x3 const& matrix ); + +} // namespace squish + +#endif // ndef SQUISH_MATHS_H diff --git a/utils/squish/rangefit.cpp b/utils/squish/rangefit.cpp new file mode 100644 index 0000000..ce0376f --- /dev/null +++ b/utils/squish/rangefit.cpp @@ -0,0 +1,201 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#include "rangefit.h" +#include "colourset.h" +#include "colourblock.h" +#include + +namespace squish { + +RangeFit::RangeFit( ColourSet const* colours, int flags, float* metric ) + : ColourFit( colours, flags ) +{ + // initialise the metric (old perceptual = 0.2126f, 0.7152f, 0.0722f) + if( metric ) + m_metric = Vec3( metric[0], metric[1], metric[2] ); + else + m_metric = Vec3( 1.0f ); + + // initialise the best error + m_besterror = FLT_MAX; + + // cache some values + int const count = m_colours->GetCount(); + Vec3 const* values = m_colours->GetPoints(); + float const* weights = m_colours->GetWeights(); + + // get the covariance matrix + Sym3x3 covariance = ComputeWeightedCovariance( count, values, weights, m_metric ); + + // compute the principle component + Vec3 principle = ComputePrincipleComponent( covariance ); + + // get the min and max range as the codebook endpoints + Vec3 start( 0.0f ); + Vec3 end( 0.0f ); + if( count > 0 ) + { + float min, max; + + // compute the range + start = end = values[0]; + min = max = Dot( values[0], principle ); + for( int i = 1; i < count; ++i ) + { + float val = Dot( values[i], principle ); + if( val < min ) + { + start = values[i]; + min = val; + } + else if( val > max ) + { + end = values[i]; + max = val; + } + } + } + + // clamp the output to [0, 1] + Vec3 const one( 1.0f ); + Vec3 const zero( 0.0f ); + start = Min( one, Max( zero, start ) ); + end = Min( one, Max( zero, end ) ); + + // clamp to the grid and save + Vec3 const grid( 31.0f, 63.0f, 31.0f ); + Vec3 const gridrcp( 1.0f/31.0f, 1.0f/63.0f, 1.0f/31.0f ); + Vec3 const half( 0.5f ); + m_start = Truncate( grid*start + half )*gridrcp; + m_end = Truncate( grid*end + half )*gridrcp; +} + +void RangeFit::Compress3( void* block ) +{ + // cache some values + int const count = m_colours->GetCount(); + Vec3 const* values = m_colours->GetPoints(); + + // create a codebook + Vec3 codes[3]; + codes[0] = m_start; + codes[1] = m_end; + codes[2] = 0.5f*m_start + 0.5f*m_end; + + // match each point to the closest code + u8 closest[16]; + float error = 0.0f; + for( int i = 0; i < count; ++i ) + { + // find the closest code + float dist = FLT_MAX; + int idx = 0; + for( int j = 0; j < 3; ++j ) + { + float d = LengthSquared( m_metric*( values[i] - codes[j] ) ); + if( d < dist ) + { + dist = d; + idx = j; + } + } + + // save the index + closest[i] = ( u8 )idx; + + // accumulate the error + error += dist; + } + + // save this scheme if it wins + if( error < m_besterror ) + { + // remap the indices + u8 indices[16]; + m_colours->RemapIndices( closest, indices ); + + // save the block + WriteColourBlock3( m_start, m_end, indices, block ); + + // save the error + m_besterror = error; + } +} + +void RangeFit::Compress4( void* block ) +{ + // cache some values + int const count = m_colours->GetCount(); + Vec3 const* values = m_colours->GetPoints(); + + // create a codebook + Vec3 codes[4]; + codes[0] = m_start; + codes[1] = m_end; + codes[2] = ( 2.0f/3.0f )*m_start + ( 1.0f/3.0f )*m_end; + codes[3] = ( 1.0f/3.0f )*m_start + ( 2.0f/3.0f )*m_end; + + // match each point to the closest code + u8 closest[16]; + float error = 0.0f; + for( int i = 0; i < count; ++i ) + { + // find the closest code + float dist = FLT_MAX; + int idx = 0; + for( int j = 0; j < 4; ++j ) + { + float d = LengthSquared( m_metric*( values[i] - codes[j] ) ); + if( d < dist ) + { + dist = d; + idx = j; + } + } + + // save the index + closest[i] = ( u8 )idx; + + // accumulate the error + error += dist; + } + + // save this scheme if it wins + if( error < m_besterror ) + { + // remap the indices + u8 indices[16]; + m_colours->RemapIndices( closest, indices ); + + // save the block + WriteColourBlock4( m_start, m_end, indices, block ); + + // save the error + m_besterror = error; + } +} + +} // namespace squish diff --git a/utils/squish/rangefit.h b/utils/squish/rangefit.h new file mode 100644 index 0000000..e538767 --- /dev/null +++ b/utils/squish/rangefit.h @@ -0,0 +1,54 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_RANGEFIT_H +#define SQUISH_RANGEFIT_H + +#include "squish.h" +#include "colourfit.h" +#include "maths.h" + +namespace squish { + +class ColourSet; + +class RangeFit : public ColourFit +{ +public: + RangeFit( ColourSet const* colours, int flags, float* metric ); + +private: + virtual void Compress3( void* block ); + virtual void Compress4( void* block ); + + Vec3 m_metric; + Vec3 m_start; + Vec3 m_end; + float m_besterror; +}; + +} // squish + +#endif // ndef SQUISH_RANGEFIT_H diff --git a/utils/squish/simd.h b/utils/squish/simd.h new file mode 100644 index 0000000..d1afd2a --- /dev/null +++ b/utils/squish/simd.h @@ -0,0 +1,40 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_SIMD_H +#define SQUISH_SIMD_H + +#include "maths.h" + +#if SQUISH_USE_ALTIVEC +#include "simd_ve.h" +#elif SQUISH_USE_SSE +#include "simd_sse.h" +#else +#include "simd_float.h" +#endif + + +#endif // ndef SQUISH_SIMD_H diff --git a/utils/squish/simd_float.h b/utils/squish/simd_float.h new file mode 100644 index 0000000..79f7c94 --- /dev/null +++ b/utils/squish/simd_float.h @@ -0,0 +1,183 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_SIMD_FLOAT_H +#define SQUISH_SIMD_FLOAT_H + +#include + +namespace squish { + +#define VEC4_CONST( X ) Vec4( X ) + +class Vec4 +{ +public: + typedef Vec4 const& Arg; + + Vec4() {} + + explicit Vec4( float s ) + : m_x( s ), + m_y( s ), + m_z( s ), + m_w( s ) + { + } + + Vec4( float x, float y, float z, float w ) + : m_x( x ), + m_y( y ), + m_z( z ), + m_w( w ) + { + } + + Vec3 GetVec3() const + { + return Vec3( m_x, m_y, m_z ); + } + + Vec4 SplatX() const { return Vec4( m_x ); } + Vec4 SplatY() const { return Vec4( m_y ); } + Vec4 SplatZ() const { return Vec4( m_z ); } + Vec4 SplatW() const { return Vec4( m_w ); } + + Vec4& operator+=( Arg v ) + { + m_x += v.m_x; + m_y += v.m_y; + m_z += v.m_z; + m_w += v.m_w; + return *this; + } + + Vec4& operator-=( Arg v ) + { + m_x -= v.m_x; + m_y -= v.m_y; + m_z -= v.m_z; + m_w -= v.m_w; + return *this; + } + + Vec4& operator*=( Arg v ) + { + m_x *= v.m_x; + m_y *= v.m_y; + m_z *= v.m_z; + m_w *= v.m_w; + return *this; + } + + friend Vec4 operator+( Vec4::Arg left, Vec4::Arg right ) + { + Vec4 copy( left ); + return copy += right; + } + + friend Vec4 operator-( Vec4::Arg left, Vec4::Arg right ) + { + Vec4 copy( left ); + return copy -= right; + } + + friend Vec4 operator*( Vec4::Arg left, Vec4::Arg right ) + { + Vec4 copy( left ); + return copy *= right; + } + + //! Returns a*b + c + friend Vec4 MultiplyAdd( Vec4::Arg a, Vec4::Arg b, Vec4::Arg c ) + { + return a*b + c; + } + + //! Returns -( a*b - c ) + friend Vec4 NegativeMultiplySubtract( Vec4::Arg a, Vec4::Arg b, Vec4::Arg c ) + { + return c - a*b; + } + + friend Vec4 Reciprocal( Vec4::Arg v ) + { + return Vec4( + 1.0f/v.m_x, + 1.0f/v.m_y, + 1.0f/v.m_z, + 1.0f/v.m_w + ); + } + + friend Vec4 Min( Vec4::Arg left, Vec4::Arg right ) + { + return Vec4( + Q_min( left.m_x, right.m_x ), + Q_min( left.m_y, right.m_y ), + Q_min( left.m_z, right.m_z ), + Q_min( left.m_w, right.m_w ) + ); + } + + friend Vec4 Max( Vec4::Arg left, Vec4::Arg right ) + { + return Vec4( + Q_max( left.m_x, right.m_x ), + Q_max( left.m_y, right.m_y ), + Q_max( left.m_z, right.m_z ), + Q_max( left.m_w, right.m_w ) + ); + } + + friend Vec4 Truncate( Vec4::Arg v ) + { + return Vec4( + v.m_x > 0.0f ? floor( v.m_x ) : ceil( v.m_x ), + v.m_y > 0.0f ? floor( v.m_y ) : ceil( v.m_y ), + v.m_z > 0.0f ? floor( v.m_z ) : ceil( v.m_z ), + v.m_w > 0.0f ? floor( v.m_w ) : ceil( v.m_w ) + ); + } + + friend bool CompareAnyLessThan( Vec4::Arg left, Vec4::Arg right ) + { + return left.m_x < right.m_x + || left.m_y < right.m_y + || left.m_z < right.m_z + || left.m_w < right.m_w; + } + +private: + float m_x; + float m_y; + float m_z; + float m_w; +}; + +} // namespace squish + +#endif // ndef SQUISH_SIMD_FLOAT_H + diff --git a/utils/squish/simd_sse.h b/utils/squish/simd_sse.h new file mode 100644 index 0000000..f820cea --- /dev/null +++ b/utils/squish/simd_sse.h @@ -0,0 +1,180 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_SIMD_SSE_H +#define SQUISH_SIMD_SSE_H + +#include +#if ( SQUISH_USE_SSE > 1 ) +#include +#endif + +#define SQUISH_SSE_SPLAT( a ) \ + ( ( a ) | ( ( a ) << 2 ) | ( ( a ) << 4 ) | ( ( a ) << 6 ) ) + +#define SQUISH_SSE_SHUF( x, y, z, w ) \ + ( ( x ) | ( ( y ) << 2 ) | ( ( z ) << 4 ) | ( ( w ) << 6 ) ) + +namespace squish { + +#define VEC4_CONST( X ) Vec4( X ) + +class Vec4 +{ +public: + typedef Vec4 const& Arg; + + Vec4() {} + + explicit Vec4( __m128 v ) : m_v( v ) {} + + Vec4( Vec4 const& arg ) : m_v( arg.m_v ) {} + + Vec4& operator=( Vec4 const& arg ) + { + m_v = arg.m_v; + return *this; + } + + explicit Vec4( float s ) : m_v( _mm_set1_ps( s ) ) {} + + Vec4( float x, float y, float z, float w ) : m_v( _mm_setr_ps( x, y, z, w ) ) {} + + Vec3 GetVec3() const + { +#ifdef __GNUC__ + __attribute__ ((__aligned__ (16))) float c[4]; +#else + __declspec(align(16)) float c[4]; +#endif + _mm_store_ps( c, m_v ); + return Vec3( c[0], c[1], c[2] ); + } + + Vec4 SplatX() const { return Vec4( _mm_shuffle_ps( m_v, m_v, SQUISH_SSE_SPLAT( 0 ) ) ); } + Vec4 SplatY() const { return Vec4( _mm_shuffle_ps( m_v, m_v, SQUISH_SSE_SPLAT( 1 ) ) ); } + Vec4 SplatZ() const { return Vec4( _mm_shuffle_ps( m_v, m_v, SQUISH_SSE_SPLAT( 2 ) ) ); } + Vec4 SplatW() const { return Vec4( _mm_shuffle_ps( m_v, m_v, SQUISH_SSE_SPLAT( 3 ) ) ); } + + Vec4& operator+=( Arg v ) + { + m_v = _mm_add_ps( m_v, v.m_v ); + return *this; + } + + Vec4& operator-=( Arg v ) + { + m_v = _mm_sub_ps( m_v, v.m_v ); + return *this; + } + + Vec4& operator*=( Arg v ) + { + m_v = _mm_mul_ps( m_v, v.m_v ); + return *this; + } + + friend Vec4 operator+( Vec4::Arg left, Vec4::Arg right ) + { + return Vec4( _mm_add_ps( left.m_v, right.m_v ) ); + } + + friend Vec4 operator-( Vec4::Arg left, Vec4::Arg right ) + { + return Vec4( _mm_sub_ps( left.m_v, right.m_v ) ); + } + + friend Vec4 operator*( Vec4::Arg left, Vec4::Arg right ) + { + return Vec4( _mm_mul_ps( left.m_v, right.m_v ) ); + } + + //! Returns a*b + c + friend Vec4 MultiplyAdd( Vec4::Arg a, Vec4::Arg b, Vec4::Arg c ) + { + return Vec4( _mm_add_ps( _mm_mul_ps( a.m_v, b.m_v ), c.m_v ) ); + } + + //! Returns -( a*b - c ) + friend Vec4 NegativeMultiplySubtract( Vec4::Arg a, Vec4::Arg b, Vec4::Arg c ) + { + return Vec4( _mm_sub_ps( c.m_v, _mm_mul_ps( a.m_v, b.m_v ) ) ); + } + + friend Vec4 Reciprocal( Vec4::Arg v ) + { + // get the reciprocal estimate + __m128 estimate = _mm_rcp_ps( v.m_v ); + + // one round of Newton-Rhaphson refinement + __m128 diff = _mm_sub_ps( _mm_set1_ps( 1.0f ), _mm_mul_ps( estimate, v.m_v ) ); + return Vec4( _mm_add_ps( _mm_mul_ps( diff, estimate ), estimate ) ); + } + + friend Vec4 Min( Vec4::Arg left, Vec4::Arg right ) + { + return Vec4( _mm_min_ps( left.m_v, right.m_v ) ); + } + + friend Vec4 Max( Vec4::Arg left, Vec4::Arg right ) + { + return Vec4( _mm_max_ps( left.m_v, right.m_v ) ); + } + + friend Vec4 Truncate( Vec4::Arg v ) + { +#if ( SQUISH_USE_SSE == 1 ) + // convert to ints + __m128 input = v.m_v; + __m64 lo = _mm_cvttps_pi32( input ); + __m64 hi = _mm_cvttps_pi32( _mm_movehl_ps( input, input ) ); + + // convert to floats + __m128 part = _mm_movelh_ps( input, _mm_cvtpi32_ps( input, hi ) ); + __m128 truncated = _mm_cvtpi32_ps( part, lo ); + + // clear out the MMX multimedia state to allow FP calls later + _mm_empty(); + return Vec4( truncated ); +#else + // use SSE2 instructions + return Vec4( _mm_cvtepi32_ps( _mm_cvttps_epi32( v.m_v ) ) ); +#endif + } + + friend bool CompareAnyLessThan( Vec4::Arg left, Vec4::Arg right ) + { + __m128 bits = _mm_cmplt_ps( left.m_v, right.m_v ); + int value = _mm_movemask_ps( bits ); + return value != 0; + } + +private: + __m128 m_v; +}; + +} // namespace squish + +#endif // ndef SQUISH_SIMD_SSE_H diff --git a/utils/squish/simd_ve.h b/utils/squish/simd_ve.h new file mode 100644 index 0000000..d86e2f1 --- /dev/null +++ b/utils/squish/simd_ve.h @@ -0,0 +1,166 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_SIMD_VE_H +#define SQUISH_SIMD_VE_H + +#include +#undef bool + +namespace squish { + +#define VEC4_CONST( X ) Vec4( ( vector float ){ X } ) + +class Vec4 +{ +public: + typedef Vec4 Arg; + + Vec4() {} + + explicit Vec4( vector float v ) : m_v( v ) {} + + Vec4( Vec4 const& arg ) : m_v( arg.m_v ) {} + + Vec4& operator=( Vec4 const& arg ) + { + m_v = arg.m_v; + return *this; + } + + explicit Vec4( float s ) + { + union { vector float v; float c[4]; } u; + u.c[0] = s; + u.c[1] = s; + u.c[2] = s; + u.c[3] = s; + m_v = u.v; + } + + Vec4( float x, float y, float z, float w ) + { + union { vector float v; float c[4]; } u; + u.c[0] = x; + u.c[1] = y; + u.c[2] = z; + u.c[3] = w; + m_v = u.v; + } + + Vec3 GetVec3() const + { + union { vector float v; float c[4]; } u; + u.v = m_v; + return Vec3( u.c[0], u.c[1], u.c[2] ); + } + + Vec4 SplatX() const { return Vec4( vec_splat( m_v, 0 ) ); } + Vec4 SplatY() const { return Vec4( vec_splat( m_v, 1 ) ); } + Vec4 SplatZ() const { return Vec4( vec_splat( m_v, 2 ) ); } + Vec4 SplatW() const { return Vec4( vec_splat( m_v, 3 ) ); } + + Vec4& operator+=( Arg v ) + { + m_v = vec_add( m_v, v.m_v ); + return *this; + } + + Vec4& operator-=( Arg v ) + { + m_v = vec_sub( m_v, v.m_v ); + return *this; + } + + Vec4& operator*=( Arg v ) + { + m_v = vec_madd( m_v, v.m_v, ( vector float ){ -0.0f } ); + return *this; + } + + friend Vec4 operator+( Vec4::Arg left, Vec4::Arg right ) + { + return Vec4( vec_add( left.m_v, right.m_v ) ); + } + + friend Vec4 operator-( Vec4::Arg left, Vec4::Arg right ) + { + return Vec4( vec_sub( left.m_v, right.m_v ) ); + } + + friend Vec4 operator*( Vec4::Arg left, Vec4::Arg right ) + { + return Vec4( vec_madd( left.m_v, right.m_v, ( vector float ){ -0.0f } ) ); + } + + //! Returns a*b + c + friend Vec4 MultiplyAdd( Vec4::Arg a, Vec4::Arg b, Vec4::Arg c ) + { + return Vec4( vec_madd( a.m_v, b.m_v, c.m_v ) ); + } + + //! Returns -( a*b - c ) + friend Vec4 NegativeMultiplySubtract( Vec4::Arg a, Vec4::Arg b, Vec4::Arg c ) + { + return Vec4( vec_nmsub( a.m_v, b.m_v, c.m_v ) ); + } + + friend Vec4 Reciprocal( Vec4::Arg v ) + { + // get the reciprocal estimate + vector float estimate = vec_re( v.m_v ); + + // one round of Newton-Rhaphson refinement + vector float diff = vec_nmsub( estimate, v.m_v, ( vector float ){ 1.0f } ); + return Vec4( vec_madd( diff, estimate, estimate ) ); + } + + friend Vec4 Min( Vec4::Arg left, Vec4::Arg right ) + { + return Vec4( vec_min( left.m_v, right.m_v ) ); + } + + friend Vec4 Max( Vec4::Arg left, Vec4::Arg right ) + { + return Vec4( vec_max( left.m_v, right.m_v ) ); + } + + friend Vec4 Truncate( Vec4::Arg v ) + { + return Vec4( vec_trunc( v.m_v ) ); + } + + friend bool CompareAnyLessThan( Vec4::Arg left, Vec4::Arg right ) + { + return vec_any_lt( left.m_v, right.m_v ) != 0; + } + +private: + vector float m_v; +}; + +} // namespace squish + +#endif // ndef SQUISH_SIMD_VE_H diff --git a/utils/squish/singlecolourfit.cpp b/utils/squish/singlecolourfit.cpp new file mode 100644 index 0000000..c4f5b13 --- /dev/null +++ b/utils/squish/singlecolourfit.cpp @@ -0,0 +1,172 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#include "singlecolourfit.h" +#include "colourset.h" +#include "colourblock.h" + +namespace squish { + +struct SourceBlock +{ + u8 start; + u8 end; + u8 error; +}; + +struct SingleColourLookup +{ + SourceBlock sources[2]; +}; + +#include "singlecolourlookup.inl" + +static int FloatToInt( float a, int limit ) +{ + // use ANSI round-to-zero behaviour to get round-to-nearest + int i = ( int )( a + 0.5f ); + + // clamp to the limit + if( i < 0 ) + i = 0; + else if( i > limit ) + i = limit; + + // done + return i; +} + +SingleColourFit::SingleColourFit( ColourSet const* colours, int flags ) + : ColourFit( colours, flags ) +{ + // grab the single colour + Vec3 const* values = m_colours->GetPoints(); + m_colour[0] = ( u8 )FloatToInt( 255.0f*values->X(), 255 ); + m_colour[1] = ( u8 )FloatToInt( 255.0f*values->Y(), 255 ); + m_colour[2] = ( u8 )FloatToInt( 255.0f*values->Z(), 255 ); + + // initialise the best error + m_besterror = INT_MAX; +} + +void SingleColourFit::Compress3( void* block ) +{ + // build the table of lookups + SingleColourLookup const* const lookups[] = + { + lookup_5_3, + lookup_6_3, + lookup_5_3 + }; + + // find the best end-points and index + ComputeEndPoints( lookups ); + + // build the block if we win + if( m_error < m_besterror ) + { + // remap the indices + u8 indices[16]; + m_colours->RemapIndices( &m_index, indices ); + + // save the block + WriteColourBlock3( m_start, m_end, indices, block ); + + // save the error + m_besterror = m_error; + } +} + +void SingleColourFit::Compress4( void* block ) +{ + // build the table of lookups + SingleColourLookup const* const lookups[] = + { + lookup_5_4, + lookup_6_4, + lookup_5_4 + }; + + // find the best end-points and index + ComputeEndPoints( lookups ); + + // build the block if we win + if( m_error < m_besterror ) + { + // remap the indices + u8 indices[16]; + m_colours->RemapIndices( &m_index, indices ); + + // save the block + WriteColourBlock4( m_start, m_end, indices, block ); + + // save the error + m_besterror = m_error; + } +} + +void SingleColourFit::ComputeEndPoints( SingleColourLookup const* const* lookups ) +{ + // check each index combination (endpoint or intermediate) + m_error = INT_MAX; + for( int index = 0; index < 2; ++index ) + { + // check the error for this codebook index + SourceBlock const* sources[3]; + int error = 0; + for( int channel = 0; channel < 3; ++channel ) + { + // grab the lookup table and index for this channel + SingleColourLookup const* lookup = lookups[channel]; + int target = m_colour[channel]; + + // store a pointer to the source for this channel + sources[channel] = lookup[target].sources + index; + + // accumulate the error + int diff = sources[channel]->error; + error += diff*diff; + } + + // keep it if the error is lower + if( error < m_error ) + { + m_start = Vec3( + ( float )sources[0]->start/31.0f, + ( float )sources[1]->start/63.0f, + ( float )sources[2]->start/31.0f + ); + m_end = Vec3( + ( float )sources[0]->end/31.0f, + ( float )sources[1]->end/63.0f, + ( float )sources[2]->end/31.0f + ); + m_index = ( u8 )( 2*index ); + m_error = error; + } + } +} + +} // namespace squish diff --git a/utils/squish/singlecolourfit.h b/utils/squish/singlecolourfit.h new file mode 100644 index 0000000..08d5ca4 --- /dev/null +++ b/utils/squish/singlecolourfit.h @@ -0,0 +1,58 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#ifndef SQUISH_SINGLECOLOURFIT_H +#define SQUISH_SINGLECOLOURFIT_H + +#include "squish.h" +#include "colourfit.h" + +namespace squish { + +class ColourSet; +struct SingleColourLookup; + +class SingleColourFit : public ColourFit +{ +public: + SingleColourFit( ColourSet const* colours, int flags ); + +private: + virtual void Compress3( void* block ); + virtual void Compress4( void* block ); + + void ComputeEndPoints( SingleColourLookup const* const* lookups ); + + u8 m_colour[3]; + Vec3 m_start; + Vec3 m_end; + u8 m_index; + int m_error; + int m_besterror; +}; + +} // namespace squish + +#endif // ndef SQUISH_SINGLECOLOURFIT_H diff --git a/utils/squish/singlecolourlookup.inl b/utils/squish/singlecolourlookup.inl new file mode 100644 index 0000000..7338f92 --- /dev/null +++ b/utils/squish/singlecolourlookup.inl @@ -0,0 +1,1064 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +static SingleColourLookup const lookup_5_3[] = +{ + { { { 0, 0, 0 }, { 0, 0, 0 } } }, + { { { 0, 0, 1 }, { 0, 0, 1 } } }, + { { { 0, 0, 2 }, { 0, 0, 2 } } }, + { { { 0, 0, 3 }, { 0, 1, 1 } } }, + { { { 0, 0, 4 }, { 0, 1, 0 } } }, + { { { 1, 0, 3 }, { 0, 1, 1 } } }, + { { { 1, 0, 2 }, { 0, 1, 2 } } }, + { { { 1, 0, 1 }, { 0, 2, 1 } } }, + { { { 1, 0, 0 }, { 0, 2, 0 } } }, + { { { 1, 0, 1 }, { 0, 2, 1 } } }, + { { { 1, 0, 2 }, { 0, 2, 2 } } }, + { { { 1, 0, 3 }, { 0, 3, 1 } } }, + { { { 1, 0, 4 }, { 0, 3, 0 } } }, + { { { 2, 0, 3 }, { 0, 3, 1 } } }, + { { { 2, 0, 2 }, { 0, 3, 2 } } }, + { { { 2, 0, 1 }, { 0, 4, 1 } } }, + { { { 2, 0, 0 }, { 0, 4, 0 } } }, + { { { 2, 0, 1 }, { 0, 4, 1 } } }, + { { { 2, 0, 2 }, { 0, 4, 2 } } }, + { { { 2, 0, 3 }, { 0, 5, 1 } } }, + { { { 2, 0, 4 }, { 0, 5, 0 } } }, + { { { 3, 0, 3 }, { 0, 5, 1 } } }, + { { { 3, 0, 2 }, { 0, 5, 2 } } }, + { { { 3, 0, 1 }, { 0, 6, 1 } } }, + { { { 3, 0, 0 }, { 0, 6, 0 } } }, + { { { 3, 0, 1 }, { 0, 6, 1 } } }, + { { { 3, 0, 2 }, { 0, 6, 2 } } }, + { { { 3, 0, 3 }, { 0, 7, 1 } } }, + { { { 3, 0, 4 }, { 0, 7, 0 } } }, + { { { 4, 0, 4 }, { 0, 7, 1 } } }, + { { { 4, 0, 3 }, { 0, 7, 2 } } }, + { { { 4, 0, 2 }, { 1, 7, 1 } } }, + { { { 4, 0, 1 }, { 1, 7, 0 } } }, + { { { 4, 0, 0 }, { 0, 8, 0 } } }, + { { { 4, 0, 1 }, { 0, 8, 1 } } }, + { { { 4, 0, 2 }, { 2, 7, 1 } } }, + { { { 4, 0, 3 }, { 2, 7, 0 } } }, + { { { 4, 0, 4 }, { 0, 9, 0 } } }, + { { { 5, 0, 3 }, { 0, 9, 1 } } }, + { { { 5, 0, 2 }, { 3, 7, 1 } } }, + { { { 5, 0, 1 }, { 3, 7, 0 } } }, + { { { 5, 0, 0 }, { 0, 10, 0 } } }, + { { { 5, 0, 1 }, { 0, 10, 1 } } }, + { { { 5, 0, 2 }, { 0, 10, 2 } } }, + { { { 5, 0, 3 }, { 0, 11, 1 } } }, + { { { 5, 0, 4 }, { 0, 11, 0 } } }, + { { { 6, 0, 3 }, { 0, 11, 1 } } }, + { { { 6, 0, 2 }, { 0, 11, 2 } } }, + { { { 6, 0, 1 }, { 0, 12, 1 } } }, + { { { 6, 0, 0 }, { 0, 12, 0 } } }, + { { { 6, 0, 1 }, { 0, 12, 1 } } }, + { { { 6, 0, 2 }, { 0, 12, 2 } } }, + { { { 6, 0, 3 }, { 0, 13, 1 } } }, + { { { 6, 0, 4 }, { 0, 13, 0 } } }, + { { { 7, 0, 3 }, { 0, 13, 1 } } }, + { { { 7, 0, 2 }, { 0, 13, 2 } } }, + { { { 7, 0, 1 }, { 0, 14, 1 } } }, + { { { 7, 0, 0 }, { 0, 14, 0 } } }, + { { { 7, 0, 1 }, { 0, 14, 1 } } }, + { { { 7, 0, 2 }, { 0, 14, 2 } } }, + { { { 7, 0, 3 }, { 0, 15, 1 } } }, + { { { 7, 0, 4 }, { 0, 15, 0 } } }, + { { { 8, 0, 4 }, { 0, 15, 1 } } }, + { { { 8, 0, 3 }, { 0, 15, 2 } } }, + { { { 8, 0, 2 }, { 1, 15, 1 } } }, + { { { 8, 0, 1 }, { 1, 15, 0 } } }, + { { { 8, 0, 0 }, { 0, 16, 0 } } }, + { { { 8, 0, 1 }, { 0, 16, 1 } } }, + { { { 8, 0, 2 }, { 2, 15, 1 } } }, + { { { 8, 0, 3 }, { 2, 15, 0 } } }, + { { { 8, 0, 4 }, { 0, 17, 0 } } }, + { { { 9, 0, 3 }, { 0, 17, 1 } } }, + { { { 9, 0, 2 }, { 3, 15, 1 } } }, + { { { 9, 0, 1 }, { 3, 15, 0 } } }, + { { { 9, 0, 0 }, { 0, 18, 0 } } }, + { { { 9, 0, 1 }, { 0, 18, 1 } } }, + { { { 9, 0, 2 }, { 0, 18, 2 } } }, + { { { 9, 0, 3 }, { 0, 19, 1 } } }, + { { { 9, 0, 4 }, { 0, 19, 0 } } }, + { { { 10, 0, 3 }, { 0, 19, 1 } } }, + { { { 10, 0, 2 }, { 0, 19, 2 } } }, + { { { 10, 0, 1 }, { 0, 20, 1 } } }, + { { { 10, 0, 0 }, { 0, 20, 0 } } }, + { { { 10, 0, 1 }, { 0, 20, 1 } } }, + { { { 10, 0, 2 }, { 0, 20, 2 } } }, + { { { 10, 0, 3 }, { 0, 21, 1 } } }, + { { { 10, 0, 4 }, { 0, 21, 0 } } }, + { { { 11, 0, 3 }, { 0, 21, 1 } } }, + { { { 11, 0, 2 }, { 0, 21, 2 } } }, + { { { 11, 0, 1 }, { 0, 22, 1 } } }, + { { { 11, 0, 0 }, { 0, 22, 0 } } }, + { { { 11, 0, 1 }, { 0, 22, 1 } } }, + { { { 11, 0, 2 }, { 0, 22, 2 } } }, + { { { 11, 0, 3 }, { 0, 23, 1 } } }, + { { { 11, 0, 4 }, { 0, 23, 0 } } }, + { { { 12, 0, 4 }, { 0, 23, 1 } } }, + { { { 12, 0, 3 }, { 0, 23, 2 } } }, + { { { 12, 0, 2 }, { 1, 23, 1 } } }, + { { { 12, 0, 1 }, { 1, 23, 0 } } }, + { { { 12, 0, 0 }, { 0, 24, 0 } } }, + { { { 12, 0, 1 }, { 0, 24, 1 } } }, + { { { 12, 0, 2 }, { 2, 23, 1 } } }, + { { { 12, 0, 3 }, { 2, 23, 0 } } }, + { { { 12, 0, 4 }, { 0, 25, 0 } } }, + { { { 13, 0, 3 }, { 0, 25, 1 } } }, + { { { 13, 0, 2 }, { 3, 23, 1 } } }, + { { { 13, 0, 1 }, { 3, 23, 0 } } }, + { { { 13, 0, 0 }, { 0, 26, 0 } } }, + { { { 13, 0, 1 }, { 0, 26, 1 } } }, + { { { 13, 0, 2 }, { 0, 26, 2 } } }, + { { { 13, 0, 3 }, { 0, 27, 1 } } }, + { { { 13, 0, 4 }, { 0, 27, 0 } } }, + { { { 14, 0, 3 }, { 0, 27, 1 } } }, + { { { 14, 0, 2 }, { 0, 27, 2 } } }, + { { { 14, 0, 1 }, { 0, 28, 1 } } }, + { { { 14, 0, 0 }, { 0, 28, 0 } } }, + { { { 14, 0, 1 }, { 0, 28, 1 } } }, + { { { 14, 0, 2 }, { 0, 28, 2 } } }, + { { { 14, 0, 3 }, { 0, 29, 1 } } }, + { { { 14, 0, 4 }, { 0, 29, 0 } } }, + { { { 15, 0, 3 }, { 0, 29, 1 } } }, + { { { 15, 0, 2 }, { 0, 29, 2 } } }, + { { { 15, 0, 1 }, { 0, 30, 1 } } }, + { { { 15, 0, 0 }, { 0, 30, 0 } } }, + { { { 15, 0, 1 }, { 0, 30, 1 } } }, + { { { 15, 0, 2 }, { 0, 30, 2 } } }, + { { { 15, 0, 3 }, { 0, 31, 1 } } }, + { { { 15, 0, 4 }, { 0, 31, 0 } } }, + { { { 16, 0, 4 }, { 0, 31, 1 } } }, + { { { 16, 0, 3 }, { 0, 31, 2 } } }, + { { { 16, 0, 2 }, { 1, 31, 1 } } }, + { { { 16, 0, 1 }, { 1, 31, 0 } } }, + { { { 16, 0, 0 }, { 4, 28, 0 } } }, + { { { 16, 0, 1 }, { 4, 28, 1 } } }, + { { { 16, 0, 2 }, { 2, 31, 1 } } }, + { { { 16, 0, 3 }, { 2, 31, 0 } } }, + { { { 16, 0, 4 }, { 4, 29, 0 } } }, + { { { 17, 0, 3 }, { 4, 29, 1 } } }, + { { { 17, 0, 2 }, { 3, 31, 1 } } }, + { { { 17, 0, 1 }, { 3, 31, 0 } } }, + { { { 17, 0, 0 }, { 4, 30, 0 } } }, + { { { 17, 0, 1 }, { 4, 30, 1 } } }, + { { { 17, 0, 2 }, { 4, 30, 2 } } }, + { { { 17, 0, 3 }, { 4, 31, 1 } } }, + { { { 17, 0, 4 }, { 4, 31, 0 } } }, + { { { 18, 0, 3 }, { 4, 31, 1 } } }, + { { { 18, 0, 2 }, { 4, 31, 2 } } }, + { { { 18, 0, 1 }, { 5, 31, 1 } } }, + { { { 18, 0, 0 }, { 5, 31, 0 } } }, + { { { 18, 0, 1 }, { 5, 31, 1 } } }, + { { { 18, 0, 2 }, { 5, 31, 2 } } }, + { { { 18, 0, 3 }, { 6, 31, 1 } } }, + { { { 18, 0, 4 }, { 6, 31, 0 } } }, + { { { 19, 0, 3 }, { 6, 31, 1 } } }, + { { { 19, 0, 2 }, { 6, 31, 2 } } }, + { { { 19, 0, 1 }, { 7, 31, 1 } } }, + { { { 19, 0, 0 }, { 7, 31, 0 } } }, + { { { 19, 0, 1 }, { 7, 31, 1 } } }, + { { { 19, 0, 2 }, { 7, 31, 2 } } }, + { { { 19, 0, 3 }, { 8, 31, 1 } } }, + { { { 19, 0, 4 }, { 8, 31, 0 } } }, + { { { 20, 0, 4 }, { 8, 31, 1 } } }, + { { { 20, 0, 3 }, { 8, 31, 2 } } }, + { { { 20, 0, 2 }, { 9, 31, 1 } } }, + { { { 20, 0, 1 }, { 9, 31, 0 } } }, + { { { 20, 0, 0 }, { 12, 28, 0 } } }, + { { { 20, 0, 1 }, { 12, 28, 1 } } }, + { { { 20, 0, 2 }, { 10, 31, 1 } } }, + { { { 20, 0, 3 }, { 10, 31, 0 } } }, + { { { 20, 0, 4 }, { 12, 29, 0 } } }, + { { { 21, 0, 3 }, { 12, 29, 1 } } }, + { { { 21, 0, 2 }, { 11, 31, 1 } } }, + { { { 21, 0, 1 }, { 11, 31, 0 } } }, + { { { 21, 0, 0 }, { 12, 30, 0 } } }, + { { { 21, 0, 1 }, { 12, 30, 1 } } }, + { { { 21, 0, 2 }, { 12, 30, 2 } } }, + { { { 21, 0, 3 }, { 12, 31, 1 } } }, + { { { 21, 0, 4 }, { 12, 31, 0 } } }, + { { { 22, 0, 3 }, { 12, 31, 1 } } }, + { { { 22, 0, 2 }, { 12, 31, 2 } } }, + { { { 22, 0, 1 }, { 13, 31, 1 } } }, + { { { 22, 0, 0 }, { 13, 31, 0 } } }, + { { { 22, 0, 1 }, { 13, 31, 1 } } }, + { { { 22, 0, 2 }, { 13, 31, 2 } } }, + { { { 22, 0, 3 }, { 14, 31, 1 } } }, + { { { 22, 0, 4 }, { 14, 31, 0 } } }, + { { { 23, 0, 3 }, { 14, 31, 1 } } }, + { { { 23, 0, 2 }, { 14, 31, 2 } } }, + { { { 23, 0, 1 }, { 15, 31, 1 } } }, + { { { 23, 0, 0 }, { 15, 31, 0 } } }, + { { { 23, 0, 1 }, { 15, 31, 1 } } }, + { { { 23, 0, 2 }, { 15, 31, 2 } } }, + { { { 23, 0, 3 }, { 16, 31, 1 } } }, + { { { 23, 0, 4 }, { 16, 31, 0 } } }, + { { { 24, 0, 4 }, { 16, 31, 1 } } }, + { { { 24, 0, 3 }, { 16, 31, 2 } } }, + { { { 24, 0, 2 }, { 17, 31, 1 } } }, + { { { 24, 0, 1 }, { 17, 31, 0 } } }, + { { { 24, 0, 0 }, { 20, 28, 0 } } }, + { { { 24, 0, 1 }, { 20, 28, 1 } } }, + { { { 24, 0, 2 }, { 18, 31, 1 } } }, + { { { 24, 0, 3 }, { 18, 31, 0 } } }, + { { { 24, 0, 4 }, { 20, 29, 0 } } }, + { { { 25, 0, 3 }, { 20, 29, 1 } } }, + { { { 25, 0, 2 }, { 19, 31, 1 } } }, + { { { 25, 0, 1 }, { 19, 31, 0 } } }, + { { { 25, 0, 0 }, { 20, 30, 0 } } }, + { { { 25, 0, 1 }, { 20, 30, 1 } } }, + { { { 25, 0, 2 }, { 20, 30, 2 } } }, + { { { 25, 0, 3 }, { 20, 31, 1 } } }, + { { { 25, 0, 4 }, { 20, 31, 0 } } }, + { { { 26, 0, 3 }, { 20, 31, 1 } } }, + { { { 26, 0, 2 }, { 20, 31, 2 } } }, + { { { 26, 0, 1 }, { 21, 31, 1 } } }, + { { { 26, 0, 0 }, { 21, 31, 0 } } }, + { { { 26, 0, 1 }, { 21, 31, 1 } } }, + { { { 26, 0, 2 }, { 21, 31, 2 } } }, + { { { 26, 0, 3 }, { 22, 31, 1 } } }, + { { { 26, 0, 4 }, { 22, 31, 0 } } }, + { { { 27, 0, 3 }, { 22, 31, 1 } } }, + { { { 27, 0, 2 }, { 22, 31, 2 } } }, + { { { 27, 0, 1 }, { 23, 31, 1 } } }, + { { { 27, 0, 0 }, { 23, 31, 0 } } }, + { { { 27, 0, 1 }, { 23, 31, 1 } } }, + { { { 27, 0, 2 }, { 23, 31, 2 } } }, + { { { 27, 0, 3 }, { 24, 31, 1 } } }, + { { { 27, 0, 4 }, { 24, 31, 0 } } }, + { { { 28, 0, 4 }, { 24, 31, 1 } } }, + { { { 28, 0, 3 }, { 24, 31, 2 } } }, + { { { 28, 0, 2 }, { 25, 31, 1 } } }, + { { { 28, 0, 1 }, { 25, 31, 0 } } }, + { { { 28, 0, 0 }, { 28, 28, 0 } } }, + { { { 28, 0, 1 }, { 28, 28, 1 } } }, + { { { 28, 0, 2 }, { 26, 31, 1 } } }, + { { { 28, 0, 3 }, { 26, 31, 0 } } }, + { { { 28, 0, 4 }, { 28, 29, 0 } } }, + { { { 29, 0, 3 }, { 28, 29, 1 } } }, + { { { 29, 0, 2 }, { 27, 31, 1 } } }, + { { { 29, 0, 1 }, { 27, 31, 0 } } }, + { { { 29, 0, 0 }, { 28, 30, 0 } } }, + { { { 29, 0, 1 }, { 28, 30, 1 } } }, + { { { 29, 0, 2 }, { 28, 30, 2 } } }, + { { { 29, 0, 3 }, { 28, 31, 1 } } }, + { { { 29, 0, 4 }, { 28, 31, 0 } } }, + { { { 30, 0, 3 }, { 28, 31, 1 } } }, + { { { 30, 0, 2 }, { 28, 31, 2 } } }, + { { { 30, 0, 1 }, { 29, 31, 1 } } }, + { { { 30, 0, 0 }, { 29, 31, 0 } } }, + { { { 30, 0, 1 }, { 29, 31, 1 } } }, + { { { 30, 0, 2 }, { 29, 31, 2 } } }, + { { { 30, 0, 3 }, { 30, 31, 1 } } }, + { { { 30, 0, 4 }, { 30, 31, 0 } } }, + { { { 31, 0, 3 }, { 30, 31, 1 } } }, + { { { 31, 0, 2 }, { 30, 31, 2 } } }, + { { { 31, 0, 1 }, { 31, 31, 1 } } }, + { { { 31, 0, 0 }, { 31, 31, 0 } } } +}; + +static SingleColourLookup const lookup_6_3[] = +{ + { { { 0, 0, 0 }, { 0, 0, 0 } } }, + { { { 0, 0, 1 }, { 0, 1, 1 } } }, + { { { 0, 0, 2 }, { 0, 1, 0 } } }, + { { { 1, 0, 1 }, { 0, 2, 1 } } }, + { { { 1, 0, 0 }, { 0, 2, 0 } } }, + { { { 1, 0, 1 }, { 0, 3, 1 } } }, + { { { 1, 0, 2 }, { 0, 3, 0 } } }, + { { { 2, 0, 1 }, { 0, 4, 1 } } }, + { { { 2, 0, 0 }, { 0, 4, 0 } } }, + { { { 2, 0, 1 }, { 0, 5, 1 } } }, + { { { 2, 0, 2 }, { 0, 5, 0 } } }, + { { { 3, 0, 1 }, { 0, 6, 1 } } }, + { { { 3, 0, 0 }, { 0, 6, 0 } } }, + { { { 3, 0, 1 }, { 0, 7, 1 } } }, + { { { 3, 0, 2 }, { 0, 7, 0 } } }, + { { { 4, 0, 1 }, { 0, 8, 1 } } }, + { { { 4, 0, 0 }, { 0, 8, 0 } } }, + { { { 4, 0, 1 }, { 0, 9, 1 } } }, + { { { 4, 0, 2 }, { 0, 9, 0 } } }, + { { { 5, 0, 1 }, { 0, 10, 1 } } }, + { { { 5, 0, 0 }, { 0, 10, 0 } } }, + { { { 5, 0, 1 }, { 0, 11, 1 } } }, + { { { 5, 0, 2 }, { 0, 11, 0 } } }, + { { { 6, 0, 1 }, { 0, 12, 1 } } }, + { { { 6, 0, 0 }, { 0, 12, 0 } } }, + { { { 6, 0, 1 }, { 0, 13, 1 } } }, + { { { 6, 0, 2 }, { 0, 13, 0 } } }, + { { { 7, 0, 1 }, { 0, 14, 1 } } }, + { { { 7, 0, 0 }, { 0, 14, 0 } } }, + { { { 7, 0, 1 }, { 0, 15, 1 } } }, + { { { 7, 0, 2 }, { 0, 15, 0 } } }, + { { { 8, 0, 1 }, { 0, 16, 1 } } }, + { { { 8, 0, 0 }, { 0, 16, 0 } } }, + { { { 8, 0, 1 }, { 0, 17, 1 } } }, + { { { 8, 0, 2 }, { 0, 17, 0 } } }, + { { { 9, 0, 1 }, { 0, 18, 1 } } }, + { { { 9, 0, 0 }, { 0, 18, 0 } } }, + { { { 9, 0, 1 }, { 0, 19, 1 } } }, + { { { 9, 0, 2 }, { 0, 19, 0 } } }, + { { { 10, 0, 1 }, { 0, 20, 1 } } }, + { { { 10, 0, 0 }, { 0, 20, 0 } } }, + { { { 10, 0, 1 }, { 0, 21, 1 } } }, + { { { 10, 0, 2 }, { 0, 21, 0 } } }, + { { { 11, 0, 1 }, { 0, 22, 1 } } }, + { { { 11, 0, 0 }, { 0, 22, 0 } } }, + { { { 11, 0, 1 }, { 0, 23, 1 } } }, + { { { 11, 0, 2 }, { 0, 23, 0 } } }, + { { { 12, 0, 1 }, { 0, 24, 1 } } }, + { { { 12, 0, 0 }, { 0, 24, 0 } } }, + { { { 12, 0, 1 }, { 0, 25, 1 } } }, + { { { 12, 0, 2 }, { 0, 25, 0 } } }, + { { { 13, 0, 1 }, { 0, 26, 1 } } }, + { { { 13, 0, 0 }, { 0, 26, 0 } } }, + { { { 13, 0, 1 }, { 0, 27, 1 } } }, + { { { 13, 0, 2 }, { 0, 27, 0 } } }, + { { { 14, 0, 1 }, { 0, 28, 1 } } }, + { { { 14, 0, 0 }, { 0, 28, 0 } } }, + { { { 14, 0, 1 }, { 0, 29, 1 } } }, + { { { 14, 0, 2 }, { 0, 29, 0 } } }, + { { { 15, 0, 1 }, { 0, 30, 1 } } }, + { { { 15, 0, 0 }, { 0, 30, 0 } } }, + { { { 15, 0, 1 }, { 0, 31, 1 } } }, + { { { 15, 0, 2 }, { 0, 31, 0 } } }, + { { { 16, 0, 2 }, { 1, 31, 1 } } }, + { { { 16, 0, 1 }, { 1, 31, 0 } } }, + { { { 16, 0, 0 }, { 0, 32, 0 } } }, + { { { 16, 0, 1 }, { 2, 31, 0 } } }, + { { { 16, 0, 2 }, { 0, 33, 0 } } }, + { { { 17, 0, 1 }, { 3, 31, 0 } } }, + { { { 17, 0, 0 }, { 0, 34, 0 } } }, + { { { 17, 0, 1 }, { 4, 31, 0 } } }, + { { { 17, 0, 2 }, { 0, 35, 0 } } }, + { { { 18, 0, 1 }, { 5, 31, 0 } } }, + { { { 18, 0, 0 }, { 0, 36, 0 } } }, + { { { 18, 0, 1 }, { 6, 31, 0 } } }, + { { { 18, 0, 2 }, { 0, 37, 0 } } }, + { { { 19, 0, 1 }, { 7, 31, 0 } } }, + { { { 19, 0, 0 }, { 0, 38, 0 } } }, + { { { 19, 0, 1 }, { 8, 31, 0 } } }, + { { { 19, 0, 2 }, { 0, 39, 0 } } }, + { { { 20, 0, 1 }, { 9, 31, 0 } } }, + { { { 20, 0, 0 }, { 0, 40, 0 } } }, + { { { 20, 0, 1 }, { 10, 31, 0 } } }, + { { { 20, 0, 2 }, { 0, 41, 0 } } }, + { { { 21, 0, 1 }, { 11, 31, 0 } } }, + { { { 21, 0, 0 }, { 0, 42, 0 } } }, + { { { 21, 0, 1 }, { 12, 31, 0 } } }, + { { { 21, 0, 2 }, { 0, 43, 0 } } }, + { { { 22, 0, 1 }, { 13, 31, 0 } } }, + { { { 22, 0, 0 }, { 0, 44, 0 } } }, + { { { 22, 0, 1 }, { 14, 31, 0 } } }, + { { { 22, 0, 2 }, { 0, 45, 0 } } }, + { { { 23, 0, 1 }, { 15, 31, 0 } } }, + { { { 23, 0, 0 }, { 0, 46, 0 } } }, + { { { 23, 0, 1 }, { 0, 47, 1 } } }, + { { { 23, 0, 2 }, { 0, 47, 0 } } }, + { { { 24, 0, 1 }, { 0, 48, 1 } } }, + { { { 24, 0, 0 }, { 0, 48, 0 } } }, + { { { 24, 0, 1 }, { 0, 49, 1 } } }, + { { { 24, 0, 2 }, { 0, 49, 0 } } }, + { { { 25, 0, 1 }, { 0, 50, 1 } } }, + { { { 25, 0, 0 }, { 0, 50, 0 } } }, + { { { 25, 0, 1 }, { 0, 51, 1 } } }, + { { { 25, 0, 2 }, { 0, 51, 0 } } }, + { { { 26, 0, 1 }, { 0, 52, 1 } } }, + { { { 26, 0, 0 }, { 0, 52, 0 } } }, + { { { 26, 0, 1 }, { 0, 53, 1 } } }, + { { { 26, 0, 2 }, { 0, 53, 0 } } }, + { { { 27, 0, 1 }, { 0, 54, 1 } } }, + { { { 27, 0, 0 }, { 0, 54, 0 } } }, + { { { 27, 0, 1 }, { 0, 55, 1 } } }, + { { { 27, 0, 2 }, { 0, 55, 0 } } }, + { { { 28, 0, 1 }, { 0, 56, 1 } } }, + { { { 28, 0, 0 }, { 0, 56, 0 } } }, + { { { 28, 0, 1 }, { 0, 57, 1 } } }, + { { { 28, 0, 2 }, { 0, 57, 0 } } }, + { { { 29, 0, 1 }, { 0, 58, 1 } } }, + { { { 29, 0, 0 }, { 0, 58, 0 } } }, + { { { 29, 0, 1 }, { 0, 59, 1 } } }, + { { { 29, 0, 2 }, { 0, 59, 0 } } }, + { { { 30, 0, 1 }, { 0, 60, 1 } } }, + { { { 30, 0, 0 }, { 0, 60, 0 } } }, + { { { 30, 0, 1 }, { 0, 61, 1 } } }, + { { { 30, 0, 2 }, { 0, 61, 0 } } }, + { { { 31, 0, 1 }, { 0, 62, 1 } } }, + { { { 31, 0, 0 }, { 0, 62, 0 } } }, + { { { 31, 0, 1 }, { 0, 63, 1 } } }, + { { { 31, 0, 2 }, { 0, 63, 0 } } }, + { { { 32, 0, 2 }, { 1, 63, 1 } } }, + { { { 32, 0, 1 }, { 1, 63, 0 } } }, + { { { 32, 0, 0 }, { 16, 48, 0 } } }, + { { { 32, 0, 1 }, { 2, 63, 0 } } }, + { { { 32, 0, 2 }, { 16, 49, 0 } } }, + { { { 33, 0, 1 }, { 3, 63, 0 } } }, + { { { 33, 0, 0 }, { 16, 50, 0 } } }, + { { { 33, 0, 1 }, { 4, 63, 0 } } }, + { { { 33, 0, 2 }, { 16, 51, 0 } } }, + { { { 34, 0, 1 }, { 5, 63, 0 } } }, + { { { 34, 0, 0 }, { 16, 52, 0 } } }, + { { { 34, 0, 1 }, { 6, 63, 0 } } }, + { { { 34, 0, 2 }, { 16, 53, 0 } } }, + { { { 35, 0, 1 }, { 7, 63, 0 } } }, + { { { 35, 0, 0 }, { 16, 54, 0 } } }, + { { { 35, 0, 1 }, { 8, 63, 0 } } }, + { { { 35, 0, 2 }, { 16, 55, 0 } } }, + { { { 36, 0, 1 }, { 9, 63, 0 } } }, + { { { 36, 0, 0 }, { 16, 56, 0 } } }, + { { { 36, 0, 1 }, { 10, 63, 0 } } }, + { { { 36, 0, 2 }, { 16, 57, 0 } } }, + { { { 37, 0, 1 }, { 11, 63, 0 } } }, + { { { 37, 0, 0 }, { 16, 58, 0 } } }, + { { { 37, 0, 1 }, { 12, 63, 0 } } }, + { { { 37, 0, 2 }, { 16, 59, 0 } } }, + { { { 38, 0, 1 }, { 13, 63, 0 } } }, + { { { 38, 0, 0 }, { 16, 60, 0 } } }, + { { { 38, 0, 1 }, { 14, 63, 0 } } }, + { { { 38, 0, 2 }, { 16, 61, 0 } } }, + { { { 39, 0, 1 }, { 15, 63, 0 } } }, + { { { 39, 0, 0 }, { 16, 62, 0 } } }, + { { { 39, 0, 1 }, { 16, 63, 1 } } }, + { { { 39, 0, 2 }, { 16, 63, 0 } } }, + { { { 40, 0, 1 }, { 17, 63, 1 } } }, + { { { 40, 0, 0 }, { 17, 63, 0 } } }, + { { { 40, 0, 1 }, { 18, 63, 1 } } }, + { { { 40, 0, 2 }, { 18, 63, 0 } } }, + { { { 41, 0, 1 }, { 19, 63, 1 } } }, + { { { 41, 0, 0 }, { 19, 63, 0 } } }, + { { { 41, 0, 1 }, { 20, 63, 1 } } }, + { { { 41, 0, 2 }, { 20, 63, 0 } } }, + { { { 42, 0, 1 }, { 21, 63, 1 } } }, + { { { 42, 0, 0 }, { 21, 63, 0 } } }, + { { { 42, 0, 1 }, { 22, 63, 1 } } }, + { { { 42, 0, 2 }, { 22, 63, 0 } } }, + { { { 43, 0, 1 }, { 23, 63, 1 } } }, + { { { 43, 0, 0 }, { 23, 63, 0 } } }, + { { { 43, 0, 1 }, { 24, 63, 1 } } }, + { { { 43, 0, 2 }, { 24, 63, 0 } } }, + { { { 44, 0, 1 }, { 25, 63, 1 } } }, + { { { 44, 0, 0 }, { 25, 63, 0 } } }, + { { { 44, 0, 1 }, { 26, 63, 1 } } }, + { { { 44, 0, 2 }, { 26, 63, 0 } } }, + { { { 45, 0, 1 }, { 27, 63, 1 } } }, + { { { 45, 0, 0 }, { 27, 63, 0 } } }, + { { { 45, 0, 1 }, { 28, 63, 1 } } }, + { { { 45, 0, 2 }, { 28, 63, 0 } } }, + { { { 46, 0, 1 }, { 29, 63, 1 } } }, + { { { 46, 0, 0 }, { 29, 63, 0 } } }, + { { { 46, 0, 1 }, { 30, 63, 1 } } }, + { { { 46, 0, 2 }, { 30, 63, 0 } } }, + { { { 47, 0, 1 }, { 31, 63, 1 } } }, + { { { 47, 0, 0 }, { 31, 63, 0 } } }, + { { { 47, 0, 1 }, { 32, 63, 1 } } }, + { { { 47, 0, 2 }, { 32, 63, 0 } } }, + { { { 48, 0, 2 }, { 33, 63, 1 } } }, + { { { 48, 0, 1 }, { 33, 63, 0 } } }, + { { { 48, 0, 0 }, { 48, 48, 0 } } }, + { { { 48, 0, 1 }, { 34, 63, 0 } } }, + { { { 48, 0, 2 }, { 48, 49, 0 } } }, + { { { 49, 0, 1 }, { 35, 63, 0 } } }, + { { { 49, 0, 0 }, { 48, 50, 0 } } }, + { { { 49, 0, 1 }, { 36, 63, 0 } } }, + { { { 49, 0, 2 }, { 48, 51, 0 } } }, + { { { 50, 0, 1 }, { 37, 63, 0 } } }, + { { { 50, 0, 0 }, { 48, 52, 0 } } }, + { { { 50, 0, 1 }, { 38, 63, 0 } } }, + { { { 50, 0, 2 }, { 48, 53, 0 } } }, + { { { 51, 0, 1 }, { 39, 63, 0 } } }, + { { { 51, 0, 0 }, { 48, 54, 0 } } }, + { { { 51, 0, 1 }, { 40, 63, 0 } } }, + { { { 51, 0, 2 }, { 48, 55, 0 } } }, + { { { 52, 0, 1 }, { 41, 63, 0 } } }, + { { { 52, 0, 0 }, { 48, 56, 0 } } }, + { { { 52, 0, 1 }, { 42, 63, 0 } } }, + { { { 52, 0, 2 }, { 48, 57, 0 } } }, + { { { 53, 0, 1 }, { 43, 63, 0 } } }, + { { { 53, 0, 0 }, { 48, 58, 0 } } }, + { { { 53, 0, 1 }, { 44, 63, 0 } } }, + { { { 53, 0, 2 }, { 48, 59, 0 } } }, + { { { 54, 0, 1 }, { 45, 63, 0 } } }, + { { { 54, 0, 0 }, { 48, 60, 0 } } }, + { { { 54, 0, 1 }, { 46, 63, 0 } } }, + { { { 54, 0, 2 }, { 48, 61, 0 } } }, + { { { 55, 0, 1 }, { 47, 63, 0 } } }, + { { { 55, 0, 0 }, { 48, 62, 0 } } }, + { { { 55, 0, 1 }, { 48, 63, 1 } } }, + { { { 55, 0, 2 }, { 48, 63, 0 } } }, + { { { 56, 0, 1 }, { 49, 63, 1 } } }, + { { { 56, 0, 0 }, { 49, 63, 0 } } }, + { { { 56, 0, 1 }, { 50, 63, 1 } } }, + { { { 56, 0, 2 }, { 50, 63, 0 } } }, + { { { 57, 0, 1 }, { 51, 63, 1 } } }, + { { { 57, 0, 0 }, { 51, 63, 0 } } }, + { { { 57, 0, 1 }, { 52, 63, 1 } } }, + { { { 57, 0, 2 }, { 52, 63, 0 } } }, + { { { 58, 0, 1 }, { 53, 63, 1 } } }, + { { { 58, 0, 0 }, { 53, 63, 0 } } }, + { { { 58, 0, 1 }, { 54, 63, 1 } } }, + { { { 58, 0, 2 }, { 54, 63, 0 } } }, + { { { 59, 0, 1 }, { 55, 63, 1 } } }, + { { { 59, 0, 0 }, { 55, 63, 0 } } }, + { { { 59, 0, 1 }, { 56, 63, 1 } } }, + { { { 59, 0, 2 }, { 56, 63, 0 } } }, + { { { 60, 0, 1 }, { 57, 63, 1 } } }, + { { { 60, 0, 0 }, { 57, 63, 0 } } }, + { { { 60, 0, 1 }, { 58, 63, 1 } } }, + { { { 60, 0, 2 }, { 58, 63, 0 } } }, + { { { 61, 0, 1 }, { 59, 63, 1 } } }, + { { { 61, 0, 0 }, { 59, 63, 0 } } }, + { { { 61, 0, 1 }, { 60, 63, 1 } } }, + { { { 61, 0, 2 }, { 60, 63, 0 } } }, + { { { 62, 0, 1 }, { 61, 63, 1 } } }, + { { { 62, 0, 0 }, { 61, 63, 0 } } }, + { { { 62, 0, 1 }, { 62, 63, 1 } } }, + { { { 62, 0, 2 }, { 62, 63, 0 } } }, + { { { 63, 0, 1 }, { 63, 63, 1 } } }, + { { { 63, 0, 0 }, { 63, 63, 0 } } } +}; + +static SingleColourLookup const lookup_5_4[] = +{ + { { { 0, 0, 0 }, { 0, 0, 0 } } }, + { { { 0, 0, 1 }, { 0, 1, 1 } } }, + { { { 0, 0, 2 }, { 0, 1, 0 } } }, + { { { 0, 0, 3 }, { 0, 1, 1 } } }, + { { { 0, 0, 4 }, { 0, 2, 1 } } }, + { { { 1, 0, 3 }, { 0, 2, 0 } } }, + { { { 1, 0, 2 }, { 0, 2, 1 } } }, + { { { 1, 0, 1 }, { 0, 3, 1 } } }, + { { { 1, 0, 0 }, { 0, 3, 0 } } }, + { { { 1, 0, 1 }, { 1, 2, 1 } } }, + { { { 1, 0, 2 }, { 1, 2, 0 } } }, + { { { 1, 0, 3 }, { 0, 4, 0 } } }, + { { { 1, 0, 4 }, { 0, 5, 1 } } }, + { { { 2, 0, 3 }, { 0, 5, 0 } } }, + { { { 2, 0, 2 }, { 0, 5, 1 } } }, + { { { 2, 0, 1 }, { 0, 6, 1 } } }, + { { { 2, 0, 0 }, { 0, 6, 0 } } }, + { { { 2, 0, 1 }, { 2, 3, 1 } } }, + { { { 2, 0, 2 }, { 2, 3, 0 } } }, + { { { 2, 0, 3 }, { 0, 7, 0 } } }, + { { { 2, 0, 4 }, { 1, 6, 1 } } }, + { { { 3, 0, 3 }, { 1, 6, 0 } } }, + { { { 3, 0, 2 }, { 0, 8, 0 } } }, + { { { 3, 0, 1 }, { 0, 9, 1 } } }, + { { { 3, 0, 0 }, { 0, 9, 0 } } }, + { { { 3, 0, 1 }, { 0, 9, 1 } } }, + { { { 3, 0, 2 }, { 0, 10, 1 } } }, + { { { 3, 0, 3 }, { 0, 10, 0 } } }, + { { { 3, 0, 4 }, { 2, 7, 1 } } }, + { { { 4, 0, 4 }, { 2, 7, 0 } } }, + { { { 4, 0, 3 }, { 0, 11, 0 } } }, + { { { 4, 0, 2 }, { 1, 10, 1 } } }, + { { { 4, 0, 1 }, { 1, 10, 0 } } }, + { { { 4, 0, 0 }, { 0, 12, 0 } } }, + { { { 4, 0, 1 }, { 0, 13, 1 } } }, + { { { 4, 0, 2 }, { 0, 13, 0 } } }, + { { { 4, 0, 3 }, { 0, 13, 1 } } }, + { { { 4, 0, 4 }, { 0, 14, 1 } } }, + { { { 5, 0, 3 }, { 0, 14, 0 } } }, + { { { 5, 0, 2 }, { 2, 11, 1 } } }, + { { { 5, 0, 1 }, { 2, 11, 0 } } }, + { { { 5, 0, 0 }, { 0, 15, 0 } } }, + { { { 5, 0, 1 }, { 1, 14, 1 } } }, + { { { 5, 0, 2 }, { 1, 14, 0 } } }, + { { { 5, 0, 3 }, { 0, 16, 0 } } }, + { { { 5, 0, 4 }, { 0, 17, 1 } } }, + { { { 6, 0, 3 }, { 0, 17, 0 } } }, + { { { 6, 0, 2 }, { 0, 17, 1 } } }, + { { { 6, 0, 1 }, { 0, 18, 1 } } }, + { { { 6, 0, 0 }, { 0, 18, 0 } } }, + { { { 6, 0, 1 }, { 2, 15, 1 } } }, + { { { 6, 0, 2 }, { 2, 15, 0 } } }, + { { { 6, 0, 3 }, { 0, 19, 0 } } }, + { { { 6, 0, 4 }, { 1, 18, 1 } } }, + { { { 7, 0, 3 }, { 1, 18, 0 } } }, + { { { 7, 0, 2 }, { 0, 20, 0 } } }, + { { { 7, 0, 1 }, { 0, 21, 1 } } }, + { { { 7, 0, 0 }, { 0, 21, 0 } } }, + { { { 7, 0, 1 }, { 0, 21, 1 } } }, + { { { 7, 0, 2 }, { 0, 22, 1 } } }, + { { { 7, 0, 3 }, { 0, 22, 0 } } }, + { { { 7, 0, 4 }, { 2, 19, 1 } } }, + { { { 8, 0, 4 }, { 2, 19, 0 } } }, + { { { 8, 0, 3 }, { 0, 23, 0 } } }, + { { { 8, 0, 2 }, { 1, 22, 1 } } }, + { { { 8, 0, 1 }, { 1, 22, 0 } } }, + { { { 8, 0, 0 }, { 0, 24, 0 } } }, + { { { 8, 0, 1 }, { 0, 25, 1 } } }, + { { { 8, 0, 2 }, { 0, 25, 0 } } }, + { { { 8, 0, 3 }, { 0, 25, 1 } } }, + { { { 8, 0, 4 }, { 0, 26, 1 } } }, + { { { 9, 0, 3 }, { 0, 26, 0 } } }, + { { { 9, 0, 2 }, { 2, 23, 1 } } }, + { { { 9, 0, 1 }, { 2, 23, 0 } } }, + { { { 9, 0, 0 }, { 0, 27, 0 } } }, + { { { 9, 0, 1 }, { 1, 26, 1 } } }, + { { { 9, 0, 2 }, { 1, 26, 0 } } }, + { { { 9, 0, 3 }, { 0, 28, 0 } } }, + { { { 9, 0, 4 }, { 0, 29, 1 } } }, + { { { 10, 0, 3 }, { 0, 29, 0 } } }, + { { { 10, 0, 2 }, { 0, 29, 1 } } }, + { { { 10, 0, 1 }, { 0, 30, 1 } } }, + { { { 10, 0, 0 }, { 0, 30, 0 } } }, + { { { 10, 0, 1 }, { 2, 27, 1 } } }, + { { { 10, 0, 2 }, { 2, 27, 0 } } }, + { { { 10, 0, 3 }, { 0, 31, 0 } } }, + { { { 10, 0, 4 }, { 1, 30, 1 } } }, + { { { 11, 0, 3 }, { 1, 30, 0 } } }, + { { { 11, 0, 2 }, { 4, 24, 0 } } }, + { { { 11, 0, 1 }, { 1, 31, 1 } } }, + { { { 11, 0, 0 }, { 1, 31, 0 } } }, + { { { 11, 0, 1 }, { 1, 31, 1 } } }, + { { { 11, 0, 2 }, { 2, 30, 1 } } }, + { { { 11, 0, 3 }, { 2, 30, 0 } } }, + { { { 11, 0, 4 }, { 2, 31, 1 } } }, + { { { 12, 0, 4 }, { 2, 31, 0 } } }, + { { { 12, 0, 3 }, { 4, 27, 0 } } }, + { { { 12, 0, 2 }, { 3, 30, 1 } } }, + { { { 12, 0, 1 }, { 3, 30, 0 } } }, + { { { 12, 0, 0 }, { 4, 28, 0 } } }, + { { { 12, 0, 1 }, { 3, 31, 1 } } }, + { { { 12, 0, 2 }, { 3, 31, 0 } } }, + { { { 12, 0, 3 }, { 3, 31, 1 } } }, + { { { 12, 0, 4 }, { 4, 30, 1 } } }, + { { { 13, 0, 3 }, { 4, 30, 0 } } }, + { { { 13, 0, 2 }, { 6, 27, 1 } } }, + { { { 13, 0, 1 }, { 6, 27, 0 } } }, + { { { 13, 0, 0 }, { 4, 31, 0 } } }, + { { { 13, 0, 1 }, { 5, 30, 1 } } }, + { { { 13, 0, 2 }, { 5, 30, 0 } } }, + { { { 13, 0, 3 }, { 8, 24, 0 } } }, + { { { 13, 0, 4 }, { 5, 31, 1 } } }, + { { { 14, 0, 3 }, { 5, 31, 0 } } }, + { { { 14, 0, 2 }, { 5, 31, 1 } } }, + { { { 14, 0, 1 }, { 6, 30, 1 } } }, + { { { 14, 0, 0 }, { 6, 30, 0 } } }, + { { { 14, 0, 1 }, { 6, 31, 1 } } }, + { { { 14, 0, 2 }, { 6, 31, 0 } } }, + { { { 14, 0, 3 }, { 8, 27, 0 } } }, + { { { 14, 0, 4 }, { 7, 30, 1 } } }, + { { { 15, 0, 3 }, { 7, 30, 0 } } }, + { { { 15, 0, 2 }, { 8, 28, 0 } } }, + { { { 15, 0, 1 }, { 7, 31, 1 } } }, + { { { 15, 0, 0 }, { 7, 31, 0 } } }, + { { { 15, 0, 1 }, { 7, 31, 1 } } }, + { { { 15, 0, 2 }, { 8, 30, 1 } } }, + { { { 15, 0, 3 }, { 8, 30, 0 } } }, + { { { 15, 0, 4 }, { 10, 27, 1 } } }, + { { { 16, 0, 4 }, { 10, 27, 0 } } }, + { { { 16, 0, 3 }, { 8, 31, 0 } } }, + { { { 16, 0, 2 }, { 9, 30, 1 } } }, + { { { 16, 0, 1 }, { 9, 30, 0 } } }, + { { { 16, 0, 0 }, { 12, 24, 0 } } }, + { { { 16, 0, 1 }, { 9, 31, 1 } } }, + { { { 16, 0, 2 }, { 9, 31, 0 } } }, + { { { 16, 0, 3 }, { 9, 31, 1 } } }, + { { { 16, 0, 4 }, { 10, 30, 1 } } }, + { { { 17, 0, 3 }, { 10, 30, 0 } } }, + { { { 17, 0, 2 }, { 10, 31, 1 } } }, + { { { 17, 0, 1 }, { 10, 31, 0 } } }, + { { { 17, 0, 0 }, { 12, 27, 0 } } }, + { { { 17, 0, 1 }, { 11, 30, 1 } } }, + { { { 17, 0, 2 }, { 11, 30, 0 } } }, + { { { 17, 0, 3 }, { 12, 28, 0 } } }, + { { { 17, 0, 4 }, { 11, 31, 1 } } }, + { { { 18, 0, 3 }, { 11, 31, 0 } } }, + { { { 18, 0, 2 }, { 11, 31, 1 } } }, + { { { 18, 0, 1 }, { 12, 30, 1 } } }, + { { { 18, 0, 0 }, { 12, 30, 0 } } }, + { { { 18, 0, 1 }, { 14, 27, 1 } } }, + { { { 18, 0, 2 }, { 14, 27, 0 } } }, + { { { 18, 0, 3 }, { 12, 31, 0 } } }, + { { { 18, 0, 4 }, { 13, 30, 1 } } }, + { { { 19, 0, 3 }, { 13, 30, 0 } } }, + { { { 19, 0, 2 }, { 16, 24, 0 } } }, + { { { 19, 0, 1 }, { 13, 31, 1 } } }, + { { { 19, 0, 0 }, { 13, 31, 0 } } }, + { { { 19, 0, 1 }, { 13, 31, 1 } } }, + { { { 19, 0, 2 }, { 14, 30, 1 } } }, + { { { 19, 0, 3 }, { 14, 30, 0 } } }, + { { { 19, 0, 4 }, { 14, 31, 1 } } }, + { { { 20, 0, 4 }, { 14, 31, 0 } } }, + { { { 20, 0, 3 }, { 16, 27, 0 } } }, + { { { 20, 0, 2 }, { 15, 30, 1 } } }, + { { { 20, 0, 1 }, { 15, 30, 0 } } }, + { { { 20, 0, 0 }, { 16, 28, 0 } } }, + { { { 20, 0, 1 }, { 15, 31, 1 } } }, + { { { 20, 0, 2 }, { 15, 31, 0 } } }, + { { { 20, 0, 3 }, { 15, 31, 1 } } }, + { { { 20, 0, 4 }, { 16, 30, 1 } } }, + { { { 21, 0, 3 }, { 16, 30, 0 } } }, + { { { 21, 0, 2 }, { 18, 27, 1 } } }, + { { { 21, 0, 1 }, { 18, 27, 0 } } }, + { { { 21, 0, 0 }, { 16, 31, 0 } } }, + { { { 21, 0, 1 }, { 17, 30, 1 } } }, + { { { 21, 0, 2 }, { 17, 30, 0 } } }, + { { { 21, 0, 3 }, { 20, 24, 0 } } }, + { { { 21, 0, 4 }, { 17, 31, 1 } } }, + { { { 22, 0, 3 }, { 17, 31, 0 } } }, + { { { 22, 0, 2 }, { 17, 31, 1 } } }, + { { { 22, 0, 1 }, { 18, 30, 1 } } }, + { { { 22, 0, 0 }, { 18, 30, 0 } } }, + { { { 22, 0, 1 }, { 18, 31, 1 } } }, + { { { 22, 0, 2 }, { 18, 31, 0 } } }, + { { { 22, 0, 3 }, { 20, 27, 0 } } }, + { { { 22, 0, 4 }, { 19, 30, 1 } } }, + { { { 23, 0, 3 }, { 19, 30, 0 } } }, + { { { 23, 0, 2 }, { 20, 28, 0 } } }, + { { { 23, 0, 1 }, { 19, 31, 1 } } }, + { { { 23, 0, 0 }, { 19, 31, 0 } } }, + { { { 23, 0, 1 }, { 19, 31, 1 } } }, + { { { 23, 0, 2 }, { 20, 30, 1 } } }, + { { { 23, 0, 3 }, { 20, 30, 0 } } }, + { { { 23, 0, 4 }, { 22, 27, 1 } } }, + { { { 24, 0, 4 }, { 22, 27, 0 } } }, + { { { 24, 0, 3 }, { 20, 31, 0 } } }, + { { { 24, 0, 2 }, { 21, 30, 1 } } }, + { { { 24, 0, 1 }, { 21, 30, 0 } } }, + { { { 24, 0, 0 }, { 24, 24, 0 } } }, + { { { 24, 0, 1 }, { 21, 31, 1 } } }, + { { { 24, 0, 2 }, { 21, 31, 0 } } }, + { { { 24, 0, 3 }, { 21, 31, 1 } } }, + { { { 24, 0, 4 }, { 22, 30, 1 } } }, + { { { 25, 0, 3 }, { 22, 30, 0 } } }, + { { { 25, 0, 2 }, { 22, 31, 1 } } }, + { { { 25, 0, 1 }, { 22, 31, 0 } } }, + { { { 25, 0, 0 }, { 24, 27, 0 } } }, + { { { 25, 0, 1 }, { 23, 30, 1 } } }, + { { { 25, 0, 2 }, { 23, 30, 0 } } }, + { { { 25, 0, 3 }, { 24, 28, 0 } } }, + { { { 25, 0, 4 }, { 23, 31, 1 } } }, + { { { 26, 0, 3 }, { 23, 31, 0 } } }, + { { { 26, 0, 2 }, { 23, 31, 1 } } }, + { { { 26, 0, 1 }, { 24, 30, 1 } } }, + { { { 26, 0, 0 }, { 24, 30, 0 } } }, + { { { 26, 0, 1 }, { 26, 27, 1 } } }, + { { { 26, 0, 2 }, { 26, 27, 0 } } }, + { { { 26, 0, 3 }, { 24, 31, 0 } } }, + { { { 26, 0, 4 }, { 25, 30, 1 } } }, + { { { 27, 0, 3 }, { 25, 30, 0 } } }, + { { { 27, 0, 2 }, { 28, 24, 0 } } }, + { { { 27, 0, 1 }, { 25, 31, 1 } } }, + { { { 27, 0, 0 }, { 25, 31, 0 } } }, + { { { 27, 0, 1 }, { 25, 31, 1 } } }, + { { { 27, 0, 2 }, { 26, 30, 1 } } }, + { { { 27, 0, 3 }, { 26, 30, 0 } } }, + { { { 27, 0, 4 }, { 26, 31, 1 } } }, + { { { 28, 0, 4 }, { 26, 31, 0 } } }, + { { { 28, 0, 3 }, { 28, 27, 0 } } }, + { { { 28, 0, 2 }, { 27, 30, 1 } } }, + { { { 28, 0, 1 }, { 27, 30, 0 } } }, + { { { 28, 0, 0 }, { 28, 28, 0 } } }, + { { { 28, 0, 1 }, { 27, 31, 1 } } }, + { { { 28, 0, 2 }, { 27, 31, 0 } } }, + { { { 28, 0, 3 }, { 27, 31, 1 } } }, + { { { 28, 0, 4 }, { 28, 30, 1 } } }, + { { { 29, 0, 3 }, { 28, 30, 0 } } }, + { { { 29, 0, 2 }, { 30, 27, 1 } } }, + { { { 29, 0, 1 }, { 30, 27, 0 } } }, + { { { 29, 0, 0 }, { 28, 31, 0 } } }, + { { { 29, 0, 1 }, { 29, 30, 1 } } }, + { { { 29, 0, 2 }, { 29, 30, 0 } } }, + { { { 29, 0, 3 }, { 29, 30, 1 } } }, + { { { 29, 0, 4 }, { 29, 31, 1 } } }, + { { { 30, 0, 3 }, { 29, 31, 0 } } }, + { { { 30, 0, 2 }, { 29, 31, 1 } } }, + { { { 30, 0, 1 }, { 30, 30, 1 } } }, + { { { 30, 0, 0 }, { 30, 30, 0 } } }, + { { { 30, 0, 1 }, { 30, 31, 1 } } }, + { { { 30, 0, 2 }, { 30, 31, 0 } } }, + { { { 30, 0, 3 }, { 30, 31, 1 } } }, + { { { 30, 0, 4 }, { 31, 30, 1 } } }, + { { { 31, 0, 3 }, { 31, 30, 0 } } }, + { { { 31, 0, 2 }, { 31, 30, 1 } } }, + { { { 31, 0, 1 }, { 31, 31, 1 } } }, + { { { 31, 0, 0 }, { 31, 31, 0 } } } +}; + +static SingleColourLookup const lookup_6_4[] = +{ + { { { 0, 0, 0 }, { 0, 0, 0 } } }, + { { { 0, 0, 1 }, { 0, 1, 0 } } }, + { { { 0, 0, 2 }, { 0, 2, 0 } } }, + { { { 1, 0, 1 }, { 0, 3, 1 } } }, + { { { 1, 0, 0 }, { 0, 3, 0 } } }, + { { { 1, 0, 1 }, { 0, 4, 0 } } }, + { { { 1, 0, 2 }, { 0, 5, 0 } } }, + { { { 2, 0, 1 }, { 0, 6, 1 } } }, + { { { 2, 0, 0 }, { 0, 6, 0 } } }, + { { { 2, 0, 1 }, { 0, 7, 0 } } }, + { { { 2, 0, 2 }, { 0, 8, 0 } } }, + { { { 3, 0, 1 }, { 0, 9, 1 } } }, + { { { 3, 0, 0 }, { 0, 9, 0 } } }, + { { { 3, 0, 1 }, { 0, 10, 0 } } }, + { { { 3, 0, 2 }, { 0, 11, 0 } } }, + { { { 4, 0, 1 }, { 0, 12, 1 } } }, + { { { 4, 0, 0 }, { 0, 12, 0 } } }, + { { { 4, 0, 1 }, { 0, 13, 0 } } }, + { { { 4, 0, 2 }, { 0, 14, 0 } } }, + { { { 5, 0, 1 }, { 0, 15, 1 } } }, + { { { 5, 0, 0 }, { 0, 15, 0 } } }, + { { { 5, 0, 1 }, { 0, 16, 0 } } }, + { { { 5, 0, 2 }, { 1, 15, 0 } } }, + { { { 6, 0, 1 }, { 0, 17, 0 } } }, + { { { 6, 0, 0 }, { 0, 18, 0 } } }, + { { { 6, 0, 1 }, { 0, 19, 0 } } }, + { { { 6, 0, 2 }, { 3, 14, 0 } } }, + { { { 7, 0, 1 }, { 0, 20, 0 } } }, + { { { 7, 0, 0 }, { 0, 21, 0 } } }, + { { { 7, 0, 1 }, { 0, 22, 0 } } }, + { { { 7, 0, 2 }, { 4, 15, 0 } } }, + { { { 8, 0, 1 }, { 0, 23, 0 } } }, + { { { 8, 0, 0 }, { 0, 24, 0 } } }, + { { { 8, 0, 1 }, { 0, 25, 0 } } }, + { { { 8, 0, 2 }, { 6, 14, 0 } } }, + { { { 9, 0, 1 }, { 0, 26, 0 } } }, + { { { 9, 0, 0 }, { 0, 27, 0 } } }, + { { { 9, 0, 1 }, { 0, 28, 0 } } }, + { { { 9, 0, 2 }, { 7, 15, 0 } } }, + { { { 10, 0, 1 }, { 0, 29, 0 } } }, + { { { 10, 0, 0 }, { 0, 30, 0 } } }, + { { { 10, 0, 1 }, { 0, 31, 0 } } }, + { { { 10, 0, 2 }, { 9, 14, 0 } } }, + { { { 11, 0, 1 }, { 0, 32, 0 } } }, + { { { 11, 0, 0 }, { 0, 33, 0 } } }, + { { { 11, 0, 1 }, { 2, 30, 0 } } }, + { { { 11, 0, 2 }, { 0, 34, 0 } } }, + { { { 12, 0, 1 }, { 0, 35, 0 } } }, + { { { 12, 0, 0 }, { 0, 36, 0 } } }, + { { { 12, 0, 1 }, { 3, 31, 0 } } }, + { { { 12, 0, 2 }, { 0, 37, 0 } } }, + { { { 13, 0, 1 }, { 0, 38, 0 } } }, + { { { 13, 0, 0 }, { 0, 39, 0 } } }, + { { { 13, 0, 1 }, { 5, 30, 0 } } }, + { { { 13, 0, 2 }, { 0, 40, 0 } } }, + { { { 14, 0, 1 }, { 0, 41, 0 } } }, + { { { 14, 0, 0 }, { 0, 42, 0 } } }, + { { { 14, 0, 1 }, { 6, 31, 0 } } }, + { { { 14, 0, 2 }, { 0, 43, 0 } } }, + { { { 15, 0, 1 }, { 0, 44, 0 } } }, + { { { 15, 0, 0 }, { 0, 45, 0 } } }, + { { { 15, 0, 1 }, { 8, 30, 0 } } }, + { { { 15, 0, 2 }, { 0, 46, 0 } } }, + { { { 16, 0, 2 }, { 0, 47, 0 } } }, + { { { 16, 0, 1 }, { 1, 46, 0 } } }, + { { { 16, 0, 0 }, { 0, 48, 0 } } }, + { { { 16, 0, 1 }, { 0, 49, 0 } } }, + { { { 16, 0, 2 }, { 0, 50, 0 } } }, + { { { 17, 0, 1 }, { 2, 47, 0 } } }, + { { { 17, 0, 0 }, { 0, 51, 0 } } }, + { { { 17, 0, 1 }, { 0, 52, 0 } } }, + { { { 17, 0, 2 }, { 0, 53, 0 } } }, + { { { 18, 0, 1 }, { 4, 46, 0 } } }, + { { { 18, 0, 0 }, { 0, 54, 0 } } }, + { { { 18, 0, 1 }, { 0, 55, 0 } } }, + { { { 18, 0, 2 }, { 0, 56, 0 } } }, + { { { 19, 0, 1 }, { 5, 47, 0 } } }, + { { { 19, 0, 0 }, { 0, 57, 0 } } }, + { { { 19, 0, 1 }, { 0, 58, 0 } } }, + { { { 19, 0, 2 }, { 0, 59, 0 } } }, + { { { 20, 0, 1 }, { 7, 46, 0 } } }, + { { { 20, 0, 0 }, { 0, 60, 0 } } }, + { { { 20, 0, 1 }, { 0, 61, 0 } } }, + { { { 20, 0, 2 }, { 0, 62, 0 } } }, + { { { 21, 0, 1 }, { 8, 47, 0 } } }, + { { { 21, 0, 0 }, { 0, 63, 0 } } }, + { { { 21, 0, 1 }, { 1, 62, 0 } } }, + { { { 21, 0, 2 }, { 1, 63, 0 } } }, + { { { 22, 0, 1 }, { 10, 46, 0 } } }, + { { { 22, 0, 0 }, { 2, 62, 0 } } }, + { { { 22, 0, 1 }, { 2, 63, 0 } } }, + { { { 22, 0, 2 }, { 3, 62, 0 } } }, + { { { 23, 0, 1 }, { 11, 47, 0 } } }, + { { { 23, 0, 0 }, { 3, 63, 0 } } }, + { { { 23, 0, 1 }, { 4, 62, 0 } } }, + { { { 23, 0, 2 }, { 4, 63, 0 } } }, + { { { 24, 0, 1 }, { 13, 46, 0 } } }, + { { { 24, 0, 0 }, { 5, 62, 0 } } }, + { { { 24, 0, 1 }, { 5, 63, 0 } } }, + { { { 24, 0, 2 }, { 6, 62, 0 } } }, + { { { 25, 0, 1 }, { 14, 47, 0 } } }, + { { { 25, 0, 0 }, { 6, 63, 0 } } }, + { { { 25, 0, 1 }, { 7, 62, 0 } } }, + { { { 25, 0, 2 }, { 7, 63, 0 } } }, + { { { 26, 0, 1 }, { 16, 45, 0 } } }, + { { { 26, 0, 0 }, { 8, 62, 0 } } }, + { { { 26, 0, 1 }, { 8, 63, 0 } } }, + { { { 26, 0, 2 }, { 9, 62, 0 } } }, + { { { 27, 0, 1 }, { 16, 48, 0 } } }, + { { { 27, 0, 0 }, { 9, 63, 0 } } }, + { { { 27, 0, 1 }, { 10, 62, 0 } } }, + { { { 27, 0, 2 }, { 10, 63, 0 } } }, + { { { 28, 0, 1 }, { 16, 51, 0 } } }, + { { { 28, 0, 0 }, { 11, 62, 0 } } }, + { { { 28, 0, 1 }, { 11, 63, 0 } } }, + { { { 28, 0, 2 }, { 12, 62, 0 } } }, + { { { 29, 0, 1 }, { 16, 54, 0 } } }, + { { { 29, 0, 0 }, { 12, 63, 0 } } }, + { { { 29, 0, 1 }, { 13, 62, 0 } } }, + { { { 29, 0, 2 }, { 13, 63, 0 } } }, + { { { 30, 0, 1 }, { 16, 57, 0 } } }, + { { { 30, 0, 0 }, { 14, 62, 0 } } }, + { { { 30, 0, 1 }, { 14, 63, 0 } } }, + { { { 30, 0, 2 }, { 15, 62, 0 } } }, + { { { 31, 0, 1 }, { 16, 60, 0 } } }, + { { { 31, 0, 0 }, { 15, 63, 0 } } }, + { { { 31, 0, 1 }, { 24, 46, 0 } } }, + { { { 31, 0, 2 }, { 16, 62, 0 } } }, + { { { 32, 0, 2 }, { 16, 63, 0 } } }, + { { { 32, 0, 1 }, { 17, 62, 0 } } }, + { { { 32, 0, 0 }, { 25, 47, 0 } } }, + { { { 32, 0, 1 }, { 17, 63, 0 } } }, + { { { 32, 0, 2 }, { 18, 62, 0 } } }, + { { { 33, 0, 1 }, { 18, 63, 0 } } }, + { { { 33, 0, 0 }, { 27, 46, 0 } } }, + { { { 33, 0, 1 }, { 19, 62, 0 } } }, + { { { 33, 0, 2 }, { 19, 63, 0 } } }, + { { { 34, 0, 1 }, { 20, 62, 0 } } }, + { { { 34, 0, 0 }, { 28, 47, 0 } } }, + { { { 34, 0, 1 }, { 20, 63, 0 } } }, + { { { 34, 0, 2 }, { 21, 62, 0 } } }, + { { { 35, 0, 1 }, { 21, 63, 0 } } }, + { { { 35, 0, 0 }, { 30, 46, 0 } } }, + { { { 35, 0, 1 }, { 22, 62, 0 } } }, + { { { 35, 0, 2 }, { 22, 63, 0 } } }, + { { { 36, 0, 1 }, { 23, 62, 0 } } }, + { { { 36, 0, 0 }, { 31, 47, 0 } } }, + { { { 36, 0, 1 }, { 23, 63, 0 } } }, + { { { 36, 0, 2 }, { 24, 62, 0 } } }, + { { { 37, 0, 1 }, { 24, 63, 0 } } }, + { { { 37, 0, 0 }, { 32, 47, 0 } } }, + { { { 37, 0, 1 }, { 25, 62, 0 } } }, + { { { 37, 0, 2 }, { 25, 63, 0 } } }, + { { { 38, 0, 1 }, { 26, 62, 0 } } }, + { { { 38, 0, 0 }, { 32, 50, 0 } } }, + { { { 38, 0, 1 }, { 26, 63, 0 } } }, + { { { 38, 0, 2 }, { 27, 62, 0 } } }, + { { { 39, 0, 1 }, { 27, 63, 0 } } }, + { { { 39, 0, 0 }, { 32, 53, 0 } } }, + { { { 39, 0, 1 }, { 28, 62, 0 } } }, + { { { 39, 0, 2 }, { 28, 63, 0 } } }, + { { { 40, 0, 1 }, { 29, 62, 0 } } }, + { { { 40, 0, 0 }, { 32, 56, 0 } } }, + { { { 40, 0, 1 }, { 29, 63, 0 } } }, + { { { 40, 0, 2 }, { 30, 62, 0 } } }, + { { { 41, 0, 1 }, { 30, 63, 0 } } }, + { { { 41, 0, 0 }, { 32, 59, 0 } } }, + { { { 41, 0, 1 }, { 31, 62, 0 } } }, + { { { 41, 0, 2 }, { 31, 63, 0 } } }, + { { { 42, 0, 1 }, { 32, 61, 0 } } }, + { { { 42, 0, 0 }, { 32, 62, 0 } } }, + { { { 42, 0, 1 }, { 32, 63, 0 } } }, + { { { 42, 0, 2 }, { 41, 46, 0 } } }, + { { { 43, 0, 1 }, { 33, 62, 0 } } }, + { { { 43, 0, 0 }, { 33, 63, 0 } } }, + { { { 43, 0, 1 }, { 34, 62, 0 } } }, + { { { 43, 0, 2 }, { 42, 47, 0 } } }, + { { { 44, 0, 1 }, { 34, 63, 0 } } }, + { { { 44, 0, 0 }, { 35, 62, 0 } } }, + { { { 44, 0, 1 }, { 35, 63, 0 } } }, + { { { 44, 0, 2 }, { 44, 46, 0 } } }, + { { { 45, 0, 1 }, { 36, 62, 0 } } }, + { { { 45, 0, 0 }, { 36, 63, 0 } } }, + { { { 45, 0, 1 }, { 37, 62, 0 } } }, + { { { 45, 0, 2 }, { 45, 47, 0 } } }, + { { { 46, 0, 1 }, { 37, 63, 0 } } }, + { { { 46, 0, 0 }, { 38, 62, 0 } } }, + { { { 46, 0, 1 }, { 38, 63, 0 } } }, + { { { 46, 0, 2 }, { 47, 46, 0 } } }, + { { { 47, 0, 1 }, { 39, 62, 0 } } }, + { { { 47, 0, 0 }, { 39, 63, 0 } } }, + { { { 47, 0, 1 }, { 40, 62, 0 } } }, + { { { 47, 0, 2 }, { 48, 46, 0 } } }, + { { { 48, 0, 2 }, { 40, 63, 0 } } }, + { { { 48, 0, 1 }, { 41, 62, 0 } } }, + { { { 48, 0, 0 }, { 41, 63, 0 } } }, + { { { 48, 0, 1 }, { 48, 49, 0 } } }, + { { { 48, 0, 2 }, { 42, 62, 0 } } }, + { { { 49, 0, 1 }, { 42, 63, 0 } } }, + { { { 49, 0, 0 }, { 43, 62, 0 } } }, + { { { 49, 0, 1 }, { 48, 52, 0 } } }, + { { { 49, 0, 2 }, { 43, 63, 0 } } }, + { { { 50, 0, 1 }, { 44, 62, 0 } } }, + { { { 50, 0, 0 }, { 44, 63, 0 } } }, + { { { 50, 0, 1 }, { 48, 55, 0 } } }, + { { { 50, 0, 2 }, { 45, 62, 0 } } }, + { { { 51, 0, 1 }, { 45, 63, 0 } } }, + { { { 51, 0, 0 }, { 46, 62, 0 } } }, + { { { 51, 0, 1 }, { 48, 58, 0 } } }, + { { { 51, 0, 2 }, { 46, 63, 0 } } }, + { { { 52, 0, 1 }, { 47, 62, 0 } } }, + { { { 52, 0, 0 }, { 47, 63, 0 } } }, + { { { 52, 0, 1 }, { 48, 61, 0 } } }, + { { { 52, 0, 2 }, { 48, 62, 0 } } }, + { { { 53, 0, 1 }, { 56, 47, 0 } } }, + { { { 53, 0, 0 }, { 48, 63, 0 } } }, + { { { 53, 0, 1 }, { 49, 62, 0 } } }, + { { { 53, 0, 2 }, { 49, 63, 0 } } }, + { { { 54, 0, 1 }, { 58, 46, 0 } } }, + { { { 54, 0, 0 }, { 50, 62, 0 } } }, + { { { 54, 0, 1 }, { 50, 63, 0 } } }, + { { { 54, 0, 2 }, { 51, 62, 0 } } }, + { { { 55, 0, 1 }, { 59, 47, 0 } } }, + { { { 55, 0, 0 }, { 51, 63, 0 } } }, + { { { 55, 0, 1 }, { 52, 62, 0 } } }, + { { { 55, 0, 2 }, { 52, 63, 0 } } }, + { { { 56, 0, 1 }, { 61, 46, 0 } } }, + { { { 56, 0, 0 }, { 53, 62, 0 } } }, + { { { 56, 0, 1 }, { 53, 63, 0 } } }, + { { { 56, 0, 2 }, { 54, 62, 0 } } }, + { { { 57, 0, 1 }, { 62, 47, 0 } } }, + { { { 57, 0, 0 }, { 54, 63, 0 } } }, + { { { 57, 0, 1 }, { 55, 62, 0 } } }, + { { { 57, 0, 2 }, { 55, 63, 0 } } }, + { { { 58, 0, 1 }, { 56, 62, 1 } } }, + { { { 58, 0, 0 }, { 56, 62, 0 } } }, + { { { 58, 0, 1 }, { 56, 63, 0 } } }, + { { { 58, 0, 2 }, { 57, 62, 0 } } }, + { { { 59, 0, 1 }, { 57, 63, 1 } } }, + { { { 59, 0, 0 }, { 57, 63, 0 } } }, + { { { 59, 0, 1 }, { 58, 62, 0 } } }, + { { { 59, 0, 2 }, { 58, 63, 0 } } }, + { { { 60, 0, 1 }, { 59, 62, 1 } } }, + { { { 60, 0, 0 }, { 59, 62, 0 } } }, + { { { 60, 0, 1 }, { 59, 63, 0 } } }, + { { { 60, 0, 2 }, { 60, 62, 0 } } }, + { { { 61, 0, 1 }, { 60, 63, 1 } } }, + { { { 61, 0, 0 }, { 60, 63, 0 } } }, + { { { 61, 0, 1 }, { 61, 62, 0 } } }, + { { { 61, 0, 2 }, { 61, 63, 0 } } }, + { { { 62, 0, 1 }, { 62, 62, 1 } } }, + { { { 62, 0, 0 }, { 62, 62, 0 } } }, + { { { 62, 0, 1 }, { 62, 63, 0 } } }, + { { { 62, 0, 2 }, { 63, 62, 0 } } }, + { { { 63, 0, 1 }, { 63, 63, 1 } } }, + { { { 63, 0, 0 }, { 63, 63, 0 } } } +}; diff --git a/utils/squish/squish.cpp b/utils/squish/squish.cpp new file mode 100644 index 0000000..a229191 --- /dev/null +++ b/utils/squish/squish.cpp @@ -0,0 +1,240 @@ +/* ----------------------------------------------------------------------------- + + Copyright (c) 2006 Simon Brown si@sjbrown.co.uk + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + -------------------------------------------------------------------------- */ + +#include "squish.h" +#include "colourset.h" +#include "maths.h" +#include "rangefit.h" +#include "clusterfit.h" +#include "colourblock.h" +#include "alpha.h" +#include "singlecolourfit.h" + +namespace squish { + +static int FixFlags( int flags ) +{ + // grab the flag bits + int method = flags & ( kDxt1 | kDxt3 | kDxt5 | kAti2 ); + int fit = flags & ( kColourIterativeClusterFit | kColourClusterFit | kColourRangeFit ); + int extra = flags & kWeightColourByAlpha; + + // set defaults + if( method != kDxt3 && method != kDxt5 && method != kAti2 ) + method = kDxt1; + if( fit != kColourRangeFit && fit != kColourIterativeClusterFit ) + fit = kColourClusterFit; + + // done + return method | fit | extra; +} + +void CompressMasked( u8 const* rgba, int mask, void* block, int flags, float* metric ) +{ + // fix any bad flags + flags = FixFlags( flags ); + + // get the block locations + void* colourBlock = block; + void* alphaBock = block; + + if( ( flags & ( kDxt3 | kDxt5 | kAti2 ) ) != 0 ) + colourBlock = reinterpret_cast< u8* >( block ) + 8; + + if( ( flags & kAti2 ) != 0 ) + { + CompressAlphaDxt5( rgba, mask, colourBlock ); + } + else + { + // create the minimal point set + ColourSet colours( rgba, mask, flags ); + + // check the compression type and compress colour + if( colours.GetCount() == 1 ) + { + // always do a single colour fit + SingleColourFit fit( &colours, flags ); + fit.Compress( colourBlock ); + } + else if( ( flags & kColourRangeFit ) != 0 || colours.GetCount() == 0 ) + { + // do a range fit + RangeFit fit( &colours, flags, metric ); + fit.Compress( colourBlock ); + } + else + { + // default to a cluster fit (could be iterative or not) + ClusterFit fit( &colours, flags, metric ); + fit.Compress( colourBlock ); + } + } + + // compress alpha separately if necessary + if( ( flags & kDxt3 ) != 0 ) + CompressAlphaDxt3( rgba, mask, alphaBock ); + else if( ( flags & kDxt5 ) != 0 ) + CompressAlphaDxt5( rgba, mask, alphaBock ); + else if( ( flags & kAti2 ) != 0 ) + CompressAlphaDxt5( rgba, mask, alphaBock, 1 ); // interpret green channel as alpha +} + +void Decompress( u8* rgba, void const* block, int flags ) +{ + // fix any bad flags + flags = FixFlags( flags ); + + // get the block locations + void const* colourBlock = block; + void const* alphaBock = block; + if( ( flags & ( kDxt3 | kDxt5 ) ) != 0 ) + colourBlock = reinterpret_cast< u8 const* >( block ) + 8; + + // decompress colour + DecompressColour( rgba, colourBlock, ( flags & kDxt1 ) != 0 ); + + // decompress alpha separately if necessary + if( ( flags & kDxt3 ) != 0 ) + DecompressAlphaDxt3( rgba, alphaBock ); + else if( ( flags & kDxt5 ) != 0 ) + DecompressAlphaDxt5( rgba, alphaBock ); +} + +int GetStorageRequirements( int width, int height, int flags ) +{ + // fix any bad flags + flags = FixFlags( flags ); + + // compute the storage requirements + int blockcount = ( ( width + 3 )/4 ) * ( ( height + 3 )/4 ); + int blocksize = ( ( flags & kDxt1 ) != 0 ) ? 8 : 16; + return blockcount*blocksize; +} + +void CompressImage( u8 const* rgba, int width, int height, void* blocks, int flags, float* metric ) +{ + // fix any bad flags + flags = FixFlags( flags ); + + // initialise the block output + u8* targetBlock = reinterpret_cast< u8* >( blocks ); + int bytesPerBlock = ( ( flags & kDxt1 ) != 0 ) ? 8 : 16; + + // loop over blocks + for( int y = 0; y < height; y += 4 ) + { + for( int x = 0; x < width; x += 4 ) + { + // build the 4x4 block of pixels + u8 sourceRgba[16*4]; + u8* targetPixel = sourceRgba; + int mask = 0; + for( int py = 0; py < 4; ++py ) + { + for( int px = 0; px < 4; ++px ) + { + // get the source pixel in the image + int sx = x + px; + int sy = y + py; + + // enable if we're in the image + if( sx < width && sy < height ) + { + // copy the rgba value + u8 const* sourcePixel = rgba + 4*( width*sy + sx ); + for( int i = 0; i < 4; ++i ) + *targetPixel++ = *sourcePixel++; + + // enable this pixel + mask |= ( 1 << ( 4*py + px ) ); + } + else + { + // skip this pixel as its outside the image + targetPixel += 4; + } + } + } + + // compress it into the output + CompressMasked( sourceRgba, mask, targetBlock, flags, metric ); + + // advance + targetBlock += bytesPerBlock; + } + } +} + +void DecompressImage( u8* rgba, int width, int height, void const* blocks, int flags ) +{ + // fix any bad flags + flags = FixFlags( flags ); + + // initialise the block input + u8 const* sourceBlock = reinterpret_cast< u8 const* >( blocks ); + int bytesPerBlock = ( ( flags & kDxt1 ) != 0 ) ? 8 : 16; + + // loop over blocks + for( int y = 0; y < height; y += 4 ) + { + for( int x = 0; x < width; x += 4 ) + { + // decompress the block + u8 targetRgba[4*16]; + Decompress( targetRgba, sourceBlock, flags ); + + // write the decompressed pixels to the correct image locations + u8 const* sourcePixel = targetRgba; + for( int py = 0; py < 4; ++py ) + { + for( int px = 0; px < 4; ++px ) + { + // get the target location + int sx = x + px; + int sy = y + py; + if( sx < width && sy < height ) + { + u8* targetPixel = rgba + 4*( width*sy + sx ); + + // copy the rgba value + for( int i = 0; i < 4; ++i ) + *targetPixel++ = *sourcePixel++; + } + else + { + // skip this pixel as its outside the image + sourcePixel += 4; + } + } + } + + // advance + sourceBlock += bytesPerBlock; + } + } +} + +} // namespace squish diff --git a/utils/squish/squish.dsp b/utils/squish/squish.dsp new file mode 100644 index 0000000..7aec4d1 --- /dev/null +++ b/utils/squish/squish.dsp @@ -0,0 +1,190 @@ +# Microsoft Developer Studio Project File - Name="squish" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=squish - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "squish.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "squish.mak" CFG="squish - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "squish - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "squish - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "squish - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\..\temp\squish\!release" +# PROP Intermediate_Dir "..\..\temp\squish\!release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /I "..\common" /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo /out:"..\common\squish.lib" + +!ELSEIF "$(CFG)" == "squish - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\..\temp\squish\!debug" +# PROP Intermediate_Dir "..\..\temp\squish\!debug" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /Gi /GX /ZI /Od /I "..\common" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /FAs /FR /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo /out:"..\common\squish.lib" + +!ENDIF + +# Begin Target + +# Name "squish - Win32 Release" +# Name "squish - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\alpha.cpp +# End Source File +# Begin Source File + +SOURCE=.\clusterfit.cpp +# End Source File +# Begin Source File + +SOURCE=.\colourblock.cpp +# End Source File +# Begin Source File + +SOURCE=.\colourfit.cpp +# End Source File +# Begin Source File + +SOURCE=.\colourset.cpp +# End Source File +# Begin Source File + +SOURCE=.\maths.cpp +# End Source File +# Begin Source File + +SOURCE=.\rangefit.cpp +# End Source File +# Begin Source File + +SOURCE=.\singlecolourfit.cpp +# End Source File +# Begin Source File + +SOURCE=.\squish.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\alpha.h +# End Source File +# Begin Source File + +SOURCE=.\clusterfit.h +# End Source File +# Begin Source File + +SOURCE=.\colourblock.h +# End Source File +# Begin Source File + +SOURCE=.\colourfit.h +# End Source File +# Begin Source File + +SOURCE=.\colourset.h +# End Source File +# Begin Source File + +SOURCE=.\config.h +# End Source File +# Begin Source File + +SOURCE=.\maths.h +# End Source File +# Begin Source File + +SOURCE=.\rangefit.h +# End Source File +# Begin Source File + +SOURCE=.\simd.h +# End Source File +# Begin Source File + +SOURCE=.\simd_float.h +# End Source File +# Begin Source File + +SOURCE=.\simd_sse.h +# End Source File +# Begin Source File + +SOURCE=.\simd_ve.h +# End Source File +# Begin Source File + +SOURCE=.\singlecolourfit.h +# End Source File +# Begin Source File + +SOURCE=.\singlecolourlookup.inl +# End Source File +# Begin Source File + +SOURCE=.\squish.h +# End Source File +# End Group +# End Target +# End Project diff --git a/utils/stalker2tga/stalker2tga.cpp b/utils/stalker2tga/stalker2tga.cpp new file mode 100644 index 0000000..92b69cc --- /dev/null +++ b/utils/stalker2tga/stalker2tga.cpp @@ -0,0 +1,366 @@ +/* +maketex.cpp - convert textures into DDS images with custom encode +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "conprint.h" +#include +#include +#include +#include +#include +#include "cmdlib.h" +#include "stringlib.h" +#include "filesystem.h" +#include "imagelib.h" + +int processed_files = 0; +int processed_errors = 0; + +int CheckSubeSides( const char *filename, char hint ) +{ + const char *ext = COM_FileExtension( filename ); + char starthint = 0; + char testpath[256]; + char cubepath[256]; + const imgtype_t *imgtype; + int numSides; + + if( hint >= IMG_SKYBOX_FT && hint <= IMG_SKYBOX_LF ) + starthint = IMG_SKYBOX_FT; + else if( hint >= IMG_CUBEMAP_PX && hint <= IMG_CUBEMAP_NZ ) + starthint = IMG_CUBEMAP_PX; + else return 0; // not a skybox or cubemap + + Q_strncpy( testpath, filename, sizeof( testpath )); + COM_StripExtension( testpath ); + + imgtype = Image_ImageTypeFromHint( starthint ); + + // NOTE: this safety operation because we just the cutoff end of string + if( imgtype ) Q_strncpy( testpath, testpath, Q_strlen( testpath ) - Q_strlen( imgtype->ext ) + 1 ); + + for( numSides = 0; numSides < 6; numSides++, imgtype++ ) + { + Q_snprintf( cubepath, sizeof( cubepath ), "%s%s.%s", testpath, imgtype->ext, ext ); + if( !COM_FileExists( cubepath )) break; + } + + return numSides; +} + +int ConvertCubeImageToEXT( const char *filename, char hint, const char *outext ) +{ + const char *ext = COM_FileExtension( filename ); + char cubepath[256]; + char outpath[256]; + const imgtype_t *imgtype; + rgbdata_t *images[6]; + bool skybox = false; + rgbdata_t *cube = NULL; + int i; + + memset( images, 0, sizeof( images )); + + if(( hint != IMG_SKYBOX_FT ) && ( hint != IMG_CUBEMAP_PX )) + { + // make sure what we have all the sides of cubemap + int sides = CheckSubeSides( filename, hint ); + return (sides == 6) ? -1 : 2; // don't process these files + } + else if( CheckSubeSides( filename, hint ) != 6 ) + return 2; + + if( hint == IMG_SKYBOX_FT ) + skybox = true; + + Q_strncpy( outpath, filename, sizeof( outpath )); + COM_StripExtension( outpath ); + + imgtype = Image_ImageTypeFromHint( hint ); + + // NOTE: this safety operation because we just the cutoff end of string + if( imgtype ) Q_strncpy( outpath, outpath, Q_strlen( outpath ) - Q_strlen( imgtype->ext ) + 1 ); + + if( COM_FileExists( va( "%s.%s", outpath, outext ))) + return -1; // already exist + + Msg( "%s found: %s\n", skybox ? "skybox" : "cubemap", outpath ); + + for( i = 0; i < 6; i++, imgtype++ ) + { + Q_snprintf( cubepath, sizeof( cubepath ), "%s%s.%s", outpath, imgtype->ext, ext ); + images[i] = COM_LoadImage( cubepath ); + if( !images[i] ) break; + + if( skybox ) MsgDev( D_INFO, "load skyside: %s[%i]\n", cubepath, i ); + else MsgDev( D_INFO, "load cubeside: %s[%i]\n", cubepath, i ); + } + + if(( i == 6 ) && ( cube = Image_CreateCubemap( images, skybox )) != NULL ) + { + int result; + + if( COM_SaveImage( va( "%s.%s", outpath, outext ), cube )) + { + MsgDev( D_INFO, "write %s.%s\n", outpath, outext ); + result = 1; + } + else + { + MsgDev( D_ERROR, "failed to save %s.%s\n", outpath, outext ); + result = 0; + } + + Mem_Free( cube ); + return result; + } + else + { + for( ; i >= 0; i-- ) + Mem_Free( images[i] ); + // not a cubemap just diffuse + return 2; + } +} + +int ConvertImageToEXT( const char *filename, const char *outext ) +{ + const char *ext = COM_FileExtension( filename ); + rgbdata_t *pic, *alpha = NULL, *gloss = NULL; + char outpath[256], lumpname[64]; + char maskpath[256], glosspath[256]; + + // store name for detect suffixes + COM_FileBase( filename, lumpname ); + + char hint = Image_HintFromSuf( lumpname ); + + if( hint == IMG_ALPHAMASK || hint == IMG_STALKER_GLOSS ) + return -1; // ignore to process + else if( hint >= IMG_SKYBOX_FT && hint <= IMG_CUBEMAP_NZ ) + { + int result; + + result = ConvertCubeImageToEXT( filename, hint, outext ); + if( result != 2 ) return result; + + // continue as normal image + hint = IMG_DIFFUSE; + } + + Q_strncpy( outpath, filename, sizeof( outpath )); + COM_StripExtension( outpath ); + + if( COM_FileExists( va( "%s.%s", outpath, outext ))) + return -1; // already exist + + pic = COM_LoadImage( filename ); + if( !pic ) + { + MsgDev( D_REPORT, "couldn't load (%s)\n", filename ); + return 0; + } + + MsgDev( D_INFO, "processing %s\n", filename ); + + // align by 4 + pic = Image_Resample( pic, ( pic->width + 15 ) & ~15, ( pic->height + 15 ) & ~15 ); + + if( hint == IMG_DIFFUSE ) + { + Q_snprintf( maskpath, sizeof( maskpath ), "%s_mask.%s", outpath, ext ); + + if( COM_FileExists( maskpath )) + alpha = COM_LoadImage( maskpath ); + + if( alpha ) + { + // get sizes from the colormap + alpha = Image_Resample( alpha, pic->width, pic->height ); + + MsgDev( D_INFO, "load mask (%s)\n", maskpath ); + pic = Image_MergeColorAlpha( pic, alpha ); + Mem_Free( alpha ); + +// COM_SaveImage( va( "%s_merged.tga", outpath ), pic ); + + if( pic->flags & IMAGE_HAS_8BIT_ALPHA ) + MsgDev( D_REPORT, "8-bit alpha detected\n" ); + if( pic->flags & IMAGE_HAS_1BIT_ALPHA ) + MsgDev( D_REPORT, "1-bit alpha detected\n" ); + } + else if( pic->flags & IMAGE_HAS_1BIT_ALPHA ) + { + MsgDev( D_REPORT, "1-bit alpha detected\n" ); + } + } + else if( hint == IMG_STALKER_BUMP ) + { + Q_snprintf( glosspath, sizeof( glosspath ), "%s#.%s", outpath, ext ); + + if( COM_FileExists( glosspath )) + gloss = COM_LoadImage( glosspath ); + + if( gloss ) + { + MsgDev( D_INFO, "load gloss (%s)\n", glosspath ); + Image_ConvertBumpStalker( pic, gloss ); + Q_strncpy( glosspath, outpath, sizeof( glosspath )); + size_t suffix = Q_strlen( glosspath ) - 4; + glosspath[suffix] = '\0'; + COM_SaveImage( va( "%sgloss.%s", glosspath, outext ), gloss ); + MsgDev( D_INFO, "write %sgloss.%s\n", glosspath, outext ); + processed_files++; + Mem_Free( gloss ); + } + + // replace _bump with _norm + size_t suffix = Q_strlen( outpath ) - 4; + outpath[suffix] = '\0'; + Q_strncat( outpath, "norm", sizeof( outpath )); + } + + int result; + + if( COM_SaveImage( va( "%s.%s", outpath, outext ), pic )) + { + MsgDev( D_INFO, "write %s.%s\n", outpath, outext ); + result = 1; + } + else + { + MsgDev( D_ERROR, "failed to save %s.%s\n", outpath, outext ); + result = 0; + } + Mem_Free( pic ); + + return result; +} + +int ProcessSearchResults( search_t *search, const char *outext ) +{ + if( !search ) return 0; + + for( int i = 0; i < search->numfilenames; i++ ) + { + int result = ConvertImageToEXT( search->filenames[i], outext ); + if( result > 0 ) processed_files++; + else if( !result ) processed_errors++; + } + + return 1; +} + +void ProcessFiles( const char *srcpath, const char *outext ) +{ + const char *ext = COM_FileExtension( srcpath ); + char folders[256], base[256], newpath[256]; + + // now convert all the files in the root folder + search_t *search = COM_Search( srcpath, true ); + ProcessSearchResults( search, outext ); + Mem_Free( search ); + + COM_FileBase( srcpath, base ); + Q_strncpy( folders, srcpath, sizeof( folders )); + COM_StripExtension( folders ); + search_t *foldsearch = COM_Search( folders, true ); + + for( int i = 0; foldsearch != NULL && i < foldsearch->numfilenames; i++ ) + { + if( COM_FolderExists( foldsearch->filenames[i] )) + { + Q_snprintf( newpath, sizeof( newpath ), "%s/%s.%s", foldsearch->filenames[i], base, ext ); + + // now convert all the files in the root folder + search_t *subsearch = COM_Search( newpath, true ); + ProcessSearchResults( subsearch, outext ); + Mem_Free( subsearch ); + } + } + Mem_Free( foldsearch ); +} + +int main( int argc, char **argv ) +{ + char srcpath[256]; + bool srcset = false; + double start, end; + char str[64]; + int i; + + atexit( Sys_CloseLog ); + Sys_InitLog( "convert.log" ); + COM_InitCmdlib( argv, argc ); + + Msg( " S.T.A.L.K.E.R texture extractor\n" ); + Msg( " XashXT Group 2018(^1c^7)\n\n\n" ); + + for( i = 1; i < argc; i++ ) + { + if( !Q_stricmp( argv[i], "-dev" )) + { + SetDeveloperLevel( atoi( argv[i+1] )); + i++; + } + else if( !srcset ) + { + Q_strncpy( srcpath, argv[i], sizeof( srcpath )); + srcset = true; + } + else + { + Msg( "maketex: unknown option %s\n", argv[i] ); + break; + } + } + + // starting default from the 'textures' folder + if( !srcset ) Q_strncpy( srcpath, "*.dds", sizeof( srcpath )); + + if( i != argc ) + { + Msg( "Usage: stalker2tga.exe \n" + "\nlist options:\n" + "^2-dev^7 - shows developer messages\n" + "\t\tPress any key to exit" ); + + system( "pause>nul" ); + return 1; + } + else + { + BuildGammaTable(); // init gamma conversion helper + + start = I_FloatTime(); + ProcessFiles( srcpath, "tga" ); + end = I_FloatTime(); + + MsgDev( D_INFO, "%3i files processed, %3i errors\n", processed_files, processed_errors ); + + Q_timestring((int)(end - start), str ); + MsgDev( D_INFO, "%s elapsed\n", str ); + + SetDeveloperLevel( D_REPORT ); + Mem_Check(); // report leaks + + if( !srcset ) + { + MsgDev( D_INFO, "press any key to exit\n" ); + system( "pause>nul" ); + } + } + + return 0; +} \ No newline at end of file diff --git a/utils/stalker2tga/stalker2tga.dsp b/utils/stalker2tga/stalker2tga.dsp new file mode 100644 index 0000000..8a0f345 --- /dev/null +++ b/utils/stalker2tga/stalker2tga.dsp @@ -0,0 +1,175 @@ +# Microsoft Developer Studio Project File - Name="stalker2tga" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=stalker2tga - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "stalker2tga.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "stalker2tga.mak" CFG="stalker2tga - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "stalker2tga - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "stalker2tga - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/SDKSrc/Tools/utils/stalker2tga", IVGBAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "stalker2tga - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir ".\Release" +# PROP BASE Intermediate_Dir ".\Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\..\temp\stalker2tga\!release" +# PROP Intermediate_Dir "..\..\temp\stalker2tga\!release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MT /W3 /GX /O2 /I "../../common" /I "../common" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "IGNORE_SEARCH_IN_WADS" /FD /c +# SUBTRACT CPP /Fr /YX +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:console /machine:I386 /opt:nowin98 +# ADD LINK32 msvcrt.lib ..\common\squish.lib /nologo /subsystem:console /pdb:none /machine:I386 /nodefaultlib:"libcmt" /nodefaultlib:"libc" /libpath:"./" /opt:nowin98 +# SUBTRACT LINK32 /nodefaultlib +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\stalker2tga\!release +InputPath=\Paranoia2\src_main\temp\stalker2tga\!release\stalker2tga.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\stalker2tga.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\stalker2tga.exe "D:\Paranoia2\tools\stalker2tga.exe" + +# End Custom Build + +!ELSEIF "$(CFG)" == "stalker2tga - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir ".\Debug" +# PROP BASE Intermediate_Dir ".\Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\..\temp\stalker2tga\!debug" +# PROP Intermediate_Dir "..\..\temp\stalker2tga\!debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MDd /W3 /Gm /Gi /GX /ZI /Od /I "../../common" /I "../common" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "IGNORE_SEARCH_IN_WADS" /FAs /FR /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:console /debug /machine:I386 +# ADD LINK32 msvcrtd.lib ..\common\squish.lib /nologo /subsystem:console /debug /machine:I386 /nodefaultlib:"libc.lib" /libpath:"./" +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\stalker2tga\!debug +InputPath=\Paranoia2\src_main\temp\stalker2tga\!debug\stalker2tga.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\stalker2tga.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\stalker2tga.exe "D:\Paranoia2\tools\stalker2tga.exe" + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "stalker2tga - Win32 Release" +# Name "stalker2tga - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=..\common\cmdlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\conprint.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\ddstex.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\filesystem.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\gamma.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\imagelib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\mathlib.cpp +# End Source File +# Begin Source File + +SOURCE=.\stalker2tga.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\stringlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\virtualfs.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\zone.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=..\common\ddstex.h +# End Source File +# Begin Source File + +SOURCE=..\common\filesystem.h +# End Source File +# Begin Source File + +SOURCE=.\imagelib.h +# End Source File +# Begin Source File + +SOURCE=..\common\stringlib.h +# End Source File +# End Group +# End Target +# End Project diff --git a/utils/studiomdl/builtin.h b/utils/studiomdl/builtin.h new file mode 100644 index 0000000..661d1f9 --- /dev/null +++ b/utils/studiomdl/builtin.h @@ -0,0 +1,251 @@ +const unsigned char default_bmp[] = +{ + 0x42, 0x4d, 0x36, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, + 0x04, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, + 0x00, 0x80, 0x80, 0x00, 0x00, 0xc0, 0xc0, 0xc0, 0x00, 0xc0, 0xdc, + 0xc0, 0x00, 0xf0, 0xca, 0xa6, 0x00, 0x00, 0x20, 0x40, 0x00, 0x00, + 0x20, 0x60, 0x00, 0x00, 0x20, 0x80, 0x00, 0x00, 0x20, 0xa0, 0x00, + 0x00, 0x20, 0xc0, 0x00, 0x00, 0x20, 0xe0, 0x00, 0x00, 0x40, 0x00, + 0x00, 0x00, 0x40, 0x20, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x40, + 0x60, 0x00, 0x00, 0x40, 0x80, 0x00, 0x00, 0x40, 0xa0, 0x00, 0x00, + 0x40, 0xc0, 0x00, 0x00, 0x40, 0xe0, 0x00, 0x00, 0x60, 0x00, 0x00, + 0x00, 0x60, 0x20, 0x00, 0x00, 0x60, 0x40, 0x00, 0x00, 0x60, 0x60, + 0x00, 0x00, 0x60, 0x80, 0x00, 0x00, 0x60, 0xa0, 0x00, 0x00, 0x60, + 0xc0, 0x00, 0x00, 0x60, 0xe0, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x80, 0x20, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0x80, 0x60, 0x00, + 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0xa0, 0x00, 0x00, 0x80, 0xc0, + 0x00, 0x00, 0x80, 0xe0, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0xa0, + 0x20, 0x00, 0x00, 0xa0, 0x40, 0x00, 0x00, 0xa0, 0x60, 0x00, 0x00, + 0xa0, 0x80, 0x00, 0x00, 0xa0, 0xa0, 0x00, 0x00, 0xa0, 0xc0, 0x00, + 0x00, 0xa0, 0xe0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x20, + 0x00, 0x00, 0xc0, 0x40, 0x00, 0x00, 0xc0, 0x60, 0x00, 0x00, 0xc0, + 0x80, 0x00, 0x00, 0xc0, 0xa0, 0x00, 0x00, 0xc0, 0xc0, 0x00, 0x00, + 0xc0, 0xe0, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0xe0, 0x20, 0x00, + 0x00, 0xe0, 0x40, 0x00, 0x00, 0xe0, 0x60, 0x00, 0x00, 0xe0, 0x80, + 0x00, 0x00, 0xe0, 0xa0, 0x00, 0x00, 0xe0, 0xc0, 0x00, 0x00, 0xe0, + 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x20, 0x00, 0x40, + 0x00, 0x40, 0x00, 0x40, 0x00, 0x60, 0x00, 0x40, 0x00, 0x80, 0x00, + 0x40, 0x00, 0xa0, 0x00, 0x40, 0x00, 0xc0, 0x00, 0x40, 0x00, 0xe0, + 0x00, 0x40, 0x20, 0x00, 0x00, 0x40, 0x20, 0x20, 0x00, 0x40, 0x20, + 0x40, 0x00, 0x40, 0x20, 0x60, 0x00, 0x40, 0x20, 0x80, 0x00, 0x40, + 0x20, 0xa0, 0x00, 0x40, 0x20, 0xc0, 0x00, 0x40, 0x20, 0xe0, 0x00, + 0x40, 0x40, 0x00, 0x00, 0x40, 0x40, 0x20, 0x00, 0x40, 0x40, 0x40, + 0x00, 0x40, 0x40, 0x60, 0x00, 0x40, 0x40, 0x80, 0x00, 0x40, 0x40, + 0xa0, 0x00, 0x40, 0x40, 0xc0, 0x00, 0x40, 0x40, 0xe0, 0x00, 0x40, + 0x60, 0x00, 0x00, 0x40, 0x60, 0x20, 0x00, 0x40, 0x60, 0x40, 0x00, + 0x40, 0x60, 0x60, 0x00, 0x40, 0x60, 0x80, 0x00, 0x40, 0x60, 0xa0, + 0x00, 0x40, 0x60, 0xc0, 0x00, 0x40, 0x60, 0xe0, 0x00, 0x40, 0x80, + 0x00, 0x00, 0x40, 0x80, 0x20, 0x00, 0x40, 0x80, 0x40, 0x00, 0x40, + 0x80, 0x60, 0x00, 0x40, 0x80, 0x80, 0x00, 0x40, 0x80, 0xa0, 0x00, + 0x40, 0x80, 0xc0, 0x00, 0x40, 0x80, 0xe0, 0x00, 0x40, 0xa0, 0x00, + 0x00, 0x40, 0xa0, 0x20, 0x00, 0x40, 0xa0, 0x40, 0x00, 0x40, 0xa0, + 0x60, 0x00, 0x40, 0xa0, 0x80, 0x00, 0x40, 0xa0, 0xa0, 0x00, 0x40, + 0xa0, 0xc0, 0x00, 0x40, 0xa0, 0xe0, 0x00, 0x40, 0xc0, 0x00, 0x00, + 0x40, 0xc0, 0x20, 0x00, 0x40, 0xc0, 0x40, 0x00, 0x40, 0xc0, 0x60, + 0x00, 0x40, 0xc0, 0x80, 0x00, 0x40, 0xc0, 0xa0, 0x00, 0x40, 0xc0, + 0xc0, 0x00, 0x40, 0xc0, 0xe0, 0x00, 0x40, 0xe0, 0x00, 0x00, 0x40, + 0xe0, 0x20, 0x00, 0x40, 0xe0, 0x40, 0x00, 0x40, 0xe0, 0x60, 0x00, + 0x40, 0xe0, 0x80, 0x00, 0x40, 0xe0, 0xa0, 0x00, 0x40, 0xe0, 0xc0, + 0x00, 0x40, 0xe0, 0xe0, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, + 0x20, 0x00, 0x80, 0x00, 0x40, 0x00, 0x80, 0x00, 0x60, 0x00, 0x80, + 0x00, 0x80, 0x00, 0x80, 0x00, 0xa0, 0x00, 0x80, 0x00, 0xc0, 0x00, + 0x80, 0x00, 0xe0, 0x00, 0x80, 0x20, 0x00, 0x00, 0x80, 0x20, 0x20, + 0x00, 0x80, 0x20, 0x40, 0x00, 0x80, 0x20, 0x60, 0x00, 0x80, 0x20, + 0x80, 0x00, 0x80, 0x20, 0xa0, 0x00, 0x80, 0x20, 0xc0, 0x00, 0x80, + 0x20, 0xe0, 0x00, 0x80, 0x40, 0x00, 0x00, 0x80, 0x40, 0x20, 0x00, + 0x80, 0x40, 0x40, 0x00, 0x80, 0x40, 0x60, 0x00, 0x80, 0x40, 0x80, + 0x00, 0x80, 0x40, 0xa0, 0x00, 0x80, 0x40, 0xc0, 0x00, 0x80, 0x40, + 0xe0, 0x00, 0x80, 0x60, 0x00, 0x00, 0x80, 0x60, 0x20, 0x00, 0x80, + 0x60, 0x40, 0x00, 0x80, 0x60, 0x60, 0x00, 0x80, 0x60, 0x80, 0x00, + 0x80, 0x60, 0xa0, 0x00, 0x80, 0x60, 0xc0, 0x00, 0x80, 0x60, 0xe0, + 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x20, 0x00, 0x80, 0x80, + 0x40, 0x00, 0x80, 0x80, 0x60, 0x00, 0x80, 0x80, 0x80, 0x00, 0x80, + 0x80, 0xa0, 0x00, 0x80, 0x80, 0xc0, 0x00, 0x80, 0x80, 0xe0, 0x00, + 0x80, 0xa0, 0x00, 0x00, 0x80, 0xa0, 0x20, 0x00, 0x80, 0xa0, 0x40, + 0x00, 0x80, 0xa0, 0x60, 0x00, 0x80, 0xa0, 0x80, 0x00, 0x80, 0xa0, + 0xa0, 0x00, 0x80, 0xa0, 0xc0, 0x00, 0x80, 0xa0, 0xe0, 0x00, 0x80, + 0xc0, 0x00, 0x00, 0x80, 0xc0, 0x20, 0x00, 0x80, 0xc0, 0x40, 0x00, + 0x80, 0xc0, 0x60, 0x00, 0x80, 0xc0, 0x80, 0x00, 0x80, 0xc0, 0xa0, + 0x00, 0x80, 0xc0, 0xc0, 0x00, 0x80, 0xc0, 0xe0, 0x00, 0x80, 0xe0, + 0x00, 0x00, 0x80, 0xe0, 0x20, 0x00, 0x80, 0xe0, 0x40, 0x00, 0x80, + 0xe0, 0x60, 0x00, 0x80, 0xe0, 0x80, 0x00, 0x80, 0xe0, 0xa0, 0x00, + 0x80, 0xe0, 0xc0, 0x00, 0x80, 0xe0, 0xe0, 0x00, 0xc0, 0x00, 0x00, + 0x00, 0xc0, 0x00, 0x20, 0x00, 0xc0, 0x00, 0x40, 0x00, 0xc0, 0x00, + 0x60, 0x00, 0xc0, 0x00, 0x80, 0x00, 0xc0, 0x00, 0xa0, 0x00, 0xc0, + 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xe0, 0x00, 0xc0, 0x20, 0x00, 0x00, + 0xc0, 0x20, 0x20, 0x00, 0xc0, 0x20, 0x40, 0x00, 0xc0, 0x20, 0x60, + 0x00, 0xc0, 0x20, 0x80, 0x00, 0xc0, 0x20, 0xa0, 0x00, 0xc0, 0x20, + 0xc0, 0x00, 0xc0, 0x20, 0xe0, 0x00, 0xc0, 0x40, 0x00, 0x00, 0xc0, + 0x40, 0x20, 0x00, 0xc0, 0x40, 0x40, 0x00, 0xc0, 0x40, 0x60, 0x00, + 0xc0, 0x40, 0x80, 0x00, 0xc0, 0x40, 0xa0, 0x00, 0xc0, 0x40, 0xc0, + 0x00, 0xc0, 0x40, 0xe0, 0x00, 0xc0, 0x60, 0x00, 0x00, 0xc0, 0x60, + 0x20, 0x00, 0xc0, 0x60, 0x40, 0x00, 0xc0, 0x60, 0x60, 0x00, 0xc0, + 0x60, 0x80, 0x00, 0xc0, 0x60, 0xa0, 0x00, 0xc0, 0x60, 0xc0, 0x00, + 0xc0, 0x60, 0xe0, 0x00, 0xc0, 0x80, 0x00, 0x00, 0xc0, 0x80, 0x20, + 0x00, 0xc0, 0x80, 0x40, 0x00, 0xc0, 0x80, 0x60, 0x00, 0xc0, 0x80, + 0x80, 0x00, 0xc0, 0x80, 0xa0, 0x00, 0xc0, 0x80, 0xc0, 0x00, 0xc0, + 0x80, 0xe0, 0x00, 0xc0, 0xa0, 0x00, 0x00, 0xc0, 0xa0, 0x20, 0x00, + 0xc0, 0xa0, 0x40, 0x00, 0xc0, 0xa0, 0x60, 0x00, 0xc0, 0xa0, 0x80, + 0x00, 0xc0, 0xa0, 0xa0, 0x00, 0xc0, 0xa0, 0xc0, 0x00, 0xc0, 0xa0, + 0xe0, 0x00, 0xc0, 0xc0, 0x00, 0x00, 0xc0, 0xc0, 0x20, 0x00, 0xc0, + 0xc0, 0x40, 0x00, 0xc0, 0xc0, 0x60, 0x00, 0xc0, 0xc0, 0x80, 0x00, + 0xc0, 0xc0, 0xa0, 0x00, 0xf0, 0xfb, 0xff, 0x00, 0xa4, 0xa0, 0xa0, + 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0xff, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, + 0xfd, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, + 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, + 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0xfd, + 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0xfd, 0xfd, + 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, + 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0xfd, + 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0xfd, 0xfd, + 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0xfd, 0xfd, 0xfd, + 0xfd, 0xfd, 0xfd, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, + 0xfd, 0xfd, 0xfd +}; + +const unsigned char white_bmp[] = +{ + 0x42, 0x4d, 0x36, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, + 0x04, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, + 0x00, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, + 0x00, 0x80, 0x80, 0x00, 0x00, 0xc0, 0xc0, 0xc0, 0x00, 0xc0, 0xdc, + 0xc0, 0x00, 0xf0, 0xca, 0xa6, 0x00, 0x00, 0x20, 0x40, 0x00, 0x00, + 0x20, 0x60, 0x00, 0x00, 0x20, 0x80, 0x00, 0x00, 0x20, 0xa0, 0x00, + 0x00, 0x20, 0xc0, 0x00, 0x00, 0x20, 0xe0, 0x00, 0x00, 0x40, 0x00, + 0x00, 0x00, 0x40, 0x20, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x40, + 0x60, 0x00, 0x00, 0x40, 0x80, 0x00, 0x00, 0x40, 0xa0, 0x00, 0x00, + 0x40, 0xc0, 0x00, 0x00, 0x40, 0xe0, 0x00, 0x00, 0x60, 0x00, 0x00, + 0x00, 0x60, 0x20, 0x00, 0x00, 0x60, 0x40, 0x00, 0x00, 0x60, 0x60, + 0x00, 0x00, 0x60, 0x80, 0x00, 0x00, 0x60, 0xa0, 0x00, 0x00, 0x60, + 0xc0, 0x00, 0x00, 0x60, 0xe0, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + 0x80, 0x20, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0x80, 0x60, 0x00, + 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0xa0, 0x00, 0x00, 0x80, 0xc0, + 0x00, 0x00, 0x80, 0xe0, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0xa0, + 0x20, 0x00, 0x00, 0xa0, 0x40, 0x00, 0x00, 0xa0, 0x60, 0x00, 0x00, + 0xa0, 0x80, 0x00, 0x00, 0xa0, 0xa0, 0x00, 0x00, 0xa0, 0xc0, 0x00, + 0x00, 0xa0, 0xe0, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xc0, 0x20, + 0x00, 0x00, 0xc0, 0x40, 0x00, 0x00, 0xc0, 0x60, 0x00, 0x00, 0xc0, + 0x80, 0x00, 0x00, 0xc0, 0xa0, 0x00, 0x00, 0xc0, 0xc0, 0x00, 0x00, + 0xc0, 0xe0, 0x00, 0x00, 0xe0, 0x00, 0x00, 0x00, 0xe0, 0x20, 0x00, + 0x00, 0xe0, 0x40, 0x00, 0x00, 0xe0, 0x60, 0x00, 0x00, 0xe0, 0x80, + 0x00, 0x00, 0xe0, 0xa0, 0x00, 0x00, 0xe0, 0xc0, 0x00, 0x00, 0xe0, + 0xe0, 0x00, 0x40, 0x00, 0x00, 0x00, 0x40, 0x00, 0x20, 0x00, 0x40, + 0x00, 0x40, 0x00, 0x40, 0x00, 0x60, 0x00, 0x40, 0x00, 0x80, 0x00, + 0x40, 0x00, 0xa0, 0x00, 0x40, 0x00, 0xc0, 0x00, 0x40, 0x00, 0xe0, + 0x00, 0x40, 0x20, 0x00, 0x00, 0x40, 0x20, 0x20, 0x00, 0x40, 0x20, + 0x40, 0x00, 0x40, 0x20, 0x60, 0x00, 0x40, 0x20, 0x80, 0x00, 0x40, + 0x20, 0xa0, 0x00, 0x40, 0x20, 0xc0, 0x00, 0x40, 0x20, 0xe0, 0x00, + 0x40, 0x40, 0x00, 0x00, 0x40, 0x40, 0x20, 0x00, 0x40, 0x40, 0x40, + 0x00, 0x40, 0x40, 0x60, 0x00, 0x40, 0x40, 0x80, 0x00, 0x40, 0x40, + 0xa0, 0x00, 0x40, 0x40, 0xc0, 0x00, 0x40, 0x40, 0xe0, 0x00, 0x40, + 0x60, 0x00, 0x00, 0x40, 0x60, 0x20, 0x00, 0x40, 0x60, 0x40, 0x00, + 0x40, 0x60, 0x60, 0x00, 0x40, 0x60, 0x80, 0x00, 0x40, 0x60, 0xa0, + 0x00, 0x40, 0x60, 0xc0, 0x00, 0x40, 0x60, 0xe0, 0x00, 0x40, 0x80, + 0x00, 0x00, 0x40, 0x80, 0x20, 0x00, 0x40, 0x80, 0x40, 0x00, 0x40, + 0x80, 0x60, 0x00, 0x40, 0x80, 0x80, 0x00, 0x40, 0x80, 0xa0, 0x00, + 0x40, 0x80, 0xc0, 0x00, 0x40, 0x80, 0xe0, 0x00, 0x40, 0xa0, 0x00, + 0x00, 0x40, 0xa0, 0x20, 0x00, 0x40, 0xa0, 0x40, 0x00, 0x40, 0xa0, + 0x60, 0x00, 0x40, 0xa0, 0x80, 0x00, 0x40, 0xa0, 0xa0, 0x00, 0x40, + 0xa0, 0xc0, 0x00, 0x40, 0xa0, 0xe0, 0x00, 0x40, 0xc0, 0x00, 0x00, + 0x40, 0xc0, 0x20, 0x00, 0x40, 0xc0, 0x40, 0x00, 0x40, 0xc0, 0x60, + 0x00, 0x40, 0xc0, 0x80, 0x00, 0x40, 0xc0, 0xa0, 0x00, 0x40, 0xc0, + 0xc0, 0x00, 0x40, 0xc0, 0xe0, 0x00, 0x40, 0xe0, 0x00, 0x00, 0x40, + 0xe0, 0x20, 0x00, 0x40, 0xe0, 0x40, 0x00, 0x40, 0xe0, 0x60, 0x00, + 0x40, 0xe0, 0x80, 0x00, 0x40, 0xe0, 0xa0, 0x00, 0x40, 0xe0, 0xc0, + 0x00, 0x40, 0xe0, 0xe0, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, + 0x20, 0x00, 0x80, 0x00, 0x40, 0x00, 0x80, 0x00, 0x60, 0x00, 0x80, + 0x00, 0x80, 0x00, 0x80, 0x00, 0xa0, 0x00, 0x80, 0x00, 0xc0, 0x00, + 0x80, 0x00, 0xe0, 0x00, 0x80, 0x20, 0x00, 0x00, 0x80, 0x20, 0x20, + 0x00, 0x80, 0x20, 0x40, 0x00, 0x80, 0x20, 0x60, 0x00, 0x80, 0x20, + 0x80, 0x00, 0x80, 0x20, 0xa0, 0x00, 0x80, 0x20, 0xc0, 0x00, 0x80, + 0x20, 0xe0, 0x00, 0x80, 0x40, 0x00, 0x00, 0x80, 0x40, 0x20, 0x00, + 0x80, 0x40, 0x40, 0x00, 0x80, 0x40, 0x60, 0x00, 0x80, 0x40, 0x80, + 0x00, 0x80, 0x40, 0xa0, 0x00, 0x80, 0x40, 0xc0, 0x00, 0x80, 0x40, + 0xe0, 0x00, 0x80, 0x60, 0x00, 0x00, 0x80, 0x60, 0x20, 0x00, 0x80, + 0x60, 0x40, 0x00, 0x80, 0x60, 0x60, 0x00, 0x80, 0x60, 0x80, 0x00, + 0x80, 0x60, 0xa0, 0x00, 0x80, 0x60, 0xc0, 0x00, 0x80, 0x60, 0xe0, + 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x20, 0x00, 0x80, 0x80, + 0x40, 0x00, 0x80, 0x80, 0x60, 0x00, 0x80, 0x80, 0x80, 0x00, 0x80, + 0x80, 0xa0, 0x00, 0x80, 0x80, 0xc0, 0x00, 0x80, 0x80, 0xe0, 0x00, + 0x80, 0xa0, 0x00, 0x00, 0x80, 0xa0, 0x20, 0x00, 0x80, 0xa0, 0x40, + 0x00, 0x80, 0xa0, 0x60, 0x00, 0x80, 0xa0, 0x80, 0x00, 0x80, 0xa0, + 0xa0, 0x00, 0x80, 0xa0, 0xc0, 0x00, 0x80, 0xa0, 0xe0, 0x00, 0x80, + 0xc0, 0x00, 0x00, 0x80, 0xc0, 0x20, 0x00, 0x80, 0xc0, 0x40, 0x00, + 0x80, 0xc0, 0x60, 0x00, 0x80, 0xc0, 0x80, 0x00, 0x80, 0xc0, 0xa0, + 0x00, 0x80, 0xc0, 0xc0, 0x00, 0x80, 0xc0, 0xe0, 0x00, 0x80, 0xe0, + 0x00, 0x00, 0x80, 0xe0, 0x20, 0x00, 0x80, 0xe0, 0x40, 0x00, 0x80, + 0xe0, 0x60, 0x00, 0x80, 0xe0, 0x80, 0x00, 0x80, 0xe0, 0xa0, 0x00, + 0x80, 0xe0, 0xc0, 0x00, 0x80, 0xe0, 0xe0, 0x00, 0xc0, 0x00, 0x00, + 0x00, 0xc0, 0x00, 0x20, 0x00, 0xc0, 0x00, 0x40, 0x00, 0xc0, 0x00, + 0x60, 0x00, 0xc0, 0x00, 0x80, 0x00, 0xc0, 0x00, 0xa0, 0x00, 0xc0, + 0x00, 0xc0, 0x00, 0xc0, 0x00, 0xe0, 0x00, 0xc0, 0x20, 0x00, 0x00, + 0xc0, 0x20, 0x20, 0x00, 0xc0, 0x20, 0x40, 0x00, 0xc0, 0x20, 0x60, + 0x00, 0xc0, 0x20, 0x80, 0x00, 0xc0, 0x20, 0xa0, 0x00, 0xc0, 0x20, + 0xc0, 0x00, 0xc0, 0x20, 0xe0, 0x00, 0xc0, 0x40, 0x00, 0x00, 0xc0, + 0x40, 0x20, 0x00, 0xc0, 0x40, 0x40, 0x00, 0xc0, 0x40, 0x60, 0x00, + 0xc0, 0x40, 0x80, 0x00, 0xc0, 0x40, 0xa0, 0x00, 0xc0, 0x40, 0xc0, + 0x00, 0xc0, 0x40, 0xe0, 0x00, 0xc0, 0x60, 0x00, 0x00, 0xc0, 0x60, + 0x20, 0x00, 0xc0, 0x60, 0x40, 0x00, 0xc0, 0x60, 0x60, 0x00, 0xc0, + 0x60, 0x80, 0x00, 0xc0, 0x60, 0xa0, 0x00, 0xc0, 0x60, 0xc0, 0x00, + 0xc0, 0x60, 0xe0, 0x00, 0xc0, 0x80, 0x00, 0x00, 0xc0, 0x80, 0x20, + 0x00, 0xc0, 0x80, 0x40, 0x00, 0xc0, 0x80, 0x60, 0x00, 0xc0, 0x80, + 0x80, 0x00, 0xc0, 0x80, 0xa0, 0x00, 0xc0, 0x80, 0xc0, 0x00, 0xc0, + 0x80, 0xe0, 0x00, 0xc0, 0xa0, 0x00, 0x00, 0xc0, 0xa0, 0x20, 0x00, + 0xc0, 0xa0, 0x40, 0x00, 0xc0, 0xa0, 0x60, 0x00, 0xc0, 0xa0, 0x80, + 0x00, 0xc0, 0xa0, 0xa0, 0x00, 0xc0, 0xa0, 0xc0, 0x00, 0xc0, 0xa0, + 0xe0, 0x00, 0xc0, 0xc0, 0x00, 0x00, 0xc0, 0xc0, 0x20, 0x00, 0xc0, + 0xc0, 0x40, 0x00, 0xc0, 0xc0, 0x60, 0x00, 0xc0, 0xc0, 0x80, 0x00, + 0xc0, 0xc0, 0xa0, 0x00, 0xf0, 0xfb, 0xff, 0x00, 0xa4, 0xa0, 0xa0, + 0x00, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, + 0x00, 0xff, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff +}; \ No newline at end of file diff --git a/utils/studiomdl/imagelib.cpp b/utils/studiomdl/imagelib.cpp new file mode 100644 index 0000000..60442e4 --- /dev/null +++ b/utils/studiomdl/imagelib.cpp @@ -0,0 +1,954 @@ +/* +imagelib.cpp - simple loader\serializer for TGA & BMP +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include +#include "cmdlib.h" +#include "mathlib.h" +#include "stringlib.h" +#include "scriplib.h" +#include "filesystem.h" +#include "imagelib.h" + +/* +================= +Image_ValidSize + +check image for valid dimensions +================= +*/ +bool Image_ValidSize( const char *name, int width, int height ) +{ + if( width > IMAGE_MAXWIDTH || height > IMAGE_MAXHEIGHT || width < IMAGE_MINWIDTH || height < IMAGE_MINHEIGHT ) + { + MsgDev( D_ERROR, "Image: %s has invalid sizes %i x %i\n", name, width, height ); + return false; + } + + return true; +} + +/* +================= +Image_Alloc + +allocate image struct and partially fill it +================= +*/ +rgbdata_t *Image_Alloc( int width, int height, bool paletted ) +{ + size_t pic_size = sizeof( rgbdata_t ) + (width * height * (paletted ? 1 : 4)) + (paletted ? 1024 : 0); + rgbdata_t *pic = (rgbdata_t *)Mem_Alloc( pic_size ); + + if( paletted ) + { + pic->buffer = ((byte *)pic) + sizeof( rgbdata_t ); + pic->palette = ((byte *)pic) + sizeof( rgbdata_t ) + width * height; + pic->flags |= IMAGE_QUANTIZED; + } + else + { + pic->buffer = ((byte *)pic) + sizeof( rgbdata_t ); + pic->palette = NULL; // not present + } + + pic->size = (width * height * (paletted ? 1 : 4)); + pic->width = width; + pic->height = height; + + return pic; +} + +/* +================= +Image_Copy + +make an copy of image +================= +*/ +rgbdata_t *Image_Copy( rgbdata_t *src ) +{ + size_t pic_size = sizeof( rgbdata_t ) + src->size + (FBitSet( src->flags, IMAGE_QUANTIZED ) ? 1024 : 0 ); + rgbdata_t *dst = (rgbdata_t *)Mem_Alloc( pic_size ); + dst->buffer = ((byte *)dst) + sizeof( rgbdata_t ); + + if( FBitSet( src->flags, IMAGE_QUANTIZED )) + { + dst->palette = dst->buffer + src->size; + memcpy( dst->palette, src->palette, 1024 ); + } + + memcpy( dst->buffer, src->buffer, src->size ); + + dst->size = src->size; + dst->width = src->width; + dst->height = src->height; + dst->flags = src->flags; + + return dst; +} + +/* +============================================================================= + + IMAGE LOADING + +============================================================================= +*/ +/* +============= +Image_LoadTGA + +expand any image to RGBA32 but keep 8-bit unchanged +============= +*/ +rgbdata_t *Image_LoadTGA( const char *name, const byte *buffer, size_t filesize ) +{ + int i, columns, rows, row_inc, row, col; + byte *buf_p, *pixbuf, *targa_rgba; + byte palette[256][4], red = 0, green = 0, blue = 0, alpha = 0; + int readpixelcount, pixelcount, palIndex; + tga_t targa_header; + bool compressed; + bool paletted; + rgbdata_t *pic; + + if( filesize < sizeof( tga_t )) + return NULL; + + buf_p = (byte *)buffer; + targa_header.id_length = *buf_p++; + targa_header.colormap_type = *buf_p++; + targa_header.image_type = *buf_p++; + + targa_header.colormap_index = *(short *)buf_p; buf_p += 2; + targa_header.colormap_length = *(short *)buf_p; buf_p += 2; + targa_header.colormap_size = *buf_p; buf_p += 1; + targa_header.x_origin = *(short *)buf_p; buf_p += 2; + targa_header.y_origin = *(short *)buf_p; buf_p += 2; + targa_header.width = *(short *)buf_p; buf_p += 2; + targa_header.height = *(short *)buf_p; buf_p += 2; + targa_header.pixel_size = *buf_p++; + targa_header.attributes = *buf_p++; + if( targa_header.id_length != 0 ) + buf_p += targa_header.id_length; // skip TARGA image comment + + // check for tga file + if( !Image_ValidSize( name, targa_header.width, targa_header.height )) + return NULL; + + if( targa_header.image_type == 1 || targa_header.image_type == 9 ) + { + // uncompressed colormapped image + if( targa_header.pixel_size != 8 ) + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) Only 8 bit images supported for type 1 and 9\n", name ); + return NULL; + } + + if( targa_header.colormap_length != 256 ) + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) Only 8 bit colormaps are supported for type 1 and 9\n", name ); + return NULL; + } + + if( targa_header.colormap_index ) + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) colormap_index is not supported for type 1 and 9\n", name ); + return NULL; + } + + if( targa_header.colormap_size == 24 ) + { + for( i = 0; i < targa_header.colormap_length; i++ ) + { + palette[i][2] = *buf_p++; + palette[i][1] = *buf_p++; + palette[i][0] = *buf_p++; + palette[i][3] = 255; + } + } + else if( targa_header.colormap_size == 32 ) + { + for( i = 0; i < targa_header.colormap_length; i++ ) + { + palette[i][2] = *buf_p++; + palette[i][1] = *buf_p++; + palette[i][0] = *buf_p++; + palette[i][3] = *buf_p++; + } + } + else + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) only 24 and 32 bit colormaps are supported for type 1 and 9\n", name ); + return NULL; + } + } + else if( targa_header.image_type == 2 || targa_header.image_type == 10 ) + { + // uncompressed or RLE compressed RGB + if( targa_header.pixel_size != 32 && targa_header.pixel_size != 24 ) + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) Only 32 or 24 bit images supported for type 2 and 10\n", name ); + return NULL; + } + } + else if( targa_header.image_type == 3 || targa_header.image_type == 11 ) + { + // uncompressed greyscale + if( targa_header.pixel_size != 8 ) + { + MsgDev( D_WARN, "Image_LoadTGA: (%s) Only 8 bit images supported for type 3 and 11\n", name ); + return NULL; + } + } + + paletted = ( targa_header.image_type == 1 || targa_header.image_type == 9 ); + + pic = Image_Alloc( targa_header.width, targa_header.height, paletted ); + if( paletted ) memcpy( pic->palette, palette, sizeof( palette )); + + columns = targa_header.width; + rows = targa_header.height; + targa_rgba = pic->buffer; + + // if bit 5 of attributes isn't set, the image has been stored from bottom to top + if( targa_header.attributes & 0x20 ) + { + pixbuf = targa_rgba; + row_inc = 0; + } + else + { + if( FBitSet( pic->flags, IMAGE_QUANTIZED )) + { + pixbuf = targa_rgba + ( rows - 1 ) * columns; + row_inc = -columns * 2; + } + else + { + pixbuf = targa_rgba + ( rows - 1 ) * columns * 4; + row_inc = -columns * 4 * 2; + } + } + + compressed = ( targa_header.image_type == 9 || targa_header.image_type == 10 || targa_header.image_type == 11 ); + + for( row = col = 0; row < rows; ) + { + pixelcount = 0x10000; + readpixelcount = 0x10000; + + if( compressed ) + { + pixelcount = *buf_p++; + if( pixelcount & 0x80 ) // run-length packet + readpixelcount = 1; + pixelcount = 1 + ( pixelcount & 0x7f ); + } + + while( pixelcount-- && ( row < rows ) ) + { + if( readpixelcount-- > 0 ) + { + switch( targa_header.image_type ) + { + case 1: + case 9: + // colormapped image + palIndex = *buf_p++; + red = palette[palIndex][0]; + green = palette[palIndex][1]; + blue = palette[palIndex][2]; + alpha = palette[palIndex][3]; + break; + case 2: + case 10: + // 24 or 32 bit image + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alpha = 255; + if( targa_header.pixel_size == 32 ) + alpha = *buf_p++; + break; + case 3: + case 11: + // greyscale image + blue = green = red = *buf_p++; + alpha = 255; + break; + } + } + + if( red != green || green != blue ) + pic->flags |= IMAGE_HAS_COLOR; + + if( alpha != 255 ) + { + if( alpha != 0 ) + { + SetBits( pic->flags, IMAGE_HAS_8BIT_ALPHA ); + ClearBits( pic->flags, IMAGE_HAS_1BIT_ALPHA ); + } + else if( !FBitSet( pic->flags, IMAGE_HAS_8BIT_ALPHA )) + SetBits( pic->flags, IMAGE_HAS_1BIT_ALPHA ); + } + + if( FBitSet( pic->flags, IMAGE_QUANTIZED )) + { + *pixbuf++ = palIndex; + } + else + { + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha; + } + + if( ++col == columns ) + { + // run spans across rows + row++; + col = 0; + pixbuf += row_inc; + } + } + } + + return pic; +} + +/* +============= +Image_LoadBMP + +expand any image to RGBA32 but keep 8-bit unchanged +============= +*/ +rgbdata_t *Image_LoadBMP( const char *name, const byte *buffer, size_t filesize ) +{ + byte *buf_p, *pixbuf; + byte palette[256][4]; + int i, columns, column, rows, row, bpp = 1; + int cbPalBytes = 0, padSize = 0, bps = 0; + rgbdata_t *pic; + bmp_t bhdr; + + if( filesize < sizeof( bhdr )) + return NULL; + + buf_p = (byte *)buffer; + bhdr.id[0] = *buf_p++; + bhdr.id[1] = *buf_p++; // move pointer + bhdr.fileSize = *(long *)buf_p; buf_p += 4; + bhdr.reserved0 = *(long *)buf_p; buf_p += 4; + bhdr.bitmapDataOffset = *(long *)buf_p; buf_p += 4; + bhdr.bitmapHeaderSize = *(long *)buf_p; buf_p += 4; + bhdr.width = *(long *)buf_p; buf_p += 4; + bhdr.height = *(long *)buf_p; buf_p += 4; + bhdr.planes = *(short *)buf_p; buf_p += 2; + bhdr.bitsPerPixel = *(short *)buf_p; buf_p += 2; + bhdr.compression = *(long *)buf_p; buf_p += 4; + bhdr.bitmapDataSize = *(long *)buf_p; buf_p += 4; + bhdr.hRes = *(long *)buf_p; buf_p += 4; + bhdr.vRes = *(long *)buf_p; buf_p += 4; + bhdr.colors = *(long *)buf_p; buf_p += 4; + bhdr.importantColors = *(long *)buf_p; buf_p += 4; + + // bogus file header check + if( bhdr.reserved0 != 0 ) return NULL; + if( bhdr.planes != 1 ) return NULL; + + if( memcmp( bhdr.id, "BM", 2 )) + { + MsgDev( D_ERROR, "Image_LoadBMP: only Windows-style BMP files supported (%s)\n", name ); + return NULL; + } + + if( bhdr.bitmapHeaderSize != 0x28 ) + { + MsgDev( D_ERROR, "Image_LoadBMP: invalid header size %i\n", bhdr.bitmapHeaderSize ); + return NULL; + } + + // bogus info header check + if( bhdr.fileSize != filesize ) + { + // Sweet Half-Life issues. splash.bmp have bogus filesize + MsgDev( D_WARN, "Image_LoadBMP: %s have incorrect file size %i should be %i\n", name, filesize, bhdr.fileSize ); + } + + // bogus compression? Only non-compressed supported. + if( bhdr.compression != BI_RGB ) + { + MsgDev( D_ERROR, "Image_LoadBMP: only uncompressed BMP files supported (%s)\n", name ); + return false; + } + + columns = bhdr.width; + rows = abs( bhdr.height ); + + if( !Image_ValidSize( name, columns, rows )) + return false; + + pic = Image_Alloc( columns, rows, ( bhdr.bitsPerPixel == 4 || bhdr.bitsPerPixel == 8 )); + + if( bhdr.bitsPerPixel <= 8 ) + { + // figure out how many entries are actually in the table + if( bhdr.colors == 0 ) + { + bhdr.colors = 256; + cbPalBytes = (1 << bhdr.bitsPerPixel) * sizeof( RGBQUAD ); + } + else cbPalBytes = bhdr.colors * sizeof( RGBQUAD ); + } + + memcpy( palette, buf_p, cbPalBytes ); + + if( bhdr.bitsPerPixel == 4 || bhdr.bitsPerPixel == 8 ) + { + pixbuf = pic->palette; + + // bmp have a reversed palette colors + for( i = 0; i < bhdr.colors; i++ ) + { + *pixbuf++ = palette[i][2]; + *pixbuf++ = palette[i][1]; + *pixbuf++ = palette[i][0]; + *pixbuf++ = palette[i][3]; + + if( palette[i][0] != palette[i][1] || palette[i][1] != palette[i][2] ) + pic->flags |= IMAGE_HAS_COLOR; + } + } + else bpp = 4; + + buf_p += cbPalBytes; + bps = bhdr.width * (bhdr.bitsPerPixel >> 3); + + switch( bhdr.bitsPerPixel ) + { + case 1: + padSize = (( 32 - ( bhdr.width % 32 )) / 8 ) % 4; + break; + case 4: + padSize = (( 8 - ( bhdr.width % 8 )) / 2 ) % 4; + break; + case 16: + padSize = ( 4 - ( bhdr.width * 2 % 4 )) % 4; + break; + case 8: + case 24: + padSize = ( 4 - ( bps % 4 )) % 4; + break; + } + + for( row = rows - 1; row >= 0; row-- ) + { + pixbuf = pic->buffer + (row * columns * bpp); + + for( column = 0; column < columns; column++ ) + { + byte red, green, blue, alpha; + int c, k, palIndex; + word shortPixel; + + switch( bhdr.bitsPerPixel ) + { + case 1: + alpha = *buf_p++; + column--; // ingnore main iterations + for( c = 0, k = 128; c < 8; c++, k >>= 1 ) + { + red = green = blue = (!!(alpha & k) == 1 ? 0xFF : 0x00); + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = 0x00; + if( ++column == columns ) + break; + } + break; + case 4: + alpha = *buf_p++; + palIndex = alpha >> 4; + red = palette[palIndex][2]; + green = palette[palIndex][1]; + blue = palette[palIndex][0]; + alpha = palette[palIndex][3]; + + if( FBitSet( pic->flags, IMAGE_QUANTIZED )) + { + *pixbuf++ = palIndex; + } + else + { + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha; + } + + if( ++column == columns ) + break; + + palIndex = alpha & 0x0F; + red = palette[palIndex][2]; + green = palette[palIndex][1]; + blue = palette[palIndex][0]; + alpha = palette[palIndex][3]; + + if( FBitSet( pic->flags, IMAGE_QUANTIZED )) + { + *pixbuf++ = palIndex; + } + else + { + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha; + } + break; + case 8: + palIndex = *buf_p++; + red = palette[palIndex][2]; + green = palette[palIndex][1]; + blue = palette[palIndex][0]; + alpha = palette[palIndex][3]; + + if( FBitSet( pic->flags, IMAGE_QUANTIZED )) + { + *pixbuf++ = palIndex; + } + else + { + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha; + } + break; + case 16: + shortPixel = *(word *)buf_p, buf_p += 2; + *pixbuf++ = blue = (shortPixel & ( 31 << 10 )) >> 7; + *pixbuf++ = green = (shortPixel & ( 31 << 5 )) >> 2; + *pixbuf++ = red = (shortPixel & ( 31 )) << 3; + *pixbuf++ = alpha = 0xff; + break; + case 24: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha = 0xFF; + break; + case 32: + blue = *buf_p++; + green = *buf_p++; + red = *buf_p++; + alpha = *buf_p++; + *pixbuf++ = red; + *pixbuf++ = green; + *pixbuf++ = blue; + *pixbuf++ = alpha; + break; + default: + MsgDev( D_ERROR, "Image_LoadBMP: illegal pixel_size (%s)\n", name ); + Mem_Free( pic ); + return NULL; + } + + if( !FBitSet( pic->flags, IMAGE_QUANTIZED ) && ( red != green || green != blue )) + pic->flags |= IMAGE_HAS_COLOR; + + if( alpha != 255 ) + { + if( alpha != 0 ) + { + SetBits( pic->flags, IMAGE_HAS_8BIT_ALPHA ); + ClearBits( pic->flags, IMAGE_HAS_1BIT_ALPHA ); + } + else if( !FBitSet( pic->flags, IMAGE_HAS_8BIT_ALPHA )) + SetBits( pic->flags, IMAGE_HAS_1BIT_ALPHA ); + } + } + + buf_p += padSize; // probably actual only for 4-bit bmps + } + + return pic; +} + +/* +================ +COM_LoadImage + +handle bmp & tga +================ +*/ +rgbdata_t *COM_LoadImage( const char *filename ) +{ + size_t fileSize; + byte *buf = (byte *)COM_LoadFile( filename, &fileSize, false ); + const char *ext = COM_FileExtension( filename ); + rgbdata_t *pic = NULL; + + if( !buf ) return NULL; + + if( !Q_stricmp( ext, "tga" )) + pic = Image_LoadTGA( filename, buf, fileSize ); + else if( !Q_stricmp( ext, "bmp" )) + pic = Image_LoadBMP( filename, buf, fileSize ); + else MsgDev( D_ERROR, "COM_LoadImage: unsupported format (%s)\n", ext ); + + Mem_Free( buf ); // release file + + return pic; // may be NULL +} + +/* +================ +COM_LoadImage + +handle bmp & tga +================ +*/ +rgbdata_t *COM_LoadImageMemory( const char *filename, const byte *buf, size_t fileSize ) +{ + const char *ext = COM_FileExtension( filename ); + rgbdata_t *pic = NULL; + + if( !buf ) + { + MsgDev( D_ERROR, "COM_LoadImageMemory: unable to load (%s)\n", filename ); + return NULL; + } + + if( !Q_stricmp( ext, "tga" )) + pic = Image_LoadTGA( filename, buf, fileSize ); + else if( !Q_stricmp( ext, "bmp" )) + pic = Image_LoadBMP( filename, buf, fileSize ); + else MsgDev( D_ERROR, "COM_LoadImage: unsupported format (%s)\n", ext ); + + return pic; // may be NULL +} + +/* +============================================================================= + + IMAGE PROCESSING + +============================================================================= +*/ +#define TRANSPARENT_R 0x0 +#define TRANSPARENT_G 0x0 +#define TRANSPARENT_B 0xFF +#define IS_TRANSPARENT( p ) ( p[0] == TRANSPARENT_R && p[1] == TRANSPARENT_G && p[2] == TRANSPARENT_B ) +#define LERPBYTE( i ) r = resamplerow1[i]; out[i] = (byte)(((( resamplerow2[i] - r ) * lerp)>>16 ) + r ) + +static void Image_Resample32LerpLine( const byte *in, byte *out, int inwidth, int outwidth ) +{ + int j, xi, oldx = 0, f, fstep, endx, lerp; + + fstep = (int)(inwidth * 65536.0f / outwidth); + endx = (inwidth-1); + + for( j = 0, f = 0; j < outwidth; j++, f += fstep ) + { + xi = f>>16; + if( xi != oldx ) + { + in += (xi - oldx) * 4; + oldx = xi; + } + if( xi < endx ) + { + lerp = f & 0xFFFF; + *out++ = (byte)((((in[4] - in[0]) * lerp)>>16) + in[0]); + *out++ = (byte)((((in[5] - in[1]) * lerp)>>16) + in[1]); + *out++ = (byte)((((in[6] - in[2]) * lerp)>>16) + in[2]); + *out++ = (byte)((((in[7] - in[3]) * lerp)>>16) + in[3]); + } + else // last pixel of the line has no pixel to lerp to + { + *out++ = in[0]; + *out++ = in[1]; + *out++ = in[2]; + *out++ = in[3]; + } + } +} + +void Image_Resample32Lerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight ) +{ + const byte *inrow; + int i, j, r, yi, oldy = 0, f, fstep, lerp, endy = (inheight - 1); + int inwidth4 = inwidth * 4; + int outwidth4 = outwidth * 4; + byte *out = (byte *)outdata; + byte *resamplerow1; + byte *resamplerow2; + + fstep = (int)(inheight * 65536.0f / outheight); + + resamplerow1 = (byte *)Mem_Alloc( outwidth * 4 * 2 ); + resamplerow2 = resamplerow1 + outwidth * 4; + + inrow = (const byte *)indata; + + Image_Resample32LerpLine( inrow, resamplerow1, inwidth, outwidth ); + Image_Resample32LerpLine( inrow + inwidth4, resamplerow2, inwidth, outwidth ); + + for( i = 0, f = 0; i < outheight; i++, f += fstep ) + { + yi = f >> 16; + + if( yi < endy ) + { + lerp = f & 0xFFFF; + + if( yi != oldy ) + { + inrow = (byte *)indata + inwidth4 * yi; + if( yi == ( oldy + 1 )) memcpy( resamplerow1, resamplerow2, outwidth4 ); + else Image_Resample32LerpLine( inrow, resamplerow1, inwidth, outwidth ); + Image_Resample32LerpLine( inrow + inwidth4, resamplerow2, inwidth, outwidth ); + oldy = yi; + } + + j = outwidth - 4; + + while( j >= 0 ) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + LERPBYTE( 4); + LERPBYTE( 5); + LERPBYTE( 6); + LERPBYTE( 7); + LERPBYTE( 8); + LERPBYTE( 9); + LERPBYTE(10); + LERPBYTE(11); + LERPBYTE(12); + LERPBYTE(13); + LERPBYTE(14); + LERPBYTE(15); + out += 16; + resamplerow1 += 16; + resamplerow2 += 16; + j -= 4; + } + + if( j & 2 ) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + LERPBYTE( 4); + LERPBYTE( 5); + LERPBYTE( 6); + LERPBYTE( 7); + out += 8; + resamplerow1 += 8; + resamplerow2 += 8; + } + + if( j & 1 ) + { + LERPBYTE( 0); + LERPBYTE( 1); + LERPBYTE( 2); + LERPBYTE( 3); + out += 4; + resamplerow1 += 4; + resamplerow2 += 4; + } + + resamplerow1 -= outwidth4; + resamplerow2 -= outwidth4; + } + else + { + if( yi != oldy ) + { + inrow = (byte *)indata + inwidth4 * yi; + if( yi == ( oldy + 1 )) memcpy( resamplerow1, resamplerow2, outwidth4 ); + else Image_Resample32LerpLine( inrow, resamplerow1, inwidth, outwidth ); + oldy = yi; + } + + memcpy( out, resamplerow1, outwidth4 ); + } + } + + Mem_Free( resamplerow1 ); +} + +void Image_Resample8Nolerp( const void *indata, int inwidth, int inheight, void *outdata, int outwidth, int outheight ) +{ + int i, j; + byte *in, *inrow; + size_t frac, fracstep; + byte *out = (byte *)outdata; + + in = (byte *)indata; + fracstep = inwidth * 0x10000 / outwidth; + + for( i = 0; i < outheight; i++, out += outwidth ) + { + inrow = in + inwidth * (i * inheight / outheight); + frac = fracstep >> 1; + + for( j = 0; j < outwidth; j++ ) + { + out[j] = inrow[frac>>16]; + frac += fracstep; + } + } +} + +/* +================ +Image_Resample +================ +*/ +rgbdata_t *Image_Resample( rgbdata_t *pic, int new_width, int new_height ) +{ + if( !pic ) return NULL; + + // nothing to resample ? + if( pic->width == new_width && pic->height == new_height ) + return pic; + + MsgDev( D_REPORT, "Image_Resample: from %ix%i to %ix%i\n", pic->width, pic->height, new_width, new_height ); + + rgbdata_t *out = Image_Alloc( new_width, new_height, FBitSet( pic->flags, IMAGE_QUANTIZED )); + + if( FBitSet( pic->flags, IMAGE_QUANTIZED )) + Image_Resample8Nolerp( pic->buffer, pic->width, pic->height, out->buffer, out->width, out->height ); + else Image_Resample32Lerp( pic->buffer, pic->width, pic->height, out->buffer, out->width, out->height ); + + // copy remaining data from source + if( FBitSet( pic->flags, IMAGE_QUANTIZED )) + memcpy( out->palette, pic->palette, 1024 ); + out->flags = pic->flags; + + // release old image + Mem_Free( pic ); + + return out; +} + +/* +============== +Image_MakeOneBitAlpha + +remap all pixels of color 0, 0, 255 to index 255 +and remap index 255 to something else +============== +*/ +void Image_MakeOneBitAlpha( rgbdata_t *pic ) +{ + byte transtable[256], *buf; + int i, j, firsttrans = -1; + + if( !pic || !FBitSet( pic->flags, IMAGE_QUANTIZED )) + return; // only for quantized images + + for( i = 0; i < 256; i++ ) + { + if( IS_TRANSPARENT(( pic->palette + ( i * 4 )))) + { + transtable[i] = 255; + if( firsttrans < 0 ) + firsttrans = i; + } + else transtable[i] = i; + } + + // if there is some transparency, translate it + if( 0 )//firsttrans >= 0 ) + { + if( !IS_TRANSPARENT(( pic->palette + ( 255 * 4 )))) + transtable[255] = firsttrans; + buf = pic->buffer; + + for( j = 0; j < pic->height; j++ ) + { + for( i = 0; i < pic->width; i++ ) + { + *buf = transtable[*buf]; + buf++; + } + } + + // move palette entry for pixels previously mapped to entry 255 + pic->palette[firsttrans*4+0] = pic->palette[255*4+0]; + pic->palette[firsttrans*4+1] = pic->palette[255*4+1]; + pic->palette[firsttrans*4+2] = pic->palette[255*4+2]; + pic->palette[firsttrans*4+3] = pic->palette[255*4+3]; + pic->palette[255*4+0] = TRANSPARENT_R; + pic->palette[255*4+1] = TRANSPARENT_G; + pic->palette[255*4+2] = TRANSPARENT_B; + pic->palette[255*4+3] = 0xFF; + } + else + { + // just turn last color to blue + pic->palette[255*4+0] = TRANSPARENT_R; + pic->palette[255*4+1] = TRANSPARENT_G; + pic->palette[255*4+2] = TRANSPARENT_B; + pic->palette[255*4+3] = 0xFF; + } + + // needs for software mip generator + SetBits( pic->flags, IMAGE_HAS_1BIT_ALPHA ); +} + +/* +================ +Image_ApplyGamma + +we can't store alpha-channel into 8-bit texture +but we can store it separate as another image +================ +*/ +void Image_ApplyGamma( rgbdata_t *pic ) +{ + if( !pic || g_gamma == 1.8f ) + return; + + if( !FBitSet( pic->flags, IMAGE_QUANTIZED )) + return; // only for quantized images + + float g = g_gamma / 1.8; + + // gamma correct the monster textures to a gamma of 1.8 + for( int i = 0; i < 256; i++ ) + { + pic->palette[i*4+0] = pow( pic->palette[i*4+0] / 255.0f, g ) * 255; + pic->palette[i*4+1] = pow( pic->palette[i*4+1] / 255.0f, g ) * 255; + pic->palette[i*4+2] = pow( pic->palette[i*4+2] / 255.0f, g ) * 255; + } +} \ No newline at end of file diff --git a/utils/studiomdl/imagelib.h b/utils/studiomdl/imagelib.h new file mode 100644 index 0000000..fb851e3 --- /dev/null +++ b/utils/studiomdl/imagelib.h @@ -0,0 +1,114 @@ +/* +imagelib.h - simple loader\serializer for TGA & BMP +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#ifndef IMAGELIB_H +#define IMAGELIB_H + +/* +======================================================================== + +.BMP image format + +======================================================================== +*/ +#pragma pack( 1 ) +typedef struct +{ + char id[2]; // bmfh.bfType + dword fileSize; // bmfh.bfSize + dword reserved0; // bmfh.bfReserved1 + bmfh.bfReserved2 + dword bitmapDataOffset; // bmfh.bfOffBits + dword bitmapHeaderSize; // bmih.biSize + int width; // bmih.biWidth + int height; // bmih.biHeight + word planes; // bmih.biPlanes + word bitsPerPixel; // bmih.biBitCount + dword compression; // bmih.biCompression + dword bitmapDataSize; // bmih.biSizeImage + dword hRes; // bmih.biXPelsPerMeter + dword vRes; // bmih.biYPelsPerMeter + dword colors; // bmih.biClrUsed + dword importantColors; // bmih.biClrImportant +} bmp_t; +#pragma pack( ) + +/* +======================================================================== + +.TGA image format (Truevision Targa) + +======================================================================== +*/ +#pragma pack( 1 ) +typedef struct tga_s +{ + byte id_length; + byte colormap_type; + byte image_type; + word colormap_index; + word colormap_length; + byte colormap_size; + word x_origin; + word y_origin; + word width; + word height; + byte pixel_size; + byte attributes; +} tga_t; +#pragma pack( ) + +#define IMAGE_MINWIDTH 1 // last mip-level is 1x1 +#define IMAGE_MINHEIGHT 1 +#define IMAGE_MAXWIDTH 4096 +#define IMAGE_MAXHEIGHT 4096 +#define MIP_MAXWIDTH 1024 // large sizes it's too complicated for quantizer +#define MIP_MAXHEIGHT 1024 // and provoked color degradation + +#define IMAGE_HAS_ALPHA (IMAGE_HAS_1BIT_ALPHA|IMAGE_HAS_8BIT_ALPHA) + +// rgbdata->flags +typedef enum +{ + IMAGE_QUANTIZED = BIT( 0 ), // this image already quantized + IMAGE_HAS_COLOR = BIT( 1 ), // image contain RGB-channel + IMAGE_HAS_1BIT_ALPHA = BIT( 2 ), // textures with '{' + IMAGE_HAS_8BIT_ALPHA = BIT( 3 ), // image contain full-range alpha-channel +} imgFlags_t; + +// loaded image +typedef struct rgbdata_s +{ + word width; // image width + word height; // image height + word flags; // misc image flags + byte *palette; // palette if present + byte *buffer; // image buffer + size_t size; // for bounds checking +} rgbdata_t; + +// common functions +rgbdata_t *Image_Alloc( int width, int height, bool paletted = false ); +rgbdata_t *Image_Copy( rgbdata_t *src ); +rgbdata_t *COM_LoadImage( const char *filename ); +rgbdata_t *COM_LoadImageMemory( const char *filename, const byte *buf, size_t fileSize ); +bool Image_ValidSize( const char *name, int width, int height ); +rgbdata_t *Image_Resample( rgbdata_t *pic, int new_width, int new_height ); +rgbdata_t *Image_Quantize( rgbdata_t *pic ); +void Image_MakeOneBitAlpha( rgbdata_t *pic ); +void Image_ApplyGamma( rgbdata_t *pic ); + +extern float g_gamma; + +#endif//IMAGELIB_H \ No newline at end of file diff --git a/utils/studiomdl/optimize.cpp b/utils/studiomdl/optimize.cpp new file mode 100644 index 0000000..7dc120d --- /dev/null +++ b/utils/studiomdl/optimize.cpp @@ -0,0 +1,332 @@ +/* +optimize.cpp - studio model optimization (generate triangle strips as possible) +Copyright (C) 2017 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "cmdlib.h" +#include "mathlib.h" +#include "stringlib.h" +#include "studio.h" +#include "studiomdl.h" + +int used[MAXSTUDIOTRIANGLES]; +// the command list holds counts and s/t values that are valid for +// every frame +short commands[MAXSTUDIOTRIANGLES * 13]; +int numcommands; +// all frames will have their vertexes rearranged and expanded +// so they are in the order expected by the command list +int allverts, alltris, stripcount; +int stripverts[MAXSTUDIOTRIANGLES+2]; +int striptris[MAXSTUDIOTRIANGLES+2]; +int neighbortri[MAXSTUDIOTRIANGLES][3]; +int neighboredge[MAXSTUDIOTRIANGLES][3]; + +s_trianglevert_t (*triangles)[3]; +s_mesh_t *pmesh; + +void FindNeighbor (int starttri, int startv) +{ + s_trianglevert_t m1, m2; + int j; + s_trianglevert_t *last, *check; + int k; + + // used[starttri] |= (1 << startv); + + last = &triangles[starttri][0]; + + m1 = last[(startv+1)%3]; + m2 = last[(startv+0)%3]; + + for (j=starttri+1, check=&triangles[starttri+1][0] ; jnumtris ; j++, check += 3) + { + if (used[j] == 7) + continue; + for (k=0 ; k<3 ; k++) + { + if (memcmp(&check[k],&m1,sizeof(m1))) + continue; + if (memcmp(&check[ (k+1)%3 ],&m2,sizeof(m2))) + continue; + + neighbortri[starttri][startv] = j; + neighboredge[starttri][startv] = k; + + neighbortri[j][k] = starttri; + neighboredge[j][k] = startv; + + used[starttri] |= (1 << startv); + used[j] |= (1 << k); + return; + } + } +} + +int StripLength (int starttri, int startv) +{ + int j; + int k; + + used[starttri] = 2; + + stripverts[0] = (startv)%3; + stripverts[1] = (startv+1)%3; + stripverts[2] = (startv+2)%3; + + striptris[0] = starttri; + striptris[1] = starttri; + striptris[2] = starttri; + stripcount = 3; + + while( 1 ) + { + if (stripcount & 1) + { + j = neighbortri[starttri][(startv+1)%3]; + k = neighboredge[starttri][(startv+1)%3]; + } + else + { + j = neighbortri[starttri][(startv+2)%3]; + k = neighboredge[starttri][(startv+2)%3]; + } + if (j == -1 || used[j]) + goto done; + + stripverts[stripcount] = (k+2)%3; + striptris[stripcount] = j; + stripcount++; + + used[j] = 2; + + starttri = j; + startv = k; + } + +done: + + // clear the temp used flags + for (j=0 ; jnumtris ; j++) + if (used[j] == 2) + used[j] = 0; + + return stripcount; +} + +int FanLength (int starttri, int startv) +{ + int j; + int k; + + used[starttri] = 2; + + stripverts[0] = (startv)%3; + stripverts[1] = (startv+1)%3; + stripverts[2] = (startv+2)%3; + + striptris[0] = starttri; + striptris[1] = starttri; + striptris[2] = starttri; + stripcount = 3; + + while( 1 ) + { + j = neighbortri[starttri][(startv+2)%3]; + k = neighboredge[starttri][(startv+2)%3]; + + if (j == -1 || used[j]) + goto done; + + stripverts[stripcount] = (k+2)%3; + striptris[stripcount] = j; + stripcount++; + + used[j] = 2; + + starttri = j; + startv = k; + } + +done: + + // clear the temp used flags + for (j=0 ; jnumtris ; j++) + if (used[j] == 2) + used[j] = 0; + + return stripcount; +} + +//Generate a list of trifans or strips for the model, which holds for all frames +int numcommandnodes; + +int BuildTris (s_trianglevert_t (*x)[3], s_mesh_t *y, byte **ppdata ) +{ + int i, j, k, m; + int startv; + int len, bestlen, besttype; + static int bestverts[MAXSTUDIOTRIANGLES]; + static int besttris[MAXSTUDIOTRIANGLES]; + static int peak[MAXSTUDIOTRIANGLES]; + int type; + int total = 0; + double t; + int maxlen; + + triangles = x; + pmesh = y; + + + t = I_FloatTime(); + + for (i=0 ; inumtris ; i++) + { + neighbortri[i][0] = neighbortri[i][1] = neighbortri[i][2] = -1; + used[i] = 0; + peak[i] = pmesh->numtris; + } + + // printf("finding neighbors\n"); + for (i=0 ; inumtris; i++) + { + for (k = 0; k < 3; k++) + { + if (used[i] & (1 << k)) + continue; + + FindNeighbor( i, k ); + } + // printf("%d", used[i] ); + } + // printf("\n"); + + // + // build tristrips + // + numcommandnodes = 0; + numcommands = 0; + memset (used, 0, sizeof(used)); + + for (i=0 ; inumtris ;) + { + // pick an unused triangle and start the trifan + if (used[i]) + { + i++; + continue; + } + + maxlen = 9999; + bestlen = 0; + m = 0; + for (k = i; k < pmesh->numtris && bestlen < 127; k++) + { + int localpeak = 0; + + if (used[k]) + continue; + + if (peak[k] <= bestlen) + continue; + + m++; + for (type = 0 ; type < 2 ; type++) + { + for (startv =0 ; startv < 3 ; startv++) + { + if (type == 1) + len = FanLength (k, startv); + else + len = StripLength (k, startv); + if (len > 127) + { + // skip these, they are too long to encode + } + else if (len > bestlen) + { + besttype = type; + bestlen = len; + for (j=0 ; j localpeak) + localpeak = len; + } + } + peak[k] = localpeak; + if (localpeak == maxlen) + break; + } + total += (bestlen - 2); + + // printf("%d (%d) %d\n", bestlen, pmesh->numtris - total, i ); + + maxlen = bestlen; + + // mark the tris on the best strip as used + for (j=0 ; jvertindex; + commands[numcommands++] = tri->normindex; + + if(( g_texture[pmesh->skinref].flags & STUDIO_NF_CHROME ) || !store_uv_coords ) + { + commands[numcommands++] = tri->s; + commands[numcommands++] = tri->t; + } + else + { +#if 0 + float error_u = fabs( tri->u - HalfToFloat( FloatToHalf( tri->u ))); + float error_v = fabs( tri->v - HalfToFloat( FloatToHalf( tri->v ))); + Msg( "store coord: ( %.7f %.7f ), error: ( %.7f %.7f )\n", tri->u, tri->v, error_u, error_v ); +#endif + commands[numcommands++] = FloatToHalf( tri->u ); + commands[numcommands++] = FloatToHalf( tri->v ); + } + } + // printf("%d ", bestlen - 2 ); + numcommandnodes++; + + if( t != I_FloatTime()) + { + // don't write progress to log + printf( "%2d%%\r", (total * 100) / pmesh->numtris ); + t = I_FloatTime(); + } + } + + commands[numcommands++] = 0; // end of list marker + + *ppdata = (byte *)commands; + + // printf("%d %d %d\n", numcommandnodes, numcommands, pmesh->numtris ); + return numcommands * sizeof( short ); +} \ No newline at end of file diff --git a/utils/studiomdl/quantizer.cpp b/utils/studiomdl/quantizer.cpp new file mode 100644 index 0000000..a231043 --- /dev/null +++ b/utils/studiomdl/quantizer.cpp @@ -0,0 +1,469 @@ +/* +quantizer.cpp - image quantizer. based on Antony Dekker original code +Copyright (C) 2015 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include +#include "cmdlib.h" +#include "mathlib.h" +#include "stringlib.h" +#include "scriplib.h" +#include "filesystem.h" +#include "imagelib.h" + +#define netsize 256 // number of colours used +#define prime1 499 +#define prime2 491 +#define prime3 487 +#define prime4 503 + +#define minpicturebytes (3*prime4) // minimum size for input image + +#define maxnetpos (netsize-1) +#define netbiasshift 4 // bias for colour values +#define ncycles 100 // no. of learning cycles + +// defs for freq and bias +#define intbiasshift 16 // bias for fractions +#define intbias (1<>betashift) // beta = 1 / 1024 +#define betagamma (intbias<<(gammashift - betashift)) + +// defs for decreasing radius factor +#define initrad (netsize>>3) // for 256 cols, radius starts +#define radiusbiasshift 6 // at 32.0 biased by 6 bits +#define radiusbias (1<> netbiasshift; + if( temp > 255 ) temp = 255; + network[i][j] = temp; + } + network[i][3] = i; // record colour num + } +} + +// insertion sort of network and building of netindex[0..255] (to do after unbias) +void inxbuild( void ) +{ + register int *p, *q; + register int i, j, smallpos, smallval; + int previouscol, startpos; + + previouscol = 0; + startpos = 0; + + for( i = 0; i < netsize; i++ ) + { + p = network[i]; + smallpos = i; + smallval = p[1]; // index on g + + // find smallest in i..netsize-1 + for( j = i + 1; j < netsize; j++ ) + { + q = network[j]; + + if( q[1] < smallval ) + { + // index on g + smallpos = j; + smallval = q[1]; // index on g + } + } + + q = network[smallpos]; + + // swap p (i) and q (smallpos) entries + if( i != smallpos ) + { + j = q[0]; q[0] = p[0]; p[0] = j; + j = q[1]; q[1] = p[1]; p[1] = j; + j = q[2]; q[2] = p[2]; p[2] = j; + j = q[3]; q[3] = p[3]; p[3] = j; + } + + // smallval entry is now in position i + if( smallval != previouscol ) + { + netindex[previouscol] = (startpos+i) >> 1; + + for( j = previouscol + 1; j < smallval; j++ ) + netindex[j] = i; + + previouscol = smallval; + startpos = i; + } + } + + netindex[previouscol] = (startpos + maxnetpos)>>1; + + for( j = previouscol + 1; j < 256; j++ ) + netindex[j] = maxnetpos; // really 256 +} + +// search for BGR values 0..255 (after net is unbiased) and return colour index +int inxsearch( int r, int g, int b ) +{ + register int i, j, dist, a, bestd; + register int *p; + int best; + + bestd = 1000; // biggest possible dist is 256 * 3 + best = -1; + i = netindex[g]; // index on g + j = i - 1; // start at netindex[g] and work outwards + + while(( i < netsize ) || ( j >= 0 )) + { + if( i < netsize ) + { + p = network[i]; + dist = p[1] - g; // inx key + + if( dist >= bestd ) + { + i = netsize; // stop iter + } + else + { + i++; + if( dist < 0 ) dist = -dist; + a = p[2] - b; + if( a < 0 ) a = -a; + dist += a; + + if( dist < bestd ) + { + a = p[0] - r; + if( a < 0 ) a = -a; + dist += a; + + if( dist < bestd ) + { + bestd = dist; + best = p[3]; + } + } + } + } + + if( j >= 0 ) + { + p = network[j]; + dist = g - p[1]; // inx key - reverse dif + + if( dist >= bestd ) + { + j = -1; // stop iter + } + else + { + j--; + if( dist < 0 ) dist = -dist; + a = p[2] - b; + if( a < 0 ) a = -a; + dist += a; + + if( dist < bestd ) + { + a = p[0] - r; + if( a < 0 ) a = -a; + dist += a; + if( dist < bestd ) + { + bestd = dist; + best = p[3]; + } + } + } + } + } + + return best; +} + +// search for biased BGR values +int contest( int r, int g, int b ) +{ + // finds closest neuron (min dist) and updates freq + // finds best neuron (min dist-bias) and returns position + // for frequently chosen neurons, freq[i] is high and bias[i] is negative + // bias[i] = gamma * ((1 / netsize) - freq[i]) + + register int *p, *f, *n; + register int i, dist, a, biasdist, betafreq; + int bestpos, bestbiaspos, bestd, bestbiasd; + + bestd = ~(1<<31); + bestbiasd = bestd; + bestpos = -1; + bestbiaspos = bestpos; + p = bias; + f = freq; + + for( i = 0; i < netsize; i++ ) + { + n = network[i]; + dist = n[2] - b; + if( dist < 0 ) dist = -dist; + a = n[1] - g; + if( a < 0 ) a = -a; + dist += a; + a = n[0] - r; + if( a < 0 ) a = -a; + dist += a; + + if( dist < bestd ) + { + bestd = dist; + bestpos = i; + } + + biasdist = dist - ((*p) >> (intbiasshift - netbiasshift)); + + if( biasdist < bestbiasd ) + { + bestbiasd = biasdist; + bestbiaspos = i; + } + + betafreq = (*f >> betashift); + *f++ -= betafreq; + *p++ += (betafreq << gammashift); + } + + freq[bestpos] += beta; + bias[bestpos] -= betagamma; + + return bestbiaspos; +} + +// move neuron i towards biased (b,g,r) by factor alpha +void altersingle( int alpha, int i, int r, int g, int b ) +{ + register int *n; + + n = network[i]; // alter hit neuron + *n -= (alpha * (*n - r)) / initalpha; + n++; + *n -= (alpha * (*n - g)) / initalpha; + n++; + *n -= (alpha * (*n - b)) / initalpha; +} + +// move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in radpower[|i-j|] +void alterneigh( int rad, int i, int r, int g, int b ) +{ + register int j, k, lo, hi, a; + register int *p, *q; + + lo = i - rad; + if( lo < -1 ) + lo = -1; + + hi = i + rad; + if( hi > netsize ) + hi = netsize; + + j = i + 1; + k = i - 1; + q = radpower; + + while(( j < hi ) || ( k > lo )) + { + a = (*(++q)); + + if( j < hi ) + { + p = network[j]; + *p -= (a * (*p - r)) / alpharadbias; + p++; + *p -= (a * (*p - g)) / alpharadbias; + p++; + *p -= (a * (*p - b)) / alpharadbias; + j++; + } + + if( k > lo ) + { + p = network[k]; + *p -= (a * (*p - r)) / alpharadbias; + p++; + *p -= (a * (*p - g)) / alpharadbias; + p++; + *p -= (a * (*p - b)) / alpharadbias; + k--; + } + } +} + +// main Learning Loop +void learn( void ) +{ + register byte *p; + register int i, j, r, g, b; + int radius, rad, alpha, step; + int delta, samplepixels; + byte *lim; + + alphadec = 30 + ((samplefac - 1) / 3); + p = thepicture; + lim = thepicture + lengthcount; + samplepixels = lengthcount / (samplefac * 4); // RGBA + delta = samplepixels / ncycles; + alpha = initalpha; + radius = initradius; + + rad = radius >> radiusbiasshift; + if( rad <= 1 ) rad = 0; + + for( i = 0; i < rad; i++ ) + radpower[i] = alpha * ((( rad * rad - i * i ) * radbias ) / ( rad * rad )); + + if( delta <= 0 ) return; + + if(( lengthcount % prime1 ) != 0 ) + { + step = prime1 * 4; // RGBA + } + else if(( lengthcount % prime2 ) != 0 ) + { + step = prime2 * 4; // RGBA + } + else if(( lengthcount % prime3 ) != 0 ) + { + step = prime3 * 4; // RGBA + } + else + { + step = prime4 * 4; // RGBA; + } + + i = 0; + + while( i < samplepixels ) + { + r = p[0] << netbiasshift; + g = p[1] << netbiasshift; + b = p[2] << netbiasshift; + j = contest( r, g, b ); + + altersingle( alpha, j, r, g, b ); + if( rad ) alterneigh( rad, j, r, g, b ); // alter neighbours + + p += step; + if( p >= lim ) p -= lengthcount; + + i++; + + if( i % delta == 0 ) + { + alpha -= alpha / alphadec; + radius -= radius / radiusdec; + rad = radius >> radiusbiasshift; + if( rad <= 1 ) rad = 0; + + for( j = 0; j < rad; j++ ) + radpower[j] = alpha * ((( rad * rad - j * j ) * radbias ) / ( rad * rad )); + } + } +} + +// returns the actual number of palette entries. +rgbdata_t *Image_Quantize( rgbdata_t *pic ) +{ + rgbdata_t *out; + int i, samples; + + if( !pic ) return NULL; + + // already quantized? + if( FBitSet( pic->flags, IMAGE_QUANTIZED )) + return pic; + + out = Image_Alloc( pic->width, pic->height, true ); + + if( FBitSet( pic->flags, IMAGE_HAS_COLOR )) + samples = 1; // maximum quality + else samples = 10; // fast mode + + initnet( pic->buffer, pic->size, samples ); + learn(); + unbiasnet(); + + for( i = 0; i < netsize; i++ ) + { + out->palette[i*4+0] = network[i][0]; // red + out->palette[i*4+1] = network[i][1]; // green + out->palette[i*4+2] = network[i][2]; // blue + out->palette[i*4+3] = 0xFF; // alpha + } + + inxbuild(); + + for( i = 0; i < pic->width * pic->height; i++ ) + out->buffer[i] = inxsearch( pic->buffer[i*4+0], pic->buffer[i*4+1], pic->buffer[i*4+2] ); + Mem_Free( pic ); // release RGBA image + + return out; +} \ No newline at end of file diff --git a/utils/studiomdl/simpilfy.cpp b/utils/studiomdl/simpilfy.cpp new file mode 100644 index 0000000..bb0b0fe --- /dev/null +++ b/utils/studiomdl/simpilfy.cpp @@ -0,0 +1,5401 @@ +/* +simplify.cpp - studio model simplification +Copyright (C) 2017 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "cmdlib.h" +#include "mathlib.h" +#include "stringlib.h" +#include "studio.h" +#include "studiomdl.h" +#include "iksolver.h" + +#define ANIM_COMPRESS_THRESHOLD 0 // more values compress animation but can skip frames (optimal range between 0-100) +#define cmp_animvalue( x, y ) ( abs( value[x] - value[y] ) <= ANIM_COMPRESS_THRESHOLD ) + +void AccumulateSeqLayers( Vector pos[], Vector4D q[], int sequence, float frame, float flWeight ); +int g_rootIndex = 0; + +//---------------------------------------------------------------------- +// underlay: +// studiomdl : delta = new_anim * ( -1 * base_anim ) +// engine : result = (w * delta) * base_anim +// +// overlay +// +// studiomdl : delta = (-1 * base_anim ) * new_anim +// engine : result = base_anim * (w * delta) +// +//---------------------------------------------------------------------- +void QuaternionSMAngles( float s, Vector4D const &p, Vector4D const &q, Radian &angles ) +{ + Vector4D qt; + QuaternionSM( s, p, q, qt ); + QuaternionAngle( qt, angles ); +} + +void QuaternionMAAngles( Vector4D const &p, float s, Vector4D const &q, Radian &angles ) +{ + Vector4D qt; + QuaternionMA( p, s, q, qt ); + QuaternionAngle( qt, angles ); +} + +//----------------------------------------------------------------------------- +// Purpose: convert pBoneToWorld back into rot/pos data +//----------------------------------------------------------------------------- +void solveBone( s_animation_t *panim, int iFrame, int iBone, matrix3x4 *pBoneToWorld ) +{ + int iParent = g_bonetable[iBone].parent; + + if( iParent == -1 ) + { + pBoneToWorld[iBone].GetStudioTransform( panim->sanim[iFrame][iBone].pos, panim->sanim[iFrame][iBone].rot ); + return; + } + + matrix3x4 worldToBone = pBoneToWorld[iParent].Invert(); + matrix3x4 local = worldToBone.ConcatTransforms( pBoneToWorld[iBone] ); + + iFrame = iFrame % panim->numframes; + + local.GetStudioTransform( panim->sanim[iFrame][iBone].pos, panim->sanim[iFrame][iBone].rot ); +} + +bool AnimationDifferent( const Vector& startPos, const Radian& startRot, const Vector& pos, const Radian& rot ) +{ + if( !VectorCompareEpsilon( startPos, pos, 0.01 )) + return true; + + if( !RadianCompareEpsilon( startRot, rot, 0.01 )) + return true; + return false; +} + +bool BoneHasAnimation( const char *pName ) +{ + bool first = true; + Vector pos; + Radian rot; + + if( !g_numani ) return false; + + int globalIndex = findGlobalBone( pName ); + + // don't check root bones for animation + if( globalIndex >= 0 && g_bonetable[globalIndex].parent == -1 ) + return true; + + // find used bones per g_model + for( int i = 0; i < g_numani; i++ ) + { + s_animation_t *panim = g_panimation[i]; + int boneIndex = findLocalBone( panim, pName ); + if( boneIndex < 0 ) continue; // not in this source? + + // this is not right, but enough of the bones are moved unintentionally between + // animations that I put this in to catch them. + int n = panim->startframe - panim->source.startframe; + first = true; + + for( int j = 0; j < panim->numframes; j++ ) + { + if ( first ) + { + pos = panim->rawanim[j+n][boneIndex].pos; + rot = panim->rawanim[j+n][boneIndex].rot; + first = false; + } + else + { + if( AnimationDifferent( pos, rot, panim->rawanim[j+n][boneIndex].pos, panim->rawanim[j+n][boneIndex].rot )) + return true; + } + } + } + + return false; +} + +bool BoneHasAttachments( char const *pname ) +{ + for( int k = 0; k < g_numattachments; k++ ) + { + if( !Q_stricmp( g_attachment[k].bonename, pname )) + return true; + } + + return false; +} + +int BoneIsProcedural( char const *pname ) +{ + int k; + + for( k = 0; k < g_numaxisinterpbones; k++ ) + { + if( !Q_stricmp( g_axisinterpbones[k].bonename, pname )) + return true; + } + + for( k = 0; k < g_numquatinterpbones; k++ ) + { + if( IsGlobalBoneXSI( g_quatinterpbones[k].bonename, pname )) + return true; + } + + for( k = 0; k < g_numaimatbones; k++ ) + { + if( IsGlobalBoneXSI( g_aimatbones[k].bonename, pname )) + return true; + } + + for( k = 0; k < g_numjigglebones; k++ ) + { + if( !Q_stricmp( g_jigglebones[k].bonename, pname )) + return true; + } + return false; +} + +bool BoneIsIK( char const *pname ) +{ + // tag bones used by ikchains + for( int k = 0; k < g_numikchains; k++ ) + { + if( !Q_stricmp( g_ikchain[k].bonename, pname )) + return true; + } + return false; +} + +bool BoneShouldCollapse( char const *pname ) +{ + for( int k = 0; k < g_collapse.Count(); k++ ) + { + if( !Q_stricmp( g_collapse[k].bonename, pname )) + return true; + } + + return ( !BoneHasAnimation( pname ) && !BoneIsProcedural( pname ) && !BoneIsIK( pname ) /* && !BoneHasAttachments( pname ) */); +} + +//----------------------------------------------------------------------------- +// Purpose: Collapse vertex assignments up to parent on bones that are not needed +// This can optimize a model substantially if the animator is using +// lots of helper bones with no animation. +//----------------------------------------------------------------------------- +void CollapseBones( void ) +{ + int count = 0; + int j, k; + + for( k = 0; k < g_numbones; k++ ) + { + if( g_bonetable[k].bDontCollapse ) + continue; + + if(( g_bonetable[k].flags != 0 || g_bonetable[k].bPreDefined ) && !BoneShouldCollapse( g_bonetable[k].name )) + continue; + count++; + + MsgDev( D_NOTE, "collapsing %s\n", g_bonetable[k].name ); + + g_numbones--; + int m = g_bonetable[k].parent; + + for( j = k; j < g_numbones; j++ ) + { + g_bonetable[j] = g_bonetable[j+1]; + + if( g_bonetable[j].parent == k ) + g_bonetable[j].parent = m; + else if( g_bonetable[j].parent >= k ) + g_bonetable[j].parent = g_bonetable[j].parent - 1; + } + k--; + } + + if( count ) MsgDev( D_REPORT, "Collapsed %d bones\n", count ); +} + +//----------------------------------------------------------------------------- +// Purpose: replace all animation, rotation and translation, etc. with a single bone +//----------------------------------------------------------------------------- +void MakeStaticProp( void ) +{ + matrix3x4 rotated = matrix3x4( g_vecZero, g_defaultrotation ); + Vector centerOffset; + int i, j, k; + + // FIXME: missing attachment point recalcs! + // replace bone 0 with "static_prop" bone and attach everything to it. + for( i = 0; i < g_nummodels; i++ ) + { + s_model_t *pmodel = g_model[i]; + + Q_strncpy( pmodel->localBone[0].name, "kHED", sizeof( pmodel->localBone[0].name )); // g-cont :-) + pmodel->localBone[0].parent = -1; + + for( k = 1; k < pmodel->numbones; k++ ) + { + pmodel->localBone[k].parent = -1; + } + + rotated.SetOrigin( g_defaultadjust ); + + Vector mins, maxs; + ClearBounds( mins, maxs ); + + for( j = 0; j < pmodel->numsrcverts; j++ ) + { + for( k = 0; k < pmodel->srcvert[j].localWeight.numbones; k++ ) + { + // attach everything to root + pmodel->srcvert[j].localWeight.bone[k] = 0; + } + + // **shift everything into identity space** + pmodel->srcvert[j].vert = rotated.VectorTransform( pmodel->srcvert[j].vert ); + + // normal + pmodel->srcvert[j].norm = rotated.VectorRotate( pmodel->srcvert[j].norm ); + + // incrementally compute identity space bbox + AddPointToBounds( pmodel->srcvert[j].vert, mins, maxs ); + } + + if( g_centerstaticprop ) + { + const char *pAttachmentName = "placementOrigin"; + bool bFound = false; + + for( k = 0; k < g_numattachments; k++ ) + { + if( !Q_stricmp( g_attachment[k].name, pAttachmentName )) + { + bFound = true; + break; + } + } + + if( !bFound ) + { + centerOffset = -0.5f * (mins + maxs); + } + + for( j = 0; j < pmodel->numsrcverts; j++ ) + { + pmodel->srcvert[j].vert += centerOffset; + } + + if ( !bFound ) + { + // now add an attachment point to store this offset + Q_strncpy( g_attachment[g_numattachments].name, pAttachmentName, sizeof( g_attachment[0].name )); + Q_strncpy( g_attachment[g_numattachments].bonename, "kHED", sizeof( g_attachment[0].name )); + g_attachment[g_numattachments].local = matrix3x4( centerOffset, g_vecZero ); + g_attachment[g_numattachments].bone = 0; + g_attachment[g_numattachments].type = 0; + g_numattachments++; + } + } + + // force the bone to be identity + pmodel->skeleton[0].pos = Vector( 0, 0, 0 ); + pmodel->skeleton[0].rot = Radian( 0, 0, 0 ); + + // make an identity boneToPose transform + pmodel->boneToPose[0].Identity(); + } + + // throw all specified hitboxes + g_numbonecontrollers = 0; + allow_boneweights = 0; + g_numimportbones = 0; + has_boneweights = 0; + g_hitboxsets.Purge(); + g_BoneMerge.Purge(); + + // throw away all animations + g_panimation[0]->numframes = 1; + g_panimation[0]->startframe = 0; + g_panimation[0]->endframe = 1; + g_panimation[0]->source.numframes = 1; + g_panimation[0]->source.startframe = 0; + g_panimation[0]->source.endframe = 1; + Q_strncpy( g_panimation[0]->name, "seq-name", sizeof( g_panimation[0]->name )); + g_panimation[0]->rotation = Radian( 0, 0, 0 ); + g_panimation[0]->adjust = Vector( 0, 0, 0 ); + g_panimation[0]->fps = 30.0f; + + g_numani = 1; + + // recalc attachment points: + for( i = 0; i < g_numattachments; i++ ) + { + if( g_centerstaticprop && ( i == g_numattachments - 1 )) + continue; + + Q_strncpy( g_attachment[i].bonename, "kHED", sizeof( g_attachment[i].name )); + g_attachment[i].local = rotated.ConcatTransforms( g_attachment[i].local ); + g_attachment[i].bone = 0; + g_attachment[i].type = 0; + } + + // throw away all sequences + Q_strncpy( g_sequence[0].name, "seq-name", sizeof( g_sequence[0].name )); + g_sequence[0].panim[0] = g_panimation[0]; + g_sequence[0].fps = g_panimation[0]->fps; + g_sequence[0].numautolayers = 0; + g_sequence[0].activity = 0; + g_sequence[0].numframes = 1; + g_sequence[0].numblends = 1; + g_sequence[0].numevents = 0; + g_sequence[0].seqgroup = 0; + g_sequence[0].paramindex[0] = -1; + g_sequence[0].paramindex[1] = -1; + g_sequence[0].groupsize[0] = 0; + g_sequence[0].groupsize[1] = 0; + g_sequence[0].fadeintime = 0.2f; + g_sequence[0].fadeouttime = 0.2f; + g_sequence[0].numiklocks = 0; + g_sequence[0].numikrules = 0; + g_sequence[0].flags = 0; + g_numseq = 1; +} + +//----------------------------------------------------------------------------- +// Purpose: set "boneref" for all the source bones used by vertices, attachments, etc. +//----------------------------------------------------------------------------- +void TagUsedBones( void ) +{ + int i, j, k, n; + + // find used bones + for( i = 0; i < g_nummodels; i++ ) + { + s_model_t *pmodel = g_model[i]; + + for( k = 0; k < MAXSTUDIOSRCBONES; k++ ) + { + pmodel->boneflags[k] = 0; + pmodel->boneref[k] = 0; + } + + for( j = 0; j < pmodel->numsrcverts; j++ ) + { + for( k = 0; k < pmodel->srcvert[j].localWeight.numbones; k++ ) + { + SetBits( pmodel->boneflags[pmodel->srcvert[j].localWeight.bone[k]], BONE_USED_BY_VERTEX ); + } + } + + for( k = 0; k < g_numattachments; k++ ) + { + for( j = 0; j < pmodel->numbones; j++ ) + { + if( !Q_stricmp( g_attachment[k].bonename, pmodel->localBone[j].name )) + { + // this bone is a keeper with or without associated vertices + // because an attachment point depends on it. + if( FBitSet( g_attachment[k].type, IS_RIGID )) + { + for( n = j; n != -1; n = pmodel->localBone[n].parent ) + { + if( FBitSet( pmodel->boneflags[n], BONE_USED_BY_VERTEX )) + { + SetBits( pmodel->boneflags[n], BONE_USED_BY_ATTACHMENT ); + break; + } + } + } + else + { + SetBits( pmodel->boneflags[j], BONE_USED_BY_ATTACHMENT ); + } + } + } + } + + for( k = 0; k < g_numikchains; k++ ) + { + for( j = 0; j < pmodel->numbones; j++ ) + { + if( !Q_stricmp( g_ikchain[k].bonename, pmodel->localBone[j].name )) + { + // this bone is a keeper with or without associated vertices + // because a ikchain depends on it. + SetBits( pmodel->boneflags[j], BONE_USED_BY_ATTACHMENT ); + } + } + } + + // Tag all bones marked as being used by bonemerge + int nBoneMergeCount = g_BoneMerge.Count(); + for( k = 0; k < nBoneMergeCount; ++k ) + { + for( j = 0; j < pmodel->numbones; j++ ) + { + if( !Q_stricmp( g_BoneMerge[k].bonename, pmodel->localBone[j].name )) + continue; + + SetBits( pmodel->boneflags[j], BONE_USED_BY_BONE_MERGE ); + } + } + + // NOTE: This must come last; after all flags have been set! + // tag bonerefs as being used the union of the boneflags all their children + for( k = 0; k < MAXSTUDIOSRCBONES; k++ ) + { + if( !pmodel->boneflags[k] ) + continue; + + // tag parent bones as used if child has been used + pmodel->boneref[k] |= pmodel->boneflags[k]; + + n = g_model[i]->localBone[k].parent; + while( n != -1 ) + { + pmodel->boneref[n] |= pmodel->boneref[k]; + n = g_model[i]->localBone[n].parent; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: change the names in the source files for bones that max auto-renamed on us +//----------------------------------------------------------------------------- +void RenameBones( void ) +{ + int i, j, k; + + // rename model bones if needed + for( i = 0; i < g_nummodels; i++ ) + { + for( j = 0; j < g_model[i]->numbones; j++ ) + { + for( k = 0; k < g_numrenamedbones; k++ ) + { + if( !Q_strcmp( g_model[i]->localBone[j].name, g_renamedbone[k].from )) + { + Q_strncpy( g_model[i]->localBone[j].name, g_renamedbone[k].to, MAXSRCSTUDIONAME ); + break; + } + } + } + } + + // rename sequence bones if needed + for( i = 0; i < g_numani; i++ ) + { + for( j = 0; j < g_panimation[i]->numbones; j++ ) + { + for( k = 0; k < g_numrenamedbones; k++ ) + { + if( !Q_strcmp( g_panimation[i]->localBone[j].name, g_renamedbone[k].from )) + { + Q_strncpy( g_panimation[i]->localBone[j].name, g_renamedbone[k].to, MAXSRCSTUDIONAME ); + break; + } + } + } + } +} + +void BuildGlobalBoneToPose( void ) +{ + // build reference pose + for( int j = 0; j < g_numbones; j++ ) + { + if( g_bonetable[j].parent == -1 ) g_bonetable[j].boneToPose = g_bonetable[j].rawLocal; + else g_bonetable[j].boneToPose = g_bonetable[g_bonetable[j].parent].boneToPose.ConcatTransforms( g_bonetable[j].rawLocal ); + } +} + +void EnforceHierarchy( void ) +{ + bool bSort = true; + int i, j, k, count = 0; + + // force changes to hierarchy + for( i = 0; i < g_numforcedhierarchy; i++ ) + { + j = findGlobalBone( g_forcedhierarchy[i].parentname ); + k = findGlobalBone( g_forcedhierarchy[i].childname ); + + if( j == -1 && Q_strlen( g_forcedhierarchy[i].parentname ) > 0 ) + { + COM_FatalError( "unknown bone: \"%s\" in forced hierarchy\n", g_forcedhierarchy[i].parentname ); + } + + if( k == -1 ) + { + COM_FatalError( "unknown bone: \"%s\" in forced hierarchy\n", g_forcedhierarchy[i].childname ); + } + + g_bonetable[k].parent = j; + } + + while( bSort ) + { + count++; + bSort = false; + for( i = 0; i < g_numbones; i++ ) + { + if( g_bonetable[i].parent > i ) + { + // swap + j = g_bonetable[i].parent; + s_bonetable_t tmp; + tmp = g_bonetable[i]; + g_bonetable[i] = g_bonetable[j]; + g_bonetable[j] = tmp; + + // relink parents + for( k = i; k < g_numbones; k++ ) + { + if( g_bonetable[k].parent == i ) + g_bonetable[k].parent = j; + else if( g_bonetable[k].parent == j ) + g_bonetable[k].parent = i; + } + bSort = true; + } + } + + if( count > 1000 ) + COM_FatalError( "circular bone heirarchy\n" ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: build transforms in source space, assuming source bones +//----------------------------------------------------------------------------- +void BuildRawTransforms( s_model_t const *pmodel, Vector const &shift, Radian const &rotate, matrix3x4 *boneToWorld ) +{ + matrix3x4 rootxform = matrix3x4( g_vecZero, rotate ); + matrix3x4 bonematrix; + + // build source space local to world transforms + for( int k = 0; k < pmodel->numbones; k++ ) + { + Radian rot = pmodel->skeleton[k].rot; + Vector pos = pmodel->skeleton[k].pos; + + if( pmodel->localBone[k].parent == -1 ) + { + // translate + Vector tmp = pos - shift; + + // rotate + pos = rootxform.VectorRotate( tmp ); + bonematrix = rootxform.ConcatTransforms( matrix3x4( g_vecZero, rot )); + bonematrix.GetStudioTransform( tmp, rot ); + clip_rotations( rot ); + } + + bonematrix = matrix3x4( pos, rot ); + + if( pmodel->localBone[k].parent == -1 ) boneToWorld[k] = bonematrix; + else boneToWorld[k] = boneToWorld[pmodel->localBone[k].parent].ConcatTransforms( bonematrix ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: build transforms in source space, assuming source bones +//----------------------------------------------------------------------------- +void BuildRawTransforms( s_animation_t const *panim, int frame, Vector const &shift, Radian const &rotate, matrix3x4 *boneToWorld ) +{ + matrix3x4 rootxform = matrix3x4( g_vecZero, rotate ); + matrix3x4 bonematrix; + + if( FBitSet( panim->flags, STUDIO_LOOPING )) + { + if( frame ) + { + while( frame < 0) + frame += panim->source.numframes; + frame = frame % panim->source.numframes; + } + } + else + { + frame = bound( 0, frame, panim->source.numframes - 1 ); + } + + // build source space local to world transforms + for( int k = 0; k < panim->numbones; k++ ) + { + Radian rot = panim->rawanim[frame][k].rot; + Vector pos = panim->rawanim[frame][k].pos; + + if( panim->localBone[k].parent == -1) + { + // translate + Vector tmp = pos - shift; + + // rotate + pos = rootxform.VectorRotate( tmp ); + bonematrix = rootxform.ConcatTransforms( matrix3x4( g_vecZero, rot )); + bonematrix.GetStudioTransform( tmp, rot ); + clip_rotations( rot ); + } + + bonematrix = matrix3x4( pos, rot ); + + if( panim->localBone[k].parent == -1 ) boneToWorld[k] = bonematrix; + else boneToWorld[k] = boneToWorld[panim->localBone[k].parent].ConcatTransforms( bonematrix ); + } +} + +void RebuildLocalPose( void ) +{ + matrix3x4 boneToPose[MAXSTUDIOBONES]; + matrix3x4 poseToBone[MAXSTUDIOBONES]; + int j; + + // build reference pose + for( j = 0; j < g_numbones; j++ ) + boneToPose[j] = g_bonetable[j].boneToPose; + + // rebuild local pose + for( j = 0; j < g_numbones; j++ ) + { + if( g_bonetable[j].parent == -1 ) g_bonetable[j].rawLocal = boneToPose[j]; + else g_bonetable[j].rawLocal = poseToBone[g_bonetable[j].parent].ConcatTransforms( boneToPose[j] ); + + g_bonetable[j].rawLocal.GetStudioTransform( g_bonetable[j].pos, g_bonetable[j].rot ); + g_bonetable[j].boneToPose = boneToPose[j]; + poseToBone[j] = boneToPose[j].Invert(); + } +} + +//----------------------------------------------------------------------------- +// Tags bones in the global bone table +//----------------------------------------------------------------------------- +void TagUsedImportedBones( void ) +{ + // NOTE: This has to happen because some bones referenced by bonemerge + // can be set up using the importbones feature + int k, j; + + // Tag all bones marked as being used by bonemerge + int nBoneMergeCount = g_BoneMerge.Count(); + for( k = 0; k < nBoneMergeCount; k++ ) + { + for( j = 0; j < g_numbones; j++ ) + { + if( Q_stricmp( g_BoneMerge[k].bonename, g_bonetable[j].name )) + continue; + g_bonetable[j].flags |= BONE_USED_BY_BONE_MERGE; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: look through all the sources and build a table of used bones +//----------------------------------------------------------------------------- +void BuildGlobalBonetable( void ) +{ + int i, j, k, n; + + g_numbones = 0; + + for( i = 0; i < MAXSTUDIOSRCBONES; i++ ) + { + g_bonetable[i].srcRealign.Identity(); + } + + // insert predefined bones first + for( i = 0; i < g_numimportbones; i++ ) + { + k = findGlobalBone( g_importbone[i].name ); + + if( k == -1 ) + { + k = g_numbones; + Q_strncpy( g_bonetable[k].name, g_importbone[i].name, sizeof( g_bonetable[0].name )); + + if( Q_strlen( g_importbone[i].parent ) == 0 ) + { + g_bonetable[k].parent = -1; + } + else + { + // FIXME: This won't work if the imported bone refers to + // another imported bone which is further along in the list + g_bonetable[k].parent = findGlobalBone( g_importbone[i].parent ); + if( g_bonetable[k].parent == -1 ) + { + MsgDev( D_WARN, "Imported bone %s tried to access parent bone %s and failed!\n", + g_importbone[i].name, g_importbone[i].parent ); + } + } + + g_bonetable[k].bPreDefined = true; + g_bonetable[k].rawLocal = g_importbone[i].rawLocal; + g_bonetable[k].rawLocalOriginal = g_bonetable[k].rawLocal; + g_numbones++; + } + + g_bonetable[k].bDontCollapse = true; + g_bonetable[k].srcRealign = g_importbone[i].srcRealign; + g_bonetable[k].bPreAligned = true; + } + + TagUsedImportedBones(); + + // union of all used bones + for( i = 0; i < g_nummodels; i++ ) + { + s_model_t *pmodel = g_model[i]; + + matrix3x4 srcBoneToWorld[MAXSTUDIOSRCBONES]; + BuildRawTransforms( pmodel, g_vecZero, g_radZero, srcBoneToWorld ); + + for( j = 0; j < pmodel->numbones; j++ ) + { + if( g_collapse_bones_aggressive ) + { + if( pmodel->boneflags[j] == 0 ) + continue; + } + else + { + if( pmodel->boneref[j] == 0 ) + continue; + } + + k = findGlobalBone( pmodel->localBone[j].name ); + + if( k == -1 ) + { + // create new bone + k = g_numbones; + Q_strncpy( g_bonetable[k].name, pmodel->localBone[j].name, sizeof( g_bonetable[0].name )); + + if(( n = pmodel->localBone[j].parent ) != -1 ) + g_bonetable[k].parent = findGlobalBone( pmodel->localBone[n].name ); + else g_bonetable[k].parent = -1; + + g_bonetable[k].bonecontroller = 0; + g_bonetable[k].flags = pmodel->boneflags[j]; + + if( g_bonetable[k].parent == -1 || !g_bonetable[g_bonetable[k].parent].bPreAligned ) + { + g_bonetable[k].rawLocal = matrix3x4( pmodel->skeleton[j].pos, pmodel->skeleton[j].rot ); + g_bonetable[k].rawLocalOriginal = g_bonetable[k].rawLocal; + } + else + { + // convert the local relative position into a realigned relative position + matrix3x4 srcParentBoneToWorld; + srcParentBoneToWorld = srcBoneToWorld[n].ConcatTransforms( g_bonetable[g_bonetable[k].parent].srcRealign ); + matrix3x4 invSrcParentBoneToWorld = srcParentBoneToWorld.Invert(); + g_bonetable[k].rawLocal = invSrcParentBoneToWorld.ConcatTransforms( srcBoneToWorld[j] ); + } + + g_bonetable[k].rawLocal = matrix3x4( pmodel->skeleton[j].pos, pmodel->skeleton[j].rot ); + g_bonetable[k].rawLocalOriginal = g_bonetable[k].rawLocal; + + g_bonetable[k].boneToPose.Identity(); // in original code this was Invalidate (write NAN at each section) + g_numbones++; + continue; + } + + if( g_overridebones && g_bonetable[k].bPreDefined ) + { + g_bonetable[k].flags |= pmodel->boneflags[j]; + + g_bonetable[k].boneToPose = srcBoneToWorld[j].ConcatTransforms( g_bonetable[k].srcRealign ); + + if( g_bonetable[k].parent == -1 ) + { + g_bonetable[k].rawLocal = g_bonetable[k].boneToPose; + } + else + { + matrix3x4 tmp = g_bonetable[g_bonetable[k].parent].boneToPose.Invert(); + g_bonetable[k].rawLocal = tmp.ConcatTransforms( g_bonetable[k].boneToPose ); + } + continue; + } + + // accumlate flags + g_bonetable[k].flags |= pmodel->boneflags[j]; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: find procedural bones and tag for inclusion even if they don't animate +//----------------------------------------------------------------------------- +void TagProceduralBones( void ) +{ + int numaxisinterpbones = 0; + int numquatinterpbones = 0; + int numaimatbones = 0; + int numjigglebones = 0; + int j; + + // look for AxisInterp bone definitions + for( j = 0; j < g_numaxisinterpbones; j++ ) + { + g_axisinterpbones[j].bone = findGlobalBone( g_axisinterpbones[j].bonename ); + g_axisinterpbones[j].control = findGlobalBone( g_axisinterpbones[j].controlname ); + + if( g_axisinterpbones[j].bone == -1 ) + { + MsgDev( D_WARN, "axisinterpbone \"%s\" unused\n", g_axisinterpbones[j].bonename ); + continue; // optimized out, don't complain + } + + if( g_axisinterpbones[j].control == -1 ) + { + COM_FatalError( "Missing control bone \"%s\" for procedural bone \"%s\"\n", + g_axisinterpbones[j].bonename, g_axisinterpbones[j].controlname ); + } + + g_bonetable[g_axisinterpbones[j].bone].flags |= BONE_ALWAYS_PROCEDURAL; // ??? what about physics rules + g_axisinterpbonemap[numaxisinterpbones++] = j; + } + g_numaxisinterpbones = numaxisinterpbones; + + // look for QuatInterp bone definitions + for( j = 0; j < g_numquatinterpbones; j++ ) + { + g_quatinterpbones[j].bone = findGlobalBoneXSI( g_quatinterpbones[j].bonename ); + g_quatinterpbones[j].control = findGlobalBoneXSI( g_quatinterpbones[j].controlname ); + + if( g_quatinterpbones[j].bone == -1 ) + { + MsgDev( D_WARN, "quatinterpbone \"%s\" unused\n", g_quatinterpbones[j].bonename ); + continue; // optimized out, don't complain + } + + if( g_quatinterpbones[j].control == -1 ) + { + COM_FatalError( "Missing control bone \"%s\" for procedural bone \"%s\"\n", + g_quatinterpbones[j].bonename, g_quatinterpbones[j].controlname ); + } + + g_bonetable[g_quatinterpbones[j].bone].flags |= BONE_ALWAYS_PROCEDURAL; // ??? what about physics rules + g_quatinterpbonemap[numquatinterpbones++] = j; + } + g_numquatinterpbones = numquatinterpbones; + + // look for AimAt bone definitions + for( j = 0; j < g_numaimatbones; j++ ) + { + g_aimatbones[j].bone = findGlobalBoneXSI( g_aimatbones[j].bonename ); + + if( g_aimatbones[j].bone == -1 ) + { + MsgDev( D_WARN, " \"%s\" unused\n", g_aimatbones[j].bonename ); + continue; // optimized out, don't complain + } + + g_aimatbones[j].parent = findGlobalBoneXSI( g_aimatbones[j].parentname ); + + if( g_aimatbones[j].parent == -1 ) + { + COM_FatalError( "Missing parent control bone \"%s\" for procedural bone \"%s\"\n", + g_aimatbones[j].parentname, g_aimatbones[j].bonename ); + } + + // Look for the aim bone as an attachment first + g_aimatbones[j].aimAttach = -1; + + for( int ai = 0; ai < g_numattachments; ai++ ) + { + if( !Q_strcmp( g_attachment[ai].name, g_aimatbones[j].aimname )) + { + g_aimatbones[j].aimAttach = ai; + break; + } + } + + if( g_aimatbones[j].aimAttach == -1 ) + { + g_aimatbones[j].aimBone = findGlobalBoneXSI( g_aimatbones[j].aimname ); + + if( g_aimatbones[j].aimBone == -1 ) + { + COM_FatalError( "Missing aim control attachment or bone \"%s\" for procedural bone \"%s\"\n", + g_aimatbones[j].aimname, g_aimatbones[j].bonename ); + } + } + + g_bonetable[g_aimatbones[j].bone].flags |= BONE_ALWAYS_PROCEDURAL; // ??? what about physics rules + g_aimatbonemap[numaimatbones++] = j; + } + g_numaimatbones = numaimatbones; + + // look for Jiggle bone definitions + for( j = 0; j < g_numjigglebones; j++ ) + { + g_jigglebones[j].bone = findGlobalBone( g_jigglebones[j].bonename ); + + if( g_jigglebones[j].bone == -1 ) + { + MsgDev( D_WARN, "jigglebone \"%s\" unused\n", g_jigglebones[j].bonename ); + continue; // optimized out, don't complain + } + + g_bonetable[g_jigglebones[j].bone].flags |= BONE_JIGGLE_PROCEDURAL; // ??? what about physics rules + g_jigglebonemap[numjigglebones++] = j; + } + g_numjigglebones = numjigglebones; +} + +//----------------------------------------------------------------------------- +// Purpose: convert original procedural bone info into correct values for existing skeleton +//----------------------------------------------------------------------------- +void RemapProceduralBones( void ) +{ + int j; + + // look for QuatInterp bone definitions + for( j = 0; j < g_numquatinterpbones; j++ ) + { + s_quatinterpbone_t *pInterp = &g_quatinterpbones[g_quatinterpbonemap[j]]; + int origParent = findGlobalBoneXSI( pInterp->parentname ); + int origControlParent = findGlobalBoneXSI( pInterp->controlparentname ); + + if( origParent == -1 ) + { + COM_FatalError( "procedural bone \"%s\", can't find orig parent \"%s\"\n\n", pInterp->bonename, pInterp->parentname ); + } + + if( origControlParent == -1 ) + { + COM_FatalError( "procedural bone \"%s\", can't find control parent \"%s\n\n", pInterp->bonename, pInterp->controlparentname ); + } + + if( g_bonetable[pInterp->bone].parent != origParent ) + { + COM_FatalError( "unknown procedural bone parent remapping\n" ); + } + + if( g_bonetable[pInterp->control].parent != origControlParent ) + { + COM_FatalError( "procedural bone \"%s\", parent remapping error, control parent was \"%s\", is now \"%s\"\n", + pInterp->bonename, pInterp->controlparentname, g_bonetable[g_bonetable[pInterp->control].parent].name ); + } + + // remap triggers and movements/rotations due to skeleton changes and realignment + for( int k = 0; k < pInterp->numtriggers; k++ ) + { + int parent = g_bonetable[pInterp->control].parent; + + // triggers are the "control" bone relative to the control's parent bone + if( parent != -1 ) + { + matrix3x4 invControlParentRealign = g_bonetable[parent].srcRealign.Invert(); + matrix3x4 srcControlParentBoneToPose = g_bonetable[parent].boneToPose.ConcatTransforms( invControlParentRealign ); + matrix3x4 srcControlRelative = matrix3x4( g_vecZero, pInterp->trigger[k] ); + matrix3x4 srcControlBoneToPose = srcControlParentBoneToPose.ConcatTransforms( srcControlRelative ); + matrix3x4 destControlParentBoneToPose = srcControlParentBoneToPose.ConcatTransforms( g_bonetable[parent].srcRealign ); + matrix3x4 destControlBoneToPose = srcControlBoneToPose.ConcatTransforms( g_bonetable[pInterp->control].srcRealign ); + matrix3x4 invDestControlParentBoneToPose = destControlParentBoneToPose.Invert(); + matrix3x4 destControlRelative = invDestControlParentBoneToPose.ConcatTransforms( destControlBoneToPose ); + + // FIXME: do revision + pInterp->trigger[k] = destControlRelative.GetQuaternion(); +#if 0 + Vector pos = srcControlRelative.GetOrigin(); + Vector rot = srcControlRelative.GetAngles(); + + Msg( "srcControlRelative : %7.2f %7.2f %7.2f\n", rot.x, rot.y, rot.z ); + + pos = destControlRelative.GetOrigin(); + rot = destControlRelative.GetAngles(); + + Msg( "destControlRelative : %7.2f %7.2f %7.2f\n", rot.x, rot.y, rot.z ); + + Msg( "\n" ); +#endif + } + + // movements are relative to the bone's parent + parent = g_bonetable[pInterp->bone].parent; + + if( parent != -1 ) + { + // Msg( "procedural bone \"%s\"\n", pInterp->bonename ); + // Msg( "pre : %7.2f %7.2f %7.2f\n", pInterp->pos[k].x, pInterp->pos[k].y, pInterp->pos[k].z ); + // get local transform + matrix3x4 srcParentRelative = matrix3x4( pInterp->pos[k] + pInterp->basepos, pInterp->quat[k] ); + + // get original boneToPose + matrix3x4 invSrcRealign = g_bonetable[parent].srcRealign.Invert(); + matrix3x4 origParentBoneToPose = g_bonetable[parent].boneToPose.ConcatTransforms( invSrcRealign ); + + // move bone adjustment into world position + matrix3x4 srcBoneToWorld = origParentBoneToPose.ConcatTransforms( srcParentRelative ); + + // calculate local transform + matrix3x4 parentPoseToBone = g_bonetable[parent].boneToPose.Invert(); + matrix3x4 destBoneToWorld =parentPoseToBone.ConcatTransforms( srcBoneToWorld ); + + // save out the local transform + pInterp->quat[k] = destBoneToWorld.GetQuaternion(); + pInterp->pos[k] = destBoneToWorld.GetOrigin(); + + pInterp->pos[k] += g_bonetable[pInterp->control].pos * pInterp->percentage; + + // Msg("post : %7.2f %7.2f %7.2f\n", pInterp->pos[k].x, pInterp->pos[k].y, pInterp->pos[k].z ); + } + + } + } + + // look for aimatbones + for( j = 0; j < g_numaimatbones; j++ ) + { + s_aimatbone_t *pAimAtBone = &g_aimatbones[g_aimatbonemap[j]]; + + int origParent = findGlobalBoneXSI( pAimAtBone->parentname ); + + if( origParent == -1 ) + { + COM_FatalError( " bone \"%s\", can't find parent bone \"%s\"\n\n", pAimAtBone->bonename, pAimAtBone->parentname ); + } + + int origAim = -1; + + for( int ai = 0; ai < g_numattachments; ++ai ) + { + if( !Q_strcmp( g_attachment[ai].name, pAimAtBone->aimname )) + { + origAim = ai; + break; + } + } + + if( origAim == -1 ) + { + COM_FatalError( " bone \"%s\", can't find aim bone \"%s\n\n", pAimAtBone->bonename, pAimAtBone->aimname ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: propogate procedural bone usage up its chain +//----------------------------------------------------------------------------- +void MarkProceduralBoneChain( void ) +{ + int k, fBoneFlags; + + // look for QuatInterp bone definitions + for( int j = 0; j < g_numquatinterpbones; j++ ) + { + s_quatinterpbone_t *pInterp = &g_quatinterpbones[g_quatinterpbonemap[j]]; + + fBoneFlags = g_bonetable[pInterp->bone].flags & BONE_USED_MASK; + + // propogate the procedural bone usage up its hierarchy + k = pInterp->control; + while( k != -1 ) + { + g_bonetable[k].flags |= fBoneFlags; + k = g_bonetable[k].parent; + } + + // propogate the procedural bone usage up its hierarchy + k = pInterp->bone; + while( k != -1 ) + { + g_bonetable[k].flags |= fBoneFlags; + k = g_bonetable[k].parent; + } + } +} + +void MapSourcesToGlobalBonetable( void ) +{ + int i, j, k; + + // map each source bone list to master list + for( i = 0; i < g_nummodels; i++ ) + { + for( k = 0; k < MAXSTUDIOSRCBONES; k++ ) + { + g_model[i]->boneGlobalToLocal[k] = -1; + g_model[i]->boneLocalToGlobal[k] = -1; + } + + for( j = 0; j < g_model[i]->numbones; j++ ) + { + k = findGlobalBone( g_model[i]->localBone[j].name ); + + if( k == -1 ) + { + int m = g_model[i]->localBone[j].parent; + while( m != -1 && ( k = findGlobalBone( g_model[i]->localBone[m].name )) == -1 ) + { + m = g_model[i]->localBone[m].parent; + } + + if( k == -1 ) k = 0; + + g_model[i]->boneLocalToGlobal[j] = k; + } + else + { + g_model[i]->boneLocalToGlobal[j] = k; + g_model[i]->boneGlobalToLocal[k] = j; + } + } + } + + // map each sequences bone list to master list + for( i = 0; i < g_numani; i++ ) + { + s_animation_t *panim = g_panimation[i]; + + for( k = 0; k < MAXSTUDIOSRCBONES; k++ ) + { + panim->boneGlobalToLocal[k] = -1; + panim->boneLocalToGlobal[k] = -1; + } + + for( j = 0; j < panim->numbones; j++ ) + { + k = findGlobalBone( panim->localBone[j].name ); + + if( k == -1 ) + { + int m = panim->localBone[j].parent; + while( m != -1 && ( k = findGlobalBone( panim->localBone[m].name )) == -1 ) + { + m = panim->localBone[m].parent; + } + + if( k == -1 ) k = 0; + + panim->boneLocalToGlobal[j] = k; + } + else + { + panim->boneLocalToGlobal[j] = k; + panim->boneGlobalToLocal[k] = j; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: go through bone and find any that arent aligned on the X axis +//----------------------------------------------------------------------------- +void RealignBones( void ) +{ + matrix3x4 boneToPose[MAXSTUDIOBONES]; + int childbone[MAXSTUDIOBONES]; + matrix3x4 poseToBone, bonematrix; + int i, j, k; + + for( j = 0; j < g_numbones; j++ ) + childbone[j] = -1; + + // force bones with IK rules to realign themselves + for( i = 0; i < g_numikchains; i++ ) + { + k = g_ikchain[i].link[0].bone; + if( childbone[k] == -1 || childbone[k] == g_ikchain[i].link[1].bone ) + { + childbone[k] = g_ikchain[i].link[1].bone; + } + else + { + COM_FatalError( "Trying to realign bone \"%s\" with two children \"%s\", \"%s\"\n", + g_bonetable[k].name, g_bonetable[childbone[k]].name, g_bonetable[g_ikchain[i].link[1].bone].name ); + } + + k = g_ikchain[i].link[1].bone; + if( childbone[k] == -1 || childbone[k] == g_ikchain[i].link[2].bone ) + { + childbone[k] = g_ikchain[i].link[2].bone; + } + else + { + COM_FatalError( "Trying to realign bone \"%s\" with two children \"%s\", \"%s\"\n", + g_bonetable[k].name, g_bonetable[childbone[k]].name, g_bonetable[g_ikchain[i].link[2].bone].name ); + } + } + + if( g_realignbones ) + { + int children[MAXSTUDIOBONES]; + + // count children + for( k = 0; k < g_numbones; k++ ) + { + children[k] = 0; + } + + for( k = 0; k < g_numbones; k++ ) + { + if( g_bonetable[k].parent != -1 ) + children[g_bonetable[k].parent]++; + } + + // if my parent bone only has one child, then tell it to align to me + for( k = 0; k < g_numbones; k++ ) + { + if( g_bonetable[k].parent != -1 && children[g_bonetable[k].parent] == 1 ) + childbone[g_bonetable[k].parent] = k; + } + } + + for( j = 0; j < g_numbones; j++ ) + boneToPose[j] = g_bonetable[j].boneToPose; + + // look for bones that aren't on a primary X axis + for( k = 0; k < g_numbones; k++ ) + { + if( !g_bonetable[k].bPreAligned && childbone[k] != -1 ) + { + float d = g_bonetable[childbone[k]].pos.Length(); + + // check to see that it's on positive X + if( d - g_bonetable[childbone[k]].pos.x > 0.01 ) + { + Vector forward, left, up; + Vector v2, v3; + + // calc X axis + g_bonetable[childbone[k]].boneToPose.GetOrigin( v2 ); + g_bonetable[k].boneToPose.GetOrigin( v3 ); + forward = (v2 - v3).Normalize(); + + // try to align to existing bone/boundingbox by finding most perpendicular + // existing axis and aligning the new Z axis to it. + Vector forward2 = boneToPose[k].GetForward(); + Vector left2 = boneToPose[k].GetRight(); // FIXME: wrong name + Vector up2 = boneToPose[k].GetUp(); + + float d1 = fabs( DotProduct( forward, forward2 )); + float d2 = fabs( DotProduct( forward, left2 )); + float d3 = fabs( DotProduct( forward, up2 )); + + if( d1 <= d2 && d1 <= d3 ) + { + up = CrossProduct( forward, forward2 ).Normalize(); + } + else if( d2 <= d1 && d2 <= d3 ) + { + up = CrossProduct( forward, left2 ).Normalize(); + } + else + { + up = CrossProduct( forward, up2 ).Normalize(); + } + + left = CrossProduct( up, forward ); + + // setup matrix + boneToPose[k].SetForward( forward ); + boneToPose[k].SetRight( left ); + boneToPose[k].SetUp( up ); + + // check orthonormality of matrix + d = fabs( DotProduct( forward, left )) + + fabs( DotProduct( left, up )) + + fabs( DotProduct( up, forward )) + + fabs( DotProduct( boneToPose[k].GetForward(), boneToPose[k].GetRight() )) + + fabs( DotProduct( boneToPose[k].GetRight(), boneToPose[k].GetUp() )) + + fabs( DotProduct( boneToPose[k].GetUp(), boneToPose[k].GetForward() )); + + if( d > 0.0001 ) + COM_FatalError( "error with realigning bone %s\n", g_bonetable[k].name ); + boneToPose[k].SetOrigin( v3 ); + } + } + } + + for( i = 0; i < g_numforcedrealign; i++ ) + { + k = findGlobalBone( g_forcedrealign[i].name ); + if( k == -1 ) + { + COM_FatalError( "unknown bone %s in $forcedrealign\n", g_forcedrealign[i].name ); + } + + matrix3x4 local = matrix3x4( g_vecZero, g_forcedrealign[i].rot ); + boneToPose[k] = boneToPose[k].ConcatTransforms( local ); + } + + // build realignment transforms + for( j = 0; j < g_numbones; j++ ) + { + if( g_bonetable[j].bPreAligned ) + continue; + + poseToBone = g_bonetable[j].boneToPose.Invert(); + g_bonetable[j].srcRealign = poseToBone.ConcatTransforms( boneToPose[j] ); + g_bonetable[j].boneToPose = boneToPose[j]; + } + + // rebuild default angles, position, etc. + for( j = 0; j < g_numbones; j++ ) + { + if( g_bonetable[j].bPreAligned ) + continue; + + if( g_bonetable[j].parent == -1 ) + { + bonematrix = g_bonetable[j].boneToPose; + } + else + { + // convert my transform into parent relative space + poseToBone = g_bonetable[g_bonetable[j].parent].boneToPose.Invert(); + bonematrix = poseToBone.ConcatTransforms( g_bonetable[j].boneToPose ); + } + + bonematrix.GetStudioTransform( g_bonetable[j].pos, g_bonetable[j].rot ); + } + + // build reference pose + for( j = 0; j < g_numbones; j++ ) + { + bonematrix = matrix3x4( g_bonetable[j].pos, g_bonetable[j].rot ); + + if( g_bonetable[j].parent == -1 ) g_bonetable[j].boneToPose = bonematrix; + else g_bonetable[j].boneToPose = g_bonetable[g_bonetable[j].parent].boneToPose.ConcatTransforms( bonematrix ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: find all the different bones used in all the source files and map everything +// to a common bonetable. +//----------------------------------------------------------------------------- +void RemapBones( void ) +{ + g_real_numbones = g_numbones; + g_real_numseq = g_numseq; + g_real_numani = g_numani; + + if( g_staticprop ) + { + MakeStaticProp( ); + } + else if ( g_centerstaticprop ) + { + MsgDev( D_WARN, "Ignoring option $autocenter. Only supported on $staticprop models!\n" ); + } + + TagUsedBones( ); + + RenameBones( ); + + BuildGlobalBonetable( ); + + BuildGlobalBoneToPose( ); + + EnforceHierarchy( ); + + // tag parent bones as being in the same way as their children + for( int k = 0; k < g_numbones; k++ ) + { + int n = g_bonetable[k].parent; + while( n != -1 ) + { + g_bonetable[n].flags |= g_bonetable[k].flags; + n = g_bonetable[n].parent; + } + } + + if( g_collapse_bones || g_numimportbones ) + { + CollapseBones( ); + } + + if( g_numbones >= MAXSTUDIOBONES ) + COM_FatalError( "Too many bones used in model, used %d, max %d\n", g_numbones, MAXSTUDIOBONES ); + + RebuildLocalPose( ); + + TagProceduralBones( ); + + MapSourcesToGlobalBonetable( ); +} + +//----------------------------------------------------------------------------- +// Purpose: convert source bone animation into global bone animation +//----------------------------------------------------------------------------- +void TranslateAnimations( const int boneGlobalToLocal[MAXSTUDIOSRCBONES], const matrix3x4 *srcBoneToWorld, matrix3x4 *destBoneToWorld ) +{ + for( int k = 0; k < g_numbones; k++ ) + { + int q = boneGlobalToLocal[k]; + matrix3x4 bonematrix; + + if( q == -1 ) + { + // unknown bone, copy over defaults + if( g_bonetable[k].parent >= 0 ) + { + bonematrix = matrix3x4( g_bonetable[k].pos, g_bonetable[k].rot ); + destBoneToWorld[k] = destBoneToWorld[g_bonetable[k].parent].ConcatTransforms( bonematrix ); + } + else + { + destBoneToWorld[k] = matrix3x4( g_bonetable[k].pos, g_bonetable[k].rot ); + } + } + else + { + destBoneToWorld[k] = srcBoneToWorld[q].ConcatTransforms( g_bonetable[k].srcRealign ); + } + } +} + +void RemapVertices( void ) +{ + int i, j; + + for( i = 0; i < g_nummodels; i++ ) + { + matrix3x4 srcBoneToWorld[MAXSTUDIOSRCBONES]; + matrix3x4 destBoneToWorld[MAXSTUDIOSRCBONES]; + Vector tmp1, tmp2, vdest, ndest; + s_model_t *pmodel = g_model[i]; + + BuildRawTransforms( pmodel, g_vecZero, g_radZero, srcBoneToWorld ); + TranslateAnimations( pmodel->boneGlobalToLocal, srcBoneToWorld, destBoneToWorld ); + + for( j = 0; j < pmodel->numsrcverts; j++ ) + { + vdest.Init(); + ndest.Init(); + + for( int n = 0; n < pmodel->srcvert[j].localWeight.numbones; n++ ) + { + + int q = pmodel->srcvert[j].localWeight.bone[n]; // src bone + int k = pmodel->boneLocalToGlobal[q]; // mapping to global bone + + if( k == -1 ) + { + vdest = pmodel->srcvert[j].vert; + ndest = pmodel->srcvert[j].norm; + break; // staticprop + } + + for( int m = 0; m < pmodel->srcvert[j].globalWeight.numbones; m++ ) + { + if( k == pmodel->srcvert[j].globalWeight.bone[m] ) + { + // bone got collapsed out + pmodel->srcvert[j].globalWeight.weight[m] += pmodel->srcvert[j].localWeight.weight[n]; + break; + } + } + + if( m == pmodel->srcvert[j].globalWeight.numbones ) + { + // add new bone + pmodel->srcvert[j].globalWeight.bone[m] = k; + pmodel->srcvert[j].globalWeight.weight[m] = pmodel->srcvert[j].localWeight.weight[n]; + pmodel->srcvert[j].globalWeight.numbones++; + } + + if( has_boneweights ) + { + // convert vertex into original models' bone local space + tmp1 = destBoneToWorld[k].VectorITransform( pmodel->srcvert[j].vert ); + // convert that into global world space using stardard pose + tmp2 = g_bonetable[k].boneToPose.VectorTransform( tmp1 ); + // accumulate + vdest += tmp2 * pmodel->srcvert[j].localWeight.weight[n]; + + // convert vertex into original models' bone local space + tmp1 = destBoneToWorld[k].VectorIRotate( pmodel->srcvert[j].norm ); + // convert that into global world space using stardard pose + tmp2 = g_bonetable[k].boneToPose.VectorRotate( tmp1 ); + // accumulate + ndest += tmp2 * pmodel->srcvert[j].localWeight.weight[n]; + } + else + { + vdest = g_bonetable[k].boneToPose.VectorITransform( pmodel->srcvert[j].vert ); + ndest = g_bonetable[k].boneToPose.VectorIRotate( pmodel->srcvert[j].norm ); + } + } + + pmodel->srcvert[j].vert = vdest; + pmodel->srcvert[j].norm = ndest.Normalize(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: make indexed vertex and normal arrays +//----------------------------------------------------------------------------- +void BuildVertexArrays( void ) +{ + for( int i = 0; i < g_nummodels; i++ ) + { + s_model_t *pmodel = g_model[i]; + + for( int j = 0; j < pmodel->nummesh; j++ ) + { + s_mesh_t *pmesh = pmodel->pmesh[j]; + + for( int k = 0; k < pmesh->numtris; k++ ) + { + s_trianglevert_t *ptriv = pmesh->triangle[k]; + + for( int q = 0; q < 3; q++ ) + { + ptriv[q].vertindex = LookupVertex( pmodel, &pmodel->srcvert[ptriv[q].vertindex] ); + ptriv[q].normindex = LookupNormal( pmodel, &pmodel->srcvert[ptriv[q].normindex] ); + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: convert source bone animation into global bone animation +//----------------------------------------------------------------------------- +void ConvertAnimation( s_animation_t const *panim, int frame, s_bone_t *dest ) +{ + matrix3x4 srcBoneToWorld[MAXSTUDIOSRCBONES]; + matrix3x4 destBoneToWorld[MAXSTUDIOSRCBONES]; + matrix3x4 destWorldToBone[MAXSTUDIOSRCBONES]; + matrix3x4 bonematrix; + int k; + + BuildRawTransforms( panim, frame, panim->adjust, panim->rotation, srcBoneToWorld ); + TranslateAnimations( panim->boneGlobalToLocal, srcBoneToWorld, destBoneToWorld ); + + for( k = 0; k < g_numbones; k++ ) + { + destWorldToBone[k] = destBoneToWorld[k].Invert(); + } + + // convert source_space_local_to_world transforms to shared_space_local_to_world transforms + for( k = 0; k < g_numbones; k++ ) + { + if( g_bonetable[k].parent == -1 ) + { + bonematrix = destBoneToWorld[k]; + } + else + { + // convert my transform into parent relative space + bonematrix = destWorldToBone[g_bonetable[k].parent].ConcatTransforms( destBoneToWorld[k] ); + } + + bonematrix.GetStudioTransform( dest[k].pos, dest[k].rot ); + clip_rotations( dest[k].rot ); + } +} + +void RemapAnimations( void ) +{ + int size = g_numbones * sizeof( s_bone_t ); + s_animation_t *panim; + int i, j, n; + + for( i = 0; i < g_numani; i++ ) + { + panim = g_panimation[i]; + n = panim->startframe - panim->source.startframe; + for( j = 0; j < panim->numframes; j++ ) + { + panim->sanim[j] = (s_bone_t *)Mem_Alloc( size ); + ConvertAnimation( panim, n + j, panim->sanim[j] ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: calculate the bone to world transforms for a processed animation +//----------------------------------------------------------------------------- +void CalcBoneTransforms( s_animation_t *panimation, s_animation_t *pbaseanimation, int frame, matrix3x4 *pBoneToWorld ) +{ + if( FBitSet( panimation->flags, STUDIO_LOOPING ) && panimation->numframes > 1 ) + { + while( frame >= ( panimation->numframes - 1 )) + frame = frame - (panimation->numframes - 1); + } + + if( frame < 0 || frame >= panimation->numframes ) + { + COM_FatalError( "requested out of range frame on animation \"%s\" : %d (%d)\n", panimation->name, frame, panimation->numframes ); + } + + for( int k = 0; k < g_numbones; k++ ) + { + matrix3x4 bonematrix; + Vector angle; + + if( !FBitSet( panimation->flags, STUDIO_DELTA )) + { + bonematrix = matrix3x4( panimation->sanim[frame][k].pos, panimation->sanim[frame][k].rot ); + } + else if( pbaseanimation ) + { + Vector4D q1, q2, q3; + Vector p3; + + AngleQuaternion( pbaseanimation->sanim[0][k].rot, q1 ); + AngleQuaternion( panimation->sanim[frame][k].rot, q2 ); + + float s = panimation->weight[k]; + + QuaternionMA( q1, s, q2, q3 ); + p3 = pbaseanimation->sanim[0][k].pos + s * panimation->sanim[frame][k].pos; + bonematrix = matrix3x4( p3, q3 ); + } + else + { + Vector4D q1, q2, q3; + Vector p3; + + AngleQuaternion( g_bonetable[k].rot, q1 ); + AngleQuaternion( panimation->sanim[frame][k].rot, q2 ); + + float s = panimation->weight[k]; + + QuaternionMA( q1, s, q2, q3 ); + p3 = pbaseanimation->sanim[0][k].pos + s * g_bonetable[k].pos; + + bonematrix = matrix3x4( p3, q3 ); + } + + if( g_bonetable[k].parent == -1 ) pBoneToWorld[k] = bonematrix; + else pBoneToWorld[k] = pBoneToWorld[g_bonetable[k].parent].ConcatTransforms( bonematrix ); + } +} + +void CalcBoneTransforms( s_animation_t *panimation, int frame, matrix3x4 *pBoneToWorld ) +{ + CalcBoneTransforms( panimation, g_panimation[0], frame, pBoneToWorld ); +} + +//----------------------------------------------------------------------------- +// Purpose: calculate the bone to world transforms for a processed animation +//----------------------------------------------------------------------------- +void CalcBoneTransformsCycle( s_animation_t *panimation, s_animation_t *pbaseanimation, float flCycle, matrix3x4 *pBoneToWorld ) +{ + float fFrame = flCycle * (panimation->numframes - 1); + int iFrame = (int)fFrame; + float s = (fFrame - iFrame); + + int iFrame1 = iFrame % (panimation->numframes - 1); + int iFrame2 = (iFrame + 1) % (panimation->numframes - 1); + + for( int k = 0; k < g_numbones; k++ ) + { + Vector4D q1, q2, q3; + matrix3x4 bonematrix; + Vector p3; + + AngleQuaternion( panimation->sanim[iFrame1][k].rot, q1 ); + AngleQuaternion( panimation->sanim[iFrame2][k].rot, q2 ); + QuaternionSlerp( q1, q2, s, q3 ); + + VectorLerp( panimation->sanim[iFrame1][k].pos, s, panimation->sanim[iFrame2][k].pos, p3 ); + + bonematrix = matrix3x4( p3, q3 ); + + if( g_bonetable[k].parent == -1 ) pBoneToWorld[k] = bonematrix; + else pBoneToWorld[k] = pBoneToWorld[g_bonetable[k].parent].ConcatTransforms( bonematrix ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: calculate the bone to world transforms for a processed sequence +//----------------------------------------------------------------------------- +void SlerpBones( Vector4D q1[MAXSTUDIOBONES], Vector pos1[MAXSTUDIOBONES], int sequence, const Vector4D q2[MAXSTUDIOBONES], const Vector pos2[MAXSTUDIOBONES], float s ) +{ + int i; + Vector4D q3, q4; + float s1, s2; + + s_sequence_t *pseqdesc = &g_sequence[sequence]; + + if( s <= 0.0f ) + { + return; + } + else if( s > 1.0f ) + { + s = 1.0f; + } + + if( pseqdesc->flags & STUDIO_DELTA ) + { + for( i = 0; i < g_numbones; i++ ) + { + + s2 = s * pseqdesc->weight[i]; // blend in based on this bones weight + if( s2 > 0.0 ) + { + if( pseqdesc->flags & STUDIO_POST ) + { + QuaternionMA( q1[i], s2, q2[i], q1[i] ); + // FIXME: are these correct? + pos1[i] = pos1[i] + pos2[i] * s2; + } + else + { + QuaternionSM( s2, q2[i], q1[i], q1[i] ); + // FIXME: are these correct? + pos1[i] = pos1[i] + pos2[i] * s2; + } + } + } + } + else + { + for( i = 0; i < g_numbones; i++ ) + { + s2 = s * pseqdesc->weight[i]; // blend in based on this animations weights + + if( s2 > 0.0f ) + { + s1 = 1.0f - s2; + + if( g_bonetable[i].flags & BONE_FIXED_ALIGNMENT ) + QuaternionSlerpNoAlign( q2[i], q1[i], s1, q3 ); + else QuaternionSlerp( q2[i], q1[i], s1, q3 ); + + pos1[i] = pos1[i] * s1 + pos2[i] * s2; + q1[i] = q3; + } + } + } +} + +void CalcPoseSingle( Vector pos[], Vector4D q[], int sequence, float frame ) +{ + s_sequence_t *pseqdesc = &g_sequence[sequence]; + s_animation_t *panim = pseqdesc->panim[0]; + + // FIXME: is this modulo correct? + int iframe = ((int)frame) % panim->numframes; + + for( int k = 0; k < g_numbones; k++ ) + { + // FIXME: this isn't doing a fractional frame + AngleQuaternion( panim->sanim[iframe][k].rot, q[k] ); + pos[k] = panim->sanim[iframe][k].pos; + } +} + +void AccumulatePose( Vector pos[], Vector4D q[], int sequence, float frame, float flWeight ) +{ + Vector pos2[MAXSTUDIOBONES]; + Vector4D q2[MAXSTUDIOBONES]; + + CalcPoseSingle( pos2, q2, sequence, frame ); + SlerpBones( q, pos, sequence, q2, pos2, flWeight ); + AccumulateSeqLayers( pos, q, sequence, frame, flWeight ); +} + +void AccumulateSeqLayers( Vector pos[], Vector4D q[], int sequence, float frame, float flWeight ) +{ + s_sequence_t *pseqdesc = &g_sequence[sequence]; + + for( int i = 0; i < pseqdesc->numautolayers; i++ ) + { + s_autolayer_t *pLayer = &pseqdesc->autolayer[i]; + + float layerFrame = frame; + float layerWeight = flWeight; + + if( pLayer->start != pLayer->end ) + { + float s = 1.0; + float index; + + if( !FBitSet( pLayer->flags, STUDIO_AL_POSE )) + { + index = frame; + } + else + { + int iPose = pLayer->pose; + if( iPose != -1 ) + { + index = 0; // undefined? + } + else + { + index = 0; + } + } + + if( index < pLayer->start ) + continue; + if( index >= pLayer->end ) + continue; + + if( index < pLayer->peak && pLayer->start != pLayer->peak ) + { + s = (index - pLayer->start) / (pLayer->peak - pLayer->start); + } + else if( index > pLayer->tail && pLayer->end != pLayer->tail ) + { + s = (pLayer->end - index) / (pLayer->end - pLayer->tail); + } + + if( pLayer->flags & STUDIO_AL_SPLINE ) + { + s = 3 * s * s - 2 * s * s * s; + } + + if(( pLayer->flags & STUDIO_AL_XFADE ) && ( frame > pLayer->tail )) + { + layerWeight = ( s * flWeight ) / ( 1 - flWeight + s * flWeight ); + } + else if( pLayer->flags & STUDIO_AL_NOBLEND ) + { + layerWeight = s; + } + else + { + layerWeight = flWeight * s; + } + + if( !FBitSet( pLayer->flags, STUDIO_AL_POSE )) + { + layerFrame = ((frame - pLayer->start) / (pLayer->end - pLayer->start)) * (g_sequence[pLayer->sequence].panim[0]->numframes - 1); + } + else + { + layerFrame = (frame / g_sequence[sequence].panim[0]->numframes - 1) * (g_sequence[pLayer->sequence].panim[0]->numframes - 1); + } + } + + AccumulatePose( pos, q, pLayer->sequence, layerFrame, layerWeight ); + } +} + +void CalcSeqTransforms( int sequence, int frame, matrix3x4 *pBoneToWorld ) +{ + Vector pos[MAXSTUDIOBONES]; + Vector4D q[MAXSTUDIOBONES]; + int k; + + for( k = 0; k < g_numbones; k++ ) + { + AngleQuaternion( g_bonetable[k].rot, q[k] ); + pos[k] = g_bonetable[k].pos; + } + + AccumulatePose( pos, q, sequence, frame, 1.0 ); + + for( k = 0; k < g_numbones; k++ ) + { + matrix3x4 bonematrix = matrix3x4( pos[k], q[k] ); + + if( g_bonetable[k].parent == -1 ) pBoneToWorld[k] = bonematrix; + else pBoneToWorld[k] = pBoneToWorld[g_bonetable[k].parent].ConcatTransforms( bonematrix ); + } +} + +void buildAnimationWeights( void ) +{ + int i, j, k; + + // relink animation weights + for( i = 0; i < g_numweightlist; i++ ) + { + if( i == 0 ) + { + // initialize weights + for( j = 0; j < g_numbones; j++ ) + { + if( g_bonetable[j].parent != -1 ) + { + // set child bones to uninitialized + g_weightlist[i].weight[j] = -1.0f; + } + else if( i == 0 ) + { + // set root bones to 1 + g_weightlist[i].weight[j] = 1.0f; + g_weightlist[i].posweight[j] = 1.0f; + } + } + } + else + { + // initialize weights + for( j = 0; j < g_numbones; j++ ) + { + if( g_bonetable[j].parent != -1 ) + { + // set child bones to uninitialized + g_weightlist[i].weight[j] = g_weightlist[0].weight[j]; + g_weightlist[i].posweight[j] = g_weightlist[0].posweight[j]; + } + else + { + // set root bones to 0 + g_weightlist[i].weight[j] = 0.0f; + g_weightlist[i].posweight[j] = 0.0f; + } + } + } + + // match up weights + for( j = 0; j < g_weightlist[i].numbones; j++ ) + { + k = findGlobalBone( g_weightlist[i].bonename[j] ); + if( k == -1 ) + { + COM_FatalError( "unknown bone reference '%s' in weightlist '%s'\n", + g_weightlist[i].bonename[j], g_weightlist[i].name ); + } + g_weightlist[i].weight[k] = g_weightlist[i].boneweight[j]; + g_weightlist[i].posweight[k] = g_weightlist[i].boneposweight[j]; + } + } + + for( i = 0; i < g_numweightlist; i++ ) + { + // copy weights forward + for( j = 0; j < g_numbones; j++ ) + { + if( g_weightlist[i].weight[j] < 0.0f ) + { + if( g_bonetable[j].parent != -1 ) + { + g_weightlist[i].weight[j] = g_weightlist[i].weight[g_bonetable[j].parent]; + g_weightlist[i].posweight[j] = g_weightlist[i].posweight[g_bonetable[j].parent]; + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: subtract each frame running interpolation of the first frame to the last frame +//----------------------------------------------------------------------------- +void linearDelta( s_animation_t *psrc, s_animation_t *pdest, int srcframe, int flags ) +{ + // create delta animations + s_bone_t src0[MAXSTUDIOSRCBONES]; + s_bone_t src1[MAXSTUDIOSRCBONES]; + int j, k; + + for( k = 0; k < g_numbones; k++ ) + { + src0[k].pos = psrc->sanim[0][k].pos; + src0[k].rot = psrc->sanim[0][k].rot; + src1[k].pos = psrc->sanim[srcframe][k].pos; + src1[k].rot = psrc->sanim[srcframe][k].rot; + } + + if( pdest->numframes == 1 ) + { + MsgDev( D_WARN, "%s too short for splinedelta\n", pdest->name ); + } + + for( k = 0; k < g_numbones; k++ ) + { + for( j = 0; j < pdest->numframes; j++ ) + { + float s = 1; + if( pdest->numframes > 1 ) + { + s = (float)j / (pdest->numframes - 1); + } + + // make it a spline curve + if( flags & STUDIO_AL_SPLINE ) + { + s = 3 * s * s - 2 * s * s * s; + } + + if( pdest->weight[k] > 0.0f ) + { + s_bone_t src; + + src.pos = src0[k].pos * (1 - s) + src1[k].pos * s; + QuaternionSlerp( src0[k].rot, src1[k].rot, s, src.rot ); + + // calc differences between two rotations + if( flags & STUDIO_AL_POST ) + { + // find pdest in src's reference frame + QuaternionSMAngles( -1.0f, src.rot, pdest->sanim[j][k].rot, pdest->sanim[j][k].rot ); + pdest->sanim[j][k].pos = pdest->sanim[j][k].pos - src.pos; + } + else + { + // find src in pdest's reference frame? + QuaternionMAAngles( pdest->sanim[j][k].rot, -1.0f, src.rot, pdest->sanim[j][k].rot ); + pdest->sanim[j][k].pos = src.pos - pdest->sanim[j][k].pos; + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: turn the animation into a lower fps encoded version +//----------------------------------------------------------------------------- +void reencodeAnimation( s_animation_t *panim, int frameskip ) +{ + int j, k, n; + + for( n = 1, j = frameskip; j < panim->numframes; j += frameskip ) + { + for( k = 0; k < g_numbones; k++ ) + { + panim->sanim[n][k] = panim->sanim[j][k]; + } + n++; + } + + panim->numframes = n; + panim->fps = panim->fps / frameskip; +} + +//----------------------------------------------------------------------------- +// Purpose: clip or pad the animation as nessesary to be a specified number of frames +//----------------------------------------------------------------------------- +void forceNumframes( s_animation_t *panim, int numframes ) +{ + int size = g_numbones * sizeof( s_bone_t ); + + // copy + for( int j = panim->numframes; j < numframes; j++ ) + { + panim->sanim[j] = (s_bone_t *)Mem_Alloc( size ); + memcpy( panim->sanim[j], panim->sanim[panim->numframes-1], size ); + } + panim->numframes = numframes; +} + +//----------------------------------------------------------------------------- +// Purpose: subtract each frame from the previous to calculate the animations derivative +//----------------------------------------------------------------------------- +void createDerivative( s_animation_t *panim, float scale ) +{ + s_bone_t orig[MAXSTUDIOSRCBONES]; + int j, k; + + j = panim->numframes - 1; + + if( FBitSet( panim->flags, STUDIO_LOOPING )) + j--; + + for( k = 0; k < g_numbones; k++ ) + { + orig[k].pos = panim->sanim[j][k].pos; + orig[k].rot = panim->sanim[j][k].rot; + } + + for( j = panim->numframes - 1; j >= 0; j-- ) + { + s_bone_t *psrc, *pdest; + Vector4D q; + + if( j - 1 >= 0 ) + psrc = panim->sanim[j-1]; + else psrc = orig; + pdest = panim->sanim[j]; + + for( k = 0; k < g_numbones; k++ ) + { + if( panim->weight[k] > 0.0f ) + { + // find pdest in src's reference frame + QuaternionSMAngles( -1, psrc[k].rot, pdest[k].rot, pdest[k].rot ); + pdest[k].pos = pdest[k].pos - psrc[k].pos; + + // rescale results (not sure what basis physics system is expecting) + AngleQuaternion( pdest[k].rot, q ); + QuaternionScale( q, scale, q ); + QuaternionAngle( q, pdest[k].rot ); + pdest[k].pos *= scale; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: subtract each frame from the previous to calculate the animations derivative +//----------------------------------------------------------------------------- +void clearAnimations( s_animation_t *panim ) +{ + panim->flags |= STUDIO_DELTA; + panim->flags |= STUDIO_ALLZEROS; + + panim->numframes = 1; + panim->startframe = 0; + panim->endframe = 1; + + for( int k = 0; k < g_numbones; k++ ) + { + panim->sanim[0][k].pos = Vector( 0, 0, 0 ); + panim->sanim[0][k].rot = Radian( 0, 0, 0 ); + panim->posweight[k] = 0.0f; + panim->weight[k] = 0.0f; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: remove all world rotation from a bone +//----------------------------------------------------------------------------- +void counterRotateBone( s_animation_t *panim, int iBone, Vector target ) +{ + matrix3x4 boneToWorld[MAXSTUDIOBONES]; + matrix3x4 defaultBoneToWorld; + Vector pos; + + defaultBoneToWorld = matrix3x4( g_vecZero, target ); + + for( int j = 0; j < panim->numframes; j++ ) + { + CalcBoneTransforms( panim, j, boneToWorld ); + + boneToWorld[iBone].GetOrigin( pos ); + defaultBoneToWorld.SetOrigin( pos ); + boneToWorld[iBone] = defaultBoneToWorld; + solveBone( panim, j, iBone, boneToWorld ); + } +} + +void setAnimationWeight( s_animation_t *panim, int index ) +{ + // copy weightlists to animations + for( int k = 0; k < g_numbones; k++ ) + { + panim->weight[k] = g_weightlist[index].weight[k]; + panim->posweight[k] = g_weightlist[index].posweight[k]; + } +} + +void addDeltas( s_animation_t *panim, int frame, float s, Vector delta_pos[], Vector4D delta_q[] ) +{ + for( int k = 0; k < g_numbones; k++ ) + { + if( panim->weight[k] > 0.0f ) + { + QuaternionSMAngles( s, delta_q[k], panim->sanim[frame][k].rot, panim->sanim[frame][k].rot ); + panim->sanim[frame][k].pos += delta_pos[k] * s; + } + } +} + +void extractUnusedMotion( s_animation_t *panim ) +{ + float motion[6]; + int type, j, k; + + type = panim->motiontype; + + for( k = 0; k < g_numbones; k++ ) + { + if( g_bonetable[k].parent == -1 ) + { + motion[0] = panim->sanim[0][k].pos.x; + motion[1] = panim->sanim[0][k].pos.y; + motion[2] = panim->sanim[0][k].pos.z; + motion[3] = panim->sanim[0][k].rot.x; + motion[4] = panim->sanim[0][k].rot.y; + motion[5] = panim->sanim[0][k].rot.z; + + for( j = 0; j < panim->numframes; j++ ) + { + if( type & STUDIO_X ) + panim->sanim[j][k].pos.x = motion[0]; + if( type & STUDIO_Y ) + panim->sanim[j][k].pos.y = motion[1]; + if( type & STUDIO_Z ) + panim->sanim[j][k].pos.z = motion[2]; + if( type & STUDIO_XR ) + panim->sanim[j][k].rot.x = motion[3]; + if( type & STUDIO_YR ) + panim->sanim[j][k].rot.y = motion[4]; + if( type & STUDIO_ZR ) + panim->sanim[j][k].rot.z = motion[5]; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: find the difference between the src and dest animations, then add that +// difference to all the frames of the dest animation. +//----------------------------------------------------------------------------- +void processMatch( s_animation_t *psrc, s_animation_t *pdest, int flags ) +{ + // process "match" + Vector delta_pos[MAXSTUDIOSRCBONES]; + Vector4D delta_q[MAXSTUDIOSRCBONES]; + int j, k; + + for( k = 0; k < g_numbones; k++ ) + { + if( flags ) delta_pos[k] = psrc->sanim[0][k].pos - pdest->sanim[0][k].pos; + QuaternionSM( -1.0f, pdest->sanim[0][k].rot, psrc->sanim[0][k].rot, delta_q[k] ); + } + + for( j = 0; j < pdest->numframes; j++ ) + { + for( k = 0; k < g_numbones; k++ ) + { + if( pdest->weight[k] > 0.0f ) + { + if( flags ) pdest->sanim[j][k].pos += delta_pos[k]; + QuaternionMAAngles( pdest->sanim[j][k].rot, 1.0f, delta_q[k], pdest->sanim[j][k].rot ); + } + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: blend the psrc animation overtop the pdest animation, but blend the +// quaternions in world space instead of parent bone space. +// Also, blend bone lengths, but only for non root animations. +//----------------------------------------------------------------------------- +void worldspaceBlend( s_animation_t *psrc, s_animation_t *pdest, int srcframe, int flags ) +{ + int j, k, n; + + // process "match" + Vector4D srcQ[MAXSTUDIOSRCBONES]; + Vector srcPos[MAXSTUDIOSRCBONES]; + matrix3x4 srcBoneToWorld[MAXSTUDIOBONES]; + matrix3x4 destBoneToWorld[MAXSTUDIOBONES]; + Vector tmp; + + if( !flags ) + { + CalcBoneTransforms( psrc, srcframe, srcBoneToWorld ); + for( k = 0; k < g_numbones; k++ ) + { + srcQ[k] = srcBoneToWorld[k].GetQuaternion(); + srcPos[k] = psrc->sanim[srcframe][k].pos; + } + } + + Vector4D targetQ, destQ; + + for( j = 0; j < pdest->numframes; j++ ) + { + if( flags ) + { + // pull from a looping source + float flCycle = (float)j / (pdest->numframes - 1); + flCycle += (float)srcframe / (psrc->numframes - 1); + CalcBoneTransformsCycle( psrc, psrc, flCycle, srcBoneToWorld ); + + for( k = 0; k < g_numbones; k++ ) + { + srcQ[k] = srcBoneToWorld[k].GetQuaternion(); + + n = g_bonetable[k].parent; + if( n == -1 ) + { + srcPos[k] = srcBoneToWorld[k].GetOrigin(); + } + else + { + matrix3x4 worldToBone = srcBoneToWorld[n].Invert(); + matrix3x4 local = worldToBone.ConcatTransforms( srcBoneToWorld[k] ); + srcPos[k] = local.GetOrigin(); + } + } + } + + + CalcBoneTransforms( pdest, j, destBoneToWorld ); + + for( k = 0; k < g_numbones; k++ ) + { + if( pdest->weight[k] > 0 ) + { + // blend the boneToWorld transforms in world space + destQ = destBoneToWorld[k].GetQuaternion(); + tmp = destBoneToWorld[k].GetOrigin(); + + QuaternionSlerp( destQ, srcQ[k], pdest->weight[k], targetQ ); + destBoneToWorld[k] = matrix3x4( tmp, targetQ ); + } + + // back solve + n = g_bonetable[k].parent; + if( n == -1 ) + { + destBoneToWorld[k].GetAngles( pdest->sanim[j][k].rot ); + + // FIXME: it's not clear if this should blend position or not....it'd be + // better if weight lists could do quat and pos independently. + } + else + { + matrix3x4 worldToBone = destBoneToWorld[n].Invert(); + matrix3x4 local = worldToBone.ConcatTransforms( destBoneToWorld[k] ); + local.GetAngles( pdest->sanim[j][k].rot ); + + // blend bone lengths (local space) + VectorLerp( pdest->sanim[j][k].pos, pdest->posweight[k], srcPos[k], pdest->sanim[j][k].pos ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: match one animations position/orientation to another animations position/orientation +//----------------------------------------------------------------------------- +void processAutoorigin( s_animation_t *psrc, s_animation_t *pdest, int motiontype, int srcframe, int destframe, int bone ) +{ + matrix3x4 srcBoneToWorld[MAXSTUDIOBONES]; + matrix3x4 destBoneToWorld[MAXSTUDIOBONES]; + matrix3x4 adjmatrix; + int j, k; + + CalcBoneTransforms( psrc, srcframe, srcBoneToWorld ); + CalcBoneTransforms( pdest, destframe, destBoneToWorld ); + + // find rotation + Radian rot( 0, 0, 0 ); + Vector srcPos = srcBoneToWorld[bone].GetOrigin(); + Vector destPos = destBoneToWorld[bone].GetOrigin(); + Vector4D q0 = srcBoneToWorld[bone].GetQuaternion(); + Vector4D q2 = destBoneToWorld[bone].GetQuaternion(); + + if( FBitSet( motiontype, STUDIO_LXR | STUDIO_LYR | STUDIO_LZR | STUDIO_XR | STUDIO_YR | STUDIO_ZR )) + { + Vector4D deltaQ2; + Vector4D q4; + Radian a3; + + QuaternionMA( q2, -1.0f, q0, deltaQ2 ); + + if( FBitSet( motiontype, STUDIO_LXR | STUDIO_XR )) + { + q4.Init( deltaQ2.x, 0, 0, deltaQ2.w ); + q4 = q4.Normalize(); + QuaternionAngle( q4, a3 ); + rot.x = a3.x; + } + + if( FBitSet( motiontype, STUDIO_LYR | STUDIO_YR )) + { + q4.Init( 0, deltaQ2.y, 0, deltaQ2.w ); + q4 = q4.Normalize(); + QuaternionAngle( q4, a3 ); + rot.y = a3.y; + } + + if( FBitSet( motiontype, STUDIO_LZR | STUDIO_ZR )) + { + q4.Init( 0, 0, deltaQ2.z, deltaQ2.w ); + q4 = q4.Normalize(); + QuaternionAngle( q4, a3 ); + rot.z = a3.z; + } + + if( FBitSet( motiontype, STUDIO_XR ) && FBitSet( motiontype, STUDIO_YR ) && FBitSet( motiontype, STUDIO_ZR )) + { + QuaternionAngle( deltaQ2, rot ); + } + } + + // find movement + Vector p0 = srcPos; + adjmatrix = matrix3x4( g_vecZero, rot ).Invert(); + Vector p2 = adjmatrix.VectorRotate( destPos ); + + Vector adj = p0 - p2; + + if( !FBitSet( motiontype, STUDIO_X | STUDIO_LX )) + adj.x = 0; + if( !FBitSet( motiontype, STUDIO_Y | STUDIO_LY )) + adj.y = 0; + if( !FBitSet( motiontype, STUDIO_Z | STUDIO_LZ )) + adj.z = 0; + + adjmatrix.SetOrigin( adj ); + + if( bone != g_rootIndex ) + { + MsgDev( D_REPORT, "%s aligning to %s - %.2f %.2f %.2f\n", pdest->name, g_bonetable[bone].name, adj.x, adj.y, adj.z ); + } + + for( k = 0; k < g_numbones; k++ ) + { + if( g_bonetable[k].parent == -1 ) + { + for( j = 0; j < pdest->numframes; j++ ) + { + matrix3x4 bonematrix = matrix3x4( pdest->sanim[j][k].pos, pdest->sanim[j][k].rot ); + bonematrix = adjmatrix.ConcatTransforms( bonematrix ); + bonematrix.GetStudioTransform( pdest->sanim[j][k].pos, pdest->sanim[j][k].rot ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: subtract one animaiton from animation to create an animation of the "difference" +//----------------------------------------------------------------------------- +void subtractBaseAnimations( s_animation_t *psrc, s_animation_t *pdest, int srcframe, int flags ) +{ + int j, k; + + // create delta animations + s_bone_t src[MAXSTUDIOSRCBONES]; + + if( srcframe >= psrc->numframes ) + { + COM_FatalError( "subtract frame %d out of range for %s\n", srcframe, psrc->name ); + } + + for( k = 0; k < g_numbones; k++ ) + { + src[k].pos = psrc->sanim[srcframe][k].pos; + src[k].rot = psrc->sanim[srcframe][k].rot; + } + + for( k = 0; k < g_numbones; k++ ) + { + for( j = 0; j < pdest->numframes; j++ ) + { + if( pdest->weight[k] > 0.0f ) + { + // calc differences between two rotations + if( FBitSet( flags, STUDIO_POST )) + { + // find pdest in src's reference frame + QuaternionSMAngles( -1.0f, src[k].rot, pdest->sanim[j][k].rot, pdest->sanim[j][k].rot ); + pdest->sanim[j][k].pos = pdest->sanim[j][k].pos - src[k].pos; + } + else + { + // find src in pdest's reference frame? + QuaternionMAAngles( pdest->sanim[j][k].rot, -1.0f, src[k].rot, pdest->sanim[j][k].rot ); + pdest->sanim[j][k].pos = src[k].pos - pdest->sanim[j][k].pos; + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: rotate the animation so that it's moving in the specified angle +//----------------------------------------------------------------------------- +void makeAngle( s_animation_t *panim, float angle ) +{ + float da = 0.0f; + + if( panim->numpiecewisekeys != 0 ) + { + // look for movement in total piecewise movement + Vector pos = panim->piecewisemove[panim->numpiecewisekeys-1].pos; + + if( pos[0] != 0 || pos[1] != 0 ) + { + float a = atan2( pos[1], pos[0] ) * (180 / M_PI); + da = angle - a; + } + + for( int i = 0; i < panim->numpiecewisekeys; i++ ) + { + panim->piecewisemove[i].pos = VectorYawRotate( panim->piecewisemove[i].pos, da ); + panim->piecewisemove[i].vector = VectorYawRotate( panim->piecewisemove[i].vector, da ); + } + } + else + { + // look for movement in root bone + Vector pos = panim->sanim[(panim->numframes - 1)][g_rootIndex].pos - panim->sanim[0][g_rootIndex].pos; + + if( pos[0] != 0 || pos[1] != 0 ) + { + float a = atan2( pos[1], pos[0] ) * (180 / M_PI); + da = angle - a; + } + } + + matrix3x4 rootxform; + matrix3x4 src, dest; + + rootxform = matrix3x4( g_vecZero, Vector( 0.0f, da, 0.0f )); + + for( int j = 0; j < panim->numframes; j++ ) + { + for( int k = 0; k < g_numbones; k++ ) + { + if( g_bonetable[k].parent == -1 ) + { + src = matrix3x4( panim->sanim[j][k].pos, panim->sanim[j][k].rot ); + dest = rootxform.ConcatTransforms( src ); + dest.GetStudioTransform( panim->sanim[j][k].pos, panim->sanim[j][k].rot ); + } + } + } + + // FIXME: not finished +} + +//----------------------------------------------------------------------------- +// Purpose: find the difference between the overlapping frames and spread out +// the difference over multiple frames. +// start: negative number, specifies how far back from the end to start blending +// end: positive number, specifies how many frames from the beginning to blend +//----------------------------------------------------------------------------- +void fixupLoopingDiscontinuities( s_animation_t *panim, int start, int end ) +{ + int j, k, m, n; + + // fix C0 errors on looping animations + m = panim->numframes - 1; + + Vector delta_pos[MAXSTUDIOSRCBONES]; + Vector4D delta_q[MAXSTUDIOSRCBONES]; + + // skip if there's nothing to smooth + if( m == 0 ) return; + + for( k = 0; k < g_numbones; k++ ) + { + delta_pos[k] = panim->sanim[m][k].pos - panim->sanim[0][k].pos; + QuaternionMA( panim->sanim[m][k].rot, -1, panim->sanim[0][k].rot, delta_q[k] ); + } + + // HACK: skip fixup for motion that'll be matched with linear extraction + // FIXME: remove when "global" extraction moved into normal ordered processing loop + for( k = 0; k < g_numbones; k++ ) + { + if( g_bonetable[k].parent == -1 ) + { + if( FBitSet( panim->motiontype, STUDIO_LX )) + delta_pos[k].x = 0.0f; + if( FBitSet( panim->motiontype, STUDIO_LY )) + delta_pos[k].y = 0.0f; + if( FBitSet( panim->motiontype, STUDIO_LZ )) + delta_pos[k].z = 0.0f; + // FIXME: add rotation + } + } + + // make sure loop doesn't exceed animation length + if(( end - start ) > panim->numframes ) + { + end = panim->numframes + start; + if( end < 0 ) + { + end = 0; + start = -(panim->numframes - 1); + } + } + + // FIXME: figure out S + float s = 0; + float nf = end - start; + + for( j = start + 1; j <= 0; j++ ) + { + n = j - start; + s = (n / nf); + s = 3 * s * s - 2 * s * s * s; + addDeltas( panim, m+j, -s, delta_pos, delta_q ); + } + + for( j = 0; j < end; j++ ) + { + n = end - j; + s = (n / nf); + s = 3 * s * s - 2 * s * s * s; + addDeltas( panim, j, s, delta_pos, delta_q ); + } +} + +void matchBlend( s_animation_t *pDestAnim, s_animation_t *pSrcAnimation, int iSrcFrame, int iDestFrame, int iPre, int iPost ) +{ + int j, k; + + if( FBitSet( pDestAnim->flags, STUDIO_LOOPING )) + { + iPre = Q_max( iPre, -pDestAnim->numframes ); + iPost = Q_min( iPost, pDestAnim->numframes ); + } + else + { + iPre = Q_max( iPre, -iDestFrame ); + iPost = Q_min( iPost, pDestAnim->numframes - iDestFrame ); + } + + Vector delta_pos[MAXSTUDIOSRCBONES]; + Vector4D delta_q[MAXSTUDIOSRCBONES]; + + for( k = 0; k < g_numbones; k++ ) + { + delta_pos[k] = pSrcAnimation->sanim[iSrcFrame][k].pos - pDestAnim->sanim[iDestFrame][k].pos; + QuaternionMA( pSrcAnimation->sanim[iSrcFrame][k].rot, -1, pDestAnim->sanim[iDestFrame][k].rot, delta_q[k] ); + } + + // HACK: skip fixup for motion that'll be matched with linear extraction + // FIXME: remove when "global" extraction moved into normal ordered processing loop + for( k = 0; k < g_numbones; k++ ) + { + if( g_bonetable[k].parent == -1 ) + { + if( FBitSet( pDestAnim->motiontype, STUDIO_LX )) + delta_pos[k].x = 0.0f; + if( FBitSet( pDestAnim->motiontype, STUDIO_LY )) + delta_pos[k].y = 0.0f; + if( FBitSet( pDestAnim->motiontype, STUDIO_LZ )) + delta_pos[k].z = 0.0f; + // FIXME: add rotation + } + } + + // FIXME: figure out S + float s = 0; + + for( j = iPre; j <= iPost; j++ ) + { + if( j < 0 ) + { + s = j / (float)(iPre - 1); + } + else + { + s = j / (float)(iPost + 1); + } + + s = SimpleSpline( 1 - s ); + k = iDestFrame + j; + + if( k < 0 ) + { + k += (pDestAnim->numframes - 1); + } + else + { + k = k % (pDestAnim->numframes - 1); + } + + addDeltas( pDestAnim, k, s, delta_pos, delta_q ); + + // make sure final frame of a looping animation matches frame 0 + if( FBitSet( pDestAnim->flags, STUDIO_LOOPING ) && k == 0 ) + { + addDeltas( pDestAnim, pDestAnim->numframes - 1, s, delta_pos, delta_q ); + } + } +} + +void forceAnimationLoop( s_animation_t *panim ) +{ + // force looping animations to be looping + if( FBitSet( panim->flags, STUDIO_LOOPING )) + { + int n = 0; + int m = panim->numframes - 1; + + for( int k = 0; k < g_numbones; k++ ) + { + int type = panim->motiontype; + + if( !FBitSet( type, STUDIO_LX )) + panim->sanim[m][k].pos[0] = panim->sanim[n][k].pos[0]; + if( !FBitSet( type, STUDIO_LY )) + panim->sanim[m][k].pos[1] = panim->sanim[n][k].pos[1]; + if( !FBitSet( type, STUDIO_LZ )) + panim->sanim[m][k].pos[2] = panim->sanim[n][k].pos[2]; + + if( !FBitSet( type, STUDIO_LXR )) + panim->sanim[m][k].rot[0] = panim->sanim[n][k].rot[0]; + if( !FBitSet( type, STUDIO_LYR )) + panim->sanim[m][k].rot[1] = panim->sanim[n][k].rot[1]; + if( !FBitSet( type, STUDIO_LZR )) + panim->sanim[m][k].rot[2] = panim->sanim[n][k].rot[2]; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: find the linear movement/rotation between two frames, subtract that +// out of the animation and add it back on as a "piecewise movement" command +// panim - current animation +// motiontype - what to extract +// iStartFrame - first frame to apply motion over +// iEndFrame - last end frame to apply motion over +// iSrcFrame - match refFrame against what frame of the current animation +// pRefAnim - reference animtion +// iRefFrame - frame of reference animation to match +//----------------------------------------------------------------------------- +void extractLinearMotion( s_animation_t *panim, int motiontype, int iStartFrame, int iEndFrame, int iSrcFrame, s_animation_t *pRefAnim, int iRefFrame ) +{ + matrix3x4 adjmatrix; + int j, k; + + // Can't extract motion with only 1 frame of animation! + if( panim->numframes <= 1 ) + { + COM_FatalError( "Can't extract motion from sequence %s (%s). Check your QC options!\n", panim->name, panim->filename ); + } + + if( panim->numpiecewisekeys >= MAXSTUDIOMOVEKEYS ) + { + COM_FatalError( "Too many piecewise movement keys in %s (%s)\n", panim->name, panim->filename ); + } + + if( iEndFrame > panim->numframes - 1 ) + iEndFrame = panim->numframes - 1; + + if( iSrcFrame > panim->numframes - 1 ) + iSrcFrame = panim->numframes - 1; + + if( iStartFrame >= iEndFrame ) + { + MsgDev( D_WARN, "Motion extraction ignored, no frames remaining in %s (%s)\n", panim->name, panim->filename ); + return; + } + + float fFrame = (iStartFrame + iSrcFrame) / 2.0f; + int iMidFrame = (int)fFrame; + float s = fFrame - iMidFrame; + + // find rotation + Radian rot( 0, 0, 0 ); + + if( FBitSet( motiontype, STUDIO_LXR | STUDIO_LYR | STUDIO_LZR )) + { + Vector4D q0, q1, q2, q4, q5; + + AngleQuaternion( pRefAnim->sanim[iRefFrame][g_rootIndex].rot, q0 ); + AngleQuaternion( panim->sanim[iMidFrame][g_rootIndex].rot, q1 ); // only used for rotation checking + AngleQuaternion( panim->sanim[iSrcFrame][g_rootIndex].rot, q2 ); + + Vector4D deltaQ1, deltaQ2; + + QuaternionMA( q1, -1.0f, q0, deltaQ1 ); + QuaternionMA( q2, -1.0f, q0, deltaQ2 ); + + // FIXME: this is still wrong, but it should be slightly more robust + Radian a3, a5; + + if( FBitSet( motiontype, STUDIO_LXR )) + { + q4.Init( deltaQ2.x, 0, 0, deltaQ2.w ); + q4 = q4.Normalize(); + QuaternionAngle( q4, a3 ); + rot.x = a3.x; + } + + if( FBitSet( motiontype, STUDIO_LYR )) + { + q4.Init( 0, deltaQ2.y, 0, deltaQ2.w ); + q4 = q4.Normalize(); + QuaternionAngle( q4, a3 ); + rot.y = a3.y; + } + + if( FBitSet( motiontype, STUDIO_LZR )) + { + q4.Init( 0, 0, deltaQ2.z, deltaQ2.w ); + q4 = q4.Normalize(); + QuaternionAngle( q4, a3 ); + + // check for possible rotations >180 degrees by looking at the + // halfway point and seeing if it's rotating a different direction + // than the shortest path to the end point + Radian a5; + q5.Init( 0, 0, deltaQ1.z, deltaQ1.w ); + q5 = q5.Normalize(); + QuaternionAngle( q5, a5 ); + if( a3.z > M_PI ) a5.z -= 2 * M_PI; + if( a3.z < -M_PI ) a5.z += 2 * M_PI; + if( a5.z > M_PI ) a5.z -= 2 * M_PI; + if( a5.z < -M_PI ) a5.z += 2 * M_PI; + if( a5.z > M_PI / 4 && a3.z < 0 ) + a3.z += 2 * M_PI; + if( a5.z < -M_PI / 4 && a3.z > 0 ) + a3.z -= 2*M_PI; + rot.z = a3.z; + } + } + + // find movement + Vector p0; + + adjmatrix = matrix3x4( g_vecZero, rot ); + p0 = adjmatrix.VectorRotate( pRefAnim->sanim[iRefFrame][g_rootIndex].pos ); + + Vector p2 = panim->sanim[iSrcFrame][g_rootIndex].pos; + Vector p1 = panim->sanim[iMidFrame][g_rootIndex].pos * (1.0f - s) + panim->sanim[iMidFrame+1][g_rootIndex].pos * s; + + p2 = p2 - p0; + p1 = p1 - p0; + + if( !FBitSet( motiontype, STUDIO_LX )) + { + p2.x = 0; + p1.x = 0; + } + + if( !FBitSet( motiontype, STUDIO_LY )) + { + p2.y = 0; + p1.y = 0; + } + + if( !FBitSet( motiontype, STUDIO_LZ )) + { + p2.z = 0; + p1.z = 0; + } + + float d1 = p1.Length(); + float d2 = p2.Length(); + + float v0 = -1.0f * d2 + 4 * d1; + float v1 = 3 * d2 - 4 * d1; + + MsgDev( D_REPORT, "%s : %d - %d : %.1f %.1f %.1f\n", panim->name, iStartFrame, iEndFrame, p2.x, p2.y, RAD2DEG( rot[2] )); + + int numframes = iEndFrame - iStartFrame + 1; + if( numframes < 1 ) return; + + float n = numframes - 1; + + if( FBitSet( motiontype, STUDIO_LINEAR )) + { + v0 = v1 = p2.Length(); + } + else if( v0 < 0.0f ) + { + v0 = 0.0; + v1 = p2.Length() * 2.0f; + } + else if( v1 < 0.0f ) + { + v0 = p2.Length() * 2.0f; + v1 = 0.0; + } + else if(( v0 + v1 ) > 0.01f && (fabs( v0 - v1 ) / ( v0 + v1 )) < 0.2f ) + { + // if they're within 10% of each other, assume no acceleration + v0 = v1 = p2.Length(); + } + + Vector v = p2.Normalize(); + Vector A, B, C; + + if( FBitSet( motiontype, STUDIO_QUADRATIC_MOTION )) + { + SolveInverseQuadratic( 0, 0, 0.5, p1.x, 1.0, p2.x, A.x, B.x, C.x ); + SolveInverseQuadratic( 0, 0, 0.5, p1.y, 1.0, p2.y, A.y, B.y, C.y ); + SolveInverseQuadratic( 0, 0, 0.5, p1.z, 1.0, p2.z, A.z, B.z, C.z ); + } + + Vector adjpos; + Radian adjangle; + matrix3x4 bonematrix; + + for( j = 0; j < numframes; j++ ) + { + float t = (j / n); + + if( FBitSet( motiontype, STUDIO_QUADRATIC_MOTION )) + { + adjpos.x = t * t * A.x + t * B.x + C.x; + adjpos.y = t * t * A.y + t * B.y + C.y; + adjpos.z = t * t * A.z + t * B.z + C.z; + } + else + { + adjpos = v * ( v0 * t + 0.5f * (v1 - v0) * t * t ); + } + + adjangle = rot * t; + adjmatrix = matrix3x4( adjpos, adjangle ).Invert(); + + for( k = 0; k < g_numbones; k++ ) + { + if( g_bonetable[k].parent == -1 ) + { + bonematrix = matrix3x4( panim->sanim[j+iStartFrame][k].pos, panim->sanim[j+iStartFrame][k].rot ); + bonematrix = adjmatrix.ConcatTransforms( bonematrix ); + bonematrix.GetStudioTransform( panim->sanim[j+iStartFrame][k].pos, panim->sanim[j+iStartFrame][k].rot ); + } + } + } + + // use adjmatrix form last frame + for( ; j + iStartFrame < panim->numframes; j++ ) + { + for( k = 0; k < g_numbones; k++ ) + { + if( g_bonetable[k].parent == -1 ) + { + bonematrix = matrix3x4( panim->sanim[j+iStartFrame][k].pos, panim->sanim[j+iStartFrame][k].rot ); + bonematrix = adjmatrix.ConcatTransforms( bonematrix ); + bonematrix.GetStudioTransform( panim->sanim[j+iStartFrame][k].pos, panim->sanim[j+iStartFrame][k].rot ); + } + } + } + + // create piecewise motion paths + s_linearmove_t *pmove = &panim->piecewisemove[panim->numpiecewisekeys++]; + + pmove->endframe = iEndFrame; + pmove->flags = motiontype; + + // concatinate xforms + if( panim->numpiecewisekeys > 1 ) + { + bonematrix = matrix3x4( adjpos, adjangle ); + adjmatrix = matrix3x4( pmove[-1].pos, pmove[-1].rot ); + bonematrix = adjmatrix.ConcatTransforms( bonematrix ); + bonematrix.GetStudioTransform( pmove[0].pos, pmove[0].rot ); + pmove->vector = pmove[0].pos - pmove[-1].pos; + } + else + { + VectorCopy( adjpos, pmove[0].pos ); + VectorCopy( adjangle, pmove[0].rot ); + pmove->vector = pmove[0].pos; + } + + pmove->vector = pmove->vector.Normalize(); + pmove->v0 = v0; + pmove->v1 = v1; + + if( iStartFrame == 0 && iSrcFrame == ( panim->numframes - 1 )) + panim->linearmovement = pmove[0].pos; // for goldsource movement +} + +//----------------------------------------------------------------------------- +// Purpose: process the "piecewise movement" commands and return where the animation +// would move to on a given frame (assuming frame 0 is at the origin) +//----------------------------------------------------------------------------- +Vector calcPosition( s_animation_t *panim, int iFrame ) +{ + Vector vecPos = g_vecZero; + + if( panim->numpiecewisekeys == 0 ) + return vecPos; + + if( panim->numframes == 1 ) + return vecPos; + + int iLoops = 0; + while( iFrame >= ( panim->numframes - 1 )) + { + iLoops++; + iFrame = iFrame - (panim->numframes - 1); + } + + float prevframe = 0.0f; + + for( int i = 0; i < panim->numpiecewisekeys; i++ ) + { + s_linearmove_t *pmove = &panim->piecewisemove[i]; + + if( pmove->endframe >= iFrame ) + { + float f = (iFrame - prevframe) / (pmove->endframe - prevframe); + float d = pmove->v0 * f + 0.5 * (pmove->v1 - pmove->v0) * f * f; + + vecPos = vecPos + d * pmove->vector; + + if( iLoops != 0 ) + { + s_linearmove_t *pmove = &panim->piecewisemove[panim->numpiecewisekeys - 1]; + vecPos = vecPos + iLoops * pmove->pos; + } + return vecPos; + } + else + { + prevframe = pmove->endframe; + vecPos = pmove->pos; + } + } + + return vecPos; +} + +//----------------------------------------------------------------------------- +// Purpose: calculate how far an animation travels between two frames +//----------------------------------------------------------------------------- +Vector calcMovement( s_animation_t *panim, int iFrom, int iTo ) +{ + Vector p1 = calcPosition( panim, iFrom ); + Vector p2 = calcPosition( panim, iTo ); + + return p2 - p1; +} + +//----------------------------------------------------------------------------- +// Purpose: try to calculate a "missing" frame of animation, i.e the overlapping frame +//----------------------------------------------------------------------------- +void fixupMissingFrame( s_animation_t *panim ) +{ + // the animations DIDN'T have the end frame the same as the start frame, so fudge it + int size = g_numbones * sizeof( s_bone_t ); + int j = panim->numframes; + + float scale = 1.0f / (j - 1.0f); + + panim->sanim[j] = (s_bone_t *)Mem_Alloc( size ); + + Vector deltapos; + + for( int k = 0; k < g_numbones; k++ ) + { + deltapos = panim->sanim[j-1][k].pos - panim->sanim[0][k].pos; + panim->sanim[j-1][k].pos += deltapos * scale; + panim->sanim[j][k].rot = panim->sanim[0][k].rot; + } + + panim->numframes = j + 1; +} + +//----------------------------------------------------------------------------- +// Purpose: shift the frames of the animation so that it starts on the desired frame +//----------------------------------------------------------------------------- +void realignLooping( s_animation_t *panim ) +{ + int j, k; + + // realign looping animations + if( panim->numframes > 1 && panim->looprestart ) + { + if( panim->looprestart >= panim->numframes ) + { + COM_FatalError( "loopstart (%d) out of range for animation %s (%d)", panim->looprestart, panim->name, panim->numframes ); + } + + for( k = 0; k < g_numbones; k++ ) + { + Vector shiftpos[MAXSTUDIOANIMATIONS]; + Radian shiftrot[MAXSTUDIOANIMATIONS]; + int n; + + // printf("%f %f %f\n", motion[0], motion[1], motion[2] ); + for( j = 0; j < panim->numframes - 1; j++ ) + { + n = (j + panim->looprestart) % (panim->numframes - 1); + shiftpos[j] = panim->sanim[n][k].pos; + shiftrot[j] = panim->sanim[n][k].rot; + } + + n = panim->looprestart; + j = panim->numframes - 1; + shiftpos[j] = panim->sanim[n][k].pos; + shiftrot[j] = panim->sanim[n][k].rot; + + for( j = 0; j < panim->numframes; j++ ) + { + panim->sanim[j][k].pos = shiftpos[j]; + panim->sanim[j][k].rot = shiftrot[j]; + } + } + } +} + +void OptimizeAnimations( void ) +{ + int i, j; + + // optimize animations + for (i = 0; i < g_numseq; i++) + { + for( j = 0; j < g_sequence[i].numevents; j++ ) + { + if( g_sequence[i].event[j].frame < g_sequence[i].panim[0]->startframe ) + { + MsgDev( D_WARN, "sequence %s has event (%d) before first frame (%d)\n", + g_sequence[i].name, g_sequence[i].event[j].frame, g_sequence[i].panim[0]->startframe ); + g_sequence[i].event[j].frame = g_sequence[i].panim[0]->startframe; + } + + if( g_sequence[i].event[j].frame > g_sequence[i].panim[0]->endframe ) + { + MsgDev( D_WARN, "sequence %s has event (%d) after last frame (%d)\n", + g_sequence[i].name, g_sequence[i].event[j].frame, g_sequence[i].panim[0]->endframe ); + g_sequence[i].event[j].frame = g_sequence[i].panim[0]->endframe; + } + } + + g_sequence[i].linearmovement = g_sequence[i].panim[0]->linearmovement; + g_sequence[i].frameoffset = g_sequence[i].panim[0]->startframe; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Acculumate quaternions and try to find the swept area of rotation +// so that a "midpoint" of the rotation area can be found +//----------------------------------------------------------------------------- +void findAnimQuaternionAlignment( int k, int i, Vector4D &qBase, Vector4D &qMin, Vector4D &qMax ) +{ + int j; + + AngleQuaternion( g_panimation[i]->sanim[0][k].rot, qBase ); + + qMin = qBase; + float dMin = 1.0; + qMax = qBase; + float dMax = 1.0; + + for( j = 1; j < g_panimation[i]->numframes; j++ ) + { + Vector4D q; + + AngleQuaternion( g_panimation[i]->sanim[j][k].rot, q ); + QuaternionAlign( qBase, q, q ); + + float d0 = DotProduct( q, qBase ); + float d1 = DotProduct( q, qMin ); + float d2 = DotProduct( q, qMax ); + + if( d1 >= d0 ) + { + if( d0 < dMin ) + { + qMin = q; + dMin = d0; + if( dMax == 1.0 ) + { + QuaternionMA( qBase, -0.01f, qMin, qMax ); + QuaternionAlign( qBase, qMax, qMax ); + } + } + } + else if( d2 >= d0 ) + { + if( d0 < dMax ) + { + qMax = q; + dMax = d0; + } + } + + QuaternionSlerpNoAlign( qMin, qMax, 0.5f, qBase ); + + dMin = DotProduct( qBase, qMin ); + dMax = DotProduct( qBase, qMax ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: For specific bones, try to find the total valid area of rotation so +// that their mid point of rotation can be used at run time to "pre-align" +// the quaternions so that rotations > 180 degrees don't get blended the +// "short way round". +//----------------------------------------------------------------------------- +void limitBoneRotations( void ) +{ + int i, j, k; + + for( i = 0; i < g_numlimitrotation; i++ ) + { + Vector4D qBase; + + k = findGlobalBone( g_limitrotation[i].name ); + if( k == -1 ) + { + COM_FatalError( "unknown bone \"%s\" in $limitrotation\n", g_limitrotation[i].name ); + } + + AngleQuaternion( g_bonetable[k].rot, qBase ); + + if( g_limitrotation[i].numseq == 0 ) + { + for( j = 0; j < g_numani; j++ ) + { + if( !FBitSet( g_panimation[j]->flags, STUDIO_DELTA ) && g_panimation[j]->numframes > 3 ) + { + Vector4D qBase2, qMin2, qMax2; + + findAnimQuaternionAlignment( k, j, qBase2, qMin2, qMax2 ); + QuaternionAdd( qBase, qBase2, qBase ); + } + } + + qBase = qBase.Normalize(); + } + else + { + for (j = 0; j < g_limitrotation[i].numseq; j++) + { + + } + } + + g_bonetable[k].qAlignment = qBase; + g_bonetable[k].flags |= BONE_FIXED_ALIGNMENT; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Realign the matrix so that its X axis points along the desired axis. +//----------------------------------------------------------------------------- +void AlignIKMatrix( matrix3x4 &mMat, const Vector &vAlignTo ) +{ + Vector tmp1, tmp2, tmp3; + + // Column 0 (X) becomes the vector. + tmp1 = vAlignTo.Normalize(); + mMat.SetForward( tmp1 ); + + // Column 1 (Y) is the cross of the vector and column 2 (Z). + tmp3 = mMat.GetUp(); + tmp2 = CrossProduct( tmp3, tmp1 ).Normalize(); + + // FIXME: check for X being too near to Z + mMat.SetRight( tmp2 ); + + // Column 2 (Z) is the cross of columns 0 (X) and 1 (Y). + tmp3 = CrossProduct( tmp1, tmp2 ); + mMat.SetUp( tmp3 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Solve Knee position for a known hip and foot location, and a known knee direction +//----------------------------------------------------------------------------- +bool solveIK( int iThigh, int iKnee, int iFoot, Vector &targetFoot, Vector &targetKneePos, Vector &targetKneeDir, matrix3x4 *pBoneToWorld ) +{ + Vector worldFoot, worldKnee, worldThigh; + + worldThigh = pBoneToWorld[iThigh].GetOrigin(); + worldKnee = pBoneToWorld[iKnee].GetOrigin(); + worldFoot = pBoneToWorld[iFoot].GetOrigin(); + + Vector ikFoot, ikTargetKnee, ikKnee; + + ikFoot = targetFoot - worldThigh; + ikKnee = targetKneePos - worldThigh; + + float l1 = (worldKnee - worldThigh).Length(); + float l2 = (worldFoot - worldKnee).Length(); + + // exaggerate knee targets for legs that are nearly straight + // FIXME: should be configurable, and the ikKnee should be from the original animation, not modifed + float d = (targetFoot - worldThigh).Length() - Q_min( l1, l2 ); + d = Q_max( l1 + l2, d ); + + // FIXME: too short knee directions cause trouble + d = d * 100.0f; + + ikTargetKnee = ikKnee + targetKneeDir * d; + + // too far away? (0.9998 is about 1 degree) + if( ikFoot.Length() > ( l1 + l2 ) * KNEEMAX_EPSILON ) + { + ikFoot = ikFoot.Normalize(); + ikFoot *= (l1 + l2) * KNEEMAX_EPSILON; + } + + // too close? + // limit distance to about an 80 degree knee bend + float minDist = Q_max( fabs( l1 - l2 ) * 1.15f, Q_min( l1, l2 ) * 0.15f ); + + if( ikFoot.Length() < minDist ) + { + // too close to get an accurate vector, just use original vector + ikFoot = (worldFoot - worldThigh); + ikFoot = ikFoot.Normalize(); + ikFoot *= minDist; + } + + CIKSolver ik; + + if( ik.solve( l1, l2, ikFoot, ikTargetKnee, ikKnee )) + { + matrix3x4 &mWorldThigh = pBoneToWorld[ iThigh ]; + matrix3x4 &mWorldKnee = pBoneToWorld[ iKnee ]; + matrix3x4 &mWorldFoot = pBoneToWorld[ iFoot ]; + + // build transformation matrix for thigh + AlignIKMatrix( mWorldThigh, ikKnee ); + AlignIKMatrix( mWorldKnee, ikFoot - ikKnee ); + + mWorldKnee[3][0] = ikKnee.x + worldThigh.x; + mWorldKnee[3][1] = ikKnee.y + worldThigh.y; + mWorldKnee[3][2] = ikKnee.z + worldThigh.z; + + mWorldFoot[3][0] = ikFoot.x + worldThigh.x; + mWorldFoot[3][1] = ikFoot.y + worldThigh.y; + mWorldFoot[3][2] = ikFoot.z + worldThigh.z; + + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Solve Knee position for a known hip and foot location, but no specific knee direction preference +//----------------------------------------------------------------------------- +bool solveIK( int iThigh, int iKnee, int iFoot, Vector &targetFoot, matrix3x4 *pBoneToWorld ) +{ + Vector worldFoot, worldKnee, worldThigh; + + worldThigh = pBoneToWorld[iThigh].GetOrigin(); + worldKnee = pBoneToWorld[iKnee].GetOrigin(); + worldFoot = pBoneToWorld[iFoot].GetOrigin(); + + Vector ikFoot, ikKnee; + + ikFoot = targetFoot - worldThigh; + ikKnee = worldKnee - worldThigh; + + float l1 = (worldKnee - worldThigh).Length(); + float l2 = (worldFoot - worldKnee).Length(); + float l3 = (worldFoot - worldThigh).Length(); + + // leg too straight to figure out knee? + if( l3 > (l1 + l2) * KNEEMAX_EPSILON ) + return false; + + Vector ikHalf = (worldFoot - worldThigh) * (l1 / l3); + + // FIXME: what to do when the knee completely straight? + Vector ikKneeDir = (ikKnee - ikHalf).Normalize(); + + return solveIK( iThigh, iKnee, iFoot, targetFoot, worldKnee, ikKneeDir, pBoneToWorld ); +} + +//----------------------------------------------------------------------------- +// Purpose: calc the influence of a ik rule for a specific point in the animation cycle +//----------------------------------------------------------------------------- +float IKRuleWeight( s_ikrule_t *pRule, float flCycle ) +{ + if( pRule->end > 1.0f && flCycle < pRule->start ) + { + flCycle = flCycle + 1.0f; + } + + float value = 0.0f; + if( flCycle < pRule->start ) + { + return 0.0f; + } + else if( flCycle < pRule->peak ) + { + value = (flCycle - pRule->start) / (pRule->peak - pRule->start); + } + else if( flCycle < pRule->tail ) + { + return 1.0f; + } + else if( flCycle < pRule->end ) + { + value = 1.0f - ((flCycle - pRule->tail) / (pRule->end - pRule->tail)); + } + + return 3.0f * value * value - 2.0f * value * value * value; +} + +//----------------------------------------------------------------------------- +// Purpose: Lock the ik target to a specific location in order to clean up bad animations (shouldn't be needed). +//----------------------------------------------------------------------------- +void fixupIKErrors( s_animation_t *panim, s_ikrule_t *pRule ) +{ + int k; + + if( pRule->start == 0 && pRule->peak == 0 && pRule->tail == 0 && pRule->end == 0 ) + { + pRule->tail = panim->numframes - 1; + pRule->end = panim->numframes - 1; + } + + // check for wrapping + if( pRule->peak < pRule->start ) + { + pRule->peak += panim->numframes - 1; + } + + if( pRule->tail < pRule->peak ) + { + pRule->tail += panim->numframes - 1; + } + + if( pRule->end < pRule->tail ) + { + pRule->end += panim->numframes - 1; + } + + if( pRule->contact == -1 ) + { + pRule->contact = pRule->peak; + } + + if( panim->numframes <= 1 ) + return; + + pRule->errorData.numerror = pRule->end - pRule->start + 1; + + switch( pRule->type ) + { + case IK_SELF: + break; + case IK_WORLD: + case IK_GROUND: + { + matrix3x4 boneToWorld[MAXSTUDIOBONES]; + + int bone = g_ikchain[pRule->chain].link[2].bone; + CalcBoneTransforms( panim, pRule->contact, boneToWorld ); + // FIXME: add in motion + + Vector footfall = boneToWorld[bone].GetOrigin(); + + for( k = 0; k < pRule->errorData.numerror; k++ ) + { + CalcBoneTransforms( panim, k + pRule->start, boneToWorld ); + + float cycle = (panim->numframes <= 1) ? 0 : (float)(k + pRule->start) / (panim->numframes - 1); + float s = IKRuleWeight( pRule, cycle ); + s = 1.0f; // FIXME - the weight rule is wrong + + Vector orig = boneToWorld[g_ikchain[pRule->chain].link[2].bone].GetOrigin(); + + Vector pos = (footfall + calcMovement( panim, k + pRule->start, pRule->contact )) * s + orig * (1.0 - s); + + solveIK( g_ikchain[pRule->chain].link[0].bone, g_ikchain[pRule->chain].link[1].bone, g_ikchain[pRule->chain].link[2].bone, pos, boneToWorld ); + solveBone( panim, k + pRule->start, g_ikchain[pRule->chain].link[0].bone, boneToWorld ); + solveBone( panim, k + pRule->start, g_ikchain[pRule->chain].link[1].bone, boneToWorld ); + solveBone( panim, k + pRule->start, g_ikchain[pRule->chain].link[2].bone, boneToWorld ); + } + } + } + + forceAnimationLoop( panim ); // !!! +} + +//----------------------------------------------------------------------------- +// Purpose: For specific bones, try to find the total valid area of rotation so +// that their mid point of rotation can be used at run time to "pre-align" +// the quaternions so that rotations > 180 degrees don't get blended the +// "short way round". +//----------------------------------------------------------------------------- +void limitIKChainLength( void ) +{ + matrix3x4 boneToWorld[MAXSTUDIOSRCBONES]; // bone transformation matrix + int i, j, k; + + for( k = 0; k < g_numikchains; k++ ) + { + Vector kneeDir = g_ikchain[k].link[0].kneeDir; + bool needsFixup = false; + bool hasKnees = false; + + if( kneeDir.Length() > 0.0f ) + { + hasKnees = true; + } + else + { + for( i = 0; i < g_numani; i++ ) + { + s_animation_t *panim = g_panimation[i]; + + if( FBitSet( panim->flags, STUDIO_DELTA )) + continue; + + if( FBitSet( panim->flags, STUDIO_HIDDEN )) + continue; + + for( j = 0; j < panim->numframes; j++ ) + { + CalcBoneTransforms( panim, j, boneToWorld ); + + Vector worldThigh; + Vector worldKnee; + Vector worldFoot; + + boneToWorld[g_ikchain[k].link[0].bone].GetOrigin( worldThigh ); + boneToWorld[g_ikchain[k].link[1].bone].GetOrigin( worldKnee ); + boneToWorld[g_ikchain[k].link[2].bone].GetOrigin( worldFoot ); + + float l1 = (worldKnee-worldThigh).Length(); + float l2 = (worldFoot-worldKnee).Length(); + float l3 = (worldFoot-worldThigh).Length(); + + Vector ikHalf = (worldFoot + worldThigh) * 0.5; + + // FIXME: what to do when the knee completely straight? + Vector ikKneeDir = (worldKnee - ikHalf).Normalize(); + // ikTargetKnee = ikKnee + ikKneeDir * l1; + + // leg too straight to figure out knee? + if( l3 > ( l1 + l2 ) * 0.999f ) + { + needsFixup = true; + } + else + { + // rotate knee into local space + Vector tmp = boneToWorld[g_ikchain[k].link[0].bone].VectorIRotate( ikKneeDir ); + float bend = (((DotProduct( worldThigh - worldKnee, worldFoot - worldKnee )) / (l1 * l3)) + 1.0f ) / 2.0f; + kneeDir += tmp * bend; + hasKnees = true; + } + } + } + } + + if( !needsFixup ) + continue; + + if( !hasKnees ) + { + MsgDev( D_WARN, "ik rules for %s but no clear knee direction\n", g_ikchain[k].name ); + continue; + } + + kneeDir = kneeDir.Normalize(); + g_ikchain[k].link[0].kneeDir = kneeDir; + MsgDev( D_REPORT, "knee %s %f %f %f\n", g_ikchain[k].name, kneeDir.x, kneeDir.y, kneeDir.z ); + } +} + +void MakeTransitions( void ) +{ + bool iHit = g_multistagegraph; + int i, j, k; + + // add in direct node transitions + for( i = 0; i < g_numseq; i++ ) + { + if( g_sequence[i].entrynode != g_sequence[i].exitnode ) + { + g_xnode[g_sequence[i].entrynode-1][g_sequence[i].exitnode-1] = g_sequence[i].exitnode; + if( g_sequence[i].nodeflags ) + g_xnode[g_sequence[i].exitnode-1][g_sequence[i].entrynode-1] = g_sequence[i].entrynode; + } + + if( g_sequence[i].entrynode > g_numxnodes ) + g_numxnodes = g_sequence[i].entrynode; + } + + // calculate multi-stage transitions + while( iHit ) + { + iHit = false; + for( i = 1; i <= g_numxnodes; i++ ) + { + for( j = 1; j <= g_numxnodes; j++ ) + { + // if I can't go there directly + if( i != j && g_xnode[i-1][j-1] == 0 ) + { + for( k = 1; k <= g_numxnodes; k++ ) + { + // but I found someone who knows how that I can get to + if( g_xnode[k-1][j-1] > 0 && g_xnode[i-1][k-1] > 0 ) + { + // then go to them + g_xnode[i-1][j-1] = -g_xnode[i-1][k-1]; + iHit = true; + break; + } + } + } + } + } + // reset previous pass so the links can be used in the next pass + for( i = 1; i <= g_numxnodes; i++ ) + { + for( j = 1; j <= g_numxnodes; j++ ) + { + g_xnode[i-1][j-1] = abs( g_xnode[i-1][j-1] ); + } + } + } + + // add in allowed "skips" + for( i = 0; i < g_numxnodeskips; i++ ) + { + g_xnode[g_xnodeskip[i][0]-1][g_xnodeskip[i][1]-1] = 0; + } + + if( g_dump_graph ) + { + for( j = 1; j <= g_numxnodes; j++ ) + { + Msg( "%2d : %s\n", j, g_xnodename[j] ); + } + Msg( " " ); + for( j = 1; j <= g_numxnodes; j++ ) + { + Msg( "%2d ", j ); + } + Msg( "\n" ); + + for( i = 1; i <= g_numxnodes; i++ ) + { + Msg( "%2d: ", i ); + for( j = 1; j <= g_numxnodes; j++ ) + { + Msg( "%2d ", g_xnode[i-1][j-1] ); + } + Msg( "\n" ); + } + } +} + +void processAnimations( void ) +{ + int i, j; + + // find global root bone. + if( Q_strlen( rootname )) + { + g_rootIndex = findGlobalBone( rootname ); + if( g_rootIndex == -1 ) g_rootIndex = 0; + } + + buildAnimationWeights( ); + + for( i = 0; i < g_numani; i++ ) + { + s_animation_t *panim = g_panimation[i]; + + extractUnusedMotion( panim ); // FIXME: this should be part of LinearMotion() + + setAnimationWeight( panim, 0 ); + + int startframe = 0; + + if( panim->fudgeloop ) + { + fixupMissingFrame( panim ); + } + + for( j = 0; j < panim->numcmds; j++ ) + { + s_animcmd_t *pcmd = &panim->cmds[j]; + + switch( pcmd->cmd ) + { + case CMD_WEIGHTS: + setAnimationWeight( panim, pcmd->weightlist.index ); + break; + case CMD_SUBTRACT: + panim->flags |= STUDIO_DELTA; + subtractBaseAnimations( pcmd->subtract.ref, panim, pcmd->subtract.frame, pcmd->subtract.flags ); + break; + case CMD_AO: + { + int bone = g_rootIndex; + if( pcmd->ao.pBonename != NULL ) + { + bone = findGlobalBone( pcmd->ao.pBonename ); + if( bone == -1 ) + { + COM_FatalError("unable to find bone %s to alignbone\n", pcmd->ao.pBonename ); + } + } + processAutoorigin( pcmd->ao.ref, panim, pcmd->ao.motiontype, pcmd->ao.srcframe, pcmd->ao.destframe, bone ); + } + break; + case CMD_MATCH: + processMatch( pcmd->match.ref, panim, false ); + break; + case CMD_FIXUP: + fixupLoopingDiscontinuities( panim, pcmd->fixuploop.start, pcmd->fixuploop.end ); + break; + case CMD_ANGLE: + makeAngle( panim, pcmd->angle.angle ); + break; + case CMD_IKFIXUP: + break; + case CMD_IKRULE: + // processed later + break; + case CMD_MOTION: + extractLinearMotion( panim, pcmd->motion.motiontype, startframe, pcmd->motion.iEndFrame, pcmd->motion.iEndFrame, panim, startframe ); + startframe = pcmd->motion.iEndFrame; + break; + case CMD_REFMOTION: + extractLinearMotion( panim, pcmd->motion.motiontype, startframe, pcmd->motion.iEndFrame, pcmd->motion.iSrcFrame, pcmd->motion.pRefAnim, pcmd->motion.iRefFrame ); + startframe = pcmd->motion.iEndFrame; + break; + case CMD_DERIVATIVE: + createDerivative( panim, pcmd->derivative.scale ); + break; + case CMD_NOANIMATION: + clearAnimations( panim ); + break; + case CMD_LINEARDELTA: + panim->flags |= STUDIO_DELTA; + linearDelta( panim, panim, panim->numframes - 1, pcmd->linear.flags ); + break; + case CMD_COMPRESS: + reencodeAnimation( panim, pcmd->compress.frames ); + break; + case CMD_NUMFRAMES: + forceNumframes( panim, pcmd->numframes.frames ); + break; + case CMD_COUNTERROTATE: + { + int bone = findGlobalBone( pcmd->counterrotate.pBonename ); + if( bone != -1 ) + { + Vector target; + + if( !pcmd->counterrotate.bHasTarget ) + { + matrix3x4 rootxform = matrix3x4( g_vecZero, panim->rotation ); + matrix3x4 defaultBoneToWorld; + defaultBoneToWorld = rootxform.ConcatTransforms( g_bonetable[bone].boneToPose ); + target = defaultBoneToWorld.GetAngles(); + } + else + { + target = Vector( pcmd->counterrotate.targetAngle ); + } + + counterRotateBone( panim, bone, target ); + } + else + { + COM_FatalError( "unable to find bone %s to counterrotate\n", pcmd->counterrotate.pBonename ); + } + } + break; + case CMD_WORLDSPACEBLEND: + worldspaceBlend( pcmd->world.ref, panim, pcmd->world.startframe, pcmd->world.loops ); + break; + case CMD_MATCHBLEND: + matchBlend( panim, pcmd->match.ref, pcmd->match.srcframe, pcmd->match.destframe, pcmd->match.destpre, pcmd->match.destpost ); + break; + } + } + + if( panim->motiontype ) + { + int lastframe; + + if( !FBitSet( panim->flags, STUDIO_LOOPING )) + { + // roll back 0.2 seconds to try to prevent popping + int frames = panim->fps * panim->motionrollback; + lastframe = Q_max( Q_min( startframe + 1, panim->numframes - 1 ), panim->numframes - frames - 1 ); + } + else + { + lastframe = panim->numframes - 1; + } + + extractLinearMotion( panim, panim->motiontype, startframe, lastframe, panim->numframes - 1, panim, startframe ); + startframe = panim->numframes - 1; + } + + realignLooping( panim ); + forceAnimationLoop( panim ); + } + + // merge weightlists + for( i = 0; i < g_numseq; i++ ) + { + for( int n = 0; n < g_numbones; n++ ) + { + g_sequence[i].weight[n] = 0.0f; + + for( int j = 0; j < g_sequence[i].groupsize[0]; j++ ) + { + for( int k = 0; k < g_sequence[i].groupsize[1]; k++ ) + { + int q = j + g_sequence[i].groupsize[0] * k; + g_sequence[i].weight[n] = Q_max( g_sequence[i].weight[n], g_sequence[i].panim[q]->weight[n] ); + } + } + } + } +} + +//----------------------------------------------------------------------------- +// CompressAnimations +//----------------------------------------------------------------------------- +static void CompressAnimations( void ) +{ + int i, j, k, n, m; + float v; + + // find scales for all bones + for( j = 0; j < g_numbones; j++ ) + { + for( k = 0; k < 6; k++ ) + { + float minv, maxv, scale; + + if( k < 3 ) + { + minv = -128.0f; + maxv = 128.0f; + } + else + { + minv = -M_PI / 8.0; + maxv = M_PI / 8.0; + } + + for( i = 0; i < g_numani; i++ ) + { + s_animation_t *panim = g_panimation[i]; + + for( n = 0; n < panim->numframes; n++ ) + { + switch( k ) + { + case 0: + case 1: + case 2: + if( panim->flags & STUDIO_DELTA ) v = panim->sanim[n][j].pos[k]; + else v = ( panim->sanim[n][j].pos[k] - g_bonetable[j].pos[k] ); + break; + case 3: + case 4: + case 5: + if( panim->flags & STUDIO_DELTA ) v = panim->sanim[n][j].rot[k-3]; + else v = ( panim->sanim[n][j].rot[k-3] - g_bonetable[j].rot[k-3] ); + clip_rotations( v ); + break; + } + + minv = Q_min( v, minv ); + maxv = Q_max( v, maxv ); + } + } + + if( minv < maxv ) + { + if( -minv > maxv ) + scale = minv / -32768.0f; + else scale = maxv / 32767.0f; + } + else + { + scale = 1.0f / 32.0f; + } + + switch( k ) + { + case 0: + case 1: + case 2: + g_bonetable[j].posscale[k] = scale; + break; + case 3: + case 4: + case 5: + g_bonetable[j].rotscale[k-3] = scale; + break; + } + } + } + + int changes = 0; + int total = 0; + + // reduce animations + for( i = 0; i < g_numani; i++ ) + { + s_animation_t *panim = g_panimation[i]; + + for( j = 0; j < g_numbones; j++ ) + { + if( FBitSet( g_bonetable[j].flags, BONE_ALWAYS_PROCEDURAL )) + continue; + + // skip bones that have no influence + if( panim->weight[j] < 0.001f ) + continue; + + for( k = 0; k < 6; k++ ) + { + mstudioanimvalue_t data[MAXSTUDIOANIMATIONS]; + mstudioanimvalue_t *pcount, *pvalue; + short value[MAXSTUDIOANIMATIONS]; + + if( panim->numframes <= 0 ) + COM_FatalError( "no animation frames: \"%s\"\n", panim->name ); + + // find deltas from default pose + for( n = 0; n < panim->numframes; n++ ) + { + s_bone_t *psrcdata = &panim->sanim[n][j]; + + switch( k ) + { + case 0: + case 1: + case 2: + if( panim->flags & STUDIO_DELTA ) + { + value[n] = psrcdata->pos[k] / g_bonetable[j].posscale[k]; + // pre-scale pos delta since format only has room for "overall" weight + float r = panim->posweight[j] / panim->weight[j]; + value[n] *= r; + } + else + { + v = ( psrcdata->pos[k] - g_bonetable[j].pos[k] ); + value[n] = v / g_bonetable[j].posscale[k]; + } + break; + case 3: + case 4: + case 5: + if( panim->flags & STUDIO_DELTA ) v = psrcdata->rot[k-3]; + else v = ( psrcdata->rot[k-3] - g_bonetable[j].rot[k-3] ); + clip_rotations( v ); + value[n] = v / g_bonetable[j].rotscale[k-3]; + break; + } + } + + // FIXME: this compression algorithm needs work + + // initialize animation RLE block + panim->numanim[j][k] = 0; + + memset( data, 0, sizeof( data )); + pcount = data; + pvalue = pcount + 1; + + pcount->num.valid = 1; + pcount->num.total = 1; + pvalue->value = value[0]; + pvalue++; + changes++; + total++; + + for( m = 1; m < n; m++ ) + { + if( pcount->num.total == 255 ) + { + // chain too long, force a new entry + pcount = pvalue; + pvalue = pcount + 1; + pcount->num.valid++; + pvalue->value = value[m]; + pvalue++; + changes++; + } + + // insert value if they're not equal, + // or if we're not on a run and the run is less than 3 units + else if( !cmp_animvalue( m, m - 1 ) || (( pcount->num.total == pcount->num.valid ) + && (( m < n - 1 ) && !cmp_animvalue( m, m + 1 )))) + { + if( pcount->num.total != pcount->num.valid ) + { + pcount = pvalue; + pvalue = pcount + 1; + } + pcount->num.valid++; + pvalue->value = value[m]; + pvalue++; + changes++; + } + pcount->num.total++; + total++; + } + + panim->numanim[j][k] = pvalue - data; + if( panim->numanim[j][k] == 2 && value[0] == 0 ) + { + panim->numanim[j][k] = 0; + } + else + { + size_t anim_size = ( pvalue - data ) * sizeof( mstudioanimvalue_t ); + panim->anim[j][k] = (mstudioanimvalue_t *)Mem_Alloc( anim_size ); + memmove( panim->anim[j][k], data, anim_size ); + } + } + } + } + + if( total != 0 ) + MsgDev( D_INFO, "animation compressed of %.1f%c at original size\n", ((float)changes / (float)total ) * 100.0f, '%' ); +} + +//----------------------------------------------------------------------------- +// Compress a single animation stream +//----------------------------------------------------------------------------- +static void CompressSingle( s_animationstream_t *pStream ) +{ + int k, n, m; + + if( pStream->numerror == 0 ) + return; + + for( k = 0; k < 6; k++ ) + { + float minv, maxv, scale; + Radian ang; + + if( k < 3 ) + { + minv = -128.0f; + maxv = 128.0f; + } + else + { + minv = -M_PI / 8.0; + maxv = M_PI / 8.0; + } + + for( n = 0; n < pStream->numerror; n++ ) + { + float v = 0.0f; + switch( k ) + { + case 0: + case 1: + case 2: + v = pStream->pError[n].pos[k]; + break; + case 3: + case 4: + case 5: + QuaternionAngle( pStream->pError[n].q, ang ); + v = ang[k-3]; + clip_rotations( v ); + break; + } + + minv = Q_min( v, minv ); + maxv = Q_max( v, maxv ); + } + + if( minv < maxv ) + { + if( -minv > maxv ) + scale = minv / -32768.0f; + else scale = maxv / 32767.0f; + } + else + { + scale = 1.0f / 32.0f; + } + + pStream->scale[k] = scale; + + mstudioanimvalue_t *pcount, *pvalue; + short value[MAXSTUDIOANIMATIONS]; + mstudioanimvalue_t data[MAXSTUDIOANIMATIONS]; + float v; + + // find deltas from default pose + for( n = 0; n < pStream->numerror; n++ ) + { + switch( k ) + { + case 0: // X Position + case 1: // Y Position + case 2: // Z Position + value[n] = pStream->pError[n].pos[k] / pStream->scale[k]; + break; + case 3: // X Rotation + case 4: // Y Rotation + case 5: // Z Rotation + QuaternionAngle( pStream->pError[n].q, ang ); + v = ang[k-3]; + clip_rotations( v ); + value[n] = v / pStream->scale[k]; + break; + } + } + + // initialize animation RLE block + pStream->numanim[k] = 0; + + memset( data, 0, sizeof( data )); + pcount = data; + pvalue = pcount + 1; + + pcount->num.valid = 1; + pcount->num.total = 1; + pvalue->value = value[0]; + pvalue++; + + // build a RLE of deltas from the default pose + for( m = 1; m < n; m++ ) + { + if( pcount->num.total == 255 ) + { + // chain too long, force a new entry + pcount = pvalue; + pvalue = pcount + 1; + pcount->num.valid++; + pvalue->value = value[m]; + pvalue++; + } + + // insert value if they're not equal, + // or if we're not on a run and the run is less than 3 units + else if( !cmp_animvalue( m, m - 1 ) || (( pcount->num.total == pcount->num.valid ) + && (( m < n - 1 ) && !cmp_animvalue( m, m + 1 )))) + { + if( pcount->num.total != pcount->num.valid ) + { + pcount = pvalue; + pvalue = pcount + 1; + } + pcount->num.valid++; + pvalue->value = value[m]; + pvalue++; + } + pcount->num.total++; + } + + pStream->numanim[k] = pvalue - data; + pStream->anim[k] = (mstudioanimvalue_t *)Mem_Alloc(( pvalue - data ) * sizeof( mstudioanimvalue_t )); + memmove( pStream->anim[k], data, (pvalue - data) * sizeof( mstudioanimvalue_t )); + } +} + +static void CalcSequenceBoundingBoxes( void ) +{ + int i, j, k; + int n, m; + + // find bounding box for each sequence + for( i = 0; i < g_numseq; i++ ) + { + Vector bmin, bmax; + + // find intersection box volume for each bone + ClearBounds( bmin, bmax ); + + s_animation_t *panim = g_panimation[i]; + + for( n = 0; n < panim->numframes; n++ ) + { + matrix3x4 bonetransform[MAXSTUDIOBONES]; // bone transformation matrix + matrix3x4 posetransform[MAXSTUDIOBONES]; // bone transformation matrix + matrix3x4 bonematrix; // local transformation matrix + Vector pos, tmp; + + for( j = 0; j < g_numbones; j++ ) + { + bonematrix = matrix3x4( panim->sanim[n][j].pos, panim->sanim[n][j].rot ); + if( g_bonetable[j].parent == -1 ) bonetransform[j] = bonematrix; + else bonetransform[j] = bonetransform[g_bonetable[j].parent].ConcatTransforms( bonematrix ); + + bonematrix = g_bonetable[j].boneToPose.Invert(); + posetransform[j] = bonetransform[j].ConcatTransforms( bonematrix ); + } + + // include bones as well. + for( k = 0; k < g_numbones; k++ ) + { + Vector tmpMin, tmpMax; + TransformAABB( bonetransform[k], g_bonetable[k].bmin, g_bonetable[k].bmax, tmpMin, tmpMax ); + AddPointToBounds( tmpMin, bmin, bmax ); + AddPointToBounds( tmpMax, bmin, bmax ); + } + + // include vertices + for( k = 0; k < g_nummodels; k++ ) + { + for( j = 0; j < g_model[k]->numsrcverts; j++ ) + { + s_srcvertex_t *v = &g_model[k]->srcvert[j]; + pos = g_vecZero; + + for( m = 0; m < v->globalWeight.numbones; m++ ) + { + if( has_boneweights ) + tmp = posetransform[v->globalWeight.bone[m]].VectorTransform( v->vert ); + else tmp = bonetransform[v->globalWeight.bone[m]].VectorTransform( v->vert ); + pos += tmp * v->globalWeight.weight[m]; + } + AddPointToBounds( pos, bmin, bmax ); + } + } + } + + panim->bmin = bmin; + panim->bmax = bmax; + } + + for( i = 0; i < g_numseq; i++ ) + { + Vector bmin, bmax; + + // find intersection box volume for each bone + ClearBounds( bmin, bmax ); + + for( j = 0; j < g_sequence[i].numblends; j++ ) + { + s_animation_t *panim = g_sequence[i].panim[j]; + AddPointToBounds( panim->bmin, bmin, bmax ); + AddPointToBounds( panim->bmax, bmin, bmax ); + } + + g_sequence[i].bmin = bmin; + g_sequence[i].bmax = bmax; + } +} + +//----------------------------------------------------------------------------- +// Links bone controllers +//----------------------------------------------------------------------------- +static void LinkBoneControllers( void ) +{ + for( int i = 0; i < g_numbonecontrollers; i++ ) + { + int j = findGlobalBone( g_bonecontroller[i].name ); + if( j == -1 ) + COM_FatalError( "unknown g_bonecontroller link '%s'\n", g_bonecontroller[i].name ); + g_bonecontroller[i].bone = j; + } +} + +//----------------------------------------------------------------------------- +// Find autolayers +//----------------------------------------------------------------------------- +static void FindAutolayers( void ) +{ + for( int i = 0; i < g_numseq; i++ ) + { + for( int k = 0; k < g_sequence[i].numautolayers; k++ ) + { + int j; + + for( j = 0; j < g_numseq; j++) + { + if( !Q_stricmp( g_sequence[i].autolayer[k].name, g_sequence[j].name )) + { + g_sequence[i].autolayer[k].sequence = j; + break; + } + } + + if( j == g_numseq ) + { + COM_FatalError( "sequence \"%s\" cannot find autolayer sequence \"%s\"\n", g_sequence[i].name, g_sequence[i].autolayer[k].name ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Links screen aligned bones +//----------------------------------------------------------------------------- +static void TagScreenAlignedBones( void ) +{ + for( int i = 0; i < g_numscreenalignedbones; i++ ) + { + int j = findGlobalBone( g_screenalignedbone[i].name ); + if( j == -1 ) + { + COM_FatalError( "unknown screenaligned bone link '%s'\n", g_screenalignedbone[i].name ); + } + + g_bonetable[j].flags |= g_screenalignedbone[i].flags; + MsgDev( D_REPORT, "tagging bone: %s as screen aligned (index %i, flags:%x)\n", g_bonetable[j].name, j, g_bonetable[j].flags ); + } +} + +//----------------------------------------------------------------------------- +// Links attachments +//----------------------------------------------------------------------------- +static void LinkAttachments( void ) +{ + int i, j, k; + + matrix3x4 boneToPose; + matrix3x4 world; + matrix3x4 poseToBone; + + // attachments may be connected to bones that can be optimized out + // so search through all the sources and move to a valid location + for( i = 0; i < g_numattachments; i++ ) + { + bool found = false; + + // search through known bones + k = findGlobalBone( g_attachment[i].bonename ); + if( k != -1 ) + { + g_attachment[i].bone = k; + boneToPose = g_bonetable[k].boneToPose; + poseToBone = boneToPose.Invert(); + found = true; + } + + if( !found ) + { + // search all the loaded sources for the first occurance of the named bone + for( j = 0; j < g_nummodels && !found; j++ ) + { + for( k = 0; k < g_model[j]->numbones && !found; k++ ) + { + if( !Q_stricmp( g_attachment[i].bonename, g_model[j]->localBone[k].name ) ) + { + boneToPose = g_model[j]->boneToPose[k]; + + // check to make sure that this bone is actually referenced in the output model + // if not, try parent bone until we find a referenced bone in this chain + while( k != -1 && g_model[j]->boneGlobalToLocal[g_model[j]->boneLocalToGlobal[k]] != k ) + { + k = g_model[j]->localBone[k].parent; + } + if( k == -1 ) + { + COM_FatalError( "unable to find valid bone for attachment %s:%s\n", + g_attachment[i].name, g_attachment[i].bonename ); + } + + poseToBone = g_model[j]->boneToPose[k].Invert(); + g_attachment[i].bone = g_model[j]->boneLocalToGlobal[k]; + found = true; + } + } + } + } + + if( !found ) COM_FatalError( "unknown attachment link '%s'\n", g_attachment[i].bonename ); + + if( g_attachment[i].type & IS_ABSOLUTE ) world = g_attachment[i].local; + else world = boneToPose.ConcatTransforms( g_attachment[i].local ); + + g_attachment[i].local = poseToBone.ConcatTransforms( world ); + } + + // flag all bones used by attachments + for( i = 0; i < g_numattachments; i++ ) + { + j = g_attachment[i].bone; + while( j != -1 ) + { + g_bonetable[j].flags |= BONE_USED_BY_ATTACHMENT; + j = g_bonetable[j].parent; + } + } +} + +static void SetupHitBoxes( void ) +{ + int i, j, k, n; + + // set hitgroups + for( k = 0; k < g_numbones; k++ ) + { + g_bonetable[k].group = -9999; + } + + for( j = 0; j < g_numhitgroups; j++ ) + { + k = findGlobalBone( g_hitgroup[j].name ); + if( k != -1 ) + { + g_bonetable[k].group = g_hitgroup[j].group; + } + else + { + COM_FatalError( "cannot find bone %s for hitgroup %d\n", g_hitgroup[j].name, g_hitgroup[j].group ); + } + } + + for( k = 0; k < g_numbones; k++ ) + { + if( g_bonetable[k].group == -9999 ) + { + if( g_bonetable[k].parent != -1 ) + g_bonetable[k].group = g_bonetable[g_bonetable[k].parent].group; + else g_bonetable[k].group = 0; + } + } + + if( g_hitboxsets.Size() == 0 ) + { + int index = g_hitboxsets.AddToTail(); + s_hitboxset_t *set = &g_hitboxsets[index]; + memset( set, 0, sizeof( *set )); + Q_strncpy( set->hitboxsetname, "default", sizeof( set->hitboxsetname )); + + // find intersection box volume for each bone + for( k = 0; k < g_numbones; k++ ) + { + g_bonetable[k].bmin = g_vecZero; + g_bonetable[k].bmax = g_vecZero; + } + + // try all the connect vertices + for( i = 0; i < g_nummodels; i++ ) + { + Vector p; + + for( j = 0; j < g_model[i]->numsrcverts; j++ ) + { + for( n = 0; n < g_model[i]->srcvert[j].globalWeight.numbones; n++ ) + { + k = g_model[i]->srcvert[j].globalWeight.bone[n]; + + if( has_boneweights ) + p = g_bonetable[k].boneToPose.VectorITransform( g_model[i]->srcvert[j].vert ); + else p = g_model[i]->srcvert[j].vert; + + if( p[0] < g_bonetable[k].bmin[0] ) g_bonetable[k].bmin[0] = p[0]; + if( p[1] < g_bonetable[k].bmin[1] ) g_bonetable[k].bmin[1] = p[1]; + if( p[2] < g_bonetable[k].bmin[2] ) g_bonetable[k].bmin[2] = p[2]; + if( p[0] > g_bonetable[k].bmax[0] ) g_bonetable[k].bmax[0] = p[0]; + if( p[1] > g_bonetable[k].bmax[1] ) g_bonetable[k].bmax[1] = p[1]; + if( p[2] > g_bonetable[k].bmax[2] ) g_bonetable[k].bmax[2] = p[2]; + } + } + } + // add in all your children as well + for( k = 0; k < g_numbones; k++ ) + { + if(( j = g_bonetable[k].parent ) != -1 ) + { + if( g_bonetable[k].pos[0] < g_bonetable[j].bmin[0] ) g_bonetable[j].bmin[0] = g_bonetable[k].pos[0]; + if( g_bonetable[k].pos[1] < g_bonetable[j].bmin[1] ) g_bonetable[j].bmin[1] = g_bonetable[k].pos[1]; + if( g_bonetable[k].pos[2] < g_bonetable[j].bmin[2] ) g_bonetable[j].bmin[2] = g_bonetable[k].pos[2]; + if( g_bonetable[k].pos[0] > g_bonetable[j].bmax[0] ) g_bonetable[j].bmax[0] = g_bonetable[k].pos[0]; + if( g_bonetable[k].pos[1] > g_bonetable[j].bmax[1] ) g_bonetable[j].bmax[1] = g_bonetable[k].pos[1]; + if( g_bonetable[k].pos[2] > g_bonetable[j].bmax[2] ) g_bonetable[j].bmax[2] = g_bonetable[k].pos[2]; + } + } + + for( k = 0; k < g_numbones; k++ ) + { + if( g_bonetable[k].bmin[0] < g_bonetable[k].bmax[0] - 1.0f + && g_bonetable[k].bmin[1] < g_bonetable[k].bmax[1] - 1.0f + && g_bonetable[k].bmin[2] < g_bonetable[k].bmax[2] - 1.0f ) + { + set->hitbox[set->numhitboxes].bone = k; + set->hitbox[set->numhitboxes].group = g_bonetable[k].group; + set->hitbox[set->numhitboxes].bmin = g_bonetable[k].bmin; + set->hitbox[set->numhitboxes].bmax = g_bonetable[k].bmax; + + if( g_dump_hboxes ) + { + Msg( "$hbox %d \"%s\" %.2f %.2f %.2f %.2f %.2f %.2f\n", + set->hitbox[set->numhitboxes].group, + g_bonetable[set->hitbox[set->numhitboxes].bone].name, + set->hitbox[set->numhitboxes].bmin[0], set->hitbox[set->numhitboxes].bmin[1], set->hitbox[set->numhitboxes].bmin[2], + set->hitbox[set->numhitboxes].bmax[0], set->hitbox[set->numhitboxes].bmax[1], set->hitbox[set->numhitboxes].bmax[2] ); + + } + set->numhitboxes++; + } + } + } + else + { + for( int s = 0; s < g_hitboxsets.Size(); s++ ) + { + s_hitboxset_t *set = &g_hitboxsets[s]; + + for( j = 0; j < set->numhitboxes; j++ ) + { + k = findGlobalBone( set->hitbox[j].name ); + + if( k != -1 ) + { + set->hitbox[j].bone = k; + } + else + { + COM_FatalError( "cannot find bone %s for bbox\n", set->hitbox[j].name ); + } + } + } + } +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +static float CalcPoseParameterValue( int control, Radian &angle, Vector &pos ) +{ + switch( control ) + { + case STUDIO_X: + return pos.x; + case STUDIO_Y: + return pos.y; + case STUDIO_Z: + return pos.z; + case STUDIO_XR: + return RAD2DEG( angle.x ); + case STUDIO_YR: + return RAD2DEG( angle.y ); + case STUDIO_ZR: + return RAD2DEG( angle.z ); + } + return 0.0f; +} + +static void CalcPoseParameters( void ) +{ + matrix3x4 boneToWorld[MAXSTUDIOBONES]; + Radian angles; + Vector pos; + + for( int i = 0; i < g_numseq; i++ ) + { + s_sequence_t *pseq = &g_sequence[i]; + + for( int iPose = 0; iPose < 2; iPose++ ) + { + if( pseq->groupsize[iPose] > 1 ) + { + if( pseq->paramattachment[iPose] != -1 ) + { + int j0 = pseq->paramindex[iPose]; + int n0 = pseq->paramattachment[iPose]; + int k0 = g_attachment[n0].bone; + + matrix3x4 boneToWorldRel; + matrix3x4 boneToWorldMid; + matrix3x4 worldToBoneMid; + matrix3x4 boneRel; + + if( pseq->paramanim == NULL ) + { + pseq->paramanim = g_panimation[0]; + } + + if( pseq->paramcompanim == NULL ) + { + pseq->paramcompanim = pseq->paramanim; + } + + // calculate what "zero" looks like to the attachment + CalcBoneTransforms( pseq->paramanim, 0, boneToWorld ); + boneToWorldMid = boneToWorld[k0].ConcatTransforms( g_attachment[n0].local ); + boneToWorldMid.GetStudioTransform( pos, angles ); + worldToBoneMid = boneToWorldMid.Invert(); + + MsgDev( D_REPORT, "%s : %s", pseq->name, g_pose[j0].name ); + + // for 2D animation, figure out what opposite row/column to use + // FIXME: make these 2D instead of 2 1D! + bool found = false; + int m[2]; + + if( pseq->paramcenter != NULL ) + { + for( int i0 = 0; !found && i0 < pseq->groupsize[0]; i0++ ) + { + for( int i1 = 0; !found && i1 < pseq->groupsize[1]; i1++ ) + { + int q = i0 + pseq->groupsize[0] * i1; + if( pseq->panim[q] == pseq->paramcenter ) + { + m[0] = i0; + m[1] = i1; + found = true; + } + } + } + } + + if( !found ) + { + m[1-iPose] = (pseq->groupsize[1-iPose]) / 2; + } + + // find changes to attachment + for( m[iPose] = 0; m[iPose] < pseq->groupsize[iPose]; m[iPose]++ ) + { + int q = m[0] + pseq->groupsize[0] * m[1]; + CalcBoneTransforms( pseq->panim[q], pseq->paramcompanim, 0, boneToWorld ); + boneToWorldRel = boneToWorld[k0].ConcatTransforms( g_attachment[n0].local ); + boneRel = worldToBoneMid.ConcatTransforms( boneToWorldRel ); + boneRel.GetStudioTransform( pos, angles ); + + float v = CalcPoseParameterValue( pseq->paramcontrol[iPose], angles, pos ); + + MsgDev( D_REPORT, " %6.2f", v ); + + if( iPose == 0 ) + { + pseq->param0[m[iPose]] = v; + } + else + { + pseq->param1[m[iPose]] = v; + } + + if( m[iPose] == 0 ) + { + pseq->paramstart[iPose] = (iPose == 0) ? pseq->param0[m[iPose]] : pseq->param1[m[iPose]]; + } + + if( m[iPose] == pseq->groupsize[iPose] - 1 ) + { + pseq->paramend[iPose] = (iPose == 0) ? pseq->param0[m[iPose]] : pseq->param1[m[iPose]]; + } + } + + MsgDev( D_REPORT, "\n" ); + + if( fabs( pseq->paramstart[iPose] - pseq->paramend[iPose] ) < 0.01 ) + { + COM_FatalError( "calcblend failed in %s\n", pseq->name ); + } + + g_pose[j0].min = Q_min( g_pose[j0].min, pseq->paramstart[iPose] ); + g_pose[j0].max = Q_max( g_pose[j0].max, pseq->paramstart[iPose] ); + g_pose[j0].min = Q_min( g_pose[j0].min, pseq->paramend[iPose] ); + g_pose[j0].max = Q_max( g_pose[j0].max, pseq->paramend[iPose] ); + } + else + { + for( int m = 0; m < pseq->groupsize[iPose]; m++ ) + { + float f = (m / (float)(pseq->groupsize[iPose] - 1.0)); + if( iPose == 0 ) + { + pseq->param0[m] = pseq->paramstart[iPose] * (1.0 - f) + pseq->paramend[iPose] * f; + } + else + { + pseq->param1[m] = pseq->paramstart[iPose] * (1.0 - f) + pseq->paramend[iPose] * f; + } + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Process IK links +//----------------------------------------------------------------------------- +s_ikrule_t *FindPrevIKRule( s_animation_t *panim, int iRule ) +{ + int i, j; + + s_ikrule_t *pRule = &panim->ikrule[iRule]; + + for( i = 1; i < panim->numikrules; i++ ) + { + j = ( iRule - i + panim->numikrules) % panim->numikrules; + if( panim->ikrule[j].chain == pRule->chain ) + return &panim->ikrule[j]; + } + + return pRule; +} + +s_ikrule_t *FindNextIKRule( s_animation_t *panim, int iRule ) +{ + int i, j; + + s_ikrule_t *pRule = &panim->ikrule[iRule]; + + for( i = 1; i < panim->numikrules; i++ ) + { + j = (iRule + i ) % panim->numikrules; + if( panim->ikrule[j].chain == pRule->chain ) + return &panim->ikrule[j]; + } + + return pRule; +} + +//----------------------------------------------------------------------------- +// Link ikchains +//----------------------------------------------------------------------------- +static void LinkIKChains( void ) +{ + int i, k; + + // create IK links + for( i = 0; i < g_numikchains; i++ ) + { + g_ikchain[i].numlinks = 3; + + k = findGlobalBone( g_ikchain[i].bonename ); + if( k == -1 ) + { + COM_FatalError( "unknown bone '%s' in ikchain '%s'\n", g_ikchain[i].bonename, g_ikchain[i].name ); + } + + g_ikchain[i].link[2].bone = k; + g_bonetable[k].flags |= BONE_USED_BY_ATTACHMENT; + + k = g_bonetable[k].parent; + if( k == -1 ) + { + COM_FatalError("ikchain '%s' too close to root, no parent knee/elbow\n", g_ikchain[i].name ); + } + + g_ikchain[i].link[1].bone = k; + g_bonetable[k].flags |= BONE_USED_BY_ATTACHMENT; + + k = g_bonetable[k].parent; + if( k == -1 ) + { + COM_FatalError("ikchain '%s' too close to root, no parent hip/shoulder\n", g_ikchain[i].name ); + } + + g_ikchain[i].link[0].bone = k; + g_bonetable[k].flags |= BONE_USED_BY_ATTACHMENT; + + // FIXME: search for toes + } +} + +//----------------------------------------------------------------------------- +// Link ikchains +//----------------------------------------------------------------------------- +static void LinkIKLocks( void ) +{ + int i, j, k; + + // create IK links + for( i = 0; i < g_numikautoplaylocks; i++ ) + { + for( j = 0; j < g_numikchains; j++ ) + { + if( !Q_stricmp( g_ikchain[j].name, g_ikautoplaylock[i].name )) + break; + } + + if( j == g_numikchains ) + { + COM_FatalError( "unknown chain '%s' in ikautoplaylock\n", g_ikautoplaylock[i].name ); + } + g_ikautoplaylock[i].chain = j; + } + + for( k = 0; k < g_numseq; k++ ) + { + for( i = 0; i < g_sequence[k].numiklocks; i++ ) + { + for( j = 0; j < g_numikchains; j++ ) + { + if( !Q_stricmp( g_ikchain[j].name, g_sequence[k].iklock[i].name )) + break; + } + + if( j == g_numikchains ) + { + COM_FatalError( "unknown chain '%s' in sequence iklock\n", g_sequence[k].iklock[i].name ); + } + g_sequence[k].iklock[i].chain = j; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: go through all the IK rules and calculate the animated path the IK'd +// end point moves relative to its IK target. +//----------------------------------------------------------------------------- +static void ProcessIKRules( void ) +{ + int i, j, k; + + // copy source animations + for( i = 0; i < g_numani; i++ ) + { + s_animation_t *panim = g_panimation[i]; + const char *pAnimationName = g_panimation[i]->name; + + for( j = 0; j < panim->numcmds; j++ ) + { + if( panim->cmds[j].cmd == CMD_IKFIXUP ) + { + fixupIKErrors( panim, panim->cmds[j].ikfixup.pRule ); + } + + if( panim->cmds[j].cmd != CMD_IKRULE ) + continue; + + if( panim->numikrules >= MAXSTUDIOIKRULES ) + { + COM_FatalError("Too many IK rules in %s (%s)\n", panim->name, panim->filename ); + } + + s_ikrule_t *pRule = &panim->ikrule[panim->numikrules++]; + + // make a copy of the rule; + *pRule = *panim->cmds[j].ikrule.pRule; + } + + for( j = 0; j < panim->numikrules; j++ ) + { + s_ikrule_t *pRule = &panim->ikrule[j]; + + if( pRule->start == 0 && pRule->peak == 0 && pRule->tail == 0 && pRule->end == 0 ) + { + pRule->tail = panim->numframes - 1; + pRule->end = panim->numframes - 1; + } + + if( pRule->start != -1 && pRule->peak == -1 && pRule->tail == -1 && pRule->end != -1 ) + { + pRule->peak = (pRule->start + pRule->end) / 2; + pRule->tail = (pRule->start + pRule->end) / 2; + } + + if( pRule->start != -1 && pRule->peak == -1 && pRule->tail != -1 ) + { + pRule->peak = (pRule->start + pRule->tail) / 2; + } + + if( pRule->peak != -1 && pRule->tail == -1 && pRule->end != -1 ) + { + pRule->tail = (pRule->peak + pRule->end) / 2; + } + + if( pRule->peak == -1 ) + { + pRule->start = 0; + pRule->peak = 0; + } + + if( pRule->tail == -1 ) + { + pRule->tail = panim->numframes - 1; + pRule->end = panim->numframes - 1; + } + + if( pRule->contact == -1 ) + { + pRule->contact = pRule->peak; + } + + // huh, make up start and end numbers + if( pRule->start == -1 ) + { + s_ikrule_t *pPrev = FindPrevIKRule( panim, j ); + + if( pPrev->slot == pRule->slot ) + { + if( pRule->peak < pPrev->tail ) + { + pRule->start = pRule->peak + (pPrev->tail - pRule->peak) / 2; + } + else + { + pRule->start = pRule->peak + (pPrev->tail - pRule->peak + panim->numframes - 1) / 2; + } + + pRule->start = (pRule->start + panim->numframes / 2) % (panim->numframes - 1); + pPrev->end = (pRule->start + panim->numframes - 1) % (panim->numframes - 1); + } + else + { + pRule->start = pPrev->tail; + pPrev->end = pRule->peak; + } + } + + // huh, make up start and end numbers + if( pRule->end == -1 ) + { + s_ikrule_t *pNext = FindNextIKRule( panim, j ); + + if( pNext->slot == pRule->slot ) + { + if( pNext->peak < pRule->tail ) + { + pNext->start = pNext->peak + (pRule->tail - pNext->peak) / 2; + } + else + { + pNext->start = pNext->peak + (pRule->tail - pNext->peak + panim->numframes - 1) / 2; + } + + pNext->start = (pNext->start + panim->numframes / 2) % (panim->numframes - 1); + pRule->end = (pNext->start + panim->numframes - 1) % (panim->numframes - 1); + } + else + { + pNext->start = pRule->tail; + pRule->end = pNext->peak; + } + } + + // check for wrapping + if( pRule->peak < pRule->start ) + { + pRule->peak += panim->numframes - 1; + } + + if( pRule->tail < pRule->peak ) + { + pRule->tail += panim->numframes - 1; + } + + if( pRule->end < pRule->tail ) + { + pRule->end += panim->numframes - 1; + } + + if( pRule->contact < pRule->start ) + { + pRule->contact += panim->numframes - 1; + } + + pRule->errorData.numerror = pRule->end - pRule->start + 1; + if( pRule->end >= panim->numframes ) + pRule->errorData.numerror = pRule->errorData.numerror + 2; + + pRule->errorData.pError = (s_streamdata_t *)Mem_Alloc( pRule->errorData.numerror * sizeof( s_streamdata_t )); + + int n = 0; + + if( pRule->usesequence ) + { + // FIXME: bah, this is horrendously hacky, add a damn back pointer + for( n = 0; n < g_numseq; n++ ) + { + if( g_sequence[n].panim[0] == panim ) + break; + } + } + + switch( pRule->type ) + { + case IK_SELF: + { + matrix3x4 boneToWorld[MAXSTUDIOBONES]; + matrix3x4 worldToBone; + matrix3x4 local; + + if( !Q_strlen( pRule->bonename )) + { + pRule->bone = -1; + } + else + { + pRule->bone = findGlobalBone( pRule->bonename ); + + if( pRule->bone == -1 ) + COM_FatalError( "unknown bone '%s' in ikrule\n", pRule->bonename ); + } + + for( k = 0; k < pRule->errorData.numerror; k++ ) + { + if( pRule->usesequence ) + { + CalcSeqTransforms( n, k + pRule->start, boneToWorld ); + } + else if( pRule->usesource ) + { + matrix3x4 srcBoneToWorld[MAXSTUDIOSRCBONES]; + BuildRawTransforms( panim, k + pRule->start + panim->startframe - panim->source.startframe, panim->adjust, panim->rotation, srcBoneToWorld ); + TranslateAnimations( panim->boneGlobalToLocal, srcBoneToWorld, boneToWorld ); + } + else + { + CalcBoneTransforms( panim, k + pRule->start, boneToWorld ); + } + + if( pRule->bone != -1 ) + { + worldToBone = boneToWorld[pRule->bone].Invert(); + local = worldToBone.ConcatTransforms( boneToWorld[g_ikchain[pRule->chain].link[2].bone] ); + } + else + { + local = boneToWorld[g_ikchain[pRule->chain].link[2].bone]; + } + + pRule->errorData.pError[k].q = local.GetQuaternion(); + pRule->errorData.pError[k].pos = local.GetOrigin(); + } + } + break; + case IK_WORLD: + break; + case IK_ATTACHMENT: + { + matrix3x4 boneToWorld[MAXSTUDIOBONES]; + matrix3x4 worldToBone; + matrix3x4 local; + + int bone = g_ikchain[pRule->chain].link[2].bone; + CalcBoneTransforms( panim, pRule->contact, boneToWorld ); + // FIXME: add in motion + + if( !Q_strlen( pRule->bonename )) + { + if( pRule->bone != -1 ) + { + pRule->bone = bone; + } + } + else + { + pRule->bone = findGlobalBone( pRule->bonename ); + if( pRule->bone == -1 ) + { + COM_FatalError( "unknown bone '%s' in ikrule\n", pRule->bonename ); + } + } + + if( pRule->bone != -1 ) + { + // FIXME: look for local bones... + CalcBoneTransforms( panim, pRule->contact, boneToWorld ); + pRule->q = boneToWorld[pRule->bone].GetQuaternion(); + pRule->pos = boneToWorld[pRule->bone].GetOrigin(); + } + + for( k = 0; k < pRule->errorData.numerror; k++ ) + { + int t = k + pRule->start; + + if( pRule->usesequence ) + { + CalcSeqTransforms( n, t, boneToWorld ); + } + else if( pRule->usesource ) + { + matrix3x4 srcBoneToWorld[MAXSTUDIOSRCBONES]; + BuildRawTransforms( panim, t + panim->startframe - panim->source.startframe, g_vecZero, g_radZero, srcBoneToWorld ); + TranslateAnimations( panim->boneGlobalToLocal, srcBoneToWorld, boneToWorld ); + } + else + { + CalcBoneTransforms( panim, t, boneToWorld ); + } + + Vector pos = pRule->pos + calcMovement( panim, t, pRule->contact ); + + local = matrix3x4( pos, pRule->q ); + worldToBone = local.Invert(); + + // calc position error + local = worldToBone.ConcatTransforms( boneToWorld[bone] ); + pRule->errorData.pError[k].q = local.GetQuaternion(); + pRule->errorData.pError[k].pos = local.GetOrigin(); + } + } + break; + case IK_GROUND: + { + matrix3x4 boneToWorld[MAXSTUDIOBONES]; + matrix3x4 worldToBone; + matrix3x4 local; + + int bone = g_ikchain[pRule->chain].link[2].bone; + + if( pRule->usesequence ) + { + CalcSeqTransforms( n, pRule->contact, boneToWorld ); + } + else if (pRule->usesource) + { + matrix3x4 srcBoneToWorld[MAXSTUDIOSRCBONES]; + BuildRawTransforms( panim, pRule->contact + panim->startframe, panim->adjust, panim->rotation, srcBoneToWorld ); + TranslateAnimations( panim->boneGlobalToLocal, srcBoneToWorld, boneToWorld ); + } + else + { + CalcBoneTransforms( panim, pRule->contact, boneToWorld ); + } + + // FIXME: add in motion + + Vector footfall = boneToWorld[bone].VectorTransform( g_ikchain[pRule->chain].center ); + footfall.z = pRule->floor; + + local = matrix3x4( footfall, g_radZero ); + worldToBone = local.Invert(); + + pRule->pos = footfall; + pRule->q = g_radZero; // auto conversion Radian->Quaternion + + float s; + + for( k = 0; k < pRule->errorData.numerror; k++ ) + { + int t = k + pRule->start; + + if( pRule->usesequence ) + { + CalcSeqTransforms( n, t, boneToWorld ); + } + else if( pRule->usesource ) + { + matrix3x4 srcBoneToWorld[MAXSTUDIOSRCBONES]; + BuildRawTransforms( panim, pRule->contact + panim->startframe, panim->adjust, panim->rotation, srcBoneToWorld ); + TranslateAnimations( panim->boneGlobalToLocal, srcBoneToWorld, boneToWorld ); + } + else + { + CalcBoneTransforms( panim, t, boneToWorld ); + } + + Vector pos = pRule->pos + calcMovement( panim, t, pRule->contact ); + s = 0.0; + + Vector cur = boneToWorld[bone].VectorTransform( g_ikchain[pRule->chain].center ); + cur.z = pos.z; + + if( t < pRule->start || t >= pRule->end ) + { + pos = cur; + } + else if( t < pRule->peak ) + { + s = (float)(pRule->peak - t) / (pRule->peak - pRule->start); + s = 3 * s * s - 2 * s * s * s; + pos = pos * (1 - s) + cur * s; + } + else if( t > pRule->tail ) + { + s = (float)(t - pRule->tail) / (pRule->end - pRule->tail); + s = 3 * s * s - 2 * s * s * s; + pos = pos * (1 - s) + cur * s; + } + + local = matrix3x4( pos, pRule->q ); + worldToBone = local.Invert(); + + // calc position error + local = worldToBone.ConcatTransforms( boneToWorld[bone] ); + pRule->errorData.pError[k].q = local.GetQuaternion(); + pRule->errorData.pError[k].pos = local.GetOrigin(); + } + } + break; + case IK_RELEASE: + case IK_UNLATCH: + break; + } + } + + if( FBitSet( panim->flags, STUDIO_DELTA ) || panim->noAutoIK ) + continue; + + // auto release ik chains that are moved but not referenced and have no explicit rules + int count[16]; + + for( j = 0; j < g_numikchains; j++ ) + { + count[j] = 0; + } + + for( j = 0; j < panim->numikrules; j++ ) + { + count[panim->ikrule[j].chain]++; + } + + for( j = 0; j < g_numikchains; j++ ) + { + if( count[j] == 0 && panim->weight[g_ikchain[j].link[2].bone] > 0.0f ) + { + k = panim->numikrules++; + panim->ikrule[k].chain = j; + panim->ikrule[k].slot = j; + panim->ikrule[k].type = IK_RELEASE; + panim->ikrule[k].start = 0; + panim->ikrule[k].peak = 0; + panim->ikrule[k].tail = panim->numframes - 1; + panim->ikrule[k].end = panim->numframes - 1; + } + } + } + + // realign IK across multiple animations + for( i = 0; i < g_numseq; i++ ) + { + for( j = 0; j < g_sequence[i].groupsize[0]; j++ ) + { + for( k = 0; k < g_sequence[i].groupsize[1]; k++ ) + { + int q = j + g_sequence[i].groupsize[0] * k; + g_sequence[i].numikrules = Q_max( g_sequence[i].numikrules, g_sequence[i].panim[q]->numikrules ); + } + } + + // check for mismatched ik rules + s_animation_t *panim1 = g_sequence[i].panim[0]; + for( j = 0; j < g_sequence[i].groupsize[0]; j++ ) + { + for( k = 0; k < g_sequence[i].groupsize[1]; k++ ) + { + int q = j + g_sequence[i].groupsize[0] * k; + s_animation_t *panim2 = g_sequence[i].panim[q]; + if( panim1->numikrules != panim2->numikrules ) + { + COM_FatalError( "%s - mismatched number of IK rules: \"%s\" \"%s\"\n", g_sequence[i].name, panim1->name, panim2->name ); + } + + for( int n = 0; n < panim1->numikrules; n++ ) + { + if(( panim1->ikrule[n].type != panim2->ikrule[n].type ) || + ( panim1->ikrule[n].chain != panim2->ikrule[n].chain ) || + ( panim1->ikrule[n].slot != panim2->ikrule[n].slot )) + { + COM_FatalError( "%s - mismatched IK rule %d: \n\"%s\" : %d %d %d\n\"%s\" : %d %d %d\n", + g_sequence[i].name, n, + panim1->name, panim1->ikrule[n].type, panim1->ikrule[n].chain, panim1->ikrule[n].slot, + panim2->name, panim2->ikrule[n].type, panim2->ikrule[n].chain, panim2->ikrule[n].slot ); + } + } + } + } + + // FIXME: this doesn't check alignment!!! + for( j = 0; j < g_sequence[i].groupsize[0]; j++ ) + { + for( k = 0; k < g_sequence[i].groupsize[1]; k++ ) + { + int q = j + g_sequence[i].groupsize[0] * k; + for( int n = 0; n < g_sequence[i].panim[q]->numikrules; n++ ) + { + g_sequence[i].panim[q]->ikrule[n].index = n; + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Compress all the IK data +//----------------------------------------------------------------------------- +static void CompressIKErrors( void ) +{ + // find scales for all bones + for( int i = 0; i < g_numani; i++ ) + { + for( int j = 0; j < g_panimation[i]->numikrules; j++ ) + { + s_ikrule_t *pRule = &g_panimation[i]->ikrule[j]; + + if( pRule->errorData.numerror == 0 ) + continue; + + CompressSingle( &pRule->errorData ); + } + } +} + +void SimplifyModel( void ) +{ + if( g_numseq == 0 ) + COM_FatalError( "model has no sequences\n" ); + + RemapBones( ); + + LinkIKChains(); + + LinkIKLocks(); + + RealignBones( ); + + RemapVertices( ); + + BuildVertexArrays( ); + + RemapAnimations( ); + + processAnimations( ); + + OptimizeAnimations( ); // FIXME: remove + + limitBoneRotations(); + + limitIKChainLength(); + + RemapProceduralBones( ); + + MakeTransitions( ); + + FindAutolayers(); + + LinkBoneControllers( ); + + // link screen aligned bones + TagScreenAlignedBones( ); + + LinkAttachments( ); + + // procedural bone needs to propagate its bone usage up its chain + // ensures runtime sets up dependent bone hierarchy + MarkProceduralBoneChain( ); + + ProcessIKRules(); + + CompressIKErrors( ); + + CalcPoseParameters(); + + SetupHitBoxes( ); + + CompressAnimations( ); + + CalcSequenceBoundingBoxes( ); + + // auto groups + if( g_numseqgroups == 1 && maxseqgroupsize < 1024 * 1024 ) + { + int i, j, k, q; + int current = 0; + + g_numseqgroups = 2; + + for( i = 0; i < g_numseq; i++ ) + { + int accum = 0; + + if( g_sequence[i].activity == 0 ) + { + for( q = 0; q < g_sequence[i].numblends; q++ ) + { + for( j = 0; j < g_numbones; j++ ) + { + for( k = 0; k < 6; k++ ) + { + accum += g_sequence[i].panim[q]->numanim[j][k] * sizeof( mstudioanimvalue_t ); + } + } + } + + accum += g_sequence[i].numblends * g_numbones * sizeof( mstudioanim_t ); + + if( current && current + accum > maxseqgroupsize ) + { + g_numseqgroups++; + current = accum; + } + else + { + current += accum; + } + // printf("%d %d %d\n", g_numseqgroups, current, accum ); + g_sequence[i].seqgroup = g_numseqgroups - 1; + } + else + { + g_sequence[i].seqgroup = 0; + } + } + } +} \ No newline at end of file diff --git a/utils/studiomdl/skin.cpp b/utils/studiomdl/skin.cpp new file mode 100644 index 0000000..13dd5ae --- /dev/null +++ b/utils/studiomdl/skin.cpp @@ -0,0 +1,464 @@ +/* +skin.cpp - process studio skins +Copyright (C) 2017 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include +#include "cmdlib.h" +#include "mathlib.h" +#include "stringlib.h" +#include "filesystem.h" +#include "studio.h" +#include "studiomdl.h" +#include "imagelib.h" +#include "builtin.h" + +void Grab_Skin( s_texture_t *ptexture ) +{ + bool use_default = false; + char file1[1024]; + rgbdata_t *pic; + + // g-cont. moved here to avoid some bugs + if( store_uv_coords && !FBitSet( ptexture->flags, STUDIO_NF_CHROME )) + SetBits( ptexture->flags, STUDIO_NF_UV_COORDS ); + + // use internal image + if( !Q_stricmp( ptexture->name, "#white.bmp" )) + { + pic = COM_LoadImageMemory( ptexture->name, white_bmp, sizeof( white_bmp )); + } + else + { + if( cdtextureset ) + { + int i; + + for( i = 0; i < cdtextureset; i++ ) + { + Q_snprintf( file1, sizeof( file1 ), "%s/%s", cdtexture[i], ptexture->name ); + if( COM_FileExists( file1 )) + break; + } + + if( i == cdtextureset ) + use_default = true; + } + else + { + Q_snprintf( file1, sizeof( file1 ), "%s/%s", cddir[numdirs], ptexture->name ); + if( !COM_FileExists( file1 )) + use_default = true; + } + + pic = COM_LoadImage( file1 ); + if( !pic ) MsgDev( D_ERROR, "unable to load %s\n", ptexture->name ); + } + + // use emo-texture + if( !pic ) pic = COM_LoadImageMemory( "default.bmp", default_bmp, sizeof( default_bmp )); + if( !pic ) COM_FatalError( "%s not found", file1 ); // ??? + + int new_width = Q_min( pic->width, store_uv_coords ? MIP_MAXWIDTH : 512 ); + int new_height = Q_min( pic->height, store_uv_coords ? MIP_MAXHEIGHT : 512 ); + + // resample to studio limits + pic = Image_Resample( pic, new_width, new_height ); + pic = Image_Quantize( pic ); // quantize if needs + + if( FBitSet( ptexture->flags, STUDIO_NF_MASKED )) + Image_MakeOneBitAlpha( pic ); // check alpha + + Image_ApplyGamma( pic ); // process gamma + ptexture->srcwidth = pic->width; + ptexture->srcheight = pic->height; + ptexture->psrc = pic; +} + +void ResizeTexture( s_texture_t *ptexture ) +{ + int i, j, s, t; + int srcadjwidth; + byte *pdest; + + // make the width a multiple of 4; some hardware requires this, and it ensures + // dword alignment for each scan + ptexture->skintop = ptexture->min_t; + ptexture->skinleft = ptexture->min_s; + ptexture->skinwidth = (int)((ptexture->max_s - ptexture->min_s) + 1 + 3) & ~3; + ptexture->skinheight = (int)(ptexture->max_t - ptexture->min_t) + 1; + + ptexture->size = ptexture->skinwidth * ptexture->skinheight + 256 * 3; + + Msg( "BMP %s [%d %d] (%.0f%%) %6d bytes\n", ptexture->name, ptexture->skinwidth, ptexture->skinheight, + ((ptexture->skinwidth * ptexture->skinheight) / (float)(ptexture->srcwidth * ptexture->srcheight)) * 100.0, ptexture->size ); + + if( ptexture->size > 1024 * 1024 + 256 * 3 ) + { + MsgDev( D_ERROR, "%.0f %.0f %.0f %.0f\n", ptexture->min_s, ptexture->max_s, ptexture->min_t, ptexture->max_t ); + COM_FatalError( "texture too large\n" ); + } + + pdest = (byte *)Mem_Alloc( ptexture->size ); + ptexture->pdata = pdest; + + // data is saved as a multiple of 4 + // NOTE: g-cont. we don't need to align width: it's already aligned +// srcadjwidth = (ptexture->srcwidth + 3) & ~3; + srcadjwidth = ptexture->srcwidth; + t = ptexture->srcheight - ptexture->skinheight - ptexture->skintop + 10 * ptexture->srcheight; + + // move the picture data to the model area, replicating missing data, deleting unused data. + for( i = 0; i < ptexture->skinheight; i++, t++ ) + { + while( t >= ptexture->srcheight ) + t -= ptexture->srcheight; + while( t < 0 ) + t += ptexture->srcheight; + + for( j = 0, s = ptexture->skinleft + 10 * ptexture->srcwidth; j < ptexture->skinwidth; j++, s++ ) + { + while( s >= ptexture->srcwidth ) + s -= ptexture->srcwidth; + *(pdest++) = *(ptexture->psrc->buffer + s + t * srcadjwidth); + } + } + + for( i = 0; i < 256; i++ ) + { + // convert 32-bit palette to 24 bit palette + pdest[i*3+0] = ptexture->psrc->palette[i*4+0]; + pdest[i*3+1] = ptexture->psrc->palette[i*4+1]; + pdest[i*3+2] = ptexture->psrc->palette[i*4+2]; + } + + Mem_Free( ptexture->psrc ); + ptexture->psrc = NULL; +} + +float adjust_texcoord( float coord ) +{ + if((coord - floor( coord )) > 0.5 ) + return ceil( coord ); + return floor( coord ); +} + +// called for the base frame +void TextureCoordRanges( s_mesh_t *pmesh, s_texture_t *ptexture ) +{ + int i, j; + + if( FBitSet( ptexture->flags, STUDIO_NF_CHROME )) + { + ptexture->skintop = 0; + ptexture->skinleft = 0; + ptexture->skinwidth = (ptexture->srcwidth + 3) & ~3; + ptexture->skinheight = ptexture->srcheight; + + for( i = 0; i < pmesh->numtris; i++ ) + { + for( j = 0; j < 3; j++ ) + { + pmesh->triangle[i][j].s = 0; + pmesh->triangle[i][j].t = 0; + } + + ptexture->max_s = 63; + ptexture->min_s = 0; + ptexture->max_t = 63; + ptexture->min_t = 0; + } + return; + } + + // check texture coords. + if( !clip_texcoords && allow_tileing ) + { + for( i = 0; i < pmesh->numtris; i++ ) + { + for( j = 0; j < 3; j++ ) + { + // texcoord U is out of range 0-1 so we can't pack them + if( pmesh->triangle[i][j].u > 1.0 || pmesh->triangle[i][j].u < 0.0 ) + clip_texcoords = true; + + // texcoord V is out of range 0-1 so we can't pack them + if( pmesh->triangle[i][j].v > 1.0 || pmesh->triangle[i][j].v < 0.0 ) + clip_texcoords = true; + } + + // tileing detected + if( clip_texcoords ) + { + MsgDev( D_INFO, "UV mapping is out of range 0-1, texture clipping enabled\n" ); + break; + } + } + } + + // pack texture coords + if( !clip_texcoords ) + { + int k, n; + + // prevent to expand texture too much + for( i = 0; i < pmesh->numtris; i++ ) + { + for( j = 0; j < 3; j++ ) + { + if( pmesh->triangle[i][j].u > 4.0f ) pmesh->triangle[i][j].u = 4.0f; + if( pmesh->triangle[i][j].u < -3.0f ) pmesh->triangle[i][j].u = -3.0f; + if( pmesh->triangle[i][j].v > 4.0f ) pmesh->triangle[i][j].v = 4.0f; + if( pmesh->triangle[i][j].v < -3.0f ) pmesh->triangle[i][j].v = -3.0f; + } + } + + // clamp U-coord + while( 1 ) + { + float min_u = 9999.0f; + float max_u =-9999.0f; + float k_max_u, n_min_u; + k = n = -1; + + for( i = 0; i < pmesh->numtris; i++ ) + { + float local_min, local_max; + + local_min = Q_min( pmesh->triangle[i][0].u, Q_min( pmesh->triangle[i][1].u, pmesh->triangle[i][2].u )); + local_max = Q_max( pmesh->triangle[i][0].u, Q_max( pmesh->triangle[i][1].u, pmesh->triangle[i][2].u )); + + if( local_min < min_u ) + { + min_u = local_min; + k_max_u = local_max; + k = i; + } + + if( local_max > max_u ) + { + max_u = local_max; + n_min_u = local_min; + n = i; + } + } + + if( k_max_u + 1.0 < max_u ) + { + for( j = 0; j < 3; j++ ) + pmesh->triangle[k][j].u += 1.0f; + } + else if( n_min_u - 1.0 > min_u ) + { + for( j = 0; j < 3; j++ ) + pmesh->triangle[n][j].u -= 1.0f; + } + else + { + break; + } + } + + // clamp V-coord + while( 1 ) + { + float min_v = 9999.0f; + float max_v =-9999.0f; + float k_max_v, n_min_v; + k = n = -1; + + for( i = 0; i < pmesh->numtris; i++ ) + { + float local_min, local_max; + + local_min = Q_min( pmesh->triangle[i][0].v, Q_min( pmesh->triangle[i][1].v, pmesh->triangle[i][2].v )); + local_max = Q_max( pmesh->triangle[i][0].v, Q_max( pmesh->triangle[i][1].v, pmesh->triangle[i][2].v )); + + if( local_min < min_v ) + { + min_v = local_min; + k_max_v = local_max; + k = i; + } + + if( local_max > max_v ) + { + max_v = local_max; + n_min_v = local_min; + n = i; + } + } + + if( k_max_v + 1.0 < max_v ) + { + for( j = 0; j < 3; j++ ) + pmesh->triangle[k][j].v += 1.0f; + } + else if( n_min_v - 1.0 > min_v ) + { + for( j = 0; j < 3; j++ ) + pmesh->triangle[n][j].v -= 1.0f; + } + else + { + break; + } + } + } + else if( !allow_tileing ) + { + for( i = 0; i < pmesh->numtris; i++ ) + { + for( j = 0; j < 3; j++ ) + { + if( pmesh->triangle[i][j].u < 0.0f ) pmesh->triangle[i][j].u = 0.0f; + if( pmesh->triangle[i][j].u > 1.0f ) pmesh->triangle[i][j].u = 1.0f; + if( pmesh->triangle[i][j].v < 0.0f ) pmesh->triangle[i][j].v = 0.0f; + if( pmesh->triangle[i][j].v > 1.0f ) pmesh->triangle[i][j].v = 1.0f; + } + } + } + + // convert to pixel coordinates + for( i = 0; i < pmesh->numtris; i++ ) + { + for( j = 0; j < 3; j++ ) + { + // FIXME losing texture coord resultion! + pmesh->triangle[i][j].s = adjust_texcoord( pmesh->triangle[i][j].u * ptexture->srcwidth ); + pmesh->triangle[i][j].t = adjust_texcoord( pmesh->triangle[i][j].v * ptexture->srcheight ); + } + } + + // find the range + if( !clip_texcoords ) + { + for( i = 0; i < pmesh->numtris; i++ ) + { + for( j = 0; j < 3; j++ ) + { + ptexture->max_s = Q_max( pmesh->triangle[i][j].s, ptexture->max_s ); + ptexture->min_s = Q_min( pmesh->triangle[i][j].s, ptexture->min_s ); + ptexture->max_t = Q_max( pmesh->triangle[i][j].t, ptexture->max_t ); + ptexture->min_t = Q_min( pmesh->triangle[i][j].t, ptexture->min_t ); + } + } + } + else + { + ptexture->max_s = ptexture->srcwidth - 1; + ptexture->min_s = 0; + ptexture->max_t = ptexture->srcheight - 1; + ptexture->min_t = 0; + } +} + +void ResetTextureCoordRanges( s_mesh_t *pmesh, s_texture_t *ptexture ) +{ + int i, j; + + // adjust top, left edge + for( i = 0; i < pmesh->numtris; i++ ) + { + for( j = 0; j < 3; j++ ) + { + pmesh->triangle[i][j].s -= ptexture->min_s; + + // quake wants t inverted + pmesh->triangle[i][j].t = (ptexture->max_t - ptexture->min_t) - (pmesh->triangle[i][j].t - ptexture->min_t); + if( store_uv_coords ) pmesh->triangle[i][j].v = -pmesh->triangle[i][j].v; + } + } +} + +void SetSkinValues( void ) +{ + int i, j; + + for( i = 0; i < g_numtextures; i++ ) + { + Grab_Skin ( &g_texture[i] ); + + g_texture[i].max_s = -9999999; + g_texture[i].min_s = 9999999; + g_texture[i].max_t = -9999999; + g_texture[i].min_t = 9999999; + } + + for( i = 0; i < g_nummodels; i++ ) + { + for( j = 0; j < g_model[i]->nummesh; j++ ) + { + TextureCoordRanges( g_model[i]->pmesh[j], &g_texture[g_model[i]->pmesh[j]->skinref] ); + } + } + + for( i = 0; i < g_numtextures; i++ ) + { + if( g_texture[i].max_s < g_texture[i].min_s ) + { + // must be a replacement texture + if( FBitSet( g_texture[i].flags, STUDIO_NF_CHROME )) + { + g_texture[i].max_s = 63; + g_texture[i].min_s = 0; + g_texture[i].max_t = 63; + g_texture[i].min_t = 0; + } + else + { + g_texture[i].max_s = g_texture[g_texture[i].parent].max_s; + g_texture[i].min_s = g_texture[g_texture[i].parent].min_s; + g_texture[i].max_t = g_texture[g_texture[i].parent].max_t; + g_texture[i].min_t = g_texture[g_texture[i].parent].min_t; + } + } + + ResizeTexture( &g_texture[i] ); + } + + for( i = 0; i < g_nummodels; i++ ) + { + for (j = 0; j < g_model[i]->nummesh; j++) + { + ResetTextureCoordRanges( g_model[i]->pmesh[j], &g_texture[g_model[i]->pmesh[j]->skinref] ); + } + } + + // build texture groups + for( i = 0; i < MAXSTUDIOSKINS; i++ ) + { + for( j = 0; j < MAXSTUDIOSKINS; j++ ) + { + g_skinref[i][j] = j; + } + } + + for( i = 0; i < g_numtexturelayers[0]; i++ ) + { + for( j = 0; j < g_numtexturereps[0]; j++ ) + { + g_skinref[i][g_texturegroup[0][0][j]] = g_texturegroup[0][i][j]; + } + } + + if( i != 0 ) + { + g_numskinfamilies = i; + } + else + { + g_numskinref = g_numtextures; + g_numskinfamilies = 1; + } +} \ No newline at end of file diff --git a/utils/studiomdl/studiomdl.cpp b/utils/studiomdl/studiomdl.cpp new file mode 100644 index 0000000..16b3e96 --- /dev/null +++ b/utils/studiomdl/studiomdl.cpp @@ -0,0 +1,5523 @@ +/* +studiomodel.cpp - generates a studio .mdl file from a .qc script +Copyright (C) 2017 Uncle Mike + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include +#include "cmdlib.h" +#include "mathlib.h" +#include "stringlib.h" +#include "scriplib.h" +#include "filesystem.h" +#define EXTERN +#include "studio.h" +#include "studiomdl.h" +#include +#include + +CUtlArray< char > g_KeyValueText; +char filename[1024]; +char line[1024]; +int linecount; +FILE *input; + +//----------------------------------------------------------------------------- +// Parsed data from a .qc file +//----------------------------------------------------------------------------- +/* +================= +ClearModel + +FIXME: finalize this if you want to compile models in cycle +================= +*/ +void ClearModel( void ) +{ + int i, j, k; + + // release memory occupied by animations + for( i = 0; i < g_real_numani; i++ ) + { + // we never know how many frames are used + for( j = 0; j < MAXSTUDIOANIMATIONS; j++ ) + { + if( g_panimation[i]->rawanim[j] ) + Mem_Free( g_panimation[i]->rawanim[j] ); + + if( g_panimation[i]->sanim[j] ) + Mem_Free( g_panimation[i]->sanim[j] ); + } + + for( j = 0; j < g_real_numbones; j++ ) + { + for( k = 0; k < 6; k++ ) + { + Mem_Free( g_panimation[i]->anim[j][k] ); + } + } + + for( j = 0; j < g_panimation[i]->numcmds; j++ ) + { + s_animcmd_t *pcmd = &g_panimation[i]->cmds[j]; + + if( pcmd->cmd == CMD_IKRULE && pcmd->cmd_source == CMDSRC_LOCAL ) + Mem_Free( pcmd->ikrule.pRule ); + + if( pcmd->cmd == CMD_IKFIXUP && pcmd->cmd_source == CMDSRC_LOCAL ) + Mem_Free( pcmd->ikfixup.pRule ); + } + + for( j = 0; j < g_panimation[i]->numikrules; j++ ) + { + s_ikrule_t *ikrule = &g_panimation[i]->ikrule[j]; + + Mem_Free( ikrule->errorData.pError ); + + for( k = 0; k < 6; k++ ) + Mem_Free( ikrule->errorData.anim[k] ); + } + + Mem_Free( g_panimation[i] ); + } + + for( i = 0; i < g_numcmdlists; i++ ) + { + s_cmdlist_t *cmdlist = &g_cmdlist[i]; + + for( j = 0; j < cmdlist->numcmds; j++ ) + { + s_animcmd_t *pcmd = &cmdlist->cmds[j]; + + if( pcmd->cmd == CMD_IKRULE && pcmd->cmd_source == CMDSRC_GLOBAL ) + Mem_Free( pcmd->ikrule.pRule ); + if( pcmd->cmd == CMD_IKFIXUP && pcmd->cmd_source == CMDSRC_GLOBAL ) + Mem_Free( pcmd->ikfixup.pRule ); + } + } + + memset( g_cmdlist, 0, sizeof( g_cmdlist )); + g_numcmdlists = 0; + + for( i = 0; i < g_nummodels; i++ ) + { + for( j = 0; j < g_model[i]->nummesh; j++ ) + { + Mem_Free( g_model[i]->pmesh[j]->triangle ); + Mem_Free( g_model[i]->pmesh[j] ); // iteslf + } + + Mem_Free( g_model[i] ); + } + + for( i = 0; i < g_real_numseq; i++ ) + { + g_sequence[i].KeyValue.Purge(); + } + + for( i = 1; i <= g_numxnodes; i++ ) + free( g_xnodename[i] ); + + for( i = 0; i < g_numtextures; i++ ) + Mem_Free( g_texture[i].pdata ); + + memset( g_xnode, 0, sizeof( g_xnode )); + memset( g_xnodename, 0, sizeof( g_xnodename )); + memset( g_xnodeskip, 0, sizeof( g_xnodeskip )); + g_numxnodes = g_numxnodeskips = 0; + + memset( g_texture, 0, sizeof( g_texture )); + g_numtextures = 0; + + memset( g_sequencegroup, 0, sizeof( g_sequencegroup )); + g_numseqgroups = 0; + + memset( g_sequence, 0, sizeof( g_sequence )); + g_real_numseq = 0; + g_numseq = 0; + + memset( g_model, 0, sizeof( g_model )); + g_nummodels = 0; + + memset( g_panimation, 0, sizeof( g_panimation )); + g_real_numani = 0; + g_numani = 0; + + memset( g_pose, 0, sizeof( g_pose )); + g_numposeparameters = 0; + + memset( g_bonetable, 0, sizeof( g_bonetable )); + g_real_numbones = 0; + g_numbones = 0; + + memset( g_renamedbone, 0, sizeof( g_renamedbone )); + g_numrenamedbones = 0; + + memset( g_importbone, 0, sizeof( g_importbone )); + g_numimportbones = 0; + + memset( g_hitgroup, 0, sizeof( g_hitgroup )); + g_numhitgroups = 0; + + memset( g_bonecontroller, 0, sizeof( g_bonecontroller )); + g_numbonecontrollers = 0; + + memset( g_screenalignedbone, 0, sizeof( g_screenalignedbone )); + g_numscreenalignedbones = 0; + + memset( g_attachment, 0, sizeof( g_attachment )); + g_numattachments = 0; + + g_hitboxsets.Purge(); + + g_BoneMerge.Purge(); +} + +bool GetLineInput( void ) +{ + while( fgets( line, sizeof( line ), input ) != NULL ) + { + linecount++; + // skip comments + if( line[0] == '/' && line[1] == '/' ) + continue; + return true; + } + + return false; +} + +bool IsEnd( char const *pLine ) +{ + if( !Q_strncmp( "end", pLine, 3 )) + return true; + return ( pLine[3] == '\0' ) || ( pLine[3] == '\n' ); +} + +int verify_atoi_dbg( const char *token, const int line ) +{ + if( token[0] != '-' && ( token[0] < '0' || token[0] > '9' )) + { + TokenError( "expecting number, got \"%s\"\ncalled at line %d", token, line ); + } + return atoi( token ); +} + +float verify_atof_dbg( const char *token, const int line ) +{ + if( token[0] != '-' && token[0] != '.' && ( token[0] < '0' || token[0] > '9' )) + { + TokenError( "expecting number, got \"%s\"\ncalled at line %d", token, line ); + } + return atof( token ); +} + +float verify_atof_with_null( const char *token ) +{ + if( !Q_strcmp( token, ".." )) + return -1; + + return verify_atof( token ); +} + +//----------------------------------------------------------------------------- +// Key value block +//----------------------------------------------------------------------------- +static void AppendKeyValueText( CUtlArray< char > *pKeyValue, const char *pString ) +{ + int nLen = Q_strlen( pString ); + int nFirst = pKeyValue->AddMultipleToTail( nLen ); + memcpy( pKeyValue->Base() + nFirst, pString, nLen ); +} + +int KeyValueTextSize( CUtlArray< char > *pKeyValue ) +{ + return pKeyValue->Count(); +} + +const char *KeyValueText( CUtlArray< char > *pKeyValue ) +{ + return pKeyValue->Base(); +} + +const char *RenameBone( const char *pName ) +{ + for( int k = 0; k < g_numrenamedbones; k++ ) + { + if( !Q_stricmp( pName, g_renamedbone[k].from )) + return g_renamedbone[k].to; + } + + return pName; +} + +bool IsGlobalBoneXSI( const char *name, const char *bonename ) +{ + name = RenameBone( name ); + + int len = Q_strlen( name ); + int len2 = Q_strlen( bonename ); + + if( len2 == len && Q_strchr( bonename, '.' ) == NULL && !Q_stricmp( bonename, name )) + return true; + + if( len2 > len ) + { + if( bonename[len2-len-1] == '.' ) + { + if( !Q_stricmp( &bonename[len2-len], name )) + { + return true; + } + } + } + return false; +} + +int findGlobalBone( const char *name ) +{ + name = RenameBone( name ); + + for( int k = 0; k < g_numbones; k++ ) + { + if( !Q_stricmp( g_bonetable[k].name, name )) + return k; + } + + return -1; +} + +int findLocalBone( const s_animation_t *panim, const char *name ) +{ + if( name ) + { + int i; + + for( i = 0; i < panim->numbones; i++ ) + { + if( !Q_stricmp( name, panim->localBone[i].name )) + return i; + } + + name = RenameBone( name ); + + for( i = 0; i < panim->numbones; i++ ) + { + if( !Q_stricmp( name, panim->localBone[i].name )) + return i; + } + } + + return -1; +} + +int findGlobalBoneXSI( const char *name ) +{ + name = RenameBone( name ); + + for( int k = 0; k < g_numbones; k++ ) + { + if( IsGlobalBoneXSI( name, g_bonetable[k].name )) + return k; + } + + return -1; +} + +int SortAndBalanceBones( int iCount, int iMaxCount, int bones[], float weights[] ) +{ + int i, bShouldSort; + float w, t; + + // collapse duplicate bone weights + for( i = 0; i < iCount-1; i++ ) + { + for( int j = i + 1; j < iCount; j++ ) + { + if( bones[i] == bones[j] ) + { + weights[i] += weights[j]; + weights[j] = 0.0; + } + } + } + + // do sleazy bubble sort + do { + bShouldSort = false; + for( i = 0; i < iCount-1; i++ ) + { + if( weights[i+1] > weights[i] ) + { + int j = bones[i+1]; + bones[i+1] = bones[i]; + bones[i] = j; + w = weights[i+1]; + weights[i+1] = weights[i]; + weights[i] = w; + bShouldSort = true; + } + } + } while( bShouldSort ); + + // throw away all weights less than 1/20th + while( iCount > 1 && weights[iCount-1] < 0.005 ) + iCount--; + + // clip to the top iMaxCount bones + if( iCount > iMaxCount ) + iCount = iMaxCount; + + t = 0.0f; + + for( i = 0; i < iCount; i++ ) + t += weights[i]; + + if( t <= 0.0f ) + { + // missing weights?, go ahead and evenly share? + // FIXME: shouldn't this error out? + t = 1.0 / iCount; + + for( i = 0; i < iCount; i++ ) + weights[i] = t; + } + else + { + // scale to sum to 1.0 + t = 1.0 / t; + + for( i = 0; i < iCount; i++ ) + weights[i] = weights[i] * t; + } + + return iCount; +} + +int LookupControl( const char *string ) +{ + if( !Q_stricmp( string, "X" )) return STUDIO_X; + if( !Q_stricmp( string, "Y" )) return STUDIO_Y; + if( !Q_stricmp( string, "Z" )) return STUDIO_Z; + if( !Q_stricmp( string, "XR" )) return STUDIO_XR; + if( !Q_stricmp( string, "YR" )) return STUDIO_YR; + if( !Q_stricmp( string, "ZR" )) return STUDIO_ZR; + + if( !Q_stricmp( string, "LX" )) return STUDIO_LX; + if( !Q_stricmp( string, "LY" )) return STUDIO_LY; + if( !Q_stricmp( string, "LZ" )) return STUDIO_LZ; + if( !Q_stricmp( string, "LXR" )) return STUDIO_LXR; + if( !Q_stricmp( string, "LYR" )) return STUDIO_LYR; + if( !Q_stricmp( string, "LZR" )) return STUDIO_LZR; + + if( !Q_stricmp( string, "LM" )) return STUDIO_LINEAR; + if( !Q_stricmp( string, "LQ" )) return STUDIO_QUADRATIC_MOTION; + + return -1; +} + +int LookupPoseParameter( const char *name ) +{ + int i; + + for( i = 0; i < g_numposeparameters; i++) + { + if( !Q_stricmp( name, g_pose[i].name )) + return i; + } + + Q_strncpy( g_pose[i].name, name, sizeof( g_pose[0].name )); + g_numposeparameters = i + 1; + + if( g_numposeparameters > MAXSTUDIOPOSEPARAM ) + TokenError( "too many pose parameters (max %d)\n", MAXSTUDIOPOSEPARAM ); + + return i; +} + +s_sequence_t *LookupSequence( const char *name ) +{ + for( int i = 0; i < g_numseq; i++ ) + { + if ( !Q_stricmp( g_sequence[i].name, name )) + return &g_sequence[i]; + } + + return NULL; +} + +s_animation_t *LookupAnimation( const char *name ) +{ + for( int i = 0; i < g_numani; i++ ) + { + if( !Q_stricmp( g_panimation[i]->name, name )) + return g_panimation[i]; + } + + s_sequence_t *pseq = LookupSequence( name ); + return pseq ? pseq->panim[0] : NULL; +} + +int LookupAttachment( const char *name ) +{ + for( int i = 0; i < g_numattachments; i++ ) + { + if( !Q_stricmp( g_attachment[i].name, name )) + return i; + } + + return -1; +} + +int LookupTexture( const char *texturename ) +{ + int i; + + for( i = 0; i < g_numtextures; i++ ) + { + if( !Q_stricmp( g_texture[i].name, texturename )) + return i; + } + + Q_strncpy( g_texture[i].name, texturename, sizeof( g_texture[0].name )); + + // XDM: allow such names as "tex_chrome_bright" - chrome and full brightness effects + if( Q_stristr( texturename, "chrome" ) != NULL ) + g_texture[i].flags |= STUDIO_NF_FLATSHADE | STUDIO_NF_CHROME; + if( Q_stristr( texturename, "bright" ) != NULL ) + g_texture[i].flags |= STUDIO_NF_FULLBRIGHT; + g_numtextures++; + + return i; +} + +s_mesh_t *LookupMesh( s_model_t *pmodel, char *texturename ) +{ + int i, j; + + j = LookupTexture( texturename ); + + for( i = 0; i < pmodel->nummesh; i++ ) + { + if( pmodel->pmesh[i]->skinref == j ) + return pmodel->pmesh[i]; + } + + if( i >= MAXSTUDIOMESHES ) + COM_FatalError( "too many meshes in model: \"%s\"\n", pmodel->name ); + + pmodel->nummesh = i + 1; + pmodel->pmesh[i] = (s_mesh_t *)Mem_Alloc( sizeof( s_mesh_t )); + pmodel->pmesh[i]->skinref = j; + + return pmodel->pmesh[i]; +} + +s_trianglevert_t *LookupTriangle( s_mesh_t *pmesh, int index ) +{ + if( index >= MAXSTUDIOTRIANGLES ) + COM_FatalError( "max studio triangles exceeds 65536\n" ); + + if( index >= pmesh->alloctris ) + { + int start = pmesh->alloctris; + pmesh->alloctris = index + 256; + + if( pmesh->triangle ) + { + pmesh->triangle = (s_trianglevert_t (*)[3])Mem_Realloc( pmesh->triangle, pmesh->alloctris * sizeof( *pmesh->triangle )); + memset( &pmesh->triangle[start], 0, ( pmesh->alloctris - start ) * sizeof( *pmesh->triangle )); + } + else + { + pmesh->triangle = (s_trianglevert_t (*)[3])Mem_Alloc( pmesh->alloctris * sizeof( *pmesh->triangle )); + } + } + + return pmesh->triangle[index]; +} + +int LookupNormal( s_model_t *pmodel, s_srcvertex_t *srcv ) +{ + int i; + + for( i = 0; i < pmodel->numnorms; i++ ) + { + if( DotProduct( pmodel->norm[i].org, srcv->norm ) > g_normal_blend && pmodel->norm[i].skinref == srcv->skinref + && !memcmp( &pmodel->norm[i].globalWeight, &srcv->globalWeight, sizeof( s_boneweight_t ))) { + return i; + } + } + + if( i >= MAXSTUDIOVERTS ) + COM_FatalError( "too many normals in model: \"%s\"\n", pmodel->name ); + + pmodel->norm[i].org = srcv->norm; + pmodel->norm[i].globalWeight = srcv->globalWeight; + pmodel->norm[i].skinref = srcv->skinref; + pmodel->numnorms = i + 1; + + return i; +} + +int LookupVertex( s_model_t *pmodel, s_srcvertex_t *srcv ) +{ + int i; + + // assume 3 digits of accuracy + srcv->vert.x = (int)(srcv->vert.x * 1000) / 1000.0; + srcv->vert.y = (int)(srcv->vert.y * 1000) / 1000.0; + srcv->vert.z = (int)(srcv->vert.z * 1000) / 1000.0; + + for( i = 0; i < pmodel->numverts; i++ ) + { + if( pmodel->vert[i].org == srcv->vert && !memcmp( &pmodel->vert[i].globalWeight, &srcv->globalWeight, sizeof( s_boneweight_t ))) + return i; + } + + if( i >= MAXSTUDIOVERTS ) + COM_FatalError( "too many vertices in model: \"%s\"\n", pmodel->name ); + + pmodel->vert[i].org = srcv->vert; + pmodel->vert[i].globalWeight = srcv->globalWeight; + pmodel->numverts = i + 1; + + return i; +} + +int LookupActivity( const char *szActivity ) +{ + int i; + + for( i = 0; activity_map[i].name; i++ ) + { + if( !Q_stricmp( szActivity, activity_map[i].name )) + return activity_map[i].type; + } + + // match ACT_# + if( !Q_strnicmp( szActivity, "ACT_", 4 )) + return verify_atoi( &szActivity[4] ); + + return 0; +} + +int LookupXNode( const char *name ) +{ + int i; + + // FIXME: check transitions in-game + if( Q_isdigit( name )) + return verify_atoi( name ); + + for( i = 1; i <= g_numxnodes; i++ ) + { + if( !Q_stricmp( name, g_xnodename[i] )) + return i; + } + + g_xnodename[i] = strdup( name ); + g_numxnodes = i; + + return i; +} + +void clip_rotations( float &rot ) +{ + // clip everything to : -M_PI <= x < M_PI + while( rot >= M_PI ) rot -= M_PI * 2.0f; + while( rot < -M_PI ) rot += M_PI * 2.0f; +} + +void clip_rotations( Radian &rot ) +{ + clip_rotations( rot.x ); + clip_rotations( rot.y ); + clip_rotations( rot.z ); +} + +void scale_animation( s_animation_t *panim ) +{ + for( int t = 0; t < panim->source.numframes; t++ ) + { + for( int j = 0; j < panim->numbones; j++ ) + panim->rawanim[t][j].pos *= panim->scale; + } +} + +void Build_Reference( s_model_t *pmodel ) +{ + for( int i = 0; i < pmodel->numbones; i++ ) + { + matrix3x4 bonematrix = matrix3x4( pmodel->skeleton[i].pos, pmodel->skeleton[i].rot ); + int parent = pmodel->localBone[i].parent; + + if( parent == -1 ) + { + // scale the done pos. + // calc rotational matrices + pmodel->boneToPose[i] = bonematrix; + + } + else + { + // calc compound rotational matrices + pmodel->boneToPose[i] = pmodel->boneToPose[parent].ConcatTransforms( bonematrix ); + } + } +} + +void Grab_Triangles( s_model_t *pmodel ) +{ + int i, j, k; + int ncount = 0; + float vmin = 9999.0f; + + // load the base triangles + while( 1 ) + { + if( !GetLineInput( )) + break; + + // check for end + if( IsEnd( line )) + break; + + char texturename[64]; + s_mesh_t *pmesh; + s_trianglevert_t *ptriv; + int bone; + Vector vert[3]; + Vector norm[3]; + + Q_strncpy( texturename, line, sizeof( texturename )); + + // strip off trailing smag + for( i = Q_strlen( texturename ) - 1; i >= 0 && !isgraph( texturename[i] ); i-- ); + texturename[i + 1] = '\0'; + + // funky texture overrides + for( i = 0; i < numrep; i++ ) + { + if( sourcetexture[i][0] == '\0' ) + { + Q_strncpy( texturename, defaulttexture[i], sizeof( texturename )); + break; + } + + if( !Q_stricmp( texturename, sourcetexture[i] )) + { + Q_strncpy( texturename, defaulttexture[i], sizeof( texturename )); + break; + } + } + + if( texturename[0] == '\0' ) + { + // weird model problem, skip them + GetLineInput(); + GetLineInput(); + GetLineInput(); + continue; + } + + if( Q_stristr( texturename, "null.bmp" ) || Q_stristr( texturename, "null.tga" )) + { + // skip all faces with the null texture on them. + GetLineInput(); + GetLineInput(); + GetLineInput(); + continue; + } + + COM_DefaultExtension( texturename, ".tga" ); // Crowbar decompiler issues + pmesh = LookupMesh( pmodel, texturename ); + + for( j = 0; j < 3; j++ ) + { + if( pmodel->flip_triangles ) + ptriv = LookupTriangle( pmesh, pmesh->numtris ) + 2 - j; // quake wants them in the reverse order + else ptriv = LookupTriangle( pmesh, pmesh->numtris ) + j; + + if( !GetLineInput( )) + { + COM_FatalError( "%s: error on line %d: %s", filename, linecount, line ); + } + + s_srcvertex_t *srcv = &pmodel->srcvert[pmodel->numsrcverts]; + int iCount = 0, bones[MAXSTUDIOSRCBONES]; + float weights[MAXSTUDIOSRCBONES]; + s_boneweight_t boneWeight; + + // get support for Source bone weights description + i = sscanf( line, "%d %f %f %f %f %f %f %f %f %d %d %f %d %f %d %f %d %f", + &bone, + &srcv->vert[0], &srcv->vert[1], &srcv->vert[2], + &srcv->norm[0], &srcv->norm[1], &srcv->norm[2], + &ptriv->u, &ptriv->v, + &iCount, + &bones[0], &weights[0], &bones[1], &weights[1], &bones[2], &weights[2], &bones[3], &weights[3] ); + + if( i < 9 ) continue; + + if( bone < 0 || bone >= pmodel->numbones ) + { + COM_FatalError( "bogus bone index\n%d %s :\n%s", linecount, filename, line ); + } + + // continue parsing more bones. + if( iCount > MAXSTUDIOBONEWEIGHTS ) + { + char *token; + int ctr = 0; + + for( k = 0; k < 18; k++ ) + { + while( line[ctr] == ' ' ) + { + ctr++; + } + + token = strtok( &line[ctr], " " ); + ctr += Q_strlen( token ) + 1; + } + + for( k = 4; k < iCount && k < MAXSTUDIOSRCBONES; k++ ) + { + while( line[ctr] == ' ' ) + { + ctr++; + } + + token = strtok( &line[ctr], " " ); + ctr += Q_strlen( token ) + 1; + + bones[k] = verify_atoi( token ); + + token = strtok( &line[ctr], " " ); + ctr += strlen( token ) + 1; + + weights[k] = verify_atof( token ); + } + } + + vmin = Q_min( srcv->vert.z, vmin ); + srcv->skinref = pmesh->skinref; + srcv->vert *= pmodel->scale; + + vert[j] = srcv->vert; + norm[j] = srcv->norm; + + // initialize boneweigts + for( k = 0; k < MAXSTUDIOBONEWEIGHTS; k++ ) + { + boneWeight.weight[k] = 0.0f; + boneWeight.bone[k] = -1; + } + + if( i == 9 || iCount == 0 ) + { + boneWeight.weight[0] = 1.0f; + boneWeight.bone[0] = bone; + boneWeight.numbones = 1; + } + else + { + iCount = SortAndBalanceBones( iCount, MAXSTUDIOBONEWEIGHTS, bones, weights ); + + if( allow_boneweights ) + { + for( k = 0; k < iCount; k++ ) + { + boneWeight.bone[k] = bound( 0, bones[k], MAXSTUDIOBONES - 1 ); + boneWeight.weight[k] = weights[k]; + } + + boneWeight.numbones = iCount; + has_boneweights = true; + } + else + { + boneWeight.bone[0] = bones[0]; + boneWeight.weight[0] = 1.0f; + boneWeight.numbones = 1; + } + } + + srcv->localWeight = boneWeight; + ptriv->vertindex = ptriv->normindex = pmodel->numsrcverts++; + + if( pmodel->numsrcverts >= MAXSRCSTUDIOVERTS ) + COM_FatalError( "too many source vertices in model: \"%s\"\n", pmodel->name ); + + // tag bone as being used + // pmodel->bone[bone].ref = 1; + } + + if( tag_reversed || tag_normals ) + { + // check triangle direction + if( DotProduct( norm[0], norm[1] ) < 0 || DotProduct( norm[1], norm[2] ) < 0 || DotProduct( norm[2], norm[0] ) < 0 ) + { + ncount++; + + if( tag_normals ) + { + // steal the triangle and make it white + s_trianglevert_t *ptriv2; + + pmesh = LookupMesh( pmodel, "#white.bmp" ); + ptriv2 = LookupTriangle( pmesh, pmesh->numtris ); + + ptriv2[0] = ptriv[0]; + ptriv2[1] = ptriv[1]; + ptriv2[2] = ptriv[2]; + } + } + else + { + Vector a1, a2, sn; + float x, y, z; + + a1 = vert[1] - vert[0]; + a2 = vert[2] - vert[0]; + sn = CrossProduct( a1, a2 ).Normalize(); + + x = DotProduct( sn, norm[0] ); + y = DotProduct( sn, norm[1] ); + z = DotProduct( sn, norm[2] ); + + if( x < 0.0 || y < 0.0 || z < 0.0 ) + { + if( tag_reversed ) + { + // steal the triangle and make it white + s_trianglevert_t *ptriv2; + + MsgDev( D_INFO, "triangle reversed (%f %f %f)\n", + DotProduct( norm[0], norm[1] ), + DotProduct( norm[1], norm[2] ), + DotProduct( norm[2], norm[0] )); + + pmesh = LookupMesh( pmodel, "#white.bmp" ); + ptriv2 = LookupTriangle( pmesh, pmesh->numtris ); + + ptriv2[0] = ptriv[0]; + ptriv2[1] = ptriv[1]; + ptriv2[2] = ptriv[2]; + } + } + } + } + + pmesh->numtris++; + } + + if( ncount ) MsgDev( D_WARN, "%d triangles with misdirected normals\n", ncount ); + if( vmin != 0.0 ) MsgDev( D_REPORT, "lowest vector at %f\n", vmin ); +} + +bool Grab_AnimFrames( s_animation_t *panim ) +{ + Vector pos; + Radian rot; + char cmd[1024]; + int index, size; + int t = -99999999; + + size = panim->numbones * sizeof( s_bone_t ); + panim->source.startframe = -1; + panim->source.endframe = 0; + + while( GetLineInput( )) + { + if( sscanf( line, "%d %f %f %f %f %f %f", &index, &pos[0], &pos[1], &pos[2], &rot[0], &rot[1], &rot[2] ) == 7 ) + { + if( panim->source.startframe < 0 ) + COM_FatalError( "missing frame start(%d) : %s\n", linecount, line ); + + panim->rawanim[t][index].pos = pos; + panim->rawanim[t][index].rot = rot; + continue; + } + + if( sscanf( line, "%1023s %d", cmd, &index ) == 0 ) + { + COM_FatalError( "(%d) : %s", linecount, line ); + continue; + } + + if( !Q_stricmp( cmd, "time" )) + { + t = index; + + if( panim->source.startframe == -1 ) + panim->source.startframe = t; + + if( t < panim->source.startframe ) + COM_FatalError( "frame error(%d) : %s\n", linecount, line ); + + if( t > panim->source.endframe ) + panim->source.endframe = t; + + t -= panim->source.startframe; + + if( t > MAXSTUDIOANIMFRAMES ) + { + MsgDev( D_ERROR, "animation %s has too many frames. Cutted at %d\n", panim->name, MAXSTUDIOANIMFRAMES ); + panim->source.numframes = MAXSTUDIOANIMFRAMES - 1; + panim->source.endframe = MAXSTUDIOANIMFRAMES - 1; + return false; + } + + if( panim->rawanim[t] != NULL ) + continue; + + panim->rawanim[t] = (s_bone_t *)Mem_Alloc( size ); + + // duplicate previous frames keys + if( t > 0 && panim->rawanim[t-1] ) + { + for( int j = 0; j < panim->numbones; j++ ) + { + panim->rawanim[t][j].pos = panim->rawanim[t-1][j].pos; + panim->rawanim[t][j].rot = panim->rawanim[t-1][j].rot; + } + } + continue; + } + + if( !Q_stricmp( cmd, "end" )) + { + panim->source.numframes = panim->source.endframe - panim->source.startframe + 1; + + for( t = 0; t < panim->source.numframes; t++ ) + { + if( panim->rawanim[t] == NULL ) + COM_FatalError( "%s is missing frame %d\n", panim->name, t + panim->source.startframe ); + } + return true; + } + + COM_FatalError( "(%d) : %s", linecount, line ); + } + + COM_FatalError( "unexpected EOF: %s\n", panim->name ); + + return true; +} + +void Grab_Skeleton( s_model_t *pmodel ) +{ + Vector pos; + Radian rot; + char cmd[1024]; + int index; + + while( GetLineInput( )) + { + if( sscanf( line, "%d %f %f %f %f %f %f", &index, &pos.x, &pos.y, &pos.z, &rot.x, &rot.y, &rot.z ) == 7 ) + { + pos *= pmodel->scale; + pmodel->skeleton[index].pos = pos; + pmodel->skeleton[index].rot = rot; + } + else if( sscanf( line, "%s %d", cmd, &index )) + { + if( !Q_strcmp( cmd, "time" )) + { + // begin building skeleton + } + else if( !Q_strcmp( cmd, "end" )) + { + Build_Reference( pmodel ); + return; + } + } + } +} + +int Grab_Nodes( s_node_t *pnodes ) +{ + int index, parent; + int numbones = 0; + char name[1024]; + + for( index = 0; index < MAXSTUDIOSRCBONES; index++ ) + pnodes[index].parent = -1; + + while( GetLineInput( )) + { + if( sscanf( line, "%d \"%[^\"]\" %d", &index, name, &parent ) == 3 ) + { + Q_strncpy( pnodes[index].name, name, sizeof( pnodes[0].name )); + pnodes[index].parent = parent; + numbones = Q_max( numbones, index ); + } + else + { + return numbones + 1; + } + } + + COM_FatalError( "Unexpected EOF at line %d\n", linecount ); + + return 0; +} + +void Grab_Studio( s_model_t *pmodel ) +{ + char cmd[1024]; + int option; + + Q_snprintf( filename, sizeof( filename ), "%s/%s", cddir[numdirs], pmodel->name ); + COM_DefaultExtension( filename, ".smd" ); + + if( !COM_FileExists( filename )) + COM_FatalError( "%s doesn't exist\n", filename ); + + if(( input = fopen( filename, "r" )) == 0 ) + COM_FatalError( "%s couldn't be open\n", filename ); + + MsgDev( D_INFO, "grabbing: %s.smd\t\t[^1mesh^7]\n", pmodel->name ); + linecount = 0; + + while( GetLineInput( )) + { + int numRead = sscanf( line, "%s %d", cmd, &option ); + + // blank line + if(( numRead == EOF ) || ( numRead == 0 )) + continue; + + if( !Q_strcmp( cmd, "version" )) + { + if( option != 1 ) + COM_FatalError( "%s version %i should be 1\n", filename, option ); + } + else if( !Q_strcmp( cmd, "nodes" )) + { + pmodel->numbones = Grab_Nodes( pmodel->localBone ); + } + else if( !Q_strcmp( cmd, "skeleton" )) + { + Grab_Skeleton( pmodel ); + } + else if( !Q_strcmp( cmd, "triangles" )) + { + Grab_Triangles( pmodel ); + } + else + { + MsgDev( D_WARN, "unknown studio command\n" ); + } + } + + fclose( input ); +} + +void Grab_Animation( const char *name, s_animation_t *panim ) +{ + char cmd[1024]; + int option; + + Q_snprintf( filename, sizeof( filename ), "%s/%s", cddir[numdirs], name ); + COM_DefaultExtension( filename, ".smd" ); + + if( !COM_FileExists( filename )) + COM_FatalError ("%s doesn't exist\n", filename); + + if(( input = fopen( filename, "r" )) == 0 ) + COM_FatalError( "%s couldn't be open\n", filename ); + linecount = 0; + + while( GetLineInput( )) + { + sscanf( line, "%s %d", cmd, &option ); + if( !Q_strcmp( cmd, "version" )) + { + if( option != 1 ) + COM_FatalError( "%s version %i should be 1\n", filename, option ); + } + else if( !Q_strcmp( cmd, "nodes" )) + { + panim->numbones = Grab_Nodes( panim->localBone ); + } + else if( !Q_strcmp( cmd, "skeleton" )) + { + if( !Grab_AnimFrames( panim )) + break; // animation was cutted + } + else + { + // some artists use mesh reference as default animation + if( Q_strcmp( cmd, "triangles" )) + MsgDev( D_WARN, "unknown studio command\n" ); + + while( GetLineInput( )) + { + if( IsEnd( line )) + break; + } + } + } + + fclose( input ); +} + +void Cmd_Eyeposition( void ) +{ + // rotate points into frame of reference + // so model points down the positive x axis + GetToken( false ); + eyeposition.y = verify_atof( token ); + + GetToken( false ); + eyeposition.x = -verify_atof( token ); + + GetToken( false ); + eyeposition.z = verify_atof( token ); +} + +void Cmd_Flags( void ) +{ + GetToken( false ); + gflags = verify_atoi( token ); +} + +void Cmd_Modelname( void ) +{ + GetToken( false ); + Q_strncpy( outname, token, sizeof( outname )); +} + +void Option_Studio( s_model_t *pmodel ) +{ + if( !GetToken( false )) + return; + + Q_strncpy( pmodel->name, token, sizeof( pmodel->name )); + pmodel->flip_triangles = flip_triangles; + pmodel->scale = g_defaultscale; + + while( TokenAvailable( )) + { + GetToken( false ); + if( !Q_stricmp( "reverse", token )) + { + pmodel->flip_triangles = 0; + } + else if( !Q_stricmp( "scale", token )) + { + GetToken( false ); + pmodel->scale = verify_atof( token ); + } + else if( !Q_stricmp( "faces", token )) + { + GetToken( false ); + GetToken( false ); + continue; + } + else if( !Q_stricmp( "bias", token )) + { + GetToken( false ); + continue; + } + else if( !Q_stricmp( "{", token )) + { + UnGetToken( ); + break; + } + else + { + COM_FatalError( "unknown command \"%s\"\n", token ); + } + } + + Grab_Studio( pmodel ); +} + +int Option_Blank( void ) +{ + g_model[g_nummodels] = (s_model_t *)Mem_Alloc( sizeof( s_model_t )); + g_bodypart[g_numbodyparts].pmodel[g_bodypart[g_numbodyparts].nummodels] = g_model[g_nummodels]; + Q_strncpy( g_model[g_nummodels]->name, "blank", sizeof( g_model[0]->name )); + + g_bodypart[g_numbodyparts].nummodels++; + g_nummodels++; + + return 0; +} + +void Cmd_Bodygroup( void ) +{ + int depth = 0; + + if( !GetToken( false )) + return; + + if( g_numbodyparts == 0 ) g_bodypart[g_numbodyparts].base = 1; + else g_bodypart[g_numbodyparts].base = g_bodypart[g_numbodyparts-1].base * g_bodypart[g_numbodyparts-1].nummodels; + Q_strncpy( g_bodypart[g_numbodyparts].name, token, sizeof( g_bodypart[g_numbodyparts].name )); + + do + { + GetToken( true ); + + if( endofscript ) + { + return; + } + else if( token[0] == '{' ) + { + depth++; + } + else if( token[0] == '}' ) + { + depth--; + break; + } + else if( !Q_stricmp( "studio", token )) + { + g_model[g_nummodels] = (s_model_t *)Mem_Alloc( sizeof( s_model_t )); + g_bodypart[g_numbodyparts].pmodel[g_bodypart[g_numbodyparts].nummodels] = g_model[g_nummodels]; + g_bodypart[g_numbodyparts].nummodels++; + + Option_Studio( g_model[g_nummodels] ); + g_nummodels++; + } + else if( !Q_stricmp( "blank", token )) + { + Option_Blank( ); + } + else + { + COM_FatalError( "unknown bodygroup option: \"%s\"\n", token ); + } + } while( 1 ); + + g_numbodyparts++; +} + +void Cmd_Body( void ) +{ + if( !GetToken( false )) + return; + + if( g_numbodyparts == 0 ) g_bodypart[g_numbodyparts].base = 1; + else g_bodypart[g_numbodyparts].base = g_bodypart[g_numbodyparts-1].base * g_bodypart[g_numbodyparts-1].nummodels; + Q_strncpy(g_bodypart[g_numbodyparts].name, token, sizeof( g_bodypart[0].name )); + + g_model[g_nummodels] = (s_model_t *)Mem_Alloc( sizeof( s_model_t )); + g_bodypart[g_numbodyparts].pmodel[g_bodypart[g_numbodyparts].nummodels] = g_model[g_nummodels]; + g_bodypart[g_numbodyparts].nummodels = 1; + + Option_Studio( g_model[g_nummodels] ); + + g_numbodyparts++; + g_nummodels++; +} + +int Cmd_Model( void ) +{ + int depth = 0; + + g_model[g_nummodels] = (s_model_t *)Mem_Alloc( sizeof( s_model_t )); + + // name + if( !GetToken( false )) + return 0; + + Q_strncpy( g_model[g_nummodels]->name, token, sizeof( g_model[0]->name )); + + // fake bodypart stuff + if( g_numbodyparts == 0 ) g_bodypart[g_numbodyparts].base = 1; + else g_bodypart[g_numbodyparts].base = g_bodypart[g_numbodyparts-1].base * g_bodypart[g_numbodyparts-1].nummodels; + Q_strncpy(g_bodypart[g_numbodyparts].name, token, sizeof( g_bodypart[0].name )); + + g_bodypart[g_numbodyparts].pmodel[g_bodypart[g_numbodyparts].nummodels] = g_model[g_nummodels]; + g_bodypart[g_numbodyparts].nummodels = 1; + g_numbodyparts++; + + Option_Studio( g_model[g_nummodels] ); + + while( 1 ) + { + if( depth > 0 ) + { + if( !GetToken( true )) + { + break; + } + } + else + { + if( !TokenAvailable( )) + { + break; + } + else + { + GetToken( false ); + } + } + + if( endofscript ) + { + if( depth != 0 ) + COM_FatalError( "missing }\n" ); + return 1; + } + + if( !Q_stricmp( "{", token )) + { + depth++; + } + else if( !Q_stricmp( "}", token )) + { + depth--; + } + else + { + // NOTE: cmd $model has many various params + // that completely ignored in goldsource + // so we just use it as equal of $body command + // and skip all this unknown commands + while( TryToken( )); + } + + if( depth < 0 ) + COM_FatalError( "missing {\n" ); + } + g_nummodels++; + + return 0; +} + +int Option_Activity( s_sequence_t *psequence ) +{ + int i; + + GetToken( false ); + + if(( i = LookupActivity( token )) != 0 ) + { + psequence->activity = i; + GetToken( false ); + psequence->actweight = verify_atoi( token ); + return 0; + } + + return 1; +} + +int Option_Event( s_sequence_t *psequence ) +{ + if(( psequence->numevents + 1 ) >= MAXSTUDIOEVENTS ) + COM_FatalError( "too many events\n" ); + + GetToken( false ); + psequence->event[psequence->numevents].event = verify_atoi( token ); + + GetToken( false ); + psequence->event[psequence->numevents].frame = verify_atoi( token ); + + psequence->numevents++; + + // option token + if( TokenAvailable( )) + { + GetToken( false ); + + if( token[0] == '}' ) // opps, hit the end + return 1; + + // found an option + Q_strncpy( psequence->event[psequence->numevents-1].options, token, sizeof( psequence->event[0].options )); + } + + return 0; +} + +void Option_IKRule( s_ikrule_t *pRule ) +{ + int i; + + // chain + GetToken( false ); + + for( i = 0; i < g_numikchains; i++) + { + if( !Q_stricmp( token, g_ikchain[i].name )) + break; + } + + if( i >= g_numikchains ) + TokenError( "unknown chain \"%s\" in ikrule\n", token ); + + pRule->chain = i; + // default slot + pRule->slot = i; + + // type + GetToken( false ); + if( !Q_stricmp( token, "touch" )) + { + pRule->type = IK_SELF; + + // bone + GetToken( false ); + Q_strncpy( pRule->bonename, token, sizeof( pRule->bonename )); + } + else if( !Q_stricmp( token, "footstep" )) + { + pRule->type = IK_GROUND; + + pRule->height = g_ikchain[pRule->chain].height; + pRule->floor = g_ikchain[pRule->chain].floor; + pRule->radius = g_ikchain[pRule->chain].radius; + } + else if( !Q_stricmp( token, "attachment" )) + { + pRule->type = IK_ATTACHMENT; + + // name of attachment + GetToken( false ); + Q_strncpy( pRule->attachment, token, sizeof( pRule->attachment )); + } + else if( !Q_stricmp( token, "release" )) + { + pRule->type = IK_RELEASE; + } + else if( !Q_stricmp( token, "unlatch" )) + { + pRule->type = IK_UNLATCH; + } + + pRule->contact = -1; + + while( TokenAvailable( )) + { + GetToken( false ); + if( !Q_stricmp( token, "height" )) + { + GetToken( false ); + pRule->height = verify_atof( token ); + } + else if( !Q_stricmp( token, "target" )) + { + // slot + GetToken( false ); + pRule->slot = verify_atoi( token ); + } + else if( !Q_stricmp( token, "range" )) + { + // ramp + GetToken( false ); + if( token[0] == '.' ) pRule->start = -1; + else pRule->start = verify_atoi( token ); + + GetToken( false ); + if( token[0] == '.' ) pRule->peak = -1; + else pRule->peak = verify_atoi( token ); + + GetToken( false ); + if( token[0] == '.' ) pRule->tail = -1; + else pRule->tail = verify_atoi( token ); + + GetToken( false ); + if( token[0] == '.' ) pRule->end = -1; + else pRule->end = verify_atoi( token ); + } + else if( !Q_stricmp( token, "floor" )) + { + GetToken( false ); + pRule->floor = verify_atof( token ); + } + else if( !Q_stricmp( token, "pad" )) + { + GetToken( false ); + pRule->radius = verify_atof( token ) / 2.0f; + } + else if( !Q_stricmp( token, "radius" )) + { + GetToken( false ); + pRule->radius = verify_atof( token ); + } + else if( !Q_stricmp( token, "contact" )) + { + GetToken( false ); + pRule->contact = verify_atoi( token ); + } + else if( !Q_stricmp( token, "usesequence" )) + { + pRule->usesequence = true; + pRule->usesource = false; + } + else if( !Q_stricmp( token, "usesource" )) + { + pRule->usesequence = false; + pRule->usesource = true; + } + else if( !Q_stricmp( token, "fakeorigin" )) + { + GetToken( false ); + pRule->pos.x = verify_atof( token ); + GetToken( false ); + pRule->pos.y = verify_atof( token ); + GetToken( false ); + pRule->pos.z = verify_atof( token ); + + pRule->bone = -1; + } + else if( !Q_stricmp( token, "fakerotate" )) + { + Vector ang; + + GetToken( false ); + ang.x = verify_atof( token ); + GetToken( false ); + ang.y = verify_atof( token ); + GetToken( false ); + ang.z = verify_atof( token ); + AngleQuaternion( ang, pRule->q ); + + pRule->bone = -1; + } + else if( !Q_stricmp( token, "bone" )) + { + GetToken( false ); + Q_strncpy( pRule->bonename, token, sizeof( pRule->bonename )); + } + else + { + UnGetToken(); + return; + } + } +} + +//----------------------------------------------------------------------------- +// Key value block! +//----------------------------------------------------------------------------- +void Option_KeyValues( CUtlArray< char > *pKeyValue ) +{ + // Simply read in the block between { }s as text + // and plop it out unchanged into the .mdl file. + // Make sure to respect the fact that we may have nested {}s + int nLevel = 1; + + if( !GetToken( true )) + return; + + if( token[0] != '{' ) + return; + + AppendKeyValueText( pKeyValue, "mdlkeyvalue\n{\n" ); + + while( GetToken( true )) + { + if( !Q_stricmp( token, "}" )) + { + if( --nLevel <= 0 ) + break; + AppendKeyValueText( pKeyValue, " }\n" ); + } + else if( !Q_stricmp( token, "{" )) + { + AppendKeyValueText( pKeyValue, "{\n" ); + nLevel++; + } + else + { + // tokens inside braces are quoted + if( nLevel > 1 ) + { + AppendKeyValueText( pKeyValue, "\"" ); + AppendKeyValueText( pKeyValue, token ); + AppendKeyValueText( pKeyValue, "\" " ); + } + else + { + AppendKeyValueText( pKeyValue, token ); + AppendKeyValueText( pKeyValue, " " ); + } + } + } + + if( nLevel >= 1 ) + { + TokenError( "Keyvalue block missing matching braces.\n" ); + } + + AppendKeyValueText( pKeyValue, "}\n" ); +} + +void Cmd_PoseParameter( void ) +{ + if( g_numposeparameters >= MAXSTUDIOPOSEPARAM ) + TokenError( "too many pose parameters (max %d)\n", MAXSTUDIOPOSEPARAM ); + + int i = LookupPoseParameter( token ); + + // name + GetToken( false ); + Q_strncpy( g_pose[i].name, token, sizeof( g_pose[0].name )); + + if( TokenAvailable( )) + { + // min + GetToken( false ); + g_pose[i].min = verify_atof( token ); + } + + if( TokenAvailable( )) + { + // max + GetToken( false ); + g_pose[i].max = verify_atof( token ); + } + + while( TokenAvailable( )) + { + GetToken( false ); + + if( !Q_stricmp( token, "wrap" )) + { + g_pose[i].flags |= STUDIO_LOOPING; + g_pose[i].loop = g_pose[i].max - g_pose[i].min; + } + else if( !Q_stricmp( token, "loop" )) + { + g_pose[i].flags |= STUDIO_LOOPING; + GetToken( false ); + g_pose[i].loop = verify_atof( token ); + } + } +} + +/* +================= +Cmd_Origin +================= +*/ +void Cmd_Origin( void ) +{ + GetToken( false ); + g_defaultadjust.x = verify_atof (token); + + GetToken( false ); + g_defaultadjust.y = verify_atof (token); + + GetToken( false ); + g_defaultadjust.z = verify_atof (token); + + if( TokenAvailable( )) + { + GetToken( false ); + g_defaultrotation.z = DEG2RAD( verify_atof( token ) + 90.0f ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Set the default root rotation so that the Y axis is up instead of the Z axis (for Maya) +//----------------------------------------------------------------------------- +void Cmd_UpAxis( void ) +{ + // We want to create a rotation that rotates from the art space + // (specified by the up direction) to a z up space + // Note: x, -x, -y are untested + GetToken( false ); + + if( !Q_stricmp( token, "x" )) + { + // rotate 90 degrees around y to move x into z + g_defaultrotation.x = 0.0f; + g_defaultrotation.y = M_PI / 2.0f; + } + else if( !Q_stricmp( token, "-x" )) + { + // untested + g_defaultrotation.x = 0.0f; + g_defaultrotation.y = -M_PI / 2.0f; + } + else if( !Q_stricmp( token, "y" )) + { + // rotate 90 degrees around x to move y into z + g_defaultrotation.x = M_PI / 2.0f; + g_defaultrotation.y = 0.0f; + } + else if( !Q_stricmp( token, "-y" )) + { + // untested + g_defaultrotation.x = -M_PI / 2.0f; + g_defaultrotation.y = 0.0f; + } + else if( !Q_stricmp( token, "z" )) + { + // there's still a built in 90 degree Z rotation :( + g_defaultrotation.x = 0.0f; + g_defaultrotation.y = 0.0f; + } + else if( !Q_stricmp( token, "-z" )) + { + // there's still a built in 90 degree Z rotation :( + g_defaultrotation.x = 0.0f; + g_defaultrotation.y = 0.0f; + } + else + { + TokenError( "unknown $upaxis option: \"%s\"\n", token ); + return; + } +} + +void Cmd_ScaleUp( void ) +{ + GetToken( false ); + g_defaultscale = verify_atof( token ); +} + +void Cmd_ScaleAxis( int axis ) +{ + GetToken( false ); + g_defaultscale[axis] = verify_atof( token ); +} + +int Cmd_SequenceGroup( void ) +{ + GetToken( false ); + Q_strncpy( g_sequencegroup[g_numseqgroups].label, token, sizeof( g_sequencegroup[0].label )); + g_numseqgroups++; + + return 0; +} + +int Cmd_SequenceGroupSize( void ) +{ + GetToken( false ); + maxseqgroupsize = 1024 * verify_atoi( token ); + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: parse order dependant s_animcmd_t token for $animations +//----------------------------------------------------------------------------- +int ParseCmdlistToken( int &numcmds, s_animcmd_t *cmds, int cmd_source ) +{ + if( numcmds >= MAXSTUDIOCMDS ) + return false; + + s_animcmd_t *pcmd = &cmds[numcmds]; + + pcmd->cmd_source = cmd_source; + + if( !Q_stricmp( "fixuploop", token )) + { + pcmd->cmd = CMD_FIXUP; + + GetToken( false ); + pcmd->fixuploop.start = verify_atoi( token ); + GetToken( false ); + pcmd->fixuploop.end = verify_atoi( token ); + } + else if( !Q_strnicmp( "weightlist", token, 6 )) + { + GetToken( false ); + + int i; + for( i = 1; i < g_numweightlist; i++ ) + { + if( !Q_stricmp( g_weightlist[i].name, token )) + break; + } + + if( i == g_numweightlist ) + { + TokenError( "unknown weightlist '%s\'\n", token ); + } + + pcmd->cmd = CMD_WEIGHTS; + pcmd->weightlist.index = i; + } + else if( !Q_stricmp( "subtract", token )) + { + pcmd->cmd = CMD_SUBTRACT; + + GetToken( false ); + + s_animation_t *extanim = LookupAnimation( token ); + if( extanim == NULL ) + { + TokenError( "unknown subtract animation '%s\'\n", token ); + } + + pcmd->subtract.ref = extanim; + + GetToken( false ); + pcmd->subtract.frame = verify_atoi( token ); + + pcmd->subtract.flags |= STUDIO_POST; + } + else if( !Q_stricmp( "presubtract", token )) // FIXME: rename this to something better + { + pcmd->cmd = CMD_SUBTRACT; + + GetToken( false ); + + s_animation_t *extanim = LookupAnimation( token ); + if( extanim == NULL ) + { + TokenError( "unknown presubtract animation '%s\'\n", token ); + } + + pcmd->subtract.ref = extanim; + + GetToken( false ); + pcmd->subtract.frame = verify_atoi( token ); + } + else if( !Q_stricmp( "alignto", token )) + { + pcmd->cmd = CMD_AO; + + pcmd->ao.pBonename = NULL; + + GetToken( false ); + s_animation_t *extanim = LookupAnimation( token ); + if( extanim == NULL ) + { + TokenError( "unknown alignto animation '%s\'\n", token ); + } + + pcmd->ao.ref = extanim; + pcmd->ao.motiontype = STUDIO_X | STUDIO_Y; + pcmd->ao.srcframe = 0; + pcmd->ao.destframe = 0; + } + else if( !Q_stricmp( "align", token )) + { + pcmd->cmd = CMD_AO; + + pcmd->ao.pBonename = NULL; + + GetToken( false ); + s_animation_t *extanim = LookupAnimation( token ); + if (extanim == NULL) + { + TokenError( "unknown align animation '%s\'\n", token ); + } + + pcmd->ao.ref = extanim; + + // motion type to match + pcmd->ao.motiontype = 0; + GetToken( false ); + int ctrl; + + while(( ctrl = LookupControl( token )) != -1 ) + { + pcmd->ao.motiontype |= ctrl; + GetToken( false ); + } + + if( pcmd->ao.motiontype == 0 ) + { + TokenError( "missing controls on align\n" ); + } + + // frame of reference animation to match + pcmd->ao.srcframe = verify_atoi( token ); + + // against what frame of the current animation + GetToken( false ); + pcmd->ao.destframe = verify_atoi( token ); + } + else if( !Q_stricmp( "alignboneto", token )) + { + pcmd->cmd = CMD_AO; + + GetToken( false ); + pcmd->ao.pBonename = strdup( token ); + + GetToken( false ); + s_animation_t *extanim = LookupAnimation( token ); + if( extanim == NULL ) + { + TokenError( "unknown alignboneto animation '%s\'\n", token ); + } + + pcmd->ao.ref = extanim; + pcmd->ao.motiontype = STUDIO_X | STUDIO_Y; + pcmd->ao.srcframe = 0; + pcmd->ao.destframe = 0; + } + else if( !Q_stricmp( "alignbone", token )) + { + pcmd->cmd = CMD_AO; + + GetToken( false ); + pcmd->ao.pBonename = strdup( token ); + + GetToken( false ); + s_animation_t *extanim = LookupAnimation( token ); + if( extanim == NULL ) + { + TokenError( "unknown alignboneto animation '%s\'\n", token ); + } + + pcmd->ao.ref = extanim; + + // motion type to match + pcmd->ao.motiontype = 0; + GetToken( false ); + int ctrl; + + while(( ctrl = LookupControl( token )) != -1 ) + { + pcmd->ao.motiontype |= ctrl; + GetToken( false ); + } + + if( pcmd->ao.motiontype == 0 ) + { + TokenError( "missing controls on align\n" ); + } + + // frame of reference animation to match + pcmd->ao.srcframe = verify_atoi( token ); + + // against what frame of the current animation + GetToken( false ); + pcmd->ao.destframe = verify_atoi( token ); + } + else if( !Q_stricmp( "match", token )) + { + pcmd->cmd = CMD_MATCH; + + GetToken( false ); + + s_animation_t *extanim = LookupAnimation( token ); + if( extanim == NULL ) + { + TokenError( "unknown match animation '%s\'\n", token ); + } + + pcmd->match.ref = extanim; + } + else if( !Q_stricmp( "matchblend", token )) + { + pcmd->cmd = CMD_MATCHBLEND; + + GetToken( false ); + + s_animation_t *extanim = LookupAnimation( token ); + if( extanim == NULL ) + { + TokenError( "unknown match animation '%s\'\n", token ); + } + + pcmd->match.ref = extanim; + + // frame of reference animation to match + GetToken( false ); + pcmd->match.srcframe = verify_atoi( token ); + + // against what frame of the current animation + GetToken( false ); + pcmd->match.destframe = verify_atoi( token ); + + // backup and starting match in here + GetToken( false ); + pcmd->match.destpre = verify_atoi( token ); + + // continue blending match till here + GetToken( false ); + pcmd->match.destpost = verify_atoi( token ); + + } + else if( !Q_stricmp( "worldspaceblend", token )) + { + pcmd->cmd = CMD_WORLDSPACEBLEND; + + GetToken( false ); + + s_animation_t *extanim = LookupAnimation( token ); + if( extanim == NULL ) + { + TokenError( "unknown worldspaceblend animation '%s\'\n", token ); + } + + pcmd->world.ref = extanim; + pcmd->world.startframe = 0; + pcmd->world.loops = false; + } + else if( !Q_stricmp( "worldspaceblendloop", token )) + { + pcmd->cmd = CMD_WORLDSPACEBLEND; + + GetToken( false ); + + s_animation_t *extanim = LookupAnimation( token ); + if (extanim == NULL) + { + TokenError( "unknown worldspaceblend animation '%s\'\n", token ); + } + + pcmd->world.ref = extanim; + + GetToken( false ); + pcmd->world.startframe = atoi( token ); + + pcmd->world.loops = true; + } + else if( !Q_stricmp( "rotateto", token )) + { + pcmd->cmd = CMD_ANGLE; + + GetToken( false ); + pcmd->angle.angle = verify_atof( token ); + } + else if( !Q_stricmp( "ikrule", token )) + { + pcmd->cmd = CMD_IKRULE; + + pcmd->ikrule.pRule = (s_ikrule_t *)Mem_Alloc( sizeof( s_ikrule_t )); + + Option_IKRule( pcmd->ikrule.pRule ); + } + else if( !Q_stricmp( "ikfixup", token )) + { + pcmd->cmd = CMD_IKFIXUP; + + pcmd->ikfixup.pRule = (s_ikrule_t *)Mem_Alloc( sizeof( s_ikrule_t )); + + Option_IKRule( pcmd->ikrule.pRule ); + } + else if( !Q_stricmp( "walkframe", token )) + { + pcmd->cmd = CMD_MOTION; + + // frame + GetToken( false ); + pcmd->motion.iEndFrame = verify_atoi( token ); + + // motion type to match + pcmd->motion.motiontype = 0; + while( TokenAvailable( )) + { + GetToken( false ); + int ctrl = LookupControl( token ); + + if( ctrl != -1 ) + { + pcmd->motion.motiontype |= ctrl; + } + else + { + UnGetToken(); + break; + } + } + } + else if( !Q_stricmp( "walkalignto", token )) + { + pcmd->cmd = CMD_REFMOTION; + + GetToken( false ); + pcmd->motion.iEndFrame = verify_atoi( token ); + + pcmd->motion.iSrcFrame = pcmd->motion.iEndFrame; + + GetToken( false ); // reference animation + s_animation_t *extanim = LookupAnimation( token ); + if( extanim == NULL ) + { + TokenError( "unknown alignto animation '%s\'\n", token ); + } + + pcmd->motion.pRefAnim = extanim; + pcmd->motion.iRefFrame = 0; + + // motion type to match + pcmd->motion.motiontype = 0; + + while( TokenAvailable( )) + { + GetToken( false ); + int ctrl = LookupControl( token ); + + if( ctrl != -1 ) + { + pcmd->motion.motiontype |= ctrl; + } + else + { + UnGetToken(); + break; + } + } + } + else if( !Q_stricmp( "walkalign", token )) + { + pcmd->cmd = CMD_REFMOTION; + + // end frame to apply motion over + GetToken( false ); + pcmd->motion.iEndFrame = verify_atoi( token ); + + // reference animation + GetToken( false ); + s_animation_t *extanim = LookupAnimation( token ); + if( extanim == NULL ) + { + TokenError( "unknown alignto animation '%s\'\n", token ); + } + pcmd->motion.pRefAnim = extanim; + + // motion type to match + pcmd->motion.motiontype = 0; + while( TokenAvailable( )) + { + GetToken( false ); + int ctrl = LookupControl( token ); + if( ctrl != -1 ) + { + pcmd->motion.motiontype |= ctrl; + } + else + { + break; + } + } + + if( pcmd->motion.motiontype == 0 ) + { + TokenError( "missing controls on walkalign\n" ); + } + + // frame of reference animation to match + pcmd->motion.iRefFrame = verify_atoi( token ); + + // against what frame of the current animation + GetToken( false ); + pcmd->motion.iSrcFrame = verify_atoi( token ); + } + else if( !Q_stricmp( "derivative", token )) + { + pcmd->cmd = CMD_DERIVATIVE; + + // get scale + GetToken( false ); + pcmd->derivative.scale = verify_atof( token ); + } + else if( !Q_stricmp( "noanimation", token )) + { + pcmd->cmd = CMD_NOANIMATION; + } + else if( !Q_stricmp( "lineardelta", token )) + { + pcmd->cmd = CMD_LINEARDELTA; + pcmd->linear.flags |= STUDIO_AL_POST; + } + else if( !Q_stricmp( "splinedelta", token )) + { + pcmd->cmd = CMD_LINEARDELTA; + pcmd->linear.flags |= STUDIO_AL_POST; + pcmd->linear.flags |= STUDIO_AL_SPLINE; + } + else if( !Q_stricmp( "compress", token )) + { + pcmd->cmd = CMD_COMPRESS; + + // get frames to skip + GetToken( false ); + pcmd->compress.frames = verify_atoi( token ); + } + else if( !Q_stricmp( "numframes", token )) + { + pcmd->cmd = CMD_NUMFRAMES; + + // get frames to force + GetToken( false ); + pcmd->compress.frames = verify_atoi( token ); + } + else if( !Q_stricmp( "counterrotate", token )) + { + pcmd->cmd = CMD_COUNTERROTATE; + + // get bone name + GetToken( false ); + pcmd->counterrotate.pBonename = strdup( token ); + } + else if( !Q_stricmp( "counterrotateto", token )) + { + pcmd->cmd = CMD_COUNTERROTATE; + + pcmd->counterrotate.bHasTarget = true; + + // get pitch + GetToken( false ); + pcmd->counterrotate.targetAngle[0] = verify_atof( token ); + + // get yaw + GetToken( false ); + pcmd->counterrotate.targetAngle[1] = verify_atof( token ); + + // get roll + GetToken( false ); + pcmd->counterrotate.targetAngle[2] = verify_atof( token ); + + // get bone name + GetToken( false ); + pcmd->counterrotate.pBonename = strdup( token ); + } + else if( !Q_stricmp( "localhierarchy", token )) + { + pcmd->cmd = CMD_LOCALHIERARCHY; + + // get bone name + GetToken( false ); + pcmd->localhierarchy.pBonename = strdup( token ); + + // get parent name + GetToken( false ); + pcmd->localhierarchy.pParentname = strdup( token ); + + pcmd->localhierarchy.start = -1; + pcmd->localhierarchy.peak = -1; + pcmd->localhierarchy.tail = -1; + pcmd->localhierarchy.end = -1; + + if( TokenAvailable( )) + { + GetToken( false ); + if( !Q_stricmp( token, "range" )) + { + GetToken( false ); + pcmd->localhierarchy.start = verify_atof_with_null( token ); + + GetToken( false ); + pcmd->localhierarchy.peak = verify_atof_with_null( token ); + + GetToken( false ); + pcmd->localhierarchy.tail = verify_atof_with_null( token ); + + GetToken( false ); + pcmd->localhierarchy.end = verify_atof_with_null( token ); + } + else + { + UnGetToken(); + } + } + + MsgDev( D_WARN, "^1localhierarchy^7: command not supported\n" ); + return false; + } + else + { + return false; + } + + numcmds++; + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: create named list of boneweights +//----------------------------------------------------------------------------- +void Option_Weightlist( s_weightlist_t *pweightlist ) +{ + int i, depth = 0; + + pweightlist->numbones = 0; + + while( 1 ) + { + if( depth > 0 ) + { + if( !GetToken( true )) + { + break; + } + } + else + { + if( !TokenAvailable( )) + { + break; + } + else + { + GetToken( false ); + } + } + + if( endofscript ) + { + if( depth != 0 ) + TokenError("missing }\n" ); + return; + } + + if( !Q_stricmp( "{", token )) + { + depth++; + } + else if( !Q_stricmp( "}", token )) + { + depth--; + } + else if( !Q_stricmp( "posweight", token )) + { + i = pweightlist->numbones - 1; + if( i < 0 ) + { + TokenError( "Error with specifing bone Position weight \'%s:%s\'\n", + pweightlist->name, pweightlist->bonename[i] ); + } + + GetToken( false ); + pweightlist->boneposweight[i] = verify_atof( token ); + if( pweightlist->boneweight[i] == 0 && pweightlist->boneposweight[i] > 0 ) + { + TokenError( "Non-zero Position weight with zero Rotation weight not allowed \'%s:%s %f %f\'\n", + pweightlist->name, pweightlist->bonename[i], pweightlist->boneweight[i], pweightlist->boneposweight[i] ); + } + } + else + { + i = pweightlist->numbones++; + if( i >= MAXWEIGHTSPERLIST ) + { + TokenError( "Too many bones (%d) in weightlist '%s'\n", i, pweightlist->name ); + } + + pweightlist->bonename[i] = strdup( token ); + GetToken( false ); + pweightlist->boneweight[i] = verify_atof( token ); + pweightlist->boneposweight[i] = pweightlist->boneweight[i]; + } + + if( depth < 0 ) + { + TokenError( "missing {\n" ); + } + } +} + +int ParseEmpty( void ) +{ + int depth = 0; + + while( 1 ) + { + if( depth > 0 ) + { + if( !GetToken( true )) + { + break; + } + } + else + { + if( !TokenAvailable( )) + { + break; + } + else + { + GetToken( false ); + } + } + + if( endofscript ) + { + if( depth != 0 ) + { + TokenError( "missing }\n" ); + } + return 1; + } + if( !Q_stricmp( "{", token )) + { + depth++; + } + else if( !Q_stricmp( "}", token )) + { + depth--; + } + + if( depth < 0 ) + { + TokenError( "missing {\n" ); + } + } + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: parse order independant s_animation_t token for $animations +//----------------------------------------------------------------------------- +bool ParseAnimationToken( s_animation_t *panim ) +{ + int i; + + if( !Q_stricmp( "if", token )) + { + // fixme: add expression evaluation + GetToken( false ); + + if( atoi( token ) == 0 && Q_stricmp( token, "true" )) + { + GetToken( true ); + + if( token[0] == '{' ) + { + int depth = 1; + while( TokenAvailable( ) && depth > 0 ) + { + GetToken( true ); + if( !Q_stricmp( "{", token )) + { + depth++; + } + else if( !Q_stricmp( "}", token )) + { + depth--; + } + } + } + } + + return true; + } + + if( !Q_stricmp( "fps", token )) + { + GetToken( false ); + panim->fps = verify_atof( token ); + if( panim->fps <= 0.0f ) + { + TokenError( "ParseAnimationToken: fps (%f from '%s') <= 0.0\n", panim->fps, token ); + } + return true; + } + + if( !Q_stricmp( "origin", token )) + { + GetToken( false ); + panim->adjust.x = verify_atof( token ); + + GetToken( false ); + panim->adjust.y = verify_atof( token ); + + GetToken( false ); + panim->adjust.z = verify_atof( token ); + return true; + } + + if( !Q_stricmp( "rotate", token )) + { + GetToken( false ); + // FIXME: broken for Maya + panim->rotation.z = DEG2RAD( verify_atof( token ) + 90.0f ); + return true; + } + + if( !Q_stricmp( "angles", token )) + { + GetToken( false ); + panim->rotation.x = DEG2RAD( verify_atof( token )); + GetToken( false ); + panim->rotation.y = DEG2RAD( verify_atof( token )); + GetToken( false ); + panim->rotation.z = DEG2RAD( verify_atof( token ) + 90.0f ); + return true; + } + + if( !Q_stricmp( "scale", token )) + { + GetToken( false ); + panim->scale = verify_atof( token ); + return true; + } + + if( !Q_strnicmp( "loop", token, 4 )) + { + panim->flags |= STUDIO_LOOPING; + return true; + } + + if( !Q_strnicmp( "rootlight", token, 4 )) + { + panim->flags |= STUDIO_LIGHT_FROM_ROOT; + return true; + } + + if( !Q_strnicmp( "startloop", token, 5 )) + { + GetToken( false ); + panim->looprestart = verify_atoi( token ); + panim->flags |= STUDIO_LOOPING; + return true; + } + + if( !Q_stricmp( "fudgeloop", token )) + { + panim->fudgeloop = true; + panim->flags |= STUDIO_LOOPING; + return true; + } + + if( !Q_strnicmp( "snap", token, 4 )) + { + panim->flags |= STUDIO_SNAP; + return true; + } + + if( !Q_strnicmp( "frame", token, 5 )) + { + GetToken( false ); + panim->startframe = verify_atoi( token ); + GetToken( false ); + panim->endframe = verify_atoi( token ); + + if( panim->startframe < panim->source.startframe ) + panim->startframe = panim->source.startframe; + + if( panim->endframe > panim->source.endframe ) + panim->endframe = panim->source.endframe; + + if( panim->endframe < panim->startframe ) + TokenError( "end frame before start frame in %s\n", panim->name ); + + panim->numframes = panim->endframe - panim->startframe + 1; + return true; + } + + if( !Q_stricmp( "blockname", token )) + { + GetToken( false ); + return true; + } + + if( !Q_stricmp( "post", token )) + { + panim->flags |= STUDIO_POST; + return true; + } + + if( !Q_stricmp( "noautoik", token )) + { + panim->noAutoIK = true; + return true; + } + + if( !Q_stricmp( "autoik", token )) + { + panim->noAutoIK = false; + return true; + } + + if( ParseCmdlistToken( panim->numcmds, panim->cmds, CMDSRC_LOCAL )) + return true; + + if( !Q_stricmp( "cmdlist", token )) + { + GetToken( false ); // A + + for( i = 0; i < g_numcmdlists; i++ ) + { + if( !Q_stricmp( g_cmdlist[i].name, token )) + break; + } + + if( i == g_numcmdlists ) + TokenError( "unknown cmdlist %s\n", token ); + + for( int j = 0; j < g_cmdlist[i].numcmds; j++ ) + { + if( panim->numcmds >= MAXSTUDIOCMDS ) + TokenError( "Too many cmds in %s\n", panim->name ); + panim->cmds[panim->numcmds++] = g_cmdlist[i].cmds[j]; + } + + return true; + } + + if( !Q_stricmp( "motionrollback", token )) + { + GetToken( false ); + panim->motionrollback = atof( token ); + return true; + } + + if( !Q_stricmp( "noanimblock", token )) + return true; + + if( !Q_stricmp( "noanimblockstall", token )) + return true; + + if( LookupControl( token ) != -1 ) + { + panim->motiontype |= LookupControl( token ); + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: create named order dependant s_animcmd_t blocks, used as replicated token list for $animations +//----------------------------------------------------------------------------- +void Cmd_Cmdlist( void ) +{ + int depth = 0; + + // name + GetToken( false ); + strncpy( g_cmdlist[g_numcmdlists].name, token, sizeof( g_cmdlist[0].name )); + + while( 1 ) + { + if( depth > 0 ) + { + if( !GetToken( true )) + { + break; + } + } + else + { + if( !TokenAvailable( )) + { + break; + } + else + { + GetToken( false ); + } + } + + if( endofscript ) + { + if( depth != 0 ) + TokenError( "missing }\n" ); + return; + } + + if( !Q_stricmp( "{", token )) + { + depth++; + } + else if( !Q_stricmp( "}", token )) + { + depth--; + } + else if( !ParseCmdlistToken( g_cmdlist[g_numcmdlists].numcmds, g_cmdlist[g_numcmdlists].cmds, CMDSRC_GLOBAL )) + { + TokenError( "unknown command: %s\n", token ); + } + + if( depth < 0 ) + { + TokenError( "missing {\n" ); + } + } + g_numcmdlists++; +} + +void Cmd_Weightlist( void ) +{ + int i; + + if( !GetToken( false )) + return; + + if( g_numweightlist >= MAXWEIGHTLISTS ) + TokenError( "Too many weightlist commands (%d)\n", MAXWEIGHTLISTS ); + + for( i = 1; i < g_numweightlist; i++ ) + { + if( !Q_stricmp( g_weightlist[i].name, token )) + { + TokenError( "Duplicate weightlist '%s'\n", token ); + } + } + + Q_strncpy( g_weightlist[i].name, token, sizeof( g_weightlist[i].name )); + Option_Weightlist( &g_weightlist[g_numweightlist] ); + g_numweightlist++; +} + +void Cmd_DefaultWeightlist( void ) +{ + Option_Weightlist( &g_weightlist[0] ); +} + +//----------------------------------------------------------------------------- +// Purpose: wrapper for parsing $animation tokens +//----------------------------------------------------------------------------- +int ParseAnimation( s_animation_t *panim ) +{ + int depth = 0; + + while( 1 ) + { + if( depth > 0 ) + { + if( !GetToken( true )) + { + break; + } + } + else + { + if( !TokenAvailable( )) + { + break; + } + else + { + GetToken( false ); + } + } + + if( endofscript ) + { + if( depth != 0 ) + TokenError( "missing }\n" ); + return 1; + } + + if( !Q_stricmp( "{", token )) + { + depth++; + } + else if( !Q_stricmp( "}", token )) + { + depth--; + } + else if( !ParseAnimationToken( panim )) + { + TokenError( "Unknown animation option\'%s\'\n", token ); + } + + if( depth < 0 ) + { + TokenError( "missing {\n" ); + } + } + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: allocate an entry for $animation +//----------------------------------------------------------------------------- +void Cmd_Animation( void ) +{ + // name + GetToken( false ); + + s_animation_t *panim = LookupAnimation( token ); + + if( panim != NULL ) + TokenError( "Duplicate animation name \"%s\"\n", token ); + + // allocate animation entry + g_panimation[g_numani] = panim = (s_animation_t *)Mem_Alloc( sizeof( s_animation_t )); + Q_strncpy( panim->name, token, sizeof( panim->name )); + g_numani++; + + // filename + GetToken( false ); + Q_strncpy( panim->filename, token, sizeof( panim->filename )); + + // grab animation frames + Grab_Animation( panim->filename, panim ); + + panim->startframe = panim->source.startframe; + panim->endframe = panim->source.endframe; + panim->adjust = g_defaultadjust; + panim->rotation = g_defaultrotation; + panim->scale = g_defaultscale; + panim->motionrollback = 0.3f; + panim->fps = 30.0f; + + ParseAnimation( panim ); + + panim->numframes = panim->endframe - panim->startframe + 1; + MsgDev( D_INFO, "grabbing: %s.smd\t\t[^2anim^7][%d frames]\n", panim->name, panim->numframes ); + + // post-apply scale to the frames + scale_animation( panim ); +} + +//----------------------------------------------------------------------------- +// Purpose: create a virtual $animation command from a $sequence reference +//----------------------------------------------------------------------------- +s_animation_t *Cmd_ImpliedAnimation( s_sequence_t *psequence, const char *filename ) +{ + s_animation_t *panim; + + // allocate animation entry + g_panimation[g_numani] = panim = (s_animation_t *)Mem_Alloc( sizeof( s_animation_t )); + Q_snprintf( panim->name, sizeof( panim->name ), "@%s", psequence->name ); + g_numani++; + + Q_strncpy( panim->filename, filename, sizeof( panim->filename )); + + panim->startframe = 0; + panim->endframe = MAXSTUDIOANIMATIONS - 1; + panim->isImplied = true; + + // grab animation frames + Grab_Animation( panim->filename, panim ); + + panim->adjust = g_defaultadjust; + panim->rotation = g_defaultrotation; + panim->scale = g_defaultscale; + panim->motionrollback = 0.3f; + panim->fps = 30.0f; + + if( panim->startframe < panim->source.startframe ) + panim->startframe = panim->source.startframe; + + if( panim->endframe > panim->source.endframe ) + panim->endframe = panim->source.endframe; + + if( panim->endframe < panim->startframe ) + TokenError( "end frame before start frame in %s", panim->name ); + + panim->numframes = panim->endframe - panim->startframe + 1; + MsgDev( D_INFO, "grabbing: %s.smd\t\t[^2anim^7][%d frames]\n", panim->name, panim->numframes ); + + // post-apply scale to the frames + scale_animation( panim ); + + return panim; +} + +//----------------------------------------------------------------------------- +// Purpose: copy globally reavent $animation options from one $animation to another +//----------------------------------------------------------------------------- +void CopyAnimationSettings( s_animation_t *pdest, s_animation_t *psrc ) +{ + pdest->fps = psrc->fps; + + pdest->adjust = psrc->adjust; + pdest->rotation = psrc->rotation; + pdest->scale = psrc->scale; + pdest->motiontype = psrc->motiontype; + + for( int i = 0; i < psrc->numcmds; i++ ) + { + if( pdest->numcmds >= MAXSTUDIOCMDS ) + TokenError("Too many cmds in %s\n", pdest->name ); + + pdest->cmds[pdest->numcmds++] = psrc->cmds[i]; + } +} + +//----------------------------------------------------------------------------- +// Purpose: allocate an entry for $sequence +//----------------------------------------------------------------------------- +s_sequence_t *ProcessCmdSequence( const char *pSequenceName ) +{ + s_animation_t *panim = LookupAnimation( pSequenceName ); + + // allocate sequence + if( panim != NULL ) + TokenError( "Duplicate sequence name \"%s\"\n", pSequenceName ); + + if( g_numseq >= MAXSTUDIOSEQUENCES ) + TokenError( "Too many sequences (%d max)\n", MAXSTUDIOSEQUENCES ); + + s_sequence_t *pseq = &g_sequence[g_numseq]; + + // initialize sequence + Q_strncpy( pseq->name, pSequenceName, sizeof( pseq->name )); + + pseq->seqgroup = g_numseqgroups - 1; + pseq->paramindex[0] = -1; + pseq->paramindex[1] = -1; + pseq->groupsize[0] = 0; + pseq->groupsize[1] = 0; + pseq->fadeintime = 0.2f; + pseq->fadeouttime = 0.2f; + pseq->fps = 30.0f; + pseq->flags = 0; + g_numseq++; + + return pseq; +} + +//----------------------------------------------------------------------------- +// Performs processing on a sequence +//----------------------------------------------------------------------------- +void ProcessSequence( s_sequence_t *pseq, int numblends, s_animation_t **animations, bool isAppend ) +{ + if( isAppend ) return; + + if( numblends == 0 ) + TokenError( "no animations found\n"); + + if( pseq->groupsize[0] == 0 ) + { + // GoldSrc 9-way blending never filled second group + if( numblends < 4 || !FBitSet( pseq->flags, STUDIO_BLENDPOSE )) + { + pseq->groupsize[0] = numblends; + pseq->groupsize[1] = 1; + } + else + { + int i = sqrt( (float)numblends ); + + if(( i * i ) == numblends ) + { + pseq->groupsize[0] = i; + pseq->groupsize[1] = i; + } + else + { + TokenError( "non-square (%d) number of blends without \"blendwidth\" set\n", numblends ); + } + } + } + else + { + pseq->groupsize[1] = numblends / pseq->groupsize[0]; + + if( pseq->groupsize[0] * pseq->groupsize[1] != numblends ) + TokenError( "missing animation blends. Expected %d, found %d\n", pseq->groupsize[0] * pseq->groupsize[1], numblends ); + } + + if( numblends == 0 ) + COM_FatalError( "no animations found\n" ); + + for( int i = 0; i < numblends; i++ ) + { + int j = i % pseq->groupsize[0]; + int k = i / pseq->groupsize[0]; +#if 0 + // remap 2D array into 1D array + Msg( "i %i, group[%i x %i] = %i\n", i, j, k, j + pseq->groupsize[0] * k ); + pseq->panim[j + pseq->groupsize[0] * k] = animations[i]; +#else + pseq->panim[i] = animations[i]; +#endif + if( i > 0 && animations[i]->isImplied ) + CopyAnimationSettings( animations[i], animations[0] ); + animations[i]->isImplied = false; // don't copy any more commands + + pseq->motiontype |= animations[i]->motiontype; + pseq->flags |= animations[i]->flags; + } + + // g-cont. backward compatibility + pseq->numframes = animations[0]->numframes; + pseq->fps = animations[0]->fps; + + pseq->numblends = numblends; +} + +//----------------------------------------------------------------------------- +// Purpose: parse options unique to $sequence +//----------------------------------------------------------------------------- +int ParseSequence( s_sequence_t *pseq, bool isAppend ) +{ + s_animation_t *animations[MAXSTUDIOBLENDS]; + int i, j, n, depth = 0; + int numblends = 0; + + // initialize first anim + if( isAppend ) animations[0] = pseq->panim[0]; + + while( 1 ) + { + if( depth > 0 ) + { + if( !GetToken( true )) + { + break; + } + } + else + { + if( !TokenAvailable( )) + { + break; + } + else + { + GetToken( false ); + } + } + + if( endofscript ) + { + if( depth != 0 ) + COM_FatalError( "missing }\n" ); + return 1; + } + + if( !Q_stricmp( "{", token )) + { + depth++; + } + else if( !Q_stricmp( "}", token )) + { + depth--; + } + else if( !Q_stricmp( "event", token )) + { + depth -= Option_Event( pseq ); + } + else if( !Q_stricmp( "activity", token )) + { + Option_Activity( pseq ); + } + else if( !Q_strnicmp( token, "ACT_", 4 )) + { + UnGetToken( ); + Option_Activity( pseq ); + } + else if( !Q_stricmp( "snap", token )) + { + pseq->flags |= STUDIO_SNAP; + } + else if( !Q_stricmp( "blendwidth", token )) + { + GetToken( false ); + pseq->groupsize[0] = verify_atoi( token ); + } + else if( !Q_stricmp( "blend", token )) + { + i = ( pseq->paramindex[0] != -1 ) ? 1 : 0; + + GetToken( false ); + + // GoldSource style blending + if(( j = LookupControl( token )) == -1 ) + { + // Source-style blending (pose parameters) + j = LookupPoseParameter( token ); + SetBits( pseq->flags, STUDIO_BLENDPOSE ); + } + + pseq->paramindex[i] = j; + pseq->paramattachment[i] = -1; + GetToken( false ); + pseq->paramstart[i] = verify_atof( token ); + GetToken( false ); + pseq->paramend[i] = verify_atof( token ); + + g_pose[j].min = Q_min( g_pose[j].min, pseq->paramstart[i] ); + g_pose[j].min = Q_min( g_pose[j].min, pseq->paramend[i] ); + g_pose[j].max = Q_max( g_pose[j].max, pseq->paramstart[i] ); + g_pose[j].max = Q_max( g_pose[j].max, pseq->paramend[i] ); + } + else if( !Q_stricmp( "calcblend", token )) + { + i = ( pseq->paramindex[0] != -1 ) ? 1 : 0; + + GetToken( false ); + j = LookupPoseParameter( token ); + pseq->paramindex[i] = j; + + GetToken( false ); + pseq->paramattachment[i] = LookupAttachment( token ); + if( pseq->paramattachment[i] == -1 ) + { + TokenError( "Unknown calcblend attachment \"%s\"\n", token ); + } + + GetToken( false ); + pseq->paramcontrol[i] = LookupControl( token ); + SetBits( pseq->flags, STUDIO_BLENDPOSE ); + } + else if( !Q_stricmp( "blendref", token )) + { + GetToken( false ); + pseq->paramanim = LookupAnimation( token ); + if( pseq->paramanim == NULL ) + { + TokenError( "Unknown blendref animation \"%s\"\n", token ); + } + } + else if( !Q_stricmp( "blendcomp", token )) + { + GetToken( false ); + pseq->paramcompanim = LookupAnimation( token ); + if( pseq->paramcompanim == NULL ) + { + TokenError( "Unknown blendcomp animation \"%s\"\n", token ); + } + } + else if( !Q_stricmp( "blendcenter", token )) + { + GetToken( false ); + pseq->paramcenter = LookupAnimation( token ); + if( pseq->paramcenter == NULL ) + { + TokenError( "Unknown blendcenter animation \"%s\"\n", token ); + } + } + else if( !Q_strnicmp( "node", token, 4 )) + { + GetToken( false ); + pseq->entrynode = pseq->exitnode = LookupXNode( token ); + } + else if( !Q_stricmp( "transition", token )) + { + GetToken( false ); + pseq->entrynode = LookupXNode( token ); + GetToken( false ); + pseq->exitnode = LookupXNode( token ); + } + else if( !Q_stricmp( "rtransition", token )) + { + GetToken( false ); + pseq->entrynode = LookupXNode( token ); + GetToken( false ); + pseq->exitnode = LookupXNode( token ); + pseq->nodeflags |= 1; + } + else if( !Q_stricmp( "exitphase", token )) + { + GetToken( false ); + } + else if( !Q_stricmp( "delta", token )) + { + pseq->flags |= STUDIO_DELTA; + pseq->flags |= STUDIO_POST; + } + else if( !Q_stricmp( "worldspace", token )) + { + pseq->flags |= STUDIO_WORLD; + pseq->flags |= STUDIO_POST; + } + else if( !Q_stricmp( "post", token )) // remove + { + pseq->flags |= STUDIO_POST; + } + else if( !Q_stricmp( "predelta", token )) + { + pseq->flags |= STUDIO_DELTA; + } + else if( !Q_stricmp( "autoplay", token )) + { + pseq->flags |= STUDIO_AUTOPLAY; + } + else if( !Q_stricmp( "fadein", token )) + { + GetToken( false ); + pseq->fadeintime = verify_atof( token ); + } + else if( !Q_stricmp( "fadeout", token )) + { + GetToken( false ); + pseq->fadeouttime = verify_atof( token ); + } + else if( !Q_stricmp( "realtime", token )) + { + pseq->flags |= STUDIO_REALTIME; + } + else if( !Q_stricmp( "posecycle", token )) + { + pseq->flags |= STUDIO_CYCLEPOSE; + + GetToken( false ); + pseq->cycleposeindex = LookupPoseParameter( token ); + } + else if( !Q_stricmp( "hidden", token )) + { + pseq->flags |= STUDIO_HIDDEN; + } + else if( !Q_stricmp( "addlayer", token )) + { + GetToken( false ); + Q_strncpy( pseq->autolayer[pseq->numautolayers].name, token, sizeof( pseq->autolayer[0].name )); + pseq->numautolayers++; + } + else if( !Q_stricmp( "iklock", token )) + { + GetToken( false ); + Q_strncpy( pseq->iklock[pseq->numiklocks].name, token, sizeof( pseq->iklock[0].name )); + + GetToken( false ); + pseq->iklock[pseq->numiklocks].flPosWeight = verify_atof( token ); + + GetToken( false ); + pseq->iklock[pseq->numiklocks].flLocalQWeight = verify_atof( token ); + + pseq->numiklocks++; + } + else if( !Q_stricmp( "keyvalues", token )) + { + Option_KeyValues( &pseq->KeyValue ); + } + else if( !Q_stricmp( "blendlayer", token )) + { + pseq->autolayer[pseq->numautolayers].flags = 0; + + GetToken( false ); + Q_strncpy( pseq->autolayer[pseq->numautolayers].name, token, sizeof( pseq->autolayer[0].name )); + + GetToken( false ); + pseq->autolayer[pseq->numautolayers].start = verify_atof( token ); + + GetToken( false ); + pseq->autolayer[pseq->numautolayers].peak = verify_atof( token ); + + GetToken( false ); + pseq->autolayer[pseq->numautolayers].tail = verify_atof( token ); + + GetToken( false ); + pseq->autolayer[pseq->numautolayers].end = verify_atof( token ); + + while( TokenAvailable( )) + { + GetToken( false ); + if( !Q_stricmp( "xfade", token )) + { + pseq->autolayer[pseq->numautolayers].flags |= STUDIO_AL_XFADE; + } + else if( !Q_stricmp( "spline", token )) + { + pseq->autolayer[pseq->numautolayers].flags |= STUDIO_AL_SPLINE; + } + else if( !Q_stricmp( "noblend", token )) + { + pseq->autolayer[pseq->numautolayers].flags |= STUDIO_AL_NOBLEND; + } + else if( !Q_stricmp( "poseparameter", token )) + { + pseq->autolayer[pseq->numautolayers].flags |= STUDIO_AL_POSE; + GetToken( false ); + pseq->autolayer[pseq->numautolayers].pose = LookupPoseParameter( token ); + } + else if( !Q_stricmp( "local", token )) + { + pseq->autolayer[pseq->numautolayers].flags |= STUDIO_AL_LOCAL; + pseq->flags |= STUDIO_LOCAL; + } + else + { + UnGetToken(); + break; + } + } + + pseq->numautolayers++; + } + else if(( numblends || isAppend ) && ParseAnimationToken( animations[0] )) + { + + } + else if( !isAppend ) + { + // assume it's an animation reference + // first look up an existing animation + for( n = 0; n < g_numani; n++ ) + { + if( !Q_stricmp( token, g_panimation[n]->name )) + { + animations[numblends++] = g_panimation[n]; + break; + } + } + + if( n >= g_numani ) + { + // assume it's an implied animation + animations[numblends++] = Cmd_ImpliedAnimation( pseq, token ); + } + + // hack to allow animation commands to refer to same sequence + if( numblends == 1 ) + { + pseq->panim[0] = animations[0]; + } + + } + else + { + TokenError( "unknown command \"%s\"\n", token ); + } + + if( depth < 0 ) + { + TokenError( "missing {\n" ); + } + } + + ProcessSequence( pseq, numblends, animations, isAppend ); + + return 0; +} + +//----------------------------------------------------------------------------- +// Process the sequence command +//----------------------------------------------------------------------------- +void Cmd_Sequence( void ) +{ + if( !GetToken( false )) + return; + + s_sequence_t *pseq = ProcessCmdSequence( token ); + if( pseq ) ParseSequence( pseq, false ); +} + +//----------------------------------------------------------------------------- +// Purpose: append commands to either a sequence or an animation +//----------------------------------------------------------------------------- +void Cmd_Append( void ) +{ + GetToken( false ); + + s_sequence_t *pseq = LookupSequence( token ); + + if( pseq ) + { + ParseSequence( pseq, true ); + return; + } + + s_animation_t *panim = LookupAnimation( token ); + + if( panim ) + { + ParseAnimation( panim ); + return; + } + + TokenError( "unknown append animation %s\n", token ); +} + +void Cmd_Prepend( void ) +{ + GetToken( false ); + + s_sequence_t *pseq = LookupSequence( token ); + s_animation_t *panim = NULL; + int iRet = false; + int count = 0; + + if( pseq ) + { + panim = pseq->panim[0]; + count = panim->numcmds; + iRet = ParseSequence( pseq, true ); + } + else + { + panim = LookupAnimation( token ); + if( panim ) + { + count = panim->numcmds; + iRet = ParseAnimation( panim ); + } + } + + if( panim && count != panim->numcmds ) + { + s_animcmd_t tmp; + tmp = panim->cmds[panim->numcmds - 1]; + for( int i = panim->numcmds - 1; i > 0; i-- ) + panim->cmds[i] = panim->cmds[i-1]; + panim->cmds[0] = tmp; + return; + } + + TokenError( "unknown prepend animation \"%s\"\n", token ); +} + +void Cmd_Continue( void ) +{ + GetToken( false ); + + s_sequence_t *pseq = LookupSequence( token ); + + if( pseq ) + { + GetToken( true ); + UnGetToken(); + if( token[0] != '$' ) + ParseSequence( pseq, true ); + return; + } + + s_animation_t *panim = LookupAnimation( token ); + + if( panim ) + { + GetToken( true ); + UnGetToken(); + if( token[0] != '$' ) + ParseAnimation( panim ); + return; + } + + TokenError( "unknown continue animation %s\n", token ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Cmd_IKChain( void ) +{ + if( !GetToken( false )) + return; + + int i; + + for( i = 0; i < g_numikchains; i++ ) + { + if( !Q_stricmp( token, g_ikchain[i].name )) + break; + } + + if( i < g_numikchains ) + { + MsgDev( D_WARN, "duplicate ikchain \"%s\" ignored\n", token ); + while( TryToken( )); + return; + } + + Q_strncpy( g_ikchain[g_numikchains].name, token, sizeof( g_ikchain[0].name )); + + GetToken( false ); + Q_strncpy( g_ikchain[g_numikchains].bonename, token, sizeof( g_ikchain[0].bonename )); + + g_ikchain[g_numikchains].axis = STUDIO_Z; + g_ikchain[g_numikchains].value = 0.0; + g_ikchain[g_numikchains].height = 18.0; // sv_stepheight + g_ikchain[g_numikchains].floor = 0.0; + g_ikchain[g_numikchains].radius = 0.0; + + while( TokenAvailable( )) + { + GetToken( false ); + + if( LookupControl( token ) != -1 ) + { + g_ikchain[g_numikchains].axis = LookupControl( token ); + GetToken( false ); + g_ikchain[g_numikchains].value = verify_atof( token ); + } + else if( !Q_stricmp( "height", token )) + { + GetToken( false ); + g_ikchain[g_numikchains].height = verify_atof( token ); + } + else if( !Q_stricmp( "pad", token )) + { + GetToken( false ); + g_ikchain[g_numikchains].radius = verify_atof( token ) / 2.0; + } + else if( !Q_stricmp( "floor", token )) + { + GetToken( false ); + g_ikchain[g_numikchains].floor = verify_atof( token ); + } + else if( !Q_stricmp( "knee", token )) + { + GetToken( false ); + g_ikchain[g_numikchains].link[0].kneeDir.x = verify_atof( token ); + GetToken( false ); + g_ikchain[g_numikchains].link[0].kneeDir.y = verify_atof( token ); + GetToken( false ); + g_ikchain[g_numikchains].link[0].kneeDir.z = verify_atof( token ); + } + else if( !Q_stricmp( "center", token )) + { + GetToken( false ); + g_ikchain[g_numikchains].center.x = verify_atof( token ); + GetToken( false ); + g_ikchain[g_numikchains].center.y = verify_atof( token ); + GetToken( false ); + g_ikchain[g_numikchains].center.z = verify_atof( token ); + } + } + g_numikchains++; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Cmd_IKAutoplayLock( void ) +{ + GetToken( false ); + Q_strncpy( g_ikautoplaylock[g_numikautoplaylocks].name, token, sizeof( g_ikautoplaylock[0].name )); + + GetToken( false ); + g_ikautoplaylock[g_numikautoplaylocks].flPosWeight = verify_atof( token ); + + GetToken( false ); + g_ikautoplaylock[g_numikautoplaylocks].flLocalQWeight = verify_atof( token ); + + g_numikautoplaylocks++; +} + + +int Cmd_Root( void ) +{ + if( GetToken( false )) + { + Q_strncpy( rootname, token, sizeof( rootname )); + return 0; + } + + return 1; +} + +int Cmd_Controller( void ) +{ + if( !GetToken( false )) + return 0; + + if( g_numbonecontrollers >= MAXSTUDIOCONTROLLERS ) + COM_FatalError( "too many bone controllers (max %d)\n", MAXSTUDIOCONTROLLERS ); + + if( !Q_stricmp( "mouth", token )) + g_bonecontroller[g_numbonecontrollers].index = STUDIO_MOUTH; + else g_bonecontroller[g_numbonecontrollers].index = verify_atoi( token ); + + if( g_bonecontroller[g_numbonecontrollers].index < 0 || g_bonecontroller[g_numbonecontrollers].index > STUDIO_MOUTH ) + COM_FatalError( "$controller: invalid input index %d (allowed in range 0-4)\n", g_bonecontroller[g_numbonecontrollers].index ); + + if( GetToken( false )) + { + Q_strncpy( g_bonecontroller[g_numbonecontrollers].name, token, sizeof( g_bonecontroller[0].name )); + GetToken( false ); + + if(( g_bonecontroller[g_numbonecontrollers].type = LookupControl( token )) == -1 ) + { + MsgDev( D_WARN, "unknown bonecontroller type '%s'\n", token ); + return 0; + } + + GetToken( false ); + g_bonecontroller[g_numbonecontrollers].start = verify_atof( token ); + + GetToken( false ); + g_bonecontroller[g_numbonecontrollers].end = verify_atof( token ); + + float start = g_bonecontroller[g_numbonecontrollers].start; + float end = g_bonecontroller[g_numbonecontrollers].end; + + if( g_bonecontroller[g_numbonecontrollers].type & ( STUDIO_XR|STUDIO_YR|STUDIO_ZR )) + { + if(((int)( start + 360 ) % 360 ) == ((int)( end + 360 ) % 360 )) + g_bonecontroller[g_numbonecontrollers].type |= STUDIO_RLOOP; + } + g_numbonecontrollers++; + } + + return 1; +} + +void Cmd_ScreenAlign( void ) +{ + if( GetToken( false )) + { + Q_strncpy( g_screenalignedbone[g_numscreenalignedbones].name, token, sizeof( g_screenalignedbone[0].name )); + g_screenalignedbone[g_numscreenalignedbones].flags = BONE_SCREEN_ALIGN_SPHERE; + + if( GetToken( false ) ) + { + if( !Q_stricmp( "sphere", token )) + { + g_screenalignedbone[g_numscreenalignedbones].flags = BONE_SCREEN_ALIGN_SPHERE; + } + else if( !stricmp( "cylinder", token )) + { + g_screenalignedbone[g_numscreenalignedbones].flags = BONE_SCREEN_ALIGN_CYLINDER; + } + } + + g_numscreenalignedbones++; + + } + else + { + COM_FatalError( "$screenalign: expected bone name\n" ); + } +} + +void Cmd_BBox( void ) +{ + GetToken( false ); + bbox[0][0] = verify_atof( token ); + + GetToken( false ); + bbox[0][1] = verify_atof( token ); + + GetToken( false ); + bbox[0][2] = verify_atof( token ); + + GetToken( false ); + bbox[1][0] = verify_atof( token ); + + GetToken( false ); + bbox[1][1] = verify_atof( token ); + + GetToken( false ); + bbox[1][2] = verify_atof( token ); + + g_wrotebbox = true; +} + +void Cmd_CBox( void ) +{ + GetToken( false ); + cbox[0][0] = verify_atof( token ); + + GetToken( false ); + cbox[0][1] = verify_atof( token ); + + GetToken( false ); + cbox[0][2] = verify_atof( token ); + + GetToken( false ); + cbox[1][0] = verify_atof( token ); + + GetToken( false ); + cbox[1][1] = verify_atof( token ); + + GetToken( false ); + cbox[1][2] = verify_atof( token ); + + g_wrotecbox = true; +} + +void Cmd_Gamma( void ) +{ + GetToken( false ); + g_gamma = verify_atof( token ); +} + +int Cmd_TextureGroup( void ) +{ + int depth = 0; + int index = 0; + int group = 0; + + if( g_numtextures == 0 ) + COM_FatalError( "texturegroups must follow model loading\n" ); + + if( !GetToken( false )) + return 0; + + if( g_numskinref == 0 ) + g_numskinref = g_numtextures; + + while( 1 ) + { + if( !GetToken( true )) + break; + + if( endofscript ) + { + if( depth != 0 ) + COM_FatalError( "missing }\n" ); + return 1; + } + + if( token[0] == '{' ) + { + depth++; + } + else if( token[0] == '}' ) + { + depth--; + if( depth == 0 ) + break; + group++; + index = 0; + } + else if( depth == 2 ) + { + int i = LookupTexture( token ); + g_texturegroup[g_numtexturegroups][group][index] = i; + if( group != 0 ) g_texture[i].parent = g_texturegroup[g_numtexturegroups][0][index]; + index++; + g_numtexturereps[g_numtexturegroups] = index; + g_numtexturelayers[g_numtexturegroups] = group + 1; + } + } + g_numtexturegroups++; + + return 0; +} + +int Cmd_Hitgroup( void ) +{ + GetToken( false ); + g_hitgroup[g_numhitgroups].group = verify_atoi( token ); + GetToken( false ); + Q_strncpy( g_hitgroup[g_numhitgroups].name, token, sizeof( g_hitgroup[0].name )); + g_numhitgroups++; + + return 0; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Cmd_Hitbox( void ) +{ + bool autogenerated = false; + + if( g_hitboxsets.Size() == 0 ) + { + g_hitboxsets.AddToTail(); + autogenerated = true; + } + + // last one + s_hitboxset_t *set = &g_hitboxsets[ g_hitboxsets.Size() - 1 ]; + + if( autogenerated ) + { + memset( set, 0, sizeof( *set ) ); + // fill in name if it wasn't specified in the .qc + Q_strncpy( set->hitboxsetname, "default", sizeof( set->hitboxsetname )); + } + + GetToken( false ); + set->hitbox[set->numhitboxes].group = verify_atoi( token ); + + // grab the bone name: + GetToken( false ); + Q_strncpy( set->hitbox[set->numhitboxes].name, token, sizeof( set->hitbox[0].name )); + + GetToken( false ); + set->hitbox[set->numhitboxes].bmin[0] = verify_atof( token ); + GetToken( false ); + set->hitbox[set->numhitboxes].bmin[1] = verify_atof( token ); + GetToken( false ); + set->hitbox[set->numhitboxes].bmin[2] = verify_atof( token ); + GetToken( false ); + set->hitbox[set->numhitboxes].bmax[0] = verify_atof( token ); + GetToken( false ); + set->hitbox[set->numhitboxes].bmax[1] = verify_atof( token ); + GetToken( false ); + set->hitbox[set->numhitboxes].bmax[2] = verify_atof( token ); + + // scale hitboxes + set->hitbox[set->numhitboxes].bmin *= g_defaultscale; + set->hitbox[set->numhitboxes].bmax *= g_defaultscale; + + TryToken(); // skip the hit box name if present: + + set->numhitboxes++; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Cmd_HitboxSet( void ) +{ + // add a new hitboxset + s_hitboxset_t *set = &g_hitboxsets[ g_hitboxsets.AddToTail() ]; + GetToken( false ); + memset( set, 0, sizeof( *set ) ); + Q_strncpy( set->hitboxsetname, token, sizeof( set->hitboxsetname )); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Cmd_BoneMerge( void ) +{ + int nIndex = g_BoneMerge.AddToTail(); + + // bone name + GetToken( false ); + Q_strncpy( g_BoneMerge[nIndex].bonename, token, sizeof( g_BoneMerge[0].bonename )); +} + + +int Cmd_Attachment( void ) +{ + Vector tmp; + int i; + + if( g_numattachments >= MAXSTUDIOATTACHMENTS ) + COM_FatalError( "too many attachments (max %d)\n", MAXSTUDIOATTACHMENTS ); + + // attachment name + GetToken( false ); + Q_strncpy( g_attachment[g_numattachments].name, token, sizeof( g_attachment[0].name )); + + // bone name + GetToken( false ); + Q_strncpy( g_attachment[g_numattachments].bonename, token, sizeof( g_attachment[0].bonename )); + + // position + GetToken( false ); + tmp.x = verify_atof( token ); + GetToken( false ); + tmp.y = verify_atof( token ); + GetToken( false ); + tmp.z = verify_atof( token ); + tmp *= g_defaultscale; + + g_attachment[g_numattachments].local.Identity(); + + while( TokenAvailable( )) + { + GetToken( false ); + + if( !Q_stricmp( token, "absolute" )) + { + g_attachment[g_numattachments].local = matrix3x4( g_vecZero, g_defaultrotation ).Invert(); + g_attachment[g_numattachments].flags |= STUDIO_ATTACHMENT_LOCAL; + g_attachment[g_numattachments].type |= IS_ABSOLUTE; + } + else if( !Q_stricmp( token, "rigid" )) + { + g_attachment[g_numattachments].flags |= STUDIO_ATTACHMENT_LOCAL; + g_attachment[g_numattachments].type |= IS_RIGID; + } + else if( !Q_stricmp( token, "world_align" )) + { + } + else if( !Q_stricmp( token, "rotate" )) + { + Vector angles = g_vecZero; + + for( i = 0; i < 3; i++ ) + { + if( !TokenAvailable( )) + break; + GetToken( false ); + angles[i] = verify_atof( token ); + } + + g_attachment[g_numattachments].local = matrix3x4( g_vecZero, angles ); + g_attachment[g_numattachments].flags |= STUDIO_ATTACHMENT_LOCAL; + } + else if( !Q_stricmp( token, "x_and_z_axes" )) + { + Vector xaxis, yaxis, zaxis; + + for( i = 0; i < 3; i++ ) + { + if( !TokenAvailable( )) + break; + + GetToken( false ); + xaxis[i] = verify_atof( token ); + } + + for( i = 0; i < 3; i++ ) + { + if (!TokenAvailable()) + break; + + GetToken( false ); + zaxis[i] = verify_atof( token ); + } + + xaxis = xaxis.Normalize(); + zaxis += xaxis * -DotProduct( zaxis, xaxis ); + zaxis = zaxis.Normalize(); + yaxis = CrossProduct( zaxis, xaxis ); + + g_attachment[g_numattachments].flags |= STUDIO_ATTACHMENT_LOCAL; + g_attachment[g_numattachments].local.SetForward( xaxis ); + g_attachment[g_numattachments].local.SetRight( yaxis ); + g_attachment[g_numattachments].local.SetUp( zaxis ); + } + else + { + MsgDev( D_WARN, "unknown attachment (%s) option\n", g_attachment[g_numattachments].name ); + while( TryToken( )); + } + } + + g_attachment[g_numattachments].local.SetOrigin( tmp ); + g_numattachments++; + + return 0; +} + +void Cmd_Renamebone( void ) +{ + // from + GetToken( false ); + Q_strncpy( g_renamedbone[g_numrenamedbones].from, token, sizeof( g_renamedbone[0].from )); + + // to + GetToken( false ); + Q_strncpy( g_renamedbone[g_numrenamedbones].to, token, sizeof( g_renamedbone[0].to )); + + g_numrenamedbones++; +} + +void Cmd_SkipTransition( void ) +{ + int nskips = 0; + int list[10]; + + while( TokenAvailable( )) + { + GetToken( false ); + list[nskips++] = LookupXNode( token ); + } + + for( int i = 0; i < nskips; i++ ) + { + for( int j = 0; j < nskips; j++ ) + { + if( list[i] != list[j] ) + { + g_xnodeskip[g_numxnodeskips][0] = list[i]; + g_xnodeskip[g_numxnodeskips][1] = list[j]; + g_numxnodeskips++; + } + } + } +} + +void Cmd_TexRenderMode( void ) +{ + GetToken( false ); + int i = LookupTexture( token ); + + GetToken( false ); + if( !Q_stricmp( token, "additive" )) + { + g_texture[i].flags |= STUDIO_NF_ADDITIVE; + } + else if( !Q_stricmp( token, "masked" )) + { + g_texture[i].flags |= STUDIO_NF_MASKED; + } + else if( !Q_stricmp( token, "masked_solid" )) + { + g_texture[i].flags |= (STUDIO_NF_MASKED|STUDIO_NF_ALPHASOLID); + } + else if( !Q_stricmp( token, "fullbright" )) + { + g_texture[i].flags |= STUDIO_NF_FULLBRIGHT; + } + else if( !Q_stricmp( token, "smooth" )) + { + g_texture[i].flags |= STUDIO_NF_SMOOTH; + } + else if( !Q_stricmp( token, "nosmooth" )) + { + g_texture[i].flags &= ~STUDIO_NF_SMOOTH; + } + else if( !Q_stricmp( token, "twoside" )) + { + g_texture[i].flags |= STUDIO_NF_TWOSIDE; + } + else MsgDev( D_WARN, "Texture '%s' has unknown render mode '%s'!\n", g_texture[i].name, token ); +} + +//----------------------------------------------------------------------------- +// Purpose: force a specific parent child relationship +//----------------------------------------------------------------------------- +void Cmd_ForcedHierarchy( void ) +{ + // child name + GetToken( false ); + Q_strncpy( g_forcedhierarchy[g_numforcedhierarchy].childname, token, sizeof( g_forcedhierarchy[0].childname )); + + // parent name + GetToken( false ); + Q_strncpy( g_forcedhierarchy[g_numforcedhierarchy].parentname, token, sizeof( g_forcedhierarchy[0].parentname )); + + g_numforcedhierarchy++; +} + +//----------------------------------------------------------------------------- +// Purpose: insert a virtual bone between a child and parent (currently unsupported) +//----------------------------------------------------------------------------- +void Cmd_InsertHierarchy( void ) +{ + // child name + GetToken( false ); + Q_strncpy( g_forcedhierarchy[g_numforcedhierarchy].childname, token, sizeof( g_forcedhierarchy[0].childname )); + + // subparent name + GetToken( false ); + Q_strncpy( g_forcedhierarchy[g_numforcedhierarchy].subparentname, token, sizeof( g_forcedhierarchy[0].subparentname )); + + // parent name + GetToken( false ); + Q_strncpy( g_forcedhierarchy[g_numforcedhierarchy].parentname, token, sizeof( g_forcedhierarchy[0].parentname )); + + g_numforcedhierarchy++; +} + +//----------------------------------------------------------------------------- +// Purpose: rotate a specific bone +//----------------------------------------------------------------------------- +void Cmd_ForceRealign( void ) +{ + // bone name + GetToken( false ); + Q_strncpy( g_forcedrealign[g_numforcedrealign].name, token, sizeof( g_forcedrealign[0].name )); + + // skip + GetToken( false ); + + // X axis + GetToken( false ); + g_forcedrealign[g_numforcedrealign].rot.x = DEG2RAD( verify_atof( token )); + + // Y axis + GetToken( false ); + g_forcedrealign[g_numforcedrealign].rot.y = DEG2RAD( verify_atof( token )); + + // Z axis + GetToken( false ); + g_forcedrealign[g_numforcedrealign].rot.z = DEG2RAD( verify_atof( token )); + + g_numforcedrealign++; +} + + +//----------------------------------------------------------------------------- +// Purpose: specify a bone to allow > 180 but < 360 rotation (forces a calculated "mid point" to rotation) +//----------------------------------------------------------------------------- +void Cmd_LimitRotation( ) +{ + // bone name + GetToken( false ); + Q_strncpy( g_limitrotation[g_numlimitrotation].name, token, sizeof( g_limitrotation[0].name )); + + while( TokenAvailable( )) + { + // sequence name + GetToken( false ); + g_limitrotation[g_numlimitrotation].sequencename[g_limitrotation[g_numlimitrotation].numseq++] = strdup( token ); + } + + g_numlimitrotation++; +} + +//----------------------------------------------------------------------------- +// Purpose: specify bones to store, even if nothing references them +//----------------------------------------------------------------------------- +void Cmd_DefineBone( void ) +{ + // bone name + GetToken( false ); + Q_strncpy( g_importbone[g_numimportbones].name, token, sizeof( g_importbone[0].name )); + + // parent name + GetToken( false ); + Q_strncpy( g_importbone[g_numimportbones].parent, token, sizeof( g_importbone[0].parent )); + + Vector pos, ang; + + // default pos + GetToken( false ); + pos.x = verify_atof( token ); + GetToken( false ); + pos.y = verify_atof( token ); + GetToken( false ); + pos.z = verify_atof( token ); + GetToken( false ); + ang.x = verify_atof( token ); + GetToken( false ); + ang.y = verify_atof( token ); + GetToken( false ); + ang.z = verify_atof( token ); + + g_importbone[g_numimportbones].rawLocal = matrix3x4( pos, ang ); + + if( TokenAvailable( )) + { + g_importbone[g_numimportbones].bPreAligned = true; + // realign pos + GetToken( false ); + pos.x = verify_atof( token ); + GetToken( false ); + pos.y = verify_atof( token ); + GetToken(false ); + pos.z = verify_atof( token ); + GetToken( false ); + ang.x = verify_atof( token ); + GetToken( false ); + ang.y = verify_atof( token ); + GetToken( false ); + ang.z = verify_atof( token ); + + g_importbone[g_numimportbones].srcRealign = matrix3x4( pos, ang ); + } + else + { + g_importbone[g_numimportbones].srcRealign.Identity(); + } + + g_numimportbones++; +} + +//---------------------------------------------------------------------------------------------- +float ParseJiggleStiffness( void ) +{ + float stiffness; + float minStiffness; // const float + float maxStiffness; // const float + + if ( !GetToken( false ) ) + { + COM_FatalError( "$jigglebone(%d): expecting stiffness value\n", linecount, line ); + return 0.0f; + } + + stiffness = verify_atof( token ); + + minStiffness = 0.0f; + maxStiffness = 1000.0f; + + return bound( minStiffness, stiffness, maxStiffness ); + +} + +//---------------------------------------------------------------------------------------------- +float ParseJiggleDamping( void ) +{ + float damping; + float minDamping; // const float + float maxDamping; // const float + + if ( !GetToken( false ) ) + { + COM_FatalError( "$jigglebone(%d): expecting damping value\n", linecount, line ); + return 0.0f; + } + + damping = verify_atof( token ); + + minDamping = 0.0f; + maxDamping = 10.0f; + + return bound( minDamping, damping, maxDamping ); +} + + +//---------------------------------------------------------------------------------------------- +int ParseJiggleAngleConstraint( s_jigglebone_t *jiggleInfo ) +{ + jiggleInfo->data.flags |= JIGGLE_HAS_ANGLE_CONSTRAINT; + + if ( !GetToken( false ) ) + { + COM_FatalError( "$jigglebone(%d): expecting angle value\n", linecount, line ); + return false; + } + + jiggleInfo->data.angleLimit = verify_atof( token ) * M_PI / 180.0f; + + return true; +} + + +//---------------------------------------------------------------------------------------------- +int ParseJiggleYawConstraint( s_jigglebone_t *jiggleInfo ) +{ + jiggleInfo->data.flags |= JIGGLE_HAS_YAW_CONSTRAINT; + + if ( !GetToken( false ) ) + { + COM_FatalError( "$jigglebone(%d): expecting minimum yaw value\n", linecount, line ); + return false; + } + + jiggleInfo->data.minYaw = verify_atof( token ) * M_PI / 180.0f; + + if ( !GetToken( false ) ) + { + COM_FatalError( "$jigglebone(%d): expecting maximum yaw value\n", linecount, line ); + return false; + } + + jiggleInfo->data.maxYaw = verify_atof( token ) * M_PI / 180.0f; + + return true; +} + +//---------------------------------------------------------------------------------------------- +int ParseJigglePitchConstraint( s_jigglebone_t *jiggleInfo ) +{ + jiggleInfo->data.flags |= JIGGLE_HAS_PITCH_CONSTRAINT; + + if ( !GetToken( false ) ) + { + COM_FatalError( "$jigglebone(%d): expecting minimum pitch value\n", linecount, line ); + return false; + } + + jiggleInfo->data.minPitch = verify_atof( token ) * M_PI / 180.0f; + + if ( !GetToken( false ) ) + { + COM_FatalError( "$jigglebone(%d): expecting maximum pitch value\n", linecount, line ); + return false; + } + + jiggleInfo->data.maxPitch = verify_atof( token ) * M_PI / 180.0f; + + return true; +} + + +//---------------------------------------------------------------------------------------------- +/** + * Parse common parameters. + * This assumes a token has already been read, and returns true if + * the token is recognized and parsed. + */ +int ParseCommonJiggle( s_jigglebone_t *jiggleInfo ) +{ + if (!stricmp( token, "tip_mass" )) + { + if ( !GetToken( false ) ) + { + return false; + } + + jiggleInfo->data.tipMass = verify_atof( token ); + } + else if (!stricmp( token, "length" )) + { + if ( !GetToken( false ) ) + { + return false; + } + + jiggleInfo->data.length = verify_atof( token ); + } + else if (!stricmp( token, "angle_constraint" )) + { + if (ParseJiggleAngleConstraint( jiggleInfo ) == false) + { + return false; + } + } + else if (!stricmp( token, "yaw_constraint" )) + { + if (ParseJiggleYawConstraint( jiggleInfo ) == false) + { + return false; + } + } + else if (!stricmp( token, "yaw_friction" )) + { + if ( !GetToken( false ) ) + { + return false; + } + + jiggleInfo->data.yawFriction = verify_atof( token ); + } + else if (!stricmp( token, "yaw_bounce" )) + { + if ( !GetToken( false ) ) + { + return false; + } + + jiggleInfo->data.yawBounce = verify_atof( token ); + } + else if (!stricmp( token, "pitch_constraint" )) + { + if (ParseJigglePitchConstraint( jiggleInfo ) == false) + { + return false; + } + } + else if (!stricmp( token, "pitch_friction" )) + { + if ( !GetToken( false ) ) + { + return false; + } + + jiggleInfo->data.pitchFriction = verify_atof( token ); + } + else if (!stricmp( token, "pitch_bounce" )) + { + if ( !GetToken( false ) ) + { + return false; + } + + jiggleInfo->data.pitchBounce = verify_atof( token ); + } + else + { + // unknown token + COM_FatalError( "$jigglebone: invalid syntax '%s'\n", token ); + return false; + } + + return true; +} + +//---------------------------------------------------------------------------------------------- +/** + * Parse parameters for is_flexible subsection + */ +int ParseFlexibleJiggle( s_jigglebone_t *jiggleInfo ) +{ + int gotOpenBracket = false; + jiggleInfo->data.flags |= (JIGGLE_IS_FLEXIBLE | JIGGLE_HAS_LENGTH_CONSTRAINT); + + while (true) + { + if (GetToken( true ) == false) + { + COM_FatalError( "$jigglebone(%d): is_flexible: parse error\n", linecount, line ); + return false; + } + + if (!stricmp( token, "{" )) + { + gotOpenBracket = true; + } + else if (!gotOpenBracket) + { + COM_FatalError( "$jigglebone(%d): is_flexible: missing '{'\n", linecount, line ); + return false; + } + else if (!stricmp( token, "}" )) + { + // definition complete + break; + } + else if (!stricmp( token, "yaw_stiffness" )) + { + jiggleInfo->data.yawStiffness = ParseJiggleStiffness(); + } + else if (!stricmp( token, "yaw_damping" )) + { + jiggleInfo->data.yawDamping = ParseJiggleStiffness(); + } + else if (!stricmp( token, "pitch_stiffness" )) + { + jiggleInfo->data.pitchStiffness = ParseJiggleStiffness(); + } + else if (!stricmp( token, "pitch_damping" )) + { + jiggleInfo->data.pitchDamping = ParseJiggleStiffness(); + } + else if (!stricmp( token, "along_stiffness" )) + { + jiggleInfo->data.alongStiffness = ParseJiggleStiffness(); + } + else if (!stricmp( token, "along_damping" )) + { + jiggleInfo->data.alongDamping = ParseJiggleStiffness(); + } + else if (!stricmp( token, "allow_length_flex" )) + { + jiggleInfo->data.flags &= ~JIGGLE_HAS_LENGTH_CONSTRAINT; + } + else if (ParseCommonJiggle( jiggleInfo ) == false) + { + COM_FatalError( "$jigglebone:is_flexible: invalid syntax '%s'\n", token ); + return false; + } + } + + return true; +} + +//---------------------------------------------------------------------------------------------- +/** + * Parse parameters for is_rigid subsection + */ +int ParseRigidJiggle( s_jigglebone_t *jiggleInfo ) +{ + int gotOpenBracket = false; + jiggleInfo->data.flags |= (JIGGLE_IS_RIGID | JIGGLE_HAS_LENGTH_CONSTRAINT); + + while (true) + { + if (GetToken( true ) == false) + { + COM_FatalError( "$jigglebone(%d):is_rigid: parse error\n", linecount, line ); + return false; + } + + if (!stricmp( token, "{" )) + { + gotOpenBracket = true; + } + else if (!gotOpenBracket) + { + COM_FatalError( "$jigglebone(%d):is_rigid: missing '{'\n", linecount, line ); + return false; + } + else if (!stricmp( token, "}" )) + { + // definition complete + break; + } + else if (ParseCommonJiggle( jiggleInfo ) == false) + { + COM_FatalError( "$jigglebone:is_rigid: invalid syntax '%s'\n", token ); + return false; + } + } + + return true; +} + +//---------------------------------------------------------------------------------------------- +/** + * Parse parameters for has_base_spring subsection + */ +bool ParseBaseSpringJiggle( s_jigglebone_t *jiggleInfo ) +{ + int gotOpenBracket = false; + jiggleInfo->data.flags |= JIGGLE_HAS_BASE_SPRING; + + while (true) + { + if (GetToken( true ) == false) + { + COM_FatalError( "$jigglebone(%d):is_rigid: parse error\n", linecount, line ); + return false; + } + + if (!stricmp( token, "{" )) + { + gotOpenBracket = true; + } + else if (!gotOpenBracket) + { + COM_FatalError( "$jigglebone(%d):is_rigid: missing '{'\n", linecount, line ); + return false; + } + else if (!stricmp( token, "}" )) + { + // definition complete + break; + } + else if (!stricmp( token, "stiffness" )) + { + jiggleInfo->data.baseStiffness = ParseJiggleStiffness(); + } + else if (!stricmp( token, "damping" )) + { + jiggleInfo->data.baseDamping = ParseJiggleStiffness(); + } + else if (!stricmp( token, "left_constraint" )) + { + if ( !GetToken( false ) ) + { + return false; + } + + jiggleInfo->data.baseMinLeft = verify_atof( token ); + + if ( !GetToken( false ) ) + { + return false; + } + + jiggleInfo->data.baseMaxLeft = verify_atof( token ); + } + else if (!stricmp( token, "left_friction" )) + { + if ( !GetToken( false ) ) + { + return false; + } + + jiggleInfo->data.baseLeftFriction = verify_atof( token ); + } + else if (!stricmp( token, "up_constraint" )) + { + if ( !GetToken( false ) ) + { + return false; + } + + jiggleInfo->data.baseMinUp = verify_atof( token ); + + if ( !GetToken( false ) ) + { + return false; + } + + jiggleInfo->data.baseMaxUp = verify_atof( token ); + } + else if (!stricmp( token, "up_friction" )) + { + if ( !GetToken( false ) ) + { + return false; + } + + jiggleInfo->data.baseUpFriction = verify_atof( token ); + } + else if (!stricmp( token, "forward_constraint" )) + { + if ( !GetToken( false ) ) + { + return false; + } + + jiggleInfo->data.baseMinForward = verify_atof( token ); + + if ( !GetToken( false ) ) + { + return false; + } + + jiggleInfo->data.baseMaxForward = verify_atof( token ); + } + else if (!stricmp( token, "forward_friction" )) + { + if ( !GetToken( false ) ) + { + return false; + } + + jiggleInfo->data.baseForwardFriction = verify_atof( token ); + } + else if (!stricmp( token, "base_mass" )) + { + if ( !GetToken( false ) ) + { + return false; + } + + jiggleInfo->data.baseMass = verify_atof( token ); + } + else if (ParseCommonJiggle( jiggleInfo ) == false) + { + COM_FatalError( "$jigglebone:has_base_spring: invalid syntax '%s'\n", token ); + return false; + } + } + + return true; +} + +/* +================= +================= +*/ +void Cmd_JiggleBone( void ) +{ + s_jigglebone_t *jiggleInfo = &g_jigglebones[g_numjigglebones]; + int gotOpenBracket = false; + + // bone name + GetToken( false ); + Q_strncpy( jiggleInfo->bonename, token, sizeof( jiggleInfo->bonename )); + + // default values + memset( &jiggleInfo->data, 0, sizeof( mstudiojigglebone_t ) ); + jiggleInfo->data.length = 10.0f; + jiggleInfo->data.yawStiffness = 100.0f; + jiggleInfo->data.pitchStiffness = 100.0f; + jiggleInfo->data.alongStiffness = 100.0f; + jiggleInfo->data.baseStiffness = 100.0f; + jiggleInfo->data.baseMinUp = -100.0f; + jiggleInfo->data.baseMaxUp = 100.0f; + jiggleInfo->data.baseMinLeft = -100.0f; + jiggleInfo->data.baseMaxLeft = 100.0f; + jiggleInfo->data.baseMinForward = -100.0f; + jiggleInfo->data.baseMaxForward = 100.0f; + + while( 1 ) + { + if( !GetToken( true )) + { + COM_FatalError( "$jigglebone(%d): parse error\n", linecount, line ); + return; + } + + if (!stricmp( token, "{" )) + { + gotOpenBracket = true; + } + else if (!gotOpenBracket) + { + COM_FatalError( "$jigglebone(%d): missing '{'\n", linecount, line ); + return; + } + else if (!stricmp( token, "}" )) + { + // definition complete + break; + } + else if (!stricmp( token, "is_flexible" )) + { + if (ParseFlexibleJiggle( jiggleInfo ) == false) + { + return; + } + } + else if (!stricmp( token, "is_rigid" )) + { + if (ParseRigidJiggle( jiggleInfo ) == false) + { + return; + } + } + else if (!stricmp( token, "has_base_spring" )) + { + if( ParseBaseSpringJiggle( jiggleInfo ) == false) + { + return; + } + } + else + { + COM_FatalError( "$jigglebone: invalid syntax '%s'\n", token ); + return; + } + } + + MsgDev( D_INFO, "Marking bone %s as a jiggle bone\n", jiggleInfo->bonename ); + g_numjigglebones++; +} + +void Grab_AxisInterpBones( void ) +{ + char cmd[1024], tmp[1025]; + s_axisinterpbone_t *pBone = &g_axisinterpbones[g_numaxisinterpbones]; + s_axisinterpbone_t *pAxis = NULL; + Vector basepos; + + while( GetLineInput( )) + { + if( IsEnd( line )) + return; + + int i = sscanf( line, "%1023s \"%[^\"]\" \"%[^\"]\" \"%[^\"]\" \"%[^\"]\" %d", + cmd, pBone->bonename, tmp, pBone->controlname, tmp, &pBone->axis ); + + if( i == 6 && !Q_stricmp( cmd, "bone" )) + { + pAxis = pBone; + pBone->axis = pBone->axis - 1; // MAX uses 1..3, engine 0..2 + g_numaxisinterpbones++; + pBone = &g_axisinterpbones[g_numaxisinterpbones]; + } + else if( !Q_stricmp( cmd, "display" )) + { + // skip all display info + } + else if( !Q_stricmp( cmd, "type" )) + { + // skip all type info + } + else if( !Q_stricmp( cmd, "basepos" )) + { + i = sscanf( line, "basepos %f %f %f", &basepos.x, &basepos.y, &basepos.z ); + // skip all type info + } + else if( !Q_stricmp( cmd, "axis" )) + { + Vector pos, rot; + int j; + + i = sscanf( line, "axis %d %f %f %f %f %f %f", &j, &pos[0], &pos[1], &pos[2], &rot[0], &rot[1], &rot[2] ); + if( i == 7 ) + { + pAxis->pos[j] = basepos + pos; + AngleQuaternion( rot, pAxis->quat[j] ); + } + } + } +} + +bool Grab_AimAtBones( void ) +{ + s_aimatbone_t *pAimAtBone = &g_aimatbones[g_numaimatbones]; + + // already know it's in the first string, otherwise wouldn't be here + if( sscanf( line, "%*s %127s %127s %127s", pAimAtBone->bonename, pAimAtBone->parentname, pAimAtBone->aimname ) == 3 ) + { + g_numaimatbones++; + + char cmd[1024]; + Vector vector; + + while( GetLineInput( )) + { + if( IsEnd( line )) + return false; + + if( sscanf( line, "%1024s %f %f %f", cmd, &vector[0], &vector[1], &vector[2] ) != 4 ) + { + bool allSpace = true; + + // Allow blank lines to be skipped without error + for( const char *pC = line; *pC != '\0' && pC < ( line + 4096 ); ++pC ) + { + if( !isspace( *pC )) + { + allSpace = false; + break; + } + } + + if( allSpace ) + { + continue; + } + + return true; + } + + if( !Q_stricmp( cmd, "" )) + { + // make sure these are unit length on read + pAimAtBone->aimvector = vector.Normalize(); + } + else if( !Q_stricmp( cmd, "" )) + { + // make sure these are unit length on read + pAimAtBone->upvector = vector.Normalize(); + } + else if( !Q_stricmp( cmd, "" )) + { + pAimAtBone->basepos = vector; + } + else + { + return true; + } + } + } + + // If we get here, we're at EOF + return false; +} + +void Grab_QuatInterpBones( void ) +{ + char cmd[1024]; + Radian rotateaxis( 0.0f, 0.0f, 0.0f ); + Radian jointorient( 0.0f, 0.0f, 0.0f ); + s_quatinterpbone_t *pBone = &g_quatinterpbones[g_numquatinterpbones]; + s_quatinterpbone_t *pAxis = NULL; + Vector basepos; + + while( GetLineInput( )) + { + if( IsEnd( line )) + return; + + int i = sscanf( line, "%s %s %s %s %s", cmd, pBone->bonename, pBone->parentname, pBone->controlparentname, pBone->controlname ); + + while( i == 4 && !Q_stricmp( cmd, "" )) + { + // if Grab_AimAtBones() returns false, there file is at EOF + if( !Grab_AimAtBones( )) + { + return; + } + + // Grab_AimAtBones will read input into line same as here until it gets a line it doesn't understand, at which point + // it will exit leaving that line in line, so check for the end and scan the current buffer again and continue on with + // the normal QuatInterpBones process + i = sscanf( line, "%s %s %s %s %s", cmd, + pBone->bonename, pBone->parentname, pBone->controlparentname, pBone->controlname ); + } + + if( i == 5 && !Q_stricmp( cmd, "" )) + { + pAxis = pBone; + g_numquatinterpbones++; + pBone = &g_quatinterpbones[g_numquatinterpbones]; + } + else if( i > 0 ) + { + // There was a bug before which could cause the same command to be parsed twice + // because if the sscanf above completely fails, it will return 0 and not + // change the contents of cmd, so i should be greater than 0 in order for + // any of these checks to be valid... Still kind of buggy as these checks + // do case insensitive stricmp but then the sscanf does case sensitive + // matching afterwards... Should probably change those to + // sscanf( line, "%*s %f ... ) etc... + + if( !Q_stricmp( cmd, "" )) + { + // skip all display info + Vector size; + float distance; + + i = sscanf( line, " %f %f %f %f", &size[0], &size[1], &size[2], &distance ); + + if( i == 4 ) + { + pAxis->percentage = distance / 100.0f; + pAxis->size = size; + } + else + { + COM_FatalError( "Line %d: Unable to parse procedual bone: %s", linecount, line ); + } + } + else if( !Q_stricmp( cmd, "" )) + { + i = sscanf( line, " %f %f %f", &basepos.x, &basepos.y, &basepos.z ); + // skip all type info + } + else if( !Q_stricmp( cmd, "" )) + { + i = sscanf( line, "%*s %f %f %f", &rotateaxis.x, &rotateaxis.y, &rotateaxis.z ); + rotateaxis.x = DEG2RAD( rotateaxis.x ); + rotateaxis.y = DEG2RAD( rotateaxis.y ); + rotateaxis.z = DEG2RAD( rotateaxis.z ); + } + else if( !Q_stricmp( cmd, "" )) + { + i = sscanf( line, "%*s %f %f %f", &jointorient.x, &jointorient.y, &jointorient.z ); + jointorient.x = DEG2RAD( jointorient.x ); + jointorient.y = DEG2RAD( jointorient.y ); + jointorient.z = DEG2RAD( jointorient.z ); + } + else if( !Q_stricmp( cmd, "" )) + { + float tolerance; + Radian trigger; + Vector pos, rot; + Radian ang; + int j; + + i = sscanf( line, " %f %f %f %f %f %f %f %f %f %f", + &tolerance, + &trigger.x, &trigger.y, &trigger.z, + &ang.x, &ang.y, &ang.z, + &pos.x, &pos.y, &pos.z ); + + if( i == 10 ) + { + trigger.x = DEG2RAD( trigger.x ); + trigger.y = DEG2RAD( trigger.y ); + trigger.z = DEG2RAD( trigger.z ); + ang.x = DEG2RAD( ang.x ); + ang.y = DEG2RAD( ang.y ); + ang.z = DEG2RAD( ang.z ); + + Vector4D q, q1, q2; + + AngleQuaternion( ang, q ); + + if( rotateaxis.x != 0.0 || rotateaxis.y != 0.0 || rotateaxis.z != 0.0 ) + { + AngleQuaternion( rotateaxis, q1 ); + QuaternionMult( q1, q, q2 ); + q = q2; + } + + if( jointorient != g_vecZero ) + { + AngleQuaternion( jointorient, q1 ); + QuaternionMult( q, q1, q2 ); + q = q2; + } + + j = pAxis->numtriggers++; + pAxis->tolerance[j] = DEG2RAD( tolerance ); + AngleQuaternion( trigger, pAxis->trigger[j] ); + pAxis->pos[j] = basepos + pos; + pAxis->quat[j] = q; + } + else + { + COM_FatalError( "Line %d: Unable to parse procedual bone: %s", linecount, line ); + } + } + else + { + COM_FatalError( "Line %d: Unable to parse procedual bone data: %s", linecount, line ); + } + } + else + { + // Allow blank lines to be skipped without error + bool allSpace = true; + + for( const char *pC = line; *pC != '\0' && pC < ( line + 4096 ); ++pC ) + { + if( !isspace( *pC )) + { + allSpace = false; + break; + } + } + + if( !allSpace ) + { + COM_FatalError( "Line %d: Unable to parse procedual bone data: %s", linecount, line ); + } + } + } +} + +void Load_ProceduralBones( void ) +{ + char filename[256]; + char shortname[64]; + char cmd[1024]; + int option; + + GetToken( false ); + Q_snprintf( filename, sizeof( filename ), "%s/%s", cddir[numdirs], token ); + COM_DefaultExtension( filename, ".vrd" ); + + if( !COM_FileExists( filename )) + { + MsgDev( D_ERROR, "%s doesn't exist\n", filename ); + return; + } + + if(( input = fopen( filename, "r" )) == 0 ) + { + MsgDev( D_ERROR, "%s couldn't be open\n", filename ); + return; + } + + COM_FileBase( filename, shortname ); + MsgDev( D_INFO, "grabbing: %s.%s\t\t[^3bones^7]\n", shortname, COM_FileExtension( filename )); + linecount = 0; + + if( !Q_stricmp( COM_FileExtension( filename ), "vrd" )) + { + Grab_QuatInterpBones( ); + } + else + { + while( GetLineInput( )) + { + sscanf( line, "%s", cmd, &option ); + if( !Q_stricmp( cmd, "version" )) + { + if( option != 1 ) + COM_FatalError( "%s version %i should be 1\n", filename, option ); + } + else if( !Q_stricmp( cmd, "proceduralbones" )) + { + Grab_AxisInterpBones( ); + } + } + } + + fclose( input ); +} + +void Cmd_KeyValues( void ) +{ + Option_KeyValues( &g_KeyValueText ); +} + +void Cmd_AlwaysCollapse( void ) +{ + GetToken( false ); + int nIndex = g_collapse.AddToTail(); + Q_strncpy( g_collapse[nIndex].bonename, token, sizeof( g_collapse[0].bonename )); + g_collapse_bones = true; +} + +void Cmd_Pushd( void ) +{ + GetToken( false ); + + Q_strncpy( cddir[numdirs+1], cddir[numdirs], sizeof( cddir[0] )); + Q_strncat( cddir[numdirs+1], token, sizeof( cddir[0] )); + Q_strncat( cddir[numdirs+1], "/", sizeof( cddir[0] )); + numdirs++; +} + +void Cmd_Popd( void ) +{ + if( numdirs > 0 ) + numdirs--; +} + +void Cmd_Lod( void ) +{ + ParseEmpty(); +} + +void Cmd_ShadowLod( void ) +{ + ParseEmpty(); +} + +/* +=============== +ParseScript +=============== +*/ +void ParseScript( void ) +{ + while( 1 ) + { + do + { // look for a line starting with a $ command + GetToken( true ); + if( endofscript ) + return; + if( token[0] == '$' ) + break; + while( TryToken( )); + } while( 1 ); + + if( !Q_stricmp( token, "$modelname" )) + { + Cmd_Modelname (); + } + else if( !Q_stricmp( token, "$cd" )) + { + if( cdset ) COM_FatalError ("Two $cd in one model"); + GetToken( false ); + Q_strncpy( cddir[0], COM_ExpandArg( token ), sizeof( cddir[0] )); + cdset = true; + } + else if( !Q_stricmp( token, "$cdtexture" ) || !Q_stricmp( token, "$cdmaterials" )) + { + while( TokenAvailable( )) + { + GetToken( false ); + Q_strncpy( cdtexture[cdtextureset], COM_ExpandArg( token ), sizeof( cdtexture[0] )); + cdtextureset++; + } + } + else if( !Q_stricmp( token, "$scale" )) + { + Cmd_ScaleUp (); + } + else if( !Q_stricmp( token, "$scale_x" )) + { + Cmd_ScaleAxis( 1 ); // x&y swapped in studio + } + else if( !Q_stricmp( token, "$scale_y" )) + { + Cmd_ScaleAxis( 0 ); // x&y swapped in studio + } + else if( !Q_stricmp( token, "$scale_z" )) + { + Cmd_ScaleAxis( 2 ); + } + else if( !Q_stricmp( token, "$root" ) || !Q_stricmp( token, "$rootbone" )) + { + Cmd_Root (); + } + else if( !Q_stricmp( token, "$pushd" )) + { + Cmd_Pushd(); + } + else if( !Q_stricmp( token, "$popd" )) + { + Cmd_Popd(); + } + else if( !Q_stricmp( token, "$alwayscollapse" )) + { + Cmd_AlwaysCollapse (); + } + else if( !Q_stricmp( token, "$controller" )) + { + Cmd_Controller (); + } + else if (!stricmp( token, "$screenalign" )) + { + Cmd_ScreenAlign( ); + } + else if (!stricmp (token, "$model")) + { + Cmd_Model(); + } + else if( !Q_stricmp( token, "$body" )) + { + Cmd_Body(); + } + else if( !Q_stricmp( token, "$bodygroup" )) + { + Cmd_Bodygroup(); + } + else if (!stricmp (token, "$animation" )) + { + Cmd_Animation(); + } + else if( !Q_stricmp( token, "$cmdlist" )) + { + Cmd_Cmdlist (); + } + else if( !Q_stricmp( token, "$sequence" )) + { + Cmd_Sequence (); + } + else if( !Q_stricmp( token, "$append" )) + { + Cmd_Append (); + } + else if( !Q_stricmp( token, "$prepend" )) + { + Cmd_Append (); + } + else if( !Q_stricmp( token, "$continue" )) + { + Cmd_Append (); + } + else if( !Q_stricmp( token, "$sequencegroup" )) + { + Cmd_SequenceGroup (); + } + else if( !Q_stricmp( token, "$sequencegroupsize" )) + { + Cmd_SequenceGroupSize (); + } + else if( !Q_stricmp( token, "$weightlist" )) + { + Cmd_Weightlist (); + } + else if( !Q_stricmp( token, "$defaultweightlist" )) + { + Cmd_DefaultWeightlist (); + } + else if( !Q_stricmp( token, "$ikchain" )) + { + Cmd_IKChain (); + } + else if( !Q_stricmp( token, "$ikautoplaylock" )) + { + Cmd_IKAutoplayLock (); + } + else if( !Q_stricmp( token, "$eyeposition" )) + { + Cmd_Eyeposition (); + } + else if( !Q_stricmp( token, "$heirarchy" )) + { + Cmd_ForcedHierarchy (); + } + else if( !Q_stricmp( token, "$hierarchy" )) + { + Cmd_ForcedHierarchy (); + } + else if( !Q_stricmp( token, "$insertbone" )) + { + Cmd_InsertHierarchy (); + } + else if( !Q_stricmp( token, "$limitrotation" )) + { + Cmd_LimitRotation (); + } + else if( !Q_stricmp( token, "$forcerealign" )) + { + Cmd_ForceRealign (); + } + else if( !Q_stricmp( token, "$origin" )) + { + Cmd_Origin (); + } + else if (!stricmp (token, "$upaxis")) + { + Cmd_UpAxis( ); + } + else if( !Q_stricmp( token, "$bbox" )) + { + Cmd_BBox (); + } + else if( !Q_stricmp( token, "$cbox" )) + { + Cmd_CBox (); + } + else if( !Q_stricmp( token, "$gamma" )) + { + Cmd_Gamma (); + } + else if( !Q_stricmp( token, "$flags" )) + { + Cmd_Flags (); + } + else if( !Q_stricmp( token, "$texturegroup" )) + { + Cmd_TextureGroup (); + } + else if( !Q_stricmp( token, "$skiptransition" )) + { + Cmd_SkipTransition (); + } + else if( !Q_stricmp( token, "$calctransitions" )) + { + g_multistagegraph = 1; + } + else if( !Q_stricmp( token, "$staticprop" )) + { + gflags |= STUDIO_STATIC_PROP; + g_staticprop = true; + } + else if( !Q_stricmp( token, "$autocenter" )) + { + g_centerstaticprop = true; + } + else if( !Q_stricmp( token, "$hgroup" )) + { + Cmd_Hitgroup (); + } + else if( !Q_stricmp( token, "$hbox" )) + { + Cmd_Hitbox (); + } + else if( !Q_stricmp( token, "$hboxset" )) + { + Cmd_HitboxSet (); + } + else if( !Q_stricmp( token, "$attachment" )) + { + Cmd_Attachment (); + } + else if( !Q_stricmp( token, "$bonemerge" )) + { + Cmd_BoneMerge (); + } + else if( !Q_stricmp( token, "$lod" )) + { + Cmd_Lod (); + } + else if( !Q_stricmp( token, "$shadowlod" )) + { + Cmd_ShadowLod (); + } + else if( !Q_stricmp( token, "$externaltextures" )) + { + split_textures = 1; + } + else if( !Q_stricmp( token, "$cliptotextures" )) + { + clip_texcoords = 1; + } + else if( !Q_stricmp( token, "$mergecontrollers" )) + { + g_mergebonecontrollers = 1; + } + else if( !Q_stricmp( token, "$fixedcoords" )) + { + store_uv_coords = 1; + } + else if( !Q_stricmp( token, "$freecords" )) + { + allow_tileing = 1; + store_uv_coords = 1; + } + else if( !Q_stricmp( token, "$freecoords" )) + { + allow_tileing = 1; + store_uv_coords = 1; + } + else if( !Q_stricmp( token, "$boneweights" )) + { + allow_boneweights = 1; + } + else if( !Q_stricmp( token, "$lockbonelengths" )) + { + g_lockbonelengths = 1; + } + else if( !Q_stricmp( token, "$renamebone" )) + { + Cmd_Renamebone (); + } + else if( !Q_stricmp( token, "$definebone" )) + { + Cmd_DefineBone (); + } + else if( !Q_stricmp( token, "$realignbones" )) + { + g_realignbones = 1; + } + else if( !Q_stricmp( token, "$unlockdefinebones" )) + { + g_overridebones = 1; + } + else if( !Q_stricmp( token, "$texrendermode" )) + { + Cmd_TexRenderMode(); + } + else if( !Q_stricmp( token, "$jigglebone" )) + { + Cmd_JiggleBone (); + } + else if( !Q_stricmp( token, "$proceduralbones" )) + { + Load_ProceduralBones( ); + } + else if( !Q_stricmp( token, "$poseparameter" )) + { + Cmd_PoseParameter( ); + } + else if( !Q_stricmp( token, "$keyvalues" )) + { + Cmd_KeyValues( ); + } + else if( !Q_stricmp( token, "$collapsebones" )) + { + g_collapse_bones = true; + } + else if( !Q_stricmp( token, "$collapsebonesaggressive" )) + { + g_collapse_bones = true; + g_collapse_bones_aggressive = true; + } + else + { + if( token[0] == '$' ) + MsgDev( D_REPORT, "^2Warning:^7 unknown command %s\n", token ); + else MsgDev( D_WARN, "unknown command %s\n", token ); + while( TryToken( )); // skip at rest of the line + } + } +} + +int main( int argc, char **argv ) +{ + int i; + char path[1024]; + + atexit( Sys_CloseLog ); + COM_InitCmdlib( argv, argc ); + + // impicit path + Q_strncpy( cddir[0], ".\\", sizeof( cddir[0] )); + g_numweightlist = 1; // skip weightlist 0 + g_defaultrotation = Radian( 0.0f, 0.0f, M_PI / 2 ); + g_normal_blend = cos( DEG2RAD( 2.0 )); + g_defaultscale = 1.0f; + + numrep = 0; + tag_reversed = 0; + tag_normals = 0; + flip_triangles = 1; + g_lockbonelengths = 0; + g_overridebones = 0; + maxseqgroupsize = 1024 * 1024; + g_multistagegraph = 0; + allow_boneweights = 0; + has_boneweights = 0; + g_gamma = 1.8f; + + if( argc == 1 ) + { + Msg( " P2:Savior Studio Model Compiler\n" ); + Msg( " XashXT Group 2018(^1c^7)\n\n\n" ); + + Msg( "usage: studiomdl file.qc\n" + "\nlist options:\n" + "^2-t^7 - replace all model textures with specified\n" + "^2-r^7 - tag reversed\n" + "^2-n^7 - tag bad normals\n" + "^2-f^7 - flip all triangles\n" + "^2-a^7 - normal blend angle\n" + "^2-h^7 - dump hitboxes\n" + "^2-g^7 - dump transition graph\n" + "^2-dev^7 - shows developer messages\n" + "\n\t\tPress any key to exit" ); + + system( "pause>nul" ); + return 1; + } + + Sys_InitLog( "studiomdl.log" ); + + Msg( " P2:Savior Studio Model Compiler\n" ); + Msg( " XashXT Group 2018(^1c^7)\n\n\n" ); + + for( i = 1; i < argc - 1; i++ ) + { + if( argv[i][0] == '-' ) + { + if( !Q_stricmp( argv[i], "-dev" )) + { + SetDeveloperLevel( verify_atoi( argv[i+1] )); + i++; + continue; + } + switch( argv[i][1] ) + { + case 't': + i++; + Q_strncpy( defaulttexture[numrep], argv[i], sizeof( defaulttexture[0] )); + if( i < argc - 2 && argv[i + 1][0] != '-' ) + { + i++; + Q_strncpy( sourcetexture[numrep], argv[i], sizeof( sourcetexture[0] )); + MsgDev( D_INFO, "Replacing %s with %s\n", sourcetexture[numrep], sizeof( defaulttexture[0] )); + } + MsgDev( D_INFO, "Using default texture: %s\n", defaulttexture ); + numrep++; + break; + case 'r': + tag_reversed = 1; + break; + case 'n': + tag_normals = 1; + break; + case 'f': + flip_triangles = 0; + break; + case 'a': + i++; + g_normal_blend = cos( DEG2RAD( verify_atof( argv[i] ))); + break; + case 'h': + g_dump_hboxes = true; + break; + case 'g': + g_dump_graph = true; + break; + } + } + } + + if( !argv[i] ) + { + MsgDev( D_ERROR, "No inputfile specified\n" ); + return 1; + } + + Q_strcpy( g_sequencegroup[g_numseqgroups].label, "default" ); + g_numseqgroups = 1; + + // load the script + Q_strncpy( path, argv[i], sizeof( path )); + Q_strncpy( outname, path, sizeof( outname )); + COM_DefaultExtension( path, ".qc" ); + COM_StripExtension( outname ); + LoadScriptFile( path ); + + // parse it + ParseScript (); + SetSkinValues (); + SimplifyModel (); + WriteFile (); + ClearModel (); + + SetDeveloperLevel( D_REPORT ); + Mem_Check(); // report leaks + + return 0; +} diff --git a/utils/studiomdl/studiomdl.dsp b/utils/studiomdl/studiomdl.dsp new file mode 100644 index 0000000..457dedc --- /dev/null +++ b/utils/studiomdl/studiomdl.dsp @@ -0,0 +1,210 @@ +# Microsoft Developer Studio Project File - Name="studiomdl" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=studiomdl - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "studiomdl.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "studiomdl.mak" CFG="studiomdl - Win32 Release" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "studiomdl - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "studiomdl - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/SDKSrc/Tools/utils/studiomdl", IVGBAAAA" +# PROP Scc_LocalPath "." +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "studiomdl - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir ".\Release" +# PROP BASE Intermediate_Dir ".\Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "..\..\temp\studiomdl\!release" +# PROP Intermediate_Dir "..\..\temp\studiomdl\!release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MD /W3 /GX /O2 /I "..\..\game_shared" /I "../common" /I "..\..\engine" /I "..\..\dlls" /I "../../common" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "IGNORE_SEARCH_IN_WADS" /FD /c +# SUBTRACT CPP /Fr /YX +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 msvcrt.lib /nologo /subsystem:console /pdb:none /machine:I386 /nodefaultlib:"libc.lib" +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\studiomdl\!release +InputPath=\Paranoia2\src_main\temp\studiomdl\!release\studiomdl.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\studiomdl.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\studiomdl.exe "D:\Paranoia2\tools\studiomdl.exe" + +# End Custom Build + +!ELSEIF "$(CFG)" == "studiomdl - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir ".\Debug" +# PROP BASE Intermediate_Dir ".\Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "..\..\temp\studiomdl\!debug" +# PROP Intermediate_Dir "..\..\temp\studiomdl\!debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /YX /c +# ADD CPP /nologo /MDd /W3 /Gm /Gi /GX /ZI /Od /I "..\..\game_shared" /I "../common" /I "..\..\engine" /I "..\..\dlls" /I "../../common" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "IGNORE_SEARCH_IN_WADS" /FAs /FR /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:console /debug /machine:I386 +# ADD LINK32 msvcrtd.lib /nologo /subsystem:console /debug /machine:I386 /nodefaultlib:"libcd.lib" +# Begin Custom Build +TargetDir=\Paranoia2\src_main\temp\studiomdl\!debug +InputPath=\Paranoia2\src_main\temp\studiomdl\!debug\studiomdl.exe +SOURCE="$(InputPath)" + +"D:\Paranoia2\tools\studiomdl.exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\studiomdl.exe "D:\Paranoia2\tools\studiomdl.exe" + +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "studiomdl - Win32 Release" +# Name "studiomdl - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat;for;f90" +# Begin Source File + +SOURCE=..\common\cmdlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\conprint.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\filesystem.cpp +# End Source File +# Begin Source File + +SOURCE=.\imagelib.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\mathlib.cpp +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\matrix.cpp +# End Source File +# Begin Source File + +SOURCE=.\optimize.cpp +# End Source File +# Begin Source File + +SOURCE=.\quantizer.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\scriplib.cpp +# End Source File +# Begin Source File + +SOURCE=.\simpilfy.cpp +# End Source File +# Begin Source File + +SOURCE=.\skin.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\stringlib.cpp +# End Source File +# Begin Source File + +SOURCE=.\studiomdl.cpp +# End Source File +# Begin Source File + +SOURCE=.\write.cpp +# End Source File +# Begin Source File + +SOURCE=..\common\zone.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl;fi;fd" +# Begin Source File + +SOURCE=.\builtin.h +# End Source File +# Begin Source File + +SOURCE=..\common\cmdlib.h +# End Source File +# Begin Source File + +SOURCE=.\imagelib.h +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\mathlib.h +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\matrix.h +# End Source File +# Begin Source File + +SOURCE=..\common\scriplib.h +# End Source File +# Begin Source File + +SOURCE=..\..\engine\studio.h +# End Source File +# Begin Source File + +SOURCE=.\studiomdl.h +# End Source File +# Begin Source File + +SOURCE=..\..\game_shared\vector.h +# End Source File +# End Group +# End Target +# End Project diff --git a/utils/studiomdl/studiomdl.h b/utils/studiomdl/studiomdl.h new file mode 100644 index 0000000..45c3bc9 --- /dev/null +++ b/utils/studiomdl/studiomdl.h @@ -0,0 +1,905 @@ +#include + +#ifndef EXTERN +#define EXTERN extern +#endif + +#define MAXSTUDIOTRIANGLES 65535 // max triangles per model +#define MAXSRCSTUDIOVERTS 65535 // max source vertices per submodel +#define MAXSTUDIOCMDS 64 +#define MAXSTUDIOMOVEKEYS 64 +#define MAXSTUDIOIKRULES 64 +#define MAXSTUDIOSRCBONES 512 // bones allowed at source movement +#define MAXSTUDIOANIMATIONS 512 // max frames per sequence +#define MAXSTUDIOANIMFRAMES 510 // max frames per sequence +#define MAXSTUDIOSEQUENCES 1024 // total animation sequences +#define MAXSTUDIOEVENTS 64 // events per sequence +#define MAXSTUDIOMESHES 256 // max textures per model +#define MAXSTUDIOBLENDS 16 // max anim blends +#define MAXSRCSTUDIONAME 128 // source names can be longer than output +#define MAXWEIGHTLISTS 128 +#define MAXWEIGHTSPERLIST (MAXSTUDIOBONES) + +#define verify_atof( a ) verify_atof_dbg( a, __LINE__ ) +#define verify_atoi( a ) verify_atoi_dbg( a, __LINE__ ) + +EXTERN char outname[1024]; +EXTERN bool cdset; +EXTERN char cddir[32][256]; +EXTERN int numdirs; +EXTERN int cdtextureset; +EXTERN char cdtexture[16][256]; +EXTERN char rootname[MAXSTUDIONAME]; // name of the root bone +EXTERN Vector g_defaultscale; +EXTERN Radian g_defaultrotation; +EXTERN Vector g_defaultadjust; +EXTERN char defaulttexture[16][256]; +EXTERN char sourcetexture[16][256]; +EXTERN int numrep; +EXTERN int tag_reversed; +EXTERN int tag_normals; +EXTERN bool flip_triangles; +EXTERN float g_normal_blend; +EXTERN bool g_dump_hboxes; +EXTERN bool g_dump_graph; +EXTERN Vector eyeposition; +EXTERN int gflags; +EXTERN Vector bbox[2]; +EXTERN Vector cbox[2]; +EXTERN bool g_wrotebbox; +EXTERN bool g_wrotecbox; +EXTERN bool g_collapse_bones; +EXTERN bool g_collapse_bones_aggressive; +EXTERN bool g_lockbonelengths; +EXTERN int maxseqgroupsize; +EXTERN bool g_multistagegraph; +EXTERN int split_textures; +EXTERN int clip_texcoords; +EXTERN bool store_uv_coords; +EXTERN bool allow_tileing; +EXTERN bool allow_boneweights; +EXTERN bool has_boneweights; +EXTERN bool g_mergebonecontrollers; +EXTERN bool g_realignbones; +EXTERN bool g_definebones; +EXTERN bool g_overridebones; +EXTERN bool g_centerstaticprop; +EXTERN bool g_staticprop; +EXTERN float g_gamma; +extern CUtlArray< char > g_KeyValueText; + +typedef struct +{ + int vertindex; + int normindex; // index into normal array + int s, t; + float u, v; +} s_trianglevert_t; + +typedef struct +{ + int numbones; + float weight[4]; + int bone[4]; +} s_boneweight_t; + +typedef struct +{ + Vector org; // original position + s_boneweight_t globalWeight; +} s_vertex_t; + +typedef struct +{ + int skinref; + Vector org; // original position + s_boneweight_t globalWeight; +} s_normal_t; + +typedef struct +{ + int skinref; + Vector vert, norm; + s_boneweight_t localWeight; + s_boneweight_t globalWeight; +} s_srcvertex_t; + +typedef struct +{ + char name[MAXSRCSTUDIONAME];// bone name for symbolic links + int parent; // parent bone + int bonecontroller; // -1 == 0 + int flags; // X, Y, Z, XR, YR, ZR + Vector pos; // default pos + Vector posscale; // pos values scale + Radian rot; // default pos + Vector rotscale; // rotation values scale + int group; // hitgroup + Vector bmin, bmax; // bounding box + matrix3x4 rawLocal; + matrix3x4 rawLocalOriginal; // original transform of preDefined bone + matrix3x4 srcRealign; + matrix3x4 boneToPose; + int proceduralindex; + Vector4D qAlignment; + bool bPreDefined; + bool bPreAligned; + bool bDontCollapse; +} s_bonetable_t; + +EXTERN s_bonetable_t g_bonetable[MAXSTUDIOSRCBONES]; +EXTERN int g_real_numbones; +EXTERN int g_numbones; + +typedef struct +{ + char from[MAXSRCSTUDIONAME]; + char to[MAXSRCSTUDIONAME]; +} s_renamebone_t; + +EXTERN s_renamebone_t g_renamedbone[MAXSTUDIOSRCBONES]; +EXTERN int g_numrenamedbones; + +typedef struct +{ + char name[MAXSRCSTUDIONAME]; + char parent[MAXSRCSTUDIONAME]; + matrix3x4 rawLocal; + bool bPreAligned; + matrix3x4 srcRealign; +} s_importbone_t; + +EXTERN s_importbone_t g_importbone[MAXSTUDIOSRCBONES]; +EXTERN int g_numimportbones; + +typedef struct +{ + char name[MAXSRCSTUDIONAME];// bone name + int bone; + int group; // hitgroup + int model; + Vector bmin, bmax; // bounding box +} s_bbox_t; + +typedef struct +{ + char hitboxsetname[MAXSRCSTUDIONAME]; + int numhitboxes; + s_bbox_t hitbox[MAXSTUDIOSRCBONES]; +} s_hitboxset_t; + +EXTERN CUtlArray< s_hitboxset_t > g_hitboxsets; + +typedef struct +{ + int models; + int group; + char name[MAXSRCSTUDIONAME];// bone name +} s_hitgroup_t; + +EXTERN s_hitgroup_t g_hitgroup[MAXSTUDIOSRCBONES]; +EXTERN int g_numhitgroups; + +typedef struct +{ + char name[MAXSRCSTUDIONAME]; + int bone; + int type; + int index; + float start; + float end; +} s_bonecontroller_t; + +EXTERN s_bonecontroller_t g_bonecontroller[MAXSTUDIOSRCBONES]; +EXTERN int g_numbonecontrollers; + +typedef struct +{ + char name[MAXSRCSTUDIONAME]; + int flags; +} s_screenalignedbone_t; + +EXTERN s_screenalignedbone_t g_screenalignedbone[MAXSTUDIOSRCBONES]; +EXTERN int g_numscreenalignedbones; + +#define IS_ABSOLUTE 0x0001 +#define IS_RIGID 0x0002 + +typedef struct +{ + char name[MAXSRCSTUDIONAME]; + char bonename[MAXSRCSTUDIONAME]; + int bone; + int type; + int flags; + matrix3x4 local; + int found; // a owning bone has been flagged +} s_attachment_t; + +EXTERN s_attachment_t g_attachment[MAXSTUDIOSRCBONES]; +EXTERN int g_numattachments; + +typedef struct +{ + char bonename[MAXSRCSTUDIONAME]; +} s_bonemerge_t; + +EXTERN CUtlArray< s_bonemerge_t > g_BoneMerge; +EXTERN CUtlArray< s_bonemerge_t > g_collapse; + +typedef struct +{ + char name[MAXSRCSTUDIONAME]; + int parent; +} s_node_t; + +typedef struct +{ + Vector pos; + Radian rot; +} s_bone_t; + +#define CMDSRC_GLOBAL 0 +#define CMDSRC_LOCAL 1 + +#define CMD_WEIGHTS 1 +#define CMD_SUBTRACT 2 +#define CMD_AO 3 +#define CMD_MATCH 4 +#define CMD_FIXUP 5 +#define CMD_ANGLE 6 +#define CMD_IKFIXUP 7 +#define CMD_IKRULE 8 +#define CMD_MOTION 9 +#define CMD_REFMOTION 10 +#define CMD_DERIVATIVE 11 +#define CMD_NOANIMATION 12 +#define CMD_LINEARDELTA 13 +#define CMD_SPLINEDELTA 14 +#define CMD_COMPRESS 15 +#define CMD_NUMFRAMES 16 +#define CMD_COUNTERROTATE 17 +#define CMD_SETBONE 18 +#define CMD_WORLDSPACEBLEND 19 +#define CMD_MATCHBLEND 20 +#define CMD_LOCALHIERARCHY 21 + +typedef struct +{ + int motiontype; + int iStartFrame; // starting frame to apply motion over + int iEndFrame; // end frame to apply motion over + int iSrcFrame; // frame that matches the "reference" animation + struct s_animation *pRefAnim; // animation to match + int iRefFrame; // reference animation's frame to match +} s_motion_t; + +typedef struct +{ + int endframe; // frame when pos, rot is valid. + int flags; // type of motion. Only linear, linear accel, and linear decel is allowed + float v0; + float v1; + Vector vector; // movement vector + Vector pos; // final position + Radian rot; // final rotation +} s_linearmove_t; + +typedef struct +{ + Vector pos; + Vector4D q; +} s_streamdata_t; + +typedef struct +{ + // source animations + int numerror; + s_streamdata_t *pError; + // compressed animations + float scale[6]; + int numanim[6]; + mstudioanimvalue_t *anim[6]; +} s_animationstream_t; + +typedef struct +{ + int chain; + + int index; + int type; + int slot; + char bonename[MAXSRCSTUDIONAME]; + char attachment[MAXSRCSTUDIONAME]; + int bone; + Vector pos; + Vector4D q; + float height; + float floor; + float radius; + + int start; + int peak; + int tail; + int end; + + int contact; + + bool usesequence; + bool usesource; + + int flags; + + s_animationstream_t errorData; +} s_ikrule_t; + +typedef struct +{ + int cmd; + int cmd_source; + union + { + struct + { + int index; + } weightlist; + + struct + { + s_animation *ref; + int frame; + int flags; + } subtract; + + struct + { + s_animation *ref; + int motiontype; + int srcframe; + int destframe; + char *pBonename; + } ao; + + struct + { + s_animation *ref; + int srcframe; + int destframe; + int destpre; + int destpost; + } match; + + struct + { + s_animation *ref; + int startframe; + int loops; + } world; + + struct + { + int start; + int end; + } fixuploop; + + struct + { + float angle; + } angle; + + struct + { + s_ikrule_t *pRule; + } ikfixup; + + struct + { + s_ikrule_t *pRule; + } ikrule; + + struct + { + float scale; + } derivative; + + struct + { + int flags; + } linear; + + struct + { + int frames; + } compress; + + struct + { + int frames; + } numframes; + + struct + { + char *pBonename; + bool bHasTarget; + float targetAngle[3]; + } counterrotate; + + struct + { + char *pBonename; + char *pParentname; + int start; + int peak; + int tail; + int end; + } localhierarchy; + + s_motion_t motion; + }; +} s_animcmd_t; + +// keep source data +typedef struct +{ + int startframe; + int endframe; + int numframes; +} s_source_t; + +typedef struct s_animation +{ + char name[MAXSRCSTUDIONAME]; // animation name + char filename[MAXSRCSTUDIONAME]; // path to .smd file + bool isImplied; // from sequence desc + int startframe; + int endframe; + int numframes; + + s_source_t source; + + int flags; + + float fps; + + int numbones; + s_node_t localBone[MAXSTUDIOSRCBONES]; + int boneLocalToGlobal[MAXSTUDIOSRCBONES]; // local bone to world bone mapping + int boneGlobalToLocal[MAXSTUDIOSRCBONES]; // world bone to local bone mapping + + // default adjustments + Vector scale; + Vector adjust; + Radian rotation; + + // piecewise linear motion + s_linearmove_t piecewisemove[MAXSTUDIOMOVEKEYS]; + int numpiecewisekeys; + int movementindex; + + Vector linearmovement; + + float weight[MAXSTUDIOSRCBONES]; + float posweight[MAXSTUDIOSRCBONES]; + + int numcmds; + s_animcmd_t cmds[MAXSTUDIOCMDS]; + + int ikruleindex; + int numikrules; + s_ikrule_t ikrule[MAXSTUDIOIKRULES]; + bool noAutoIK; + + int motiontype; + + int fudgeloop; + int looprestart; // new starting frame for looping animations + + float motionrollback; + + Vector bmin; + Vector bmax; + + s_bone_t *rawanim[MAXSTUDIOANIMATIONS]; // [frame][bones]; + s_bone_t *sanim[MAXSTUDIOANIMATIONS]; // [frame][bones]; + int numanim[MAXSTUDIOSRCBONES][6]; + mstudioanimvalue_t *anim[MAXSTUDIOSRCBONES][6]; +} s_animation_t; + +EXTERN s_animation_t *g_panimation[MAXSTUDIOSEQUENCES*MAXSTUDIOBLENDS]; // each sequence can have 16 blends +EXTERN int g_numani, g_real_numani; + +typedef struct +{ + char name[MAXSRCSTUDIONAME]; + int chain; + float flPosWeight; + float flLocalQWeight; +} s_iklock_t; + +EXTERN int g_numikautoplaylocks; +EXTERN s_iklock_t g_ikautoplaylock[16]; + +typedef struct +{ + char name[MAXSRCSTUDIONAME]; + s_animcmd_t cmds[MAXSTUDIOCMDS]; + int numcmds; +} s_cmdlist_t; + +EXTERN s_cmdlist_t g_cmdlist[MAXSTUDIOANIMATIONS]; +EXTERN int g_numcmdlists; + +typedef struct +{ + int event; + int frame; + char options[MAXEVENTSTRING]; +} s_event_t; + +typedef struct +{ + char name[MAXSRCSTUDIONAME]; + int sequence; + int flags; + int pose; + float start; + float peak; + float tail; + float end; +} s_autolayer_t; + +typedef struct +{ + int motiontype; + Vector linearmovement; + + char name[MAXSRCSTUDIONAME]; + int flags; + float fps; + int numframes; + + int activity; + int actweight; + + int frameoffset; // used to adjust frame numbers + + int numevents; + s_event_t event[MAXSTUDIOEVENTS]; + + int numblends; + int groupsize[2]; + s_animation_t *panim[MAXSTUDIOBLENDS]; + int paramindex[2]; + float paramstart[2]; + float paramend[2]; + int paramattachment[2]; + int paramcontrol[2]; + float param0[MAXSTUDIOBLENDS]; + float param1[MAXSTUDIOBLENDS]; + float weight[MAXSTUDIOSRCBONES]; + + s_animation_t *paramanim; + s_animation_t *paramcompanim; + s_animation_t *paramcenter; + + int seqgroup; + int animindex; + int animdescindex; + + float fadeintime; + float fadeouttime; + + Vector bmin; + Vector bmax; + int entrynode; + int exitnode; + int nodeflags; + + int numikrules; + + int cycleposeindex; + + int numautolayers; + s_autolayer_t autolayer[64]; + + s_iklock_t iklock[64]; + int numiklocks; + + CUtlArray< char > KeyValue; +} s_sequence_t; + +EXTERN s_sequence_t g_sequence[MAXSTUDIOSEQUENCES]; +EXTERN int g_numseq, g_real_numseq; + +typedef struct +{ + char label[MAXSRCSTUDIONAME]; + char name[MAXSRCSTUDIONAME]; +} s_sequencegroup_t; + +EXTERN s_sequencegroup_t g_sequencegroup[MAXSTUDIOSEQUENCES]; +EXTERN int g_numseqgroups; + +typedef struct +{ + char name[MAXSRCSTUDIONAME]; + float min; + float max; + int flags; + float loop; +} s_poseparameter_t; + +EXTERN s_poseparameter_t g_pose[MAXSTUDIOPOSEPARAM]; +EXTERN int g_numposeparameters; + +EXTERN int g_xnode[100][100]; +EXTERN char *g_xnodename[100]; +EXTERN int g_numxnodes; +EXTERN int g_xnodeskip[10000][2]; +EXTERN int g_numxnodeskips; + +// FIXME: what about texture overrides inline with loading models +typedef struct +{ + char name[MAXSRCSTUDIONAME]; + int flags; + int srcwidth; + int srcheight; + struct rgbdata_s *psrc; + float max_s; + float min_s; + float max_t; + float min_t; + int skintop; + int skinleft; + int skinwidth; + int skinheight; + float fskintop; + float fskinleft; + float fskinwidth; + float fskinheight; + void *pdata; + int size; + int parent; +} s_texture_t; + +EXTERN s_texture_t g_texture[MAXSTUDIOSKINS]; +EXTERN int g_numtextures; +EXTERN int g_numskinref; +EXTERN int g_numskinfamilies; +EXTERN int g_skinref[256][MAXSTUDIOSKINS]; // [skin][skinref], returns texture index +EXTERN int g_numtexturegroups; +EXTERN int g_numtexturelayers[32]; +EXTERN int g_numtexturereps[32]; +EXTERN int g_texturegroup[32][32][32]; + +typedef struct +{ + int alloctris; + int numtris; + s_trianglevert_t (*triangle)[3]; + + int skinref; + int numnorms; +} s_mesh_t; + +typedef struct s_model_s +{ + char name[MAXSRCSTUDIONAME]; + + int numbones; + s_node_t localBone[MAXSTUDIOSRCBONES]; + matrix3x4 boneToPose[MAXSTUDIOSRCBONES]; + s_bone_t skeleton[MAXSTUDIOSRCBONES]; + + Vector scale; + bool flip_triangles; + + // bone remapping + int boneflags[MAXSTUDIOSRCBONES]; // is local bone (or child) referenced with a vertex + int boneref[MAXSTUDIOSRCBONES]; // is local bone (or child) referenced with a vertex + int boneLocalToGlobal[MAXSTUDIOSRCBONES]; // local bone to world bone mapping + int boneGlobalToLocal[MAXSTUDIOSRCBONES]; // world bone to local bone mapping + + Vector boundingbox[MAXSTUDIOSRCBONES][2]; + + int numverts; + s_vertex_t vert[MAXSTUDIOVERTS]; + + int numnorms; + s_normal_t norm[MAXSTUDIOVERTS]; + + int numsrcverts; + s_srcvertex_t srcvert[MAXSRCSTUDIOVERTS]; + + int nummesh; + s_mesh_t *pmesh[MAXSTUDIOMESHES]; + + float boundingradius; +} s_model_t; + +EXTERN s_model_t *g_model[MAXSTUDIOMODELS]; +EXTERN int g_nummodels; + +typedef struct +{ + char name[MAXSRCSTUDIONAME]; + int nummodels; + int base; + s_model_t *pmodel[MAXSTUDIOMODELS]; +} s_bodypart_t; + +EXTERN s_bodypart_t g_bodypart[MAXSTUDIOBODYPARTS]; +EXTERN int g_numbodyparts; + +typedef struct +{ + int flags; + char bonename[MAXSRCSTUDIONAME]; + int bone; + char controlname[MAXSRCSTUDIONAME]; + int control; + int axis; + Vector pos[6]; + Vector4D quat[6]; +} s_axisinterpbone_t; + +EXTERN s_axisinterpbone_t g_axisinterpbones[MAXSTUDIOBONES]; +EXTERN int g_axisinterpbonemap[MAXSTUDIOBONES]; // map used axisinterpbone's to source axisinterpbone's +EXTERN int g_numaxisinterpbones; + +typedef struct +{ + int flags; + char bonename[MAXSRCSTUDIONAME]; + int bone; + char parentname[MAXSRCSTUDIONAME]; + char controlparentname[MAXSRCSTUDIONAME]; + char controlname[MAXSRCSTUDIONAME]; + int control; + int numtriggers; + Vector size; + Vector basepos; + float percentage; + float tolerance[32]; + Vector4D trigger[32]; + Vector pos[32]; + Vector4D quat[32]; +} s_quatinterpbone_t; + +EXTERN s_quatinterpbone_t g_quatinterpbones[MAXSTUDIOBONES]; +EXTERN int g_quatinterpbonemap[MAXSTUDIOBONES]; // map used quatinterpbone's to source axisinterpbone's +EXTERN int g_numquatinterpbones; + +typedef struct +{ + char bonename[MAXSRCSTUDIONAME]; + int bone; + char parentname[MAXSRCSTUDIONAME]; + int parent; + char aimname[MAXSRCSTUDIONAME]; + int aimAttach; + int aimBone; + Vector aimvector; + Vector upvector; + Vector basepos; +} s_aimatbone_t; + +EXTERN s_aimatbone_t g_aimatbones[MAXSTUDIOBONES]; +EXTERN int g_aimatbonemap[MAXSTUDIOBONES]; // map used aimatpbone's to source aimatpbone's (may be optimized out) +EXTERN int g_numaimatbones; + +typedef struct +{ + // weights, indexed by numbones per weightlist + char name[MAXSTUDIONAME]; + int numbones; + char *bonename[MAXWEIGHTSPERLIST]; + float boneweight[MAXWEIGHTSPERLIST]; + float boneposweight[MAXWEIGHTSPERLIST]; + + // weights, indexed by global bone index + float weight[MAXSTUDIOBONES]; + float posweight[MAXSTUDIOBONES]; +} s_weightlist_t; + +EXTERN s_weightlist_t g_weightlist[MAXWEIGHTLISTS]; +EXTERN int g_numweightlist; + +typedef struct +{ + int bone; + Vector kneeDir; +} s_iklink_t; + +typedef struct +{ + char name[MAXSRCSTUDIONAME]; + char bonename[MAXSRCSTUDIONAME]; + int axis; + float value; + int numlinks; + s_iklink_t link[10]; // hip, knee, ankle, toes... + float height; + float radius; + float floor; + Vector center; +} s_ikchain_t; + +EXTERN s_ikchain_t g_ikchain[16]; +EXTERN int g_numikchains; + +typedef struct +{ + int flags; + char bonename[MAXSRCSTUDIONAME]; + int bone; + mstudiojigglebone_t data; // the actual jiggle properties +} s_jigglebone_t; + +EXTERN s_jigglebone_t g_jigglebones[MAXSTUDIOBONES]; +EXTERN int g_jigglebonemap[MAXSTUDIOBONES]; // map used jigglebone's to source jigglebonebone's +EXTERN int g_numjigglebones; + +typedef struct +{ + char parentname[MAXSRCSTUDIONAME]; + char childname[MAXSRCSTUDIONAME]; + char subparentname[MAXSRCSTUDIONAME]; +} s_forcedhierarchy_t; + +EXTERN s_forcedhierarchy_t g_forcedhierarchy[MAXSTUDIOBONES]; +EXTERN int g_numforcedhierarchy; + +typedef struct +{ + char name[MAXSRCSTUDIONAME]; + Radian rot; +} s_forcedrealign_t; + +EXTERN s_forcedrealign_t g_forcedrealign[MAXSTUDIOBONES]; +EXTERN int g_numforcedrealign; + +typedef struct +{ + char name[MAXSRCSTUDIONAME]; + int numseq; + char *sequencename[64]; +} s_limitrotation_t; + +EXTERN s_limitrotation_t g_limitrotation[MAXSTUDIOBONES]; +EXTERN int g_numlimitrotation; + +// +// studiomdl.cpp +// +int findGlobalBone( const char *name ); +int findGlobalBoneXSI( const char *name ); +int findLocalBone( const s_animation_t *panim, const char *name ); +bool IsGlobalBoneXSI( const char *name, const char *bonename ); +int LookupVertex( s_model_t *pmodel, s_srcvertex_t *srcv ); +int LookupNormal( s_model_t *pmodel, s_srcvertex_t *srcv ); +int LookupAttachment( const char *name ); +void clip_rotations( float &rot ); +void clip_rotations( Radian &rot ); +// methods associated with the key value text block +int KeyValueTextSize( CUtlArray< char > *pKeyValue ); +const char *KeyValueText( CUtlArray< char > *pKeyValue ); + +// +// simplify.cpp +// +void SimplifyModel( void ); + +// +// skin.cpp +// +void SetSkinValues( void ); + +extern int BuildTris (s_trianglevert_t (*x)[3], s_mesh_t *y, byte **ppdata ); +int LoadBMP (const char* szFile, byte** ppbBits, byte** ppbPalette, int *width, int *height ); + +// +// write.cpp +// +extern void WriteFile( void ); diff --git a/utils/studiomdl/write.cpp b/utils/studiomdl/write.cpp new file mode 100644 index 0000000..5a01298 --- /dev/null +++ b/utils/studiomdl/write.cpp @@ -0,0 +1,1317 @@ +#pragma warning( disable : 4244 ) +#pragma warning( disable : 4237 ) +#pragma warning( disable : 4305 ) + +#include +#include +#include +#include +#include +#include "cmdlib.h" +#include "mathlib.h" +#include "stringlib.h" +#include "studio.h" +#include "studiomdl.h" +#include "filesystem.h" + +int totalframes = 0; +float totalseconds = 0; +extern int numcommandnodes; + +// bounds checking +byte *pData; +byte *pStart; +studiohdr_t *phdr = NULL; +studiohdr2_t *phdr2 = NULL; +studioseqhdr_t *pseqhdr; + +#define ALIGN( a ) a = (byte *)((int)((byte *)a + 3) & ~ 3) +#define FILEBUFFER (48 * 1024 * 1024) // 48 Mb for now +#define TRUNCNAME "ValveBiped." + +mstudioboneinfo_t g_boneinfo[MAXSTUDIOBONES]; + +void SetupBoneInfo( int bone, mstudioboneinfo_t *out ) +{ + matrix3x4 m = g_bonetable[bone].boneToPose.Invert(); + + memset( out, 0, sizeof( *out )); + + out->poseToBone[0][0] = m[0][0]; + out->poseToBone[1][0] = m[0][1]; + out->poseToBone[2][0] = m[0][2]; + + out->poseToBone[0][1] = m[1][0]; + out->poseToBone[1][1] = m[1][1]; + out->poseToBone[2][1] = m[1][2]; + + out->poseToBone[0][2] = m[2][0]; + out->poseToBone[1][2] = m[2][1]; + out->poseToBone[2][2] = m[2][2]; + + out->poseToBone[0][3] = m[3][0]; + out->poseToBone[1][3] = m[3][1]; + out->poseToBone[2][3] = m[3][2]; + + out->qAlignment = g_bonetable[bone].qAlignment; + + AngleQuaternion( g_bonetable[bone].rot, out->quat ); + QuaternionAlign( out->qAlignment, out->quat, out->quat ); +} + +void WriteKeyValues( studiohdr2_t *phdr, CUtlArray< char > *pKeyValue ) +{ + phdr->keyvaluesize = KeyValueTextSize( pKeyValue ); + + if( phdr->keyvaluesize ) + { + phdr->keyvalueindex = (pData - pStart); + memcpy( pData, KeyValueText( pKeyValue ), phdr->keyvaluesize ); + + // Add space for a null terminator + pData[phdr->keyvaluesize] = 0; + ++phdr->keyvaluesize; + pData += phdr->keyvaluesize * sizeof( char ); + } + ALIGN( pData ); +} + +void WriteSeqKeyValues( mstudioseqdesc_t *pseqdesc, CUtlArray< char > *pKeyValue ) +{ + // FIXME: no variables in mstudioseqdesc_t to store keyvaluesize without break binary compatibility :-( + int keyvaluesize = KeyValueTextSize( pKeyValue ); + + if( keyvaluesize ) + { + pseqdesc->keyvalueindex = (pData - pStart); + memcpy( pData, KeyValueText( pKeyValue ), keyvaluesize ); + + // add space for a null terminator + pData[keyvaluesize] = 0; + keyvaluesize++; + pData += keyvaluesize * sizeof( char ); + } + + ALIGN( pData ); +} + +void CopyStringTruncate( char *out, const char *in, size_t outsize ) +{ + size_t insize = Q_strlen( in ); + size_t truncsize = Q_strlen( TRUNCNAME ); + const char *trunc; + size_t start; + + *out = '\0'; + + if(( trunc = Q_strstr( in, TRUNCNAME )) != NULL ) + { + start = trunc - in; + Q_strncpy( out, in, start ); // may be 0 + Q_strncat( out, trunc + truncsize, outsize ); + MsgDev( D_REPORT, "%s renamed to %s\n", in, out ); + return; // best case to truncate + } + else if( insize > ( outsize - 1 )) + { + MsgDev( D_WARN, "%s is too long (limit is %i symbols). string will be truncated\n", in, outsize ); + } + + Q_strncpy( out, in, outsize ); // this may cause problems... +} + +void WriteBoneInfo( void ) +{ + int i, j, k; + mstudiobonecontroller_t *pbonecontroller; + mstudioattachment_t *pattachment; + mstudioboneinfo_t *pboneinfo; + mstudiobone_t *pbone; + mstudiobbox_t *pbbox; + + // save bone info + pbone = (mstudiobone_t *)pData; + phdr->numbones = g_numbones; + phdr->boneindex = (pData - pStart); + + for( i = 0; i < g_numbones; i++ ) + { + CopyStringTruncate( pbone[i].name, g_bonetable[i].name, sizeof( pbone[0].name )); + pbone[i].parent = g_bonetable[i].parent; + pbone[i].flags = g_bonetable[i].flags; + pbone[i].value[0] = g_bonetable[i].pos[0]; + pbone[i].value[1] = g_bonetable[i].pos[1]; + pbone[i].value[2] = g_bonetable[i].pos[2]; + pbone[i].value[3] = g_bonetable[i].rot[0]; + pbone[i].value[4] = g_bonetable[i].rot[1]; + pbone[i].value[5] = g_bonetable[i].rot[2]; + pbone[i].scale[0] = g_bonetable[i].posscale[0]; + pbone[i].scale[1] = g_bonetable[i].posscale[1]; + pbone[i].scale[2] = g_bonetable[i].posscale[2]; + pbone[i].scale[3] = g_bonetable[i].rotscale[0]; + pbone[i].scale[4] = g_bonetable[i].rotscale[1]; + pbone[i].scale[5] = g_bonetable[i].rotscale[2]; + + SetupBoneInfo( i, &g_boneinfo[i] ); + } + pData += g_numbones * sizeof( mstudiobone_t ); + ALIGN( pData ); + + if( FBitSet( gflags, STUDIO_HAS_BONEINFO )) + { + pboneinfo = (mstudioboneinfo_t *)pData; + memcpy( pboneinfo, g_boneinfo, g_numbones * sizeof( mstudioboneinfo_t )); + pData += g_numbones * sizeof( mstudioboneinfo_t ); + ALIGN( pData ); + + // save procedural bone info + if( g_numaxisinterpbones ) + { + mstudioaxisinterpbone_t *pProc = (mstudioaxisinterpbone_t *)pData; + pData += g_numaxisinterpbones * sizeof( mstudioaxisinterpbone_t ); + ALIGN( pData ); + + for( i = 0; i < g_numaxisinterpbones; i++ ) + { + j = g_axisinterpbonemap[i]; + k = g_axisinterpbones[j].bone; + pboneinfo[k].procindex = (byte *)&pProc[i] - pStart; + pboneinfo[k].proctype = STUDIO_PROC_AXISINTERP; + // Msg( "bone %d %d\n", j, pbone[k].procindex ); + pProc[i].control = g_axisinterpbones[j].control; + pProc[i].axis = g_axisinterpbones[j].axis; + for( k = 0; k < 6; k++ ) + { + pProc[i].pos[k] = g_axisinterpbones[j].pos[k]; + pProc[i].quat[k] = g_axisinterpbones[j].quat[k]; + } + } + } + + if( g_numquatinterpbones ) + { + mstudioquatinterpbone_t *pProc = (mstudioquatinterpbone_t *)pData; + pData += g_numquatinterpbones * sizeof( mstudioquatinterpbone_t ); + ALIGN( pData ); + + for( i = 0; i < g_numquatinterpbones; i++ ) + { + j = g_quatinterpbonemap[i]; + k = g_quatinterpbones[j].bone; + pboneinfo[k].procindex = (byte *)&pProc[i] - pStart; + pboneinfo[k].proctype = STUDIO_PROC_QUATINTERP; + // Msg( "bone %d %d\n", j, pbone[k].procindex ); + pProc[i].control = g_quatinterpbones[j].control; + + mstudioquatinterpinfo_t *pTrigger = (mstudioquatinterpinfo_t *)pData; + pProc[i].numtriggers = g_quatinterpbones[j].numtriggers; + pProc[i].triggerindex = (byte *)pTrigger - pStart; + pData += pProc[i].numtriggers * sizeof( mstudioquatinterpinfo_t ); + + for( k = 0; k < pProc[i].numtriggers; k++ ) + { + pTrigger[k].inv_tolerance = 1.0 / g_quatinterpbones[j].tolerance[k]; + pTrigger[k].trigger = g_quatinterpbones[j].trigger[k]; + pTrigger[k].pos = g_quatinterpbones[j].pos[k]; + pTrigger[k].quat = g_quatinterpbones[j].quat[k]; + } + } + } + + if( g_numjigglebones ) + { + mstudiojigglebone_t *jiggleInfo = (mstudiojigglebone_t *)pData; + pData += g_numjigglebones * sizeof( mstudiojigglebone_t ); + ALIGN( pData ); + + for( i = 0; i < g_numjigglebones; i++ ) + { + j = g_jigglebonemap[i]; + k = g_jigglebones[j].bone; + pboneinfo[k].procindex = (byte *)&jiggleInfo[i] - pStart; + pboneinfo[k].proctype = STUDIO_PROC_JIGGLE; + + jiggleInfo[i] = g_jigglebones[j].data; + } + + } + + // write aim at bones + if( g_numaimatbones ) + { + mstudioaimatbone_t *pProc = (mstudioaimatbone_t *)pData; + pData += g_numaimatbones * sizeof( mstudioaimatbone_t ); + ALIGN( pData ); + + for( i = 0; i < g_numaimatbones; i++ ) + { + j = g_aimatbonemap[i]; + k = g_aimatbones[j].bone; + pboneinfo[k].procindex = (byte *)&pProc[i] - pStart; + pboneinfo[k].proctype = g_aimatbones[j].aimAttach == -1 ? STUDIO_PROC_AIMATBONE : STUDIO_PROC_AIMATATTACH; + pProc[i].parent = g_aimatbones[j].parent; + pProc[i].aim = g_aimatbones[j].aimAttach == -1 ? g_aimatbones[j].aimBone : g_aimatbones[j].aimAttach; + pProc[i].aimvector = g_aimatbones[j].aimvector; + pProc[i].upvector = g_aimatbones[j].upvector; + pProc[i].basepos = g_aimatbones[j].basepos; + } + } + } + + // map bonecontroller to bones + for( i = 0; i < g_numbones; i++ ) + { + for( j = 0; j < 6; j++ ) + pbone[i].bonecontroller[j] = -1; + } + + for( i = 0; i < g_numbonecontrollers; i++ ) + { + j = g_bonecontroller[i].bone; + + switch( g_bonecontroller[i].type & STUDIO_TYPES ) + { + case STUDIO_X: + pbone[j].bonecontroller[0] = i; + break; + case STUDIO_Y: + pbone[j].bonecontroller[1] = i; + break; + case STUDIO_Z: + pbone[j].bonecontroller[2] = i; + break; + case STUDIO_XR: + pbone[j].bonecontroller[3] = i; + break; + case STUDIO_YR: + pbone[j].bonecontroller[4] = i; + break; + case STUDIO_ZR: + pbone[j].bonecontroller[5] = i; + break; + default: + COM_FatalError( "unknown bonecontroller type %i\n", g_bonecontroller[i].type & STUDIO_TYPES ); + } + + if( g_mergebonecontrollers ) + { + for( j = 0; j < i; j++ ) + { + if( g_bonecontroller[i].start == g_bonecontroller[j].start + && g_bonecontroller[i].end == g_bonecontroller[j].end + && g_bonecontroller[i].index == g_bonecontroller[j].index + && (( g_bonecontroller[i].type & (STUDIO_X | STUDIO_Y | STUDIO_Z )) != 0 ) == (( g_bonecontroller[j].type & ( STUDIO_X|STUDIO_Y|STUDIO_Z )) != 0 )) + { + switch( g_bonecontroller[i].type & STUDIO_TYPES ) + { + case STUDIO_X: + pbone[g_bonecontroller[i].bone].bonecontroller[0] = j; + break; + case STUDIO_Y: + pbone[g_bonecontroller[i].bone].bonecontroller[1] = j; + break; + case STUDIO_Z: + pbone[g_bonecontroller[i].bone].bonecontroller[2] = j; + break; + case STUDIO_XR: + pbone[g_bonecontroller[i].bone].bonecontroller[3] = j; + break; + case STUDIO_YR: + pbone[g_bonecontroller[i].bone].bonecontroller[4] = j; + break; + case STUDIO_ZR: + pbone[g_bonecontroller[i].bone].bonecontroller[5] = j; + break; + } + + memcpy( &g_bonecontroller[i], &g_bonecontroller[i + 1], ( g_numbonecontrollers - ( i + 1 )) * sizeof( g_bonecontroller[0] )); + g_numbonecontrollers--; + i--; + break; + } + } + } + } + + // save bonecontroller info + pbonecontroller = (mstudiobonecontroller_t *)pData; + phdr->numbonecontrollers = g_numbonecontrollers; + phdr->bonecontrollerindex = (pData - pStart); + + for( i = 0; i < g_numbonecontrollers; i++ ) + { + pbonecontroller[i].bone = g_bonecontroller[i].bone; + pbonecontroller[i].index = g_bonecontroller[i].index; + pbonecontroller[i].type = g_bonecontroller[i].type; + pbonecontroller[i].start = g_bonecontroller[i].start; + pbonecontroller[i].end = g_bonecontroller[i].end; + } + + pData += g_numbonecontrollers * sizeof( mstudiobonecontroller_t ); + ALIGN( pData ); + + // save attachment info + pattachment = (mstudioattachment_t *)pData; + phdr->numattachments = g_numattachments; + phdr->attachmentindex = (pData - pStart); + + for( i = 0; i < g_numattachments; i++ ) + { + CopyStringTruncate( pattachment[i].name, g_attachment[i].name, sizeof( pattachment[0].name )); + pattachment[i].bone = g_attachment[i].bone; + pattachment[i].org = g_attachment[i].local.GetOrigin(); + pattachment[i].flags = g_attachment[i].flags; + + // evil FP jitter + for( j = 0; j < 3; j++ ) + { + if( fabs( pattachment[i].org[j] ) < 0.00001f ) + pattachment[i].org[j] = 0.0f; + } + + // save rotation matrix for local attachments + if( FBitSet( pattachment[i].flags, STUDIO_ATTACHMENT_LOCAL )) + { + pattachment[i].vectors[0] = g_attachment[i].local.GetForward(); + pattachment[i].vectors[1] = g_attachment[i].local.GetRight(); + pattachment[i].vectors[2] = g_attachment[i].local.GetUp(); + } + } + + pData += g_numattachments * sizeof( mstudioattachment_t ); + ALIGN( pData ); + + // save bbox info + s_hitboxset_t *set = &g_hitboxsets[0]; // just use 0 group + pbbox = (mstudiobbox_t *)pData; + phdr->numhitboxes = set->numhitboxes; + phdr->hitboxindex = (pData - pStart); + + for( i = 0; i < set->numhitboxes; i++ ) + { + pbbox[i].bone = set->hitbox[i].bone; + pbbox[i].group = set->hitbox[i].group; + pbbox[i].bbmin = set->hitbox[i].bmin; + pbbox[i].bbmax = set->hitbox[i].bmax; + } + pData += set->numhitboxes * sizeof( mstudiobbox_t ); + ALIGN( pData ); + + // save hitbox sets + phdr2->numhitboxsets = g_hitboxsets.Size(); + + // remember start spot + mstudiohitboxset_t *hitboxset = (mstudiohitboxset_t *)pData; + phdr2->hitboxsetindex = pData - pStart; + pData += phdr2->numhitboxsets * sizeof( mstudiohitboxset_t ); + ALIGN( pData ); + + for ( int s = 0; s < g_hitboxsets.Size(); s++, hitboxset++ ) + { + set = &g_hitboxsets[s]; + + CopyStringTruncate( hitboxset->name, set->hitboxsetname, sizeof( hitboxset->name )); + hitboxset->numhitboxes = set->numhitboxes; + hitboxset->hitboxindex = ( pData - pStart ); + + // save bbox info + pbbox = (mstudiobbox_t *)pData; + for( i = 0; i < hitboxset->numhitboxes; i++ ) + { + pbbox[i].bone = set->hitbox[i].bone; + pbbox[i].group = set->hitbox[i].group; + pbbox[i].bbmin = set->hitbox[i].bmin; + pbbox[i].bbmax = set->hitbox[i].bmax; + } + pData += hitboxset->numhitboxes * sizeof( mstudiobbox_t ); + ALIGN( pData ); + } +} + +void WriteSequenceInfo( void ) +{ + int i, j, k; + + mstudioseqgroup_t *pseqgroup; + mstudioseqdesc_t *pseqdesc; + mstudioevent_t *pevent; + byte *ptransition; + + // save sequence info + pseqdesc = (mstudioseqdesc_t *)pData; + phdr->numseq = g_numseq; + phdr->seqindex = (pData - pStart); + pData += g_numseq * sizeof( mstudioseqdesc_t ); + + for( i = 0; i < g_numseq; i++, pseqdesc++ ) + { + CopyStringTruncate( pseqdesc->label, g_sequence[i].name, sizeof( pseqdesc->label )); + pseqdesc->numframes = g_sequence[i].numframes; + pseqdesc->fps = g_sequence[i].fps; + pseqdesc->flags = g_sequence[i].flags; + + pseqdesc->numblends = g_sequence[i].numblends; + + pseqdesc->groupsize[0] = g_sequence[i].groupsize[0]; + pseqdesc->groupsize[1] = g_sequence[i].groupsize[1]; + pseqdesc->blendtype[0] = g_sequence[i].paramindex[0]; + pseqdesc->blendtype[1] = g_sequence[i].paramindex[1]; + pseqdesc->blendstart[0] = g_sequence[i].paramstart[0]; + pseqdesc->blendend[0] = g_sequence[i].paramend[0]; + pseqdesc->blendstart[1] = g_sequence[i].paramstart[1]; + pseqdesc->blendend[1] = g_sequence[i].paramend[1]; + + pseqdesc->motiontype = g_sequence[i].motiontype; + pseqdesc->linearmovement = g_sequence[i].linearmovement; + + pseqdesc->seqgroup = g_sequence[i].seqgroup; + + pseqdesc->animindex = g_sequence[i].animindex; // anim data[numblends] + pseqdesc->animdescindex = g_sequence[i].animdescindex; // anim desc[numblends] + + if(( g_sequence[i].groupsize[0] > 1 || g_sequence[i].groupsize[1] > 1 )) + { + // save posekey values + float *pposekey = (float *)pData; + pseqdesc->posekeyindex = (pData - pStart); + pData += (pseqdesc->groupsize[0] + pseqdesc->groupsize[1]) * sizeof( float ); + + for( j = 0; j < pseqdesc->groupsize[0]; j++ ) + *(pposekey++) = g_sequence[i].param0[j]; + + for( j = 0; j < pseqdesc->groupsize[1]; j++ ) + *(pposekey++) = g_sequence[i].param1[j]; + } + + pseqdesc->activity = g_sequence[i].activity; + pseqdesc->actweight = g_sequence[i].actweight; + + pseqdesc->bbmin = g_sequence[i].bmin; + pseqdesc->bbmax = g_sequence[i].bmax; + + pseqdesc->fadeintime = bound( 0, g_sequence[i].fadeintime * 100, 255 ); + pseqdesc->fadeouttime = bound( 0, g_sequence[i].fadeouttime * 100, 255 ); + + pseqdesc->entrynode = g_sequence[i].entrynode; + pseqdesc->exitnode = g_sequence[i].exitnode; + pseqdesc->nodeflags = g_sequence[i].nodeflags; + pseqdesc->cycleposeindex = g_sequence[i].cycleposeindex; + +// totalframes += g_sequence[i].numframes; +// totalseconds += g_sequence[i].numframes / g_sequence[i].fps; + + // save events + pevent = (mstudioevent_t *)pData; + pseqdesc->numevents = g_sequence[i].numevents; + pseqdesc->eventindex = (pData - pStart); + pData += pseqdesc->numevents * sizeof( mstudioevent_t ); + ALIGN( pData ); + + for( j = 0; j < g_sequence[i].numevents; j++ ) + { + pevent[j].frame = g_sequence[i].event[j].frame - g_sequence[i].frameoffset; + pevent[j].event = g_sequence[i].event[j].event; + memcpy( pevent[j].options, g_sequence[i].event[j].options, sizeof( pevent[j].options ) ); + } + + // save ikrules + if( g_sequence[i].numikrules ) + SetBits( pseqdesc->flags, STUDIO_IKRULES ); // just a bit to tell what animations contain ik rules + + // save autolayers + mstudioautolayer_t *pautolayer = (mstudioautolayer_t *)pData; + pseqdesc->numautolayers = g_sequence[i].numautolayers; + pseqdesc->autolayerindex = (pData - pStart); + pData += pseqdesc->numautolayers * sizeof( mstudioautolayer_t ); + + for( j = 0; j < g_sequence[i].numautolayers; j++ ) + { + pautolayer[j].iSequence = g_sequence[i].autolayer[j].sequence; + pautolayer[j].iPose = g_sequence[i].autolayer[j].pose; + pautolayer[j].flags = g_sequence[i].autolayer[j].flags; + + if( !FBitSet( pautolayer[j].flags, STUDIO_AL_POSE )) + { + pautolayer[j].start = g_sequence[i].autolayer[j].start / (g_sequence[i].numframes - 1); + pautolayer[j].peak = g_sequence[i].autolayer[j].peak / (g_sequence[i].numframes - 1); + pautolayer[j].tail = g_sequence[i].autolayer[j].tail / (g_sequence[i].numframes - 1); + pautolayer[j].end = g_sequence[i].autolayer[j].end / (g_sequence[i].numframes - 1); + } + else + { + pautolayer[j].start = g_sequence[i].autolayer[j].start; + pautolayer[j].peak = g_sequence[i].autolayer[j].peak; + pautolayer[j].tail = g_sequence[i].autolayer[j].tail; + pautolayer[j].end = g_sequence[i].autolayer[j].end; + } + } + + + // save boneweights + int oldweightlistindex = 0; + j = 0; + + // look up previous sequence weights and try to find a match + for( k = 0; k < i; k++ ) + { + j = 0; + // only check newer boneweights than the last one + if( pseqdesc[k-i].weightlistindex > oldweightlistindex ) + { + oldweightlistindex = pseqdesc[k-i].weightlistindex; + for( j = 0; j < g_numbones; j++ ) + { + if( g_sequence[i].weight[j] != g_sequence[k].weight[j] ) + break; + } + if( j == g_numbones ) + break; + } + } + + // check to see if all the bones matched + if( j < g_numbones ) + { + // allocate new block + float *pweight = (float *)pData; + pseqdesc->weightlistindex = (pData - pStart); + pData += g_numbones * sizeof( float ); + + for( j = 0; j < g_numbones; j++ ) + pweight[j] = g_sequence[i].weight[j]; + } + else + { + // use previous boneweight + pseqdesc->weightlistindex = oldweightlistindex; + } + + // save iklocks + mstudioiklock_t *piklock = (mstudioiklock_t *)pData; + pseqdesc->numiklocks = g_sequence[i].numiklocks; + pseqdesc->iklockindex = (pData - pStart); + pData += pseqdesc->numiklocks * sizeof( mstudioiklock_t ); + ALIGN( pData ); + + for( j = 0; j < pseqdesc->numiklocks; j++ ) + { + piklock->chain = g_sequence[i].iklock[j].chain; + piklock->flPosWeight = g_sequence[i].iklock[j].flPosWeight; + piklock->flLocalQWeight = g_sequence[i].iklock[j].flLocalQWeight; + piklock++; + } + + WriteSeqKeyValues( pseqdesc, &g_sequence[i].KeyValue ); + } + + // save sequence group info + pseqgroup = (mstudioseqgroup_t *)pData; + phdr->numseqgroups = g_numseqgroups; + phdr->seqgroupindex = (pData - pStart); + pData += phdr->numseqgroups * sizeof( mstudioseqgroup_t ); + ALIGN( pData ); + + for( i = 0; i < g_numseqgroups; i++ ) + { + CopyStringTruncate( pseqgroup[i].label, g_sequencegroup[i].label, sizeof( pseqgroup[0].label )); + CopyStringTruncate( pseqgroup[i].name, g_sequencegroup[i].name, sizeof( pseqgroup[0].name )); + } + + // save transition graph + ptransition = (byte *)pData; + phdr->numtransitions = g_numxnodes; + phdr->transitionindex = (pData - pStart); + pData += g_numxnodes * g_numxnodes * sizeof( byte ); + ALIGN( pData ); + + for( i = 0; i < g_numxnodes; i++ ) + { + for( j = 0; j < g_numxnodes; j++ ) + { + *ptransition++ = g_xnode[i][j]; + } + } +} + +byte *WriteIkErrors( s_animation_t *srcanim, byte *pData ) +{ + int j, k, n; + + // NOTE: GoldSrc models saves animations per-sequence and duplicate them + // but we can to avoid wrote identical data block again. + if( !srcanim->numikrules || srcanim->ikruleindex ) + return pData; // already stored? + + srcanim->ikruleindex = pData - pStart; + + // write IK error keys + mstudioikrule_t *pikruledata = (mstudioikrule_t *)pData; + pData += srcanim->numikrules * sizeof( mstudioikrule_t ); + ALIGN( pData ); + + for( j = 0; j < srcanim->numikrules; j++ ) + { + mstudioikrule_t *pikrule = pikruledata + j; + + pikrule->index = srcanim->ikrule[j].index; + pikrule->chain = srcanim->ikrule[j].chain; + pikrule->bone = srcanim->ikrule[j].bone; + pikrule->type = srcanim->ikrule[j].type; + pikrule->slot = srcanim->ikrule[j].slot; + pikrule->pos = srcanim->ikrule[j].pos; + pikrule->quat = srcanim->ikrule[j].q; + pikrule->height = srcanim->ikrule[j].height; + pikrule->floor = srcanim->ikrule[j].floor; + pikrule->radius = srcanim->ikrule[j].radius; + pikrule->attachment = LookupAttachment( srcanim->ikrule[j].attachment ); + + if( srcanim->numframes > 1.0 ) + { + pikrule->start = srcanim->ikrule[j].start / (srcanim->numframes - 1.0f); + pikrule->peak = srcanim->ikrule[j].peak / (srcanim->numframes - 1.0f); + pikrule->tail = srcanim->ikrule[j].tail / (srcanim->numframes - 1.0f); + pikrule->end = srcanim->ikrule[j].end / (srcanim->numframes - 1.0f); + pikrule->contact = srcanim->ikrule[j].contact / (srcanim->numframes - 1.0f); + } + else + { + pikrule->start = 0.0f; + pikrule->peak = 0.0f; + pikrule->tail = 1.0f; + pikrule->end = 1.0f; + pikrule->contact = 0.0f; + } + + pikrule->iStart = srcanim->ikrule[j].start; + + // skip writting the header if there's no IK data + for( k = 0; k < 6; k++ ) + { + if( srcanim->ikrule[j].errorData.numanim[k] ) + break; + } + + if( k == 6 ) continue; + + // compressed + pikrule->ikerrorindex = (pData - pStart); + mstudioikerror_t *pCompressed = (mstudioikerror_t *)pData; + pData += sizeof( *pCompressed ); + mstudioanimvalue_t *panimvalue = (mstudioanimvalue_t *)pData; + + for( k = 0; k < 6; k++ ) + { + pCompressed->scale[k] = srcanim->ikrule[j].errorData.scale[k]; + + if( srcanim->ikrule[j].errorData.numanim[k] == 0 ) + { + pCompressed->offset[k] = 0; + } + else + { + pCompressed->offset[k] = ((byte *)panimvalue - (byte *)pCompressed); + for( n = 0; n < srcanim->ikrule[j].errorData.numanim[k]; n++ ) + { + panimvalue->value = srcanim->ikrule[j].errorData.anim[k][n].value; + panimvalue++; + } + } + } + + pData = (byte *)panimvalue; + ALIGN( pData ); + } + + return pData; +} + +byte *WriteMovements( s_animation_t *srcanim, byte *pData ) +{ + mstudiomovement_t *pmovement = (mstudiomovement_t *)pData; + + // NOTE: GoldSrc models saves animations per-sequence and duplicate them + // but we can to avoid wrote identical data block again. + if( srcanim->numpiecewisekeys && !srcanim->movementindex ) + { + srcanim->movementindex = (pData - pStart); + for( int j = 0; j < srcanim->numpiecewisekeys; j++ ) + { + pmovement[j].endframe = srcanim->piecewisemove[j].endframe; + pmovement[j].motionflags = srcanim->piecewisemove[j].flags; + pmovement[j].v0 = srcanim->piecewisemove[j].v0; + pmovement[j].v1 = srcanim->piecewisemove[j].v1; + pmovement[j].angle = RAD2DEG( srcanim->piecewisemove[j].rot[2] ); + pmovement[j].vector = srcanim->piecewisemove[j].vector; + pmovement[j].position = srcanim->piecewisemove[j].pos; + } + + pData += srcanim->numpiecewisekeys * sizeof( mstudiomovement_t ); + ALIGN( pData ); + } + + return pData; +} + +byte *WriteAnimations( byte *pData, byte *pStart, int group ) +{ + int i, j, k; + int q, n; + + mstudioanim_t *panim; + mstudioanimvalue_t *panimvalue; + + // hack for seqgroup 0 + // pseqgroup->data = (pData - pStart); + + for( i = 0; i < g_numseq; i++ ) + { + if( g_sequence[i].seqgroup == group ) + { + // save animations + panim = (mstudioanim_t *)pData; + g_sequence[i].animindex = (pData - pStart); + pData += g_sequence[i].numblends * g_numbones * sizeof( mstudioanim_t ); + ALIGN( pData ); + + panimvalue = (mstudioanimvalue_t *)pData; + + for( q = 0; q < g_sequence[i].numblends; q++ ) + { + // save animation value info + for( j = 0; j < g_numbones; j++ ) + { + for( k = 0; k < 6; k++ ) + { + if( g_sequence[i].panim[q]->numanim[j][k] == 0 ) + { + panim->offset[k] = 0; + } + else + { + panim->offset[k] = ((byte *)panimvalue - (byte *)panim); + for( n = 0; n < g_sequence[i].panim[q]->numanim[j][k]; n++ ) + { + panimvalue->value = g_sequence[i].panim[q]->anim[j][k][n].value; + panimvalue++; + } + } + } + + // a main limitation of GoldSrc studio format ;-( + if(((byte *)panimvalue - (byte *)panim) > 65535 ) + COM_FatalError( "sequence \"%s\" is greater than 64K ( %s )\n", + g_sequence[i].name, Q_memprint( ((byte *)panimvalue - (byte *)panim))); + panim++; + } + } + + pData = (byte *)panimvalue; + ALIGN( pData ); + } + } + + return pData; +} + +byte *WriteAnimDescriptions( byte *pData, byte *pStart, int group ) +{ + int i, j, q; + + mstudioanimdesc_t *panimdesc; + + // hack for seqgroup 0 + // pseqgroup->data = (pData - pStart); + + for( i = 0; i < g_numseq; i++ ) + { + if( g_sequence[i].seqgroup == group ) + { + // write piecewise movement + for( q = 0; q < g_sequence[i].numblends; q++ ) + { + s_animation_t *srcanim = g_sequence[i].panim[q]; + pData = WriteMovements( srcanim, pData ); + } + + // write IK errors + for( q = 0; q < g_sequence[i].numblends; q++ ) + { + s_animation_t *srcanim = g_sequence[i].panim[q]; + pData = WriteIkErrors( srcanim, pData ); + } + + // write animation descriptions + panimdesc = (mstudioanimdesc_t *)pData; + g_sequence[i].animdescindex = (pData - pStart); + pData += g_sequence[i].numblends * sizeof( mstudioanimdesc_t ); + ALIGN( pData ); + + // write anim descriptions + for( q = 0; q < g_sequence[i].numblends; q++ ) + { + s_animation_t *srcanim = g_sequence[i].panim[q]; + mstudioanimdesc_t *destanim = &panimdesc[q]; + + CopyStringTruncate( destanim->label, srcanim->name, sizeof( destanim->label )); + destanim->fps = srcanim->fps; + destanim->flags = srcanim->flags; + + totalframes += srcanim->numframes; + totalseconds += srcanim->numframes / srcanim->fps; + + destanim->numframes = srcanim->numframes; + destanim->nummovements = srcanim->numpiecewisekeys; + destanim->movementindex = srcanim->movementindex; + destanim->numikrules = srcanim->numikrules; + destanim->ikruleindex = srcanim->ikruleindex; + + j = srcanim->numpiecewisekeys - 1; + if( srcanim->piecewisemove[j].pos[0] != 0 || srcanim->piecewisemove[j].pos[1] != 0 ) + { + float t = (srcanim->numframes - 1) / srcanim->fps; + float r = 1.0f / t; + float a = RAD2DEG( atan2( srcanim->piecewisemove[j].pos[1], srcanim->piecewisemove[j].pos[0] )); + float d = sqrt( DotProduct( srcanim->piecewisemove[j].pos, srcanim->piecewisemove[j].pos ) ); + MsgDev( D_REPORT, "%12s %7.2f %7.2f : %7.2f (%7.2f) %.1f\n", + srcanim->name, srcanim->piecewisemove[j].pos[0], srcanim->piecewisemove[j].pos[1], d * r, a, t ); + } + } + } + } + + return pData; +} + +void WriteTextures( void ) +{ + int i, j; + mstudiotexture_t *ptexture; + short *pref; + + // save bone info + ptexture = (mstudiotexture_t *)pData; + phdr->numtextures = g_numtextures; + phdr->textureindex = (pData - pStart); + pData += g_numtextures * sizeof( mstudiotexture_t ); + ALIGN( pData ); + + phdr->skinindex = (pData - pStart); + phdr->numskinref = g_numskinref; + phdr->numskinfamilies = g_numskinfamilies; + pref = (short *)pData; + + for( i = 0; i < phdr->numskinfamilies; i++ ) + { + for( j = 0; j < phdr->numskinref; j++ ) + { + *pref = g_skinref[i][j]; + pref++; + } + } + pData = (byte *)pref; + ALIGN( pData ); + + phdr->texturedataindex = (pData - pStart); // must be the end of the file! + + for( i = 0; i < g_numtextures; i++ ) + { + CopyStringTruncate( ptexture[i].name, g_texture[i].name, sizeof( ptexture[0].name )); + ptexture[i].flags = g_texture[i].flags; + ptexture[i].width = g_texture[i].skinwidth; + ptexture[i].height = g_texture[i].skinheight; + ptexture[i].index = (pData - pStart); + memcpy( pData, g_texture[i].pdata, g_texture[i].size ); + pData += g_texture[i].size; + } + + ALIGN( pData ); +} + +void WriteModel( void ) +{ + int i, j, k; + + mstudiobodyparts_t *pbodypart; + mstudiomodel_t *pmodel; + // Vector *bbox; + byte *pbone; + Vector *pvert; + Vector *pnorm; + mstudioboneweight_t *pweight; + mstudiomesh_t *pmesh; + s_trianglevert_t *psrctri; + int cur; + int total_tris = 0; + int total_strips = 0; + + pbodypart = (mstudiobodyparts_t *)pData; + phdr->numbodyparts = g_numbodyparts; + phdr->bodypartindex = (pData - pStart); + pData += g_numbodyparts * sizeof( mstudiobodyparts_t ); + + pmodel = (mstudiomodel_t *)pData; + pData += g_nummodels * sizeof( mstudiomodel_t ); + + for (i = 0, j = 0; i < g_numbodyparts; i++) + { + CopyStringTruncate( pbodypart[i].name, g_bodypart[i].name, sizeof( pbodypart[0].name )); + pbodypart[i].nummodels = g_bodypart[i].nummodels; + pbodypart[i].base = g_bodypart[i].base; + pbodypart[i].modelindex = ((byte *)&pmodel[j]) - pStart; + j += g_bodypart[i].nummodels; + } + ALIGN( pData ); + + cur = (int)pData; + for( i = 0; i < g_nummodels; i++ ) + { + static int normmap[MAXSTUDIOVERTS]; + static int normimap[MAXSTUDIOVERTS]; + int n = 0; + + CopyStringTruncate( pmodel[i].name, g_model[i]->name, sizeof( pmodel[0].name )); + + // save bbox info + + // remap normals to be sorted by skin reference + for (j = 0; j < g_model[i]->nummesh; j++) + { + for (k = 0; k < g_model[i]->numnorms; k++) + { + if (g_model[i]->norm[k].skinref == g_model[i]->pmesh[j]->skinref) + { + normmap[k] = n; + normimap[n] = k; + n++; + g_model[i]->pmesh[j]->numnorms++; + } + } + } + + // save vertice bones + pbone = pData; + pmodel[i].numverts = g_model[i]->numverts; + pmodel[i].vertinfoindex = (pData - pStart); + for (j = 0; j < pmodel[i].numverts; j++) + { + *pbone++ = g_model[i]->vert[j].globalWeight.bone[0]; + } + ALIGN( pbone ); + + // save normal bones + pmodel[i].numnorms = g_model[i]->numnorms; + pmodel[i].norminfoindex = ((byte *)pbone - pStart); + for (j = 0; j < pmodel[i].numnorms; j++) + { + *pbone++ = g_model[i]->norm[normimap[j]].globalWeight.bone[0]; + } + ALIGN( pbone ); + + pData = pbone; + + if( FBitSet( gflags, STUDIO_HAS_BONEWEIGHTS )) + { + // save blended vertice bones + pweight = (mstudioboneweight_t *)pData; + + pmodel[i].blendvertinfoindex = (pData - pStart); + for (j = 0; j < pmodel[i].numverts; j++) + { + for( k = 0; k < MAXSTUDIOBONEWEIGHTS; k++ ) + { + if( k < g_model[i]->vert[j].globalWeight.numbones ) + { + pweight->bone[k] = g_model[i]->vert[j].globalWeight.bone[k]; + pweight->weight[k] = g_model[i]->vert[j].globalWeight.weight[k] * 255; + } + else + { + pweight->bone[k] = -1; + pweight->weight[k] = 0; + } + } + pweight++; + } + + pData = (byte *)pweight; + ALIGN( pData ); + + // save blended normal bones + pmodel[i].blendnorminfoindex = (pData - pStart); + for (j = 0; j < pmodel[i].numnorms; j++) + { + for( k = 0; k < MAXSTUDIOBONEWEIGHTS; k++ ) + { + if( k < g_model[i]->norm[normimap[j]].globalWeight.numbones ) + { + pweight->bone[k] = g_model[i]->norm[normimap[j]].globalWeight.bone[k]; + pweight->weight[k] = g_model[i]->norm[normimap[j]].globalWeight.weight[k] * 255; + } + else + { + pweight->bone[k] = -1; + pweight->weight[k] = 0; + } + } + pweight++; + } + + pData = (byte *)pweight; + ALIGN( pData ); + } + + // save group info + pvert = (Vector *)pData; + pData += g_model[i]->numverts * sizeof( Vector ); + pmodel[i].vertindex = ((byte *)pvert - pStart); + ALIGN( pData ); + + pnorm = (Vector *)pData; + pData += g_model[i]->numnorms * sizeof( Vector ); + pmodel[i].normindex = ((byte *)pnorm - pStart); + ALIGN( pData ); + + for (j = 0; j < g_model[i]->numverts; j++) + { + VectorCopy( g_model[i]->vert[j].org, pvert[j] ); + } + + for (j = 0; j < g_model[i]->numnorms; j++) + { + VectorCopy( g_model[i]->norm[normimap[j]].org, pnorm[j] ); + } + Msg( "vertices %7d bytes (%d vertices, %d normals)\n", pData - cur, g_model[i]->numverts, g_model[i]->numnorms ); + cur = (int)pData; + + // save mesh info + pmesh = (mstudiomesh_t *)pData; + pmodel[i].nummesh = g_model[i]->nummesh; + pmodel[i].meshindex = (pData - pStart); + pData += pmodel[i].nummesh * sizeof( mstudiomesh_t ); + ALIGN( pData ); + + total_tris = 0; + total_strips = 0; + for (j = 0; j < g_model[i]->nummesh; j++) + { + int numCmdBytes; + byte *pCmdSrc; + + pmesh[j].numtris = g_model[i]->pmesh[j]->numtris; + pmesh[j].skinref = g_model[i]->pmesh[j]->skinref; + pmesh[j].numnorms = g_model[i]->pmesh[j]->numnorms; + + psrctri = (s_trianglevert_t *)(g_model[i]->pmesh[j]->triangle); + for (k = 0; k < pmesh[j].numtris * 3; k++) + { + psrctri->normindex = normmap[psrctri->normindex]; + psrctri++; + } + + numCmdBytes = BuildTris( g_model[i]->pmesh[j]->triangle, g_model[i]->pmesh[j], &pCmdSrc ); + + pmesh[j].triindex = (pData - pStart); + memcpy( pData, pCmdSrc, numCmdBytes ); + pData += numCmdBytes; + ALIGN( pData ); + total_tris += pmesh[j].numtris; + total_strips += numcommandnodes; + } + Msg( "mesh %7d bytes (%d tris, %d strips)\n", pData - cur, total_tris, total_strips ); + cur = (int)pData; + } +} + +void WriteFile( void ) +{ + int i, j, total = 0; + + pStart = (byte *)Mem_Alloc( FILEBUFFER ); + + COM_StripExtension( outname ); + + if( has_boneweights ) + SetBits( gflags, STUDIO_HAS_BONEWEIGHTS|STUDIO_HAS_BONEINFO ); + + // procedural bones are invoke to write extended boneinfo + if( g_numaxisinterpbones || g_numquatinterpbones || g_numjigglebones || g_numaimatbones ) + SetBits( gflags, STUDIO_HAS_BONEINFO ); + + for( i = 1; i < g_numseqgroups; i++ ) + { + // write the non-default sequence group data to separate files + char groupname[128], localname[128]; + + Q_snprintf( groupname, sizeof( groupname ), "%s%02d.mdl", outname, i ); + + Msg( "writing %s:\n", groupname ); + + pseqhdr = (studioseqhdr_t *)pStart; + pseqhdr->id = IDSEQGRPHEADER; + pseqhdr->version = STUDIO_VERSION; + + pData = pStart + sizeof( studioseqhdr_t ); + + pData = WriteAnimations( pData, pStart, i ); + pData = WriteAnimDescriptions( pData, pStart, i ); + + COM_FileBase( groupname, localname ); + Q_snprintf( g_sequencegroup[i].name, sizeof( g_sequencegroup[0].name ), "models\\%s.mdl", localname ); + CopyStringTruncate( pseqhdr->name, g_sequencegroup[i].name, sizeof( pseqhdr->name )); + pseqhdr->length = pData - pStart; + + Msg( "total %6d\n", pseqhdr->length ); + + COM_SaveFile( groupname, pStart, pseqhdr->length, true ); + memset( pStart, 0, pseqhdr->length ); + } + + if( split_textures ) + { + // write textures out to a separate file + char texname[128]; + + sprintf( texname, "%sT.mdl", outname ); + + Msg( "writing %s:\n", texname ); + + phdr = (studiohdr_t *)pStart; + phdr->ident = IDSTUDIOHEADER; + phdr->version = STUDIO_VERSION; + + pData = (byte *)phdr + sizeof( studiohdr_t ); + + WriteTextures( ); + + phdr->length = pData - pStart; + Msg( "textures %7d bytes\n", phdr->length ); + + COM_SaveFile( texname, pStart, phdr->length, true ); + + memset( pStart, 0, phdr->length ); + pData = pStart; + } + +// +// write the model output file +// + COM_DefaultExtension( outname, ".mdl" ); + + Msg( "---------------------\n" ); + phdr = (studiohdr_t *)pStart; + + phdr->ident = IDSTUDIOHEADER; + phdr->version = STUDIO_VERSION; + COM_FileBase( outname, phdr->name ); // g-cont. don't show real path on artist machine! palevo! + Q_strncat( phdr->name, " (written by Xash3D studiomdl)", sizeof( phdr->name )); // write signature instead + VectorCopy( eyeposition, phdr->eyeposition ); + VectorCopy( bbox[0], phdr->min ); + VectorCopy( bbox[1], phdr->max ); + VectorCopy( cbox[0], phdr->bbmin ); + VectorCopy( cbox[1], phdr->bbmax ); + + phdr->flags = gflags; + + pData = (byte *)phdr + sizeof( studiohdr_t ); + phdr2 = (studiohdr2_t *)pData; + phdr->studiohdr2index = pData - pStart; + pData += sizeof( studiohdr2_t ); + + WriteBoneInfo( ); + Msg( "bones %7d bytes (%d)\n", pData - pStart - total, g_numbones ); + total = pData - pStart; + + // write ik chains + mstudioikchain_t *pikchain = (mstudioikchain_t *)pData; + phdr2->numikchains = g_numikchains; + phdr2->ikchainindex = pData - pStart; + pData += g_numikchains * sizeof( mstudioikchain_t ); + ALIGN( pData ); + + for( j = 0; j < g_numikchains; j++ ) + { + CopyStringTruncate( pikchain->name, g_ikchain[j].name, sizeof( pikchain->name )); + pikchain->numlinks = g_ikchain[j].numlinks; + + mstudioiklink_t *piklink = (mstudioiklink_t *)pData; + pikchain->linkindex = (pData - pStart); + pData += pikchain->numlinks * sizeof( mstudioiklink_t ); + + for( i = 0; i < pikchain->numlinks; i++ ) + { + piklink[i].bone = g_ikchain[j].link[i].bone; + piklink[i].kneeDir = g_ikchain[j].link[i].kneeDir; + } + pikchain++; + } + + // save autoplay locks + mstudioiklock_t *piklock = (mstudioiklock_t *)pData; + phdr2->numikautoplaylocks = g_numikautoplaylocks; + phdr2->ikautoplaylockindex = pData - pStart; + pData += g_numikautoplaylocks * sizeof( mstudioiklock_t ); + ALIGN( pData ); + + for( j = 0; j < g_numikautoplaylocks; j++ ) + { + piklock->chain = g_ikautoplaylock[j].chain; + piklock->flPosWeight = g_ikautoplaylock[j].flPosWeight; + piklock->flLocalQWeight = g_ikautoplaylock[j].flLocalQWeight; + piklock++; + } + + // save pose parameters + mstudioposeparamdesc_t *ppose = (mstudioposeparamdesc_t *)pData; + phdr2->numposeparameters = g_numposeparameters; + phdr2->poseparamindex = pData - pStart; + pData += g_numposeparameters * sizeof( mstudioposeparamdesc_t ); + ALIGN( pData ); + + for( i = 0; i < g_numposeparameters; i++ ) + { + CopyStringTruncate( ppose[i].name, g_pose[i].name, sizeof( ppose[0].name )); + ppose[i].start = g_pose[i].min; + ppose[i].end = g_pose[i].max; + ppose[i].flags = g_pose[i].flags; + ppose[i].loop = g_pose[i].loop; + } + + Msg( "ik/pose %7d bytes\n", pData - pStart - total ); + + pData = WriteAnimations( pData, pStart, 0 ); + pData = WriteAnimDescriptions( pData, pStart, 0 ); + + WriteSequenceInfo( ); + Msg( "sequences %6d bytes (%d frames) [%d:%02d]\n", pData - pStart - total, totalframes, (int)totalseconds / 60, (int)totalseconds % 60 ); + total = pData - pStart; + + WriteModel( ); + Msg( "models %7d bytes\n", pData - pStart - total ); + total = pData - pStart; + + WriteKeyValues( phdr2, &g_KeyValueText ); + Msg( "keyvalues %7d bytes\n", pData - pStart - total ); + + // NOTE: textures must be last! + if( !split_textures ) + { + WriteTextures( ); + Msg( "textures %7d bytes\n", pData - pStart - total ); + total = pData - pStart; + } + + total = pData - pStart; + + phdr->length = pData - pStart; + + Msg( "total %s\n", Q_memprint( phdr->length )); + + Msg( "writing: %s\n", outname ); + + COM_SaveFile( outname, pStart, phdr->length, true ); + + // allow to write model but warn the user + if( pData > pStart + FILEBUFFER ) + COM_FatalError( "WriteModel: memory buffer is corrupted. Check the model!\n" ); + Mem_Free( pStart ); +} \ No newline at end of file diff --git a/utils/vgui/include/VGUI.h b/utils/vgui/include/VGUI.h new file mode 100644 index 0000000..acbd542 --- /dev/null +++ b/utils/vgui/include/VGUI.h @@ -0,0 +1,107 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_H +#define VGUI_H + +//If you are going to add stuff to the vgui core... +// +//Keep it simple. +// +//Never put code in a header. +// +//The name of the class is the name of the the file +// +//Each class gets its own .cpp file for its definition and a .h for its header. Helper +//classes can be used but only within the .cpp and not referenceable from anywhere else. +// +//Don't add unneeded files. Keep the API clean. +// +//No platform specific code in vgui\lib-src\vgui dir. Code in vgui\lib-src\vgui should +//only include from vgui\include or standard C includes. ie, if I see windows.h included +//anywhere but vgui\lib-src\win32 I will hunt you down and kill you. Don't give me any crap +//that mfc is platform inspecific. +// +//Always use <> and not "" for includes +// +//Use minimum dependencies in headers. Don't include another header if you can get away +//with forward declaring (which is usually the case) +// +//No macros in headers. They are tools of satan. This also means no use of DEFINEs, use enum +// +//Minimize global functions +// +//No global variables. +// +//Panel is getting pretty plump, try and avoid adding junk to it if you can + +//TODO: Look and Feel support +// add Panel::setPaintProxy, if _paintProxy exists, it calls _paintProxy->paint +// instead of Panel::paint. Components should implement their painting in a seperate +// plugin class. Perhaps to encourage this, Panel::paint should just go away completely +// The other option is to have Panel have the interface Paintable +// class Paintable +// { +// public: +// virtual void paint()=0; +// }; +// Then a component can implement its paint in the class itself and then call +// setPaintProxy(this). If this is the case _paintProxy->paint should always be called +// and never Panel::paint from within paintTraverse +//TODO: Figure out the 'Valve' Look and Feel and implement that instead of a the Java one +//TODO: Determine ownership policy for Borders, Layouts, etc.. +//TODO: tooltips support +//TODO: ComboKey (hot key support) +//TODO: add Background.cpp, remove paintBackground from all components +// Panel implements setBackground, Panel::paintBackground calls _background->paintBackground +// similiar to the way Border works. +//TODO: Builtin components should never overide paintBackground, only paint +//TODO: All protected members should be converted to private +//TODO: All member variables should be moved to the top of the class prototype +//TODO: All private methods should be prepended with private +//TODO: Use of word internal in method names is not consistent and confusing +//TODO: Cleanup so bullshit publics are properly named, maybe even figure out +// a naming convention for them +//TODO: Breakup InputSignal into logical pieces +//TODO: Button is in a state of disarray, it should have ButtonModel support +//TODO: get rid of all the stupid strdup laziness, convert to vgui_strdup +//TODO: actually figure out policy on String and implement it consistently +//TODO: implement createLayoutInfo for other Layouts than need it +//TODO: BorderLayout should have option for a null LayoutInfo defaulting to center +//TODO: SurfaceBase should go away, put it in Surface +//TODO: ActionSignals and other Signals should just set a flag when they fire. +// then App can come along later and fire all the signals +//TODO: Change all method naming to starting with a capital letter. + +#ifdef _WIN32 +# define VGUIAPI __declspec( dllexport ) +#else +# define VGUIAPI +#endif + +#define null 0L + +typedef unsigned char uchar; +typedef unsigned short ushort; +typedef unsigned int uint; +typedef unsigned long ulong; + +namespace vgui +{ + +VGUIAPI void vgui_setMalloc(void* (*malloc)(size_t size)); +VGUIAPI void vgui_setFree(void (*free)(void* memblock)); +VGUIAPI void vgui_strcpy(char* dst,int dstLen,const char* src); +VGUIAPI char* vgui_strdup(const char* src); +VGUIAPI int vgui_printf(const char* format,...); +VGUIAPI int vgui_dprintf(const char* format,...); +VGUIAPI int vgui_dprintf2(const char* format,...); + +} + +#endif + diff --git a/utils/vgui/include/VGUI_ActionSignal.h b/utils/vgui/include/VGUI_ActionSignal.h new file mode 100644 index 0000000..530f468 --- /dev/null +++ b/utils/vgui/include/VGUI_ActionSignal.h @@ -0,0 +1,84 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_ACTIONSIGNAL_H +#define VGUI_ACTIONSIGNAL_H + +#include + +/* + +TODO: maybe try something like this.. + +class VGUIAPI ActionSignal +{ +}; + +class VGUIAPI ActionSignalSimple : public ActionSignal +{ +public: + virtual void actionPerformed(Panel* panel)=0; +}; + +class VGUIAPI ActionSignalInt : public ActionSignal +{ +public: + virtual void actionPerformed(int value,Panel* panel)=0; +}; + + +DefaultButtonModel would implement: + +virtual void addActionSignal(ActionSignal* s) +{ + if(s!=null) + { + _actionSignalDar.putElement(s); + } +} + +virtual void fireActionSignal() +{ + for(int i=0;i<_actionSignalDar.getCount();i++) + { + ActionSignal* signal=_actionSignalDar[i]; + + ActionSignalSimple* ss=dynamic_cast(signal); + if(ss!=null) + { + ss->actionPerformed(this); + } + + ActionSignalCommand* si=dynamic_cast(signal); + if(si!=null) + { + si->actionPerformed(_intValue,this); + } + } +} + + +*/ + +#include + +namespace vgui +{ + +class Panel; + +class VGUIAPI ActionSignal +{ +public: + virtual void actionPerformed(Panel* panel)=0; +}; + +} + +#endif + + diff --git a/utils/vgui/include/VGUI_App.h b/utils/vgui/include/VGUI_App.h new file mode 100644 index 0000000..a13cdb5 --- /dev/null +++ b/utils/vgui/include/VGUI_App.h @@ -0,0 +1,132 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_APP_H +#define VGUI_APP_H + +#include +#include +#include +#include +#include + +namespace vgui +{ + +enum MouseCode; +enum KeyCode; +class Panel; +class TickSignal; +class Scheme; +class TickSignal; +class SurfaceBase; + +class VGUIAPI App +{ +public: + App(); + App(bool externalMain); +public: + static App* getInstance(); + //TODO: the public and public bullshit are all messed up, need to organize + //TODO: actually all of the access needs to be properly thought out while you are at it +public: + virtual void start(); + virtual void stop(); + virtual void externalTick(); + virtual bool wasMousePressed(MouseCode code,Panel* panel); + virtual bool wasMouseDoublePressed(MouseCode code,Panel* panel); + virtual bool isMouseDown(MouseCode code,Panel* panel); + virtual bool wasMouseReleased(MouseCode code,Panel* panel); + virtual bool wasKeyPressed(KeyCode code,Panel* panel); + virtual bool isKeyDown(KeyCode code,Panel* panel); + virtual bool wasKeyTyped(KeyCode code,Panel* panel); + virtual bool wasKeyReleased(KeyCode code,Panel* panel); + virtual void addTickSignal(TickSignal* s); + virtual void setCursorPos(int x,int y); + virtual void getCursorPos(int& x,int& y); + virtual void setMouseCapture(Panel* panel); + virtual void setMouseArena(int x0,int y0,int x1,int y1,bool enabled); + virtual void setMouseArena(Panel* panel); + virtual void requestFocus(Panel* panel); + virtual Panel* getFocus(); + virtual void repaintAll(); + virtual void setScheme(Scheme* scheme); + virtual Scheme* getScheme(); + virtual void enableBuildMode(); + virtual long getTimeMillis(); + virtual char getKeyCodeChar(KeyCode code,bool shifted); + virtual void getKeyCodeText(KeyCode code,char* buf,int buflen); + virtual int getClipboardTextCount(); + virtual void setClipboardText(const char* text,int textLen); + virtual int getClipboardText(int offset,char* buf,int bufLen); + virtual void reset(); + virtual void internalSetMouseArena(int x0,int y0,int x1,int y1,bool enabled); + virtual bool setRegistryString(const char* key,const char* value); + virtual bool getRegistryString(const char* key,char* value,int valueLen); + virtual bool setRegistryInteger(const char* key,int value); + virtual bool getRegistryInteger(const char* key,int& value); + virtual void setCursorOveride(Cursor* cursor); + virtual Cursor* getCursorOveride(); + virtual void setMinimumTickMillisInterval(int interval); +public: //bullshit public stuff + virtual void main(int argc,char* argv[])=0; + virtual void run(); + virtual void internalCursorMoved(int x,int y,SurfaceBase* surfaceBase); //expects input in surface space + virtual void internalMousePressed(MouseCode code,SurfaceBase* surfaceBase); + virtual void internalMouseDoublePressed(MouseCode code,SurfaceBase* surfaceBase); + virtual void internalMouseReleased(MouseCode code,SurfaceBase* surfaceBase); + virtual void internalMouseWheeled(int delta,SurfaceBase* surfaceBase); + virtual void internalKeyPressed(KeyCode code,SurfaceBase* surfaceBase); + virtual void internalKeyTyped(KeyCode code,SurfaceBase* surfaceBase); + virtual void internalKeyReleased(KeyCode code,SurfaceBase* surfaceBase); +private: + virtual void init(); + virtual void updateMouseFocus(int x,int y,SurfaceBase* surfaceBase); + virtual void setMouseFocus(Panel* newMouseFocus); +protected: + virtual void surfaceBaseCreated(SurfaceBase* surfaceBase); + virtual void surfaceBaseDeleted(SurfaceBase* surfaceBase); + virtual void platTick(); + virtual void internalTick(); +protected: + static App* _instance; +protected: + bool _running; + bool _externalMain; + Dar _surfaceBaseDar; + Panel* _keyFocus; + Panel* _oldMouseFocus; + Panel* _mouseFocus; + Panel* _mouseCapture; + Panel* _wantedKeyFocus; + bool _mousePressed[MOUSE_LAST]; + bool _mouseDoublePressed[MOUSE_LAST]; + bool _mouseDown[MOUSE_LAST]; + bool _mouseReleased[MOUSE_LAST]; + bool _keyPressed[KEY_LAST]; + bool _keyTyped[KEY_LAST]; + bool _keyDown[KEY_LAST]; + bool _keyReleased[KEY_LAST]; + Dar _tickSignalDar; + Scheme* _scheme; + bool _buildMode; + bool _wantedBuildMode; + Panel* _mouseArenaPanel; + Cursor* _cursor[Cursor::DefaultCursor::dc_last]; + Cursor* _cursorOveride; +private: + long _nextTickMillis; + long _minimumTickMillisInterval; + friend class SurfaceBase; +}; +} + +#endif + + + diff --git a/utils/vgui/include/VGUI_Bitmap.h b/utils/vgui/include/VGUI_Bitmap.h new file mode 100644 index 0000000..04de306 --- /dev/null +++ b/utils/vgui/include/VGUI_Bitmap.h @@ -0,0 +1,37 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_BITMAP_H +#define VGUI_BITMAP_H + +#include +#include + +namespace vgui +{ + +class Panel; + +class VGUIAPI Bitmap : public Image +{ +private: + int _id; + bool _uploaded; +public: + Bitmap(); +protected: + virtual void setSize(int wide,int tall); + virtual void setRGBA(int x,int y,uchar r,uchar g,uchar b,uchar a); +public: + virtual void paint(Panel* panel); +protected: + uchar* _rgba; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_BitmapTGA.h b/utils/vgui/include/VGUI_BitmapTGA.h new file mode 100644 index 0000000..f0ecedb --- /dev/null +++ b/utils/vgui/include/VGUI_BitmapTGA.h @@ -0,0 +1,29 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_BITMAPTGA_H +#define VGUI_BITMAPTGA_H + +#include + +namespace vgui +{ + +class Panel; +class InputStream; + +class VGUIAPI BitmapTGA : public Bitmap +{ +public: + BitmapTGA(InputStream* is,bool invertAlpha); +private: + virtual bool loadTGA(InputStream* is,bool invertAlpha); +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_Border.h b/utils/vgui/include/VGUI_Border.h new file mode 100644 index 0000000..416b878 --- /dev/null +++ b/utils/vgui/include/VGUI_Border.h @@ -0,0 +1,45 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_BORDER_H +#define VGUI_BORDER_H + +#include +#include + +//TODO: all borders should be titled + +namespace vgui +{ + +class Panel; + +class VGUIAPI Border : public Image +{ +public: + Border(); + Border(int left,int top,int right,int bottom); +public: + virtual void setInset(int left,int top,int right,int bottom); + virtual void getInset(int& left,int& top,int& right,int& bottom); +protected: + virtual void drawFilledRect(int x0,int y0,int x1,int y1); + virtual void drawOutlinedRect(int x0,int y0,int x1,int y1); + virtual void drawSetTextPos(int x,int y); + virtual void drawPrintText(int x,int y,const char* str,int strlen); + virtual void drawPrintChar(int x,int y,char ch); +protected: + int _inset[4]; +private: + Panel* _panel; +friend class Panel; +friend class BorderPair; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_BorderLayout.h b/utils/vgui/include/VGUI_BorderLayout.h new file mode 100644 index 0000000..2d7678e --- /dev/null +++ b/utils/vgui/include/VGUI_BorderLayout.h @@ -0,0 +1,46 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_BORDERLAYOUT_H +#define VGUI_BORDERLAYOUT_H + +#include +#include + +namespace vgui +{ + +class LayoutInfo; + +class VGUIAPI BorderLayout : public Layout +{ +public: + enum Alignment + { + a_center=0, + a_north, + a_south, + a_east, + a_west, + }; +private: + int _inset; +public: + BorderLayout(int inset); +public: + virtual void performLayout(Panel* panel); + virtual LayoutInfo* createLayoutInfo(Alignment alignment); +}; + +} + +#endif + + + + + diff --git a/utils/vgui/include/VGUI_BorderPair.h b/utils/vgui/include/VGUI_BorderPair.h new file mode 100644 index 0000000..82318d4 --- /dev/null +++ b/utils/vgui/include/VGUI_BorderPair.h @@ -0,0 +1,33 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_BORDERPAIR_H +#define VGUI_BORDERPAIR_H + +#include +#include + +namespace vgui +{ + +class Panel; + +class VGUIAPI BorderPair : public Border +{ +public: + BorderPair(Border* border0,Border* border1); +public: + virtual void doPaint(Panel* panel); +protected: + virtual void paint(Panel* panel); +protected: + Border* _border[2]; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_BuildGroup.h b/utils/vgui/include/VGUI_BuildGroup.h new file mode 100644 index 0000000..694ac5c --- /dev/null +++ b/utils/vgui/include/VGUI_BuildGroup.h @@ -0,0 +1,67 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_BUILDGROUP_H +#define VGUI_BUILDGROUP_H + +#include +#include + + +namespace vgui +{ + +enum KeyCode; +enum MouseCode; +class Panel; +class Cursor; +class ChangeSignal; + +class VGUIAPI BuildGroup +{ +private: + bool _enabled; + int _snapX; + int _snapY; + Cursor* _cursor_sizenwse; + Cursor* _cursor_sizenesw; + Cursor* _cursor_sizewe; + Cursor* _cursor_sizens; + Cursor* _cursor_sizeall; + bool _dragging; + MouseCode _dragMouseCode; + int _dragStartPanelPos[2]; + int _dragStartCursorPos[2]; + Panel* _currentPanel; + Dar _currentPanelChangeSignalDar; + Dar _panelDar; + Dar _panelNameDar; +public: + BuildGroup(); +public: + virtual void setEnabled(bool state); + virtual bool isEnabled(); + virtual void addCurrentPanelChangeSignal(ChangeSignal* s); + virtual Panel* getCurrentPanel(); + virtual void copyPropertiesToClipboard(); +private: + virtual void applySnap(Panel* panel); + virtual void fireCurrentPanelChangeSignal(); +protected: + friend class Panel; + virtual void panelAdded(Panel* panel,const char* panelName); + virtual void cursorMoved(int x,int y,Panel* panel); + virtual void mousePressed(MouseCode code,Panel* panel); + virtual void mouseReleased(MouseCode code,Panel* panel); + virtual void mouseDoublePressed(MouseCode code,Panel* panel); + virtual void keyTyped(KeyCode code,Panel* panel); + virtual Cursor* getCursor(Panel* panel); +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_Button.h b/utils/vgui/include/VGUI_Button.h new file mode 100644 index 0000000..7885192 --- /dev/null +++ b/utils/vgui/include/VGUI_Button.h @@ -0,0 +1,61 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_BUTTON_H +#define VGUI_BUTTON_H + +#include +#include +#include + +namespace vgui +{ + +enum MouseCode; +class ButtonController; +class ButtonGroup; +class ActionSignal; + +//TODO: Button should be derived from an AbstractButton +class VGUIAPI Button : public Label +{ +public: + Button(const char* text,int x,int y,int wide,int tall); + Button(const char* text,int x,int y); +private: + void init(); +public: + virtual void setSelected(bool state); + virtual void setSelectedDirect(bool state); + virtual void setArmed(bool state); + virtual bool isSelected(); + virtual void doClick(); + virtual void addActionSignal(ActionSignal* s); + virtual void setButtonGroup(ButtonGroup* buttonGroup); + virtual bool isArmed(); + virtual void setButtonBorderEnabled(bool state); + virtual void setMouseClickEnabled(MouseCode code,bool state); + virtual bool isMouseClickEnabled(MouseCode code); + virtual void fireActionSignal(); + virtual Panel* createPropertyPanel(); +protected: + virtual void setButtonController(ButtonController* _buttonController); + virtual void paintBackground(); +protected: + char* _text; + bool _armed; + bool _selected; + bool _buttonBorderEnabled; + Dar _actionSignalDar; + int _mouseClickMask; + ButtonGroup* _buttonGroup; + ButtonController* _buttonController; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_ButtonController.h b/utils/vgui/include/VGUI_ButtonController.h new file mode 100644 index 0000000..f4f6d10 --- /dev/null +++ b/utils/vgui/include/VGUI_ButtonController.h @@ -0,0 +1,27 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_BUTTONCONTROLLER_H +#define VGUI_BUTTONCONTROLLER_H + +#include + +namespace vgui +{ + +class Button; + +class VGUIAPI ButtonController +{ +public: + virtual void addSignals(Button* button)=0; + virtual void removeSignals(Button* button)=0; +}; + +} + +#endif diff --git a/utils/vgui/include/VGUI_ButtonGroup.h b/utils/vgui/include/VGUI_ButtonGroup.h new file mode 100644 index 0000000..5f14bb4 --- /dev/null +++ b/utils/vgui/include/VGUI_ButtonGroup.h @@ -0,0 +1,30 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_BUTTONGROUP_H +#define VGUI_BUTTONGROUP_H + +#include +#include + +namespace vgui +{ + +class Button; + +class VGUIAPI ButtonGroup +{ +public: + virtual void addButton(Button* button); + virtual void setSelected(Button* button); +protected: + Dar _buttonDar; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_ChangeSignal.h b/utils/vgui/include/VGUI_ChangeSignal.h new file mode 100644 index 0000000..85d6d27 --- /dev/null +++ b/utils/vgui/include/VGUI_ChangeSignal.h @@ -0,0 +1,26 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_CHANGESIGNAL_H +#define VGUI_CHANGESIGNAL_H + +#include + +namespace vgui +{ + +class Panel; + +class VGUIAPI ChangeSignal +{ +public: + virtual void valueChanged(Panel* panel)=0; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_CheckButton.h b/utils/vgui/include/VGUI_CheckButton.h new file mode 100644 index 0000000..a3d7169 --- /dev/null +++ b/utils/vgui/include/VGUI_CheckButton.h @@ -0,0 +1,28 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_CHECKBUTTON_H +#define VGUI_CHECKBUTTON_H + +#include +#include + +namespace vgui +{ + +class VGUIAPI CheckButton : public ToggleButton +{ +public: + CheckButton(const char* text,int x,int y,int wide,int tall); + CheckButton(const char* text,int x,int y); +protected: + virtual void paintBackground(); +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_Color.h b/utils/vgui/include/VGUI_Color.h new file mode 100644 index 0000000..2ed676a --- /dev/null +++ b/utils/vgui/include/VGUI_Color.h @@ -0,0 +1,44 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_COLOR_H +#define VGUI_COLOR_H + +#include +#include + +//TODO: rename getColor(r,g,b,a) to getRGBA(r,g,b,a) +//TODO: rename setColor(r,g,b,a) to setRGBA(r,g,b,a) +//TODO: rename getColor(sc) to getSchemeColor(sc) +//TODO: rename setColor(sc) to setSchemeColor(sc) + +namespace vgui +{ + +class VGUIAPI Color +{ +private: + uchar _color[4]; + Scheme::SchemeColor _schemeColor; +public: + Color(); + Color(int r,int g,int b,int a); + Color(Scheme::SchemeColor sc); +private: + virtual void init(); +public: + virtual void setColor(int r,int g,int b,int a); + virtual void setColor(Scheme::SchemeColor sc); + virtual void getColor(int& r,int& g,int& b,int& a); + virtual void getColor(Scheme::SchemeColor& sc); + virtual int operator[](int index); +}; + +} + + +#endif diff --git a/utils/vgui/include/VGUI_ComboKey.h b/utils/vgui/include/VGUI_ComboKey.h new file mode 100644 index 0000000..adc8abf --- /dev/null +++ b/utils/vgui/include/VGUI_ComboKey.h @@ -0,0 +1,34 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_COMBOKEY_H +#define VGUI_COMBOKEY_H + +#include + +namespace vgui +{ + +enum KeyCode; + +class ComboKey +{ +public: + ComboKey(KeyCode code,KeyCode modifier); +public: + bool isTwoCombo(KeyCode code,KeyCode modifier); +protected: + bool check(KeyCode code); +protected: + KeyCode _keyCode[2]; +friend class Panel; +}; + +} + + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_ConfigWizard.h b/utils/vgui/include/VGUI_ConfigWizard.h new file mode 100644 index 0000000..713ecb4 --- /dev/null +++ b/utils/vgui/include/VGUI_ConfigWizard.h @@ -0,0 +1,40 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_CONFIGWIZARD_H +#define VGUI_CONFIGWIZARD_H + +#include +#include + +namespace vgui +{ + +class TreeFolder; +class Panel; +class Button; + +class VGUIAPI ConfigWizard : public Panel +{ +public: + ConfigWizard(int x,int y,int wide,int tall); +public: + virtual void setSize(int wide,int tall); + virtual Panel* getClient(); + virtual TreeFolder* getFolder(); +protected: + TreeFolder* _treeFolder; + Panel* _client; + Button* _okButton; + Button* _cancelButton; + Button* _applyButton; + Button* _helpButton; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_Cursor.h b/utils/vgui/include/VGUI_Cursor.h new file mode 100644 index 0000000..44ae111 --- /dev/null +++ b/utils/vgui/include/VGUI_Cursor.h @@ -0,0 +1,57 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_CURSOR_H +#define VGUI_CURSOR_H + +#include + +namespace vgui +{ + +class Bitmap; + +class VGUIAPI Cursor +{ +public: + enum DefaultCursor + { + dc_user, + dc_none, + dc_arrow, + dc_ibeam, + dc_hourglass, + dc_crosshair, + dc_up, + dc_sizenwse, + dc_sizenesw, + dc_sizewe, + dc_sizens, + dc_sizeall, + dc_no, + dc_hand, + dc_last, + }; +private: + int _hotspot[2]; + Bitmap* _bitmap; + DefaultCursor _dc; +public: + Cursor(DefaultCursor dc); + Cursor(Bitmap* bitmap,int hotspotX,int hotspotY); +public: + virtual void getHotspot(int& x,int& y); +private: + virtual void privateInit(Bitmap* bitmap,int hotspotX,int hotspotY); +public: + virtual Bitmap* getBitmap(); + virtual DefaultCursor getDefaultCursor(); +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_Dar.h b/utils/vgui/include/VGUI_Dar.h new file mode 100644 index 0000000..c0f1990 --- /dev/null +++ b/utils/vgui/include/VGUI_Dar.h @@ -0,0 +1,193 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_DAR_H +#define VGUI_DAR_H + +#include +#include +#include + + + +namespace vgui +{ + +//Simple lightweight dynamic array implementation +template class VGUIAPI Dar +{ +public: + Dar() + { + _count=0; + _capacity=0; + _data=null; + ensureCapacity(4); + } + Dar(int initialCapacity) + { + _count=0; + _capacity=0; + _data=null; + ensureCapacity(initialCapacity); + } +public: + void ensureCapacity(int wantedCapacity) + { + if(wantedCapacity<=_capacity){return;} + + //double capacity until it is >= wantedCapacity + //this could be done with math, but iterative is just so much more fun + int newCapacity=_capacity; + if(newCapacity==0){newCapacity=1;} + while(newCapacity_capacity)) + { + return; + } + _count=count; + } + int getCount() + { + return _count; + } + void addElement(ELEMTYPE elem) + { + ensureCapacity(_count+1); + _data[_count]=elem; + _count++; + } + bool hasElement(ELEMTYPE elem) + { + for(int i=0;i<_count;i++) + { + if(_data[i]==elem) + { + return true; + } + } + return false; + } + void putElement(ELEMTYPE elem) + { + if(hasElement(elem)) + { + return; + } + addElement(elem); + } + void insertElementAt(ELEMTYPE elem,int index) + { + if((index<0)||(index>_count)) + { + return; + } + if((index==_count)||(_count==0)) + { + addElement(elem); + } + else + { + addElement(elem); //just to make sure it is big enough + for(int i=_count-1;i>index;i--) + { + _data[i]=_data[i-1]; + } + _data[index]=elem; + } + } + void setElementAt(ELEMTYPE elem,int index) + { + if((index<0)||(index>=_count)) + { + return; + } + _data[index]=elem; + } + void removeElementAt(int index) + { + if((index<0)||(index>=_count)) + { + return; + } + + //slide everything to the right of index, left one. + for(int i=index;i<(_count-1);i++) + { + _data[i]=_data[i+1]; + } + _count--; + } + void removeElement(ELEMTYPE elem) + { + for(int i=0;i<_count;i++) + { + if(_data[i]==elem) + { + removeElementAt(i); + break; + } + } + } + void removeAll() + { + _count=0; + } + ELEMTYPE operator[](int index) + { + if((index<0)||(index>=_count)) + { + return null; + } + return _data[index]; + } +protected: + int _count; + int _capacity; + ELEMTYPE* _data; +}; + +//forward referencing all the template types used so they get exported +template class VGUIAPI Dar; +template class VGUIAPI Dar; +template class VGUIAPI Dar; +template class VGUIAPI Dar; +template class VGUIAPI Dar; +template class VGUIAPI Dar; +template class VGUIAPI Dar; +template class VGUIAPI Dar; +template class VGUIAPI Dar; +template class VGUIAPI Dar; +template class VGUIAPI Dar; +template class VGUIAPI Dar*>; +template class VGUIAPI Dar; +template class VGUIAPI Dar; +template class VGUIAPI Dar; +template class VGUIAPI Dar; +template class VGUIAPI Dar; +template class VGUIAPI Dar; + + +} + + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_DataInputStream.h b/utils/vgui/include/VGUI_DataInputStream.h new file mode 100644 index 0000000..dba1acf --- /dev/null +++ b/utils/vgui/include/VGUI_DataInputStream.h @@ -0,0 +1,49 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_DATAINPUTSTREAM_H +#define VGUI_DATAINPUTSTREAM_H + +#include +#include + +namespace vgui +{ + +class VGUIAPI DataInputStream : virtual public InputStream +{ +private: + InputStream* _is; +public: + DataInputStream(InputStream* is); +public: + virtual void seekStart(bool& success); + virtual void seekRelative(int count,bool& success); + virtual void seekEnd(bool& success); + virtual int getAvailable(bool& success); + //virtual uchar readUChar(bool& success); + virtual void readUChar(uchar* buf,int count,bool& success); + virtual void close(bool& success); + virtual void close(); +public: + virtual bool readBool(bool& success); + virtual char readChar(bool& success); + virtual uchar readUChar(bool& success); + virtual short readShort(bool& success); + virtual ushort readUShort(bool& success); + virtual int readInt(bool& success); + virtual uint readUInt(bool& success); + virtual long readLong(bool& success); + virtual ulong readULong(bool& success); + virtual float readFloat(bool& success); + virtual double readDouble(bool& success); + virtual void readLine(char* buf,int bufLen,bool& success); +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_Desktop.h b/utils/vgui/include/VGUI_Desktop.h new file mode 100644 index 0000000..da8e0d0 --- /dev/null +++ b/utils/vgui/include/VGUI_Desktop.h @@ -0,0 +1,42 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_DESKTOP_H +#define VGUI_DESKTOP_H + +#include +#include +#include + +namespace vgui +{ + +class DesktopIcon; +class TaskBar; + +class VGUIAPI Desktop : public Panel +{ +public: + Desktop(int x,int y,int wide,int tall); +public: + virtual void setSize(int wide,int tall); + virtual void iconActivated(DesktopIcon* icon); + virtual void addIcon(DesktopIcon* icon); + virtual void arrangeIcons(); + virtual Panel* getBackground(); + virtual Panel* getForeground(); +protected: + Panel* _background; + Panel* _foreground; + TaskBar* _taskBar; + Dar _desktopIconDar; + int _cascade[2]; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_DesktopIcon.h b/utils/vgui/include/VGUI_DesktopIcon.h new file mode 100644 index 0000000..192fef4 --- /dev/null +++ b/utils/vgui/include/VGUI_DesktopIcon.h @@ -0,0 +1,41 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_DESKTOPICON_H +#define VGUI_DESKTOPICON_H + +#include +#include + +namespace vgui +{ + +class MiniApp; +class Image; +class Desktop; + +class VGUIAPI DesktopIcon : public Panel +{ +public: + DesktopIcon(MiniApp* miniApp,Image* image); +public: + virtual void doActivate(); + virtual void setImage(Image* image); +public: //bullshit public + virtual void setDesktop(Desktop* desktop); + virtual MiniApp* getMiniApp(); +protected: + virtual void paintBackground(); +protected: + Desktop* _desktop; + MiniApp* _miniApp; + Image* _image; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_EditPanel.h b/utils/vgui/include/VGUI_EditPanel.h new file mode 100644 index 0000000..23e2926 --- /dev/null +++ b/utils/vgui/include/VGUI_EditPanel.h @@ -0,0 +1,65 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_EDITPANEL_H +#define VGUI_EDITPANEL_H + +#include +#include +#include + +namespace vgui +{ + +class Font; + +class VGUIAPI EditPanel : public Panel +{ +public: + EditPanel(int x,int y,int wide,int tall); +public: + virtual void doCursorUp(); + virtual void doCursorDown(); + virtual void doCursorLeft(); + virtual void doCursorRight(); + virtual void doCursorToStartOfLine(); + virtual void doCursorToEndOfLine(); + virtual void doCursorInsertChar(char ch); + virtual void doCursorBackspace(); + virtual void doCursorNewLine(); + virtual void doCursorDelete(); + virtual void doCursorPrintf(char* format,...); + virtual int getLineCount(); + virtual int getVisibleLineCount(); + virtual void setCursorBlink(bool state); + virtual void setFont(Font* font); + virtual void getText(int lineIndex, int offset,char* buf,int bufLen); + +public: //bullshit public + void getCursorBlink(bool& blink,int& nextBlinkTime); +protected: + virtual void paintBackground(); + virtual void paint(); + virtual void addLine(); + virtual Dar* getLine(int lineIndex); + virtual void setChar(Dar* lineDar,int x,char ch,char fill); + virtual void setChar(Dar* lineDar,int x,char ch); + virtual void shiftLineLeft(Dar* lineDar,int x,int count); + virtual void shiftLineRight(Dar* lineDar,int x,int count); +private: + virtual int spatialCharOffsetBetweenTwoLines(Dar* srcDar,Dar* dstDar,int x); +protected: + Dar*> _lineDarDar; + int _cursor[2]; + bool _cursorBlink; + int _cursorNextBlinkTime; + Font* _font; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_EtchedBorder.h b/utils/vgui/include/VGUI_EtchedBorder.h new file mode 100644 index 0000000..7047c08 --- /dev/null +++ b/utils/vgui/include/VGUI_EtchedBorder.h @@ -0,0 +1,29 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_ETCHEDBORDER_H +#define VGUI_ETCHEDBORDER_H + +#include +#include + +namespace vgui +{ + +class Panel; + +class VGUIAPI EtchedBorder : public Border +{ +public: + EtchedBorder(); +protected: + virtual void paint(Panel* panel); +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_FileInputStream.h b/utils/vgui/include/VGUI_FileInputStream.h new file mode 100644 index 0000000..41b6086 --- /dev/null +++ b/utils/vgui/include/VGUI_FileInputStream.h @@ -0,0 +1,38 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_FILEINPUTSTREAM_H +#define VGUI_FILEINPUTSTREAM_H + +//TODO : figure out how to get stdio out of here, I think std namespace is broken for FILE for forward declaring does not work in vc6 + +#include +#include + +namespace vgui +{ + +class VGUIAPI FileInputStream : public InputStream +{ +private: + FILE* _fp; +public: + FileInputStream(const char* fileName,bool textMode); +public: + virtual void seekStart(bool& success); + virtual void seekRelative(int count,bool& success); + virtual void seekEnd(bool& success); + virtual int getAvailable(bool& success); + virtual uchar readUChar(bool& success); + virtual void readUChar(uchar* buf,int count,bool& success); + virtual void close(bool& success); + virtual void close(); +}; + +} + +#endif diff --git a/utils/vgui/include/VGUI_FlowLayout.h b/utils/vgui/include/VGUI_FlowLayout.h new file mode 100644 index 0000000..c958565 --- /dev/null +++ b/utils/vgui/include/VGUI_FlowLayout.h @@ -0,0 +1,29 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_FLOWLAYOUT_H +#define VGUI_FLOWLAYOUT_H + +#include +#include + +namespace vgui +{ + +class VGUIAPI FlowLayout : public Layout +{ +private: + int _hgap; +public: + FlowLayout(int hgap); +public: + virtual void performLayout(Panel* panel); +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_FocusChangeSignal.h b/utils/vgui/include/VGUI_FocusChangeSignal.h new file mode 100644 index 0000000..0681f8a --- /dev/null +++ b/utils/vgui/include/VGUI_FocusChangeSignal.h @@ -0,0 +1,26 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_FOCUSCHANGESIGNAL_H +#define VGUI_FOCUSCHANGESIGNAL_H + +#include + +namespace vgui +{ + +class Panel; + +class VGUIAPI FocusChangeSignal +{ +public: + virtual void focusChanged(bool lost,Panel* panel)=0; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_FocusNavGroup.h b/utils/vgui/include/VGUI_FocusNavGroup.h new file mode 100644 index 0000000..b7e8307 --- /dev/null +++ b/utils/vgui/include/VGUI_FocusNavGroup.h @@ -0,0 +1,35 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_FOCUSNAVGROUP_H +#define VGUI_FOCUSNAVGROUP_H + +#include +#include + +namespace vgui +{ + +class Panel; + +class VGUIAPI FocusNavGroup +{ +public: + FocusNavGroup(); +protected: + virtual void addPanel(Panel* panel); + virtual void requestFocusPrev(); + virtual void requestFocusNext(); + virtual void setCurrentPanel(Panel* panel); +protected: + Dar _panelDar; + int _currentIndex; +friend class Panel; +}; +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_Font.h b/utils/vgui/include/VGUI_Font.h new file mode 100644 index 0000000..822722c --- /dev/null +++ b/utils/vgui/include/VGUI_Font.h @@ -0,0 +1,48 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_FONT_H +#define VGUI_FONT_H + +#include + +namespace vgui +{ + +class BaseFontPlat; + +//TODO: cursors and fonts should work like gl binds +class VGUIAPI Font + { + public: + Font(const char* name,int tall,int wide,float rotation,int weight,bool italic,bool underline,bool strikeout,bool symbol); + // If pFileData is non-NULL, then it will try to load the 32-bit (RLE) TGA file. If that fails, + // it will create the font using the specified parameters. + // pUniqueName should be set if pFileData and fileDataLen are set so it can determine if a font is already loaded. + Font(const char* name,void *pFileData,int fileDataLen, int tall,int wide,float rotation,int weight,bool italic,bool underline,bool strikeout,bool symbol); + private: + virtual void init(const char* name,void *pFileData,int fileDataLen, int tall,int wide,float rotation,int weight,bool italic,bool underline,bool strikeout,bool symbol); + public: + BaseFontPlat* getPlat(); + virtual void getCharRGBA(int ch,int rgbaX,int rgbaY,int rgbaWide,int rgbaTall,uchar* rgba); + virtual void getCharABCwide(int ch,int& a,int& b,int& c); + virtual void getTextSize(const char* text,int& wide,int& tall); + virtual int getTall(); + virtual int getId(); + protected: + char* _name; + BaseFontPlat* _plat; + int _id; + friend class Surface; + }; + + +void Font_Reset(); + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_Frame.h b/utils/vgui/include/VGUI_Frame.h new file mode 100644 index 0000000..ffe41f8 --- /dev/null +++ b/utils/vgui/include/VGUI_Frame.h @@ -0,0 +1,73 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_FRAME_H +#define VGUI_FRAME_H + +#include +#include +#include + +namespace vgui +{ + +class Button; +class FrameSignal; + +class VGUIAPI Frame : public Panel + { + public: + Frame(int x,int y,int wide,int tall); + public: + virtual void setSize(int wide,int tall); + virtual void setInternal(bool state); + virtual void paintBackground(); + virtual bool isInternal(); + virtual Panel* getClient(); + virtual void setTitle(const char* title); + virtual void getTitle(char* buf,int bufLen); + virtual void setMoveable(bool state); + virtual void setSizeable(bool state); + virtual bool isMoveable(); + virtual bool isSizeable(); + virtual void addFrameSignal(FrameSignal* s); + virtual void setVisible(bool state); + virtual void setMenuButtonVisible(bool state); + virtual void setTrayButtonVisible(bool state); + virtual void setMinimizeButtonVisible(bool state); + virtual void setMaximizeButtonVisible(bool state); + virtual void setCloseButtonVisible(bool state); + public: //bullshit public + virtual void fireClosingSignal(); + virtual void fireMinimizingSignal(); + protected: + char* _title; + bool _internal; + bool _sizeable; + bool _moveable; + Panel* _topGrip; + Panel* _bottomGrip; + Panel* _leftGrip; + Panel* _rightGrip; + Panel* _topLeftGrip; + Panel* _topRightGrip; + Panel* _bottomLeftGrip; + Panel* _bottomRightGrip; + Panel* _captionGrip; + Panel* _client; + Button* _trayButton; + Button* _minimizeButton; + Button* _maximizeButton; + Button* _closeButton; + Button* _menuButton; + Dar _frameSignalDar; + Frame* _resizeable; + }; + +} + +#endif diff --git a/utils/vgui/include/VGUI_FrameSignal.h b/utils/vgui/include/VGUI_FrameSignal.h new file mode 100644 index 0000000..7deab55 --- /dev/null +++ b/utils/vgui/include/VGUI_FrameSignal.h @@ -0,0 +1,27 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_FRAMESIGNAL_H +#define VGUI_FRAMESIGNAL_H + +#include + +namespace vgui +{ + +class Frame; + +class VGUIAPI FrameSignal +{ +public: + virtual void closing(Frame* frame)=0; + virtual void minimizing(Frame* frame,bool toTray)=0; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_GridLayout.h b/utils/vgui/include/VGUI_GridLayout.h new file mode 100644 index 0000000..946159d --- /dev/null +++ b/utils/vgui/include/VGUI_GridLayout.h @@ -0,0 +1,30 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_GRIDLAYOUT_H +#define VGUI_GRIDLAYOUT_H + +#include +#include + +namespace vgui +{ + +/* +class VGUIAPI GridLayout : public Layout +{ +public: + GridLayout(int rows,int cols,int hgap,int vgap); +protected: + int _rows; + int _cols; +}; +*/ + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_HeaderPanel.h b/utils/vgui/include/VGUI_HeaderPanel.h new file mode 100644 index 0000000..163d88f --- /dev/null +++ b/utils/vgui/include/VGUI_HeaderPanel.h @@ -0,0 +1,65 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_HEADERPANEL_H +#define VGUI_HEADERPANEL_H + +#include +#include +#include +#include + +namespace vgui +{ + +enum MouseCode; +class ChangeSignal; + +class VGUIAPI HeaderPanel : public Panel +{ + +private: + + Dar _sliderPanelDar; + Dar _sectionPanelDar; + Dar _changeSignalDar; + Panel* _sectionLayer; + int _sliderWide; + bool _dragging; + int _dragSliderIndex; + int _dragSliderStartPos; + int _dragSliderStartX; + +public: + + HeaderPanel(int x,int y,int wide,int tall); + +protected: + + virtual void performLayout(); + +public: + + virtual void addSectionPanel(Panel* panel); + virtual void setSliderPos(int sliderIndex,int pos); + virtual int getSectionCount(); + virtual void getSectionExtents(int sectionIndex,int& x0,int& x1); + virtual void addChangeSignal(ChangeSignal* s); + +public: //bullshit public + + virtual void fireChangeSignal(); + virtual void privateCursorMoved(int x,int y,Panel* panel); + virtual void privateMousePressed(MouseCode code,Panel* panel); + virtual void privateMouseReleased(MouseCode code,Panel* panel); + +}; + +} + +#endif + diff --git a/utils/vgui/include/VGUI_Image.h b/utils/vgui/include/VGUI_Image.h new file mode 100644 index 0000000..8250a87 --- /dev/null +++ b/utils/vgui/include/VGUI_Image.h @@ -0,0 +1,62 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_IMAGE_H +#define VGUI_IMAGE_H + +#include +#include +#include + +//TODO:: needs concept of insets + +namespace vgui +{ + +class Panel; + +class VGUIAPI Image +{ +friend class Panel; +private: + int _pos[2]; + int _size[2]; + Panel* _panel; + Color _color; +public: + Image(); +public: + virtual void setPos(int x,int y); + virtual void getPos(int& x,int& y); + virtual void getSize(int& wide,int& tall); + virtual void setColor(Color color); + virtual void getColor(Color& color); +protected: + virtual void setSize(int wide,int tall); + virtual void drawSetColor(Scheme::SchemeColor sc); + virtual void drawSetColor(int r,int g,int b,int a); + virtual void drawFilledRect(int x0,int y0,int x1,int y1); + virtual void drawOutlinedRect(int x0,int y0,int x1,int y1); + virtual void drawSetTextFont(Scheme::SchemeFont sf); + virtual void drawSetTextFont(Font* font); + virtual void drawSetTextColor(Scheme::SchemeColor sc); + virtual void drawSetTextColor(int r,int g,int b,int a); + virtual void drawSetTextPos(int x,int y); + virtual void drawPrintText(const char* str,int strlen); + virtual void drawPrintText(int x,int y,const char* str,int strlen); + virtual void drawPrintChar(char ch); + virtual void drawPrintChar(int x,int y,char ch); + virtual void drawSetTextureRGBA(int id,const char* rgba,int wide,int tall); + virtual void drawSetTexture(int id); + virtual void drawTexturedRect(int x0,int y0,int x1,int y1); + virtual void paint(Panel* panel); +public: + virtual void doPaint(Panel* panel); +}; +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_ImagePanel.h b/utils/vgui/include/VGUI_ImagePanel.h new file mode 100644 index 0000000..cb492cd --- /dev/null +++ b/utils/vgui/include/VGUI_ImagePanel.h @@ -0,0 +1,38 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_IMAGEPANEL_H +#define VGUI_IMAGEPANEL_H + +#include +#include + +namespace vgui +{ + +class Image; + +class VGUIAPI ImagePanel : public Panel +{ +public: + inline ImagePanel() + { + _image=null; + } + + ImagePanel(Image* image); +public: + virtual void setImage(Image* image); +protected: + virtual void paintBackground(); +protected: + Image* _image; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_InputSignal.h b/utils/vgui/include/VGUI_InputSignal.h new file mode 100644 index 0000000..d5fc02a --- /dev/null +++ b/utils/vgui/include/VGUI_InputSignal.h @@ -0,0 +1,39 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_INPUTSIGNAL_H +#define VGUI_INPUTSIGNAL_H + +#include + +namespace vgui +{ + +enum MouseCode; +enum KeyCode; +class Panel; + +//these are lumped into one for simplicity sake right now +class VGUIAPI InputSignal +{ +public: + virtual void cursorMoved(int x,int y,Panel* panel)=0; + virtual void cursorEntered(Panel* panel)=0; + virtual void cursorExited(Panel* panel)=0; + virtual void mousePressed(MouseCode code,Panel* panel)=0; + virtual void mouseDoublePressed(MouseCode code,Panel* panel)=0; + virtual void mouseReleased(MouseCode code,Panel* panel)=0; + virtual void mouseWheeled(int delta,Panel* panel)=0; + virtual void keyPressed(KeyCode code,Panel* panel)=0; + virtual void keyTyped(KeyCode code,Panel* panel)=0; + virtual void keyReleased(KeyCode code,Panel* panel)=0; + virtual void keyFocusTicked(Panel* panel)=0; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_InputStream.h b/utils/vgui/include/VGUI_InputStream.h new file mode 100644 index 0000000..141e9e2 --- /dev/null +++ b/utils/vgui/include/VGUI_InputStream.h @@ -0,0 +1,30 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_FILE_H +#define VGUI_FILE_H + +#include + +namespace vgui +{ + +class VGUIAPI InputStream +{ +public: + virtual void seekStart(bool& success)=0; + virtual void seekRelative(int count,bool& success)=0; + virtual void seekEnd(bool& success)=0; + virtual int getAvailable(bool& success)=0; + virtual uchar readUChar(bool& success)=0; + virtual void readUChar(uchar* buf,int count,bool& success)=0; + virtual void close(bool& success)=0; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_IntChangeSignal.h b/utils/vgui/include/VGUI_IntChangeSignal.h new file mode 100644 index 0000000..9aa4d49 --- /dev/null +++ b/utils/vgui/include/VGUI_IntChangeSignal.h @@ -0,0 +1,26 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_INTCHANGESIGNAL_H +#define VGUI_INTCHANGESIGNAL_H + +#include + +namespace vgui +{ + +class Panel; + +class VGUIAPI IntChangeSignal +{ +public: + virtual void intChanged(int value,Panel* panel)=0; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_IntLabel.h b/utils/vgui/include/VGUI_IntLabel.h new file mode 100644 index 0000000..275b3ce --- /dev/null +++ b/utils/vgui/include/VGUI_IntLabel.h @@ -0,0 +1,35 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_INTLABEL_H +#define VGUI_INTLABEL_H + +#include +#include +#include + +namespace vgui +{ + +class Panel; + +class VGUIAPI IntLabel : public Label , public IntChangeSignal +{ +public: + IntLabel(int value,int x,int y,int wide,int tall); +public: + virtual void setValue(int value); + virtual void intChanged(int value,Panel* panel); +protected: + virtual void paintBackground(); +protected: + int _value; +}; + +} + +#endif diff --git a/utils/vgui/include/VGUI_KeyCode.h b/utils/vgui/include/VGUI_KeyCode.h new file mode 100644 index 0000000..f100cd4 --- /dev/null +++ b/utils/vgui/include/VGUI_KeyCode.h @@ -0,0 +1,126 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_KEYCODE_H +#define VGUI_KEYCODE_H + +#include + +namespace vgui +{ +enum VGUIAPI KeyCode +{ + KEY_0=0, + KEY_1, + KEY_2, + KEY_3, + KEY_4, + KEY_5, + KEY_6, + KEY_7, + KEY_8, + KEY_9, + KEY_A, + KEY_B, + KEY_C, + KEY_D, + KEY_E, + KEY_F, + KEY_G, + KEY_H, + KEY_I, + KEY_J, + KEY_K, + KEY_L, + KEY_M, + KEY_N, + KEY_O, + KEY_P, + KEY_Q, + KEY_R, + KEY_S, + KEY_T, + KEY_U, + KEY_V, + KEY_W, + KEY_X, + KEY_Y, + KEY_Z, + KEY_PAD_0, + KEY_PAD_1, + KEY_PAD_2, + KEY_PAD_3, + KEY_PAD_4, + KEY_PAD_5, + KEY_PAD_6, + KEY_PAD_7, + KEY_PAD_8, + KEY_PAD_9, + KEY_PAD_DIVIDE, + KEY_PAD_MULTIPLY, + KEY_PAD_MINUS, + KEY_PAD_PLUS, + KEY_PAD_ENTER, + KEY_PAD_DECIMAL, + KEY_LBRACKET, + KEY_RBRACKET, + KEY_SEMICOLON, + KEY_APOSTROPHE, + KEY_BACKQUOTE, + KEY_COMMA, + KEY_PERIOD, + KEY_SLASH, + KEY_BACKSLASH, + KEY_MINUS, + KEY_EQUAL, + KEY_ENTER, + KEY_SPACE, + KEY_BACKSPACE, + KEY_TAB, + KEY_CAPSLOCK, + KEY_NUMLOCK, + KEY_ESCAPE, + KEY_SCROLLLOCK, + KEY_INSERT, + KEY_DELETE, + KEY_HOME, + KEY_END, + KEY_PAGEUP, + KEY_PAGEDOWN, + KEY_BREAK, + KEY_LSHIFT, + KEY_RSHIFT, + KEY_LALT, + KEY_RALT, + KEY_LCONTROL, + KEY_RCONTROL, + KEY_LWIN, + KEY_RWIN, + KEY_APP, + KEY_UP, + KEY_LEFT, + KEY_DOWN, + KEY_RIGHT, + KEY_F1, + KEY_F2, + KEY_F3, + KEY_F4, + KEY_F5, + KEY_F6, + KEY_F7, + KEY_F8, + KEY_F9, + KEY_F10, + KEY_F11, + KEY_F12, + KEY_LAST, +}; +} + + +#endif + diff --git a/utils/vgui/include/VGUI_Label.h b/utils/vgui/include/VGUI_Label.h new file mode 100644 index 0000000..af90e9f --- /dev/null +++ b/utils/vgui/include/VGUI_Label.h @@ -0,0 +1,80 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_LABEL_H +#define VGUI_LABEL_H + +#include +#include +#include + +//TODO: this should use a TextImage for the text + +namespace vgui +{ + +class Panel; +class TextImage; + +class VGUIAPI Label : public Panel +{ +public: + enum Alignment + { + a_northwest=0, + a_north, + a_northeast, + a_west, + a_center, + a_east, + a_southwest, + a_south, + a_southeast, + }; +public: + Label(int textBufferLen,const char* text,int x,int y,int wide,int tall); + Label(const char* text,int x,int y,int wide,int tall); + Label(const char* text,int x,int y); + Label(const char* text); + + inline Label() : Panel(0,0,10,10) + { + init(1,"",true); + } +private: + void init(int textBufferLen,const char* text,bool textFitted); +public: + virtual void setImage(Image* image); + virtual void setText(int textBufferLen,const char* text); + virtual void setText(const char* format,...); + virtual void setFont(Scheme::SchemeFont schemeFont); + virtual void setFont(Font* font); + virtual void getTextSize(int& wide,int& tall); + virtual void getContentSize(int& wide,int& tall); + virtual void setTextAlignment(Alignment alignment); + virtual void setContentAlignment(Alignment alignment); + virtual Panel* createPropertyPanel(); + virtual void setFgColor(int r,int g,int b,int a); + virtual void setFgColor(vgui::Scheme::SchemeColor sc); + virtual void setContentFitted(bool state); +protected: + virtual void computeAlignment(int& tx0,int& ty0,int& tx1,int& ty1,int& ix0,int& iy0,int& ix1,int& iy1,int& minX,int& minY,int& maxX,int& maxY); + virtual void paint(); + virtual void recomputeMinimumSize(); +protected: + bool _textEnabled; + bool _imageEnabled; + bool _contentFitted; + Alignment _textAlignment; + Alignment _contentAlignment; + TextImage* _textImage; + Image* _image; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_Layout.h b/utils/vgui/include/VGUI_Layout.h new file mode 100644 index 0000000..081c2ec --- /dev/null +++ b/utils/vgui/include/VGUI_Layout.h @@ -0,0 +1,31 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_LAYOUT_H +#define VGUI_LAYOUT_H + +#include + +namespace vgui +{ + +class Panel; + +class VGUIAPI Layout +{ +//private: +// Panel* _panel; +public: + Layout(); +public: + //virtual void setPanel(Panel* panel); //called by Panel::setLayout + virtual void performLayout(Panel* panel); +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_LayoutInfo.h b/utils/vgui/include/VGUI_LayoutInfo.h new file mode 100644 index 0000000..569be76 --- /dev/null +++ b/utils/vgui/include/VGUI_LayoutInfo.h @@ -0,0 +1,21 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_LAYOUTINFO_H +#define VGUI_LAYOUTINFO_H + +namespace vgui +{ + +class VGUIAPI LayoutInfo +{ + virtual LayoutInfo* getThis()=0; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_LineBorder.h b/utils/vgui/include/VGUI_LineBorder.h new file mode 100644 index 0000000..1263a46 --- /dev/null +++ b/utils/vgui/include/VGUI_LineBorder.h @@ -0,0 +1,39 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_LINEBORDER_H +#define VGUI_LINEBORDER_H + +#include +#include +#include + +namespace vgui +{ + +class Panel; + +class VGUIAPI LineBorder : public Border +{ +private: + Color _color; +public: + LineBorder(); + LineBorder(int thickness); + LineBorder(Color color); + LineBorder(int thickness,Color color); + + inline void setLineColor(int r, int g, int b, int a) {_color = Color(r,g,b,a);} +private: + virtual void init(int thickness,Color color); +protected: + virtual void paint(Panel* panel); +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_ListPanel.h b/utils/vgui/include/VGUI_ListPanel.h new file mode 100644 index 0000000..542f5bd --- /dev/null +++ b/utils/vgui/include/VGUI_ListPanel.h @@ -0,0 +1,40 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_LISTPANEL_H +#define VGUI_LISTPANEL_H + +#include +#include + +namespace vgui +{ + +class ScrollBar; + +//TODO: make a ScrollPanel and use a constrained one for _vpanel in ListPanel +class VGUIAPI ListPanel : public Panel +{ +public: + ListPanel(int x,int y,int wide,int tall); +public: + virtual void setSize(int wide,int tall); + virtual void addString(const char* str); + virtual void addItem(Panel* panel); + virtual void setPixelScroll(int value); + virtual void translatePixelScroll(int delta); +protected: + virtual void performLayout(); + virtual void paintBackground(); +protected: + Panel* _vpanel; + ScrollBar* _scroll; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_LoweredBorder.h b/utils/vgui/include/VGUI_LoweredBorder.h new file mode 100644 index 0000000..85ed51c --- /dev/null +++ b/utils/vgui/include/VGUI_LoweredBorder.h @@ -0,0 +1,29 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_LOWEREDBORDER_H +#define VGUI_LOWEREDBORDER_H + +#include +#include + +namespace vgui +{ + +class Panel; + +class VGUIAPI LoweredBorder : public Border +{ +public: + LoweredBorder(); +protected: + virtual void paint(Panel* panel); +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_Menu.h b/utils/vgui/include/VGUI_Menu.h new file mode 100644 index 0000000..035c148 --- /dev/null +++ b/utils/vgui/include/VGUI_Menu.h @@ -0,0 +1,30 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_MENU_H +#define VGUI_MENU_H + +#include +#include + +namespace vgui +{ + +class Panel; + +class VGUIAPI Menu : public Panel +{ +public: + Menu(int x,int y,int wide,int tall); + Menu(int wide,int tall); +public: + virtual void addMenuItem(Panel* panel); +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_MenuItem.h b/utils/vgui/include/VGUI_MenuItem.h new file mode 100644 index 0000000..33cfa27 --- /dev/null +++ b/utils/vgui/include/VGUI_MenuItem.h @@ -0,0 +1,30 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_MENUITEM_H +#define VGUI_MENUITEM_H + +#include +#include + +namespace vgui +{ + +class Menu; + +class VGUIAPI MenuItem : public Button +{ +public: + MenuItem(const char* text); + MenuItem(const char* text,Menu* subMenu); +protected: + Menu* _subMenu; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_MenuSeparator.h b/utils/vgui/include/VGUI_MenuSeparator.h new file mode 100644 index 0000000..f183a97 --- /dev/null +++ b/utils/vgui/include/VGUI_MenuSeparator.h @@ -0,0 +1,27 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_MENUSEPARATOR_H +#define VGUI_MENUSEPARATOR_H + +#include +#include + +namespace vgui +{ + +class VGUIAPI MenuSeparator : public Label +{ +public: + MenuSeparator(const char* text); +protected: + virtual void paintBackground(); +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_MessageBox.h b/utils/vgui/include/VGUI_MessageBox.h new file mode 100644 index 0000000..38a6e3c --- /dev/null +++ b/utils/vgui/include/VGUI_MessageBox.h @@ -0,0 +1,53 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_MESSAGEBOX_H +#define VGUI_MESSAGEBOX_H + +#include +#include +#include + + +namespace vgui +{ + +class Label; +class Button; +class ActionSignal; + +class VGUIAPI MessageBox : public Frame +{ + +private: + + Label* _messageLabel; + Button* _okButton; + Dar _actionSignalDar; + +public: + + MessageBox(const char* title,const char* text,int x,int y); + +protected: + + virtual void performLayout(); + +public: + + virtual void addActionSignal(ActionSignal* s); + virtual void fireActionSignal(); + +}; + +} + + + + + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_MiniApp.h b/utils/vgui/include/VGUI_MiniApp.h new file mode 100644 index 0000000..b7ce668 --- /dev/null +++ b/utils/vgui/include/VGUI_MiniApp.h @@ -0,0 +1,33 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_MINIAPP_H +#define VGUI_MINIAPP_H + +#include + +namespace vgui +{ + +class Frame; + +class VGUIAPI MiniApp +{ +public: + MiniApp(); +public: + virtual void getName(char* buf,int bufLen); + virtual Frame* createInstance()=0; +protected: + virtual void setName(const char* name); +protected: + char* _name; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_MouseCode.h b/utils/vgui/include/VGUI_MouseCode.h new file mode 100644 index 0000000..8adf864 --- /dev/null +++ b/utils/vgui/include/VGUI_MouseCode.h @@ -0,0 +1,24 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_MOUSECODE_H +#define VGUI_MOUSECODE_H + +#include + +namespace vgui +{ +enum VGUIAPI MouseCode +{ + MOUSE_LEFT=0, + MOUSE_RIGHT, + MOUSE_MIDDLE, + MOUSE_LAST, +}; +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_Panel.h b/utils/vgui/include/VGUI_Panel.h new file mode 100644 index 0000000..d5cbd7b --- /dev/null +++ b/utils/vgui/include/VGUI_Panel.h @@ -0,0 +1,224 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_PANEL_H +#define VGUI_PANEL_H + + +/* + +TODO: + +Maybe have the border know who they are added to. +A border can only be added to 1 thing, and will be +removed from the other. That way they can actually +be memory managed. Also do Layout's this way too. + +TODO: + outlinedRect should have a thickness arg + +*/ + + +#include +#include +#include +#include + +namespace vgui +{ + +enum KeyCode; +enum MouseCode; +class SurfaceBase; +class FocusChangeSignal; +class InputSignal; +class Cursor; +class Layout; +class FocusNavGroup; +class Border; +class Font; +class BuildGroup; +class App; +class LayoutInfo; + +class VGUIAPI Panel +{ +public: + Panel(); + Panel(int x,int y,int wide,int tall); +private: + void init(int x,int y,int wide,int tall); +public: + virtual void setPos(int x,int y); + virtual void getPos(int& x,int& y); + virtual void setSize(int wide,int tall); + virtual void getSize(int& wide,int& tall); + virtual void setBounds(int x,int y,int wide,int tall); + virtual void getBounds(int& x,int& y,int& wide,int& tall); + virtual int getWide(); + virtual int getTall(); + virtual Panel* getParent(); + virtual void setVisible(bool state); + virtual bool isVisible(); + virtual bool isVisibleUp(); + virtual void repaint(); + virtual void repaintAll(); + virtual void getAbsExtents(int& x0,int& y0,int& x1,int& y1); + virtual void getClipRect(int& x0,int& y0,int& x1,int& y1); + virtual void setParent(Panel* newParent); + virtual void addChild(Panel* child); + virtual void insertChildAt(Panel* child,int index); + virtual void removeChild(Panel* child); + virtual bool wasMousePressed(MouseCode code); + virtual bool wasMouseDoublePressed(MouseCode code); + virtual bool isMouseDown(MouseCode code); + virtual bool wasMouseReleased(MouseCode code); + virtual bool wasKeyPressed(KeyCode code); + virtual bool isKeyDown(KeyCode code); + virtual bool wasKeyTyped(KeyCode code); + virtual bool wasKeyReleased(KeyCode code); + virtual void addInputSignal(InputSignal* s); + virtual void removeInputSignal(InputSignal* s); + virtual void addRepaintSignal(RepaintSignal* s); + virtual void removeRepaintSignal(RepaintSignal* s); + virtual bool isWithin(int x,int y); //in screen space + virtual Panel* isWithinTraverse(int x,int y); + virtual void localToScreen(int& x,int& y); + virtual void screenToLocal(int& x,int& y); + virtual void setCursor(Cursor* cursor); + virtual void setCursor(Scheme::SchemeCursor scu); + virtual Cursor* getCursor(); + virtual void setMinimumSize(int wide,int tall); + virtual void getMinimumSize(int& wide,int& tall); + virtual void requestFocus(); + virtual bool hasFocus(); + virtual int getChildCount(); + virtual Panel* getChild(int index); + virtual void setLayout(Layout* layout); + virtual void invalidateLayout(bool layoutNow); + virtual void setFocusNavGroup(FocusNavGroup* focusNavGroup); + virtual void requestFocusPrev(); + virtual void requestFocusNext(); + virtual void addFocusChangeSignal(FocusChangeSignal* s); + virtual bool isAutoFocusNavEnabled(); + virtual void setAutoFocusNavEnabled(bool state); + virtual void setBorder(Border* border); + virtual void setPaintBorderEnabled(bool state); + virtual void setPaintBackgroundEnabled(bool state); + virtual void setPaintEnabled(bool state); + virtual void getInset(int& left,int& top,int& right,int& bottom); + virtual void getPaintSize(int& wide,int& tall); + virtual void setPreferredSize(int wide,int tall); + virtual void getPreferredSize(int& wide,int& tall); + virtual SurfaceBase* getSurfaceBase(); + virtual bool isEnabled(); + virtual void setEnabled(bool state); + virtual void setBuildGroup(BuildGroup* buildGroup,const char* panelPersistanceName); + virtual bool isBuildGroupEnabled(); + virtual void removeAllChildren(); + virtual void repaintParent(); + virtual Panel* createPropertyPanel(); + virtual void getPersistanceText(char* buf,int bufLen); + virtual void applyPersistanceText(const char* buf); + virtual void setFgColor(Scheme::SchemeColor sc); + virtual void setBgColor(Scheme::SchemeColor sc); + virtual void setFgColor(int r,int g,int b,int a); + virtual void setBgColor(int r,int g,int b,int a); + virtual void getFgColor(int& r,int& g,int& b,int& a); + virtual void getBgColor(int& r,int& g,int& b,int& a); + virtual void setBgColor(Color color); + virtual void setFgColor(Color color); + virtual void getBgColor(Color& color); + virtual void getFgColor(Color& color); + virtual void setAsMouseCapture(bool state); + virtual void setAsMouseArena(bool state); + virtual App* getApp(); + virtual void getVirtualSize(int& wide,int& tall); + virtual void setLayoutInfo(LayoutInfo* layoutInfo); + virtual LayoutInfo* getLayoutInfo(); + virtual bool isCursorNone(); +public: //bullshit public + virtual void solveTraverse(); + virtual void paintTraverse(); + virtual void setSurfaceBaseTraverse(SurfaceBase* surfaceBase); +protected: + virtual void performLayout(); + virtual void internalPerformLayout(); + virtual void drawSetColor(Scheme::SchemeColor sc); + virtual void drawSetColor(int r,int g,int b,int a); + virtual void drawFilledRect(int x0,int y0,int x1,int y1); + virtual void drawOutlinedRect(int x0,int y0,int x1,int y1); + virtual void drawSetTextFont(Scheme::SchemeFont sf); + virtual void drawSetTextFont(Font* font); + virtual void drawSetTextColor(Scheme::SchemeColor sc); + virtual void drawSetTextColor(int r,int g,int b,int a); + virtual void drawSetTextPos(int x,int y); + virtual void drawPrintText(const char* str,int strlen); + virtual void drawPrintText(int x,int y,const char* str,int strlen); + virtual void drawPrintChar(char ch); + virtual void drawPrintChar(int x,int y,char ch); + virtual void drawSetTextureRGBA(int id,const char* rgba,int wide,int tall); + virtual void drawSetTexture(int id); + virtual void drawTexturedRect(int x0,int y0,int x1,int y1); + virtual void solve(); + virtual void paintTraverse(bool repaint); + virtual void paintBackground(); + virtual void paint(); + virtual void paintBuildOverlay(); + virtual void internalCursorMoved(int x,int y); + virtual void internalCursorEntered(); + virtual void internalCursorExited(); + virtual void internalMousePressed(MouseCode code); + virtual void internalMouseDoublePressed(MouseCode code); + virtual void internalMouseReleased(MouseCode code); + virtual void internalMouseWheeled(int delta); + virtual void internalKeyPressed(KeyCode code); + virtual void internalKeyTyped(KeyCode code); + virtual void internalKeyReleased(KeyCode code); + virtual void internalKeyFocusTicked(); + virtual void internalFocusChanged(bool lost); + virtual void internalSetCursor(); +protected: + int _pos[2]; + int _size[2]; + int _loc[2]; + int _minimumSize[2]; + int _preferredSize[2]; + Dar _childDar; + Panel* _parent; + SurfaceBase* _surfaceBase; + Dar _inputSignalDar; + Dar _repaintSignalDar; + int _clipRect[4]; + Cursor* _cursor; + Scheme::SchemeCursor _schemeCursor; + bool _visible; + Layout* _layout; + bool _needsLayout; + FocusNavGroup* _focusNavGroup; + Dar _focusChangeSignalDar; + bool _autoFocusNavEnabled; + Border* _border; +private: + bool _needsRepaint; + bool _enabled; + BuildGroup* _buildGroup; + Color _fgColor; + Color _bgColor; + LayoutInfo* _layoutInfo; + bool _paintBorderEnabled; + bool _paintBackgroundEnabled; + bool _paintEnabled; +friend class Panel; +friend class App; +friend class SurfaceBase; +friend class Image; +}; +} + +#endif diff --git a/utils/vgui/include/VGUI_PopupMenu.h b/utils/vgui/include/VGUI_PopupMenu.h new file mode 100644 index 0000000..26f6bbc --- /dev/null +++ b/utils/vgui/include/VGUI_PopupMenu.h @@ -0,0 +1,30 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_POPUPMENU_H +#define VGUI_POPUPMENU_H + +#include +#include + +namespace vgui +{ + +class Panel; + +class VGUIAPI PopupMenu : public Menu +{ +public: + PopupMenu(int x,int y,int wide,int tall); + PopupMenu(int wide,int tall); +public: + virtual void showModal(Panel* panel); +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_ProgressBar.h b/utils/vgui/include/VGUI_ProgressBar.h new file mode 100644 index 0000000..9c3ca11 --- /dev/null +++ b/utils/vgui/include/VGUI_ProgressBar.h @@ -0,0 +1,33 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_PROGRESSBAR_H +#define VGUI_PROGRESSBAR_H + +#include +#include + +namespace vgui +{ + +class VGUIAPI ProgressBar : public Panel +{ +private: + int _segmentCount; + float _progress; +public: + ProgressBar(int segmentCount); +protected: + virtual void paintBackground(); +public: + virtual void setProgress(float progress); + virtual int getSegmentCount(); +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_RadioButton.h b/utils/vgui/include/VGUI_RadioButton.h new file mode 100644 index 0000000..89add27 --- /dev/null +++ b/utils/vgui/include/VGUI_RadioButton.h @@ -0,0 +1,29 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_RADIOBUTTON_H +#define VGUI_RADIOBUTTON_H + +#include +#include + +namespace vgui +{ + +class VGUIAPI RadioButton : public ToggleButton +{ +public: + RadioButton(const char* text,int x,int y,int wide,int tall); + RadioButton(const char* text,int x,int y); +protected: + virtual void paintBackground(); +}; + +} + +#endif + diff --git a/utils/vgui/include/VGUI_RaisedBorder.h b/utils/vgui/include/VGUI_RaisedBorder.h new file mode 100644 index 0000000..b5a3588 --- /dev/null +++ b/utils/vgui/include/VGUI_RaisedBorder.h @@ -0,0 +1,29 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_RAISEDBORDER_H +#define VGUI_RAISEDBORDER_H + +#include +#include + +namespace vgui +{ + +class Panel; + +class VGUIAPI RaisedBorder : public Border +{ +public: + RaisedBorder(); +protected: + virtual void paint(Panel* panel); +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_RepaintSignal.h b/utils/vgui/include/VGUI_RepaintSignal.h new file mode 100644 index 0000000..e728a4b --- /dev/null +++ b/utils/vgui/include/VGUI_RepaintSignal.h @@ -0,0 +1,25 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_REPAINTSIGNAL_H +#define VGUI_REPAINTSIGNAL_H + + + +namespace vgui +{ + +class RepaintSignal +{ +public: + virtual void panelRepainted(Panel* panel)=0; +}; + +} + + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_Scheme.h b/utils/vgui/include/VGUI_Scheme.h new file mode 100644 index 0000000..82f8500 --- /dev/null +++ b/utils/vgui/include/VGUI_Scheme.h @@ -0,0 +1,82 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_SCHEME_H +#define VGUI_SCHEME_H + +#include + + +namespace vgui +{ + +class Font; +class Cursor; + +class VGUIAPI Scheme +{ +public: + enum SchemeColor + { + sc_user=0, + sc_black, + sc_white, + sc_primary1, + sc_primary2, + sc_primary3, + sc_secondary1, + sc_secondary2, + sc_secondary3, + sc_last, + }; + enum SchemeFont + { + sf_user=0, + sf_primary1, + sf_primary2, + sf_primary3, + sf_secondary1, + sf_last, + }; + enum SchemeCursor + { + scu_user=0, + scu_none, + scu_arrow, + scu_ibeam, + scu_hourglass, + scu_crosshair, + scu_up, + scu_sizenwse, + scu_sizenesw, + scu_sizewe, + scu_sizens, + scu_sizeall, + scu_no, + scu_hand, + scu_last, + }; +public: + Scheme(); +public: + virtual void setColor(SchemeColor sc,int r,int g,int b,int a); + virtual void getColor(SchemeColor sc,int& r,int& g,int& b,int& a); + virtual void setFont(SchemeFont sf,Font* font); + virtual Font* getFont(SchemeFont sf); + virtual void setCursor(SchemeCursor sc,Cursor* cursor); + virtual Cursor* getCursor(SchemeCursor sc); +protected: + int _color[sc_last][4]; + Font* _font[sf_last]; + Cursor* _cursor[scu_last]; + friend class Panel; + friend class Canvas; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_ScrollBar.h b/utils/vgui/include/VGUI_ScrollBar.h new file mode 100644 index 0000000..ea280ea --- /dev/null +++ b/utils/vgui/include/VGUI_ScrollBar.h @@ -0,0 +1,56 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_SCROLLBAR_H +#define VGUI_SCROLLBAR_H + +#include +#include +#include + +namespace vgui +{ + +class IntChangeSignal; +class Button; +class Slider; + +class VGUIAPI ScrollBar : public Panel +{ +public: + ScrollBar(int x,int y,int wide,int tall,bool vertical); +public: + virtual void setValue(int value); + virtual int getValue(); + virtual void addIntChangeSignal(IntChangeSignal* s); + virtual void setRange(int min,int max); + virtual void setRangeWindow(int rangeWindow); + virtual void setRangeWindowEnabled(bool state); + virtual void setSize(int wide,int tall); + virtual bool isVertical(); + virtual bool hasFullRange(); + virtual void setButton(Button* button,int index); + virtual Button* getButton(int index); + virtual void setSlider(Slider* slider); + virtual Slider* getSlider(); + virtual void doButtonPressed(int buttonIndex); + virtual void setButtonPressedScrollValue(int value); + virtual void validate(); +public: //bullshit public + virtual void fireIntChangeSignal(); +protected: + virtual void performLayout(); +protected: + Button* _button[2]; + Slider* _slider; + Dar _intChangeSignalDar; + int _buttonPressedScrollValue; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_ScrollPanel.h b/utils/vgui/include/VGUI_ScrollPanel.h new file mode 100644 index 0000000..88bcf2f --- /dev/null +++ b/utils/vgui/include/VGUI_ScrollPanel.h @@ -0,0 +1,55 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_SCROLLPANEL_H +#define VGUI_SCROLLPANEL_H + +#include +#include + +//NOTE: You need to call validate anytime you change a scrollbar + +namespace vgui +{ + +class ScrollBar; + +class VGUIAPI ScrollPanel : public Panel +{ +private: + Panel* _clientClip; + Panel* _client; + ScrollBar* _horizontalScrollBar; + ScrollBar* _verticalScrollBar; + bool _autoVisible[2]; +public: + ScrollPanel(int x,int y,int wide,int tall); +protected: + virtual void setSize(int wide,int tall); +public: + virtual void setScrollBarVisible(bool horizontal,bool vertical); + virtual void setScrollBarAutoVisible(bool horizontal,bool vertical); + virtual Panel* getClient(); + virtual Panel* getClientClip(); + virtual void setScrollValue(int horizontal,int vertical); + virtual void getScrollValue(int& horizontal,int& vertical); + virtual void recomputeClientSize(); + virtual ScrollBar* getHorizontalScrollBar(); + virtual ScrollBar* getVerticalScrollBar(); + virtual void validate(); +public: //bullshit public + virtual void recomputeScroll(); +}; + +} + + + + + + +#endif diff --git a/utils/vgui/include/VGUI_Slider.h b/utils/vgui/include/VGUI_Slider.h new file mode 100644 index 0000000..c4c6924 --- /dev/null +++ b/utils/vgui/include/VGUI_Slider.h @@ -0,0 +1,69 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_SLIDER_H +#define VGUI_SLIDER_H + +#include +#include +#include + +namespace vgui +{ + +enum MouseCode; +class IntChangeSignal; + +class VGUIAPI Slider : public Panel +{ +private: + bool _vertical; + bool _dragging; + int _nobPos[2]; + int _nobDragStartPos[2]; + int _dragStartPos[2]; + Dar _intChangeSignalDar; + int _range[2]; + int _value; + int _rangeWindow; + bool _rangeWindowEnabled; + int _buttonOffset; +public: + Slider(int x,int y,int wide,int tall,bool vertical); +public: + virtual void setValue(int value); + virtual int getValue(); + virtual bool isVertical(); + virtual void addIntChangeSignal(IntChangeSignal* s); + virtual void setRange(int min,int max); + virtual void getRange(int& min,int& max); + virtual void setRangeWindow(int rangeWindow); + virtual void setRangeWindowEnabled(bool state); + virtual void setSize(int wide,int tall); + virtual void getNobPos(int& min, int& max); + virtual bool hasFullRange(); + virtual void setButtonOffset(int buttonOffset); +private: + virtual void recomputeNobPosFromValue(); + virtual void recomputeValueFromNobPos(); +public: //bullshit public + virtual void privateCursorMoved(int x,int y,Panel* panel); + virtual void privateMousePressed(MouseCode code,Panel* panel); + virtual void privateMouseReleased(MouseCode code,Panel* panel); +protected: + virtual void fireIntChangeSignal(); + virtual void paintBackground(); +}; + +} + +#endif + + + + + diff --git a/utils/vgui/include/VGUI_StackLayout.h b/utils/vgui/include/VGUI_StackLayout.h new file mode 100644 index 0000000..9da6652 --- /dev/null +++ b/utils/vgui/include/VGUI_StackLayout.h @@ -0,0 +1,30 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_STACKLAYOUT_H +#define VGUI_STACKLAYOUT_H + +#include +#include + +namespace vgui +{ + +class VGUIAPI StackLayout : public Layout +{ +private: + int _vgap; + bool _fitWide; +public: + StackLayout(int vgap,bool fitWide); +public: + virtual void performLayout(Panel* panel); +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_String.h b/utils/vgui/include/VGUI_String.h new file mode 100644 index 0000000..bfe9fde --- /dev/null +++ b/utils/vgui/include/VGUI_String.h @@ -0,0 +1,61 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_STRING_H +#define VGUI_STRING_H + +#include + + +namespace vgui +{ + +class VGUIAPI String +{ + +friend class String; + +private: + + char* _text; + +public: + + String(); + String(const char* text); + String(const String& src); + +public: + + ~String(); + +private: + + int getCount(const char* text); + +public: + + int getCount(); + String operator+(String text); + String operator+(const char* text); + bool operator==(String text); + bool operator==(const char* text); + char operator[](int index); + const char* getChars(); + +public: + + static void test(); + +}; + + +} + + +#endif + diff --git a/utils/vgui/include/VGUI_Surface.h b/utils/vgui/include/VGUI_Surface.h new file mode 100644 index 0000000..7b5476a --- /dev/null +++ b/utils/vgui/include/VGUI_Surface.h @@ -0,0 +1,67 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_SURFACE_H +#define VGUI_SURFACE_H + +#include +#include +#include + +namespace vgui +{ + +class Panel; +class Cursor; + +class VGUIAPI Surface : public SurfaceBase +{ +public: + Surface(Panel* embeddedPanel); +public: + virtual void setTitle(const char* title); + virtual bool setFullscreenMode(int wide,int tall,int bpp); + virtual void setWindowedMode(); + virtual void setAsTopMost(bool state); + virtual int getModeInfoCount(); + virtual void createPopup(Panel* embeddedPanel); + virtual bool hasFocus(); + virtual bool isWithin(int x,int y); +protected: + virtual int createNewTextureID(void); + virtual void drawSetColor(int r,int g,int b,int a); + virtual void drawFilledRect(int x0,int y0,int x1,int y1); + virtual void drawOutlinedRect(int x0,int y0,int x1,int y1); + virtual void drawSetTextFont(Font* font); + virtual void drawSetTextColor(int r,int g,int b,int a); + virtual void drawSetTextPos(int x,int y); + virtual void drawPrintText(const char* text,int textLen); + virtual void drawSetTextureRGBA(int id,const char* rgba,int wide,int tall); + virtual void drawSetTexture(int id); + virtual void drawTexturedRect(int x0,int y0,int x1,int y1); + virtual void invalidate(Panel *panel); + virtual bool createPlat(); + virtual bool recreateContext(); + virtual void enableMouseCapture(bool state); + virtual void setCursor(Cursor* cursor); + virtual void swapBuffers(); + virtual void pushMakeCurrent(Panel* panel,bool useInsets); + virtual void popMakeCurrent(Panel* panel); + virtual void applyChanges(); +protected: + class SurfacePlat* _plat; + bool _needsSwap; + Panel* _embeddedPanel; + Dar _modeInfoDar; + friend class App; + friend class Panel; +}; + +} + +#endif + diff --git a/utils/vgui/include/VGUI_SurfaceBase.h b/utils/vgui/include/VGUI_SurfaceBase.h new file mode 100644 index 0000000..f654f9a --- /dev/null +++ b/utils/vgui/include/VGUI_SurfaceBase.h @@ -0,0 +1,81 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_SURFACEBASE_H +#define VGUI_SURFACEBASE_H + +#include +#include + +namespace vgui +{ + +class Panel; +class Cursor; +class Font; +class App; +class ImagePanel; + +class VGUIAPI SurfaceBase +{ +public: + SurfaceBase(Panel* embeddedPanel); +protected: + ~SurfaceBase(); +public: + virtual Panel* getPanel(); + virtual void requestSwap(); + virtual void resetModeInfo(); + virtual int getModeInfoCount(); + virtual bool getModeInfo(int mode,int& wide,int& tall,int& bpp); + virtual App* getApp(); + virtual void setEmulatedCursorVisible(bool state); + virtual void setEmulatedCursorPos(int x,int y); +public: + virtual void setTitle(const char* title)=0; + virtual bool setFullscreenMode(int wide,int tall,int bpp)=0; + virtual void setWindowedMode()=0; + virtual void setAsTopMost(bool state)=0; + virtual void createPopup(Panel* embeddedPanel)=0; + virtual bool hasFocus()=0; + virtual bool isWithin(int x,int y)=0; + virtual int createNewTextureID(void)=0; +protected: + virtual void addModeInfo(int wide,int tall,int bpp); +protected: + virtual void drawSetColor(int r,int g,int b,int a)=0; + virtual void drawFilledRect(int x0,int y0,int x1,int y1)=0; + virtual void drawOutlinedRect(int x0,int y0,int x1,int y1)=0; + virtual void drawSetTextFont(Font* font)=0; + virtual void drawSetTextColor(int r,int g,int b,int a)=0; + virtual void drawSetTextPos(int x,int y)=0; + virtual void drawPrintText(const char* text,int textLen)=0; + virtual void drawSetTextureRGBA(int id,const char* rgba,int wide,int tall)=0; + virtual void drawSetTexture(int id)=0; + virtual void drawTexturedRect(int x0,int y0,int x1,int y1)=0; + virtual void invalidate(Panel *panel)=0; + virtual void enableMouseCapture(bool state)=0; + virtual void setCursor(Cursor* cursor)=0; + virtual void swapBuffers()=0; + virtual void pushMakeCurrent(Panel* panel,bool useInsets)=0; + virtual void popMakeCurrent(Panel* panel)=0; + virtual void applyChanges()=0; +protected: + bool _needsSwap; + App* _app; + Panel* _embeddedPanel; + Dar _modeInfoDar; + ImagePanel* _emulatedCursor; + Cursor* _currentCursor; +friend class App; +friend class Panel; +}; + +} + +#endif + diff --git a/utils/vgui/include/VGUI_SurfaceGL.h b/utils/vgui/include/VGUI_SurfaceGL.h new file mode 100644 index 0000000..b509a12 --- /dev/null +++ b/utils/vgui/include/VGUI_SurfaceGL.h @@ -0,0 +1,98 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_SURFACEGL_H +#define VGUI_SURFACEGL_H + +//macros borrowed from GLUT to get rid of win32 dependent junk in gl headers +#ifdef _WIN32 +# ifndef APIENTRY +# define VGUI_APIENTRY_DEFINED +# if (_MSC_VER >= 800) || defined(_STDCALL_SUPPORTED) +# define APIENTRY __stdcall +# else +# define APIENTRY +# endif +# endif +# ifndef CALLBACK +# define VGUI_CALLBACK_DEFINED +# if (defined(_M_MRX000) || defined(_M_IX86) || defined(_M_ALPHA) || defined(_M_PPC)) && !defined(MIDL_PASS) +# define CALLBACK __stdcall +# else +# define CALLBACK +# endif +# endif +# ifndef WINGDIAPI +# define VGUI_WINGDIAPI_DEFINED +# define WINGDIAPI __declspec(dllimport) +# endif +# ifndef _WCHAR_T_DEFINED +typedef unsigned short wchar_t; +# define _WCHAR_T_DEFINED +# endif +# pragma comment(lib,"opengl32.lib") +#endif + +#include +#include + +#ifdef VGUI_APIENTRY_DEFINED +# undef VGUI_APIENTRY_DEFINED +# undef APIENTRY +#endif + +#ifdef VGUI_CALLBACK_DEFINED +# undef VGUI_CALLBACK_DEFINED +# undef CALLBACK +#endif + +#ifdef VGUI_WINGDIAPI_DEFINED +# undef VGUI_WINGDIAPI_DEFINED +# undef WINGDIAPI +#endif + + + +#include +#include +#include + +namespace vgui +{ + +class VGUIAPI SurfaceGL : public Surface +{ +public: + SurfaceGL(Panel* embeddedPanel); +public: + virtual void createPopup(Panel* embeddedPanel); +protected: + virtual bool recreateContext(); + virtual void pushMakeCurrent(Panel* panel,bool useInsets); + virtual void popMakeCurrent(Panel* panel); + virtual void makeCurrent(); + virtual void swapBuffers(); + virtual void setColor(int r,int g,int b); + virtual void filledRect(int x0,int y0,int x1,int y1); + virtual void outlinedRect(int x0,int y0,int x1,int y1); + virtual void setTextFont(Font* font); + virtual void setTextColor(int r,int g,int b); + virtual void setDrawPos(int x,int y); + virtual void printText(const char* str,int strlen); + virtual void setTextureRGBA(int id,const char* rgba,int wide,int tall); + virtual void setTexture(int id); + virtual void texturedRect(int x0,int y0,int x1,int y1); +protected: + int _drawPos[2]; + uchar _drawColor[3]; + uchar _drawTextColor[3]; +}; + +} + +#endif + diff --git a/utils/vgui/include/VGUI_TabPanel.h b/utils/vgui/include/VGUI_TabPanel.h new file mode 100644 index 0000000..f80bca5 --- /dev/null +++ b/utils/vgui/include/VGUI_TabPanel.h @@ -0,0 +1,49 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_TABPANEL_H +#define VGUI_TABPANEL_H + +#include +#include + +namespace vgui +{ + +class ButtonGroup; + +class VGUIAPI TabPanel : public Panel +{ +public: + enum TabPlacement + { + tp_top=0, + tp_bottom, + tp_left, + tp_right, + }; +public: + TabPanel(int x,int y,int wide,int tall); +public: + virtual Panel* addTab(const char* text); + virtual void setSelectedTab(Panel* tab); + virtual void setSize(int wide,int tall); +protected: + virtual void recomputeLayoutTop(); + virtual void recomputeLayout(); +protected: + TabPlacement _tabPlacement; + Panel* _tabArea; + Panel* _clientArea; + Panel* _selectedTab; + Panel* _selectedPanel; + ButtonGroup* _buttonGroup; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_TablePanel.h b/utils/vgui/include/VGUI_TablePanel.h new file mode 100644 index 0000000..3a76f7a --- /dev/null +++ b/utils/vgui/include/VGUI_TablePanel.h @@ -0,0 +1,71 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_TABLEPANEL_H +#define VGUI_TABLEPANEL_H + +#include +#include +#include + +namespace vgui +{ + +class HeaderPanel; + +class VGUIAPI TablePanel : public Panel +{ +friend class FooVGuiTablePanelHandler; +private: + vgui::Dar _columnDar; + bool _gridVisible[2]; + int _gridWide; + int _gridTall; + int _selectedCell[2]; + int _mouseOverCell[2]; + int _editableCell[2]; + Panel* _fakeInputPanel; + bool _columnSelectionEnabled; + bool _rowSelectionEnabled; + bool _cellSelectionEnabled; + Panel* _editableCellPanel; + int _virtualSize[2]; + bool _cellEditingEnabled; +public: + TablePanel(int x,int y,int wide,int tall,int columnCount); +public: + virtual void setCellEditingEnabled(bool state); + virtual void setColumnCount(int columnCount); + virtual void setGridVisible(bool horizontal,bool vertical); + virtual void setGridSize(int gridWide,int gridTall); + virtual int getColumnCount(); + virtual void setColumnExtents(int column,int x0,int x1); + virtual void setSelectedCell(int column,int row); + virtual void getSelectedCell(int& column,int& row); + virtual void setHeaderPanel(HeaderPanel* header); + virtual void setColumnSelectionEnabled(bool state); + virtual void setRowSelectionEnabled(bool state); + virtual void setCellSectionEnabled(bool state); + virtual void setEditableCell(int column,int row); + virtual void stopCellEditing(); + virtual void getVirtualSize(int& wide,int& tall); + virtual int getRowCount()=0; + virtual int getCellTall(int row)=0; + virtual Panel* getCellRenderer(int column,int row,bool columnSelected,bool rowSelected,bool cellSelected)=0; + virtual Panel* startCellEditing(int column,int row)=0; +protected: + virtual void paint(); + virtual Panel* isWithinTraverse(int x,int y); +private: + virtual void privateMousePressed(MouseCode code,Panel* panel); + virtual void privateMouseDoublePressed(MouseCode code,Panel* panel); + virtual void privateKeyTyped(KeyCode code,Panel* panel); +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_TaskBar.h b/utils/vgui/include/VGUI_TaskBar.h new file mode 100644 index 0000000..e9ec49d --- /dev/null +++ b/utils/vgui/include/VGUI_TaskBar.h @@ -0,0 +1,37 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_TASKBAR_H +#define VGUI_TASKBAR_H + +#include +#include +#include + +namespace vgui +{ + +class Frame; +class Button; + +class VGUIAPI TaskBar : public Panel +{ +public: + TaskBar(int x,int y,int wide,int tall); +public: + virtual void addFrame(Frame* frame); +protected: + virtual void performLayout(); +protected: + Dar _frameDar; + Dar _taskButtonDar; + Panel* _tray; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_TextEntry.h b/utils/vgui/include/VGUI_TextEntry.h new file mode 100644 index 0000000..682f000 --- /dev/null +++ b/utils/vgui/include/VGUI_TextEntry.h @@ -0,0 +1,80 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_TEXTENTRY_H +#define VGUI_TEXTENTRY_H + +#include +#include +#include + +namespace vgui +{ + +enum MouseCode; +enum KeyCode; +class ActionSignal; + +class VGUIAPI TextEntry : public Panel , public InputSignal +{ +public: + TextEntry(const char* text,int x,int y,int wide,int tall); +public: + virtual void setText(const char* text,int textLen); + virtual void getText(int offset,char* buf,int bufLen); + virtual void resetCursorBlink(); + virtual void doGotoLeft(); + virtual void doGotoRight(); + virtual void doGotoFirstOfLine(); + virtual void doGotoEndOfLine(); + virtual void doInsertChar(char ch); + virtual void doBackspace(); + virtual void doDelete(); + virtual void doSelectNone(); + virtual void doCopySelected(); + virtual void doPaste(); + virtual void doPasteSelected(); + virtual void doDeleteSelected(); + virtual void addActionSignal(ActionSignal* s); + virtual void setFont(Font* font); + virtual void setTextHidden(bool bHideText); +protected: + virtual void paintBackground(); + virtual void setCharAt(char ch,int index); +protected: + virtual void fireActionSignal(); + virtual bool getSelectedRange(int& cx0,int& cx1); + virtual bool getSelectedPixelRange(int& cx0,int& cx1); + virtual int cursorToPixelSpace(int cursorPos); + virtual void selectCheck(); +protected: //InputSignal + virtual void cursorMoved(int x,int y,Panel* panel); + virtual void cursorEntered(Panel* panel); + virtual void cursorExited(Panel* panel); + virtual void mousePressed(MouseCode code,Panel* panel); + virtual void mouseDoublePressed(MouseCode code,Panel* panel); + virtual void mouseReleased(MouseCode code,Panel* panel); + virtual void mouseWheeled(int delta,Panel* panel); + virtual void keyPressed(KeyCode code,Panel* panel); + virtual void keyTyped(KeyCode code,Panel* panel); + virtual void keyReleased(KeyCode code,Panel* panel); + virtual void keyFocusTicked(Panel* panel); +protected: + Dar _lineDar; + int _cursorPos; + bool _cursorBlink; + bool _hideText; + long _cursorNextBlinkTime; + int _cursorBlinkRate; + int _select[2]; + Dar _actionSignalDar; + Font* _font; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_TextGrid.h b/utils/vgui/include/VGUI_TextGrid.h new file mode 100644 index 0000000..beb7e52 --- /dev/null +++ b/utils/vgui/include/VGUI_TextGrid.h @@ -0,0 +1,42 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_TEXTGRID_H +#define VGUI_TEXTGRID_H + +#include +#include +#include + +namespace vgui +{ + +class VGUIAPI TextGrid : public Panel +{ +public: + TextGrid(int gridWide,int gridTall,int x,int y,int wide,int tall); +public: + virtual void setGridSize(int wide,int tall); + virtual void newLine(); + virtual void setXY(int x,int y); + //virtual void setBgColor(int r,int g,int b); + //virtual void setFgColor(int r,int g,int b); + virtual int vprintf(const char* format,va_list argList); + virtual int printf(const char* format,...); +protected: + virtual void paintBackground(); +protected: + int _xy[2]; + int _bgColor[3]; + int _fgColor[3]; + char* _grid; //[_gridSize[0]*_gridSize[1]*7] ch,br,bg,bb,fr,fg,fb + int _gridSize[2]; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_TextImage.h b/utils/vgui/include/VGUI_TextImage.h new file mode 100644 index 0000000..b11feaf --- /dev/null +++ b/utils/vgui/include/VGUI_TextImage.h @@ -0,0 +1,58 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_TEXTIMAGE_H +#define VGUI_TEXTIMAGE_H + +#include +#include +#include + + +//TODO: need to add wrapping flag instead of being arbitrary about when wrapping and auto-resizing actually happens +// This is probably why you are having problems if you had text in a constructor and then changed the font + +namespace vgui +{ + +class Panel; +class Font; +class App; + +class VGUIAPI TextImage : public Image +{ +private: + char* _text; + int _textBufferLen; + vgui::Scheme::SchemeFont _schemeFont; + vgui::Font* _font; + int _textColor[4]; + vgui::Scheme::SchemeColor _textSchemeColor; +public: + TextImage(int textBufferLen,const char* text); + TextImage(const char* text); +private: + virtual void init(int textBufferLen,const char* text); +public: + virtual void getTextSize(int& wide,int& tall); + virtual void getTextSizeWrapped(int& wide,int& tall); + virtual Font* getFont(); + virtual void setText(int textBufferLen,const char* text); + virtual void setText(const char* text); + virtual void setFont(vgui::Scheme::SchemeFont schemeFont); + virtual void setFont(vgui::Font* font); + virtual void setSize(int wide,int tall); +protected: + virtual void paint(Panel* panel); +}; + +} + +#endif + + + diff --git a/utils/vgui/include/VGUI_TextPanel.h b/utils/vgui/include/VGUI_TextPanel.h new file mode 100644 index 0000000..b000f7b --- /dev/null +++ b/utils/vgui/include/VGUI_TextPanel.h @@ -0,0 +1,46 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_TEXTPANEL_H +#define VGUI_TEXTPANEL_H + +#include +#include +#include + +//NOTE : If you are having trouble with this class, your problem is probably in TextImage + +namespace vgui +{ + +class TextImage; +class Font; + +class VGUIAPI TextPanel : public Panel +{ +private: + TextImage* _textImage; +public: + TextPanel(const char* text,int x,int y,int wide,int tall); +public: + virtual void setText(const char* text); + virtual void setFont(vgui::Scheme::SchemeFont schemeFont); + virtual void setFont(vgui::Font* font); + virtual void setSize(int wide,int tall); + virtual void setFgColor(int r,int g,int b,int a); + virtual void setFgColor(Scheme::SchemeColor sc); + virtual TextImage* getTextImage(); +protected: + virtual void paint(); +}; + +} + +#endif + + + diff --git a/utils/vgui/include/VGUI_TickSignal.h b/utils/vgui/include/VGUI_TickSignal.h new file mode 100644 index 0000000..bb0bcd5 --- /dev/null +++ b/utils/vgui/include/VGUI_TickSignal.h @@ -0,0 +1,22 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_TICKSIGNAL_H +#define VGUI_TICKSIGNAL_H + +#include + +namespace vgui +{ +class VGUIAPI TickSignal + { + public: + virtual void ticked()=0; + }; +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_ToggleButton.h b/utils/vgui/include/VGUI_ToggleButton.h new file mode 100644 index 0000000..0e6d8bc --- /dev/null +++ b/utils/vgui/include/VGUI_ToggleButton.h @@ -0,0 +1,26 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_TOGGLEBUTTON_H +#define VGUI_TOGGLEBUTTON_H + +#include +#include + +namespace vgui +{ + +class VGUIAPI ToggleButton : public Button +{ +public: + ToggleButton(const char* text,int x,int y,int wide,int tall); + ToggleButton(const char* text,int x,int y); +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_TreeFolder.h b/utils/vgui/include/VGUI_TreeFolder.h new file mode 100644 index 0000000..ab86cda --- /dev/null +++ b/utils/vgui/include/VGUI_TreeFolder.h @@ -0,0 +1,36 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_TREEFOLDER_H +#define VGUI_TREEFOLDER_H + +#include +#include + +namespace vgui +{ + +class VGUIAPI TreeFolder : public Panel +{ +public: + TreeFolder(const char* name); + TreeFolder(const char* name,int x,int y); +protected: + virtual void init(const char* name); +public: + virtual void setOpenedTraverse(bool state); + virtual void setOpened(bool state); + virtual bool isOpened(); +protected: + virtual void paintBackground(); +protected: + bool _opened; +}; + +} + +#endif \ No newline at end of file diff --git a/utils/vgui/include/VGUI_WizardPanel.h b/utils/vgui/include/VGUI_WizardPanel.h new file mode 100644 index 0000000..d2f7a2b --- /dev/null +++ b/utils/vgui/include/VGUI_WizardPanel.h @@ -0,0 +1,146 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +#ifndef VGUI_WIZARDPANEL_H +#define VGUI_WIZARDPANEL_H + +#include +#include + +namespace vgui +{ + +class ActionSignal; + +class VGUIAPI WizardPanel : public Panel +{ + +public: + + class VGUIAPI WizardPage : public Panel + { + + friend class WizardPanel; + + private: + + WizardPage* _backWizardPage; + WizardPage* _nextWizardPage; + bool _backButtonEnabled; + bool _nextButtonEnabled; + bool _finishedButtonEnabled; + bool _cancelButtonEnabled; + bool _backButtonVisible; + bool _nextButtonVisible; + bool _finishedButtonVisible; + bool _cancelButtonVisible; + char* _backButtonText; + char* _nextButtonText; + char* _finishedButtonText; + char* _cancelButtonText; + Dar _switchingToBackPageSignalDar; + Dar _switchingToNextPageSignalDar; + char* _title; + Panel* _wantedFocus; + + private: + + virtual void fireSwitchingToBackPageSignals(); + virtual void fireSwitchingToNextPageSignals(); + virtual void init(); + + public: + + WizardPage(); + WizardPage(int wide,int tall); + + public: + + virtual void setBackWizardPage(WizardPage* backWizardPage); + virtual void setNextWizardPage(WizardPage* nextWizardPage); + virtual WizardPage* getBackWizardPage(); + virtual WizardPage* getNextWizardPage(); + + virtual bool isBackButtonEnabled(); + virtual bool isNextButtonEnabled(); + virtual bool isFinishedButtonEnabled(); + virtual bool isCancelButtonEnabled(); + virtual void setBackButtonEnabled(bool state); + virtual void setNextButtonEnabled(bool state); + virtual void setFinishedButtonEnabled(bool state); + virtual void setCancelButtonEnabled(bool state); + + virtual bool isBackButtonVisible(); + virtual bool isNextButtonVisible(); + virtual bool isFinishedButtonVisible(); + virtual bool isCancelButtonVisible(); + virtual void setBackButtonVisible(bool state); + virtual void setNextButtonVisible(bool state); + virtual void setFinishedButtonVisible(bool state); + virtual void setCancelButtonVisible(bool state); + + virtual void getBackButtonText(char* text,int textLen); + virtual void getNextButtonText(char* text,int textLen); + virtual void getFinishedButtonText(char* text,int textLen); + virtual void getCancelButtonText(char* text,int textLen); + virtual void setBackButtonText(const char* text); + virtual void setNextButtonText(const char* text); + virtual void setFinishedButtonText(const char* text); + virtual void setCancelButtonText(const char* text); + + virtual void setWantedFocus(Panel* panel); + virtual Panel* getWantedFocus(); + + virtual void addSwitchingToBackPageSignal(ActionSignal* s); + virtual void addSwitchingToNextPageSignal(ActionSignal* s); + + virtual void setTitle(const char* title); + virtual void getTitle(char* buf,int bufLen); + + }; + +private: + + Button* _backButton; + Button* _nextButton; + Button* _finishedButton; + Button* _cancelButton; + WizardPage* _currentWizardPage; + Dar _pageChangedActionSignalDar; + +private: + + virtual void fireFinishedActionSignal(); + virtual void fireCancelledActionSignal(); + virtual void firePageChangedActionSignal(); + +protected: + + virtual void performLayout(); + +public: + + WizardPanel(int x,int y,int wide,int tall); + +public: + + virtual void setCurrentWizardPage(WizardPage* currentWizardPage); + virtual void addFinishedActionSignal(ActionSignal* s); + virtual void addCancelledActionSignal(ActionSignal* s); + virtual void addPageChangedActionSignal(ActionSignal* s); + virtual void doBack(); + virtual void doNext(); + virtual void getCurrentWizardPageTitle(char* buf,int bufLen); + virtual WizardPage* getCurrentWizardPage(); + +}; + +} + + +#endif + diff --git a/utils/vgui/lib/win32_vc6/vgui.lib b/utils/vgui/lib/win32_vc6/vgui.lib new file mode 100644 index 0000000..aff7716 Binary files /dev/null and b/utils/vgui/lib/win32_vc6/vgui.lib differ